聆听 沉淀 传播… 关注微信公众号【架构技术之美】,学习更多好文于学习资料
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
注解以
@注解名
的形式存在于代码中,例如@Override
,还可以添加一些参数值,例如@Auth(value = "super")
。
Java 有10个内置 注解,6个注解是作用在代码上的,4个注解是负责注解其他注解的(即元注解),元注解提供对其他注解的类型说明。
注解 | 作用 | 作用范围 |
---|---|---|
@Override | 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。 | 作用在代码上 |
@Deprecated | 标记表示过时的,不推荐使用。可以用于修饰方法,属性,类。如果使用被此注解修饰的方法,属性或类,会报编译警告。 | 作用在代码上 |
@SuppressWarnings | 指示编译器去忽略注解中声明的警告。 | 作用在代码上 |
@SafeVarargs | Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。 | 作用在代码上 |
@FunctionalInterface | Java 8 开始支持,标识一个匿名函数或函数式接口。 | 作用在代码上 |
@Repeatable | Java 8 开始支持,标识某注解可以在同一个声明上使用多次。 | 作用在代码上 |
@Retention | 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。包含关系runtime>class>source。 | 作用在其他注解上,即元注解 |
@Documented | 标记这些注解是否包含在用户文档中。 | 作用在其他注解上,即元注解 |
@Target | 标记某个注解的使用范围,例如作用方法上,类上,属性上等等。 | 作用在其他注解上,即元注解 |
@Inherited | 说明子类可以集成父类中的此注解,默认注解并没有继承于任何子类 | 作用在其他注解上,即元注解 |
使用@interface在这里插入代码片
定义注解,而且自动继承java.lang.annotation.Annotation
接口。
import java.lang.annotation.*;
/**
* @author Mr.nobody
* @Description 自定义注解
* @date 2020/8/30
*/
@Target(ElementType.METHOD) // 此注解只能用在方法上。
@Retention(RetentionPolicy.RUNTIME) // 此注解保存在运行时,可以通过反射访问。
@Inherited // 说明子类可以集成父类中的此注解。
@Documented // 此注解包含在用户文档中。
public @interface CustomAnnotation {
String value(); // 使用时需要显示赋值
int id() default 0; // 有默认值,使用时可以不赋值
}
/**
* @author Mr.nobody
* @Description 测试注解
* @date 2020/8/30
*/
public class TestAnnotation {
// @CustomAnnotation(value = "test") 只能注解在方法上,这里会报错
private String str = "Hello World!";
@CustomAnnotation(value = "test")
public static void main(String[] args) {
System.out.println(str);
}
}
讲解反射前,我们先来谈谈静态语言和动态语言。
动态语言是一类在运行时可以改变其结构的语言。例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或者结构上的一些变化。简单说即是在运行时代码可以根据某些条件改变自身结构。动态语言主要C#,Object-C,JavaScript,PHP,Python等。
静态语言是运行时结构不可改变的,例如Java,C,C++等。
Java不是动态语言,但是她可以称为准动态语言,因为Java可以利用反射机制获得类似动态语言的特性,Java的动态性让它在编程时更加灵活。
反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性以及方法等。
类在被加载完之后,会在堆内存的方法区中生成一个Class类型的对象,一个类只有一个Class对象,这个对象包含了类的结构信息。我们可以通过这个对象看到类的结构。
例如可以通过如下方法获取String类的Class对象:Class c = Class.forName("java.lang.String");
当然,每个类都隐式继承Object类,Object类有个getClass()
方法也能获取Class对象。
通过Class对象可以得知某个类的属性,方法,构造器,注解,以及实现了哪些接口等等信息。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定的结构(class,interface,enum,annotation,private type,void,[])的有关信息。
Class类的常用方法:
获取运行时类的完整结构
通过反射可以获取运行时类的完整结构:
调用指定的方法:Object invoke(Object obj, Object ... args)
package com.nobody;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/4
*/
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class<?> aClass = Class.forName("com.nobody.Student");
System.out.println(aClass.getName());
System.out.println(aClass.getSimpleName());
System.out.println("-----------------------");
System.out.println("获取public的属性");
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("-----------------------");
System.out.println("获取全部的属性");
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("-----------------------");
System.out.println("获取指定名称的属性");
Field name = aClass.getDeclaredField("name");
System.out.println(name);
System.out.println("-----------------------");
System.out.println("获取本类和父类的全部public方法");
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("-----------------------");
System.out.println("获取本类的方法");
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
System.out.println("-----------------------");
System.out.println("获得指定名字的方法");
Method getName = aClass.getDeclaredMethod("getName");
System.out.println(getName);
Method setName = aClass.getDeclaredMethod("setName", String.class);
System.out.println(setName);
System.out.println("-----------------------");
System.out.println("获得构造器");
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
System.out.println("-----------------------");
System.out.println("获取指定的构造器");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class);
System.out.println(declaredConstructor);
System.out.println("-----------------------");
System.out.println("生成类的实例");
Student student = (Student) aClass.newInstance();
Student student1 = (Student) declaredConstructor.newInstance("小明", 20);
System.out.println(student);
System.out.println(student1);
System.out.println("-----------------------");
System.out.println("调用实例的方法");
setName.invoke(student, "小花");
System.out.println(student);
System.out.println("-----------------------");
System.out.println("使用实例的属性");
name.setAccessible(true); // 因为student的name变量是私有的,所以要加此行代码,关闭安全检测
name.set(student, "小红");
System.out.println(student);
}
}
class Student {
private String name;
public int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private void testMethod01() {
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
输出结果:
com.nobody.Student
Student
-----------------------
获取public的属性
public int com.nobody.Student.age
-----------------------
获取全部的属性
private java.lang.String com.nobody.Student.name
public int com.nobody.Student.age
-----------------------
获取指定名称的属性
private java.lang.String com.nobody.Student.name
-----------------------
获取本类和父类的全部public方法
public java.lang.String com.nobody.Student.toString()
public java.lang.String com.nobody.Student.getName()
public void com.nobody.Student.setName(java.lang.String)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) 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()
-----------------------
获取本类的方法
public java.lang.String com.nobody.Student.toString()
public java.lang.String com.nobody.Student.getName()
public void com.nobody.Student.setName(java.lang.String)
private void com.nobody.Student.testMethod01()
-----------------------
获得指定名字的方法
public java.lang.String com.nobody.Student.getName()
public void com.nobody.Student.setName(java.lang.String)
-----------------------
获得构造器
public com.nobody.Student()
public com.nobody.Student(java.lang.String,int)
-----------------------
获取指定的构造器
public com.nobody.Student(java.lang.String,int)
-----------------------
生成类的实例
Student{
name='null', age=0}
Student{
name='小明', age=20}
-----------------------
调用实例的方法
Student{
name='小花', age=0}
-----------------------
使用实例的属性
Student{
name='小红', age=0}
获取Class类的实例
Class clazz = User.class;
Class clazz = user.getClass();
Class clazz = Class.forName("com.nobody.User");
package com.nobody;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/2
*/
public class User extends Person {
private String name;
public static void main(String[] args) throws ClassNotFoundException {
User user = new User();
Class<User> userClass = User.class;
Class<? extends User> aClass = user.getClass();
Class<?> aClass1 = Class.forName("com.nobody.User");
System.out.println(userClass.hashCode());
System.out.println(aClass.hashCode());
System.out.println(aClass1.hashCode());
Class<? super User> superclass = userClass.getSuperclass();
System.out.println(superclass);
Class<Integer> type = Integer.TYPE;
System.out.println(type);
}
}
class Person {
}
输出结果:
685325104
685325104
685325104
class com.nobody.Person
int
有Class对象的类型
package com.nobody;
import java.lang.annotation.Documented;
import java.util.List;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/2
*/
public class ClassTest {
public static void main(String[] args) {
Class<Object> objectClass = Object.class;
Class<List> listClass = List.class;
Class<String[]> aClass = String[].class;
Class<String[][]> aClass1 = String[][].class;
Class<Override> overrideClass = Override.class;
Class<Documented> documentedClass = Documented.class;
Class<Integer> integerClass = Integer.class;
Class<Void> voidClass = void.class;
Class<Class> classClass = Class.class;
System.out.println(objectClass);
System.out.println(listClass);
System.out.println(aClass);
System.out.println(aClass1);
System.out.println(overrideClass);
System.out.println(documentedClass);
System.out.println(integerClass);
System.out.println(voidClass);
System.out.println(classClass);
}
}
输出结果:
class java.lang.Object
interface java.util.List
class [Ljava.lang.String;
class [[Ljava.lang.String;
interface java.lang.Override
interface java.lang.annotation.Documented
class java.lang.Integer
void
class java.lang.Class
当程序主动使用某个类时,如果此类还未被加载到内存中,则系统会通过以下三个步骤来对此类进行初始化。
()
方法的过程,类构造器package com.nobody;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/3
*/
public class TestA {
public static void main(String[] args) {
Child child = new Child();
System.out.println(">>> age = " + Child.age);
}
}
class Child extends Father {
static {
System.out.println(">>> Child static block...");
age = 20;
}
static int age = 18;
public Child() {
System.out.println(">>> Child constructor...");
}
}
class Father {
static {
System.out.println(">>> Father static block...");
}
public Father () {
System.out.println(">>> Father constructor...");
}
}
输出结果为:
>>> Father static block...
>>> Child static block...
>>> Father constructor...
>>> Child constructor...
>>> age = 18
如果Child类的静态代码块和static int age = 18;语句位置交换,则最后age的值为20(0 -> 18 -> 20)。因为编译器会按定义顺序
自动收集类中所有类变量的赋值动作
和静态代码块中的语句
合并产生类构造器
class Child extends Father {
static int age = 18;
static {
System.out.println(">>> Child static block...");
age = 20;
}
public Child() {
System.out.println(">>> Child constructor...");
}
}
输出结果为:
>>> Father static block...
>>> Child static block...
>>> Father constructor...
>>> Child constructor...
>>> age = 20
类的主动引用(一定会发生类的初始化)
类的被动引用(不会发生类的初始化)
Child.age;
,而age属性是在父类定义的。Child[] childs = new Child[5];
类加载器的作用:将class文件字节码加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
package com.nobody;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/3
*/
public class TestClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
// 获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// 获取系统类加载器的父加载器,也就是扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
// 获取扩展类加载器的父加载器,也就是根加载器,用C或C++编写
ClassLoader parent1 = parent.getParent();
System.out.println(systemClassLoader);
System.out.println(parent);
System.out.println(parent1);
System.out.println("----------------------------------------");
// 测试我们定义的类是哪个类加载器加载的
ClassLoader classLoader = Class.forName("com.nobody.TestClassLoader").getClassLoader();
// 测试JDK内置的的类是哪个类加载器加载的
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader1);
// 获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
}
}
输出结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null // 根加载器(引导类加载器)我们获取不到,所以是null
----------------------------------------
sun.misc.Launcher$AppClassLoader@18b4aac2
null
C:\Program Files\Java\jdk1.8.0_202\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar;
D:\IdeaProjects\nobody\project02\out\production\hello-world;
D:\devTools\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar
Method,Field,Constructor都由setAccessible()方法,它的作用是开启或禁用访问安全检查。如果代码中用到反射,而且此代码被频繁调用,为了提高反射效率,则最好禁用访问安全检查,即设置为true。
package com.nobody;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/5
*/
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test01();
test02();
test03();
}
public static void test01() {
Teacher t = new Teacher();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
t.getName();
}
long end = System.currentTimeMillis();
System.out.println("普通方式执行10亿次消耗:" + (end - start) + "ms");
}
public static void test02() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Teacher t = new Teacher();
Class<?> aClass = Class.forName("com.nobody.Teacher");
Method getName = aClass.getDeclaredMethod("getName");
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(t, null);
}
long end = System.currentTimeMillis();
System.out.println("反射方式执行10亿次消耗:" + (end - start) + "ms");
}
public static void test03() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Teacher t = new Teacher();
Class<?> aClass = Class.forName("com.nobody.Teacher");
Method getName = aClass.getDeclaredMethod("getName");
getName.setAccessible(true);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(t, null);
}
long end = System.currentTimeMillis();
System.out.println("关闭安全检查反射方式执行10亿次消耗:" + (end - start) + "ms");
}
}
class Teacher {
private String name;
public String getName() {
return name;
}
}
输出结果:
普通方式执行1亿次消耗:6ms
反射方式执行1亿次消耗:4294ms
关闭安全检查反射方式执行1亿次消耗:1963ms
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是一旦编译完成,所有和泛型有关的类型全部擦除。
为了通过反射操作这些类型,Java新增了ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
几种类型来代表不能被归到Class类中的类型但是又和原始类型齐名的类型。
package com.nobody;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/5
*/
public class Test03 {
public void test01(Map<String, Integer> map, Person person) {
}
public Map<String, Student> test02() {
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method test01 = Test03.class.getDeclaredMethod("test01", Map.class, Person.class);
// 获取方法test01的参数类型
Type[] genericParameterTypes = test01.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);
}
}
}
Method test02 = Test03.class.getDeclaredMethod("test02", null);
// 获取方法test02的返回值类型
Type genericReturnType = test02.getGenericReturnType();
System.out.println("<<< " + genericReturnType);
// 如果参数类型等于参数化类型
if (genericReturnType instanceof ParameterizedType) {
// 获得真实参数类型
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
输出结果:
<<< java.util.Map<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
<<< class com.nobody.Person
<<< java.util.Map<java.lang.String, com.nobody.Student>
class java.lang.String
class com.nobody.Student
通过反射我们可以获取代码中的注解,并且获取注解的属性值,下面演示如何获取类和属性的注解,解析和数据库映射的相关信息。
package com.nobody;
import java.lang.annotation.*;
/**
* @author Mr.nobody
* @Description
* @date 2020/9/5
*/
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> aClass = Class.forName("com.nobody.Book");
// 获得Book类的注解
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获取类的指定注解,并且获取注解的值
Table annotation = aClass.getAnnotation(Table.class);
String value = annotation.value();
System.out.println("Book类映射的数据库表名:" + value);
java.lang.reflect.Field bookName = aClass.getDeclaredField("bookName");
Field annotation1 = bookName.getAnnotation(Field.class);
System.out.println("bookName属性映射的数据库字段属性 - 列名:" + annotation1.colName()
+ ",类型:" + annotation1.type() + ",长度:" + annotation1.length());
java.lang.reflect.Field price = aClass.getDeclaredField("price");
Field annotation2 = price.getAnnotation(Field.class);
System.out.println("price属性映射的数据库字段属性 - 列名:" + annotation2.colName()
+ ",类型:" + annotation2.type() + ",长度:" + annotation2.length());
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field {
String colName();
String type();
int length();
}
@Table("t_book")
class Book {
@Field(colName = "name", type = "varchar", length = 15)
String bookName;
@Field(colName = "price", type = "int", length = 10)
int price;
}
输出结果:
@com.nobody.Table(value=t_book)
Book类映射的数据库表名:t_book
bookName属性映射的数据库字段属性 - 列名:name,类型:varchar,长度:15
price属性映射的数据库字段属性 - 列名:price,类型:int,长度:10