注解和反射
什么是注解
- Annotation是从JDK5.0开始引入的新技术;
- Annotation的作用:
- 不是程序本身,但可以对程序做出解释;
- 可以被其他程序读取(比如:编译器读取)。
- Annotation的格式:
- @+注解名称,也可以添加一些参数,例如:@SuppressWarnings("all")、@Override
- Annotation可以用在哪里:
- 可以附加在package、method、class、Filed等等上,相当于给它们增加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问。
内置注解
- @Override:只作用在方法生,表示重写父类的方法;
- @Deprecated:可以做用在类、方法、属性上,表示不鼓励程序员使用,通常是它是危险的或者有更改好的选择;
- @SuppressWarnings:用来抑制编译时的警告信息,这个注解强制需要添加参数;@SuppressWarnings("all")、@SuppressWarning("unchecked")、@SuppressWarnings(value={"unchecked","deprecation"})等等。
元注解(Meta-Annotation)
元注解的作用是用来负责注释其他注解的注解。java中定义了四个元注解类型(@Target、@Retention、@Documented、@Inherited)用来对其他注解类型进行说明。
- @Target:用于描述注解可以使用的范围,即注解可以用在什么地方(类、方法、字段等等);
- @Retention:表示在什么级别保存注解信息,用于描述注解的声明周期(runtime(运行时期)>class(编译阶段)>source(代码阶段)),在实际开发中几乎用的都是runtime;
- @Documented:表示注解包含在javadoc中;
- @Inherited:子类可以继承父类的注解。
java
public class Demo01 extends Object{
@Override
public String toString() {
return super.toString();
}
}
//@Target表示注解可以做用在什么地方
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//@Retention 表示注解在哪些地方有效
//runtime(运行时)>class(编译成class)>source(代码阶段)
@Retention(RetentionPolicy.RUNTIME)
@Documented //表示生成在javadoc中
@Inherited //表示子类可以继承父类的注解
@interface MyAnnotation{
}
自定义注解
- 自定义注解的格式:public @interface 注解名{定义内容}
- 定义内容格式为:返回值类型+参数名();
- 方法的名称就是参数的名称;
- 返回类型就是参数的类型,返回值类型的范围:基本数据类型、class、String、enum;
- 可以通过default声明参数的默认值;
- 如果只有一个参数时,一般定义参数名为value,这样在使用注解是可以直接写参数,不用写参数名;
- 注解元素必须要有值,定义注解元素时,常用空字符串、0作为默认值;
- 当注解内有多个参数时,使使用注解时可以不按照注解参数的顺序进行使用;
java
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation02 {
//定义方式: 类型+参数名();
String name() default "";
int age() default 0;
int id() default -1;//默认 -1 表示不存在
String[] books();
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation03 {
String value();
}
public class Demo01{
@MyAnnotation02(books = {"《三国》"},age=18)
public void test01(){
}
//如果只有一个参数时,一般定义参数名为value,这样在使用注解是可以直接写参数,不用写参数名;
@MyAnnotation03("hello")
public void test02(){
}
}
反射
- java的反射机制
- 理解Classs类并获取class实例
- 类的加载与ClassLoader
- 创建运行时类的对象
- 获取运行时类的完整结构
- 调用运行时类的指定结构
静态语言和动态语言
动态语言
- 在运行时可以改变其结构的语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
- 主要动态语言:Object-C、C#、JavaScript、PHP、Python等。
html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function myFunction() {
var num = "var a=1;var b= 2;alert(a+b)";
eval(num);//可以让num变成一条可执行的语句,并弹窗显示a+b的结果
}
</script>
<button onclick="myFunction()">点击我</button>
</body>
</html>
静态语言
-
运行时结构不可变的语言。java、C、C++;
-
Java不是动态语言,但Java可以称之为"准动态语言"。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
javaClass c = Class.forName("java.lang.String");
Java Reflection
-
Java Reflection允许程序在运行期间,借助Reflection API去的任何类的内部信息,并操作任意对象的属性和方法;
-
类在加载完成后,在堆内存的方法区中就产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为反射。
正常方式:引入需要的"包类"名称 --> 通过new实例化 --> 取得实例化对象
反射方式:实例化对象 --> getClass()方法 --> 得到完整的"包类"名称
-
反射的效率和安全性会下降。
获取class类实例方式
java
public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException {
Student student = new Student();
System.out.println(student.name);
//方式一:通过对象获得
Class c1 = student.getClass();
System.out.println(c1.hashCode());
//方式二:通过forName获得
Class c2 = Class.forName("com.zwb.po.Student");
System.out.println(c2.hashCode());
//方式三:通过类名.class获得
Class c3 = Student.class;
System.out.println(c3.hashCode());
//方式四:基本类型的包装类都有一个Type属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//获得父类的类型
Class c5 = c1.getSuperclass();
System.out.println(c5);
}
}
java
public class Person {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
java
public class Student extends Person{
public Student() {
this.name = "学生";
}
}
哪些类可以有class对象
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
- interface:接口
-
\]:数组
- annotation:注解@interface
- primitive type:
- 基本数据类型void
java
public class Demo04 {
public static void main(String[] args) {
Class c1 = Object.class;//类
Class c2 = Comparable.class;//接口
Class c3 = String[].class;//一维数组
Class c4 = int[][].class;//二维数组
Class c5 = Integer.class;//基本数据类型
Class c6 = void.class;//void
Class c7 = Override.class;//注解
Class c8 = ElementType.class;//枚举
Class c9 = Class.class;//Class
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
int[] ary1 = new int[10];
int[] ary2 = new int[100];
//不同数组的class是同一个 ary1和ary2的类是同一个
System.out.println(ary1.getClass().hashCode());
System.out.println(ary2.getClass().hashCode());
}
}
类的加载内存分析
类的加载与ClassLoader的理解
- 加载:类加载器(classLoader)将字节码文件(.class文件、网络文件等)加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,生成一个可以代表这个类的java.lang.Class对象。
- 链接:将java类的二进制代码合并到jvm中的过程;
- 验证:确认加载的类信息是否符合jvm规范,是否安全;
- 准备:为static变量初始化默认值和分配内存空间;final修饰的变量在编译时期就放在了常量池中;
- 解析:将常量池中的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器()方法的过程。类构造器()合并类中所有变量的赋值动作和静态代码块的语句合并;
- 初始化类时,发现对应的父类没有进行初始化,先初始化父类;
- 虚拟机会确保在多线程环境下一个类的()方法会被正确的同步和加锁。
java
public class Demo05 {
static{
System.out.println("Main类被初始化");
}
public static void main(String[] args) {
A a = new A();
System.out.println(a.num);
}
}
class A{
static{
System.out.println("A的静态代码块被初始化");
num = 1;
}
static int num = 100;
public A(){
System.out.println("A的无参构造器被初始化");
}
}
/*
输出:
Main类被初始化
A的静态代码块被初始化
A的无参构造器被初始化
100
*/
什么时候类会发生初始化
- 类的主动引用(一定会发生初始化)
- 当虚拟机启动时,先初始化main方法所在的类;
- new一个对象;
- 调用静态成员(非final修饰的常量)和静态方法;
- 使用java.lang.reflect包的方法对类进行反射调用;
- 先初始化一个类,如过父类没有进行初始化,会先初始化父类;
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:子类访问父类的静态变量,子类不会被初始化;
- 通过数组定义类引用,不会触发此类的初始化;
- 引用常量不会触发此类的初始化(常量在编译阶段就被放入了常量池中)。
java
public class Demo06 {
static {
System.out.println("Main被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
/**************类的主动引用*********************/
//方式一:new方式会加载类
// S s = new S();
/*
Main被加载
父类被初始化
子类被初始化
*/
//方式二:通过反射
// Class.forName("com.zwb.anntation.S");
/*
Main被加载
父类被初始化
子类被初始化
*/
//方式三:调用静态成员
// System.out.println(S.num2);
/*
Main被加载
父类被初始化
子类被初始化
101
*/
/**************类的被动引用*********************/
//方式一:子类访问父类的静态变量
// System.out.println(S.num1);
/*
Main被加载
父类被初始化
100
*/
//方式二:数组定义引用
// S[] ary = new S[6];//Main被加载
//方式三:应用常量
System.out.println(S.str);
/*
Main被加载
这是一个常量
*/
}
}
class F{
static int num1 = 100;
final static String str = "这是一个常量";
static{
System.out.println("父类被初始化");
}
}
class S extends F{
static{
System.out.println("子类被初始化");
num2 = 102;
}
static int num2 = 101;
}
类加载器
-
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对家,作为方法区中类数据的访问入口。
-
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

-
类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器。
-
引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库(rt.jar),用来装载核心类库。该加载器无法直接获取。
-
扩展类加载器:负责jre/lib/ext目录下的jar包或-java.ext.dirs指定目录下的jar包装入工作库.
-
系统类加载器:负责java -classpath或-Djava.class.path所指的目录下的类与jar包装入工作,是最常用的加载器。
-
-
说明:为什么不能定义类似java.lang.String的类,因为java类加载时会一层一层往上找,确保不会重复加载导致出错。(**双亲委派机制是 Java 虚拟机中类加载的核心规则,要求类加载请求优先交给上级处理,确保核心类库安全且不被重复加载。** 这一机制通过层级化的类加载器结构,保证了 Java 程序运行时的稳定性和安全性 。)

java
public class Demo07 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器--扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器--根加载器
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//获取当前测试类的类加载器
ClassLoader classLoader = Class.forName("com.zwb.anntation.Demo07").getClassLoader();
System.out.println(classLoader);
//获取JDK内置类的类加载器
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);
//获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
}
}
获取类运行时的完整结构
通过反射获取运行时类的完整结构Field, Method, Constructor、 Superclass, Interface, Annotation
实现的全部接口
所继承的父类
全部的构造器
全部的方法
全部的Field
注解
...
java
public class Person {
public String name;
private int age;
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;
}
//私有方法
private void privateMethod(){}
//公有方法
public void publicMethod(){}
public Person(){}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
java
public class Demo08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c = Class.forName("com.zwb.po.Person");
System.out.println(c.getName());//获取类的名字 包名+类名
System.out.println(c.getSimpleName());//获取类的简单名字 类名
System.out.println("=====================================");
//获取类的属性
Field[] fields = c.getFields();//获取类的所有public属性
for (Field field : fields) {
System.out.println("类的所有public属性:"+field);
}
Field[] declaredFields = c.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("类的所有属性:"+declaredField);
}
Field name = c.getDeclaredField("name");
System.out.println("获取类的指定属性:"+name);
System.out.println("=====================================");
//获取类的方法
Method[] methods = c.getMethods();//获取类的public方法,包括父类的方法
for (Method method : methods) {
System.out.println("获取类的public方法:"+method);
}
Method[] declaredMethods = c.getDeclaredMethods();//获取类的所有方法,仅限本类中声明的方法
for (Method declaredMethod : declaredMethods) {
System.out.println("获取类的所有方法:"+declaredMethod);
}
Method privateMethod = c.getDeclaredMethod("privateMethod", null);
System.out.println("获取类中的指定方法(无参):"+privateMethod);
Method privateMethod2 = c.getDeclaredMethod("setName", String.class);
System.out.println("获取类中的指定方法(有参):"+privateMethod2);
System.out.println("=====================================");
//获取构造器
Constructor[] constructors = c.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("获取类中public的构造器:"+constructor);
}
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println("获取类中所有的构造器:"+declaredConstructor);
}
Constructor declaredConstructor = c.getDeclaredConstructor(String.class, int.class);
System.out.println("获取指定构造器:"+declaredConstructor);
}
}
动态创建对象执行方法
-
通过Class对象创建类的对象
-
方式一: 通过无参构造器创建:Class对象.newInstance()
-
**方式二:**通过有参构造器创建:Class对象.getDeclaredConstructor(params...).newInstance(params);
-
-
调用指定的方法
通过Class类的getMethod(String name,Class...parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型;
之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
-
设置属性
通过Class类的getDeclaredField(属性名)来获取属性;之后使用Object invoke(Object obj, 属性名称)来设置值。
-
**说明:**要调用的属性和方法是private时,需要setAccessible(true);才能使用。
-
Method和Field、Constructor对象都有setAccessible()方法。setAccessible作用是启动和禁用访问安全检查的开关。
-
参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
-
提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。使得原本无法访问的私有成员也可以访问。
-
参数值为false则指示反射的对象应该实施Java语言访问检查。
-
滥用会破坏封装性、引发安全风险(如篡改敏感状态)。
-
java
public class User {
private String name;
private int age;
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 User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class c = Class.forName("com.zwb.po.User");
User u = (User)c.newInstance();//调用无参构造器创建User对象
System.out.println(u);
System.out.println("==================================");
//通过有参构造器创建对象
Constructor declaredConstructor = c.getDeclaredConstructor(String.class, int.class);
User u1 = (User)declaredConstructor.newInstance("DaiV", 18);
System.out.println(u1);
System.out.println("==================================");
//通过反射调用普通方法
Method setName = c.getMethod("setName", String.class);
setName.invoke(u1,"猪猪包");// 方法名.invoke(方法的对象,方法的值)
System.out.println(u1.getName());
System.out.println("==================================");
//通过对象操作属性
User u2 = (User)c.newInstance();
Field age = c.getDeclaredField("age");
//不能直接操作私有属性,通过设置这个属性,可以修改私有属性
age.setAccessible(true);//关闭安全检测,可以操作私有属性
age.set(u2,18);
System.out.println(u2.getAge());
}
反射和正常调用的性能分析
java
public class Demo10 {
//正常方式调用方法
public static void test01(){
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("正常方式调用方法耗时:"+(endTime - startTime) + "ms");
}
//反射方式调用方法--开启安全检查
public static void test02() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> c = Class.forName("com.zwb.po.User");
User user = (User)c.newInstance();
Method getName = c.getDeclaredMethod("getName");
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
getName.invoke(user);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式调用方法--开启安全检查耗时:"+(endTime - startTime) + "ms");
}
//反射方式调用方法--开启安全检查
public static void test03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> c = Class.forName("com.zwb.po.User");
User user = (User)c.newInstance();
Method getName = c.getDeclaredMethod("getName");
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
getName.invoke(user);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式调用方法--开启安全检查耗时:"+(endTime - startTime) + "ms");
}
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
test01();
test02();
test03();
}
}
获取泛型信息
- Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。****(数组在运行时没有泛型擦除)
- 为了通过反射操作这些类型,Java新增了ParameterizedType , GenericArrayType ,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化类型,比如Collection
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型TypeVariable:是各种类型变量的公共父接口
- WVildcardType:代表一种通配符类型表达式
java
public class Demo11 {
public static void test01(Map<String, User> map, List<User> list){
System.out.println("test01");
}
public static Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Class c = Demo11.class;
//获取方法参数
Method test01 = c.getDeclaredMethod("test01", Map.class, List.class);
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);
}
}
}
System.out.println("==========================================");
//获取方法返回值
Method test02 = c.getDeclaredMethod("test02", null);
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
getAnnotations()
getAnnotation()
**示例:**通过注解,获取实体类和数据库表之间的映射关系。
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnntation {
String value();
}
java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnntation {
String column();//列名
String type();//类型
int length();//长度
}
java
@TableAnntation("db_people")
public class People {
@FieldAnntation(column = "db_id",type = "varchar",length = 32)
private String id;
@FieldAnntation(column = "db_name",type = "varchar",length = 256)
private String name;
@FieldAnntation(column = "db_age",type = "number",length = 3)
private int age;
public String getId() {
return id;
}
public void setId(String id) {
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;
}
}
java
public class Demo12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c = Class.forName("com.zwb.po.People");
//获取类上的所有注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("类上的注解:"+annotation);
}
System.out.println("============================");
//获取字段上的注解
Field id = c.getDeclaredField("id");
FieldAnntation annotation = id.getAnnotation(FieldAnntation.class);
System.out.println(annotation.column());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```java
public class Demo12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c = Class.forName("com.zwb.po.People");
//获取类上的所有注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("类上的注解:"+annotation);
}
System.out.println("============================");
//获取字段上的注解
Field id = c.getDeclaredField("id");
FieldAnntation annotation = id.getAnnotation(FieldAnntation.class);
System.out.println(annotation.column());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}