読書ノート——『Java 8実戦』シリーズの行動パラメータ化


自動回転http://www.wxueyuan.com/blog/articles/2017/10/13/1507857576802.html
最近『Java 8実戦』という本を読んで、いくつかの本の中の重点知識をみんなと共有して、文の中でいくつかの本の中の例を使って、特に声明します.
もし友達がこの本に興味を持っているが、読む時間がないなら、私のブログを見て、この本の重点知識を大体理解することができます.
動作パラメータ化は、その名の通り、コードブロック(動作)をパラメータとして別の方法に渡し、プログラムの他の部分を使用することができます.その最大の利点は、頻繁に交換されるリクエストを処理するために、コードを実行を遅らせることです.
生活の中でよく見られる例を挙げましょう.もし私たちが今学生クラスのStudioを持っているとしたら.

class Student{
    private String name;           //    
    private int avgScore;          //    
    private int height;            //    

    public Student(String name, int avgScore, int height) {
        super();
        this.name = name;
        this.avgScore = avgScore;
        this.height = height;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAvgScore() {
        return avgScore;
    }
    public void setAvgScore(int avgScore) {
        this.avgScore = avgScore;
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }

}

もし私たちが今、180 cm以上の身長の学生を選別する方法を提供する必要があるなら、私たちはどうしますか?
   //  students        ,          
   public static List getHighStudent(List students){

   }

この方法はもちろん皆さんを困らせることはできません.3、2回で完成できます.

   public static List getHighStudent(List students){
        List highStudents = new ArrayList<>();
        for(Student s : students) {
            if(s.getHeight()>=180)
                highStudents.add(s);
        }
        return highStudents;
    }

もしこの时突然需要が変化したら、要求方法はすべての身長が160 cm以上の学生をスクリーニングして、私たちはまたどのようにしますか?
上のコードをコピーして、180を160に変更しますか?この方法は明らかに望ましくなくて、もし需要がまた変化したら、私たちはもう一度数字を変えることはできません.
このようなコードを書く方法は私たちの抽象化の原則に合わない.
もちろんほとんどの学生は解決策を考えて、身長をパラメータとして方法の中に入れます.

   public static List getStudentByHeight(List students, int height){
        List highStudents = new ArrayList<>();
        for(Student s : students) {
            if(s.getHeight()>=height)
                highStudents.add(s);
        }
        return highStudents;
    }

この時、私たちは需要の変化をあまり心配しなくて、私たちに身長以上の学生を得るように要求しても、私たちはそれをパラメータとして方法に伝えることができます.
同様に、平均成績が90点以上の学生を取得する必要がある場合は、前のコードをコピーし、heightパラメータをavgScoreに変更するだけでよい.

   public static List getStudentByAvgScore(List students, int avgScore){
        List highStudents = new ArrayList<>();
        for(Student s : students) {
            if(s.getHeight()>=avgScore)
                highStudents.add(s);
        }
        return highStudents;
    }

このようにするのは簡単そうに見えますが、ほとんどがコードのコピーgetStudentByHeight()とgetStudentByAvgScore()の2つの方法の違いは、意外にも入力されたパラメータだけが異なるだけです.
18歳以上の学生をすべて削除する必要がある場合は?私たちはずっと上のコードをコピーして修正しますか?
では、より良質で抽象的な方法でこの問題を解決することはありますか?
答えは今日のタイトルの動作をパラメータ化したことです
まず、私たちが現在直面している問題を抽象化し、私たちが考えている対象は学生であり、私たちのニーズはいくつかの条件(ここの条件は学生のいくつかの属性)を満たすすべての学生を取得することです.
すべての条件を満たすとtrueを返し,そうでなければfalseを返す方法を抽象化することができる.
このようないくつかの条件が満たされているかどうかに基づいてBoolean値を返す関数を述語(predicate)と呼ぶ.
では、モデリングのためにインタフェースを抽象化します.

    interface StudentPredicate{
        //                 ,           Student    
        boolean test(Student s);
    }

私たちが処理しなければならない問題に対して、身長が180 cmより大きい学生と平均成績が90より大きい学生を獲得することに対して、私たちは2つのクラスを確立し、上のインタフェースを実現することができます.

class StudentHeightPredicate implements StudentPredicate{

    @Override
    public boolean test(Student s) {
        if(s.getHeight()>=180)
            return true;
        return false;
    }

}

class StudentAvgScorePredicate implements StudentPredicate{

    @Override
    public boolean test(Student s) {
        if(s.getAvgScore()>=90)
            return true;
        return false;
    }

}

では、ある条件に合致するすべての学生をスクリーニングする必要がある場合、私たちはこのように書くことができます.

   public static List studentFilter(List students, StudentPredicate predicate){
        List highStudents = new ArrayList<>();
        for(Student s : students) {
           //                 true
            if(predicate.test(s))
                highStudents.add(s);
        }
        return highStudents;
    }

呼び出し時に、異なる判断条件に対して異なるpredicateパラメータをパラメータに入れることができます.
   //      180cm   
   List filteredStudents = studentFilter(students, new StudentHeightPredicate());
   //      90cm   
   List filteredStudents = studentFilter(students, new StudentAvgScorePredicate());

他の条件に従って条件を満たす学生を返す必要がある場合は、StudentPredicateインタフェースを実装するために新しいクラスを構築するだけで、既存のコードをコピーして貼る必要がなく、変更に抽象的に対応することができます.
しかし、私たちはまだあまり早く喜ぶことができません.ある学生は、条件を満たすコードがパラメータとして方法に渡されるかどうかを判断するために、StudentPredicateインタフェースを実現した複数のクラスを構築しなければなりません.
これらのメソッドが1、2回しか呼び出されない場合、これらの新しく確立されたクラスは少しうるさいように見えます.
Javaの匿名クラスメカニズムは、1、2回しか使用されていないクラスの構築を避けるのに役立ちます.
簡単に言えば、Javaの匿名クラスでは、クラスを宣言しながらインスタンス化できます.つまり、StudentHeightPredicateとStudentAvgScorePredicateの2つのクラスを構築する必要はありません.
     //      180cm   
     List filteredStudents = studentFilter(students, new StudentPredicate() {
            @Override
            public boolean test(Student s) {
                if(s.getHeight()>=180)
                    return true;
                return false;
            }
        });

        //      90cm   
        List filteredStudents = studentFilter(students, new StudentPredicate() {
            @Override
            public boolean test(Student s) {
                if(s.getAvgScore()>=90)
                    return true;
                return false;
            }
        });

ここを見て学生たちは聞くかもしれませんが、この匿名のメカニズムもJava 8の新しい特性ではありません.どうしてここで苦労して説明しますか.
Java 8で提供されるLambda式は、上記のコードを大きく簡略化することができるからです.

    List filteredStudents2 = studentFilter(students, 
                Student s -> s.getHeight()>=180
    );

コードがかなり簡素になり、きれいになったのではないでしょうか.Lambda式の詳細については、ブロガーの次のブログを参照してください.
Javaの汎用メカニズムを利用して、私たちのStudentPredicateインタフェースを抽象的な道からもっと遠くまで歩くこともできます.
まず私たちのStudentPredicateを思い出してみましょう

    interface StudentPredicate{
        //                 ,           Student    
        boolean test(Student s);
    }

このように書くインタフェースは明らかに抽象的ではなく、汎用的なインタフェースを利用してより抽象的なインタフェースを書くことができます.

   interface Predicate{
        //           
        boolean test(T t);
    }

StudentFilterは、それに応じて汎用的なFilterメソッドを導入するように変更することもできる.

   public static List studentFilter(List students, StudentPredicate predicate){
        List highStudents = new ArrayList<>();
        for(Student s : students) {
           //                 true
            if(predicate.test(s))
                highStudents.add(s);
        }
        return highStudents;
    }

次のように変更します.

   public static  List filter(List list, Predicate predicate){
        List result = new ArrayList<>();
        for(T t : list) {
           //                 true
            if(predicate.test(t))
                result.add(t);
        }
        return result;
    }

このようなスーパー抽象版のPredicateインタフェースがあれば、ユーザーのニーズは、いくつかの条件を満たすすべての学生を返すか、いくつかの条件を満たすすべての先生を返すことです.
   //      90           
    List filteredStudents = filter(students, new Predicate(){

    @Override
            public boolean test(Student s) {
                if(s.getAvgScore()>=90)
                    return true;
                return false;
            }
        });

    //      90      Lambda     
    List filteredStudents = filter(students,
                Student s -> s.getAvgScore()>=90
    );

     //      5000           
    List filteredTeachers = filter(teachers, new Predicate(){

    @Override
            public boolean test(Teacher t) {
                if(t.getAvgSalary()>=5000)
                    return true;
                return false;
            }
        });

    //      5000      Lambda     
    List filteredTeachers = filter(teachers,
                Teacher t -> t.getAvgSalary()>=5000
    );

まとめ:
  • 挙動パラメータ化は、複数の異なる挙動をパラメータとして受け入れる方法であり、内部でそれらを用いて、異なる挙動を達成する能力
  • である.
  • 動作パラメータ化により、コードは絶えず変化する需要に適応し、将来の作業量
  • を軽減することができる.
  • 伝達コードは、新しい動作をパラメータとして方法に伝達することである.しかし、Java 8の前に実現するのはうるさい.インタフェースに宣言される1回のみのエンティティクラスの多くによる煩わしいコードは、Java 8の前に匿名クラスで
  • を除去することができる.