原文地址:https://www.cloudcrossing.xyz/post/36/
1.继承
1.1 继承概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键字可以实现类与类的继承,格式:class 子类名 extends 父类名 {}
.
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。继承中类之间提现的是一种"is a"的关系。
//使用继承前
/*
class Student {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Teacher {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
*/
//使用继承后
class Person {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Student extends Person {}
class Teacher extends Person {}
class ExtendsDemo {
public static void main(String[] args) {
Student s = new Student();
s.eat();
s.sleep();
System.out.println("-------------");
Teacher t = new Teacher();
t.eat();
t.sleep();
}
}
继承的好处:
- A:提高了代码的复用性,多个类相同的成员可以放到同一个类中
- B:提高了代码的维护性,如果功能的代码需要修改,修改一处即可
- C:让类与类产生了一个关系,是多态的前提
继承的弊端:
- A:让类的耦合性增强。这样某个类的改变,就会影响其他和该类相关的类。
- 原则:低耦合,高内聚。
- 耦合:类与类的关系
- 内聚:自己完成某件事情的能力
- B:打破了封装性
继承的特点:
- A:Java中类只支持单继承,即一个类只有一个父类
- B:Java中可以多层继承(继承体系)
- class A {}
- class B extends A {}
- class C extends B {}
继承的注意事项:
- A:子类不能继承父类的私有成员
- B:子类不能继承父类的构造方法,但可以通过
super()
去访问
1.2 继承中的成员关系
(1)成员变量:
- A:子类的成员变量名称和父类中的成员变量名称不一样,直接访问
- B:子类的成员变量名称和父类中的成员变量名称一样,
- 子类的方法访问变量的查找顺序
- 在子类的方法中的局部范围找,有就访问
- 在子类的成员范围中找,有就访问
- 在父类的成员范围中找,有就访问
- 找不到就报错
- 子类的方法访问变量的查找顺序
class Father {
public int num3 = 10;
public void method() {
int num = 50;
}
}
class Son extends Father {
public int num2 = 20;
public int num = 30;
public void show() {
int num = 40;
System.out.println(num); //输出40
System.out.println(num2); //输出20
System.out.println(num3); //输出10
System.out.println(this.num); //输出30
}
}
class ExtendsDemo4 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
s.show();
}
}
(2)构造方法
- A:子类的构造方法默认会去访问父类的无参构造方法(隐式语句)
- 是为了子类访问父类数据的初始化
- 子类每一个构造方法的第一条语句默认都是:super();
- B:父类中如果没有无参构造方法,则
- 子类用super去明确调用父类的带参构造
- 子类通过this调用本身的其他构造,但是一定会有一个去访问父类的构造,否则父类数据就没有初始化
- 让父类提供构造方法
如果父类没有无参构造方法,那么子类的构造方法会出现什么现象呢?答案是报错。
class Father {
/*
public Father() {
System.out.println("Father的无参构造方法");
}
*/
public Father(String name) {
System.out.println("Father的带参构造方法");
}
}
class Son extends Father {
public Son() {
//super();隐式语句
super("随便给");
System.out.println("Son的无参构造方法");
//super("随便给");
}
public Son(String name) {
//super();隐式语句
//super("随便给");
this();
System.out.println("Son的带参构造方法");
}
}
class ExtendsDemo7 {
public static void main(String[] args) {
Son s = new Son();
System.out.println("----------------");
Son ss = new Son("林青霞");
}
}
//运行结果
Father的带参构造方法
Son的无参构造方法
----------------
Father的带参构造方法
Son的无参构造方法
Son的带参构造方法
注意:this(...)或者super(...)必须出现在第一条语句上。如果不是放在第一条语句上,就可能对父类的数据进行了多次初始化(因为子类的构造方法默认会去访问父类的无参构造方法),所以必须放在第一条语句上。并且super(...)和this(..)不能同时存在构造函数第一行。
(3)成员方法
- A:子类的成员方法和父类中的成员方法名称不一样,直接调用
- B:子类的成员方法和父类中的成员方法名称一样,则
- 通过通过子类对象访问一个方法的查找顺序:
- 在子类中找,有就调用
- 在父类中找,有就调用
- 找不到,就报错
- 通过通过子类对象访问一个方法的查找顺序:
class Father {
public void show() {
System.out.println("show Father");
}
}
class Son extends Father {
public void method() {
System.out.println("method Son");
}
public void show() {
System.out.println("show Son");
}
}
class ExtendsDemo8 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
s.show(); //输出:show Son
s.method(); //输出:method Son
//s.fucntion(); //找不到符号
}
}
1.3 super关键字
(1)super关键字作用
- A:主要存在于子类方法中,用于指向子类对象中父类对象。
- B:访问父类的属性
- C:访问父类的函数
- D:访问父类的构造函数
(2)this和super的区别
- A:this代表本类对应的引用
- B:super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)
注意:this,super只能在有对象的前提下使用,不能在静态上下文使用。
(3) this和super的使用
- A:调用成员变量
-
this.成员变量
调用本类的成员变量 -
super.成员变量
调用父类的成员变量
-
- B:调用构造方法
-
this(...)
调用本类的构造方法 -
super(...)
调用父类的构造方法
-
- C:调用成员方法
-
this.成员方法
调用本类的成员方法 -
super.成员方法
调用父类的成员方法
-
class Father {
public int num = 10;
}
class Son extends Father {
public int num = 20;
public void show() {
int num = 30;
System.out.println(num); //输出30
System.out.println(this.num); //输出20
System.out.println(super.num); //输出10
}
}
class ExtendsDemo5 {
public static void main(String[] args) {
Son s = new Son();
s.show();
}
}
思考:如果自定义了一个类,没有显示的进行类的继承,那么该类中成员函数是否可以使用super关键字?可以使用,这个类继承了Object类,Object类是所有类的父类。
1.4 方法重写
子类中出现了和父类中方法声明一模一样的方法,叫做方法重写(Override)。
而方法重载(Overload)是本类中出现的方法名一样,参数列表不同的方法(与返回值无关)。
子类对象调用方法的时候,先找子类本身,再找父类。
方法重写的应用:
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。这样,即沿袭了父类的功能,又定义了子类特有的内容。
class Phone {
public void call(String name) {
System.out.println("给"+name+"打电话");
}
}
class NewPhone extends Phone {
public void call(String name) {
//System.out.println("给"+name+"打电话");
super.call(name);
System.out.println("可以听天气预报了");
}
}
class ExtendsDemo9 {
public static void main(String[] args) {
NewPhone np = new NewPhone();
np.call("林青霞");
}
}
//运行结果:
给林青霞打电话
可以听天气预报了
方法重写的注意事项:
- A:父类中私有方法不能被重写
- 因为父类的私有方法子类根本无法继承
- B:子类重写父类方法时,访问权限不能更低,最好一致
- C:父类静态方法,子类也必须是通过静态方法进行重写
- D:子类重写父类方法的时候,最好声明一模一样
1.5 继承相关的几个练习题
(1)看程序写结果
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
class ExtendsTest2 {
public static void main(String[] args) {
Zi z = new Zi();
}
}
//运行结果:
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
- A:一个类的静态代码块,构造代码块,构造方法的执行流程
- 静态代码块 > 构造代码块 > 构造方法
- B:静态的内容是随着类的加载而加载
- 静态代码块的内容会优先执行
- C:子类初始化之前先会进行父类的初始化
(2)看程序写结果
class X {
Y b = new Y(); //1
X() {
System.out.print("X"); //2
}
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y(); //3
Z() {
//super
System.out.print("Z"); //4
}
public static void main(String[] args) {
new Z();
}
}
//运行结果:
YXYZ
一个类的初始化过程:
- 成员变量的初始化
- 默认初始化
- 显示初始化
- 构造方法初始化
子父类的初始化(分层初始化):先进行父类初始化,然后进行子类初始化。
虽然子类中构造方法默认有一个super(),但是初始化的时候,不是按照那个顺序进行的。而是按照分层初始化进行的。它仅仅表示要先初始化父类数据,再初始化子类数据。(不是YYXZ)
(3)方法重写和方法重载的区别?方法重载能改变返回值类型吗?
- 方法重写:在子类中,出现和父类中一模一样的方法声明的现象
- 方法重载:同一个类中,出现的方法名相同,参数列表不同的现象
- 方法重载能改变返回值类型,因为它和返回值类型无关。
2 final关键字
2.1 final关键字概述
final关键字是最终的意思,可以修饰类、方法、变量。
特点:
- A:final关键字修饰的类不能被继承
- B:final关键字修饰的方法不能被重写
- C:final关键字修饰的变量是一个常量(即自定义常量),只能被赋值一次
//final class Fu //无法从最终Fu进行继承
class Fu {
public int num = 10;
public final int num2 = 20;
/*
public final void show() {
}
*/
}
class Zi extends Fu {
// Zi中的show()无法覆盖Fu中的show()
public void show() {
num = 100;
System.out.println(num);
//无法为最终变量num2分配值
//num2 = 200;
System.out.println(num2);
}
}
class FinalDemo {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
2.1 final修饰局部变量的问题
- 基本类型:基本类型的值不能改变
- 引用类型:引用类型的地址值不能发生改变,该对象的堆内存的值可以发生改变
class Student {
int age = 10;
}
class FinalTest {
public static void main(String[] args) {
//局部变量是基本数据类型
int x = 10;
x = 100;
System.out.println(x);
final int y = 10;
//无法为最终变量y分配值
//y = 100;
System.out.println(y);
System.out.println("--------------");
//局部变量是引用数据类型
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);
System.out.println("--------------");
final Student ss = new Student();
System.out.println(ss.age);
ss.age = 100;
System.out.println(ss.age);
//重新分配内存空间
//无法为最终变量ss分配值
ss = new Student();
}
}
2.1 final修饰变量的初始化时机
- 被final修饰的变量只能被赋值一次
- 在构造方法完毕前(针对非静态常量)即可
- 定义成员变量的时候赋值(推荐)
- 构造代码块内赋值
- 构造方法内赋值
class Demo {
//int num = 10;
//final int num2 = 20;
int num;
final int num2;
{
//num2 = 10;
}
public Demo() {
num = 100;
//无法为最终变量num2分配值
num2 = 200;
}
}
class FinalTest2 {
public static void main(String[] args) {
Demo d = new Demo();
System.out.println(d.num);
System.out.println(d.num2);
}
}
3 多态
3.1 多态概述
多态指的是某一个事物在不同时刻表现出来的不同状态。
举个例子:
- 猫可以使猫的类型。
Cat m = new Cat();
- 同时猫也是动物的一种,也可以把猫称为动物,
Animal m = new Cat();
- 或者水的三种状态,液体、气体、固体
多态的前提:
- A:有继承或者实现关系
- B:有方法重写
动物 d = new 猫(); d.show(); 动物 d = new 狗(); d.show();
- C:有父类或者父接口引用指向子类对象
父 f = new 子();
多态的分类:
- A:具体类多态
- class Fu {}
- class Zi extends Fu {}
- Fu f = new Zi();
- B:抽象类多态
- abstract class Fu {}
- class Zi extends Fu {}
- Fu f = new Zi();
- C:接口多态
- interface Fu {}
- class Zi implements Fu {}
- Fu f = new Zi();
3.2 多态中的成员访问特点
- A:成员变量:编译看左边,运行看左边
- B:构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化
- C:成员方法:编译看左边,运行看右边(因为成员方法存在方法重写,所以它运行看右边)
- D:静态方法:编译看左边,运行看左边(静态和类相关,算不上重写,所以,访问还是左边的)
class Fu {
public int num = 100;
public void show() {
System.out.println("show Fu");
}
public static void function() {
System.out.println("function Fu");
}
}
class Zi extends Fu {
public int num = 1000;
public int num2 = 200;
public void show() {
System.out.println("show Zi");
}
public void method() {
System.out.println("method zi");
}
public static void function() {
System.out.println("function Zi");
}
}
class DuoTaiDemo {
public static void main(String[] args) {
//要有父类引用指向子类对象。
Fu f = new Zi();
System.out.println(f.num); //输出100
//找不到符号
//System.out.println(f.num2);
f.show(); //输出show Zi
//找不到符号
//f.method();
f.function(); //输出function Fu
}
}

3.3 多态的优缺点
多态的好处:
- 提高代码的维护性(继承体现)
- 提高代码的扩展性(多态体现)
class Animal {
public void eat(){
System.out.println("eat");
}
public void sleep(){
System.out.println("sleep");
}
}
class Dog extends Animal {
public void eat(){
System.out.println("狗吃肉");
}
public void sleep(){
System.out.println("狗站着睡觉");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void sleep() {
System.out.println("猫趴着睡觉");
}
}
class Pig extends Animal {
public void eat() {
System.out.println("猪吃白菜");
}
public void sleep() {
System.out.println("猪侧着睡");
}
}
//针对动物操作的工具类
class AnimalTool {
private AnimalTool(){}
/*
//调用猫的功能
public static void useCat(Cat c) {
c.eat();
c.sleep();
}
//调用狗的功能
public static void useDog(Dog d) {
d.eat();
d.sleep();
}
//调用猪的功能
public static void usePig(Pig p) {
p.eat();
p.sleep();
}
*/
public static void useAnimal(Animal a) {
a.eat();
a.sleep();
}
}
class DuoTaiDemo2 {
public static void main(String[] args) {
//我喜欢猫,就养了一只
Cat c = new Cat();
c.eat();
c.sleep();
//我很喜欢猫,所以,又养了一只
Cat c2 = new Cat();
c2.eat();
c2.sleep();
//我特别喜欢猫,又养了一只
Cat c3 = new Cat();
c3.eat();
c3.sleep();
//...
System.out.println("--------------");
//问题来了,我养了很多只猫,每次创建对象是可以接受的
//但是呢?调用方法,你不觉得很相似吗?仅仅是对象名不一样。
//我们准备用方法改进
//调用方式改进版本1
//useCat(c);
//useCat(c2);
//useCat(c3);
//AnimalTool.useCat(c);
//AnimalTool.useCat(c2);
//AnimalTool.useCat(c3);
//上面的调用还是太反锁了,每次都要调用相应动物的工具类方法
//我们可以利用多态的特性改进
//调用方式改进版本2
AnimalTool.useAnimal(c);
AnimalTool.useAnimal(c2);
AnimalTool.useAnimal(c3);
System.out.println("--------------");
}
/*
//调用猫的功能
public static void useCat(Cat c) {
c.eat();
c.sleep();
}
//调用狗的功能
public static void useDog(Dog d) {
d.eat();
d.sleep();
}
*/
}
首先定义对应的类(猫,狗,猪),继承自动物,提供对应的方法重写,并在工具类添加方法调用。但是随着不同动物的增加(比如蛇,鸟等),工具类的方法是否需要每次修改添加呢?
不用这么麻烦!我们可以利用多态的特性。观察以上代码
多态的弊端:父不能使用子的特有功能。现象:子可以当作父使用,父不能当作子使用。
class Fu {
public void show() {
System.out.println("show fu");
}
}
class Zi extends Fu {
public void show() {
System.out.println("show zi");
}
public void method() {
System.out.println("method zi");
}
}
class DuoTaiDemo3 {
public static void main(String[] args) {
//测试
Fu f = new Zi();
f.show(); //输出:show zi
//f.method(); //报错
}
}
3.4 多态的转型以及理解
由于多态不能使用子类的特有功能。那么我们如何使用子类的特有功能呢?
- 创建子类对象调用方法即可(可以,但是很多时候不合理。而且,太占内存了)
- 把父类的引用强制转换为子类的引用(向下转型)
对象间的转型问题:
- 向上转型:从子到父,
Fu f = new Zi();
- 向下转型:从父到子,
Zi z = (Zi)f;
,要求该f必须是能够转换为Zi的
class Fu {
public void show() {
System.out.println("show fu");
}
}
class Zi extends Fu {
public void show() {
System.out.println("show zi");
}
public void method() {
System.out.println("method zi");
}
}
class DuoTaiDemo4 {
public static void main(String[] args) {
//测试
Fu f = new Zi();
f.show();
//f.method();
//创建子类对象调用方法
//Zi z = new Zi();
//z.show();
//z.method();
//把父类的引用强制转换为子类的引用(向下转型)
Zi z = (Zi)f;
z.show();
z.method();
}
}
再举一个例子来理解多态转型的现象。
class 孔子爹 {
public int age = 40;
public void teach() {
System.out.println("讲解JavaSE");
}
}
class 孔子 extends 孔子爹 {
public int age = 20;
public void teach() {
System.out.println("讲解论语");
}
public void playGame() {
System.out.println("英雄联盟");
}
}
//Java培训特别火,很多人来请孔子爹去讲课,这一天孔子爹被请走了
//但是还有人来请,就剩孔子在家,价格还挺高。孔子一想,我是不是可以考虑去呢?
//然后就穿上爹的衣服,带上爹的眼睛,粘上爹的胡子。就开始装爹
//向上转型
孔子爹 k爹 = new 孔子();
//到人家那里去了
System.out.println(k爹.age); //40
k爹.teach(); //讲解论语
//k爹.playGame(); //这是儿子才能做的
//讲完了,下班回家了
//脱下爹的装备,换上自己的装备
//向下转型
孔子 k = (孔子) k爹;
System.out.println(k.age); //20
k.teach(); //讲解论语
k.playGame(); //英雄联盟
4 抽象类
4.1 抽象类的概述
java把多个共性的东西提取到一个类中,叫做继承。但是在有些时候,方法声明一样,每个具体的对象在具体实现的时候内容不一样。所以,我们在定义这些共性的方法的时候,就不能给出具体的方法体。
而一个没有具体的方法体的方法是抽象的方法。在一个类中如果有抽象方法,该类必须定义为抽象类。
4.2 抽象类的特点
- A:抽象类和抽象方法必须用关键字abstract修饰,格式:
abstract class 类名 {};
,public abstract void eat();
- B:抽象类中不一定有抽象方法,但是有抽象方法的类一定是抽象类
- C:抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。且抽象方法只包含一个方法名,没有方法体
- D:抽象类不能实例化,因为它不是具体的。如果需要实例化,则由具体的子类实例化,即抽象类多态。格式:
Animal a = new Cat();
- E:抽象类的子类
- a:是一个抽象类
- b:是一个具体类。这个类必须重写抽象类中的所有抽象方法
//父类是抽象类
abstract class Animal {
//抽象方法
//public abstract void eat(){} //存在空方法体,会报错。因为抽象方法不能有主体
public abstract void eat();
public Animal(){}
}
//子类是抽象类
abstract class Dog extends Animal {}
//子类是具体类,重写抽象方法
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
class AbstractDemo {
public static void main(String[] args) {
//创建对象
//Animal是抽象的; 无法实例化
//Animal a = new Animal();
//通过多态的方式
Animal a = new Cat();
a.eat();
}
}
4.3 抽象类的成员特点
- 成员变量:有变量,有常量
- 构造方法:有构造方法,但是不能实例化(具体类的构造方法不能用abstract修饰,否则无法实例化)
- 抽象类的构造方法的作用:用于子类访问父类数据的初始化
- 成员方法:
- 有抽象方法:限定子类必须完成某些动作
- 有非抽象方法:子类继承的事情,提高代码服用性
abstract class Animal {
public int num = 10;//变量
public final int num2 = 20;//常量
public Animal() {}//无参构造方法
public Animal(String name,int age){}//带参构造方法
public abstract void show();//不能有主体的抽象方法
public void method() {//非抽象方法
System.out.println("method");
}
}
class Dog extends Animal {
public void show() {
System.out.println("show Dog");
}
}
class AbstractDemo2 {
public static void main(String[] args) {
//创建对象
Animal a = new Dog();
a.num = 100;
System.out.println(a.num);
//a.num2 = 200;
System.out.println(a.num2);
System.out.println("--------------");
a.show();
a.method();
}
}
4.4 抽象类的几个小问题
(1)一个类如果没有抽象方法,却定义为了抽象类,有什么用?
- 答案是为了不让创建对象
(2)abstract不能和哪些关键字共存?
- A:final-->冲突,因为抽象类必须有子类,而final定义的类不能有子类
- B:private-->冲突
- C:static-->无意义
abstract class Fu {
public abstract void show();
//非法的修饰符组合: abstract和private
//private abstract void show();
//非法的修饰符组合:非法的修饰符组合: abstract和final
//Zi中的show()无法覆盖Fu中的show()
//final abstract void show();
//非法的修饰符组合:abstract和static
//static abstract void show();
public static void method() {
System.out.println("method");
}
}
class Zi extends Fu {
public void show() {}
}
class AbstractDemo3 {
public static void main(String[] args) {
Fu.method();
}
}
4.5 抽象类的猫狗案例
- 具体事物:猫,狗
- 共性:姓名,年龄,吃饭
分析:从具体到抽象
- 猫:
- 成员变量:姓名,年龄
- 构造方法:无参,带参
- 成员方法:吃饭(猫吃鱼)
- 狗:
- 成员变量:姓名,年龄
- 构造方法:无参,带参
- 成员方法:吃饭(狗吃肉)
因为有共性的内容,所以就提取了一个父类:动物。但是又由于吃饭的内容不一样,所以吃饭的方法是抽象的,而方法是抽象的类,类就必须定义为抽象类。
抽象动物类:
- 成员变量:姓名,年龄
- 构造方法:无参,带参
- 成员方法:吃饭();
实现:从抽象到具体
- 动物类:
- 成员变量:姓名,年龄
- 构造方法:无参,带参
- 成员方法:吃饭();
- 狗类:
- 继承自动物类
- 重写吃饭();
- 猫类:
- 继承自动物类
- 重写吃饭();
//定义抽象的动物类
abstract class Animal {
//姓名
private String name;
//年龄
private int age;
public Animal() {}
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//定义一个抽象方法
public abstract void eat();
}
//定义具体的狗类
class Dog extends Animal {
public Dog() {}
public Dog(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("狗吃肉");
}
}
//定义具体的猫类
class Cat extends Animal {
public Cat() {}
public Cat(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("猫吃鱼");
}
}
//测试类
class AbstractTest {
public static void main(String[] args) {
//测试狗类
//具体类用法
//方式1:
Dog d = new Dog();
d.setName("旺财");
d.setAge(3);
System.out.println(d.getName()+"---"+d.getAge());
d.eat();
//方式2:
Dog d2 = new Dog("旺财",3);
System.out.println(d2.getName()+"---"+d2.getAge());
d2.eat();
System.out.println("---------------------------");
//抽象多态类用法
//方式1:
Animal a = new Dog();
a.setName("旺财");
a.setAge(3);
System.out.println(a.getName()+"---"+a.getAge());
a.eat();
//方式2:
Animal a2 = new Dog("旺财",3);
System.out.println(a2.getName()+"---"+a2.getAge());
a2.eat();
}
}
5 接口
5.1 接口概述
回顾猫狗案例,它们仅仅提供一些基本功能。如果我们想要训练他们获取新技能,比如:猫钻火圈,狗跳高等,不是动物本身就具备的技能,这应该属于经过特殊的培训训练出来的。
所以,这些额外的动作定义到动物类中就不合适,也不适合直接定义到猫或者狗中,因为只有部分猫狗具备这些技能。
为了体现事物功能的扩展性,Java中就提供了接口来定义这些额外功能,并不给出具体实现,将来哪些猫狗需要被培训,只需要这部分猫狗把这些额外功能实现即可。
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
5.2 接口的特点
- A:接口用关键字interface修饰,格式:
interface 接口名 {}
- B:类实现接口用implements修饰,格式:
class 类名 implements 接口名 {}
- C:接口不能实例化(如果需要实例化,则由具体的子类实例化,即接口多态)
- D:接口的实现类(子类)
- a:是一个抽象类
- b:是一个具体类,这个类必须重写接口中的所有抽象方法
PS:由此可见具体类多态几乎没有,抽象类多态常用,接口多态最常用。
//定义动物培训接口
interface AnimalTrain {
public abstract void jump();
}
//抽象类实现接口
abstract class Dog implements AnimalTrain {
}
//具体类实现接口
class Cat implements AnimalTrain {
public void jump() {
System.out.println("猫可以跳高了");
}
}
class InterfaceDemo {
public static void main(String[] args) {
//AnimalTrain是抽象的; 无法实例化
//AnimalTrain at = new AnimalTrain();
//at.jump();
AnimalTrain at = new Cat();
at.jump();
}
}
5.3 接口的成员特点
- A:成员变量
- 只能是常量,默认修饰符为 public static final,建议:自己手动给出
- B:构造方法
- 没有构造方法,因为接口主要是扩展功能的,没有具体的存在
- C:成员方法
- 只能是抽象的,默认修饰符为 public abstract,建议:自己手动给出
interface Inter {
public int num = 10;
public final int num2 = 20;
public static final int num3 = 30;
//错误: 需要<标识符>,接口没有构造方法
//public Inter() {}
//接口方法不能带有主体
//public void show() {}
//abstract void show(); //默认public
public void show(); //默认abstract
}
//接口名+Impl这种格式是接口的实现类命名格式
/* 没有重写抽象方法
class InterImpl implements Inter {
public InterImpl() {
super();
}
}
*/
class InterImpl implements Inter {
public InterImpl() {
super();
}
public void show() {}
}
//测试类
class InterfaceDemo2 {
public static void main(String[] args) {
//创建对象
Inter i = new InterImpl();
System.out.println(i.num);//输出10
System.out.println(i.num2);//输出20
//i.num = 100;//无法为最终变量num分配值
//i.num2 = 200;//无法为最终变量num2分配值
System.out.println(Inter.num);//输出10
System.out.println(Inter.num2);//输出20
System.out.println("--------------");
}
}
5.4 类与接口之间的关系
- A:类与类:继承关系,只能单继承,可以多层继承
- B:类与接口:实现关系,可以单实现,也可以多实现,并且还可以在继承一个类的同时实现多个接口
- C:接口与接口:继承关系,可以单继承,也可以多继承
interface Father {
public abstract void show();
}
interface Mother {
public abstract void show2();
}
interface Sister extends Father,Mother {
}
//多实现
class Son implements Father,Mother {
public void show() {
System.out.println("show son");
}
public void show2() {
System.out.println("show2 son");
}
}
class InterfaceDemo3 {
public static void main(String[] args) {
//创建对象
Father f = new Son();
f.show();
//f.show2(); //报错
Mother m = new Son();
//m.show(); //报错
m.show2();
}
}
5.5 抽象类与接口的区别
- A:成员区别
- 抽象类
- 成员变量:可以变量,也可以常量
- 构造方法:有
- 成员方法:可以抽象,也可以非抽象
- 接口
- 成员变量:只可以常量
- 构造方法:无
- 成员方法:只可以抽象
- 抽象类
- B:关系区别
- 类与类:继承关系,只可以单继承
- 类与接口:实现关系,可以单实现,也可以多实现
- 接口与接口:继承,单继承,多继承
- C:设计理念区别
- 抽象类:被继承提现的是”is a”的关系。抽象类中定义的是该继承体系的共性功能
- 接口:被实现体现的是”like a”的关系。接口中定义的是该继承体系的扩展功能
5.6 接口的猫狗案例
接抽象类的猫狗案例,加入跳高的额外功能。
分析:从具体到抽象
- 猫:
- 姓名,年龄
- 吃饭,睡觉
- 狗:
- 姓名,年龄
- 吃饭,睡觉
由于有共性功能,所以,我们抽取出一个父类:动物。
- 动物:
- 姓名,年龄
- 吃饭();
- 睡觉(){}
- 猫:继承自动物
- 狗:继承自动物
跳高的额外功能是一个新的扩展功能,所以我们要定义一个接口。
- 接口:跳高
- 部分猫:实现跳高
- 部分狗:实现跳高
实现:从抽象到具体。使用:使用具体类。
//定义跳高接口
interface Jumpping {
//跳高功能
public abstract void jump();
}
//定义抽象类
abstract class Animal {
//姓名
private String name;
//年龄
private int age;
public Animal() {}
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//吃饭();
public abstract void eat();
//睡觉(){}
public void sleep() {
System.out.println("睡觉觉了");
}
}
//具体猫类
class Cat extends Animal {
public Cat(){}
public Cat(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("猫吃鱼");
}
}
//具体狗类
class Dog extends Animal {
public Dog(){}
public Dog(String name,int age) {
super(name,age);
}
public void eat() {
System.out.println("狗吃肉");
}
}
//有跳高功能的猫
class JumpCat extends Cat implements Jumpping {
public JumpCat() {}
public JumpCat(String name,int age) {
super(name,age);
}
public void jump() {
System.out.println("跳高猫");
}
}
//有跳高功能的狗
class JumpDog extends Dog implements Jumpping {
public JumpDog() {}
public JumpDog(String name,int age) {
super(name,age);
}
public void jump() {
System.out.println("跳高狗");
}
}
class InterfaceTest {
public static void main(String[] args) {
//定义跳高猫并测试
JumpCat jc = new JumpCat();
jc.setName("哆啦A梦");
jc.setAge(3);
System.out.println(jc.getName()+"---"+jc.getAge());
jc.eat();
jc.sleep();
jc.jump();
System.out.println("-----------------");
JumpCat jc2 = new JumpCat("加菲猫",2);
System.out.println(jc2.getName()+"---"+jc2.getAge());
jc2.eat();
jc2.sleep();
jc2.jump();
}
}
网友评论