Почему этот код иногда вызывает исключение NullPointerException?

Рассмотрим следующий источник Java:

if( agents != null ) {
  for( Iterator iter = agents.keySet().iterator(); iter.hasNext(); ) {
    // Code that uses iter.next() ...
    //
  }
}

agents является HashMap.

Почему оператор for иногда бросает NullPointerException?

Спасибо.

Ответ 1

Безопасность потоков

Если ваш код многопоточен, тогда это возможно. Например:

public class C {
  private Hashtable agents = new Hashtable();

  public iterate() {
    if( agents != null ) {
      for (Iterator iter = agents.keySet().iterator(); iter.hasNext();) {
        // Code goes here
      }
    }
}

Если другой поток устанавливает agents в null сразу после выполнения инструкции if (но до цикла for), вы получите NullPointerException. Избегайте этого с помощью аксессуаров (в сочетании с ленивой инициализацией).

Кроме того, как отмечали другие, избегайте подобных циклов в пользу дженериков, если это возможно. Подробнее см. В других ответах.

Аксессоры предлагают защиту

Если вы всегда используете следующий шаблон, у вас никогда не будет NullPointerException в вашем исходном коде (с другой стороны, у стороннего кода могут быть проблемы, из-за которых ваш код не работает, косвенно, чего не может быть легко избежать).

public class C {
  private Hashtable agents;

  private synchronized Hashtable getAgents() {
    if( this.agents == null ) {
      this.agents = new Hashtable();
    }

    return this.agents;
  }

  public iterate() {
    Hashtable agents = getAgents();

    for (Iterator iter = agents.keySet().iterator(); iter.hasNext();) {
      // Code goes here
    }
  }
}

Код, который выполняет итерации над агентами, больше не нужно проверять на null. Этот код гораздо более воинственный по многим причинам. Вы можете заменить Hashmap (или любой другой абстрактный тип данных, например ConcurrentHashMap<K,V>) на Hashtable.

Принцип открытого закрытия

Если вы чувствовали себя особенно щедрыми с вашим временем, вы могли бы зайти так далеко, как:

public class C {
  private Hashtable agents;

  private synchronized Hashtable getAgents() {
    if( this.agents == null ) {
      this.agents = createAgents();
    }

    return this.agents;
  }

  public iterate() {
    Iterator i = getAgentKeyIterator();

    while( i.hasNext() ) {
      // Code that uses i.next() ...
    }
  }

  protected Hashtable createAgents() {
    return new Hashtable();
  }

  private Iterator getAgentKeyIterator() {
    return getAgentKeys().iterator();
  }

  private KeySet getAgentKeys() {
    return getAgents().keySet();
  }
}

Это позволит подклассам (написанным другими разработчиками) заменить свой собственный подкласс используемого абстрактного типа данных (позволяя системе большую гибкость в соответствии с Принцип открытого закрывания), без необходимости изменять (или копировать/тратить) свою оригинальную работу.

Ответ 2

Не должен ли быть цикл while?

if (agents != null) {
    Iterator iter = agents.keyset().iterator();
    while (iter.hasNext()) {
        //some stuffs here
    }
}

или a для каждого?

if (agents != null) {
    //Assuming the key is a String
    for (String key : agents.keyset()) {
        //some stuffs here
    }
}

Ответ 3

Убедитесь, что значение iter не установлено в цикле.