【学习笔记】Java笔记10:Java8新特性

112 阅读6分钟

1. Lambda表达式

1.1 相关说明

  • 创建函数式接口的匿名对象时,可以使用Lambda表达式来减少代码量

  • 语法格式:(参数列表) -> {代码体}

  • 函数式接口:只有一个抽象方法的接口,可以使用注解@FunctionalInterface来标记对应接口

    函数式接口是“面向方法编程”的思想。当某个方法的参数是一个函数式接口时,那么传进一个该接口的实现类,其实就是把这个方法本身作为传参,接参的地方就会执行这个方法的逻辑

  • Java内置的4个基本函数式接口如下

    接口名方法
    Consumer<T>void accept(T t)
    Supplier<T>T get()
    Function<T, R>R apply(T t)
    Predicate<T>boolean test(T t)

1.2 例子

1.2.1 Consumer<T>

public class Test {
    public static void main(String[] args) {
        addMoney(400, money -> System.out.println("零花钱加了"+ money + "元"));
    }

    public static void addMoney(double money, Consumer<Double> consumer) {
        consumer.accept(money);
    }
}

1.2.2 Predicate<T>

public class Test {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123abc", "456", "4abc");
        // Lambda表达式中,代码体只有一行时,可以省略花括号和"return"
        List<String> result = filterString(list, str -> str.contains("abc"));
        System.out.println(result);
    }

    public static List<String> filterString(List<String> list, Predicate<String> pre) {
        ArrayList<String> result = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)) {
                result.add(s);
            }
        }
        return result;
    }
}

2. 方法引用

2.1 相关说明

  • 本质上还是Lambda表达式,对应的接口也得是函数式接口,代码量更少

  • 对于Lambda表达式代码体中的方法,已有其他现成的方法时,可以使用方法引用

  • 语法格式

    1、对象::非静态方法

    2、类::静态方法

    3、类::非静态方法

2.2 例子

2.2.1 对象::非静态方法

  与"类::静态方法"的情况类似

public class Test {
    public static void main(String[] args) {
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("Lambda表达式");

        // 上述Lambda表达式中代码体的效果与PrintStream的println(T t)方法一致
        // 且参数列表也一样,于是可以用方法引用
        PrintStream ps = System.out;
        Consumer<String> con2 = ps::println;
        con2.accept("方法引用");
    }
}

2.2.2 类::非静态方法

public class Test {
    public static void main(String[] args) {
        // 类::非静态方法
        // Comparator的int compare(T t1, T t2)
        // String的int t1.compareTo(t2)
        Comparator<String> con = String::compareTo;
        // 实际上是调用了"abc".compareTo("abq")
        System.out.println(con.compare("abc", "abq"));
    }
}

3. 构造器引用

public class Test {
    public static void main(String[] args) {
        // 原始写法
        Function<String, Student> fun1 = new Function<String, Student>() {
            @Override
            public Student apply(String name) {
                // 自定义Student类,构造器传入学生名字
                return new Student(name);
            }
        };
        // Lambda表达式
        Function<String, Student> fun2 = name -> new Student(name);
        // 构造器引用,在Student类中刚好有需要一个参数的构造器,所以能直接用Student::new
        Function<String, Student> fun3 = Student::new;
        Student tom = fun3.apply("Tom");
        System.out.println(tom);
    }
}

4. Stream API

4.1 相关说明

  • Stream操作有点类似于复杂的SQL语句,可以对集合数据进行过滤、截取等操作

  • Stream关注的是对数据的运算,与CPU打交道;集合关注的是数据的存储,与内存打交道

  • Stream本身不会存储元素

  • Stream不会改变源对象,会返回一个新的Stream

  • Stream操作是延迟执行的,只有执行终止操作时才执行中间操作

  • 执行流程如下

    1、实例化

    2、中间操作(过滤、映射等)

    3、终止

  • 一旦执行终止操作,Stream将不能再使用;想要再用,就得再实例化

4.2 操作

4.2.1 实例化

  • 通过集合的stream()方法获取顺序流;parallelStream()方法获取并行流
  • 通过Arrays.stream()方法,参数是数组
  • 通过Stream.of()方法,参数是多个元素,如:Stream.of(1, 2, 3, 4)

4.2.2 筛选与切片

@Test
public void test() {
    List<String> list = Arrays.asList("123abc", "456", "789abc", "456");
    // filter():过滤,过滤包含"abc"的
    // forEach():这是一个终止操作,参数传一个Consumer
    list.stream().filter(str -> str.contains("abc")).forEach(System.out::println);

    // limit():截取
    list.stream().limit(2).forEach(System.out::println);

    // skip():跳过多个元素
    list.stream().skip(1).forEach(System.out::println);

    // distinct():去重
    list.stream().distinct().forEach(System.out::println);
}

4.2.3 映射

1、map()

@Test
public void test() {
    List<String> list = Arrays.asList("aa", "bb", "cc");

    // map():映射,参数是Function类
    list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
}

2、flatMap()

public class StreamTest {
    @Test
    public void test() {
        List<String> list = Arrays.asList("aaa", "bbb", "ccc");

        // 如果使用map(),得到的Stream中的元素是Stream,不会把自定义方法的返回值拆开
        Stream<Stream<Character>> streamStream = list.stream().map(StreamTest::fromStringToStream);
        streamStream.forEach(s -> s.forEach(System.out::print));

        // 如果使用flatMap(),得到的Stream中的元素是Character,会把自定义方法的返回值拆开
        Stream<Character> characterStream = list.stream().flatMap(StreamTest::fromStringToStream);
        characterStream.forEach(System.out::print);
    }

    // 自定义方法,把字符串的每个字符加到list集合中,返回该list的Stream
    public static Stream<Character> fromStringToStream(String str) {
        ArrayList<Character> list = new ArrayList<>();
        for (char c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }
}

4.2.4 排序

@Test
public void test() {
    List<Integer> list = Arrays.asList(88, -10, 40);
    // sorted():排序,如果集合中的元素是自定义类,则要么sorted()参数传一个比较器,要么该自定义类实现Comparable接口
    list.stream().sorted().forEach(System.out::println);
}

4.2.5 终止之查找与匹配

方法说明
boolean allMatch(Predicate p)检查是否匹配所有元素
boolean anyMatch(Predicate p)检查是否至少匹配一个元素
boolean noneMatch(Predicate p)检查是否没有匹配元素
Optional findFirst()获取第一个元素
Optional findAny()获取任一元素
long count()统计元素个数
Optional max(Comparator c)获取最大值元素
Optional min(Comparator c)获取最小值元素
void forEach(Consumer c)内部迭代
@Test
public void test() {
    // 自定义Student类的集合,Student类有name和age属性
    List<Student> list = Arrays.asList(new Student("jack", 20), new Student("tom", 50), new Student("mi", 30));
    Optional<Student> max = list.stream().max((e1, e2) -> e1.getAge() - e2.getAge());
    System.out.println(max);
}

4.2.6 终止之归约

@Test
public void test() {
    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
    // T reduce(T identity, BinaryOperator<T> accumulator)
    // 第一个参数是初始值,
    // 第二个参数表示,将流中的元素反复结合起来,结合的方式具体要看传入的参数,本例的意思是:
    // 初始值10加上第一个元素1得到11,11加上第二个元素2得到13,如此循环
    Integer reduce = list.stream().reduce(10, Integer::sum);
    System.out.println(reduce);
}

@Test
public void test2() {
    List<Student> list = Arrays.asList(new Student("jack", 20), new Student("tom", 50), new Student("mi", 30));
    Stream<Integer> ageStream = list.stream().map(Student::getAge);
    // Optional<T> reduce(BinaryOperator<T> accumulator)
    // 只有一个参数,将流中的元素反复结合起来,结合的方式是依次累加
    Optional<Integer> reduce = ageStream.reduce((d1, d2) -> d1 + d2);
    System.out.println(reduce);
}

4.2.7 终止之收集

@Test
public void test() {
    List<Student> list = Arrays.asList(new Student("jack", 20), new Student("tom", 50), new Student("mi", 30));
    // collect(Collector c):收集
    // 把结果收集到list中
    List<Student> resultList = list.stream().filter(e -> e.getAge() > 20).collect(Collectors.toList());
    resultList.forEach(System.out::println);

    // 把结果收集到set中
    Set<Student> resultSet = list.stream().filter(e -> e.getAge() > 20).collect(Collectors.toSet());
    resultSet.forEach(System.out::println);
}

5. Optional类

  在Stream操作中经常会返回这种类型。Optional<T>是一个容器类,存放T类型的值,可以避免空指针异常。

5.1 实例化

@Test
public void test() {
    Student tom = new Student("tom", 20);
    // 使用Optional.of()创建实例,参数不能是null
    // Optional.ofNullable()的参数可以是null
    Optional<Student> student = Optional.of(tom);
    System.out.println(student);
}

5.2 相关方法

方法说明
Optional.of(T t)创建一个Optional实例,参数t不能为空
Optional.empty()创建一个空的实例
Optional.ofNullable(T t)创建一个Optional实例,参数t可以为空
boolean isPresent()判断Optional的内容是否为空
void ifPresent(Consumer c)如果内容不为空,则执行Consumer代码,并把Optional的内容作为参数传入Consumer中
T get()返回Optional的内容,如果为空则抛异常
T orElse(T t)如果当前Optional的内容非空,则返回该内容;否则如果为空,返回参数里的t
T orElseGet(Supplier other)返回Optional的内容,如果为空则返回Supplier代码中的返回值
T orElseThrow(Supplier s)返回Optional的内容,如果为空则抛出Supplier代码中抛出的异常

6. 相关链接