Важная цель Java-дженериков: не позволяйте кошке стоять в собачьей упряжке.

Java

Четвертое издание «Java Programming Thinking» занимает 75 страниц, чтобы описатьДженерики——От толстой кучи контента легко закружится голова — но на самом деле столько и не нужно, достаточно одного предложения: я — родовая очередь, собаки могут стоять, кошки тоже могут стоять, но это лучше не с кошкой и с собакой!

01. Что такое дженерики

Дженерики, некоторые люди разбивают это слово на «параметризованные типы». Этот вид разборки на самом деле не так просто понять, давайте разберемся с ним в соответствии со значением Silent Wang Er.

Теперь есть стакан, вы можете дать ему подержать чашку с кипяченой водой, или вы можете подержать чашку Erguotou - это концепция дженериков,при изготовлении этой чашкиНет необходимости определять смерть в руководстве, указывая, что она может держать только кипяченую воду, а не Эрготоу!

В инструкции по эксплуатации можно указать, что он используется для хранения жидкостей, но лучше этого не делать, потому что пользователь хочет использовать его для хранения нескольких леденцов!

С учетом сказанного, не кажется ли вам это менее абстрактным? На самом деле дженерики означают, что при определении класса, интерфейса и метода конкретный тип указывается без ограничений, а вызывающая сторона класса, интерфейса и метода решает, какой тип параметров использовать.

Например, производитель стакана сказал: «Я не знаю, что пользователь делает с этим стаканом, поэтому я несу ответственность только за изготовление такого стакана; пользователь стакана сказал: «Правильно, я решу это». Только стакан наполняется кипяченой водой или Erguotou, или каменным сахаром.

02. Когда использовать дженерики

Давайте посмотрим на короткий фрагмент кода:

public class Cmower {

	class Dog {
	}

	class Cat {
	}

	public static void main(String[] args) {
		Cmower cmower = new Cmower();
		Map map = new HashMap();
		map.put("dog", cmower.new Dog());
		map.put("cat", cmower.new Cat());

		Cat cat = (Cat) map.get("dog");
		System.out.println(cat);
	}

}

Смысл этого кода таков: Ставим на карту собаку (Dog) и кошку (Cat).Когда мы хотим убрать кота с карты, мы случайно удаляем собаку. .

Этот код компилируется без проблем, но будет сообщать при запускеClassCastException(Собаки все-таки не кошки):

Exception in thread "main" java.lang.ClassCastException: com.cmower.java_demo.sixteen.Cmower$Dog cannot be cast to com.cmower.java_demo.sixteen.Cmower$Cat
	at com.cmower.java_demo.sixteen.Cmower.main(Cmower.java:20)

Почему это так?

1) Программист, который пишет код, небрежен. Чтобы убрать кота с карты, нельзя достать собаку!

2) При создании карты тип, который должен быть размещен на карте, не был явно указан. Если указано ставить кошек, это должна быть кошка, когда вы ее берете, и собака не будет вынесена; если обозначено поставить собаку, это также причина.

The first case not a good solution, programmers can not beat the meal (I do not want to do a daily back-pot programmer, heavy good); second situation is relatively easy to solve, because the support of the Pan Map type (generic интерфейс).

public interface Map<K,V> {
}

Примечание. В Java параметры в форме T, E, K, V и т. д. часто используются для представления общих параметров.

T: представляет любой класс в целом. E: Представляет значение Элемента или значение Исключения. K: означает ключ. V: обозначает значение, обычно используется вместе с K.

Поскольку Map поддерживает дженерики, HashMap (универсальный класс), реализация Map, также поддерживает дженерики.

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
}

Метод put (общий метод) определяется следующим образом:

public V put(K key, V value) {
  return putVal(hash(key), key, value, false, true);
}

Что ж, теперь используйте общую форму для определения карты, которая может содержать только Cat!

public class Cmower {

	class Dog {
	}

	class Cat {
	}

	public static void main(String[] args) {
		Cmower cmower = new Cmower();
		Map<String, Cat> map = new HashMap<>();
//		map.put("dog", cmower.new Dog()); // 不再允许添加
		map.put("cat", cmower.new Cat());

		Cat cat = map.get("cat");
		System.out.println(cat);
	}
}

При использовании дженериков для определения карты (ключ имеет тип String, а значение — тип Cat):

1) Компилятор больше не позволяет добавлять на карту объекты собак.

2) При удалении кота с карты больше не нужно форсировать перестройку.

03. Введите стирание

Некоторые люди говорят, что дженерики Java просто поверхностны — универсальная информация существует на этапе компиляции (собачьей команде не разрешается стоять на кошках во время компиляции) и исчезает на этапе выполнения (во время выполнения в очереди нет информации о кошках, даже нет информации о собаке) - это явление называется "стиранием типа".

Приходите, посмотрите на код, чтобы объяснить:

public class Cmower {

	class Dog {
	}

	class Cat {
	}

	public static void main(String[] args) {
		Cmower cmower = new Cmower();
		Map<String, Cat> map = new HashMap<>();
		Map<String, Dog> map1 = new HashMap<>();
		
		// The method put(String, Cmower.Cat) in the type Map<String,Cmower.Cat> is not applicable for the arguments (String, Cmower.Dog)
		//map.put("dog",cmower.new Dog());
		
		System.out.println(map.getClass());
		// 输出:class java.util.HashMap
		System.out.println(map1.getClass());
		// 输出:class java.util.HashMap
	}

}

Ключ карты - Кошка, поэтому ставить Собаку нельзя, иначе компилятор напомнитThe method put(String, Cmower.Cat) in the type Map<String,Cmower.Cat> is not applicable for the arguments (String, Cmower.Dog). Компилятор работает хорошо и заслуживает похвалы.

Но тут возникает проблема, Class тип карты HashMap, а Class тип map1 тоже HashMap - то есть Java-код не знает, что ключ map ставится на Cat, а ключ map1 находится на ключе карты 1. Поставьте Собаку.

Итак, попробуйте подумать что-нибудь ужасное: поскольку общая информация во время выполнения стирается, а механизм отражения определяет информацию о типе во время выполнения, то, используя механизм отражения, можно ли использовать механизм отражения на Карте, ключ которой — Cat? Как насчет Собаки?

Давайте попробуем:

public class Cmower {

	class Dog {
	}

	class Cat {
	}

	public static void main(String[] args) {
		Cmower cmower = new Cmower();
		Map<String, Cat> map = new HashMap<>();
		
		try {
			Method method = map.getClass().getDeclaredMethod("put",Object.class, Object.class);
			
			method.invoke(map,"dog", cmower.new Dog());
			
			System.out.println(map);
			// {dog=com.cmower.java_demo.sixteen.Cmower$Dog@55f96302}
		} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
	}

}

вы видели? Мы даже поместили Собаку на Карту с ключом Кошки!

Примечание. Разработчики Java представили дженерики только в JDK 1.5, но чтобы позаботиться о недостатках предыдущего дизайна и в то же время обеспечить совместимость с неуниверсальным кодом, им пришлось пойти на компромиссную стратегию: время компиляции Требования к дженерикам Строгие, но среда выполнения стирает дженерики - очень сложно быть совместимым с предыдущими версиями, а также обновлять и расширять новые функции!

04. Общие подстановочные знаки

Иногда вы увидите такой код:

List<? extends Number> list = new ArrayList<>();
List<? super Number> list = new ArrayList<>();

?и ключевые словаextendsилиsuperВместе это на самом деле расширенное применение дженериков: подстановочные знаки.

Давайте определим общий класс — PetHouse (дом для домашних животных), у которого есть некоторые базовые действия (вы можете жить в питомце и выпускать его):

public class PetHouse<T> {
	private List<T> list;

	public PetHouse() {
	}

	public void add(T item) {
		list.add(item);
	}

	public T get() {
		return list.get(0);
	}
}

Если мы хотим жить в домашнем животном, мы можем определить коттедж следующим образом (его общий тип — Домашнее животное):

PetHouse<Pet> petHouse = new PetHouse<>();

Затем мы позволяем котятам и щенкам жить в:

petHouse.add(new Cat());
petHouse.add(new Dog());

Если мы только хотят жить только в котенке, мы планируем определить, как это:

PetHouse<Pet> petHouse = new PetHouse<Cat>();

Но на самом деле компилятор не позволяет нам это определение: поскольку дженерики не поддерживаются напрямую переход. Что мы можем сделать по этому поводу?

Хижину можно определить так:

PetHouse<? extends Pet> petHouse = new PetHouse<Cat>();

То есть в питомнике может жить котенок, но это должен быть домашний питомец (Pet или подкласс Pet), а не дикий кот.

Но, к сожалению, в этой хижине не могут жить котята, см. рисунок ниже.

Это связано с тем, что Java поддерживает преобразование типов дженериков (используяextendsподстановочный знак), но мы не можем ничего добавить к нему — компилятор не знает, будет ли домик для домашних животных котенком, щенком или любым другим домашним животным, поэтому он просто ничего не разрешает.

Увидев это, вы, должно быть, сильно растеряетесь, так какPetHouse<? extends Pet>Определенная кабина для домашних животных ничего не позволяет, так зачем определять ее таким образом? подумайте некоторое время.

05. Резюме общего читателя

Общий квалификатор имеет описание: верхняя граница не существует, а нижняя граница не принимается.

Причина, по которой верхняя граница не существует: например, в List компилятор знает только, что контейнер содержит Отца и его подклассы, но не знает конкретного типа.Когда компилятор видит класс Отца за расширениями, он просто отмечает аCAP#1В качестве заполнителя, что бы в него ни вставляли, компилятор не знает, можно ли его комбинировать сCAP#1совпадают, поэтому вставка не допускается.

Роль расширений: вы можете сохранить значение во время инициализации, и это может обеспечить стабильность данных.К нему можно только получить доступ, но не сохранить. Прочитанные данные могут быть сохранены в родительском классе или базовом классе.

Причина, по которой нижняя граница не берется: нижняя граница ограничивает минимальную степень детализации элемента, что фактически ослабляет контроль типа элемента-контейнера. Например, список, элементом которого является базовый класс класса «Отец», может хранить класс «Отец» и его подклассы. Но компилятор не знает, какой класс является суперклассом для отца, например, для человека. При чтении, естественно, вы не знаете, какой это тип, вы можете вернуть только Object, поэтому вся информация об элементе теряется.

Роль super: используется для квалификации типа параметра.

Принципы ПЕКС:

1. Если вы часто читаете контент, целесообразно использовать расширения. 2. В него часто вставляется, подходит для супер


Предыдущий:HashMap, сложность не в Map, а в Hash

Следующий:Обработка исключений Java: страховка для программы

Поиск в WeChat »Тихий король 2"Общественный номер, подпишитесь и ответьте"бесплатное видео"Получите 500 ГБ высококачественных обучающих видео (по категориям).