Java注解和反射笔记
Java注解和反射笔记
1 注解
1.1 定义
Annotation是从JDK1.5开始引入的技术
作用
- 不是程序本身,可以对程序作出解释
- 可以被其他程序(编译器等)读取
格式
- @注释名,可以添加一些数值
- 注解可以附加在package,class,method,field上面,可以通过反射机制实现对这些元数据的访问
1.2 内置注解
- @Override:定义在java.lang.Override中,只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
- @Deprecated:定义在java.lang.Deprecated中,可以修饰方法,属性,类,表示不建议使用这样的元素,有更好的选择
- @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息
1.3 元注解
元注解的作用是负责注解其他注解,Java定义了4个标注的meta-annotation类型
- @Target:用于描述注解的使用范围(类,方法,属性等)
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
- SOURCE < CLASS < RUNTIME
- @Documented:说明该注解将被包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
1.4 自定义注解
- 使用@interface自定义注解,自动继承java.lang.annotation.Annotation接口
- 其中的每个方法实际上是声明了配置参数,方法的名称就是参数的名称,返回值的类型就是参数的类型
- 用default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value,且定义为value后,使用时可以省略参数名value,直接写值
Copy@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
//注解的参数:参数类型+参数名()
String name() default ""; //默认为空
int age() default 0;
int id() default -1; //默认值为-1,代表不存在
String[] jobs();
}
2 静态语言和动态语言
- 动态语言
- 在运行时可以改变其结构,新的函数、对象、代码可以被引进,已有的函数可以被删除或是其他结构上的变化
- Object-C,C#,JavaScript,PHP,Python
- 静态语言
- 运行时结构不可改变
- C,C++,Java
Java不是动态语言,但是可以利用反射机制获得类似动态语言的特性
3 反射
3.1 概述
反射机制允许程序在执行期间借助于Reflection API获得任何类的内部信息,并能直接操作任意对象的内部属性及方法
CopyClass c = Class.forName("java.lang.String");
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,通过这个类可以看到类的结构
- 正常方式
- 引入需要的包类名称 ---> 通过new实例化 ---> 取得实例化对象
- 反射方式
- 实例化对象 ---> getClass()方法 ---> 得到完整的包类结构信息
3.2 Class类
对于每个类而言,jre都会为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构的信息
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成的
- Class类是Reflection的根源,针对任何想动态加载、运行的类,只有先获得相应的Class对象
3.3 获得反射对象
首先,哪些类型有Class对象
- class:外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接口
- []:数组
- 只要元素类型和维度一样,就是同一个Class,不管数据长度
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
获得Class对象的方法
已知具体的类,通过类的class属性获取,这种方法最安全快速
CopyClass clazz = Person.class;
已知某个类的实例,调用该实例的getClass()方法获取Class对象
CopyClass clazz = persion.getClass();
已知类的全类名,通过Class类的静态方法forName()获取,可能抛出异常
CopyClass clazz = Class.forName("com.hjc.pojo.User");
内置基本数据类型可以直接用类名.TYPE
利用ClassLoader
Copypublic class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println(person.name);
//通过对象获得
Class c1 = person.getClass();
//通过forName获得
Class c2 = Class.forName("com.hjc.reflection.Student");
//通过类名.class获得
Class c3 = Student.class;
//基本数据类型
Class c4 = Integer.TYPE;
//获得父类
Class c5 = c1.getSuperClass();
}
}
class Person {
String name;
//省略构造函数和set/get方法
}
class Student extends Person {
public Student() {
this.name = "student";
}
}
class Teacher extends Person {
public Teacher() {
this.name = "teacher";
}
}
3.4 类加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过三个步骤对该类进行初始化
- 加载
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,生成一个代表这个类的java.lang.Class对象
- 链接
- 将Java类的二进制代码合并到JVM的运行状态之中
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值,这些内存都在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)
- 将Java类的二进制代码合并到JVM的运行状态之中
- 初始化
- 执行类构造器()方法的过程
- 当初始化一个类的时候,如果发现父类还没有初始化,则要先触发父类的初始化
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
3.5 类初始化
什么时候会发生类初始化
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则会先初始化其父类
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化
- 比如,通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化
- 常量在链接阶段就存入调用类的常量池中了
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化
Copypublic class Test {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//主动引用,会初始化类
B b = new B();
//反射,会初始化类
Class.forName("com.hjc.reflection.B");
//B不会被加载,会加载A
System.out.println(B.n);
//B不被加载
B[] array = new B[10];
System.out.println(B.M);
}
}
class A {
static {
System.out.println("父类被加载");
}
static int n = 2;
}
classs B extends A {
static {
System.out.println("子类被加载");
m = 300;
}
static int m = 100;
static final int M = 1;
}
3.6 类加载器
类加载器的作用
- 将class文件字节码内容加载到内存中,并将这些静态数据转换程方法区的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
类缓存
- 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,但JVM垃圾回收机制可以回收这些Class对象
类加载器
- 引导类加载器
- 用C++编写,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库,该加载器无法直接获取
- 扩展类加载器
- 负责jre/lib/ext目录下的jar包装入工作库
- 系统类加载器
- 负责java -classpath下的jar包装入工作库,是最常用的加载器
Copypublic class Test {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类,扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类,引导类加载器,无法获取
parent = parent.getParent();
System.out.println(parent);
//测试当前类是由哪个类加载器加载的
ClassLoader classLoader = Class.forName("com.hjc.reflection.Test").getClassLoader();
System.out.println(ClassLoader);
//测试jdk内置的类
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(ClassLoader);
}
}
3.7 获取运行时类的完整结构
获得了类的Class对象,那么我们就可以得到类的运行时信息
Copypublic class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c = Class.forName("com.hjc.reflection.User");
//获得类的名字
System.out.println(c.getName()); //包名+类名
System.out.println(c.getSimpleName()); //类名
//获得类的属性
Field[] fields = c.getFields(); //只能得到public属性
for (Field field : fields) {
System.out.println(field);
}
fields = c.getDeclaredFields(); //可以得到全部属性
for (Field field : fields) {
System.out.println(field);
}
//获得指定属性
Field name = c.getField("name"); //找不到,因为name是private
System.out.println(name);
Field name = c.getDeclaredField("name"); //可以找到
System.out.println(name);
//获得类的方法
Method[] methods = c.getMethods(); //获得本类及其父类的全部public方法
for (Method method : methods) {
System.out.println(method);
}
methods = c.getDeclaredMethods(); //获得本类的全部方法
for (Method method : methods) {
System.out.println(method);
}
//获得类的指定方法
Method getName = c.getMethod("getName", null); //方法名+参数
Method setName = c.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
//获得类的构造器
Constructor[] constructors = c.getConstructors(); //获得public
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = c.getDeclaredConstructors(); //获得全部
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
//获得指定构造器
Constructor declaredConstructor = c.getDeclaredConstructor(int.class, String.class, int.class);
System.out.println(declaredConstructor);
}
}
class User {
private int id;
private String name;
private int age;
//省略构造方法和set/get方法
}
3.8 动态创建对象执行方法
Copypublic class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.hjc.reflection.User");
//构造一个对象
User user = (User) c.newInstance(); //本质上调用了类的无参构造器
System.out.println(user);
//如果没有无参构造器,通过有参构造器创建对象
Constructor constructor = c.getDeclaredConstructor(int.class, String.class, int.class);
User user = (User) constructor.newInstance(1, "test", 18);
System.out.println(user);
//反射调用方法
Method setName = c.getDeclaredMethod("setName", String.class);
setName.invoke(user, "test1"); //invoke传递对象和方法参数值
System.out.println(user);
//反射操作属性
Field name = c.getDeclaredField("name");
//不能直接操作私有属性,需要关闭程序的安全检测
//调用属性或者方法的setAccessible(true)
name.setAccessible(true);
name.set(user, "test2");
System.out.println(user);
}
}
3.9 性能分析
Copypublic class Test {
//普通方式
public void test1() {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式执行十亿次:" + (endTime - startTime) + "ms");
}
//反射方式
public void test2() throws NoSuchMethodException {
User user = new User();
Class c = user.getClass();
Method getName = c.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("反射方式执行十亿次:" + (endTime - startTime) + "ms");
}
//反射方式,关闭检测
public void test3() throws NoSuchMethodException {
User user = new User();
Class c = user.getClass();
Method getName = c.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("关闭检测后,反射方式执行十亿次:" + (endTime - startTime) + "ms");
}
public static void main(String[] args) {
test1();
test2();
test3();
}
}
3.10 反射操作泛型
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是一旦编译完成,所有和泛型有关的类型全部擦除
为了通过反射操作泛型,Java引入了几个类
- ParameterizedType:表示一种参数化类型,如Collection
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable:各种类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式
Copypublic class Test {
public void test1(Map<String, User> map, List<User> list) {
System.out.println("test1");
}
public Map<String, User> test2() {
System.out.println("test2");
return null;
}
public static void main(String[] args) {
Method method = Test.class.getMethod("test1", Map.class, List.class);
Type[] types = method.getGenericParameterTypes();
for (Type type : types) {
System.out.println(type);
if (type instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println(actualType);
}
}
}
method = Test.class.getMethod("test2", null);
Type returnType = method.getGenericReturnType();
System.out.println(resultType);
if (returnType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println(actualType);
}
}
}
}
3.11 反射操作注解
很多框架都是通过反射获取注解信息,来帮我们解决了很多事情
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.hjc.reflection.Student");
//通过反射获得注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的值
Table1 table1 = (Table1) c.getAnnotation(Table1.class);
String value = table1.value();
System.out.println(value);
//获得指定的注解
Field f = c.getDeclaredField("name");
Field1 f1 = f.getAnnotation(Field1.class);
System.out.println(f1.columnName());
System.out.println(f1.type());
System.out.println(f1.length());
}
}
@Table1("db_student")
class Student {
@Field1(columnName = "db_id", type = "int", length = 10)
private int id;
@Field1(columnName = "db_name", type = "varchar", length = 3)
private String name;
@Field1(columnName = "db_age", type = "int", length = 10)
private int age;
//省略构造器和set/get方法
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table1 {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field1 {
String columnName();
String type();
int length();
}
Java注解和反射笔记的更多相关文章
- Java注解与反射
概要 本文主要是总结Java注解与反射的相关知识,加深自己对Java类动态语言的理解,同时为日后学习Spring打下基础. 注解: 什么是注解 Annotation的作用 不是程序本身,但是可以对程序 ...
- 小白都能学会的Java注解与反射机制
前言 Java注解和反射是很基础的Java知识了,为何还要讲它呢?因为我在面试应聘者的过程中,发现不少面试者很少使用过注解和反射,甚至有人只能说出@Override这一个注解.我建议大家还是尽量能在开 ...
- Java 注解(Annoation)学习笔记
1 Junit中的@Test为例: 1.1 用注解(@Test)前 private boolean isTestMethod(Method m) { return m.getParameterType ...
- Java注解和反射
1.注解(Annotation) 1.1.什么是注解(Annotation) 注解不是程序本身,可以在程序编译.类加载和运行时被读取,并执行相应的处理.注解的格式为"@注释名(参数值)&qu ...
- java注解实例-反射生成sql
定义描述用户表的注解: package dao; import java.lang.annotation.ElementType; import java.lang.annotation.Retent ...
- Java注解Annotation学习笔记
一.自定义注解 1. 使用关键字 @interface 2. 默认情况下,注解可以修饰 类.方法.接口等. 3. 如下为一个基本的注解例子: //注解中的成员变量非常像定义接口 public @int ...
- JAVA 注解和反射
通过反射来获取类 Class MyTest{ private String name; public String showName{ System.out.println(this.name); } ...
- java注解和反射学习
spring框架很多地方都应用了注解,如@controller,所以要学会自定义注解及注解处理器. Class<?> cl=Class.froName(className) //通过类名加 ...
- java自定义注解与反射
java注解与反射一.Java中提供了四种元注解,专门负责注解其他的注解,分别如下 1.@Retention元注解,表示需要在什么级别保存该注释信息(生命周期).可选的RetentionPoicy参数 ...
随机推荐
- openstack中Cinder组件简解
一,Cinder组件介绍 概念 cinder组件作用: 块存储服务,为运行实例提供稳定的数据块存储服务 块存储服务,提供对 volume 从创建到删除整个生命周期的管理 二,常用操作 1.Volume ...
- KingbaseES R3 集群cluster日志切割和清理案例
案例说明: 对于KingbaseES R3集群的cluster日志默认系统是不做切割和清理的,随着运行时长的增加,日志将增长为一个非常大的文件,占用比较大的磁盘空间,并且在分析问题读取大文件时效率很低 ...
- torch.sort 和 torch.argsort
定义 torch.sort(input,dim,descending) torch.argsort(input,dim,descending) 用法 torch.sort:对输入数据排序,返回两个值, ...
- Java八股文纯享版——篇②:并发编程
注: 1.笔记为个人归纳整理,尽力保证准确性,如有错误,恳请指正 2.写文不易,转载请注明出处 3.本文首发地址 https://blog.leapmie.com/archives/c02a6ed1/ ...
- Erda 开源的迷失和反思
前言 Erda 是我从2018年初加入上家公司直到今年初离开的四年时间里一直在做的一个云原生 PaaS 平台.在开源之前,Erda 在公司内部的名字代号是 D ,在21年初改名为现在的 Erda 进行 ...
- Stream流式计算
Stream流式计算 集合/数据库用来进行数据的存储 而计算则交给流 /** * 现有5个用户,用一行代码 ,一分钟按以下条件筛选出指定用户 *1.ID必须是偶数 *2.年龄必须大于22 *3.用户名 ...
- 在k8s集群中安装traefik,并结合kuboard界面使用
安装traefik 参考步骤:https://blog.51cto.com/u_13760351/2764008?xiangguantuijian&01 修改好的四个yaml文件下载地址:ht ...
- centos7使用yum方式安装redis6
yum -y install epel-release wget make gcc-c++ cd /opt wget https://download.redis.io/releases/redis- ...
- jenkins邮箱配置
- k8s使用心得
查看当前所有namespaces [root@master ~]# kubectl get namespaces -A NAME STATUS AGE default Active 63d hkd A ...