文章目录
Lambda表达式
1.出现的背景
java是面向对象的语言,除了部分简单数据类型,java中的一切都是对象,即使是数组也是一种对象,每个类创建的实例都是对象.在java中定义的方法不可能完全独立,也不可能将方法作为一个参数或作为返回值给其他方法来使用.
在java 8以前,我们要想把某些功能传递给某一个方法,总是需要去写一个(匿名)内部类.
public class Car {
private int num;
private String name;
public Car(int num, String name) {
this.num = num;
this.name = name;
}
public int getNum() {
return num;
}
@Override
public String toString() {
return "Car{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Test {
public static void main(String[] args) {
Car car0 = new Car(100, "宝马0");
Car car1 = new Car(101, "宝马1");
Car car2 = new Car(102, "宝马2");
Car car3 = new Car(103, "宝马3");
Car car4 = new Car(104, "宝马4");
ArrayList<Car> arrayList = new ArrayList<>();
Collections.addAll(arrayList, car2,car4,car1,car3,car0);
//将比较功能传递给sort方法
Collections.sort(arrayList, new Comparator<Car>() {
@Override
public int compare(Car o1, Car o2) {
return o1.getNum()-o2.getNum();
}
});
//按照升序输出集合
System.out.println(arrayList);
}
}
在上面的例子里,为了对集合进行排序,我们为Comparator接口创建了它的一个匿名内部类对象,重写接口中的方法,来实现排序的功能.
简而言之,在java里将普通的方法像参数一样传值并不简单,为此,java 8增加了一个语言级的新特性—Lambda表达式.
2.Lambda简介
Lambda表达式是一个匿名函数,我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样可以传递),使用它可以写出更简洁,更灵活的代码.作为一种更紧凑的代码风格,使java语言的表达能力得到提升.
Lambda表达式的本质是一个"语法糖’',由编译期推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能.
3.Lambda表达式的结构
Lambda表达式可以有零个,一个或者多个参数.
可以显式声明参数的类型,也可以由编译器自动从上下文推断参数的类型.例如(int a,int b)与(a,b)相同.参数用小括号括起来,用逗号分割.空括号表示一组空的参数.
当且仅有一个参数的时候,如果不显式指明类型,则不必使用小括号.例如a -> return a*a.
Lambda表达式的正文可以包含零条,一条或多条语句.如果只有一条语句,那么大括号可以不用写(推荐不论什么情况都加上花括号),表达式的返回值类型要与匿名函数的返回类型相同.
java中的Lambda表达式通常用**(argument) -> {body}**语法书写.
比如我们上面的代码可以更改一下:
//因为Comparator接口被@FunctionalInterface标注(功能函数接口),因此可以使用Lambda表达式表示
Collections.sort(arrayList, ((o1, o2) ->{
return o1.getNum()-o2.getNum();
}));
这样就更为简单快捷了.我们自己创建的接口也是可以使用Lambda表达式来进行简化语句的.
public interface Demo {
/*
要是这个接口能够使用Lambda表达式来表示,接口中只允许有一个抽象方法
*/
//无参且无返回值的方法
void show();
}
public class Test {
public static void test(Demo demo){
//如果我们调用了test方法,test方法调用了接口中的show方法,需要重写这个方法
demo.show();
}
public static void main(String[] args) {
Test.test(new Demo() {
@Override
public void show() {
System.out.println("重写了接口中的show方法");
}
});
}
}
我们必须确定我们的接口中只有一个抽象方法才能使用,如果有两个抽象方法,我们使用Lambda表达式的时候会报错.即使两个方法的参数不相同也不可以.
Test.test(()->{
System.out.println("重写的show方法");
});
4.功能接口FunctionalInterface
**Lambda表达式只支持函数式接口.也就是只有一个抽象方法的接口.功能接口是java 8中的新增功能,他们只允许一个抽象方法.这些接口也称为单抽象方法接口.java 8也引入了一个注释,即@FunctionalInterface,**当你的接口违反了它的契约(也就是说不是仅有一个抽象方法),他可以用于编译期级错误.
一个最简单的,我们之前提到过的线程,如果只是一个简单的功能,那我们没有必要直接去写一个线程.直接使用Lambda表达式即可:
public class ThreadDemo {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}).start();
}
}
Stream
1.什么是Stream
Stream是java 8的新特性,它允许你以声明式的方式处理数据集合,不需要我们自己写for循环进行遍历等操作.可以把他看做是遍历数据集的高级迭代器.此外与stream与lambda结合后编码效率可以大大提高,并且可读性也更强.
要清楚的是这里的stream与我们之前IO讲到的InputStream和OutputStream是完全不同的两个概念.
那到底什么是流.简单的定义,就是**“从支持数据处理操作的源生成的元素序列.”**
元素列表:和集合一样,流也提供了一个接口,访问特定元素类型的一组有序值.
数据源:获取数据的源,比如集合.
数据处理操作:流更偏向于数据处理和计算.比如filter,map,find,sort等.
简单来说,我们通过一个数据集合的stream方法获取一个流,然后对这个流进行一系列的流操作.最后再构成我们需要的数据集合.
2.如何获取流
要利用stream对数据集合进行操作,第一步一定是获取流.我们之前学过的数据集合暂时来说只有两个(数组和集合).
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;
//获取流的方法
public class Demo {
public static void main(String[] args) {
//使用Collection接口下的Stream()
//Collection接口定义了Stream方法,意味着List集合与Set集合都实现了这个方法
ArrayList<Integer> arrayList = new ArrayList<>();
Stream<Integer> stream = arrayList.stream();
//使用Arrays类中的Stream方法,将数组转换为流
Integer[] a = new Integer[3];
Stream<Integer> stream1 = Arrays.stream(a);
//我们也可以直接把元素当做参数传入方法中
//of方法的参数是可变长度参数
Stream<Integer> stream2 = Stream.of(1,2,3,6,5,8);
}
}
这里一定要注意.我们的数组里面应该存储的是引用类型的数据.
3.如何对流进行操作
我们对流进行操作都是声明式的,我们只要说明想完成什么(筛选,排序,取值),而不是说明如何实现一个操作(利用for循环或者迭代器遍历),我们将这些操作连接起来,达到一种流水线式的效果.
流操作可以分为两类:**中间操作和终端操作.**终端操作的作用是将流关闭,构建新的数据集合对象(也可以不构建).
import java.util.Arrays;
public class Demo1 {
public static void main(String[] args) {
Integer[] integers = {9,5,1,3,7,8,2,6,4};
Arrays.stream(integers)//获取流
.distinct()//去重
.filter((integer) -> {return integer < 6;})//对流进行过滤,只需要小于6的元素,返回一个流
.sorted((a,b)->{return a-b;})//对流进行排序,返回一个流
.limit(4)//只需要取出4个元素
.skip(1)//跳过第一个元素
.forEach((integer) -> {
System.out.println(integer);
});//对这个流遍历输出
}
}
上面就是简单的对数组这种数据集合进行流操作.不需要我们写for循环来实现这些,我们只需要声明即可.
我们看看常用的中间操作还有终端操作都有哪些
常用的中间操作:
filter();//过滤流中的某些元素
sorted();//自然排序,流中的元素须实现Comparable接口
distinct();//去除重复元素
limit(n);//获取n个元素
skip(n);//跳过n个元素,配合limit可以实现分页
map();//将其映射成为一个新的元素
常用的终端操作:
forEach();//遍历流中的元素
toArray();//将流中的元素传入一个数组
collect();//将流中的元素传入一个集合
Min();//返回流中元素的最小值
Max();//返回流中元素的最大值
count();//返回流中元素的总个数
reduce();//所有元素求和
anyMatch();//接收一个Predicate函数,只要流中有一个元素满足条件返回true,否则返回false
allMatch();//接收一个Predicate函数,流中所有元素满足条件返回才true,否则返回false
findFirst();//返回流中的第一个元素
对于对流的操作的一些小问题,我们通过几个代码示例可以轻松搞定.
import java.util.Arrays;
public class Demo2 {
public static void main(String[] args) {
Integer[] integers = {9,5,1,3,7,8,2,6,4};
Integer integer = Arrays.stream(integers)
.distinct()
.max((a, b) -> { return a - b; }) //max方法返回的是一个Optional对象
.get();//调用get方法来得到我们想要的最大值
System.out.println(integer.intValue());
}
}
import java.util.Arrays;
public class Demo3 {
public static void main(String[] args) {
Integer[] integers = {9,5,1,3,7,8,2,6,4};
Arrays.stream(integers)
.reduce((a,b)->{return a+b;})
.get();//依旧需要调用get方法
boolean b = Arrays.stream(integers)
.anyMatch((integer)->{return integer > 10;});
System.out.println(b);
}
}
当然这些就是比较简单的,我们通过看源码或者查阅API结合Lambda表达式是完全可以掌握的.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
public class TestCar {
public static void main(String[] args) {
Car car1 = new Car(101, "蓝色", "宝马1");
Car car2 = new Car(102, "黑色", "宝马2");
Car car3 = new Car(103, "白色", "宝马3");
Car car4 = new Car(104, "绿色", "宝马4");
Car car5 = new Car(105, "红色", "宝马5");
ArrayList<Car> arrayList = new ArrayList<>();
Collections.addAll(arrayList,car2,car4,car3,car5,car1);
Map<Integer,String> map =
arrayList.stream()
.sorted((c1,c2)->{return c1.getNum()-c2.getNum();})
//利用车的号码以及颜色构成一个键值对,将所有对象封装为一个map集合
.collect(Collectors.toMap(Car::getNum,Car::getColor));
System.out.println(map);
//注意我们中间操作的map与集合的map并不一样,这个map是一个映射的意思
Object[] nums = arrayList.stream()
.map(Car::getName)
.toArray();
//这个数组存储了所有car对象的num信息
System.out.println(Arrays.toString(nums));
}
}