Дженерики в Java – [Примеры кода]

Сегодня мы поговорим о Дженериках (Generics) в языке Java. Иногда их называют обобщениями. В статье я буду использовать все эти употребления, но означают они одно и тоже.

Java – строго типизированный язык программирования. Поэтому если мы задаем для переменной какой-то тип – то должны всегда присваивать этой переменной значение строгого типа.

Иногда есть необходимость не указывать тип переменной. Зачастую есть ситуации когда разработчики не могут даже точно знать тип переменной. Вот Вам реальный пример. В Java есть классы коллекций. Представьте что Вы являетесь разработчиком этих классов. Вы же не можете знать какой тип объекта будут использовать люди при работе с Вашим классом.

ArrayList как пример работы с обобщенными типами

Можно сделать по хитрому. Просто объявить тип переменной как Object. Ведь мы знаем что в Java все классы – это наследники Object. Код будет работать.

package com.javamaster.generics;

public class CollectionExample {
	
	private Object[] collectionElements;
	
	public CollectionExample() {
		collectionElements = new Object[10];
	}
	
	public void add(Object object, int position) {
		collectionElements[position] = object; 
	}
	
	public Object get(int index) {
		return collectionElements[index];
	}
	
	public void printElements() {
		for(int i=0; i<collectionElements.length; i++) {
			if(collectionElements[i]!= null)
			System.out.println(collectionElements[i]);
		}
	}
	
	public static void main(String[] args) {
		CollectionExample c = new CollectionExample();
		c.add("Hi there", 0);
		c.add(1, 1);
		c.add(c, 2);
		c.printElements();
		System.out.println(c.get(0));
		int element = (int) c.get(0);
	}
	
}

НО! Такой подход – очень не безопасный. Ведь при попытке получить объект из коллекции можно получить ошибку ClassCastException.

Вот что выводит код выше:

Все работает хорошо пока мы не вызвали элемент и не присвоили его значение неверному типу. Именно поэтому строго типизированные языки программирования считаются более безопасными. Мы знаем заранее тип переменной. И исходя из этого знания можем соответственно оперировать переменной.

Так как нам быть с коллекцией выше? Нам нужно чтобы она принимала любой тип объекта и при этом избежать ClassCastException.

package com.javamaster.generics;

public class CollectionExample<T> {
	
	private T [] collectionElements;
	
	public CollectionExample() {
		collectionElements = (T[]) new Object[10];
	}
	
	public void add(T object, int position) {
		collectionElements[position] = object; 
	}
	
	public T get(int index) {
		return collectionElements[index];
	}
	
	public void printElements() {
		for(int i=0; i<collectionElements.length; i++) {
			if(collectionElements[i]!= null)
			System.out.println(collectionElements[i]);
		}
	}
	
	public static void main(String[] args) {
		CollectionExample<String> c = new CollectionExample();
		c.add("Hi there", 0);
	//	c.add(1, 1);
	//	c.add(c, 2);
		c.printElements();
		System.out.println(c.get(0));
		//int element = (int) c.get(0);
	}
	
}

Дженерики как раз и созданы для ситуации выше. С помощью универсального параметра мы можем указать, что наш класс или метод будет работать с любым типом. Только этот тип нужно передать на этапе создания объекта. В примере выше мы создаем CollectionExample с типом String. Теперь код

//	c.add(1, 1);
//	c.add(c, 2);
//int element = (int) c.get(0);

больше не будет компилироваться. Ведь коллекция ожидает от нас только переменных с типом String. Мы вроде как позволяем нашему классу работать с любыми объектами. И в тот же момент защищаем его от неправильного использования.

С дженериками можно пойти еще дальше. Можно указать что обобщенный тип может быть не таким уж и обобщенным.

На картинке выше ми добавили <T extends CollectionElement>. Теперь наш класс будет принимать только CollectionElement тип или его наследников. Как видно на строке 27: компилятор не позволяет нам задать тип String. Также нам нужно изменить строку 8 на (T[]) new CollectionElement[10]. Ведь теперь у нас универсальный тип T наследуется от CollectionElement класса.

При использовании дженериков можно передавать сразу несколько обобщенных типов:

package com.javamaster.generics;

public class CollectionExample<T extends CollectionElement, S> {
	
	private T [] collectionElements;
	private S someOtherValue;
	
	public CollectionExample() {
		collectionElements = (T[]) new CollectionElement[10];
	}
	
	public CollectionExample(S value) {
		someOtherValue = value;
	}
	
	public void add(T object, int position) {
		collectionElements[position] = object; 
	}
	
	public T get(int index) {
		return collectionElements[index];
	}
	
	public S getSomeOtherValue() {
		return someOtherValue;
	}
	
	public void printElements() {
		for(int i=0; i<collectionElements.length; i++) {
			if(collectionElements[i]!= null)
			System.out.println(collectionElements[i]);
		}
	}
	
	public static void main(String[] args) {
		CollectionExample<CollectionElementChild, String> c = new CollectionExample();
		c.add(new CollectionElementChild(), 0);
		
		CollectionExample<CollectionElement, String> c1 = new CollectionExample("Hi there");
		System.out.println(c1.getSomeOtherValue());
	}
	
}

Также как и классы, обобщенными могут быть интерфейсы и методы. Принцип работы такой же самый. Вот пример кода интерфейса:

package com.javamaster.generics;

public interface GenericInterface<T> {
	
	T returnParametrizedValue();

}
package com.javamaster.generics;

public class GenericInterfaceImpl implements GenericInterface<Integer> {
		
	@Override
	public Integer returnParametrizedValue() {
		return 8;
	}

	public static void main(String[] args) {
		GenericInterface genericInterface = new GenericInterfaceImpl();
		System.out.println(genericInterface.returnParametrizedValue());;
	}

}

Еще пример с методом, который использует универсальный параметр:

package com.javamaster.generics;

import java.util.Arrays;

public class ParametrizedMethodExample {

	public <T> void workWithParametrizedValues(T[] values) {
		for (int i = 0; i < values.length; i++) {
			System.out.println(values[i]);
		}
	}

	public static void main(String[] args) {
		ParametrizedMethodExample parametrizedMethodExample = new ParametrizedMethodExample();
		Integer[] numbers = {1, 3, 6, 1, 4};
		String[] letters = {"A", "Abc", "Cdb"};
		parametrizedMethodExample.<Integer>workWithParametrizedValues(numbers);
		parametrizedMethodExample.<String>workWithParametrizedValues(letters);
	}

}

Как видно из примеров выше, дженерики – штука очень мощная. В умелых руках конечно))

Это все, что я хотел сказать об обобщенных типах в языке Java. Делайте примеры, учите теорию и дженерики станут Вам отличным инструментом в разработке.

Leave a Comment

Your email address will not be published.