基础知识反射

如何从new对象引出反射

new一个对象是在编译时期就确定的事情,在编译的时候就可以把对象类型确定下来

如果需求发生变更,可能有时候是创建类型B,那么则需要修改上述源码,为了满足需求则需要加入开关,把A、B两种类型加进去

但是如果此时又增加类型C、D,那岂不是又要修改源码,分支又会变得非常多

这时候就要引出反射了,我们通过在运行的时候告知 JVM我们要哪种类型,然后JVM直接把相应的类型生成然后给我们,而不是我们自己去生成各种类型,在切换不同的类型时,无需修改源码、编译程序,这就是反射

因此反射的思想可以总结为:在程序运行的过程中JVM帮我们确定以及解析相应的数据类型

作用:当我们在编译时期无法确定将要使用的类型时,通过反射在程序运行的过程中帮我们生成相应的类型、解析相应的数据类型,而不是我们自己去生成类型

反射的组成:

我们通过反射可以得到四部分内容,一,类的实例对象Class,无论想通过反射做什么,都必须先找到类的实例对象;二,类的属性Field,这个部分包含了类的所有属性信息,例如访问修饰符符、属性的数据类型、属性名;三,类的构造方法Constructor,包含了类的所有构造方法的所有信息,例如访问修饰符、参数类型、参数名字;四,类的方法Method,包含了类的所有方法信息,跟Constructor类似,但是Method有返回值类型而Constructor没有,因为构造方法是没有返回值的

demo:SmallPineapple类

public class SmallPineapple {
 public String name;
 public int age;
 private double weight; // ֛᯿ݝํᛔ૩Ꭳ᭲
 
 public SmallPineapple() {}
 
 public SmallPineapple(String name, int age) {
 this.name = name;
 this.age = age;
 }
 public void getInfo() {
 System.out.print("["+ name + " ጱଙἻฎғ" + age + "]");
}
}

反射的常见功能:一,在运行时获取一个类的class对象;二,在运行的过程中实例化一个对象;三、在运行中获取一个类的属性、构造方法、构造器、注解

获取类的Class对象:当我们写完一个java文件之后,通过javac会编译出一个.class文件出来,这个class文件包含了java文件里的所有东西,例如属性、方法、构造方法等,等class文件被加载到JVM的时候就会生成一个Class对象,这个class对象包含了类的所有信息

那么如何获取class对象?

1)类名.class,这种方式只有在编译时声明了该类的类型才能获取到这个类的class对象

eg:Class clazz=SmallPineapple.class

2)实例对象.getClass(),这种方式如同第一种,必须已经声明了该类的类型

eg:SmallPineapple smallPineapple=new SmallPineapple();

Class clazz=smallPineapple.getClass();

3)Class.forName(全限定名),全限定名即包名+类名,通过全限定名也能得到class对象

eg:Class clazz = Class.forName("com.bean.smallpineapple");
拿到class对象之后,就可以剥开它的皮(获取到类信息),指挥它做事(拿到所有方法)、看透它的一切(获取所有属性)、构造方法了
但是需要注意的事,我们通过上面三种方法拿到的class对象都是同一个,因为双亲委派模型,双亲委派模型可以保证在加载每个类的时候内存至多只有一个类
class对象有了,那么怎么获取实例?
1)class对象调用newInstance()方法,通过这种方式默认调用的无参的构造方法,由于是无参构造方法,所有属性对应的类型的初始值
Class clazz = Class.forName("com.bean.SmallPineapple");
SmallPineapple smallPineapple = (SmallPineapple) clazz.newInstance();
smallPineapple.getInfo();

2)Constructor构造器调用newInstance()方法,通过这种方式调用的是有参的构造方法,通过getConstructor可以获得指定参数类型的构造方法,然后通过newInstance传进自己想要的初始值

Class clazz = Class.forName("com.bean.SmallPineapple");
Constructor constructor = clazz.getConstructor(String.class, int.class);
//这个constructor将获得超级权限
constructor.setAccessible(true);
SmallPineapple smallPineapple2 = (SmallPineapple) constructor.newInstance("小明",
21);
smallPineapple2.getInfo();
获取类中的变量(Field)
-Field[]  getFields(),获取包括类中以及父类里被public修饰的变量,注意只是public方法
-Field[]  getField(String name),通过变量名获取类中以及父类里被public修饰的变量
-Field[]  getDeclaredFields(),该方法可以获取类中的所有变量(包括私有),但无法获取继承下来的变量,即使是被public修饰的
-Field[]  getDeclaredField(String name),通过变量名获取类中的所有变量(包括私有),但无法获取继承下来的变量,即使是被public修饰的
获取类中的方法(Method)
-Method[]  getMethods(),获取类中以及父类中被public修饰的所有方法
-Method  getMethod(String name, Class...<?> paramTypes),根据方法名字和参数列表类型来找到类中以及父类中被public修饰的方法
-Method[]  getDeclaredMethods(),获取类中所有的方法(包括私有),但是不包含父类里的方法
--Method  get DeclaredMethod(String name, Class...<?> paramTypes),根据方法名字和参数列表类型来找到类中的方法,包括私有方法,不包括父类里的方法
获取类中的构造器(Constructor)
-Constructor[]  getConstructors(),获取类中以及父类里被public修饰的所有构造方法
-Constructor  getConstructor(Class...<?> paramTypes),根据参数列表类型获取类中以及父类里被public修饰的构造方法
 
-Constructor[]  getDeclaredConstructors(),获取类中的所有构造方法
-Constructor  getDeclaredConstructor(Class...<?> paramTypes),根据参数列表类型获取类中的构造方法
总结:
无论是属性还是方法还是构造方法,都可以分为两类,不被declared修饰和被declared修饰的方法
-不被declared修饰的,可以获取到该类以及父类(或者父父类,往上找)中对应被public修饰的数据
-被declared修饰的,可以获取到该类中所有数据,包括私有的数据
如果想获取本类的所有变量、方法、构造器,以及父类的public变量、方法、构造方法,很简单,两种方法都调用,但是要放进set里,防止出现重复
PS:如果父类的属性用protected修饰,可以发现,子类是无法通过反射获取得到这些属性的,通过反射只能获取到父类的public变量,虽然说无法通过反射获取到,但是protected变量是确确实实存在于子类里的
获取注解
注解的获取跟属性、方法、构造方法的获取不同,它不属于类的信息;每个属性、方法、构造方法都可以被注解修饰,因此我们Field、Method、Constructor对象都可以通过以下几个方法获取到相应的注解
-Annotation[] getAnnotations(),获取对象上的所有注解
- Annotation getAnnotation(Class annotaionClass),通过注解类型获取到相应的注解
-Annotation[] getDeclaredAnnotations(),获取对象上所有显示注解,但是无法获取继承到的注解
-Annotation getDeclaredAnnotation(Class annotationClass),通过注解类型获取对象上相应的注解,但是无法获取继承到的注解
只有注解的 @Retension标注为runtime时,@Retension是一个元注解,用来修饰注解的注解才可以通过反射获得相应的注解
@Retension有三种保存策略
-Source,只在源文件.java里保存,即该注解只能保留在源文件里,编译时会忽略这些注解,例如@override
-Class,保存在字节码文件.class里,但是运行时不会对该注解进行解析
-Runtime,一直保存到运行时,在运行的时候可以获取到注解的所有信息
通过反射调用方法
-当通过反射获得某个Method对象之后,Method对象可以通过调用invoke(Object obj,Object ...args),参数一代表的是调用该方法的对象,参数二是方法的参数列表值
当然如果调用的静态方法,参数一传null即可,当然不传null也可以
示例:
// 获得class对象
Class clazz = Class.forName("com.bean.SmallPineapple");
// 获得对象对象里的构造器对象
Constructor constructor = clazz.getConstructor(String.class, int.class);
// 表示所有构造器都可以访问,包括私有的
constructor.setAccessible(true);
// 通过构造器创建对象
SmallPineapple sp = (SmallPineapple) constructor.newInstance("???", 21);
// 获取Method对象
Method method = clazz.getMethod("getInfo");
// 通过Method对象调用方法,方法名为getInfo,调用对象为sp,方法参数为空
if (method != null) {
method.invoke(sp, null);
}
反射的常见应用场景
-工厂模式,spring里通过反射在工厂模式里实例化对象;
-工厂模式,直接通过反射在工厂模式里来创建对象,通过传入全限定名
-加载JDBC数据库驱动类,配置文件里的driver-class-name: com.mysql.cj.jdbc.Driver
反射创建对象的步骤:获取class对象-获取构造器对象-通过构造器对象调用newInstance()方法
反射的优点
我们可以通过一个全限定名就可以灵活地实例化不同对象,配置文件也好代码里也好。在配置文件里特别明显,通过书写不同的数据源全限定名反射可以使用不同的数据源
缺点:
-破坏程序的封装性,我们可以拿到所有属性、方法、构造器,然后 setAccessable(true)可以无视访问修饰符的限制,直接到私有的数据
-性能损耗,通过反射编译器无法进行检查可访问行,需要在程序运行中才能检查,不过只有在同时调用100w次才能体现出来
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值