Java Guide: Legal Return Types
- Details
- Category: Programming Guides & Tutorials
- Published on Monday, 29 August 2011 02:22
- Written by Prabhu Selvakumar
Legal Return Types
This objective covers two aspects of return types: what you can declare as a return type, and what you can actually return as a value. What you can and cannot declare is pretty straightforward, but it all depends on whether you're overriding an inherited method or simply declaring a new method (which includes overloaded
methods). We'll take just a quick look at the difference between return type rules for overloaded and overriding methods, because we've already covered that in this chapter. We'll cover a small bit of new ground, though, when we look at polymorphic return types and the rules for what is and is not legal to actually return.
Return Type Declarations
This section looks at what you're allowed to declare as a return type, which depends primarily on whether you are overriding, overloading, or declaring a new method.
Return Types on Overloaded Methods
Remember that method overloading is not much more than name reuse. The overloaded method is a completely different method from any other method of the same name. So if you inherit a method but overload it in a subclass, you're not subject to the restrictions of overriding, which means you can declare any return
type you like. What you can't do is change only the return type. To overload a method, remember, you must change the argument list. The following code shows an overloaded method:
public class Foo{
void go() { }
}
public class Bar extends Foo {
String go(int x) {
return null ;
}
}
Notice that the Bar version of the method uses a different return type. That's
perfectly fine. As long as you've changed the argument list, you're overloading the
method, so the return type doesn't have to match that of the superclass version.
What you're NOT allowed to do is this:
public class Foo{
void go() { }
}
public class Bar extends Foo {
String go() { // Not legal! Can't change only the return type
return null ;
}
}
Overriding and Return Types, and Covariant Returns
When a subclass wants to change the method implementation of an inherited method (an override), the subclass must define a method that matches the inherited version exactly. Or, as of Java 5, you're allowed to change the return type in the overriding method as long as the new return type is a subtype of the declared return type of the overridden (superclass) method.
Let's look at a covariant return in action:
class Alpha {
Alpha doStuff(char c) {
return new Alpha() ;
}
}
class Beta extends Alpha {
Beta doStuff(char c) { // legal override in Java 1.5
return new Beta();
}
}
As of Java 5, this code will compile. If you were to attempt to compile this code with a 1.4 compiler or with the source flag as follows:
javac -source 1.4 Beta.java
you would get a compiler error something like this:
attempting to use incompatible return type
Returning a Value
You have to remember only six rules for returning a value:
1. You can return null in a method with an object reference return type.
public Button doStuff() {
return null ;
}
2. An array is a perfectly legal return type.
public String[] go() {
return new String[] {"Fred", "Barney", "Wilma"} ;
}
3. In a method with a primitive return type, you can return any value or variable that can be implicitly converted to the declared return type.
public int foo() {
char c = 'c' ;
return c ;
// char is compatible with int
}
4. In a method with a primitive return type, you can return any value or variable that can be explicitly cast to the declared return type.
public int foo () {
float f = 32.5f ;
return (int) f ;
}
5. You must not return anything from a method with a void return type.
public void bar() {
return "this is it" ;
// Not legal!!
}
6. In a method with an object reference return type, you can return any object type that can be implicitly cast to the declared return type.
public Animal getAnimal() {
return new Horse() ;
// Assume Horse extends Animal
}
public Object getObject() {
int[] nums = {1,2,3} ;
return nums ;
// Return an int array,
// which is still an object
}
public interface Chewable { }
public class Gum implements Chewable { }
public class TestChewable {
// Method with an interface return type
public Chewable getChewable() {
return new Gum() ;
// Return interface implementer
}
}
Watch for methods that declare an abstract class or interface return type, and know that any object that passes the IS-A test (in other words, would test true using the instanceof operator) can be returned from that method— for example:
public abstract class Animal { }
public class Bear extends Animal { }
public class Test {
public Animal go() {
return new Bear(); // OK, Bear "is-a" Animal
}
}
This code will compile, the return value is a subtype.
Constructors and Instantiation
Objects are constructed. You can't make a new object without invoking a constructor. In fact, you can't make a new object without invoking not just the constructor of the object's actual class type, but also the constructor of each of its superclasses! Constructors are the code that runs whenever you use the keyword new. OK, to be a bit more accurate, there can also be initialization blocks that run when you say new, but we're going to cover them (init blocks), and their static initialization counterparts, in the next chapter. We've got plenty to talk about
here—we'll look at how constructors are coded, who codes them, and how they work at runtime. So grab your hardhat and a hammer, and let's do some object building.
Constructor Basics
Every class, including abstract classes, MUST have a constructor. Burn that into your brain. But just because a class must have one, doesn't mean the programmer has to type it. A constructor looks like this:
class Foo {
Foo() { } // The constructor for the Foo class
}
Notice what's missing? There's no return type! Two key points to remember about constructors are that they have no return type and their names must exactly match the class name. Typically, constructors are used to initialize instance variable state, as follows:
class Foo {
int size;
String name;
Foo(String name, int size) {
this.name = name;
this.size = size;
}
}
In the preceding code example, the Foo class does not have a no-arg constructor. That means the following will fail to compile:
Foo f = new Foo(); // Won't compile, no matching constructor
but the following will compile:
Foo f = new Foo("Fred", 43); // No problem. Arguments match
// the Foo constructor.
So it's very common (and desirable) for a class to have a no-arg constructor, regardless of how many other overloaded constructors are in the class (yes, constructors can be overloaded). You can't always make that work for your classes; occasionally you have a class where it makes no sense to create an instance without
supplying information to the constructor. A java.awt.Color object, for example, can't be created by calling a no-arg constructor, because that would be like saying to the JVM, "Make me a new Color object, and I really don't care what color it is...you pick." Do you seriously want the JVM making your style decisions?
Constructor Chaining
We know that constructors are invoked at runtime when you say new on some class type as follows:
Horse h = new Horse();
But what really happens when you say new Horse() ?
(Assume Horse extends Animal and Animal extends Object.)
1. Horse constructor is invoked. Every constructor invokes the constructor of its superclass with an (implicit) call to super(), unless the constructor invokes an overloaded constructor of the same class (more on that in a minute).
2. Animal constructor is invoked (Animal is the superclass of Horse).
3. Object constructor is invoked (Object is the ultimate superclass of all classes, so class Animal extends Object even though you don't actually type "extends Object" into the Animal class declaration. It's implicit.) At
this point we're on the top of the stack.
4. Object instance variables are given their explicit values. By explicit values, we mean values that are assigned at the time the variables are declared, like "int x = 27", where "27" is the explicit value (as opposed to the
default value) of the instance variable.
5. Object constructor completes.
6. Animal instance variables are given their explicit values (if any).
7. Animal constructor completes.
8. Horse instance variables are given their explicit values (if any).
9. Horse constructor completes.
Figure 2-6 shows how constructors work on the call stack.

Rules for Constructors
The following list summarizes the rules you'll need to know for the exam (and to understand the rest of this section). You MUST remember these, so be sure to study them more than once.
- Constructors can use any access modifier, including private. (A privateconstructor means only code within the class itself can instantiate an object of that type, so if the private constructor class wants to allow an instance of the class to be used, the class must provide a static method or variable that allows access to an instance created from within the class.)
- The constructor name must match the name of the class.
- Constructors must not have a return type.
- It's legal (but stupid) to have a method with the same name as the class,but that doesn't make it a constructor. If you see a return type, it's a method rather than a constructor. In fact, you could have both a method and aconstructor with the same name—the name of the class—in the same class,and that's not a problem for Java. Be careful not to mistake a method for a constructor—be sure to look for a return type.
- If you don't type a constructor into your class code, a default constructor will be automatically generated by the compiler.
- The default constructor is ALWAYS a no-arg constructor.
- If you want a no-arg constructor and you've typed any other constructor(s) into your class code, the compiler won't provide the no-arg constructor (or any other constructor) for you. In other words, if you've typed in a constructor with arguments, you won't have a no-arg constructor unless you type it in
yourself ! - Every constructor has, as its first statement, either a call to an overloaded constructor (this()) or a call to the superclass constructor (super()), although remember that this call can be inserted by the compiler.
- If you do type in a constructor (as opposed to relying on the compiler-generated default constructor), and you do not type in the call to super() or a call to this(), the compiler will insert a no-arg call to super() for you, as the very first statement in the constructor.
- A call to super() can be either a no-arg call or can include arguments passed to the super constructor.
- A no-arg constructor is not necessarily the default (i.e., compiler-supplied) constructor, although the default constructor is always a no-arg constructor. The default constructor is the one the compiler provides! While the default constructor is always a no-arg constructor, you're free to put in your own noarg constructor.
- You cannot make a call to an instance method, or access an instance variable, until after the super constructor runs.
- Only static variables and methods can be accessed as part of the call to super() or this(). (Example: super(Animal.NAME) is OK, because NAME is declared as a static variable.)
- Abstract classes have constructors, and those constructors are always called when a concrete subclass is instantiated.
- Interfaces do not have constructors. Interfaces are not part of an object's inheritance tree.
- The only way a constructor can be invoked is from within another constructor. In other words, you can't write code that actually calls a constructor as follows:
class Horse {
Horse() { } // constructor
void doStuff() {
Horse(); // calling the constructor - illegal!
}
}
Legal Return Types , Rules for Constructors , Constructor Chaining , Constructor Basics , Constructors and Instantiation , Returning a Value , Overriding and Return Types , and Covariant Returns , Return Types on Overloaded Methods , Return Type Declarations ,blog comments powered by Disqus
More 
