一、反射入门
1.1 反射之前
首先创建一个实体类Person,类中含有两个属性name(private)和age(public),还有一个参数为name的私有构造器,还有一个私有方法showNation。
public class Person {private String name;public int age;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Person(String name, int age) {this.name = name;this.age = age;}private Person(String name) {this.name = name;}public Person() {System.out.println("Person()");}public void show(){System.out.println("你好,我是一个人");}private String showNation(String nation){System.out.println("我的国籍是:" + nation);return nation;}}
反射之前,我们要想获取Person类中的属性和方法,必须得通过Person对象来操作。并且,在Person类外部,不可以通过Person类的对象调用其内部私有结构(私有属性、私有构造器、私有方法)。
@Testpublic void test1() {//1.创建Person类的对象Person p1 = new Person("Tom", 12);//2.通过对象,调用其内部的属性、方法p1.age = 10;System.out.println(p1.toString());p1.show();//在Person类外部,不可以通过Person类的对象调用其内部私有结构。//比如:name、showNation()以及私有的构造器}
那如果想获取Person类中定义的私有结构,就得通过反射了。
1.2 反射操作
通过反射来创建Person类的对象
//反射之后,对于Person的操作@Testpublic void test2() throws Exception{Class clazz = Person.class;//1.通过反射,创建Person类的对象Constructor cons = clazz.getConstructor(String.class, int.class);Object obj = cons.newInstance("Tom", 12);Person p = (Person) obj;System.out.println(p.toString());}
通过反射,调用对象指定的属性和方法。
//2.通过反射,调用对象指定的属性、方法//调用属性Field age = clazz.getDeclaredField("age");age.set(p, 10);System.out.println(p.toString());//调用方法Method show = clazz.getDeclaredMethod("show");show.invoke(p);
此外,通过反射也可以调用Person类的私有结构的。比如:私有的构造器、方法、属性。
//调用私有的构造器Constructor cons1 = clazz.getDeclaredConstructor(String.class);cons1.setAccessible(true);Person p1 = (Person) cons1.newInstance("Jerry");System.out.println(p1);
//调用私有的属性Field name = clazz.getDeclaredField("name");name.setAccessible(true);name.set(p1,"HanMeimei");System.out.println(p1);
// 调用私有的方法Method showNation = clazz.getDeclaredMethod("showNation", String.class);showNation.setAccessible(true);// 相当于String nation = p1.showNation("中国")String nation = (String) showNation.invoke(p1,"中国");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
Class clazz1 = Person.class;
方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();Class clazz2 = p1.getClass();
方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
对于同一个类,上面四种方式产生的clazz对象都是同一个,即以下语句输出的结果为true。
System.out.println(clazz1 == clazz2);System.out.println(clazz1 == clazz3);System.out.println(clazz1 == clazz4);
二、老韩听课笔记
2.1 引出问题

1、定义一个re.properties配置文件,内部写有两个键值对,如下:
classfullpath=cn.ypf.Catmethod=hi
2、在cn.ypf包下新建一个Cat类,在relfection.question包下新建ReflectionQuestion.java类。
Cat.java:
public class Cat {private String name = "Tomcat";public void hi(){System.out.println("hi " + name);}}
ReflectionQuestion.java:
如果使用传统的方式调用Cat类中的hi()方法
// 传统方式调用Cat类中的hi()方法Cat cat = new Cat();cat.hi();
使用IO流读取配置文件里面的值,通过值来创建对象(其实是行不通的,读取配置文件得到的返回值是字符串,字符串怎么可以new呢???)
// 通过流来读取配置文件里面的内容Properties prop = new Properties();prop.load(new FileInputStream("src\\re.properties"));// 这里两变量classfullpath和method就是配置文件中的值,不过是字符串String classfullpath = prop.get("classfullpath").toString();String methodName = prop.get("method").toString();// 创建对象???行不通 ,得靠反射机制// new classfullpath ×
那么,既然上面方法行不通,那就得用反射机制来解决了。
// 反射机制来解决// 1 加载类,返回Class类型的对象clazzClass clazz = Class.forName(classfullpath);// 2 通过clazz对象得到你所加载的类(cn.ypf.Cat)的对象实例Object o = clazz.newInstacnce();// 对象.getClass()可以得到此对象的运行类型System.out.println("对象o的运行类型:" + o.getClass());// 3 通过clazz得到你加载的类(cn.ypf.Cat)的方法对象,即在反射中,方法也视为对象,万物皆对象Method method = clazz.getMethod(methodName);// 4 通过method方法对象来调用方法,而传统方式是使用对象.方法名(),反射中使用方法对象.invoke(对象)method.invoke(o);
2.2 反射机制

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

获得某个类的成员变量:
// Field对象表示某个类的成员变量,注意getField()方法不能获取私有属性Field fieldName = clazz.getField("age");//传统写法: 对象.成员变量//反射中写法: 成员变量对象.get(对象)fieldName.get(o);
获得某个类的构造方法:
// Constructor对象表示构造器Constructor cons1 = clazz.getConstructor();// ()中可以指定构造器参数类型Constructor cons2 = clazz.getConstructor(String.class);
获得某个类的成员方法:
// 通过clazz得到你加载的类(cn.ypf.Cat)的方法对象,即在反射中,方法也视为对象,万物皆对象Method method = clazz.getMethod("hi");// 通过method方法对象来调用方法,而传统方式是使用对象.方法名(),反射中使用方法对象.invoke(对象)method.invoke(o);
2.4 反射优缺点

实例测试:使用传统方式和反射机制来调用类中的方法.
// 传统方式public void m1() {Cat cat = new cat();long start = System.currentTimeMillis();for(int i = 0; i < 9000000000; i++){cat.hi();}long start = System.currentTimeMillis();System.out.println("传统方式耗时:" + (end - start));}
// 反射机制public void m2() {Class clazz = Class.forname("cn.ypf.Cat");Object o = clazz.newInstance();Method method = clazz.getMethod("hi");long start = System.currentTimeMillis();for(int i = 0; i < 9000000000; i++){method.invoke(o);}long start = System.currentTimeMillis();System.out.println("反射机制耗时:" + (end - start));}
2.5 反射调用优化

// 反射机制调优public void m3() {Class clazz = Class.forname("cn.ypf.Cat");Object o = clazz.newInstance();Method method = clazz.getMethod("hi");// 在反射调用方法时,取消访问检查,提高反射效率method.setAccessible(true);long start = System.currentTimeMillis();for(int i = 0; i < 9000000000; i++){method.invoke(o);}long start = System.currentTimeMillis();System.out.println("反射机制耗时:" + (end - start));}
2.6 Class类
Class类的继承结构图:
不管new多少个Cat对象,那么Cat类对应的Class类对象在堆内存中只会有一个,可以使用下面的代码测试:
Class cls1 = Class.forName("cn.ypf.Cat");Class cls2 = Class.forName("cn.ypf.Cat");System.out.println(cls1.hashCode());System.out.println(cls2.hashCode());
可以发现输出的hashCode是一样的,说明了对于某个类Cat的Class类对象,不管创建多少个Cat对象,在堆内存中只会有一个,因为类只加载一次。
Class类的常用方法如下:
案例:
// 定义一个Car类public class Car {public String brand;public Integer price;public String color;}
1、获取到Car类对应的 Class 对象
Class cls = Class.forName("cn.ypf.Car");// 输出cls对象,即是哪个类的Class对象, cn.ypf.CarSystem.out.println(cls);// 输出cls的运行时类型,即 java.lang.ClassSystem.out.println(cls.getClass());
2、得到包名
System.out.println(cls.getPackage().getName());
3、得到全类名
System.out.println(cls.getName());
4、通过cls创建对象实例
Car car = (car)cls.newInstance();System.out.println(car);
5、通过反射获取属性的值和给属性赋值
Field brand = cls.getField("brand");System.out.println(brand.get(car));brand.set(car, "奔驰");System.out.println(brand.get(car)); // 奔驰
6、得到所有的属性(字段)
Field[] fields = cls.getField();for(Field f : fields){System.out.println(f.getName());}
2.7 获取Class类对象
1、通过Class.forName("xxx")获取
String classAllPath = "cn.ypf.Car";Class cls1 = Class.forName(classAllPath);sout(cls1);
2、通过类名.class获取
Class cls2 = Car.class;sout(cls2);
3、 通过对象.getClass()获取
Car car = new Car();Class cls3 = car.getClass();sout(cls3);
4、通过类加载器获取类的Class对象
// 1 先得到类加载器ClassLoader classLoader = car.getClass().getClassLoader();// 2 通过类加载器得到Class对象Class cls4 = classLoader.loadClass(classAllPath);sout(cls4);
在堆内存中,对某个类Car而言,只能有一个Class对象与这个类Car对应。所以上面的cls1~4都是同一个,可以试着输出上面四个对象的hashCode,一定是一样的。
5、8种基本数据类型按如下方式得到Class类对象
Class<Integer> integerClass = int.class;Class<Character> characterClass = char.class;Class<Boolean> booleanClass = boolean.class;sout(integerClass);......
6、8种基本数据类型对应的包装类,可通过 .TYPE 得到Class类对象
Class<Integer> integerType = Integer.TYPE;Class<Character> characterType = Character.TYPE;sout(integerType);......
通过输出5中的integerClass和6中的integerType 的hashCode,可以发现他两的hashCode相等,说明是同一个对象。
2.8 Class对象拥有者

Class<String> cls1 = String.class;Class<Serializable> cls2 = Serializable.class;Class<Integer[]> cls3 = Integer[].class;Class<Long> cls4 = long.class;Class<void> cls5 = void.class;Class<Class> cls6 = Class.class;
2.9 类加载

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

详解类加载各个阶段:
加载阶段:
连接阶段:
- 验证

- 准备

举个例子:
- 解析
2.10 反射之暴破
2.10.1 暴破创建实例

1、创建一个User类
class User{private int age = 10;private String name = "peng";public User(){}public User(String name){this.name = name;}private User(int age, String name){this.age = age;this.name = name;}}
2、通过public的无参构造器创建实例
// 先获取到User类的Class对象Class userClass = Class.forName("cn.ypf.User");// 通过public的无参构造器创建实例Object o = userClass.newInstance();sout(o);
3、通过public的有参构造器创建实例
//先得到对应的构造器Constructor cons = userClass.getConstructor(String.class);// 创建实例对象,并传入实参Object o = cons.newInstance("peng");sout(o);
4、通过非public的有参构造器创建实例
// 得到private的构造器对象Constructor cons2 = userClass.getDeclaredConstructor(int.class, String.class);// 暴破【暴力破解】,使用反射可以访问到private构造器cons2.setAccessible(true);// 创建实例,并传入实参Object o = cons2.newInstance(20, "zhangsan");sout(o);
2.10.2 暴破操作属性

1、创建一个User类
class User{public int age;private static String name;public User(){}public User(String name){this.name = name;}private User(int age, String name){this.age = age;this.name = name;}}
2、通过反射操作属性age
// 先获取到User类的Class对象Class userClass = Class.forName("cn.ypf.User");// 通过public的无参构造器创建实例Object o = userClass.newInstance();// 使用反射得到age属性Field age = userClass.getField("age");age.set(o, 25);sout(age.get(o));
3、通过反射操作私有属性name
Field name = userClass.getDeclaredField("name");// 对name进行暴破,操作私有属性name.setAccessible(true);name.set(o, "ypf");sout(o);// 由于name属性是静态的,也可以把o改成nullname.set(null, "peng");sout(o);
2.10.3 暴破操作方法

1、创建一个User类
class User{public int age;private static String name;public User(){}private static String say(int n, String s) {return n + " " + s;}public void hi(String s){sout("hi, " + s);}}
2、通过反射操作public方法
// 先获取到User类的Class对象Class userClass = Class.forName("cn.ypf.User");// 通过public的无参构造器创建实例Object o = userClass.newInstance();// 调用public的hi方法,getMethod只能获取到public的Method hi = userClass.getMethod("hi", String.class);// 或者使用这个也行,getDeclaredMethod可以获取到所有的Method hi = userClass.getDeclaredMethod("hi", String.class);// 调用hi.invoke(o, "peng");
3、通过反射操作private方法
Method say = userClass.getDeclaredMethod("say", int.class, String.class);// say方法是private的,需要爆破say.setAccessible(true);//调用sout(say.invoke(o, 50, "zhangsan"));// 因为say方法又是静态的,还可以这么写sout(say.invoke(null, 30, "lisi"));
注:在反射中,如果方法有返回值,统一返回Object,但是运行类型和方法定义的返回类型一致,如;
Object res = say.invoke(null, 66, "wangwu");sout(res.getClass()); // 输出say方法的返回值类型:String
2.11 反射练习1

1、定义PrivateTest类
public class PrivateTest{private String name = "hellokitty";public String getName(){return name;}}
2、定义测试类
public class ReflectionTest{@Testpublic void test1(){// 1、得到PrivateTest类对应的Class对象Class clazz = Class.forName("cn.ypf.reflection");//2、创建对象实例Object o = clazz.newInstance();//3、得到私有name属性Field name = clazz.getDeclaredField("name");//4、私有的要暴破name.setAccessible(true);//5、为name属性设置新值name.set(o, "张三");}@Testpublic void test2(){// 1、得到PrivateTest类对应的Class对象Class clazz = Class.forName("cn.ypf.reflection");//2、创建对象实例Object o = clazz.newInstance();//3、得到getName方法对象Method method = clazz.getMethod("getName");//4、getName是共有的,直接调用Object invoke = method.invoke(o);sout(invoke);}}
2.12 反射练习2

定义一个测试方法:
@Testpublic void test3(){// 得到File类的Class对象Class clazz = Class.forName("java.io.File");// 得到所有的构造器Constructor[] cons = clazz.getDeclaredConstructors();// 遍历输出for(Constructor[] con : cons){sout(con);}// 创建File对象,用有参构造器创建,因为file对象要传入一个String类型的字符串Constructor con = clazz.getDeclaredConstructor(String.class);// 创建File对象Object file = con.newInstance("E:\\mynew.txt");// 得到 createNewFile 的方法对象Method method = clazz.getMethod("createNewFile");// 调用 createNewFile 方法创建文件method.invoke(file);}

