【问题标题】:How to locate lambda when exception happens?发生异常时如何定位 lambda?
【发布时间】:2015-05-24 14:41:40
【问题描述】:

假设我有一个 ComparatorFactory,它有 许多 个由 lambda 组成的比较器:

   public static Comparator<SomeClass> getXCmp() {
      return (o1, o2) -> {
         Double d1 = Double.parseDouble(o1.getX());
         Double d2 = Double.parseDouble(o2.getX());
         return d1.compareTo(d2);
      };
   }

我使用这些比较器对数据进行排序和过滤。 不幸的是,我在某个地方使用了错误的比较器,导致ClassCastException 如下所示:

java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
at businesslogic.utility.ComparatorFactory$$Lambda$24/115669291.compare(Unknown Source)
at javax.swing.DefaultRowSorter.compare(DefaultRowSorter.java:968)
...
...

如您所见,它显示了(Unknown Source),这让我很难找到哪个比较器是错误的。我还尝试在比较发生之前添加一个断点(即在上面的示例中,在DefaulRowSorter.java:968),但next step 也无法找到它是哪个lambda(它跳转到与此无关的错误比较器doublestring 当我终于找到错误时,它不是正确的)。

在我找到 bug 之后(通过尝试了解整个项目和大量时间),我尝试了一个匿名类。堆栈的回溯明确告诉我它在哪里。

问:

如果我希望 lambda 提供简洁的代码,有没有什么好的方法可以找到 lambda 源在哪里,或者有什么好的做法可以在发生异常时帮助我?

一个simple example重新产生类似的问题。

【问题讨论】:

  • 只是使用比较器来比较。
  • ComparatorFactory$$Lambda$24 - 您可以计算 ComparatorFactory 中的所有 lambda 表达式,并得到第 24 个 :) - 但它不准确。
  • 问题可能不在您显示的代码中。您提到您使用了错误的比较器。您的例外是将双精度转换为字符串,但是您的代码中没有任何地方可以转换为字符串-都是双精度。发布决定使用什么比较器的代码,并提供导致失败的输入。
  • @Tony 将字符串转换为双精度 - 如果 parseDouble() 中正在进行转换。如果您试图争辩在可以调用 parseDouble 时将对象转换为字符串,那么这又是错误的 - 转换不会像那样神奇地发生。您的例外是将双精度转换为字符串。
  • 如果您对调试 lambda 而不是修复您的错误感兴趣,您可能最好在您显示的代码中使用错误,而不是在此 lambda 之外的错误。显示不相关的代码并寻求帮助充其量只是分散您尝试提出的问题的注意力

标签: java exception lambda


【解决方案1】:

确保在编译类时为 javac 包含此选项:

-g:lines,source,vars

“-g”编译器选项可用于控制应在类文件中生成多少调试信息(请参阅documentation

这是一个使用 lambda 的简单示例:

package test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestLambda {

    public static Comparator<String> comparator1() {
        return (o1, o2) -> {
            return o1.compareTo(o2);
        };
    }

    public static Comparator<String> comparator2() {
        return (o1, o2) -> {
            System.out.println("test");
            if (true) {
                throw new RuntimeException("Exception"); // line 20: stacktrace points to this line
            }
            return o1.compareTo(o2);
        };
    }

    public static void main(String[] args) {
        List<String> strings = Arrays.asList("string1", "string2", "string3");

        Collections.sort(strings, comparator2());
    }
}

这是堆栈跟踪:

Exception in thread "main" java.lang.RuntimeException: Exception
    at test.TestLambda.lambda$comparator2$1(TestLambda.java:20)
    at test.TestLambda$$Lambda$1/189568618.compare(Unknown Source)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351)
    at java.util.TimSort.sort(TimSort.java:216)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at java.util.Collections.sort(Collections.java:175)
    at test.TestLambda.main(TestLambda.java:29)

如您所见,堆栈跟踪 at test.TestLambda.lambda$comparator2$1(TestLambda.java:20) 指向源代码的确切行。

您的 IDE 应该能够解析堆栈跟踪并使用单击链接来装饰它,这些链接应该会将您带到源代码中的确切行(至少 IntelliJ IDEA 是这样做的)。

如果您使用-g:none 编译,堆栈跟踪将有所不同:

Exception in thread "main" java.lang.RuntimeException: Exception
    at test.TestLambda.lambda$comparator2$1(Unknown Source)
    at test.TestLambda$$Lambda$1/189568618.compare(Unknown Source)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351)
    at java.util.TimSort.sort(TimSort.java:216)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at java.util.Collections.sort(Collections.java:175)
    at test.TestLambda.main(Unknown Source)

更新:

下面是另一个更接近问题中提供的示例:

package test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestLambda {

    public static Comparator<String> comparator1() {
        return (o1, o2) -> {
            return o1.compareTo(o2);
        };
    }

    public static Comparator<String> comparator2() {
        return (o1, o2) -> {
            System.out.println("test");
            if (true) {
                throw new RuntimeException("Exception");
            }
            return o1.compareTo(o2);
        };
    }

    public static void main(String[] args) {
        List strings = Arrays.asList(1, 2, 3);

        Collections.sort(strings, comparator2());
    }
}

唯一的区别是它对 List 使用原始类型,因此可以将 String 比较器用于 Integers 的列表。堆栈跟踪确实不包含行号,因为异常发生在强制转换期间,而不是在我们的源代码中:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at test.TestLambda$$Lambda$1/189568618.compare(Unknown Source)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:351)
    at java.util.TimSort.sort(TimSort.java:216)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at java.util.Collections.sort(Collections.java:175)
    at test.TestLambda.main(TestLambda.java:29)

这里的经验法则是不要使用原始类型,在这种情况下这会使调试过程更容易 (What is a raw type and why shouldn't we use it?)。编译器也可以在这里为您提供帮助:为 javac 包含此选项:

-Xlint:all

编译器会警告您有关原始类型的许多其他事情。添加另一个选项:

-Werror

并且编译器将产生错误而不是警告(在与 CI 服务器一起使用以确保源代码的高质量时很有用)

【讨论】:

  • 看来,如果我不明确抛出异常,我仍然找不到 lambda 在哪里。在我提供的简单示例中,它不起作用。
  • @Tony 用另一个例子更新了我的答案。请看看它是否有帮助。
  • 感谢您的建议,但是在swing的某些GUI部分,您不能使用特定的类型,这隐藏了一些问题。我想要的是通过堆栈跟踪轻松找到错误。
  • 好吧,如果您真的需要使用原始类型,那么一种选择是自己进行安全转换,而不是依赖 javac 为您执行转换。例如,在将 Collections.sort() 与原始列表一起使用之前,您明确地将其转换为 List ,如该问题的答案stackoverflow.com/questions/509076/… 所示。在这种情况下,异常发生在您的代码中,因此编译器可以向您显示行号,而不是隐式转换。否则你只需要接受它作为使用原始类型的副作用。
【解决方案2】:

据我尝试和搜索,您无法找到 Java 8 中的 lambda。

这里的 lambda 是匿名类的替代品,但替代品对于 JVM 来说是不可见,这就是为什么 JVM 无法找到 lambda。

以两个简单的比较器为例:

    public static Comparator<String> comparator1() {
    return (o1, o2) -> {
        Double d1 = Double.parseDouble(o1);
        Double d2 = Double.parseDouble(o2);
        return d1.compareTo(d2);
    };
    }

    public static Comparator<String> comparator2() {
    return new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            Double d1 = Double.parseDouble(o1);
            Double d2 = Double.parseDouble(o2);
            return d1.compareTo(d2);
        }
    };
    }

从上面的代码示例编译(删除一些多余的行):

public static comparator1()Ljava/util/Comparator;
    INVOKEDYNAMIC compare()Ljava/util/Comparator; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      // arguments:
      (Ljava/lang/Object;Ljava/lang/Object;)I, 
      // handle kind 0x6 : INVOKESTATIC
      lambda/ComparatorFa.lambda$comparator1$0(Ljava/lang/String;Ljava/lang/String;)I, 
      (Ljava/lang/String;Ljava/lang/String;)I
    ]

public static comparator2()Ljava/util/Comparator;
    NEW lambda/ComparatorFa$1
    DUP
    INVOKESPECIAL lambda/ComparatorFa$1.<init> ()V

一个非常重要的区别是,第二个比较器有一个类,NEW 有一个实例,但带有 lambda 的比较器只是变成了一个方法,即INVOKEDYNAMIC

我们发现编译器只是编译了一个synthetic method让JVM调用:

  private static synthetic lambda$comparator1$0(Ljava/lang/String;Ljava/lang/String;)I
    ALOAD 0
    INVOKESTATIC java/lang/Double.parseDouble (Ljava/lang/String;)D
    INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
    ASTORE 2

    ALOAD 1
    INVOKESTATIC java/lang/Double.parseDouble (Ljava/lang/String;)D
    INVOKESTATIC java/lang/Double.valueOf (D)Ljava/lang/Double;
    ASTORE 3

    ALOAD 2
    ALOAD 3
    INVOKEVIRTUAL java/lang/Double.compareTo (Ljava/lang/Double;)I
    IRETURN

所以 JVM 完全不知道lambda 的存在。它只是在必要时调用一个方法,它显然无法找到那个 lambda 在哪里,所以它必须显示Unknown source

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 2016-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    相关资源
    最近更新 更多