JDK1.8新特性(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013068377/article/details/82861655

本文主要介绍了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原则上讲,这个接口中,只能有一个函数需要被实现,但是也可以有如下例外:

  1. 默认方法与静态方法并不影响函数式接口的契约,可以任意使用,即函数式接口中可以有静态方法,一个或者多个静态方法不会影响SAM接口成为函数式接口,并且静态方法可以提供方法实现可以由 default 修饰的默认方法方法,这个关键字是Java8中新增的,为的目的就是使得某一些接口,原则上只有一个方法被实现,但是由于历史原因,不得不加入一些方法来兼容整个JDK中的API,所以就需要使用default关键字来定义这样的方法;
  2. 可以有 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

猜你喜欢

转载自blog.csdn.net/u013068377/article/details/82861655