Java 8 实战 P4 Beyond Java 8

Chapter 13. Thinking functionally

13.1 实现和维护系统

有synchronized关键字的不要维护
容易使用的程序算法

  • Stream的无状态的行为(函数不会因为须要等待从另外一个方法中读取变量,或者因为须要写入的变量同时有另外一个方法正在写入,而发生中断)让咱们
  • 最好类的 结构应该反映出系统的结构
  • 提供指标对结构的合理性进行评估,好比耦合性(软件系统中各组件之间是否相互独立)以及内聚性(系统的各相关部分之间如何协做)

不过对于平常事务,最关心的是代码维护时的调试:代码遭遇一些没法预期的值就有可能发生崩溃。这些没法预知的变量都源于共享的数据结构被你所维护的代码中的多个方法读取和更新。数据库

对此,函数式编程提出的“无反作用”以及“不变性”编程

无反作用
函数:若是一个方法既不修改它内嵌类的状态,也不修改其余对象的状态,使用return返回全部的计算结果,那么咱们称其为无反作用的。函数若是抛出异常,I/O和对类中的数据进行任何修改(除构造器内的初始化)都是有反作用的。设计模式

  • 变量:final型

声明式编程
通常经过编程实现一个系统,有两种思考方式。一种专一于如何实现,另外一种方式则更加关注要作什么。
前一种为经典的面向对象编程,命令式;后一种为内部迭代,声明式。
第二种方式编写的代码更加接近问题陈述。缓存

函数式编程实现了上述的两种思想:使用不相互影响的表达式,描述想要作什么(由系统来选择如何实现)。安全

13.2 函数式编程

1.函数式Java编程
Java语言没法实现纯粹函数式(彻底无反作用)的程序,只能接近(反作用不会被察觉)。
这种函数只能修改局部变量,它的引用对象(参数及其余外部引用)都是不可修改对象(复制后再使用非函数式行为,如add)。除此以外,不抛异常(用Optional,或者局部抛异常),不进行I/O。数据结构

2.引用透明性(上面规定的隐含)
一个函数只要传递一样的参数值,它老是返回一样(==)的结果。闭包

3.例子app

//给定一个List<value>,返回其子集,类型为List<List<Integer>>,下面是总体算法,下下面是函数式的实践
static List<List<Integer>> subsets(List<Integer> list)
    if (list.isEmpty()) {
        List<List<Integer>> ans = new ArrayList<>();
        ans.add(Collections.emptyList());
        return ans;
    }
    Integer first = list.get(0);
    List<Integer> rest = list.subList(1,list.size());
    List<List<Integer>> subans = subsets(rest);
    List<List<Integer>> subans2 = insertAll(first, subans);
    return concat(subans, subans2);

//    
static List<List<Integer>> insertAll(Integer first,
       List<List<Integer>> lists) {
    List<List<Integer>> result = new ArrayList<>();
    for (List<Integer> list : lists) {
        List<Integer> copyList = new ArrayList<>();//复制新list,而不是直接用参数调用.add
        copyList.add(first);
        copyList.addAll(list);
        result.add(copyList);
    }
    return result;
}
//下面方式相同
static List<List<Integer>> concat(List<List<Integer>> a,
    List<List<Integer>> b) {
        List<List<Integer>> r = new ArrayList<>(a);
        r.addAll(b);
        return r;
}

13.3 递归和迭代

将加强for改成迭代器方式没有反作用?

Iterator<Apple> it = apples.iterator();
    while (it.hasNext()) {
       Apple apple = it.next();
        // ... 
}

利用递归而非迭代来消除没步都需更新迭代变量(可是使用迭代在Java效率一般更差),如阶乘:

//下面代码除效率问题,还有StackOverflowError风险
static long factorialRecursive(long n) {
        return n == 1 ? 1 : n * factorialRecursive(n-1);
}

//尾迭代能解决StackOverflowError问题。每次调用函数时,把新的结果传入函数。遗憾Java目前还不支持这种优化,Scala能够。
static long factorialTailRecursive(long n) {
        return factorialHelper(1, n);
}

static long factorialHelper(long acc, long n) {
    return n == 1 ? acc : factorialHelper(acc * n, n-1);
}

//Stream更简单
static long factorialStreams(long n){
        return LongStream.rangeClosed(1, n)
                         .reduce(1, (long a, long b) -> a * b);
}

总结:尽可能使用Stream取代迭代操做,从而避免变化带来的影响。此外,若是递归能不带任何反作用地让你以更精炼的方式实现算法,你就应该用递归替换迭代,由于它更加易于阅读、实现和理解。大多数时候编程的效率要比细微的执行效率差别重要得多。

Chapter 14. Functional programming techniques

函数式语言更普遍的含义是:函数能够做参数、返回值,还能存储。
1.高阶函数
接受至少一个函数作参数,返回结果是一个函数

接收的做为参数的函数可能带来的反作用以文档的方式记录下来,最理想的状况下,接收的函数参数应该没有任何反作用。
2.科里化
一种将具有n个参数(好比,x和y)的函数f转化为使用m(m < n)个参数的函数g,而且这个函数的返回值也是一个函数,它会做为新函数的一个参数。后者的返回值和初始函数的 返回值相同。

//将下面函数科里化,即预设各类f和b的组合,须要使用时只需调用相应的函数加上确实的x。
static double converter(double x, double f, double b) {
    return x * f + b;
}

//建立高阶函数
static DoubleUnaryOperator curriedConverter(double f, double b){ 
    return (double x) -> x * f + b;
}
//其中一种组合
DoubleUnaryOperator convertUSDtoGBP = curriedConverter(0.6, 0);
//使用
double gbp = convertUSDtoGBP.applyAsDouble(1000);

14.2 持久化数据结构

这里指的不是数据库中的持久化
链表例子(火车旅行)

//TrainJourney类(火车站)有两个公有变量,price和onward(下一站),构造函数以下
public TrainJourney(int p, TrainJourney t) {
    price = p;
    onward = t; 
}

//link方法把两个单向链表(一列火车站)连成一体。下面代码在a的基础上链接,这样会破坏原来a的结构。若是a本来在其余地方有应用,那么那些地方也会受到影响。
static TrainJourney link(TrainJourney a, TrainJourney b){
    if (a==null) return b;
    TrainJourney t = a;
    while(t.onward != null){
        t = t.onward;
    }
    t.onward = b;
    return a; 
}

//函数式实现。下面的实现的结果是a的副本,后面接上b。因此要确保结果不被修改,不然b也会没修改。这也包括下面的tree例子
static TrainJourney append(TrainJourney a, TrainJourney b){
    return a==null ? b : new TrainJourney(a.price, append(a.onward, b));
}

//函数式不免会有必定程度的复制,上面例子至少只复制了a,而不是在一个全新的list上链接a和b

树例子(我的信息)

//节点信息,若是强制遵照函数式编程,能够将下面变量声明为final
class Tree { 
    private String key;
    private int val;
    private Tree left, right;
    public Tree(String k, int v, Tree l, Tree r) {
        key = k; val = v; left = l; right = r;
  } 
} 

//函数式的节点更新,每次更新都会建立一个新tree,一般而言,若是树的深度为d,而且保持必定的平衡性,那么这棵树的节点总数是2^d
public static Tree fupdate(String k, int newval, Tree t) {
    return (t == null) ?
        new Tree(k, newval, null, null) :
            k.equals(t.key) ?
                new Tree(k, newval, t.left, t.right) :
            k.compareTo(t.key) < 0 ?
                new Tree(t.key, t.val, fupdate(k,newval, t.left), t.right) :
                new Tree(t.key, t.val, t.left, fupdate(k,newval, t.right));
}

?实现部分函数式(某些数据更新对某些用户可见):

  • 典型方式:只要你使用非函数式代码向树中添加某种形式的数据结构,请马上建立它的一份副本
  • 函数式:改动前,复制修改处以前的部分,而后接上剩余部分

14.3 Stream 的延迟计算

有一个延迟列表的实现例子
若是延迟数据结构能让程序设计更简单,就尽可能使用它们。若是它们会带来没法接受的性能损失,就尝试以更加传统的方式从新实现它们。

14.4 模式匹配(Java暂未提供)

1.访问者设计模式
一个式子简化的代码,如5+0变为5,使用Expr.simplify。但一开始要对expr进行各类检查,如expr的类型,不一样类型有不一样变量,当符合条件才返回结果。这个过程涉及instanceof和cast等操做,比较麻烦。
而访问者设计模式能获得必定的简化,它须要建立一个单独的类(SimplifyExprVisitor),这个类封装了一个算法(下面的visit),能够“访问”某种数据 类型。

class BinOp extends Expr{
    String opname; 
    Expr left, right;
    
    public Expr accept(SimplifyExprVisitor v){
            return v.visit(this);
    } 
}

public class SimplifyExprVisitor {
    ...
    public Expr visit(BinOp e){
        if("+".equals(e.opname) && e.right instanceof Number && ...){
            return e.left;
        }
        return e;
    }
}

//Java 中模式的判断标签被限制在了某些基础类型、枚举类型、封装基础类型的类以及String类型。
//Scala的简单实现
def simplifyExpression(expr: Expr): Expr = expr match {
    case BinOp("+", e, Number(0)) => e
    case BinOp("*", e, Number(1)) => e
    case BinOp("/", e, Number(1)) => e
    case _ => expr
}

14.5 杂项

1.缓存或记忆表(并不是函数式方案)

final Map<Range,Integer> numberOfNodes = new HashMap<>();
Integer computeNumberOfNodesUsingCache(Range range) {
    Integer result = numberOfNodes.get(range);
    if (result != null){
        return result;
    }
    result = computeNumberOfNodes(range);
    numberOfNodes.put(range, result);
    return result;
}

这段代码虽然是透明的,但并非线程安全的(numberOfNodes可变)
2.结合器

Chapter 15. comparing Java 8 and Scala

下面默认先写Scala,或只写Scala

15.1 Scala 简介

1.你好

//命令式
object Beer {//单例对象
  def main(args: Array[String]/*Java先类型后变量*/){//不须要void,一般非递归方法不须要写返回类型;对象声明中的方法是静态的
    var n : Int = 2
    while( n <= 6 ){
      println(s"Hello ${n} bottles of beer")
      n += 1 
    }
  } 
}

//函数式
2 to 6 /*Int的to方法,接受Int,返回区间,即也能够用2.to(6)。后面foreach理解相同*/foreach { n => println(s"Hello ${n} bottles of beer") }

一样一切为对象,但没有基本类型之分
Scala中用匿名函数或闭包指代lambda

2.数据结构
Map:
val authorsToAge = Map("Raoul" -> 23)Java须要建立后put
val authors = List("Raoul", "Mario")

Scala中的集合默认都是持久化的:更新一个Scala集合会生成一个新的集合,这个新的集合和以前版本的集合共享大部分的内容,最终的结果是数据尽量地实现了持久化。因为这一属性,代码的隐式数据依赖更少:人们对代码中集合变动的困惑(好比在何处更新了集合,何时作的更新)也会更少。
val newNumbers = numbers + 8numbers为Set,添加元素是建立一个新Set对象

Java的不可变(immutable)比不可修改(unmodifiable)更完全

val fileLines = Source.fromFile("data.txt").getLines.toList() 
val linesLongUpper
      = fileLines.filter(l => l.length() > 10)
                 .map(l => l.toUpperCase())
//另外一种表达,多加.par表示并行
fileLines.par filter (_.length() > 10) map(_.toUpperCase())

元组
val book = (2014, "Java 8 in Action", "Manning")可不一样类型,任意长度(上限23)
Java须要本身建pair类,�且3个以上元素的pair比较麻烦

Stream
Scala中能够访问以前计算的值,能够经过索引访问,同时内存效率会变低。

Option
和Java很像

def getCarInsuranceName(person: Option[Person], minAge: Int) = 
    person.filter(_.getAge() >= minAge)
          .flatMap(_.getCar)
          .flatMap(_.getInsurance)
          .map(_.getName)
          .getOrElse("Unknown")

15.2 函数

Scala多了“可以读写非本地变量”和对科里化的支持
1.一等函数

//filter的函数签名
def filter[T](p: (T) => Boolean/*Java用函数式接口Predicate<T>或 者Function<T, Boolean>,Scala直接用函数描述符或名为函数类型*/): List[T]//参数类型

//定义函数
def isShortTweet(tweet: String) : Boolean = tweet.length() < 20
//使用函数,tweets是List[String]
tweets.filter(isShortTweet).foreach(println)

2.匿名函数和闭包

//上面代码的匿名方式以下(都是语法糖)
val isLongTweet : String => Boolean
    = (tweet : String) => tweet.length() > 60

isLongTweet("A very short tweet")

//Java的匿名方式
Function<String, Boolean> isLongTweet = (String s) -> s.length() > 60;

boolean long = isLongTweet.apply("A very short tweet");

//闭包
var count = 0
val inc = () => count+=1
inc()
println(count)
//Java
int count = 0;
Runnable inc = () -> count+=1;//会出错,count必须为final或效果为final
inc.run();

3.科里化
Java须要手工地切分函数,麻烦在于多参数状况

//Java
static Function<Integer, Integer> multiplyCurry(int x) {
    return (Integer y) -> x * y;
}

Stream.of(1, 3, 5, 7)
      .map(multiplyCurry(2))
      .forEach(System.out::println);

//Scala
def multiplyCurry(x :Int)(y : Int) = x * y

val multiplyByTwo : Int => Int = multiplyCurry(2)
val r = multiplyByTwo(10)

15.3 类和trait

1.类
Scala中的getter和setter都是隐式实现的

class Student(var name: String, var id: Int)

val s = new Student("Raoul", 1)
println(s.name)//getter
s.id = 1337//setter

2.trait
与interface相似,有抽象方法、默认方法、接口多继承。但trait还有抽象类的字段。Java支持行为的多继承,但还不支持对状态的多继承。
Scala能够在类实例化时才决定trait

val b1 = new Box() with Sized

Chapter 16. Conclusions

Java8的发展体现了两种趋势:多核处理的需求(独立CPU速度瓶颈)->并行计算;更简洁地对抽象数据进行操做。 1.行为参数化:Lambda和方法引用 2.对大量数据的处理Stream:能在一次遍历中完成多种操做,并且按需计算。 并行处理中的重点:无反作用、Lambda、方法引用、内部迭代 3.CompletableFuture提供了像thenCompose、thenCombine、allOf这样的操做,避免Future中的命令式编程 4.Optional:能显式表示缺失值。正确使用可以发现数据缺失的缘由。还有一些与Stream相似的方法。 5.默认方法

相关文章
相关标签/搜索