java反射笔记

一、引入

场景:多态通过父类引用指向子类对象确实可以提高代码的扩展性,但是扩展性没有达到最好。

解决办法:反射机制。

介绍:

  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

  • 在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。

  • 在执行程序(java.exe)时候,将字节码文件读入JVM中--->这个过程叫做类的加载。然后在内存中对应创建一个java.lang.Class对象-->这个对象会被放入字节码信息中,这个Class对象,就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。

  • 所以:我们可以通过这个对象看到类的结构,这个对象就好像是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射。这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

  • 说明:在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

Java反射机制,可以实现以下功能:

  1. 在运行时判断任何一个对象所属的类

  1. 在运行时构造任意一个类的对象

  1. 在运行时判断任意一个类所具有的的成员变量和方法

  1. 在运行时调用任意一个对象的方法

  1. 生成动态代理

二、Class类

介绍:

  • 所有类的对象都是Class的实例。

  • .java源文件在经过编译后会生成.class的字节码文件。在运行阶段,jvm会将需要的.class文件加载到jvm内存中,并为每个.class字节码文件创建一个Class类的对象。

  • Class类是专门用来描述.class字节码文件的,只能由jvm创建该类的对象。Class类的对象包含了.class字节码文件的所有信息,所以可以通过Class类的对象获取到.class字节码文件中包含的类的信息:类的成员(方法、属性、构造器等)信息、类的签名、方法签名等等。

  • Class对象将被作为程序访问方法区中的对应类的各种数据的外部接口。

  • 从面向对象思想来看,万事万物皆对象:

获取源头Class的四种方式:

//1.对象.getClass
String str = new String("abc");
Class<? extends String> strClass = str.getClass();
System.out.println(strClass);
System.out.println(strClass.getName());
System.out.println("------------------");
//2.类.Class
Class<String> strClass1 = String.class;
System.out.println(strClass);
System.out.println(strClass1.getName());
System.out.println("------------------");
System.out.println(strClass==strClass1);    //true
//注意:方式1和方式2  不常用
//3.用的最多:调用Class类提供的静态方法forName
Class<?> strClass2 =  Class.forName("java.lang.String");
System.out.println(strClass2);
System.out.println(strClass2.getName());
System.out.println(Arrays.toString(strClass2.getMethods()));
System.out.println(Arrays.toString(strClass2.getFields()));
String o = (String) strClass2.newInstance();
//方式4:利用类的加载器(了解技能点)
//一般自定义的类是通过系统类加载器进行加载的,这里得到的是系统类加载器
ClassLoader loader = Test.class.getClassLoader();    
Class c4 = loader.loadClass("com.test02.Person");

注:1、2不常用,因为一般得到字节码信息的目的是为了获取这个类,而1、2已经有了这个类了再去拿字节码信息获取类显得多此一举。

Class类的具体的实例:

public class TestClass {
    /*
        Class类的具体的实例:
        (1)类:外部类,内部类
        (2)接口
        (3)注解
        (4)数组
        (5)基本数据类型
        (6)void
         */
    public static void main(String[] args) {
        //验证:
        Class<BankCard> bankCardClass = BankCard.class; //1.类
        Class<Comparable> comparableClass = Comparable.class;   //2.接口
        Class<Override> overrideClass = Override.class;     //3.注解
        //4.数组
        int[] arr1 = {1,2,3};
        int[] arr2 = {1,2,3};
        Class<? extends int[]> aClass1 = arr1.getClass();
        Class<? extends int[]> aClass2 = arr2.getClass();
        //同一个维度,同一个元素类型,得到的字节码就是同一个
        System.out.println(aClass2 == aClass1); //true
        //5.基本数据类型
        Class<Integer> integerClass = int.class;
        //6.void
        Class<Void> voidClass = void.class;
        
    }
}

三、反射的使用

  1. 获取修饰符

//以属性对象为例:
Class<Apple> clz = Apple.class;
Apple a = new Apple("红富士", 2.6);
Field field = clz.getField("weight");
//getModifiers()返回一个整型需要Modifier.toString()转换
System.out.println(Modifier.toString(field.getModifiers()));    //public
  1. 获取构造器并实例化对象

//获取字节码信息
Class<Student> cls = Student.class;
//通过字节码信息可以获取构造器:
//getConstructors只能获取当前运行时的类被 public修饰的构造器
Constructor<?>[] c1 = cls.getConstructors();
for(Constructor c:c1){
    System.out.println(c);
}
System.out.println("-------------------");
//getDeclaredConstructors:获取运行时类的全部修饰符的构造器
Constructor[] c2 = cls.getDeclaredConstructors();
for(Constructor c:c2){
    System.out.println(c);
}
System.out.println("-------------------");
//获取指定的构造器:
//得到空构造器
Constructor con1 = cls.getConstructor();
System.out.println(con1);
//得到两个参数的有参构造器:并且是default修饰
Constructor con2 = cls.getDeclaredConstructor(int.class, double.class);
System.out.println(con2);
//得到一个参数的有参构造器:并且是private修饰的
Constructor con3 = cls.getDeclaredConstructor(int.class);
System.out.println(con3);
//有了构造器以后我就可以创建对象:
Object o1 = con1.newInstance();
System.out.println(o1);
Object o2 = con2.newInstance(1, 170.6);
System.out.println(o2);
  1. 获取属性

//获取运行时类的字节码信息:
Class cls = Student.class;
//获取属性:
//getFields:获取运行时类和父类中被public修饰的属性
Field[] fields = cls.getFields();
for (Field f: fields){
    System.out.println(f);
}
System.out.println("---------------------");
//getDeclaredFields:获取运行时类中的所有属性
Field[] declaredFields = cls.getDeclaredFields();
for(Field f:declaredFields){
    System.out.println(f);
}
System.out.println("---------------------");
//获取指定的属性:
Field score = cls.getField("score");
System.out.println(score);
Field sno = cls.getDeclaredField("sno");
System.out.println(sno);
System.out.println("---------------------");
//属性的具体结构:
//获取修饰符
System.out.println(Modifier.toString(sno.getModifiers()));  //private
//获取属性的数据类型:
System.out.println(sno.getType());  //int
//获取属性的名字:
System.out.println(sno.getName());  //sno
System.out.println("-------------------------------");
//给属性赋值:(给属性设置值,必须要有对象)
Field scoreField = cls.getField("score");
Object obj = cls.newInstance(); //调用空参构造器
scoreField.set(obj, 98);    //给obj这个对象的score属性设置具体的值,这个值为98
System.out.println(obj);
  1. 获取方法

//获取字节码信息:
Class cls = Student.class;
//获取方法:
//getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
Method[] methods = cls.getMethods();
for(Method m:methods){
    System.out.println(m);
}
System.out.println("-----------------------");
//getDeclaredMethods:获取运行时类中的所有方法:
Method[] declaredMethods = cls.getDeclaredMethods();
for(Method m:declaredMethods){
    System.out.println(m);
}
System.out.println("-----------------------");
//获取指定的方法:
Method showInfo1 = cls.getMethod("showInfo");
System.out.println(showInfo1);
Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);
System.out.println(showInfo2);
Method work = cls.getDeclaredMethod("work",int.class);
System.out.println(work);
System.out.println("-----------------------");
//获取方法的具体结构:
/*
@注解
修饰符 返回值类型  方法名(参数列表) throws XXXXX{}
 */
//名字:
System.out.println(work.getName());
//修饰符:
System.out.println(work.getModifiers());
//返回值:
System.out.println(work.getReturnType());
System.out.println("--------------------");
//参数列表:
Class<?>[] parameterTypes = work.getParameterTypes();
for (Class c:parameterTypes){
    System.out.println(c);
}
System.out.println("--------------------");
//获取注解:
Method myMethod = cls.getMethod("myMethod");
Annotation[] annotations = myMethod.getAnnotations();
for(Annotation a:annotations){
    System.out.println(a);
}
System.out.println("--------------------");
//获取异常:
Class<?>[] exceptionTypes = myMethod.getExceptionTypes();
for (Class<?> c:exceptionTypes){
    System.out.println(c);
}
//调用方法:
Object o = cls.newInstance();
myMethod.invoke(o);//调用o对象的mymethod方法
System.out.println(showInfo2.invoke(o,12,45));

四、总结

反射有什么用?

可以通过反射获取类中的方法、字段、属性、子类。

反射的优点和缺点?

优点:提高程序的灵活性与可扩展性,降低代码的耦合性。

缺点:

1. 过多使用,会造成逻辑模糊,因为反射已经绕过的源码,看不到逻辑实现,会造成后期维护上的困扰。

2. 性能的问题,主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。

面试题:

【1】问题1:创建Person的对象,以后用new Person()创建,还是用反射创建?

问题本质:什么时候使用反射?

答:在某种业务场景下,无法在编写源代码时就确定要用哪个类的对象,需要根据用户的行为做出动态地响应。这个时候就可以考虑用反射机制在运行阶段根据用户的输入来判断到底实列化哪个类的对象,并调用该对象的方法等操作。例如:在美团点外卖后付款的界面,用户可以选择多种付款方式(微信、支付宝、银行卡等等)。假如每种支付方式都对应一个类,而在编写源代码的时候我们不能确定使用那种付款方式,为了代码的可扩展性,也不想用分支结构并为每个支付方式的类创建对象。那么,这种情况下就可以考虑用反射机制,用户点击哪个支付方式,程序就在运行阶段创建哪个支付方式类的对象完成支付。

【2】问题2:反射是否破坏了面向对象的封装性?

什么是封装?

  答:程序的封装指的是利用各种权限修饰符(private、public、protected、default)来对类中的成员的访问权限加以限制,强制用户使用该类的时候必须遵守这个权限限制,只能按照类中设计的访问接口进行访问。简言之:提高代码安全性。

  举个现实中的例子:就像生活中的电脑一样,电脑是在工厂经过一系列组装,封装成了我们看到的样子。只给我们留下了有限的接口如:屏幕,键盘,usb接口等等,用户只能使用这些接口访问笔记本电脑里的数据,而不能直接从内存条或者硬盘上访问数据。这就是现实生活中的一种封装。

反射是否破坏了面向对象的封装性?

  答:反射并没有破坏面向对象的封装性。因为通过反射机制获取到的带权限修饰符的方法和属性都依然遵照权限修饰符限定的访问方式。

  除非调用setAccessible(true)方法来抑制java访问权限检查,从而达到可以随意访问的效果。但是利用setAccessible(true)是一种暴力方式,不安全。开发人员一般不会用这种方式损害自己代码的封装性。

  举个例子:就像现实生活中的电脑,是在工厂经过一系列组装,封装成了我们看到的样子。只给我们留下了有限的接口如:屏幕,键盘,usb接口等等,用户只能使用这些接口访问笔记本电脑里的数据,而不能直接从内存条或者硬盘上访问数据。但是,电脑维修的时候,也可以用暴力的方式拆开电脑的外壳,从而看到电脑的内部结构。

  同样的道理setAccessible(true)就是java反射中的一种最终的暴力手段,但是这并不破坏Java的封装性。就像电脑可以被暴力拆解,但是不影响电脑是一个被封装起来供用户使用的数码产品。

答案原文链接:https://blog.csdn.net/yangpeiheng/article/details/126147904

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值