Java8新特性


Java8 新增了非常多的特性,主要有以下几个:

  • Lambda 表达式:Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)
  • 函数式接口:指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式
  • 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码
  • Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • Optional 类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Date Time API:加强对日期与时间的处理。

一、Lambda表达式

Lambda表达式,也称为闭包,它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。

Lambda表达式的语法格式:

(parameters) -> expression

或

(parameters) -> {statements; }

Lambda编程风格,可以总结为四类:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
  • 可选的大括号:如果主体只包含了一行时,就不需要使用大括号;当主体包含多行时,需要使用大括号
  • 可选的返回关键字:如果表达式中的语句块只有一行时,则可以不使用return语句返回值的类型也由编译器推理得出;如果语句块有多行,需要指定明表达式返回值是的接口

变量作用域

Lambda 表达式可以引用类成员和局部变量,但是会将这些变量隐式得转换成final


二、函数式编程

概念:
“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。

常用函数:

Function<T, R> 接口 R apply(T t); 有参数有返回值

Supplier 接口 T get(); 没参数有返回值

Consumer 接口 void accept(T t); 有参数没返回值

三、方法引用

方法引用使用一对冒号::,通过方法的名字来指向一个方法。

下面,我们在Car类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。

public class Car {

//Supplier是jdk1.8的接口,这里和lamda一起使用了 

public static Car create(final Supplier<Car> supplier) { 

    return supplier.get(); 

} 

public static void collide(final Car car) { 

    System.out.println("Collided " + car.toString()); 

} 

public void follow(final Car another) { 

    System.out.println("Following the " + another.toString()); 

} 

public void repair() { 

    System.out.println("Repaired " + this.toString()); 

} 

}

3.1 构造器引用

语法:
Class::new,或者更一般的Class< T >::new

实例如下:

final Car car = Car.create( Car::new ); 

final List< Car > cars = Arrays.asList( car );

3.2、静态方法引用

语法
Class::static_method,

实例如下:

cars.forEach( Car::collide );

3.3、类的成员方法引用

语法
Class::method

实例如下:

cars.forEach( Car::repair );

3.4、实例对象的成员方法的引用

语法
instance::method

实例如下

final Car police = Car.create( Car::new ); 

cars.forEach( police::follow );

四 Stream API

4.1 Stream流简介:

Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。

4.2 Stream对象的创建:

    使用数组构建
    String[] strArray = new String[] {"a", "b", "c"};
    Stream<String> stream = Stream.of(strArray);
    Stream<String> stream = Arrays.stream(strArray);
    
    利用集合构建(不支持Map集合)
    //在 Java 8 中, 集合接口有两个方法来生成流:
    //stream() − 为集合创建串行流。
    //parallelStream() − 为集合创建并行流。
    stream = Arrays.asList(strArray).stream();
    数值Stream的构建
    //[1,3)
    IntStream stream2 = IntStream.range(1, 3);
    //[1,3]
    IntStream stream3 = IntStream.rangeClosed(1, 3)
    = list.stream();

对于基本数值类型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。

4.3 Stream转换为其它类型:

    // 1. 转换为Array
    String[] strArray = stream.toArray(String[]::new);
    // 2. 转换为Collection
    Stream<String> stream = Stream.of("hello","world","tom");
    //List<String> list1 = stream.collect(Collectors.toList());
    //List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
    //Set<String> set3 = stream.collect(Collectors.toSet());
    Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
    // 3. 转换为String
    Stream<String> stream = Stream.of("hello","world","tom");
    String str = stream.collect(Collectors.joining()).toString();

4.4 Stream常用操作

在使用流的时候,你首先需要从一些来源中获取一个流,执行一个或者多个中间操作,然后执行一个最终操作。

中间操作(Intermediate)包括:
filter、map、flatMap、peel、distinct、sorted、limit和substream
终止操作(Terminal):
forEach、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst和 findAny

java.util.stream.Collectors是一个非常有用的实用类。该类实现了很多归约操作,例如将流转换成集合和聚合元素。

4.4.1 map/flatMap

map/flatMap映射 根据指定的Function接口把 Stream中 的每一个元素,映射成另外一个元素。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    private static void mapTes() {
        Stream<String> wordList = Stream.of("hello","world","tom");
        //转换大写
        Stream<String> stringStream = wordList.map(String::toUpperCase);
        List<String> strings = stringStream.collect(Collectors.toList());
        //也可以直接使用forEach循环输出
        //strings.forEach(System.out::println);

        //map生成的是个1:1映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。
        //flatMap 把 stream1 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终新的 stream2 里面已经没有 List 了,都是直接的数字。
        Stream<List<Integer>> streamList = Stream.of(
                Arrays.asList(1),
                Arrays.asList(2, 3),
                Arrays.asList(4, 5, 6)
        );
      //  streamList.forEach(e->System.out.println(e));//输出[1] [2, 3] [4, 5, 6]
        Stream<Integer> streamList2 = streamList. flatMap(e -> {
            System.out.println("streamList2: "+e);
            return e.stream();
        });

        System.out.println("streamList: "+streamList2); //java.util.stream.ReferencePipeline$7@694f9431
        streamList2.forEach(e->System.out.println(e));//输出1 2 3 4 5 6
    }
4.4.2 forEach 遍历

接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

forEach 是 terminal 操作,执行完stream就不能再用了

  private static void forEachTes() {
        List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
        list.stream().forEach(System.out::println);
    }
4.4.3 filter 过滤

对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。

 private static void filterTes() {
        List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
        List<String> collect = list.stream().filter(s -> s.length() > 4).collect(Collectors.toList());
        collect.forEach(System.out::println);
    }
4.4.4 peek

对每个元素执行操作并返回一个新的 Stream

注意:peek是一个intermediate 操作,调用peek之后,一定要有一个最终操作

private static void peekTes() {
        List<String> list = Arrays.asList("one", "two", "three", "four");
        List<String> list2 = list.stream()
                .filter(e -> e.length() > 3)
                .peek(e -> System.out.println("第一次符合条件的值为: " + e))
                .filter(e->e.length()>4)
                .peek(e -> System.out.println("第二次符合条件的值为: " + e))
                .collect(Collectors.toList());
        list2.stream().forEach(System.out::println);//打印结果为 three
    }
4.4.5 sort 排序

排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据list是不会被修改的。

private static void sortTes() {
        List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
        //按照字符串的长短排序
        list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);
    }
4.4.6 match 匹配

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。

    private static void matchTes() {
        List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
        boolean allMatch = list.stream().allMatch((s)->s.startsWith("j"));
        System.out.println(allMatch);
    }
4.4.7 Reduce 规约/合并

这是一个最终操作,允许通过指定的函数来将stream中的多个元素规约合并为一个元素。
Stream.reduce,常用的方法有average, sum, min, max, and count,返回单个的结果值, 并且reduce操作每处理一个元素总是创建一个新值。
1.先调用stream方法
2.再排序,按照字符串的长度进行排序,长的在前短的再后
3.再过滤,字符串必须是以字符’j’开头的
4.再进行映射,把每个字符串后面拼接上"_briup"
5.再调用reduce进行合并数据,使用"|"连接字符串
6.最后返回Optional类型数据,处理好的字符串数据就封装在这个对象中

   private static void reduceTes() {
        List<String> list =Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
        Optional<String> reduce = list.stream()
                .sorted((s1,s2)->s2.length()-s1.length())
                .filter(s->s.startsWith("j"))
                .map(s->s+"_briup")
                .reduce((s1,s2)->s1+"|"+s2);
        String s = reduce.get();
        System.out.println(s);
    }
4.4.8 limit/skip

limit 返回 Stream 的前面 n 个元素;skip 则是跳过前 n 个元素只要后面的元素

private static void skipTes() {
        List<String> list = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
        list.stream().limit(5).forEach(System.out::println);
        list.stream().skip(6).forEach(System.out::println);
    }
4.4.9 Collectors

java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的操作。

例如把Stream转变输出为 Collection,或者把 Stream 元素进行分组。

       private static void collectorsTes() {
        List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
        List<String> result = list.stream().filter(s->s.length()>4).collect(Collectors.toList());
        System.out.println(result);
        //分组:按照字符串的长度分组
        Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(String::length));
        System.out.println(collect);
    }
4.4.10 并行Streams
   private static void parallelTes() {
        //生成100万个不同的字符串放到集合中
        int max = 1000000;
        List<String> values = new ArrayList<String>(max);
        for (int i = 0; i < max; i++) {
            UUID uuid = UUID.randomUUID();
            values.add(uuid.toString());
        }


        //1纳秒*10^9=1秒
        long t0 = System.nanoTime();
        //串行stream
        //long count = values.stream().sorted().count();
        //并行stream
        long count = values.parallelStream().sorted().count();
        long t1 = System.nanoTime();

        long time = t1 - t0;
        System.out.println(count);
        System.out.println(time); //1034319329 833816253  904603571  904603571
                                  //537822397  681201464  944406460  552126394
    }

扩展:

《JAVA8函数式编程》
读者可能已经发现,Lambda 表达式有一个常见的用法:Lambda 表达式经常调用参数。比 如想得到艺术家的姓名,Lambda 的表达式如下:
artist -> artist.getName() 这种用法如此普遍,因此Java 8 为其提供了一个简写语法,叫作方法引用,帮助程序员重 用已有方法。
用方法引用重写上面的Lambda 表达式,代码如下: Artist::getName 标准语法为Classname::methodName。
需要注意的是,虽然这是一个方法,但不需要在后面 加括号,因为这里并不调用该方法。
我们只是提供了和Lambda 表达式等价的一种结构, 在需要时才会调用。凡是使用Lambda 表达式的地方,就可以使用方法引用。
构造函数也有同样的缩写形式,如果你想使用Lambda 表达式创建一个Artist 对象,
可能 会写出如下代码: (name, nationality) -> new Artist(name, nationality) 使用方法引用,上述代码可写为: Artist::new
这段代码不仅比原来的代码短,而且更易阅读。
Artist::new 立刻告诉程序员这是在创建 一个Artist 对象,程序员无需看完整行代码就能弄明白代码的意图。
另一个要注意的地方 是方法引用自动支持多个参数,前提是选对了正确的函数接口。 还可以用这种方式创建数组,下面的代码创建了一个字符串型的数组: String[]::new 从现在开始,我们将在合适的地方使用方法引用,因此读者很快会看到更多的例子。
一开 始探索Java 8 时,有位朋友告诉我,方法引用看起来“就像在作弊”。他的意思是说,了 解如何使用Lambda 表达式让代码像数据一样在对象间传递之后,这种直接引用方法的方 式就像“作弊”。 放心,这不是在作弊。读者只要记住,每次写出形如x -> foo(x) 的Lambda 表达式时, 和直接调用方法foo 是一样的。方法引用只不过是基于这样的事实,提供了一种简短的语 法而已。

五、Optional类

常用方法:
isPresent:如果值存在返回true,否则返回false。
ifPresent:如果Optional实例有值则为其调用consumer,否则不做处理
get:如果Optional有值则将其返回,否则抛出NoSuchElementException。因此也不经常用。
orElse:如果有值则将其返回,否则返回指定的其它值。
orElseGet:orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值。

orElseGet方法可以接受Supplier接口的实现用来生成默认值。

参考:java8新特性 lambda Stream map(函数式编程)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值