JDK8新特性

一、介绍

1、概述

随着java的发展,越来越多的企业开始使用 java8 版本。Java8 是自 java5 之后最重要的版本,这个版本包含语言、编译器、库、工具、JVM等方面的十多个新特性。

Jdk8新增的特性如下:

  • Lambda 表达式
  • 新的日期API Datetime
  • 引入Optional 防止空指针异常
  • 使用Base64
  • 接口的默认方法和静态方法
  • 新增方法引用格式
  • 新增Stream类
  • 注解相关的改变
  • 支持并行(parallel)数组
  • 对并发类(Concurrency)的扩展。
  • JavaFX

二、接口新特性

1、接口默认方法

    当我们去实现某个框架提供的一个接口时,需要实现其所有的抽象方法,当该框架更新版本,在这个接口中加入了新的抽象方法时,我们就需要对项目重新编译,并且实现其新增的方法。当实现类太多时,操作起来很麻烦。

    JDK之前是使用开闭设计模式:对扩展开放,对修改关闭。即:创建一个新的接口,继承原有的接口,定义新的方法。但是这样的话,原本的那些实现类并没有新的方法。这时候可以使用接口默认方法。

使用接口默认方法:

    使用default关键字进行修饰,方法需要方法体,这样的方法所有的子类会默认实现(不用自己写),如果想要覆盖重写,也可以在实现类中覆盖重写。

A、新建一个User类

package com.itan.pojo;

public class User {
    private Integer id;
    private String name;
    private String sex;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
  
  	@Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

B、新建一个接口

package com.itan.service;

import com.itan.pojo.User;

public interface UserService {
    /**
     * 保存
     * @param user
     */
    void save(User user);

    /**
     * 根据id查询
     * @param id
     * @return
     */
    User findById(Integer id);
}

C、新建一个Data类(模拟数据库)

package com.itan.data;

import com.itan.pojo.User;
import java.util.ArrayList;
import java.util.List;

public class UserData {
    //模拟数据库
    public static List<User> list = new ArrayList<User>(16);
}

D、新建一个实现类

package com.itan.service.impl;

import com.itan.data.UserData;
import com.itan.pojo.User;
import com.itan.service.UserService;

import java.util.List;

public class UserServiceImpl implements UserService {
    @Override
    public void save(User user) {
    }

    @Override
    public User findById(Integer id) {
        List<User> list = UserData.list;
        for (User user : list) {
            if (user.getId().equals(id)){
                return user;
            }
        }
        return null;
    }
}

E、再建一个实现类

package com.itan.service.impl;

import com.itan.data.UserData;
import com.itan.pojo.User;
import com.itan.service.UserService;

import java.util.List;

public class UserServiceImpl2 implements UserService {
    @Override
    public void save(User user) {
        UserData.list.add(user);
    }

    @Override
    public User findById(Integer id) {
        return null;
    }
}

F、如果在接口中再加一个方法,发现实现类就会报错,必须要实现这个新加的抽象方法,这是就需要使用接口默认方法

/**
 * 获取user,从 java8 开始,接口中允许定义 default 默认方法
 * @return
 */
default User getUser(){
  	return new User();
}

G、测试

package com.itan.test;

import com.itan.pojo.User;
import com.itan.service.UserService;
import com.itan.service.impl.UserServiceImpl;
import org.junit.Test;

public class InterfaceTest {
    @Test
    public void testInterfaceDefault(){
        UserService userService=new UserServiceImpl();
        User user=userService.getUser();
        System.out.println(user);
      	// 输出:User{id=null, name='null', sex='null'}
    }
}

H、注意

    这里的 defaultjdk8 新增的关键字,和访问限定修饰符 default 不是同一个概念,与switch中的 default 功能完成不同。

    与抽象类的不同:抽象类更多的是提供一个模板,子类之间的某个流程大致相同,仅仅是某个步骤可能不一样(模板方法设计模式),这个时候使用抽象类,该步骤定义为抽象方法。而default关键字是用于扩展。

2、接口静态方法

/**
 * 从java8开始,接口中允许定义静态方法,与一般类的静态方法用法相同
 * @return
 */
static User getUser1(){
  	return new User();
}

注意: 接口中的静态方法不会被实现类所继承

三、函数式接口

1、概念

有且仅有一个抽象方法的接口。

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

2、格式

确保接口中有且只有一个抽象方法即可。

Public interface 接口名称 {
    返回值 方法名称();
}

3、@FunctionalInterface注解

有的注解是在编译期起作用,如@Override注解。而@FunctionalInterface也是在编译期起作用。该注解是java8专门为函数式接口引入的新的注解,作用于一个接口上。

一旦使用该注解来定义接口,编译期会强制检查该接口是否符合函数式接口的条件,不符合则会报错。需要注意的是:即使不使用该注解,只要满足函数式接口的定义,这就是一个函数式接口。

package com.itan.functional;

@FunctionalInterface //标识这是一个函数式接口
public interface MyFunctionalInterface {
    /**
     * 仅有的抽象方法
     */
    void method();

    /**
     * 默认方法
     */
    default void defaultMethod(){
        System.out.println("接口默认方法");
    };
}

四、Lambda表达式

1、概述

Java8 的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。

2、语法和示例

三要素: 参数、箭头、代码

(参数类型 参数1,参数类型 参数2...)->{代码}

说明:

  1. 如果参数有多个,那么使用逗号分隔。如果参数没有,则留空。
  2. 箭头是固定写法。
  3. 大括号相当于方法体。
  4. 使用前提:必须是函数式接口。

示例:

普通方式开启线程

package com.itan.lambda;

public class LambdaTest1 {
    public static void main(String[] args) {
        /**
         * 普通开启线程(Runnable)
         * 1.new Runnable()
         * 2.实现run方法
         * 3.new Thread(runnable).start()
         */
        Runnable runnable1=new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<=10;i++){
                    System.out.println("线程1开启了,"+i);
                }
            }
        };
        Runnable runnable2=new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<=10;i++){
                    System.out.println("线程2开启了,"+i);
                }
            }
        };
        Runnable runnable3=new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<=10;i++){
                    System.out.println("线程3开启了,"+i);
                }
            }
        };
        new Thread(runnable1).start();
        new Thread(runnable2).start();
        new Thread(runnable3).start();
    }
}

Lambda表达式开启线程

package com.itan.lambda;

public class LambdaTest2 {
    public static void main(String[] args) {
        /**
         * Lambda的思想,只关注做什么,而不关注怎么实现
         */
        Runnable runnable = ()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("线程1开启了,"+i);
            }
        };
        new Thread(runnable).start();
        /**
         * 简化
         */
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("线程2开启了," + i);
            }
        }).start();
    }
}

3、Lambda省略规则

  1. 参数类型可以省略。但是只能同时省略所有参数的类型,或者干脆都不省略。
  2. 如果参数有且仅有一个,那么小括号可以省略。
  3. 如果大括号内的语句有且仅有一条,那么无论是否有返回值,return、大括号、分号都可以省略。

4、Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。

性能浪费的案例:

package com.itan.lambda;

import org.junit.Test;

public class LambdaTest3 {
    private void log(int level,String msg){
        if(level==1){
            System.out.println(msg);
        }
    }

    @Test
    public void testStringConcat(){
        String str1="hello";
        String str2="world";
        String str3="Java";
        log(1,str1 + str2 + str3);
    }
}

存在问题: 无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。

Lambda优化写法:

// 接口
package com.itan.functional;
public interface MessageBuilder {
    String buildMessage();
}
// 测试
package com.itan.lambda;

import com.itan.functional.MessageBuilder;
import org.junit.Test;

public class LambdaTest3 {
    private void log(int level, MessageBuilder messageBuilder){
        if(level==1){
            System.out.println(messageBuilder.buildMessage());
        }
    }

    @Test
    public void testStringConcat(){
        String str1="hello";
        String str2="world";
        String str3="Java";
        log(1,()->str1 + str2 + str3);
    }
}

只有当满足条件的时候才会进行三个字符串的拼接。否则不会拼接。在不符合要求的情况下,lambda将不会执行。

五、常用函数式接口

1、Supplier接口

java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

package com.itan.lambda;

import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Supplier;

public class SupplierTest {
    private static User getUser(Supplier<User> supplier){
        return supplier.get();
    }
    @Test
    public void getUser(){
        User user=getUser(()->new User());
        System.out.println(user);
    }
}

示例:求数组的最小值

package com.itan.lambda;

import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Supplier;

public class SupplierTest {
    @Test
    public void testGetMin(){
        int [] arr={1,2,55,88,44,0};
        int minNum=getMain(()->{
            //假设第一个是最小值
            int min=arr[0];
            for (int item:arr){
                if (item<min){
                    min=item;
                }
            }
            return min;
        });
        System.out.println(minNum);
    }

    private int getMain(Supplier<Integer> supplier){
        return supplier.get();
    }
}

2、Consumer接口

java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,

其数据类型由泛型决定。

抽象方法:accept,意为消费一个指定泛型的数据。

package com.itan.lambda;

import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Consumer;

public class ConsumerTest {
    @Test
    public void test(){
        User user=new User();
        setUser(u-> u.setSex("男"),user);
        System.out.println(user);
    }
    private void setUser(Consumer<User> consumer,User user){
        consumer.accept(user);
    }
}

默认方法:endThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。

package com.itan.lambda;

import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Consumer;

public class ConsumerTest {
    @Test
    public void test(){
        User user=new User();
        setUserDefaultNameAndSex(u-> {
            u.setName("张三");
            System.out.println(u);
        },u->{
            u.setSex("男");
            System.out.println(u);
        },user);
        System.out.println(user);
    }
    private void setUserDefaultNameAndSex(Consumer<User> one,Consumer<User> two,User user){
        one.andThen(two).accept(user);
    }
}

3、Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T> 接口。

抽象方法:test

Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断,条件判断的标准是传入lambda表达式逻辑。

package com.itan.lambda;

import org.junit.Test;
import java.util.function.Predicate;

public class PredicateTest {
    @Test
    public void test(){
        PredicateMethod(s->s.length()>5,"HelloWorld");
    }
    private void PredicateMethod(Predicate<String> predicate,String str){
        boolean flag=predicate.test(str);
        System.out.println("判断结果"+flag);
    }
}

默认方法:add

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and。

如果要判断一个字符串既要包含大写“H”,又要包含大写“W”

package com.itan.lambda;

import org.junit.Test;
import java.util.function.Predicate;

public class PredicateTest {
    @Test
    public void test(){
        PredicateDefaultAndMethod(s->s.contains("H"),s->s.contains("W"));
    }
    private void PredicateDefaultAndMethod(Predicate<String> one,Predicate<String> two){
        boolean flag=one.and(two).test("HelloWorld");
        System.out.println("判断结果"+flag);
    }
}

**默认方法:or **

如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变

package com.itan.lambda;

import org.junit.Test;
import java.util.function.Predicate;

public class PredicateTest {
    @Test
    public void test(){
        PredicateDefaultOrMethod(s->s.contains("H"),s->s.contains("P"));
    }
    private void PredicateDefaultOrMethod(Predicate<String> one,Predicate<String> two){
        boolean flag=one.or(two).test("HelloWorld");
        System.out.println("判断结果"+flag);
    }
}

默认方法:negate

表示取相反的

package com.itan.lambda;

import org.junit.Test;
import java.util.function.Predicate;

public class PredicateTest {
    @Test
    public void test(){
        PredicateDefaultNegateMethod(s->s.length()<5,"HelloWorld");
    }
    private void PredicateDefaultNegateMethod(Predicate<String> one,String str){
        boolean flag=one.negate().test(str);
        System.out.println("判断结果"+flag);
    }
}

4、Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

抽象方法:apply

Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。

使用的场景例如:将 String 类型转换为 Integer 类型。

package com.itan.lambda;

import org.junit.Test;
import java.util.function.Function;

public class FunctionTest {
    @Test
    public void test(){
        Integer i = parseInteger(s -> Integer.parseInt(s), "10");
        System.out.println(i);
    }

    private Integer parseInteger(Function<String,Integer> function,String str){
        return function.apply(str);
    }
}

默认方法:andThen

package com.itan.lambda;

import org.junit.Test;
import java.util.function.Function;

public class FunctionTest {
    @Test
    public void test(){
        Integer i = defaultAndThenMethod(s -> Integer.parseInt(s)+10, s1->s1*10);
        System.out.println(i);
    }

    private Integer defaultAndThenMethod(Function<String,Integer> one,Function<Integer,Integer> two){
        return one.andThen(two).apply("10");
    }
}

六、方法引用

1、方法引用符

双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

package com.itan.lambda;

import org.junit.Test;
import java.util.function.Consumer;

public class FunctionRefTest {
    @Test
    public void test(){
        print(System.out::println);
    }
    private void print(Consumer<String> consumer){
        consumer.accept("HelloWorld");
    }
}

分析: System.out 对象中有一个重载的 println(String)方法恰好就是我们所需要的。

那么对printString 方法的函数式接口参数,对比下面两种写法,完全等效:

  • Lambda表达式写法: s -> System.out.println(s);
  • 方法引用写法: System.out::println

第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println 方法去处理。

第二种等效写法的语义是指:直接让System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。

注意: Lambda 中传递的参数 一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

2、通过对象名引用成员方法

package com.itan.lambda;

import com.itan.pojo.User;
import org.junit.Test;

import java.util.function.Consumer;
import java.util.function.Function;

public class FunctionRefTest {
      @Test
      public void test(){
          String userName=getUserName(User::getName);
          System.out.println(userName);
      }
      private String getUserName(Function<User,String> function){
          User user=new User();
          user.setName("张三");
          return function.apply(user);
      }
}

3、通过类名称引用静态方法

由于在 java.lang.Math 类中已经存在了静态方法abs ,所以当我们需要通过Lambda来调用该方法时,有两种写法。

方式一、函数式接口

@FunctionalInterface
public interface Calcable {
    int calc(int num);
}
public class Demo05Lambda {
    private static void method(int num, Calcable lambda) {
        System.out.println(lambda.calc(num));
    }
    public static void main(String[] args) {
        method(-10, n -> Math.abs(n));
    }
}

方式二、使用方法引用

public class Demo06MethodRef {
    private static void method(int num, Calcable lambda) {
        System.out.println(lambda.calc(num));
    }
    public static void main(String[] args) {
        method(-10, Math::abs);
    }
}

4、通过super引用成员方法

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。

首先是函数式接口:

@FunctionalInterface
public interface Greetable {
    void greet();
}

父类Human的内容

public class Human {
    public void sayHello() {
        System.out.println("Hello!");
    }
}

子类Man的内容

public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }

    //定义方法method,参数传递Greetable接口 
    public void method(Greetable g) {
        g.greet();
    }

    public void show() {
        //调用method方法,使用Lambda表达式 
        method(() -> {
            //创建Human对象,调用sayHello方法 
            new Human().sayHello();
        });
        //简化Lambda 
        method(() -> new Human().sayHello());
        //使用super关键字代替父类对象 
        method(() -> super.sayHello());
    }
}

但是如果使用方法引用会更好

public class Woman extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }

    public void method(Greetable g) {
        g.greet();
    }

    public void show() {
        method(super::sayHello);
    }
}

5、通过this引用成员方法

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。

首先是函数式接口:

@FunctionalInterface
public interface Richable {
    void buy();
}
public class Husband {
    private void buyHouse() {
        System.out.println("买套房子");
    }

    private void marry(Richable lambda) {
        lambda.buy();
    }

    public void beHappy() {
        marry(this::buyHouse);
    }
}

七、Stream流

1、概述

说到Stream便容易想到I/O Stream,而实际上,在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

弊端: Stream流式操作性能比传统的For循环要低,就性能而言,传统的for循环最高。

2、获取Stream流

java.util.stream.Stream<T> 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)

获取一个流非常简单,有以下几种常用的方式:

  • 所有的 Collection 集合都可以通过 stream 默认方法获取流。
  • Stream 接口的静态方法 of 可以获取数组对应的流。

根据Collection获取流

首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

package com.itan.lambda;

import org.junit.Test;
import java.util.*;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void testGetStream(){
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();
        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();
    }
}

根据Map获取流

java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流

需要分key、value或entry等情况:

package com.itan.lambda;

import org.junit.Test;
import java.util.*;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void testGetStream(){
        Map<String,Object> map = new HashMap<>();
        Stream<String> stream1 = map.keySet().stream();
        Stream<Object> stream2 = map.values().stream();
    }
}

根据数组获取流

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of

package com.itan.lambda;

import org.junit.Test;
import java.util.*;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void testGetStream(){
        String[] array = {"张无忌", "张翠山", "张三丰", "张一元"};
        Stream<String> stream = Stream.of(array);
    }
}

3、逐一处理:forEach

package com.itan.lambda;

import org.junit.Test;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array = {"张无忌", "张三丰", "周芷若"};
        Stream<String> stream = Stream.of(array);
        stream.forEach(s -> System.out.println(s));
    }
}

4、过滤:filter

可以通过 filter 方法将一个流转换成另一个子集流。

package com.itan.lambda;

import org.junit.Test;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
        Stream<String> stream = Stream.of(array);
      	//Lambda表达式来指定筛选条件
        stream.filter(s -> s.length() == 3).forEach(System.out::println);
    }
}

5、映射:map

如果需要将流中的元素映射到另一个流中,可以使用 map 方法。

package com.itan.lambda;

import org.junit.Test;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
        Stream<String> stream = Stream.of(array);
        Stream<String> stream1 = stream.map(e -> e.substring(2));
        stream1.forEach(System.out::println);
    }
}

6、统计个数:count

package com.itan.lambda;

import org.junit.Test;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
        Stream<String> stream = Stream.of(array);
        long count = stream.count();
        System.out.println(count);
    }
}

7、取用前几个:limit

package com.itan.lambda;

import org.junit.Test;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
        Stream<String> stream = Stream.of(array);
        stream.limit(2).forEach(System.out::println);
    }
}

8、跳过前几个:skip

package com.itan.lambda;

import org.junit.Test;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
        Stream<String> stream = Stream.of(array);
        stream.skip(2).forEach(System.out::println);
    }
}

9、组合:concat

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat

package com.itan.lambda;

import org.junit.Test;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array1 = {"张无忌", "张三丰"};
        String[] array2 = {"周芷若","赵敏"};
        Stream<String> stream1 = Stream.of(array1);
        Stream<String> stream2 = Stream.of(array2);
        Stream.concat(stream1,stream2).forEach(System.out::println);
    }
}

10、将流转化为集合:collect

package com.itan.lambda;

import org.junit.Test;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TestList {
    @Test
    public void test(){
        String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
        Stream<String> stream = Stream.of(array);
        List<String> list = stream.limit(2).collect(Collectors.toList());
        for (String s : list) {
            System.out.println(s);
        }
    }
}

11、集合排序方法:sorted

1、测试数据

List<User> list = new ArrayList();
list.add(new User(1,"张三","男","1998-07-24",135));
list.add(new User(2,"李四","女","1999-10-02",186));
list.add(new User(3,"王五","男","1997-08-23",156));

2、升序排列

//按生日升序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBirthDay))
  				   .collect(Collectors.toList());

3、降序排列

//按生日降序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBirthDay).reversed())
  				   .collect(Collectors.toList());

4、多字段排序

//按分数升序,当分数相同时,按生日升序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBranch).thenComparing(User::getBirthDay))
				   .collect(Collectors.toList());
//按分数升序,当分数相同时,按生日降序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBranch).thenComparing(User::getBirthDay,Comparator.reverseOrder()))
				   .collect(Collectors.toList());

八、Stream操作collectingAndThen根据对象属性进行去重操作

1、实体对象

@Data
@AllArgsConstructor
public class User {
    private  Integer id;

    private String name;

    private String sex;
}

2、在user集合中根据name、sex属性去重,生成一个新的集合

public class WareInfoController {
    public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        User user1 = new User(1,"张三","男");
        User user2 = new User(2,"张三","男");
        User user3 = new User(3,"李四","男");
        User user4 = new User(4,"李四","男");
        User user5 = new User(5,"王五","女");
        User user6 = new User(6,"王五","女");
        list.add(user1);
        list.add(user2);
        list.add(user3);
        list.add(user4);
        list.add(user5);
        list.add(user6);
        List<User> users = list.stream().collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(User::getName).thenComparing(User::getSex))
                ), ArrayList::new)
        );
    }
}
1、输出的结果为:[{1,“张三”,“男”},{3,“李四”,“男”},{5,“王五”,“女”}]
2、运行原理:首先利用java8新特性中的stream流对集合进行操作,collectingAndThen用于对流中的数据进行处理,可以对流中的数据进行聚合操作,接着需要使用toCollection收集器并提供特定集合实现,然后将结果集放到TreeSet中,由于TreeSet的特性是不重复,于是就可以达到去重的效果,Comparator.comparing括号中是筛选的条件,thenComparing方法是拼接第二个筛选条件,最后将流中的元素累积到汇总到collect收集器中,再以ArrayList的形式返回。

3、也可以使用以下代码操作

List<User> users = list.stream().collect(
    Collectors.collectingAndThen(Collectors.toCollection(
        () -> new TreeSet<>(Comparator.comparing(o -> o.getName() + ";" + o.getSex()))
    ), ArrayList::new)
);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值