一、反射入门

1.1 反射之前

首先创建一个实体类Person,类中含有两个属性name(private)和age(public),还有一个参数为name的私有构造器,还有一个私有方法showNation。

  1. public class Person {
  2. private String name;
  3. public int age;
  4. @Override
  5. public String toString() {
  6. return "Person{" +
  7. "name='" + name + '\'' +
  8. ", age=" + age +
  9. '}';
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public int getAge() {
  18. return age;
  19. }
  20. public void setAge(int age) {
  21. this.age = age;
  22. }
  23. public Person(String name, int age) {
  24. this.name = name;
  25. this.age = age;
  26. }
  27. private Person(String name) {
  28. this.name = name;
  29. }
  30. public Person() {
  31. System.out.println("Person()");
  32. }
  33. public void show(){
  34. System.out.println("你好,我是一个人");
  35. }
  36. private String showNation(String nation){
  37. System.out.println("我的国籍是:" + nation);
  38. return nation;
  39. }
  40. }

反射之前,我们要想获取Person类中的属性和方法,必须得通过Person对象来操作。并且,在Person类外部,不可以通过Person类的对象调用其内部私有结构(私有属性、私有构造器、私有方法)

  1. @Test
  2. public void test1() {
  3. //1.创建Person类的对象
  4. Person p1 = new Person("Tom", 12);
  5. //2.通过对象,调用其内部的属性、方法
  6. p1.age = 10;
  7. System.out.println(p1.toString());
  8. p1.show();
  9. //在Person类外部,不可以通过Person类的对象调用其内部私有结构。
  10. //比如:name、showNation()以及私有的构造器
  11. }

那如果想获取Person类中定义的私有结构,就得通过反射了。

1.2 反射操作

通过反射来创建Person类的对象

  1. //反射之后,对于Person的操作
  2. @Test
  3. public void test2() throws Exception{
  4. Class clazz = Person.class;
  5. //1.通过反射,创建Person类的对象
  6. Constructor cons = clazz.getConstructor(String.class, int.class);
  7. Object obj = cons.newInstance("Tom", 12);
  8. Person p = (Person) obj;
  9. System.out.println(p.toString());
  10. }

通过反射,调用对象指定的属性和方法。

  1. //2.通过反射,调用对象指定的属性、方法
  2. //调用属性
  3. Field age = clazz.getDeclaredField("age");
  4. age.set(p, 10);
  5. System.out.println(p.toString());
  6. //调用方法
  7. Method show = clazz.getDeclaredMethod("show");
  8. show.invoke(p);

此外,通过反射也可以调用Person类的私有结构的。比如:私有的构造器、方法、属性。

  1. //调用私有的构造器
  2. Constructor cons1 = clazz.getDeclaredConstructor(String.class);
  3. cons1.setAccessible(true);
  4. Person p1 = (Person) cons1.newInstance("Jerry");
  5. System.out.println(p1);
  1. //调用私有的属性
  2. Field name = clazz.getDeclaredField("name");
  3. name.setAccessible(true);
  4. name.set(p1,"HanMeimei");
  5. System.out.println(p1);
  1. // 调用私有的方法
  2. Method showNation = clazz.getDeclaredMethod("showNation", String.class);
  3. showNation.setAccessible(true);
  4. // 相当于String nation = p1.showNation("中国")
  5. String nation = (String) showNation.invoke(p1,"中国");
  6. System.out.println(nation);

Q:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?

A:直接new的方式。

Q:什么时候会使用:反射的方式?

A:反射的特征:动态性。

Q:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术? A:不矛盾。

1.3 反射细节

关于java.lang.Class类的理解:
1、类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。换句话说,Class的实例就对应着一个运行时类。

2、获取Class的实例的方式(前三种方式需要掌握)
方式一:调用运行时类的属性.class

  1. Class clazz1 = Person.class;

方式二:通过运行时类的对象,调用getClass()

  1. Person p1 = new Person();
  2. Class clazz2 = p1.getClass();

方式三:调用Class的静态方法:forName(String classPath)

  1. Class clazz3 = Class.forName("com.atguigu.java.Person");

方式四:使用类的加载器:ClassLoader (了解)

  1. ClassLoader classLoader = ReflectionTest.class.getClassLoader();
  2. Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");

对于同一个类,上面四种方式产生的clazz对象都是同一个,即以下语句输出的结果为true。

  1. System.out.println(clazz1 == clazz2);
  2. System.out.println(clazz1 == clazz3);
  3. System.out.println(clazz1 == clazz4);

二、老韩听课笔记

2.1 引出问题

image.png
1、定义一个re.properties配置文件,内部写有两个键值对,如下:

  1. classfullpath=cn.ypf.Cat
  2. method=hi

2、在cn.ypf包下新建一个Cat类,在relfection.question包下新建ReflectionQuestion.java类。
Cat.java:

  1. public class Cat {
  2. private String name = "Tomcat";
  3. public void hi(){
  4. System.out.println("hi " + name);
  5. }
  6. }

ReflectionQuestion.java:
如果使用传统的方式调用Cat类中的hi()方法

  1. // 传统方式调用Cat类中的hi()方法
  2. Cat cat = new Cat();
  3. cat.hi();

使用IO流读取配置文件里面的值,通过值来创建对象(其实是行不通的,读取配置文件得到的返回值是字符串,字符串怎么可以new呢???)

  1. // 通过流来读取配置文件里面的内容
  2. Properties prop = new Properties();
  3. prop.load(new FileInputStream("src\\re.properties"));
  4. // 这里两变量classfullpath和method就是配置文件中的值,不过是字符串
  5. String classfullpath = prop.get("classfullpath").toString();
  6. String methodName = prop.get("method").toString();
  7. // 创建对象???行不通 ,得靠反射机制
  8. // new classfullpath ×

那么,既然上面方法行不通,那就得用反射机制来解决了。

  1. // 反射机制来解决
  2. // 1 加载类,返回Class类型的对象clazz
  3. Class clazz = Class.forName(classfullpath);
  4. // 2 通过clazz对象得到你所加载的类(cn.ypf.Cat)的对象实例
  5. Object o = clazz.newInstacnce();
  6. // 对象.getClass()可以得到此对象的运行类型
  7. System.out.println("对象o的运行类型:" + o.getClass());
  8. // 3 通过clazz得到你加载的类(cn.ypf.Cat)的方法对象,即在反射中,方法也视为对象,万物皆对象
  9. Method method = clazz.getMethod(methodName);
  10. // 4 通过method方法对象来调用方法,而传统方式是使用对象.方法名(),反射中使用方法对象.invoke(对象)
  11. method.invoke(o);

2.2 反射机制

image.png
对于任何一个java程序,都会有以下三个阶段,首先将java文件编译成字节码文件,然后类加载器对字节码文件进行加载,得到Class类对象(存放在堆中),最后通过Class类对象调用对象和方法,操作属性等。
image.png
java的反射机制可以完成以下5个问题:
image.png

2.3 反射相关API

image.png
获得某个类的成员变量:

  1. // Field对象表示某个类的成员变量,注意getField()方法不能获取私有属性
  2. Field fieldName = clazz.getField("age");
  3. //传统写法: 对象.成员变量
  4. //反射中写法: 成员变量对象.get(对象)
  5. fieldName.get(o);

获得某个类的构造方法:

  1. // Constructor对象表示构造器
  2. Constructor cons1 = clazz.getConstructor();
  3. // ()中可以指定构造器参数类型
  4. Constructor cons2 = clazz.getConstructor(String.class);

获得某个类的成员方法:

  1. // 通过clazz得到你加载的类(cn.ypf.Cat)的方法对象,即在反射中,方法也视为对象,万物皆对象
  2. Method method = clazz.getMethod("hi");
  3. // 通过method方法对象来调用方法,而传统方式是使用对象.方法名(),反射中使用方法对象.invoke(对象)
  4. method.invoke(o);

image.png

2.4 反射优缺点

image.png
实例测试:使用传统方式和反射机制来调用类中的方法.

  1. // 传统方式
  2. public void m1() {
  3. Cat cat = new cat();
  4. long start = System.currentTimeMillis();
  5. for(int i = 0; i < 9000000000; i++){
  6. cat.hi();
  7. }
  8. long start = System.currentTimeMillis();
  9. System.out.println("传统方式耗时:" + (end - start));
  10. }
  1. // 反射机制
  2. public void m2() {
  3. Class clazz = Class.forname("cn.ypf.Cat");
  4. Object o = clazz.newInstance();
  5. Method method = clazz.getMethod("hi");
  6. long start = System.currentTimeMillis();
  7. for(int i = 0; i < 9000000000; i++){
  8. method.invoke(o);
  9. }
  10. long start = System.currentTimeMillis();
  11. System.out.println("反射机制耗时:" + (end - start));
  12. }

通过观察两种方法的运行时长,发现反射机制比较耗时间。

2.5 反射调用优化

image.png

  1. // 反射机制调优
  2. public void m3() {
  3. Class clazz = Class.forname("cn.ypf.Cat");
  4. Object o = clazz.newInstance();
  5. Method method = clazz.getMethod("hi");
  6. // 在反射调用方法时,取消访问检查,提高反射效率
  7. method.setAccessible(true);
  8. long start = System.currentTimeMillis();
  9. for(int i = 0; i < 9000000000; i++){
  10. method.invoke(o);
  11. }
  12. long start = System.currentTimeMillis();
  13. System.out.println("反射机制耗时:" + (end - start));
  14. }

2.6 Class类

Class类的继承结构图:
image.png
不管new多少个Cat对象,那么Cat类对应的Class类对象在堆内存中只会有一个,可以使用下面的代码测试:

  1. Class cls1 = Class.forName("cn.ypf.Cat");
  2. Class cls2 = Class.forName("cn.ypf.Cat");
  3. System.out.println(cls1.hashCode());
  4. System.out.println(cls2.hashCode());

可以发现输出的hashCode是一样的,说明了对于某个类Cat的Class类对象,不管创建多少个Cat对象,在堆内存中只会有一个,因为类只加载一次。
image.png
Class类的常用方法如下:
image.png
案例:

  1. // 定义一个Car类
  2. public class Car {
  3. public String brand;
  4. public Integer price;
  5. public String color;
  6. }

1、获取到Car类对应的 Class 对象

  1. Class cls = Class.forName("cn.ypf.Car");
  2. // 输出cls对象,即是哪个类的Class对象, cn.ypf.Car
  3. System.out.println(cls);
  4. // 输出cls的运行时类型,即 java.lang.Class
  5. System.out.println(cls.getClass());

2、得到包名

  1. System.out.println(cls.getPackage().getName());

3、得到全类名

  1. System.out.println(cls.getName());

4、通过cls创建对象实例

  1. Car car = (car)cls.newInstance();
  2. System.out.println(car);

5、通过反射获取属性的值和给属性赋值

  1. Field brand = cls.getField("brand");
  2. System.out.println(brand.get(car));
  3. brand.set(car, "奔驰");
  4. System.out.println(brand.get(car)); // 奔驰

6、得到所有的属性(字段)

  1. Field[] fields = cls.getField();
  2. for(Field f : fields){
  3. System.out.println(f.getName());
  4. }

2.7 获取Class类对象

1、通过Class.forName("xxx")获取
image.png

  1. String classAllPath = "cn.ypf.Car";
  2. Class cls1 = Class.forName(classAllPath);
  3. sout(cls1);

2、通过类名.class获取
image.png

  1. Class cls2 = Car.class;
  2. sout(cls2);

3、 通过对象.getClass()获取
image.png

  1. Car car = new Car();
  2. Class cls3 = car.getClass();
  3. sout(cls3);

4、通过类加载器获取类的Class对象
image.png

  1. // 1 先得到类加载器
  2. ClassLoader classLoader = car.getClass().getClassLoader();
  3. // 2 通过类加载器得到Class对象
  4. Class cls4 = classLoader.loadClass(classAllPath);
  5. sout(cls4);

在堆内存中,对某个类Car而言,只能有一个Class对象与这个类Car对应。所以上面的cls1~4都是同一个,可以试着输出上面四个对象的hashCode,一定是一样的。

5、8种基本数据类型按如下方式得到Class类对象

  1. Class<Integer> integerClass = int.class;
  2. Class<Character> characterClass = char.class;
  3. Class<Boolean> booleanClass = boolean.class;
  4. sout(integerClass);
  5. ......

6、8种基本数据类型对应的包装类,可通过 .TYPE 得到Class类对象

  1. Class<Integer> integerType = Integer.TYPE;
  2. Class<Character> characterType = Character.TYPE;
  3. sout(integerType);
  4. ......

通过输出5中的integerClass和6中的integerType 的hashCode,可以发现他两的hashCode相等,说明是同一个对象。

2.8 Class对象拥有者

image.png

  1. Class<String> cls1 = String.class;
  2. Class<Serializable> cls2 = Serializable.class;
  3. Class<Integer[]> cls3 = Integer[].class;
  4. Class<Long> cls4 = long.class;
  5. Class<void> cls5 = void.class;
  6. Class<Class> cls6 = Class.class;

2.9 类加载

image.png
静态加载:
其实就是在编译的时候就加载,IDEA中就是这样,看有没有语法错误,如果有则直接爆红。
动态加载:
只有在运行到此行问题代码时才会报错,如果没运行到(像if-else),则就算代码有错误,还是不会报错。反射就是一种动态加载,运行时没有执行到代码来就不会报错。

案例:

  1. 代码太多,直接看 反射P10

image.png
详解类加载各个阶段:
image.png
加载阶段
image.png
连接阶段

  1. 验证

image.png

  1. 准备

image.png
举个例子:
image.png

  1. 解析

image.png
初始化:
image.png

2.10 反射之暴破

2.10.1 暴破创建实例

image.png
1、创建一个User类

  1. class User{
  2. private int age = 10;
  3. private String name = "peng";
  4. public User(){}
  5. public User(String name){
  6. this.name = name;
  7. }
  8. private User(int age, String name){
  9. this.age = age;
  10. this.name = name;
  11. }
  12. }

2、通过public的无参构造器创建实例

  1. // 先获取到User类的Class对象
  2. Class userClass = Class.forName("cn.ypf.User");
  3. // 通过public的无参构造器创建实例
  4. Object o = userClass.newInstance();
  5. sout(o);

3、通过public的有参构造器创建实例

  1. //先得到对应的构造器
  2. Constructor cons = userClass.getConstructor(String.class);
  3. // 创建实例对象,并传入实参
  4. Object o = cons.newInstance("peng");
  5. sout(o);

4、通过非public的有参构造器创建实例

  1. // 得到private的构造器对象
  2. Constructor cons2 = userClass.getDeclaredConstructor(int.class, String.class);
  3. // 暴破【暴力破解】,使用反射可以访问到private构造器
  4. cons2.setAccessible(true);
  5. // 创建实例,并传入实参
  6. Object o = cons2.newInstance(20, "zhangsan");
  7. sout(o);

2.10.2 暴破操作属性

image.png
1、创建一个User类

  1. class User{
  2. public int age;
  3. private static String name;
  4. public User(){}
  5. public User(String name){
  6. this.name = name;
  7. }
  8. private User(int age, String name){
  9. this.age = age;
  10. this.name = name;
  11. }
  12. }

2、通过反射操作属性age

  1. // 先获取到User类的Class对象
  2. Class userClass = Class.forName("cn.ypf.User");
  3. // 通过public的无参构造器创建实例
  4. Object o = userClass.newInstance();
  5. // 使用反射得到age属性
  6. Field age = userClass.getField("age");
  7. age.set(o, 25);
  8. sout(age.get(o));

3、通过反射操作私有属性name

  1. Field name = userClass.getDeclaredField("name");
  2. // 对name进行暴破,操作私有属性
  3. name.setAccessible(true);
  4. name.set(o, "ypf");
  5. sout(o);
  6. // 由于name属性是静态的,也可以把o改成null
  7. name.set(null, "peng");
  8. sout(o);

2.10.3 暴破操作方法

image.png
1、创建一个User类

  1. class User{
  2. public int age;
  3. private static String name;
  4. public User(){}
  5. private static String say(int n, String s) {
  6. return n + " " + s;
  7. }
  8. public void hi(String s){
  9. sout("hi, " + s);
  10. }
  11. }

2、通过反射操作public方法

  1. // 先获取到User类的Class对象
  2. Class userClass = Class.forName("cn.ypf.User");
  3. // 通过public的无参构造器创建实例
  4. Object o = userClass.newInstance();
  5. // 调用public的hi方法,getMethod只能获取到public的
  6. Method hi = userClass.getMethod("hi", String.class);
  7. // 或者使用这个也行,getDeclaredMethod可以获取到所有的
  8. Method hi = userClass.getDeclaredMethod("hi", String.class);
  9. // 调用
  10. hi.invoke(o, "peng");

3、通过反射操作private方法

  1. Method say = userClass.getDeclaredMethod("say", int.class, String.class);
  2. // say方法是private的,需要爆破
  3. say.setAccessible(true);
  4. //调用
  5. sout(say.invoke(o, 50, "zhangsan"));
  6. // 因为say方法又是静态的,还可以这么写
  7. sout(say.invoke(null, 30, "lisi"));

注:在反射中,如果方法有返回值,统一返回Object,但是运行类型和方法定义的返回类型一致,如;

  1. Object res = say.invoke(null, 66, "wangwu");
  2. sout(res.getClass()); // 输出say方法的返回值类型:String

2.11 反射练习1

image.png
1、定义PrivateTest类

  1. public class PrivateTest{
  2. private String name = "hellokitty";
  3. public String getName(){
  4. return name;
  5. }
  6. }

2、定义测试类

  1. public class ReflectionTest{
  2. @Test
  3. public void test1(){
  4. // 1、得到PrivateTest类对应的Class对象
  5. Class clazz = Class.forName("cn.ypf.reflection");
  6. //2、创建对象实例
  7. Object o = clazz.newInstance();
  8. //3、得到私有name属性
  9. Field name = clazz.getDeclaredField("name");
  10. //4、私有的要暴破
  11. name.setAccessible(true);
  12. //5、为name属性设置新值
  13. name.set(o, "张三");
  14. }
  15. @Test
  16. public void test2(){
  17. // 1、得到PrivateTest类对应的Class对象
  18. Class clazz = Class.forName("cn.ypf.reflection");
  19. //2、创建对象实例
  20. Object o = clazz.newInstance();
  21. //3、得到getName方法对象
  22. Method method = clazz.getMethod("getName");
  23. //4、getName是共有的,直接调用
  24. Object invoke = method.invoke(o);
  25. sout(invoke);
  26. }
  27. }

2.12 反射练习2

image.png
定义一个测试方法:

  1. @Test
  2. public void test3(){
  3. // 得到File类的Class对象
  4. Class clazz = Class.forName("java.io.File");
  5. // 得到所有的构造器
  6. Constructor[] cons = clazz.getDeclaredConstructors();
  7. // 遍历输出
  8. for(Constructor[] con : cons){
  9. sout(con);
  10. }
  11. // 创建File对象,用有参构造器创建,因为file对象要传入一个String类型的字符串
  12. Constructor con = clazz.getDeclaredConstructor(String.class);
  13. // 创建File对象
  14. Object file = con.newInstance("E:\\mynew.txt");
  15. // 得到 createNewFile 的方法对象
  16. Method method = clazz.getMethod("createNewFile");
  17. // 调用 createNewFile 方法创建文件
  18. method.invoke(file);
  19. }