吃透Java的反射机制——基础知识总结

1、什么是Java的反射java

你们都知道,要让Java程序可以运行,那么就得让Java类要被Java虚拟机加载。Java类若是不被Java虚拟机加载,是不能正常运行的。如今咱们运行的全部的程序都是在编译期的时候就已经知道了你所须要的那个类的已经被加载了。mysql

Java的反射机制是在编译并不肯定是哪一个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特色就是反射。sql

2、反射机制的做用设计模式

在Java运行时环境中,对于任意一个类,能够知道这个类有哪些属性和方法。对于任意一个对象,能够调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。简单的来讲,反射机制指的是程序在运行时可以获取自身的信息。在java中,只要给定类的名字,那么就能够经过反射机制来得到类的全部信息。安全

3、哪里用到反射机制app

有些时候,咱们用过一些知识,可是并不知道它的专业术语是什么,在刚刚学jdbc时用过一行代码,Class.forName("com.mysql.jdbc.Driver.class").newInstance();可是那时候只知道那行代码是生成驱动对象实例,并不知道它的具体含义。听了反射机制这节课后,才知道,原来这就是反射,如今不少开框架都用到反射机制,hibernate、struts都是用反射机制实现的。框架

IDE中当咱们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象可以使用的全部的方法和属性所有都列出来,供用户进行选择。这就是利用了Java反射的原理,是对咱们建立对象的探知、自审。其余例如Spring框架:IOC(控制反转);Hibernate框架:关联映射等;白盒测试等ide

4、反射机制的优势与缺点函数

为何要用反射机制?直接建立对象不就能够了吗,这就涉及到了动态与静态的概念,工具

静态编译:在编译时肯定类型,绑定对象,即经过。

动态编译:运行时肯定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以下降类之间的藕合性。

优势

(1)可以运行时动态获取类的实例,大大提升系统的灵活性和扩展性。

(2)与Java动态编译相结合,能够实现无比强大的功能

缺点:

(1)使用反射的性能较低,使用反射基本上是一种解释操做,咱们能够告诉JVM,咱们但愿作什么而且它知足咱们的要求。这类操做老是慢于只直接执行相同的操做。

(2)使用反射相对来讲不安全

(3)破坏了类的封装性,能够经过反射获取这个类的私有方法和属性

因此何时使用反射,就要靠业务的需求、大小,以及经验的积累来决定。

5、反射机制的功能

首先Java 反射机制主要提供了如下功能

在运行时判断任意一个对象所属的类。 在运行时构造任意一个类的对象。 在运行时判断任意一个类所具备的成员变量和方法。 在运行时调用任意一个对象的方法

反射的经常使用类和函数:Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;

其中class表明的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象,

经过这四个对象咱们能够粗略的看到一个类的各个组成部分。其中最核心的就是java.lang.Class这个类。它是Java反射机制的起源、基础。当一个类被加载之后,Java虚拟机就会自动产生一个Class对象。经过这个Class对象咱们就能得到加载到虚拟机当中这个Class对象对应的方法、成员以及构造方法的声明和定义等信息。应用反射时咱们最关心的通常是一个类的构造器、属性和方法,下面咱们主要介绍Class类中针对这三个元素的方法:

一、获得构造器的方法 Constructor getConstructor(Class[] params) -- 得到使用特殊的参数类型的公共构造函数, Constructor[] getConstructors() -- 得到类的全部公共构造函数 Constructor getDeclaredConstructor(Class[] params) -- 得到使用特定参数类型的构造函数(与接入级别无关) Constructor[] getDeclaredConstructors() -- 得到类的全部构造函数(与接入级别无关)

二、得到字段信息的方法

Field getField(String name) -- 得到命名的公共字段 Field[] getFields() -- 得到类的全部公共字段 Field getDeclaredField(String name) -- 得到类声明的命名的字段 Field[] getDeclaredFields() -- 得到类声明的全部字段

三、得到方法信息的方法

Method getMethod(String name, Class[] params) -- 使用特定的参数类型,得到命名的公共方法 Method[] getMethods() -- 得到类的全部公共方法 Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,得到类声明的命名的方法 Method[] getDeclaredMethods() -- 得到类声明的全部方法

在程序开发中使用反射并结合属性文件,能够达到程序代码与配置文件相分离的目的,若是咱们想要获得对象的信息,通常须要“引入须要的‘包.类’的名称——经过new实例化——取得实例化对象”这样的过程。使用反射就能够变成“实例化对象——getClass()方法——获得完整的‘包.类’名称”这样的过程。正常方法是经过一个类建立对象,反射方法就是经过一个对象找到一个类的信息。

6、如何运用反射机制

一、使用反射机制的步骤:

导入java.lang.relfect 包

遵循三个步骤

第一步是得到你想操做的类的 java.lang.Class 对象

第二步是调用诸如 getDeclaredMethods 的方法

第三步使用 反射API 来操做这些信息

二、得到Class对象的方法

1)若是一个类的实例已经获得,你可使用

【Class c = 对象名.getClass(); 】

例: TextField t = new TextField();

Class c = t.getClass();

Class s = c.getSuperclass();

2)若是你在编译期知道类的名字,你可使用以下的方法

Class c = java.awt.Button.class; 或者Class c = Integer.TYPE;

3)若是类名在编译期不知道, 可是在运行期能够得到, 你可使用下面的方法

Class c = Class.forName(strg);

这样得到Class类对象的方法,实际上是利用反射API把指定字符串的类加载到内存中,因此也叫类加载器加载方法。此时类加载器会把该类的静态方法和静态属性,以及静态代码所有加载到内存中。但此时类的对象尚未产生。因此这就是为何静态方法不能访问非静态属性和方法。由于静态方法和属性产生的时机在非静态属性和方法以前。

7、直观地认识反射

         反射之中包含了一个“反”的概念,因此要想解释反射就必须先从“正”开始解释,通常而言,当用户使用一个类的时候,应该先知道这个类,然后经过这个类产生实例化对象,可是“反”指的是经过对象找到类。

package cn.mldn.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person() ; // 正着操做
System.out.println(per.getClass().getName()); // 反着来
}
}

以上的代码使用了一个getClass()方法,然后就能够获得对象所在的“包.类”名称,这就属于“反”了,可是在这个“反”的操做之中有一个getClass()就做为发起一切反射操做的开端。

Person的父类是Object类,而上面所使用getClass()方法就是Object类之中所定义的方法。取得Class对象:public final Class<?> getClass(),反射之中的全部泛型都定义为?,返回值都是Object。而这个getClass()方法返回的对象是Class类的对象,因此这个Class就是全部反射操做的源头。可是在讲解其真正使用以前还有一个须要先解释的问题,既然Class是全部反射操做的源头,那么这个类确定是最为重要的,而若是要想取得这个类的实例化对象,Java中定义了三种方式:

方式一:经过Object类的getClass()方法取得,基本不用:

package cn.mldn.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person() ; // 正着操做
Class<?> cls = per.getClass() ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}

方式二:使用“类.class”取得,在往后学习Hibernate开发的时候使用

package cn.mldn.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}

方式三:使用Class类内部定义的一个static方法,主要使用

取得Class类对象:public static Class<?> forName(String className) throws ClassNotFoundException;

package cn.mldn.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}

那么如今一个新的问题又来了,取得了Class类的对象有什么用处呢?对于对象的实例化操做以前一直依靠构造方法和关键字new完成,但是有了Class类对象以后,如今又提供了另一种对象的实例化方法:

经过反射实例化对象:public T newInstance() throws InstantiationException, IllegalAccessException;

范例:经过反射实例化对象

package cn.mldn.demo;
class Person {
@Override
public String toString() {
return "Person Class Instance .";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance() ; // 实例化对象,和使用关键字new同样
Person per = (Person) obj ; // 向下转型
System.out.println(per);
}
}

在进行Class.forName("cn.mldn.demo.Person")的时候,其实是对cn.mldn.demo.Person进行类加载,这时候,会把静态属性、方法以及静态代码块都加载到内存中。但这时候,对象却尚未产生。当执行cla.newInstance()的时候,就是利用反射机制将Class对象生成一个该类的一个实例。这时候对象就产生了。那么如今能够发现,对于对象的实例化操做,除了使用关键字new以外又多了一个反射机制操做,并且这个操做要比以前使用的new复杂一些,但是有什么用?

         对于程序的开发模式以前一直强调:尽可能减小耦合,而减小耦合的最好作法是使用接口,可是就算使用了接口也逃不出关键字new,因此实际上new是形成耦合的关键元凶。

范例:回顾一下以前所编写的工厂设计模式

package cn.mldn.demo;
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Factory {
public static Fruit getInstance(String className) {
if ("apple".equals(className)){
return new Apple() ;
}
return null ;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("apple") ;
f.eat() ;
}
}

以上为以前所编写最简单的工厂设计模式,可是在这个工厂设计模式之中有一个最大的问题:若是如今接口的子类增长了,那么工厂类确定须要修改,这是它所面临的最大问题,而这个最大问题形成的关键性的病因是new,那么若是说如今不使用关键字new了,变为了反射机制呢?

反射机制实例化对象的时候实际上只须要“包.类”就能够,因而根据此操做,修改工厂设计模式。

package cn.mldn.demo;
interface Fruit {
public void eat() ;
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
};
}
class Orange implements Fruit {
public void eat() {
System.out.println("吃橘子。");
};
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null ;
try {
f = (Fruit) Class.forName(className).newInstance() ;
} catch (Exception e) {
e.printStackTrace();
}
return f ;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("cn.mldn.demo.Orange") ;
f.eat() ;
}
}

         发现,这个时候即便增长了接口的子类,工厂类照样能够完成对象的实例化操做,这个才是真正的工厂类,能够应对于全部的变化。若是单独从开发角度而言,与开发者关系不大,可是对于往后学习的一些框架技术这个就是它实现的命脉,在往后的程序开发上,若是发现操做的过程之中须要传递了一个完整的“包.类”名称的时候几乎都是反射机制做用。

反射的深刻应用

以上只是利用了Class类做为了反射实例化对象的基本应用,可是对于一个实例化对象而言,它须要调用类之中的构造方法、普通方法、属性,而这些操做均可以经过反射机制完成。

调用构造

使用反射机制也能够取得类之中的构造方法,这个方法在Class类之中已经明肯定义了:

No. 方法名称 类型 描述
1 public Constructor<?>[] getConstructors() throws SecurityException 普通 取得一个类的所有构造
2 public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 普通 取得一个类的指定参数构造

如今发现以上的两个方法返回的都是java.lang.reflect.Constructor类的对象。

范例:取得一个类之中的所有构造

package cn.mldn.demo;
import java.lang.reflect.Constructor;
class Person { // CTRL + K
public Person() {}
public Person(String name) {}
public Person(String name,int age) {}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Constructor<?> cons [] = cls.getConstructors() ; // 取得所有构造
for (int x = 0; x < cons.length; x++) {
System.out.println(cons[x]);
}
}
}

验证:在以前强调的一个简单Java类必须存在一个无参构造方法

范例:观察没有无参构造的状况

package cn.mldn.demo;
class Person { // CTRL + K
private String name ;
private int age ;
public Person(String name,int age) {
this.name = name ;
this.age = age ;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance() ; // 实例化对象
System.out.println(obj);
}
}

此时程序运行的时候出现了错误提示“java.lang.InstantiationException”,由于以上的方式使用反射实例化对象时须要的是类之中要提供无参构造方法,可是如今既然没有了无参构造方法,那么就必须明确的找到一个构造方法,然后利用Constructor类之中的新方法实例化对象:

实例化对象:public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException

package cn.mldn.demo;
import java.lang.reflect.Constructor;
class Person { // CTRL + K
private String name ;
private int age ;
public Person(String name,int age) {
this.name = name ;
this.age = age ;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
// 取得指定参数类型的构造方法
Constructor<?> cons = cls.getConstructor(String.class,int.class) ;
Object obj = cons.newInstance("张三", 20); // 为构造方法传递参数
System.out.println(obj);
}
}

很明显,调用无参构造方法实例化对象要比调用有参构造的更加简单、方便,因此在往后的全部开发之中,凡有简单Java类出现的地方,都必定要提供无参构造。

调用普通方法

当取得了一个类实例化对象以后,下面最须要调用的确定是类之中的方法,因此能够继续使用Class类取得一个类中所定义的方法定义:

取得所有方法:public Method[] getMethods() throws SecurityException;

取得指定方法:public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

发现以上的方法返回的都是java.lang.reflect.Method类的对象。

范例:取得一个类之中所定义的所有方法

package cn.mldn.demo;
import java.lang.reflect.Method;
class Person {
private String name ;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Method met [] = cls.getMethods() ; // 取得所有方法
for (int x = 0; x < met.length; x++) {
System.out.println(met[x]);
}
}
}

可是取得了Method类对象最大的做用再也不于方法的列出(方法的列出都在开发工具上使用了),可是对于取得了Method类对象以后还有一个最大的功能,就是能够利用反射调用类中的方法:

调用方法:public Object invoke(Object obj, Object... args) throws IllegalAccessException,IllegalArgumentException, InvocationTargetException

以前调用类中方法的时候使用的都是“对象.方法”,可是如今有了反射以后,能够直接利用Object类调用指定子类的操做方法。(同时解释一下,为何setter、getter方法的命名要求如此严格)。

范例:利用反射调用Person类之中的setName()、getName()方法

package cn.mldn.demo;
import java.lang.reflect.Method;
class Person {
private String name ;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Object obj = cls.newInstance(); // 实例化对象,没有向Person转型
String attribute = "name" ; // 要调用类之中的属性
Method setMet = cls.getMethod("set" + initcap(attribute), String.class);// setName()
Method getMet = cls.getMethod("get" + initcap(attribute));// getName()
setMet.invoke(obj, "张三") ; // 等价于:Person对象.setName("张三")
System.out.println(getMet.invoke(obj));// 等价于:Person对象.getName()
}
public static String initcap(String str) {
return str.substring(0,1).toUpperCase().concat(str.substring(1)) ;
}
}

 在往后的全部框架技术开发之中,简单Java类都是如此应用的,因此必须按照标准进行。

调用成员

类之中最后一个组成部分就是成员(Field,也能够称为属性),若是要经过反射取得类的成员可使用方法以下:

取得本类的所有成员:public Field[] getDeclaredFields() throws SecurityException;

取得指定的成员:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;

这两个方法的返回值类型是java.lang.reflect.Field类的对象,下面首先观察如何取得一个类之中的所有属性。

范例:取得一个类之中的所有属性

package cn.mldn.demo;
import java.lang.reflect.Field;
class Person {
private String name ;
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
Field field [] = cls.getDeclaredFields() ; // 取得所有属性
for (int x = 0; x < field.length; x++) {
System.out.println(field[x]);
}
}
}

可是找到Field实际上就找到了一个颇有意思的操做,在Field类之中提供了两个方法:

设置属性内容(相似于:对象.属性 = 内容):

public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;

取得属性内容(相似于:对象.属性):

public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException

但是从类的开发要求而言,一直都强调类之中的属性必须封装,因此如今调用以前要想办法解除封装。

解除封装:public void setAccessible(boolean flag) throws SecurityException;

范例:利用反射操做类中的属性

package cn.mldn.demo;
import java.lang.reflect.Field;
class Person {
private String name;
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class对象
Object obj = cls.newInstance(); // 对象实例化属性才会分配空间
Field nameField = cls.getDeclaredField("name") ; // 找到name属性
nameField.setAccessible(true) ; // 解除封装了
nameField.set(obj, "张三") ; // Person对象.name = "张三"
System.out.println(nameField.get(obj)); // Person对象.name
}
}

虽然反射机制运行直接操做类之中的属性,但是不会有任何一种程序直接操做属性,都会经过setter、getter方法。

以上就是对java反射机制的总结,内容大都来源于网友的总结,太多了就不一一指出,包括知乎和csdn等渠道,我只是对其加以合理性的组织了一下,方便本身和你们总结理解,若有不足和不对之处还望指正。

相关文章
相关标签/搜索