文章目录
Java8 新特性
Java 8 是目前使用的最广的 jdk 版本,其中包含了很多新特性,这里,我们就来好好学习一下 java8 的新特性
这篇文章可能不会将 Java8 新特性介绍得那么全,因为这些知识点,都是我在企业型目中碰到的,都是一些比较常见的内容,可能会有部分不怎么常用的部分,我这里介绍不到
一、集合
1、Optional
1)Optional 介绍
Optional类实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional 类的引入很好的解决空指针异常。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。尽量避免在程序中直接调用Optional对象的get()和isPresent()方法,避免使用Optional类型声明实体类的属性。
Optional 和 对象的关系,就像是父母和小孩的关系:
如果小孩(对象)没有父母的管教,那这个小孩可能会光着屁股满街跑(空指针),引发不必要的麻烦
而有了父母(Optional)的管教,当小孩(对象)还想光屁股(空指针)的时候,就会被父母抓住,强行穿上裤子(伪造非空对象)
2)Optional 演示
我们来演示一下,Optional 遍历一个空的列表的时候,该怎么操作:
// 空的列表
ArrayList<Integer> list = null;
// Optional中包了一个空的列表,如果为空,我们就新建一个 ArrayList对象(orElse(new ArrayList<>())),然后遍历它,从而避免空指针
Optional.ofNullable(list).orElse(new ArrayList<>()).stream().forEach(System.out::println);
我们也可以抛出自定义异常:
ArrayList<Integer> list = null;
Optional.ofNullable(list).orElseThrow(()->new Exception("对象不能为空"));
3)Optional 方法介绍
3)Mybatis 使用 Optional
Mybatis 3.5 之后,支持返回 Optional 类型了
这样,我们就可以愉快的进行非空判断了
传统写法:
@Mapper
public interface UserMapper {
@Select("select * from user where id = #{id}")
User selectById(Long id);
}
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
User user = this.userMapper.selectById(id);
if(user == null) {
// 抛异常,或者做点其他事情(麻烦)
}
}
}
Optional 写法:
@Mapper
public interface UserMapper {
@Select("select * from user where id = #{id}")
Optional<User> selectById(Long id);
}
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
return this.userMapper.selectById(id)
.orElseThrow(() -> new IllegalArgumentException("This user does not exit!"));
}
}
二、Lambda 表达式
Java 8 的一大更新,就是 Lambda 表达式,只是 java 中的 lambda 表达式是阉割版的
java 中的 lambda 表达式,只能用在 函数式接口 上,即这个接口只能有一个方法
下面,我们就来具体看一下 lambda 表达式的使用
1、无参函数式接口
无参函数式接口,我们最熟悉的,就是 Runnable
接口了
// 这个注解,表示是一个函数式接口,可有可无
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
我们平时使用的时候,是这样的:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("哈哈哈");
}
}
}).start();
使用匿名内部类,我们就不用再显示的写一个类,继承 Runnabe
接口,重写 run()
方法
但是,使用 lambda
表达式,我们可以更加简洁的编写上面的代码:
new Thread( () -> {
for (int i = 0; i < 10; i++) {
System.out.println("哈哈哈");
}
}).start();
省去了被重写的方法名和 @Override
注解
如果是单行的实现,我们甚至可以省略大括号:
// 我们自定义函数式接口,和调用这个函数式接口的函数
@FunctionalInterface
interface MyInterface {
int getNum();
}
public static void judgeNum(MyInterface myInterface) {
if (myInterface.getNum()==10) {
System.out.println("不错,获得了我想要的数");
} else {
System.out.println("不是我想要的数");
}
}
// 使用这个函数
judgeNum(() -> 10); // 不错,获得了我想要的数
这里,除了单行实现不需要大括号以外,我们还要注意一点,就是单行的实现,不能显示的写 return
,因为如果有返回值的话,是默认 return 的
2、带参函数式接口
如果我们的函数式接口带参,是要在括号中加上参数就好
区别在于,括号中加上的参数,不需要加上参数类型,因为程序会自动进行推断
带参函数式接口的最好的例子,就是 Comparator
了
不使用 lambda
表达式的话,我们是这么实现的:
List<Integer> list = Arrays.asList(3, 5, 4, 2, 4, 6, 7);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
使用 lambda
,是这么实现的:
List<Integer> list = Arrays.asList(3, 5, 4, 2, 4, 6, 7);
Collections.sort(list,(o1,o2) -> o1-o2);
可以发现,代码量是大大减少了
我们讲 lambda
,不仅是为了偷懒,更重要的是,接下来要介绍的 java8 流式编程,是需要依靠 lambda 表达式的。
二、流式编程
流式编程是 Java8 中比较重要的一个部分
使用流式编程,之前很多 for 循环的遍历,都可以大大缩减
**把大象关进冰箱有三步:**打开冰箱,把大象放进冰箱,关上冰箱
**流式编程也是三步:**开启流,中间操作,结束操作
这么说,可能有人要骂街了,那我们就用一个实际的例子来讲一讲:
需求:筛选出 List
中元素为 1 的数,并进行收集
List<Integer> list = Arrays.asList(3, 2, 2, 4, 21, 1, 341, 1);
1、开启流
list.stream.<这里是中间操作>.<这里是结束操作>;
2、中间操作
list.stream().filter(elem -> elem==1).<这里是结束操作>;
3、结束操作
list.stream().filter(elem -> elem==1).collect(Collectors.toList());
下表,汇总了 stream
接口的常见方法:
1、获得流
要想进行流式编程,我们起码要先获得流
获得流有两个方式
- 使用
stream().of()
方法 - 调用集合的
stream()
方法
// 方式1:
Stream<Integer> stream = Stream.of(3, 2, 2, 4, 21, 341);
// 方式2:
List<Integer> list = Arrays.asList(3, 2, 2, 4, 21, 341);
list.stream();
2、中间操作
中间操作有不少,这里,我介绍几个比较常见的中间操作
1)distinct()
去重
List<Integer> list = List.of(2, 4, 2, 1, 4, 5, 8, 1, 1, 1, 1);
list = list.stream().distinct().collect(Collectors.toList());
System.out.println(list);
2)filter()
过滤
按照我们需要的内容,对流进行过滤
List<Integer> list = List.of(2, 4, 2, 1, 4, 5, 8, 1, 1, 1, 1);
// 只要流中为 1 的元素
list = list.stream().filter(obj -> obj==1).collect(Collectors.toList());
System.out.println(list);
3)map()
将数据转换为我们希望的形式
// 将字符串全部转换为大写
List<String> list = List.of("abc", "def", "ghi");
list = list.stream().map(obj -> obj.toUpperCase()).collect(Collectors.toList());
System.out.println(list);
4)sorted()
元素排序
需要我们自己传入一个比较器
List<Integer> list = List.of(2, 4, 2, 1, 4, 5, 8, 1, 1, 1, 1);
list = list.stream().sorted((o1,o2) -> o1-o2).collect(Collectors.toList());
System.out.println(list);
剩下的不是太常用,但是使用方法和上面的是大同小异的,如果有需要的话,可以去翻阅相关文档。
3、结束操作
结束操作,我们主要介绍一个 forEach()
和 collect()
1)forEach()
forEach()
会遍历流中的所有元素,
我们一般用这个方法,快读打印元素信息
List<Integer> list = List.of(2, 4, 2, 1, 4, 5, 8, 1, 1, 1, 1);
// System.out::println 是调用 System.out 类中的 println 方法
// 通过这个方法,可以调用该类中的静态方法
// 格式是 类名::方法名
list.stream().forEach(System.out::println);
2)collect()
collect
的操作,可以变得十分复杂,可以通过下游收集器,实现类似于 SQL 的操作,但是,在一般业务中,我们不太可能会有如此离谱的需求,因为这些事都交给 SQL 去做了
这里,我们熟悉一下怎么通过收集器,将流变为 集合 即可
我们最常用的操作,是将流变为 List 和 Set:
List<Integer> list = List.of(2, 4, 2, 1, 4, 5, 8, 1, 1, 1, 1);
List<Integer> myList = list.stream().collect(Collectors.toList());
Set<Integer> mySet = list.stream().collect(Collectors.toSet());
这两个方法,返回的是一个接口,我们并不知道底层实现
如果想指定底层实现,我们需要这么写:
List<Integer> list = List.of(2, 4, 2, 1, 4, 5, 8, 1, 1, 1, 1);
// 我们指定底层实现是 ArrayList 和 HashMap
List<Integer> myList = list.stream().collect(Collectors.toCollection(ArrayList::new));
Set<Integer> mySet = list.stream().collect(Collectors.toCollection(HashSet::new));