Java——泛型

  • 1 理解泛型

从Java5以后,Java引入了“参数类型化(parameterized type)”的概念,允许在创建集合时指定集合元素的类型。泛型很大程度上是为了解决集合中存放元素类型的控制,从而可以保证程序如果在编译时没有发出警告,运行时就不会发生ClassCastException异常,并且从Java7之后有了泛型的“菱形”语法,更好的简化了泛型的编程。

在一些资料中是这样定义泛型的概念:所谓泛型,就是允许在定义类、接口、方法时使用类型参数,这个类型参数将在声明变量、创建对象、调用方法时动态指定(即传入实际的类型参数,类型实参),从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。看下面一个简单示例:

//在定义类时声明类型形参,类型形参在整个勒种可当做类型使用
public class Person<T> {
    private T info;
    public Person(){}
    public Person(T info){
        this.info = info;
    }
    public T getInfo() {
        return info;
    }
    public void setInfo(T info) {
        this.info = info;
    }
    public static void main(String[] args){
        Person<String> strPerson = new Person<>("张三");
        Person<Double> doublePerson = new Person<>(30.0);
        System.out.println(strPerson.getClass()==doublePerson.getClass());
    }
}

在使用Person<T>时可以为T类型形参传入实际类型,这样就可以生成如Person<String>、Person<Double>形式的逻辑上的子类,但物理上并不存在,从下图来分析Java代码在计算机中经历的三个阶段,不管为泛型的类型形参传入哪一种类型实参,对于Java来说它仍然被当成同一个类处理,在内存中也只占用一块内存空间,因此strPerson.getClass()==doublePerson.getClass()逻辑判断结果为true!!!(从以上几句话也能够知道在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参

注:当创建带泛型声明的自定义类,为该类定义构造器时,构造器还是原来的类名,不要增加泛型声明。

  • 2.从泛型类派生子类

当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或者从该父类派生子类,需要指出的是,当使用这些接口、父类时不能再包含类型形参,必须制定类型实参。

public class Srudent extends Person<String> {}
//或者
public class Srudent extends Person {}
//错误写法
public class Srudent extends Person<T> {}

在指定了具体的类型实参后,若需要重写父类中使用了返回结果类型为类型参数的方法,也必须要改为具体实参类型:

public class Srudent extends Person<String> {
    @Override
    public String getInfo() {
        return super.getInfo();
    }
}
  • 3.通配符

考虑这样一个工具类中的一个方法:遍历List集合,打印集合中的元素对象。若直接在方法中写List作为方法参数类型,将引起泛型警告,可是在使用时,有可能每次调用该方法时传入的List中的元素类型不同,在不引起泛型警告的情况下,该如何解决该问题??相信很多初识Java的人和我一样,认为答案是使用List<Object>,其实不对,List<String>等之类不能当做List<Object>对象使用。

public class Srudent extends Person<String> {
    public static void main(String[] args){
        List<String> strList = new ArrayList<>();
        //提示List<String>不能应用于List<Object>
        opList(strList);
    }
    public static void opList(List<Object> list){}
}

注意此类用法和数组的区别,若一个方法参数为Number[],在调用时传入Integer[]是可以的:

// 定义一个Integer数组
Integer[] ia = new Integer[5];
// 可以把一个Integer[]数组赋给Number[]变量
Number[] na = ia;
// 下面代码编译正常,但运行时会引发ArrayStoreException异常
// 因为0.5并不是Integer
na[0] = 0.5;
List<Integer> iList = new ArrayList<>();
// 下面代码导致编译错误
List<Number> nList = iList;

能够看出,在早期的Java设计之初,对于数组的设计安全缺陷是显而易见的,因此在泛型中很好的避免了此类问题,如果A是B的一个子类型,C是具有泛型声明的类或接口,G<A>并不是G<B>的子类型!!

回到我们的需求上,为了能够表示所有List的父类,可使用通配符(?),将问号作为类型实参传递给List集合,意思是未知元素类型的List集合,它的元素类型可以匹配任何类型。另外,Java还提供了通配符限定,extends来限制通配符上限,super来限制通配符下限。在使用通配符的集合方法中,是不能向集合中执行添加对象的操作,因为程序无法确定集合中元素的类型

注:该需求的另一个解决方式就是使用泛型方法

  • 4.泛型方法,泛型方法与通配符的区别

在定义类、接口时可以使用类型形参,在该类的方法和成员变量定义、接口的方法定义中,这些类型形参可被当成普通类型来使用。在另外一些情况下,定义类、接口时没有使用类型形参,但定义方法时想自己定义类型形参,这种方法就是泛型方法,在调用泛型方法时要传入具体的类型实参。定义语法格式如下

修饰符 <T,S> 返回值类型 方法名(形参列表){
    //方法体
}

回到在介绍通配符部分提出的需求,在此处可以使用泛型方法来解决

public class Srudent extends Person<String> {
    public static void main(String[] args){
        List<String> strList = new ArrayList<>();
        opList(strList);
    }
    public static <T> void opList(List<T> list){}
}

那么,新的问题来了,何时使用通配符,何时使用泛型方法?一般可参考如下准则:

若类型形参只使用了一次,产生的唯一效果是可以在不同的调用点传入不同类型的实际参数,对于这种情况应该使用通配符;泛型方法允许类型型参被用来表示方法的一个或多个参数之间的依赖关系,或者方法返回值与型参之间的依赖关系,应该使用泛型方法。如果某个方法中一个型参(a)的类型或返回值的类型依赖于另一个型参(b)的类型,则型参(b)的类型不应该使用通配符——因为型参或返回值的类型依赖型参,如果型参(b)的类型不确定,程序就无法定义型参(a)的类型,在这种情况下,只能考虑使用在方法签名中声明类型型参,也就是泛型方法。

  • 5.泛型构造器

可以像声明泛型方法那样声明构造器——在构造器签名中声明类型形参。

class Foo {
    public <T> Foo(T t) {
        System.out.println(t);
    }
}
public class GenericConstructor {
    public static void main(String[] args) {
        // 泛型构造器中的T参数为String。
        new Foo("疯狂Java讲义");
        // 泛型构造器中的T参数为Integer。
        new Foo(200);
        // 显式指定泛型构造器中的T参数为String,
        // 传给Foo构造器的实参也是String对象,完全正确。
        new <String> Foo("疯狂Android讲义");
        // 显式指定泛型构造器中的T参数为String,
        // 但传给Foo构造器的实参是Double对象,下面代码出错
        new <String> Foo(12.3);
    }
}

 

一直都想把泛型好好的学习一下,希望能够系统的对它有个很好的认识,在观看一些视频和书籍之后,记录笔记。

一点一滴的积累的量变,夯实质变的根基

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值