简体   繁体   中英

super() causes NullPointerException

While I am studying Liang's book I am stuck at a point and I don't understand what is going on. The cause of error is the constructor of MyArrayList class. The author warns us about not calling super(object), but they didn't explain the reason. Now when I try and run the code the author is right - when we call super(object) I get an error. What is the cause of this error?

MyArrayList.java

public class MyArrayList<E> extends MyAbstractList<E> {
  public static final int INITIAL_CAPACITY = 16;
  private E[] data = (E[])new Object[INITIAL_CAPACITY];

  /** Create a default list */
  public MyArrayList() {
  }

  /** Create a list from an array of objects */
  public MyArrayList(E[] objects) {
    /*for (int i = 0; i < objects.length; i++)
     add(objects[i]); // Warning: don't use super(objects)! */
     super(objects);  //!!! AUTHOR WARNS US ABOUT NOT INVOKING THIS LINE !!!
  }

  /** Add a new element at the specified index in this list */
  public void add(int index, E e) {
    ensureCapacity();

    // Move the elements to the right after the specified index
    for (int i = size - 1; i >= index; i--)
      data[i + 1] = data[i];

    // Insert new element to data[index]
    data[index] = e;

    // Increase size by 1
    size++;
  }

  /** Create a new larger array, double the current size */
  private void ensureCapacity() {
    if (size >= data.length) {
      E[] newData = (E[])(new Object[size * 2 + 1]);
      System.arraycopy(data, 0, newData, 0, size);
      data = newData;
    }
  }

  /** Clear the list */
  public void clear() {
    data = (E[])new Object[INITIAL_CAPACITY];
    size = 0;
  }

  /** Return true if this list contains the element */
  public boolean contains(E e) {
    for (int i = 0; i < size; i++)
      if (e.equals(data[i])) return true;

    return false;
  }

  /** Return the element from this list at the specified index */
  public E get(int index) {
    return data[index];
  }

  /** Return the index of the first matching element in this list.
   *  Return -1 if no match. */
  public int indexOf(E e) {
    for (int i = 0; i < size; i++)
      if (e.equals(data[i])) return i;

    return -1;
  }

  /** Return the index of the last matching element in this list
   *  Return -1 if no match. */
  public int lastIndexOf(E e) {
    for (int i = size - 1; i >= 0; i--)
      if (e.equals(data[i])) return i;

    return -1;
  }

  /** Remove the element at the specified position in this list
   *  Shift any subsequent elements to the left.
   *  Return the element that was removed from the list. */
  public E remove(int index) {
    E e = data[index];

    // Shift data to the left
    for (int j = index; j < size - 1; j++)
      data[j] = data[j + 1];

    data[size - 1] = null; // This element is now null

    // Decrement size
    size--;

    return e;
  }

  /** Replace the element at the specified position in this list
   *  with the specified element. */
  public E set(int index, E e) {
    E old = data[index];
    data[index] = e;
    return old;
  }

  /** Override toString() to return elements in the list */
  public String toString() {
    StringBuilder result = new StringBuilder("[");

    for (int i = 0; i < size; i++) {
      result.append(data[i]);
      if (i < size - 1) result.append(", ");
    }

    return result.toString() + "]";
  }

  /** Trims the capacity to current size */
  public void trimToSize() {
    if (size != data.length) { // If size == capacity, no need to trim
      E[] newData = (E[])(new Object[size]);
      System.arraycopy(data, 0, newData, 0, size);
      data = newData;
    }
  }
}

MyList.java

public interface MyList<E> {
  /** Add a new element at the end of this list */
  public void add(E e);

  /** Add a new element at the specified index in this list */
  public void add(int index, E e);

  /** Clear the list */
  public void clear();

  /** Return true if this list contains the element */
  public boolean contains(E e);

  /** Return the element from this list at the specified index */
  public E get(int index);

  /** Return the index of the first matching element in this list.
   *  Return -1 if no match. */
  public int indexOf(E e);

  /** Return true if this list contains no elements */
  public boolean isEmpty();

  /** Return the index of the last matching element in this list
   *  Return -1 if no match. */
  public int lastIndexOf(E e);

  /** Remove the first occurrence of the element o from this list.
   *  Shift any subsequent elements to the left.
   *  Return true if the element is removed. */
  public boolean remove(E e);

  /** Remove the element at the specified position in this list
   *  Shift any subsequent elements to the left.
   *  Return the element that was removed from the list. */
  public E remove(int index);

  /** Replace the element at the specified position in this list
   *  with the specified element and returns the new set. */
  public Object set(int index, E e);

  /** Return the number of elements in this list */
  public int size();
}

MyAbstractList.java

public abstract class MyAbstractList<E> implements MyList<E> {
  protected int size = 0; // The size of the list

  /** Create a default list */
  protected MyAbstractList() {
  }

  /** Create a list from an array of objects */
  protected MyAbstractList(E[] objects) {
    for (int i = 0; i < objects.length; i++)
      add(objects[i]);
  }

  /** Add a new element at the end of this list */
  public void add(E e) {
    add(size, e);
  }

  /** Return true if this list contains no elements */
  public boolean isEmpty() {
    return size == 0;
  }

  /** Return the number of elements in this list */
  public int size() {
    return size;
  }

  /** Remove the first occurrence of the element o from this list.
   *  Shift any subsequent elements to the left.
   *  Return true if the element is removed. */
  public boolean remove(E e) {
    if (indexOf(e) >= 0) {
      remove(indexOf(e));
      return true;
    }
    else
      return false;
  }
}

TestMyArrayList.java

public class TestMyArrayList {
    public static void main(String[] args)
    {

        String[] str = {"manisa","turkey","germany"};

        MyList<String> list = new MyArrayList<String>(str);

        list.add("America");
        list.add(0,"Canada");
        list.add(1,"England");
        System.out.println(list);
    }
}

Here is the error code :

Exception in thread "main" java.lang.NullPointerException
    at MyArrayList.ensureCapacity(MyArrayList.java:36)
    at MyArrayList.add(MyArrayList.java:21)
    at MyAbstractList.add(MyAbstractList.java:16)
    at MyAbstractList.<init>(MyAbstractList.java:11)
    at MyArrayList.<init>(MyArrayList.java:16)
    at TestMyArrayList.main(TestMyArrayList.java:8)

The problem here is that the constructor in MyAbstractList is calling add before data has been initialized.

The data field is declared and initialized in the MyArrayList class. But the initialization doesn't occur until after the superclass initialization has finished, and the add calls are made during the superclass initialization ... when data is still null .


The general problem here is that it is dangerous for a constructor to call a method that could be overridden by a subclass. The override method is liable to be called before the subclass initialization has occurred.

In this case, add(E) could be overridden. Worse still, add(E) calls add(E, int) which definitely is overridden, because it is abstract in the superclass.

Lets simplify your code to the bare essentials:

public abstract class MyAbstractList<E> {
  protected int size = 0; // The size of the list

  protected MyAbstractList() {}

  protected MyAbstractList(E[] objects) {
    for (int i = 0; i < objects.length; i++)
      add(objects[i]);
}

public class MyArrayList<E> extends MyAbstractList<E> {
  public static final int INITIAL_CAPACITY = 16;
  private E[] data = (E[])new Object[INITIAL_CAPACITY];

  public MyArrayList(E[] objects) {
     super(objects); // this call to super() executes before data is initialized
  }
}

public static void main(String[] args) {
  String[] str = {"manisa","turkey","germany"};
  MyList<String> list = new MyArrayList<String>(str);
}

The important thing to understand is that the parent class' constructor is called before the child class is even initialized (which is why super() always has to be the first call in a constructor), meaning when MyAbstractList 's constructor is running, data is still null .

Replacing the super() call with its contents means the for loop is executed while MyArrayList is initializing, after data has been properly set.

In essence, the problem is that MyAbstractList provides a constructor that calls methods that will be overridden by a child class, which is a serious anti-pattern. MyAbstractList should not provide an add-all style constructor.

For more, see Effective Java Item 17, which notes:

Constructors must not invoke overridable methods , directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will get invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM