1. 考虑容器的可替换性
package com.learn.dp.iterator;
/**
* Collection叫集合,是一个统一的集合,所有的实现类都要实现这个接口
* 有了接口之后呢,我们就用接口的方式统一了,凡是实现了我这个接口的子类,
* 肯定有add和size方法,
* @author Leon.Sun
*
*/
public interface Collection {
/**
* 我就不写public了,因为interface里面的方法都是public
*/
void add(Object o);
int size();
}
package com.learn.dp.iterator;
/**
* 我们让ArrayList来实现Collection接口
* @author Leon.Sun
*
*/
public class ArrayList implements Collection {
/**
* 我在这里定义一个数组,模拟可以装任意多个对象的容器
* 我们现在 一个数组往里头装任何的对象,加入这个数组里面能装10个,
* 一旦装满了之后,我就在这个数组的基础上扩展一些新的空间,一旦装满了就再扩展新的空间
* 这个时候我就可以往里头装任意多个对象了,所以这个写法就简单了,
*/
Object[] objects = new Object[10];
/**
* 默认情况下一个都没装,index代表下一个空位置在哪里
* 最开始一个都没有装,当你装的时候就在index这个位置上
* 每当我们添加一个新的进来的时候,让index++
*/
int index = 0;
/**
* 父类引用指向子类对象
* 第一个容器我用数组来模拟,往里装的时候要判断一下,现在装到哪了,
*
*/
public void add(Object o) {
/**
* 一旦你加到index==objects.length的时候,
* 再往里面添加新对象的时候,超过他的容量了,这个时候把原来的数组做扩展
*/
if(index==objects.length) {
/**
* 当一个数组的容量不够使了,我就new个新数组,新数组的容量是老数组的两倍
* 我们这么写严格来讲不是很科学,两倍两倍一直乘下去话数量级会非常大,根据原来有多长再加多少,
* 不过我们的重点并不是放在这个地方,所以我采取最简单的方式,把原来的数组copy到新数组里面,
*
*/
Object[] newObjects = new Object[objects.length * 2];
/**
* 这里用到了System类的静态方法
* 这个方法要是不理解去读API文档,其实每次用到arraycopy文档的时候总想骂SUN两句
* 如果按照JAVA里面的变量驼峰标识风格的话,C一般大写才对,可是这个C就是小写
* String类中的有一个substring第二个应该是大写,结果他还是小写,个人柑橘非常的别扭
* SUN有时候这些细节上不是非常的到位,很庆幸的是JAVA已经被收购了,已经不控制在SUN手里了
* 这个世界感觉美好了很多
* 第一个参数objects原来的数组
* 第二个参数从哪里开始
* 第三个参数拷贝到哪个数组去
* 第四个参数目标参数从哪里开始
* 第五个参数是一共拷贝多少个
* 原来数组满了,所以一共拷贝这么多个
*/
System.arraycopy(objects, 0, newObjects, 0, objects.length);
/**
* 所以原来的引用就指向了新的数组
*/
objects = newObjects;
}
objects[index] = o;
/**
* 这样index就记录了我装了多少个对象了
* 每当我添加一个新对象,index都要往上加
*/
index++;
}
/**
* 作为一个容器别人一般会问你装了多少个东西了
* 调用它的时候告诉容器装了多少个了
*/
public int size() {
/**
* 装了index个了
*/
return index;
}
}
package com.learn.dp.iterator;
/**
* LinkedList也让他去实现Collection
* @author Leon.Sun
*
*/
public class LinkedList implements Collection {
Node head = null;
/**
* 所以你会发现这样一个问题,当我们添加新节点的时候,如果我们知道哪个节点是最后一个,
* 那就爽很多了,因为我们只要让最后一个拉住我们的小手就可以
* 所以我们可以这么写
*/
Node tail = null;
/**
* 要有一个东西记录到底有多少个元素了
* 其实这个size是冗余数据,你不记录也可以
* 但是我们有一个冗余数据,没添加一个就让他加加
* 最后只要return size就可以了
*/
int size = 0;
/**
* 当我们往链表里添加一个节点的时候,这个时候我们该怎么做,
* 当然你要做各种判断,第一个如果head本身就是空的话,那么作为你新添加进来的内容,
* 它就是第一个节点,添加进来新的Object
*
* 加入我们已经有了一个节点了,next指向一个空值,当我们再往里面添加一个节点的时候,
* 把新的节点放在右边,把前面的next指向它,如果还有第三个,新加进来的就指向空值
*
* @param o
*/
public void add(Object o) {
/**
* 真正的内容是传进来的o,它的next是没有,所以只能传一个空值
*/
Node n = new Node(o,null);
/**
* 如果添加进来的是第一个节点,那head就等于n
* head等于空,head等于n,tail也等于n
* 你是第一个的话你既是头也是尾,如果tail不等于空,那就是一回事
*/
if(head==null) {
head = n;
tail = n;
}
/**
* 把tail的这只小手
*/
tail.setNext(n);
/**
* tail要变成本身加入进来的节点
* 这样一个新的节点就加入进来了
*/
tail = n;
size++;
}
public int size() {
return size;
}
}
package com.learn.dp.iterator;
public class MainTest {
public static void main(String[] args) {
/**
* 该用LinkedList
* 我ArrayList用腻了,我就马上改成LinkedList,其他的代码不用变
* 我现在并没有任何一种机制来约束说这个方法就叫add,加入LinkedList里面的
* 方法不叫add,叫addObject,那大家想一下,你从ArrayList换成LinkedList的时候
* 那下面的话一定会发生改变,所以我们有必要定义这样一种机制来把他们两个公开的一些方法给他统一起来
* 那我想大多人人能够想到他的解决方案
*
* 我在定义定义具体容器的时候,
*/
// LinkedList ll = new LinkedList();
/**
* 一旦我不满意了,我就换成LinkedList,放心下面一定都不会变
* 原因是因为我通过c来调的,通过c来调只能看到Collection的接口
* 只能看到add和size方法,反正你是具体的子类,一定会实现这两个方法,
* 下面我只是针对接口编程,针对接口编程就不用考虑我到底的具体实现类是什么,
* 所以你会发现你的程序会更灵活,因为你要替换其他实现类的时候,只需要换一个地方
* 如果你把这个东西放在配置文件里,你甚至连代码都不用改,不知道刚刚讲的有没有明白,
* 这就是他带来的好处,面向接口编程,我们现在针对Collection接口编程,它具体的
* 实现我不管他,代码不用变,可以灵活的替代具体的实现,我们定义这个就更好了,
* 不过新的问题马上就要来了,我们考虑容器的可替换性,我想把这个容器的元素给遍历出来,
* 把他们都给我遍历出来,你该怎么做,
*
*/
Collection c = new ArrayList();
for(int i=0;i<15;i++) {
c.add(new Cat(i));
}
System.out.println(c.size());
}
}