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();
}
});
这是我们经常设置监听器的代码,不管你要做的事情是什么,必须有:
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表达式常用形式
示例代码:
// 没有参数的方法,也没有返回
Runnable run = () -> System.out.println("do something");
// 1个参数的方法可以省略(),且有返回, 只有一句, 默认认为是要返回表达式的结果
IntUnaryOperator func = x -> x + 1; // 输入x, 返回 (x + 1)的结果
// 2个参数的方法
DoubleBinaryOperator func2 = (x, y) -> x + y;
// 可以在()里面定义好变量的类型,多数情况下,可以不需要,由编译器自己推断参数类型
IntBinaryOperator func3 = (int x, int y) -> x + y;
// 多行语句,需要用{表达},且需要返回值的函数不能省略return
IntBinaryOperator func4 = (x, y) -> {
int temp = x % 3;
int temp2 = y / 2;
return temp + temp2;
};
Lambda表达式是由左右两部和"->"构成的:
1.左边部分代表参数,无参用()表达,一个参数可以省略(),多个参数必须用()包含。
2.右边部分代表方法体,可以是一个表达式,也可以是{}包含的代码块
Lambda表达式对外部局部变量的引用属于值引用,类似匿名类引用外部局部变量时候,必须声明为final,只不过Lambda表达式引用时候,不需要显示的声明为final,减少代码杂音。
3.Java8对函数的抽象---函数接口
我们经常使用只有一个方法的接口来表示某特定方法行为,比如我们的OnClickListener。这种函数接口反复出现,现在Java8提供了一些核心的函数接口,抽象化了这些行为。(在java.util.function包下面),列出比较重要的几个函数接口如下:
1.Predicate<T>
代表:参数为T -> Boolean,用于判断
示例 :
Predicate<Integer> func = x -> x > 1;
System.out.println("是否大于1? " + func.test(2));
2.Consumer<T>
代表:参数 T ->{} ,用于处理某事物
示例:
Consumer<Integer> func = x -> System.out.println(x);
func.accept(2);
3.Function<T, R>
代表 : 参数 T -> R, 用于对数据进行处理变换,返回R类型
示例:
Function<Point, Double> func = p -> p.distance(0, 0);
System.out.println("点[3,4]距离原点的距离是? " + func.apply(new Point(3, 4)));
4.Supplier<T>
代表:参数 ( ) -> T,生成对象
示例:
Supplier<Point> func = () -> new Point(3, 4);
5.UnaryOperator<T>
代表:参数 T -> T,对T进行处理,并且返回同类型
示例:
UnaryOperator<Integer> addSelf = x -> x + 1;
System.out.println("1+1="+addSelf.apply(1));
6.BinaryOperator<T>
代表:参数(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.外部迭代到内部迭代
/**
* 计算字符串里面所有数字的和, 只考虑单一字符
*
* @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循环,每次迭代都必须写类似代码,如果多个for循环,还要考虑很多其他问题,如果要并行处理,就需要修改for循环逻辑
传统的for循环,或者Iterator叫做外部迭代。Java 8提供了流,进行内部迭代,代码如下:
/**
* 计算字符串里面所有数字的和
*
* @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))//过滤掉不是数字的字符
.mapToInt(c -> Character.digit(c, 10));//将所有字符转为数字
System.out.println(String.format("它们的和是[%d]", stream.sum()));
}
看起来貌似代码复杂多了,但是我们清晰的看出了所有要执行的“意图”:
1.查找所有字符
2.过滤掉不是数字的
3.将字符转为数字
4.求和
而for循环代码无法如此清晰的表达我们需要执行的动作,所以代码得简洁性得到提高。
惰性求值方法:在stream方法里面,像mapToObj这样的方法,只刻画了什么样的stream而不会去产生新集合,结果也只是返回一个新的stream
及早求值方法:像count这样会得到一个具体值,会对集合真正进行操作
所以上面代码,只有真正执行到count(及早求值方法)时候,才会去对集合操作,不会进行多次循环。
2.常用流操作
可以发现,流基本全部都是传入前面提到的函数接口
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是可比较的,如果没有实现Comparable会报错
6.Stream<T> sorted(Comparator<? super T> comparator);
代表:排序
7.Stream<T> peek(Consumer<? super T> action);
代表:对流进行处理的时候,可以对这些被处理的元素进行消费,而不影响新集合内容
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);
代表:最多拿取多少个源数据
上面代码加入:
stream.limit(2)
Filtered value: three
Filtered value: four
List size 2
9.void forEach(Consumer<? super T> action);
代表:循环遍历
10.Stream<T> skip(long n);
代表:对开始的n个数据忽略
上面代码加入:
stream.skip(1);
Filtered value: four
Filtered value: five
Filtered value: seven
List size 3
11.long count();
代表:获取新集合个数
12.boolean anyMatch(Predicate<? super T> predicate);
13.boolean allMatch(Predicate<? super T> predicate);
14.T reduce(T identity, BinaryOperator<T> accumulator);
代表:reduce 操作可以实现从一组值中生成一个值,如 count方法,max方法,min方法,都可以用reduce来实现。
书本练习题: 只用 reduce 和 Lambda 表达式实现 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.接口静态方法
上面代码,我们在使用stream时候,可以如下调用:
Stream<String> s = Stream.of("1", "2", "3", "4");
查看源码,发现Stream是接口,如下:
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
也就是说可以直接在接口里面定义我们自己的静态方法实现。
如果一个方法有充分的语义原因和某个概念相关,那么就应该将该方法和相关的类或接口放在一起,而不是放到另一个工具类中。这有助于更好地组织代码
2.接口default方法
解决场景(二进制接口的兼容性):
1.Java8中,对Collection 接口中增加了新的 stream()。
2.如果你在Java8 以前实现了自己的MyList继承了Collection接口,那么你在Java8运行时候会报错,因为以前的实现里面根本没有stream()
3.为解决如上问题,Java8新增default字段,标记默认实现方法Collection 接口告诉它所有的子类:“如果你没有实现 stream 方法,就使用我的吧。”接口中这样的方法叫作默认方法
public interface IStuff {
String getName();
double getPrice();
default void showSelf() {
System.out.println(String.format("[%s]%.02f", getName(), getPrice()));
}
}
//子类的实现不一定要有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 表达式经常调用参数,比如:
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** ,如果想调用构造函数则用new代替
参考:
《Java 8 函数式编程》