前言
目前java已经更新到15了,然鹅,我还在使用8以前的特性,所以为了以后的饭碗,咱还是得自学一下新特性。
Java8新增的几个重要特性
java8中新增了非常多的特性,其中需要主要学习的我认为有以下几个
- lambda表达式
- 方法引用
- Stream API
- Optional类
- 函数式接口
- 默认方法
下面我就来着重介绍一下他们。
Lambda表达式
相信有前端 ES6
基础的小伙伴都知道 lambda
是啥,没错,java8中也新增了这个特性。
总的来说lambda表达式就是一个语法糖,它可以极大的减少我们的代码量,但同时或许会给后续的维护工作带来一些困扰。
不同于 ES6
中的 =>
,在 Java
中 lambda
的语法格式如下
(parameters) -> expression
or
(parameters) ->{ statements; }
我们可以发现,在 Java
中符号变为了 ->
,这个也是需要我们注意的一点。
下面我们来看一个例子,首先,声明一个接口,之后我们用 Lambda
表达式来进行使用。
/**
* @作者: Seale
* @时间: 2020/10/15 0:43
* @说明: 使用lambda表达式
* @类名: LambdaTest
*/
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);
}
}
运行结果如下:
before:24
now:24
但值得注意的是,lambda
表达式只能引用标记了final的外层局部变量,反之那么就说明,在lambda表达域之内,我们不能修改在域外的局部变量(可以访问),否则会编译错误。
在 Lambda
表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
方法引用
方法引用是指通过一个方法的名字来指向它。
方法引用可以使得结构更紧凑和简洁,据我所知,在 BeetlSQL
这个持久化框架(类似于Mybatis)就大量使用到了方法引用来优雅的处理SQL。
方法引用使用的是 ::
一对冒号。
提供的可用来进行方法引用的例子。
/**
* @作者: Seale
* @时间: 2020/10/15 0:59
* @说明: 方法引用
* @类名: MethodReference
*/
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表达式。
函数式接口有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
同时,在Java8中,已经内置了几种常用的函数式接口:
- 消费型接口
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;
/**
* @作者: Seale
* @时间: 2020/10/15 0:59
* @说明: 方法引用
* @类名: MethodReference
*/
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
默认方法
默认方法是 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实例 |
boolean equals(Object obj) | 判断其他对象是否和Optional中的值相同 |
T get() | 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException |
boolean isPresent() | 如果值存在则方法会返回true,否则返回 false。 |
static |
返回一个指定非null值的Optional。否则抛出空指针异常 |
static |
如果为非空,返回 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
我们可以很明显的对比出来,顺序流和并行流的区别。
-
使用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
-
映射
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+" ")); }
-
排序
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,否则返回falsenoneMatch
:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回falseanyMatch
:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回falsefindFirst
:返回流中第一个元素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实例,将流中元素收集成为另外一个数据结构。