为什么HashSet不保持唯一性?

时间:2018-01-01 13:52:32

标签: java hashset

考虑员工类 -

    namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

创建了2个员工对象 -

These credentials do not match our records.

将它们添加到hashset后,大小将为2。 HashSet在内部使用hashmap来保持唯一性 -

public class Employer implements Serializable{

  private Long id;
  private String name;

  @Override
  public boolean equals(Object obj) {

    if (obj == null)
        return false;
    if (obj instanceof Employer) {
        Employer employer = (Employer) obj;
        if (this.id == employer.id) {
            return true;
        } 
    }
    return false;
  }

  //Idea from effective Java : Item 9
  @Override
  public int hashCode() {
    int result = 17;
    result = 31 * result + id.hashCode();
    //result = 31 * result + name.hashCode();
    return result;
  }
}

现在,如果我将第二个员工的id设置为与第一个员工的id相同,即 -

Employer employer1 = new Employer();
employer1.setId(10L);

Employer employer2 = new Employer();
employer2.setId(11L);

大小仍然是2。 为什么不是1? in-variants是否会被破坏?

2 个答案:

答案 0 :(得分:8)

  

尺寸仍然是2.为什么不是1? in-variants是否会被破坏?

如果您修改hashCode中已有的实例用于计算equalsHashSet的任何属性,则HashSet实施不知道该更改。

因此它会保留两个实例,即使它们现在彼此相等。

您不应对作为成员或HashSet s(或HashMap s中的键)的实例进行此类更新。如果您必须进行此类更改,请在变更之前从Set中删除该实例,然后再重新添加。

答案 1 :(得分:8)

所有基于散列的容器(包括HashSet<T>)都对其键的哈希码做出了非常重要的假设:它们假设哈希代码在对象位于容器内时永远不会改变。

您的代码违反了此假设,因为它仍然在哈希集中时修改实例。 HashSet<T>没有实际的方法来应对此更改,因此您必须选择以下两种方法之一来处理此问题:

  • 永远不要修改基于散列的容器的密钥 - 这是目前最常用的方法,通常是通过使哈希键不可变来实现的。
  • 跟踪修改并手动重新哈希对象 - 实质上,您的代码确保在哈希键位于容器外时对哈希键进行所有修改:从容器中删除对象,make修改,然后把它放回去。

第二种方法经常成为维护问题的根源。当您需要在基于散列的容器中保留可变数据时,一种好方法是在计算哈希代码和相等性检查时仅使用final个字段。在您的示例中,这意味着制作id字段final,并从类中删除setId方法。