java
集合有一个缺点是当把一个对象放入集合里面之后,集合就会”忘记”这个对象的数据类型,当再次取出该对象的时候,该对象的编译类型就变成了Object
类型(该对象的运行时类型没变)。
java
集合之所以被设计成这样的原因是由于java
集合的设计者不知道我们要拿它来保存什么类型的对象,所以他们把集合设计成可以保存任意类型的对象,只要求具有很好的通用性,但是这样做会带来两个问题:
(1) 集合对元素类型没有任何限制,这样会引发一些问题。例如,想创建一个保存
Dog
对象的集合,但是程序也可以轻易的把Cat
对象保存进去,所以会引发异常。(2) 由于把对象保存到集合里面时,集合丢失了对象的状态信息,集合只知道它盛装的是
Object
,因此取出集合元素之后通常还需要进行强制类型转换。这种强制类型转换即增加了编程的复杂度,也可能引发类型转换ClassCastException
异常。
1.1 编译时不检查类型的异常
看看下面的案例ListErr.java
:
public class ListErr
{
public static void main(String[] args)
{
// 创建一个只想保存字符串的List集合
List strList = new ArrayList();
strList.add("org.light4j");
strList.add("com.light4j");
strList.add("com.longjiazuo");
// "不小心"把一个Integer对象"丢进"了集合
strList.add(5); // ①
for (int i = 0; i < strList.size() ; i++ )
{
// 因为List里取出的全部是Object,所以必须强制类型转换
// 最后一个元素将出现ClassCastException异常
String str = (String)strList.get(i); // ②
}
}
}
上面的案例程序创建了一个List
集合,而且只希望该List
集合保存字符串对象,但是我们没有办法进行任何限制,如果程序在①
处”不小心”把一个Integer
对象保存到List
集合中,这将导致程序在②
处引发ClassCastException
异常,因为程序试图把一个Integer
对象转换为String
类型。
1.2 手动实现编译时检查类型
如果希望创建一个List
对象,且该List
对象中只能保存字符串类型,那么我们可以拓展ArrayList
类,下面程序创建了一个StrList
集合类,该集合类里面只能保存String
对象。
//自定义一个StrList集合类,使用组合的方式来复用ArrayList类
class StrList {
private List strList = new ArrayList();
// 定义StrList的add方法
public boolean add(String ele) {
return strList.add(ele);
}
// 重写get方法,将get方法的返回值类型改为String类型
public String get(int index) {
return (String) strList.get(index);
}
public int size() {
return strList.size();
}
}
public class CheckType {
public static void main(String[] args) {
// 创建一个只想保存字符串的List集合
StrList strList = new StrList();
strList.add("org.light4j");
strList.add("com.light4j");
strList.add("com.longjiazuo");
// 下面语句不能把Integer对象“丢进”集合中,将引起编译错误
strList.add(5); // ①
System.out.println(strList);
for (int i = 0; i < strList.size(); i++) {
// 因为StrList里元素的类型就是String类型,
// 所以无须强制类型转换
String str = strList.get(i);
}
}
}
上面案例程序中定义的StrList
类就实现了编译时的异常检查,当程序在①
处试图将一个Integer
对象添加到StrList
集合中时,程序将无法通过编译。因为StrList
只接受String
对象作为参数,所以①
处代码在编译的时候会得到错误提示。
从代码健壮性的角度来看,该方法极其有用,而且使用get()
方法返回集合元素时,无需进行类型转换。这种做法虽然有效,但是局限性非常明显,程序员需要定义大量的List
子类,这是一件让人沮丧的事情。从java5
以后,java
引入了”参数化类型(parameterized type)
“的概念,允许我们在创建集合时指定集合元素的类型。Java
的参数化类型被称为泛型(Generic
)。
1.3 使用泛型
对于上面的ListErr.java
程序,可以使用泛型改进这个程序,代码如下所示:
public class GenericList
{
public static void main(String[] args)
{
// 创建一个只想保存字符串的List集合
List<String> strList = new ArrayList<String>(); // ①
strList.add("org.light4j");
strList.add("com.light4j");
strList.add("com.longjiazuo");
// 下面代码将引起编译错误
strList.add(5); // ②
for (int i = 0; i < strList.size() ; i++ )
{
// 下面代码无须强制类型转换
String str = strList.get(i); // ③
}
}
}
上面的程序成功创建了一个特殊的List
集合:strList
,这个List
集合只能保存字符串对象,不能保存其他类型的对象。创建这种特殊集合的方法是:在集合接口或者类后增加尖括号,尖括号里面放一个数据类型,即表明这个集合接口,集合类只能保存特定类型的对象。注意①
处的类型声明,它指定strList
不是一个任意的List
,而是一个String
类型的List
,写作:List<String>
。我们说List
是带一个类型参数的泛型接口,在本例中,类型参数是String
。在创建这个ArrayList
对象时也指定了一个类型参数。
上面程序将在②
处引发编译异常,因为strList
集合只能添加String
对象,所以不能将Integer
对象保存到该集合。而且程序在③
处不需要进行强制类型转换,因为strList
对象可以”记住”它的所有集合元素都是String
类型。
上面代码不仅更加健壮,程序再也不能”不小心”地把其他对象保存到strList
集合中:而且程序更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。这一切好处都是因为java5提供的泛型支持。
1.4 java7泛型的”菱形”语法
在Java7
以前,如果使用带有泛型的接口,类定义常量,那么调用构造器创建对象时构造器的后面也必须带泛型,这显得有些多余了。例如下面两条语句:
List<String> strList=new ArrayList<String>();
Map<String,Integer> scores=new HashMap<String,Integer>();
上面两条语句中后面尖括号里面的泛型声明完全是多余的,在java7
以前这是必须的,不能省略。从java7
开始,java
允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>
)即可,Java
可以推断括号里面应该是什么泛型信息。即上面两条语句可以改写成为如下的形式:
List<String> strList=new ArrayList<>();
Map<String,Integer> scores=new HashMap<>();
把两个尖括号放在一起非常像一个菱形,这种语法也被称为”菱形”语法,下面程序示范了java7
的菱形语法。
public class DiamondTest
{
public static void main(String[] args)
{
// Java自动推断出ArrayList的<>里应该是String
List<String> books = new ArrayList<>();
books.add("org.light4j");
books.add("com.light4j");
books.add("com.longjiazuo");
// 遍历时集合元素就是String
for (String book : books )
{
System.out.println(book);
}
//Java自动推断出HashMap的<>里应该是String , List<String>
Map<String , List<String>> schoolsInfo = new HashMap<>();
// Java自动推断出ArrayList的<>里应该是String
List<String> schools = new ArrayList<>();
schools.add("斜月三星洞");
schools.add("西天取经路");
schoolsInfo.put("孙悟空" , schools);
// 遍历Map时,Map的key是String类型
for (String key : schoolsInfo.keySet())
{
// value是List<String>类型
List<String> list = schoolsInfo.get(key);
System.out.println(key + "-->" + list);
}
}
}
从上面的程序不难看出,”菱形”语法对原有的泛型并没有改变,只是更好的简化了泛型编程。
附:源代码示例
公众号ID:longjiazuoA

未经允许不得转载:人生设计师 » 泛型系列(一):泛型入门