JavaSE基础day10多态、抽象类/方法、接口
一. 多态
(一) 多态概述
1. 事物的多种表现形态就是多态
java中的多态就理解为对象的不同数据类型的体现也就是子类对象充当父类类型对象
2. 多态的发生前提:
(1) 必须要有继承或实现关系
(2) 有方法的重写
(3) 父类引用指向子类的对象
3. 格式:
父类类型 变量名 = new 子类名(实参);
4. 举例:
设计出一个Person类(表示父类)
Teacher类, Person类的子类
Doctor类, Person类的子类
class Person{}
class Teacher extends Person{}
class Doctor extends Person{}
多态表达式体现:
Person p; // 父类的引用
new Teacher(); // 子类对象
new Doctor(); // 子类对象
Person p = new Teacher();// 人类,可以是一个教师对象,多态表达式
Person p1 = new Doctor();// 人类,可以是一个医生对象,多态表达式
这即是人类的多种表现形态
(二) 多态中成员变量的访问原则
1. 原则:
编译看左,运行看左
2. 解释:
(1) 编译的时候,要看【=】左边的引用所属的类型中,是否有该变量的定义,如果有,就编译成功,如果没有,就编译失败
(2) 运行的时候,也是使用等号左边类型中的变量
(三) 多态中成员方法的访问特点
1. 原则:
编译看左,运行看右
2. 解释:
(1) 编译的时候,要看【=】左边的引用所属的类型中,是否有该方法的定义,如果有,就编译成功,如果没有,就编译失败。
(2) 运行的时候,要看【=】右边的对象所属的类型中,是如何实现这个方法的。最终运行的是子类重写过的方法实现。
(四) 向上向下转型
1、向上转型:
使用子类的引用指向子类的对象(正常情况)
多态中,使用父类的引用指向子类的对象(向上转型)
本质:缩小了对象本身的访问范围,减少了访问的权限(只能访问父类中定义的内容)
2、向下转型:
概念:
让指向子类对象的父类引用,【恢复】成子类的引用
格式:
子类类型 引用名称 = (子类类型)父类类型的引用;
本质:
【恢复】子类类型原本就有的访问范围
3、缺陷: 向下转型的时候有可能转为其他子类的类型,编译不会报错但是运行时会发生类型转换异常
4、解决方案:
1) 转之前判断一下要转换的类型是不是多态对象之前的类型。使用一个关键字 instanceof 来判断向下转换类型是不是自己的类型
2) 格式:
多态对象 instanceof 指定的数据类型 , 返回值为布尔类型的数据
public class Person { String name = "张三"; public void eat(){ System.out.println("人类都吃饭"); } }
public class Teacher extends Person{ // name属性与Person中成员属性一致 String name = "小李老师"; // sex是Teacher中的特有属性成员 String sex = "女"; @Override public void eat(){ System.out.println("教师晚上吃凉皮"); } }
public class Doctor extends Person{ @Override public void eat(){ System.out.println("医生晚上吃青菜"); } // Doctor中的特有功能 public void save(){ System.out.println("医生可以治病救人"); } }
public class Test { public static void main(String[] args) { // 1. 多态表达式: 父类引用指向子类对象 Person p = new Teacher(); System.out.println(p); // 2. 多态表达式中的成员变量使用规则: 编译看左, 运行看左 System.out.println(p.name); // System.out.println(p.sex); // 4. 因为多态向上转型表达式只能使用父类中具有成员, 因此需要向下转型, 将父类引用恢复成子类类型本身 // 就可以调用子类中的特有功能和成员 Teacher t = (Teacher)p; System.out.println(t + "------"); System.out.println(t.sex);// 女 p.eat(); Person p1 = new Doctor(); // 3. 多态表达式中成员方法使用规则: 编译看左, 运行看右 p1.eat(); Doctor d = (Doctor)p1; d.save(); // 5. 在多态向下转型过程中, 可以会出现转型错误 // 为了避免向下转型出现问题, 可以使用关键字 instanceof 判断 // 父类引用 instanceof 子类类型 : 判断这个父类引用是否可以转换成对应的子类类型, 如果可以, 返回true; 否则返回false if(p instanceof Doctor){ Doctor or = (Doctor)p; or.save(); }else { System.out.println("不能转换"); } } }
4.1 练习
定义人类和超人,分别在人类和超人类中定义名字属性和谈生意功能,并且在超人类中重写人类的谈生意功能,最后在超人类中定义特有的飞翔功能
分析:
1. 定义人类,有姓名属性, 谈生意功能
2. 定义出一个超人类, 有姓名(可以从人类中继承), 重写谈生意功能, 超人中额外定义出一个特有的飞翔功能
3. 定义出一个测试类, 通过多态的方式测试以下超人的各种功能使用
public class Man { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void dealBusiness(){ System.out.println("人类都可以去谈生意"); } }
public class SuperMan extends Man{ @Override public void dealBusiness(){ System.out.println(getName() + "在谈大生意"); } public void fly(){ System.out.println(getName() + "正在飞,等着救人"); } }
public class TestMan { public static void main(String[] args) { Man m = new SuperMan(); m.setName("猪猪侠"); m.dealBusiness(); if(m instanceof SuperMan){ SuperMan su = (SuperMan)m; su.fly(); }else { System.out.println("不能进行类型转换"); } } }
(五) 多态的好处
1、提高了代码的扩展性(灵活性)
2、在方法的参数列表中,形式参数是父类类型的引用,将来调用方法的时候,父类类型的任意子类对象,都可以作为方法的实际参数。
3、不在方法的参数列表中,就在普通的方法体中,使用父类的类型指向子类的对象,也能提高代码的可扩展性。对象的来源非常广泛,不仅仅是new出来的,(还可能是通过反射获取的,通过文件读取的,还可能是网络传递的,在写代码的编译阶段,无法知道对象具体的子类类型的)需要使用父类类型的引用,操作不知道的子类类型的对象
案例 : 有一个农场(设计成Farmer类), 农场中可以养很多动物, 农场中有一个方法功能(feed), 可以根据提供动物类型不同, 输出每种动物需要吃的食物
举例 : 给feed方法提供猫类型Cat参数, 方法输出: 猫吃鱼;
给feed方法提供狗类型Dog参数, 方法输出 : 狗啃骨头;
给feed方法提供羊类型Sheep参数, 方法输出 : 羊吃青草;
... 有无数种动物, 提供不同的动物类型, feed方法输出不同动物需要喂食的东西
分析:
1. 定义出一个农场类 Farmer, 在这个类中定义出一个方法功能feed
public void feed(){// feed方法的参数列表 : 需要一个动物, 可以是任意一个动物
// 例如 Cat c 或者 Dog d 或者 Sheep s ...
// 规律 : 需要的所以的参数, 共性, 属于动物
}
2. 经过1的分析, 需要定义出一个Animal动物类, 所有的实际动物都应该是Animal类型的子类, 于是将Animal父类类型作为feed方法的形式参数, 当调用feed方法的时候, Animal类型的任意一个子类,都可以作为方法的实际参数
3. 集合: 容器, 长度可变的容器
集合存储特点: 只存储引用数据类型; 可以存储所有的引用数据类型; 集合容器存储的时候需要在参数别表中, 支持所有的引用数据类型作为参数传递进来, 参数存储在容器中;
将所有引用类型的父类Object作为方法的形式参数
public abstract class Animal { public abstract void eat(); }
public class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼"); } }
public class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃骨头"); } }
public class Sheep extends Animal{ @Override public void eat() { System.out.println("羊吃青草"); } }
public class Farmer { // 将一个父类类型作为feed方法的形式参数 // feed实际调用时, 可以传递任意一个Animal的子类对象 public void feed(Animal a){// Animal a = new Dog(); // Animal a = new Sheep(); // Animal a = new Cat(); a.eat(); } public void useI(int i){// int i = 15; int i = 3.14; System.out.println(i); } }
public class Test { public static void main(String[] args) { Farmer f = new Farmer(); Dog d = new Dog(); Animal dog = new Dog(); f.feed(dog);// new Dog(); 狗啃骨头 Animal sheep = new Sheep(); f.feed(sheep);// new Sheep(); f.feed(new Cat()); int w = 15; f.useI(w);// 15 // f.useI(3.14); } }
二. 抽象类
(一) 抽象方法
1、抽象方法:java中只有方法声明没有方法实现并且被关键字abstract修饰的方法就是抽象方法
2、格式:
修饰符 abstract 返回值类型 方法名(参数列表);
3、作用:
提取相同功能不同结果的方法的声明
4、注意:
抽象方法只能定义在抽象类中和接口中
(二) 抽象类
1、抽象类: 可以定义抽象方法的类,就是抽象类。
2、java中被关键字abstract修饰的类就是抽象类
3、定义格式:
修饰符 abstract class 类名 {
}
// 将类使用抽象关键字修饰, 这个类就是一个抽象类 public abstract class Animal { // 抽象方法只能存在于抽象类中 public abstract void eat(); }
(三) 抽象类的特点
1、抽象类和抽象方法都需要使用abstract关键字修饰
抽象类:public abstract class {}
抽象方法:public abstract void test();
2、抽象类和抽象方法的关系:
抽象方法所在的类必须是抽象类
抽象类中未必一定都定义抽象方法,抽象类中未必存在抽象方法
3、抽象类的实例化(创建对象)
抽象类不能直接实例化
定义抽象类的子类,由子类创建对象,调用方法
注意 : 抽象类存在的意义就是为了有子类继承, 重写抽象方法
4、抽象类子类的前途
在子类中,将父类所有的抽象方法全部重写(实现),子类就成了一个普通类,就可以创建对象
在子类中,没有将父类中所有的抽象方法全部实现,子类就还是一个抽象类,还需要使用abstract关键字修饰子类。
// 2. 抽象类不能实例化对象 // 抽象类等着当父类 public abstract class AbstractClass { int i = 10; public void fun(){ System.out.println("普通方法fun"); } // 1. 抽象方法不能运行的, 因为没有方法体 // 存在意义: 为子类限定需要实现的规则 public abstract void function(); }
// 3. 抽象父类的子类前途: // 1) 子类将父类中所有抽象方法全部重写, 那么这个子类就是一个普通的类, 可以正常使用 public class AbstractClassSub1 extends AbstractClass{ @Override public void function(){ System.out.println("我是子类重写的父类抽象方法"); } }
// 抽象类的子类第二种前途: 子类没有将父类中所有抽象方法全部重写, 那么这个子类就还是一个抽象类 public abstract class AbatractClassSub2 extends AbstractClass{ }
public class TestAbstract { public static void main(String[] args) { // 1. 测试抽象类不能实例化对象 // AbstractClass ac = new AbstractClass(); // 2. 抽象子类在重写了所有抽象方法后可以正常使用 AbstractClassSub1 acs = new AbstractClassSub1(); System.out.println(acs.i); acs.fun(); acs.function(); // AbatractClassSub2 acs2 = new AbatractClassSub2(); } }
(四) 抽象类中的成员
1、成员变量:可以定义变量,也可以定义常量,但是不能被抽象
2、构造方法:有
虽然抽象类无法创建对象,但是抽象类一定有子类,子类会访问父类的抽象方法。
是否有构造方法,不取决于是否可以创建对象,而是取决于是否可以定义成员变量。如果可以定义成员变量,那么就需要初始化成员变量,就是构造方法来完成的。
3、成员方法:
既可以是抽象方法:强制子类重写
也可以是非抽象方法:用于给子类继承,提高代码的复用性
public abstract class Demo01_抽象类成员组成 { // 普通类 + 抽象方法 private String name; /* 一个类型中是否可以定义构造方法, 与这个类型能否创建对象无关; 与是否可以定义成员变量有关, 如果类型中可以定义成员变量, 那么就可以定义构造方法 */ public Demo01_抽象类成员组成(){ } public Demo01_抽象类成员组成(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public abstract void fun(); }
(五 ) 员工类练习
程序员类:属性(姓名、工号、工资、奖金),行为(工作:软件开发)
测试工程师:属性(姓名、工号、工资),行为(工作:软件测试)
项目经理类:属性(姓名、工号、工资、奖金),行为(工作:控制进度)
使用继承思想实现
分析:
1. 上述三个类,都是属于员工, 因此将三个类的共性抽取到父类Employee员工类中
属性: 姓名, 工号, 工资
功能: 工作
public abstract class Employee { private String name; private String id; private double salary; public Employee() { } public Employee(String name, String id, double salary) { this.name = name; this.id = id; this.salary = salary; } public abstract void work(); public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } }
public class Programmer extends Employee{ private double jiangJin; public Programmer(){} public Programmer(String name, String id, double salary, double jiangJin) { super(name, id, salary); this.jiangJin = jiangJin; } public double getJiangJin() { return jiangJin; } public void setJiangJin(double jiangJin) { this.jiangJin = jiangJin; } @Override public void work() { System.out.println(getName() + "--" + getId() + "--" + getSalary() + "--" + jiangJin + "开发"); } }
public class TestProgrammer extends Employee{ @Override public void work() { System.out.println(getName() + "--" + getId() + "--" + getSalary() + "--测试"); } }
public class Test { public static void main(String[] args) { Programmer p = new Programmer("张三","zs001",15000,3888); p.work(); TestProgrammer tp = new TestProgrammer(); tp.setName("李四"); tp.setId("ls007"); tp.setSalary(13999); tp.work(); } }
三. 接口
(一) 接口的概述
1. 接口是java用来描述多种不同规则的集合体;
2. 规则在java中就是抽象方法,接口就是存放了不同的抽象方法的集合体
好处:
一旦将命名规则定义出来,【方法的调用】和【方法的实现】就分离开了,可以提升开发效率,降低代码的耦合性
3. java中使用关键字interface表示,接口和类同级别的
接口的源文件也是.java文件,照样参与编译,编译后的文件依然是字节码文件【.class】
2. 程序员类, 项目经理类, 特有属性 : 奖金
(二) 接口的定义特点及注意事项
1. 格式:
修饰符 interface 接口名 {接口的内容}
2. 内容:
属性:接口中的成员变量, 实际是成员常量,默认被 public static final修饰(使用接口名访问即可)
方法:JDK1.8之前, 只有抽象方法, 默认被public abstract 修饰
3. 注意事项:
(1) 没有构造方法,不能直接创建对象的
(2) 抽象方法必须需要类来实现之后被类的对象调用使用
public interface MyInterface { // JDK1.8以及之前版本中: 只有两个部分 // 1. 成员常量: 接口中所有的成员变量实际上都是使用 public static final 修饰的 // 这些修饰符写一部分还是不写, 都会默认补全或者存在 public interface MyInterface { // JDK1.8以及之前版本中: 只有两个部分 // 1. 成员常量: 接口中所有的成员变量实际上都是使用 public static final 修饰的 // 这些修饰符写一部分还是不写, 都会默认补全或者存在 public int W = 99; // 2. 抽象方法: 接口中所有抽象方法默认修饰 public abstract public abstract void eat(); void sleep(); // 3. 接口中没有构造方法,因为不能定义出成员变量 // public MyInterface(){} }
public class TestInterface { public static void main(String[] args) { System.out.println(MyInterface.W); // MyInterface.W = 888; } }
4、特别声明
在JDK1.7及之前接口中只能使用抽象方法。
在JDK1.8后接口中可以使用 默认方法和静态方法(实现功能,不需要重写)。
public interface InterfaceDemo01 { public static final String name = "接口01"; // JDK1.7 public abstract void func(); // JDK1.8后 可以有默认方法,切default唯一用法 public default void func2(){ System.out.println("默认方法"); } // JDK1.8后 可以有静态方法 public static void func3() { System.out.println("静态方法"); } }
(三) 接口实现
1. 接口书写好之后里面的规则要想被使用,需要类来重写,使类拥有接口中的规则功能,如此类和接口就发生关联关系, 类去重写接口规则的过程就叫做实现接口
2. 类实现接口:
使用关键字implements连接类和接口
3. 格式:
类 implements 接口名1,接口名2....{类的内容}
4. 接口的实现类前途:
是一个抽象类,该类没有实现接口中的所有抽象方法
是一个普通类,该类实现了接口中的所有抽象方法
5. 单实现:
一个类实现了一个接口的实现方式
类 implements 接口名{
重写接口中所有的抽象方法
}
6. 多实现:
一个类同时实现多个不同接口的实现方式就是多实现
类 implements 接口名1,接口名2....{
1. 重写所有接口中所有的抽象方法
2. 不同的接口中相同方法声明的抽象方法只需要重写一次
}
接口单实现
public interface MyInterface { // JDK1.8以及之前版本中: 只有两个部分 // 1. 成员常量: 接口中所有的成员变量实际上都是使用 public static final 修饰的 // 这些修饰符写一部分还是不写, 都会默认补全或者存在 public int W = 99; // 2. 抽象方法: 接口中所有抽象方法默认修饰 public abstract public abstract void eat(); void sleep(); // 3. 接口中没有构造方法,因为不能定义出成员变量 // public MyInterface(){} }
// 1. 一般来讲, 接口的实现类, 命名: 接口名 + Impl, 表达与接口的关系, 提高代码的可阅读性 public class MyInterfaceImpl implements MyInterface{ @Override public void eat() { System.out.println("实现的接口中的抽象方法eat"); } @Override public void sleep(){ System.out.println("实现的接口中的抽象方法sleep"); } }
public abstract class MyInterfaceImpl2 implements MyInterface{ }
public class TestInterface { public static void main(String[] args) { System.out.println(MyInterface.W); // MyInterface.W = 888; // 1. 测试接口不能实例化对象 // MyInterface my = new MyInterface(); // 2. 测试重写了所有抽象方法接口的实现类使用 MyInterfaceImpl my = new MyInterfaceImpl(); System.out.println(MyInterfaceImpl.W); my.eat(); my.sleep(); } }
public interface A { // A接口中的eat方法与MyInterface接口中的A方法一致 public abstract void eat(); }
public class ImplAll implements A,MyInterface{ @Override public void eat() { System.out.println("从写的eat"); } @Override public void sleep() { System.out.println("重写的sleep"); } }
(四) 类与类、类与接口、接口与接口之间的关系
1、类与类
继承的关系,使用extends关键字
可以单继承、不可以多继承、可以多层继承
2、类与接口:
实现关系,使用implements关键字
java中有单实现和多实现
多实现没有安全隐患:即使两个接口中有一样的方法声明,但是在类中也只有一个实现
3、在继承一个父类的前提下,还可以实现多个接口
格式:
class 子类类名 extends 父类类名 implements 接口1, 接口2.....{
//重写父类和所有接口中的所有抽象方法
}
注意 : 父优先
4、接口与接口:
继承关系,使用extends
可以单继承、也可以多继承、可以多层继承
多继承的格式:
interface 接口名 extends 父接口1, 父接口2.....{
相当于继承了所有父接口的所有抽象方法
}
5、类和接口的区别(设计区别):
抽象类:定义事物本身具有的固有属性和行为
接口:定义事物通过学习、训练而扩展出来的行为
继承一个父类同时实现多个接口代码
public interface A { public abstract void eat(); }
public interface B { void sleep(); }
public class Fu { public void eat(){ System.out.println("fu类让zi类吃饭"); } }
public class Zi extends Fu implements A,B{ @Override public void sleep() { System.out.println("sleep"); } }
public class TestfuAndZi { public static void main(String[] args) { Zi z = new Zi(); z.eat(); z.sleep(); } }
接口与接口的关系
public interface C extends A,B{ // C接口中同时拥有A和B两个接口中的所有抽象方法 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!