JAVA-DIY-7-WEEK

Persistence is not necessarily successful, but not sure will not succeed.

Posted by Hyuga on April 6, 2019

第7次讨论主题:Lambda表达式

函数式编程的优势和劣势分别是什么?

什么是函数式编程

函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程典范,,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

—- 维基百科

  • 优势:
    • 代码精简,少了很多规范化的代码
    • 链式调用,10行变一行都不是问题
    • 数据并行化操作
    • 扩展性和重用性更好
    • 便于统计分析数据,高阶函数封装对运算有极大的益处
    • 指令式到函数式的升级,从大体上看更符合人的阅读理解
    • 封装了语言更高级的api,以函数当参数使用,当对象使用,更符合语言未来的发展趋势
  • 劣势:
    • 可读性变差,很多人都这样认为
    • 不容易调试
    • 使用中需要考虑性能,用起来方便但不代表任何情况都适用lambda
    • 需要一定学习成本,对有些人来说,上手不是那么容易

Stream流的哪一个方法最有价值,为什么?

JDK8引入Stream API和lambda表达式。其中Stream API中有几个很常用的方法,如下:

  • stream()
  • parallelStream()
  • sorted
  • filter()
  • map()
  • limit()
  • forEach()
  • collect()

其中个人最有价值的应该是 .stream(),将多种对象类型转换为流对象,又为Stream提供了很多高阶函数,这对于java而言是一个很大的创新

相对于其他的方法,stream更像是那个中心点,其他的更像是在它基础上衍生出来的伴生函数。

首先了解下 interface AutoCloseable

package java.lang;
public interface AutoCloseable {
    void close() throws Exception;
}

实现AutoCloseable接口:

  • 使用try–with–resources 代码块 替代 try–catch–finally
  • 在代码块运行完毕后 自动实现 资源的关闭

接着再看下 interface BaseStream

public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {
    //返回此流元素的迭代器
    Iterator<T> iterator();
    //返回此流元素的spliterator
    Spliterator<T> spliterator();
    //返回此流,如果要执行终端操作,是否会并行执行。在调用终端流操作方法后调用此方法可能会产生不可预测的结果.
    //如果执行,这个流将并行执行
    boolean isParallel();
    //返回一个顺序的等效流。可能返回本身,要么因为流已经是顺序的,要么因为将底层流状态修改为顺序的。
    S sequential();
    //返回并行的等效流。可能返回本身,这可能是因为流已经是并行的,也可能是因为底层流状态被修改为并行的。
    S parallel();
    //返回一个等价的流,unordered。可能返回本身,要么因为流已经无序,要么因为将底层流状态修改为无序。
    S unordered();
    //具有处理程序的流,如果流关闭则运行该处理程序
    S onClose(Runnable closeHandler);
    //关闭此流,导致调用此流管道的所有关闭处理程序。
    Override
    void close();
}

最后再看下 interface Stream

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    ...
}

这个接口属性太多就不一一列举了,如下图!

这里面提供了一系列的流操作函数,只要是将集合、数据等对象转换为流后,就可以使用这些高阶函数接口。

8种基于jdk8及以上版本创建流对象Stream的方法:

  • 直接创建
    • Stream.empty();
    • Stream.of("a", "b", "c");
  • 采用集合创建
    • Collection.stream();
    • Arrays.asList().stream();
    • new ArrayList().stream();
  • 从数组创建流对象
    • Arrays.stream(0, 1, 3);
  • 使用Stream.builder()
    • Stream.<String>builder().add("a").add("b").add("c").build();
  • 使用Stream.generate()
    • Stream.generate(() -> "element").limit(10);
  • 使用Stream.iterate()
    • Stream.iterate(1, n -> n + 2).limit(5);
  • 从文件获取流
    • Path path = Paths.get("/User/admin/file.txt");
    • Stream<String> streamOfStrings = Files.lines(path);
    • Stream<String> streamWithCharset = Files.lines(path, Charset.forName("UTF-8"));
  • 从原始数据中获取
    • IntStream intStream = IntStream.range(1, 3);
    • LongStream longStream = LongStream.rangeClosed(1, 3);
    • Random random = new Random();DoubleStream doubleStream = random.doubles(3);

至此,我们明白了Stream流的一些特性:

  • 集合数组等可以转换为流对象
  • 流对象有很多高阶函数提供
  • 流对象执行完后会自动将流赋值为null

相对于其他函数方法,.stream()更具有核心价值。

写一段最精简的函数式接口和Lambda

  • 函数式接口 JDK8自带函数式接口
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);
}

public interface Stream<T> extends BaseStream<T, Stream<T>> {
   void forEach(Consumer<? super T> action);
}
  • 最精简的Lambda
Stream.of(1).forEach(System.out::println);
  • 随便写个链式,对比下
IntStream.range(1, 11).filter(i -> i > 5).mapToObj(i -> i+"_hyuga").skip(1).limit(1).forEach(System.out::println);

如果不用lambda又要怎么写呢。。。

List<String> list = new ArrayList<>();
for (int i = 1; i < 11; i++) {
    if (i <= 5) {
        continue;
    }
    list.add(i + "_hyuga");
}
if (list.size() > 0) {
    list.remove(0);
}
if (list.size() > 1) {
    System.out.println(list.get(0));
}