如何正确实现泛型链表的 toString() 方法

本文详解因泛型类型参数命名不当(如误用 `string`)导致 `tostring()` 无法重写的问题,提供两种修复方案,并重点推荐语义清晰、无歧义的泛型命名方式(如 `t`),同时修正链表遍历逻辑与空指针隐患。

你在定义泛型类 List 时,将类型参数命名为 String,这会「遮蔽」(shadow)Java 标准库中的 java.lang.String 类。编译器此时认为:类中所有未限定的 String 都是指你声明的类型变量 String extends Object,而非真正的字符串类。因此:

  • public String toString() 被解析为返回你自定义的泛型 String,而非 java.lang.String,违反了 Object.toString() 的签名契约(返回类型必须是 java.lang.String);
  • String string = ""; 中的 "" 是 java.lang.String 字面量,无法赋值给类型变量 String(即使它继承自 Object,二者仍属不同类型)。

根本解决方案:避免使用内置类名作为类型参数

推荐采用通用、无歧义的占位符,如 T(Type)、E(Element)、K/V(Key/Value)等:

public class List {  // ✅ 正确:T 表示任意元素类型

    private static class Node {  // 建议静态内部类 + 显式泛型
        private final T element;
        private Node next;

        Node(T element, Node next) {
            this.element = element;
            this.next = next;
        }

        Node(T element) {
            this(element, null);
        }
    }

    private Node head = null;
    // ⚠️ 注意:移除冗余的 'private Node current = head;' —— 它在 toString() 中被错误复用且未重置!

    public void prepend(T element) {
        head = new Node<>(element, head);
    }

    public void append(T element) {
        if (head == null) {
            head = new Node<>(element);
            return;
        }
        Node current = head;
        while (current.next != null) {
            current = current.next;
        }
        current.next = new Node<>(element);
    }

    public T first() {
        if (head == null) throw new NoSuchElementException("List is empty");
        return head.element;
    }

    public T get(int index) {
        if (index < 0 || head == null) throw new IndexOutOfBoundsException();
        Node current = head;
        for (int i = 0; i < index && current != null; i++) {
            current = current.next;
        }
        if (current == null) throw new IndexOutOfBoundsException();
        return current.element;
    }

    public int size() {
        int size = 0;
        Node current = head;
        while (current != null) {
            size++;
            current = current.next;
        }
        return size;
    }

    @Override  // ✅ 显式添加 @Override 注解,增强可读性与编译检查
    public String toString() {
        if (head == null) return "[]";

        StringBuilder sb = new StringBuilder("[");
        Node current = head;
        while (current != null) {
            sb.append(current.element);
            if (current.next != null) {
                sb.append(" -> ");
            }
            current = current.next;
        }
        sb.append("]");
        return sb.toString();
    }
}

? 关键修复点说明:

  • 类型参数重命名 彻底消除与 java.lang.String 的命名冲突;
  • toString() 逻辑修正:原代码中错误地使用了类字段 current(未初始化且被多次调用污染),现改用局部变量遍历,并用 StringBuilder 提升性能与可读性;
  • 空安全增强:first() 和 get() 添加边界检查,避免 NullPointerException;
  • 静态内部类优化:Node 不依赖外部类实例状态,声明为 static 可节省内存;
  • 显式 @Override:明确表达重写意图,便于 IDE 和编译器校验。

不推荐的备选方案(仅作理解)
虽然可强行写成 public java.lang.String toString() 并用全限定名声明变量,但这治标不治本——类型参数 String 依然存在语义混淆,后续所有 Node、方法参数等均需反复加 java.lang.,严重降低可维护性。

? 总结建议:
永远避免用 String、Integer、List 等 JDK 类名作为泛型类型参数。遵循 Java 命名规范,使用单字母大写标识符(T, E, K, V, N)是最安全、最通用的做法。这不仅解决编译错误,更是写出清晰、健壮、可协作泛型代码的第一步。