收集的java常见问题与自己的理解

来源:互联网 发布:js控制div显示隐藏 编辑:程序博客网 时间:2024/06/17 03:05



j2se知识体系





------------------------------------------

equals() 与 hashcode()

------------------------------------------



参考网址

http://www.programcreek.com/2011/07/java-equals-and-hashcode-contract/


http://blog.csdn.net/lifetragedy/article/details/9751079


http://blog.csdn.net/lifetragedy


========================

作用是什么?

   >>> hash table用来实现快速查找的。那么 hashcode用于快速定位元素,相对于线性查找,效率比较高。 [KLY 需要把数据结构的内容再进行补充]


 这个又涉及到Map的实现。它从实现上是一个二维的数组。类似 map[ hashcode() ] [ equals() ]

查找的过程是,先通过hashcode找到第一维的位置,然后通过equals找到第二维的位置。从而实现元素的定位。可以通过HashMap的get方法,观察这个过程:


-------------------------------------------------code---begin--------------------------------------------------------

public V get(Object key) {

       if (key == null)

           return getForNullKey();

       int hash = hash(key.hashCode());

       for (Entry<K,V> e = table[indexFor(hash, table.length)];  //定位第一维元素

            e != null;

            e = e.next) { //遍历第二维的元素

           Object k;

           if (e.hash == hash && ((k = e.key) == key || key.equals(k)))                            

                   return e.value;

       }

       return null;

   }

----------------------------------------------------code---end------------------------------------------------------  




这种查找的先后顺序决定了equals()与hashcode()这两个方法的关系(相当于实现中的约定):


1. If two objects are equal, then they must have the same hash code.


  如果两个元素是equal的,那么他们的必须hashcode相等。


2. If two objects have the same hashcode, they may or may not be equal.

 

  如果两个元素的hashcode相等,那么他们不一定equal。




一个比喻: 可以理解hash表通过一个个的hashcode来表示不同的”桶“,这些桶里可以放不同的元素。在查找对象的时候,使用hashcode来查找到对应的”桶“,然后再通过equals来定位需要的对象。通过这种方式进行查找,比“把所有对象线性的排列然后一个一个顺序比较”的效率要高。




<<<<  常见问题 >>>>



何时需要重写equals()


    当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。


如何覆写equals()和hashcode()


覆写equals方法

1  使用instanceof操作符检查“实参是否为正确的类型”。

2  对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。

3. 对于非float和double类型的原语类型域,使用==比较;

4  对于对象引用域,递归调用equals方法;

5  对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;

6  对于double域,使用Double.doubleToLongBits(adouble)转换为int,再使用==比较;

7  对于数组域,调用Arrays.equals方法。


覆写hashcode


1. 把某个非零常数值,例如17,保存在int变量result中;

2. 对于对象中每一个关键域f(指equals方法中考虑的每一个域):

3, boolean型,计算(f? 0 : 1);

4. byte,char,short型,计算(int);

5. long型,计算(int)(f ^ (f>>>32));

6. float型,计算Float.floatToIntBits(afloat);

7. double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];

8. 对象引用,递归调用它的hashCode方法;

9. 数组域,对其中每个元素调用它的hashCode方法。

10. 将上面计算得到的散列码保存到int变量c,然后执行result=31*result+c;

11. 返回result。





>>>在存储数据计算hash地址的时候,我们希望尽量减少有同样的hash地址,所谓“冲突”。<<<


    31是个神奇的数字,因为任何数n * 31就可以被JVM优化为 (n << 5) -n,移位和减法的操作效率要比乘法的操作效率高的多,对左移现在很多虚拟机里面都有做相关优化,并且31只占用5bits!


参考

[

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

http://www.azillionmonkeys.com/qed/hash.html

]


比如 7 * 31 = ( 7 * 32 ) - 7 = (7 * 25) - 7


  2的5次方就相当于左移5位。



这里String的hashcode使用素数来产生,是一个比较老的技术。基本思想就是素数与其他数相乘,出现重复的概率比较小。[也有其他方法来产生hashcode,而且效率也比较高]


研究人员通过研究发现31这个数,可以使得hash分布比较均匀,而且冲突比较少。[KLY我觉得应该是通过统计的方式进行研究的]


 



----------------------

comparable接口与comparator

----------------------





--------------------------------

 java实现浅克隆(shallow clone)与深克隆(deep clone)

--------------------------------


浅克隆:从对象A中复制出对象B 时,只复制A中的对象引用C,导致当B操作对应的对象C时,A中的对象C也发生了改变。  


深克隆: 从对象A中复制出对象B时, A中的引用对象C也被对应的复制了出来D,当B操作对象D时,A中的对象C不会发生变化。



Deep Clone的几种方法:

<1> 实现 Serializable 接口。


通过 序列化 和 反序列化 来完成深克隆。。



-------------------------------------------------code---begin--------------------------------------------------------

  ByteArrayOutputStream baos = new ByteArrayOutputStream();  

  ObjectOutputStream oos = new ObjectOutputStream(baos);  

   oos.writeObject(src);  

    oos.close();  

  ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());  

   ObjectInputStream ois = new ObjectInputStream(bais);  

   o = ois.readObject();  

   ois.close();  

----------------------------------------------------code---end------------------------------------------------------  



总结:

当克隆的对象只有基本类型,不含引用类型时,可以用浅克隆实现.     

当克隆的对象含有引用类型时,必须使用深克隆实现.




序列化的问题


Serializable

Externalizable

http://bbs.csdn.net/topics/100019240



还有其他的实现方法

http://stackoverflow.com/questions/2156120/java-recommended-solution-for-deep-cloning-copying-an-instance


----------------------------------------------

谈谈final, finally, finalize的区别

----------------------------------------------


final


意味着它不能再派生出新的子类,不能作为父类被继承。

因此一个类不能既被声明为 abstract的,又被声明为final的。

将变量或方法声明为final,可以保证它们在使用中不被改变。

被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。

被声明为final的方法也同样只能使用,不能重载.


finally

 异常处理函数中用来执行清除操作。

如果抛出一个异常时,先会执行捕捉该异常的catch语句中的内容,最后会执行finally语句中的内容。

在实际应用中,主要用来清理执行过程中用到的变量,比如关闭输入输出流,关闭数据库连接之类的。

在finally中嵌套try-catch-finally语句的问题。

http://stackoverflow.com/questions/3779285/exception-thrown-in-catch-and-finally-clause


try-catch-finally 的处理逻辑是[KLY应该是jvm编译器编译的逻辑]:

当exception (Ex1)从一个程序块抛出来,那么就会到达程序块的外部,这时候,如果外部的程序块抛出了一个新的exception(Ex2),那么之前抛出的Ex1就“被忽略”了,然后这个Ex2继续抛到更外层的程序块中。 当使用 try-catch-finally 来处理的时候,try程序块中抛出的异常,会在catch中进行捕捉,然后执行finally程序块;假设catch中也抛出了异常,这时,finally依然会执行。

---------------------------------code-begin----------------------------------------------

public class Main {

  public static void main(String args[]) throws Exception {

  try {

           System.out.print(1);

          q();

      }

      catch ( Ex1 i ) {

        System.out.print(2);

          throw( new Ex2());

      }

      finally {

          System.out.print(3);

          throw( new Ex1() );

      }


  }

  

  static void q() throws Exception {

      try {

     System.out.print(4);

          throw( new Ex1());

      }

      catch( Exception y ) {

        System.out.print(5);

          throw( new Ex1());

     

      } finally {

          System.out.print(6);

          throw( new Ex2() );

      }

  }

}


class Ex1 extends Exception {}

class Ex2 extends Exception {}

---------------------------------code-end----------------------------------------------

输出结果:    14563Exception in thread "main" me.exception.Ex1


---------------------------------------------------------------------------------------

开始执行时输出1,然后就执行q(),try块中输入4,抛出Ex1的异常,由于Ex1是Exception的子类,所以被catch块捕捉,输出5,

之后catch块抛出Ex1异常,但是没有对应的try-catch来捕捉它,所以就抛到了外部,由于有finally模块,所以会执行finally块,输出6。这时finally块会抛出类型为Ex2的异常,那么之前的catch中抛出的Ex1异常就”被忽略”了,最终q()方法抛出的异常是finally块中抛出的Ex2。这时,回到了main块中,q执行完后,抛出了Ex2类型的异常,但是catch块只捕捉Ex1类型的异常,那么catch块就没有执行,也就不输出2,然后就到了main中finally块中,输出3,然后抛出了Ex1类型的异常。







finalize


GC在检查某个对象没有任何引用指向它时,会执行的一个方法,但不一定是立即执行。

这个方法只会执行一次。

这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。

它是在 Object 类中定义的,因此所有的类都继承了它。

子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。[KLY 这个用法也不一定准确,因为执行finalize方法之后,GC不一定会立刻把对象清除,这个和具体的GC算法有关系,这个方法不建议override进行使用]






-----------------------------------------------------

  Java对象的强、软、弱和虚引用

-----------------------------------------------------


String abc=new String("abc");  //强

SoftReference<String> abcSoftRef=new SoftReference<String>(abc);  //强

WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //强  

abc=null; //软

abcSoftRef.clear();//弱



软引用


软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避 免内存溢出。

具体应用到“资源池”的设计中。


【一个例子】

  雇员信息查询系统,应用场景就是用户有可能查询之前已经查询过的数据。就是过一段不确定的时间(从几秒到几天),会查询之前查询过的数据。

程序的实现方式:

1> 把查询过的雇员信息都放到内存里。

2> 每次用户查询时,都重新从数据库中读取信息,然后再生成对应的java对象,查询完毕后,就删除该对象。

第一种方法很显然会浪费大量的内存,造成内存溢出。

第二种方法执行性能很低,从访问文件、访问网络资源、查询数据库等操作,都是影响系统性能的重要因素。而且每次都会生成新的java对象,如果之前查询的数据还在内存里面,还没有被GC回收,也用不了。

比较理想的场景是:

  用户查询数据时,程序先去看看内存里有没有,如果没有再去其他地方(文件、数据库、网络资源等)拿。 而程序为了防止内存溢出,会定期的把内存里当前没有使用到的对象进行回收,保持系统的稳定性。

  


这个时候,就可以使用SoftReference来实现对应的需求。






-----------------------------------------------------------------------------------------------

在J2EE的bean设计时我们对有些bean需要实现Serializable,为什么?

-----------------------------------------------------------------------------------------------


实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。


0 0
原创粉丝点击