jdk1.8


Jdk1.8新特性

毫無疑問,Java 8是Java自Java 5(發布於2004年)之后的最重要的版本。這個版本包含語言、編譯器、庫、工具和JVM等方面的十多個新特性。在本文中我們將學習這些新特性,並用實際的例子說明在什么場景下適合使用。

這個教程包含Java開發者經常面對的幾類問題:

  • 語言
  • 編譯器
  • 工具
  • 運行時(JVM)

 Lambda表達式和函數式接口

簡介:

Lambda表達式(也稱為閉包)是Java 8中最大和最令人期待的語言改變。它允許我們將函數當成參數傳遞給某個方法,或者把代碼本身當作數據處理:函數式開發者非常熟悉這些概念。很多JVM平台上的語言(Groovy、Scala等)從誕生之日就支持Lambda表達式,但是Java開發者沒有選擇,只能使用匿名內部類代替Lambda表達式。

Lambda的設計耗費了很多時間和很大的社區力量,最終找到一種折中的實現方案,可以實現簡潔而緊湊的語言結構。最簡單的Lambda表達式可由逗號分隔的參數列表、->符號和語句塊組成

例子:

1.示例

Arrays.asList("a","b","c").forEach(e -> System.out.println(e));

2.在上面這個代碼中的參數e的類型是由編譯器推理得出的,你也可以顯式指定該參數的類型,例如:

Arrays.asList("a","b","c").forEach((String e) -> System.out.println(e));

3.如果Lambda表達式需要更復雜的語句塊,則可以使用花括號將該語句塊括起來,類似於Java中的函數體,例如:

Arrays.asList("a","b","c").forEach((String e) -> {
    System.out.println(e);
    System.out.println(e);
});

4Lambda表達式可以引用類成員和局部變量(會將這些變量隱式得轉換成final),例如下列兩個代碼塊的效果完全相同:

String name=",";
final String name2=",,";
Arrays.asList("a","b","c").forEach((String e) -> {
    System.out.println(e+name);
    System.out.println(e+name2);
});

5Lambda表達式有返回值,返回值的類型也由編譯器推理得出。如果Lambda表達式中的語句塊只有一行,則可以不用使用return語句,下列兩個代碼片段效果相同:

Arrays.asList("a","b","c").sort((e1,e2)->e1.compareTo(e2));

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

函數接口

Lambda的設計者們為了讓現有的功能與Lambda表達式良好兼容,考慮了很多方法,於是產生了函數接口這個概念。函數接口指的是只有一個函數的接口,這樣的接口可以隱式轉換為Lambda表達式。java.lang.Runnablejava.util.concurrent.Callable是函數式接口的最佳例子。在實踐中,函數式接口非常脆弱:只要某個開發者在該接口中添加一個函數,則該接口就不再是函數式接口進而導致編譯失敗。為了克服這種代碼層面的脆弱性,並顯式說明某個接口是函數式接口,Java 8 提供了一個特殊的注解@FunctionalInterface(Java 庫中的所有相關接口都已經帶有這個注解了)

@FunctionalInterface
public interface Functional{
    void method();
  }

不過有一點需要注意,默認方法和靜態方法不會破壞函數式接口的定義,因此如下的代碼是合法的。

@FunctionalInterface
public interface Functional{
    void method();

    default void defaultMethod(){

    }
}

接口的默認方法和靜態方法

Java 8使用兩個新概念擴展了接口的含義:默認方法和靜態方法。默認方法使得接口有點類似traits,不過要實現的目標不一樣。默認方法使得開發者可以在 不破壞二進制兼容性的前提下,往現存接口中添加新的方法,即不強制那些實現了該接口的類也同時實現這個新加的方法。

默認方法和抽象方法之間的區別在於抽象方法需要實現,而默認方法不需要。接口提供的默認方法會被接口的實現類繼承或者覆寫,例子代碼如下:

private interface Defaulable{
    default String notRequired(){
        return "Default implementation";
    }
}
private static class DefaultableImpl implements Defaulable {
   
}
private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

方法實現與打印:

Defaulable defaulable = DefaulableFactory.create(DefaultableImpl::new);
System.out.println(defaulable.notRequired());

Defaulable defaulable1 = DefaulableFactory.create(OverridableImpl::new);
System.out.println(defaulable1);

方法的引用

方法引用使得開發者可以直接引用現存的方法、Java類的構造方法或者實例對象。方法引用和Lambda表達式配合使用,使得java類的構造方法看起來緊湊而簡潔,沒有很多復雜的模板代碼。

public class Demo2 {
    public static Demo2 create(Supplier<Demo2> supplier){
        return supplier.get();
    }

    public static void collide(Demo2 demo2){
        System.out.println("Collided "+demo2.toString());
    }

    public void follow( Demo2 another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}

引用方法:

/**
 *
第一種方法引用的類型是構造器引用,語法是Class::new,或者更一般的形式:Class<T>::new。注意:這個構造器沒有參數。
 
*/
Demo2 demo2= Demo2.create(Demo2::new);
List<Demo2> demo2s= Arrays.asList(demo2);
/**
 *
第二種方法引用的類型是靜態方法引用,語法是Class::static_method。注意:這個方法接受一個Car類型的參數。
 
*/
demo2s.forEach(Demo2::collide);
/**
 *
第三種方法引用的類型是某個類的成員方法的引用,語法是Class::method,注意,這個方法沒有定義入參:
 
*/
demo2s.forEach(Demo2::repair);
/**
 *
第四種方法引用的類型是某個實例對象的成員方法的引用,語法是instance::method。注意:這個方法接受一個Car類型的參數:
 
*/

demo2s.forEach(demo2::follow);

重復注解

自從Java 5中引入注解以來,這個特性開始變得非常流行,並在各個框架和項目中被廣泛使用。不過,注解有一個很大的限制是:在同一個地方不能多次使用同一個注解。Java 8打破了這個限制,引入了重復注解的概念,允許在同一個地方多次使用同一個注解。

在Java 8中使用@Repeatable注解定義重復注解,實際上,這並不是語言層面的改進,而是編譯器做的一個trick,底層的技術仍然相同。

拓寬注解的應用場景

public class Annotations {

    @Retention( RetentionPolicy.RUNTIME )

    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )

    public @interface NonEmpty {       

    }

 

    public static class Holder< @NonEmpty T > extends @NonEmpty Object {

        public void method() throws @NonEmpty Exception {           

        }

    }

 

    @SuppressWarnings( "unused" )

    public static void main(String[] args) {

        final Holder< String > holder = new @NonEmpty Holder< String >();       

        @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();       

    }

}

Java編譯器的新特性

獲取參數名稱:

為了在運行時獲得Java程序中方法的參數名稱,老一輩的Java程序員必須使用不同方法,例如Paranamer liberary。Java 8終於將這個特性規范化,在語言層面(使用反射API和Parameter.getName()方法)和字節碼層面(使用新的javac編譯器以及-parameters參數)提供支持。

Optional

Java應用中最常見的bug就是空值異常。在Java 8之前,Google Guava引入了Optionals類來解決NullPointerException,從而避免源碼被各種null檢查污染,以便開發者寫出更加整潔的代碼。Java 8也將Optional加入了官方庫。

Optional僅僅是一個容易:存放T類型的值或者null。它提供了一些有用的接口來避免顯式的null檢查,可以參考Java 8官方文檔了解更多細節。

接下來看一點使用Optional的例子:可能為空的值或者某個類型的值:

Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

如果Optional實例持有一個非空值,則isPresent()方法返回true,否則返回false;orElseGet()方法,Optional實例持有null,則可以接受一個lambda表達式生成的默認值;map()方法可以將現有的Opetional實例的值轉換成新的值;orElse()方法與orElseGet()方法類似,但是在持有null的時候返回傳入的默認值。

Optional方法詳解

Of

為非null的值創建一個Optional。

of方法通過工廠方法創建Optional類。需要注意的是,創建對象時傳入的參數不能為null。如果傳入參數為null,則拋出NullPointerException 

//調用工廠方法創建Optional實例
Optional<String> name = Optional.of("Sanaulla");
//傳入參數為null,拋出NullPointerException.
Optional<String> someNull = Optional.of(null);

ofNullable

為指定的值創建一個Optional,如果指定的值為null,則返回一個空的Optional。

ofNullable與of方法相似,唯一的區別是可以接受參數為null的情況。示例如下:

//下面創建了一個不包含任何值的Optional實例
//例如,值為'null'
Optional empty = Optional.ofNullable(null);

isPresent

非常容易理解

如果值存在返回true,否則返回false。

//isPresent方法用來檢查Optional實例中是否包含值
if (name.isPresent()) {
    //在Optional實例內調用get()返回已存在的值
    System.out.println(name.get());//輸出Sanaulla
}

Get

如果Optional有值則將其返回,否則拋出NoSuchElementException。

上面的示例中,get方法用來得到Optional實例中的值。下面我們看一個拋出NoSuchElementException的例子:

//執行下面的代碼會輸出:No value present
try {
    //在空的Optional實例上調用get(),拋出NoSuchElementException
    System.out.println(empty.get());
} catch (NoSuchElementException ex) {
    System.out.println(ex.getMessage());
}

ifPresent

如果Optional實例有值則為其調用consumer,否則不做處理

要理解ifPresent方法,首先需要了解Consumer類。簡答地說,Consumer類包含一個抽象方法。該抽象方法對傳入的值進行處理,但沒有返回值。Java8支持不用接口直接通過lambda表達式傳入參數。

如果Optional實例有值,調用ifPresent()可以接受接口段或lambda表達式。類似下面的代碼:

//ifPresent方法接受lambda表達式作為參數。
//lambda表達式對Optional的值調用consumer進行處理。
name.ifPresent((value) -> {
    System.out.println("The length of the value is: " + value.length());
});

orElse

如果有值則將其返回,否則返回指定的其它值。

如果Optional實例有值則將其返回,否則返回orElse方法傳入的參數。示例如下:

//如果值不為null,orElse方法返回Optional實例的值。
//如果為null,返回傳入的消息。
//輸出:There is no value present!
Optional<String> name = Optional.of("Sanaulla");
//傳入參數為null,拋出NullPointerException.
Optional<String> someNull = Optional.of(null);
System.out.println(empty.orElse("There is no value present!"));
//輸出:Sanaulla
System.out.println(name.orElse("There is some value!"));

orElseGet

orElseGet與orElse方法類似,區別在於得到的默認值。orElse方法將傳入的字符串作為默認值,orElseGet方法可以接受Supplier接口的實現用來生成默認值。示例如下:

Optional<String> name = Optional.of("Sanaulla");
//傳入參數為null,拋出NullPointerException.
Optional<String> someNull = Optional.of(null);
//orElseGet與orElse方法類似,區別在於orElse傳入的是默認值,
//orElseGet可以接受一個lambda表達式生成默認值。
//輸出:Default Value
System.out.println(someNull.orElseGet(() -> "Default Value"));
//輸出:Sanaulla
System.out.println(name.orElseGet(() -> "Default Value"));

orElseThrow

如果有值則將其返回,否則拋出supplier接口創建的異常。

在orElseGet方法中,我們傳入一個Supplier接口。然而,在orElseThrow中我們可以傳入一個lambda表達式或方法,如果值不存在來拋出異常。

try {
    //orElseThrow與orElse方法類似。與返回默認值不同,
    //orElseThrow會拋出lambda表達式或方法生成的異常
    Optional empty = Optional.ofNullable("asa");
    empty.orElseThrow(Exception::new);
} catch (Throwable ex) {
    //輸出: No value present in the Optional instance
    System.out.println(ex.getMessage());
}

map

map方法文檔說明如下:

如果有值,則對其執行調用mapping函數得到返回值。如果返回值不為null,則創建包含mapping返回值的Optional作為map方法返回值,否則返回空Optional

map方法用來對Optional實例的值執行一系列操作。通過一組實現了Function接口的lambda表達式傳入操作。如果你不熟悉Function接口,可以參考我的這篇博客。map方法示例如下:

Optional<String> name = Optional.of("Sanaulla");
//map方法執行傳入的lambda表達式參數對Optional實例的值進行修改。
//為lambda表達式的返回值創建新的Optional實例作為map方法的返回值。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));

flatMap

如果有值,為其執行mapping函數返回Optional類型返回值,否則返回空Optional。flatMap與map(Funtion)方法類似,區別在於flatMap中的mapper返回值必須是Optional。調用結束時,flatMap不會對結果用Optional封裝。

flatMap方法與map方法類似,區別在於mapping函數的返回值不同。map方法的mapping函數返回值可以是任何類型T,而flatMap方法的mapping函數必須是Optional參照map函數,使用flatMap重寫的示例如下:

//flatMap與map(Function)非常類似,區別在於傳入方法的lambda表達式的返回類型。
//map方法中的lambda表達式返回值可以是任意類型,在map函數返回之前會包裝為Optional。
//但flatMap方法中的lambda表達式返回值必須是Optionl實例。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//輸出SANAULLA

Filter

filter個方法通過傳入限定條件對Optional實例的值進行過濾。文檔描述如下:

如果有值並且滿足斷言條件返回包含該值的Optional,否則返回空Optional。

讀到這里,可能你已經知道如何為filter方法傳入一段代碼。是的,這里可以傳入一個lambda表達式。對於filter函數我們應該傳入實現了Predicate接口的lambda表達式。

現在我來看看filter的各種用法,下面的示例介紹了滿足限定條件和不滿足兩種情況:

//filter方法檢查給定的Option值是否滿足某些條件。
//如果滿足則返回同一個Option實例,否則返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//輸出Sanaulla

//另一個例子是Optional值不滿足filter指定的條件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
//輸出:name長度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));

Streams

新增的Stream API(java.util.stream)將生成環境的函數式編程引入了Java庫中。這是目前為止最大的一次對Java庫的完善,以便開發者能夠寫出更加有效、更加簡潔和緊湊的代碼。

public class Streams {

    private enum Status{
        OPEN,CLOSED
   
};

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }

    public static void main(String[] args) {
        final Collection< Task > tasks = Arrays.asList(
                new Task( Status.OPEN, 5 ),
                new Task( Status.OPEN, 13 ),
                new Task( Status.CLOSED, 8 )
        );

        long sum = tasks.stream().filter(task -> task.getStatus() == Status.OPEN).mapToInt(Task::getPoints).sum();

         System.out.println("Total points: "+sum);

         double reduce = tasks.stream().parallel().map(task -> task.getPoints()).reduce(0, Integer::sum);

         System.out.println("Total points (all tasks): "+reduce);
        
Map<Integer, List<Task>> collect = tasks.stream().collect(Collectors.groupingBy(Task::getPoints));

         System.out.println(collect);
        
List<String> collect1 = tasks.stream().mapToInt(Task::getPoints).asLongStream().mapToDouble( points -> points / reduce).boxed().mapToLong(weigth -> (long) (weigth * 100)).mapToObj(percentage -> percentage + "%").collect(Collectors.toList());

         System.out.println(collect1);

    }
}

首先,tasks集合被轉換成steam表示;其次,在steam上的filter操作會過濾掉所有CLOSED的task;第三,mapToInt操作基於每個task實例的Task::getPoints方法將task流轉換成Integer集合;最后,通過sum方法計算總和,得出最后的結果。

晚期操作(例如forEach或者sum),會遍歷steam並得出結果或者附帶結果;在執行晚期操作之后,steam處理線已經處理完畢,就不能使用了。在幾乎所有情況下,晚期操作都是立刻對steam進行遍歷。

可以通過steam處理io:

final Path path = new File( filename ).toPath();

try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {

    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );

}

Stream流特性解釋:

Stream接口還包含幾個基本類型的子接口如IntStream, LongStream 和 DoubleStream。

關於流和其它集合具體的區別,可以參照下面的列表:

  1. 不存儲數據 。流是基於數據源的對象,它本身不存儲數據元素,而是通過管道將數據源的元素傳遞給操作。
  2. 函數式編程 。流的操作不會修改數據源,例如filter不會將數據源中的數據刪除。
  3. 延遲操作 。流的很多操作如filter,map等中間操作是延遲執行的,只有到終點操作才會將操作順序執行。
  4. 可以解綁 。對於無限數量的流,有些操作是可以在有限的時間完成的,比如limit(n)或findFirst(),這些操作可是實現”短路”(Short-circuiting),訪問到有限的元素后就可以返回。
  5. 純消費 。流的元素只能訪問一次,類似Iterator,操作沒有回頭路,如果你想從頭重新訪問流的元素,對不起,你得重新生成一個新的流。

流的操作是以管道的方式串起來的。流管道包含一個數據源,接着包含零到N個中間操作,最后以一個終點操作結束。

Stream特征:

並行 Parallelism

所有的流操作都可以串行執行或者並行執行。除非顯示地創建並行流,否則Java庫中創建的都是串行流。Collection.stream()為集合創建串行流而Collection.parallelStream()為集合創建並行流。IntStream.range(int, int)創建的是串行流。通過parallel()方法可以將串行流轉換成並行流,sequential()方法將流轉換成並行流。

除非方法的Javadoc中指明了方法在並行執行的時候結果是不確定(比如findAny、forEach),否則串行和並行執行的結果應該是一樣的。

不干涉 Non-interference

流可以從非線程安全的集合中創建,當流的管道執行的時候,非concurrent數據源不應該被改變。下面的代碼會拋出java.util.ConcurrentModificationException異常:

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

在設置中間操作的時候,可以更改數據源,只有在執行終點操作的時候,才有可能出現並發問題(拋出異常,或者不期望的結果),比如下面的代碼不會拋出異常:

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

對於concurrent數據源,不會有這樣的問題,比如下面的代碼很正常:

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

雖然我們上面例子是在終點操作中對非並發數據源進行修改,但是非並發數據源也可能在其它線程中修改,同樣會有並發問題。

無狀態 Stateless behaviors

大部分流的操作的參數都是函數式接口,可以使用Lambda表達式實現。它們用來描述用戶的行為,稱之為行為參數(behavioral parameters)。

如果這些行為參數有狀態,則流的操作的結果可能是不確定的,比如下面的代碼:

List<String> l = new ArrayList(Arrays.asList("one", "two", ……));class State {    boolean s;}final State state = new State();Stream<String> sl = l.stream().map(e -> {    if (state.s)        return "OK";    else {        state.s = true;        return e;    } });sl.forEach(System.out::println);

上面的代碼在並行執行時多次的執行結果可能是不同的。這是因為這個lambda表達式是有狀態的。

副作用 Side-effects

有副作用的行為參數是被鼓勵使用的。

副作用指的是行為參數在執行的時候有輸入輸入,比如網絡輸入輸出等。

這是因為Java不保證這些副作用對其它線程可見,也不保證相同流管道上的同樣的元素的不同的操作運行在同一個線程中。

很多有副作用的行為參數可以被轉換成無副作用的實現。一般來說println()這樣的副作用代碼不會有害。

ArrayList<String> results = new ArrayList<>();stream.filter(s -> pattern.matcher(s).matches())      .forEach(s -> results.add(s));  // 副作用代碼

上面的代碼可以改成無副作用的。

List<String>results =    stream.filter(s -> pattern.matcher(s).matches())          .collect(Collectors.toList());  // No side-effects!

排序 Ordering

某些流的返回的元素是有確定順序的,我們稱之為encounter order。這個順序是流提供它的元素的順序,比如數組的encounter order是它的元素的排序順序,List是它的迭代順序(iteration order),對於HashSet,它本身就沒有encounter order。

一個流是否是encounter order主要依賴數據源和它的中間操作,比如數據源List和Array上創建的流是有序的(ordered),但是在HashSet創建的流不是有序的。

sorted()方法可以將流轉換成有序的,unordered可以將流轉換成無序的。除此之外,一個操作可能會影響流的有序,比如map方法,它會用不同的值甚至類型替換流中的元素,所以輸入元素的有序性已經變得沒有意義了,但是對於filter方法來說,它只是丟棄掉一些值而已,輸入元素的有序性還是保障的。

對於串行流,流有序與否不會影響其性能,只是會影響確定性(determinism),無序流在多次執行的時候結果可能是不一樣的。

對於並行流,去掉有序這個約束可能會提供性能,比如distinct、groupingBy這些聚合操作。

結合性 Associativity

一個操作或者函數op滿足結合性意味着它滿足下面的條件:

(a op b) op c == a op (b op c)

對於並發流來說,如果操作滿足結合性,我們就可以並行計算:

a op b op c op d == (a op b) op (c op d)

比如min、max以及字符串連接都是滿足結合性的。

Stream創建方式:

可以通過多種方式創建流:

1、通過集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()。

2、通過Arrays.stream(Object[])方法, 比如Arrays.stream(new int[]{1,2,3})。

3、使用流的靜態方法,比如Stream.of(Object[]),IntStream.range(int, int)或者Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n * 2),或者generate(Supplier<T> s)如Stream.generate(Math::random)。

4、BufferedReader.lines()從文件中獲得行的流。

5、Files類的操作路徑的方法,如list、find、walk等。

6、隨機數流Random.ints()。

7、其它一些類提供了創建流的方法,如BitSet.stream(),Pattern.splitAsStream(java.lang.CharSequence), 和JarFile.stream()。

8、更底層的使用StreamSupport,它提供了將Spliterator轉換成流的方法。

中間操作 intermediate operations

中間操作會返回一個新的流,並且操作是延遲執行的(lazy),它不會修改原始的數據源,而且是由在終點操作開始的時候才真正開始執行。這個Scala集合的轉換操作不同,Scala集合轉換操作會生成一個新的中間集合,顯而易見Java的這種設計會減少中間對象的生成。

常見流的中間操作:

Distinct

distinct保證輸出的流中包含唯一的元素,它是通過Object.equals(Object)來檢查是否包含相同的元素。

List<String> collect = Stream.of("a", "b", "c", "d").distinct().collect(Collectors.toList());
System.out.println(collect);

Filter

filter返回的流中只包含滿足斷言(predicate)的數據。

下面的代碼返回流中的偶數集合。

List<Integer> collect1 = IntStream.range(1, 10).filter(i -> i % 2 == 0).boxed().collect(Collectors.toList());
System.out.println(collect1);

Map

map方法將流中的元素映射成另外的值,新的值類型可以和原來的元素的類型不同。

下面的代碼中將字符元素映射成它的哈希碼(ASCII值)。

List<Integer> collect2 = Stream.of("a", "b", "c").map(c -> c.hashCode()).collect(Collectors.toList());
System.out.println(collect2);

Flatmap

flatmap方法混合了map+flattern的功能,它將映射后的流的元素全部放入到一個新的流中。它的方法定義如下:

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(" ")));
List<String> l = words.map(w -> {
    if (w.endsWith(",") || w.endsWith(".") || w.endsWith("?"))
        return w.substring(0, w.length() - 1).trim().toLowerCase();
    else return w.trim().toLowerCase();
}).distinct().sorted().collect(Collectors.toList());
System.out.println(l);

Limit

limit方法指定數量的元素的流。對於串行流,這個方法是有效的,這是因為它只需返回前n個元素即可,但是對於有序的並行流,它可能花費相對較長的時間,如果你不在意有序,可以將有序並行流轉換為無序的,可以提高性能。

List<Integer> collect3 = IntStream.range(1, 100).limit(5).boxed().collect(Collectors.toList());
System.out.println(collect3);

Peek

peek方法方法會使用一個Consumer消費流中的元素,但是返回的流還是包含原來的流中的元素。

String[] strings = {"a", "b", "c", "d"};
Arrays.asList(strings).stream().peek(System.out::println);

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> collect = 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(collect);

Skip

skip返回丟棄了前n個元素的流,如果流中的元素小於或者等於n,則返回空的流。

終點操作 terminal operations

Match

這一組方法用來檢查流中的元素是否滿足斷言。

allMatch只有在所有的元素都滿足斷言時才返回true,否則flase,流為空時總是返回true

anyMatch只有在任意一個元素滿足斷言時就返回true,否則flase,

noneMatch只有在所有的元素都不滿足斷言時才返回true,否則flase,

System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0)); //true
System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0)); //true

Count

count方法返回流中的元素的數量。它實現為:

mapToLong(e -> 1L).sum();

Collect

使用一個collector執行mutable reduction操作。輔助類 Collectors 提供了很多的collector,可以滿足我們日常的需求,你也可以創建新的collector實現特定的需求。它是一個值得關注的類,你需要熟悉這些特定的收集器,如聚合類averagingInt、最大最小值maxByminBy、計數counting、分組groupingBy、字符串連接joining、分區partitioningBy、匯總summarizingInt、化簡reducing、轉換toXXX等。】

Find

findAny()返回任意一個元素,如果流為空,返回空的Optional,對於並行流來說,它只需要返回任意一個元素即可,所以性能可能要好於findFirst(),但是有可能多次執行的時候返回的結果不一樣。findFirst()返回第一個元素,如果流為空,返回空的Optional。

forEach、forEachOrdered

forEach遍歷流的每一個元素,執行指定的action。它是一個終點操作,和peek方法不同。這個方法不擔保按照流的encounter order順序執行,如果對於有序流按照它的encounter order順序執行,你可以使用forEachOrdered方法。

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

最大最小值

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

Reduce

第一個方法使用流中的第一個值作為初始值,后面兩個方法則使用一個提供的初始值。

Optional<Integer> total = Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> x + y);
Integer total2 = Stream.of(1, 2, 3, 4, 5).reduce(0, (x, y) -> x + y);

toArray()

將流中的元素放入到一個數組中。

Date/time時間api

Java 8引入了新的Date-Time API(JSR 310)來改進時間、日期的處理。時間和日期的管理一直是最令Java開發者痛苦的問題。java.util.Date和后來的java.util.Calendar一直沒有解決這個問題(甚至令開發者更加迷茫)。

因為上面這些原因,誕生了第三方庫Joda-Time,可以替代Java的時間管理API。Java 8中新的時間和日期管理API深受Joda-Time影響,並吸收了很多Joda-Time的精華。新的java.time包包含了所有關於日期、時間、時區、Instant(跟日期類似但是精確到納秒)、duration(持續時間)和時鍾操作的類。新設計的API認真考慮了這些類的不變性(從java.util.Calendar吸取的教訓),如果某個實例需要修改,則返回一個新的對象。

我們接下來看看java.time包中的關鍵類和各自的使用例子。首先,Clock類使用時區來返回當前的納秒時間和日期。Clock可以替代System.currentTimeMillis()TimeZone.getDefault()

// Get the system clock as UTC offset

final Clock clock = Clock.systemUTC();

System.out.println( clock.instant() );

System.out.println( clock.millis() );

這個例子的輸出結果是:

2014-04-12T15:19:29.282Z

1397315969360

第二,關注下LocalDateLocalTime類。LocalDate僅僅包含ISO-8601日歷系統中的日期部分;LocalTime則僅僅包含該日歷系統中的時間部分。這兩個類的對象都可以使用Clock對象構建得到。

// Get the local date and local time

final LocalDate date = LocalDate.now();

final LocalDate dateFromClock = LocalDate.now( clock );

 

System.out.println( date );

System.out.println( dateFromClock );

 

// Get the local date and local time

final LocalTime time = LocalTime.now();

final LocalTime timeFromClock = LocalTime.now( clock );

 

System.out.println( time );

System.out.println( timeFromClock );

上述例子的輸出結果如下:

2014-04-12

2014-04-12

11:25:54.568

15:25:54.568

Base64支持

public class Base64s {

    public static void main(String[] args) {

        final String text = "Base64 finally in Java 8!";

 

        final String encoded = Base64

            .getEncoder()

            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );

        System.out.println( encoded );

 

        final String decoded = new String(

            Base64.getDecoder().decode( encoded ),

            StandardCharsets.UTF_8 );

        System.out.println( decoded );

    }

}

並行數組

Java8版本新增了很多新的方法,用於支持並行數組處理。最重要的方法是parallelSort(),可以顯著加快多核機器上的數組排序。下面的例子論證了parallexXxx系列的方法:

Arrays.parallelSetAll(longs,index-> ThreadLocalRandom.current().nextInt(1000000));
Arrays.stream(longs).limit(10).forEach(i-> System.out.print(i+" "));
System.out.println();

Arrays.parallelSort(longs);
Arrays.stream(longs).limit(10).forEach(i-> System.out.print(i+" "));
System.out.println();

並發性

基於新增的lambda表達式和steam特性,為Java 8中為java.util.concurrent.ConcurrentHashMap類添加了新的方法來支持聚焦操作;另外,也為java.util.concurrentForkJoinPool類添加了新的方法來支持通用線程池操作(更多內容可以參考我們的並發編程課程)。

Java 8還添加了新的java.util.concurrent.locks.StampedLock類,用於支持基於容量的鎖——該鎖有三個模型用於支持讀寫操作(可以把這個鎖當做是java.util.concurrent.locks.ReadWriteLock的替代者)。

java.util.concurrent.atomic包中也新增了不少工具類,列舉如下:

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

匿名內部類

以前:

bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
    @Override
    public ChannelPipeline getPipeline() throws Exception {
        return Channels.pipeline(new HelloClientHandler());
    }
});

現在直接()->跟返回結果

bootstrap.setPipelineFactory(() -> Channels.pipeline(new HelloClientHandler()));

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM