❤️❤️前言~🥳🎉🎉🎉
hellohello~,大家好💕💕,这里是E绵绵呀✋✋ ,如果觉得这篇文章还不错的话还请点赞❤️❤️收藏💞 💞 关注💥💥,如果发现这篇文章有问题的话,欢迎各位评论留言指正,大家一起加油!一起chin up!👍👍
💥个人主页:E绵绵的博客
💥 所属专栏:JAVASE题目练习 JAVASE知识点专栏 c语言知识点专栏 c语言题目练习
这篇文章就是关于抽象类与接口的最后一篇了,我们接下来将会给大家主要介绍接口的实例使用,这部分也是最难的,还请各位认真观看,不要错过每一处细节。💕💕
接口使用实例
给对象数组排序
首先在了解它之前我们一些接口以及其他知识点。
comparable
❤️❤️观察其内部结构我们可以知道在Comparable后面还有个<T>,在语法上这是泛型,之后会讲,这并不影响我们现在的思路,这个<T>中的T你写student类,后面的compareTo方法中的第一个参数就是student类,如上图。
对于comparable接口中只有一个compareTo方法,所以我们使用该接口时只需要重写该compareTo方法就行。
比较大小时可以用comparable该接口。
关于其comprable使用如下:
class Student implements Comparable<Student> { public String name; public int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public int compareTo(Student o) { //System.out.println("===fdsfsafdsafdsafdsafdsafdsafsafdsafa"); return this.age - o.age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class Test { public static void bubbleSort(Comparable[] comparables) { for (int i = 0; i < comparables.length-1; i++) { for (int j = 0; j < comparables.length-1-i; j++) { if(comparables[j].compareTo(comparables[j+1]) > 0) { Comparable tmp = comparables[j]; comparables[j] = comparables[j+1]; comparables[j+1] = tmp; } } } } public static void main(String[] args) { Student[] students = new Student[3]; students[0] = new Student("zhangsan",10); students[1] = new Student("lisi",4); students[2] = new Student("abc",5); System.out.println("排序前: "+Arrays.toString(students)); bubbleSort(students); System.out.println("排序后: "+Arrays.toString(students)); }
所以就通过该代码去使用comparable接口中的compareTo方法,这个代码你们自己仔细看一下应该就能看懂了,不细讲,讲个大概就行了。
tostring重写
在上文代码中出现了一个问题,就是当Arrays.toString内部为类数组时如students时,为什么要将toString重写,(注意这里的toString重写不是将Arrays的toString重写,是将内部的object中的tostring重写)原因如下:
请看下面的图片:
分析上述图得知,当其内部为像students时的类数组时,不重写则会打印出地址
我们的目的是打印出其每个类的每个数据,所以要将toString重写,如下
所以就达成了目的,将数组的每个类的每个数据都打印出来了
🎯🎯 所以一般来说我们目前遇到的需要toString重写的就两种情况。
1.Arrays.toString(存储类的数组)打印出该数组的每个类的每个数据
2.println(类),打印出该类的各个数据
comparator
❤️❤️该接口有许多方法,但必须重写的只有一个,就是下图中的compare方法需要重写。
以下是使用方式
class Student { public String name; public int age; public Student(String name, int age) { this.name = name; this.age = age; }} class AgeComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return o2.age - o1.age ; } } class NameComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { //String 自己重写的 compareTo 方法 return o1.name.compareTo(o2.name); } } public class Test { public static void main(String[] args) { { Student student1 = new Student("zhangsan", 10); Student student2 = new Student("lisi", 4); Test test = new Test(); test.a(student1,student2); } } public void a(Student student1,Student student2) { AgeComparator ageComparator = new AgeComparator(); System.out.println(ageComparator.compare(student1, student2)); NameComparator nameComparator = new NameComparator(); System.out.println(nameComparator.compare(student1, student2)); } }
该接口中的compare方法也是用来进行比较的。
在该代码中我们还出现了compareTo,不过这里出现的跟我们往常见到的不一样。接下来将介绍一下。
String类中的compareTo
❤️❤️现在我们虽然还没深入了解String,但是呢还是懂一点的,我们观察下String类的内部。
可以发现它实施了comparable接口,则它一定重写了compareTo方法。
在这了解了内部之后我们只需要知道其string.compareTo的作用为
compareTo方法的实现会按照Unicode值的比较。它会逐个比较两个字符串对应位置上的字符的Unicode值,直到找到不同的字符或者其中一个字符串结束为止。找到不同的字符,会根据它们unicode值确定大小关系。
如果最终一直相等,则返回0,如果调用该方法的字符串小于参数里的字符串,则返回负数 ;反之为正数。
下面是一个例子
String str1 = "abc"; String str2 = "def"; int result = str1.compareTo(str2); System.out.println(result); // 输出负数,表示str1在unicode值上小于str2(也可以认为字典顺序上小于)
正文(给对象数组排序)
class Student { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } public String toString() { return "Student{" + "name='" + name + '\'' + ", score=" + score + '}'; } } public class Main{ public static void main(String[] args) { Student[] students = new Student[]{ new Student("张三", 95), new Student("李四", 96), new Student("王五", 97), new Student("赵六", 92), }; Arrays.sort(students); System.out.println(Arrays.toString(students)); }}
报错了这是为什么呢,我们来看下内部结构吧。
所以如果sort()内部参数为类(引用类型)数组,则其对应的代码如上,这里的a是object类,但其实指向的是创建的student类数组,发生了向上转型。下方还出现了comparable强制类型转换,但因为在上文中comparable和student没有任何关系,所以在这会报错,这也是为什么原文代码没有发生编译错误,但运行却报错了,所以我们必须得让comparable和student产生关系,这样代码才能成功。正确代码如下:
class Student implements Comparable<Student> { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } @Override public int compareTo(Student this,Student O) { return O.name .compareTo( this.name) ;//逆序,compareTo比较大小 } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", score=" + score + '}'; } } public class Main{ public static void main(String[] args) { Student[] students = new Student[]{ new Student("张三", 95), new Student("李四", 96), new Student("王五", 97), new Student("赵六", 92), }; Arrays.sort(students); System.out.println(Arrays.toString(students)); }}
结果如上,这是对类中的name进行排序,当然我们可以通过改变compareTo中的代码去来改变要排序的成员。
所以当sort参数为类(引用类型)数组时,我们就必须让该类实施comparable接口,并且重写comparable中的compareTo方法使其对类中的某个成员进行排序。(注意其compare to返回值为整形,分为大于0,小于0,0三种)
拷贝
之前就略微讲过拷贝,只不过之前只涉及了浅拷贝,讲的很少,这次我们认真讲下拷贝,它不仅涉及浅拷贝还有深拷贝。
clone
❤️❤️object类中存在一个方法为clone,我们可以通过调用该方法来复制该类的对象从而去创建该类的对象的副本。
我们先来看下代码:
class Person implements Cloneable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // 重写clone()方法 @Override public Person clone() throws CloneNotSupportedException { return (Person) super.clone(); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class Date{ public static void main(String[] args)throws CloneNotSupportedException { Person person=new Person("小美",5); Person person1=person.clone(); System.out.println(person1); } }
从这代码我们可以注意到一些事项 :
🎯🎯 1.实现Cloneable接口:要使用clone()方法,需要确保被复制的类实现了Cloneable接口。否则,在调用clone()方法时会抛出CloneNotSupportedException异常。
而被复制的类的对象实则是引用clone方法的类对象。如上文中的peson。
🎯🎯2重写clone()方法:被复制的类需要重写clone()方法,并将其访问修饰符设置为public。在重写的clone()方法中,我们通过调用父类的clone()方法来实现对象的浅拷贝。
如下:
public Object clone() throws CloneNotSupportedException { return super.clone(); }
之所以要重写clone,是因为clone是被protected修饰,只能在object的同一个包中或不同包的子类中出现。这时如果不重写clone,直接在Date类里引用person.clone,那么就会因为这是person里的clone,而clone在不同包中的非子类Date中出现,所以受访问权限不能访问。因此需要在person类里重写clone方法。(一般重写为如上代码格式)
🎯🎯 3.异常处理:在调用clone()方法时,可能会抛出CloneNotSupportedException异常。因此,在使用clone()方法时,需要在使用clone()方法中的成员方法上声明抛出该异常,否则会报错。
throws CloneNotSupportedException就是该异常声明
🎯🎯4.注意clone返回的是object类型,所以在接收时我们还需要强制类型转换才能成功。
🎯🎯5.
先看代码:
class Money implements Cloneable{ public double m = 19.9; } class Person implements Cloneable{ public String name; public int age; public Money money = new Money(); public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "age=" + age + ", money=" + money.m + '}'; } @Override protected Object clone() throws CloneNotSupportedException { Person tmp = (Person)super.clone(); return super.clone(); }} public class Date { public static void main(String[] args) throws CloneNotSupportedException{ Person person=new Person("0",99); Person person1= (Person) person.clone(); person.money.m=99.9; System.out.println(person); System.out.println(person1); } }
之所以会出现两个99.9,是因为默认情况下,clone()方法执行的是浅拷贝,即复制对象的引用,不会将引用所指向的对象一并复制,所以person1中的money指向的依然是同一个对象,依然是99.9.那如果需要实现深拷贝,即复制对象的内容而不是引用,需要在clone()方法中手动处理对象内部的引用。
如下才是深拷贝真正代码:
class Money implements Cloneable{ public double m = 19.9; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Person implements Cloneable{ public String name; public int age; public Money money = new Money(); public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "age=" + age + ", money=" + money.m + '}'; } @Override protected Object clone() throws CloneNotSupportedException { Person tmp = (Person)super.clone(); tmp.money = (Money) this.money.clone(); return tmp; }} public class Date { public static void main(String[] args) throws CloneNotSupportedException{ Person person=new Person("0",99); Person person1= (Person) person.clone(); person.money.m=99.9; System.out.println(person); System.out.println(person1); } }
我们手动处理了对象内部的引用,所以money类的对象也被复制了,两个money引用所指向的对象不同,导致如上结果不同。
深拷贝和浅拷贝
❤️❤️深拷贝和浅拷贝是在Java中用于复制对象的两种不同方式。由上文可知,我们可以知道深拷贝和浅拷贝的区别:
浅拷贝是指创建一个新对象,然后将原始对象的成员变量的值复制到新对象中。如果成员变量是基本类型,则复制其值;如果成员变量是引用类型,则复制引用而不是其指向的实际对象。因此,始对象和新对象将共享相同的引用对象。这意味着,如果修改其中一个对象的引用对象,另一个对象也会受到影响。
深拷贝是指创建一个新对象,并复制原始对象的所有成员变量及其所有引用变量所指向的对象。这意味着,虽然原始对象和新对象将拥有相同的值(引用变量的值不同,因为指向不同对象),但是它们引用的是不同的对象。因此,修改其中一个对象的引用对象不会影响另一个对象。
对于浅拷贝我们能通过clone()实现。
而深拷贝我们能通过Serializable接口去实现,这个我们之后会学习,现在还没到那个时候。
抽象类和接口的区别
❤️❤️抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(重要!!! 常见面试题).
核心区别:
1.抽象类中可以包含方法和成员变量, 这样的方法和成员变量可以被子类直接使用(不必重写)
2.接口中只能有抽象方法,default或static修饰的方法,。子类必须重写所有的抽象方法.
再次提醒: 抽象类存在的意义是为了让编译器更好的校验, 像 Animal 这样的类我们并不会直接使用, 而是使用它的子类. 万一不小心创建了 Animal 的实例, 编译器会及时提醒我们.
Object类
🎯🎯Object是Java默认提供的一个类。之前也讲过,Java里面当类没继承关系时,会默认将Object类当作父类,所以所有的类都会继承Object类。它有可能是父类,也有可能是父类的父类等。所以所有类的对象都可以使用Object的引用进行接收。
对于object类,不需要自己主动将其类导入。系统会默认帮你导入这个object类。
❤️❤️范例:使用Object接收所有类的对象:
class Person{} class Student{} public class Test { public static void main(String[] args) { function(new Person()); function(new Student()); } public static void function(Object obj) { System.out.println(obj); } } //执行结果: Person@1b6d3586 Student@4554617c
所以在开发之中,Object类是参数的最高统一类型,所有类都可以用它接收。并且我们的Object类内部还存在定义好的一些方法。如下:
对于object类的方法,我们目前要了解的有四个,clone(),toString(),equals(),hashcode()。刚才已经讲过了clone(),所以现在讲解一下其他三个。
tostring获取对象信息
💞 💞对于object里的tostring(),如果参数是基本类型变量,则返回其基本类型变量的值。如果参数是引用类型变量,返回其引用变量的值(地址)。(注意返回值是String类型,所以都要带双引号)
所以如果我们要打印出对象中的内容,用object里的tostring()是没用的,我们需要直接重写Object类中的toString()方法,对于这个重写我们之前已经讲过了,此处不再累赘。这里直接看一个很典型的重写代码:
public String toString() { return "Person{" + "age=" + age + ", money=" + money.m + '}'; }
equals方法
❤️❤️在Java中,用==进行比较时:分为两种:
如果==左右两侧是基本类型变量,比较的是变量中值是否相同
如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
对于我们的equals,它适用于类的对象的比较。以下是其equals内部代码:
// Object类中的equals方法 public boolean equals(Object obj) { return (this == obj); // 使用引用中的地址直接来进行比较 }
通过观察以上代码可知,因为其参数为object,所以适用于类的对象的比较。
而通过对其观察,因为其object的equals方法是直接拿地址进行比较,所以我们要比较对象中内容时,必须重写Object中的equals方法,否则会得到错误结果。
如下:
class Person{ private String name ; private int age ; public Person(String name, int age) { this.age = age ; this.name = name ; } } public class Test { public static void main(String[] args) { Person p1 = new Person("gaobo", 20) ; Person p2 = new Person("gaobo", 20) ; int a = 10; int b = 10; System.out.println(a == b); // 输出true System.out.println(p1 == p2); // 输出false System.out.println(p1.equals(p2)); // 输出false } }
此时无论如何,用equals方法比较两个类对象其结果都是false,所以比较类的对象中内容是否相同的时候,一定要重写equals方法。
对于该重写方法我们可以通过快捷键自动生成equals重写方法:
对于该代码,在正常情况下我们主要考虑最后一段代码
我们观察下objects.equals的内部代码
这里有疑惑的是又出现了equals,如果这里的a和b是普通类的话,那么它们直接比较地址不就返回false,结果不又是一直false的吗,所以我们引出下文:
在String类中,equals跟compareTo一样都被重写了,为上图代码。通过该代码可知,只要两个字符串完全相等,则返回true,否则false。
所以只要是有String类.equals方法,那么就会引用String类中的equals的重写方法,从而判断字符串是否相等。
所以我们现在就很好的分析完这段代码以及其内部代码了,懂得了equals方法的相关知识点。
hashcode方法
对于hashCode()这个方法,它的作用:它能帮我算出对象的具体位置,而这里面涉及数据结构,但是我们还没学数据结构,没法讲述,所以我们只能说它是个内存地址。我们要等到学哈希表才能真正清楚的去讲述清楚它。所以先放一下这个hashcode方法,之后再讲清楚。
总结
所以这就是我们的抽象类与接口的最后一章,里面涉及的知识点过多,还请大家认真多看几遍,好好消化。一起加油吧!帮作者点点关注,评评论谢谢家人们了~❤️❤️🥳🎉🎉🎉