I must say that you will learn a lot! Not only this lesson, but also the next few lessons, will be devoted to generics.
So, if you're interested in generics, today's you're lucky day: you will learn a lot about the features of generics. And if not, resign yourself and relax! :) This is a very important topic, and you need to know it.
Let's start with the simple: the "what" and the "why".
What are generics?
Generics are types that have a parameter.
When creating a generic type, you specify not only a type, but also the data type that it will work with.
I'm guessing the most obvious example has already come to your mind: ArrayList! This is how we usually create one in a program:
As you might guess, a feature of this list is that we can't stuff everything into it: it works exclusively with
Suppose we want our list to only store
One of the programmers missed this comment and inadvertently put several Strings in a list of numbers and then calculated their sum:
Console output:
What's the worst part of this situation? Certainly not the carelessness of the programmer. The worst part is that incorrect code ended up in an important place in our program and compiled successfully. Now we'll encounter the bug not while writing code, but only during testing (and this is the best case scenario!). Fixing bugs in later stages of development costs much more — both in terms of money and time. This is precisely where generics benefit us: a generic class lets the unlucky programmer detect the error right away. The program simply won't compile!
The programmer immediately realizes his or her mistake and instantly gets better. By the way, we didn't have to create our own
Console output:
In other words, even using Java's "native" mechanisms, we can make this sort of mistake and create an unsafe collection. However, if we paste this code into an IDE, we get a warning: "Unchecked call to add(E) as a member of raw type of java.util.List" We're told that something may go wrong when adding an item to a collection that lacks a generic type. But what does the phrase "raw type" mean? A
Pay attention to the syntax. It looks a bit unusual:
We write <T> before the return type. This indicates that we're dealing with a generic method. In this case, the method accepts 2 parameters as an input: a list of T objects and another separate T object. By using <T>, we parameterize the method's parameter types: we can't pass in a list of Strings and an Integer. A list of Strings and a String, a list of Integers and an Integer, a list of our own
Imagine if we didn't have generic methods and needed the logic of the
Our
And in the last line of code, when we try to put the number 12345 inside the box, we get a compilation error! It's that easy! We've created our own generic class! :)
As you might guess, a feature of this list is that we can't stuff everything into it: it works exclusively with
String
objects.
Now let's take a little digression into Java's history and try to answer the question "why?" To do this, we'll write our own simplified version of the ArrayList class.
Our list only knows how to add data to and retrieve data from an internal array:
Suppose we want our list to only store
Integer
s. We're not using a generic type.
We don't want to include an explicit "instanceof Integer
" check in the add()
method. If we did, our whole class would be suitable only for Integer
, and we would have to write a similar class for every other data type in the world!
We'll rely on our programmers, and just leave a comment in the code to ensure that they don't add anything we don't want:
One of the programmers missed this comment and inadvertently put several Strings in a list of numbers and then calculated their sum:
Console output:
What's the worst part of this situation? Certainly not the carelessness of the programmer. The worst part is that incorrect code ended up in an important place in our program and compiled successfully. Now we'll encounter the bug not while writing code, but only during testing (and this is the best case scenario!). Fixing bugs in later stages of development costs much more — both in terms of money and time. This is precisely where generics benefit us: a generic class lets the unlucky programmer detect the error right away. The program simply won't compile!
The programmer immediately realizes his or her mistake and instantly gets better. By the way, we didn't have to create our own
List
class to see this sort of error.
Simply remove the angle brackets and type (<Integer>
) from an ordinary ArrayList!
Console output:
In other words, even using Java's "native" mechanisms, we can make this sort of mistake and create an unsafe collection. However, if we paste this code into an IDE, we get a warning: "Unchecked call to add(E) as a member of raw type of java.util.List" We're told that something may go wrong when adding an item to a collection that lacks a generic type. But what does the phrase "raw type" mean? A
raw type
is a generic class whose type has been removed.
In other words, List myList1
is a raw type
. The opposite of a raw type
is a generic type
— a generic class with an indication of the parameterized type(s)
. For example, List<String> myList1
.
You might ask why the language allows the use of raw types
?
The reason is simple. Java's creators left support for raw types
in the language in order to avoid creating compatibility problems. By the time Java 5.0 was released (generics first appeared in this version), a lot of code had already been written using raw types
.
As a result, this mechanism is still supported today.
We have repeatedly mentioned Joshua Bloch's classic book "Effective Java" in the lessons.
As one of the creators of the language, he didn't skip raw types
and generic types
in his book.
Chapter 23 of the book has a very eloquent title: "Don't use raw types in new code"
This is what you need to remember. When using generic classes, never turn a generic type
into a raw type
.
Generic methods
Java lets you parameterize individual methods by creating so-called generic methods. How are such methods helpful? Above all, they are helpful in that they let you work with different types of method parameters. If the same logic can be safely applied to different types, a generic method can be a great solution. Consider this a very simple example: Suppose we have a some list calledmyList1
. We want to remove all values from the list and fill all the empty spaces with new values.
Here's what our class looks like with a generic method:
Pay attention to the syntax. It looks a bit unusual:
We write <T> before the return type. This indicates that we're dealing with a generic method. In this case, the method accepts 2 parameters as an input: a list of T objects and another separate T object. By using <T>, we parameterize the method's parameter types: we can't pass in a list of Strings and an Integer. A list of Strings and a String, a list of Integers and an Integer, a list of our own
Cat
objects and another Cat
object — that's what we need to do.
The main()
method illustrates how the fill()
method can be easily used to work with different types of data.
First, we use the method with a list of Strings and a String as input, and then with a list of Integers and an Integer.
Console output:
Imagine if we didn't have generic methods and needed the logic of the
fill()
method for 30 different classes.
We would have to write the same method 30 times for different data types! But thanks to generic methods, we can reuse our code! :)
Generic classes
You aren't limited to the generic classes provided in the standard Java libraries — you can create your own! Here's a simple example:Our
Box<T>
class is a generic class. Once we assign a data type (<T>
) during creation, we are no longer able to place objects of other types in it.
This can be seen in the example. When creating our object, we indicated that it would work with Strings:
And in the last line of code, when we try to put the number 12345 inside the box, we get a compilation error! It's that easy! We've created our own generic class! :)
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.