Object类
经过了一系列的学习可以发现有一个最麻烦的问题出现了:现在有对象、数组接口,那么这么多数据怎么能够进行统一呢?于是就有了Object类的设计。
一、Object类的基本定义
Object类是所有类的父类,也就是说任何一个类在定义的时候如果没有明确的继承一个父类的话,那么它就是Object类的子类,也就是说,以下两种类定义的最终效果是完全相同的:
class Book{}
class Book extends Object{}
在整个Java里面类的继承关系一直都存在(Object类除外)。
既然Object类是所有类的父类,那么最大的一个好处就在于:利用Object类可以接受全部类的对象,因为可以向上自动转型。
范例:利用Object类来接收对象
class Book extends Object { } public class Demo { public static void main(String args[]) { Object obja = new Book();// 向上转型 Object objb = "hello";// 向上转型 Book b = (Book) obja; String s = (String) objb; } }
既然这样,如果在不确定参数类型的时候,使用Object类型应该是最好的选择。
问题:为什么在Object类里面要定义一个无参构造方法呢?
既然Object类是所有类的父类,那么所有类对象实例化的时候,子类构造方法一定要默认调用父类的无参构造。
从严格意义上来讲(一般不遵守),任何一个简单Java类都应该覆写Object类中的如下三个方法:
· 取得对象信息:public String toSring();
· 对象比较:public boolean equals(Object obj);
· 取得对象HASH码:public int hashCode()。
二、取得对象信息:toString()方法
如果说现在是一个String类对象,直接输出这个对象可以取得字符串的内容,但是如果是一个自定义的对象,那么发现就会出现一个对象编码。
范例:观察代码
class Book extends Object { } public class Demo { public static void main(String args[]) { Book b = new Book(); String s = "Hello"; System.out.println(b); System.out.println(b.toString()); System.out.println(s); } }
发现现在如果直接输出对象与调用toString()方法后输出对象的功能是完全一样的。
在进行对象输出的时候,输出操作会自动调用对象中的toString()方法将对象变为字符串后再输出,而默认情况下Object类中的toString()为了适应于所有对象的输出,所以只输出了对象的编码。
如果现在有需要,也可以自己根据实际情况需要来覆写此方法。
范例:覆写toString()方法
class Book { private String title; private double price; public Book(String title, double price) { this.title = title; this.price = price; } public String toString() { return "书名:" + this.title + ",价格:" + this.price; } } public class Demo { public static void main(String args[]) { Book b = new Book("Java", 78.8); System.out.println(b); } }
直接输出对象就调用了toString(),等于输出对象的时候代码又节约了。
三、对象比较:equals()
对象比较在很多的开发之中都是一定要使用到的核心概念,而在之前使用了一个自定义名称的compare()方法作为对象比较方法的名称,但是这个不标准,标准的做法是使用equals()方法完成。
范例:实现对象比较
class Book { private String title; private double price; public Book(String title, double price) { this.title = title; this.price = price; } public boolean equals(Object obj) { if (this == obj) { return true;// 地址相同 } if (obj == null) { return false; } if (!(obj instanceof Book)) {// 不是本类实例 return false; } Book book = (Book) obj; if (this.title.equals(book.title) && this.price == book.price) { return true; } return false; } public String toString() { return "书名:" + this.title + ",价格:" + this.price; } } public class Demo { public static void main(String args[]) { Book b1 = new Book("Java", 78.8); Book b2 = new Book("Java", 78.8); System.out.println(b1.equals(b2)); } }
对象的比较操作写出以上代码应该就算是完整了。
四、Object类可以接收一切引用数据类型
Object类是所有类的父类,所以Object类的对象可以接收所有类的对象,可是除了类的对象之外,Object类连数组和接口对象也都可以接收。
范例:接收数组数据
public class Demo { public static void main(String args[]) { Object obj = new int[] { 1, 2, 3 };// 向上转型 System.out.println(obj);// [aaa@qq.com if(obj instanceof int[]){//是否是int数组 int data[] = (int[]) obj;//向下转型 for(int x=0;x<data.length;x++){ System.out.println(data[x]); } } } }
除了数组外,接口也同样可以。
范例:Object类接收接口对象
interface A { public void fun(); } class B implements A { @Override public void fun() { System.out.println("hello world"); } } public class Demo { public static void main(String args[]) { A a = new B();// 接口对象 Object obj = a;// 接收接口对象 A t = (A) obj;// 向下转型 t.fun(); } }
整个程序的参数就统一在了Object类型上。
五、修改链表
长期一直困扰我们的就是链表问题,因为在讲解所有的知识之前,一直带有疑问:链表不能统一数据,所以就造成了。每一次使用链表的时候都要进行重复的开发,但是有了Object之后,这一切就彻底解决了,因为Object的任何类型都可以接收,同时在链表之中由于需要对象比较的功能,这一功能Object也提供有equals()方法。
范例:修改可用链表
class Link {// 链表类,外部只能看见这一个 // 之所以定义在内部,主要是让其为Link类服务 private class Node {// 定义的节点类 private Object data;// 保存数据 private Node next;// 引用关系 public Node(Object data) { this.data = data; } public void addNode(Node newNode) { if (this.next == null) {// 当前的下一个节点为空 this.next = newNode; } else {// 向后继续保存 this.next.addNode(newNode); } } // 第一次调用(Link):this = Link.root // 第二次调用(Node):this = Link.root.next public boolean containsNode(Object data) { if (data.equals(this.data)) {// 当前节点数据为要查询的数据 return true;// 后面不再查询了 } else {// 当前节点数据不满足查询条件 if (this.next != null) {// 有后续节点 return this.next.containsNode(data); } else {// 没有后续节点 return false; } } } public Object getNode(int index) { // 使用当前的foot内容与要查询的索引进行比较 // 随后将foot的内容自增,目的是为了下次查询方便 if (Link.this.foot++ == index) {// 要查询的索引 return this.data;// 返回当前节点数据 } else {// 现在应该继续向后查询 return this.next.getNode(index); } } public void setNode(int index, Object data) { if (Link.this.foot++ == index) { this.data = data;// 进行内容的修改 } else { this.next.setNode(index, data); } } // 第一次调用(Link):previous = Link.root、this = Link.root.next // 第二次调用(Node):previous = Link.root.next、this = Link.root.next.next // 要传递上一个节点以及要删除的数据 public void removeNode(Node previous, Object data) { if (data.equals(this.data)) {// 当前节点为要删除节点 previous.next = this.next;// 空出当前节点 } else {// 应该向后继续查询 this.next.removeNode(this, data); } } // 第一次调用(Link):this = Link.root // 第二次调用(Node):this = Link.root.next public void toArrayNode() { Link.this.retArray[Link.this.foot++] = this.data; if (this.next != null) {// 有后续元素 this.next.toArrayNode(); } } } // ==========以上为内部类=========== private Node root;// 需要根节点 private int count;// 保存元素的个数 private int foot; private Object[] retArray;// 返回的数组 public void add(Object data) { Node newNode = new Node(data); if (data == null) {// 假设不允许有空 return; } if (this.root == null) {// 当前没有根节点 this.root = newNode;// 保存根节点 } else {// 根节点存在,其他节点交给Node类处理 this.root.addNode(newNode); } this.count++;// 每一次保存完成之后数据量加1 } public int size() {// 取得保存的数据量 return this.count; } public boolean isEmpty() { return this.count == 0; } public Object get(int index) { if (index > this.count) {// 超过了查询范围 return null;// 没有数据 } this.foot = 0;// 表示从前向后查询 return this.root.getNode(index);// 将查询过程交给Node类 } public boolean contains(Object data) { // 现在没有要查询的数据,根节点也不保存数据 if (data == null || this.root == null) { return false;// 没有查询结果 } return this.root.containsNode(data); } public void set(int index, Object data) { if (index > this.count) { return;// 结束方法调用 } this.foot = 0;// 重新设置foot属性的内容,作为索引出现 this.root.setNode(index, data);// 交给Node类设置数据内容 } public void remove(Object data) { if (this.contains(data)) {// 主要功能是判断数据是否存在 // 要删除数据是否是根节点数据 // root是Node类的对象,此处直接访问了内部类的私有操作 if (data.equals(this.root.data)) {// 为要删除节点 this.root = this.root.next;// 空出当前节点 } else {// 不是根元素 // 此时根元素已经判断过了,从第二个元素开始判断 this.root.next.removeNode(this.root, data); } this.count--;// 个数减少 } } public Object[] toArray() { if (this.root == null) { return null; } this.foot = 0;// 需要脚标控制 this.retArray = new Object[this.count];// 根据保存内容开辟数组 this.root.toArrayNode();// 交给Node类处理 return this.retArray; } }
范例:测试
public class Demo { public static void main(String args[]) { Link all = new Link(); all.add("A");// String转为Object all.add("B");// String转为Object all.add("C");// String转为Object all.remove("A");// String覆写了equals() Object data[] = all.toArray(); for (int x = 0; x < data.length; x++) { String str = (String) data[x];// Object转为String System.out.println(str); } } }
从现在开始链表程序的代码就固定了,以后就不需要修改了,你只需要会使用即可。
六、综合实战:宠物商店
整合链表应用,同时进一步巩固接口的作用(标准)。
思考题:要求以程序结构为主
实现一个宠物商店的模型,一个宠物商店可以保存多个宠物的信息(名字、年龄),可以实现宠物的上架、下架、模糊查询的功能。
现在已经提供了一个可用的链表,并且这两个链表里面还提供Object统一参数。
范例:定义出宠物的标准
interface Pet {// 定义一个宠物的标准 public String getName();// 得到宠物的名字 public int getAge();// 得到宠物年龄 }
宠物商店与具体的宠物没有任何的关系,它只是与宠物这个接口的标准有关。
范例:定义宠物商店
class PetShop {// 一个宠物商店要保存有多个宠物的信息 private Link pets = new Link();// 保存的宠物信息 public void add(Pet pet) {// 上架 this.pets.add(pet);// 向集合里面保存数据 } public void delete(Pet pet) {// 下架 this.pets.remove(pet); } // 模糊查询一定是返回多个内容,不知道多少个,返回Link即可 public Link Search(String keyWord) { Link result = new Link(); // 将集合变为对象数组的形式返回,因为集合保存的是Object // 但是真正要查询的数据在Pet接口对象的getName()方法的返回值 Object obj[] = this.pets.toArray(); for (int x = 0; x < obj.length; x++) { Pet p = (Pet) obj[x]; if (p.getName().contains(keyWord)) {// 查询到了 result.add(p);// 保存满足条件的结果 } } return result; } }
下面就可以根据宠物的标准定义各个子类。
范例:定义猫
class Cat implements Pet {// 如果不实现接口无法保存宠物信息 private String name; private int age; public Cat(String name, int age) { this.name = name; this.age = age; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Cat)) { return false; } Cat c = (Cat) obj; if (this.name.equals(c.name) && this.age == c.age) { return true; } return false; } public String toString() { return "猫的名字:" + name + ",猫的年龄:" + age; } @Override public String getName() { return this.name; } @Override public int getAge() { return this.age; } }
范例:定义狗
class Dog implements Pet {// 如果不实现接口无法保存宠物信息 private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Cat)) { return false; } Dog d = (Dog) obj; if (this.name.equals(d.name) && this.age == d.age) { return true; } return false; } public String toString() { return "狗的名字:" + name + ",狗的年龄:" + age; } @Override public String getName() { return this.name; } @Override public int getAge() { return this.age; } }
可以发现,有了接口之后,子类的形式都非常类似,这属于接口的实现特点,因为接口就规定好了子类要做的事情。
范例:测试类
public class Demo { public static void main(String args[]) { PetShop shop = new PetShop(); shop.add(new Cat("1", 2)); shop.add(new Cat("3", 2)); shop.add(new Cat("4", 2)); shop.add(new Cat("5", 2)); shop.add(new Dog("5", 2)); shop.add(new Dog("6", 2)); shop.add(new Dog("4", 2)); shop.add(new Dog("5", 2)); Link all = shop.Search("5"); Object obj[] = all.toArray(); for (int x = 0; x < obj.length; x++) { System.out.println(obj[x]); } } }
此时实现了现实关系的模拟。