jdk1.8.0_144

  Object类作为Java中的顶级类,位于java.lang包中。所有的类直接或者间接都继承自它。所以Object类中的方法在所有类中都可以直接调用。在深入介绍它的API时,先插一句它和泛型之间的关系。

  在JDK1.5之前是没有泛型的,集合能够装下任意的类型,这就导致了一个潜在的问题——不能在编译时做类型检查,也就可能导致程序bug出现的概率。JDK1.5出现了泛型,在定义一个集合时指定一个泛型,这就能在编译时能做类型检查,避免了一些低级bug的出现。时至今日,实际上在JDK源码中遗留了部分不是特别优美的代码,从今天的角度来看是能够将其泛型化的(例如Map的get方法),但在当时需要考虑向后兼容不得不放弃对某些方法和类的泛型化,才导致了一丝瑕疵。

  接下来将详细的剖析Object类中的一些方法,其中某些方法会延伸到其他方面(例如:wait和notify等)。

public final native Class<?> getClass()

  返回Class对象实例。Class类有点“特殊”,因为它在我们的日常代码逻辑中不常出现,它所出现的地方往往是一些基础框架或者基础工具。  

  Class类所处的包同样是java.lang,毫无疑问它的父类还是Object。在学习面向对象编程时,我们知道类是对一个事物抽象的定义,对象实例是表示的是一个具体的事物。那么Class这个名字有点含糊的类抽象的是什么呢?它的实例有代表的是什么呢?

  在程序中定义一个People类,我们将男人、女人抽象为了人类——People,它的实例表示的是男人或女人。程序中类似People这样的类千千万万,Class类就是千千万万类和接口的抽象,Class类的对象实例就这千千万万中具体的某个类或接口。再继续,男人和女人能被抽象为People类,这是因为男人和女人都有很多相同的特征,那千千万万类和接口都有名字、方法等也就意味着它们也能被抽象,故Class类就千千万万类和接口的抽象,Class类的对象实例就这千千万万中具体的某个类或接口。

  Class类作为类和接口的抽象,它存在的意义在哪里呢?它是类和接口的抽象,它的实例是某个具体的类,那为何不直接通过People p = new People()来实例化一个People对象呢?而是麻烦的需要先获取Class类,再获取它的实例,再通过它的实例创造一个类的对象。

  通常情况下使用Class类来获取某个类在实际编码中确实不常见,但这是JVM的执行机制。每个类被创建编译过后都对应一个.class文件,这个.class文件包含了它对应的Class对象,这个类的Class对象会被载入内存,它就会被JVM来创建这个类的所有实例对象。当然在实际运用中,Java的反射机制是离不开Class类的。 所以,回到Object类的getClass方法,提供的是该类的Class对象,每个类都可以通过这个方法获取它对应的Class对象。

public native int hashCode()

  这个方法是一个native本地方法,它的具体实现是有C++实现的。在Java程序中,每个对象实例(注意是对象实例)都有一个唯一的hashCode值(哈希码值),可以通过对比两个对象实例是否相同来判断是否指向同一个对象实例。

  它有这么一个性质,例如判断两个String字符串是否相等,使用“==”表示的两个对象的引用是否相等,而使用equals则表示两个对象的值是否相等。equals相等,则hashCode值一定相等;hashCode值相等,而equals不一定相等。并且它被应用在我们熟悉的Map集合中。

public boolean equals(Object obj)

  该方法用于比较两个对象是否“相等”。之所以相等有引号,是这个相等在代码逻辑中分为两种情况:对象引用相等;对象值相等。

  Object中equals方法有一个默认实现,它直接使用“==”进行比较,也就是说在Object中equals和“==”是等价的。但是在String和Integer等中, equals方法是被重写的,它们的equals方法代表的是值相等,而不是引用相等。

  注意在重写equals方法时,需要遵守以下几个原则:

  1. 自反性。也就是说自己调用equals方法和自己比较时,必须返回true。(自己都不和自己相等,那谁才相等)

  2. 对称性。我和你比较返回ture,你和我比较也要返回true,a.equals(b)返回true,b.equals(a)返回true。

  3. 传递性。这个根据名字就很好理解。a.equals(b)返回true,b.equals(c)返回true,a.equals(c)也需要返回true。

  4. 一致性。也就是在没有修改两个对象的情况下,多次调用返回的结果应该是一样的。

  5. 非空性。非空对象与null值比较必须返回false。

e.g.

 package com.coderbuff.customequals;

 /**
* Studen类
* Created by Kevin on 2018/2/10.
*/
public class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 性别
*/
private byte sex; public Student() {
} public Student(String name, int age, byte sex) {
this.name = name;
this.age = age;
this.sex = sex;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public byte getSex() {
return sex;
} public void setSex(byte sex) {
this.sex = sex;
} @Override
public boolean equals(Object obj) {
if (!(obj instanceof Student)) {
return false;
}
Student other = (Student)obj;
if (other.getName().equals(this.name) && other.getAge() == this.age && other.getSex() == this.sex) {
return true;
}
return false;
}
}

测试代码:

 package com.coderbuff.customequals;

 import org.junit.Before;
import org.junit.Test; import static org.junit.Assert.assertEquals; /**
* 测试Student类equals方法
* Created by Kevin on 2018/2/10.
*/
public class StudentTest {
private Student a, b, c; @Before
public void setUp() {
a = new Student("Kevin", 23, (byte)0);
b = new Student("Kevin", 23, (byte)0);
c = new Student("Kevin", 23, (byte)0);
} /**
* 自反性
*/
@Test
public void testReflexive() {
assertEquals(true, a.equals(a));
} /**
* 对称性
*/
@Test
public void testSymmetric() {
assertEquals(true, a.equals(b));
assertEquals(true, b.equals(a));
} /**
* 传递性
*/
@Test
public void testTransitive() {
assertEquals(true, a.equals(b));
assertEquals(true, b.equals(c));
assertEquals(true, a.equals(c));
} /**
* 一致性
*/
@Test
public void testConsistent() {
for (int i = 0; i < 100; i++) {
assertEquals(true, a.equals(b));
}
} /**
* 非空性
*/
@Test
public void testNonNullity() {
assertEquals(false, a.equals(null));
}
}

  从上面重写的equals的测试结果来看是通过的,但是实际上是错误的,如果在你的程序中只使用这个类的equals方法,而不会使用到集合,那没问题,但是一旦使用Map集合,上面的错误立马暴露。例如如果运行以下测试方法,返回的结果将会是null。

e.g.

 /**
* 测试equals方法
*/
@Test
public void testMap() {
Map<Student, String> map = new HashMap<>();
map.put(a, "this is map.");
assertEquals("this is map.", map.get(b));
}

  但明明逻辑中a和b是相等的,b也应该能取出值来,这就是没有重写hashCode方法a和b对象的hashCode值不一致导致的问题,这不是bug,这是没有满足JDK的规定。上面的hashCode方法末尾提到了equals相等,hashCode值也相等;hashCode值相等,equals不一定相等。上面的代码3个对象的hashCode值是不相等的,所以导致b不能从Map中取出相应的值,相等的对象必须具有相等的hashCode值。

  这就涉及到如何设计一个良好运作的散列函数。一个好的散列函数,更能较为平均地散列到散列通中,而不是造成大量的散列冲突,大量的散列冲突会使得散列表退化成链表形式,这会使得效率大大降低。设计上要设计一个好的散列函数并不是一件容易的事,下面为Student类设计的散列函数是根据《Effective Java》中的解决办法。

 @Override
public int hashCode() {
int result = 17;
result = name.hashCode() + result;
result = 31 * result + age;
result = 31 * result + (int)sex;
return result;
}

测试方法:

 /**
* 测试hashCode值是否相等
*/
@Test
public void testHashCode() {
assertEquals(true, a.hashCode() == b.hashCode());
}

protected native Object clone() throws CloneNotSupportedException

  “克隆”,也称为“复制”。这个方法在访问权限不同于其他方法,它在Object类中是protected修饰的方法。protected意味着只能在它的子类调用Object类中的clone方法,而不能直接在外部调用,想要使用对象的clone方法,需要在方法中调用父类的clone方法,并且需要实现Cloneable接口。

  这个方法如其名,复制一个相同的对象示例,而不是将引用拷贝给它,所以复制后的对象实例一个新的对象示例。

e.g.

 //还是上面Student类,重写clone方法,并且实现Cloneable接口
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}

测试方法:

 /**
* 测试clone方法
* @throws CloneNotSupportedException
*/
@Test
public void testClone() throws CloneNotSupportedException {
Student cloneA = (Student) a.clone();
assertEquals(false, cloneA == a);
assertEquals(true, cloneA.equals(a));
}

  这个方法呢,坑比较多。它有一个比较重要的地方——“深复制”和“浅复制”。

  一个复杂类的成员属性有“基本数据类型”和“引用数据类型”。

  假设现在需要对对象A复制一个对象B。

  对于浅复制来讲,对象B的基本数据类型和A的基本数据类型它们相等,且互相不会受到影响。但是如果修改了对象B中的引用数据类型,此时将会影响到A对应的引用数据类型。

  但对于深复制来讲,对象B就是完完全全和A一样的对象实例,不管是基本的还是引用的数据类型都不会相互影响。

  如果像上面的示例代码重写clone方法,它所实现的就是浅复制(当然在Student类中并没有引用类型),如果在Student类中有一个Course引用类型的话,想要它实现深复制需要完成以下2点:

  1. Course本身也已经实现Cloneable接口,且重写了clone方法。

  2. Student类中在调用了父类的clone方法后,还需要调用Course的clone方法。

  如下所示:

 @Override
protected Student clone() throws CloneNotSupportedException {
Student s = (Student) super.clone();
s.course = (Course) this.course.clone();
return s;
}

  重写实现clone方法时一定要仔细,切记需要调用父类的clone方法。

public String toString()

  返回类的一些描述信息。“最佳的编程实践”是最好对每个类都重写toString方法。在Object类中这个方法的实现是调用getClass返回类信息+@符号+16进制的hashCode值。

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException;

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException

  这几个方法拿到一块来说是因为它们用于多线程并发编程当中。

  上面的5个方法实际上只有前3个核心方法,后两个只是wait方法的重载而已。我们先了解前3个,后两个也会迎刃而解。

  开头提到这用于多线程并发编程中,众所周知Java应用程序号称“一次编译,到处运行”的奥秘就在于Java应用程序是运行在Java虚拟机(JVM)之上的,而JVM的设计实际上是类同于一个操作系统的。在操作系统中谈的更多是进程与进程之间的关系,例如进程间的同步与通信,进程和进程的并发等等。Java应用程序在操作系统中只是1个进程,在JVM中就蕴含了N个线程,就类似于操作系统的N个进程,所以在Java中提及更多的是线程间的同步与通信,线程和线程的并发等。

  Java中的线程用于自己的运行空间,称之为虚拟机栈,这块空间是线程所独占的。如果Java应用程序中的N个线程相互孤立互不干扰的运行,可以说这个应用程序并没有多大的价值,最大的价值是N个线程之间相互配合完成工作。那自然就会涉及到多个线程间的通信问题。在本文只着重讲解线程间的通信,而对于线程安全这个议题不做过多深究。

  在操作系统中,对于进程间同步有这么一个定义:为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。将定义中的进程换为线程,即可当做在Java中对同步的定义。

  例如:线程T1运行到某处时,需要线程T2完成另一项任务才能继续运行下去,此时T1对CPU的占用就需要让位给T2,而T1此时只能等待T2完成。当T2完成任务后通知T1重新获取对CPU的占用继续完成未完成的任务。这个例子就是简单的同步示例,其中涉及到的等待、通知即表示线程间的通信,wait和notify、notifyAll所代表的就是线程间的通信。

  所以,Object类中的wait和notify、notifyAll方法是用于线程间的通信,且它们的调用需要在获取对象锁的情况下才可以(也就是说需要在线程安全的条件下调用),在具体一点是只有在synchronized关键字所修饰的同步方法或者同步代码块才可以使用,并不是任何地方都可以调用。这里有一个有关线程间通信的经典示例——生产者消费者模型。通过仔细咀嚼这个模型我们能好的理解线程间的通信。

e.g.

 package com.coderbuff.communication;

 import java.util.Queue;

 /**
* Producer
* Created by Kevin on 2018/2/13.
*/
public class Producer implements Runnable{
Queue<String> queue; public Producer(Queue<String> queue) {
this.queue = queue;
} @Override
public void run() {
synchronized (queue) {
try {
while (queue.size() == 10) {
System.out.println("生产线程" + Thread.currentThread().getId() + "执行,队列为满,生产者等待");
queue.wait();
}
queue.add(String.valueOf(System.currentTimeMillis()));
System.out.println("生产线程" + Thread.currentThread().getId() + "执行,队列不为满,生产者生产:" + String.valueOf(System.currentTimeMillis()) + ",容量" + queue.size());
queue.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
 package com.coderbuff.communication;

 import java.util.Queue;

 /**
* Consumer
* Created by Kevin on 2018/2/13.
*/
public class Consumer implements Runnable{
Queue<String> queue; public Consumer(Queue<String> queue) {
this.queue = queue;
} @Override
public void run() {
synchronized (queue) {
try {
while (queue.isEmpty()) {
System.out.println("消费线程" + Thread.currentThread().getId() + "执行,队列为空,消费者等待");
queue.wait();
}
System.out.println("消费线程" + Thread.currentThread().getId() + "执行,队列不为空,消费者消费:" + queue.remove() + ",容量" + queue.size());
queue.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

测试方法:

 package com.coderbuff.communication;

 import org.junit.Before;
import org.junit.Test; import java.util.LinkedList;
import java.util.Queue; /**
* Test Producer & Consumer
* Created by Kevin on 2018/2/13.
*/
public class ProducerConsumerTest {
Queue<String> queue; @Before
public void setUp() {
queue = new LinkedList<>();
} @Test
public void test() {
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Producer(queue)).start();
new Thread(new Producer(queue)).start();
}
}

  这个生产者消费者模型很好的演示了线程间是如何通过Object中的wait和notify、notifyAll方法进行通信的。在程序中使用的是notifyAll方法而不是notify方法,实际当中也多用notify方法。它们俩的区别就是notify方法只会唤醒等待队列中的一个线程使之进入同步队列进而使之有了争夺CPU执行的权力,而notifyAll方法是会唤醒等待队列中的所有线程使之进入同步队列。注意,它们都是让等待线程从等待队列进入同步队列,它们仅仅是拥有了争夺CPU的权力,调用这两个方法不代表它们就会拥有CPU执行的权力。

  至于wait方法另外个重载方法:

  wait(long):等待N毫秒没用收到通知就超时返回;

  wait(long, int):同样也是超时等待指定的时间没有收到通知超时返回,不同的是第二个参数可以达到更加细粒度的时间控制——纳秒。

protected void finalize() throws Throwable { }

  这个方法在对象在被GC前会被调用,需要着重强调的是,千万不要依赖此方法在其中做一些资源的关闭,因为我们不能保证JVM何时进行GC,所以我们也就无法判断该方法何时会被执行,除非你不在意它执行的时间,否则千万不要重写它。它不能当做是C++中的析构函数。

这是一个能给程序员加buff的公众号 

万类之父——Object的更多相关文章

  1. Java类的根Object

    一.Object类介绍 Object全名java.lang.Object,java.lang包在使用的时候无需显示导入,编译时由编译器自动导入.Object类是类层次结构的根,Java中所有的类从根本 ...

  2. 13_Python的面向对象编程-类class,对象object,实例instance

    1.面向对象概述 1.类是用来描述对象的工具,把拥有相同属性和行为的对象分为一组     2.对象是由类实例化出来的一个具体的对象         属性: 对象拥有的名词,用变量表示         ...

  3. 把一个类(或者Object)转换成字典

    直接上代码:把一个类转换成object,然后在转换成字典 internal static IDictionary<string, string> GetDictionary(this ob ...

  4. python定义类()中写object和不写的区别

    这里需要说明一下: python3中,类定义默认继承object,所以写不写没有区别 但在python2中,并不是这样 所以此内容是针对python2的,当然python3默认继承,不代表我们就傻乎乎 ...

  5. Python中新式类 经典类的区别(即类是否继承object)

    首先什么是新式类 经典类呢: #新式类是指继承object的类 class A(obect): ........... #经典类是指没有继承object的类 class A: ........... ...

  6. Java类的设计----Object 类

    Object类 Object类是所有Java类的根父类如果在类的声明中未使用extends关键字指明其父类,则默认父类为Object类 public class Person { ... } 等价于: ...

  7. Object类 任何类都是object类的子类 用object对象接收数组 object类的向上向下转型

    任何类都是object类的子类 用object对象接收数组 object类的向上向下转型

  8. 最终父类【根类】:Object类&Objects类

    一.java.lang.Object类 1.Object类介绍 Object类是所有类的父类.一个类都会直接或间接继承自该类: ​ 该类中提供了一些非常常用的方法! 2.toString()方法 A: ...

  9. 深入理解ClassLoader(四)—类的父委托加载机制

    上几次我们介绍到了JVM内部的几个类加载器,我们来重新画一下这个图,再来看一下他们之间的关系.

随机推荐

  1. EMC题2

    易安信笔试题分享:1 protected成员函数能被肿么调用2 “has-a” relationship是指的啥,答案有instance, reference, pointer等...3 int, c ...

  2. 01-01_环境准备_pyenv

    本文重点: 了解pyenv pyenv下载及安装 pyenv 使用 安装ipython 一.了解pyenv 经常遇到这样的情况: 系统自带的 Python 是 2.6,自己需要 Python 2.7 ...

  3. Spring的IOC分析(一)

    我们学习Spring之前需要对23种java的设计模式的9种有一定的理解,设计模式为了解耦,Spring也是在解耦的方向上设计的,所以设计模式要理解一下,它当中用到了很多. 单例模式(写法很多钟,7种 ...

  4. 关于React Native的那些坑

    好久没写博客了,特地把之前接触React Native时遇到的坑总结一下. 初始化一个React Native项目时,可能会遇到以下这些坑: 1.项目版本号与安卓模拟器中安装的 compileSdkV ...

  5. 织梦CMS首页调用分类信息栏目及列表方法

    不懂代码,搜索学习一晚上,都是说调用特定栏目分类信息列表的,用这个代码 {dede:arclistsg row='10' titlelen='24' orderby='pubdate' channel ...

  6. HTML 5  标签

    HTML 5 标签 标签定义文档中的节(section.区段).比如章节.页眉.页脚或文档中的其他部分. E 9+.Firefox.Opera.Chrome 和 Safari 标签. 注释:IE 8 ...

  7. java垃圾回收的分类

    1.线程数 分为串行垃圾回收器和并行垃圾回收器.串行垃圾回收器一次只使用一个线程进行垃圾回收:并行垃圾回收器一次将开启多个线程同时进行垃圾回收.在并行能力较强的 CPU 上,使用并行垃圾回收器可以缩短 ...

  8. sqlite入门基础(一):sqlite3_open,sqlite3_exec,slite3_close

    打开数据库链接sqlite3_open用法 原型: int sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sq ...

  9. 关于 httpUrlConnection 的 setDoOutput 与 setDoInput的区别

    httpUrlConnection.setDoOutput(true) httpUrlConnection.setDoInput(true) 这两个方法在develope的httpUrlConnect ...

  10. CentOS6.x机器安装Azure CLI2.0【1】

    安装Azure CLI 2.0的前提是:机器中必须有 Python 2.7.x 或 Python 3.x.如果机器中没有其中任何一个Python版本,请及时安装 1.准备一台CentOS 6.9的机器 ...