基本语法
一、基本数据类型
Java 是强类型语言,它要求变量在使用之前必须声明类型。Java 提供了八种基本数据类型:
1. 整数类型
byte
:1字节,-128 ~ 127
short
:2字节,-32,768 ~ 32,767
int
:4字节,-2^31 ~ 2^31 - 1
long
:8字节,-2^63 ~ 2^63 - 1
2. 浮点类型
float
:4字节,单精度浮点数
double
:8字节,双精度浮点数
3. 字符类型
char
:2字节,用于表示单个字符,Unicode 编码。范围:\u0000
~ \uffff
。
4. 布尔类型
boolean
:1字节,值为 true
或 false
。
5. 字符串类型
String
:虽然 String
是一个对象类型,但它常用于存储文本数据。
二、变量声明与赋值
1. 声明变量
变量必须先声明后使用。声明时需要指定变量的类型。
1 2 3 4 5
| int x = 10; float pi = 3.14f; boolean isValid = true; char letter = 'A'; String message = "Hello, Java!";
|
2. 常量
常量使用 final
关键字声明,一旦赋值后,值不能改变。
1
| final int MAX_SIZE = 100;
|
三、运算符
Java 提供了丰富的运算符,主要包括:
1. 算术运算符
2. 关系运算符
==
:等于
!=
:不等于
>
:大于
<
:小于
>=
:大于等于
<=
:小于等于
3. 逻辑运算符
&&
:与运算(AND)
||
:或运算(OR)
!
:非运算(NOT)
4. 赋值运算符
=
:赋值
+=
:加法赋值
-=
:减法赋值
*=
:乘法赋值
/=
:除法赋值
四、控制结构
1. 条件语句
条件语句用于根据条件判断执行不同的代码块。
1 2 3 4 5 6 7
| if (x > 0) { System.out.println("Positive"); } else if (x == 0) { System.out.println("Zero"); } else { System.out.println("Negative"); }
|
1 2 3 4 5 6 7 8 9 10
| switch (day) { case 1: System.out.println("Monday"); break; case 2: System.out.println("Tuesday"); break; default: System.out.println("Invalid day"); }
|
2. 循环语句
1 2 3
| for (int i = 0; i < 10; i++) { System.out.println(i); }
|
1 2 3
| do { x++; } while (x < 10);
|
五、数组
数组是存储相同类型元素的集合。
1. 声明和初始化数组
1 2 3 4 5
| int[] numbers = {1, 2, 3, 4, 5}; String[] fruits = new String[3]; fruits[0] = "Apple"; fruits[1] = "Banana"; fruits[2] = "Cherry";
|
2. 访问数组元素
1 2
| System.out.println(numbers[0]); int length = numbers.length;
|
六、方法与函数
1. 方法声明
方法用于封装一段可重复使用的代码。
1 2 3
| public returnType methodName(parameters) { }
|
2. 方法调用
1
| int result = add(10, 20);
|
3. 方法重载
方法重载是指同名方法根据参数类型或数量不同而区分:
1 2 3 4 5 6 7
| public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }
|
七、类与对象
1. 定义类
Java 是一种面向对象的编程语言,所有的操作都是通过对象来完成的。类是对象的模板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Person { String name; int age;
public Person(String name, int age) { this.name = name; this.age = age; }
public void introduce() { System.out.println("My name is " + name + " and I'm " + age + " years old."); } }
|
2. 创建对象
1 2
| Person p = new Person("Alice", 30); p.introduce();
|
3. 构造方法
构造方法用于初始化对象的状态,名称与类名相同,不返回任何类型。
1 2 3 4
| Person(String name, int age) { this.name = name; this.age = age; }
|
八、面向对象特性
1. 继承
Java 支持类的继承,一个类可以继承另一个类的属性和方法。
1 2 3 4 5 6 7 8 9 10 11 12
| class Animal { public void speak() { System.out.println("Animal speaks"); } }
class Dog extends Animal { @Override public void speak() { System.out.println("Dog barks"); } }
|
2. 多态
多态是指同一个方法调用,可以根据对象的不同而表现出不同的行为。
1 2
| Animal animal = new Dog(); animal.speak();
|
3. 封装
封装是指将数据和对数据的操作封装到一个类中,保护数据不被外界直接修改。
1 2 3 4 5 6 7 8 9 10
| class Person { private String name; private int age;
public String getName() { return name; } public void setName(String name) { this.name = name; }
public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
|
4. 抽象类与接口
- 抽象类:使用
abstract
关键字,不能实例化,可以有具体的方法实现。
1 2 3
| abstract class Animal { public abstract void sound(); }
|
- 接口:接口只定义方法签名,类通过
implements
关键字实现接口。
1 2 3 4 5 6 7 8 9
| interface Animal { void sound(); }
class Dog implements Animal { public void sound() { System.out.println("Bark"); } }
|
九、异常处理
1. 基本结构
1 2 3 4 5 6 7
| try { } catch (ExceptionType e) { } finally { }
|
2. 常见异常类型
NullPointerException
:空指针异常
ArrayIndexOutOfBoundsException
:数组下标越界
IOException
:输入输出异常
十、输入与输出
1. 控制台输入与输出
1
| System.out.println("Hello, Java!");
|
1 2 3
| import java.util.Scanner; Scanner scanner = new Scanner(System.in); int number = scanner.nextInt();
|
高级特性
一、反射(Reflection)
反射是 Java 的一项强大功能,它允许程序在运行时动态地查询类的信息、创建对象、访问字段和方法、甚至修改类的行为。反射机制使得 Java 程序能够更加灵活和动态,适用于许多框架和工具。
1. 获取类的信息
反射通过 Class
类来获取对象的类信息:
1 2
| Class<?> clazz = MyClass.class; System.out.println(clazz.getName());
|
2. 创建对象
使用反射可以动态创建对象,而无需在编译时知道类的具体类型:
1 2
| Class<?> clazz = Class.forName("com.example.MyClass"); Object obj = clazz.getDeclaredConstructor().newInstance();
|
3. 访问字段和方法
反射使得我们可以在运行时访问类的字段和方法:
1 2 3 4 5 6
| Field field = clazz.getDeclaredField("myField"); field.setAccessible(true); Object value = field.get(obj); Method method = clazz.getDeclaredMethod("myMethod", String.class); method.setAccessible(true); method.invoke(obj, "argument");
|
4. 反射的应用场景
- 动态代理:如 Java 的动态代理(
Proxy
)就使用了反射。
- ORM 框架:Hibernate 等 ORM 框架通过反射映射数据库表和 Java 对象。
- 单元测试:测试框架如 JUnit 会使用反射来执行测试方法。
二、泛型(Generics)
泛型是 Java 提供的一种机制,允许在类、接口和方法中使用类型参数,从而使代码更加通用,类型安全。泛型提供了类型检查、减少类型转换的错误并且增加了代码的复用性。
1. 泛型类
泛型类允许在类定义时指定类型参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Box<T> { private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; } }
Box<Integer> integerBox = new Box<>(); integerBox.setValue(10); System.out.println(integerBox.getValue());
|
2. 泛型方法
泛型不仅能用于类和接口,也可以用于方法:
1 2 3 4 5 6 7 8
| public <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } }
Integer[] intArray = {1, 2, 3}; printArray(intArray);
|
3. 泛型的边界
通过使用 extends
关键字可以为泛型设定边界,限制泛型的类型范围:
1 2 3 4 5 6 7
| public <T extends Number> void printNumber(T number) { System.out.println(number); }
printNumber(10); printNumber(3.14);
|
4. 通配符
通配符 ?
表示任何类型:
1 2 3 4 5
| public void printList(List<?> list) { for (Object item : list) { System.out.println(item); } }
|
5. 泛型的限制
- 泛型类和方法的类型参数在运行时会被擦除(Type Erasure)。因此,无法在运行时获取泛型的具体类型。
三、注解(Annotations)
注解是一种元数据,用于为程序元素(类、方法、字段、参数等)提供额外的信息。Java 提供了许多内置注解,也可以自定义注解。
1. 常见注解
@Override
:表示方法重写。
@Deprecated
:表示已废弃的方法。
@SuppressWarnings
:抑制编译器警告。
1 2 3 4
| @Override public void someMethod() { }
|
2. 自定义注解
1 2 3 4 5
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value() default "default"; }
|
3. 注解的应用
1 2 3
| Method method = MyClass.class.getMethod("myMethod"); MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); System.out.println(annotation.value());
|
- 框架应用:Spring、Hibernate 等框架大量使用注解来配置和管理对象。
四、Lambda 表达式
Lambda 表达式是 Java 8 引入的一种匿名函数形式,可以让你以更简洁的方式表达函数式接口。Lambda 表达式广泛应用于 Java 的流(Stream)和集合处理 API 中。
1. 基本语法
Lambda 表达式的基本语法如下:
1
| (parameters) -> expression
|
例如,传递一个简单的函数式接口:
1 2 3
| Runnable r = () -> System.out.println("Hello, Lambda!"); r.run();
|
2. Lambda 表达式与函数式接口
Lambda 表达式通常与函数式接口(仅包含一个抽象方法的接口)配合使用:
1 2 3 4 5 6 7
| @FunctionalInterface interface MyFunctionalInterface { void execute(); }
MyFunctionalInterface f = () -> System.out.println("Executing..."); f.execute();
|
3. Lambda 表达式的应用
Lambda 表达式常用于集合框架中的操作,如 forEach
、filter
、map
等方法:
1 2
| List<String> list = Arrays.asList("apple", "banana", "cherry"); list.forEach(s -> System.out.println(s));
|
五、Stream API
Java 8 引入了 Stream API,使得集合的操作更加简洁且具有函数式编程风格。Stream API 允许你声明式地操作数据集,进行过滤、映射、聚合等操作。
1. 创建 Stream
可以通过集合的 stream()
方法创建 Stream:
1 2
| List<String> list = Arrays.asList("apple", "banana", "cherry"); Stream<String> stream = list.stream();
|
2. 常用操作
1 2 3 4
| List<String> filtered = list.stream() .filter(s -> s.startsWith("a")) .collect(Collectors.toList()); System.out.println(filtered);
|
1 2 3 4
| List<Integer> lengths = list.stream() .map(String::length) .collect(Collectors.toList()); System.out.println(lengths);
|
1 2 3 4
| int sum = list.stream() .map(String::length) .reduce(0, Integer::sum); System.out.println(sum);
|
- 排序(
sorted
):对 Stream 进行排序。
1 2 3 4
| List<String> sorted = list.stream() .sorted() .collect(Collectors.toList()); System.out.println(sorted);
|
六、并发编程
Java 提供了多线程和并发编程的支持,能够在多个处理器上并行执行任务,提升性能。
1. 线程的创建
1 2 3 4 5 6 7
| class MyThread extends Thread { public void run() { System.out.println("Thread is running"); } } MyThread t = new MyThread(); t.start();
|
1 2 3 4 5 6 7
| class MyRunnable implements Runnable { public void run() { System.out.println("Runnable is running"); } } Thread t = new Thread(new MyRunnable()); t.start();
|
2. 线程池
Java 提供了线程池来管理线程,避免频繁创建和销毁线程的开销。
1 2 3
| ExecutorService executor = Executors.newFixedThreadPool(2); executor.submit(() -> System.out.println("Task executed")); executor.shutdown();
|
3. 同步
使用 synchronized
关键字来保证多个线程之间的互斥访问:
1 2 3
| public synchronized void increment() { counter++; }
|
或者使用 Lock
来显式控制锁:
1 2 3 4 5 6 7
| Lock lock = new ReentrantLock(); lock.lock(); try { } finally { lock.unlock(); }
|
4. volatile
volatile
是 Java 中的一个关键字,用于修饰变量,它在多线程编程中具有特殊的语义和重要的作用,以下是关于它的详细介绍:
基本概念
volatile
关键字可以保证变量的可见性和禁止指令重排序优化。当一个变量被声明为volatile
时,它会告诉编译器和处理器,这个变量可能会被多个线程同时访问,因此需要特殊处理。
可见性
- 在多线程环境中,每个线程都有自己的工作内存,变量的值可能会在工作内存和主内存之间进行拷贝。如果一个线程修改了一个普通变量的值,其他线程可能不会立即看到这个修改,因为它们可能还在使用自己工作内存中的旧值。
- 当一个变量被声明为
volatile
后,对该变量的写操作会立即刷新到主内存中,而对该变量的读操作会从主内存中重新获取最新的值,从而保证了变量在多个线程之间的可见性。
禁止指令重排序
- 为了提高程序的执行效率,编译器和处理器可能会对指令进行重排序。在单线程环境中,指令重排序不会影响程序的最终结果,但在多线程环境中,指令重排序可能会导致程序出现意外的结果。
volatile
关键字可以禁止指令重排序优化,从而保证代码的执行顺序与程序的编写顺序一致。具体来说,volatile
变量的写操作和读操作之间存在 happens-before 关系,即写操作一定先于读操作发生。
适用场景
- 状态标志位:当多个线程需要根据某个共享变量的状态来执行不同的操作时,可以使用
volatile
关键字来保证状态标志位的可见性。
- 单例模式:在双重检查锁定(DCL)实现的单例模式中,需要使用
volatile
关键字来保证实例变量的可见性和禁止指令重排序,从而确保单例对象的唯一性和正确性。
与synchronized
的区别
- 使用场景:
volatile
主要用于保证变量的可见性和禁止指令重排序,适用于对单个变量的读写操作;而synchronized
主要用于保证代码块或方法在同一时刻只能被一个线程访问,适用于对共享资源的并发访问控制。
- 性能开销:
volatile
的性能开销相对较小,因为它只涉及到变量的读写操作和指令重排序的控制;而synchronized
的性能开销相对较大,因为它涉及到线程的阻塞和唤醒等操作。
注意事项
volatile
并不能保证原子性,即对volatile
变量的读写操作在多线程环境中可能不是原子的,可能会出现数据不一致的情况。
volatile
变量的读写操作可能会比普通变量的读写操作慢一些,因为它需要保证可见性和禁止指令重排序。
七、Optional 类
Optional
类是 Java 8 引入的一个容器类,用于表示可能为 null
的值,从而避免 NullPointerException
。
1 2
| Optional<String> optional = Optional.of("Hello"); optional.ifPresent(s -> System.out.println(s));
|
八、动态代理(Dynamic Proxy)
动态代理是 Java 反射机制的一个重要应用,允许在运行时创建接口的代理对象,并将方法调用转发给一个处理器(通常是一个 InvocationHandler
)。这使得开发者可以在运行时动态地对方法调用进行增强、拦截或代理。
1. Java 动态代理的基本概念
动态代理允许你在运行时为一个或多个接口生成代理类,而无需手动编写实现类。代理对象会拦截方法调用并将其传递给 InvocationHandler
进行处理。
Proxy
类:通过 Proxy
类的 newProxyInstance
方法创建代理对象。
InvocationHandler
接口:用于定义方法调用的具体处理逻辑。
2. 动态代理的使用步骤
- 创建接口:定义代理对象所实现的接口。
- 实现
InvocationHandler
接口:通过 InvocationHandler
实现自定义的逻辑。
- 通过
Proxy.newProxyInstance()
创建代理对象:为接口创建代理实例。
3. 示例代码:创建一个动态代理
假设我们有一个接口 UserService
,我们要为它创建一个代理对象,在方法调用时记录日志。
1) 定义接口
1 2 3 4
| public interface UserService { void addUser(String username); void deleteUser(String username); }
|
2) 实现接口
这是接口的一个实现类,它模拟了真实的业务逻辑。
1 2 3 4 5 6 7 8 9 10 11
| public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("Adding user: " + username); }
@Override public void deleteUser(String username) { System.out.println("Deleting user: " + username); } }
|
3) 实现 InvocationHandler
接口
InvocationHandler
接口的 invoke
方法会在代理对象的方法被调用时执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;
public class LogInvocationHandler implements InvocationHandler { private final Object target;
public LogInvocationHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Method " + method.getName() + " is called with arguments: " + args); return method.invoke(target, args); } }
|
4) 创建代理对象
使用 Proxy.newProxyInstance()
创建动态代理对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.lang.reflect.Proxy;
public class ProxyTest { public static void main(String[] args) { UserService userService = new UserServiceImpl();
LogInvocationHandler handler = new LogInvocationHandler(userService);
UserService proxy = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, handler );
proxy.addUser("John"); proxy.deleteUser("John"); } }
|
5) 运行输出
1 2 3 4
| Method addUser is called with arguments: [John] Adding user: John Method deleteUser is called with arguments: [John] Deleting user: John
|
4. 代理的工作原理
- 代理对象:在运行时动态生成,继承自
Proxy
类。
- 方法拦截:当代理对象的方法被调用时,
Proxy
会委托给 InvocationHandler
的 invoke
方法。
- **
InvocationHandler
**:处理具体的业务逻辑,例如执行原方法、记录日志、性能统计等。
5. 动态代理的应用场景
动态代理非常适合以下场景:
- AOP(面向切面编程):Spring 等框架使用动态代理来实现切面功能,如事务管理、权限控制等。
- 日志记录:通过动态代理在方法执行前后记录日志。
- 延迟加载:代理对象可以在调用方法时延迟加载实际的数据或资源。
- 权限控制:代理方法可以用于在执行前后进行权限检查。
6. JDK 动态代理与 CGLIB 代理
JDK 动态代理只能代理接口,不能代理类。如果需要代理类本身(包括没有接口的类),则可以使用 CGLIB(Code Generation Library)库,它可以生成子类来实现代理。
- JDK 动态代理:只适用于接口。
- CGLIB 代理:适用于类(通过继承方式创建代理类)。
在 Spring 中,默认使用 JDK 动态代理,如果类没有实现接口,则会使用 CGLIB 代理。