Java面向对象-继承和多态
封装-解决是数据安全问题
继承-解决代码复用(代码冗余)问题
多态-解决代码扩展问题
1.1.java 中的继承
- 使用extends关键字,继承是 IS-A的关系
- 通过继承,子类可以自动拥有父类所有可访问的成员(成员变量和方法)
- Java中只支持单继承,子类只能有一个父类
- 创建子类对象的时候会一定会先执行父类的一个构造方法再执行子类构造方法,虚拟机默认去执行的是父类无参构造,如果父类没有无参构造,子类的构造中显式添加父类的有参构造方法调用
- 所有的类都默认继承了Object,Object是所有类的根基类
- instanceof可以判断是否是当前类的子类型或本类对象
if (bmw instanceof Car) {
System.out.println("bmw is a car");
}
public class SoftwareEngineer extends Employee {
private int hot;
SoftwareEngineer() {
}
SoftwareEngineer(int id, String name, char gender, double salary, int hot) {
super(id, name, gender, salary);
this.hot = hot;
}
}
1.2.super关键字
- this表示当前对象的引用,super就表示当前对象的父类的引用
- 使用super调用父类的构造,虚拟机会默认在子类构造方法中添加父类的无参构造,必须在子类构造方法的第一行
- 使用super关键字还可以访问父类成员
ProgramManager(int id, String name, char gender, double salary, String exp, double bonus) {
super(id, name, gender, salary);
this.exp = exp;
this.bonus = bonus;
}
1.3.访问修饰符
- private public protected 缺省的(default)用于修饰类,成员变量,成员方法;注意是限定访问权限
- 修饰类的时候,常用public和缺省的
修饰符 | 类内部 | 同一个包 | 子类 | 任何地方 |
---|---|---|---|---|
private | Yes | |||
缺省的(不写) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
1.4.方法重写(Override)
- 重载(overload)
- 有继承关系发生,才有重写
- 子类不满意父类的方法,重新写
- 重写方法和被重写方法具有相同的方法名,参数列表,返回值类型
- 重写方法不能比 被重写方法有更严格的访问权限
@Override
public void show() {
super.show();
System.out.print(" 关注度" + hot);
}
开闭原则:对扩展开发,对修改关闭
1.5.多态
- 多态解决代码扩展问题
- 多态的体现:继承,重写,父类引用指向子类对象
- 执行期间去判断对象的实际类型,根据实际类型去调用其重写的方法;
- 父类引用指向了子类对象,但是父类引用不能访问子类中新增的成员【向上转型】
public class Circus {
public void show(Animal animal) {
animal.play();
}
}
测试类
public static void main(String[] args) {
Circus circus = new Circus();
//父类引用指向子类对象,向上转型(uppercasting)
// 注意这里的Animal只能调用Animal类型里面的方法,而不能去调用 他的子类里面特有的方法。
Animal monkey = new Monkey();
Animal elephant = new Elephant();
Animal tiger = new Tiger();
Animal lion = new Lion();
circus.show(lion);
//向下转型
/*(强转)
Animal animal = new Animal();
Monkey monkey1 = (Monkey) animal;
*/
}
1.6.抽象类
- 有些类是不能实例化,就是用来被继承的,可以把这些类定义为抽象类
- 有些方法就是用来被重写的,不需要有方法体,可以声明为抽象方法
- 使用关键字abstract修饰
- 包含抽象方法的类必须声明为抽象类,抽象类中可以有非抽象方法
- 抽象类中可以有构造方法
public abstract class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("");
}
public abstract void play();
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;
}
}
1.7 由继承过渡到多态的马戏团小案例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sWd92Kbg-1600868374876)(E:\wmj\workproject\qy25code\0922-extends\assets\1600763028165.png)]
1.7.1 使用继承、重载、重写的方法来完成该案例
【父类】
package exercise.animal;
// 父类
public class Animals {
private String name;
private int age;
Animals() {
}
public void Animals(String name, int age) {
this.name = name;
this.age = age;
}
public void playProgrammer() {
System.out.println("小动物们表演节目");
};
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;
}
}
【Monkey】
package exercise.animal;
// 继承于父类的一个子类 Monkey
public class Monkey extends Animals {
public Monkey() {
}
@Override
public void playProgrammer() {
// super.playProgrammer();
System.out.println("表演骑单车");
}
}
【Tiger】
package exercise.animal;
// 继承于父类的一个子类 Tiger
public class Monkey extends Animals {
public Monkey() {
}
@Override
public void playProgrammer() {
// super.playProgrammer();
System.out.println("表演骑单车");
}
}
【大象】
package exercise.animal;
// 继承于父类的一个子类 Elephants
public class Elephant extends Animals {
@Override
public void playProgrammer() {
// super.playProgrammer();
System.out.println("表演喷水");
}
}
【马戏团】
package exercise.animal;
public class Circus {
/**
* 马戏团的动物们会表演节目,新建一个马戏团的类,利用【方法重载】来完成在测试类里面对各个子类中表演动作的实现(具体的动物的循环调用)
*/
public void show(Monkey monkey) {
monkey.playProgrammer();
}
public void show(Tiger tiger) {
tiger.playProgrammer();
}
public void show(Elephant elephant) {
elephant.playProgrammer();
}
}
【Test】
package exercise.animal;
public class Test {
public static void main(String[] args) {
// Animals animals = new Animals();
Circus circus = new Circus();
Monkey monkey = new Monkey();
Tiger tiger = new Tiger();
Lion lion = new Lion();
/**
* 这里对circus的调用就是利用了方法的重载,不同的时候调用了不同的参数(动物种类),
* 从而使得在测试类中能够对各个子类中的方法进行选择调用
*/
circus.show(monkey);
}
}
【结果】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ps3aqIKQ-1600868374880)(E:\wmj\workproject\qy25code\0922-extends\assets\1600764704550.png)]
1.7.2 新加入动物狮子,让其进行随意表演【多态】
【Lion】
package exercise.animal;
public class Lion extends Animals {
@Override
public void playProgrammer() {
System.out.println("我是狮子,随意表演");
}
}
如果按照目前的方法,如果要引入新的动物狮子,那么在创建了一个新的类的前提之下,还需要在 circus 的类中,再次重载 show() 方法,将 lion这个参数传入,这样就违反了代码中的【开闭原则:对扩展开发,对修改关闭】,不可以使用。
【重载 circus】
package exercise.animal;
public class Circus {
/**
* 马戏团的动物们会表演节目,新建一个马戏团的类,利用【方法重载】来完成在测试类里面对各个子类中表演动作的实现(具体的动物的循环调用)
*/
public void show(Monkey monkey) {
monkey.playProgrammer();
}
public void show(Tiger tiger) {
tiger.playProgrammer();
}
public void show(Elephant elephant) {
elephant.playProgrammer();
}
/**
* 新加入的狮子的重载,这部分修改了代码,使得代码不够健壮
*/
public void show(Lion lion) {
lion.playProgrammer();
}
}
【多态】
【circus】
package exercise.animal;
public class Circus {
public void show (Animals animals) {
animals.playProgrammer();
}
}
【多态的测试: Test1】
package exercise.animal;
public class Test1 {
public static void main(String[] args) {
Circus circus = new Circus();
Monkey monkey = new Monkey();
Tiger tiger = new Tiger();
Elephant elephant = new Elephant();
Lion lion = new Lion();
/**
* 在狮子的具体类里面,对playProgrammer的方法进行了重写,所以根据多态的特点
* 就会根据实际传入的参数,去调用实际对应的方法,这里调用了狮子的 playProgrammer 方法
*/
circus.show(lion);
}
}
1.8 final 关键字
- 凡是final修饰的都是表示不能改动的
- final可以修饰类,成员变量,方法,方法参数
- final修饰的类不能被继承,final修饰的方法表示不能被重写
- final修饰成员变量,表示值是不能改动的,在声明的时候进行赋值;可以把它当成常量来看(命名的时候全部大写)
- final修饰方法参数,表示值在方法体中不能改动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6QLSOtB-1600868374883)(E:\wmj\workproject\qy25code\0922-extends\assets\1600849428520.png)]
1.9 接口
- Java只支持单继承,使用接口 可以做到类似的多重继承
- 使用interface 关键字声明接口
- 接口是非常特殊的抽象类
- 接口特性:
- 接口中的方法默认都是 public abstract 修饰的
- 接口中的成员变量默认都是public static final 修饰
- jdk8之后接口中可以有普通方法,使用default修饰
- 实现类和接口之间使用关键字implements,可以实现多个接口
- 接口之间可以继承,接口之间可以多重继承
- 与继承类似,接口和实现类之间存在多态性
1.10【qq接口实现】
需求:模拟腾迅QQ使用接口搭建系统
为了使整套系统具备良好的维护性和扩展性,整套系统主要由一个主程序为主要
框架,所有的功能,都以插件的形式进行加载到主程序中,主程序在运行时首先会
加载插件,并会自动显示加载插件过程中的运行信息,并且主程序也可以卸载插件
的使用。
模拟分别添加QQ空间、QQ音乐、QQ游戏三个功能,并编写测试类测试加载功
能插件的过程。
QQ插件接口代码:
public interface Plugin {
String PLUGIN_NAME = "aaa";
/**
* 安装
*/
void install();
/**
* 卸载
*/
void remove();
/**
* update就是一个普通的方法,所有实现类中都自动拥有了
*/
default void update() {
System.out.println(" plugin update");
}
}
QQ音乐和QQ空间实现类
public class QqSpace implements Plugin {
@Override
public void install() {
System.out.println("qq空间正在安装中=========");
}
@Override
public void remove() {
//接口中的普通方法自动拥有了
update();
System.out.println(PLUGIN_NAME);
System.out.println("qq空间正在卸载中=========");
}
}
QQ系统
public class QqSystem {
public void load(Plugin plugin) {
plugin.install();
}
public void remove(Plugin plugin) {
plugin.remove();
}
}
测试类
public static void main(String[] args) {
QqSystem qqSystem = new QqSystem();
QqMusic qqMusic = new QqMusic();
QqSpace qqSpace = new QqSpace();
qqSystem.load(qqSpace);
}
抽象类:定义了一群类的模板,而不需要实例化的时候
接口:类具备的角色(功能)
面向接口开发
1.11 矩形、圆、图形的面积周长计算
【题目】
描述 图形 、 矩形、 圆形 三个类,每一个图形都是有计算面积与周长的方法。
1. 计算类中定义一个方法可以接收所有的图像对象,并打印图形对象的周长与面积.
2. 计算类中定义一个方法可以返回任意的图形对象
【图形】
首先由于图形包含矩形和圆两种图形的求面积,求周长的两种行为,又能用接口不用抽象类的原则,所以这里把图形写为一个接口,且其中包含两个抽象方法。
package task.task2;
public interface Graph {
double calArea();
double calPerimeter();
String getGraphName();
}
【矩形】【圆】
矩形是作为接口的一个实现类来存在的,重写了接口里面的求面积和周长的两个方法,并且定义了求面积,周长所需要的相关变量(length, width, radius),写了需要的构造方法用来传参数和后面的空构造方法的调用。
package task.task2;
public class Rectangle implements Graph {
private String name;
private double length;
private double width;
// 构造
public Rectangle() {
}
public Rectangle(String name, double length, double width) {
this.name = name;
this.length = length;
this.width = width;
}
// 求面积
@Override
public double calArea() {
return length * width;
}
// 求周长
@Override
public double calPerimeter() {
return 2 * (length + width);
}
// get set
@Override
public String getGraphName() {
return name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
package task.task2;
public class Circle implements Graph {
private String name;
private double radius;
// 构造
public Circle() {
}
public Circle(String name, double radius) {
this.name = name;
this.radius = radius;
}
// 求面积
@Override
public double calArea() {
return Math.PI * Math.pow(radius, 2);
}
// 求周长
@Override
public double calPerimeter() {
return 2 * Math.PI * radius;
}
// get set
@Override
public String getGraphName() {
return name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
}
【计算类】
在计算类内写两个需要的方法,是最难倒腾清楚的部分,这个类是一个方法实现的中间类,不进行测试,用多态,传入不同的参数的方法来对不同的值进行计算。
另外,第二部分: 定义一个方法可以返回任意的图形对象。该问题涉及到了返回接口类型的多态。
package task.task2;
public class Calculate {
// 计算面积和周长
public void printGraph(Graph graph) {
System.out.println( graph.getGraphName() +"面积:" + graph.calArea());
System.out.println( graph.getGraphName() +"周长:" + graph.calPerimeter());
}
// 定义一个可以返回任意的图形对象。
/**
*
* @param name
* @return Graph
*
* 首先由于返回的可能是两种类型,所以该方法应该定义为接口类型。
* 其次,由于传参数的过程,需要一个name变量作为过渡,父类引用指向子类对象,是返回接口类型的多态。
* 第三,返回值的简便写法,可以直接返回new的对象,return null是需要排除,
* 输入的返回值两种情况都不属于的例外情况,以免无返回值返回。
*/
public Graph returnGraph(String name) {
if("circle".equals(name)) {
return new Circle();
} else if("rectangle".equals(name)) {
return new Rectangle();
} return null;
}
}
【测试类】
-
首先要对调用求面积的方法,而求面积的方法写在Calculate的类里面,所以有
Calculate calculate = new Calculate();
-
利用 Rectangle Circus的构造方法传参数,
Circle circle = new Circle("circle", 4.5); Rectangle rectangle = new Rectangle("rectangle",4,6);
-
调用求面积,周长的方法
calculate.printGraph(circle);
- 调用传返回接口类型的方法并打印。
Graph circle1 由于不知道传进来的参数是什么类型,只能用Graph类型来接收,Graph 是抽象的并不能new 一个实例变量,所以变量写成了 circle1 .
父类的引用指向子类的对象。
Graph circle1 = calculate.returnGraph("rectangle");
System.out.println(circle1);
测试类代码
package task.task2;
public class Test {
public static void main(String[] args) {
Calculate calculate = new Calculate();
Circle circle = new Circle("circle", 4.5);
Rectangle rectangle = new Rectangle("rectangle",4,6);
calculate.printGraph(circle);
Graph circle1 = calculate.returnGraph("rectangle");
System.out.println(circle1);
// System.out.println(circle1.calArea(circle1));
}
}
的参数是什么类型,只能用Graph类型来接收,Graph 是抽象的并不能new 一个实例变量,所以变量写成了 circle1 .
父类的引用指向子类的对象。
Graph circle1 = calculate.returnGraph("rectangle");
System.out.println(circle1);
测试类代码
package task.task2;
public class Test {
public static void main(String[] args) {
Calculate calculate = new Calculate();
Circle circle = new Circle("circle", 4.5);
Rectangle rectangle = new Rectangle("rectangle",4,6);
calculate.printGraph(circle);
Graph circle1 = calculate.returnGraph("rectangle");
System.out.println(circle1);
// System.out.println(circle1.calArea(circle1));
}
}