Inheritance
Sometimes it is useful to create "speciality" versions of a class. Building on our previous Student
class from last lecture, we may want to create versions of that class for graduate students, international students, and international graduate students. Additionally, we may want to create a version for instructors. With inheritance, classes can inherit all the instance variables and methods of another class. This reduces code duplication.
- Inheritance
- Visibility: public vs. private vs. protected vs. package
- Java notes
All the code files for today: Person.java; Instructor.java; Student.java; GraduateStudent.java; InternationalStudent.java; InternationalGraduateStudent.java; CollegeApp.java; CompareTest.java; DynamicDispatchExample.java.
Inheritance
Last lecture we created a Student
class which enabled us to instantiate many objects that each represent one student at a college. We will expand on that idea today. Suppose we want to model both students and instructors at the college. We could easily create a class called Instructor, but students and instructors have some commonality. For example, each will have an ID and a name. We might then create a class called Person
with instance variables for ID and name, plus getters/setters and toString. Imagine we've now written and debugged a possbily complex Person
class (our class won't be too complex, but in the real world it could be). If we then want to create Student
and Instructor
classes, we'd rather not duplicate all the code in Person
. Instead Student
and Instructor
can leverage all the work done for Person
. In object-oriented programming, we call this technique
We call the existing class the
The Golden Rule of Inheritance. If there is one thing you should remember about inheritance, it is
Use inheritance to model "is-a" relationships and only "is-a" relationships.
What does this mean? Suppose we have two classes: "A" is the superclass of "B" (which is therefore the subclass). If we have used inheritance correctly, then
- Every object of class "B" is also an object of class "A".
- The set of objects of class "B" is a subset of the objects of class "A".
- Every method that can be called on an object of class "A", can also be called on an object of class "B". (However, the results of the call may be very different.)
- Objects of class "B" are like objects of class "A", but with additional specialization.
For example, we'll have a subclass of Person
called Instructor
that represents instructors at the college. Person
is the superclass of Instructor
; Instructor
is a subclass of Person
. Every Instructor
"is a" Person
. The set of all Person
's includes all the Instructors
. Every method that can be called on a Person
can be called on a Instructor
.
Let's see how that looks in code.
public class Person {
String name;
String Id;
/**
* Constructor
* @param Id
* @param name
*/
public Person(String name, String Id) {
this.name = name;
this.Id = Id;
}
/**
* Getters
*/
public String getName() { return name; }
public String getId() { return Id; }
/**
* Setters
*/
public void setName(String name) {this.name = name; }
public void setId(String Id) { this.Id = Id;}
/**
* Returns a String representation of a Person
* @return String
*/
public String toString() {
String s = "Name: " + name + " (" + Id + ")\n";
s += "\tPosition: " + getPosition() + "\n";
s += "\tTitle: " + title;
return s;
}
}
public class Instructor extends Person {
boolean tenured;
int yearsEmployed;
String department;
/**
* Constructors
*/
public Instructor(String name, String Id) {
super(name, Id);
this.tenured = false; //not required, Java initializes boolean instance variables to false
this.yearsEmployed = 0; //not required, Java initializes numeric values instance variables to 0
this.department = null; //not required, Java initializes objects to null
title = "Instructor";
}
public Instructor(String name, String Id, boolean tenured, int yearsEmployed, String department) {
super(name, Id);
this.tenured = tenured;
this.yearsEmployed = yearsEmployed;
this.department = department;
title = "Instructor";
}
/**
* Getters
*/
public boolean getTenuredStatus() { return tenured;}
public int getYearsEmployed() { return yearsEmployed;}
public String getDepartment() { return department; }
/**
* Setters
*/
public void setTenured(boolean tenured) { this.tenured = tenured; }
public void setYearsEmployed(int yearsEmployed) { this.yearsEmployed = yearsEmployed; }
public void setDepartment(String department) { this.department = department;}
/**
* Return a String representation of an instructor
* @return - string representing the instructor
*/
@Override
public String toString() {
String s = super.toString() + "\n";
s += "\tTenured: " + tenured + "\n";
s += "\tYears Employed: " + yearsEmployed + "\n";
s += "\tDepartment: " + department;
return s;
}
}
The Instructor class is a specialized version of Person; it inherits the Person
's instance variables and methods with the keyword Instructor
does not define a setName
method, it inherits that method from Person
(it also inherits Person
's instance variables as well!). When calling a method on Instructor
, Java will check to see if Instructor
implements the method. If not, Java checks the base class. If it find the method in the base class, Java runs that method. If the base class does not implement the method, Java checks the base class's base class and so forth. If Java never finds an implementation of the method after hunting all the way up the inheritance chain, it throws an exception.
Subclasses can Instructor
provides a toString
method. Person
also has a toString
method. When toString
is called on an object of type Instructor
, Java looks to see if Instructor
implements toString
. In this case, Instructor
does implement toString
, so Java runs the toString
in Instructor
instead of Person
. Use the @Override
is not required, but it is a good idea to include it. If you use @Override
, but the base class does not implement the method, Java will alert you before compile time.
In lecture we will create classes that inherit from other classes. In Java, however, we can only inherit from a single base class. For example, as shown in the figure below, an International Graduate Student is both a Graduate Student and an International Student, but we can only extend one of them. Java calls this

Visibility: public vs. private vs. protected vs. package
Unlike some other languages (e.g., Python), Java allows us to control which code can access a class's instance variables and methods. Public access means that any method of any class can access the variable or method. Private access is limited to the given class and its inner classes. With protected access, the variable or method should be accessible to only the class and any subclass that extends the class (or extends a class that extends the class, etc.). Protected access seems to be a good compromise between public and private, and it is certainly an improvement on public.
There is a problem with protected access, however. Because anyone can extend a class, there is no real control over who can access protected instance variables. Again, there is no abstraction barrier between the superclass and its subclasses. A better way to restrict access is to make all instance variables (except constants) private and then to make the methods that can access those instance variables protected. That should prevent classes other than subclasses from accessing the instance variables, while still keeping encapsulation.
The default access, package access, is what you get if you don't put anything before the instance variable or method. I consider it a design flaw of the language that there is no package visibility level specifier, as there is for public, private, and protected. It is too easy to forget to declare an instance variable (or method) private. I would have preferred that leaving off the visibility level specifier would be a syntax error. That way the programmer would always have to make an explicit choice.
Package access makes the variable or method visible to everything in the same package. A package is a way of grouping methods together that are intended to work together as a group, like java.util or java.awt. To put a file into a package, you start the file with a package statement:
package yourPackageName;
If there is not a package statement, then all classes in the file go into the default package. (For consistency in debugging and grading, that's the standard for CS 10, though in bigger projects later, you'll probably want to split things up into packages so that names don't interfere with each other.) You seldom want package access. Anyone can add a class to a package, so you have very little control of who has access to things that have package access. It sometimes makes sense to give package access for methods that you want as utility routines within a package, but it seldom makes sense to have package access for instance variables.
You may have noticed that when I described what protected means, I said it "should" only be available to the class and its descendants (subclasses or subclasses of those subclasses, etc.). Unfortunately, that is not what it means in Java. As we have described it, public is the least restrictive, private the most, and protected and package are both somewhere in between, but you can't say that one is more restrictive than the other. They control access in very different ways. The designers of Java decided that not having a linear hierarchy of protection levels was a bad idea. Therefore they somewhat arbitrarily decided that protected access should include package access, so that package access is more restrictive than protected. So in Java protected really means visible in descendants and to classes in the same package. If you are using the default package, that means it is not much different than public! However, I will still declare things protected to indicate who should have access.
Java notes
Again, this isn't a comprehensive reference to the language; the textbook and on-line references provide much more detail. But hopefully it gives sufficient intuition and an organizational structure. Give a yell if I've missed something important.
- extends
- This indicates that a class is a subclass of another class, extending its instance variables and methods, and perhaps overriding some of them.
- static final
- This tag on a variable declaration indicates that its value won't ever be changed after being initially set. This is a good way to establish overall settings of a program in a clear, visible way, without "magic numbers" buried in the code.
- super
- A way to directly access a method of the superclass from a method of the subclass. By default, the superclass constructor is invoked automatically from the subclass constructor, but if it needs parameters, then this is a way to provide them. By default, other superclass methods are not automatically invoked from corresponding subclass methods; the whole point of the subclass method is to refine the functionality appropriately. If that refinement entails just doing something in addition, then specifically call the
super
. - import
- Lots of additional functionality is provided by extra packages; the
import
statement says to use them and the classes they define. When writing code from scratch, IntelliJ will help you figure out what needs to be imported from where. - type conversion / casting
- In some cases we can convert one type to another, e.g., saying that an
int
should be treated as adouble
(intuitively, 1 becomes 1.0). That conversion happens automatically, because it's safe — no loss of information. Going the other way might lose information, so requires a type cast (e.g., (int)1.6), saying to treat thisdouble
as anint
, throwing away the decimal part (so (int)1.6 becomes 1). - conditional
- Java has the usual kinds of conditionals, with the "if" test in parentheses, the "then" in braces, and optionally an "else" (which can be an "else if" to tack on another conditional).
- more math functions
- Beyond +, -, etc., the Math package has additional useful functions, including min, max, random, etc.
- visibility (public/private/protected)
- This part of a declaration says who has access: public (any other method has access), private (only the object's methods), or protected (subclasses can too). It's actually a bit more complicated than that, as we'll see later, but this is a good general sense.
- class type cast
- We saw this before with numbers; it also work for superclass to subclass when we somehow know that the object referenced by a variable is an instance of the subclass (though the variable is declared as the superclass).
- primitive types vs. objects
- Primitive types, including
int
(integer numbers),double
andfloat
(floating point numbers),boolean
(true
orfalse
), andchar
(a single character) are stored directly in a variable and are passed by value. Objects (includingString
s) are stored as references to the actual data and passed that way.