(一)继承
面向对象语言的三大特征:封装,继承,多态
我们知道每种生物都会有各自的特征,但也会有一些共性,我们继承就是对这些共性来进行抽取,实现代码复用,我们把抽象出来的共性放到一个类中,把他叫做“ 父类 ”,然后在这些共性上进行扩展(加上每种生物自己的特征),这样产生新的类,就叫做子类(派生类)
继承呈现了面向对象程序设计的层次结构,主要解决了:共性的抽取,来实现代码的复用
就比如:猫和狗这两个类,他们都会吃东西,但是猫会抓老鼠,狗不会,狗会守大门,猫不会,但是他们吃东西这个特性是两者都有点,这样我们就可以把这个共性内容抽取,采用继承的思想来达到共用。
上述是一个继承图,我们可以看到cat和dog继承了animal类,子类可以复用父类中的成员,并且拥有属于自己的成员,并且我们可以看到这里体现了继承实现代码复用的好处
1)继承的语法
其实也就是extends
注:子类会把父类的成员变量或者成员方法继承到子类中,但是被private修饰的不会被继承
2)父类成员访问
1.子类父类的成员变量不同名
2.子类父类有同名成员变量
我们遵循的是如果子类有就访问子类,子类没有才访问父类,如果父类也没有就报错,如果变量同名访问自己的,如果我们想要访问父类中同名变量使用super.变量名(等一会讲)
3)super关键字
我们上述说子类父类有同名成员,在子类调用时会优先使用子类的成员,但是如果想要访问父类的,就需要使用我们的super关键字,这个关键字的作用就是在子类方法中访问父类的成员
注:super其实跟this很像,super是父类继承下来的引用,所以super跟this一样,只能在非静态方法中使用
4)子类构造方法
我们子类对象在构造时,需要先调用父类构造方法的,然后等待父类构造方法完成,才能执行子类的构造方法
如果我们不主动写父类的构造方法(super())系统会自动隐式的给我们加上
我们之前说类和对象的时候说,系统给我们的默认构造方法,只有在我们没有自己写的时候才有,当我们自己实现了一个构造方法,这个默认的构造方法就没有了,所以一旦我们自己写了一个父类构造方法,就需要我们手动的去调用,否则编译失败
在子类构造方法中super()调用父类构造方法时,必须super要是构造函数第一条语句,与this()冲突,所以两者只能有一个
5)super和this
这里来讲一下super和this的相同点和不同点
相同点:都是Java关键字
只能在类的非静态方法中使用访问非静态成员方法和字段
在构造方法中必须为构造方法中的第一条语句,所以不可以同时出现
不同点:this是当前对象的引用,super是当前对象父类的引用
在非静态成员方法中,this用来访问本类的引用,super访问父类引用
构造方法中,this用来调用本类构造方法,super用来调用父类构造方法(两者不能同时出现)
6)初始化代码块的顺序
跟之前的顺序一样 :静态代码块-->实例代码块-->构造方法,只不过父类要先于子类执行
注:静态代码块只会执行一次
7)继承方式
我们生活中,关系是很复杂的,我们上面说cat和dog继承animal,但是他们还可以继承其他父类
所以这里我们来看一下Java中支持的继承关系
还有一个就是我们刚才的dog和cat(不同类)都继承于同一个类
Java唯独不支持多继承也就是class A同时继承于class B和class C(但是接口可以解决这个问题,下篇我们再说)
8)final关键字
final是用来修饰变量,成员方法和类的
1.final修饰变量,就会把他变为常量(不可以修改)
2.修饰方法表示方法不能被重写
3.修饰类表示类不可以被继承
这里再来介绍下他的两个兄弟:finally和finalize()
finally:表示finally里面的代码无论如何都会执行(无论是报错还是抛异常都会执行)
finalize:用于内存回收前的一些清理操作
(二)多态
多态我们简单理解下就是多种形态,也就是完成一个行为,不同的对象会有不同的形态
1)多态实现条件
有三个:子类重写父类一个方法,发生向上转型,通过父类调用子类重写的方法
那接下来我们就来解析一下这三个条件
2)重写
重写也叫覆盖,就是子类对父类非静态,非private修饰,非final修饰,非构造方法等实现过程进行重写编写,函数名,返回值和形参都不变,只有函数体改变
他的好处就是子类可以根据需要,定义属于自己的行为
重写规则总结:
1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
2.被重写的方法返回值类型可以不同,但是必须是具有父子关系的
3.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方 法就不能声明为 protected
4.父类被static、private修饰的方法、构造方法都不能被重写。
5.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写.
其实很好理解,重写除了方法体不同其余都相同,重载只有方法名相同其余都可以不同
这里来简单介绍一下静态绑定和动态绑定
静态绑定:代码的编译时能够清楚的知道要执行哪一个方法
动态绑定:代码的编译时不能够清楚的知道要执行哪一个方法,只有在运行时才可以知道
3)向上转型
其实就是实例化一个子对象但是由父类引用接收
向上转型也有三种使用方式:直接赋值
方法传参
方法返回
向上转型的优点:让代码实现更加简单灵活
缺点:向上转型后就不可以使用子类的特有方法了,如果我们想使用就需要使用向下转型
向下转型
在我们向上转型后,我们发现是调用不了自己的方法的所以我们可以强转回Dog类
但是这里有个问题,我们还有一个Cat类继承这个animal,cat也有自己的方法,那我们该怎么办?如果转为Dog类会有什么问题?我们怎么解决?
我们发现抛异常,因为我们向下转型animal转为Dog了,所以向下转型充满了不确定性,我们需要判断实际上animal这个引用,引用的究竟是哪一个类(也就是我们要判断他的类型)
这里可以使用instanceof
这样我们就可以很好判断类型,然后针对化的进行向下转型
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。
之后我们多态就只剩最后一步,通过父类引用调用子类重写的方法,这一步应该都会吧,就不说了
4)多态代码演示
讲完了上面三步,我们就可以使用好多态了
class Animal{int age;String name;public void say(){System.out.println("说话!!!!");}
}
class Dog extends Animal{public void say(){System.out.println(getClass()+" 狗在说话!!!!");}
}
class Cat extends Animal{public void say(){System.out.println(getClass()+" 猫在说话!!!!");}
}
public class Text {public static void func(Animal animal){animal.say();}public static void main(String[] args) {Animal animal = new Cat();animal.say();func(new Dog());}
}
5)多态的优缺点
1.能够避免使用大量的“if else”
例如我们现在要打印多个形状
public static void drawShapes() {Rect rect = new Rect();Cycle cycle = new Cycle();Flower flower = new Flower();String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};for (String shape : shapes) {if (shape.equals("cycle")) {cycle.draw();} else if (shape.equals("rect")) {rect.draw();} else if (shape.equals("flower")) {flower.draw();}}
}
这样就需要我们重复多次的判断
如果使用多态,我们代码就可变成这样
2.可扩展性强
就还拿上面打印形状举例,如果我们新增一个形状,当我们没有使用多态时,我们就需要再添一个if-else,如果我们使用多态,就什么都不用做
多态缺点:
1.属性是没有多态的
当父类子类有同名属性时,通过父类引用,只能引用父类自己的成员属性
2.构造方法们没有多态
class B {public B() {// do nothingfunc();}public void func() {System.out.println("B.func()");}
}class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}
}public class Test {public static void main(String[] args) {D d = new D();}
}
我们看上面这个代码,这个代码是有坑存在的 提问:num最后输出为多少?
我们来分析下这个代码,首先new D()会先调用构造方法,然后构造方法中会隐式调用父类构造方法,然后父类构造方法中调用func()(但是这里发生多态,我们实际上new的是D所以会调用D类中的func函数)但是这里我们D的构造方法还没有执行完,num还没有赋1,所以num打印0
所以我们尽量不要再构造方法中调用方法(如果这个方法被子类重写就会触发动态绑定,此时子类对象并没有构造完成,一些值还不正确)