- Java Generics with Subtyping and the Substitution Principle
- 1. What is subtyping?
- 2. What is the Substitution Principle?
- 3. Java Generics with Subtyping and the Substitution Principle
- Related Java Generics Tutorials:
- Other Java Collections Tutorial:
- About the Author:
- Add comment
- Comments
- Generics, Inheritance, and Subtypes
- Generic Classes and Subtyping
- Generics and Subtyping
Java Generics with Subtyping and the Substitution Principle
This article helps you understand a couple of important rules with regard to generics in Java. They are subtyping and the Substitution Principle.
1. What is subtyping?
Subtyping is a key feature of object-oriented programming languages such as Java. In Java, S is a subtype of T if S extends or implements T .
Subtyping is transitive, meaning that if R is a subtype of S , then R is also a subtype of T ( T is the super type of both S and R ).
— Integer is a subtype of Number
— ArrayList is a subtype of Collection
— String is a subtype of Object
2. What is the Substitution Principle?
The Substitution Principlewas developed in 1987 by Barbara Liskov — an American computer scientist (hence it is also called Liskov’s Substitution Principle). This principle is then adopted in object-oriented programming languages.
The Substitution Principle states that:
A variable of a given type may be assigned a value of any subtype, and a method with a parameter of a given type may be invoked with an argument of any subtype of that type.
Here’s a couple of examples in Java:
— Subtyping in declarations:
Number num = new Integer(2000); Object obj = new String(“Hello World”);
JPanel panel = new JPanel(); panel.add(new JTextField(20)); panel.add(new JButton(“OK”));
JPanel is a class in Swing, which has the add(Component) method, and JButton and JTextField are two subtypes of Component .
3. Java Generics with Subtyping and the Substitution Principle
A list of Number can contain Integer and Double objects (two subtypes of Number ), hence the following example:
List numbers = new ArrayList(); numbers.add(2016); // auto-boxing converts primitive to new Integer(2016) numbers.add(3.14); // auto-boxing converts primitive to new Double(3.14)
Here, as you can see, the Substitution Principle is applied for the reference type ( List ) and object type ( ArrayList ). The add() method can accept any type which is subtype of Number , thus an Integer and a Double objects can be added to the collection.
However, the Substitution Principle does not work with the parameterized types, meaning that it is illegal to write:
List numbers = new ArrayList();
WHY is this disallowed?
The following example explains why subtyping is not allowed for the parameterized types of generics:
List integers = new ArrayList(); integers.add(2016); integers.add(2017); List numbers = integers; // Compile error numbers.add(1.68); System.out.println(integers); // can print [2016, 2017, 1.68]
Look at the 4 th line. If that assignment is accepted by the compiler, then we can add a double number to a list of integer as shown in the last two lines, right?
That’s why the 4 th line causes a compile error to early prevent wrong objects from being added to the collection afterward.
Now, you understand why the Substitution Principle does not work with generics type, right?
That also means it’s illegal to pass a collection of a subtype to a method with a parameter of the supertype. The following example illustrates this rule well:
class Animal < >class Dog extends Animal < >class Test < public void addAnimal(Listanimals) < >public void test() < Listdogs = new ArrayList(); addAnimal(dogs); // COMPILE ERROR! > >
We’ll get a compile error because inside the addAnimal() method, it’s possible to add a object of Animal to the passed-in collection which may accepts only Dog objects.
— Subtyping can be used with reference type and object type in generics declaration.
— Subtyping cannot be used with parameterized types.
Related Java Generics Tutorials:
Other Java Collections Tutorial:
About the Author:
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He started programming with Java in the time of Java 1.4 and has been falling in love with Java since then. Make friend with him on Facebook and watch his Java videos you YouTube.
Add comment
Comments
Thank You for your answer, Nam. I think I understand what You mean. If I’d cast an object of type Animal to List will produce ClassCastException.
Thanks for your feedback, Kami.
I think it was correct. It means that the possibility of adding an object of type Animal to a List will cause issue, thus the code won’t compile.
Hello
I have a question. At the end of your article there is: «We’ll get a compile error because inside the addAnimal() method, it’s possible to add a object of Animal to the passed-in collection which may accepts only Dog objects.»
Should’t be: «We’ll get a compile error because inside the addAnimal() method, it’s NOT possible to add a object of Animal to the passed-in collection which may accepts only Dog objects.» ? Best regards.
CodeJava.net shares Java tutorials, code examples and sample projects for programmers at all levels.
CodeJava.net is created and managed by Nam Ha Minh — a passionate programmer.
Copyright © 2012 — 2023 CodeJava.net, all rights reserved.
Generics, Inheritance, and Subtypes
As you already know, it is possible to assign an object of one type to an object of another type provided that the types are compatible. For example, you can assign an Integer to an Object, since Object is one of Integer‘s supertypes:
Object someObject = new Object(); Integer someInteger = new Integer(10); someObject = someInteger; // OK
In object-oriented terminology, this is called an «is a» relationship. Since an Integer is a kind of Object, the assignment is allowed. But Integer is also a kind of Number, so the following code is valid as well:
public void someMethod(Number n) < /* . */ >someMethod(new Integer(10)); // OK someMethod(new Double(10.1)); // OK
The same is also true with generics. You can perform a generic type invocation, passing Number as its type argument, and any subsequent invocation of add will be allowed if the argument is compatible with Number:
Box box = new Box(); box.add(new Integer(10)); // OK box.add(new Double(10.1)); // OK
Now consider the following method:
What type of argument does it accept? By looking at its signature, you can see that it accepts a single argument whose type is Box . But what does that mean? Are you allowed to pass in Box or Box , as you might expect? The answer is «no», because Box and Box are not subtypes of Box .
This is a common misunderstanding when it comes to programming with generics, but it is an important concept to learn.
Box is not a subtype of Box even though Integer is a subtype of Number.
Note: Given two concrete types A and B (for example, Number and Integer), MyClass has no relationship to MyClass , regardless of whether or not A and B are related. The common parent of MyClass and MyClass is Object.
For information on how to create a subtype-like relationship between two generic classes when the type parameters are related, see Wildcards and Subtyping.
Generic Classes and Subtyping
You can subtype a generic class or interface by extending or implementing it. The relationship between the type parameters of one class or interface and the type parameters of another are determined by the extends and implements clauses.
Using the Collections classes as an example, ArrayList implements List , and List extends Collection . So ArrayList is a subtype of List , which is a subtype of Collection . So long as you do not vary the type argument, the subtyping relationship is preserved between the types.
A sample Collections hierarchy
Now imagine we want to define our own list interface, PayloadList, that associates an optional value of generic type P with each element. Its declaration might look like:
interface PayloadList extends List
The following parameterizations of PayloadList are subtypes of List :
Generics and Subtyping
Let's test your understanding of generics. Is the following code snippet legal?
List ls = new ArrayList(); // 1 List lo = ls; // 2
Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object . Most people instinctively answer, "Sure!"
Well, take a look at the next few lines:
lo.add(new Object()); // 3 String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Here we've aliased ls and lo . Accessing ls , a list of String , through the alias lo , we can insert arbitrary objects into it. As a result ls does not hold just String s anymore, and when we try and get something out of it, we get a rude surprise.
The Java compiler will prevent this from happening of course. Line 2 will cause a compile time error.
In general, if Foo is a subtype (subclass or subinterface) of Bar , and G is some generic type declaration, it is not the case that G is a subtype of G . This is probably the hardest thing you need to learn about generics, because it goes against our deeply held intuitions.
We should not assume that collections don't change. Our instinct may lead us to think of these things as immutable.
For example, if the department of motor vehicles supplies a list of drivers to the census bureau, this seems reasonable. We think that a List is a List , assuming that Driver is a subtype of Person . In fact, what is being passed is a copy of the registry of drivers. Otherwise, the census bureau could add new people who are not drivers into the list, corrupting the DMV's records.
To cope with this sort of situation, it's useful to consider more flexible generic types. The rules we've seen so far are quite restrictive.