函数式编程——类库

本文作者:杨龙,叩丁狼高级讲师。原创文章,转载请注明出处。

基类型和包装类型

拆箱装箱的性能问题

将基本类型转换为装箱类型,称为装箱,反之则称为拆箱,两者都需要额外的计算开销。包装类型在求和的时候,会出现性能问题。
所以我们会把下面 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

toIntLongDoubleStream.png

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); // 这里编译会报错,调用方法出现歧义
    }
}

最好的解决办法是不该再重载,而应当开始重新命名重载方法。

@FunctionalInterface

每个用作函数接口的接口最好添加这个注解。

该注解会强制 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

Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。人们常常使用 null 值表示值不存在,Optional 对象能更好地表达这个概念。Optional 对象相当于值的容器。

使用 null 代表值不存在的最大问题在于 NullPointerException。一旦引用一个存储 null 值的变量,程序会立即崩溃。使用 Optional 对象有两个目的:首先,Optional 对象鼓励程序员适时检查变量是否为空,以避免代码缺陷;其次,它将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单很多。

Optional API

  • static Optional of(T value) ,可以从某个值创建出一个 Optional 对象。
  • static Optional ofNullable(T value),可以从某个值(包含 null)创建出一个 Optional 对象。
  • static Optional empty(),返回一个没有值的 Optional 对象。
  • boolean isPresent(),判断 Optional 对象中是否有值。
  • T get(),从 Optional 对象获取值。
  • T orElse(T other),从 Optional 对象获取值,当 Optional 对象为空时,该方法提供了一个备选值。
  • T orElseGet(Supplier<? extends T> other),从 Optional 对象获取值,当 Optional 对象为空时,该方法提供了一个备选值,适合备选值计算复杂时使用。
  • T orElseThrow(Supplier<? extends X> exceptionSupplier),从 Optional 对象获取值,当 Optional 对象为空时,抛出异常,不设置 exceptionSupplier 的值抛出的异常是空指针,也可以通过 exceptionSupplier 来自定以抛出异常。
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("无值")); // 抛出运行时异常

在这里插入图片描述

おすすめ

転載: blog.csdn.net/wolfcode_cn/article/details/89404028