Java语言学习高级笔记(八):反射

本笔记适合在了解Java基础后想要进一步进阶学习的同学,同时包含部分Java经典面试题供同学们深入理解。

Reflection(反射)被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

静态语言:与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

注:Java虽然不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。

反射相关的主要API:

  java.lang.Class:代表一个类。
  java.lang.reflect.Method:代表类的方法。
  java.lang.reflect.Field:代表类的成员变量。
  java.lang.reflect.Constructor:代表类的构造器。

问题1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底应该用哪个?
:用直接new的方式,反射的方式主要应用于它的动态性,使其在执行期间也可以操作想要调用的类。

问题2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
:不矛盾。封装性中设置成私有的结构指的是不建议外部进行调用或没有必要去调用此结构,说明的是“建不建议调用”的问题,反射是说明的是“能不能调用”的问题,我们还是要遵循封装性的原则来构造代码,并且反射的方式主要应用于它的动态性,使其在执行期间也可以操作想要调用的类。

关于java.lang.Class类的理解:

1.类的加载过程:程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

获取Class的实例的方式:

方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;

方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();

方式三:调用Class的静态方法:forName(String classPath)(常用)
Class clazz3 = Class.forName(“com.atguigu.java.Person”);

方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass(“com.atguigu.java.Person”);

总结:创建类的对象的几种方式:
1.new + 构造器。
2.要创建Xxx类的对象,可以考虑Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在,可以调用其静态方法,创建Xxx对象。
3.通过反射实现:clazz.newInstance();

Class实例可以是哪些结构的说明:

        Class c1 = Object.class;		//类
        Class c2 = Comparable.class;	//接口
        Class c3 = String[].class;		//数组
        Class c4 = int[][].class;		//二维数组
        Class c5 = ElementType.class;	//枚举类
        Class c6 = Override.class;	//注解
        Class c7 = int.class;		//基本数据类型
        Class c8 = void.class;		//viod
        Class c9 = Class.class;		//Class本身

注:只要数组的元素类型与维度一样,就是同一个Class。

类的加载过程:

类的加载(Load):将class文件读入内存,并为之创建一个java.lang.Class对象。

类的链接(Link):将类的二进制数据合并到JVM的运行状态之中。

类的初始化(Initialize):JVM负责对类进行初始化。

了解类的加载器:

类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存:标准的JavaSE类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收器可以回收这些Class对象。

引导类加载器:用C++编写,负责Java核心类库(如String类)。该加载器无法获取。(获取会默认为null。)

扩展类加载器:负责将指定目录下的jar包装入工作库。例:ClassLoader classLoader1 = classLoader.getParent();

系统类加载器:用于加载自己写的自定义的类,是最常用的加载器。例:ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();

Properties:用来读取配置文件

读取配置文件的方式一:(此时的文件默认在IDEA当前的module下。)(此时的文件默认在Eclipse当前的project下。)

    Properties pros =  new Properties();
    //FileInputStream fis = new FileInputStream("jdbc.properties");
    FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
    pros.load(fis);

读取配置文件的方式二:使用ClassLoader。(此时的文件默认在当前的src下。)

    Properties pros =  new Properties();
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
    pros.load(is);

创建运行时类的对象:

newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

    Class<Person\> clazz = Person.class;
    Person obj = clazz.newInstance();

要想此方法正常的创建运行时类的对象,要求
1.运行时类必须提供空参的构造器。
2.空参的构造器的访问权限得够。通常,设置为public。

拓展:在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象。
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器。

获取运行时类的属性结构:

  Field[] getFields():获取当前运行时类及其父类中声明为public访问权限的属性。
  Field[] getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)

获取属性的权限修饰符

  int modifier = f.getModifiers();      
  System.out.print(Modifier.toString(modifier));

获取属性的数据类型

  Class type = f.getType();      
  System.out.print(type.getName());

获取属性的变量名:

  String fName = f.getName(); 

获取运行时类的方法结构:

  Method[] getMethods():获取当前运行时类及其所有父类中声明为public权限的方法。
  Method[] getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)

获取方法的注解:(注:生命周期只有是RunTime的才可以反射。框架中常用。)

  Annotation[] annos = m.getAnnotations();
  for(Annotation a : annos){      System.out.println(a);      }

获取方法的权限修饰符

  System.out.print(Modifier.toString(m.getModifiers()));

获取方法的返回值类型

  System.out.print(m.getReturnType().getName());

获取方法的方法名

  System.out.print(m.getName());

获取方法的形参列表的类型

  Class[] parameterTypes = m.getParameterTypes();

获取方法的抛出的异常

  Class[] exceptionTypes = m.getExceptionTypes();

获取运行时类的构造器结构:

  Constructor[] getConstructors():获取当前运行时类中声明为public的构造器。
  Constructor[] getDeclaredConstructors():获取当前运行时类中声明的所有的构造器。

获取运行时类的父类

  Class getSuperclass();

获取运行时类的带泛型的父类

  Type getGenericSuperclass();//Type是一个接口,Class类实现了这个接口。

获取运行时类的带泛型父类的泛型:(例如:StudentDAO类中获取父类DAO的泛型来进行创建类承载从数据库中取出的Student数据。)

  Type genericSuperclass = clazz.getGenericSuperclass();
  ParameterizedType paramType = (ParameterizedType) genericSuperclass;      
  Type[] actualTypeArguments = paramType.getActualTypeArguments();
  //System.out.println(actualTypeArguments[0].getTypeName());
  System.out.println(((Class)actualTypeArguments[0]).getName());

获取运行时类的接口:(例如:动态代理中的代理类需要获取被代理类的接口来实现。)

  Class[] getInterfaces();

获取运行时类的父类实现的接口

  Class[] interfaces1 = clazz.getSuperclass().getInterfaces();

获取运行时类所在的包:

  Package getPackage();

获取运行时类声明的注解:(框架中常用。)

  Annotation[] annotations = clazz.getAnnotations();
  for(Annotation annos : annotations){      System.out.println(annos);      }

调用运行时类中指定的结构:属性、方法、构造器:

属性:

  Class clazz = Person.class;
  Person p = (Person) clazz.newInstance();
  Field name = clazz.getDeclaredField("name");//如果没有Declared只能访问public权限。
  name.setAccessible(true);//保证当前属性是可访问的。
  name.set(p,"Tom");
  System.out.println(name.get(p));

方法://例如:private String show(String nation)

  Class clazz = Person.class;
  Person p = (Person) clazz.newInstance();
  Method show = clazz.getDeclaredMethod("show", String.class);//参数1 :指明获取的方法的名称 , 参数2:指明获取的方法的形参列表的类型。
  show.setAccessible(true);//保证当前方法是可访问的。
  Object returnValue = show.invoke(p,"CHN");//参数1:方法的调用者 , 参数2:给方法形参赋值的实参。返回值即为对应类中调用的方法的返回值。
                                            //相当于String nation = p.show("CHN");

静态方法://例如:private static void showDesc()
Class clazz = Person.class;
Method showDesc = clazz.getDeclaredMethod(“showDesc”);
show.setAccessible(true);
Object returnVal = showDesc.invoke(Person.class);//用类.class或者null代替实例对象名。
System.out.println(returnVal);//原函数返回值为void时,invoke函数返回null。
构造器:
Class clazz = Person.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class);//参数:指明构造器的参数列表的类型。
constructor.setAccessible(true);
Person per = (Person) constructor.newInstance(“Tom”);

动态代理:见代码。

静态代理的缺点:
①代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。
②每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理。

动态代理的特点:
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘学长丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值