Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 434|回复: 0

[泛型学习]Java泛型再学习

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-29 23:56:34 | 显示全部楼层 |阅读模式
    泛型引入java语言已经有很长一段时间了,在JDK5出来的时候也非常认真地学习过,不过学习的资料都是网上泛滥并且重复的教程。这几天下了《The Java Programming Language》的第4版,准备把jdk5引入的新东西再重新系统地学习一次,同时再次回顾下Java基础。今天记录下学习泛型那一章的注意点。
    一、泛型类型的声明
    1.需要着重注意的一点,比如声明类Cell<E>:
       
         
        package
         net.rubyeye.javaprogramming.generic;


        public
         
        class
         Cell
        <
        E
        >
         {
         
        private
         Cell
        <
        E
        >
         next;

         
        private
         E element;

         
        public
         Cell(E element) {
             
        this
        .element
        =
         element;
         }

         
        public
         Cell(E element, Cell
        <
        E
        >
         next) {
             
        this
        .next
        =
         next;
             
        this
        .element
        =
         element;
         }

         
        public
         E getElement() {
             
        return
         element;
         }

         
        public
         
        void
         setElement(E element) {
             
        this
        .element
        =
         element;
         }

         
        public
         Cell
        <
        E
        >
         getNext() {
             
        return
         next;
         }

         
        public
         
        void
         setNext(Cell
        <
        E
        >
         next) {
             
        this
        .next
        =
         next;
         }

    }

       
    然后如此使用:
       
         
        Cell
        <
        String
        >
         strCell
        =
         
        new
         Cell
        <
        String
        >
        (
        "
        Hello
        "
        );
    Cell
        <
        Integer
        >
         intCell
        =
         
        new
         Cell
        <
        Integer
        >
        (
        25
        );

       
    那么Cell<String>和Cell<Integer>是两个类吗?不,他们是同一个类,通过下面的实验证明:
       
         
        assertTrue(strCell.getClass()
        ==
         intCell.getClass()));

       
    java泛型的实现采用的“擦拭法”,Cell<E>仍然是一个类,无论E被任何具体的类型所替代。

    2.泛型的类型参数不能用于static变量、static方法和static初始化,比如下面的使用方式都不能编译通过:
       
         
        public
         
        class
         Cell
        <
        E
        >
         {
         
        private
         
        static
         Cell
        <
        E
        >
         next;
         
         
        private
         
        static
         
        void
         test(E e){
             
         }
         

        同样,静态方法是与类相关联的,调用也只能通过类,假设Cell有一个静态方法test,怎么调用才是正确的呢?
       
         
        Cell
        <
        E
        >
        .test();  
        //
        编译错误
       

        Cell
        <
        String
        >
        .test();  
        //
        同样编译错误
       

        Cell.test();  
        //
        正确的方式
        类似的,泛型的类型参数不能用于声明数组类型,比如下面的代码同样无法编译通过:
       
         
        class
         SingleLinkQueue<E> {
         
        // ...

            
        public
         E[] toArray() {
         
        //...

            }
    }
       
    3.类型参数可以继承其他的类和接口,如果有多个接口可以用&符号连接,通过extend参数限制了类型参数的范围,比如:
       
         
        interface
         SortedCharSeqCollection<E
        extends
         Comparable<E>
                                       & CharSequence> {
         
        //
         ... sorted char sequence collection methods ...
       

        }


        SortedCharSeqCollection的类型参数E强制继承自Comparable和CharSequence接口,也就是替代的具体的类型参数必须实现这两个接口,从而限制了类型参数(type parameter)。

    4.比较有趣的内部类的泛型,对于静态内部类的类型参数可以与外部类的类型参数名不一样,静态内部类的类型参数与外部类的类型参数其实没有一点关系,比如:
       
         
        class
         SingleLinkQueue<E> {
         
        static
         
        class
         Cell<E> {
             
        private
         Cell<E> next;
             
        private
         E element;
             
        public
         Cell(E element) {
                
        this
        .element = element;
             }
             
        public
         Cell(E element, Cell<E> next) {
                
        this
        .element = element;
                
        this
        .next = next;
             }
             
        public
         E getElement() {
                
        return
         element;
             }
             
        /*
         ... rest of Cell methods as before ...
        */
       
         }

         
        protected
         Cell<E> head;
         
        protected
         Cell<E> tail;

         
        /* ... rest of SingleLinkQueue methods as before ... */
       
    }


       
    Cell<E>类的声明和SingleLinkQueue<E> 两个类中的E仅仅是名称相同,他们之间的关联是通过head和tail的声明才关联在一起,你可以将Cell<E>中的E改成F也没关系,比如:
       
         
        package
         net.rubyeye.javaprogramming.generic;


        class
         AnotherSingleLinkQueue<E> {
         
        static
         
        class
         Cell<F> {
             
        private
         Cell<F> next;

             
        private
         F element;

             
        public
         Cell(F element) {
                
        this
        .element = element;
             }

             
        public
         Cell(F element, Cell<F> next) {
                
        this
        .element = element;
                
        this
        .next = next;
             }

             
        public
         F getElement() {
                
        return
         element;
             }
             
        /*
         ... rest of Cell methods as before ...
        */
       
         }

         
        protected
         Cell<E> head;

         
        protected
         Cell<E> tail;

         
        /* ... rest of SingleLinkQueue methods as before ... */
       
    }

       
    而一般的内部类就不一样了,内部类可以直接使用外部类的类型参数甚至隐藏。

    二、子类型与通配符
    今天读了第2节,泛型的使用比我原先所知的更为复杂,java语法本来以简洁优美著称,随着java5,java7的到来,语法是越来越复杂,甚至可以说丑陋!-_-

         要知道一点,比如List<Integer>不是List<Number>的子类,而是Collection<Integer>的子类。因为List<Integer>和List<Number>的类型是一样的,都是List。那么如何表示参数化类型是Number的子类呢?这就需要用到通配符:
       
         
        List
        <?
         
        extends
         Number
        >
        表示类型变量是Number或者Number的子类。这个就是所谓的上界通配符,同样,如果要表示类型变量是Number或者Number的super type,可以使用下界通配符:
       
         
        List
        <?
         
        super
         Number
        >
       
    而通配符List<?>等价于:
       
         
        List
        <?
         
        extends
         Object
        >
       
         通配符只能用于变量、局部变量、参数类型和返回类型,不能用于命名类和接口。比如下面的代码将不能编译通过:
       
         
        class
         MyList
        implements
         List
        <?>
        {
       
        //...
       

        }
            通配符有另一个问题:因为通配符代表的是未知的类型,你不能在任何需要类型信息的地方使用它。比如下面的代码同样无法编译通过:
       
         
        SingleLinkQueue
        <?>
         strings
        =
       
         
        new
         SingleLinkQueue
        <
        String
        >
        ();
    strings.add(
        "
        Hello
        "
        );               
        //
         INVALID: 无法编译
       

       
    SingleLinkQueue
        <?
         
        extends
         Number
        >
         numbers
        =
       
         
        new
         SingleLinkQueue
        <
        Number
        >
        ();
    numbers.add(Integer.valueOf(
        25
        ));   
        //
         INVALID: 无法编译
       

       

       
    三、泛型方法和类型推断
         如果我们想参数化方法的参数和返回值的类型,这就引出了泛型方法的声明,声明一个泛型方法的方式如下:
       
         
        <
        T
        >
         T passThrough(T obj) {
         
        return
         obj;
    }

       
    这个方法限制传入的参数的类型与返回的参数类型将一致,可以看到,在方法签名前加上<T>即可。我们可以这样调用这个方法:
       
         
        String s1
        =
         
        "
        Hello
        "
        ;
    String s2
        =
         
        this
        .
        <
        String
        >
        passThrough(s1);

       
    这样的调用是不是比较奇怪?幸好提供了类型推断,根据参数的类型来自动判断方法的类型(比如返回值类型),因此可以直接调用:
       
         
        String s1
        =
         
        "
        Hello
        "
        ;
    String s2
        =
         
        this
        .passThrough(s1);

       
         如果方法有两个类型变量,类型推断将怎么处理呢?比如:
       
         
        <
        T
        >
         T passThrough(T obj1,T obj2) {
             
        return
         (T)(obj1.toString()
        +
        obj2.toString());
         }
       
    然后我们传入两个参数,一个String,一个int,那么返回什么呢?
       
         
        String s1
        =
        "
        test
        "
        ;
    String s3
        =
        this
        .passThrough(s1,
        1
        );  
        //
        编译出错
        类型推断是比较复杂的,这里将返回的将是Object类型,是传入的参数类型的交集
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-26 00:58 , Processed in 0.354404 second(s), 46 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表