初识java(十三):抽象类和接⼝(一)
目录
内容大纲:
这篇博客聚焦 Java 的三大抽象机制:
✅ 抽象类——提供模板结构,可含抽象/具体方法,子类必须实现抽象方法;
✅ 接口——定义行为契约(JDK 8+ 支持default方法),支持多实现,解耦能力强;
一:抽象类
1.抽象类概念
在⾯向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是⽤来描绘 对象的,如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。⽐如:


在打印图形例⼦中,我们发现,⽗类Shape中的draw⽅法好像并没有什么实际⼯作,主要的绘制图形都 是由Shape的各种⼦类的draw⽅法来完成的.像这种没有实际⼯作的⽅法,我们可以把它设计成⼀个 抽象⽅法(abstractmethod),包含抽象⽅法的类我们称为抽象类(abstractclass)
2.抽象类语法
在Java中,⼀个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的⽅法称为抽象 ⽅法,抽象⽅法不⽤给出具体的实现体。
//抽象类,被abstract修饰的类
abstract class Shape{
//抽象方法,被abstract修饰的方法,没有方法体
//抽象成员方法
abstract public void draw();
abstract void calcArea();
//抽象类也是类,是类就可以包括成员方法,成员变量,还有构造方法
//成员方法
public double getArea(){
return area;
}
//成员变量
protected double area;
}
注意:抽象类也是类,内部可以包含普通⽅法和属性,甚⾄构造⽅法
3.抽象类特性
1.抽象类不能直接实例化对象
public class Test {
Shape shape = new Shape();
}
原因:
2.抽象⽅法不能是private的
abstract public class Shape {
//成员方法
abstract private void drawn();
}
原因:
3.抽象⽅法不能被final和static修饰,因为抽象⽅法要被⼦类重写
abstract public class Shape {
abstract final void dff();
abstract public static void ddd();
}
原因:
4. 抽象类必须被继承,并且继承后⼦类要重写⽗类中的抽象⽅法,否则⼦类也是抽象类,必须要使⽤ abstract 修饰
Shape(父类)
abstract public class Shape {
//抽象方法
abstract public void draw();
abstract void calcArea();
//成员变量
protected double area;
}
Rect
public class Rect extends Shape{
//成员方法
private double length;
private double width;
//构造方法
public Rect(double length, double width) {
this.length = length;
this.width = width;
}
//成员方法
public void draw(){
System.out.println(" 矩形: length= "+length+" width= " + width);
}
public void calcArea(){
area = length * width;
}
}
Circle
public class Circle extends Shape{
//成员变量
private double r;
final private static double PI = 3.14;
//构造方法
public Circle(double r) {
this.r = r;
}
//成员方法
public void draw(){
System.out.println(" 圆: r = "+r);
}
public void calcArea(){
area = PI * r * r;
}
}
Triangle
abstract public class Triangle extends Shape{
private double a;
private double b;
private double c;
//重写父类的抽象方法
@Override
public void draw() {
System.out.println(" 三⻆形: a = "+a + " b = "+b+" c = "+c);
}
}
此时我们的Triangle是被abstract修饰的,重写父类的抽象方法就可以从子类调用父类里面的抽象方法,这是没有任何问题的
如果Triangle不是被abstract修饰的,则会报错
public class Triangle extends Shape{
private double a;
private double b;
private double c;
}
原因:
解决方法:
第一个就是加上我们的abstract修饰子类,这样就可以继承抽象的父类
第二个就是实现父类的抽象方法,注意父类的抽象方法都要实现
public class Triangle extends Shape{
private double a;
private double b;
private double c;
@Override
public void draw() {
}
@Override
void calcArea() {
}
}
此时我们的代码就是没有任何问题的
5. 抽象类中不⼀定包含抽象⽅法,但是有抽象⽅法的类⼀定是抽象类
6. 抽象类中可以有构造⽅法,供⼦类创建对象时,初始化⽗类的成员变量
4.抽象类的作⽤
抽象类本⾝不能被实例化,要想使⽤,只能创建该抽象类的⼦类.然后让⼦类重写抽象类中的抽象⽅法
有些同学可能会说了,普通的类也可以被继承呀,普通的⽅法也可以被重写呀,为啥⾮得⽤抽象类和抽 象⽅法呢
确实如此.但是使⽤抽象类相当于多了⼀重编译器的校验.
使⽤抽象类的场景就如上⾯的代码,实际⼯作不应该由⽗类完成,⽽应由⼦类完成.那么此时如果不⼩⼼ 误⽤成⽗类了,使⽤普通类编译器是不会报错的.但是⽗类是抽象类就会在实例化的时候提⽰错误,让我 们尽早发现问题
很多语法存在的意义都是为了"预防出错",例如我们曾经⽤过的final也是类似.创建的变量⽤⼾不去 修改,不就相当于常量嘛?但是加上final能够在不⼩⼼误修改的时候,让编译器及时提醒我们
充分利⽤编译器的校验,在实际开发中是⾮常有意义的.
二:接⼝
1.接⼝的概念
在现实⽣活中,接⼝的例⼦⽐⽐皆是,⽐如:笔记本上的USB⼝,电源插座等

电脑的USB⼝上,可以插:U盘、⿏标、键盘...所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲...所有符合规范的设备
通过上述例⼦可以看出:接⼝就是公共的⾏为规范标准,⼤家在实现时,只要符合规范标准,就可以 通⽤。
在Java中,接⼝可以看成是:多个类的公共规范,是⼀种引⽤数据类型。
2.语法规则
接⼝的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了⼀个接 ⼝。
public interface 接⼝名称{
// 抽象⽅法
public abstract void method1(); // public abstract 是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
// 注意:在接⼝中上述写法都是抽象⽅法,推荐⽅式 4 ,代码更简洁
}
提⽰:
1. 创建接⼝时,接⼝的命名⼀般以⼤写字⺟ I 开头.
2. 接⼝的命名⼀般使⽤"形容词"词性的单词.
3. 阿⾥编码规范中约定,接⼝中的⽅法和属性不要加任何修饰符号,保持代码的简洁性.
3.接⼝使⽤
接⼝不能直接使⽤,必须要有⼀个"实现类"来"实现"该接⼝,实现接⼝中的所有抽象⽅法
public class 类名称 implements 接⼝名称 {
// ...
}
注意:⼦类和⽗类之间是extends继承关系,类与接⼝之间是implements实现关系
实现笔记本电脑使⽤ USB ⿏标、 USB 键盘的例⼦
1. USB 接⼝:包含打开设备、关闭设备功能
2. 笔记本类:包含开机功能、关机功能、使⽤ USB 设备功能
3. ⿏标类:实现 USB 接⼝,并具备点击功能
4. 键盘类:实现 USB 接⼝,并具备输⼊功能
代码实现:
USB(接口)
//实现一个接口
public interface USB {
//抽象方法
void openDevice();
void closeDevice();
}
Mouse(继承接口)
public class Mouse implements USB{
//接口的实现
@Override
public void openDevice() {
}
@Override
public void closeDevice() {
}
//成员方法
public void click(){
System.out.println("⿏标点击 ");
}
}
KeyBoard (继承接口)
public class KeyBoard implements USB{
@Override
public void openDevice() {
}
@Override
public void closeDevice() {
}
//成员方法
public void inPut(){
System.out.println(" 键盘输⼊ ");
}
}
Computer
public class Computer {
//成员方法
public void powerOn(){
System.out.println(" 打开笔记本电脑 ");
}
public void powerOff(){
System.out.println(" 关闭笔记本电脑 ");
}
//带有参数的成员方法
public void useDevice(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();
}
}
Test
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
//打开电脑
computer.powerOn();
//使⽤⿏标设备
computer.useDevice(new Mouse());
//使⽤键盘设备
computer.useDevice(new KeyBoard());
//关闭电脑
computer.powerOff();
}
}
4.接⼝特性
1.接⼝类型是⼀种引⽤类型,但是不能直接new接⼝的对象
public class Test {
public static void main(String[] args) {
USB usb = new USB();
}
}
原因:

2.接⼝中每⼀个⽅法都是public的抽象⽅法,即接⼝中的⽅法会被隐式的指定为public abstract(只能是public abstract,其他修饰符都会报错)
//实现一个接口
public interface USB {
private void openDevice();
}
原因:
3.接⼝中的⽅法是不能在接⼝中实现的,只能由实现接⼝的类来实现
//实现一个接口
public interface USB {
void closeDevice(){
System.out.println("11");
}
}
原因:
4.重写接⼝中⽅法时,不能使⽤默认的访问权限
public class Mouse implements USB{
//接口的实现
@Override
void openDevice() {
}
}
原因:

5.接⼝中可以含有变量,但是接⼝中的变量会被隐式的指定为public static final变量
public class Test {
public static void main(String[] args) {
System.out.println(USB.brand);
USB.brand = 3.1;
}
}
可以输出3.0可以说明接口是被Static修饰的

想要修改接口里面的变量
原因:
6.接⼝中不能有静态代码块和构造⽅法
//实现一个接口
public interface USB {
//接口不能有构造方法
public USB(){
}
//接口不能有静态代码块
{
}
}
原因:
7.接⼝虽然不是类,但是接⼝编译完成后字节码⽂件的后缀格式也是.class
8.如果类没有实现接⼝中的所有的抽象⽅法,则类必须设置为抽象类
9.jdk8中:接⼝中还可以包含default⽅法。
5.实现多个接⼝
在Java中,类和类之间是单继承的,⼀个类只能有⼀个⽗类,即Java中不⽀持多继承,但是⼀个类可 以实现多个接⼝。下⾯通过类来表⽰⼀组动物.
Aninmal(父类)
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
另外我们再提供⼀组接⼝,分别表⽰"会⻜的","会跑的","会游泳的".
IFlying(接口)
public interface IFlying {
void fly();
}
IRunning(接口)
public interface IRunning {
void run();
}
ISwimming (接口)
public interface ISwimming {
void swim();
}
接下来我们创建⼏个具体的动物
猫,是会跑的
public class Cat extends Animal implements IRunning{
//实现接口方法
@Override
public void run() {
System.out.println(this.name + " 正在⽤四条腿跑 ");
}
//构造父类方法
public Cat(String name) {
super(name);
}
}
⻥,是会游的.
public class Fish extends Animal implements ISwimming{
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + " 正在⽤尾巴游泳 ");
}
}
⻘蛙,既能跑,⼜能游(两栖动物)
public class Frog extends Animal implements ISwimming,IRunning{
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + " 正在往前跳 ");
}
@Override
public void swim() {
System.out.println(this.name + " 正在蹬腿游泳 ");
}
}
Test
public class Test {
public static void main(String[] args) {
Cat cat= new Cat("小三");
cat.run();
Fish fish=new Fish(" 小鱼");
fish.swim();
Frog frog = new Frog("小蛙");
frog.run();
frog.swim();
}
}
输出:
注意:⼀个类实现多个接⼝时,每个接⼝中的抽象⽅法都要实现,否则类必须设置为抽象类。
提⽰,IDEA中使⽤ctrl+i快速实现接⼝
上⾯的代码展⽰了Java⾯向对象编程中最常⻅的⽤法:⼀个类继承⼀个⽗类,同时实现多种接⼝.
继承表达的含义是 is - a 语义,⽽接⼝表达的含义是 具有 xxx 特性
猫是⼀种动物,具有会跑的特性.
⻘蛙也是⼀种动物,既能跑,也能游泳
这样设计有什么好处呢?时刻牢记多态的好处,让程序猿忘记类型.有了接⼝之后,类的使⽤者就不必关 注具体类型,⽽只关注某个类是否具备某种能⼒
6.接⼝间的继承
在Java中,类和类之间是单继承的,⼀个类可以实现多个接⼝,接⼝与接⼝之间可以多继承。即:⽤接⼝可以达到多继承的⽬的
接⼝可以继承⼀个接⼝,达到复⽤的效果. 使⽤extends关键字
public interface IAmphibious extends IRunning,ISwimming{
}
接⼝间的继承相当于把多个接⼝合并在⼀起
7.接⼝使⽤实例
对象之间进⾏⼤⼩关系⽐较
Student
public class Student {
public String name;
public int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
Test
public class Test {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",10);
Student s2 = new Student("lisi",20);
System.out.println(s1 > s2 );
}
}
输出:
此时程序会编译报错,并没有指定根据分数还是什么进⾏⼤⼩⽐较。所以,应该指定以什么样的⽅式 进⾏⽐较??
不太好的点是,只要按照该⽅式⽐较后,不灵活。⽆法按照其他⽅式进⾏⽐较
⽅式⼀:使⽤Comparable接⼝
让我们的Student类实现Comparable接⼝,并实现其中的compareTo⽅法
Student
public class Student implements Comparable{
public String name;
public 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;
}
}
}
Test
public class Test {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",10);
Student s2 = new Student("lisi",20);
System.out.println(s1.compareTo(s2));
}
}
输出:
如果s1⼤于s2那么返回⼤于0的数字,如果相同返回0,否则返回⼩于0的数字。
⽅式⼆:使⽤Comparator接⼝
ScoreComparator
import java.util.Comparator;
public class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.score-o2.score;
}
}
NameComparator
import java.util.Comparator;
public class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
Test1
public class Test1 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",10);
Student s2 = new Student("lisi",20);
//分数比较
ScoreComparator scoreComparator = new ScoreComparator();
System.out.println(scoreComparator.compare(s1, s2));
//名字比较
NameComparator nameComparator = new NameComparator();
System.out.println(nameComparator.compare(s1, s2));
}
}
8.Clonable接⼝和深拷⻉
Java中内置了⼀些很有⽤的接⼝,Clonable就是其中之⼀
Object类中存在⼀个clone⽅法,调⽤这个⽅法可以创建⼀个对象的"拷⻉".但是要想合法调⽤clone ⽅法,必须要先实现Clonable接⼝,否则就会抛出CloneNotSupportedException异常
Animal
public 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;
}
}
Test1
public class Test1 {
public static void main(String[] args) {
Animal animal = new Animal();
Animal animal2 = animal.clone();
System.out.println(animal == animal2);
}
}
输出:
浅拷⻉VS深拷⻉
Cloneable 拷⻉出的对象是⼀份"浅拷⻉
观察以下代码:
Person
public class Person implements Cloneable{
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Money
public class Money {
public double m = 99.99;
}
Test
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println(" 通过 person2 修改前的结果 ");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
person2.money.m = 13.6;
System.out.println(" 通过 person2 修改后的结果 ");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
}
}
输出:
如上代码,我们可以看到,通过clone,我们只是拷⻉了Person对象。但是Person对象中的Money对 象,并没有拷⻉。通过person2这个引⽤修改了m的值后,person1这个引⽤访问m的时候,值也发⽣了改变。这⾥就是发⽣了浅拷⻉。那么同学们想⼀下如何实现深拷⻉呢?
9.抽象类和接⼝的区别
抽象类和接⼝都是Java中多态的常⻅使⽤⽅式.都需要重点掌握.同时⼜要认清两者的区别(重要!!!常 ⻅⾯试题).
核⼼区别:抽象类中可以包含普通⽅法和普通字段,这样的普通⽅法和字段可以被⼦类直接使⽤(不必重写), ⽽接⼝中不能包含普通⽅法,⼦类必须重写所有的抽象⽅法.
如之前写的Animal例⼦.此处的Animal中包含⼀个name这样的属性,这个属性在任何⼦类中都是存 在的.因此此处的Animal只能作为⼀个抽象类,⽽不应该成为⼀个接⼝.
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
再次提醒:
抽象类存在的意义是为了让编译器更好的校验,像Animal这样的类我们并不会直接使⽤,⽽是使⽤它 的⼦类.万⼀不⼩⼼创建了Animal的实例,编译器会及时提醒我们

后面还有部分内容,我们放到下一章!!!!
更多推荐



所有评论(0)