Java8 学习笔记,PPT 备忘录~
默认方法让接口 增加新方法 的同时又能保证对使用这个接口的 老版本代码的兼容
如果在面向接口编程里面,功能 1 要新增一个方法,在接口中添加了该方法,则实现该接口的其他类都得再去实现这个方法
所以在 Java8 中新增了接口默认方法,默认方法让接口 增加新方法 的同时又能保证对使用这个接口的 老版本代码的兼容
假设 gif 中的 Exoplayer 、 Exoplayer Wrapper 和 app 是分别三个团队,当 app 发现一个 bug 后一层层往上报,最终发现是 Exoplayer 团队的 bug,那么 Exoplayer 团队将 bug fix 了,发布了 Exoplayer 2.0 :
Exoplayer 2.0 不是二进行兼容的,那么 Exoplayer Wrapper 也需要重新编译发布 2.0 版本 Exoplayer 2.0 是二进行兼容的,那么 Exoplayer Wrapper 无需重新编译, app 直接引用 Exoplayer 2.0 边行 接口的修改是二进制兼容的,但是如果这样修改了的话会让程序出现不可控的异常
default
一个类只能 继承一个 父类,可以 实现多个 接口
但是加了默认方法之后,在默认方法的使用上出现了一些冲突
default
Optional是 Java 8 提供的为了解决 null 安全问题的一个 API
def path = mManagerData?.getData()?.getInfo()?.getPath() : getDefaultPath();
Java 8 为啥不引入 安全操作符 呢?先来看看 Optional :
public String getPath() {
return Optional.ofNullable(mManagerData)
.map(ManagerData::getData)
.map(Data::getInfo)
.map(Info::getPath).orElse(getDefaultPath());
}
整个样式结构跟 Stream 很相似,当然搭配着 Stream 食用味道更佳
public Optional<ManagerData> getFirstDraftData() {
LinkedHashMap<Long, String> linkedHashMap = mDB.getFirst();
if (linkedHashMap.size() != 1) {
return Optional.empty();
}
ArrayList<ManagerData> dataList = new ArrayList<>();
ManagerData data = null;
for (Map.Entry<Long, String> entry : linkedHashMap.entrySet()) {
long id = entry.getKey();
String content = entry.getValue();
getDirtyOrCleanDraft(dataList, id, content);
if (dataList.size() > 0) {
data = dataList.get(0);
}
}
return Optional.of(data);
}
如果是对外提供功能,返回值是一个 Optional 的话更能让调用者知道该怎么操作
public String getPath() {
return Optional.ofNullable(mManagerData)
.map(ManagerData::getData)
.map(Data::getInfo)
.map(Info::getPath).orElseGet(() -> getDefaultPath())
}
orElse 方法的延时调用版 orElesGet :如果是 orElse 默认情况下会先去初始化失败情况下的值,如果是 orElesGet 的话,只有失败了的情况才会走其中的方法
用更简洁流畅的代码完成一个功能
public static List<Apple> filterGreenApples(List<Apple> list) {
List<Apple> result = new ArrayList<>();
for (Apple apple : list) {
if ("green".equals(apple.color)) {
result.add(apple);
}
}
return result;
}
第一个版本,需要将绿颜色的苹果给挑选出来
public static List<Apple> filterColorApples(List<Apple> list, String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : list) {
if (color.equals(apple.color)) {
result.add(apple);
}
}
return result;
}
经过第一个版本后,需求改了,颜色可能是各种各样的,那么 将颜色作为参数传进来 的方式来满足需求
####版本 3
public static List<Apple> filterColorOrWeightApples(List<Apple> list, String color, int weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple : list) {
if (color.equals(apple.color) || weight > apple.weight) {
result.add(apple);
}
}
return result;
}
又经过一个版本,判断条件可能不止颜色,还有重量,那么再将重量作为参数传进来;但是这样的做法会 显得越来越笨拙
public static List<Apple> filter(List<Apple> list, Filter filter) {
List<Apple> result = new ArrayList<>();
for (Apple apple : list) {
if (filter.satisfied(apple)) {
result.add(apple);
}
}
return result;
}
interface Filter {
boolean satisfied(Apple apple);
}
那么优化一下,通过类似策略模式的方式,放入不同的算法来实现
class WeightFilter implements Filter {
@Override
public boolean satisfied(Apple apple) {
return apple.weight > 100;
}
}
class ColorFilter implements Filter {
@Override
public boolean satisfied(Apple apple) {
return "red".equals(apple.color);
}
}
list = filter(list, new WeightFilter());
list = filter(list, new ColorFilter());
这样就把行为抽象出来了,代码适应了需求的变化,但是这个过程很 啰嗦 ,因为需要声明很多只需要实例化一次的类
list = filter(list, new Filter() {
@Override
public boolean satisfied(Apple apple) {
return apple.weight > 100;
}
});
再优化一下上个版本的问题,改为用匿名内部类,虽然上个版本的问题解决了,但是匿名内部类看起来很笨重,占用空间多,除此之外,有时候看起来还特别费力
int weight = 100;
list = filter(list, new Filter() {
int weight = 200;
@Override
public boolean satisfied(Apple apple) {
return apple.weight > weight;
}
});
此时的 weight 看起来就比较费力,到底引用的是外部的还是内部的呢
list = filter(list, apple -> apple.weight > 100);
通过 lambda 的方式,再来解决匿名内部类带来的问题
只定义了一个抽象方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
如果再往 Runnable 中加方法,会导致编译失败
Stream 可以更好的、更为流畅的、更为语义化的操作集合
Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言 + 多核时代综合影响的产物。
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
数据操作又可以分为无状态的 (Stateless) 和有状态的 (Stateful) ,无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果
终端操作又可以分为短路操作和非短路操作,短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素
结构图,栗子中要用到
class Dish {
String type;//小吃,素菜,荤菜
String name;//名字
int price;//钱
}
从菜单中挑选出 荤菜 且 菜名带『肉』字 且 价格最贵的 3 个
List<String> filter(List<Dish> list) {
List<Dish> typeResult = new ArrayList();
for (Dish dish : list) {
if ("荤菜".equals(dish.type)) {
typeResult.add(dish);
}
}
List<Dish> meatResult = new ArrayList<>();
for (Dish dish : typeResult) {
if (dish.name.contains("肉")) {
meatResult.add(dish);
}
}
Collections.sort(meatResult, new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
return o1.price - o2.price;
}
});
List<String> nameList = new ArrayList<>();
int count = 3;
if (meatResult.size() < 3) {
count = meatResult.size();
}
for (int i = 0; i < count; i++) {
nameList.add(meatResult.get(i).name);
}
return nameList;
}
List<String> filter2(List<Dish> list) {
return list.stream().filter(dish -> "荤菜".equals(dish.type))
.filter(dish -> dish.name.contains("肉"))
.sorted(Comparator.comparingInt(o -> o.price))
.limit(3)
.map(dish -> dish.name)
.collect(Collectors.toList());
}
List<String> filter2(List<Dish> list) {
return list.stream().filter(dish -> "荤菜".equals(dish.type))
.filter(dish -> dish.name.contains("肉"))
.sorted(Comparator.comparingInt(o -> o.price))
.limit(3)
.map(dish -> dish.name)
.collect(Collectors.toList());
}
像 SQL,像 builder 构建者模式
集合是一种数据结构;它的主要关注点是在内存中组织数据,而且集合会在一段时间内持久存在。集合通常可用作流管道的来源或目标,但流的关注点是计算,而不是数据;每个中间操作都返回流,让整个流能串起来
自动并行是采用的 Fork / Join 框架
把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果
线程 1 负责处理 4 个任务,线程 2 负责处理 4 个任务,当线程 1 任务处理完了,但线程 2 还在处理任务。干完活的线程与其闲着,不如去帮其他线程干活。于是它就去其他线程的队里里窃取一个任务来执行。在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务之间的竞争,通过会使用双端队列,被窃取任务线程永远从双端队列的头部执行任务,而窃取任务线程用于从双端队列的尾部拿任务。