反射机制
概述
静态语言和动态语言
- 动态语言:运行时可以改变其结构的语言 C#、JavaScript、PHP,Python
- 静态语言:运行时结构不可变得语言 Java、C、C++
- Java不是动态语言,但Java可以称之为准动态语言,即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性
- Reflection 是Java被视为动态语言的关键,反射机制允许程序在执行期间借助Reflection API获得任何类的内部信息,并且能直接操作任意对象的内部属性及方法
- 加载完类之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
- 正常方式: 引入需要的“包类”名称–>通过new实例化–>取得实例化对象
- 实例化对象–>getClass()方法–>得到完整的“包类”名称
- Class类
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪一个Class实例所生成
- 通过Class可以完整的得到一个类中的所有被动态加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得响应的Class对象
import com.phx.oop.Demo1.Student;
public class Demo01 {
public static void main(String[] args) {
//正常方式
Student student=new Student();
//反射方式
System.out.println(student.getClass());//class com.phx.oop.Demo1.Student
}
}
- 优点:可以实现动态创建对象和编译,体现出很大的灵活性
- 缺点:对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("com.phx.reflection.User");
Class c2 = Class.forName("com.phx.reflection.User");
Class c3 = Class.forName("com.phx.reflection.User");
System.out.println(c1.getSuperclass());//Oclass java.lang.Object
System.out.println(c1.getClass());//class java.lang.Class
System.out.println(c1);//class com.phx.reflection.User
System.out.println(User.class);
//一个类在内存中只有一个Class对象
// 一个类被加载后,类的整个结构都会被封装的Clss对象中
System.out.println(c1.hashCode());//21685669
System.out.println(c1.hashCode());//21685669
System.out.println(c1.hashCode());//21685669
}
JVM
类的加载器
- 作用:将class字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取系统类的加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@14ae5a5
//获取扩展类加载器的父类加载器-->根加载器(c/c++) Java无法获取
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);//null
//测试当前类是由哪个加载器加载的
ClassLoader classLoader = Class.forName("com.phx.reflection.Demo07").getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//测试JDK内置类是由哪个加载器加载的
ClassLoader classLoader2 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader2);//null 根加载器加载
//测试ext包下的类是由哪个加载器加载的
ClassLoader classLoader3 = Class.forName("com.sun.nio.zipfs.ZipCoder").getClassLoader();
System.out.println(classLoader3);//sun.misc.Launcher$ExtClassLoader@14ae5a5
//双亲委派机制
//如何获得系统类加载可以加载的路径
System.out.println(System.getProperty("java.class.path"));
/**
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\charsets.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\deploy.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\javaws.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jce.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jfr.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\jsse.jar;C
* :\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\management-agent.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\plugin.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\resources.jar;
* C:\Program Files (x86)\Java\jdk1.8.0_131\jre\lib\rt.jar;
* D:\Program Files\idea\WorkSpace\JavaSE\out\production\BasicGrammar;
* D:\Program Files\idea\WorkSpace\JavaSE\BasicGrammar\src\lib\commons-io-2.6.jar;
* D:\Program Files\idea\IDEA\IntelliJ IDEA 2018.3.1\lib\idea_rt.jar
*/
}
类的加载
- 当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在方法栈中
- 当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在方法的栈中,该变量所指向的对象是放在堆类存中的。
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类java.lang.Class对象,所以该对象不能人为创建,只能获取
- 链接:将Java的二进制 代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
- 初始化:执行类构造器()方法的过程,类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现父类还没有初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的方法在多线程环境中被正确加锁和同步
public static void main(String[] args) {
A a=new A();//A类静态代码块初始化
//A类的无参构造初始化
System.out.println(A.class);
System.out.println(A.m);//300
/**
* 1、加载到内存(方法区中),会产生一个类对应的Class对象:com.phx.reflection.A(存储在堆中)
* 2、链接,链接结束后m=0()给类变量默认初始值
* 3、初始化 <clinit>(){
* static int m=100;
* System.out.println("A类静态代码块初始化");
* m=300;
* }
* m=300
*/
}
}
class A {
static int m=100;
static {
System.out.println("A类静态代码块初始化");
m=300;
}
public A(){
System.out.println("A类的无参构造初始化");
}
}
类的初始化
类的主动引用(一定发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果父类没有被初始化,则先会初始化它的父类
类的主动引用(不会发生类的初始化)
- 当访问一个静态域,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类被初始化
- 使用数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
public class Demo06 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
System.out.println("main类被加载");
//1、主动引用
Son son=new Son();//main类被加载 父类静态代码块 子类静态代码块 父类无参初始化 子类无参初始化
//反射也会产生主动引用
Class.forName("com.phx.reflection.Son").newInstance();//main类被加载 父类静态代码块 子类静态代码块
//2、不会产生类的引用的方法
//通过子类加载父类的静态方法或变量不,会导致子类被初始化
System.out.println(Son.b);//main类被加载 父类静态代码块 2
//使用数组定义类引用,不会触发此类的初始化
Son[] sons=new Son[5];//main类被加载
// 引用常量不会触发此类的初始化
System.out.println(Son.M);// main类被加载 100
}
}
class Father{
static int b=2;
static {
System.out.println("父类静态代码块");
}
Father(){
System.out.println("父类无参初始化");
}
}
class Son extends Father{
static int a=0;
static final int M=100;
static {
System.out.println("子类静态代码块");
}
Son(){
System.out.println("子类无参初始化");
}
}
创建运行时类的对象
- 获取Class类实例的方式
Person student=new Student();
System.out.println(((Student) student).name);//学生
//方式一:通过对象获得
System.out.println(student.getClass());//class com.phx.reflection.Student
//方式二:类名.Class
System.out.println(Student.class);class com.phx.reflection.Student
//方式三:Class.forName(全路径)
System.out.println(Class.forName("com.phx.reflection.Student"));
//方式四:基本内置类型的包装类都有一个Type属性
System.out.println(Integer.TYPE);//int
- 哪些类可以有Class对象
//所有类型的Class
public class Demo04 {
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;//void
Class c9=Class.class;
System.out.println(c1);//class java.lang.Object
System.out.println(c2);//interface java.lang.Comparable
System.out.println(c3);//class [Ljava.lang.String;
System.out.println(c4);//class [[I
System.out.println(c5);//interface java.lang.Override
System.out.println(c6);//class java.lang.annotation.ElementType
System.out.println(c7);//class java.lang.Integer
System.out.println(c8);//void
System.out.println(c9);//class java.lang.Class
int[]a={1,2,3};
int[]b=new int[10];
int[][]c={{1,2},{1,2}};
//只要元素类型和维度一样,就是同一个Class,通过对象.getClass获得的是同一个Class
System.out.println(a.getClass());//class [I
System.out.println(b.getClass());//class [I
System.out.println(c.getClass());//class [[I
}
}