Java+Maven+EclipseなプロジェクトにScalaを混ぜる


# はじめに

一からScalaのプロジェクトを始めるならIntelliJ+sbtにしましょう。

## 対象
* 現在Java+Maven+Eclipseで開発していて、徐々にScalaに移行したい人

結論から言うと、前半で書く内容は参考だけに留めておいて、マイクロプロジェクトごとにScalaとJavaに分割した方がいいです。理由は後でいっぱい出てきます。

でもせっかくScalaなので、Javaと親和性高いところみたいですね。単に多言語呼び出すだけならJVM言語じゃなくてできますが、ScalaのクラスをJavaで継承したり、ScalaのメソッドがJavaのコード補完で出てくるのはなかなかないんじゃないでしょうか。ClojureではJavaのメソッドがコード補完で出てきましたね。

## 準備
EclipseにScala IDE pluginを入れます。m2e(Maven Integrator for Eclipse)はすでに入っている人も多いでしょう。lombokをセットアップしたことが無い人は入れておいてください。まずはmavenでScalaプロジェクトを作ってみます。

「File - New - Project - Maven Project」を選択し、アーキタイプの選択で「scala-archtype-simple 1.6」を選択します。

![qiita-maven.png](https://qiita-image-store.s3.amazonaws.com/0/16962/8e5dfa76-a41f-b974-bfbd-7ac683ff038c.png)


GroupIDとArtifactIDを入れて生成します。この時点でエラーだらけですね。プロジェクトを右クリックして「Configure - Add Scala Nature」を選択するとScalaがコンパイルできるようになります。パースペクティブをScalaに切り替えてプロジェクトcleanしたらエラーが消えました。App.scalaを右クリックして「Run as - Scala Application」で「Hello world!」が出力されます。

specs.scalaファイルにエラーがありますが、Ctrl+Shift+Oでimportを再編成してあげれば直ります。私はspecs2使わないので消してしまいます。いっぺんにいろいろ覚えると面倒なので、慣れるまでテストは今まで通りJUnitで書くのもいいかもしれません。

## Javaのクラスを作る

「New - Source Folder」で「src/main/java」を作ります。パッケージは「jp.lavans.scalatest.java」にします。ここに次のようなJavaクラスを作ります。

```java:Person.java
package jp.lavans.scalatest.java;
public class Person {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String value) {
		name = value;
	}
}
```
よく見るひな形ですね。まずはscalaから呼び出してみます。

```scala:App.scala
object App {
  def main(args : Array[String]) {
    val p = new Person
    p.setName("name")
    println(p.getName)
  }
}
```
エラーが消えないことがありますが、プロジェクトのcleanで消えると思います。Javaのクラスが全部コンパイルできていないとscalaをコンパイルが通らないことが多いです。JavaのA,Bというクラスがあって、scalaからはAしか使っていなくてもBがコンパイルエラーだとScala側のコンパイルも通りません。target/classesの下のファイルを見てるとよくわかると思います。この辺がScalaとJavaを同じプロジェクトでやるべきでない理由の一つです。

Appを実行すると
```
name
```
と表示されました。

次はJavaのクラスを継承してみます。App.scalaの下の方にそのまま書いてしまいます。

```scala:App.scala
case class SPerson(_sname: String) extends Person
```
mainメソッドで出力します。

```scala:App.scala
object App {
  def main(args: Array[String]) {
    val sp = new SPerson("sname")
    sp.setName("name")
    println(sp.sname)
  }
}
```
これでJavaで定義したnameとScalaで定義したsnameの両方を使うことが出来ました。もちろんJavaでScalaのクラスを継承することもできます。

型引数が絡んでくるとうまくいかない事もあります。上手く行ったり行かなかったりするのもストレス高いです。特に最初のうちは何が悪いか見当がつかないので、ScalaとJavaを混ぜるとつらい、という気持ちになります。

「何度もcleanをする必要がある」「Javaを全コンパイルしてからScalaがコンパイルされる」というのはプロジェクトが大きくなればなるほど効いてきます。IntelliJ IDEAでは差分コンパイルが効いているようで、同じマシンを使っていても体感でEclipseの方が断然遅いです。

## mavenでビルドする

ここでコマンドラインでビルドしてみましょう。プロジェクトのディレクトリに行って

```
mvn build
```
最初の一回が長いのは我慢します。「scalac error: bad option: '-make:transitive'」あ、これ初めて見た気がします。pom.xmlのscala-maven-pluginからこのタグを消します。もう一度ビルドすると

```
[ERROR] /home/.../scalatest/src/main/scala/jp/lavans/scalatest/App.scala:18: error: not found: type Person
[ERROR] class SPerson(_sname: String) extends Person{
```
Personが無いと怒られます。

## pom.xml
　さて、みんな大好きpom.xml(怒)です。実はここが一番はまりました。まずはpom.xmlを見てみると

 ```xml:pom.xml
   <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <plugins>
      <plugin>
```
ソースディレクトリが指定してありますがjavaがありません。しかもこのタグは複数指定できませんでした。指定するには別のプラグインを使います(怒)。

```xml:pom.xml
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>add-source</id>
            <phase>generate-sources</phase>
            <goals>
              <goal>add-source</goal>
            </goals>
            <configuration>
              <sources>
                <source>src/main/scala</source>
                <source>src/main/java</source>
              </sources>
            </configuration>
          </execution>
        </executions>
      </plugin>
 ```
 
 ちょっとディレクトリ足すのに長すぎませんかね。
 
 ```
 mvn compile
 ```
 で普通にコンパイルできました。前は" "Plugin execution not covered by lifecycle configuration"のエラーが出てpluginExecutionFilterとか指定していたのですが少し楽になりましたね(怒)。
 
 古いEclipseを使わざるを得ない人もいると思うので、lifecycleのエラーを消すためのタグも書いておきます。

 ```xml:pom.xml
 <build>
    ...
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.eclipse.m2e</groupId>
          <artifactId>lifecycle-mapping</artifactId>
          <version>1.0.0</version>
          <configuration>
            <lifecycleMappingMetadata>
              <pluginExecutions>
                <pluginExecution>
                  <pluginExecutionFilter>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <versionRange>[1.0.0,)</versionRange>
                    <goals>
                      <goal>add-source</goal>
                    </goals>
                  </pluginExecutionFilter>
                  <action>
                    <execute>
                      <runOnIncremental>false</runOnIncremental>
                    </execute>
                  </action>
                </pluginExecution>
                <pluginExecution>
                  <pluginExecutionFilter>
                    <groupId>net.alchim31.maven</groupId>
                    <artifactId>scala-maven-plugin</artifactId>
                    <versionRange>[1.0.0,)</versionRange>
                    <goals>
                      <goal>compile</goal>
                      <goal>testCompile</goal>
                    </goals>
                  </pluginExecutionFilter>
                  <action>
                    <execute>
                      <runOnIncremental>false</runOnIncremental>
                    </execute>
                  </action>
                </pluginExecution>
              </pluginExecutions>
            </lifecycleMappingMetadata>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
```
 
## 数々のデメリット

「いい加減Javaからステップアップしたい、でも一からScalaは厳しい」そう考えて迷い込んだ道でしたが、環境構築だけで難関でした。

* 謎のエラーがでてcleanビルドしなければいけない
* Javaのプログラムに一部でもコンパイルエラーがあるとScalaのコンパイルをしてくれない
* pom.xmlが人外向け

プログラムを書いていてもいろいろと問題が出ます。

* JavaからScalaを利用する場合は、Scala objectがClass\$MODULE\$のような名前になったり内部クラスがClass\$1になってソースが見にくい
* ScalaからJavaを利用する場合はコレクションの変換する処理が入り、どれがScalaのものでどれがJavaのものかわかりにくい。
* ScalaとJavaで流儀が違うものが混在すると読みにくい(アクセッサ等)。


### 解決策

結局ソースを綺麗に保つためにScala部分とJava部分を明確に分けて、間にラッパーを作ることになります。それぞれをmavenでjarとして生成し、元のシステムに取り込むようにしましょう。

新規Scalaプロジェクトで、純粋なScalaモデルとサービス層を作ります。別途Javaプロジェクトで、当該サービスとScala側の橋渡しをするだけのプロジェクトを作ります。元のプロジェクトからはこのJavaラッパーだけに依存して、Scalaの知識を必要としないような造りにするのがいいと思います。ちなみに、ScalaからJavaのライブラリを使うときも、Scalaっぽく書きたくてついついラッパーを書く人も多いようです。

ScalaとJavaではプログラム内で相互変換できますが、可能であればそれぞれ独立したサービスにしてjsonやProtocolBuffersでやりとりするに越したことはありません。Scalaも過渡期の技術で、きっとまた便利なものがでてくるでしょう。システムを漸進的に置き換え可能にするには、出来るだけ小さく作り疎結合にしておくに限ります。そうすればその部分だけを新しい技術に置き換えられますからね。

余談はともかく、mavenプロジェクトでScalaを入れようとしてpomになぶられた話は以上です。