17

Say I have a ComparatorFactory, it has many comparators composed by a 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);
      };
   }

I used those comparators to sort and filter data. Unfortunately, I used the wrong comparator at some place and it caused a ClassCastException as shown:

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)
...
...

As you can see, it shows (Unknown Source) which makes it hard for me to find which comparator is wrong. I also tried to add a breakpoint before the compare happens(ie, in the upper example, at DefaulRowSorter.java:968), but next step also can not locate which lambda it is(it jumps to the wrong comparator which has nothing to do with double and string and when I finally found the bug it was not the correct one).

After I found the bug(by trying to understanding the whole project and a lot of time), I tried an anonymous class.The backtrace of stack explicitly told me where it is.

Q:

If I want the lambda to provide concise code, is there any good way to find where the lambda source is or any good practice to help me when an exception happens?

A simple example to re-produce similar problem.

Community
  • 1
  • 1
Tony
  • 5,972
  • 2
  • 39
  • 58
  • Just using the comparator to compare. – Tony May 24 '15 at 14:59
  • Why don't you use a try-catch-block in your lamda-expression? or why don't you cann the lamdas in try-catch-blocks? ^^ – danielspaniol May 24 '15 at 15:38
  • In your example, I think you're rather implementing a `Comparator` of '`o1.getClass()`', not `Comparator`. Though due to type erasure your code may compile, but it would fail as `Double.parseDouble` takes a String, not double (I'm assuming `o1.getX()` returns double). But of course it's interesting whether there's support for debugging lambdas. – molnargab May 24 '15 at 15:39
  • Can you provide more code? This example does not compile – Valijon May 24 '15 at 15:52
  • Can `getX()` return by default `Object` ? Than you overridden your particular class into String? – Valijon May 24 '15 at 16:24
  • 1
    `ComparatorFactory$$Lambda$24` - you could count all the lambda expressions in ComparatorFactory, and get the 24'th :) - but it won't be accurate. – ZhongYu May 24 '15 at 21:03
  • @exhauzt I don't think it's a situation I need try-catch.it's a programming bug, not an invalid input. – Tony May 25 '15 at 04:30
  • @bayou.io I tried. And it's not 24th.:) – Tony May 25 '15 at 04:33
  • 1
    The problem is probably not within the code you show. You mention that you're using the wrong comparator. Your exception is about casting a double to a string, but there is nowhere in your code that casts to strings - it's all doubles. Post the code that decides what comparator to use, and provide the inputs that cause failure. – atk May 25 '15 at 13:23
  • @atk First, 'Double.parseDoube' require a 'String' that is where `exception` happen. Second, it has nothing to do with this specific problem. I just want to know if there is debug skill about lambda. – Tony May 25 '15 at 13:38
  • 1
    @Tony that would be casting a string to a double - if there were casting going on in parseDouble(). If you are trying to argue that the object is being cast to a string when parseDouble can be called, this is again false - casts don't magically happen like that. Your exception is about casting a double to a string. – atk May 25 '15 at 14:13
  • 1
    If you are interested in debugging a lambda not fixing your bug, you might be better off with a bug in the code you show rather than a bug outside this lambda. Showing irrelevant code and asking for help is at best a distraction from the question you are trying to ask – atk May 25 '15 at 14:15

2 Answers2

6

Make sure you include this option for javac when compiling your classes:

-g:lines,source,vars

The "-g" compiler option can be used to control how much debugging information should be generated into the class files (see documentation)

Here is a simple example with 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());
    }
}

Here is the 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)

As you can see the stacktrace at test.TestLambda.lambda$comparator2$1(TestLambda.java:20) points to the exact line of the source code.

Your IDE should be able to parse the stacktrace and decorate it with links clicking on which should bring you to the exact line in your sources (at least that's what IntelliJ IDEA does).

If you compile with -g:none the stacktrace will be different:

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)

Update:

Below is another example that is closer to the one provided in the question:

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

The only difference is that it uses raw type for List thus making possible using String comparator for list of Integers. The stacktrace indeed doesn't contain the line number since the exception happened during casting and not in our source code:

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)

A rule of thumb here is not to use raw types which in this case will make the debugging process easier (What is a raw type and why shouldn't we use it?). The compiler can help you here too: include this option for javac:

-Xlint:all

The compiler will warn you about raw types a lots of other things. Add another option:

-Werror

and the compiler will produce an error instead of warning (useful when using with CI servers to ensure high quality of the source code)

Community
  • 1
  • 1
medvedev1088
  • 3,645
  • 24
  • 42
  • It seems that if I don't explicit throw an exception I still can't find where is the lambda. And in the simple example that I have provided, it can't work. – Tony May 25 '15 at 14:06
  • 1
    @Tony Updated my answer with another example. Please see if it helps. – medvedev1088 May 25 '15 at 14:21
  • Thank you for your advice but in some GUI part of swing, you just can't use the specific type, which hide some problem. And what I want is to find bug easily by the stack trace. – Tony May 25 '15 at 14:25
  • 1
    Well, if you really need to use raw types then one option is to do a safe cast yourself instead of relying on javac do casts for you. For example before using Collections.sort() with raw list you explicitly cast it to List as shown in the answer to this question http://stackoverflow.com/questions/509076/how-do-i-address-unchecked-cast-warnings . In this case the exception happens in your code so the compiler can show you the line number as opposed to implicit cast. Otherwise you just have to accept it as the side effect of using raw types. – medvedev1088 May 25 '15 at 15:45
2

As far as I have tried and searched, you can't locate where is the lambda in Java 8.

The lambda here is a replacement of anonymous class but the replacement is invisible for JVM and that is why JVM can't locate the lambda.

Take Two simple comparators as examples:

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

Compiled from upper code example (remove some redundant lines):

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

An very important difference is that the second comparator has a class and NEW an instance, but the one with lambda just become a method which is INVOKEDYNAMIC.

And we find that compiler just compile a synthetic method to let JVM to invoke:

  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

So JVM is totally unaware of the existence of lambda. It just invoke a method when necessary and it obviously can't locate where is that lambda, so it has to show Unknown source.

Tony
  • 5,972
  • 2
  • 39
  • 58