【JavaSE】十三、枚举类Enum && Lambda表达式 && 列表排序常见写法

文章目录

  • [Ⅰ. 枚举类定义与使用](#Ⅰ. 枚举类定义与使用)
  • [Ⅱ. 枚举类的构造方法默认就是 `private`](#Ⅱ. 枚举类的构造方法默认就是 private)
  • 总结
  • [Ⅲ. 什么是 `Lambda` 表达式](#Ⅲ. 什么是 Lambda 表达式)
  • [Ⅳ. `Lambda` 表达式的使用](#Ⅳ. Lambda 表达式的使用)
  • 列表排序的常用写法
    • [1. **基本比较(数值)**](#1. 基本比较(数值))
      • [`Integer.compare(a, b)`](#Integer.compare(a, b))
    • [2. **方法引用 +** **`Comparator.comparing`**](#2. 方法引用 + Comparator.comparing)
    • [3. **多条件排序**](#3. 多条件排序)
    • [4. **倒序排序**](#4. 倒序排序)
    • [5. **处理 null 值**](#5. 处理 null 值)
    • [6. **字符串比较(忽略大小写)**](#6. 字符串比较(忽略大小写))
    • [7. **完全自定义规则**](#7. 完全自定义规则)

Ⅰ. 枚举类定义与使用

typescript 复制代码
public enum Color {
    RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, PINK, BROWN, BLACK, WHITE;
}

每个我们写的 enum 类都会默认继承于一个抽象类 Enum,如下图所示:

而这个 Enum 抽象类中有挺多方法,如下所示:

下面是使用举例:

java 复制代码
public static void main(String[] args) {
    // 使用Color.RED直接访问枚举元素,或者直接RED都行,但不能使用new来生成枚举类对象
    Color color = Color.RED;
    System.out.println(color.name());    // 输出颜色名称
    System.out.println(color.ordinal()); // 输出颜色的序号
    System.out.println(color); // 直接输出颜色对象
    System.out.println(color.describeConstable()); // 输出颜色的描述信息
    System.out.println(Color.valueOf("BLACK"));    // 通过名称获取枚举值

    // 有个比较特殊的是values()方法,它可以返回所有枚举值数组,它不存在Enum中,而是在编译时由编译器生成的。
    // 但是如果枚举值太多,可能导致内存溢出,所以一般不用这个方法。
    System.out.println("----------------------输出所有枚举元素:");
    Color[] colors = Color.values();
    for(int i = 0; i < colors.length; ++i) {
        System.out.println(colors[i] + " " + colors[i].ordinal());
    }
    
    Color c2 = new Color(); // ❌不能实例化枚举类,只能通过枚举值访问其属性和方法。  
}

// 运行结果:
RED
0
RED
Optional[EnumDesc[Color.RED]]
BLACK
----------------------输出所有枚举元素:
RED 0
GREEN 1
BLUE 2
YELLOW 3
ORANGE 4
PURPLE 5
PINK 6
BROWN 7
BLACK 8
WHITE 9

这里要注意 values() 是由 Java 编译器为每个枚举类自动生成的静态方法 ,所以在 Enum 类里是找不到它的源码的,但每个具体的枚举类都有它。

还有就是枚举类内也可以有方法 ,并且这些方法只能通过枚举元素来调用,而不能直接通过枚举类名来调用,如下图所示:

Ⅱ. 枚举类的构造方法默认就是 private

枚举类的构造方法是隐式 private 的,是为了:

  • 保证枚举常量的唯一性
  • 避免外部创建新的实例(防止破坏 enum 的封闭性)
  • 在某些场景下,天然实现单例模式,并且是线程安全的、防反射、防反序列化

首先枚举类也是可以写构造函数,然后枚举元素可以进行构造,如下所示:

java 复制代码
public enum Color {
    RED("red", 1), GREEN("green", 2), BLUE("blue", 3);

    private String name;
    private int value;

    // 构造函数默认就是private,不写也可以,但是不能写其它的访问权限
    private Color(String name, int value) {
        this.name = name;
        this.value = value;
    }

    public static void main(String[] args) {
        Color c = Color.RED;
        System.out.println(c.name);
        System.out.println(c.value);
    }
}

// 运行结果:
red
1

然后测试一下不同对象引用同一个枚举元素,是不是同一地址:

java 复制代码
public static void main(String[] args) {
    Color c1 = Color.RED;
    Color c2 = Color.RED;
    System.out.println(c1 == c2); // true
}

很明显不同对象引用同一个枚举元素,都是同一个对象

然后看一下是不是能够在类内调用构造方法:

java 复制代码
public static void main(String[] args) {
    Color c = new Color(); // ❌报错:无法实例化枚举类型
}

那么就想,能不能用反射来设置访问权限,强制调用构造方法,如下所示:

java 复制代码
public static void main(String[] args) {
    try {
        // 先获取构造函数,再设置Accessible为true
        Class<?> cs = Class.forName("EnumDemo.Color");
        Constructor<?> ctor = cs.getDeclaredConstructor(String.class, int.class);
        ctor.setAccessible(true);

        // 调用构造函数创建对象
        Color c = (Color)ctor.newInstance("yellow", 4);
        System.out.println(c.name + " " + c.value);

    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

然后报了如下错误:

其实这个参数错误,是因为每个枚举类都默认继承了 Enum,而 Enum 类的构造方法又有两个参数,如下所示:

这时就需要一起放在我们的枚举类的参数列表中传入才行:

java 复制代码
Constructor<?> ctor = cs.getDeclaredConstructor(String.class, int.class, String.class, int.class);

解决上述问题之后,此时又有一个错误如下所示:

点击第二行跳转过去看看源码:

总结

  1. Enum 类同样有构造方法,但一定得是 private 修饰
  2. Enum 类的每一个枚举元素都是【单例】
  3. Enum 类型的类不能通过调用【构造方法】来创建实例
  4. Enum 类型的类不能通过【反射】来创建新的实例,因为 Enum 的实例在编译时就已经确定 ,且 JVM 保证了这些实例的唯一性。

Ⅲ. 什么是 Lambda 表达式

Lambda 表达式是 JavaSE8 中一个重要的新特性,它允许通过表达式来代替功能接口。

Lambda 表达式和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(可以是一个表达式或一个代码块)。基本语法如下所示:

java 复制代码
(parameters) -> expression
或者
(parameters) -> { statements; }

举一些例子:

java 复制代码
// 1. 不需要参数,返回值为 2 
() -> 2 

// 2. 接收⼀个参数,返回其2倍的值 
x -> 2 * x 

// 3. 接受2个参数,并返回他们的和 
(x, y) -> x + y 

// 4. 接收2个int型整数,返回他们的乘积 
(int x, int y) -> x * y 

// 5. 接受⼀个 string 对象,并在控制台打印,不返回任何值
(String s) -> System.out.print(s)

函数式接口定义:一个接口,且其中只有一个抽象方法(可以包含普通方法)

注意:如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。

举个例子:

java 复制代码
@FunctionalInterface 
interface NoParameterNoReturn { 
    void test(); 
    
    // 合法!可以有普通方法
    default void test2() { 
        System.out.println("JDK1.8新特性,default默认⽅法可以有具体的实现"); 
    } 
}

Ⅳ. Lambda 表达式的使用

java 复制代码
@FunctionalInterface
interface MyInterface1 {
    void myMethod();
}

@FunctionalInterface
interface MyInterface2 {
    int myMethod(int a, int b);
}

public class demo2 {
    public static void main(String[] args) {
        MyInterface1 myif1 = () -> System.out.println("无参无返回值方法");
        myif1.myMethod();

        MyInterface2 myif2 = (a, b) -> a + b;
        System.out.println("有参数有返回值:" + myif2.myMethod(2, 3));
    }
}

☠ 注意事项:

  1. 参数类型可以省略。如果需要省略,每个参数的类型都要省略。

  2. 参数的小括号里面只有一个参数,那么小括号可以省略

  3. 如果方法体当中只有一句代码,那么大括号可以省略

  4. 如果方法体中只有一条语句,且是 return 语句,那么大括号可以省略,且去掉 return 关键字。

  5. lambda 表达式捕获变量的规则,和匿名内部类是一样的,具体可以参考匿名内部类笔记

  6. lambda 表达式是 "函数式接口" 的一个匿名实现对象,区分开以下两者的区别:

    java 复制代码
       // Runnable是一个函数式接口,需要重写里面的run()方法!
       Runnable r = () -> System.out.println("Hello, Lambda!");
       
       // Thread只是一个类,但是可以Thread(Runnable r)来构造,所以才需要new Thread,然后回到上面的问题!
       Thread t = new Thread(() -> System.out.println("work"));

列表排序的常用写法

1. 基本比较(数值)

Integer.compare(a, b)

安全的数值比较,避免溢出:

java 复制代码
list.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));

适用于:

  • int, double, long 等基本类型
  • 数值比较安全性优先(避免 a - b 的溢出问题)

2. 方法引用 + Comparator.comparing

最简洁可读的写法:

java 复制代码
list.sort(Comparator.comparing(Person::getAge));

优点:

  • 语义明确,别人一看就知道是按年龄排序
  • 支持链式调用 .thenComparing(...)

3. 多条件排序

java 复制代码
list.sort(
    Comparator.comparing(Person::getAge)
              .thenComparing(Person::getName)
);

场景:

  • 先按主条件排(年龄)
  • 如果相同,再按次条件(名字)

4. 倒序排序

两种常见方式:

java 复制代码
// 方式1:reversed()
list.sort(Comparator.comparing(Person::getAge).reversed());

// 方式2:负数技巧(不推荐大数)
list.sort((p1, p2) -> Integer.compare(p2.getAge(), p1.getAge()));

5. 处理 null 值

java 复制代码
list.sort(
    Comparator.comparing(Person::getAge, Comparator.nullsLast(Integer::compareTo))
);

说明:

  • nullsFirst → null 在最前面
  • nullsLast → null 在最后面

6. 字符串比较(忽略大小写)

java 复制代码
list.sort(Comparator.comparing(Person::getName, String.CASE_INSENSITIVE_ORDER));

7. 完全自定义规则

当规则复杂(比如特殊业务逻辑)时:

java 复制代码
list.sort((p1, p2) -> {
    if (p1.getAge() == p2.getAge()) {
        return p1.getName().length() - p2.getName().length();
    }
    return p1.getAge() - p2.getAge();
});
相关推荐
float_六七4 小时前
Java反射:万能遥控器拆解编程
java·开发语言
han_hanker4 小时前
java 异常类——详解
java·开发语言
源码获取_wx:Fegn08954 小时前
基于springboot + vue健身房管理系统
java·开发语言·前端·vue.js·spring boot·后端·spring
LinHenrY12274 小时前
初识C语言(自定义结构:结构体)
c语言·开发语言
峥嵘life4 小时前
Android16 EDLA 认证测试CTS问题分析解决
android·java·服务器
Matlab仿真实验室4 小时前
基于Matlab实现可见光通信仿真
开发语言·matlab
Mr1ght4 小时前
为什么 InheritableThreadLocal 在 Spring 线程池中“偶尔”能传递变量?——一次线程池上下文传播的误解
java·spring
CreasyChan4 小时前
C# 反射详解
开发语言·前端·windows·unity·c#·游戏开发
毕设源码-赖学姐4 小时前
【开题答辩全过程】以 基于Java的保定理工科研信息管理系统的设计与实现为例,包含答辩的问题和答案
java·开发语言