注意:这里是JAVA自学与了解的同步笔记与记录,如有问题欢迎指正说明
目录
前言
今天因为个人的一些事情,博客推到了晚上才晚上,好在今天我们内容并不有很深的算法逻辑。今天我们来谈谈昨天一笔带过的Object类的通用循环队列,恰好博主我其实也第一次接触Java的类似编程,所以这次一边同大家讲述并一边学习,所以可能引用的文章偏多。
一、所有类的父级——Object类
Object 类是所有类的父类,若说Java的所有类构成一个庞大的继承网络,那么Object类就是这颗树的根,只要此类是个Java环境下创建的类,那么其必然继承了 Object,可以共享使用Object的一切可以的属性和方法。Object 类位于 java.lang 包中,编译时会自动导入,我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为 Object 的子类(参考自:网址)。或者用继承中的显式与隐式继承的概念来说,我们一切正常定义的类都隐式继承了Object类。
我们之前一直在重写的toString方法就是继承自Object类,因为这个方法是存在与计算机上的一切用于操作的类所共用的一种属性。
当然除此之外Object类还包含其余的一些方法,下面我借用互联网上的资料记录下相关功能,便于我后续查阅:(参考网站:Java Object 类 | 菜鸟教程)
感觉常用的有:
protected Object clone()
创建并返回一个对象的拷贝
boolean equals(Object obj)
比较两个对象是否相等
Class<?> getClass()
获取对象的运行时对象的类
int hashCode()
获取对象的 hash 值
String toString()
返回对象的字符串表示形式
这些涉及相关线程有关方法,目前不常用,具体用时再查看。
protected void finalize()
当 GC (垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法
void notify()
唤醒在该对象上等待的某个线程
void notifyAll()
唤醒在该对象上等待的所有线程
void wait()
让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法
void wait(long timeout)
让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数设置的timeout超时时间。
void wait(long timeout, int nanos)
与 wait(long timeout) 方法类似,多了一个 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。
二、利用Object构造一个通用性类:循环队列
关于循环队列,我的这篇博客说得很细了,这里我们就看使用Object构造后的细节:
package datastructure.queue;
/**
* The usage of the if statement.
*
* @author Xingyi Zhang 1328365276@qq.com
*/
public class CircleObjectQueue {
/**
* The total space. One space can never be used.
*/
public static final int TOTAL_SPACE = 10;
/**
* The data.
*/
Object[] data;
/**
* The index of the head.
*/
int head;
/**
* The index of the tail.
*/
int tail;
/**
*******************
* The constructor
*******************
*/
public CircleObjectQueue() {
data = new Object[TOTAL_SPACE];
head = 0;
tail = 0;
}// Of the first constructor
/**
*********************
* Enqueue.
*
* @param paraValue The value of the new node.
*********************
*/
public void enqueue(Object paraValue) {
if ((tail + 1) % TOTAL_SPACE == head) {
System.out.println("Queue full.");
return;
} // Of if
data[tail % TOTAL_SPACE] = paraValue;
tail++;
}// Of enqueue
/**
*********************
* Dequeue.
*
* @return The value at the head.
*********************
*/
public Object dequeue() {
if (head == tail) {
// System.out.println("No element in the queue");
return null;
} // Of if
Object resultValue = data[head % TOTAL_SPACE];
head++;
return resultValue;
}// Of dequeue
/**
*********************
* Overrides the method claimed in Object, the superclass of any class.
*********************
*/
public String toString() {
String resultString = "";
if (head == tail) {
return "empty";
} // Of if
for (int i = head; i < tail; i++) {
resultString += data[i % TOTAL_SPACE] + ", ";
} // Of for i
return resultString;
}// Of toString
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String args[]) {
CircleObjectQueue tempQueue = new CircleObjectQueue();
}// Of main
}// Of CircleObjectQueue
仔细看其实不难发现,我们几乎是只把char类型替换为了Object类型而已,原本的Char类型的数组可直接声明为Object数组,也可以直接赋值为Object类型,并不会报错。同时Object遵循自动类型转换的功能,在toString方法的重写中,我们声明了一个字符串类型遍历resultString,后续我们直接把Object类型作用于这个字符串类型便直接实现了Object到String的转换:
resultString += data[i % TOTAL_SPACE] + ", ";
这些特征增加了Object的一般性。为了更加明确发现这种转换的特点,下面我们对昨天的BFS部分的内容进行重构,昨天我们的BFS分别沿用了一个专门构造的Int循环队列和Object循环队列,而今天我们发现了Object兼容一般性的特征,于是我们试着只用Object循环队列来完成昨日的核心代码。
但是最后我要提一句,Object支持null的返回(可见我们的dequeue方法)。为何我要强调这个部分,因为有些特殊类型是没有null的使用的,具体我会在接下来提到,请记住这个问题!
三、 昨日代码重构
本部分初次阅读请结合昨日博客内容:
// Initialize arrays.
int tempLength = getNumNodes();
valuesArray = new char[tempLength];
indicesArray = new int[tempLength];
int i = 0;
初始化一切正常。
// Traverse and convert at the same time.
CircleObjectQueue tempQueue = new CircleObjectQueue();
tempQueue.enqueue(this);
CircleObjectQueue tempIntQueue = new CircleObjectQueue();
Integer tempIndexInteger = Integer.valueOf(0);
tempIntQueue.enqueue(tempIndexInteger);
声明队列时渐渐图穷匕见了,这里出现了新面孔:Integer类。这个类本质上是对int类型的封装,完善和扩充了int类一些残缺的方法,极大增加了int类型的易用性。但是本质来说它不是int类型,而是一种类,因此其具备对象的所有特征:可以用new声明,可以用点号进行属性与方法调用。但同时,它又具有很多类似于int的写法,比如:
Integer a = 128;
通过反编译,我们发现其本质是调用了Integer类中的valueOf方法罢了,因此其具有自动装箱的能力(解释:装箱是将基本数据类型变为类的过程,其逆过程是拆箱),同时,其也有自动拆箱的能力,我们允许下列的写法:
Integer a = new Integer(128);
int m = a;
同时Integer类的构造是兼容String的初始化的:
String dataString = "1234";
Integer dataInteger = new Integer(dataString);
因为本质上构造函数中使用了parseInt()方法,所以字符串中只能是阿拉伯数字字符,所以拆箱的接收也只能是int才行,所以下面这样会报错:
String dataString = "1234";
Integer dataInteger = new Integer(dataString);
String dataString2 = dataInteger; //Error
关于Integer的方法之后可以查看这篇博客
回到我们的代码,我们利用的是“Integer tempIndexInteger = Integer.valueOf(0);”进行的初始化了一个值为0的Integer对象(等价于Integer tempIndexInteger = 0),之后直接把此类装于使用Object类的tempIntQueue中并没有报错,说明可以兼用正常的类型转换。
BinaryCharTree tempTree = (BinaryCharTree) tempQueue.dequeue();
int tempIndex = ((Integer) tempIntQueue.dequeue()).intValue();
System.out.println("tempIndex = " + tempIndex);
while (tempTree != null) {
valuesArray[i] = tempTree.value;
indicesArray[i] = tempIndex;
i++;
if (tempTree.leftChild != null) {
tempQueue.enqueue(tempTree.leftChild);
tempIntQueue.enqueue(Integer.valueOf(tempIndex * 2 + 1));
} // Of if
if (tempTree.rightChild != null) {
tempQueue.enqueue(tempTree.rightChild);
tempIntQueue.enqueue(Integer.valueOf(tempIndex * 2 + 2));
} // Of if
tempTree = (BinaryCharTree) tempQueue.dequeue();
if (tempTree == null) {
break;
}//Of if
tempIndex = ((Integer) tempIntQueue.dequeue()).intValue();
} // Of while
后续代码基本上与昨日BFS核心代码一致,至少核心逻辑上是一致的,我们主要来说些不同。众所周知,在计算机语言的类型转换中特殊转变为一般可以,但是一般变为特殊则需要说明情况,这就是典型的向下兼容而不向上兼容。所以我们的Object类型要变为可以在BFS的循环体中可以使用的逻辑主体的话,我们需要利用强制转换将其变为特定的子类才行。因为我们使用的队列是通用性队列,所以在获取队列元素的时候为了方便程序使用,我们都将其转换为特殊的构造类:
BinaryCharTree tempTree = (BinaryCharTree) tempQueue.dequeue();
int tempIndex = ((Integer) tempIntQueue.dequeue()).intValue();
刚刚因为提到了Integear具有自动拆箱的能力,所以这里第二句也可简化为:int tempIndex = (Integer) tempIntQueue.dequeue();
最后,我们用于推进循环的语句与昨日也有些不同,昨天我们用的是两个并列的双队列平行出队(见下)。因为我们最初构造的Int循环队列一旦队空会抛出-1值,这个值可以被正常的int数据接收,并不会报错,甚至可以作为非法条件用于控制循环。
tempTree = (BinaryCharTree) tempQueue.dequeue();
tempIndex = tempIntQueue.dequeue();
但是现在我们采用通用队列后,其会抛出一个针对Object对象无效数据null,这个数据无法被int强制转化,因此若按照下面这样的写法会报错:
tempTree = (BinaryCharTree) tempQueue.dequeue();
tempIndex = ((Integer) tempIntQueue.dequeue()).intValue(); // intValue() can be ignored
但是我们构造的BinaryCharTree作为一个类引用,可以正常接收null,所以我们可以灵活处理,在tempTree接收到null值时便提前退出就好了,于是乎就有了我上述的均采用通用性队列的转储代码。
总结
今天的文章相比于前几日输出我所想,更多的输入我所不知,并且博主也是在学习Java的过程中嘛。
数据类型的重用是在高级语言逐步补充各种数据结构的多用库是必然考虑的问题,可以设想,如果每种数据结构都要针对不同数据类型开发重复版本,那么整个库的重用性可就太糟糕了,而且这样也无法兼容各种用户自定义的数据类型。其实今天在学习这个内容的时候,我满脑子都想的是我在前几天10日总结的博客中提到的泛型编程,因为无论是C++还是Java,我们似乎都有利用<T>表示有关模糊类型的构造方案,像C++中的STL就是一个兼容各种类型的库,而Java也继承了这种思想,比如我们申请一个集合,我们会采用<>字符去特别说明什么使用样数据项的数据类型:
HashSet<String> sites = new HashSet<String>();
伴随着好奇,我了解Object声明通用类和泛型的差异:
首先泛型确实出现得要晚些,在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”。而这个任意化的本质其实是依托强制转换机制来实现的,但是强制转换机制其实不算是非常安全的,因为这个机制需要保证编程人员足够了解我们的数据,知道我们的数据可能有什么结果的前提下进行的。否则可能出现各种奇怪的转换机制错误。上面我们出现的int不可接收null可能还好,因为编译器记录了这种非法可能,但是某些情况下这种错误可能会绕过编译器,导致程序顺利执行但数据却不正确。
但是如果抛开这个不安全的弊端,其实我们的使用过程基本是一致的,从某种意义上来说,只要我们对于数据可能出现的全部情况有充足预备与了解,对于简单的数据结构使用Object完成重用基本上是没问题的。