作业12:List集合类
一 为什么使用List?
1 List与数组
List | 数组 |
---|---|
可变长度 | 固定长度 |
方便的接口(支持Java8的Stream) | / |
2 List的实现方式
数组:ArrayList
- 查询效率比较高
- 插入效率比较低(指的是add(int index, E element)方法,不是add(E element)方法。)
- 特殊情况下,数量多时,空间占用比LinkedList高(当数组刚刚扩容1.5,约等于3分之1的空间没有利用。)
链表:LinkedList
- 查询效率比较低
- 插入效率比较高
- 数量少时,空间占用比ArrayList高
空间占用计算
默认开启指针压缩:对象头占用12字节,8字节填充格式
new Integer(1) = 12 bytes 对象头 + 4 bytes int value = 16 bytes
new Integer[1] = 12 bytes 对象头 + 4 bytes 数组指针 + 4 bytes int value + 4 bytes 填充 = 24 bytes
验证数量少时,LinkedList的空间占用高(以单向链表计算)
(1)单个Node占用 = 12 bytes 对象头 + 4 bytes next 指针 +16 bytes 元素占用(以Integer为例子)= 32 bytes
new MyLinkedList() 并 添加N个元素 = N * 32 bytes Node 占用 +( 2*4 bytes first 和 last 指针占用 + 4 bytes size 字段占用(int)+ 12 bytes 对象头 ) = 32N+24 bytes
(2)new MyArrayList() 并 添加N个元素 = (12 bytes 对象头 + 4 bytes 数组指针占用 + 4*N + 填充字节?) + (4 bytes 数组指针占用 + 4 bytes size字段占用 + 12 bytes 对象头 + 4 bytes 填充 )= 20 + 4N + 填充字节? + 24 bytes
但是数组可能存在3分之1的浪费,所以随着N的增长,总有个时刻,ArrayList的占用比LinkedList高。
对象占用空间大小参考:https://blog.csdn.net/u013256816/article/details/51008443
3 自己实现一个可变数组
(1)数组方式
import java.util.Iterator;
/**
* 因为jdk的List接口太多方法了,所以没实现。
* @param <E>
*/
public class MyArrayList<E> {
private int size;
private Object[] objs;
public MyArrayList() {
// 从零开始:因为可能创建对象之后一直不添加元素。
this(0);
}
public MyArrayList(int size) {
this.objs = new Object[size];
this.size = size;
}
private void resize(int capacity) {
if (capacity == 0) capacity = 1;
Object[] copy = new Object[capacity];
for (int i = 0; i < size; i++)
copy[i] = objs[i];
objs = copy;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public Iterator<E> iterator() {
return new ArrayIterator<>();
}
public void add(E e) {
// 扩容方式:翻倍
if (size == objs.length) resize(2 * objs.length);
objs[size++] = e;
}
private void checkArgument(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException();
}
public E get(int index) {
checkArgument(index);
return (E) objs[index];
}
private void shift(int index) {
for (int i = index; i < size - 1; i++)
objs[i] = objs[i + 1];
objs[--size] = null;
}
public E remove(int index) {
checkArgument(index);
E obj = (E) objs[index];
shift(index);
// 扩容方式:减半
if (size > 0 && size == objs.length / 4)
resize(objs.length / 2);
return obj;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder("[");
for (int i = 0; i < size; i++) {
str.append(objs[i].toString());
if (i != size - 1)
str.append(",");
}
return str.append("]").toString();
}
private class ArrayIterator<E> implements Iterator<E> {
private int next = 0;
@Override
public boolean hasNext() {
return next < size;
}
@Override
public E next() {
if (!hasNext())
throw new RuntimeException("List hasn't next element.");
return (E) objs[next++];
}
}
// 测试方法
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
list.add("My");
list.add("Hello");
list.add("World");
System.out.println(list);
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
}
}
(2)链表方式
public class MyLinkedList<E> {
private Node first;
private Node last;
private int size;
public MyLinkedList() {
first = null;
last = null;
size = 0;
}
// 单向链表
private class Node {
public Object t;
public Node next;
Node(Object t) {
this.t = t;
}
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public Iterator<E> iterator() {
return new LinkedListIteraotr();
}
private class LinkedListIteraotr implements Iterator {
private Node current = MyLinkedList.this.first;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public E next() {
if (!hasNext()) throw new NoSuchElementException();
E t = (E) current.t;
current = current.next;
return t;
}
}
public void add(E e) {
Node node = new Node(e);
if (isEmpty()) first = node;
else last.next = node;
last = node;
size++;
}
private void checkArgument(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException();
}
public E get(int index) {
checkArgument(index);
return (E) findNode(index).t;
}
private Node findNode(int index) {
Node node = first;
for (int i = index; i > 0; i--)
node = node.next;
return node;
}
public E remove(int index) {
checkArgument(index);
E e ;
if (size == 1) {
Node node = findNode(index);
e = (E) node.t;
first = last = null;
} else {
if (index == 0) {
Node node = findNode(index);
e = (E) node.t;
first = node.next;
} else if (index == size - 1) {
Node preNode = findNode(index - 1);
last = preNode;
e = (E) preNode.next.t;
preNode.next = null;
} else {
Node preNode = findNode(index - 1);
Node midNode = preNode.next;
e = (E) midNode.t;
preNode.next = midNode.next;
}
}
size--;
return e;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder("[");
for (Node node = first; node != null; node = node.next) {
str.append(node.t.toString());
if (node.next != null)
str.append(",");
}
return str.append("]").toString();
}
public static void main(String[] args) {
MyLinkedList<Integer> list = new MyLinkedList<>();
list.add(1);
list.add(2);
list.add(3);
// list.remove(0);
// list.remove(1);
System.out.println(list);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
温馨提示:Coursera 有基础算法课程,不是计算机专业,可以通过这个网站学习一下。
4 JDK与自己实现比对
(1)数组:ArrayList
- 扩容:jdk为1.5倍,自己实现为2倍
- 自己实现省略了异常和检测,如:容量不能超过Integer.MAX_VALUE
- 省略了fail-fast(其他方案:fail-safe,简单来说就是复制一份数据进行遍历,这样多线程修改也不会发生异常,但是牺牲了实时性,空间开销较大。)异常检测机制
- 定义:java集合类的一种错误机制,看源码可知多线程状态下去删除集合中的元素,将导致ConcurrentModificationException.
- 实现:迭代前记住修改次数(modCount),其他线程进行修改时,modCount改变,与原来的不相符,所以直接抛出异常。
- 注意:modCount没有用volatile修饰,意味着当一个线程修改了modCount,其他的线程并不能马上感知,即不保证fail-fast机制一定会发生。
- 猜想:至于jdk为什么不采用volatile修饰,我个人认为ArrayList既然不是线程安全的类,那么就不要在多线程中使用,这样就不会发生错误。多线程相关的操作:可见性也好,原子性也好,或多或少都会影响操作性能,所以jdk没有将modCount修饰为volatile吧。
volatile的可见性参考:https://www.cnblogs.com/zhengbin/p/5654805.html
- 其他API没实现,不做比较
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
transient Object[] elementData;
// 1.扩容和异常检测
//调用关系:add(E) -> add(E,Object[],int) -> grow -> grow(int) -> newCapacity(int)
public boolean add(E e) {
modCount++;// fail-fast机制,定义在AbstractList中
add(e, elementData, size);
return true;
}
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;
// 扩容具体:1.5倍
// 其他就是越界检测
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity-MAX_ARRAY_SIZE<=0)? newCapacity: hugeCapacity(minCapacity);
}
// 3.fail-fast
// ArrayList非线程安全,不在多线程情况下使用,所以没有实现fail-fast机制。
private class Itr implements Iterator<E> {
// 省略了一部分方法
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// 具体看这个方法就好
checkForComodification();
int i = cursor;
if (i >= size) throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// 调用List.remove不修改expectedModCount,即单线程下需要在Iterator.remove删除元素。
public void remove() {
if (lastRet < 0) throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 很简单的方法:比对List的当前modCount和原先modCount
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
(2)链表:LinkedList
- JDK实现了Deque接口(双向队列),所以内部的Node多了prev节点,即双向链表,我内部使用单向链表。
- 与ArrayList相同都有fail-fast机制,其他差不多,不贴代码了。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
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;
}
}
}
5 增强For循环的实现原理
(1)Java代码
List<Integer> list = new ArrayList<>();
list.add(new Integer(1));
for (Object i : list){ }
for (Integer i : list){ } // 类型检查并转换 Object -> Integer
(2)字节码
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: new #4 // class java/lang/Integer
12: dup
13: iconst_1
14: invokespecial #5 // Method java/lang/Integer."<init>":(I)V
17: invokeinterface #6, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 类型擦除,存入Object类型的元素
22: pop
23: aload_1
24: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
29: astore_2
30: aload_2
31: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
36: ifeq 49 // 当栈顶int型数值等于0时跳转,hasNext返回0或1,上一期作业说过,jvm字节码没有布尔类型,True -> 1,False -> 0
39: aload_2
40: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
45: astore_3
46: goto 30 // 无条件跳转
49: aload_1
50: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
55: astore_2
56: aload_2
57: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
62: ifeq 78
65: aload_2
66: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
71: checkcast #4 // class java/lang/Integer
74: astore_3
75: goto 56
78: return
(3)结论
- 泛型存入List时,进行类型擦除,将泛型转换为Object类型。
- 从List中取出泛型时,需要类型检查和转换,Object -> 泛型。
- 增强for循环实现原理,其实是编译器编译为iterator循环。
(4)自己实现的List支持增强for循环吗?
// 可以支持,但是必需实现Iterable接口,不然编译器不通过
// 错误提示:for-each要求数组或者java.lang.Iterable
public class MyArrayList<E> implements Iterable<E> {
// @Override注解,加不加无所谓,非强制
@Override
public Iterator<E> iterator() {
return new ArrayIterator<>();
}
}
(5)数组的增强for循环
- JAVA代码
int[] ints = new int[0];
for (int i :ints){}
- 字节码(这一段有点点复杂,冷静分析就好了,但是我不做过多的解释了,下面解释应该足够了。)
0 iconst_1
1 newarray 10 (int) // 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
3 astore_1
4 aload_1
5 astore_2 //ints
6 aload_2
7 arraylength // 获得数组的长度值并压入栈顶
8 istore_3
9 iconst_0 // 获取常数0
10 istore 4 // 栈顶的值赋值给本地变量,0赋值给int
12 iload 4
14 iload_3
15 if_icmpge 30 (+15) // 比较栈顶两int型数值大小,当结果大于等于0时跳转
18 aload_2 // 将第三个引用类型本地变量推送至栈顶,指的是ints
19 iload 4 // 将指定的int型本地变量推送至栈顶,指的是i
21 iaload
22 istore 5 // 将栈顶int型数值存入指定本地变量,指的是tmp
24 iinc 4 by 1 //将指定int型变量增加指定1
27 goto 12 (-15)
30 return
- 翻译字节码为java代码
int[] ints = new int[0];
int length = ints.length;
for (int i = 0; i - length < 0; i++){
int tmp = ints[i];
}
- newarray 10 // 其中10代表类型
参考:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.newarray
Array Type | atype |
---|---|
T_BOOLEAN | 4 |
T_CHAR | 5 |
T_FLOAT | 6 |
T_DOUBLE | 7 |
T_BYTE | 8 |
T_SHORT | 9 |
T_INT | 10 |
T_LONG | 11 |
6 数组与List转换
- 数组 => List:Arrays.asList(T[] arr)
- List => 数组:list.toArray(T[] arr)
Integer[] integers = new Integer[2];
integers[0] = 1;
integers[1] = Integer.valueOf(2);
List<Integer> list = Arrays.asList(integers);
Object[] objects = list.toArray();
// 尽量不用,大部分jdk版本都运行时报错,只有少部分支持(1.8.0_171不报错)
// Integer[] newInteger1 = (Integer[]) objects;
Integer[] newInteger2 = list.toArray(new Integer[list.size()]);
System.out.println(Arrays.toString(newInteger2)); // [1, 2]
二 线程安全的List
1 Vector
- 基本所有方法都用了synchronized,即用来对象锁,同一时刻只能有一个线程访问该对象。
public synchronized void addElement(E obj) {
modCount++;
add(obj, elementData, elementCount);
}
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
......
2 Collections.synchronizedList(ArrayList)
- RandomAccess:空接口,只用于标识能够快速随机访问。
// 快速随机访问重要表现:下标访问遍历 runs faster than 迭代器遍历:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
- 根据是否实现RandomAccess接口,创建不同的线程安全集合
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
// SynchronizedRandomAccessList继承了SynchronizedList
static class SynchronizedRandomAccessList<E> extends SynchronizedList<E> implements RandomAccess {
SynchronizedRandomAccessList(List<E> list) {
super(list);
}
// ......
}
// SynchronizedList继承了SynchronizedCollection
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
// ......
}
// 很明显对象锁,与Vector差不多,就是提供多一点的Api而已
// 唯一不同就是mutex对象锁,可以外部指定,非必定为this对象
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}
}
// ......
}
3 CopyOnWriteArrayList
- 适用于读多写少,复制需要消耗一定的性能
- 缺失了一定实时性,写过程中,其他线程读取到的都是旧值
// java10
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/**
* The lock protecting all mutators. (We have a mild preference
* for builtin monitors over ReentrantLock when either will do.)
*/
// java8不是Object,而是ReentrantLock
final transient Object lock = new Object();
private transient volatile Object[] array;
// 读取操作不加锁
public E get(int index) {
return elementAt(getArray(), index);
}
@SuppressWarnings("unchecked")
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
// 写操作加对象锁
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
// CopyOnWrite的由来:由于读不加锁,我们希望在写过程中,读取多少次数据都得到相同结果,所以不对原来的数组进行修改,而是复制一份出来。
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements); // array使用volatile修饰具有可见性,可以让其他读取线程马上知道
return true;
}
}
public E remove(int index) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
E oldValue = elementAt(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
}
// 无法重复加相同的元素,判断方法为equals方法
// 但该方法不能用做set使用,因为它在内部是循环查询O(n),不是采用Hash命中O(1),效率差距很大
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
synchronized (lock) {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i]
&& Objects.equals(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
// ......
}
- 重点介绍下复制方法
// Arrays类
@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
// Arrays类
@HotSpotIntrinsicCandidate
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)?
(T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
return copy;
}
// Array类
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
return newArray(componentType, length);
}
// Array类
@HotSpotIntrinsicCandidate
private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;
// System类
@HotSpotIntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
// 稍微展示一下JVM中的TypeArray的copy_array
// 在/hotspot/src/share/vm/oops/typeArrayKlass.cpp
void TypeArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) {
// ......
// 前面都是异常检查,上下界检查,忽略即可,重要的是下面
// This is an attempt to make the copy_array fast.
int l2es = log2_element_size();
int ihs = array_header_in_bytes() / wordSize;
char* src = (char*) ((oop*)s + ihs) + ((size_t)src_pos << l2es);
char* dst = (char*) ((oop*)d + ihs) + ((size_t)dst_pos << l2es);
Copy::conjoint_memory_atomic(src, dst, (size_t)length << l2es);
}
void Copy::conjoint_memory_atomic(void* from, void* to, size_t size) {
address src = (address) from;
address dst = (address) to;
uintptr_t bits = (uintptr_t) src | (uintptr_t) dst | (uintptr_t) size;
if (bits % sizeof(jlong) == 0) {
Copy::conjoint_jlongs_atomic((jlong*) src, (jlong*) dst, size / sizeof(jlong));
} else if (bits % sizeof(jint) == 0) {
Copy::conjoint_jints_atomic((jint*) src, (jint*) dst, size / sizeof(jint));
} else if (bits % sizeof(jshort) == 0) {
Copy::conjoint_jshorts_atomic((jshort*) src, (jshort*) dst, size / sizeof(jshort));
} else {
// Not aligned, so no need to be atomic.
Copy::conjoint_jbytes((void*) src, (void*) dst, size);
}
}
// /hotspot/src/share/vm/utilities/copy.hpp
static void conjoint_jbytes(void* from, void* to, size_t count) {
pd_conjoint_bytes(from, to, count);
}
// 最后发现该方法与平台相关
static void pd_conjoint_bytes(void* from, void* to, size_t count) {
#ifdef AMD64
(void)memmove(to, from, count);
#else
// Includes a zero-count check.
intx temp;
__asm__ volatile(" testl %6,%6 ;"
" jz 13f ;"
" leal -1(%4,%6),%3 ;"
//...中间是一大段汇编代码,大家应该不感兴趣,省略
" jbe 1f ;"
" addl $3,%1 ;"
"11: rep; smovb ;"
"12: cld ;"
"13: nop ;"
: "=S" (from), "=D" (to), "=c" (count), "=r" (temp)
: "0" (from), "1" (to), "2" (count), "3" (temp)
: "memory", "flags", "%edx");
#endif // AMD64
}
// Class类:数组类型的Class才有返回值,返回数组存放的类型
public Class<?> getComponentType() {
// Only return for array types. Storage may be reused for Class for instance types.
if (isArray()) {
return componentType;
} else {
return null;
}
}
// 简单测试一下
// Test1:API的基本使用
int[] src = {1,2,3,4,5,6,7,8,9,10};
int[] des = new int[5];
System.arraycopy(src,0,des,0,des.length);
System.out.println(Arrays.toString(des)); // [1, 2, 3, 4, 5]
// Test2:getComponentType方法
System.out.println(int[].class.getComponentType()); //int
System.out.println(Integer[].class.getComponentType()); //class java.lang.Integer
// Test3
System.out.println((Object) Object[].class == (Object) Object[].class);// true
System.out.println((Object) Object[].class == (Object) Integer[].class);// false
// Test4:Array.newInstance的作用:创建泛型数组创建并且强转不报错
Integer[] objs = (Integer[]) Array.newInstance(Integer[].class.getComponentType(), 3);
// Integer[] objs = (Integer[]) new Object[3]; // 这里会报错,所以对于不是Object数组,不嫩那个使用new Object[length]的方式创建
objs[0] = Integer.valueOf(1);
objs[1] = Integer.valueOf(2);
objs[2] = Integer.valueOf(3);
System.out.println(Arrays.toString(objs));//[1, 2, 3]
作业12:List集合类的更多相关文章
- | C语言I作业12
C语言I作业12-学期总结 标签:18软件 李煦亮 问题 答案 这个作业属于那个课程 C语言程序设计I 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/S ...
- C语言Ⅰ作业12—学期总结
一.我学到的内容 二.我的收获 作业链接 收获 C语言Ⅰ博客作业01 认识了PTA编程,博客园,Markdown基本语法1,Markdown基本语法2 C语言Ⅰ博客作业02 PTA系统常见问题解答 C ...
- C语言I作业12—学期总结
一.我学到的内容 二我的收获 作业 收获 C语言博客作业1 刚开始初步了解C语言方面的知识 学会Markdown基本语法 C语言博客作业2 学会了应该如何提问 PTA系统常见问题解答 学会了MinGW ...
- 作业——12 hadoop大作业
作业的要求来自于:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3339 Hadoop综合大作业 1.以下是爬虫大作业产生的csv文件 ...
- C语言I作业12一学期总结
一.我学到的内容 二.我的收获 作业 收获 C语言I博客作业01 学会了编程"Hello word" C语言I博客作业02 安装编译器,将代码建立在自己的文件里面 C语言I博客作业 ...
- C语言I博客作业12—学期总结
一.我学到的内容 二.我的收获(包括我完成的所有作业的链接+收获)不能只有作业链接,没有收获 作业次数 作业链接 第一次 C语言I博客作业01 第二次 C语言I博客作业02 第三次 C语言I博客作业0 ...
- C语言|作业12—学期总结
一. 我学到的内容 二. 我的收获 作业链接 收获 C语言l博客作业01 对这个专业.学科以及markdown语法有了初步了解,打印出了"Hello world!" C语言l博客作 ...
- C语言|博客作业12—学期总结
一.我学到的内容 二.我的收获 (1)https://edu.cnblogs.com/campus/zswxy/CST2019-4/homework/7603 收获:第一次接触C语言和写博客,感觉特别 ...
- C语言I博客作业12
一.我学到的内容 二.我的收获 作业链接 收获 博客第一次作业:https://www.cnblogs.com/gm12/p/11584148.html 第一次作业收获:第一次作业是我初步接触C语言的 ...
随机推荐
- arcpy 获得是否为布局mxd.activeView
arcpy 获得是否为布局mxd.activeView print mxd.activeView PAGE_LAYOUT mxd.pageSizePageSize(width=21.590043180 ...
- IDEA中log4j.properties配置文件详解
配置实例 ### 配置根 ### log4j.rootLogger = debug,console ,fileAppender,dailyRollingFile,ROLLING_FILE,MAIL,D ...
- [MySql]当虚拟机的IP地址自动更换后,JDBC使用原来的配置连不上MySql数据库时所报的异常。
Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. ...
- pymysql检查是否断开, 断开重连
python mysql使用持久链接 python链接mysql中没有长链接的概念,但我们可以利用mysql的ping机制,来实现长链接功能~ 思路: 1 python mysql 的cping 函数 ...
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_01-页面静态化需求分析
上半部分就是静态化 业务流程如下: 1.获取模型数据 2.制作模板 3.对页面进行静态化 4.将静态化生成的html页面存放文件系统中 5.将存放在文件系统的html文件发布到服务器
- 下了个pkg包的jenkins,的使用方法
三.如何启动Jenkins1.最简单的方法是双击Jenkins的pkg包,一步一步点同意,默认8080端口2.使用命令行启动打开terminal,进入到war包所在目录,执行命令: java -jar ...
- 记录一次vxworks下使用NFS组件的过程
问题:有三块CPU都运行vxworks6.9,现在想要CPU3做server,CPU1-2通过NFS访问CPU3上的文件 补充:使用防火墙可能会影响NFS访问,目前我还没有找到解决办法... 下面是过 ...
- SpringMVC接收集合页面参数
SpringMVC接收集合页面参数 Spring MVC在接收集合请求参数时,需要在Controller方法的集合参数里前添加@RequestBody,而@RequestBody默认接收的enctyp ...
- k8s学习之资料参考网址(持续更新)
此文章持续更新关于学习k8s生态的参考网址: 二进制方式搭建 (此部署方式是一步一步的部署,过程清晰) https://github.com/opsnull/follow-me ...
- Reveal v4(8796) 使用
文件下载地址 http://xclient.info/s/reveal.html dmg安装成功后, pod install 植入项目. pod 'Reveal-SDK', '~> 4', ...