Java8新特性
lambda表达式
在JDK8之前,一个方法能接受的参数都是变量,例如: object.method(Object o)
那么,如果需要传入一个动作呢?比如回调。
那么你可能会想到匿名内部类。
//传统方式:
interface Printer{
public void printer(integer id);
}
class Operation(){
public static void create(integer id,Printer printer){
printer.printer(id);
}
}
class demo{
public static void main(String[] args) {
Operation operation=new Operation();
int id=1;
//匿名内部类
operation.create(i,new Printer(){
public void printer(integer id){
System.out.println("id:"+id);
}
});
}
}
Printer其实就是一种动作,但是我们真正关心的只有printer方法里的内容而已,我们用Lambda
表示,可以将上面的代码就可以优化成
//Lambda
public static void main(String[] args) {
Operation operation=new Operation();
int id=1;
operation.create(id, x->System.out.println("id:"+x));
}
如果我们的接口方法定义不带任何参数,则可以用空括号替换:
()-> System.out.println("anything you wan to print")
注意:
- 即使没有在箭头的左侧指定参数的类型,编译器也会从接口方法的形式参数中推断出其类型
- 当只有一个参数的时候,我们完全可以省略参数的括号
- 当函数体只有一行的时候,我们完全可以省略函数体花括号
函数式接口
函数式接口是新增的一种接口定义。
用**@FunctionalInterface修饰的接口叫做函数式接口**,或者,函数式接口就是一个只具有一个抽象方法的普通接口,@FunctionalInterface可以起到校验的作用。
Java8中提供给我们这么多函数式接口就是为了让我们写Lambda表达式更加方便,当然遇到特殊情况,你还是需要定义你自己的函数式接口然后才能写对应的Lambda表达式。
总的来说,如果没有函数式接口,就不能写Lambda表达式。
接口的默认方法和静态方法
在接口中用default修饰的方法称为默认方法。
接口中的默认方法一定要有默认实现(方法体),接口实现者可以继承它,也可以覆盖它。
default String methodDefault(String s) {
System.out.println("lol");
}
方法引用
-
实例对象::实例方法名
-
类名::静态方法名
-
类名::实例方法名
-
引用构造方法
-
引用数组
// 引用方法1 实例对象::实例方法名 // System.out代表的就是PrintStream类型的一个实例,println是这个实例的一个方法 // Consumer<String> consumer1 = s -> System.out.println(s); Consumer<String> consumer2 = System.out::println; consumer2.accept("呵呵"); // 引用方法2 类名::静态方法名 // Function中的唯一抽象方法apply方法参数列表与abs方法的参数列表相同,都是接收一个Long类型参数。 // Function<Long, Long> f = x -> Math.abs(x); Function<Long, Long> f = Math::abs; Long result = f.apply(-3L); // 引用方法3 类名::实例方法名 // 若Lambda表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,就可以使用这种方法 // BiPredicate<String, String> b = (x,y) -> x.equals(y); BiPredicate<String, String> b = String::equals; b.test("a", "b"); // 引用构造方法 // 在引用构造方法的时候,构造方法参数列表要与接口中抽象方法的参数列表一致,格式为 类名::new // Function<Integer, StringBuffer> fun = n -> new StringBuffer(n); Function<Integer, StringBuffer> fun = StringBuffer::new; StringBuffer buffer = fun.apply(10); // 引用数组 // 引用数组和引用构造器很像,格式为 类型[]::new,其中类型可以为基本类型也可以是类 // Function<Integer, int[]> fun = n -> new int[n]; Function<Integer, int[]> fun1 = int[]::new; int[] arr = fun1.apply(10); Function<Integer, Integer[]> fun2 = Integer[]::new; Integer[] arr2 = fun2.apply(10);
-
实例对象::实例方法名
引入了stream流(管道流)
Stream 是在 Java8 新增的特性,普遍称其为流;它不是数据结构也不存放任何数据,其主要用于集合的逻辑处理。Stream是对集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者打批量数据操作
这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选、排序、聚合等。
和以前的collection操作不同,Stream操作还有两个基础的特征:
Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,比如延迟执行和短路。
内部迭代:以前对集合遍历都是通过Iterator或者ForEach的方式,显示的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,通过访问者模式实现。并行流parallelStream
Stream管道流的基本操作
- 源操作:可以将数组、集合类、行文本文件转换成管道流Stream进行数据处理
- 中间操作:对Stream流中的数据进行处理,比如:过滤、数据转换等等
- 终端操作:作用就是将Stream管道流转换为其他的数据类型。这部分我们还没有讲,我们后面章节再介绍。
管道的功能包括:Filter(过滤)、Map(映射)、sort(排序)等,集合数据通过Java Stream管道处理之后,转化为另一组集合或数据输出。
List<String> nameStrs = Arrays.asList("Monkey", "Lion", "Giraffe","Lemur");
List<String> list = nameStrs.stream()
.filter(s -> s.startsWith("L"))
.map(String::toUpperCase)
.sorted()
.collect(toList());
System.out.println(list);
-
首先,我们使用Stream()函数,将一个List转换为管道流
-
调用filter函数过滤数组元素,过滤方法使用lambda表达式,以L开头的元素返回true被保留,其他的List元素被过滤掉
-
然后调用Map函数对管道流中每个元素进行处理,字母全部转换为大写
-
然后调用sort函数,对管道流中数据进行排序
-
最后调用collect函数toList,将管道流转换为List返回
将数组转换为管道流
使用Stream.of()方法,将数组转换为管道流。
String[] array = {"Monkey", "Lion", "Giraffe", "Lemur"}; Stream<String> nameStrs2 = Stream.of(array); Stream<String> nameStrs3 = Stream.of("Monkey", "Lion", "Giraffe", "Lemur");
将集合类对象转换为管道流
通过调用集合类的stream()方法,将集合类对象转换为管道流。
List<String> list = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur"); Stream<String> streamFromList = list.stream(); Set<String> set = new HashSet<>(list); Stream<String> streamFromSet = set.stream();
将文本文件转换为管道流
通过Files.lines方法将文本文件转换为管道流,下图中的Paths.get()方法作用就是获取文件,是Java NIO的API!
也就是说:我们可以很方便的使用Java Stream加载文本文件,然后逐行的对文件内容进行处理。
Stream<String> lines = Files.lines(Paths.get("file.txt"));
参考:具体可以参考这个写的很详细,很棒
Stream总结
不是数据结构,它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素 所有 Stream 的操作必须以 lambda 表达式为参数 惰性化,很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始,Intermediate操作永远是惰性化的 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的
Date/Time API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。对日期与时间的操作一直是Java程序员最痛苦的地方之一。标准的 java.util.Date以及后来的java.util.Calendar一点没有改善这种情况(可以这么说,它们一定程度上更加复杂)。
这种情况直接导致了Joda-Time——一个可替换标准日期/时间处理且功能非常强大的Java API的诞生。Java 8新的Date-Time API (JSR 310)在很大程度上受到Joda-Time的影响,并且吸取了其精髓。
LocalDate类
/**
* LocaleDate只持有ISO-8601格式且无时区信息的日期部分
*/
public static void testLocaleDate() {
LocalDate date = LocalDate.now(); // 当前日期
date = date.plusDays(1); // 增加一天
date = date.plusMonths(1); // 减少一个月
date = date.minusDays(1); // 减少一天
date = date.minusMonths(1); // 减少一个月
System.out.println(date);
}
LocalTime类
/**
* LocaleTime只持有ISO-8601格式且无时区信息的时间部分
*/
public static void testLocaleTime() {
LocalTime time = LocalTime.now(); // 当前时间
time = time.plusMinutes(1); // 增加一分钟
time = time.plusSeconds(1); // 增加一秒
time = time.minusMinutes(1); // 减少一分钟
time = time.minusSeconds(1); // 减少1秒
System.out.println(time);
}
LocalDateTime类
/**
* LocaleDateTime把LocaleDate与LocaleTime的功能合并起来,它持有的是ISO-8601格式无时区信息的日期与时间
*/
public static void testLocalDateTime() {
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // UTC格式
System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 自定义格式
// 原有方法
// Date nowDate = new Date();
// System.out.println(nowDate);
// System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").format(nowDate));
}
ZoneDateTime类
/**
* ZonedDateTime持有ISO-8601格式,具有时区信息的日期与时间。
*/
public static void testZonedDateTime() {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
System.out.println(zonedDatetimeFromZone);
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
}
Clock类
/**
* 它通过指定一个时区,然后就可以获取到当前的时刻,日期与时间。
* Clock可以替换System.currentTimeMillis()与TimeZone.getDefault()
*/
public static void testClock() {
Clock utc = Clock.systemUTC(); // 世界标准时间
System.out.println(LocalDateTime.now(utc));
Clock shanghai = Clock.system(ZoneId.of("Asia/Shanghai")); // 上海时间
System.out.println(LocalDateTime.now(shanghai));
}
Duration类
/**
* Duration使计算两个日期间的不同变的十分简单。
*/
public static void testDuration() {
final LocalDateTime from = LocalDateTime.parse("2019-07-15 18:50:50", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
final LocalDateTime to = LocalDateTime.parse("2019-07-16 19:50:50", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
final Duration duration = Duration.between(from, to);
System.out.println("Duration in days: " + duration.toDays()); // 1
System.out.println("Duration in hours: " + duration.toHours()); // 25
}