枚举和Lambda表达式

559 阅读5分钟

​一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

1.枚举

        枚举是在 JDK 1.5 引⼊的,主要是用来表示⼀组相同业务的值,比如我们要实现卖车的程序,我们要定义⼀组颜色来穷举这辆⻋所提供的所有颜色,在没有枚举之前,我们是这样实现的:

public static int final RED = 1;
public static int final GREEN = 2;
public static int final BLACK = 3;

以上代码存在的主要问题有以下 3 个:

  1. 代码可读性低,⽐如,当我们看到数字 2 时,并不能准确的知道它代表的具体是什么颜⾊,我们要去代码⾥⾯查;
  2. 参数传递很容易出错,以上代码类型为 int,所以在传递时理论上是可以接受所有的 int 值的,但只有部分值是有效的颜⾊,所有很容易传递值出错;
  3. 写法不够优雅,在外层(外边类)调⽤时,看到的都是⼀个个 魔法数字 ,很让⼈很疑惑。

但有了 枚举 之后,我们就可以使⽤以下代码来组织所有的颜⾊了:

public enum ColorEnum { 
    RED, GREEN, BLACK;
}

它的优点有以下几个:

  1. 增强了代码的可读性;
  2. 减少了传递参数的错误概率;
  3. switch 判断更⽅便,语法清晰;
  4. 代码⾜够简洁、优雅。

1.1 switch 判断

public enum ColorEnum { 
    RED, GREEN, BLACK;

    public static void main(String[] args) { 
        ColorEnum colorEnum = ColorEnum.GREEN;
        switch (colorEnum) {
            case RED:
                System.out.println("红⾊");
                break;
            case BLACK:
                System.out.println("⿊⾊");
                break;
            case GREEN:
                System.out.println("绿⾊");
                break;
            default:
                System.out.println("其他颜⾊");
                break;
        }
    }
}

 运行结果:

1.2 枚举的常用方法

 1.2.1 values() 使用

以数组形式返回枚举类型的所有成员。

public enum ColorEnum {
    RED, GREEN, BLACK;

    public static void main(String[] args) {
        for (ColorEnum colorEnum : ColorEnum.values()) {
            System.out.println(colorEnum);
        }
    }
}

执行结果:

1.2.2 ordinal() 使用

public enum ColorEnum2 {
    RED, GREEN, BLACK;

    public static void main(String[] args) {
        for (ColorEnum colorEnum : ColorEnum.values()) {
            System.out.println(colorEnum + ":" + colorEnum.ordinal());
        }
    }
}

执行结果: 

 1.2.3 valueOf() 使用

将普通字符串转换为枚举实例。

public enum ColorEnum3 {
    RED, GREEN, BLACK;

    public static void main(String[] args) {
        ColorEnum color = ColorEnum.valueOf("BLACK");
        System.out.println(color.ordinal());
    }
}

执行结果:

1.2.3 compareTo() 使用

比较两个枚举成员在定义时的顺序,返回值为 枚举成员下标差值的int值。

public enum ColorEnum4 {
    RED, GREEN, BLACK;

    public static void main(String[] args) {
        ColorEnum4 color1 = ColorEnum4.RED;
        ColorEnum4 color2 = ColorEnum4.GREEN;
        ColorEnum4 color3 = ColorEnum4.BLACK;

        System.out.println("红色对比绿色:" + color1.compareTo(color2));
        System.out.println("红色对比黑色:" + color1.compareTo(color3));
        System.out.println("绿色对比黑色:" + color2.compareTo(color3));
        System.out.println("绿色对比红色:" + color2.compareTo(color1));
        System.out.println("黑色对比红色:" + color3.compareTo(color1));
        System.out.println("黑色对比绿色:" + color3.compareTo(color2));
    }
}

 运行结果:

1.3 枚举优缺点

优点:

  1. 增强了代码的可读性;
  2. 减少了传递参数的错误概率;
  3. switch 判断更⽅便,语法清晰;
  4. 代码⾜够简洁、优雅;
  5. 枚举有内置⽅法,功能更强⼤。

 缺点:

  1. 不可继承,⽆法扩展。

 2.Lambda 表达式

        Lambda 表达式是 JDK 8 中⼀个重要的新特性,lambda 表达式允许你通过表达式来代替功能接口(通过表达式通过实现业务功能)。lambda 表达式就和⽅法⼀样,它提供了⼀个正常的参数列表和⼀个使⽤这些参数的主体(body,可以是⼀个表达式或⼀个代码块),Lambda 表达式(Lambda expression)可以看作是⼀个 匿名函数 。 

2.1 为什么要用 Lambda

原因有三个

  1. 提供了更简单的语法和写代码的⽅式;
  2. 取代匿名内部类;
  3. 简化代码,干净整洁。

2.2 Lambda 语法

2.2.1 Lambda 表达式的基本语法如下:

(⼊参) -> {实现代码}

表达式由 3 部分组成:

  1. 入参 paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外当只有⼀个推断类型时可以省略掉圆括号;
  2. ->:可理解为“被⽤于”的意思;
  3. 实现代码(方法体):可以是表达式也可以代码块,是函数式接⼝⾥⽅法的实现。代码块可返回⼀个值或者什么都不反回,这⾥的代码块块等同于⽅法的⽅法体。如果是表达式,也可以返回⼀个值或者什么都不返回。

2.2.2 Lambda 基础使用:

    public static void main(String[] args) {
        List<String> lists = Arrays.asList("Hello", "World", "Java");
        // 没使⽤ lambda 之前
        for (String list : lists) {
            System.out.println(list);
        }
        System.out.println();
        // 使⽤ lambda 之后
        lists.forEach(s -> System.out.println(s));
    }

注意事项:

  1. 如果 lambda 参数的小括号里面只有⼀个参数,那么小括号可以省略;
  2. 如果⽅法体当中只有⼀句代码,那么⼤括号可以省略;
  3. 如果⽅法体中只有⼀条语句,其是 return 语句,那么⼤括号可以省略,且去掉 return 关键字。

2.3 Lambda 和函数式接口

 Lambda 表达式的写法有很多,比如以下这些:

// 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 对象,并在控制台打印,不返回任何值(看起来像是返回void) 
(String s) -> System.out.print(s)

但是Lambda 表达式不能直接使用,它在使用之前必须初始化,lambda 表达式必须借助 函数式接口(@FunctionalInterface)来初始化。

函数式接口:

定义:⼀个有且只有⼀个抽象方法的接口。

函数式接口的定义代码如下

@FunctionalInterface
    interface MyFunctionalInterface { 
        void myMethod(Object... args);
}

 注意事项:

  1. 如果⼀个接口只有⼀个抽象⽅法,那么该接口就是⼀个函数式接口;
  2. 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接⼝的定义来要求该接⼝,这样如果有两个抽象⽅法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接⼝中只有⼀个抽象方法,你可以不加这个注解。加上就会⾃动进⾏检测的。

有了函数式接口之后,我们就可以使用 lambda 表达式对其实例化了,实现代码如下:

@FunctionalInterface
interface MyFunctionalInterface {
    void myMethod(Object... args);
}

public class LambdaExample {
    public static void main(String[] args) {
        // lambda 表达式借助函数式接⼝初始化
        MyFunctionalInterface functionalInterface = (objects) -> {
            for (Object o : objects) {
                System.out.println(o.toString());
            }
        };
        // 执⾏ lambda 表达式
        functionalInterface.myMethod("张三", "李四", "王五");
    }
}

执行结果:

2.4 Lambda 的变量捕获

        在 lambda 中获取变量和在匿名内部类获取变量的规则⼀致:也就是在 lambda 中,如果要获取外部的变量,那么这个变量要么是被 final 修饰,如果不是被 final 修饰的,要保证在使用之前,没有修改,否则就会报错

@FunctionalInterface
interface MyFunctionalInterface {
    void myMethod(Object... args);
}

public class LambdaExample {
    public static void main(String[] args) {
        int count = 10;
        MyFunctionalInterface functionalInterface = (objects) -> {
            // count = 99; // 在使⽤之前,如果修改就会报错
            for (Object o : objects) {
                System.out.println(o.toString());
            }
            System.out.println("打印外部变量:" + count); // 可以正常执⾏
        };
        functionalInterface.myMethod("张三", "李四", "王五");
    }
}

 运行结果:

将 count 值修改为 99 后会报错:

 2.5 Lambda 在集合中的使用

         Lambda 表达式最有价值的作⽤就是可以配合集合⼀块使⽤。Lambda 可以配合使⽤的⽅法列表如下:

2.5.1 Map 中的 forEach

        HashMap<String, String> map = new HashMap<String, String>() {{
            put("name", "Java");
            put("desc", "Good");
        }};
        
        // jdk 1.8 之前
        for (Map.Entry<String, String> item : map.entrySet()) {
            System.out.println(item.getKey() + ":" + item.getValue());
        }
        System.out.println();
        
        // lambda
        map.forEach((k, v) -> System.out.println(k + ":" + v));

 2.5.2 List 排序

        List<Integer> list = Arrays.asList(5, 3, 2, 7, 9);
        System.out.println("排序前:" + list);

        // 不使用 Lambda 表达式
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });

        // 使用 Lambda 表达式
        list.sort((o1, o2) -> o2 - o1);

        System.out.println("排序后:" + list);

2.5.3 List 查找

        List<String> list = Arrays.asList("Java", "Lambda", "Spring", "Lambda", "SpringBoot", "MyBatis");

        // 1.8 之前
        for (String item : list) {
            if (item.equals("Lambda")) {
                System.out.println("");
            }
        }

        // lambda
        List<String> finds = list.stream().filter(s -> s.equals("Lambda")).collect(Collectors.toList());
        System.out.println(finds);

2.6 Lambda 优缺点分析

优点:

  1. 代码简洁,开发迅速;
  2. 方便函数式编程;
  3. 非常容易进行并行计算;
  4. Java 引⼊ Lambda,改善了集合操作。

缺点 :

  1. 代码可读性变差;
  2. 在非并行计算中,很多计算未必有传统的 for 性能要高;
  3. 不容易进行调试。