- 了解枚举的概念
- 掌握枚举的格式
- 掌握枚举的应用场景
- 掌握枚举的使用
(2) 为什么要使用枚举枚举是 Java 中一种特殊的类,它可以定义固定数量的枚举实例,例如:性别、交通信号灯、季节等等
假设我们要定义一个人类,人类中包含姓名和性别。通常会将性别定义成字符串类型,效果如下:
public class Person {
private String name;
private String sex;
public Person() {
}
public Person(String name, String sex) {
this.name = name;
this.sex = sex;
}
// 省略 get/set/toString 方法
}
public class Demo01 {
public static void main(String[] args) {
Person p1 = new Person("张三", "男");
Person p2 = new Person("张三", "abc"); // 因为性别是字符串,所以我们可以传入任意字符串
}
}
不使用枚举存在的问题:可以给性别传入任意的字符串,导致性别是非法的数据,不安全。
(3) 作用(4) 格式一个方法接收的参数是固定范围之内的时候,那么即可使用枚举类型
enum 枚举名 {
第一行都是罗列枚举实例,这些枚举实例直接写大写名字即可。
}
(5) 入门案例
- 定义枚举:MALE 表示男,FEMALE 表示女
enum Gender {
MALE, FEMALE; // 男,女
}
- Perosn 中的性别由 String 类型改为 Gender 枚举类型
public class Person {
private String name;
private Gender gender;
public Person() {
}
public Person(String name, Gender gender) {
this.name = name;
this.gender = gender;
}
// 省略 get/set/toString 方法
}
- 使用时只能传入枚举中的固定值
public class Demo {
public static void main(String[] args) {
Person p1 = new Person("张三", Gender.MALE);
Person p2 = new Person("张三", Gender.FEMALE);
// Person p3 = new Person("张三", "abc"); // 没有定义该数据就会报错
}
}
(6) 枚举中添加成员变量和成员方法
枚举的本质是一个类,所以枚举中还可以有成员变量,成员方法等。
public enum Gender {
MALE("male"), FEMALE("female"); // 就相当于 new 一个对象,需要传入参数
public String tag; // tag 表示某一种性别的标识
// 重写枚举的构造方法(有参构造) 理论上可以拥有无参构造器,但是实际需求不需要用到,所以不用写
Gender(String tag) {
this.tag = tag;
}
// 可以使用 get 方法获取 tag
public String getTag() {
return tag;
}
public void showTag() {
System.out.println("它的性别标志是: " + tag);
}
}
public class Demo {
public static void main(String[] args) {
Person p1 = new Person("张三", Gender.MALE);
Person p2 = new Person("张三", Gender.FEMALE);
Gender.MALE.showTag();
Gender.FEMALE.showTag();
}
}
(7)枚举的红绿灯案例:
public enum Color {
RED("红"),YELLOW("黄"),GREEN("绿");
private String colorTag; // 颜色标识
public String getColorTag() {
return colorTag;
}
Color(String colorTag){
this.colorTag=colorTag;
}
}
二、反射
1. 学习目标
- 了解类的加载过程
- 理解类初始化过程
- 了解类加载器
- 掌握获取 Class 对象的四种方式
- 能够运用反射获取类型的详细信息
- 能够运用反射动态创建对象
- 能够运用反射动态获取成员变量并使用
- 能够运用反射动态获取成员方法并使用
- 能够运用反射获取泛型父类的类型参数
① 类的加载过程类在内存中的生命周期:加载 → 使用 → 卸载
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM 将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。
类的加载又分为三个阶段:
(1)加载:load
就是指将类型的 class 字节码数据读入内存 (得到一个Class 对象)
(2)连接:link
①验证:校验合法性等
②准备:准备对应的内存(方法区),创建 Class 对象,为类变量赋默认值,为静态常量赋初始值。
③解析:把字节码中的符号引用替换为对应的直接地址引用
(3)初始化:initialize(类初始化)即执行 < clinit > 类初始化方法,会给类的静态变量赋初始值
② 类初始化 Ⅰ、哪些操作会导致类的初始化(加载)?- 运行主方法所在的类,要先完成类初始化,再执行 main 方法
- 第一次使用某个类型就是在 new 它的对象,此时这个类没有初始化的话,先完成类初始化再做实例初始化
- 调用某个类的静态成员(类变量和类方法),此时这个类没有初始化的话,先完成类初始化
- 子类初始化时,发现它的父类还没有初始化的话,那么先初始化父类
- 通过反射操作某个类时,如果这个类没有初始化,也会导致该类先初始化
- 测试案例:(了解)
// 父类
class Father{
static{
System.out.println("main方法所在的类的父类(1)"); // 初始化子类时,会初始化父类
}
}
// 继承类
public class TestClinit1 extends Father{
static{
System.out.println("main方法所在的类(2)"); // 主方法所在的类会初始化
}
// main 方法
public static void main(String[] args) throws ClassNotFoundException {
new A(); // 第一次使用 A 就是创建它的对象,会初始化 A 类
B.test(); // 直接使用 B 类的静态成员会初始化 B 类
Class clazz = Class.forName("包名.C"); // 通过反射(获取字节码对象)操作 C 类,会初始化 C 类
}
}
// A 类
class A{
static{
System.out.println("A 类初始化");
}
}
// B 类
class B{
static{
System.out.println("B 类初始化");
}
public static void test(){
System.out.println("B 类的静态方法");
}
}
// C 类
class C{
static{
System.out.println("C 类初始化");
}
}
Ⅱ、哪些使用类的操作,但是不会导致类的初始化(加载)?
- 使用某个类的静态的常量(static final)
- 通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化
- 用某个类型声明数组并创建数组对象时,不会导致这个类初始化
- 测试案例:(了解)
public class TestClinit2 {
// main 方法
public static void main(String[] args) {
System.out.println(D.NUM); // 调用 D 类的常量 D 类不加载
System.out.println(F.num); // 调用 F 类继承 E 类的静态变量,F 类不加载,E 类会加载(父类加载)
F.test(); // 调用 F 类继承 E 类的静态方法,上面 E 类已经加载了,不会再加载一遍(一个类只会加载一次)
G[] arr = new G[5]; // new 这个类的数组,G 类不会加载
}
}
// D 类
class D{
public static final int NUM = 10;
static{
System.out.println("D类的初始化");
}
}
// E 类
class E{
static int num = 10;
static{
System.out.println("E父类的初始化");
}
public static void test(){
System.out.println("父类的静态方法");
}
}
// F 类继承 E 类
class F extends E{
static{
System.out.println("F子类的初始化");
}
}
// G 类
class G{
static{
System.out.println("G类的初始化");
}
}
③ 类加载器
Ⅰ、 类加载器分为:很多开发人员都遇到过 java.lang.ClassNotFoundException或java.lang.NoClassDefError,想要更好的解决这类问题,或者在一些特殊的应用场景,比如需要支持类的动态加载或需要对编译后的字节码文件进行加密解密操作,那么需要你自定义类加载器,因此了解类加载器及其类加载机制也就成了每一个 Java 开发人员的必备技能之一。
-
引导类加载器(Bootstrap Classloader)又称为根类加载器
它负责加载 jre/lib 中的核心库 它本身不是 Java 代码实现的,也不是 ClassLoader 的子类,获取它的对象时往往返回 null
-
扩展类加载器(Extension ClassLoader)
它负责加载 jre/lib/ext 扩展库 它是 ClassLoader 的子类
-
应用程序类加载器(Application Classloader)
它负责加载项目的 classpath 路径下的类 它是 ClassLoader 的子类
-
自定义类加载器
当你的程序需要加载“特定”目录下的类,可以自定义类加载器; 当你的程序的字节码文件需要加密时,那么往往会提供一个自定义类加载器对其进行解码 后面会见到的自定义类加载器:tomcat 中
简单描述:
下一级的类加载器,如果接到任务时,会先搜索是否加载过,如果没有,会先把任务往上传,如果都没有加载过,一直到根加载器,如果根加载器在它负责的路径下没有找到,会往回传,如果一路回传到最后一级都没有找到,那么会报ClassNotFoundException或NoClassDefError,如果在某一级找到了,就直接返回 Class 对象。
- 应用程序类加载器 把扩展类加载器视为父加载器
- 扩展类加载器把引导类加载器视为父加载器
(不是继承关系,是组合的方式实现的。)
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
(通俗来讲:反射就是我们拿到这个类的 Class 类的对象,从而反过来操作这个类的属性、构造器、方法…)
要想解剖一个类,必须先要获取到该类的 Class 对象。而剖析一个类或用反射解决具体的问题就是使用相关 API :(1)java.lang.Class(2)java.lang.reflect.* 。所以 Class 对象是反射的根源。
测试反射:创建一个可以创建任意一个类的对象的类:(最简单的反射类)
public class BeanFactory {
public static Object createBean(String className) throws Exception{
Class clazz = Class.forName(className);
return clazz.newInstance();
}
}
创建 User 类:
public class User {
private String userName="张三";
@Override
public String toString() {
return "User{" +
"userName='" + userName + ''' +
'}';
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
创建 Person 类:
public class Person {
private int age=10;
private String name="张三";
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + ''' +
'}';
}
}
调用:
public static void main(String[] args) throws Exception{
// 目标一:创建 User 类对象
// 需要进行类型强转
User user=(User) BeanFactory.createBean("com.xxxxxxx.object.User"); // 传入权限名(具体位置的包名文件名)
System.out.println("user = " + user);
// 目标二:创建 Person 类对象
Person person=(Person)BeanFactory.createBean("com.xxxxxxx.object.Person");
System.out.println("person = " + person);
}
简单解释就是:在编码时并不知道自己需要操作什么,只有在运行时才知道自己要操作的时哪个类、呢个方法、那个变量。所以只有在写框架(框架是给所有程序员使用的东西)是才会需要运用到反射,平常写代码,能不使用反射就不使用。
① 哪些类型可以获取 Class 对象所有 Java 类型都可以获取 Class 对象
代码示例:
//(1)基本数据类型和void 例如:int.class void.class //(2)类和接口 例如:String.class Comparable.class //(3)枚举 例如:ElementType.class //(4)注解 例如:Override.class //(5)数组 例如:int[].class② 获取 Class 对象的四种方式
(1)类型名.class
- 要求编译期间已知类型
(2)对象.getClass()
- 获取对象的运行时类型
(3)Class.forName(类型全名称) ,通常需要配置文件配置配合使用
- 可以获取编译期间未知的类型
(4)ClassLoader的类加载器对象.loadClass(类型全名称)
- 可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
public class TestReflect {
public static void main(String[] args) throws Exception {
// 1. 第一种方式获取 Class :类型名.class
Class clazz01= Person.class;
// 反射在一般情况下不指定类型(泛型),否则这个框架只能用于某一个类,所以不使用泛型
// 但是可以使用不同的方法直接传递字节码文件创建对象(参见 BeanFactory 中的 public static Object createBean(Class clazz) 方法)
// 2. 第二种方式获取 Class :对象.getClass()
Person person = new Person();
Class clazz02 = person.getClass();
// 3. 第三种方式获取 Class :Class.forName("类的全限定名")
Class clazz03 = Class.forName("com.xxxxxxx.object.Person");
}
}
public class BeanFactory {
public static Object createBean(String className) throws Exception{
Class clazz = Class.forName(className);
return clazz.newInstance();
}
public static Object createBean(Class clazz) throws Exception{
return clazz.newInstance();
// 这种方式表示想创建哪个类就把哪个类的字节码对象传递过来
}
public static void main(String[] args) throws Exception{
// 目标一:创建 User 类对象
// 需要进行类型强转
User user=(User) BeanFactory.createBean("com.xxxxxxx.object.User"); // 传入权限名
System.out.println("user = " + user);
// 目标二:创建 Person 类对象 传递权限类名
Person person01=(Person) BeanFactory.createBean("com.xxxxxxx.object.Person");
System.out.println("person = " + person01);
// 目标三:传入字节码对象,创建 Person 类对象
Person person02=(Person) BeanFactory.createBean(Person.class);
System.out.println("person = " + person02);
// Person 类 和 User 类 参见上方
}
}
(3) 反射的概念
(4) 反射的应用场景反射是一种机制/功能,利用该机制/功能可以在程序运行过程中对类进行解剖并操作类中的构造方法,成员方法,成员属性。
(5) 反射的应用 ① 获取类型的详细信息主要应用于:各种框架的设计(主要场景)
各大框架的内部实现也大量使用到了反射机制,所以要想学好这些框架,则必须要求了解反射机制
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)
Ⅰ、 获取包信息
Package pkg = clazz.getPackage();
Ⅱ、 获取修饰符(类的修饰符)
int mod = clazz.getModifiers();
修饰符定义在 Modifier 类中,该类里面有很多常量值,每一个常量对应一种修饰符
源码:
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;
Ⅲ、 获取类名
String name = clazz.getName();
Ⅳ、 获取父类的字节码对象
Class superclass = clazz.getSuperclass();
Ⅴ、 获取该类实现的所有接口的字节码对象
Class[] interfaces = clazz.getInterfaces();
Ⅵ、 获取该类的属性(成员变量)(非静态)
Field[] declaredFields = clazz.getDeclaredFields();
clazz.getField(); // 根据属性名获取属性对象,但是这个方法能够获取父类和自己的公有属性 clazz.getFields(); // 获取父类和自己的所有公有属性 clazz.getDeclaredField(); // 根据属性名获取自己的属性(无论公有还是私有) clazz.getDeclaredFields(); // 根据属性名获取自己的所有的属性(无论公有还是私有)
Ⅶ、 获取该类的构造方法
Method[] declaredMethods = clazz.getDeclaredMethods();
clazz.getConstructor(); // 获取父类和自己的构造方法 clazz.getConstructors(); // 获取父类和自己的所有构造方法 clazz.getDeclaredConstructor(); // 获取自己的构造方法(无论公有还是私有) clazz.getDeclaredConstructors(); // 获取自己的所有构造方法(无论公有还是私有)
Ⅷ、 获取该类的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
clazz.getMethod(); // 获取父类和自己的构造方法 clazz.getMethods(); // 获取父类和自己的所有构造方法 clazz.getDeclaredMethod(); // 获取自己的构造方法(无论公有还是私有) clazz.getDeclaredMethods(); // 获取自己的所有构造方法(无论公有还是私有)② 使用反射创建任意引用类型的对象(重点)
两种方式:
- 直接通过 Class 对象来实例化(要求必须有无参构造)
- 通过获取构造器对象来进行实例化
方式一的步骤:
(1)获取该类型的 Class 对象
(2)创建对象
@Test
public void test()01throws Exception{
Class> clazz = Class.forName("com.xxxxxxx.test.Student");
// Caused by: java.lang.NoSuchMethodException: com.xxxxxxx.test.Student.()
// 即说明 Student 没有无参构造,就没有无参实例初始化方法
Object stu = clazz.newInstance();
System.out.println(stu);
}
@Test
public void test02() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
// 使用第一种方式创建 Person 类的对象
// 强转一定是建立在父子关系的前提下
Person person = (Person) clazz.newInstance();
System.out.println(person);
}
方式二的步骤:
(1)获取该类型的 Class 对象
(2)获取构造器对象
(3)创建对象
如果构造器的权限修饰符修饰的范围不可见,也可以调用 setAccessible(true)
Person 类:
public class Person {
private int age=10;
private String name="张三";
public String address="火星";
public Person(){
System.out.println("执行了无参构造器");
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Person(String name, String address) {
this.name = name;
this.address = address;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + ''' +
", address='" + address + ''' +
'}';
}
}
示例代码:
import java.lang.reflect.Constructor;
public class TestCreate {
public static void main(String[] args) throws Exception {
// 获取 Person 类的 Class 对象
Class clazz=Person.class;
// 1. 使用第一种方式创建 Person 类的对象
// 强制类型转换一定是建立在父子关系的前提下
Person person01 =(Person)clazz.newInstance();
System.out.println(person01);
// 2. 使用第二种方式创建 Person 对象
// 获取无参的构造方法
Constructor constructor01= clazz.getConstructor();
// 获取有参的构造方法
Constructor constructor02= clazz.getConstructor(String.class, String.class);
// 使用无参构造方法创建对象
Person person02=(Person) constructor01.newInstance();
System.out.println(person02);
// 使用有参构造方法创建对象
Person person03=(Person)constructor02.newInstance("无名氏", "无家可归");
System.out.println(person03);
}
}
③ 操作任意类型的属性(重点)
Ⅰ、获取该类型的 Class 对象
Class clazz = Class.forName("com.xxxxxxx.bean.User");
Ⅱ、获取属性对象
Field field = clazz.getDeclaredField("username");
Ⅲ、设置属性可访问
field.setAccessible(true);
Ⅳ、创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = clazz.newInstance();
Ⅴ、设置属性值
field.set(obj,"chai");
Ⅵ、获取属性值
Object value = field.get(obj);
如果操作静态变量,那么实例对象可以省略,用 null 表示,当然一般不会使用反射操作静态变量
示例代码:
仍然使用上述案例中的 Person 类:
public static void main(String[] args) throws Exception {
// 获取 Person 类的字节码对象
Class clazz=Person.class;
// 获取创建对象
Object obj = clazz.newInstance();
// 获取 Person 类的所有属性(只能获取自己的,包含公有的和私有的)
Field[] declaredFields= clazz.getDeclaredFields();
for (Field declaredField:declaredFields){
// 获取每个属性的属性名和属性值
// 获取属性名
String name = declaredField.getName();
// 获取属性的类型
Class> type = declaredField.getType();
// 获取属性的修饰符
int modifiers = declaredField.getModifiers();
// 获取属性的值
// (1)这样操作只能获取私有之外的值
if (modifiers==1) {
Object value = declaredField.get(obj); // 这里传递 person 的对象,这里的操作就等值于 对象.属性名 的操作,私有的不能访问
System.out.println(name+","+value+","+type+","+modifiers);
}
// (2)暴力反射:通过反射可以访问类的私有成员
declaredField.setAccessible(true);
Object value = declaredField.get(obj);
System.out.println(name+","+value+","+type+","+modifiers);
}
}
public static void main(String[] args) throws Exception {
// 获取Person的字节码对象
Class clazz = Person.class;
Object obj = clazz.newInstance();
// 单独获取某一个属性,比如获取 name
Field filed = clazz.getDeclaredField("address");
// 设置其属性值为"北京" (相当于 person。address="北京";)
filed.set(obj,"北京");
// 获取其属性值(这里 address 属于 public 不需要使用暴力反射)
String address = (String) filed.get(obj);
System.out.println(address);
}
④ 调用任意类型的方法
(1)获取该类型的 Class 对象
Class clazz = Class.forName("com.xxxxxxx.service.UserService");
(2)获取方法对象
Method method = clazz.getDeclaredMethod("login",String.class,String.class);
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj,"chai","123);
如果方法的权限修饰符修饰的范围不可见,也可以调用 setAccessible(true)
如果方法是静态方法,实例对象也可以省略,用 null 代替
示例代码:
public static void main(String[] args) throws Exception {
// 使用反射操作类的方法:获取方法、调用方法
// 1. 获取类的字节码对象
Class clazz= Person.class;
Object obj = clazz.newInstance();
// 2. 获取某一个方法,例如: getName()
// 获取无参的 getName 方法
Method getNameMethod = clazz.getDeclaredMethod("getName");
// 获取带一个 String 类型参数的 study 方法
Method studyMethod = clazz.getDeclaredMethod("study", String.class, int.class);
// 调用方法
String name = (String) getNameMethod.invoke(obj);
System.out.println("获取到的name:" + name);
// 暴力反射
studyMethod.setAccessible(true);
studyMethod.invoke(obj,"Java",180);
}
⑤ Type 接口的介绍(了解)
Ⅰ、 使用反射获取 Typejava.lang.reflect.Type 接口及其相关接口用于描述 Java 中用到的所有类型,是 Java 的反射中很重要的组成部分。Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
有很多场景下我们可以获得Type,比如:
- 当我们拿到一个 Class,用 Class.getGenericInterfaces() 方法得到 Type[],也就是这个类实现接口的 Type 类型列表。
- 当我们拿到一个 Class,用 Class.getDeclaredFields() 方法得到 Field[],也就是类的属性列表,然后用Field. getGenericType()方法得到这个属性的 Type 类型。
- 当我们拿到一个 Method,用 Method.getGenericParameterTypes() 方法获得 Type[],也就是方法的参数类型列表。
- 当我们拿到一个 Class,用 clazz.getGenericSuperclass() 这样就可以获取父类的泛型实参列表。
Type 接口包含了一个实现类(Class)和四个实现接口(TypeVariable, ParameterizedType, GenericArrayType, WildcardType),这四个接口都有自己的实现类,但这些实现类开发都不能直接使用,只能用接口。
- Class : 当需要描述的类型是普通 Java 类、数组、自定义类、 8种 Java 基本类型 的时候,Java 会选择 Class 来作为这个 Type 的实现类,我们甚至可以直接把这个 Type 强行转换类型为 Class 。这些类基本都有一个特点:基本和泛型无关,其他4种 Type 的类型,基本都是泛型的各种形态。
- ParameterizedType :当需要描述的类是泛型类时,比如 List,Map 等,不论代码里写没写具体的泛型,Java 会选择ParameterizedType 接口做为 Type 的实现。ParameterizedType 接口有 getActualTypeArguments() 方法,用于得到泛型的 Type 类型数组。
- GenericArrayType :当需要描述的类型是泛型类的数组时,比如比如 List[],Map[],Type 用 GenericArrayType 接口作为Type 的实现。GenericArrayType 接口有 getGenericComponentType() 方法,得到数组的组件类型的 Type 对象。
- WildcardType :当需要描述的类型是泛型类,而且泛型类中的泛型被定义为(? extends xxx)或者(? super xxx)这种类型,比如 List extends TestReflect>,这个类型首先将由 ParameterizedType 实现,当调用 ParameterizedType 的 getActualTypeArguments() 方法后得到的 Type 就由 WildcardType 实现。
示范代码:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class TestType {
public static void main(String[] args) {
// 获取 Son 类的字节码对象
Class clazz=Son.class;
// 通过子类的字节码对象获取父类的泛型
Type type = clazz.getGenericSuperclass();
// 进行强制类型转换
ParameterizedType parameterizedType=(ParameterizedType) type;
// 获取类型的泛型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type typeArgument : actualTypeArguments) {
System.out.println(typeArgument);
}
}
class Father{}
class Son extends Father{}
}
⑥ 获取泛型父类信息
示例代码获取泛型父类信息:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class TestType {
public static void main(String[] args) {
// 需求:在运行时,获取 Son 类型的泛型父类的泛型实参
// 获取 Son 类的字节码对象
Class clazz=Son.class;
// 通过子类的字节码对象获取父类的泛型
// getSuperclass() 只能得到父类名,无法得到父类的泛型实参列表
Type type = clazz.getGenericSuperclass();
// 进行强制类型转换
// Father 属于 ParameterizedType
ParameterizedType parameterizedType=(ParameterizedType) type;
// 获取父类类型的泛型泛型实参列表
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type typeArgument : actualTypeArguments) {
System.out.println(typeArgument);
}
}
// 泛型形参:
class Father{}
// 泛型实参:
class Son extends Father{}
}
⑦ 动态创建和操作任意类型的数组
在 java.lang.reflect 包下还提供了一个 Array 类,Array 对象可以代表所有的数组。程序可以通过使用 Array 类来动态的创建数组,操作数组元素等。
Array 类提供了如下几个方法:
- public static Object newInstance(Class> componentType, int... dimensions):创建一个具有指定的组件类型和维度的新数组。
- public static void setXxx(Object array,int index,xxx value):将 array 数组中 [index] 元素的值修改为 value 。此处的 Xxx 对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用 set(Object array,int index, Object value) 方法。
- public static xxx getXxx(Object array,int index,xxx value):将 array 数组中 [index] 元素的值返回。此处的 Xxx 对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用 get(Object array,int index) 方法。
public static void main(String[] args) {
// 使用反射操作数组
// 1. 使用反射创建一个 String 类型的数组,长度是5
Object array = Array.newInstance(String.class, 5);
// 2. 往数组中存入数据
for (int i=0;i<5;i++){
Array.set(array,i,"value"+i); // set 方法没有返回值
}
// 使用 Array 获取数组中的元素
for (int i=0;i<5;i++){
System.out.println(Array.get(array, i)); // get 方法的返回值类型是 Object
}
}
三、 注解
1. 学习目标
- 了解注解的概念
- 了解 JDK 提供的三种基本注解
- 掌握自定义注解
- 掌握元注解
- 掌握注解解析
② 注解的作用:注解英文是 annotation ,是一种代码级别的说明,(注解的本质就是一种特殊的接口)和类、接口平级关系。相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac 编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上定义
(2) JDK 提供的三个基本的注解①执行编译期的检查,例如:@Override
②分析代码(主要用途:替代配置文件)
③用在框架里面,简化框架的开发,注解开发
- @Override :描述方法的重写
- @SuppressWarnings :压制警告
- @Deprecated :标记过时,不推荐使用
语法: @interface 注解名{}
代码示例:
// 定义注解
public @interface Annotation01 {
}
② 注解属性
Ⅰ、 注解属性的作用
Ⅱ、 注解属性的类型注解属性可以让注解具备携带存储数据的功能
- 基本类型
- String
- 枚举类型
- 注解类型
- Class 类型
- 以上类型的一维数组类型
==注意:一旦注解有属性了,使用注解的时候,属性必须有值 ==
格式:
@注解名(属性名=值,属性名2=值2) 例如:@MyAnnotation3(i = 0,s="23")Ⅳ、 属性赋值的特殊情况
若属性类型的一维数组的时候,当数组的值只有一个的时候可以省略{}
@MyAnnotation4(ss = { "a" })
@MyAnnotation4(ss = "a")
注解属性可以有默认值
属性类型 属性名() default 默认值;
若属性名为 value 的时候,且只有这一个属性需要赋值的时候可以省略 value(重点)
Ⅴ、代码示例:
public @interface MyAnnotation01 {
String str() default "张三";
int num();
Color color() default Color.YELLOW;
Class clazz();
MyAnnotation02 myAnnotation02();
String[] value();
}
public @interface MyAnnotation02 {
}
import static com.xxxxxxx.object.Color.RED;
// 在这个类中使用自定义注解
// 这里可以使用注解
public class UseAnnotation {
// 这里可以使用注解
private int age;
@MyAnnotation01( num = 0, color = RED, clazz = UseAnnotation.class, myAnnotation02 = @MyAnnotation02, value = {"a","b","c"}) // 这里可以使用注解
public void say( String name){ //这里可以使用注解
System.out.println(name);
}
public void study(){
System.out.println("努力学习!");
}
}
public enum Color {
RED,YELLOW,GREEN;
}
(4) 元注解
① 元注解的作用
② 常用的元注解元注解是使用在自定义的注解上的注解,为自定义的注解提供支持
-
@Target:定义该注解作用在什么上面(位置),默认注解可以在任何位置,值为: ElementType 的枚举值
- METHOD:方法
- TYPE:类 接口
- FIELD:字段
- CONSTRUCTOR:构造方法声明
-
@Retention:定义该注解保留到那个代码阶段,值为:RetentionPolicy 类型,默认只在源码阶段保留
- SOURCE:只在源码上保留(默认)
- CLASS:在源码和字节码上保留
- RUNTIME:在所有的阶段都保留 (一般都保留到运行时)
java (源码阶段) ——编译——→ .class(字节码阶段) ——加载内存——→ 运行(RUNTIME)
代码示例:
@Target(value = {ElementType.METHOD,ElementType.TYPE }) // 表示注解只能用在哪些位置
@Retention(value = RetentionPolicy.RUNTIME) // 表示该注解保留到哪个阶段
public @interface MyAnnotation03 {
int a();
String b();
}
(5) 注解解析
java.lang.reflect.AnnotatedElement 接口 ,Class(类)、Method(方法)、Field(属性)、Constructor(构造函数) 等实现了 AnnotatedElement 接口
- T getAnnotation(Class
annotationType):得到指定类型的注解引用,没有就返回 null(使用实现类调用) - boolean isAnnotationPresent(Class annotationType):判断指定的注解是否存在(使用实现类调用)
- Annotation[] getAnnotations():得到所有的注解,包含从父类继承下来的注解
- Annotation[] getDeclaredAnnotations():得到自己身上的注解
代码示例:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation01 {
String str() default "张三";
int num();
Color color() default Color.YELLOW;
Class clazz();
MyAnnotation02 myAnnotation02();
String[] value();
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation02 {
}
public class UseAnnotation {
private int age;
@MyAnnotation02
@MyAnnotation01(str = "李四", num = 1, color = Color.RED, clazz = UseAnnotation.class, myAnnotation02 = @MyAnnotation02, value = {"a","b","c"}) // 这里可以使用注解
public void say(String name){
System.out.println(name);
}
public void study(){
System.out.println("努力学习!");
}
}
public enum Color {
RED,YELLOW,GREEN;
}
import java.lang.reflect.Method;
public class TestAnnotation {
public static void main(String[] args) throws Exception {
// 注解解析
// 目标一. 判断 UserAnnotation 类的所有方法上是否包含 MyAnnotation02 的注解
// (1)使用反射获取该类的所有方法
Class clazz=UseAnnotation.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
// (2)遍历出每一个方法,判断该方法是否包含 MyAnnotation02 的注解
for (Method declaredMethod : declaredMethods) {
boolean annotationPresent = declaredMethod.isAnnotationPresent(MyAnnotation02.class);
System.out.println(declaredMethod.getName()+","+annotationPresent);
}
// 目标二. 获取 UserAnnotation 类的 say 方法上的 MyAnnotation01 的注解,并且要拿到该注解的所有属性
// (1)使用反射获取 say 方法
Method say = clazz.getDeclaredMethod("say", String.class);
// (2) 获取到 say 方法上的 MyAnnotation01 的注解
MyAnnotation01 myAnnotation01 = say.getDeclaredAnnotation(MyAnnotation01.class);
System.out.println(myAnnotation01.str());
System.out.println(myAnnotation01.num());
System.out.println(myAnnotation01.annotationType());
System.out.println(myAnnotation01.clazz());
System.out.println(myAnnotation01.color());
System.out.println(myAnnotation01.value());
}
}



