lambda表达式
说到了Lambda就不得不说函数式编程。在数学中函数f(x)
司空见惯,函数要求有输入量也有输出量并且还有表达式,简单地说就是“拿什么东西做什么事情最后输出我们想要的结果”。然而,在java中面向对象编程强调“必须通过对象的形式来做事情”,如此函数式编程与面向对象编程便区别了出来,前者强调“做什么”,后者强调“以什么形式做”。就好像有人说去日本,函数式编程就说一句去日本,而面向对象编程便要考虑是坐飞机去日本还是开船去日本。
lanmbda表达式就是函数式编程的一种体现,它强调的是做什么,而不讲究以什么形式去做。使得编程的关注点聚焦于方法。
lambda表达式的形式
在Java8以前的java代码中,经常使用内部匿名类的形式作为接口实现类对象参数
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("任务B执行了...");
}
}).start();
这样的代码看起来很繁琐,如果使用了Lambda后
new Thread(
() -> {
System.out.println("任务C执行了...");
}
).start();
从代码结构中就可以看出规律,lanmbda表达式的书写格式就是:
只需要按照匿名内部类的形式写出代码,然后保留抽象方法的()和{},在()和{}之间添加一个->
因此lambda表达式的组成
就是:() -> {},其中()中类型可以省略,要是只有一个参数()也可以省略;{}中如果只有一行语句,那么return和末尾分号都可以省略;->是绝对不能省略的。
lambda表达式的使用前提
- 使用lambda代替的是函数式接口抽象方法的实现,也就是要求接口中有且仅有一个抽象方法。
- 必须要有方法使用接口作为参数。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface ,专门用来检测是不是函数式接口
(重点!)常用函数式接口
1、Comparator:比较型接口,用于两个对象的比较,其中抽象方法compare
接受两个参数o1,o2,如果return o1 - o2表示升序,如果return o2 - o1表示降序(o1和o2都不是引用类型对象)
Person[] arr = new Person[]{
new Person("ttt",12),
new Person("qw",24),
new Person("rr",38),
new Person("cc",45),
new Person("uu",38),
};
// 匿名类写法
Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
// lambda标准写法
Arrays.sort(arr, (Person o1, Person o2) -> {
return o2.getAge() - o1.getAge();
});
// lambda简化写法
Arrays.sort(arr,(o1, o2) -> o1.getAge() - o2.getAge());
2、Conusmer:消费型接口,给他的抽象方法accept
一个参数,就叫消费一个对象。
public static void main(String[] args) {
String str = "Hello World";
// 给一个str对象消费掉
// 匿名内部类的形式
method(str, new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// lanmbda标准形式
method(str,(String s)->{
System.out.println("长度:" + s.length());
});
// lambda简化形式
method(str,s -> System.out.println("全部大写" + s.toUpperCase()));
}
public static void method(String str,Consumer<String> consumer) {
// 调用抽象方法
consumer.accept(str);
}
3、Predicate:判断型接口,返回一个boolean类型作为判断结果。
public static void main(String[] args) {
String str = "Helloworld";
// 匿名内部类对象的方式
method(str, new Predicate<String>() {
@Override
public boolean test(String s) {
//判断字符串长度是否大于5
return s.length()>5;
}
});
// lambda表达式的标准方式
method(str,(String s)->{
return s.length()>5;
});
// lambda表达式的简化形式
method(str,s->s.length()>5);
}
/**
* 定义方法,使用函数式接口Predicate作为参数
*/
public static void method(String str,Predicate<String> p){
boolean result = p.test(str);
System.out.println(result);
}
Stream流
Java8以后,引入了Stream,此流非彼流,它本质上强调的是工作流程,用于解决集合类中既有的弊端,比如在遍历集合的同时要做一些筛选过滤。
一、展示什么叫优雅
Stream综合练习
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
- 打印整个队伍的姓名信息。
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
Stream.concat(one.stream().filter(s -> s.length() == 3).limit(3)
,two.stream().filter(s -> s.startsWith("张")).skip(2))
.forEach(s-> System.out.println(s));
}
三行代码完成上面的需求!过程清晰明了!
二、获取Stream对象
1、通过Collection 集合
public default Stream<E> stream() 方法获取,因此Collection的实现类都能获取流对象。
- 注意:Map集合的实现类要通过Stream方式操作的话只能先获取key集合、value集合或者entry集合,通过Set集合来进行操作。
2、Stream接口的静态方法
static <T> Stream<T> of(T… values) ,可以传入数组来获取流对象。
三、Stream常用方法
1、filter方法
Stream <T> filter(Predicate predicate) : 按照方法参数predicate对Stream流对象中的元素进行过滤,并返回新的Stream流对象
2、limit方法和skip方法
Stream<T> limit(long n): 获取流对象中的前n个元素,返回新的Stream流对象
Stream<T> skip(long n): 跳过流对象中的前n个元素,返回新的Stream流对象
3、contact方法
static <T> Stream<T> concat(Stream<T> a, Stream<T> b): ,把两个流对象a和b合并成一个流对象并返回新的流对象
4、count方法
long count() : 终结方法,调用此方法后,Stream流对象将不可以继续使用
5、forEach方法
void forEach(Consumer<T> action): 终结方法,调用此方法后,Stream流对象将不可以继续使用
该方法接收一个Consumer接口函数
,会将每一个流元素交给该函数进行处理,不保证顺序.
使用过程可能抛出的异常
Exception in thread “main” java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.(AbstractPipeline.java:203)
at java.util.stream.ReferencePipeline.(ReferencePipeline.java:94)
at java.util.stream.ReferencePipeline$StatelessOp.(ReferencePipeline.java:618)
at java.util.stream.ReferencePipeline$2.(ReferencePipeline.java:163)
at java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:162)
at com.lambda.DemoFilter.main(DemoFilter.java:16)
这是重复使用了已经用过的Stream的对象而导致的问题,因为每个Stream的对象只能用一次。
总结:
1、终结方法:count、forEach,返回值都不是Stream对象,不能再链式调用
2、流对象只能被使用一次
总结
Lambda表达式简化了Java编程的复杂性,为函数式接口编程提供了一个面向函数编程的方式,不用再书写繁琐的匿名内部类。