大数据学习笔记——Java篇之集合框架(ArrayList)

Java集合框架学习笔记

1. Java集合框架中各接口或子类的继承以及实现关系图:

 

2. 数组和集合类的区别整理:

数组:java

1. 长度是固定的算法

2. 既能够存放基本数据类型又能够存放引用数据类型数组

3. 存放进数组的必须是相同类型的数据安全

VS数据结构

集合类:多线程

1. 长度是可变的并发

2. 只能存放对象的引用app

3. 存放进集合的能够是不一样的数据类型框架

3. 集合类经常使用API源码分析

 在以后的大数据学习中,灵活运用各类各样的数据结构能够说是一项基本技能了,所以,了解各类数据结构的底层源码将有助于用户更好地使用各类开源框架,如下将以ArrayList为例,详细地解读源码,其余各类数据结构之后也会陆续更新:ide

 3.1 文档解读

那么首先,咱们先摘录一段文档,从总体上把控一下ArrayList类的概况:

* Resizable-array implementation of the <tt>List</tt> interface.  Implements
* all optional list operations, and permits all elements, including
* <tt>null</tt>. In addition to implementing the <tt>List</tt> interface,
* this class provides methods to manipulate the size of the array that is
* used internally to store the list. (This class is roughly equivalent to
* <tt>Vector</tt>, except that it is unsynchronized.)

(1) 这段话首先点明了ArrayList类是实现自List接口的可调整大小的数组,说明它的底层仍然是使用数组实现的,它实现了一切可选的有关list的操做,而且容许任何类型的元素进入该集合,包括null

(2) 除了实现List接口外,此类还提供了方法可以内部地操做数组的长度来存储list

(3) 此类与Vector基本一致,区别只是Vector类是线程安全的,而ArrayList不是

* <p>The <tt>size</tt>, <tt>isEmpty</tt>, <tt>get</tt>, <tt>set</tt>,
* <tt>iterator</tt>, and <tt>listIterator</tt> operations run in constant
* time. The <tt>add</tt> operation runs in <i>amortized constant time</i>,
* that is, adding n elements requires O(n) time. All of the other operations
* run in linear time (roughly speaking). The constant factor is low compared
* to that for the <tt>LinkedList</tt> implementation.

(1) 这段话主要列举了一些方法的时间复杂度,首先是size,isEmpty,get,set,iterator和ListIterator的方法是常数时间的复杂度O(1)

(2) add方法的复杂度是“amortized constant time”,分段式的常数时间,意思就是说add方法的复杂度是须要分类讨论的,若是是add一个元素,那么时间复杂度是O(1),而若是是"adding n elements",时间复杂度就变成了O(n)

(3) 除上述两种情形,其余全部的操做都是线性时间的复杂度,而常数因子对于LinkedList的实现来讲要低一些

* <p>Each <tt>ArrayList</tt> instance has a <i>capacity</i>.  The capacity is
* the size of the array used to store the elements in the list. It is always
* at least as large as the list size. As elements are added to an ArrayList,
* its capacity grows automatically. The details of the growth policy are not
* specified beyond the fact that adding an element has constant amortized
* time cost.

(1) 每个ArrayList类的实例对象都有一个“容量”,容量的意思是用来在list中存放元素的数组的长度,而这个长度至少和list的长度同样大

(2) 当元素被添加到一个ArrayList的对象时,它的容量也会自动增加,然而,尽管以前提到增添元素的时间复杂度是分段式的常数时间,增加策略的细节是并不明确的

* <p>An application can increase the capacity of an <tt>ArrayList</tt> instance
* before adding a large number of elements using the <tt>ensureCapacity</tt>
* operation. This may reduce the amount of incremental reallocation.

(1) 这段话提到了一个API,ensureCapacity方法,在把大量元素添加到ArrayList中去以前,使用这个API能够提升实例对象的容量

(2) 这种方法可以下降在增添元素时从新分配空间所产生的开销

* <p><strong>Note that this implementation is not synchronized.</strong>
* If multiple threads access an <tt>ArrayList</tt> instance concurrently,
* and at least one of the threads modifies the list structurally, it
* <i>must</i> be synchronized externally. (A structural modification is
* any operation that adds or deletes one or more elements, or explicitly
* resizes the backing array; merely setting the value of an element is not
* a structural modification.) This is typically accomplished by
* synchronizing on some object that naturally encapsulates the list.

* If no such object exists, the list should be "wrapped" using the
* {@link Collections#synchronizedList Collections.synchronizedList}
* method. This is best done at creation time, to prevent accidental
* unsynchronized access to the list:<pre>
* List list = Collections.synchronizedList(new ArrayList(...));</pre>

(1) 必需要注意的是ArrayList这一实现子类并不是是线程安全的(Vector类线程安全),若是有多个线程并发地进入到一个ArrayList实例对象中去而且至少有一个线程结构上修改了这一实例对象,那么就必须在外部进行同步!!!

(2) 何为结构上改变了一个数据结构:仅仅是将这个集合中的某一个元素的值进行设置不能称之为结构化地改变一个集合,必需要添加或删除一个或多个元素,换言之使得这个集合的长度发生了改变才能叫作结构化地改变一个集合

(3) 文档中还推荐若是涉及到了多线程的场景,最好在建立对象的时候就使用同步的集合类,能够调用Collections工具类的静态方法实现,给出的例子是:List list = Collections.synchronizedList(new ArrayList(...)) ;

* <p><a name="fail-fast">
* The iterators returned by this class's {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator's own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}. Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.

(1) 这段话提到了两个迭代器,Iterator和ListIterator,这两种迭代器都是“fail-fast”的

(2) 那么何为"fail-fast"呢?文档中又提到了一个叫作ConcurrentModificationException即“并发修改异常”的异常,当一个集合的迭代器对象被建立出来以后,当集合使用了它自己的方法进行告终构上的改变,好比,add,remove方法而没有使用迭代器的方法时,就会抛出这个异常;而迭代器的这种行为是"fail-fast"的,由于一旦遇到并发修改,迭代器将不会采起任何武断的,不明确的行为,而是“快速地”在采起下一步行动以前就抛出这个异常

3.2 API解读

 首先咱们看一下ArrayList类的成员变量以及以前提到过的那个ensureCapacity方法:

3.2.1 成员变量

  /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;

 能够看到,ArrayList默认的容量是10个元素,而且准备了两个空的Object类型的数组,EMPTY_ELEMENTDATA以及DEFAULTCAPACITY_EMPTY_ELEMENTDATA,后者与前者的区别在于,后者能够知道ArrayList被添加了第一个元素以后,数组的长度应该要被被扩展到多长,这个长度是由DEFAULT_CAPACITY指定的,数值默认为10,elementData变量被transient所修饰,代表了它不可以被序列化(多是为了节省存储空间),size变量指的是集合所存放的元素的个数

3.2.2 构造方法

  /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }

 构造方法一共有三种:

(1) public ArrayList(int initialCapacity):第一种构造方法指定了一个初始容量,若是初始容量大于0,则新建一个该长度的Object类型数组,若是等于0,则返回成员变量中的长度为0的数组变量,若是小于0,则抛异常

(2) public ArrayList():第二种构造方法是一个空参构造,使用这种方式,默认建立一个长度为10的数组

(3) public ArrayList():此外还提供了一个构造方法能够传入一个集合对象c,该构造方法的执行流程是首先调用toArray方法转换成数组对象赋给elementData,因为返回值有可能不是Object类型的数组,所以又在if判断中调用了Arrays工具类的copyOf方法将其转化成数组

3.2.3 ensureCapacity方法

  /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } /** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size.  : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }

 (1) 在讲解ensureCapacity方法以前,咱们先来看一个叫作trimToSize的方法,这个方法能够当作是一个优化手段,若是elementData对象的长度是大于size的,那么就将它的长度调整至size大小,从而达到了节省空间的目的

(2) 在以后的API中,咱们会反复看到modCount变量,查看了一下本类,并无看到这个变量,说明咱们应该去父类中找寻它,最终,在它的父类抽象类AbstractList中找到了它,根据文档可知,它实际上是一个修改计数器,也就是以前提到过的"Structual modification",只有发生告终构化的改变才会触发这个变量的增长,很显然,上文的trimToSize引发告终构化的改变,所以致使了这一变量的自增1

(3) 如今咱们正式开始查看ensureCapacity方法的代码,当用户传入的参数minCapacity大于10的时候,就会调用另外一个方法,ensureExplicitCapacity(minCapacity),这个方法中,咱们看到了注释,//overflow-conscious code,翻译过来就是防止出现溢出现象,也就是说,只有当你指定的最小容量是大于elementData.length的时候,才会触发扩容操做!

(4) 成员变量MAX_ARRAY_SIZE解读:因为虚拟机将某些"header words"转化到数组中去,所以这个值并不是是Integer.MAX_VALUE,而是整型的最大值减8,一旦想要分配的数组的长度大于这个值,则会触发内存溢出错误,OutOfMemoryError

(5) 扩容操做的具体实现,grow(int minCapacity):首先oldCapacity变量记录了elementData本来的长度,而后将oldCapacity + (oldCapacity >> 1)也就是oldCapacity的1.5倍赋值给了变量newCapacity,若是扩了容后这个值都还比minCapacity小,那么就把minCapacity赋给newCapacity,若是newCapacity大于MAX_ARRAY_SIZE,就调用hugeCapacity()方法,在这个方法中,有可能会抛出OOM错误,最后使用Arrays.copyOf(elementData,newCapacity)方法实现了数组扩容

3.2.4 经常使用的API

contains方法

  public boolean contains(Object o) {
        return indexOf(o) >= 0; } /** * Returns the index of the first occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>, * or -1 if there is no such index. */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }

contains方法中调用了indexOf方法,经过这个方法的返回值是否大于等于0来判断list是否包含某元素,而查看indexOf方法可知,它是经过遍历这个elementData数组,若是equals方法返回true,则返回这个索引,若是找完了都没找到,则返回-1,由此可知,若是用户自定义了一个类,就必需要重写equals方法,那么下面,咱们就举一个例子验证一下这个问题!

首先定义一个学生类Student

public class Student {

    private String name; private int age; private int id; public Student(String name, int age, int id) { this.name = name; this.age = age; this.id = id; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && id == student.id && name.equals(student.name); } }

 而后写一个测试类: 

import java.util.ArrayList;

/*
    测试ArrayList的contains方法
 */
public class StudentTest { public static void main(String[] args) { ArrayList<Student> students = new ArrayList<Student>(); Student stu1 = new Student("tom", 10, 1); Student stu2 = new Student("alice", 20, 2); Student stu3 = new Student("peter", 25, 3); students.add(stu1); students.add(stu2); students.add(stu3); System.out.println(students.contains(new Student("tom",10,1))); } }

首先咱们把equals方法注释起来,最终控制台输出的结果为false;而后将注释放开,结果变为了true,由此得证。

add方法

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }

 

add方法中是经过调用ensureCapacityInternal方法来实现数组的扩容的,而这个方法在以前讲解ensureCapacity时并未说起,那么,咱们再回过头来查看这个方法的源码,可知,当原数组是个空数组时,会直接把长度扩容到10,而后执行语句elementData[size++] = e,注意,++是写在后面的,所以执行顺序应该是先在size的索引位置处添加上新元素,而后size再自增1;add语句不断执行,数组的长度不断增加,当size + 1大于10的时候,ensureExplicitCapacity方法中的防溢出代码就会触发grow操做,将原数组的长度扩张到1.5倍,而后继续执行相同流程

add方法的另外一个重载

 

  /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }   /** * A version of rangeCheck used by add and addAll. */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }

整个过程使用下图便可解释:

 

好比我要在第二个索引位置处加上元素7,实际过程就是如上图所示,将3,4,5,6这四个元素日后移动一格,而后在空出来的那一位上填上7便可

addAll方法

public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0;
}

addAll方法的实现原理是首先调用toArray方法将一个集合对象c转换成Object数组,以后获取到这个数组的长度,而后调用arraycopy方法将c中的每个元素都加到ArrayList的实现子类对象中去,最后判断加的集合是否为空,空的话就返回false,非空代表添加成功,返回true。注:addAll方法与add方法的最大区别是add方法会将一整个集合看做一个元素进行添加,而addAll则会把一个集合中的元素打散了一个一个地进行添加

remove方法

public E remove(int index) {
        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;
}

 

一样画图演示:

根据源码算法,若是须要移除的是索引为2的元素,首先计算出须要移动的元素个数为3,而后使用数组拷贝方法将index + 1以后的全部元素拷贝到index的位置,这样再将最后一个索引置空交给java的垃圾回收机制处理便可,最后返回须要移除的索引值对应的元素

注意:当在遍历集合的同时删除元素时,因为会发生总体移动,所以须要注意remove以后将索引减一!

batchRemove方法:

private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified;
} public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false);
} public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); } 

(1) 查看removeAll以及retainAll方法可知,它们两个都是经过调用batchRemove方法来实现的,区别只是removeAll方法中complement参数是false,而retainAll方法是true,因而咱们转向研究batchRemove方法,首先能够看到定义了两个局部变量r和w,能够理解为read 和 write,r负责遍历ArrayList中的元素,w负责将符合条件的元素进行写出,看到这里,咱们就恍然大悟,原来complement参数指的是你要保留仍是移除,若是指定的是true,即只有当集合中的元素和ArrayList中的相等时才写出,那么就等同于retainAll方法,而反之亦然

(2) 了解了上述这一点咱们就能理解elementData[w++] = elementData[r]这句代码了,咱们发现这个方法是套在try-finally框架中的,这就意味着,不管try里面的语句有没有发生异常,finally语句块中的语句是必定会被执行到的,那么咱们转而去看一下finally中到底作了些什么吧!首先看第一个if块中的代码,if(r != size),我相信,大多数人在看到这里时都是懵逼的,try中的是一个循环语句,当r等于size的时候就会跳出循环,因此最终r应该是等于size的才对,那么这句语句为何会被执行到呢?咱们先跳过这个问题不谈,先看一下r = size的时候会发生什么?很明显,r = size的时候代码会运行到第二个if判断,即if(w != size),这段代码就相对好理解一些了,因为在以前的try语句块中咱们已经找到了符合要求的元素并进行写出了,所以在第二个if语句块中,直接把w以后的元素直接置空,最后将size的值调整到w的值便可,而modified变量这时也变成了true,由于确确实实进行过了修改!

(3) 那么回到第二点中的遗留问题,何时才会出现try语句块中循环条件没有执行完的状况呢?不着急,先看一下finally语句块一上来的那两句注释,// Preserve behavioral compatibility with AbstractCollection,// even if c.contains() throws.

翻译过来的意思是,它要和AbstractCollection类保持兼容性,contains方法是有可能抛出异常的,这样一来循环条件执行不完这种状况就是有可能会发生的了,所以在finally语句块中第一个if判断就有可能被触发!咱们再回过头来看这个if判断,能够发现实际上它就是把没有遍历到的那些元素(即size - r个元素)又拷贝到了w索引的后面,而后执行完w += size - r以后再判断w是否和size相等

3.2.5 迭代器

并发修改异常举例:

import java.util.ArrayList;
import java.util.ListIterator; /* 演示并发修改异常 */ public class ConcurrentModificationExceptionDemo { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); list.add(4); ListIterator<Integer> it = list.listIterator(); while(it.hasNext()){ if(it.next().equals(1)){ list.add(5); } } } } Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.lf.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:18)

在此例中,使用ListIterator进行集合的遍历,然而却调用了list自身的add方法进行元素的添加,结果抛出了"ConcurrentModificationException"的并发修改异常

三种集合迭代方法:注意,foreach的本质其实仍是迭代器!!!

import java.util.ArrayList;
import java.util.Iterator; /* 演示三种迭代集合的方法 */ public class IterateDemo { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); list.add("tom"); list.add("alice"); list.add("peter"); list.add("mary"); //使用Iterator Iterator<String> it = list.iterator(); while(it.hasNext()){ System.out.println(it.next()); } System.out.println("======================="); //使用索引法 for(int i = 0; i < list.size(); i++){ System.out.println(list.get(i)); } System.out.println("======================="); //加强for循环 for (String name : list) { System.out.println(name); } } } 

3.2.6 泛型以及泛型方法

泛型的好处:

1. 对进入集合的元素进行了类型检查,将运行时期的异常提早到了编译时期

2. 避免了类型转换异常,ClassCastException

import java.util.Date;

/*
    演示泛型方法,定义一个泛型打印方法,能够打印任何数据类型
 */
public class GenericMethodDemo { public static void main(String[] args) { //打印字符串 genericPrint("tom"); //打印整数 genericPrint(3); //打印当前日期 genericPrint(new Date()); } public static <T> void genericPrint(T t){ System.out.println(t); } } 

泛型方法的定义方式和泛型类不一样,须要把泛型写在返回值的前面,程序会根据用户传入的参数自定义地判断它是属于什么数据类型的

3.2.7 泛型通配符

 

/*
    演示泛型通配符
 */

import java.util.ArrayList;
import java.util.Collection; class A{ } class B extends A{ } class Generic{ public void test0(Collection<?> c){ } public void test(Collection<? extends A> c){ } public void test2(Collection<? super A> c){ } } public class GenericDemo { public static void main(String[] args) { Generic gen = new Generic(); //任何类型均可以传入 gen.test0(new ArrayList<String>()); //A以及A的子类均可以传入泛型中去 gen.test(new ArrayList<B>()); //A以及A的父类均可以传入泛型中去 gen.test2(new ArrayList<Object>()); } }
相关文章
相关标签/搜索