反射机制动态语言 VS 静态语言动态语言静态语言反射概念Java 反射机制提供的功能Java 反射的优点和缺点Java 反射的主要 APIClass 类Class 类的常用方法获取 Class 类的实例拥有 Class 对象的类型创建运行时类的对象获取运行时类的完整结构操作运行时类的内部属性及方法性能对比分析反射操作泛型反射操作注解
动态语言是一类在运行时可以改变其结构的语言
例如:新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除或是其他结构上的变化
通俗点说就是运行时代码可以根据某些条件改变自身结构
主要的动态语言:Object-C、C#、JavaScript、PHP、Python 等
与动态语言相对应的,运行时结构不可变的语言就是静态语言,如:Java、C、C++
Java 不是动态语言,但 Java 可以称之为「准动态语言」,即 Java 有一定的动态性
我们可以利用反射机制获得类似动态语言的特性,Java 的动态性让编程的时候更加灵活!
PS:关于「动态语言」和「动态类型语言」的对比可见 动态语言 vs 动态类型语言
反射 (Reflection) 是 Java 被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reelection API 获得任何类的内部信息 (包括:类名、接口、属性、方法等),并能直接操作任意对象的内部属性及方法
例如:一个类有「成员变量、方法、构造方法、包」等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象
加载完类之后,类的类型信息会被存储在方法区中,同时会在堆中生成一个Class
类型的对象 (一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息,详情可见 加载阶段
我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射
// 通过反射机制获取 Class 类型的对象
Class c = Slass.forName("java.lang.String");
正常方式 (通过类名获得实例化对象):「引入需要的"包类"名称」 --> 「通过new
实例化」 --> 「获得实例化对象」
反射方式 (通过实例化对象获得类名):「实例化对象」 --> 「getClass()
方法」 --> 「获得 Class 对象」
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
...
优点:可以实现动态创建对象和编译,体现出很大的灵活性
缺点:对性能有影响。使用反射基本上是一种解释操作,告诉 JVM 希望做什么,大概比正常new
对象慢了十几倍
java.lang.Class
:代表一个类
java.lang.reflect.Method
:代表类的方法
java.lang.reflect.Field
:代表类的成员变量
java.lang.reflect.Constructor
:代表类的构造器
...
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
// 通过反射机制获取 Class 类型的对象
Class<?> c1 = Class.forName("com.lfool.myself.User");
Class<?> c2 = Class.forName("com.lfool.myself.User");
// 一个类在内存中只有一个 Class 对象
// 一个类被加载后,类的整个结构都会被封装在 class 对象中
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
}
}
class User {
private int id;
private String name;
private int age;
}
在Object
类中定义了getClass()
方法,此方法将被所有子类继承。调用该方法,可以获得该对象的 Class 对象
public final native 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 对象
方法名 | 功能说明 |
---|---|
public static Class<?> forName(String className) | 返回指定类名的 Class 对象 |
public T newInstance() | 调用默认构造函数,返回 Class 对象的一个实例 |
public String getName() | 返回此 Class 对象所表示的 实体 (类、接口、数组、void) 的名称 |
public native Class<? super T> getSuperclass() | 返回当前 Class 对象父类的 Class 对象 |
public Class<?>[] getInterfaces() | 返回当前 Class 对象的接口 |
public ClassLoader getClassLoader() | 返回该类的类加载器 |
public Constructor<?>[] getConstructors() | 返回一个包含某些 Constructor 对象的数组 |
public Method getMethod(String name, Class<?>... parameterTypes) | 返回一个 Method 对象 此对象的形参类型为 parameterType |
public Field[] getDeclaredFields() | 返回 Field 对象的一个数组 |
方法一:若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高
Class<User> userClass = User.class;
方法二:已知某个类的实例,调用该实例的getClass()
方法获取 Class 对象
User user = new User();
Class<? extends User> userClass = user.getClass();
方法三:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法forName()
获取,可能抛出ClassNotFoundException
异常
Class<?> userClass = Class.forName("com.lfool.myself.User");
方法四:内置基本数据类型可直接用「包装类类名.Type」
Class<Integer> intClass = Integer.TYPE;
方法五:利用 ClassLoader
class:外部类、成员 (成员内部类、静态内部类)、局部内部类、匿名内部类
interface:接口
[]:数组
enum:枚举
annotation:注解 @interface
primitive type:基本数据类型
void
public static void main(String[] args) {
Class c1 = Object.class; // 类
Class c2 = Comparator.class; // 接口
Class c3 = String[].class; // 数组
Class c4 = int[][].class; // 二维数组
Class c5 = ElementType.class; // 枚举
Class c6 = Override.class; // 注解
Class c7 = Integer.class; // 基本类型
Class c8 = Void.class; // void
Class c9 = Class.class; // Class
}
// result
class java.lang.Object
interface java.util.Comparator
class [Ljava.lang.String;
class [[I
class java.lang.annotation.ElementType
interface java.lang.Override
class java.lang.Integer
class java.lang.Void
class java.lang.Class
通过反射获取运行时类的完整结构:Field、Method、Constructor、Superclass、Interface、Annotation
全部的 Field (域)
全部的方法
全部的构造器
全部的父类
全部的接口
注解
...
xxxxxxxxxx
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> c = Class.forName("com.lfool.myself.User");
System.out.println("-- 获取全部的 Field --");
Field[] fields = c.getFields(); // getFields() 只能获取 public Field
for (Field f : fields) {
System.out.println(f);
}
System.out.println("---------------------");
fields = c.getDeclaredFields(); // getDeclaredFields() 可以获取所有 Field
for (Field f : fields) {
System.out.println(f);
}
System.out.println("-- 获取指定的 Field --");
Field age = c.getField("age"); // getField(String name) 只能获取 public Field
System.out.println(age);
System.out.println("---------------------");
Field name = c.getDeclaredField("name"); // getDeclaredField(String name) 可以获取 private Field
System.out.println(name);
System.out.println("-- 获取所有的方法 --");
Method[] methods= c.getMethods(); // getMethods() 只能获取 public 方法,但包括父类的方法
for (Method m : methods) {
System.out.println(m);
}
System.out.println("---------------------");
methods= c.getDeclaredMethods(); // getDeclaredMethods() 可以获取所有方法,但不包括父类的方法
for (Method m : methods) {
System.out.println(m);
}
System.out.println("-- 获取指定的方法 --");
Method setAge = c.getMethod("setAge", int.class);
System.out.println(setAge);
}
创建类的对象
调用 Class 对象的newInstance()
方法
类必须有一个无参的构造器
类的构造器的访问权限需要足够 (不能是 private)
思考:难道没有无参构造器就不能创建对象吗?
解释:只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,就可以实例化操作
调用指定的方法
通过反射,调用类中的方法,通过 Method 类完成
setAccessible
Method、Field、Constructor 对象都有setAccessible()
方法
setAccessible
的作用是启动和禁止访问安全检查的开关 (如:一般不能调用 private 属性、方法,但设置为true
后,就可以调用)
参数值为true
则表示反射的对象在使用时应该取消 Java 语言访问检查
提高反射的效率。如果代码中必须使用反射,而该句代码需要频繁的被调用,那么请设置为true
使得原本无法访问的私有成员也可以访问
参数为false
则表示反射的对象应该实施 Java 语言访问检查
xxxxxxxxxx
public static void main(String[] args) throws Exception {
Class<?> userClass = Class.forName("com.lfool.myself.User");
// 默认调用无参构造器,如果没有无参构造器就会报错
User user1 = (User) userClass.newInstance();
// 获取指定构造器实例化对象
Constructor<?> constructor = userClass.getDeclaredConstructor(int.class, String.class, int.class);
User user2 = (User) constructor.newInstance(1, "zs", 18);
// 调用方法
Method setAge = userClass.getDeclaredMethod("setAge", int.class);
setAge.invoke(user2, 20);
// 修改属性
// 由于 age 是 private,不能修改;除非设置访问权限:age.setAccessible(true);
Field age = userClass.getDeclaredField("age");
age.setAccessible(true);
age.set(user2, 22);
}
xxxxxxxxxx
// 普通方法调用
public static void test01() {
User user = new User(1, "zs", 18);
long startTime = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方法调用: " + (endTime - startTime) + " ms");
}
// 反射方法调用
public static void test02() throws Exception {
User user = new User(1, "zs", 18);
Class<User> userClass = User.class;
Method getName = userClass.getDeclaredMethod("getName");
long startTime = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
getName.invoke(user);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方法调用: " + (endTime - startTime) + " ms");
}
// 反射方法调用 关闭访问检查
public static void test03() throws Exception {
User user = new User(1, "zs", 18);
Class<User> userClass = User.class;
Method getName = userClass.getDeclaredMethod("getName");
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
getName.invoke(user);
}
long endTime = System.currentTimeMillis();
System.out.println("关闭访问检查: " + (endTime - startTime) + " ms");
}
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
// result
// 普通方法调用: 3 ms
// 反射方法调用: 2542 ms
// 关闭访问检查: 2088 ms
Java 采用泛型擦除的机制来引入泛型,java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题
但是,一旦编译完成,所有和泛型有关的类型全部擦除
为了通过反射操作这些类型,Java 新增了ParameterizedType
、GenericArrayType
、TypeVariable
和WildcardType
几种类型来代表不能被归一到 Class 类中的类型但又和原始类型齐名的类型
ParameterizedType
:表示一种参数化类型,比如:Collection<String>
GenericArrayType
:表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable
:是各种类型变量的公共父接口
WildcardType
:代表一种通配符类型表达式
xxxxxxxxxx
public class ReflectionTest {
public static void test01(Map<String, Integer> map, List<Integer> list) {
}
public static Map<String, Integer> test02() {
return null;
}
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("com.lfool.myself.ReflectionTest");
Method m1 = c.getDeclaredMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = m1.getGenericParameterTypes();
for (Type g : genericParameterTypes) {
System.out.println("# " + g);
if (g instanceof ParameterizedType) {
Type[] types = ((ParameterizedType) g).getActualTypeArguments();
for (Type type : types) {
System.out.println(type);
}
}
}
System.out.println("-----------------------");
Method m2 = c.getDeclaredMethod("test02");
Type genericReturnType = m2.getGenericReturnType();
System.out.println("# " + genericReturnType);
if (genericReturnType instanceof ParameterizedType) {
Type[] types = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type type : types) {
System.out.println(type);
}
}
}
}
// result
# java.util.Map<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
# java.util.List<java.lang.Integer>
class java.lang.Integer
-----------------------
# java.util.Map<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
xxxxxxxxxx
public class ReflectionAnnotationTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> c = Class.forName("com.lfool.myself.Student");
// 通过反射获取注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获取注解的值
Table table = c.getAnnotation(Table.class);
System.out.println(table.value());
// 获取类指定属性的注解
Field name = c.getDeclaredField("name");
annotations = name.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获取类指定属性的注解的值
DBField annotation = name.getAnnotation(DBField.class);
System.out.println(annotation.colName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
"Student") (
class Student {
colName = "id", type = "int", length = 32) (
private int id;
colName = "name", type = "String", length = 32) (
private String name;
colName = "age", type = "int", length = 32) (
private int age;
public Student() {
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
ElementType.TYPE) (
RetentionPolicy.RUNTIME) (
@interface Table {
String value();
}
ElementType.FIELD) (
RetentionPolicy.RUNTIME) (
@interface DBField {
String colName();
String type();
int length();
}