Java开发:反射的意义价值和用法 | 通过反射获取私有属性和方法 |反射的作用 | 反射的优缺点 | 反射破坏了封装性为什么还要用

前言

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;
		this.name = name;
	}
	
	public Person(String id, String name, Double score) {
		super();
		this.id = id;
		this.name = 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) {
		this.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!

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值