В этом уроке вы узнаете о совместимости с Java.
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
foo$_eq("newfoo");
Вым можете комментировать 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";
}
});
}
Заметьте, что мы можем использовать обобщенные функции, что определить параметры аргументов.