Java学习笔记之注解和反射
1 注解
1.1 什么是注解
- 是从
jdk 5.0
开始引入的新技术 - 注解的作用
- 不是程序本身,可以对程序做出解释
- 可以被其他程序调用
2 内置注解
-
Oveiride
:定义在java.lang.Override
中,此方法只适用于修辞方法,表示一个方法打算重写超类中的一个方法声明 -
Deprecated
:定义在java.lang.Deprecated
中,次注解可以用于修辞方法、属性、类,表示不推荐使用的元素 -
SuppressWarnings
:定义在java.lang.SuppressWarnings
中,用来抑制编译时的警告信息,镇压警告
3 元注解
元注解的作用是用来负责注解其他注解,Java
定义了 4
个标准的 meta-annotation
类型
-
@Target
:用于描述注解的使用范围 -
@Retention
:表示需要在什么级别上保存该注释信息,用于描述注解的生命周期 -
@Documented
:说明该注解被包含在javadoc
中 -
@Inherited
:说明子类可以继承父类中的该注解
@MyAnnotation
public class TargetAnnotation {
@MyAnnotation
public static void main(String[] args) {
}
}
//表示我们的主机可以用在哪些地方
@Target(value = {ElementType.METHOD, ElementType.TYPE})
//定义一个注解
//Retention 表示注解在什么地方还有效
@Retention(value = RetentionPolicy.RUNTIME)
//Documented 表示是否将我们的注解生成在 Javadoc 中
@Documented
//Inherited 表示子类可以继承父类的注解
@Inherited
@interface MyAnnotation {
}
4 自定义注解
使用 @interface
自定义注解时,自动继承了 java.lang.annotation.Annotation
接口
-
@interface
用来声明一个注解 -
其中的每一个方法实际上是声明了一个参数
-
方法的名称就是参数的名称
-
返回值类型就是参数的类型,返回值只能是基本类型
-
可以用
default
来声明参数的默认值 -
如果只有一个参数成员,一般为
value
-
注解元素必须要有值,自定义元素注解时,经常使用空字符串、
0
作为默认值
public class AnnTest {
@MyAnnotation01
private void Test01(){}
@MyAnnotation01(age = 20, name = "荒天帝")
private void Test02(){}
//只有一个参数,且名称为 value 时,才可以省略 value =
@MyAnnotation02({1,2,3})
private void Test03(){}
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation01 {
String name() default "荒";
int age() default 0;
String[] list() default {"1", "2", "3"};
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface MyAnnotation02 {
int[] value();
}
2 反射
2.1 什么是反射
反射(Reflection
)是 Java
被视为准动态语言的关键,反射机制允许程序在执行期间借助于反射接口取得任何类的内部信息,并且能够直接操作任意对象的内部属性及方法
2.2 Class 类
public class ClassInfo {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> c1 = Class.forName("Reflection.ClassInfo");
Class<?> c2 = Class.forName("Reflection.ClassInfo");
System.out.println(c1.hashCode() == c2.hashCode()); //true -> 是同一个类
}
}
class User {
private String name;
private int age;
private int id;
public User() {
}
public User(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
2.3 类的加载机制
-
加载:将
class
字节文件码内容加载到内存中,并将这些静态数据转换成方法区的运行时的数据结构,然后生成一个代表这个类的Class
对象 -
链接 :将
Java
类的二进制代码合并到JVM
的运行状态之中的过程-
验证:确保加载的类的信息符合
JVM
的规范,没有安全问题 -
准备:正式为类变量(
static
)分配并设置类变量默认初始的阶段,这些内存都将在方法区中进行分配 -
解析:虚拟机常量池中的符号引用(常量名)替换为直接引用(地址)的过程
-
-
初始化
-
执行类构造器
<clinit>()
方法 -
初始化一个类的时候,如果发现父类还没有进行初始化,则需要先触发父类的初始化
-
虚拟机会保证一个类的
<clinit>()
方法在多个线程环境中被正确加锁和同步
-
public class ClassInfo2 {
public static void main(String[] args) {
System.out.println(new Test().m);
}
}
class Test {
static {
System.out.println("Test 的静态代码块执行");
m = 100;
}
static int m = 300;
Test() {
System.out.println("Test 类无参构造执行");
}
}
/*
1 类加载到内存,会形成一个对应的 Class 对象
2 链接,链接结束后 m = 300
3 初始化,JVM 执行 <clinit>() {
System.out.println("Test 的静态代码块执行");
m = 100;
m = 300;
}
*/
2.4 类的初始化
- 类的主动引用(一定会发生类的初始化)
-
当虚拟机启动,先初始化
main
方法所在的类 -
new
一个类的对象 -
调用类的静态成员(除过
final
常量)和静态方法 -
使用
java.lang.reflect
包的方法对类进行反射调用 -
当初始化一个类时,若其父类未初始化,则会先初始化他的父类
-
- 类的被动引用(不会发生类的初始化)
-
当访问一个静态域时,只有真正声明这个域的类才会被初始化(通过一个子类引用父类的静态变量,不会导致子类初初始化)
-
通过数组定义类引用,不会触发此类的初始化
-
引用常量不会触发类的初始化(常量在链接阶段就已经存入调用类的常量池中去了)
-
2.5 类加载器
-
类加载器作用:将
class
字节码文件加载到内存中,并将这些静态数据转换成方法区的运行数据结构,然后在堆中生成一个代表这个类的Class
对象,作为方法区中类数据的访问入口 -
类缓存:类加载器可以按照要求加载类,一旦某个类别加载到类加载器中,它将维持加载一段时间。
JVM
中的垃圾回收机制可以回收这些Class
对象 -
JVM
的基本加载器-
引导类加载器:用
C++
编写,是JVM
自带的类加载器,负责Java
平台核心库,用来装载核心类库,该加载器无法直接获取 -
扩展类加载器:负责
jre/lib/ext
目录下的jar
包或者-D java.ext.dirs
指定目录下的jar
包装入工作库 -
系统类加载器:负责
java -classpath
或-D java.class.path
所指向的目录下的类与jar
包装入工作,是最常用的加载器
-
public class LoaderInfo {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器 sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器 sun.misc.Launcher$ExtClassLoader@677327b6
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器 null 由 C++ 编写,Java 无法读取
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//用户自定义的类由哪个类加载器加载 sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader classLoader = Class.forName("Reflection.LoaderInfo").getClassLoader();
System.out.println(classLoader);
//jdk 的内置类由哪个类加载器加载 null
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
//获得系统类加载器可以加载的路径
String[] classPath = System.getProperty("java.class.path").split(";");
for (String path : classPath) System.out.println(path);
}
}
2.6 创建运行时类的对象
2.6.1 获取运行时类的完整结构
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> aClass = Class.forName("Reflection.User");
//获得类名
System.out.println("getName : " + aClass.getName());
//获得简单类名
System.out.println("getSimpleName : " + aClass.getSimpleName());
System.out.println("--------------------");
//获得类的 public 属性
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println("getFields : " + field);
}
//获得类的全部属性
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("getDeclaredFields : " + declaredField);
}
//获得指定属性的值
Field name = aClass.getDeclaredField("name");
System.out.println("getDeclaredField[name] : " + name);
System.out.println("--------------------");
//获得类的方法
//获得本类以及其父类的所有方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println("getMethods : " + method);
}
//只获得本类的所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("getDeclaredMethods : " + declaredMethod);
}
System.out.println("--------------------");
//获得指定的方法
Method getName = aClass.getMethod("getName", null);
Method setName = aClass.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
System.out.println("--------------------");
//获得构造器
Constructor<?>[] constructors = aClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("constructor : " + constructor);
}
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("declaredConstructor : " + declaredConstructor);
}
System.out.println("--------------------");
//获得指定构造器
Constructor<?> constructor = aClass.getConstructor(String.class, int.class, int.class);
System.out.println("constructor[String, int, int] : " + constructor);
}
}
2.6.2 使用反射操作对象
1 创建类的对象:调用 Class
对象的 newInstance()
方法
-
类必须有一个无参构造器
-
类的构造器访问权限要足够
-
若类没有无参构造,那么在操作的时候要明确的调用类的构造器,并且将参数传递进去后,才可实例化对象
-
通过
Class
类的getDeclaredConstructor(Class...parameterTypes)
获取本类的指定有参构造器 -
向构造器中传递一个对象数组进去,包含了构造器所需要的各种参数
-
通过
Constructor
实例化对象
-
2 调用指定的方法
-
通过
Class
类的getDeclaredMethod(String name, Class...parameterTypes)
获取一个Method
对象,并设置此方法操作时所需的参数类型 -
使用
Object invoke(Object obj, Object[] args)
进行调用,并向方法中传递需要设置的obj
对象的参数信息-
Object
对应原方法的返回值,默认为null
-
若原方法为静态方法,此时形参
Object obj
可以为null
-
若原方法形参列表为空,则
Object[] args
可以为null
-
若原方法声明为
private
,则需要在调用invoke()
方法之前,显式调用方法对象的setAccseeible(true)
方法,关闭Java
的安全访问检测, 然后才可以访问private
方法
-
public class Test02 {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("Reflection.User");
//构造一个对象
User user1 = (User) aClass.newInstance(); //本质是调用无参构造,若没有无参构造会报错
System.out.println(user1);
//通过构造器创建对象
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User) declaredConstructor.newInstance("荒天帝", 1, 18);
System.out.println(user2);
//通过反射调用方法
User user3 = (User) aClass.newInstance();
Method setName = aClass.getDeclaredMethod("setName", String.class);
setName.invoke(user3, "荒");
System.out.println(user3.getName());
//通过反射操作属性
User user4 = (User) aClass.newInstance();
Field name = aClass.getDeclaredField("name");
//不能直接操作私有属性,需要关掉程序的安全性检测
name.setAccessible(true);
name.set(user4, "叶凡");
System.out.println(user4.getName());
}
}
2.7 运行性能分析
-
Method
、Field
、Constructor
都有setAccseeible()
方法 -
setAccessible
作用是启动和禁止访问安全检查的开关 -
参数为
true
,则提示反射对象在使用时应该取消Java
的访问检查-
如果代码中必须用到反射,且该句代码需要频繁的被调用,就可以设置为
true
-
使得原本无法访问的私有成员也可以访问
-
-
参数为
false
,则提示反射对象在使用时应该执行Java
的访问检查
public class Test03 {
//普通方式调用
public static void test01() {
User user1 = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10_0000_0000; i++) {
user1.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式运行10亿次耗时 " + (endTime - startTime) + " ms");
}
//反射方式调用,不关闭安全检测
public static void test02() throws Exception {
User user2 = new User();
Class<? extends User> aClass = user2.getClass();
Method getName = aClass.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10_0000_0000; i++) {
getName.invoke(user2, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式运行10亿次耗时 " + (endTime - startTime) + " ms");
}
//反射方式调用,关闭安全检测
public static void test03() throws Exception {
User user3 = new User();
Class<? extends User> aClass = user3.getClass();
Method getName = aClass.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10_0000_0000; i++) {
getName.invoke(user3, null);
}
long endTime = System.currentTimeMillis();
System.out.println("关闭检测运行10亿次耗时 " + (endTime - startTime) + " ms");
}
public static void main(String[] args) throws Exception {
test01();
test02();
test03();
}
}
//普通方式运行10亿次耗时 4 ms
//反射方式运行10亿次耗时 1891 ms
//关闭检测运行10亿次耗时 925 ms
2.8 反射操作泛型
-
Java
采用 泛型擦除机制来引入泛型,Java
中的泛型仅仅是给编译器javac
使用的,确保数据的安全性和强制类型转换问题,一旦编译结束,所有和泛型有关的类型会被全部擦除 -
ParameterizedType
:表示一种参数化类型,比如Collection<String>
-
GenericArrayType
:表示一种元素类型是参数化类型或者类型变量的数组类型 -
TypeVariable
:是各种类型公共变量的父借口 -
WildcardType
:代表一种通配符类型的表达式
public class Test04 {
public void test01(Map<String, User> map, List<User> list){
System.out.println("test01");
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test04.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
if (genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
}
2.9 使用反射操作注解
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> aClass = Class.forName("Reflection.Person");
//通过反射获取注解
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//通过注解获取 value 值
TableName tableName = (TableName) aClass.getAnnotation(TableName.class);
String value = tableName.value();
System.out.println(value);
//获得指定注解
Field name = aClass.getDeclaredField("name");
FieldName annotation = name.getAnnotation(FieldName.class);
System.out.println(annotation.columnName());
System.out.println(annotation.length());
System.out.println(annotation.type());
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableName {
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldName {
String columnName();
String type();
int length();
}
@TableName(value = "t_person")
class Person {
@FieldName(columnName = "t_id", type = "int", length = 10)
private int id;
@FieldName(columnName = "t_age", type = "int", length = 10)
private int age;
@FieldName(columnName = "t_name", type = "varchar", length = 3)
private String name;
public Person() {
}
public Person(int id, int age, String name) {
this.id = id;
this.age = age;
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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}