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

Posted Hermioner

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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, Strin

以上是关于13. Java基础之类型信息(RTTI和反射)的主要内容,如果未能解决你的问题,请参考以下文章

Java基础RTTI与反射之Java

RTTI与反射之Java

Java基础 -- 深入理解Java类型信息(Class对象)与反射机制

类型信息(RTTI和反射)——反射

Java SE基础2:Class类与反射

Java基础RTTI与反射之Java