Что такое принцип PECS в Java

PECS рашифровывается как Producer -- extends, Consumer -- super, что можно перевести как, если у тебя продьюсер, то используй extends, если у тебя консьюмер, то используй super. Связан этот принцип с коллекциями, где при использовании обобщений (а, начиная с Java 5, коллекции рекомендовано использовать только с обобщениями) и возникают подобные ключевые слова, например:
List<? extends Number> nums...List<? super Number> objs...
Принцип PECS сводится к следующему:
  • Если в конкретном методе из вашей коллекции будут только извлекаться элементы, то такая коллекция является продьюсером (producer -- производитель) и обобщение в <> должно содержать ключевое слово extends (<? extends T>, например).
  • Если в конкретном методе в вашу коллекцию будут только помещать элементы, то такая коллекция является консьюмером (consumer -- потребитель) и обобщение в <> должно содержать ключевое слово super (<? super T>, например).
  • Если предполагается, что в коллекцию элементы будут и добавляться, и извлекаться, то обобщение в <> не должно содержать, ни extends, ни super.

Коллекции-продьюсеры


Рассмотрим такой метод:public static void numListProcessor(List<? extends Number> nums) { Number num = nums.get(0); System.out.println(num.doubleValue());}
Допустим, что в него всегда передают непустую коллекцию. Внутри метода из коллекции извлекается первый элемент, он приводится к типу Number. Затем на нём вызывается метод doubleValue() результат которого выводится на консоль.
О чём компилятору говорит такое объявление параметра: List<? extends Number>?
  • Во-первых, что в метод будут передаваться списки параметризованные наследниками класса Number. То есть этот метод способен в качестве параметра принять List<Integer>, List<Double>, List<Long> и т.д. Совершенно очевидно, что в объявленный таким образом список нельзя добавлять элементы (и компилятор этого не позволит), так как мы не знаем, список которого из наследников передан.
  • Во-вторых, если List<? extends Number> это список какого-то из наследников Number, то элемент списка можно привести к Number. А значит его можно как минимум извлечь. Ну а дальше делать с ним то, что можно делать с любым Number'ом. Не так уж мало, на самом деле.

Подобные коллекции по-английски называются producer collection, коллекция-производитель, или просто продьюсер. Принцип PECS рекомендует добровольно наложить на коллекцию ограничение на добавление элементов с помощью слова extends, если вы знаете, что в том или ином методе ни при каких обстоятельствах не предусматривается модификация коллекции путём добавления в неё элементов.

Коллекции-консьюмеры


Рассмотрим такой метод:public static void numListProcessor(List<? super Integer> nums) { Object unknown = nums.get(0); nums.add(0);}
В данном методе из коллекции читается элемент, но он может быть приведён только к Object. За то здесь в коллекцию добавляется Integer. Более того, компилятор следит, чтобы коллекцию добавлялись только Integer'ы.
О чём компилятору говорит такое объявление параметра: List<? super Number>?
  • В этот метод можно передать коллекции параметризованные классами от Integer до Object. А это всего три варианта: List<Integer>, List<Number>, List<Object>.
  • Единственный способ что-то прочитать из этой коллекции -- это привести результат метода get к Object. Что и происходит в первой строке. При этом неизвестно, что это за объект. Там вполне может лежать new Object(). Поэтому реальное чтение из этой коллекции не предполагается.
  • А вот записать в него можно только Integer. Ведь, передай нам хоть List<Integer>, хоть List<Number>, хоть вообще List<Object>, только Integer в такую коллекцию можно поместить не нарушив безопасности типов.

Подобные коллекции по-английски называются consumer collection, коллекция-потребитель, или просто консьюмер. Принцип PECS рекомендует добровольно наложить на коллекцию ограничение на извлечение элементов (можно извлечь только Object, реально не зная, что там за объект на самом деле) с помощью слова super, если вы считаете, что извлечение элементов не предполагается, но вам нужно, чтобы компилятор продолжал следить, что в коллекцию добавляются данные определённого типа.