JavaSE进阶15 - Object类、toString方法、equals方法、finalize方法、hashCode方法、匿名内部类

源码及API文档概述及toString和equals知识点总结


1.JDK类库的根类:Object

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

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

		什么是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()   //垃圾回收器负责调用的方法

	1.3toString()方法
		以后所有类的toString()方法是需要重写的。
		重写规则:越简单越明了就好。

		System.out.println(引用);这里会自动调用"引用"toString()方法

		String类是SUN写的,toString方法已经重写了。

	1.4equals()方法
		以后所有类的equals方法也需要重写,因为Object中的equals方法比较
		的是两个对象的内存地址,我们应该比较内容,所以需要重写。

		重写规则:自己定,主要看什么和什么相等时表示两个对象相等。

		基本数据类型比较时使用:==
		对象和对象比较:调用equals方法

		String类是SUN编写的,所以String类的equals方法重写了。
		以后判断两个字符串是否相等,最好不要使用==,要调用字符串对象的equals方法

Object类中的toString方法及重写toString

package com.bjpowernode.javase.test001;

/**
关于Object中的toString()方法
	1、源代码
	public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    源代码上toString()方法的默认实现是:
    	类名@对象的内存地址转换为十六进制的形式
    	
    2、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);//com.bjpowernode.javase.test001.MyTime@15db9742
		
		//MyTime类重写toString()方法之后
		System.out.println(s1);//1970年1月1日
		
		//注意:输出引用的时候,会自动调用该引用的toString()方法。
		System.out.println(t1);

	}

}

class MyTime{
	int year;
	int month;
	int day;
	
	public MyTime() {
		super();
	}
	public MyTime(int year, int month, int day) {
		super();
		this.year = year;
		this.month = month;
		this.day = day;
	}
	
	//重写toString()方法
	//方向:简洁的、详实的、易阅读的
	public String toString() {
		//return this.year + "年" + this.month + "月" + this.day + "日";
		return this.year + "/" + this.month + "/" + this.day;
	}
	
}

Object类中的equals方法及重写equal

package com.bjpowernode.javase.test001;

/**
关于Object类中的equals()方法
	1、equals方法的源代码
	    public boolean equals(Object obj) {
	        return (this == obj);
	    }
	    以上这个方法是Object类的默认实现。
	    
	2、equals()方法的作用是什么?
		以后编程的过程当中,都要通过equals方法来判断两个对象是否相等。
		equals方法是判断两个对象是否相等的。
		
	3、我们需要研究一下Object类给的这个默认的equals方法够不够用!!!
		在Object类中的equals方法当中,默认采用的是"=="判断两个java对象是否相等。而
		"=="判断的是两个java对象的内存地址,我们应该判断两个java对象的内容是否相等。所以
		老祖宗的equals方法不够用,需要子类重写equals。
	
	4、判断两个java对象是否相等,不能使用"==",因为"=="比较的是两个对象的内存地址。

 */
public class Test02 {

	public static void main(String[] args) {
		
		//判断两个基本数据类型的数据是否相等,直接使用"=="就行。
		int a = 100;
		int b = 100;
		System.out.println(a == b);//true
		
		//判断两个java对象是否相等,我们怎么办?能直接使用"=="吗
		//创建了一个日期对象是:2008年8月8日。
		MyTime2 t1  = new MyTime2(2008,8,8);
		//创建了一个新的日期对象,但表示的日期也是2008年8月8日。
		MyTime2 t2  = new MyTime2(2008,8,8);
		
		//测试下,比较两个对象是否相等,能不能使用"=="??
		//这里的"=="判断的是t1保存的对象内存地址和t2中保存的对象内存地址是否相等
		System.out.println(t1 == t2);//false	
		
		//未重写equals()之前(比较的是对象的内存地址)
		boolean flag = t1.equals(t2);
		//System.out.println(flag);//false
		
		//重写equals()之后(比较的是对象的内容)
		System.out.println(flag);//true		
		
		//再创建一个新的日期
		MyTime2 t3 = new MyTime2(2008,8,9);
		System.out.println(t1.equals(t3));//false
		
		//我们这个程序有bug吗?可以运行,但是效率怎么样?低(怎么改造。)
		MyTime2 t4 = null;
		System.out.println(t1.equals(t4));//false
		
		
		
	}

}

class MyTime2{
	
	int year;
	int month;
	int day;
	
	public MyTime2() {
		super();
	}
	
	public MyTime2(int year, int month, int day) {
		super();
		this.year = year;
		this.month = month;
		this.day = day;
	}

	
//    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;
		  
		  //获取第二个日期的年月日
		  //Object类中没有year、month、day属性
		  //int year1 = obj.year;
		  //int month1 = obj.month;
		  //int day1 = obj.day;	
		 
		  //当调用的属性是子类中特有的,父类中没有,需要进行向下转型。
		  if(obj instanceof MyTime2) { 
			  MyTime2 t = (MyTime2)obj;
			  int year2 = t.year;
			  int month2 = t.month;
			  int day2 = t.day;	
			  if(year1 == year2 && month1 == month2 && day1 == day2) {
				  return true;
			  }
		  }
		  return false;
	  }
	  */
	
	//改良
	/*
		public boolean equals(Object obj) {
			//如果obj是空,直接返回false
			if(obj == null) {
				return false;
			}
			
			//如果obj不是一个MyTime2,没必要比较了,直接返回false
			if(!(obj instanceof MyTime2)) {
				return false;
			}
			
			//如果this和obj保存的内存地址相同,没必要比较了,直接返回true。
			if(this == obj) {
				return true;
			}
			//程序能够致执行到此处说明什么?
			//说明obj不是null,obj是MyTime2类型。
			MyTime2 t =(MyTime2)obj;
			if(this.year == t.year && this.month == t.month && this.day == t.day) {
				return true;
			}
			
			//程序执行到这里返回false
			return false;
		}
	*/
	
	//再次改良
//	public boolean equals(Object obj) {
//		if(obj == null || !(obj instanceof MyTime2)) {
//			return false;
//		}
//		if(this == obj) {
//			return true;
//		}
//		MyTime2 t =(MyTime2)obj;
//		return this.year == t.year && this.month == t.month && this.day == t.day;
//	}
}	

String类中的toString方法和equals方法

package com.bjpowernode.javase.test001;

/**
	java语言当中的字符串String有没有重写toString方法,有没有重写equals方法
	
	总结:
 		1、String类已经重写了equals方法,比较两个字符串不能使用==,必须使用equals。equals是通用的。
 		
 		2、String类已经重写了toString方法。
 		
 	大结论:
 		java中什么类型的数据可以使用"=="判断
 			java中基本数据类型比较是否相等,使用==。
 		java中什么类型的数据需要使用equals判断
 			java中所有的引用数据类型统一使用equals方法来判断是否相等。
 		这是规矩。
 */
public class Test03 {

	public static void main(String[] args) {
		
		//大部分情况下,采用这样的方式创建字符串对象
		String s1 = "hello";
		String s2 = "abs";
		
		//实际上String也是一个类,不属于基本数据类型
		//既然String是一个类,那么一定存在构造方法。
		String s3 = new String("Test1");
		String s4 = new String("Test1");
		//new两次,两个对象内存地址,s3保存的内存地址和s4保存的内存地址不同。
		//"=="判断的是内存地址。不是内容。
		System.out.println(s3 == s4);//false
		
		//比较两个字符串能不能用"=="?
		//不能,必须调用equals()
		//String类已经重写了equals方法
		System.out.println(s3.equals(s4));//true
		
		//String类有没有重写toString方法呢?
		String x = new String("动力节点");
		//如果String类没有重写toString方法,输出结果:java.lang.String@十六进制的地址
		//经过测试:String类已经重写toString()方法
		System.out.println(x.toString());//动力节点
		System.out.println(x);//动力节点
	}

}

练习

package com.bjpowernode.javase.test001;

//String对象比较的时候必须使用equals方法
public class Test04 {

	public static void main(String[] args) {
		/*
		Student s1 = new Student(111,"北京市");
		Student s2 = new Student(111,"北京市");
		System.out.println(s1 == s2);//false
		System.out.println(s1.equals(s2));//true
		*/
		
		Student s1 = new Student(111,new String("北京市"));
		Student s2 = new Student(111,new String("北京市"));
		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;
	}

}

equals方法深究

package com.bjpowernode.javase.test001;

//equals方法重写要彻底
public class Test05 {

	public static void main(String[] args) {
		
		User u1 = new User("zhangsan",new Address("北京","大兴区","11111"));
		User u2 = new User("zhangsan",new Address("北京","大兴区","11111"));
		System.out.println(u1.equals(u2));//true
		
		User u3 = new User("zhangsan",new Address("北京","朝阳区","11111"));
		System.out.println(u1.equals(u3));//false
	}

}

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

class Address{
	String city;
	String street;
	String zipcode;
	
	public Address() {
		super();
	}
	public Address(String city, String street, String zipcode) {
		super();
		this.city = city;
		this.street = street;
		this.zipcode = zipcode;
	}
	
	//重写equals方法
	public boolean equals(Object obj) {
		if(obj == null || !(obj instanceof Address)) return false;
		if(this == obj) return true;
		Address a = (Address)obj;
		if(this.city.equals(a.city) && this.street.equals(a.street) && this.zipcode.equals(a.zipcode)) return true;
		return false;
	}

}

finalize()方法

package com.bjpowernode.javase.test001;

/**
	关于Object类中的finalize()方法(非重点,了解即可)
		
		1、在Object类中的源代码:
			protected void finalize() throws Throwable { }
			
		2、finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的。
		
		3、这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法。
		
		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 <= 10000000;i ++)
		{
			Person p = new Person();
			p = null;
		}
		*/
		
		//有一段代码建议启动垃圾回收器
		for(int i=0;i<=1000;i++)
		{
			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 + "即将被销毁!");
	}
}

hashCode方法

package com.bjpowernode.javase.test001;

/**
	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{
	
	
}

匿名内部类

package com.bjpowernode.javase.test002;

/**
	匿名内部类:
		1、什么是内部类?
			内部类:在类的内部又定义了一个新的类,被称为内部类。
			
		2、内部类的分类:
			静态内部类:类似于静态变量
			实例内部类:类似于实例变量
			局部内部类:类似于局部变量
			
		3、使用内部类编写的代码,可读性很差,能不用尽量不用。
		
		4、匿名内部类是局部内部类的一种,因为这个类没有名字而得名,叫做匿名内部类。
		
		5、学习匿名内部类主要是让大家以后在阅读别人代码的时候,能够理解。
		并不代表以后都要这样写。因为匿名内部类有两个缺点:
			缺点1:太复杂,太乱,可读性差。
			缺点2:类没有名字,以后想重复使用,不能用。
			
		6、记忆即可
		
 */
public class Test01 {
	
	//静态变量
	static String country;
	//该类在类的内部,所以称为内部类
	//由于前面有static,所以称为"静态内部类"
	static class Inner1{
	}
	
	//实例变量
	int age;
	//该类在类的内部,所以称为内部类
	//没有static叫做实例内部类
	class Inner2{
	}
	
	public void doSome(){
		
		//局部变量
		String name;
		//该类在类的内部,所以称为内部类
		//局部内部类
		class Inner3{	
		}
		
	}
	
	public void doOther() {
		//doSome()方法中的局部内部类Inner3,在doOther()中不能用。
	}
	
	//main方法,入口
	public static void main(String[] args) {
		// 调用MyMath中的mySum方法。
		MyMath mm = new MyMath();
		/*
		Compute c = new ComputeImpl();
		mm.mySum(c, 100, 200);*/
		//合并(这样写代码,表示这个类名是有的。类名是:ComputeImpl)
		//mm.mySum(new ComputeImpl(), 100, 200);
		
		// 使用匿名内部类,表示这个ComputeImpl类没名字了。
		// 这里表明看上去好像是接口可以直接new了,实际上并不是接口可以new了。
		// 后面的{}代表了对接口的实现。
		// 不建议使用匿名内部类,为什么?
		// 因为一个类没有名字,没有办法重复使用。另外代码太乱,可读性太差。
		mm.mySum(new Compute() {
			public int sum(int a, int b) {
				return a + b;
			}
		}, 200, 300);
		
		
	}

}

//负责计算接口
interface Compute{
	
	//抽象方法
	int sum(int a,int b);
	
}

//你自动会在这里编写一个Compute接口的实现类
/*
//使用匿名内部类以后,可以省略ComputeImpl类的编写
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);
	}
}


版权声明:本文为weixin_43636084原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。