我们的第一个小目标,是做一个链表结构。其实就是之前写的TuziLinkedList,只不过我们不仅仅要存储Customer,还要存储任意的其他对象。
首先,我们复习一下之前写的链表结构:
源代码如下:
package tool; import entity.Customer; import tool.CustNode; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class TuziLinkedList implements Iterator{ public CustNode firstNode; //第一个节点 public CustNode currentNode;//当前的节点 public CustNode nodeForEach; //用于遍历的节点,默认等于第一个节点 //新增的方法 public void add(Customer cst){ //将数据用节点类包装好,这样才能实现下一个数据的指向 CustNode data = new CustNode(cst); //先判断是否是第一个节点 if(this.firstNode == null){ this.firstNode = data; this.currentNode = data; }else{ //不是第一个节点,就指向当前节点的下一个节点,即currentNode.next this.currentNode.next = data; //因为已经指向下一个了,所以当前节点也要移动过来 this.currentNode = data; } } //展示所有节点 public void display(){ //第一步,肯定是展示第一个节点(this其实可以省略的) if(firstNode != null){ System.out.println(firstNode.data.getName()); //然后循环,一直寻找next是否为空 CustNode node = firstNode.next; while(node != null ){ String name = node.data.getName(); System.out.println(name); //循环的最后,再指向下一个节点,继续下一轮 node = node.next; } } } @Override public boolean hasNext() { if(nodeForEach == null && firstNode != null) return true; if(nodeForEach == null) return false; return nodeForEach.next != null; } @Override public Object next() { if(nodeForEach == null && firstNode != null){ nodeForEach = firstNode; return nodeForEach.data; } nodeForEach = nodeForEach.next; return nodeForEach.data; } }
TuziLinkedList类的核心就是一个单链表,里面维护了CustNode节点,代码如下:
package tool; import entity.Customer; public class CustNode{ public Customer data; public CustNode next; public CustNode(Customer data){ this.data = data; } }
我们的TuziLinkedList还实现了Iterator接口,这个接口可以帮助我们遍历TuziLinkedList中的内容。
以上是单链表的一般结构,我们会发现,链表中的每一个元素是一个Object,什么是Object呢?Object是Java里面的一个类,它是所有类的父类。在Java中,所有的类都默认继承自Object。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
这些都是概念上的解释,最多也就面试的时候问问。不过随着现在Java的行情越来越**“卷”**,面试也很少问这么基础的东西了。
随着我们代码的深入,相信你会对继承非常熟练的。
因此,与其我现在一股脑儿给你背书,读概念,还不如在之后的章节中,你自己看看继承都用在了哪些地方,是怎么用的?
回到正题,为什么链表的元素要放Object呢?那是因为,所有的类都会直接,或间接继承自Object。Java里面有一个叫做多态的特性,就是父类引用可以指向子类对象。
比如,我有一个人类,和教师类。
class Person{ } class Teacher extends Person { }
Teacher类用extends关键字,继承了Person类,那么说明Teacher类是Person类的子类对吧?
那么,你可以直接new一个Teacher类的对象:
Teacher t = new Teacher();
还可以这样:
Person p = new Teacher();
这也是允许的,但是需要注意的是,父类引用(p)指向了子类对象new Teacher(),那么p就只能调用Person类中的属性和方法,不能调用子类的。除非你用强制转换,像这样:
Person p = new Teacher(); Teacher t = (Teacher) p;
因为p本来就是Teacher的实例,所以你强制转换是允许的,只要你觉得有必要,就强转吧。
说了这么多,现在相信你明白为什么要用Object了吧,因为Object是所有类的父类,所以你用Object可以接收任意类型的对象啊,就这么简单!
方法重写,重载
刚开始,我们不要考虑太多,就做单链表即可。这一节,我们会遇到方法重写和重载的问题。
1、数据模型改为Object
2、AbstractSequentialList
3、add方法报错了
4、什么是方法重写?
5、重写add方法
6、欣赏原生的add方法
为了让我们的链表可以海纳百川,就把Node换成Object型的。
创建一个新的工具包:
CustNode改为Node,就是通用节点的意思。
package com.tuzi.util; public class Node{ public Object data; public Node next; public Node(Object data){ this.data = data; } }
然后是TuziLinkedList,改为LinkedList。
JDK中的LinkedList继承了AbstractSequentialList类,这是一个抽象类。
啥叫抽象类,就是有abstract关键字的类,这种类有点像接口,就是里面会有一些方法只有声明而没有实现。我们看下AbstractSequentialList,里面就有一些这样的方法,比如:
public abstract ListIterator<E> listIterator(int index);
这种方法就是抽象方法,在之前接口的章节中,我们已经见过它了。
为了学习的深度,我们虽然不需要做到跟Java中的LinkedList一模一样,但是从表面上,也可以去和它一个标准。就是说,Java中的LinkedList继承了AbstractSequentialList,我们的LinkedList也要去继承AbstractSequentialList。
public class LinkedList extends AbstractSequentialList implements Iterator{ }
因为继承了AbstractSequentialList,我们就不得不去实现它里面所有的抽象方法(和接口真的有点像)
所以,我们不得不实现listIterator方法(就是上面介绍的那个)
@Override public ListIterator listIterator(int index) { return null; }
虽然这是标准,但是我们现在,甚至连这个玩意到底干啥的都不知道,那就先放着。
继承了AbstractSequentialList之后,add方法就报错了。这是怎么回事呢?
从提示来看,是void用错了。
原来,AbstractSequentialList还继承了AbstractList。
public abstract class AbstractSequentialList<E> extends AbstractList<E> { }
AbstractList里面有一个方法也叫做add。
public boolean add(E e) { add(size(), e); return true; }
参数是E,是泛型,目测编译器是把这种的也看成方法重写了。我们看下报错:
意思就是冲突啦,这个被认为是方法重写。
继续之前的例子,我们快速学习一下这个知识点。
class Person{ public void eat(){ System.out.println("人在吃饭。。。"); } } class Teacher extends Person { public void eat(){ System.out.println("老师在吃饭。。。"); } }
父类和子类都有一个eat方法,且都没有参数(即参数列表相同),返回值也相同,这就叫做参数重写。
测试代码:
Person p = new Teacher(); Teacher t = (Teacher) p; t.eat();
运行结果:
老师在吃饭。。。
注意,方法重写的前提是:
1.方法名
2.参数列表
3.返回值
以上三者全部相同,唯有方法体不一样。这种的才叫方法重写。
除了方法重写,还有一种叫做方法重载,就是同一个方法名,参数列表不同的情况,比如:
class Teacher extends Person { public void eat(){ System.out.println("老师在吃饭。。。"); } public void eat(String food){ System.out.println("老师在吃" + food ); } }
两个方法都叫做eat,但是第二个eat方法多了一个参数,就是方法重载。注意,方法重载和返回值没有半毛钱关系,这个概念经常会在笔试题里面考。比如,下面这样的写法就是错误的: