概述
Java8 是自 java5之后最重要的版本,这个版本包含语言、编译器、库、工具、JVM等方面的十多个新特性。
Jdk8新增的特性如下:
- Lambda表达式 类似于ES6中的箭头函数
- 接口的默认方法和静态方法
- 新增方法引用格式
- 新增Stream类
- 新的日期API,Datetime,更方便对日期的操作
- 引入Optional,在SpringData中使用较多,然后再通过get获取值,可以防止空指针异常
- 支持Base64
- 注解相关的改变
- 支持并行(parallel)数组
- 对并发类(Concurrency)的扩展。
- JavaFX
一:接口新特性
1、接口默认方法
接口中的方法默认都是抽象方法,在jdk8之前,每个实现这个接口的类都要重写所继承接口的所有方法。
假如某个框架中对一个接口增加一个新的方法,那么实现这个接口的所有类都不得不重写接口中的方法,显然这不是我们想要的。
在jdk8中提供了 default 关键字,可以对接口中的方法进行修饰,使用 default 关键字修饰的方法子类不一定要去实现,但必须要有自己的方法体
public interface UserService {
default void test(){
System.out.println("测试接口中方法的默认实现");
}
}
2、接口静态方法
在接口中,可以使用 static 修饰方法,该方法必须要有自己的实现,且只能通过 接口名.方法名
进行调用,不能通过实现类进行调用,实现类不可以重写该方法
public interface UserService {
static void test(){
System.out.println("这是接口中的静态方法");
}
}
3、函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口,可以有默认方法和静态方法。
而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
可以通过@FunctionalInterface
注解对接口进行代码规范
@FunctionalInterface 注解与 @Override 一样,都只是在编译期起作用,所谓编译期就是你加不加都是不影响最终的运行效果,但你加了之后,代码会显得更加清晰。
@FunctionalInterface
public interface UserService {
void test();
}
二:lambda表达式
语法
三要素:参数、箭头、代码
格式:(参数类型 参数1, 参数类型 参数2…) – > {代码}
- 如果参数有多个,那么使用逗号分隔。如果参数没有,则留空
- 箭头是固定写法
- 大括号相当于方法体。
Lambda 省略规则
- 参数类型可以省略。但是只能同时省略所有参数的类型,或者同时都不省略。
- 如果参数有且仅有一个,那么小括号可以省略。
- 如果大括号内的语句有且仅有一条,那么无论是否有返回值,return、大括号、分号都可以省略
Lamdba对函数式接口的省略demo
- 无参数的情况
@Test
public void testRunnable(){
new Thread(() -> System.out.println("开启了子线程")).start();
}
- 一个参数没返回值的情况
@Test
public void test1(){
interOneImpl("ssss",str -> System.out.println(str));
}
private void interOneImpl(String str,InterOne interOne){
interOne.printStr(str);
}
@FunctionalInterface
interface InterOne{
void printStr(String str);
}
- 多个参数有返回值的情况
@Test
public void test2(){
// 参数类型要么都省略,要么都不省略
interTwoImpl(5,8,(a,b) -> a+b );
}
private void interTwoImpl(int a,int b,InterTwo interTwo){
int i = interTwo.addNum(a, b);
System.out.println("执行结果:"+i);
}
@FunctionalInterface
interface InterTwo{
int addNum(int a,int b);
}
延迟加载
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
性能浪费的案例
public class Demo{
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
Lambda的优化写法
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
// ................
public class DemoLambda {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, () -> msgA + msgB + msgC );
}
}
这样一来,只有当满足条件的时候才会进行三个字符串的拼接。否则不会拼接。
三:Stream流操作
1、 获取Stream流的方式
java.util.stream.Stream 是Java 8新加入的流接口。(不是一个函数式接口) 通过里面的默认方法 stream 可以获取流,所以其实现类均可获取流。
- 所有的 Collection 集合都可以通过 stream 默认方法获取流;
- Stream 接口的静态方法 of 可以获取数组对应的流。
// list
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
// set
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
// Map,需要分key、value或entry等情况
Map<String,String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
// 数组,通过静态方法Stream.of()获取
String[] strings = {"111","222"};
Stream<String> stream3 = Stream.of(strings);
2、常用方法
forEach、filter、map、count、limit、skip、concat
public static void main(String[] args) {
List<String> list = getList();
// 遍历:forEach
list.stream().forEach(System.out::println);
// 过滤:filter,根据返回Boolean决定是否返回元素
// 获取以张开头的,并打印
list.stream().filter(e ->e.startsWith("张"))
.forEach(System.out::println);
// map:将流中的元素映射到另一个流中,返回要映射的元素
Stream<String> streamString = list.stream().map(e -> e.substring(1));
streamString.forEach(System.out::println);
// 统计个数
// long count = streamString.count();
// System.out.println(count);
System.out.println("--------------------------------");
// 分页,limit(取几个)、skip(跳过几个)
// 每页两个数据,第二页
Stream<String> page = list.stream().skip(1*2).limit(2);
page.forEach(System.out::println);
System.out.println("----------------------------------");
// Stream.concat:合并流
List<String> list1 = new ArrayList<>();
list1.add("111");
list1.add("222");
List<String> list2 = new ArrayList<>();
list1.add("333");
list1.add("444");
Stream<String> concat = Stream.concat(list1.stream(), list2.stream());
concat.forEach(System.out::println);
}
public static List<String> getList(){
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张三丰");
list.add("周芷若");
list.add("赵敏");
list.add("杨过");
return list;
}
附:JDK1.8中提供的函数式接口
Supplier
java.util.function.Supplier :无参有返回值。
抽象方法:T get():用来获取一个泛型参数指定类型的对象数据。
// 通过Supplier接口实现字符串拼接
@Test
public void test1(){
String str1 = "Hello";
String str2 = "World";
String res = test1Handler(() -> str1 + str2);
System.out.println(res);
}
private String test1Handler(Supplier<String> supplier){
return supplier.get();
}
// 使用Supplier接口实现求数组中的最小值
@Test
public void test2(){
int[] nums = {7,3,22,15,1,85,89};
int res = test2Handler(() -> {
int min = nums[0];
for (int num : nums) {
if (num < min) {
min = num;
}
}
return min;
});
System.out.println(res);
}
private int test2Handler(Supplier<Integer> supplier){
return supplier.get();
}
Consumer
java.util.function.Consumer:有参无返回值。 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据。
抽象方法:void accept(? t),意为消费一个指定泛型的数据
默认方法:Consumer<?> andThen(Consumer<?> t),执行完A的accept方法,然后再执行B的accept方法,类比迭代执行
// 打印格式化信息
public static void handle(Consumer<String> one,Consumer<String> two,String[] arr){
for(String item:arr){
// 执行完1的accept方法,然后再执行B的accept方法
one.andThen(two).accept(item);
}
}
public static void main(String[] args) {
String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
handle(e -> System.out.println("姓名" + e.split(",")[0]),
e -> System.out.println("性别:"+e.split(",")[1]), array);
}
Predicate
java.util.function.Predicate:有参有返回值。
抽象方法:boolean test(T t):有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。
默认方法:Predicate and(Predicate p)、Predicate or(Predicate p)、Predicate negate(Predicate p)
// 通过Predicate实现一个字符串中以"好"开头,并且包含"坏"字
public static boolean handler(Predicate<String> one,Predicate<String> two,String str){
// 两个为true,才返回true
return one.and(two).test(str);
}
public static void main(String[] args) {
String str = "好xxx坏反反复复";
boolean res = handler(e->e.startsWith("好"), e->e.contains("坏"), str);
System.out.println(res);
}
Function
java.util.function.Function<T,R>:有参有返回值。用来根据一个类型的数据(T)得到另一个类型的数据(R)。
抽象方法:R apply(T t),接受类型为T的参数,返回类型为R的参数。
默认方法:Function<T,R> andThen(Function<T,R> f);
// 1. 将字符串截取数字年龄部分,得到字符串;
// 2. 将上一步的字符串转换成为int类型的数字;
// 3. 将上一步的int数字累加100,得到结果int数字。
public static int handler(String str,Function<String,String> one,Function<String,Integer> two,Function<Integer,Integer> three){
return one.andThen(two).andThen(three).apply(str);
}
public static void main(String[] args) {
String str = "张三,18";
int res = handler(str,e->e.split(",")[1],e->Integer.parseInt(e),e->e+100);
System.out.println(res);
}