跳到主要内容

Java 高级语法特性

Java 高级特性是面试和实际开发中的重点内容,掌握这些特性能够让你写出更加优雅、安全和高效的代码。本文将详细介绍 Java 中最重要的几个高级特性:泛型、注解、反射、枚举等。

1. 泛型(Generics)

1.1 什么是泛型

泛型是 Java 5 引入的重要特性,它允许在定义类、接口和方法时使用类型参数。泛型提供了编译时的类型安全检查,避免了强制类型转换。

// 泛型类定义
public class Box {
private T content;

public void set(T content) {
this.content = content;
}

public T get() {
return content;
}
}

// 使用泛型类
Box stringBox = new Box<>();
stringBox.set("Hello String");
String content = stringBox.get(); // 无需强制转换

1.2 泛型的优势

1. 类型安全:在编译时就能发现类型错误

List list = new ArrayList(); // 原始类型
list.add("Hello");
list.add(123); // 编译通过,但运行时可能出错

List<String> stringList = new ArrayList<>(); // 泛型类型
stringList.add("Hello");
stringList.add(123); // 编译错误,类型不匹配

2. 消除强制转换

// 不使用泛型
List list = new ArrayList();
list.add("Hello");
String text = (String) list.get(0); // 需要强制转换

// 使用泛型
List<String> list = new ArrayList<>();
list.add("Hello");
String text = list.get(0); // 无需强制转换

1.3 泛型方法

public class GenericMethods {
// 泛型方法
public static T getMax(T[] array) {
if (array == null || array.length == 0) {
return null;
}

T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0) { // 要求T实现Comparable接口
max = array[i];
}
}
return max;
}

public static void main(String[] args) {
Integer[] numbers = {1, 5, 3, 9, 2};
Integer maxNumber = getMax(numbers); // 自动推断类型

String[] words = {"apple", "banana", "cherry"};
String maxWord = getMax(words); // 自动推断类型
}
}

1.4 类型通配符

上界通配符(? extends T)

public void processList(List<? extends Number> list) {
// 可以读取,不能写入(除了null)
Number number = list.get(0); // OK
// list.add(123); // 编译错误
}

// 使用示例
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
processList(intList); // OK
processList(doubleList); // OK

下界通配符(? super T)

public void addToSuperList(List<? super Integer> list) {
// 可以写入Integer,读取为Object
list.add(123); // OK
// Integer num = list.get(0); // 编译错误,只能读为Object
Object obj = list.get(0); // OK
}

// 使用示例
List<Number> numberList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
addToSuperList(numberList); // OK
addToSuperList(objectList); // OK

1.5 类型擦除

Java 泛型在运行时会被擦除,这是为了向后兼容:

public class GenericClass<T> {
private T value;

public void setValue(T value) {
this.value = value;
}

public T getValue() {
return value;
}
}

// 编译后,泛型信息会被擦除
// 实际上类似于:
public class GenericClass {
private Object value;

public void setValue(Object value) {
this.value = value;
}

public Object getValue() {
return value;
}
}

2. 注解(Annotations)

2.1 什么是注解

注解是 Java 5 引入的元数据机制,它为代码提供额外的信息。注解不会直接影响程序的执行,但可以被编译器、开发工具或运行时框架使用。

// 内置注解示例
@Override
public String toString() {
return "This is a custom toString method";
}

@Deprecated
public void oldMethod() {
// 这个方法已经过时
}

@SuppressWarnings("unchecked")
public void someMethod() {
List list = new ArrayList(); // 忽略未检查类型警告
}

2.2 元注解

元注解是用来注解其他注解的注解:

import java.lang.annotation.*;

// 定义自定义注解
@Target(ElementType.METHOD) // 作用范围:方法
@Retention(RetentionPolicy.RUNTIME) // 保留策略:运行时
@Documented // 包含在Javadoc中
@Inherited // 可以被继承
public @interface MyAnnotation {
String value() default ""; // 元素
int count() default 1;
String[] authors() default {};
}

元注解详解

  1. @Target:指定注解的使用范围

    public enum ElementType {
    TYPE, // 类、接口、枚举
    FIELD, // 字段
    METHOD, // 方法
    PARAMETER, // 参数
    CONSTRUCTOR, // 构造函数
    LOCAL_VARIABLE, // 局部变量
    ANNOTATION_TYPE,// 注解类型
    PACKAGE, // 包
    TYPE_PARAMETER, // 类型参数
    TYPE_USE // 类型使用
    }
  2. @Retention:指定注解的保留策略

    public enum RetentionPolicy {
    SOURCE, // 源码级别,编译器丢弃
    CLASS, // 类文件级别,JVM丢弃
    RUNTIME // 运行时级别,可通过反射读取
    }

2.3 自定义注解

// 定义任务相关的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Task {
String name();
String description() default "";
int priority() default 5;
String[] tags() default {};
}

// 使用自定义注解
public class TaskManager {
@Task(
name = "数据备份",
description = "执行数据库备份操作",
priority = 1,
tags = {"database", "backup", "maintenance"}
)
public void performBackup() {
System.out.println("正在执行数据备份...");
}

@Task(
name = "日志清理",
priority = 3,
tags = {"maintenance", "logs"}
)
public void cleanLogs() {
System.out.println("正在清理日志文件...");
}
}

2.4 注解处理器

import java.lang.reflect.Method;

public class AnnotationProcessor {
public static void processTasks(Object obj) {
Class<?> clazz = obj.getClass();

for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Task.class)) {
Task taskAnnotation = method.getAnnotation(Task.class);

System.out.println("发现任务:");
System.out.println(" 名称: " + taskAnnotation.name());
System.out.println(" 描述: " + taskAnnotation.description());
System.out.println(" 优先级: " + taskAnnotation.priority());
System.out.println(" 标签: " + String.join(", ", taskAnnotation.tags()));

try {
method.invoke(obj); // 执行任务
} catch (Exception e) {
System.err.println("任务执行失败: " + e.getMessage());
}
}
}
}

public static void main(String[] args) {
TaskManager manager = new TaskManager();
processTasks(manager);
}
}

3. 反射(Reflection)

3.1 什么是反射

反射是 Java 的强大特性,允许程序在运行时检查和修改自身的结构和行为。通过反射,我们可以:

  • 获取类的信息(字段、方法、构造函数等)
  • 动态创建对象
  • 动态调用方法
  • 动态修改字段值
public class ReflectionExample {
private String name;
private int age;

public ReflectionExample(String name, int age) {
this.name = name;
this.age = age;
}

public void sayHello() {
System.out.println("Hello, I'm " + name + ", " + age + " years old.");
}

private void secretMethod() {
System.out.println("This is a secret method!");
}
}

3.2 获取 Class 对象

// 获取 Class 对象的三种方式
public class GetClassExample {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1:通过对象的 getClass() 方法
String str = "Hello";
Class<?> class1 = str.getClass();

// 方式2:通过 .class 属性
Class<?> class2 = String.class;

// 方式3:通过 Class.forName() 方法
Class<?> class3 = Class.forName("java.lang.String");

System.out.println(class1 == class2); // true
System.out.println(class2 == class3); // true
}
}

3.3 反射操作

import java.lang.reflect.*;

public class AdvancedReflection {
public static void demonstrateReflection() throws Exception {
// 获取 ReflectionExample 的 Class 对象
Class<?> clazz = ReflectionExample.class;

// 获取构造函数并创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object instance = constructor.newInstance("张三", 25);

// 获取并调用公共方法
Method publicMethod = clazz.getMethod("sayHello");
publicMethod.invoke(instance);

// 获取并调用私有方法
Method privateMethod = clazz.getDeclaredMethod("secretMethod");
privateMethod.setAccessible(true); // 取消访问控制检查
privateMethod.invoke(instance);

// 获取并修改私有字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(instance, "李四");
System.out.println("修改后的名字: " + nameField.get(instance));

// 获取所有字段信息
System.out.println("\n类的所有字段:");
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
System.out.println(field.getName() + ": " + field.getType().getSimpleName() +
" = " + field.get(instance));
}
}
}

3.4 反射的实际应用

1. 框架开发中的应用

// 简单的依赖注入示例
public class DIContainer {
private Map<Class<?>, Object> instances = new HashMap<>();

public void register(Class<?> interfaceType, Class<?> implementationType) {
try {
Object instance = implementationType.newInstance();
instances.put(interfaceType, instance);
} catch (Exception e) {
throw new RuntimeException("注册失败", e);
}
}

@SuppressWarnings("unchecked")
public T get(Class<?> type) {
return (T) instances.get(type);
}
}

// 使用示例
interface UserService {
void addUser(String name);
}

class UserServiceImpl implements UserService {
public void addUser(String name) {
System.out.println("添加用户: " + name);
}
}

// 在配置中使用
DIContainer container = new DIContainer();
container.register(UserService.class, UserServiceImpl.class);
UserService userService = container.get(UserService.class);
userService.addUser("张三");

2. 动态代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DynamicProxyExample {
public static void main(String[] args) {
// 创建真实对象
UserService realService = new UserServiceImpl();

// 创建动态代理
UserService proxyService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(realService, args);
System.out.println("After method: " + method.getName());
return result;
}
}
);

proxyService.addUser("李四");
}
}

4. 枚举(Enums)

4.1 什么是枚举

枚举是 Java 5 引入的特殊类,用于表示一组固定的常量。枚举提供了类型安全和更多功能。

// 定义颜色枚举
public enum Color {
RED, GREEN, BLUE
}

// 使用枚举
public class EnumExample {
public static void main(String[] args) {
Color favoriteColor = Color.RED;

// 枚举的比较
if (favoriteColor == Color.RED) {
System.out.println("最喜欢的颜色是红色");
}

// 遍历所有枚举值
for (Color color : Color.values()) {
System.out.println(color + " 的序号是: " + color.ordinal());
}
}
}

4.2 高级枚举用法

public enum Operation {
ADD("+") {
@Override
public double apply(double x, double y) {
return x + y;
}
},
SUBTRACT("-") {
@Override
public double apply(double x, double y) {
return x - y;
}
},
MULTIPLY("*") {
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
@Override
public double apply(double x, double y) {
if (y == 0) throw new ArithmeticException("除数不能为0");
return x / y;
}
};

private final String symbol;

Operation(String symbol) {
this.symbol = symbol;
}

public String getSymbol() {
return symbol;
}

// 抽象方法,每个枚举值必须实现
public abstract double apply(double x, double y);

// 工具方法
public static Operation fromSymbol(String symbol) {
for (Operation op : values()) {
if (op.symbol.equals(symbol)) {
return op;
}
}
throw new IllegalArgumentException("未知的操作符号: " + symbol);
}
}

4.3 枚举的实际应用

public enum HttpStatus {
OK(200, "OK"),
NOT_FOUND(404, "Not Found"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error");

private final int code;
private final String message;

HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}

public int getCode() {
return code;
}

public String getMessage() {
return message;
}

public static HttpStatus fromCode(int code) {
for (HttpStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("未知的HTTP状态码: " + code);
}
}

// 使用示例
public class HttpStatusExample {
public static void handleResponse(int statusCode) {
HttpStatus status = HttpStatus.fromCode(statusCode);

switch (status) {
case OK:
System.out.println("请求成功");
break;
case NOT_FOUND:
System.out.println("资源未找到");
break;
case INTERNAL_SERVER_ERROR:
System.out.println("服务器内部错误");
break;
default:
System.out.println("未知状态");
}
}
}

5. 综合实战案例

5.1 简单的依赖注入框架

// 自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}

// 简单的容器实现
public class SimpleContainer {
private final Map<Class<?>, Object> instances = new ConcurrentHashMap<>();

public void scan(String basePackage) throws Exception {
// 扫描指定包下的类
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Component.class));

for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
Class<?> clazz = Class.forName(bd.getBeanClassName());
register(clazz);
}
}

private void register(Class<?> clazz) throws Exception {
Object instance = createInstance(clazz);
instances.put(clazz, instance);
}

private Object createInstance(Class<?> clazz) throws Exception {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object instance = constructor.newInstance();

// 依赖注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
Object dependency = getDependency(field.getType());
field.set(instance, dependency);
}
}

return instance;
}

private Object getDependency(Class<?> type) throws Exception {
return instances.computeIfAbsent(type, this::createInstance);
}

@SuppressWarnings("unchecked")
public T getBean(Class<?> type) {
return (T) instances.get(type);
}
}

5.2 配置驱动的任务调度器

// 任务注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ScheduledTask {
String cron(); // cron表达式
String description() default "";
}

// 任务调度器
public class TaskScheduler {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);

public void registerTasks(Object taskObject) {
Class<?> clazz = taskObject.getClass();

for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(ScheduledTask.class)) {
ScheduledTask annotation = method.getAnnotation(ScheduledTask.class);
scheduleTask(taskObject, method, annotation);
}
}
}

private void scheduleTask(Object target, Method method, ScheduledTask annotation) {
Runnable task = () -> {
try {
method.setAccessible(true);
method.invoke(target);
} catch (Exception e) {
System.err.println("任务执行失败: " + e.getMessage());
}
};

// 简化的调度逻辑(实际应该解析cron表达式)
long delay = calculateDelay(annotation.cron());
scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.SECONDS);

System.out.println("已注册任务: " + annotation.description());
}

private long calculateDelay(String cron) {
// 简化版本,实际应该解析完整的cron表达式
return 10; // 10秒间隔
}

public void shutdown() {
scheduler.shutdown();
}
}

// 使用示例
@Component
public class ScheduledTasks {

@ScheduledTask(cron = "0 */5 * * * ?", description = "数据备份任务")
public void backupData() {
System.out.println("执行数据备份: " + LocalDateTime.now());
}

@ScheduledTask(cron = "0 */10 * * * ?", description = "日志清理任务")
public void cleanLogs() {
System.out.println("清理日志文件: " + LocalDateTime.now());
}
}

6. 面试重点总结

6.1 泛型相关面试题

Q1: 什么是类型擦除?

  • Java 泛型在编译时会擦除类型信息
  • 主要是为了向后兼容
  • 运行时无法获取泛型类型信息

Q2: "? extends T" 和 "? super T" 的区别?

  • ? extends T:上界通配符,只能读取,不能写入(生产者)
  • ? super T:下界通配符,只能写入,不能读取(消费者)

6.2 注解相关面试题

Q1: 元注解有哪些?

  • @Target:指定作用范围
  • @Retention:指定保留策略
  • @Documented:包含在文档中
  • @Inherited:可以被子类继承

Q2: 注解在什么时候生效?

  • SOURCE:编译时丢弃
  • CLASS:类文件保留,运行时丢弃
  • RUNTIME:运行时可通过反射获取

6.3 反射相关面试题

Q1: 反射的优缺点? 优点:

  • 动态性,运行时操作
  • 框架开发的基础
  • 解耦和扩展性强

缺点:

  • 性能开销较大
  • 破坏封装性
  • 安全性风险

Q2: 反射的应用场景?

  • 框架开发(Spring、MyBatis等)
  • 动态代理
  • 序列化/反序列化
  • 开发工具和调试

6.4 枚举相关面试题

Q1: 枚举相比常量的优势?

  • 类型安全
  • 可以包含方法和字段
  • 支持switch语句
  • 内置的序列化机制

Q2: 枚举是单例吗?

  • 枚举类型本身是单例的
  • 但每个枚举值都是该类的实例

7. 最佳实践和注意事项

7.1 使用建议

  1. 泛型使用建议

    • 优先使用泛型而不是原始类型
    • 合理使用通配符
    • 避免过度复杂的泛型设计
  2. 注解使用建议

    • 选择合适的保留策略
    • 不要在注解中存储太多信息
    • 考虑使用组合注解
  3. 反射使用建议

    • 缓存反射获取的对象
    • 注意异常处理
    • 考虑性能影响
  4. 枚举使用建议

    • 用于表示固定的常量集合
    • 避滥用枚举
    • 利用枚举的高级特性

7.2 常见陷阱

  1. 类型擦除导致的类型转换异常
  2. 反射操作私有成员时的安全限制
  3. 注解的生命周期管理
  4. 枚举序列化的一致性问题

通过掌握这些 Java 高级特性,你将能够编写更加灵活、安全和高效的 Java 程序。这些特性不仅是面试的重点,也是实际项目开发中的重要工具。