反射

利用反射机制获取到每个类对应的Class对象,一共有三种方法获取

  1. 类名.class
  2. Class.forName()​ 使用Class类静态方法forName(),通过包名.类名,注意返回值是Class<?>
  3. getClass()​ 方法

Class对象和多态

使用反射机制判断类型

public static void main(String[] args) {
    String str = "";
    System.out.println(str.getClass() == String.class);   //直接判断是否为这个类型
}

判断是否为子类或者是接口/抽象类的实现,使用asSubClass()​方法:

public static void main(String[] args) {
    Integer i = 10;
    i.getClass().asSubclass(Number.class);   //当Integer不是Number的子类时,会产生异常
}

使用getSuperclass()​方法获取当前父类的Class对象

    public static void main(String[] args) {
        System.out.println(Integer.class.getSuperclass());
    }

使用getGenericSuperclass()​获取父类的原始类型的Type

使用类似的方法去获取父类的接口以及接口类型

    public static void main(String[] args) {
        for (Class<?> anInterface : String.class.getInterfaces()) {
            System.out.println(anInterface.getName());
        }
        System.out.println("-----------------------------------");
        for (Type genericInterface : String.class.getGenericInterfaces()) {
            System.out.println(genericInterface.getTypeName());
        }
    }

创建类对象

获取到当前类的Class对象后,可以使用newInstance()​方法创建该类的对象

public class test01 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Class<Student> clazz = Student.class;
        Student student = clazz.newInstance();
        student.test();
    }
}
class Student{
    public void test(){
        System.out.println("hello world");
    }
}

但是newInstance()​只适用于无参构造,否则会出现InstantiationException异常

当默认无参构造的权限不是public​时,会出现IllegalAccessException异常,表示我们无权去调用默认构造方法。

在JDK9之后,不再推荐使用newInstance()​方法了,而是使用getConstructor()​方法来获取类的构造方法,使用的格式为

Class对象.getConstructor(构造方法形参1的class对象,构造方法形参2的class对象,...).newInstance(实参1,实参2,...)

示例:

public class test01 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<Student> clazz = Student.class;
        Student student = clazz.getConstructor(String.class,int.class).newInstance("123",123);
    }

}
class Student{
    Student(){

    }
    public Student(String str,int x){
        System.out.println("构造方法");
    }
    public void test(){
        System.out.println("hello world");
    }
}

需要注意的是,该方法的前提是该有参构造函数的权限是public​,否则就需要使用getDeclaredConstructor()​方法,并使用setAccessible()​方法修改访问权限,然后再进行构造

public class test01 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<Student> clazz = Student.class;
        Constructor<Student> declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        Student student = declaredConstructor.newInstance("123",123);
        student.test();
    }

}
class Student{
    Student(){

    }
    Student(String str,int x){
        System.out.println("构造方法");
    }
    public void test(){
        System.out.println("hello world");
    }
}

调用类方法

使用getMethod()​方法可以获取到类中声明为public​的方法,得到一个Method​对象,使用该对象中的invoke()​方法(返回值为获取到的方法的返回值)调用已获取到方法,格式为

Method method = class对象.getMethod("对象中的public成员方法名",方法中形参的class对象,...);
method.invoke(该类的实例化对象,实参1,实参2,...);

示例:

public class test01 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<Student> studentClass = Student.class;
        Student student = studentClass.newInstance();
        Method test = studentClass.getMethod("test", String.class);
        test.invoke(student,"fafafafd");
    }

}
class Student{
    Student(){
    }
    Student(String str,int x){
        System.out.println("构造方法");
    }
    public void test(String a){
        System.out.println(a + "hello world");
    }
}

同构造方法一样,出现非public​方法时,使用getDeclaredMethod()​方法,然后使用Method​对象中的setAccessible(true)​无视权限修饰符调取方法

如果参数的类型是可变参数,实际就是该参数类型的数组,只需要传入该数组的Class对象即可

修改类的属性

使用getField()​方法可以获取一个类定义的指定字段,得到一个Field​对象后使用该对象的set()​方法即可设定属性值

ublic class test01 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class<Student> studentClass = Student.class;
        Student student = studentClass.newInstance();
        Field age = studentClass.getField("age");
        age.set(student,100);
        System.out.println(student.age);
    }

}
class Student{
    public int age;
    Student(){
    }
    Student(String str,int x){
        System.out.println("构造方法");
    }
    public void test(String a){
        System.out.println(a + "hello world");
    }
}

如果该属性是private属性,也可以按照之前的操作使用getDeclaredField()​获取Field​对象,实现越权访问并设置属性值

public class test01 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class<Student> studentClass = Student.class;
        Student student = studentClass.newInstance();
        Field age = studentClass.getDeclaredField("age");
        age.setAccessible(true);
        age.set(student,100);
        System.out.println(student.age);
    }

}
class Student{
    int age;
    Student(){
    }
    Student(String str,int x){
        System.out.println("构造方法");
    }
    public void test(String a){
        System.out.println(a + "hello world");
    }
}

注解

注解可以被标注在任意地方,包括方法上、类名上、参数上、成员属性上、注解定义上等,就像注释一样,它相当于我们对某样东西的一个标记。而与注释不同的是,注解可以通过反射在运行时获取,注解也可以选择是否保留到运行时

预设注解

JDK预设了以下注解,作用于代码:

  • @Override- 检查(仅仅是检查,不保留到运行时)该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated- 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings- 指示编译器去忽略注解中声明的警告(仅仅编译器阶段,不保留到运行时)
  • @FunctionalInterface- Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @SafeVarargs- Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。

元注解

元注解是作用于注解上的注解,用于我们编写自定义的注解:

  • @Retention- 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented- 标记这些注解是否包含在用户文档中。
  • @Target- 标记这个注解应该是哪种 Java 成员。
  • @Inherited- 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
  • @Repeatable- Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

@Retention表示此注解的保留策略,使用时可以传入参数RetentionPolicy.*​,‘‘ * ’’有三种选择:CLASS​、SOURCE​、RUNTIME

@Target限定注解的使用范围,使用时传入参数ElementType.*​,例如该注解只能标记方法,则使用@Target(ElementType.METHOD)

注解的使用

注解中还可以定义一些属性,也叫成员变量。注解只能有成员变量,不能有成员方法,注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value();
}

默认只有一个属性,且该属性名称为value​时,赋值时不需要手动指定注解的属性名称

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value();
}
public class Main {
    @Test("hello world") //括号内传入属性值
    public static void main(String[] args) {

    }
}

如果不是value​则需要指定属性名称后赋值

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String test();
}
public class Main {
    @Test(test = "hello world")
    public static void main(String[] args) {

    }
}

使用default​关键字可以为属性指定默认值

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value() defualt "hello world";
}

当属性存在默认值,使用注解不用传入属性,如果是数组,数组只有一个元素可以和普通属性一样传入一个值即可,如果是多个值则需要罗列出来用逗号隔开并套上花括号

@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String[] test();
    String[] test1();
}
@Test(test={"1231","123131"},test1 = {"123131","12313131"})
public class test01 {
  
    public static void main(String[] args){

    }

}

反射获取注解

@Test(test={"1231","123131"},test1 = {"123131","12313131"})
public class test01 {
    public static void main(String[] args) {
        for (Annotation annotation : test01.class.getAnnotations()) {
            System.out.println(annotation.annotationType());	//注解类型
			System.out.println(annotation instanceof Test);	//判断是否为Test
            Test test = (Test) annotation;
            System.out.println(Arrays.toString(test.test()));	//获取注解中的属性值
            System.out.println(Arrays.toString(test.test1()));
        }
    }

}