• <T>泛型,广泛的类型


    其实早在1999年的JSR 14规范中就提到了泛型概念,知道jdk5泛型的使用才正式发布,在jdk7后,又对泛型做了优化,泛型的推断.

    泛型类

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, T second) {
            this.second = second;
            this.first = first;
        }
    
        public void setFirst(T newValue) {
            first = newValue;
        }
    
        public void setSecond(T newValue) {
            second = newValue;
        }
    }

    怎么理解泛型类的定义.首先在类名后根上<T>这个T就是任意类型.在 Java 库中, 使用变量 E 表示集合的元素类型, K 和 V 分别表示表的关键字与值的类型。T ( 需要时还可以用临近的字母 U 和 S) 表示“ 任意类型”。然后在类中的成员,都可以使用这个T,你既可以把T当做参数,也可以把T当做返回值.也可以把T当做成员变量的类型.这个T到底存储的什么类型,取决于你在实例化Pair时指定的具体类型.但是以上写法,你一旦指定了一个实际类型,那么这个类中所有的T都会是同一个类型.

    public class Main {
        
        public static void main(String[] args) {
            Pair<String> pair = new Pair<>();
            pair.setFirst("第一");
            pair.setSecond("第二");
        }
    }

    你也可以在一个泛型类上定义多个泛型

    public class Pair<T,U> {
        private T first;
        private U second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, U second) {
            this.second = second;
            this.first = first;
        }
    
        public void setFirst(T newValue) {
            first = newValue;
        }
    
        public void setSecond(U newValue) {
            second = newValue;
        }
    }

    但是你需要记得,因为泛型的作用域在类级别.一下写法是错误的.

     你在类上定义了个T表示,你所实例化的每一个类都要指定一个类型,现在,现在你试图不做类的实例化,而直接使用T,那么这个T你要从哪里定义呢?记住要使用泛型,先确定泛型的具体类型.

    泛型方法

    public class Demo3 {
        public   <T> void show(T t){
            System.out.println(t.toString());
        }
        public static  <S> void show2(S s){
            System.out.println(s);
        }
    
    public class Main {
        public static void main(String[] args) {
            Demo3 demo3 = new Demo3();
            demo3.<String>show("a");
            demo3.show("a");
            Demo3.show2(1);
        }
    }

    你在一个方法上指定了泛型,即泛型的作用域在方法体上,也就是说,你每次调用方法都要指定具体的类型.当然你不用每次调用都使用<T>语法,因为jdk7的泛型推断.编译器自然可以通过你的实参而推断出你想要的实际类型.将泛型定义到方法上,泛型的T可以用到参数,方法体,返回值.

    当然在你指定泛型时,也可以有以下写法

    public class Demo1 {
        public  <String> void add(String t){
        }
    }

    不过这通常是没有任何意义的,否则,你要想表达什么呢?定义了一个泛型方法,并且限定泛型的实际类型是String?

    泛型的擦除

    泛型的擦除可谓是泛型中的重中之重了.字面意思,泛型类型会被擦除.引起两个问题:1在什么情况下擦除2被擦除后的的类什么样.

    文档说明泛型只在编译器用来检测类型,编译时即会擦除泛型.所以泛型是在编译时被擦除的.下面看一下代码

    public class Demo4<T> {
        private T type;
    
        public void add(T t) {
        }
    }

     可以看出在无限定类型时(没有使用extends 或 super 限定泛型)在编译后原来的T被替换成了Object.

    泛型表达式

    看以下代码

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, T second) {
            this.second = second;
            this.first = first;
        }
    
        public T getFirst() {
            return first;
        }
    
        public void setFirst(T first) {
            this.first = first;
        }
    
        public T getSecond() {
            return second;
        }
    
        public void setSecond(T second) {
            this.second = second;
        }
    }
    public class Main {
        public static void main(String[] args) {
            Pair<Student> pair = new Pair<>();
            Student s = pair.getFirst();
        }
    }

    前边已经说过,对于无限定类型,在编译时会擦掉泛型的而变成object.那么以上这个Main中运行的代码,pair通过get()方法的返回值确可以直接赋值给Student这又是怎么回事呢?

     

     这是两个class反编译后的.可以看到,在get()方法后,编译器帮我们自动做了类型转换

    泛型与多态的冲突(桥方法)

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, T second) {
            this.second = second;
            this.first = first;
        }
    
        public T getFirst() {
            return first;
        }
    
        public void setFirst(T first) {
            this.first = first;
        }
    
        public T getSecond() {
            return second;
        }
    
        public void setSecond(T second) {
            this.second = second;
        }
    }
    public class PairChild extends Pair<Person> {
        @Override
        public void setFirst(Person first) {
            super.setFirst(first);
        }
    }

    我们继承了Pair并且指定了他的具体类型.那么我们覆盖Pair方法时就只能传入Person类型的参数了.那么,在已经编译好的Pair.class中,setFirst()应该还是Object类型.这时我们到底算是覆盖父类的方法了吗?

    Pair.class

     PairChild.class

    其中object参数的方法就是桥方法,当我们使用setFirst时,会先调用这个桥方法,这个桥方法,会将object强制转换成Person然后在调用PairChild自己的setFirst().

    打破泛型的约束

    有时间泛型对待数据类型也不是绝对安全的.请看一下示例

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, T second) {
            this.second = second;
            this.first = first;
        }
    
        public T getFirst() {
            return first;
        }
    
        public void setFirst(T first) {
            this.first = first;
        }
    
        public T getSecond() {
            return second;
        }
    
        public void setSecond(T second) {
            this.second = second;
        }
    }
    public class PairSort  {
        public void sort(Pair pair){
            pair.setFirst("可是我是字符串");
        }
    }
    public class Main {
        public static void main(String[] args) {
            PairSort pairSort = new PairSort();
            Pair<Person> pair = new Pair();
            pairSort.sort(pair);
            Person first = pair.getFirst();
        }
    }

    我们有一个PairSort类,这个类接受一个Pair实例,但是并没有指定泛型,也就是说,它现在接受的是一个Object.然后给他的却是一个指定了Person类型的Pair.这时我们在调用Pair就获得了一个错误.

     约束与限制

    1 不能使用基本类型实例化类型参数,我们不能传递基本数据类型当做泛型的具体类型,原因是当泛型擦除后需要转换成具体的object子类.而基本数据类型,并不能转换成object

    2 检查一个对象的类型不能带泛型参数.看一下代码

     3 不能创建参数化类型的数组

     4 不能实例化类型变量

     5 尽管有泛型的擦除,但是在静态中依然不能使用泛型

     6 泛型类中不能覆盖父类的方法

    通配符

    请看以下错误代码

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair() {
            first = null;
            second = null;
        }
    
        public Pair(T first, T second) {
            this.second = second;
            this.first = first;
        }
    
        public T getFirst() {
            return first;
        }
    
        public void setFirst(T first) {
            this.first = first;
        }
    
        public T getSecond() {
            return second;
        }
    
        public void setSecond(T second) {
            this.second = second;
        }
    }
    public class Employee {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    public class Manager extends Employee {
    }
    public class Main {
    
        public static void main(String[] args) {
            Pair<Manager> pair = new Pair<>();
            printBuddies(pair);//错误的
        }
    
        public static void printBuddies(Pair<Employee> p) {
            Employee first = p.getFirst();
            Employee second = p.getSecond();
            System.out.println(first.getName());
        }
    }
    printBuddies()需要一个Pair类型参数,我们指定Pair的泛型类型是Employee.当我们传递实参时,传递的是Pair<Manager>类型的实参,这是不正确的.Pair<Employee>和Pair<Manager>没有父子关系.
    他们都只是Pair类型.那么对于这种问题有没有解决方案的?
    public class Main {
    
        public static void main(String[] args) {
            Pair<Manager> pair = new Pair<>();
            printBuddies(pair);
            
            Pair<Employee> employeePair = new Pair<>();
            printBuddies(employeePair);
        }
    
        public static void printBuddies(Pair<? extends Employee> p) {
            Employee first = p.getFirst();
            Employee second = p.getSecond();
            System.out.println(first.getName());
        }
    }

    使用 ? extends Employee 指定Pair的泛型类型是Employee或者是其子类.这样就不会出现编译异常

    看一下对通配符的使用

    public class Main {
        public static void main(String[] args) {
            Pair<Manager> pair  = new Pair<>();
            Pair<? extends Employee> pair1 = pair;
            pair.setFirst(new Employee());//错误的
            pair.setFirst(new Manager());
            Employee first = pair.getFirst();
        }
    }

    我们创建一个Manager类型的Pair.将他赋值给Pair<Employee>这是没错的.但是当我们调用set方法则会出现编译异常.原因是编译器知道我们要传入Employee的子类型但是不知道具体传入的是哪个子类型所以

    拒绝编译.但是get方法则没有问题,因为返回是一个Employee

    超类通配符

    public class Main {
    
        public static void main(String[] args) {
            printBuddies(new Pair<Employee>());
            printBuddies(new Pair<Manager>());
        }
    
        public static void printBuddies(Pair<? super Manager> p) {
            Object first = p.getFirst();
            Object second = p.getSecond();
        }
    }

    我们限定printBuddies的参数为Manager或者其父类.

     由于我们希望传入Manager或者其父类所以get方法拒绝我们用一个Manager接收.它无法确定我们到底传入的是Manager还是其父类所以只能用Object接收.

    无限定通配符

     虽是无限定通配符,但是他的使用限定是最大的.我们甚至无法使用set方法.除非传递一个null.Object都不行.在get方法时我们也只能用Object来接收.那么为什么要有这样一个鸡肋的通配符呢?

  • 相关阅读:
    docker安装minio
    详解nohup /dev/null 2>&1 含义的使用
    CentOS7系统更换yum Repo源
    centos7运行yum报如下提示:Run "yum repolist all" to see the repos you have
    linux安全篇:禁止频繁访问的ip访问nginx
    Nginx 添加防爬虫
    Nginx 加载conf.d (内文件***.conf)
    rabbitMq消费死循环
    RabbitMq安装(单点与集群)rabbitMq以及状态查询
    rabbitMq内存与磁盘分配问题
  • 原文地址:https://www.cnblogs.com/zumengjie/p/11595554.html
Copyright © 2020-2023  润新知