文章疑惑点

 

 

18和27(对应1,2,3)个人的理解。

 

 

18. 成员变量与局部变量的区别有那些

  1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰;

  2. 从变量在内存中的存储方式来看:如果成员变量是使用 static修饰的,那么这个成员变量是属于类的,如果没有使用使用 static修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存

  3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。

  4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。

  变量分为局部变量和成员变量,成员变量分为类变量和对象(实例)变量。

27. hashCode 与 equals (重要)

面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”

hashCode()介绍

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

为什么要有 hashCode

我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

hashCode()与equals()的相关规定

  1. 1)如果两个对象相等,则hashcode一定也是相同的

  2. 2)两个对象相等,对两个对象分别调用equals方法都返回true

  3. 3)两个对象有相同的hashcode值,它们也不一定是相等的

  4. 4)因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

  5. 5)hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

原始没有改写的对象的equals和==意义是一样的,唯一区别一个是符号比较一个是方法比较。重写后通常都把equals比较数值相同,==比较地址相等(如String)。equals即就是相同的东西就返回true(相同的东西只是相似不是同一个东西),==就是相等的东西才返回true(是同一个东西)。

所以上面的1,2,3,4,5中的相等我会理解成相同来解释。

1)如果两个对象相同,则hashcode一定也是相同的

这里其实比较两个对象如果它们的值相同,那么他们的hashcode也是一定相同的,这里你可以理解成java约束定的规则,实际上可以不相同,先告诉你我们实际上可以不相同的情况,然后再告诉你为什么最后需要这样约束,如下面我模仿String改写的例子,重写了equals()没有重写hashcode(),看代码最后位置。

/**
 * 
 */
package cn.ubiton.com.upload;
/**   
* @author jueying:   
* @version 创建时间:2019年2月27日 下午6:02:13   
* 类说明   
*/
/**
 * @author jueying
 *
 */



public class StringSample {
	 private final char value[];
	 //private int hash;
	 
	 public StringSample(String value){
		 this.value=value.toCharArray();
	 }
	 
	 public int length(){
		 return this.value.length;
	 }
	 
	 public char[] toCharArray() {
	        // Cannot use Arrays.copyOf because of class initialization order issues
	        char result[] = new char[value.length];
	        System.arraycopy(value, 0, result, 0, value.length);
	        return result;
	    }
	
	@Override
	public boolean equals(Object anObject) {
	    if (this == anObject) {
	    	System.out.println("比较的两个对象是同一个对象,因为他们的地址相同");
	        return true;
	    }
	    if (anObject instanceof StringSample) {
	    	StringSample anotherString = (StringSample) anObject;
	    	System.out.println("对象"+this+"的hashCode值是"+this.hashCode());
	        int n = value.length;
	        if (n == anotherString.length()) {
	            char v1[] = value;
	            char v2[] = anotherString.toCharArray();
	            int i = 0;
	            while (n-- != 0) {
	                if (v1[i] != v2[i])
	                        return false;
	                i++;
	            }
	            return true;
	        }
	    }
	    return false;
	}
	
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		StringSample b=new StringSample("abc");
		StringSample a=new StringSample("abc");
		System.out.println("对象a的地址:"+a.hashCode());
		System.out.println("对象b的地址:"+b.hashCode());
		System.out.println(a.equals(b));
	}

}

输出:

对象a的地址:24562387
对象b的地址:33431531
对象cn.ubiton.com.upload.StringSample@176cad3的hashCode值是24562387
true

如果这样写是不是就是两个对象相同,但是他们的hashCode是不一样的,为什么上面一定说它们的hashCode一定相同呢?

那是因为java已经规定了这样的规则,如果你重写了Object的equals方法,那么你就必须重写hashCode方法。下面是java api的解释:

 

我们知道所有对象都是继承了Object,那么我们不重写的情况下都会默认使用从Object继承而来的方法,Objec的hashCode方法是一个native的方法(这个具体细节以后看),它是返回对象存放的地址值。那如果我们重写了equals没有重写hashCode那么即使两个对象equals是相等的,那么他们的hashCode仍然可以不相等,就是因为定了规则所以才会有上面那句话如果两个对象相同,那么他们的hashCode一定相等。但是java为什么需要强制约束呢(如果重写equals必须重写hashCode)?下面看下面解释。用HashMap来讲解,顺便自己也复习下这里的知识点。

/**
 * 
 */
package cn.ubiton.com.upload;


import java.util.HashMap;
import java.util.Map;


	public class HashMapSimple
	{

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
				Map<String,Object> map=new HashMap<String,Object>();//JDK1.7
				map.put("key1", 1);//table[7]
				map.put("key2", 2);//table[6]
				map.put("key3", 3);//table[1]
				
				
				//StringSample重写了equals没有重写hashCode
				StringSample simple1 = new StringSample("abc");
				StringSample simple2 = new StringSample("abc");
				
				System.out.println(simple1.equals(simple2));//这里返回是true,在业务上来说我们会把他们当作两个相同的对象
				
				//进行如下操作我们期望的是hashMap中只存在一个值 但是实际上我们对于两个相同的值4都插入到了hashMap中
				//因为toString方法会调用StringSample的hashCode方法,我们没有重写hashCode,会调用从父类Object继承而来的hashCode
				//所以hashCode返回的是两个对象的存放地址,两个不同的对象地址不同,那么他们就会当做两个对象插入,出现了值重复的情况,这样就会有问题
				map.put(simple1.toString(), 4);
				map.put(simple2.toString(), 4);

				System.out.println("map大小:" + map.size());
				
				for (java.util.Map.Entry<String, Object> entry : map.entrySet()) {
					/*
					 * 输出:
					 * 
					 */
					System.out.println(entry.getKey() + ":" + entry.getValue());
				}
						
			
		
	}

}

如下因为String重写了hashCode和equals,所以不会出现上面的情况

/**
 * 
 */
package cn.ubiton.com.upload;


import java.util.HashMap;
import java.util.Map;


	public class HashMapSimple
	{

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Map<String,Object> map=new HashMap<String,Object>();//JDK1.7
		map.put("key1", 1);//table[7]
		map.put("key2", 2);//table[6]
		map.put("key3", 3);//table[1]
		
		
		//String重写了equals和hashCode
		String simple1 = new String("abc");
		String simple2 = new String("abc");
		
		System.out.println(simple1.equals(simple2));//这里返回是true,在业务上来说我们会把他们当作两个相同的对象
		
		//进行如下操作我们期望的是hashMap中只存在一个值 因为String重写了hashCode,所以两个对象返回的hashCode是一样的
		//因为toString方法会调用String的hashCode方法,重写hashCode,不会调用从父类Object继承而来的hashCode
		//所以hashCode返回的值相等,那么他们就会当做同一个对象插入,不会出现值重复的情况,这样就没有问题
		map.put(simple1.toString(), 4);
		map.put(simple2.toString(), 4);

		System.out.println("map大小" + map.size());
		
		for (java.util.Map.Entry<String, Object> entry : map.entrySet()) {
			/*
			 * 输出:
			 * 
			 */
			System.out.println(entry.getKey() + ":" + entry.getValue());
		}
				
			
		
	}

}

 

补充:HashMap是table数组(entry[0],entry[1],entry[2])+链表(Entry->Entry->Entry)构成的,

通过hash函数根据key的hashCode算出hash值,然后

hash & (table.length-1);得出bucketindex,一旦产生hash冲突,就会调用equals和bucketIndex处开始的链表中的结点比较是否存在key,如果存在就返回之前旧的数值,再进行覆盖。如果不存在key值,那么就会当做一个新的结点值插入。代码如下

/**
 * 
 */
package cn.ubiton.com.upload;


import java.util.HashMap;
import java.util.Map;


	public class HashMapSimple
	{

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// TODO Auto-generated method stub
				Map<String,Object> map=new HashMap<String,Object>();//JDK1.7
				map.put("key1", 1);//table[7]
				map.put("key2", 2);//table[6]
				map.put("key3", 3);//table[1]
			    map.put("Aa", 4);//table[4]
				map.put("BB", 4);//制造冲突 table[4],使用前插法插入结点,那么输出时BB结点在Aa的前面
				
				System.out.println("map大小:"+map.size());
				
				for(java.util.Map.Entry<String, Object> entry:map.entrySet()){
					/*输出:
					    map大小:5
						key3:3
						BB:4
						Aa:4
						key2:2
						key1:1
						true
						a的hashcode:4
						b的hashcode:4
					*/
					System.out.println(entry.getKey()+":"+entry.getValue());
				}
				
				//Integer重写了equals和hashCode
				Integer a=(Integer) map.get("Aa");
				Integer b=(Integer) map.get("BB");
				/*
				a的hashcode:4
				b的hashcode:4
				*/
				System.out.println(a.equals(b));
				System.out.println("a的hashcode:"+a.hashCode());
				System.out.println("b的hashcode:"+b.hashCode());
				
						
			
		
	}

}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值