Как поменять местами два элемента в generics

Рейтинг: 4Ответов: 1Опубликовано: 13.02.2023

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


public class Swap<A,B> {   
  private A first;
  private B second;

  public Swap(A first, B second) {
    this.second = second;
    this.first = first;   
  }

  void setFirst (A first)  { this.first = first; }
  void setSecond(B second) { this.second = second; }
  A getFirst()  { return first; }
  B getSecond() { return second;}

  void swap(Pair[]pairs,A first,B second) {
    Pair temp=pairs[first];
    pairs[first]=pairs[second];
    pairs[second]=temp;   
  } 
} 

Я немного запутался с реализацией swap метода, помогите его исправить пожалуйста.


Ответы

▲ 5Принят

Если класс Swap объявлен как обобщённый, то в его экземпляре нельзя переставить объекты произвольных типов A и B.

Однако можно создать новый типизированный экземпляр класса Swap и изменить в нём порядок аргументов:

public class Swap<A, B> {
    private A first;
    private B second;
    public Swap(A first, B second) {
        this.first = first;
        this.second = second;
    }

    public A getFirst() {return first;}
    public B getSecond() {return second;}

    @Override
    public String toString() {
        return "Swap: first(" + first.getClass() + ")=" + first + "; second(" + second.getClass() + ")=" + second;
    }

    // статический метод
    public static<A, B> Swap<B, A> swap(Swap<A, B> item) {
        return new Swap<>(item.second, item.first);
    }
    
    // метод экземпляра
    public Swap<B, A> swap() {
        return new Swap<>(this.second, this.first);
    }
}

Тест:

Swap<Integer, String> foo = new Swap<>(1, "foo");
var bar = Swap.swap(foo);
var baz = bar.swap();

System.out.println("foo: " + foo);
System.out.println("bar: " + bar);
System.out.println("baz: " + baz);
foo: Swap: first(class java.lang.Integer)=1; second(class java.lang.String)=foo
bar: Swap: first(class java.lang.String)=foo; second(class java.lang.Integer)=1
baz: Swap: first(class java.lang.Integer)=1; second(class java.lang.String)=foo

Классическая корректная перестановка допустима только если оба параметра first и second имеют один общий тип A:

class Pair<A> {
    private A first;
    private A second;
// ... конструкторы / геттеры / сеттеры и т.п.

    public void swap() {
        A tmp = first;
        first = second;
        second = tmp;
    }
}

Разумеется, можно написать реализацию swap для "сырого" (нетипизированного) типа (см. недавний похожий вопрос как в List<Integer> добавить объект типа String?), однако, пользоваться таким классом будет затруднительно:

// class Swap
// код рабочий
// предупреждение: Raw use of parameterized class 'Swap'
public static void rawSwap(Swap swap) {
    Object a = swap.first;
    swap.first = swap.second;
    swap.second = a;
}

Например, "переставим" поля в экземпляре baz:

Integer f1 = baz.getFirst();
String s1 = baz.getSecond();
System.out.println("f1: " + f1 + "; s1: " + s1);

Swap.rawSwap(baz); // "сырая" перестановка

Теперь после такого обмена вся информация о типах объектов в экземпляре swap будет потеряна, и например пользоваться геттерами/сеттерами будет невозможно:

  • если обратиться к геттеру по "новому" типу, возникнет ошибка компиляции о несовместимости типов -- компилятор "не в курсе", что тип уже изменился:
// Integer ff = baz.getSecond(); // java: incompatible types: java.lang.String cannot be converted to java.lang.Integer
// String ss = baz.getFirst();   // java: incompatible types: java.lang.Integer cannot be converted to java.lang.String
  • обращение по "старому" типу приведёт к ClassCastException:
System.out.println("swp: " + baz);
Integer f2 = baz.getFirst(); // java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer
String s2 = baz.getSecond();
System.out.println("f2: " + f2 + "; s2: " + s2);

Результат:

f1: 1; s1: foo
swp: Swap: first(class java.lang.String)=foo; second(class java.lang.Integer)=1
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer ...