Section 12. (Generics) Udemy - Java Programming Masterclass
- Git
- Overview
- Generic naming convention
- Raw usage of generic classes
- Converting ArrayList to a List of a specific type
- Comparable interface
- Comparator interface
- Generic Method example
- Generic Type Parameters
- Multiple bounds
- Generic Wildcards
Git
Overview
- java supports generic types, such as
- classes
- records
- interfaces
- methods
- generics allow the compiler to do compile-time type checking, when adding and processing elements in the list
Example
1 2 3 4 5 6 7 8 9
// not using generics public class ITellYou { private String field; } // using generics public class ITellYou<T> { private T field; }
- T is the placeholder for a type that will be specified later
- T is the type identifier
- The type idenfiter can be any letter or word but T is short for Type which is why it is commonly used
More than 1 Generic Type
- You can have more than 1 type parameter
convention is T, S, U in that order
1 2 3 4 5 6
public class Team<T, S, U, V> { private T variable1; private S variable2; private U variable3; private V variable4; }
Generic naming convention
- some letters are reserved for special use cases
- the most commonly used type parameters are
- E - element (used extensively by the Java Collections framework)
- K - key (used for mapped types)
- N - for Number
- T - for Type
- V - for Value
- S, U, V etc for 2nd, 3rd, 4th types
Raw usage of generic classes
- you can use generic classes without specifying a type in the type parameters
- this is called the Raw Use of the reference type
- raw use is still available for backwards compability, but is highly discouraged
1 2 3 4 5 6 7 8 9 10 11
public class MyArray <T> { T myvar; } public static void Main(String.. args){ // raw type MyArray rawtype = new MyArray(); // generic type (preferred) MyArray<Integer> myintarray = new MyArray<>(); }
Bounded Generic classes
- Generic classes can be bounded, limiting the types that can use it
1
public class Team <T extends Player>
- this extends keyword doesn’t have the same meaning as extends, when its used in a class declaration
- This isn’t saying that our type T extends Player although it could
- All this is saying is that the paramaterized type T, has to be a Player, or a subtype of Player
- A subclass that has extended or implements Player would work just fine
- this is saying that a type that extends the Player class can be passed as a type to this class during instanciation
- the example is called an upper bound
Converting ArrayList to a List of a specific type
- stackoverflow
- ArrayList.toArray() will always return an array of objects if no parameter of the specific type requested is passed to it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// our String arraylist
ArrayList<String> someData = new ArrayList<>();
// this works fine
String someLine = someData.get(0);
// but this will fail, this is because .toArray() will return an array of objects
String[alt-text] arrayOfData = someData.toArray();
// the way to do it is the following
String[alt-text] arrayOfData = someData.toArray(new String[0]);
// or
String[alt-text] arrayOfData = someData.toArray(new String[alt-text]{});
Comparable interface
- Comparable is an interface
- if a class implements Comparable it may use the Arrays.sort() to sort the elements in the array
1
2
3
public interface Comparable<T> {
int compareTo(T o);
}
If we wanted to have our class be able to use the sort method on an array we would have to implement it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum Name{TRESTEN, KLARI, BRIANA}
public class Student implements Comparable<Student> {
// field
private Name name;
// constructor
public Student(Name name) {
this.name = name;
}
@Override
public int compareTo(Student otherStudent) {
return name.ordinal() - otherStudent.name.ordinal();
// example if Tresten is comparing against Klari would return -1 which means that Klari > Tresten
// example if Tresten is comparing against Briana would return -2 which means that Briana > Tresten
// example if Tresten is comparing against Tresten would return 0 which means that Tresten == Tresten
// ordinal() is a method you can call on an enum to return the index in the enum, if we wanted to change the order we would just have to
// ... change the order in the enum
}
}
- We have to pass the Student into the Comparable Type parameter on line 3 to tell the interface to take in a Student as an argument for the compareTo() method
Comparator interface
- the Comparator interface is similiar to the Comparable interface, and they often get confused for one another
- They both have different method signatures
- it’s common practice to include a Comparator as a nested class
1
2
3
public interface Comparator<T> {
int compare(T o, T o2);
}
1
2
3
4
5
6
7
8
public class StudentGPAComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return (String.valueOf(o1.gpa)).compareTo(String.valueOf(o2.gpa));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String... args){
// create the students
Student student1 = new Student("Tresten");
Student student2 = new Student("Briana");
Student student3 = new Student("Bianca");
// fill the array
Student[alt-text] studentsArray = new Student[alt-text]{student3, student1, student2};
// create instance of the Comparator
Comparator<Student> gpaSorter = new StudentGPAComparator();
// sort the array in reverse order
Arrays.sort(studentsArray, gpaSorter.reversed());
}
- to sort in reverse order just use the .reversed() method on the class that implements Comparator
Generic Method example
- For the following assume the following. A subclass LPAStudent extends the Student base class
1
2
3
4
5
6
7
public class Student {
// implementation goes here
}
public class LPAStudent extends Student{
// implementation goes here
}
- In the main function we are creating an arraylist of LPAStudents
- We also create a static function called printList() which takes in a List of Students as an argument
- We will get a compiler error if we try to call printList(students) with a list of LPAStudents even though it is a subclass of Student
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String... args){
// regular students
int studentCount = 10;
List<Student> students = new ArrayList<>();
for(int i = 0; i < studentCount; i++){
students.add(new Student());
}
students.add(new LPAStudent()); // we can add an lps student to the List<Student>
printList(students);
// lpa students
int lpaStudentCount = 10;
List<LPAStudent> lpaStudents = new ArrayList<>();
for(int i = 0; i < lpaStudentCount; i++){
lpaStudents.add(new LPAStudent());
}
printList(lpaStudents); // this gives compiler error because LPAStudent doesn't inherit from the Student class
// however, if we removed the <Student> out of the type parameter for the printList method this would work but it would mean that
// ... List is using raw generic type, which is bad
}
public static void printList(List<Student> students){
for (var student : students){
System.out.println(student);
}
System.out.println();
}
- So how do we get around this ???
- but we can also use a generic wildcard shown later
- or we can create a generic method with type parameters like shown below
1
2
3
4
5
6
7
// now the parameter will be a class that is upper bounded by the Student class meaning any subclass of Student
public static <T extends Student> void printList(List<T> students){
for (var student : students){
System.out.println(student);
}
System.out.println();
}
- When used as a reference types, a container of one type has no relationship to the same container of another type, even if the contained types do have a relationship
Limitation of a reference of generic classs with a list argument
- When we declare a variable or method parameter with
List<Student>
- Only List subtypes with Student elements can be assigned to this variable or method argument like the following:
ArrayList<Student>()
LinkedList<Student>()
Generic Type Parameters
- Type parameters are placed after any modifer and before the methods return type
- Type parameters can be referenced in the:
- method parameters
- method return type
- method code block
- We can also add an upperbound to the generic type
1
2
3
4
5
6
public static <T extends Student> void printList(List<T> students){
for (var student : students){
System.out.println(student);
}
System.out.println();
}
Multiple bounds
- you can specifiy a type parameter to be a subclass of multiple base classes as shown below
- if you extend a class and an interface, the class be listed first
- you can extend up to one class at most
- you can extend zero to many interfaces
1
2
3
4
public class GenericClass<T extends AbstractClassA & interfaceA & interfaceB>
{
// implementation ...
}
Generic Wildcards
- Generic method that use type parameters are different than generic wildcards
- Wildcards are represented by a question mark ?
- Wildcards cannot be used to define a generic class or interface
- A wildcard can only used in a type argument, not in the type parameter declaration like below
public static void myFunc(List<? extends Animal> inputList){ ... }
This is OKAY in a type argumentTeam<? extends Player> myteam = new Team<>();
This would NOT BE VALID in a type parameter declaration
1
2
3
4
5
6
7
// using generic wildcard that means that the List parameter can accept any subtype of Student
public static void printList(List<? extends Student> students){
for (var student : students){
System.out.println(student);
}
System.out.println();
}
Wildcard upperbound, lowerbound
Type Erasure
- Generics exist to enforce tighter type checks, at compile time.
- The compiler transforms a generic class into a typed class, meaning the byte code, or class file, contains no type parameters
- Everywhere a type parameter is used in a class, it gets replaced with either the type Object, if no upperbound was specified. or the upper bound type itself
- This transformation process is called type erasure, because the T parameter is erased, or replaced with a true type
Understanding how type erasure works for overloaded methods, may be imporant
- Example
1 2 3 4 5 6 7 8
public static <E> boolean containsElement(E [alt-text] elements, E element){ for (E e : elements){ if(e.equals(element)){ return true; } } return false; }
- gets transformed into this since the Type E was unbounded and thus gets turned into the Object type at compile-time
1
2
3
4
5
6
7
8
public static boolean containsElement(Object [alt-text] elements, Object element){
for (Object e : elements){
if(e.equals(element)){
return true;
}
}
return false;
}
Pitfalls of wildcards
- in he example below we are accepting a wildcard of Student or any subclass of Student
- however we get an error on line 5 because it says that we are trying to set the an element in a of subclass Student but not Student with a Student object
- The compiler is saying that the list students may be a Student but may also be a subtype of Student so it would not be valid
1
2
3
4
5
6
7
8
9
10
11
public static void printMoreList(List<? extends Student> students){
// assigning the first element in the students list to the last student in the list
Student last = students.get(students.size()-1);
students.set(0, last); // COMPILE-TIME ERROR: Required type : "capture of ? extends Student" Provided: "Student"
for (var student : students){
System.out.println( student);
}
System.out.println();
}
This post is licensed under CC BY 4.0 by the author.