Lambda 表达式

  • Post author:
  • Post category:其他




jdk8new

速度更快

代码更少(增加了新的语法 Lambda 表达式)

强大的 Stream API

便于并行

最大化减少空指针异常 Optional

其中最为核心的为 Lambda 表达式与Stream API



1.Lambda 表达式 — lambda表达式的本质是接口的实例



1.1为什么使用 Lambda 表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。可以写出更简洁、更 灵活的代码。作为一种更紧凑的代码风格,使 Java的语言表达能力得到了提升。



1.2 从匿名类到Lambda的转换

在这里插入图片描述

在这里插入图片描述



1.3lambda表达式语法

Lambda 表达式在Java 语言中引入了一个新的语法元 素和操作符。这个操作符为 “->” , 该操作符被称 为 Lambda 操作符或剪头操作符。它将 Lambda 分为 两个部分: 左侧:指定了 Lambda 表达式需要的所有参数 右侧:指定了 Lambda 体,即 Lambda 表达式要执行 的功能。

在这里插入图片描述

在这里插入图片描述



1.4 类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可

以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上

下文环境,是由编译器推断出来的。这就是所谓的“类型推断”



2.函数式接口



2.1什么时函数式接口


只包含一个抽象方法的接口,称为函数式接口。


你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)。


我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包 含一条声明,说明这个接口是一个函数式接口。



2.2自定义函数式接口

在这里插入图片描述



2.3作为参数传递lambda表达式


lambda 表达式的本质是 接口的实例对象

★★★★★

在这里插入图片描述



2.4java内置四大核心函数式接口

在这里插入图片描述



2.5其它接口

在这里插入图片描述



3.方法引用与构造器引用



3.1 方法引用

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



3.2 构造器引用

在这里插入图片描述



3.3数组引用

在这里插入图片描述



4.Stream API

4.1 了解Stream

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一 个则是 Stream API(java.util.stream.*)。 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对 集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数 据库查询。也可以使用 Stream API 来并行执行操作。简而言之, Stream API 提供了一种高效且易于使用的处理数据的方式。



4.2什么时Stream

在这里插入图片描述



4.3Stream的操作三个步骤

在这里插入图片描述



4.4创建Stream



4.4.1 集合创建流

在这里插入图片描述



4.4.2 数组创建流

在这里插入图片描述



4.4.3 由值创建流

在这里插入图片描述



4.4.4 由函数创建流:创建无限流

在这里插入图片描述



4.5Stream的中间操作

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



4.6Stream的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的 值,例如:List、Integer,甚至是 void 。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



4.7并行流与串行流

在这里插入图片描述



4.7.1 了解Fork/join框架

在这里插入图片描述

在这里插入图片描述



5.接口中的默认方法与静态方法



6.新时间日期 API





7.其他新特性



hashmap

什么是hash ?

核心理论: Hash也称散列,哈希对应的英文Hash ,基本原理就是把任意长度的输入 通过hash算法变成固定长度的输出。这个映射的规则就是对应的Hash算法,而原始数据映射后的二进制串就是哈希值。

Hash的特点:

1.从hash值不可以反向推导出原始数据

2.输入数据的微小变化会得到完全不同的hash 值,相同的数据会得到相同的值

3.hash算法执行的效率很高,长的文本也能快速的计算出哈希值

核心属性

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16     table  数组 默认初始容量
static final int MAXIMUM_CAPACITY = 1 << 30;     table  数组 最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;       缺省 负载因子 
static final int TREEIFY_THRESHOLD = 8;   //链表 升级为树    树化 的阈值  
static final int UNTREEIFY_THRESHOLD = 6;    // 树降级为 链表的阈值 
static final int MIN_TREEIFY_CAPACITY = 64;   //  转红黑树需要的最小数组长度64   某个链表的元素 超过8 才转为树
transient Node<K,V>[] table;      hash表    存放元素的数组
transient int size;    hash 表中元素的个数
transient int modCount;   当前hash 表结构的修改次数   添加 和 修改元素 都算结构修改操作   ,替换不是结构修改   
int threshold;     扩容阈值   当hash 表中的元素个数 超出这个阈值  触发扩容     threshold = capacity * loadFactor
final float loadFactor;   负载因子       

put 方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
/**
 * Implements Map.put and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value    如果hash表中已经存在这个 key 则 不进行添加操作,一般为false     有的话 则替换 ,没有的话 则添加
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    //tab 引用当前hashmap的散列表
    //p  表示当前散列表的元素
    //n 表示散列表数组的长度
    // i 表示路由寻址的 结果    下标
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    //延迟初始化逻辑 第一次调用putVal 方法时 会初始化hashmap对象中最耗费内存的散列表 
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    
    //最简单的一种情况:寻址找到的索引位置 没有元素  刚好为null ,这时 直接将这个node 放进去 
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    
    else {
    	// e node临时变量
    	//k 表示 临时的 k
        Node<K,V> e; K k;
        //表示 hash表数组中的元素 与当前插入的key 完全一致 ,表示要进行替换操作
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
        	//红黑树
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
        	//链表
            for (int binCount = 0; ; ++binCount) {   //遍历整个链表
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    
                    //条件成立的话 说明当前链表的长度 达到树化的标准 需要进行树化操作
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st     树化条件成立
                        treeifyBin(tab, hash);  //树化
                    break;
                }
                //hash碰撞 条件成立的话 说明找到了完全一致的元素  要进行替换了
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        
        //e不等于null 条件成立说明 找到了与插入的元素key  完全一致的数据, 需要进行替换
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

扩容方法

为什么扩容?

为了减少哈希碰撞 扩容能够缓解这个问题

 final Node<K,V>[] resize() {
        // oldTab 引用扩容前的hash表
        Node<K,V>[] oldTab = table;
        // oldCap: 表示扩容之前table 数组的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;  // new HashMap() 还没放数据的时候 table 为null 
        //oldThr: 表示扩容之前的扩容阈值 触发本次扩容的阈值
        int oldThr = threshold;
     	//newCap : 扩容之后 table 数组的大小
     	//newThr: 扩容之后 下次再次触发扩容的条件
        int newCap, newThr = 0;
        //如果条件成立 说明 hashmap 中的散列表已经初始化过了  这是一次正常的扩容
        if (oldCap > 0) {
            //扩容之前的table数组大小已经达到最大阈值 则不扩容 且设置扩容条件为int的最大值
            if (oldCap >= MAXIMUM_CAPACITY) {
                //达到上限 无法扩容了
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //oldCap 左移一位  实现数值翻倍 并且赋值给newCap ,newCap 小于数组最大值限制 且扩容之前的阈值 >=16 
            //这种情况 下次扩容的阈值 是当前阈值的 2 倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
     //创建一个 更长更大的数组 
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //说明 本次扩容之前 table不为 null 
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                //当前node 节点
                Node<K,V> e;
                //说明当前桶位中有数据 但是数据具体是单个数据  还是链表 还是 红黑树并不知道
                if ((e = oldTab[j]) != null) {
                    //方便JVM GC时回收内存
                    oldTab[j] = null;
                    //第一情况 当前桶位只有一个元素 从未发生过碰撞 这种情况 直接计算出当前元素应该存放在新数组中的位置 放进去
                    if (e.next == null)
                        //把节点放入新的 table的 桶位  桶位计算  是   hash & 数组的length -1
                        newTab[e.hash & (newCap - 1)] = e;
                    // 第二种情况 当前节点 已经树化
                    else if (e instanceof TreeNode)
                        //树
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        //第三种情况 桶位已经形成链表 
                        //低位链表:存放在扩容之后的数组的下标位置 与当前数组的下标位置一致
                        Node<K,V> loHead = null, loTail = null;
                        //高位链表:存放在扩容之后的数组的下标位置为  当前数组的下标位置  + 扩容之前的数组的长度
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }



枚举



版权声明:本文为weixin_51313154原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。