劍指offer(五十五):連結串列中環的入口結點
阿新 • • 發佈:2020-07-24
Lambda表示式
一、Lambda表示式簡介
什麼是Lambda? Lambda是JDK8新增的新特性,Lambda本質上是一個匿名函式
為什麼使用Lambda? 使用Lambda表示式可以讓一個介面方法的實現非常簡潔
Lambda對介面的要求? 雖然可以使用Lambda對某些介面進行簡單實現,但不是所有的介面都可以用Lambda實現,要求介面中定義的必須要實現的抽象方法只能有一個(即函式式介面)
在JAVA8中 ,對介面加了一個新特性:default
可以使用default對介面方法進行修飾,被修飾的方法在介面中可以預設實現
@FunctionalInterface:修飾函式式介面的,表示介面中的抽象方法只有一個
二、Lambda的基礎語法
1.語法
// 1.Lambda表示式的基礎語法
// Lambda是一個匿名函式 一般關注的是以下兩個重點
// 引數列表 方法體
/**
* ():用來描述引數列表
* {}:用來描述方法體 有時可以省略
* ->: Lambda運算子 讀作goes to
* 例 Test t=()->{System.out.println("hello word")}; 大括號可省略
*/
2.建立多個介面
/** * 無引數無返回值介面 */ @FunctionalInterface public interface LambdaNoneReturnNoneParmeter { void test(); } /** * 無返回值有單個引數 */ @FunctionalInterface public interface LambdaNoneReturnSingleParmeter { void test(int n); } /** * 無返回值 多個引數的介面 */ @FunctionalInterface public interface LambdaNoneReturnMutipleParmeter { void test(int a,int b); } /** * 有返回值 無引數介面 */ @FunctionalInterface public interface LambdaSingleReturnNoneParmeter { int test(); } /** * 有返回值 有單個引數的介面 */ @FunctionalInterface public interface LambdaSingleReturnSingleParmeter { int test(int n); } /** * 有返回值 有多個引數的介面 */ @FunctionalInterface public interface LambdaSingleReturnMutipleParmeter { int test(int a,int b); }
3.建立測試類
package com.alan.learn.syntax; import com.alan.learn.interfaces.*; public class Syntax1 { public static void main(String[] args) { // 1.Lambda表示式的基礎語法 // Lambda是一個匿名函式 一般關注的是以下兩個重點 // 引數列表 方法體 /** * ():用來描述引數列表 * {}:用來描述方法體 * ->: Lambda運算子 讀作goes to */ // 無參無返回 LambdaNoneReturnNoneParmeter lambda1=()->{ System.out.println("hello word"); }; lambda1.test(); // 無返回值 單個引數 LambdaNoneReturnSingleParmeter lambda2=(int n)->{ System.out.println("引數是:"+n); }; lambda2.test(10); // 無返回值 多個引數 LambdaNoneReturnMutipleParmeter lambda3=(int a,int b)->{ System.out.println("引數和是:"+(a+b)); }; lambda3.test(10,12); // 有返回值 無引數 LambdaSingleReturnNoneParmeter lambda4=()->{ System.out.println("lambda4:"); return 100; }; int ret=lambda4.test(); System.out.println("返回值是:"+ret); // 有返回值 單個引數 LambdaSingleReturnSingleParmeter lambda5=(int a)->{ return a*2; }; int ret2= lambda5.test(3); System.out.println("單個引數,lambda5返回值是:"+ret2); //有返回值 多個引數 LambdaSingleReturnMutipleParmeter lambda6=(int a,int b)->{ return a+b; }; int ret3=lambda6.test(12,14); System.out.println("多個引數,lambda6返回值是:"+ret3); } }
三、語法精簡
針對上述基礎語法的精簡
1.引數型別精簡
/**
* 語法精簡
* 1.引數型別
* 由於在介面的抽象方法中,已經定義了引數的數量型別 所以在Lambda表示式中引數的型別可以省略
* 備註:如果需要省略型別,則每一個引數的型別都要省略,千萬不要一個省略一個不省略
*/
LambdaNoneReturnMutipleParmeter lambda1=(int a,int b)-> {
System.out.println("hello world");
};
可以精簡為:
LambdaNoneReturnMutipleParmeter lambda1=(a,b)-> {
System.out.println("hello world");
};
2.引數小括號精簡
/**
* 2.引數小括號
* 如果引數列表中,引數的數量只有一個 此時小括號可以省略
*/
LambdaNoneReturnSingleParmeter lambda2=(a)->{
System.out.println("hello world");
};
可以精簡為:
LambdaNoneReturnSingleParmeter lambda2= a->{
System.out.println("hello world");
};
3.方法大括號精簡
/**
* 3.方法大括號
* 如果方法體中只有一條語句,此時大括號可以省略
*/
LambdaNoneReturnSingleParmeter lambda3=a->{
System.out.println("hello world");
};
可以精簡為:
LambdaNoneReturnSingleParmeter lambda3=a->System.out.println("hello world");
4.大括號精簡補充
/**
* 4.如果方法體中唯一的一條語句是一個返回語句
* 賊省略大括號的同時 也必須省略return
*/
LambdaSingleReturnNoneParmeter lambda4=()->{
return 10;
};
可以精簡為:
LambdaSingleReturnNoneParmeter lambda4=()->10;
5.多引數,有返回值 精簡
LambdaSingleReturnNoneParmeter lambda4=(a,b)->{
return a+b;
};
可以精簡為:
LambdaSingleReturnMutipleParmeter lambda5=(a,b)->a+b;
四、Lambda語法進階
1.方法引用(普通方法與靜態方法)
語法:
/**
*方法引用:
* 可以快速的將一個Lambda表示式的實現指向一個已經實現的方法
* 方法的隸屬者 如果是靜態方法 隸屬的就是一個類 其他的話就是隸屬物件
* 語法:方法的隸屬者::方法名
* 注意:
* 1.引用的方法中,引數數量和型別一定要和介面中定義的方法一致
* 2.返回值的型別也一定要和介面中的方法一致
*/
例:
-
package com.alan.learn.syntax; import com.alan.learn.interfaces.LambdaSingleReturnSingleParmeter; public class Syntax3 { public static void main(String[] args) { LambdaSingleReturnSingleParmeter lambda1=a->a*2; LambdaSingleReturnSingleParmeter lambda2=a->a*2; LambdaSingleReturnSingleParmeter lambda3=a->a*2; //簡化 LambdaSingleReturnSingleParmeter lambda4=a->change(a); //方法引用 LambdaSingleReturnSingleParmeter lambda5=Syntax3::change; } /** * 自定義的實現方法 */ private static int change(int a){ return a*2; } }
2.方法引用(構造方法)
目前有一個實體類
public class Person {
public String name;
public int age;
public Person() {
System.out.println("Person的無參構造方法執行");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person的有參構造方法執行");
}
}
需求
兩個介面,各有一個方法,一個介面的方法需要引用Person的無參構造,一個介面的方法需要引用Person的有參構造 用於返回兩個Person物件,例:
interface PersonCreater{
//通過Person的無參構造實現
Person getPerson();
}
interface PersonCreater2{
//通過Person的有參構造實現
Person getPerson(String name,int age);
}
那麼可以寫作:
public class Syntax4 {
public static void main(String[] args) {
PersonCreater creater=()->new Person();
//引用的是Person的無參構造
//PersonCreater介面的方法指向的是Person的方法
PersonCreater creater1=Person::new; //等價於上面的()->new Person()
//實際呼叫的是Person的無參構造 相當於把接口裡的getPerson()重寫成new Person()。
Person a=creater1.getPerson();
//引用的是Person的有參構造
PersonCreater2 creater2=Person::new;
Person b=creater2.getPerson("張三",18);
}
}
注意:是引用無參構造還是引用有參構造 在於介面定義的方法引數
五、綜合練習
1.集合排序案例
package com.alan.exercise;
import com.alan.learn.data.Person;
import java.util.ArrayList;
/**
* 集合排序案例
*/
public class Exercise1 {
public static void main(String[] args) {
//需求:已知在一個ArrayList中有若干各Person物件,將這些Person物件按照年齡進行降序排列
ArrayList<Person> list=new ArrayList<>();
list.add(new Person("張三",10));
list.add(new Person("李四",12));
list.add(new Person("王五",13));
list.add(new Person("趙六",14));
list.add(new Person("李雷",11));
list.add(new Person("韓梅梅",8));
list.add(new Person("jack",10));
System.out.println("排序前:"+list);
//將排列的依據傳入 具體的方法指向的是 內部元素的age相減 sort會依據結果的正負進行降序排列
//sort 使用提供的 Comparator對此列表進行排序以比較元素。
list.sort((o1, o2) -> o2.age-o1.age);
System.out.println("排序後:"+list);
}
}
2.Treeset排序案例
package com.alan.exercise;
import com.alan.learn.data.Person;
import java.util.TreeSet;
public class Exercise2 {
public static void main(String[] args) {
/**Treeset 自帶排序
* 但是現在不知道Person誰大誰小無法排序
* 解決方法:
* 使用Lambda表示式實現Comparator介面,並例項化一個TreeSet物件
* 注意:在TreeSet中如果Comparator返回值是 0 會判斷這是兩個元素是相同的 會進行去重
* TreeSet<Person> set=new TreeSet<>((o1, o2) -> o2.age-o1.age);
* 這個獲取的物件列印會少一個Person
* 此時我們將方法修改
*/
TreeSet<Person> set=new TreeSet<>((o1, o2) ->{
if(o1.age>=o2.age){
return -1;
}else {
return 1;
}
});
set.add(new Person("張三",10));
set.add(new Person("李四",12));
set.add(new Person("王五",13));
set.add(new Person("趙六",14));
set.add(new Person("李雷",11));
set.add(new Person("韓梅梅",8));
set.add(new Person("jack",10));
System.out.println(set);
}
}
3.集合的遍歷
package com.alan.exercise;
import java.util.ArrayList;
import java.util.Collections;
/**
* 集合的遍歷
*/
public class Exercise3 {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9);
/**
* list.forEach(Consumer<? super E> action)
* api文件解釋: 對 集合中的每個元素執行給定的操作,直到所有元素都被處理或動作引發異常。
* 將集合中的每一個元素都帶入到介面Consumer的方法accept中 然後方法accept指向我們的引用
* 輸出集合中的所有元素
* list.forEach(System.out::println);
*/
//輸出集合中所有的偶數
list.forEach(ele->{
if(ele%2==0){
System.out.println(ele);
}
});
}
}
4.刪除集合中滿足條件的元素
package com.alan.exercise;
import com.alan.learn.data.Person;
import java.util.ArrayList;
/**
* 刪除集合中滿足條件的元素
*/
public class Exercise4 {
public static void main(String[] args) {
ArrayList<Person> list=new ArrayList<>();
list.add(new Person("張三",10));
list.add(new Person("李四",12));
list.add(new Person("王五",13));
list.add(new Person("趙六",14));
list.add(new Person("李雷",11));
list.add(new Person("韓梅梅",8));
list.add(new Person("jack",10));
//刪除集合中年齡大於12的元素
/**
* 之前迭代器的做法
* ListIterator<Person> it = list.listIterator();
* while (it.hasNext()){
* Person ele=it.next();
* if(ele.age>12){
* it.remove();
* }
* }
*/
/**
* lambda實現
* 邏輯
* 將集合中的每一個元素都帶入到介面Predicate的test方法中,
* 如果返回值是true,則刪除這個元素
*/
list.removeIf(ele->ele.age>10);
System.out.println(list);
}
}
5.開闢一條執行緒 做一個數字的輸出
package com.alan.exercise;
/**
* 需求:
* 開闢一條執行緒 做一個數字的輸出
*/
public class Exercise5 {
public static void main(String[] args) {
/**
* 通過Runnable 來例項化執行緒
*/
Thread t=new Thread(()->{
for(int i=0;i<100;i++){
System.out.println(i);
}
});
t.start();
}
}
六、系統內建的函式式介面
package com.alan.functional;
import java.util.function.*;
/**
* 系統內建的一些函式式介面
*/
public class FunctionalInterface {
public static void main(String[] args) {
// Predicate<T> : 引數是T 返回值boolean
// 在後續如果一個介面需要指定型別的引數,返回boolean時可以指向 Predicate
// IntPredicate int -> boolean
// LongPredicate long -> boolean
// DoublePredicate double -> boolean
// Consumer<T> : 引數是T 無返回值(void)
// IntConsumer int ->void
// LongConsumer long ->void
// DoubleConsumer double ->void
// Function<T,R> : 引數型別T 返回值R
// IntFunction<R> int -> R
// LongFunction<R> long -> R
// DoubleFunction<R> double -> R
// IntToLongFunction int -> long
// IntToDoubleFunction int -> double
// LongToIntFunction long -> int
// LongToDoubleFunction long -> double
// DoubleToLongFunction double -> long
// DoubleToIntFunction double -> int
// Supplier<T> : 引數 無 返回值T
// UnaryOperator<T> :引數T 返回值 T
// BiFunction<T,U,R> : 引數 T、U 返回值 R
// BinaryOperator<T> :引數 T、T 返回值 T
// BiPredicate<T,U> : 引數T、U 返回值 boolean
// BiConsumer<T,U> : 引數T、U 無返回值
/**
* 常用的 函式式介面
* Predicate<T>、Consumer<T>、Function<T,R>、Supplier<T>
*/
}
}
七、Lambda閉包
package com.alan.closure;
import java.util.function.Supplier;
public class ClosureDemo {
public static void main(String[] args) {
/**
* lambda的閉包會提升包圍變數的生命週期
* 所以區域性變數 num在getNumber()方法內被 get()引用 不會在getNumber()方法執行後銷燬
* 這種方法可以在外部獲取到某一個方法的區域性變數
*/
int n=getNumber().get();
System.out.println(n);
}
private static Supplier<Integer> getNumber(){
int num=10;
/**
* Supplier supplier=()->num;
* return supplier;
*/
return ()->{
return num;
};
}
}
*************************************************************************
package com.alan.closure;
import java.util.function.Consumer;
public class ClosureDemo2 {
public static void main(String[] args) {
int a=10;//系統預設加了final修飾
Consumer<Integer> c=ele->{
System.out.println(a+1);
//System.out.println(ele);
//System.out.println(a++); 會報錯
//在lambda中引用區域性變數 這個變數必須是一個常量
};
//a++; 這樣也會導致內部報錯
//如果在內部已經引用區域性變數 引數傳遞後 列印的還是 10
c.accept(1);
}
}