Spesso raccomando la trasformazione AST di Groovy @Immutable
come un modo semplice per rendere le classi, bene, immutabili. Questo funziona sempre bene con altre classi di Groovy, ma qualcuno mi ha chiesto di recente se potessi mescolare quelle classi nel codice Java. Ho sempre pensato che la risposta fosse sì, ma sto colpendo un ostacolo.Groovy classi @Immutable in Java
Dire che ho un immutabile User
classe:
import groovy.transform.Immutable
@Immutable
class User {
int id
String name
}
Se provo questo utilizzando un test JUnit scritto in Groovy, tutto funziona come previsto:
import org.junit.Test
class UserGroovyTest {
@Test
void testMapConstructor() {
assert new User(name: 'name', id: 3)
}
@Test
void testTupleConstructor() {
assert new User(3, 'name')
}
@Test
void testDefaultConstructor() {
assert new User()
}
@Test(expected = ReadOnlyPropertyException)
void testImmutableName() {
User u = new User(id: 3, name: 'name')
u.name = 'other'
}
}
posso fare lo stesso con un JUnit test scritto in Java:
import static org.junit.Assert. *;
import org.junit.Test;
public class UserJavaTest {
@Test
public void testDefaultCtor() {
assertNotNull(new User());
}
@Test
public void testTupleCtor() {
assertNotNull(new User(3, "name"));
}
@Test
public void testImmutableName() {
User u = new User(3, "name");
// u.setName("other") // Method not found; doesn't compile
}
}
Questo funziona, anche se ci sono problemi all'orizzonte. IntelliJ 15 non ama la chiamata a new User()
, sostenendo che il costruttore non è stato trovato. Ciò significa anche che l'IDE sottolinea la classe in rosso, il che significa che ha un errore di compilazione. Il test passa comunque, il che è un po 'strano, ma così sia.
Se provo a utilizzare direttamente la classe User
nel codice Java, le cose iniziano a diventare strane.
public class UserDemo {
public static void main(String[] args) {
User user = new User();
System.out.println(user);
}
}
Ancora una volta IntelliJ non è felice, ma compila e gira. L'uscita è, di tutte le cose:
User(0)
Questo è strano, perché anche se il @Immutable
trasformazione non genera un metodo toString
, io invece lo aspettavo l'uscita di mostrare entrambe le proprietà. Tuttavia, ciò potrebbe essere dovuto al fatto che la proprietà name
è null, quindi non è inclusa nell'output.
Se cerco di utilizzare il costruttore tuple:
public class UserDemo {
public static void main(String[] args) {
User user = new User(3, "name");
System.out.println(user);
}
}
ottengo
User(0, name)
come l'uscita, almeno questa volta (a volte non funziona affatto).
Quindi ho aggiunto un file di build Gradle. Se metto le classi Groovy sotto src\main\groovy
e le classi Java in src\main\java
(lo stesso per i test, ma utilizzando la cartella test
invece), immediatamente ottengo un problema di compilazione:
> gradle test
error: cannot find symbol
User user = new User(...)
^
Io di solito risolvere i problemi di cross-compilazione come questo cercando di utilizzare il compilatore Groovy per tutto. Se metto entrambe le classi sotto src\main\java
, non cambia nulla, che non è una grande sorpresa. Ma se ho messo entrambe le classi sotto src\main\groovy
, allora ottengo questo durante la fase di compileGroovy
:
> gradle clean test
error: constructor in class User cannot be applied to the given types;
User user = new User(3, "name");
required: no arguments
found: int,String
reason: actual and formal arguments differ in length
Huh. Questa volta si oppone al costruttore di tuple, perché pensa che abbia solo un costruttore predefinito. So che la trasformazione aggiunge un predefinito, una mappa e un costruttore di tupla, ma forse non vengono generati in tempo perché il codice Java possa vederli.
Per inciso, se separare le classi Java e Groovy di nuovo, e aggiungere il seguente al mio Gradle costruire:
sourceSets {
main {
java { srcDirs = []}
groovy { srcDir 'src/main/java' }
}
}
ottengo lo stesso errore. Se non aggiungo il blocco sourceSets
, ottengo l'errore di classe User
non trovato in precedenza.
Quindi la linea di fondo è, qual è il modo corretto per aggiungere una classe @Immutable
Groovy a un sistema Java esistente? C'è un modo per generare i costruttori in tempo perché Java li veda?
Ho fatto presentazioni Groovy agli sviluppatori Java per anni e ho detto che si può fare questo, solo per ora incorrere in problemi. Per favore aiutami a salvare la faccia in qualche modo. :)
Gli IDE tendono a dare falsi negativi quando si tratta di Groovy, specialmente con le trasformazioni AST. Per il problema della compilazione incrociata potresti provare a creare due progetti separati, uno java e uno groovy. Se questa è ancora una domanda irrisolta, gli darò un vortice al mattino. Questo sembra davvero dovrebbe funzionare. – cjstehno
Il comportamento è lo stesso con '@ CompileStatic'? – dmahapatro
'@ CompileStatic' non modifica nulla – kousen