Java - declaring from Interface type instead of Class
Asked Answered
C

7

41

In my quest to correctly grasp Interface best practices, I have noticed declarations such as:

List<String> myList = new ArrayList<String>();

instead of

ArrayList<String> myList = new ArrayList<String>();

-To my understanding the reason is because it allows flexibility in case one day you do not want to implement an ArrayList but maybe another type of list.

With this logic, I set up an example:

public class InterfaceTest {

    public static void main(String[] args) {

        PetInterface p = new Cat();
        p.talk();

    }

}

interface PetInterface {                

    public void talk();

}

class Dog implements PetInterface {

    @Override
    public void talk() {
        System.out.println("Bark!");
    }

}

class Cat implements PetInterface {

    @Override
    public void talk() {
        System.out.println("Meow!");
    }

    public void batheSelf() {
        System.out.println("Cat bathing");
    }

}

My question is, I cannot access the batheSelf() method because it only exists for Cat. That leads me to believe that I should only declare from an Interface if I am only going to use methods declared in the Interface (and not extra methods from the subclass), otherwise I should declare from the Class directly (in this case Cat). Am I correct in this assumption?

Carbazole answered 1/8, 2010 at 20:53 Comment(0)
L
36

When there is a choice between referring to an object by their interface or a class, the former should be preferred, but only if an appropriate type exists.

Consider StringimplementsCharSequence as an example. You should not just blindly use CharSequence in preferrence to String for all cases, because that would deny you simple operations like trim(), toUpperCase(), etc.

However, a method that takes a String only to care about its sequence of char values should use CharSequence instead, because that is the appropriate type in this case. This is in fact the case with replace(CharSequence target, CharSequence replacement) in the String class.

Another example is java.util.regex.Pattern and its Matcher matcher(CharSequence) method. This lets a Matcher be created from Pattern for not just String, but also for all other CharSequence there are out there.

A great example in the library of where an interface should've been used, but unfortunately wasn't, can also be found in Matcher: its appendReplacement and appendTail methods accept only StringBuffer. This class has largely been replaced by its faster cousin StringBuilder since 1.5.

A StringBuilder is not a StringBuffer, so we can not use the former with the append… methods in Matcher. However, both of them implementsAppendable (also introduced in 1.5). Ideally Matcher's append… method should accept any Appendable, and we would then be able to use StringBuilder, as well as all other Appendable available!

So we can see how when an appropriate type exists referring to objects by their interfaces can be a powerful abstraction, but only if those types exist. If the type does not exist, then you may consider defining one of your own if it makes sense. In this Cat example, you may define interface SelfBathable, for example. Then instead of referring to a Cat, you can accept any SelfBathable object (e.g. a Parakeet)

If it does not make sense to create a new type, then by all means you can refer to it by its class.

See also

  • Effective Java 2nd Edition, Item 52: Refer to objects by their interfaces

If appropriate interface types exist, then parameters, return values, and fields should all be declared using interface types. If you get into the habit of using interface types, your program will be much more flexible. It is entirely appropriate to refer to an object by a class if no appropriate interface exists.

Related links

Lauritz answered 1/8, 2010 at 21:48 Comment(2)
Thank you. This clarified a lot.Carbazole
@Lauritz you mentioned - "you may define interface SelfBathable, for example. Then instead of referring to a Cat, you can accept any SelfBathable object (e.g. a Parakeet)". Since I am completely new to java and I had this question in my mind for long time , I really would like to know how to solve the problem by defining a new interface.Oar
W
11

Yes, you are correct. You should declare as the most general type providing the methods you use.

This is the concept of polymorphism.

Workhorse answered 1/8, 2010 at 20:56 Comment(0)
L
5

Your are correct, but you can cast from the interface to the desired pet if you need. For example:

PetInterface p = new Cat();
((Cat)p).batheSelf();

Of course if you try to cast your pet to a dog you cannot call the batheSelf() method. It would not even compile. So, to avoid problems, you could have a method like this:

public void bathe(PetInterface p){
    if (p instanceof Cat) {
        Cat c = (Cat) p;
        c.batheSelf();
    }
}

When using instanceof, you make sure you will not try to make a dog bathe himself during runtime. Which would throw an error.

Latton answered 1/8, 2010 at 21:21 Comment(2)
you can do this, but it is a bad idea. In this case for instance, it embeds the assumption that only cats can bath themselves into the bathe method.Adalie
I can see the reason for the second code snippet being a bad idea, but is the first code snippet advisable? It worked for me, but I am wondering if it is good practice?Carbazole
P
2

Yes, you are correct. By having Cat implent "PetInterface" you can use it in the example above and easily add more kinds of pets. If you really need to be Cat-specific you need to access the Cat class.

Procession answered 1/8, 2010 at 20:57 Comment(0)
D
2

You can call method batheSelf from talk in Cat.

Davila answered 1/8, 2010 at 21:42 Comment(0)
B
2

Generally, you should prefer interfaces to concrete classes. Along those lines, if you can avoid using the new operator (which always requires a concrete type as in your new ArrayList example), even better.

This all has to do with managing dependencies in your code. It's best to depend only on highly abstract things (like interfaces) because they also tend to be very stable (see http://objectmentor.com/resources/articles/stability.pdf). Because they have no code, they only must be changed when the API changes...in other words, when you want that interface to present a different behavior to the world, i.e., a design change.

Classes, on the other hand, change all the time. Code that depends upon a class doesn't care how it does what it does, as long as the inputs and the outputs of the API don't change, callers shouldn't care.

You should strive to nail down the behavior of your classes according to the Open-Closed Principle (see http://objectmentor.com/resources/articles/ocp.pdf), that way existing interfaces need not change even when you add functionality, you can just specify a new subinterface.

The old way of avoiding the new operator was by using the Abstract Factory pattern, but that comes with its own set of problems. Better is to use a tool like Guice that does dependency injection, and prefer constructor injection. Make sure you understand the Dependency Inversion Principle (see http://objectmentor.com/resources/articles/dip.pdf) before you start using dependency injection. I've seen a lot of people inject inappropriate dependencies and then later complain that the tool isn't helping them...it won't make you a great programmer, you still have to use it appropriately.

Example: you are writing a program that helps students learn physics. In this program, students can put a ball in various physical scenarios and watch how it behaves: shoot it out of a cannon off a cliff, put it underwater, in deep space, etc. Question: you want to include something about the heaviness of the ball in the Ball API...should you include a getMass() method or a getWeight() method?

Weight depends upon the environment the ball happens to be in. It might be convenient for callers to be able to call one method and get the weight of the ball wherever it happens to be, but how do you write this method? Each ball instance must constantly keep track of where it is and what the current gravitational constant is. So you should prefer getMass(), because mass is an intrinsic property of the ball and doesn't depend on its environment.

Wait, what if you just use getWeight(Environment) instead? This way, the ball instance can just get its current g out of the environment and proceed...better yet, you can use Guice to inject the Environment in the Ball's constructor! This is the type of misuse I often see, and people end up blaming Guice for not being able to handle dependency injection as seamlessly as they would've hoped.

The problem is not Guice here, it's the Ball API design. Weight is not an intrinsic property of the ball, so it's not a property that should be accessible from the ball. Instead, Ball should implement the MassiveObject interface with a getMass() method, and Environment should have a method called getWeightOf(MassiveObject). Intrinsic to the Environment is its own gravitational constant, so this is much better. And Environment only depends upon a simple interface now, MassiveObject...but it's job is to contain objects, so this is as it should be.

Bazaar answered 17/11, 2011 at 17:44 Comment(1)
The links are dead. Due to pending edits in the Suggested Edit queue I cannot suggest an edit. Here are the replacement links: web.archive.org/web/20110517042408/http://objectmentor.com/… // web.archive.org/web/20110517001433/http://objectmentor.com/… // web.archive.org/web/20110527002000/http://objectmentor.com/…Shluh
F
0

Why not simply do this!

Cat c = new Cat();
PetInterface p = (PetInterface)c;
p.talk();
c.batheSelf();

Now we have a single object, which can be manipulated using 2 references.
The reference p can be used to call functions defined in interface and c can be used to call functions defined in class(or superclass) only.

Following answered 18/3, 2013 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.