Java8新特性

Lambda表达式详见https://www.cnblogs.com/zys2019/p/13778194.html

1.Stream API

1.1概述

Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的操作。

1.2Stream的操作步骤

1)创建Stream:从一个数据源中获取流。

2)中间操作:对数据源的数据进行操作。

3)终止操作:执行中间操作链,并产生结果。

1.3创建流的方式

1)通过Collection集合提供的stream()或parallelStream()创建

List<Integer> list=new ArrayList<>();
Stream<Integer> stream = list.stream();

2)通过Arrays中的静态方法stream()获取数组流创建

String[] arr = {"java", "python", "c++"};
Stream<String> stream1 = Arrays.stream(arr);

3)通过Stream类的静态方法of创建

Stream<Integer> stream = Stream.of(1, 5, 7, 9, 20, 1);

4)无限流

Stream.generate(()->Math.random()).forEach(System.out::println);

System.out::println就是方法的引用,当此接口有返回值是才能使用。

1.4中间操作

多个中间操作会连接成一个流水线,只有触发终止操作时才会去真正的处理操作得到结果,否则不会进行任何的处理。

1.4.1筛选和切片

1)filter:接收Lambda,从流中排除某些操作

案例:过滤集合中所有的偶数

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
list.stream().filter(e -> e % 2 == 0).forEach(System.out::println);

2)distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。

案例:去除集合中的重复元素

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
list.stream().distinct().forEach(System.out::println);

需要注意的是,使用此方法,必须对集合中存储的数据类对象重新hashCode()和equals()方法。这里使用的是Integer类型,它已经重写了,如果是自定义的类型,则必须手动重写,下图就是对自定义的User类重写:

3)limit:截断流,使其元素不超过给定对象

案例:只获取集合中的前5个元素

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
list.stream().limit(5).forEach(System.out::println);

4)skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补

案例:只获取集合中第6个以后的元素

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
list.stream().skip(6).forEach(System.out::println);

1.4.2映射

1)map:接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream。

案例:遍历集合中的每一个元素,将其转为大写

List<String> list = Arrays.asList("abc","bbb","cbd","ddd");
list.stream().map(e->e.toUpperCase()).forEach(System.out::println);

2)flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。flatMap在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后将一个包含多个元素的Stream返回。

案例:对上述的集合元素取出每一个元素的每一个内容

  public static Stream<Character> getCharacterByString(String str) {
        List<Character> characterList = new ArrayList<>();
        for (Character character : str.toCharArray()) {
            characterList.add(character);
        }
        return characterList.stream();
    }

    @Test
    public void test() {
        List<String> list = Arrays.asList("abc","bbb","cbd","ddd");
        //MyTest是类名
    list.stream().flatMap(MyTest::getCharacterByString).forEach(System.out::print);
    }

打印结果是abcbbbcbdddd。

1.4.3排序

1)sorted():自然排序(Comparable)

案例:使用默认从小到大排序

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
//使用默认的排序方法,从小到大
list.stream().sorted().forEach(System.out::println);

2)sorted(Comparator com):定制排序(Comparator)

案例:自定义从大到小排序

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
//使用自定义的排序方法,从大到小
list.stream().sorted((e1,e2)-> e2-e1).forEach(System.out::println);

案例:使用stream与不使用stream的对比

        List<String> list = new ArrayList<>();
        for (int i = 0; i < 999999; i++) {
            list.add("测试效率" + i);
        }

        //开始时间戳
        Long startTime = System.currentTimeMillis();
        list.forEach(item -> {

        });
        //开始时间戳
        Long endTime = System.currentTimeMillis();
        System.out.println("遍历的时间:" + (endTime - startTime));//45

        //开始时间戳
        Long startTime2 = System.currentTimeMillis();
        list.stream().forEach(item -> {

        });
        //开始时间戳
        Long endTime2 = System.currentTimeMillis();
        System.out.println("遍历的时间2:" + (endTime2 - startTime2));//10

通过它们执行的时间的对比,可以发现使用stream的效率远高于不使用stream,对代码的运行速度有很大的提高。

1.5终止操作

1.5.1查找和匹配

1)allMatch:检查是否匹配所有元素。都符合条件返回true,否则返回false

案例:判断集合中的元素是否都大于50

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
boolean b = list.stream().allMatch(e -> e > 50);
System.out.println(b);

2)anyMatch:检查是否至少匹配一个元素。只要有一个符合就返回true,否则返回false

3)noneMatch:检查是否没有匹配所有元素

4)findFirst:返回第一个元素。返回值是Optional类型,再使用get方法获取内容。

案例:获取集合的第一个元素

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
Optional<Integer> first = list.stream().findFirst();
System.out.println(first.get());

5)findAny:返回当前流中的任意元素。返回值是Optional类型

6)count:返回流中元素的总个数

7)max:返回流中最大值。返回值是Optional类型

案例:获取集合中的最大值

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
Optional<Integer> max = list.stream().max((o1, o2) -> o1 - o2);
System.out.println(max.get());

8)min:返回流中最小值。返回值是Optional类型

1.5.2归约

将流中元素反复结合起来,得到一个值。方法是reduce

案例:把1-100放到集合中并求和

        List<Integer> integerList = new ArrayList<>(100);
        for (int i = 1; i <= 100; i++) {
            integerList.add(i);
        }
        final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y);
        System.out.println("结果为:" + reduce);//5050

1.5.3收集

将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法。方法是collect

 案例:计算平均值

List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100);
Double collect = list.stream().collect(Collectors.averagingInt(p -> p));
System.out.println(collect);

1.6实战演练

1.6.1 stream转集合

先看需求:现有一个包含用户对象的集合,想只获取这些用户的姓名组成一个集合,如何去做?

用户对象如下:

@Data
public class User {
    private Integer id;
    private  String name;
    private String phone;
}

列表如下(演示数据):

List<User> userList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
   User user = new User();
   user.setId(i + 1);
   user.setName("" + (i + 1) + "");
   userList.add(user);
}

第一种方式:遍历用户对象的集合进行获取

List<String> nameList = new ArrayList<>();
userList.stream().forEach(user -> nameList.add(user.getName()));

第二种方式:使用流方式提前数据(推荐)

List<String> nameList = userList.stream().map(User::getName).collect(Collectors.toList());

可使用collect方法将stream转成集合,其效率明显优于循环方式。

2.Optional类

2.1概述

Optional类是一个容器类,代表一个值存在或不存在,主要解决的问题是空指针异常。

2.2常用方法

先创建一个实体类,供下面使用:

public class User {
    private String name;
    private Integer age;
    //set和get,toString方法在此略,请自行加入
}

1)Optional.of(T t):创建一个实例,返回值是Optional

当返回值是Optional时,要获取里面的内容,再使用get方法即可。当是空实例时,使用get方法就会抛出异常

User user = new User();
Optional<User> opt = Optional.of(user);
System.out.println(opt.get());//User{name='null', age=null}

2)Optional.empty(T t):创建一个空的实例,返回值是Optional

获取抛出异常:

Optional<User> opt = Optional.empty();
System.out.println(opt.get());//No value present

3)Optional.ofNullable(T t):若T不为null则创建一个实例,否则创建一个空实例,返回值是Optional

User user = new User();
Optional<User> opt = Optional.ofNullable(user);
System.out.println(opt.get());//User{name='null', age=null}
Optional<User> opt2 = Optional.ofNullable(null);
System.out.println(opt2.get());//No value present

4)isPresent():判断是否包含值,包含值返回true,否则返回false

有了这个方法,就可以对第二个方法进行改写,就不会出现异常。

Optional<User> opt = Optional.empty();
if (opt.isPresent()) {
     System.out.println(opt.get());//No value present
}else{
     System.out.println("opt不包含值");
}
 //opt不包含值

5)isPresent():在value值不为空时,做一些操作。

6)orElse(T t):如果调用对象包含值就返回该值,否则返回默认值t,必须和ofNullable结合使用。此方法也使用的比较多,为null时进行替换。

String nullValue = null;
String notNullValue = "嘿嘿嘿";
String replaceValue = "哈哈哈";

String optional = Optional.ofNullable(nullValue).orElse(replaceValue);
System.out.println(optional);//哈哈哈

String nonNullOptional = Optional.ofNullable(notNullValue).orElse(replaceValue);
System.out.println(nonNullOptional);//嘿嘿嘿

7)orElseGet(Supplier s):如果调用对象包含值就返回该值,否则返回默认值s,必须和ofNullable结合使用。

String nullValue = null;
String notNullValue = "嘿嘿嘿";
String replaceValue = "哈哈哈";

String optionalGet = Optional.ofNullable(nullValue).orElseGet(() -> replaceValue);
System.out.println(optionalGet);//哈哈哈
        
String nonNullOptionalGet = Optional.ofNullable(notNullValue).orElseGet(() -> replaceValue);
System.out.println(nonNullOptionalGet);//嘿嘿嘿

orElseGet和orElse达到的效果是一样的,只是他们的参数不同。

8)orElseThrow(Supplier s):如果值是null就会抛出异常。

Optional.ofNullable(null).orElseThrow(()->new Exception("用户不存在"));

9)map(Function f):如果有值对其处理,就返回处理后的Optional,否则返回空实例

public class MyTest {
    @Test
    public void testMap() {
        C c = null;
        Optional<Optional<String>> s1 = Optional.ofNullable(c).map(MyTest::getOrderNumber);
        System.out.println(s1);//Optional.empty

        c = new C("hello");
        Optional<Optional<String>> s2 = Optional.ofNullable(c).map(MyTest::getOrderNumber);
        System.out.println(s2.get().get());//hello
    }

    private static Optional<String> getOrderNumber(C c) {
        return Optional.ofNullable(c).map(f -> f.getOrderNumber());
    }


}

class C {
    private String orderNumber;

    public String getOrderNumber() {
        return orderNumber;
    }

    public C(String orderNumber) {
        this.orderNumber = orderNumber;
    }

}

2.3实战演练

1)示例1:替换if 判断是否为空

以前的写法

User user = new User();
if (user != null) {
    System.out.println(user.toString());
}

现在的写法

User user = new User();
Optional.ofNullable(user).ifPresent(u -> {
    System.out.println(user.toString());
});

虽然现在的写法比以前的写法看起来复杂,实际上代码更优雅。

 2)示例2:替换if-else 判断是否为空

以前的写法

User user = new User();
if (user != null) {
    System.out.println("我不是null");
} else {
    System.out.println("我是null");
}

现在的写法

User user = new User();
Optional.ofNullable(user).filter(u -> {
    System.out.println("我不是null");
    return true;
}).orElseGet(()->{
    System.out.println("我是null");
    return null;
});

需要注意的是,一定要进行return。

3)示例2:替换if-else 判断集合是否为空,不为空且有数据时取第一条

以前的写法

List<String> list = new ArrayList();
list.add("123");
list.add("555");
if (list != null && list.size() > 0) {
    System.out.println(list.get(0));
}

现在的写法

List<String> list = new ArrayList();
list.add("123");
list.add("555");
Optional.ofNullable(list).filter(l -> l.size() > 0).filter(u -> {
    System.out.println(list.get(0));
    return true;
});

 

 

3.接口的方法新特性

在jdk1.8中,在接口中除了能定义抽象的方法外,还可以定义默认的方法和静态的方法。

3.1接口中的默认方法

默认方法使用default修饰,并且需要些方法体,当一个类实现它时,子类会继承父类的这个方法,可以对这个方法进行重写。

1)接口的定义

public interface A {

    default int sum(int a,int b){
        return a+b;
    }

}

2)实现类

public class A1 implements A {

}

3)调用

@Test
public void test10() {
        A a = new A1();
        int sum = a.sum(10, 20);
        System.out.println(sum);
}

执行结果是30。

有这样一种情况,当子类不仅有实现,还有继承时,如果接口的被继承的类中方法名相同时,那么子类会使用哪个类的方法呢?实际上有类优先的原则,即会先使用类的方法。原因很简单,当继承一个类时,类中的方法必须被重写,自然而然调用的方法就是这个被重写的方法。

3.2接口中的静态方法

 静态方法使用static修饰,并通过类名.方法名调用:

1)接口定义

public interface A {

    static int sum(int a,int b){
        return a+b;
    }

}

2)调用

@Test
public void test10() {
        int sum = A.sum(20, 12);
        System.out.println(sum);
}

 .

posted @ 2020-10-19 09:52  钟小嘿  阅读(200)  评论(0编辑  收藏  举报