由Contains开始的

今天看到一道题目,感觉挺简单的,顺便看下作者的答案,如下。

去除重复字符并排序

运行时间限制:无限制
内容限制:       无限制
输入:              字符串
输出:              去除重复字符并排序的字符串
样例输入:       aabcdefff
样例输出:       abcdef

答案

 1 public void deleteAndSort(String str){
 2       int len=str.length();
 3        ArrayList<Character> list=new ArrayList<Character>();
 4         for(int i=0;i<len;i++){
 5           char ele=str.charAt(i);
 6           if(!list.contains(ele)){
 7             list.add(ele);
 8           }
 9        }
10        Character[] arr=(Character[])list.toArray(new Character[0]);
11        int size=arr.length;
12        for(int i=0;i<size;i++){
13            for(int j=0;j<size-i-1;j++){
14                   if(arr[j]>arr[j+1]){
15                       char tmp=arr[j];
16                       arr[j]=arr[j+1];
17                       arr[j+1]=tmp;
18                   }
19               }
20        }
21        for(char c : arr){
22          System.out.print(c);
23        }
24     }

其中对第六行的一句话list.contains(ele)感到很奇怪,按理说这个List里面都是对象,即使内容相同,但是它们引用(内存地址)和hashCode应该不是一样的,而contains一般都是通过hashCode 来判断是否包含的,这样的话,通过这句话能够知道是否包含吗?于是去查资料。

ArrayList里面contains的源码为:

 1 public boolean contains(Object o) {
 2     return indexOf(o) >= 0;
 3     }
 4 
 5 
 6  public int indexOf(Object o) {
 7     if (o == null) {
 8         for (int i = 0; i < size; i++)
 9         if (elementData[i]==null)
10             return i;
11     } else {
12         for (int i = 0; i < size; i++)
13         if (o.equals(elementData[i]))
14             return i;
15     }
16     return -1;
17     }

这里面是通过equals来比较是否包含的。

那么equals是通过什么方式进行比较的呢?它与==有什么区别呢?

总的来说有以下两点

1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;

    如果作用于引用类型的变量,则比较的是所指向的对象的地址

2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量

    如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

    诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

由于java对八种基本类型byte int short long float double boolean char 都设计了包装类,并且重写了这些包装类的equals方法和hashCode方法,(这里包括Date和String)这样就可以实现上面程序中通过equals来判断是否包含某个字符。

例如String的equals方法的实现:

 public boolean equals(Object anObject) { 
if (this == anObject) { 
    return true; 
} 
if (anObject instanceof String) { 
    String anotherString = (String)anObject; 
    int n = count; 
    if (n == anotherString.count) { 
char v1[] = value; 
char v2[] = anotherString.value; 
int i = offset; 
int j = anotherString.offset; 
while (n-- != 0) { 
    if (v1[i++] != v2[j++]) 
return false; 
} 
return true; 
    } 
} 
return false; 
} 

这里已经不是对地址进行比较了,而是直接对内容中的字符一个一个的比较了。

其次是hashCode().

根据java官方文档的定义,我们可以抽出成以下几个关键点:

1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;(hashCode的作用)

2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同

这句话可以理解成只要equals相同,hashCode必定相同,但是反过来hashCode相同,equals不一定相同。

3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”

 例如String的hashCode()

 public int hashCode() { 
int h = hash; 
if (h == 0) { 
    int off = offset; 
    char val[] = value; 
    int len = count; 

            for (int i = 0; i < len; i++) { 
                h = 31*h + val[off++]; 
            } 
            hash = h; 
        } 
        return h; 
} 

一般情况下进行判断都是使用equals函数,假如一个类没有重写equals函数和hashCode函数那么在进行判断的时候就会使native 的hashCode进行判断。

例如HashSet中不能有重复的对象,它在添加对象的时候先进行hashCode的判断,假如两个对象的hashCode不同,则直接断定这两个对象不同;假如hashCode相同

,则会跳转到判断是否equals。(注意:hashCode相同但是equals未必是true

    String s1=new String("abc"); 
    String s2=new String("abc");

    Set hashset=new HashSet(); 
    hashset.add(s1); 
    hashset.add(s2);  
    System.out.println(s1==s2);//false 
    System.out.println(s1.equals(s2));//true      Iterator it
=hashset.iterator(); while(it.hasNext()) { System.out.println(it.next()); }

输出:

false

true

abc

假如添加的是自定义的User类,并且这个User类没有重写equals和hashCode方法,结果就是另一个样子

class User
{   String name;   
int age;   public User(String name,int age)   {       this.name=name;     this.age=age;   }  
  public void toString()
  {
    return name;
  
}
}
    User s1=new User(1,"abc");
    User s2=new
User(1,"abc");
    Set hashset=new HashSet(); 
    hashset.add(s1); 
    hashset.add(s2);  
    System.out.println(s1==s2);//false 
    System.out.println(s1.equals(s2));//false      Iterator it=hashset.iterator(); while(it.hasNext()) { System.out.println(it.next()); }
 

输出:

false

false

abc

abc

这里第二个输出的false  因为User类没有重写equals方法,这里equals方法判断的是他们的地址,所以这里是不等的。而abc打印两次,他们的hashCode都不是一样的,肯定打印两次。

说到这里hashCode与内存地址有什么关系呢,假如hashCode是由内存地址产生的,那由对象的一致性原则,hashCode一样,那么它们的内存地址也是一样的,这样他们equals肯定是true,但是事实并不是这样的,事实是equals为true肯定可以推出hashCode相同,但是hashCode相同未必能够推出equals为true。这样就是说hashCode与内存关系不大,参考:hashCode返回的并不一定是对象的(虚拟)内存地址,具体取决于运行时库和JVM的具体实现。

附:

1、List,Map,Set三者的contains的实现

List的contains的实现如上。

Set的Contains的实现:

public boolean contains(Object o) {
    return map.containsKey(o);
    }

其内部使用的是HashTable

而HashMap也是使用同样的办法。

2、标准的基本类型只要值相等,哈希值就相同;

Integer a=10;

Integer b=10;

那么a和b的哈希值就相同。类似的还有Short、Long、Byte、Boolean、String等等

3、同一个对象,与何时运行该程序无关;

哈希值算法中,对象的内存地址不参与运算。因此只要是同一个对象,那么该对象的哈希值就不会改变。

4、关于容器的哈希值

java中常用的容器有List、Map、Set。那么它们的哈希值又有什么特点呢?

假设有如下两个List:

List<String> list1= new ArrayList<String>();

list1.add("item1");

list1.add("item2");

List<String> list2= new ArrayList<String>();

list2.add("item2");

list2.add("item1");

这两个List的哈希值是不一样的。对于List来讲,每一个元素都有它的顺序。如果被添加的顺序不同,最后的哈希值必然不同。

假如有如下两个Map:

Map<String, String> map1= new HashMap<String, String>();

map1.put("a", "1");

map1.put("b", "2");

map1.put("c", "3");

Map<String, String> map2= new HashMap<String, String>();

map2.put("b", "2");

map2.put("a", "1");

map2.put("c", "3");

这两个Map虽然元素添加的顺序不一样,但是每一个元素的Key-Value值一样。Map是一种无序的存储结构,因此它的哈希值与元素添加顺序无关,这两个Map的哈希值相同。

假如有如下两个Set:

Set<String> set1= new HashSet<String>();

set1.add("a");

set1.add("b");

set1.add("c");

Set<String> set2= new HashSet<String>();

set2.add("b");

set2.add("a");

set2.add("c");

类似的,由于Set也是一种无序的存储结构,两个Set虽然添加元素的顺序不一样,但是总体来说元素的个数和内容是一样的。因此这两个Set的哈希值也相同。

其实,Set的实现是基于Map的。我们可以看到,如果将Set中的元素当做Map中的Key,把Map中的value始终设置为null,那么它就变成了一个Set。

Set<String> set1= new HashSet<String>();

set1.add("a");

set1.add("b");

set1.add("c");

Map<String, String> map1= new HashMap<String, String>();

map1.put("a", null);

map1.put("b", null);

map1.put("c", null);

通过实验我最后得到了印证,set1与map1的哈希值相同。

原文地址:https://www.cnblogs.com/maydow/p/4579104.html