20 注解(annotation)
20.1 注解简介
导入包:java.annotation
Annotation是从JDK1.5开始引入的新技术,注解即可以对程序员解释又可以对程序解释。
20.1.1 注解和注释
注释:对程序员解释代码信息(对程序没有任何影响)
注解:对程序和程序员解释代码信息
20.1.2 注解的作用
- 不是程序本身,但可以对程序作出解释(与注释comment类似)
- 可以被其他程序(编译器)读取到。
20.1.3 注解的格式
在被添加的程序上注解是以"@注释名"存在,且注释上还可以添加其他参数。
例如:@SuppressWarnings(“unchecked”)
20.1.4 注解的应用
注解可以附加在package(包)、class(类)、method(方法)、field(属性)等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制实现对这些数据的访问
20.2 注解的分类
20.2.1 内置注解
常见的内置注解
注解 | 功能 |
---|---|
@Overrlde | 定义在java.lang.Override中,此注解只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明(出现此注解,如果修饰的方法不是重写方法,将会报错) |
@Deprecated | 定义在java.lang.Deprecated中.此注解可以用于修饰方法,属性,类。表示不鼓励使用这样的元素.通常是因为它很危险或者存在更好的选择 |
@SuppressWarnings | 镇压警告,定义在java.lang.SuppressWarnings中用来抑制编译时的警告信息,需要添加参数才能正确使用。其中的参数有all(镇压全部警告)、unchecked(镇压单类型的警告信息)… |
注:
- 在注解中添加参数,需要使用键值对方式,即key=value,但当传递的参数在注解中的定义为“value”时可以不需要写“value=”。
- 传递多个参数时需要使用{}(前提是注解允许传递多个参数),单个参数可以省略。
示例:
@SuppressWarnings("all")
@SuppressWarnings({"all"})
@SuppressWarnings(value={"all"})
@SuppressWarnings(value="all")
@SuppressWarnings(value={"unchecked","deprecation"})
SuppressWarnings注解的底层
//Target和Retention都是元注解详情见----20.2.2元注解
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) //表示可以此注解可以被添加的位置
@Retention(RetentionPolicy.SOURCE) //注解的生命周期,此处为编译时丢弃
public @interface SuppressWarnings {
String[] value();
}
20.2.2 元注解
元注解的作用:负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明
这些类型和它们所支持的类在java.lang.annotation包中可以找到
(@Target,@Retention,@Documented,@Inherited )
@Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
关键字 | 使用范围 |
---|---|
TYPE | 作用于类 |
FIELD | 作用于属性 |
METHOD | 作用于方法 |
PARAMETER | 作用于形参 |
CONSTRUCTOR | 作用于构造方法 |
LOCAL_VARIABLE | 作用于局部变量 |
ANNOTATION_TYPE | 作用于类型声明 |
PACKAGE | 作用于包 |
TYPE_PARAMETER | 参数类型声明 |
TYPE_USE | 标注任何类型名称 |
@Retention:表示需要要在什么级别保存该注择信息,用于描述注解的生命周期
(SOURCE (编译时丢弃)< CLASS(运行时丢弃)< RUNTIME(运行时仍然存在))
@Document:说明该注解将被包含在javadoc中
@lnherited:说明子类可以继承父类中的该注解
20.2.3 自定义注解
关键字:@interface
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
@interface用来声明一个注解,格式:public @interface注解名{定义内容}
其中的每一个方法实际上是声明了一个配置参数.而方法的名称就是参数的名称.
返回值类型就是参数的类型(返回值只能是基本类型、Class、String、enum)
示例:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String str () default "aaa";
String[] value();
}
使用:
@MyAnnotation("ddd")
public class Test_1 {
@MyAnnotation(str="aaa",value={"bbbb","ccc"})
public static void main(String[] args) {
}
}
注:
-
可以通过default来声明参数的默认值 ,当有默认值存在,再使用时才不必须传值。
-
如果只有一个参数成员,一般参数名为value(),在传值时,不需要写“value=”
-
注解元素必須要有值,我们定义注解元素时,经常使用空字符串作为默认值
20.4 注解在反射中的运用
此内容详见 21.3.6 利用反射获取注解
21 反射机制(reflex)
21.1 java的反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
21.2 反射关键点—获取Class文件对象
java的反射机制之所以能够毫无阻碍的访问任何对象,及对象中的私有化属性和方法,其根本在于反射是通过类的Class文件对象进行访问,每个类在内存中都只有一分Class文件,这个文件中记载着类的所有信息。
获取类Class文件对象的方法示例:
//获取类的字节码文件对象
//方式1---通过类
Class<?> c1=Student.class;
//方式2---通过类的对象
Student stu=new Student("小红", '女', 18, "2107", "001");
Class<?> c2 = stu.getClass();
//方式3---通过类的路径
Properties p = new Properties();
p.load(Test_1.class.getClassLoader().getResourceAsStream("classPath.properties"));
String classPath=p.getProperty("classPath");
Class<?> c3 = Class.forName(classPath);
System.out.println(c1==c2);//true
System.out.println(c2==c3);//true
//这些类字节码文件对象只有一个,所以他们是相等的。
注:
方式3更加适合在工作中使用,通过变更配置文件就能够改变访问的类,通用性强,代码灵活,在维和和变更需求时,不需要对代码进行修改。
classPath.properties:
classPath=com.dream.reflex01.Student
配置文件的使用详细可见 java入门基础十三 --集合 13.4.5–集合–配置文件
21.3 利用反射
21.3.1 反射操作构造方法
示例:
/**
* 利用反射机制操作构造方法
*/
//从配置文件中获取Class路径
Properties p = new Properties();
p.load(Test_1.class.getClassLoader().getResourceAsStream("classPath.properties"));
String classPath=p.getProperty("classPath");
//获取字节码文件对象
Class<?> c = Class.forName(classPath);
//获取该类public的构造方法的对象
Constructor<?>[] constructors = c.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("--------------");
//获取该类所有的构造方法
Constructor<?>[] constructors2 = c.getDeclaredConstructors();
for (Constructor<?> constructor : constructors2) {
System.out.println(constructor);
}
//利用无参构造方法创建Student对象
Constructor<?> constructor_1 = c.getConstructor();
Student stu = (Student) constructor_1.newInstance();
System.out.println(stu);
//利用有参构造方法创建Student对象
Constructor<?> constructor_2 = c.getDeclaredConstructor(String.class,char.class,int.class,String.class,String.class);
constructor_2.setAccessible(true);//设置权限
Student stu1 = (Student) constructor_2.newInstance("小明",'女',18,"2107","001");
System.out.println(stu1);
注:
-
在调用公共构造方法时可以使用getConstructor,但想要调用私有构造方法需要使用getDeclaredConstructor方法,还需要setAccessible(true);来设置权限。
-
经验告诉我们,在使用反射机制时直接使用getDeclaredConstructors能够得到所有的构造方法,所以可以不考虑getConstructor。
-
使用反射时,其他属性和方法也类似。
21.3.2 反射操作属性
示例:
/**
* 利用反射机制操作属性
*/
//从配置文件中获取Class路径
Properties p = new Properties();
p.load(Test_1.class.getClassLoader().getResourceAsStream("classPath.properties"));
String classPath=p.getProperty("classPath");
//获取字节码文件对象
Class<?> c = Class.forName(classPath);
//获取到本类和父类的共有属性的对象
Field[] fields = c.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("---------");
//获取到本类所有的属性
Field[] declaredFields = c.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
System.out.println("---------");
//获取到父类中的所有属性
// Class<?> superclass = c.getSuperclass();//获取父类的字节码文件对象
//
// Field[] declaredFields2 = superclass.getDeclaredFields();
// for (Field field : declaredFields2) {
// System.out.println(field);
// }
//利用有参构造方法创建Student对象
Constructor<?> constructor_2 = c.getDeclaredConstructor(String.class,char.class,int.class,String.class,String.class);
constructor_2.setAccessible(true);
Student stu = (Student) constructor_2.newInstance("小明",'女',18,"2107","001");
System.out.println(stu);
System.out.println("---------");
//设置非私有化属性
Field field = c.getField("name");
field.set(stu, "小红");
System.out.println(stu);
System.out.println("----------");
//设置私有的属性
Field field2 = c.getDeclaredField("age");
field2.setAccessible(true); //设置权限
System.out.println(field2.get(stu));
field2.set(stu, 20);
System.out.println(stu);
注:想要获取到父类的所有属性和方法,可以通过子类的Class字节码对象获取到父类的字节码文件对象(getSuperclass();),再操作。
21.3.3 反射机制操作成员方法
示例:
/**
* 利用反射机制操作成员方法
*/
//从配置文件中获取Class路径
Properties p = new Properties();
p.load(Test_1.class.getClassLoader().getResourceAsStream("classPath.properties"));
String classPath=p.getProperty("classPath");
//获取字节码文件对象
Class<?> c = Class.forName(classPath);
//获取该类及父类共有方法的对象
Method[] methods = c.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("--------");
//获取该类私有方法的对象
Method[] declaredMethods = c.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
//利用有参构造方法创建Student对象
Constructor<?> constructor_2 = c.getDeclaredConstructor(String.class,char.class,int.class,String.class,String.class);
constructor_2.setAccessible(true);
Student stu = (Student) constructor_2.newInstance("小明",'女',18,"2107","001");
System.out.println(stu);
//调用共有方法
Method method = c.getMethod("setName", String.class);
method.invoke(stu, "小兰");
System.out.println(stu);
System.out.println("---------");
//调用私有方法
Method method2 = c.getDeclaredMethod("sleep");
method2.setAccessible(true); //设置权限
method2.invoke(stu);
21.3.4 利用反射操作数组
示例:
/**
* 利用反射机制操作数组
*/
//创建String数组
String[] strs = (String[]) Array.newInstance(String.class, 5);
//利用反射获取数组的长度
System.out.println(Array.getLength(strs));
//利用反射设置数组元素
Array.set(strs, 0, "小红");
Array.set(strs, 1, "小白");
Array.set(strs, 2, "小张");
Array.set(strs, 3, "小绿");
Array.set(strs, 4, "小明");
//利用反射遍历数组
for (int i = 0; i < strs.length; i++) {
System.out.println(Array.get(strs, i));
}
21.3.5 利用反射操作泛型
示例:
public class Test_6 {
public static void main(String[] args) throws Exception {
/**
* 利用反射机制操作构造泛型
*/
//获取本类的字节码文件对象
Class<?> c = Class.forName("com.dream.reflex01.Test_6");
//获取method_1方法的对象
Method method_1 = c.getMethod("method_1", ArrayList.class,HashMap.class);
method_1.setAccessible(true);
Type[] genericParameterTypes = method_1.getGenericParameterTypes();
for (Type type : genericParameterTypes) {
ParameterizedType parameterizedType= (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type type2 : actualTypeArguments) {
System.out.println(type2);
}
}
System.out.println("------------------");
//获取method_2方法的对象
Method method_2 = c.getMethod("method_2");
Type genericReturnType = method_2.getGenericReturnType();
ParameterizedType parameterizedType= (ParameterizedType) genericReturnType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type type : actualTypeArguments) {
System.out.println(type);
}
}
public static void method_1(ArrayList<String> list,HashMap<String, Integer> map){
}
public static HashMap<String , Integer> method_2(){
return null;
}
}
/*
class java.lang.String
class java.lang.String
class java.lang.Integer
------------------
class java.lang.String
class java.lang.Integer
*/
拓展示例:反射操作数组+泛型 见:java 例题:万能数组拷贝
21.3.6 利用反射获取注解
示例应用场景:当数据库建表的字段和实体类的属性(或表名和类名)有出入时,可以使用注解标记属性,且可以存储该属性在数据库中的信息。
自定义属性信息注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)//设置注解的应用为属性注解
@Retention(RetentionPolicy.RUNTIME)//设置生命周期为保留在运行时
public @interface FieldInfo {
String name();//字段名
String type();//字段类型
int length();//字段长度
}
自定义表名信息注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)//设置注解的应用为类注解
@Retention(RetentionPolicy.RUNTIME)//设置生命周期为保留在运行时
public @interface TableInfo {
String tableName();//表名
}
实体类:
@TableInfo(tableName="student")
public class Student {
@FieldInfo(name="stu_name",type="varchar",length=10)
private String name;
@FieldInfo(name="stu_sex",type="char",length=1)
private char sex;
@FieldInfo(name="stu_age",type="int",length=3)
private int age;
public Student(String name, char sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", sex=" + sex + ", age=" + age + "]";
}
}
测试方法:
Student stu = new Student("小王",'男',18);
//获取类的字节码文件对象
Class<? extends Student> c = stu.getClass();
//获取类上的注解
TableInfo annotation = c.getAnnotation(TableInfo.class);
//获取注解的数据
String tableName = annotation.tableName();
System.out.println("类在数据库中的表名为:"+tableName);
//获取属性上的注解和对应的数据
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
//获取对应属性上的注解
FieldInfo field_Annotation = field.getAnnotation(FieldInfo.class);
//获取注解的各种数据
String name = field_Annotation.name();
String type = field_Annotation.type();
int length = field_Annotation.length();
//输出
System.out.println(name+"----"+type+"----"+length);
}
/*
类在数据库中的表名为:student
stu_name----varchar----10
stu_sex----char----1
stu_age----int----3
*/