前言
上一篇讲到了接口,接下来将补充一下常用的接口以及Object类的初识,链接如下:
《JAVA SE》面向对象编程(中篇)
一、 Object类初识
✦JAVA中的万物之母 : Object类
✦全名称:包名.类名
✦java.lang.Object
- Object类是Jvav中所有类的默认父类,无须使用extends来定义。
- class声明的类都有一个父类,Object类。
- 因为Object类是所有类的父类,使用Object引用来接受所有的类型,参数最高统一化。
eg:
注:Java中所有类型都可以发生向上转型变成Object类型
- Object类中所有的方法都被子类继承下来了~~
- 之所以System.out.println(任意的引用类型) = 》是因为里面默认都会调用该类型的toString()方法,因为Object类存在toString();
如果想输出当前类的属性值,我们就得覆写toString()方法!!!
- JAVA中引用数据类型之间的相等比较使用equals方法!!不能用“==”,比较的是地址。
自己覆写equals方法,完成对自己定义的Student类比较属性的方法。
@Override
public boolean equals(Object obj) {
// 1.若当前对象就是obj
if (this == obj) {
return true;
}
// 2.此时当前对象和obj指向的对象确实不是一个地址
// 若此时obj指向的对象和Student压根没关系,obj指向一个Dog对象,没有可比性,直接返回false
if (obj instanceof Student) {
// 3.obj这个引用指向的对象确实是Student类的对象且和当前对象不是一个对象
// Object obj = new Student();
Student stu = (Student) obj;
// 所有引用类型比较属性值一定要用equals方法,"=="比的是地址!!!!!!!!
return this.score == stu.score && this.name.equals(stu.name);
}
return false;
}
- Object不仅是所有类的父类,JDK对Object类做了扩展。==Object类可以接受所有引用数据类型的对象(接口、数组、类)==
==因此在Java中,若一个方法参数或者返回值是Object类型,说明该参数或者返回值可以是任意引用数据类型(数组、类、接口)==
此时除了8大基本类型没法用Object类来接收之外,所有类型都能使用Object类来接收。
包装类应运而生~
二、接口使用实例
接口优先原则,当一个场景既可以使用抽象类也可以使用抽象类定义时,优先考虑使用接口(更灵活),以下将介绍两个JDK内置的常用接口。
2.1 Comparable 接口
接下来将用一个例子介绍java.lang.Comparable接口:
给对象数组排序
给定一个学生类
class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
}
再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序).
Student[] students = new Student[] {
new Student("张三", 95),
new Student("李四", 96),
new Student("王五", 97),
new Student("赵六", 92),
}
按照我们之前的理解, 数组工具类我们有一个现成的 sort 方法, 能否直接使用这个方法呢?
Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to
java.lang.Comparable
仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定? 需要我们额外指定!
让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法:
class Student implements Comparable {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
@Override
public int compareTo(Object o) {
if(this == 0){
return 0; //返回0表示相等
}
if(o instanceof Student){
//当前传入的o就是Student类型的引用,向下转型还原为Student
//要比较Student对象的大小关系,就要用到Student的独有属性,向下转型
Student stu = (Student)o;
return this.score - stu.score;
}
//若传入不是学生类,则抛出异常
throw new IllegalArgumentException("不是学生类型,无法比较!")
}
在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
✦如果当前对象应排在参数对象之前, 返回小于 0 的数字;
✦如果当前对象应排在参数对象之后, 返回大于 0 的数字;
✦如果当前对象和参数对象不分先后, 返回 0;
再次执行程序, 结果就符合预期了
// 执行结果
[[王五:97], [李四:96], [张三:95], [赵六:92]]
注意事项:
对于 sort 方法来说, 需要传入的数组的每个对象都是 "可比较" 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则。
为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序):(其实Arrays.sort()内部也是和下面代码类似的,只是被封装了)
public static void sort(Comparable[] array) {
for (int bound = 0; bound < array.length; bound++) {
for (int cur = array.length - 1; cur > bound; cur--) {
if (array[cur - 1].compareTo(array[cur]) > 0) {
// 说明顺序不符合要求, 交换两个变量的位置
Comparable tmp = array[cur - 1];
array[cur - 1] = array[cur];
array[cur] = tmp;
}
}
}
}
再次执行代码
sort(students);
System.out.println(Arrays.toString(students));
// 执行结果
[[王五:97], [李四:96], [张三:95], [赵六:92]]
2.2 Clonable 接口
Java 中内置了一些很有用的接口, java.lang.Clonable 就是其中之一.
==类似于Clonable接口,把这种接口称之为“标记接口”,这种接口本身内部没有任何的抽象方法,只有打上这个标记的子类才拥有克隆能力!==
==JVM在运行时会检查所有实现了Cloneable接口的子类,赋予其克隆的能力。==
注意:clone()是Object类提供的方法。
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
class Animal implements Cloneable {
private String name;
@Override
public Animal clone() {
Animal o = null;
try {
o = (Animal)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
Animal animal2 = animal.clone();
System.out.println(animal == animal2);
}
}
// 输出结果
// false
2.3 深拷贝VS浅拷贝
Cloneable 拷贝出的对象是一份 "浅拷贝”
观察以下代码:
public class Test {
static class A implements Cloneable {
public int num = 0;
@Override
public A clone() throws CloneNotSupportedException {
return (A)super.clone();
}
}
static class B implements Cloneable {
public A a = new A();
@Override
public B clone() throws CloneNotSupportedException {
return (B)super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
B b = new B();
B b2 = b.clone();
b.a.num = 10;
System.out.println(b2.a.num);
}
}
// 执行结果
10
通过 clone 拷贝出的 b 对象只是拷贝了 b 自身, 而没有拷贝内部包含的 a 对象. 此时 b 和 b2 中包含的 a 引用仍然是指向同一个对象. 此时修改一边, 另一边也会发生改变。
==Java中实现深拷贝的方法有两种:==
- 递归使用clone()方法
- 序列化(json字符串)
总结
抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别。
核心区别:
抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.