一、Java 的反射机制

1.1 Java 反射的概念及作用

如何使用一个 Java 类?

  • 已知一个类的类名,以及类中的方法,属性,构造方法等
  • 调用构造方法创建对象
  • 使用对象调用方法 或 属性

问:

如果仅仅知道一个类的类名,能否动态得到类的定义信息,包括哪些方法,属性等?

答:
可以使用反射做到

1.1.1 反射的概念

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能成为 Java 语言的 反射机制
**

1.1.2 反射的作用

  • 动态获取类信息,进一步实现需要的功能
  • 例如:Spring框架通过 XML文件描述类的基本信息,使用反射机制动态装配对象

1.2 Java 反射相关 API

1.2.1 Java 反射相关的类主要包括

  • Class 类型
  • Constructor 构造方法
  • Method 方法
  • Field 属性

  • 除了 Class外,其他类都位于 java.lang.reflect 包中

  • 可见,反射 API 将类的类型、方法、属性都封装成了类
  • 其中最重要的类是 Class,反射的使用都是从 Class 开始

1.3 Class 类

1.3.1 Class 类的作用

  • Class 类是 Java 反射机制的基础,通过 Class 类,可以得到一个类的基本信息
  • Class 类中的主要方法
    • getMethods:返回类中某一个方法的实例
    • getMethods:返回类中所有方法的实例
    • getField:返回类中某一个属性的实例
    • getConstructor:范湖类中的一个构造方法的实例
    • Class 类中还有若干 getXXX 方法,可以获得类 的基本信息

1.3.2 获得 Class 实例

获得 Class 类实例的常用方法

  1. Object 类中的 getClass 方法,适用于通过对象获得 Class 实例的情况
    1. 任何类都继承到了 getClass 方法,任意对象可以调用 getClass 方法获得 Class 实例
  2. 类名.class 方式,适用于通过类名获得 Class 实例的情况
    1. 任何类名加.class 即返回 Class 实例,例如 Class cls = String.class;
  3. Class 类的静态方法 forName(String name); 适用于通过类型获得 Class 实例的情况,尤其类名是变量
    1. 例如 Class.forName(className);
  1. public class TestClas {
  2. public static void main(String[] args) {
  3. // 使用三种方式获得 java.lang.String 类的 Class 实例
  4. String s = "Hello World";
  5. // 使用对象名获得 Class 实例
  6. Class cls1 = s.getClass();
  7. System.out.println("第一种方式:"+cls1);
  8. // 使用对象名获得 Class 实例,类名必须是变量
  9. Class cls2 = String.class;
  10. System.out.println("第二种方式:"+cls2);
  11. // 使用类名获得 Class 实例,类名可以是变量
  12. try {
  13. Class cls3 = Class.forName("java.lang.String"); // 这种方法常常用来加入我们自定义的类
  14. System.out.println("第三种方式:"+cls3);
  15. } catch (ClassNotFoundException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }

image.png

1.3.3 使用 Class 实例获得类的信息

接上,将获得的 Class 类的实例,打印出 String类的所有方法名字

  1. System.out.println("====== 使用 Class 实例获得类的信息 =======");
  2. Method[] methods = cls1.getMethods();
  3. for (Method m:methods) {
  4. System.out.println(m.getName());
  5. }

image.png

所以获得了 Class 实例后,就可以使用其 getXXX 方法得到该类的基本信息,包括方法,属性构造方法等

1.4 Constructor 类

1.4.1 Constructor 基本特点

  • Constructor 类可以通过 getXXX 方法获得构造方法的基本信息,例如:
    • getName:返回构造方法的名字
    • getParameterTypes:返回构造方法的参数类型
  • 处理获取构造方法的基本信息,还可以创建实例
    • newInstance(Object….initargs):创建实例

1.4.2 获取 Constructor 类实例

  • Constructor实例通过Class实例获得,Class类中定义了如下方法
    • Constructor getConstructor(Cla… parameterTypes):通过指定参数类型,返回构造方法实例。
  • Constructor[] getConstructors():返回该类的所有构造方法实例。

1.4.3 demo实现

  1. 写一个 学生类
  1. package day11;
  2. public class Student {
  3. private String name;
  4. private int age;
  5. private double score;
  6. public Student() {
  7. }
  8. public Student(String name, int age, double score) {
  9. this.name = name;
  10. this.age = age;
  11. this.score = score;
  12. }
  13. @Override
  14. public String toString() {
  15. return "Student{" +
  16. "name='" + name + '\'' +
  17. ", age=" + age +
  18. ", score=" + score +
  19. '}';
  20. }
  21. // getter 和 setter 省略
  22. }
  1. 测试 学生类 ```java import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;

public class TestStudent { public static void main(String[] args) { // 获取 Student 类的Class 实例 Class cls1 = Student.class; try { // 获取 Student 中有参构造方法的三种参数 Constructor con = cls1.getConstructor(String.class, int.class, double.class); // 参数为构造方法的类型

  1. // 得到构造方法就可以使用 newInstance
  2. Student stu = (Student) con.newInstance("小红",12,88.5);
  3. System.out.println(stu);
  4. } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
  5. e.printStackTrace();
  6. }
  7. }

}


3. 运行结果

![image.png](https://cdn.nlark.com/yuque/0/2020/png/473179/1586144399836-ce964a97-bf69-471e-b344-ae7b32c564ed.png#align=left&display=inline&height=82&margin=%5Bobject%20Object%5D&name=image.png&originHeight=82&originWidth=387&size=22830&status=done&style=none&width=387)

这种方式使用的很少,一般在和 xml 配置文件中使用的比较多,这里只是演示基本的使用方式
<a name="9k3vY"></a>
### 1.5 Method 方法
<a name="rWylw"></a>
#### 1.5.1 Method类的作用

- Method类将类中的方法进行封装,可以动态获得方法的信息,例如
   - getReturnType :获得方法返回值类型
   - getName:获得方法名字
   - getParameterTypes: 获得方法参数类型
- 除了动态获得方法信息外,Method还能动态调用某一个对象的具体方法
   - invoke(Object obj, Object... args) :使用obj调用该方法,参数为args

<a name="pyRX9"></a>
#### 1.5.2 获取 Method 实例
 Method实例都是通过CIass类的方法获得

- Method getMethod(String name, Class... .<br />parameterTypes):通过指定方法名,参数类型,<br />返回一个Method实例
- Method[] getMethods():返回该类中所有方法<br />的Method实例

<a name="EAuDt"></a>
#### 1.5.3 demo2
使用 TestMethod 类做如下操作<br />TestMethod类中:

- 获得Student类的Class实例
- 通过Class实例获得无参构造方法实例
- 通过无参构造方法实例,创建对象
- 通过Class实例获得方法实例
- 调用setXXX方法为Student实例的属性赋值

```java
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestMethod {
    public static void main(String[] args) {
        Class cls1 = null;
        try {
            // 获得 Student 类的 Class 实例
            cls1 = Class.forName("day11.Student");
            // 获取 Student 类的无参构造方法实例
            Constructor con = cls1.getConstructor(null);
            // 使用无参构造方法创建对象
            Student stu = (Student) con.newInstance(null);
            // 返回 Student 类的所有方法实例
            Method[] methods = cls1.getMethods();

            // 迭代数组,调用 setName 和 setAge,setScore 方法,为 stu 对象的属性赋值
            for (Method m:methods) {
                if (m.getName().startsWith("setName")) {
                    m.invoke(stu,"王三");
                }
                if (m.getName().startsWith("setAge")) {
                    m.invoke(stu,18);
                }
                if (m.getName().startsWith("setScore")) {
                    m.invoke(stu,95.5);
                }
                System.out.println(stu);
            }
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

image.png

1.6 Field 属性

1.6.1 Field 类的作用

Field 类将类的属性进行封装,可以获得属性的基本信息、属性的值,也可以对属性进行赋值

  • getName:返回属性的名字
  • getXXX:例如,getFloat 返回该属性 float 类型的值
  • setXXX:例如,setFloat为属性赋值float类型的值

1.6.2 获得 Field 实例

  • 通过 Class 中的方法实现
    • public Field getField(String name)
    • 通过指定 Field 名字,返回 Field 实例
    • 注意 Field 的访问权限

      1.6.3 demo3

      这里是显示不了数据的,因为权限访问问题

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class TestMethod {
    public static void main(String[] args) {
        Class cls1 = null;
        try {
            // 获得 Student 类的 Class 实例
            cls1 = Class.forName("day11.Student");
            // 获取 Student 类的无参构造方法实例
            Constructor con = cls1.getConstructor(null);
            // 使用无参构造方法创建对象
            Student stu = (Student) con.newInstance(null);

            // 通过名字返回为 name, age 的属性实例,注意name,age的访问权限必须是 public
            Field name = cls1.getField("name");
            Field age = cls1.getField("age");
            Field score = cls1.getField("score");
            // 调用 Field 类中的 set 方法,为对象 stu 属性赋值
            name.set(stu, "张三");
            name.setInt(stu,18);
            name.setDouble(stu,99.5);
            System.out.println(name.get(stu)+" "+age.getInt(stu)+" "+score.getDouble(stu));

        }  catch (NoSuchFieldException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

image.png

1.7 使用场景

1.7.1 JDBC 中加载驱动

        Class.forName("com.mysql.jdbc.Driver");

二、枚举

2.1 定义枚举类

  • 枚举的作用是定义有限个对象的一种结构,枚举属于多例设计
  • 我们使用 enum 关键字,利用此关键字可以实现枚举的定义
  • 使用枚举就是把有限个结果一一列举出来
// 定义一个枚举类
public enum Color {
    RED,GREEN,BLUE; // 实例化对象
}
public class TestColor {
    public static void main(String[] args) {
        Color c = Color.RED;
        System.out.println(c); // 获取枚举中的内容

        for (Color c1:Color.values()) {
            System.out.println(c1); // 循环获取枚举中的每一条数据
        }

        // 枚举和 switch 结合
        switch (c) {
            case RED:
                System.out.println("红色");
                break;
            case GREEN:
                System.out.println("绿色");
                break;
            default:
                System.out.println("没有你要的颜色");
        }
    }
}

2.2 Enum 类

  • 枚举的本质相对一个类,它会默认继承 Enum 类

2.2.1 Enum 类的结构

image.png

  • 现在定义的枚举类的类型就是 Enum 中所使用的的 E 类型

2.2.2 Enum 类的方法

image.png

对象序号是根据枚举对象的定义顺序决定的。

        for (Color c1:Color.values()) {
            System.out.println(c1.ordinal()+"-"+c1); // 循环获取枚举中的每一条数据
        }

image.png

image.png

2.3 定义枚举结构

枚举本身属于一种多例的设计模式,那么既然是多例设计模式,多例设计模式可以有 构造方法,普通方法,属性等,这些内容在枚举中依然可以定义,在枚举类中不能使用 private!!!

2.3.1 枚举使用构造方法

// 定义一个枚举类
public enum Color {
    RED("红色"),GREEN("绿色"),BLUE("蓝色"); // 实例化对象
    private String title;

    Color(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "Color{" +
                "title='" + title + '\'' +
                '}';
    }
}

2.3.2 枚举使用接口

image.png

2.3.3 枚举的使用

枚举的功能,就是把有限个集合一一列举出来,这才是它本身的功能

enum Sex {
    MALE("男"), FEAMLE("女") ;
    private String title;

    private Sex(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "Sex{" +
                "title='" + title + '\'' +
                '}';
    }
}

class Person {
    private String name;
    private int age;
    private Sex sex;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}

public class Demo {
    public static void main(String[] args) {
        System.out.println(new Person("张三",20,Sex.MALE));
    }
}

2.4 补充

2.4.1 面试题一:请解释 enum 和 Enum 的区别

  • enum:是 JDK 1.5 之后提供的一个关键字,用于定义枚举类
  • Enum:是一个抽象类,所以使用 enum关键字定义的类就默认继承了此类