import java.util.*;

class Ideone {
	private static class Pair<T> {
        private T first;
        private T second;

        public Pair(T first, T second) {
            this.first = first;
            this.second = second;
        }

        public void setFirst(T first) {
            this.first = first;
        }

        public void setSecond(T second) {
            this.second = second;
        }

        public T getFirst() {
            return first;
        }

        public T getSecond() {
            return second;
        }

        @Override
        public String toString() {
            return String.format("(%s, %s)", first, second);
        }
    }
	
	public static void main (String[] args) {
		Pair<?> stringPair = new Pair<>("1", "2");
        Pair<?> intPair = new Pair<>(3, 4);
        
        // список, который может хранить в себе любые пары
        List<Pair<?>> list1 = new ArrayList<>(Arrays.asList(stringPair, intPair));
        // тут всё безопасно в отличие от raw типов, потому что читать 
        // из такой пары можно разве что в переменную типа Object, а 
        // записать в неё можно разве что null
        Pair<?> pair = list1.get(0);
        
        // всё в порядке, по сути то же самое
        new ArrayList<Pair<?>>(Collections.singleton(stringPair)).add(intPair);
        // ошибка времени компиляции, потому что из-за capture conversion
        // был создан ArrayList<X>, в который нельзя засунуть intPair
        new ArrayList<>(Collections.singleton(stringPair)).add(intPair);
	}
}