异常发生时如何定位lambda?

时间:2015-05-24 14:41:40

标签: java exception lambda

假设我有一个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无关,当我最终发现错误时,它不是正确的。)

在我发现错误后(通过尝试理解整个项目和很多时间),我尝试了一个匿名类。堆栈的回溯明确地告诉我它在哪里。

问:

如果我想让lambda提供简洁的代码,有没有什么好方法可以找到lambda源的位置,或者在异常发生时帮助我的任何好的做法?

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

2 个答案:

答案 0 :(得分:5)

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

-g:lines,source,vars

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

以下是lambdas的一个简单示例:

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());
    }
}

这是stacktrace:

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)

正如您所见,stacktrace 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服务器一起使用以确保源代码的高质量时非常有用)

答案 1 :(得分:1)

据我尝试和搜索过,你无法找到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