WebHU - Programozási kérdések és válaszok

A super() NullPointerException kivételt okoz

Amíg Liang könyvét tanulmányozom, egy ponton elakadok, és nem értem, mi történik. A hiba oka a MyArrayList osztály konstruktora. A szerző figyelmeztet, hogy ne hívjuk a szuper(object)-t, de az okot nem magyarázták el. Most, amikor megpróbálom futtatni a kódot, a szerzőnek igaza van - amikor a szuper(object)-t hívjuk, hibát kapok. Mi az oka ennek a hibának?

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);
    }
}

Íme a hibakód:

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)
13.08.2015

  • Ha elolvasta a NullPointerExceptions-t, akkor tudni fogja, hogy az azt dobó sor(ok) kulcsfontosságúak. Tehát melyek azok? A MyArrayList 36. és 21. sora pl. 14.08.2015
  • @JeroenVannevel van egy jogos kérdés az objektum inicializálásának sorrendjét illetően, nem ez az alap npe kérdés. 14.08.2015
  • @njzk2: és utánaolvasni annak, hogy mi az NPE és hogyan lehet hibakeresni, megmutatta volna a problémát, ami után tudták, mit kell keresni. A kanonikus kérdés célja, hogy megtanuljuk, hogyan kell kutatni, nem pedig az NPE minden lehetséges okát felsorolni. 14.08.2015
  • Egyet kell értenem @njzk2-vel. Ezt a hibát az okozza, hogy az alosztály ensureCapacity olyan állapotban van meghívva, amikor az alosztály inicializálói még nem hajtottak végre. Ez nem triviális NPE. 14.08.2015
  • @HovercraftFullOfEels akkor láttad, hogy a data null, kivéve, hogy a data a deklaráláskor van megadva. Az OP is megemlíti, hogy a másik módszer működik, amikor az ember intuitív módon azt várná, hogy mindkettő ugyanúgy viselkedjen, mivel mindkettő nagyjából egy időben hívja a add-t. 14.08.2015
  • Ehelyett a stackoverflow.com/q/3404301/1321716 másolataként jelölném meg. Sajnos most csak az újranyitásra tudok szavazni... 14.08.2015
  • Ez a stackoverflow.com/a/17806502/671543 is segíthet. Bemutatja a konstruktor, a szuperkonstruktor és a tag inicializálásának sorrendjét. (ne feledje, hogy a super mindig meghívásra kerül, alapértelmezés szerint a no-args) 14.08.2015
  • a konstruktorhívások lehetséges ismétlődése a Java többszintű öröklődésében 14.08.2015

Válaszok:


1

Leegyszerűsítjük a kódot a lényegre:

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);
}

Fontos megérteni, hogy a szülőosztály konstruktora még a gyermekosztály inicializálása előtt meghívásra kerül (ezért a super()-nek mindig az első hívásnak kell lennie egy konstruktorban), vagyis amikor az MyAbstractList konstruktora fut, a data továbbra is null.

A super() hívás lecserélése annak tartalmára azt jelenti, hogy a for hurok végrehajtásra kerül a MyArrayList inicializálása közben, miután data megfelelően be lett állítva.

Lényegében az a probléma, hogy a MyAbstractList olyan konstruktort biztosít, amely olyan metódusokat hív meg, amelyeket egy gyermekosztály felülír, ami komoly anti-minta. A MyAbstractList nem tartalmazhat kiegészítõ stíluskonstruktort.

További információért lásd: Hatékony Java 17. tétel, amely megjegyzi:

A konstruktőrök nem hívhatnak meg felülírható metódusokat sem közvetlenül, sem közvetve. Ha megszegi ezt a szabályt, a program meghibásodik. A szuperosztály-konstruktor az alosztály-konstruktor előtt fut, így az alosztályban lévő felülbíráló metódus az alosztály-konstruktor lefutása előtt kerül meghívásra. Ha a felülbíráló metódus az alosztály-konstruktor által végrehajtott inicializálástól függ, a metódus nem a várt módon fog viselkedni.

13.08.2015

2

A probléma itt az, hogy a MyAbstractList-ben lévő konstruktor a add-t hívja, mielőtt a data inicializálódott.

A data mezőt a MyArrayList osztály deklarálja és inicializálja. Az inicializálás azonban csak azután történik meg, hogy a szuperosztály inicializálása befejeződött, és a add hívások közben a szuperosztály inicializálása során... amikor a data még mindig null.


Az általános probléma itt az, hogy veszélyes, ha egy konstruktor olyan metódust hív meg, amelyet egy alosztály felülírhat. A felülírási metódus valószínűleg az alosztály inicializálása előtt meghívásra kerül.

Ebben az esetben a add(E) felülbírálható. Ami még rosszabb, a add(E) meghívja a add(E, int)-et, ami határozottan felül van írva, mert absztrakt a szuperosztályban.

13.08.2015
Új anyagok

A rádiógomb ellenőrzött eseményének használata a jQueryben
Ebben a cikkben látni fogjuk, hogyan kell dolgozni a jquery választógombbal ellenőrzött eseményeivel. A választógombok HTML gombok, amelyek segítenek kiválasztani egyetlen értéket egy csoportból...

Körkörös függőségek megoldása terraformban adatforrásokkal – lépésről lépésre
Mi az a körkörös függőségek Dolgozzunk egy egyszerű eseten, amikor az SQS-sor és az S3-vödör közötti körkörös függőség problémája van egy egymástól függő címkeérték miatt. provider..

Miért érdemes elkezdeni a kódolást 2023-ban?
01100011 01101111 01100100 01100101 — beep boop beep boop Világunk folyamatosan fejlődik a technológia körül, és naponta fejlesztenek új technológiákat a valós problémák megoldására. Amint..

🎙 Random Noise #2  – Örökbefogadás és hit
az analitika íratlan világának gondozása Szeretné, hogy ezek a frissítések a postaládájába kerüljenek? Iratkozzon fel itt . "Ha önvezető autókat gyártanak, akkor mi miért ne..

A legrosszabb politika és prediktív modellek májátültetésre jelöltek számára az Egyesült Államokban
A máj (vagy óangolul lifer) az emberi test legnehezebb belső szervére utal, amely csendesen működik a nap 24 órájában. Mit csinál a máj? 500 feladatot hajt végre a szervezet egészségének..

5 webhely, amely 2022-ben fejleszti front-end fejlesztői készségeit
Frontendmentor.io A tényleges projektek létrehozásával a Frontendmentor.io segítséget nyújt a front-end kódolási képességeinek fejlesztésében. A kódolást azután kezdheti meg, hogy..

Mikor kell használni a Type-t az interfészhez képest a TypeScriptben?
A TypeScript a JavaScript gépelt szuperkészlete, amely statikus gépelést ad a nyelvhez. Ez megkönnyíti a robusztus és karbantartható kód írását azáltal, hogy a hibákat a fordítási időben..