多态的核心意义就在可以用父类的作为参数进行函数操作(函数调用和函数返回),极其方便。
继承
关键字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不允许一个类继承多个类,以避免菱形继承,
接口
接口就类似于100%的纯抽象类
所有的方法都是抽象的
定义方式
1
2
3
public interface InterfaceName() {
}
接口实现
1
2
3
public class Class implements InterfaceName {
}
一个类可以实现多个接口,这样当接口作为类型进行传参和返回时,可以是传入的类型大大增加了
而接口与类不一样,一个类真正的类型,看的是它实例化的类型
类与抽象类的区别
可以看到类型里面全是抽象方法(也正因为如此,里面的方法并不需要再声明其为absract
, 因为已经默认是了),它与抽象类的区别,我个人认为类更灵活,而抽象类更易扩展
抽象类中可以有抽象和非抽象的方法,当想要个某一家族加上同一的方法时,直接在其抽象父类(有的话)加上一个非抽象的方法就好了,如果是接口的话,在接口声明一个抽象方法后,则需对每一个实现 接口的对象都实现这个方法(如果对象中有一些继承关系就还好)。但类会受到严格只能继承一个父类的限制,在某些情况并不适合一些抽象关系的表达,接口就不一样了,毫无关联的类如果有相同的可抽象的行为,都可以实现接口(官方也提供了很多这样的接口,如序列化 的Serialiable
, 线程的Runnable
)