Java集合
Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作
一、集合框架的概述
1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
2.1 数组在存储多个数据方面的特点:
> 一旦初始化以后,其长度就确定了。
> 数组一旦定义好,其元素的类型就确定了。
比如:String[] arr;int[] arr1;Object[] arr2(什么都能放);
2.2 数组在存储多个数据方面的缺点:
> 一旦初始化以后,其长度就不可修改。
> 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
> 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。
> 数组储存数据的特点是有序、可重复的;但对于无序、不重复数据的需求,不能满足。
.
.
二、集合框架
|----Collection接口:单列集合,用来存储一个一个对象
|----List接口:存储有序的、可重复的数据。 --> “动态”数组(因为特点和数组很像,但长度可变)
|----ArrayList、LinkedList、Vector
|----Set接口:存储无序的、不可重复的数据。
|----HashSet、linkedHashSet、TreeSet
|----Map接口:双列集合,用来存储一对(key -> value)一对的数据
|----HashMap、linkedHashMap、TreeMap、Hashtable、Properties
Collection接口:
Map接口
.
.
.
Collection接口
1、Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
2、JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
3、在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
(更严谨,是什么数据类型就装什么数据)
.
.
Collection 接口方法
1、添加
add(Object e):将元素 e 添加到集合coll中
addAll(Collection c):将 c 集合中的元素添加到当前的集合中
2、获取添加的元素个数
int size()
3、清空集合
void clear()
4、判断当前集合是否为空
boolean isEmpty()
5、是否包含某个元素,在判断是会调用equals()方法来判断
contains(Object obj):判断当前集合中是否包含obj
containsAll(Collection c):判断形参 c 中的所有元素是否都存在于当前集合中
6、删除
remove(Object obj):从当前集合中移除obj元素
removeAll(Collection c):从当前集合中移除 c 中所有元素
7、交集:获取当前集合和 c 集合的交集,并返回给当前集合
retainAll(Collection c)
8、判断两个集合中的元素是否相等(若是List接口则还要判断是否有序)
equals(Object obj)
9、集合与数组(只限对象数组,不包括基本数据类型数组)间的转换
toArray:集合 --> 数组
数组 --> 集合:调用Arrays类的静态方法asList()
注意:List arr1 = Arrays.asList(new int[]{123, 456});//这个只认为是一个数据,打印出的是地址值
10、获取集合对象的哈希值
hashCode()
11、遍历
iterator():返回迭代器(Iterator)对象,用于集合遍历
结论:
向Collection接口中实现类的对象中添加数据obj时,要求obj所在的类要重写equals()。
对于List而言:在使用 contains()、remove()、retainsAll()... 等方法时需要调用对应数据的equals()方法。
对于Set而言:
(以HashSet、LinkedHashSet为例):它的不可重复性就是通过equals()和HashCode()方法去判断。
(以TreeSet为例):它的不可重复性是通过Comparable()、Comparator()判断。
.
.
集合遍历:Iterator迭代器接口
1、Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
2、Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
3、Iterator 仅用于遍历集合,集合对象每次调用iterator()方法都得到一个全新的迭代器对象Iterator。
(也就意味着指针会再次返回到集合第一个元素前面)!!!
.
.
Iterator的方法
hasNext():检测集合中是否还有元素,存在则返回true !!!
next():返回迭代器中的下一个元素 !!!
remove():将当前迭代器返回的元素删除(使用remove()时,迭代器的指针不能为初始位置(或重复使用remove()),否则会报IllegalStateException)
遍历过程中是通过迭代器对象的remove方法,不是集合对象的remove方法。
代码:
public static void main(String[] args) {
// 创建集合
ArrayList<String> data = new ArrayList<>();
data.add("Google");
data.add("IDEA");
data.add("Baidu");
// 获取迭代器
Iterator<String> iterator = data.iterator();
// hasNext 检查集合中是否还有元素,如果有则进入循环。
while (iterator.hasNext()){
String str = iterator.next();
//判断用next()方法返回的元素是否是"IDEA"
if("IDEA".equals(str)){
//将指针指向的元素删除
iterator.remove();
}else{
// 循环打印集合中的元素
System.out.println(str);
}
}
}
迭代器的执行原理
.
.
使用 foreach 循环遍历集合元素
Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。
遍历集合的底层调用Iterator完成操作。
遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
格式:
//for(集合元素的类型 局部变量 : 集合对象)
for(Object obj : coll){
System.out.println(obj);
}
把集合对象 coll 的第一个元素赋给 obj 然后打印出来,在把第二个元素赋给 obj 再打印,以此类推。
面试代码:
public void test3(){
String[] arr = new String[]{"MM","MM","MM"};
//方式一:普通for循环:arr中元素改变
// for (int i = 0; i < arr.length; i++) {
// arr[i] = "GG";
// }
//方式二:foreach循环:arr中元素不改变
for(String i : arr){
i = "GG";
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
.
.
List
鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
.
.
一、List接口框架
|----Collection接口:单列集合,用来存储一个一个对象
|----List接口:存储有序的、可重复的数据。 --> “动态”数组(因为特点和数组很像,但长度可变),替换原有的数组
|----ArrayList:作为List接口的主要实现类;线程不安全的(遇到多线程时,可以使用工具类返回一个线程安全的集合),效率高;底层使用Object[]存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[]存储 (非常不经常使用)
二、源码分析:
1. ArrayList:
① jdk 7 的情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData。
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来容量的1.5倍,同时需要将原有的数组中的数据复制到新的数组中。
② jdk 8 中ArrayList的变化
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{},并没有创建长度为10的数组。
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]。
...
后续的添加和扩容操作与jdk 7 一样。
如果开发中基本上确定要放多少个数据建议使用带参构造器:ArrayList list = new ArrayList(int capacity);效率要高一些。
③ 小结:jdk 7中的ArrayList对象的创建类似于单例中的饿汉式,而jdk 8中的创建类似于单例中的懒汉式,延迟了数组的创建,节省内存。
2. LinkedList:
LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为为null
list.add(123);//将123封装到Node中,创建Node对象。
其中,Node的定义体现了LinkedList的双向链表的说法
3. Vector:
jdk 7和jdk 8中通过Vector() 构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,扩容为原来的2倍。
面试题:ArrayList、LinkedList、Vector三者的异同?
同:三个类都实现了List接口,存储数据的特点相同:有序、可重复的数据
不同:见上面的介绍
.
.
List接口方法
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
常用
方法
总结常用方法:
增:add(Object obj) / addAll(int index, Collection eles)
删:remove(int index) / remove(Object obj)
改:set(int index,Object ele)
查:get(int index)
插:add(int index,Object ele)
长度:size()
遍历:① Iterator迭代器 / ② 增强for循环(foreach) / ③ 普通for循环(配合get()方法)
.
.
.
Set
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
一、Set接口的框架
|----Collection接口:单列集合,用来存储一个一个对象
|----Set接口:存储无序的、不可重复的数据。
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值;
|----linkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历的,
对于频繁的遍历操作,LinkedHashSet效率高于HashSet;
|----TreeSet:可以按照添加对象的指定属性,进行排序。
.
.
二、Set分析:
一、Set:存储无序性、不可重复性
以HashSet为例声明:
1. 无序性:不等于随机性。
存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
2.不可重复性:保证添加的元素按照equals()判断时,不能返回true;即:相同的元素只能添加一个。
👇👇👇
二、添加元素的过程:以HashSet为例(体现不可重复性)
例如向HashSet中添加元素 A
① 首先调用元素 A 所在类的hashCode()方法,计算元素 A 的哈希值,
② 此哈希值接着通过某种算法计算出 A 在HashSet底层数组中的存放位置(即为:索引位置),
③ 判断数组此位置上是否已有元素(判断是不是null):
如果此位置上没有其他元素,则元素 A 添加成功。 --> 添加成功情况1
如果此位置上有其他元素 B(或以链表形式存在的多个元素),则比较元素 A 与元素 B 的hash值:
如果hash值不相同,则元素 A 添加成功。 --> 添加成功情况2
如果hash值相同,进而调用元素 A 所在的equlas()方法:
equals()返回true,元素 A 添加失败,
equals()返回false,元素 A 添加成功。 --> 添加成功情况3
对于添加成功的情况2和情况3而言:元素 A 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7:元素 A 放到数组中,指向原来的元素。 原数据 -> A
jdk 8:原来的元素在数组中,指向元素 A。 A -> 原数据
HashSet底层:数组+链表(单项链表)的结构。(前提:jdk 7)
.
.
关于hashCode()和equals()
要求:向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象(类中的属性)必须具有相等的散列码(hash值)
重写两个方法的技巧:通常参与计算hashCode()的对象的属性也应该参与到equals()中进行计算。(或直接快捷生成)
Set集合在添加数据的时候通过HashCode值就把位置确定了,之后如果再改变数据中的值,虽然这个数据的值和HashCode值改变了,但它在集合中的位置不会发生变化,从而继续添加一个和这个数据一模一样的数据时,是可以添加进去的!!!!数据和已经在集合中的数据的HashCode值和equals()虽然相等,但根据HashCode值所定义的位置是空的,进而无法进行equals()比较,所以可以添加!!!!!!!!!!!!!!!!!!!!!!
.
.
LinkedHashSet的使用
LikedHashSet作为HashSet的子类
可以按照添加对象的指定属性,进行排序
按照添加的顺序遍历的原理:
在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据(双向链表);
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
.
.
TreeSet的使用
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口,或在集合形参中添加已创建的Comparator对象。
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator接口)
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()比较。
TreeSet set = new TreeSet();
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()比较。
先创建一个Comparator对象com,
TreeSet set = new TreeSet(com);
.
.
.
Map
Map与Collection并列存在。用于保存具有映射关系的数据:key-value
Map 中的 key 和 value 都可以是任何引用类型的数据
Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类
一、Map实现类的结构:
|----Map:双列数据,存储key-value 对的数据
|----HashMap:作为Map的主要实现类;线程不安全,效率高;可以存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素(双向链表)。
对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value进行排序,实现排序遍历。此时考虑的是key的自然排序和定制排序。
底层使用红黑树。
|----Hashtable:作为古老的实现类;线程安全,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:
数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk8)
面试题:
1.HashMap的底层实现原理?
2.HashMap和Hashtable的异同?
3.CurrentHashMap 和 Hashtable的异同?
.
.
二、Map结构的理解:
Map中的key:无序的、不可重复的,使用Set存储所有的key ---> 以HashMap为例:key所在的类要重写equals()和hashCode();
Map中的value:无序的、可重复的,使用Collection存储所有的value ---> 以HashMap为例:value所在的类要重写equals();
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry;
.
.
三、HashMap的底层原理?
以jdk7为例说明:
HashMap map = new HashMap();在实例化以后,底层创建了长度是16的一维数组Entry[] table
....可能已经多次执行put....
map.put(key1,value1);
首先,调用key1所在类的hashCode()计算key1的哈希值,通过此哈希值得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时key1-value1添加成功。 ---情况1
如果此位置上的数据不为空,意味着此位置上存在一个或多个数据(以链表方式存在),
比较key1和已经存在的一个或多个数据的哈希值:
如果:key1的哈希值与已经存在的数据的哈希值都不同,此时key1-value1添加成功。 ---情况2
如果:key1的哈希值和已经存在的某一数据(key2-value2)哈希值相同,继续比较,
调用key1所在类的equals(key2)方法:
如何返回false:此时key1-value1添加成功 ---情况3
如果equals()返回true:value1的值就会替换掉value2的值
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
当数组长度超出临界值(且要存放的位置非空)时,扩容。
默认扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8 相较于 jdk7在底层实现方面的不同:
1.new HashMap():底层没有创建一个长度为16的数组
2.jdk 8底层的数组是:Node[],而非Entry[]
3.首次调用put()方法时,底层创建长度为16的数组
4.jdk 7底层结构只有:数组+链表。jdk 8中底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表的形式存在的数据个数大于8且大于64,此时此索引上所有数据改为使用红黑树存储。
源码中的重要常量:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
.
.
四:LinkedHashMap的底层实现原理(了解)
LinkedHashMap 是 HashMap 的子类
在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致
源码中的双向链表:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; //能够记录添加的元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
特点:可以按照添加的顺序实现遍历。
.
.
五:TreeMap类
TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。
TreeSet底层使用红黑树结构存储数据
向TreeMap中添加key-value,要求key必须是同一个类创建的对象
因为要按照key进行排序:自然排序、定制排序
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有
的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException。
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对
TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
.
.
六、Map中定义的方法
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
常用方法:
增: put(Object key,Object value)、putAll(Map m)
删: remove(Object key)、clear()
改: put(Object key,Object value)
查: get(Object key)
长度: size()
遍历: keySet()、values()、entrySet()
.
.
7:Properties类
Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
代码:
Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user = pros.getProperty("user");
System.out.println(user);
.
.
Collections工具类
Collections 是一个操作 Set、List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
Collections常用方法
排序操作:(均为static方法)
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找、替换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换
List 对象的所有旧值
.
.
Collections常用方法:同步控制
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
.
.
.
数据结构简述
算法 + 数据结构 = 程序
一、数据结构是什么?
数据结构(Date Structure)是一门和计算机硬件与软件都有密切相关的学科,重点研究的是在计算机的程序设计领域中探讨如何在计算机中组织和存储数据并进行高效率的运用
。
涉及内容包括:数据的逻辑关系、数据存储结构、排序算法、查找(或搜索)等。
总结:数据结构,就是一种程序设计优化的方法论,研究数据的逻辑结构
和物理结构
及他们之间的关系,并对这种结构定义相应的运输,目的加快程序的执行速度、减少内存占用的空间
。
.
.
二、
① 数据结构只是静态的描述了数据元素之间的关系。
② 高效的程序需要在数据结构的基础上设计和选择算法。
③ 算法是计算机处理信息的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行指定的任务。
总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体。
.
.
.
泛型(Generic)
把元素的类型设计成一个参数,这个类型参数叫做泛型。
例如:Collection<E>,E表示的是数据类型(引用数据类型,不能是基本数据类型)。
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时
(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
为什么要使用泛型?
.
.
泛型的嵌套:
Set<Map.Entry<Integer, String>> set = map.entrySet();
注意:这里的Map.Entry格式的意思是Map接口下的Entry接口,
因为Entry,是一个内部的接口,
不只是接口,内部类也是以这种方式表示的。
.
.
.
泛型的使用
1.jdk 5.0新增的特性
2.在集合中使用泛型:
总结:
① 集合接口或集合类在jdk 5.0时都改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型。
③ 指明完以后,在集合类或接口中的内部结构(比如:方法、构造器、属性等)使用到类的泛型,都指定为实例化的泛型类型。
比如:add(E e) ---> 实例化以后:add(Integer e)
⑩ 注意:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置可以拿包装类替换。
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
3.jdk 7新特性:类型推断(右边省略泛型)
Map<Integer, String> map = new HashMap<>();
右边的泛型虽然省略,但一定要加<>
4. 创建泛型时可以指定指定上限
格式 :
<E extends Number> (无穷小 , Number]
只允许泛型为Number及Number子类的引用调用
<E extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用
.
.
自定义泛型类、接口
格式:interface List<T> 和 class GenTest<K,V>
其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。
.
.
1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
2. 泛型类的构造器如下:public GenericClass(){}。错误的:public GenericClass<E>(){}。
说明:声明构造器的时候不用加<>,但在创建对象时要加<>。
3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4. 泛型不同的引用不能相互赋值。
例如:
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
list1 = list2; //错误的:编译不通过
取决于泛型的优点能在编译时检测错误,就不会把这段代码放跑到运行时,因为在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
5. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
6. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object(更高深)。
示例:
7. 静态方法中不能使用类的泛型。因为静态方法随着类的加载而加载,要早于对象的创建。
很有可能对象都没有创建(没有指明是什么类型)就直接用 "类.静态方法" 去调用了。
9. 异常类不能是泛型的
10. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[size];
10.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
没有类型 擦除
具体类型
子类保留父类的泛型:泛型子类
全部保留
部分保留
子类除了指定或保留父类的泛型,还可以增加自己的泛型
示例:
.
.
自定义泛型方法
泛型方法与所属的类是不是泛型类,没有关系!
在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
.
泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
public <E> List<E> copyArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
示例:
.
.
泛型在继承上的体现
类A是类B的父类,C是具有泛型声明的类或接口,C<A> 和 C<B> 二者不具备子父类关系,是两个并列的结构。(接口方式也如此)
并且两者不能相互赋值,是因为很有可能赋过来的值的泛型和声明的不一样,导致混入非设置的泛型进入,出错。
注意:要是类A是类B的的父类,A<C> 是 B<C> 的父类,可以赋值!!!
.
.
通配符的使用
1.使用类型通配符:?
比如:List<?> ,Map<?,?>
List<?>是List、List等各种泛型List的父类。
2.读取
List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
3.写入
list中的元素时,不行。因为我们不知道?是什么元素类型,我们不能向其中添加对象。(唯一的例外是null,它是所有类型的成员)
List<?> list = new ArrayList<Integer>();
list3.add(null);
// list3.add(123);//报错
注意点:
.
.
有限制的通配符
1、<?>允许所有泛型的引用调用
2、通配符指定上限 extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
3、通配符指定下限super:使用时指定的类型不能小于操作的类,即>=
举例:
<? extends Number> (无穷小 , Number]
只允许泛型为Number及Number子类的引用调用
<? super Number> [Number , 无穷大)
只允许泛型为Number及Number父类的引用调用
<? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用
体会:类型转换相关