本文作者:杨龙,叩丁狼高级讲师。原创文章,转载请注明出处。
将基本类型转换为装箱类型,称为装箱,反之则称为拆箱,两者都需要额外的计算开销。包装类型在求和的时候,会出现性能问题。
所以我们会把下面 count 类型声明基本类型 long。
Long count = 0;
for(int i = 1; i <= 10; i++){
count += i; // 这会先拆箱后又装箱
}
System.out.println(count);
那么在函数变成里面若求和是不是会出现上述问题呢?在 Java 8 中,仅对整型、长整型和双浮点型做了特殊处理,因为它们在数值计算中用得最多,特殊处理后的系统性能提升效果最明显。提供下面类型下面这样函数:
接口 | 参数 | 返回类型 |
---|---|---|
ToLongFunction | T | long |
LongFunction | long | T |
LongUnaryOperator | long | long |
ToIntFunction | T | int |
IntFunction | int | T |
IntUnaryOperator | int | int |
ToDoubleFunction | T | double |
DoubleFunction | double | T |
DoubleUnaryOperator | double | double |
Stream 中 map 和 flatMap 的方法的 Lambda 表达式须是以上接口的一个实例,这些基本类型都有与之对应的 Stream,以基本类型名为前缀,如 LongStream 。事实上 mapToLong 方法返回的不是一个一般的 Stream,而是一个特殊处理的 Stream。这些特殊的 Stream 还提供额外的方法,避免重复实现一些通用的方法,让代码更能体现出数值计算的意图。包含一个 summaryStatistics 方法,这个方法能计算出各种各样的统计值,如 IntStream 对象内所有元素中的最小值、最大值、平均值以及数值总和。
IntStream intStream = Stream.of(1, 2, 3, 4).mapToInt(value -> value);
IntSummaryStatistics intSummaryStatistics = intStream.summaryStatistics();
System.out.println(intSummaryStatistics.getAverage());
System.out.println(intSummaryStatistics.getCount());
System.out.println(intSummaryStatistics.getMax());
System.out.println(intSummaryStatistics.getMin());
/* System.out.println(intStream.average()); 上面那些方法等价下面这些方法
System.out.println(intStream.count());
System.out.println(intStream.max());
System.out.println(intStream.min()); */
Java 方法中是可以存在重载的,即同类中方法名相同,方法签名不相同,但若参数是一个 Lambda 表达式,出现调用歧义。
interface IntPredicate {
public boolean test(int value);
}
class MethodOverloadTest {
public static void overloadedMethod(Predicate<Integer> predicate) {
System.out.print("Predicate");
}
public static void overloadedMethod(IntPredicate predicate) {
System.out.print("IntPredicate");
}
}
public class Main {
public static void main(String[] args) {
MethodOverloadTest.overloadedMethod((x) -> true); // 这里编译会报错,调用方法出现歧义
}
}
最好的解决办法是不该再重载,而应当开始重新命名重载方法。
每个用作函数接口的接口最好添加这个注解。
该注解会强制 javac 检查一个接口是否符合函数接口的标准。如果该注解添加给一个枚举类型、类或另一个注解,或者接口包含不止一个抽象方法, javac 就会报错。重构代码时,使用它能很容易发现问题。
虽然 Java 在持续演进,但它一直在保持着向后二进制兼容。具体来说,使用 Java 1 到 Java 7 编译的类库或应用,可以直接在 Java 8 上运行。
Java 8 中为 Collection 接口增加了 stream 方法,这意味着所有实现了 Collection 接口的类都必须增加这个新方法。对核心类库里的类来说,实现这个新方法。但在 JDK 之外实现 Collection 接口的类,例如 MyCustomList,也仍然需要实现新增的 stream 方法。这就带来了不兼容的问题。
在 Java8 中,Collection 接口告诉它所有的子类:“如果你没有实现 stream 方法,就使用我的吧”,接口中这样的方法叫作默认方法(使用 default 关键字修饰),那么实现类不实现该方法,也不会编译报错。
Collection {
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
interface IWalkable {
default void walk(){
System.out.println("IWalkable walk method");
}
}
class Dog implements IWalkable {
public void walk(){
System.out.println("Dog walk method");
}
}
interface IDanceWalk extends IWalkable{
default void walk(){
System.out.println("IDanceWalk walk method");
}
}
class Husky extends Dog implements IDanceWalk {
}
public class Main {
public static void main(String[] args) {
IWalkable walkable = new Dog(); // Dog walk method, 证明第一条重写规则
walkable.walk();
IWalkable walkable2 = new Husky(); // Dog walk method, 证明第二条重写规则
walkable2.walk();
}
}
当实现类实现多个接口时,多个接口中同时存在相同方法签名的方法,实现类并没有覆盖。
interface A {
public default String myMehtod() {
return "A";
}
}
interface B {
public default String myMehtod() {
return "B";
}
}
// javac 并不明确应该继承哪个接口中的方法,
// 因此编译器会报错 C inherits unrelated defaults for myMehtod() from types A and B
class C implements A, B {
}
解决办法,实现类中覆盖接口中方法:
class C implements A, B {
public String myMehtod() {
return A.super.myMehtod();
}
}
Stream 是个接口,Stream.of 是接口的静态方法。这也是 Java 8 中添加的一个新的语言特性,旨在帮助编写类库的开发人员,但对于日常应用程序的开发人员也同样适用。
比如我们自己开发一个工具类的时候,里面会定义很多静态方法,而这些方法属性类,不属于对象,为了不让使用者创建该工具类的对象,一般会把类声明成私有或者把其构造器私有,稍微麻烦一些。
Java8 支持接口中添加静态方法,那么以后工具都可以使用接口来定义了更加方便。
Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。人们常常使用 null 值表示值不存在,Optional 对象能更好地表达这个概念。Optional 对象相当于值的容器。
使用 null 代表值不存在的最大问题在于 NullPointerException。一旦引用一个存储 null 值的变量,程序会立即崩溃。使用 Optional 对象有两个目的:首先,Optional 对象鼓励程序员适时检查变量是否为空,以避免代码缺陷;其次,它将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单很多。
Optional<String> a = Optional.of("a");
assertEquals("a", a.get());
assertTrue(a.isPresent());
Optional emptyOptional = Optional.empty();
Optional alsoEmpty = Optional.ofNullable(null);
assertFalse(emptyOptional.isPresent());
assertFalse(alsoEmpty.isPresent());
assertEquals("b", emptyOptional.orElse("b"));
assertEquals("c", emptyOptional.orElseGet(() -> "c"));
emptyOptional.orElseThrow(null); // 抛出空指针异常
emptyOptional.orElseThrow(() -> new RuntimeException("无值")); // 抛出运行时异常