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();
}
};
-
\$\begingroup\$ Samples of how you use it would be appreciated. \$\endgroup\$Rahul Gopinath– Rahul Gopinath2012年06月03日 21:27:28 +00:00Commented 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\$missingfaktor– missingfaktor2012年06月03日 22:27:43 +00:00Commented 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\$kaoD– kaoD2012年06月03日 22:41:06 +00:00Commented 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\$missingfaktor– missingfaktor2012年06月03日 22:44:02 +00:00Commented 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\$missingfaktor– missingfaktor2012年06月03日 22:45:28 +00:00Commented Jun 3, 2012 at 22:45
2 Answers 2
Four notes:
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.)The constructor should check
null
inputs. (Effective Java 2nd Edition, Item 38: Check parameters for validity)You should use
append
instead ofString
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();
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.
-
\$\begingroup\$ 1. Indeed. Should I put it in a synchronized block? \$\endgroup\$missingfaktor– missingfaktor2012年06月04日 05:16:29 +00:00Commented Jun 4, 2012 at 5:16
-
\$\begingroup\$ 3. Actually, I was planning to get rid of
StringBuilder
. Java optimizes this stuff anyway. \$\endgroup\$missingfaktor– missingfaktor2012年06月04日 05:17:49 +00:00Commented 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\$missingfaktor– missingfaktor2012年06月04日 05:18:40 +00:00Commented Jun 4, 2012 at 5:18
-
\$\begingroup\$ Overall, a good advice. +1. \$\endgroup\$missingfaktor– missingfaktor2012年06月04日 05:18:50 +00:00Commented Jun 4, 2012 at 5:18
-
\$\begingroup\$ I misunderstood your 2nd bullet, so removed that comment. \$\endgroup\$missingfaktor– missingfaktor2012年06月04日 07:46:12 +00:00Commented Jun 4, 2012 at 7:46
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>()?
-
\$\begingroup\$ 1. The other
F{n}
abstract classes contain abstract methods, and therefore cannot be interfaces. So for the sake of consistency, I madeF0
an abstract class. But yes, I agree, it should really be an interface. \$\endgroup\$missingfaktor– missingfaktor2012年06月04日 05:08:35 +00:00Commented 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 withisInitialized
, and add anull
check ininit
. \$\endgroup\$missingfaktor– missingfaktor2012年06月04日 05:10:00 +00:00Commented 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\$missingfaktor– missingfaktor2012年06月04日 05:11:22 +00:00Commented 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\$missingfaktor– missingfaktor2012年06月04日 05:15:45 +00:00Commented Jun 4, 2012 at 5:15 -
\$\begingroup\$ Overall, a good advice. +1. \$\endgroup\$missingfaktor– missingfaktor2012年06月04日 05:15:55 +00:00Commented Jun 4, 2012 at 5:15