Kapitel 1. Aufgaben in der Befehlszeile
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Höchstwahrscheinlich wird einer der ersten Schritte auf deiner Reise mit Scala 3 die Arbeit an der Kommandozeile sein. Nachdem du Scala wie in "Scala installieren" beschrieben installiert hast , möchtest du vielleicht die REPL - ScalasRead/Eval/Print/Loop - starten, indem du scala
in die Kommandozeile deines Betriebssystems eingibst. Oder du möchtest ein kleines "Hello, world"-Projekt aus einer Datei erstellen und es dann kompilieren und ausführen. Da diese Kommandozeilenaufgaben für viele Menschen der Einstieg in die Arbeit mit Scala sind, werden sie hier zuerst behandelt.
Die REPL ist eine Kommandozeilen-Shell. Sie ist eine Spielwiese, auf der du kleine Tests durchführen kannst, um zu sehen, wie Scala und die Bibliotheken von Drittanbietern funktionieren. Wenn du mit der JShell von Java, der irb
von Ruby, der Python-Shell oder IPython oder der ghci
von Haskell vertraut bist, ähnelt die REPL von Scala all diesen Programmen. Wie in Abbildung 1-1 zu sehen ist, startest du die REPL, indem du scala
in die Befehlszeile deines Betriebssystems eingibst und dann deine Scala-Ausdrücke eingibst.
Wann immer du Scala-Code testen willst, ist die REPL eine großartige Spielumgebung. Es ist nicht nötig, ein komplettes Projekt zu erstellen - du kannst deinen Testcode einfach in die REPL einfügen und damit experimentieren, bis du weißt, dass er funktioniert. Weil die REPL ein so wichtiges Werkzeug ist, werden ihre wichtigsten Funktionen in den ersten beiden Rezepten dieses Kapitels vorgestellt.
Die REPL ist zwar großartig, aber sie ist nicht das einzige Spiel in der Stadt. Die Ammonite REPL wurde ursprünglich für Scala 2 entwickelt und hatte viel mehr Funktionen als die Scala 2 REPL, darunter:
-
Die Möglichkeit, Code aus GitHub- und Maven-Repositories zu importieren
-
Die Möglichkeit, Sitzungen zu speichern und wiederherzustellen
-
Hübsch gedruckte Ausgabe
-
Mehrzeilige Bearbeitung
Zum Zeitpunkt der Erstellung dieses Artikels wird Ammonite noch auf Scala 3 portiert, aber viele wichtige Funktionen funktionieren bereits. In Rezept 1.3 findest du Beispiele dafür, wie du diese Funktionen nutzen kannst.
Wenn du Scala-Projekte bauen musst, verwendest du normalerweise ein Build-Tool wie sbt, das in Kapitel 17 vorgestellt wird. Wenn du jedoch eine kleine Scala-Anwendung kompilieren und ausführen möchtest, z. B. eine, die nur aus ein oder zwei Dateien besteht, kannst du deinen Code mit dem Befehl scalac
kompilieren und mit scala
ausführen, genau wie du es in Java mit den Befehlen javac
und java
machst. Dieser Prozess wird in Rezept 1.4 demonstriert. Danach zeigt Rezept 1.6, wie du Anwendungen, die du als JAR-Datei verpackst, mit den Befehlen java
oder scala
ausführen kannst.
1.1 Erste Schritte mit der Scala REPL
Lösung
Wenn du REPL-Umgebungen in Sprachen wie Java, Python, Ruby und Haskell benutzt hast, wird dir die Scala REPL sehr vertraut sein. Um die REPL zu starten, gibst du in der Befehlszeile deines Betriebssystems scala
ein. Wenn die REPL startet, siehst du möglicherweise eine erste Meldung, gefolgt von einer Eingabeaufforderung scala>
:
$ scala Welcome to Scala 3.0 Type in expressions for evaluation. Or try :help. scala> _
Die Eingabeaufforderung zeigt an, dass du jetzt die Scala REPL verwendest. In der REPL-Umgebung kannst du alle Arten von Experimenten und Ausdrücken ausprobieren:
scala> val x = 1 x: Int = 1 scala> val y = 2 y: Int = 2 scala> x + y res0: Int = 3 scala> val x = List(1, 2, 3) x: List[Int] = List(1, 2, 3) scala> x.sum res1: Int = 6
Wie in diesen Beispielen gezeigt:
-
Nachdem du deinen Befehl eingegeben hast, zeigt die REPL-Ausgabe das Ergebnis deines Ausdrucks an, einschließlich der Informationen zum Datentyp.
-
Wenn du keinen Variablennamen angibst, wie im dritten Beispiel, erstellt das REPL eine eigene Variable, die mit
res0
beginnt, dannres1
usw. Du kannst diese Variablennamen so verwenden, als ob du sie selbst erstellt hättest:
scala> res1.getClass res2: Class[Int] = int scala> res1 + 2 res3: Int = 8
Sowohl Anfänger als auch erfahrene Entwickler schreiben jeden Tag Code in der REPL, um schnell zu sehen, wie Scala-Funktionen und ihre eigenen Algorithmen funktionieren.
Registerkarte Abschluss
Es gibt ein paar einfache Tricks, mit denen du die REPL effektiver nutzen kannst. Ein Trick ist die Verwendung der Tabulatorvervollständigung, um die Methoden zu sehen, die für ein Objekt verfügbar sind. Um zu sehen, wie die Tabulatorvervollständigung funktioniert, gibst du die Zahl 1
ein, dann eine Dezimalzahl und drückst dann die Tabulatortaste. Die REPL zeigt daraufhin die Dutzenden von Methoden an, die für eine Instanz von Int
verfügbar sind:
scala> 1. != finalize round ## floatValue self % floor shortValue & formatted sign * getClass signum many more here ...
Du kannst die Liste der angezeigten Methoden auch einschränken, indem du den ersten Teil eines Methodennamens eingibst und dann die Tabulatortaste drückst. Wenn du z. B. alle Methoden auf List
sehen willst, gibst du List(1).
ein und drückst dann die Tabulatortaste, um über zweihundert Methoden zu sehen. Wenn du dich aber nur für die Methoden auf List
interessierst, die mit den Zeichen to
beginnen, gibst du List(1).to
ein und drückst dann die Tabulatortaste, und die Ausgabe wird auf diese Methoden reduziert:
scala> List(1).to to toIndexedSeq toList toSet toTraversable toArray toIterable toMap toStream toVector toBuffer toIterator toSeq toString
Diskussion
Ich benutze die REPL, um viele kleine Experimente zu erstellen, und sie hilft mir auch, einige Typkonvertierungen zu verstehen, die Scala automatisch durchführt. Als ich zum Beispiel anfing, mit Scala zu arbeiten und den folgenden Code in die REPL eintippte, wusste ich nicht, welchen Typ die Variable x
hat:
scala> val x = (3, "Three", 3.0) val x: (Int, String, Double) = (3,Three,3.0)
Mit der REPL ist es einfach, Tests wie diesen auszuführen und dann getClass
für eine Variable aufzurufen, um ihren Typ zu sehen:
scala> x.getClass val res0: Class[? <: (Int, String, Double)] = class scala.Tuple3
Obwohl ein Teil dieser Ergebniszeile schwer zu lesen ist, wenn du zum ersten Mal mit Scala arbeitest, lässt dich der Text auf der rechten Seite von =
wissen, dass der Typ eine Tuple3
Klasse ist.
Du kannst auch den REPL-Befehl :type
verwenden, um ähnliche Informationen zu erhalten, allerdings wird der Name Tuple3
derzeit nicht angezeigt:
scala> :type x (Int, String, Double)
In vielen anderen Fällen ist es jedoch hilfreich:
scala> :type 1 + 1.1 Double scala> :type List(1,2,3).map(_ * 2.5) List[Double]
Auch wenn dies einfache Beispiele sind, wirst du feststellen, dass die REPL sehr hilfreich ist, wenn du mit komplizierterem Code und Bibliotheken arbeitest, mit denen du nicht vertraut bist.
Starten der REPL innerhalb von sbt
Du kannst eine Scala REPL-Sitzung auch aus der sbt-Shell heraus starten. Wie in Rezept 17.5, "Andere sbt-Befehle verstehen", gezeigt , startest du einfach die sbt-Shell innerhalb eines sbt-Projekts:
$ sbt MyProject> _
Verwende dann entweder den console
oder den consoleQuick
Befehl von dort aus:
MyProject> console scala> _
Der Befehl console
kompiliert die Quellcodedateien im Projekt, legt sie auf den Klassenpfad und startet die REPL. Der Befehl consoleQuick
startet die REPL mit den Projektabhängigkeiten auf dem Klassenpfad, aber ohne die Projektquellcodedateien zu kompilieren. Diese zweite Option ist nützlich, wenn sich dein Code nicht kompilieren lässt oder wenn du einen Testcode mit deinen Abhängigkeiten (Bibliotheken) ausprobieren möchtest.
Siehe auch
Wenn dir die Idee einer REPL-Umgebung gefällt, du aber Alternativen zur Standard-REPL ausprobieren möchtest, gibt es einige tolle kostenlose Alternativen:
-
Die Ammonite REPL hat mehr Funktionen als die REPL und wird in Rezept 1.3 demonstriert.
-
Scastie ist eine webbasierte Alternative zur REPL, die sbt-Optionen unterstützt und mit der du externe Bibliotheken zu deiner Umgebung hinzufügen kannst.
-
ScalaFiddle ist auch eine webbasierte Alternative.
-
Die IDEs IntelliJ IDEA und Visual Studio Code (VS Code) haben beide Arbeitsblätter, die der REPL ähnlich sind.
1.2 Quellcode und JAR-Dateien in die REPL laden
Lösung
Verwende den Befehl :load
, um Quellcodedateien in die REPL-Umgebung einzulesen. Ein Beispiel: Dieser Code befindet sich in einer Datei namens Person.scala in einem Unterverzeichnis namensmodels:
class
Person
(
val
name
:
String
):
override
def
toString
=
name
kannst du den Quellcode wie folgt in die laufende REPL-Umgebung laden:
scala> :load models/Person.scala // defined class Person
Nachdem der Code in die REPL geladen wurde, kannst du eine neue Person
Instanz erstellen:
scala> val p = Person("Kenny") val p: Person = Kenny
Beachte jedoch, dass, wenn dein Quellcode eine package
Deklaration enthält:
// Dog.scala file
package
animals
class
Dog
(
val
name
:
String
)
wird der Befehl :load
fehlschlagen:
scala> :load Dog.scala 1 |package foo |^^^ |Illegal start of statement
Quellcodedateien können in der REPL keine Pakete verwenden. Daher musst du sie in Situationen wie dieser in eine JAR-Datei kompilieren und sie dann in den Klassenpfad aufnehmen, wenn du die REPL startest. So verwende ich zum Beispiel die Version 0.2.0 meiner Simple Test Library mit der Scala 3 REPL:
// start the repl like this $ scala -cp simpletest_3.0.0-0.2.0.jar scala> import com.alvinalexander.simpletest.SimpleTest.* scala> isTrue(1 == 1) true
Zum Zeitpunkt der Erstellung dieses Artikels kannst du einer bereits laufenden REPL-Sitzung keine JARs hinzufügen, aber diese Funktion könnte in Zukunft hinzugefügt werden.
Diskussion
Gut zu wissen ist auch, dass kompilierte Klassendateien im aktuellen Verzeichnis automatisch in die REPL geladen werden. Wenn du zum Beispiel diesen Code in eine Datei mit dem Namen Cat.scala schreibst und ihn dann mit scalac
kompilierst, wird eine Cat.class-Datei erstellt:
case
class
Cat
(
name
:
String
)
Wenn du die REPL in demselben Verzeichnis wie die Klassendatei startest, kannst du eine neue Cat
erstellen:
scala> Cat("Morris") val res0: Cat = Cat(Morris)
Auf Unix-Systemen kannst du diese Technik verwenden, um deine REPL-Umgebung anzupassen. Befolge dazu die folgenden Schritte:
-
Erstelle in deinem Home-Verzeichnis ein Unterverzeichnis namens repl. In meinem Fall lege ich dieses Verzeichnis als /Users/al/repl an. (Du kannst dieses Verzeichnis beliebig benennen.)
-
Lege alle *.class-Dateien, die du willst, in diesem Verzeichnis ab.
-
Erstelle einen Alias oder ein Shell-Skript, mit dem du die REPL in diesem Verzeichnis starten kannst.
Auf meinem System habe ich eine Datei mit dem Namen Repl.scala in meinem ~/repl-Verzeichnis angelegt, die diesenInhalt hat:
import
sys
.
process
.
*
def
clear
=
"clear"
.
!
def
cmd
(
cmd
:
String
)
=
cmd
.
!!
def
ls
(
dir
:
String
)
=
println
(
cmd
(
s"ls -al
$
dir
"
))
def
help
=
println
(
"\n=== MY CONFIG ==="
)
"cat /Users/Al/repl/Repl.scala"
.
!
case
class
Person
(
name
:
String
)
val
nums
=
List
(
1
,
2
,
3
)
Dann kompiliere ich diesen Code mit scalac
, um die Klassendateien in diesem Verzeichnis zu erstellen. Dann erstelle und verwende ich diesen Alias, um die REPL zu starten:
alias
repl
=
"cd ~/repl; scala; cd -"
Dieser Alias verschiebt mich in das Verzeichnis ~/repl, startet die REPL und kehrt dann in mein aktuelles Verzeichnis zurück, wenn ich die REPL verlasse.
Eine andere Möglichkeit ist, ein Shell-Skript mit dem Namen repl
zu erstellen, es ausführbar zu machen und es in deinem ~/bin-Verzeichnis (oder irgendwo anders auf deinem PATH
) abzulegen:
#!/bin/sh
cd
~/
repl
scala
Da ein Shell-Skript in einem Unterprozess ausgeführt wird, kehrst du zu deinem ursprünglichen Verzeichnis zurück, wenn du die REPL verlässt.
Auf diese Weise werden deine benutzerdefinierten Methoden in die REPL geladen, wenn sie gestartet wird, sodass du sie innerhalb der scala
Shell verwenden kannst:
clear
// clear the screen
cmd
(
"ps"
)
// run the 'ps' command
ls
(
"."
)
// run 'ls' in the current directory
help
// displays my Repl.scala file as a form of help
Nutze diese Technik, um andere benutzerdefinierte Definitionen, die du in der REPL verwenden möchtest, vorzuladen.
1.3 Erste Schritte mit der Ammonite REPL
Lösung
Die Ammonite REPL funktioniert genau wie die Scala REPL: Lade sie einfach herunter, installiere sie und starte sie dann mit dem Befehl amm
. Wie die standardmäßige Scala REPL wertet sie Scala-Ausdrücke aus und weist einen Variablennamen zu, wenn du keinen angibst:
@ val x = 1 + 1 x: Int = 2 @ 2 + 2 res0: Int = 4
Aber Ammonite hat viele zusätzliche Funktionen. Du kannst die Eingabeaufforderung der Shell mit diesem Befehl ändern:
@ repl.prompt() = "yo: " yo: _
Wenn du diese Scala-Ausdrücke in einer Datei namens Repl.scala in einem Unterverzeichnis namens foo:
import
sys
.
process
.
*
def
clear
=
"clear"
.
!
def
cmd
(
cmd
:
String
)
=
cmd
.
!!
def
ls
(
dir
:
String
)
=
println
(
cmd
(
s"ls -al
$
dir
"
))
kannst du sie mit diesem Befehl in deine Ammonite REPL importieren:
@ import $file.foo.Repl, Repl.*
Dann kannst du diese Methoden in Ammonite verwenden:
clear
// clear the screen
cmd
(
"ps"
)
// run the 'ps' command
ls
(
"/tmp"
)
// use 'ls' to list files in /tmp
Auf ähnliche Weise kannst du eine JAR-Datei mit dem Namen simpletest_3.0.0-0.2.0.jar in einem Unterverzeichnis namens foo in deine amm
REPL-Sitzung importieren, indem du die Variable Ammonite $cp
verwendest:
// import the jar file
import
$cp
.
foo
.
`simpletest_3.0.0-0.2.0.jar`
// use the library you imported
import
com
.
alvinalexander
.
simpletest
.
SimpleTest
.
*
isTrue
(
1
==
1
)
Mit dem Befehl import ivy
kannst du Abhängigkeiten aus Maven Central (und anderen Repositories) importieren und sie in deiner aktuellen Shell verwenden:
yo: import $ivy.`org.jsoup:jsoup:1.13.1` import $ivy.$ yo: import org.jsoup.Jsoup, org.jsoup.nodes.{Document, Element} import org.jsoup.Jsoup yo: val html = "<p>Hi!</p>" html: String = "<p>Hi!</p>" yo: val doc: Document = Jsoup.parse(html) doc: Document = <html> ... yo: doc.body.text res2: String = "Hi!"
Mit dem in Ammonite eingebauten Befehl time
kannst du die Zeit messen, die du für die Ausführung deines Codes benötigst:
@ time(Thread.sleep(1_000)) res2: (Unit, FiniteDuration) = ((), 1003788992 nanoseconds)
Die Autovervollständigungsfunktion von Ammonite ist beeindruckend. Gib einfach einen Ausdruck wie diesen ein und drücke nach dem Komma die Tabulatortaste:
@ Seq("a").map(x => x.
Wenn du das tust, zeigt Ammonite eine lange Liste von Methoden an, die auf x
-einer String
- verfügbar sind, beginnend mit diesen Methoden:
def intern(): String def charAt(x$0: Int): Char def concat(x$0: String): String much more output here ...
Das ist gut, denn es zeigt dir nicht nur die Methodennamen, sondern auch ihre Eingabeparameter und den Rückgabetyp.
Diskussion
Die Liste der Funktionen von Ammonite ist lang. Eine weitere großartige Funktion ist, dass du eine Startkonfigurationsdatei verwenden kannst, genau wie eine Unix .bashrc oder .bash_profile Startdatei. Füge einfach ein paar Ausdrücke in eine Datei ~/.ammonite/predef.sc ein:
import
sys
.
process
.
*
repl
.
prompt
()
=
"yo: "
def
clear
=
"clear"
.
!
def
cmd
(
cmd
:
String
)
=
cmd
.
!!
def
ls
(
dir
:
String
)
=
println
(
cmd
(
s"ls -al
$
dir
"
))
def
reset
=
repl
.
sess
.
load
()
// similar to the scala repl ':reset' command
Wenn du dann die Ammonite REPL startest, ändert sich deine Eingabeaufforderung in yo:
und diese anderen Methoden stehen dir zur Verfügung.
Eine weitere großartige Funktion ist, dass du eine REPL-Sitzung speichern kannst und alles, was du bis zu diesem Punkt gemacht hast, gespeichert wird. Um das zu testen, erstelle eine Variable in der REPL und speichere dann deine Sitzung:
val
remember
=
42
repl
.
sess
.
save
()
Dann erstelle eine weitere Variable:
val
forget
=
0
Wenn du jetzt die Sitzung neu lädst, siehst du, dass die Variable remember
immer noch verfügbar ist, aber die Variable forget
wie gewünscht vergessen wurde:
@ repl.sess.load() res3: SessionChanged = SessionChanged(removedImports = Set('forget), addedImports = Set(), removedJars = Set(), addedJars = Set()) @ remember res4: Int = 42 @ forget |val res5 = forget | ^^ | Not found: forget
Du kannst auch mehrere Sitzungen speichern und laden, indem du ihnen unterschiedliche Namen gibst, z.B. so:
// do some work
val
x
=
1
repl
.
sess
.
save
(
"step 1"
)
// do some more work
val
y
=
2
repl
.
sess
.
save
(
"step 2"
)
// reload the first session
repl
.
sess
.
load
(
"step 1"
)
x
// this will be found
y
// this will not be found
In der Ammonite-Dokumentation findest du Details zu weiterenFunktionen.
1.4 Kompilieren mit scalac und Ausführen mit scala
Problem
Obwohl du in der Regel ein Build-Tool wie sbt oder Mill verwenden wirst, um Scala-Anwendungen zu erstellen, möchtest du vielleicht gelegentlich auch einfachere Tools verwenden, um kleine Testprogramme zu kompilieren und auszuführen, so wie du vielleicht javac
und java
für kleine Java-Anwendungen verwendest.
Lösung
Kompiliere kleine Programme mit scalac
, und führe sie mit scala
aus. Nehmen wir zum Beispiel diese Scala-Quellcodedatei namens Hello.scala:
@main
def
hello
=
println
(
"Hello, world"
)
kompiliere es auf der Kommandozeile mit scalac
:
$ scalac Hello.scala
Dann führe sie mit scala
aus und gib dem Befehl scala
den Namen der Methode @main
, die du erstellt hast:
$ scala hello Hello, world
Diskussion
Das Kompilieren und Ausführen von Klassen ist dasselbe wie in Java, einschließlich Konzepten wie dem Klassenpfad. Stell dir zum Beispiel vor, du hast eine Klasse namens Pizza
in einer Datei namens Pizza.scala, die von einem Topping
Typ abhängt:
class
Pizza
(
val
toppings
:
Topping
*
):
override
def
toString
=
toppings
.
toString
Angenommen, Topping
ist wie folgt definiert:
enum
Topping
:
case
Cheese
,
Mushrooms
und dass sie sich in einer Datei namens Topping.scala befindet und zu Topping.class in einem Unterverzeichnis namens classes kompiliert wurde, kompiliere Pizza.scala wie folgt:
$ scalac -classpath classes Pizza.scala
Beachte, dass der Befehl scalac
viele zusätzliche Optionen hat, die du verwenden kannst. Wenn du zum Beispiel die Option -verbose
zum vorherigen Befehl hinzufügst, siehst du Hunderte von Zeilen zusätzlicher Ausgaben, die zeigen, wie scalac
funktioniert. Diese Optionen können sich im Laufe der Zeit ändern, daher solltest du die Option -help
verwenden, um zusätzliche Informationen zu erhalten:
$ scalac -help Usage: scalac <options> <source files> where possible standard options include: -P Pass an option to a plugin, e.g. -P:<plugin>:<opt> -X Print a synopsis of advanced options. -Y Print a synopsis of private options. -bootclasspath Override location of bootstrap class files. -classpath Specify where to find user class files. much more output here ...
Wichtigste Methoden
Da wir gerade über die Kompilierung von main
Methoden sprechen, ist es hilfreich zu wissen, dass sie in Scala 3 auf zwei Arten deklariert werden können:
-
Verwendung der
@main
Annotation für eine Methode -
Die Deklaration einer
main
Methode mit der richtigen Signatur in einerobject
Wie in der Lösung gezeigt, kann eine einfache @main
Methode, die keine Eingabeparameter benötigt, wie folgt deklariert werden:
@main
def
hello
=
println
(
"Hello, world"
)
Du kannst auch eine Methode @main
deklarieren, die beliebige Parameter aus der Befehlszeile entgegennimmt, wie in diesem Beispiel String
und Int
:
@main
def
hello
(
name
:
String
,
age
:
Int
):
Unit
=
println
(
s"Hello,
$
name
, I think you are
$
age
years old."
)
Nachdem der Code mit scalac
kompiliert wurde, kann er wie folgt ausgeführt werden:
$ scala hello "Lori" 44 Hello, Lori, I think you are 44 years old.
Beim zweiten Ansatz ist die Deklaration einer main
Methode innerhalb einer object
genauso wie die Deklaration einer main Methode in Java, und die Signatur für die Scala main
Methode muss wie folgt aussehen:
object
YourObjectName
:
// the method must take `Array[String]` and return `Unit`
def
main
(
args
:
Array
[
String
]):
Unit
=
// your code here
Wenn du mit Java vertraut bist, ist dieser Scala-Code analog zu diesem Java-Code:
public
class
YourObjectName
{
public
static
void
main
(
String
[]
args
)
{
// your code here
}
}
1.5 Disassemblieren und Dekompilieren von Scala-Code
Lösung
Die wichtigste Methode, um Scala-Code zu disassemblieren, ist der Befehl javap
. Du kannst auch einen Decompiler verwenden, um deine Klassendateien wieder in Java-Quellcode umzuwandeln. Diese Option wird in der Diskussion vorgestellt.
Mit javap
Da deine Scala-Quellcodedateien in reguläre JVM-Klassendateien kompiliert werden, kannst du den Befehl javap
verwenden, um sie zu disassemblieren. Nimm zum Beispiel an, dass du eine Datei mit dem Namen Person.scala
erstellt hast, die diesen Quellcode enthält:
class
Person
(
var
name
:
String
,
var
age
:
Int
)
Als nächstes kompilierst du diese Datei mit scalac
:
$ scalac Person.scala
Jetzt kannst du die resultierende Person.class -Datei mit javap
in ihre Signatur zerlegen:
$ javap -public Person Compiled from "Person.scala" public class Person { public Person(java.lang.String, int); public java.lang.String name(); public void name_$eq(java.lang.String); public int age(); public void age_$eq(int); }
Dies zeigt die öffentliche Signatur der Klasse Person
, die ihre öffentliche API oder Schnittstelle darstellt. Selbst in einem einfachen Beispiel wie diesem kannst du sehen, wie der Scala-Compiler seine Arbeit für dich macht und Methoden wie name()
, name_$eq
, age()
und age_$eq
erstellt. Die Diskussion zeigt ausführlichere Beispiele.
Wenn du möchtest, kannst du mit der Option javap -private
zusätzliche Informationen anzeigen:
$ javap -private Person Compiled from "Person.scala" public class Person { private java.lang.String name; // new private int age; // new public Person(java.lang.String, int); public java.lang.String name(); public void name_$eq(java.lang.String); public int age(); public void age_$eq(int); }
javap
hat noch einige weitere nützliche Optionen. Verwende die Option -c
, um die tatsächlichen Befehle zu sehen, aus denen der Java-Bytecode besteht, und füge die Option -verbose
hinzu, um viele weitere Details zu sehen. Führe javap -help
aus, um Details zu allen Optionen zu erhalten.
Diskussion
Das Disassemblieren von Klassendateien mit javap
kann eine hilfreiche Methode sein, um zu verstehen, wie Scala funktioniert. Wie du im ersten Beispiel mit der Klasse Person
gesehen hast, erzeugt die Definition der Konstruktorparameter name
und age
als var
Felder eine ganze Reihe von Methoden für dich.
Als zweites Beispiel nimmst du das Attribut var
aus diesen beiden Feldern heraus, so dass du diese Klassendefinition erhältst:
class
Person
(
name
:
String
,
age
:
Int
)
Kompiliere diese Klasse mit scalac
und führe dann javap
auf die resultierende Klassendatei aus. Du wirst sehen, dass dies zu einer viel kürzeren Klassensignatur führt:
$ javap -public Person Compiled from "Person.scala" public class Person { public Person(java.lang.String, int); }
Wenn du hingegen var
für beide Felder belässt und die Klasse in eine Fallklasse umwandelst, wird die Menge an Code, die Scala für dich generiert, deutlich größer. Um das zu sehen, ändere den Code in Person.scala so, dass du diese Fallklasse hast:
case
class
Person
(
var
name
:
String
,
var
age
:
Int
)
Wenn du diesen Code kompilierst, werden zwei Ausgabedateien erstellt, Person.class und Person$.class. Disassembliere diese beiden Dateien mit javap
:
$ javap -public Person Compiled from "Person.scala" public class Person implements scala.Product,java.io.Serializable { public static Person apply(java.lang.String, int); public static Person fromProduct(scala.Product); public static Person unapply(Person); public Person(java.lang.String, int); public scala.collection.Iterator productIterator(); public scala.collection.Iterator productElementNames(); public int hashCode(); public boolean equals(java.lang.Object); public java.lang.String toString(); public boolean canEqual(java.lang.Object); public int productArity(); public java.lang.String productPrefix(); public java.lang.Object productElement(int); public java.lang.String productElementName(int); public java.lang.String name(); public void name_$eq(java.lang.String); public int age(); public void age_$eq(int); public Person copy(java.lang.String, int); public java.lang.String copy$default$1(); public int copy$default$2(); public java.lang.String _1(); public int _2(); } $ javap -public Person$ Compiled from "Person.scala" public final class Person$ implements scala.deriving.Mirror$Product, java.io.Serializable { public static final Person$ MODULE$; public static {}; public Person apply(java.lang.String, int); public Person unapply(Person); public java.lang.String toString(); public Person fromProduct(scala.Product); public java.lang.Object fromProduct(scala.Product); }
Wenn du eine Klasse als Fallklasse definierst, generiert Scala eine Menge Code für dich, und diese Ausgabe zeigt die öffentliche Signatur für diesen Code. In Rezept 5.14, "Erzeugen von Boilerplate-Code mit Case-Klassen", findest du eine detaillierte Beschreibung dieses Codes.
Über diese .tasty-Dateien
Du hast vielleicht schon bemerkt, dass Scala 3 während des Kompilierungsprozesses nicht nur .class-Dateien, sondern auch .tasty-Dateien erzeugt. Diese Dateien werden im so genannten TASTy-Format erzeugt, wobei das Akronym TASTy von dem Begriff typisierte abstrakte Syntaxbäume kommt.
In der TASTy-Inspektionsdokumentation heißt es dazu: "TASTy-Dateien enthalten den vollständigen Typisierungsbaum einer Klasse einschließlich der Quellpositionen und der Dokumentation. Dies ist ideal für Werkzeuge, die semantische Informationen aus dem Code analysieren oder extrahieren."
Sie werden unter anderem für die Integration zwischen Scala 3 und Scala 2.13+ verwendet. Auf der Scala-Seite zur Vorwärtskompatibilität heißt es: "Scala 2.13 kann diese (TASTy)-Dateien lesen, um zum Beispiel zu erfahren, welche Terme, Typen und Implikits in einer bestimmten Abhängigkeit definiert sind und welcher Code generiert werden muss, um sie korrekt zu verwenden. Der Teil des Compilers, der dies verwaltet, wird als Tasty Reader bezeichnet."
Siehe auch
-
In meinem Blogbeitrag "How to Create Inline Methods in Scala 3" zeige ich, wie man diese Technik verwendet, um
inline
Methoden zu verstehen. -
Vielleicht kannst du auch Decompiler verwenden, um .class-Dateien in Java-Code umzuwandeln. Ich verwende gelegentlich ein Tool namens JAD, das 2001 eingestellt wurde, aber erstaunlicherweise auch zwanzig Jahre später noch in der Lage ist, Klassendateien zumindest teilweise zu dekompilieren. Ein viel modernerer Decompiler namens CFR wurde auch im Scala Gitter Channel erwähnt.
Weitere Informationen über TASTy und .tasty-Dateien findest du in diesen Ressourcen:
1.6 JAR-Dateien mit Scala und Java ausführen
Lösung
Erstelle zunächst ein grundlegendes sbt-Projekt, wie in Rezept 17.1, "Erstellen einer Projektverzeichnisstruktur für sbt" gezeigt . Füge dann sbt-assembly in die Projektkonfiguration ein, indem du diese Zeile in die Datei project/plugins.sbt einfügst:
// note: this version number changes several times a year
addSbtPlugin
(
"com.eed3si9n"
%
"sbt-assembly"
%
"0.15.0"
)
Dann lege diese Hello.scala-Quellcodedatei in das Stammverzeichnis des Projekts:
@main
def
hello
=
println
(
"Hello, world"
)
Als Nächstes erstellst du eine JAR-Datei entweder mit dem Befehl assembly
oder show assembly
in der sbt-Shell:
// option 1 sbt:RunJarFile> assembly // option 2: shows the output file location sbt:RunJarFile> show assembly [info] target/scala-3.0.0/RunJarFile-assembly-0.1.0.jar
Wie gezeigt, gibt die Ausgabe des Befehls show assembly
den Ort aus, an den die JAR-Ausgabedatei geschrieben wird. Diese Datei beginnt mit dem Namen RunJarFile, weil das der Wert des Feldes name
in meiner build.sbt-Datei ist. Ebenso stammt der Teil 0.1.0 des Dateinamens aus dem Feld version
in dieser Datei:
lazy
val
root
=
project
.
in
(
file
(
"."
))
.
settings
(
name
:=
"RunJarFile"
,
version
:=
"0.1.0"
,
scalaVersion
:=
"3.0.0"
)
Als Nächstes erstellst du ein Unterverzeichnis Example, wechselst in dieses Verzeichnis und kopierst die JAR-Datei in dieses Verzeichnis:
$ mkdir Example $ cd Example $ cp ../target/scala-3.0.0/RunJarFile-assembly-0.1.0.jar .
Da das sbt-assembly Plugin alles, was du brauchst, in die JAR-Datei packt, kannst du die Methode hello
main mit diesem scala
Befehl ausführen:
$ scala -cp "RunJarFile-assembly-0.1.0.jar" hello Hello, world
Wenn deine JAR-Datei mehrere @main
Methoden in Paketen enthält, kannst du sie mit ähnlichen Befehlen ausführen, indem du den vollständigen Pfad zu den Methoden am Ende des Befehls angibst:
scala
-
cp
"RunJarFile-assembly-0.1.0.jar"
com
.
alvinalexander
.
foo
.
mainMethod1
scala
-
cp
"RunJarFile-assembly-0.1.0.jar"
com
.
alvinalexander
.
bar
.
mainMethod2
Diskussion
Wenn du (a) versuchst, deine JAR-Datei mit dem Befehl java
auszuführen, oder (b) die JAR-Datei mit sbt package
anstelle von sbt assembly
erstellst, musst du die Abhängigkeiten deiner JAR-Datei manuell zu deinem Klassenpfad hinzufügen. Wenn du zum Beispiel eine JAR-Datei mit dem Befehl java
ausführst, musst du einen Befehl wie den folgenden verwenden:
$ java -cp "~/bin/scala3/lib/scala-library.jar:my-packaged-jar-file.jar" ↵ foo.bar.Hello Hello, world
Beachte, dass der gesamte java
Befehl in einer Zeile stehen sollte, einschließlich des foo.bar.Hello
Teils am Ende der Zeile.
Für diesen Ansatz musst du die Datei scala-library.jar finden. Da ich die Scala 3-Distribution manuell verwalte, habe ich sie in dem angegebenen Verzeichnis gefunden. Wenn du ein Tool wie Coursier verwendest, um deine Scala-Installation zu verwalten, findest du die Dateien, die es herunterlädt, in diesen Verzeichnissen:
-
Unter macOS: ~/Library/Caches/Coursier/v1
-
Unter Linux: ~/.cache/coursier/v1
-
Unter Windows: %LOCALAPPDATA%\Coursier\Cache\v1, was für einen Benutzer namens Alvin normalerweise C:\Benutzer\Alvin\AppData\Local\Coursier\Cache\v1 entspricht
Auf der Seite Coursier Cache findest du aktuelle Informationen zu diesen Verzeichnissen.
Warum sbt-assembly verwenden?
Wenn deine Anwendung verwaltete oder nicht verwaltete Abhängigkeiten verwendet und du sbt package
anstelle von sbt assembly
verwendest, musst du alle diese Abhängigkeiten und ihre transitiven Abhängigkeiten verstehen, diese JAR-Dateien finden und sie dann in die Klassenpfadeinstellungen aufnehmen. Aus diesem Grund wird die Verwendung von sbt assembly
oder einem ähnlichen Tool dringend empfohlen.
Siehe auch
-
In Rezept 17.11, "Bereitstellen einer einzelnen ausführbaren JAR-Datei", findest du weitere Informationen zur Konfiguration und Verwendung von sbt-assembly.
Get Scala Kochbuch, 2. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.