JDK13于昨天正式GA,版本新特性可参考: https://www.oschina.net/news/109934/jdk-13-released
虽然JDK更新迅速,但开发者貌似并不买账,据统计,目前仍以JDK8使用最多,预计可能还会延续好长一段时间。虽然JDK版本已至13,但对Java8的新特性,掌握程度如何呢?
本文对Java8的主要特性进行了梳理。供温习参考。
以前的接口只允许有抽象方法(没有实现体),java8中提供了接口默认方法支持,即可以提供方法的默认实现,实现类可以直接继承,也可以覆盖。默认方法主要解决接口的修改导致现有实现类不兼容的问题。
@RunWith(SpringRunner.class) @SpringBootTest public class InterfaceDefaultFunctionTest { public interface MyFunction<T> { T func(T t); //默认方法 default int func2(T t){ return t.hashCode(); } //静态方法 static<T> void print(T t) { System.out.println(t); } } @Test public void testInterface(){ MyFunction<String> myFunction = new MyFunction<String>(){ @Override public String func(String s) { return s.toUpperCase(); } }; System.out.println(myFunction.func("abc")); System.out.println(myFunction.func2("abc")); LambdaTest.MyFunction.print("efg"); } }
默认方法通过关键字 default 声明。同时也可以在接口中定义静态方法。
函数式接口就是有且仅有一个抽象方法的接口(可以有其它非抽象方法),如1所示代码中 MyFunction 就是一个函数式接口,只有一个抽象方法 func, 其它非抽象方法如默认方法 func2, 静态方法 print 不影响其函数式接口的特性。
函数式接口可以使用注解 @FunctionalInterface 标注,该注解会去检查接口是否符合函数式接口的规范要求,不符合的话IDE会给出提示。
java中内置了一些函数式接口,
函数式接口 | 描述 |
---|---|
Consumer
|
包含方法 void accept(T t), 对类型为T的对象t进行操作 |
Supplier
|
包含方法 T get(),返回类型为T的对象 |
Function<T,R> | 包含方法 R apply(T t),对类型为T的对象进行操作,返回类型R的对象 |
Predicat
|
包含方法 boolean test(T t), 判断类型为T的对象是否满足条件 |
以及基于这些接口的其它变种或子接口,如BiConsumer<T,U>,BiFunction<T,U,R>等。还有如Runnable,Callable等接口,也属于函数式接口 —— 都只有一个抽象方法。
@FunctionalInterface public interface BiConsumer<T, U> { void accept(T t, U u); default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) { Objects.requireNonNull(after); return (l, r) -> { accept(l, r); after.accept(l, r); }; } }
lambda表达式实质就是一个匿名函数,在python中很常见,java到了jdk8提供了支持。
lambda表达式的格式形如: (参数) -> {方法体语句},当参数只有一个时,左边小括号可以省略,当方法体语句只有一条时,右边大括号可以省略。
Java的lambda表达式基本上是对函数式接口实现的一种简化 —— 用lambda表达式直接代替一个函数式接口的具体实现(抽象方法的实现)。当我们使用jdk8在IDE中编写1中代码时,IDE会给出提示,
匿名实现类可以用lambda表达式替换。上述代码使用lambda表达式替换可调整为,
@Test public void testInterface(){ MyFunction<String> myFunction = s -> s.toUpperCase(); System.out.println(myFunction.func("abc")); System.out.println(myFunction.func2("abc")); }
lambda表达式甚至可作为方法参数传入(实质也是作为一个函数式接口的实现类实例)
@FunctionalInterface public interface MyFunction<T> { T func(T t); } public void print(MyFunction<String> function, String s){ System.out.println(function.func(s)); } @Test public void testInterface(){ //将lambda表达式作为方法参数传入 print((String s) -> s.toUpperCase(), "abc"); }
局部变量在lambda表达式中是只读的,虽可不声明为final,但无法修改。如
@Test public void testInterface(){ int i = 1; //lambda表达式中无法修改局部变量i,将报编译错误 print((String s) -> {i = i+10; return s.toUpperCase();}, "abc"); }
当需要使用lambda表达式时,如果已经有了相同的实现方法,则可以使用方法引用来替代lambda表达式,几种场景示例如下。
@RunWith(SpringRunner.class) @SpringBootTest public class FunctionReferenceTest { @Test public void testFunctionReference() { // 实例::实例方法 Consumer<String> consumer = s -> System.out.println(s); //lambda表达式 Consumer<String> consumer2 = System.out::println; //方法引用 consumer.accept("abc"); consumer2.accept("abc"); //类::静态方法 Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y); //lambda表达式 Comparator<Integer> comparator2 = Integer::compare; //方法引用 System.out.println(comparator.compare(10, 8)); System.out.println(comparator2.compare(10, 8)); //类::实例方法, 当引用方法是形如 a.func(b)时,用类::实例方法的形式 BiPredicate<String, String> biPredicate = (a, b) -> a.equals(b); //lambda表达式 BiPredicate<String, String> biPredicate2 = String::equals; //方法引用 System.out.println(biPredicate.test("abc", "abb")); System.out.println(biPredicate2.test("abc","abb")); //type[]::new 数组引用 Function<Integer,Integer[]> fun= n-> new Integer[n]; //lambda表达式 Function<Integer,Integer[]> fun2=Integer[]::new; //方法引用 System.out.println(fun.apply(10)); System.out.println(fun2.apply(10)); //构造器引用 Function<String,String> func = n-> new String(n); //lambda表达式 Function<String,String> func2 = String::new; //方法引用 System.out.println(func.apply("aaa")); System.out.println(func2.apply("aaa")); } }
Stream与lambda应该是java8最重要的两大特性。Stream 对集合的处理进行了抽象,可以对集合进行非常复杂的查找、过滤和映射等操作。提供了一种高效的且易于使用的处理数据的方式。
Stream的三个特性:
Java8 的Collection接口包含了两个方法 stream(), parallelStream(), 分别返回一个顺序流与一个并行流,所有Collection类型(如List, )的对象可以调用这两个方法生成流。
Java8 的Arrays类也提供了 stream(T[] array)等方法用以生成流。也可以使用静态方法 Stream.iterate() 和 Stream.generate() 来创建无限流。
Stream的中间操作包括
操作 | 描述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中过滤出满足条件的元素 |
distinct() | 通过hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流,使元素的个数不超过给定数量 |
skip(long n) | 跳过前面的n个元素,若流中元素不足n个,则返回一个空流 |
map(Function f) | 将每个元素使用函数f执行,将其映射成一个新的元素 |
mapToDouble(ToDoubleFunction f) | 将每个元素使用f执行,产生一个新的DoubleStream流 |
mapToInt(ToIntFunction f) | 将每个元素使用f执行,产生一个新的IntStream流 |
mapToLong(ToLongFunction f) | 将每个元素使用f执行,产生一个新的LongStream流 |
flatMap(Function f) | 将流中的每个值都通过f转换成另一个流,然后把所有流连接成一个流 |
sorted() | 按自然顺序排序,产生一个新流 |
sorted(Comparator comp) | 根据比较器排序,产生一个新流 |
allMatch(Predicate p) | 判断是否匹配所有元素 |
anyMatch(Predicate p) | 判断是否匹配至少一个元素 |
noneMatch(Predicate p) | 判断是否没有匹配任意元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回任意一个元素 |
reduce(T iden, BinaryOperator b) | 对流中的元素进行reduce操作,返回T类型对象 |
reduce(BinaryOperator b) | 对流中的元素进行reduce操作,返回Optional
|
Stream的终止操作包括
操作 | 描述 |
---|---|
count() | 返回元素总数 |
max(Comparator c) | 返回最大值 |
min(Comparator c) | 返回最小值 |
forEach(Consumer c) | 内部迭代调用Consumer操作 |
collect(Collector c) | 将流转换为其他形式,一般通过Collectors来实现 |
Stream使用示例
@Test public void testStream() { List<User> list = new ArrayList<>(); //转换为List,这里没啥意义,仅做示范 List<User> users = list.stream().collect(Collectors.toList()); //转换为Set Set<User> users1 = list.stream().collect(Collectors.toSet()); //转换为Collection Collection<User> users2 = list.stream().collect(Collectors.toCollection(ArrayList::new)); //计数 long count = list.stream().collect(Collectors.counting()); //求和 int total = list.stream().collect(Collectors.summingInt(User::getAge)); //求平均值 double avg= list.stream().collect(Collectors.averagingInt(User::getAge)); //获取统计对象,通过该统计对象可获取最大值,最小值之类的数据 IntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(User::getAge)); //将值通过","拼接 String str= list.stream().map(User::getName).collect(Collectors.joining(",")); //最大值 Optional<User> max= list.stream().collect(Collectors.maxBy(Comparator.comparingInt(User::getAge))); //最小值 Optional<User> min = list.stream().collect(Collectors.minBy(Comparator.comparingInt(User::getAge))); //从累加器开始,对指定的值,这里是年龄,进行sum的reduce操作 int t =list.stream().collect(Collectors.reducing(0, User::getAge, Integer::sum)); //对转换的结果再进行处理 int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); //分组 Map<String, List<User>> map= list.stream().collect(Collectors.groupingBy(User::getName)); //根据条件进行分区 Map<Boolean,List<User>> vd= list.stream().collect(Collectors.partitioningBy(u -> u.getName().startsWith("W"))); }
Optional是一个容器类,可以避免显式的null判断,基本使用示例如下
@RunWith(SpringRunner.class) @SpringBootTest public class OptionalTest { @Test public void testOptional(){ // of 不允许传入null值,否则抛出NPE Optional<Integer> optional = Optional.of(new Integer(10)); System.out.println(optional.get()); // ofNullable 允许传入null,但是直接调用get会抛出NoSuchElementException异常, // 可通过isPresent判断是否存在值 Optional<Integer> optional1 = Optional.ofNullable(null); if(optional1.isPresent()) { System.out.println(optional1.get()); }else{ System.out.println("optional1 is empty"); } // orElse 判断是否存在值,存在则返回,不存在则返回参数里的值 Integer value = optional1.orElse(new Integer(0)); // map方法,如果optional有值,则对值进行处理返回新的Optional, // 如果没有值则返回Optional.empty() optional = optional.map(x -> x*x); System.out.println(optional.get()); // 与map类似,只是要求返回值必须是Optional,进一步避免空指针 optional = optional.flatMap(x ->Optional.of(x*x)); System.out.println(optional.get()); } }
在java8中,Base64成为了java类库的标准,可直接使用
import java.util.Base64; @RunWith(SpringRunner.class) @SpringBootTest public class Base64Test { @Test public void testBase64(){ //base64编码 String encode = Base64.getEncoder().encodeToString("abc".getBytes()); System.out.println(encode); //base64解码 System.out.println(new String(Base64.getDecoder().decode(encode))); } }
以前的Date类是非线程安全的,并且一些常用的日期时间运算需要自己编写util工具类。java8推出了java.time包,里面包含了如 LocalDate, LocalTime, LocalDateTime等类,可方便地进行日期时间的运算,如日期间隔、时间间隔,日期时间的加减,格式化等等。
—————————————————————————————
作者:空山新雨
欢迎关注我的微信公众号:jboost-ksxy