一、抽象类
1、抽象类和抽象方法
如下代码,父类Shape方法中的draw方法没有什么实际工作,实际工作都是由子类的draw方法完成的;
因此我们把没有实际工作的方法成为抽象方法(abstract method)包含它的类必须改为抽象类(abstract class);
抽象类和抽象方法都用abstract修饰符来表示。
//打印多种形状代码片段(全段见多态文章)
class Shape {
public void draw() {
}
}
2、语法规则
//以上代码改为抽象类如下:
abstract class Shape {
//抽象方法没有方法体{},不能执行具体代码
abstract public void draw();
}
3、抽象类的注意事项
1.抽象类不能被实例化
2.抽象方法是不能private的(要被重写,别的类要用)
3. 抽象方法也不能被final修饰(final代表不能重写,abstract代表要被重写,相互矛盾)
4. 一个普通类如果继承了抽象类,则要重写抽象类中的抽象方法
5. 一个抽象类A继承了抽象类B之后,可以不重写抽象类B中的方法;
但一旦抽象类A 被继承之后,继承的那个类要重写抽象类A和B中全部的抽象方法
6. 抽象类可以包含和普通类一样的成员,也可以包含其他非抽象方法(和普通方法规则一致),可以被重写,也可以被子类直接调用
4、抽象类的作用
1. 抽象类存在的最大意义就是——被继承
2. 使用抽象类相当于多了一重编译器校验(可以提示我们不要忘记重写)
二、接口
接口的出现是为了满足Java中多继承的要求(idea使用ctrl+i快速实现接口)
1、接口定义
接口是抽象类的更进一步,接口中的方法只能包含抽象方法,字段只能包含静态常量
2、接口包含非抽象方法的情况
1.表示该接口的默认方法(用的少),即使该接口被实现,该方法也不能被重写
//1.表示该接口的默认方法
default public void func() {
System.out.println("这是一个默认方法");
}
2.接口中可以有静态方法(接口不能被实例化,静态方法不依赖对象,因此通过接口名字可以直接调用静态方法)
3、语法规则
//以上代码改为接口如下:
interface IShape {
//接口中方法全部默认为public abstract(可省略)
void draw();
//接口只能包含静态常量,因此字段中的public static final关键字都可以省略(省略后意思不变)
public static final int num = 10;
}
//接口的实现用关键词implements
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("⚪")
}
}
public class Test {
public static void main(String[] args) {
//向上转型(直接赋值)
IShape shape = new Rect();
shape.draw();
}
}
4、注意事项
1.使用interface定义接口
2.接口中的方法一定是抽象方法,上述两种情况除外,因此可以省略abstract关键字
3.接口中的方法一定是public,因此可以省略public
4.接口不能单独被实例化,接口可以发生向上转型(实例化的类一定实现了该接口)
5.接口用implements关键字继承接口,此时含义不再是“扩展”,而是“实现”
6.一个类可以实现多个接口
7.对于字段来说,接口中只能包含静态常量
8.接口命名一般以"I"开头
5、抽象类实现接口
interface IShape {
void draw();
}
abstract class B implements IShape {
//public不能省略
public void draw();
}
public不能省略原因:根据方法重写规则,子类重写父类方法访问权限要大于父类;接口默认为public,抽象类B不会默认,因此不能省略public。
若普通类继续继承抽象类B,且B中有自己的抽象方法,那么该类必须既重写B的方法,还要重写IShape接口的方法。
6、实现多个接口
有时候我们需要一个类同时继承多个父类,但 java只支持单继承,一个类只能extends一个父类,但是可以实现多个接口,达到多继承的效果。
//通过类表示一组动物
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
//提供接口,表示会飞的
interface IFlying {
void fly();
}
//提供接口,表示会跑的
interface IRunning {
void run();
}
//提供接口,表示会游泳的
interface ISwimming {
void swim();
}
//创建具体动物:猫,是会跑的
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println("猫在跑步");
}
}
//创建具体动物:青蛙,是会跑的、会游泳的
class Frog extends Animal implements IRunning,ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println("青蛙正在跑");
}
@Override
public void swim() {
System.out.println("青蛙正在游泳");
}
}
//参数不是动物,只要会跑即可,接口不关注类型,只关注具备什么功能
class Robot implements IRunning {
@Override
public void run() {
System.out.println("机器人正在跑步");
}
}
public class Test3 {
//向上转型
public static void func(IRunning iRunning) {
iRunning.run();
}
public static void main(String[] args) {
func(new Robot());
}
public static void main3(String[] args) {
func(new Cat("猫"));
}
public static void main2(String[] args) {
IRunning iRunning = new Cat("猫");
ISwimming iSwimming = new Frog("青蛙");
}
public static void main1(String[] args) {
Animal animal = new Cat("猫");
}
}
上面代码展示了java面向对象编程最常见的用法:一个类继承一个父类,同时实现多个接口。
优点:程序猿可以忘记类型,有了接口之后使用者不必关注具体类型,只关注某个类是否具备某种能力(如上代码Robot类具备跑步功能,不必关注它是不是Animal类)
7、接口和继承的区别
1.继承表达的含义是is-a的语义;接口表达的含义是:具有xxx特性。
2.继承父类的关键字是extends;实现接口的关键字是implements。
3.一个类只能继承一个父类;一个类可以实现多个接口
4.一个类继承一个父类的同时可以实现多个接口
8、常见接口
(1)Comparable接口
Ⅰ、尝试用数组排序方法(sort)给含多个元素学生数组排序
//1.给定一个学生类
class Student {
//字段尽量写成private的,再提供get和set方法(此处方便观看写为public的)
public int age;
public String name;
public double score;
//提供构造方法
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
//2.给定一个学生数组,对数组中元素进行排序
public static void main2(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(13,"张三",58.9);
students[1] = new Student(14,"李四",78.2);
students[2] = new Student(11,"王五",68.5);
//按照之前的理解排序发现运行报错如下图
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
因此,两个学生对象的大小关系的确定需要我们额外指定。就用到了Comparable接口,并实现其中的compareTo方法,如下所示。
Ⅱ、给学生数组排序方法一:Comparable接口
该方法针对不经常改变排序方式的类
让学生类继承Comparable接口,具体实现如下代码:
class Student implements Comparable<Student> {
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
//按年龄排序
public int compareTo(Student o) {
//谁调用该方法谁就是this
/*if(this.age > o.age) {
return 1;
}else if (this.age == o.age) {
return 0;
}else {
return -1;
}*/
//以上代码可以优化为如下:(默认升序排序)
return this.age - o.age;
}
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(13,"张三",58.9);
students[1] = new Student(14,"李四",78.2);
students[2] = new Student(11,"王五",68.5);
//sort方法中会自动调用comparaTo方法
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
执行结果如下:
以上代码是按照年龄排序,若要按照姓名排序,方法应该改写为如下:
@Override
//按姓名排序(默认为首字母)
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
根据如上更换排序方式的缺点:对类的侵入性太强(若这次要根据成绩排序,下次要根据名字排序,需要不停的更改类的内部)
因此我们就可以使用另外一个接口以供需要经常改变排序的类的使用,即:Comparator接口,如下所示。
(2)Comparable接口
Ⅲ、给学生数组排序方法二:Comparator接口(比较器)
class Student {
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
//对年龄排序
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 (int)(o1.score - o2.score);
}
}
//对名字排序
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public static void main(String[] args) {
Student[] student = new Student[3];
student[0] = new Student(13,"张三",58.9);
student[1] = new Student(14,"李四",78.2);
student[2] = new Student(11,"王五",68.5);
AgeComparator ageComparator = new AgeComparator();
System.out.println("====按年龄排序====");
Arrays.sort(student,ageComparator);
System.out.println(Arrays.toString(student));
System.out.println("=====按姓名排序====");
NameComparator nameComparator = new NameComparator();
//排序给sort方法传参即可
Arrays.sort(student,nameComparator);
System.out.println(Arrays.toString(student));
}
执行结果如下:
根据如上代码,我们发现实现Comparator接口,无需Student类进行继承,只需要重新定义类,根据想要的不同,调用不同的类即可,对类的侵入性很小。
(3)Clonable接口和深浅拷贝(空接口/标志接口)
如果一个类实现了该接口,说明该类可以被克隆
public class Test {
static class A implements Cloneable {
public int num = 0;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class B implements Cloneable {
public A a = new A();
@Override
//“受查异常”——所有该异常编译都无法通过
protected Object clone() throws CloneNotSupportedException {
//浅拷贝
return (B)super.clone();
/*
//实现深拷贝
B b = (B)super.clone();
b.a = (A)this.a.clone();
return b;*/
}
}
//重写的clone方法会抛出异常,这里要进行处理异常
public static void main(String[] args) throws CloneNotSupportedException{
B b = new B();
B b2 = (B)b.clone();//受查异常
b.a.num = 10;
//设置b1.a.num的值,b2.a.num的值不变,则说明达到深拷贝
System.out.println(b2.a.num);
}
}
9、接口间的继承(称为扩展)
接口可以继承一个接口,达到复用的效果,使用extends关键字。
接口之间的继承相当于把多个接口合并在一起。
interface IA {
void funcA();
}
interface IB extends IA {
void funcB();
}
interface IC extends IB {
void funcC();
}
//以上接口实现了扩展,此时实现接口创建的类就要继续实现三个接口中的方法
class IClass implements IC {
class Iclass implements TT3 {
@Override
public void funcA() {
System.out.println("重写接口IA中的方法");
}
@Override
public void funcB() {
System.out.println("重写接口IB中的方法");
}
@Override
public void funcC() {
System.out.println("重写接口IC中的方法");
}
}
10、抽象类和接口的区别(重要!!)
核心区别: 抽象类中可以包含普通方法和普通字段, 这些普通方法和字段子类能直接使用(不必重写);
而接口中不能包含普通方法, 子类必须重写所有的抽象方法.