java基础学习——反射与注解

反射

什么是反射

Java 反射(Reflection)是一种强大的机制,它允许程序在运行时检查或“自省”自身,并能够操作内部属性。通过反射,你可以在运行时获取类的信息、创建对象实例、访问和修改字段(包括私有字段)、调用方法(包括私有方法),以及获取构造函数信息等。

Java 反射的主要用途

  1. 框架开发:很多框架(如 Spring、Hibernate 等)使用反射来实现依赖注入、ORM 映射等功能。
  2. 动态代理:用于创建代理对象,在不改变原有代码的情况下为对象添加额外的功能。
  3. 测试工具:一些单元测试框架利用反射来设置私有字段或调用私有方法进行测试。
  4. 序列化/反序列化:可以用来保存对象状态到文件或者网络传输,然后恢复对象状态。
  5. 插件系统:允许应用程序加载并执行第三方模块或插件。

反射的核心类

要使用反射,首先需要导入 java.lang.reflect 包中的类。以下是几个核心的反射类:

  • Class<T>:表示类或接口的类型信息。
  • Constructor<T>:代表类的构造器。
  • Field:代表类的成员变量(字段)。
  • Method:代表类的方法。

获取class对象

在 Java 中,有几种不同的方式来获取 Class 对象。每种方法都有其特定的使用场景和特点。以下是三种主要的方法:

  1. 使用 .class 语法

这是最直接的方式之一,适用于编译时已知类型的类。

// 假设我们有一个名为 MyClass 的类
Class<MyClass> clazz = MyClass.class;

这种方式简单且高效,因为它不需要通过字符串名称加载类,因此不会抛出 ClassNotFoundException

  1. 使用对象的 getClass() 方法

如果你已经有了某个类的实例,那么可以通过该实例的 getClass() 方法来获取对应的 Class 对象。

MyClass instance = new MyClass();
Class<?> clazz = instance.getClass();

这种方法同样简单,并且可以用来获取任何对象的实际运行时类型信息。

  1. 使用 Class.forName(String className) 方法

当类名是在运行时才知道的情况下,或者你需要动态加载一个类时,可以使用 Class.forName 方法。这个方法接收一个包含完全限定类名(即包名加类名)的字符串参数。

try {
    
    
    Class<?> clazz = Class.forName("com.example.MyClass");
} catch (ClassNotFoundException e) {
    
    
    // 处理异常
    e.printStackTrace();
}

Class.forName 会在调用时初始化类(如果尚未被初始化),这意味着它会执行静态初始化块和静态变量初始化。此外,需要注意的是,如果指定的类不存在,将会抛出 ClassNotFoundException 异常。

  1. 使用原始数组类型

对于数组类型,可以直接在其类型后加上 .class 来获取其 Class 对象。

Class<?> intArrayClass = int[].class;
Class<?> stringArrayClass = String[].class;
  1. 使用基本数据类型的包装类中的 TYPE 字段

对于基本数据类型(如 int, char 等),它们没有 .class 表达式,但可以通过相应的包装类(如 Integer, Character)中的 TYPE 字段来访问。

Class<?> intPrimitiveClass = Integer.TYPE; // 相当于 int.class
Class<?> charPrimitiveClass = Character.TYPE; // 相当于 char.class

总结:

  • .class:适合编译时已知类型的类。
  • getClass():适合已有实例的情况。
  • Class.forName():适合需要根据字符串动态加载类的情况,可能会抛出 ClassNotFoundException
  • 原始数组类型:直接通过类型加上 .class 获取。
  • 基本数据类型:通过相应包装类的 TYPE 字段获取。

选择哪种方法取决于你的具体需求和上下文环境。通常情况下,如果可能的话,尽量使用 .classgetClass(),因为它们更安全、更直观并且性能更好。

反射构造方法

在 Java 中,反射提供了几种方式来获取构造方法。这些方式允许你根据不同的需求选择最适合的方法。以下是获取构造方法的主要方式:

  1. 获取公共构造方法:getConstructor(Class<?>... parameterTypes)

此方法用于获取指定参数类型的公共构造方法。如果没有找到匹配的构造方法,则抛出 NoSuchMethodException

Class<MyClass> clazz = MyClass.class;

try {
    
    
    // 获取无参的公共构造方法
    Constructor<MyClass> constructor = clazz.getConstructor();
    
    // 获取带有一个 String 参数的公共构造方法
    Constructor<MyClass> paramConstructor = clazz.getConstructor(String.class);
} catch (NoSuchMethodException e) {
    
    
    e.printStackTrace();
}
  1. 获取所有声明的构造方法(包括私有、保护和包级私有):getDeclaredConstructor(Class<?>... parameterTypes)

此方法用于获取特定参数类型的构造方法,无论其访问修饰符是什么。同样地,如果找不到匹配的构造方法,会抛出 NoSuchMethodException

try {
    
    
    // 获取无参的所有声明构造方法(包括私有的)
    Constructor<MyClass> privateConstructor = clazz.getDeclaredConstructor();
    
    // 获取带有参数的所有声明构造方法
    Constructor<MyClass> declaredParamConstructor = clazz.getDeclaredConstructor(String.class);
} catch (NoSuchMethodException e) {
    
    
    e.printStackTrace();
}

getDeclaredConstructors():此方法返回一个包含所有声明的构造方法(包括私有、保护和包级私有)的数组。

Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> c : constructors) {
    
    
    System.out.println("Constructor: " + c);
}
  1. 获取所有的公共构造方法:getConstructors()

此方法返回一个包含所有公共构造方法的数组。

Constructor<?>[] publicConstructors = clazz.getConstructors();
for (Constructor<?> c : publicConstructors) {
    
    
    System.out.println("Public Constructor: " + c);
}

完整示例代码

import java.lang.reflect.Constructor;

public class ReflectionExample {
    
    

    public static void main(String[] args) {
    
    
        try {
    
    
            Class<MyClass> clazz = MyClass.class;

            // 获取无参的公共构造方法
            Constructor<MyClass> constructor = clazz.getConstructor();
            System.out.println("Public no-arg constructor: " + constructor);

            // 获取带有一个 String 参数的公共构造方法
            Constructor<MyClass> paramConstructor = clazz.getConstructor(String.class);
            System.out.println("Public one-arg constructor: " + paramConstructor);

            // 获取无参的所有声明构造方法(包括私有的)
            Constructor<MyClass> privateConstructor = clazz.getDeclaredConstructor();
            System.out.println("Declared no-arg constructor: " + privateConstructor);

            // 获取所有声明的构造方法
            Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
            for (Constructor<?> c : allConstructors) {
    
    
                System.out.println("Declared Constructor: " + c);
            }

            // 获取所有的公共构造方法
            Constructor<?>[] publicConstructors = clazz.getConstructors();
            for (Constructor<?> c : publicConstructors) {
    
    
                System.out.println("Public Constructor: " + c);
            }

        } catch (NoSuchMethodException e) {
    
    
            e.printStackTrace();
        }
    }

    // 测试用的类
    public static class MyClass {
    
    
        public MyClass() {
    
    }
        public MyClass(String message) {
    
     System.out.println(message); }
        private MyClass(int value) {
    
     System.out.println("Private constructor called with value: " + value); }
    }
}

这段代码将输出 MyClass 类中定义的不同构造方法的信息。通过这种方式,你可以详细了解类的构造方法,并且可以在运行时动态地创建对象实例。请记住,在处理非公共成员(如私有构造方法)时,可能需要调用 setAccessible(true) 来绕过 Java 的访问控制检查。

反射成员方法

在 Java 中,反射提供了多种方式来获取类的成员方法(即实例方法)。这些方式允许你根据不同的需求选择最适合的方法。以下是几种主要的方式来获取成员方法:

  1. 获取公共成员方法:getMethod(String name, Class<?>... parameterTypes)

此方法用于获取指定名称和参数类型的公共成员方法(包括从父类继承的方法)。如果找不到匹配的方法,则抛出 NoSuchMethodException

Class<MyClass> clazz = MyClass.class;

try {
    
    
    // 获取无参的公共成员方法
    Method method = clazz.getMethod("publicMethod");
    
    // 获取带有特定参数类型的公共成员方法
    Method paramMethod = clazz.getMethod("paramMethod", String.class);
} catch (NoSuchMethodException e) {
    
    
    e.printStackTrace();
}
  1. 获取所有声明的成员方法(包括私有、保护和包级私有):getDeclaredMethod(String name, Class<?>... parameterTypes)

此方法用于获取指定名称和参数类型的所有声明的方法,无论其访问修饰符是什么。同样地,如果找不到匹配的方法,会抛出 NoSuchMethodException

try {
    
    
    // 获取无参的所有声明成员方法(包括私有的)
    Method privateMethod = clazz.getDeclaredMethod("privateMethod");
    
    // 获取带有参数的所有声明成员方法
    Method declaredParamMethod = clazz.getDeclaredMethod("declaredParamMethod", int.class);
} catch (NoSuchMethodException e) {
    
    
    e.printStackTrace();
}

getDeclaredMethods():此方法返回一个包含所有声明的方法(包括私有、保护和包级私有)的数组。

Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
    
    
    System.out.println("Declared Method: " + m);
}
  1. 获取所有的公共成员方法(包括继承的):getMethods()

此方法返回一个包含所有公共方法的数组,包括从父类继承的方法。

Method[] publicMethods = clazz.getMethods();
for (Method m : publicMethods) {
    
    
    System.out.println("Public Method: " + m);
}

完整代码示例

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class ReflectionExample {
    
    

    public static void main(String[] args) {
    
    
        try {
    
    
            Class<MyClass> clazz = MyClass.class;

            // 获取并调用无参的公共成员方法
            Method publicMethod = clazz.getMethod("publicMethod");
            MyClass instance = new MyClass();
            publicMethod.invoke(instance);

            // 获取并调用带有特定参数类型的公共成员方法
            Method paramMethod = clazz.getMethod("paramMethod", String.class);
            paramMethod.invoke(instance, "Hello");

            // 获取并调用无参的所有声明成员方法(包括私有的)
            Method privateMethod = clazz.getDeclaredMethod("privateMethod");
            privateMethod.setAccessible(true); // 必须设置为可访问
            privateMethod.invoke(instance);

            // 获取并调用带有参数的所有声明成员方法
            Method declaredParamMethod = clazz.getDeclaredMethod("declaredParamMethod", int.class);
            declaredParamMethod.setAccessible(true);
            declaredParamMethod.invoke(instance, 42);

            // 获取所有声明的方法
            Method[] allMethods = clazz.getDeclaredMethods();
            for (Method m : allMethods) {
    
    
                System.out.println("Declared Method: " + m);
            }

            // 获取所有的公共方法(包括继承的)
            Method[] publicMethods = clazz.getMethods();
            for (Method m : publicMethods) {
    
    
                System.out.println("Public Method: " + m);
            }

        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
    
    
            e.printStackTrace();
        }
    }

    // 测试用的类
    public static class MyClass {
    
    
        public void publicMethod() {
    
    
            System.out.println("Public method called.");
        }

        public void paramMethod(String message) {
    
    
            System.out.println("Parameterized method called with: " + message);
        }

        private void privateMethod() {
    
    
            System.out.println("Private method called.");
        }

        private void declaredParamMethod(int value) {
    
    
            System.out.println("Declared parameterized method called with value: " + value);
        }
    }
}

这段代码演示了如何通过反射获取 MyClass 类中定义的不同成员方法的信息,并且调用了这些方法。请注意,在处理非公共成员(如私有方法)时,可能需要调用 setAccessible(true) 来绕过 Java 的访问控制检查。

注意事项

  • 异常处理:使用反射调用方法时可能会抛出多种异常,如 IllegalAccessException, InvocationTargetExceptionNoSuchMethodException。确保正确处理这些异常。
  • 性能问题:反射操作通常比直接调用慢得多,因为它涉及到解析类结构的操作。此外,频繁调用 setAccessible(true) 可能会影响安全性。
  • 安全性:反射打破了封装性,使得私有成员变得可见。这可能会导致安全漏洞,并且违反了面向对象的设计原则。

反射成员变量

在 Java 中,反射提供了多种方式来获取类的成员变量(字段)。这些方式允许你根据不同的需求选择最适合的方法。以下是几种主要的方式来获取成员变量:

  1. 获取公共成员变量:getField(String name)

此方法用于获取指定名称的公共成员变量(包括从父类继承的字段)。如果找不到匹配的字段,则抛出 NoSuchFieldException

Class<MyClass> clazz = MyClass.class;

try {
    
    
    // 获取公共成员变量
    Field publicField = clazz.getField("publicField");
} catch (NoSuchFieldException e) {
    
    
    e.printStackTrace();
}
  1. 获取所有声明的成员变量(包括私有、保护和包级私有):getDeclaredField(String name)

此方法用于获取指定名称的所有声明的字段,无论其访问修饰符是什么。同样地,如果找不到匹配的字段,会抛出 NoSuchFieldException

try {
    
    
    // 获取所有声明的成员变量(包括私有的)
    Field privateField = clazz.getDeclaredField("privateField");
} catch (NoSuchFieldException e) {
    
    
    e.printStackTrace();
}

getDeclaredFields()

此方法返回一个包含所有声明的字段(包括私有、保护和包级私有)的数组。

Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
    
    
    System.out.println("Declared Field: " + f);
}
  1. 获取所有的公共成员变量(包括继承的):getFields()

此方法返回一个包含所有公共字段的数组,包括从父类继承的字段。

Field[] publicFields = clazz.getFields();
for (Field f : publicFields) {
    
    
    System.out.println("Public Field: " + f);
}

完整代码示例

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class ReflectionExample {
    
    

    public static void main(String[] args) {
    
    
        try {
    
    
            Class<MyClass> clazz = MyClass.class;
            MyClass instance = new MyClass();

            // 获取并读取/设置公共成员变量
            Field publicField = clazz.getField("publicField");
            System.out.println("Original public field value: " + publicField.get(instance));
            publicField.set(instance, "New Public Value");
            System.out.println("Updated public field value: " + publicField.get(instance));

            // 获取并读取/设置所有声明的成员变量(包括私有的)
            Field privateField = clazz.getDeclaredField("privateField");
            privateField.setAccessible(true); // 必须设置为可访问
            System.out.println("Original private field value: " + privateField.get(instance));
            privateField.set(instance, "New Private Value");
            System.out.println("Updated private field value: " + privateField.get(instance));

            // 获取所有声明的字段
            Field[] allFields = clazz.getDeclaredFields();
            for (Field f : allFields) {
    
    
                f.setAccessible(true); // 对于非公共字段必须设置为可访问
                System.out.println("Declared Field: " + f.getName() + ", Value: " + f.get(instance));
            }

            // 获取所有的公共字段(包括继承的)
            Field[] publicFields = clazz.getFields();
            for (Field f : publicFields) {
    
    
                System.out.println("Public Field: " + f.getName() + ", Value: " + f.get(instance));
            }

        } catch (NoSuchFieldException | IllegalAccessException e) {
    
    
            e.printStackTrace();
        }
    }

    // 测试用的类
    public static class MyClass {
    
    
        public String publicField = "Initial Public Value";
        private String privateField = "Initial Private Value";

        @Override
        public String toString() {
    
    
            return "MyClass{" +
                    "publicField='" + publicField + '\'' +
                    ", privateField='" + privateField + '\'' +
                    '}';
        }
    }
}

这段代码演示了如何通过反射获取 MyClass 类中定义的不同成员变量的信息,并且读取或设置了这些字段的值。请注意,在处理非公共成员(如私有字段)时,可能需要调用 setAccessible(true) 来绕过 Java 的访问控制检查。

注意事项:

  • 异常处理:使用反射访问字段时可能会抛出多种异常,如 IllegalAccessExceptionNoSuchFieldException。确保正确处理这些异常。
  • 性能问题:反射操作通常比直接访问字段慢得多,因为它涉及到解析类结构的操作。此外,频繁调用 setAccessible(true) 可能会影响安全性。
  • 安全性:反射打破了封装性,使得私有成员变得可见。这可能会导致安全漏洞,并且违反了面向对象的设计原则。
  • 线程安全:当你通过反射修改静态字段时,请考虑线程安全性问题,因为这可能会影响到所有实例的状态。

通过这些反射机制,你可以动态地访问和修改类的字段,从而实现更加灵活的编程模式。然而,在实际应用中应该谨慎使用反射,以避免潜在的问题。

处理泛型

从 Java 8 开始,反射 API 支持获取泛型信息。例如,你可以使用 TypeParameterizedType 接口来处理泛型参数。

注意事项

  • 性能问题:反射通常比直接调用慢得多,因为它涉及到解析类结构的操作。因此,在性能敏感的应用中应谨慎使用。
  • 安全性:反射打破了封装性,使得私有成员变得可见。这可能会导致安全漏洞,并且违反了面向对象的设计原则。
  • 版本兼容性:由于反射是基于字符串名称操作类和成员,如果这些名称发生变化(比如重构),反射代码可能会失效。

总之,Java 反射提供了一种非常灵活的方式来操作类和对象,但应该根据具体需求权衡其使用的利弊。

注解

什么是注解

Java 注解(Annotations)是 Java 语言的一种元数据形式,它们提供了关于程序代码的数据,但并不直接影响程序的直接逻辑。注解可以被用于类、方法、变量、参数和包声明上,并且可以在编译时或运行时通过反射机制读取。从 Java 5 开始引入注解以来,它们已经成为开发框架和工具的重要组成部分,例如 Spring 和 Hibernate 都广泛使用了注解。

  • 定义:注解是一种特殊的接口,它可以通过 @interface 关键字来定义。
  • 目标:注解可以用来提供额外的信息给编译器、IDE 或其他工具,也可以在运行时通过反射获取并执行相应的逻辑。
  • 作用域:注解可以应用于几乎任何元素,包括但不限于类、方法、字段、参数等。

Java 常见的内置注解

Java 提供了一些内置的标准注解:

  1. @Override
    • 指示一个方法重写父类中的方法。如果方法签名不匹配,编译器会报错。
  2. @Deprecated
    • 标记某个程序元素(如方法或类)已过时。当使用这种元素时,编译器会发出警告。
  3. @FunctionalInterface
    • 指示一个接口是函数式接口,意味着该接口只能有一个抽象方法。
  4. @Repeatable
    • 允许同一位置重复应用同一个注解。
  5. @Target
    • 定义注解可以应用的目标元素类型,如 ElementType.METHOD, ElementType.FIELD 等。
  6. @Retention
    • 指定注解保留的时间长度,有三种策略:
      • RetentionPolicy.SOURCE:仅保留在源码级别,编译后会被丢弃。
      • RetentionPolicy.CLASS:默认值,编译到 .class 文件中,但在运行时不可用。
      • RetentionPolicy.RUNTIME:编译到 .class 文件中,并且在运行时可通过反射访问。

注解的定义及使用

  1. 使用 @interface 关键字定义注解。
  2. 可选地指定元注解,如 @Target@Retention 来定义注解的应用范围和生命周期。
  3. 定义注解的成员(即属性),可以有默认值。
  4. 在代码中应用自定义注解。

代码示例

定义一个简单的自定义注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 定义注解及其应用范围和生命周期
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    
    
    String value() default "default value";
}

使用自定义注解:

public class MyClass {
    
    

    @MyAnnotation("This is a custom annotation")
    public void myMethod() {
    
    
        // 方法实现
    }
}

处理注解:

要处理注解,你可以使用反射 API:

  • isAnnotationPresent():判断该方法有没有使用指定的注解。
  • getAnnotation():获取注解。

下面是一个例子,展示了如何在运行时读取注解:

import java.lang.reflect.Method;

public class AnnotationProcessor {
    
    

    public static void main(String[] args) throws Exception {
    
    
        Class<MyClass> clazz = MyClass.class;
        for (Method method : clazz.getDeclaredMethods()) {
    
    
            if (method.isAnnotationPresent(MyAnnotation.class)) {
    
    
                MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                System.out.println("Found annotation on method " + method.getName() + ": " + annotation.value());
            }
        }
    }
}

这段代码将输出 myMethod 上应用的 MyAnnotation 的值。

总结

注解为 Java 提供了一种强大的方式来增强代码的功能性和可维护性。它们不仅简化了开发过程,而且促进了更好的编程实践。然而,应该注意的是,虽然注解本身不会影响程序的行为,但是不当使用可能会导致不必要的复杂性。因此,在设计系统时应谨慎考虑何时以及如何使用注解。

元注解

元注解是 Java 中用来修饰其他注解的特殊注解。它们定义了自定义注解的行为和特性,如它们可以应用到哪些程序元素上、它们的生命周期有多长等。Java 提供了几种内置的元注解,这些元注解可以帮助开发者更好地控制自定义注解的应用范围和行为。

以下是 Java 中主要的元注解及其详细说明:

  1. @Target

@Target 元注解用于指定自定义注解可以应用的目标元素类型。它接收一个或多个 ElementType 枚举值作为参数,表示该注解可以被放置的位置。例如,如果一个注解只能应用于方法,则可以使用 @Target(ElementType.METHOD) 来声明。

ElementType 值:

  • TYPE:类、接口(包括注释类型)或枚举声明。
  • FIELD:字段声明(包括枚举常量)。
  • METHOD:方法声明。
  • PARAMETER:参数声明。
  • CONSTRUCTOR:构造函数声明。
  • LOCAL_VARIABLE:局部变量声明。
  • ANNOTATION_TYPE:注解类型声明。
  • PACKAGE:包声明。
  • TYPE_PARAMETER:类型参数声明(自 Java 8 起支持)。
  • TYPE_USE:任何类型的使用(自 Java 8 起支持)。
@Target({
    
     ElementType.METHOD, ElementType.FIELD })
public @interface MyAnnotation {
    
    
    // 注解成员
}
  1. @Retention

@Retention 元注解用于指定自定义注解的保留策略,即该注解在编译之后是否仍然存在。它接收一个 RetentionPolicy 枚举值作为参数。

RetentionPolicy 值:

  • SOURCE:注解仅保留在源码级别,编译后会被丢弃。
  • CLASS:默认值,注解会编译到 .class 文件中,但在运行时不可用。
  • RUNTIME:注解不仅编译到 .class 文件中,并且可以在运行时通过反射访问。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    
    
    // 注解成员
}
  1. @Documented

@Documented 元注解用于指示当使用 javadoc 工具生成文档时,该注解应该包含在生成的文档中。这意味着被此注解标记的元素将出现在 Javadoc 文档中。

@Documented
public @interface MyAnnotation {
    
    
    // 注解成员
}
  1. @Inherited

@Inherited 元注解用于指定某个注解是否可以被子类继承。默认情况下,注解不会被继承,但如果加上了 @Inherited,那么子类可以从父类那里继承该注解。

需要注意的是,@Inherited 只适用于类层次结构中的注解,而不适用于方法或字段上的注解。

@Inherited
public @interface MyAnnotation {
    
    
    // 注解成员
}
  1. @Repeatable

从 Java 8 开始引入的 @Repeatable 允许同一个地方多次出现同一个注解。通常,你需要定义一个容器注解来持有重复的注解实例。

首先定义一个容器注解:

import java.lang.annotation.Repeatable;

public @interface Container {
    
    
    MyRepeatedAnnotation[] value();
}

@Repeatable(Container.class)
public @interface MyRepeatedAnnotation {
    
    
    String value();
}

然后你可以在同一个位置多次使用 MyRepeatedAnnotation

@MyRepeatedAnnotation("First")
@MyRepeatedAnnotation("Second")
public class MyClass {
    
    
    // 类内容
}

总结

元注解为创建和管理自定义注解提供了强大的工具。通过合理地使用这些元注解,你可以精确地控制自定义注解的行为和作用范围,从而提高代码的可读性和维护性。理解并熟练运用这些元注解是掌握 Java 注解机制的关键部分。