gzypersonal 发表于 2021-11-29 16:28

java枚举、反射以及注解

## 一 枚举

#### 1.1 枚举的概述

枚举是 Java 中一种特殊的类,它可以定义固定数量的枚举实例,例如: 性别、交通信号灯、季节等等

#### 1.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方法
}
```

```java
public class Demo01 {
    public static void main(String[] args) {
      Person p1 = new Person("张三", "男");
      Person p2 = new Person("张三", "abc"); // 因为性别是字符串,所以我们可以传入任意字符串
    }
}
```

不使用枚举存在的问题:可以给性别传入任意的字符串,导致性别是非法的数据,不安全。

#### 1.3 作用

一个方法接收的参数是固定范围之内的时候,那么即可使用枚举类型

#### 1.4 格式

```java
enum 枚举名 {
    第一行都是罗列枚举实例,这些枚举实例直接写大写名字即可。
}
```

#### 1.5 案例

1. 定义枚举:MALE表示男,FEMALE表示女

```java
enum Gender {
    MALE, FEMALE; // 男,女
}
```

2. Perosn中的性别有String类型改为Sex枚举类型

```java
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方法
}
```

3. 使用是只能传入枚举中的固定值

```java
public class Demo02 {
    public static void main(String[] args) {
      Person p1 = new Person("张三", Gender.MALE);
      Person p2 = new Person("张三", Gender.FEMALE);
      Person p3 = new Person("张三", "abc");//报错
    }
}
```

#### 1.6 枚举中添加成员变量和成员方法

枚举的本质是一个类,所以枚举中还可以有成员变量,成员方法等。

```java
public enum Gender {
    MALE("male"), MALE("female");
    public String tag;

    Sex(String tag) {
      this.tag = tag;
    }

    public void showTag() {
      System.out.println("它的性别标志是: " + tag);
    }
}
```

```java
public class Demo03 {
    public static void main(String[] args) {
      Person p1 = new Person("张三", Sex.MALE);
      Person p2 = new Person("李四", Sex.MALE);

      Sex.BOY.showTag();//它的性别标志是male
    }
}
```

## 二反射

2.1 反射的概念

反射是一种机制/功能,利用该机制/功能可以在**程序运行**过程中对类进行解剖并操作类中的构造方法,成员方法,成员属性。

####2.2反射的应用场景

各种框架的设计(主要场景)

各大框架的内部实现也大量使用到了反射机制,所以要想学好这些框架,则必须要求了解反射机制

#### 2.3 反射的应用

##### 2.3.1 获取类型的详细信息

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)

###### 2.3.1.1 获取包信息

```java
Package pkg = clazz.getPackage();
```

###### 2.3.1.2 获取修饰符

```java
int mod = clazz.getModifiers();
```

修饰符定义在Modifier类中,该类里面有很多常量值,每一个常量对应一种修饰符

###### 2.3.1.3 获取类名

```java
String name = clazz.getName();
```

###### 2.3.1.4 获取父类的字节码对象

```java
Class superclass = clazz.getSuperclass();
```

###### 2.3.1.5 获取该类实现的所有接口

```java
Class[] interfaces = clazz.getInterfaces();
```

###### 2.3.1.6 获取该类的所有属性

```
Field[] declaredFields = clazz.getDeclaredFields();
```

###### 2.3.1.7 获取该类的所有构造函数

```java
Method[] declaredMethods = clazz.getDeclaredMethods();
```

###### 2.3.1.8 获取该类的所有方法

```java
Method[] declaredMethods = clazz.getDeclaredMethods();
```

例子

```java
//Person类
package reflect;

public class Person implements Hello{
    private String name="张三";
    private int age;
    public String color;
    @Override
    public String toString() {
      return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public Person() {
      System.out.println("执行了无参构造");
    }

    public Person(String name) {
      this.name = name;
    }

    public Person(String name, int age, String color) {
      this.name = name;
      this.age = age;
      this.color = color;
    }

    public Person(String name, int age) {
      this.name = name;
      this.age = age;
    }

    public String getName() {
      return name;
    }

    @Override
    public void sayHello() {
      System.out.println("你好");
    }
    private void study(String course,int day){
      System.out.println("努力学习"+course+""+day+"天");
    }
}
//测试类
package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TestReflect {
    public static void main(String[] args) throws ClassNotFoundException {
//                        第一种
      //   Class clazz=Person.class;
//      第二种
//      Person person=new Person();
//      Class clazz=person.getClass();
//      第三种
      Class clazz = Class.forName("reflect.Person");
//             1.使用反射获取包
      Package pac = clazz.getPackage();
      String pacName = pac.getName();
      System.out.println(pacName);
//      2.获取修饰符,获取类的修饰符,1代表public,2代表private
      int modifiers = clazz.getModifiers();
      System.out.println(modifiers);
//      3.获取类名
      String clazzName = clazz.getName();
      System.out.println(clazzName);
      String simpleName = clazz.getSimpleName();
      System.out.println(simpleName);
//      4.获取父类的字节码对象,默认继承object
      Class superName=clazz.getSuperclass();
//      5.获取该类实现的接口的字节码对象
      Class[] interfaces = clazz.getInterfaces();
      for (Class anInterface : interfaces) {
            System.out.println(anInterface);
      }
//      6.获取该类的所有属性(成员变量),getDeclaredFields获取自己的公有和私有,getFields获取自己何父类的共有属性
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
            System.out.println(field);
      }
      System.out.println("=============");
      Field[] fields1 = clazz.getFields();
      for (Field field : fields1) {
            System.out.println(field);
      }
      System.out.println("==============");
//      7.获取类的所有构造函数
      Constructor[] constructors = clazz.getConstructors();
      for (Constructor constructor : constructors) {
            System.out.println(constructor);
      }
      System.out.println("-------------------");
      Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
      for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor.getParameterCount());
      }
//      参数个数
//      8.获取类的所有方法
      Method[] methods = clazz.getMethods();
      for (Method method : methods) {
            System.out.println(method);
      }
      System.out.println("-----------");
      Method[] declaredMethods = clazz.getDeclaredMethods();
      for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
      }

    }

}


```

##### 2.4.2创建任意引用类型的对象(重点)

两种方式:

1、直接通过Class对象来实例化(要求必须有无参构造)

2、通过获取构造器对象来进行实例化

**方式一的步骤**:

(1)获取该类型的Class对象(2)创建对象

```java
               Class clazz = Person.class;
//      使用第一种方式创建Person类对象
//强转一定是建立在父子关系的前提下
      Person person = (Person) clazz.newInstance();
      System.out.println(person);
```

**方式二的步骤:**

(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象

> 如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

示例代码:

```java
public static void main(String[] args) throws Exception {
    //获取Person类的Class对象
    Class clazz = Person.class;
    //使用第二种方式创建Person类的对象
    //      使用构造函数创建对象
    //获取无参的构造函数
   Constructor declaredConstructor = clazz.getDeclaredConstructor();
    Person person1 = (Person) declaredConstructor.newInstance();
    System.out.println(person1);
//      获取有参数的构造函数创建对象
      Constructor declaredConstructor1 = clazz.getDeclaredConstructor(String.class, int.class);
      Person person2 = (Person) declaredConstructor1.newInstance("李四", 15);
      System.out.println(person2);
}
```

##### 2.3.3 操作任意类型的属性(重点)

(1)获取该类型的Class对象

```java
Class clazz = Class.forName("reflect.Person");
```

(2)获取属性对象

```java
Field field = clazz.getDeclaredField("username");
```

(3)设置属性可访问(暴力反射)

```java
field.setAccessible(true);
```

(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象

```java
Object obj = clazz.newInstance();
```

(4)设置属性值

```java
field.set(obj,"chai");
```

(5)获取属性值

```java
Object value = field.get(obj);
```

> 如果操作静态变量,那么实例对象可以省略,用null表示,当然一般不会使用反射操作静态变量

示例代码:

```java
public static void main(String[] args) throws Exception {
    //1. 获取Person的字节码对象
    Class clazz = Person.class;

    Object obj = clazz.newInstance();
    //2.1 获取Person的所有属性(只能获取自己的,包含公有的和私有的)
    /*Field[] declaredFields = clazz.getDeclaredFields();
      for (Field declaredField : declaredFields) {
            //获取每个属性的属性名和属性值
            //获取属性名
            String name = declaredField.getName();
            //获取属性的类型
            Class<?> type = declaredField.getType();
            //获取属性的修饰符
            int modifiers = declaredField.getModifiers();

            //暴力反射: 通过反射可以访问类的私有成员
            declaredField.setAccessible(true);

            //获取属性的值
            Object value = declaredField.get(obj); //等值于 对象.属性名

            System.out.println(name + "," + value + "," + type + "," + modifiers);
      }*/

    //2.2 单独获取某一个属性,比如获取name
      Field filed = clazz.getDeclaredField("name");
//      设置其属性值为王五,如果是私有属性,则需要暴力获取
      filed.setAccessible(true);
      filed.set(obj,"王五");
      String str = (String) filed.get(obj);
      System.out.println(str);
}
```

##### 2.3.4 调用任意类型的方法

(1)获取该类型的Class对象

```java
Class clazz = Class.forName("reflect.Person");
```

(2)获取方法对象

```java
Method method = clazz.getDeclaredMethod("login",String.class,String.class);
```

(3)创建实例对象

```java
Object obj = clazz.newInstance();
```

(4)调用方法

```java
Object result = method.invoke(obj,"chai","123);
```

> 如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
>
> 如果方法是静态方法,实例对象也可以省略,用null代替

示例代码:

```java
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);//invoke先当与执行方法
    System.out.println("获取到的name:" + name);

    //暴力反射
    studyMethod.setAccessible(true);
    studyMethod.invoke(obj,"Java",180);
}
```

##### 2.3.5 Type接口的介绍(了解)

`java.lang.reflect.Type`接口及其相关接口用于描述java中用到的所有类型,是Java的反射中很重要的组成部分。Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。

######2.3.5.1 使用反射获取Type

有很多场景下我们可以获得Type,比如:

1. 当我们拿到一个Class,用`Class.getGenericInterfaces()`方法得到Type[],也就是这个类实现接口的Type类型列表。

2. 当我们拿到一个Class,用`Class.getDeclaredFields()`方法得到Field[],也就是类的属性列表,然后用`Field. getGenericType()`方法得到这个属性的Type类型。

3. 当我们拿到一个Method,用`Method.getGenericParameterTypes()`方法获得Type[],也就是方法的参数类型列表。

4. 当我们拿到一个Class,用`clazz.getGenericSuperclass()`这样就可以获取父类的泛型实参列表

###### 2.3.5.2 Type的分类

Type接口包含了一个实现类(Class)和四个实现接口(TypeVariable, ParameterizedType, GenericArrayType, WildcardType),这四个接口都有自己的实现类,但这些实现类开发都不能直接使用,只能用接口。

1. Class:当需要描述的类型是普通Java类、数组、自定义类、 8种java基本类型 的时候, java会选择Class来作为这个Type的实现类,我们甚至可以直接把这个Type强行转换类型为Class。这些类基本都有一个特点:**基本和泛型无关**,其他4种Type的类型,基本都是泛型的各种形态。
2. ParameterizedType: 当需要描述的类是**泛型类**时,比如List,Map等,不论代码里写没写具体的泛型,java会选择ParameterizedType接口做为Type的实现。ParameterizedType接口有getActualTypeArguments()方法,用于得到泛型的Type类型数组。
3. GenericArrayType:当需要描述的类型是**泛型类的数组**时,比如比如List[],Map[],type用GenericArrayType接口作为Type的实现。GenericArrayType接口有getGenericComponentType()方法,得到数组的组件类型的Type对象。
4. WildcardType: 当需要描述的类型是泛型类,而且泛型类中的泛型被定义为(? extends xxx)或者(? super xxx)这种类型,比如List<? extends TestReflect>,这个类型首先将由ParameterizedType实现,当调用ParameterizedType的getActualTypeArguments()方法后得到的Type就由WildcardType实现。

##### 2.3.6 获取泛型父类信息

示例代码获取泛型父类信息:

```java
public class TestGeneric {
      public static void main(String[] args) {
                //需求:在运行时,获取Son类型的泛型父类的泛型实参<String,Integer>
               
                //(1)还是先获取Class对象
                Class clazz = Son.class;//四种形式任意一种都可以
               
                //(2)获取泛型父类
                /*
               * getSuperclass()只能得到父类名,无法得到父类的泛型实参列表
               */
                Type type = clazz.getGenericSuperclass();
               
                // Father<String,Integer>属于ParameterizedType
                ParameterizedType pt = (ParameterizedType) type;
               
                //(3)获取泛型父类的泛型实参列表
                Type[] typeArray = pt.getActualTypeArguments();
                for (Type type2 : typeArray) {
                        System.out.println(type2);
                }
      }
}
//泛型形参:<T,U>
class Father<T,U>{
      
}
//泛型实参:<String,Integer>
class Son extends Father <String,Integer>{
      
}
```

##### 2.3.7 动态创建和操作任意类型的数组

在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数组中元素的值修改为value。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用set(Object array,int index, Object value)方法。

public static xxx getXxx(Object array,int index,xxx value):将array数组中元素的值返回。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用get(Object array,int index)方法。

```java
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);
    }

    //使用Array获取数组中的元素
    for (int i=0;i<5;i++){
      System.out.println(Array.get(array, i));
    }
}
```

## 三 注解

#### 3.1 注解概述

##### 3.1.1 什么是注解

注解英文是annotation,是一种代码级别的说明,和类 接口平级关系。相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上定义

##### 3.1.2 注解的作用

执行编译期的检查 例如:@Override   

分析代码(主要用途:替代配置文件);   用在框架里面, 注解开发



#### 3.2 JDK提供的三个基本的注解

1. `@Override`:描述方法的重写.

2. `@SuppressWarnings`:压制警告.

3. `@Deprecated`:标记过时

#### 3.3 自定义注解(重点)

##### 3.3.1 自定义注解语法

**语法**:`@interface 注解名{}`

**示例**

```java
/**
* 定义了注解
*
*/
public @interface Annotation01 {

}
```

##### 3.3.2 注解属性

###### 3.3.2.1 注解属性的作用

注解属性可以让注解具备携带存储数据的功能

###### 3.3.2.2 注解属性的类型

1. 基本类型

​                        2.String

​                        3.枚举类型

​                        4.注解类型

​                        5.Class类型

​                        6.以上类型的一维数组类型

**注意:**

​      一旦注解有属性了,使用注解的时候,属性必须有值

- 示例代码

```java
/**
*注解的属性; 格式和接口的方法很类似
*      1.基本类型
      2.String
      3.枚举类型
      4.注解类型
      5.Class类型
      6.以上类型的一维数组类型

*/
public @interface Annotation02 {
      int a();//基本类型
      
      String b();//String
      
      Color c();//枚举类型
      
      Annotation01 d();//注解类型
      
      Class e();//Class类型
      
      String[] f();//一维数组类型
      
}
```

###### 3.3.2.3 使用注解时给属性赋值

- 格式

```java
@注解名(属性名=值,属性名2=值2)实例:@MyAnnotation3(i = 0,s="23")
```

###### 3.3.2.4 属性赋值的特殊情况

- **若属性类型的一维数组的时候,当数组的值只有一个的时候可以省略{}**

```java
@MyAnnotation4(ss = { "a" })
@MyAnnotation4(ss = "a")
```

- 注解属性可以有默认值

```java
属性类型 属性名() default 默认值;
```

* **若属性名为value的时候,且只有这一个属性需要赋值的时候可以省略value**【重点】

实例:

```java
//注解1类
package myAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)//在运行时解析
public @interface MyAnnotation {
//    注解的作用
//    1.作为标记
//    2.用于代替配置文件,存储一些配置信息
//    3.注解但凡定义了属性,那么在使用注解的时候,就要给每一个注解的属性进行赋值
//    4.注解属性可以使用default进行赋值()默认值,被赋予默认值的属性,我们在使用该注解的时候可以不进行赋值
//    5.如果注解属性是字符属性,但赋值的时候该数组只有一个元素,那么可以省略这个数组的大括号
//    6.如果只有一个注解属性必须赋值,并且这个注解属性的属性名是value,那么赋值的时候可以省略
//元注解
//    1.Target表示注解只能用在哪些位置
//    2.Retention 表示该注解保留到哪个阶段,自定义注解一般都需要保存在RUNTIME时

    String str() default "小高";
    int num() default 12;
    Color color() default Color.YRLLOW;
    Class clas() default UseAnnotation.class;
    MyAnnotation2 anno() default @MyAnnotation2;
    String[] value();
}
//注解2类
package myAnnotation;
public @interface MyAnnotation2 {
}
//枚举类
package myAnnotation;
public enum Color {
    RED,GREEN,YRLLOW;
}
//测试类
package myAnnotation;
//    在这个类中使用自定义注解
public class UseAnnotation {
    private int age;
    //使用默认赋值
    @MyAnnotation(num = 0, clas = UseAnnotation.class, anno = @MyAnnotation2(), value = "省略大括号")
    public void say(String name) {
      System.out.println(name);
    }
//    @MyAnnotation({"你好"})//省略value=
    public void study() {
      System.out.println("努力学习...");
    }
}

```

#### 3.4 元注解

##### 3.4.1 元注解的作用

元注解是使用在自定义的注解上,为自定义的注解提供支持的

##### 3.4.2 常用的元注解

`@Target`:定义该注解作用在什么上面(位置),默认注解可以在任何位置. 值为:`ElementType`的枚举值

​                `METHOD`:方法

​                `TYPE`:类 接口

​                `FIELD`:字段

​                `CONSTRUCTOR`:构造方法声明

`@Retention`:定义该注解保留到那个代码阶段, 值为:`RetentionPolicy`类型,**默认只在源码阶段保留**

​                `SOURCE`:只在源码上保留(默认)

​                `CLASS`:在源码和字节码上保留

​                `RUNTIME`:在所有的阶段都保留

java (源码阶段) ----编译---> .class(字节码阶段) ----加载内存--> 运行(RUNTIME)

eg:

```java
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAnnotation03 {
      int a();
      String b();
}
```

#### 3.5 注解解析

java.lang.reflect.AnnotatedElement

- **T getAnnotation(Class<T>annotationType):得到指定类型的注解引用。没有返回null。**

- **boolean isAnnotationPresent(Class<?extends Annotation> annotationType)**:判断指定的注解有没有。

Class、Method、Field、Constructor等实现了AnnotatedElement接口.

- Annotation[] getAnnotations():得到所有的注解,包含从父类继承下来的。

- Annotation[] getDeclaredAnnotations():得到自己身上的注解。

```java
public @interface Annotation01(){

}

@Annotation01
class Demo01(){

          @Annotation01
          public void fun01(){
      
          }

    public void fun02(){
      
          }
}

//1.获得Demo01字节码对象
Class clazz =Demo01.class;
//2. 获得Demo01上面的注解对象
Annotation01 annotation01 = clazz.getAnnotation(Annotation01.class);
//3.反射获得fun01()方法对象
Method method =clazz.getMethod("fun01");
//4.判断fun01()方法上面是否有@Annotation01注解
boolean flag = method.isAnnotationPresent(Annotation01.class);
```

aleng-79 发表于 2021-11-29 20:59

这是人学的?{:301_972:}

Sqq1475369 发表于 2021-11-29 21:14

我竟然看懂了&#128514;

gzypersonal 发表于 2021-11-29 22:05

Sqq1475369 发表于 2021-11-29 21:14
我竟然看懂了&#128514;

优秀的人啊

VisionCrystal 发表于 2021-11-29 22:08

正好学到反射,感谢大佬分享

nekoneko2021 发表于 2021-11-30 00:38

面试的时候,经常被问到反射{:1_937:}

niufather 发表于 2021-11-30 09:49

一直都没看懂枚举是干嘛的。。

aisrch 发表于 2021-12-4 21:07

很实用的知识!{:1_921:}

Allen·Greene 发表于 2022-3-18 09:59

niufather 发表于 2021-11-30 09:49
一直都没看懂枚举是干嘛的。。

可以想象成字典

ningwang88 发表于 2022-3-18 11:07

挺全挺完整的,不错
页: [1] 2
查看完整版本: java枚举、反射以及注解