目录
1、接口使用实例
给对象(student)数组排序
import java.util.Arrays;
class Student{
public String name;
public int age;
public int score;
public Student(String name ,int age, int score){
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
//测试类
public class Test {
public static void main(String[] args) {
Student[] student =new Student[3];
student[0] = new Student("zhangsan",19,10);
student[1] = new Student("lisi",29,20);
student[2] = new Student("wangwu",39,5);
Arrays.sort(student);//这样排序编译器会报错,因为编译器不知道按什么方式给数组成员排序
System.out.println(Arrays.toString(student));
}
运行结果为:
结果中蓝色字体表示:自己编写的程序有错误
灰色字体表示:调用的方法在那一行出错。
📕逐步分析学生数组排序的写法
✨思路:
- 要排序先从数组的元素比较开始
- 元素比较完成之后,在进行排序
1️⃣、元素比较
这里要比较学生对象,还是要看一下Java中给出ComparableTimSort.java第320行的源码。
在我们自己写的代码中Student和Comparable这个类型没有关系,联系不到一起。所以在来看这个源码
所以在Student类上实现接口Comparable就可以(在Student类上实现接口Comparable接口,实际上就是规定了一个比较规则)
class Student implements Comparable<Student>{//这里的<>表示泛型,<>中的Student表示比较Student这个类
在Comparable接口的源码中,有一个compareTo的方法 ,所以在Student类中重写这个方法。但是在ComparableTimSort.java方法的源码中调用了compareTo的方法,刚好对上
在数组中存在的是student[0]、student[1]和student[2]的引用,是学生的地址,但是在比较的时候不可能是对地址的比较。所以因该还要提供比较的方法。所以重写的compareTo方法就是比较的方法。
在测试类中调用compareTo方法对student[0]和student[1]进行比较
System.out.println(student[0].compareTo(student[1]));
🎃 那么现在我们按照学生的年龄来比较:因为重写的方法的返回值是int类型,所以比较的返回值设置为1,-1,0
@Override public int compareTo(Student o) { if(this.age > o.age) {//this表示的是:谁调用这个compareTo这个方法,谁就是this //this表示的就是student[0],o表示的就是student[1] return 1; }else if(this.age < o.age){ return -1; }else { return 0; } } }
上述写法有些繁琐,可以这样写,更简洁
public int compareTo(Student o) { return this.age - o.age; }
🎃、按照名字来比较
这里很多人就想到了equals这个方法,但是放在这里用作比较肯定是不满足需求的,并且他只能比较相不相同,不能用来比较大小。来看一下他的源码
再来了解一下String这个类型
那么此时用名字比较的方法为:
public int compareTo(Student o) { if (this.name.compareTo(o.name) > 0) { return 1; } else if (this.name.compareTo(o.name) < 0) { return -1; } else { return 0; }
这里将比较的代码解释一下
在Student类中
在测试类中
2️⃣、数组元素排序
测试类中:
1、直接调用Java中的sort方法
Arrays.sort(student); System.out.println(Arrays.toString(student));
2、通过自己写一个冒泡排序
public class Test { public static void sort(Comparable[] array) {//写接口数组作为形式参数,接收传过来的数组参数 for (int i = 0; i < array.length-1; i++) { for (int j = 0; j < array.length-1-i; j++) { /*if(array[j] > array[j+1]) { 交换; }*/ if(array[j].compareTo(array[j+1]) > 0) { Comparable tmp = array[j]; array[j] = array[j+1]; array[j+1] = tmp; } } }
✨代码实现
1️⃣、元素之间的比较
🎉按照年龄比较,将完整的代码写出来
import java.util.Arrays;
class Student implements Comparable<Student>{
public String name;
public int age;
public int score;
public Student(String name ,int age, int score){
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
if(this.age > o.age) {//this表示的是:谁调用这个compareTo这个方法,谁就是this
//this表示的就是student[0],o表示的就是student[1]
return 1;
}else if(this.age < o.age){
return -1;
}else {
return 0;
}
}
}
public class Test {
public static void main(String[] args) {
Student[] student =new Student[3];
student[0] = new Student("zhangsan",19,10);
student[1] = new Student("lisi",29,20);
student[2] = new Student("wangwu",39,5);
System.out.println(student[0].compareTo(student[1]));
}
}
🎉 按照名字比较,将完整代码写出来
import java.util.Arrays;
class Student implements Comparable<Student> {
public String name;
public int age;
public int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
if (this.name.compareTo(o.name) > 0) {
return 1;
} else if (this.name.compareTo(o.name) < 0) {
return -1;
} else {
return 0;
}
}
}
public class Test {
public static void main(String[] args) {
Student[] student =new Student[3];
student[0] = new Student("zhangsan",19,10);
student[1] = new Student("lisi",29,20);
student[2] = new Student("wangwu",39,5);
System.out.println(student[0].compareTo(student[1]));
}
2️⃣、数组元素排序(按名字排序)
🧨调用Java中的Array.sort();方法
import java.util.Arrays;
class Student implements Comparable<Student> {
public String name;
public int age;
public int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
if (this.name.compareTo(o.name) > 0) {
return 1;
} else if (this.name.compareTo(o.name) < 0) {
return -1;
} else {
return 0;
}
}
}
public class Test {
public static void main(String[] args) {
Student[] student =new Student[3];
student[0] = new Student("zhangsan",19,10);
student[1] = new Student("lisi",29,20);
student[2] = new Student("wangwu",39,5);
Arrays.sort(student);
System.out.println(Arrays.toString(student));
}
🧨调用自己写的冒泡方法(sort)
import java.util.Arrays;
class Student implements Comparable<Student>{
public String name;
public int age;
public int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
if(this.name.compareTo(o.name) > 0) {
return 1;
}else if(this.name.compareTo(o.name) < 0) {
return -1;
}else {
return 0;
}
}
}
public class Test3 {
public static void sort(Comparable[] array) {//只要Student类实现了接口Comparable就可以
for (int i = 0; i < array.length-1; i++) {
for (int j = 0; j < array.length-1-i; j++) {
/*if(array[j] > array[j+1]) {
交换;
}*/
if(array[j].compareTo(array[j+1]) > 0) {
Comparable tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan",19,10);
students[1] = new Student("lisi",59,20);
students[2] = new Student("abc",39,5);
sort(students);
System.out.println(Arrays.toString(students));
}
}
✨弊端
上述代码中的比较方法存在不灵活的弊端。
在compareTo方法中实现的比较方法在Student类中写死之后,在测试类中没有办法改变。
public int compareTo(Student o) {
if (this.name.compareTo(o.name) > 0) {
return 1;
} else if (this.name.compareTo(o.name) < 0) {
return -1;
} else {
return 0;
}
当我们在实际的项目中,三种比较方法要灵活切换,该怎样实现?
我们不可能直接在类中修改比较的方法,直接在类中修改,那使用过这个类的测试结果,就会全部出错。
所以下面来看一下改进的方案。
📕、改进
改进思路:
🎊1、这里来了解一下Comparator接口。
compare方法根据其返回值确定比较对象的大小,如果返回值为正,认为o1>o2;返回值为负,认为o1<o2;返回值为0,认为两者相等。
🎊2、再来了解一下Arrays类中的sort(T[],Comparator<? super T>):void方法。
public static <T> void sort(T[] a, Comparator<? super T> c) {
上述方法可以根据比较器的compare方法对数组进行排序,compare方法的不同实现对应着不同的排序准则。
🎊3、设置比较器
创建一个AgeComparator类,重写compare方法(以年龄比较)
class AgeComparator implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { return o1.age - o2.age; } }
创建一个ScoreComparator类,重写compare方法(以成绩比较)
class ScoreComparator implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { return o1.score- o2.score; } }
创建一个NameComparator类,重写compare方法(以姓名比较)
class NameComparator implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { return o1.name.compareTo(o2.name);//这里compareTo方法是String类中实现Comparable接口后重写的方法 } }
代码实现:
import java.util.Arrays;
import java.util.Comparator;
class Student implements Comparable<Student> {
public String name;
public int age;
public int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
if(this.age > o.age) {
return 1;
}else if(this.age < o.age){
return -1;
}else {
return 0;
}
}
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.score- o2.score;
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class Test{
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan",10,19);
students[1] = new Student("lisi",8,78);
students[2] = new Student("wangwu",15,57);
//比较器
//这样就是实现了灵活使用不同规则排序
AgeComparator ageComparator = new AgeComparator();
ScoreComparator scoreComparator = new ScoreComparator();
NameComparator nameComparator = new NameComparator();
//Arrays.sort(students);//当然不传,是以Student类实现的接口Comparable中重写的compareTo方法的比较规则排序
Arrays.sort(students,ageComparator);
// Arrays.sort(students,scoreComparator);这里传的是那个比较器,就用那种比较规则排序
//Arrays.sort(students,nameComparator);
System.out.println(Arrays.toString(students));
}
}
运行结果:
2、Cloneable接口和深拷贝
2.1、cloneable接口的作用
- Java中内置了一些很有用的接口,Cloneable就是其中之一。
- Object 类中存在一个clone()方法,调用这个方法可以创建一个对象的"拷贝"。但是要想合法调用clone()方法,必须要先实现Cloneable接口,但是Cloneable是一个空接口,里面没有任何内容,但是如果没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。
- 通常实现了Cloneable接口的子类,应当以public访问权限重写clone()方法(尽管Java.Object类中的clone方法是protected类型的)
- 因为每个类的基类都是Object,每个类都会默认继承Object类,所以每个类都有clone方法,但是它是protected,所以不能在不同包当中的类中访问,要在不同包当中的子类(也就是说继承了Object类的类)中访问,克隆一个对象,需要重写clone
所以可以把Cloneable 接口看成是实现clone()方法必须要的一个因素。
2.2、深拷贝和浅拷贝
2.2.1、浅拷贝
我们通过这个代码来了解一下浅拷贝:
package demo;
class Money{
public double money = 12.25;
}
//要拷贝student,当然Student要实现Cloneable接口
class Student implements Cloneable{
public String name;
public Money m = new Money();//实例化对象m,将对象的地址存入引用m中
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student();
Student student2 =(Student)student.clone();//student2只拷贝了student对象,并没有拷贝m对象
System.out.println(student.m.money);//对象student调用属性m,m是类Money的引用
System.out.println(student2.m.money);
System.out.println("=============");
student2.m.money = 99;//修改拷贝后的m所引用的对象
System.out.println(student.m.money);
System.out.println(student2.m.money);
}
}
如上代码,我们可以看到,通过clone,我们只拷贝了student对象。但是studentd对象中的m对象,并没有拷贝,通过student2这个引用修改了money的值后,student这个引用访问money的时候,值也发生了改变。这里就是发生了浅拷贝。
画图理解:
2.2.2、深拷贝
同样通过代码来了解深拷贝:要实现深拷贝,那么当前对象中的所有对象都得拷贝。
package demo;
//Money对象要实现拷贝,就得在Money类中实现接口Cloneable,并重写clone方法
class Money implements Cloneable{
public double money = 12.25;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//Student对象要实现拷贝,就得在Student类中实现接口Cloneable,并重写clone方法
class Student implements Cloneable{
public String name;
public Money m = new Money();
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {//克隆方法的返回值为Object类型
//两次克隆都发生了向下转型,都是将Object类转换为各自引用相应的类
//只是克隆了Student对象,发生向下转型,所以将Object类型强转为Student类型
//这里在Student类中创建一个student3引用,用来接收克隆后的对象的地址
Student student3 = (Student) super.clone();
//克隆了Student对象 里面的Money对象,克隆的是Money对象,所以将Object类型强转为Money类型
//将克隆后的对象地址传给student3引用所指向的对象中的m引用
student3.m = (Money) this.m.clone();
//这里调用的clone方法是Money类当中的clone方法。
//通过this.m调用clone方法,现在需要克隆的是student对象中m引用所指向的对象,所以this代表的就是student引用
return student3;
//这里是将克隆后用来接收克隆结果的student3作为返回结果返回
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student();
//student调用clone方法,将student对象进行拷贝,student2引用将student克隆结果进行接收
Student student2 =(Student)student.clone();//student2接收了返回的克隆结果
System.out.println(student.m.money);
System.out.println(student2.m.money);
System.out.println("=============");
student2.m.money = 99;
System.out.println(student.m.money);
System.out.println(student2.m.money);
}
}
画图解释:
🧨🧨🧨 总结:深浅拷贝和你实现的方法(例如:clone方法)没关系,和你实现的方式(代码的实现)有关系
3、Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的 。所有的类默认会继承Object这个父类。即所有类的对象都可以使用Object的引用进行接收。
范例:使用Object接收所类的对象
class Person{};
class Student{};
public class Test {
public static void function(Object obj) {
}
public static void main(String[] args) {
function(new Person());
function(new Student());
}
}
上述代码中的 没有引用接收的对象:叫做匿名对象。
function(new Person());
function(new Student());
它的缺点就是每次使用都得new。他的使用场景是只需要使用一次的时候。
Object类中存在有定义好的一些方法。如下:
我们来了解一些上述的方法
3.1、toString方法的调用
❓❓❓先来想一个问题,为什么Object类型中实现了toString方法,当我们在类中重写了toString方法后,运行代码时,回调用我们自己的toString方法?
带着问题,我们通过代码来了解toString方法。
class Student{
public String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student);
}
}
画图解释:
- 上述代码中将student引用传给println方法,println方法的实现中用Object类型的 x引用接收;
- 在prinln方法中有调用了String类中的valueOf方法,将参数x传给了valueOf方法;
- valueOf方法用Object类型的obj引用接收,就相当于valueOf方法中obj这个引用,引用的就是student对象,就相当于父类引用,引用子类对象,所以发生了向上转型和动态绑定。站在valueOf这个方法的角度看,当引用的子类对象不一样,发生的调用toString方法的行为是不一样的(若在一个类中没有重写toString方法,他就会调用Object类当中的toString方法 )。这也就体现了多态的思想。
- 在valueOf方法的实现中,判断使用哪种toString方法,调用Object类当中的toString方法,还是调用自己在Student类当中重写的toString方法。
3.2、equals方法(比较是否一样)
✨第一种:用 == 判断相等
class Student{ public String name; @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } } public class Test2 { public static void main(String[] args) { Student student1 = new Student1(); student1.name = "zhangsan"; Student student2 = new Student1(); student2.name = "zhangsan"; System.out.println(student1 == student2); } }
假设在一个班中,只有一个zhangsan,现在代码中有两个同学都叫zhansan。那么在我的逻辑上,我认为这两个同学是同一个同学。但是不能通过像上述代码中的 用==去判断,那样结果会是
这是将两个引用进行比较,引用中存的是地址,比较结果肯定是false。
✨第二种:调用equals方法,不重写。
那么我么使用equals方法来比较
先看代码
class Student1{ public String name; @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } } public class Test2 { public static void main(String[] args) { Student1 student = new Student1(); student.name = "zhangsan"; Student1 student2 = new Student1(); student2.name = "zhangsan"; boolean flg = student.equals(student2); System.out.println("flg:"+flg); } }
结果还是false,为什么呢?
这里实现的效果和用==判断是一样的。因为我们没有在Student1类中重写equals方法,现在使用的是Object类中的equals方法
✨第三种:调用equals方法,并且在类中重写equals方法。
class Student1{ public String name; @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } public boolean equals(Object obj) { if(obj == null) { return false; } //判断是否指向的是同一个对象 if(this == obj){ return true; } //判断两个对象是否为同一个类型(判断是不是Student1类) if(!(obj instanceof Student1)){ return false; } Student1 student =(Student1) obj; if(this.name.equals(student.name)){ return true; } return false; } } public class Test2 { public static void main(String[] args) { Student1 student = new Student1(); student.name = "zhangsan"; Student1 student2 = new Student1(); student2.name = "zhangsan"; boolean flg = student.equals(student2); System.out.println("flg:"+flg); } }
equals方法的调用,和toString 方法的调用是同理的,所以我们在写代码时,在使用给定的方法时,一定要符合自己的代码场景,当然不符合使用场景的时候我们可以在继承了该方法所在类之后,对该方法进行重写。满足自己的使用场景。
✨第四种:通过编译器自己生成重写equals方法。
第一步:
第二步:
第三步:
往后直接戴拿Next就行,知道生成代码就行
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student1 student1 = (Student1) o; return Objects.equals(name, student1.name); } @Override public int hashCode() { return Objects.hash(name); } }
以后写代码的时候更多的是通过编译器自己生成需要重写的方法