在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 比如:
这里的车就可以看作一个抽象类,他没有足够的信息来描绘一个具体的对象
公交车 大卡车 摩托车 小轿车都是车 所以他们之间是继承关系
像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
public abstract class Shape {
public abstract void draw();
//抽象方法不能写具体的实现
public static void area(){
System.out.println("面积");
}
//抽象类也是类 内部可以定义普通方法和属性甚至是构造方法
}
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
但是有一个疑问 普通的类也可以被继承 普通的方法也可以被重写 为什么必须使用抽象类和抽想法呢???
确实如此. 但是使用抽象类相当于多了一重编译器的校验
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。
电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备
通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口
public interface 接口名称{
// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
public class 类名称 implements 接口名称{
// ...
}
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
举个例子
public interface USB {
void openDevice();
void closeDevice();
}//接口
public class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void click(){
System.out.println("鼠标点击");
}
}//鼠标类
public class keyBoard implements USB{
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void inPut(){
System.out.println("键盘输入");
}
}//键盘类
public class Computer {
public void powerOn() {
System.out.println("打开电脑");
}
public void powerOff() {
System.out.println("关闭电脑");
}
public void usbDevice(USB 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 TestComputer {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
System.out.println("==========================");
//使用鼠标设备
computer.usbDevice(new Mouse());
System.out.println("==========================");
//使用键盘设备
computer.usbDevice(new keyBoard());
System.out.println("==========================");
computer.powerOff();
}
}//测试类
接口其实就是对一个标准的规范 可以看作是抽象类的进一步抽象
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物.
public abstract class Animals {
protected String name;
public Animals(String name) {
this.name = name;
}
}//父类
public interface IFlying {
void fly();
}
public interface ISwimming {
void swim();
}
public interface IRunning {
void run();
}//接口们
public class Cat extends Animals implements IRunning{//猫是一个动物且会跑
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println("猫正在用四条腿跑步");
}
}
public class Fish extends Animals implements ISwimming{//鱼是一个动物且会游泳
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println("鱼正在游泳");
}
}
public class Dog extends Animals implements ISwimming,IRunning{//狗是一个动物且会游泳和跑步
@Override
public void swim() {
System.out.println("狗正在狗刨式游泳");
}
@Override
public void run() {
System.out.println("狗正在用四条腿跑");
}
public Dog(String name) {
super(name);
}
}
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字
public interface IAmphibious extends IRunning,ISwimming {
//两栖类接口继承跑和游泳的接口
}
//此时狗这个方法就可以不用实现游泳和跑的接口 直接实现两栖类的接口即可
public class Dog extends Animals implements IAmphibious{
@Override
public void swim() {
System.out.println("狗正在狗刨式游泳");
}
@Override
public void run() {
System.out.println("狗正在用四条腿跑");
}
public Dog(String name) {
super(name);
}
}
public 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 "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhansgan",85);
students[1] = new Student("lisi",70);
students[2] = new Student("wangwu",90);
Arrays.sort(students);//这里能够给数组排序吗?
}
}
我们可以想到这样排序是不现实的 因为我们并没有告诉编译器我们应该是按照名字排序还是按照成绩来排序 所以我们预测会抛出异常
运行结果
解决办法就是让Student类实现 Comparable接口 并实现其中的compareTo方法
public class Student implements Comparable{//实现Comparable接口
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Object o) {
Student s = (Student)o;
// if (this.score > s.score) {
// return -1;
// } else if (this.score < s.score) {
// return 1;
// } else {
// return 0;
// }
//可以使用注释掉的if else方法也可以使用下面的return返回的方法
return s.score - this.score;
}
}
在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.然后比较当前对象和参数对象的大小关系(按分数来算).
为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序)
public class Test {
public static void sort(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[j]调用的compareTo方法所以this.就是student[j];
//参数是student[j + 1] 所以Object o接收的就是student[j + 1]
Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhansgan", 85);
students[1] = new Student("lisi", 70);
students[2] = new Student("wangwu", 90);
sort(students);//调用自己实现的冒泡排序
System.out.println(Arrays.toString(students));
}
}
Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要
先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
public 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 "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class test {
public static void main(String[] args) {
Student student1 = new Student("zhangsan",85);
Student student2 = student1.clone();//这里会报错
}
}
我们写出上述代码发现会报错
编译器提示clone方法是被protected修饰的
我们可以跳转到clone方法的实现
protected修饰的在不同包中的子类去访问通常要使用super
我们在这里可以重写这个方法
编译器默认生成了这段代码 他其实什么都没有干 只是调用了父类的clone方法
此时就是异常的问题了
我们只需要把异常写到main方法后面就可以解决
此时报错信息变为类型的问题 编译器提示我们这里需要一个Student类型但是我们提供了一个Object的类型 我们只需要强制类型转换
我们发现这里没有报错信息表示可以运行
当我们运行时发现又报错了 提示我们克隆不支持异常
我们只需要实现Cloneable接口 这个接口又是什么呢?
我们可以看到这个接口什么也没有 这叫做标记接口
当我们实现了这个接口后 发现没有报错了
public class Student implements Cloneable{
private String name ;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class test {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student("zhangsan", 85);
Student student2 = (Student) student1.clone();
System.out.println(student1.toString());
System.out.println(student2.toString());
}
}
我们在原有代码的基础上新增一个Money类 并在Student中实例化
我们想让student2在克隆student1时将Money类实例化的也克隆进去
我们首先想到刚才的操作 第一步实现Cloneable接口 第二步重写clone方法
class Money implements Cloneable{
public int money;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Student implements Cloneable {
private String name;
private int score;
public Money m = new Money();
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
", m=" + m +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class testStudent {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student("zhangsan",85);
Student student2 = (Student) student1.clone();
System.out.println(student1.m.money);
System.out.println(student2.m.money);
student1.m.money = 55;
System.out.println(student1.m.money);
System.out.println(student2.m.money);
}
}
运行结果
我们发现在修改student1时 student2也发生了改变 这明显是不符合逻辑的
**重新思考我们的代码 我们只是给Money做了克隆的前置任务 实际上并没有克隆 **
如图所示 我们只是复制了Student 中的所有 包括我们实例化Money的地址 意味着即使我们复制后student2也是保存的同一个m的地址 所以我们改变student1的m 通过student2调用m调用的是同一个m 就出现了运行时的结果 这也就是浅拷贝
通过对浅拷贝的认识 深拷贝的理解也就很简单
还是上面这个例子 我们不想要student1和student2都调用同一个m 该如何实现
我们只需要在上面那个代码重写clone方法做一点改动
改动后
抽象类表示是什么 例如猫属于动物 大卡车属于车
接口表示什么会干什么 例如猫会跑 狗既会跑也会游泳
这也可以体现出为什么只能单继承 但是却可以实现多个接口 因为一个类一定不是只会干一种事情 抽象类的意义就是被继承