17.Java8相较于Java7在集合模块的改动

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();
		}
	}
}

源码解析

  1. 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));
        }
    }
    
  2. forEach方法上打了@Override注解,说明该方法是继承实现的,该方法定义在Iterable接口上,Java7和Java8的ArrayList都实现了该接口,但在Java7的ArrayList源码中并没有发现该方法的实现,编译器也没有报错。这是因为Iterable接口的forEach方法被加上了default关键字,这个关键字只会出现在接口类中,被该关键字修饰的方法无需强制要求子类继承,但需要自己有默认实现,具体源码如下所示。
    源码
    public interface Iterable<T> {
    	default void forEach(Consumer<? super T> action) {
    	    Objects.requireNonNull(action);
    	    for (T t : this) {
    	        action.accept(t);
    	    }
    	}
    }
    
    不仅仅forEach这个方法是这么做的,List、Set、Map接口中很多新增方法都是这么实现的,通过default关键字,可以让Java7的集合子类无需实现Java8中新增的方法。如果想在接口中新增一个方法,但又不想子类强制实现该方法时,可以给该方法加上default关键字,这个在实际工作中,也经常使用到,算是重构的小技巧。

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算法,几乎所有底层方法都重写了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值