视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=1
视频范围P526 - P542
目录描述
源码及API文档
1.概述
从哪可以看object类当中的常用方法?
- 第一种:去源代码中找(比较麻烦,源代码也比较难)
- 第二种:去查阅java的类库的帮助文档
API:
- API是应用程序编程接口(Application Program Interface)
- 整个JDK的类库就是一个javase的API
- 每一个API都会配置一套API帮助文档
- SUN公司提前写好的这套类库就是API(一般每一份API都对应一份API帮助文档)
目前为止只需要知道以下几个方法就行:
- protected Object clone() //负责对象克隆的
- int hashCode() //获取对象哈希值的一个方法
- boolean equals(Object obj) //判断两个对象是否相等
- String toString() //将对象转换成字符串形式
- protected void finalize() //垃圾回收器负责调用的方法
2.Object的toString方法
- 源码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 源代码上toString()方法的默认实现是:类名@对象的内存地址转换为十六进制的形式
- toString()方法设计目的:通过调用这个方法可以将一个“java对象”转换成“字符串表示形式”
- SUN公司开发java语言的时候,建议所有的子类都去重写toString()方法
toString()方法应该是一个简洁的、详实的、易阅读的
代码示例:
时间类:
package toString;
public 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()方法
public String toString() {
return this.year + "年" + this.month + "月" + this.day + "日";
}
}
测试类:
package toString;
public class Test01 {
public static void main(String[] args) {
MyTime t1 = new MyTime(1997,7,27);
//一个日期对象转换成字符串形式的话,更希望看到具体的日期信息
String s1 = t1.toString();
//MyTime类重写toString()方法之前
//System.out.println(s1);//输出为:toString.MyTime@1175e2db
//MyTime类重写toString()之后
System.out.println(s1);//输出为:1997年7月27日
//等价于
System.out.println(t1.toString());//输出为:1997年7月27日
//注意:输出引用的时候,会自动调用该引用的toString()方法
System.out.println(t1);//输出为:1997年7月27日
}
}
3.Object的equals方法
3.1 基本概念
- 源码(Object类的默认实现):
public boolean equals(Object obj) {
return (this == obj);
}
- SUN公司设计目的:通过equals方法来判断两个对象是否相等
代码示例:
时间类:
package toString;
public 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;
}
//默认的equals方法
//public boolean equals(Object obj) {
// return (this == obj);
//}
//重写的equals方法
public boolean equals(Object obj) {
//当年相同,月相同,并且日也相同的时候,表示两个日期相同,两个对象相等
//获取第一个日期的年月日
int year1 = this.year;
int month1 = this.month;
int day1 = this.day;
//获取第二个日期的年月日
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;
}
}
测试类:
package toString;
public class Test02 {
public static void main(String[] args) {
MyTime t1 = new MyTime(1997,7,27);
MyTime t2 = new MyTime(1997,7,27);
//测试一下,两个对象是否相等
//这里的“==”判断的是:t1中保存的对象内存地址和t2中保存的对象内存地址是否相等
System.out.println(t1 == t2);//输出为:false
//重写的equals方法之前(比较的是对象内存地址)
//boolean flag = t1.equals(t2);
//System.out.println(flag);//输出为:false
//重写的equals方法之后(比较的是内容)
boolean flag1 = t1.equals(t2);
System.out.println(flag1);//输出为:true
}
}
但是当遇到下面情况的时候:
package toString;
public class Test02 {
public static void main(String[] args) {
MyTime t1 = new MyTime(1997,7,27);
MyTime t4 = null;
System.out.println(t1.equals(t4));//输出为:false
}
}
也可以运行,但是效率太低了,于是对时间类进行改良:
时间类:
package toString;
public 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;
}
//改良的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;
}
MyTime t = (MyTime)obj;
if (this.year == t.year && this.month == t.month && this.day == t.day) {
return true;
}
//程序能到这里返回false
return false;
}
}
进一步改良:
package toString;
public 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;
}
//进一步改良的equals方法
public boolean equals(Object obj) {
//如果obj是空,或者obj不是一个MyTime 直接返回false
if (obj == null || !(obj instanceof MyTime)) {
return false;
}
//如果this和obj保存的内存地址相同,没必要比较了,直接返回true
if (this == obj) {
return true;
}
MyTime t = (MyTime)obj;
return this.year == t.year && this.month == t.month && this.day == t.day;
}
}
3.2 String类重写toString和equals
- String类已经重写了toString方法
- String类已经重写了equals方法,比较两个字符串不能使用==,必须使用equals,equals是通用的
- java中基本数据类型比较是否相等,使用==
- java中所有的引用数据类型统一使用equals方法来判断是否相等
package toString;
public class Test03 {
public static void main(String[] args) {
//大部分情况下,采用这样的方式创建字符串对象
String s1 = "hello";
String s2 = "abc";
//实际上String也是一个类,不属于基本数据类型,存在构造方法
String s3 = new String("Test1");
String s4 = new String("Test1");
//==判断的是内存地址,不是内容
System.out.println(s3 == s4);//输出为:false
//比较两个字符串必须调用equals方法
//String类已经重写了equals方法
System.out.println(s3.equals(s4));//输出为:true
//String类已经重写了toString方法
String x = new String("西南大学");
//如果没有重写那么输出结果为:java.lang.String@十六进制的地址
System.out.println(x.toString());//输出为:西南大学
System.out.println(x);//输出为:西南大学
}
}
3.3 重写Object的equals方法
学生类:
package toString;
public class Student {
int no;
String school;
public Student() {
}
public Student(int no, String school) {
this.no = no;
this.school = school;
}
//重写toString方法
public String toString() {
return "学号" + no + ",所在学校名称" + school;
}
//重新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;
}
}
测试类:
package toString;
public class Test04 {
public static void main(String[] args) {
Student s1 = new Student(5108,"西南大学");
Student s2 = new Student(5108,"西南大学");
System.out.println(s1 == s2);//输出为:false
System.out.println(s1.equals(s2));//输出为:true
//下面情况为特殊情况,用student的类中最后注释掉的方法返回就会出现问题
//Student s3 = new Student(5108,new String("西南大学"));
//Student s4 = new Student(5108,new String("西南大学"));
//System.out.println(s1 == s2);//输出为:false
//System.out.println(s1.equals(s2));//输出为:false
}
}
3.4 equals方法深层次理解
注意:equals方法重写的要彻底
地址类:
package toString;
public 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;
}
//注意:这里并没有重写equals方法
//这里的equals方法判断的是:Address对象和Address对象是否相等
}
用户类:
package toString;
public 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);
}
}
测试类:
package toString;
public class Test05 {
public static void main(String[] args) {
User u1 = new User("liujie",new Address("重庆","北碚区","5108"));
User u2 = new User("liujie",new Address("重庆","北碚区","5108"));
System.out.println(u1.equals(u2));//输出为:false
}
}
针对上面的问题,需要在地址类中也要定义equals方法:
地址类:
package toString;
public 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;
}
// 注意:这里并没有重写equals方法
// 这里的equals方法判断的是:Address对象和Address对象是否相等
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);
}
}
测试类:
package toString;
public class Test05 {
public static void main(String[] args) {
User u1 = new User("liujie",new Address("重庆","北碚区","5108"));
User u2 = new User("liujie",new Address("重庆","北碚区","5108"));
System.out.println(u1.equals(u2));//输出为:true
}
}
4.Object的finalize方法
- 源码:
protected void finalize() throws Throwable{}
- finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的
- 这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法
如:不像equals、toString,这两个需要写代码调用,但是finalize()方法只需要重写,重写完将来自动会有程序来调用 - finalize()方法的执行时机:
当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法 - finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机。
如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中 - 静态代码块的作用是什么?
静态代码块在类加载时刻执行,并且只执行一次,这是一个SUN准备的类加载时机
static{
.....
}
- finalize()方法同样也是SUN为程序员准备的一个时机,这个时机是垃圾回收时机
- java中的垃圾回收器不是轻易启动的,垃圾太少,或者时间没到,种种条件下,有可能启动,也有可能不启动
人类:
package toString;
public class Person {
//重写finalize()方法
//Person类型的对象被垃圾回收器回收的时候,垃圾回收器负责调用:p.finalize();
protected void finalize() throws Throwable{
System.out.println("即将被销毁!!!");
}
}
测试类:
package toString;
import java.util.Iterator;
public class Test06 {
public static void main(String[] args) {
// 创建对象
// Person p = new Person();
// 怎么将Person对象变成垃圾
// 第一种方法
// for (int i = 0; i < 100000000; i++) {
// Person p = new Person();
// p = null;
// }
// 第二种
for (int i = 0; i < 100000000; i++) {
Person p = new Person();
p = null;
//有一段代码可以建议垃圾回收器启动
if (i % 2 == 0) {
System.gc();//建议启动垃圾回收器,但只是建议,可能不启动,也可能启动
}
}
}
}
运行效果:
5.Object的hashCode方法
- 源码
这个方法不是抽象方法,带有native关键字,底层调用C++程序
public native int hashCode();
- hashCode()方法返回的是哈希码:实际上就是一个java对象的内存地址,经过哈希算法,得出的一个值
- hashCode()方法的执行结果可以等同看做一个java对象的内存地址
package toString;
public class Test07 {
public static void main(String[] args) {
Object o = new Object();
int hashCodeValue = o.hashCode();
//对象内存地址经过哈希算法转换的一个数字,可以等同看做内存地址
System.out.println(hashCodeValue);//输出为:2111991224
MyClass mc = new MyClass();
int hashCodeValue2 = mc.hashCode();
System.out.println(hashCodeValue2);//输出为:917142466
//等价于
System.out.println(mc.hashCode());//输出为:917142466
}
}
class MyClass{
}
6.内部类
6.1 内部类概述
- 内部类:在类的内部又定义了一个新的类
- 内部类分类
内部类 | 备注 |
---|---|
静态内部类 | 类似于静态变量 |
实例内部类 | 类似于实例变量 |
局部内部类 | 类似于局部变量 |
- 使用内部类编写的代码可读性很差,能不用尽量不用
class Test01 {
// 静态变量
static String country;
// 该类在类的内部,被称为内部类
// 由于前面的static,所以称为“静态内部类”
static class Inner1 {
}
// 实例变量
int age;
// 没用static叫做“实例内部类”
class Inner2 {
}
// 方法
public void doSome() {
// 局部变量
int i = 100;
// “局部内部类”
class Inner3 {
}
}
public void doOther() {
// doSome()方法中的局部内部类Inner3,在doOther()中不能用
}
}
6.2 匿名内部类
- 匿名内部类:类没有名字
- 匿名内部类是局部内部类中的一种
- 学习匿名内部类主要是让大家以后在阅读别人代码的时候能理解
示例代码:
当不使用匿名内部类的时候:
package toString;
public class Test08 {
public static void main(String[] args) {
MyMath mm = new MyMath();
mm.mySum(new ComputeImpl(), 100, 200);
}
}
//负责计算的接口
interface Compute{
//抽象方法
int sum(int a,int b);
}
//Compute接口的实现类
class ComputeImpl implements Compute{
//对方法的实现
public int sum(int a, int b) {
return a + b;
}
}
//数学类
class MyMath{
//数学求和
public void mySum(Compute c, int x, int y) {
int retValue = c.sum(x, y);
System.out.println(x + "+" + y + "=" + retValue);
}
}
当使用匿名内部类的时候,不需要对接口进行实现:
package toString;
public class Test08 {
public static void main(String[] args) {
MyMath mm = new MyMath();
//使用匿名内部类
//表面看上去好像是接口可以直接new,实际上并不是接口可以new,后面的{}代表了对接口的实现
mm.mySum(new Compute(){
public int sum(int a, int b) {
return a + b;
}
}, 100, 200);
}
}
//负责计算的接口
interface Compute{
//抽象方法
int sum(int a,int b);
}
//数学类
class MyMath{
//数学求和
public void mySum(Compute c, int x, int y) {
int retValue = c.sum(x, y);
System.out.println(x + "+" + y + "=" + retValue);
}
}
总结:
不建议使用匿名内部类,因为一个类没有名字,没有办法重复使用,另外代码太乱,可读性太差