反射
反射机制是在运行状态中
对于任意一个类,都能知道这个类的所有属性和方法
对于任意一个对象,都能调用他的任意一个方法和属性
反射提供的功能
在运行时判断任意一个对象所属于的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法
生成动态代理
反射入口 – 获取class对象
在java中有两种对象 — 实例对象和class对象,实例对象大家都会使用的比较多,就是我们平时直接new 出来的对象,通过对象可以get set属性值,还有执行这个对象所属类的一些方法等等操作,class对象对于我们来说可能是比较陌生的一个概念,但是class对象在反射中却被称之为入口对象,因为:
第一,class对象包含着与类有关的信息
第二,实例对象就是通过class对象来创建的
第三。每一个类只有一个class对象
如何获取class对象
1.class 的class.forName(“全类名”)无需从类的实例获取class对象
// 第一种方式:class.forName("全类名")
Class personClass1 = Class.forName("com.it.reflect.Person");
2.类.class 通过class字面量获取class对象引用。相比其他两种,更加安全和简单
// 第二种方式: 类.class
Class personClass2 = Person.class;
3.类对象.getClass() 需从类的实例获取class对象
Person person = new Person();
Class personClass3 = person.getClass();
补充:
- 无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,说通俗点就是,class对象保存了这个类所有的类型信息,每一个实例对象就是从这个class获取信息的,如图所示:
- class类只存在私有构造函数
- class类的对象作用是在运行时提供或获得某个对象的类型信息
通过反射获取方法
- 获取所有公共方法(本类,父类以及父接口的公共方法)
// 获取所有公有方法
Method[] methods = personClass.getMethods();
- 获取所有的方法(包括私有方法)
// 获取所有方法(包括私有方法)
Method[] allMethod = personClass.getDeclaredMethods();
完整如下:
public static void getMethodDemo() {
// 获取class对象,进入反射入口
Class personClass = Person.class;
// 获取所有公有方法
Method[] methods = personClass.getMethods();
for(Method m : methods) {
System.out.println("Person所有方法如下:"+m+"\n");
}
// 获取所有方法(包括私有方法)
Method[] allMethod = personClass.getDeclaredMethods();
for(Method m : allMethod) {
System.out.println("Person所有方法包括私有方法如下:"+m+"\n");
}
}
结果:
Person所有方法如下:public java.lang.String com.it.reflect.Person.getName()
Person所有方法如下:public int com.it.reflect.Person.getId()
Person所有方法如下:public void com.it.reflect.Person.setName(java.lang.String)
Person所有方法如下:public int com.it.reflect.Person.getAge()
Person所有方法如下:public void com.it.reflect.Person.publicMethod()
Person所有方法如下:public void com.it.reflect.Person.setAge(int)
Person所有方法如下:public void com.it.reflect.Person.setId(int)
Person所有方法如下:public final void java.lang.Object.wait() throws java.lang.InterruptedException
Person所有方法如下:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
Person所有方法如下:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
Person所有方法如下:public boolean java.lang.Object.equals(java.lang.Object)
Person所有方法如下:public java.lang.String java.lang.Object.toString()
Person所有方法如下:public native int java.lang.Object.hashCode()
Person所有方法如下:public final native java.lang.Class java.lang.Object.getClass()
Person所有方法如下:public final native void java.lang.Object.notify()
Person所有方法如下:public final native void java.lang.Object.notifyAll()
Person所有方法包括私有方法如下:public java.lang.String com.it.reflect.Person.getName()
Person所有方法包括私有方法如下:public int com.it.reflect.Person.getId()
Person所有方法包括私有方法如下:public void com.it.reflect.Person.setName(java.lang.String)
Person所有方法包括私有方法如下:private void com.it.reflect.Person.privateMethod()
Person所有方法包括私有方法如下:public int com.it.reflect.Person.getAge()
Person所有方法包括私有方法如下:public void com.it.reflect.Person.publicMethod()
Person所有方法包括私有方法如下:public void com.it.reflect.Person.setAge(int)
Person所有方法包括私有方法如下:public void com.it.reflect.Person.setId(int)
获取所有的接口
// 获取class对象,进入反射入口
Class personClass = Person.class;
// 获取所有接口
System.out.println("获取所有接口:"+personClass.getInterfaces());
获取所有父类,并且获取父类的所有公共方法
public static void getSuper() {
// 获取class对象,进入反射入口
Class personClass = Person.class;
Class superClass = personClass.getSuperclass();
Method[] method = superClass.getMethods();
for(Method m : method) {
System.out.println("父类的所有公共方法:"+m);
}
}
由于Person没有继承其他类,所以他的父类默认就是Object
父类的所有公共方法:public final void java.lang.Object.wait() throws java.lang.InterruptedException
父类的所有公共方法:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
父类的所有公共方法:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
父类的所有公共方法:public boolean java.lang.Object.equals(java.lang.Object)
父类的所有公共方法:public java.lang.String java.lang.Object.toString()
父类的所有公共方法:public native int java.lang.Object.hashCode()
父类的所有公共方法:public final native java.lang.Class java.lang.Object.getClass()
父类的所有公共方法:public final native void java.lang.Object.notify()
父类的所有公共方法:public final native void java.lang.Object.notifyAll()
获取该类的构造方法
// 获取所有构造方法
Constructor[] constructor = personClass.getDeclaredConstructors();
通过构造方法获取实例对象
// 通过无参构造方法实例化对象
Person person = (Person) personClass.getConstructor().newInstance();
// 通过有参构造方法实例化对象
Person person2 = (Person) personClass.getConstructor(int.class,String.class).newInstance(21,"kevin");
通过查看获取类构造方法这个方法
Constructor<T> getConstructor(Class<?>... parameterTypes)
可以看到,这个方法的参数列表是一个可变参数,当不传参时,会找出无参构造方法,也可以指定具体参数,参数传得是参数类型的class对象,找具体的构造方法后,通过newInstance()这个方法便可以构造出实例对象,当调用有参构造时,newInstance()里传得就是具体的参数值
补充:
这两个一样吗:
personClass.getConstructor(int.class,String.class).newInstance(21,"kevin");
personClass.getConstructor(Integer.class,String.class).newInstance(21,"kevin");
这样会报错,在反射中,根据类型获取方法时:基本类型和包装类是不可以通用的,是两种类型
结果:
person所有构造方法:public com.it.reflect.Person()
person所有构造方法:public com.it.reflect.Person(int,java.lang.String)
person 无参构造
有参构造:21 kevin
获取所有的属性
- 获取所有的公共属性
Field[] fields = personClass.getFields();
- 获取所有的属性(包括私有属性)
Field[] fields = personClass.getDeclaredFields();
- 获取指定属性并进行赋值
Person person = (Person) personClass.newInstance();
// 获取指定字段
Field field = personClass.getDeclaredField("id");
// 消除private 的影响
field.setAccessible(true);
// 为字段赋值
field.set(person, 21);
System.out.println("ID"+person.getId());
结果:
person 无参构造
ID21
反射可以越过泛型检查 — 实际开发不推荐
public static void crossT() throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Class listClass = list.getClass();
Method method = listClass.getMethod("add", Object.class);
method.invoke(list, 123);
System.out.println(list.size());
}
输出结果:4
可以看到,现在的list中不单单可以添加String类型的元素 ,可以添加元素的范围大到Object,但是在实际开发中并不建议这么使用,因为这会使得程序不安全
如何去理解反射?
- 第一,首先应该理解什么是反,为什么是反?
原来我们仔使用某个类的时候必定知道它是什么类,用来干什么,于是,我们直接对他用new进行实例化,这就是 正 ,但是现在要求我们在不知道要初始化的类对象是什么的时候,自然就无法通过new来实例化对象,这时候就需要用到反射的API 进行反射调用 - 现在很多流行的框架都用了反射,例如spring框架的ioc原理重点就是反射,举个例子好理解:
比如订餐程序 美团外卖,在下单支付的时候,我们可以通过支付宝支付,可以通过微信支付,可以通过银行卡直接 等等途径。那么在美团外卖和这些支付的公司合作的时候,就一定要制定一个支付的准则,这个准则,在java中怎么表示?自然是用接口了,所以由美团外卖指定接口,然后这些合作的公司去实现这个接口,现在有两个类实现了这个接口,一个类是支付宝支付,另一个是微信支付,你作为用户来说,你肯定是要么用支付宝支付,要么用微信支付,二选一。但是难道你说用支付宝支付,然后你自己过来改个源码,然后明天支付宝没钱了你用微信支付,你又跑来改源码,说你用微信支付。。这不可能把?你可能想,那么可以让程序员在定义的时候,就用if-else进行判断,但是这样,代码的可扩展性就不好,如果哪天要来个京东支付或者什么银行卡支付,这又得加多几个 else if ,这会很耗时间和精力,但是如果通过反射,让客户在选择支付方式的时候,前台传过来接口实现类的全类名,在通过class.forName()构造出相应实例,调用相应支付方法,这样代码的可扩展性就很好了