Java里的泛型Generics

  1. 我们知道集合(Collection、Map之类的容器)是用来存储任意对象(Object)的一系列“容器类或者接口”,注意这里的“任意对象”,就是指我们可以在这些类或接口中存放任意类型的对象,但这些对象在存储之前都需要统一向上转型为Object类型(因为Object类是所以类的的父类),比如类ArrayList就属于“集合类”,我们可以用这个类来存储任意对象:
package bean;

import java.util.ArrayList;

public class ArrayListDemo {
    public static void main(String[] args) {
        
        //首先我们创建一个ArrayList对象
        ArrayList al = new ArrayList();
        
        //往"容器"添加元素:我们添加了字符串"哈哈",数字2,boolean类型true,最后我们甚至添加了自己。
        al.add("哈哈");
        al.add(2);
        al.add(true);
        al.addAll(al);
        //由于ArrayList类覆盖了toString(),这里我们可以直接打印查看效果:
        System.out.println(al);
    }
}

最后打印出来的字符串是:[哈哈, 2, true, 哈哈, 2, true]

值得注意的是:这里的2不是int而是Integer,因为ArrayList只能存对象,而“添加它自己”addAll(al)的实质是将al中的所有元素再存入al中,正如打印效果显示的一样,我们成功添加了类型不一样的对象:有String,有Integer、还有Boolean。当然,你可以添加任意你想要添加的对象。因为这些对象在存入“容器”al中时会全部转型为Object类型,有没有觉得这样的“容器”用起来会很爽,什么东西都能往里放。但是,便捷往往会付出相应代价。

我们将各种类型的元素存入“容器”中当然不是让它睡上一觉,我们需要将存储的元素取出使用。对于这些“容器”,我们一般使用迭代器Iterator来获取每一个元素。(这里不进行迭代器使用说明,只需理解迭代器是取出"容器"中元素的工具就行).

假设现在我们需要对我们上面创建的“容器”al中的元素进行操作:打印容器中字符串的长度,即“哈哈”的长度。

package bean;

import java.util.ArrayList;
import java.util.Iterator;

public class ArrayListDemo {
    public static void main(String[] args) {
        //首先我们创建一个ArrayList对象,并添加对象
        ArrayList al = new ArrayList();
        al.add("哈哈");
        al.add(2);
        al.add(true);
        
        //使用迭代器Iterator进行遍历,并打印容器中字符串的长度:
        //注:next()方法是按照容器的存储顺序,逐一取出第一个到最后一个对象。
        Iterator i = al.iterator();
        while(i.hasNext()){
            String s = (String) i.next();
            System.out.println(s.length());
        }
    }
    
}

这是运行结果:
在这里插入图片描述
我们来进行代码分析:while循环中,先用i.next()方法取出第一个元素"哈哈",然后装换为String类型(因为存进去的时候是Object类型,使用的使用要向下转型),所以接下来打印了“哈哈”的长度=2。接着循环:迭代器取出第二个元素:2,但这是Integer类型不能强制装换为String类型,所以抛出异常:ClassCastException:类型转换异常。

这就是“能存万物”所带来的代价:我们在使用“容器”存储对象并调用时就换存在“类型转换异常”的安全隐患,但这需要在运行后才能得知,编译时无法发现。所以我们该如何解决这个问题呢?我们发现问题在于“类型转换异常”,所以我们只要保证“容器”中存储的是同一类型的元素就不会出现这个异常了。类似数组的定义一样:如 int[] 就表示专门用来存储int类型数据的数组,String[] 就是用来存储String类型数据的数组。我们只要将“容器”标上相应类型的标签就可以了。看到这里,你应该已经理解了泛型的由来,这就是泛型的设计:引入类型参数的概念,即对对象类型进行申明。

2、泛型的定义格式
格式为:类名<类型名>

3、泛型的好处
除了解决上面的安全隐患即抛出异常问题,泛型的类或接口在取出对象时将不需要再进行向下类型转换,因为存储的时候就是该类型 。

另外,泛型的使用让安全问题在编译时就报错而不是运行后抛出异常,这样便于程序员及时准确地发现问题。

4、什么时候使用泛型?
一般类或接口在api文档中的定义中带有标识的,在使用的时候都需要使用泛型机制。如ArrayList、Iterator。

5、泛型的擦除
泛型是运用在编译时期的技术:编译时编译器会按照<类型名>的类型对容器中的元素进行检查,检查不匹配,就编译失败。如果全部检查成功,则编译通过,但,编译通过后产生的.class文件中并没有<类型名>这个标识,即类文件中没有泛型,这就是泛型的擦除。

一句话总结就是:在.java文件运用泛型技术时,编译器在文件编译通过后自动擦除泛型标识。

由于泛型的擦除,类文件中没有泛型机制,同时也没有使用向下类型转换,那么为何运行时无异常?

6、泛型的补偿

编译器在擦除泛型后,会自动将类型转换为原定义的"泛型",这样就不必再做向下类型转换了。

泛型的擦除和补偿 这两个机制都是编译器内部自动完成的,了解其原理即可。

7、泛型的应用
7.1【泛型类】
所谓的泛型类就是运用到泛型技术的类,如上面讲到的ArrayList,Iterator等都是java中的泛型类,这些类都是Java已经定义好的泛型类,直接使用就可以了。但有时候我们会遇到这样的问题:

假设我们现在有两个自定义类:Worker类和Student类,现在我们需要一个工具类Tool来获取Worker对象和Student对象,并能对对象进行操作。

分析:

我们可能会想到将Worker和Student类作为Tool类的成员变量,以此来实现对这两个类的操作。但这样有一个问题,就是如果这不是两个类而是很多类,甚至说无数个类,即Tool类可以操作任意类,这种通过添加成员变量来实现调用对象的方法显然不可行。注意这个“添加任意对象”,这不就是上面说的ArrrayList这些类所具有的特点吗?泛型类就是为了解决“添加任意对象”而产生的,这里提到的ArrayList属于已定义泛型类(Java中自带的),这里我们要用到Tool类来“存储任意对象”,所以要将Tool类定义为泛型类,这就是根据需求自己设计的自定义泛型类:

首先我们先简单的定义一下:Worker类和Student类:

Worker类:

package bean;

import java.util.ArrayList;
import java.util.Iterator;

public class ArrayListDemo {
    public static void main(String[] args) {
        //首先我们创建一个ArrayList对象,并申明泛型类型为String类型:只能存储String类型的对象
        ArrayList<String> al = new ArrayList<String>();
        al.add("哈哈");
        al.add("asdafa");
        al.add("tobeyourself");
        
        //使用迭代器Iterator进行遍历,并打印容器中字符串的长度:这里迭代器也使用了泛型
        Iterator<String> i = al.iterator();
        while(i.hasNext()){
            String s = (String) i.next();
            System.out.println(s+"的长度为="+s.length());
        }
    }
    
}

Student类:

package bean;

public class Student {
    private String name;
    private String sex;
    Student(){
        
    }
    Student(String name,String sex){
        this.name = name;
        this.sex = sex;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    
}

现在我们需要将Tool类定义为泛型类:(注意格式):

package bean;

public class Tool<E> {
    private E e;
    
    public Tool(E e1){
        this.e = e1;
    }

    public E getE() {
        return e;
    }
    public void setE(E e) {
        this.e = e;
    }
    
}

这里我们写的很简单,但已经满足需求了,重点在于泛型类的写法:Tool,这里的E字母可以写你自己喜欢的代码,这也就是类型参数的应用,E相当于一个类型参数,代表了Tool可以传入任意对象,下面我们具体使用来看看效果:

package bean;

class Tooltest{
    public static void main(String[] args) {
        //假设有两个对象:一个学生,一个工人
        Student t1 = new Student("张三","男");
        Worker w1 = new Worker("李四",20);
        
        //现在我们使用Tool类来调用t1和w1
        Tool<Student> ts = new Tool<Student>(t1);
        Tool<Worker> tw = new Tool<Worker>(w1);
        
        //打印查看效果
        System.out.println("使用ts调用t1中的数据:"+ts.getE().getName()+":"+ts.getE().getSex());
        System.out.println("使用tw调用w1中的数据:"+tw.getE().getName()+":"+tw.getE().getAge());
    }
}

8、泛型の通配符:?
当我们不确定传入的对象类型时我们就可以使用?来代替。“?”即泛型通配符。

9、泛型的限定
我们知道使用泛型类时:如果明确参数类型,那么泛型就代表一种类型;如果使用通配符?,那么泛型就代表任意类型。但有时候我们希望指定某些类型(不是一个,也不要所有)能作为参数类型,这应该怎么办呢?

    Java中利用泛型的限定解决了这个问题,即泛型的限定。我们只需要按这样的格式书写:

   上限:<? extends E>表示参数类型是E及其所有子类。

   下限:<? super E>表示参数类型是E及其所有超类(即父类)。

10、总结
泛型即“类型参数化”,用的多了就懂了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值