泛型与集合
一、泛型
1.泛型定义
泛型是将数据类型参数化,即在编写代码时将数据类型定义成参数,这些类型参数在使用之前再进行指明。
泛型提高了代码的重用性,使得程序更加灵活、安全和简洁。
JDK 5.0之前,为了实现参数类型的任意化,都是通过Object类型来处理,缺点:代码臃肿,需要进行强制类型转换,参数类型必须已知,否则容易引起ClassCastException异常。
JDK 5.0之后,Java增加对泛型的支持,解决以上缺点。泛型经常在类、接口和方法的定义中,分别称为泛型类、泛型接口和泛型方法。
GenericDemo.java:
class Generic <T>{
private T data;
public Generic(){
}
public Generic(T data){
this.data = data;
}
public T getData(){
return data;
}
public void setData(T data){
this.data = data;
}
public void showDataType(){
//获取对象的类名并打印输出
System.out.println("数据的类型是:"+data.getClass().getName());
}
}
public class GenericDemo{
public static void main(String[] args){
//采用带参数的构造方法实例化泛型对象
Generic <String> strObj = new Generic <String>("欢迎使用泛型类");
strObj.showDataType();
System.out.println(strObj.getData());
System.out.println("--------------------------------");
Generic <Double> dObj = new Generic <>(3.1415);
dObj.showDataType();
System.out.println(dObj.getData());
System.out.println("--------------------------------");
Generic <Integer> intObj = new Generic <>(123);
intObj.showDataType();
System.out.println(intObj.getData());
System.out.println("--------------------------------");
}
}
2.泛型的限制
(1)实例化泛型时,类型参数只能是类类型(包括自定义类),如String、Integer等,不能是简单类型。
(2)同一个泛型类可以有多个版本(不同参数类型),不同版本的泛型类的实例是不兼容的,例如Generic 与Generic 的实例是不兼容的。
(3)定义泛型时,类型参数只是占位符,不能直接实例化,例如“new T()”是错误的。
(4)不能实例化泛型数组。
(5)泛型类不能继承Throwable及其子类,即泛型类不能是异常类。
二、集合概述
Java的集合类是一些常用的数据结构,如队列、栈、链表等。
Java集合就像一种“容器”,用于存储数量不等的对象,并按照规范实现一些常用的操作和算法。
1.集合框架
Java所有集合类都在java.util包下,
Java集合类主要有两个接口派生而成:Collection和Map,这两个接口是集合框架的根接口。
NoteBook.java:
import java.util.ArrayList;
public class NoteBook{
private ArrayList<String> notes = new ArrayList<String>();//ArrayList是有序列表
public void add(String s){
notes.add(s);//添加数据
}
public int getSize(){
return notes.size();//获取数据条数
}
public String getNote(int index){
return notes.get(index);//根据下标找到数据
}
public void removeNote(int index){
notes.remove(index);//删除下标为index的数据
}
public String[] list(){
String a[] = new String[notes.size()];
for(int i=0;i<notes.size();i++){
a[i] = notes.get(i);
}
return a;
}
public static void main(String[] args){
NoteBook nb = new NoteBook();
nb.add("first");
nb.add("second");
nb.add("third");
System.out.println(nb.getSize());
System.out.println(nb.getNote(1));
nb.removeNote(1);
String[] a = nb.list();
for(String s:a)//获取数组a的每一个值并赋给s
{
System.out.println(s);
}
}
}
使用迭代器后:
import java.util.ArrayList;
import java.util.Iterator;
public class NoteBook{
private ArrayList<String> notes = new ArrayList<String>();
public void add(String s){
notes.add(s);
}
public int getSize(){
return notes.size();
}
public String getNote(int index){
return notes.get(index);
}
public void removeNote(int index){
notes.remove(index);
}
public void list(){
Iterator<String> iterator = notes.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static void main(String[] args){
NoteBook nb = new NoteBook();
nb.add("first");
nb.add("second");
nb.add("third");
System.out.println(nb.getSize());
System.out.println(nb.getNote(1));
nb.removeNote(1);
nb.list();
}
}
两种方式的运行结果一致如下:
(注意:默认下标从0开始)
2.迭代器接口
迭代器(Iterator):可以采用统一的方式对Collection集合中的元素进行遍历操作。该接口是Collection接口的父接口。
方法:
boolean hasNext():判断是否有下一个可访问的对象,有返回true,没有返回false。
E next():返回可访问的下一个元素。
void remove():移除迭代器返回的最后一个元素,该方法必须紧跟在一个元素的访问后执行。
三、集合类
1.Collection接口
Collection接口是Set、Queue和List接口的父接口。该接口中的方法可以操作这三个接口中的任一个集合。由接口的特点可知,其内部的方法都是抽象方法,在应用时需要重写。
注意:
(1)add()、addAll()、remove()、removeAll()和retainAll()方法可能会引发不支持该操作的UnsupportedOperationException异常。
(2)将一个不兼容的对象添加到集合中,将产生ClassCastException异常。
(3)Collection接口没有提供获取某个元素的方法,但可以通过iterator()方法获取迭代器来遍历集合中的所有元素。
(4)虽然Collection中可以存储任何Object对象,但不建议在同一个集合容器中存储不同类型的对象,建议使用泛型增强集合的安全性,以免引起ClassCastException异常。
2.List 接口及其实现类
List接口:有序、可重复的集合,与数组类似,List集合可以记住每次添加元素的顺序,因此可以根据元素的索引访问List集合中的元素,元素可以重复且长度是可变的(相当于可变长数组)。
ArrayList(数组列表)和Vector(向量)是List接口的两个典型实现类,两者功能类似,推荐使用前者。
ArrayListDemo.java:
import java.util.*;
public class ArrayListDemo{
public static void main(String[] args){
ArrayList <String> list = new ArrayList <>();
//向集合中添加元素
list.add("北京");
list.add("上海");
list.add("天津");
list.add("济南");
list.add("青岛");
//错误,只能添加字符串
//list.add(1);//错误原因:不同版本的泛型类的实例是不兼容的
//使用foreach语句遍历
System.out.println("使用foreach语句遍历");
for(String e:list){
System.out.println(e);
}
System.out.println("---------------------");
System.out.println("使用迭代器遍历:");
//获取ArrayList的迭代器
Iterator <String> iterator = list.iterator();
//使用迭代器遍历
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("---------------------");
//删除下标索引是1的元素,即第二个元素“上海”
list.remove(1);
//删除指定元素
list.remove("青岛");
System.out.println("删除后剩下的数据:");
for(String e:list){
System.out.println(e);
}
}
}
运行结果:
3.Set 接口及其实现类
Set接口:类似一个罐子,将一个对象添加到Set集合时,Set集合无法记住添加的顺序,因此Set集合中的元素不能重复。
常用的实现类:HashSet、TreeSet、EnumSet。
**HashSet:**大多数使用Set集合时都使用该实现类,HashSet使用Hash算法来存储集合中的元素,具有良好的存、取以及可查找性。
**TreeSet:**采用“树”的数据结构来存储集合元素,可保证几何中的元素处于排序状态。
**EnumSet:**专为枚举类设计的集合类,所有元素必须是指定的枚举类型。
HashSetDemo.java:
import java.util.HashSet;
import java.util.Iterator;
public class HashSetDemo{
public static void main(String[] args){
HashSet <String> hs = new HashSet <>();
//向集合中添加元素
hs.add("北京");
//添加重复元素
hs.add("北京");
hs.add("上海");
hs.add("天津");
hs.add("济南");
hs.add("青岛");
System.out.println(hs);//以数组形式输出
//使用foreach语句遍历
System.out.println("使用foreach语句遍历");
for(String e:hs){
System.out.println(e);
}
System.out.println("---------------------");
//删除元素
hs.remove("青岛");
System.out.println("删除后剩下的数据:");
Iterator <String> iterator = hs.iterator();
//使用迭代器遍历
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
运行结果:
结果中北京并没有出现在第一位,且仅出现了一次,证明了Set集合无法记住添加的顺序,Set集合中的元素不能重复的特点。
4.Queue 接口及其实现类
Queue接口:以“先进先出(FIFO)”的方式排序各个元素。
Deque(双端队列)是Queue的子接口,LinkedList是Deque和List两个接口的实现类,兼具队列和列表两种特性,是最常使用的集合类之一。
LinkedListDemo.java:
import java.util.LinkedList;
public class LinkedListDemo{
public static void main(String[] args){
LinkedList <String> books = new LinkedList <>();
//在队尾添加元素
books.offer("Java Web开发技术详解");
//在队头添加元素
books.push("数据库原理");
//在队头添加元素
books.offerFirst("Java8基础应用与开发");
//在队尾添加元素
books.offerLast("C++程序设计");
System.out.println(books);
System.out.println("---------------------");
//使用foreach语句遍历
System.out.println("使用foreach语句遍历:");
for(String e:books){
System.out.println(e);
}
}
}
运行结果:
注意:经检验知push与offerFirst等价,都可以实现将数据插入开头;offer与offerLast等价,都是将数据插入末尾。
5.Map 接口及其实现类
Map接口:以key-value键值对映射关系存储的集合,可以根据每个元素的key来访问对应的value,Map集合中的key不允许重复,value可以重复。key-value键值对映射关系的示意图如下所示。
Map接口的两个实现类:
HashMap:基于哈希算法的Map接口的实现类,该实现类提供所有映射操作,并允许使用null键和null值,但不能保证映射的顺序,即是无序的映射集合。
TreeMap:基于“树”接口来存储的Map接口实现类,可以根据键的自然顺序进行排序,或定制排序方式。在使用TreeMap时,不允许使用null键和null值,当使用get()方法获取元素时,没有指定的键时会返回null。
HashMapDemo.java:
import java.util.HashMap;
public class HashMapDemo{
public static void main(String[] args){
HashMap <Integer,String> hm = new HashMap <>();
//添加数据,key-value键值对形式
hm.put(1,"张三");
hm.put(2,"李四");
hm.put(3,"王二麻子");
hm.put(4,"赵五");
hm.put(5,"刘六");
hm.put(null,null);
//根据key值获取value
System.out.println(hm.get(1));
System.out.println(hm.get(3));
System.out.println(hm.get(5));
System.out.println(hm.get(null));
//根据key删除
hm.remove(1);
//key为1的元素已经删除,返回null
System.out.println(hm.get(1));
}
}
TreeMapDemo.java:
import java.util.TreeMap;
public class TreeMapDemo{
public static void main(String[] args){
TreeMap <Integer,String> tm = new TreeMap <>();
//添加数据,key-value键值对形式
tm.put(1,"张三");
tm.put(2,"李四");
tm.put(3,"王二麻子");
tm.put(4,"赵五");
tm.put(5,"刘六");
//错误,不允许null键和null值
//tm.put(null,null);
//根据key值获取value
System.out.println(tm.get(1));
System.out.println(tm.get(3));
System.out.println(tm.get(5));
//错误,不允许null键和null值
//System.out.println(tm.get(null));
//根据key删除
tm.remove(1);
//key为1的元素已经删除,返回null
System.out.println(tm.get(1));
}
}
四、集合转换(了解)
Map集合可以转换为Collection集合,方法:
entrySet():返回一个包含了Map中元素的集合,每个元素都包括键和值。
keySet():返回Map中所有键的集合。
values():返回Map中所有值的集合。
MapChangeCollectionDemo.java:
import java.util.*;
import java.util.Map.Entry;
public class MapChangeCollectionDemo{
public static void main(String[] args){
HashMap <Integer,String> hm = new HashMap <>();
//添加数据,key-value键值对形式
hm.put(1,"张三");
hm.put(2,"李四");
hm.put(3,"王二麻子");
hm.put(4,"赵五");
hm.put(5,"刘六");
//使用entrySet()方法获取Entry键值对集合,Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。
Set<Entry <Integer,String>> set = hm.entrySet();
System.out.println("所有Entry:");
//遍历所有元素
for(Entry <Integer,String> entry:set){
System.out.println(entry.getKey()+":"+entry.getValue());
}
System.out.println("-----------------");
//使用keySet()方法获取所有键的集合
Set <Integer> keySet = hm.keySet();
System.out.println("所有key:");
for(Integer key:keySet){
System.out.println(key);
}
System.out.println("-----------------");
//使用values()方法获取所有值的集合
Collection <String> valueSet = hm.values();
System.out.println("所有value:");
for(String value:valueSet){
System.out.println(value);
}
}
}
五、集合工具类(了解)后期扩展
Java集合框架汇总还提供了两个辅助工具类:Collections和Arrays。
Collections工具类:提供了一些对Collection集合常用的静态方法,如排序、复制、查找以及填充等操作。
Arrays工具类:提供了针对数组的各种静态方法,如排序、复制、查找等操作。
总结
本章重点掌握前三个知识点,泛型是将数据类型参数化,提高了代码的重用性。集合类分别讲了Collection接口下的三个常用子接口:List=>ArrarList(下标从0开始,可重复);Set=>HashSet(HashSet使用Hash算法来存储集合中的元素,具有良好的存、取以及可查找性,无序不可重复);Queue=>LinkedList(先进先出队列,push=offerFirst,offer=offerLast)。Map接口:Map=>HashMap(有键值对,key值不可以重复,value值可以重复,无序,键值对可以为null)。