ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Generics and Generic Methods
    StaticPL/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

    https://www.freecodecamp.org/news/understanding-java-generic-types-covariance-and-contravariance-88f4c19763d2/

    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

    댓글

Designed by Tistory.