在Java中用不可变堆栈定义Contravariant add方法

时间:2015-12-21 19:02:51

标签: java immutability contravariance

我一直试图在Java中定义一个不可变的ConsList,有点像Lists在Scala中的工作方式。每个前置创建一个新的列表视图(从新头开始)并返回它。这是我到目前为止所做的,所有这些都可以正确编译和运行。

package common.dataStructures;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;

/**
 * An immutable collection representing a Cons List.
 * All Cons Lists are terminated with a Nil element (no tail, no value),
 * but this element is hidden from iteration and stringing when there
 * are non-nil elements in the list.
 * <br><br>
 * Because the List is immutable, different lists can be branched off of a shared
 * tail without altering the tail. This makes the ConsList useful in some algorithms
 * that require exploring different possibility chains without altering earlier
 * sub-chains.
 *
 * @param <E> - the generic type of elements stored within the lst.
 *            No methods are ever called on elements of type E, so no requirements
 *            are made of the type.
 * @author Mshnik
 */
public class ConsList<E> implements Iterable<E> {

  /**
   * A string used for toString implementations for the Nil element of
   * a Cons List
   */
  public static final String NIL_STRING = "_NIL_";

  /**
   * The tail of this List.
   * If this is Nil, tail is null. Otherwise, this is another ConsList
   * of one smaller size.
   */
  private final ConsList<E> tail;

  /**
   * The value stored in the head of this list.
   * If this is Nil, value is null. Otherwise the value may be null,
   * or the value stored in the head.
   */
  public final E val;

  /**
   * The size of this list. Because ConsLists are immutable, this size
   * value is immutable. If this is Nil, size is 0. Otherwise, size
   * is some positive, non-zero value
   */
  public final int size;

  /**
   * Initializes an empty (NIL) ConsList, with tail = null, val = null,
   * size = 0. <br>
   * A Nil element is required to be at the end of every ConsList,
   * so this constructor is used to start any ConsList. Elements are then
   * cons (prepended) on to this element to build a list.
   */
  public ConsList() {
    val = null;
    tail = null;
    size = 0;
  }

  /**
   * Creates a new head of a list
   *
   * @param val  - the value to store in the head of this list
   * @param tail - the tail of this list, an existing ConsList
   * @param size - the size of this list
   */
  private ConsList(E val, ConsList<E> tail, int size) {
    this.val = val;
    this.size = size;
    this.tail = tail;
    if (val == null || tail == null || size < 1)
      throw new RuntimeException("Illegal ConsList construction" + this);
  }

  /**
   * Returns the value stored in the head of this ConsList
   */
  public E value() {
    return val;
  }

  /**
   * Returns the tail of this ConsList. Returns null if this is NIL
   */
  public ConsList<E> tail() {
    return tail;
  }

  /**
   * Returns true if this is NIL - the trailing element in a consList.
   * A Nil element has no tail and no value
   */
  public boolean isNil() {
    return tail() == null && val == null;
  }

  /**
   * Returns true if this is the last element in a list.
   * Returns true for NIL elements, and the last real element before a NIL element
   * Used to stop iteration before it reaches the NIL terminator
   */
  public boolean isLast() {
    return isNil() || tail().tail() == null && tail().val == null;
  }

  /**
   * Returns a new ConsList that consists of {@code head} prepended
   * onto this ConsList
   */
  public ConsList<E> cons(E head) {
    return new ConsList<E>(head, this, size + 1);
  }

  /**
   * Returns a new ConsList that consists of this full list, reversed.
   */
  public ConsList<E> reverse() {
    ConsList<E> reversed = new ConsList<E>();
    ConsList<E> ptr = this;
    while (!ptr.isNil()) {
      reversed = reversed.cons(ptr.val);
      ptr = ptr.tail;
    }
    return reversed;
  }

  /**
   * Returns a string representation of a ConsList
   */
  @Override
  public String toString() {
    String s = "(";
    ConsList<E> current = this;
    while (current != null) {
      if (current.val == null && current.tail() == null) {
        if (current == this) s += NIL_STRING + ",";
      } else {
        s += current.val + ",";
      }
      current = current.tail();
    }
    return s.substring(0, s.length() - 1) + ")";
  }

  /**
   * Returns true if this equals {@code o}.
   * Two ConsLists are equal if they are the same length and every value they
   * at each index are equal.
   */
  @Override
  public boolean equals(Object o) {
    if (!(o instanceof ConsList<?>) || o == null) return false;

    ConsList<?> lst = (ConsList<?>) o;

    return size == lst.size && Objects.equals(val, lst.val) && Objects.equals(tail(), lst.tail());
  }

  /**
   * Returns the size of this list. See {@link common.dataStructures.ConsList#size}
   */
  public int size() {
    return size;
  }

  /**
   * Returns true iff the size of this list is 0. Equivalently, if this list is NIL
   */
  public boolean isEmpty() {
    return size == 0;
  }

  /**
   * Returns true iff this list contains the Object o.
   * NIL lists do not contain any elements, thus calling contains on a NIL
   * list for any input will always return false.
   */
  public boolean contains(Object o) {
    return !isNil() && (Objects.equals(val, o) || !isLast() && tail().contains(o));
  }

  /**
   * Returns true iff, for all {@code Object o : c}, this.contains(o)
   */
  public boolean containsAll(Collection<?> c) {
    for (Object o : c) {
      if (!contains(o)) return false;
    }
    return true;
  }

  /**
   * Returns an Iterator over the elements in this ConsList.
   * As ConsLists are immutable, the returned iterator does not support removal,
   * and cannot throw a ConcurrentModificationException.
   * The iterator returned will stop at the last real element in the list,
   * *before* the NIL element that terminates all lists. If this method
   * is called on the NIL list, the iterator will have no elements to return -
   * hasNext() will immediately return false. Thus the iterator behaves as if
   * the NIL element does not exist.
   */
  @Override
  public Iterator<E> iterator() {
    return new ConsIterator<E>(this);
  }

  /**
   * Converts this ConsList to an array of Objects.
   * The length of the returned array is equal to this.size.
   * Therefore for the NIL array, the returned array is of length 0.
   */
  public Object[] toArray() {
    Object[] arr = new Object[size];
    ConsList<E> current = this;
    for (int i = 0; i < size; i++, current = current.tail()) {
      arr[i] = current.val;
    }
    return arr;
  }

  /**
   * Converts this ConsList to an array of type T.
   * The length of the returned array is equal to Max(this.size, arr.length).
   * Therefore for the NIL array, the returned array is of length 0.
   * See the List interface for the weird type bullcrap about casting to T[]
   */
  @SuppressWarnings("unchecked")
  public <T> T[] toArray(T[] arr) {
    if (arr.length < size) {
      arr = Arrays.copyOf(arr, size);
    }
    ConsList<E> current = this;
    for (int i = 0; i < size; i++, current = current.tail()) {
      arr[i] = (T) current.val;
    }
    return arr;
  }

  /**
   * Returns the {@code index}th element in this list.
   *
   * @throws IllegalArgumentException - if index &lt; 0 or index &gt;= size - thus throws
   * exception if this is called with any index value on the nil list
   */
  public E get(int index) {
    if (index < 0 || index >= size)
      throw new IllegalArgumentException("Can't get element at index " + index + " OOB");

    if (index == 0) return val;
    return tail().get(index - 1);
  }

  /**
   * Returns the first index of {@code o} in this List, or -1 if it does not occur
   */
  public int indexOf(Object o) {
    return indexOf(o, 0);
  }

  /**
   * Helper method for the indexOf function - keeps track of how many recursive
   * calls have been made thus far to this function to return the correct value
   * if {@code o} is found.
   */
  private int indexOf(Object o, int x) {
    if (Objects.equals(val, o)) return x;
    else if (isLast()) return -1;
    return tail().indexOf(o, x + 1);
  }

  /**
   * Helper class for iterating over ConsList
   * Keeps track of a current list element that will be returned by next() calls.
   *
   * @param <E>
   * @author Mshnik
   */
  public static class ConsIterator<E> implements Iterator<E> {

    private ConsList<E> current; //next element to return when next() is called

    /**
     * Creates a new ConsIterator, starting at first
     */
    public ConsIterator(ConsList<E> first) {
      current = first;
    }

    /**
     * Returns true iff there is another element to iterate over.
     * Specifically, returns {@code !current.isNil()}
     */
    @Override
    public boolean hasNext() {
      return !current.isNil();
    }

    /**
     * Returns the next value in this iteration, and moves current
     * forward one element.
     */
    @Override
    public E next() {
      E val = current.val;
      current = current.tail();
      return val;
    }

  }

}

这一切都适用于类型E及其合作伙伴。如果我有ConsList<Animal>,并且我致电cons(new Dog()),则会根据需要为我提供ConsList<Animal>

但是,我已经尝试了所有我能想到的定义逆变cons方法。我最好的猜测看起来大致如下:

public <X super E> ConsList<X> cons(X head) {
    return new ConsList(head, this, size+1);
}

我希望我能够在[{1}}上创建ConsList<String>,利弊并获得new Object

但是这种方法没有编译,也没有其他任何尝试。我理解为什么可变数据结构(即ConsList<Object>)不支持协方差或逆变,但有没有任何语法上有效的方法来定义Java中的通用逆变,即使对于不可变结构?如果是这样,它是什么,如果没有,为什么?

1 个答案:

答案 0 :(得分:0)

备注:这不是一个完整的答案,但它提供了一个想法,如何通过将一些检查推送到运行时并添加类信息来解决它。 关于“为什么”的另一个问题,我无法回答。

您需要传递对象和对象的类。如果类不是super E,那么编译器将抛出异常。虽然检查头部是否真的是一个clazz实例需要手动完成。

public <T> ConsList<T> cons(final T head, final Class<? super E> clazz) {
    if (clazz.isInstance(head) && head.getClass().equals(clazz)) {
        return new ConsList(head, this, this.size + 1);
    } else {
        throw new IllegalArgumentException("T is not instance of class");
    }
}

这不是你需要通过课程的大小。但至少你可以这样称呼:

list.cons(headElement, headElement.getClass());