I'm trying to understand how to properly "extend" 3rd party libraries (with both classes and interfaces) in Java and how to simply use them as drop-in replacements.
I'm currently using the Twitter4J
library. Right now I have to manually prepend @
to the result of getScreenName()
method of User
every time I call it:
twitter4j.User user = getTwitterUser();
String userHandle = "@" + user.getScreenName();
I've currently tried a few things, but nothing works. This is what I've come up with:
package my.app;
public abstract class User implements twitter4j.User {
public String getHandle() {
return "@" + getScreenName();
}
}
calling it like this:
User user = (User) getTwitterUser();
String userHandle = user.getHandle();
This crashes with the message:
java.lang.ClassCastException: twitter4j.UserJSONImpl cannot be cast to my.app.User
Let's make a car analogy. You have a garage next to your place that sells cars. So you can get a car from the garage:
Car car = garage.buyCar(money);
The cars that the garage happens to sell are Volvos. So, you get a car, and this car (Car is an interface) is actually a Volvo. If you know that the car is a Volvo, you can thus do that:
Volvo car = (Volvo) garage.buyCar(money);
That doesn't change, at all, the way the garage sells cars, or the types of cars that the garage sells. It's just that, since you know they're volvos, you can cast the result to access Volvo car's specificities.
Note that this works now. But nothing prevents the garage to, later, decide to sell VWs instead of Volvos. If they do that, the cast won't work anymore, and it will be your fault: the garage told you it sells cars, but didn't promise it would be a Volvo.
Now, you would like the garage to produce Porsches instead. Trying to do
Porsche car = (Porsche) garage.buyCar(money);
won't work. Just because you want very hard for the car to be a Porsche won't magically change the Volvo that the garage produces to a Porsche.
All you can do is take the User
and call a utility method that creates a handle from that user. Or to create your own UserWithHandle
class that implements User
, adds a getHandle()
method, and implements all the other methods by delegating to the original, wrapped User:
UserWithHandle u = new UserWithHandle(getTwitterUser())
You could do what you want if the library allowed you to provide a UserFactory
implementation, that would replace its way of creating users by your own way. Kind of like if the garage allowed you to provide your own painter.
Doing what you want, i.e. adding a method to an existing class is not possible in Java, but is possible in some languages like Kotlin (which is compatible with Java and runs on the JVM, if you're interested), where they're called extension methods. Basically, these methods are static utility methods that would, for example, take a User
as argument and return its handle, but can be called as if they were instance method of User
.