反射的概念
Reflection(反射)是Java被视为动态语言的关键
反射机制允许程序在执行期借助于Reflection API获得任何类的内部信息,
并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象)。
这个对象就包含了整个的类的结构信息,我们可以通过这个对象看到类的结构。
这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射
正常方式:引入需要的“包类名称”–>通过new实例化–>取得实例化对象;
反射方式:实例化对象–>getClass()方法–》得到完整的“包类名称”;
反射的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时构造任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时处理注解
生成动态代理
优点:可以实现动态类 创建对象和编译,体现出很大的灵活性
缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM我们希望做什么并且它满足我们的要求。这类操做总是慢于直接执行的相同操作
反射的使用
public class Reflect1 {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的Class对象
Class c1=Class.forName("Reflect.Reflect1");
System.out.println(c1);
Class c2=Class.forName("Reflect.Reflect1");
Class c3=Class.forName("Reflect.Reflect1");
Class c4=Class.forName("Reflect.Reflect1");
//一个类在内存中只有一个Class对象
// 一个类被加载后,类的整个结构都会被封装在Class对象中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
//实体类: pojo,entity
class User {
private String name;
private int id;
private int age;
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
}
}
Class类详解
对象照镜子后可以得到的信息:某个类的属性、方法和构造器,某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定的某个结构(class/interface/enum/annotation/primitive type/void[])的有关信息
·Class本身也是一个类
·Class对象只能由系统建立对象
·一个加载的类在JVM中只会有一个Class实例
·一个Class对象对应的是一个加载到JVM中的一个.class文件
·每个类的实例都会记得自己是由那个Class实例所生成
·通过Class可以完整地得到一个类中所有被加载 的结构
·Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
Class类的创建方式
public class Reflect2 {
public static void main(String[] args) throws ClassNotFoundException {
Person person=new Student();
System.out.println("这个人是"+ person.name);
//方式一:通过对象获得
Class c1=person.getClass();
System.out.println(c1.hashCode());
//方式二:forname 获得
Class c2=Class.forName("Reflect.Reflect2");
System.out.println(c2.hashCode());
//方式三:通过类名.class获得
Class c3=Student.class;
System.out.println(c3.hashCode());
//方式四: 基本内置类型的包装类都是有一个Type 属性
Class c4=Integer.TYPE;
System.out.println(c4);
//获得父类类型
Class c5=c1.getSuperclass();
System.out.println(c5);
}
}
class Person{
public String name;
public Person() {
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student extends Person {
public Student() {
this.name = "学生";
}
class Teacher extends Person {
public Teacher() {
this.name = "老师";
}
}
}
三种创建方式的比较
一般使用Class.forName 方法去动态加载类 ,且使用forName就不需要导入其他类,可以加载我们任意的类。
而使用 .class属性,则需要导入类的包,依赖性强,且容易抛出编译错误。
而使用实例化对象的 getClass() 方法,需要本身创建一个对象,是静态的,就体现不出反射机制意义了。
所以我们在获取class对象中, 一般都会使用 Class.forName去获取
哪些类型可以有Class对象?
class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
interface:接口
[ ]:数组
enum:枚举
annotation:注解@interfac
primitive type:基本数据类型
void
public class Reflect3 {
public static void main(String[] args) {
Class c1= Object.class;
Class c2=Comparable.class;
Class c3=String[].class;
Class c4=int [][].class;
Class c5=Override.class;
Class c6= ElementType.class;
Class c7=Integer.class;
Class c8=void.class;
Class c9=Class.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
//只要元素类型一样,就是同一个Class.
int []a=new int[10];
int []b=new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
}
}
利用反射获取成员变量Field
//返回 字段对象,该对象反映此 类对象表示的类或接口的指定声明字段。
getDeclaredField(String name)
//返回 字段对象的数组, 字段对象反映由此 类对象表示的类或接口声明的所有字段。
getDeclaredFields()
//返回 字段对象,该对象反映此 类对象表示的类或接口的指定公共成员字段。
getField(String name)
//返回一个包含 字段对象的数组, 字段对象反映此 类对象所表示的类或接口的所有可访问公共字段。
getFields()
例如:
Class<?> cls = Class.forName("com.person");
System.out.println(cls.getName());
//只能获取public 的属性
Field[] field = cls.getFields();
for (Field field1 : field) {
System.out.println(" 属性: "+field1.getName());
}
System.out.println("========");
//可以获取类全部属性
Field[] declaredFields = cls.getDeclaredFields();
for (Field all : declaredFields) {
System.out.println("属性" + all.getName());
}
利用反射获取方法Method
//返回 方法对象,该对象反映此 类对象表示的类或接口的指定声明方法。
getDeclaredMethod(String name, 类<?>... parameterTypes);
//返回一个包含 方法对象的数组, 方法对象反映此 类对象表示的类或接口的所有已声明方法,包括public,protected,default(package)访问和私有方法,但不包括继承的方法。
getDeclaredMethods(); //数组
//返回 方法对象,该对象反映此 类对象表示的类或接口的指定公共成员方法。
getMethod(String name, 类<?>... parameterTypes);
//返回一个包含 方法对象的数组, 方法对象反映此 类对象所表示的类或接口的所有公共方法,包括由类或接口声明的那些以及从超类和超接口继承的那些。
getMethods();
用法 跟Field 类似。
但是要注意后面的 String name, 类<?>… parameterTypes
例如
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class);
Runtime 类中的 exec 实现了方法的重载, 因此需要使用forMethod就要带上第二个参数,才能准确的找到指定方法的对象。
利用反射获取构造函数
//返回一个 构造器对象,该对象反映此 类对象所表示的类或接口的指定构造函数。
getDeclaredConstructor(类<?>... parameterTypes)
//返回 构造器对象的数组, 构造器对象反映由此 类对象表示的类声明的所有构造函数。
getDeclaredConstructors()
//返回一个 构造器对象,该对象反映此 类对象所表示的类的指定公共构造函数。
getConstructor(类<?>... parameterTypes)
//返回一个包含 构造器对象的数组, 构造器对象反映了此 类对象所表示的类的所有公共构造函数。
getConstructors()
用法几乎差不多,可以使用 getConstructors 再进行遍历取出
利用反射实例化对象
得到地址和类名后,使用newInstance 实例化这个类:
Class<?> cls = Class.forName("com.person");
Object o = cls.newInstance();
System.out.println(o);
也可以通过构造器实例化对象
Constructor<?> cons =
cls.getDeclaredConstructor(String.class, double.class);
Object snowy = cons.newInstance("snowy", 1000000000);
System.out.println(snowy);
利用反射中调用对象中的方法
反射中的调用,是用方法来调用对象,如:
方法.invoke(对象)
例子, 比如要调用 person 类中的 hi() 方法,首先要得到 hi() 方法的 Method对象,其次 还需要得到 person类的对象 作为invoke的参数
Class<?> cls = Class.forName("com.person");//加载类
Constructor<?> cons =
cls.getDeclaredConstructor(String.class, double.class); //获取构造器
Object snowy = cons.newInstance("snowy", 123);//实例化对象
Method m2 = cls.getMethod("m2");//得到Method 对象
Object name = m2.invoke(snowy); // 调用方法
如果调用的是普通方法,第一个参数就是类对象;
如果调用的这个方法是静态方法,第一个参数是类,或者可以护忽略,设置为null
特殊权限方法的反射访问方式
如果得到的Field 、Method、Constructor 是私有属性的话,受到权限影响,不能直接调用或者读取,需要设置 setAccessible(true) ;
setAccessible方法是AccessibleObejct类的一个方法,它是Field、Method和Constructor类的公共超类。这个特性是为调式、持久存储和类似机制提供的。
例如,Runtime类的构造函数是私有的,可以这样获取对象
Class<?> cls = Class.forName("java.lang.Runtime");
Constructor<?> m = cls.getDeclaredConstructor();
m.setAccessible(true);
cls.getMethod("exec", String.class).invoke(m.newInstance(),"calc.exe");