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 {};
}
元注解详解:
-
@Target:指定注解的使用范围
public enum ElementType {
TYPE, // 类、接口、枚举
FIELD, // 字段
METHOD, // 方法
PARAMETER, // 参数
CONSTRUCTOR, // 构造函数
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE,// 注解类型
PACKAGE, // 包
TYPE_PARAMETER, // 类型参数
TYPE_USE // 类型使用
} -
@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 使用建议
-
泛型使用建议:
- 优先使用泛型而不是原始类型
- 合理使用通配符
- 避免过度复杂的泛型设计
-
注解使用建议:
- 选择合适的保留策略
- 不要在注解中存储太多信息
- 考虑使用组合注解
-
反射使用建议:
- 缓存反射获取的对象
- 注意异常处理
- 考虑性能影响
-
枚举使用建议:
- 用于表示固定的常量集合
- 避滥用枚举
- 利用枚举的高级特性
7.2 常见陷阱
- 类型擦除导致的类型转换异常
- 反射操作私有成员时的安全限制
- 注解的生命周期管理
- 枚举序列化的一致性问题
通过掌握这些 Java 高级特性,你将能够编写更加灵活、安全和高效的 Java 程序。这些特性不仅是面试的重点,也是实际项目开发中的重要工具。