天天看點

java 8新特性總結<接口的預設方法>

<接口的預設方法>

解決的問題:在java8 之前的版本,在修改已有的接口的時候,需要修改實作該接口的實作類。

作用:解決接口的修改與現有的實作不相容的問題。在不影響原有實作類的結構下修改新的功能方法。

java 8抽象類和接口的差別

相同點:

Ø  都是抽象類型

Ø  都可以有實作方法(在java8之前是不可以的)

Ø  都可以不需要實作類或者繼承者去實作所有方法,(以前不行,現在接口中預設方法不需要實作者實作)

不同點:

Ø  抽象類不可以多重繼承,接口可以(無論是多重類型繼承還是多重行為繼承);

Ø  抽象類和接口所反映出的設計理念不同。其實抽象類表示的是"is-a"關系,接口表示的是"like-a"關系;

Ø  接口中定義的變量預設是publicstatic final 型,且必須給其初值,是以實作類中不能改變其值;抽象類中的變量預設是 friendly 型,其值可以在子類中重新定義,也可以重新指派。

<Lambda表達式>

解決問題:為 Java 添加了缺失的函數式程式設計特點,

作用:使程式更簡潔便于可讀可維護。

文法:

(arg1, arg2...) -> { body }      
(type1 arg1, type2 arg2...) -> { body }      
例子:      
(int a, int b) -> {  return a + b; }      
() -> System.out.println("Hello World");      
(String s) -> { System.out.println(s); }      
() -> 42      
() -> { return 3.1415 };      

l  lambda表達式的結構:

Ø  一個 Lambda 表達式可以有零個或多個參數

Ø  參數的類型既可以明确聲明,也可以根據上下文來推斷。

Ø  例如:(int a)與(a)效果相同

所有參數需包含在圓括号内,參數之間用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)

Ø  空圓括号代表參數集為空。例如:() -> 42

Ø  當隻有一個參數,且其類型可推導時,圓括号()可省略。例如:a -> return a*a

Ø  Lambda 表達式的主體可包含零條或多條語句

Ø  如果 Lambda 表達式的主體隻有一條語句,花括号{}可省略。匿名函數的傳回類型與該主體表達式一緻

Ø  7.如果 Lambda 表達式的主體包含一條以上語句,則表達式必須包含在花括号{}中(形成代碼塊)。匿名函數的傳回類型與代碼塊的傳回類型一緻,若沒有傳回則為空

<Lambda範圍>

l  通路本地變量

我們可以通路在lambda表示式之外的本地final變量:

    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在編譯期是被隐式的轉換為fianl類型的。下面的代碼是不能被成功的編譯的:

    int num = 1;        
    Converter<Integer, String> stringConverter =        
            (from) -> String.valueOf(from + num);  //這裡的num會編譯報錯      
    num = 3;        

在lambda表達式内部向變量num寫入值同樣是不允許的。

l  通路對象字段和靜态變量

class Lambda4 {        
        static int outerStaticNum;        
        int outerNum;        
        void testScopes() {        
            Converter<Integer, String> stringConverter1 = (from) -> {        
                outerNum = 23;        
                return String.valueOf(from);        
            };        
            Converter<Integer, String> stringConverter2 = (from) -> {        
                outerStaticNum = 72;        
                return String.valueOf(from);        
            };        
        }        
    }        

l  通路接口預設方法

Ø  接口預設方法不能被Lambda表達式的内部代碼所通路

<功能性接口>

Ø  JDK1.8包括了許多功能性接口。它們中的一些是老版本中被熟知的接口,例如Comparator和Runnable。這些已存在的接口已經通過@FunctionalInterface注解擴充為支援Lambda表達式。

Ø  在 Java 中,Marker(标記)類型的接口是一種沒有方法或屬性聲明的接口,簡單地說,marker 接口是空接口。相似地,函數式接口是隻包含一個抽象方法聲明的接口

Ø  每個 Lambda 表達式都能隐式地指派給函數式接口,當不指明函數式接口時,編譯器會自動解釋這種轉化

Ø  函數式接口例子:

Consumer<Integer>  c = (int x) -> { System.out.println(x) };      
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);      
Predicate<String> p = (String s) -> { s == null };      

l  相關接口:

1.        斷言接口(Predicates) 是隻擁有一個參數的Boolean型功能的接口。這個接口擁有多個預設方法用于構成predicates複雜的邏輯術語

Predicate<String> predicate = (s) -> s.length() > 0;        
    predicate.test("foo");              // true        
    predicate.negate().test("foo");     // false        
    Predicate<Boolean> nonNull = Objects::nonNull;        
    Predicate<Boolean> isNull = Objects::isNull;        
    Predicate<String> isEmpty = String::isEmpty;        
    Predicate<String> isNotEmpty = isEmpty.negate();        

2.        功能接口(Functions)Functions接受一個參數并産生一個結果,預設方法能夠用于将多個函數連結在一起。

Function<String, Integer> toInteger = Integer::valueOf;        
Function<String, Integer> backToString = toInteger.addThen(String::valueOf);      

backToString.apply(“5120”);

3.        供應接口(Supplines) 對于給定的泛型類型産生一個執行個體。不同于Functions,Suppliers不需要任何參數。

    Supplier<Person> personSupplier = Person::new;        
    personSupplier.get();   // new Person       

4.        消費接口(Consumers)代表在隻有一個輸入參數時操作被如何執行

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);        
    greeter.accept(new Person("Luke", "Skywalker"));        

5.        比較接口(Comparators)

   Comparator<Person> comparator=       
(p1, p2) -> p1.firstName.compareTo(p2.firstName);        
    Person p1 = new Person("John", "Doe");        
    Person p2 = new Person("Alice", "Wonderland");        
    comparator.compare(p1, p2);             // > 0        
   comparator.reversed().compare(p1, p2);  // < 0        

6.選項接口(Optionals)一種特殊的工具用來解決NullPointerException

    Optional<String> optional = Optional.of("bam");        
     optional.isPresent();           // true        
     optional.get();                 // "bam"        
     optional.orElse("fallback");    // "bam"        
optional.ifPresent((s)->System.out.println(s.charAt(0))); // "b"       

7. 流接口(Streams)

java.util.Stream代表着一串你可以在其上進行多種操作的元素。流操作既可以是連續的也可以是中斷的。中斷操作傳回操作結果。而連續操作傳回流本身,這樣你就可以在該行上繼續操作。流是建立在資料源上的,例如:java.util.Collection、list集合和set集合。流操作既可以順序執行也可以并行執行

首先建立一個字元串的數組

List<String> stringCollection = new ArrayList<>();        
    stringCollection.add("ddd2");        
    stringCollection.add("aaa2");        
    stringCollection.add("bbb1");        
    stringCollection.add("aaa1");        
    stringCollection.add("bbb3");        
    stringCollection.add("ccc");        
    stringCollection.add("bbb2");        
    stringCollection.add("ddd1");        

Java8的Collections類已經被擴充了,你可以簡單的調用Collection.stream()或者Collection.parallelSteam()來建立流。下面部分将介紹大部分流操作。

Filter

Filter接受一個predicate來過濾流中的所有元素。這個操作是連續的,它可以讓我們在結果上繼續調用另外一個流操作forEach。ForEach接受一個consumer,它被用來對過濾流中的每個元素執行操作。ForEach是一個中斷操作,是以我們不能在ForEach後調用其他流操作。

    stringCollection        
        .stream()        
        .filter((s) -> s.startsWith("a"))        
        .forEach(System.out::println);        
    // "aaa2", "aaa1"        

Sorted

Sorted是一個連續操作,它傳回流的已排序版本。如果你沒有顯示的指定Comparator,那麼流中元素的排序規則為預設的。

    stringCollection        
        .stream()        
        .sorted()        
        .filter((s) -> s.startsWith("a"))        
        .forEach(System.out::println);        
    // "aaa1", "aaa2"        

需要注意的是sorted隻建立了流的排序結果,它并沒有改變集合中元素的排序位置。stringCollection中元素排序是沒有改變的。

    System.out.println(stringCollection);        
    // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1        

Map

連續性操作map通過指定的Function将流中的每個元素轉變為另外的對象。下面的示例将每個字元串轉換為大寫的字元串。此外,你也可以使用map将每個元素的類型改變為其它類型。轉換後流的泛型類型依賴于你傳入的Function的泛型類型。

    stringCollection        
        .stream()        
        .map(String::toUpperCase)        
        .sorted((a, b) -> b.compareTo(a))        
        .forEach(System.out::println);        
    // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"        

Match

各種比對操作可以用來檢測是否某種predicate和流中元素相比對。所有的這些操作是中斷的并傳回一個boolean結果。

    boolean anyStartsWithA =         
        stringCollection        
            .stream()        
            .anyMatch((s) -> s.startsWith("a"));        
    System.out.println(anyStartsWithA);      // true        
    boolean allStartsWithA =         
        stringCollection        
            .stream()        
            .allMatch((s) -> s.startsWith("a"));        
    System.out.println(allStartsWithA);      // false        
    boolean noneStartsWithZ =         
        stringCollection        
            .stream()        
            .noneMatch((s) -> s.startsWith("z"));        
    System.out.println(noneStartsWithZ);      // true        

Count

Count是中斷型操作,它傳回流中的元素數量。

    long startsWithB =         
        stringCollection        
            .stream()        
            .filter((s) -> s.startsWith("b"))        
            .count();        
    System.out.println(startsWithB);    // 3        

Reduce

這個中斷性操作使用指定的function對流中元素實施消減政策。此操作的傳回值是一個包括所有被消減元素的Optional。

    Optional<String> reduced =        
        stringCollection        
            .stream()        
            .sorted()        
            .reduce((s1, s2) -> s1 + "#" + s2);        
    reduced.ifPresent(System.out::println);        
    // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"        

Parallel Streams

在前面部分我們提到流可以是順序的也可以是并行的。順序流的操作是在單線程上執行的,而并行流的操作是在多線程上并發執行的。

随後的例子我們展示了并行流可以多麼容易的提高性能。

首先,我們建立一個包含唯一進制素的大容器:

    int max = 1000000;        
    List<String> values = new ArrayList<>(max);        
    for (int i = 0; i < max; i++) {        
        UUID uuid = UUID.randomUUID();        
        values.add(uuid.toString());        
    }        

現在我們開始測試排序這些元素需要多長時間。

Sequential Sort

    long t0 = System.nanoTime();        
    long count = values.stream().sorted().count();        
    System.out.println(count);        
    long t1 = System.nanoTime();        
    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);        
    System.out.println(String.format("sequential sort took: %d ms", millis));        
    // sequential sort took: 899 ms        

Parallel Sort

    long t0 = System.nanoTime();        
    long count = values.parallelStream().sorted().count();        
    System.out.println(count);        
    long t1 = System.nanoTime();        
    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);        
    System.out.println(String.format("parallel sort took: %d ms", millis));        
    // parallel sort took: 472 ms        

你會觀察到這兩種模式的代碼基本上是一緻的,但是并行排序所花費的時間大約是順序排序的一半。

Map

我們已經提到maps不支援流。然而現在maps包括了許多新的非常有用的方法用于執行通用任務。

    Map<Integer, String> map = new HashMap<>();        
    for (int i = 0; i < 10; i++) {        
        map.putIfAbsent(i, "val" + i);        
    }        
    map.forEach((id, val) -> System.out.println(val));        

上述的代碼應該很清晰了:putIfAbsent使得我們不用寫是否為null值的檢測語句;forEach使用consumer來對map中的每個元素進行操作。下面的例子向我們展示使用功能性函數在map裡執行代碼:

    map.computeIfPresent(3, (num, val) -> val + num);        
    map.get(3);             // val33        
    map.computeIfPresent(9, (num, val) -> null);        
    map.containsKey(9);     // false        
    map.computeIfAbsent(23, num -> "val" + num);        
    map.containsKey(23);    // true        
    map.computeIfAbsent(3, num -> "bam");        
    map.get(3);             // val33        

接下來,我們将學習如何删除給定鍵所對應的元素。删除操作還需要滿足給定的值需要和map中的值想等:

    map.remove(3, "val3");        
    map.get(3);             // val33        
    map.remove(3, "val33");        
    map.get(3);             // null        

其他一些幫助性方法:

    map.getOrDefault(42, "not found");  // not found        

合并map中的實體是十分容易的:

    map.merge(9, "val9", (value, newValue) -> value.concat(newValue));        
    map.get(9);             // val9        
    map.merge(9, "concat", (value, newValue) -> value.concat(newValue));        
    map.get(9);             // val9concat        

如果map不存在指定的鍵,那麼它将把該鍵值對key/value加入map中。反而,如果存在,它将調用function來進行合并操作。

l  Date API 新的date和time 的api,與joda-Time庫是相容的。

Ø  Clock

提供了通路目前日期和時間的方法。Clock是時區敏感的并且它可以被用來替代System.currentTimeMillis進行擷取目前毫秒數。同時,時間軸上的時間點是可以用類Instant來表示的。Instants可以被用來建立遺留的java.util.Date對象。

    Clock clock = Clock.systemDefaultZone();        
    long millis = clock.millis();        
    Instant instant = clock.instant();        
    Date legacyDate = Date.from(instant);   // legacy java.util.Date        

Ø  TimeZones

被用來表示ZoneId。它們可以通過靜态工廠方法通路。TImeZones定義了時差,它在instants和本地日期時間轉換上十分重要。

        System.out.println(ZoneId.getAvailableZoneIds());        
    // prints all available timezone ids        
    ZoneId zone1 = ZoneId.of("Europe/Berlin");        
    ZoneId zone2 = ZoneId.of("Brazil/East");        
    System.out.println(zone1.getRules());        
    System.out.println(zone2.getRules());        
    // ZoneRules[currentStandardOffset=+01:00]        
    // ZoneRules[currentStandardOffset=-03:00]        

Ø  LocalTime

本地時間代表了一個和時區無關的時間,e.g. 10pm or 17:30:15.

LocalTime now1 = LocalTime.now(zone1);        
    LocalTime now2 = LocalTime.now(zone2);        
    System.out.println(now1.isBefore(now2));  // false        
    long hoursBetween = ChronoUnit.HOURS.between(now1, now2);        
    long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);        
    System.out.println(hoursBetween);       // -3        
    System.out.println(minutesBetween);     // -239        

LocalTime包括很多個工廠方法用來簡化建立過程,如下面的例子:

LocalTime late = LocalTime.of(23, 59, 59);        
    System.out.println(late);       // 23:59:59        
    DateTimeFormatter germanFormatter =        
        DateTimeFormatter        
            .ofLocalizedTime(FormatStyle.SHORT)        
            .withLocale(Locale.GERMAN);        
    LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);        
    System.out.println(leetTime);   // 13:37        

Ø  LocalDate

 LocalDate代表了一個可區分日期,e.g.2014-03-11。它是不變的同時工作原理類似于LocalTime。下面的例子描繪了通過加減年,月,日來計算出一個新的日期。需要注意的是這每個操作都傳回一個新的執行個體。

LocalDate today = LocalDate.now();        
    LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);        
    LocalDate yesterday = tomorrow.minusDays(2);        
    LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);        
    DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();        
    System.out.println(dayOfWeek);    // FRIDAY        

     解析字元串的時間格式:

DateTimeFormatter germanFormatter =        
        DateTimeFormatter        
            .ofLocalizedDate(FormatStyle.MEDIUM)        
            .withLocale(Locale.GERMAN);        
    LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);        
    System.out.println(xmas);   // 2014-12-24        

Ø  LocalDateTime

LocalDateTime代表日期和時間。它将我們前部分看到的時間和日期組合進一個執行個體。LocalDateTime是不可變的并且它的工作原理和LocalTime和LocalDate十分相似。

使用示例:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);        
    DayOfWeek dayOfWeek = sylvester.getDayOfWeek();        
    System.out.println(dayOfWeek);      // WEDNESDAY        
    Month month = sylvester.getMonth();        
    System.out.println(month);          // DECEMBER        
    long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);        
    System.out.println(minuteOfDay);    // 1439        

在一些額外的時區資訊幫助下,它可以被轉換為instant。Instants可以被容易的轉換為遺留的java.util.Date類型。

    Instant instant = sylvester        
            .atZone(ZoneId.systemDefault())        
            .toInstant();        
    Date legacyDate = Date.from(instant);        
    System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014        

格式date-time的過程和格式date和time基本上是一樣的。在使用系統自帶的定義格式時,我們也可以定義我們自己的格式:

    DateTimeFormatter formatter =        
        DateTimeFormatter        
            .ofPattern("MMM dd, yyyy - HH:mm");        
    LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);        
    String string = formatter.format(parsed);        
    System.out.println(string);     // Nov 03, 2014 - 07:13        

<方法和構造函數引用>

解決問題:簡化靜态方法的引用

使用方式:通過關鍵字::來傳遞方法和構造函數的引用

例子:

靜态方法:

    @FunctionalInterface        
    interface Converter<F, T> {        
        T convert(F from);        
    }        
  Converter<String, Integer> converter = Integer::valueOf;        
    Integer converted = converter.convert("123");        
    System.out.println(converted);   // 123        

對象方法:

 class Something {        
        String startsWith(String s) {        
            return String.valueOf(s.charAt(0));        
        }        
    }        
    Something something = new Something();        
    Converter<String, String> converter = something::startsWith;        
    String converted = converter.convert("Java");        
    System.out.println(converted);    // "J"        

<Annotations>

支援多重注解,在注解聲明時使用@Repeatable。

例子:

我們首先定義一個包裝注解

  @interface Hints {        
        Hint[] value();        
    }        
    @Repeatable(Hints.class)        
    @interface Hint {        
        String value();        
    }        

使用1:容器注解

@Hints({@Hint("hint1"), @Hint("hint2")})        
class User {}       

使用2:重複注解

@Hint("hint1")      
@Hint("hint2")       
class User {}       

特别注意:

在原有注解使用範圍基礎之上,如下:

public enum ElementType {

   TYPE,

   FIELD,

   METHOD,

   PARAMETER,

   CONSTRUCTOR,

   LOCAL_VARIABLE,

   ANNOTATION_TYPE,

    PACKAGE

Java8注解的使用範圍新增兩種類型:

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

其他特性:Arrays.parallelSort,StampedLock,CompletableFuture