JAVA8 新特性~~~~

Lambda 表达式&函数式接口

Lambda表达式好像已经出现很久了好像大部分人还是比较倾向于使用内部类
(老程序员表示Lambda表达式影响可读性,但是在很多组件中使用Lambda表达式还是很有必要的)

Lamdba表达式和接口是分不开的,JAVA8改动Lambda表达式的同时也新增了函数式接口

函数式接口

Functional Interface的定义很简单:任何包含唯一一个抽象方法的接口都可以称之为函数式接口。

但是函数式接口中还可以存在签名与Object的public方法相同的接口(毕竟最终父类时Object),并且可以存在静态方法。

@FunctionalInterface
interface FunctionalInterfaceWithStaticMethod {
    static int sum(int[] array) {
        return Arrays.stream(array).reduce((a, b) -> a+b).getAsInt();
    }

    boolean equals(Object obj);
    
    void apply();
}
//这依旧是一个函数式接口

为了让编译器帮助我们确保一个接口满足函数式接口的要求,Java8提供了@FunctionalInterface注解。

另外JDK中已有的一些接口本身就是函数式接口,如Runnable。 JDK 8中又增加了java.util.function包, 提供了常用的函数式接口。

举个函数式接口的栗子

@FunctionalInterface  
public interface Runnable {  
    public abstract void run();  
}  
//Runnable接口提供了一个run()抽象方法

在java8之前,你可以通过匿名内部类来实现对这个接口的调用,像下面这样

Thread thread = new Thread(new Runnable() {
  public void run() {
    System.out.println("In another thread");
  }
}); 

但是如果我们想要我们的代码更加优雅,我们应该使用Lambda表达式,当我们使用Lambda表达式刚刚代码就可以这样写:

Thread thread = new Thread(() -> 
System.out.println("In another thread"));

Lambda表达式

Lambda表达式在Java8中最大的改动是
它允许把函数作为一个方法的参数(函数作为参数传递进方法中)

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动确定返回值

还是先上栗子:

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括号
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括号
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

变量作用域

  • lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。(其实也可以不声明final)
int num = 1;
Converter<Integer, String> s =
        (param) -> String.valueOf(param + num);
num = 5;
//编译会出错
  • 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); 
 //编译会出错 

方法引用

方法引用通过方法的名字来指向一个方法。
方法引用使用一对冒号 ::
内容比较简单并且Java核心已经有讲过
所以直接上栗子,我们在 Car 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。

package com.runoob.main;
 
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
 
class Car {

    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());
    }
}
  • 构造器引用
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
  • 静态方法引用
cars.forEach( Car::collide );
//Class< T >::new
  • 特定类的任意对象的方法引用
    //Class::static_method
cars.forEach( Car::repair );
//Class::method
  • 特定对象的方法引用
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
//instance::method

Java Stream

Stream是 Java 8新增加的类,用来补充集合类
既然是补充集合类,那么先来列举一下它们的区别(主要是与迭代器的区别):
1.不储存数据 流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
2.函数式编程 流的操作不会修改数据源,例如filter不会将数据源中的数据删除。
3.延迟操作 流的很多操作如filter,map等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行。
4.可以解绑 对于无限数量的流,有些操作是可以在有限的时间完成的,比如limit(n) 或 findFirst(),这些操作可是实现"短路"(Short-circuiting),访问到有限的元素后就可以返回。
5.纯消费 流的元素只能访问一次,类似Iterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流。

在具体讲解方法之前,还是先来看一个栗子:

List<Integer> a = {1,2,3};
List<Integer> b = a.stream()
             .filter(i ->  i >= 2 )
             .collect(Collectors.toList());

我们可以把以上代码分为三部分来具体分析:
1.创建Stream

  • 通过集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()。
  • 通过Arrays.stream(Object[])方法, 比如Arrays.stream(new int[]{1,2,3})。
  • 使用流的静态方法,比如Stream.of(Object[]), IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n *
  • BufferedReader.lines()从文件中获得行的流。
  • Files类的操作路径的方法,如list、find、walk等。
  • 随机数流Random.ints()。
  • 更底层的使用StreamSupport,它提供了将Spliterator转换成流的方法。

2.中间操作

  • distinct
    distinct保证输出的流中包含唯一的元素,它是通过Object.equals(Object)来检查是否包含相同的元素。
List<String> l = Stream.of("a","b","c","b")
        .distinct()
        .collect(Collectors.toList());
System.out.println(l); //[a, b, c]
  • filter
    filter返回的流中只包含满足断言(predicate)的数据。
    下面的代码返回流中的偶数集合。
List<Integer> l = IntStream.range(1,10)
        .filter( i -> i % 2 == 0)
        .boxed()
        .collect(Collectors.toList());
System.out.println(l); //[2, 4, 6, 8]
  • map
    map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。
map( c -> c*2)
  • flatmap
    flatmap方法将映射后的流的元素全部放入到一个新的流中。
String poetry = "Where, before me, are the ages that have gone?\n" +
        "And where, behind me, are the coming generations?\n" +
        "I think of heaven and earth, without limit, without end,\n" +
        "And I am all alone and my tears fall down.";
Stream<String> lines = Arrays.stream(poetry.split("\n"));
Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" ")));

  • limit
    limit方法指定数量的元素的流。对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。
List<Integer> l = IntStream.range(1,100).limit(5)
        .boxed()
        .collect(Collectors.toList());
System.out.println(l);//[1, 2, 3, 4, 5]
  • peek
    peek方法方法会使用一个Consumer消费流中的元素,但是返回的流还是包含原来的流中的元素。
String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
        .peek(System.out::println) //a,b,c,d
        .count();
  • sorted
    sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。
    sorted(Comparator<? super T> comparator)可以指定排序的方式。

对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。

String[] arr = new String[]{"b_123","c+342","b#632","d_123"};
List<String> l  = Arrays.stream(arr)
        .sorted((s1,s2) -> {
            if (s1.charAt(0) == s2.charAt(0))
                return s1.substring(2).compareTo(s2.substring(2));
            else
                return s1.charAt(0) - s2.charAt(0);
        })
        .collect(Collectors.toList());
System.out.println(l); //[b_123, b#632, c+342, d_123]

  • skip
    skip返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流。

3.终点操作

  • Match
    分为三种具体方法,用来检查流中的元素是否满足段言。
public boolean  allMatch(Predicate<? super T> predicate)
public boolean  anyMatch(Predicate<? super T> predicate)
public boolean  noneMatch(Predicate<? super T> predicate)

  • count
    count方法返回流中的元素的数量。

  • collect
    这是一个比较重要的终点操作,实现最终对处理过的流的收集。辅助类Collectors提供了很多的collector,可以满足我们日常的需求,你也可以创建新的collector实现特定的需求。

  • find
    findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
    findFirst()返回第一个元素,如果流为空,返回空的Optional。

  • forEach
    forEach遍历流的每一个元素,执行指定的action。

Stream.of(1,2,3,4,5).forEach(System.out::println);

  • max/min
    max返回流中的最大值,
    min返回流中的最小值。

  • toArray()
    将流中的元素放入到一个数组中。

多说一句~~~~~~~~~
流可以从非线程安全的集合中创建,当流的管道执行的时候,非concurrent数据源不应该被改变。下面的代码会抛出java.util.ConcurrentModificationException异常:

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
sl.forEach(s -> l.add("three"));

但是使用CopyOnWriteArrayList可以解决这个问题

Optional——一个可以为 null 的容器

Java 8中的Optional<T>是一个可以包含或不可以包含非空值的容器对象,在 Stream API中很多地方也都使用到了Optional。(暂时没有找到特别合适的用法...)

基本方法:

  • of()

  • ofNullable()

  • isPresent()
    如果值存在,返回 true,否则返回 false

  • map()

  • orElse()

  • orElseGet()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • Java 8自Java 5(发行于2004)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发...
    谁在烽烟彼岸阅读 884评论 0 4
  • Nothing is imposible for a willing heart. Java 8 (又称为 jdk...
    北纬26阅读 1,009评论 1 6
  • 家中三个哥哥黑不溜秋的,吊儿郎当,而她,却生就一副美人坯子的模样,乖巧懂事,聪明伶俐。父亲没怎么读过书,也没啥...
    钱一青子阅读 315评论 0 0
  • “ 要么不做,做就做好” 前几天,我在微信上呼叫夏天同学,让他帮我设计一下微信公众号的二维码图片。 夏天同学是跟我...
    斤斗云阅读 386评论 0 1
  • 从心底里去爱每一个阶段的你,学会自己陪伴自己,去宽容自己。如果你真的可以去欣赏每一个阶段的你,我觉得你会是美的。 ...
    童希园阅读 181评论 0 0