一、包(package)
1.1、包(package)是组织类的一种方式
包里存的基本上都是类,而这些类都是别人写好的。我们只需要拿着用。前提是导入对应的包
比如说:打印数组
import java.util.Arrays;
public class TestDome {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
System.out.println(Arrays.toString(array));
}
}
一个包里面有很多个类
用类名 调用的方法就是static修饰的方法
1.2、什么是 package 和 import
package (包)
指:类所在的包
import (引入包中的类)
引入在类中需要的类(我们写的程序就是一个类,import 的作用就是 在我们写程序的时候, 引入我们所需要的类)
1.3、在java中不同的包可能会有同名的类
比如说:
那么 “ impor java.util.*; ” 和 “ impor java.util.具体的类名; ” ,那个更好?
impor java.util.具体的类名; 更好!
1.4、import static - 静态导入
1.5、使用包的主要目的是保证类的唯一性
但是 如果一个项目很大,有50多人参与这个项目,有可能会出现 类名相同的情况。此时包的作用就体现出来了
1.6、如何创建一个包
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
- 包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
- 如果一个类没有 package 语句, 则该类被放到一个默认包中.
1.7、在包里面创建一个类
1.8、在另一个包里,导入同名的类
1.8、包的访问权限控制 - 只能在同一个包里面使用
- 我们已经了解了类中的 public 和 private. private 中的成员只能被类的内部使用.
- 如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用
1.9、常见的系统包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包
二、继承 - extends
代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法).
有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联
封装:不必要公开的数据成员和方法,使用private关键字进行修饰。意义:安全性。
继承:对共性的抽取。使用extends关键字进行处理的。 意义:可以对代码进行重复使用。
定义两个类,Dog和Bird
class Dog{
public String name;
public int age;
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class Bird{
public String name;
public int age;
public void eat(){
System.out.println(name + "正在吃饭!");
}
public void Fly(){
System.out.println(name + "正在飞翔!");
}
}
这两个类分别表示狗和鸟,他们都有共同的属性,名字吗,年龄,吃饭
我们可以将这些共性抽取出来
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class Dog extends Animal{
}
class Bird extends Animal{
public void Fly(){
System.out.println(name + "正在飞翔!");
}
}
Dog和Bird都继承于Animal,Animal是动物,Dog和Bird都继承于Animal,说明Dog和Bird都具有名字吗,年龄,吃饭这些属性
这就是继承
如何访问Dog和Bird的属性和方法
和以前一样,通过对象的引用来调用
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(name + "正在吃饭!");
}
}
class Dog extends Animal{
}
class Bird extends Animal{
public void Fly(){
System.out.println(name + "正在飞翔!");
}
}
public class TestDome {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "小狗";
dog.age = 10;
System.out.println(dog.name);
System.out.println(dog.age);
dog.eat();
System.out.println("-----------------");
Bird bird = new Bird();
bird.name = "小鸟";
bird.age = 10;
System.out.println(bird.name);
System.out.println(bird.age);
bird.eat();
bird.Fly();
}
}
仔细分析, 我们发现 Animal 和 Dog以及 Bird 这几个类中存在一定的关联关系:
- 这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的.
- 这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.
- 从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义).
从逻辑上讲, Dog和 Bird 都是一种 Animal (is - a 语义).
狗是一种动物,鸟也是一种动物
此时我们就可以让 Dog和 Bird 分别继承 Animal 类, 来达到代码重用的效果.
此时, Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类, 对于像 Dog和 Bird 这样的类, 我们称为 子类, 派生类
和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果
2.1、语法规则
- 使用 extends 指定父类.
- Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
- 子类会继承父类的所有 public 的字段和方法.
- 对于父类的 private 的字段和方法, 子类中是无法访问的.
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
2.2、子类在进行构造的时候要先给父类进行构造(super的三种用法)
- super(); — 调用父类的构造方法,只能出现在构造方法里面,并且只能在第一行
- super.eat(); — 调用父类的普通方法
- super.属性;— 调用父类的属性
- super(); 不能出现在静态方法中,
2.3、在java中只能是单继承
2.4、protected 关键字
刚才我们发现, 如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.
两全其美的办法就是 protected 关键字.
- 对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
2.5、小结: Java 中对于字段和方法共有四种访问权限
- private: 类内部能访问, 类外部不能访问
- 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
- protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
- public : 类内部和类的调用者都能访问
2.6、更复杂的继承关系
举个例子, 只涉及到 Animal, Cat 和 Bird 三种类. 但是如果情况更复杂一些呢?
针对 Cat 这种情况, 我们可能还需要表示更多种类的猫~
如刚才这样的继承方式称为多层继承, 即子类还可以进一步的再派生出新的子类.
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字
2.7、 final 关键字
-
修饰一个变量或者字段的时候, 表示 常量 (不能修改)
-
final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
final 关键字的功能是 限制 类被继承
“限制” 这件事情意味着 “不灵活”. 在编程中, 灵活往往不见得是一件好事. 灵活可能意味着更容易出错.
是用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的
三、组合
组合和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
例如表示一个学校:
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.
这是我们设计类的一种常用方式之一.
组合表示 has - a 语义
在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师.
继承表示 is - a 语义
在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物
四、多态
4.1、向上转型 - 父类引用引用子类对象
class Animal{
String name;
int age;
protected int val;
private int hight;
public int getHight() {
return hight;
}
public void setHight(int hight) {
this.hight = hight;
}
public Animal(String name, int age){
System.out.println("调用Animal的构造方法");
}
public void eat(String name){
System.out.println(this.name + "正在吃饭!");
}
}
class Dog extends Animal {
public Dog(String name, int age){
super(name,age);
}
}
class Bird extends Animal {
public Bird(String name, int age){
super(name,age);
}
public void fly(String name){
System.out.println(this.name + "正在飞翔!");
}
}
public class TestDmoe {
public static void main(String[] args) {
// Dog dog = new Dog("小狗", 18);
// Animal animal = dog; // 父类引用 引用 子类对象
//我们也可以写成下面这种情况
Animal ani = new Dog("小狗", 18); //父类引用 引用 子类对象
}
}
4.1.1、向上转型发生的时机
1、直接赋值
2、作为函数的参数
3、作为函数的返回值
public class TestDmoe {
public static void func(Animal animal){
}
public static Animal func1(){
Dog dog = new Dog("小狗", 10);
return dog;
}
public static void main(String[] args) {
Dog dog = new Dog("小狗", 10);
func(dog);
Animal animal = func1();
}
public static void main1(String[] args) {
// Dog dog = new Dog("小狗", 18);
// Animal animal = dog; // 父类引用 引用 子类对象
//我们也可以写成下面这种情况
Animal ani = new Dog("小狗", 18);
}
}
4.2、动态绑定 - 多态的基础
动态绑定发生的前提:
- 父类引用 引用 子类对象
- 子类 重写父类 同名的方法
如果子类重写类同名的方法,那就会调用子类的方法
如果没有重写,那就调用父类字节的方法
4.2.1、覆写/重写/覆盖(Override)
1、在继承(父子类)的关系上
2、方法名相同
3、方法的参数列表相同(个数,类型)
4、返回值相同
super和this的区别
4.2.2、运行时绑定(也叫动态绑定)
我们反汇编看一下汇编代码
像这种编译时期不确定调用的是哪个的方法
只有在运行的才能确定调用的是哪个的方法,就称为运行时绑定(动态绑定)
4.2.3、编译时绑定(静态绑定)
重载的条件:
1、方法名相同
2、参数列表不同(个数或者类型)
3、返回值可以不同
class Dog extends Animal {
@Override
public Dog ret(){
return null;
}
@Override
public void eat(){
System.out.println("hello 正在吃饭!");
}
public Dog(String name, int age){
super(name,age);
}
public void func(){
System.out.println("不带参数的func");
}
public void func(int age){
System.out.println("带一个参数的func");
}
public int func(int age, String name){
return 0;
}
}
public class TestDmoe {
public static void main(String[] args) {
Dog dog = new Dog("小狗", 10);
dog.func(10);
}
}
此时看一下反汇编代码
这种在编译时就知道调用的是哪个方法,就叫做编译时绑定,也叫静态绑定
在编译的时候,通过方法的参数来确定调用的是哪个方法
4.2.4、重写的注意事项
- 1、方法不能是静态方法
- 2、子类的访问修饰限定的访问范围一定要大于等于 父类 的 访问修饰限定
- 3、private修饰的方法不能被重写
- 4、被final修饰的方法也不能被重写
- 5、返回值不一样也可以(协变类型)
方法不能是静态方法
子类的访问修饰限定的访问范围一定要大于等于 父类 的 访问修饰限定
重写中子类的方法的访问权限 不能低于 父类的方法访问权限
private修饰的方法不能被重写
被final修饰的方法也不能被重写
返回值不一样也可以(协变类型)
4.2.5、在构造方法中调用重写的方法(一个坑)
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Dome {
public static void main(String[] args) {
D d = new D();
}
}
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0
4.3、理解多态
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("♦");
}
}
///分割线/
public class TestDome {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Flower flower = new Flower();
drawMap(flower);
Rect rect = new Rect();
drawMap(rect);
}
}
在这个代码中,分割线上方的代码是 类的实现者 编写的,分割线下方的代码是 类的调用者 编写的
当类的调用者在编写 drawMap 这个方法的时候,参数类型为 Shape (父类),此时在该方法内部并不知道,也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例。此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关),这种行为就称为 多态
4.3.1、使用多态的好处是什么?
1)类调用者对类的使用成本进一步降低.
- 封装是让类的调用者不需要知道类的实现细节.
- 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.
这也贴合了 <<代码大全>> 中关于 “管理代码复杂程度” 的初衷
2)能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("♦");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
///分割线/
public class TestDome {
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
String[] strings = {"rect", "flower", "triangle", "rect", "flower"};
for (String str : strings) {
if(str.equals("rect")){
rect.draw();
}else if(str.equals("flower")){
flower.draw();
}else if(str.equals("triangle")){
triangle.draw();
}
}
}
}
如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单
public static void main(String[] args) {
Shape[] shapes = {new Rect(), new Triangle(), new Flower(),
new Triangle(),new Rect()};
for (Shape shape : shapes){
shape.draw();
}
}
什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. 如果一个方法的圈复杂度太高, 就需要考虑重构
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10
4.4、向下转型
向上转型是 is - a 的关系,比如说:鸟是一种动物,狗是一种动物
父类引用 引用 子类对象
向下转型不好,不安全,你总不能说所有的动物是狗吧
子类引用 引用 父类对象
class Animal{
String name = "hello";
int age;
public Animal(String name, int age){
System.out.println("调用Animal的构造方法");
}
public void eat(){
System.out.println(this.name + "正在吃饭!");
}
public Animal ret(){
return null;
}
}
class Bird extends Animal {
public Bird(String name, int age){
super(name,age);
}
public void fly(String name){
System.out.println(name + "正在飞翔!");
}
}
public class TestDmoe {
public static void main(String[] args) {
Animal animal = new Bird("小鸟", 10);
//animal.fly();
Bird bird = (Bird) animal;
bird.fly("小鸟");
}
}
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true
向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见
注意事项
编译过程中, animal 的类型是 Animal, 此时编译器只知道这个类中有一个 eat 方法, 没有 fly 方法.
虽然 animal 实际引用的是一个 Bird 对象, 但是编译器是以 animal 的类型来查看有哪些方法的.
对于 Animal animal = new Bird(“圆圆”) 这样的代码,
- 编译器检查有哪些方法存在, 看的是 Animal 这个类型
- 执行时究竟执行父类的方法还是子类的方法, 看的是 Bird 这个类型
但是这样的向下转型有时是不太安全
Animal animal = new Dog("小狗", 10);
Bird bird = (Bird) animal;
bird.fly("小狗");
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true
public static void main(String[] args) {
Animal animal = new Dog("小狗", 10);
if(animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.fly("小狗");
}
}
4.5、总结
多态是面向对象程序设计中比较难理解的部分. 我们会在后面的抽象类和接口中进一步体会多态的使用. 重点是多态带来的编码上的好处.
另一方面, 如果抛开 Java, 多态其实是一个更广泛的概念, 和 “继承” 这样的语法并没有必然的联系.
- C++ 中的 “动态多态” 和 Java 的多态类似. 但是 C++ 还有一种 “静态多态”(模板), 就和继承体系没有关系了.
- Python 中的多态体现的是 “鸭子类型”, 也和继承体系没有关系.
- Go 语言中没有 “继承” 这样的概念, 同样也能表示多态.
无论是哪种编程语言, 多态的核心都是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式
五、抽象类
在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class).
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
那这个方法直接就不实现任何操作就行了
(下面的写法是错误的示范)
class Shape{
public void draw();
}
此时就要用 abstract 关键字来修饰,用 abstract 关键字修饰的方法 叫做 抽象方法,一个类里面含有抽象方法,那这个类也要用 abstract 关键字类修饰,那这个类就叫做抽象类
abstract class Shape{
public abstract void draw();
}
5.1、注意事项
1)抽象类不实例化对象
2)抽象类只能被继承
3)一个普通类 继承 抽象类必须 重写 抽象类中的抽象方法
4)抽象类中可以包含和普通一样的成员
5)一个抽象类A 继承 一个 抽象B,就不用重写 父类B的抽象方法
6)结合第5点,一个普通类继承抽象类A,那这个普通类 就要重写 抽象类A和抽象类B 所有的抽象方法
7)抽象类不能被final修饰,抽象方法也不能被final修饰
8)抽象类不能被 private 修饰
5.2、抽象类的作用
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
- 有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
- 很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.
充分利用编译器的校验, 在实际开发中是非常有意义的
六、接口
接口是抽象类的更进一步,抽象类中还可以包含非抽象方法,和字段,而接口中包含的方法都是抽象方法,字段只能包含静态常量
6.1、interface 关键字
interface
英 [ˈɪntəfeɪs] 美 [ˈɪntərfeɪs]
n. (人机)界面(尤指屏幕布局和菜单);接口;接口程序;连接电路;(两学科、体系等的)接合点,边缘区域
v. (使通过界面或接口)接合,连接
接口都是由interface 修饰的
在刚才的打印图形的示例中, 我们的父类 Shape 并没有包含别的非抽象方法, 也可以设计成一个接口
interface IShape{
public abstract void draw();
}
接口中不能有普通方法的具体实现
如果非要实现,那就用 default 修饰,该方法表示该接口的默认方法
在接口中静态方法可以有具体的实现
接口中的方法(默认,静态),只能通过该接口或者 实现该接口 的 类 的引用来调用
interface IShape{
public abstract void draw();
default public void func(){
System.out.println("");
}
public static void funcStatic(){
System.out.println("接口中的静态方法");
}
}
class Rect implements IShape{
@Override
public void draw() {
System.out.println("♦");
}
}
public class Test1 {
public static void main(String[] args) {
Rect rect = new Rect();
IShape.funcStatic();//通过接口来调用静态方法
rect.draw();//通过实现接口的类 的引用来调用
}
}
不知有没有细心的人发现上面程序程序中 修饰方法的关键字public都是灰色的,意味着在接口当中,所有的方法都默认是public。
因为默认的方法也是public修饰的,所以默认方法的public可以省略
接口是不能被实例化的
一个类通过关键字implements来实现接口,这个类要重写接口中所有的抽象方法,默认方法和静态方法不需要重写
implements
英 [ˈɪmplɪments] 美 [ˈɪmplɪments]
v.
使生效;贯彻;执行;实施
n.
工具;器具;(常指)简单的户外用具
implement的第三人称单数和复数
interface IShape{
public abstract void draw();
void drawI();//抽象方法中的 public 和 abstract 是可以省略的
default void func(){
System.out.println("");
}
public static void funcStatic(){
System.out.println("接口中的静态方法");
}
}
class Rect implements IShape{
@Override
public void draw() {
System.out.println("♦");
}
@Override
public void drawI() {
}
}
通过接口可以实现向上转型,动态绑定,多态
interface IShape{
public abstract void draw();
default void func(){
System.out.println("");
}
public static void funcStatic(){
System.out.println("接口中的静态方法");
}
}
class Rect implements IShape{
@Override
public void draw() {
System.out.println("♦");
}
}
class Flower implements IShape {
@Override
public void draw() {
System.out.println("❀");
}
}
class Triangle implements IShape {
@Override
public void draw() {
System.out.println("△");
}
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("●");
}
}
向上转型,动态绑定
public static void main(String[] args) {
IShape iShape1 = new Rect();
iShape1.draw();
iShape1.func();
}
多态
public class Test1 {
public static void drawMap(IShape iShape){
iShape.draw();
}
public static void main(String[] args) {
IShape iShape1 = new Rect();
IShape iShape2 = new Flower();
IShape iShape3 = new Cycle();
IShape iShape4 = new Triangle();
IShape[] iShapes = {iShape1, iShape2, iShape3, iShape4};
for (IShape ip : iShapes) {
ip.draw();
}
}
}
扩展(extends) vs 实现(implements)
扩展指的是当前已经有一定的功能了, 进一步扩充功能.
实现指的是当前啥都没有, 需要从头构造出来
接口中的成员变量默认是 public static final 修饰的(常量)
接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).
当一个类实现一个接口的时候,重写接口中的抽象方法,重写的方法前面必须加public修饰
一个类可以继承一个类,同时也可以实现多个接口,这个类要重写接口中的所有的抽象方法;
接口和接口之间的关系
接口和接口之间可以使用extends来操作他们的关系,此时,这里面意为:拓展。
一个接口 IB 通过 extends来拓展另一个接口 IC 的功能。此时当一个类D通过implements实现这个接口 IB 的时候,此时重写的方法不仅仅是 IB 的抽象方法,还有他从 IC 接口,拓展来的功能【方法】。
interface IC{
void draw();
}
interface IB extends IC{
void func();
}
class D implements IB{
@Override
public void draw() {
System.out.println("hehe");
}
@Override
public void func() {
System.out.println("hukkk");
}
}
6.2、实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
现在我们通过类来表示一组动物:
//动物
class Animal{
protected String name;
public Animal(String name){
this.name = name;
}
}
//跑
interface IRunning{
void run();
}
//吃饭
interface IEat{
void eat();
}
//飞翔
interface IFly{
void fly();
}
//游泳
interface ISwimming{
void swim();
}
//鱼
class Fish extends Animal implements ISwimming{
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + " 正在游泳!");
}
}
//狗
class Dog extends Animal implements IRunning,IEat{
public Dog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + " 正在跑!");
}
@Override
public void eat() {
System.out.println(this.name + " 正在吃饭!");
}
}
//鸭子
class Duck extends Animal implements IRunning,IEat,ISwimming,IFly{
public Duck(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + " 正在跑!");
}
@Override
public void eat() {
System.out.println(this.name + " 正在吃饭!");
}
@Override
public void swim() {
System.out.println(this.name + " 正在游泳!");
}
@Override
public void fly() {
System.out.println(this.name + " 正在飞翔!");
}
}
public class TestDome {
public static void swimming(ISwimming iSwimming){
iSwimming.swim();
}
public static void running(IRunning iRunning){
iRunning.run();
}
public static void fly(IFly iFly){
iFly.fly();
}
public static void main(String[] args) {
swimming(new Duck("鸭子"));
running(new Dog("小狗"));
swimming(new Fish("鱼"));
fly(new Duck("鸭子"));
}
}
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .
- 狗是一种动物, 具有会跑的特性.
- 青蛙也是一种动物, 既能跑, 也能游泳
- 鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而
只关注某个类是否具备某种能力.