-2

I was able to get this working with lambda's returning void and taking in 0 args using a hash table, see here -> Create lambda two dimensional array

Now, I'm trying to create a Runnable[] array, with lambda's in the index, and each lambda takes a String argument and returns a boolean.

Here is the code...

public class testLambdaWithPrimitiveType {
    private final String[] numArray = {"One", "Two", "Three"};
    private boolean numFound = false;

    testLambdaWithPrimitiveType(String num){
        setNumFound(num);
    }

    private void setNumFound(String num){
        Runnable[] runnableNumArray = {
                () -> isStringOne(num),
                () -> isStringTwo(num),
                () -> isStringThree(num)
        };

        for (int numChecked = 0; numChecked < runnableNumArray.length; numChecked++){
            if (runnableNumArray[numChecked].run(num)){
                this.numFound = true;
            }
        }
    }

    private boolean isNumFound(){return this.numFound;}

    private boolean isStringOne(String num){
        return num.equals(numArray[0]);
    }

    private boolean isStringTwo(String num){
        return num.equals(numArray[1]);
    }

    private boolean isStringThree(String num){
        return num.equals(numArray[2]);
    }

    public static void main(String[] args) {
        testLambdaWithPrimitiveType objectOne = new testLambdaWithPrimitiveType("One");
        testLambdaWithPrimitiveType objectTwo = new testLambdaWithPrimitiveType("Two");
        testLambdaWithPrimitiveType objectThree = new testLambdaWithPrimitiveType("Three");
        testLambdaWithPrimitiveType objectFour = new testLambdaWithPrimitiveType("Four");

        System.out.println(objectFour.isNumFound()); // false
        System.out.println(objectThree.isNumFound()); // true
        System.out.println(objectTwo.isNumFound()); // true
        System.out.println(objectOne.isNumFound()); // true
    }
}

It looks like the array gets initialized correctly, but when I try to call on the index if (runnableNumArray[numChecked].run(num)){, I get a compile error. Any idea why this is happening?

Fiddle Freak
  • 1,923
  • 5
  • 43
  • 83

3 Answers3

1

In the Java Language, Runnable instances cannot have parameters, lambdas which do have parameters are Callable instances instead. In other words, your question is inaccurate... you cannot create Runnable array that takes parameters, even though the compiler (wrongly) allows you to.

The error is that the Runnable interface has a run method with the signature,

public abstract void run()

Yet you are trying pass a parameter to that run method.

runnableNumArray[numChecked].run( num )

Removing the num parameter will still give you an error. This is because the run method returns void which is nothing (look again at the signature) but if statements require a boolean value to evaluate.

I am not sure what you are trying to achieve with this Array of lambdas. If you give me more info, I might be able to correct your code. As it stands though, it is unclear what you are expecting the Runnables to achieve.

Here is an example of using Callable instances to achieve something of what you wanted.

private void setNumFound(String num) throws Exception {
  Callable[] runnableNumArray = {
      () -> isStringOne( num ),
      () -> isStringTwo( num ),
      () -> isStringThree( num )
  };

  for ( int numChecked = 0; numChecked < runnableNumArray.length; numChecked++ ){
    if ( ( Boolean ) runnableNumArray[numChecked].call() ){
      this.numFound = true;
    }
  }
}
RudolphEst
  • 1,240
  • 13
  • 21
  • I'm trying to avoid using a long chain of `if/else if` statements. In my real application, I figured if I could set two `Runnable[]` arrays with the same size, and use the first one to return a bool under the if condition (basically what is in this question), the index can be used to execute the 2nd array[lambda]. – Fiddle Freak May 18 '17 at 18:41
  • It sounds like I may have to write a new class that overrites the `run()` with the Runnable object (unless Runnable has a method supporting a function run() that returns a primitive type). – Fiddle Freak May 18 '17 at 18:41
  • @FiddleFreak The lambda already overrides the `Runnable` interface. I think the usage of Runnable is where you are getting into trouble. Can you explain what you are trying to achieve with input ant output details, instead of specifics of the classes you are using? There is no `Runnable` method which allows any return type. You could use `Callable` though. – RudolphEst May 18 '17 at 18:45
  • I have added an example of changing the `Runnable` which does not return a value, to a `Callable`, which does return a value. You do not need to send a parameter to `call()` because you have already *captured* that variable in the `Callable` creation. Note that because generic arrays cannot be created, you will need to cast the result of `call()` to a boolean value to use in your `if` statement. – RudolphEst May 18 '17 at 18:54
  • In addition, the constructor needs to issue a `try/catch`, when calling this method. But +1 for being a workable solution also :) – Fiddle Freak May 18 '17 at 18:59
1

That is because Runnable has method void run(), with no parameters, and you're trying to call run(num). Since num has already been applied from the setNumFound() parameter, just call using run().

Of course, that leads to second error, i.e. method returns void, so the if (run()) doesn't work.

Seems you might want a method boolean xxx(String), so replace Runnable with Predicate<String>, and you can call it using test(num) instead of run().

That then leads to compilation error Cannot create a generic array of Predicate<String>, so you have to replace the array with a List.

You can then use method references instead.

private void setNumFound(String num){
    List<Predicate<String>> runnableNumList = Arrays.asList(
            this::isStringOne,
            this::isStringTwo,
            this::isStringThree
    );

    for (Predicate<String> runnableNum : runnableNumList){
        if (runnableNum.test(num)){
            this.numFound = true;
        }
    }
}
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Perfect this is exactly what I'm looking for. Didn't know about the Predicate library. Thanks a bunch :) – Fiddle Freak May 18 '17 at 18:46
  • In my real application I have to stick to the for loop (as the index number will tell me where the next Runnable array I need to execute), but it's good to know how to do this using a for-each loop too :) – Fiddle Freak May 18 '17 at 18:48
  • A `predicate` is a method returning `boolean`. The `java.util.function` package has a set of predicate interfaces for commonly used parameters: `Predicate` (an object parameter), `IntPredicate` (`int` parameter), `LongPredicate` (`long` parameter), `DoublePredicate` (`double` parameter), and `BiPredicate` (two object parameters). For any other type of parameter(s), you'll have to write your own interface. – Andreas May 18 '17 at 18:50
  • quick question, if the `isStringOne()` method is static, what would i use instead of `this::isStringOne`? – Fiddle Freak May 18 '17 at 18:54
  • 1
    @FiddleFreak Replace `this` with class name, e.g. `testLambdaWithPrimitiveType::isStringOne` --- *FYI:* Class name should start with uppercase letter. – Andreas May 18 '17 at 19:34
  • Perfect! Thanks for the info :) – Fiddle Freak May 18 '17 at 19:36
0
    private void setNumFound(String num){
    boolean[] a = new boolean[1];

    Runnable[] runnableNumArray = {
            () -> a[0] = isStringOne(num),
            () -> a[0] = isStringTwo(num),
            () -> a[0] = isStringThree(num)
    };

    for (Runnable r : runnableNumArray ) {
        r.run();
        if ( a[0] ) {
            this.numFound = true;
            break; }
    }
}

I rewrite your method and added boolean variable as array[1]. I think, so wrong write, in general situation you will get error: "local variables referenced from a lambda expression must be final or effectively final" - but its work in Java SE 8 (build 31).