NoSuchElementException с существующим ключом

Класс здания:

class Building(val name: String, val skill: String, @volatile var workHours: Int) {

  var workers = new HashMap[Artisan, Int]()

  def doWork(worker: Artisan): Boolean = {
    ...
    workers.get(worker) match {
      case Some(i: Int) =>
        worker.cash += i
        true
      case None => false
    }
    ...
  }

Класс ремесленников:

class Artisan(val skill: String, city: City) extends Player(skill, city) {
  var assignment = new Building("Empty", "", 0)

  def doWork() {
    if ( !assignment.doWork(this) )
    ...
  }

  def canEqual(other: Any): Boolean = other.isInstanceOf[Artisan]

  override def equals(other: Any): Boolean = other match {
    case that: Artisan =>
      (that canEqual this) &&
        assignment == that.assignment &&
        skill == that.skill
    case _ => false
  }

  override def hashCode(): Int = {
    val state = Seq(assignment, skill)
    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
  }
}

Прецедент:

@Test
def testIncome() {
  val building = new Building("Bakery", "Builder", 10)
  building.setSallery(100, player)  // add player to building hashmap
  player.assignment = building
  player.doWork()
  assertEquals("Should earn 100", player.cash, 100)
}

выход:

Negative test case

Я не понимаю, как этот код мог фактически обеспечить эту ошибку. Когда мой код должен просто возвращать false, если элемент не существует. При отладке программы в мою хэш-карту добавляется объект Artisan, поэтому я не понимаю, почему он не работает.


person miniwolf    schedule 13.07.2014    source источник
comment
Как выглядит ваш Artisan класс? Вы используете equals, чтобы увидеть, находится ли рабочий ключ в наборе, но метод apply использует ==, который просто вызывает equals, хотя, если вы переопределите его, это может вызвать проблемы.   -  person Noah    schedule 14.07.2014
comment
Вы можете использовать метод contains, чтобы проверить, содержит ли Map ключ, то есть !workers.contains(worker).   -  person wingedsubmariner    schedule 14.07.2014
comment
У меня есть еще один тестовый пример с использованием keySet.exists(_.equals(worker))). Разве это не даст тот же результат?   -  person miniwolf    schedule 14.07.2014
comment
Как говорит @Noah, было бы интересно взглянуть на ваш Artisan класс. Для меня это выглядит как несоответствие между вашими методами equals и hashCode.   -  person Mario Camou    schedule 14.07.2014
comment
Я вижу, что внутри класса Artisan есть переменные. Не уверен, насколько целесообразно использовать такой класс в качестве ключа (и почему это вообще возможно). Теоретически такие методы, как doWork, должны возвращать новый объект, а не изменять существующий и т. д.   -  person Ashalynd    schedule 14.07.2014
comment
Только использование val внутри моего hashCode() в классе Artisan помогло. Большое спасибо, ребята, за этот ценный урок.   -  person miniwolf    schedule 14.07.2014


Ответы (1)


Использование return в Scala не рекомендуется. Ваше сообщение об ошибке выглядит так, как будто оценивается вся функция, даже если был оператор возврата. Есть несколько альтернатив для return в случае, подобном вашему:

1) с помощью опции

var workers = new HashMap[Artisan, Int]()

  def doWork(worker: Artisan): Boolean = {
    val maybeIncome = workers.get(worker)
    maybeIncome match {
        case None => false
        case Some(income) => {
          val result = <do something with income that returns Boolean>
          result
         }
       }
    }

2) использование for-comprehensions

var workers = new HashMap[Artisan, Int]()

  def doWork(worker: Artisan): Boolean = {
    val result = for { income <- workers.get(worker) } yield {
      <do something with income that returns boolean>
    }
    result getOrElse false
  }

ОБНОВЛЕНИЕ: я вижу, что в вашем классе Artisan есть изменяемые элементы. Использование такого класса в качестве ключа может привести к непредсказуемым результатам (и мне интересно, почему это вообще возможно). Я бы предпочел определить две карты: Map[ArtisanID, Artisan] и Map[ArtisanID, доход], обе из которых будут иметь неизменяемые ключи. (ArtisanID может быть чем угодно, что однозначно идентифицирует вашего мастера и никогда не меняется).

person Ashalynd    schedule 13.07.2014
comment
Несмотря на то, что это был отличный ответ на мою ошибку с нулевым указанием, это не помогло решить мое понимание. Теперь .get(worker) приводит к None, даже когда отладка программы показывает, что элемент помещен в хэш-карту, как предполагалось. - person miniwolf; 14.07.2014
comment
Это действительно странно. Семантика get заключается в том, что он возвращает None, когда ключ отсутствует, и Some(value), когда ключ присутствует. Как выглядит ваш Artisan класс? или, что еще лучше, не могли бы вы показать код вызова, использующий doWork? - person Ashalynd; 14.07.2014
comment
Другое дело: ваш workers — это var. Может быть, вам стоит попробовать вместо этого использовать изменчивую карту? Кроме того, не могли бы вы распечатать все workers ключи при входе в вашу doWork функцию? Это может пролить свет. - person Ashalynd; 14.07.2014
comment
Ой, вы используете класс с изменяемыми элементами в качестве ключа. Это напрашивается на неприятности. Можете ли вы разработать ключ, который не будет изменяться? - person Ashalynd; 14.07.2014
comment
Я попытаюсь отредактировать свои переменные, используемые для hashkey, и вместо этого использовать только vals. Я обновлю код снова, когда закончу. - person miniwolf; 14.07.2014