这个方法的作用就是简单的介绍一下对象。
如果自建的对象中不重写 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 + '}'; }
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的子类实例
总结:当向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的作用就是判断两个对象是不是一样,比如两个对象看上去一样,但实际上并不是一个对象。
原文链接:https://blog.csdn.net/TYRA9/article/details/128745582
多态实现的前提条件:
1:有继承关系 / 实现关系(接口,抽象类)继承是实现多态的前提。
2:有方法的重写。
3:父类引用指向子类对象。
Animal animal = new Cat();
等号左面的animal是一个Animal类型的引用变量,但是,这个Animal类型的引用所指向的对象,即堆空间中真正的对象,却是一个Cat类型。在多态中,我们将等号左边的这个引用变量的类型,称为编译类型。而将等号右边的——在堆空间中new出来的——真正的对象的类型,称为运行类型。其中,编译类型决定了引用变量可以调用哪些属性和行为;而运行类型则是在程序运行过程中jvm实际使用的类型。
比方说,现在我们通过animal对象来调用eat() 方法,因为编译类型是Animal类,因此在编译时,编译器要判断Animal类有没有eat() 方法。诶,有eat() 方法,那就可以调用。但在实际调用时,jvm会优先使用运行类型Cat类中的eat() 方法,因此打印结果为“喜欢吃”。
使用规则 :
编译看左(即左边的编译类型有没有这个成员方法,这决定了我们能不能调用目标成员方法)
运行看右(即右边的运行类型中的该成员,才是运行中实际使用的成员) 父类引用也不可以使用子类特有的方法。
使用规则 :
编译看左(即左边的编译类型有没有这个成员,这决定了我们能不能调用目标成员)
运行看左(多态关系中,成员变量是不涉及重写的)
父类引用不能直接使用子类的特有成员。
编译类型决定了引用变量可以调用哪些属性和行为;而运行类型则是在程序运行过程中jvm实际使用的类型。父类引用,说明编译类型是父类类型,以父类类型编译当然只能使用父类中存在的成员。当然,这里所说的成员包括成员变量和成员方法,这二者在多态关系中的使用略有不同:使用的成员变量必须是在父类中存在的,且成员变量不涉及重写;使用的成员方法也必须是在父类中存在的,但是如果子类重写了父类方法,优先调用子类重写的方法。
从jvm内存的角度解释就是 : .java文件经"javac.exe"命令编译后会生成.class的字节码文件,当代码中需要使用到某个类,该类的字节码文件就会加载进入方法区,而jvm识别并执行的就是字节码文件。因此,编译类型为父类类型,那jvm识别的当然是这个类的字节码文件,子类的特有成员,根本就不在这个字节码文件里面,jvm当然不认识。而对于子类重写的方法,父类字节码文件中包含有被重写方法的信息,jvm能够识别。而因为父类引用真正指向的是堆空间中的子类类型对象,所以此时会优先从堆空间中的子类对象里面找,使用子类重写后的方法,若子类没有重写,根据继承机制,则使用父类的该方法。
子类类型转换成父类类型(父类引用指向子类对象)。向上转型是自动进行的,我们的多态就是一种向上转型。
即父类类型转换成子类类型。为什么叫强制类型转化呢? 因为向下转型不会自动发生,需要人为强转。并且,向下转型改变了编译类型,而编译类型决定了我们可以使用哪些成员,当编译类型由父类类型转换为子类类型后,我们当然可以使用子类的特有成员了。因此,我们说要使用子类的特有功能,靠的就是向下转型!
Animal animal = new Cat();
/*
如果使用的是多态的话,animal只能调用Animal类中的成员变量,只能调用Cat中重写的方法以及父类中剩余的方法。
这时候如果我们想用animal调用子类特有的成员变量和成员方法,我们就需要对其进行向下转型。即如下,
综上就可以获取子父类中所有我们想要的东西。
*/
(Cat)animal
注意事项 :
①只有在继承关系的继承上才可以进行类型转换,否则会报出ClassCastException(类型转换异常)。
②在对引用变量进行向下转型之前,必须保证该引用变量指向的——堆空间中真正的对象的类型就是目标类型。(重要)
比如,Animal animal = new Cat(); 现在animal引用指向了一个Cat类型的对象,如果要对animal引用进行强制向下转型,就只能转换成Cat类型的引用;如果想转换成其他类型的引用,就需要先改变animal引用的指向,使其指向目标类型的对象。否则,同样会报出类型转换异常。
③那么,我们在进行向下转型之前,怎么就能知道——当前引用指向的对象是不是我们想要的目标类型的对象呢?
答案是 : 在进行强制类型转化之前,使用instanceof关键字来进行判断。
原文链接:https://blog.csdn.net/TYRA9/article/details/128880552
在继承下,Java 中查找方法的顺序为 : 本类方法—>父类方法—>更高一级的父类—>…Object(顶层父类) 。然而,在某些情况下,这样的原则也会被凌驾。今天我们要说的java动态绑定机制,就是这样一个区别于继承机制的例外。
在继承下——即java中查找变量的顺序 : 局部变量—>成员变量—>父类—>更高的父类—>…—>Object 。
(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() 方法是子类的方法,而不是父类的。
以上都是对链接中的内容进行的总结与截取,详细内容在链接中。
equals( )方法主要是比较两个对象是不是相等。
而comparator比较器主要是指明如何进行比较,比如说是由小到大排列还是由大到小排列。
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
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;
}
}