تسلل Null للأعلى: كسر خيار Scala باستخدام Java

تحياتي يا هبر! أوجه انتباهكم إلى مقالة قصيرة يوم الجمعة حول Java و Scala والمبرمجين المجانين والوعود المحطمة.




تؤدي الملاحظات البسيطة أحيانًا إلى أسئلة غير بسيطة للغاية.


هنا ، على سبيل المثال ، هي حقيقة بسيطة وخارجية ، وربما حتى تافهة تنص على أنه في Java يمكنك توسيع أي فئة غير نهائية وأي واجهة في النطاق. وآخر ، بسيط أيضًا ، يقول أن Scala code الذي تم تجميعه لـ JVM يمكن استخدامه من Java code.


ومع ذلك ، فإن الجمع بين هاتين الحقائقين جعلني أتساءل: كيف ستتصرف أي فئة من وجهة نظر جافا ، التي يتم إغلاقها في سكالا ، أي لا يمكن توسيعه برمز خارجي متعلق بالملف الخاص به؟



فئة سكالا مترجمة من وجهة نظر الفنان. المصدر: 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();
    //    
}

ومع ذلك ، لن يكون رمز Decompiled رمز 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