第十八章 Object

第十八章 JDK类库的根类:Object

1 这个老祖宗类中的方法我们需要先研究一下,因为这些方法都是所有子类通用的,任何一个类默认是继承Object,就算没有直接继承,最终也会间接继承。

2 Object类当中有哪些常用的方法?
去哪里找这些方法呢?
第一种方法:去源代码当中。(但是这种方式比较麻烦,源代码也比较难)
第二种方法:去查阅java的类库的帮助文档

什么是API?
  应用程序编程接口
  整个JDK的类库就是一个javase的API
  每一个API都会配置一套API帮助文档
 
目前为止我们只需要知道这几个方法即可:
protected Object clone()  // 负责对象克隆的
  int hashCode()  // 获取对象哈希值的一个方法
  boolean equals(Object obj)  // 判断两个对象是否相等
  String toString()  // 将对象转换成字符串形式
  protected void finalize()  // 垃圾回收器负责调用的方法

 

 

1 Object

1.1 Object类的toString()方法

1、源代码长什么样?
public String toString(){
return getClass().getName + "@" + Integer.toHexString(hashCode());
}
源代码上toString()方法的默认实现是:
类名@对象的内存地址转换为十六进制的形式。

2、SUN公司设计toString()方法的目的是什么?
toString()方法的作用是什么?
toString()方法的设计目的是:通过调用这个方法可以将一个“java对象”转换成“字符串表示形式”

3、其实SUN公司开发java语言的时候,建议所有的子类都去重写toString()方法。toString()方法应该是一个简洁的、详实的、易阅读的。


public class Test01{
public static void main(String[] args){
MyTime t1 = new MyTime(1970, 1, 1);
// 一个日期对象转换成字符串形式的话,我可能还是希望能看到具体的日期信息,而不是一个对象的内存地址。
String s1 = t1.toString();

// MyTime类重写toString()方法之前
System.out.println(s1);  // MyTime@4517d9a3

// MyTime类重写toString()方法之后
System.out.println(s1);  // 1970年1月1日

// System.out.println(t1.toString()); // 1970年1月1日
// 注意:输出引用的时候,会自动调用该引用的toString()方法。
System.out.println(t1);  // 1970年1月1日
}
}

class MyTime{
int year;
int month;
int day;

public MyTime(){}
public MyTime(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}

// 重写toString()方法
// 这个toString()方法怎么重写?
// 越简洁越好,可读性越强越好。
// 向简洁的、详实的、易阅读的方法发展
public String toString(){
return this.year + "年" + this.month + "月" + this.day + "日";
}
}

 

1.2 Object类的equal()方法

Test02.java


1、equals方法的源代码
public boolean equals(Object obj){
return (this == obj);
}
以上这个方法是Object类的默认实现。

2、SUN公司设计equals方法的目的是什么?
以后编程的过程当中,都要通过equal方法来判断两个对象是否相等。
equals方法是判断两个对象是否相等的。

3、我们需要研究一下Object类给的这个默认的equals方法够不够用?
在Object类中的equals方法当中,默认采用的是“==”判断两个java对象是否相等。
而“==”判断的是两个java对象的内存地址,我们应该判断两个java对象的内容是否相等。】
所以Object类中的equals方法不够用,需要子类重写equals方法。

4、判断两个java对象是否相等,不能使用“==”,因为“==”比较的是两个对象的内存地址。



public class Test02{
public static void main(String[] args){
// 判断两个基本数据类型的数据是否相等直接使用“==”即可
int a = 100;
int b = 100;
// 这个“==”是判断a中保存的100和b中保存的100是否相等。
System.out.println(a == b);

// 判断两个java对象是否相等,我们怎么办?能直接使用“==”吗?
// 创建一个日期对象是:2000年2月15日。
MyTime t1 = new MyTime(2000, 2, 15);
// 创建一个日期对象是,但表示的日期也是:2000年2月15日。
MyTime t2 = new MyTime(2000, 2, 15);

// 测试以下,比较两个对象是否相等,能不能使用“==”?
// 这里的“==”判断的是:t1中保存的对象内存地址和t2中保存的对象的内存地址是否相等。
System.out.println(t1 == t2);

// 重写Object equals方法之前(比较的是对象的内存地址)
/*
boolean flag = t1.equals(t2);
System.out.println(flag); // false
*/

// 重写Object equals方法之后(比较的是内容)
boolean flag = t1.equals(t2);
System.out.println(flag);  // true

// 再创建一个新的日期
MyTime t3 = new MyTime(2000, 2, 16);
System.out.println(t1.equals(t3));  // false

// 我们这个程序有bug吗?没有,但是效率低。(怎么改造)
MyTime t4 = null;
System.out.println(t1.equals(t4));
}
}

class MyTime{
int year;
int month;
int day;

public MyTime(){}
public MyTime(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}

/*
// 重写Object类的equals方法
// 怎么重写?复制粘贴,相同的返回值类型、相同的方法名、相同的形式参数列表。
// equals到底应该怎么重写?你自己定,你认为两个对象什么相等的时候表示相等,你自己定
public boolean equals(Object obj){
// 当年相同,月相同,并且日也相同的时候,表示两个日期对象相等。
// 获取第一个日期的年月日
int year1 = this.year;
int month1 = this.month;
int day1 = this.day;

// 获取第二个日期的年月日
// int year2 = obj.year;
// int month2 = obj.month;
// int day2 = obj.day2;
if(obj instanceof MyTime){
MyTime t = (MyTime)obj;
int year2 = t.year;
int month2 = t.month;
int day2 = t.day;
if(year1 == year2 && month1 == month2 && day1 == day2){
return true;
}
}
// 程序能够执行到这里表示日期不相等
return false;
}
*/

/*
// 改良equals方法
public boolean equals(Object obj){
// 如果obj是空,直接返回false
if(obj == null){
return false;
}
// 如果obj不是一个MyTime,没必要比较了,直接返回false
if(!(obj instanceof MyTime)){
return false;
}
// 如果this和obj保存的内存地址相同,没必要比较了,直接返回true
// 内存地址相同的时候指向的堆内存的对象肯定是同一个。
if(this == obj){
return true;
}
// 程序能够执行到此外说明什么?
// 说明obj不是null,obj是MyTime类型。
MyTime t = (MyTime)obj;
if(this.year == t.year && this.month == t.month && this.day == t.day){
return true;
}
// 程序通到这里返回false
return false;
}
*/

/*
// 再次改良equals方法
public boolean equals(Object obj){
// 如果obj是空,直接返回false
if(obj == null){
return false;
}
// 如果obj不是一个MyTime,没必要比较了,直接返回false
if(!(obj instanceof MyTime)){
return false;
}
// 如果this和obj保存的内存地址相同,没必要比较了,直接返回true
// 内存地址相同的时候指向的堆内存的对象肯定是同一个。
if(this == obj){
return true;
}
// 程序能够执行到此外说明什么?
// 说明obj不是null,obj是MyTime类型。
MyTime t = (MyTime)obj;
return this.year == t.year && this.month == t.month && this.day == t.day;
}
*/

public boolean equals(Object obj){
if(!(obj instanceof MyTime) || obj == null){
return false;
}

if(this == obj){
return true;
}

MyTime t = (MyTime)obj;
return this.year == t.year && this.month == t.month && this.day == t.day;
}

}

Test03.java

java语言当中的字符串String有没有重写toString方法,有没有重写equals方法。

总结:
1、String类重写了equals方法,比较两个字符串不能使用“==”,必须使用equals

结论:
java中什么类型的数据需要使用“==”判断
java中基本的数据类型比较是否相等,使用==
java中什么类型的数据需要使用equals判断
java中所有的引用数据类型统一使用equals方法来判断是否相等。


public class Test03{
public static void main(String[] args){
// 大部分情况下,采用这样的方式创建字符串对象
String s1 = "hello";
String s2 = "abc";

// 实际上String是一个类,不属于基本数据类型。
// 既然String是一个类,那么一定存在构造方法。
String s3 = new String("Test01");
String s4 = new String("Test01");
// new两次,两个对象内存地址,s3保存的内存地址和s4保存的内存地址不同。
// == 判断的是内存地址,不是内容
// System.out.println(s3 == s4); // false

// 比较两个字符串能不能使用双等号?
// 不能,必须调用equals方法。
System.out.println(s3.equals(s4));  // true

// String类有没有重写toString方法呢?
String x = new String("xiaohong");
// 如果String没有重写toString()方法,输出结果:java.lang.String@十六进制的地址
// 经过测试:String类已经重写了toString()方法。
System.out.println(x.toString());  // xiaohong
System.out.println(x);  // xiaohong

}
}

Test04.java


// String对象比较的时候最好使用equals方法。
public class Test04{
public static void main(String[] args){
/*
Student s1 = new Student(111, "hello学校");
Student s2 = new Student(111, "hello学校");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
*/

Student s1 = new Student(111, new String("hello学校"));
Student s2 = new Student(111, new String("hello学校"));
System.out.println(s1 == s2);  // false
System.out.println(s1.equals(s2));  // true

}
}

class Student{
// 学号
int no;  // 基本数据类型,比较时使用:==

// 所在学校
String school;  // 引用数据类型,比较时使用equals方法

public Student(){}
public Student(int no, String school){
this.no = no;
this.school = school;
}

// 重写toString方法
public String toString(){
return "学号" + no + ", 所在学校名称" + school;
}

// 重写equals方法
// 需求:当一个学生的学号相等,并且学校相同时,表示同一个学生。
// 思考:这个equals该怎么重写呢?
public boolean equals(Object obj){
if(obj == null || !(obj instanceof Student)){
return false;
}
if(this == obj){
return true;
}
Student s = (Student)obj;
return this.no == s.no && this.school.equals(s.school);  // 针对上面两种创建对象的方式都可以
// return this.no == s.no && this.school == s.school; // 针对方式1创建的对象可以

}
}

Test05.java

// equals方法重写的时候要彻底
public class Test05{
public static void main(String[] args){
User u1 = new User("xiaohong", new Address("楚雄", "冬瓜镇", "111111"));
User u2 = new User("xiaohong", new Address("楚雄", "冬瓜镇", "111111"));
System.out.println(u1.equals(u2));  // true

User u3 = new User("xiaohong", new Address("楚雄", "大街道", "111111"));
System.out.println(u1.equals(u3));
}
}

class User{
String name;
Address addr;

public User(){}
public User(String name, Address addr){
this.name = name;
this.addr = addr;
}

// 重写equals方法
// 重写规则:当一个用户的用户名和家庭住址都相同,表示同一个用户。
// 这个equals判断的是User对象和User对象是否相等。
public boolean equals(Object obj){
if(obj == null || !(obj instanceof User)){
return false;
}
if(this == obj){
return true;
}
User u = (User)obj;
return this.name.equals(u.name) && this.addr.equals(u.addr);
}
}

class Address{
String city;
String street;
String zipcode;

public Address(){}
public Address(String city, String street, String zipcode){
this.city = city;
this.street = street;
this.zipcode = zipcode;
}

// 重写toString()方法
// 重写equals()方法
// 这里的equals方法判断的是:Address对象和Address对象是否相等。
// Address中的equals方法没有重写的时候,比较的是对象的内存地址。
public boolean equals(Object obj){
if(obj == null || !(obj instanceof Address)){
return false;
}
if(this == obj){
return true;
}
// 怎么算是家庭住址相同呢?
// 城市相同,街道相同,邮编相同,表示相同
Address a = (Address)obj;
return this.city.equals(a.city) && this.street.equals(a.street) && this.zipcode.equals(a.zipcode);
}
}

 

1.3 Object类的finalize方法

1、在Object类中的源代码:
protected void finalize() throws Throwable{}

2、finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的。

3、这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用。
不像equals, toString, equals和toString方法是需要你写代码调用的。
finalize()只需要重写,重写完将来自动会有程序来调用。

4、finalize()方法的执行时机:
当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法。

5、finalize()方法实际上SUN公司为java程序员准备的一个时机,垃圾销毁机制。如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中。

6、静态代码块的作用是什么?
static{
...
}
静态代码块在类加载时刻执行,并且只执行一次。
这是一个SUN准备的类加载时机。

finalize()方法同样也是SUN为程序员准备的一个时机。

7、提示:
java中的垃圾回收器不是轻易启动的,垃圾太少,或者时间没到,各种条件下,有可能启动,也有可能不启动。


public class Test06{
public static void main(String[] args){
/*
// 创建对象
Person p = new Person();
// 怎么把Person对象变成垃圾?
p = null;
*/

// 多造点垃圾
/*
for(int i = 0; i < 100000000; i++){
Person p = new Person();
p = null;
}
*/

Person p = new Person();
p = null;
// 有一段代码可以建议垃圾回收器启动。(只是建议,可能不启动,也可能启动,启动的概率变高)
System.gc();
}
}

// 项目开发中有这样的业务需求:所有对象在JVM中被释放的时候,请记录一下释放时间。
// 记录对象被释放的时间点,这个负责记录的代码写到哪里?
// 写到finalize()方法中。
class Person{
// 重写finalize()方法
// Person类型的对象被垃圾回收器回收的时候,垃圾回收器负责调用:p.finalize();
protected void finalize() throws Throwable{
// this代表当前对象
System.out.println(this + "即将被销毁");
}
}

 

1.4 Object的hashCode方法

在Object中的hashCode方法是怎样的?
public native int hashCode();
这个方法不是抽象方法,带有native关键字,底层调用C++程序。

hashCode()方法返回的是哈希码:
实际上就是一个java对象的内存地址,经过哈希算法,得出的一个值。
所以hashCode()方法执行结果可以等同看做一个java对象的内存地址。


public class Test07{
public static void main(String[] args){
Object o = new Object();
int hashCodeValue = o.hashCode();

// 内存地址经过哈希算法转换的一个数字。可以等同看做内存地址。
System.out.println(hashCodeValue);

MyClass mc = new MyClass();
int hashCodevalue2 = mc.hashCode();
System.out.println(hashCodevalue2);
}
}

class MyClass{

}

 

 

2 内部类

1、什么是内部类?
内部类:在类的内部又定义了一个新的类,被称为内部类

2、内部类的分类:
静态内部类:类似于静态变量
实例内部类:类似于实例变量
局部内部类:类似于局部变量

3、使用内部类编写的代码,可读性很差,能不用尽量不用。

4、匿名内部类是局部内部类的一种。因为这个

5、学习匿名内部类主要是让大写以后在阅读别人代码的时候,能够理解。并不代表以后都要这样写,因为匿名内部类有两个缺点:
缺点1:太复杂,太乱,可读性差。
缺点2:类没有名字,以后想重复使用,不能用。

ps:
IDEA中快速纠错的快捷键:alt + 回车


class Test01{

// 该类在类的内部,所以称为内部类
// 由于前面有static,所以称为“静态内部类”
static class Inner1{

}

// 该类在类的内部,所以称为内部类
// 没有static叫做实例内部类。
class Inner2{

}

public void doSome(){
// 该类在类的内部,所以称为内部类
// 局部内部类。
class Inner3{

}
}

public void doOther(){
// doSome()方法中的局部内部类Inner3,在doOther()中不能用。
}

// main方法,入口
public static void main(String[] args){
// 调用MyMath中的sum方法。
MyMath mm = new MyMath();
// 不用匿名内部类之前的写法
// mm.sum(new ComputeImp1(), 100, 200);

// 使用匿名内部类,表示这个ComputeImp1这个类没名字了。
// 这里表面看上去好像是接口可以直接new了,实际上并不是接口可以new了。
// 后面的{}代表了对接口的实现
// 不建议使用匿名内部类,为什么?
// 因为一个类没有名字,没有办法重复使用,另外代码太乱,可读性太差
mm.sum(new Compute(){
public int sum(int a, int b){
return a + b;
}
}, 100, 200);
}

}


// 不用匿名之前的写法
/*
// 负责计算的接口
interface Compute{

// 抽象方法
int sum(int a, int b);
}

// Compute接口的实现类
class ComputeImp1 implements Compute{
public int sum(int a, int b){
return a + b;
}
}

// 数学类
class MyMath{

public void sum(Compute c, int x, int y){
int retValue = c.sum(x, y);
System.out.println(x + "+" + y + "=" + retValue);
}
}
*/


// 负责计算的接口
interface Compute{

// 抽象方法
int sum(int a, int b);
}

// 数学类
class MyMath{

public void sum(Compute c, int x, int y){
int retValue = c.sum(x, y);
System.out.println(x + "+" + y + "=" + retValue);
}
}
 
posted @   路走  阅读(162)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示