本文的目的是介绍为什么ArrayList实现使用Object[]?的详细情况,特别关注arraylist为什么不能放基本类型的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面
本文的目的是介绍为什么ArrayList实现使用Object []?的详细情况,特别关注arraylist为什么不能放基本类型的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解为什么ArrayList实现使用Object []?的机会,同时也不会遗漏关于ArrayList中的writeObject和readObject怎么用?、BAT面试题系列 基础篇(二) 数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?、Collection->List->ArrayList、LinkedList实现原理、HashMap(key:String,value:ArrayList)返回一个Object而不是ArrayList?的知识。
本文目录一览:- 为什么ArrayList实现使用Object []?(arraylist为什么不能放基本类型)
- ArrayList中的writeObject和readObject怎么用?
- BAT面试题系列 基础篇(二) 数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
- Collection->List->ArrayList、LinkedList实现原理
- HashMap(key:String,value:ArrayList)返回一个Object而不是ArrayList?
为什么ArrayList实现使用Object []?(arraylist为什么不能放基本类型)
在Java ArrayList<E>
实现中基于对象数组。
谁能解释我为什么ArrayList<E>
使用数组Object[]
而不是数据存储的实现E[]
?使用有什么好处Object[]
?
答案1
小编典典在Java中,创建通用类型的数组并不容易。
简单的方法不能编译:
public class Container<E> { E[] arr = new E[3]; // ERROR: Cannot create a generic array of E}
替换E
为Object
,一切都很好(以容器实现中其他地方增加的复杂性为代价)。
有其他方法,但是它们呈现了一组不同的权衡。有关广泛的讨论,请参见如何在Java中创建通用数组?
ArrayList中的writeObject和readObject怎么用?
我在看源码的时候,发现这两个私有方法。是private的,但是这个类中并没有使用过这两个方法,我就纳闷了,那他在什么时候用呢?我考虑过native,但是这个类中也没有native的东西。这两个方法是针对io的,分别从io读和写。
请问谁知道他是拿来干嘛的呢?不会是作者写了两个多余的方法吧…… 求指导
我主要想知道究竟可以通过哪些方式访问到private
thx in advance!
BAT面试题系列 基础篇(二) 数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
BAT面试题系列 基础篇 目录
BAT面试题系列 基础篇(一)
BAT面试题系列 基础篇(二)
BAT面试题系列 基础篇(三)
BAT面试题系列 基础篇(四)
BAT面试题系列 基础篇(五)
BAT面试题系列 基础篇(六)
BAT面试题系列 基础篇(七)
BAT面试题系列 基础篇(八)
BAT面试题系列 基础篇(二)
● 请你谈谈大O符号(big-O notation)并给出不同数据结构的例子
考察点:JAVA notation
参考回答:
大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。
大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。
同时,大O符号表示一个程序运行时所需要的渐进时间复杂度上界。
其函数表示是:
对于函数f(n),g(n),如果存在一个常数c,使得f(n)<=c*g(n),则f(n)=O(g(n));
大O描述当数据结构中的元素增加时,算法的规模和性能在最坏情景下有多好。
大O还可以描述其它行为,比如内存消耗。因为集合类实际上是数据结构,因此我们一般使用大O符号基于时间,内存,性能选择最好的实现。大O符号可以对大量数据性能给予一个很好的说明。
● 请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
考察点:Array
参考回答:
Array和ArrayList的不同点:
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
● 请你解释什么是值传递和引用传递?
考察点:JAVA引用传递
参考回答:
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.
一般认为,java内的传递都是值传递.
● 请你讲讲Java支持的数据类型有哪些?什么是自动拆装箱?
考察点:JAVA数据类型
参考回答:
Java语言支持的8种基本数据类型是:
byte
short
int
long
float
double
boolean
char
自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成Double,等等。反之就是自动拆箱。
● 请你解释为什么会出现4.0-3.6=0.40000001这种现象?
考察点:计算机基础
参考回答:
原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。
● 请你讲讲一个十进制的数在内存中是怎么存的?
考察点:计算机基础
参考回答:
补码的形式。
● 请你说说Lamda表达式的优缺点。
考察点:Java基础
参考回答:
优点:1. 简洁。2. 非常容易并行计算。3. 可能代表未来的编程趋势。
缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。
● 你知道java8的新特性吗,请简单介绍一下
考察点:java8
参考回答:
Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
方法引用− 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法− 默认方法就是一个在接口里面有了一个实现的方法。
新工具− 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
Date Time API − 加强对日期与时间的处理。
Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
● 请你说明符号“==”比较的是什么?
考点:基础
参考回答:
“==”对比两个对象基于内存引用,如果两个对象的引用完全相同(指向同一个对象)时,“==”操作将返回true,否则返回false。“==”如果两边是基本类型,就是比较数值是否相等。
● 请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的?
考点:基础
参考回答:
Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。
● 请你解释为什么重写equals还要重写hashcode?
考点:java基础
参考回答:
HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地址,这样即便有相同含义的两个对象,比较也是不相等的。HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。重载hashCode()是为了对同一个key,能得到相同的Hash Code,这样HashMap就可以定位到我们指定的key上。重载equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样我们才真正地获得了这个key所对应的这个键值对。
● 请你介绍一下map的分类和常见的情况
考点:java基础
参考回答:
java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.
Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。
Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。 HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
一般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.
HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为NULL,允许多条记录的值为NULL。
HashMap不支持线程同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致性。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。
Hashtable与HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtable在写入时会比较慢。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。
在遍历的时候会比HashMap慢TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的记录是排过序的。
Collection->List->ArrayList、LinkedList实现原理
我们在开发中会进场用到List来进行数据的处理尤其是它的两个实现ArrayList、LinkedList
但是这两种实现究竟有什么区别,这个在之前一直是没有仔细考虑的,一般写代码就直接一个ArrayList实现初始化对象,在企业级的互联网开发中比较少有数据处理超过100万的,这导致了我们在开发中对这两个实现的具体适合的应用场景有所忽略。
那么下面我们从这两种实现类的存储原理、源代码处理逻辑来进行分析,找出这两种类的区别以及何时的使用场景。
环境JDK8
一、 存储原理:
1、ArrayList内存存储原理:
ArrayList的类成员变量


private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
从成员变量的定义可以看出ArrayList是对数组的一个封装,那么这时候可以根据数组的特点初步判断:在进行正常的读写操作时将会是比较便利的。
2、LinkedList内存处处原理:
LinkedList的类成员变量:


transient int size = 0;
transient Node<E> first;
transient Node<E> last;
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
我们可以看到LinkedList的两个关键的变量first、last,类型是一个内部的Node类,而且里面有next以及prev两个引用以及泛型对象本身的引用,这是一个链表式的结构:
此结构不难看出:要对这个链表结构进行操作一定是从first或者last开始的,获取中间的元素都要通过一个一个遍历的方式来进行获取,当然其也有他的优势,在排序或者常规递增增加元素时有ArrayList所没有的优势。
二、功能实现对比
1、List的常规add(obj)方法
(1)ArrayList实现


public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//确认初始化的容器大小是否是长度为0的容器
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);//判断容器是否满足要求,进行扩容
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//以原大小的一半进行扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//新建一个数组然后将原数组进行复制
}
private static int hugeCapacity(int minCapacity) {//最大容量是Integer.MAX_VALUE
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
这段代码可以看出如果new一个ArrayList没有进行大小初始化的时候,在第一次add的时候会有一个初始化容器大小10,在操作过程中如果出现容器大小不够时,会重新创建一个3/2大小容量的数组,然后进行数组复制。也就是说如果在容器够的情况下ArrayList
的操作速度还是很快的。
(2)LinkedList实现


public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
典型的链表操作逻辑,重新new一个Node节点然后调整last引用以及newNode和lastNode内部的引用,达到add的操作效果。从实现原理来看LinkedList是没有add的数量上限的,并且add操作的也比较快。
2、List的常规add(int index, E element)方法
(1)ArrayList实现


public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); //扩容判断以及执行
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//将index往后的元素整体向后移动一位
elementData[index] = element;
size++;
}
这里的实现很明显这种操作需要对数组内容copy,并且index的值相对于容器size越小,进行copy的元素就越多,资源消耗的也就越多了。
(2)LinkedList实现


public void add(int index, E element) {
checkPositionIndex(index);//进行index越界检查
if (index == size)
linkLast(element);//直接进行尾部追加
else
linkBefore(element, node(index));
}
Node<E> node(int index) {//遍历找到对应的Node节点
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
在插入某一个节点时,还是会对链表进行遍历,找到对应index位置的节点,然后进行节点的插入操作,也就是index的值相对于容器size的值越大,遍历的数量越多,相应的消耗的也越多,这种情况会比ArrayList的会优越一些。
3、List的常规get(int index)方法
(1)ArrayList实现


public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {//直接通过数组下标获取元素返回
return (E) elementData[index];
}
内部实现直接通过数组下表获取元素这种方式无疑是最快的方式了。
(2)LinkedList实现


public E get(int index) {
checkElementIndex(index);
return node(index).item;//通过遍历获取对应index位置的元素
}
从实现上可以看到index位置越是靠后,需要遍历的元素就越多,这种方法的性能是没法跟ArrayList进行比较的。
4、List常规方法remove
(1)ArrayList实现


public E remove(int index) {//直接通过index对数组进行copy移位,并且最后一个赋值null
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public boolean remove(Object o) {//先要遍历对比,获取对应的index然后进行移除操作
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {//数组移除,需要将index后的数据copy前移一位,最后一位赋值null
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
}
两种remove都要进行数组中部分元素的copy移位,不同的是remove(obj)多了一个遍历比较的过程。
(2)LinkedList实现


public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));//node方法遍历寻找对应的index的node然后调用unlink
}
public boolean remove(Object o) {//遍历比较相等的数值找出对应的node然后调用unlink
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;//目标节点的下一个节点
final Node<E> prev = x.prev;//目标节点的上一个节点
if (prev == null) {
first = next;//边界情况
} else {
prev.next = next;//改变上一个节点的next引用
x.prev = null;//这里进行引用的消除方便回收
}
if (next == null) {
last = prev;//边界情况
} else {
next.prev = prev;//改变下一个节点的prev引用
x.next = null;//这里进行引用的消除方便回收
}
x.item = null;//这里进行引用的消除方便回收
size--;
modCount++;
return element;
}
两种remove都要进行遍历,然后调用unlink进行链路节点的删除,不同的是remove(obj)多了一个equals的对比操作
小结:
通过以上的list常用的功能对比他们的实现,暂做出如下的总结:
当list的读操作比较多(不涉及排序、无序插入、元素移除)时,ArrayList具有明显的优势,如果可以根据业务判断容器使用的大小,那么ArrayList是绝佳的选择。
而LinkedList在排序、无序插入、元素移除等写的方面具有优势。
我们在使用的时候可以根据业务场景判断读写的具体情况进行选择。
补充:
ArrayList以及LinkedList都实现了iterator相关的接口,但是获取的iterator对象的相关遍历操作都是基于原有的数据存储结构进行操作的,因此在性能上与原有的list本身操作并无差距
三、性能测试验证
我们已经了解了数据的存储结构,以及根据接口实现原理大致推断了性能差别,但是还是需要实际的测试进行验证,关于验证会在后续进行补充的
HashMap(key:String,value:ArrayList)返回一个Object而不是ArrayList?
我将数据存储在HashMap中(键:字符串,值:ArrayList)。我遇到问题的部分声明了一个新的ArrayList“当前”,在HashMap中搜索字符串“
dictCode”,如果找到,则将current设置为返回值ArrayList。
ArrayList current = new ArrayList(); if(dictMap.containsKey(dictCode)) { current = dictMap.get(dictCode); }
“ current = …”行返回编译器错误:
Error: incompatible typesfound : java.lang.Objectrequired: java.util.ArrayList
我不明白… HashMap是否返回一个Object而不是我存储在其中的ArrayList作为值?如何将该对象转换为ArrayList?
谢谢。
答案1
小编典典在该范围内如何表达HashMap声明?它应该是:
HashMap<String, ArrayList> dictMap
如果不是,则假定为对象。
例如,如果您的代码是:
HashMap dictMap = new HashMap<String, ArrayList>();...ArrayList current = dictMap.get(dictCode);
那不管用。相反,您想要:
HashMap<String, ArrayList> dictMap = new HashMap<String, Arraylist>();...ArrayList current = dictMap.get(dictCode);
泛型的工作方式是类型信息对编译器可用,但在运行时不可用。这称为类型擦除。HashMap的实现(或任何其他泛型实现)正在处理Object。类型信息在编译期间用于类型安全检查。请参阅泛型文档。
还要注意,ArrayList
它也被实现为通用类,因此您可能还需要在其中指定一个类型。假设您ArrayList
的课程包含MyClass
,则上面的行可能是:
HashMap<String, ArrayList<MyClass>> dictMap
今天关于为什么ArrayList实现使用Object []?和arraylist为什么不能放基本类型的讲解已经结束,谢谢您的阅读,如果想了解更多关于ArrayList中的writeObject和readObject怎么用?、BAT面试题系列 基础篇(二) 数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?、Collection->List->ArrayList、LinkedList实现原理、HashMap(key:String,value:ArrayList)返回一个Object而不是ArrayList?的相关知识,请在本站搜索。
本文标签: