抽象类与接口:特征与比较
在Java面向对象编程中,抽象类和接口是两个基础且强大的概念。它们为代码设计提供了结构化和多态性的基础,但各自有不同的特点和使用场景。本文将详细介绍这两个概念的特征,并对它们进行比较分析。
抽象类的特征
抽象类是一种特殊的类,它不能被直接实例化,需要通过子类继承并实现其抽象方法才能使用。
1. 基本特征
使用abstract关键字声明
可以包含抽象方法(没有方法体的方法)
可以拥有普通方法、属性和构造方法
子类通过extends关键字继承
2. 重要特性
抽象类不能直接实例化对象
抽象方法不能是private的(因为需要被子类重写)
抽象方法不能被final和static修饰
如果类中有抽象方法,则该类必须声明为抽象类
子类必须实现父类中的所有抽象方法,否则子类也必须是抽象类
抽象类的注意的地方:
1.抽象类和抽象方法都使用abstract进行修饰
2.抽象类不能进行实例化,但是普通类是可以的!
3.抽象类不一定包含抽象方法,但是包含抽象方法的一定是抽象类。
4.抽象类当中 可以定义成员变量和成员方法!
5.当一个普通类 继承我们的抽象类了,此时在普通类当中一定要重写抽象类中的抽象方法!!!
6.抽象类存在的最大意义就是 为了被继承
7.当一个抽象类B继承了抽象类A,此时抽象类B不需要重写抽象类A的抽象方法,但是当一个普通类C,继承了抽象类B,此时就需要重写所有没有被重写的抽象方法(包含A,B抽象类的)
8.一定满足重写的要求
9.final关键字 不可能同时作用在一个方法或者类上(final和abstract天生冲突,因为abstract还需要重写,final不可以变动了)!
10.抽象类当中可以存在构造方法,在子类实例化的时候,会帮助父亲的成员进行初始化!
代码案例:
如果使用普通的继承画图像类
package demo1;
class Shape{
public void draw(){
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("⚪");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("矩形");
}
}
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
drawMap(new Cycle());
drawMap(new Rect());
}
}
如果使用抽象类继承,代码
package demo2;
/**
* Created with IntelliJ IDEA.
* Description:
* User: laichangyang
* Date: 2025-04-02
* Time: 14:55
*/
//抽象类
abstract class Shape{
public abstract void draw();
}
class Cyele extends Shape{
@Override
public void draw() {
System.out.println("⚪");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("矩形");
}
}
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
drawMap(new Cyele());
drawMap(new Rect());
}
}
好像和不使用抽象类实现的效果,为什么还要脱裤子放屁呢???
抽象类可以帮助我们检查是否重写了抽象方法,没有重写,就会报错,这个是它的重要作用。
还有就是,抽象类不可以实例化,但是如果不使用抽象类,父类(基类)就可以实例化了。
接口的特征
接口是一种完全抽象的类型,它只定义行为规范,不提供实现。
1. 基本特征
使用interface关键字声明
包含抽象方法(Java 8之前)
方法默认是public abstract
字段默认是public static final
类通过implements关键字实现接口
2. 重要特性
接口不能直接实例化
接口中的方法都是抽象的(Java 8前)
接口没有构造方法
接口不能包含普通变量,只能有常量
一个类可以实现多个接口
接口之间可以多继承(使用extends关键字)
接口:其实就是对一个标准的规范!!!可以算抽象类的进一步抽象
接口注意的地方:
1.接口是使用interface来进行定义的
2.接口当中 不能有实现的方法,但是有2种类型的方法需要注意:
2.1静态方法可以有具体的实现
2.2这个方法被default关键字修饰,也是可以的【1.8开始引入这个特征】
3.接口当中的方法默认是public abstract 修饰的
4.接口当中的成员变量默认是:public static final 修饰的
5.接口也不能通过关键字new 来进行实例化
6.类和接口之间 使用关键字implements来进行关联
7.当一个类实现一个接口后,这个类必须重写这个接口当中的抽象方法
8.当接口存在default方法,可以选择重写,也可以不重写,具体看业务需求
9.不管是接口还是抽象类,他们仍然是可以发生向上转型的
10.子类实现接口的方法的时候,这个方法一定是public修饰的!!!
11.接口当中不能有构造方法和代码块
12.一个类 不能实现接口当中的方法 这个类,可以定义为抽象类
一个类可以实现多个接口,可以解决多继承的问题!!!
如果继承和接口同时存在,先继承类,然后实现接口
如果使用接口实现前面的画图
package demo3;
interface Ishpe{
// int age=1;//默认是 public static final
void draw();//默认是public abstract
// default public void draw2(){
// System.out.println("1234");
// }
//
// public static void draw3(){
// System.out.println("1234");
// }
}
class Cycle implements Ishpe{
@Override
public void draw() {
System.out.println("⚪");
}
// @Override
// public void draw2() {
// System.out.println("实现接口,default修饰方法的重写");
// }
}
class Rect implements Ishpe{
@Override
public void draw() {
System.out.println("矩形");
}
}
public class Test {
public static void drawMap(Ishpe shape){
shape.draw();
}
public static void main(String[] args) {
Ishpe ishpe= new Cycle();
Ishpe ishpe1=new Rect();
drawMap(ishpe);
drawMap(ishpe1);
drawMap(new Cycle());
drawMap(new Rect());
}
}
接口案例Computer类
先设计接口USB,包含打开和关闭驱动,两个抽象方法
package demo4;
public interface USB {
void openDevice();
void closeDevice();
}
然后设计电脑的组件,键盘和鼠标
package demo4;
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("键盘输入文字");
}
}
package demo4;
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("点击鼠标!");
}
}
鼠标和键盘类,使用USB接口,需要重写接口的抽象方法,打开和关闭驱动,不同类又有自己特有的方法
接下来设计Computer类
package demo4;
public class Computer {
public void open(){
System.out.println("打开电脑!");
}
public void close(){
System.out.println("关闭电脑!");
}
public void useDevice(USB usb){
usb.openDevice();
if(usb instanceof KeyBoard){
KeyBoard keyBoard=(KeyBoard)usb;//向下转型
keyBoard.input();
}else if(usb instanceof Mouse){
Mouse mouse=(Mouse)usb;
mouse.click();
}
usb.closeDevice();
}
public static void main(String[] args) {
Computer computer=new Computer();
computer.useDevice(new KeyBoard());
System.out.println("_______________");
computer.useDevice(new Mouse());
}
}
使用Computer的使用,先new一个,先打开电脑,然后使用驱动,最后关闭电脑
代码的运行结果
接口和继承一起使用的案例:
package demo5;
/**
* Created with IntelliJ IDEA.
* Description:
* User: laichangyang
* Date: 2025-04-02
* Time: 18:54
*/
interface IFlying{
void fly();
}
interface ISwimming{
void swim();
}
interface IRunning{
void run();
}
abstract class Animal{
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public abstract void eat();
}
class Dog extends Animal implements IRunning,ISwimming{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void swim() {
System.out.println(this.getName()+"正在狗刨!");
}
@Override
public void run() {
System.out.println(this.getName()+"正在跑!");
}
@Override
public void eat() {
System.out.println(this.getName()+"正在吃狗粮!");
}
}
class Bird extends Animal implements IFlying{
public Bird(String name, int age) {
super(name, age);
}
@Override
public void fly() {
System.out.println(this.getName()+"正在飞");
}
@Override
public void eat() {
System.out.println(this.getName()+"正在吃鸟粮");
}
}
class Robot implements IRunning{
@Override
public void run() {
System.out.println("机器人在跑");
}
}
public class Test {
public static void testFly(IFlying iFlying){
iFlying.fly();
}
public static void testRun(IRunning iRunning){
iRunning.run();
}
public static void testSwim(ISwimming iSwimming){
iSwimming.swim();
}
public static void main(String[] args) {
testFly(new Bird("小鸟",2));
testSwim(new Dog("小狗",3));
testRun(new Dog("小狗",5));
testRun(new Robot());
}
}
每一个接口可以代表一种属性,比起之前写死在动物类里面的优势在于,不是每一个动物都会飞和跑的,如果一个动物有这个属性,就调用这个接口就可以了。
如果给一个无序数组,大家都知道调用sort排序,那么是一个类,我们进行排序,怎么操作呢?
首先看数组的排序
package demo6;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] array={1,8,9,6,5,4,3,2};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
我们先构建学生类,包含姓名,年龄,分数等信息,然后建立一个学生类的数组,然后对数组排序,观察排序前和排序后
package demo6;
import java.util.Arrays;
/**
* Created with IntelliJ IDEA.
* Description:
* User: laichangyang
* Date: 2025-04-02
* Time: 19:17
*/
class Student{
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student[] array= new Student[3];
array[0]=new Student("zhangsan",19,85.5);
array[1]=new Student("lisi",20,80.5);
array[2]=new Student("wangwu",19,95.5);
System.out.println("排序前:"+Arrays.toString(array));
Arrays.sort(array);
System.out.println("排序后:"+Arrays.toString(array));
}
public static void main1(String[] args) {
int[] array={1,8,9,6,5,4,3,2};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
我们的程序明显是有问题的,排序的时候,我们根据什么排序,一个类有那么多属性。
输出结果:
通过观察
排序的时候是强制转换成Comparable类(一个接口),我们当然需要重写抽象的方法,那么我们使用一个接口,并且重写方法
最后发现还是不行,我们需要手动实现一下排序
最终的代码:
package demo6;
import java.util.Arrays;
/**
* Created with IntelliJ IDEA.
* Description:
* User: laichangyang
* Date: 2025-04-02
* Time: 19:17
*/
class Student implements Comparable<Student>{
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
int ret=this.age-o.age;
if(ret>=0) return 1;
else return 0;
}
}
public class Test {
public static void bubbleSort(Comparable[] comparables){
for(int i=0;i<comparables.length-1;i++){
for(int j=0;j<comparables.length-i-1;j++){
if(comparables[j].compareTo(comparables[j+1])>0){
Comparable tmp=comparables[j];
comparables[j]=comparables[j+1];
comparables[j+1]=tmp;
}
}
}
}
public static void main(String[] args) {
Student[] array= new Student[3];
array[0]=new Student("zhangsan",19,85.5);
array[1]=new Student("lisi",20,80.5);
array[2]=new Student("wangwu",19,95.5);
System.out.println("排序前:"+Arrays.toString(array));
//Arrays.sort(array);
bubbleSort((array));
System.out.println("排序后:"+Arrays.toString(array));
}
public static void main1(String[] args) {
int[] array={1,8,9,6,5,4,3,2};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
还有一个接口Comparator也可以实现比较,看一下它怎么使用吧
具体使用
package demo7;
import java.util.Comparator;
/**
* Created with IntelliJ IDEA.
* Description:
* User: laichangyang
* Date: 2025-04-02
* Time: 19:54
*/
class Student{
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", 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);
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("zhangsan",30,48);
Student student2 = new Student("lisi",20,80);
AgeComparator ageComparator= new AgeComparator();
System.out.println(ageComparator.compare(student1,student2));
System.out.println("------------");
ScoreComparator scoreComparator = new ScoreComparator();
System.out.println(scoreComparator.compare(student1,student2));
}
}
抽象类和接口的比较
何时使用抽象类,何时使用接口?
使用抽象类的情况:
需要在多个相关类之间共享代码和状态
需要非公开的方法和成员变量
类之间有明确的继承关系和层次结构
希望修改基类后,所有派生类都受到影响
使用接口的情况:
不相关的类需要共享同一行为(如不同类实现Comparable接口)
需要多重继承
只关心对象能做什么,而不是它是什么
需要规范一组相关或不相关的类
总结
抽象类和接口是Java中实现抽象和多态的两种重要机制。抽象类更适合用于表示类之间的继承关系和共享实现,而接口则更适合定义对象的行为规范和能力。在实际开发中,我们需要根据具体需求选择合适的抽象机制,有时甚至需要结合使用两者,以创建灵活、可扩展的代码结构。
随着Java语言的发展,接口也在不断增强功能(如Java 8引入的默认方法),使得接口和抽象类之间的界限变得越来越模糊。但理解它们各自的特点和适用场景,仍然是Java开发者必须掌握的基础知识。