【JavaSE】---泛型与容器类
一、泛型
1、泛型的概念
我们知道java.lang.Object类是最上层的类,它是所有类的父类。所以为了让程序通用,编写代码时候通常使得传入的值与返回的值都用Object类型为主,当需要使用相应的实例时候,必须正确地将该实例转换为正确的实例。否则程序在运行的时候会报出类型强制转换异常:ClassCastException。
通过泛型技术,这样的问题得以解决。使用泛型的主要优点是能够在编译时而不是在运行时检测出错误。泛型实际上是在定义类、接口或者方法时通过为其增加“参数类型”来实现的。
即泛型所操作的数据类型被指定为一个参数,这个参数被称为类型参数(type parameters),所以说,泛型的实质是将数据的类型参数化,按照惯例,用T或者E这样的单个大写字母来表示类型参数。
2、泛型的定义
- 泛型类的定义:
[修饰符] class 类名 <T>
- 泛型接口的定义:
[public] interface 接口名 <T>
- 泛型方法的定义:
[public] [static] <T> 返回值类型 方法名(T参数)
泛型的实际参数类型必须是 类 类型,利用泛型类创建的对象称为泛型对象,这个过程也称之为泛型实例化。
泛型的概念实际上是基于:类型也可以像变量一样实现参数化。
3、泛型类及应用
示例:
public class FanClass <T>{
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
public static void main(String[] args) {
FanClass<String> name = new FanClass<String>(); //创建FanClass<String>型的对象
FanClass<Integer> age = new FanClass<>(); //创建FanClass<Integer>型的对象
name.setObj("darkerg");
age.setObj(25);
System.out.println("姓名:"+name.getObj());
System.out.println("年龄:"+age.getObj());
}
}
4、泛型方法
要定义泛型方法,只需将泛型的类型参数<T>置于方法返回值类型前即可。在Java中,任何方法(包括静态方法和构造器)都可以声明为泛型方法。
程序示例
public class FanMethod {
public static void main(String[] args) {
Integer[] num = {1,2,3,4,5};//定义数组
String[] str = {"红","橙","黄","绿"};
display(num);
display(str);
}
public static <E> void display(E[] list){//定义泛型方法,E为类型参数。
for (int i = 0; i < list.length; i++) {
System.out.print(list[i]+" ");
}
System.out.println();
}
}
5、限制泛型的可用类型
语法:
class ClassName <T extends anyClass>
<T> = <T extends Object>
该语句表示T是ClassName类的类型参数,且T有一个限制,即T必须是anyClass类或者是继承了anyClass类的子类,或者是实现了anyClass接口的类。这意味着,当用该类创建对象时,类型实际参数必须是anyClass类或其子类是实现了anyClass接口的类,且无论是anyClass是类还是接口,在进行泛型限制时都必须使用extends关键字。
注意:
在Java中,子类被认为和父类是兼容的类型。但是在使用泛型的时候,要注意它们之间的关系。
例如:
虽然Integer是Number的子类,但是GeneralType<Integer>与GeneraType<Number>没有父子关系。
即GeneralType<Integer>不是GeneraType<Number>的子类。
这种限定称为:泛型不是协变的。
6、泛型的类型通配符和泛型数组的应用
除了上面提到的有限制的泛型类之外,还引入了通配符“?”的概念。
- 用于创建可以 重新赋值 但是不可以 修改 其内容的泛型对象。
- 方法的参数中,限制传入不想要的类型实参。
①使用同一个泛型对象名引用不同的泛型对象
只知道通配符“?”表示是某个类又或者是 继承该类的子类 又或者是 实现某个接口的类,但是具体是什么类型不知道。
泛型类名<? extends T> o = null; //声明泛型类对象o
其中,“?extends T" 表示是T 或者T的未知子类型 或者是实现接口T的类。
案例:
我们知道,java.util.LinkedList与java.util.ArrayList均是实现了接口java.util.List的类。
FanClass <? extends List> x = null;
x = new FanClass<LinkedList>(); //LinkedList类实现了List接口
//对x重新赋值,只要类型实参实现了List接口即可。
x = new FanClass<ArrayList>(); //ArrayList实现了List接口
//由于HashMap没有实现List接口,下面语句是错误的。
FanClass<? extends List>x = new FanClass<HashMap>();
②?用在方法的参数中以防止传入不允接收的类型实参
demo
public class FanClass_02 {
public static void main(String[] args) {
GeneralType<String> n = new GeneralType<>();
n.setObj("darkerg");
GeneralType.showObj(n);
GeneralType<Double> num = new GeneralType<>();
num.setObj(25.0);
System.out.println("数值型值:"+num.getObj());//不可以调用方法showObj(num)
}
}
class GeneralType<T>{
T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
public static void showObj(GeneralType<? extends String>o){
System.out.println("给出的值是:"+o.getObj());
}
}
7、继承泛型类与实现泛型接口
被定义为泛型的类或者或者接口可以被继承与实现:
public class ExtendClass<T1>{
}
class SubClass<T1,T2,T3> extends ExtendClass<T1>
如果在SubClass类继承ExtendClass类时保留父类的类型参数,需要在继承的时候指明,如果没有指明,直接使用extends ExtendClass语句进行继承声明,则SubClass类中的T1、T2和T3都会自动变为Object所以在一般情况下都将父类的类型参数保留。
在定义泛型接口的时,泛型接口也可被实现。
interface Face<T1>{
}
class SubClass<T1,T2> implements Face<T1>{
}
二、容器类
容器类是Java以类库的形式供用户开发程序时可直接使用的各种数据结构。在面向对象的思想里,一种数据结构被认为是一个容器。数组是一种简单的数据结构,除数组外Java还以类库的形式提供了许多其他数据结构。这些数据结构通常称为容器类或者集合类。
1、Java容器框架
容器框架中有两个名称分别为Collection和Set的接口,为了防止名称的冲突。将Collection称为容器,将Set称为集合。
- Collection接口,定义了所有容器的基本操作,比如添加、删除、遍历等等。
- Set、List等则提供了更加特殊的功能
- Set用来存储一组不重复的元素集合
- List的对象用于存储一个由元素构成的线性表
- Map接口,它保持了“键”到“值”的映射,可以通过键来实现对值得快速访问。
2、容器接口Collection
容器接口通常不能直接使用,但是该接口提供了添加、删除、管理数据元素的方法。Set和List都继承了Collection接口,因此这些方法对集合Set与列表List是通用的。
Collection<E>接口的常用方法
常用方法 | 功能说明 |
---|---|
int size() | 返回容器中元素的个数 |
boolean isEmpty() | 判断容器是否为空 |
boolean contains(Object obj) | 判断容器是否包含元素obj |
boolean add(E element) | 向容器中添加元素element,成功返回true,否则false |
int hashCode() | 返回容器的哈希值 |
Object[] toArray() | 将容器转换为数组,返回的数组包含容器的所有元素 |
boolean remove(Object obj) | 从容器中删除元素obj,成功返回true,否则false |
void clear() | 删除容器中的所有元素 |
Iterator<E> iterator() | 返回容器的迭代器 |
boolean equals(Object o ) | 比较此collection与指定对象o是否相等 |
void shuffle(<List<?> list) | 以随机方式重排list中的元素,即洗牌 |
boolean containsAll(Collection<?> c) | 判断当前容器是否包含容器c中的所有元素 |
boolean addAll(Coolection<?extends E>c) | 将容器c中的所有元素添加到当前容器中,集合并运算 |
boolean removeAll(Collection<?> c) | 在当前容器中删除包含在c中的所有元素,集合差运算 |
boolean retainAll(Collection<?> c) | 仅保留当前容器中也被容器c包含的元素,集合交运算 |
3、列表接口List
List是Coleection的子接口,它是一种包含有序元素的现行七宝,其中的元素必须按照顺序存放,并且可以重复,也可以是空值null。元素之间的顺序关系可以由添加到列表的先后来觉得。也可以由元素值得大小来决定。List接口适用下标来访问元素。
下标范围为0~size()-1。
List<E>接口的常用方法
常用方法 | 功能说明 |
---|---|
E get(int index) | 返回列表中指定位置的元素 |
E set(inr index,E elemenyt) | 用元素element取代index位置的元素,返回被取代的元素 |
int indexOf(Object o ) | 返回元素o首次出现的序号,若o不存在返回-1 |
int lastIndexOf(Object o ) | 返回元素o最后出现的序号 |
void add(int index,E element) | 在index位置插入元素element |
boolean add(E element) | 在列表的最后日安家元素element |
E remove(int index) | 在列表中删除index位置的元素 |
boolean addAll(Collection<? extends E> c) | 在列表的最后添加容器c中的所有元素 |
boolean addAll(int index,Collection<? extends E> c) | 在index位置按照容器c中元素的原有次序插入c中所有元素 |
ListIterator<E> listIterator() | 返回列表中元素的列表迭代器 |
ListIterator<E>listIterator(int index) | 返回从index位置开始的列表迭代器 |
①链表实现类---LinkedList
LinkedList链表类采用链表结构保存对象,使用循环双链表实现List。这种结构向链表中任意位置插入、删除元素时不需要移动其他元素,链表的大小可以动态增大或者减小的,但是不具有随机存取的特性。
LinkedList<E>类的构造方法
构造方法 | 功能说明 |
---|---|
public LinkedList() | 创建空链表 |
public LinkedList(Collection <? extends E> c) | 创建包含容器c中所有元素的链表 |
LinkedList<E>类的常用方法
常用方法 | 功能说明 |
---|---|
public void addFirst(E e) | 将元素e插入到列表的开头 |
public void addLast(E e) | 将元素e添加到列表的末尾 |
public E getFirst() | |
public E getLast() | |
public E removeFirst() | 删除并返回列表中的第一个元素 |
public E removeLast() |
②数组列表类实现类---ArrayList
ArrayList数组列表类使用一维数组实现List,该类实现的时可变数组,允许所有元素,包括null。具有随机存取的特性,插入、删除时候需要移动其他元素,当元素很多的时候插入。删除操作比较慢。
ArrayList<E>类的构造方法
构造方法 | 功能说明 |
---|---|
public ArrayList() | 创建初始容量为10的空数组列表 |
public ArrayList(int initialCapacity) | 创建初始容量为initialCapacity的空数组列表 |
public ArrayList(Collection<? extends E>c) | 创建包含容器c所有元素的数组列表,元素次序与c相同 |
ArrayList<E>类的常用方法
常用方法 | 功能说明 |
---|---|
public void trimToSize() | 将ArrayList对象的容量缩小到该列表的当前大小 |
public void forEach(Consumer<? super E> action) | 将action对象执行遍历操作 |
③使用
- 若要通过下标随机访问元素,但除了在末尾处之外,不再其他位置插入或者删除元素,应该选择ArrayList。
- 如果需要在线性表的任意位置上进行插入或者删除操作,应该选择LinkedList类
使用线性表的时候通常声明为List<E>类型,然后通过不同的实现类来实例化列表。
List<String> list1 = new LinkedList<String>();
List<String> list2 = new ArrayList<String>();
④利用LinkedList<E>类构造一个先进后出的堆栈
StringStack
package 堆栈;
import java.util.LinkedList;
//利用LinkedList<E>类构造一个先进后出的堆栈
public class StringStack {
private LinkedList<String> Id = new LinkedList<>();//创建LinkedList对象Id
public void push(String name){ //入栈操作
Id.addFirst(name); //将name添加到链表的头
}
public String pop(){ //出战操作
return Id.removeFirst(); //获取并移除堆栈中的第一个元素
}
public boolean isEmpty(){
return Id.isEmpty();
}
}
Test
package 堆栈;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
StringStack stack = new StringStack();
System.out.println("请输入数据(quit)结束");
while (true){
String input = sc.next();
if (input.equals("quit")){
break;
}
stack.push(input);
}
System.out.println("先进后出的顺序:");
while (!stack.isEmpty()){
System.out.print(stack.pop()+" ");//出栈
}
}
}
4、迭代器
迭代功能由可迭代接口Iterable和迭代器接口Iterator、ListIterator实现的,迭代器是一种允许对容器中元素进行遍历并且有选择的删除元素的对象。由于Collection接口声明继承了Iterable接口,因此每个实现了Collection接口的容器对象都可以调用iterator()方法返回一个迭代器。
Iterator<E>接口的常用方法:
常用方法 | 功能说明 |
---|---|
public abstract boolean hasNext() | 判断是否还有后续元素,若有则返回true。 |
public abstract E next() | 返回后续元素 |
public abstract void remove() | 删除迭代器当前指向的(即最后被迭代的)元素,即删除由最近一次next()或者previous()方法调用返回的元素 |
使用迭代器遍历容器的代码段:
Iterator it = c.iterator(); //c是重写了iterator()方法的容器所实现的类 的对象
while(it.hasNext()){
Object o = it.next(); //取得下一个未被迭代的元素
}
对于容器中元素的遍历次序,接口Iterator支持对List对象从前向后的遍历,但其子接口ListIterator支持对List对象的双向遍历。
ListIterator<E>接口的常用方法
常用方法 | 功能说明 |
---|---|
public abstract boolean hasPrevious() | 判断是否有前驱元素 |
public abstract E previous() | 返回前驱元素 |
public abstract void add(E e) | 将指定的元素插入到列表中。若next()方法的返回值为非空,钙元素被插入到next()方法返回的元素之前;若previous()方法的返回值为非空,该元素被插入到previous()方法返回的元素之后;若线性表没有元素,则直接将该元素加入其中 |
public abstract void set(E e) | 用元素e替换列表的当前元素 |
public abstract int nextIndex() | 返回基于next()调用的元素序号 |
public abstract int previousIndex() | 返回基于previous调用的元素序号 |
修改遍历迭代器应用
package 迭代器;
import java.util.ArrayList;
import java.util.ListIterator;
public class IteraTest {
public static void main(String[] args) {
ArrayList<Integer> a1 = new ArrayList<>();
for (int i = 1; i < 5; i++) {
a1.add(i);
}
System.out.println("数组列表的原始数据:"+a1);
ListIterator<Integer> listIter = a1.listIterator();
listIter.add(0); //在需要为0的元素前添加一个元素0
System.out.println("添加后数组列表"+a1);
if (listIter.hasNext()){//如果有后续元素
int i = listIter.nextIndex(); //执行该语句时i的值是1
listIter.next(); //返回序号为1的元素
listIter.set(9); //修改数组列表a1中序号为1的元素
System.out.println("修改后数组列表"+a1);
}
listIter = a1.listIterator(a1.size()); //重新创建从a1最后位置开始的迭代器listIter
System.out.print("反向遍历数组列表:");
while (listIter.hasPrevious()){
System.out.print(listIter.previous()+" ");//反向遍历数组列表
}
}
}
5、集合接口Set
Set是一个不含重复元素的集合接口,它继承自Collection接口,并没有声明其他方法,它的方法都是从Collection接口继承来的。
Set集合中的对象不按照特定的方式排序,只是简单地吧对象加入到集合中即可,但是加入的对象一定不能重复。集合中元素的顺序与元素加入集合的顺序无关。
①哈希集合类---HashSet
哈希集合对 所包含元素的访问并不像是线性表一样使用下标,而是根据哈希值来存取集合中的元素。哈希集合是在元素的存储位置和元素的值k之间建立一个特定的对应关系f,使得每个元素与一个唯一的存储位置相对于。因而在查找的时候,只需要根据元素的值k,计算f(k)的值即可,如果次元素在集合中,那么必定存在存储位置f(k)上,因此不需要与集合中的其他元素进行比较便可以直接取得所查的元素。称这个对应关系f为哈希函数,按照这种关系建立的表称之为哈希表,也称为散列表。
HashSet集合不保证迭代顺序,但允许元素值为null。在比较啷个加入哈希集合HashSet中的元素是否相同时,会先比较哈希值方法hashCode()的返回值是否想吐,如果相同则再使用equals()方法比较其存储位置(即内存地址),若两者相同则视为相同的元素。之所以在比较了哈希值之后,还需要通过equals方法进行比较,是因为可能出现哈希碰撞。所以,对于哈希集合来说,如果重写了元素对于类的equals()方法或者hashCode()方法中的某一个,则必须重写另一个,以保证其判断的一致性。
HashSet<E>集合类的构造方法
构造方法 | 功能说明 |
---|---|
public HashSet() | 创建默认初始容量是16,默认上座率为0.75的空哈希集合。 |
public HashSet(int initialCapacity) | 创建初始容量是initialCapacity,默认上座率为0.75的空哈希集合。 |
public HashSet(int initialCapacity,float loadFactor) | 创建初始容量是initialCapacity,默认上座率为loadFactor的空哈希集合。 |
public HashSet(Collection<? extends E>c) | 创建包含容器c中所有元素,默认上座率为0.75的哈希集合。 |
- 说明:上座率也称为装填银子,上座率的值为0.0~1.0表示集合的饱和度。当集合中的元素个数超过了容量与上座率的乘积,容量就会自动翻倍。
HashSet<E>集合类的常用方法
常用方法 | 功能说明 |
---|---|
public boolean add(E e) | 如果集合中常委包含指定元素,则添加返回true。 |
public void clear() | 删除集合中的所有元素,集合为 空 |
public boolean contains(Object o) | 如果集合中包含元素o,则返回true |
public int size() | 返回集合中所包含元素的个数,即返回集合的容量 |
测试:
package Set;
import java.util.*;
public class SetTest {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<String>();
String[] demo = {"I","come","I","see","I","go"};
for (String s : demo) {
if (!hs.add(s)) //向哈希集合hs中添加元素,但是重复的元素不添加。
System.out.println("元素"+s+"重复!");
}
System.out.println("集合的容量为:"+hs.size()+"各元素为:");
Iterator<String> it = hs.iterator();
while (it.hasNext())
System.out.print(it.next()+" ");
}
}
- 输出哈希集合元素时候并不一定是按照元素的存储顺序输出,因为哈希集合中的元素是没有特定的顺序的,如果一定要让元素有序输出,则需要使用LinkedHashSet类。
②树集合类---TreeSet
树集合类TreeSet不仅实现了Set接口,还实现了java.util.SortedSet接口。TreeSet的工作原理与HashSet相似,但是TreeSet增加了一个额外的步骤,用来保证集合中的元素总是处于有序的状态。因此,当排序很重要的时候,就选择TreeSet。TreeSet<E>类的大多数方法继承自其父类或者祖先类。
TreeSet<E>类的构造方法
构造方法 | 功能说明 |
---|---|
public TreeSet() | 创建新的空树集合,其元素按照自然顺序进行排序 |
public TreeSet(Collection<? extends E> c) | 创建包含容器c元素的新TreeSet |
TreeSet<E>类新增的方法
新增的方法 | 功能说明 |
---|---|
public E first() | 返回集合中的第一个(最低)元素 |
public E last() | 返回集合中的最后一个(最高)元素 |
public SoredSet<E> headSet(E toElement) | 返回一个新集合,元素是toElement之前的所有元素 |
public SoredSet<E> tailSet(E fromElement) | 返回一个新集合,包含fromElement及fromElement之后的所有元素 |
public SoredSet<E> subSet(E fromElement,E toElement) | 返回一个新集合,包含从fromElement到toElement之间的所有元素 |
public E lower(E e) | 返回严格小于给定元素e的最大元素,如不存在,返回Null |
public E higher(E e) | 返回严格大于给定元素e的最小元素,如不存在,返回Null |
public E floor(E e) | 返回小于等于给定元素e的最大元素,如不存在,返回Null |
public E ceiling(E e) | 返回大于等于给定元素e的最小元素,如不存在,返回Null |
测试:
package Set;
import java.util.HashSet;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
String[] demo = {"唐 僧","孙悟空","猪八戒","沙和尚","白龙马"};
for (String s : demo) {
hs.add(s);
}
TreeSet<String> ts = new TreeSet<>(hs);
System.out.println("树集合:"+ts);
System.out.println("第一个元素"+ts.first());
System.out.println("最后一个一个元素"+ts.last());
System.out.println(ts.headSet("孙悟空"));
System.out.println(ts.tailSet("孙悟空"));
System.out.println(ts.ceiling("沙"));
}
}
6、映射接口Map
Map是另一种存储数据结构的对象,它提供了键值对的映射。值是指要存入Map中的元素(对象),在将元素存入Map对象时,需要同时给定一个键,这个键决定了元素在Map中的存储位置。一个键和它对应的值构成一个条目,真正在Map中存储的是这个条目。
Map<K,V>接口的常用方法
常用方法 | 功能说明 |
---|---|
V put(K key, V value) | 以key为键,向集合中添加值为value的元素,key必须唯一。 |
void putAll(Map <? extends K, ? extends V> m) | 将映射m中的所有映射关系复制到调用此方法的映射中 |
boolean containsKey(Object key) | 判断是否包含指定的键key |
boolean containsValue(Object value) | 判断是否包含指定的值value |
V get(Object key) | 返回键key所映射的值,若key不存在则返回null |
Set <K> keySet() | 返回该映射中所有键对象形成Set集合 |
Collection<V> values() | 返回该映射中所有值对象形成Collection集合 |
V remove(Object key) | 将键为key的条目,从Map对象中删除 |
映射接口Map常用的实现类有哈希映射HashMap和树映射TreeMap,HashMap映射是基于哈希表的Map接口的实现类,所以HashMap通过哈希值对其内部的映射关系进行快速查找,因此对于添加和删除映射关系效率较高,并且允许使用null值和Null键,但是必须保证键的唯一性;而树映射TreeMap中的映射关系存在一定的顺序,如果希望Map映射中的元素也存在一定的顺序,应该使用TreeMap类实现的Map映射,由于TreeMap类实现的Map映射中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键对象是null。
①HashMap<K,V>映射常用的构造方法
构造方法 | 功能说明 |
---|---|
public HashMap() | 构造一个具有默认初始容量(16)和默认上座率0.75的HashMap对象 |
public HashMap(int initialCapacity) | 创建初始容量为initialCapacity和默认上座率0.75的空HashMap对象 |
public HashMap(Map<? extends K, ? extends V> m) | 创建一个映射关系与指定Map相同的新HashMap对象 |
②TreeMap<K,V>映射的构造方法
构造方法 | 功能说明 |
---|---|
public TreeMap() | 使用键的自然顺序创建一个新的空树映射 |
public TreeMap(Map<? extends K,extends V> m) | 创建一个与给定映射具有相同映射关系的新树映射 |
public K firstKey() | 返回映射中第一个键 |
public K lastKey() | 返回映射中的最后一个键 |
public SortedMap<K,V> headMap(K toKey) | 返回键值小于toKey的那部分映射 |
public SortedMap<K,V>tailMap(K fromKey) | 返回键值大于或等于fromKey的那部分映射 |
public K lowerKey(K key) | 返回严格小于给定键key的最大键,如不存在,返回null |
public K floorKey(K key) | 返回小于或者等于key的最大键 |
public K higherKey(K key) | 返回严格大于给定键key的最小键 |
public K ceilingKey(K key) | 返回大于或者等于Key的最小建 |
③测试
package Map;
import java.util.*;
public class TestMap {
public static void main(String[] args) {
Map<String,String> hm = new HashMap<String,String>();
hm.put("006","唐 僧");
hm.put("008","孙悟空");
hm.put("009","猪八戒");
hm.put("007","沙和尚");
hm.put("010","白龙马");
System.out.println("哈希映射中的内容如下:\n"+hm);
String str =(String) hm.remove("010");
Set<String> keys = hm.keySet();
Iterator<String> it = keys.iterator();
System.out.println("HashMap类实现的Map映射,无序:");
while (it.hasNext()){
String xh =(String) it.next();
String name =(String) hm.get(xh);
System.out.println(xh+" "+name);
}
TreeMap<String, String> tm = new TreeMap<>();
tm.putAll(hm);
Iterator<String> iter = tm.keySet().iterator();//获取迭代器
System.out.println("TreeMap类实现的Map映射,键值升序:");
while (iter.hasNext()){
String xh = iter.next();
String name = hm.get(xh);
System.out.println(xh + " "+name);
}
}
}