反射机制(Reflection)
定义
反射是框架设计的灵魂
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
反射是Java被视为动态语言的关键
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
以上的总结就是什么是反射
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
反射优缺点
一、反射机制的优点
首先,反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
其次,通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
再次,使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
最后,反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。
二、反射机制的缺点
1、性能问题。
Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。
2、安全限制。
使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。
3、程序健壮性。
反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。
package Reflection;
//什么叫反射
public class Textdemo01 {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获得类的class对象
Class aclass=Class.forName("Reflection.User");
System.out.println(aclass); //class Reflection.User
Class aclass1=Class.forName("Reflection.User");
Class aclass2=Class.forName("Reflection.User");
Class aclass3=Class.forName("Reflection.User");
//一个类在内存中只有一个class对象
//一个类被加载后,类的整个结构都会被封装在class对象中
System.out.println(aclass1.hashCode());
System.out.println(aclass2.hashCode());
System.out.println(aclass3.hashCode()); //hashcode相同,表示是同一类
}
}
//实体类:pojo,entity
class User{
private String name;
private int id;
private int age;
public User(){
}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
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;
}
@Override
public String toString() {
return "User [name=" + name + ", id=" + id + ", age=" + age + "]";
}
}
测试class类的创建方式的有哪些?
方式一:通过对象获得
Persion persion=new Student();
方式二:forName获得
Class cla=Class.forName("包名下的类");
方式三: 通过类名.class获得
Class cla1=Student.class;
方式四:基本内置类型的包装类都有一个Type属性
Class cla2=Interger.Type
方式五:调用实例的getClass()方法获取class对象
==========================================
获得父类的类型
Class cla3=cla.getSuperclass();
package Reflection;
import java.lang.annotation.ElementType;
//所有类型的class
public class Textdemo02 {
public static void main(String[] args) {
Class c1=Object.class; //类
Class c2=Runnable.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 ; //void类型
Class c9=Class.class; //Class本身
//只要元素类型与维度一样,就是同一个class
int []a=new int[10];
int []b=new int[100];
System.out.println(a.getClass().hashCode()); //366712642
System.out.println(b.getClass().hashCode()); //366712642
}
}
类的加载
类加载的过程
类的个生命周期如下图:
加载
- 通过全限定类名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 文件格式验证:如是否以魔数 0xCAFEBABE 开头、主、次版本号是否在当前虚拟机处理范围之内、常量合理性验证等。
此阶段保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java类型信息的要求。 - 元数据验证:是否存在父类,父类的继承链是否正确,抽象类是否实现了其父类或接口之中要求实现的所有方法,字段、方法是否与父类产生矛盾等。
第二阶段,保证不存在不符合 Java 语言规范的元数据信息。 - 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。例如保证跳转指令不会跳转到方法体以外的字节码指令上。
- 符号引用验证:在解析阶段中发生,保证可以将符号引用转化为直接引用。
可以考虑使用 -Xverify:none
参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备
为类变量分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配。
解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。
初始化
到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行<clinit>()
方法的过程。
<clinit>()
方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。(不包括构造器中的语句。构造器是初始化对象的,类加载完成后,创建对象时候将调用的 <init>()
方法来初始化对象)
静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问,如下程序:
public class Test {
static {
// 给变量赋值可以正常编译通过
i = 0;
// 这句编译器会提示"非法向前引用"
System.out.println(i);
}
static int i = 1;
}
<clinit>()
不需要显式调用父类(接口除外,接口不需要调用父接口的初始化方法,只有使用到父接口中的静态变量时才需要调用)的初始化方法<clinit>()
,虚拟机会保证在子类的 <clinit>()
方法执行之前,父类的 <clinit>()
方法已经执行完毕,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
<clinit>()
方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()
方法。
虚拟机会保证一个类的<clinit>()
方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>()
方法完毕。
类加载的时机
对于初始化阶段,虚拟机规范规定了有且只有 5 种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
- 遇到new、getstatic 和 putstatic 或 invokestatic 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。对应场景是:使用 new 实例化对象、读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法。
- 对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全部都完成了初始化)
- 虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),
虚拟机会先初始化这个主类。
- 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
第5种情况,我暂时看不懂。
以上这 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用,例如:
- 通过子类引用父类的静态字段,不会导致子类初始化。
- 通过数组定义来引用类,不会触发此类的初始化。
MyClass[] cs = new MyClass[10];
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
package Reflection;
public class Textdemo03 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader parent =ClassLoader.getSystemClassLoader();
System.out.println(parent);
//获取系统类加载器的父类加载器—->扩展类加载器
ClassLoader parent1=parent.getParent();
System.out.println(parent1); //
//获取系统类加载器的父类加载器—->根加载器(c/c++)
ClassLoader pLoader =parent1.getParent();
System.out.println(pLoader); //null
//测试当前类是哪个加载器加载的
ClassLoader loader=Class.forName("Reflection.Textdemo03").getClassLoader();
System.out.println(loader); //系统类的加载器
}
}
package Reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Textdemo04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException {
Class c1=Class.forName("Reflection.User");
System.out.println(c1.getName()); //包名+类名
System.out.println(c1.getSimpleName()); //类名
Field[] s1=c1.getFields(); //只能获得public修饰的属性
for(Field field:s1) {
System.out.println(field);
}
Field[] s2=c1.getDeclaredFields();
for(Field field:s2) {
System.out.println(field); //全部的属性
}
Field s3=c1.getDeclaredField("name"); //查找要找的属性
System.out.println(s3);
Method[] method=c1.getMethods(); //只能获得public修饰的方法,包括父类的方法
for(Method s4:method) {
System.out.println(s4);
}
}
}
package Reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TextDemo05 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
//获得class对象
Class c1=Class.forName("Reflection.User");
//构造一个对象
User user=(User) c1.newInstance(); //本质调用 类的无参构造
System.out.println(user);
//通过构造器创建对象
Constructor constructor=c1.getConstructor(int.class,String.class,int.class);
User user2=(User) constructor.newInstance(23,"流",14);
System.out.println(user2);
//通过反射调用普通方法
User user3=(User) c1.newInstance();
//通过反射获得一个方法
Method seMethod=c1.getMethod("setName",String.class);
//invoke :激活的意思
//(对象,“方法的值”)
seMethod.invoke(user3, "矿石");
System.out.println(user3.getName());
//通过反射操作属性
User user4=(User) c1.newInstance();
Field field=c1.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全检测,属性或者方法的setAccessible(true);
field.setAccessible(true);
field.set(user4, "安定");
System.out.println(user4.getName());
}
}
package Reflection;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
public class TextDemo07 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException {
Class c1=Class.forName("Reflection.Student2");
Annotation[]annotations=c1.getAnnotations();
for(Annotation annotation:annotations) {
System.out.println(annotation);
}
//获得注解的value的值
TableKuang kuang=(TableKuang) c1.getAnnotation(TableKuang.class);
System.out.println(kuang.value());
//获得指定的注解
Field field=c1.getDeclaredField("id");
Fieldkuang annotation=field.getAnnotation(Fieldkuang.class);
System.out.println(annotation.columnName()+annotation.type()+annotation.length());
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableKuang{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldkuang{
String columnName();
String type();
int length();
}
@TableKuang("haha")
class Student2{
@Fieldkuang(columnName ="db_id",type ="int",length = 10)
private int id;
@Fieldkuang(columnName ="db_name",type ="varchar",length = 10)
private String name;
@Fieldkuang(columnName ="db_age",type ="int",length = 3)
private int age;
public Student2(int id, String name, int age) {
super();
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;
}
@Override
public String toString() {
return "Student2 [id=" + id + ", name=" + name + ", age=" + age + "]";
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}