12月3日——培训第12天

早上的天气越来越冷了,别的倒没有什么,麻烦的主要是来到教室之后手指被冻得敲打键盘都很不灵活。
最近的日记里面几乎好像全是上课的内容,鲜有自己的一些感受之类的东西了,是不是已经没心情写了呢……大概吧。
袁龙友今天就不来了,他公司里有事情,今天还是田老师上课。

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

上午课程开始:
先是讲一些java基础方面的东西。

class Base
{
 int x = 2;
 int method()
 {
  return x;
 }
}

public class SubBase extends Base
{
 int x = 3 ;
 int method()
 {
  return x ;
 }

 public static void main(String[] args)
 {
  Base b = new SubBase();
  System.out.println(b.x);
  System.out.println(b.method());
 }
}

答案是2和3。//注意这道题目的关键是子类的属性不会覆盖父类的属性!!子类的方法会覆盖父类的方法!
Base b = new SubBase();
这句话是个陷阱,多态针对的是方法,而不是属性,是动态的决定
方法的地址,也就是说子类的x不会覆盖父类的x,b是父类的引用,肯定会
去找Base类的属性。子类中的属性不会覆盖父类中的x,也就是说子类中
有父类的x属性和自己本身的x属性。

多态只是多态这个方法,不是属性,也就是说子类的方法会覆盖父类的方法,
但是子类的属性不会覆盖父类的属性,在子类中如果要调用父类的属性的话,
可以用super.x来调用。

由于我们对引用和对象不是太清楚,所以有必要巩固一下。

1 基本数据类型:int、char、float、double、byte、short、boolean、long
基本数据类型所属的内存空间永远位于堆栈里面,而对象永远位于堆中。

2 对象
 只要是通过new操作符创建出来的都是对象。

3 引用
 是对象的地址,它也是位于堆栈里面的。

包装类的作用:有时必须使用对象,而不能够使用整型值
public class Demo
{
 public static void main(String[] args)
 {
  int ap = 1;
  int bp = 1;
  ArrayList list = new ArrayList();
  list.add(ap); //这是不可以的,因为list的add方法只能添加对象,不能是基本数据类型。

  System.out.println(ap==bp); //打印出true
  Integer a = new Integer(1);
  Integer b = new Integer(1);  //Integer是对int的包装类。
  System.out.println(a==b);   //打印出false
  //ap和bp在堆栈里面,所以等值比较的是数值,a和b是堆栈中的引用,他们指向的是对象的首地址,
  //a和b的等值比较的话,比较的是各自指向的对象的首地址。
  System.out.println(a.equals(b));//打印出true
  //


  Person p1 = new Person(23);       //p1和p2都是32位的引用地址
  Person p2 = new Person(23);
  System.out.println(p1==p2);       //结果是false
  System.out.println(p1.equals(p2));//如果调用的是Object中的equals方法,也就是说
       //如果没有底下的Person类中的equals方法的话,那么结果是false,
       //否则因为底下的Person类中重写了equals方法,这里用的就是Person类中
       //的equals方法,比较的就是值而不是引用地址了,返回的就是true了。

  //每一个对象都有一个equals方法,equals方法来自于Object根类
  //p1.equals(p2)其实就是判断p1和p2的地址是否相同。
  //equals方法默认情况下比较的是两个对象的地址,其实就是obj.equals(obj1)等价于obj==obj1

  //千万注意一个问题!!!!Integer等包装类中的equals方法不是默认的Object中的equals方法,
  //而是重载了这个方法!!也就是像底下的Person类中的equals方法一样!Integer包装类中的
  //equals方法比较的就是对象里面的属性值是否相等!
 }
};

class Person
{
 int age ;

 public Person(int age)
 {
  this.age= age ;
 }
 public int getAge()
 {
  return age ;
 }
 public void setAge(int age)
 {
  this.age=age;
 }

 public boolean equals(Object obj) //自己定义的equals方法,注意它更改了默认的Object的equals方法!!!
 {                                 //这个equals方法比较的是值。
  boolean result = false ;
  if(obj instanceof Person)//如果obj是Person的一个实例,那么执行if体
  {
   Person p = (Person)obj ; //强制类型转换obj为Person
   if(this.age==p.getAge())
    result = true ;
  }
 }
};

//注意上面关键的一点是Object的equals方法和包装类中的equals方法是完全不同的!!!!
//还有,instanceof是个关键字,也就是个二元运算符,不是函数,它的运算后的结果是boolean类型的
//instanceof左边是个对像,右边是个类,用来判断对象是否是类的一个实例。

再来看下面这个问题:

String s1 = "hh";
String s2 = "hh";
String s3 = new String("hh");
String s4 = new String("hh");

System.out.println(s1==s2); //true
System.out.println(s1==s3); //false
System.out.println(s3==s4); //false

注意s1,s2,s3,s4都是引用,代表地址,其实比较的都是地址。
s1和s2这种创建字符串的方法是在堆里面创造了一个池,把"hh"放进去,
    也就是java会把它作为一个字符串的池,s2创建出来的时候会从池子里面拿出"hh"给s2
 不会创建新的空间去存储了。

所以s1==s2就是true了。

总结:1 多态覆盖的是方法,不是属性!
     2 引用、对象、基本数据类型什么时候在堆栈,什么时候在堆里面,其中引用和基本数据类型
  如果在对象中的话,那么就在堆中,而不是在栈中了
  3 equals方法的问题,以及equals方法的重写,Object继承下来的equals方法比较的一定
    是引用地址,不是值。包装类中的equals方法是重写过的等值比较,但是如果你自己写的
    类的话必须重新实现自己类中的equals方法才能实现等值比较,这一点要切记!!!
--------------------------------------------
并不是说每天讲的内容都要100%的掌握,量力而行,田老师每天都会告诉我们哪些是必须掌握的,
因为有的时候会讲的比较深,额外的东西肯定是会讲的,但是那些未必是要求你一定要掌握的,必须
掌握的东西是一定要会的,额外的东西可以看情况而定。

现在是课间休息时间,下节课讲深拷贝和浅拷贝。
--------------------------------------------
深拷贝和浅拷贝:

需要使用下面的类:


class Person
{
 int age ;
 String name;
 public Person(int age,String name)
 {
  this.age= age ;
  this.name = name ;
 }
 public int getAge()
 {
  return age ;
 }
 public void setAge(int age)
 {
  this.age=age;
 }

 public int getName()
 {
  return name ;
 }
 public void setName(String name)
 {
  this.name=name;
 }
 public boolean equals(Object obj) //自己定义的equals方法,注意它更改了默认的Object的equals方法!!!
 {                                 //这个equals方法比较的是值。
  boolean result = false ;
  if(obj instanceof Person)//如果obj是Person的一个实例,那么执行if体
  {
   Person p = (Person)obj ; //强制类型转换obj为Person
   if(this.age==p.getAge())
    result = true ;
  }
 }
};


浅拷贝:
int ap = 1;
int bp = 2;
ap = bp ;  //将bp的值赋给ap

Person p1 = new Person(32,"john");
Person p2 = new Person(33,"king");
//p1 = p2 ; //让p1指向p2的地址,这叫做地址赋值
p1.setAge(p2.getAge()); //下面两句才是真正意义上的对象的拷贝!但是是浅拷贝!!
p1.setName(p2.getName());

System.out.println(p1==p2); //这两个对象是不相等的,这一点勿庸置疑
System.out.println(p1.getName()==p2.getName());//但是这两者的地址确实是相等的,因为是浅拷贝,赋的是字符串name
            //的地址。

对象中的属性直接用“=”赋值叫做浅拷贝,上面的setName注意是把p2中的name地址给了p1中
的name,相当于是简单的等号来赋值,赋了地址的值。p1和p2的name对象就指向了同一个字符串。
---------------------------------------
深拷贝:两个对象中的属性值都是对应相同的,但是呢,他们的属性又都各自独立,不会出现像上面说的
       那种两个对应属性完全相同,地址一样的情况。其实浅拷贝相当于快捷方式的创建,虽然有两个
    文件,但是其实指向的地址其实是一样的。
如果对象里面包含对象,而这个被包含的对象里面还有对象,如果要用深拷贝的话,就有一个递归的问题,
也就是说浅拷贝不会对对象中的属性作更深一步的拷贝

p1.setAge(p2.getAge()); //这句话是深拷贝
p1.setName(p2.getName()); //这句话需要更改一下,因为它是浅拷贝……

第二句话改为 p1.setName(new String(p2.getName())) 也就是得到p2的name字符串,用new String
来包装起来成为一个新的字符串,然后再赋给p1就可以达到深拷贝的目的了。传入的p2.getName()是地址,
传入的是p2字符串的地址,根据这个地址找对应的字符串值,然后开辟一块新的空间来存储。

new String(字符串地址)得到新的字符串对象。

int[] a = {1,2,3};
int[] a = new int[3] ;
a在这里也是引用,在堆栈中,后面的那个int[3]在堆里面。a是有属性的,比如a.length就是。
----------------------------------------
java中的克隆(clone方法):创建对象的副本,也就是深拷贝。注意如果要实现clone方法的话
必须在那个要被克隆的类也就是Person类后面加上implements Cloneable,Cloneable是个空
接口,向jdk标明这个对象可以克隆,但是还得注意:clone

class Person implements Cloneable
{
 int age ;
 String name;
 public Person(int age,String name)
 {
  this.age= age ;
  this.name = name ;
 }
 public int getAge()
 {
  return age ;
 }
 public void setAge(int age)
 {
  this.age=age;
 }

 public int getName()
 {
  return name ;
 }
 public void setName(String name)
 {
  this.name=name;
 }
 public boolean equals(Object obj) //自己定义的equals方法,注意它更改了默认的Object的equals方法!!!
 {                                 //这个equals方法比较的是值。
  boolean result = false ;
  if(obj instanceof Person)//如果obj是Person的一个实例,那么执行if体
  {
   Person p = (Person)obj ; //强制类型转换obj为Person
   if(this.age==p.getAge())
    result = true ;
  }
 }
 //重写Object类中的clone方法。Object类中的clone方法是受保护的,必须重写为public才能用
 //clone方法很特殊,比如:
 public class Demo
 {
  public void a()
  {
   this.clone();//这样是可以的,因为任何类都是直接继承Object类,所以protected方法可以用
   Person p = new Person(22,"john");
   p.clone();//这样是不可以的,不可以间接的在一个类里面用其他对象的clone方法!!
  }
 };

 public Object clone() throws CloneNotSupportedException
 {
 /* Person p = new Person(this.age,this.name) ;
  return p ;*/
  return super.clone(); 
 }
};

 

比如上面的Person类:
Person p = new Person(32,"john");
try
{
 Person p1 = (Person)p.clone();
 System.out.println(p1.age==p.age);  //true ;
 System.out.println(p1.name==p.name); //true ;
 System.out.println(p==p1);  //false ;
 //可见,即便是使用super.clone()方法还是实现的是浅克隆,对象的副本中的属性和主本中的属性
 //其实还是一个地址。
}
catch(CloneNotSupportedException ex)
{
 ex.printStackTrace();
}

其实有的时候深拷贝是必要的,也就是说克隆出另外一个对象,clone()方法是Object根类支持的。
------------------------------------
Object方法讲解:
finalize()方法:垃圾回收之前被调用,这个方法不一定会被调用。垃圾不是一产生就被回收的,
              是虚拟机JVM去作垃圾回收,垃圾回收肯定是回收堆里面的垃圾,但是不是一产生一个
     垃圾就去回收那个垃圾,否则容易产生磁盘碎片,碎片前后都是正在使用的对象,也就是
     对象存储的不连续,也就是说如果碎片大小如果不满足存储要求的话就会闲置不用造成
     浪费。内存碎片对虚拟机影响比较大,假如碎片很多,现在需要一块20K的地方来存储,
     那么虚拟机需要一块一块碎片的去找,看哪个碎片满足20K,才可以把那个东西塞进去,
     这样多次比较的话太耗虚拟机的资源了。
既然每一次回收容易产生碎片,那么虚拟机就换了一种方法来回收垃圾,由于堆是一块连续的存储空间,
所以虚拟机在发现存储空间不够用的时候,才一次性的把前面的所有垃圾全部回收,这样就不用因为一次
回收一个垃圾来给众多的碎片作链表了,而且再往里面存储的话也容易不少。

但是这里也有一个弊病,会在一个指定的时间JVM集中精力去回收众多的垃圾,就像eclipse,使用到一定
的时候就会很慢,这就是在回收垃圾,这时可以重启eclipse来一次性释放所有的内存。

IBM的内存回收远远好于SUN的内存回收。

我们根本没有任何办法来干预内存回收,我们只能用System.gc()方法来建议虚拟机去内存回收,至于虚拟机
是否进行内存回收,是由它根据当时的情况来判断的。所以写程序时不要无事生非的老是用new对象,也就是少
产生无用对象,其实System.gc()在实际开发中也起不了什么作用。

现在来说finalize的问题,它基本上起不了什么作用,因为只有在垃圾回收的时候它才调用,但是呢,
你又不知道什么时候垃圾会被回收,其实它的作用就是在对象要被回收之前告诉你一下。
--------------------------
hasCode:该方法返回哈希码,计算出的该码是代表了外存中的地址
数据库记录存储的散列方式:数据存储的地方是根据哈希函数计算的,计算出的地址就是要存的地方。
散列函数不能保证每次散列出的地址是唯一的,万一散列出的地址和以前的有冲突的话(也就是有数据存在那里了),
那就再散列一次,也就是冲突解决机制。

如果两个对象相同,那么它们必须提供相同的hashCode,因为同一个对象必须散列到同一个地址上去,
但是如果hashCode相同,那么未必这两个对象相同,因为可能他们散列到同一个地址上去了。


----------------------------------
下午会讲一下hashcode的细节,克隆方法和equals方法是必须掌握的,其他的可以当作知识的扩充
注意:getClass方法是用于反射中的

中午还是不精神,想睡的感觉……买了本读者随便翻了翻,感觉还不错,毕竟这么长时间都没有看了……
中午童晓军问了一下他这两天一直在琢磨的文件名读取到数据库中的问题,看来下午要先讲一下这个了
------------------------------------
童晓军的问题:
遍历提取文件夹目录下的所有文件名并且将其都存入数据库中,如果文件太多的话,
就不能把这些文件一次都读取出来,然后再一次插入数据库了,而是应该分批来读取和写入。
比如每次读出30个记录

但是现在有个问题,怎么样保证一次就读30个文件,而且每次读出的不会有重复呢??(文件存储无序的话)
这个问题田老师也不清楚,就先搁下了,汗……

现在继续讲课,怎么样给JVM虚拟机来分配初始内存呢?虚拟机一般的初始内存是64MB左右
java -Xmx512m 主类名称     这样就在运行主类时,给虚拟机加载了512M内存。
其中“-Xmx512m”是eclipse中的虚拟机参数
如果指定-Xms128M则说明最小要分配给128M,-Xmx512m是最大给分配512M
----------------------------------
HashCode的实现细节:
田老师提问了一些上午刚刚讲过的东西,结果好多人都答不上来,这让田老师十分的着急
“你们学不好的话责任是在我,如果这样下去的话事情会十分的棘手,这个精英培训班可能
就失去应有的意义了……”
class Person
{
 int identificationCard ;
 String name ;
 
 pubilc Person(int identificationCard,String name)
 {
  this.name = name ;
  this.identificationCard = identificationCard ;
 }
 public int getName()
 {
  return name;
 }
 public void setName(int name)
 {
  this.name = name;
 }
 
 public int getIdentificationCard()
 {
  return identificationCard;
 }
 public void setIdentification(int identificationCard)
 {
  this.identificationCard = identificationCard;
 }
 public boolean equals(Object obj)
 {
  boolean result = false ;
  if(obj instance of Person)
  {
   Person pp = (Person)obj ;
   if(this.identificationCard==pp.getIdentificationCard()&&
    p.getName().equals(this.name))
   {result = true ;}
  }
  return result ;
 }

  //重写Object中的hashCode()方法,之所以要覆盖hashCode
 //是因为我们已经把equals方法重写了,这样满足equals方法的两个
 //对象返回的hashCode肯定是不相同的,因为那两个对象的地址不同
 //我们的原则是,只要满足了上面的equals方法,那么满足这个方法的
 //两个对象必然要有相同的hashCode!这样存储起来的话根据散列表原理
 //会更快。

 //Object类中的hashCode方法返回的值和地址密切相关,所以可以肯定的说
 //两个不同的对象返回的Object中的hashCode方法必然是不同的!!!

 //既然相等的两个对象身份证号码是相同的,那么完全可以根据身份证
 //号码来作文章,
 public int hashCode()
 {
  //如果仅有一个身份证号码属性时这么返回hashCode就可以了,毕竟直接返回身份证号码不太好
  //这个hashCode方法有一点需要注意:只要是包装类,那么它们的equals方法、hashCode方法
  //全部重写覆盖了Object类中的equals方法和hashCode方法。

  //这时return的是由身份证号码得到的整形包装类的hashCode方法得到的值。
  //这里有一点一定要注意:包装类中的hashCode和Object类中的hashCode是完全不同的!!
  
  //也就是说,只要是对同一个整数进行整型包装,那么不管你生成了几个整型对象,这几个整型
  //对象都必定具备相同的hashCode返回值!!!而不会由于他们的地址不同而得到不同的hashCode值!!
  return new Integer(this.identificationCard).hashCode();

  //如果Person中还有一个姓名属性的话,那么这么返回就可以了:
  return new Integer(this.identificationCard).hashCode()+name.hashCode();

  //也就是说把名字和身份证号码的哈希码值相加。
 }
};


public clas Demo
{
 public static void main(String[] args)
 {
  Object obj = new Object();
  System.out.println(obj.hashCode()); //返回3526198;
  System.out.println(obj.toString());//返回java.lang.Object@35ce36
 }
}

 

------------------------------------------
集合类:
集合分两类:Collection和Map

Collection:
List和Set都继承了Collection

List:add(int index,Object element):如果往第三个位置插入一个元素的话,其他元素顺序
      往下移动,List是有索引的,也就是说它是有序的集合

Set:不包含重复元素的集合,里面最多包含一个null值
 注意:重复元素的含义:满足e1.equals(e2)。

-----------------
List:AbstractList,ArrayList,LinkedList,Vector

1.ArrayList

Person p1 = new Person();
Person p2 = new Person();
ArrayList list = new ArrayList();
list.add(p1); //如果直接加一个对象,会加到这个集合的最后一个位置。
list.add(p1); //可以加两个相同的元素
list.add(0,p2)

Person p = (Person)list.get(0);//返回p2。
System.out.println(p2==p); //值为true

p1 = null; //如果在p1已经加入到集合中的情况下把p1置空的话,那么如果不释放集合的话,那么不会垃圾回收。
但是不可以出现list.add(4,p2);//因为是按照有序的来存储的,不能跳着存储。

在底层ArrayList是通过一维数组来实现的,这个一维数组必然是Object类型,这样才能够保证添加进去的每一个元素
都是对象!
list.add(1)是不行的!!必须list.add(new Integer(1));
这个Object类型的一维数组可能会出现溢出的情况,但是ArrayList是不会出现溢出情况的,那么是如何避免
数组溢出的问题呢?

ArrayList构造方法中有一个是:ArrayList(int initialCapacity)可以指定初始大小。默认情况下ArrayList
的数组容量是10。

for(int i = 0 ; i < 20 ; i ++)
{
 list.add(new Integer(i));
}

如果添加达到了容量限制的话,那么ArrayList就会默认再创建一个容量为之前2倍的存储空间,把刚才的那部分数据
放入这个新创建的空间中继续存储,如果又满了的话还是类似的处理方法。
但是这样倍数的扩展空间会降低效率,ArrayList一般用在容量确定的集合中。而且如果频繁的进行插入和删除
的操作的话也不推荐用ArrayList,毕竟对于数组来说,插入和删除需要牵掣的元素太多。

2 LinkedList
以链表方式来实现的,链表方式存储的各个元素不连续,每个元素除存数据外还存着一个下一个节点的地址(指针),
链表并不存在溢出的问题,只要有下一个节点的地址就一直存下去,而且linkedList的插入删除很方便,不用像
ArrayList那样删一个要把后面的元素都往前面移动或是插入一个元素要把后面的元素都往后面移动。虽然LinkedList
插入删除比ArrayList方便,但是查找元素的话远远没有arrayList方便。

一般用于频繁插入和删除,不经常查找以及数据量不确定的情况。

LinkedList list = new LinkedList();

for(int j = 0 ; j < list.size(); j ++)
{
 System.out.println(list.get(j).toString());
}

3 迭代器
注意!!Set集合中根本就没有get方法!所以没有办法按照上面的循环来取数据。
只有用iterator()方法才可以。当然list中也有iterator方法

Iterator it = list.iterator();
while(it.hasNext)
{
 Object obj = it.next();
 if(obj instanceof Person)
 {
 
 }
 else
 {
  System.out.println(obj);
 }
}
迭代器不需要关心集合是数组还是链表

 

 


Set:AbstractSet,HashSet,LinkedHashSet,TreeSet
Set里面不可以存储相同的元素,也就是说调用对象的.equals方法如果返回值
为true的话,那么不可以插进集合里面去!

hashSet主要需要hashCode散列地址,散列到地址上后,进行equals方法比较
如果返回值为true的话,那么不插入!

LinkedHashSet:多个hash表再进行一下链表操作

TreeSet:红黑树。根节点是黑节点,不能有两个连续的红节点,黑节点可以连续,
         而且叶子节点必须是黑节点。保证了红黑树中任何一个深度之差是n-1,
   具体原理可以参见往上资料。n是红节点的数目。
-----------------------------------------
记住:“给你一些数据,然后你给我构造出一颗二叉排序树来”,这才是典型的大公司笔试题目!
-----------------------------------------

Person p1 = new Person();
p1.setIdentificationCard(1);
p1.setName("aa");

Person p2 = new Person();
p2.setIdentificationCard(1);
p2.setName("aa");

Set set = new HashSet();
set.add(p1);
set.add(p2);//注意:如果我没有在Person类中重写equals方法的话,那么默认是
   //调用的Object中的equals方法,这样一来,p1和p2必然不是一个对象。
   //这样p1.equals(p2)必然返回false!所以p1和p2都可以插入到set中去
//当然了,如果重写了equals方法的话,那么毫无疑问由于p1和p2对象等值,那么重写的
//equals方法会返回true,那么就不可能插入p2了。
Iterator it = set.iterator();
while(it.hasnext())
{
 Person p = (Person)it.next();
 System.out.println(p.name);
}

----------------------------
今天的重点内容,必须掌握的内容
1 多态在属性和方法上面的区别:子类属性不可以覆盖父类的属性
2 clone一个对象的实现步骤,即实现Cloneable接口、重载Object的clone方法,实现
  深拷贝
3 实现一个对象的equals方法和hashCode方法。它们在什么时候比较有用?
4 每个人至少写一个类,实现相同的对象不能同时插入到set中,并且呢,学会Iterator使用。

下午的课程到此结束。
---------------------------------
课后练习:

自己写一个MyArrayList和MyIterator,MyArrayList初始容量是20,
MyArrayList实现List接口,实现里面的增加、删除、修改,MyIterator
继承Iterator,让MyIterator可以迭代自己写的那个MyArrayList。

每个人至少写一个类,实现相同的对象不能加入到set中两次以上,迭代器的使用。

在命令行中要求输入若干个字符串,当输入"end"
时结束,然后呢把他们放到一个linkedList集合中,再用迭代器显示出来。
---------------------------------
**************************************************************************************
**************************************************************************************

现在是晚上了,六点钟,怎么说呢,这12天虽然课程安排的十分的紧凑,但是精神方面还是比较撑得住,
也就是不怎么犯困,这一点还是不错的。

废话也不说了,看看老师的这三个练习吧,先看最后一个题目:
------------
在命令行中要求输入若干个字符串,当输入"end"
时结束,然后呢把他们放到一个linkedSet集合中,再用迭代器显示出来。

作这道题目的话其实有两种想法,首先就是把那些字符串读出来放到String[]
数组中,再有一种选择就是读出来放到LinkedSet集合中,当然题目的要求是放在LinkedSet集合中,
不管怎么样:最最核心的代码是以下几句:

  String[] input = new String[200] ;
  //下面这句话十分的重要,用于把控制台上输入的一行行的字符串以字符流的方式通过
  //readLine方法读取出来然后交给字符串数组或者集合!
  BufferedReader in = new BufferedReader(
        new InputStreamReader(System.in) );
  //in.readLine();
  for(int i = 0 ; ; i ++)
  {
   String line = in.readLine();
   if(line!=null&&line.equals("end"))
   {
    break;
   }
   //System.out.println(line);
   input[i] = line ;
   
  }
  return input ;
//有一点必须提醒注意的是,上面这个返回的input字符串数组中一共有200个元素,其中除了一开始
//的若干个元素是控制台上赋给的字符串值以外,剩下的值都是null值,这在取数据的时候必须切记!!

虽然略显罗嗦,但是为了给日后留下更完整的记忆,把我关于这道题目的幼稚解答赋上,过几年看到后
应该会颇有一番感慨把:

import java.io.*;
import java.util.* ;
public class CommandLine {

 /**
  * @param args
  *
  */
 public LinkedList readLine() throws IOException
 {
  LinkedList set = new LinkedList();
  
  BufferedReader in = new BufferedReader(
        new InputStreamReader(System.in) );
  //in.readLine();
  for(int i = 0 ; ; i ++)
  {
   String line = in.readLine();
   if(line!=null&&line.equals("end"))
   {
    break;
   }
   //System.out.println(line);
   set.add(line);
   
  }
  return set ;
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  CommandLine c = new CommandLine();
  System.out.println("下面请您输入字符串,注意以end结束!");
  try {
   LinkedList list = c.readLine();
   Iterator it = list.iterator();
   System.out.println("下面把刚才输入的字符串都给您打印出来吧:");
   while(it.hasNext())
   {
    String result = (String)it.next();
    System.out.println(result);
   }
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }

}
--------------------------------------------------------------------------------
再来看看第二道题目:
每个人至少写一个类,实现相同的对象不能加入到set中两次以上,迭代器的使用。

这道题目呢……其实说白了,和老师课上讲的题目是一样的,只不过呢,要求你自己再重新写一遍熟悉一下
罢了。这里面有一个问题是需要特别说明的,因为我好像在记录上课的内容时没有特殊强调过这个问题,
现在回想一下,挺有必要把它加上的,即便前面已经有了,这里也值得重复一下子。

这道题目其实是针对于一个我们自己构造的类来说的,本来,我们无论构造出什么样子的类,它都会继承自
根类Object,自然也会有Object的一些方法,但是,Object中的有些方法并不是我们真正应用时所需要的,
比如equals,按照我们正常的理解,我们建立的类的实例对象的equals方法应该是和另一个对象的等值比较,
然而Object中提供的equals方法却和我们的希望背道而驰,它不是等值比较,而是等地址比较。

为了改变这种尴尬的情况,要求我们必须手动重载equals方法。但是题目同时还说了,相同的对象不能加入到
set中两次以上,这是什么意思呢?

其实也很简单,Collection分为两类,一类是list,一类就是set,其中list中允许完全相同的对象重复出现
多次(注意这里所说的相同的对象是指的是它们在堆中的地址是完全相同的),另外一类set则绝对不允许两个
地址相同的对象在里面。那么就有了一个问题,set类是如何防止这种情况的出现呢?

事情是这样的:当set类使用add方法来添加进一个对象p的时候,JVM首先要调用这个p对象的hashCode方法
来得到这个对象的哈希码,然后根据哈希表散列到这个哈希码所指定的内存地址,在这个找到的内存地址中也许
会有一个已经存入set的对象,也许没有。如果没有对象在那个地址的话,事情就很好办了,直接把p存入那个
哈希码所对应的内存地址就ok了,但是如果对象p的哈希码对应的内存地址中有了一个对象那怎么办呢?

假设对象p的哈希码对应的内存地址中已经存了一个对象叫做q,那么JVM就会开始调用p.equals(q)来观察返回值,
如果返回值是true的话,那么p会被抛弃,也就是不会把p插入到这个set中,如果返回值是false的话,那么会
调用哈希表的冲突处理机制找另外一个内存地址把它存进去。本来按照Object类的equals方法,p和q必须具备相同
的内存地址才可以出现返回值为true的情况,但是现在我们重写了该方法,使得p和q如果等值的话(里面属性值
对应相等)也会返回true,所以就保证了题目中所说的,相同的对象或是等值的对象不可以插入到set里面两次以上了。

但是是不是因为我们重写了equals方法就可以达到题目的要求了呢?当然不是,在执行equals方法之前,对象p必须
首先找到q所在的内存地址才可以。假如我们不重写hashCode方法的话,那么即便p和q确实是等值的,但是由于Object类
中的hashCode方法返回的哈希码值和内存地址密切相关,所以p和q的哈希码是绝对不可能相同的,也就是说,p按照Object
类的hashCode方法是绝对不可能得到和q相同的内存地址的,既然得不到q的内存地址,那还谈什么equals方法呢?

所以如果不重写Object类的hashCode方法的话,我们可以模拟一下过程:
首先存q对象,根据Object类的hashCode方法得到q的哈希码,然后找到对应存储的内存地址把q给存进去,然后又来了一个
p对象,p和q是等值的对象。同理根据Object类的hashCode方法得到p的哈希码,由于p和q地址不相同所以根据p的哈希码不可能
找到q这个对象,找不到q,自然就没有了equals方法,于是造成的结果就是把等值的p和q都给存入了set,这当然不是我们想要的。

解决的办法很简单,让等值的对象统统返回一样的哈希码就可以了,如何去作呢?
等值的对象有一个共同的特点,它们的属性值是对应相同的,所以只需要根据属性值返回hashCode则必然可以达到等值对象的
哈希码相同。

这里有一个地方同样需要提醒注意:就是包装类不但重写了equals方法,也重写了hashCode方法,都作到了对于等值的
包装类对象来说,equals方法返回true,而且hashCode返回的哈希码也是相同的,这样就不用考虑两个等值对象的地址
问题了。

答案如下:


import java.io.*;
import java.util.*;

class Person
{
 private int id ;
 private String name ;
 public Person(int id, String name)
 {
  this.id = id;
  this.name = name ;
 }
 public int getId()
 {
  return id ;
 }
 public String getName()
 {
  return name ;
 }
 
 public void setId(int id)
 {
  this.id = id ;
 }
 public void setName(String name)
 {
  this.name = name ;
 }
 
 public boolean equals(Object arg0) {
  // TODO Auto-generated method stub
  boolean flag = false ;
  if(this == arg0)
   flag = true ;
  else if(arg0 instanceof Person)
  {
   Person p = (Person)arg0 ;
   if(this.id==p.getId()&&this.name.equals(p.getName()))
   {
    flag = true ;
   }
  }
  
  return flag ;
 }
 public int hashCode() {
  // TODO Auto-generated method stub
  return (new Integer(id).hashCode()+ name.hashCode());
 }
 
}

public class SetStore {

 /**
  * @param args
  */
 
 public static void main(String[] args) {
  HashSet set = new HashSet();
  Person p1 = new Person(1,"yuanbin");
  Person p2 = new Person(1,"yuanbin");
  set.add(p1);
  set.add(p2);
  
  System.out.println(p1.equals(p2));
  Iterator it = set.iterator();
  while(it.hasNext())
  {
   System.out.println(it.next().toString());
  }
 }

}

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

自己写一个MyArrayList和MyIterator,MyArrayList初始容量是20,
MyArrayList实现List接口,实现里面的增加、删除、修改,MyIterator
继承Iterator,让MyIterator可以迭代自己写的那个MyArrayList。

这道题目最麻烦了,现在8点半了,时间好像有点不太够了……汗啊
==========================================
MyArrayList:
package homework;
import java.util.*;
public class MyArrayList extends ArrayList{
 private Object[] list = new Object[20];
 public Iterator iterator() {
  // TODO Auto-generated method stub
  return new MyIterator(list);
 }
 
 public void add(int arg0, Object arg1) {
  // TODO Auto-generated method stub
  int i ;
  for(i = 0 ; list[i]!=null ; i ++)
  {
   
  }
  if(i == 20)
   System.out.println("数组满了,没法插入了");
  else
  {
   for(int j = i ; j > arg0 ; j --)
   {
    list[j] = list[j - 1];
   }
   list[arg0] = arg1 ;
  }
 }

 public boolean add(Object arg0) {
  // TODO Auto-generated method stub
  boolean flag = false ;
  int i = 0 ;
  for(i = 0 ; list[i]!=null ; i ++)
  {
   
  }//这个循环结束后,得到的下标就是该插入的地方!!
  if(i < 20)
  {
   list[i] = arg0 ;
   flag = true ;
  }
  return flag ;
 }

 public void clear() {
  // TODO Auto-generated method stub
  for(int i = 0 ; i < list.length ; i ++)
  {
   list[i] = null ;
  }
 }

 public Object get(int arg0) {
  // TODO Auto-generated method stub
  return list[arg0];
 }

 public Object remove(int arg0) {
  // TODO Auto-generated method stub
  int j ;
  for(int i = arg0 ; i < list.length ; i ++)
  {
   if(i == 19)
   {
    list[i] = null ;
   }
   list[i]=list[i+1];   
  }
  
  return list[arg0] ;
 }

 public boolean remove(Object arg0) {
  // TODO Auto-generated method stub
  return super.remove(arg0);
 }

 public int size() {
  // TODO Auto-generated method stub
  return list.length;
 }

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub

 }

}
-------------------
MyIterator:

package homework;
import java.util.*;
public class MyIterator implements Iterator {
 Object[] list = new Object[20];
 
 public MyIterator()
 {
  
 }
 public MyIterator(Object[] list)
 {
  this.list = list ;
 }
 
 public boolean hasNext() {
  // TODO Auto-generated method stub
  boolean flag = true ;
  int i ;
  for(i = 0 ; list[i]!=null ; i ++)
  {
   
  }
  if(i == 20)
   flag = false ;
  
  return flag;
 }

 public Object next() {
  // TODO Auto-generated method stub
  return null;
 }

 public void remove() {
  // TODO Auto-generated method stub
  
 }

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub

 }

}

 


 

你可能感兴趣的:(四个月的编程培训经历,equals,object,string,iterator,integer,list)