java进阶-第八讲 泛型、集合
1 什么是泛型(Generic)
public class Node {
int elment;
Node next;
public Node() {
}
public Node(int elment, Node next) {
this.elment = elment;
this.next = next;
}
}
..............
public class LinkList {
Node head;
int size = 0;
void add(int elem) {
if (head == null) {
head = new Node(elem, null);
}
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = new Node(elem,null);
size++;
}
}
..................
public class TestList {
public static void main(String[] args) {
LinkList linkList = new LinkList();
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
System.out.println(linkList1.size);
}
}
- 以上是简单的单链表,我们构造的链表中只能存放int型的数据,如果我们想要存放String类型的数据,那么我们就需要修改代码或者是重新构造一个链表。这样做不通用。
- 要使得程序变得通用,我们就要使用泛型(jdk1.5新特性)
- 具体的做法如下:
public class Node<T> {
T elment;
Node next;
public Node() {
}
public Node(T elment, Node next) {
this.elment = elment;
this.next = next;
}
}
..............
public class LinkList<T> {
Node head;
int size = 0;
void add(T elem) {
if (head == null) {
head = new Node(elem, null);
}
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = new Node(elem,null);
size++;
}
public T get(T elem) {
if (head == null) {
throw new NullPointerException("链表不存在");
}
Node curr = head;
while (curr.next != null) {
if (curr.elment != elem) {
curr = curr.next;
}else if (curr.elment == elem){
break;
}
}
return (T)curr.elment;
}
}
..................
public class TestList {
public static void main(String[] args) {
LinkList<String> linkList = new LinkList<>();
linkList.add("a");
linkList.add("b");
linkList.add("a");
linkList.add("a");
linkList.add("a");
linkList.add("a");
System.out.println(linkList.size);
LinkList<Integer> linkList1 = new LinkList<>();
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
linkList1.add(1);
System.out.println(linkList1.size);
}
}
什么是泛型:
泛型就是类型参数化。也就是说,SUN公司给java程序员提供了一个机制,这种机制和方法参数的传递类似。他们把类型(基础类型、引用类型)做成了一个类似变量的东西(类型参数 T V K),使得我们可以给这个变量传类型。
T ---> 基础数据类型 Object Stering int[]
思考问题:
程序写好以后,要编译,问,给类型参数T传递一个值(类型名)比如:Object,这个过程在哪个阶段发生?运行时还是编译时?编译时干了什么,运行时又干了什么?
编译的时候把所有的T都替换了,替换成什么了?Object
因为java中就没有"T V K"之类的类型,所以编译的时候就做好替换。
运行时其实应该是多态的机制在起作用。
这叫做类型擦除。
思考,泛型在哪里不能使用?static修饰的变量或者是方法,不能使用泛型。static修饰的是没有多态的。static修饰的只有一种形态,叫做静态。
注意:<T>这种形式,其实是泛型的声明,一般出现在类名后,或者是方法返回值类型的位置。
在方法返回值类型的位置出现<T>T
以上的方式是自定义泛型,也可以直接<具体的类型名>
比如<Object> <String>
如果是具体的类型,不能是基础数据类型,类型名一般推荐使用大写字母,
不推荐使用小写字母或者是单词,更不能使用关键字。
2 集合(重点,超级重点)
集合就是一种容器,可以装很多东西。
在java中,集合就是数据结构,其实就是工具。比如链表、顺序表、树等...
集合在java中,只能存放引用,不存放具体的值。
集合:Collection
Interface Collection<E> 是一个接口,它的父接口是Iterable(可迭代的,可遍历)
Collection的继承关系:

3 先学习ArrayList
补充一个关键字的介绍:transient 该属性不被序列化
跟着API帮助文档,和源码学习:
看源码,不是所有的代码都要能看懂。要看一些关键的代码。
要注意源码中的成员属性,尤其要注意一些有意义的常量,这些是以后面试常问
笔试常考的内容。同时还要看注释。
如果要了解一个类,就概要的了解一下这个类的源码。
java可以对接口多实现,最重要的接口一般都在最前面。
要了解一个类的实现方式,首先看成员属性,如果成员属性中没有具体的信息
要追溯到父类中去看。
ArrayList类源码:
private static final int DEFAULT_CAPACITY = 10;// 默认的初始化容量
private static final Object[] EMPTY_ELEMENTDATA = {};// 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
成员属性:
transient Object[] elementData;
这说明ArrayList底层(这就是说ArrayList要构造对象,对象中的内容是什么?)
是Object[] elementData;
private int size;// 这应该是数组中元素的个数
构造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这是一个无参构造,用无参构造构造出来的ArrayList对象,初始化容量是多大?
有无参构造构造出来的ArrayList对象,初始化容量大小就是 "0"
解释如下图:

<? extends E>
1.?和E都是引用类型,只要引用类型,就可能存在继承关系
2.? extends E 说明,? 所代表的类型,继承自E这个类型
3.? 表示不可知,不知道,在这里是个占位符,就是说?是表示不知道的任何类型
4.因为有了extends之后,说明,这个?可以传递的类型参数是E的子类
5.这个?是一个占位符,它占住了一个位置。只能是E的子类。
6.也就是说,这个泛型的上界只能是E,也就是说这种语法规定了泛型参数的范围
7.这里可以传入的参数只能是E或者是E的子类。
<? super E>:只能传入E或者是E的父类。这规定了类型参数的下界
public void test(Collection<? super T> c) {
}
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);
}
}
解析:
如果传入的初始化容量大于0,那么就会在堆中new Object[initialCapacity]对象
如果传入的参数为0,那么ArrayList中的Object[] elementData数组指向常量池
中的EMPTY_ELEMENTDATA空元素的Object[]数组。
如果小于0,直接抛出运行时异常:IllegalArgumentExceptio
这是运行时异常,不强制要求处理。

- 第二个有参构造:通过集合的实现类来构造ArrayList
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
这种用法不多,用得最多的是通过ArrayList来构造ArrayList
具体的执行方式如下:
(第一张图是指ArrayList作为参数的时候,已经有内容了。)
(第二张图是指ArrayList作为参数的时候,调用了无参构造)
A表示:EMPTY_ELEMENTDATA
B表示:DEFAULTCAPACITY_EMPTY_ELEMENTDATA


- 补充一个重要的发现:泛型的类型推断 infer arguments
泛型是可以进行类型推断的,钻石表达是就是推断类型用的
ArrayList<String> al = new ArrayList<>();
在new ArrayList<>()中,"<>"砖石表达式中不需要有任何的类型
编译器会自动推断出它的类型,它的类型推断的依据是ArrayList<String>中
它的String类型参数
有上下界的泛型,它的推断机制如下:
public ArrayList(Collection<? extends E> c)
ArrayList<Integer> arrList = new ArrayList<>();
ArrayList<String> arrayList2 = new ArrayList<>(arrList);
上面这个构造方法是通过ArrayList对象构造一个新的ArrayList对象
这条语句,编译报错,因为无法进行类型推断。"cannot infer arguments"
如果没有砖石表达式"<>",下面这条语句,编译能够通过,还能执行。
ArrayList<String> arrayList2 = new ArrayList(arrList);
但是,它不符合我们的要求,因为我们希望在new ArrayList(Conllection<? extends E> c)
构造方法中,限定可以传入的类型参数,我们希望这个类型是String或者是String的子类
但是,我们知道String没有子类
所以这里只能传入String,不能传入Integer
但是,如果没有砖石表达式,这里编译通过,说明,没有砖石表达式,根本不做类型推断
这种情况处理的方式有两种:
第一种,确保传入的参数一定是E的子类或者是E,这样编译能过
第二种,在方法参数之前的砖石表达式<>中传入具体的类型参数。
ArrayList<String> arrayList2 = new ArrayList<String>(arrList);
这时,报错在参数列表中,有利于程序员的判断,知道是哪里出问题了。
类型参数 引用类型名作为参数 String、Object等等作为参数
参数类型 参数是String、Object等等类型
new ArrayList<String>(arrList);这里是就近推断,也是就近原则。
T[] oK
- 记住,在使用泛型的时候,等号右边一定要带有砖石表达式。不带不行。
4 ArrayList中的add(E)方法:这是重点
- 在ArrayList中的add方法运行的原理是: 初始化的时候如果调用无参构造,那么第一次调用add方法的时候,会扩容
- 当add方法执行完毕以后,elementData数组的大小扩容为10
- 当elementData中的元素个数为elementData.length 的时候,会再次扩容。
- 也就是说,当数组满了的时候,会再次扩容。
- 再次扩容为(当前长度+当前长度/2)
加入第一个元素
public boolean add(String e = "a") {
ensureCapacityInternal((size = 0) + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity = 1) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY = 10, minCapacity = 1);
}
ensureExplicitCapacity(minCapacity = 10);
}
private void ensureExplicitCapacity(int minCapacity = 10) {
modCount++;
if (minCapacity = 10 - elementData.length = 0 > 0)
grow(minCapacity = 10);
}
private void grow(int minCapacity = 10) {
int oldCapacity = elementData.length = 0;
int newCapacity = (oldCapacity = 0) + (((oldCapacity = 0) >> 1)=0);
if (newCapacity = 0 - minCapacity = 10 < 0)
newCapacity = minCapacity = 10;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity = 10);
}
public static Object[] copyOf(Object[] original = elementData ,
int newLength = 10) {
return (Object[]) copyOf(original = elementData,
newLength = 10,
original.getClass() = Object[].class);
}
public static Object[] copyOf(
Object[] original = elementData,
int newLength = 10,
Class<? extends Object[]> newType= Object[].class) {
@SuppressWarnings("unchecked")
Object[] copy = ((Object)newType == (Object)Object[].class)
? ( Object[]) new Object[newLength = 10]
: ( Object[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original = elementData, 0, copy, 0,
Math.min(original.length = 0, newLength = 10));
return copy;
}
返回copy
private void grow(int minCapacity = 10) {
elementData = Arrays.copyOf(elementData, newCapacity = 10);
}
elementData = copy 这时elementData中有了10个元素大小的空间,但是没有值
接着执行以下语句:
public boolean add(String e = "a") {
ensureCapacityInternal((size = 0) + 1);
elementData[size++] = e;
return true;
}
第一次调用add方法,完成
所得结果为:Object[] elementData 大小已经为10
且elementData[0] = "a";
内存图如下:

5 java中的位运算
">>" 带符号的右移
2 >> 1 = 1
-4 >> 1 = -2
"<<" 带符号的左移
2 << 1 = 4
-4 << 1 = -8
"<<<" : 无符号左移
">>>":无符号右移
在程序设计中,位运算是最快的,%运算是效率是最差的
位运算效率最高 + > - > * > / > %