五、Java中的泛型

目录

1.泛型的定义

2.泛型的使用

3.泛型通配符的使用

4.泛型的作用


1.泛型的定义

在引出泛型之前,我们首先来看这样一个案例:

我们知道,Object 类是所有类的基类,因此我们如果创建一个 Object 类型的数组,那么我们就可以往里面添加任意类型的元素。我们以包装类为例,分别添加不同的包装类,在遍历数组时都按 String 类型对其元素进行使用,那么运行时就会报出异常。

    public static void main(String[] args) {
        
        //创建一个Object类数组
        Object[] objects = new Object[10];

        //为Object数组给定不同包装类型元素
        objects[0] = (Integer)12;
        objects[1] = "aaa";
        objects[2] = (Double)12.5;
        
        //遍历输出这个数组
        for (int i = 0; i < objects.length; i++) {
            String a = (String)objects[i];
            System.out.println(a);
        }
    }

对于类似情形——把类型明确的工作推迟到创建对象或调用方法的时候,我们就可以引入泛型来解决这个问题。

泛型是在 JDK 1.5 中引入的一个新特新,就是所谓的 “ 参数化类型 ” 。将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参), 然后在使用/调用时传入具体的类型(类型实参)。也就是说在泛型使用过程中, 操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

2.泛型的使用

需要注意的是:泛型只能为引用数据类型,对于 8 大基本数据类型要使用对应的包装类

(1)泛型类:泛型类也就是把泛型定义在类上,这样用户在使用类的时候才把类型给确定下来。类中的泛型会根据已确定的类型自动转化,因此无需在编写程序时对类型做强制转换。

①定义一个泛型类

//泛型类的定义:在类名后加<T>,表示未指定类中成员类型
public class Generic<T> {

    private T attribute;//未指定类型的成员属性

    private Integer age = 12;//普通的成员属性

    public void getType(T param) {//未指定参数类型的成员方法

        String typeName = param.getClass().getName();//获取参数类型名
        System.out.println("方法参数类型为:" + typeName);

        attribute = param;//属性默认为null时无法判断类型
        String typeAttribute = attribute.getClass().getName();//获取attribute类型名
        System.out.println("成员属性attribute类型为:" + typeAttribute);

        String typeAge = age.getClass().getName();//获取age类型名
        System.out.println("成员属性age类型为:" + typeAge);
    }
}

②使用泛型类

    public static void main(String[] args) {

        //实例化一个泛型类:必须在类名后声明具体类型
        //此时所有 T 类型均变为 String 类型
        Generic<String> generic = new Generic<String>();

        //调用泛型类的getType方法,此时所有T
        generic.getType("111");//此时的参数类型必须是 String,若为其他报错
    }

③运行结果

方法参数类型为:java.lang.String
成员属性attribute类型为:java.lang.String
成员属性age类型为:java.lang.Integer

(2)泛型方法:有时我们不关心类而是只关心方法,此时就可以用把此方法单独写成一个泛型方法。泛型方法在调用该方法时明确具体的类型。

public class Start {

    public static void main(String[] args) {

        //调用泛型方法
        show("a");//字符串
        show(12);//Integer
        show(new Integer[]{1,2,3,5});//Integer数组
    }

    //泛型方法:在方法修饰符后面有一个 <T>
    public static <T> void show(T t){
        String str = t.getClass().getName();
        System.out.println("参数值为:"+t);
        System.out.println("参数类型为:"+str);
    }
}

结果为:

参数值为:a
参数类型为:java.lang.String
参数值为:12
参数类型为:java.lang.Integer
参数值为:[Ljava.lang.Integer;@1b6d3586
参数类型为:[Ljava.lang.Integer;

注意事项:

①泛型方法必须带有<T>(<>中的内容可任意,但首字母必须大写),且<T>必须写在方法的修饰符之后。

②泛型方法和泛型类中带有泛型参数的方法是不一样的。泛型方法在调用方法时确定参数具体的类型,而泛型类中的方法在创建类时就已经确定了参数具体的类型。

③在泛型类中可以定义泛型方法,但该方法的泛型与类的泛型是相互独立的,在方法调用时单独确定。

public class Generic<T> {

    //未指定参数类型的成员方法
    public void getType1(T param) {
        System.out.println("执行带泛型的成员方法。。。。");
        String typeName = param.getClass().getName();//获取参数类型名
        System.out.println("方法参数类型为:" + typeName);
    }

    //泛型方法
    public <T> void getType2(T param){
        System.out.println("执行泛型方法");
        String typeName = param.getClass().getName();//获取参数类型名
        System.out.println("方法参数类型为:" + typeName);
    }
}


    public static void main(String[] args) {
        
        //规定泛型类型为Integer
        Generic<Integer> generic = new Generic<>();
        //未指定参数类型的成员方法
        generic.getType1(123);//参数只能为Integer
        //泛型方法
        generic.getType2("1234");//参数可任意引用类型
    }

④泛型方法能使方法独立于类而产生变化,因此尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化, 那么就应该使用泛型方法。

⑤泛型类在创建对象时确定具体类型。因此对于类中带泛型的成员属性和成员方法,不能使用 static 关键字。要想在泛型类中使用方法的静态能力,就必须使用泛型方法。

(3)泛型接口:泛型接口的定义方式与泛型类基本相同,在实现类实现泛型接口时分为明确类型实现和不明确类型实现。

//定义一个泛型接口
public interface Interface<T> {
    public void show(T param);
}

//明确类型:此时这个实现类就是一个普通的实现类(泛型已经明确类型)
public class InterfaceImpl1 implements Interface<String>{
    @Override
    public void show(String param) {
        //方法体
    }
}

//不明确类型:类名后要加上<T>
public class InterfaceImpl2<T> implements Interface<T>{
    @Override
    public void show(T param) {
        //方法体
    }
}

3.泛型通配符的使用

    public static void main(String[] args) {
        Collection<String> list1 = new ArrayList<String>();
        list1.add("111");
        list1.add("222");
        list1.add("333");
        Collection<Integer> list2 = new ArrayList<Integer>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
    }

当我们遇到上面所示的情形:有两个不同类型的集合,但是要用一个方法来实现对集合元素的遍历。

此时无法在定义这个方法时确定传入集合泛型的具体类型,那么把传入的集合泛型定义为 Object 类可以吗?显然是不可以的。

尽管 Object 类是所有类的父类,但 Collection < Object > 并不是 Collection < String > 或者 Collection < Integer > 的父类,那么该如何让解决这个问题呢?

我们可以引入泛型通配符 < ? > 来解决这一类型的问题。其实 ?在泛型中起到了和 Object 类相同的作用,可以把它看成是泛型中所有具体类型的父类,因此它作为具体类型是一个实参而不是形参

    //定义一个方法遍历任意类型的集合
    //从逻辑上讲就是把所有泛型的集合都当做它们父类 ? 进行遍历类
    public static void show(Collection<?> collection) {
        Iterator<?> iterator = collection.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

泛型通配符的强大不止于此,泛型通配符还有其独特的过滤功能——泛型通配符的上下限:

上限:< ? extends A >     此时只能接受 A 或者 A 的子类

下限:< ?super B >        此时只能接受 B 或者 B 的父类

我们拿上下限来举个例子:Number 是 Integer 的父类,且是 Object 的子类。

 此时可以看出,对于上限为 Number 的集合,Object 就不能作为参数传入;对于下限为 Number 的集合,Integer 就不能作为参数传入。通过通配符的上下限,我们在编写程序时就自动过滤了不想要的参数类型。

4.泛型的作用

(1)类型安全。引入泛型的主要目的就是提高 Java 程序的类型安全。泛型的实现方式,支持泛型几乎不需要 JVM 或类文件更改,所有工作都在编译器中完成,编译器生成的类没有泛型(和强制类型转换),只是来确保数据类型安全;

(2)消除强制类型转换。泛型一个附带好处是,消除代码中许多强制类型的转换。减少代码出错率,更好阅读;

(3)潜在的性能收益,可以带来更好的优化可能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值