Null悄悄溜走:使用Java破坏Scala Option

问候,哈勃!我提请您注意周五有关Java,Scala,疯狂的程序员和诺言的简短文章。




简单的观察有时会引出不太简单的问题。


例如,这里是一个简单的,甚至是琐碎的事实,它表明在Java中您可以扩展范围内的任何非最终类和任何接口。另一个也很简单的说法是,可以从Java代码中使用为JVM编译的Scala代码。


然而,这两个事实的结合使我感到奇怪:从Java的角度来看,任何类的行为如何?Java在Scala中是密封的,即 不能使用相对于其自身文件的外部代码进行扩展?



从艺术家的角度反编译Scala类。资料来源:https : //specmahina.ru/wp-content/uploads/2018/08/razobrannaya-benzopila.jpg


作为实验兔子,我参加了标准班Option通过将其输入到IntelliJ Idea内置的反编译器中,我们得到如下信息:


//  ,     
public abstract class Option 
implements IterableOnce, Product, Serializable {
    //   
    public abstract Object get();
    //    
}

但是,反编译后的代码将不是有效的Java代码- 例如,此方法类似于此处描述的问题


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 = {
    // snip
    tryMatch
  }

  private def tryDo[T](action: => T): Unit = {
    // snip
  }

  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 = {
    // snip
    tryMap
  }

  private def tryDo[T](action: => T): Unit = {
    // snip
  }

  private def tryMap(implicit opt: Option[String]): Unit = 
    tryDo(opt.map(_.length))
}

:


java.lang.NullPointerException

, map:


sealed abstract class Option[+A] /* extends ... */ {
  //   . ,  None -  .
  final def isEmpty: Boolean = this eq None
  //  ,       null.
  def get: A
  // , ,  map.
  @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 = {
    // snip
    tryContainsNull
  }

  private def tryDo[T](action: => T): Unit = {
    // snip
  }

  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.


实际上,本文所需要做的只是一个小实验,以揭示一个JVM上不同语言交互的细微差别。稍加思索的细微差别是显而易见的,但是-就我的口味而言,仍然很有趣。


All Articles