Java反射机制

一、简介

反射是Java语言的一种特性。JAVA反射机制使得我们可以在运行过程中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,同时也能改变它的任意属性。

二、Type接口

Type是Java编程语言中所有类型的通用父接口。 其中包括原始类型、参数化类型、数组类型、类型变量和基本类型。

(一)ParameterizedType

参数化类型。表示泛型,例如List。

(二)GenericArrayType

数据类型。需要注意的是,这个数组并不是普通数组,而是泛型数组,例如T[]。

(三)TypeVariable

类型变量。例如T a。

(四)WildcardType

泛型表达式。WildcardType虽是Type的子接口,但不属于Java类型,例如<? extends Object>。

(五)Class

Java类实例。Class是Type的一个实现类,它反射的核心类,将在下文详细介绍。

三、Class类

Class类是类的实例,可以是类也可以是接口。枚举是一种类,注解是一种接口。数组属于一个反映为Class对象的类,该类对象由具有相同元素类型和维数的所有数组共享。 原始Java类型(boolean,byte,char,short,int,long,float和double)以及关键字void也都表示为Class对象。 ——以上来自Java 8 官方API
Class类没有公共构造方法,Class对象是在类加载过程中通过类加载器自动构造的。要理解这点,我们需要清楚JVM的类加载机制。简单来说,JVM会通过加载、连接和初始化三个阶段完成对一个Java类的加载。在加载阶段,JVM将经过编译生成的class文件加载至内存,并为其创建一个java.lang.Class类对象。也就是说,程序中用到的每一个类都拥有一个java.lang.Class类对象。关于JVM类加载机制可以看《深入理解Java虚拟机》,后面有时间我会系统的介绍。

(一)获取Class类实例

1、通过类的class属性获取

Class stringClass = String.class;

2、通过类对象的getClass()方法获取,这是Object类的一个方法。

String str = "reflect";
Class stringClass = str.getClass();

3、通过全限定类名获取,当无法通过指定全限定类名找到Class类实例时抛出ClassNotFoundException异常。

Class stringClass = Class.forName("java.lang.String");

(二)常用方法

1、getClassLoader();

说明: 获取类加载器。

2、newInstance();

说明: 获取一个实例对象。
示例:

String newInstance = stringClass.newInstance();

3、getName();

说明: 获取全限定类名。
示例:

System.out.println(stringClass.getName());    // java.lang.String

4、getPackage();

说明: 获取包名。
示例:

System.out.println(stringClass.getPackage());    // package java.lang, Java Platform API Specification, version 1.8

5、getSimpleName();

说明: 获取类名。
示例:

System.out.println(stringClass.getSimpleName());	// String

6、getSuperclass();

说明: 获取父类Class类实例(由于泛型擦除,故不包含泛型)。如果当前Class实例是一个Object、接口、基本类型或void返回null;如果当前Class实例是一个数组则返回Object Class实例。
示例:

System.out.println(stringClass.getSuperclass());    // class java.lang.Object

7、getGenericSuperclass();

说明: 获取父类的Type(包含泛型)。如果当前Class实例是一个Object、接口、基本类型或void返回null;如果当前Class实例是一个数组则返回Object Class实例。
示例:

System.out.println(stringClass.getGenericSuperclass());    // class java.lang.Object

8、getClass();

说明: 获取当前和父类Class实例中的公共类和接口Class实例。

9、getDeclaringClass();

说明: 获取当前Class实例中的所有类和接口Class实例。

四、反射相关类库

在Java中,Class类和java.lang.reflect类库共同构成了对反射机制的支持。其中我们主要介绍三个最基本的类。

(一)Field:类的成员变量。

1、getField(String name);

说明: 获取某个公共成员变量。

2、getFields();

说明: 获取所有公共成员变量。

3、getDeclaredField(String name);

说明: 获取某个成员变量。

4、getDeclaredFields();

说明: 获取所有成员变量。

(二)Method:类的方法。

1、getMethod(String name, Class<?>… parameterTypes);

说明: 获取某个形参类型顺序匹配的公共方法。如匹配到多个公共方法则返回其中返回值类型更为具体的一个,否则返回任意一个方法。

2、getMethods();

说明: 获取所有公共方法。

3、getDeclaredMethod(String name, Class<?>… parameterTypes);

说明: 获取某个形参类型顺序匹配的方法。如匹配到多个方法则返回其中返回值类型更为具体的一个,否则返回任意一个方法。

4、getDeclaredMethods();

说明: 获取所有方法。

(三)Constructor:类的构造器。

1、getConstructor(Class<?>… parameterTypes);

说明: 获取某个形参类型顺序匹配的公共构造器。

2、getConstructors();

说明: 获取所有公共构造器。

3、getDeclaredConstructor(Class<?>… parameterTypes);

说明: 获取某个形参类型顺序匹配的构造器。

4、getDeclaredConstructors();

说明: 获取所有构造器。

五、通过反射访问私有成员变量、私有方法及私有构造器

public class ReflectTest {

	private class InnerClass {

		private String str = InnerClass.class.getSimpleName();

		private InnerClass() {
			System.out.println("InnerClass Private Constructor");
		}
	}

	private static class StaticInnerClass {

		private String str;

		private StaticInnerClass(String str) {
			this.str = str;
			System.out.println("StaticInnerClass Private Constructor");
		}

		private void setStr(String str) {
			this.str = str;
			System.out.println("StaticInnerClass Private Method: " + this.str);
		}
	}

	public static void main(String[] args) {
		try {
			// 反射成员内部类私有无参构造器和私有成员变量
			reflectPrivateField();

			// 反射静态内部类私有有参构造器和私有方法
			reflectPrivateMethod();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 反射成员内部类私有无参构造器和私有成员变量
	private static void reflectPrivateField() throws Exception {
		// 1.获取外部类实例
		Class<ReflectTest> reflectTestClass = ReflectTest.class;
		ReflectTest reflectTest = reflectTestClass.newInstance();
		// 2.获取成员内部类私有无参构造器(成员内部类构造隐式持有外部类this引用)
		Class<InnerClass> innerClass = InnerClass.class;
		Constructor innerClassConstructor = innerClass.getDeclaredConstructor(reflectTestClass);
		// 3.因无参构造方法定义为private,需关闭Java访问权限检查
		innerClassConstructor.setAccessible(true);
		// 4.通过无参构造器构造成员内部类实例
		InnerClass instance = (InnerClass) innerClassConstructor.newInstance(reflectTest);    // InnerClass Private Constructor
		// 5.获取成员内部类私有成员变量实例
		Field str = innerClass.getDeclaredField("str");
		// 6.因成员内部类成员变量定义为private,需关闭Java访问权限检查
		str.setAccessible(true);
		// 7.通过成员内部类实例获取成员内部类私有成员变量的值
		System.out.println(str.get(instance));    // InnerClass
	}

	// 反射静态内部类私有有参构造器和私有方法
	private static void reflectPrivateMethod() throws Exception {
		// 1.获取外部类Class
		Class<ReflectTest> reflectTestClass = ReflectTest.class;
		// 2.获取静态内部类私有有参构造器
		Class<StaticInnerClass> staticInnerClass = StaticInnerClass.class;
		Constructor staticInnerClassConstructor = staticInnerClass.getDeclaredConstructor(String.class);
		// 3.因有参构造方法定义为private,需关闭Java访问权限检查
		staticInnerClassConstructor.setAccessible(true);
		// 4.通过有参构造器构造静态内部类实例
		StaticInnerClass instance = (StaticInnerClass) staticInnerClassConstructor.newInstance(staticInnerClass.getSimpleName());    // StaticInnerClass Private Constructor
		// 5.获取静态内部类私有方法实例
		Method setStr = staticInnerClass.getDeclaredMethod("setStr", String.class);
		// 6.因静态内部类方法定义为private,需关闭Java访问权限检查
		setStr.setAccessible(true);
		// 7.通过静态内部类实例调用静态内部类私有方法
		setStr.invoke(instance, staticInnerClass.getSimpleName());    // StaticInnerClass Private Method: StaticInnerClass
	}
}

六、通过反射越过泛型检查

通过Java反射机制我们可以越过泛型检查。当然在这之前你需要知道泛型只在编译前是有效的,在编译后的class文件中是没有泛型的,未知的泛型都将被当作Object处理。——这就是泛型擦除,有时间后面我会详细介绍(这篇埋的坑有点多,但是也没办法,知识都是交织的)

List<Integer> integers = new ArrayList<Integer>() {{
	add(1);
}};
// integers.add("2");	泛型检查这是不允许的

Class<? extends List> integersClass = integers.getClass();
// 通过源码我们可以发现add方法实现接受E,未知泛型在编译后将将被当作Object处理
Method addInt = integersClass.getMethod("add", Object.class);
addInt.invoke(integers, "2");
System.out.println(integers.get(1));    // 2

我个人建议尽量减少使用反射越过泛型检查,毕竟这违背了泛型的初衷。越过泛型检查无疑减低了代码的安全性,且大多数情况下异常都属于编译时不易发现的运行时异常,例如下面的情况将抛出ClassCastException异常。

ArrayList<String> strings = new ArrayList<String>() {{
	add("1");
}};
// strings.add(2);	泛型检查这是不允许的
Class<? extends ArrayList> stringsClass = strings.getClass();
Method addString = stringsClass.getMethod("add", Object.class);
addString.invoke(strings, 2);
try {
	System.out.println(strings.get(1));
} catch (ClassCastException e) {
	System.err.println(e.getMessage());    // java.lang.Integer cannot be cast to java.lang.String
}

大家肯定一头雾水,为什么将Integer换成String就不行了。这是因为当指定泛型是String时,正常编译后println()将会调用形参是String的重载方法,但是strings.get(1)却是一个Integer当然就会报错。而当指定泛型是Integer时,由于println()并没有形参是Integer的重载方法,所以实际上调用的是形参为Object的重载方法,此时无论是String还是Integer都不会报错。


尽管Java反射机制带来了极大的灵活性和方便性,但建议大家在能不通过反射完成时尽量不要使用,因为灵活与方便的同时必将导致性能、安全性和健壮性等方面的损失。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值