继承:对共性的一个抽取,使用extends关键字来实现,
语法 A extends B
A 是 子类
B 是 父类
A is B
意义:为了代码的重复使用
注意:
1.子类继承了父类,那么子类在构造时,要先帮助父类进行构造,需要在子类的构造方法当中,
‘ ’使用super关键字来显示(int a =10;显示就是简单而直观)的调用父类的构造方法
2. super 和 this 的 区别(super针对父类,this 针对当前的类)
3. 重写 和 重载的区别 (
3.1重载:参数类型和个数至少有一个不同,返回值不做限制;重写:参数的类型和个数都相同,除特殊情况外,返回值相同
3.2 重载:针对一个类;重写:针对 子类(不限于一个类)
4. 讲解了访问修饰限定词的访问权限范围。
4.1 privatae : 仅限一个类
4.1.1 在类中 一个成员变量 和 方法,如果被 private 所修饰,那么在该类被继承时,会不会被继承到子类当中。
我更偏向于不会,因为在子类中不能通过super.data 去访问它,而且编译器在 super后面加点,也点不出来。(这个更权威一些,有理有据)
说会的,意思是 被private修饰的成员变量 和 方法,虽然无法访问和调用,但是还是被继承下来了。
4.2 default(包的访问权限):限定在一个包内,就是字段/属性/成员变量,在不加任何修饰限定词的前提下,该成员变量默认时 包的访问权限
4.3 protected :在一个包里可以随意使用,在不同包里,必须是子类才能访问
4.4 public : 哪里都能用
在了解多态之前,我需要搞懂 什么向上转型,什么是运行时绑定。
1.向上转型:父类引用 引用 子类对象
2、运行时绑定(动态绑定):通过父类引用 调用 父类和子类 同名的覆盖方法。(覆写 == 覆盖 == 重写)
2.1 方法名称相同
2.2 参数的类型和个数都相同
2.3 返回值相同 【特殊:返回值也可以是协变类型】
注意事项:
1。 static 方法不能重写
2. private 修饰的方法不能重写
3. final 修饰的方法不能重写
4. 子类方法的修饰限定 访问的范围 大于等于 父类方法的修饰限定(子类方法的访问权限要大于等于父类方法的访问权限)
3. 编译时绑定(静态绑定):通过方法的重载实现的。编译的时候,会根据你调用方法时,所给参数的类型和个数,来决定调用哪个同名方法
4. 向下转型: 子类引用 引用父类对象,但不安全,不建议使用。
有了面的向上转型, 动态绑定, 方法重写之后, 我们就可以使用 多态(Polymorphism) 的形式来设计程序了
class Shape{// 无论是 三角形,还是正方形等等,它们都是图形
// 以此作为共性抽出
public void draw(){
System.out.println("Shape::draw()");
}
}
// 矩形
class Rect extends Shape{
@Override// 当我们在子类中,重写了父类中的方法。那么父类方法中的输出语句就显得毫无意义
// 最后都是输出子类draw方法,如果你想的话,可以删掉父类的 输出语句
public void draw(){
System.out.println("♦");
}
}
// 花
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
class Shape{// 无论是 三角形,还是正方形等等,它们都是图形
// 以此作为共性抽出
public void draw(){
System.out.println("Shape::draw()");
}
}
// 矩形
class Rect extends Shape{
@Override// 当我们在子类中,重写了父类中的方法。那么父类方法中的输出语句就显得毫无意义
// 最后都是输出子类draw方法,如果你想的话,可以删掉父类的 输出语句
public void draw(){
System.out.println("♦");
}
}
// 花
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
public class Test {
public static void main(String[] args) {
Rect rect = new Rect();
rect.draw();// 这里是单纯,new对象,访问普通成员方法
// 使用向上转型,重写,父类当中 draw 方法。
// 使父类的draw有了另一种实现的方法,
// 这种情况,被称为动态绑定。
// 在不断重写父类draw方法,呈现draw方法的多态的实现
// 这就是我们所说的多态
Shape shape = new Rect();
shape.draw();
Shape shape1 = new Flower();
shape1.draw();
}
}
class Shape{// 无论是 三角形,还是正方形等等,它们都是图形
// 以此作为共性抽出
public void draw(){
System.out.println("Shape::draw()");
}
}
// 矩形
class Rect extends Shape{
@Override// 当我们在子类中,重写了父类中的方法。那么父类方法中的输出语句就显得毫无意义
// 最后都是输出子类draw方法,如果你想的话,可以删掉父类的 输出语句
public void draw(){
System.out.println("♦");
}
}
// 花
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
public class Test {
public static void paint(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
paint(rect);
paint(new Rect());
System.out.println("=============");
Flower flower = new Flower();
paint(flower);
paint(new Flower());
}
}
从另一个方面来说:通过一个引用来调用不同的draw方法,会呈现出不同的表现形式。表现的形式取决于将来它引用那个对象。这就是动态。而且实现多态的大前提,就是一定要向上转型,且实现 父类和子类的重写方法。
在这个代码中, 上方的代码(矩形、花、继承)是 类的实现者 编写的, 下方的代码(main所在的类)是 类的调用者 编写的。
当类的调用者在编写 Paint 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现。(和 shape 对应的实例相关), 这种行为就称为 多态。
多态 顾名思义, 就是 “一个引用, 能表现出多种不同形态”。
多态是面向对象程序设计中比较难理解的部分. 我们会在后面的抽象类和接口中进一步体会多态的使用. 重点是多态带来的编码上的好处.
另一方面,
如果抛开 Java, 多态其实是一个更广泛的概念, 和 "继承" 这样的语法并没有必然的联系.
C++ 中的 "动态多态" 和 Java 的多态类似. 但是 C++ 还有一种 "静态多态"(模板), 就和继承体系没有关系了.
Python 中的多态体现的是 "鸭子类型", 也和继承体系没有关系.
Go 语言中没有 "继承" 这样的概念, 同样也能表示多态.
无论是哪种编程语言, 多态的核心都是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式.
1. 类调用者对类的使用成本进一步降低。
封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可。因此,多态可以理解成是封装的更进一步, 让类的调用者对类的使用成本进一步降低.
这也贴合了 <<代码大全>> 中关于 “管理代码复杂程度” 的初衷.
2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
3. 可扩展能力更强.:如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解.
而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度".
如果一个方法的圈复杂度太高, 就需要考虑重构
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10
在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由
Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract
method), 包含抽象方法的类我们称为 抽象类(abstract class)
abstract class Shape {
abstract public void draw();
}
在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.
abstract class Shape{
// 凡是 一个类中,包含抽象方法,就要改类前面加上 abstract 修饰,叫做抽象类。
public abstract void draw();// 加上分号,代表没有不实现任何现象
// 在 public 后面加上 abstract 这样它就变成了一个抽象方法
}
有人可能会说,又不能new,里面就算能存储方法和变量,也不能用,那我们要它干什么用,徒增脱发??
另外:继承抽象类的普通类当中 需要重写所有的抽象方法,上面重写一个,只是因为 被abstract修饰的方法只有一个,
既然知道了 抽象类不能被 final修饰的原理,那么由此推论出:抽象方法也不可以被 final修饰。.
1. 包含抽象方法的类,叫做抽象类
2. 什么是抽象方法,一个没有具体实现的方法,被 abstract 所修饰。
3. 抽象类是不可以被实例化(不能 new)
4. 由于抽象类不能实体化,所以,抽象类只能被继承
5. 抽象类当中,也可以包含和普通类一样的成员和方法
6. 一个普通类,继承了一个抽象类,那么这个普通类当中,需要重写
所有的抽象方法。
7. 抽象类的最大的作用,就是为了被继承。
8. 一个抽象类A,如果继承了另一个抽象类B,那么抽象类A可以不实现
抽象类B的抽象方法。
9. 结合第8点,当A类 再次被一个普通类所继承后,那么 A 和 B 两个
抽象类当中的抽象方法,必须被重写。
10. 抽象类 不能被 final修饰,那么由此推论出:抽象方法也不可以被
final修饰。
11.抽象方法不能是 private 的
12.抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象
方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调
用.
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
有些朋友可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验(如果你没有重写抽象方法,编译器会报错,提醒你)
&ensp;
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含
静态常量.
如果非要有实现具体方法,也行在方法前面加上 default修饰,表示 该方法是此接口的默认方法。
由此得出结论:接口当中的方法(静态,和 默认),只能通过 接口的引用来调用。
接口与接口之间,可以使用 extends来 操作它们的关系,此时,extends 在这里意为 拓展。(一个接口A 通过 extends 来拓展 另一个接口B的功能,此时当一个类 通过 implements 实现 接口A,此时重写的方法,不仅仅是 A 的抽象方法,还有从B接口,拓展来的功能【方法】)
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力
1.使用interface来定义一个接口,例:interface IA{},这就是一个接口
2. 接口当中的普通方法,不能有具体的实现。非要实现,只能通过关
键字 default 来修饰 这个方法。
3.接口当中,可以有static的方法。
4.里面所有方法都是public的。
5. 抽象方法,默认是 public abstract 的。
6. 接口是不可以被实例化的(不可以被new的)。
7. 类和接口之间的关系是通过 implements(工具)实现的。
8.当一个类 实现了 一个接口,就必须要重写接口当中的抽象方法。
9.在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例
10.接口当中的字段/属性/成员变量,默认是public static final 修饰的。
11. 当一个类实现接口之后,重写抽象方法的时候,重写方法的前面必
须加上public,因为接口中方法默认都是public的,而且重写方法的访
问权限,必须要大于等于父类当中方法的访问权限
12.一个类可以通过关键字extends继承一个抽象类或者普通类。但是
只能继承一个类。同时也可以通过implements实现多个接口。接口之
间使用逗号隔开。
13.接口与接口之间,可以使用 extends来 操作它们的关系,此时,
extends 在这里意为 拓展,而不是继承,(一个接口 通过 extends 来拓展 另一个接口的功能)