Führen Sie Scala's Option.or Null in Java aus

Führen Sie Scala's Option.or Null in Java aus

Beachten Sie, dass es keine Möglichkeit gab, Scalas "Some (a)" in "a" und "None" in "null" zu ändern.

Es ist ziemlich lang geworden. Wenn Sie es eilig haben, empfehlen wir Ihnen, die Überschriften der einzelnen Abschnitte zu durchsuchen.

Fazit vorerst

Verwendung nur der Methode "scala.Option", die von Java aus verwendet werden kann, ohne die Anweisung "if" zu verwenden

option.fold(() -> null, some -> some)

Ziel

Hintergrund

Wenn ich ein in Java geschriebenes Programm von angemessener Größe Schritt für Schritt in Scala umschreibe, möchte ich den Teil mit "null" in "Option" ändern.

<! - Möchten Sie erklären, warum Sie null nicht verwenden möchten? ->

Ziehen Sie beispielsweise in Betracht, eine Java-Methode zu portieren, die eine Erweiterung von einer Dateinamenzeichenfolge an Scala zurückgibt. Diese Methode gibt die Zeichenfolge nach dem Punkt zurück, wenn der Dateiname einen Punkt (.) enthält, oder null, wenn dies nicht der Fall ist.

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;
        }
    }
}

Ein Beispiel für ein Programm, das diese Klasse verwendet, lautet wie folgt.

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 + "\"Erweiterung ist\"" + ext + "\"ist.");
        } else {
            System.out.println("\"" + filename + "\"Hat keine Erweiterung.");
        }
    }
}

//Ausführungsergebnis:
// "foo.txt"Erweiterung ist"txt"ist.
// "hosts"Hat keine Erweiterung.

Lassen Sie uns die obige FileName Klasse mit Scala umschreiben. Ich möchte nicht "null" verwenden, daher ist der Rückgabetyp von "getExtension" "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
    }
}

Da sich der Rückgabetyp von "FileName.getExtension" geändert hat, wird "Program.java" nicht so kompiliert, wie es ist. Das Portieren von Program nach Scala ist der legitimste Weg, dies zu tun, aber hier wollen wir den Fall behandeln, in dem die Menge an Java-Code sehr groß ist und es nicht realistisch ist, alles nach Scala zu portieren, also Program Erwägen Sie die Verwendung von "FileName.getExtension" in Java.

Politik

** Java ** zum Konvertieren von "Some (a)" in "a" und "None" in "null". Dies ist der Zweck dieses Artikels.

Ich denke, dass es eine Standardpraxis ist, auf der Scala-Seite eine Hilfsmethode für diese Art von Konvertierungsprozess zu erstellen, aber ich denke, dass dies aufgrund der großen Anzahl der zu portierenden Methoden möglicherweise nicht realistisch ist. Ich bleibe irgendwie bei der Java-Seite (Wenn Sie das Makro-Paradies von Scala verwenden, wird automatisch eine Wrapper-Methode generiert, die anstelle von Option [A] A oder null zurückgibt. Vielleicht können Sie eine Anmerkung machen (bitte machen Sie jemanden).

Sichere Hand: Gebrauchsklasse

Wahrscheinlich die häufigste 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 ()gibt true zurück, wenn o Some (a) ist, und gibt false zurück, wenn None. --o.get () gibt a zurück, wenn o Some (a) ist, und löst eine Ausnahme aus, wenn None.

Verwenden Sie also zuerst "o.isDefined ()", um zu überprüfen, ob "o" einen Wert hat, stellen Sie fest, dass es sich um "Some (a)" handelt, und verwenden Sie dann "o.get", um "a" und "abzurufen" Wenn okeinen Wert hat, gibt esnull` zurück, ohne zu versuchen, ihn abzurufen.

Es ist sicher, hat aber einige Nachteile.

a. Sie müssen eine neue Klasse erstellen.

Das Erstellen einer neuen Klasse verursacht die folgenden Probleme.

--Wählen Sie ein Paket

Ich denke, es ist eine dumme Geschichte, aber aus der Perspektive der Pflege des Codes ist es überraschend ärgerlich.

Außerdem haben Sie wahrscheinlich bereits eine Bibliothek mit ähnlichen Klassen, auch wenn Sie diese nicht selbst geschrieben haben, aber ich zögere, die Abhängigkeit nur dafür zu erhöhen.

b. Kann nicht als Instanzmethode aufgerufen werden.

Wenn Sie eine Methodenkette mit Code ausführen, der "null" missbraucht (oder ignoriert, um genau zu sein), wird durch die Einführung einer Dienstprogrammklasse wie der oben beschriebenen die Lesbarkeit erheblich beeinträchtigt.

Betrachten Sie beispielsweise den folgenden Code. Angenommen, alle get-Methoden können null zurückgeben. (Bild wie java.util.Map <String, *>)

person.get("account").get("balance").get("USD")

Ersetzen Sie diese get -Methode durch eine Methode, die Option zurückgibt, und versuchen Sie, die Utility-Klasse zu verwenden. Um die bestehende Operation beizubehalten, wagen wir es, die ursprünglich erforderliche Nullprüfung wegzulassen.

ScalaCompat.optionOrNull(ScalaCompat.optionOrNull(ScalaCompat.optionOrNull(person.get("account")).get("balance")).get("USD"))

Können Sie sehen, wie schwer es ist, zu lesen und zu schreiben? "Check null!", "Ändern Sie die Struktur des Codes!", "Portieren Sie es auf Scala!" Usw., aber in der Realität aus zeitlichen Gründen andere Methoden derselben Klasse Aufgrund der Situation wie einer großen Anzahl ist es notwendig, "als Java" umzugestalten, ohne "null" (= ohne die Bedingung zu ändern, dass NPE auftritt) und die Methode in einer solchen Situation zu überprüfen Die "statische" Methode der Ketten- und Utility-Klasse ist die schlechteste Übereinstimmung.

Variante 1: if Anweisung

Es ist nicht schwierig, es geht nur darum, die if-Anweisung zu verwenden.

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();

Der Code, der ursprünglich eine Zeile war, ist auf 13 Zeilen angewachsen. Neben der erhöhten Anzahl von Zeilen muss auch der Typ angegeben werden, um ihn einmal einer lokalen Variablen zuzuweisen. Scala hat ein Verständnis für diese Gelegenheiten, aber Java macht das leider nicht.

Variante 2: try Anweisung

Option.get löst NoSuchElementException für None aus, sodass Sie es abfangen und stattdessen NPE senden können.

try {
  person.get("account").get().get("balance").get().get("USD").get();
} catch (final NoSuchElementException e) {
  throw new NullPointerException();
}

Es kann relativ präzise geschrieben werden. Es sieht gut aus, wenn Sie eine Zeile tolerieren können, die auf vier Zeilen erhöht wird. Dieser Ansatz weist jedoch einige nüchterne, aber ärgerliche Probleme auf. Was ist, wenn "person.get (" account ")" selbst eine "NoSuchElementException" anstelle von "Option.get ()" auslöst? Der ursprüngliche Code löst die Ausnahme unverändert aus, aber dieser Code ersetzt sie auch durch NPE. Es fängt Ausnahmen ab, die nicht abgefangen werden sollten, was heimlich Fehler verursachen kann.

Ich denke, es hängt vom Fall ab ... In diesem Artikel werden alle diese als unpraktisch angesehen. Lassen Sie uns im Folgenden überlegen, wie Sie mit der Instanzmethode von "scala.Option" "Option" auf "null" setzen können.

Fehler 1: Option.orNull

Option hat eine Methode namens orNull. Dies ist eine sehr maßgeschneiderte Methode, die "a" zurückgibt, wenn "Option" selbst "Some (a)" und "null" für "None" ist, aber leider von Java aus schwierig zu verwenden ist. Sie können anhand der Deklaration "Option.orNull" nachlesen, warum. Laut [Scala API Reference] api-option-ornull lautet die Signatur

final def orNull[A1 >: A](implicit:ev<:<[Null,A1]): A1

Das Problem ist dieses "implizite" Argument. Kurz gesagt, dies wird verwendet, um zu verhindern, dass das Typargument "A" für Typen verwendet wird, die nicht "null" sein können, wie z. B. "Int" (detaillierte Beschreibung: [StackOverflow] [so-Option]. -ornull]). Sie müssen sich keine Sorgen machen, da der Compiler das implizite Argument für die Verwendung mit Scala automatisch ausfüllt. In Java müssen Sie es jedoch von Hand ausfüllen, was eine sehr mühsame Aufgabe ist.

Was wäre, wenn ich es tatsächlich von Hand ausfüllen müsste? Lassen Sie uns darüber nachdenken, weil es eine große Sache ist.

Laut javap scheint Option.orNull ab Scala-2.12.6 in die folgende Methode kompiliert zu sein:

$ javap scala/Option.class | grep orNull
  public final <A1> A1 orNull(scala.Predef$$less$colon$less<scala.runtime.Null$, A1>)
...

Sie können sehen, dass das implizite Argument nur zu einem Argument kompiliert wird. Der Typ ist <: <, eine Mitgliedsklasse von scala.Predef $, einer Standardbibliothek von Scala, und eine Unterklasse von Function1.

[Scala-Standardbibliothek scala / Predef.scala] [api-predef- <: <] zitiert nur die Hauptpunkte

scala/Predef.scala


object Predef {
  //Mystery-Parametertyp(1)
  sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
  // <:<Einzige Instanz von(2)
  private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
  //Quelle des impliziten Wertes(3)
  implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

Ich werde jeden Punkt erklären.

--(1): Definition des Typs des Mystery-Parameters (Identität des impliziten Arguments). Es unterscheidet sich nicht von "Function1 [From, To]", außer dass es "Serializable" einmischt. --(2):(1)ist die versiegelte Klasse, daher ist dies die einzige Instanz. Wenn Sie sich den Inhalt von "def apply" genau ansehen, ist dies praktisch dasselbe wie "identity". --(3): Eine implizite Def-Funktion, die keinen Wertparameter annimmt. Daher wird dieser Wert wie der Wert "impliziter Wert" implizit verwendet, wenn der Parameter "implizit" übergeben wird.

Zusammengenommen ist der implizite Parameter, den der Scala-Compiler beim Aufrufen von "Option.orNull" implizit übergibt, "(3)", und sein Inhalt ist "(2)" (cast), was effektiv ist Es bedeutet, dass es "Identität" ist.

Um etwas genauer zu sein, verfolgen wir ein Programm, das "# orNull" für "None: Option [X]" mit einem beliebigen Typ "X" aufruft.

val o = Option.empty[X]

val x: X
  = o.orNull
  //Da der Typ des Ausdrucks X ist, wird X für das Typargument A1 von orNull ausgewählt.
  // <:<Ist die Umkehrung für Argumente vom Typ 1, Null<:Weil es X ist(Null <:< X) >: (X <:< X)Ist.
  // (X <:<X ist Null<:<Es ist eine Unterklasse von X.)
  //Deshalb Predef.$conforms[X]: X <:<Für das implizite Argument wird X ausgewählt.(☆)
  = o.orNull(Predef.$conforms[X])
  = o.orNull(singleton_<:<.asInstanceOf[X <:< X])
  // singleton_<:<Beachten Sie, dass dies im Wesentlichen Identität ist.
  ~= o.orNull(identity: X => X)

"Singleton_ <: <" ist jedoch "private [this]". Wenn Sie es also explizit angeben möchten, können Sie "Predef. $ Conforms [X]" angeben. Wenn Sie dies in Java schreiben

scala.Predef.<X>$conforms()

Es bedeutet das.

Um das Obige zusammenzufassen: Wenn Sie Option.orNull in Java aufrufen, ist dies wie folgt.

final scala.Option<A> option = ...;
final A aOrNull = option.orNull(scala.Predef.<A>$conforms());

Das war's. Dies wird jedoch nicht kompiliert. A <: <B ist rebellisch gegen A, weil es nur in Scala ist und in Java als unveränderlich behandelt wird. Der Typ von "scala.Predef. $ entspricht ()" ist "scala.Predef $$ less $ common $ less <A, A>", wenn in Java geschrieben, aber der Typ des impliziten Arguments ist " scala" Da .Predef $$ less $ common $ less <scala.runtime.Null $, A1> , müssen sowohl A als auch A1``scala.runtime.Null $ `sein.

Daher sollte es wie folgt sein.

final scala.Option<A> option = ...;
final A aOrNull = (A)(Object) option.orNull(scala.Predef.<scala.runtime.Null$>$conforms());
  • Indem Sie im generischen Argument von "$ konform" "scala.runtime.Null $" angeben, können Sie durch Nicht-Degeneration (Hack) gelangen.
  • Infolgedessen lautet der Rückgabetyp von "# orNull" "scala.runtime.Null $", also setzen Sie ihn in "A" (Hack) um.
  • Da es sich um eine Besetzung zwischen nicht verwandten Typen handelt, werden wir die Doppelbesetzungstechnik verwenden, um einmal auf "Objekt" zu wirken (Super-Hack).

Wenn Sie Program.java damit neu schreiben, sieht es wie folgt aus.

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 + "\"Erweiterung ist\"" + ext + "\"ist.");
        } else {
            System.out.println("\"" + filename + "\"Hat keine Erweiterung.");
        }
    }
}

//Ausführungsergebnis:
// "foo.txt"Erweiterung ist"txt"ist.
// "hosts"Hat keine Erweiterung.

Ich habe es mit einem halben Witz versucht, aber das Ergebnis war hackiger als ich erwartet hatte. Der Grund, warum dieses Programm wie beabsichtigt funktioniert (wird), ist, dass "Predef.singleton_ <: <" effektiv "Identität" ist und die Typen zur Kompilierungs- und Laufzeit überhaupt nicht kennt.

Erstens hat das implizite Argument von "Option.orNull" keine besondere Bedeutung im Wert selbst (es ist nur "Identität"), sondern "ob der Wert existiert oder nicht". Wenn Sie beispielsweise als Gegenbeispiel versuchen, "Option [Int] .orNull" aufzurufen, sucht der Compiler nach einem Wert vom Typ "Null <: <Int". Wenn Sie jedoch "A" durch "Int" von "implizit def $" ersetzen, entspricht [A]: A <: <A ", was der einzige implizite Wert ist, erhalten Sie" Int <: <Int ", aber" Null <: Int " Gilt nicht, also gilt (Null <: <Int)>: (Int <: <Int) nicht (das obige (☆) schlägt fehl). Daher kann das implizite Argument, das "Option [Int] .orNull" entspricht, nicht gefunden werden, und es ist möglich, einen ** Kompilierungszeitfehler ** zu machen.

In Java (nicht erklärt, aber in Scala) können Sie die manuelle Übergabe des impliziten Arguments erzwingen, indem Sie die Umwandlung missbrauchen. Die Überprüfung der Kompilierungszeit funktioniert jedoch nicht.

Wenn Sie aus irgendeinem Grund "Option.orNull" unbedingt von Java aus aufrufen müssen, ist es möglicherweise eine gute Idee, es als letzten Ausweg zu versuchen. Ich empfehle es überhaupt nicht.

Fehler 2: Option.getOrElse (null)

Es gibt verschiedene Methoden vom Typ "Option [A] => A" oder in der Nähe davon ("orNull" ist eine davon), aber die typische ist "getOrElse".

Betrachten Sie die Deklaration von Option.getOrElse in [API-Referenz] api-option-getorelse:

final def getOrElse[B >: A](default:=>B): B

Im Gegensatz zu "orNull" hat es kein implizites Argument. Das Argument "default" ist der Wert, der stattdessen zurückgegeben wird, wenn "Option" "None" ist. Sie sollten also anscheinend "null" angeben.

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);
    }
}

Die Zusammenstellung wird bestanden. Es funktioniert auch wie erwartet, wenn "Option" "Some" ist. Wenn jedoch "Option" "Keine" ist, wird NPE gesendet.

Die Ursache liegt immer noch in der getOrElse-Signatur. Wenn Sie sich die Deklaration genau ansehen, steht dort "default:=>B". Der Typ von "Standard" ist "B", wird jedoch als Name ("=>") übergeben. Vom Namen übergebene Argumente sind spezielle Argumente, und die Auswertung (Ausführung) des Arguments erfolgt in der aufgerufenen Methode, nicht im Aufrufer. Java hat diese Funktion nicht, daher muss sie zu etwas Besonderem kompiliert worden sein! Werfen wir einen Blick auf javap:

$ javap scala/Option.class | grep getOrElse
  public final <B> B getOrElse(scala.Function0<B>)

Es stellt sich also heraus, dass das Pass-by-Name-Argument => B in scala.Function0 <B> kompiliert wird (dh die Nullargumentfunktion () => B). Dies bedeutet, dass die Übergabe von "null" an das Argument "default" von "getOrElse [B](Standard: => B)" bedeutet, dass ** "null" nicht "B" ist, sondern "Function0 [ B] ist **. Sowohl B als auch Function0 sind Unterklassen von Object` für Java, daher werden sie kompiliert. Da das "Objekt, das Ihnen sagen soll, was im Fall von" Keine "zurückzugeben ist" "Null" ist, tritt NPE im Fall von "Keine" auf.

... bedeutet, dass Sie das Objekt, das () => null: Function0 [B] entspricht, in Java schreiben sollten.

Fehler 3: Option.getOrElse (() -> null)

Dank der in Java8 eingeführten "SAM-Konvertierung (Single Abstract Method)" werden Java-Lambda-Ausdrücke direkt in "scala.Function1" konvertiert. So können Sie in Java schreiben:

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

Sie können getOrElse fast wie Scala aufrufen. Vielen Dank. Es scheint, als hätten wir endlich das erreicht, was wir erwartet hatten, aber leider gibt es immer noch Fallstricke. Tatsächlich ist Option.getOrElse in * Java nicht * typsicher.

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
        //Bis zu diesem Punkt ist OK

        //Der Inhalt von Option ist Datum, aber der Empfänger ist String
        final scala.Option<Date> now = scala.Option.apply(new Date());
        final String nowstr = now.getOrElse(() -> null);              // !! throw ClassCastException !!
    }
}

Da "now" vom Typ "Option " ist, sollte der Typ "now.getOrElse (...)" "Date" sein, aber wie oben erwähnt, wird es kompiliert, selbst wenn es von einem anderen Typ empfangen wird. Ich werde am Ende. Ich bekomme zur Laufzeit eine "ClassCastException".

Warum passiert das? Schauen wir uns nun die API-Referenz und die Javap-Ausgabe von Scala genauer an.

apidoc.scala


final def getOrElse[B >: A](default:=>B): B

javap.java


public final <B> B getOrElse(scala.Function0<B>)

Es gibt einen Unterschied in den Einschränkungen, die "B" auferlegt werden. In Scala "[B>: A]" muss "B" eine Oberklasse von "A" sein, in Java jedoch nur "", dh "B" ist "A" Sie können einen irrelevanten Typ auswählen. Im Beispiel "TooBad_CCE.java" wird "String" für "B" aus dem Rückgabetyp ausgewählt, dies ist jedoch für den Java-Compiler kein Problem. Daher wird es kompiliert und der Typ ist tatsächlich unterschiedlich, sodass zur Laufzeit eine Ausnahme auftritt.

Derzeit ist es möglich, einen Kompilierungsfehler durch explizite Angabe des generischen Arguments zu generieren.

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 !!

Es ist jedoch eine Menge Arbeit, und wenn Sie fälschlicherweise den Empfängertyp anstelle des Objekttyps "Option" angeben, wird immer noch ein Laufzeitfehler angezeigt, der zu unachtsamen Fehlern führt.

Es stellt sich heraus, dass getOrElse wie erwartet funktioniert, aber die Typensicherheit verliert. Es verursacht schwer zu findende Fehler und sollte nach Möglichkeit vermieden werden.

Richtige Antwort: Option.fold (() -> null, some-> some)

Option hat eine Methode namens fold. Aus [API-Referenz] api-option-fold:

final def fold[B](ifEmpty:=>B)(f:(A)=>B): B

Wenn "Option" "Keine" ist, wird der ausgewertete Wert von "ifEmpty" zurückgegeben, und wenn "Some (a)", "f (a)" zurückgegeben wird. Wenn Sie "Identität" für "f" angeben, verhält es sich genauso wie "getOrElse". Der Punkt ist, dass sowohl "A" als auch "B" im zweiten Parameterabschnitt erscheinen. Wenn Sie also eine Funktion mit dem Typ "A => A" angeben, können Sie "B" angeben, ohne den Typparameter explizit anzugeben. Es kann auf "A" festgelegt werden.

$ javap scala/Option.class | grep fold
  public final <B> B fold(scala.Function0<B>, scala.Function1<A, B>);

Es scheint also gut, eine Funktion anzugeben, die als erstes Argument "null" zurückgibt, und eine Funktion, die das Argument so zurückgibt, wie es als zweites Argument ist. Machen wir das.

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);   //Aktuelle Uhrzeit
        System.out.println(nulldate);  // null
    }
}

(Sie können einen beliebigen Namen anstelle von "some" verwenden, aber ich habe diesen Namen versucht, um zu verdeutlichen, dass er den Fall von "some" behandelt.)

Es ist ein bisschen lang, aber ich konnte eine typsicherere Methode "mit der Instanzmethode" scala.Option "" erreichen.

Nebenbei: Wenn die Typinferenz fehlschlägt

Wenn der Java-Compiler keine Typinferenz durchführen kann, z. B. wenn er als Argument an eine überladene Methode übergeben wird, müssen Sie das generische Argument explizit angeben.

Selbst in diesem Fall bleibt die Typensicherheit jedoch im Gegensatz zu "Option.getOrElse (() -> null)" erhalten, da ein Kompilierungsfehler auftritt, wenn das falsche generische Argument angegeben wird.

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,Aktuelle Uhrzeit
        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);
    }
}

Fazit

So ändern Sie Scalas "Option [A]" in Java in "A" oder "null"

option.fold(() -> null, some -> some)

Ist empfohlen.

Verwenden wir dies, um Program.java im Abschnitt" Hintergrund "für FileName.scala neu zu schreiben.

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); //Das hat sich geändert
        if (ext != null) {
            System.out.println("\"" + filename + "\"Erweiterung ist\"" + ext + "\"ist.");
        } else {
            System.out.println("\"" + filename + "\"Hat keine Erweiterung.");
        }
    }
}

//Ausführungsergebnis:
// "foo.txt"Erweiterung ist"txt"ist.
// "hosts"Hat keine Erweiterung.

Ich bin glücklich.

Lektion

Ich habe einige Lektionen daraus gelernt, also werde ich sie zusammenfassen.

--Option.orNull ist für Java praktisch unbrauchbar.

  • Das Argument für die Namensübergabe (=> B) wird in scala.Function0 <B> kompiliert. Wenn Sie also null übergeben, übergeben Sie den Lambda-Ausdruck () -> null.
  • Wenn Sie eine Methode mit Typparametern der oberen und unteren Schranke ([B>: A]) von Scala aus Java aufrufen, wird die Typensicherheit möglicherweise nicht aufrechterhalten.

Wenn Sie jedoch das gesamte Programm in Scala schreiben, sind alle oben genannten Arbeiten nicht erforderlich. Daher sollten Sie bei der Entwicklung eines neuen Programms Scala berücksichtigen.

Recommended Posts