• 13. Java基础之类型信息(RTTI和反射)


    一. Java反射机制介绍

    Java 反射机制。通俗来讲呢,就是在运行状态中,我们可以根据“类的部分已经的信息”来还原“类的全部的信息”这里“类的部分已经的信息”,可以是“类名”或“类的对象”等信息。“类的全部信息”就是指“类的属性,方法,继承关系和Annotation注解”等内容。

    举个简单的例子:假设对于类ReflectionTest.java,我们知道的唯一信息是它的类名是“com.skywang.ReflectionTest”。这时,我们想要知道ReflectionTest.java的其它信息(比如它的构造函数,它的成员变量等等),要怎么办呢?
    这就需要用到“反射”。通过反射,我们可以解析出ReflectionTest.java的完整信息,包括它的构造函数,成员变量,继承关系等等。

    在了解了“java 反射机制”的概念之后,接下来思考一个问题:如何根据类的类名,来获取类的完整信息呢?

    这个过程主要分为两步:
    第1步:根据“类名”来获取对应类的Class对象。
    第2步:通过Class对象的函数接口,来读取“类的构造函数,成员变量”等信息。
    下面,我们根据示例来加深对这个概念的理解。示例如下(Demo1.java): 

     1 package com.test.b;
     2 
     3 public class Person {
     4     public Person() {
     5         System.out.println("no param constru");
     6     }
     7     
     8     public Person(String name) {
     9         System.out.println("have param constru");
    10     }
    11 
    12 }
    View Code
    1 public class Demo1 {
    2     public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    3         Class<?> class1=Class.forName("com.test.b.Person");
    4         Object object=class1.newInstance();
    5         System.out.println("cls="+class1);
    6         System.out.println(object);
    7     }
    8 
    9 }
    View Code
    1 no param constru
    2 cls=class com.test.b.Person
    3 com.test.b.Person@33909752
    View Code

    说明
    (01) Person类的完整包名是"com.test.b.Person"。而 Class.forName("com.test.b.Person"); 这一句的作用是,就是根据Person的包名来获取Person的Class对象。
    (02) 接着,我们调用Class对象的newInstance()方法,创建Person对象。


    现在,我们知道了“java反射机制”的概念以及它的原理。有了这个总体思想之后,接下来,我们可以开始对反射进行深入研究了。

     二. Class详细说明

            RTTI(Run-Time Type Infomation),运行时类型信息。可以在运行时识别一个对象的类型。类型信息在运行时通过Class对象表示,Class对象包含与类有关的信息,可以使用Class对象来创建类的实例。RTTI和Class对象有莫大的关系。

      每个类对应一个Class对象,这个Class对象放在.class文件中,当我们的程序中首次主动使用某类型时,会把该类型所对应的Class对象加载进内存。

         我们如何获取到Class对象呢?有三种方法

           1. Class.forName("全限定名");(其中,全限定名为包名+类名)。

           2. 类字面常量,如String.class,对应String类的Class对象。

           3.通过getClass()方法获取Class对象,如String str = "hello";str.getClass();。

      通过一个类对应的Class对象后,我们可以做什么?我们可以获取该类的父类、接口、创建该类的对象、该类的构造器、字段、方法等等。

      下面我们通过例子来熟悉Class对象的各种用法。

     1. 三种获取对象Class对象的方法

    note1:在面向对象的世界里,万事万物都是对象。类是对象,类是java.lang.Class类的实例对象。

    note2:查看Class类的源码,发现构造函数是private的,只有虚拟机才可以创建class对象。

    note3:任何一个类都是Class的实例对象,这个实例对象有3中标识方式:(1)(2)(3)所示。

    (1)类名.class-------不会引起类初始化

    这实际告诉我们任何一个类都有一个隐含的静态成员变量class

     1 public class Person {
     2     public int a=1;
     3     public static int b=2;
     4     public final static int c=3;
     5     static {
     6         System.out.println("hello");
     7     }
     8 
     9 }
    10 
    11 
    12 public class Test2 {
    13     public static void main(String args[]) {
    14          Class<?> class1=Person.class;    //未引起初始化
    15     }
    16 
    17 }
    18 
    19 
    20 未输出任何东西,说明.class没有起到初始化作用
    View Code

    (2)对象.getClass()------会引起类初始化

     1 public class Test2 {
     2     public static void main(String args[]) {
     3         Person person=new Person();
     4         Class<?>class1=person.getClass();
     5     }
     6 
     7 }
     8 
     9 
    10 
    11 输出:
    12 hello
    View Code

    (3)Class.forName(“全限定名”)--------会引起类初始化

     1 public class Test2 {
     2     public static void main(String args[]) throws ClassNotFoundException {
     3         Class<?>class1=Class.forName("com.test.a.Person");
     4     }
     5 
     6 }
     7 
     8 
     9 输出:
    10 hello
    View Code

    (4)一个类只可能是Class类的一个实例对象

    因此上述三种方法都会得到同样的一个Class类的实例对象。

     1 package com.test.a;
     2 
     3 public class Test {
     4     public static void main(String args[]) throws ClassNotFoundException {
     5 
     6         Class class1 = Test.class;
     7         Test test = new Test();
     8         Class class2 = test.getClass();
     9         Class class3 = Class.forName("com.test.a.Test");
    10         System.out.println(class1 == class2);
    11         System.out.println(class1 == class3);
    12     }
    13 }
    14 true
    15 true
    View Code

    (5)可以通过类的类类型创建该类的对象实例

      Foo foo=(Foo)c1.newInstance();

     2. Class类本身定义的方法使用

     1 package com.test.a;
     2 
     3 public class Person {
     4     public String name;
     5     public int age;
     6 
     7     public String getName() {
     8         return name;
     9     }
    10 
    11     private void setName(String name) {
    12         this.name = name;
    13     }
    14 
    15     public int getAge() {
    16         return age;
    17     }
    18 
    19     private void setAge(int age) {
    20         this.age = age;
    21     }
    22 }
    23 package com.test.a;
    24 
    25 public class Woman extends Person{
    26     public Double salary;
    27     private String sex;
    28     public Woman(String sex) {
    29         this.sex=sex;
    30     }
    31     private Woman() {
    32         
    33     }
    34     public void print()
    35     {
    36         System.out.println("hello");
    37     }
    38     
    39     private void print2() {
    40         System.out.println("hello2");
    41     }
    42 
    43 }
    View Code

    (1)获取包名

     1 package com.test.a;
     2 
     3 public class Test {
     4     public static void main(String args[]) {
     5         Class class1 = Test.class;
     6         System.out.println(class1.getName());
     7         System.out.println(class1.getSimpleName());
     8         Class c1 = double.class;
     9         Class c2 = String.class;
    10         Class c3 = Void.class;
    11 
    12         System.out.println(c1.getName());
    13         System.out.println(c1.getSimpleName());
    14         System.out.println(c2.getName());
    15         System.out.println(c2.getSimpleName());//不包含包名
    16         System.out.println(c3.getName());
    17         System.out.println(c3.getSimpleName());
    18     }
    19 }
    20 
    21 
    22 com.test.a.Test
    23 Test
    24 double
    25 double
    26 java.lang.String
    27 String
    28 java.lang.Void
    29 Void
    View Code

    (2)获取方法

     1 package com.test.a;
     2 
     3 import java.lang.reflect.Method;
     4 
     5 public class Test {
     6     public static void main(String args[]) {
     7         Woman woman=new Woman();
     8         Class class1=woman.getClass();//传递的是哪个子类的对象,class1就是该子类的类类型
     9         //一个成员方法就是一个Method对象
    10         Method[] publicMethods=class1.getMethods();//得到所有的public方法,包含从父类继承而来的Public方法
    11         for(Method i:publicMethods) {
    12             System.out.println(i);
    13         }
    14         
    15         System.out.println("***************");
    16         Method[] declaredMethod=class1.getDeclaredMethods();//只打印该类自己定义的方法(没有权限限制)
    17         for(Method i:declaredMethod) {
    18             System.out.println(i);
    19         }
    20     }
    21 }
    22 
    23 /**
    24 public void com.test.a.Woman.print()
    25 public java.lang.String com.test.a.Person.getName()
    26 public int com.test.a.Person.getAge()
    27 public final void java.lang.Object.wait() throws java.lang.InterruptedException
    28 public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    29 public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    30 public boolean java.lang.Object.equals(java.lang.Object)
    31 public java.lang.String java.lang.Object.toString()
    32 public native int java.lang.Object.hashCode()
    33 public final native java.lang.Class java.lang.Object.getClass()
    34 public final native void java.lang.Object.notify()
    35 public final native void java.lang.Object.notifyAll()
    36 ***************
    37 public void com.test.a.Woman.print()
    38 private void com.test.a.Woman.print2()
    39 
    40 */
    View Code

    (3)获取成员变量

     1 package com.test.a;
     2 
     3 import java.lang.reflect.Field;
     4 import java.lang.reflect.Method;
     5 
     6 public class Test {
     7     public static void main(String args[]) {
     8         Woman woman=new Woman();
     9         Class class1=woman.getClass();//传递的是哪个子类的对象,class1就是该子类的类类型
    10         Field fields[]=class1.getFields();
    11         for(Field i:fields) {
    12             System.out.println(i);
    13         }
    14         
    15         System.out.println("**************");
    16         Field decFileds[]=class1.getDeclaredFields();
    17         for(Field i:decFileds) {
    18             System.out.println(i);
    19         }
    20     }
    21 }
    22 
    23 /**
    24 public java.lang.Double com.test.a.Woman.salary
    25 public java.lang.String com.test.a.Person.name
    26 public int com.test.a.Person.age
    27 **************
    28 public java.lang.Double com.test.a.Woman.salary
    29 private java.lang.String com.test.a.Woman.sex
    30 
    31 
    32 */
    View Code

    (4)获取对象的构造函数信息

     1 package com.test.a;
     2 
     3 import java.lang.reflect.Constructor;
     4 import java.lang.reflect.Field;
     5 import java.lang.reflect.Method;
     6 
     7 public class Test {
     8     public static void main(String args[]) {
     9         Woman woman=new Woman("femal");
    10         Class class1=woman.getClass();//传递的是哪个子类的对象,class1就是该子类的类类型
    11         
    12         Constructor<Woman> constructors[]=class1.getConstructors();
    13         for(Constructor<Woman> i:constructors) {
    14             System.out.println(i);
    15         }
    16         System.out.println("********");
    17         
    18         Constructor<Woman> constructors2[]=class1.getDeclaredConstructors();
    19         for(Constructor<Woman> i:constructors2) {
    20             System.out.println(i);
    21         }
    22     }
    23 }
    24 
    25 /**
    26  public com.test.a.Woman(java.lang.String)
    27 ********
    28 public com.test.a.Woman(java.lang.String)
    29 private com.test.a.Woman()
    30 
    31  */
    View Code

    3. 静态编译 vs 动态编译

    Java中编译类型有两种:

    • 静态编译:在编译时确定类型,绑定对象即通过。
    • 动态编译:运行时确定类型,绑定对象。动态编译最大限度地发挥了Java的灵活性,体现了多态的应用,可以减低类之间的耦合性。

    在静态语言中,使用一个变量时,必须知道它的类型。在Java中,变量的类型信息在编译时都保存到了class文件中,这样在运行时才能保证准确无误;换句话说,程序在运行时的行为都是固定的。如果想在运行时改变,就需要反射这东西了。

    三. 深入理解反射

    1. 为什么会有反射

    假如你在程序运行过程中,从磁盘上或者从网络上读取接收了一串代表一个类的字节,既然这个类在你的程序被编译很久之后才出现,那么你怎样使用这样的类呢?

     解决:Class类和java.lang.reflect类库一起对反射的概念进行了支持。

    如果在编译时编译器不知道某个特定类的信息,本质是编译时无法获得并打开特定类的.class文件,而是在程序运行起来时jvm才拥有该特定类的.class文件。那么,如何使用这样的文件呢?于是“反射”这个概念应运而生---提供在运行时操作.class文件的统一API

    1 class TestClass {}
    2     TestClass testClass = new TestClass();
    3     Class c = Class.forName("TestClass");  TestClass testClass = c.newInstance(); //运行期间检查
    4     Class c = TestClass.class;  TestClass testClass = c.newInstance();   //编译期间检查
    View Code

           与传统RTTI必须在编译器就知道所有类型不同,反射不必在编译期就知道所有的类型,它可以在运行过程中使用动态加载的类,而这个类不必在编译期就已经知道。反射主要由java.lang.reflect类库的Field、Method、Constructor类支持。这些类的对象都是JVM在运行时进行创建,用来表示未知的类。

      关于两者的区别更深刻表达如下:对于RTTI而言,编译器在编译时打开和检查.class文件;对于反射而言,.class文件在编译时是不可获取的,所以在运行时打开和检查.class文件。

      其实在的第一个例子中我们已经用到了Constructor、Method类,现在我们来更加具体的了解Constructor、Method、Field类。  

     1 package com.test.a;
     2 
     3 import java.lang.reflect.Constructor;
     4 import java.lang.reflect.Field;
     5 import java.lang.reflect.InvocationTargetException;
     6 import java.lang.reflect.Method;
     7 
     8 import javax.activation.FileDataSource;
     9 
    10 public class Test2 {
    11     public static void main(String args[]) throws NoSuchMethodException, SecurityException, InstantiationException,
    12             IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
    13         Class<?> class1 = Person.class;
    14         Constructor<?> constructor = class1.getConstructor(int.class, String.class);// 区别getConstructors,这里不是Integer.class
    15         Person person1 = (Person) constructor.newInstance(23, "Tom");
    16         System.out.println(person1);
    17 
    18 //        Method method=class1.getMethod("f");//运行异常,因为该方法不适用于private方法
    19         Method method2 = class1.getMethod("g");
    20         method2.invoke(person1);
    21         Method method3 = class1.getDeclaredMethod("f");
    22         method3.setAccessible(true);
    23         method3.invoke(person1);// 必须要setAccessible成true,才可以访问private方法
    24 
    25         Field field = class1.getDeclaredField("age");
    26         System.out.println(person1);
    27         field.setAccessible(true);
    28         field.set(person1, 34);
    29         System.out.println(person1);
    30     }
    31 
    32 }
    View Code
    1 The name is: Tomthe age is 23
    2 I am public function
    3 I am private function
    4 The name is: Tomthe age is 23
    5 The name is: Tomthe age is 34
    View Code

    note:

    反射使用总结:通过运行时期加载class文件。首先由class文件生成对应的构造器,再利用构造器生成具体的实例对象。
    然后通过class对象生成Method对象,利用Mehod对象的invoke来调用方法;最后用class对象生成Field对象,利用filed对象
    来完成变量的赋值。-------总之就是用class对象来生成一个类的所有对象(反射),完成类的实例化,从而访问该类的对象的成员和方法

    说明:反射可以让我们创建一个类的实例、在类外部访问类的私有方法、私有字段。

    2.方法的反射

    (1)如何获取某个方法

      方法的名称和方法的参数列表才能唯一决定某个方法

    (2)方法反射的操作

      method.invoke(对象,参数列表)

    note:要获取一个方法,就是要获取类的信息,要获取一个类的信息,就是获取类类型。

     1 package com.test.a;
     2 
     3 import java.lang.reflect.InvocationTargetException;
     4 import java.lang.reflect.Method;
     5 
     6 public class Test {
     7     public static void main(String args[]) throws NoSuchMethodException, SecurityException, IllegalAccessException,
     8             IllegalArgumentException, InvocationTargetException {
     9         // 1.获取类的信息
    10         R a1 = new R();
    11         Class class1 = a1.getClass();
    12         // 2.获取方法:名称和参数列表来决定
    13         Method method = class1.getMethod("print", new Class[] { int.class, int.class });
    14         // 也可以写成Method method2=class1.getMethod("print",
    15         // int.class,int.class);因为...代表可变参数,可以携程数组形式,也可以全部写出来
    16 
    17         // 3.方法的反射操作
    18         // note1:方法的反射操作是用method对象来进行方法调用,和a1.print调用的效果相同。(正常情况下是对象操作方法,反射反过来,通过print对象操作a1)
    19         // note2:如果方法没有返回值,返回null,有返回值返回具体的返回值
    20         Object object = method.invoke(a1, new Object[] { 10, 20 });
    21 
    22     }
    23 }
    24 
    25 package com.test.a;
    26 
    27 public class R {
    28     public void print(int a, int b) {
    29         System.out.println(a + b);
    30     }
    31 
    32     public void print(String a, String b) {
    33         System.out.println(a.toUpperCase() + "," + b.toLowerCase());
    34     }
    35 }
    View Code

     30

    3.Java通过反射了解集合泛型的本质

     1 package com.test.a;
     2 
     3 import java.lang.reflect.InvocationTargetException;
     4 import java.lang.reflect.Method;
     5 import java.util.ArrayList;
     6 
     7 public class Test {
     8     public static void main(String args[]) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
     9         ArrayList list1=new ArrayList();
    10         ArrayList<String> list2=new ArrayList<>();
    11         list2.add("a");//only string,can't list.add(20);
    12         Class class1=list1.getClass();
    13         Class class2=list2.getClass();
    14         System.out.println(class1==class2);
    15         /**
    16          * class1==class2的结果为true说明编译后的集合的泛型是去泛型化的。
    17          * Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了。
    18          * 验证:我们可以通过方法的反射来操作,绕过编译
    19          */
    20         Method method=class2.getMethod("add", Object.class);
    21         method.invoke(list2, 20);//绕过编译操作就绕过了泛型
    22         System.out.println(list2.size());
    23         System.out.println(list2);
    24         
    25     }
    26 }
    27 
    28 true
    29 2
    30 [a, 20]
    View Code

    4.反射和传统RTTI区别

    传统的RTTI与反射最主要的区别,在于RTTI在编译期需要.class文件,而反射不需要。传统的RTTI使用转型或Instance形式实现,但都需要指定要转型的类型,比如:

    public void rtti(Object obj){
        A a= A(obj);
        // A a= Class.forName("com.rtti.A")
        // obj instanceof A
    }

    注意其中的obj虽然是被转型了,但在编译期,就需要知道要转成的类型A,也就是需要A的.class文件。

    相对的,反射完全在运行时在通过Class类来确定类型,不需要提前加载A的.class文件。

    相同:

    它俩的目的一样:

      在运行时,识别对象和类的信息。

    相同点:

      目的相同;

      功能都是通过Class类来实现的

    不同点:

    反射与RTTI的本质区别只是检查一个类的.class文件的时机不同:

    反射:.class 文件是在编译时不可获得的,所以在运行时打开和检查未知类的.class文件从而变已知。

    RTTI:  .class 文件是在编译时打开和检查。

    5.反射的缺点

    反射机制给予Java开发很大的灵活性,但反射机制本身也有缺点,代表性的缺陷就是反射的性能,一般来说,通过反射调用方法的效率比直接调用的效率要至少慢一倍以上。

    6.为什么要引入反射以及在实际开发中反射的作用

    参考:https://www.cnblogs.com/Eason-S/p/5851078.html

    Spring中的IoC的实现原理就是工厂模式加反射机制。

    (1).我们首先看一下不用反射机制时的工厂模式:

     1 /**
     2  * 工厂模式
     3  */
     4 interface fruit{
     5     public abstract void eat();
     6 }
     7 
     8 class Apple implements fruit{
     9     public void eat(){
    10         System.out.println("Apple");
    11     }
    12 }
    13 
    14 class Orange implements fruit{
    15     public void eat(){
    16         System.out.println("Orange");
    17     }
    18 }
    19 // 构造工厂类
    20 // 也就是说以后如果我们在添加其他的实例的时候只需要修改工厂类就行了
    21 class Factory{
    22     public static fruit getInstance(String fruitName){
    23         fruit f=null;
    24         if("Apple".equals(fruitName)){
    25             f=new Apple();
    26         }
    27         if("Orange".equals(fruitName)){
    28             f=new Orange();
    29         }
    30         return f;
    31     }
    32 }
    33 
    34 class hello{
    35     public static void main(String[] a){
    36         fruit f=Factory.getInstance("Orange");
    37         f.eat();
    38     }
    39 }
    View Code

    当我们在添加一个子类的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改的就会很多。

    (2). 利用反射机制的工厂模式:

     1 package Reflect;
     2 
     3 interface fruit{
     4     public abstract void eat();
     5 }
     6 
     7 class Apple implements fruit{
     8     public void eat(){
     9         System.out.println("Apple");
    10     }
    11 }
    12 
    13 class Orange implements fruit{
    14     public void eat(){
    15         System.out.println("Orange");
    16     }
    17 }
    18 
    19 class Factory{
    20     public static fruit getInstance(String ClassName){
    21         fruit f=null;
    22         try{
    23             f=(fruit)Class.forName(ClassName).newInstance();
    24         }catch (Exception e) {
    25             e.printStackTrace();
    26         }
    27         return f;
    28     }
    29 }
    30 
    31 class hello{
    32     public static void main(String[] a){
    33         fruit f=Factory.getInstance("Reflect.Apple");
    34         if(f!=null){
    35             f.eat();
    36         }
    37     }
    38 }
    View Code

    现在就算我们添加任意多个子类的时候,工厂类就不需要修改。

      使用反射机制的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们通过属性文件的形式配置所需要的子类。

    (3).使用反射机制并结合属性文件的工厂模式(即IoC)

    首先创建一个fruit.properties的资源文件:

    1 apple=Reflect.Apple
    2 orange=Reflect.Orange

    然后编写主类代码:

     1 package Reflect;
     2 
     3 import java.io.*;
     4 import java.util.*;
     5 
     6 interface fruit{
     7     public abstract void eat();
     8 }
     9 
    10 class Apple implements fruit{
    11     public void eat(){
    12         System.out.println("Apple");
    13     }
    14 }
    15 
    16 class Orange implements fruit{
    17     public void eat(){
    18         System.out.println("Orange");
    19     }
    20 }
    21 //操作属性文件类
    22 class init{
    23     public static Properties getPro() throws FileNotFoundException, IOException{
    24         Properties pro=new Properties();
    25         File f=new File("fruit.properties");
    26         if(f.exists()){
    27             pro.load(new FileInputStream(f));
    28         }else{
    29             pro.setProperty("apple", "Reflect.Apple");
    30             pro.setProperty("orange", "Reflect.Orange");
    31             pro.store(new FileOutputStream(f), "FRUIT CLASS");
    32         }
    33         return pro;
    34     }
    35 }
    36 
    37 class Factory{
    38     public static fruit getInstance(String ClassName){
    39         fruit f=null;
    40         try{
    41             f=(fruit)Class.forName(ClassName).newInstance();
    42         }catch (Exception e) {
    43             e.printStackTrace();
    44         }
    45         return f;
    46     }
    47 }
    48 
    49 class hello{
    50     public static void main(String[] a) throws FileNotFoundException, IOException{
    51         Properties pro=init.getPro();
    52         fruit f=Factory.getInstance(pro.getProperty("apple"));
    53         if(f!=null){
    54             f.eat();
    55         }
    56     }
    57 }
    58 //【运行结果】:Apple
    View Code

    四.反射和多态的区别

    既然 RTTI是运行时类型检查,为什么还要严格和多态区别呢,用《thinking in java》的原话说就是“Java希望我们始终使用多态机制,只在必须的时候使用RTTI”。那么,RTTI与多态到底有什么区别呢?

     是因为:多态的实现是依靠rtti(运行时类型检查或者叫后期绑定)??

  • 相关阅读:
    AirtestIDE 游戏自动化(unity3d跨 Windows、Mac、IOS、Android)
    Python josn 实例说明
    CS0012 类型“DbContext”在未引用的程序集中定义。必须添加对程序集“EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089”的引用。 Repository E:项目wxRepositoryDbContextFac
    利用Senparc.Weixin SDK 实现微信用户的授权,并获取信息
    关于SVN浏览服务器的错误
    P1074 靶形数独
    P1941 飞扬的小鸟
    P3178 [HAOI2015]树上操作
    [校内模拟题3]
    P4231 三步必杀
  • 原文地址:https://www.cnblogs.com/Hermioner/p/9596335.html
Copyright © 2020-2023  润新知