发生异常时如何定位 lambda?

2022-09-03 02:52:08

假设我有一个比较器工厂,它有许多由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)
...
...

如您所见,它表明这使我很难找到哪个比较器是错误的。我还试图在比较发生之前添加一个断点(即,在上面的示例中,at),但也找不到它是哪个lambda(它跳转到错误的比较器,这与我最终发现错误无关,当我最终发现错误时,它不是正确的)。(Unknown Source)DefaulRowSorter.java:968next stepdoublestring

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

Q:

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

个简单的例子来重新产生类似的问题。


答案 1

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

-g:lines,source,vars

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

下面是一个 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 使用原始类型,因此可以将比较器用于 列表。堆栈跟踪确实不包含行号,因为异常发生在转换过程中,而不是在我们的源代码中:StringIntegers

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)

这里的经验法则是不要使用原始类型,在这种情况下,这将使调试过程更容易(什么是原始类型,为什么我们不应该使用它?)。编译器也可以在这里帮助你:包括javac的这个选项:

-Xlint:all

编译器会警告您有关原始类型的大量其他内容。添加另一个选项:

-Werror

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


答案 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

一个非常重要的区别是,第二个比较器有一个类和一个实例,但是具有lambda的比较器只是成为一个调用动力学的方法NEW

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

  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的位置,因此它必须显示.lambdaUnknown source


推荐