Java 13はすべて来て、あなたはまだJava 8の新しい(古い)特性を理解していませんか?

28931 ワード

Javaの現在のバージョンの反復速度はあまり速くないので、うっかりして、いくつかのバージョンを逃してしまいました.公式バージョンはJava 12に更新されていますが、現在のところ、多くのJavaシステムはJava 8上で実行されており、残りの一部の歴史的遺留システムはJava 7、さらにはJava 6上で走っています.私がJavaを習ったばかりの頃は、ちょうどJava 7のバージョンの末期で、その時すでにJava 8の新しい特性に関する噂がたくさんありましたが、当時は初心者としてはあまり注目されていませんでしたが、「lambda式」や「関数式プログラミング」などをぼんやり覚えていて、その真意もよく分かりませんでした.本当にJava 8を大量に応用したのは、私が仕事をしてから1年後のことかもしれませんが、IBMフォーラムの文章から始まったことを覚えています.
先日、ある大学の同級生と話していたとき、彼らの会社のいくつかの問題について話しました.彼らのシステムはJDK 7のバージョンに基づいており、ほとんどの従業員はバージョンをアップグレードしたくないので、Java 8の新しい特性を受け入れたくないからです.私はとても驚いて、Java 13に近づいています.Java 8の新しい(古い)特性を知りたくありませんか.そこでこの文章では,Java 8のlambdaとstreamに関する新しい(古い)特性を分かりやすいコードと結びつけて紹介し,関数式プログラミングの考え方を体得する.
Lambda式
lambda式は匿名の内部類のより簡潔な文法糖であると簡単に考えることができる.次の2つのスレッドの作成方法を見て、直感的に感じてみましょう.
//      
new Thread(new Runnable() {
    @Override
    public void run() {
        // ...
    }
}).start();

// lambda
new Thread(() -> {
    // ...
}).start();

Lambda式を上手に使うには、まず関数式インタフェースを理解する必要があります.では、関数式インタフェースとは何ですか.まず、interfaceによって修飾されたインタフェースでなければならない.次に、インタフェースには、実装されるべき方法が1つしかない.関数インタフェースと一般インタフェースを区別する簡単な方法があります.それは、インタフェースに@FunctionalInterface注釈を追加し、エラーを報告しなければ、関数インタフェースであり、匿名の内部クラスの代わりにlambda式を使用することができます.次の例を見ると、AとBは関数式インタフェースであり、Cには抽象的な方法がなく、Dはインタフェースではないので、lambda式は使用できないことが明らかになった.
//       
interface A {
    void test();
}

//       
interface B {
    default void def() {
        // balabala...
    }
    void test();
}

//        
interface C {
    default void def() {}
}

//        
abstract class D {
   public abstract void test();
}

Lambda式は、インタフェースを実装する方法パラメータ、戻り値、コード行数などによって、いくつかの異なる書き方があります.
  • パラメータなし
  • interface A {
        void test();
    }
    
    A a = () -> {
        // ...
    };
    
  • 単一パラメータの
  • interface B {
        void test(String arg);
    }
    
    B b = arg -> {
        // ...
    };
    
  • 複数のパラメータの
  • interface C {
        void test(String arg1, String arg2);
    }
    
    C c = (a1, a2) -> {
        // ...
    };
    
    interface D {
        void test(String arg1, String arg2, String arg3);
    }
    
    D d = (a1, a2, a3) -> {
        // ...
    };
    
  • コードが1行しかない場合は、括弧
  • を省略できます.
    interface B {
        void test(String arg);
    }
    
    B b = arg -> System.out.println("hello " + arg);
    
  • には、戻り値の
  • がある.
    interface E {
        String get(int arg);
    }
    
    E e = arg -> {
        int r = arg * arg;
        return String.valueOf(r);
    };
    //           return    
    e = arg -> String.valueOf(arg * arg);
    

    lambda式は匿名の内部クラスと同様にfinal修飾の外部のリソースしか参照できないことに注意してください.Java 8に表示されない宣言変数はfinalですが、lambda式の内部では修正できません.
    int i = 0;
    A a = () -> {
        i++; //        
        // ...
    };
    

    Lambda式にはもっと簡単な書き方がありますが、次のコードを見て、この::記号はよく知られていますか?やはりCシステムの影響から離れられないのでしょうか?
    class Math {
        int max(int x, int y) {
            return x < y ? y : x;
        }
        
        static int sum(int x, int y) {
            return x + y;
        }
    }
    
    interface Computer {
        int eval(int arg1, int arg2);
    }
    
    
    //         
    Computer sumFun = Math::sum;
    //        
    sumFun = (x, y) -> x + y;
    
    Math math = new Math();
    //       
    Computer maxFun = math::max;
    //        
    maxFun = (x, y) -> x < y ? y : x;
    
    int sum = sumFun.eval(1, 2);
    int max = maxFun.eval(2, 3);
    

    上の例を拡張して、下のコードを見て、関数式プログラミングの思想を体得します.関数をパラメータとして,computeメソッドを実際に呼び出したときに,どのような演算を行うべきかを決定した.
    class Biz {
        int x, y;
        Biz(int x, int y) {
            this.x = x;
            this.y = y;
        }
        int compute(Computer cpt) {
            // ...
            return cpt.eval(x, y);
        }
    }
    Biz biz = new Biz(1, 2);
    int result = biz.compute((x, y) -> x * y);
    result = biz.compute(Math::sum);
    

    内蔵関数インタフェース
    Java 8には多くの関数インタフェースが内蔵されており、すべてjava.util.functionパッケージの下に置かれています.これらのインタフェースは日常開発の大部分のニーズを満たすことができます.これらの関数インタフェースは主に以下のように分類されています.
  • 戻り値なし、パラメータ付きConsumerタイプ
  • Consumer consumer = str -> {
        // ...
    };
    BiConsumer biConsumer = (left, right) -> {
        // ...
    };
    
  • 戻り値がある、パラメータがないSupplierタイプ
  • Supplier supplier = () -> {
        // ...
        return "hello word";
    };
    
  • 戻り値がある、パラメータがあるFunction
  • Function function = i -> {
        // ...
        return "hello word " + i;
    };
    BiFunction biFunction = (m, n) -> {
        int s = m + n;
        return "sum = " + s;
    };
    
  • はboolean、パラメータのあるPredicateタイプを返し、Functionの特例
  • と見なすことができる.
    Predicate predicate = str -> {
        // ...
        return str.charAt(0) == 'a';
    };
    BiPredicate biPredicate = (left, right) -> {
        // ...
        return left.charAt(0) == right.charAt(0);
    };
    

    集合クラスのStream
    Java 8は、集合フレームワークにフロー処理機能を追加し、集合データを容易に処理する方法を提供しています.Streamは、中間動作と端末動作の2つの動作に大きく分けることができ、ここでは中間動作状態の問題は考慮されない.中間操作は複数あってもよいが、端末操作は1つしかない.中間操作は一般的に、中間操作を追加するとすぐに有効にならない対流要素の追加操作であり、終端操作が追加されると、ストリーム全体が開始されます.また、ストリームは多重化できず、ストリームが起動すると、このストリームに端末操作を付加することはできません.
    Streamの作成方法
    ストリームの作成方法は、次のとおりです.
    String[] array = 
    Stream stream;
    // 1.   Stream builder  
    stream = Stream.builder()
            .add("1")
            .add("2")
            .build();
    
    // 2.   Stream.of    ,            
    stream = Stream.of("1", "2", "3");
    
    // 3.   Collection  stream    ,       
    Collection list = Arrays.asList("1", "2", "3");
    stream = list.stream();
    
    // 4.   IntStream、LongStream、DoubleStream  
    IntStream intStream = IntStream.of(1, 2, 3);
    LongStream longStream = LongStream.range(0L, 10L);
    DoubleStream doubleStream = DoubleStream.of(1d, 2d, 3d);
    
    // 5.             StreamSupport    
    stream = StreamSupport.stream(list.spliterator(), false);
    

    中間操作
    sparkやflinkに詳しいと、中間操作は実はspark、flinkの演算子と同じで、ネーミングも同じで、ストリームが中間操作を呼び出す方法は、すぐにこの操作を実行することはなく、端末操作を呼び出すまで待ってから実行されることがわかります.次の例ではtoArrayの端末操作が追加されています.ストリームを配列に変換します.
  • filterオペレーション、パラメータはPredicateであり、このオペレーションはデータストリームの断言結果がfalseであるすべての要素
  • をフィルタリングする.
    //           1      
    // array = [2, 3]
    Integer[] array = Stream.of(1, 2, 3)
                            .filter(i -> i > 1)
                            .toArray(Integer[]::new);
    
  • map操作、パラメータはFunctionで、この操作はデータストリームの中の要素をすべて新しい要素に処理し、mapToInt、mapToLong、mapToDoubleとmapは
  • に似ている.
    //        10
    // array = [11, 12, 13]
    Integer[] array = Stream.of(1, 2, 3)
                            .map(i -> i + 10)
                            .toArray(Integer[]::new);
    
  • flatMapオペレーション.パラメータはFunctionですが、Functionの戻り値はStreamです.このオペレーションはmapと同様に各エレメントを処理します.mapは現在のストリームの1つのエレメントを別のエレメントに処理しますが、flatMapは現在のストリームの1つのエレメントを複数のエレメントに処理します.flatMapToInt、flatMapToDouble、flatMapToLong、flatMapと似ています.
  • //        ","  ,  Stream
    // array = ["1", "2", "3", "4", "5", "6"]
    String[] array = Stream.of("1", "2,3", "4,5,6")
                           .flatMap(s -> {
                               String[] split = s.split(",");
                               return Stream.of(split);
                           })
                           .toArray(String[]::new);
    
  • peek操作、パラメータはConsumerで、変更操作は各要素を処理しますが、新しいオブジェクトは戻りません.
  • Stream.of(new User("James", 40), new User("Kobe", 45), new User("Durante", 35))
          .peek(user -> {
              user.name += " NBA";
              user.age++;
          }).forEach(System.out::println);
    // User(name=James NBA, age=41)
    // User(name=Kobe NBA, age=46)
    // User(name=Durante NBA, age=36)
    
  • distinct操作は、各要素のequals方法に従って重量を除去することが明らかである.
  • // array = [hello, hi]
    String[] array = Stream.of("hello", "hi", "hello")
                           .distinct()
                           .toArray(String[]::new);
    
  • sorted操作です.これはソート操作であることは明らかです.パラメータのないsortedを使用すると、要素はComparableタイプに変換され、変換できないと異常が放出されます.また、比較器Comparatorに入力し、比較器の比較結果に従ってソートすることもできる.
  • //          
    // sorted = [hi, haha, hello]
    String[] sorted = Stream.of("hello", "hi", "haha")
                            .sorted(Comparator.comparingInt(String::length))
                            .toArray(String[]::new);
    
  • limit操作、パラメータは非負のlongタイプ整数であり、この操作はストリームの最初のn要素を切り取り、パラメータnがストリームの長さより大きい場合、何もしていないことに相当する.
  • //      
    // array = [hello, hi, haha]
    String[] array = Stream.of("hello", "hi", "haha", "heheda")
                           .limit(3)
                           .toArray(String[]::new);
    
  • skip操作で、パラメータは非負のlongタイプ整数であり、この操作はストリームの最初のn要素をスキップし、パラメータnがストリームの長さより大きい場合、すべての要素をスキップします.
  • //      
    // array = [haha, heheda]
    String[] array = Stream.of("hello", "hi", "haha", "heheda")
                           .skip(2)
                           .toArray(String[]::new);
    

    ターミナルオペレーション
    各ストリームには1つの端末操作しかありません.端末操作方法を呼び出すと、ストリームは本当に中間操作を実行し始め、複数の中間操作の処理を経て、最終的には端末操作で1つの結果を生成します.
  • forEach操作、パラメータはConsumerで、これは簡単な遍歴操作に相当し、処理されたストリームの各要素を遍歴します.
  • Stream.of("hello", "hi", "haha", "heheda")
          .limit(0)
          .forEach(s -> System.out.println(">>> " + s));
    
  • toArray動作は、上述のように、中間動作の処理結果に基づいて、新たな配列
  • を生成する動作である.
    // array = [hello, hi, haha, heheda]
    Object[] array = Stream.of("hello", "hi", "haha", "heheda")
                           .toArray();
    
  • allMatch、anyMatch、noneMatch操作は、クエリ
  • に一致するPredicateを受信する.
    // b = false
    boolean b = Stream.of("hello", "hi", "haha", "heheda")
                      .allMatch(s -> s.equals("hello"));
    // b = true
    b = Stream.of("hello", "hi", "haha", "heheda")
              .anyMatch(s -> s.equals("hello"));
    // b = true
    b = Stream.of("hello", "hi", "haha", "heheda")
              .noneMatch(s -> s.equals("nihao"));
    
  • findFirst、findAny操作は、いずれもストリームの要素を返し、戻り値はOptionalで包装されます.
  • String first = Stream.of("hello", "hi", "haha", "heheda")
                         .findFirst().get();
    first = Stream.of("hello", "hi", "haha", "heheda")
                  .findAny().get();
    
  • reduceは比較的複雑な操作であり、単一パラメータ、二重パラメータ、三パラメータの3つのリロード方法がある.主に積算演算に用いられるもので、どのリロード方法でも二重パラメータのBiFunctionを提供する必要があります.このBiFunctionの最初のパラメータは前のすべての要素の積算値を表し、2番目のパラメータは現在の要素の値を表し、いくつかの例を見てみましょう.
  • //      
    // reduceS ="hello ; hi ; haha ; heheda"
    String reduceS = Stream.of("hello", "hi", "haha", "heheda")
                     .reduce((x, y) -> x + " ; " + y)
                     .get();
    
    //           
    // lenght = 17
    int length = Stream.of("hello", "hi", "haha", "heheda")
                       .map(String::length)
                       .reduce(0, (x, y) -> x + y);
    
    //   ,     ,          ,              
    int reduce = Stream.of("hello", "hi", "haha", "heheda")
                       .reduce(0, (x, y) -> x + y.length(), (m, n) -> m + n);
    
  • max、min、countの3つの操作はいずれも比較的簡単で、それぞれストリーム中の最大値、最小値、要素個数
  • を返す.
    // max = "heheda"
    String max = Stream.of("hello", "hi", "haha", "heheda")
                       .max(Comparator.comparingInt(String::length))
                       .get();
    // min = "hi"
    String min = Stream.of("hello", "hi", "haha", "heheda")
                       .min(Comparator.comparingInt(String::length))
                       .get();
    // count = 4
    long count = Stream.of("hello", "hi", "haha", "heheda")
                       .count();
    
  • collect操作、この操作はtoArrayに似ていますが、ここではストリームをCollectionまたはMapに変換します.一般的にこの操作はCollectorsツールクラスと組み合わせて使用されます.次の簡単な例を見てください.
  • です.
    //    List [hello, hehe, hehe, hi, hi, hi]
    List list = Stream.of("hello", "hehe", "hehe", "hi", "hi", "hi")
                              .collect(Collectors.toList());
    //    Set [hi, hehe, hello]
    Set set = Stream.of("hello", "hehe", "hehe", "hi", "hi", "hi")
                            .collect(Collectors.toSet());
    //           ,           Map,map key      ,value         
    // map = {hi=3, hehe=2, hello=1}
    Map map = Stream.of("hello", "hehe", "hehe", "hi", "hi", "hi")
                                     .collect(Collectors.toMap(s -> {
                                         //      map key
                                         return s;
                                     }, s -> {
                                         // 1  map value
                                         return 1;
                                     }, (x, y) -> {
                                         // key        
                                         return x + y;
                                     }, () -> {
                                         //      Map   
                                         return new LinkedHashMap<>();
                                     }));
    

    単語統計のケース
    最後に、上記の操作を組み合わせて、単語統計の例を通じて、フロー処理のメリットをより直感的に感じさせます.
    Path path = Paths.get("/Users/.../test.txt");
    List lines = Files.readAllLines(path);
    lines.stream()
         .flatMap(line -> {
             String[] array = line.split("\\s+");
             return Stream.of(array);
         })
         .filter(w -> !w.isEmpty())
         .sorted()
         .collect(Collectors.toMap(w -> w, w -> 1,
                                   (x, y) -> x + y,
                                   LinkedHashMap::new))
         .forEach((k, v) -> System.out.println(k + " : " + v));
    

    残念ながらJava 8のStreamではグループ化や集約操作はサポートされていないので、ここではtoMapメソッドを使用して単語の数を統計しています.
    Java 8の集合クラスはparallelStreamメソッドを提供してパラレルストリーム(最下位はForkJoinベース)を取得します.一般的には推奨されませんが、データ規模が小さい場合はパラレルストリームを使用するよりもシリアルを使用するほうが効率的ではありません.データ規模が大きい場合、シングルマシンの計算能力は限られています.分散計算には、より強力なsparkまたはflinkを使用することをお勧めします.
    これでJava 8はlambdaとStreamの特性について分析を終えた.もちろんJava 8は古典的なバージョンとして、それだけではないに違いない.Doug Leaの大物の共同発注もJava 8バージョンで多くの内容を更新し、より多彩な同時ツールを提供し、新しいtimeパッケージなどを提供し、これらは新しい話題として議論することができる.その後の文章では引き続き関連内容を共有できることを期待しています.
    オリジナルは容易ではありません.転載は出典を明記してください.www.yangxf.top/
    転載先:https://juejin.im/post/5ca55d3251882544136e97dc