Java反射机制
一、Java反射机制概述
● Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
● 加载完类之后,在堆内存的方法区中就产生了一个 Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射。
- 谈谈反射机制的优缺点
- 优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
- 缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
二、理解Class类并获取Class实例 ★
1.Class类
在Object类中定义了以下的方法,此方法 将被所有子类继承:
● public final Class getClass()
以上的方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即: 可以通过对象反射求出类的名称。
对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含 了特定某个结构
**(class/interface/enum/annotation/primitive type/void/[])**的有关信息。
● Class本身也是一个类
● Class 对象只能由系统建立对象
● 一个加载的类在 JVM 中只会有一个Class实例
● 一个Class对象对应的是一个加载到JVM中的一个.class文件
● 每个类的实例都会记得自己是由哪个 Class 实例所生成
● 通过Class可以完整地得到一个类中的所有被加载的结构
● Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象
2.Class类的常用方法
3.获取Class实例的4种方法:
1)前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高
实例:Class clazz = String.class;
2)前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
实例:Class clazz = “www.camille.com”.getClass();
3)前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方 法forName()获取,可能抛出ClassNotFoundException
实例:Class clazz = Class.forName(“java.lang.String”);
4)其他方式(不做要求)
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);
类代码:
public class Person {
private String name;
public int id;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
public void show() {
System.out.println("I am a person.");
}
private String showNation(String nation) {
System.out.println("My's nation is " + nation);
return nation;
}
private static void showDect(){
System.out.println("我很帅!~");
}
}
//获取Class的实例的方式,前三种方式需要掌握
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class<Person> class1 = Person.class;
System.out.println(class1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class class2 = p1.getClass();
System.out.println(class2);
//方式三:调用Class的静态方法:forName(String classPath)
Class class3 = Class.forName("com.camille.java.Person");
System.out.println(class3);
System.out.println(class1 == class2);//true
System.out.println(class1 == class3);//true
//方式四:使用类的加载项:ClassLoader
ClassLoader classloader =ReflectionTest.class.getClassLoader();
Class class4=classloader.loadClass("com.camille.java.Person");
System.out.println(class4);
System.out.println(class4==class1);//true
说明:加载到内存中的运行时类,会在缓存区存在一段时间(生命周期),在此时间内,可以通过不同的方式获取此运行时类。(获取的是同一个)
4.哪些类型可以有Class对象?
(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[ ]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void
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;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);//ture
三、类的加载与ClassLoader(了解即可)
1.类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过 如下三个步骤来对该类进行初始化。
1、加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的 过程需要类加载器参与。
2、链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
3、类的初始化:执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 息的,不是构造该类对象的构造器)。
2.ClassLoader
类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的 类的加载器
拓展:
使用类加载器读取配置文件
@Test
public void test1() throws Exception {
Properties pros = new Properties();
//读取配置文件的方式一:
//此时的文件默认在当前的Module下
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = "+user+", password = "+password);
}
@Test
public void test1() throws Exception {
Properties pros = new Properties();
//读取配置文件的方式二:
//此时配置文件的默认识别在:当前Module下的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = "+user+", password = "+password);
}
四、创建运行时类的对象 ★
创建类的对象:调用Class对象的 newInstance() 方法
要求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class<Person> clazz = Person.class;
/**
* newIntance():调用此方法创建对应的运行时类的对象
*
* 要象此方法正常的创建运行时类的对象,要求:
* 1、运行时类必须提供空参的构造器
* 2、空参的构造器的访问权限得够,通常,设置为public
*
* 原因:
* 1、便于通过反射,创建运行时类的对象
* 2、便于子类继承此运行时类时,调用默认super(),保证父类有此构造器
*/
Person obj = clazz.newInstance();
System.out.println(obj);
}
}
或者使用带参构造器:
//1.根据全类名获取对应的Class对象
String name = “atguigu.java.Person";
Class clazz = null;
clazz = Class.forName(name);
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);
五、获取运行时类的完整结构
通过反射获取运行时类的完整结构:
Field、Method、Constructor、Superclass、Interface、Annotation
-
实现的全部接口
public Class<?>[ ] getInterfaces()
确定此对象所表示的类或接口实现的接口。 -
所继承的父类
public Class<? Super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。 -
全部的构造器
public Constructor[ ] getConstructors()
返回此 Class 对象所表示的类的所有 public构造方法。
public Constructor[ ] getDeclaredConstructors()
返回此 Class 对象表示的类声明的所有构造方法。
● Constructor类中:
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?> [ ] getParameterTypes();
- 全部的方法
public Method[ ] getDeclaredMethods()
返回此Class对象所表示的类或接口的全部方法
public Method[ ] getMethods()
返回此Class对象所表示的类或接口的public的方法
● Method类中:
public Class<?> getReturnType() 取得全部的返回值
public Class<?>[ ] getParameterTypes() 取得全部的参数
public int getModifiers() 取得修饰符
public Class<?>[ ] getExceptionTypes() 取得异常信息
- 全部的Field
public Field[ ] getFields()
返回此Class对象所表示的类或接口的public的Field。
public Field[ ] getDeclaredFields()
返回此Class对象所表示的类或接口的全部Field。
Field方法中:
public int getModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。
-
Annotation 相关
get Annotation(Class annotationClass) 获取运行时类的注解
getDeclaredAnnotations() -
泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments() -
类所在的包
Package getPackage()
六、调用运行时类的指定结构 ★
1.调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。
public Field getField(String name) 返回此Class对象表示的类或接口的指定的 public的Field。
public Field getDeclaredField(String name) 返回此Class对象表示的类或接口的 指定的Field。
在Field中:
public Object get(Object obj) :取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value) :设置指定对象obj上此Field的属性内容
代码示例:
/*
操作运行时类中的指定属性————需要掌握
*/
@Test
public void test2() throws Exception {
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
//1、getDeclaredField(String fieldName):获取运行时类中制定变量名的属性
Field name = clazz.getDeclaredField("name");
//2、保证当前属性是可访问的(获取属性修改权限)
name.setAccessible(true);
//3、获取、设置指定对象的属性值
name.set(p, "Tom");
System.out.println(name.get(p));
}
2.调用指定构造器
/*
调用运行时类中的指定构造器——需要掌握
*/
@Test
public void test4() throws Exception {
Class<Person> clazz=Person.class;
//1、创建运行时类的对象
Person p= clazz.newInstance();
/*
//private Person(String name)
2、获取指定的某个构造器
getDeclaredConstructor():参数:指明获取的构造器的参数列表
*/
Constructor constructor=clazz.getDeclaredConstructor(String.class);
//3、保证当前构造器是可访问的
constructor.setAccessible(true);
/*
调用此构造器创建运行时类的对象
*/
Person person=(Person)constructor.newInstance("Tom");
System.out.println(person);
}
}
3.调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
1.通过Class类的getMethod(String name,Class…parameterTypes)方法取得 一个Method对象,并设置此方法操作时所需要的参数类型。
2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中 传递要设置的obj对象的参数信息。
/**
* 操作运行时类中的指定方法————需要掌握
*/
@Test
public void test3() throws Exception {
Class<Person> clazz=Person.class;
//1、创建运行时类的对象
Person p= clazz.newInstance();
/*
2、获取指定的某个方法
getDeclaredMethod():参数1:指明获取的方法的名称 参数2:指明获取方法的形参列表
*/
Method showNation=clazz.getDeclaredMethod("showNation",String.class);
//3、保证当前方法是可访问的
showNation.setAccessible(true);
/*
调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即对应类中调用方法的返回值
*/
Object returnValue=showNation.invoke(p,"China");
System.out.println(returnValue);
//如何调用一个静态方法
// private static void showDect()
Method showDect =clazz.getDeclaredMethod("showDect");
showDect.setAccessible(true);
//如果调用的方法没有返回值,则invoke()返回null
//Object returnValue2=showDect.invoke(null);
Object returnValue2=showDect.invoke(p);
System.out.println(returnValue2);
}
Object invoke(Object obj, Object … args)
说明:
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用 方法对象的setAccessible(true)方法,将可访问private的方法。
4.关于setAccessible方法的使用
● Method和Field、Constructor对象都有setAccessible()方法。
● setAccessible启动和禁用访问安全检查的开关。
参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被 调用,那么请设置为true。
使得原本无法访问的私有成员也可以访问
● 参数值为false则指示反射的对象应该实施Java语言访问检查。
七、反射的应用:动态代理
链接: 动态代理.