由于最近总监要求学习Java 8的一些知识,就去网上找了 一套教程来学习学习,将学习结果做一个小的总结记录,方便以后使用;
主要内容:
其中,最主要的部分为Lambda表达式与Stream API;
速度更快 代码更少(增加了新的语法Lambda表达式) 强大的Stream API 便于并行 最大化减少空指针异常(Optional)
哈希算法 数组-链表-红黑树
Java8中对HashMap的改变。(优先选择哈希表)
ConcurrentHashMap (并发级别:默认16个 concurrentLevel=16)
Java8中改为CAS算法
CAS算法比锁效率高,是底层操作系统支持的算法。
栈,堆,方法区。
方法区属于堆中永久区(PremGen)的一部分。
但是将方法区放置在堆区外。
方法区:加载类信息;几乎不会被垃圾回收机制回收;会被垃圾回收机制回收,但是回收条件比较苛刻。
JYM厂商很多种:Oracle-SUN、Hotspot、Oracle JRocket、IBM J9 JVM;
当方法区快满的时候,垃圾回收机制开始回收。
Java 8后,没有永久区了,改为MetaSpace元空间;其使用的为物理内存。
物理内存较大,垃圾回收机制使用的概率也变小。默认物理内存多大,元空间多大。
Java 8以前的JYM调优参数也改变了。
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递);
可以写出更简洁、更灵活的代码。
作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
使用Lambda表达式以后;
需求:获取当前公司员工年龄大于35的员工信息;
首先;先创建一个Employee的类,里面包含属性name,age,salary,添加set、get方法,toString方法,
以及无参和有参构造函数;
输出:
需求:获取当前公司中员工工资大于5000的员工信息
会发现以前的操作方法十分繁复冗余,那么可以通过哪些方式可以来优化代码呢?
采用设计模式。 设计接口 ;
然后在类中实现该接口;
优化后的写法:
然后
输出:
如果想使用工资过滤,则新建类实现MyPredicate接口;
然后在测试类中编写代码;
输出:
缺点:每次实现一个策略,必须新建一个类;
然后;
输出:
Lambda表达式:
输出:
输出:
private static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes) { return dishes.stream() // 选出400卡路里以下的菜肴 .filter(d -> d.getCalories() < 400) // 按照卡路里排序 .sorted(comparing(Dish::getCalories)) // 提取菜名 .map(Dish::getName) // 转为集合 .collect(toList()); }
为了利用多核架构并行执行代码,将stream()改为parallelStream()。
private static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes) { return dishes .parallelStream() // 选出400卡路里以下的菜肴 .filter(d -> d.getCalories() < 400) // 按照卡路里排序 .sorted(comparing(Dish::getCalories)) // 提取菜名 .map(Dish::getName) // 转为集合 .collect(toList()); }
可以把几个基础操作链接起来,来表达复杂的数据处理流水线(在 filter 后面接上sorted 、 map 和 collect 操作),同时保持代码清晰可读。 filter 的结果被传给了 sorted 方法,再传给 map 方法,最后传给 collect 方法。
流:从支持数据处理操作的源生成的元素序列。
元素序列:就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList 与 LinkedList )。但流的目的在于表达计算,比如你前面见到的filter 、 sorted 和 map 。集合讲的是数据,流讲的是计算。
源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可并行执行。
此外,流操作有两个重要特点。
List<Dish> menu = Dish.MENU; // 从menu获得流 List<String> threeHighCaloricDishNames = menu.stream() // 通过链式操作,筛选出高热量的菜肴 .filter(d -> d.getCalories() > 300) // 获取菜名 .map(Dish::getName) .limit(3) .collect(Collectors.toList()); // [pork, beef, chicken] System.out.println(threeHighCaloricDishNames);
集合与流之间的差异就在于什么时候进行计算。集合是一个内存中的数据结构,它包含数据结构中目前所有的值,集合中的每个元素都得先算出来才能添加到集合中。
相比之下,流则是在概念上固定的数据结构,其元素则是按需计(懒加载)算的。需要多少就给多少。这是一种生产者与消费者的关系。从另一个角度来说,流就像是一个延迟创建的集合:只有在消费者要求的时候才会生成值。与之相反,集合则是急切创建的(就像黄牛囤货一样)。
流只能遍历一次
和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了)。例如以下代码会抛出一个异常,说流已被消费掉了:
List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action"); Stream<String> s = names.stream(); s.forEach(System.out::println); // 再继续执行一次,则会抛出异常 s.forEach(System.out::println);
外部迭代与内部迭代
使用Collection接口需要用用户去做迭代(比如用for-each),这个称为外部迭代。反之,Stream库使用内部迭代,它帮你把迭代做了,还把得到的流值存在了某个地方,你只要给出一个函数说要干什么就可以了。下面的代码说明了这种区别。
集合:使用for-each循环外部迭代:
// 集合:使用for-each循环外部迭代 List<Dish> menu = Dish.MENU; List<String> names = new ArrayList<>(); for (Dish dish : menu) { names.add(dish.getName()); }
集合:用背后的迭代器做外部迭代。
List<String> names = new ArrayList<>(); Iterator<String> iterator = menu.iterator(); while(iterator.hasNext()) { Dish d = iterator.next(); names.add(d.getName()); }
流:内部迭代
List<String> names = menu.stream() .map(Dish::getName) .collect(toList());
List<String> names = menu.stream() // 中间操作 .filter(d -> d.getCalories() > 300) // 中间操作 .map(Dish::getName) // 中间操作 .limit(3) // 将Stream转为List .collect(toList());
filter、map和limit可以连成一条线,collect触发流水线执行并关闭它。可以连起来的称为中间操作,关闭流的操作可以称为终端操作。
诸如filter和sorted等中间操作会返回一个流。让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理它们懒得很。这就是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
List<String> names = menu.stream() .filter(d -> { System.out.println("filtering:" + d.getName()); return d.getCalories() > 300; }) .map(dish -> { System.out.println("mapping:" + dish.getName()); return dish.getName(); }) .limit(3) .collect(toList()); System.out.println(names);
执行结果:
filtering:pork mapping:pork filtering:beef mapping:beef filtering:chicken mapping:chicken [pork, beef, chicken]
终端操作会从流的流水线生产结果。其结果是任何不是流的值,比如List、Integer,甚至是void。例如,在下面的流水线中,foreachh返回的是一个void的终端操作,它对源中的每道菜应用一个Lambda。把System.out.println()传递给foreach,并要求它打印出由menu生成的流中每一个Dish:
menu.stream().forEach(System.out::println);
流的使用一般包括三件事:
终端: