18和27(对应1,2,3)个人的理解。
18. 成员变量与局部变量的区别有那些
-
从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰;
-
从变量在内存中的存储方式来看:如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的,如果没有使用使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存 -
从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
-
成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 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)如果两个对象相等,则hashcode一定也是相同的
-
2)两个对象相等,对两个对象分别调用equals方法都返回true
-
3)两个对象有相同的hashcode值,它们也不一定是相等的
-
4)因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
-
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());
}
}