Java 基础知识(二)

一、Java基础

1、重载(overload)和重写(override)的区别

①、被覆盖的方法不能是private,那样就相当于在之类中新建了一个新的方法而已
②、覆盖是指对父类方法的重写,抛出的异常范围小于父类,访问权限应该不小于父类
③、重载是一个类中有多个相同名称的方法,它们的参数类型、参数顺序、参数个数不一样(返回值不能作为方法签名)

Note:
a、上述的方法签名:方法名和参数列表是在Java代码层面的,在Class文件层面范围会更大一些:还包括方法返回值和受查异常表

b、返回值不能作为签名原因:Java中调用函数并不需要强制赋值
如:int d(){…} 和 void d(){…}
调用的地方:d();此时就会无法区别是调用的哪一个方法。

2、接口和抽象类的区别

①、接口是完全的抽象类,所有的方法都是抽象的,而抽象类中允许有非抽象的方法
②、抽象类只能单继承,接口可以多个实现
③、抽象类中可以有变量,接口中只能有static final的常量,且必须被初始化
④、接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
⑤、java8中可以使用default来修饰方法,此时方法可以有方法体

3、java常用的包

java.lang 、java.util 、 java.io 、 java.net 、 java.sql

二、Java中常见集合

1、常见的集合

Collection

所有集合框架的父接口,没有具体实现,而是让List、Set和Queue接口进行继承在各自实现。

Set

特点:无序、不能重复,基于散列表实现
主要实现类:AbstractSet(实现Set)、HashSet(继承AbstractSet)、LinkedHashSet(继承HashSet)、TreeSet(继承AbstractSet)、SortedSet(接口,继承Set)

  • 1、HashSet通过HashMap(哈希表)实现,其实就是Map的key,非线程安全、集合元素可以为null,无序
  • 2、LinkedHashSet是HashSet的子类,基于哈希表和链表实现
  • 3、TreeSet唯一实现了SortedSet的类,通过NavigableMap来实现(基于红黑树),其实就是Map的key,大小有序
  • 4、EnumSet专门为枚举类设计的集合(是Set实现类中性能最好的),内部以位向量的形式存储,所以运行效率高、占内存小。但是传入的必须是指定的枚举类的枚举值(在构造器中传入),加入的值不能为空,有序。

    1、普通插入、删除HashSet比LinkedHashSet更快
    2、遍历LinkedHashSet会更快
    3、上面三个实现都是线程不安全的,解决办法:Set set = Collections.synchronizedSet(set 对象)

List

特点:有序、可以重复,可以按索引访问
主要实现类:ArrayList(继承AbstractList,实现List)、LinkedList(继承AbstractList,实现List)、Vector(继承AbstractList,实现List)、Stack(继承自Vector)等

  • ArrayList通过数组实现,查询、更新快,删除、插入慢,线程不安全,但是效率高
  • Vector通过数组实现,查询、更新快,删除、插入慢,线程安全,但是效率低
  • LinkedList通过链表实现,查询慢,删除、插入快,线程不安全,效率高

Map

特点:

  • 1、一个Map可以看成是多个Entry(key,value)的集合
  • 2、Map不能包含重复的key,但是可以包含相同的value。

常用实现类:HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap和TreeMap

HashMap

使用红黑树实现,快速访问,只能有一条记录的key为NULL,允许多条记录的value为NULL,线程不安全

Hashtable

HashMap的线程安全版,每次上锁的时候会将整个表锁住,会很大的影响速度,效率较低,且不允许key为NULL

ConcurrentHashMap

相比Hashtable来说稍细粒度的锁(锁分段技术),将整个表分为16个Segment,每个Segment相当于一个Hashtable,每次上锁针对的是相应的segment。

LinkedHashMap

有HashMap的全部特性,会保存数据插入的顺序,遍历的时候会比HashMap慢,但是插入的时候应该比HashMap快

TreeMap

使用红黑树实现,根据键值大小排序,默认升序,也可以指定排序的比较器(同TreeSet),非线程安全,不允许key为NULL

Iterator 和 Iterable

所有的Collection类,都继承了Iterable接口,这个接口主要是提供for-each循环,在这个接口里面封装了iterator。

在Iterator中主要包含以下三种方法:

  • 1.hasNext()是否还有下一个元素。
  • 2.next()返回下一个元素。
  • 3.remove()删除当前元素。

Iterator替换了原始的Enumeration类,主要是:

  • 1、允许在使用迭代器的过程中移除元素
  • 2、优化了方法名

2、List、Set、Map的初始大小、加载因子和扩容大小

ArrayList

初始大小是10,加载因子是1,之后每次增加当前容量的一半(oldCapacity + (oldCapacity >> 1)),比如当前大小是10,扩容之后是15

Vector

初始默认大小是10,加载因子是1,扩容为原来的一倍(oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity)),如果给出了扩容大小capacityIncrement则每次扩容的大小就是capacityIncrement,没有给出则capacityIncrement为0,此时每次扩容就是增加一倍的大小,比如,原本10,扩容之后为20

Hashtable

初始默认大小是11,加载因子是0.75,使用Entry数组实现的

HashSet 和 HashMap

两个一样的原则,初始大小是16,加载因子是0.75,扩容增量为原来容量的1倍

由上可知,HashMap的大小应该为2的幂,原因如下:

  • 1、tab[i = (n - 1) & hash],其中n是tab的大小,tab就是当前HashMap的大小,hash就是key对应的hash值,得到的i就是应该放置的位置。
  • 2、当n为2的次幂,则就能保证n-1都是111111….的形式,此时&hash的值就很好就算了,这样的话每一个hash值的位置都能利用上,而不会浪费,提高利用率。利用率高了,冲突也会少一些,查询的效率更高。比如:如果n = 15,则n-1 = 14,对应的二进制为1110, 此时无论hash的值为多少,末尾的一位为1的时候时候就会无法存放元素了,比如:0001,0011,0101,0111,1001,1011,1101这几个位置的空间就会浪费了。

3、Comparable接口和Comparator接口

Comparable

是排序接口,实现了这个接口的类可以支持Arrays.sort()和Collections.sort()进行排序,具体比较两个类的大小的方法是通过实现Comparable中的compareTo方法来实现的。

特点:简单,但是,假设Person类要实现这个接口,则修改Person类中的代码,如果比较的方法发生变化,也需要修改 Person中的compareTo方法。甚至,如果当前的比较对象不是Person了,则有需要在Peroson中删掉这一段代码。

Comparator

是一个比较器,是定义在Person外部的,实现这个比较器对Person这个类没有任何影响。可以在实现的这个外部比较器中实现通用的比较方法,根据泛型传入父类,就可以实现大范围的比较了。

必须实现里面的compare()方法。

4、集合中的快速失败机制(fail-fast)

是什么

在多线程环境下,JAVA的Collection不允许当一个线程对集合进行iterator迭代的时候,另一个线程去改变集合的结构。

原理

在所有的List和HashMap里面(HashSet里面是用的HashMap实现的,所以应该也存在这个机制),都维护了一个变量modCount来记录当前集合被修改的次数。同时,这些类中都是实现了Iterator迭代器(注意,这个机制只会存在于迭代的过程中,因为是在实现Iterator接口的时候抛出的异常)。

每次集合中涉及到修改集合内容的时候,如add()、remove()、clear()等操作,都会将modCount变量进行增加,表示被修改了。在Iterator的实现中,还维护了一个变量expectedModCount,这个变量的值一直都是等于modCount的值(ArrayList为例):

int expectedModCount = ArrayList.this.modCount;

在每次进行next()时,会调用checkForComodification()检查当前的modCount和expectedModCount的值是否相等,如果不相等就会抛出ConcurrentModificationException异常。

出现的原因:
假设A线程开始迭代遍历list,此时的modCount为N,expectedModCount的值也为N,然后此时另一个线程删除了一个元素,此时modCount的值变为了N + 1,但是在A线程中expectedModCount已经是N了,然后当A线程进行next的时候,检测发现modCount = N + 1,但是expectedModCount = N,此时就会出发上述的异常。

解决办法

  • 在所有要进行修改集合数据的方法上加上synchronized关键字
  • 用concurrent包下的CopyOnWriteArrayList代替ArrayList

CopyOnWriteArrayList能解决的原因

写时复制的原则,在要对集合进行修改操作的时候,先将集合进行复制一份出来,对复制出来的副本进行修改的操作,修改完了之后再将原来数组的引用指向新的数组。

三、高并发-JUC包

1、线程和进程的区别

  • 进程是操作系统进行资源分配和调度的基本单位,线程是能独立运行的基本单位
  • 一个程序至少一个进程,一个进程至少一个线程
  • 由于进程之间是独立的,所以每个进程有独立的内存空间,所以在进行进程之间的切换的时候的时间开销很大,相比之下,同一个进程中的线程之间是共享父进程的内存空间的,在进行线程切换的时间开销相比较小
  • 进程开销大,但是由于空间独立,所以利于资源的管理和保护。线程虽然开销不大,但是同一个进程的多个线程之间是可以共享内存的,所以不利于资源的管理和保护。
  • 线程是属于进程的,一个进程内部可以创建多个线程进行并发的执行

2、单线程和多线程

单线程:一个进程中只有一个线程
多线程:一个进程中有多个线程

区别:

  • 单个线程的运行过程顺序的执行一个指令流;多线程就是运行程序运行的时候并发的执行多个指令流
  • 多线程的执行在逻辑上是并发的,由于cpu在一个时间段只能执行一个线程,所以,每个线程都会占用一段CPU的时间片,然后CPU就轮流这在多个线程之间进行切换,由于这个时间片很短,就会感觉是同时执行的
  • 单线程不存在线程的切换问题,但是多线程之间存在线程切换的问题,线程的切换是有时间开销的,所以对於单核CPU来说,两个线程的并发执行会比一个线程连续执行两次的时间开销大,执行的速度会变慢,但是用户的响应时间减少了
  • 对于多核的CPU来说的话,多个线程会映射到多个CPU的核心上进行运行,这个是可以提高运行速度的

3、实现多线程按照顺序进行运行

问题:让十个线程按照顺序打印0,1,2,3,4,5,6,7,8,9
思路:

  • 1、首先创建是个线程,每个线程需要传入一个线程的id号,用来表示这个线程应该执行的指令是什么(或者说应该按照什么顺序执行)
  • 2、然后按照给定的顺序让线程进行启动
  • 3、在每个线程里,判断当前要执行的指令是不是这个线程id对应的指令,是的话就执行,不是的就进入等待状体,等待应该执行这条指令的的行程执行完之后唤醒所有等待的线程
  • 4、在执行的代码块中,一个是执行命令(此处就为打印输出当前的指令),执行完之后需要唤醒所有等待的线程(notifyAll),但是分析一下,如果当前等待的线程为0,再调用notifyAll,会出现IllegalMonitorStateException,所以,需要再维护一个全局变量来表示当前处于等待状态的线程的数量,如果这个数量的值大于0,就调用notifyAll()方法

4、产生死锁的原因和解决方法

分析产生死锁的原因:

  • 系统的资源不足,导致有线程没能获得到相应的资源
  • 线程之间的顺序不合理,存在互相阻塞的情况
  • 资源的分配不合理,比如说某个线程需要运行很长的时间却被分配了很长的CPU时间

根据上面的原因,分析的到下面的产生死锁的必要条件:

  • 互斥条件:如果不存在并发,这不会后死锁,换句话说,正是由于并发的存在,为了资源能正确的被访问,所以很多时候限制了一个资源同时只能被一个线程使用
  • 请求保持:当一个线程A因为要请求某个资源被阻塞了,此时这个线程A所占用的资源也会保持不放,这就会是的那些需要请求A线程所占有的资源的线程就会一直被阻塞
  • 不剥夺条件:对于线程已经获得的资源,在未使用完之前不被剥夺
  • 循环等待条件:若干个线程之间形成了头尾相连的循环等待

以上必要条件:发生死锁必须上面是个条件都同时满足,任何一个不满足则不会发生死锁(因为只有有一个不满足就有方法结束阻塞,释放资源)

解决的方法:结合上面的原因来分析解决的办法

1、死锁的预防(保证系统不进入死锁状态,排除死锁的静态策略)

  • 让某一些资源不允许被同时访问
  • 合理的安排系统资源,避免线程永久占据资源(即打破不可剥夺条件)
  • 要防止线程进入等待状态下还占用资源(请求保持条件)
  • 指定线程获取锁的顺序(即获取资源的顺序):比如规定线程必须先获取小号资源才能申请大号资源等

2、死锁的避免(排除死锁的动态策略)
首先,是不限制线程对资源的申请,然后,再对线程申请资源的时候加以检查,判断当前线程申请的这个资源是不是安全的。

银行家算法:寻找安全序列

银行家算法的优点:限制条件少了,资源的利用率提高了

银行家算法的缺点:
1、要求客户(即线程数)保持不变,这个在多道程序系统中很难实现
2、银行家算法能保证所有的线程在有限的时间内得到系统的资源,但是对于实时要求的客户来说却不能满足
3、需要寻找一个安全序列,增加系统的开销

3、死锁的检测与恢复
上述的预防和避免实际上都不能很好的排除死锁。提供一种检测和解脱死锁的方式:能发现死锁并且从死锁状态中恢复出来。

5、sleep(n)、wait()、wait(n)

sleep(n)

睡眠n毫秒,通过Thread.sleep(n)方法调用,睡眠的时候不释放锁。

wait()

在wait方法处停止运行,等待notify或者中断为止。在进行wait()之前,必须获得Object对象级(wait是Object的方法,又因为Object是所有的父类,即其子类的对象即可)的锁,所以必须和synchronized一起使用。在使用wait之后会释放当前获得的对象锁。

Note:wait()是指无限制等待直到notify/notifyAll或者中断,wait(n)在时间n毫秒之后会自动唤醒

与wait相对应的就是notify了,两个一起构成了等待/通知机制来实现线程之间的通信:

notify()

也必须获得Object对象级的锁。调用成功后会随机选择一个处于等待状态中的线程,使其或者该对象的对象锁。

notifyAll()

就是对所有的线程都进行notify操作

注意:notify之后,当前线程不会立马释放当前获得的对象锁,所以被唤醒的线程也不会立马被唤醒获得对象锁,而是要等到notify所在的同步方法或者同步块执行完后才会释放锁。

所以:可知wait方法会立即释放锁,notify不会立即释放锁

6、对线程池的了解

JUC下的ThreadPoolExecutor类

实现

继承自AbstractExecutorService -> 实现自ExecutorService接口 -> 继承自Executor

主要方法

execute() : 在ThreadPoolExecutor具体实现,传入一个实现了Runnable的对象,此时会将该对象放到工作队列中

submit() : 跟execute差不多,就是运行一个线程,但是submit使用Future能返回线程执行的结果

shutsown() :
关闭线程池之后,线程池的状态变为SHUTDOWN,线程池不在接收新的线程,等待运行中的线程结束

shutdownNow():
关闭线程池之后,线程池变为STOP状态,会不在接收新线程,同时尝试终止其他正在运行的线程

主要参数

corePoolSize : 核不大于最大线程池大小的核心线程池的大小
maxinumPoolSze : 允许存在的最大的线程池的大小
keepAliveTime : 默认情况下,当线程池中线程的数量大于corePoolSize的时候就会起作用,此时会将超过keepAliveTime没有任务执行的线程进行关闭,直到不大于corePoolSize
workQueue : 是一个阻塞的工作队列(BlockingQueue)

Note :
1、当运行的线程的数量少于corePoolSize的大小,则Executor始终会优先选择进行添加线程,而不是进行排队
2、当运行的线程的数量等于或者多余corePoolSize的大小,则Executor会首选将请求加入队列,而不是创建新的线程
3、若在将请求添加到等待队列中,发现队列已经满了,则会选择新建线程,如果此时运行的线程的数量已经达到了maxinumPoolSize的大小,则会拒绝该请求。

任务缓存队列及排队策略

ArrayBlockingQueue :
基于数组的先进先出队列,该队列创建时必须指定大小

LinkedBlockingQueue :
基于链表的链表的先进先出队列,创建时如果没有指定大小这默认为Integer.MAX_VALUE

SynchronousQueue :
比较特殊,不会保存提交过来的任务,而是直接新建一个线程来执行该任务。

任务拒绝的策略

AbortPolicy : 丢弃任务并抛出RejectedExecutionException,也是默认的策略

DiscardPolicy : 丢弃任务但是不抛出异常

DiscardOldestPolicy : 丢弃当前等待队列最前面的任务,然后再尝试重新执行这个任务

动态设置线程池的容量参数

setCorePoolSize();
setMaxinumPoolSize();

总结 ThreadPoolExecutor的执行过程

1、首先将实现了Runnable接口的任务通过execute()进行提交,也可以使用submit()方法提交。
2、如果当前线程池中的线程的数量小于corePoolSize,则会首选创建新的线程来执行所提交的任务
3、如果当前线程池中的线程的数量大于或者等于corePoolSize,则会首选将提交的任务放到任务队列中进行排队等候执行(上述有队列的排队策略)
4、如果无法加入任务队列或者队列已经满了,则将新建一个新的线程来执行执行,如果此时线程池中的线程的数量已经达到最大值maxPoolSize,此时会拒绝这个任务(上述有拒绝的策略)

7、ThreadLocal (线程局部变量)

ThreadLocal维护一个变量的时候,为每一个使用该变量的线程维护了一个独立的变量副本,这样每一个线程就可以独立的改变其副本中的变量,这样就不会影响到其他线程的变量副本。

特性:线程之间的变量是相互隔离的。(在ThreadLocal中,每次获取变量的值的时候,会出现获取第一个值的时候是返回的NULL,此时可以通过继承ThreadLocal类,并重写方法initialValue(是protected的访问权限)来返回初值)

ThreadLocal的和实现原理

1、每个线程的内部都会维护一个ThreadLocalMap的对象,里面就包含了若干的Entry(即key-value键值对),每个entry的key就是当前线程中所使用的那个TreadLocal实例,其value就是当前线程的属性
2、所以每个Entry就当前对象所使用的ThreadLocal实例和当前对象所特有的属性的一个对应关系
3、ThreadLocalMap中对key的引用是弱引用,对value的引用是强引用

以set一个值到ThreadLocal中为例的过程

1、首先通过Thread.getCurrentThread()来获取当前使用这个ThreadLocal的线程
2、由于在Thread中维护了一个ThreadLocal.ThreadLocalMap threadLocals,所以可以用1中获取到的线程来获取这个线程的threadLocals属性
3、判断,如果当前的threadLocals为null表示还没有初始化,这创建这个ThreadLocalMap对象,如果不为Null,则通过threadLocals.set(key,value)的形式赋值,这个key就是当前线程所使用的ThreadLocal的实例,value就是当前线程的属性值

其他的知识点

1、ThreadLocal的的值是存放在哪儿的?

分析:首先,知道栈上存放的是线程私有的变量,每个线程都有一段自己私有的线程空间;而堆内存的变量是对所有线程都可见的,也就是堆内存中的变量是可以被所有其他线程访问的。

ThreadLocal的值是线程私有的,那这个值是存放在栈中么?
不是。ThreadLocal也是被其创建的类所持有,所以也是在堆上,但是其实现的方式达到了线程私有的目的,比如:每个线程自己本身是有一个ThreadLocalsMap变量的,这个是每个线程所私有的。这个Map中存放的就是以一个ThreadLocal实例为key,相应的属性值为value的Entry对象。在每次通过ThreadLocal进行变量值的设置和获取变量值的时候,首先是拿到当前是哪一个线程,然后得到这个线程里面的私有变量threadLocalsMap,然后以当前这个ThreadLocal实例作为key来获取当前这个线程中的变量的值。

2、ThreadLocal是否会导致内存泄露

分析:由于有线程池的存在,所以,在一些线程执行完任务之后,为了提高线程的复用率,执行完了任务的线程会依然还在线程池中。此时当这个线程又去执行其他的任务的时候,该线程之前所持有的由ThreadLocal所维持的一些变量依然还存在于这个线程中,所以此时会导致线程泄露。

但是,实际上是不会泄露!
原因:通过源码分析,看到在ThreadLocal中的ThreadLocalMap中的每一条记录Entry的key的引用是弱引用,其值是强引用class Entry extends WeakReference

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

可以看出来,子进程的inheritableThreadLocals的属性直接就是来自其父线程的。

所以,此时使用IheritableThreadLocal可以实现在子线程中访问父线程的ThreadLocal变量。

8、sychronized关键字

底层实现:
1、底层维护了一个monitorenter监视器,相当于一个信号量,当有一个线程进入的时候monitorenter就会加一,当线程出去的时候就会减一。
2、所以,在线程想要进入一段同步块或者同步方法的时候,先判断当前的monitorenter是否为0,是则可以访问,不是则不可以访问

9、volatile 关键字

两个作用:(参考书籍《深入理解Java虚拟机》 12.3.3)
1、可见性
2、禁止JVM进行指令重排

10、Atomic+<基本数据类型>

如:AtomicInteger、AtomicLong等

作用:能以原子的方式让基本数据类型就行自增自减等操作

四、Java中的IO

几种IO模型,最开始的BIO,到NIO,再到AIO

BIO

又分为两种:传统的BIO模型、伪异步IO模型

传统的BIO模型

由Socket进行连接的创立,双方进行同步阻塞通信,通常由一个独立的Acceptor线程负责监听Client端的连接,每接收到一个client的连接就会创建一个新的线程进行处理。

问题:当多个客户端连接之后,创建了多个线程,系统的性能就会急剧下降。

伪异步IO

为了改进1:1的线程模型,改用将创建的线程交由线程池来进行管理,实现M个线程处理N个客户端的模型,但是实际上实现的时候还是BIO的方式。

实现的方式,就是在BIO的模型的基础上,将处理消息的线程交由ExecutorService来处理即可:

Socket socket = server.accept();
executorService.execute(new Serverhandler(socket));
//其中ServerHandler就是处理连接的线程

问题:当并发量比较大(大于线程池最大线程数量)时,线程的读取速度有比较慢的时候,其他的客户端连接就会一直被阻塞。

NIO

官方称为New IO,在民间也称为Non-block IO即非阻塞IO。

提供了相对于BIO的两种套接字:ServerChannel和ServerSocketChannel,这两种套接字都支持阻塞和非阻塞两种模式。

Buffer

实际上是一个数组,在NIO的库中,所有数据都是用缓冲区处理的,比如读数据是从缓冲区中读,写数据是写到缓冲区。

具体的有:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer,都实现了Buffer的接口。

Channel

想水管一样,相比于流来说,支持双向操作,是全双工的,可以写、读、同时读写。

主要分为:
SelectableChannel:用于网络读写
FileChannel:用于文件的读写(只能在阻塞模式下进行)

ServerSocketChannel和SocketChannel都是SelectableChannel的子类。

多路复用器Selector

用来管理Channel,Selector会不断的轮询注册在其上的Channel,如果某个Channel上面发生了读或者写事件,那这个Channel就算处于就绪的状态,就会呗Selector轮询出来,然后通过SelectionKey可以获取当前就绪的Channel的集合,然后进行后面的操作。

AIO

发起非阻塞的IO操作,操作完了之后进行通知。

完成通知的任务:Future和Callback的方式

Future

即提交的每一个IO操作都返回一个Future,然后可以对返回的Future进行检查:Future result = channel.read(buf),然后使用result.get()来返回读取到的数据的字节数,并进行相关的判断。

Callback

为每一次IO操作指定一个CompletionHandler,在这个接口里面实现两个方法:
void completed(V result, A attachment):当操作完成之后被调用,result表示操作结果,attachment表示提交操作请求时的参数

void failed(Throwable exc, A attachment):当操作失败调用,exc 参数表示失败原因。attachment 参数同上。

五、Java 8相关

HashMap 增加了红黑树的实现

红黑树:

特点

1、根节点是黑色
2、所有的叶子结点是黑色(这个叶子结点是指最下面的空节点)
3、如果当前节点是红节点,则它的孩子节点是黑色的节点
4、从一个节点出发,到该节点的任意子孙路径上包含的相同数目的黑节点的数量
5、所有的节点不是黑色就是红色

插入数据

每一次新插入的数据都是红色的,因为插入数据之后会导致树不满足了红黑树的特点,于是就需要进行树的调整。

case1:若当前节点的父节点是红色,而且当前节点的叔叔节点也是红色

操作:
1、先将当前节点的父节点和叔叔节点都设置为黑色
2、然后将祖父节点设置为红色
3、将当前节点移到祖父节点上,继续上面的判断
4、直到根节点,如果处理完之后根节点是红色,则将根节点设置为黑色,否则结束

case2:若当前节点的父亲节点是红色,叔叔节点为黑色,且祖父节点、父节点、当前节点处在同一条斜线上

操作:
1、将当前节点的父节点当做当前节点
2、以当前节点(即新插入节点的父节点)进行右旋转,并且将当前节点(新插入节点的之前的父节点)和当前节点的和旋转之前的父节点的颜色互换一下

case3:若当前节点的父亲节点是红色,叔叔节点为黑色,且祖父节点、父节点、当前节点不处在同一条斜线上

操作:
1、先将新插入的节点进行左旋转
2、然后根据case2的样子处理

(有待补充)

接口中可以实现方法default

比如Map集合的接口中就有default方法

新增加了Lambda表达式

函数式编程

Exmaples:

(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

函数式接口

是只包含一个抽象方法声明的接口。每个Lambda表达式都可以隐式的赋值给函数式接口,如:

Runnable r = () -> System.out.println("hello world");

Consumer<Integer>  c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

可以使用@FunctionalInterface注释来表示一个接口是函数式接口,这样声明一个接口之后,该接口之中就只能有一个抽象方法了。

新增加了java.time包

1、包含了所有的关于日期、时间、时区、持续时间和时钟操作的类
2、这些类都是不可变的线程安全的

增加了很多时间的基本操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章