继承和多态@继承和多态
继承
概念
继承就是对不同类的共性进行抽取,吧抽取出来的共性放到父类(基类/超类)当中,使用子类来继承,进而实现了代码的复用。
比如:猫和狗,都属于动物,动物是一个父类,猫和狗为子类。
关键字extends
用于实现继承。
语法形式:修饰符 class 子类 extends 父类{。。。。},如public class Cat extens Animal{。。。}
Tips
(1)子类会将父类中的成员变量或者成员方法继承到了子类中;
(2)子类继承父类之后,必须添加自己特有的成员,才能体现出与基类的不同,否则就没有继承的意义。
子类对父类的访问
(1)子类和父类不存在同名成员变量,直接在子类中使用,如下所示:
public class Base {
int a;
int b;
}
class Derived extends Base{
int c;
public void method(){
a = 10;
b = 20;
c = 30;
}
}
(2)子类和父类成员变量同名,在子类的方法中或者通过子类对象访问成员变量时:
1)如果访问的成员变量子类中存在,则优先访问做自己子类的成员变量;
2)如果访问的成员变量子类中不存在,则访问从父类继承下来的,如果父类也不存在则会报错;
3)如果访问的成员变量子类、父类都存在,即同名,则优先访问子类自己的;
4)如果想访问父类的,就使用super.的方式访问。
public class Base {
int a;
int b;
int c;
}
public class Derived extends Base{
int a;
char b;
public void method(){
a = 100;
b = 101;
super.b=99;
c = 102;
}
}
成员方法同成员变量相同的道理,只是成员方法还需要看参数列表来区分方法是否完全相同。
super
1.同this一样,只能在非静态方法中使用;
2.在子类方法中,访问父类的成员变量和方法
super与this
·相同点
1.都是Java中的关键字;
2.只能在类的非静态方法中使用,用来访问非静态成员方法和字段;
3.在构造方法中调用时,必须是构造方法中的第一条语句,并且两个关键字不能同时存在。
·不同点
1.this是当前对象的引用,当前对象即调用实例方法的对象;super相当于是子类对象从父类继承下来部分成员的引用。可以将super看成是this的特殊情况。
2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。
3.在构造方法中,this(...)用于调用本类构造方法,super(...)用来调用父类构造方法,两种调用不能同时在构造方法中出现。
4.构造方法中一定存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。
子类构造方法
每个类都存在构造方法。在继承中,实例化子类对象时,父类需要在子类之前完成构造,即当子类对象构造时,需要先调用父类的构造方法,然后再执行子类的构造方法,所以父类的构造方法必须放在子类构造方法代码块的第一行。
1.父类的构造方法无参数:可以不写,编译器会自动生成
public class Base {
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base{
public Derived(){
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
结果打印:
Base()
Derived()
2.父类的构造方法有参数:此时需要手动调用父类的构造方法放在子类构造方法代码块的第一行,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增的成员初始化完整。调用方法为super(参数1,参数2,...)。
代码块
代码块分为普通代码块、实例/构造代码块以及静态代码块。程序中执行顺序为:
静态代码块>实例代码块>调用的对应的构造方法
具体为:
1.静态代码块先执行,并且只执行一次,在类加载阶段执行
2.当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后执行构造方法
而在继承中,也就是存在子类与父类时,静态代码块、实例代码块以及构造方法执行的顺序为:
1.父类静态代码块;
2.子类静态代码块;
3.父类实例代码块;
4.父类构造方法;
5.子类实例代码块和构造方法。
另外,需要注意!!!!在第二次实例化子类对象时,父类和子类的静态代码块都不会再执行。
protected关键字
关键字及对应范围 | private | default | protected | public |
---|
同一包中的同一类 | √ | √ | √ | √ |
同一包中的不同类 | | √ | √ | √ |
不同包中的子类 | | | √ | √ |
不同包中的非子类 | | | | √ |
可以看出,protected关键字是在继承时使用的,其访问权限为同包以及不同包的父子类关系。
final关键字
在继承中存在单继承、多层继承以及不同类继承于同一类,但是在Java中不支持同一个类继承于多个类。随着继承层数的增加,代码复杂度会增加,为了防止继承层数的无限增加,使用**final**关键字进行控制。一般希望不出现超过三层的继承关系。
final关键字的作用:
1.用于修饰变量或者字段,将变量变成一个常量,不允许其值的修改;
2.用于修饰方法,表示不允许对该方法进行重写;
3.用于修饰类,表示不允许该类被继承。
如String字符串类,就是使用final修饰的,不能被继承。
如:
final class Animal{
String name;
int age;
public Animal(){
System.out.println("不含参数的构造方法!");
}
}
class Cat extends Animal{
}
继承与组合
组合与继承都是表达类之间关系的方式,也能够达到代码复用的小狗,但是组合并没有涉及到特殊的语法(诸如继承的extends关键字),仅仅是将一个类的实例作为另外一个类的字段。
继承表示类之间的关系是**is-a**的关系,比如狗是动物,猫是动物。
组合表示对象之间是**has-a**的关系,比如学校里面有学生、学校里面有老师,比如汽车,汽车有车轮、转向器、发动机等。
能用组合尽量用组合。
多态
概念
多态通俗说就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态,比如动物的吃饭,对应于猫是吃猫粮,狗是吃狗粮。
多态实现条件
1.必须在继承体系下
2.子类必须要对父类中大方法进行重写
3.通过父类的引用调用重写的方法
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 + "在吃饭");
}
}
class Cat extends Animal{
public Cat(String name,int age){
super(name,age);
}
public void eat(){
System.out.println(name + "在吃猫粮");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
public void eat(){
System.out.println(name + "在吃狗粮");
}
}
public class Test1{
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("奶茶",3);
Dog dog = new Dog("康康",5);
eat(cat);
eat(dog);
}
}
奶茶在吃猫粮
康康在吃狗粮
通过上述代码可以看出,在使用eat(Animal a)方法时,不管a这个引用不同,会出现不同的结果,这种现象就是多态。
重写
概念
也叫做覆盖,重写的方法其方法名、返回值、参数个数及类型都不能发生改变,如果两个互为重写关系的方法所处的类是父子关系,那么重写方法的返回值类型可以改变,但是子类中重写的方法的返回值类型范围必须是大于父类该方法的返回值类型的。继承中重写的好处在于子类可以根据需要,定义特定于自己的行为,即子类能够根据需要实现父类的方法。
不能被重写的方法:
1.静态方法,static修饰;
2.private修饰的方法;
3.final修饰的方法;(对应final关键字介绍部分)
4.构造方法不能被重写。
重写和重载的区别
区别点 | 重写 | 重载 |
---|
参数列表 | 一定不能修改 | 必须修改 |
返回类型 | 一定不能修改(除非可以构成父子类关系) | 可以修改 |
访问限定符 | 一定不能做更加严格的限制 | 可以修改 |
向上转型
实际就是创建一个子类对象,将其当成父类对象来使用。
应用场景:
1.使用父类引用来引用子类对象,即直接赋值
语法格式:父类类型 对象名 = new 子类类型()
Animal animal = new Cat("奶茶",3);
2.方法传参
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("奶茶",3);
Dog dog = new Dog("康康",5);
eat(cat);
eat(dog);
}
上面的代码中,eat方法参数的类型是Animal类型,但是在使用的使用Cat和Dog类型的对象也可以传参使用,发生了向上转型。
3.返回值
1)情况1
public static Animal eat(){
return new Dog("蓝妹",3);
}
上面的代码中,方法eat返回的是子类Dog对象,但是方法定义的返回值类型为Animal,是合法的,因为Animal类是Dog类的父类。
2)情况2
public static Animal eat(){
return new Dog("蓝妹",3);
}
public static void main(String[] args) {
Animal animal = eat();
}
上面的代码也是返回值类型的向上转型,有点类似直接赋值。
动态绑定
动态绑定也叫后期绑定:在编译时不能确定方法的行为,也即不能确定针对的是哪个类的方法,等到程序运行的时候,才能够确定调用哪个类的方法。
Animal animal = new Dog("蓝妹",12);
animal.eat()
此时的编译情况为:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/051a550342c7fb20e9c1f3b641bd6bc6.png)
最终运行之后的结果是子类Dog的eat()方法,以上过程就是动态绑定。
静态绑定
静态绑定也成为前期绑定:即在编译时,根据用户所传递实参类型就确定了调用哪个方法。典型代表:方法的重载(对同一方法名的返回值、参数个数或者参数类型进行修改)
向下转型
使用强制类型转换的方法将父类对象转换成子类对象。
Animal animal = new Dog("蓝妹",4);
Dog dog = (Dog)animal;
但是向下转型是不安全的,一旦转换失败,运行时就会抛出异常,为了提高向下转型的安全性,引入了instanceof,如果表达式为true,则可以进行安全转换。
Animal animal1 = new Cat("奶茶",4);
if (animal1 instanceof Cat){
Cat cat = (Cat) animal1;
cat.mimi();
}
在进行向下转型时,使用instanceof关键字提高安全性,当instanceof表达式为真时才进行向下转型。
总结:向上转型就是将子类对象当成父类对象使用,此时子类所特有的功能不能进行实现;而向下转型就是为了实现子类特有的功能将父类还原为子类的过程。
多态的优缺点
优点:
1.减少了if-else的使用。
class Shape{
public void draw(){
System.out.println("画图形");
}
}
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("❀"); }
}
public class Test {
static void drawShape(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape[] shapes = {new Cycle(),new Rect(),new Cycle(),new Rect(),new Flower()};
for (Shape shape : shapes){
shape.draw();
}
}
}
以上代码避免使用了if-else写法
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.可扩展能力更强,如果需要新增一种子类,使用多态的方法进行代码代码的成本较低。
缺点:代码运行效率低
1.属性没有多态性,通过父类引用只能引用父类的属性;
2.同时构造方法没有多态性。