Java Object

Object

      • 1 Object概述
      • 2 成员方法
        • 2.1 getClass()方法
        • 2.2 hashCode()方法
        • 2.3 toString()方法
        • 2.4 equals()方法
        • 2.5 finalize()方法
        • 2.6 clone()方法
      • 3 作业

1 Object概述

Object:
    a.概述:Object是类层次结构的根类,也就是所有类的父类。所有对象(包括数组)都实现这个类的方法。
    b.成员变量:没有成员变量。
    c.构造方法:
        Object() 只有一个无参构造方法
    d.成员方法:
        protected Obeject() clone() 获取拷贝对象,默认是浅拷贝。
        boolean equals(Object obj) 判断两个对象是否相等,默认比较的是地址。
        protected void finalize() 当GC确定不存在对该对象的更多引用时,由对象的GC调用此方法。
        Class getClass() 获取字节码文件对象(反射),或者说返回此Object的运行时类。
        int hashCode() 返回对象的哈希值,是一个十进制整数,引入此方法的目的是为了提高哈希表的性能。默认是将对象的内存地址转换成一个整数(哈希值)来实现的。
        String toString() 默认返回:运行时类名字 + @ + 对象的hashCode的十六进制的字符串形式,建议所有类重写此方法。
public class ObjectDemo1 {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();
        System.out.println(obj1); // java.lang.Object@1b6d3586,调用toString()方法
        System.out.println(obj2); // java.lang.Object@4554617c,调用toString()方法
        System.out.println(obj1.toString()); // java.lang.Object@1b6d3586
        System.out.println(obj2.toString()); // java.lang.Object@4554617c
    }
}

2 成员方法

2.1 getClass()方法

1.public final Class getClass()
    被final修饰,所以子类不能重写此方法,即任何一个对象,它的getClass()行为是一致的。
    返回此Object的运行时类,或者说返回字节码文件对象(反射)。

Class:类类型
    概述:Class类的实例表示正在运行的Java应用程序中的类或接口。枚举是一种类,注解是一种接口。
    public String getName():返回Class对象的运行时类的全限定名。

Student:

package com.cskaoyan.a_object;

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /* getter & setter 省略 */

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

ObjectDemo2:

public class ObjectDemo2 {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Class<?> cl1 = obj1.getClass();
        System.out.println(cl1); // class java.lang.Object // Class类中重写了toString()方法
        String name1 = cl1.getName();
        System.out.println("name1 = " + name1); // name1 = java.lang.Object

        System.out.println("--------------------------");

        Object obj2 = new Student(); // 多态
        Class<?> cl2 = obj2.getClass();
        System.out.println(cl2); // class com.cskaoyan.a_object.Student // Class类中重写了toString()方法
        String name2 = cl2.getName();
        System.out.println("name2 = " + name2); // name2 = com.cskaoyan.a_object.Student
        // 正在运行的类是Student类(子类),Caution!!!
    }
}

2.2 hashCode()方法

2.public int hashCode()
    返回对象的哈希值,是一个十进制整数,引入此方法的目的是为了提高哈希表的性能。默认是将对象的内存地址转换成一个整数(哈希值)来实现的。

hashCode()的常规协定:
    a.一致性: 如果一个对象进行比较的信息没有发生修改,那么在一次程序运行期间,它们的hash值要一致。
    b.相等性: 如果两个对象通过 equals 比较是相等的,那么它们的 hashcode 也要相等。
    c.哈希碰撞概率低: 如果两个对象不相等,那么它们的 hashcode 最好不相等,这个可以提高哈希表的性能。
public class ObjectDemo3 {
    public static void main(String[] args) {
        Object obj1 = new Object();
        System.out.println(obj1.hashCode()); // 460141958
        Object obj2 = new Object();
        System.out.println(obj2.hashCode()); // 1163157884
    }
}

补充:哈希函数(算法)

(1)哈希函数

哈希函数是把任意长度的输入数据映射成固定长度(字长)的输出数据的一种函数。哈希函数是不可逆的,也就是不能通过结果值,来反推输入值。

哈希冲突:输入值不同,但是它们的哈希值相同。

好的哈希函数应该具备的特征:
	a.很难通过哈希值,反推出输入值。
	b.对输入数据非常敏感,输入数据哪怕只有一个bit不同,哈希值也要表现出不相关性(或者表现出无关性)。
	c.对于任意两个输入数据,哈希值相同的概率很小。(哈希冲突概率很小)
	d.运算效率要比较高,对于很长的数据(如大文本),也要很快地计算出哈希值。
	总的来说,好的哈希函数是在模拟随机映射。

经典的哈希函数包括:MD4, MD5, SHA家族。

(2)如何自己设计一个哈希函数(算法)

常见的哈希函数有:加法哈希、乘法哈希、位运算哈希、混合哈希(将前面三个结合在一起)。见下图。

Java Object_第1张图片

**注意:**对于乘法哈希,一般选择乘以31,即hash * 31,乘法是比较耗时的,所以底层会转化为hash << 5 - hash,不会选择乘以诸如28,25等,因为这样仍然需要乘法运算。

2.3 toString()方法

3.public String toString():
    返回该对象的字符串形式,结果应该是一个简明且易于读懂的信息表达式。建议所有子类都重写此方法。
    默认实现:return getClass().getName() + "@" + Integer.toHexString(hashCode());

注意事项:
    直接打印对象,默认会调用该对象的toString()方法。

Student:

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /* getter & setter */

    @Override // 重写toString()方法
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

ObjectDemo4:

public class ObjectDemo4 {
    public static void main(String[] args) {
        Object obj = new Object();
        String s = obj.toString();
        System.out.println(s); // 主动调用toString()方法 java.lang.Object@1b6d3586
        System.out.println(obj.getClass().getName() + "@" + Integer.toHexString(obj.hashCode())); // toString()方法的底层实现 java.lang.Object@1b6d3586
        System.out.println(obj); // 直接打印对象,默认会调用toString()方法 java.lang.Object@1b6d3586

        System.out.println("----------------------");

        Student stu = new Student("Heson_z", 18);
        System.out.println(stu); // Student类重写了toString()方法 Student{name='Heson_z', age=18}
    }
}

2.4 equals()方法

4.public boolean equals(Object obj):
    判断两个对象是否相等。
    默认实现:return this == obj; 即默认比较的是两个对象的内存地址。

实体类:
    一个对象对应一个实体,不需要重写equanl()方法。
值类:
    如String、Point(x, y)、之前写的Rectangle(矩形) 都属于值类,需要重写equals()方法。
    对于Rectangle来说:
        关键域:x, y
        衍生域: area, perimeter
        无关域: color
    应该根据关键域来重写equals()方法。

equals()方法的常规协定:
    equals()方法在非空对象引用上实现相等关系:
        自反性:对于任何非空引用值 x,x.equals(x) 都应返回true。
        对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回true时,x.equals(y)才应返回true。
        传递性:对于任何非空引用值 x、y 和 z,如果x.equals(y)返回 true,并且y.equals(z)返回 true,那么 x.equals(z) 应返回 true。
        一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
        非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。

注意事项:
    当equals()方法被重写时,通常有必要重写hashCode()方法。比如在Set集合中,添加的元素重写了equals()方法,但是没有重写hashCode()方法,可能会得到错误的结果。

instanceof:
    null instanceof 类名 --> false
    obj instanceof Object --> true

Point:

import java.util.Objects;

public class Point {
    int x;
    int y;

    public Point() {
    }

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    /* getter & setter */

    /* toString() */

    // 这不是重写方法,因为重写要求方法名和形参相同,这里形参的类型是Point,而equals()方法的形参是Object类型。Caution!!!
    /*@Override
    public boolean equals(Point point) {
        return false;
    }*/
    @Override // 使用IDEA自动生成的代码也是这样的
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || this.getClass() != obj.getClass()) return false;
        Point point = (Point) obj;
        return (this.x == point.x) && (this.y == point.y);
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

ColorPoint:

import java.util.Objects;

public class ColorPoint extends Point {
    String color;

    public ColorPoint() {
    }

    public ColorPoint(int x, int y, String color) {
        super(x, y);
        this.color = color;
    }

    /* getter & setter & toString() */

    @Override // 与IDEA自动生成的equal()方法一致。
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || this.getClass() != obj.getClass()) return false;
        ColorPoint cp = (ColorPoint) obj;
        return super.equals(obj) && Objects.equals(this.color, cp.color);
    }

    @Override
    public int hashCode() {
        int hash = super.hashCode();
        hash = hash * 31 + Objects.hash(color);
        return hash;
    }
}

ShapePoint:

import java.util.Objects;

public class ShapePoint extends Point {
    String shape;

    public ShapePoint() {
    }

    public ShapePoint(int x, int y, String shape) {
        super(x, y);
        this.shape = shape;
    }

    /* getter & setter & toString() */

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || this.getClass() != obj.getClass()) return false;
        ShapePoint sp = (ShapePoint) obj;
        return super.equals(obj) && Objects.equals(this.shape, sp.shape);
    }

    @Override
    public int hashCode() {
        // hash * 一个数,这个数通常选择质数31,因为:hash * 31 = hash << 5 - hash,效率高。Caution!!!
        int hash = super.hashCode();
        hash = hash * 31 + Objects.hashCode(shape);
        return hash;
    }
}

ObjectDemo5:

public class ObjectDemo5 {
    public static void main(String[] args) {
        /*Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = obj1;
        System.out.println(obj1 == obj2); // false
        System.out.println(obj1.equals(obj2)); // false
        System.out.println(obj1 == obj3); // true
        System.out.println(obj1.equals(obj3)); // true*/

        /*Point p1 = new Point(3, 4);
        Point p2 = new Point(3, 4);
        Student student = new Student();
        System.out.println(p1.equals(p2)); // true
        System.out.println(p1.equals(student)); // false*/

        /*System.out.println(null instanceof Object); // false
        Point p = new Point(3, 4);
        System.out.println(p instanceof Object); // true*/

        /*Point p1 = new Point(3, 4);
        ColorPoint cp = new ColorPoint(3, 4, "red");
        System.out.println(p1.equals(cp)); // false
        System.out.println(cp.equals(p1)); // false*/
        /*ShapePoint sp = new ShapePoint(3, 4, "dot");
        Point p1 = new Point(3, 4);
        ColorPoint cp = new ColorPoint(3, 4, "red");
        System.out.println(sp.equals(p1)); // false
        System.out.println(p1.equals(cp)); // false
        System.out.println(sp.equals(cp)); // false*/
        // 使用IDEA自动生成的equals()方法,对于字父类对象的判断都会返回false,因为它们的运行时类不同,返回false;
        // 这违反了里氏替换原则:父类出现的地方都可以用子类来代替。

        Point p = new Point(3, 4);
        Point p1 = new Point(3, 4);
        System.out.println(p.equals(p1)); // true
        System.out.println(p.hashCode()); // 1058 重写了Point的hashCode()方法,所有p和p1的hashCode()结果相同。
        System.out.println(p1.hashCode()); // 1058
    }
}

2.5 finalize()方法

5.protected void finalize() thorws Throwable
    概述:当GC回收此对象时,会自动调用此方法。子类重写finalize()方法,以释放资源。
    默认实现:方法体为空,什么都没做。

    finalize():
        1.可以手动调用,该对象没有被回收。
        2.当GC回收这个对象时,会自动调用此方法。
        3.释放资源最好不要放在finalize()里面。
            因为GC线程是一个优先级低的线程,当一个对象变成垃圾后,并不会马上被回收,因此资源不会得到立即释放。
                那么应该如何释放资源呢? --> try ... catch ... finally 异常处理语句来释放资源。
public class ObjectDemo6 {
    public static void main(String[] args) {
        Student s = new Student("Heson_z", 18);
        s.finalize(); // clear resources
        System.out.println(s); // Student{name='Heson_z', age=18} 自己调用finalize()方法,该对象没有被回收,仍然能够打印出来。

        s = null; // GC线程优先级低,对象不会立即被回收。

        System.gc(); // clear resources 强制执行gc线程,自动调用finalize()方法,进行垃圾回收。
    }
}

2.6 clone()方法

6.protected Object clone() throws CloneNotSupportedException:
    创建并返回该对象的一个"副本",默认实现的是浅拷贝。

异常:CloneNotSupportedException
    解决方法:对于需要克隆的对象,其对应类必须实现Cloneable接口,否则抛出上述异常。

Cloneable接口(空接口,标记接口,表示具有克隆能力):
    如果某个类(比如Student类)实现了Cloneable接口,则student.clone()方法可以合法地对该类(Student类)实例进行按字段拷贝。
    如果某个类(比如Student类)没有实现Cloneable接口,则Student类的实例student调用clone()方法会抛出CloneNotSupportedException。
    Cloneable接口没有定义任何内容,通常这样的接口叫做空接口,在Java中往往起标记作用。

Java默认实现的是深拷贝还是浅拷贝?
    浅拷贝
如何实现多级深拷贝呢?
    在FirstLevel、SecondLevel等例子中演示。

Student:

import java.util.Objects;

public class Student implements Cloneable { // 必须要实现Cloneable接口,对象才能进行拷贝。
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /* getter & setter & toString() */

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override // 返回值类型被父类返回值类型兼容,属于方法重写。
    protected Student clone() throws CloneNotSupportedException {
        Student s = (Student) super.clone(); // 调用Object的clone()方法,默认是浅拷贝。
        s.name = new String(name); // 进行深拷贝(一级深拷贝)
        return s; // 返回的克隆对象实现了深拷贝。
    }
}

ObjectDemo7:

public class ObjectDemo7 {
    public static void main(String[] args) throws CloneNotSupportedException {
        // Object obj = new Object();
        // obj.clone(); // clone()使用protected修饰,且Object在java.lang包下,子类在当前包下,只能通过子类对象访问。Caution!!!

        Student s1 = new Student("Henson_z", 18);
        Student s2 = s1.clone(); // 必须先确保Student类实现了Cloneable接口,否则会抛出CloneNotSupportedException。s2是深拷贝
        System.out.println(s1.equals(s2));  // true
        System.out.println(s2); // Student{name='Henson_z', age=18}
        System.out.println(s1 == s2); // false
        System.out.println(s1.getName().equals(s2.getName())); // ture;
        System.out.println(s1.getName() == s2.getName()); // false 因为是深拷贝(一级深拷贝)
    }
}

(1)什么是浅拷贝和深拷贝?

以上面的Student类为例,浅拷贝和深拷贝分别如下所示:

Java Object_第2张图片

(2)实现FirstLevel深拷贝(两级深拷贝)

FirstLevel:

/*
对FirstLevel进行深拷贝:
    分析:
        1.实现Cloneable接口;
        2.重写clone()方法。
 */

public class FirstLevel implements Cloneable {
    int a;
    SecondLevel secondLevel;

    public FirstLevel() {
    }

    public FirstLevel(int a, SecondLevel secondLevel) {
        this.a = a;
        this.secondLevel = secondLevel;
    }

    @Override
    protected FirstLevel clone() throws CloneNotSupportedException {
        // return ((FirstLevel) super.clone()); // a处
        FirstLevel firstLevel = (FirstLevel) super.clone(); // 调用父类的clone()方法

        // 对SecondLevel进行深拷贝不是FirstLevel的职责,所以不应该写在这里,将其放到SecondLevel中。
        /*SecondLevel secondLevel = new SecondLevel();
        secondLevel.b = firstLevel.secondLevel.b;
        secondLevel.s = new String(firstLevel.secondLevel.s);
        firstLevel.secondLevel = secondLevel;
        return firstLevel;*/

        // 正确写法
        firstLevel.secondLevel = secondLevel.clone(); // 进行深拷贝
        return firstLevel;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        SecondLevel secondLevel = new SecondLevel(20, "java");
        FirstLevel firstLevel = new FirstLevel(10, secondLevel);
        FirstLevel clone = firstLevel.clone();
        System.out.println(clone.secondLevel == firstLevel.secondLevel); // false 深拷贝
        System.out.println(clone.secondLevel.s == firstLevel.secondLevel.s); // false 深拷贝
        // 如果clone()方法只有a处一行代码,则上面两行都打印true。
    }
}

SecondLevel:

public class SecondLevel implements Cloneable {
    int b;
    String s;

    public SecondLevel() {
    }

    public SecondLevel(int b, String s) {
        this.b = b;
        this.s = s;
    }

    @Override
    protected SecondLevel clone() throws CloneNotSupportedException {
        SecondLevel secondLevel = (SecondLevel) super.clone(); // 调用父类clone()方法
        secondLevel.s = new String(s); // 进行深拷贝
        return secondLevel;
    }
}

总结: 浅拷贝直接调用父类的clone()方法即可,即return super.clone(),深拷贝需要对每一级的引用都要重新创建对象,但是最开始仍然是先调用父类的clone方法。

思考: 为什么最开始都要使用super.clone()

Object中的clone()方法是native修饰的,底层是C/C++代码实现,执行的时候使用了RTTI(run-time type identification)机制,动态地找到目前正在调用clone()方法地那个引用,根据它的大小申请内存空间,然后进行bitwise复制,将该对象的内存空间完全复制到新的空间中去,从而达到浅拷贝的目的。所以调用super.clone()得到的是当前调用类的副本,而不是父类的副本

3 作业

// 实现FirstLevel深拷贝(三级深拷贝)
public class FirstLevel {
    int i;
    int j;
    SecondLevel sec;

    public FirstLevel(int i, int j, SecondLevel sec) {
        this.i = i;
        this.j = j;
        this.sec = sec;
    }
}

class SecondLevel {
    double j;
    ThirdLevel third;

    public SecondLevel(double j, ThirdLevel third) {
        this.j = j;
        this.third = third;
    }
}

class ThirdLevel implements Cloneable{
    int k;

    public ThirdLevel(int k) {
        this.k = k;
    }
}

FirstLevel:

public class FirstLevel implements Cloneable {
    int i;
    int j;
    SecondLevel sec;

    public FirstLevel(int i, int j, SecondLevel sec) {
        this.i = i;
        this.j = j;
        this.sec = sec;
    }

    @Override
    protected FirstLevel clone() throws CloneNotSupportedException {
        FirstLevel clone = (FirstLevel) super.clone();
        clone.sec = sec.clone();
        return clone;
    }
}

SecondLevel:

public class SecondLevel implements Cloneable {
    double j;
    ThirdLevel third;

    public SecondLevel(double j, ThirdLevel third) {
        this.j = j;
        this.third = third;
    }

    @Override
    protected SecondLevel clone() throws CloneNotSupportedException {
        SecondLevel clone = (SecondLevel) super.clone(); // 最开始都调用父类的clone()
        clone.third = third.clone();
        return clone;
    }
}

ThirdLevel:

class ThirdLevel implements Cloneable {
    int k;

    public ThirdLevel(int k) {
        this.k = k;
    }

    @Override
    protected ThirdLevel clone() throws CloneNotSupportedException {
        // 没有引用数据类型,直接返回父类的clone()方法。
        return ((ThirdLevel) super.clone());
    }
}

Proj2(测试):

public class Proj2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 测试结果
        ThirdLevel thirdLevel = new ThirdLevel(30);
        SecondLevel secondLevel = new SecondLevel(20, thirdLevel);
        FirstLevel firstLevel = new FirstLevel(10, 11, secondLevel); // 原对象
        FirstLevel clone = firstLevel.clone(); // 拷贝对象
        System.out.println(firstLevel.sec == clone.sec); // false
        System.out.println(firstLevel.sec.third == clone.sec.third); // false
    }
}

你可能感兴趣的:(JAVASE,java,开发语言,后端)