В этом уроке вы узнаете о совместимости с Java.

Javap

javap – это это инструмент, который поставляется вместе с JDK. Не с JRE. Между ними есть разница. Javap декомпилирует определения класса и показывает вам, что внутри. Использовать его довольно просто

[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait
Compiled from "Scalaisms.scala"
public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
    public abstract java.lang.String traitName();
    public abstract java.lang.String upperTraitName();
}

Если вы преподчитаете что-нибудь пожестче, то можете поглядеть на байткод

[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap -c MyTrait\$class
Compiled from "Scalaisms.scala"
public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
  Code:
   0:	aload_0
   1:	invokeinterface	#12,  1; //InterfaceMethod com/twitter/interop/MyTrait.traitName:()Ljava/lang/String;
   6:	invokevirtual	#17; //Method java/lang/String.toUpperCase:()Ljava/lang/String;
   9:	areturn

public static void $init$(com.twitter.interop.MyTrait);
  Code:
   0:	return

}

Если вы удивлены почему какие-то вещи не работают в среде Java, доставайте javap!

Классы

Следует учитывать 4 свойства при использовании Scala класса в Java

Мы спроектируем простой Scala класс, чтобы показать полный спектр возможностей

package com.twitter.interop

import java.io.IOException
import scala.throws
import scala.reflect.{BeanProperty, BooleanBeanProperty}

class SimpleClass(name: String, val acc: String, @BeanProperty var mutable: String) {
  val foo = "foo"
  var bar = "bar"
  @BeanProperty
  val fooBean = "foobean"
  @BeanProperty
  var barBean = "barbean"
  @BooleanBeanProperty
  var awesome = true

  def dangerFoo() = {
    throw new IOException("SURPRISE!")
  }

  @throws(classOf[IOException])
  def dangerBar() = {
    throw new IOException("NO SURPRISE!")
  }
}

Параметры класса

class SimpleClass(acc_: String) {
  val acc = acc_
}

что делает его доступным из кода на Java подобно другим val

Val переменные

Var переменные

foo$_eq("newfoo");

BeanProperty

Вым можете комментировать val и var переменные с помощью @BeanProperty. Она генерирует геттеры/сеттеры подобно тому, как определяются POJO геттеры/сеттеры. Если вам нужен вариант isFoo, используйте аннотацию BooleanBeanProperty. Ранее уродливый foo$_eq становится

setFoo("newfoo");
getFoo();

Исключения

Scala не проверяет исключения. Java проверяет. Мы не будем вдаваться, в этот философский спор, но это важно, когда вы хотите иметь возможность отлавливать исключения в Java. Определения dangerFoo и dangerBar демонстрируют это. В Java я не могу сделать этого

        // исключение стирается!
        try {
            s.dangerFoo();
        } catch (IOException e) {
            // Плохой стиль
        }

Java жалуется, что s.dangerFoo никогда не бросает IOException. Мы можем попытаться отлавливать Throwable, но это “хромое” р��шение.

Вместо этого, для хорошего пользователя Scala достойная идея, это использовать бросания аннотаций, как мы делали в dangerBar. Это позволяет нам продолжить использование отмеченных исключений в Java.

Что еще почитать оп этой теме

Полный список Scala аннотаций для поддержки совместимости Java можно найти здесь http://www.scala-lang.org/node/106.

Трейты

Как можно получить интерфейс + реализация? Давайте возьмем простое определение трейта и посмотрим

trait MyTrait {
  def traitName:String
  def upperTraitName = traitName.toUpperCase
}

Этот трейт имеет один абстрактный метод (traitName) и один реализованный метод (upperTraitName). Что Scala генерирует для нас? Интерфейс с именем MyTrait, и соответствующую реализацию с именем MyTrait$class.

Реализация MyTrait, то что вы ожидаете

[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait
Compiled from "Scalaisms.scala"
public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
    public abstract java.lang.String traitName();
    public abstract java.lang.String upperTraitName();
}

Реализация MyTrait$class более интерресна

[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait\$class
Compiled from "Scalaisms.scala"
public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
    public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
    public static void $init$(com.twitter.interop.MyTrait);
}

MyTrait$class имеет только статичные методы, которые принимает экземпляр MyTrait. Это дает нам ключ к пониманию того, как расширить трейт в Java.

Наша первая попытка реализована ниже

package com.twitter.interop;

public class JTraitImpl implements MyTrait {
    private String name = null;

    public JTraitImpl(String name) {
        this.name = name;
    }

    public String traitName() {
        return name;
    }
}

И мы получаем следущую ошибку

[info] Compiling main sources...
[error] /Users/mmcbride/projects/interop/src/main/java/com/twitter/interop/JTraitImpl.java:3: com.twitter.interop.JTraitImpl is not abstract and does not override abstract method upperTraitName() in com.twitter.interop.MyTrait
[error] public class JTraitImpl implements MyTrait {
[error]        ^

мы можем просто реализовать это сами. Но есть другой способ.

package com.twitter.interop;

    public String upperTraitName() {
        return MyTrait$class.upperTraitName(this);
    }

Мы можем передать этот вызов сгенерированной Scala реализации. Мы также можем переопределить его, если захотим.

Объекты

Объекты – это способ реализации статических методов/синглтонов на Scala. Использование их в Java является немного странным. Существует не такой стилистически идеальный способ, чтобы использовать их, но в Scala 2.8 об этом не стоит беспокоиться

Объект Scala компилируется в класс, имя которого заканчивается на “$”. Давайте создадим класс и соответствующий объект

class TraitImpl(name: String) extends MyTrait {
  def traitName = name
}

object TraitImpl {
  def apply = new TraitImpl("foo")
  def apply(name: String) = new TraitImpl(name)
}

Мы можем просто обратиться к нему в Java, например так

MyTrait foo = TraitImpl$.MODULE$.apply("foo");

Сейчас вы можете спросить себя, что это за чушь? Это верно. давайте посмотрим что внутри TraitImpl$

local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap TraitImpl\$
Compiled from "Scalaisms.scala"
public final class com.twitter.interop.TraitImpl$ extends java.lang.Object implements scala.ScalaObject{
    public static final com.twitter.interop.TraitImpl$ MODULE$;
    public static {};
    public com.twitter.interop.TraitImpl apply();
    public com.twitter.interop.TraitImpl apply(java.lang.String);
}

Там на самом деле нет никаких статических методов. Вместо этого он имеет статический член с именем MODULE$. Реализация метода передает все этому члену. Это делает доступ уродливым, но работоспособным, если вы знаете как использовать MODULE$.

Методы переадресации

В Scala 2.8 работать с объектами стало немного проще. Если у вас есть класс с соответствующим объектом, на 2.8 компилятор сгенерирует методы переадресации на класс-спутник. Так что, если вы написали код на 2.8, вы можете получить доступ к методам в объекте TraitImpl, например так

MyTrait foo = TraitImpl.apply("foo");

Замыкания

Одной из важных особенностей Scala является возможность использования функций первого рода. Давайте определим класс, который имеет методы, принимающие функции в качестве аргументов.

class ClosureClass {
  def printResult[T](f: => T) = {
    println(f)
  }

  def printResult[T](f: String => T) = {
    println(f("HI THERE"))
  }
}

В Scala я могу вызвать их, например так

val cc = new ClosureClass
cc.printResult { "HI MOM" }

В Java это не так просто, но тоже не сложно. Давайте посмотрим из чего на самом деле состоит ClosureClass:

[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap ClosureClass
Compiled from "Scalaisms.scala"
public class com.twitter.interop.ClosureClass extends java.lang.Object implements scala.ScalaObject{
    public void printResult(scala.Function0);
    public void printResult(scala.Function1);
    public com.twitter.interop.ClosureClass();
}

Все не так страшно. “f: => T” переводится в “Function0”, а “f: String => T” переводится в “Function1”. Scala фактически объявляет Function0 через Function22, поддерживающая до 22 аргументов. Которой, на самом деле, достаточно.

Теперь нам нужно определить, как это сделать в Java. Оказывается Scala предоставляет AbstractFunction0 и AbstractFunction1, которые мы можем передать вот так

    @Test public void closureTest() {
        ClosureClass c = new ClosureClass();
        c.printResult(new AbstractFunction0() {
                public String apply() {
                    return "foo";
                }
            });
        c.printResult(new AbstractFunction1<String, String>() {
                public String apply(String arg) {
                    return arg + "foo";
                }
            });
    }

Заметьте, что мы можем использовать обобщенные функции, что определить параметры аргументов.