В этом уроке вы узнаете о совместимости с 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 можно найти здесь https://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";
                }
            });
    }

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