Java8基础及其在项目中的使用场景
Plan
Java 基础知识提升:
- 熟悉Java8特性
- 总结出Java8在小西项目中的使用场景
Do
第一个任务:熟悉Java8特性
Java8提供了新特性来提高开发效率。
- Lambda表达式(也称为闭包)
- 函数式接口
- 方法引用与构造器引用
- Stream API
- 接口的增强:接口的默认方法与静态方法
- 新的日期时间 API:Date Time API
- Optionl 类
- IO/NIO 的改进
其中,引用最广泛的新特性是Lambda表达式和Stream API。
tip:
过了一天后,导师发现我Java8新特性看的有点慢,给我画了重点。
我们项目中经常使用的是Stream API、Lambda表达式(可以私下敲代码巩固)、Optional的ofNullable()方法,知道DateTime API是如何使用的,其他的了解即可如:函数式接口
因此本此只学上面所说重点,记录顺序如下:
- Lambda表达式(也称为闭包)
- 函数式接口
- Stream API
- 新的日期时间 API:Date Time API
- Optionl 类
1. Lambda表达式(也称为闭包)
Lambda表达式是一个匿名函数,,很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,而是使用匿名内部类来代替Lambda表达式。
我们来看一个例子:比较两个Integer类型数据的大小
匿名内部类的实现:
//内部实体类
Comparator<Integer> com = new Comparator<Integer>() {
@Overridepublicintcompare(Integer o1, Integer o2){
return Integer.compare(o1, o2);
}};
//接下来将上述匿名内部类的实例作为参数,传递到其他方法中
TreeSet<Integer> treeSet = new TreeSet<>(com);
Lambda表达式的实现:
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
上面整个匿名内部类中,真正有用的就是下面一行代码。
return Integer.compare(o1, o2);
其他的代码本质上都是“冗余”的。但是为了书写上面的一行代码,我们不得不在匿名内部类中书写更多的代码。
而Java8引入Lambda表达式真是为了解决这种"冗余"现象,实现简洁而紧凑的语言结构。
Lambda 表达式形式:
一个最基本的 Lambda 表达式,它由三部分组成:包含一个 Lambda 表达式的运算符 ->,在运算符的左边是输入参数,右边则是函数主体。
参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:
如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来:
Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的,且不可以改变值),例如下列两个代码块的效果完全相同:
和
不可以修改局部变量:
Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量:
2. 函数式接口
如果一个接口中,**只声明了一个抽象方法,**则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口。
而Lambda表达式的本质: 作为函数式接口的实例,也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
函数式接口有下面几个特点:
- 接口有且仅有一个抽象方法;
- 允许定义静态方法;
- 允许定义默认方法;
- 允许 java.lang.Object 中的 public 方法;
- 推荐使用 @FunctionInterface 注解(如果一个接口符合函数式接口的定义,加不加该注解都没有影响,但加上该注解可以更好地让编译器进行检查)。
例子如下:
@FunctionalInterface
interface TestFunctionalInterface
{
//抽象方法
public void doTest();
//java.lang.Object中的public方法
public boolean equals(Object obj);
public String toString();
//默认方法
public default void doDefaultMethod(){
System.out.println("call dodefaultMethod");}
//静态方法
public static void doStaticMethod(){
System.out.println("call doStaticMethod");}
public static void main(String...s){
//实现抽象方法
TestFunctionalInterface test = ()->{
System.out.println("call doTest");
};
//调用抽象方法
test.doTest();
//调用默认方法
test.doDefaultMethod();
//调用静态方法
TestFunctionalInterface.doStaticMethod();
//调用toString方法
System.out.println(test.toString());
}
}
JDK 8 之后新增了一个函数接口包 java.util.function 这里面包含了我们常用的一些函数接口:
例子:
@Test
public void test2(){
List<String> list = Arrays.asList("北京","南京","天津","东京","西京","普京");
List<String> filterStrs = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(filterStrs);
List<String> filterStrs1 = filterString(list,s -> s.contains("京"));
System.out.println(filterStrs1);
}
//根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre){
ArrayList<String> filterList = new ArrayList<>();
for(String s : list){
if(pre.test(s)){
filterList.add(s);
}
}
return filterList;
}
3. Stream API
Java 8 引入了流式操作(Stream),通过该操作可以实现对集合(Collection)的并行处理和函数式操作。
学习流式操作,就是学习java.util.stream包下的API,我们称之为Stream API,它把真正的函数式编程引入到了 Java 中。
根据流的并发性,流又可以分为串行和并行两种,而流式操作可实现集合的过滤、排序、映射等功能。Stream通过串行、并行的方式处理大批量数据,提高处理效率。
根据操作返回的结果不同,流式操作分为中间操作和最终操作两种。
中间操作只对操作进行了记录,即只会返回一个流,不会进行计算操作,这样就可以将多个操作依次串联起来;而终结操作是实现了计算操作,返回一特定类型的结果。
我们开发一般是用Stream API通过Lambda表达式对集合进行各种非常便利高效的大批量数据操作。
案例如下:
// java.util.Collection
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
@Data
class Student {
private Integer height;
private String sex;
}
Map<String, List<Student>> map = Maps.newHashMap();
List<Student> list = Lists.newArrayList();
// 传统的迭代方式
for (Student student : list) {
if (student.getHeight() > 160) {
String sex = student.getSex();
if (!map.containsKey(sex)) {
map.put(sex, Lists.newArrayList());
}
map.get(sex).add(student);
}
}
// Stream API,串行实现
map = list.stream().filter((Student s) -> s.getHeight() > 160).collect(Collectors.groupingBy(Student::getSex));
// Stream API,并行实现
map = list.parallelStream().filter((Student s) -> s.getHeight() > 160).collect(Collectors.groupingBy(Student::getSex));
Tips:
Stream 和 Collection 集合的区别:
Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
注意点:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
Stream 的操作三个步骤
1- 创建 Stream
一个数据源(如:集合、数组),获取一个流
2- 中间操作
一个中间操作链,对数据源的数据进行处理
3- 终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
创建 Stream方式,常用中间操作,常用终止操作,请参考文章:Java 流式操作
4. Date Time API
Java8之前的 Date Time API有如下缺点:
- 可变性:旧的 API 日期和时间是可变的,日期和时间这样的类应该是不可变的;
- 偏移性:Date 中的年份是从 1990 年开始,月份是从 0 开始,星期天是用 1 表示的,不了解 API 的开发者很容易用错;
- 格式化:SimpleDateFormat 只能用于格式化 Date 类型,不能格式化 Calendar 类型。
Java8 Date Time API针对于以上问题改进如下:
- 在java.time 包中,提供了更优秀易用的 API。包含了 LocalTime(本地时间)、LocalDate(本地日期)、LocalDateTime(本地日期时间)、ZonedDateTime(带时区的日期时间)和 Duration(时间间隔)类。
- 在java.util.Date 类下面增加了 toInstant() 方法,用于把 Date 转换为新的类型
- 月份使用 1~12 表示 1 月 到 12 月,星期使用 1 ~ 7 表示星期一到星期天。
- 使用了新的 DateTimeFormatter 来取代旧的 SimpleDateFormat。
案例如下:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* @author colorful@TaleLin
*/
public class LocalDateTimeDemo1 {
public static void main(String[] args) {
// 获取当前日期
LocalDate localDate = LocalDate.now();
// 获取当前时间
LocalTime localTime = LocalTime.now();
// 获取当前日期和时间
LocalDateTime localDateTime = LocalDateTime.now();
// 打印
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
}
}
运行结果:
2020-06-10
14:17:48.618294
2020-06-10T14:17:48.618421
在实际开发中,LocalDateTime 相较于 LocalDate 和 LocalTime 更加常用
本地日期和时间通过 now() 获取到的总是以当前默认时区返回的。
另外,可以使用 of() 方法来设置当前日期和时间:
// 2020-9-30
LocalDate date = LocalDate.of(2020, 9, 30);
// 14:15:10
LocalTime time = LocalTime.of(14, 15, 10);
// 将date和time组合成一个LocalDateTime
LocalDateTime dateTime1 = LocalDateTime.of(date, time);
// 设置 年、月、日、时、分、秒
LocalDateTime dateTime2 = LocalDateTime.of(2020, 10, 21, 14, 14);
5. Optionl 类
Java应用中最常见的bug就是空值异常,使用Optionl 可以避免这个问题。
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在,或者仅仅保存null,表示这个值不存在。它提供了一些有用的接口来避免显式的null检查。
Optional常用的使用场景:空判断。
空判断主要是用于不知道当前对象是否为NULL的时候,需要设置对象的属性。不使用Optional时候的代码如下:
if(null != order){
order.setAmount(orderInfoVo.getAmount());
}
使用Optional时候的代码如下:
Optional.ofNullable(order).ifPresent(o -> o.setAmount(orderInfoVo.getAmount()));
参考文章:Java函数式编程之Optional
但注意一个事情,Optional.ofNullable()只能判断是否为null这一种情况,不能用于判断字符串是否为空字符串(对于字符串的判空,极不建议使用optional 来实现,因为字符串的空包含了多种情况:null 、“null” 、“”、“ ” 所以还是建议老老实实用StrUtil.isBlank方法对于对象的判空,还是用处挺大的)
第二个任务:总结出Java8在小西项目中的使用场景
1.Lambda表达式
2.Stream API
小西中,Lambda表达式和Stream API大多数情况下会结合使用。
如清理空库存方法:
从数据库中获取到商品列表后,把它转成流对象并进行遍历,并把流对象里面的每个数据的属性MallConstants变为ENABLE_FALSE,再更新数据库.
@Override
public void cleanEmptyStockTask() {
List<GoodsSku> goodsSkuList = baseMapper.selectList(Wrappers.<GoodsSku>lambdaQuery().eq(GoodsSku::getStock, 0));
goodsSkuList.stream().forEach(goodsSku -> {
if (goodsSku.getEnable().equals(MallConstants.ENABLE_TRUE)) {
goodsSku.setEnable(MallConstants.ENABLE_FALSE);
baseMapper.updateById(goodsSku);
}
});
3.DateTime API
小西中主要用在签到,更新会场日期,日期工具类。
LocalDateTime 相较于 LocalDate 和 LocalTime用的会多一些。
4.Optional的ofNullable()方法
主要用在判断对象是否为空