0

I had a question about inheritance on a Java exam. It went like this:

class A{
    public int getInt(){
        return 0;
        }
    }

class B extends A{
    public int getInt(){
        return 60;
        }
    }

class C extends B{
    public int getInt(){
        return 150;
        }    
    }

class Z4{
    public static void main(String[] args){
        A c1 = new C();
        B c2 = new C();
        C c3 = new C();
        System.out.println(c1.getInt() + " " + c2.getInt() + " " + c3.getInt());
        }
    }

The code prints out "150 150 150"

I understand why it does that: the variables are the type expressed on the left side of the operator, but the objects are the type expressed on the right side of the operator. Since all objects are type C, they all use C's overridden method.

Furthermore, if class C had an overloaded (as opposed to overridden) method, the first two variables would not have been able to use it, since they variable types don't have that method signature.

On a side note, a variable of type superclass can reference an object of type subclass. But a variable of type subclass can't reference an object of type superclass. "Super myObject = new Sub();" works. "Sub myObject = new Super();" doesn't.

My first question is: Are the above statements correct? My second question is: when would you do that?

In my very limited experience, I've made ArrayLists of type superclass and populated it with objects of various subclasses. I see where that comes in handy. But is there a case where you specifically create variables that are a different type from their object? Do you ever enter "SuperClass myObject = new SubClass();"? I can't see a practical use for that.

Adam Lear
  • 38,111
  • 12
  • 81
  • 101
Bagheera
  • 1,358
  • 4
  • 22
  • 35
  • 1
    Read **Runtime Polymorphism** http://java.dzone.com/articles/runtime-polymorphism-java – Prateek Nov 20 '13 at 00:17
  • My guess is that you're focusing too much on the fact it's a local variable. It was probably a simplification for the sake of keeping the test readable, nothing more. Seeing how C#'s `var` keyword (which implicitly types a variable as the type of its initialiser expression) is probably commonly used I'm guessing the reasons given in the answers below are more retroactive justification for a habit than anything else. As for myself, I'd use `List foo = new ArrayList()` because `List` is shorter to type. – millimoose Nov 20 '13 at 00:38
  • @millimoose I don't see C# `var` or C++ `auto` as relevant here. First, in languages with that feature, it's _even easier_ to change the implementation from, say, a HashSet to a Tree or vice versa. You can find the recommendation to declare variables as interfaces when possible in any reasonable Java guide or earlier on S.O. http://stackoverflow.com/questions/1484445/why-are-variables-declared-with-their-interface-name-in-java – Andrew Lazarus Nov 20 '13 at 01:13
  • @AndrewLazarus I was mainly addressing the answer that says it's better than an implicit upcast on return with that comparison. Also, I'd say that swapping out the implementation (which I think is an absolutely ludicrous concern when it comes to local variables and their very limited scope) is easier when you don't depend on the specifics of that implementation, which actually typing as the interface would help you accomplish. Saving one click in the search-and-replace box doesn't seem like that great a practical advantage, especially not in a statically typed language. – millimoose Nov 20 '13 at 01:51

5 Answers5

1

The use comes into play in taking advantage of abstraction and polymorphism. It's hard to communicate with contrived examples like the one from the test. Basically it's about delaying decisions and allowing for replacement.

In pure variable declarations with immediate assignment it's a bit subtle. It's probably better to use an example of a returned object reference (admittedly cheating a bit, since I'm using an interface rather than a superclass):

public Collection makeSomeFancyCollection() {
}

Callers of this code don't need to know or care what type of Collection is actually returned (and they certainly shouldn't downcast).

Collection fancy = makeSomeFancyCollection();

It may be that someday, it would make good sense for the implementation of the method to return a different implementation of Collection than it once did. Callers will never notice, at least at compile-time.

However, if the method was instead written as such:

public ArrayList makeSomeFancyCollectionConcrete() {
    …
}

A caller of a method like this, if it used the specific type, would need to be changed and recompiled if the implementation changed to return a different type of collection:

ArrayList fancy = makeSomeFancyCollectionConcrete();

However, the wisest thing to do is the following:

Collection fancy = makeSomeFancyCollectionConcrete();

(So long as the Collection interface is sufficient, anyway).

MikeThomas
  • 544
  • 3
  • 6
  • BTW, I like the analogy that @connor used. I was trying to address just the "why assign a more specific type instance to a less specific reference". – MikeThomas Nov 20 '13 at 00:31
1

"Do you ever enter "SuperClass myObject = new SubClass();"? I can't see a practical use for that."

This

 A c1 = new C();
 B c2 = new C();
 C c3 = new C();

Or this

 List a = new ArrayList(); 

Are really not the best examples to understand polymorphism. The second notation is primarily used because it's more concise, in my opinion. In theory it can be used to assign a different implementation, in practice it happens in less than 2% of the cases.

Functional languages

In many functional languages (i.e. Scala, Dart, Kotlin, static Groovy, Java 8 lambdas) left part is considered redundant and the whole thing looks like:

val a = Array();          (Scala)
def a = new ArrayList();  (Groovy)

A better example could be when you build a modular system which suggests multiple implementations for interfaces. These implementation mappings are typically configured for the whole project with i.e. Dependency Injection libraries.

Example

I.e. your module receives e-mails via a Client interface. And an e-mail client may support different protocols - PopClient or IMAPClient classes. So the benefit is that logic which uses this client to receive e-mails is not aware of how it works - it uses Client interface.

Andrey Chaschev
  • 16,160
  • 5
  • 51
  • 68
0

An analogy I like to use is having an Animal superclass and various subclasses like Cat, Dog, Bird, Lion etc. So for your first question, this line would make sense: Animal myBird = new Bird(); but Bird myBird = new Animal(); would not. That's because all Birds are Animals but not all Animals are Birds. All Animals would inherit methods like breathe() and move() but a Bird may have additional methods like fly().

Having an arraylist of type Animal to hold a bunch of Animals is one use that you've already discovered. Another possible use is if you have a method that takes an object of the superclass's type. For example, a Lion could have an eat method that could take an Animal object. Then you could pass a Bird, Zebra, Fish or any other Animal into that method.

Connor Pearson
  • 63,902
  • 28
  • 145
  • 142
0

The quick answer is that you do it for abstraction. You may have a bunch of specialized classes (e.g. Cat and Dog) but you don't want to write a new method for each one. In some cases you have a single method (e.g. `feed()') which works the same on both classes.

You make a super class Animal and you make both Cat and Dog extend it. You make the method take a parameter of Animal (e.g. feed(Animal a)) and you can pass either Cat or Dog to it.

The same thing with variables. You have a variable that can hold an Animal because you don't care whether it is a Cat or a Dog. Whichever one happens to be placed in there will be fine.

This allows you to write a bunch of logic about Animal that does not need to be duplicated for every Cat, Dog, or Fish class you write. It is, in the end, a labor saving device.

AgilePro
  • 5,588
  • 4
  • 33
  • 56
0

The short answer is YES. Here's an example of a longer answer. Let's take a database connection. The actual database is likely to be specified in a configuration or runtime parameter, including the name of the actual driver class.

DriverManager.getConnection(/* bunch of parameters to identify which connection */)

returns some subclass of JdbcConnection depending on the database driver. Might be an OracleConnection, a PostgreqlConnection, even a CsvFileConnection. (Those aren't real driver names, but they are more obvious than the real ones!).

Inside the driver itself, my belief is it is clearer to write

JdbcConnection value_to_return = new MyProprietaryDBConnection( /* all sorts of stuff */);

rather than rely on an implicit cast at the return.

It's also very common to define a variable using only the minimal interface for the methods that are called on it. That makes it easier to swap implementations if you decide your Collection should be an ArrayList instead of a Set, etc.

UPDATE in response to comment: Let me also mention that one sometimes sees assignments where the right hand side is an anonymous subclass. In that case, one has to use a superclass of the actual anonymous class.

Andrew Lazarus
  • 18,205
  • 3
  • 35
  • 53
  • Your "belief" (stylistic preference) isn't exactly a technical reason why one would do it. – millimoose Nov 20 '13 at 00:39
  • @millimoose I'm not quite sure I get this disagreement. There isn't a _technical_ reason not to use good variable names. However, I will add another point about anonymous subclasses. – Andrew Lazarus Nov 20 '13 at 01:08
  • Okay, but good variable names are good because they're more descriptive. (Also I don't think a question like "why should I use long variable names?" would be appropriate for SO in the first place, or any extended discussion on the topic.) I'm not sure you could come up with specific qualities, even subjective ones, that make an implicit upcast on a return less clear than the implicit upcast done in the assignment, or when returning an expression of some sort where there is no explicit type information in your method's body at all. It's splitting hairs over a fairly special case. – millimoose Nov 20 '13 at 01:59