- 1、什么是注解
- 2、内置注解
- 3、元注解
- 4、反射
- 5、得到Class类的几种方式
- 6、所有类型 的Class对象
- 7、类加载内存分析
- 8、什么时候会发生类初始化
- 9、类加载器
- 类加载器的作用
- 10、类的加载与ClassLoader的理解
- 11、获取类的运行时结构
- 12、动态创建对象执行方法
- 创建类的对象
- 1 调用Class对象的newInstance()方法
- 2 调用类的指定构造器
- 通过反射调用方法
- 通过反射操作属性
Annotation的格式:
注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:
@SuppressWarings(value=“unchecked”)
Annotation的作用:
- 不是程序本身,可以对程序做出解释
- 可以被其它程序(如编译器)读取
Annotation在哪里使用?
可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
2、内置注解-
@Override:此注解只用于修饰方法,表示重写超类中的一个方法
-
@Deprecated:此注解可用于修饰类、方法和属性,表示不鼓励使用这个类/方法/属性,通常是因为它很危险或者存在更好的选择
-
@SuppressWarnings:用来抑制编译时的警告信息
元注解的作用就是负责注解其它注解,有以下4个标准的meta-annotation类型,用来提供对其它注解类型作说明
- @Target:用于描述注解的作用范围(即指明被描述的注解可以用在什么地方)
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
SOURCESOURCE:注解将被编译器丢弃
CLASS:类文件会记录这些注解,但VM不需要在运行时保留这些注解(默认级别)
RUNTIME:编译器会将这些注解保留到类文件中,并在运行时由VM保留,因此它们可以被反射地读取 - @document:说明该注解将包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
Reflection(反射)是java被视为动态语言的关键,反射机制允许程序在执行期间借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
Class clazz=Class.forName("java.lang.String");
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象地称之为:反射
正常方式:
(1)引入需要的“包类”名称
(2)通过new实例化
(3)取得实例化对象
反射方式:
(1)实例化对象
(2)调用getClass()方法
(3)得到完整的“包类”名称
java反射机制提供的功能
(1)在运行时判断任意一个对象所属的类
(2)在运行时构造任意一个类的对象
(3)在运行时判断任意一个类所具有的成员变量和方法
(4)在运行时获取泛型信息
(5)在运行时调用任意一个对象的成员变量和方法
(6)在运行时处理注解
(7)生成动态代理
Class通过反射可以得到如下信息:类的属性、方法、构造器、实现的接口。对于每个类而言,JRE为其保留了一个不变的Class类型的对象。一个Class对象包含了某个特定结构的信息(class/interface/enum/annotation/void/primitive type)
- Class 本身也是一个类
- Class对象只能由系统建立
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应一个加载到JVM中的.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整地得到一个类中所有被加载的结构
- Class类是reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象
获取Class类的实例
(1)如果已知当前类的类型,则直接通过类的class属性获取。
Class clazz=Person.class;
这种方式最安全可靠,性能最高
(2)如果已有类的实例,则调用类的getClass()方法获取Class对象
Class clazz=person.getClass();
(3)已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException异常
Class clazz=Class.forName("com.test.entity.Person");
(4)基本数据类型可以直接用类名.Type
(5)还可以利用ClassLoader
示例:
public class TestAnnotation {
public static void main(String[] args) throws ClassNotFoundException {
Person person=new Student("学生");
System.out.println("这个人是"+person.getName());
//方式一:如果已有类的实例,则调用类的getClass()方法获取Class对象
Class clazz1=person.getClass();
System.out.println(clazz1.hashCode());
//方式二:如果已知当前类的类型,则直接通过类的class属性获取。
Class clazz2 = Student.class;
System.out.println(clazz2.hashCode());
//方式三:已知一个类的全类名,且该类在类路径下,可通过`Class类`的静态方法forName()获取
Class clazz3=Class.forName("Student");
System.out.println(clazz3.hashCode());
//获得父类类型
Class superclass = clazz1.getSuperclass();
System.out.println(superclass);
}
}
class Person{
private String name;
public Person(){
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
'}';
}
}
class Student extends Person{
public Student(String name) {
super(name);
}
}
6、所有类型 的Class对象
- class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface: 接口
- []: 数组
- enum: 枚举
- annotation: 注解@interface
- primitive type: 基本数据类型
- void
示例如下“
public static void main(String[] args) {
//类
Class c1=Object.class;
//interface: 接口
Class c2=Comparable.class;
//[]: 数组
Class c3=char[].class;
Class c4=int[][].class;
//enum: 枚举
Class c5= ElementType.class;
//annotation: 注解@interface
Class c6=Override.class;
//primitive type: 基本数据类型
Class c7=int.class;
//void
Class c8=void.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);
}
输出结果
class java.lang.Object interface java.lang.Comparable class [C class [[I class java.lang.annotation.ElementType interface java.lang.Override int void7、类加载内存分析
java内存
-
堆
- 存放new的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
-
栈
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(存放这个引用在堆中的内存地址)
-
方法区
- 可以被所有的线程共享
- 包含所有的class和static变量
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:
(1)类的加载
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
(2)类的链接
将类的二进制数据合并到JRE中
(3)类的初始化
JVM负责对类进行初始化
类的加载与ClassLoader的理解
(1)加载
将class的字节码文件内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
(2)链接
将java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
(3)初始化
- 执行类构造器
()方法的过程。类构造器 ()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器) - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的
()方法在多线程环境中被正确加锁和同步
示例如下:
public class Test01 {
public static void main(String[] args) {
A a=new A();
System.out.println(A.m);
}
}
class A{
static {
System.out.println("A类中的静态代码块执行");
m=300;
}
static int m=100;
public A() {
System.out.println("A类的无参构造初始化");
}
}
运行结果:
A类中的静态代码块执行 A类的无参构造初始化 100
在链接的准备阶段,为静态m赋默认值,因此m=0
在初始化阶段,根据"类构造器
因此合并成了
m=300; m=100;
因此输出m的值为100
8、什么时候会发生类初始化(1)类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则会先初始化其父类
(2)类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类的引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
示例如下:
public class Test01 {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//主动引用:通过new进行主动调用
Son son=new Son();
运行结果:
Main类被加载
父类被加载
子类被加载
//主动引用:使用java.lang.reflect包的方法对类进行反射调用
Class.forName("Son");
运行结果:
Main类被加载
父类被加载
子类被加载
//主动引用:调用静态成员
System.out.println(Son.m);
运行结果:
Main类被加载
父类被加载
子类被加载
100
//被动引用:子类引用父类静态成员(不会引起子类初始化)
System.out.println(Son.b);
运行结果:
Main类被加载
父类被加载
50
//被动引用:通过数组定义类的引用,不会触发此类的初始化
Son son[]=new Son[10];
运行结果:
Main类被加载
//被动引用:引用常量不会触发此类的初始化
System.out.println(Son.N);
运行结果:
Main类被加载
10
}
}
class Father{
static int b=50;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
m=300;
}
static int m=100;
static final int N=10;
}
9、类加载器
类加载器的作用
类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器:
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
初始化:
- 执行类构造器
()方法的过程。类构造器 ()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器) - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机可以保证一个类的
()方法在多线程环境中被正确加锁和同步
public static void main(String[] args) throws ClassNotFoundException {
//获取系统的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器-->根加载器
ClassLoader parentParent = parent.getParent();
System.out.println(parentParent);
//测试内置类是由哪个类加载器加载的
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);
}
运行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@4554617c null null
后两个输出结果之所以为null,是因为引导类加载器是无法直接获取的
11、获取类的运行时结构通过反射获取运行时类的完整结构
filed method constructor superclass interface annotation
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的字段
- 注解
- …
示例如下:
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class aClass = Class.forName("weimeng.Solution");
//获得类的名字
System.out.println(aClass.getName()); //获得包名+类名
System.out.println(aClass.getSimpleName()); //获得类名
//获得类的属性
Field[] fields = aClass.getFields(); //获得public属性
fields=aClass.getDeclaredFields(); //获得所有属性
aClass.getField("publicFiledName"); //获得指定的public属性
aClass.getDeclaredField("FiledName"); //获取指定的任意属性
//获得类的方法
aClass.getMethods(); //获得当前类和父类的public方法
aClass.getDeclaredMethods(); //获得当前类的所有方法
//获得指定方法
Method getName = aClass.getMethod("getName", null);
Method setName = aClass.getMethod("setName", String.class); //方法存在重载,因此需要传递参数
//获得构造器
Constructor[] constructors = aClass.getConstructors(); //获得public构造方法
Constructor[] declaredConstructors = aClass.getDeclaredConstructors(); //获得所有的构造方法
}
12、动态创建对象执行方法
创建类的对象
1 调用Class对象的newInstance()方法
(1)类必须有一个无参构造器
(2)如类的构造器访问权限为private,可调用setAccessible(true)即可顺利调用类的构造器
(1)通过Class类的getDeclaredConstructor(Class>… parameterTypes),根据形参列表取得本类的指定构造器
(2)向构造器的形参中传递一个对象数组,里面包含了构造器中所需的各个参数
(3)通过Constructor实例化对象
示例如下:
public static void main(String[] args) throws ClassNotFoundException{
Class clazz = Class.forName("yongyou.User");
//实例化一个User对象,默认是调用无参构造器
User user1 = (User) clazz.newInstance();
System.out.println(user1);
//调用有参构造器
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
User user2 = (User)constructor.newInstance("张三", 18);
System.out.println(user2);
}
输出结果:
User{name='null', age=0}
User{name='张三', age=18}
通过反射调用方法
通过反射,调用类中的方法,通过Method类来完成,步骤如下:
- 通过Class类的getDeclaredMethod(String name, Class>... parameterTypes)方法取得一个Method对象,name是方法名,parameterTypes是参数列表
2.获得Method对象后,让Method对象调用invoke(Object obj, Object... args)方法,obj是调用底层方法的对象,args是方法调用的参数列表
示例如下:
public static void main(String[] args){
Class clazz = Class.forName("yongyou.User");
//根据方法名和参数列表得到方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
Method setAge = clazz.getDeclaredMethod("setAge", int.class);
User user1 = (User)clazz.newInstance();
setName.invoke(user1,"DaMing");
setAge.invoke(user1,20);
System.out.println(user1);
}
运行结果如下:
User{name='DaMing', age=20}
对于方法Object invoke(Object obj, Object... args)
- Object对应原方法的返回值, 若原方法没有返回值, 此时返回null
- 若原方法为静态方法, 此时形参obj可以为null
- 若原方法参数列表为空,则形参列表args为null
- 若原方法访问权限为private, 则需要在调用 invoke() 方法前调用Method对象的 setAccessible(true) 方法, 即可访问权限为private的方法
示例如下:
public static void main(String[] args){
Class clazz = Class.forName("yongyou.User");
//根据方法名和参数列表得到方法
User user = (User) clazz.newInstance();
Field name = clazz.getDeclaredField("name");
Field age = clazz.getDeclaredField("age");
//name,age字段为private,无法直接访问,因此需要调用setAccessible(true)使其可以被访问
name.setAccessible(true);
age.setAccessible(true);
name.set(user,"LingLing");
age.set(user,80);
System.out.println(user);
}
运行结果:
User{name='LingLing', age=80}
视频传送:【狂神说Java】注解和反射



