2023.3.30

@20:toString()方法

这个方法的作用就是简单的介绍一下对象。

如果自建的对象中不重写 toString( ) 方法,默认调用的是Object中的 toString( ) 方法。此时返回的结果如下:

(对象所在的类名)@(对象所在堆中的地址通过哈希算法得到的哈希值)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l9wQRrxc-1680184787992)(D:/%E4%BD%A0%E5%A5%BDJava/1026.png)]

自建的对象中不重写 toString( ) 方法,直接SOUT(对象的引用) == SOUT(对象的引用 . tostring( )

在这里插入图片描述

子类Student对父类Object提供的toString方法不满意,不满意–》对toString方法进行重写:

	@Override
	    public String toString() {
	        return "Student{" +
	                "name='" + name + '\'' +
	                ", age=" + age +
	                ", height=" + height +
	                '}';
	    }

@21:equals方法和 instanceof()方法

1:==:当比较的是基本数据类型时:比较的是值的大小关系。

​ 当比较的是引用数据类型时:比较的是内存地址。

2:equals( ):当没有重写equals( )方法时:相当于 ==。

在这里插入图片描述

​ 当重写equals( )方法后:按照自己的标准进行比较。

public class Phone {
    //属性:
    private String brand;//品牌型号
    private double price;//价格
    private int year ;//出产年份
    //构造器:

    public Phone() {
    }

    public Phone(String brand, double price, int year) {
        this.brand = brand;
        this.price = price;
        this.year = year;
    }
    
    //方法:
	public String toString() {
        return "Phone{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", year=" + year +
                '}';
    }
    
    //自己写的equals()方法
    public boolean equals(Object obj) {//Object obj = new Phone();
        // 将obj转为Phone类型:
        if(obj instanceof Phone) {
            Phone other = (Phone)obj;//向下转型,为了获取子类中特有的内容
  		   if(this.getBrand()==other.getBrand()&&this.getPrice()==other.getPrice()
                &&this.getYear()==other.getYear()){
                return true;
            }
        }
        return false;
    }
    
    // 系统自写的equals()方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Phone phone = (Phone) o;
        return Double.compare(phone.price, price) == 0 && year == phone.year 
               && Objects.equals(brand, phone.brand);
    }
}

A instanceof( B)方法:A:是对象名,B是类名,A对象是不是B的实例,A可以是B的子类实例

@22:重写hashcode与equals

总结:当向hashmap中传入引用类型的时候,通过hashcode来确定位置,通过equals来比较对象

当我们使用 HashMap, HashSet 等集合类时,会通过 hash 值来计算对象在散列表中的位置。

如果我们只重写 hashCode 方法,而不重写 equals 方法,那么两个相等的对象就会存储到同一个位置,但此时 equals 方法没有重写,两个对象就判定为不相等,从而可能存储大量相同对象,无法起到去重的效果。

如果我们只重写 equals 方法,而没有重写 hashCode 方法,HashMap 等集合类的底层逻辑是,先判断 hash 值是否相等,再判断对象值是否相等。此时没有重写 hashCode 方法,那么两个相等的对象就会被存储到散列表中不同的位置(Object 的 hashCode 方法,只是把地址转为一个 int 值), 从而没有 equals 判断的机会,也可能会存储大量相同对象 ,无法起到去重的效果。

但是也不绝对,如果是我们自定义的类,也不需要用到这些集合类,也就没人会去调用 hashCode 方法,此时只需要重写 equals 方法即可,也就没有必要重写 hashCode 方法了。即不调用hashcode就没必要重写

但还是建议重写 equals 方法时,也重写 hashCode 方法。因为不重写可能出错,重写一定不会出错。

而且要做到 equals 相等的对象,hashCode 一定是一致的。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对象在堆中的地址 —> 对地址进行hash算法 —> hashcode

hashcode的作用就是判断两个对象是不是一样,比如两个对象看上去一样,但实际上并不是一个对象。

@23:多态

原文链接:https://blog.csdn.net/TYRA9/article/details/128745582

1:多态的简单介绍:

多态实现的前提条件:

1:有继承关系 / 实现关系(接口,抽象类)继承是实现多态的前提。

2:有方法的重写。

3:父类引用指向子类对象。

Animal animal = new Cat();

等号左面的animal是一个Animal类型的引用变量,但是,这个Animal类型的引用所指向的对象,即堆空间中真正的对象,却是一个Cat类型。在多态中,我们将等号左边的这个引用变量的类型,称为编译类型。而将等号右边的——在堆空间中new出来的——真正的对象的类型,称为运行类型。其中,编译类型决定了引用变量可以调用哪些属性和行为而运行类型则是在程序运行过程中jvm实际使用的类型。

比方说,现在我们通过animal对象来调用eat() 方法,因为编译类型是Animal类,因此在编译时,编译器要判断Animal类有没有eat() 方法。诶,有eat() 方法,那就可以调用。但在实际调用时,jvm会优先使用运行类型Cat类中的eat() 方法,因此打印结果为“🐱喜欢吃🥩”。

2:多态的使用:

(1):成员方法的使用:

​ 使用规则 :

编译看左(即左边的编译类型有没有这个成员方法,这决定了我们能不能调用目标成员方法)
运行看右(即右边的运行类型中的该成员,才是运行中实际使用的成员)

​ 父类引用也不可以使用子类特有的方法。

(2):成员变量的使用:

​ 使用规则 :

编译看左(即左边的编译类型有没有这个成员,这决定了我们能不能调用目标成员)
运行看左多态关系中,成员变量是不涉及重写的

父类引用不能直接使用子类的特有成员。

​ 编译类型决定了引用变量可以调用哪些属性和行为;而运行类型则是在程序运行过程中jvm实际使用的类型。父类引用,说明编译类型是父类类型,以父类类型编译当然只能使用父类中存在的成员。当然,这里所说的成员包括成员变量和成员方法,这二者在多态关系中的使用略有不同:使用的成员变量必须是在父类中存在的,且成员变量不涉及重写;使用的成员方法也必须是在父类中存在的,但是如果子类重写了父类方法,优先调用子类重写的方法。
​ 从jvm内存的角度解释就是 : .java文件经"javac.exe"命令编译后会生成.class的字节码文件,当代码中需要使用到某个类,该类的字节码文件就会加载进入方法区,而jvm识别并执行的就是字节码文件。因此,编译类型为父类类型,那jvm识别的当然是这个类的字节码文件,子类的特有成员,根本就不在这个字节码文件里面,jvm当然不认识。而对于子类重写的方法,父类字节码文件中包含有被重写方法的信息,jvm能够识别。而因为父类引用真正指向的是堆空间中的子类类型对象,所以此时会优先从堆空间中的子类对象里面找,使用子类重写后的方法,若子类没有重写,根据继承机制,则使用父类的该方法。

3:类型转换:

(1)向上转型:

子类类型转换成父类类型(父类引用指向子类对象)。向上转型是自动进行的,我们的多态就是一种向上转型。

(2)向下转型:

即父类类型转换成子类类型。为什么叫强制类型转化呢? 因为向下转型不会自动发生,需要人为强转。并且,向下转型改变了编译类型,而编译类型决定了我们可以使用哪些成员,当编译类型由父类类型转换为子类类型后,我们当然可以使用子类的特有成员了。因此,我们说要使用子类的特有功能,靠的就是向下转型!

Animal animal = new Cat();
/*
如果使用的是多态的话,animal只能调用Animal类中的成员变量,只能调用Cat中重写的方法以及父类中剩余的方法。
这时候如果我们想用animal调用子类特有的成员变量和成员方法,我们就需要对其进行向下转型。即如下,
综上就可以获取子父类中所有我们想要的东西。
*/
(Cat)animal

注意事项 :
①只有在继承关系的继承上才可以进行类型转换,否则会报出ClassCastException(类型转换异常)。

②在对引用变量进行向下转型之前,必须保证该引用变量指向的——堆空间中真正的对象的类型就是目标类型。(重要)
比如,Animal animal = new Cat(); 现在animal引用指向了一个Cat类型的对象,如果要对animal引用进行强制向下转型,就只能转换成Cat类型的引用;如果想转换成其他类型的引用,就需要先改变animal引用的指向,使其指向目标类型的对象。否则,同样会报出类型转换异常。

③那么,我们在进行向下转型之前,怎么就能知道——当前引用指向的对象是不是我们想要的目标类型的对象呢?
答案是 : 在进行强制类型转化之前,使用instanceof关键字来进行判断。

4:动态绑定:

原文链接:https://blog.csdn.net/TYRA9/article/details/128880552

1:前言:

在继承下,Java 中查找方法的顺序为 : 本类方法—>父类方法—>更高一级的父类—>…Object(顶层父类) 然而,在某些情况下,这样的原则也会被凌驾。今天我们要说的java动态绑定机制,就是这样一个区别于继承机制的例外。

在继承下——即java中查找变量的顺序 : 局部变量—>成员变量—>父类—>更高的父类—>…—>Object

2:特点:

(1)当通过对象的引用调用方法时,该对象的引用会和堆内存中真正的该对象——的内存地址绑定,且这种绑定关系会贯穿方法的执行全过程。这就是所谓的“动态绑定机制”。(重点)

(2)当通过对象的形式调用属性时,不存在动态绑定机制,符合继承机制——即java中查找变量的顺序 : 局部变量—>成员变量—>父类—>更高的父类—>…—>Object 。

动态绑定机制只发生在成员方法上,不发生在成员变量上。

举例说明

class Father {                  /** 父类 */
 int temp = 5;

 public int getTemp() {
     // temp:Father类中temp成员变量
     return temp;
 }

 public int add_temp() {
     return getTemp() + 10;
 }

 public int add_temp_EX() {
     return temp + 10;
 }
}

class Son extends Father {      
 int temp = 11;

 public int getTemp() {
     return temp;
 }
 // 重写的方法1
 public int add_temp() {
     // temp:Son类中的temp成员变量
     return temp + 100; 
 }
 // 重写的方法2      
 public int add_temp_EX() {
     return getTemp() + 1000;
 }
}

public class TestBinding {    
 public static void main(String[] args) {
     //建立多态关系
     Father father = new Son();
     System.out.println(father.add_temp());
     System.out.println(father.add_temp_EX());
 }
}

根据多态中成员方法的使用规则——编译看左,运行看右:父类引用,说明编译类型是父类类型,而父类中定义了这两个方法,因此编译没问题,可调用;又因为多态关系——父类引用此时指向了子类对象,因此运行类型为子类,子类重写了父类的这两个方法,当然要优先调用子类中的方法,加之变量没有动态绑定的机制,因此输出的结果:111,1011

当我们把子类中add_temp( )注释掉后

在这里插入图片描述

现在我们要去调用父类的add_temp() 方法了,但是问题来了 : 父类的add_temp() 方法中调用了getTemp() 方法,那这时候的getTemp() 方法是用谁的呢?

这里就是一个初学者大概率犯错误的地方,如果没有了解过动态绑定机制,它们会想当然的认为 : 现在执行的是Father类中的add_temp() 方法,根据java中查找方法的原则,当然是使用Father类本类的getTemp() 方法了!所以,最后的返回值就是5 + 10 = 15,输出15。但是真的是如此吗 ?结果输出的是:21 1011

输出结果表明父类add_temp() 方法最后返回的不是5 + 10,而是11 + 10,这表明add_temp() 方法中调用的getTemp() 方法是子类中的getTemp() 方法。为什么呢?

原因就是我们说的动态绑定机制,由于测试类中是通过father引用来调用方法,而它指向的对象是Son类对象。根据动态绑定机制,在调用add_temp() 方法时,father引用已经和它指向的对象绑定了,并且这种绑定关系会贯穿方法执行的全过程。因此,这里调用的getTemp() 方法是子类的方法,而不是父类的。

以上都是对链接中的内容进行的总结与截取,详细内容在链接中。

@20:equals()与 比较器区别

equals( )方法主要是比较两个对象是不是相等。

而comparator比较器主要是指明如何进行比较,比如说是由小到大排列还是由大到小排列。

@21:合并多个有序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

在这里插入图片描述

import java.util.Comparator;
import java.util.PriorityQueue;

//  合并 K 个升序链表
public class MergeKSortedLists {
    public static class ListNode {
        public int val;
        public ListNode next;
    }

    public ListNode mergeKLists(ListNode[] lists) {
        if(lists == null) {
            return null;
        }
        PriorityQueue<ListNode> heap = new PriorityQueue<>(new Comparator<ListNode>() {
            // 节点值小的放在前面
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val - o2.val;
            }
        });
        // 所有链表的头节点入堆
        for (int i = 0; i < lists.length; i++) {
            if(lists[i] != null) {
                heap.add(lists[i]);
            }
        }
        // 当所有都节点都是空的时候
        if(heap.isEmpty()) {
            return null;
        }

        ListNode head = heap.poll();
        ListNode cur = head;
        while(!heap.isEmpty()) {
            if(cur.next != null) {
                heap.add(cur.next);
            }
            ListNode node = heap.poll();
            cur.next = node;
            cur = node;
        }
        return head;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chen森柏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值