文章目录
1.1 为什么学习java8
java8 之前,必须利用线程才能使用多核。java的演变之路看来一直都是想让并发编程变得更加的容易,出错更少。
java1.0 里面有线程和锁。
java 5 中添加了工业级的构建模块,线程池和并发集合。
java 7 中 fork/join 框架。使得并行变得更加的实用。
例子体验一下java8
- stream api。
- 支持许多处理数据的并行操作。
- 和数据库查询语句的思路类似,用更加高级的方式表达想要的东西。实现通过Stream 库。选择最佳的执行方式。不需要开发者考虑 synchronized 编写代码。
- 向方法传递代码的技巧。
- 接口中默认的方法。
1.1 java 怎么还在变化?
c/c++ 仍然是构建操作系统和各种嵌入式系统的流行工具,是因为虽然他们编出来的程序安全性不佳,但是占用的资源少。缺乏安全性可能会导致程序意外崩溃。
1.2 流处理
java8 在 java.util.stream 中添加了一个Stream Api; Stream 就是一系列T 类型的项目。 可以将它看成是一种比较花哨的迭代器。
Stream Api 的很多方法可以连接起来形成一个复杂的流水线,就像是之前例子里面 链接起来的 Unix 命令一样的。
这种编程的做法关键在于,我们可以在一个更加抽象的层次上面编写 java8程序了。
思路变成了 : 怎么把一个流变成另外一个流
- 自动支持流水线并行操作。
1.3 用行为参数化把代码传递给方法
将方法作为参数传递给
1.4 并行与共享的可变数据
- 前面stream api 的并行 只有假定你的代码存在多个副本独立运行的情况。
1.5 方法和lambda 作为一等公民
让方法作为值构成其他若干java 8 功能,(如 Stream ) 的基础。
-
方法引用
筛选一个目录中的所有隐藏文件demo
java8 之前的实现方式
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isHidden();
}
})
java8
File[] hiddenFiles = new File(".").listFiles(File::isHidden)
说明 我们已经有了函数 isHidden,
::
这里表示的是方法引用 的语法。就是说把这个方法作为值。将这个方法作为值的参数 传递给listFile 方法。
在java8里面写下File::isHidden 的时候,你就创建了一个方法引用,你同样可以传递它。
- lambda 匿名函数
除了可以命名函数称为一等值之外。java8 另外一个更广义的将函数作为值的思想,包括Lambda 匿名函数。
//表示调用时给定参数 x 就返回 x + 1 的函数
(int x) -> x + 1
同样的也可以新建一个类 MyMathUtils 这个类里面定义一个add1 方法。‘
MyMathUtils::add1
这种方式也是可以的,但是新的lambda 语法更加的简洁。
总的来说“编写把函数作为一等值来传递的程序”
1.5.1传递代码:例子
传递代码说的是,java 8 会把条件代码作为参数传递进去,这样就能够避免filter 方法出现重复代码。
public static boolean isGreenApple(Apple apple) {
return "green".equls(apple.getColor());
}
public static boolean isHeavyApple(Apple apple) {
return apple.getWeight() > 150;
}
//写出来是为了清晰(平常只要从java.util.function导入就可以了)
public interface Predicate<T>{
boolean test(T t);
}
// 方法作为Predicate 参数P 传递进去
static List<Apple> filterApples(List<Apple> inventory,Predicate<Apple> p) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if (p.test(apple)) { // 苹果符合 P 所代表的条件吗
result.add(apple);
}
}
return result;
}
//调用方式
filterApples(inventory,Apple::isGreenApple);
//或者是
filterApples(inventory,Apple::isHeavyApple);
什么是谓词?
数学上常常用来表示一个类似函数的东西。接受一个参数值,并返回ture 或则是 false。
前面的代码传递了方法 Apple::isGreenApple (接受参数Apple 并返回一个Boolean给FilterApples)
Function<Apple,Boolean>
//使用 Predicate<Apple> 是更标准的方式,效率也会高一点。这避免了把boolean 封装在Boolean 里面。
1.5.2 从传递方法到 Lambda
把方法作为值来传递显然很有用,但是要是为类似于isHeavyApple 和 isGreenApple 这种比较简短的只用一次两次的方法写一堆定义有点烦人,不过java 8 也解决了这个问题。引入了新的方式 匿名函数或者是lambda.
filterApples(inventory,(Apple a) -> "green".equals(a.getColor()));
// 或者
filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
// 或者
filterApples(inventory, (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()) );
可以不需要为只用一次的方法写定义;代码更干净,更清晰,因为你不用找自己到底传递了什么代码。
如果方法比较复杂,不能用简短的几行来定义一个方法的话,还是应该用方法引用来指向一个有描述性名称的方法。而不是使用lambda。
以代码的清晰度为准
java 上加上Filter 和几个相关的东西作为通用库方法就足以让人满意了。
filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
// 有相关的类库
static <T> Collection<T> filter(Collection<T> c, Predicate<T> p);
filter(inventory, (Apple a) -> a.getWeight() > 150 );
/* 但是,为了更好地利用并行,Java的设计师没有这么做。Java 8中有一整套新的类集合
API——Stream,它有一套函数式程序员熟悉的、类似于filter的操作,比如map、reduce,还
有我们接下来要讨论的在Collections和Streams之间做转换的方法。*/
但是,为了更好地利用并行,Java的设计师没有这么做。java8中有一整套新的类集合 API——Stream,它有一套函数式程序员熟悉的、类似于filter的操作,比如map、reduce,**还 有我们接下来要讨论的在Collections和Streams之间做转换的方法。
1.6 流
几乎每个程序都会制造和处理集合。集合用起来不总是那么理想。需要从一个列表中筛选出金额较高的交易,然后按照货币分组。
// 建立累积交易分组的Map
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
// 遍历交易的 List
for (Transaction transaction : transactions) {
// 筛选金额较高的交易
if(transaction.getPrice() > 1000){
// 提取交易的货币
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency,transactionsForCurrency);
}
// 将当前遍历的交易添加到具有同一货币的交易 List 中。
transactionsForCurrency.add(transaction);
}
}
Stream Api 的使用
import static java.util.stream.Collectors.toList;
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream()
.filter((Transaction t) -> t.getPrice() > 1000)
.collect(groupingBy(Transaction::getCurrency));
Collection Api 相比,Stream Api 处理数据的方式非常的不同。用集合的话,需要自己做迭代过程。你需要用for-each 循环一个一个做迭代过程。---- 这种迭代方式称为 外部迭代
Stream Api 我们根本不需要操心循环的事情。数据处理完全是在内部进行的。可以把这种思想叫做 内部迭代
1.6.1 多线程并非易事
java8 也用Stream Api 解决了两个问题:集合处理时的套路,难以利用的多线程。其实一些高级的如 filterApples
这些操作是完全可以并行化的。
Collection 主要是 为了存储和访问数据,Stream 主要是用于描述对数据的计算。
几乎免费的并行?
一般是将 collection 转换成 Stream 进行并行处理,然后转换回List
顺序处理
import static java.util.stream.Collectors.toList;
List<Apple> heavyApples =
inventory.stream().filter((Apple a) -> a.getWeight() > 150)
.collect(toList());
并行处理
import static java.util.stream.Collectors.toList;
List<Apple> heavyApples =
inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150)
.collect(toList());
java 中的并行与无共享可变状态
函数式编程 中函数的主要意思是 “把函数作为一等值”,第二层意思就是,执行时在元素之间无互动。
1.6.2默认方法
如何改变已经发布的接口而不破坏已有的实现呢?
java8 的解决方法就是打破最后一环。
接口如今可以包含实现类没有提供实现的方法签名了!缺失的方法主体随接口提供了,而不是由实现类提供。
这就给接口设计者提供了一个扩充接口的方式,而不会破坏现有的代码。Java 8在接口声明 中使用新的default关键字来表示这一点。
1.7 函数式编程的其他好思想
- lambda 作为一等值
- 没有共享状态的时候,函数或方法可以有效,安全地并行执行。
2 通过行为参数化传递代码
行为参数化可以帮助你处理频繁变更的需求的一种软件开发模式。
2.1 应对不断变化的需求
编写能够应对变化的需求的代码并不容易。
2.1.1需求从列表中选出特定属性的元素。
筛选出绿色的苹果
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<Apple>();
for(Apple apple: inventory){
// 条件筛选这里仅仅筛选出绿色的苹果
if( "green".equals(apple.getColor() ) {
result.add(apple);
}
}
return result;
}
把颜色作为参数
增加颜色作为参数
public static List<Apple> filterApplesByColor(List<Apple> inventory,
String color) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple: inventory){
if ( apple.getColor().equals(color) ) {
result.add(apple);
}
}
return result;
}
// 通过颜色传参可以得到不同颜色的苹果。
List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");
通过重量选择苹果,苹果的重量一般是
public static List<Apple> filterApplesByWeight(List<Apple> inventory,
int weight) {
List<Apple> result = new ArrayList<Apple>();
For (Apple apple: inventory){
if ( apple.getWeight() > weight ){
result.add(apple);
}
}
return result;
}
打破了DRY 原则。软件工程角度来说这样太笨重了。
第三次尝试,每个能想到的属性都做筛选。
public static List<Apple> filterApples(List<Apple> inventory, String color,
int weight, boolean flag) {
List<Apple> result = new ArrayList<Apple>();
for (Apple apple: inventory){
if ( (flag && apple.getColor().equals(color)) ||
(!flag && apple.getWeight() > weight) ){
result.add(apple);
}
}
return result;
}
这样写非常的笨重。
List<Apple> greenApples = filterApples(inventory, "green", 0, true);
2.2 行为参数化
需要一种比添加参数更好的方法来应对需求的变化。
-
对选择的标准进行建模
- 需要根据apple 的某些属性来返回一个boolean 值。-----谓词
定义一个接口对选择标准建模
public interface ApplePredicate{
boolean test (Apple apple);
}
可以用 applepredicate 的多个接口的实现代表不同的选择标准了。
选出苹果的重量
public class AppleHeavyWeightPredicate implements ApplePredicate{
public boolean test(Apple apple){
return apple.getWeight() > 150;
}
}
选出绿色苹果
public class AppleGreenColorPredicate implements ApplePredicate{
public boolean test(Apple apple){
return "green".equals(apple.getColor());
}
}
上面写的两种方式和策略模式比较相关,它让你定义一族算法,把它们封装起来,然后在运行时选择一个算法。
不同的策略就是AppleHeavyWeightPredicate 和 AppleGreenColorPredicate
如何利用 applePredicate的不同实现,需要让filterApples 方法接受applePredicate 对象,对apple做条件测试。这就是行为参数化。让方法接受多种行为。或者是参数,并在内部使用。来完成不同的行为。
java8 collection 接口
removeif() 示例
作用:
删除集合中符合条件的成员,empty集合也可以,但不能是null。
/**
* 删除集合中符合条件的成员,empty集合也可以。
*/
private static void removeIfTest() {
List<String> list = Lists.newArrayList("1","12","13","14","15","0");
System.out.println("初始时:"+ list.toString());
list.removeIf(s -> s.contains("1"));
System.out.println("过滤完:" + list.toString());
}