JavaSE之抽象类和接口
文章目录
- 抽象类
- 抽象类概念
- 抽象类语法
- 抽象类要点总结
- 接口
- 接口的概念
- 接口实例
- 接口间的多继承
- 接口要点总结
- Comparable接口
- Comparator接口
- Clonable接口
- 深拷贝的代码实现
- 抽象类和接口的区别
给个关注叭
个人主页
JavaSE专栏
前言:本篇文章主要整理了抽象类的概念及语法、抽象类要点总结.接口的概念,通过实例进一步理解接口、接口间的多继承、接口要点总结、Comparable接口、Comparator接口、Cloneable接口、浅拷贝和深拷贝代码实现及其堆栈图、抽象类和接口的区别。
抽象类
抽象类概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,那么这样的类就是抽象类。
比如:
- Animal类是动物类,其内部有一个bark方法,每个动物都有不同叫的方法,但是由于Animal类不是一个具体的动物,因此其内部bark()方法无法具体实现
- Dog是狗类,与Animal类是继承关系,其次狗是一种具体的动物,其bark方法有具体的叫法,“汪汪汪”
- Cat是猫类,与Animal类是继承关系,其次猫是一种具体的动物,其bark方法有具体的叫法,“喵喵喵”
- 所以这种Animal类中的bark()方法不需要有具体的实现,那么可以把Animal类设置为抽象类,只需让其具体的子类根据需要去重写这个抽象方法
在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法, 包含抽象方法的类我们称为 抽象类
抽象类语法
抽象类需要用abstract
的关键字来修饰类,同时abstract
关键字也可以修饰方法,被abstract
修饰的方法称为抽象方法,抽象方法可以没有具体的实现。代码示例如下:
// 抽象类:被abstract修饰的类
public abstract class Shape {
// 抽象方法:被abstract修饰的方法,没有方法体
abstract public void draw();
abstract void calcArea();
// 抽象类也是类,也可以增加普通方法和属性
public double getArea(){
return area;
}
protected double area; // 面积
}
抽象类首先是一个类,和普通类一样,可以有普通方法和变量,甚至构造方法,可以被继承,唯一不同的是抽象类不能实例化对象
抽象类要点总结
- 被
abstract
修饰的类称为抽象类,被abstract
修饰的方法称为抽象方法,抽象方法可以没有具体的实现 - 抽象类和普通类一样,可以定义普通类中的成员变量和成员方法,构造方法
- 抽象类不能实例化对象
- 如果一个类中包含抽象方法,那么这个类一定是抽象类(被
abstract
修饰) - 抽象类就是用来被继承的,继承后,子类一定要重写抽象类中的所有抽象方法,不然会有红线
在子类B中要重写抽象类A中的所有抽象方法,代码示例如下:@Override public void func1() { }
- 如果不想重写抽象类的抽象方法,那么可以同样用
abstract
修饰子类;但是“出来混迟早是要还的”,如果再有一个类继承了这个类(已经继承了抽象类的类),那么必须重写这两个类中所有的抽象方法。
这里虽然B类使用abstract
关键字修饰来避免了重写父类A中的抽象方法,但是C类继承了B类,C类还是要把A类和B类中的所有抽象方法全部重写
代码示例如下:class C extends B { @Override public void func1() { } @Override public void func2() { } }
- 抽象方法既然要被重写,就要遵循重写的规则,抽象方法不能被final、private、static修饰
- 被private关键字修饰后,说明这个变量或方法只能在当前类中被访问。有关
private
关键字的更多介绍,请移步 封装.访问限定符 - 被static关键字修饰后,说明这个变量或方法是当前类的变量或方法,不属于某一个具体的对象,是所有对象共享的;随类的加载而创建,随类的销毁而卸载;而且只随类的创建加载一次,再次加载类或者new一个对象时,不会再次加载。有关
static
关键字的更多介绍,请移步 封装.static
接口
接口的概念
接口类似一种功能,只要某个事物具备这样的功能,都可以实现这个接口。(公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。)
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
接口实例
实例一、
定义IShpe接口,Rect、Triangle、Cycle三个类实现IShape接口
public interface IShape {
void draw();//默认被public abstract修饰,建议不加,代码更简洁
}
public class Rect implements IShape{
@Override
public void draw() {
System.out.println("画一个矩形...");
}
}
public class Triangle implements IShape{
@Override
public void draw() {
System.out.println("画一个三角形...");
}
}
public class Cycle implements IShape{
@Override
public void draw() {
System.out.println("画一个圆...");
}
}
public class Test {
public static void drawShape(IShape shape) {
shape.draw();
}
public static void main(String[] args) {
IShape rect = new Rect();
IShape triangle = new Triangle();
IShape cycle = new Cycle();
drawShape(rect);
drawShape(triangle);
drawShape(cycle);
}
}
运行结果:
画一个矩形…
画一个三角形…
画一个圆…
结果表明,向上转型也可以体现在类实现接口的关系上(同时也体现出了动态绑定和多态)
实例二、
实现笔记本电脑使用USB鼠标、USB键盘
- USB接口:包含打开设备、关闭设备功能
public interface IUsb {
void openDevice();
void closeDevice();
}
- 鼠标类:实现USB接口,并具备点击功能
public class Mouse implements IUsb{
@Override
public void openDevice() {
System.out.println("连接鼠标...");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标...");
}
public void click() {
System.out.println("点击鼠标...");
}
}
- 键盘类:实现USB接口,并具备输入功能
public class KeyBoard implements IUsb{
@Override
public void openDevice() {
System.out.println("连接键盘...");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘...");
}
public void input() {
System.out.println("输入数据...");
}
}
- 笔记本类:包含开机功能、关机功能、使用USB设备功能
public class Computer {
public void openPower() {
System.out.println("打开电脑...");
}
public void closePower() {
System.out.println("关闭电脑...");
}
public void IUsbService(IUsb usb) {
usb.openDevice();
if(usb instanceof Mouse) {
Mouse mouse = (Mouse) usb;
mouse.click();
}else if(usb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard) usb;
keyBoard.input();
}
usb.closeDevice();
}
}
- 测试类
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.openPower();
//使用鼠标
computer.IUsbService(new Mouse());
//使用键盘
computer.IUsbService(new KeyBoard());
computer.closePower();
}
}
运行结果:
打开电脑…
连接鼠标…
点击鼠标…
关闭鼠标…
连接键盘…
输入数据…
关闭键盘…
关闭电脑…
实例三、
一个类实现多个接口,定义Animal、Dog、Bird、Duck类,定义IRunable、ISwimable、IFlyable接口。让这些动物类实现合适的一个或多个接口
接口定义:
public interface IRunning {
void run();
}
public interface ISwimming {
void swim();
}
public interface IFlying {
void fly();
}
各种类的定义
public class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("正在吃...");
}
}
下面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
public class Dog extends Animal implements IRunning,ISwimming{
public Dog(String name,int age) {
super(name,age);
}
@Override
public void run() {
System.out.println(this.name + " 正在跑...");
}
@Override
public void swim() {
System.out.println(this.name + " 正在游...");
}
}
public class Bird extends Animal implements IFlying{
public Bird(String name,int age) {
super(name,age);
}
@Override
public void fly() {
System.out.println(this.name + " 正在飞...");
}
}
public class Duck extends Animal implements IRunning,ISwimming,IFlying{
public Duck(String name,int age) {
super(name,age);
}
@Override
public void run() {
System.out.println(this.name + " 正在跑...");
}
@Override
public void swim() {
System.out.println(this.name + " 正在游...");
}
@Override
public void fly() {
System.out.println(this.name + " 正在飞...");
}
}
测试类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("旺财",3);
Bird bird = new Bird("小飞飞",1);
Duck duck = new Duck("唐老鸭",5);
walk(dog);
walk(duck);
fly(bird);
fly(duck);
}
public static void walk(IRunning iRunning) {
iRunning.run();
}
public static void fly(IFlying iFlying) {
iFlying.fly();
}
}
运行结果:
旺财 正在跑…
唐老鸭 正在跑…
小飞飞 正在飞…
唐老鸭 正在飞…
run方法和fly方法,使用接口这种引用类型作为参数,可以不用关注类的调用者的具体类型,只要这个类具备这个功能特性(实现了这个接口),就可以使用
接口间的多继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的.比如接口C 继承接口A和接口B,那C就具A和B的功能特性,当一个类实现这个接口C时,就要重写A、B、C接口中所有的方法
interface A {
void testA();
}
interface B {
void testB();
}
interface C extends A,B{
void testC();
}
public class Test implements C{
@Override
public void testA() {
}
@Override
public void testB() {
}
@Override
public void testC() {
}
}
接口要点总结
- 接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口
- 创建接口时, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 “形容词” 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
- 接口中不能定义构造方法,也不能定义有具体实现的方法(否则会有红线)
- 接口中如果要定义有具体实现的方法,要用static 或 default 关键字修饰
- 接口中可以定义变量,默认被public static final 修饰,为静态常量(如果用默认值以外的修饰符修饰,会有红线)
- 接口中可以定义没有具体实现的方法,默认被public abstract修饰,为抽象方法(如果用默认值以外的修饰符修饰,会有红线)
- 接口中变量 或 方法可以不加任何限定修饰符,它们都有自己的默认修饰符
- 接口和抽象类一样,不能直接实例化对象,接口是用来被类实现的
- 一个类实现接口,使用 implements关键字,代表类实现了接口
- 实现接口的类,要在类中重写接口中所有的(抽象)方法
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
- 一个类可以实现多个接口,中间用逗号隔开
- 接口与接口之间也可以存在继承关系,接口与接口之间可以多继承
Comparable接口
在Comparable接口源码中,有一个compareTo方法
现有一个学生类,需要对两个学生对象 按照年龄 进行比较。不能使用直接使用 >、<或== 来比较两个学生引用。
应该让Student类实现Comparable接口,然后在Student中 按照比较需求 重写这个接口中的compareTo方法。代码示例如下:
public class Student implements Comparable<Student>{
String name;
int age;
public Student(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
if(this.age > o.age) {
return 1;
}else if(this.age == o.age) {
return 0;
}else {
return -1;
}
}//可以把上面一堆代码是换成 return this.age-o.age
}
测试类:
public class Test {
public static void main(String[] args) {
Student student1 = new Student("zhangSan",18);
Student student2 = new Student("liSi",17);
int ret = student1.compareTo(student2);
if(ret > 0) {
System.out.println("student1 > student2");
}else if(ret == 0) {
System.out.println("student1 == student2");
}else {
System.out.println("student1 < student2");
}
}
}
运行结果:
student1 > student2
使用Arrays.sort() 对学生类数组students进行排序,下图是Arrays.sort() 的源码,可以看出Arrays.sort() 的底层也是使用了comparable接口,所以Student类必须要实现comparable接口,并根据比较需求重写compareTo方法。
【students数组中的每个元素都是学生对象,比较时也是通过冒泡排序的方式,源码中要对学生对象强转为comparable接口,并且使用compareTo方法,所以Student类必须实现comparable接口】
Student 类实现comparable接口,重写compareTo方法,使用Arrays.sort(students)对学生数组按照年龄排序,代码示例如下:
public class Student implements Comparable<Student>{
String name;
int age;
public Student(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("zhangSan",18);
Student student2 = new Student("liSi",17);
Student student3 = new Student("aBao",13);
Student[] students = new Student[3];
students[0] = student1;
students[1] = student2;
students[2] = student3;
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
运行结果:
[Student{name=‘aBao’, age=13}, Student{name=‘liSi’, age=17}, Student{name=‘zhangSan’, age=18}]
Student 类实现comparable接口,重写compareTo方法,使用Arrays.sort(students)对学生数组按照姓名排序,代码示例如下:
public class Student implements Comparable<Student>{
String name;
int age;
public Student(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
运行结果:
[Student{name=‘aBao’, age=13}, Student{name=‘liSi’, age=17}, Student{name=‘zhangSan’, age=18}]
只需改动compareTo方法的比较方式,测试类Test不变
【点进String类的源码,其实String类中也实现了Comparable接口,所以String类也重写了compareTo方法,因此对于String这种类的引用是可以调用compareTo方法,来比较两个引用类型的大小的】
使用冒号排序BubbleSort()实现 Arrays.sort(),按照姓名进行比较排序,代码示例如下:
public class Student implements Comparable<Student>{
String name;
int age;
public Student(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("zhangSan",18);
Student student2 = new Student("liSi",17);
Student student3 = new Student("aBao",13);
Student[] students = new Student[3];
students[0] = student1;
students[1] = student2;
students[2] = student3;
bubbleSort(students);
System.out.println(Arrays.toString(students));
public static void bubbleSort(Student[] students) {
for (int i = 0; i < students.length-1; i++) {
for (int j = 0; j < students.length-1-i; j++) {
if(students[j].compareTo(students[j+1]) > 0) {
Student tmp = students[j];
students[j] = students[j+1];
students[j+1] = tmp;
}
}
}
}
}
运行结果:
[Student{name=‘aBao’, age=13}, Student{name=‘liSi’, age=17}, Student{name=‘zhangSan’, age=18}]
students[j].compareTo(students[j+1]
能够使用compareTo()比较的原因是,数组的每个元素都是Student类对象,而学生类实现了comparable接口,同时重写了compareTo方法
使用冒号排序BubbleSort()实现 Arrays.sort(),按照年龄从小到大进行比较排序,只需更改compareTo方法里面的比较方式。代码示例如下:
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
如果想要按照年龄从大到小排序,只需将
return this.age - o.age
改为return o.age - this.age
Comparator接口
Comparable接口的缺点:如果一个类已经实现了Comparable接口,并且根据需要已经重写了compareTo抽象方法,那么一般这个已经改写的抽象方法就不会再变了,比如说之前的业务都是使用年龄age来比较,但是如果确实需要通过姓名比较,也不能把原来已经重写好的方法再次改写按照姓名比较,这样一来以前使用年龄age比较的所有业务都会出错。那么我们可以使用一个新的接口Comparator接口,可以设置两种不同比较方式的类,让两个类都实现Comparator接口,分别按照比较需求重写Comparator中的抽象方法。
以下是comparator接口的源码,可以看到有两个抽象方法
这个接口里面有两个抽象方法,但是一个类实现了这个接口后只重写compare()方法就可以了,也不会报错。这是为啥子呢?解释:我们知道Object类是所有类的父类,其实在Object类中就有equals()方法的具体实现,而作为子类的AgeComparator也实现了Comparator接口,可以认为子类已经从父类继承了equals方法,近似认为已经重写了equals()方法,所以不需要再次重写equals()方法。
使用AgeComparator类中重写的compare()方法按照年龄比较,代码示例如下:
public class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("zhangSan",18);
Student student2 = new Student("liSi",17);
AgeComparator ageComparator = new AgeComparator();
int ret = ageComparator.compare(student1,student2);
if(ret > 0) {
System.out.println("student1 > student2");
}else if(ret == 0) {
System.out.println("student1 == student2");
}else {
System.out.println("student1 < student2");
}
}
}
运行结果:
student1 > student2
使用NameComparator类中重写的compare()方法按照姓名比较,代码示例如下:
public 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 student1 = new Student("zhangSan",18);
Student student2 = new Student("liSi",17);
NameComparator nameComparator = new NameComparator();
int ret = nameComparator.compare(student1,student2);
if(ret > 0) {
System.out.println("student1 > student2");
}else if(ret == 0) {
System.out.println("student1 == student2");
}else {
System.out.println("student1 < student2");
}
}
运行结果:
student1 > student2
Clonable接口
现通过一个实例总结使用clone()的注意事项;有一个Goods类,代码示例如下:
class Money {
public double money = 9.9;
}
public class Goods {
public String name;
public Money money;
public Goods(String name) {
this.name = name;
this.money = new Money();
}
}
现需要创建一个Goods对象,并克隆这个对象。在Test类中,创建好了一个goods对象,当用这个对象的引用通过 点 访问clone()方法时,但实际 点 不出来clone()方法,此时可以在子类Goods中重写clone()方法,这时候就能 点出来了。这是因为Object类中已经实现了clone()方法,所以作为子类的Goods类,重写这个方法后一定可以被访问。
但是我们发现还是会有红线,此时阅读Cloneable的源代码可以发现:
1 . 返回值是Object,所以要把Object类型的赋值给左边Goods类型的,必须要对右边进行强转为(Goods)。
public static void main(String[] args) {
Goods goods = new Goods("苹果");
Goods goods1 = (Goods)goods.clone();
}
- 强转之后发现还会有红线,这是因为
throw CloneNotSuppurtedException
是一个不支持克隆的异常,需要在编译时解决异常,解决异常的方法是:main()方法后 加throw CloneNotSuppurtedException
。
public static void main(String[] args) throws CloneNotSupportedException {
Goods goods = new Goods("苹果");
Goods goods1 = (Goods)goods.clone();
}
- 解决当前类是否能被克隆,编译通过但运行时报错,是因为没有实现Cloneable接口,实现Cloneable接口后才能真正实现拷贝,
阅读Cloneable接口的源码发现,其内部没有任何方法,
那么实现这个接口有什么用呢?它只是用来标记当前类是可以被拷贝的
完整代码示例如下:
class Money {
public double money = 9.9;
}
public class Goods implements Cloneable{
public String name;
public Money money;
public Goods(String name) {
this.name = name;
this.money = new Money();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Goods goods = new Goods("苹果");
Goods goods1 = (Goods)goods.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Goods goods = new Goods("苹果");
Goods goods1 = (Goods)goods.clone();
System.out.println("改变前goods money = " +goods.money.money );
System.out.println("改变前goods1 money = " +goods1.money.money );
goods1.money.money = 66.6;
System.out.println("改变后goods money = " +goods.money.money );
System.out.println("改变后goods1 money = " +goods1.money.money );
}
}
运行结果:
改变前goods money = 9.9
改变前goods1 money = 9.9
改变后goods money = 66.6
改变后goods1 money = 66.6
通过以上测试类的结果说明,Money这个类的对象并没有被真正拷贝,新拷贝的goods1中的money这个引用还是指向了原来的地址,这是浅拷贝
使用堆栈图更能清晰的表示:
此时的goods1 和 goods 中的money引用所指向的对象是同一个对象(引用所存放的地址相同),所以在通过goods.money.money = 66.6
这条语句改变money的值时,goods中的money也会改变,money引用指向的地址都是同一个地址
深拷贝的代码实现
以下是实现深拷贝的具体代码实现:
class Money implements Cloneable{
public double money = 9.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Goods implements Cloneable{
public String name;
public Money money;
public Goods(String name) {
this.name = name;
this.money = new Money();
}
@Override
protected Object clone() throws CloneNotSupportedException {
Goods tmp = (Goods)super.clone();
tmp.money = (Money)this.money.clone();
return tmp;
}
}
public class Test {
public static void main(String[] args)
throws CloneNotSupportedException {
Goods goods = new Goods("苹果");
Goods goods1 = (Goods)goods.clone();
System.out.println("改变前goods money = " +goods.money.money );
System.out.println("改变前goods1 money = " +goods1.money.money );
goods1.money.money = 66.6;
System.out.println("改变后goods money = " +goods.money.money );
System.out.println("改变后goods1 money = " +goods1.money.money );
}
}
运行结果:
改变前goods money = 9.9
改变前goods1 money = 9.9
改变后goods money = 9.9
改变后goods1 money = 66.6
操作的堆栈图:
先拷贝一份goods 给 tmp,然后再对money这个引用的对象进行拷贝,把拷贝好的money对象的地址 给 tmp.money。这里money引用也进行拷贝,所以Money也要实现Cloneable接口,重写clone()方法。
注意:从代码层次上,浅拷贝或深拷贝 是由具体的代码实现来决定的,不能说某个clone()方法是浅拷贝或深拷贝
抽象类和接口的区别
- 抽象类中可以有普通成员变量和普通成员方法,构造方法,也可以有抽象方法;接口中可以定义变量,不能定义构造方法 和 有具体实现的方法(如果想有具体实现,使用static或default修饰),接口中的方法默认是被public abstract 修饰(抽象方法),变量默认是被 public static final 修饰(静态常量)
- 抽象类使用 abstract 关键字;接口使用 interface 关键字
- 抽象类 用来被继承,,关键字 extends;接口用来被实现,关键字 implements
- 类与类之间不支持多继承;接口可以实现多继承,一个类可以实现多个接口,接口与接口之间也可以继承,甚至继承多个接口