本文主要介绍了JDK1.8的一些新特性,仅供参考;
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值,如果不存在,则存放新的键值对到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表达式本质上是一段匿名内部类,也可以是一段可以传递的代码
(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 |
口诀:左右遇一省括号,左侧推断类型省
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);
/**
* 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!"));
// 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();
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);
}
}
函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。
定义了这种类型的接口,使得以其为参数的方法,在调用时,可以使用一个lambda表达式作为参数。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口,这个类型必定会使用@FunctionalInterface进行修饰。
从SAM原则上讲,这个接口中,只能有一个函数需要被实现,但是也可以有如下例外:
JDK中以前所有的函数式接口都已经使用 @FunctionalInterface 定义,可以通过查看JDK源码来确认
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
java.util.function
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
序号 接口 & 描述
1 BiConsumer
代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction
代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate
代表了一个两个参数的boolean值方法
5 BooleanSupplier
代表了boolean值结果的提供方
6 Consumer
代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction
代表接受一个double值参数的方法,并且返回结果
10 DoublePredicate
代表一个拥有double值参数的boolean值方法
11 DoubleSupplier
代表一个double值结构的提供方
12 DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果
14 DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double 。
15 Function
接受一个输入参数,返回一个结果。
16 IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer
接受一个int类型的输入参数,无返回值 。
18 IntFunction
接受一个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
接受一个long类型输入参数,返回一个结果。
27 LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier
无参数,返回一个结果long类型的值。
29 LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction
接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer
接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer
接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer
接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate
接受一个输入参数,返回一个布尔值结果。
36 Supplier
无参数,返回一个结果。
37 ToDoubleBiFunction
接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction
接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction
接受两个输入参数,返回一个int类型结果。
40 ToIntFunction
接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction
接受两个输入参数,返回一个long类型结果。
42 ToLongFunction
接受一个输入参数,返回一个long类型结果。
43 UnaryOperator
接受一个参数为类型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 predicate = n -> true
// n 是一个参数传递到 Predicate 接口的 test 方法
// n 如果存在则 test 方法返回 true
System.out.println("输出所有数据:");
// 传递参数 n
eval(list, n->true);
// Predicate predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true
System.out.println("输出所有偶数:");
eval(list, n-> n%2 == 0 );
// Predicate 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