1. 程式人生 > >java 8 部分新特性和函數語言程式設計

java 8 部分新特性和函數語言程式設計

1.介面可以新增非抽象的方法和靜態方法,使用關鍵字 default 即可
程式碼如下:

public interface Defaulable {

    void printName();

    default void printAge(){
        System.out.println(19);
    }

     static void printSex(){
        System.out.println("女");
    }


}

實現如下:

public class DefaulableImpl implements Defaulable
{
@Override public void printName() { System.out.println("abel"); } public static void main(String [] args){ Defaulable d=new DefaulableImpl(); d.printName(); d.printAge(); Defaulable.printSex(); } }

Java1.8中介面和抽象類存在的不同?

當兩個介面都定義兩個 完全相同的 預設方法
default void printAge(){
//do Something
},
實現類同時實現這兩個介面,會出現衝突。 實現類需要重新覆寫該預設方法() 。

  • 抽象類允許非靜態和非final的域,允許方法成為public,static和final
  • 所有的介面方法本質為public的。

JDK 8 爭議性的把抽象類的優點帶給介面。造成的影響是今天有大量的抽象類通過預設方法可能被替換為介面。

2. :: 方法引用

方法引用提供了非常有用的語法,可以直接引用已有Java類或物件(例項)的方法或構造器。與lambda聯合使用,方法引用可以使語言的構造更緊湊簡潔,減少冗餘程式碼。

::是java 8裡引入lambda後的一種用法,表示引用,

比如靜態方法的引用String::valueOf;
比如構造器的引用,ArrayList::new
比如輸出方法的引用 list.forEach(System.out::println); 也可以引用類的屬性。

下面,我們以定義了4個方法的Car這個類作為例子,區分Java中支援的4種不同的方法引用。

public static 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() );
    }
}

第一種方法引用是構造器引用,它的語法是Class::new,或者更一般的Class< T >::new。請注意構造器沒有引數。

final Car car = Car.create( Car::new ); //引用 Car 的 new 構造方法
final List< Car > cars = Arrays.asList( car );

第二種方法引用是靜態方法引用,它的語法是Class::static_method。請注意這個方法接受一個Car型別的引數。

cars.forEach( Car::collide );

第三種方法引用是特定類的任意物件的方法引用,它的語法是Class::method。請注意,這個方法沒有引數。

cars.forEach( Car::repair );

最後,第四種方法引用是特定物件的方法引用,它的語法是instance::method。請注意,這個方法接受一個Car型別的引數

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

執行上面的Java程式在控制檯上會有下面的輸出(Car的例項可能不一樣):

Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

3.Clock

Clock類提供了訪問當前日期和時間的方法,Clock是時區敏感的,可以用來取代 System.currentTimeMillis() 來獲取當前的微秒數。某一個特定的時間點也可以使用Instant類來表示,Instant類也可以用來建立老的java.util.Date物件。

 ZoneId zone1 = ZoneId.of("Europe/Berlin");

         System.out.println(ZoneId.getAvailableZoneIds());
         Clock clock=Clock.system(zone1);
//       Clock clock=Clock.systemDefaultZone();//獲取預設時區
         Instant instant=clock.instant();
         long millis = clock.millis();
         System.out.println(millis);//可替換System.currentTimeMillis()
         System.out.println(clock+"-----sd"+instant);

如果你需要特定時區的日期/時間,那麼ZonedDateTime是你的選擇。它持有ISO-8601格式具具有時區資訊的日期與時間。下面是一些不同時區的例子:

 ZoneId zone1 = ZoneId.of("Europe/Berlin");
         Clock clock=Clock.system(zone1);

         final ZonedDateTime zonedDatetime = ZonedDateTime.now();
         final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
         final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

         System.out.println( zonedDatetime );
         System.out.println( zonedDatetimeFromClock );
         System.out.println( zonedDatetimeFromZone );

輸出結果

2016-09-11T16:45:08.711+08:00[Asia/Shanghai]
2016-09-11T10:45:08.711+02:00[Europe/Berlin]
2016-09-11T01:45:08.724-07:00[America/Los_Angeles]

4. Base64

在Java 8中,Base64編碼成為了Java類庫的標準。Base64類同時還提供了對URL、MIME友好的編碼器與解碼器。

除了這十大新特性之外,還有另外的一些新特性:

更好的型別推測機制:Java 8在型別推測方面有了很大的提高,這就使程式碼更整潔,不需要太多的強制型別轉換了。

編譯器優化:Java 8將方法的引數名加入了位元組碼中,這樣在執行時通過反射就能獲取到引數名,只需要在編譯時使用-parameters引數。

並行(parallel)陣列:支援對陣列進行並行處理,主要是parallelSort()方法,它可以在多核機器上極大提高陣列排序的速度。

併發(Concurrency):在新增Stream機制與Lambda的基礎之上,加入了一些新方法來支援聚集操作。

Nashorn引擎jjs:基於Nashorn引擎的命令列工具。它接受一些JavaScript原始碼為引數,並且執行這些原始碼。

類依賴分析器jdeps:可以顯示Java類的包級別或類級別的依賴。

JVM的PermGen空間被移除:取代它的是Metaspace(JEP 122)。

5.Optional

Optional實際上是個容器:它可以儲存型別T的值,或者僅僅儲存null。Optional提供很多有用的方法,這樣我們可以用Optional進行空值檢測。

        Person person = new Person();
//        person = null;
        Boolean flag = Optional.ofNullable(person).isPresent();
        System.out.println("Full Name is set? " + flag);

如果Optional類的例項為非空值的話,isPresent()返回true,否從返回false。

5.Lambda表示式(函數語言程式設計)

函數語言程式設計可以說是 宣告式程式設計。(文章末尾有解釋。)

  • 指令式程式設計:命令“機器”如何去做事情(how),這樣不管你想要的是什麼(what),它都會按照你的命令實現。
  • 宣告式程式設計:告訴“機器”你想要的是什麼(what),讓機器想出如何去做(how)。

lambda表示式允許你通過表示式來代替功能介面。 lambda表示式就和方法一樣,它提供了一個正常的引數列表和一個使用這些引數的主體(body,可以是一個表示式或一個程式碼塊)。
Lambda表示式還增強了集合庫。 Java SE 8添加了2個對集合資料進行批量操作的包: java.util.function 包以及 java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了許多額外的功能。 總的來說,lambda表示式和 stream 是自Java語言新增泛型(Generics)和註解(annotation)以來最大的變化。

Lambda 表示式簡單語法:

// 1. 不需要引數,返回值為 5  
() -> 5  

// 2. 接收一個引數(數字型別),返回其2倍的值  
x -> 2 * x  

// 3. 接受2個引數(數字),並返回他們的差值  
(x, y) -> x – y  

// 4. 接收2個int型整數,返回他們的和  
(int x, int y) -> x + y  

// 5. 接受一個 string 物件,並在控制檯列印,不返回任何值(看起來像是返回void)  
(String s) -> System.out.print(s)  

Lambda 的for 迴圈

    //初始化資料
    List<String> str = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            str.add(i + "");
        }

        //通過表示式輸出,如果知道是String 型就寫String 否則就寫範型,java會自動識別。
         str.forEach((String) -> System.out.println(String+";"));

 str.forEach((String) -> {
         System.out.println(String+";");
         System.out.println(String+"附加的操作");
         });
        str.clear();

這樣寫就讓程式碼變得簡單了。
匿名類可以使用lambda表示式來代替。 同樣,在實現Runnable介面時也可以這樣使用:

// 1.1使用匿名內部類  
new Thread(new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
}).start();  

// 1.2使用 lambda expression  
new Thread(() -> System.out.println("Hello world !")).start();  

// 2.1使用匿名內部類  
Runnable race1 = new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
};  

// 2.2使用 lambda expression  
Runnable race2 = () -> System.out.println("Hello world !");  

// 直接呼叫 run 方法(沒開新執行緒哦!)  
race1.run();  
race2.run();

使用Lambdas排序集合

//方式一,通過首字母排序
List<String> str2 = Arrays.asList("abel", "don", "bruce", "sean");
    str2.sort((e1, e2) -> e1.compareTo(e2));
 // str2.forEach((String)->System.out.println(String+";"));
    str2.forEach((e) -> System.out.println(e));

//通過長度排序
List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");
        //長度從小到大排序
        str3.sort((e1, e2) -> e1.length()-e2.length()); 
        str3.forEach((e) -> System.out.println(e));

使用這種方式也可以實現排序

List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");  
        Comparator<String> sortByName=(String e1, String e2) -> e1.length()-e2.length();
        str3.sort(sortByName); //長度從小到大排序
        str3.forEach((e) -> System.out.println(e));

使用Lambdas和Streams

使用fileter 過濾顯示

List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");
        //輸出長度大於3的
        str3.stream().filter((String e1) -> (e1.length() > 3)).forEach((e) -> System.out.println(e));

結果如下
abel
bruce
sean

多個過濾器組合使用,也可以這樣單獨的定義過濾器

List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");
        //定義過濾器
        Predicate<String> lengthFilter = (String e1) -> (e1.length() > 3);
        Predicate<String> conFilter = (String e1) -> (e1.contains("a"));
        str3.stream().filter(lengthFilter).filter(conFilter).forEach((e) -> System.out.println(e));

還有limit 限定,最大最小值方法。

List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");
//按首字母排序,輸出前三個
    str3.stream().sorted((e1, e2) -> e1.compareTo(e2)).limit(3).forEach((e) -> System.out.println(e));
        System.out.println("------------------------------------------------------------");
        //輸出長度最長的
        String max=str3.stream().max(( e1,e2) -> (e1.length() - e2.length())).get();
        //輸出長度最短的
        String min=str3.stream().min(( e1,e2) -> (e1.length() - e2.length())).get();
System.out.println(max+"---"+min);  

stream 的平行計算和使用summaryStatistics方法獲得stream 中元素的各種彙總資料

List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");
//平行計算
        int sum=str3.stream().parallel().mapToInt(p->p.length()).sum();
        System.out.println(sum);
        //使用summaryStatistics方法獲得stream 中元素的各種彙總資料
        IntSummaryStatistics sumstatis=str3.stream().mapToInt(p->p.length()).summaryStatistics();
        System.out.println("求長度平均數"+sumstatis.getAverage()+"\n長度最小的"+sumstatis.getMin()+"\n長度最大的"+sumstatis.getMax()+"\n求總和"+sumstatis.getSum());

除此之外 Lambdas結合 map 方法,還可以使用 collect 方法來將我們的結果集放到一個字串,一個 Set 或一個TreeSet中這裡就不做解釋了

引用類的成員變數與區域性變數

Lambda可以引用類的成員變數與區域性變數(如果這些變數不是final的話,它們會被隱含的轉為final,這樣效率更高)。例如,下面兩個程式碼片段是等價的:

String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

和:

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

5. 我用Lambdas 舉的一些例子

測試類。

package com.us;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Created by yangyibo on 16/12/26.
 */
public class LambdaTest {


    public static void main(String[] args) {
//        forEachT();
//        threadT();
//        sortT();
//        streamsFilterT();
//        streamsCountT();
//        personTComparator();
//        boysAndGirls();
        stringToIntT();

    }




    //Lambda 的for 迴圈
    private static void forEachT() {
        List<String> str = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            str.add(i + "");
        }
        str.forEach((String) -> System.out.println(String + ";"));

        str.forEach((String) -> {
            System.out.print(String + ";");
            System.out.println(String + "附加的操作");
        });
        str.clear();
    }





    //匿名類 執行緒操作
    private static void threadT() {

        System.out.println("current--" + Thread.currentThread().getId());

        //使用 lambda expression  開執行緒
        new Thread(() -> System.out.println("Hello world Thread !---" + Thread.currentThread().getId())).start();

        //使用匿名內部類   沒有開執行緒
        Runnable race2 = () -> System.out.println("Hello world  Runnable !--" + Thread.currentThread().getId());
        race2.run();
    }





    //Comparator 排序
    private static void sortT() {
        //方式一,通過首字母排序
        List<String> str2 = Arrays.asList("abel", "don", "bruce", "sean");
        str2.sort((e1, e2) -> e1.compareTo(e2));
        str2.forEach((e) -> System.out.println(e));


        System.out.println("--------------分割線--------------------");
        //通過長度排序
        List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");
        //長度從小到大排序
        str3.sort((e1, e2) -> e1.length() - e2.length());
        str3.forEach((e) -> System.out.println(e));


        System.out.println("--------------分割線--------------------");
        //通過長度排序
        List<String> str4 = Arrays.asList("abel", "don", "bruce", "sean");
        Comparator<String> sortByName = (String e1, String e2) -> e1.length() - e2.length();
        str4.sort(sortByName); //長度從小到大排序
        str4.forEach((e) -> System.out.println(e));
    }





    //過濾
    private static void streamsFilterT() {
        List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");
        //輸出長度大於3的
        str3.stream().filter((String e1) -> (e1.length() > 3)).forEach((e) -> System.out.println(e));

        System.out.println("--------------分割線------------輸出包涵 a 字母--------");

        //輸出包涵 a 字母
        List<String> str2 = (List<String>) str3.stream().filter(x -> x.contains("a"));
        str2.forEach(e -> System.out.println(e));

        System.out.println("--------------分割線------------按首字母排序,輸出前三個--------");
        //按首字母排序,輸出前三個
        str3.stream().sorted((e1, e2) -> e1.compareTo(e2)).limit(3).forEach((e) -> System.out.println(e));

        System.out.println("--------------分割線--------------------");

        //輸出長度最長的
        String max = str3.stream().max((e1, e2) -> (e1.length() - e2.length())).get();
        System.out.println("輸出長度最長的: " + max);

        System.out.println("--------------分割線--------------------");

        //輸出長度最短的
        String min = str3.stream().min((e1, e2) -> (e1.length() - e2.length())).get();
        System.out.println("輸出長度最短的: " + min);
    }






    // reduce 聚合操作   使用summaryStatistics方法獲得stream 中元素的各種彙總資料
    private static void streamsCountT() {
        List<String> str3 = Arrays.asList("abel", "don", "bruce", "sean");

        //平行計算
        int sum = str3.stream().parallel().mapToInt(p -> p.length()).sum();
        System.out.println(sum);

        System.out.println("--------------分割線-----------reduce---------");

        //reduce 求和
        int sum2 = str3.stream().parallel().mapToInt(p -> p.length()).reduce(0, (x, y) -> (x + y));
        System.out.println("reduce 求和" + sum2);


        System.out.println("--------------分割線--------------------");

        //使用summaryStatistics方法獲得stream 中元素的各種彙總資料
        IntSummaryStatistics sumstatis = str3.stream().mapToInt(p -> p.length()).summaryStatistics();
        System.out.println("求長度平均數" + sumstatis.getAverage()
                + "\n長度最小的" + sumstatis.getMin()
                + "\n長度最大的" + sumstatis.getMax()
                + "\n求總和" + sumstatis.getSum());
    }






    //使用 Comparator 排序
    private static void personTComparator() {
        //將person 物件按照名字首字母順序排列
        List<Person> persons = new ArrayList<>();

        for (int i = 0; i < 4; i++) {
            Person person = new Person();
            person.setName("abel-" + i);
            person.setAge(20 + i);
            persons.add(person);
        }
        Comparator<Person> byName = Comparator.comparing(Person::getName);
        persons.sort(byName);
        persons.forEach(p -> System.out.println(p.getName()));
    }







    //  使用 Collectors.groupingBy 轉換為map   使用Predicate 進行過濾操作。
    public static void boysAndGirls() {
        List<Person> persons = new ArrayList<>();

        for (int i = 1; i <= 40; i++) {
            Person person = new Person();
            person.setName("abel-" + i);
            person.setSex((int) (Math.random() * 2));
            person.setAge(25 + i);
            persons.add(person);
        }
        persons.forEach(p -> System.out.println(p.toString()));

        // 統計年齡在25-65歲,且名字中包涵 "-1"或者"-2" 的男女人數、比例   0:為女性,1:為男性
        Map<Integer, Integer> result = persons.parallelStream().filter(p -> p.getAge() <= 65 && p.getAge() >= 25
                && (p.getName().contains("-1") || p.getName().contains("-2"))).
                collect(
                        //把結果收集到一個Map中,用統計到的男女自身作為鍵,其出現次數作為值。
                        Collectors.groupingBy(p -> p.getSex(), Collectors.summingInt(p -> 1))
                );
        System.out.println("\n" + "boysAndGirls result is " + result);


        System.out.println("--------------分割線--------------------");


        // 統計年齡在25-65歲,且名字中以"a" 開頭的男女人數、比例   0:為女性,1:為男性
        Predicate<Person> startsWithA = (n) -> n.getName().startsWith("a");
        Predicate<Person> ageMax = (n) -> n.getAge() <= 65;
        Predicate<Person> ageMin = (n) -> n.getAge() >= 25;
        Map<Integer, Integer> result2 = persons.parallelStream().filter(startsWithA.and(ageMax).and(ageMin)).
                collect(
                        //把結果收集到一個Map中,用統計到的男女自身作為鍵,其出現次數作為值。
                        Collectors.groupingBy(p -> p.getSex(), Collectors.summingInt(p -> 1))
                );
        System.out.println("boysAndGirls Predicate  result2 is " + result2);

    }






    //使用map 轉換物件。使用Collectors 返回集合 使用Comparator排序
    private static void stringToIntT() {
        //將String 集合 轉換為 int 取得大於4,小於11的元素,並按從大到小排序,轉換為 integer 集合。
        List<String> list = Arrays.asList("1", "3","3", "4", "5", "5", "6", "7","7", "8", "9", "10", "10", "10", "11", "12", "13", "14", "15");
        Comparator<Integer> sort = (Integer e1, Integer e2) -> e2 - e1;
        List<Integer> listInt = list.stream()
                .distinct()
                .map(x -> new Integer(x))
                .filter(x -> x > 4 && x < 11)
                .sorted(sort)
                .collect(Collectors.toList());
        listInt.forEach(e -> System.out.print(e+" , "));

    }




}

pojo 類

package com.us;

/**
 * Created by yangyibo on 16/12/26.
 */
public class Person {

    int age;
    String name;
    int sex;


    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;

    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return  "name: "+this.getName()+"  --AGE: "+this.getAge() +"  --SEX: "+this.getSex();
    }
}

文章最後解釋一些函式式的一些特性,用於理解。

6. 函數語言程式設計的三大特性:

immutable data 不可變資料:

像Clojure一樣,預設上變數是不可變的,如果你要改變變數,你需要把變數copy出去修改。這樣一來,可以讓你的程式少很多Bug。因為,程式中的狀態不好維護,在併發的時候更不好維護。(你可以試想一下如果你的程式有個複雜的狀態,當以後別人改你程式碼的時候,是很容易出bug的,在並行中這樣的問題就更多了)

first class functions:

這個技術可以讓你的函式就像變數一樣來使用。也就是說,你的函式可以像變數一樣被建立,修改,並當成變數一樣傳遞,返回或是在函式中巢狀函式。這個有點像Javascript的Prototype

尾遞迴優化:

我們知道遞迴的害處,那就是如果遞迴很深的話,stack受不了,並會導致效能大幅度下降。所以,我們使用尾遞迴優化技術——每次遞迴時都會重用stack,這樣一來能夠提升效能,當然,這需要語言或編譯器的支援。Python就不支援。

函數語言程式設計的幾個技術

map & reduce :

這個技術不用多說了,函數語言程式設計最常見的技術就是對一個集合做Map和Reduce操作。這比起過程式的語言來說,在程式碼上要更容易閱讀。(傳統過程式的語言需要使用for/while迴圈,然後在各種變數中把資料倒過來倒過去的)這個很像C++中的STL中的foreach,find_if,count_if之流的函式的玩法。

pipeline:

這個技術的意思是,把函式例項成一個一個的action,然後,把一組action放到一個數組或是列表中,然後把資料傳給這個action list,資料就像一個pipeline一樣順序地被各個函式所操作,最終得到我們想要的結果。

recursing 遞迴 :

遞迴最大的好處就簡化程式碼,他可以把一個複雜的問題用很簡單的程式碼描述出來。注意:遞迴的精髓是描述問題,而這正是函數語言程式設計的精髓。

currying:

把一個函式的多個引數分解成多個函式, 然後把函式多層封裝起來,每層函式都返回一個函式去接收下一個引數這樣,可以簡化函式的多個引數。在C++中,這個很像STL中的bind_1st或是bind2nd。

higher order function 高階函式:

所謂高階函式就是函式當引數,把傳入的函式做一個封裝,然後返回這個封裝函式。現象上就是函式傳進傳出,就像面向物件物件滿天飛一樣。

函式式的一些好處

parallelization 並行:

所謂並行的意思就是在並行環境下,各個執行緒之間不需要同步或互斥。

lazy evaluation 惰性求值:

這個需要編譯器的支援。表示式不在它被繫結到變數之後就立即求值,而是在該值被取用的時候求值,也就是說,語句如x:=expression; (把一個表示式的結果賦值給一個變數)明顯的呼叫這個表示式被計算並把結果放置到 x 中,但是先不管實際在 x 中的是什麼,直到通過後面的表示式中到 x 的引用而有了對它的值的需求的時候,而後面表示式自身的求值也可以被延遲,最終為了生成讓外界看到的某個符號而計算這個快速增長的依賴樹。

determinism 確定性:

所謂確定性的意思就是像數學那樣 f(x) = y ,這個函式無論在什麼場景下,都會得到同樣的結果,這個我們稱之為函式的確定性。而不是像程式中的很多函式那樣,同一個引數,卻會在不同的場景下計算出不同的結果。所謂不同的場景的意思就是我們的函式會根據一些執行中的狀態資訊的不同而發生變化。