import java.util.ArrayList;

class Box<T> {
  T value; // T is a placeholder for any data type

  void set(T value) {
    this.value = value;
  }

  T get() {
    return value;
  }
}

class Car extends Box<String> {
	public Car(String p){
    	super.value = p;
    }
    
    @Override
    public String toString() {
        return this.value;
    }
}

public class Main {
  public static void main(String[] args) {
    // Create a Box to hold a String
    Box<String> stringBox = new Box<>();
    stringBox.set("Hello");
    System.out.println("Value: " + stringBox.get());

    // Create a Box to hold an Integer
    Box<Integer> intBox = new Box<>();
    intBox.set(123);
    System.out.println("Value: " + intBox.get());
    
    // Create a box to hold an Car 
    Box<Car> carBox = new Box<>();
    Car myCar = new Car("ABC123");
    carBox.set(myCar);
    System.out.println("Value: " + carBox.get());

    //Collection before Generics
    ArrayList list = new ArrayList(); 
	  list.add("Hello"); //Upcasting. String -> Object
    list.add(123); // Integer -> Object
    String str = (String)list.get(0); //Downcasting. Object -> String. Without casting manually, compile error.
    Object strOb = list.get(0);

    //String num = (String)list.get(1); //Runtime Error
    Object numOb = list.get(1);

    System.out.println(str);

    //Collection with Generics
    ArrayList<String> list02 = new ArrayList<>();
    list02.add("Hi"); //Upcasting. String -> Object
    //list02.add(123); //Compile Error
    String str02 = list02.get(0); //Downcasting automatically and safely by compiler. Object -> String
    
    System.out.println(str02);
  }
}
