простой универсальный список в Java

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

Ниже приведен мой собственный код для сравнения массивов и объектов. Код является коротким и простым:

Array.prototype.equals = function(otherArray) {
  if (!otherArray || this.length != otherArray.length) return false;
  return this.reduce(function(equal, item, index) {
    var otherItem = otherArray[index];
    var itemType = typeof item, otherItemType = typeof otherItem;
    if (itemType !== otherItemType) return false;
    return equal && (itemType === "object" ? item.equals(otherItem) : item === otherItem);
  }, true);
};

if(!Object.prototype.keys) {
  Object.prototype.keys = function() {
    var a = [];
    for (var key in this) {
      if (this.hasOwnProperty(key)) a.push(key);
    }
    return a;
  }
  Object.defineProperty(Object.prototype, "keys", {enumerable: false});
}

Object.prototype.equals = function(otherObject) {
  if (!otherObject) return false;
  var object = this, objectKeys = object.keys();
  if (!objectKeys.equals(otherObject.keys())) return false;
  return objectKeys.reduce(function(equal, key) {
    var value = object[key], otherValue = otherObject[key];
    var valueType = typeof value, otherValueType = typeof otherValue;
    if (valueType !== otherValueType) return false;
    // this will call Array.prototype.equals for arrays and Object.prototype.equals for objects
    return equal && (valueType === "object" ? value.equals(otherValue) : value === otherValue);
  }, true);
}
Object.defineProperty(Object.prototype, "equals", {enumerable: false});

Этот код поддерживает массивы, вложенные в объекты, и объекты, вложенные в массивы.

Вы можете увидеть полный набор тестов и самостоятельно протестировать код в этом репл: https://repl.it/Esfz/3

7
задан andersonbd1 16 July 2009 в 13:30
поделиться

5 ответов

Вы спотыкаетесь о том, что дженерики Java не полиморфны по параметру типа.

Обсуждая ваш фрагмент кода, давайте разберем пример:

protected List<GreyHound> greyHounds; // List<GreyHound> is fine

/** This method returns a lovely List of GreyHounds */
public List<GreyHound> getGreyHounds() { 
  return this.greyHounds;
}

/** Here is the problem.  A List<GreyHound> is not a List<Dog> */
public List<Dog> getDogs() {
  return getGreyHounds(); //compiler error
}

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

  1. Попробуйте вернуть новый список, как вы предлагаете в своем комментарии. Например, return new ArrayList (this.greyHounds);

  2. Вам действительно нужно вести список собак определенной породы? Возможно, вам следует определить элемент данных как List , в который вы добавляете своих конкретных GreyHounds. То есть, защищенный список greyHoundsOnly; , в котором вы управляете, какие собаки разрешены в питомнике, через внешний интерфейс объекта.

Если у вас нет веских причин для ведения списка, зависящего от типа, я бы серьезно подумал о варианте 2.

РЕДАКТИРОВАТЬ: конкретизировать предложенные мной выше варианты:

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

// Original code starts here.
public interface DogKennel {
  public List<Dog> getDogs();
}

public class GreyHoundKennel implements DogKennel {

  protected List<GreyHound> greyHounds;

  public List<GreyHound> getGreyHounds() {
    return this.greyHounds;
  }
// Original code ends here

  public List<Dog> getDogs() {
    // This line eliminates the thread safety issue in returning 
    // an internal reference.  It does use additional memory + cost
    // CPU time required to copy the elements.  Unless this list is
    // very large, it will be hard to notice this cost.
    return new ArrayList<Dog>(this.greyHounds);
  }

}

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

public abstract class DogKennel {
  protected List<Dog> dogs = new ArrayList<Dog>();
}

public class GreyHoundKennel extends DogKennel {

  // Force an interface that only allows what I want to allow
  public void addDog(GreyHound greyHound) { dogs.add(greyHound); }

  public List<Dog> getDogs() {
    // Greatly reduces risk of side-effecting and thread safety issues
    // Plus, you get the generic list that you were hoping for
    return Collections.unmodifiableList(this.dogs);
  }

}
1
ответ дан 7 December 2019 в 07:49
поделиться

Это объявление:

List<?> getOuterList() { }

сообщает компилятору: «Я действительно не знаю, какой список я верну». Затем вы по существу выполняете

list<dunno-what-this-is>.add((MyObject)myObject)

. Он не может добавить MyObject в список того, что он не знает, какой это тип.

Это объявление:

protected List<? extends Object> getOuterList() { ... }

сообщает компилятору: «Это список вещей, которые являются подтипами объекта ". Итак, опять же, конечно, вы не можете преобразовать в «MyObject», а затем добавить его в список объектов. Поскольку все, что знает компилятор, это то, что список может содержать объекты.

Вы можете сделать что-то вроде этого:

List<? super MyObject>.getOuterList() { ... }

, а затем успешно добавить MyObject. Это потому, что теперь компилятор знает, что List - это список MyObject или любой супертип MyObject, поэтому он наверняка может принять MyObject.

Изменить: Что касается вашего примера DogKennel, этот фрагмент кода, я думаю, делает то, что вы хотите:

protected List<GreyHound> greyHounds;

// We only want a List of GreyHounds here:
public List<GreyHound> getGreyHounds() {
    return this.greyHounds;
}

// The list returned can be a List of any type of Dog:
public List<? extends Dog> getDogs() {
    return getGreyHounds();
}
3
ответ дан 7 December 2019 в 07:49
поделиться

You are saying that the method returns a "List of some unknown type" (which you can't add to, because you can't guarantee that the thing you are adding is a subtype of that type). You actually want to say, a "List of whatever type you want", so you have to make the method generic:

protected <T> List<T> getOuterList() {
  // blah blah
}

Okay, I just looked at your update:

It all depends on what you intend to be able to do with the result of getDogs(). If you do not intend to be able to add any items to the list, then getDogs() should return type List, and then the problem would be solved.

If you intend to be able to add things to it, and by the type List it means that you can add any kind of Dog to it, then logically this list cannot be the same list as greyHounds, because greyHounds has type List and so Dog objects should not go in it.

Which means that you must create a new list. Keeping in mind of course that any changes to the new list would not be reflected in the original list greyHouds.

3
ответ дан 7 December 2019 в 07:49
поделиться

a generic type of ? means "some specific type, but i don't know which". anything using a ? is essentially read-only because you can't write to it w/out knowing the actual type.

0
ответ дан 7 December 2019 в 07:49
поделиться

Уже есть принятый ответ, однако, пожалуйста, рассмотрите следующую модификацию кода.

public interface DogKernel {
    public List<? extends Dog> getDogs();
}

public class GreyHoundKennel implements DogKernel {
    protected List<GreyHound> greyHounds;

    public List<GreyHound> getGreyHounds() {
        return this.greyHounds;
    }

    public List<? extends Dog> getDogs() {
        return getGreyHounds(); // no compilation error
    }

    public static void main(String[] args) {
    GreyHoundKennel inst = new GreyHoundKennel();
    List<? extends Dog> dogs = inst.getDogs();
    }
}

Дженерики Java действительно сломаны, но не настолько. Кстати, Scala исправляет это очень элегантным способом, обеспечивая обработку отклонений.

ОБНОВЛЕНИЕ ----------

Обратите внимание на обновленный фрагмент кода.

public interface DogKennel<T extends Dog> {
    public List<T> getDogs();
}

public class GreyHoundKennel implements DogKennel<GreyHound> {
    private List<GreyHound> greyHounds;

    public List<GreyHound> getDogs() {
        return greyHounds; // no compilation error
    }

    public static void main(String[] args) {
        GreyHoundKennel inst = new GreyHoundKennel();
        inst.getDogs().add(new GreyHound()); // no compilation error
    }
}
0
ответ дан 7 December 2019 в 07:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: