谜题89:泛型迷药

和前一个谜题一样,本谜题也大量使用了泛型。我们从前面的错误中吸取教训,这次不再使用原生类型了。这个程序实现了一个简单的链表数据结构。main程序构建了一个包含2个元素的list,然后输出它的内容。那么,这个程序会打印出什么呢?


public class LinkedList<E> {

    private Node<E> head = null;

    private class Node<E> {

        E value;

        Node<E> next;

        // Node constructor links the node as a new head

        Node(E value) {

            this.value = value;

            this.next = head;

            head = this;

        }

    }

    public void add(E e) {

        new Node<E>(e);

        // Link node as new head

    }

    public void dump() {

        for (Node<E> n = head; n != null; n = n.next)

            System.out.println(n.value + " ");

    }

    public static void main(String[] args) {

        LinkedList<String> list = new LinkedList<String>();

        list.add("world");

        list.add("Hello");

        list.dump();

    }

}

又是一个看上去相当简单的程序。新元素被添加到链表的表头,而dump方法也是从表头开始打印list。因此,元素的打印顺序正好和它们被添加到链表中的顺序相反。在本例中,程序先添加了“world”然后添加了“Hello”,所以总体来看它似乎就是一个复杂化的Hello World程序。遗憾的是,如果你尝试着编译它,就会发现它不能通过编译。编译器的错误消息是令人完全无法理解的:


LinkedList.java:11: incompatible types

found : LinkedList<E>.Node<E>

required: LinkedList<E>.Node<E>

              this.next = head;

                            ^

LinkedList.java:12: incompatible types

found : LinkedList<E>.Node<E>

required: LinkedList<E>.Node<E>

              head = this;

                      ^

编译器试图告诉我们,这个程序太过复杂了。一个泛型类的内部类可以访问到它的外围类的类型参数。而编程者的意图很明显,即一个Node的类型参数应该和它外围的LinkedList类的类型参数一样,所以Node完全不需要有自己的类型参数。要订正这个程序,只需要去掉内部类的类型参数即可:

// 修复后的代码,可以继续修改得更好


public class LinkedList<E> {

    private Node head = null;

    private class Node {

        E value;

        Node next;

        //Node的构造器,将node链接到链表上作为新的表头

        Node(E value) {

            this.value = value;

            this.next = head;

            head = this;

        }

    }

    public void add(E e) {

        new Node(e);

        //将node链接到链表上作为新的表头

    }

    public void dump() {

        for (Node n = head; n != null; n = n.next)

            System.out.print(n.value + " ");

    }

}

以上是解决问题的最简单的修改方案,但不是最优的。最初的程序所使用的内部类并不是必需的。正如谜题80中提到的,你应该优先使用静态成员类而不是非静态成员类[EJ Item 18]。LinkedList.Node的一个实例不仅含有value和next域,还有一个隐藏的域,它包含了对外围的LinkedList实例的引用。虽然外部类的实例在构造阶段会被用来读取和修改head,但是一旦构造完成,它就变成了一个甩不掉的包袱。更糟的是,这样使得构造器中被置入了修改head的负作用,从而使程序变得难以读懂。应该只在一个类自己的方法中修改该类的实例域。

因此,一个更好的修改方案是将最初的那个程序中对head的操作移到LinkedList.add方法中,这将会使Node成为一个静态嵌套类而不是真正的内部类。静态嵌套类不能访问它的外围类的类型参数,所以现在Node就必须有自己的类型参数了。修改后的程序既简单清楚又正确无误:


class LinkedList<E> {

    private Node<E> head = null;

    private static class Node<T> {

        T value; Node<T> next;

        Node(T value, Node<T> next) {

            this.value = value;

            this.next = next;

        }

    }

    public void add(E e) {

        head = new Node<E>(e, head);

    }

    public void dump() {

        for (Node<E> n = head; n != null; n = n.next)

            System.out.print(n.value + " ");

    }

}

总之,泛型类的内部类可以访问到其外围类的类型参数,这可能会使得程序模糊难懂。本谜题所阐述的误解对于初学泛型的程序员来说是普遍存在的。在一个泛型类中设置一个内部类并不是必错的,但是很少用到这种情况,而且你应该考虑重构你的代码来避免这种情况。当你在一个泛型类中嵌套另一个泛型类时,最好为它们的类型参数设置不同的名字,即使那个嵌套类是静态的也应如此。对于语言设计者来说,或许应该考虑禁止类型参数的遮蔽机制,同样的,局部变量的遮蔽机制也应该被禁止。这样的规则就可以捕获到本谜题中的错误了。

原文地址:https://www.cnblogs.com/yuyu666/p/9842694.html

时间: 2024-10-13 14:47:48

谜题89:泛型迷药的相关文章

《Java解惑》读书笔记

 摘选自<Java解惑>一书,之前整理了部分,一直没看完,最近为了督促自己每天读点这本书,决定一天至少更新一个谜题的内容,欢迎讨论. 欢迎关注技术博客http://blog.sina.com.cn/u/1822488043 Java解惑读书笔记 谜题1:奇数性 取余操作的定义: ( a / b ) * b + ( a % b ) = a 其中(a/b)是java运算的结果,也就是a/b是一个整数,比如3/2=1. 所以当取余操作返回一个非零结果的时候,它与左操作数具有相同符号. 请测试你的

千穗谷大家空心砖接着看见数据库恢复

pinterest.com/youzhenqu/%E6%BF%89%E6%BA%AA%E5%8E%BF%E5%93%AA%E9%87%8C%E6%9C%89%E6%89%BE%E5%B0%8F%E5%A7%90%E6%9C%8D%E5%8A%A1%E7%94%B5%E8%AF%9D%E4%BF%A1%E6%81%AF pinterest.com/youzhenqu/%E7%95%8C%E9%A6%96%E6%89%BE%E5%B0%8F%E5%A7%90%E4%B8%8A%E9%97%A8%E5

7. 及时发布。除非真正的用户接触到你的产品并给予反馈

7. 及时发布.除非真正的用户接触到你的产品并给予反馈,你永远都不会知道你的产品是好是坏. 8. 尽快发布,经常发布.不要惦记着再增加一些其它功能.只要能达到可以用来收集用户反馈的最小功能集合,那就发布它.收集反馈信息,反复这个过程,发布下一 个版本.下一个版本,越快越好.如果你3个月才发布出第一版面向用户的产品,你拖延的太久了.如果3个星期才发布一次更新包,你拖延的太久了.如果不能一 周几次,那每周发布一次更新.每3周发布一次重大更新. 9. 唯一有意义的事是你的产品的好坏.其它的都是鸡毛蒜皮

否极泰来接电话高科技电话施

http://www.yhd.com/ctg/s2/c19606-0-60977/%E8%BF%9E%E4%BA%91%E6%B8%AF%E6%B5%B7%E5%B7%9E%E5%8C%BA%E5%93%AA%E9%87%8C%E6%9C%89%E6%89%BE%E5%AD%A6%E7%94%9F%E5%A6%B9%E6%89%93%E7%82%AE%E4%B8%8A%E9%97%A8%E6%9C%8D%E5%8A%A1%E5%8C%85%E5%A4%9C%E7%94%B5%E8%AF%9D%2

Java的泛型和通配符

泛型:1.泛型类    class A<T>{    }2.在创建实例时,需要为其类型变量赋值 3.泛型方法    class A<T>{        public T fun1(){}        public void fun2(T t){}        //以上两个都不是泛型方法,他们是泛型类里面的一个方法        //发现方法要求需要在方法上有泛型的定义        public <T> T fun3(){}//此为泛型方法    } class

Java 中的泛型详解-Java编程思想

Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限. 2.简单泛型 促成泛型出现最引人注目的一个原因就是为了创造容器类. 首先看一个只能持有单个对象的类,这个类可以明确指定其持有的对象的类型 class Holder1 { private Circle a; public Holder1(Circle a) { this.a = a; } Circle get() { return a; } } 上面的类的可重用性不怎么样,无法持有其他类型的任何对象,下面通过持有Object

算法学习之基础(背包 列队 栈) 习题1.3.33泛型双向队列

前几天写的.. 1 package gh; 2 3 import java.util.Iterator; 4 5 /** 6 * 泛型双向队列(双向链表实现) 7 * @author ganhang 8 * 9 */ 10 public class Deque<T> implements Iterable<T> { 11 private Node first; 12 private Node last; 13 private int n=0; 14 public Deque(){

十、java泛型

一.泛型的基本知识 泛型的语义上理解是“适合许多种类型”, 设计出来的最初目的是希望类或者方法拥有最广泛的表达能力. 1.简单泛型 对于泛型来说最常用也是最简单的应用就是创造容器类 1 package com; 2 3 public class Holder<T> { 4 private T a ; 5 public Holder() { 6 } 7 8 public Holder(T a ) { 9 this.a = a; 10 } 11 12 public static void main

读取数据库返回泛型集合 把DataSet类型转换为List&lt;T&gt;泛型集合

转自 http://www.cnblogs.com/wuhuisheng/archive/2012/04/26/2471733.html 1 /// <summary> 2 /// 获取UserInfo泛型集合 3 /// </summary> 4 /// <param name="connStr">数据库连接字符串</param> 5 /// <param name="sqlStr">要查询的T-SQL&