一
1.新特性主要内容:
- Lambda 表达式
- 函数式接口
- 方法引用 / 构造器引用
- Stream API
- 接口中的默认方法 / 静态方法
- 新时间日期 API
- 其他新特性
2.新特性作用:
- 速度更快:
hashMap的改动:
jdk1.7的hashMap:
就是hash表,我们称它为hash表,但是底层其实就是数组,这个数组中存的都是entry。用这种数组有什么好处呢?我们知道数组都得有索引值,比如0,1,2,3,4。当新添加一个值时,通过hash算法算出数组的索引值,根据索引值找到数组的位置。看这个位置是否有对象在,如果没有对象在就直接存储。如果有对象再通过equals比较两个对象的内容,如果内容一样它的value就把以前的value覆盖掉了,等于还是保存的一个对象。如果内容不一样那就形成链表,后加的放前边做表头,这种情况称他为碰撞,这种情况我门应该尽量避免。为什么要避免呢,因为如果这里的元素过多,那就会效率低。因为如果我再加一个对象的话,加入这里我需要跟这个索引里的对象都进行比较内容,这就效率很低了。碰撞是不可避免地,因为你的数组索引值就那么些,不管你的hash算法怎么做,他最终对应的还是那几个数组索引值。但是我们可以避免里面的元素过多,这时候java开发者就提出了叫加载因子的东西,加载因子默认是0.75。0.75指的是当元素到达hash表的75%的时候进行扩容。为什么不到100%扩容?因为我可能hash算法算出的索引只有123,0和4都没有对象进去,那我永远到达不了100。一旦扩容的话,他就会吧链表里面的内容重排序,运算成新的位置,比如我这个对象本来在索引1里,重新运算他就到5里了。这样的话,碰撞的概率就降低了,这就是java7的方式。
java8的hashMap:
即使是有扩容的机制,还是避免不了碰撞,这样效率还是低,于是在java8之后又做了改变。以前是数组+链表,现在是数组+链表+红黑树。什么时候变红黑树呢?当你碰撞的元素个数大于8,并且在总元素(或者叫总容量)大于64,就把链表转变为红黑树。转变为红黑树的好处:除了添加以外其余的效率都比链表高。这里进行扩容也不需要重新排序,只需要把以前数组总索引+当前索引就行,加入我现在的索引是3,5(01234总索引是5)+3=8,新的位置就是8。这里的原理就是红黑树的原理,也就是二叉树,左小右大,比当前元素小的元素放左边,在小继续放。这里就是红黑树的知识了,可以自己看看,这里不再赘述,只是简单说一下。
hashSet也是同理。
java7的ConcurrentHashMap:
并发级别是16,就是默认分成16个段,每个段里面又分16张表。
java8的ConcurrentHashMap:
在java8之后这个段就不用了,改为了CAS算法。为什么取消呢?因为有可能段太大,那就浪费了。可是如果太小了就造成每个段中元素过大,效率过低。现在就改成了链表+红黑树,并且还采用了CAS算法,他要比锁的效率高(cas是并发的算法,无锁比较,比较之前的值和现在的值是不是期望值)。所以ConcurrentHashMap效率也提高了。
java7的内存结构:
有栈,堆和方法区(PremGen),还有一部分垃圾回收区。方法区是属于堆的永久区的一部分,永久区主要存的就是一些类信息,永久区几乎不被垃圾回收机制所回收,但也有例外,比如说你方法区快要满的时候,那就会进行回收。
java8的内存结构:
没有永久区了,取而代之的是MetaSpace,也就是元空间,方法区也从永久区剥离出来了。元空间和永久区的最大的不同就是元空间使用的是物理内存,也就是说直接用的是你系统的内存,而不是自己弄的内存,也就意味着垃圾回收机制也优化了,当元空间快要满的时候,或者说容量过大的时候垃圾回收机制才会开始回收,而现在我们用的是物理内存,辣么大,所以回收的几率就会很低。PremGenSize和MaxPremGenSize参数也无效了,取而代之的是MetaSpaceSize和MaxMetaSpaceSize。
- 代码更少
- 强大的 Stream API
- 便于并行
- 最大化减少空指针异常 Optional (Kotlin ?)
二
1.lambda表达式(重点)
简介: Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);可以写出更简洁、更灵活的代码;作为一种更紧凑的代码风格,是Java语言表达能力得到提升。
匿名内部类和lamda表达式的比较(实现同样的功能):
@Test
public void test(){
//匿名内部类
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer a1, Integer a2) {
return Integer.compare(a1,a2);
}
@Override
public boolean equals(Object obj) {
return false;
}
};
//调用
TreeSet<Integer> set = new TreeSet<>(comparator);
}
@Test
public void test2(){
// Lambda 表达式
Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
TreeSet<Integer> set = new TreeSet<>(comparator);
}
函数式接口:
- 接口中只有一个抽象方法的接口,称为函数式接口,如:
/**
* 函数式接口
*/
public interface MyPredicte<T> {
public boolean test(T t);
}
可以使用注解 @FunctionalInterface
修饰的,则为函数式接口
/**
* 接口,用于解决重复条件
* @param <T>
*/
@FunctionalInterface
public interface MyPredicte<T> {
public boolean test(T t);
}
只有函数式接口时,lambda表达式才能生效
语法格式一:
无参数,无返回值,如() -> sout
实例:实现Runnable接口
语法格式二:
有一个参数,并且无返回值
(x)->System.out.println(x);
语法格式三:
若只有一个参数,小括号可以省略不写
x->System.out.println(x)
语法格式四:
有两个以上的参数,有返回值,并且lambda表达式中有多条语句
Comparator<Integer> com =(x,y)->{
System.out.println("凑个语句");
return Integer.compare(x,y);
};
语法格式五:
若lambda体中只有一条语句,return和大括号可以省略不写
Comparator<Integer> com=(x,y)->return Integer.compare(x,y);
语法格式六:
lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即类型推断
三 函数式接口
Java内置四大核心函数式接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
Consumer 消费型接口 | T | void | 对类型为T的对象应用操作:void accept(T t) |
Supplier 提供型接口 | 无 | T | 返回类型为T的对象:T get() |
Function<T, R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t) |
Predicate 断言型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t) |
四 引用:
1.方法引用:
若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”,就是相当于对lambda的简化。
语法格式:
- 对象 :: 实例方法
- 类 :: 静态方法
- 类 :: 实例方法
对象::实例方法
@Test
public void test01(){
PrintStream ps = System.out;
Consumer<String> con1 = (s) -> ps.println(s);
con1.accept("aaa");
Consumer<String> con2 = ps::println;
con2.accept("bbb");
}
注意:Lambda 表达实体中调用方法的参数列表、返回类型必须和函数式接口中抽象方法保持一致
类::静态方法
@Test
public void test03(){
BiPredicate<String, String> bp1 = (x, y) -> x.equals(y);
System.out.println(bp1.test("a","b"));
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("c","c"));
}
类::实例方法
@Test
public void test03(){
BiPredicate<String, String> bp1 = (x, y) -> x.equals(y);
System.out.println(bp1.test("a","b"));
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("c","c"));
}
注:第一个参数是方法的调用者,第二个参数是方法的参数
2.构造器引用
格式:ClassName::new
@Test
public void test(){
Supplier<List> sup1 = () -> new ArrayList();
Supplier<List> sup2 = ArrayList::new;
}
这种构造器引用是看你参数的,比如有的是无参构造器,有的是一个参数构造器,有的是两个参数构造器,甚至更多,还有的是这几个全都有
比如:
public class student {
private int id;
private String name;
public student() {
}
public student(int id) {
this.id = id;
}
public student(int id, String name) {
this.id = id;
this.name = name;
}
}
这种就是全都有,这时候你传入几个参数他就按照几个参数的构造器来执行,如下:
@Test
//这种是无参构造器
public void a()
{
Supplier<student> q=()->new student();
Supplier<student> a=student::new;
student student = a.get();
System.out.println(student);
}
//这种是一个参数构造器 @Test
public void b()
{
// 这个Integer就是一个参数,就是id
Function<Integer,student> f=(x)->new student(x);
Function<Integer,student> f1=student::new;
//这个就是他传入的参数,就是id为1
student apply = f1.apply(1);
}
//这个是两个参数,几个参数就是用几个参数的函数式接口,无参可以用Supplier,一参Function,两参BiFunction
@Test
public void c()
{
BiFunction<Integer,String,student> b=(x,y)->new student(x,y);
BiFunction<Integer,String,student> b1=student::new;
student lisi = b1.apply(2, "lisi");
}
注意:需要调用的构造器的参数列表要与函数时接口中抽象方法的参数列表保持一致
3.数组引用
Type :: new
@Test
public void c()
{
Function<Integer,String[]> f=(x)->new String[x];
String[] apply = f.apply(10);
Function<Integer,String[]> f1=String[]::new;
String[] apply1 = f1.apply(1);
}
五:Optional
Optional不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下Optional的工作原理。
Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。
//of():为非null的值创建一个Optional
Optional<String> optional = Optional.of("bam");
// isPresent(): 如果值存在返回true,否则返回false
optional.isPresent(); // true
//get():如果Optional有值则将其返回,否则抛出NoSuchElementException
optional.get(); // "bam"
//orElse():如果有值则将其返回,否则返回指定的其它值
optional.orElse("fallback"); // "bam"
//ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
这个的意义其实就是在于你能快速地找到空指针异常报错的地方,你知道实在Optional这一行出现错误了。但是我感觉这个的实际意义比较小,因为我用的idea编译器本来就能够准确地说出报错的地址,精确到每一行。
六:Stream流
java.util.Stream
表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection
的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。
首先看看Stream是怎么用,首先创建实例代码需要用到的数据List:
List<String> stringList = new ArrayList<>();
stringList.add("ddd2");
stringList.add("aaa2");
stringList.add("bbb1");
stringList.add("aaa1");
stringList.add("bbb3");
stringList.add("ccc");
stringList.add("bbb2");
stringList.add("ddd1");
Filter(过滤)
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。
stringList
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);//aaa2 aaa1
Sorted(排序)
排序是一个 中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。
stringList
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);// aaa1 aaa2
Map(映射)
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
下面的示例展示了将字符串转换为大写字符串。你也可以通过map来将对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
stringList
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
Count(计数)
计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long。
long startsWithB =
stringList
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB);
还有几个方法就不写了,这几个就够理解了。