0%

Java 语法速览

基本语法

一、基本数据类型

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字节,值为 truefalse

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. 条件语句

条件语句用于根据条件判断执行不同的代码块。

  • if 语句
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");
}
  • switch 语句
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. 循环语句

  • for 循环
1
2
3
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
  • while 循环
1
2
3
while (x > 0) {
x--;
}
  • do-while 循环
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]); // 输出:1
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(); // 输出:Dog barks

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()); // 输出:10

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); // 合法
// printNumber("String"); // 编译错误

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
// 使用Lambda表达式实现Runnable接口
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 表达式常用于集合框架中的操作,如 forEachfiltermap 等方法:

1
2
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.forEach(s -> System.out.println(s)); // 使用 Lambda 表达式打印列表元素

五、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. 常用操作

  • 过滤(filter:返回符合条件的元素。
1
2
3
4
List<String> filtered = list.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
System.out.println(filtered); // 输出:["apple"]
  • 映射(map:将元素转换为其他形式。
1
2
3
4
List<Integer> lengths = list.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); // 输出:[5, 6, 6]
  • 聚合(reduce:对元素进行聚合操作。
1
2
3
4
int sum = list.stream()
.map(String::length)
.reduce(0, Integer::sum);
System.out.println(sum); // 输出:17
  • 排序(sorted:对 Stream 进行排序。
1
2
3
4
List<String> sorted = list.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sorted); // 输出:["apple", "banana", "cherry"]

六、并发编程

Java 提供了多线程和并发编程的支持,能够在多个处理器上并行执行任务,提升性能。

1. 线程的创建

  • 继承 Thread
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();
  • 实现 Runnable 接口
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)); // 输出:"Hello"

八、动态代理(Dynamic Proxy)

动态代理是 Java 反射机制的一个重要应用,允许在运行时创建接口的代理对象,并将方法调用转发给一个处理器(通常是一个 InvocationHandler)。这使得开发者可以在运行时动态地对方法调用进行增强、拦截或代理。

1. Java 动态代理的基本概念

动态代理允许你在运行时为一个或多个接口生成代理类,而无需手动编写实现类。代理对象会拦截方法调用并将其传递给 InvocationHandler 进行处理。

  • Proxy:通过 Proxy 类的 newProxyInstance 方法创建代理对象。
  • InvocationHandler 接口:用于定义方法调用的具体处理逻辑。

2. 动态代理的使用步骤

  1. 创建接口:定义代理对象所实现的接口。
  2. 实现 InvocationHandler 接口:通过 InvocationHandler 实现自定义的逻辑。
  3. 通过 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();

// 创建 InvocationHandler
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 会委托给 InvocationHandlerinvoke 方法。
  • **InvocationHandler**:处理具体的业务逻辑,例如执行原方法、记录日志、性能统计等。

5. 动态代理的应用场景

动态代理非常适合以下场景:

  • AOP(面向切面编程):Spring 等框架使用动态代理来实现切面功能,如事务管理、权限控制等。
  • 日志记录:通过动态代理在方法执行前后记录日志。
  • 延迟加载:代理对象可以在调用方法时延迟加载实际的数据或资源。
  • 权限控制:代理方法可以用于在执行前后进行权限检查。

6. JDK 动态代理与 CGLIB 代理

JDK 动态代理只能代理接口,不能代理类。如果需要代理类本身(包括没有接口的类),则可以使用 CGLIB(Code Generation Library)库,它可以生成子类来实现代理。

  • JDK 动态代理:只适用于接口。
  • CGLIB 代理:适用于类(通过继承方式创建代理类)。

在 Spring 中,默认使用 JDK 动态代理,如果类没有实现接口,则会使用 CGLIB 代理。