読者です 読者をやめる 読者になる 読者になる

糸電話式のアレ

プログラミングのこと。毎日のこと。書いています。

Java使いからScala使いになるための手引き

scala scaladojo

Level1)ScalaJavaと同じものが書けるようになる

このレベルではScalaを始めたJavaプログラマが初めて出会う、Scalaの記法と、キーワードの使い方をJavaと一対一に対応させて説明します。
ScalaJavaに対する優位性などは語りません。
あくまで、JavaプログラマJavaで考えたコードを、機械的にScalaコードに置き換えられるようになることが目的となります。


息をするようにJavaコードを書けていたプログラマが、Scalaコードとなると1行も書けない状態から抜け出すための第一歩です。
Scalaの世界でも息苦しくならない呼吸の仕方を覚えましょう。

新技術編

Javaにはなかった技術について

1.シングルトンオブジェクト

初めてScalaをコンパイルをしてコンソールアプリを実行しようとすると、まずobjectキーワードに出会います。
Javaではコンソールアプリケーションの起動メソッドとなるmainメソッドは、classキーワードで宣言されたクラスの中に定義していましたが、Scalaではobjectキーワードで宣言された見慣れないものの中に定義されています。


このobjectキーワードで宣言されているものは、シングルトンオブジェクトというものです。
シングルトンオブジェクトは、宣言した時点でインスタンスとなる特殊なオブジェクトです。インスタンス変数名にあたるものは宣言時に指定した名前です。このオブジェクトはnewせずに公開されているプロパティやメソッドにアクセス可能です。


このレベルでは、シングルトンオブジェクトは「Javaであればクラスの中で静的に宣言するものを書く場所」と理解するといいでしょう。


実は、Scalaには静的に宣言するためのキーワードはありません。
静的に実装したいものはシングルトンオブジェクトとして実装することで、Javaで静的なプロパティやメソッドをクラスに実装した時と同じように、newしなくても使えます。
ただし、これだけでは、先ほど言った「クラスの中で静的に宣言するものを書く場所」にはなりません。クラスとシングルトンオブジェクトが全く別の存在になると、Javaでは一つの名前で呼んでいたものが二つの名前で呼ばなくてはならなくなります。


そう言うことにならないように、このシングルトンオブジェクトには、もう一つ特殊な機能があります。シングルトンオブジェクトと同名のクラスが定義できるのです。
そして、シングルトンオブジェクトと同じ名前のクラスからは、シングルトンオブジェクト内でprivateキーワード付きの外部から隠蔽されたプロパティやメソッドを含む、オブジェクトの全てのメンバにアクセスできるのです。また、逆にシングルトンオブジェクトからも同名のクラスの全てのメンバにアクセスが可能です。
これはコンパニオンと呼ばれる機能で、同名のシングルトンオブジェクトとクラスはそれぞれ、コンパニオンオブジェクトとコンパニオンクラスと呼びます。


コンパニオン関係にあるクラスとシングルトンオブジェクトの操作を外部から見たとき、クラス名に対してドット演算子を用いると、同名であるシングルトンオブジェクトへのアクセスとなり、その公開プロパティやメソッドを使用できます。見た目には静的なプロパティやメソッドを持つクラスを使用しているかのように見えます。


なお、Javaではmainメソッドは静的に宣言します。これまで説明したように、静的なものはScalaではobjectキーワードで宣言したシングルトンオブジェクトの中に書くことになっていますので、コンソールアプリケーションの最初にこいつがでてくるようになっているのです。


では、実際のソースコードの例を見てみましょう。


Javaで、静的に宣言したフィールドとメソッドを使ったプログラムの例
(大盛りでも値段が一緒のサバ料理)

/* Dish.java */
package dishj;

public class Dish{
    private static final String fish = "saba";
    private boolean ohmori = false;
    public static final int sell(){
       return 300;
    }
    public void setOhmori(boolean order){
        ohmori = order;
    }
    public String returnOrder(){
        return fish+((ohmori)?" ohmori ":"")+sell()+"Yen Death.";
    }
}

/* DishOrder.java */
package dishj;

public class DishOrder{
    public static void main(String[] args){
        System.out.println("Default Sell : \\"+Dish.sell());
        Dish d = new Dish();
        d.setOhmori(true);
        System.out.println(d.returnOrder());
    }
}

これを、Scalaで実装すると、以下のようになります。

/* Dish.scala */
package dishs;

object Dish {
    private val fish: String = "saba"
    def sell():Int = {
        return 300;
    }
}

class Dish() {
    private var ohmori: Boolean = false
    def setOhmori(order: Boolean): Unit = {
        ohmori = order;
    }
    def returnOrder(): String = {
        return Dish.fish + (if ((ohmori)) " ohmori " else "") +  Dish.sell() + "Yen Death.";
    }
}

/* DishOrder.scala */
package dishs;å

object DishOrder {
    def main(args: Array[String]): Unit = {
        System.out.println("Default Sell : \"+Dish.sell());
        var d: Dish = new Dish();
        d.setOhmori(true);
        System.out.println(d.returnOrder());
    }
}


Dishクラスについて、JavaScalaの実装を見比べると、Javaで静的に宣言してある部分が、Scalaではobjectキーワードで宣言したシングルトンオブジェクトに移動されていますね。
そして、このシングルトンオブジェクトと同名のクラスからは、シングルトンオブジェクトのフィールドやメソッドはprivateで宣言されていようとアクセスできるようになっています。


このように、Javaで静的に宣言するものは、Scalaではシングルトンオブジェクトに分離して記述するようになっています。
まずはこの使い方でシングルトンオブジェクトを理解すると良いでしょう。
なぜこのような一見ややこしそうな書き方になっているのかは、後々、Scalaを使いこなせるようになってくれば理解できるでしょう。


2.トレイト
トレイトとは、インスタンス化できない(Javaで言う匿名クラスのような使い方は可能)クラスです。
Scalaでは静的なメンバも持てませんので、トレイトだけでは基本的には役に立ちません。
インスタンス化可能なクラスに継承して使います。


抽象化を行うとき、Javaではインタフェースと抽象クラスの二つの方法をとっていました。
このうち、インタフェースはScalaにはないので、これはトレイトで代用することになります。


トレイトには、実装を記述できるので、抽象クラスの代用としても使えますが。しかし、抽象クラスはScalaにもありますので、Javaと同じ実装という意味では、インタフェースをトレイトで代用する考え方で十分でしょう。


トレイトは、インタフェースとほとんど同様に振る舞えます。
一つのクラスにいくつでも継承させることが来ます。括弧書きで触れましたが、匿名クラスのようなトレイト型のインスタンス化も可能です。


Javaであればinterfaceと宣言しているが、Scalaではtraitと宣言する」と理解するとまずはいいでしょう。


Javaのインタフェースを使ったプログラムの例
(注文書インターフェースと、vip客の値段は0.8掛けで値引く注文書を出力するクラス)

/* IDishOrderSheet.java */
package dishj;

interface IDishOrderSheet{
    String getOrderId();
    int getOrderValue();
}

/* DishOrderSheet.java */
package dishj;

class DishOrderSheet implements IDishOrderSheet{
    private String orderId = "Guest";
    private int defaultValue;
    private static final double SERVICE = 0.8;

    public DishOrderSheet(int defaultValue){
        this.defaultValue = defaultValue;
    }
    public void setOrderId(String setid){
        orderId = setid;
    }
    public String getOrderId(){
        return orderId;
    }
    public int getOrderValue(){
        return (int)(defaultValue * ((orderId.indexOf("vip") >= 0)?SERVICE:1));
    }

    public IDishOrderSheet takeOrderSheet(){
        return (IDishOrderSheet)this;
    }
}

/* DishOrderSheetTest.java */
package dishj;

public class DishOrderSheetTest {
    public static void main(String[] args){
      DishOrderSheet os = new DishOrderSheet(800);
      IDishOrderSheet ios = os.takeOrderSheet();
      System.out.println("1:"+ios.getOrderValue());
      os.setOrderId("k.kano_vip");
      System.out.println("2:"+ios.getOrderValue());
    }
}

Scalaのインタフェース(トレイトで代用)宣言

/* IDishOrderSheet.scala */
package dishs;

abstract trait IDishOrderSheet {
    def getOrderValue(): Int;
    def getOrderId(): String;
}


/* DishOrderSheet.scala */
package dishs;

object DishOrderSheet {
    private val SERVICE: Double = 0.8;
}
class DishOrderSheet(defaultValue: Int) extends IDishOrderSheet {
    val this.defaultValue:Int = defaultValue;
    private var orderId: String = "Guest";

    def getOrderValue(): Int = {
        return (defaultValue * (if ((orderId.indexOf("vip") >= 0)) DishOrderSheet.SERVICE else 1)).toInt;
    }
    def setOrderId(setid: String): Unit = {
        orderId = setid;
    }
    def getOrderId(): String = {
        return orderId;
    }

    def takeOrderSheet(): IDishOrderSheet = {
        return this.asInstanceOf[IDishOrderSheet];
    }
}

/* DishOrderSheetTest.scala */
package dishs;

object DishOrderSheetTest {
    def main(args: Array[String]): Unit = {
        var os: DishOrderSheet = new DishOrderSheet(800);
        var ios: IDishOrderSheet = os.takeOrderSheet();
        System.out.println("1:" + ios.getOrderValue());
        os.setOrderId("k.kano_vip");
        System.out.println("2:" + ios.getOrderValue());
    }
}

上の例を見ていただければ、interfaceと宣言している部分がtraitとなっているのが分かりますね。
このように、トレイトはインタフェースを代用できるものです。


なお、前項の内容に従い、上の例でも静的かつfinalに宣言された定数はシングルトンオブジェクトに分離しています。


とりあえずはこの程度の理解をもてば、Javaプログラマは新しいキーワードを抵抗なく受け入れることができるかもしれません。

次回以降の予定

Lv2)面倒な部分を関数型で
Lv3)新オブジェクト指向での実装
Lv4)並列化プログラミング
Lv5)テストを含んだ実装(テストをコードに書かないやつなんて大嫌いだ!)