[java] compiler ziet overeenkomst interfaces niet

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • 2playgames
  • Registratie: Februari 2005
  • Laatst online: 01-06 15:19
Ik heb de volgende drie interfaces

Java:
1
2
3
4
5
6
7
8
9
10
11
interface A {
A foo();
}

interface B {
B foo();
}

interface C extends A, B {
C foo();
}

C.foo() kan C teruggeven en dan nog steeds aan interface A en B voldoen, want C is een A en ook een B.
De ingebouwde compiler van Eclipse ziet dat en compileert dan ook netjes.
Als ik echter javac aanroep vanuit een Ant-buildfile krijg ik
types A and B are incompatible; both define foo(), but with unrelated return types
Wat kan ik hier aan doen?

Dit is de (verder niet bijzondere) javac-task:
code:
1
2
3
4
5
6
7
8
        <javac
            source="1.6"
            target="1.6"
            srcdir="${dirs.src}" 
            destdir="${dirs.bin}"
            classpathref="classpath"
            optimize="true"
        />

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 21:23
javac heeft gelijk: A en B zijn verschillende types, dus is een methode die A returnt een andere methode dan een methode die B return. Java ondersteunt alleen overloading op basis van de typen van de parameters van een methode, níet op de return types, dus als twee methoden een ander return type hebben, dan mogen ze niet én dezelfde naam én dezelfde parameters hebben. (Je kunt dus niet in één klasse zowel een methode int foo() als een methode String foo() definiëren).

Vandaar dat een klasse die én interface A én interface B implementeert, dus niet bestaan, en kan aan interface C dus nooit voldaan worden, en blijkbaar weigert javac de interface declaratie dan.

[ Voor 34% gewijzigd door Soultaker op 28-11-2008 21:04 ]


Acties:
  • 0 Henk 'm!

  • 2playgames
  • Registratie: Februari 2005
  • Laatst online: 01-06 15:19
Maar zoals ik aangeef, is dit geval gewoon geldig.

C.foo() geeft C terug, maar is nog steeds compatibel met A.foo() en B.foo().
A.foo() geeft A terug, maar C is ook een vorm van A
B.foo() geeft B terug, maar C is ook een vorm van B

misschien wat concreter

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Boot {
 Boot getMe();
}

interface Vliegtuig {
 Vliegtuig getMe();
}

interface WaterVliegtuig extends Boot, Vliegtuig {
 WaterVliegtuig getMe();
}

Boot b = eenWaterVliegtuig.getMe(); // moet kunnen toch
Vliegtuig v = eenWaterVliegtuig.getMe(); // maar dit ook


Het zal toch niet zo zijn dat de eclipse-compiler een bug bevat, en het daarom onterecht accepteert? (ja dat kan natuurlijk, maar die kans lijkt me kleiner).

(p.s. mijn interfaces heten in het echt Transformable, Geometry en TransformableGeometry met de methode move(Vector2))

[ Voor 30% gewijzigd door 2playgames op 28-11-2008 21:30 ]


Acties:
  • 0 Henk 'm!

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 26-09 17:46
2playgames schreef op vrijdag 28 november 2008 @ 21:27:
Maar zoals ik aangeef, is dit geval gewoon geldig.

C.foo() geeft C terug, maar is nog steeds compatibel met A.foo() en B.foo().
A.foo() geeft A terug, maar C is ook een vorm van A
B.foo() geeft B terug, maar C is ook een vorm van B

misschien wat concreter

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Boot {
 Boot getMe();
}

interface Vliegtuig {
 Vliegtuig getMe();
}

interface WaterVliegtuig extends Boot, Vliegtuig {
 WaterVliegtuig getMe();
}

Boot b = eenWaterVliegtuig.getMe(); // moet kunnen toch
Vliegtuig v = eenWaterVliegtuig.getMe(); // maar dit ook


Het zal toch niet zo zijn dat de eclipse-compiler een bug bevat, en het daarom onterecht accepteert? (ja dat kan natuurlijk, maar die kans lijkt me kleiner).

(p.s. mijn interfaces heten in het echt Transformable, Geometry en TransformableGeometry met de methode move(Vector2))
Het voorbeeld is valide (covariante return typen) omdat het hier om interfaces gaat. De Eclipse compiler pakt dit goed op. Javac kan er echter niet goed mee omgaan. In het bug tracking systeem van Eclipse zijn hier ook al issues over te vinden, waarin gerefereerd wordt aan de relevante delen van de JLS. Misschien interessant om eens te bekijken: https://bugs.eclipse.org/bugs/show_bug.cgi?id=122881

Acties:
  • 0 Henk 'm!

  • Robtimus
  • Registratie: November 2002
  • Laatst online: 20:22

Robtimus

me Robtimus no like you

Gezien de logica achter covariant returns lijkt het me eerder een fout in de Sun compiler. Zoals al gezegd, als iets een instance of C is dan is het automatisch ook een instance of A en een instance of B. Dus als je ergens C returned, dan is dat met covariant returns compatible met zowel A als B.

More than meets the eye
There is no I in TEAM... but there is ME
system specs


Acties:
  • 0 Henk 'm!

  • 2playgames
  • Registratie: Februari 2005
  • Laatst online: 01-06 15:19
Is er geen enkele manier om javac dit toch te laten accepteren?
Lijkt me een vrij serieuze bug, en het verbaast me dat hij na al die tijd niet is opgelost (ontdekt in 1.5, ik heb 1.6).

-karma voor Sun.

edit: ah, ik zie in dit topic dat deze bug in build 31 van jdk7 is opgelost. nu is de filisofie van mijn project gelukkig om zoveel mogelijk de nieuwste versie van software te gebruiken, dus zal ik de gebruikers die geen eclipse willen gebruiken maar aanraden jdk7 te proberen (mijn project is een library, een java game engine)

[ Voor 52% gewijzigd door 2playgames op 29-11-2008 20:12 ]


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 21:23
Hmm, ik realiseerde me niet dat Java methoden met verschillende signatures maar met covariante return types als compatibel beschouwt. Dat verklaart waarom het in Eclipse wel werkt.

Acties:
  • 0 Henk 'm!

  • Sv3n
  • Registratie: Mei 2002
  • Laatst online: 15:33
Is wel op te lossen met generics:
Java:
1
2
3
4
5
6
7
8
9
10
11
interface Boot <T extends Boot> {
 T getMe();
}

interface Vliegtuig <T extends Vliegtuig> {
 T getMe();
}

interface WaterVliegtuig extends Boot<WaterVliegtuig >, Vliegtuig<WaterVliegtuig > {
 WaterVliegtuig getMe();
} 


:)

Last.fm
Films!


Acties:
  • 0 Henk 'm!

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 07-10-2022
Soultaker schreef op vrijdag 28 november 2008 @ 22:39:
Hmm, ik realiseerde me niet dat Java methoden met verschillende signatures maar met covariante return types als compatibel beschouwt. Dat verklaart waarom het in Eclipse wel werkt.
Wat details hierover: het is geintroduceerd in Java 5, maar alleen op het niveau van Java, niet in de virtual machine. Het zou in de Java virtual machine heel lastig zijn om dit te supporten, omdat methoden aangeroepen worden met hun volledige signature (waarvan het return type een onderdeel is).

De Java compiler voegt zogenaamde 'bridge' methods toe om een methode met een covariant return type aan te roepen.

Voorbeeldje:
Java:
1
2
3
4
5
6
7
public class A {
  Number foo() { return null; }
}

class B extends A {
  Integer foo() { return null; }
}


Decompile class B met javap -c:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
class B extends A{
java.lang.Integer foo();
  Code:
   0:   aconst_null
   1:   areturn

java.lang.Number foo();
  Code:
   0:   aload_0
   1:   invokevirtual   #2; //Method foo:()Ljava/lang/Integer;
   4:   areturn
}


Zoals je kan zien is er een methode foo() toegevoegd die foo() in class A override met exact hetzelfde signature. Deze method roept simpelweg de methode foo() met return type Integer aan.

In de bytecode, heeft deze gegenereerde methode de speciale access flag BRIDGE. Helaas laat javap deze niet zien.

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 21:23
Ah, bedankt voor de uitleg, die feature kende ik inderdaad nog niet. :)

offtopic:
Leuk je hier weer eens te zien, mbravenboer.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:49

.oisyn

Moderator Devschuur®

Demotivational Speaker

C++ doet dat overigens ook, vaak eveneens geïmplementeerd met proxy methods in het geval van het voorbeeld van de TS. A::foo() moet nou eenmaal een A* moet returnen, en B::foo() een B*, wat in het geval van een C* geen fysiek identieke pointers zijn. Als B hier weggelaten wordt, en A* en C* zijn nog steeds niet identiek, dan is er echter geen proxy noodzakelijk omdat C::foo() dan gewoon altijd een A* kan returnen, en de code die de foo() op een C* aanroept weet of een afgeleide daarvan aanroept weet dat die A* ook een C* is, dus die kan dan onder water zelf casten.

[ Voor 39% gewijzigd door .oisyn op 29-11-2008 15:38 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1