一、反射入门
1.1 反射之前
首先创建一个实体类Person,类中含有两个属性name(private)和age(public),还有一个参数为name的私有构造器,还有一个私有方法showNation。
public class Person {
private String name;
public int age;
@Override
public 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类的对象调用其内部私有结构(私有属性、私有构造器、私有方法)。
@Test
public 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的操作
@Test
public 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.Cat
method=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类型的对象clazz
Class 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.Car
System.out.println(cls);
// 输出cls的运行时类型,即 java.lang.Class
System.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改成null
name.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{
@Test
public 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, "张三");
}
@Test
public 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
定义一个测试方法:
@Test
public 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);
}