Рубрики
Без рубрики

Пример учебника по Java – Обобщениям – Универсальный Метод, Класс, Интерфейс

Пример учебника по дженерикам Java – Дженерики на Java. Общий метод Java, Класс, Интерфейс, Тип, Функция, Пример списка, Шаблон Java, T в java

Автор оригинала: Pankaj Kumar.

Java Generics является одной из наиболее важных функций, представленных в Java 5.

Если вы работали над коллекциями Java и с версией 5 или выше, я уверен, что вы ее использовали.

Генерики в Java с классами коллекций очень просты, но они предоставляют гораздо больше возможностей, чем просто создание типа коллекции.

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

  1. Дженерики на Java
  2. Универсальный класс Java
  3. Универсальный интерфейс Java
  4. Универсальный тип Java
  5. Общий метод Java
  6. Параметры Ограниченного Типа Дженериков Java
  7. Универсальные средства Java и наследование
  8. Общие классы и подтипы Java
  9. Подстановочные знаки Java-дженериков
    1. Подстановочный знак Java-Дженериков с Верхней Границей
    2. Универсальные шаблоны Java Неограниченный подстановочный знак
    3. Java-дженерики с нижним ограниченным подстановочным знаком
  10. Подтипирование с использованием подстановочного знака Дженериков
  11. Стирание типов дженериков Java
  12. Часто задаваемые вопросы по дженерикам

1. Дженерики на Java

Универсальные средства были добавлены в Java 5 для обеспечения проверки типов во время компиляции и устранения риска ClassCastException , который был распространен при работе с классами коллекций. Вся структура коллекции была переписана для использования дженериков для обеспечения безопасности типов. Давайте посмотрим, как универсальные средства помогают нам безопасно использовать классы коллекций.

List list = new ArrayList();
list.add("abc");
list.add(new Integer(5)); //OK

for(Object obj : list){
	//type casting leading to ClassCastException at runtime
    String str=(String) obj; 
}

Приведенный выше код компилируется нормально, но вызывает исключение ClassCastException во время выполнения, потому что мы пытаемся преобразовать объект в списке в строку, в то время как один из элементов имеет тип Integer. После Java 5 мы используем классы коллекций, как показано ниже.

List list1 = new ArrayList(); // java 7 ? List list1 = new ArrayList<>(); 
list1.add("abc");
//list1.add(new Integer(5)); //compiler error

for(String str : list1){
     //no type casting needed, avoids ClassCastException
}

Обратите внимание, что во время создания списка мы указали, что тип элементов в списке будет строковым. Поэтому, если мы попытаемся добавить любой другой тип объекта в список, программа выдаст ошибку во время компиляции. Также обратите внимание, что в цикле for нам не нужна типизация элемента в списке, поэтому во время выполнения мы удаляем исключение ClassCastException.

2. Универсальный класс Java

Мы можем определить наши собственные классы с помощью универсального типа. Универсальный тип-это класс или интерфейс, который параметризуется по типам. Мы используем угловые скобки (<>) для указания параметра типа.

Чтобы понять выгоду, давайте предположим, что у нас есть простой класс, такой как:

package com.journaldev.generics;

public class GenericsTypeOld {

	private Object t;

	public Object get() {
		return t;
	}

	public void set(Object t) {
		this.t = t;
	}

        public static void main(String args[]){
		GenericsTypeOld type = new GenericsTypeOld();
		type.set("Pankaj"); 
		String str = (String) type.get(); //type casting, error prone and can cause ClassCastException
	}
}

Обратите внимание, что при использовании этого класса мы должны использовать приведение типов, и это может привести к исключению ClassCastException во время выполнения. Теперь мы будем использовать универсальный класс java для перезаписи того же класса, как показано ниже.

package com.journaldev.generics;

public class GenericsType {

	private T t;
	
	public T get(){
		return this.t;
	}
	
	public void set(T t1){
		this.t=t1;
	}
	
	public static void main(String args[]){
		GenericsType type = new GenericsType<>();
		type.set("Pankaj"); //valid
		
		GenericsType type1 = new GenericsType(); //raw type
		type1.set("Pankaj"); //valid
		type1.set(10); //valid and autoboxing support
	}
}

Обратите внимание на использование класса универсального типа в основном методе. Нам не нужно выполнять приведение типов, и мы можем удалить исключение ClassCastException во время выполнения. Если мы не предоставим тип во время создания, компилятор выдаст предупреждение о том, что “Тип Generics является необработанным типом.

Ссылки на универсальный тип Универсальный тип должны быть параметризованы”. Когда мы не указываем тип, тип становится Object , и, следовательно, он допускает как строковые, так и целочисленные объекты. Но мы всегда должны стараться избегать этого, потому что нам придется использовать приведение типов при работе с необработанным типом, что может привести к ошибкам во время выполнения.

Также обратите внимание, что он поддерживает автобокс java .

3. Универсальный интерфейс Java

Сопоставимый интерфейс является отличным примером универсальных интерфейсов, и он написан как:

package java.lang;
import java.util.*;

public interface Comparable {
    public int compareTo(T o);
}

Аналогичным образом мы можем создавать универсальные интерфейсы на java. Мы также можем иметь несколько параметров типа, как в интерфейсе карты. Опять же, мы также можем предоставить параметризованное значение параметризованному типу, например new HashMap<Строка, список<Строка>>(); допустимо.

4. Универсальный тип Java

Соглашение об именовании общих типов Java помогает нам легко понимать код, и наличие соглашения об именовании является одной из лучших практик языка программирования Java. Таким образом, дженерики также имеют свои собственные соглашения об именовании. Обычно имена параметров типа состоят из отдельных прописных букв, чтобы их можно было легко отличить от переменных java. Наиболее часто используемыми именами параметров типа являются:

  • E – элемент (широко используется платформой коллекций Java, например ArrayList, Set и т.д.)
  • K – клавиша (используется на карте)
  • N – Число
  • Т – Образный
  • V – Значение (используется на карте)
  • S,U,V и т.д. – 2-й, 3-й, 4-й типы

5. Общий метод Java

Иногда мы не хотим, чтобы весь класс был параметризован, в этом случае мы можем создать универсальный метод java. Поскольку конструктор-это особый вид метода, мы также можем использовать универсальный тип в конструкторах.

Вот класс, показывающий пример универсального метода java.

package com.journaldev.generics;

public class GenericsMethods {

	//Java Generic Method
	public static  boolean isEqual(GenericsType g1, GenericsType g2){
		return g1.get().equals(g2.get());
	}
	
	public static void main(String args[]){
		GenericsType g1 = new GenericsType<>();
		g1.set("Pankaj");
		
		GenericsType g2 = new GenericsType<>();
		g2.set("Pankaj");
		
		boolean isEqual = GenericsMethods.isEqual(g1, g2);
		//above statement can be written simply as
		isEqual = GenericsMethods.isEqual(g1, g2);
		//This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets.
		//Compiler will infer the type that is needed
	}
}

Обратите внимание на сигнатуру isEqual метода, показывающую синтаксис для использования универсального типа в методах. Кроме того, обратите внимание, как использовать эти методы в нашей программе java. Мы можем указать тип при вызове этих методов, или мы можем вызвать их как обычный метод. Компилятор Java достаточно умен, чтобы определить тип используемой переменной, это средство называется вывод типа .

6. Параметры Ограниченного Типа Дженериков Java

Предположим, мы хотим ограничить тип объектов, которые могут использоваться в параметризованном типе, например, в методе, который сравнивает два объекта, и мы хотим убедиться, что принятые объекты сопоставимы. Чтобы объявить параметр ограниченного типа, перечислите имя параметра типа, за которым следует ключевое слово extends, а затем его верхнюю границу, аналогично приведенному ниже методу.

public static > int compare(T t1, T t2){
		return t1.compareTo(t2);
	}

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

Параметры ограниченного типа могут использоваться как с методами, так и с классами и интерфейсами.

Дженерики Java также поддерживают несколько границ, т. Е. расширяет A & B & C>. В этом случае A может быть интерфейсом или классом. Если A-это класс, то B и C должны быть интерфейсом. У нас не может быть более одного класса в нескольких границах. расширяет A & B & C>. В этом случае A может быть интерфейсом или классом. Если A-это класс, то B и C должны быть интерфейсом. У нас не может быть более одного класса в нескольких границах.

7. Универсальные средства Java и наследование

Мы знаем, что наследование Java позволяет нам присваивать переменную A другой переменной B, если A является подклассом B. Таким образом, мы могли бы подумать, что любой универсальный тип A может быть отнесен к универсальному типу B, но это не так. Давайте посмотрим на это с помощью простой программы.

package com.journaldev.generics;

public class GenericsInheritance {

	public static void main(String[] args) {
		String str = "abc";
		Object obj = new Object();
		obj=str; // works because String is-a Object, inheritance in java
		
		MyClass myClass1 = new MyClass();
		MyClass myClass2 = new MyClass();
		//myClass2=myClass1; // compilation error since MyClass is not a MyClass
		obj = myClass1; // MyClass parent is Object
	}
	
	public static class MyClass{}

}

Нам не разрешается назначать переменную MyClass переменной MyClass, потому что они не связаны, на самом деле родителем MyClass является объект.

8. Общие классы и подтипы Java

Мы можем создать подтип универсального класса или интерфейса, расширив или реализовав его. Взаимосвязь между параметрами типа одного класса или интерфейса и параметрами типа другого определяется предложениями extends и implements.

Например, ArrayList реализует список, который расширяет коллекцию, поэтому ArrayList<Строка> является подтипом Списка<Строка>, а Список<Строка> является подтипом Коллекции<Строка>.

Связь подтипов сохраняется до тех пор, пока мы не изменим аргумент типа, ниже показан пример нескольких параметров типа.

interface MyList extends List{
}

Подтипами списка<Строка> могут быть myList<Строка,Объект>, myList<Строка,Целое число> и так далее.

9. Подстановочные Знаки Java-Дженериков

Вопросительный знак (?) является подстановочным знаком в общих выражениях и представляет неизвестный тип. Подстановочный знак может использоваться как тип параметра, поля или локальной переменной, а иногда и как тип возвращаемого значения. Мы не можем использовать подстановочные знаки при вызове универсального метода или создании экземпляра универсального класса. В следующих разделах мы узнаем о подстановочных знаках с верхними ограничениями, подстановочных знаках с нижними ограничениями и захвате подстановочных знаков.

9.1) Подстановочный знак Java-Дженериков с Верхней Границей

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

public static double sum(List list){
		double sum = 0;
		for(Number n : list){
			sum += n.doubleValue();
		}
		return sum;
	}

Теперь проблема с вышеуказанной реализацией заключается в том, что она не будет работать со списком целых чисел или двойников, потому что мы знаем, что список<Целое число> и список<Двойной> не связаны, это когда полезно использовать подстановочный знак с верхней границей. Мы используем универсальный подстановочный знак с ключевым словом extends и классом или интерфейсом верхняя граница , который позволит нам передавать аргумент верхней границы или типы подклассов.

Вышеуказанная реализация может быть изменена, как показано ниже.

package com.journaldev.generics;

import java.util.ArrayList;
import java.util.List;

public class GenericsWildcards {

	public static void main(String[] args) {
		List ints = new ArrayList<>();
		ints.add(3); ints.add(5); ints.add(10);
		double sum = sum(ints);
		System.out.println("Sum of ints="+sum);
	}

	public static double sum(List list){
		double sum = 0;
		for(Number n : list){
			sum += n.doubleValue();
		}
		return sum;
	}
}

Это похоже на написание нашего кода с точки зрения интерфейса, в приведенном выше методе мы можем использовать все методы верхнего класса. Обратите внимание, что с верхним ограниченным списком нам не разрешается добавлять в список какие-либо объекты, кроме null. Если мы попытаемся добавить элемент в список внутри метода sum, программа не будет компилироваться.

9.2) Неограниченный Подстановочный Знак Java-Дженериков

Иногда у нас возникает ситуация, когда мы хотим, чтобы наш универсальный метод работал со всеми типами, в этом случае можно использовать неограниченный подстановочный знак. Это то же самое, что использовать расширяет объект>. расширяет объект>.

public static void printData(List list){
		for(Object obj : list){
			System.out.print(obj + "::");
		}
	}

Мы можем предоставить методу PrintData аргумент списка объектов<Строка> или Список<Целое число> или любой другой тип аргумента списка объектов. Подобно списку с верхней границей, нам не разрешается ничего добавлять в список.

9.3) Java-дженерики с Нижним ограниченным подстановочным знаком

Предположим, мы хотим добавить целые числа в список целых чисел в методе, мы можем сохранить тип аргумента как List<Целое число>, но он будет связан с целыми числами, тогда как List<Число> и List<Объект> также могут содержать целые числа, поэтому мы можем использовать подстановочный знак с нижней границей для достижения этой цели. Для этого мы используем универсальный подстановочный знак (?) с ключевым словом super и классом с нижней границей.

Мы можем передать нижнюю границу или любой супертип нижней границы в качестве аргумента, в этом случае компилятор java позволяет добавлять типы объектов с нижней границей в список.

public static void addIntegers(List list){
		list.add(new Integer(50));
	}

10. Подтипирование с использованием подстановочного знака Дженериков

List intList = new ArrayList<>();
List  numList = intList;  // OK. List is a subtype of List

11. Стирание Типов Дженериков Java

Обобщения в Java были добавлены для обеспечения проверки типов во время компиляции, и они бесполезны во время выполнения, поэтому компилятор java использует функцию стирание типов для удаления всего кода проверки типов обобщений в байтовом коде и при необходимости вставляет приведение типов. Стирание типов гарантирует, что для параметризованных типов не будут созданы новые классы; следовательно, универсальные типы не требуют дополнительных затрат времени выполнения.

Например, если у нас есть универсальный класс, как показано ниже;

public class Test> {

    private T data;
    private Test next;

    public Test(T d, Test n) {
        this.data = d;
        this.next = n;
    }

    public T getData() { return this.data; }
}

Компилятор Java заменяет параметр ограниченного типа T первым связанным интерфейсом, сопоставимым, как показано ниже кода:

public class Test {

    private Comparable data;
    private Test next;

    public Node(Comparable d, Test n) {
        this.data = d;
        this.next = n;
    }

    public Comparable getData() { return data; }
}

12. Часто задаваемые вопросы по дженерикам

12.1) Почему мы используем дженерики в Java?

Универсальные средства обеспечивают надежную проверку типов во время компиляции и снижают риск исключения ClassCastException и явного приведения объектов.

12.2) Что такое Дженерик?

Мы используем для создания универсального класса, интерфейса и метода. T заменяется фактическим типом, когда мы его используем.

12.3) Как дженерики работают в Java?

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

13. Дженерики в Java – Дальнейшие чтения

  • Дженерики не поддерживают подтип, поэтому Список<Число> ArrayList<Целое число>(); не будет компилироваться, узнайте почему дженерики не поддерживают подтип .
  • Мы не можем создать универсальный массив, поэтому List<Целое число>[] ArrayList<Целое число>[10] не будет компилироваться, читайте почему мы не можем создать универсальный массив? .

Это все для дженериков в java , дженерики java-действительно обширная тема, и для ее понимания и эффективного использования требуется много времени. Этот пост здесь представляет собой попытку предоставить основные сведения о дженериках и о том, как мы можем использовать их для расширения нашей программы с учетом безопасности типов.