泛型类、泛型接口、泛型方法及泛型的应用

面向对象的一个重要目标就是对代码的重用,支持这个目标的重要机制就是泛型;

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,
* 分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
* 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,
* 而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,
* 在运行的时候才出现异常,这是一个安全隐患。
* 泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率

1:使用接口类型表示泛型

  例如 考虑由一些项组成的数组中找出最大项的问题,基本的代码是与类型无关的,但是它必须有一种能力来比较任意两个对象的大小,并且确定哪一个是大的,哪一个是小的,因此我们不能直接找出Object数组中的最大元,我们需要更多的信息,最简单的就是比较Comparable的数组中的最大元,我们可以使用CompareTo来确定顺序,它对于所有实现Comparable接口的类都是可用的,但是这样并不是总会行的通的,因为有些类库中的类 去实现接口是不可能的,例如一个类可能是库中的类,但是接口却是用户自定义的接口,并且一个类如果是一个final类,那么我们是不可能扩展它来创建一个新的类,所以这种方式的局限性太大,

2:数组类型的兼容性

   语言设计中的困难之一就是如何处理集合类型的继承关系,假设Employee iS-A Person ,那么这是不是也就意味着 Employee [] IS-A Person[] 呢?换句话说,如果一个例程可以允许 Person[] 作为参数传递,那么 Employee[] 可不可以当做参数传入例程呢?

  乍一看,这应该不是一个问题,似乎Employee[] 和 Person[] 就应该是兼容的,但是这个问题要比我们想象的要复杂,假设除了Employee外,我们还有Student IS-A Person,此时考虑下面两条语句

 //测试数组的协变形
    /**
     * 在声明一个数组的类型为超类  它可以应用任意类型的它的子类
     * 编译的时候 Employee 是一个Person类 是通过编译的
     * 但在运行的时候 会抛出运行时异常的错误
     * java.lang.ArrayStorexception
     * 数组的协变性导致了 编译的通过 但是却导致了后面运行时异常的错误
     * 使用泛型的全部意义就是在于 产生编译错误而不是运行时的异常错误
     * ,所以泛型集合是不会协变得
     */
    public void test(){
        Person [] p = new Employee[5];
        p[0] = new Student("学生");
        p[1] = new Employee("职员");
        System.out.println(p[0].toString());
        System.out.println(p[1].toString());

    }

所以数组的这种协变形导致可以通过编译,但是运行会抛出错误

java.lang.ArrayStoreException: javabean.Student


Process finished with exit code 255

 3:简单的泛型类

  当指定一个泛型类的时候,类的声明则包含一个或者多个类型参数,这些被放进类名后面的一个尖括号内,

例如下面

GenericMemoryCell类在声明的时候在类的名字后加上了尖括号 并且在尖括号之内指定了 该类的类型参数

public class GenericMemoryCell<AnyType> {
    private AnyType storedValue;
    public AnyType read(){
        return  storedValue;
    }

    public void write(AnyType storedValue) {
        this.storedValue = storedValue;
    }
}
 public void testfanxing(){
        GenericMemoryCell<Integer> memoryCell =  new GenericMemoryCell<>();
        memoryCell.write(5);
        GenericMemoryCell<Person> p  = new GenericMemoryCell<Person>();
        p.write(new Person("二愣子"));
        GenericMemoryCell<String> memoryCell1 = new GenericMemoryCell<>();
        memoryCell1.write("asdas");
        memoryCell.read();
        memoryCell1.read();
        p.read();
    }

 下面是运行的结果

TestCompare,testfanxing
5
asdas
姓名二愣子

Process finished with exit code 0
package javabean;

public class Person {
    private  String name;
    public  Person(String name){
       this.name = name;
   }
    public String getName(){
        return this.name;
    }
    @Override
    public String toString() {
        return "姓名"+getName();
    }
}

 泛型是不支持协变得也就是 虽然Person 是 studet 和Empoyee的父类,但是在使用Person去声明类型的时候,是不可以引用子类的实例对象的,在编译时就会检查出错误,例如下面的代码

     //下面在声明时 指定了泛型为 Person时候,即使 Employee 为Person的子类但是 编译器 是 不认账的
        //也就是泛型是不支持协变
        Collection<Person> personCollection  = new ArrayList<Employee>();
        Collection<Person> people2 = new ArrayList<Student>();

我们知道基于java多态我们是可以在声明父类时 去 引用子类的实例对象的,为了在使用泛型的时候更加的灵活, 引进了通配符,通配符是为了表示参数类型的子类或者父类,例如

下面的代码,通配符使我们的编码更加的灵活了

 */
        ArrayList<? extends Object> objects
        = new ArrayList<String>();
        Collection<? super String > strings = new ArrayList<Object>();
        Collection<? extends Person> people1 = new ArrayList<Student>();
        Collection<? super Student> students1 = new ArrayList<Person>();
        Collection<? extends Person> people2 = new ArrayList<Employee>();

 4:泛型方法

  1:非静态  传入参数为泛型 的方法

    话不多说 上代码

/**
     * 参数泛型方法 使用通配符
     *
     */
    public void fanXingTong(Collection<? extends Person> collections){
        for (Person person:collections
                ) {
            System.out.println(person.toString());
        }

    }
    /**
     * 当使用Coollection<T>来限制 上述问题 在编译时报错
     *
     */
    public void fanXing(Collection<Person> collections){
        for (Person person:collections
                ) {
            System.out.println(person.toString());
        }

    }
    @Test
    public void testFanXing(){
        List<Person> personList = new ArrayList<Person>();
        Employee employee = new Employee("Employee");
        Student student = new Student("Student");
        personList.add(employee);
        personList.add(student);
        List<Student> studentList = new ArrayList<Student>();
        studentList.add(student);
        //下面代码 通不过编译
        fanXing(studentList);
        //带有通配符可以,但是注意的 对于传过去的参数 操作的时候一定要 使用父类中的方法以及属性 
        fanXingTong(studentList);
        
        
    }

 不可以直接使用 通配符 声明 一个对象实例

class Info<T>{
        private T var ;
        public void setVar(T var){
            this.var = var ;
        }
        public T getVar(){
            return this.var ;
        }
        public String toString(){
            return this.var.toString() ;
        }


    };
@Test
        public  void main(){
    //不可以使用通配符 声明
            Info<?> i = new Info<String>() ;        //单此一句用问号接收这个类型的对象是可以
           System.out.println(i.getClass().getName());
            //i.setVar("MLDN") ;                  //但是加上这一句赋值的就编译报错.
        }

 在使用 通配符 声明一个对象 例如上述 i 时   就算真实的引用类型 已经确定为<String> 时 但是 在对i 操作时    java只会按照声明类型进行操作,而直接使用?时 jvm虚拟机 将不会知道?是什么类型 ,所以在上述 i.setVar()时 编译器 不知道?是什么类型 所以 也就不会知道 info 对象中的T是什么类型 ,会有一个底层的 Capture of ?的一个类型 参数 我也不知道是啥

 

原文地址:https://www.cnblogs.com/ChenD/p/8965224.html