JDK8中部分新特性
- 对Lambda表达式的支持
- 方法引用
- 集合中加入了新的方法stream()
- 其他的还有接口加入了默认方法
- 解决空指针异常的Optional 类
- 新的日期时间工具类
其他的特性我这里基本不怎么用,所以也没有罗列出来,毕竟自己也没多少理解,下面详细说一下上面这几种特性
一、lambda表达式
要使用lambda,参数类型必须为函数式接口,所以从函数式接口说起
函数式接口
函数式接口要求接口中只有一个抽象方法,可以使用@FunctionalInterface注解表示。和@Override类似,可以不写,但建议标注上。
以List集合的foreach方法举例(不是增强for,是jdk8中新增的方法)
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);//这个就是唯一的抽象方法
//这个方法为1.8中另一个新特性,默认方法
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
javaAPI中包含很多的函数式接口,可以自行查看,另:感谢菜鸟教程,很好的新手入门网站
Lambda表达式
lambda表达式包含三个部分:
- 形式参数:用括号包裹,单个参数可以省略括号;参数类型可以省略
- 符号:->
- 方法体:多行用大括号包裹,单行可以省略
举三个栗子:
()->System.out.println(“没有参数~~”);
param-> System.out.println(param);
(String param)->{
String s = “两行代码了”;
System.out.println(param);
}
- 用法一:替换匿名类,还是以经典的线程创建启动为例,new Thread()可以接收一个Runnable接口作为参数,先看Runnable接口的源码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
很明显这是一个函数式接口,并且方法抽象方法是没有参数的,那么我们可以编写测试
@Test
public void test(){
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("曾经我们这么写");
}
}).start();
new Thread(() -> System.out.println("现在我们一行写完了,6不6")).start();
}
没有玩过的小朋友,可以尝试自己定义一个多参数的函数式接口,然后直接使用Lambda表达式写接口的实现
- 用法二:最常使用的迭代集合(此处标记重点),用的非常多,先来个list集合的栗子
先跟踪源码会发现List接口继承Collection接口,Collection接口又继承Iterable接口,我们看Iterable接口的源码:
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
OK,可以看到,源码中存在一个forEach方法,上面我们已经看过Consumer接口,为一个函数式接口,接下来我们就可以编写测试了
public void testList(){
//先初始化一个list集合
List<String> list = Arrays.asList("java好","java妙","java呱呱叫");
//曾经我们使用增强for循环
for(String s : list){
System.out.println("java怎么样?回答:"+s);
}
//我们先不用Lambda的话foreach方法使用
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("java怎么样?回答:"+s);
}
});
//最后再来看,函数式接口用Lambda表达式实现
list.forEach(s->System.out.println("java怎么样?回答:"+s));
}
啊,一行代码实现上面两种方式的,就是这么嗨皮,最后顺手来个Map的遍历看看
@Test
public void testMap(){
//顺手写个Map的测试吧
Map<String,String> m = new HashMap<>();
m.put("1","java好");
m.put("2","java妙");
m.put("3","java呱呱叫");
m.forEach((k,v)->System.out.println(k+v););
}
二、方法引用
直接上个栗子,简单明了
@Test
public void test(){
//先初始化一个list集合
List<String> list = Arrays.asList("java好","java妙","java呱呱叫");
//正常的输出
list.forEach(s-> System.out.println(s));
//方法引用
list.forEach(System.out::println);
//应用到Stream的复杂一点栗子
list.stream().map(s->s.toUpperCase()).forEach(s-> System.out.println(s));
list.stream().map(String::toUpperCase).forEach(System.out::println);
}
没错,就是这么简单粗暴,不需要参数信息,Java编译器在为该方法引用生成实例时,会进行自动地将集合中的元素作为参数传入到该方法中
三、stream方法
到这里我们使用Lambda大部分还都是在集合的foreach方法中,那如果有一些复杂点的业务,比如需要先将结合中的元素排个序然后再一次输出,或者把集合中包含某个字母的元素全部都找出来,并且输出,那怎么办,这时候stream的强大之处就体现出来了。
集合接口有两个方法来生成流:stream() − 为集合创建串行流。parallelStream() − 为集合创建并行流,字面就可以理解,一个是顺序执行,一个是多线程并发执行,接下来的测试都采用stream() 即可
先来看一下Stream接口中提供的一些方法,已加入注释
public interface Stream<T> extends BaseStream<T, Stream<T>> {
//通过设置的条件过滤出元素,只有符合条件的才会筛选出来
Stream<T> filter(Predicate<? super T> predicate);
//对元素做一些如求长度、转换大小写等操作后返回,下面是对应了一大堆数据类型
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
//很好理解,去重
Stream<T> distinct();
//排序方法
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
//监控方法
Stream<T> peek(Consumer<? super T> action);
//用于获取指定数量的流
Stream<T> limit(long maxSize);
//skip(n)为跳过前n个数据
Stream<T> skip(long n);
//这个我们很熟悉,遍历
void forEach(Consumer<? super T> action);
void forEachOrdered(Consumer<? super T> action);
//转为数组
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
T reduce(T identity, BinaryOperator<T> accumulator);
//进行归约操作
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
//Collector类提供了很多的归约方法
<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
//最大最小值
Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);
//得到总数
long count();
//判断流中是否存在至少一个元素满足指定的条件
boolean anyMatch(Predicate<? super T> predicate);
//判断流中的所有元素是否都满足指定条件
boolean allMatch(Predicate<? super T> predicate);
//用于判断流中的所有元素是否都不满足指定条件
boolean noneMatch(Predicate<? super T> predicate);
//获取第一个元素findFirst
Optional<T> findFirst();
//从流中随便选一个元素出来
Optional<T> findAny();
...
}
接下来使用代码来看下stream的强大之处
@Test
public void test(){
//先初始化一个list集合
List<String> list = Arrays.asList("java好","java妙","java呱呱叫","JDK8顶呱呱");
//筛选出以java开头的第一个元素,Optional在后面有详细的解释
System.out.println(list.stream().filter(s -> s.startsWith("JDK")).findFirst().get());
System.out.println("------------------------------");
//获取每个元素的长度
list.stream().map(String::length).forEach(System.out::print);
System.out.println("\r\n------------------------------");
//获取每个元素的长度并取得总长度值,最大值
System.out.println("总长度:"+list.stream().mapToInt(String::length).sum());
System.out.println("最大值:"+list.stream().mapToInt(String::length).max().getAsInt());
System.out.println("------------------------------");
//查找打印集合中最长的字符串(相同取第一个)
System.out.println("归约找最长:"+list.stream().reduce((x,y)->x.length()>=y.length()?x:y).get());
System.out.println("归约找最长含有默认:"+list.stream().reduce("一只超级长的默认值",(x,y)->x.length()>=y.length()?x:y));
System.out.println("------------------------------");
//将集合中的元素以逗号分隔组合成一个串打印
System.out.println("reduce逗号分隔结果:"+list.stream().reduce((x,y)-> x+","+y).get());
//使用collect方法
System.out.println("collect逗号分隔结果:"+list.stream().collect(Collectors.joining(",")));
//String.join方法
System.out.println("String.join:"+String.join(",",list));
//全部都计算长度后用逗号分隔拼接
System.out.println("计算长度后逗号分隔结果:"+list.stream().map(s->s.length()+"").reduce((x,y)->x+","+y).get());
//计算长度并排序后逗号分隔拼接
System.out.println("计算长度并排序后逗号分隔结果:"+list.stream().map(s->s.length()+"").sorted().reduce((x,y)->x+","+y).get());
}
这个栗子比较多,所以把执行结果一并放上来看下
四、默认方法
曾经我们的接口中只能有定义不能有实现,也就是说接口中的方法全部都是抽象方法,子类需要全部实现,而到了1.8这里,多出来了个默认方法,这样接口只要在方法上加上default关键字,那么接口也可以实现方法了,并且子类不需要实现这个方法。
很好理解,简单写个栗子吧
public interface DefaultInterFace{
//这个还是普通的抽象方法,子类必须实现
public void oldMethod();
//这个就是默认方法了子类可以不实现,也可以重写
public default void defaultMethod(){
Sysout.out.println("我是在接口中的实现,6不6");
}
}
五、Optional
这个怎么解释,看下API吧
public final class Optional
extends Object
A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.
Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present).
This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.
好吧,应该有人和我一样,英文渣渣,所以,google or baidu or 其他 翻译一下吧。
第一段就是说,这是一个可以包含null值得容器对象,若值存在, isPresent()将返回true,get()方法返回对应的值
第二段大致说,提供了其他的判断值存不存在的方法,如:orElse() (如果值不存在则返回默认值)和ifPresent() (如果值存在则执行代码块)。
第三段大致就是,Optional使用(==,标识哈希码,或同步)可能不可预测的实例结果,应该避免
使用Optional可以很好的避免空指针异常,下面看个栗子
@Test
@Test
public void test(){
//ofNullable允许传入null
Optional<Integer> x = Optional.ofNullable(null);
Optional<Integer> y = Optional.ofNullable(2);
//of方法如果传入null,会抛出空指针
Optional<Integer> z = Optional.of(3);
//做求和操作,因为x为null,所以需要处理成0
Integer a = x.orElse(0);
Integer b = y.orElse(0);
Integer c = z.get();
System.out.println("x存在值:"+x.isPresent()+";y存在值:"+y.isPresent()+";x+y+z求和:"+(a+b+c));
}
执行结果
六、新的日期时间
我们一直在使用的时间工具类有:
java.util包中的Date和Calendar等
java.text包中的SimpleDateFormat等
原来的时间工具类有很多弊端,缺乏年、月、日、时间、星期的单独抽象,线程不安全等等,这些大家可以查查或者在工作中体会。
JDK8中在java.time包下提供了很多新的时间处理方式
详细的可以查查API,简单写几个获取时间的栗子
@Test
public void test(){
LocalDateTime localDateTime = LocalDateTime.now();
String now = localDateTime.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(now);
System.out.println(LocalDate.now());
System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
System.out.println("年:"+localDateTime.getYear()+"月:"+localDateTime.getMonthValue()+
"日:"+localDateTime.getDayOfMonth()+"时:"+localDateTime.getHour()+
"分:"+localDateTime.getMinute()+"秒:"+localDateTime.getSecond());
System.out.println(LocalDate.of(2008,Month.DECEMBER,21));
System.out.println(LocalTime.parse("14:12:18"));
System.out.println("巴黎时间:"+ZonedDateTime.now(ZoneId.of("Europe/Paris"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
System.out.println("当前时区:"+ZoneId.systemDefault());
}
看下执行结果:
总结
其实已经不新了,不过是当前我们刚更新上来不到一年,所以对这一顿时间看代码或者使用做一下总结,水平有限,路漫漫其修远啊~~