Lambda表達式
1.初步印象
示例代碼:
View view = findViewById(R.id.textView2);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("lambda", String.format("點擊[%d]", v.getId()));
Toast.makeText(MainActivity.this, "顯示文字", Toast.LENGTH_SHORT).show();
}
});
這是我們經(jīng)常設置監(jiān)聽器的代碼匕积,不管你要做的事情是什么项乒,必須有:
new View.OnClickListener(){
@Override
public void onClick(View view){
...
}
}
這代碼就很繁瑣,很多“雜音”九榔,而且當你需要使用外部的Context時候,你還不得不老是調用 XXXX.this涡相,Lambda表達式不會從超類(supertype)中繼承任何變量名哲泊,也不會引入一個新的作用域
如果用Lambda表達式簡化,代碼如下:
view.setOnClickListener(v -> {
Log.i("lambda", String.format("點擊[%d]", v.getId()));
Toast.makeText(this, "顯示文字", Toast.LENGTH_SHORT).show();
});
Lambda表達式可以認為是對行為的抽象催蝗,表達式比較清晰的表明了一個我們要做什么
2.Lambda表達式常用形式
示例代碼:
// 沒有參數(shù)的方法切威,也沒有返回
Runnable run = () -> System.out.println("do something");
// 1個參數(shù)的方法可以省略(),且有返回, 只有一句, 默認認為是要返回表達式的結果
IntUnaryOperator func = x -> x + 1; // 輸入x, 返回 (x + 1)的結果
// 2個參數(shù)的方法
DoubleBinaryOperator func2 = (x, y) -> x + y;
// 可以在()里面定義好變量的類型丙号,多數(shù)情況下先朦,可以不需要,由編譯器自己推斷參數(shù)類型
IntBinaryOperator func3 = (int x, int y) -> x + y;
// 多行語句犬缨,需要用{表達},且需要返回值的函數(shù)不能省略return
IntBinaryOperator func4 = (x, y) -> {
int temp = x % 3;
int temp2 = y / 2;
return temp + temp2;
};
Lambda表達式是由左右兩部和"->"構成的:
1.左邊部分代表參數(shù)喳魏,無參用()表達,一個參數(shù)可以省略()怀薛,多個參數(shù)必須用()包含刺彩。
2.右邊部分代表方法體,可以是一個表達式,也可以是{}包含的代碼塊
Lambda表達式對外部局部變量的引用屬于值引用创倔,類似匿名類引用外部局部變量時候嗡害,必須聲明為final,只不過Lambda表達式引用時候畦攘,不需要顯示的聲明為final霸妹,減少代碼雜音。
3.Java8對函數(shù)的抽象---函數(shù)接口
我們經(jīng)常使用只有一個方法的接口來表示某特定方法行為知押,比如我們的OnClickListener叹螟。這種函數(shù)接口反復出現(xiàn),現(xiàn)在Java8提供了一些核心的函數(shù)接口台盯,抽象化了這些行為罢绽。(在java.util.function包下面),列出比較重要的幾個函數(shù)接口如下:
1.Predicate<T>
代表:參數(shù)為T -> Boolean爷恳,用于判斷
示例 :
Predicate<Integer> func = x -> x > 1;
System.out.println("是否大于1? " + func.test(2));
2.Consumer<T>
代表:參數(shù) T ->{} 有缆,用于處理某事物
示例:
Consumer<Integer> func = x -> System.out.println(x);
func.accept(2);
3.Function<T, R>
代表 : 參數(shù) T -> R, 用于對數(shù)據(jù)進行處理變換温亲,返回R類型
示例:
Function<Point, Double> func = p -> p.distance(0, 0);
System.out.println("點[3,4]距離原點的距離是? " + func.apply(new Point(3, 4)));
4.Supplier<T>
代表:參數(shù) ( ) -> T棚壁,生成對象
示例:
Supplier<Point> func = () -> new Point(3, 4);
5.UnaryOperator<T>
代表:參數(shù) T -> T,對T進行處理栈虚,并且返回同類型
示例:
UnaryOperator<Integer> addSelf = x -> x + 1;
System.out.println("1+1="+addSelf.apply(1));
6.BinaryOperator<T>
代表:參數(shù)(T, T) -> T 袖外,變換返回同類型T
示例:
BinaryOperator<Integer> max = (x, y) -> {
return x > y ? x : y;
};
System.out.println("比較5和4,大的是: " + max.apply(4, 5));
Java8 流
Java8 對集合類庫進行了大量修改魂务,并且引入了新概念:流
1.外部迭代到內(nèi)部迭代
/**
* 計算字符串里面所有數(shù)字的和, 只考慮單一字符
*
* @param text
* @return
*/
private static void countNum(String text) {
char[] arr = text.toCharArray();
int sum = 0;
for (int i = 0; i < arr.length; i++) {
if (Character.isDigit(arr[i])) {
sum += Character.digit(arr[i], 10);
}
}
System.out.println(String.format("它們的和是[%d]", sum));
}
需要進行for循環(huán)曼验,每次迭代都必須寫類似代碼,如果多個for循環(huán)粘姜,還要考慮很多其他問題鬓照,如果要并行處理,就需要修改for循環(huán)邏輯
傳統(tǒng)的for循環(huán)孤紧,或者Iterator叫做外部迭代豺裆。Java 8提供了流,進行內(nèi)部迭代号显,代碼如下:
/**
* 計算字符串里面所有數(shù)字的和
*
* @param text
* @return
*/
private static void countNumSeq(String text) {
IntStream stream = IntStream.range(0, text.length())
.mapToObj(i -> text.charAt(i))//拿到每個字符
.filter(c -> Character.isDigit(c))//過濾掉不是數(shù)字的字符
.mapToInt(c -> Character.digit(c, 10));//將所有字符轉為數(shù)字
System.out.println(String.format("它們的和是[%d]", stream.sum()));
}
看起來貌似代碼復雜多了臭猜,但是我們清晰的看出了所有要執(zhí)行的“意圖”:
1.查找所有字符
2.過濾掉不是數(shù)字的
3.將字符轉為數(shù)字
4.求和
而for循環(huán)代碼無法如此清晰的表達我們需要執(zhí)行的動作,所以代碼得簡潔性得到提高押蚤。
惰性求值方法:在stream方法里面蔑歌,像mapToObj這樣的方法,只刻畫了什么樣的stream而不會去產(chǎn)生新集合揽碘,結果也只是返回一個新的stream
及早求值方法:像count這樣會得到一個具體值次屠,會對集合真正進行操作
所以上面代碼园匹,只有真正執(zhí)行到count(及早求值方法)時候,才會去對集合操作帅矗,不會進行多次循環(huán)偎肃。
2.常用流操作
可以發(fā)現(xiàn)煞烫,流基本全部都是傳入前面提到的函數(shù)接口
1.Stream<T> filter(Predicate<? super T> predicate);
代表:對流進行過濾
2.<R> Stream<R> map(Function<? super T, ? extends R> mapper);
代表:對流進行轉換從T->R
類似:
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
3.<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
代表:將流轉換為新類型的流,而map是得到新類型
示例如下:
/**
* 合并流
*/
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(toList());
assertEquals(asList(1, 2, 3, 4), together);
4.Stream<T> distinct();
代表:去重,依賴T.equals()方法判斷
5.Stream<T> sorted();
代表:排序涛舍,依賴T是可比較的拂共,如果沒有實現(xiàn)Comparable會報錯
6.Stream<T> sorted(Comparator<? super T> comparator);
代表:排序
7.Stream<T> peek(Consumer<? super T> action);
代表:對流進行處理的時候,可以對這些被處理的元素進行消費料饥,而不影響新集合內(nèi)容
List<String> list = Stream.of("one", "two", "three", "four","five", "six", "seven")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.collect(Collectors.toList());
System.out.println("List size " + list.size());
輸出結果:
Filtered value: three
Filtered value: four
Filtered value: five
Filtered value: seven
List size 4
8.Stream<T> limit(long maxSize);
代表:最多拿取多少個源數(shù)據(jù)
上面代碼加入:
stream.limit(2)
Filtered value: three
Filtered value: four
List size 2
9.void forEach(Consumer<? super T> action);
代表:循環(huán)遍歷
10.Stream<T> skip(long n);
代表:對開始的n個數(shù)據(jù)忽略
上面代碼加入:
stream.skip(1);
Filtered value: four
Filtered value: five
Filtered value: seven
List size 3
11.long count();
代表:獲取新集合個數(shù)
12.boolean anyMatch(Predicate<? super T> predicate);
13.boolean allMatch(Predicate<? super T> predicate);
14.T reduce(T identity, BinaryOperator<T> accumulator);
代表:reduce 操作可以實現(xiàn)從一組值中生成一個值蒲犬,如 count方法,max方法岸啡,min方法原叮,都可以用reduce來實現(xiàn)。
書本練習題: 只用 reduce 和 Lambda 表達式實現(xiàn) Stream 上的map()
試著寫了下巡蘸,不知道是不是這個意思奋隶,如下:
/**
* reduce模擬map操作
*/
private static <T, R> List<R> map(Stream<T> stream, Function<T, R> todo) {
List<R> list = new ArrayList<>();
stream.reduce(null, (init, e) -> {
list.add(todo.apply(e));
return init;
});
return list;
}
List<Double> list = map(Stream.of("1", "2", "3", "4"),
(s) -> Double.valueOf(s));
list.stream()
.forEach((d) -> System.out.println(d));
Java8 新增特性
1.接口靜態(tài)方法
上面代碼,我們在使用stream時候悦荒,可以如下調用:
Stream<String> s = Stream.of("1", "2", "3", "4");
查看源碼唯欣,發(fā)現(xiàn)Stream是接口,如下:
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
也就是說可以直接在接口里面定義我們自己的靜態(tài)方法實現(xiàn)搬味。
如果一個方法有充分的語義原因和某個概念相關境氢,那么就應該將該方法和相關的類或接口放在一起,而不是放到另一個工具類中碰纬。這有助于更好地組織代碼
2.接口default方法
解決場景(二進制接口的兼容性):
1.Java8中萍聊,對Collection 接口中增加了新的 stream()。
2.如果你在Java8 以前實現(xiàn)了自己的MyList繼承了Collection接口悦析,那么你在Java8運行時候會報錯寿桨,因為以前的實現(xiàn)里面根本沒有stream()
3.為解決如上問題,Java8新增default字段她按,標記默認實現(xiàn)方法Collection 接口告訴它所有的子類:“如果你沒有實現(xiàn) stream 方法牛隅,就使用我的吧∽锰”接口中這樣的方法叫作默認方法
public interface IStuff {
String getName();
double getPrice();
default void showSelf() {
System.out.println(String.format("[%s]%.02f", getName(), getPrice()));
}
}
//子類的實現(xiàn)不一定要有showSelf媒佣,如果沒有回默認使用default方法
IStuff stuff = new IStuff() {
@Override
public String getName() {
// TODO Auto-generated method stub
return "iPhone7";
}
@Override
public double getPrice() {
// TODO Auto-generated method stub
return 4897.4647;
}
};
stuff.showSelf();
3.方法引用
Lambda 表達式有一個常見的用法:Lambda 表達式經(jīng)常調用參數(shù),比如:
Function<Integer, String[]> func = (i) -> new String[i];
Function<Point, Double> func2 = p -> p.getX();
這種用法如此普遍陵刹,Java 8 提供了一個簡寫語法默伍,叫作方法引用
func = String[] :: new;
func2 = Point :: getX;
形式就是** Classname :: Methodname** ,如果想調用構造函數(shù)則用new代替
參考:
《Java 8 函數(shù)式編程》