Stacktrace与类名中的数字 – 为什么?

当finalizeOperation运行时(在更大的应用程序中生产):

public interface OperationFinalizerHook { void onOperationFinalize(Operation operation, Object context); } private final List operationFinalizeHooks = new ArrayList(); ... public void finalizeOperation(Object context) { final Operation operation = getOperation(); operationFinalizeHooks.forEach(hook -> hook.onOperationFinalize(operation, context)); } 

跟随调用树/堆栈跟踪构建:

 11 at com.company.SomeClass.lambda$finalizeOperation$0 (SomeClass.java:51) 12 at com.company.SomeClass$$Lambda$135/2085968933.accept (Unknown source) 13 at java.util.ArrayList.forEach (ArrayList.java:1249) 14 at com.company.SomeClass.finalizeOperation (SomeClass.java:51) 

我对第12行感兴趣 – 这个名字来自哪里? 为什么我会期望一个类的名字随机数?

编辑:以下是Niklas P 提到的博客文章中的代码:

 public class Test { public static void main(String... args) { List names = Arrays.asList("adam", ""); Stream lengths = names.stream().map(name -> check(name)); lengths.count(); } public static int check(String s) { if (s.equals("")) throw new IllegalArgumentException(); return s.length(); } } 

但结果不包含此数字名称,stacktrace是(jdk8u102):

 Exception in thread "main" java.lang.IllegalArgumentException at Test.check(Test.java:19) at Test.lambda$main$0(Test.java:12) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) at Test.main(Test.java:14) 

在jdk8u25上,数字是:

 Exception in thread "main" java.lang.IllegalArgumentException at Test.check(Test.java:18) at Test.lambda$main$0(Test.java:11) at Test$$Lambda$1/1554547125.apply(Unknown Source) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.LongPipeline.reduce(LongPipeline.java:438) at java.util.stream.LongPipeline.sum(LongPipeline.java:396) at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526) at Test.main(Test.java:13) 

这里有两个重叠的问题。 首先,当您将lambda表达式转换为对象类型时,必须有一些实现function接口的东西 – 细节不是那么重要; 唯一需要了解的是,将会有一些实现接口并调用lambda表达式的代码或方法引用的目标。

当前的JRE实现生成匿名类,顾名思义,它们不依赖于它们的名称是唯一的。 类名后面打印的数字是此属性的工件。 无论哪种方式,无论是否有数字,您都无法使用ClassLoader查找这些类。

在堆栈跟踪中使用合成工件对Java来说并不是什么新鲜事。 使用内部类时会生成存取方法,例如

 import java.util.*; import java.util.function.Function; import java.util.stream.Stream; public class Test { public static void main(String... args) { List names = Arrays.asList("adam", ""); Stream lengths = names.stream().map(new Function() { public Integer apply(String name) { return check(name); } }); lengths.count(); } private static int check(String s) { if (s.equals("")) throw new IllegalArgumentException(); return s.length(); } } 
 Exception in thread "main" java.lang.IllegalArgumentException at Test.check(Test.java:17) at Test.access$000(Test.java:5) at Test$1.apply(Test.java:10) at Test$1.apply(Test.java:8) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) … (shortened it a bit) at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526) at Test.main(Test.java:13) 

请注意堆栈跟踪中存在access$000 ,这不会出现在源代码中; 它的关联行号没有意义,它只是外部类定义的开头。

现在,似乎堆栈跟踪生成发生了变化,在最近的JRE版本中省略了匿名类的合成成员。 这也会影响reflection调用的堆栈跟踪,例如使用MethodHandle实例。 这可能被认为对大多数用例有用,但它也暗示在某些情况下调用者和被调用者之间可能存在不匹配,因为堆栈跟踪报告调用者调用接口方法,但最终在其他地方,例如

 import java.util.*; import java.util.stream.Stream; public class Test { public static void main(String... args) { Stream.of("adam", "", null).filter("foo"::contains).count(); } } 

将打印

 Exception in thread "main" java.lang.NullPointerException at java.lang.String.contains(String.java:2133) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) … 

其中ReferencePipeline.java:174包含Predicate接口的accept方法的调用,但最终在方法containsString 。 我们可以最大化:

 import java.util.*; import java.util.stream.Stream; public class Test { public static void main(String... args) { Stream.of("adam", "", null).filter(String::isEmpty).count(); } } 

将在最新的JRE上产生以下内容:

 Exception in thread "main" java.lang.NullPointerException at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.LongPipeline.reduce(LongPipeline.java:438) at java.util.stream.LongPipeline.sum(LongPipeline.java:396) at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526) at Test.main(Test.java:6) 

省略最终将在String实例上调用isEmpty的合成代码,这可能会更加混乱,因为ReferencePipeline.java:174只包含interface方法的调用而interface实例不是null (这在之前已经检查过很久了)码)。

请注意,这是一个动态的发展。 使用Java 9,将有StackWalker API,它允许应用程序使用已配置的隐藏/reflection堆栈帧处理生成自己的快照。 一旦应用程序使用此API创建可预测的堆栈跟踪,即不再依赖Throwable.getStackTrace()的特定行为, Throwable.getStackTrace()的行为可以通过JVM选项或系统属性进行配置……

这些数字来自匿名类,为lambda操作创建的JVM – 请参阅此处: -dark-expression-in-java-8-dark-side-lambda-expressions-in-java-8