本文链接:https://github.com/wmyskxz/MoreThanJava/blob/master/java-base/java8.md
1.接口中的默认方法和静态方法
默认方法可以为接口添加一些默认的实现,实现接口时可以直接使用默认方法。
public interface TestInterface1 {
default void sameMethod() { System.out.println("Invoke TestInterface1 method!"); }
}
静态方法:如果实现接口时,没有理由再额外提供一个带有实用方法的工具类
public interface StaticInterface {
static void method() {
System.out.println("这是Java8接口中的静态方法!");
}
}
2.lambda表达式
Lambda
允许把函数作为一个方法的参数,即 行为参数化,函数作为参数传递进方法中。函数作为参数传递进方法中。
3.函数式接口
一个接口函数需要被实现的接口类型,我们叫它「函数式接口」。
例如
Comparator(比较器接口)
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
Consumer的作用
给定义一个参数,对其进行(消费)处理,处理的方式可以是任意操作.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
4.方法引用
可以直接通过方法引用来简写 Lambda 表达式中已经存在的方法或构造函数,这种特性就叫做方法引用(Method Reference
)。
Arrays.sort(strArray, (s1, s2) -> s1.compareToIgnoreCase(s2));
Arrays.sort(strArray, String::compareToIgnoreCase);
方法引用是一个 Lambda 表达式,其中方法引用的操作符是双冒号
::
。方法引用的标准形式是:
类名::方法名
。(注意:只需要写方法名,不需要写括号)有以下四种形式的方法引用:
- 引用静态方法: ContainingClass::staticMethodName
- 引用某个对象的实例方法: containingObject::instanceMethodName
- 引用某个类型的任意对象的实例方法:ContainingType::methodName
- 引用构造方法: ClassName::new
5.stream流式操作
特点一:内部迭代
foreach采用外部迭代,流采用内部迭代
采用内部迭代,项目可以透明地并行处理,或者用优化的顺序进行处理,要是使用 Java 过去的外部迭代方法,这些优化都是很困难的。
特点二:只能遍历一次
和迭代器一样,流只能遍历一次。当流遍历完之后,我们就说这个流已经被消费掉了,你可以从原始数据那里重新获得一条新的流,但是却不允许消费已消费掉的流。
特点三:并行处理
只需要加上 .parallel()
就行了!例如我们使用下面程序来说明一下多线程流操作的方便和快捷,并且与单线程做了一下对比:
public class StreamParallelDemo {
/** 总数 */
private static int total = 100_000_000;
public static void main(String[] args) {
System.out.println(String.format("本计算机的核数:%d", Runtime.getRuntime().availableProcessors()));
// 产生1000w个随机数(1 ~ 100),组成列表
Random random = new Random();
List<Integer> list = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
list.add(random.nextInt(100));
}
long prevTime = getCurrentTime();
list.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);
System.out.println(String.format("单线程计算耗时:%d", getCurrentTime() - prevTime));
prevTime = getCurrentTime();
// 只需要加上 .parallel() 就行了
list.stream().parallel().reduce((a, b) -> a + b).ifPresent(System.out::println);
System.out.println(String.format("多线程计算耗时:%d", getCurrentTime() - prevTime));
}
private static long getCurrentTime() {
return System.currentTimeMillis();
}
}
本计算机的核数:8 655028378 单线程计算耗时:4159 655028378 多线程计算耗时:540
并行流的内部使用了默认的 ForkJoinPool 分支/合并框架,它的默认线程数量就是你的处理器数量,这个值是由 Runtime.getRuntime().availableProcessors()
得到的(当然我们也可以全局设置这个值)。我们也不再去过度的操心加锁线程安全等一系列问题。
一些示例
Filter 过滤
stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);
Sort 排序
stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);
Map 映射
stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println);
Match 匹配
boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
Count 计数
long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Reduce 归约
这是一个最终操作,允许通过指定的函数来将 stream
中的多个元素规约为一个元素,规越后的结果是通过 Optional
接口表示的。代码如下:
Optional<String> reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println);
6.optional
检查空值
Optional<String> fullName = Optional.ofNullable(null);
System.out.println("Full Name is set? " + fullName.isPresent());
System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]"));
System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));
如果 Optional
类的实例为非空值的话,isPresent()
返回 true
,否从返回 false
。
为了防止 Optional 为空值,orElseGet()
方法通过回调函数来产生一个默认值。
map()
函数对当前 Optional
的值进行转化,然后返回一个新的 Optional
实例。
orElse()
方法和 orElseGet()
方法类似,但是 orElse
接受一个默认值而不是一个回调函数。下面是这个程序的输出:
只有当 Optional<T>
结合 Lambda 一起使用的时候,才能发挥出其真正的威力!
我们现在就来对比一下下面四种常见的 null 处理中,Java 8 的 Lambda + Optional<T>
和传统 Java 两者之间对于 null 的处理差异。
情况一:存在则继续
情况二:存在则返回,无则返回不存在
情况三:存在则返回,无则由函数产生
情况四:夺命连环 null 检查
7.date/time API
LocalTime 本地时间,LocalData 本地日期,LocalDateTime 本地日期时间,线程安全
8.重复注解
相同的注解可以在同一地方声明多次
重复注解机制本身必须用 @Repeatable
注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。让我们看一个快速入门的例子:
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatingAnnotations {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Filters {
Filter[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Filters.class)
public @interface Filter {
String value();
};
@Filter("filter1")
@Filter("filter2")
public interface Filterable {
}
public static void main(String[] args) {
for(Filter filter: Filterable.class.getAnnotationsByType(Filter.class)) {
System.out.println(filter.value());
}
}
}
正如我们看到的,这里有个使用 @Repeatable(Filters.class)
注解的注解类 Filter
,Filters
仅仅是 Filter
注解的数组,但Java编译器并不想让程序员意识到 Filters
的存在。这样,接口 Filterable
就拥有了两次 Filter
(并没有提到Filter
)注解。
同时,反射相关的API提供了新的函数getAnnotationsByType()
来返回重复注解的类型(请注意Filterable.class.getAnnotation(Filters.class
)`经编译器处理后将会返回Filters的实例)。
9.扩展注解支持
Java 8 扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。下面演示几个例子:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
public @interface NonEmpty {
}
public static class Holder<@NonEmpty T> extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings("unused")
public static void main(String[] args) {
final Holder<String> holder = new @NonEmpty Holder<String>();
@NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
}
}
10.base64
在 Java 8 中,Base64 编码已经成为 Java 类库的标准。它的使用十分简单,下面让我们看一个例子:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64s {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!";
final String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);
final String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
System.out.println(decoded);
}
}
程序在控制台上输出了编码后的字符与解码后的字符:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ== Base64 finally in Java 8!
Base64 类同时还提供了对 URL、MIME 友好的编码器与解码器(Base64.getUrlEncoder() / Base64.getUrlDecoder()
, Base64.getMimeEncoder() / Base64.getMimeDecoder()
)。
11.其他性能提升
(1)HashMap引入红黑树,改用尾插法,ConcurrentHashMap修改分段锁为synchronized
(2)NIO
主要包括:改进了java.nio.charset.Charset
的实现,使编码和解码的效率得以提升,也精简了jre/lib/charsets.jar
包;
优化了String(byte[], *)
构造方法和String.getBytes()
方法的性能;
增加了一些新的IO/NIO
方法,使用这些方法可以从文件或者输入流中获取流(java.util.stream.Stream
),通过对流的操作,可以简化文本行处理、目录遍历和文件查找。
新增的 API 如下:
BufferedReader.line()
: 返回文本行的流Stream<String>
File.lines(Path, Charset)
: 返回文本行的流Stream<String>
File.list(Path)
: 遍历当前目录下的文件和目录File.walk(Path, int, FileVisitOption)
: 遍历某一个目录下的所有文件和指定深度的子目录File.find(Path, int, BiPredicate, FileVisitOption...)
: 查找相应的文件
下面就是用流式操作列出当前目录下的所有文件和目录:
Files.list(new File(".").toPath()).forEach(System.out::println);
(3)JVM 的 PermGen 空间被移除
PermGen
空间被移除了,取而代之的是Metaspace(JEP 122)
。JVM 选项-XX:PermSize
与-XX:MaxPermSize
分别被-XX:MetaSpaceSize
与-XX:MaxMetaspaceSize
所代替。
区别:
- 元空间并不在虚拟机中,而是使用本地内存
- 默认情况下,元空间的大小仅受本地内存限制
- 也可以通过-XX:MetaspaceSize指定元空间大小