在Java中,所有的事物都可以用类来描述,但并不是所有的类都是用来描绘的。如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
访问权限修饰符 abstract class 类名 {
// 成员变量
// 成员方法
// 构造方法
// 抽象方法(用abstract修饰的方法)
abstract 权限修饰符 返回类型 方法名(参数);
}
注意:用 abstract 关键字修饰的类即为抽象类,抽象类也是类,内部可以包含普通方法和属性,甚至构造方法。
abstract class Shape {
// 抽象类也是类,也可以增加普通方法和属性
protected double area; // 面积
public double getArea(){
return area;
}
// 抽象方法:被abstract修饰的方法,没有方法体
abstract public void draw();
abstract void calcArea();
}
// 编译报错,抽象类无法实例化
Shape shape = new Shape();
抽象类可以继承另一个抽象类。若抽象类含有抽象方法,则继承该抽象类的普通类必须重写该抽象类及其父类的所有抽象方法。(将子类声明为抽象类,则可不实现抽象方法)
抽象类不能用 final 关键字修饰,抽象方法不能用 private 或 static 修饰。(原因: 抽象类必须被继承,而加上 final 关键字的类不能被继承;抽象方法必须被子类重写,而 private 修饰的方法只能在类内使用;static 修饰的方法为类方法,子类不能进行重写)
抽象类虽然可以使用普通类替代,但实际上使用抽象类可以充分利用编译器的校验功能。在某些场景下,一些工作应该由子类去完成(重写父类方法),若不小心误用父类,使用实例化的普通父类编译器并不会报错,从而造成逻辑错误。
在日常生活中,接口几乎随处可见:(如图)
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备。
电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备。
从以上例子可知:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口描述了一个类或对象的外部行为,而不关心其内部实现。其他类可以实现这个接口,并提供相应的方法实现。
interface 接口名称 {
// 可以有成员变量,以下写法等价
int x;
public final static double y;
// 抽象方法,以下写法等价
public abstract void method1(); // public abstract 为固定搭配,可以省略
public void method2();
abstract int method3();
double method4(); // 推荐接口方法使用该写法,表达更为简洁
}
注意:接口可以有成员变量,默认由public final static修饰;接口的方法一定是抽象方法,默认由 public abstract 修饰。
接口可以被类使用 implements 关键字实现,普通类实现接口必须重写接口的所有方法;也可以被另一个接口使用 extends 关键字继承,用于拓展接口的功能。
例如有以下父类和接口:
// 父类
abstract class Animal {
protected String name; // 属性
protected String species;
// 构造方法
public Animal(String name, String species) {
this.name = name;
this.species = species;
}
// 成员方法
public void eat() {}
}
// 接口,实现该接口的类都具有“跑”的功能
interface Running {
void run();
}
实现Comparable接口需要在类内部重写compareTo()方法,该方法返回一个整型值,表示当前对象与另一个同类型对象之间的大小关系。
Comparable接口的作用:
使用Comparable接口进行非递减排序,应让调用该方法的对象的属性值 - 作为方法参数的对象的属性值,即返回值为 this.属性 - o.属性。反之,进行非递增排序,返回值为 o.属性 - this.属性。
使用实例:
1.创建一个Student 对象并实现Comparable接口
class Student implements Comparable<Student>{
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public int compareTo(Student o) {
if (this == o) return 0;
// 非递减排序
// 先按照分数排序
int ret = this.score - o.score;
// 分数如果相同按年龄大小排序
ret = ret==0?this.age-o.age:ret;
return ret;
}
}
2.创建Studnet对象数组并进行排序
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("zhangsan",15, 78),
new Student("lisi",18, 95),
new Student("wangwu", 12,88)};
System.out.println("===========");
Arrays.sort(students);
}
实现Conparator接口需要重写compare()方法,该方法同样返回一个整数值,表示两个对象之间的大小关系。
Comparator接口的作用:
使用实例:(实现Comparator接口创建大根堆)
1.先创建一个Student类
class Student {
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getScore() {
return score;
}
}
2.创建优先队列,使用内部类实现Comparator接口并重写compare()方法;加入Student对象。
public static void main(String[] args) {
PriorityQueue<Student> queue = new PriorityQueue<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getScore() - o1.getScore();
}
});
queue.add(new Student("zhangsan",15, 78));
queue.add(new Student("lisi",18, 95));
queue.add(new Student("wangwu", 12,88));
queue.add(new Student("tianqi", 12,99));
System.out.println("===========");
}
Cloneable的中文意思是“可克隆的”,从它的含义我们大概猜出实现这个接口的类就具有“克隆”的能力,事实也确实是这样的,但要想真正了解Cloneable接口的正确用法,我们需要先了解Object类中的clone()方法。
假设有以下定义的Person类和一个对应的实例化对象
// Person类
class Person {
public int age;
public Person(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
//实例化一个Person对象
public class Test {
public static void main(String[] args) {
Person person1 = new Person(18);
}
}
在Java中所有的类都默认继承Object类,而Object类中带有一个clone()方法,如果我们想要得到两个一模一样的类对象,就可以通过一个对象调用该方法“克隆”出另一个相同对象。
但如果我们真正去调用该方法时,可能会出现Person类的方法调用中找不到clone()方法的情况,原因是什么呢?
其实我们通过查看Object()类的方法实现可发现clone()方法是被 protected 关键字修饰的,因此该方法只能在Object类内、与Object()类同个包的类 或 其子类内使用(当前即Person类)。
当我们想要在另一个类的调用clone()方法,应在Person类中重写clone()方法才能实现在另一个类调用该方法的效果。(重写方法的前后效果对比如下)
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
当我们重写clone()方法后,会发现使用clone()方法依然会出现如下报错。
这里的原因其实有两个:
当我们完成以上步骤,调用toString()方法打印将这两个对象的信息,依然发现程序运行时报了以下异常。
这个异常的意思是该类不支持被克隆。
到此,Cloneable接口的作用在这里体现出来了,只有实现Cloneable接口的类才支持被克隆。
通过前面对接口的介绍,我们都知道实现一个接口必须重写接口中的所有方法,但实际上Cloneable接口是一个空接口,即这个接口里面没有任何方法。
因此,Cloneable接口相当于一个标记接口,实现该接口则证明了该类是可以被克隆的。
接口实现及代码运行结果如下:
class Person implements Cloneable{
public int age;
public Person(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person(18);
Person person2 = (Person) person1.clone();
System.out.println(person1.toString());
System.out.println(person2.toString());
}
}
什么是浅拷贝?
浅拷贝可以简单理解为调用clone()方法克隆出的对象并非所有资源独立,而存在部分共享资源。
对于上面定义的Person类,我们通过 “==” 可以知道person1 和 person2 这两个引用并非指向同一个对象,因此它们此时是资源独立的。
当我们对Person类作出以下修改:(在Person类增加一个类对象成员)
// 新增类
class Purse {
int money;
}
class Person implements Cloneable{
public int age;
Purse purse; // 新增类成员
public Person(int age, int money) {
this.age = age;
purse = new Purse();
purse.money = money;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", money=" + purse.money +
'}';
}
}
同样实例化一个Person对象,使用clone()方法克隆出一个新的对象并对两个person对象的信息进行打印:
可以看出表面上确实克隆出了一个一模一样的全新的对象,但当我们通过其中一个person对象修改money的值时会发现两个perosn对象的money值都发生了改变。
原因是什么呢?
通过简单的思考我们也许可以猜测出,虽然person1 和 person2 所指的是两个不同的对象,但是对象里面purse引用指向的是同一个pruse对象,所以修改其中一个person对象里面purse对象里面money属性,会同时影响person1 和 perosn2 中的money属性。(person1 和 person2 的内存分布及代码验证如下)
因此要实现深拷贝,我们需要将Person类中的Pruse也实现Cloneable接口并重写Object类的clone()方法。当调用person1的clone()方法时,不要立即返回结果,先将返回值用临时Person对象接收,并调用purse成员的clone()方法,使purse引用指向一个新的Purse对象。(代码实现及程序运行结果如下)
class Purse implements Cloneable{
int money;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public int age;
Purse purse;
public Person(int age, int money) {
this.age = age;
purse = new Purse();
purse.money = money;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.purse = (Purse) tmp.purse.clone();
return tmp;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", money=" + purse.money +
'}';
}
}
抽象类和接口最核心的区别为:
抽象类可以包含普通成员变量和普通方法,而接口只能包含由 public final static 修饰的常量和抽象方法。
以上就是本篇文章的全部内容了,如果这篇文章对你有些许帮助,你的点赞、收藏和评论就是对我最大的支持。
另外,文章的不足之处,也希望你可以给我一点小小的建议,我会努力检查并改进。