一、注解
1.1 概述
- 注解(
Annotaion
)是JDK5.0
开始引入的技术 - 可以用在 包、类、方法、属性等上面,相当于给它们添加了额外的信息。
- 可以通过反射机制实现对注解信息的访问
- 注解是所有框架的底层
- 注解是给程序看的,注释是给程序员看的
1.2 内置注解
-
在
java.lang
包下定义了3个注解-
@Override - 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
-
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
-
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。与前面两个注解有所不同,该注解需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好。
@SuppressWarnings(“all”)
@SuppressWarnings(“unchecked”)@SuppressWarnings(value={“unchecked”,“deprecation”})
-
1.3 自定义注解
-
自定义一个注解
@interface myAnnotation { String value(); }
-
使用
[权限修饰符] @interface
自定义一个注解,自动继承java.lang.annotaion.Annotaion
接口 -
String value();
表面上是个方法,实际上是生命了一个配置参数,在使用时需要传值。 -
可以通过
default
给参数进行默认赋值,在使用时如果没有默认值则必须进行传值。@interface myAnnotation { String value() default ""; }
-
只有一个成员时,一般参数名设置为
value
-
-
注解的使用和传值
-
定义如下注解:
@interface myAnnotation { int id(); String name(); String sex(); }
-
在一个类中使用注解
@ myAnnotation(id = 1001,name="tom",sex = "man") class UserInfo{ }
1. 使用注解时通过`key=value`的形式传值 1. 如果不指定`key`,则默认传递给注解中的`value`参数。
-
1.4 元注解
元注解就是作用在注解上的注解,定义在java.lang.annotaion
包下。
-
@Target
:用开说明注解的使用范围。-
源代码中的定义:
public @interface Target { /** * Returns an array of the kinds of elements an annotation interface * can be applied to. * @return an array of the kinds of elements an annotation interface * can be applied to */ ElementType[] value(); }
-
源码中的
ElementType
是一个枚举类型,定义在java.lang.annotation
中,此枚举类型的常量提供了注释可能出现在Java程序中的语法位置的简单分类。 这些常量用于Target
元注释,以指定写入给定类型注释的合法位置。public enum ElementType { TYPE, // 类、接口、枚举类 FIELD, // 成员变量(包括:枚举常量) METHOD, // 成员方法 PARAMETER, // 方法参数 CONSTRUCTOR, // 构造方法 LOCAL_VARIABLE, // 局部变量 ANNOTATION_TYPE, // 注解类 PACKAGE, // 可用于修饰:包 TYPE_PARAMETER, // 类型参数,JDK 1.8 新增 TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
-
使用
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE}) @interface myAnnotation { }
-
-
@Retention
:描述注解保留的时间范围,一共有三种策略,定义在RetentionPolicy枚举中:public enum RetentionPolicy { SOURCE, // 源文件保留 CLASS, // 编译期保留,默认值 RUNTIME // 运行期保留,可通过反射去获取注解信息 }
@Retention(RetentionPolicy.CLASS) public @interface ClassPolicy {
-
Documented
注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。 -
@Inherited
表示允许子类继承父类中的注解。
二、反射
2.1 概述
在程序运行中动态地获取类的相关属性,同时调用对象的方法和获取属性,这种机制被称之为Java反射机制。
- 实现原理
Java反射是Java实现动态语言的关键,也就是通过反射实现类动态加载
静态加载: 在编译时加载相关的类,如果找不到类就会报错,依赖性比较强
动态加载:在运行时加载需要的类,在项目跑起来之后,调用才会报错,降低了依赖性
例子:静态加载,如下代码,如果找不到类的情况,代码编译都不通过
User user = new User();
而动态加载,就是反射的情况,是可以先编译通过的,然后在调用代码时候,也就是运行时才会报错
Class<?> cls = Class.forName("com.example.core.example.reflection.User");Object obj = cls.newInstance();
java中的反射允许程序在执行期借助jdk中Reflection API来获取类的内部信息,比如成员变量、成员方法、构造方法等等,并能操作类的属性和方法
java中反射的实现和jvm和类加载机制有一定的关系,加载好类之后,在jvm的堆中会产生一个class类型的对象,这个class类包括了类的完整结构信息,通过这个class对象就可以获取到类的结构信息,所以形象地称之为java反射。
2.2 获取反射对象
-
定义一个实体类
public class User { private String name; private int age; private String sex; public User() { } public User(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}'; } }
-
通过反射获取类的
class
对象public static void main(String[] args) throws ClassNotFoundException { Class c1 = Class.forName("com.a001.User"); System.out.println(c1);//class com.a001.User }
-
一个类在内存中只有一个
Class
对象:Class c1 = Class.forName("com.a001.User"); Class c2 = Class.forName("com.a001.User"); System.out.println(c1 == c1);//true
-
类被加载后,;类的整个结果都在
Class
对象中。
-
-
Object
类中定义了getClass
方法,可以通过类的对象获取类的Class
对象。public static void main(String[] args) throws ClassNotFoundException { Class c1 = Class.forName("com.a001.User"); Class c2 = new User().getClass(); System.out.println(c1 == c2);//true }
-
每一个类都有
class
属性,通过类名.class
属性获得Class
的对象(比较高效)public static void main(String[] args) throws ClassNotFoundException { Class c1 = com.a001.User.class }
-
基本包装类型包装类都有一个
TYPE
属性public static void main(String[] args) throws ClassNotFoundException { Class c1 = Intger.TYPE }
2.3 Class
对象
-
Class
对象存储了一个类的相关信息 -
Class
本身也是个类,但对应的对象不是被new
出来的,而是系统创建的。 -
一个
Class
对象对应一个加载到JVM
中的一个.class
文件 -
每个类的实例都划记得自己由哪个
Class
实例生成的,可以通过getClass
方法生成。 -
Class
类中提供的方法 -
那些类型可以有
Class
对象- 类:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类
- 接口
- 枚举
- 注解
- 基本数据类型
- void
Class c1=Object.class; Class c2=Comparable.class; Class c3=int[][].class; Class c4= ElementType.class; Class c5=Target.class; Class c6=Integer.class; Class c7=void.class; Class c8=Class.class;
2.4 类的加载内存分析
-
类的加载大致过程
- 类的加载(Load):将
.class
文件读入内存,并将这件静态数据转换成方法区的运行时数据,并为之创建一个Class
对象代表这个类。 - 类的链接(Link):将类的二进制数据合并到
JRE
中。- 验证:确保类的加载信息是否符合规范,没有安全方面的问题
- 准备:正式为类变量(
static
)分配内存并设置初始值,这些内存在方法区中分配 - 解析:虚拟机中常量池中的符号引用(常量名)转化为直接引用(地址)的过程。(即把所有常量的引用替换成真实的数据)
- 类的初始化(Initialize):
JVM
负责对类进行初始化。- 执行类构造器
clinit()
方法的过程,其方法体是所有类中的static
成员合并而成的(相同的代码会被替换)。 - 若父类没有初始化,先触发父类的初始化。
- 虚拟机保证
clinit()
方法在多线程中被正确加锁和同步。
- 执行类构造器
- 类的加载(Load):将
-
类初始化的时机
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,会发生
main
方法所在的类 new
一个类的对象- 调用类的静态成员(除了
final
常量)和静态方法 - 使用
java.lang.reflect
包中的方法对类进行反射调用 - 初始化子类时,若父类没有初始化,先触发父类的初始化。
- 当虚拟机启动,会发生
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会初始化,如通过子类引用父类的静态变量,不会导致子类的初始化。
- 通过数组定义类引用,不会触发类的初始化。
- 引用常量不会触发此类的初始化(常量在链接阶段就存入常量池中了)
- 类的主动引用(一定会发生类的初始化)
-
类加载器
类加载器的作用:将
.class
文件读入内存,并将这件静态数据转换成方法区的运行时数据,并为之创建一个Class
对象代表这个类,==该对象作为作为访问方法区类数据的入口。==JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:-
根类加载器(bootstrap class loader):它用来加载 Java 的核心类,由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
在
lib
目录下找到rt.jar
,并用解压软件打开,里面java/long
目录下就是对应的java.long
包,java
目录下还能看到其他常用的类库(Math
,io
,time
)。根类加载器就是加载这些包的。 -
扩展类加载器(extensions class loader):它负责加载
JRE
的扩展目录,==lib/ext
==或者由java.ext.dirs
系统属性指定的目录中的JAR包
的类。由Java语言实现,父类加载器为null
。 -
系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的
-classpath
选项、java.class.path
系统属性,或者CLASSPATH
换将变量所指定的JAR包和类路径。程序可以通过ClassLoader
的静态方法getSystemClassLoader()
来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader
。
// 测试某个类是由哪个类加载器加载的 ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader1);//jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7 ClassLoader classLoader2 = Object.class.getClassLoader(); System.out.println(classLoader2);//null 因为无法直接获取
//获得系统加载类在那些路径加载类的 System.out.println(System.getProperty("java.class.path"));
-
2.5 获取运行时类的完整结构
- 定义一个类
class User {
private String name;
private int age;
private String sex;
public User() {}
public User(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}';
}
}
- 获取类对象
Class c1 = User.class;
- 获取类名
System.out.println(c1.getName()); // com.a001.User
System.out.println(c1.getSimpleName()); // User
- 获取属性
Field[] field = c1.getFields();
System.out.println(Arrays.toString(field));
Field[] declaredFields = c1.getDeclaredFields(); // []
System.out.println(Arrays.toString(declaredFields));
// [private java.lang.String com.a001.User.name, private int com.a001.User.age, private
// java.lang.String com.a001.User.sex]
Field name = c1.getDeclaredField("name");
System.out.println(name); // private java.lang.String com.a001.User.name
获取所有的public属性,包括继承过来的 | 获取在本类声明的所有属性 | |
---|---|---|
获取满足条件的所有属性 | getFields() | getDeclaredFields() |
根据属性名获取属性 | getField(String name) | getDeclaredField(String name) |
- 获取方法
Method[] methods = c1.getMethods();
System.out.println(Arrays.toString(methods));
// [public java.lang.String com.a001.User.toString(), public final void
// java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void
// java.lang.Object.wait() throws java.lang.InterruptedException, public final native void
// java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean
// java.lang.Object.equals(java.lang.Object), public native int java.lang.Object.hashCode(),
// public final native java.lang.Class java.lang.Object.getClass(), public final native void
// java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
Method[] declaredMethods = c1.getDeclaredMethods();
System.out.println(Arrays.toString(declaredMethods));
//[public java.lang.String com.a001.User.toString()]
获取所有的public方法,包括继承过来的 | 获取在本类声明的所有方法,包括重写的 | |
---|---|---|
获取满足条件的所有方法 | getMethods() | getDeclaredMethods() |
根据方法名获取方法 | getMethod(String name,*参数列表) | getDeclaredMethod(String name,*参数列表) |
注; 方法名+参数列表才能定位到具体的方法,尤其是对于重载的方法而言
public class Main {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
Class c1 = Test.class;
System.out.println(c1.getDeclaredMethod("func", int.class, String.class));
//public void com.a001.Test.func(int,java.lang.String)
}
}
class Test {
public void func(int a) {}
public void func(String b) {}
public void func(int a, String b) {}
}
- 获得构造器
获取在本类声明的所有的public构造器 | 获取在本类声明的所有构造器 | |
---|---|---|
获取满足条件的所有方法 | getMethods() | getDeclaredMethods() |
根据方法名获取方法 | getMethod(String name,*参数列表) | getDeclaredMethod(String name,*参数列表) |
Class userClass = User.class;
System.out.println(Arrays.toString(userClass.getConstructors()));
//[public com.a001.User(), public com.a001.User(java.lang.String,int,java.lang.String)]
System.out.println(Arrays.toString(userClass.getDeclaredConstructors()));
//[public com.a001.User(), public com.a001.User(java.lang.String,int,java.lang.String)]
System.out.println(userClass.getDeclaredConstructor(String.class, int.class, String.class));
//public com.a001.User(java.lang.String,int,java.lang.String)
2.6 通过反射创建一个对象
-
newInstance()
Class userClass = User.class; // 创建一个对象 User user1 = (User) userClass.newInstance(); System.out.println(user1);//User{name='null', age=0, sex='null'} /* 分析:1. newInstance通过调用无参构造器创建了对象 2. 该方法已经弃用 */
-
通过获得类的构造器来创建对象
Class userClass = User.class; // 获取指定的构造器 Constructor declaredConstructor = userClass.getDeclaredConstructor(String.class, int.class, String.class); //根据构造器创建对象 User user2 = (User) declaredConstructor.newInstance("Tom", 20, "man"); System.out.println(user2); //User{name='Tom', age=20, sex='man'}
2.7 通过反射调用对象的属性和方法
Class userClass = User.class;
User user = new User("tom", 12, "man");
// 获得对应的方法
Method toString = userClass.getDeclaredMethod("toString", null);
// 传入指定的对象和参数值(若有)调用方法 invoke(翻译:激活)
System.out.println(toString.invoke(user));
//User{name='tom', age=12, sex='man'}
Class userClass = User.class;
User user = new User("tom", 12, "man");
Field name = userClass.getDeclaredField("name");
name.setAccessible(true); // 关闭权限检测,可以对私有属性进行修改
name.set(user, "tom2");
System.out.println(name.get(user));
总结:
操作 | 方法 |
---|---|
通过构造器创建实例 | constructor.newInstance() |
调用方法 | method.invoke() |
获取、修改属性 | field.get() field.get() (对私有属性要关闭权限检查) |
2.8 获取范型信息
-
Java
中采用范型擦除机制,即范型仅在编译阶段使用,一旦编译完成后,所有和范型相关的类型全部擦除掉。 -
为了在反射中操作这些范型型,
java
新增了ParameterizedType
,GenericArrayType
、TypeVariable
和WildcardType
等操作范型的类:
public class Main {
public static void main(String[] args) throws NoSuchMethodException {
Class c1 = Main.class;
// 获得方法
Method test = c1.getMethod("Test", Map.class, List.class);
System.out.println(test);
// public static java.util.ArrayList com.a001.Main.Test(java.util.Map,java.util.List)
/*
分析:修饰符 public static
返回值: java.util.ArrayList
方法名 com.a001.Main.Test
参数列表 java.util.Map,java.util.List
*/
// 获得方法中参数列表中的范型信息
Type[] genericParameterTypes = test.getGenericParameterTypes();
System.out.println(Arrays.toString(genericParameterTypes));
// [java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.lang.Integer>]
// 获得具体的范型
for (Type genericParameterType : genericParameterTypes) {
// 判断获取的类型是不是参数化类型
if (genericParameterType instanceof ParameterizedType) {
// 强制转化成真实类型
Type[] actualTypeArguments =
((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
/*
class java.lang.String
class java.lang.String
class java.lang.Integer
*/
}
}
}
}
public static ArrayList<Character> Test(Map<String, String> map, List<Integer> list) {
return new ArrayList<Character>();
}
}
2.9 获取注解信息
-
定义注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TableUser { String value(); }
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface FiledUser { String colName(); String type(); int length(); }
-
定义类,并使用注解
@TableUser("db_user") public class User { @FiledUser(colName = "db_id",type = "int",length =4) private int id; @FiledUser(colName = "db_name",type = "varchar",length =3) private String name; @FiledUser(colName = "db_age",type = "int",length =2) private int age; public User() { } public User(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } }
-
获取注解信息
public class Main { public static void main(String[] args) throws NoSuchFieldException { Class classUser = com.a001.User.class; // 获得类上的注解 Annotation[] annotations = classUser.getAnnotations(); System.out.println(Arrays.toString(annotations)); // [@com.a001.TableUser("db_user")] // 获得指定的注解 TableUser annotation = (TableUser) classUser.getAnnotation(TableUser.class); // 获取注解中对应的valud值 System.out.println(annotation.value()); //db_user //获取属性上的注解 //1. 获取属性 Field id = classUser.getDeclaredField("id"); // 2. 根据属性获得对应的注解 FiledUser annotation1 = id.getAnnotation(FiledUser.class); System.out.println(annotation1.colName()); System.out.println(annotation1.type()); System.out.println(annotation1.length()); /* db_id int 4 */ } }