Note that there was no way to change Scala's Some (a)
to ʻaand
None to
null`.
It has become quite long, so if you are in a hurry, we recommend that you scan around the headings of each section.
How to use only the scala.Option
method that can be used from Java without using the ʻif` statement
option.fold(() -> null, some -> some)
--Those who have some knowledge of Java and have used Scala's [ʻOption` class] api-option --Java 8 and Scala 2.12 ――However, the general procedure should not change even if the version is different.
As I gradually rewrite a program of a reasonable scale written in Java to Scala, I want to change the part using null
to ʻOption`.
<!-Do you want to explain why you don't want to use null? ->
For example, consider porting a Java method to Scala that returns an extension from a filename string. This method returns the string after the dot if the filename contains a dot (.
), or null
if it does not.
FileName.java
public class FileName {
public static String getExtension(final String filename) {
final int dotIdx = filename.lastIndexOf('.');
if (dotIdx >= 0) {
return filename.substring(dotIdx);
} else {
return null;
}
}
}
An example of a program that uses this class is as follows.
Program.java
public class Program {
public static void main(final String[] args) {
printExtension("foo.txt");
printExtension("hosts");
}
public static void printExtension(final String filename) {
final String ext = FileName.getExtension(filename);
if (ext != null) {
System.out.println("\"" + filename + "\"The extension of\"" + ext + "\"is.");
} else {
System.out.println("\"" + filename + "\"Has no extension.");
}
}
}
//Execution result:
// "foo.txt"The extension of"txt"is.
// "hosts"Has no extension.
Let's rewrite the above FileName
class in Scala. I don't want to use null
, so the return type of getExtension
should be ʻOption [String]`.
FileName.scala
object FileName {
def getExtension(filename: String): Option[String] =
filename.lastIndexOf('.') match {
case dotIdx if dotIdx >= 0 =>
Some(filename.substring(dotIdx))
case _ =>
None
}
}
Since the return type of FileName.getExtension
has changed, Program.java
will not compile as it is. Porting Program
to Scala is the most legitimate way to do it, but here we want to handle the case where the amount of Java code is huge and it is not practical to port everything to Scala, so Program
is Consider using FileName.getExtension
somehow in Java.
The process of converting Some (a)
to ʻaand
None to
null` is ** Java **. This is the purpose of this article.
I think that it is a standard practice to create a helper method on the Scala side for this kind of conversion process, but I think that it may not be realistic due to the large number of methods to be ported, so here. I'll stick to ** Java side ** somehow (If you use Scala's macro-paradise, it will automatically generate a wrapper method that returns ʻA or
null instead of ʻOption [A]
. Maybe you can make an annotation. Please make someone).
Probably the most common sense hand.
ScalaCompat.java
import scala.Option;
public class ScalaCompat {
public static <A> A optionOrNull(final Option<A> o) {
if (o.isDefined())
return o.get();
else
return null;
}
}
--ʻO.isDefined () returns
true if ʻo
is Some (a)
, and returns false
if None
.
--ʻO.get () returns ʻa
if ʻo is
Some (a) and throws an exception if
None`.
So, first use ʻo.isDefined () to check if ʻo
has a value, and after finding out that it is Some (a)
, take out ʻa with ʻo.get
and If o
doesn't have a value, it returns null
without trying to retrieve it.
It's safe, but it has some drawbacks.
Having to create a new class causes the following troubles.
--Choose a package --Think about the class name --You must continue to use this class in the future. Otherwise code duplication will occur
I think it's a silly story, but from the perspective of maintaining the code, it's surprisingly annoying.
Also, you probably already have a library with similar classes, even if you didn't write it yourself, but I'm hesitant to increase the dependency just for this.
If you're doing a methodchain with code that abuses (or ignores, to be exact) null
, introducing a utility class like the one above will significantly reduce readability.
For example, consider the following code. Suppose all get
methods can return null
. (Image like java.util.Map <String, *>
)
person.get("account").get("balance").get("USD")
Replace this get
method with a method that returns ʻOption` and try using a utility class. In order to maintain the existing behavior, we dare to omit the null check that is originally required.
ScalaCompat.optionOrNull(ScalaCompat.optionOrNull(ScalaCompat.optionOrNull(person.get("account")).get("balance")).get("USD"))
Can you see how hard it is to read and write? "Check null
! "," Change the structure of the code! "," Port it to Scala! ", Etc., but in the reality of time constraints, other methods of the same class Due to the situation such as a large number, it is necessary to refactor "as Java" "without checking null
(= without changing the condition that NPE occurs), and the method under such a situation The static
methods of the chain and utility classes are the worst match.
It's not difficult, it's just a matter of using the ʻif` statement.
final Option<Account> account = person.get("account");
if (account.isDefined()) {
final Option<Balance> balance = account.get().get("balance");
if (balance.isDefined()) {
final Option<Double> usd = balance.get().get("USD");
if (usd.isDefined()) {
return usd;
} else {
return null;
}
}
}
throw new NullPointerException();
The code, which was originally one line, has expanded to 13 lines. In addition to the increased number of lines, it is also necessary to specify the type to assign to a local variable once. Scala has a for comprehension for these times, but unfortunately Java doesn't do that.
try
statementʻOption.get throws a
NoSuchElementExceptionfor
None`, so you can catch it and send an NPE instead.
try {
person.get("account").get().get("balance").get().get("USD").get();
} catch (final NoSuchElementException e) {
throw new NullPointerException();
}
It can be written relatively concisely. It looks fine if you can tolerate one line increasing to four lines. However, this approach has some sober but annoying problems. What if person.get ("account ")
itself throws a NoSuchElementException
instead of ʻOption.get ()`? The original code throws the exception as is, but this code also replaces it with an NPE. It catches exceptions that shouldn't be caught, which can secretly cause bugs.
I think it depends on the case ... In this article, all of these are considered impractical. Below, let's consider how to make ʻOption`` null "using the instance method of
scala.Option` ".
ʻOption has a method called ʻorNull
. This is a very custom method that returns ʻa if ʻOption
itself isSome (a)
and null
for None
, but unfortunately it is difficult to use from Java. You can see why by looking at the declaration of ʻOption.orNull`. According to the [Scala API Reference] api-option-ornull, the signature is
final def orNull[A1 >: A](implicit:ev<:<[Null,A1]): A1
The problem is this ʻimplicit argument. In a nutshell, this is used to prevent the type argument ʻA
from being used for types that cannot be null
, such as ʻInt (detailed description: [StackOverflow] [so-option]. -ornull]). You don't have to worry about it because the compiler automatically fills the ʻimplicit
argument for use in Scala, but in Java you have to fill it by hand, which is a very tedious task.
What if I had to actually fill it in by hand? Let's think about it because it's a big deal.
According to javap
, as of Scala-2.12.6, ʻOption.orNull` seems to be compiled into a method like this:
$ javap scala/Option.class | grep orNull
public final <A1> A1 orNull(scala.Predef$$less$colon$less<scala.runtime.Null$, A1>)
...
You can see that the ʻimplicitargument is compiled into just an argument. The type is
<: <, a member class of Scala's standard library, scala.Predef $, and a subclass of
Function1`.
[Scala standard library scala / Predef.scala] [api-predef- <: <] quotes only the main points
scala/Predef.scala
object Predef {
//Mystery parameter type(1)
sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
// <:<Only instance of(2)
private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
//Source of implicit value(3)
implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]
I will explain each point.
--(1)
: Definition of the type of mystery parameter (identity of implicit argument). It is no different from Function1 [From, To]
except that it mixins Serializable
.
--(2)
: (1)
is the sealed
class, so this is the only instance. If you look closely at the contents of def apply
, it is practically the same as ʻidentity. --
(3): ʻimplicit def
function that does not take a value parameter. Therefore, like the ʻimplicit val value, this value is used implicitly when passing the ʻimplicit
parameter.
Taken together, the implicit parameter implicitly passed by the Scala compiler when calling ʻOption.orNull is
(3) , and its contents are
(2) (cast), which is effectively In the end, it is ʻidentity
.
To be a little more specific, let's trace a program that calls # orNull
forNone: Option [X]
with any type X
.
val o = Option.empty[X]
val x: X
= o.orNull
//Since the expression type is X, X is selected for the orNull type argument A1.
// <:<Is reversal for type 1 arguments, Null<:Because it is X(Null <:< X) >: (X <:< X)Is.
// (X <:<X is null<:<It is a subclass of X.)
//Therefore Predef.$conforms[X]: X <:<X is selected for the implicit argument.(☆)
= o.orNull(Predef.$conforms[X])
= o.orNull(singleton_<:<.asInstanceOf[X <:< X])
// singleton_<:<Note that is essentially identity,
~= o.orNull(identity: X => X)
However, singleton_ <: <
is private [this]
, so if you want to specify it explicitly, you can specify Predef. $ Conforms [X]
. If you write this in Java
scala.Predef.<X>$conforms()
It means that.
To summarize the above, when calling ʻOption.orNull` in Java, it will be as follows.
final scala.Option<A> option = ...;
final A aOrNull = option.orNull(scala.Predef.<A>$conforms());
I see. But this doesn't compile. ʻA <: <B is rebellious about ʻA
because it's only in Scala and is treated as immutable in Java. The type of scala.Predef. <A> $ conforms ()
is scala.Predef $$ less $ common $ less <A, A>
when written in Java, but the implicit argument type is <A1> scala Since .Predef $$ less $ common $ less <scala.runtime.Null $, A1>
, both ʻA and ʻA1
must bescala.runtime.Null $
.
Therefore, it should be as follows.
final scala.Option<A> option = ...;
final A aOrNull = (A)(Object) option.orNull(scala.Predef.<scala.runtime.Null$>$conforms());
--By specifying scala.runtime.Null $
in the generics argument of $ conforms
, you can get through non-denaturation (hack).
--As a result, the return type of # orNull
will bescala.runtime.Null $
, so recast it to ʻA (hack). --Since it is a cast between unrelated types, we will use the double cast technique to cast to ʻObject
once (super hack).
If you rewrite Program.java
using this, it will be as follows.
Program.java
public class Program {
public static void main(final String[] args) {
printExtension("foo.txt");
printExtension("hosts");
}
public static void printExtension(final String filename) {
final String ext = (String)(Object) FileName.getExtension(filename).orNull(scala.Predef.<scala.runtime.Null$>$conforms());
if (ext != null) {
System.out.println("\"" + filename + "\"The extension of\"" + ext + "\"is.");
} else {
System.out.println("\"" + filename + "\"Has no extension.");
}
}
}
//Execution result:
// "foo.txt"The extension of"txt"is.
// "hosts"Has no extension.
I tried it with half a joke, but the result was more hacky than I expected. The reason this program works (becomes) as intended is that Predef.singleton_ <: <
is effectively ʻidentity` and has no compile-time or run-time type concerns.
In the first place, the implicit argument of ʻOption.orNull has no particular meaning in the value itself (it's just ʻidentity
), but in" whether or not the value exists ". For example, as a counterexample, if you try to call ʻOption [Int] .orNull, the compiler will look for a value of type
Null <: <Int. However, the only implicit value, ʻimplicit def $ conforms [A]: A <: <If you substitute ʻInt
for ʻA of A
, you get ʻInt <: <Int, but
Null <: IntDoes not hold, so
(Null <: <Int)>: (Int <: <Int)does not hold (the above
(☆) fails). Therefore, the implicit argument corresponding to ʻOption [Int] .orNull
cannot be found, and it is possible to make a ** compile-time ** error.
In Java (not explained, but in Scala), you can force the implicit argument to be passed manually by abusing the cast, but the compile-time check will not work.
If for some reason you absolutely must call ʻOption.orNull` from Java, it might be a good idea to try it as a last resort. I do not recommend it at all.
There are several methods of type ʻOption [A] => A or close to it (ʻor Null
is one of them), but the typical one is getOrElse
.
Looking at the declaration of ʻOption.getOrElse` in [API Reference] api-option-getorelse:
final def getOrElse[B >: A](default:=>B): B
Unlike ʻor Null, there is no implicit argument. The argument
default is the value returned instead if ʻOption
is None
, so it seems like you should specify null
.
TooBad_NPE.java
public class TooBad_NPE {
public static void main(final String[] args) {
final scala.Option<String> some = scala.Option.apply("foo"); // Some("foo")
final String foo = some.getOrElse(null);
System.out.println(foo); // foo
final scala.Option<String> none = scala.Option.apply(null); // None
final String shouldBeNull = none.getOrElse(null); // !! throw NullPointerException !!
System.out.println(shouldBeNull);
}
}
The compilation will pass. It also works as expected when ʻOption is
Some. However, if ʻOption
is None
, NPE will be sent.
The cause is still in the getOrElse
signature. If you look closely at the declaration, it says default:=>B
. The type of default
is B
, but it is passed by name (=>
). Arguments passed by name are special arguments, and the evaluation (execution) of the argument is done in the called method, not in the caller. Java doesn't have this feature, so it must have been compiled into something special! Let's take a look at javap
:
$ javap scala/Option.class | grep getOrElse
public final <B> B getOrElse(scala.Function0<B>)
So it turns out that the name-passing argument => B
is compiled intoscala.Function0 <B>
(that is, the zero-argument function () => B
). This means that passing null
to the argument default
of getOrElse [B](default:=>B)
means that ** null
is not B
, but Function0 [ B]
is **. Both B
andFunction0 <B>
are subclasses of ʻObject for Java, so they will compile. Since the "object that should tell you what to return in the case of
None "is
null, NPE will occur in the case of
None`.
… So it looks like you should write an object in Java that corresponds to () => null: Function0 [B]
.
Thanks to the "SAM (Single Abstract Method) conversion" introduced in Java8, Java lambda expressions are directly converted to scala.Function1
. So you can write in Java:
final scala.Option<String> some = scala.Option.apply("foo"); // Some("foo")
System.out.println(some.getOrElse(() -> null)); // foo
final scala.Option<String> none = scala.Option.apply(null); // None
System.out.println(none.getOrElse(() -> null)); // null
You can call getOrElse
almost like Scala. Thank you. It seems like we've finally achieved what we expected, but unfortunately there are still pitfalls. In fact, ʻOption.getOrElse` is not * type-safe in * Java.
TooBad_CCE.java
import java.util.Date;
public class TooBad_CCE {
public static void main(final String[] args) {
final scala.Option<String> some = scala.Option.apply("foo"); // Some("foo")
final String foo = some.getOrElse(() -> null);
System.out.println(foo); // foo
final scala.Option<String> none = scala.Option.apply(null); // None
final String shouldBeNull = none.getOrElse(() -> null);
System.out.println(shouldBeNull); // null
//Up to this point is OK
//The content of Option is Date, but the recipient is String
final scala.Option<Date> now = scala.Option.apply(new Date());
final String nowstr = now.getOrElse(() -> null); // !! throw ClassCastException !!
}
}
Since now
is of type ʻOption , the type of
now.getOrElse (...)should be
Date, but as mentioned above, even if it is received by another type, it will compile. I will end up. I get a
ClassCastException` at run time.
Why is this happening? Now let's take a closer look at Scala's API reference and javap output.
apidoc.scala
final def getOrElse[B >: A](default:=>B): B
javap.java
public final <B> B getOrElse(scala.Function0<B>)
There are differences in the constraints imposed on B
. In Scala [B>: A]
, that is, B
must be a superclass of ʻA, but in Java it's just
, that is,
B is ʻA
. You can choose an irrelevant type. In the TooBad_CCE.java
example, String
is selected for B
from the return type, but this is not a problem for the Java compiler. Therefore, it compiles, and the type is actually different, so an exception is thrown at runtime.
For the time being, it is possible to generate a compile error by explicitly specifying the generics argument.
final scala.Option<Date> now = scala.Option.apply(new Date());
final String nowstr_ct = now.<Date>getOrElse(() -> null); // compilation error
final String nowstr_rt = now.<String>getOrElse(() -> null); // !! throw ClassCastException !!
However, it is a lot of work, and if you mistakenly specify the recipient type instead of the ʻOption` object type, you will still get a run-time error, which will cause careless mistakes.
It turns out that getOrElse
works as expected, but loses type safety. It causes bugs that are difficult to find, so I would like to avoid it if possible.
ʻOption has a method called
fold`. From [API Reference] api-option-fold:
final def fold[B](ifEmpty:=>B)(f:(A)=>B): B
If ʻOption is
None, the evaluated value of ʻif Empty
is returned, and ifSome (a)
,f (a)
is returned. If you specify ʻidentity for
f, it behaves the same as
getOrElse. The point is that both ʻA
and B
appear in the second parameter section, so if you give a function with the type ʻA => A, you can get
B without explicitly specifying the type parameter. It can be fixed to ʻA
.
This method compiles into just a two-argument method. From javap
:
$ javap scala/Option.class | grep fold
public final <B> B fold(scala.Function0<B>, scala.Function1<A, B>);
So, it seems good to specify a function that returns null
as the first argument and a function that returns the argument as it is as the second argument. let's do it.
WorksAsIntended.java
import java.util.Date;
public class WorksAsIntended {
public static void main(final String[] args) {
final scala.Option<Date> now = scala.Option.apply(new Date());
// final String nowstr = now.fold(() -> null, some -> some); // compilation error
final Date nowdate = now.fold(() -> null, some -> some); // ok, now
final scala.Option<Date> none = scala.Option.apply(null);
final Date nulldate = none.fold(() -> null, some -> some); // ok, null
System.out.println(nowdate); //Current time
System.out.println(nulldate); // null
}
}
(You can use any name instead of some
, but I tried this name to make it clear that it handles the case of Some
.)
It's a bit long, but I was able to achieve a more type-safe way "using the instance method of scala.Option
".
If the Java compiler cannot do type inference, such as when passing it as an argument to an overloaded method, you need to explicitly specify the generics argument.
However, even in that case, if you specify the wrong generics argument, a compile error will occur, so type safety is maintained unlike the case of ʻOption.getOrElse (()-> null)`.
ExplicitGenParam.java
import java.util.Date;
public class ExplicitGenParam {
public static void main(final String[] args) {
final scala.Option<Date> now = scala.Option.apply(new Date());
println(now.fold(() -> null, some -> some)); // compilation error, ambiguous
println(now.<Date>fold(() -> null, some -> some)); // OK,Current time
println(now.<String>fold(() -> null, some -> some)); // compilation error, incompatible types
}
public static void println(final Date x) {
System.out.println(x);
}
public static void println(final String x) {
System.out.println(x);
}
}
How to change Scala's ʻOption [A] to ʻA
or null
in Java
option.fold(() -> null, some -> some)
Is recommended.
Let's use this to rewrite Program.java
in the" Background "section for FileName.scala
.
Program.java
public class Program {
public static void main(final String[] args) {
printExtension("foo.txt");
printExtension("hosts");
}
public static void printExtension(final String filename) {
final String ext = FileName.getExtension(filename).fold(() -> null, some -> some); //This has changed
if (ext != null) {
System.out.println("\"" + filename + "\"The extension of\"" + ext + "\"is.");
} else {
System.out.println("\"" + filename + "\"Has no extension.");
}
}
}
//Execution result:
// "foo.txt"The extension of"txt"is.
// "hosts"Has no extension.
I'm happy.
I have learned some lessons from this matter, so I will summarize them.
--ʻOption.orNull is virtually unusable from Java. --The name-passing argument (
=> B) is compiled into
scala.Function0 , so when passing
null, pass the lambda expression
()-> null. --Type safety may not be maintained when calling a method with Scala upper and lower bound type parameters (
[B>: A]`) from Java.
However, if you write the entire program in Scala, all the above work will be unnecessary, so everyone, please consider Scala when developing a new program.
Recommended Posts