一、反射
1、反射机制定义及作用
反射是允许程序在运行时检查和操作类、方法、属性等的机制,能够动态地获取信息、调用方法等。换句话说,在编写程序时,不需要知道要操作的类的具体信息,而是在程序运行时获取和使用。
2、反射机制的原理
程序运行时,JVM会将编译好的 .class 文件(代表一个类)解析为 java.lang.Class 类的实例,这个实例包含了该类的所有信息。通过反射机制,可以用到这个实例,来获取该类的信息并进行操作。
下面介绍反射的相关类。以 Student 类为例子:
java">public class Student {
//私有属性name
private String name = "小帅";
//公有属性age
public int age = 18;
//不带参数的构造方法
public Student(){
System.out.println("Student()");
}
private Student(String name,int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,name)");
}
private void eat(){
System.out.println("i am eat");
}
public void sleep(){
System.out.println("i am pig");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Class 在 java.lang 中,不需要导包。其它三个都在 java.lang.reflect 中。
3、Class 类
代表类的实体。
获取 JVM 给类解析的 Class 对象:
其它方法:
4、Field 类
代表类的属性。
- getField(String name):获得指定公有属性对象。
- getField():获得所有公有属性对象。
- getDeclaredField(String name):获得某个属性对象。(不限于公有)
- getDeclaredField():获得所有属性对象。
属性对象.get(对象) 表示获取指定对象的某属性,静态属性不需对象,参数填 null。
5、Method 类
代表类的方法。
6、Constructor 类
代表类的构造方法。
利用构造方法对象获得类的实例:
‘
7、使用反射动态加载类并创建对象
编写一个程序,在运行时根据用户输入的类名来动态加载类并创建对象,调用其 draw
方法。
java">import java.util.Scanner;
// 定义一个 Shape 接口
interface Shape {
void draw();
}
// 定义 Circle 类实现 Shape 接口
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制一个圆形");
}
}
// 定义 Rectangle 类实现 Shape 接口
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制一个矩形");
}
}
public class DynamicClassLoadingExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要加载的类名(如 Circle 或 Rectangle):");
String className = scanner.nextLine();
try {
// 根据用户输入的类名获取 Class 对象
Class<?> clazz = Class.forName(className);
// 创建该类的实例
Shape shape = (Shape) clazz.getDeclaredConstructor().newInstance();
// 调用 draw 方法
shape.draw();
} catch (Exception e) {
e.printStackTrace();
}
scanner.close();
}
}
反射优点:让程序更灵活,动态加载类和方法、可以访问和修改私有成员。
反射缺点:性能开销大、破坏封装性和安全性、程序不易读。
二、枚举
1、定义及作用
一组常量,比如颜色,它们是同一类,我们想把这组常量组织在一起,就用枚举。作用是与没有意义的数字区分开来(如果定义一组常量,就要用数字表示它们)。例子:
java">public enum Color {
RED, GREEN, BLUE;
public static void main(String[] args) {
Color color = Color.BLUE;
switch (color) {
case RED:
System.out.println("RED");
break;
case GREEN:
System.out.println("GREEN");
break;
case BLUE:
System.out.println("BLUE");
break;
default:
System.out.println("default");
break;
}
}
}
枚举类默认继承了 java.lang.Enum。枚举常量的默认是 public static final 修饰。
2、常用方法
枚举类型能够使用这些方法,是因为默认继承了 Enum 类:
但是可以发现,Enum 类中没有 values 方法。这是因为自定义枚举类型中枚举常量的数量、类型都是不确定的,如果 values 封装在 Enum 类中,就需要在运行时动态确定每个枚举常量(需要用到反射,破坏了封装性、增强了复杂性等)。实际上,编译器编译时会为每个枚举类型自动加上 values 方法,返回的数组在编译时就确定。
3、构造方法
枚举相当于是类,可以有属性、构造方法、方法。构造方法默认私有,且只能。这样保证了枚举实例的唯一性。
查看构造函数的实际参数有哪些:
多了 String 和 int 类型参数,因为枚举类默认继承了 Enum,其本身有 name 和 ordinal 属性,隐藏了 super(name, ordinal) ,所以 Color 构造函数会多 2 个参数。
4、枚举和反射
通过反射获取枚举类的实例:
出现错误:
查看 newInstance 源码:
因此,不能通过反射创建枚举类的实例。这样设计的目的是让枚举类只有一个实例,防止反射攻击。由私有构造函数、防反射攻击这两个特点得知,枚举实现单例模式(一种创建型模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例)是安全的。
枚举优点:简单安全。
枚举缺点:不能继承。
5、枚举实现单例模式
后续学了单例模式补充。
三、Lambda 表达式
1、定义及作用
相当于方法,但是比方法更简化。语法:
java">(形参列表)->表达式
(形参列表)->{代码块}
// 参数类型可以省略(要省全都省),只有一个参数可以省略圆括号。
// 返回一个值或者不返回值
// 只有一条 return 语句,可省略 return
2、函数式接口
- 函数式接口:一个接口只有一个抽象方法。
- @FunctionalInterface 注解:以函数式接口的标准(只有一个抽象方法)要求接口,检查作用。
- lambda 可简化匿名内部,实现函数式接口。
几个函数式接口代码:
java">//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
void test();
}
使用匿名内部类实现接口,重写方法:
使用 lambda 实现接口,重写方法:
java">NoParameterNoReturn noParameterNoReturn = ()->{System.out.println("hello");};
3、变量捕获
匿名内部类中可捕获外部变量。如果想修改捕获的变量,那该变量必须被 final 修饰,否则报错。lambda 表达式同理。
java"> int a = 10;
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// a = 99; 不允许修改未被 final 修饰的外部变量
System.out.println(a); // 变量 a 捕获
return o1.compareTo(o2);
}
});
4、Lambda 在集合中的应用
lambda 表达式作为以上方法的参数,实现接口中的方法。
4.1、Collection 接口
以 forEach 为例:
源码如下:Consumer 是函数式接口,accept 是实现的抽象方法。
实现函数式接口:
4.2、List 接口
以 sort 为例:
源码:
在 Java 中,所有类都直接或间接地继承自 Object 类,因此,Comparator
接口继承了 Object
类的 equals
方法。它不是 Comparator
接口自身定义的抽象方法,因此,不违反函数式接口的定义规则。
java"> @FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj); // 继承自 Object,不影响函数式接口
......
}
实现:
4.3、Map 接口
以 forEach 为例:
源码:
实现:
5、总结:
优点:语句简洁、方便函数式编程(替换匿名内部类)、改善集合操作。
缺点:可读性差、不易调试。