-
Generics and Generic MethodsStaticPL/JAVA 2019. 8. 27. 11:45
1. Overview
Generic was designed to extend Java's type system to allow "a type or method to operate on objects of various types while providing compile-time type safety". The aspect of compile-time type safety was not fully achieved since it was shown in 2016 that it is not guaranteed in all classes.
2. Motivation
- Ensure type safety
- Ensure that generics wouldn't cause an overhead at runtime which is provided using type erasure on generics at compile time
- Resolving code cluttering
List list = new LinkedList(); list.add(new Integer(1)); Integer i = list.iterator().next(); // type casting required makes codes looks cluttering. Integer i = (Integer) list.iterator.next(); // Using generic, resolving explicit type casting as shown below. // The compiler can enforce the type at compile time. List<Integer> list = new LinkedList<>();
3. Description
3.1 Generic Methods
- Being able to have a type parameter which is enclosed within a diamond operator before the return type of the method declaration
- Type parameters can be bounded(also means restricted)
- Being able to have different type parameters separated by commas in the method signature
- Method body for a generic method is just like a normal method
// One generic type sample public <T> List<T> fromArrayToList(T[] a) { return Arrays.stream(a).collect(Collectors.toList()); } // Two generic type sample public static <T, G> List<G> fromArrayToList(T[] a, Function<T, G> mapperFunction) { return Arrays.stream(a) .map(mapperFunction) .collect(Collectors.toList()); } // Ensure generic function declared in fromArrayToList signature as second parameter // accept and return different type as signature @Test public void givenArrayOfIntegers_thanListOfStringReturnedOK() { Integer[] intArray = {1, 2, 3, 4, 5}; List<String> stringList = Generics.fromArrayToList(intArray, Object::toString); assertThat(stringList, hasItems("1", "2", "3", "4", "5")); }
3.2 Bounded Generics
3.2.1 extends
extends is the upper bound in case of a class or implements an upper bound in case of an interface
// The type T extends the upper bound in case of a class or implements an upper bound in case of an interface public <T extends Number> List<T> fromArrayToList(T[] a) { ... } // Also multiple upper bounds can be set as follow <T extends Number & Comparable>
3.2.2 super
super is Lower bounds in case of a class or implements an upper bound in case of an interface
<R, A> R collect(Collector<? super T, A, R> collector);
3.3 Wildcards with Generics
class Building { protected static void paint(Building building) { } } class House extends Building { } public class Main { public static void paintAllBuildings(List<Building> buildings) { buildings.forEach(Building::paint); } public static void main(String[] args) { List<House> houses = new ArrayList(); // compile error even if House is a subtype of Building. // It's like even though Object is the supertype of all Java calsses, // a collection of Object is not the supertype of any collection. // This also applies a List<Object> is not the supertype of List<String> and // assigning a variable of type List<Object> to a variable of type List<String> // will cause a compiler error. This is to prevent possible conflicts that can happen // if we add heterogeneous types to the same collection paintAllBuildings(houses); } } // But using magical tool which is wildcard can do the thing. // This method will work with type Building and all its subtypes. // This is called an upper bounded wildcard where type Building is the upper bound. public class Main { public static void paintAllBuildings2(List<? extends Building> buildings) { buildings.forEach(Building::paint); } public static void main(String[] args) { List<House> houses = new ArrayList(); // compile error paintAllBuildings(houses); // no errror paintAllBuildings2(houses); } } // Wildcards can also be specified with a lower bound, // where the unknown type has to be a supertype of the specified type. // Lower bounds can be specified using the super keyword followed by the specific type // For example, <? super T> means unknown type that is a superclass of T(= T and all its parents) // Well known usage is collect method in java.util.stream.Stream.java <R, A> R collect(Collector<? super T, A, R> collector);
3.4 Type Erasure
// before being processed by complier public <T> List<T> genericMethod(List<T> list) { return list.stream().collect(Collectors.toList()); } // for illustration public List<Object> withErasure(List<Object> list) { return list.stream().collect(Collectors.toList()); } // which in practice results in public List withErasure(List list) { return list.stream().collect(Collectors.toList()); } // If the type is bounded, // Then the type will be replaced by the bound at compile time public <T extends Building> void genericMethod(T t) { ... } // after compilation public void genericMethod(Building t) { ... }
3.5 Generics and Primitive Data Types
A restriction of generics in Java is that the type parameter cannot be a primitive type
// An example of Why generics can't work with primitive types // generics are a compile-time feature, // meaning the type parameter is erased // and all generic types are implemented as type Object List<int> list = new ArrayList<>(); list.add(17); // The signature of the add method is boolean add(E e); // will be compiled to boolean add(Object e); // So, type parameters must be convertible to Object. // Since primitive types don't extend Object, we can't use them as type parameters // However, Java provides boxed types for primitives, along with autoboxing and unboxing // to unwrap them Integer a = 17; int b = a; // If we want to create a list which can hold integers, we can use the wrapper List<Integer> list = new ArrayList<>(); list.add(17); int first = list.get(0); // The compiled code will be the equivalent of List list = new ArrayList<>(); list.add(Integer.valueOf(17)); int first = ((Integer) list.get(0)).intValue();
4. Restriction
4.1 Type argument cannot be primitive
// int is primitive type. So it can't assign into type parameter Store<int> intStore = new Store<int>();
4.2 Type parameter cannot be used in a static context
public class Device<T> { private static T deviceType; } Device<Smartphone> phone = new Device<>(); Device<Pager> pager = new Device(); // What is T? It can't be
5. Convention
- T is meant to be a Type
- E is meant to be an Element (List<E>: a list of Elements)
- K is Key (in a Map<K, V>)
- V is Value (as a return value or mapped value)
- N is Number
They are fully interchangeable (conflicts in the same declaration notwithstanding).
6. References
https://www.baeldung.com/java-generics
https://www.tutorialspoint.com/java/java_generics.htm
https://en.wikipedia.org/wiki/Generics_in_Java
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html
https://medium.com/omnius/wildcards-in-java-generics-part-1-3-dd2ce5b0e59a
https://code.snipcademy.com/tutorials/java/generics/inheritance
'StaticPL > JAVA' 카테고리의 다른 글
Boxing, unboxing, and autoboxing (0) 2019.09.27 Functional (0) 2019.08.27 Reflection (0) 2019.08.27 Checked and Unchecked Exceptions (0) 2019.08.23 Fork Join framework (0) 2019.08.23