java-函數語言程式設計-函式式介面以及方法應用
一、lambda表示式
1、 Lambda表示式是匿名內部類的簡化寫法。
Lambda標準格式:
(引數型別 引數名) -> {
方法體;
return 返回值;
}
省略規則:
1. 小括號中的引數型別可以省略。
2. 如果小括號中只有一個引數,那麼可以省略小括號
3. 如果大括號中只有一條語句,那麼可以省略大括號,return,以及;
使用前提:
1. 必須有介面, 介面中有且僅有一個需要被重寫的抽象方法。
2. 必須支援上下文推導。 要麼有介面作為引數,要麼有一個變數接收這個Lambda表示式.
函數語言程式設計: 可推導就是可省略
注:此處省略了persion物件的程式碼
public class Demo01Lambda {
public static void main(String[] args) {
//使用Lambda表示式實現多執行緒
new Thread(() -> {
System.out.println("Lambda標準格式執行多執行緒");
}).start();
//使用Lambda省略格式實現多執行緒
new Thread(() -> System.out.println("Lambda省略格式執行多執行緒")).start();
}
}
package cn.itcast.demo01;
import java.util.ArrayList;
import java.util.Collections;
/*
建立集合,儲存Person,並且使用比較器排序方式對集合中的Person物件按照年齡升序排序
*/
public class Demo02Lambda {
public static void main(String[] args) {
//建立集合
ArrayList<Person> list = new ArrayList<>();
//新增Person
list.add(new Person("大冪冪", 30));
list.add(new Person("王思聰", 28));
list.add(new Person("王健林", 40));
//使用比較器排序對集合中的Person按照年齡升序排序
/*
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
*/
Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());
//列印集合
System.out.println(list);
}
}
二、函式式介面
1、概念:如果一個介面中有且僅有一個需要被重寫的抽象方法,那麼這個介面就是函式式介面。
2、函式式介面的使用:
(1)當做一個普通介面去使用(給其他類實現)。
(2)當做 lambda表示式的使用前提去使用(使用lambda表示式必須的使用函式式介面)。
有一個註解叫做:@ Functionallnterface,真個註解可以驗證是否是函式式介面,這個註解在函式的上面,如果某個介面不是函式式介面,那麼加上這個註解會報錯。
這個註解僅僅用來驗證一個介面是否是一個函式式介面,如果沒有這個註解,只要某個介面滿足函式式介面的條件,那麼這個介面就是一個函式式介面。
@FunctionalInterface
public interface MyInterface {
void method();
}
請定義一個函式式介面 Eatable ,內含抽象 eat 方法,沒有引數或返回值。使用該介面作為方法的引數,並進而
通過Lambda來使用它
public interface Eatable {
//eat方法
void eat();
}
public class Demo01LambdaTest {
public static void main(String[] args) {
method(() -> System.out.println("使用Lambda表示式吃飯飯,香香噠"));
}
//引數是一個函式式介面。
//引數如果是函式式介面,那麼可以傳遞Lambda表示式
public static void method(Eatable eatable) {
eatable.eat();
}
}
請定義一個函式式介面 Sumable ,內含抽象 sum 方法,可以將兩個int數字相加返回int結果。使用該介面作為方法的引數,並進而通過Lambda來使用它。
@FunctionalInterface
public interface Sumable {
int sum(int a, int b);
}
public class Demo02LambdaTest {
public static void main(String[] args) {
method(new Sumable() {
@Override
public int sum(int a, int b) {
return a + b;
}
});
//呼叫method方法,傳遞Lambda表示式
method((a, b) -> a + b);
}
public static void method(Sumable sumable) {
int result = sumable.sum(10, 20);
System.out.println("result:" + result);
}
}
三、使用函式式介面列印日誌,優化效能浪費問題
(1)傳統方式列印日誌
步驟:
1. 定義了三個字串
2. 對這三個字串進行了拼接,並且當做引數傳遞給了printLog方法。
3. 進行判斷,如果日誌等級滿足要求,那麼就列印日誌資訊。
如果日誌等級不滿足要求,那麼就不列印。
如果不滿足要求,那麼就不會列印日誌資訊,但是之前已經把日誌資訊拼接好了,拼接好之後卻沒有使用
這樣就產生了效能浪費。
public class Demo01Log {
public static void main(String[] args) {
//定義三個字串
String s1 = "hello";
String s2 = "world";
String s3 = "java";
//呼叫printLog方法,列印日誌資訊
printLog(1, s1 + s2 + s3);
}
//用來列印日誌資訊
//引數給兩個,一個是日誌等級。 第二個引數是日誌資訊
public static void printLog(int level, String msg) {
//進行判斷,如果日誌等級是3,那麼就列印日誌資訊
if (level == 3) {
System.out.println(msg);
}
}
}
(2)使用Lambda表示式解決效能浪費的問題
如果日誌等級滿足要求,那麼就進行拼接,然後列印這個日誌資訊。
如果日誌等級不滿足要求,那麼就不拼接了。
Lambda表示式是作為介面中抽象方法的內容存在的。 所以當通過介面呼叫抽象方法時,才會
執行對應的Lambda表示式。
public class Demo02LogLambda {
public static void main(String[] args) {
//建立三個字串
String s1 = "hello";
String s2 = "world";
String s3 = "java";
//呼叫method方法,根據日誌等級列印日誌資訊
method(3, () -> {
String msg = s1 + s2 + s3;
System.out.println(msg);
});
}
//第一個引數為日誌等級,第二個引數為用來列印日誌的函式式介面。
public static void method(int level, MessageBuilder messageBuilder) {
if (level == 1) {
messageBuilder.printMsg();
}
}
}
.//定義一個函式式介面
public interface MessageBuilder {
void printMsg();
}
四、Lambda表示式的六種使用方式
1、lambda表示式作為方法引數
//lambda表示式做為方法引數
public class Demo01LambdaParam {
public static void main(String[] args) {
new Thread(() -> System.out.println("使用Lambda表示式執行了執行緒")).start();
}
}
2、Lambda表示式作為返回值型別
/*
Lambda表示式作為返回值
*/
public class Demo02LambdaReturn {
public static void main(String[] args) {
//建立集合
ArrayList<Person> list = new ArrayList<>();
//呼叫list的add方法,向集合中新增Person物件
list.add(new Person("滅絕師太", 50));
list.add(new Person("金花婆婆", 20));
list.add(new Person("金毛獅王", 30));
//使用比較器排序,對學生根據年齡升序排序
Collections.sort(list, getComparator());
//列印結果
System.out.println(list);
}
public static Comparator<Person> getComparator() {
return (o1, o2) -> o1.getAge() - o2.getAge();
/*
return new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
};
*/
}
}
(1) 請自定義一個函式式介面 MySupplier ,含有無引數的抽象方法 get 得到 Object 型別的返回值。並使用該函式式
介面分別作為方法的引數和返回值。
public class Demo03LambdaTest {
public static void main(String[] args) {
//呼叫method方法,傳遞Lambda表示式
//method(() -> "abc");
method(getMySupplier());
}
public static void method(MySupplier mySupplier) {
Object obj = mySupplier.get();
System.out.println(obj);
}
//Lambda表示式作為方法的返回值.
public static MySupplier getMySupplier() {
return () -> "hello";
}
}
public interface MySupplier {
//沒有引數,有一個Object型別的返回值
Object get();
}
public interface MySupplier{
return get();
}
五、方法引用
1、 Lambda表示式做的事情是拿到引數之後, 【直接】對引數進行了列印
此時Lambda表示式做的事情就和方法做的事情重疊了。
Lambda表示式拿到引數之後直接進行了列印, 這樣的話其實就沒有必要再寫一遍引數了, 因為這個內容可以推導。
如果Lambda表示式拿到引數之後,直接去幹了某些事情,那麼可以寫成另一種方式,另一種方式是Lambda的孿生兄弟: 方法引用
方法引用是Lambda表示式的一種簡化方式
方法的使用格式 :: 兩個冒號
public class Demo01Lambda {
public static void main(String[] args) {
//lambda表示式, 方法引數多餘了,因為拿到引數之後直接對引數進行了列印
method(str -> System.out.println(str));
//方法引用
//含義, 拿到引數之後,直接使用System.out的println方法列印這個引數.
method(System.out::println);
}
public static void method(Printable printable) {
printable.print("hello");
}
}
public class Demo02Lambda {
public static void main(String[] args) {
//呼叫method方法,傳遞一個Lambda表示式
method(num -> System.out.println(num));
//換成方法引用
method(System.out::println);
}
public static void method(PrintNumberable printNumberable) {
printNumberable.printNumber(10);
}
}
public interface Printable {
void print(String str);
}
public interface PrintNumberable {
void printNumber(int num);
}
2、方法 引用的六種方式
對於類來說有四種:
(1)物件引用成員方法
(2)類名引用靜態方法
(3)supper應用父類方法
(4)this引用本類方法
對於構造器來說有兩種方式
(1)類的構造器引用
(2)陣列的構造器引用
1、物件引用成員方法:
格式;物件名::方法名
public class Demo01MethodRef {
public static void main(String[] args) {
//呼叫method方法,傳遞Lambda表示式。 將引數字串轉成大寫並列印
//method(str -> System.out.println(str.toUpperCase()));
MyClass myClass = new MyClass();
//method(str -> myClass.printUpperStr(str));
//換成方法引用
//拿到引數之後,直接呼叫myClass物件的printUpperStr方法去對這個引數進行處理。
method(myClass::printUpperStr);
}
//引數是函式式介面,呼叫的時候可以傳遞Lambda表示
public static void method(Printable printable) {
printable.print("hello");
}
}
public class Demo02MethodRef {
public static void main(String[] args) {
//呼叫method方法,傳遞Lambda表示式
//method(fileName -> System.out.println("Lambda表示式在七手八腳的處理這個檔案:" + fileName));
//建立一個助手物件
Assistant assistant = new Assistant();
//method(fileName -> assistant.doFile(fileName));
//方法引用
method(assistant::doFile);
}
public static void method(Helper helper) {
helper.help("工資流水.txt");
}
}
public interface Helper {
//幫助處理檔案
void help(String fileName);
}
public class MyClass {
//定義方法,接收一個字串的引數,並且將這個字串轉成大寫列印
public void printUpperStr(String str) {
System.out.println(str.toUpperCase());
}
}
public interface Printable {
void print(String str);
}
2、類名引用過成員方法
格式 類名::靜態方法
public class Demo01MethodRef {
public static void main(String[] args) {
//呼叫method方法,傳遞Lambda表示式
//method(num -> num > 0 ? num : -num);
//Math數學工具類中有一個abs方法,可以直接求出一個數的絕對值
method(num -> Math.abs(num));
//拿到引數num,直接呼叫Math類的靜態方法abs對引數num進行了處理。
method(Math::abs); //表示拿到引數之後,對引數直接通過Math類的abs方法進行處理。
}
public static void method(Calcable calcable) {
int result = calcable.abs(-10);
System.out.println(result);
}
}
/*
類名引用靜態方法的練習
*/
public class Demo02MethodRef {
public static void main(String[] args) {
//呼叫method方法,傳遞Lambda表示式,檢查引數字串是否為空(如果這個字串是null,或者是一個"",那麼就是空)
//如果字串是空,那麼就返回true。不是空返回false
//method(str -> str == null || str.equals(""));
method(str -> StringUtils.isBlank(str));
//改成方法引用
method(StringUtils::isBlank); //拿到引數直接對引數使用StringUtils的靜態方法isBlank進行處理。
}
public static void method(Checker checker) {
boolean flag = checker.check("");
System.out.println("flag:" + flag);
}
}
3、super引用成員方法
格式 super ::方法名
public class Demo01MethodRef {
public static void main(String[] args) {
//建立一個Student物件
Student stu = new Student();
stu.sayHello();
}
}
@FunctionalInterface
public interface Greetable {
void greet();
}
public class Person {
public void sayHello() {
System.out.println("雷猴");
}
}
public class Student extends Person{
public void sayHello() {
//method(() -> System.out.println("雷猴"));
//method(() -> super.sayHello());
//改成方法引用。 使用super呼叫了父類的方法。 所以格式為: super::父類方法
method(super::sayHello);
}
//定義方法,方法引數傳遞一個函式式介面
public void method(Greetable greetable) {
greetable.greet();
}
}
4、this引用本類方法
public class Demo01MethodRef {
public static void main(String[] args) {
Man man = new Man();
man.beHappy();
}
}
public class Man {
public void buyGift() {
System.out.println("買一個500平米的大house");
}
public void marry(Richable richable) {
richable.buy();
}
public void beHappy() {
//marry(() -> System.out.println("買了一個500克拉的大鑽戒"));
//marry(() -> buyGift());
//使用this引用本類方法。 this::本類方法
marry(this::buyGift);
}
}
public interface Richable {
void buy();
}
5、類的構造器使用
格式 類名:: new
public class Demo01MethodRef {
public static void main(String[] args) {
//呼叫method方法。 傳遞Lambda表示式
//拿到引數姓名和年齡之後直接通過構造方法根據姓名和年齡建立了Person物件.所以可以換成方法引用.
method((name, age) -> new Person(name, age));
//使用類的構造器引用, 因為之前程式碼是在使用構造方法建立物件
method(Person::new); //含義,拿到引數之後直接使用這些引數通過構造方法建立物件
}
//函式式介面當做方法引數
public static void method(PersonBuilder personBuilder) {
Person p = personBuilder.createPerson("大冪冪", 30);
System.out.println(p);
}
}
6、陣列的構造器引用。
格式:
資料型別[]::new
public class Demo02MethodRef {
public static void main(String[] args) {
//呼叫method方法,傳遞lambda表示式,引數是多少,就建立一個長度是多少的陣列然後返回
//method(len -> new int[len]); //拿到引數len之後直接根據引數類建立了一個int陣列。所以可以換成方法引用
method(int[]::new); //拿到引數後,直接根據引數建立了一個int陣列
}
//引數傳遞函式式介面
public static void method(ArrayBuilder arrayBuilder) {
int[] arr = arrayBuilder.createArray(10);
System.out.println(Arrays.toString(arr));
}
}
public interface PersonBuilder {
//給我姓名和年齡,那麼我就可以根據這個姓名和年齡給你一個Person物件。
Person createPerson(String name, int age);
}
public interface ArrayBuilder {
//給我長度,我就可以給你對應長度的int陣列
int[] createArray(int len);
}
package cn.itcast.demo10;
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
}
六、Java提供的函式式介面
在jdk8之後,多了一個java.util.function包。裡面提供了大量的函式式介面。
其中有一個介面,叫做Supplier,可以看成一個生產者,裡面的get方法,可以獲取一個物件
Supplier<T> 泛型表示要獲取的物件的資料型別。
抽象方法:
T get() 獲取一個物件
public class Demo01Supplier {
public static void main(String[] args) {
//呼叫method方法。引數是一個函式式介面,可以傳遞Lambda表示式
method(() -> "hello");
}
//定義一個method方法,把函式式介面Supplier當做方法的引數
public static void method(Supplier<String> supplier) {
String str = supplier.get();
System.out.println(str);
}
}
/*
使用 Supplier 介面作為方法引數型別,通過Lambda表示式求出int陣列中的最大值。提示:介面的泛型請使用
java.lang.Integer 類。
*/
public class Demo02SupplierTest {
public static void main(String[] args) {
//定義一個int陣列
int[] arr = {5, 10, -20, 3, 8};
method(() -> {
//在Lambda表示式大括號中,求出陣列的最大值,然後返回即可
//定義參照物max用於表示每次比較的最大值。 最開始可以把陣列中索引為0的元素當成最大的賦值給這個變數
int max = arr[0];
//拿陣列中其他的每一個元素和這個max進行比較,如果比max大,那麼就把這個值賦值給max
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//求出結果之後,返回最終結果
return max;
});
}
public static void method(Supplier<Integer> supplier) {
//呼叫Supplier的get方法獲取一個結果
Integer result = supplier.get();
System.out.println("result:" + result);
}
}
2、 在java中,還有一個函式式介面叫做Consumer,可以看成一個消費者,用來消費一個數據(使用這個資料去做一些事情)。
Consumer<T> 泛型T,表示要消費資料的資料型別。
抽象方法:
void accept(T t): 表示要對引數t進行消費。(拿到引數t之後去做一些事情)
public class Demo03Consumer {
public static void main(String[] args) {
//呼叫method方法, 傳遞一個Lambda表示式
//method(s -> System.out.println(s));
//方法引用的寫法
method(System.out::println);
}
//定義方法,把函式式介面Consumer當做引數
public static void method(Consumer<String> consumer) {
consumer.accept("hello");
}
}
/*
在函式式介面Consumer中有一個預設方法,叫做andThen,可以將兩個Consumer合併(拼接)成一個Consumer物件
default Consumer andThen(Consumer after): 將兩個Consumer進行合併。比如a.andThen(b)。 是對a和b進行合併。並且有先後順序是先a後b
*/
public class Demo04Consumer {
public static void main(String[] args) {
method(s -> System.out.println(s.toUpperCase()),
s -> System.out.println(s.toLowerCase()));
}
/*
定義方法,要兩個Consumer
第一個Consumer用來將Hello全部轉成大寫並列印
第二個Consumer用來將Hello全部轉成小寫並列印
*/
public static void method(Consumer<String> one, Consumer<String> two) {
//分別消費處理Hello
//one.accept("Hello");
//two.accept("Hello");
//將one的內容和two的內容合併
//合併之後的three裡面包含的one和two的內容, 先one後two。
//Consumer<String> three = one.andThen(two);
//呼叫three的accept方法,處理Hello
//three.accept("Hello"); //因為three中包含了one和two的內容,所以這句話相當於 one.accept("Hello") two.accept("Hello")
//將one和two合併之後直接呼叫accept方法處理這個字串
one.andThen(two).accept("Hello"); //先通過one呼叫accept,再通過two呼叫accept
}
}
/*
下面的字串陣列當中存有多條資訊,請按照格式“ 姓名:XX。性別:XX。 ”的格式將資訊打印出來。要求將列印姓
名的動作作為第一個 Consumer 介面的Lambda例項,將列印性別的動作作為第二個 Consumer 介面的Lambda實
例,將兩個 Consumer 介面按照順序“拼接”到一起
列印陣列中的內容,要使用兩個Consumer進行列印, 第一個Consumer列印姓名,第二個Consumer列印性別.
還要將這兩個Consumer合併成一個Consumer
列印內容
姓名:迪麗熱巴。 性別:女
姓名:古力娜扎。 性別:女
姓名:馬爾扎哈。 性別:男
*/
public class Demo05ConsumerTest {
public static void main(String[] args) {
String[] array = { "迪麗熱巴,女", "古力娜扎,女", "馬爾扎哈,男" };
//呼叫method方法,進行處理
method(str -> System.out.print("姓名:" + str.split(",")[0]),
str -> System.out.println("。 性別:" + str.split(",")[1]),
array);
}
/*
引數one用來處理陣列中每個元素的姓名
引數two用來處理陣列中每個元素的性別
引數arr表示要處理的陣列
*/
public static void method(Consumer<String> one, Consumer<String> two, String[] arr) {
//遍歷陣列,拿到陣列中的每個元素
for(String str : arr) {
//使用one處理列印姓名
//one.accept(str);
//使用two處理列印性別
//two.accept(str);
//將one和two進行合併,然後呼叫accept方法
one.andThen(two).accept(str);
}
}
}