目录
一、继承
1.1为什么要继承
一句话总结来说:就是对共性进行抽取,进行代码的复用
1.2继承概念
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
1.3继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
修饰符 class 子类 extends 父类 {
// ...
}
举例:
public class Animal{
String name;
int age;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
}
public class Dog extends Animal{
void bark(){
System.out.println(name + "汪汪汪~~~");
}
}
public class Cat extends Animal{
void mew(){
System.out.println(name + "喵喵喵~~~");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
// dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
System.out.println(dog.name);
System.out.println(dog.age);
// dog访问的eat()和sleep()方法也是从Animal中继承下来的
dog.eat();
dog.sleep();
dog.bark();
}
}
注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
1.4成员访问
public class Base {
int a;
int b;
int c;
}
public class Derived extends Base{
int a; // 与父类中成员a同名,且类型相同
char b; // 与父类中成员b同名,但类型不同
public void method(){
a = 100;
b = 101;
c = 102;
}
}
在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
1.5方法访问
public class Base {
public void methodA(){
System.out.println("Base中的methodA()");
}
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
public void methodA(int a) {
System.out.println("Derived中的method(int)方法");
}
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
}
}
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错。
1.6初始化代码块
在没有继承关系下的代码块的执行顺序是:
- 静态代码块执行
- 实例代码块执行
- 构造方法执行
- 静态代码块先执行,并且只执行一次,在类加载阶段执行
- 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
那么在继承关系下的代码块执行顺序是什么样子呢?下面我来举个例子:
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
} {
System.out.println("Person:实例代码块执行");
}
static {
System.out.println("Person:静态代码块执行");
}
}
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:构造方法执行");
} {
System.out.println("Student:实例代码块执行");
}
static {
System.out.println("Student:静态代码块执行");
}
}
public class TestDemo4 {
public static void main(String[] args) {
Student student1 = new Student("张三",19);
System.out.println("===========================");
Student student2 = new Student("xyk",20);
}
执行结果:
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行通过分析执行结果,得出以下结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
1.7继承方式
在java中只支持以下几种继承方式:
注意:Java中不支持多继承。
二、多态
1.1多态的概念
多态的概念:
- 通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
- 总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。
1.2多态实现条件
在java中要实现多态,必须要满足如下几个条件,缺一不可:
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
下面来举个例子:
public class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃鱼~~~");
}
}
public class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃骨头~~~");
}
}
public class TestAnimal {
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
eat(cat);
eat(dog);
}
}
运行结果:
元宝吃鱼~~~
元宝正在睡觉
小七吃骨头~~~
小七正在睡觉
1.3动态绑定与静态绑定
- 静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
- 动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
1.4向上转型与向下转型
向上转型:
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
Animal animal = new Cat("元宝",2);
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
向下转型:
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
1.5多态的优缺点
【使用多态的好处】
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
什么叫 "圈复杂度" ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度".如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过102. 可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低下面举个运用多态的例子:
class Shape {
//属性....
public void draw() {
System.out.println("画图形!");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("♦");
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("●");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("❀");
}
}
public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
多态缺陷:代码的运行效率降低。
1. 属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
2. 构造方法没有多态性
1.6需要注意的地方:避免在构造方法中调用重写的方法
一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果
D.func() 0
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
- 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。