对于OOP语言而言, 有继承, 封装, 多态等多重特性, 但是最主要的就是继承, 封装和多态, 接下来我会依次进行介绍
一. 继承, 封装
“继承” 这个词对于我们来说并不陌生, 我们常常会听见继承家产, 继承家业之类的. 继承之后, 这些家产, 家业就都归继承者所有.
1. 什么是继承?
继承就是子类继承父类的一种行为.
继承的意义: 为了达到代码的复用效果
2. 怎样使用继承
(1). 继承的关键字: extends
class 子类 extends 父类 {
// 属性
// 方法
}
extends关键字的左右关系如下:
例如:
class Animal {
public String name;
public void eat() {
System.out.println(this.name + "Animal :: eat()");
}
}
class Cat extends Animal {
// 继承了Animal的属性和方法(name, eat() )
}
class Bird extends Animal {
// 继承了Animal的属性和方法(name, eat() )
public void fly() {
System.out.println(this.name + "Bird :: fly()");
}
}
由以上可知, Animal 是父类, 而 Cat 和 Bird 是子类, 它们都继承了 Animal 的特性.
(2). 继承使用时的注意事项:
- 子类继承父类除构造方法以外的所有
- 在 Java 中, 一个子类只能继承一个父类
- 子类在构造的时候, 要先帮父类进行构造(重点)
class Animal {
public String name;
// 构造方法
public Animal(String name) {
this.name = name;
System.out.println("Animal(String)");
}
public void eat() {
System.out.println(this.name + "Animal :: eat()");
}
}
class Cat extends Animal {
public Cat(String name) {
// 帮父类进行构造
super(name);
System.out.println("Cat(String)");
}
}
我们知道, 一个对象的创建分为两步: 1. 给对象分配内存; 2. 调用合适的构造方法.
Cat 在创建对象的时候, 需要调用自己的构造方法. 在创建自己的构造方法时, 需要先使用关键字 super 调用父类 Animal 的构造方法帮助Animal先构造, 然后再进行自己的构造.
这里引入了关键字 super , 这也很容易让我们联想到 this 关键字.
this 和 super 的区别是什么呢?
this | super |
---|---|
当前对象的引用 | 父类对象的引用 |
this( ): 调用当前类中其他的构造方法(必须放在第一行) | super( ); 调用父类的构造方法(必须放在第一行) |
this.data; 调用当前类中的属性 | super.data: 访问父类的属性 |
this.func: 调用当前类中的成员方法 | super.func: 调用父类的成员方法 |
3. 继承中的访问修饰限定符
访问修饰限定符: private, default(默认), protected, public
我们知道, private是用于封装的访问修饰限定符, 一个属性或者方法被private修饰后, 只能在当前类内访问.
如果父类中的属性或者方法被private修饰之后, 子类就无法访问到父类中的这些方法了.
那么, 一个代码, 既要体现出封装性, 又要体现出继承性, 该怎么办? protected就很好的解决了这个问题
protected 关键字主要就体现在继承上
Java中的四种访问修饰限定符
访问修饰限定符的访问权限: private < default < protected < public
值得注意的是: 不同包中的子类可以访问父类的 protected 属性或方法. 但是应该使用 super 去访问.
public class TestMain {
protected int b;
}
public class TestDemo extends TestMain {
public void func() {
TestMain testMain = new TestMain();
System.out.println(super.b); // 使用super来访问父类中protected的属性
}
public static void main(String[] args) {
TestMain testMain = new TestMain();
// System.out.println(testMain.b); // error 访问方式不对
}
}
4. 多层继承
一个父类可以派生出许多子类, 子类还可以继续派生子类, 这就是多层继承
一般情况下, 继承最多为三层继承
(1). final 关键字
如果一个类不想再被继承的话,直接在类前加上 final 关键字即可
final关键字:
- final 修饰一个变量: 表示的是一个常量, 只能被初始化一次, 接下来就不能再修改了
- final 修饰一个类: 密封类, 一旦这个类被 final 修饰, 那他必然不能再被继承
- final 修饰一个方法: 密封方法, 一旦这个方法被 final 修饰, 那他就不能再修改
二. 组合
组合在程序中就是 has-a 的关系
class Student {
public String name;
public String id;
}
class Teacher {
public String name;
}
public class School {
public Student student;
public Teacher teacher;
}
因为学校是有老师和学生的, 所以 School 和 Student, Teacher 就是组合的关系.
三. 多态
1. 向上转型
父类引用 引用子类对象 (把子类赋值给父类)
// 发生了向上转型
Animal animal = new Cat();
Animal animal = new Bird();
发生向上转型的机遇:
- 直接赋值
- 传参
- 返回值
class Animal {
public String name;
public Animal(String name) {
this.name = name;
System.out.println("Animal(String)");
}
public void eat() {
System.out.println(this.name + "Animal :: eat");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
System.out.println("Cat(String)");
}
}
public class TestMain {
public static Animal func() {
Cat cat = new Cat("mimi");
return cat;
}
public static void main(String[] args) {
// 3. 返回值
Animal animal = func();
animal.eat();
}
public static void func(Animal animal) {
animal.eat();
}
public static void main2(String[] args) {
// 2. 传参
Cat cat = new Cat("mimi");
func(cat);
}
public static void main1(String[] args) {
// 1. 直接赋值
Animal animal = new Cat("mimi");
animal.eat();
}
}
得到的结果:
Animal(String)
Cat(String)
mimiAnimal :: eat
通过上面的代码, 可以看出, animal 调用的 eat( ) 方法均为 Animal 自身的方法
2. 运行时绑定 / 动态绑定 / 多态
class Animal {
public String name;
public Animal(String name) {
this.name = name;
System.out.println("Animal(String)");
}
public void eat() {
System.out.println(this.name + "Animal :: eat");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
System.out.println("Cat(String)");
}
@Override // 重写
public void eat() {
System.out.println(this.name + "Cat :: eat()");
}
}
public class TestMain {
public static void main(String[] args) {
Animal animal = new Cat("mimi");
animal.eat();
}
}
运行结果:
Animal(String)
Cat(String)
mimiCat :: eat()
同样是使用 animal 调用 eat( ) 方法, 这是却调用的是 Cat 的 eat( ) 方法, 这是因为在 Cat 的类中, 重写了 eat( ) 方法, 使得程序发生了运行时绑定.
编译时还访问的是 Animal 里的 eat( ) 方法, 运行时却调用的是 Cat 里的 eat( ) 方法.
3. 重写 / 覆盖 / 覆写(override)
注意事项:
- 需要重写的方法, 一定不能是被 final 修饰的. 如果是 final 修饰的方法, 就是密封方法,不能进行修改
- 被重写的方法, 访问限定修饰符一定不能是private
- 被重写的方法, 子类的访问限定权限一定要大于等于父类的访问限定权限
- 被 static 修饰的方法是不能进行重写的
重写 和 重载 的区别
重载 | 重写 |
---|---|
方法名相同 | 方法名相同 |
参数列表不同(参数的个数, 类型) | 参数列表相同 |
返回值不做要求 | 返回值相同 |
前提: 在同一个类中 | 前提: 在不同的类中, 且具有继承的关系 |
4.向下转型(极其不安全)
class Bird extends Animal {
public Bird(String name) {
super(name);
System.out.println("Bird(String)");
}
public void fly() {
System.out.println(this.name + "Bird :: fly()");
}
}
public class TestMain {
public static void main(String[] args) {
Animal animal = new Bird("八哥");
// animal.fly(); // error fly不是animal的方法
// 发生了向下转型
Bird bird = (Bird)animal;
bird.fly();
}
由于向下转型是极其不安全的, 因此, 我们会较少的使用向下转型.
若要使用向下转型, 最好加上 instanceof 关键字
A instanceof B : 判断 A 是不是 B 的一个实例
public static void main(String[] args) {
Animal animal = new Cat("mimi");
if(animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
} else {
return;
}
}
显然, 这里一定返回空, 因为 animal 不是 Bird 的实例.