我正在创建一个类似于java.util.LinkedList
的课程,并且得到了一个完全出乎意料的ClassFormatError
。我的IDE没有显示任何警告。仅供参考,我使用的是Java 8u20。 更新:已在Java 8u60中修复。
T̶h̶i̶s̶̶i̶n̶c̶l̶u̶d̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶
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)
答案 0 :(得分:8)
您的代码有效,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
),问题就会消失。同样,将其重命名为不同的覆盖方法(即remove
或hasNext
)会导致问题再次出现。伴随ClassFormatError
的消息(&#34;重复字段名称和签名&#34;)准确地描述了问题,但没有解释为什么这应该是这样一个精确定义的运行时错误;编译的代码,并且方法和变量具有相同的名称是合法的(如果不明智)。使用&#34;这个前缀。&#34;没有解决问题。
在失败的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
接口在Java 8中实现remove()
作为默认方法。因为你永远不会调用Iterator.remove()
,你可以删除你的两个实现。
<强> 更新 强>: 我在Oracle上针对此问题提出了一个错误(JDK-8080842),并且会在1.8.0_60版本中对其进行修复。
更新至2015年8月27日 : Oracle已经在8u60之后的所有版本中解决了这个问题。我验证了对8u60的修复。错误JDK-8080842引用。
答案 1 :(得分:1)
nodes
iterable中的两个变量和private class Node
next
中的变量属于同一类型(Node
)并具有相同的名称:在私有类中,它们是两者都在范围内,但不能消除歧义。
因此,问题是不 next
字段和next()
方法,但next
和Iterable<Node> nodes
中的private class Node
命名变量{1}}。
如果您将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`
...
它将编译并运行。
作为个人记录,我不鼓励你编写这样的代码:阅读并弄清楚它的作用是非常困难的。
希望这有帮助!
答案 2 :(得分:0)
尝试编译&#39; .java&#39;使用jdk安装命令行上的文件。
使用JDK编译之后,看看是否可以使用新的&#39; .class&#39;文件。如果您的类加载器在这些条件下工作,则IDE不使用javac的JDK副本(或者配置为编译为不同的--target级别)。
Eclipse过去常常发布它自己的编译器(我相信它仍然存在)。 Netbeans始终遵循JDK编译器。在过去的几个版本中,JDK编译器在IDE集成方面进行了相当多的工作;但是,我不认为Eclipse已经重新设计了他们的IDE以利用这一努力。
无论如何,您很快就会知道您的问题是JDK编译器还是IDE编译器。
答案 3 :(得分:0)
根据java jdk文档:当Java虚拟机尝试读取类文件并确定该文件格式错误或无法解释为类文件时抛出。
您可以尝试删除 out 编译类 folder 的内容,然后进行完全重建。
那对我有用。