类库

目录

1.基本类型

2.默认方法

2.1 接口的继承

2.2 多重继承

3.内置函数式接口

3.1 Optional

3.2 Supplier

3.3 Consumer例子

3.4 Predicate操作

3.5 Function例子

4.总结


前面讨论了如何编写 Lambda 表达式, 接下来将详细阐述另一个重要方面: 如何使用Lambda 表达式。 即使不需要编写像 Stream 这样重度使用函数式编程风格的类库, 学会如何使用 Lambda 表达式也是非常重要的。 即使一个最简单的应用,也可能会因为代码即数据的函数式编程风格而受益。

Java 8中的另一个变化是引入了默认方法和接口的静态方法,它改变了人们认识类库的方式,接口中的方法也可以包含代码体了。

1.基本类型

在 Java 中, 有一些相伴的类型, 比如 int 和 Integer——前者是基本类型, 后者是装箱类型。 基本类型内建在语言和运行环境中, 是基本的程序构建模块; 而装箱类型属于普通的 Java 类, 只不过是对基本类型的一种封装。

Java 的泛型是基于对泛型参数类型的擦除——换句话说, 假设它是 Object 对象的实例——因此只有装箱类型才能作为泛型参数。 这就解释了为什么在 Java 中想要一个包含整型值的列表 List<int>, 实际上得到的却是一个包含整型对象的列表 List<Integer>。麻烦的是, 由于装箱类型是对象, 因此在内存中存在额外开销。 比如, 整型在内存中占用4 字节, 整型对象却要占用 16 字节。

为了减小这些性能开销,Java 8对整型、长整型和双浮点型做了特殊处理, 因为它们在数值计算中用得最多, 特殊处理后的系统性能提升效果最明显。对基本类型做特殊处理的方法在命名上有明确的规范,如果方法返回类型为基本类型,则在基本类型前加 To,如下图所示:

如果参数是基本类型,则不加前缀只需类型名即可,如下图所示:

如果高阶函数使用基本类型,则在操作后加后缀To再加基本类型,如mapToLong。如有可能,应尽可能多地使用对基本类型做过特殊处理的方法,进而改善性能。 这些特殊的 Stream 还提供额外的方法,避免重复实现一些通用的方法,让代码更能体现出数值计算的意图。

 private static void statisticsTest() {
        IntSummaryStatistics summaryStatistics = studentDTOList.stream()
                .mapToInt(student -> student.getAge()).summaryStatistics();
        System.out.printf("Max:%d,Min:%d,Ave:%f,Sum:%d", summaryStatistics.getMax(), summaryStatistics.getMin(),
                summaryStatistics.getAverage(), summaryStatistics.getSum());
    }

这里使用对基本类型进行特殊处理的方法mapToInt,将每个学生映射为学生年龄并转换年龄为int类型。因为该方法返回一个IntStream对象,它包含一个summaryStatistics 方法,这个方法能计算出各种各样的统计值,如 IntStream 对象内所有元素中的最小值、最大值、平均值以及数值总和。

2.默认方法

Collection接口中新增加了stream方法,如何能让Collection的子类在不实现该方法的情况下通过编译?Java 8通过在接口中增加默认方法来解决该问题,在任何接口中,无论函数接口还是非函数接口,都可以使用该方法。Iterable接口中也新增加了一个默认方法:forEach,该方法的功能和for循环类似,但是允许用户使用一个Lambda表达式作为循环体。

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

它使用一个常规的for循环遍历Iterable对象,然后对每个值调用accept方法。和类不同的是,接口没有成员变量,因此默认方法只能通过调用子类的方法来修改子类本身,避免对子类的实现做出各种假设。

2.1 接口的继承

子接口重写了父接口中的默认方法,实现类方法在被调用的时候,调用的是子接口的默认方法。

定义接口Person:

public interface Person {
    default void say() {
        System.out.println("I am person!");
    }
}

定义子接口Child

public interface Child extends Person{
    @Override
    default void say() {
        System.out.println("I am Child");
    }
}

实现类: 

public class ChildImpl implements Child {
    public static void main(String[] args) {
        ChildImpl child = new ChildImpl();
        child.say();
    }
}

输出:I am Child

2.2 多重继承

接口允许多重继承,因此有可能碰到两个接口包含签名相同的默认方法的情况。比如我们将上面定义Child方法修改为:

public interface Child {
    default void say() {
        System.out.println("I am Child");
    }
}

ChildImpl 方法定义如下:

public class ChildImpl implements Child, Person {
   
}

此时,javac并不明确应该继承哪个接口中的方法,因此编译器会报错:

类 com.martin.learn.java8.domain.ChildImpl从类型 com.martin.learn.java8.domain.Child 和 com.martin.learn.java8.domain.Person 中继承了say() 的不相关默认值

总结:

  • 类胜于接口:如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法。
  • 子类胜于父类:如果一个接口继承了另一个接口,且两个接口都定义个一个默认方法,那么子类中定义的方法胜出。

3.内置函数式接口

@FunctionalInterface注释会强制 javac 检查一个接口是否符合函数接口的标准。 如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac 就会报错,重构代码时,使用它能很容易发现问题。为了更好地支持函数式编程,所以jdk给我们内置了很多常用的函数式接口,一般都是放在java.util.function包中。

3.1 Optional

Optional是为核心类库新设计的一个数据类型,用来替换null值。人们常常使用null值表示值不存在,Optional对象能更好地表达这个概念,使用null代表值不存在的最大问题在于NullPointerException,一旦引用一个存储null值的变量,程序就会立即崩溃。下面我们举例说明Optional对象的API,从而切身体会一下它的使用方法。使用工厂方法of,可以从某个值创建出一个Optional对象,Optional对象相当于值的容器,而该值可以通过get方法提取。

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> optional = Optional.of("a");
        Assert.isTrue("a".equals(optional.get()), "值不为a");
    }
}

Optional对象也可能为空,因此还有一个对应的工厂方法empty,另外一个工厂方法ofNullable则可将一个空值转换成Optional对象。isPresent方法表示一个Optional对象里是否有值。

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> empty = Optional.empty();
        Optional alsoEmpty = Optional.ofNullable(null);
        System.out.println(empty.isPresent());
        System.out.println(alsoEmpty.isPresent());
    }
}

输出结果如下:

false
false

使用Optional对象的方式之一是在调用get()方法前,先使用isPresent检查Optional对象是否有值。使用orElse方法则更加简洁,当Optional对象为空时,该方法提供一个备选值;也可以使用orElseGet方法,该方法接收一个Supplier对象,只用在Optional对象真正为空时才会调用。

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> empty = Optional.empty();
        Optional alsoEmpty = Optional.ofNullable(null);
        System.out.println(empty.orElse("a"));
        System.out.println(alsoEmpty.orElseGet(() -> "b"));
    }
}

输出结果如下:

a
b

3.2 Supplier

supplier<T>接口的作用是向外提供一个数据,拥有get()方法,用它可以产生或者获取我们需要的数据。lambda如果要使用外部局部变量,局部变量必须是final的。

import java.util.function.Supplier;

/**
 * @date: 2019/7/16 15:40
 * @description:
 */
public class SupplierTest {
    private static void method(Supplier<String> supplier) {
        String str = supplier.get();
        System.out.println(str);
    }

    public static void main(String[] args) {
        String str = "Hello";
        method(() -> str);
    }
}

3.3 Consumer例子

和Supplier相反,该函数式接口用于消费数据,比如对于获取的数据,做下一步的处理:


import java.util.function.Consumer;

/**
 * @date: 2019/7/16 15:53
 * @description:
 */
public class ConsumerTest {
    public static void method(Consumer<String> consumer) {
        //接收待处理的数据
        consumer.accept("Hello World");
        consumer.accept("I am pretty");
    }

    public static void method2(Consumer<String> one, Consumer<String> two) {
        //分别先执行one操作,然后再做two操作
        one.andThen(two).accept("Hello World");
    }

    public static void main(String[] args) {
        //t表示consumer中保存的数据逐一遍历
        method(t -> {
            System.out.println(t.toUpperCase());
        });

        method2(one -> System.out.println(one.toLowerCase()), two -> System.out.println(two.toUpperCase()));
    }
}

输出结果如下:

HELLO WORLD
I AM PRETTY
hello world
HELLO WORLD

3.4 Predicate操作

Predicate接口用于断言,也叫谓语接口,简单的判断“是”与“不是”:接收一个参数,得到一个boolean结果值,多用于判断和过滤。

import java.util.function.Predicate;

/**
 * @author: martin
 * @date: 2019/7/16 16:37
 * @description:
 */
public class PredicateTest {
    private static void method(Predicate<String> predicate) {
        boolean longCheck = predicate.test("Hello World");
        if (longCheck) {
            System.out.println("字符串太长");
        } else {
            System.out.println("字符串符合要求");
        }
    }

    public static void main(String[] args) {
        method(s -> s.length() > 40);
    }
}

输出结果为:
字符串符合要求

3.5 Function例子

函数式编程中函数是可以作为参数的,该接口的作用即是如此。这样函数就可以被传递与复用,说白了就是一个复合函数:f(g(x),g(x)作为f(y)的参数:

import java.util.function.Function;

/**
 * @date: 2019/7/16 16:55
 * @description:
 */
public class FunctionTest {
    /**
     * 转换逻辑
     *
     * @param function
     */
    public static void method(Function<String, Integer> function) {
        Integer apply = function.apply("100");
        System.out.println(apply);
    }

    public static void method2(Function<String, String> one, Function<String, Integer> two) {
        //先执行one操作,再执行two操作
        Integer age = one.andThen(two).apply("张三,12");
        age++;
        System.out.println("年龄:" + age);
    }

    public static void method3(Function<String, String> one, Function<String, Integer> two) {
        //先执行one操作,再执行two操作
        Integer age = two.compose(one).apply("张三,12");
        age++;
        System.out.println("年龄:" + age);
    }

    public static void main(String[] args) {
        method(Integer::parseInt);
        method2(s -> s.split(",")[1],Integer::parseInt);
        method2(s -> s.split(",")[1],Integer::parseInt);
    }
}

输出结果如下:

100
年龄:13
年龄:13

4.总结

  • 使用为基本类型定制的Lambda表达式和Stream,如IntStream可以显著提升系统性能
  • 默认方法是指接口中定义的包含方法体的方法,方法名有default关键字做前缀。
  • 在一个值可能为空的情况下,使用Optional对象能替代使用null值。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值