Scala for Java programmers このエントリーをはてなブックマークに追加

提供:Asterisk Works Wiki
移動: 案内, 検索

このページはScala for Java programmers の翻訳です。


目次

JavaプログラマのためのScala

Javaのバーチャルマシンを対象とした多数の言語がある。 (スクリプト言語や動的型言語だけではなく)別の静的型言語を学ぶのはどうだい?JVM向けにコンパイルされ、Java言語とシームレスに統合されるのは? 最新のマントラは"一つの偉大な仮想マシンに多くの言語"さ。だろ?:-)

- Scala- に注目してみないか? 関数型でオブジェクト指向でコンカレントな言語で、そいつはJavaプラットフォームで走るんだ。

ノート:この文書は言語比較の訓練じゃない。[つまり、どっちがよりよいかを結論づける X 対 Yの比較じゃない]。 むしろJavaプラットフォームで走る他の言語を学びたいJavaプログラマのためによいきっかけを提供する試みなんだ。 それで目的は静的型言語に注目しているが「Java, Groovy と(J)Ruby」や「Java, JavaScript とJython」のような私のブログの過去のエントリーと同じだ。

特徴 Java Scala

静的型

Yes Yes

オブジェクト指向プログラミング

Yes Yes

関数型プログラミング

No Yes

変数の宣言

// type var_name = init_value;
int i = 0;
// var var_name: type = init_value;
var i : int = 0;

定数の宣言

// final type var_name = init_value;
final int i = 0;
// val var_name: type = init_value;
val i : int = 0;

クラス宣言

class Person {
// members here
}
class Person {
  // members here
}

メソッド

// RetType name(PType1 pName1, PType2 pName2...)

class Person {
  // members here
  public String getName() {
     // code here..
  }

  public void setAge(int age) {
     // code here..
  }
}
// def name(pName: PType1, pName2: PType2...) : RetType 

class Person {
  // members here
  def getName() : String = {
     // code here..
  }

  def  setAge(age: int) : unit = {
     // code here..
  }
}

コンストラクター

Method(s) with same name as class name with no return type.

class Person {
   public Person(String name, int age) {
      // initialization here..
   }

   public Person(String name) {
      // call the other constructor
      this(name, 1);
   }
}
コンストラクターの引数はクラス宣言そのもので定義する。たとえば:
class Person(name: String, age: int) {
   // any method can access "name" or "age" parameter
}

その上、"2つ目のコンストラクター"を作ることもできる。たとえば:

class Person(name: String, age: int) {
   // any method can access "name" or "age" parameter
   def this(name: String) {
       // call the "primary" constructor
       this(name, 1);
   }
}

演算子のオーバーライド

No. 文字列の連結を"+”を使ってできる("concat"メソッドが呼ばれたと変換される)以外はできない Yes. 演算子はメソッド名として使える。たとえば:
class Complex {
  def + (other: Complex) : Complex = {
     //....
  }
}

Also, any single parameter method can be used as an infix operator. Example: また、任意の引数を一つだけ取るメソッドも2項演算子として使える。たとえば:

// call System.exit(int) using infix notation
System exit 0
// call Thread.sleep(int) using infix notation
Thread sleep 10

参考: Scala operators

スタティックな属性とメソッド

class Person {
  private static Person president = ....
  private static Person getPresident() {
     return president;
  }
}
スタティックなクラスメンバーはない。singletonを使う

シングルトン

言語仕様としてサポートしない。次の様に実装する:
class President extends Person {
    // make the constructor private - so that
    // instance can not be created outside of this class
    private President() {}

    // create the singleton object here..
    private static President thePresident = new President();

    // provide accessor for singleton 
    // and add other methods here..
}
object President extends Person {
   // have methods of the singleton object
}

シングルトンな"オブジェクト"はScalaでは実際には"モジュール"だ。

Also, every Java class is viewed like any other Scala class without static methods and fields and a singleton object [whose name is same as Java class name] that contains only the static methods and fields of Java class as it's (non-static) members. また、すべてのJavaクラスはスタティックなメソッドと属性のない他のどんなScalaクラス (Javaのクラス名と同じ名前の)シングルトンオブジェクト

This allows accesing static methods of Java classes but at the same time not having static members in Scala. これによりJavaクラスのスタティックなメンバーにアクセスできる。一方で同時にScalaでは

継承

class Person {
}

class Graduate extends Person {
}
class Person {
}

class Graduate extends Person {
}

参考: Scala subclassing. しかし、この資料は更新が必要だ. 今ならこちらか, Java (since JDK 5.0) supports covariant return types.

親クラスのコンストラクタの呼び出し

class Graduate extends Person {
    public Graduate(String name, int age, String degree) {
        super(name, age);
    }
}
You call super class constructor in class declaration itself.
class Graduate(name: String , age: int, degree: String) 
     extends Person(name, age) {
}

メソッドのオーバーライド

class Graduate extends Person {
  @Override public String getName() {
    // code here...
  } 
}

@Overrideはつけなくてもよい - でも、エラー発見の助けになる(オーバーライドしたと思っていいても実際には親クラスのメソッドのオーバーロードであるときがある)

class Graduate extends Person {
  override def getName(): String = {
    // code here...
  }
}

"orverride"キーワードは抽象メソッドでないメソッドのオーバーライドではつけなければならない

抽象クラスとメソッド

abstract class Person {
  abstract public String getName();
}
abstract class Person {  
  def getName(): String;
}

"abstract"キーワードはメソッドには必要ない。クラスだけでよい

すべての参照型の基底クラス

java.lang.Object scala.AnyRef - JVM中のjava.lang.Objectのスカラによる実装と同じだ

統一的なオブジェクト指向か? (i.e., すべてがオブジェクトか否か?)

違う。

プリミティブと参照型がある. しかし autoboxing/unboxingによって, b/wプリミティブ型と対応するbox型の変換に気を遣う必要はない

その通り. たとえば"int"は"scala.Int"のエイリアスだ

A bit explanation of type hierarchy. 少し型階層の説明をすると、 scala.Anyはすべての型の親だ scala.AnyValは数値型(たとえば"int"など)の親だ scala.AnyRefはすべての"参照"型の親だ。しかし、メソッドなどは数値型の様に機能する。たとえば、

  • 44.+(4) は次と同じだ 44 + 4 ここで"+" は scala.Intクラスのメソッドだ.
  • また、引数を一つだけ撮るメソッドは演算子メソッドのように"中置"記法を使って呼び出せる

たとえば: System exit 4 は 次と同じだ。 System.exit(4)

インターフェース

interface Runnable {
   void run();
}
Use traits. Traits are like interface but can have method bodies (i.e., not just method declarations). See also: multiple inheritance.
trait Runnable {
  def run(): unit;
}

多重継承

できない. 複数のインターフェースをインプリメントすることはできる。

複数のインターフェースをインプリメントできるが継承できるのは一つのクラスだけだ。

複数のクラスを継承することはできない。複数のトレイトを利用することはできる。そしてトレイトは宣言だけではなく実装を持つことができる。
trait Runnable {  
   // 引数を取るrunメソッド
   def run(obj: AnyRef) : unit;

   // 引数なしのrunメソッド - 代わりにnullを引数として渡す
   def run() : unit = {
       System.out.println("assuming null...");
       run(null);
   }
}

//一つのクラスを拡張できる。一方”with”を
//使って複数のトレイトで拡張することができる。

class MyClass extends AnyRef with Runnable {
   // 引数ありのrunメソッドだけ実装する 
   // 他の "run"メソッドは Runnable から引き継ぐ
   def run(obj: AnyRef) : unit = {
       System.out.println("got " + obj);
   }   
}

See also: Scala mixins, mixins paper, traits paper.

インナークラス

public class Book {
  private String name;
  // ...

  public Order newOrder() {
     ...
  }

  public class Order {
      private Date orderDate;
      private int quantity;
      // ...
  }   
}
class Book(name: String) {          

  class Order(orderDate: Date, 
              quantity : int) {
  }   

  def newOrder() : Order {
      return new Order(new Date(), 1);
  }
}

Unlike Java where such inner classes are members of the enclosing class, in Scala inner classes are bound to the outer object. With the above classes, in Java you can write クラスのメンバーとしてインナークラスを持つJavaの様ではなく、 Scalaではインナークラスは外側のオブジェクト(インスタンス)と結びつけられる。上記のクラスを使ってJavaでは次のように書ける。

// Javaでは
Book freakonomics = new Book("Freakonomics");
Book letUsKillGandhi = new Book("Let us kill Gandhi");
Book.Order o1 = freakonomics.newOrder();
Book.Order o2 = letUsKillGandhi.newOrder();

//任意のBook.Orderを他のBook.Orderにあてがうことができる

o2 = o1;

// Scalaでは違う
val freakonomics: Book = new Book("freakonomics");
val letUsKillGandhi : Book = new Book("Let us kill Gandhi");
var o1 = freakonomics.newOrder();
var o2 = letUsKillGandhi.newOrder();

// 次の例はコンパイルできない。
// o1は"freakonomics.Order"型であり、
// o2は"letUsKillGandhi.Order"型である

o2 = o1;

言い換えれば、"Freakonomics"という本のorderは"Let us kill Gandhi"のorderのインスタンスとして扱うことができない。 Javaの様にインナークラスを参照したければ、次のようにする。

var o1 : Book#Order = freakonomics.newOrder();
var o2 : Book#Order = letUsKillGandhi.newOrder();
o1 = o2;

参照: Scala inner classes.

Class literals [section 15.8.2 of JLS].

String.class
Object.class
classOf[String]
classOf[AnyRef]

参照: Scala classOf.

動的な型チェック

x instanceof String
n instanceof Number
x.isInstanceof[String]
n.isInstanceof[Number]

動的な型キャスト

String x = (String) obj;
Number n = (Number) obj;
x : String = obj.asInstanceof[String];
n : Number = obj.asInstanceof[Number];

ジェネリクス

// 型パラメータのあるインターフェース
interface Stack<E> {
    void push(T t);
    T pop();
    boolean isEmpty();
}

// 型パラメータのあるクラス
class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    // more code...
}
// 型パラメータのあるトレイト
trait Stack[E] {
    def push(o: E) : unit;
    def pop() : E;
    def isEmpty(): boolean;
}

// 2つの型パラメータのあるクラス
class Pair[K, V](key: K, value: V) {
    // code here...
}

参照: Scala generics.

ジェネリックなメソッド

class Test {
  public <T> List<T> dup(T t, int n) {
    //....
  }
}
class Test {
  def dup[T](x: T, n: Int): List[T] = {
    if (n == 0) Nil
    else x :: dup(x, n - 1)
  }
}

参考:Scala polymorphic methods.

型仮引数の境界

// 上限境界
class Foo<E extends SomeType> {
   // ... code here
}

// 下限境界
class Foo<E super SomeType> {
  // ...
}

// 境界ありの型仮引数
class Stack<E extends Comparable<E>> {
  // ...
}
// 上限境界
class Foo[E <: SomeType] {
  // ...
}

// 下限境界
class Foo[F >: SomeType] {
  // ...
}

// 境界ありの型仮引数
class Stack[E extends Comparable[E]] {
  // ...
}

ワイルドカード

// 上限境界
ArrayList<? extends Number> l = ...

// 下限境界
ArrayList<? super Number> l = ...

// 境界なしのワイルドカード
List<?> list = ...

参考: Wildcards paper

Javaでは可変のアノテーションはクライアントにより規定される。
ScalaではFoo[A]をFoo[B]のサブタイプにしたい場合、AはBのサブタイプである。そのように宣言する必要がある。

// annotation +T declares type T to be used 
// only in covariant positions.
class Foo[+A] {
  // ...
}

// annotation -T declares type T to be used 
// only in contravariant positions.
class Foo[-A] {
}

例:Scalaではリストは不変でリストは供変のアノテーションを持っている。それでBがAのサブタイプの時、List[B]はList[A]のサブタイプとなる。

参考:Scala variances

配列

String[] s = new String[10];
var s : Array[String] = new Array[String](10);

配列は巣からではジェネリックな型である(JavaにおけるVectorやListとそっくりだ)

注意:配列は供変のサブタイプルールには従わない。すなわち、Array[Graduate]はArray[Person]のサブクラスではない。たとえGraduateがPersonのサブタイプであっても。Graduate[]がPerson[]のサブタイプとなるJavaとは異なるところだ。

配列要素へのアクセスと更新

int[] a = new int[3];
a[0] = 3;
System.out.println(a[0]);
var a : Array[int] = new Array[int](3);
a(0) = 3;
System.out.println(a(0));

配列要素へのアクセスと更新は実際にはArray[T]のメソッドが呼ばれている。 (要素の更新の時)メソどの呼び出しが右側に現れているのに驚くかもしれない ScalaはC++スタイルの参照(&)を持っているわけではない。 a(i)=10はa.update(i,10)のように読み替えられるのだ

可変個変数

class Calc {
   public static int sum(int... values) {
       int res;
       for (int i in values) {
           res += i;
       }
       return res;
   } 
}

// with the above definition, caller can use
Calc.sum(3, 44)
Calc.sum(4, 4, 5,45, 45);

最後の引数の型の後の"..."はメソッドは可変個引数を取ることをしめす。 上記の例では"values"の型はint[]となる。 一般的に型はT[]となる。もし最後の引数の型がT...となっていれば。

object Calc {
  def sum(values: int*): int = {
    var res: int = 0;
    for (val v <- values) {
       res = res + 1;
    }
    return res;
  }
}

// With the above definition, caller can use
Calc.sum(3, 44);
Calc.sum(4, 5, 6, 6);

引数の後ろについた"*"はメソッドが可変個引数を取ることを示す。 "values"の型は上記の例ではSeq[int]となる。 一般的には最後の引数の型がT*であれば型はSeq[T]となる。

型推論

ジェネリックなメソッドのみで行われる(将来的には改善されるかも?) どこでもサポートされる。たとえば
  • varやvalで宣言するときに型を指定しなくてよい。最初の値の型から推論される。
  • その上、再帰的でないメソッドの戻り値の型で型を指定しなくてよい。戻り値の型から推論される。
// "i"に対してint型が推論される
val i = 10;

// "s" に対してString型が推論される
var s = "hello"

// "int"の戻り値型が推論される
def add(i: int, j:int) = i * j

// "unit"の戻り値型が推論される
def sayHello = System.out.println("hello world")

// scala.List[java.lang.String] が推論される
var v = List("hello", "world");

// with class Pair[K, V](k: K, v: V) {...}
// Pair[int, String] is inferred for the following.
var p = new Pair(2, "hello");

関数、無名関数、クロージャ

まだない。将来は採用するかも サポートしている。

関数や無名関数、クロージャをどこでも定義できる。---ただし、トップレベルスコープをのぞいて。

// 関数
def add(i: int, j: int) = i + j

// 関数がファーストクラスオブジェクト
// 引数としてコールバック関数を受け入れる
def oncePerSecond(callback: ()=>unit): unit = {
   while (true) { callback(); Thread.sleep(1000);
}

def printHello() = {
   System.out.println("hello world");
}

// 引数としてprintHelloを渡している
oncePerSecond(printHello);

// 無名関数を定義し、読んでいる
((i:int, j:int) => i+j)(3, 4)

// 引数として無名関数を渡す
oncePerSecond(()=>System.out.println("hello"));

// ネストした関数
def outer() = {
  def inner() = {
     System.out.println("I am inner");
  }
  inner();
}

// 内部関数は外側関数のローカル変数や引数にアクセスできる
def outer(s: String) = {
  def inner() = {
     System.out.println("outer's 's': " + s);
  }
  inner();
}

// Javaのクロージャの提案は自動的にクロージャを
//インターフェースに変換することについて語られる
// Scalaではviewを利用できる。
// パラメータのない関数からjava.lang.Runnableへの変換を定義できる
implicit
def asRunnable(func : ()=>unit) : Runnable = {
   new Runnable() {
       def run() {
           func()
       }
   }
}

def main ( args : Array[String] ) = { 
    // 新しいスレッドを生成し、無名関数をRunnableとして渡す
    var t = new Thread(()=>Console.println("hello"));
    t.start();
    // Runnableを関数とともに初期化することができる
    var r : Runnable = ()=>Console.println("I am running!")
    r.run();
} 

リストのサポート

java.lang.Listとクラスで実装されている
// "List"関数でListを作る(List関数は実際には"apply"を持つオブジェクト)

var names : List[String] = List("Java", "JavaScript")

// :: は結合する演算子
// "Scala"を最初の要素として残りを"name"リストの要素からなる新たなリストを作る
val l = "Scala" :: names

// ::はListのメソッドとなる。
// -- 他の演算子とは異なり ::は右辺の値はメソッドを呼び出すターゲットとなるリストである

Scalaのリストは不変である。 Listクラスにはmapやfilterなどのようにメソッドがあり、クロージャを引数にとる。たとえば、

// 短い名前が含まれる新しいリストを作りる。
//shortNames の型はList[String]
var shortNames = names.filter(n=>n.length() < 6)

// 名前の長さを持つ新たなリストを作る。
// "lengths"の型はList[int]。
var lengths = names.map(n=>n.length())

There is much more to Scala - pattern matching, for- comprehension, exception handling, packages, abstract types, XML literals, currying, implicit parameters etc. But, we have had enough for a single blog entry

Scalaにはパターンマッチング、for-comprehension、例外処理、パッケージ、アブストラクト型、XMLリテラル、カリー化、暗黙の引数などまだまだいろいろある。 でも1つのエントリーには大きすぎる。

というわけで続く

個人用ツール
名前空間
変種
操作
案内
主なカテゴリ
ツールボックス