本文主要介绍了JDK1.8的一些新特性,仅供参考;
前言:JDK1.8新特性知识点
- 红黑树 – 速度更快
- Lambda表达式 – 代码更少
- 函数式接口
- 方法引用和构造器调用
- Stream API – 强大的Stream API
- 接口中的默认方法和静态方法
- 新时间日期API
一、HashMap中的红黑树
HashMap碰撞:
HashMap中用的最多的方法就属put() 和 get() 方法;HashMap的Key值是唯一的,那如何保证唯一性呢?
我们首先想到的是用equals比较,没错,这样可以实现,但随着内部元素的增多,put和get的效率将越来越低,
这里的时间复杂度是O(n),假如有1000个元素,put时最差情况需要比较1000次。
实际上,HashMap很少会用到equals方法,因为其内通过一个哈希表管理所有元素,哈希是通过hash单词音译过来的,
也可以称为散列表,哈希算法可以快速的存取元素,当我们调用put存值时,HashMap首先会调用Key的hash方法,
计算出哈希码,通过哈希码快速找到某个存放位置(桶),这个位置可以被称之为bucketIndex,但可能会存在多
个元素找到了相同的bucketIndex,有个专业名词叫碰撞,当碰撞发生时,这时会取到bucketIndex位置已存储
的元素,最终通过equals来比较,equals方法就是碰撞时才会执行的方法,所以前面说HashMap很少会用到equals。
HashMap通过hashCode和equals最终判断出Key是否已存在,如果已存在,则使用新Value值替换旧Value值,并返
回旧Value值,如果不存在,则存放新的键值对<K, V>到bucketIndex位置。
JDK1.8对Map结构优化:
在jdk1.8中对hashMap等map集合的数据结构进行了优化:
1.hashMap数据结构的优化
原来的hashMap采用的数据结构是哈希表(数组+链表),hashMap默认大小是16,一个0-15索引的数组;
如何往里面存储(put)元素,首先调用元素的hashcode方法,计算出哈希码值,经过哈希算法算出数组的索引值,
如果对应的索引处没有元素,直接存放,如果有对象在,那么再调用它们的equals方法比较内容;
如果内容一样,后一个value会将前一个value的值覆盖,如果不一样,在1.7的时候,后加的放在前面,形成一个链表,
形成了碰撞。
加载因子:0.75,数组扩容,达到总容量的75%,就进行扩容,但是无法避免碰撞的情况发生。
在1.8之后,在数组+链表+红黑树来实现hashmap,当碰撞的元素个数大于8时并且总容量大于64,会有红黑树的引入。
1.8之后链表新进元素加到末尾,除此之外,效率都比链表高。
二、Lambda表达式
lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码
1.基本语法
(parameters) -> expression
(parameters) -> { statements; }
前置 | 语法 |
---|---|
无参数无返回值 | () -> System.out.println(“Hello World”) |
有一个参数无返回值 | (x) -> System.out.println(x) |
有且只有一个参数无返回值 | x -> System.out.println(x) |
有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
口诀:左右遇一省括号,左侧推断类型省
2.例子
2.1 集合的foreach循环
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer","Roger Federer",
"Andy Murray","Tomas Berdych",
"Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
// jdk1.8 以前的循环方式
for (String player : players) {
System.out.print(player + "; ");
}
// jdk1.8 使用lambda表达式以及函数操作(functional operation)
players.forEach((player) -> System.out.print(player + "; "));
// 在 Java 8 中使用双冒号操作符(double colon operator)
players.forEach(System.out::println);
2.2 匿名内部类
2.2.1 awt的监听、动作
/**
* awt编程,button的动作设置
*/
// 使用匿名内部类
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
// 或者使用 lambda expression
btn.setOnAction(event -> System.out.println("Hello World!"));
2.2.2 线程
// 1.1使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
}).start();
// 1.2使用 lambda expression
new Thread(() -> System.out.println("Hello world !")).start();
// 2.1使用匿名内部类
Runnable race1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
};
// 2.2使用 lambda expression
Runnable race2 = () -> System.out.println("Hello world !");
// 直接调用 run 方法(没开新线程哦!)
race1.run();
race2.run();
2.2.3 集合排序
String[] players = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka", "David Ferrer",
"Roger Federer", "Andy Murray",
"Tomas Berdych", "Juan Martin Del Potro",
"Richard Gasquet", "John Isner"};
// 1.1 使用匿名内部类根据 name 排序 players
Arrays.sort(players, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return (s1.compareTo(s2));
}
});
// 1.2 使用 lambda expression 排序 players
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players, sortByName);
// 1.3 也可以采用如下形式:
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));
二、函数式接口
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:
interface Formula {
double calculate(int a);
boolean isStr(String s);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
特点:
- 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
- 函数式接口可以被隐式转换为lambda表达式。
- 函数式接口现有的函数可以友好地支持 lambda。
解释:
函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。
定义了这种类型的接口,使得以其为参数的方法,在调用时,可以使用一个lambda表达式作为参数。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口,这个类型必定会使用@FunctionalInterface进行修饰。
从SAM原则上讲,这个接口中,只能有一个函数需要被实现,但是也可以有如下例外:
- 默认方法与静态方法并不影响函数式接口的契约,可以任意使用,即函数式接口中可以有静态方法,一个或者多个静态方法不会影响SAM接口成为函数式接口,并且静态方法可以提供方法实现可以由 default 修饰的默认方法方法,这个关键字是Java8中新增的,为的目的就是使得某一些接口,原则上只有一个方法被实现,但是由于历史原因,不得不加入一些方法来兼容整个JDK中的API,所以就需要使用default关键字来定义这样的方法;
- 可以有 Object 中覆盖的方法,也就是 equals,toString,hashcode等方法;可以有 Object 中覆盖的方法,也就是 equals,toString,hashcode等方法;
JDK中以前所有的函数式接口都已经使用 @FunctionalInterface 定义,可以通过查看JDK源码来确认
JDK 1.8之前已有的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
java.util.function
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
序号 接口 & 描述
1 BiConsumer<T,U>
代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction<T,U,R>
代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate<T,U>
代表了一个两个参数的boolean值方法
5 BooleanSupplier
代表了boolean值结果的提供方
6 Consumer<T>
代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction<R>
代表接受一个double值参数的方法,并且返回结果
10 DoublePredicate
代表一个拥有double值参数的boolean值方法
11 DoubleSupplier
代表一个double值结构的提供方
12 DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果
14 DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double 。
15 Function<T,R>
接受一个输入参数,返回一个结果。
16 IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer
接受一个int类型的输入参数,无返回值 。
18 IntFunction<R>
接受一个int类型输入参数,返回一个结果 。
19 IntPredicate
:接受一个int输入参数,返回一个布尔值的结果。
20 IntSupplier
无参数,返回一个int类型结果。
21 IntToDoubleFunction
接受一个int类型输入,返回一个double类型结果 。
22 IntToLongFunction
接受一个int类型输入,返回一个long类型结果。
23 IntUnaryOperator
接受一个参数同为类型int,返回值类型也为int 。
24 LongBinaryOperator
接受两个参数同为类型long,返回值类型也为long。
25 LongConsumer
接受一个long类型的输入参数,无返回值。
26 LongFunction<R>
接受一个long类型输入参数,返回一个结果。
27 LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier
无参数,返回一个结果long类型的值。
29 LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction
接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer<T>
接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer<T>
接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer<T>
接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate<T>
接受一个输入参数,返回一个布尔值结果。
36 Supplier<T>
无参数,返回一个结果。
37 ToDoubleBiFunction<T,U>
接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction<T>
接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction<T,U>
接受两个输入参数,返回一个int类型结果。
40 ToIntFunction<T>
接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction<T,U>
接受两个输入参数,返回一个long类型结果。
42 ToLongFunction<T>
接受一个输入参数,返回一个long类型结果。
43 UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。
函数式接口实例:
Predicate 接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。
该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。
该接口用于测试对象是 true 或 false。
我们可以通过以下实例(Java8Tester.java)来了解函数式接口 Predicate 的使用:
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Java8Tester {
public static void main(String args[]){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// Predicate<Integer> predicate = n -> true
// n 是一个参数传递到 Predicate 接口的 test 方法
// n 如果存在则 test 方法返回 true
System.out.println("输出所有数据:");
// 传递参数 n
eval(list, n->true);
// Predicate<Integer> predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true
System.out.println("输出所有偶数:");
eval(list, n-> n%2 == 0 );
// Predicate<Integer> predicate2 = n -> n > 3
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n 大于 3 test 方法返回 true
System.out.println("输出大于 3 的所有数字:");
eval(list, n-> n > 3 );
}
public static void eval(List<Integer> list, Predicate<Integer> predicate) {
for(Integer n: list) {
if(predicate.test(n)) {
System.out.println(n + " ");
}
}
}
}
三、方法引用
三种表现形式:
类型 | 示例 |
---|---|
某个对象的方法引用 | Object : instanceMethodName |
特定类的任意对象的方法引用 | Type : methodName |
类静态方法引用 | Class : staticMethodName |
构造方法引用 | className : new |
使用形式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
函数接口中抽象方法的参数列表,必须与方法引用方法的参数列表保持一致,方法引用中::后只是方法名,不能加();
实例方法引用
循环集合中元素,使用forEach方法
(s) -> System.out.println(s)的类型是Consumer类型
其accept接受参数和println一致
import java.util.ArrayList;
import java.util.List;
public class Demo01 {
public static void main(String[] args) {
// 创建集合
List<String> list = new ArrayList<>();
// 添加元素
list.add("e");
list.add("c");
list.add("a");
list.add("d");
list.add("b");
// 排序
list.sort((s1, s2) -> s1.compareTo(s2));
// 遍历
list.forEach((s) -> System.out.println(s));
list.forEach(System.out::println);
}
}
类静态方法引用
求绝对值,使用Function实现:
import java.util.function.Function;
public class Demo01 {
public static void main(String[] args) {
// 调用
long l1 = testAbs(-10L, (s) -> Math.abs(s));
// 改进
long l2 = testAbs(-10, Math::abs);
System.out.println(l1);
System.out.println(l2);
}
public static long testAbs(long s, Function<Long, Long> fun) {
Long l = fun.apply(s);
return l;
}
}
构造方法引用
在引用构造器的时候,构造器参数列表要与接口中抽象方法的参数列表一致
格式为 类名::new
import java.util.function.Supplier;
public class Demo01 {
public static void main(String[] args) {
getString(String :: new);
}
public static void getString(Supplier<String> su) {
String s = su.get();
System.out.println(s == null);
}
}
任意对象的实例方法
public void test() {
/**
*注意:
* 1.lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
* 2.若lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
*
*/
Consumer<Integer> con = (x) -> System.out.println(x);
con.accept(100);
// 方法引用-对象::实例方法
Consumer<Integer> con2 = System.out::println;
con2.accept(200);
// 方法引用-类名::静态方法名
BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);
BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
Integer result = biFun2.apply(100, 200);
// 方法引用-类名::实例方法名
BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
BiFunction<String, String, Boolean> fun2 = String::equals;
Boolean result2 = fun2.apply("hello", "world");
System.out.println(result2);
}
由于篇幅太长,JDK1.8新特性(二)见下面链接:
https://blog.csdn.net/u013068377/article/details/82877403