Scala can implement a Java interface for an object without creating a class that implements the interface

The purpose of this article is to mainly illustrate these two things:
(1) That Scala can be gradually introduced into your project/organization because of the nice interoperability with Java (in this code below, there is very little Scala code, but only some Scala implementations of a Java interface)
(2) That you can implement an interface for a specific instance, without having to create a class that implements the interface

The initial scenario is that we have some duplicated code which you want to refactor away, but with Java you will need to write a bit more code than you would like to, while the Scala solution is a bit shorter.

In the class below, there are two methods that contain exactly the same implementation, but they have different parameters. One of the method receives an instance of the class RandomAccessFile, while the other receives an instance of BufferedReader.

It is very important (for the purpose of this example) to note that these two classes do NOT ( ! ) share a common base type (except Object) that contains the only used method 'readLine', because if there were such a base type, then we would be done by simply removing one of the methods, and change the formal parameter type to that common base type with the mehod 'readLine' which then could be invoked with normal polymorphism.

public class ConsolePrinter {

    // Note the following inheritance:
    //   "RandomAccessFile extends Object"
    //   "BufferedReader extends Reader extends Object"
    // i.e. RandomAccessFile and BufferedReader do NOT share a common
    // base type (except Object) with a readLine method

    // These two methods below are almost duplicated,
    // (except from the parameter type and name)
    // and will shortly become refactored into one method 	

    public final void printAllLines(final RandomAccessFile randomAccessFile)
        throws IOException
    {
        String line;
        while( (line = randomAccessFile.readLine()) != null) {
            printRow(SOME_HYPHENS);
            printRow(line);
            printRow(line.toUpperCase());
            printRow(line.toLowerCase());
            printRow(SOME_HYPHENS);
        }
    }


    public final void printAllLines(final BufferedReader bufferedReader)
        throws IOException
    {
        String line;
        while( (line = bufferedReader.readLine()) != null) {
            printRow(SOME_HYPHENS);
            printRow(line);
            printRow(line.toUpperCase());
            printRow(line.toLowerCase());
            printRow(SOME_HYPHENS);
        }
    }

    private void printRow(String s) {
        printStream.println(s);
    }

    static final String SOME_HYPHENS = "---";
    
    ConsolePrinter() {
        this(System.out); // print to the console, when using default constructor
    }
    ConsolePrinter(final PrintStream printStream) {
        // Can be used for the purpose of using a Test Spy for the PrintStream
        this.printStream = printStream;
    }    
    private final PrintStream printStream;
}

Both classes BufferedReader and RandomAccessFile has a method signature like this:
public String readLine() throws IOException
but the problem is that the method is not defined in a common interface, which we would like. So, we will have to define such an interface, and then refactor the above code into only one method, using that interface:


import java.io.IOException;
public interface LineReader {
    String readLine() throws IOException;
}


public class ConsolePrinter {

    // Now the class is refactored into ONE method using an interface
    // instead of the two previously duplicated methods with
    // different parameters

    public final void printAllLines(
        final LineReader lineReader
    )
        throws IOException
    {
        String line;
        while( (line = lineReader.readLine()) != null) {
            printRow(SOME_HYPHENS);
            printRow(line);
            printRow(line.toUpperCase());
            printRow(line.toLowerCase());
            printRow(SOME_HYPHENS);
        }
    }
	
	... the rest as before ...
}

Now the problem is how to let the RandomAccessFile and BufferedReader instances implement the new interface 'LineReader', to be able to use the refactored class above, which now can only handle objects implementing the new interface.

With Java, we can not really do that (but with Scala we can, as will be shown below) but instead have to create new classes, implementing the interface, for example by extending or wrapping the classes (BufferedReader and RandomAccessFile)

We will hide that kind of code (i.e. the code that uses a RandomAccessFile or a BufferedReader to implement the new interface) within the implementation of factory classes (which later will also be implemented by Scala in an alternative easier way without having to use new classes, but will create implementations on the fly so to speak).

The below factory interface 'LineReaderFactory' will define a method with a File object as parameter, and one of the two Java implementations of the factory will use a RandomAccessFile to create a 'LineReader' implementation, while the other will instead use a BufferedReader.

The reason for the suffix 'Java' in the names for these two classes below is that we will further down use Scala implementations to create two similar (but simpler) factory classes.

import java.io.File;
public interface LineReaderFactory {
    LineReader createLineReader(File file);
}


// In the Java code below, there is a class that implements the interface as declared in the bold parts below, 
// but further down in bold Scala code, the instances will implement the interface without having to create classes

import java.io.File;
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.BufferedReader;
import java.io.Reader;
public final class LineReaderFactoryBufferedReaderJava implements LineReaderFactory {

    public LineReader createLineReader(final File file)  {
        try {
            return new BufferedReaderLineReader(new FileReader(file));
        } catch (FileNotFoundException e) {	
			// FileNotFoundException is a checked exception needed to be 
			// while RuntimeException is an unchecked exception
            throw new RuntimeException(e);
        }
    }

    // It would indeed be appropriate to implement code that will invoke the method 
    // 'BufferedReaderLineReader.close()' but that kind of cleanup is not used here
    // to keep the focus of the code sample and reduce extra code not 
    // really relevant for the purpose, i.e. to illustrate how to avoid using 
    // a class to implement an interface (which this java code below does)


    // This implementation of the LineReader is using inheritance of the BufferedReader, but could be implemented
    // by wrapping it instead, as the other factory implementation is doing with the RandomAccessFile below

	private final static class BufferedReaderLineReader extends BufferedReader implements LineReader {
        public BufferedReaderLineReader(final Reader in)  {
            super(in);
        }
    }
    
}


import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public final class LineReaderFactoryRandomAccessFileJava implements LineReaderFactory {

    public LineReader createLineReader(final File file) {
        try {
            return new RandomAccessFileWrapper(new RandomAccessFile(file, "r"));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    // It would indeed be appropriate to implement code that will invoke the method
    // 'RandomAccessFile.close()' but that kind of cleanup is not used here
    // to keep the focus of the code sample and reduce extra code not
    // really relevant for the purpose, i.e. to illustrate how to avoid using
    // a class to implement an interface (which this java code below does)

    // This implementation of the LineReader is using wrapping of the RandomAccessFile, but could be implemented
    // by inheriting it instead, as the other factory implementation is doing with the BufferedReader above     

    private final static class RandomAccessFileWrapper implements LineReader {
        private final RandomAccessFile randomAccessFile;
        private RandomAccessFileWrapper(final RandomAccessFile randomAccessFile) {
            this.randomAccessFile = randomAccessFile;    
        }
        public String readLine() throws IOException {
            return randomAccessFile.readLine();
        }
    }
}

Now we can (as in the test code below) use the above created factories to create instances of LineReader, which will be able to invoke the 'readLine' method with the same result, i.e. both implementations will work in the same way.

import org.apache.commons.io.FileUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNull;
public class LineReaderTest {

    private final static List<LineReaderFactory> LINE_READER_FACTORIES = Arrays.asList(
        new LineReaderFactoryBufferedReaderJava(),
        new LineReaderFactoryRandomAccessFileJava()
    );

    @Test
    public void verifyThatAllExpectedLinesInFileCanBeReadByAllLineReaders() throws IOException {
        for (LineReaderFactory lineReaderFactory : LINE_READER_FACTORIES) {
            verifyThatAllExpectedLinesInFileCanBeReadByLineReader(lineReaderFactory);
        }
    }

    private void verifyThatAllExpectedLinesInFileCanBeReadByLineReader(
        final LineReaderFactory lineReaderFactory
    ) throws IOException
    {
        final LineReader lineReader = lineReaderFactory.createLineReader(tempFile);
        assertEquals(FILE_CONTENT_LINE_1, lineReader.readLine());
        assertEquals(FILE_CONTENT_LINE_2, lineReader.readLine());
        assertEquals(FILE_CONTENT_LINE_3, lineReader.readLine());
        assertNull(lineReader.readLine());
    }

    @BeforeClass
    public static void beforeClass() throws IOException {
        tempFile = File.createTempFile("LineReaderTest", "");

        FileUtils.writeLines(
            tempFile,
            Arrays.asList(
                FILE_CONTENT_LINE_1,
                FILE_CONTENT_LINE_2,
                FILE_CONTENT_LINE_3
            )
        );
    }

    @AfterClass
    public static void afterClass() throws IOException {
        tempFile.delete();
    }

    private static File tempFile;
    // the three rows that the above file will be populated in the method beforeClass
    private final static String FILE_CONTENT_LINE_1 = "Row 1";
    private final static String FILE_CONTENT_LINE_2 = "Row 2";
    private final static String FILE_CONTENT_LINE_3 = "Row 3";
}

As you have seen above, in the two factory methods implemented with Java it was necessary to create new classes for BufferedReader and RandomAccessFile, even though they both already contained the desired method signature in the LineReader interface.

However, with Scala, as you will in the code example below, it is not necessary to create those two new classes, but you can instead let the instance implement the desired interface, by using the "with" construct as below.


// With the bold Scala code below, the instances will implement the interface, while the Java implementations 
// of the factory class (further up in bold code) had to use classes for implementing the interface


// Scala code :
import java.io.{File, RandomAccessFile}
class LineReaderFactoryRandomAccessFileScala extends LineReaderFactory {
  def createLineReader(file: File): LineReader = {
    val randomAccessFileWithLineReader = new RandomAccessFile(file, "r") with LineReader
    return randomAccessFileWithLineReader // the return keyword is optional in Scala, but used here to make it easier for Java developers to understand 
  }
}

// Scala code :
import java.io.{BufferedReader, File, FileReader}
class LineReaderFactoryBufferedReaderScala extends LineReaderFactory {
  def createLineReader(file: File): LineReader = {
    val bufferedReaderWithLineReader = new BufferedReader(new FileReader(file)) with LineReader
    bufferedReaderWithLineReader // the return keyword is optional in Scala, i.e. not needed here
  }
}

To further illustrate the nice interoperability with Java, we can add the above implementations of the factory classes into the Java code as below:

public class LineReaderTest {

    private final static List<LineReaderFactory> LINE_READER_FACTORIES = Arrays.asList(
        new LineReaderFactoryBufferedReaderJava(),
        new LineReaderFactoryRandomAccessFileJava(),
        new LineReaderFactoryBufferedReaderScala(),
        new LineReaderFactoryRandomAccessFileScala()
    );
	... the rest of code as previously... 
}

To sum up, we have above illustrated how you (with Scala !) can let an object be typed with an interface if it already defines the method in the interface, but with Java, you still had to write extra code by first creating a class that implements the interface.

We have also illustrated how Scala can interoperate with Java. Most of the above code is indeed Java, and from the Java code we invoked a Scala implementation of a Java interface, which returned another Java interface.

In other words, we had the Java interface 'LineReaderFactory' which was implemented by two Scala classes (e.g. LineReaderFactoryRandomAccessFileScala) and its creation method returned an instance which implemented the Java interface LineReader (but could create such an instance without creating a new class!).

/ Tomas Johansson, Stockholm, Sweden