标签:hub 关系 java面向对象 nod sem 实现原理 ati 创建 private
首先,就目前学习过的知识而言,如果想要保存多个对象,唯一能想到的就是对象数组。
同时如果该数组可以保存任意的对象,那么可以想到的就是Object型的数组。
Object[] data = new Object[数组长度] ;
但是问题在于,数组是一个定长的线性结构,也就是说虽然对象数组的形式可以满足存放多个对象的需求。不过一旦我们的内容不足或者内容过多都有可能造成内存的浪费(数组空间没有充分利用、数组扩充都会造成内存空间浪费),而具体有多少内容是难以提前预测准确的。
范例:链表的基本雏形
class Node { // 因为只有Node类可以在保存数据的同时设置节点(数据的先后关系)
private Object data ; // 当前真正要保存的数据
private Node next ; // 定义下一个节点
public Node(Object data) { // 车厢中保存数据
this.data = data ;
}
public void setData(Object data) {
this.data = data ;
}
public Object getData() {
return this.data ;
}
public void setNext(Node next) {
this.next = next ;
}
public Node getNext() {
return this.next ;
}
}
public class TestLinkDemo {
public static void main(String args[]) throws Exception {
// 1、封装几个节点
Node root = new Node("火车头") ; // 现在假设存放的数据是String型
Node n1 = new Node("一号车厢") ;
Node n2 = new Node("二号车厢") ;
Node n3 = new Node("三号车厢") ;
// 2、需要设置节点的关系(把车厢串起来)
root.setNext(n1) ;
n1.setNext(n2) ;
n2.setNext(n3) ;
// 3、输出节点
print(root) ;
}
public static void print(Node node) {
if (node != null) { // 表示当前存在有节点
System.out.println(node.getData()) ;
print(node.getNext()) ; // 继续向下取出
}
}
}
总结:在整个链表的实现过程之中,Node类的核心作用在于:保存数据和连接节点关系。但是以上的代码中比较麻烦,因为发现客户端(主方法)需要自己来进行节点的创建以及关系的配置。
所以所谓链表,就是需要有一个单独的类(假设名为Link)来实现Node的数据保存以及关系的配置。
上节讲到的链表的实现是有巨大缺陷的,因为它需要使用者自己来处理数据的保存、数据的关系。所以为了更好地处理,应该追加一个程序类。假设这个类称为Link类。
下面是一个完整的链表设计结构:
但是目前可能还没有能力理解这种复杂程序,所以还是先学习简单的定义:
class Link { // 负责链表的操作
// 定义为私有内部类,表示Node类只为Link类服务
private class Node { // Node类负责保存数据、设置节点
private Object data ; // 要保存的数据
private Node next ; // 定义下一个节点
public Node(Object data) { // Object类:保存任意类的对象
this.data = data ;
}
}
/* ---------下面定义真正的Link类------------ */
}
上面是链表中内部类的定义,链表具体功能的实现之后讲解。
public void add(Object data) {}
想要在链表中实现一个数据的增加操作,应该在链表中提供一个追加的方法,而这个追加方法上的参数应该是Objec类。
public void add(Object data) {
if (data == null) {
return ;
}
}
private Node root ;
如果想要进行数据的保存,那么必须将数据封装在Node节点类中,因为如果没有封装好就无法确认节点的先后顺序。
在内部类add中添加:
Node newNode = new Node(data) ;
if (this.root == null) { // 如果当前没有根节点
this.root = newNode ; // 把第一个节点设置为根节点
} else { // 如果根节点已经存在
// 把此时的节点顺序的处理交给Node类自己完成
this.root.addNode(newNode) ;
}
在内部类Node中添加:
// 第一次调用addNode:this = Link.root
// 第二次调用addNode:this = Link.root.next
// 第三次调用addNode:this = Link.root.next.next
// ······
public void addNode (Node newNode) { // 处理节点关系
if (this.next == null) { // 当前节点下一个节点为空时。可以保存
this.next = newNode ;
} else { // 当前节点的下一个节点不为空时
this.next.addNode(newNode) ; // 下一个节点再调用addNode,直到找到下一个节点为空
}
}
https://github.com/colderThyKiss/LinkedList.git
public int size() {}
在一个链表之中可以保存多个元素的数据,那么为了操作的方便就需要知道其一共保存的数据个数,所以需要有一个计数的统计操作。
count
private int count = 0 ; // 当前的保存个数
this.count++ ;
可以直接取得元素的个数,定义方法:
public int size() { // 取得元素个数
return this.count ;
}
https://github.com/colderThyKiss/LinkedList.git
可以看到,往Link中添加了三个数据后能够检测出数据的个数。
public boolean isEmpty() {}
如果根元素为空,或者保存的元素个数为0,则称为空链表,则isEmpty()方法返回值应该为true。
public boolean isEmpty() {
return this.root == null && this.count == 0 ;
}
调用:
System.out.println(all.size() + " = " + all.isEmpty());
public Object[] toArray() {}
首先链表是一个动态对象数组,那么它当然也可以返回对象数组。但是要想进行数组的返回,首先必须要开辟一个数组(长度为上节中讲解的count
),同时该数组内容的填充应该依次将节点中的数据取出才可以正常完成。
public Object[] toArray() {}
所有在返回的对象数组中的数据都在Node类中,那么就证明这些数据应该在Node类中利用递归来依次取出。那么应该在外部类(Link)中定义一个返回类型的属性。
private Object[] reData ; // 返回类型
private int foot = 0 ; // 操作脚标
······
public Object[] toArray() {
if (this.root == null && this.count == 0) {
return null ;
} else {
// 链表中有数据,则开辟相应长度的数组
// 该数组一定要交给Node类进行处理
this.reData = new Object[this.count] ;
this.foot = 0 ;
this.root.toArrayNode() ;
return this.reData ;
}
}
// 第一次调用toArrayNode:this = Link.root
// 第二次调用toArrayNode:this = Link.root.next
// 第三次调用toArrayNode:this = Link.root.next.next
// ······
public void toArrayNode () { // 处理节点关系
Link.this.reData[Link.this.foot++] = this.data ;
if (this.next != null) { // 现在还有下一个节点
this.next.toArrayNode() ;
}
}
https://github.com/colderThyKiss/LinkedList.git
public boolean contains(Object data) {}
在链表之中存在许多数据,如果要想查询数据或判断某数据是否存在就可以用到contains
方法。
如果链表是空,不需要进行后续迭代;如果不为空,进行数据的依次递归判断。考虑到标准化问题,这种操作需要equals()方法的支持。
// 第一次调用containsNode:this = Link.root
// 第二次调用containsNode:this = Link.root.next
// 第三次调用containsNode:this = Link.root.next.next
// ······
public boolean containsNode(Object search) {
if (search.equals(this.data)) { // 找到了想查询的数据
return true ;
} else {
if (this.next != null) { // 当前节点之后还有其他节点
return this.next.containsNode(search) ;
} else { // 当前节点是最后一个节点
return false ;
}
}
}
public boolean contains(Object search) {
if (search == null || this.root == null) { // 没有要查询的内容并且链表为空
return false ;
}
return this.root.containsNode(search) ;
}
https://github.com/colderThyKiss/LinkedList.git
注意:查找需要equals()方法的支持,所以如果是自定义类一定要覆写equals()方法。
public Object get(int index) {}
对于链表中的所有数据,严格来说都是有顺序的,增加顺序就是它的保存顺序。链表既然是动态数组,那么也应该有近似于数组的通过索引取得内容的操作。
public Object getNode(int index) {
if (Link.this.foot++ == index) { // 找到了想查询的数据
return this.data ;
} else {
this.next.getNode(index) ;
}
return null ;
}
这个方法在执行时必须保证链表有数据。
public Object get(int index) {
if (index >= this.count) { // 超过了保存的个数
return null ;
}
this.foot = 0 ;
return this.root.getNode(index) ;
}
https://github.com/colderThyKiss/LinkedList.git
System.out.println(all.get(0)) ;
System.out.println(all.get(3)) ;
public void set(int index, Object newData) {}
该方法与上一节get方法类似,不过get方法是返回数据,而本set方法需要做一个数据的修改。
public void setNode(int index, Object newData) {
if (Link.this.foot++ == index) {
this.data = newData;
} else {
if (this.next != null) {
this.next.setNode(index,newData) ;
}
}
}
public void set(int index, Object newData) {
if (index >= this.count) { // 超过了保存的个数
return ; // void没有返回值
}
this.foot = 0 ;
this.root.setNode(index, newData) ;
}
https://github.com/colderThyKiss/LinkedList.git
all.set(0, "HELLO") ;
System.out.println(all.get(0)) ;
public void remove(Object data) {}
如果要进行数据的删除处理,需要考虑两种情况:
// 第一次调用removeNode:this = Link.root.next,previous = Link.root
// 第二次调用removeNode:this = Link.root.next.next,previous = Link.root.next
// ······
public void removeNode(Node previous, Object data) {
if (this.data.equals(data)) { // 当前节点为要删除节点
previous.next = this.next; // 上一个节点指向当前节点
} else {
this.next.removeNode(this, data) ;
}
}
public void remove(Object data) {
if (this.contains(data)) { // 如果该数据存在则向下进行
// 首先判断要删除的是否是根节点数据
if (this.root.data.equals(data)) { // root是Node类的对象,Node是Link的内部类,所以可以直接使用
this.root = this.root.next ; // 根节点变为下一个节点,代表原根节点被删除
} else {
this.root.next.removeNode(this.root, data) ;
}
this.count-- ;
}
}
https://github.com/colderThyKiss/LinkedList.git
all.remove("HELLO") ;
all.remove("java") ;
Object[] result1 = all.toArray() ;
for (int i = 0 ; i < result1.length ; i++) {
System.out.println(result1[i]) ;
}
至此,最简化链表才算完成,当然这只是一个最简单的单向链表,也没有考虑到性能问题。但是通过这个链表的学习,应该做到:清楚动态数组的实现原理。同时再次强调:contains和remove方法需要equals的支持。
序号 | 方法名 | 类型 | 功能 |
---|---|---|---|
99 | public void add(Object data) {} | 普通 | 向链表中添加数据 |
100 | public int size() {} | 普通 | 取得元素个数 |
100 | public boolean isEmpty() {} | 普通 | 判断链表是否为空链表 |
101 | public Object[] toArray() {} | 普通 | 链表数据转换为对象数组 |
102 | public boolean contains(Object data) {} | 普通 | 查询数据或判断某数据是否存在 |
103 | public Object get(int index) {} | 普通 | 取得制定索引内容 |
104 | public void set(int index, Object newData) {} | 普通 | 修改指定索引对应的数据 |
105 | public void remove(Object data) {} | 普通 | 删除数据,需要equals支持 |
阿里云【名师课堂】Java面向对象开发97 ~ 105:链表
标签:hub 关系 java面向对象 nod sem 实现原理 ati 创建 private
原文地址:https://www.cnblogs.com/playerone/p/13232077.html