java8新特性學習

rowandjj發表於2015-09-28

內容:
1. lambda表示式
2. streamAPI
3. 內建函式介面
4. 介面預設實現方法
5. android中使用lambda/streamAPI

=======

1.lambda表示式:
幾種形式:

()->statement
()->(statement)
()->{statement}

以Thread為例:

new Thread(new Runnable(){
   public void run(){
    }
}).start();
new Thread(()->{
     System.out.println(Thread.currentThread().getName());
}).start();

再比如:

Collections.sort(list, (x, y) -> y - x);

lambda表示式簡化了匿名內部類的寫法.它要求匿名內部類中只有一個抽象方法,這其實也稱為函式介面。如果該抽象方法有多個引數,可以這樣寫:

(arg1,arg2)->{
}
(int arg1,double arg2)->{
}

兩種寫法都是可以的。
lambda的範圍與匿名內部類類似,可以訪問外部區域的區域性final變數,以及成員變數和靜態變數。

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
stringConverter.convert(2);     // 3
//但是與匿名物件不同的是,變數num並不需要一定是final。
int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
stringConverter.convert(2);     // 3
//num在編譯的時候被隱式地當做final變數來處理,下面的寫法是錯誤的
int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;

2.函式介面:
函式式介面是隻包含一個抽象方法的介面。對於函式式介面,除了可以使用Java中標準的方法來建立實現物件之外,還可以使用lambda表示式來建立實現物件。這可以在很大程度上簡化程式碼的實現。在使用lambda表示式時,只需要提供形式引數和方法體。由於函式式介面只有一個抽象方法,所以通過lambda表示式宣告的方法體就肯定是這個唯一的抽象方法的實現,而且形式引數的型別可以根據方法的型別宣告進行自動推斷。

3.內建函式介面:
Java 8 API 還提供了很多新的函式式介面,來降低程式設計師的工作負擔。

Predicates
Predicate是一個布林型別的函式,該函式只有一個輸入引數。Predicate介面包含了多種預設方法,用於處理複雜的邏輯動詞(and, or,negate)

private static void testPredicate(List<String> list,Predicate<String> p){
        list.stream().filter(p).forEach(arg->{
            System.out.println(arg);
        });
    }

//client呼叫
List<String> l = Arrays.asList("aaa","hhaha","hiahia","hehe");
testPredicate(l,(arg)->arg.length()==3);

testPredicate方法的功能是在list集合中通過predicate指定的規則過濾元素,最後把過濾通過的元素列印出來。過濾條件的實現當然可以通過lambda表示式:(arg)->arg.length==3,意為過濾條件是元素的size為3。當然,Predicate可以允許有多個條件:

Predicate<String> p1 = (arg)->arg.charAt(0)=='h';
    Predicate<String> p2 = (arg)->arg.length()<6;
    testPredicate(l,p1.and(p2));

原理可以參考Predicate原始碼:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

可見,Predicate其實是一個函式介面。negate/and/or都是它的預設方法。

Functions
Function介面接收一個引數,並返回單一的結果。預設方法可以將多個函式串在一起(compse, andThen)

String s = "abcdefg";
Function<String,String> f = arg->arg.substring(3);
System.out.println(f.apply(s));//defg

再比如:

String s = "abcdefg";
Function<String,String> f = arg->arg.substring(3);
Function<String,Integer> f2 = f.andThen(arg->(arg.length()));
System.out.println(f2.apply(s));//輸出4

先計算f,返回arg.substring(3),然後對返回的String執行arg.length(),所以結果應該是4.

參考原始碼:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

Supplier:
Supplier介面產生一個給定型別的結果。與Function不同的是,Supplier沒有輸入引數。
比如構造物件:

Supplier<Person> s = ()->new Person();
s.get().sayHello();
static class Person{
        public void sayHello(){
            System.out.println("hello");
        }

}

參考原始碼:

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

Consumers:
Consumer代表了在一個輸入引數上需要進行的操作。
比如:

private static void testConsumer(List<Integer> l,Consumer<Integer> consumer){
        l.stream().forEach(i->{
            consumer.accept(i);
        });
    }
testConsumer(Arrays.asList(1222,12,9),i->{System.out.println("hello:"+i);});

BiConsumer:
跟Consumer類似,不過接受兩個引數,常見的場景是Map的遍歷。
Map的foreach遍歷:

Map<String,String> map = new HashMap<>();

map.put("aa", "value1");
map.put("bb", "value2");
map.put("cc", "value3");

map.forEach((arg1,arg2)->{
    System.out.println(arg1+","+arg2);
});

4.Stream API:
java.util.Stream表示了某一種元素的序列,在這些元素上可以進行各種操作。Stream操作可以是中間操作,也可以是完結操作。完結操作會返回一個某種型別的值,而中間操作會返回流物件本身,並且你可以通過多次呼叫同一個流操作方法來將操作結果串起來。Stream是在一個源的基礎上建立出來的,例如java.util.Collection中的list或者set(map不能作為Stream的源)。Stream操作往往可以通過順序或者並行兩種方式來執行。
常見的操作filter/sorted/map/match/count/reduce/
比如現在建立一個集合:

List<String> l = Arrays.asList("aaa","hhaha","hiahia","hehe");

filter:過濾特定的元素(返回stream)

Object[] l1 = l.stream().filter(arg->{
    return arg.length()>3;
}).toArray();

for(int i = 0; i<l1.length; i++){
    System.out.println(l1[i]);
}

map:對每個元素執行某種操作:(返回stream)

Object[] l2 = l.stream().map(arg1->arg1.toUpperCase()).toArray();

for(int i = 0; i<l2.length; i++){
    System.out.println(l2[i]);
}

sorted:排序(返回stream)

l.stream().sorted().forEach(arg->System.out.println(arg));

Match:匹配某種條件,有anyMatch/allMatch/noneMatch ( 返回boolean)

boolean result = l.stream().anyMatch(arg->arg.startsWith("a"));
System.out.println(result);

Parallel Streams
像上面所說的,流操作可以是順序的,也可以是並行的。順序操作通過單執行緒執行,而並行操作則通過多執行緒執行。

l.parallelStream().filter(arg->arg.startsWith("h")).map(arg->arg.toUpperCase()).forEach(arg->{
            System.out.println(arg);
        });

使用上跟stream()一樣,但是效率更高.

5.介面的預設實現:
java8允許介面中有預設的實現方法,方法需加default宣告:

@FunctionalInterface
public interface FooInterface {


    public default void funcA(){
        System.out.println("hello java8");
    }

    public int evaluate();

}

@FunctionalInterface註解代表當前介面是一個函式介面,也就是說裡面必須只有一個抽象方法,如果出現多個則會報錯,當然,預設方法不算。

注:
java8的介面和抽象類的區別:
1.抽象類注重繼承,是為了繼承而生。
2.抽象類可以有構造器、成員屬性,介面不能有構造器,成員都是靜態變數,隸屬於類。
3.java8介面的預設方法主要作用是相容低版本。因為當一個介面定義後之後,如果後期想增加一個方法的話,會導致之前的實現類無法使用,必須也實現新方法。預設方法的出現可以解決這一問題,只需增加一個預設方法,之前的實現類不會報錯,而且新實現的類可以複寫預設方法也可以不復寫。
參考:

http://stackoverflow.com/questions/19998454/interface-with-default-methods-vs-abstract-class-in-java-8

想在android裡面使用?

方案:
retrolambda+Lightweight-Stream-API
地址:

https://github.com/evant/gradle-retrolambda
https://github.com/aNNiMON/Lightweight-Stream-API

根目錄下gradle檔案中增加:

 dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
        classpath 'me.tatarka:gradle-retrolambda:3.2.3'

    }

module目錄下gradle檔案中增加:

apply plugin: 'me.tatarka.retrolambda'
dependencies {
  ...
  compile 'com.annimon:stream:1.0.3'
  ...
}

另需在gradle中增加編譯選項:

android{
compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
} 

相關文章