Java基础

98 阅读6分钟

一、Java中的泛型作用

Java中的泛型主要用于增强代码的类型安全性代码的可读性,并减少代码的冗余性。泛型允许类、接口和方法操作对象时,能够指定对象的类型,并且可以在编译期进行类型检查,从而避免运行时的ClassCastException

泛型的主要作用:
  1. 类型安全

    • 泛型允许在编译时检查类型,而不是在运行时。它确保了在编译期间,程序不会插入错误的类型,避免了强制类型转换时可能出现的错误。
    • 例如,使用泛型之前,List只能存储Object类型的元素,需要进行强制类型转换;而使用泛型之后,可以明确指定存储的类型,避免了潜在的类型转换问题。
    // 使用泛型前:
    List list = new ArrayList();
    list.add("string");
    String str = (String) list.get(0); // 需要强制类型转换
    
    // 使用泛型后:
    List<String> list = new ArrayList<>();
    list.add("string");
    String str = list.get(0); // 无需强制类型转换,编译时检查类型
    
  2. 代码复用

    • 泛型允许编写与类型无关的通用代码,从而使代码更具通用性和可重用性。你可以使用泛型类或方法来处理不同类型的数据,而不需要为每种类型编写单独的代码。
    // 泛型方法
    public <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element);
        }
    }
    
  3. 消除强制类型转换

    • 泛型可以消除强制类型转换的需求,因为编译器会自动处理类型转换。这样不仅减少了代码的复杂性,还避免了潜在的类型转换错误。
  4. 增强代码可读性

    • 泛型允许程序员明确指定集合或方法所操作的类型,使代码更具可读性和可理解性。

二、泛型的类型擦除

1. 什么是类型擦除?

Java中的泛型是编译时的特性,在编译时使用泛型进行类型检查,而在运行时,由于Java的泛型实现是通过类型擦除(Type Erasure)来实现的,泛型信息会被擦除,所有泛型参数类型都被替换为其上界(如果没有上界,则替换为Object)。这意味着在运行时,泛型类型的具体类型信息将不可用。

  • 编译时:编译器会对泛型进行类型检查,确保类型安全。
  • 运行时:Java虚拟机(JVM)并不会保留泛型的类型信息,所有泛型类型参数在运行时都会被擦除。
2. 类型擦除的阶段

类型擦除发生在编译阶段。在编译时,编译器会将泛型的类型参数擦除,并生成相应的字节码。在运行时,JVM执行的代码已经是去除了泛型类型参数的代码。

  • 泛型类/方法在编译时进行类型检查,确保类型安全。
  • 编译后的字节码中,泛型信息被擦除,替换为具体的类型(通常为Object或类型参数的上界)。

例如,泛型类List<String>在编译后,类型信息<String>会被擦除,变成List,而在方法内部,所有泛型参数T都会被替换为它的上界(如果没有定义上界,则为Object)。

public class GenericClass<T> {
    private T value;

    public T getValue() {
        return value;
    }
}

编译后的字节码中,这段代码会被修改为:

public class GenericClass {
    private Object value;

    public Object getValue() {
        return value;
    }
}
3. 类型擦除后的约束和限制

由于类型擦除的存在,Java泛型有一些限制:

  • 不能使用基本数据类型作为泛型类型参数(例如,不能直接使用List<int>,只能使用List<Integer>)。
  • 泛型类型的运行时类型信息不可用,例如,instanceofnew T()之类的操作无法直接使用泛型类型。
  • 无法创建泛型数组,例如,new T[]是不允许的。

三、注解(Annotations)

1. 注解的作用

注解是Java中的一种元数据机制,允许在代码中嵌入一些额外的信息,并且这些信息可以被编译器、开发工具或运行时环境读取和使用。注解不会影响程序的实际业务逻辑,但可以用于代码生成编译时检查运行时反射等场景。

注解的主要作用如下:

  1. 代码标记:注解可以为类、方法、变量、参数等添加元数据信息。通过这些标记,工具和框架可以对代码进行特殊处理。

  2. 编译时检查:某些注解可以用于编译时检查错误或警告,例如@Override注解用于确保方法覆盖了父类的方法,@Deprecated注解表示方法或类已过时,编译器会发出警告。

    @Override
    public String toString() {
        return "This is a test";
    }
    
  3. 运行时反射:某些注解可以在运行时通过反射机制读取,从而影响程序的行为。例如,在Java EE、Spring等框架中,依赖注入、事务管理等机制都依赖于注解。

    @Autowired
    private UserService userService;  // Spring 中的依赖注入
    
  4. 代码生成:注解可以用于代码生成工具或框架中,帮助生成代码或配置。例如,Lombok库通过注解自动生成gettersetterequals等方法。

  5. 文档生成:注解可以用于自动生成文档。例如,@param@return等Javadoc注解用于生成API文档。

2. 常见的注解类型
  • 元注解:用于定义其他注解的注解。Java提供了4个元注解:

    • @Retention:定义注解的保留策略(源码、编译期、运行期)。
    • @Target:定义注解可以应用的目标(类、方法、字段等)。
    • @Inherited:允许子类继承父类的注解。
    • @Documented:标记该注解是否会在Javadoc中显示。
  • 编译时注解

    • @Override:用于标识方法是覆盖父类方法的。
    • @Deprecated:标记该方法或类已过时,不建议使用。
    • @SuppressWarnings:抑制编译器的警告。
  • 运行时注解

    • @Autowired:Spring中用于自动注入依赖。
    • @Transactional:Spring中用于声明事务操作。
    • @Entity@Table:Hibernate中用于声明实体类与数据库表的映射关系。
3. 自定义注解

Java允许开发者自定义注解,可以通过元注解@Retention@Target来控制注解的使用范围和生命周期。

// 自定义注解
@Target(ElementType.METHOD)  // 只能用于方法
@Retention(RetentionPolicy.RUNTIME)  // 运行时可用
public @interface MyAnnotation {
    String value();
}

// 使用自定义注解
public class MyClass {
    @MyAnnotation(value = "Test")
    public void myMethod() {
        // method implementation
    }
}

通过反射机制,可以在运行时获取注解并做相应的处理:

Method method = MyClass.class.getMethod("myMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {
    MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
    System.out.println(annotation.value());  // 输出 Test
}

总结

  • 泛型的作用:泛型提供了类型安全、代码复用、消除强制类型转换和提高代码可读性等好处。泛型的类型擦除机制在编译时发生,并在编译后的字节码中不再保留泛型相关的类型信息。

  • 类型擦除:泛型的擦除在编译阶段进行,泛型类型参数会被替换为其上界(如果没有上界,则为Object)。这意味着在运行时,泛型的类型信息不可用。

  • 注解的作用:注解是一种元数据机制,可以