1.Object类
1.1.Object
-
JDK类库的根类:Object
-
这个老祖宗类中的方法我们需要先研究一下,因为这些方法都是所有子类通用的。
任何一个类默认继承Object。
就算没有直接继承,最终也会间接继承。 -
Object类当中有哪些常用的方法?
- 我们去哪里找这些方法呢?
- 第一种方法:去源代码当中。(但是这种方式比较麻烦,源代码也比较难)
- 第二种方法:去查阅java的类库的帮助文档。
- 我们去哪里找这些方法呢?
-
什么是API?
- 应用程序编程接口。(Application Program Interface),整个JDK的类库就是一个javase的API。
- 每一个API都会配置一套API帮助文档。SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。)
-
目前为止我们只需要知道这几个方法即可:
- protected Object clone() // 负责对象克隆的。底层调用C++ 自行了解深克隆浅克隆
- int hashCode() // 获取对象哈希值的一个方法。底层调用C ++
- boolean equals(Object obj) // 判断两个对象是否相等,默认使用比较内存地址
- String toString() // 将对象转换成字符串形式
- protected void f inalize() // 垃圾回收器负责调用的方法
- 以上方法重写方法
- 手动重写
- idea自动导入,alt+insert
1.2.重写toString
-
源代码长什么样?
- public String toString() {
return this.getClass().getName() + “@” + Integer.toHexString(hashCode());
} - 源代码上toString()方法的默认实现是:
类名@
对象的内存地址转换为十六进制的形式
- public String toString() {
-
SUN公司设计toString()方法的目的是什么?
- 通过调用这个方法可以将一个
“java对象”
转换成“字符串表示形式”
- 通过调用这个方法可以将一个
-
其实SUN公司开发java语言的时候,建议所有的子类都去重写toString()方法。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@28a418fc
//MyTime类重写toString()方法之后
System.out.println(s1); // 1970年1月1日
//System.out.println(t1.toString()); //1970年1月1日
// 注意:输出引用的时候,会自动调用该引用的toString()方法。
System.out.println(t1);
}
}
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 + "日";
return this.year + "/" + this.month + "/" + this.day;
}
}
1.3.重写equals
1.3.1.equals简介与自定义
-
关于Object类中的equals方法
- equals方法源代码
public boolean equals(Object obj) {
return (this == obj);
// this 当前对象,obj待比较对象
// t1 equals t2
}
以上这个方法是Object类的默认实现。以= =
进行比较,比较内存地址 - SUN公司设计equals方法的目的是什么?
- 以后编程的过程当中,都要通过equals方法来判断两个对象是否相等。equals方法是判断两个对象是否相等的。
- equals方法源代码
-
我们需要研究一下Object类给的这个默认的equals方法够不够用!!!!
- 在Object类中的equals方法当中,默认采用的是**==**”判断两个java对象是否相等。而双等号判断的是两个java对象的内存地址
- 若我们应该判断两个java对象的内容是否相等。所以老祖宗的equals方法不够用,需要子类重写equals。
- 判断两个java对象内容是否相等,不能使用
= =
,因为比较的是两个对象的内存地址。
-
重写Object类的equals方法
- 怎么重写?
- 复制粘贴。相同的返回值类型、相同的方法名、相同的形式参数列表。
- equals到底应该怎么重写?你自己定,
你认为两个对象什么相等的时候表示相等,你就怎么重写。
- 自动生成
- idea alt + enter
- 怎么重写?
public class Test02{
public static void main(String[] args){
// 判断两个基本数据类型的数据是否相等直接使用“==”就行。
int a = 100;
int b = 100;
// 这个“==”是判断a中保存的100和b中保存的100是否相等。
System.out.println(a == b); //true(相等) false(不相等)
// 判断两个java对象是否相等,我们怎么办?能直接使用“==”吗?
// 创建一个日期对象是:2008年8月8日。
MyTime t1 = new MyTime(2008, 8, 8); //MyTime t1 = 0x1234;
// 创建了一个新的日期对象,但表示的日期也是:2008年8月8日。
MyTime t2 = new MyTime(2008, 8, 8); //MyTime t2 = 0x3698;
//测试以下,比较两个对象是否相等,能不能使用“==”???
// 这里的“==”判断的是:t1中保存的对象内存地址和t2中保存的对象内存地址是否相等。
System.out.println(t1 == t2); // false
// t1 t2 都是引用,保存的都是堆内存地址,两个对象自然两个堆内存地址,自然不同
// 重写Object equals方法之前(比较的是对象内存地址)
/*
boolean flag = t1.equals(t2);
//重写之前,这里的equals本质上和 == 一模一样
System.out.println(flag); //false
*/
// 重写Object equals方法之后(比较的是内容。而不是内存地址)
boolean flag = t1.equals(t2);
System.out.println(flag); //true
// 再创建一个新的日期
MyTime t3 = new MyTime(2008, 8, 9);
// 两个日期不相等,就是false。
System.out.println(t1.equals(t3)); // false
// 我们这个程序有bug吗?可以运行,但是效率怎么样?低(怎么改造。)
MyTime t4 = null;
System.out.println(t1.equals(t4)); //false
}
}
class MyTime { //extends Object{ 默认继承老祖宗类
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);
}
*/
/*
//
public boolean equals(Object obj) {
// 当年相同,月相同,并且日也相同的时候,表示两个日期相同。两个对象相等。
// 获取第一个日期的年月日 简单,直接获取,因为当前对象就有year这些属性
int year1 = this.year;
int month1 = this.month;
int day1 = this.day;
// 获取第二个日期的年月日
//int year2 = obj.year;
//int month2 = obj.month;
//int day2 = obj.day;
//上面代码直接报错,因为obj 是老祖宗类对象的引用,而老祖宗类中并没有year 这些属性
//报错,找不到相应的year month day 符号
//解决:使用多态,向下转型,将老祖宗类的引用转成MyTime对象---先逻辑判断
判断object 与 MyTime 是否存在继承关系,是,就执行 转型
if(obj instanceof MyTime){
MyTime t = (MyTime)obj;
//父类object转为子类MyTime类
int year2 = t.year;
int month2 = t.month;
int day2 = t.day;
if(year1 == year2 && month1 == month2 && day1 == day2){
return true;
//return完整执行完毕。会结束return所在的方法
}
}
// 程序能够执行到此处表示日期不相等。
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。
// 内存地址相同的时候指向的堆内存的对象肯定是同一个。
//巧用return结束方法,结合if判断,每执行到每一步都有特定的意义
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;
}
*/
//再次改良。
/*
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 ;
//利用短路与提升判断效率,return后面本身结果就是布尔型
}
*/
public boolean equals(Object obj) {
if(obj == null || !(obj instanceof MyTime)){
return false;
}
if(this == obj){
return true;
}
MyTime t = (MyTime)obj;
return this.year == t.year && this.month == t.month && this.day == t.day ;
}
}
/*
class Person{
private String idCard;
}
*/
1.3.2.String中的equals
-
String类已经重写了equals方法,比较两个字符串不能使用==,必须使用equals。equals引用类型中是通用的。
-
String类已经重写了toString方法。
- java中什么类型的数据可以使用
= =
判断,基本数据类型比较是否相等,使用= =
- java中什么类型的数据需要使用equals判断,
java中所有的引用数据类型统一使用equals方法来判断是否相等。
- java中什么类型的数据可以使用
/*
*/
public class Test03{
public static void main(String[] args){
// 大部分情况下,采用这样的方式创建字符串对象
String s1 = "hello";
String s2 = "abc";
// 实际上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); //动力节点,自动调用toString()
}
}
-
字符串
- 对于字符串的比较一律使用equals,因为正常情况下,很难自道字符串是怎么创建的
- 如果字符串是 = 创建的,equals和
==
判断,都可以,如果是new 出来的,必须用equals,涉及堆内存地址
-
String类,已经重写了equals,直接用
-
-
自定义类
- 该重写都重写,不管用不用得上,包括构造方法,直接idea生成
-
// 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该怎么重写呢? // 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重写要彻底,尤其存在继承关系时,更要注意
-
// equals方法重写的时候要彻底。 public class Test05{ public static void main(String[] args){ // 多态(自动类型转换。) Object o1 = new String("hello world!"); Object o2 = new User(); Object o3 = new Address(); 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("北京","朝阳区","11112")); System.out.println(u1.equals(u3)); // false } } class User{ // 用户名 String name; // 用户的住址 Address addr; public User(){ } public User(String name, Address addr){ this.name = name; this.addr = addr; } // 重写equals方法 // 重写规则:当一个用户的用户名和家庭住址都相同,表示同一个用户。 // 这个equals判断的是User对象和User对象是否相等。 //把u2 u3 什么的传入进来。对于下一行来说,没问题,因为不管什么类 //都会间接或者直接继承Object,既然有继承关系,就可以自动向上转型 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(){ } 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; if(this.city.equals(a.city) && this.street.equals(a.street) && this.zipcode.equals(a.zipcode)){ return true; } return false; } }
1.3.重写f inalize
-
已经过时
-
-
关于Object类中的finalize()方法。(非重点 非重点 非重点 了解即可。)
-
在Object类中的源代码:
protected void finalize() throws Throwable { } -
GC:负责调用finalize()方法。
-
finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的。
-
这个方法不需要程序员手动调用,**JVM的垃圾回收器负责调用这个方法。**不像equals toString,equals和toString()方法是需要你写代码调用的。finalize()只需要重写,重写完将来自动会有程序来调用。
-
finalize()方法的执行时机:
- 当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法。
-
finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机。如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法当中。
-
-
静态代码块的作用是什么?
static {
…
}
静态代码块在类加载时刻执行,并且只执行一次。这是一个SUN准备的类加载时机。 -
finalize()方法同样也是SUN为程序员准备的一个时机。这个时机是垃圾回收时机。
-
提示:
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; } */ for(int i = 0; i < 1000; i++){ Person p = new Person(); p = null; // 有一段代码可以建议垃圾回收器启动。 if(i % 2 == 0){ //偶数次就执行一次System.gc(); 建议启动GC频率搞了,启动成功概率也就高了 System.gc(); // 建议启动垃圾回收器。(只是建议, //可能不启动,也可能启动。启动的概率高了一些。) } } } } // 项目开发中有这样的业务需求:所有对象在JVM中被释放的时候,请记录一下释放时间!!! // 记录对象被释放的时间点,这个负责记录的代码写到哪里? // 写到finalize()方法中。 class Person{ // 重写finalize()方法 // Person类型的对象被垃圾回收器回收的时候,垃圾回收器负责调用:p.finalize(); protected void finalize() throws Throwable { // this代表当前对象 System.out.println(this + "即将被销毁!"); } }
1.4.hashCode方法
-
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); //798154996
MyClass mc = new MyClass();
int hashCodeValue2 = mc.hashCode();
System.out.println(hashCodeValue2); //1392838282
MyClass mc2 = new MyClass();
System.out.println(mc2.hashCode()); // 523429237
}
}
class MyClass
{
}
2.内部类
- 铁律:接口不可以创建对象
- 匿名内部类:
- 什么是内部类?内部类:在类的内部又定义了一个新的类。被称为内部类。
- 内部类的分类:
- 静态内部类:类似于静态变量
- 实例内部类:类似于实例变量
- 局部内部类:类似于局部变量
- 使用内部类编写的代码,可读性很差。能不用尽量不用。
- 匿名内部类是局部内部类的一种。因为这个类没有名字而得名,叫做匿名内部类。
- 学习匿名内部类主要是让大家以后在阅读别人代码的时候,能够理解。并不代表以后都要这样写。因为匿名内部类有两个缺点:
- 缺点
- 太复杂,太乱,可读性差。
- 类没有名字,以后想重复使用,不能用。
- 太复杂,太乱,可读性差。
- 不理解算了,你只要记住这种写法就行。
2.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()中不能用。
}
// 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了。
// 后面的{} 代表了对接口的实现。
// 不建议使用匿名内部类,为什么?
// 因为一个类没有名字,没有办法重复使用。另外代码太乱,可读性太差。
/*大部分情况下接口都可以按类来处理,多态什么的,继承啊,唯一与类区别就是接口不能实例化
在传参数并没有出错,new ComputeImpl()本质上Compute c = new ComputeImpl();
中 特殊局部变量 c 所代指的对象,所以下面直接new 实现接口的类来创建对象,没问题
因为这里是向上转型,ComputeImpl实现 接口实现接口 ,等同于ComputeImpl类是接口Compute
的子类 多态---向上转型(自动转型) Compute c = new ComputeImpl();
在调用mySum方法时,直接new 实现接口的类,来创建对象,没问题
*/
mm.mySum(new ComputeImpl(), 200, 300);
}
}
// 负责计算的接口
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);
}
}
2.2.使用匿名内部类
/*
匿名内部类:
1、什么是内部类?
内部类:在类的内部又定义了一个新的类。被称为内部类。
2、内部类的分类:
静态内部类:类似于静态变量
实例内部类:类似于实例变量
局部内部类:类似于局部变量
3、使用内部类编写的代码,可读性很差。能不用尽量不用。
4、匿名内部类是局部内部类的一种。
因为这个类没有名字而得名,叫做匿名内部类。
5、学习匿名内部类主要是让大家以后在阅读别人代码的时候,能够理解。
并不代表以后都要这样写。因为匿名内部类有两个缺点:
缺点1:太复杂,太乱,可读性差。
缺点2:类没有名字,以后想重复使用,不能用。
6、不理解算了,你只要记住这种写法就行。
*/
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()中不能用。
}
// 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接口的实现类
/*
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);
}
}