Optional

  Java 8中的Optional是一个可以包含或不可以包含非空值的容器对象,在 Stream API中很多地方也都使用到了Optional。

  java中非常讨厌的一点就是nullpoint,碰到空指针就会出错抛Exception,然后需要逐行检查是哪个对象为空,带来大量的不必要精力损耗,抛出NPE错误不是用户操作的错误,而是开发人员的错误,应该被避免,那么只能在每个方法中加入非空检查,阅读性和维护性都比较差。

  如下面这个代码的手工非空检查:
public void addAddressToCustomer(Customer customer, Address newAddress){
if ( customer == null || newAddress == null)
return;

if ( customer.getAddresses() == null ){
customer.setAddresses ( new ArrayList<>());
}
customer.addAddress(newAddress);
}

  另外还有一些开发人员喜欢通过非空检查来实现业务逻辑,空对象不应该用来决定系统的行为,它们是意外的Exceptional值,应当被看成是错误,而不是业务逻辑状态。

  当我们一个方法返回List集合时,应该总是返回一个空的List,而不是Null,这就允许调用者能够遍历它而不必检查Null,否则就抛出NPE。

  但是如果我们根据标识键ID查询数据库,没有查到,需要返回一个空对象怎么办?有人建议抛出Exception,其实这不符合函数方法一进一出的原则,变成一个函数方法有两个返回,一个是正常返回,一个出错Exception,函数式编程范式告诫我们不要轻易抛Exception。

  这时Java 8的Optional就发挥作用了,允许我们返回一个空的对象。

  Optional有方法 isPresent() 和 get() 是用来检查其包含的对象是否为空或不是,然后返回它,如:

Optional someValue = someMethod();

if (someValue.isPresent()) { // check

someValue.get().someOtherMethod(); // retrieve and call

}

  但是这种用法并不能体现Java 8的全部好处,你可以将Optional看成是需要使用某个T值的方法之间某种中间人或者协调者Mediator,而不只是一个普通对象的包装器。

  如果你有一个值返回类型T,你有一个方法需要使用这个值,那么你可以让 Optional 处于中间,确保它们之间交互进行,而不必要人工干预。

  这样,协调者Optional能够照顾T的值提供给你的方法作为输入参数,在这种情况下,如果T是空,可以确保不会出错,这样在T值为空时也可以让一切都正常运作,你也可以让Optional执行其他动作,如执行一段代码块等等,这样它就实际上是语言机制的很好的补充。

  下面这个案例涉及到Lambda表达式 方法引用,是将单词流中第一个以”L”开始单词取出,作为返回结果是一个Optional
使用ifPresent()

  这个案例的代码如下:

Stream names = Stream.of(“Lamurudu”, “Okanbi”, “Oduduwa”);

Optional longest = names

.filter(name -> name.startsWith("L"))

.findFirst();

longest.ifPresent(name -> {

    String s = name.toUpperCase();

    System.out.println("The longest name is "+ s);

});

  这里ifPresent() 是将一个Lambda表达式作为输入,T值如果不为空将传入这个lambda。那么这个lambda将不为空的单词转为大写输出显示。在前面names单词流寻找结果中,有可能找不到开始字母为L的单词,返回为空,也可能找到不为空,这两种情况都传入lambda中,无需我们打开盒子自己编写代码来判断,它自动帮助我们完成了,无需人工干预。

使用map()

  如果你想从Optional中返回一个值怎么办?使用 map(),如下:

Stream names = Stream.of(“Lamurudu”, “Okanbi”, “Oduduwa”);

Optional longest = names

.filter(name -> name.startsWith("L"))

.findFirst();

Optional lNameInCaps = longest.map(String::toUpperCase);

  使用Optional的map方法能够返回另外一个Optional,如上面的 LnameInCaps,因为传入map()的参数值也许会导致一个空值。

使用orElse()

  如果在T可能空时你需要一个值的话,那么可以使用 orElse(),它能在T值存在的情况下返回这个值,否则返回输入值。

Stream names = Stream.of(“Lamurudu”, “Okanbi”, “Oduduwa”);

Optional longest = names

.filter(name -> name.startsWith("Q"))

.findFirst();

String alternate = longest.orElse(“Nimrod”);

System.out.println(alternate); //prints out “Nimrod”

使用orElseGet()

  orElseGet() 方法类似于orElse(),但是不是直接返回输入参数,而是调用输入参数,返回调用的结果,这个输入参数通常是lambda:

Stream names = Stream.of(“Lamurudu”, “Okanbi”, “Oduduwa”);

Optional longest = names

.filter(name -> name.startsWith("Q"))

.findFirst();

String alternate = longest.orElseGet(() -> {

// perform some interesting code operation

// then return the alternate value.

return "Nimrod";

});

System.out.println(alternate);

使用 orElseThrow()

  orElseThrow()是在当遭遇Null时,决定抛出哪个Exception时使用:

Stream names = Stream.of(“Lamurudu”, “Okanbi”, “Oduduwa”);

Optional longest = names

.filter(name -> name.startsWith("Q"))

.findFirst();

longest.orElseThrow(NoSuchElementStartingWithQException::new);

总结,你能创建下面三种类型的Optional

Optional getSomeValue() {

// 返回一个空的Optional类型;

return Optional.empty();

}

Optional getSomeValue() {

SomeType value = …;

// 使用这个方法,值不可以为空,否则抛exception

return Optional.of(value);

}

Optional getSomeValue() {

SomeType value = …;

// 使用这个方法,值可以为空,如果为空返回Optional.empty

return Optional.ofNullable(value);

// usage

Optional someType = getSomeValue();

首先创建一个函数接口ResultSetProcessor :

@FunctionalInterface
public interface ResultSetProcessor {

public void process(ResultSet resultSet,
                    long currentRow)
                    throws SQLException;

}

下面做个简单查询案例,使用这个接口遍历

public static void select(Connection connection,
String sql,
ResultSetProcessor processor,
Object… params) {
try (PreparedStatement ps = connection.prepareStatement(sql)) {
int cnt = 0;
for (Object param : params) {
ps.setObject(++cnt, param));
}
try (ResultSet rs = ps.executeQuery()) {
long rowCnt = 0;
while (rs.next()) {
processor.process(rs, rowCnt++);
}
} catch (SQLException e) {
throw new DataAccessException(e);
}
} catch (SQLException e) {
throw new DataAccessException(e);
}
}

调用这个select语句如下:

select(connection, “select * from MY_TABLE”,(rs, cnt)-> {
System.out.println(rs.getInt(1)+” “+cnt)
});

select的第三个参数ResultSetProcessor这是个函数,所以我们传入的是一个匿名函数。
Streams API

   java 8提供了更强大的 Streams API,我们可以对ResultSet处理更加强大。创建一个自己的Tuple 类型,代表ResultSet中一行记录。

   下面我们将一个查询和ResultSet包装在一个Iterator中:

public class ResultSetIterator implements Iterator {

private ResultSet rs;
private PreparedStatement ps;
private Connection connection;
private String sql;

public ResultSetIterator(Connection connection, String sql) {
    assert connection != null;
    assert sql != null;
    this.connection = connection;
    this.sql = sql;
}

public void init() {
    try {
        ps = connection.prepareStatement(sql);
        rs = ps.executeQuery();

    } catch (SQLException e) {
        close();
        throw new DataAccessException(e);
    }
}

@Override
public boolean hasNext() {
    if (ps == null) {
        init();
    }
    try {
        boolean hasMore = rs.next();
        if (!hasMore) {
            close();
        }
        return hasMore;
    } catch (SQLException e) {
        close();
        throw new DataAccessException(e);
    }

}

private void close() {
    try {
        rs.close();
        try {
            ps.close();
        } catch (SQLException e) {
            //nothing we can do here
        }
    } catch (SQLException e) {
        //nothing we can do here
    }
}

@Override
public Tuple next() {
    try {
        return SQL.rowAsTuple(sql, rs);
    } catch (DataAccessException e) {
        close();
        throw e;
    }
}

}

这是一个遍历器,每次返回ResultSet的一行记录,返回类型是我们定义Tuple.

关于tuple定义可见源码。

我们和Stream绑定在一起如下:

public static Stream stream(final Connection connection,
final String sql,
final Object… parms) {
return StreamSupport
.stream(Spliterators.spliteratorUnknownSize(
new ResultSetIterator(connection, sql), 0), false);
}

Java 8 提供StreamSupport静态方法.stream来创建 java.util.stream.Stream实例,同时还需要java.util.stream.Spliterator,这是一个用来遍历和分区一个序列元素(集合)的特殊类型,有了它才能并行处理我们饿操作,而Spliterators 这是能够对已经存在的集合如java.util.Iterator.提供并行操作。

我们调用上面stream如下:

long result = stream(connection, “select TEST_ID from TEST_TABLE”)
.filter((t) -> t.asInt(“TEST_ID”) % 2 == 0)
.limit(100)
.count();

这是查询所有的TEST_ID,然后过滤掉所有非偶数,最后再运行一个计数。非常简单明了。如果你使用ORM/JPA等框架,可能无法让自己的SQL代码如此优雅直接了,它类似Hibernate的 criteria。

https://github.com/jexenberger/lambda-tuples

java8

  1. 实现Runnable线程案例

使用() -> {} 替代匿名类:

//Before Java 8:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(“Before Java8 “);
}
}).start();

//Java 8 way:
new Thread( () -> System.out.println(“In Java8!”) ).start();

Output:
too much code, for too little to do
Lambda expression rocks !!

你可以使用 下面语法实现Lambda:

(params) -> expression
(params) -> statement
(params) -> { statements }

如果你的方法并不改变任何方法参数,比如只是输出,那么可以简写如下:
() -> System.out.println(“Hello Lambda Expressions”);

如果你的方法接受两个方法参数,如下:

(int even, int odd) -> even + odd

2.实现事件处理

如果你曾经做过Swing 编程,你将永远不会忘记编写事件侦听器代码。使用lambda表达式如下所示写出更好的事件侦听器的代码。

// Before Java 8:
JButton show = new JButton(“Show”);
show.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(“without lambda expression is boring”);
}
});

// Java 8 way:
show.addActionListener((e) -> {
System.out.println(“Action !! Lambda expressions Rocks”);
});

在java 8中你可以使用Lambda表达式替代丑陋的匿名类。

3.使用Lambda表达式遍历List集合

//Prior Java 8 :
List features = Arrays.asList(“Lambdas”, “Default Method”,
“Stream API”, “Date and Time API”);
for (String feature : features) {
System.out.println(feature);
}

//In Java 8:
List features = Arrays.asList(“Lambdas”, “Default Method”, “Stream API”,
“Date and Time API”);
features.forEach(n -> System.out.println(n));

// Even better use Method reference feature of Java 8
// method reference is denoted by :: (double colon) operator
// looks similar to score resolution operator of C++
features.forEach(System.out::println);

Output:
Lambdas
Default Method
Stream API
Date and Time API

方法引用是使用两个冒号::这个操作符号。
4.使用Lambda表达式和函数接口

为了支持函数编程,Java 8加入了一个新的包java.util.function,其中有一个接口java.util.function.Predicate是支持Lambda函数编程:

public static void main(args[]){
List languages = Arrays.asList(“Java”, “Scala”, “C++”, “Haskell”, “Lisp”);

System.out.println(“Languages which starts with J :”);
filter(languages, (str)->str.startsWith(“J”));

System.out.println(“Languages which ends with a “);
filter(languages, (str)->str.endsWith(“a”));

System.out.println(“Print all languages :”);
filter(languages, (str)->true);

System.out.println(“Print no language : “);
filter(languages, (str)->false);

System.out.println(“Print language whose length greater than 4:”);
filter(languages, (str)->str.length() > 4);
}

public static void filter(List names, Predicate condition) {
for(String name: names) {
if(condition.test(name)) {
System.out.println(name + “ “);
}
}
}
}

Output:
Languages which starts with J :
Java
Languages which ends with a
Java
Scala
Print all languages :
Java
Scala
C++
Haskell
Lisp
Print no language :
Print language whose length greater than 4:
Scala
Haskell

//Even better
public static void filter(List names, Predicate condition) {
names.stream().filter((name) -> (condition.test(name)))
.forEach((name) -> {System.out.println(name + “ “);
});
}

你能看到来自Stream API 的filter方法能够接受 Predicate参数, 能够允许测试多个条件。

5.复杂的结合Predicate 使用

java.util.function.Predicate提供and(), or() 和 xor()可以进行逻辑操作,比如为了得到一串字符串中以”J”开头的4个长度:

// We can even combine Predicate using and(), or() And xor() logical functions
// for example to find names, which starts with J and four letters long, you
// can pass combination of two Predicate
Predicate startsWithJ = (n) -> n.startsWith(“J”);
Predicate fourLetterLong = (n) -> n.length() == 4;

names.stream()
.filter(startsWithJ.and(fourLetterLong))
.forEach((n) -> System.out.print(“\nName, which starts with
‘J’ and four letter long is : “ + n));

其中startsWithJ.and(fourLetterLong)是使用了AND逻辑操作。

6.使用Lambda实现Map 和 Reduce

最流行的函数编程概念是map,它允许你改变你的对象,在这个案例中,我们将costBeforeTeax集合中每个元素改变了增加一定的数值,我们将Lambda表达式 x -> x*x传送map()方法,这将应用到stream中所有元素。然后我们使用 forEach() 打印出这个集合的元素.

// applying 12% VAT on each purchase
// Without lambda expressions:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
for (Integer cost : costBeforeTax) {
double price = cost + .12*cost;
System.out.println(price);
}

// With Lambda expression:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost + .12*cost)
.forEach(System.out::println);

Output
112.0
224.0
336.0
448.0
560.0
112.0
224.0
336.0
448.0
560.0

reduce() 是将集合中所有值结合进一个,Reduce类似SQL语句中的sum(), avg() 或count(),

// Applying 12% VAT on each purchase
// Old way:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double total = 0;
for (Integer cost : costBeforeTax) {
double price = cost + .12*cost;
total = total + price;

}
System.out.println(“Total : “ + total);

// New way:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost)
.reduce((sum, cost) -> sum + cost)
.get();
System.out.println(“Total : “ + bill);

Output
Total : 1680.0
Total : 1680.0

7.通过filtering 创建一个字符串String的集合

Filtering是对大型Collection操作的一个通用操作,Stream提供filter()方法,接受一个Predicate对象,意味着你能传送lambda表达式作为一个过滤逻辑进入这个方法:

// Create a List with String more than 2 characters
List filtered = strList.stream().filter(x -> x.length()> 2)
.collect(Collectors.toList());
System.out.printf(“Original List : %s, filtered list : %s %n”,
strList, filtered);

Output :
Original List : [abc, , bcd, , defg, jk], filtered list : [abc, bcd, defg]

8.对集合中每个元素应用函数

我们经常需要对集合中元素运用一定的功能,如表中的每个元素乘以或除以一个值等等.

// Convert String to Uppercase and join them using coma
List G7 = Arrays.asList(“USA”, “Japan”, “France”, “Germany”,
“Italy”, “U.K.”,”Canada”);
String G7Countries = G7.stream().map(x -> x.toUpperCase())
.collect(Collectors.joining(“, “));
System.out.println(G7Countries);

Output :
USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA

上面是将字符串转换为大写,然后使用逗号串起来。

9.通过复制不同的值创建一个子列表

使用Stream的distinct()方法过滤集合中重复元素。

// Create List of square of all distinct numbers
List numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List distinct = numbers.stream().map( i -> i*i).distinct()
.collect(Collectors.toList());
System.out.printf(“Original List : %s, Square Without duplicates :
%s %n”, numbers, distinct);

Output :
Original List : [9, 10, 3, 4, 7, 3, 4], Square Without
duplicates : [81, 100, 9, 16, 49]

10.计算List中的元素的最大值,最小值,总和及平均值

//Get count, min, max, sum, and average for numbers
List primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x)
.summaryStatistics();
System.out.println(“Highest prime number in List : “ + stats.getMax());
System.out.println(“Lowest prime number in List : “ + stats.getMin());
System.out.println(“Sum of all prime numbers : “ + stats.getSum());
System.out.println(“Average of all prime numbers : “ + stats.getAverage());

Output :
Highest prime number in List : 29
Lowest prime number in List : 2
Sum of all prime numbers : 129
Average of all prime numbers : 12.9

在本教程中主要讲解Java 8新的函数式编程功能,熟悉这些新的 API:streams, 函数接口, map扩展和新的日期API。
接口的缺省方法

Java 8让我们能够增加非抽象方法实现到一个接口中, 使用default,这个特点就是 Extension Methods.

interface Formula {

double calculate(int a);
default double sqrt(int a) {
    return Math.sqrt(a);
}

}

接口 Formula 定义了一个默认方法sqrt. 该接口实现类只要完成接口中抽象方法calculate即可,而sqrt方法可以被外部使用。

ormula formula = new Formula() {

@Override
public double calculate(int a) {
    return sqrt(a * 100);
}

};

formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0

代码中formula 实现类是一个匿名类,在Java 8中有很多好的方法实现这种单个方法的匿名类。
Lambda表达式

先看看传统Java的代码例子:

List names = Arrays.asList(“peter”, “anna”, “mike”, “xenia”);

Collections.sort(names, new Comparator() {

@Override
public int compare(String a, String b) {
    return b.compareTo(a);
}

});

静态方法Collections.sort接受一个集合List和一个比较器comparator,后者是为了对集合中元素排序,一般我们都是创建一个匿名的比较器对象传递集合中。

Java 8使用Lambda表达式替代这种匿名类。

Collections.sort(names, (String a, String b) -> {

return b.compareTo(a);

});

代码相对简短,还可以再短小些:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

对于只有一个方法,你可以忽略{} 和 return语法,当然还可以再简短:

Collections.sort(names, (a, b) -> b.compareTo(a));

Java编译器会照顾你忽略了a和b的类型String。这称为语言的类型判断。

函数接口

Lambda表达式是一种函数语法,与Java的类型语言是两种不同性质的语法,如同南北两个不同方向,那么Java 8的Lambda表达式如何配合Java天生的类型系统呢?每个Lambda都对应一个给定的类型,主要是一个接口类型,也称为函数接口,只能包含一个抽象方法,每个类型的Lambda表达式与这个抽象方法匹配,因为默认default方法不是抽象方法,你可以在接口中自由增加自定义的default默认方法。

我们可以使用很多接口作为lambda表达式,只要这个接口只包含一个抽象方法,为了确保你的接口符合需要,你应当加入元注解 @FunctionalInterface. 编译器将会注意到这个元注解,如果你试图增加第二个抽象方法到接口中,它会抛出编译错误。

@FunctionalInterface

interface Converter {

T convert(F from);

}

Converter converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert(“123”);
System.out.println(converted); // 123

这段代码中,我们定义了接口Converter一个抽象方法,注意虽然使用了@FunctionalInterface ,但是不使用也是可以,那么这个接口中抽象方法就可以作为Lambda表达式使用,首先,我们定义了这个接口的实现:

Converter converter = (from) -> Integer.valueOf(from);

(from) -> Integer.valueOf(from)实际是抽象方法 T convert(F from)的实现具体细节,这里是将字符串转换为整数型。

然后,我们就可以直接调用这个接口:converter.convert(“123”)

方法和构造器的引用

上述代码如果使用静态方法引用static method references将会更加简化:

Converter converter = Integer::valueOf;
Integer converted = converter.convert(“123”);
System.out.println(converted); // 123

Java 8 使用符号 ::让你传递方法或构造器的引用,因为 Integer.valueOf是一个静态方法,其引用方式是 Integer::valueOf,我们还能引用一个对象的普通方法:

class Something {

String startsWith(String s) {

    return String.valueOf(s.charAt(0));

}

}

Something something = new Something();
Converter converter = something::startsWith;
String converted = converter.convert(“Java”);
System.out.println(converted); // “J”

这里引用的是类Something的方法startsWith,我们首先要创建这个类的实例,然后使用something::startsWith,这相当于实现了接口Converter的convert方法。不必像我们传统方式,通过Something implements Converter,然后在具体实现Converter的抽象方法convert。这样摆脱了子类对接口的依赖。

符号::也可以使用在构造器方法中:

class Person {

String firstName;
String lastName;
Person() {}

Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

}

}

下面是创建Person的工厂接口:

interface PersonFactory

{

P create(String firstName, String lastName);

}

有别于传统手工实现这个接口的方式,我们使用::将接口和实现粘合在一起。

PersonFactory personFactory = Person::new;
Person person = personFactory.create(“Peter”, “Parker”);

我们使用Person::new创建了一个指向Person构造器的引用,Java编译器将会自动挑选匹配PersonFactory.create方法签名的构造器。

Lambda作用域

从Lambda表达式访问外部变量非常类似匿名对象访问外部一样,匿名对象只能访问外部访问有final定义的变量。
访问本地变量:

final int num = 1;
Converter stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2);

这里(from) -> String.valueOf(from + num)表达式 访问了外部的变量num,区别于匿名类,这个变量不是必须加上final定义。下面也可以:

int num = 1;
Converter stringConverter = (from) -> String.valueOf(from + num);

这就是Lambda表达式比匿名类的好处。但是这不代表你可以修改num值,这里是隐式的final,下面语法是不可以的:

int num = 1;
Converter stringConverter = (from) -> String.valueOf(from + num);
num = 3;

一旦被写入了Lambda表达式的变量不能再被修改了。
访问字段和静态变量

正好和本地变量相反,我们在Lambda表达式中可以对实例的字段和静态变量进行读和写。无论这个字段或静态变量是否在lambda表达式中使用过。

class Lambda4 {

static int outerStaticNum;
int outerNum;

void testScopes() {

    Converter<Integer, String> stringConverter1 = (from) -> {
        outerNum = 23;
        return String.valueOf(from);
    };

    Converter<Integer, String> stringConverter2 = (from) -> {
        outerStaticNum = 72;
        return String.valueOf(from);
    };
}

}
访问接口的默认方法

还记得开始的formula案例吗?接口Formula定义了一个默认方法sqrt,它可以被匿名对象访问 ,但是不能被Lambda表达式访问。

默认方法不能在Lambda表达式中访问,下面代码不会编译通过:

Formula formula = (a) -> sqrt( a * 100);

前面谈了java8的lambda,现在来了解Java8的Monad,Monad是范畴理论中一个词语,但是就像我们吃披萨不用到意大利一样,不学习饶舌的范畴论我们也可以理解Monad。范畴论中是如此隐晦地定义:

a monad in X is just a monoid in the category of endofunctors of X。

翻译软件都不知道单词endofunctors 。了解了闭包 Lambda,我们基本可以理解Monad是一个个lambda串联起来,说得标准一些,如下:

A monad is a structure that puts a value in a computational context

Monad是一种结构,这种结构能够将一个值放入一个可计算的上下文中。因为Lambda是一个和上下文环境有关的表达式,所以,这里对Monad的上下文理解就比较容易。

  scala中的map就是一个monad,见:Scala入门之函数编程。下面我们看看在java8中如何自己实现一个monad。

M unit(A a);
M bind(M
ma, Function> f);

interface M {
 M map(Function f){
  return flatMap( x -> unit( f.apply(x) ) );//
 }
 M flatMap(Function> f);
}

这里的map定义了一个monad,将flatMap和Unit绑定组合(串联)在一起。

  下面看看monad的好处,假设有三个类,有关车的保险名称:

public class Person {
 private Car car;
 public Car getCar() { return car; }
}
public class Car {
 private Insurance insurance;
 public Insurance getInsurance() { return insurance; }
}
public class Insurance {
 private String name;
 public String getName() { return name; }
}

这里Insurance是保险,里面有保险名字,为了得到某个人的车辆的保险名字,需要下面:

String getCarInsuranceName(Person person) {
 if (person != null) {
  Car car = person.getCar();
  if (car != null) {
   Insurance insurance = car.getInsurance;
   if (insurance != null) {
    return insurance.getName()
   }
  }
 }
 return “Unknown”;
}

需要经过很深入嵌套式的判断然后才能返回。

  我们首先定义一个Optional :

public class Optional {
 private static final Optional<?> EMPTY = new Optional<>(null);
 private final T value;
 private Optional(T value) {
  this.value = value;
 }
 public Optional map(Function<? super T, ? extends U> f) {
  return value == null ? EMPTY : new Optional(f.apply(value));
 }
 public Optional flatMap(Function<? super T, Optional> f) {
  return value == null ? EMPTY : f.apply(value);
 }
}

然后对前面三个模型重新编写,加入Optional:

public class Person {
 private Optional car;
 public Optional getCar() { return car; }
}
public class Car {
 private Optional insurance;
 public Optional getInsurance() { return insurance; }
}
public class Insurance {
 private String name;
 public String getName() { return name; }
}

寻找一个人的车辆保险名字的查询如下:

String getCarInsuranceName(Optional person) {
 return person.flatMap(person -> person.getCar())
       .flatMap(car -> car.getInsurance())
       .map(insurance -> insurance.getName())
       .orElse(“Unknown”);
}

第一句:person.flatMap(person -> person.getCar()) 得到的是 Optional类型的car.

.flatMap(car -> car.getInsurance())得到的是Optional的Insurance;

.map(insurance -> insurance.getName())

```
目前,CAP(Consistency一致性、Availability可用性、Partition-tolerance分区可容忍性)理论普遍被当作是大数据[注]技术的理论基础。同时根据该理论,业界有一种非常流行和“专业”的认识,那就是:关系型数据库设计选择了C(一致性)与A(可用性),NoSQL数据库设计则不同。其中,HBase选择了C(一致性)与P(分区可容忍性),Cassandra选择了A(可用性)与P(分区可容忍性)。

该说法现在似乎已经成为一种经典认知,无论是初学大数据技术,还是已经有了相当经验的技术人员,都将其奉为真理。大家大概是认为,从CAP这样著名的理论推导出来的结论当然是权威而又正确的,最起码在形式上感觉是专业而又严肃的。有人甚至还将这种认知画成一个三角形图,三个顶点分别是C、A、P,三条边分别是关系型数据库、HBase与Cassandra,这样一来,CAP理论就显然更加神圣了。

实际上,这种认识是不准确,甚至是不正确的。暂且不说深入的分析与研究,只要先从表面上简单分析一下,你就能发现问题:难道说从理论上讲Cassandra就一定比HBase的可用性更高吗?而要彻底搞清楚这个问题,还得先从CAP理论本身开始研究。

常见的理解及分析

目前流行的对CAP理论解释的情形,是从同一数据在网络环境中的多个副本出发的。为了保证数据不会丢失,在企业级的数据管理方案中,一般必须考虑数据的冗余存储问题,而这应该是通过在网络上的其他独立物理存储节点上保留的另一份或多份数据副本来实现的(如附图所示)。因为在同一个存储节点上的数据冗余明显不能解决单点故障问题,这与通过多节点集群来提供更好的计算可用性的道理是相同的。

其实,不用做严格的证明也可以想见,如附图的情况,数据在节点A、B、C上保留了三份,如果对节点A上的数据进行了修改,然后再让客户端通过网络对该数据进行读取。那么,客户端的读取操作应该什么时候返回呢?

有这样两种情况:一种情况是要求节点A、B、C的三份数据完全一致后返回。也就是说,这时从任何一个网络节点读取的数据都是一样的,这就是所谓的强一致性读。很明显,这时数据读取的Latency要高一些(因为要等数据在网络中的复制)。同时,A、B、C三个节点中任何一个宕机,都会导致数据不可用。也就是说,要保证强一致性,网络中的副本越多,数据的可用性就越差。

另一种情况是,允许读操作立即返回,容忍B节点读取与A节点读取不一致的情况发生。这样一来,可用性显然得到了提高,网络中的副本也可以多一些,唯一得不到保证的是数据一致性。当然,对写操作同样也有多个节点一致性的情况,此处不再赘述。

可以看出,上述对CAP理论的解释主要是从网络上多个节点之间的读写一致性出发考虑问题的。而这一点,对于关系型数据库意味着什么呢?当然主要是指通常所说的Standby(关于分布式事务,涉及到更多考虑,随后讨论)情况。对此,在实践中我们大多已经采取了弱一致性的异步延时同步方案,以提高可用性。这种情况并不存在关系型数据库为保证C、A而放弃P的情况。而对海量数据管理的需求,关系型数据库扩展过程中所遇到的性能瓶颈,似乎也并不是CAP理论中所描述的那种原因造成的。那么,上述流行的说法中所描述的关系型数据库为保证C、A而牺牲P到底是在指什么呢?

因此,如果根据现有的大多数资料对CAP理论的如上解释,即只将其当作分布式系统中多个数据副本之间的读写一致性问题的通用理论对待,那么就可以得出结论:CAP既适用于NoSQL数据库,也适用于关系型数据库。它是NoSQL数据库、关系型数据库,乃至一切分布式系统在设计数据多个副本之间读写一致性问题时需要遵循的共同原则。

更深入的探究:

两种重要的分布式场景

在本文中我们要说的重点与核心是:关于对CAP理论中一致性C的理解,除了上述数据副本之间的读写一致性以外,分布式环境中还有两种非常重要的场景,如果不对它们进行认识与讨论,就永远无法全面地理解CAP,当然也就无法根据CAP做出正确的解释。但可惜的是,目前为止却很少有人提及这两种场景:那就是事务与关联。

先来看看分布式环境中的事务场景。我们知道,在关系型数据库的事务操作遵循ACID原则,其中的一致性C,主要是指一个事务中相关联的数据在事务操作结束后是一致的。所谓ACID原则,是指在写入/异动资料的过程中,为保证交易正确可靠所必须具备的四个特性:即原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)和持久性(Durability)。

例如银行的一个存款交易?务,将导致交易流水表增加一条记录,同时账户表余额也必须发生变化,这两个操作必须是在一个事务中全部完成,以保证相关数据的一致性。而前文解释的CAP理论中的C是指对一个数据多个备份的读写一致性。表面上看,这两者不是一回事,但实际上却是本质基本相同的事物:数据请求会等待多个相关数据操作全部完成才返回。对分布式系统来讲,这就是我们通常所说的分布式事务问题。

众所周知,分布式事务一般采用两阶段提交策略来实现,这是一个非常耗时的复杂过程,会严重影响系统效率,在实践中我们尽量避免使用它。在实践过程中,如果我们为了扩展数据容量将数据分布式存储,而事务的要求又完全不能降低,那么系统的可用性一定会大大降低。在现实中我们一般都采用对这些数据不分散存储的策略。

当然,我们也可以说,最常使用的关系型数据库因为这个原因,扩展性(分区可容忍性P)受到了限制,这是完全符合CAP理论的。但同时我们应该意识到,这对NoSQL数据库也是一样的。如果NoSQL数据库也要求严格的分布式事务功能,情况并不会比关系型数据库好多少。只是在NoSQL的设计中,我们往往会弱化,甚至去除事务的功能,该问题才表现得不那么明显而已。

因此,在扩展性问题上,如果要说关系型数据库是为了保证C、A而牺牲P,在尽量避免分布式事务这一点上来看,应该是正确的。也就是说:关系型数据库应该具有强大的事务功能,如果分区扩展,可用性就会降低。而NoSQL数据库干脆弱化,甚至去除了事务功能。因此,分区的可扩展性就大大增加了。

再来看看分布式环境中的关联场景。初看起来,关系型数据库中常用的多表关联操作与CAP理论就更加不沾边了。但仔细想想,也可以用它来解释数据库分区扩展对关联所带来的影响。对一个数据库来讲,采用了分区扩展策略来扩充容量,数据分散存储了,很显然多表关联的性能就会下降。因为我们必须在网络上进行大量的数据迁移操作,这与CAP理论中数据副本之间的同步操作,本质上也是相同的。

因此,如果要保证系统的高可用性,需要同时实现强大的多表关系操作的关系型数据库,在分区可扩展性上就遇到了极大的限制(即使是那些采用了各种优秀解决方案的MPP架构的关系型数据库,如TeraData,Netezza等,其水平可扩展性也是远远不如NoSQL数据库的)。而NoSQL数据库则干脆在设计上弱化,甚至去除了多表关联操作。那么,从这一点上来理解“NoSQL数据库是为了保证A与P,而牺牲C”的说法也是可以讲得通的。当然,我们应该理解,关联问题在很多情况下不是并行处理的优点所在,这在很大程度上与Amdahl定律相符合。

所以,从事务与关联的角度来看关系型数据库的分区可扩展性为什么受限的原因是最为清楚的。而NoSQL数据库也正是因为弱化,甚至去除了像事务与关联(全面地讲,其实还有索引等特性)等在分布式环境中会严重影响系统可用性的功能,才获得了更好的水平可扩展性。

那么,如果将事务与关联也纳入CAP理论中一致性C的范畴的话,问题就很清楚了:关于“关系型数据库为了保证一致性C与可用性A,而不得不牺牲分区可容忍性P”的说法便是正确的了。但关于“NoSQL选择了C与P,或者A与P”的说法则是错误的。所有的NoSQL数据库在设计策略的大方向上都是选择了A与P(虽然对同一数据多个副本的读写一致性问题的设计各有不同),从来没有完全选择C与P的情况存在。

结论

现在看来,如果理解CAP理论只是指多个数据副本之间读写一致性的问题,那么它对关系型数据库与NoSQL数据库来讲是完全一样的。它只是运行在分布式环境中的数据管理设施在设计读写一致性问题时需要遵循的一个原则而已,却并不是NoSQL数据库具有优秀的水平可扩展性的真正原因。而如果将CAP理论中的一致性C理解为读写一致性、事务与关联操作的综合,则可以认为关系型数据库选择了C与A,而NoSQL数据库则全都是选择了A与P,并没有选择C与P的情况存在。这才是用CAP理论来支持NoSQL数据库设计的正确认识。

其实,这种认识正好与被广泛认同的NoSQL的另一个理论基础相吻合,即与ACID对着干的BASE(基本可用性、软状态与最终一致性)。因为BASE的含义正好是指“NoSQL数据库设计可以通过牺牲一定的数据一致性和容错性来换取高性能的保持,甚至提高”,即NoSQL数据库都应该是牺牲C来换取P,而不是牺牲A。可用性A正好是所有NoSQL数据库都普遍追求的特性。(更多内容详见: http://www.cnw.com.cn/P/5523)
转自网界网:http://weekly.cnw.com.cn/weekly/htm2014/20140312_293558.shtml

配置报错

debian8 jessi下的mongodb3安装:http://hut8.io/posts/mongodb-3-on-debian-jessie/
Install using the repository for wheezy
sudo apt-key adv –keyserver ‘keyserver.ubuntu.com’ –recv ‘7F0CEB10’
echo ‘deb http://repo.mongodb.org/apt/debian wheezy/mongodb-org/3.0 main’ | sudo tee ‘/etc/apt/sources.list.d/mongodb-org-3.0.list’
sudo apt-get update
sudo apt-get install -y mongodb-org
Do not start MongoDB yet if you want to use WiredTiger!
Replace the configuration
The new YAML configuration format is way better than the old format, but the Debian wheezy package ships with the old format. So edit /etc/mongod.conf and replace it with equivalent YAML options. This is what I came up with. The engine: “wiredTiger” part is what prompted me to switch to the new configuration format.

storage:
dbPath: “/var/lib/mongodb”
engine: “wiredTiger”
wiredTiger:
collectionConfig:
blockCompressor: snappy

systemLog:
destination: file
path: “/var/log/mongodb/mongodb.log”
logAppend: true
timeStampFormat: iso8601-utc

net:
bindIp: “127.0.0.1”
port: 27017
WiredTiger
Hopefully you’ve specified in the configuration file that you want to use the wiredTiger storage engine before MongoDB starts for the first time. I did not do this. This leaves the /var/lib/mongodb directory full of files compatible only with MMAPv1. The easiest ay to fix this is just to delete all the files in the data directory after stopping MongoDB:

Don’t do this if you have data loaded already
sudo systemctl stop mongod.service
sudo rm -rf /var/lib/mongodb/*
Then add the engine: “wiredTiger” option, as shown above. Restarting should work fine. If you choose to invoke MongoDB without the init script / systemctl (which is useful with debugging), make sure that you use:

sudo -u mongodb mongod [… options]
Otherwise, mongod will create necessary files owned by either root or yourself, depending on whether you used sudo or not, which will lead to tricky issues such as the log (where error messages go!) not being writable by the mongodb user.

知乎

1
2
3
> 写篇文章时,2.8版本还没有出。据我的生产实践,Mongodb占据的磁盘空间比MySQL大得多,可以理解文档数据如Json这种格式,存在许多冗余数据,但空间占用大得不正常,甚至是传统数据库的三四倍,不太契合工程实践,应该有改善的余地。 查阅了一些资料,

具体理下Mongodb的空间分配。1. MongoDB每个库逻辑上包含许多集合(collection),物理上存储为多个数据文件,数据文件的分配是预先分配的,预分配的方式可以减少碎片,程序申请磁盘空间的时候更高效,但MongoDB预分配的策略可能导致空间的浪费。默认的分配空间的策略是:随着数据库数据的增加,MongoDB会不断分配更多的数据文件。每个新数据文件的大小都是上一个已分配文件的两倍( 64M, 128M, 256M, 512M, 1G, 2G, 2G, 2G ),直到预分配文件大小的上限2G。虽然2G的阀值可以调整,但一般运维等时候往往也不会去调整,就这点来说,可能导致空间的浪费。对于磁盘的空间的分配效率,我报以怀疑的态度,如果本身有IO瓶颈,预分配一个2G的文件,将可能导致服务出现严重性能问题。预分配文件,可以减少碎片,提高程序申请空间的效率,但有无必要一次分配初始化一个巨大的文件,这点值得商榷。 虽然预分配的机制,文档记载是可以关闭的,但一般使用NOSQL产品都是会使用默认配置,也建议使用默认的配置,因默认配置往往经历了长久的考验,没有那么多bug。2. MongoDB的文档在数据文件中是连续存储的,这点不同于一些关系数据库的做法(它们会把长记录拆分为两部分,溢出的那部分单独存放在另一处),如果没有预留足够的空间,那么更新可能导致原有空间放不下新的文档。当更新迫使引擎在BSON存储中移动文档时,存储碎片可以导致意外的延迟。对此MongoDB官方的解释是如下,“如果有足够的空间,在MongoDB中更新文档时,数据会在原地更新。如果更新后的文档大小大于已经分配的空间,那么文档会在一个新位置被重写。MongoDB最终会重用原来的空间,但这可能需要时间,而且空间可能会过度分配。在MongoDB 2.6中,默认的空间分配策略将是powerOf2Sizes,这个选项从MongoDB 2.2开始就已经提供了。该设置会将MongoDB分配的空间大小向上取整为2的幂(比如,2468163264等等)。该设置会降低需要移动文档的几率,并使空间可以更高效地重用,结果是更少的空间过度分配和更可预测的性能。用户仍然可以使用精确匹配的分配策略,如果文档大小不增加,该策略更节省空间。”显然,这种策略又将导致空间的浪费,特别是对于导入只读类型的数据。3. MongoDB不支持数据文件的压缩,也不能回收空间。它所使用的碎片整理的策略,可能是在一个新的地方重写,而不是对旧的碎片进行整理、合并。4. 不校验数据页。页面校验对于数据库是非常重要的,有助于识别存储设备异常。就这点,MongoDB存储的数据是不安全的,也许哪天就起不来了。

知乎

1
mongodb写入数据是靠操作系统的缓存来实现的,我们需要清楚这中间发生了什么?对于更新操作,可能存在以下两种状态。pre-fsync post-update state   对数据的变更已经写入journal日志,但日志并没有刷新(fsync)的磁盘上post-fsync post-update state 对数据的变更已经刷新到日志文件,这个时候,如果宕机,可以利用journal日志进行灾难恢复。那么在pre-fsync post-update state阶段,如果宕机了。很可能会丢失数据,因为连日志都没有刷新到磁盘。此时会出现这样一种情况,用户读取到了新的数据,然后宕机了,这个数据被丢失了。 用户读到了数据,但这个数据后来又不存在了,这种情况是否有问题呢? 得看应用。mongodb提供了其他选项,写入操作可被block,直到从库应答后(具体机制我不记得了,可能和MySQL的半同步类似的道理)。但主库上面的读取操作也许仍然可以读取到新的值(不必等从库应答)注意:由于mmap的机制,如果只是mongod进程挂了。那么不会丢失数据,因为操作系统没有挂,操作系统会继续写入完数据的。还有一点需要留意,对于数据库来说,比如MySQL在读取和写入页块的时候,会对页块进行校验,但mongodb不会,对于重要的数据来说,这是不能接受的。很可能因为硬件的故障导致数据异常而DBA不知道。