JAva関数式プログラミングとLambda式(一)

16018 ワード

関数プログラミングとは
関数式のプログラミングについて話す前に、料理の例を使って、以前のプログラミングの方法を思い出します.
public class Demo1 {
	public static void main(String[] args) {
		/*
		 *      ,           
		 */
		CookingTask ct = new CookingTask();
		ct.wash();
		ct.cut();
		ct.deepFry();
		ct.fried();
	}
}
/**
 *         ,     、  、  、  
 * @author huangyifan
 *
 */
class CookingTask{
	public void wash() {
		System.out.println("    ");
	}
	public void cut() {
		System.out.println("    ");
	}
	public void deepFry() {
		System.out.println("     ");
	}
	public void fried() {
		System.out.println("  ");
	}
}

上の例は以前私たちが使っていたコマンドプログラミングで、このコマンドプログラミングはどのように実現するかに集中しています.私たちはすべての煮物のステップを単独で方法にカプセル化したからです.ステップを追加する必要がある場合は、もう1つの方法をカプセル化する必要があります.次に、関数式プログラミングが同じ煮物である例を見てみましょう.
public class Demo2 {
	public static void main(String[] args) {
		CookingDemo ck = new CookingDemo();
		/*
		 *               
		 */
		ck.doTask("  ", (material)->System.out.println("  "+material));
		ck.doTask("  ", (material)->System.out.println("  "+material));
		ck.doTask("   ", (material)->System.out.println("  "+material));
		ck.doTask("", (material)->System.out.println("  "+material));
		/*
		 *                    ,             CookingTask         
		 *                 “  ,      ”,           
		 */
		ck.doTask("  ", (material)->System.out.println(" "+material));
	}
}
/**
 *        ,          ,       ,                ,          
 * @author huangyifan
 *
 */
class CookingDemo{
	/**
	 * 
	 * @param material    
	 * @param consumer           ,       
	 */
	public void doTask(String material,Consumer consumer) {
		consumer.accept(material);
	}
}
関数プログラミングでは、各ステップをメソッドにカプセル化する必要はありません.1つのメソッドをカプセル化するだけです.上記の2つのケースを比較すると,関数式プログラミングはコードの意味をより理解しやすいことが分かった.例えば関数式プログラミングで野菜を洗うというコード
ck.doTask("  ", (material)->System.out.println("  "+material));

見ただけで「野菜を洗う」ということが分かりましたが、コマンドプログラミングではオブジェクトがwash()メソッドを呼び出しているのしか見えませんでしたが、Demo 1の折衷クラスからwash()メソッドの具体的な実装はわかりません.CookingTaskクラスの野菜にwash()メソッドの具体的な実装を見なければなりません.
一例として、Personクラスがあります.このクラスには3つのメンバー変数name、gender、ageがあり、ビジネス要件はnameとgenderに基づいて要件を満たすpersonを見つけることです.
public class Person {
	private String name;
	private String gender;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}

ビジネスを処理するクラスPersonServiceを定義し、従来のコマンドプログラミングで2つのメソッドfindByNameとfindByGenderを宣言する必要があります.
public class PersonService {
	private static List list = new ArrayList<>();
	//    ,          
	public List findByName(String name) {
		List person = new ArrayList<>(); 
		for(Person p:list) {
			if(name.equals(p.getName())) {
				person.add(p);
			}
		}
		return person;
	}
	//    ,          
	public List findByGender(String gender) {
		List person = new ArrayList<>(); 
		for(Person p:list) {
			if(gender.equals(p.getGender())) {
				person.add(p);
			}
		}
		return person;
	}
}

上記の2つの方法は多重性が悪く,重複するコードが多く,この2行の違いを除いて同じであることが明らかになった.
if(name.equals(p.getName()))
if(gender.equals(p.getGender()))

ここでnameとgenderは2つの文字列で、1つのString strで置き換えることができます.コードは次のようになります.
if(str.equals(p.getName()))
if(str.equals(p.getGender()))

これでgetNameとgetGenderが異なり、この2つの方法を1つのfind方法として抽象化し、find方法の具体的な実装(要求に合致するpersonがあるかどうかを比較する)を1つのインタフェースにカプセル化できるかどうかを考えます.
//        
@FunctionalInterface
public interface Criteria {
	// findByName findBygender         
	boolean matches(Person p);
}

ではPersonServiceは以下のように変更できます
public class PersonService {
	private static List list = new ArrayList<>();
	public List find(Criteria criteria){
		List person = new ArrayList<>();
		for(Person p:list) {
			if(criteria.matches(p)) {
				person.add(p);
			}
		}
		return person;
	}
	//         Criteria  matches  
	public List findByName(String name) {
		return find(new Criteria() {
			@Override
			public boolean matches(Person p) {
				return name.equals(p.getName());
			}
		});
	}
	//         Criteria  matches  
	public List findByGender(String gender) {
		return find(new Criteria() {
			@Override
			public boolean matches(Person p) {
				return gender.equals(p.getGender());
			}
		});
	}
}

匿名の内部クラスをlambda式に変更
public class PersonService {
	private static List list = new ArrayList<>();
	public List find(Criteria criteria){
		List person = new ArrayList<>();
		for(Person p:list) {
			if(criteria.matches(p)) {
				person.add(p);
			}
		}
		return person;
	}
	public List findByName(String name){
		return find((p)->name.equals(p.getName()));
	}
	public List findByGneder(String gender){
		return find((p)->gender.equals(p.getGender()));
	}
}

ビジネスを追加する必要がある場合は、年齢に応じて該当するpersonを見つけるには、次のような方法を追加する必要があります.
	public List findByAge(int age){
		return find((p)->age == p.getAge());
	}

では、いったい何が関数式プログラミングなのか:この問題に対する最も簡単な答えは「彼は関数を使ってプログラミングする方法だ」であり、その核心は、問題を考えるときに、可変値と関数を使って、関数を利用して可変値を別の値にマッピングすることである.個人的な理解は,メソッドを関数インタフェースでカプセル化してパラメータとして伝達することである.
Lambda式の概要
Lambda式はコンパクトで,動作を伝達する方式であり,Java 8が導入したものでもある.まず次の例を見てみましょう
public class Demo3 {
	public static void main(String[] args) {
		//           ,                   ,            
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("  0   ");
			}
		});
		t.start();
		//  lambda          ,        ,           ,                  。
		Thread t1 = new Thread(()->System.out.println("  1   "));
		t1.start();
	}
}
public class Demo4 {
	public static void main(String[] args) {
		List list = new ArrayList<>();
		list.add("  ");
		list.add(" ");
		list.add("   ");
		Collections.sort(list, new Comparator() {
			public int compare(String o1,String o2) {
				return o1.length()-o2.length();
			}
		});
		System.out.println(list);
		//
		Collections.sort(list, (o1,o2)->o2.length()-o1.length());
		System.out.println(list);
		
	}
}

Lambda式はコンパイラによって認められ、その内容はコンパイラによって匿名の内部クラスの形式に復元されます.上記の例から、コンパイラはsortのメソッド署名に基づいてlambda式が実現するインタフェースを判断し、そのインタフェースの抽象的なメソッドに基づいてパラメータのタイプを判断するので、生命パラメータのタイプを必要としない場合がある.
Lambada式の構文は(パラメータリスト)->メソッド体
メソッドが1行しかない場合はカッコを省略できますが、メソッドにreturnがある場合はカッコとreturnを同時に省略するか、カッコを省略せずにreturnを付けます.
Collections.sort(list, (o1,o2)->{return o2.length()-o1.length();});

Lambda式の5つの異なる形式
		Runnable r = ()->System.out.println("      ");//1
		Collections.sort(list, (o1,o2)->{return o2.length()-o1.length();});//2
		Collections.sort(list, (String o1,String o2)->o2.length()-o1.length());//3
		Runnable r2 = ()->{//4
			System.out.println("      ");
			System.out.println("      ");
		};
		ActionListener al = event->System.out.println("button clicked");//5
	}

1)パラメータが空で、
2)2つのパラメータで、コンパイラはコンテキストに基づいてパラメータタイプを判断します.戻り値については、コンパイラはvoid sort(list list,Comparator c)という方法に基づいてlambda式のメソッドボディが実際にComparatorインタフェースの抽象メソッド(int comparare(T o 1,To 2))を書き換えたものであり、戻り値がintタイプであることを得ることができる.
3)また,コンパイラではパラメータタイプが得られないため,パラメータタイプを加える必要がある.ここではカッコとreturnを同時に省略できます.
4)メソッドボディが1行以上のコードである場合,カッコを省略することはできない.
5)パラメータが1つしかない場合は括弧を省略できる
関数インタフェース
関数は、1つの抽象メソッドのみがインタフェースを取得しますが、このインタフェースには複数のデフォルトメソッドまたは静的メソッドがあります.しかし、Compatorインタフェースのような特殊な場合もあります.彼は2つの抽象的な方法を持っています.Compare()とequales()のうちequals()の方法はObjectのequalsの方法と同じ方法で署名されています.この場合だけ、equalsの方法を書き換える必要はありません.
このメソッドint show(String str)を見て、メソッドを呼び出すときに文字列をメソッドに入力することができます.パラメータタイプはStringです.ではLambda式のタイプは何でしょうか.コンパイラがコンテキストに基づいて得た関数インタフェースです.
JAva 8のコア関数インタフェース
JAva 8には、次のコアインタフェースと基本タイプの関数インタフェース、およびいくつかの変換された関数インタフェースを含む関数インタフェースパッケージjava.util.functionパッケージが追加されました.
インタフェース
パラメータ
戻りタイプ
説明
Predicate
T
boolean
オブジェクトを判別するために使用されます.例えば、男性かどうか
Consumer
T
void
オブジェクトを受信して処理するために使用されますが、返されません.たとえば、人を受信して名前を印刷します.
Function
T
R
オブジェクトを異なるタイプのオブジェクトに変換
Supplier
None
T
オブジェクトを指定
UnaryOperator
T
T
オブジェクトを受信し、同じタイプのオブジェクトを返します.
BinaryOperator
(T, T)
T
同じタイプの2つのオブジェクトを受信し、元のタイプのオブジェクトを返します.
Consumerコンシューマインタフェースは,対像を受信してオブジェクトを処理するために用いられ,戻り値がなく,抽象的な方法はvoid accept(T t)である.
public class ConsumerDemo {
	/**
	 *       ,         ,            
	 * @param money 
	 * @param con
	 */
	public void doSome(int money,Consumer con) {
		con.accept(money);
	}
	/**
	 *          
	 */
	public void	buyCar() {
		doSome(50000,(m)->System.out.println("   "+m+"      "));
	}
	public static void main(String[] args) {
		ConsumerDemo cd = new ConsumerDemo();
		cd.buyCar();
	}
}

Supplier供給型インタフェースは,1つのオブジェクトのみを提供し,抽象的な方法はT get()である.
/**
 *       10          
 * @author huangyifan
 *
 */
public class SupplierDemo {

	/**
	 *              
	 * @param num        
	 * @param sup        ,        
	 * @return
	 */
	public List getNumList(int num,Supplier sup){
		List list=new ArrayList<>();
		for(int i=0;i list=getNumList(10,() ->(int)(Math.random()*100));
		System.out.println(list);
		
	}
	public static void main(String[] args) {
		SupplierDemo sd = new SupplierDemo();
		sd.test();
	}
}

Function関数インタフェースは,異なるタイプのオブジェクトに変換され,パラメータはTタイプ,戻り値はRタイプ,抽象メソッドはR apply(T t)である.
public class FunctionDemo {
	/**
	 *            ,             ,
	 * @param str         
	 * @param ft        ,        
	 * @return
	 */
	public String strHandle(String str,Function ft) {
		return ft.apply(str);
	}
	//       ,           、     
	public void test() {
		String str1 = strHandle("\t\t\t\t       ",(str) ->str.trim());
		System.out.println(str1);
		
		String subStr = strHandle("        ",str -> str.substring(2, 5));
		System.out.println(subStr);
	}
	public static void main(String[] args) {
		FunctionDemo f = new FunctionDemo();
		f.test();
	}
}

タイプは自由に変換できます
public class FunctionDemo {
	/**
	 *            ,             ,
	 * @param str         
	 * @param ft        ,        
	 * @return
	 */
	public Object strHandle(String str,Function ft) {
		return ft.apply(str);
	}
	//       ,           、     、        
	public void test() {
		String str1 = (String)strHandle("\t\t\t\t       ",(str) ->((String)str).trim());
		System.out.println(str1);
		
		String subStr =(String) strHandle("        ",str -> ((String)str).substring(2, 5));
		System.out.println(subStr);
		
		int len = (Integer)strHandle("     ",str ->((String)str).length());
		System.out.println(len);
	}
	public static void main(String[] args) {
		FunctionDemo f = new FunctionDemo();
		f.test();
	}
}

Predicate断言型インタフェース、判断操作に用いられ、抽象的な方法はboolean test(T t)である.
public class PredicateDemo {
	/**
	 *            ,                      
	 * @param list
	 * @param pi        ,        
	 * @return
	 */
	public List stringFilter(List list,Predicate pi){
		List list1 = new ArrayList<>();
		//        
		for(String s:list) {
			if(pi.test(s)) {
				list1.add(s);
			}
		}
		return list1;
	}
	/**
	 *          ,          3    
	 */
	public void test() {
		List list = Arrays.asList("dkafjsdf","dkfs","eiurowerv","da");
		list = stringFilter(list,str->str.length()>3);
		System.out.println(list);
	}
	public static void main(String[] args) {
		PredicateDemo pd = new PredicateDemo();
		pd.test();
	}
}

メソッド参照
メソッドリファレンスとは:Lambdaボディのコードが実装されている場合、メソッドリファレンスを使用できます.具体的な文法は以下の3種類があります.
1)オブジェクト:インスタンスメソッド名
public class TextMethodRef {
	public static void main(String[] args) {
		// Lambda     Consumer          ,
		Consumer con = (x)->System.out.println(x);
		con.accept("abcde");
		//  Lambda          ::    。out()   System      ,      PrintStream  
		PrintStream ps = System.out;
		Consumer con1 = ps::println;
		con1.accept("abcde");
		//       
		Consumer con2 = System.out::println;
		con2.accept("abcde");
	}
}
public class TestMethodRef {
	public static void main(String[] args) {
		Person p = new Person();
		/*
		 * getNmae       (),    Lambda               
		 * getName           Supplier              .
		 *                                 (),    ()
		 *       ,
		 */
		Supplier s =p::getName;
		String name = s.get();
		System.out.println(name);
	}
}

2)クラス:静的メソッド名
public class TestMethodRef1 {
	public static void main(String[] args) {
		//        int    
		Comparator c = (x,y) -> Integer.compare(x,y);
		int fig  = c.compare(5, 3);
		System.out.println(fig);
		// Lambda            
		Comparator c1 = Integer::compare;
		fig = c.compare(3, 5);
		System.out.println(fig);
	}
}

3)クラス::インスタンスメソッド名(比較的特殊)
public class TestMethodRef2 {
	public static void main(String[] args) {
		BiPredicate bp = (x,y) -> x.equals(y);
		boolean fig=bp.test("abc", "abcd");
		System.out.println(fig);
		//  
		BiPredicate bp1 = Integer::equals;
		fig = bp1.test(5, 3);
		System.out.println(fig);
	}
}
@FunctionalInterface
public interface BiPredicate {

    boolean test(T t, U u);

注意:1)lambdaボディで呼び出されたメソッドのパラメータタイプと戻り値タイプは、関数インタフェースの抽象メソッドのパラメータと戻り値と一致します.
           2)Lambda式の2つのパラメータ、1つ目のパラメータがインスタンスメソッドの呼び出し元、2つ目のパラメータがインスタンスメソッドのパラメータの場合、クラス::インスタンスメソッド名
コンストラクタリファレンス
オブジェクトの作成
書式:Classname::new
注:呼び出すコンストラクタのパラメータリストは、抽象メソッドのパラメータリストと一致するようにします.
public class TestConstr {
	public static void main(String[] args) {
		Supplier sl = () -> new Person();
		Person p=sl.get();
		//  
		Supplier sl1 = Person::new;
		Person p1 = sl1.get();
		//         
		Function sl2 = Person::new;
		Person p2=sl2.apply("zhangsan");
		//         
		BiFunction sl3 = Person::new;
		Person p3 = sl3.apply("lisi", "nv");
	}
}
public class Person {
	private String name;
	private String gender;
	private int age;
	
	public Person() {
		
	}
	public Person(String name) {
		this.name = name;
	}
	public Person(String name,String gender) {
		this.name = name;
		this.gender = gender;
	}
}

配列参照
書式:Type[]::new
public class TestArray {
	public static void main(String[] args) {
		Function f = (x)->new String[x];
		String[] arr = f.apply(20);
		System.out.println(arr.length);
		//  
		Function f1 = String[]::new;
		String[] arr1= f1.apply(30);
		System.out.println(arr1.length);
	}
}