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对象?
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());
}
}
Java内存分析
堆:
存放new的对象和数组
可以被所有的线程共享,不会存放别的对象引用
栈:
存放基本变量类型(会包含这个基本类型的具体地址)
引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区: 可以被所有的线程共享
包含了所有的class和static变量
类的加载
加载:将Class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程
验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
解析:虚拟机常量池内符号引用(常量名)替换为直接引用(地址)的过程
初始化:
执行类构造器<clinit>( )方法的过程。类构造器<clinit>( )方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
虚拟机会保证一个类的<clinit>( )方法在多线程环境中被正确枷锁和同步
什么时候会发生类的初始化?
1、类的主动引用(一定会发生勒的初始化)
(1)当虚拟机启动,先初始化main方法所在的类
(2)new一个类的对象
(3)调用类的静态成员(除了final常量)和静态方法
(4)使用java.lang.reflect包的方法对类进行反射调用
(5)当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
2、类的被动调用(不会发生类的初始化)
(1)访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过之类引用父类的静态变量,不会导致子类初始化
(2)通过数组定义类的引用,不会触发此类的初始化
(3)引用常量不会触发此类的初始化(常量在连接阶段就存入调用类的常量池中了)
类加载器的作用
类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
JVM规范定义了如下类型的类的加载器
引导类加载器:用C++编写的,是JVM自带的类加载器,负责JAVA平台核心库,用来装载核心类库。该加载器无法直接获取
扩展类加载器:负责jre/lib/ext目录下的jar包或-Djava.ext.dirs指定目录下的jar包装入工作库
系统类加载器:负责java-dasspath或-Djava.class.path所指的目录下的类与jar包装入工作,是最常用的加载器