目录
继承
背景
代码中创建的主类,主要是为了抽象现实中的一些事物,有的时候客观事物之间存在关联关系,那么表示成类和对象的时候也一定会存在关系。
例:猫和狗都是动物;三角形和正方形都是图形(si-a语义)
// Animal.java
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
// Cat.java
class Cat {
public String name;
public Cat(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
// Bird.java
class Bird {
public String name;
public Bird(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
public void fly() {
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
- 这个代码我们发现其中存在了大量的冗余代码.
- 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系:
此时我们就可以让 Cat 和 Bird 分别继承 Animal 类, 来达到代码重用的效果
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 调用父类的构造方法.
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
Animal 被继承的类: 父类 , 基类 或 超类 。
Cat 和 Bird:称为 子类, 派生类。
时刻牢记:
类是现实事物的抽象. 公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 类之间的继承层次不可以太复杂. 一般不希望出现超过三层的继承关系. 如果继承层
次太多, 就需要考虑对代码进行重构了.
语法规则
class 子类 extends 父类{
}
- 使用 extends 指定父类.
- Java 中一个子类只能继承一个父类 (而C++/Python支持多继承)
- 子类会继承父类的所有 public 的字段和方法
- 对于父类的 private 的字段和方法, 子类中是无法访问的.
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.
protect关键字
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.
- 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
- 对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
小结:java四种访问权限(访问修饰限定符)
- private: 类内部能访问, 类外部不能访问
- 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
- protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
- public : 类内部和和类的调用者都能使用
NO | 范围 | private | default | protected | public |
---|---|---|---|---|---|
1 | 同一包中同一类 | ✔ | ✔ | ✔ | ✔ |
2 | 同一包中不同类 | ✔ | ✔ | ✔ | |
3 | 不同包中的子类 | ✔ | ✔ | ||
4 | 不同包中的非子类 | ✔ |
final关键字
final修饰变量
- final 关键字, 修饰一个变量或者字段的时候, 表示 常量 (不能修改).
final int a = 10;
a = 20;//编译出错
final修饰方法
- 当方法被final修饰是 其子类无法重写draw()方法
final修饰类
- final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
多态
理解多态
含义
前提:1.向上转型 2.动态绑定。一种思想,其父类引用引用多个子类对象;
顾名思义, 就是 “一个引用, 能表现出多种不同形态”
代码实现
打印多种形状
class Shape {
public void draw() {
// 啥都不用干
}
}
class Cycle extends Shape {
@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();
}
}
意义
1. 类调用者对类的使用成本进一步降低.
~- 封装是让类的调用者不需要知道类的实现细节.
~- 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
不基于多态
Rect rect = new Rect();
Circle circle = new Circle();
Flower flower = new Flower();
String []shapes = {"rect","circle","flower","circle"};
for (String s:shapes
) {
if (s.equals("rect")){
rect.draw();
}else if (s.equals("circle")){
circle.draw();
}else if (s.equals("flower")){
flower.draw();
}
}
使用多态
//代码简洁其逻辑结构相对复杂
public static void main(String[] args) {
Shape [] shapes = {new Rect(),new Circle(),new Flower(),new Circle()};//此处发生向上转型
for (Shape s:shapes
) {
s.draw();
}
}
向上转型
直接赋值时
Bird bird = new Bird("圆圆");
这个代码也可以写成这个样子
Bird bird = new Bird("圆圆");
Animal bird2 = bird;
或者写成下面的方式
Animal bird2 = new Bird("圆圆");
此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为 向上转型.
方法传参时
public class Test {
public static void main(String[] args) {
Bird bird = new Bird("圆圆");
feed(bird);
}
public static void feed(Animal animal) {
animal.eat("谷子"); //形参 animal 的类型是 Animal (基类), 对应到 Bird (父类) 的实例.发生向上转型
}
}
// 执行结果
圆圆正在吃谷子
方法返回时
public class Test {
public static void main(String[] args) {
Animal animal = findMyAnimal();
}
public static Animal findMyAnimal() {
Bird bird = new Bird("圆圆");
return bird; //此时方法 findMyAnimal 返回的是一个 Animal 类型的引用, 但是实际上对应到 Bird 的实例.
}
}
动态绑定
发生条件:当子类和父类中出现同名方法的时候, 再去调用时。
代码实现:
对前面的代码稍加修改, 给 Bird 类也加上同名的 eat 方法, 并且在两个 eat 中分别加上不同的日志.
// 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("谷子");
}
}
// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子
在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.
静态 ——> 父类(先实例再构造) ——>子类(先实例再构造)
方法重写
含义
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 称为 覆写/重写/覆盖(Override).
注意事项
- 重写和重载完全不一样. 不要混淆
- 普通方法可以重写, static 修饰的静态方法不能重写.
- 重写中子类的方法的访问权限不能低于父类的方法访问权限.
- 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).
代码示例
class Shape{
public void draw(){
}
}
class Circle extends Shape{
@Override//以此标识重写方法
public void draw() {
System.out.println("画一个⭕");
}
}
重写&重载
No | 区别 | 重载(overLoad) | 覆写(overRide) |
---|---|---|---|
1 | 概念 | 方法名相同、参数类型、顺序以及个数不同 | 方法名、返回值类型、参数类型以及个数完全相同 |
2 | 范围 | 一个类 | 继承关系 |
3 | 限制 | 没有限制要求 | 被覆写的方法不能拥有比父类更严格的访问控制权限 |
向下转型
super关键字
在子类内部调用父类方法时可以使用super 关键字.super 表示获取到父类实例的引用
- 使用了 super 来调用父类的构造器
public Bird(String name) {
super(name);
}
- 使用 super 来调用父类的普通方法
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
@Override
public void eat(String food) {
// 修改代码, 让子调用父类的接口.
super.eat(food);
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
}
在这个代码中, 如果在子类的 eat 方法中直接调用 eat (不加super), 那么此时就认为是调用子类自己的 eat (也就是递归了). 而加上 super 关键字, 才是调用父类的方法.
super&this
No | 区别 | this | super |
---|---|---|---|
1 | 概念 | 访问本类型的属性和方法 | 由子类访问父类中的属性、方法 |
2 | 查找范围 | 先查找本类,如果本类没有就调用父类 | 不查找本类而直接调用父类定义 |
3 | 特殊 | 表示当前对象的引用 | 无 |
抽象类
定义
打印图形例子中, 父类 Shape 中的 draw 方法并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class).
语法规则
- 抽象类不能进行实例化 也就是:Shape shape = new Shape();
- 在抽象类当中,可以拥有和普通类一样的数据成员和方法
public int age;
public static int count;
public void func() {
} - 抽象类是可以被继承的,可以发生向上转型。。。。
- 当一个普通类继承了一个抽象类,那么注意,当前这个普通类,一定要 重写抽象类当中的抽象方法。
- 当普通类A继承了抽象类,且不想实现抽象类当中的抽象方法的时候
那么这个普通类可以被修改为抽象类A,此时就不需要进行实现了,当然你也可以实现。
如果一个普通类B,继承了这个抽象类A,此时就要实现这个抽象方法了 出来混 迟早要还的。 - 抽象方法不能是private修饰的,因为抽象方法就是用来被重写的
- 抽象类的出现 其实最大的意义就是为了被继承
代码实例
abstract class Shape{//抽象类:包含抽象方法的类 抽象类,也不能被实例化。
public abstract void draw();//1.抽象方法不能有内容 也就是{}
//2.在抽象类当中,可以拥有和普通类一样的数据成员和方法
public int age;
public static int count;
public void func(){
}
//6、抽象方法不能是private修饰的,因为抽象方法就是用来被重写的
/*private abstract void func() {
}*/
}
//3.抽象类是可以被继承的,可以发生向上转型。。。。
class Circle extends Shape{
//4.当一个普通类继承了一个抽象类,那么注意,当前这个普通类,一定要重写抽象类当中的抽象方法。
@Override
public void draw() {
}
}
//5、当普通类Flower继承了抽象类,且不想实现抽象类当中的抽象方法的时候那么这个普通类可以被修改为抽象类Flower,此时就不需要进行实现了,当然你也可以实现。
abstract class Flower extends Shape{
}
class redFlower extends Flower{
//如果一个普通类redFlower,继承了这个抽象类Flower,此时就要实现这个抽象方法了 出来混 迟早要还的。
@Override
public void draw() {
}
}
public class TestDemo {
}
意义
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
使用抽象类相当于多了一重编译器的校验.