一、继承
背景:
- 在面向对象编程的时候我们会根据实际的问题来对一件事物进行抽象。在抽象的时类和属性之间会存在一定的关联,在有效解决这些关联关系时引入了继承。
实质:
- 抽取所有的类之间的共性东西,创建别的类使用这些共性的时候,直接继承自父类即可。这样就不用重复在每一个类中,写相同的方法,操作的时候更加方便。
作用:
- 能有效的进行代码重用,防止大量重复代码、减少风险、便于维护,能在父类基础上进行更好的扩展。
关键名词:
-
父类:又叫做基类或者超类,简单的理解为父亲,在这个类中的方法和属性都是可以被子类继承的。但是父类用private修饰的方法和属性不能被子类访问到。
-
子类:又叫做派生类,是父亲的孩子,能继承父亲中的方法和属性(private修饰的不能被访问到)。
-
extends关键字:在用代码表示继承的时候要用到 extends关键字。
形象理解:
- 上边讲到在继承的时候会出现父类和子类。这里我们用动物类(animal)来表示父类,用小狗类(dog)来表示子类。动物类中可以有名字属性,也可以有吃东西方法。我们在写dog类的时候就不用写和animal中相同的属性和方法,只需要让dog类来继承animal类即可,这样我们在写其他的小猫,小鸡,小鸭等类的时候,就不用重复写共同的属性和方法了。你想一想,只要是写一个动物类,这个动物一定是有名字,性别,年龄等属性。也肯定有吃饭,喝水、玩耍等方法。这时直接写一个父类(就是孩子的爸爸),直接让所有的子类继承父类方法属性,这样就少写好多重复代码,后边在进行属性,方法维护的时候也更加方便。
注意事项
- 在子类继承父类方法和属性时用的是extends关键字,格式如下: 子类 extends 父类
- 在Java中一个子类只能继承自一个父类,简单理解为一个孩子只有一个爸爸。
- 子类会继承父类所有属性和方法,但是父类中所有用public修饰的方法和属性子类能访问到,用private修饰的属性和方法子类无法访问。
- 子类的实例也包含着父类的实例,可以用super关键字获取到父类的引用。
二、封装
背景:
- 还是基于面向对象编程,在对事物进行抽象完成之后,引入了封装。
实质:
- 将事物抽象成对应的类之后,对类进行封装。就是只让自己能知道,别人在调用时候不必关心类的内部实现过程。
作用:
- 类在写出来就是为了完成更复杂的功能调用,一个类往往实现一个功能。在调用者调用这个类的时候不用关心内部实现过程,只需要按照自己的逻辑,调用需要的类来实现更复杂的功能即可。这样我们的逻辑就不会收到类的内部影响,操作起来会更方便,能较好的维护。
三、多态
含义:
- 就拿字面意思来理解,理解为多种形态。实质就是定义的一个引用到底要指向哪个实例、调用哪个实例的方法、在程序运行之前都是不确定的,体现为多种形态。只有在程序运行起来之后,才会有具体引用的指向,因此说它和程序的运行是有关的,在运行之前可能表示多种形态,就叫做多态。
形象理解:
-
比如你爱喝茶,不管哪种茶叶只要一进你嘴里你就知道它是什么品牌。这里我们默认所有的茶水颜色都一样。现在是展示自己的时候了。桌子上放有3个茶杯,分别是毛尖、龙井、普洱三种茶叶冲泡出来的,现在让你来区别一下每个杯子里是什么茶。拿眼睛是看不出来的,所以我们要品。只有在一个一个品过去之后,才知道杯子里对应的是什么茶。
-
你看将上边的逻辑抽象为代码
茶叶 1 = 龙井;
茶叶 2 = 毛尖;
茶叶 3 = 普洱; -
开始的时候你不知道是什么茶,就相当于在代码运行之前你不知道定义的引用到底指向那个实例。在你品了之后,你说1是龙井,2是毛尖,3是普洱。这就好比代码在运行了之后,就能找间具体指向的实例。
形成条件:
- 向上转型、动态绑定、方法重写
向上转型
- 先记住一句话:父类引用指向子类实例,这就是对向上转型的理解。
- 看这个代码: Animal cat = new Cat (“小黑”);代码中的cat表示的是Animal类(父类)中的一个引用(父类引用),指向Cat类(子类)的一个具体的小黑实例(子类实例)。还是父类引用指向了子类实例。
- 平时用到的:List list = new ArrayList(); List list = new LinkedList(); 等等都是对向上转型的一种体现,表现的是父类引用指向子类实例。
动态绑定:
- 字面意思就是表示的是动态的,正好符合多态中的多变的性质。实质就是在代码没运行之前定义的引用是不知道到底指向那个实例,在运行时才会动态的绑定一种实例。这个结合上边的多态的形象话理解
- 下边用代码来说明一下动态绑定。
// Animal.java父类
public class Animal {
// 属性是姓名
protected String name;
public Animal(String name) {
this.name = name;
}
// 表示的是父类方法中的吃这个方法
public void eat(String food) {
System.out.println("我是一只小动物");
System.out.println(this.name + "正在吃" + food);
}
}
// Bird.java 子类
// 子类继承父类
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void eat(String food) {
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
}
// Test.java
public class Test {
public static void main(String[] args) {
Animal animal1 = new Animal("圆圆");
animal1.eat("谷子");
Animal animal2 = new Bird("扁扁");
animal2.eat("谷子");
}
}
// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子
最终分析:我们看的是最后的结果,在test中我们先是定义了
一个animal1这个引用,在后边我们又定义了一个animal2这样
的引用。并且这两个引用的类型都是Animal型的。最后在调用方
法的时候,最终的结果却是不同的,原因就是在进行.eat方法调
用的时候,会进行动态绑定。animal1.eat 调用的方法是
Animal(父类)中的eat方法,所以在运行时,就是按父类中
的eat方法进行的;同理animal2.eat调用的是Bird(子类)
中的方法,在运行的时候绑定的是Bird中的 .eat 方法
方法重写(有专门的博客内容):
- 如同上边的在子类中有个和父类一样的eat方法,方法的参数和类型都和父类相同的,这叫做方法重写。
- 在子类中会对父类的方法进行重新定义,所以要进行重写。
多态的作用:
- 是对封装的进一步操作,更好的封装,让使用者不用关心代码的实现过程,降低使用者的成本。
- 方便进行扩展,创建一个子类方法继承父类,进行方法重写即可。
- 可以减少大量的分支语句,降低了圈复杂度。
四、综合的理解多态
下边用一段代码辅助理解多态的全过程
大家仔细看里边的注释,多态的形成条件都在代码中有详细的注释。
// 定义了一个形状的类,表示的是父类
class Shape {
public void draw() {
// 啥都不用干
}
}
// 又定义了一个子类,圆圈这个类是形状的一个子类。
// 用到了extends关键字让子类继承自父类
class Cycle extends Shape {
// 下边就是多态的形成条件啊之一,方法重写,用的是@Override
@Override
public void draw() {
System.out.println("○");
}
}
// 多个方法进行是,直接重写父类的方法即可
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
// 直接重写父类的方法即可
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
/我是分割线//
// Test.java,下边表示的是测试的代码。
public class Test {
public static void main(String[] args) {
// 下边是多态的形成条件之一,向上转型.父类的引用指向不同子类的实例
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
drawMap(shape1);
drawMap(shape2);
drawMap(shape3);
}
// 打印单个图形
public static void drawShape(Shape shape) {
// 下边又是多态的形成条件之一,动态绑定,只要在程序运行的时候会自己绑定具体的实例。
shape.draw();
}
}