注解&&反射&&类加载

基础

注解

Annotation:

  • 不是程序本身,可以对程序作出解释,可以被其他程序(比如:编译器)读取

  • 格式:”@注释名” 在代码中存在,还可以添加一些参数值,eg:@SuppressWarnings(value=”unchecked”)

  • 可以附加在package,class,method,field 等上面。相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

内置注解

  • @Override:定义在java.lang.Override 中 重写方法
  • @Deprecated: 定义在 java.lang.Deprecated 中 ,表示不鼓励程序员使用这样的元素
  • @SuppressWarnings: 定义在java.labg.SuppressWarnings 中,用来抑制编译时的警告信息,他需要添加一个参数才能使用,这些参数都是定义好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.zhang.annotation;

import java.util.ArrayList;
import java.util.List;

public class Test01 extends Object {

//@Override 这就是 重写 的注解
@Override
public String toString() {
return super.toString();
}

@Deprecated // @Deprecated 表示不推荐程序员使用,
public static void test(){
System.out.println("Deprecated");

}
@SuppressWarnings("all")//镇压警告
public void test02(){
List list = new ArrayList();

} public static void main(String[] args) {
test();
}

}

元注解

  • 作用:负责注解其他注解,Java定义了4个标准的meta-annotation , 他们呃用来提供对其他annotation 类型做说明

  • 这些类型在java.lang.annotation 包中可以找到(@Target @Retention @Documented @Inherited)

    • @Target: 描述注解的使用范围即:被描述的注解可以用在什么地方

      • ElementType取值:
        1. TYPE:可以作用在类上
        2. METHOD:可以作用于方法上
        3. FIELD:可以作用于成员变量上
    • @Retention: 表示需要在什么级别保存被描述的注解信息,用于描述注解的生命周期

      SOURCE<CLASS<RUNTIME

      • RUNTIME:当前被描述的注解会被保存在class字节码文件中,并且被JVM读取到
      • CLASS:当前被描述的注解会被保存在class字节码文件中,但不会被JVM读取到
      • SOURCE:当前被描述的注解不会被保存在class字节码文件中
    • @Document:说明被描述的注解将被包含在javadoc文档中

    • Inherited:说明子类可以继承父类中的被描述的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.zhang.annotation;

import java.lang.annotation.*;

//测试元注解
public class Test02 {

}

//@Target 注解作用范围
//定义一个注解 关键字 @interface 名称 MyAnnotation
//写元注解@Target @Retention @Documected
@Target(value={ElementType.METHOD, ElementType.TYPE})//表示MyAnnoation 能作用于方法,类
@Retention(RetentionPolicy.RUNTIME)//表示当前 被描述的注解 会保留到class字节码文件中,并被JVM读取到
@Documented //该被描述的注解会被保留在javadoc文档中
@Inherited //子类可以继承父类该被描述的注解
@interface MyAnnotation {

}

自定义注解

  • 关键字 @interface 自定义注解,自动继承了java.lang.annotation.Annotation 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.zhang.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class Test03 {
//注解可以显示赋值 可以默认值定义,没有默认值必须显示赋值
//显式定义格式 (参数=),没有固定顺序
@MyAnnotation2(age=18)
public static void test() {}


//显式赋值时,不写参数名的话,就必须只有一个参数且参数名默认必须为value 才不报错
@MyAnnotation3("秦疆")
public void test2(){}


}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//这是注解的参数 : 参数类型 +参数名(); 命名要求最后要加括号
String name() default "";//default 默认赋值
int age() ;
int id() default -1;//如果默认值为-1,代表不存在。
String[] school() default{"西工大","西部开源"};
}

//只有一个参数
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
String value();
}

反射

加载类,并允许以编程的方式解剖类中的各种成分(成员变量,方法,构造器)

  • 动态语言:在运行时可以改变其结构的语言 eg: PHP Python JavaScript C #

  • 静态语言: 与动态语言相对应,运行时结构不可改变的语言 eg: Java C C++

  • Java不是动态语言,但Java 可以称之为“准动态语言”,即Java 有一定的动态性,我们可以利用反射机制获得类似动态语言的特性

  • 反射:是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Refelection API取得任何类的内部信息,并能直接操纵任意对象的内部属性及方法(可以直接二读取到private )

  1. 反射第一步:加载类,获取类的字节码:Class对象(万物皆对象,获取类本身)

  2. 获取类的构造器对象:Constructor 对象

  3. 获取类的成员变量:Field 对象

  4. 获取类的成员方法对象:Method 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    package com.zhang.reflection;

    import static java.lang.Class.forName;

    public class Test02 extends Object {
    public static void main(String[] args) throws ClassNotFoundException {
    //通过反射获取 User类 的Class对象
    Class c1 = Class.forName("com.zhang.reflection.User");
    System.out.println(c1);
    Class c2 = Class.forName("com.zhang.reflection.User");
    Class c3 = Class.forName("com.zhang.reflection.User");
    Class c4 = Class.forName("com.zhang.reflection.User");

    //一个类在内存中只有一个Class对象
    //一个类被加载后,整个类的结构都会被封装在Class对象中
    System.out.println(c2.hashCode());
    System.out.println(c3.hashCode());
    System.out.println(c4.hashCode());


    }

    }

    //实体类 :pojo entity
    class User{
    private String name;
    private int id;
    private int age;
    //有参构造器
    public User(String name, int id, int age) {
    this.name = name;
    this.id = id;
    this.age = age;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    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;
    }

    @Override
    public String toString() {
    return "User{" +
    "name='" + name + '\'' +
    ", id=" + id +
    ", age=" + age +
    '}';
    }
    }
    image-20250327222718531

获取class对象的三种方式

  • Class c1=类名.class 每一个类都有一个默认的class属性 class关键字

  • 调用Class提供的方法:Class c2= Class. forName(“类的全类名”); Class方法forName

  • 通过实例 : Object 提供的方法: new 类(); Class c3=对象. getClass(); Object方法

    注意:[getClass 返回实际类型的Class类型]

    ​ [子类Class 类型的对象 getSuperClass方法 可以获得父类的Class 对象 ]

    1
    2
    3
    Person s1 = new Student2();
    Class c1 = s1.getClass();//创建对象,获得子类的Class对象
    c1.getSuperclass();//通过子类Class对象,获得父类Class对象
    • 特殊的: 基本内置类型的包装类都有一个Type 属性

      Class c4= Integer.TYPE;

获取类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.zhang.reflection;

public class Dome01 {
public static void main(String[] args) throws ClassNotFoundException {
//1.获取类本身:类.Class
Class c1 = Student.class;
System.out.println(c1);

//2.获取类本身:Class.forName("类的全类名")
Class c2 = Class.forName("com.zhang.reflection.Student");
System.out.println(c2);
//类对象在内存中只会加载一个
System.out.println(c1 == c2);//true c1 c2 指向的都是同一个class


//3.获取类本身:对象.getClass()
Student s = new Student();
Class c3 = s.getClass();//任何对象都可以调getClass方法 因为它是Object的方法
System.out.println(c3);
System.out.println(c1 == c3);//true c1 c2 c3 指向的都是同一个class
}
}

所有类型的Class对象

1
2
3
4
5
6
7
8
9
10
11
#所有的Class类型:
类.class:Object.class
接口.class: Comparable.class
一维数组.class:String[].class
二维数组.class:String[][].class
注解.class: @Override.class
枚举.class: ElementType.class
基本数据类型.class: Integer.class #Integer 是int 基本数据类型的包装类
void.class
Class.class
# 只要元素类型为度一样,就是同一个Class

Java 内存分析

联想截图_20250328105121

——–

类加载

了解类的加载过程:

  • 程序主动使用某个类时,如果该类还未被加载到内存中,系统会自动通过以下三个步骤对该类进行初始化:

    • 类的加载:将类的class文件读入内存,

      并为之在堆中创建一个java.lang.Class对象。此过程由类加载器完成

      • 加载完类之后,在堆中就产生了一个Class 类型的对象,一个类就只有一个Class对象,这个对象包含了完整的类的结构(成员变量,构造器,成员方法….),对象就像一面镜子,透过这个镜子,看到类的结构
    • 类的链接:将java类二进制数据合并到 JRE中

      • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
      • 准备:正式为类变量 (static) 分配内存并设置类变量默认初始值的阶段(所以说static在初始化之前就已经有了一个值),这些内存都将在方法区中进行分配
      • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
    • 类的初始化:JVM负责对类进行初始化

      执行类构造器()方法的过程,类构造器<clinit>()方法 是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的(就是所有static 的变量和代码块)(类构造器是构造类信息的,不是构造该类对象的构造器

      当初始化一个类的时候,如果发现它的父类还没进行初始化,则先触发其父类的初始化

      {说白了,类初始化就是通过类构造器()方法将该类的静态变量和静态代码块合并}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.zhang.reflection;

import javax.swing.plaf.synth.SynthOptionPaneUI;
import java.sql.SQLOutput;

public class Test5 {
public static void main(String[] args) {
A a = new A();
System.out.println(a.m);
/*
* 1.加载到内存,在堆中生成一个Class对象
* 2.链接,提前将static 赋值为0(m=0)
* 3.初始化,用<clinit>()方法,将静态变量,静态代码合并起来
* <clinit>(){
* System.out.println("A类静态代码块初始化");
m=300;
m=100;
*
* 4.最后m=100
* } */

}
}
class A{
static{
System.out.println("A类静态代码块初始化");
m=300;
}
static int m=100;
public A() {
System.out.println("A类无参构造初始化");
}
}


所以回顾之前的一个结论 静态方法属于类,非静态方法属于对象:

因为在验证准备阶段,在方法区中会为静态变量分配内存空间,而非静态方法依赖于该类的实例化对象,实例化时才会分配内存


Java程序的三个阶段:

1 .先进行编译阶段 : 源代码通过javac变成class字节码文件(字节码文件中有:类的属性,构造器,成员方法….)(存放于方法区)

2 . 加载阶段:通过类加载器ClassLoader(体现反射),在堆中产生了一个Class类型的对象

**3 .**Runtime运行阶段

new Cat()对象,这个Cat对象在堆中,==该对象知道它是属于哪个Class对象的==

联想截图_20250328112734


类初始化的触发

初始化阶段开始之前,自然还是要先经历 加载、验证、准备 、解析的。

回顾类初始化:类构造器通过方法将该类中的所有类变量和静态代码块合并起来

  • 类的主动引用一定会触发类的初始化
    • 虚拟机启动,先初始化main方法所在的类
    • new 类
    • 使用java.lang.reflect包的方法进行反射调用 ==类.class调用反射不能触发类的初始化==
    • 直接用类调用静态方法或属性 (除了final变量)
  • 类的被动引用不会触发类的初始化
    • 子类调用父类的静态方法或属性(子类不会初始化,父类被初始化)
    • 通过数组定义类引用
    • 引用一个类中的常量,不会触发该类的初始化(常量在链接阶段就存入调用类的常量池中了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.zhang.reflection;

public class Test06 {
static{
System.out.println("Main 类被加载");
}

public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用 触发初始化
//Son son=new Son();


//2. 用java.lang.reflect包调用反射也能触发初始化
//Class<?> aClass = Class.forName("com.zhang.reflection.Son");
//** 用类默认的class属性 不能触发类的初始化 而 Object的getClass()方法 先new了就触发类的初始化啦
// Class<Son> sonClass = Son.class;


//3.子类调用自己的静态方法或属性 可以触发类初始化,父类未初始化的情况下,就先触发其父类的初始化
// System.out.println(Son.m);


//而不会触发类初始化的:
//1.子类调用父类静态方法或属性
//System.out.println(Son.b);
//2.数组
// Son[] array=new Son[10];//这个只会加载Main 方法,只初始化Test06
//3.常量
//System.out.println(Son.M);
}
}
class Father{
static int b=1;
static{
System.out.println("父类被加载");
}

}
class Son extends Father{
static{
System.out.println("子类被加载");
m=300;
}
static int m=200;
static final int M=11;


}

类加载器的作用

  • 引导类加载器: 用c++编写,该加载器无法直接获取
  • 扩展类加载器:
  • 系统类加载器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.zhang.reflection;

public class Test07 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类的加载器的父类加载器---->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);

//获取扩展类加载器的父类加载器-->根加载器(c/c++写的)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);

//测试当前类的加载器(它是哪个类加载的)
ClassLoader classLoader = Class.forName("com.zhang.reflection.Test07").getClassLoader();
System.out.println(classLoader);
//测试jdk内部的类是谁加载的
classLoader=Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);

//获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
}
}

双亲委派机制:

双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。


———

反射

获取类的运行结构

  • 类的名字

    类对象.getName()

1
2
3
4
5
6
7
8
9
10
//通过Class.forName()获得Class对象
Class c1 = Class.forName("com.zhang.reflection.User");

//获得类的名字
System.out.println(c1.getName());//包名+类名
System.out.println(c1.getSimpleName());//类名
//也可以通过new 得到Class对象,获得类名字
User u1 = new User("xiao",1,18);
Class c2 = u1.getClass();
System.out.println(c2.getName());
  • 类的属性

    类对象.getFields(): 获取所有public属性

    类对象.getDeclaredFields(): 获取所有的属性

    类对象.getDeclaredField(“name”):获取指定的属性

    类对象.getField(“name”):获得指定的public属性

1
2
3
4
5
6
7
8
9
10
11
12
//获得类的属性  getFields()方法   返回的是数组
Field[] fields = c1.getFields(); //!!getFields 获得的是public属性
fields=c1.getDeclaredFields();//!! getDeclaredFields获得的是全部属性

for (Field field : fields) {
System.out.println(field);
}
System.out.println("====");

//获得指定属性
Field id=c1.getDeclaredField("id");//注意有参数的没有 s
System.out.println(id);
  • 类的方法

    getMethods() 获取本类及父类的所有public方法

    getDeclaredMethods() :获取本类的所有方法

    getMethod(“方法名”,“参数(丢个类型)反射的形式)”) : 获取本类及父类的指定public方法

    getDeclaredMethod(“方法名”,“参数(反射的形式)”) :获取本类的所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//获得类的方法
System.out.println("====");
Method[] methods = c1.getMethods();//获得本类及其父类的全部public方法

for(Method m: methods){
System.out.println("正常的"+m);
}
;
methods=c1.getDeclaredMethods();//获得本类的全部方法
for(Method m: methods){
System.out.println("getClaredMethods"+m);

}
//获得指定方法
//一定需要写方法的参数 有重载的情况
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);

System.out.println(getName);
System.out.println("===========");

  • 类的构造器

    getConstructors():获得所有public构造器(本类)

    ……如有参数,参数也是反射类型的

1
2
3
4
5
6
7
8
9
//获得所有构造器
Constructor[] constructor = c1.getConstructors();
for(Constructor c: constructor){
System.out.println(c);
}
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
//获得指定构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);//参数必须是反射的形式
System.out.println(declaredConstructor);

小总结

  • getDeclaredField(“属性名”)

  • getDeclaredMethod(“方法名”,参数(class形式的))

  • getDeclaredConstructor(参数(class形式的)) 构造器与类肯定同名不需要写构造器名了

  • getDeclaredAnnotation(注解(class形式的))

    注解VS构造器:
    注解不需要写参数,构造器不需要写构造器名


动态创建对象执行方法

  • 总结流程就是:
    1. 先由反射获得Class对象
    2. Class对象 通过newInstance()(或者借助有参构造器)获得一般对象 (之后备用哈ha)
    3. 获得(抽象【就是由class对象getDeclaredField()得到的】)(其实也就是Method,Field对象)属性/方法
    4. 将抽象属性/方法/构造器 进行(set/invoke/) 抽象方法激活,抽象属性设置
      • 抽象方法.invoke(所属的一般对象,方法的参数值)
      • 抽象属性.set(所属的一般对象,属性赋值)

注意:

  1. 操作私有的方法或属性时:需要将程序的安全检验关掉并使用detDeclaredMethod/getDeclaredFiled 获取指定私有方法或属性

    Method,Field,Constructor 对象都有setAccessible()方法

    参数值为true则指反射的对象在使用时应该取消Java语言访问检查

    1
    2
    方法或属性.setAccessible(true);//关闭程序的安全及检验只是能对私有方法或属性操作,但并不能直接调用,之后还得借助public的方法

  2. class对象.getDeclaredConstructor().newInstance() 之后得到的是Object类型的对象,需强转成想要的类型

  3. 得到抽象方法或属性时,注意第二个参数是反射类型的

细节: c1.newInstance()本质调用无参构造器(一定要用无参构造器,且允许访问)—等价于c1.getDeclaredConstructor().newInstance()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//获取Class对象
Class c1 = Class.forName("com.zhang.reflection.User");
//Class对象创建一般对象 返回的是Object类型的,需强转成User类型的
User user1 = (User)c1.getDeclaredConstructor().newInstance();//这里需要注意getDeclaredConstructor()不加参数 本质上是调用的无参构造器,没有public 无参构造器会报错的!!
System.out.println(user1);

//通过有参构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 =(User) constructor.newInstance("xiao", 001, 18);
System.out.println(user2);
//直接调用
/* user1.setName("xiao");
System.out.println(user1.getName());*/

//通过反射调用普通方法
User user3 = (User) c1.getDeclaredConstructor().newInstance();
//先获得抽象方法 上面代码这里本质上调用无参构造器
Method setName = c1.getDeclaredMethod("setName", String.class);
//激活抽象方法
setName.invoke(user3,"xiao");
System.out.println(user3.getName());
System.out.println("333333333333");


//操作私有方法试一下
//1.先得到指定的抽象的方法
Method test5 = c1.getDeclaredMethod("test5");
test5.setAccessible(true);//关闭程序的安全及检验只是能对私有方法或属性操作,但并不能直接调用
test5.invoke(user3,null);
user3.getTest05();//调用私有方法它无返回值时一定不能打印!!,直接调用即可

本质记忆

类Person

1
2
3
4
5
6
7
Class c1=Class.forName("com.test.classLoad.Person");
//1.本质使用无参构造器创建对象
c1.newInstance();
//2.本质还是无参构造器
c1.getDelaredConstruct().newInstance();
//3.本质使用有参构造器
c1.getDeclaredConstruct(String.class,int.class).newInstance("zhang",18);

newInstance是往构造器中传参进行初始化

性能对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

public class Test10{

//普通测试
public static void test01(){
User user = new User();

long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long end = System.currentTimeMillis();
System.out.println("普通方式执行"+(end - start));
}


//反射测试
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class<User> c1 = User.class;
Method getName = c1.getDeclaredMethod("getName", null);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long end = System.currentTimeMillis();
System.out.println("反射方式执行"+ (end - start));
}


//反射测试 关闭检测
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class<User> c1 = User.class;
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
test01();//3
test02();//4194
test03();//3750 关闭点检测能提高点效率

}

}

泛型

  • 泛型就是表示一种数据类型的数据类型
  • 泛型俗称“标签”,使用表示。泛型就是在允许定义类,接口时通过一个标识表示某个属性的类型或者是某个方法的返回值或者是参数类型,参数类型在具体使用的时候确定,在使用之前对类型进行检查。
  • 在类声明或实例化对象是只要指定好需要的具体类型即可
    • 在类声明时通过一个标识表示类中某个属性的类型或某个方法/构造器的返回值类型,参数类型
  • 补充:在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字和值的类型。T表示任意类型。
1
2
3
4
5
6
7
8
9
class Person<E>{
E s;
public Person(E s){
this.s=s;
}
public E f(){
return s;
}
}
  • 泛型的声明

    interface接口{} 和class 类<K,V>{}

    T,K,V代表类型

  • 泛型的好处:

    • 编译时检查添加的元素的类型,提高了安全性
    • 减少了类型转换的次数,提高效率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public class dome01 {
public static void main(String[] args) {
//ArrayList<Dog> 表示存放到集合ArrayList中的元素都是Dog类型
//若编译器发现添加的元素不是指定的Dog类型,就会报错的
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("汪汪",10));
arrayList.add(new Dog("旺财",1));
arrayList.add(new Dog("小汪",5));
//遍历:之前传统方式直接取Dog类型的会报错,只能取Object类型的然后再向下转,现在就可以直接取Dog类型的啦
for (Dog d : arrayList) {
System.out.println(d.getName()+":"+d.getAge());
}

}

}
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}
  • 泛型的分类:

    • 类泛型:类定义中使用类型参数代表具体谁的类型,在实例化类时指定类型参数

    • 泛型方法:适用于传许多不同的类型数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class Printer {
      public <T> void print(T data) {
      System.out.println(data);
      }
      }

      // 使用泛型方法打印不同类型的数据
      Printer printer = new Printer();
      printer.print("Hello"); // 打印输出: Hello

      printer.print(123); // 打印输出: 123

      printer.print(3.14); // 打印输出: 3.14
    • 泛型接口:当我们需要定义一个可以适用于不同类型的接口时,就可以使用泛型接口,在实现类中传具体的数据类型

      eg:

      1
      public class StringBox implements Box<String> 

反射获取泛型信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.zhang.reflection;



import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
//通过反射获取泛型
public class Test11 {
//泛型作为参数
public void test01(Map<String,User> map, List<User> list) {

System.out.println("test01");
}
//泛型作为返回类型
public Map<String,User> test02(){
System.out.println("test02");
return null;
}




public static void main(String[] args) throws NoSuchMethodException {
Method method = Test11.class.getMethod("test01", Map.class, List.class);//Method对象
Type[] genericExceptionTypes = method.getGenericParameterTypes();//获得泛型的参数类型
for (Type genericParameterType : genericExceptionTypes) {
System.out.println("#"+genericParameterType);
//ParameterizedType 表示一种参数化类型
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();//强转获得真实参数信息
for(Type actualTypeArgument : actualTypeArguments){
System.out.println(actualTypeArgument);
}
}
}
System.out.println("========");
Method method2 = Test11.class.getMethod("test02",null);
Type genericReturnType = method2.getGenericReturnType();//获得泛型的返回类型
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();//强转获得真实参数信息
for(Type actualTypeArgument : actualTypeArguments){
System.out.println(actualTypeArgument);
}
}
}


}

  • 泛型可作为方法的参数也可作为方法的返回类型

    总的思路是:

    1. 先获得Method 方法
    2. 再获得泛型的参数类型或者返回类型(看泛型在哪个位置)
    3. 强转获得真实参数信息

反射获取注解信息

  • ROM:Object relationship Mapping —–> 对象关系映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.zhang.reflection;

import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//练习反射操作注解
public class Test13 {

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
//创建反射对象
Class c1 = Class.forName("com.zhang.reflection.Student33");
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}

//获得指定注解的value值 类注解是直接由class对象操作
A2 annotationA2 = (A2)c1.getAnnotation(A2.class); //返回Annotation 类型的 强转成A2
String value = annotationA2.value();
System.out.println(value);

//获得指定的属性注解 属性注解 : 先获得class对象,再由生成的Field对象 (抽象属性)操作
Field nameClass = c1.getDeclaredField("id");
B annotation = nameClass.getAnnotation(B.class);//由(抽象)属性获得指定属性注解
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());

//获得指定构造器注解亦同理
Constructor constructor = c1.getDeclaredConstructor(null); //这里注意获取指定的构造器括号里只需写参数类型,不能再写构造器名称了
C annotation1 = (C)constructor.getAnnotation(C.class);
System.out.println(annotation1.value());


}

}

@A2("db_student")
class Student33{
@B(columnName="db_name",type="varchar",length=3)
private String name;
@B(columnName="db_age",type="int",length=10)
private int age;
@B(columnName="db_id",type="int",length=10)
private int id;

@C("CONSTRUCTIR")
public Student33() {
}

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;
}
}

//自定义类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A2 {
String value();
}

//自定义属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface B{
String columnName();
String type();
int length();
}

//构造器注解
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
@interface C{
String value();
}

小总结

抽象对象(瞎编的一个词) 即由class对象下的各种Method/Field/Constructor对象(或它们下的对象)

  • 反射获得类注解

    1. 获得class对象 c1

    2. 获得抽象注解:直接class对象c1 由getAnnotation(指定注解.class) 获得指定(抽象的)类注解

      注意(抽象)类注解获得的时候自动返回Annotation类的,要强转一下,转成指定的类注解

    3. 由(抽象的)类注解.value() 即可获得注解值

  • 反射获得属性注解

    1. 获得class对象 c1
    2. 获得抽象属性:(多一步)由class对象c1 先生成(抽象即Field对象)属性对象:
      • c1.getDeclaredField(name)
    3. (抽象)属性对象 由getAnnotation(指定注解.class) 获得指定(抽象的)类注解
    4. 由(抽象的)类注解.value() 即可获得注解值
  • 反射获得构造器注解

    1. 获得class对象 c1
    2. 获得抽象构造器:(多一步)由class对象c1 先生成(抽象即Constructor对象)构造器对象:
      • c1.getDeclaredConstructor(参数class类)
    3. (抽象)构造器对象 由getAnnotation(指定注解.class) 获得指定(抽象的)类注解
    4. 由(抽象的)类注解.value() 即可获得注解值
  • 反射获得方法注解同理

(总的感觉从反射这一层来看:注解是抽象类的抽象属性/方法/构造器的下一抽象层)

什么类型的抽象对象.getAnnotation(什么类型的注解.class)—->得到抽象的一个注解


注解&&反射&&类加载
https://bxhhf.github.io/2025/03/26/注解-类加载-反射/
作者
bxhhf
发布于
2025年3月26日
许可协议