Why Java Interfaces Are Terrible

John Forstmeier on November 02, 2018

So maybe they aren't terrible, but they certainly don't behave the way I was hoping that they would, being the newbie to Java that I am. My prob... [Read Full]
markdown guide
 

If one interface doesn't extend the other then no, you're not going to be able to treat an implementor of A as an implementor of B. That would be duck typing, which describes Go's type system but not Java's.

On the plus side, because Java is extremely strict about what goes where, you can just create dual constructors where one takes your DynamoImpl and the other takes an AmazonDynamoDB.

Don't do that, use a mock framework.

 

That would be duck typing, which describes Go's type system but not Java's.

This is why I should read more about language fundamentals. : )

And could you explain how the dual constructors would work? I tried that approach but when I added the argument as an attribute on the class, I would get an error (since the attribute was expected either one of the two interfaces).

 

Strictly speaking, you're "supposed" to buckle down and stub out miles of interface method implementations that throw NotImplementedExceptions and this is absolutely a kludge. It's also a little more involved than I'd thought at first (I've been writing primarily Node for years and my Java is rusty):

  • You need a wrapper (formally an "adapter") class which essentially forwards the AmazonDynamoDB methods you care about. Your Database class may be this wrapper already, in which case, great.
  • The wrapper class has two constructors: one accepts a DynamoImpl, the other an AmazonDynamoDB.
  • The wrapper class also has matching DynamoImpl and an AmazonDynamoDB fields. The "forwarding" methods choose whether to invoke dynamoImpl.putItem or amazonDynamoDB.putItem based on which one has been instantiated.

Overall I can't recommend actually doing this purely for the sake of having something easily testable. If you're using an IDE (which is not a bad idea with Java), it can stub out an interface implementation for you. Java is verbose; you kind of just have to live with it.

Very helpful; it's definitely a lot of work but it seems like that may be the one plausible solution.

 

I'm actually not sure if dual constructors would be the best course of action for what you're trying to do (unless I'm severely misinterpreting either of you two). I would personally either just use a mocking framework to automatically make a generic mock class that implements AmazonDynamoDB (which is similar to what you were attempting to do, OR just make a mock class yourself that implements it, and use your IDE or a lot of copy/pasting to basically implement all the methods you don't care about as stubs (i.e. "return null").

Dual constructors basically means: have a constructor that takes the real AmazonDynamoDB, and a constructor that takes the fake one. But then you have to basically implement code in the class you're testing, that reacts differently to whether you're using the real or fake one, and that kinda spits in the face of both polymorphism and mocking.

@forstmeier this is also good advice, look into mock frameworks like Mockito. I don't know if there's anything more current.

My thoughts exactly with the dual constructors when I realized I couldn't set the inputs of the constructors to the same attribute within the class; creating "test vs prod" switches raises red flags for me.

You can set them to the same attribute if they both inherit from/implement that attribute. For example, you could make your attribute an AmazonDynamoDB, and have a constructor that takes the real one, and a constructor that takes the fake one, and have them both set the same attribute to what they're given.

HOWEVER, this is more or less useless, because you could just make your constructor take an AmazonDynamoDB as well, and pass either type into that constructor.

 

Java and Go both use the term "interface" for very different things.

Because of its duck-typing in Go, an interface is just a collection of methods. There's really nothing more clever under the surface.

In Java, an interface is a type, in the same way that a class is a type. Realistically, the only difference between a class and an interface is that classes represent state while interfaces represent behavior. That is, interfaces can only expose methods to the world, but cannot define any fields. Classes, because they represent state, can expose fields which hold state, and also methods to modify that state (or do anything else).

Where Java classes and interfaces come together is that a class explicitly conforms to one or more interfaces. When it does this, a class effectively has multiple types. You can assign the class to another variable of that class type, or to any variable of any of its interface types.

The main takeaway is that while Go treats interfaces as facts about what an object can do, Java treats interfaces as facts about what the object is.

The problem with your example is that you're programming to the class, and not to the interface. In your example, you're treating the interface as something that can be swapped out as needed on the Database, but types are static and compile-time checked and cannot simply be swapped out. But objects can be swapped out, as long as multiple objects implement the same interface, you can swap them out however you wish. So instead of

public class Database {
    public DynamoImpl db;

    // code removed for simplicity

    public Database(DynamoImpl db) {
        this.db = db;
    }
}
public interface DynamoImpl {
  public PutItemResult putItem(PutItemRequest putItemRequest);

  public GetItemResult getItem(GetItemRequest getItemRequest);
}

What you really want to do is invert the class and interface, since you want to use a generic Database with multiple implementations.

public interface Database {
    public PutItemResult putItem(PutItemRequest putItemRequest);

    public GetItemResult getItem(GetItemRequest getItemRequest);
}
public class DynamoDatabaseImpl implements Database {
    public AmazonDynamoDB db;

    public Database(AmazonDynamoDB db) {
        this.db = db;
    }

    public PutItemResult putItem(PutItemRequest putItemRequest) {
        return db.putItem(putItemRequest)
    }

    public GetItemResult getItem(GetItemRequest getItemRequest) {
        return db.getItem(getItemRequest)
    }
}
public class TestDatabaseImpl implements Database {
    public MockDatabaseImpl db;

    public Database(MockDatabaseImpl db) {
        this.db = db;
    }

    public PutItemResult putItem(PutItemRequest putItemRequest) {
        return db.putItem(putItemRequest)
    }

    public GetItemResult getItem(GetItemRequest getItemRequest) {
        return db.getItem(getItemRequest)
    }
}
 

This is the most natural and simple solution in my opinion. The point being--don't use a class to represent the Database if you want to vary the database implementation.

If you want to only specify the methods the database exposes (its 'contract') but have the ability to change its implementation, then it's clear that Database should be the interface (the contract), and then you can create any number of concrete implementations of the Database as concrete implementation classes...

public class AmazonDB implements Database {...}

public class MockDB implements Database {...}

Now, classes can simply code against the abstract Database interface...

public class DatabaseUser {
  public void useDatabase(Database d) {
    PutItemRequest testPutReq = new PutItemRequest();
    d.putItem(testPutReq);
  }
}

You can then pass in whatever implementation of Database you want to the DatabaseUser class:

DatabaseUser dbUser = new DatabaseUser();

// implementation 1
Database aDb = new AmazonDB();

// mock db implementation
Database mockDb = new MockDB();

// Have DatabaseUser use Amazon
dbUser.useDatabase(aDb);

// now have it use the mock
dbUser.useDatabase(mockDb);
 

I can list a hundred things Java that are terrible, but this one certainly isn't. If your Database class expects to work with AmazonDynamoDB interface (that is, one that has 60+ methods in it), then it makes sense to not pass in a reduced interface.

If you just want different implementations for production and mock testing, I believe you can just straight mock the interface AmazonDynamoDB itself. Most mocking frameworks are capable of this.

 

The reason for the reduced interface was just because I would only need those two methods. Is there a better way to get make this happen in Java? Also, is there a framework you'd recommend? I looked at Mockito but didn't find good examples for what I was trying to do.

 

If Database only needs to work with those two method then the Java way to do is to write an implementation class for DynamoImpl which is a wrapper of your production AmazonDynamoDB, but only exposes those two methods publicly.

Mockito is a good framework that will probably have most of the features you'd want. What are you trying to do specificly? In Mockito you can do things like "create a (mock) object that implement this interface, but when method A() is called return some fake data" or stuffs like that.

Yes, Database only needs those two methods (right now, if I expand it later, I can add those methods); ideally, I want to be able to hit the "put" and "get" methods within the Database class without actually touching the production DynamoDB (which it wouldn't be able to do from a local run anyway without additional configuration).

 
 

Java itself isn't really that bad. It has it's issues, but every language does.

 

The language may have its flaws, but a huge ecosystem of high-quality libraries more than makes up for it. Especially when you realize that you can eliminate most/all of the language's flaws with Lombok (hacky as it may be) or Kotlin, and still get the benefits of the great JVM ecosystem.

And still no working resource management which strikes all the JVM languages out for most things. Sorry, even Perl 6.

 
 

To understand the Java philosophy its useful to look at Pascal. Pascal is a language where everything is nicely structured for you and strict.

The idea is that we're going to put a lot in the language so that you avoid shooting yourself in the foot. That stands in contrast with dynamically typed languages of the day like Lisp or Smalltalk or BASIC, which were more free for all, but had few compiler checks.

The idea is that you want to burden the compiler as much as you can in finding bugs for you so you don't have to look for obvious type problems later on. It was also written in a time before automatic type inference.

So in this Java world, everything has to be formally declared and formally compatible. The advantage is that when looking at a 1 million lines of code program its easy to know what type of object you're going to get because that info is declared.

Interfaces are a way to make statically typed languages like Java and C++ a bit more flexible by allowing a looser coupling. But its not as loose as duck typing. You still need to preserve the formal type system so that it can determine if you're doing something wrong or not.

So Java is good if you like the compiler cleaning out obvious bugs for you. Dynamic typing is good when you want less protocol and shorter code in the hopes that shorter code means its easier to find/fix bugs.

But if you show me a huge codebase, I think I'd prefer it to be Java over something super dynamic like Javascript or Python. Its just easier to understand what is going on. But maybe I'm old fashioned.

Note: most companies are still old fashioned which is why Java and C# are so popular, but maybe changes in education with the emphasis of Python (a fully dynamic language) will finally shift the tide over time. I don't remember a dynamic language being so popular as a first language in Universities before (Scheme doesn't count because it was mostly Ivy League schools only). Also Javascript is becoming so predominant and I don't recall a dynamically typed language being so popularly used for major development since BASIC in the early 80s (and even that was limited). So this may mark a significant change in the culture of programming.

 

One nit I want to pick at is that you named your interface *Impl - don't know about Go, but in Java land you'd normally name your interface something like Database and your implementation of that would be something like DynamoImpl. NBD and to each their own, but I'd reject a pull request for that at the very least.

 
 

Assuming you come from Go, you'll need to learn some proper OOP before jumping into Java.

 
 

Don't get me wrong, I used to be one of the "proper OOP" guys, that was almost 15 years ago (Java EE was the new cool thing). Nowadays I just want productivity and stick with Python and Go

 

haha I could object to the definition of "proper OOP" and I would call it "Java style OOP" but yeah :D

 

Proper OO is Smalltalk.. so I'd say Python and Ruby are closer to "proper OO" than Java. (And I'm a Java guy)

 

Smalltalk is "proper OO" not Java. I'd say Python and Ruby are closer to "proper OO" than Java. (And I'm a Java guy)

 

Go has a structural type system in the sense that interfaces are just bags of methods. So if you have a small bag as input then you can always just substitute a bigger bag that contains the smaller bag and the type system won't care.

Java has a nominal type system and the same approach won't work. Incidentally Typescript has a structural type system and what you're trying to do here would work just fine.

The only way you can make this work in Java is make a class that implements your interface and wraps the actual dynamo DB implementation. In other words, you'll need to use an adapter class.

 

What you're describing is called dependency injection, which was first popularized by Java ([citation needed]). What you're doing can work, you just need to change a few things. Your class itself needs to implement the interface, and then call the injected db's methods:

public class Database implements DynamoImpl {

    private DyanmoImpl db;

    public Database(DyanmoImpl db) {
        this.db = db;
    }

    // implemented methods
    public PutItemResult putItem(PutItemRequest putItemRequest) {
        this.db.putItem(putItemRequest);
    }

    public GetItemResult getItem(GetItemRequest getItemRequest) {
        this.db.getItem(getItemRequest);
    }

}
 

Interesting. Would this then be able to accept an AmazonDynamoDB interface into the constructor? Or would there be a need for to like:

public Database(AmazonDynamoDB db) {
    this.db = db;
}

public Database(DynamoImpl db) {
    this.db = db;
}
 

You have hard coded the implementation types. Code to the interfaces of those types and you can then pass in any concrete type that implements the interface. If that doesn't make sense, I will try to clarify. Good luck!

 

I wrote an article describing the easiest possible solution I can think of. I hope it helps to understand Java interfaces.

 

This is AWESOME and I will definitely be reading it!

EDIT: I'm going to include a link to this piece on my article.

 

The problem is different. You can pass bigInterfaceImpl as smallInterface into db and cast into bigInterface. I think your problem is how to switch between big and small interfaces. When you have new BigSmallInterfaceImpl() in code you can not change it. There is when Spring Framework comes. Spring can be heavly overused and in many cases and it could be just factory pattern be better approach:
Factory.createInstance(MyInterface.class)
In factory you can take based on your environment you decide if you want to create mock, real db or yet something different implementation for given interface.

 

Wow. Just wow. Anything else would probably violate community standards.

 

Which standards do you believe are being violated?

 

Ok, I see my comment was unclear. I'm saying if I wanted to give an honest response to your clickbaity titled article, I would be violating community standards.

 

1) read the language docs

2) read something about API design, the fundamental flaw is that you have an *Impl class in your API, why not have a constructor that takes an Interface? Thats how testable programs are written

3) use Spock for testing (I hope you are taling about automated tests) that way you dont need to implement all the method and just do Mock(DynamoDbInterface) and implement the methods you need for testing

 

1) Yep, this is a must.
2) I did have a constructor on the class that was accepting the DynamoImpl interface which only defined two methods. These two methods were identical to two methods in the much larger AmazonDynamoDB interface. The goal here was to be able to only have to provide two fake methods instead of 60 in testing which is where I ran into trouble because it wouldn't accept the larger AmazonDynamoDB interface in the Database constructor.
3) I'll definitely take a look!

 

also Java is a awesome language - once you really understand it, yes it has its downsides, but thats true for every language, if you are just learning Java I would suggest starting with Kotlin instead, much nicer to work with (not for you perhaps now, but in the long run yes)

 

If AmazonDynamoDB is an instance of DynamoImpl it should work. Maybe you don't have a clue about Java?

code of conduct - report abuse