上一篇文章:java通过行为参数化传递代码
上一篇文章讲述了什么叫做行为参数化,也描述了如何从一个变量最终抽象演化到了lambda表达式,这是一个循序渐进的过程,是通过一层一层的抽象实现的。所以延续上篇,本篇来特别详细的讲述一下lambda表达式的使用。
lambda表达式的组成
lambda表达式由三部分组成,分别是:
参数列表:参数列表里面放的是要进行操作的参数,和方法里面的参数是一样的。
箭头:就是这个:-> 它表示需要拿到参数列表里面的参数,然后进行后面的操作。
lambda主体:它是一个表达式,表示参数列表里面的参数在这里进行了什么操作,返回了什么内容。
小小的总结一下,其实它就是一个方法,有参数,有返回值。下面看几个例子,来熟悉一下lambda表达式
第一个例子我来展示全面的代码,顺便在复习一下上篇文章的内容-----什么叫做行为参数化
//创建一个函数式接口
public interface StringTestService {
public int test(String s);
}
//写一个方法来打印字符串的长度(省略class)
public static void printLength(StringTestService stringTestService){
String s = "123";
System.out.println(stringTestService.test(s));
}
//main方法中通过lambda表达式来调用上面的方法(省略class)
public static void main(String[] args) {
printLength((String s)->s.length());
}
上面就是行为参数化的写法啦(将代码作为参数进行传入),打印结果为3。其中下面lambda表达式的解释:
(String s)->s.length()
表示String类型的参数,返回一个int类型的值,没有return语句,因为lambda隐含了return,如果想要return,需要加上花括号{}。(改为 {return s.length();} 参考第三个例子)
第二个例子:
(Apple a)->a.getWeight()>50
该表达式参数为Apple类型,返回一个Boolean值(重量大于50的)
第三个例子:
(String s)->{return "hahaha";}
这是显示返回的写法,会加上花括号和return。
第四个例子:
()->{"hahhahha"}
这是没有参数,且返回值是String类型的lambda表达式。可以显示的写成下面的形式:
()->{return "hahhahha";}
lambda表达式实战
上面介绍了lambda表达式的组成,接下来说一下在什么场景下会使用lambda表达式------函数式接口
函数式接口就是只有一个抽象方法的接口,下面我们来看一下java中常见的函数式接口都有哪些:
Predicate
Predicate接口是java.util包下面的,里面有一个抽象方法test()参数为泛型,返回值为布尔值,我们可以用它来做一个集合中去除空字符串的方法,代码如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public class PredicateOperation {
public static <T> List<T> filter(List<T> list, Predicate<T> predicate){
List<T> result = new ArrayList<>();
list.forEach(item->{
if (predicate.test(item)){
result.add(item);
}
});
return result;
}
public static void main(String[] args) {
System.out.println(filter(Arrays.asList("", "123", "456"), (String s) -> !s.isEmpty()));
}
}
Consumer
java.util下的Consumer接口定义了一个抽象方法accept(),它接收参数是泛型T,但是没有返回值,是void方法。如果我们想访问某对象,并对其进行操作,可以采用该接口。比如可以用它来创建一个foreach方法,接收一个Integer类型的集合,并对其中的每个元素进行操作,代码如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
public class ConsumerOperation {
public static <T> void printContent(List<T> list, Consumer<T> consumer) {
list.forEach(item->{
consumer.accept(item);
});
}
public static void main(String[] args) {
printContent(Arrays.asList(1,3,4), (Integer i)-> System.out.println(
i
));
}
}
Function
java.util包下的Function接口定义了一个叫做apply的方法,它接收一个泛型T的对象,返回一个泛型R的对象,如果我们需要定一个lambda,将输入对象的信息映射到输出,可以使用该接口。比如我们可以做一个map方法,来打印每一个字符串的长度,代码如下:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
public class FunctionOperation {
public static <T, R> List<R> map(List<T> stringList, Function<T, R> function) {
List<R> result = new ArrayList<>();
stringList.forEach(item -> {
result.add(function.apply(item));
});
return result;
}
public static void main(String[] args) {
System.out.println(map(Arrays.asList("123", "11111", "hehehe"), (String s) -> s.length()));
}
}
lambda与局部变量
上面的lambda表达式都是使用的参数列表里面的参数,它还可以使用外层作用域中的变量,比如下面的代码就可以捕获参数列表外的参数port进行打印:
public static void main(String[] args) {
int port=123123;
Thread thread = new Thread(()-> System.out.println(port));
thread.start();
}
}
但是需要注意的是:lambda表达式只能捕获局部变量一次,也就是该局部变量必须声明为final,或者事实上为final(只赋予一次值),这是因为局部变量是存储在栈中的,而lambda是单独的一个线程,当lambda访问局部变量时,如果该局部变量所在的方法执行结束了,就释放栈帧,而这时,lambda所在线程就无法访问该局部变量,因为这根本是两个线程。所以使用final来修饰的时候,lambda实际访问的是该变量的副本。如下图,我再对port变量进行赋值的话,就会报错:
方法引用
如果一个lambda表达式是直接调用某个方法,那可以直接显示的指明该方法的名字,这样可以让我们的代码可读性更好。写法是:目标引用放在分隔符::前面,方法的名称放在后面。比如:
Apple::getWeight
上面代码的含义就是引用了Apple类中定义的方法getWeight,它也可以写成下面的形式,只是上面引用的方式看起来更加的简单明了:
(Appple a)->a.getWeight()
方法引用可以作用于静态方法和实例方法,下面我们敲一个demo来实战一下。
现在想要写一个方法来对集合里面的数字进行排序,在不使用stream流的情况下,可以用集合自带的sort方法,sort方法里面的参数是一个Comparator接口,该接口为函数式接口,里面有一个抽象方法compare,参数为两个同类型的泛型,返回值为int,我们可以初步来这样实现:
//sort方法源码如下:
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
//Comparator接口如下:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
//我们的第一种写法:
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 5, 2, 7);
list.sort((Integer a, Integer b) -> a.compareTo(b));
}
学习了方法引用之后,可以变成下面的方法,直接调用Integer的compareTo方法:
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 5, 2, 7);
list.sort(Integer::compareTo);
}
有人好奇他的参数类型在哪里判断的呢,这个java会根据上下文自动判断其参数类型,可以写也可以不写,比如第一种写法写成下面这样的也是没问题的:
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 5, 2, 7);
list.sort( (a,b) -> a.compareTo(b));
}
总结:本篇文章主要讲了lambda表达式的组成、使用方法、简化,也涉及到一些常见的java自带的函数式接口,我们可以在工作中尝试着来使用。其实我们对集合的操作大部分还是使用stream流,后面我会继续写有关stream流的文章,欢迎大家斧正。
参考资料----《java8实战》