「Head First Java」 04

Polymorphism

继承、多态、接口

Posted by Leo on February 26, 2022

多态的核心意义就在可以用父类的作为参数进行函数操作(函数调用和函数返回),极其方便。

继承

关键字extends

简单来说就是找相同的部分,认它当父亲,从而关注于新增的部分。

选择父类时也要尽可能抽象,子类可以使用父类的方法,可以加入自己的方法,也可以覆盖掉父类的方法(高级一点,or专业一点就是重写)

多个类之间可能即有共性的行为,也有共性的数据属性,此时使用类建立抽象层次更加合适

注意一点:子类可以调用父亲的方法,父亲不能调用子类的方法。

同样继承时,继承public而不能继承private

如果子类想在原有方法上添加可以使用super.function() 先行调用父亲的内容

如果不想让某个东西被继承,可以使用final,它表示是继承树的末端,不能被继承,也不可被覆盖

(还有别的方法,如可以控制存储,将类标记为非公有,这样就只会被同一个包的类当作父类;也可让类只拥有private的构造程序(Constructer)。)

重写(Override )


和父亲不同的方法,利用重写了更新

一般会用@Override进行标识,Java会从层次树的最下方找方法(最最儿子的),一般是引用变量的实例化方法,因此找到儿子的方法之后就不会向上找父亲的,可以保证方法调用的准确性。

注意,可以通过重写覆盖方法,但实例变量不会被覆盖。

规则


  • 重写时应该保证参数与返回值和之前的一样,子类要保证能够执行父类的一切

  • 不能降低方法的存储权限,只能相同或更为开方,即不能覆盖一个方法使其变为私有

重载(Overload)


两个方法的名称相同,但参数不同,可以理解就是个相同名字的不同方法。

同时也要注意,重载的返回类型可以不同,但不能只改变返回类型,参数也应该改变,但是返回类型可以相同。即,重载的核心在于参数的不同,返回类型可以自由定义。

重载时也可以自由的定义方法的存取权限

多态


当定义出一组类的父型时,可以用子型的任何类来填补任何需要或期待父型的位置

—Head First Java

引用与对象可以是不同类型,一般来说是定义引用类型为实际对象的父类,换句话说,任何extends过声明引用变量类型的对象都可以被赋值给这个引用变量。

一般使用的是数据结构(数组这种),可以使得每个成员在一个抽象父亲的前提下,又是独特的个体,面向对象的优越性啊

1
2
3
4
5
6
Animal[] animals = new Animal[5];
animals[0] = new Dog();
animals[1] = new Cat();
animals[2] = new Wolf();
animals[3] = new Hippo();
animals[4] = new Lion();

这样Java也能确保调用相同的方法时,调用的是所实例化对象的方法。

重载不属于多态

多态的实例化对象,当出现方法的重写时,用哪个方法取决于对象的创建类型(new 的那个),而非引用类型。

1
2
3
4
Course c1 = new Course();
Course c2 = new OOCourseAlpha();
c1.displayInfo();
c2.displayInfo();

其中通过c1调用的实际是Course类实现的displayInfo方法,而通过c2调用的则是OOCourseAlpla类重写的displayInfo方法,但实际上c1和c2的引用类型都是Course。 上面我们提到的这个特性,就叫做多态。

抽象类


当设定了某个父类后,我们并不希望它可以被new出来(莫得意义,还容易造成bug),将这个类转换成抽象类(关键字abstract),而编译器是不会让我们初始化抽象类的。抽象类没有值,但可以有static成员

抽象方法


它是没有实体的,代表此方法一定要被覆盖过。在某些有着继承关系但是某个行为各不相同的类上(无法给出都有意义的共同方法)很好用。

e.g,

1
public abstract void eat();

非抽象类不可以有抽象方法,但抽象类可以有非抽象方法

objec


Java所有的类都是从object类继承出来的,没有直接继承过其他类的 类都会是隐含的继承对象

所以如果不知道某个函数会返回什么类型,需要先来一个笼统的声明,就用object类吧

而object类自带的方法所有类也都会继承,也就是equal(), hashCode(), getclass(), toString()

自定义类也可以对这写方法进行改写,以满足我们的需求

向下转型和向上转型


所有都隐含继承object,那为啥不能用object替代所有(比如参数传入,函数返回),而还要具体点明呢?因为java不允许父类使用子类新创的方法,即使实例化了它,即尽管可以用父类类型名为子类占位,可以用子类对象给父类实例化,但无法调用其方法,即向上转型(Upward transformation)。也就是说一个类能用的方法和声明对象有关,与实例化对象无关。

我们假设有一个animal类,dog类继承自animal,有一个独特的方法为beFriendly()

1
2
Animal dog = new Dog(); // no error, it's legal
dog.beFriendly(); //it will raise error

特别地,当用Object当作ArrayList的成员类型时,尽管可以把任何类型都add进来,但后面get的时候变量都会转变为Object,不管原来是什么。即编译器只管引用类型,而不是对象的类型

如果想调用子类所独特的方法,要将变量名强制转型,也就是向下转型,一般为了保证转换类型的正确,不引发CkassCastException错误,需要加一个intanceof判断

1
2
3
if (o instanceof Dog) {
    Dog d = (Dog) o;
}

这边注明一点,Java不允许一个类继承多个类,以避免菱形继承

qiSUf0.png

接口


接口就类似于100%的纯抽象类

所有的方法都是抽象的

定义方式

1
2
3
public interface InterfaceName() {
    
}

接口实现

1
2
3
public class Class implements InterfaceName {
    
}

一个类可以实现多个接口,这样当接口作为类型进行传参和返回时,可以是传入的类型大大增加了

而接口与类不一样,一个类真正的类型,看的是它实例化的类型

类与抽象类的区别


可以看到类型里面全是抽象方法(也正因为如此,里面的方法并不需要再声明其为absract, 因为已经默认是了),它与抽象类的区别,我个人认为类更灵活,而抽象类更易扩展

抽象类中可以有抽象和非抽象的方法,当想要个某一家族加上同一的方法时,直接在其抽象父类(有的话)加上一个非抽象的方法就好了,如果是接口的话,在接口声明一个抽象方法后,则需对每一个实现 接口的对象都实现这个方法(如果对象中有一些继承关系就还好)。但类会受到严格只能继承一个父类的限制,在某些情况并不适合一些抽象关系的表达,接口就不一样了,毫无关联的类如果有相同的可抽象的行为,都可以实现接口(官方也提供了很多这样的接口,如序列化 的Serialiable, 线程的Runnable