黑马程序员——java拾遗之反射机制

来源:互联网 发布:赵丽颖替身 知乎 编辑:程序博客网 时间:2024/04/28 14:31

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”,Java不是动态语言。但是JAVA有着一个非常突出的动态相关机制:反射机制。指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。


要了解java的反射机制,需要先了解java的Class类。这个类是java反射机制的基础。Class 类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时的类和接口,也用来表达boolean, byte, char, short, int, long, float, double以及关键词void。Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。 


通俗的讲,Class类是对java类的抽象,而Class 类的实例表示正在运行的 Java 应用程序中的类和接口。java反射的本质,就是用java一切都是对象的思想来描述自身。反射机制把java类以及java类中的各种成分用对应的java类进行了抽象,或者说映射。举个例子,java类中的成员变量,反射机制用Field类进行描述,类中定义的方法用Method类描述。构造方法用Constructor类来描述。最重要的是,java类自身用Class类来描述。


下面通过对反射机制常用到的几个类进行描述,来对java的反射机制做一下归纳——

一.Class类

(对java类的抽象)

1.获取Class的实例对象

Class类没有提供可用的构造方法。要获取Class类的实例有以下三种方法:

<span style="font-size:14px;">String str1 = "123";Class calzz1 = str1.getClass();Class calzz2 = String.class;Class calzz3 = Class.forName("java.lang.String");</span>

第一种是对一个类的实例对象调用getClass()方法;第二种是直接用类名加".class"来获取一个类的对应的Class对象;第三种是通过Class.forName("XXX")的方式来获取Class类的实例,forName方法中需要传入需要获取Class实例的目标类的完整路径,比如java.lang.String,不能只传一个String了事。

注意:Class类的实例对象表示的是一个类被jvm加载到内存中的字节码。不同类的字节码是不同的。但是对同一个类而言,尽管可能存在很多不同的实例对象,但是它们的类被加载的字节码是相同的,下面用代码来验证一下:

<span style="font-size:14px;">String str1 = "123";String str2 = "456";System.out.println(str1.getClass() == str2.getClass());</span>

这段代码的打印结果是:
true

同理,同一个类用三种不同的获取Class实例的方法获取到的Class实例也是相同的,验证代码如下:

<span style="font-size:14px;">System.out.println(String.class == "123".getClass());System.out.println(String.class == Class.forName("java.lang.String"));System.out.println("123".getClass() == Class.forName("java.lang.String"));</span>

打印结果如下:
true
true
true

2.基本数据类型的Class实例

void.class
int.class
byte.class
long.class
char.class
short.class
float.class
double.class
boolean.

以上的8种基本数据类型以及void都是存在对应的Class实例的,在内存中它们都有对应字节码。

3.Class类的常用方法

forName(String className)  返回与带有给定字符串名的类或接口相关联的 Class 对象。
getName() 以 String 的形式返回此 Class 对象所表示的实体名称。
getConstructor(Class<?>... parameterTypes)  返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
getConstructors()  返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
getMethod(String name, Class<?>... parameterTypes) 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
getMethods() 返回一个包含某些 Method 对象的数组(公共)
getField(String name) 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
getFields() 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
isArray()  判定此 Class 对象是否表示一个数组类
isInterface() 判定指定的 Class 对象是否表示一个接口类型
isPrimitive()  判定指定的 Class 对象是否表示一个基本类型。
newInstance()  创建此 Class 对象所表示的类的一个新实例。

与 Constructor 、 Field 和 Method 类相关的方法放在后面介绍这三个类的时候再详细介绍。简单演示下Class类的其他常用方法:

<span style="font-size:14px;">Class clazz1 = String.class;Class clazz2 = int.class;System.out.println(clazz1.getName());//java.lang.StringSystem.out.println(clazz1.isArray());//falseSystem.out.println(clazz1.isInterface());//falseSystem.out.println(clazz1.isPrimitive());//falseSystem.out.println(clazz2.isPrimitive());//trueclazz1.newInstance();clazz2.newInstance();</span>

上面的代码执行到clazz2.newInstance();这一行的时候报出了InstantiationException这个异,因为Class类的newInstance方法等同于用空参数的构造方法实例化对象,如果此Class对象所表示的类不存在空参数的构造方法,就会抛出这个异常。

二.Constructor 类

(用来描述类的构造方法)

1.获取Constructor类的实例对象

Constructor类没有构造方法,获取Constructor的实例对象必须通过一个Class类实例对象。常用的方法用Class类的getConstructor方法和getConstructors() ,前者是根据参数的不同返回特定的构造方法,后者是以Constructor数组的形式返回该Class实例对象对应类的所有构造方法。
<span style="font-size:14px;">Class clazz1 = String.class;Constructor constructor = clazz1.getConstructor(StringBuilder.class);</span>


这两行代码演示的是获取 String 类参数是一个 StringBuilder 的构造方法。

2.通过Constructor类对象进行实例化

请看下面的代码

<span style="font-size:14px;">Class clazz1 = String.class;Constructor constructor = clazz1.getConstructor(StringBuilder.class);String str = (String)constructor.newInstance(new StringBuilder("123"));System.out.println(str);System.out.println(str.charAt(1));String str1 = (String)constructor.newInstance(new StringBuffer("123")); </span>
执行的结果是
123
2
Exception in thread "main" java.lang.IllegalArgumentException: argument type mismatch

上面的代码先获取了一个参数是StringBuilder的构造方法对象,之后用这个构造方法实例化出一个字符串123,最后一句报错,是因为实例化的时候传入的参数类型和获取构造方法时指定的类型不一致。

三.Field 类

(用来描述类或者接口中的字段)

1.获取Field类的实例,Field类没有构造方法,要获取它的实例也要通过Class类。

先自定义一个类
<span style="font-size:14px;">class Person{    private int age;    public String name;    public Person(int age, String name)    {        this.age = age;        this.name = name;    }}</span>
获取Class类对应Person类的实例对象,并通过这个对象来获取Person类中的字段抽象出的Field类对象。
<span style="font-size:14px;">Person p1 = new Person(10, "zhangsan");Person p2 = new Person(20, "lisi");Class clazz1 = Person.class;Field fieldName = clazz1.getField("name");System.out.println(fieldName.get(p1));System.out.println(fieldName.get(p2));</span>
执行结果:
zhangsan

lisi

上面这段代码做了两件事,第一件是获取Person类字节码里面对应name字段的Field类对象;第二件是用Field对象的get方法分别获取对象p1和p2中name字段的值。
同样的代码,在访问Person类的私有字段时会出现问题,比如下面的代码:
<span style="font-size:14px;">Class clazz1 = Person.class;Field fieldAge = clazz1.getField("age");</span>
在执行的时候会报错:Exception in thread "main" java.lang.NoSuchFieldException: age

原因是age是Person类的私有字段,getField只能获取类的公共字段。必须用getDeclaredField方法才能获取所有已经声明的字段。如下面的代码:

<span style="font-size:14px;">Person p1 = new Person(10, "zhangsan");Person p2 = new Person(20, "lisi");Class clazz1 = Person.class;Field fieldAge = clazz1.getDeclaredField("age");System.out.println(fieldAge.get(p1));System.out.println(fieldAge.get(p2));</span>
在这段代码中,fieldAge可以被获取到,但是对fieldAge进行操作时仍然会报错:Exception in thread "main" java.lang.IllegalAccessException:……
原因是getDeclaredField虽然能够获取一个类的私有字段,但是却不能让私有字段变得可以访问。想要让私有字段变得可以访问,还需要用另一个方法setAccessible来让私有字段变得可以访问,最后修改完的代码如下:
<span style="font-size:14px;">Person p1 = new Person(10, "zhangsan");Person p2 = new Person(20, "lisi");Class clazz1 = Person.class;Field fieldAge = clazz1.getDeclaredField("age");fieldAge.setAccessible(true);Field fieldName = clazz1.getField("name");System.out.println(fieldAge.get(p1));System.out.println(fieldName.get(p1));System.out.println(fieldAge.get(p2));System.out.println(fieldName.get(p2));</span>

执行结果如下:
10
zhangsan
20
lisi

上面这段代码访问了Person类中的全部两个字段,并且无视字段是私有还是公有。

四.Method 类

(用来描述一个类或接口上单独某个方法)

1.与前面介绍的几个对象一致,Method也需要通过Class类的实例来获取,才能实例化。

实例化示例:

<span style="font-size:14px;">String str1 = "abc";Method methodCharAt = String.class.getMethod("charAt", int.class);</span>
上面的代码表示了利用反射机制,将String类的charAt方法抽象成了Method类的一个对象methodCharAt。抽象出来之后我们来看怎么使用这个对象。

2.Method 类对象的调用

<span style="font-size:14px;">String str1 = "abc";Method methodCharAt = String.class.getMethod("charAt", int.class);System.out.println(methodCharAt.invoke(str1, 1));</span>

执行结果:
b
这段代码等同于System.out.println("abc".charAt(1));
对于多个参数的方法调用可以用如下代码中的方式:
<span style="font-size:14px;">String str1 = "abc";Method methodReplace = String.class.getMethod("replace", char.class, char.class);System.out.println(methodReplace.invoke(str1, new Object[]{'a' ,'b'}));</span>
执行结果
bbc

最后,Constructor类,Field类,Method类三个类没有构造方法,无法自己实例化,其实很好理解,因为在java语言里,如果脱离类来谈构造方法、成员方法、成员变量都是没有意义的。构造方法、成员方法、成员变量都是声明在类中的,在反射中,这三者也是依托Class的类存在的。
0 0
原创粉丝点击