乐趣区

Object类入门这一篇就够了

第三阶段 JAVA 常见对象的学习

第一章 常见对象——Object 类

引言:

在讲解 Object 类之前,我们不得不简单的提一下什么是 API,先贴一组百度百科的解释:

API(Application Programming Interface, 应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

简单的说:就是 Java 中有好多现成的类库,其中封装了许多函数,只提供函数名和参数,但隐藏了函数的具体实现,这些可见的部分作为与外界联系的桥梁,也就是我们所称的 API,不过由于 Java 是开源的,所以这些隐藏的实现我们也是可以看到的。

(一) Object 类的概述

(1) Object 是类层次结构的 根类,所有的类都隐式的(不用写 extends)继承自 Object 类。

(2) Java 所有的对象都拥有 Object 默认方法

(3) Object 类的构造方法有一个,并且是 无参构造

这就对应了前面学习中的一句话,子类构造方法默认访问父类的构造是无参构造

我们需要 了解的方法 又有哪些呢?

A: hashCode() B: getClass() C: finalize() D: clone

E: notify() F: notifyAll()

我们需要 掌握的方法 又有哪些呢?

A: toString() B: equals()

(1) hashCode

返回对象的哈希值(散列码),不是实际地址值,不过可以理解为地址值。

它实际上返回一个 int 型整数,用于 确定该对象在哈希表中的索引位置

暂时了解即可,学习 集合框架 内容后将会专篇深入学习

//Student 类

public class Student extends Object {}
//StudentDemo 类

public class StudentDemo {public static void main(String[] args) {Student s1 = new Student();
        System.out.println(s1.hashCode());

        Student s2 = new Student();
        System.out.println(s2.hashCode());

        Student s3 = s1;
        System.out.println(s3.hashCode());
    }
}

// 运行结果:460141958
1163157884
460141958

(2) getClass

返回对象的 字节码文件对象 ,在 反射 篇章详细解释, 暂做简单了解。

public class StudentDemo {public static void main(String[] args) {Student s = new Student();
        Class c = s.getClass();
        String str = c.getName();
        System.out.println(str);

        // 链式编程
        String str2 = s.getClass().getName();
        System.out.println(str2);
    }
}

// 运行结果
cn.bwh_02_getClass.Student
cn.bwh_02_getClass.Student

(3) finalize()

在对象将被垃圾回收器清除前调用,但不确定时间,并且对象的 finalize()方法只会被调用一次,调用后也不一定马上清除该对象。

(4) clone()

以实现 对象的克隆 ,包括成员变量的数据复制,但是它和 两个引用指向同一个对象 是有区别的。

我们先来解释一下 后半句话

如果我们想要复制一个变量,可以这样做 Eg:

int a = 20;
int b = a;

那么我们想要复制一个对象,是不是也可以这样做呢?

//Student

public class Student {
    int age;

    public int getAge() {return age;}

    public void setAge(int age) {this.age = age;}
}
//StudentDemo

public class StudentDemo {public static void main(String[] args) {Student s1 = new Student();
        s1.setAge(20);
        Student s2 = s1;// 将引用赋值
        System.out.println("学生 1 年龄:" + s1.getAge());
        System.out.println("学生 2 年龄:" + s2.getAge());
        System.out.println("------------------------------");

        s2.setAge(25);
        System.out.println("学生 1 年龄:" + s1.getAge());
        System.out.println("学生 2 年龄:" + s2.getAge());
    }
}

// 运行结果
学生 1 年龄:20
学生 2 年龄:20
---------------------------
学生 1 年龄:25
学生 2 年龄:25

很明显,即使将 对象 s1赋值给 对象 s2,但是通过 set 传值的时候,两者仍然会同时变化,并没有起到克隆(独立)的作用,这是因为赋值时只是将存储在栈中,对对象的引用赋值,因此它们两个的引用指向同一个对象(堆中),所以无论如何赋值,只要堆中的对象属性发生了变化,通过引用显示属性的时候,均是相同的。

用法:
  1. 实现 Cloneable 接口
  2. 重写 clone 方法
分类:

浅拷贝: 仅拷贝对象, 不拷贝成员变量, 仅复制了变量的引用, 拷贝前后变量使用同一块内存, 内存销毁后, 必须重新定义(两者同生共死)

深拷贝: 不仅拷贝对象, 也拷贝成员变量(真正意义上的复制, 两者独立无关)

// 浅拷贝

public class Person implements Cloneable{
    private int age = 20;
    
    @Override
    protected Object clone() throws CloneNotSupportedException {return super.clone();
    }
}
// 深拷贝

public class Person implements Cloneable {
    public int age = 20;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 拷贝对象
        Person person = (Person) super.clone();
        // 拷贝成员变量
        person.age = (int) age.clone();
        // 返回拷贝对象
        return person;
    }
}

我们来利用浅拷贝解决刚开始那个问题

//Person 类,写出 get、set 方法、重写 clone 方法

//PersonDemo 类

public class PersonDemo {public static void main(String[] args) throws CloneNotSupportedException {Person p1 = new Person();
        p1.setAge(20);
        Person p2 = (Person) p1.clone();
        System.out.println("第一个人的年龄:"+ p1.getAge() );
        System.out.println("第二个人的年龄:"+ p2.getAge() );
        System.out.println("--------------------------");

        p2.setAge(25);
        System.out.println("第一个人的年龄:"+ p1.getAge() );
        System.out.println("第二个人的年龄:"+ p2.getAge() );
    }
}

运行结果:第一个人的年龄:20
第二个人的年龄:20
--------------------------
第一个人的年龄:20
第二个人的年龄:25

(5) wait、notify 和 notifyAll

三者属于线程通信间的 Api,此部分放在日后讲

(6) toString()——重要

public static toString():返回该对象的字符串表示

//Student 类

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

    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;}
}
//StudentDemo 类

package cn.bwh_04_toString;

public class StudentDemo {public static void main(String[] args) {Student s = new Student();
        s.setName("admin");
        s.setAge(20);
        // 直接输出 s 也会默认的调用 toString 方法
        System.out.println(s.toString());
    }
}

// 通过 set 方法赋值后,直接调用 toString()

运行结果:cn.bwh_04_toString.Student@1b6d3586

很明显,给我们返回这样的信息意义是不大的,所以我们建议对 所有子类重写该方法

// 在 Student 类中重写 toString()

    @Override                                                          
    public String toString() {return "Student[" + "name=" + name + "," + "age=" + age + "]";

// 运行结果:Student[name=admin, age=20]

通过重写 toString 后,结果按照我们所定的规则以字符串的形式输出

(重写后会优先使用类中的 toString 方法)

为什么要用它呢?

主要目的还是为了简化输出

  1. 在类中重写 toString()后,输出类对象就变得有了意义(输出 s 和 s.toString()是一样的,不写也会默认调用),变成了我们实实在在的信息,而不是上面的 cn.bwh_04_toString.Student@1b6d3586。
  2. 如果我们想要多次输出 类中的成员信息,就需要多次书写 get 方法(每用一次就得写)

    System.out.println("Student[" + "name=" + s.getName() + "," + "age=" + s.getAge() + "]");

    而调用 toString()就简单多了

补充:
// 两者等价
toString();
getClass().getName()+ '@' + Integer.toHexString(hashCode())
    
// 输出结果
cn.bwh_04_toString.Student@1b6d3586。

(7) equals()——重要

比较两个对象是否相同

默认情况下,比较的是地址值是否相同。

而比较地址值是没有意义的,所以,一般子类也会重写该方法。

在诸多子类,如 String、Integer、Date 等均重写了 equals()方法

改进思路:

我们可以将比较地址值转变为比较成员变量

  1. 因为 name 为 String 类型,而 String 类型为引用类型,所以不能够用 == 比较,应该用 equal()
  2. String 中默认重写过的 equal()方法是用来比较字符串内容是否相同
  3. 我们要使用的是学生类的成员变量,所以父类 Object 不能调用子类 Student 的特有功能

    所以使用向下转型

    // 重写 v1.0
    public boolean equals(Object o) {Student s = (Student) o;
        if (this.name.equals(s.name) && this.age == s.age) {return true;} else {return false;}
    }
    // 重写 v2.0 (可作为最终版)
    public boolean equals(Object o) {if (this.name == o) {return true;}
        // 测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。if (!(o instanceof Student)) {return false;}
        Student s = (Student) o;
        return this.name.equals(s.name) && this.age == s.age;
    }
   //idea 自动生成版
    @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);
    }
equals() 和 == 的区别

 == 的作用:

基本类型:比较值是否相同

引用类型:比较的就是堆内存地址是否相同

equals 的作用:

引用类型:默认情况下,比较的是地址值。

注:一般选择重写方法,比较对象的成员变量值是否相同,不过一般重写都是自动生成。

结尾:

如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家!^_^

如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

在这里的我们素不相识,却都在为了自己的梦而努力 ❤

一个坚持推送原创 Java 技术的公众号:理想二旬不止

退出移动版