注解与反射 annotation and reflect
注解与反射是所有框架的一个底层
MyBits 框架、Spring 框架、SpringBoot 框架等等这些框架的底层实现就是注解和反射
注解annotation
一、什么是注解
1、annotation 是从 JDK 5.0 开始引入的新技术
2、annotation 的作用:
- 不是程序本身,可以对程序做出解释(这一点和注释 comment 也没什么区别)
- 可以被其它程序(比如:编译器等)读取
3、annotation 的格式:
注解是以 “@注释名” 在代码中存在的,还可以添加一系列参数值
例如:@Override(重写的注解)、@SuppressWarnings(value=“unchecked”) 等等
4、annotation 在哪里使用?
可以附加在 package、class、method、field 等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
二、内置注解
1、@Override
定义在 java.lang.Override 中,此注释只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明。
如果使用此注释类型注释方法,则除非至少满足以下条件之一,否则需要编译器生成错误信息:
- 该方法将覆盖或实现在超类型中声明的方法
- 该方法具有与 Object 中声明的任何公共方法的覆盖相同的签名
@Override //重写的注解
public String toString(){
return super.toString();
}
2、@Deprecated
定义在 java.lang.Deprecated 中,此注释可以用于修饰方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者是存在更好的选择。
注释 @Deprecated 的程序元素是程序员不鼓励使用的程序元素,通常是因为它是危险的,或者因为存在更好的替代方法。编译器在不被弃用的代码中使用或覆盖不推荐使用的程序元素时发出警告。
public class Test extends Object{
@Deprecated //不推程序员使用,但是可以使用,或者存在更好的方式
public static void test01(){
System.out.println("Deprecated");
}
public static void main(String args[]){
test01();
}
}
//输出:Deprecated
//此时程序是能正常跑起来的,只是系统不推荐使用 @Deprecated 标注了的程序元素
3、@SuppressWarnings
定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息,可以修饰方法,也可以修饰类。
与前两个注释有所不同,此时你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了
- @SuppressWarnings(“all”)
- @SuppressWarnings(“unchecked”)
- @SuppressWarnings(value={“unchecked”,“deprecation”})
- 等等…
注意:平时写代码不建议大家将警告信息抑制,因为警告能帮助我们去识别程序的一些错误,除非你看的真的心烦了
@SuppressWarnings("all")
public class Test extends Object{
@Deprecated //标注不推荐程序员使用的代码,但是可以使用,或者存在更好的方式
public static void test01(){
System.out.println("Deprecated");
}
public static void main(String args[]){
test01();
}
//@SuppressWarnings("all")
//可以修饰方法,也可以修饰类
public void test02(){
List list = new ArrayList();
}
}
三、元注解 meta-annotation
1、元注解的作用
负责注解其它注解,Java 定义了4个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明
2、四个元注解的具体解释
这些元注解和它们所支持的类在 java.lang.annotation 包中可以找到(@Target、@Retention、@Documented、@Inherited)
- @Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
1、TYPE:
Class, interface (including annotation type), or enum declaration
类,接口(包括注释类型)或枚举声明
2、FIELD
Field declaration (includes enum constants)
字段声明(包括枚举常量)
3、METHOD
Method declaration
方法声明
4、PARAMETER
Formal parameter declaration
形式参数声明
5、CONSTRUCTOR
Constructor declaration
构造函数声明
6、LOCAL_VARIABLE
Local variable declaration
局部变量声明
7、ANNOTATION_TYPE
Annotation type declaration
注释类型声明
8、PACKAGE
Package declaration
包声明
-
@Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
SOURCE (源码级别) < CLASS (Class 文件级别) < RUNTIME (运行时级别)
-
@Documented:说明该注解将被包含在 javadoc 中
-
@Inherited:说明子类可以继承父类中的该注解
3、测试元注解
//测试元注解
public class Test{
public void T
}
//自定义一个注解
@Target(value = {
ElementType.METHOD,ElementType.TYPE}) //表明此自定义注解只能修饰方法、接口和类
@Retention(value = RetentionPolicy.RUNTIME) //表明我们的注解在运行阶段还有效
@Documented //表示是否将我们的注解生成在 Javadoc 中
@Inherited //表示子类可以继承父类的注解
@interface Myannotation{
}
四、自定义注解
使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口
分析:
- @interface 用来声明一个注解,格式:public @interface 注解名 {注解内容}
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型、Class、String、enum)
- 可以通过 default 来声明参数的默认值
- 如果只有一个参数成员,一般参数名为 value,也可以忽略不写(参数名不为 value 就不能省略了)
- 注解参数必须要有值,我们定义注解参数时,经常使用空字符串、0 作为默认值
自定义注解
public class Test{
//如果注解参数没有默认值,我们就必须给注解参数赋值,如 name、schools
//如果注解参数有默认值,就不用再去定义了,如 age、id
//注解参数是没有顺序的,随便先定义哪个都行
@MyAnnotation(name = "zyt",schools = {
"西北大学","电子科技大学","上海交大"})
public void demo(){
}
}
@Target({
ElementType.TYPE,ElementTypeMETHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的参数:参数类型 + 参数名 + () + default + 默认值;
String name();
int age() default 0;
int id() default -1; //如果默认值为-1,代表不存在,与 indexof 有异曲同工之妙(如果找不到就返回-1)
String[] schools();
}
反射 reflect
java.lang.reflection
一、静态语言与动态语言
1、动态语言
动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或者是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
2、主要的几种动态语言
Object-C、C#、JavaScript、PHP、Python
3、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言:例如:Java、C、C++
Java 不是动态语言,但 Java 可以称之为 “准动态语言” 。即 Java 有一定的动态性,我们可以利用反射机制使 Java 获得类似动态语言的特性。Java 的动态性让编程的时候更加灵活!
二、Java 反射机制概述
Reflection(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.String");
加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
正常方式:引入需要的“包类”名称———通过new实例化——取得实例化对象
反射方式:实例化对象——getClass()方法——得到完整的“包类”名称
Java 反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理(这是一个机制,在之后学习 AOP 的时候会用到)
- 等等…
反射的优点与缺点
1、优点
可以实现动态创建对象和编译,体现出很大的灵活性
2、缺点
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的需求。这类操作总是慢于直接执行相同的操作。
反射相关的主要 API
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
.......
三、理解 Class 类并获取 Class 实例
在 Object 类中定义了以下的方法,此方法将被所有的子类继承
public final Class getClass();
以上的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称
1、Class 类
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[ ])的有关信息。
- Class 本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个 Class 实例
- 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过 Class 可以完整地得到一个类中的所有被加载的结构
- Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象
2、Class 类的常用方法
方法名 | 功能说明 |
---|---|
static ClassforName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance( ) | 调用缺省构造函数,返回 Class 对象的一个实例 |
getName( ) | 返回此 Class 对象所表示的实体(类、接口、数组类或 void)的名称 |
Class getSuperClass( ) | 返回当前 Class 对象的父类的 Class 对象 |
Class[] getinterfaces( ) | 获取当前 Class 对象的接口 |
ClassLoader getClassLoader( ) | 返回该类的加载器 |
Constructor[] getConstructors( ) | 返回一个包含某些 Constructor 对象的数组 |
Method getMethod(String name,Class… T) | 返回一个 Method 对象,此对象的形参类型为 paramType |
Field[] getDeclaredFields( ) | 返回 Field 数组的一个数组 |
3、获取 Class 类的实例
a、若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高
Class clazz = Person.class;
b、已知某个类的实例,调用该实例的 getClass( ) 方法获取 Class 对象
Class clazz = person.getClass();
c、已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName( ) 获取,可能抛出 ClassNotFoundException
Class clazz = Class.forName(“demo01.Student”);
d、内置基本数据类型的封装类可以直接使用类名.TYPE
e、还可以利用 ClassLoader我们之后会进行讲解
拓展:使用 getSuperclass() 方法获取父类的 Class 实例
//测试获取 Class 类的实例的各个方法
package com.zyt.reflection;
public class Test {
public static void main(String[] args) throws ClassNotFoundException{
Person person = new Person();
System.out.println("这个人是:"+person.name);
System.out.println(person.toString());
//方式一:通过某个类的实例对象调用 getClass() 获取
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式二:通过 forClass() 获取
Class c2 = Class.forName("com.zyt.reflection.Student");
System.out.println(c2.hashCode());
//方式三:通过类的 class 属性获取
Class c3 = Student.class;
System.out.println(c3.hashCode());
//通过比较 c2 和 c3 的 hash 值发现相同,验证了一个类只有一个 Class 实例的说法
//方式四:基本内置类型的封装类都有一个 TYPE 属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//获得父类类型——getSuperclass()
Class c5 = c1.getSuperclass();
System.out.println(c5)