但行好事  莫问前程

泛型系列(一):泛型入门

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);
        }
    }
}

从上面的程序不难看出,”菱形”语法对原有的泛型并没有改变,只是更好的简化了泛型编程。

附:源代码示例

github地址:点击查看
码云地址:点击查看

打赏
欢迎关注人生设计师的微信公众账号
公众号ID:longjiazuoA

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

分享到:更多 ()

人生设计师-接受不同的声音

联系我关于我