Saudações, Habr! Trago à sua atenção um pequeno artigo de sexta-feira sobre Java, Scala, programadores malucos e promessas quebradas.
Às vezes, observações simples levam a perguntas não muito simples.
Aqui, por exemplo, é um fato simples e externo, talvez até trivial, que afirma que em Java você pode estender qualquer classe não final e qualquer interface no escopo. E outro, também bastante simples, dizendo que o código Scala compilado para a JVM pode ser usado a partir do código Java.
A combinação desses dois fatos, no entanto, me fez pensar: como uma classe se comportará do ponto de vista do Java, que em Scala está selado, ou seja, não pode ser expandido com código externo em relação ao seu próprio arquivo?
Classe Scala descompilada na visĂŁo do artista. Fonte: https://specmahina.ru/wp-content/uploads/2018/08/razobrannaya-benzopila.jpg
Como coelho experimental, participei da aula padrĂŁo Option
. Ao alimentá-lo com o descompilador incorporado no IntelliJ Idea, obtemos algo assim:
public abstract class Option
implements IterableOnce, Product, Serializable {
public abstract Object get();
}
O código descompilado, no entanto, não será um código Java válido - um problema semelhante ao descrito aqui , por exemplo, com este método:
public List toList() {
return (List)(
this.isEmpty()
? scala.collection.immutable.Nil..MODULE$
: new colon(this.get(), scala.collection.immutable.Nil..MODULE$)
);
}
MODULE$ , package object. , , Java , ?
, , ...
Java ( Maven), Scala provided- — , , , :
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.13.1</version>
<scope>provided</scope>
</dependency>
, scala.Option
. Idea, , Scala , sealed-, , , :
package hack;
public class Hacking<T> extends scala.Option<T> {
@Override
public T get() {
return null;
}
public int productArity() {
return 0;
}
public Object productElement(int n) {
return null;
}
public boolean canEqual(Object that) {
return false;
}
}
- , Option Product.
, — . mvn package
— , , jar-, , Java, , , .
, Scala.
… scala-
Scala (, SBT) — lib , , , ; build.sbt , Idea. ( ) :
import hack.Hacking
object Main {
def main(args: Array[String]): Unit = {
implicit val opt: Option[String] = new Hacking()
}
private def tryDo[T](action: => T): Unit = {
try {
println(action)
} catch {
case e: Throwable => println(e.toString)
}
}
}
implicit var
.
tryDo
— , : , , . call-by-name tryDo
, , .
, match
— , sealed class- ( , sealed-, ?)
object Main {
def main(args: Array[String]): Unit = {
tryMatch
}
private def tryDo[T](action: => T): Unit = {
}
private def tryMatch(implicit opt: Option[String]): Unit = tryDo {
opt match {
case Some(inner) => inner
case None => "None"
}
}
}
:
scala.MatchError: hack.Hacking
, : Option — sealed class, ( case Some case None), :
[warn] $PATH/Main.scala:22:5: match may not be exhaustive.
[warn] It would fail on the following input: None
[warn] opt match {
[warn] ^
, .
, - Option:
object Main {
def main(args: Array[String]): Unit = {
tryMap
}
private def tryDo[T](action: => T): Unit = {
}
private def tryMap(implicit opt: Option[String]): Unit =
tryDo(opt.map(_.length))
}
:
java.lang.NullPointerException
, map:
sealed abstract class Option[+A] /* extends ... */ {
final def isEmpty: Boolean = this eq None
def get: A
@inline final def map[B](f: A => B): Option[B] =
if (isEmpty) None else Some(f(this.get))
}
, :
- — None. , isEmpty false.
- , this.get, null.
- null , — length.
- length null NPE.
, NPE , Java ? (, , , ...)
:
object Main {
def main(args: Array[String]): Unit = {
tryContainsNull
}
private def tryDo[T](action: => T): Unit = {
}
private def tryContainsNull(implicit opt: Option[String]): Unit =
tryDo(opt.contains(null))
}
(, , null) , contains -Nullable . , , , Option false — null. ?
:
true
, , , contains map: !isEmpty && this.get == elem
.
, . , null , Option ( , , ) else match.
De fato, tudo o que este artigo era necessário era um pequeno experimento para revelar uma nuance da interação de diferentes idiomas em uma JVM. A nuance, com um pouco de reflexão, é óbvia, mas - para meu gosto, ainda é interessante.