本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一.反射的引入
1.先了解反射的作用
对于任意一个类,可以知道这个类有哪些属性和方法
对于任意一个对象,可以知道调用它的任意一个方法
动态获取类的信息和动态调用对象的方法的功能。
可以在运行时检查类,接口,方法和变量等信息,无需知道类的名字,方法名等
还可以在运行时实例化新对象,调用方法,以及设置和获取变量值。
2.反射的引入
通过一个案例来引入反射
1.有这样一个需求
美团外卖与支付宝,微信,招商银行等厂商合作,这些厂商提供付款功能,那么需要美团来制定标准,其他厂商来实现。
定义美团支付的接口,厂商提供实现类去继承
2.实现代码
美团的接口 public interface Mtwm { void payOnline(); } 微信的支付实现类 public class WeChat implements Mtwm{ @Override public void payOnline() { System.out.println("使用微信支付"); } } 支付宝的支付实现类 public class AliPay implements Mtwm{ @Override public void payOnline() { System.out.println("使用支付宝支付"); } } 银行卡的支付实现类 public class BankCard implements Mtwm{ @Override public void payOnline() { System.out.println("银行卡支付"); } } 假设str是选择支付方式时传进来的值 public class Test { public static void main(String[] args) { String str = "微信"; if("微信".equals(str)){ pay(new WeChat()); } if("支付宝".equals(str)){ pay(new AliPay()); } if("银行卡".equals(str)){ pay(new BankCard()); } } public static void pay(WeChat wc){ wc.payOnline(); } public static void pay(AliPay wc){ wc.payOnline(); } public static void pay(BankCard wc){ wc.payOnline(); } }
这样的不好之处是当需要再添加厂商时,需要手动去写支付的方法和if的判断,拓展性不好。
所以利用多态可以改进支付方法。
public class Test { public static void main(String[] args) { String str = "微信"; if("微信".equals(str)){ pay(new WeChat()); } if("支付宝".equals(str)){ pay(new AliPay()); } if("银行卡".equals(str)){ pay(new BankCard()); } } public static void pay(Mtwm m){ m.payOnline(); } }
还有最后一个缺点,就是继续提高拓展性,不想再通过添加if来判断。
这就体现了反射的作用。
public class Test { public static void main(String[] args) throws Exception{ String str = "introduce.WeChat"; Class cls = Class.forName(str); Object o = cls.newInstance(); Method method = cls.getMethod("payOnline"); method.invoke(o); } }
通过反射来解决拓展性的问题。
二.反射学习
1.反射的概念
编辑
编辑
一个字节码文件对应一个Class对象,通过Class对象能获取到这个字节码文件的全部信息,这个Class对象就像一面镜子,光线经过Class对象,反射到了字节码文件上,看到了字节码文件的信息,所以称之为反射。
反射是动态的,因为只有程序运行起来,才会在JVM中加载出Class对象
2.Class类的理解
编辑
想要获取红色:电脑类->张三的电脑->红色
想要获取方法:Class类->具体实例或对象->方法
所以Class类就是向上的抽取。
3.读取字节码信息的四种方式
用到的代码,为了阅读方便,将不同Java文件的代码放到了一起。
public class Person { private int age; public String name; private void eat() { System.out.println("PersonEat"); } public void sleep(){ System.out.println("PersonSleep"); } } public class Student extends Person{ private int sno; double height; protected double weight; public double score; public String showInfo(){ return "我是好学生"; } private void work(){ System.out.println("工作"); } public Student(){ System.out.println("空构造器"); } private Student(int sno){ this.sno = sno; } Student(int sno,double weight){ this.sno = sno; this.weight = weight; } } public class Test { public static void main(String[] args) throws Exception{ //方式1 Person p = new Person(); Class class1 = p.getClass(); //方式2 Class class2 = Person.class; //方式3 Class class3 = Class.forName("learn2.Person"); //方式4 ClassLoader classLoader = Test.class.getClassLoader(); Class class4 = classLoader.loadClass("learn2.Person"); } }
方式1和方式2不常用,因为获取Class对象的目的是获取具体实例再获取属性方法等,既然已经知道了类或者该类的具体实例,就能直接获取属性和方法了。
方式3最常用。
方式4通过获取类加载器,所以字节码的加载都需要经过类加载器,所以任何类都可以获得到类加载器,再通过类加载器去获取Class对象。
4.Class类的具体实例
1.类:外部类,内部类
2.接口
3.注解
4.数组
5.基本数据类型
6.void
public class Test { public static void main(String[] args) throws Exception{ Class c1 = Test.class; Class c2 = Comparable.class; Class c3 = Override.class; int []arr1 = {1,2,3}; int []arr2 = {4,5,6}; Class c4 = arr1.getClass(); Class c5 = arr2.getClass(); System.out.println(c4==c5);//true Class c6 = int.class; Class c7 = void.class; } }
三.利用反射
1.获取构造器并创建对象
@MyAnnotation(value = "hello") public class Student extends Person implements MyInterface{ private int sno; double height; protected double weight; public double score; @MyAnnotation(value = "himethod") public String showInfo(){ return "我是好学生"; } public String showInfo(int a,int b){ return "重写方法,我是好学生"; } private void work(){ System.out.println("工作"); } void happy(){ System.out.println("开心"); } protected int getSno(){ return sno; } public Student(){ System.out.println("空构造器"); } public Student(double weight,double score){ this.weight = weight; this.score = score; } private Student(int sno){ this.sno = sno; } Student(int sno,double weight){ this.sno = sno; this.weight = weight; } protected Student(int sno,double height,double weight){ this.sno = sno; } @MyAnnotation(value = "hellomethod") @Override public void myMethod() { System.out.println("重写myMethod"); } @Override public String toString() { return "Student{" + "sno=" + sno + ", height=" + height + ", weight=" + weight + ", score=" + score + '}'; } }
//获取构造器并创建对象 public class Test { public static void main(String[] args) throws Exception{ Class studentClass = Student.class; //通过字节码信息获取构造器 //只能获取被public修饰的构造器 Constructor[] constructors = studentClass.getConstructors(); for(Constructor c : constructors){ System.out.println(c); } System.out.println("----------我是分割线-----------"); //得到所有被修饰的构造器 Constructor[] declaredConstructors = studentClass.getDeclaredConstructors(); for(Constructor c : declaredConstructors){ System.out.println(c); } System.out.println("----------我是分割线-----------"); //获取指定构造器 //什么参数都不传得到空构造器 Constructor constructor = studentClass.getConstructor(); System.out.println(constructor); System.out.println("----------我是分割线-----------"); //得到两个参数的有参构造器 Constructor constructor1 = studentClass.getConstructor(double.class, double.class); System.out.println(constructor1); //获取private修饰的构造器 Constructor constructor2 = studentClass.getDeclaredConstructor(int.class); System.out.println(constructor2); //有了构造器就能创建对象了 Object o = constructor.newInstance(); System.out.println(o); Object o1 = constructor1.newInstance(100, 100); System.out.println(o1); } }
获取构造器时
1.用public修饰的构造器用getConstructor()或getConstructors()获取
2.不用public修饰的构造器用getDeclaredConstructor()或getDeclaredConstructors()获取
3.上面方法的参数要根据具体构造器的参数传入对于的class
创建对象时
用newInstance(),参数要传具体的数据。
2.获取属性并赋值
public class Test02 { public static void main(String[] args) throws Exception{ Class cls = Student.class; //获取运行时类和父类的public修饰的属性 Field[] fields = cls.getFields(); for(Field f : fields){ System.out.println(f); } System.out.println("-----------------------------"); //获取运行时类所有属性 Field[] declaredFields = cls.getDeclaredFields(); for(Field f : declaredFields){ System.out.println(f); } System.out.println("-----------------------------"); //获取具体的public修饰的属性 Field score = cls.getField("score"); System.out.println(score); //获取具体的非public修饰的属性 Field sno = cls.getDeclaredField("sno"); System.out.println(sno); //属性的具体结构 //获取修饰符的数量, int modifiers = sno.getModifiers(); System.out.println(modifiers);//输出2 System.out.println(Modifier.toString(modifiers));//输出private //获取数据的具体类型 Class type = sno.getType(); System.out.println(type); //获取属性的名字 System.out.println(sno.getName()); //给属性赋值 //要有对象和属性对应的值 Field score1 = cls.getField("score"); Object object = cls.newInstance(); score1.set(object,98); System.out.println(object); }
3.获取方法和调用方法
public class Test03 { public static void main(String[] args) throws Exception { Class cls = Student.class; //获取方法 //获取被public修饰的运行时类的方法和所有父类中的方法 Method[] methods = cls.getMethods(); for(Method m : methods){ System.out.println(m); } System.out.println("------------------------"); //获取运行时类中所有方法 Method[] methods1 = cls.getDeclaredMethods(); for(Method m : methods1){ System.out.println(m); } System.out.println("------------------------"); //获取指定public修饰无参方法 Method method = cls.getMethod("showInfo"); System.out.println(method); //获取指定public修饰有参方法 Method method1 = cls.getMethod("showInfo", int.class, int.class); System.out.println(method1); //获取非public修饰的无参方法 Method method2 = cls.getDeclaredMethod("work"); System.out.println(method2); //获取方法的具体结构 //获取方法的名字 System.out.println(method2.getName()); //获取修饰符 int modifiers = method2.getModifiers(); System.out.println(Modifier.toString(modifiers)); //获取返回值 System.out.println(method2.getReturnType()); //获取参数的类型 Class[] parameterTypes = method2.getParameterTypes(); for(Class c : parameterTypes){ System.out.println(c); } //获取方法的注解 //只能获取到RUNTIME的注解 Method mymethod = cls.getMethod("myMethod"); Annotation[] annotation = mymethod.getAnnotations(); for(Annotation annotation1 : annotation){ System.out.println(annotation1); } //获取异常 Class[] exceptionTypes = mymethod.getExceptionTypes(); for(Class c : exceptionTypes){ System.out.println(c); } //调用方法 //说明这个方法是哪个对象的方法和方法的参数 Object o = cls.newInstance(); mymethod.invoke(o); //调用有参数的方法 System.out.println(method1.invoke(o, 12, 25)); } }
4.获取类的接口,所在包,注解
public class Test04 { public static void main(String[] args) { //获取字节码信息的对象 Class cls = Student.class; //获取运行时类的接口 Class[] interfaces = cls.getInterfaces(); for(Class c : interfaces){ System.out.println(c); } //得到父类的接口:先得到父类的字节码信息->再得到父类的接口 Class[] interfaces1 = cls.getSuperclass().getInterfaces(); //获取运行时类所在的包 Package aPackage = cls.getPackage(); System.out.println(aPackage); //直接获取包名 System.out.println(aPackage.getName()); //获取运行时类的注解 Annotation[] annotations = cls.getAnnotations(); for(Annotation a : annotations){ System.out.println(a); } } }
【小白也能听懂的】Java反射与自定义注解底层原理,(从入门到入坟)_哔哩哔哩_bilibili
\