1.新增forEach方法
List、Set、Map在Java8版本中都增加了forEach方法,方法的入参是Consumer,Consumer是一个函数式接口,可以简单理解成允许一个入参,但没有返回值的函数式接口。下面以ArrayList的forEach源码为例,来分析该方法是如何实现的 ,具体源码如下。
源码
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
@Override
public void forEach(Consumer<? super E> action) {
//判断非空
Objects.requireNonNull(action);
//拷贝modCount的原始值
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
//每次循环都会判断该数组是否被修改,一旦被修改,停止循环
for (int i=0; modCount == expectedModCount && i < size; i++) {
//执行循环内容,action代表需要处理的任务
action.accept(elementData[i]);
}
//如果数组被修改了,就抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
源码解析
- action.accept就是在for循环中要处理的任务,如使用log.info(“当前值为:{}”, value)来打印一些信息,代码示例如下。
@Slf4j public class TestForEach { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>() {{ add(1); add(3); add(2); add(4); }}; //value是每次循环的入参,即list中的每个元素 list.forEach(value -> log.info("当前值为:{}", value)); } }
- forEach方法上打了@Override注解,说明该方法是继承实现的,该方法定义在Iterable接口上,Java7和Java8的ArrayList都实现了该接口,但在Java7的ArrayList源码中并没有发现该方法的实现,编译器也没有报错。这是因为Iterable接口的forEach方法被加上了default关键字,这个关键字只会出现在接口类中,被该关键字修饰的方法无需强制要求子类继承,但需要自己有默认实现,具体源码如下所示。
源码
不仅仅forEach这个方法是这么做的,List、Set、Map接口中很多新增方法都是这么实现的,通过default关键字,可以让Java7的集合子类无需实现Java8中新增的方法。如果想在接口中新增一个方法,但又不想子类强制实现该方法时,可以给该方法加上default关键字,这个在实际工作中,也经常使用到,算是重构的小技巧。public interface Iterable<T> { default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } }
2.ArrayList的扩容逻辑修改
ArrayList无参初始化时,Java7是直接初始化10的大小,Java8去掉了这个逻辑,初始化时是空数组,在第一次add时才开始按照10进行扩容,源码如下所示。
源码
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
//Java8
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//Java7
public ArrayList() {
this(10);
}
}
3.HashMap
Java8和Java7在HashMap上的区别主要有以下几点。首先和ArrayList一样,Java8中的HashMap在无参构造器中丢弃了Java7中直接把数组初始化为16的做法,而是采用在第一次新增时,才开始扩容数组大小的策略。其次是两个版本的hash算法计算公式也不同,Java8的hash算法更加简单,代码更加简洁。
Java8的HashMap增加了红黑树的数据结构,这个是Java7中没有的,Java7只有数组 + 链表的结构,Java8中提出了数组 + 链表 + 红黑树的结构。当key为Java的API时,如String,这些hashcode实现很好的API,几乎不会出现链表转化成红黑树的情况,因为String这些API的hash算法很完善。只有当key是自定义的类并且需要覆写hashcode时,才会真正使用到红黑树,来提高检索速度。正是因为Java8新增了红黑树,所以几乎所有操作数组的方法,都发生了变动,如put、remove等操作。Java7的很多问题都被Java8解决了,如扩容时极小概率的死锁,丢失数据等问题。最后,Java8还新增了一些好用的方法,如getOrDefault,具体源码如下所示。
源码
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
//如果key对应的值不存在,返回期望的默认值defaultValue
@Override
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
}
还有putIfAbsent(K key, V value)方法,意思是,如果map中key已经存在了,那么value就不覆盖,如果不存在,则会新增。
还有compute方法,意思是允许把key和value的值进行计算后,再put到map中,为防止key值不存在造成未知错误,map还提供了computeIfPresent方法,表示只有在key存在的时候,才执行计算,代码示例如下。
@Slf4j
public class TestComputMap {
public static void main(String[] args) throws IOException, InterruptedException {
HashMap<Integer, Integer> map = Maps.newHashMap();
map.put(10, 10);
log.info("compute 之前值为:{}", map.get(10));
map.compute(10, (key, value) -> key * value);
log.info("compute 之后值为:{}", map.get(10));
//还原测试值
map.put(10, 10);
//如果为11的key不存在的话,需要注意value为空的情况,下面这行代码就会报空指针
// map.compute(11,(key,value) -> key * value);
//为了防止key不存在时导致的未知异常,一般有两种办法
//1:自己判断空指针
map.compute(11, (key, value) -> null == value ? null : key * value);
//2:computeIfPresent方法里面判断
map.computeIfPresent(11, (key, value) -> key * value);
log.info("computeIfPresent 之后值为:{}", map.get(11));
}
}
4.Arrays
Java8的Arrays类提供了一些以parallel开头的方法,这些方法支持并行计算,在数据量大的时候,会充分利用CPU,提高计算效率,如parallelSort方法,底层有判断,只有数据量大于8192时,就会走并行的实现,大大提升了计算效率。
5.面试题
(1).Java8在List、Map接口上新增了很多方法,为什么Java7中这些接口的实现者不需要强制实现这些方法呢?
答:因为这些新增的方法被default关键字修饰了,default一旦修饰接口上的方法,就需要在接口的方法中写默认实现,并且子类无需强制实现这些方法,所以Java7的接口实现者无需感知。
(2).说说computeIfPresent方法的使用?
答:computeIfPresent是可以对key和value进行计算,然后把计算结果重新赋值给key的,并且如果key不存在时,不会报空指针,会返回null值。
(3).Java8中集合新增了forEach方法,和普通的for循环有啥不同?
答:新增forEach方法的入参是函数式的接口,如Consumer和BiConsumer,这样做的好处是封装了for循环的代码,让使用者只需关注实现循环的业务逻辑,简化了重复的for循环代码,使代码更加简洁。普通的for循环,每次都需要写重复的for循环代码,forEach把这种重复的逻辑隐藏了,更加便于使用。
(4).Java8和7中HashMap有啥区别?
答:HashMap8和7的差别比较大,新增了红黑树并修改了底层数据逻辑,修改了hash算法,几乎所有底层方法都重写了。