zoukankan      html  css  js  c++  java
  • 泛型类、泛型接口、泛型方法及泛型的应用

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

    泛型是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 ?的一个类型 参数 我也不知道是啥

     

  • 相关阅读:
    Uboot启动流程分析(四)
    git更新提交到仓库
    MSM8953通过ADB进行AT指令操作
    Uboot启动流程分析(五)
    去除字符串中多个空格,保留一个空格
    socket握手SYN和ACK理解
    Redhat6更改yum源 (转)
    form提供的两种数据传输方式 get和post method=”post“和method=”get”
    linux误删除恢复(未验证)
    web前端学习路线
  • 原文地址:https://www.cnblogs.com/ChenD/p/8965224.html
Copyright © 2011-2022 走看看