学习到现在有两个概念没有讲解透彻:
· this表示当前对象,只是在对象比较中简单的应用了一次;
· 内部类做什么用?
在之前所编写的第二个代码模型利用的是对象数组完成的,但是成是对象数组,败也是对象数组,因为对象数组有着严格的顺序索引同时不可修改大小。所以必须利用自己的代码进行问题的解决。
链表 = 动态对象数组,是利用引用关系的组合实现的,所以来讲本处都是引用关系的操作。
在现实之中有可能出现这样一种情况:从的领导是顾,顾的领导是谢、谢的领导是刁,刁的领导是金。现在如果由我们个人来表示这样的关系,那么类该如何设计?
范例:问题的引出
class Emp { private String name ; private Emp mgr ; // 领导 public Emp(String name) { this.name = name ; } public void setMgr(Emp mgr) { this.mgr = mgr ; } public Emp getMgr() { return this.mgr ; } public String getName() { return this.name ; } } public class LinkDemo { public static void main(String args[]) { Emp ea = new Emp("从") ; Emp eb = new Emp("顾") ; Emp ec = new Emp("谢") ; Emp ed = new Emp("刁") ; Emp ee = new Emp("金") ; ea.setMgr(eb) ; eb.setMgr(ec) ; ec.setMgr(ed) ; ed.setMgr(ee) ; } } |
现在通过如上的设计关系,可以发现,所有的对象就可以像一个个节点一样,进行连接。
范例:定义节点类
class Node { // 只是作为一个数据的载体 private String data ; // 假设现在存放的是String型 private Node next ; // 下一个节点 public Node(String data) { // 有数据才应该有节点 this.data = data ; } public String getData() { return this.data ; } public void setNext(Node next) { this.next = next ; } public Node getNext() { return this.next ; } } |
范例:设置节点的关联
public class LinkDemo { public static void main(String args[]) { // 第一层:设置节点关系 Node root = new Node("数据A") ; Node n1 = new Node("数据B") ; Node n2 = new Node("数据C") ; root.setNext(n1) ; n1.setNext(n2) ; // 第二层:取出节点关系 Node currentNode = root ; // 当前操作节点 while(currentNode != null) { // 有节点 System.out.println(currentNode.getData()) ; currentNode = currentNode.getNext() ; // 改变当前节点 } } } |
但是很明显以上的代码通过循环的方式并不好,最好的方式是使用递归进行输出。
范例:通过递归实现输出
public class LinkDemo { public static void main(String args[]) { // 第一层:设置节点关系 Node root = new Node("数据A") ; Node n1 = new Node("数据B") ; Node n2 = new Node("数据C") ; root.setNext(n1) ; n1.setNext(n2) ; print(root) ; // 从root开始输出 } public static void print(Node currentNode) { if (currentNode != null) { System.out.println(currentNode.getData()) ; print(currentNode.getNext()) ; } } } |
以上通过设置节点保存,就可以发现可以保存无数的信息,没有长度限制。
通过以上的分析可以发现现在的问题:用户需要自己准备好数据,需要自己定义节点,需要自己设置节点关系,取得的时候要分析节点组成后取出数据,这些实在是太麻烦了,用户关心的只是数据往那里保存,从那里得到。
所以应该有一个类专门进行节点的操作,即:用户不应该去关心Node类之间的操作。于是现在要准备出一个Link类,这个类负责操作Node。
在整个的Link类之中最需要关心的就是第一个节点(根节点)只有存在了根节点,才可以进行后期的所有操作。
范例:实现代码
class Node { // 只是作为一个数据的载体 private String data ; // 假设现在存放的是String型 private Node next ; // 下一个节点 public Node(String data) { // 有数据才应该有节点 this.data = data ; } public String getData() { return this.data ; } public void setNext(Node next) { this.next = next ; } public Node getNext() { return this.next ; } // 第一次(Link):this = Link.root // 第二次(Node):this = Link.root.next // 第三次(Node):this = Link.root.next.next public void addNode(Node newNode) { if (this.next == null) { // 当前的下一个节点为null this.next = newNode; // 保存新节点 } else { this.next.addNode(newNode) ; } } // 第一次(Link):this = Link.root // 第二次(Node):this = Link.root.next public void printNode() { System.out.println(this.data) ; if (this.next != null) { this.next.printNode() ; } } } class Link { // 专门操作节点 private Node root ; // 根节点 public void add(String data) { // 保存数据 // 将数据封装在节点之中,因为只有封装之后才可以设置节点关系 Node newNode = new Node(data) ; if (this.root == null) { // 现在没有根节点 this.root = newNode ; // 第一个作为根节点 } else { this.root.addNode(newNode) ; } } public void print() { if (this.root != null) { // 有数据 this.root.printNode() ; // 交给Node类处理 } } } public class LinkDemo { public static void main(String args[]) { Link all = new Link() ; all.add("货物A") ; all.add("货物B") ; all.add("货物C") ; all.print() ; } } |
以上就是一个最最最最最简单的链表操作,也包含了所有链表的基本操作形式。
之前的代码只能够说是满足了链表的基本形式要求,但是代码不可用。最核心的问题在于,所有的数据都是直接进行输出的,这在实际的工作上根本就无法去使用。但是通过之前的程序也可以发现一点问题:
· Node类是整个链表节点配置关系的核心部分,但是用户不需要知道Node类存在,但是在之前的程序,发现Node类用户可以直接去使用,不合法;
· Link类作为节点排列的操作类,一定要建立好根节点。
范例:首先修改程序的基本组成
class Link { // 用户最需要关注的是这个类 // Node类不希望被其它类使用,但是Link类要使用 private class Node { // 存储数据和配置节点关系 private String data ; // 假设现在保存的是String private Node next ; // 下一个节点 public Node(String data) { // 节点要有数据 this.data = data ; } } // ********************************** private Node root ; // 根节点 } |
而在本结构之中最重要的几个操作:增加、删除、查找、输出全部。
1、 首先需要在Link类里面增加一个add()方法;
2、 将传入的数据封装为一个Node类对象,封装的好处是可以进行节点的排列,此方法中的数据类型一定要与Node类之中data属性的类型相同;
class Link { // 用户最需要关注的是这个类 // Node类不希望被其它类使用,但是Link类要使用 private class Node { // 存储数据和配置节点关系 private String data ; // 假设现在保存的是String private Node next ; // 下一个节点 public Node(String data) { // 节点要有数据 this.data = data ; } public void addNode(Node newNode) { if (this.next == null) { // 当前节点的下一个为null this.next = newNode ; } else { this.next.addNode(newNode) ; } } } // ********************************** private Node root ; // 根节点 public void add(String data){ if (data == null) { return ; // 保存的数据不为null } Node newNode = new Node(data) ; // 将数据封装为节点 if (this.root == null) { // 没有根节点 this.root = newNode ; // 新节点作为根节点 } else { // 交给Node类进行处理 this.root.addNode(newNode) ; } } } |
每当一个新数据保存成功之后,应该进行一个数量的修改。
1、 由于需要针对于所有的节点操作,那么首先应该在Link类里面增加一个count的属性;
private int count ; // 统计个数 |
2、 在add()方法执行完毕之后表示增加成功,所以保存的个数要增加
this.count ++ ; // 增加个数 |
3、 在Link类里面增加一个size()方法,此方法就返回count这个属性的内容;
public int size() { return this.count ; } |
如果说现在链表之中没有保存任何数据(root是null或者count为0),那么就应该返回true,否则返回false。
1、 在Link类之中增加isEmpty()方法
public boolean isEmpty() { return this.count == 0 ; } |
如果个数为0返回true,否则返回false。
整个链表就是靠着Link类中的root属性的,如果root的内容是null,那么链表一定都不会保存。
public void clean() { this.root = null ; // 清空 this.count = 0 ; // 归零 } |
索引号是动态生成的,那么这些节点的索引生成代码应该由Link类负责。
1、 首先在Link类之中增加一个用于标记索引的属性:foot
private int foot ; // 用于标记索引顺序 |
2、 在Link类之中增加一个get()方法,查询数据。
public String get(int index) { if (index > this.count) { return null ; } this.foot = 0 ; // 让脚标从0开始 return this.root.getNode(index) ; } |
3、 在Node类里面如果要进行查询,肯定要修改foot属性的内容,而幸运的是,在内部类之中可以方便的访问外部类的私有数据。
public String getNode(int index) { if(index == Link.this.foot ++) { // 符合查找索引 return this.data ; // 返回当前节点数据 } else { return this.next.getNode(index) ; } } |
1、 首先需要在Link类里面增加一个contains()方法,这个方法里面要先判断查询的数据是否为null。
public boolean contains(String data) { if (data == null) { return false ; // 没有查询数据 } return this.root.containsNode(data) ; } |
2、 将查询过程交给Node类进行处理,就是将每一个节点进行比较,如果查询到了返回true,否则返回false。
public boolean containsNode(String data) { if (data.equals(this.data)) { // 查询数据与当前节点吻合 return true ; } else { if (this.next != null) { return this.next.containsNode(data) ; } else { return false ; } } } |
如果要想进行数据的删除操作,必须考虑两种情况:
情况一:要删除的数据是根节点(应该由Link类完成,而且在Link类里面一定已经判断完了根节点)
情况二:要删除的节点不是根节点(Node类进行,至少从第二个节点开始)
1、 首先在Link类之中增加一个remove()方法,而删除数据之前应该执行一个查询,查询要删除的数据是否存在,如果不存在就没必要删除了。如果查询到数据存在,则需要在Link类里面判断要删除的数据是否是根节点数据。
public void remove(String data) { if(this.contains(data)) { // 查找到数据了 if (this.root.data.equals(data)) { this.root = this.root.next ; // 改变根节点 } else { // 要删除的不是根节点 this.root.next.removeNode(this.root,data) ; } this.count -- ; } } |
2、 在Node类之中进行删除操作(现在是从第二个节点开始的判断)
public void removeNode(Node previous,String data) { if(this.data.equals(data)) { // 当前节点满足删除数据 previous.next = this.next ; // 空出当前节点 } else { // 向下继续判断 this.next.removeNode(this,data) ; } } |
1、 此对象数组所有节点都应该可以看见,那么只能够定义在Link类之中。
private String [] retData ; // 保存的对象数组 |
2、 在Link类之中增加一个toArray()方法,此方法一定是要返回retData这个属性内容,但是retData的内容应该交给Node类来处理,而开辟的大小应该是由count决定的;
public String [] toArray() { if(this.count == 0) { return null ; // 没数据 } this.foot = 0 ; // 通过foot设置数组索引 this.retData = new String [this.count] ; // 开辟数组 this.root.toArrayNode() ; // 交给Node类处理 return this.retData ; } |
3、 在Node类之中增加一个toArrayNode()方法
public void toArrayNode() { Link.this.retData[Link.this.foot ++] = this.data ; if (this.next != null) { this.next.toArrayNode() ; } } |
所有的链表程序不要求你会写(会写最好了),但是这些方法的作用一定要知道。
以上的代码为了讲解方便,是采用了String数据类型操作的,但是在实际之中有可能需要操作的是用户定义的对象,那么如果要想将其修改为用户定义对象需要修改以下部分:
· 所有的数据类型应该是用户自定义对象;
· 查询数据和删除数据依靠的是String的equals()方法,这个方法的实际功能是对象比较,那么如果是用户对象,则对象所在的类一定要编写对象比较操作(compare());
第一步:定义自己的类,提供好对象比较方法
class Person { private String name ; private int age ; public Person(String name,int age) { this.name = name ; this.age = age ; } public boolean compare(Person person) { if (this == person) { return true ; } if (person == null) { return false ; } if (this.name.equals(person.name) && this.age == person.age) { return true ; } return false ; } public String getInfo() { return "姓名:" + this.name + ",年龄:" + this.age ; } } |
第二步:修改Link类,此时保存的类型是Person,比较的方法是compare()
class Link { // 用户最需要关注的是这个类 // Node类不希望被其它类使用,但是Link类要使用 private class Node { // 存储数据和配置节点关系 private Person data ; // 假设现在保存的是String private Node next ; // 下一个节点 public Node(Person data) { // 节点要有数据 this.data = data ; } public void addNode(Node newNode) { if (this.next == null) { // 当前节点的下一个为null this.next = newNode ; } else { this.next.addNode(newNode) ; } } public Person getNode(int index) { if(index == Link.this.foot ++) { // 符合查找索引 return this.data ; // 返回当前节点数据 } else { return this.next.getNode(index) ; } } public boolean containsNode(Person data) { if (data.compare(this.data)) { // 查询数据与当前节点吻合 return true ; } else { if (this.next != null) { return this.next.containsNode(data) ; } else { return false ; } } } public void removeNode(Node previous,Person data) { if(this.data.compare(data)) { // 当前节点满足删除数据 previous.next = this.next ; // 空出当前节点 } else { // 向下继续判断 this.next.removeNode(this,data) ; } } public void toArrayNode() { Link.this.retData[Link.this.foot ++] = this.data ; if (this.next != null) { this.next.toArrayNode() ; } } } // ********************************** private Node root ; // 根节点 private int count ; // 统计个数 private int foot ; // 用于标记索引顺序 private Person [] retData ; // 保存的对象数组 public void add(Person data){ if (data == null) { return ; // 保存的数据不为null } Node newNode = new Node(data) ; // 将数据封装为节点 if (this.root == null) { // 没有根节点 this.root = newNode ; // 新节点作为根节点 } else { // 交给Node类进行处理 this.root.addNode(newNode) ; } this.count ++ ; // 增加个数 } public int size() { return this.count ; } public boolean isEmpty() { return this.count == 0 ; } public void clean() { this.root = null ; // 清空 this.count = 0 ; // 归零 } public Person get(int index) { if (index > this.count) { return null ; } this.foot = 0 ; // 让脚标从0开始 return this.root.getNode(index) ; } public boolean contains(Person data) { if (data == null) { return false ; // 没有查询数据 } return this.root.containsNode(data) ; } public void remove(Person data) { if(this.contains(data)) { // 查找到数据了 if (this.root.data.compare(data)) { this.root = this.root.next ; // 改变根节点 } else { // 要删除的不是根节点 this.root.next.removeNode(this.root,data) ; } this.count -- ; } } public Person [] toArray() { if(this.count == 0) { return null ; // 没数据 } this.foot = 0 ; // 通过foot设置数组索引 this.retData = new Person [this.count] ; // 开辟数组 this.root.toArrayNode() ; // 交给Node类处理 return this.retData ; } } public class LinkDemo { public static void main(String args[]) { Link all = new Link() ; all.add(new Person("张三",20)) ; all.add(new Person("李四",30)) ; all.add(new Person("王五",50)) ; Person temp [] = all.toArray() ; for (int x = 0 ; x < temp.length ; x ++) { System.out.println(temp[x].getInfo()) ; } System.out.println(all.contains(new Person("王五",50))) ; } } |
对于此部分的内容会修改使用即可,不要求从无到有编写。
之所以不再使用对象数组而使用链表进行替代,很大一个关系在于,链表是一种动态的顺序结构,可以实现轻松的数据删除与查询功能,这一点是对象数组所不具备的功能。
0、 利用链表来修改之前的部门和雇员关系(一对多)
1、 多对多映射
配置完关系之后,要求可以实现如下的功能:
· 可以根据一个管理员取得此管理员所在的所有管理员组,以及每个管理员组所具备的权限;
· 可以根据一个管理员组取得此管理员组下的所有管理员和权限信息;
· 可以根据一个权限取得具备此权限的所有管理员组,以及每个管理员组的管理员信息。
那么到现在为止就可以清楚的发现,如果继续按照之前所学习的代码进行项目的编写,最麻烦的莫过于数据的统一问题,就拿链表为例,换一种数据类型就需要去修改链表结构,这种代码是不可使用的。所以后面的内容就是进行参数类型的统一。
本程序只是一个简单的多对多映射。
多对多映射(能理解就理解)
最麻烦的地方在于下载记录,此时的下载记录之中发现存在有多个数据,一个用户有多个下载记录,一个软件有多个下载记录。