Implementering av ett java interface direkt på ett scala objekt utan att behöva skapa en ny klass

Om två Java-klasser definierar en gemensam metodsignatur men utan att den metoden finns definierad i en gemensam bastyp, så kan du inte (med Java) enkelt återanvända kod som använder metoden utan måste både skapa ett interface samt klasser som implementerar interfacet, som visserligen är enkelt men ändå omständigt.

Från Scala behöver du däremot inte skapa klasserna som implementerar interfacet, utan det räcker med att hänvisa till Java interfacet (alternativt ett Scala "trait") vid själva instansieringen, dvs
'new KlassMedEnVissMetodSignatur() with InterfaceSomDefinierarMetoden'
och sedan kan man använda den instansen på vanligt sätt, dvs man kan skicka in den till en metod som tar emot parameter som är typad som interfacet och som sedan kan använda instansen med vanliga polymorfiska anrop.

Nedan följer ett lite mer utförligt exempel med två existerande Java-klasser med en gemensam metodsignatur men utan att den metoden finns i ett gemensamt interface.

Betrakta de båda java-klasserna BufferedReader och RandomAccessFile.
Båda klasserna implementerar en metod med följande signatur:
public String readLine() throws IOException

Antag nu att du vill skapa en metod som skall kunna ta emot en instans av någon av dessa klasser, och som sedan bara behöver använda sig av denna metod som existerar i båda klasserna. Det skulle du enkelt kunna göra, om det vore på det viset att dessa klasser har en gemensam bastyp och om den bastypen skulle definiera den aktuella readLine-metoden. Någon sådan gemensam basklass eller interface existerar dock inte, utan arvshierarkin för klasserna ser nämligen ut på följande sätt:
RandomAccessFile extends Object
BufferedReader extends Reader extends Object

Det man kan göra i Java är att själv definiera ett interface som definierar den aktuella metoden och att sedan skriva en generell metod som endast använder den metoden, dvs man kan skriva följande kod alltså:

	public interface LineReader {
		String readLine() throws IOException;
	}
		
och sedan kan man använda det interfacet i en metod (i någon klass) som endast använder den aktuella metoden på interfacet, dvs en metod som ser ut något i stil med följande:
	public void printAllLines(LineReader lineReader) throws IOException {
		String line;
		while( (line = lineReader.readLine()) != null) {
			[ o.s.v. ]
		

Problem uppstår dock i Java när du skall försöka anropa metoden ovan med en instans av BufferedReader eller RandomAccessFile, för trots att de båda klasserna visserligen implementerar metoden readLine (som nu finns i det egendefinierade interfacet 'LineReader') så kan du ändå inte skicka in sådana objekt som parametrar till metoden, dvs följande kod fungerar alltså inte:
File file = new File("C:\\temp\\myTestFile.txt")
BufferedReader bufferedReader = new BufferedReaderLineReader(new FileReader(file));
printAllLines(bufferedReader)

Nu skulle man därför gärna vilja kunna tala om för kompilatorn att man vill typa sitt objekt med interfacet, eftersom den faktiskt implementerar interfacet dvs faktiskt implementerar den enda metodsignaturen i interfacet, dvs följande Java-kod skulle vara önskvärt (men fungerar tyvärr inte):
BufferedReader lineReader = new BufferedReaderLineReader(new FileReader(file)) implements LineReader;
printAllLines(lineReader)

Om man i stället använder Scala så kan man faktiskt göra precis detta dvs man kan implementera ett interface för ett specifikt objekt m.h.a. följande Scala-syntax:
val lineReader = new BufferedReader(new FileReader(file)) with LineReader

Observera att Scala är statiskt typat (se FAQ) dvs man kan inte skriva 'with LineReader' när man instansierar vilket objekt som helst, bara för att sedan få ett RunTimeException, utan om det aktuella objektet inte definierar metoden så får man då i stället ett kompileringsfel.

Observera också att ovanstående implementation av LineReader som instansierades av Scala kan användas av Java-kod, samtidigt som Scala kan använda Java-kod. Denna kompatibiliteten mellan Java och Scala gör att det finns goda möjligheter att succesivt börja använda ny Scala-kod från din Java-kod, samtidigt som du kan återanvända all existerande Java-kod från din nya Scala-kod.

En potentiell, men mer omständlig jämfört med Scala, lösning för att med Java implementera interfacet i exemplet ovan är att ärva den aktuella klassen som då automatiskt implementerar det nya interfacet, men det är alltså nödvändigt att skapa en sådan subklass-instans för att den ska bli typad enligt interfacet med den metod som redan finns där:

	class BufferedReaderLineReader extends BufferedReader implements LineReader {
		public BufferedReaderLineReader(final Reader in)  {
			super(in);
		}
	}
		
En alternativ, men också omständlig, Java-lösning är att implementera interfacet genom att använda komposition, dvs att wrappa klassen BufferedReader och sedan delegera vidare anropet till den.
	class BufferedReaderLineReader implements LineReader {
		private final BufferedReader bufferedReader;
		BufferedReaderLineReader(final BufferedReader bufferedReader) {
			this.bufferedReader = bufferedReader;
		}
		public String readLine() throws IOException {
			return bufferedReader.readLine();
		}
	}
		

Visserligen är de båda ovanstående Java-lösningarna triviala, men ändå onödigt omständliga jämfört med att låta Scala säga till kompilatorn att instansen implementerar det aktuella interfacet.

Mer detaljerad källkod (och kommenterarer på engelska) finns på websidan Scala can implement a Java interface for an object without creating a class that implements the interface

/ Tomas Johansson, 2010-01-19