This article is an in-house technology community of Fujitsu Systems Limited Web Technology, "Innovation Promotion Community" This is the 16th day article of Inobeko Advent Calendar 2020 planned by "Inobeko" for short. The content of this article is my own opinion and does not represent the organization to which I belong.
Promise so far: wink:
In this article, a Java programmer who started Scala decompiles the generated class file by compiling Scala. I came up with the idea of wanting to understand Scala from a Java perspective what I started.
This time, I would like to mainly look back on the contents of the Class-Tuple edition.
Class
User.scala
class User
User.java
import scala.reflect.ScalaSignature;
@ScalaSignature(bytes = "\006\005]1AAA\002\001\031!)1\003\001C\001)\t!Qk]3s\025\t!Q!\001\btG\006d\027\rZ3d_6\004\030\016\\3\013\005\0319\021A\0034jg\"L'-Y:iS*\021\001\"C\001\007O&$\b.\0362\013\003)\t1aY8n\007\001\031\"\001A\007\021\0059\tR\"A\b\013\003A\tQa]2bY\006L!AE\b\003\r\005s\027PU3g\003\031a\024N\\5u}Q\tQ\003\005\002\027\0015\t1\001")
public class User {}
There is nothing special to say ...
Point.scala
class Point(var x: Int, var y: Int) {
def move(dx: Int, dy: Int): Unit = {
x = x + dx
y = y + dy
}
override def toString: String =
s"($x, $y)"
}
Point.java
@ScalaSignature(bytes = "\006\005\0313AAC\006\001)!A1\004\001BA\002\023\005A\004\003\005!\001\t\005\r\021\"\001\"\021!9\003A!A!B\023i\002\002\003\025\001\005\003\007I\021\001\017\t\021%\002!\0211A\005\002)B\001\002\f\001\003\002\003\006K!\b\005\006[\001!\tA\f\005\006g\001!\t\001\016\005\006s\001!\tE\017\002\006!>Lg\016\036\006\003\0315\tab]2bY\006$WmY8na&dWM\003\002\017\037\005Qa-[:iS\n\f7\017[5\013\005A\t\022AB4ji\",(MC\001\023\003\r\031w.\\\002\001'\t\001Q\003\005\002\02735\tqCC\001\031\003\025\0318-\0317b\023\tQrC\001\004B]f\024VMZ\001\002qV\tQ\004\005\002\027=%\021qd\006\002\004\023:$\030!\002=`I\025\fHC\001\022&!\t12%\003\002%/\t!QK\\5u\021\0351#!!AA\002u\t1\001\037\0232\003\tA\b%A\001z\003\025Ix\fJ3r)\t\0213\006C\004'\013\005\005\t\031A\017\002\005e\004\023A\002\037j]&$h\bF\0020cI\002\"\001\r\001\016\003-AQaG\004A\002uAQ\001K\004A\002u\tA!\\8wKR\031!%N\034\t\013YB\001\031A\017\002\005\021D\b\"\002\035\t\001\004i\022A\0013z\003!!xn\025;sS:<G#A\036\021\005q\032eBA\037B!\tqt#D\001@\025\t\0015#\001\004=e>|GOP\005\003\005^\ta\001\025:fI\0264\027B\001#F\005\031\031FO]5oO*\021!i\006")
public class Point {
private int x;
private int y;
public int x() {
return this.x;
}
public void x_$eq(int x$1) {
this.x = x$1;
}
public int y() {
return this.y;
}
public void y_$eq(int x$1) {
this.y = x$1;
}
public Point(int x, int y) {}
public void move(int dx, int dy) {
x_$eq(x() + dx);
y_$eq(y() + dy);
}
public String toString() {
return (new StringBuilder(4)).append("(").append(x()).append(", ").append(y()).append(")").toString();
}
}
, Getter/setter, _ $ eq method for each property. The override keyword of the toString method has disappeared.
Point.scala
class Point(var x: Int = 0, var y: Int = 0)
Point.java
@ScalaSignature(bytes = "\006\005\0353A!\004\b\001/!Aa\004\001BA\002\023\005q\004\003\005$\001\t\005\r\021\"\001%\021!Q\003A!A!B\023\001\003\002C\026\001\005\003\007I\021A\020\t\0211\002!\0211A\005\0025B\001b\f\001\003\002\003\006K\001\t\005\006a\001!\t!M\004\bm9\t\t\021#\0018\r\035ia\"!A\t\002aBQ\001M\005\005\002eBqAO\005\022\002\023\0051\bC\004G\023E\005I\021A\036\003\013A{\027N\034;\013\005=\001\022AD:dC2\fG-Z2p[BLG.\032\006\003#I\t!BZ5tQ&\024\027m\0355j\025\t\031B#\001\004hSRDWO\031\006\002+\005\0311m\\7\004\001M\021\001\001\007\t\0033qi\021A\007\006\0027\005)1oY1mC&\021QD\007\002\007\003:L(+\0324\002\003a,\022\001\t\t\0033\005J!A\t\016\003\007%sG/A\003y?\022*\027\017\006\002&QA\021\021DJ\005\003Oi\021A!\0268ji\"9\021FAA\001\002\004\001\023a\001=%c\005\021\001\020I\001\002s\006)\021p\030\023fcR\021QE\f\005\bS\025\t\t\0211\001!\003\tI\b%\001\004=S:LGO\020\013\004eQ*\004CA\032\001\033\005q\001b\002\020\b!\003\005\r\001\t\005\bW\035\001\n\0211\001!\003\025\001v.\0338u!\t\031\024b\005\002\n1Q\tq'A\016%Y\026\0348/\0338ji\022:'/Z1uKJ$C-\0324bk2$H%M\013\002y)\022\001%P\026\002}A\021q\bR\007\002\001*\021\021IQ\001\nk:\034\007.Z2lK\022T!a\021\016\002\025\005tgn\034;bi&|g.\003\002F\001\n\tRO\\2iK\016\\W\r\032,be&\fgnY3\0027\021bWm]:j]&$He\032:fCR,'\017\n3fM\006,H\016\036\0233\001")
public class Point {
private int x;
private int y;
public static int $lessinit$greater$default$2() {
return Point$.MODULE$.$lessinit$greater$default$2();
}
public static int $lessinit$greater$default$1() {
return Point$.MODULE$.$lessinit$greater$default$1();
}
public int x() {
return this.x;
}
public void x_$eq(int x$1) {
this.x = x$1;
}
public int y() {
return this.y;
}
public void y_$eq(int x$1) {
this.y = x$1;
}
public Point(int x, int y) {}
}
Point$.java
public final class Point$ {
public static final Point$ MODULE$ = new Point$();
public int $lessinit$greater$default$1() {
return 0;
}
public int $lessinit$greater$default$2() {
return 0;
}
}
It has been separated into Point class and Point \ $ class. The default value is implemented as a static method of the Point \ $ class (why not call it from \ $ lessinit \ $ greater \ $ default \ $? ...)
Java does not have the concept of default arguments and is often implemented using overloads, builder patterns, and so on.
Even so, I don't really understand the merit of separating the value from the method that calls the static constant. When is \ $ lessinit \ $ greater \ $ default \ $? Called ...
When I pasted the above java code into jshell and executed it, I got the following error, so Scala's Runtime Reflection may have solved it in some way ... It became.
var point = new Point()
|error:
|The constructor Point of class Point cannot be applied to the specified type.
|Expected value: int,int
|Detected value:No arguments
|Reason:The length of the actual argument list and the formal argument list are different.
| var point = new Point();
| ^---------^
It seems that we will touch on the default arguments in the next chapter, and it says, "Default parameters defined in Scala are not optional when called from Java code."
You can name the arguments when you call the method.
def printName(first: String, last: String): Unit = {
println(first + " " + last)
}
//Caller
printName("John", "Smith") // ① Prints "John Smith"
printName(first = "John", last = "Smith") // ② Prints "John Smith"
printName(last = "Smith", first = "John") // ③ Prints "John Smith"
public void printName(String first, String last) {
Predef$.MODULE$.println((new StringBuilder(1)).append(first).append(" ").append(last).toString());
}
//Caller
printName("John", "Smith"); // ①
printName("John", "Smith"); // ②
String x$1 = "Smith", x$2 = "John";
printName("John", "Smith"); // ③
The result was somewhat moody ...
Since Java has no named arguments, the results of ① and ② are as expected. However, it is puzzling why the x $ 1 and x $ 2 arguments are prepared but not used when assigning to printName. I think it's probably due to the optimization of the JVM. .. ..
For the time being, will it be compiled once as variable ⇒ assignment? I will assume that I have obtained the knowledge. .. ..
Tuple
TupleSample.java
class TupleSample {
def getIngredient: (String, Int) = {
("Sugar", 25)
}
def useIngredient(): Unit = {
val ingredient = getIngredient;
println(ingredient._1)
println(ingredient._2)
}
}
TupleSample.java
import scala.Predef$;
import scala.Tuple2;
import scala.reflect.ScalaSignature;
import scala.runtime.BoxesRunTime;
@ScalaSignature(bytes = "\006\005E2A\001B\003\001\035!)Q\003\001C\001-!)\021\004\001C\0015!)A\006\001C\001[\tYA+\0369mKN\013W\016\0357f\025\t1q!\001\btG\006d\027\rZ3d_6\004\030\016\\3\013\005!I\021A\0034jg\"L'-Y:iS*\021!bC\001\007O&$\b.\0362\013\0031\t1aY8n\007\001\031\"\001A\b\021\005A\031R\"A\t\013\003I\tQa]2bY\006L!\001F\t\003\r\005s\027PU3g\003\031a\024N\\5u}Q\tq\003\005\002\031\0015\tQ!A\007hKRLen\032:fI&,g\016^\013\0027A!\001\003\b\020*\023\ti\022C\001\004UkBdWM\r\t\003?\031r!\001\t\023\021\005\005\nR\"\001\022\013\005\rj\021A\002\037s_>$h(\003\002&#\0051\001K]3eK\032L!a\n\025\003\rM#(/\0338h\025\t)\023\003\005\002\021U%\0211&\005\002\004\023:$\030!D;tK&swM]3eS\026tG\017F\001/!\t\001r&\003\0021#\t!QK\\5u\001")
public class TupleSample {
public Tuple2<String, Object> getIngredient() {
return new Tuple2("Sugar", BoxesRunTime.boxToInteger(25));
}
public void useIngredient() {
Tuple2<String, Object> ingredient = getIngredient();
Predef$.MODULE$.println(ingredient._1());
Predef$.MODULE$.println(BoxesRunTime.boxToInteger(ingredient._2$mcI$sp()));
}
}
Of note here are scala.TupleN
and scala.runtime.BoxesRunTime
. .. ..
scala.TupleN
scala.TupleN is Scala's built-in Tuple type with N = 2 ~ 22 defined.
scala.runtime.BoxesRunTime
The Int
specified in the second element of Tuple in the scala code is Scala's built-in type scala.Int
.
String is effectively just a String because java.lang.String
is defined as a String in scala.Predef
as an alias.
So, I don't know if it can be called auto boxing (?) For use in the JVM world, It seems that BoxexRunTime is doing the process of returning to the type on the Java side.
BoxesRunTime.java
public static java.lang.Integer boxToInteger(int i) {
return java.lang.Integer.valueOf(i);
}
You can decompose elements by pattern matching.
val (name, quantity) = getIngredient
println(name)
println(quantity)
Tuple2 tuple21;
Tuple2<String, Object> tuple2 = getIngredient();
if (tuple2 != null) {
String str = (String)tuple2._1();
int i = tuple2._2$mcI$sp();
tuple21 = new Tuple2(str, BoxesRunTime.boxToInteger(i));
} else {
throw new MatchError(tuple2);
}
Tuple2 tuple22 = tuple21;
String name = (String)tuple22._1();
int quantity = tuple22._2$mcI$sp();
Predef$.MODULE$.println(name);
Predef$.MODULE$.println(BoxesRunTime.boxToInteger(quantity));
The code, which was only three lines, has become super troublesome. .. .. The point is around here, right?
if (tuple2 != null) { //Check if tuple is null
String str = (String)tuple2._1();
int i = tuple2._2$mcI$sp();
tuple21 = new Tuple2(str, BoxesRunTime.boxToInteger(i)); //Create Tuple again here for some reason
} else {
throw new MatchError(tuple2); //Well, if it's null, I know it throws an error
}
Tuple2 tuple22 = tuple21; //Substitute for new Tuple here for some reason
String name = (String)tuple22._1();
int quantity = tuple22._2$mcI$sp();
Isn't this code useless?
try {
String name = (String)tuple2._1();
int quantity = tuple2._2$mcI$sp();
} catch (XXXException e) { //ClassCastException
throw new MatchError(tuple2);
}
I would like to know why the above decompiled code is better.
I think for inclusion notation is often used in scala
val numPairs = List((2, 5), (3, -7), (20, 56))
for ((a, b) <- numPairs) {
println(a * b)
}
List numPairs = (List)new .colon.colon(new Tuple2.mcII.sp(2, 5), (List)new .colon.colon(new Tuple2.mcII.sp(3, -7), (List)new .colon.colon(new Tuple2.mcII.sp(20, 56), (List)Nil$.MODULE$)));
numPairs.withFilter(TupleSample::$anonfun$forInclusion$1$adapted).foreach(TupleSample::$anonfun$forInclusion$2$adapted);
This is also a great code. .. .. When I looked it up, there was an article like this. .. .. https://qtamaki.hatenablog.com/entry/20120125/1327500931
Apparently, it seems that the contents of for are expanded to the inner class, but as far as the build result of gradle is seen, such an inner class was not found and it was not included in the jar. .. ..
First of all, it is famous that scala's for
is map/flatMap/withFilter syntax sugar,
The part processed by numPairs.withFilter doesn't need to be here first? I think that numPairs contains (List) Nil $ .MODULE $
, and I think that variables a and b are created, so I probably bite the filter here. Are you there?
So, is $ anonfun $ forInclusion $ 2 $ adapted
probably the part of println that was written in the for?
Anyway, just looking at it so far makes me feel like I understand scala, but I don't know anything about it, so I feel that I need to investigate it carefully.
This time it was an article about Class and Tuple, but there are many things that can not be understood just by decompiling. It has become a content that requires a little more deep moat.
In the next article (it is undecided when), it is good to do TOUR OF SCALA after that, but I'm thinking of learning what Scala's compiler runtime does for you.
~~ I think the contents are a little thin. ~~
Recommended Posts