#java #generics
Вопрос:
Я хочу создать пользовательский компаратор в java, который может принимать различные типы классов и сортировать по определенному методу (который будет одинаковым для всех классов). Эти классы не связаны друг с другом, и наследование не может быть применено к ним.
Например, предположим, что у меня есть 2 класса автомобилей и велосипедов, и у обоих из них есть метод получения, который подсчитывает, сколько было продано этого конкретного транспортного средства. Теперь я хочу отсортировать список конкретного транспортного средства на основе этого параметра.
public class Car {
private int selledUnits;
...
public int getSelledUnits() {
return selledUnits;
}
}
public class Bike {
private int selledUnits;
...
public int getSelledUnits() {
return selledUnits;
}
...
}
Теперь предположим, что у меня есть List<Car> cars
List<Bike> bikes
и я хочу отсортировать эти автомобили по selledUnits
полю.
В настоящее время я сортирую, создавая различные компараторы для другого класса .
Например, для автомобиля я создал ниже компаратор для сортировки
class CarComparator implements Comparator<Car> {
public int compare(Car c1, Car c2) {
return c1.selledUnits - c2.selledUnits;
}
}
Аналогично для сортировки списка велосипедов ниже используется компаратор :
class BikeComparator implements Comparator<Bike> {
public int compare(Bike b1, Bike b2) {
return b1.selledUnits - b2.selledUnits;
}
}
Мой вопрос в том, могу ли я вместо создания отдельного компаратора для каждого из этих классов создать универсальный компаратор, поскольку оба они сортируются на основе одних и тех же полей.
Я попытался создать универсальный компаратор с помощью отражения, и он работает нормально, но хотел создать то же самое без использования отражения.
Комментарии:
1. Вы должны извлечь части оммона в интерфейс. Затем вы можете создать компаратор на основе этого интерфейса.
2. @Seelenvirtuose Я не могу создать общий интерфейс, как указано в выделенной жирным шрифтом части выше в вопросе
3. Например, предположим, что у меня есть 2 класса автомобилей и велосипедов, и у обоих из них есть метод получения, который подсчитывает, сколько из этого конкретного транспортного средства было продано. Неправильное место. Зачем экземпляру транспортного средства знать о других экземплярах такого рода? Только такой класс, как, скажем,
VehicleSeller
мог или должен был это знать.4. Тогда наиболее распространенным типом является
Object
. ДелатьComparator<Object>
. Вы можете проверить, соответствуют ли сравниваемые типы типам, которые вы ожидаете вcompare
методе.5. Я не думаю, что это возможно без размышлений или введения общего интерфейса.
Ответ №1:
Вот пример того, как вы могли бы достичь этого с помощью оболочки:
class NotVehicleException extends RuntimeException {}
class VehicleWrapper{
Object wrappedVehicle;
public VehicleWrapper(Car wrappedVehicle) {
this.wrappedVehicle = wrappedVehicle;
}
public VehicleWrapper(Bike wrappedVehicle) {
this.wrappedVehicle = wrappedVehicle;
}
public int getSelledUnits() {
if(wrappedVehicle instanceof Car)
return ((Car) wrappedVehicle).getSelledUnits();
else if ( wrappedVehicle instanceof Bike)
return ((Bike) wrappedVehicle).getSelledUnits();
else throw new NotVehicleException();
}
}
class VehicleComparator implements Comparator<VehicleWrapper>{
public int compare(VehicleWrapper b1, VehicleWrapper b2) {
return b1.getSelledUnits() - b2.getSelledUnits();
}
}
Конечно, это не даст вам той гибкости и элегантности, которых вы достигли бы, имея общий класс или интерфейс, поэтому каждый раз, когда вы добавляете другой тип транспортного средства, вам придется обновлять оболочку. Но в целом, обертки являются распространенным решением для связывания несвязанного.
Комментарии:
1. Использование структуры if-else-if, вероятно, будет более читабельным, чем вложенные тернарные операторы.
2. полностью согласен, просто набрал это быстро 🙂
3. Когда-нибудь мы сможем использовать выражения переключения здесь: openjdk.java.net/jeps/406
4. Вы могли бы просто заставить компаратор сравнивать объекты, проверять свой класс и вообще не иметь класса обертывания.
Ответ №2:
Java имеет встроенный компаратор для сравнения объектов путем сопоставления с int. Это общее.
List<Bike> bikes;
List<Car> cars;
//populated etc.
bikes.sort( Comparator.comparingInt( item -> item.selledUnits ) );
cars.sort( Comparator.comparingInt( item -> item.selledUnits ) );
Ответ №3:
Вы должны использовать родительский класс. Как этот =>
class Parent{
int sellingUnit;
String name;
}
class Bike extends Parent{
Bike(int a,String n){
sellingUnit = a;
name=n;
}
}
class Car extends Parent{
Car(int a ,String n){
sellingUnit = a;
name=n;
}
}
class MainComparator{
//sort by name
static Comparator<Parent> sortByName() {
return new Comparator<Parent>() {
@Override
public int compare(Parent p1, Parent p2) {
return p1.name.compareTo(p2.name);
}
};
}
//sort by selling unit
static Comparator<Parent> sortBySellingUnit() {
return new Comparator<Parent>() {
@Override
public int compare(Parent p1, Parent p2) {
return p1.sellingUnit-p2.sellingUnit;
}
};
}
}
public class Myy {
public void display(Parent p[], String type) {
for(Parent e:p) {
System.out.println(type " name " e.name " selling unit " e.sellingUnit);
}
}
public static void main(String asd[]) throws Exception
{
MainComparator comparator=new MainComparator();
Myy my=new Myy();
//testing bike
Bike b[]=new Bike[4];
b[0]=new Bike(1,"b1");
b[1]=new Bike(2,"b2");
b[2]=new Bike(4,"b4");
b[3]=new Bike(3,"b3");
System.out.println("before sorting by selling unit");
my.display(b, "bike");
Arrays.sort(b,comparator.sortBySellingUnit());
System.out.println("after sorting");
my.display(b, "bike");
//car testing
Car c[]=new Car[3];
c[0]=new Car(10,"c1");
c[1]=new Car(30,"c3");
c[2]=new Car(20,"c2");
System.out.println("before sorting car by name");
my.display(c,"car");
Arrays.sort(c, comparator.sortByName());
System.out.println("after sorting car by name");
my.display(c, "car");
}
}
Ответ №4:
Без размышлений это невозможно.
Вы можете делать все, что угодно во время выполнения, используя отражение.
Вероятно, вам не следует этого делать, но вы можете сравнивать только совершенно разные объекты (например), принимая во внимание названия полей (вы, конечно, можете учитывать любую другую информацию, например, если один из них является Integer
, а другой Double
выполняет требуемое преобразование и так далее).
Определите аннотацию для доступных полей «сортировки» :
@Retention(RetentionPolicy.RUNTIME)
public @interface ComparableField {
}
Пусть два совершенно независимых класса для сортировки вместе:
@Getter
@Setter
@AllArgsConstructor
@ToString
public class Car {
private int id;
@ComparableField
private String name;
@ComparableField
private int sales;
}
@Getter
@Setter
@AllArgsConstructor
@ToString
public class Bike {
private int id;
@ComparableField
private String name;
@ComparableField
private int stock;
}
обратите внимание, что оба поля являются общими name
, но не полями sales
и. stock
Мы можем заставить сравнивать любое поле, используя странный универсальный компаратор:
private static Comparator<? super Object> genericFieldComparator(String field) {
return (a, b) -> {
Method fa = getMethods(a.getClass()).stream().filter(f -> f.getName().equalsIgnoreCase("get" field)).findFirst().orElse(null);
Method fb = getMethods(b.getClass()).stream().filter(f -> f.getName().equalsIgnoreCase("get" field)).findFirst().orElse(null);
try {
Object va = fa == null ? null : fa.invoke(a);
Object vb = fb == null ? null : fb.invoke(b);
if (va != null)
return unsafeCompare((Comparable<Object>) va, vb);
if (vb != null)
return -unsafeCompare((Comparable<Object>) vb, va);
return 0;
} catch (IllegalAccessException | InvocationTargetException e) {
// cannot compare
throw new RuntimeException("argh!");
}
};
}
private static <B, A extends Comparable<B>> int unsafeCompare(A a, B b) {
return b == null ? -1 : a.compareTo(b);
}
при этом сортировать любой список просто так
private static Stream<Object> sortUsingField(List<Object> objects, String field) {
return objects.stream().sorted(genericFieldComparator(field));
}
например, если мы запустим
List<Object> objects = asList(
new Car(1, "Audi", 300),
new Bike(2, "MX", 30),
new Car(3, "M Benz", 250),
new Bike(4, "BH", 45)
);
// list possible fields to compare in runtime
System.out.printf("Available sorting fields:%n");
List<String> fields = objects.stream().flatMap(o ->
getAllFields(o.getClass(), withAnnotation(ComparableField.class)).stream().map(Field::getName))
.distinct()
.peek(f -> System.out.printf(" - %s%n", f))
.collect(toList());
// sort using every field
for (String field : fields) {
System.out.printf("Sorting using `%s`:%n", field);
sortUsingField(objects, field).forEach(o -> System.out.printf(" - %s%n", o));
}
мы получаем
Available sorting fields:
- name
- sales
- stock
Sorting using `name`:
- Car(id=1, name=Audi, sales=300)
- Bike(id=4, name=BH, stock=45)
- Car(id=3, name=M Benz, sales=250)
- Bike(id=2, name=MX, stock=30)
Sorting using `sales`:
- Car(id=3, name=M Benz, sales=250)
- Car(id=1, name=Audi, sales=300)
- Bike(id=2, name=MX, stock=30)
- Bike(id=4, name=BH, stock=45)
Sorting using `stock`:
- Bike(id=2, name=MX, stock=30)
- Bike(id=4, name=BH, stock=45)
- Car(id=1, name=Audi, sales=300)
- Car(id=3, name=M Benz, sales=250)
как и ожидалось.
Обратите внимание, что нет никакой необходимости в том, чтобы классы имели какое-либо отношение друг к другу и даже поля не сравнивались. Любые расхождения могут быть устранены во время выполнения (хотя это нежелательно).
В частности, классам не обязательно иметь общего предка или интерфейс, и поля разных типов также могут быть разрешены во время выполнения.
Комментарии:
1. в вопросе упоминается, что не следует использовать отражение
2. Без размышлений это невозможно.
Ответ №5:
Использование сопоставления шаблонов Java 17 instanceof
делает его немного аккуратнее. Последняя версия позволяет определять компаратор в одной строке, но вводит в заблуждение NullPointerException
, если в нем есть объект, List
который не относится к одному из ожидаемых типов.
public class ComparatorEg {
static class Car {
private final int sales;
Car(int sales) {this.sales = sales;}
public int getSales() {
return sales;
}
}
static class Bike {
private final int sales;
Bike(int sales) {this.sales = sales;}
public int getSales() {
return sales;
}
}
public static void main(String[] args) {
List<Object> vehicles = new ArrayList<>(List.of(new Car(10), new Bike(9), new Car(8)));
vehicles.sort(Comparator.comparingInt(o -> {
if (o instanceof Bike b) {
return b.getSales();
} else if (o instanceof Car c) {
return c.getSales();
} else {
throw new RuntimeException("unexpected object: " o);
}
}));
List<Object> vehicles2 = new ArrayList<>(List.of(new Car(10), new Bike(9), new Car(8), new Object()));
// this will throw an NPE because of the presence of the Object in the list
vehicles2.sort(Comparator.comparing(o -> (o instanceof Bike b) ? b.getSales() : (o instanceof Car c ? c.getSales() : null)));
}
}