第12章 泛型与容器类
泛型技术可以通过一种类型或方法操纵各种不同类型的对象,同时又提供了翻译时的类型安全保证。容器(即集合)则是以类库的形式提供的多种多种数据结构,用户在编程时可直接使用。泛型通常可以与容器一起使用。
12.1 泛型
泛型其实质就是将数据的类型参数化,通过为类、接口及方法设置类型参数来定义泛型。泛型使一个类或一个方法可在多种不同类型的对象上进行操作,运用泛型意味着编写的代码可以被很多类型不同的对象所引用,从而减少数据类型转换造成的潜在错误。引入泛型的概念,为了兼容已有 代码,编译器并不认为i不使用泛型的代码存在语法错误,只不过在编译时会给出一些警告信息,提醒用户使用了raw类型,但代码还是可以运行的。
12.1.1 泛型的概念
泛型实际上是定义类、接口或方法时通过为其增加“类型参数”来实现在编译时检测出错误而不是在运行时。泛型所操作的数据类型给被指定了一个参数,这个参数称为类型参数,所以说,泛型的是指是将数据的类型参数化。当这种参数用在类、接口以及方法的声明中时,则分别称为泛型类、泛型接口和泛型方法。其定义的格式是一般类、一般接口和一般方法定义的基础上加一个或多个用尖括号括起来的类型参数实现的。类型参数实际上就是一种:类型形式参数“。按照通常的惯例,用T或E这样的单个大写字母来表示类型参数。泛型类的定义是子啊类名后加上一<T>,泛型接口的定义是子啊接口名后面加上<T>,而泛型方法的定义是在方法的返回值类型前面加上<T>,其头部定义分别如下:
泛型类的定义:
[修饰符] class 类名<T>
泛型接口的定义:
[public] interface 接口名<T>
泛型方法的定义:
[public] [static] <T> 返回值类型 方法名(T参数)
定义泛型之后,就可以在代码中使用类型参数T来表示某一种数据的类型而并非数据的值,即T可以看作是泛型类的一种”类型形式参数“。子啊定义类型参数后,就可以在类体或接口中定义的各个部分直接使用这些类型参数。二在应用这些具有泛型特性的类或接口时,需要指明实际的具体类型,即用”类型实际参数“来替换”类型形式参数“,也就是说,用泛型类创建对象就是在类体内的每个类型参数T处分别用这个具体的实际类型替代。泛型的实际参数必须是类类型,利用泛型类的创建称为泛型对象,这个过程称为泛型的实例化。所以说,泛型的概念实际上是给予”类型也可以像变量一样实现参数化“这一简单的设计理念设计的,因此泛型也称为参数多态。
12.1.2 泛型类及应用
在使用泛型类创建对象时,即在泛型实例化时,可以根据不同的需求给出类型参数T的具体类型。而在调用泛型类的方法传递的或返回数据类型时可以不用进行类型转换,而是直接使用T作为类型来代替参数类型或返回值类型。
//filename:App12_1.java
public class App12_1<T>
{
private T obj;
public T getObj()
{
return obj;
}
public void setObj(T obj)
{
this.obj=obj;
}
public static void main(String[] args)
{
App12_1<String> name=new App12_1<String>();
App12_1<Integer> age=new App12_1<Integer>();
name.setObj("zhang san");
age.setObj(35);
String newName = name.getObj();
int newAge= age.getObj();
System.out.println("name :"+newName);
System.out.println("age :"+newAge);
}
}
.12.1.3 泛型方法
一个方法是否是泛型方法与其所在的类是否是泛型类没有关系。要定义泛型方法,是需将泛型的类型参数置于返回值类型前面即可。在Java中任何方法包括静态方法都可恒明为泛型方法。泛型方法除了定义不同,调用时与普通方法一样。
//filename: App12_2.java
public class App12_2
{
public static void main(String[] args)
{
Integer[] num= {1,2,3,4,5};
String[] str= {"red","orange","yello","green","purple","vyan"};
App12_2.display(num);
App12_2.display(str);
}
public static <E> void display(E[] list)
{
for(int i=0;i<list.length;i++)
System.out.print(list[i]+" ");
System.out.println();
}
}
一般来说,编写Java型方法时,返回值类型和至少一个参数类型应该是泛型,而且类型应该是已一致的,如果只有返回值类型或参数类型之一使用了泛型,这个泛型方法就大大地受到了限制,基本限制到不用泛型一样的程度。
注:一个static方法,无法访问泛型类的类型参数,所以如果static方法需要使用泛型能力,必须使其成为泛型方法。
当使用泛型类时,必须在创建泛型对象的时候指定类型参数的实际值,而调用泛型方法时,通常不必指明参数类型,因为编译器有个功能叫做:类型参数推断,此时编译器会额外i我们找出具体的类型。类型推断只对赋值操作有效,其他时候病不起作用。
12.1.4 限制泛型的可用类型
子啊定义泛型类时,默认可以使用任何类型来实例化一个泛型类对象,但在Java语言中,可以在用泛型类创建对象时对数据类型做出限制。其语法如下:
class ClassName<T extends anyClass>
其中anyClass是指某个类或接口。该语句表示”T是ClassName类的类型参数,且T有一个限制,即T必须是anyClass类或是继承了anyClass类的子类或是实现了anyClass接口的类。
注:对于实现了某接口的有限制泛型,也是用extends关键字,而不是用implements关键字。
12.1.5 泛型的类型通配符和泛型数组的应用
在泛型机制中除了有限制的泛型类之外,还引入了通配符“?”的概念,其主要作用有两个方面,一是用于创建课重新赋值但不可修改其内容 的泛型对象,二是用在方法的参数中,限制传入不想要的类型实参。
创建泛型类语句的语法:
泛型类名<? extends T> o=null;
其中“? extends T”表示是T或T的未知子类型或是实现接口T的类。
12.1.6 继承泛型类与实现泛型接口
被定义为泛型的类或接口可被继承与实现。
12.2 容器类
容器类是Java以类库的形式供用户开发程序时可直接使用的各种数据结构。所谓数据结构,就是以某种方式将数据组织在一起,并存储在计算机中。数据结构不仅可以存储数据,还支持访问和处理数据的操作。在面向对象的思想中,一种数据结构被认为是一个容器,数组是一种简单的 数据结构,除数组之外Java还以类库的形式提供了许多其他数据结构。这些数据结构称为容器类或集合类。
12.2.1 Java容器框架
Java程序框架中有两个名称为Collection和Set的接口,为防止名称的冲突,将Collection视作容器,将Set视作集合。
Java容器框架提供了一些现成的数据结构可供使用,这些数据结构可以存储对象的集合,在这里对象也称为元素。.
12.2.2 Collection接口
Collection接口通常不能直接使用,,但该接口哦提供了添加元素、删除元素、管理数据和方法。12.2.3 列表接口LIst
列表接口LIst是Collection子接口,它是一种包含有序元素的线性表,其中的元素必须按顺序存放,且可重复,可以存放空值null。元素之家的顺序关系可以由添加到泪飙的先后来决定,也可由元素的大小来决定。
实现列表类主要有两个:链表类LinkedList和数组列表类ArraryList。他们都是线性表。
LinkedList链表类采用链表结构保存对象,使用循环双链表实现List。这种结构向聊表中任意位置添加、删除元素时不需要移动其他元素,链表的大小可以动态增大或减小,但不具有随机存放特性。
ArraryList数组列表类使用一维数组实现List,,该类实现的是可变数组,允许所有元素,包括null。具有随机存取特性,插入、删除时需要移动其他元素,当元素很多时,插入、删除操作的速度较慢。
选用这两种线性表的原则是:若要通过下表随机访问元素,但除了在末尾处之外,,不在其他位置插入或删除元素,则应该选择ArraryList类;但若需要子啊线性表中任意位置上进行插入或删除操作,则应该选择LinkedList类。
语法:
List list1=new LinkedList();
List list2=new ArraryList();
//filename: App12_7.java
import java.util.*;
class StringStack
{
private LinkedList<String> ld=new LinkedList<String>();
public void push(String name)
{
ld.addFirst(name);
}
public String pop()
{
return ld.removeFirst();
}
public boolean isEmpty()
{
return ld.isEmpty();
}
}
public class App12_7
{
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
StringStack stack=new StringStack();
System.out.println("Please input data(end with quit)");
while(true)
{
String input=sc.next();
if(input.equals("quit"))
break;
stack.push(input);
}
System.out.println("Last in and First out: ");
while(!stack.isEmpty())
System.out.print(stack.pop()+" ");
}
}
对于容器中元素进行访问时,经常需要按照某种次序将容器中的元素访问且仅访问一次,这就是遍历也称为迭代,便利是指从容器中获得当前元素的后续元素。对容器的遍历可有多种方式。
第一种方式就是利用foreach循环结构,绝大多数容器都支持该循环结构的遍历;
第二种方式是利用Collection接口中定义的toArrary()方法将容器对象转换为数组,然后再利用循环语句对数组中的每个元素进行访问;
第三种方法是利用size()和get()方法进行遍历;
第四种方法是利用Java提供的迭代功能。
迭代功能是由可迭代接口Iterable和迭代器接口Iterator、ListIterator实现的,迭代器是一种允许对容器内部元素进行遍历并有选择的删除元素的对象。使用迭代器遍历容器的代码如下;
Iterator it=c.iterator();
while(it.hasNext())_
{
Object o=it.next();
...
}
12.2.4 集合接口Set
Set是一个不含重复元素的集合接口,它继承自Collection接口,并没有声明其他方法,它的方法都是从Collection接口继承来的。Set集合中的对象不按特定的方式排序,至少简单地吧对象加入集合中即可,但加入的对象一定不能重复。
1.哈希集合HashSet
哈希集合对所包含元素的访问并不像是线性表一样使用下标,而是根据哈希码来存取集合中的元素。
//filename:App12_9.java
import java.util.*;
public class App12_9
{
public static void main(String[] args)
{
HashSet<String> hs=new HashSet<String>();
for(String a:args)
if(!hs.add(a))
System.out.println("element "+a+" is repeated ");
System.out.println("the size of this set is "+hs.size()+" , each element is : ");
Iterator it=hs.iterator();
while(it.hasNext())
System.out.print(it.next()+" ");
}
}
2.树集合TreeSet
树集合TreeSet类不仅实现了Set接口,还实现了java.util.SortedSet接口。
12.2.5 映射接口Map
Map是另一种存储数据结构的对象,Map接口与List接口和Set接口有明显的区别。
//filename:App12_11.java
import java.util.*;
public class App12_11
{
public static void main(String[] args)
{
Map<String,String> hm=new HashMap<String,String>();
hm.put("006", "tang_seng");
hm.put("008", "sun_wu_kong");
hm.put("009", "zhu_ba_jie");
hm.put("007", "sha_he_shang");
hm.put("010", "bai_long_ma");
System.out.println("The content in hashmap is as follows:\n"+hm);
String str=(String)hm.remove("010");
Set keys=hm.keySet();
Iterator it=keys.iterator();
System.out.println("disorder map in HashMap class:");
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<String,String>();
tm.putAll(hm);
Iterator iter=hm.keySet().iterator();
System.out.println("ordered map in TreeMap class");
while(iter.hasNext())
{
String xh=(String)iter.next();
String name=(String)hm.get(xh);
System.out.println(xh+" "+name);
}
}
}