Scalaのパッケージ、アクセス修飾子、オブジェクト継承スケーラブルで関数型でオブジェクト指向なScala入門(6)(2/3 ページ)

» 2012年05月22日 00時00分 公開
[中村修太クラスメソッド株式会社]

メソッドや定数を定義できる「パッケージオブジェクト」とは

 「パッケージオブジェクト」はパッケージ自体をScalaのオブジェクトのように使う機能で、Scala 2.8から導入されました。

 Javaにおいてパッケージはクラスやインターフェイスを定義する場所ですが、Scalaでは、パッケージオブジェクトを使用することで、パッケージ自体にメソッドや定数を定義できます。

 構文は以下のようにします。

package object {パッケージ名} {
    //メソッドや定数の定義
}

 実際にパッケージオブジェクトを定義してみましょう。パッケージオブジェクトのファイル名は、一般的に「package.scala」という名前が使われます。「src」ディレクトリにpackage.scalaファイルを作成し、下記のようにパッケージオブジェクトを作成します。

package foo
  
package object bar {
    val baz = "baz!"
    def getBaz = baz
}

 これでfoo.barパッケージにあるクラスからはimport文なしでbaz変数やgetBazメソッドが使えます。

/*
* package.scala
*/
package foo
package object bar {
    val baz = "baz!"
    def getBaz = baz
}
  
/*
*sample.scala
*/
package foo.bar {
    class MyFoo(val name: String) {
        def myFooBaz = baz + getBaz
    }
}

 また、パッケージオブジェクトをほかのパッケージから使用したい場合、通常のオブジェクトと同じようにimport文を使用すれば利用できます。

アクセス修飾子とアクセス制限

 前回の記事では公開メンバ(public)と非公開メンバ(private)について、少しだけ解説しましたが、Scalaではもっと複雑なアクセス制限を設定可能です。ここでは、アクセス修飾子を用いたアクセス制限を紹介します。

 Scalaではパッケージ、クラスやオブジェクトのメンバに大きく分けて3種類のアクセス権限を指定できます。

公開権限

 1つ目は公開権限(public)です。アクセス修飾子を何も指定しない場合、公開権限となり、どこからでもアクセス可能です。Javaでは明示的に「public」を付けますが、Scalaに公開権限を示すキーワードはありません。

class X {
    //公開メンバとして、どこからでもアクセス可能
    def foo = println("foo")
}

非公開権限(private)

 2つ目は非公開(private)権限です。これは、「private」キーワードを付けることで実現できます。非公開権限はそれ自身を定義しているクラス/オブジェクトからのみ参照可能となっていますが、Javaと違い、外側のクラスから内側のクラスのプライベートメンバにアクセスできません。

class X {
    //このクラスからのみアクセス可能
    private val name = "name"
    def print = {
        val x = new X
        println(x.name) //これはOK
    }
}

限定公開権限(protected)

 3つ目は限定公開権限(protected)です。これは、指定したメンバのサブクラスからしかアクセスできません。Javaではサブクラスに加えて同一パッケージからのアクセスも可能ですが、Scalaではサブクラスからのみアクセス可能です。

スコープ限定子

 Scalaでは、これらに加え、限定子を追加してさらに詳細なアクセス権限を設定できます。

 非公開権限と限定公開権限にスコープ限定子を追加した権限です。これらは、それぞれの権限にパッケージ、またはクラス/オブジェクトを指定し、その指定した対象までは公開するという権限です。

スコープ指定非公開権限

 まずはスコープ指定非公開権限を見てみましょう。

 例えば、以下のようにすれば、クラスXはfooパッケージ(と、そのサブパッケージ)からは見えるという意味になります。

private[foo] class X

 また、「private[this]」とすると、通常のprivateよりも厳しい制限となり、そのインスタンスからのみアクセス可能になります。

class X {
    private[this] val name = "name"
    def print = {
        //これはNG 
        //val x = new X
        //println(x.name) 
  
        println(this.name) //これはOK
    }
}

スコープ指定公開権限

 スコープ指定限定公開権限は、スコープ指定非公開権限にサブクラスからのアクセスも許可した権限です。

 これは、アクセス対象を抱合したパッケージかクラス/オブジェクトを指定できます。

package bar {
    class X {
        //barパッケージからはアクセス可能
        def print = (new Y).name
    }
    class Y {
        protected[bar] val name = "name"
    }
}
  
package foo {
    class Z extends bar.Y{
        //サブクラスからアクセス可能
        def print = this.name
    }
}

アクセス権限一覧

 このように、ScalaではJavaと比べてパッケージやクラスレベルでの詳細なアクセス権限が設定できます。最後に、Scalaのアクセス権限一覧を表に示します。

名前 キーワード 説明
公開(public) なし どこからでも参照可能
非公開 private それを定義しているクラス、オブジェクト内からのみ参照可能
限定公開 protected それを定義しているサブクラスからのみ参照可能
スコープ指定非公開 private[scope] scopeはアクセス可能
スコープ指定限定公開 protected[scope] 対象のサブクラスとscopeはアクセス可能
表 アクセス権限一覧

Scalaにおける「継承」「抽象クラス」

 ScalaはJavaと同じく実装継承をサポートしています。Javaでは継承を用いることで、クラスを拡張したり新しいメソッドやフィールドの追加、既存メソッドの上書きなどが可能でした。

 いくつかの違いはあるものの、Scalaの継承はJavaの継承とほとんど同じです。本稿では、主にJavaとの違いを紹介します。

 Javaにおける継承の基本について詳しく知りたい方は、以下の記事などを参照してください。

継承やオーバーライドで簡単にクラスを“拡張”しよう
【改訂版】Eclipseではじめるプログラミング(12) オブジェクト指向の“肝”といえる「extends」によるクラスの拡張や「super」によるスーパークラスへのアクセスなどを解説
Java Solution」フォーラム 2009/11/13

抽象クラスの定義と継承の仕方

 Javaの場合と同じく、クラスを継承するには「extends」キーワードを用いてクラスを継承します。また、「abstract」キーワードを使用することで、抽象クラス(メンバーの具体的な実装をせず、サブクラスで定義させる。インスタンス化不可)も定義できます。

 実際にクラスを定義して確認してみましょう。まずはエンジニアを表現する抽象クラス、Engineerを定義します(※この章のサンプルはすべてREPLで入力してください)。

abstract class Engineer {
    def work():Unit
}

 エンジニアが仕事をするためのworkメソッドを持っていますが、これは実装を持たない抽象メソッドです。

 次に、extendsキーワードを使用してEngineerを継承し、Programmerクラスを作成します。

class Programmer(name:String,age:Int) extends Engineer{
    def work() = printf("%s(%d)さんはコーディングします",name,age)
}

scalaのオーバーライド

 そして、抽象メソッドであるworkメソッドをオーバーライドしています。実際にProgrammerクラスをインスタンス化し、workメソッドを呼び出してみます。

scala> val p = new Programmer("taro",25)
p: Programmer = Programmer@7baf1934
  
scala> p.work
taro(25)さんはコーディングします

 次は、メソッドをオーバーライドしてみましょう。具象メンバをオーバーライドする場合、必ずoverrideキーワードが必要です。逆に抽象メソッドをオーバーライドする場合にはoverrideキーワードはあってもなくても構いません。

 では、Engineerクラスに「study」という具象メソッドをを追加します。

abstract class Engineer {
    def work():Unit
    def study() = printf("勉強します")

 Programmerクラスでstudyメソッドをオーバーライドしましょう。メソッドやフィールドをオーバーライドするには、「override」キーワードを使用します。

class Programmer(name:String,age:Int) extends Engineer{
    def work() = printf("%s(%d)さんはコーディングします",name,age)
    override def study() = println("プログラミングの勉強します")
}

 なお、抽象クラスでフィールドを値なしで宣言すると、それは抽象フィールドとなり、サブクラスでの値の設定が期待されます。

補足 メソッドを「val」でオーバーライド

Javaではクラス内に同じ名前のメソッドとフィールドをそれぞれ定義できますが、Scalaではフィールドとメソッドを統一的に扱っており、同じ名前で定義できません。

なぜ、こうなっているかというと、パラメータなしのメソッドをvalでオーバーライドできるようにするためです。下記コードをREPLで入力してみましょう。

abstract class X {
    def contents:String = "contents from X"
}
  
class Y extends X {
    override val contents:String = "contents!"
}
  
scala> val y = new Y
y: Y = Y@56de8a94
  
scala> y.contents
res15: String = contents!

抽象クラスXはcontentsメソッドを定義しており、そのサブクラスYは

contentsメソッドをvalでオーバーライドしています。

このように、パラメータなしのメソッドはvalでオーバーライドが可能になっています。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。