Java基础——泛型

一、为什么需要泛型?

  1.集合元素存储时候的安全性

  2.集合元素取出时候的强转问题

 主要内容:

泛型在集合中的使用

自定义泛型类、泛型接口、泛型方法

泛型与继承的关系

通配符

二、泛型(Generic)的几个术语:

  对于List<User> 和List<T>

    整个List<T>   泛型类型

    List<T>中的T   类型参数

    整个List<User>    参数化类型

    List<User>中的User   实际类型参数

    <> 为   typeof

  注意的地方:

    参数化类型没有实际类型参数的继承关系

    List<Integer> list = new List<Object>(); //报错,反之亦然

    其中,JDK7之后的新特性:后一个泛型可以省略,JDK可以自动推断!

 Map<String, String> map = new HashMap<>();

    参数化类型与原始类型的兼容性

  • 参数化类型可以引用一个原始类型的对象,编译时编译器会报警告,例如:Collection<String> c = new Vector();
  • 原始类型可以引用一个参数化类型的对象,编译时编译器会报警告,例如:Collection c = new Vector<String>();

三、自定义泛型类、接口、方法

  1.自定义泛型类

  集合中的泛型就不再赘述了,我们可以尝试着做自定义泛型类,最简单的入门办法,就是先看看集合类它是怎么做的:

public interface List<E> extends Collection<E> {

  基本上就是在类名后进行泛型的声明,之后涉及的类型参数地方都用类型参数(当然类型参数T是自定义的字母,T E自定义即可)代替,那么实际使用中类型参数就会是我们传入的泛型;

  定义泛型类:

package com.zw.test.generic;

/**
 * Person的泛型类
 * 作者: Administrator
 * 日期: 2017/9/22
 **/
public class Person<T> {
    private String name;
    private Integer age;
    private T t;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", t=" + t +
                '}';
    }
}

 

如何使用:

    当然泛型不是强制需要传入的,如果不传入,那么就是Object类型(不推荐)

//        Person p = new Person();
//        Object t = p.getT();

    正常应该是要指定的:——这样泛型类中的类型参数就会被实际类型参数代替

      Person<Boolean> p = new Person<>();
        p.setT(true);
        Boolean b = p.getT();

    当然,可以通过子类继承的时候指定泛型,这样子类继承自父类的方法属性都会自动确定相关的泛型:

class subClass extends Person<Integer> {

     自定义接口基本类同,就不赘述了。

  2.自定义泛型方法

  我们还是来看看集合类中怎么写的:

public class ArrayList<E> extends AbstractList<E>

  注意,以下这个不是泛型方法,不过返回值是类在声明时候的泛型,仅此而已:

 public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

  

这点可以参见泛型总结的一篇随笔http://blog.csdn.net/s10461/article/details/53941091

  但下面这个ArrayList中的方法是真正的泛型方法:

@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

//为了和类声明时候的泛型区分,这里使用的是T(与类的E区分,当然,即使这里使用的是E也没关系,它依然是一个全新的类型,不会受限于类的泛型声明E),这也是泛型方法的标准格式:

  修饰符 [static] <T> 返回值 方法名(参数列表)

  其他的泛型方法相关的详细细节(例如T可以在任意位置,而且T也可以是多个 <T,E>等)可以参考上面的链接

四、泛型的通配符
  泛型引用和创建两端泛型变量必须相同
  重载时只有泛型变量无法完成,因为泛型擦除后参数是相同的
    此问题主要是由于兼容老版本引起的,使用的是假的泛型
    于是,通配符就出现了 List<? extends Object> list,这里的问号?就是通配符

  通配符就是解决泛型的继承方面的问题

  通配符只能出现在引用一端,而new那端不能用,只能用在左边,而不能用在右边
  ?表示一个不确定的值,只会在调用时进行确定,当然可以简写成List<?>
  通配符的局限就出来了:
    当使用通配符时,对泛型类中参数为泛型的方法起了副作用:无法使用此方法(如无法使用add()方法等)
    返回值为泛型类型时也无法使用
  好处就是形参带泛型时可以更加通用

  ?通配符还有边界的概念(上下边界,父类子类边界)
    如:List<? extends Number> list 只能传递Number及其子类型
      List<? super Number> list2 只能传递Number及其父类型
    也就是分为三种:无界通配、子类通配、父类通配

  上界(父类)通配:add()受限——但是可以get(),数据可以通过赋值得到,这也就是我们说的通配符实现泛型继承的意义

  下界(子类)通配:get()受限

  更多泛型通配符的概念,请参见:http://blog.csdn.net/claram/article/details/51943742

 五、泛型的继承关系

  (注意,list<String>不是list<Object>的子类,也就说类型参数的 父子关系不能引申到泛型类型的父子关系,关于这点可以参见之前泛型出现的意义(类型安全)结合例子进行推断,泛型的继承关系需要通过通配符

  定义一个父类:

package cn.test;

public class Father<T> {

     T text;//成员变量

    //get方法
    public T getText() {
        return text;
    }

    //无参构造器
    public Father() {
        
    }
    //有参构造器
    public Father(T text) {
        super();
        this.text = text;
    }
    
    
}

1.最正常的继承,子类的泛型参数和父类的参数是一致的 

public class Child<T> extends Father<T>{

  2.子类增加了一个泛型参数,父类的泛型参数不能遗漏,所以仍然要定义

public class Child<T,E> extends Father<T>{

  3.继承时不指定父类的泛型参数,会有警告信息:

    Father is a raw type.  References to generic type Father<T> should be  parameterized  

public class Child extends Father{

  4.继承时指定父类的泛型参数,子类就不用再写泛型参数,如果写了,那就是子类自己新增加的 

public class Child extends Father<String>{

    public Child(){
        super("我是子类");
    }
    
    public void print(){
        System.out.println(text);
    }
    
}

调用如上print方法正常输出 我是子类

  5. 父类指定了类型,子类又增加了,这时子类的只是新增加的泛型参数,跟父类没有关系 

public class Child<E> extends Father<String>{

 

posted on 2018-11-23 21:58  LeviZhuang  阅读(128)  评论(0编辑  收藏  举报

导航