【问题标题】:ClassFormatError in java 8?java 8中的ClassFormatError?
【发布时间】:2021-08-30 12:32:58
【问题描述】:

我正在做一个类似于java.util.LinkedList 的课程,却得到了一个完全出乎意料的ClassFormatError。我的 IDE 没有显示警告。仅供参考,我使用的是 Java 8u20。 更新:已在 Java 8u60 中修复。

T̶h̶i̶s̶̶i̶n̶c̶l̶u̶d̶e̶s̶̶a̶l̶l̶̶r̶e̶l̶e̶v̶a̶n̶t̶̶m̶e̶t̶h̶o̶d̶s̶:̶ 将示例更新为完全可编译:

import java.io.Serializable;
import java.util.*;
import java.util.function.Function;

public class Foo<E> implements Deque<E>, Serializable {
    private static final long serialVersionUID = 0L;

    private final Node sentinel = sentinelInit();
    private final Iterable<Node> nodes = (Iterable<Node> & Serializable) () -> new Iterator<Node>() {
        @SuppressWarnings("UnusedDeclaration")
        private static final long serialVersionUID = 0L;

        private Node next = sentinel.next;

        @Override
        public boolean hasNext() {
            return next != sentinel;
        }

        @Override
        public Node next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            Node old = next;
            next = next.next;
            return old;
        }

        @Override
        public void remove() {
             if (next.previous == sentinel) {
                throw new IllegalStateException();
            }
            removeNode(next.previous);
        }
    };

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return null;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public void addFirst(E e) {

    }

    @Override
    public void addLast(E e) {

    }

    @Override
    public boolean offerLast(E e) {
        return false;
    }

    @Override
    public E removeFirst() {
        return null;
    }

    @Override
    public E removeLast() {
        return null;
    }

    @Override
    public E pollFirst() {
        return null;
    }

    @Override
    public E getFirst() {
        return null;
    }

    @Override
    public E getLast() {
        return null;
    }

    @Override
    public E peekFirst() {
        return null;
    }

    @Override
    public boolean removeFirstOccurrence(Object o) {
        return false;
    }

    @Override
    public boolean removeLastOccurrence(Object o) {
        return false;
    }

    @Override
    public E remove() {
        return null;
    }

    @Override
    public E element() {
       return null;
    }

    @Override
    public void push(E e) {

    }

    @Override
    public E pop() {
        return null;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @Override
    public boolean offerFirst(E e) {
        return false;
    }

    @Override
    public E pollLast() {
        return null;
    }

    @Override
    public E peekLast() {
        return null;
    }

    @Override
    public boolean offer(E e) {
        Node node = new Node(e);
        sentinel.previous.next = node;
        node.previous = sentinel.previous;
        sentinel.previous = node;
        node.next = sentinel;
        return true;
    }

    @Override
    public E poll() {
        return null;
    }

    @Override
    public E peek() {
        return null;
    }

    @Override
    public boolean remove(Object o) {
        for (Node node : nodes) {
            if (node.value.equals(o)) {
                removeNode(node);
                return true;
            }
        }
        return false;
    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public Iterator<E> descendingIterator() {
        return null;
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            private final Iterator<Node> backingIter = nodes.iterator();

            @Override
            public boolean hasNext() {
                return backingIter.hasNext();
            }

            @Override
            public E next() {
                return backingIter.next().value;
            }

            @Override
            public void remove() {
                backingIter.remove();
            }
        };
    }

    private Node sentinelInit() {
        Node sentinel = new Node();
        sentinel.next = sentinel;
        sentinel.previous = sentinel;
        return sentinel;
    }

    private void removeNode(Node node) {
        node.previous.next = node.next;
        node.next.previous = node.previous;
    }

    private class Node implements Serializable {
        private static final long serialVersionUID = 0L;
        public E value;
        public Node next;
        public Node previous;

        public Node(E value) {
            this.value = value;
        }

        public Node() {
            this(null);
        }
    }

    public static <I, O> List<O> map(Function<? super I, O> function, Iterable<I> objects) {
        ArrayList<O> returned = new ArrayList<>();
        for (I obj : objects) {
            returned.add(function.apply(obj));
        }
        return returned;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean ret = false;
        for (boolean changed : map(this::add, c)) {
            if (changed) {
                ret = true;
            }
        }
        return ret;
    }

    @Override
    public boolean add(E e) {
        if (!offer(e)) {
            throw new IllegalStateException();
        }
        return true;
    }

    public static void main(String[] args) {
        Foo<String> list = new Foo<>();
        System.out.println("Constructed list");
        list.addAll(Arrays.asList("a", "B", "c"));
        System.out.println("Added a, B and c.");
        list.forEach(System.out::println);
        list.remove("B");
        list.forEach(System.out::println);
    }
}

这是输出:

Constructed list
Added a, B and c.
Exception in thread "main" java.lang.ClassFormatError: Duplicate field name&signature in class file uk/org/me/Foo$1
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:455)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:367)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at uk.org.me.Foo.lambda$new$c83cc381$1(Foo.java:18)
    at uk.org.me.Foo$$Lambda$1/1392838282.iterator(Unknown Source)
    at uk.org.me.Foo$2.<init>(Foo.java:222)
    at uk.org.me.Foo.iterator(Foo.java:221)
    at java.lang.Iterable.forEach(Iterable.java:74)
    at uk.org.me.Foo.main(Foo.java:300)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

【问题讨论】:

  • 您的运行时与您的编译时 java 版本不匹配
  • 似乎在抱怨你有一个字段Node next 和一个方法Node next()。但我不确定为什么会出现问题,如果是,为什么编译不会失败。
  • @MarkPeters 谢谢。我尝试将字段next 更改为nextNode,这似乎解决了问题。不过,我不确定为什么具有相同签名的方法和字段会发生冲突。
  • 我建议向 Oracle 创建一个错误报告
  • 根据我对下面更新答案的更新,Oracle 现在已经从 8u60 版本开始修复了这个错误,并且我已经验证了他们在 8u60 上的修复。错误报告 8080842 参考。

标签: java java-8


【解决方案1】:

您的代码是有效的,ClassFormatError 是由于 Java 8 编译器中的错误造成的。我认为您已经发现了一个极端情况:加载为 lambda 表达式主体生成的类文件,其中包含与在该主体中被覆盖的方法同名的变量可能会失败。

我将您的代码粘贴到最新版本的 Intellij Idea (13.1.5)、NetBeans (8.0.1) 和 Eclipse (Kepler SR2) 中。在所有情况下编译的代码。运行时,它在 NetBeans 和 Idea 中以 ClassFormatError 失败,但在 Eclipse 中运行良好。

堆栈跟踪显示错误是由于尝试加载由分配给实例变量Foo.nodes 的lamba 表达式生成的类失败引起的。根本原因是在分配给Foo.nodes 的lambda 主体中,您有一个变量next,与被覆盖的方法next() 同名。

正如您已经注意到的,如果您将声明 private Node next = sentinel.next; 中的变量重命名为某个唯一值(例如 next2),问题就会消失。同样,将其重命名为不同的覆盖方法(即removehasNext)会导致问题再次出现。 ClassFormatError ("Duplicate field name&signature") 附带的消息准确地描述了问题,但没有解释为什么这应该是一个如此精确定义的运行时错误;编译的代码,并且方法和变量具有相同的名称是合法的(如果不明智的话)。以“this”为前缀。不能解决问题。

在失败的 Idea 类文件(在我的例子中为foo/Foo$1)上运行 javap 显示:

C:\Idea\Java8\Foo\out\production\Foo\foo>"C:\Program Files\Java\jdk1.8.0_20\bin\javap.exe" Foo$1.class
Compiled from "Foo.java"
class foo.Foo$1 implements java.util.Iterator<foo.Foo<E>.Node> {
  final foo.Foo this$0;
  foo.Foo$1(foo.Foo);
  public boolean hasNext();
  public foo.Foo<E>.Node next();
  public void remove();
} 

但是,在运行良好的相应 Eclipse 类上运行 javap 会显示一个额外的条目:

public java.lang.Object next();

如果 lambda 主体中的变量 next 被重命名为独特的东西,那么额外的方法条目也会神奇地出现在 Idea 类文件中,从而允许代码运行。我建议您将此作为编译器错误报告给 JetBrains。

另外,作为一个不相关的问题,Iterator 接口实现 remove() 作为 Java 8 中的默认方法。由于您从不调用 Iterator.remove(),您可以删除您的两个实现。

更新: 我针对 Oracle 的这个问题提出了一个错误 (JDK-8080842),并将在 1.8.0_60 版中修复它。

更新更新(2015 年 8 月 27 日): Oracle 已在 8u60 及以后的所有版本中修复了此问题。我验证了针对 8u60 的修复。 Bug JDK-8080842 参考。

【讨论】:

  • 向 JetBrains 报告? IDEA 使用 javac,这发生在原始命令行 javac 中。由于他们的自定义编译器,它只适用于 Eclipse。
  • 由于在 Idea 中失败,将其报告给 JetBrains 似乎是合理的,但如果您认为这更合适,请报告给 Oracle。或两者兼而有之。
  • @bcsb1001 我为您在 NetBeans 中发布的应用程序编写了一个更简单的版本,重现了错误,并将其作为编译器错误报告给 Oracle。详情请见netbeans.org/bugzilla/show_bug.cgi?id=247843
  • 嗯?错误 8080842 仍然存在。
  • @bayou.io 其实8080842现在已经关闭了,8u60以后问题已经修复了。我上面的更新是无可救药的错误,但我现在已经更正了。谢谢你让我诚实。
【解决方案2】:

nodes 中的两个变量和称为nextprivate class Node 中的两个变量都属于同一类型 (Node) 并且具有相同的名称:在该私有类中,它们都在范围内,但不能消除歧义。

所以,问题不是next 字段和next() 方法,而是nextIterable&lt;Node&gt; nodes 和您的private class Node 中的命名变量。

如果您将sentinel.next 重构为其他名称:

private final Iterable<Node> nodes = (Iterable<Node> & Serializable) () -> new Iterator<Node>() {
    @SuppressWarnings("UnusedDeclaration")
    private static final long serialVersionUID = 0L;

    private Node snext = sentinel.next;  // refactor `next` to `snext`
    ...

它将编译并运行。

作为个人说明,我不鼓励您编写这样的代码:阅读和弄清楚它的作用非常困难。

希望这会有所帮助!

【讨论】:

  • 我已经知道我可以通过重命名next 来解决这个问题(参见 cmets),但还是谢谢你。如果您想要一个工作示例,请参阅我的编辑。
  • 您的评论说:“我不确定为什么具有相同签名的方法和字段会发生冲突”——不是方法的名称与实例变量名:冲突的是内部 Node 类的名为 node 的成员。正如您还提到这可能是“Java 编译器错误”,我只是指出并非如此。
  • 顺便说一句 - 如果您认为我的回答解决了这个问题,我们将不胜感激:)(因为它也有助于其他人寻找相同的例外/问题)。谢谢!
  • 我看不出这不是 Java 编译器错误,您的回答确实 not 解释了为什么不这样做。 Iterable&lt;Node&gt; 的实例应该能够具有与其通用类型 (Node) 中的签名相同的字段。
  • 它们都是Node类型的实例,它们都被称为next并且在private class Node内部它们不能被消除歧义。是的,Iterable&lt;Node&gt;(称为nodes)没有进入这里的等式。无论如何,没关系 - 我已经尝试过提供帮助,但请按照您的方式进行:随时向 JCP 提交错误。
【解决方案3】:

尝试使用您的 jdk 安装在命令行上编译“.java”文件。

使用 JDK 编译后,看看是否可以运行新的“.class”文件。如果您的类加载器在这些条件下工作,则您的 IDE 未使用 javac 的 JDK 副本(或配置为编译到不同的 --target 级别)。

Eclipse 曾经发布它自己的编译器(我相信它仍然如此)。 Netbeans 始终遵循 JDK 编译器。在最近的几个版本中,JDK 编译器在 IDE 集成方面做了很多工作。但是,我认为 Eclipse 并没有重新设计他们的 IDE 来利用这一努力。

无论如何,您很快就会知道您的问题是您的 JDK 编译器还是 IDE 编译器。

【讨论】:

  • 我使用 IntelliJ IDEA 并且我没有改变默认编译器。这个默认编译器是 javac。来源:jetbrains.com/idea/webhelp/compiler-java-compiler.html
  • 那么您是否尝试了上述步骤?阅读某件事应该如何工作或失败与观察它如何工作或失败之间存在天壤之别。
  • 我尝试从命令行编译运行,同样的错误重现。所以这绝对是JDK的问题。
【解决方案4】:

根据 java jdk 文档:当 Java 虚拟机尝试读取类文件并确定该文件格式错误或无法解释为类文件时抛出。

您可以尝试删除 out 已编译类 文件夹 的内容,然后进行完全重建。

这对我有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-21
    • 1970-01-01
    • 2011-05-29
    • 2016-04-21
    • 2014-02-21
    • 2021-03-01
    • 1970-01-01
    相关资源
    最近更新 更多