Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend a Java Library by adding methods to it

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
like image 487
Sheharyar Avatar asked Apr 30 '16 07:04

Sheharyar


1 Answers

Java objects work somewhat like in the real world.

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.


You can't extend a library that way

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.

like image 138
JB Nizet Avatar answered Sep 20 '22 03:09

JB Nizet