lambda表达式
方法引用
Stream API
Optional类
函数式接口
默认方法
Lambda表达式
相信有前端 ES6基础的小伙伴都知道 lambda 是啥,没错,java8中也新增了这个特性。
总的来说lambda表达式就是一个语法糖,它可以极大的减少我们的代码量,但同时或许会给后续的维护工作带来一些困扰。
不同于 ES6 中的 => ,在 Java 中 lambda 的语法格式如下
(parameters) -> expression
or
(parameters) ->{ statements; }
Java 复制
我们可以发现,在 Java 中符号变为了 ->,这个也是需要我们注意的一点。
public class LambdaTest {
public static void main(String[] args) {
// 之前的隐式实现
Itest sumBefore = new Itest() {
@Override
public Integer calc(int a, int b) {
return a+b;
}
};
System.out.println("before:"+sumBefore.calc(12,12));
// 使用lambda表达式
Itest sum = (a,b)-> a + b;
System.out.println("now:"+sum.calc(12,12));
}
interface Itest{
Integer calc(int a,int b);
}
}
但值得注意的是,lambda 表达式只能引用标记了final的外层局部变量,
反之那么就说明,在lambda表达域之内,我们不能修改在域外的局部变量(可以访问),否则会编译错误。
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
方法引用
方法引用是指通过一个方法的名字来指向它。
方法引用可以使得结构更紧凑和简洁,据我所知,在 BeetlSQL 这个持久化框架(类似于Mybatis)就大量使用到了方法引用来优雅的处理SQL。
方法引用使用的是 :: 一对冒号。
提供的可用来进行方法引用的例子。
public class MethodReference {
public static void main(String[] args) {
ICalc sum = Integer::sum;
/*ICalc max = Integer::max;*/
System.out.println(sum.sum(12,15));
}
interface ICalc{
int sum(int a , int b);
/*int max(int a, int... nums);*/
}
}
方法引用主要有三类:
指向静态方法的方法引用,比如 Integer::parseInt
指向任意类型实例方法的方法引用,比如 String::length
指向现有对象的实例方法引用 this::hashCode
函数式接口
首先,我们需要知道函数式接口的作用,类似于我们常常使用的注解 @Override ,在我们声明的接口上方,我们需要先声明一个注解 @FunctionalInterface ,那么这个接口就可以被隐式的转换为lambda表达式。
函数式接口有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
消费型接口 Consumer<T>
供给型接口 Supplier<T>
函数型接口 Function<T,R>
断言型接口 Predicate<T>
消费型接口
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
接收一个参数进行消费,但是无需返回结果。
import java.util.function.Consumer;
public class MethodReference {
public static void main(String[] args) {
Consumer<String> consumer = (str)->System.out.println(str+"\t消费成功");
consumer.accept("女盆友1号");
consumer.accept("女盆友2号");
consumer.accept("女盆友3号");
}
}
女盆友1号 消费成功
女盆友2号 消费成功
女盆友3号 消费成功
供给型接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
返回一个结果
public static void main(String[] args) {
Supplier<String> t1Sl = ()->"男盆友";
Supplier<String> t2Sl = ()->"女盆友";
System.out.print(t1Sl.get()+"->");
System.out.println(t2Sl.get());
}
函数型接口
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
...
}
同样的,函数型接口需要我们提供一个参数,之后返回一个结果。
public static void main(String[] args) {
Function<String, Boolean> function = (s) -> {
System.out.println("当前名字:"+s+"\t执行了工作!");
return true;
};
boolean flag = function.apply("小王");
System.out.println("执行结果:" + flag);
}
断言型接口
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
...
}
传入一个参数,返回成功与否。
public static void main(String[] args) {
Predicate<String> pre1 = String::isEmpty;
Predicate<String> pre2 = (str)-> str.equals("测试");
boolean result1 = pre1.test("");
System.out.println("执行的结果:"+result1);
boolean result2 = pre2.test("测试1");
System.out.println("执行的结果:"+result2);
}
执行的结果:
执行的结果:true
执行的结果:false
Cmd 复制
默认方法
默认方法是 Java 8 中新增加的特性,简单来说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其中的接口方法。
public interface ITest{
default void print(){
System.out.println("测试默认方法");
}
// 接口的静态方法
static void staticPrint(){
System.out.print("测试的静态方法");
}
}
Optional 类对象
Optional类是一个可以为null的容器对象,如果存在值那么isPresent()方法会返回true,调用get()方法会返回该对象。
Optional类的引入很好的解决了空指针异常,使得我们在平时的开发中不用再去解决/处理空指针异常。
常用的方法
方法名 描述
static Optional empty() 返回空的Optional实例
boolean equals(Object obj) 判断其他对象是否和Optional中的值相同
T get() 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
boolean isPresent() 如果值存在则方法会返回true,否则返回 false。
static Optional of(T value) 返回一个指定非null值的Optional。否则抛出空指针异常
static Optional ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
T orElse(T other) 如果存在该值,返回值, 否则返回 other。
表格内容来自菜鸟教程
Stream(超级重要)
Stream是 JAVA 8 中处理集合的抽象概念,它可以让我们以一种声明的方式来处理数据。它用一种类似于SQL语句的方式来操作Java集合、进行集合运算。它是一种对Java集合操作运算的高阶抽象类。
这种操作将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,进行一些集合操作。
特性
不是数据结构,它不会保存数据。
不会修改原来对象的数据,它会将操作后的数据保存到另外一个对象中去。
惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止的时候才会进行实际的计算。
Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。这样可以对操作优化,比如延迟执行等。
内部迭代:在之前对集合遍历都是通过迭代器或者foreach的方式来进行的,显示的在集合外部进行迭代,这叫做外部迭代。而Stream提供了内部迭代的方式,通过访问者模式实现。
什么是Stream
Stream(流)是一个来自数据源的元素队列并且支持聚合操作。
元素是特定类型的独享,形成一个队列,Java中的流对象并不会存储元素,而是按需计算。
数据源是流的来源,可以是集合,数组,I/O通道,产生器等等。
聚合操作类似于SQL语句一样,如filter,map,reduce,find,match,sorted。
常用的创建方法
使用集合下的 stream() 和 parallelStream() 方法来获取流对象。
public class Stream1 {
public static void main(String[] args) {
List<String> strings = Arrays.asList("a","b","c","d","e","f","g");
Stream<String> stringStream = strings.stream(); //获取一个顺序流
Stream<String> parallelStream = strings.parallelStream(); //获得一个并行流
stringStream.forEach(System.out::print);
System.out.println();
parallelStream.forEach((s)->{
System.out.print(s + " ");
});
}
}
对于以上代码片段有如下结果
abcdefg
e d a f c g b
Cmd 复制
我们可以很明显的对比出来,顺序流和并行流的区别。
使用Stream中的静态方法来构建流对象
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream1 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream1.forEach((i) -> {
System.out.print(i + " ");
}); // 0 2 4 6 8 10 12 14 16 18
System.out.println();
Stream<Double> stream2 = Stream.generate(()-> Math.random() * 10 +1).limit(10);
stream2.forEach((d) -> {
System.out.print(d.intValue() + " ");
}); // 10个随机数
}
流的中间操作
筛选与切片操作
熟悉Python的同学就知道,在Python中可以对字典,集合,元组等数据结构进行一些pythonic风格的操作,其中切片操作最为经典。Java8 也支持类似的操作了。
filter:过滤流中的某些元素
limit(n):获取n个元素
skip(n):跳过n元素
distinct:通过流中元素的hashCode()和equals()去除重复元素
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(6, 15, 100, 258, 150, 0, 1, 2, 5, 4, 8, 9, 6, 3, 6, 9, 6, 165, 4189, 1, 911, 5614, 9, 1, 9, 4, 98, 4, 156, 16, 1);
Stream<Integer> newStream = stream.filter(i -> i > 5) //获取大于5的元素流对象
.distinct();//去重
newStream.forEach(i-> System.out.print(i + " "));
}
运行结果
6 15 100 258 150 8 9 165 4189 911 5614 98 156 16
Cmd 复制
映射
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
public static void main(String[] args) {
List<String> list = Arrays.asList("a,b,c","1,2,3");
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",",""));
s1.forEach(s -> System.out.print(s+" "));
Stream<String> s3 = list.stream().flatMap(s ->{
// 将每个元素转换成一个stream
String[] split = s.split(",");
return Arrays.stream(split);
});
s3.forEach(s->System.out.print(s+" "));
}
Java 复制
排序
sort:自然排序,流中元素需要实现Comparable接口
sorted(Comparator com):自定义排序
public static void main(String[] args) {
List<String> list = Arrays.asList("a","v","b");
list.stream().sorted().forEach(s -> System.out.print(s+" "));
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",19);
Student s3 = new Student("王五",20);
Student s4 = new Student("陈麻子",17);
List<Student> students = Arrays.asList(s1,s2,s3,s4);
students.stream().sorted(Comparator.comparingInt(Student::getAge)).forEach(System.out::println);
}
消费
peek:接收Consumer表达式,没有返回值。
public static void main(String[] args) {
List<String> list = Arrays.asList("a","v","b");
list.stream().sorted().forEach(s -> System.out.print(s+" "));
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",19);
Student s3 = new Student("王五",20);
Student s4 = new Student("陈麻子",17);
List<Student> students = Arrays.asList(s1,s2,s3,s4);
students.stream()
.peek(o -> o.setAge(o.getAge()+20))
.sorted(Comparator.comparingInt(Student::getAge).reversed())
.forEach(System.out::println);
// 在原有的基础上自增20,并进行倒序
}
流的终止操作
匹配/聚合
allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:返回流中第一个元素
findAny:返回流中的任意元素
count:返回流中元素的总个数
max:返回流中元素最大值
min:返回流中元素最小值
规约(reduce)
从一组值中生成一个新的值,reduce 函数其实用途非常广泛,作用也比较大,我们举一个累加的例子。
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5);
int sum = list.stream().reduce((before,now)->before+now).get();
System.out.println(sum);
}
collect:接收一个Collector实例,将流中元素收集成为另外一个数据结构。
评论区