Java_Class类和反射(20)

Class 类

RTTI,Run-Time Type Identification,运行时类型识别,对象的类型信息就保存在 java.lang.Class 类的对象中

但 Class 类没有 public 的构造方法,其对象只能由 JVM 来创建,每创建一个普通类的对象,JVM 都会创建一个对应的 Class 类的对象来保存该普通对象的类信息,一个普通类可以创建多个实例对象,但其对应的 Class 类的对象 JVM 只会创建一个

编译一个类会创建同名的 .class 文件,Class 对象即保存在 .class 文件中,类加载相当于 Class 对象的加载,类在第一次被使用时才加载到 JVM,可通过 Class.forName 方法手动地加载某个类

获取 Class 对象的引用

例如,对于 Test.java,有三种方法获取其 Class 对象的引用

1
2
3
4
5
Class clazz1 = Class.forName("Student");

Class clazz2 = Student.class;

Class clazz3 = new Student().getClass();

可以给 Class 添加泛型在编译时进行类型检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Test {
  public static void main(String[] args) {
    //不添加泛型则可以改变clszz引用指向的对象
    Class clszz = int.class;
    clszz = double.class;
    //添加泛型后就有了类型检查,但包装类和基本类型还是可以相互转换
    Class<Integer> integerClazz1 = int.class;
    Class<Integer> integerClazz2 = Integer.class;
    integerClazz1 = Integer.class;
    integerClazz2 = int.class;
    //类型不同就会报错
    //integerClazz1 = Double.class; //类型报错
    //integerClazz2 = double.class; //类型报错
  }
}

实例化对象

获取到 Class 类的对象后,可以通过 newInstance 方法生成目标类的实例对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
  public static void main(String[] args) throws Exception {
    Class<?> clazz1 = Class.forName("Student");
    Object obj1 = clazz1.newInstance();
    Student s1 = (Student) obj1;

    Class<Student> clazz2 = Student.class;
    Student s2 = clazz2.newInstance();

    Class<?> clazz3 = new Student().getClass();
    Object obj3 = clazz1.newInstance();
    Student s3 = (Student) obj3;

    //从同一个字节码文件获取类信息,所以对象相等
    System.out.println(clazz1 == clazz2); //true
    System.out.println(clazz1 == clazz3); //true
    System.out.println(clazz2 == clazz3); //true
  }
}
class Student {}

newInstance 方法是通过调用目标类的无参构造方法创建对象,对于没有无参构造方法的类,则可以调用 Class 类的 getConstructor 方法获取类的构造方法来创建

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class Test {
  public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("Student");
    //Student student = (Student) clazz.newInstance(); //报错,实例化异常
    Constructor<?> c = clazz.getConstructor(String.class);
    Student student = (Student) c.newInstance("张三");
    System.out.println(student); //张三
  }
}
class Student {
  String name;
  public Student(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return name;
  }
}

获取类方法和属性

getMethods 和 getFileds 方法返回类中所有 public 方法和属性,包括其父类的 public 方法和属性

getDeclaredMethods 和 getDeclaredFields 方法返回在本类中定义的所有方法和属性,包括重写的

getMethod 和 getFiled 方法返回指定类中的方法和属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Test {
  public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("Son");
    Son s = (Son) clazz.newInstance();

    for (Method method : clazz.getDeclaredMethods()) {
      System.out.println(method.getName()); //print、printSon、getNum
      if ("print".equals(method.getName())) {
        //调用子类重写的print方法
        method.invoke(s); //F_Son
      }
    }

    for (Field field : clazz.getDeclaredFields()) {
      //只能获取到本类中定义的,包括private的num
      System.out.println(field.getName()); //num
      if ("num".equals(field.getName())) {
        field.setAccessible(true); //使private的num可访问
        int num = (int) field.get(s);
        System.out.println(num); //10
      }
    }

    for (Method method : clazz.getMethods()) {
      System.out.println(method.getName()); //获取不到private的printSon
    }

    for (Field field : clazz.getFields()) {
      //只能获取public,包括父类public的name,不能获取private
      System.out.println(field.getName()); //name
    }
  }
}

class Father {
  public String name;
  public void print() {
    System.out.println("Father");
  }
}

class Son extends Father {
  private int num = 10;
  @Override
  public void print() {
    System.out.println("F_Son");
  }
  private void printSon() {
    System.out.println("Son");
  }
  public int getNum() {
    return num;
  }
}

反射

反射是指在运行时获取并操作类中的属性和方法,前提是要先获取被反射对象对应的 Class 类的对象,例如通过反射改变不可变的 String

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Test {
  public static void main(String[] args) throws Exception {
    String s = "Hello World";
    //首先获取s对象对应的Class的对象
    Class<String> clazz = String.class;
    //获取String类中的value字段
    Field valueFieldOfString = clazz.getDeclaredField("value");
    //改变value的访问权限
    valueFieldOfString.setAccessible(true);
    //获取value的值
    char[] value = (char[]) valueFieldOfString.get(s);
    //改变value指向的字符数组的值
    value[5] = '_';
    System.out.println(s);  //Hello_World
  }
}

反射最重要的用途是开发配置化的通用框架,需要根据配置文件加载不同的类、调用不同的方法

通过反射实现配置化

不使用反射的普通多态方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test {
  public static void main(String[] args) {
    Feeder f = new Feeder();
    f.feed(new Cat()); //cat eat
    f.feed(new Dog()); //dog eat
  }
}

interface Animal {
  void eat();
}

class Cat implements Animal {
  @Override
  public void eat() {
    System.out.println("cat eat");
  }
}

class Dog implements Animal {
  @Override
  public void eat() {
    System.out.println("dog eat");
  }
}

class Feeder {
  public void feed (Animal a) {
    a.eat();
  }
}

使用反射,通过读取配置文件的方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Test {
  public static void main(String[] args) throws Exception {
    BufferedReader br = new BufferedReader(
      new FileReader("src/config.properties"));
    Class<?> clazz = Class.forName(br.readLine());
    Animal a = (Animal) clazz.newInstance();
    Feeder f = new Feeder();
    f.feed(a); //cat eat
  }
}

要添加具体的动物在配置文件中修改即可,不用再在源码中进行修改

config.properties

1
2
3
Cat
#Dog
#...

以上内容是玉山整理的笔记,如有错误还请指出