java基础之Object类
1、简单概述
Object类是是所有类的父类,对于java中的任意一个类来说,都是Object类的子类。
Object类中的源码:
public class Object {
// 本地注册一些必要信息
private static native void registerNatives();
static {
registerNatives();
}
// 获取得到字节码文件对象。对于每一个类来说,是唯一的
// 用final修饰,禁止子类重写
public final native Class<?> getClass();
// 默认返回的是对象的hash码。一般实用的是在map中
public native int hashCode();
// equals方法默认比较的就是两个对象的hashCode值
// 所以equals方法和hashCode方法通常都会来进行重写
public boolean equals(Object obj) {
return (this == obj);
}
// 看到这个权限修饰符就知道是要留给子类来去重写的。
// 使用了protected来修饰,没有用final修饰,说明需要的时候子类需要重写
protected native Object clone() throws CloneNotSupportedException;
// toString方法,可以看到打印出来的是类名+@+该对象十六进制的整数。
// 这个方法一般来说,都会去进行重写
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// 这些方法出现在Object类中,首先想到的是Object是所有对象的父类。
// 那么下面的肯定是和对象有关的方法。
// 通过方法注释反复看到一个关键字:monitor对象监视器
// 用对象来作为一个监视器,所有的线程都将会去抢夺这个监视器。如果抢到了,那么来进行执行,如果没有抢到,那么就进入到阻塞阶
// 段。notify、notifyAll和wait方法通常都是放到一个synchronized修饰的代码块来或者是方法中来进行使用的。
// 需要注意的是:对象监视器必须是同一个而且必须要使用这个对象监视器来调用方法来唤醒其他线程等等。
// notify是随机唤醒对象监视器上的一个线程
// 还需要注意一点的是:调用了整个方法,并不会立马释放掉对象监视器,而是需要等待下面的逻辑执行完成之后才会去进行调用。
public final native void notify();
// notifyAll是唤醒对象监视器上的所有线程。这个时候就会有争夺对象监视器的现象
public final native void notifyAll();
// 这个方法指的是在对象监视器上等待获取得到对象监视器的使用权。超过时间之后,将会自动再次去进行抢夺
// 在此期间,会释放掉占用的锁对象已经进入到超时等待状态中去。
public final native void wait(long timeout) throws InterruptedException;
// 从这里可以看到调用的都是上面的方法
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
// 子类重写的方法。这里是在垃圾回收机制之前做的是结尾工作。
protected void finalize() throws Throwable { }
所以我更喜欢将这个对象监视器理解成是一个数据结构来盛放线程的。我假想是一个map,map中的同一个key,value是list集合。
syncronized关键字盛放的一个map集合,k是对于同一个对象来说,其value是不断的来进行扩充的多线程。
是同一个对象的多线程。这样一个线程执行完成或者是调度了,那么会从集合中来随机挑选一个线程执行。
对于每一个线程来说,都会有寄存器来记录上线程的线程状态,然后将线程的执行现场来进行恢复。
同时注意一点,native关键字修饰的方法是本地方式,底层是通过c和c++来进行实现的。
所以没有方法体的方法不一定就是抽象方法;抽象方法一定是没有方法体的;
2、代码演示
先从equals方法开始
public boolean equals(Object obj) {
return (this == obj);
}
比较的是在对象在JVM堆中的地址。我以为是hashcode的值。所以我又试了一下hashcode方法
public class TestOne {
public static void main(String[] args) {
Node node1 = new Node();
Node node2 = new Node();
System.out.println(node1.equals(node2)); // false
System.out.println(node1.hashCode()==node2.hashCode());// true
}
}
class Node{
private int id;
@Override
public int hashCode() {
return 1;
}
}
经过测试,尽管我重写了hashcode方法,两个对象用equals方法,但是我没有重写,所以比较的还是地址值。比较的还是对象在内存中的地址值。
演示一下通过Object类中的wait方法和notify方法来进行线程间的通信:
2.1、Demo1
public class Foodie extends Thread {
private BaoZi baoZi;
public Foodie(BaoZi baoZi) {
this.baoZi = baoZi;
}
@Override
public void run() {
synchronized (baoZi) {
for (; ; ) {
synchronized (baoZi) {
// 如果有包子,那么就开始进入到等待状态;如果说没有包子,那么就开始制作包子
if (baoZi.getFlag() == false) {
System.out.println("没有包子,通知厨师来包包子");
try {
// 线程就会在对象监视器上进行等待
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序执行到了这个地方,说明了是没有包子的。那么开始做包子
baoZi.setFlag(false);
baoZi.notify();
}
}
}
}
}
public class Cook extends Thread {
private BaoZi baoZi;
public Cook(BaoZi baoZi) {
this.baoZi = baoZi;
}
@Override
public void run() {
synchronized (baoZi) {
for (; ; ) {
// 如果有包子,那么就消费;如果没有,那么就进行等待
if (baoZi.getFlag() == true) {
try {
System.out.println("有包子,开始等消费者来进行消费.......");
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序走到了这里,那么就说明这里有了包子
// System.out.println("开始做包子,然后将包子修改成没有包子");
baoZi.setFlag(true);
baoZi.notify();
}
}
}
}
public class BaoZi {
// FLAG为true的时候,表示的是有包子;为false的时候,表示的是没有包子
private boolean flag;
public Boolean getFlag() {
return flag;
}
public void setFlag(Boolean flag) {
this.flag = flag;
}
}
public class DemoTwo {
public static void main(String[] args) {
BaoZi baoZi = new BaoZi();
Cook cook = new Cook(baoZi);
Foodie foodie = new Foodie(baoZi);
cook.start();
foodie.start();
}
}
通过上面的例子可以看到控制台中的输出信息:
没有包子,通知厨师来包包子
有包子,开始等消费者来进行消费.......
没有包子,通知厨师来包包子
有包子,开始等消费者来进行消费.......
2.2、Demo2
/**
* 使用三条线程交替打印出来ABC
*/
public class DemoOne {
private static int num = 1;
public static void main(String[] args) {
new Thread(() -> {
while (true){
synchronized (DemoOne.class) {
while (num != 1) {
try {
DemoOne.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num=2;
DemoOne.class.notifyAll();
System.out.println("A");
}
}
}).start();
new Thread(() -> {
while (true){
synchronized (DemoOne.class) {
while (num != 2) {
try {
DemoOne.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num=3;
DemoOne.class.notifyAll();
System.out.println("B");
}
}
}).start();
new Thread(() -> {
while (true){
synchronized (DemoOne.class) {
while (num != 3) {
try {
DemoOne.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num=1;
DemoOne.class.notifyAll();
System.out.println("C");
}
}
}).start();
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、克隆方法
上面提到过的克隆方法。其实克隆就相当于是一份拷贝方法。为了更加方便容易的来书写我们的代码而写。
可以来使用一下,通过有深拷贝和浅拷贝。
深拷贝:
浅拷贝:就是复制一个对象
使用克隆方法之前,有两步必须做的事情,第一继承Cloneble接口;第二步:重写clone方法
public class ObjectTest {
public static void main(String[] args) {
Person person = new Person();
person.setId(1);
person.setUsername("guang");
System.out.println(person);
try {
Person person1 = (Person)person.clone();
person1.setId(222);
System.out.println(person);
System.out.println(person1);
System.out.println(person1);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Person implements Cloneable{
private Integer id;
private String username;
@Override
public String toString() {
return "Person{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
从理论中来,到实践中去,最终回归理论