第十六讲 java进阶-API

第十六讲 java进阶-重写equals方法的深度解读

1 接口的补充

  • 接口为什么不能new对象?有没有必要new对象?或者是说如果能new对象,new出来的是什么东西?有什么内容,是否符合对象的特征?

    • 接口中属性只能是常量,常量并不是对象的成员属性。接口中的方法只能是抽象的方法,没有方法体。
    • 对象要有成员属性,每个对象的成员属性值都不一样。要有成员方法,每个对象的成员方法,对象的引用都能调用。
    • 如果接口能new对象,那么接口对象的属性是没有的,接口对象的方法都是不能执行的。它违背对象的定义。
    • 接口的本身就是一个抽象的定义。
    • 抽象类为什么也不能new对象呢?道理是一样的。一旦抽象类中有抽象方法之后,这个方法不能被对象的引用调用。
  • 关于接口的应用:

public interface IOpen
{
// 所有的遥控器都有一个开关电视的功能。遥控器上有开关。能打开电视
	void open();
} 



public interface ITV
{
	// 所有的电视都能播放
	public void play();
} 


public class YaoKongQi implements IOpen
{
	// 万能遥控器
	// 可以打开小米牌电视,可以打开长虹牌电视,可以打开任何电视
	private ITV itv;//昨天说的接口也是引用类型
    // ITV itv,这是接口类型的引用,也是引用类型,我们可以看做是父类型的引用。
    // 继承:就是扩展。接口说的实现,实现的本质也是扩展。所以,你可以将接口看成是父类。
    // 这就满足了多态的机制。接口类型的引用指向接口实现类类型的对象。
    // 这也符合多态的语法机制。

	public void setTV(ITV itv) {
		this.itv = itv;
	}

	public void open() {
		itv.play();
	}
} 


public class XiaoMiTV implements ITV
{
	public void play() {
		System.out.println("小米电视play!!");
	}
} 

public class ChangHongTV implements ITV 
{
	public void play() {
		System.out.println("长虹电视Play");
	}
}


public class Test 
{
	public static void main(String[] args) 
	{
		XiaoMiTV xmtv = new XiaoMiTV();
		YaoKongQi ykq = new YaoKongQi();
		ykq.setTV(xmtv);
		ykq.open();

		ChangHongTV chtv = new ChangHongTV();
		ykq.setTV(chtv);
		ykq.open();
	}
}

// 面向对象编程的时候,我们要面向抽象,不要面向具体。要面向接口,不要面向具体的实现。

  • 接口隔离
    • 我们知道,接口定义之后,实现类一定要实现接口中所有的抽象方法。
    • 问题来了,如果一个接口,有5个方法,它的某一个实现类只要用到其中的一个,那么另外4个也一定要实现,否则编译都过不去。那么就会造成代码的冗余。
    • 所以,接口要隔离,接口的功能要单一。

2 API 和 API帮助文档

  • 什么是API,什么是API帮助文档
    • API:application programming Interface 说白了API就是SUN公司提供的java源代码
    • API帮助文档,是通过javadoc指令,将java源代码中/** */注释的内容提取出来,形成了这个帮助文档。
    • 以后,我们要用的时候,是直接看API还是用帮助文档?文档
    • 以后我们开发怎么使用SUN公司提供的API呢?导包、调用方法。导包,创建实例(有的甚至不用),然后“.”方法。

3 Object类探究(equals方法深度解读)

@Override : 这是一个注解。加上这个注解的,说明这个方法是父类中的方法,被子类重写了。这个方法的原型在父类中,子类重写了父类的方法。
  • 我们写一个User类,将它作为一个实体类,具体的写法如下:
package com.demo01.pojo;

import java.util.Objects;

public class User {
    private int id;
    private String name;

    public User() {
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}


// 测试类
public class Demo01 {
    public static void main(String[] args) {
        User user = new User(100, "张三" );
        System.out.println(user);
        User user1 = new User(100, "张三");
        System.out.println(user1);
        System.out.println(user.equals(user1));// ?不重写 false 重写后 true


//        public boolean equals(Object obj) {
//            return (this == obj);
//        }

        //com.tj.demo01.pojo.User@16d3586
        // 我们想要看看user这个对象在内存中到底有哪些属性和值,
        // 但是父类中的toString给我们提供的是com.tj.demo01.pojo.User@16d3586
        // 这不是我们想要的,因为我们根本看不懂这个东西User@16d3586
        // 你能判断赋值成功了吗?对象的成员属性有值还是没值,值是多少?这些是我们能看懂的
        // 怎么办?
        // 重写父类的toString()方法。

    }
}

如果我们不重写父类Object中的toString()方法,那么在测试类中我们希望打印对象的引用,所得结果如下:com.tj.demo01.pojo.User@16d3586

对我们而言,我们希望看到对象的属性和赋值情况,而Object类中的toString()方法只是告诉我们对象创建成功。这不符合我们的预期,因此我们需要在User类中重写toString()方法。

在Object类中有很多native修饰的方法,其具体的实现是用C++实现,具体的实现方式可以找资料阅读。但其提供的方法是可以直接调用的。

toString()方法的重写,我们可以通过IDEA来自动生成,也可以根据我们需要的方式进行重写。

Object类是所有类的superclass,因此该类中的所有成员方法都是其他类所产生的对象直接调用的。如果不符合我们的预期,我们可以对其进行重写。

重点介绍Object类中的equals()方法:

Object类中提供的equals(Object obj)方法,具体的实现如下:

public boolean equals(Object obj) {
        return (this == obj);
    }
// equals方法的目的是给我们提供比较两个对象是否是一个对象的工具
// Object类中原生的方法实现只能比较两个对象的引用是否相等
// 引用如果相等,说明两个引用指向了同一个对象,这当然是同一个对象
// 如果,一个类的两个对象,拥有同样的属性值,
// 虽然引用不相等,我们也需要将其认定为是相同的对象,是同一个对象
// 那么,Object类中的原生的方式就无法满足我们的需求,因此需要重写
// 一下是重写的equals方法:
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    User user = (User) o;
    return id == user.id &&
           Objects.equals(name, user.name);
}

// 以上代码满足了我们上述的需求,两个不同引用指向的对象,
// 拥有相同的属性值我们也认定为是相同的对象
// 就好比,一个人的身份证号 和 姓名 与另一个人的身份证号、姓名完全一致
// 我们就可以断定这是同一个人,虽然他可能出现在上海,过段时间出现在北京
// 不管是在上海还是在北京,都是同一个人无疑

// 以上代码要如何阅读呢?
// 第一步:我们看返回值类型,boolean 最后的结果一定是布尔值,即相等为true,不等为false
// 第二步:我们看参数列表,形参是Object o,
//  	我们要比较的是User类型的对象,传入的实参一定是User类型的对象引用
// 		这里就有多态的语法机制,因为User是Object的子类。
//		即:传参的时候是这样做的:Objcet o = new User();
// 		这是父类型的引用指向了子类型的对象,这一点至关重要。
// 		那么在equals(Object o)方法被调用的时候,实际上传入的是:
//		指向了子类型User对象的父类型引用Object o
// 		该equals(Object o)是成员方法,该方法中还有一个隐藏的参数this,是第一个形参
// 		它的原型是这样的equals(Object this, Object o);
// 		方法的调用者给这个this传实际值,然后同Object o所指向的类型进行比较
// 第三步:我们看方法体。方法体中第一条语句:if (this == o) return true;
//		这就是Object类中原生的equals()方法,它只是判定了两个对象的引用是不是同一个
//		如果是,就表示是同一个对象,这毫无疑问。
//		栈(堆)内存中的两个引用类型的变量指向了堆内存中的同一个对象,
//		这肯定是同一个对象,好比一个人书名叫张三,它的小名叫二狗子,都是同一个人。
//		第二条语句:if (o == null || getClass() != o.getClass()) return false;
//		如果传入的实参为null,为空意味着它还没有指向堆内存中的对象,这当然没得比。
//		如果不为null,说明堆中已经有了一个对象,
// 		如果this所代表的对象的类型和传入的实参对象的类型不一样,
//		都不是一个类,那也没得比较。
// 第四步:最重要也是最难懂的一步:User user = (User)o;
//		看懂这一步,说明对向下转型、多态都有了一定的理解。
//		为什么能这么转型?回答清楚这个问题,这个方法的解读就毫无压力了。
//		首先,我们要知道,o所指向的是子类型的对象,因为O是传入的实参
//		我们在第一步中解释了它的由来。Object o = new User();
// 		父类型的引用指向了子类型的对象,这是向上转型,自动类型转换。
//		只有在这个前提下,我们才能进行向下转型,也就是说才能将o这个父类型的引用
//		转为子类型的引用,否则无法转。
// 		User user = (User)o;
//		这里还要回答另一个问题,就是ClassCastException异常为什么不会发生?
// 		我们没有使用instanceof来判定,但这段代码中确保了异常是不会发生的。
// 		因为,第三条语句保证了,如果不是一个类型,方法就直接返回false,结束了。
//		所以,我们不用考虑类型的问题。
//		也就是说在类型转换异常的问题处理上,我们又多了一个思路。
//		当然,对于初学者来说,这段代码略显得有些高深。后面再详细讲解。
// 第五步:return id == user.id && Objects.equals(name, user.name);
//		这是最后一条语句,使用了短路与&&
// 		这条语句的前局很简单,就是比较两个整型的数据是否相等。
//		如果User这个对象的id属性相等,我们就进行下一步:
//		比较两个String类型的name属性是否相等
//		这一步也蕴含了很多信息,首先我们可以断定,这里调用了一个静态的equals()方法
//		该方法是Objects类中的,是另外一个类。
//		这个类是一个工具类,java.util包下的。因为直接"类名."的方式调用的equals方法
//		经查实,确实是这样的。
//		equals(name, user.name)这个方法的原型如下:
public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }
//		我们依然看到了这里的两个形参是两个Object类型的引用
//		name 和 user.name作为实际参数值传进来,是怎么回事?
//		这一步理解了,User类中重写的equals()方法阅读也就完成了
//		首先,name 和 user.name是String类型的,String类型也是一个引用类型
//		String没有显式的继承任何类,它的父类是Object
//		这里的这种写法,根本上就是父类型的引用指向子类型对象
//		即 Object a = new String(name);
//		所以如果a,b代表的实参都指向了同一个String类型的对象
//		仔细想,这里a==b指的是a、b同时为空,如果同时为空,id又相等
//		那两个User类型的对象当然是同一个
//		如果a不为空,那么我们再看看,a是否与b相同,
//		这里调用的equals方法本质上调用的是String中重写的equals方法。
//		这就是多态的机制。如果想要了解这里如何做的比较,可以去看String类中
//		重写的equals方法。这里不做赘述。
//		再次解释一下:这里为何调用的是String的equals方法,
//		因为运行时,会先去绑定子类型中重写的方法。
//		补充String中重写的equals方法如下:
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值