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

Дженерики Java и проблемы, которые они решали.

Краткое описание проблем, которые java generics решает в программировании на Java. С тегами java, generics, collections, ооп.

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

package tutorial;

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

public class GenericsExample {
    public static void main(String[] args) {
        List sales = new ArrayList<>();
        sales.add(2.30);
        sales.add(3.30);
        sales.add(4.30);
        sales.add(5.30);
        sales.add(6.30);

        System.out.println("\nExpected usage average is " + findAverage(sales));

        sales = new ArrayList();
        sales.add(2.30);
        sales.add(3.30);
        sales.add(4.30);
        sales.add(5.30);
        sales.add(6.30);
        sales.add("7.30");

        System.out.println("Buggy use case is " + findAverage(sales));

    }

    private static double findAverage(List sales){
        int sum = 0;
        for(int i = 0; i < sales.size(); i++){
            sum += (double) sales.get(i);
        }

        return (double) sum / sales.size();
    }
}

Приведенный выше код вызовет ClassCastException во время выполнения. Исключение не отмечено и поэтому не может быть перехвачено во время компиляции. Разработчик этой программы явно ожидает двойного типа, что имеет смысл, учитывая, что он/она имеет дело с деньгами. Но нет никакого способа на самом деле гарантировать, что это значение является тем, что на самом деле предоставляется. Чтобы решить эту проблему, нам нужно иметь способ позволить компилятору проверять наличие этих потенциальных ошибок и уведомлять нас об ошибке во время компиляции. Java 1.5 поставляется с дженериками, а в java 7 был введен бриллиант для указания типа объекта, который мы ожидаем в коллекции. Возможно, вы слишком долго использовали дженерики, не зная, что это такое. Дженерики позволяют нам использовать типизированные параметры, указывая тип объекта (включая объекты с автоматической упаковкой), который мы ожидаем в коллекции или даже в классе или методе. Вышеизложенное можно решить, включив класс Double autoboxed в угловую скобку при объявлении списка таким образом.

List sales = new ArrayList<>();

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

Универсальные классы Чтобы продемонстрировать, как классы используют дженерики, давайте создадим класс игрока, который имитирует реальных спортивных игроков

abstract class Player{
    private String name;
    private int jerseyNumber;

    public Player(String name, int jerseyNumber) {
        this.name = name;
        this.jerseyNumber = jerseyNumber;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getJerseyNumber() {
        return jerseyNumber;
    }

    public void setJerseyNumber(int jerseyNumber) {
        this.jerseyNumber = jerseyNumber;
    }
}

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

class FootBallPlayer extends  Player{
    public FootBallPlayer(String name, int jerseyNumber) {
        super(name, jerseyNumber);
    }
}

class BasketBallPlayer extends Player{
    public BasketBallPlayer(String name, int jerseyNumber) {
        super(name, jerseyNumber);
    }
}

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

class Team{
    private String name;
    private int numberOfMatchesPlayed;
    private List players = new ArrayList<>();

    public Team(String name, int numberOfMatchesPlayed) {
        this.name = name;
        this.numberOfMatchesPlayed = numberOfMatchesPlayed;
        this.players = new ArrayList<>();
    }

    public String getName() {
        return name;
    }

    public int getNumberOfMatchesPlayed() {
        return numberOfMatchesPlayed;
    }

    public boolean addPlayer(Player player){
        if(!players.contains(player)){
            this.players.add(player);
            return true;
        }

        return false;
    }
}

Чтобы добавить игроков в новую футбольную команду Арсенал у нас мог бы быть такой код

class GenericClassExample {
    public static void main(String[] args) {
        FootBallPlayer messi = new FootBallPlayer("Lionel Messi", 10);
        FootBallPlayer ronaldo = new FootBallPlayer("Cristiano Ronaldo", 7);

        BasketBallPlayer kawhi = new BasketBallPlayer("Kawhi Leonard", 2);
        BasketBallPlayer james = new BasketBallPlayer("King Lebron James", 23);

        Team arsenal = new Team("Arsenal", 0);
        arsenal.addPlayer(ronaldo);
        arsenal.addPlayer(messi);
        arsenal.addPlayer(kawhi);
    }

}

Код действителен, но что-то явно не так, Кави – баскетболист, и его не следует добавлять в “Арсенал”. это может быть ошибкой со стороны программистов, и, к сожалению, код будет скомпилирован нормально. Оговорка в том, что если бы это была игра. Если кто-то выберет “Арсенал”, он увидит, что Кави играет в баскетбольной майке очень неловко. Поэтому, если бы у нас было средство, которое гарантировало бы, что наш код не будет компилироваться, если мы попытаемся добавить игрока в команду, в которой он не должен быть, у нас было бы меньше ошибок, о которых нужно беспокоиться. Одним из способов предотвращения вышеуказанной ошибки является расширение футбольных и баскетбольных команд из класса team но если бы был лучший способ сделать это, зачем дублировать код? Вводим типизированные параметры T . Используя дженерики, мы бы изменили наш класс Team на этот

class Team{
    private String name;
    private int numberOfMatchesPlayed;
    private List players;

    public Team(String name, int numberOfMatchesPlayed) {
        this.name = name;
        this.numberOfMatchesPlayed = numberOfMatchesPlayed;
        this.players = new ArrayList<>();
    }

    public String getName() {
        return name;
    }

    public int getNumberOfMatchesPlayed() {
        return numberOfMatchesPlayed;
    }

    public boolean addPlayer(T player){
        if(!players.contains(player)){
            this.players.add(player);
            System.out.println("Added " + player.getName() + " to Team " + this.getName());
            return true;
        }

        return false;
    }
}

И затем мы изменили бы инициализацию Team в main на

Team arsenal = new Team("Arsenal", 0);

Что следует отметить

  • Оператор diamond после имени класса.
  • расширяет Player внутри оператора diamond.
  • T в обоих
private List players;
public boolean addPlayer(T player)

Обычно это подразумевает, что

  • мы сообщаем классу, что будем инициализировать его с помощью типа,
  • Тип должен иметь верхнюю границу как класс игрока. ie должен быть подклассом Player (Это называется ограниченные параметры ), и он не может принимать ничего другого.
  • Тип, указанный в классе во время инициализации, также должен использоваться там, где находятся T s. Это позволило бы нам инициализировать один и тот же класс Team более одного раза с более чем одним типом, и весь компилятор будет проверять типы объекта, который мы назначаем или используем с этими объектами типа Team.

это означает, что Кави и Джеймс не могут быть добавлены в команду Арсенал, и если мы инициализируем команду Лейкерс следующим образом

Team arsenal = new Team("LA Lakers", 0);

и иметь возможность добавить Джеймса и Кави в команду. Такой подход делает наш код СУХИМ и позволяет нам избежать потенциальных ошибок.

Побочные примечания

  • Вы также можете указать интерфейс в качестве типа в java generics.
  • Дженерики также могут использоваться в интерфейсах
Interface MyInterface{
    //some code
}
  • К типизированному параметру можно добавить несколько границ, если он соответствует правилу наследования, т.е. Он может расширять только один класс и реализовывать несколько интерфейсов. Синтаксис будет выглядеть примерно так
class Team{
    // some code
}

где Игрок – это класс, а тренер и рейтинг – это интерфейсы. (классы должны быть первыми в порядке, иначе код не сможет скомпилироваться)

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

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

Я с нетерпением жду ваших отзывов. Спасибо

Оригинал: “https://dev.to/mrphayo/java-generis-and-the-problems-it-solved-1i41”