hashcode 订阅
哈希码产生的依据:哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。 展开全文
哈希码产生的依据:哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。
信息
外文名
HashCode
包    括
equals和HashCode
类    别
一种算法
中文名
哈希码
应    用
java编程
应用代码类别
Object类,String类,Integer类
哈希码简介
在Java中,哈希码代表对象的特征。例如对象 String str1 = “aa”, str1.hashCode= 3104String str2 = “bb”, str2.hashCode= 3106String str3 = “aa”, str3.hashCode= 3104根据HashCode由此可得出str1!=str2,str1==str3下面给出几个常用的哈希码的算法。1、Object类的hashCode.返回对象的 [1]  内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。2、String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同。3、Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
收起全文
精华内容
参与话题
问答
  • hashcode

    千次阅读 2019-07-12 18:03:43
    hashcode java中的每一个类都继承Object Object类中有一个方法,该方法返回hashcode,native表示调用本地操作系统的方法 任何类都可以重写该方法,例如创建一个Person类并重写该方法,代码如下 public class ...

    hashcode

    • java中的每一个类都继承Object
    • Object类中有一个方法,该方法返回hashcode,native表示调用本地操作系统的方法
      在这里插入图片描述
    • 任何类都可以重写该方法,例如创建一个Person类并重写该方法,代码如下
    public class Person extends Object{
        @Override
        public int hashCode(){
            return 1;
        }
    }
    
            Person p= new Person();
            System.out.println(p);
            Person  p2=new Person();
            System.out.println(p2);
    
    • 结果如下
      在这里插入图片描述
    • 因此@后面表示的是hash值,也可以理解成为逻辑值。但是此时物理地址不一样
    展开全文
  • equals和hashcode总结

    万次阅读 多人点赞 2019-07-31 18:37:06
    equals和hashcode总结: 1.equals方法没有重写的话,用于判断对象的内存地址引用是否是用一个地址。重写之后一般用来比较对象的内容是否相等(比如student对象,里面有姓名和年龄,我们重写 equals方法来判断只要...

    equals和hashcode总结:

    1.equals方法没有重写的话,用于判断对象的内存地址引用是否是用一个地址。重写之后一般用来比较对象的内容是否相等(比如student对象,里面有姓名和年龄,我们重写

    equals方法来判断只要姓名和年龄相同就认为是用一个学生)。

    2.hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值,当然你也可以重写它,hashcode方法只有在集合中用到。

    3.对象放入集合中时,先判断hashcode是否相等,再判断equals是否相等,都相等就算是同一个对象,list则可以放入,set因为不允许重复所以不会放入。

    4.例如:

    public class Student {

            private int age;

            private String name;

            

            public Student(int age ,String name){

                this.age = age;

                this.name = name;

            }

            

            public int getAge() {

                return age;

            }

            public void setAge(int age) {

                this.age = age;

            }

            public String getName() {

                return name;

            }

            public void setName(String name) {

                this.name = name;

            }

            //重写equals方法,判断姓名和年龄相同就是相等的

            public boolean equals(Object o){

                if(o == null){

                    return false;

                }

                if(this.getClass() != o.getClass()){

                 return false;   

                }

                Student student = (Student)o;

                if(name == null){

                    return false;

                }

                if(age==student.getAge()&&name.equals(student.getName())){

                    return true;

                }

                return false;

            }

            

        public static void main(String[] args) {

            Student studentOne = new Student(1,"yjc");

            Student studentTwo = new Student(1,new String("yjc"));

            System.out.println(studentOne.equals(studentTwo));

            System.out.println("1: "+studentOne.getName().hashCode());

            System.out.println("2: "+studentTwo.getName().hashCode());

        }

        //输出结果:true

                    1: 119666

                    2: 119666

     

    }

    以上可以看出,两个String都叫"yjc",无论是直接"yjc"还是new String("yjc"),他们的hashcode都相同。所以在重写hashcode方法时可以运用这一点。

    比如你希望如果姓名和年龄相同,不仅equals相同,他们的hashcode也要相同,可以这样重写hashcode:

    public int hashcode(){

    final int prime = 31;

    int result = 1;

    result = prime*result + age;

    result = prime*result + (name == null? 0 : name.hashcode());

    return result;//直接写age+(name == null? 0 : name.hashcode())也行就是感觉太简单了0.0

    }

    这样一来两个姓名和年龄相同的Student对象就是同一个对象了,放入set中会被认为是同一个,无论放几个这样的对象,set.size()都是等于1。

    同样,HashMap因为key也是唯一的,HashMap对象是根据其Key的hashCode来定位存储位置,并使用equals(key)获取对应的Value,所以在put时判断key是否重复用到了hashcode和equals,若重复了则会覆盖。

    展开全文
  • HashCode详解

    千次阅读 多人点赞 2018-09-27 17:51:09
    HashCode详解 起初的时候我一直认为hashcode是内存地址的一个映射,但是今天仔细研究了一下hashcode,以往的认识就这样无情的被颠覆了。 起初我把对象的内存地址和hashcode一起输出,由于java是不建议用户之间操作...

    HashCode详解

    起初的时候我一直认为hashcode是内存地址的一个映射,但是今天仔细研究了一下hashcode,以往的认识就这样无情的被颠覆了。

    起初我把对象的内存地址和hashcode一起输出,由于java是不建议用户之间操作内存的,所以一般情况下是不能够拿到对象的内存地址,但是Java留了一个后门:Unsafe,通过Unsafe类我们可以访问到对象的内存地址

    public class Test {
        private static Unsafe unsafe;
    
        static {
            try {
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                unsafe = (Unsafe) field.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static long addressOf(Object o) throws Exception {
            Object[] array = new Object[]{o};
            long baseOffset = unsafe.arrayBaseOffset(Object[].class);
            int addressSize = unsafe.addressSize();
            long objectAddress;
            switch (addressSize) {
                case 4:
                    objectAddress = unsafe.getInt(array, baseOffset);
                    break;
                case 8:
                    objectAddress = unsafe.getLong(array, baseOffset);
                    break;
                default:
                    throw new Error("unsupported  address  size:  " + addressSize);
            }
            return (objectAddress);
        }
    
    
        public static void main(String[] args) throws Exception {
            Test test=new Test();
            System.out.println(addressOf(test));
            System.out.println(System.identityHashCode(test));
    
            Test test2=new Test();
            System.out.println(addressOf(test2));
            System.out.println(System.identityHashCode(test2));
    
            Test test3=new Test();
            System.out.println(addressOf(test3));
            System.out.println(System.identityHashCode(test3));
        }
    }
    

    我反复运行上面这段代码,结果如下

    第一次 第二次 第三次
    4028048182
    1956725890
    4028048279
    356573597
    4028048324
    1735600054
    4028048091
    1956725890
    4028048188
    356573597
    4028048233
    1735600054
    4028048190
    1956725890
    4028048287
    356573597
    4028048332
    1735600054

    我们可以看到,内存地址不一样,但是hashcode是一样的,更有意思的是hashcode好像和new对象的顺序有关。
    为了验证是否hashcode是否和对象生成的顺序有关,我用一个死循环去循环生成对象,然后看出现hash冲突的位置。

    public class SameClassRepetTest {
        public static void main(String[] args) {
            HashSet<Integer> set=new HashSet<Integer>();
            int count=0;
            int repetCount=0;
            while (true){
                count++;
                int code=new SameClassRepetTest().hashCode();
                if (set.contains(code)){
                    System.out.println("code:"+code);
                    System.out.println("count:"+count);
                    repetCount++;
                    System.out.println(repetCount);
                    if (repetCount==10){
                        break;
                    }
                }
                else
                    set.add(code);
            }
        }
    

    当出现10次重复hashcode的时候程序终止,结果如下

    code:2134400190
    count:105825
    1
    code:651156501
    count:111272
    2
    code:1867750575
    count:121933
    3
    code:2038112324
    count:145456
    4
    code:1164664992
    count:146389
    5
    code:273791087
    count:152369
    6
    code:996371445
    count:180981
    7
    code:254720071
    count:184294
    8
    code:1872358815
    count:186922
    9
    code:332866311
    count:206932
    10

    每一次运行的结果都是相同的,这就很有意思了,基本上初步就可以判断hashcode与对象生成的顺序有关

    然后我生成不同的类对象去重复上述操作

    public class DeffClassRepetTest {
        public static void main(String[] args) {
            HashSet<Integer> set = new HashSet<Integer>();
            int count = 0;
            int repetCount = 0;
            while (true) {
                count++;
                int code = count%2==0?new SameClassRepetTest().hashCode():new DeffClassRepetTest().hashCode();
                if (set.contains(code)) {
                    System.out.println("code:" + code);
                    System.out.println("count:" + count);
                    repetCount++;
                    System.out.println(repetCount);
                    if (repetCount == 10) {
                        break;
                    }
                } else
                    set.add(code);
            }
        }
    }
    
    

    运行结果与同一个类生成对象的结果完全一致,可以断定hashcode与对象类型无关,对象的生成顺序有关。当我以为认识到真相的时候,手贱敲了段代码,结果认识又被颠覆了。

    我用多个线程去执行上述操作:

    public class ConcurrentTest {
        public static void main(String[] args) {
            AtomicInteger count =new AtomicInteger(0);
            AtomicInteger repetCount=new AtomicInteger(0);
            ConcurrentSkipListSet<Integer> set=new ConcurrentSkipListSet<Integer>();
            new Thread(()->{
                while (true) {
                    count.incrementAndGet();
                    int code = count.get()%2==0?new SameClassRepetTest().hashCode():new DeffClassRepetTest().hashCode();
                    if (set.contains(code)) {
                        System.out.println("Tread1:code:" + code);
                        System.out.println("Tread1:count:" + count.get());
                        repetCount.incrementAndGet();
                        System.out.println(repetCount.get());
                        if (repetCount.get() >= 10) {
                            break;
                        }
                    } else
                        set.add(code);
                }
            }).start();
            new Thread(()->{
                while (true) {
                    count.incrementAndGet();
                    int code = count.get()%2==0?new SameClassRepetTest().hashCode():new DeffClassRepetTest().hashCode();
                    if (set.contains(code)) {
                        System.out.println("Tread2:code:" + code);
                        System.out.println("Tread2:count:" + count.get());
                        repetCount.incrementAndGet();
                        System.out.println(repetCount.get());
                        if (repetCount.get() >= 10) {
                            break;
                        }
                    } else
                        set.add(code);
                }
            }).start();
        }
    

    重复运行这段代码

    第一次 第二次
    Tread2:code:308698969
    Tread2:count:133848
    1
    Tread2:code:39252308
    Tread2:count:141446
    2
    Tread2:code:462746542
    Tread2:count:144624
    3
    Tread1:code:699788746
    Tread1:count:158743
    4
    Tread1:code:1745215832
    Tread1:count:172682
    5
    Tread1:code:571947411
    Tread1:count:174724
    6
    Tread1:code:1609797262
    Tread1:count:175730
    7
    Tread1:code:1406244557
    Tread1:count:175922
    8
    Tread1:code:361509194
    Tread1:count:215637
    9
    Tread1:code:476907224
    Tread1:count:217548
    10
    Tread2:code:1600420189
    Tread2:count:222308
    11
    Tread1:code:147103475
    Tread1:count:45814
    1
    Tread2:code:30086540
    Tread2:count:61907
    2
    Tread2:code:222755065
    Tread2:count:73802
    3
    Tread1:code:1370187659
    Tread1:count:105263
    4
    Tread2:code:327698381
    Tread2:count:143654
    5
    Tread2:code:1745215832
    Tread2:count:175517
    6
    Tread2:code:1609797262
    Tread2:count:178438
    7
    Tread2:code:1406244557
    Tread2:count:178628
    8
    Tread2:code:476907224
    Tread2:count:219082
    9
    Tread1:code:1012470049
    Tread1:count:226958
    10
    Tread2:code:345268840
    Tread2:count:245758
    11

    看起来没有任何的规律,这时我就觉得有点茫然了,没办法,于是我开始去扒源码
    Object源码地址
    在Object中我们可以看到hashCode实际上是调用了IHashCode

    static JNINativeMethod methods[] = {
        {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
        {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
        {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
        {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
        {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
    };
    

    然后在jvm.cpp找到了IHashCode实际调用的是FastHashCode

    JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
      JVMWrapper("JVM_IHashCode");
      // as implemented in the classic virtual machine; return 0 if object is NULL
      return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
    JVM_END
    

    接着扒FastHashCode,FastHashCode在synchronizer.cpp

    intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
      if (UseBiasedLocking) {
        // NOTE: many places throughout the JVM do not expect a safepoint
        // to be taken here, in particular most operations on perm gen
        // objects. However, we only ever bias Java instances and all of
        // the call sites of identity_hash that might revoke biases have
        // been checked to make sure they can handle a safepoint. The
        // added check of the bias pattern is to avoid useless calls to
        // thread-local storage.
        if (obj->mark()->has_bias_pattern()) {
          // Box and unbox the raw reference just in case we cause a STW safepoint.
          Handle hobj (Self, obj) ;
          // Relaxing assertion for bug 6320749.
          assert (Universe::verify_in_progress() ||
                  !SafepointSynchronize::is_at_safepoint(),
                 "biases should not be seen by VM thread here");
          BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
          obj = hobj() ;
          assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
        }
      }
    
      // hashCode() is a heap mutator ...
      // Relaxing assertion for bug 6320749.
      assert (Universe::verify_in_progress() ||
              !SafepointSynchronize::is_at_safepoint(), "invariant") ;
      assert (Universe::verify_in_progress() ||
              Self->is_Java_thread() , "invariant") ;
      assert (Universe::verify_in_progress() ||
             ((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant") ;
    
      ObjectMonitor* monitor = NULL;
      markOop temp, test;
      intptr_t hash;
      markOop mark = ReadStableMark (obj);
    
      // object should remain ineligible for biased locking
      assert (!mark->has_bias_pattern(), "invariant") ;
    	//mark是对象头
      if (mark->is_neutral()) {
        hash = mark->hash();              // 取出hash值(实际上可以理解为缓存,有就直接返回,没有就生成一个新的)
        if (hash) {                       // if it has hash, just return it
          return hash;
        }
        hash = get_next_hash(Self, obj);  // 这是生成hashcode的核心方法
        temp = mark->copy_set_hash(hash); // merge the hash code into header
        // use (machine word version) atomic operation to install the hash
        test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
        if (test == mark) {
          return hash;
        }
        // If atomic operation failed, we must inflate the header
        // into heavy weight monitor. We could add more code here
        // for fast path, but it does not worth the complexity.
      } else if (mark->has_monitor()) {
        monitor = mark->monitor();
        temp = monitor->header();
        assert (temp->is_neutral(), "invariant") ;
        hash = temp->hash();
        if (hash) {
          return hash;
        }
        // Skip to the following code to reduce code size
      } else if (Self->is_lock_owned((address)mark->locker())) {
        temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
        assert (temp->is_neutral(), "invariant") ;
        hash = temp->hash();              // by current thread, check if the displaced
        if (hash) {                       // header contains hash code
          return hash;
        }
        // WARNING:
        //   The displaced header is strictly immutable.
        // It can NOT be changed in ANY cases. So we have
        // to inflate the header into heavyweight monitor
        // even the current thread owns the lock. The reason
        // is the BasicLock (stack slot) will be asynchronously
        // read by other threads during the inflate() function.
        // Any change to stack may not propagate to other threads
        // correctly.
      }
    
      // Inflate the monitor to set hash code
      monitor = ObjectSynchronizer::inflate(Self, obj);
      // Load displaced header and check it has hash code
      mark = monitor->header();
      assert (mark->is_neutral(), "invariant") ;
      hash = mark->hash();
      if (hash == 0) {
        hash = get_next_hash(Self, obj);
        temp = mark->copy_set_hash(hash); // merge hash code into header
        assert (temp->is_neutral(), "invariant") ;
        test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
        if (test != mark) {
          // The only update to the header in the monitor (outside GC)
          // is install the hash code. If someone add new usage of
          // displaced header, please update this code
          hash = test->hash();
          assert (test->is_neutral(), "invariant") ;
          assert (hash != 0, "Trivial unexpected object/monitor header usage.");
        }
      }
      // We finally get the hash
      return hash;
    }
    

    get_next_hash

    static inline intptr_t get_next_hash(Thread * Self, oop obj) {
      intptr_t value = 0 ;
      //随机获取一个
      if (hashCode == 0) {
         // This form uses an unguarded global Park-Miller RNG,
         // so it's possible for two threads to race and generate the same RNG.
         // On MP system we'll have lots of RW access to a global, so the
         // mechanism induces lots of coherency traffic.
         value = os::random() ;
      } else
      //根据内存地址计算一个
      if (hashCode == 1) {
         // This variation has the property of being stable (idempotent)
         // between STW operations.  This can be useful in some of the 1-0
         // synchronization schemes.
         intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
         value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
      } else
      //返回1
      if (hashCode == 2) {
         value = 1 ;            // for sensitivity testing
      } else
      //不太清楚什么意思 求大佬解释
      if (hashCode == 3) {
         value = ++GVars.hcSequence ;
      } else
      //直接返回内存地址
      if (hashCode == 4) {
         value = cast_from_oop<intptr_t>(obj) ;
      }
      //这是一种生成随机数的一种算法
       else {
         // Marsaglia's xor-shift scheme with thread-specific state
         // This is probably the best overall implementation -- we'll
         // likely make this the default in future releases.
         unsigned t = Self->_hashStateX ;
         t ^= (t << 11) ;
         Self->_hashStateX = Self->_hashStateY ;
         Self->_hashStateY = Self->_hashStateZ ;
         Self->_hashStateZ = Self->_hashStateW ;
         unsigned v = Self->_hashStateW ;
         v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
         Self->_hashStateW = v ;
         value = v ;
      }
    
      value &= markOopDesc::hash_mask;
      if (value == 0) value = 0xBAD ;
      assert (value != markOopDesc::no_hash, "invariant") ;
      TEVENT (hashCode: GENERATE) ;
      return value;
    }
    

    到了这里我们基本上就知道了hashcode是怎么生成的了,实际上在jdk1.8在是用的第五种生成方式,我们可以在Linux系统下输入:java -XX:+PrintFlagsFinal -version|grep hashCode命令查看
    在这里插入图片描述
    ok,接下来我们来分析一下第5种方式,看到这个代码我的第一反应是懵逼的,那个什么_hashStateX是个什么鬼?
    我们来看一下thead.cpp里面是怎样定义的:

    // thread-specific hashCode stream generator state - Marsaglia shift-xor form
      _hashStateX = os::random() ;
      _hashStateY = 842502087 ;
      _hashStateZ = 0x8767 ;    // (int)(3579807591LL & 0xffff) ;
      _hashStateW = 273326509 ;
    

    在thread里面定义了一个随机数,三个常数,通过这四个数根据上述的算法来生成hashcode。

    具体原理请参考论文:Xorshift RNGs

    因为在上述算法在,需要得到线程里面的一个随机数作为一个初始值,上述算法在前后具有因果关系,后面的结果是根据前面的结果推算而来的,因此对于相同的线程来说,在某种意义上,对象的hashcode的生成是和顺序有关的。

    为什么主函数里面的hashcode的生成一直是有序的呢?我猜测是主线程里面的——hashStateX是一个固定值(未证实)

    可见hashcode在1.8中和内存地址是无关的,与所在线程(或者说生成的一个随机数)以及生成顺序有关

    展开全文
  • hashCode

    千次阅读 2019-02-22 08:19:49
    hashCode() hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的; 如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象...

    hashCode()

    hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
    如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
    如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
    两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”

    ​ hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的

    解决hash碰撞方法

    https://blog.csdn.net/lppl010_/article/details/80839160

    开放定址法

    当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中,通用的再散列函数形式:
    Hi=(H(key)+di)% m i=1,2,…,n
    其中H(key)为哈希函数,m 为表长,di称为增量序列

    增量序列的取值方式不同,相应的再散列方式也不同
    线性探测再散列
    二次探测再散列
    伪随机探测再散列

    再哈希法

    ​ 同时构造多个不同的哈希函数:
    Hi=RH1keyi=12kHi=RH1(key)Hi=RH2(key) Hi=RH1(key) i=1,2,…,k 当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生 不易产生聚集,但增加了计算时间
    当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生
    不易产生聚集,但增加了计算时间

    链地址法

    将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行
    适用于经常进行插入和删除的情况

    建立公共溢出区

    ​ 将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

    展开全文
  • HashCode

    2019-10-04 09:28:13
    HashCode hashcode /*哈希值:是一个十进制的整数,由系统随机给出(就是一个对象的地址值,是一个逻辑地址,是模拟出来得到的地址,不是实际地址) 在object类中有一个方法,可以获得对象的哈希值 int hashcode...
  • Hashcode

    2016-01-11 18:21:36
    Hashcode 1) 关于object类的equals方法的特点: a) 自反性:x.equals(x)应该返回true b) 对称性:x.equals(y)为true那么y.equals(x)应为true, c) 传递性:x.equals(y)为true,并且y.equals(z)为true,那么x....

空空如也

1 2 3 4 5 ... 20
收藏数 27,333
精华内容 10,933
热门标签
关键字:

hashcode