11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-02-17

はじめに

一から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

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クラスを作ります。

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から呼び出してみます。

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の下の方にそのまま書いてしまいます。

App.scala
case class SPerson(_sname: String) extends Person

mainメソッドで出力します。

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を見てみると

pom.xml
  <build>
   <sourceDirectory>src/main/scala</sourceDirectory>
   <testSourceDirectory>src/test/scala</testSourceDirectory>
   <plugins>
     <plugin>

ソースディレクトリが指定してありますがjavaがありません。しかもこのタグは複数指定できませんでした。指定するには別のプラグインを使います(怒)。

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のエラーを消すためのタグも書いておきます。

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になぶられた話は以上です。

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?