Introduction to Scala from a Java perspective (Class-Tuple edition) to decompile and understand

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:

Introduction

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

Basic class with no fun

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

Basic class 2 with no fun

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.

Constructor with default arguments

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."

Named arguments

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

Pattern matching with tuples

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.

for comprehension

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. .. .. image.png

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.

Finally

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

Introduction to Scala from a Java perspective (Class-Tuple edition) to decompile and understand
Introduction to Scala from a Java perspective, decompiled and understood (basic)
How to write Scala from the perspective of Java
[Introduction to Java] How to write a Java program
Introduction to monitoring from Java Touching Prometheus
[Introduction to Java] Variable declarations and types
Connect to Aurora (MySQL) from a Java application
To become a VB.net programmer from a Java shop
Create Scala Seq from Java, make Scala Seq a Java List
[Java] Introduction to Java
Introduction to java
Convert Java enum enums and JSON to and from Jackson
How to jump from Eclipse Java to a SQL file
Understand the characteristics of Scala in 5 minutes (Introduction to Scala)
Java language from the perspective of Kotlin and C #
6 features I missed after returning to Java from Scala
Java classes and instances to understand in the figure
[Java] How to erase a specific character from a character string
How to convert A to a and a to A using AND and OR in Java
Changes from Java 8 to Java 11
Sum from Java_1 to 100
From Java to Ruby !!
Introduction to java command
Introduction to Effective java by practicing and learning (Builder pattern)
Assign a Java8 lambda expression to a variable and reuse it
I want to make a list with kotlin and java!
[Java] How to convert a character string from String type to byte type
I want to make a function with kotlin and java!
How to store a string from ArrayList to String in Java (Personal)
How to write and notes when migrating from VB to JAVA
Build a Node-RED environment with Docker to move and understand
How to develop and register a Sota app in Java
ClassCastException occurs when migrating from Java7 to Java8 ~ Generics and overload ~
Take a look at Kotlin from an Effective Java perspective
Convert Excel to Blob with java, save it, read it from DB and output it as a file!
Migration from Cobol to JAVA
New features from Java7 to Java8
Connect from Java to PostgreSQL
[Java] Introduction to lambda expressions
[Java] Introduction to Stream API
Java thread to understand loosely
From Ineffective Java to Effective Java
[Introduction to rock-paper-scissors games] Java
[Introduction to Java] Variables and types (variable declaration, initialization, data type)
Notes on building Kotlin development environment and migrating from Java to Kotlin
From fledgling Java (3 years) to Node.js (4 years). And the impression of returning to Java
[Swift5] How to communicate from ViewController to Model and pass a value
[Java] How to convert from String to Path type and get the path