Java Generics Concepts: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
No edit summary
No edit summary
 
(58 intermediate revisions by the same user not shown)
Line 2: Line 2:
* [[Java_Generics#Subjects|Java Generics]]
* [[Java_Generics#Subjects|Java Generics]]
=Overview=
=Overview=
'''Generics''', or '''generic types''', or '''parameterized types''' are a Java language extension and a set of compiler features introduced in [[Java#Java_5|Java 5]] and improved in subsequent Java releases, which allow writing more reliable code by making certain categories of bugs detectable at compile time.
'''Generics''', or '''[[#Generic_Type|generic types]]''', or '''parameterized types''' are a Java language extension and a set of compiler features introduced in [[Java#Java_5|Java 5]] and improved in subsequent Java releases, which allow writing more reliable, type-safe code. Generics make certain categories of type-related bugs detectable at compile time.


Generics enable types (classes and interfaces) and individual methods to be parameterized with other types when they are declared in source code. '''Type parameters''' are formal parameters of a type or a method much like a function parameters allow the function code to produce different results in presence of different arguments, when invoked. Type parameters provide a way to re-use the same code with different inputs - other types in this case.
Generics enable types (classes and interfaces) and individual methods to be parameterized with other types when they are declared in the source code. '''Type parameters''' are formal parameters of a type or of a method much like a function parameters allow the function code to produce different results when invoked in the presence of different arguments. Type parameters provide a way to re-use the same code with different inputs - other types in this case.
<syntaxhighlight lang='java'>
<syntaxhighlight lang='java'>
public class SomeClass<T> {
public class SomeClass<T> {
   public void doSomething(T arg) {
   public T doSomething(T t) {
     // ...
    System.out.println(t);
    return t;
  }
  public <V> V doSomethingElse(V v) {
    System.out.println(v);
    return v;
  }
}
 
public class Main() {
  public static void main(String[] args) {
 
     SomeClass<String> scs = new SomeClass<>();
    String s = scs.doSomething("something"); // no cast
    // scs.doSomething(new Object()); // compile-time failure: "error: incompatible types: Object cannot be converted to String"
 
    SomeClass<Integer> sci = new SomeClass<>();
    Integer i = sci.doSomething(1); // no cast
 
    Double d = sci.doSomethingElse(1.0d); // no cast
   }
   }
}
}
</syntaxhighlight>
</syntaxhighlight>
Java code that uses generics has several benefits over non-generic code:
* Allows for stronger type checks at compile time.
* Removes the need to cast.
* Enables programmers to implement generic algorithms - algorithms that work on collections of different types, can be customized, are type safe and easier to read.
=Generic Type=
==Generic Type Declaration==
A '''generic type''' or a '''parameterized type''' is a class or an interface that is parameterized over one or more types.


=Type Inference=
The syntax of a generic type declaration is:
<font size=-1>
<font color='green'>class</font>|<font color='green'>interface</font> <font color='blue'>name</font><<i>T1</i>, <i>T2</i>, ... <i>Tn</i>> {
  <font color='gray'>// ...</font>
}
</font>
 
==<span id='Type_Variable'></span>Type Parameters==
The '''type parameter''' section is delimited by angle brackets (<code><...></code>). The type parameters are also called '''type variables'''. A type variable can be any non-primitive type: any class type, any interface type, any array type, <font color=darkgray>or even another type variable</font>. The type variables can be used anywhere inside the class:
<font size=-1>
  private <i>T1</i> a;
  private <i>T2</i> b;
  public void something(<i>T1</i> t1, <i>T2</i> t2) {
    <font color='gray'>// ...</font>
  }
</font>
By convention, type parameter names are single, uppercase letters. Without this convention it would be difficult to tell the difference between a type variable and an ordinary class or interface name. The most commonly used parameter names are:
* E - Element
* K - Key
* V - value
* N - Number
* T - type
* S, U, V, etc. - 2nd, 3rd and 4th types
 
==Generic Type Invocation==
To use a generic type from code, the generic type variable must be declared by replacing the type variables with concrete type values, such as <code>String</code> or <code>Integer</code>. This is called "invoking the type", or "using the type". A generic type invocation can be thought to be similar to an ordinary method invocation, where instead of passing method arguments, we are passing <span id='Type_Argument'></span>'''type arguments''', which are actual types. As such, a variable that has a generic type is declared as follows:
<syntaxhighlight lang='java'>
SomeType<String> s;
</syntaxhighlight>
<span id='Ivoking_Paremeterized_Types_with_Other_Parameterized_Types'></span>It is possible to invoke a parameterized type using another parameterized type, with all its type parameters replaced by concrete type arguments:
<syntaxhighlight lang='java'>
SomeType<List<String>> s;
</syntaxhighlight>
Note that type parameters and type arguments are not the same, and are not interchangeable, in the same way function parameters and arguments are not the same. See: {{Internal|Variables, Parameters, Arguments|Variables, Parameters, Arguments}}
To instantiate a class of a generic type, use the <code>new</code> keyword as usual, but replace the generic type parameter with an actual class or interface - the type argument:
<syntaxhighlight lang='java'>
SomeType<String> s = new SomeType<String>();
</syntaxhighlight>
===<span id='The_Diamond'></span>The Diamond <>===
Starting with [[Java#Java_7|Java 7]], the type arguments required to invoke the constructor of a generic class can be replaced with an empty set of type arguments <code><></code>, as long as the compiler can infer the type arguments from the context:
<syntaxhighlight lang='java'>
SomeType<Integer> st = new SomeType<>();
</syntaxhighlight>
 
=Raw Type=
A '''raw type''' is a [[#Generic_Type|generic type]] [[#Generic_Type_Invocation|invoked]] without any type argument. The <code>SomeType</code> class declared in [[#Overview|Overview]] invoked as below creates a raw type:
<syntaxhighlight lang='java'>
SomeType rawSt = new SomeType();
</syntaxhighlight>
Raw types show up in legacy code because many API classes, such as the Collections classes, were not generic prior to [[Java#Java_5|Java 5]]. While allowing raw type invocations, the compiler warns:
 
For backward compatibility, assigning a parameterized type to its raw type is allowed:
<syntaxhighlight lang='java'>
SomeType rawSt = new SomeType();
SomeType<String> st = rawSt; // compiler will issue a warning: unchecked conversion.
</syntaxhighlight>
 
 
Note that a non-generic class or interface, meaning a type that was not declared as generic, is NOT a raw type.
 
=Organizatorium=
==Type Inference==
{{Internal|Java 7 Type Inference|Java Type Inference}}
{{Internal|Java 7 Type Inference|Java Type Inference}}
=Organizatorium=
==Covariance==
==Covariance==
{{External|https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)}}
{{External|https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)}}
Let T and S be two types (class or function types), such that S is a subtype of T. If method m of T is overridden in S, then the corresponding types from the m's signature can either preserve the relationship between T and S (the type used in S is a subtype of the corresponding type in T), reverse the relationship (the type used in S is a super type of the type used in T), or neither preserve nor reverse this relationship. If they preserve the relationship to T and S, we say they are covariant, if they reverse the relationship of T and S, we say they are contravariant.
Let T and S be two types (class or function types), such that S is a subtype of T. If method m of T is overridden in S, then the corresponding types from the m's signature can either preserve the relationship between T and S (the type used in S is a subtype of the corresponding type in T), reverse the relationship (the type used in S is a super type of the type used in T), or neither preserve nor reverse this relationship. If they preserve the relationship to T and S, we say they are covariant, if they reverse the relationship of T and S, we say they are contravariant.

Latest revision as of 18:40, 17 August 2023

Internal

Overview

Generics, or generic types, or parameterized types are a Java language extension and a set of compiler features introduced in Java 5 and improved in subsequent Java releases, which allow writing more reliable, type-safe code. Generics make certain categories of type-related bugs detectable at compile time.

Generics enable types (classes and interfaces) and individual methods to be parameterized with other types when they are declared in the source code. Type parameters are formal parameters of a type or of a method much like a function parameters allow the function code to produce different results when invoked in the presence of different arguments. Type parameters provide a way to re-use the same code with different inputs - other types in this case.

public class SomeClass<T> {
  public T doSomething(T t) {
    System.out.println(t);
    return t;
  }
  public <V> V doSomethingElse(V v) {
    System.out.println(v);
    return v;
  }
}

public class Main() {
  public static void main(String[] args) {

    SomeClass<String> scs = new SomeClass<>();
    String s = scs.doSomething("something"); // no cast
    // scs.doSomething(new Object()); // compile-time failure: "error: incompatible types: Object cannot be converted to String"

    SomeClass<Integer> sci = new SomeClass<>();
    Integer i = sci.doSomething(1); // no cast

    Double d = sci.doSomethingElse(1.0d); // no cast
  }
}

Java code that uses generics has several benefits over non-generic code:

  • Allows for stronger type checks at compile time.
  • Removes the need to cast.
  • Enables programmers to implement generic algorithms - algorithms that work on collections of different types, can be customized, are type safe and easier to read.

Generic Type

Generic Type Declaration

A generic type or a parameterized type is a class or an interface that is parameterized over one or more types.

The syntax of a generic type declaration is:

class|interface name<T1, T2, ... Tn> {
  // ...
}

Type Parameters

The type parameter section is delimited by angle brackets (<...>). The type parameters are also called type variables. A type variable can be any non-primitive type: any class type, any interface type, any array type, or even another type variable. The type variables can be used anywhere inside the class:

 private T1 a;
 private T2 b;
 public void something(T1 t1, T2 t2) {
   // ...
 }

By convention, type parameter names are single, uppercase letters. Without this convention it would be difficult to tell the difference between a type variable and an ordinary class or interface name. The most commonly used parameter names are:

  • E - Element
  • K - Key
  • V - value
  • N - Number
  • T - type
  • S, U, V, etc. - 2nd, 3rd and 4th types

Generic Type Invocation

To use a generic type from code, the generic type variable must be declared by replacing the type variables with concrete type values, such as String or Integer. This is called "invoking the type", or "using the type". A generic type invocation can be thought to be similar to an ordinary method invocation, where instead of passing method arguments, we are passing type arguments, which are actual types. As such, a variable that has a generic type is declared as follows:

SomeType<String> s;

It is possible to invoke a parameterized type using another parameterized type, with all its type parameters replaced by concrete type arguments:

SomeType<List<String>> s;

Note that type parameters and type arguments are not the same, and are not interchangeable, in the same way function parameters and arguments are not the same. See:

Variables, Parameters, Arguments

To instantiate a class of a generic type, use the new keyword as usual, but replace the generic type parameter with an actual class or interface - the type argument:

SomeType<String> s = new SomeType<String>();

The Diamond <>

Starting with Java 7, the type arguments required to invoke the constructor of a generic class can be replaced with an empty set of type arguments <>, as long as the compiler can infer the type arguments from the context:

SomeType<Integer> st = new SomeType<>();

Raw Type

A raw type is a generic type invoked without any type argument. The SomeType class declared in Overview invoked as below creates a raw type:

SomeType rawSt = new SomeType();

Raw types show up in legacy code because many API classes, such as the Collections classes, were not generic prior to Java 5. While allowing raw type invocations, the compiler warns:

For backward compatibility, assigning a parameterized type to its raw type is allowed:

SomeType rawSt = new SomeType();
SomeType<String> st = rawSt; // compiler will issue a warning: unchecked conversion.


Note that a non-generic class or interface, meaning a type that was not declared as generic, is NOT a raw type.

Organizatorium

Type Inference

Java Type Inference

Covariance

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

Let T and S be two types (class or function types), such that S is a subtype of T. If method m of T is overridden in S, then the corresponding types from the m's signature can either preserve the relationship between T and S (the type used in S is a subtype of the corresponding type in T), reverse the relationship (the type used in S is a super type of the type used in T), or neither preserve nor reverse this relationship. If they preserve the relationship to T and S, we say they are covariant, if they reverse the relationship of T and S, we say they are contravariant.