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;
- }
- }
- View Code
测试代码:
- 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));
- }
- }
- View Code
从上面重写的 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));
- }
- View Code
但明明逻辑中 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());
- }
- View Code
- 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();
- }
- View Code
测试方法:
- /**
- * 测试 clone 方法
- * @throws CloneNotSupportedException
- */
- @Test
- public void testClone() throws CloneNotSupportedException {
- Student cloneA = (Student) a.clone();
- assertEquals(false, cloneA == a);
- assertEquals(true, cloneA.equals(a));
- }
- View Code
这个方法呢, 坑比较多它有一个比较重要的地方深复制和浅复制
一个复杂类的成员属性有基本数据类型和引用数据类型
假设现在需要对对象 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 和 notifynotifyAll 所代表的就是线程间的通信
所以, Object 类中的 wait 和 notifynotifyAll 方法是用于线程间的通信, 且它们的调用需要在获取对象锁的情况下才可以(也就是说需要在线程安全的条件下调用), 在具体一点是只有在 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();
- }
- }
- }
- }
- View Code
- 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();
- }
- }
- }
- }
- View Code
测试方法:
- 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();
- }
- }
- View Code
这个生产者消费者模型很好的演示了线程间是如何通过 Object 中的 wait 和 notifynotifyAll 方法进行通信的在程序中使用的是 notifyAll 方法而不是 notify 方法, 实际当中也多用 notify 方法它们俩的区别就是 notify 方法只会唤醒等待队列中的一个线程使之进入同步队列进而使之有了争夺 CPU 执行的权力, 而 notify 方法是会唤醒等待队列中的所有线程使之进入同步队列注意, 它们都是让等待线程从等待队列进入同步队列, 它们仅仅是拥有了争夺 CPU 的权力, 调用这两个方法不代表它们就会拥有 CPU 执行的权力
至于 wait 方法另外个重载方法:
wait(long): 等待 N 毫秒没用收到通知就超时返回;
wait(long, int): 同样也是超时等待指定的时间没有收到通知超时返回, 不同的是第二个参数可以达到更加细粒度的时间控制纳秒
protected void finalize() throws Throwable { }
这个方法在对象在被 GC 前会被调用, 需要着重强调的是, 千万不要依赖此方法在其中做一些资源的关闭, 因为我们不能保证 JVM 何时进行 GC, 所以我们也就无法判断该方法何时会被执行, 除非你不在意它执行的时间, 否则千万不要重写它它不能当做是 C++ 中的析构函数
这是一个能给程序员加 buff 的公众号
来源: http://www.bubuko.com/infodetail-2497111.html