Does Java 8 Support Closures?
Asked Answered
B

4

52

I'm confused. I thought Java 8 was going to emerge from the stone age and start supporting lambdas/closures. But when I try this:

public static void main(String[] args) {
    int number = 5;

    ObjectCallback callback = () -> {
        return (number = number + 1);
    };

    Object result = callback.Callback();
    System.out.println(result);
}

...it says that number should be effectively final. That's uh, not a closure I think. That just sounds like it's copying the environment by value, rather than by reference.

Bonus question!

Will Android support Java 8 features?

Bywoods answered 20/6, 2013 at 2:15 Comment(11)
docs.oracle.com/javase/tutorial/java/javaOO/… Look at this documentUnmentionable
Why oh why, Java. Why oh why.Bywoods
You do realize you are trying to access a method local variable from another class don't you?Barbaraanne
and they can keep their insanity to themselves. I dont miss var parameters either.Barbaraanne
A lambda is not a closure, it is just an anonymous function. A closure is a function (anonynous or not) using the context it was created in. See #221158. If you want to benefit from them with Java, you have some possibilities with Scala or Groovy. Java 8 is supporting lambdas docs.oracle.com/javase/tutorial/java/javaOO/…, not closuresNepotism
Java 8 is supporting lambdas not closures. Sure it does - in about the same sense as Java supports generics. See the accepted answer.Bywoods
That depends on what you think a closure is. In interpreted, effectively singlethreaded languages, that sort of mutable capture makes perfect sense, because what you're actually storing is effectively a pointer to the relevant environment / symbol table. That does not work well with compiled languages which are highly multithreaded. Ask yourself - what would capture of a mutable variable even mean in JVM bytecode terms? And how could you represent it?Sapphirine
Scala and C# both seem to be able to pull this off without any issues.Bywoods
@Barbaraanne there's no var parameter in c# you probably mean var variablesUnderlaid
@Fabio Marcolini - was not referring to C# regarding var parameters, I can understand why it is inferred that I did though.Barbaraanne
You cannot mutate the variable inside lambda as it's not thread safe. You can only use effective final variables inside lambda.. If you don't do any operation on the variable 'number'.. then you can use it. Like assign the value of variable number to a local variable inside lambda and then do the operations on that local variable.Croze
M
43

Why oh why, Java. Why oh why.

You would need to hold a long (private) discussion with the relevant Oracle Java team members for the true answer. (If they would be willing to talk to you ...)


But I suspect it is a combination of backwards compatibility and project resourcing constraints. And the fact that the current approach is "good enough" from a pragmatic perspective.

Implementing procedure contexts as first-class objects (i.e. closures) requires that the lifetime of certain local variables extends beyond the return of the declaring method call. That means that you cannot just put them on the stack. Instead you end up with a situation where some local variables have to be fields of an heap object. That means you need a new kind of hidden class OR fundamental changes to the JVM architecture.

While it is technically possible to implement this kind of thing, the Java language is not a "green field" language. A change of the nature that would needed to support "real closures" in Java would be difficult:

  • It would take a huge amount of effort from Oracle and 3rd party implementors to update all of the tool chains. (And we are not just talking about compilers. There are debuggers, profilers, obfuscators, bytecode engineering frameworks, persistence frameworks ...)

  • Then there is the risk that some of these changes would impact on backwards compatibility for the millions of existing deployed Java applications out there.

  • There is the potential impact on other languages, etc that leverage the JVM in some way. For example, Android depends on the JVM architecture / bytecode files as the "input language" for its Davlik tool-chain. There are language implementations for Python, Ruby and various functional languages that code generate for the JVM platform.


In short "real closures" in Java would be a big scary proposition for everyone concerned. The "closures for finals" hack is a pragmatic compromise that does work, and that is good enough in practice.

Lastly, there is always the possibility that the final restriction could be removed in a future edition. (I wouldn't hold my breath though ....)


Will android support Java-8 features?

That is impossible to answer unless someone has credible inside knowledge. And if they did, they would be crazy to reveal it here. Certainly Google have not announced support for Java 8.

But the good news is that Java 7 syntax extensions are now supported with KitKat and corresponding versions of Android Studio or Eclipse ADT.

Morvin answered 20/6, 2013 at 3:5 Comment(5)
"C# can do it ..." - C# / CLR was still pretty much a green-field language / platform when support for closures went in. It is amazing what you can do if you don't have to worry about breaking existing code, you have masses of resources ... and you can learn from other peoples' mistakes.Morvin
Re green-field languages and breaking changes, this is why C# has reified generics and Java does not. ;-)Greasy
@ChrisJester-Young - Yes. And support for tail-call optimization and a few other things.Morvin
@StephenC good post. But interesting to note JVM langauges such as Groovy and Scala provide scala.Freed
@MoreThanFive - Well yea ... but they don't have the same (business) constraints as Java does.Morvin
P
14

You will have to state your definition of "closure".

To me, a "closure" is something (a function or object or other thing that can be run in some way like having methods) that captures ("closes over") a local variable from its enclosing scope, and which can use that variable in its code, even when the function or object's method is run at a later time, including when the enclosing scope no longer exists. In different languages, it may be possible to capture a variable by value, or by reference, or both.

By this definition, Java anonymous classes (which have been there since Java 1.1) are closures, since they can refer to local variables from its enclosing scope.

Lambdas in Java 8 are basically a special case of anonymous classes (namely, an anonymous class which implements an interface with exactly one method (a "functional interface"), and which has no instance variables, and which does not refer to itself (using this explicitly or implicitly)). Any lambda can be re-written into an equivalent anonymous class expression. So what is said above also apply to lambdas.

That's uh, not a closure I think.

Well, you sir, have a messed up definition of "closure".

Plasticine answered 21/6, 2013 at 0:52 Comment(7)
By definition, a compiled program that accepts command line arguments would be a closure...Piecework
“…have always been from the first version of Java” is not correct as the very first version of Java did not have inner classes. They were introduced in Java 1.1, so in this regard, closures exist in Java since 1.1…Nitrosamine
Your definition of "close over" is "copy by value in anonymous class constructor and never access it in the closure"?Zoara
@doug65536: Anonymous classes can't declare constructors. Also, I don't know what you mean by "never access it in the closure" -- only outside variables that are actually used in the anonymous class/lambda are captured. Captured variables are not passed to any constructor. Yes, captured variables are copied by value when the anonymous class/lambda is created.Plasticine
@Plasticine Java fakes closures by copying final variables into an instance of an anonymous class - which I expressed as being done "in the constructor" (which isn't declared by the programmer). You can't modify the original variable in the outer scope, that's why they have to be final. In effect, java doesn't have closures at all. One may not notice the difference unless they have used real closures in a language that actually supports them.Zoara
@doug65536: I don't know how you are defining your closures, but to me, the fact that something containing code (whether function or object) can transparently use local variables from an outer local scope, even after that outer local scope has ended, means it's a closure. I don't see what whether you can assign to the variable in the inner or outer scope has to do with it. If you have a requirement of being able to assign, then it seems that languages like Haskell, OCaml, and Standard ML cannot have closures either because you cannot assign to any variables in those languages at all.Plasticine
@newacct, I am having to agree with doug65536 that Java is (badly) faking closures based on my own experimentation after uncovering a bug based on the expectation of a real closure. See my question for details (#47804999). It's very disappointing since the docs suggest otherwise (docs.oracle.com/javase/tutorial/java/javaOO/…).Foy
H
13

I think the final restriction has technical reasons. A lambda expression simply takes the value from the surrounding method context because the reference lives on the stack and will not survive the finishing of the method.

If you put the context's value into a reference, you can build a "real" closure:

import java.util.function.Supplier;

public class CreatingAClosure {

    public static void main(String[] args) {
        Supplier<Supplier<String>> mutterfunktion = () -> {
            int container[] = {0};
            return () -> {
                container[0]++;
                return "Ich esse " + container[0] + " Kuchen.";
            };
        };
        Supplier<String> essen = mutterfunktion.get();
        System.out.println(essen.get());
        System.out.println(essen.get());
        System.out.println(essen.get());
    }
}

Ausgabe:

Ich esse 1 Kuchen.
Ich esse 2 Kuchen.
Ich esse 3 Kuchen.

Instead of an array you can take any suitable instance of any object, because it lives on the heap and only the reference to this instance is kept (final) in the lambda expression.

In this case the value of container is enclosed into mutterfunktion. Every call to mutterfunktion creates a new referenced instance.

The value is not accessible from outside the function (which was very hard to build in Java 7 and before). Because of lambda expressions are implemented as method references, there are no inner classes involved in this example.

You could also define container in the method's context an you will be able to do changes outside the lambda:

public static void main(String[] args) {
    int container[] = {0};
    Supplier<String> essen = () -> {
        container[0]++;
        return "Ich esse " + container[0] + " Kuchen.";
    };
    System.out.println(essen.get());
    System.out.println(essen.get());
    container[0]++;
    System.out.println(essen.get());
}

Ausgabe:

Ich esse 1 Kuchen.
Ich esse 2 Kuchen.
Ich esse 4 Kuchen.

So the answer to your question will be "yes".

Hornback answered 9/10, 2014 at 11:55 Comment(1)
Instead of int container[] the use of java.util.concurrent.atomic.AtomicInteger would be more appropriate.Hornback
C
1

You can use final references to get around altering state of variables declared in an outer scope, but the result remains the same, the state of the closures outer scope is not kept and further alterations to the object referenced to (by a final reference) are seen in the closure.

@Test
public void clojureStateSnapshotTest() {
    Function wrapperFunc;
    wrapperFunc = (a) -> {
        // final reference
        final WrapLong outerScopeState = new WrapLong();

        outerScopeState.aLong = System.currentTimeMillis();
        System.out.println("outer scope state BEFORE: " + outerScopeState.aLong);

        Function closure = (b) -> {
            System.out.println("closure: " + outerScopeState.aLong);
            return b;
        };

        outerScopeState.aLong = System.currentTimeMillis();
        System.out.println("outer scope state AFTER: " + outerScopeState.aLong);

        // show correct snapshot state
        closure.apply(new Object());

        return a;
    };
    // init clojure
    wrapperFunc.apply(new Object());
}

public class WrapLong {
    public long aLong = 0;
}

but still fun...

Czechoslovak answered 13/2, 2014 at 17:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.