3
\$\begingroup\$

I have written following small utility to abstract away the lazy initialization logic. (I tend to use lazy variables quite often.)

Are there any issues in this implementation?

// File: F0.java
public abstract class F0<A> {
 public abstract A apply();
}
// File: Lazy.java
public class Lazy<A> {
 private boolean isInitialized = false;
 private A content;
 private final F0<A> init;
 private Lazy(F0<A> init) {
 this.init = init;
 }
 public static <A> Lazy<A> initializedAs(F0<A> init) {
 return new Lazy<A>(init);
 }
 @Override
 public String toString() {
 return this.get().toString();
 }
 public A get() {
 if (!isInitialized) {
 content = init.apply();
 isInitialized = true;
 }
 return content;
 }
 @Override
 public boolean equals(Object o) {
 if (this == o) {
 return true;
 }
 if (o == null || getClass() != o.getClass()) {
 return false;
 }
 Lazy lazy = (Lazy) o;
 Object c = this.get();
 if (c != null ? !c.equals(lazy.get()) : lazy.get() != null) {
 return false;
 }
 return true;
 }
 @Override
 public int hashCode() {
 return this.get().hashCode();
 }
}

Edit:

Here is an example usage from the current project I am working on.

final Prerequisite prerequisite = new Prerequisite() {
 private Lazy<String> minimumRequiredJavaVersion = Lazy.initializedAs(
 new F0<String>() {
 @Override
 public String apply() {
 return SystemProperties.isWindows() && SystemProperties.is64Bit() ? "1.7" : "1.6";
 }
 }
 );
 @Override
 public boolean isSatisfied() {
 return SystemProperties.javaVersionIsMinimum(minimumRequiredJavaVersion.get());
 }
 @Override
 public String failureMessage() {
 StringBuilder sb = new StringBuilder();
 sb.append("Minimum required Java version is: " + minimumRequiredJavaVersion.get() + "\n");
 sb.append("Your system has " + JAVA_SPECIFICATION_VERSION + ".");
 return sb.toString();
 }
};
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jun 3, 2012 at 21:03
\$\endgroup\$
6
  • \$\begingroup\$ Samples of how you use it would be appreciated. \$\endgroup\$ Commented Jun 3, 2012 at 21:27
  • \$\begingroup\$ @blufox, I added an example. Basically, the variable is not initialized until it is required. (And not initialized at all if it is not required.) After computing it once, it is cached and this cached value is returned on further accesses. \$\endgroup\$ Commented Jun 3, 2012 at 22:27
  • \$\begingroup\$ This has nothing to do with the question, but... do you know Scala? I found the language quite interesting (functional-oriented, so lazyness is part of the language), it runs in the JVM too and is completely interoperable with Java. \$\endgroup\$ Commented Jun 3, 2012 at 22:41
  • \$\begingroup\$ @kaoD, I have been a Scala enthusiast for more than 2 years, have been professionally working with it since past 1 year, and am one of the top answeres in Scala tag on stackoverflow (with a gold badge). :-) \$\endgroup\$ Commented Jun 3, 2012 at 22:44
  • \$\begingroup\$ @kaoD, the current project however is in Java. (Team decision.) So I am trying to replicate some abstractions from Scala that I find indispensable. \$\endgroup\$ Commented Jun 3, 2012 at 22:45

2 Answers 2

2
\$\begingroup\$

Four notes:

  1. It's not thread-safe. init.apply() could be called multiple times from different threads. (I don't know whether it is an issue or not.)

  2. The constructor should check null inputs. (Effective Java 2nd Edition, Item 38: Check parameters for validity)

  3. You should use append instead of String concatenation:

    StringBuilder sb = new StringBuilder();
    sb.append("Minimum required Java version is: ").append(minimumRequiredJavaVersion.get()).append("\n");
    sb.append("Your system has ").append(JAVA_SPECIFICATION_VERSION).append(".");
    return sb.toString();
    
  4. Instead of the anonymous Prerequisite inner class I'd use a named top-level class (for example, MinimumRequiredJavaVersionPrerequisite). I think it's easier to read, naming it helps readers to understand the purpose of the class.

answered Jun 3, 2012 at 23:49
\$\endgroup\$
7
  • \$\begingroup\$ 1. Indeed. Should I put it in a synchronized block? \$\endgroup\$ Commented Jun 4, 2012 at 5:16
  • \$\begingroup\$ 3. Actually, I was planning to get rid of StringBuilder. Java optimizes this stuff anyway. \$\endgroup\$ Commented Jun 4, 2012 at 5:17
  • \$\begingroup\$ 4. Personal matter of taste. The code follows a functional programming style, and therefore anonymous inner classes are abound. \$\endgroup\$ Commented Jun 4, 2012 at 5:18
  • \$\begingroup\$ Overall, a good advice. +1. \$\endgroup\$ Commented Jun 4, 2012 at 5:18
  • \$\begingroup\$ I misunderstood your 2nd bullet, so removed that comment. \$\endgroup\$ Commented Jun 4, 2012 at 7:46
2
\$\begingroup\$

I do not see any issues with your code, however, A few questions,

  • Why is F0 abstract? (why not an interface)?
  • Why do you need isInitialized? cant you null check the content? even if init could throw, there doesn't seem to be a difference.
  • What happens if the initialization results in an exception? Do you want to retry each time .get is called? or do you want to remember that it was unsuccessful?
  • I am also curious, why did you opt to use initializedAs rather than using just new Lazy<String>()?
answered Jun 4, 2012 at 0:52
\$\endgroup\$
12
  • \$\begingroup\$ 1. The other F{n} abstract classes contain abstract methods, and therefore cannot be interfaces. So for the sake of consistency, I made F0 an abstract class. But yes, I agree, it should really be an interface. \$\endgroup\$ Commented Jun 4, 2012 at 5:08
  • \$\begingroup\$ 2. To allow null as a valid value. In the retrospect, I cannot think of a case where I would require that. So perhaps, yes, I should do away with isInitialized, and add a null check in init. \$\endgroup\$ Commented Jun 4, 2012 at 5:10
  • \$\begingroup\$ 3. A very good point. I did not think about it. I think it would make sense to retry the initialization on each access if it failed on last access. \$\endgroup\$ Commented Jun 4, 2012 at 5:11
  • \$\begingroup\$ 4. For better type inference, and less redundancy (at use site). In case of direct constructor use, user has to supply the type parameter. In case of static methods, it can be inferred from the type of reference to which the object is being assigned. (This is in the same spirit as say Guava's Lists.newArrayList.) \$\endgroup\$ Commented Jun 4, 2012 at 5:15
  • \$\begingroup\$ Overall, a good advice. +1. \$\endgroup\$ Commented Jun 4, 2012 at 5:15

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.