1. All classes inherit Object
All classes in Java implicitly inherit the Object
class.
We'll analyze what inheritance is and how it works in Java in the Java Core quest. For now, we'll consider one simple fact that follows from this:
An object of any class can be assigned to an Object
variable. Example:
Code | Note |
---|---|
|
The o variable stores a reference to a Scanner object |
|
The o variable stores a reference to a String object |
|
The o variable stores a reference to an Integer object |
|
The o variable stores a reference to a String object |
This is where the good news ends. The compiler doesn't keep track of the original type of object saved in an Object
variable, so you won't be able to call methods on the saved object other than the methods of the Object
class.
If you need to call the methods associated with the object's original type, then you need to first save a reference to it in a variable of the correct type, and then call the methods on that variable:
Code | Note |
---|---|
|
The program will not compile. The Object class has no nextInt() method. |
|
This will work. Here we save a reference to a Scanner object in a Scanner variable using a typecast operator.
|
You can't just go and assign an Object
variable to a Scanner variable, even if the Object
variable stores a reference a Scanner
object. But you can do this if you use the typecast operator, which you already know about. This is its general appearance:
Type name1 = (Type) name2;
Where name1
is the name of a Type
variable, and name2
is the name of an Object
variable that stores a reference to a Type
object.
Typecasting
If variable's type and the object's type do not match, then a ClassCastException
will be thrown. Example:
Code | Note |
---|---|
|
An error will occur at runtime: a ClassCastException will be thrown here |
There's a way to avoid this error in Java: we do this by checking the type of the object stored in a variable:
name instanceof Type
The instanceof
operator checks whether the name
variable is a Type
object.
As an example, let's find a string in an array of diverse objects:
Code | Note |
---|---|
|
Autoboxing will convert these values into an Integer , String , and Double , respectively.Loop over the array of objects If the object is a String Save it to a String variableDisplay the variable on the screen. |
2. Why generics appeared — collections
Let's return to collections.
As soon as Java developers created the ArrayList
class, they wanted to make it universal, so it could store any type of object. So they used an array of Object
s to store the elements.
The strength of this approach is that you can add an object of any type to the collection.
Of course, there are several weaknesses.
Disadvantage 1.
It was always necessary to write a type conversion operator when retrieving elements from a collection:
Code | Note |
---|---|
|
Create a collection to store references to Object objectsFill the collection with numbers 10 , 20 , ... 100 ;Sum the elements of the collection Typecasting is necessary |
Disadvantage 2.
There was no guarantee that a collection contains a specific type of element
Code | Note |
---|---|
|
Create a collection to store references to Object objectsWe fill the collection with numbers represented as Double objects:0.0 , 2.5 , 5.0 , ...Sum the elements of the collection There will be an error: a Double cannot be cast to an Integer |
Data can be put into the collection anywhere:
- in another method
- in another program
- from a file
- over the network
Disadvantage 3.
The data in the collection can be changed accidentally.
You can pass a collection filled with your data to some method. That method, written by a different programmer, adds its data to your collection.
The name of the collection doesn't clearly indicate which types of data can be stored in it. And even if you give your variable a clear name, a reference to it can be passed to a dozen methods, and those methods definitely won't know anything about the original name of the variable.
3. Generics
In Java, all these problems are eliminated by this cool thing called generics.
In Java, generics means the ability to add type parameters to types. The result is a complex composite type. The general view of such a composite type is this:
ClassName<TypeParameter>
This is a generic class. And it can be used wherever you normally use classes.
Code | Description |
---|---|
|
Creating variables |
|
Creating objects |
|
Creating arrays |
Only Integer
variables can be stored in such a collection:
Code | Description |
---|---|
|
ArrayList collection with Integer elementsThis is allowed And this will also work Autoboxing
But this is not allowed: compilation error |
You will learn how to create your own classes with type parameters in the Java Collections quest. For now, we'll look at how to use them and how they work.
4. How generics work
Actually, generics are terribly primitive.
The compiler simply replaces generic types with ordinary types. But when methods of a generic type are used, the compiler adds a typecast operator to cast parameters to the type parameters:
Code | What the compiler does |
---|---|
|
|
|
|
|
|
|
|
Suppose we have a method that sums the numbers in a collection of integers:
Code | What the compiler does |
---|---|
|
|
In other words, generics are a kind of syntactic sugar, just like autoboxing, but a little more. With autoboxing, the compiler adds methods for converting an int
to an Integer
and vice versa, and for generics it adds typecast operators.
After the compiler compiles your generic classes with type parameters, they are simply converted to ordinary classes and typecast operators. Information about the type arguments passed to variables of generic types is lost. This effect is also called type erasure.
Sometimes programmers writing generic classes (classes with type parameters) really need the information about the types passed as arguments. In the Java Collections quest, you'll learn how to deal with this and what it entails.
5. A few facts about generics
Here are some more interesting facts about generics.
Classes can have several type parameters. It looks something like this:
ClassName<TypeParameter1, TypeParameter2, TypeParameter3>
Actually, this isn't really surprising. Anywhere the compiler can add an operator to cast to one type, it can add multiple typecast operators.
Examples:
Code | Note |
---|---|
|
The put method's first parameter is an Integer , and the second is a String |
Generic types can also be used as parameters. It looks something like this:
ClassName<TypeParameter<TypeParameterParameter>>
Suppose we want to create a list that will store lists of strings. In this case, we will get something like this:
// List of greetings
ArrayList<String> listHello = new ArrayList<String>();
listHello.add ("Hello");
listHello.add ("Hi");
// List of goodbyes
ArrayList<String> listBye = new ArrayList<String>();
listBye.add("Bye");
listBye.add ("Goodbye");
// List of lists
ArrayList<ArrayList<String>> lists = new ArrayList<ArrayList<String>>();
lists.add(listHello);
lists.add(listBye);
Generic types (types with type parameters) can also be used as array types. It looks something like this:
ClassName<TypeParameter>[] array = new ClassName<TypeParameter>[size];
There's nothing magical happening here: the angle brackets just indicate the type name:
Code | Non-generic counterpart |
---|---|
|
|
|
|
|
|
GO TO FULL VERSION