前言
1.它是什么
它就像一面具有特异功能的镜子,通过类的全量限定名(包名+类名),复制出和原类功能上没有任何差异的镜像类,包括类的私有属性、私有方法一并会被这面镜子穿透,并获取其全部使用权!
注意:这个特异功能,是它的优点,也是它的缺点,破坏了封装性,不能随意使用。好比男生可以进女厕所,但是不要进的那么理所当然,进的那么随意,否则会有意想不到的后果!
2.它能干什么,有什么用
1.为男生合情合理进女厕所(化妆、整容、变性)提供了一种可能。
代码层面,那些理应受保护的私有属性、方法,在反射面前,是透明的都可以被据需使用。
2.特殊业务场景,可以使程序变得更具灵活性(代码量少而精悍),好比动态代理,它的实现就离不开反射。
一、反射的优缺点
1.优点
反射主要应用在对灵活性和扩展性要求较高的系统框架上,普通程序不建议使用。
2.缺点
1.反射的执行效率低于常规写法。
2.反射可以访问private、project、default修饰的本应该受保护(不应该被外界随意访问)属性、方法,使用不当会造成一些难以预知的问题。
3.反射等绕过了源代码的技术,会带来维护问题
二、获取反射类的四种方法(重点记忆第三种)
示例:Person的全量限定名是 com.succ.demo.Person
方式一:通过对象.getClass()
Person p = new Person();
Class c1 = p.getClass();
System.out.println(c1);
打印结果:class com.succ.demo.Person
方式二:通过内置class属性,直接对象.class
Class c2 = Person.class;
System.out.println(c2);
System.out.println(c1==c2);
//打印结果
//class class com.succ.demo.Person
//true
方式三: 调用Class类提供的静态方法Class.forName()该方式最为常用
Class c3 = Class.forName("com.succ.demo.Person");
方式四:利用类的加载器(了解技能点)
ClassLoader loader = Test.class.getClassLoader();//注:Test是当前类的类名
Class c4 = loader.loadClass("com.succ.demo.Person");
欲了解 Class.forName和ClassLoader.loadClass的区别
三、可以获取Class反射类6种类型
(1)类:外部类,内部类 (2)接口 (3)注解 (4)数组 (5)基本数据类型 (6) void
public class Demo {
public static void main(string[]args) {
Class c1 = Person.class;
Class c2 = Comparable.class;
Class c3 = Override.class;
System.out.println(c1.getName());//com.succ.demo.Person
System.out.println(c2.getName());//java.lang.Comparable
System.out.println(c3.getName());//java.lang.Override
int[] arr1 = {1,2,3]};
Class c4 = arr1.getc1ass();
int[] arr2 = {5,6,7};
Class c5 = arr2.getclass();
System.out.println(c4==c5);//true 同一个维度,同一个元素类型,得到的字节码就是同一个
Class c6 = int.class;
Class c7 = void.c1ass;
System.out.println(c6.getName());//int
System.out.println(c7.getName());//void
}
}
至此,理论知识点,就介绍到这里,下面全部是干货,以及用法
当你看到,某个方法中间含关键字 Declared时,代表要穿透,获取私有属性或方法
当你看到,方法以s结尾时,此方法获取的是列表
当你看到,方法没有以s结尾时,代表获取单个特定属性/方法
下面的货,比较干,浏览前,务必先知晓这些小细节!
四、获取所有构造方法,并创建反射对象
Class clazz= Class.forName("com.succ.demo.Person");//正常情况下,里面的参数是动态的
此处的clazz,就是反射对象,下文中出现的反射对象,都指的是clazz,下面不在累述。
1.获取构造方法列表
1.反射对象.getConstructors() 只能获取类中,被public修饰的构造函数列表
2.反射对象.getDeclaredConstructors() 可获取被任意修饰符修饰的构造函数列表
2.获取指定的构造方法
1.反射对象.getConstructor,获取被public修饰无参构造器
2.反射对象.getConstructor(double.class,double.class),获取被public修饰有两个参数的构造器
3.反射对象.getDeclaredConstructor(int.class),获取用任意修饰符修饰只有一个参数的构造器
4.反射对象.getConstructor(Class<?>... parameterTypes) 获取多个入参的构造方法
3.反射对象.newInstance(),创建对象
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Person p = (Person) Class.forName("com.succ.reflect.test.Person").newInstance();//java9中已被废弃
p.setId("100");
p.setName("zhangsan");
p.setScore(78.5);
System.out.println(p);
Person p2 = (Person) Class.forName("com.succ.reflect.test.Person").getDeclaredConstructor().newInstance();//java11的写法
p2=new Person("99", "chouniu",60.0);
System.out.println(p2);
}
}
注意:1.clazz.newInstance()该写法在JDK9中已被废弃。2.newInstance()只能实例化实体类的无参构造方法。举例:不能写作clazz.newInstance(“100”,“zhangsan”)直接调用其有参构造方法。
package com.succ.reflect.test;
public class Person {
public String id;
public String name;
public Double score;
public Person() {
super();
}
public Person(String id, String name) {
super();
this.id = id;
= name;
}
public Person(String id, String name, Double score) {
super();
this.id = id;
= name;
this.score = score;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", score=" + score + "]";
}
}
五、获取所有属性列表、单个属性、属性的具体信息
1.获取字段/属性列表
1.反射对象.getFields() 获取子类+父类所有用public修饰的属性列表
2.反射对象.getDeclaredFields() 只获取当前类被任意修饰符修饰的属性列表(不包含父类)
2.获取单个指定的字段/属性
1.反射对象.getField("score") 只获取单个被public修饰的属性
2.反射对象.getDeclaredField() 获取单个被任意修饰符修饰的属性
注:"score"是已知对象里面的某个字段,比如事先知道对象person里有个字段叫score.
六、获取所有方法列表和方法的调用
1.获取方法列表
1.反射对象.getMethods()获取所有被public修饰的方法列表(包含父类和超级父类)
2.反射对象.getDeclaredMethods()只获取自己本类被任意修饰符修饰的方法列表
Class clazz= Class.forName("com.succ.demo.Person");
Method[] ms1=clazz.getMethods();
Method[] ms2=clazz.getDeclaredMethods();
2.获取单个方法
1.反射对象.getMethod() 根据方法名只获取被public修饰的指定方法(无参或有参)
2.反射对象.getDeclaredMethod()根据方法名获取被任意修饰符修饰指定方法(无参或有参)
Method work1 = cls.getMethod("work");
Method work2 = cls.getMethod("work",int.class,int.class);
Method work = cls.getDeclaredMethod("work");
3.获取方法具体结构(名称、返回值、修饰符、注解等)
1.获取方法的名称
system.out. println(work.getName());
2.获取方法修饰符
int modifiers=work.getModifiers();
System.out.println(Modifier.toString(modifiers));//数字转为文字修饰符
3.获取方法返回值
system.out.println(work.getReturnType());
4.获取方法参数列表
Class[] parameterTypes = work.getParameterTypes();5.获取方法异常
class[] exceptionTypes = work.getExceptionTypes(); 6.获取方法的注解
Annotation[] annotations = work.getAnnotations();//仅获取生命周期是RUNTIME 的注解
注:注解的生命周期,通过注解上方修饰符RetentionPolicy来控制,它下面有三个固定的生命周期常量,使用时只能选其一,分别是:
a) RetentionPolicy.SOURCE 注解仅在源文件中有效,在编译时做校验后被编译器丢弃。
b) RetentionPolicy.CLASS 注解可保留在.class文件中,内存运行时不被保留(默认状态)
c) RetentionPolicy.RUNTIME 注解全程被保留,在内存运行时依然被保留
注:这个Override就是常用的注解之一
Modifier科普:
方法名称.getModifiers(),得到的返回值是一个数字。在反射中,方法的修饰符可能是1个数字或几个数字的组合体。
文字描述难表其义,上图!!
注:获取修饰符数字后,需要Modifier.toString(num)一下,才能得到想要的文字修饰符。
4.方法通过.invoke调用
Class clazz= Class.forName("com.succ.demo.Student");
Method work= clazz.getMethod("work");
Method sumNum= clazz.getMethod("sumNum");
object obj = clazz. newInstance();
work.invoke(obj );//无参数
sumNum.invoke(obj ,22,33);//有参数
七、获取属性列表、单个属性、属性的具体信息
1.获取字段(属性)具体结构(原理同上)
Class clazz= Class.forName("com.succ.demo.Person");
Field sno=clazz.getField("sno"); //只能获取被public修饰的属性
或者
Field sno=clazz.getDeclaredField("sno"); //可获取被任意修饰符修饰的属性1.属性.getName()获取属性名称 : sno.getName();
2.属性.getType()获取属性数据类型 : sno.getType();
3.属性.getModifiers()获取属性修饰符 : int num =sno.getModifiers();
注:获取修饰符数字后,需要Modifier.toString(num)一下,才能得到想要的文字修饰符。
2.给属性赋值
//给属性赋值:(给属性设置值,第一个入参,必须是字段所归属的对象)
Class clazz= Class.forName("com.succ.demo.Student");
Field score=clazz.getDeclaredField("score");
Object obj = clazz.newInstance();
score.set(obj, 98);//给obj这个对象的score学号属性设置具体的值,这个值为98
system.out.println(obj);
打印结果:
Student[sno=0,name="",score=98.0] //打印的实际是Student的toString()方法
八、获取当前类/父类的接口、所在包、注解
1.获取类的接口
反射对象.getInterfaces(),仅获取自己独有接口列表
如需获取父类接口,需先获取父类 = 当前类的反射对象.getSuperclass;再通过父类.getInterfaces()。
Class clazz= Class.forName("com.succ.demo.Student");
Class [] interfaces=clazz.getInterfaces();
Class father= clazz.getSuperclass();//获取父类
2.获取类所在的包
Class clazz= Class.forName("com.succ.demo.Student");
Package pack= clazz.getPackage();
system.out.println(pack);
system.out.println(pack.getName());//打印结果:
package com.succ.demo
com.succ.demo
3.获取类的注解
Class clazz= Class.forName("com.succ.demo.Student");
Annotation[]annotations = clazz.getAnnotations(); //方法的注解是method.getAnnotations()
九、模拟美团后台支付
public class MainTest {
public static void main(String[] args) throws Exception{
String payWay="com.succ.demo.AliPay";//从前台传
double paymoney=0.99;//从前台传
Class<?> cls=Class.forName(payWay);//通过反射,反射出对象类
Object obj=cls.newInstance();//实例化(初始化)对象
Method method=cls.getMethod("payOnline", float.class);//获取被public修饰的方法payOnline
method.invoke(obj, paymoney);//执行各支付类通用的方法
}
}
微信也好、支付宝也罢,只需要美团统一定义支付接口InterfacePayWay,里面定义一个payOnline的接口。
微信、支付宝、银联等支付类,只需实现InterfacePayWay接口,然后它们再分别重载各自的payOnline()方法就可以了,无论后期增加或减少多少种支付方式,上面这段核心代码都不需要变动。
就是这么强大!!!
总结
对于不了解注解的同学,浏览完以上内容或许,还是显得有些懵逼,下面总结梳理一下:
1、注解常用的映射方法:Class clazz= Class.forName("com.succ.demo.Student");
2、有了映射类clazz,就意味着你拿到了这个类的一切。
在获取属性s、方法s时只需要注意,要想获取受保护的构件,方法中间需要带Declared,如:getDeclaredMethods、getDeclaredMethod、getDeclaredFields、getDeclaredField
3、方法的执行用反射到的方法method.invoke(obj);该方法第一个参数,务必是映射类
尾言
反射就是破坏了封装性,但是为什么还要用?
它提高了程序灵活性和扩展性的同时,还提供了一种Declared特殊通道,可以访问私有属性和方法,好比男生通过化妆(反射)进入女厕所,但是并不建议这么做,在充分考虑清楚后果后,依然要进也能进得去。
择其善者而从之,则其不善者而改之,Nice!