关键字:链表,数据结构,接口,内部类,泛型,私有属性,构造方法
最近复习了一遍Java的基础知识,正好使用这个算法经典案例来练习一下,接下来代码中会用到大部分的基础知识,作为Java基础的总结。
接口定义所有方法的标准
要求一个IList接口规范需要实现的方法,使用泛型定义类型,要求的实现的方法有:
1.数据增加
2.获取集合个数
3.空集合判断
4.返回集合数据
5.根据索引取得数据
6.修改指定索引数据
7.判断数据是否存在
8.数据删除
9.清空链表
interface IList<E>{
public void add(E data) ;//增加数据
public int getSize() ;//获取集合个数
public boolean isEmpty() ;//判断是否是空集合
public Object[] toArray () ;//获取对象数组
public E getData(int index) ;//通过索引获取数据
public void setData(int index , E data) ;//根据索引修改相应位置的数据
public boolean contains(E data) ;//判断数据是否存在
public void remove(E data) ;//数据删除
public void clean() ;//清空整个链表
}
接口实现类
将链表的节点定义为一个内部类,方便访问其私有属性。
class ListImpl<E> implements IList<E>{
private class Node{
private E data ;
private Node next ;
public Node(E data){
this.data = data ;
}
/**
* 处理外部类提交的新节点,判断root节点之后是否为空;
* 若不为空则循环调用此方法直到找到合适的位置;
* @param newNode [待存放的新节点]
*/
public void addNode(Node newNode){
if(this.next == null)
this.next = newNode ;
else
this.next.addNode(newNode) ;
}
/**
* 将调用此方法的节点的数据传入,外部类中的returnData[]属性
* 如果此节点的next属性不为空则递归调用此方法
*/
public void toArrayNode(){
ListImpl.this.returnData[ListImpl.this.foot++] = this.data ;
if(this.next != null)
this.next.toArrayNode() ;
}
/**
* 外部类中从根节点开始调用此方法;
* 如果此时的脚标与传入的索引值相同就返回此节点的data属性值
* 每次递归调用此方法时将脚标自增1
* @param index [待取得数据的索引值]
* @return data [索引处的数据]
*/
public E getNode(int index){
if(ListImpl.this.foot++ == index)
return this.data ;
else
return this.next.getNode(index) ;
}
/**
* 与getNode()方法类似
* @param index [待修改数据的索引值]
* @param data [修改后的数据]
*/
public void setNode(int index , E data){
if(ListImpl.this.foot++ == index)
this.data = data ;
else
this.next.setNode(index , data) ;
}
/**
* 判断链表中是否存有指定数据
* 判断当前节点是否有后续节点,如果没有
* @param data [description]
* @return [description]
*/
public boolean containsNode(E data){
if(this.data.equals(data))
return true ;
else{
if(this.next == null)
return false ;
else
return this.next.containsNode(data) ;
}
}
/**
* 如果要移除的数据不是根节点则从根节点的下一个节点开始执行此方法
* 如果数据值相同则将上一个节点的next属性设置为本节点的next属性
* 全局计数器--
* 否则从此节点的下一个节点开始递归调用本方法
* 如果查询到某个节点没有next属性,说明查询结束,方法结束
* @param previous [description]
* @param data [description]
*/
public void removeNode(Node previous , E data){
if(this.data.equals(data)){
previous.next = this.next ;
ListImpl.this.count-- ;
}
else{
if(this.next == null){
System.out.println("————查询完毕,无结果————") ;
return ;
}
else
this.next.removeNode(this , data) ;
}
}
}
private Node root ;
private int count ;
private Object returnData[] ;
private int foot ;
/**
* 继承自IList接口,用于增加一个数据;
* 如果传入的数据为空直接结束方法,返回空;
* 否则使用内部类构造方法新建一个Node对象,并将其放入合适的位置;
* 如果根节点为空,就将此节点设为根节点;
* 否则将此节点提交给内部类中的addNode()方法处理。
* 每次成功操作之后计数器自增1 ;
* @param E data [传入的数据]
*/
@Override
public void add(E data){
if(data == null) return;
Node newNode = new Node(data) ;
if(root == null)
this.root = newNode ;
else
this.root.addNode(newNode) ;
count++ ;
}
/**
* 根据计数器获取当前链表长度
* @return this.count [当前链表的长度]
*/
@Override
public int getSize(){
return this.count ;
}
/**
* 根据计数器获取当前链表是否为空
* @return [当前对象是否为空的布尔值]
*/
@Override
public boolean isEmpty(){
return this.count == 0 ;
}
/**
* 将当前链表转换成一个对象数组并返回;
* 如果当前链表为空,直接结束此方法;
* 否则将returnData[]属性初始化为,一个count长度的Object类型的数组
* 将脚标清零
* 从root节点开始一次调用内部类的toArratNode()方法
* @return Object[] [对象数组]
*/
@Override
public Object[] toArray (){
if(this.isEmpty()) return null;
this.foot = 0 ;
this.returnData = new Object[this.count] ;
this.root.toArrayNode() ;
return this.returnData ;
}
/**
* 根据传入的索引值获取对应位置的数据;
* 如果索引值越界则直接结束方法
* 将脚标清零;
* 从根节点开始依次调用获取数据的方法
* @param index [要获取数据的对象的索引值]
* @return E data [查询到的数据]
*/
@Override
public E getData(int index){
if(index >= this.count)
return null ;
this.foot = 0 ;
return this.root.getNode(index) ;
}
/**
* 将传入的索引处的数据设置成指定数据;
* 如果数组越界则直接结束方法
* 处理时将脚标清零,调用内部类中的setNode()方法递归设置数据
* @param index [要修改的数据位置索引]
* @param data [要放入的数据]
*/
@Override
public void setData(int index , E data){
if(index >= this.count) return ;
this.foot = 0 ;
this.root.setNode(index , data) ;
}
/**
* 判断链表中是否存有传入的数据
* 如果传入的参数为空,返回false
* 否则交给内部类中的containsNode()方法处理
* @param data [description]
* @return [description]
*/
@Override
public boolean contains(E data){
if(data == null) return false ;
return this.root.containsNode(data) ;
}
/**
* 移除存储传入数据处的节点
* 首先从根节点开始判断,如果数据相同,将根节点的下一个节点设为根节点
* 否则从根节点的下一个节点开始递归调用
* @param data [description]
*/
@Override
public void remove(E data){
if(this.root.data.equals(data)){
this.root = this.root.next ;
this.count-- ;
}
else
this.root.next.removeNode(this.root , data) ;
}
/**
* 将根节点设为空,全局计数器清零
*/
@Override
public void clean(){
this.root = null ;
this.count = 0 ;
}
}
测试类
由于就是个练手的小项目,所以没有搞得过于自动化,功能可以正常使用就好了
public class ListDemo{
public static void main(String args[]){
IList<String> all = new ListImpl<String>() ;
System.out.println("————程序开始————") ;
System.out.println("————判断链表是否为空————") ;
System.out.println(all.isEmpty()) ;
all.add("第一个节点") ;
all.add("第二个节点") ;
all.add("第三个节点") ;
System.out.println("————获取链表长度————") ;
System.out.println(all.getSize()) ;
System.out.println("————判断链表是否为空————") ;
System.out.println(all.isEmpty()) ;
System.out.println("————将链表转换为对象数组并循环输出————") ;
Object result[] = all.toArray() ;
for(Object obj : result)
System.out.println(obj) ;
System.out.println("————查询索引为2的对象————") ;
System.out.println(all.getData(2)) ;
System.out.println("————查询索引为5的对象(没这个对象)————") ;
System.out.println(all.getData(5)) ;
System.out.println("————将第二个对象设置为\"hello\"————") ;
all.setData(1 , "hello") ;
System.out.println("————再次将链表转换为对象数组并循环输出————") ;
result = all.toArray() ;
for(Object obj : result)
System.out.println(obj) ;
System.out.println("————查询是否有\"hello\"这个对象————") ;
System.out.println(all.contains("hello")) ;
System.out.println("————查询是否有\"world\"这个对象(应该没有)————") ;
System.out.println(all.contains("world")) ;
System.out.println("————清空链表————") ;
all.clean() ;
}
}
附上编译后的执行结果:
执行结果:
D:\Java>javac ListDemo.java
D:\Java>java ListDemo
————程序开始————
————判断链表是否为空————
true
————获取链表长度————
3
————判断链表是否为空————
false
————将链表转换为对象数组并循环输出————
第一个节点
第二个节点
第三个节点
————查询索引为2的对象————
第三个节点
————查询索引为5的对象(没这个对象)————
null
————将第二个对象设置为"hello"————
————再次将链表转换为对象数组并循环输出————
第一个节点
hello
第三个节点
————查询是否有"hello"这个对象————
true
————查询是否有"world"这个对象(应该没有)————
false
————清空链表————