大学生入门:抽象 及 接口
目录
一、抽象
1、抽象方法
2、抽象类
二、接口
1、概念
2、注意:
3、命名习惯
三、抽象类和接口对比
四、易踩坑的点
1、抽象类
2、接口
3、设计选择误区
在介绍抽象和接口之前,我们先来简单了解一下它们的历史背景和发展:
java诞生时,抽象类其实是C++的“私生子”
它继承了父类 virtual 函数特性,去掉了危险的 多重继承
Sun 公司的工程师们当时喝着咖啡讨论着要不要让接口也能带方法体
最终投票结果是:接口必须保持纯洁!
(但是这个决定在20年后被推翻)
一、抽象
抽象是将复杂的概念和现实问题简化为更易于理解和处理的表示方法
在编程过程中,抽象关注问题的本质和关键特征,忽略其具体实现细节的方法
在面向对象编程中,抽象主要通过定义类、接口和方法来实现
1、抽象方法
将子类共性的行为(方法)抽取到父类中
发现父类中无法实现子类的具体实现逻辑
那么就可以将该方法定义为抽象方法
回顾一下普通方法:
大学生入门:初识方法及其易踩坑的点-CSDN博客https://blog.csdn.net/qq_73698057/article/details/149579229?spm=1011.2124.3001.6209
——特点:
1> 无方法体
只有方法声明
没有具体的实现代码
2> 必须在抽象类或者接口中
3> 子类必须实现
4> 不能是private、static、final修饰的
大学生入门:static及其易踩坑的点-CSDN博客https://blog.csdn.net/qq_73698057/article/details/149784774?spm=1011.2124.3001.6209
大学生入门:final 关键字-CSDN博客https://blog.csdn.net/qq_73698057/article/details/149944359?sharetype=blogdetail&sharerId=149944359&sharerefer=PC&sharesource=qq_73698057&spm=1011.2480.3001.8118
2、抽象类
用 abstract 关键字声明的类
——特点:
1> 抽象类不能实例化创建对象
2> 抽象类可以定义成员属性
3> 包含抽象方法的类必须声明为抽象类
没有抽象方法也可以声明为抽象类
4> 子类必须重写父类中的所有抽象方法
否则子类中会有隐藏的未重写的抽象方法
此时子类需要声明为抽象类
但是这样就违背了我们的初衷
所以一定不要漏掉每一个抽象方法
5> 含有构造器
否则子类构造器super不到父类的构造方法
没有显示提供的话,编译器会自动提供无参构造器
6> 可以包含具体方法
// 抽象类定义
abstract class Animal {// 抽象类可以定义成员属性protected String name;protected int age;// 构造器(可选,但建议提供)public Animal() {this.name = "未知";this.age = 0;}public Animal(String name, int age) {this.name = name;this.age = age;}// 抽象方法(子类必须实现)public abstract void makeSound();// 具体方法(有实现)public void sleep() {System.out.println(name + " is sleeping");}// 普通具体方法public void eat() {System.out.println(name + " is eating");}// getter方法public String getName() {return name;}public int getAge() {return age;}
}// 具体子类 - 必须实现所有抽象方法
class Dog extends Animal {public Dog() {super(); // 调用父类构造器}public Dog(String name, int age) {super(name, age);}// 实现抽象方法@Overridepublic void makeSound() {System.out.println(name + " says: Woof!");}// 可以添加自己的方法public void wagTail() {System.out.println(name + " is wagging tail");}
}// 另一个具体子类
class Cat extends Animal {public Cat(String name, int age) {super(name, age);}@Overridepublic void makeSound() {System.out.println(name + " says: Meow!");}
}// 演示类
public class AbstractDemo {public static void main(String[] args) {// 抽象类不能直接实例化// Animal animal = new Animal(); // 编译错误// 创建具体子类对象Dog dog = new Dog("旺财", 3);Cat cat = new Cat("咪咪", 2);// 使用对象System.out.println("狗的名字:" + dog.getName() + ",年龄:" + dog.getAge());dog.makeSound(); // 实现的抽象方法dog.sleep(); // 继承的具体方法dog.wagTail(); // 自己的方法System.out.println();System.out.println("猫的名字:" + cat.getName() + ",年龄:" + cat.getAge());cat.makeSound(); // 实现的抽象方法cat.sleep(); // 继承的具体方法// 多态使用Animal animal1 = new Dog("小白", 1);Animal animal2 = new Cat("小花", 2);animal1.makeSound();animal2.makeSound();}
}
二、接口
1、概念
1> 引用数据类型的一种
2> java 是单继承,接口是对单继承的补充
继承(父亲)亲爹唯一
接口(老师)老师多个
3> 关键字 implements
可以和继承混合使用
eg:
public class Dog extends Animal implements IAction1,IAction2{...
}
2、注意:
1>
接口关键字 interface
类关键字 class
类编译后文件后缀 .class
接口编译后文件后缀 .class
2> 接口不是类
但是接口和类的实现关系中,可以使用多态
大学生入门:封装、继承和多态-CSDN博客https://blog.csdn.net/qq_73698057/article/details/149912534?spm=1001.2014.3001.5502 一个类可以同时实现多个接口
3> 接口中的属性默认是常量,默认public static final修饰
接口中的方法默认是抽象方法,默认public abstract修饰
所以接口也不能实例化创建对象
4> java中类的继承只能是单继承,不支持多继承
java中类和接口之间的实现关系是多实现
java中接口和接口之间的继承关系是多继承
5> jdk8 之前,接口中只允许存在常量、抽象方法
jdk8 引入 default 方法 和 static 方法(可以具体实现)
jdk9 引入 private 方法 和 private static 方法(可以具体实现)
3、命名习惯
类:首字母大写,驼峰命名
接口:首字母一般是大写 I ,然后和类一样
接口综合示例:
// 定义一个接口 - Flyable(可飞行的)
interface Flyable {// 接口中的属性默认是 public static final 常量int MAX_ALTITUDE = 10000;// 接口中的方法默认是 public abstract 抽象方法void fly();void land();// 获取最大飞行高度int getMaxAltitude();
}// 定义另一个接口 - Swimmable(可游泳的)
interface Swimmable {// 抽象方法void swim();// 默认方法(JDK 8+)default void dive() {System.out.println("潜水ing...");}
}// 定义第三个接口 - Runnable(可奔跑的)
interface Runnable {void run();// 静态方法(JDK 8+)static void showSpeedInfo() {System.out.println("奔跑速度信息:哺乳动物通常奔跑速度在5-100km/h之间");}
}// 具体实现类 - 鸭子(实现了多个接口)
class Duck implements Flyable, Swimmable, Runnable {private String name;public Duck(String name) {this.name = name;}// 实现 Flyable 接口的方法@Overridepublic void fly() {System.out.println(name + " 正在天空中飞翔");}@Overridepublic void land() {System.out.println(name + " 安全着陆了");}@Overridepublic int getMaxAltitude() {return MAX_ALTITUDE; // 使用接口常量}// 实现 Swimmable 接口的方法@Overridepublic void swim() {System.out.println(name + " 正在水中游泳");}// 实现 Runnable 接口的方法@Overridepublic void run() {System.out.println(name + " 正在陆地上奔跑");}// 可以重写默认方法(可选)@Overridepublic void dive() {System.out.println(name + " 正在水中潜水觅食");}
}// 另一个实现类 - 人类
class Human implements Swimmable, Runnable {private String name;public Human(String name) {this.name = name;}@Overridepublic void swim() {System.out.println(name + " 正在游泳池里游泳");}@Overridepublic void run() {System.out.println(name + " 正在跑道上奔跑");}
}// 测试类
public class InterfaceDemo {public static void main(String[] args) {// 接口不能直接实例化// Flyable flyable = new Flyable(); // 错误!// 创建具体实现类对象Duck duck = new Duck("唐老鸭");Human human = new Human("小明");// 使用对象调用方法System.out.println("=== 鸭子的能力 ===");duck.fly();duck.land();System.out.println("最大飞行高度:" + duck.getMaxAltitude());duck.swim();duck.run();duck.dive(); // 调用默认方法System.out.println("\n=== 人类的能力 ===");human.swim();human.run();human.dive(); // 调用默认方法// 多态使用System.out.println("\n=== 多态演示 ===");Flyable flyable = new Duck("多态鸭子");Swimmable swimmable1 = new Duck("多态鸭子");Swimmable swimmable2 = new Human("多态人类");Runnable runnable1 = new Duck("多态鸭子");Runnable runnable2 = new Human("多态人类");flyable.fly();swimmable1.swim();swimmable2.swim();runnable1.run();runnable2.run();// 调用静态方法System.out.println("\n=== 静态方法调用 ===");Runnable.showSpeedInfo();}
}
三、抽象类和接口对比
维度 | 抽象类 | 接口 |
---|---|---|
实例化 | 不能直接实例化 | 不能实例化 |
构造器 | 必须存在 | 不允许存在 |
方法实现 | 可包含具体实现 | Java8前必须全部抽象 |
成员变量 | 普通变量/常量均可 | 只能是常量 |
设计目标 | 代码复用+部分约束 | 纯行为契约 |
四、易踩坑的点
1、抽象类
1> 忘记定义构造器导致子类编译错误
2> 子类漏实现抽象方法却不声明 abstract
3> 错误实例化
//错误:
抽象类名 对象名 = new 抽象类名();//正确(使用多态的向上转型):
抽象类名 对象名 = new 子类名();
2、接口
1> 常量被 final 修饰,不可修改
2> 实现多个接口时,出现同名默认方法冲突
interface A {default void method() {System.out.println("A's method");}
}interface B {default void method() {System.out.println("B's method");}
}// 错误 - 必须明确指定使用哪个接口的实现
/*
class C implements A, B {// 编译错误:类C从类型A和B继承了method()的不相关默认值
}
*/// 正确 - 重写冲突的方法
class C implements A, B {@Overridepublic void method() {A.super.method(); // 调用A的实现// 或者提供自己的实现}
}
3、设计选择误区
1> 过度使用抽象类导致继承体系臃肿
2> 用接口替代所有抽象场景(虽然要优先接口,但是需合理选择)
3> 混淆 extends 和 implements 关键字