Java基础篇-注解与反射
注解(Annotation)
注解Annotation 是 JDK5.0 引入的一种注释机制
Annotation的作用:
- 不是程序本身,可以对程序作出解释
- 可以被其他程序(比如:编译器等)读取
Annotation的作用:
- 注解是以"@注释名"在代码中存在的,还可以添加一些参数值
Annotation在哪里使用?
- 可以附加在package、class、method、field上,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
<1>内置注解
三个类型在 java.lang 包中
-
@Override - 定义在java.lang.Override中,检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
-
@Deprecated - 定义在java.lang.Deprecated中,在标记过时方法、属性、类。如果使用该方法,会报编译警告。因为它是危险的,或者存在更好的替代方法
-
@SuppressWarnings - 定义在java.lang.SuppressWarnings,指示编译器去忽略注解中声明的警告,用来抑制编译时的警告信息
与前两个注释有所不同,你需要添加一个参数才能正确使用
<2>元注解
-
元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明
-
这些类型在 java.lang.annotation 包中,(@Target,@Retention,@Documented,@Inherited)
- @Target - 用于描述注解的适用范围(即:被描述的注解可以用在什么地方)
- Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问 用于描述注解的生命周期 (SOURCE < CLASS < RUNTIME)
- Documented - 说明该注解将包含在用户文档(javadoc)中
- Inherited - 说明子类可以继承父类中的该注解
<3>自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析:
- @interface来声明一个注解,格式:public @interface 注解名{定义内容}(类里的话把public去掉)
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型 Class,String,enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
反射概述
游戏在运行时,可以写一段代码注入进去,叫作hook 在运行时去改变代码里的数据 是反射的一种体现
<1>动态语言vs静态语言
动态语言
- 可以在运行时改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或者是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构
- 主要动态语言:object-c、C#、JavaScript、PHP、Python等
例如:php里可以eval()函数 将字符串当作代码执行
静态语言
- 与动态语言相对应,运行时结构不可变的语言就是静态语言。如:Java、C、C++
- Java不是动态语言,但Java可以称为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性,Java语言的动态性让编程的时候更加灵活
<2> Java Reflection概述
- Reflection(反射)是Java是为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.String")
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过对这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射
正常方式:引入需要的"包类"名称 --》 通过new例实话 --》取得实例化对象
反射方式:实例化对象 --》 getClass()方法 --》 得到Class对象
<3> Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时获取广泛信息
- 在运行时处理注解
- 生成动态代理 (机制 AOP会用到)
- ······
<4> Java反射优点与缺点
- 优点:可以实现动态创建对象与编译,体现出很强大的灵活性
- 缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求,这类操作总是慢于 直接执行相同的操作
<5> 反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
- ······
<6> Class类
在Object类中定义了以下的方法,此方法将被所有子类继承
public final Class getClass()
- 以上的方法返回值类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称
class类常用方法:
获取class类的实例
-
若已知具体的类,通过类的class属性获取,该方法最安全、可靠
Class clazz = Person.class;
-
若已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz = person.getClass();
-
已知一个类的全名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能会抛出ClassnotFoundException
Class clazz = Class.forName("demo.Student")
-
内置基本数据类型可以直接用 类名.Type
Integer.TYPE; int类
-
还可以用ClassLoader
在这三种获取CLass类方式中,我们一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。
而使用类.class属性,需要导入类的包,依赖性太强,在大型项目中容易抛出编译错误;
而使用实例化对象的getClass()方法,需要本身创建一个对象,本身就没有了使用反射机制意义。
所以我们在获取class对象中,一般使用Class.forName方法去获取。
哪些类型可以有Class对象?
- class:外部类、成员、局部内部类、匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
<7>类加载过程
(1) 类加载与ClassLoader理解
加载:将class文件字节码内容加载到内存中。将这些静态数据结构转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
链接:将java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全方面问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
初始化:
- 执行类构造器
()方法的过程。类构造器 ()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器) - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的
()方法在多线程环境中被正确加锁和同步
静态代码块和静态代码语句执行顺序取决于编写顺序
public class test05 {
public static void main(String[] args) {
A a = new A();
System.out.println(a.m);
/*
* 1. 加载到内存,会产生一个类对应Class对象
* 2. 链接,链接结束后m=0
* 3. 初始化
* <clinit>(){
* System.out.println("A类静态代码块初始化");
* m = 300;
* m = 100
* }
* 所以 m=100
*/
}
}
class A{
{
System.out.println("Empty block initial");
}
static {
System.out.println("A类静态代码块初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A类的无参构造初始化");
}
输出:
A类静态代码块初始化
Empty block initial
A类的无参构造初始化
首先调用的是 static{} 其次是 {} 然后是构造函数
其中, static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯,
但在当前构造函数内容的前⾯
(2) 类加载器作用
类加载器作用:
将class文件字节码内容加载到内存中,并将这些静态数据结构转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
类缓存:
标准的JavaSE类加载器可以按要求查找类,但一旦某一个类被加载到类加载器中,他将维持加载(缓存)一段时间,不过 JVM 垃圾回收机制可以回收这些Class对象
(3) ★获取运行时类的完整结构
通过反射获取运行时类的完整结构
Field、Method、Constructor、Superclass、Interface、Annotation
- 实现的全部接口
- 继承的父类
- 全部的构造器
- 全部方法
- 全部Field
- 注解
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class test08 {
public static void main(String[] args) throws ClassNotFoundException{
// Class c1 = Class.forName("com.lv.Reflection.User");
User user = new User();
Class c1 = user.getClass();
System.out.println(c1.getName()); //包名+类名
System.out.println(c1.getSimpleName());
//获得类的属性
System.out.println("==========================");
Field[] fields = c1.getFields(); //只能获取非私有
//有Declared字眼的都是获取本类所有任何访问级别的数据,没有的可以获取本类及父类所有公共级别的数据 即获取所有属性
fields = c1.getDeclaredFields();
for (Field field:fields){
System.out.println(field);
}
//获得类的方法
System.out.println("==========================");
Method[] methods = c1.getMethods(); //获得本类及其父类的所有public方法
for (Method method : methods) {
System.out.println("正常的"+method);
}
methods = c1. getDeclaredMethods(); //获取本类的所有方法(包括私有)
for (Method method : methods) {
System.out.println("getDeclaredmethods:"+method);
}
//获取指定的构造器
System.out.println("==========================");
Constructor[] constructors = c1.getConstructors(); //获取怕public方法
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = c1.getDeclaredConstructors(); //获取全部的
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
}
小结:
- 熟悉java. l an g. reflect包的作用,反射机制
- 如何获取属性、方法 、构造器名称、修饰符等
(4) ★有了Class对象,能做什么?
创建类的对象:调用Class对象的newInstance()方法
- 类必须有一个无参数的构造器
- 类的构造器的访问权限需要足够
思考: 难道没用无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后,才可以实例化操作。
步骤如下:
- 通过Class类的getDeclaredConstructor()取得本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
- 通过Constructor实例化对象
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//获得Class对象
Class c1=Class.forName("com.lv.Reflection.User");
User user = (User) c1.getDeclaredConstructor(String.class,int.class,int.class).newInstance("gaga",001,18); //jdk高版本 直接c1.newInstance()被弃用了
System.out.println(user);
//通过反射调用普通方法
User user2 = (User) c1.getDeclaredConstructor().newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke:激活的意思
//(对象,"方法的值")
setName.invoke(user2,"1vxyz");
System.out.println(user2.getName());
//通过反射操作属性
System.out.println("==========================");
User user3 = (User) c1.getDeclaredConstructor().newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全检测,通过属性或者方法的setAccessible(true)
name.setAccessible(true); //
name.set(user3,"1vxyz2");
System.out.println(user3.getName());
}
setAccessible
- Method和Field、Constructor对象都有setAccessible()方法
- setAccessible的作用是启动和禁用访问安全检查的开关
- 参数值为true则指示反射的代码在使用时应取消java语言的访问检查
- 如果代码中必须用反射,该句代码需要频繁的被调用,设置为true
- 使得原本无法访问的私有成员也可以被访问
- 参数值为false则指示反射的对象应该实施java语言访问检查
性能分析:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//性能对比分析
public class test10 {
//普通方式调用
public static void test01(){
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms");
}
//反射方式调用
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms");
}
//反射方式调用 关闭检测
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms");
}
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
test01();
test02();
test03();
}
}
学习来源:https://www.bilibili.com/video/BV1p4411P7V3/?spm_id_from=333.999.0.0