Generics Tutorial – Example Class, Interface, Methods, Wildcards and much more

Generics Tutorial – Example Class, Interface, Methods, Wildcards and much more

Generics is one of the core feature of Java programming and it was introduced in Java 5. If you have been working on Java Collectionsand with version 5 or higher, I am sure that you have used it. Using generics with collection classes is very easy but it provides a lot more features than just creating the type of collection and we will try to learn features of generics in this article. Understanding generics can become confusing sometimes if we go with jargon words, so I would try to keep it simple and easy to understand.
We will look into below topics of generics in this tutorial.java-generics-tutorial
 
 
 

  1. Java Generics Example
  2. Generics with Class and Interfaces
  3. Generics Type Naming Convention
  4. Generics in Methods and Constructors
  5. Generics Bounded Type Parameters
  6. Generics and Inheritance
  7. Generic Classes and Subtyping
  8. Generics Wildcards
    1. Generics Upper Bounded Wildcard
    2. Generics Unbounded Wildcard
    3. Generics Lower bounded Wildcard
  9. Subtyping using Generics Wildcard
  10. Type Erasure

Java Generics Example

Generics was added in Java 5 to provide compile-time type checking and removing risk of ClassCastException that was common while working with collection classes. The whole collection framework was re-written to use generics for type-safety. Let’s see how generics help us using collection classes safely.

1 List list = new ArrayList();
2 list.add("abc");
3 list.add(new Integer(5)); //OK
4  
5 for(Object obj : list){
6         String str=(String) obj; //type casting leading to ClassCastException at runtime
7 }

Above code compiles fine but throws ClassCastException at runtime because we are trying to cast Object in the list to String whereas one of the element is of type Integer. After Java 5, we use collection classes like below.

1 List<String> list1 = new ArrayList<String>(); // java 7 ? List<String> list1 = new ArrayList<>();
2 list1.add("abc");
3 //list1.add(new Integer(5)); //compiler error
4  
5 for(String str : list1){
6      //no type casting needed, avoids ClassCastException
7 }

Notice that at the time of list creation, we have specified that the type of elements in the list will be String. So if we try to add any other type of object in the list, the program will throw compile time error. Also notice that in for loop, we don’t need type casting of the element in the list, hence removing the ClassCastException at runtime.

Generics with Class and Interfaces

We can define our own classes and interfaces with generics type. A generic type is a class or interface that is parameterized over types. We use angle brackets (<>) to specify the type parameter.

To understand the benefit, lets say we have a simple class as:

01 package com.journaldev.generics;
02  
03 public class GenericsTypeOld {
04  
05     private Object t;
06  
07     public Object get() {
08         return t;
09     }
10  
11     public void set(Object t) {
12         this.t = t;
13     }
14  
15         public static void main(String args[]){
16         GenericsTypeOld type = new GenericsTypeOld();
17         type.set("Pankaj");
18         String str = (String) type.get(); //type casting, error prone and can cause ClassCastException
19     }
20 }

Notice that while using this class, we have to use type casting and it can produce ClassCastException at runtime. Now we will use generics to rewrite the same class with generics type as shown below.

01 package com.journaldev.generics;
02  
03 public class GenericsType<T> {
04  
05     private T t;
06  
07     public T get(){
08         return this.t;
09     }
10  
11     public void set(T t1){
12         this.t=t1;
13     }
14  
15     public static void main(String args[]){
16         GenericsType<String> type = new GenericsType<>();
17         type.set("Pankaj"); //valid
18  
19         GenericsType type1 = new GenericsType(); //raw type
20         type1.set("Pankaj"); //valid
21         type1.set(10); //valid and autoboxing support
22     }
23 }

Notice the use of GenericsType class in the main method. We don’t need to do type-casting and we can remove ClassCastException at runtime. If we don’t provide the type at the time of creation, compiler will produce a warning that “GenericsType is a raw type. References to generic type GenericsType<T> should be parameterized”. When we don’t provide type, the type becomes Object and hence it’s allowing both String and Integer objects but we should always try to avoid this because we will have to use type casting while working on raw type that can produce runtime errors.

Tip: We can use @SuppressWarnings("rawtypes") annotation to suppress the compiler warning, check out java annotations tutorial.

Also notice that it supports java autoboxing.

Comparable interface is a great example of Generics in interfaces and it’s written as:

1 package java.lang;
2 import java.util.*;
3  
4 public interface Comparable<T> {
5     public int compareTo(T o);
6 }

In similar way, we can use generics in our interfaces and classes. We can also have multiple type parameters as in Map interface. Again we can provide parameterized value to a parameterized type also, for example new HashMap<String, List<String>>(); is valid.

Generics Type Naming Convention

Naming convention helps us understanding code easily and having a naming convention is one of the best practices of java programming language. So generics also comes with it’s own naming conventions. Usually type parameter names are single, uppercase letters to make it easily distinguishable from java variables. The most commonly used type parameter names are:

  • E – Element (used extensively by the Java Collections Framework, for example ArrayList, Set etc.)
  • K – Key (Used in Map)
  • N – Number
  • T – Type
  • V – Value (Used in Map)
  • S,U,V etc. – 2nd, 3rd, 4th types

Generics in Methods and Constructors

Sometimes we don’t want whole class to be parameterized, in that case we can use generics type in methods also. Since constructor is a special kind of method, we can use generics type in constructors too.

Here is a class showing example of generics type in method.

01 package com.journaldev.generics;
02  
03 public class GenericsMethods {
04  
05     //Generics in method
06     public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){
07         return g1.get().equals(g2.get());
08     }
09  
10     public static void main(String args[]){
11         GenericsType<String> g1 = new GenericsType<>();
12         g1.set("Pankaj");
13  
14         GenericsType<String> g2 = new GenericsType<>();
15         g2.set("Pankaj");
16  
17         boolean isEqual = GenericsMethods.<String>isEqual(g1, g2);
18         //above statement can be written simply as
19         isEqual = GenericsMethods.isEqual(g1, g2);
20         //This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets.
21         //Compiler will infer the type that is needed
22     }
23 }

Notice the isEqual method signature showing syntax to use generics type in methods. Also notice how to use these methods in our java program. We can specify type while calling these methods or we can invoke them like a normal method. Java compiler is smart enough to determine the type of variable to be used, this facility is called as type inference.

Generics Bounded Type Parameters

Suppose we want to restrict the type of objects that can be used in the parameterized type, for example in a method that compares two objects and we want to make sure that the accepted objects are Comparables. To declare a bounded type parameter, list the type parameter’s name, followed by the extends keyword, followed by its upper bound, similar like below method.

1 public static <T extends Comparable<T>> int compare(T t1, T t2){
2         return t1.compareTo(t2);
3     }

The invocation of these methods is similar to unbounded method except that if we will try to use any class that is not Comparable, it will throw compile time error.

Bounded type parameters can be used with methods as well as classes and interfaces.

Generics supports multiple bounds also, i.e <T extends A & B & C>. In this case A can be an interface or class. If A is class then B and C should be interfaces. We can’t have more than one class in multiple bounds.

Generics and Inheritance

We know that Java inheritance allows us to assign a variable A to another variable B if A is subclass of B. So we might think that any generic type of A can be assigned to generic type of B, but it’s not the case. Lets see this with a simple program.

01 package com.journaldev.generics;
02  
03 public class GenericsInheritance {
04  
05     public static void main(String[] args) {
06         String str = "abc";
07         Object obj = new Object();
08         obj=str; // works because String is-a Object, inheritance in java
09  
10         MyClass<String> myClass1 = new MyClass<String>();
11         MyClass<Object> myClass2 = new MyClass<Object>();
12         //myClass2=myClass1; // compilation error since MyClass<String> is not a MyClass<Object>
13         obj = myClass1; // MyClass<T> parent is Object
14     }
15  
16     public static class MyClass<T>{}
17  
18 }

We are not allowed to assign MyClass<String> variable to MyClass<Object> variable because they are not related, in fact MyClass<T> parent is Object.

Generic Classes and Subtyping

We 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.

For example, ArrayList<E> implements List<E> that extends Collection<E>, so ArrayList<String> is a subtype of List<String> and List<String> is subtype of Collection<String>.

The subtyping relationship is preserved as long as we don’t change the type argument, below shows an example of multiple type parameters.

1 interface MyList<E,T> extends List<E>{
2 }

The subtypes of List<String> can be MyList<String,Object>, MyList<String,Integer> and so on.

Generics Wildcards

Question mark (?) is the wildcard in generics and represent an unknown type. The wildcard can be used as the type of a parameter, field, or local variable and sometimes as a return type. We can’t use wildcards while invoking a generic method or instantiating a generic class. In following sections, we will learn about upper bounded wildcards, lower bounded wildcards, and wildcard capture.

Generics Upper Bounded Wildcard

Upper bounded wildcards are used to relax the restriction on the type of variable in a method. Suppose we want to write a method that will return the sum of numbers in the list, so our implementation will be something like this.

1 public static double sum(List<Number> list){
2         double sum = 0;
3         for(Number n : list){
4             sum += n.doubleValue();
5         }
6         return sum;
7     }

Now the problem with above implementation is that it won’t work with List of Integers or Doubles because we know that List<Integer> and List<Double> are not related, this is when upper bounded wildcard is helpful. We use generics wildcard with extends keyword and theupper bound class or interface that will allow us to pass argument of upper bound or it’s subclasses types.

The above implementation can be modified like below program.

01 package com.journaldev.generics;
02  
03 import java.util.ArrayList;
04 import java.util.List;
05  
06 public class GenericsWildcards {
07  
08     public static void main(String[] args) {
09         List<Integer> ints = new ArrayList<>();
10         ints.add(3); ints.add(5); ints.add(10);
11         double sum = sum(ints);
12         System.out.println("Sum of ints="+sum);
13     }
14  
15     public static double sum(List<? extends Number> list){
16         double sum = 0;
17         for(Number n : list){
18             sum += n.doubleValue();
19         }
20         return sum;
21     }
22 }

It’s similar like writing our code in terms of interface, in above method we can use all the methods of upper bound class Number. Note that with upper bounded list, we are not allowed to add any object to the list except null. If we will try to add an element to the list inside the sum method, the program won’t compile.

Generics Unbounded Wildcard

Sometimes we have a situation where we want our generic method to be working with all types, in this case unbounded wildcard can be used. Its same as using <? extends Object>.

1 public static void printData(List<?> list){
2         for(Object obj : list){
3             System.out.print(obj + "::");
4         }
5     }

We can provide List<String> or List<Integer> or any other type of Object list argument to the printData method. Similar to upper bound list, we are not allowed to add anything to the list.

Generics Lower bounded Wildcard

Suppose we want to add Integers to a list of integers in a method, we can keep the argument type as List<Integer> but it will be tied up with Integers whereas List<Number> and List<Object> can also hold integers, so we can use lower bound wildcard to achieve this. We use generics wildcard (?) with super keyword and lower bound class to achieve this.

We can pass lower bound or any super type of lower bound as an argument in this case, java compiler allows to add lower bound object types to the list.

1 public static void addIntegers(List<? super Integer> list){
2         list.add(new Integer(50));
3     }

Subtyping using Generics Wildcard

1 List<? extends Integer> intList = new ArrayList<>();
2 List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

Type Erasure

Generics was added to provide type-checking at compile time and it has no use at run time, so java compiler uses type erasure feature to remove all the generics type checking code in byte code and insert type-casting if necessary. Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

For example if we have a generic class like below;

01 public class Test<T extends Comparable<T>> {
02  
03     private T data;
04     private Test<T> next;
05  
06     public Test(T d, Test<T> n) {
07         this.data = d;
08         this.next = n;
09     }
10  
11     public T getData() { return this.data; }
12 }

The Java compiler replaces the bounded type parameter T with the first bound interface, Comparable, as below code:

01 public class Test {
02  
03     private Comparable data;
04     private Test next;
05  
06     public Node(Comparable d, Test n) {
07         this.data = d;
08         this.next = n;
09     }
10  
11     public Comparable getData() { return data; }
12 }

Thats all for generics in java, generics is a really vast topic and requires a lot of time to understand and use it effectively. This post here is an attempt to provide basic details of generics and how can we use it to extend our program with type-safety.

本项目是一个基于SSM(Spring+SpringMVC+MyBatis)后端框架与Vue.js前端框架开发的疫情居家办公系统。该系统旨在为居家办公的员工提供一个高效、便捷的工作环境,同时帮助企业更好地管理远程工作流程。项目包含了完整的数据库设计、前后端代码实现以及详细的文档说明,非常适合计算机相关专业的毕设学生和需要进行项目实战练习的Java学习者。 系统的核心功能包括用户管理、任务分配、进度跟踪、文件共享和在线沟通等。用户管理模块允许管理员创建和管理用户账户,分配不同的权限。任务分配模块使项目经理能够轻松地分配任务给团队成员,并设置截止日期。进度跟踪模块允许员工实时更新他们的工作状态,确保项目按计划进行。文件共享模块提供了一个安全的平台,让团队成员可以共享和协作处理文档。在线沟通模块则支持即时消息和视频会议,以增强团队之间的沟通效率。 技术栈方面,后端采用了Spring框架来管理业务逻辑,SpringMVC用于构建Web应用程序,MyBatis作为ORM框架简化数据库操作。前端则使用Vue.js来实现动态用户界面,搭配Vue Router进行页面导航,以及Vuex进行状态管理。数据库选用MySQL,确保数据的安全性和可靠性。 该项目不仅提供了一个完整的技术实现示例,还为开发者留下了扩展和改进的空间,可以根据实际需求添加新功能或优化现有功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值