继承与多态
实际上,我们在Java中定义的每个类都是从隐式地从java.lang.Object类中继承而来的。
超类和子类(Superclasses and Subclasses):
声明子类为继承的方式如下:
public class SUBCLASS extends SUPERCLASS{
}
super关键字:
类似与
this关键字一样,
super关键字是指向父类的。
这个关键字主要是子类在构造函数中能够调用父类的构造函数的唯一方法。(因为在继承的时候,父类的构造函数是不会继承给子类的)
在子类的构造函数中,若要使用super(arguments)来调用父类的构造函数,则应当将其作为第一条语句。
实际上,如果不在子类中显式地调用父类的构造函数,在子类的构造函数中会自动将在第一条语句之前加上super()来调用不带参数的父类构造函数。
所以,如果当一个类是被设计用来派生出子类的话,那么这个类最好要显式地写出不带参数的构造函数,否则可能会子类继承的时候无法编译。
当然
super关键字也可以用于调用父类的其他成员,虽然看起来不是很有必要这样使用,但是当子类中有成员*
覆盖(Override)了父类的成员的话,在调用父类成员时,
super关键字就是必要的了。
只有当父类中的成员对于子类来说是可以访问的,这样的成员才肯能被子类的成员覆盖,否则二者是不相关的。
特别的,一个
static成员也可以被继承,但是不能被覆盖。如果父类中的一个
static成员在子类中被以相同的方式再次声明了一遍,则父类中对应的那个成员就会被隐藏掉,这个将会在之后的内容中继续探讨。
Object类:
每个类都是从java.lang.Object中衍生过来的。
在Object类中,有六个经常用到的方法很重要,需要掌握:
public boolean equals(Object object)
public int hashCode()
public String toString()
protected void finalize() throws Throwable
protected native Object clone() throws CloneNotSupportedException
public final native Class getClass()
多态性,动态绑定,泛型编程:
多态性允许方法在一个“广泛”的对象参数列表中类属地进行使用,即泛型编程。这里“广泛”指的是这些对象不一定要拘泥于参数表中定义的对象类,而是可以这个类派生出来的子类的对象都可以作为这个参数列表的对象。
e.g.
package allen_3.test;
public class Test {
public static void message(Object obj){
System.out.println(obj.toString());
} //message
public static void main(String[] args){
message(new Student());
message(new Person());
message(new Test());
} //main
} //Test
class Student extends Person{
} //Student
class Person{
public String toString(){
return "Person";
} //toString
} //Person
运行的结果为:
Person
Person
allen_3.test.Test@612dcb8c
Person
allen_3.test.Test@612dcb8c
可以看到,message()方法传入的参数可以是Object类的子类。
同样,再仔细一点会发现,钱两个message()输出的结果均为Person.
这里便体现了Java的*
动态绑定(Dynamic Binding)和
静态匹配(Static Matching)机制。
这两种机制这里不赘述,可以自行了解。
对象强制转换和instanceof运算符:
这里我们要区分一下对象和对象的引用,以便于理解:
对于子类对象用父类引用来指向,则称为
向上转换(Upcasting),是隐式的转换。
对于子类对象将父类引用改为子类引用,则称为
向下转换(Downcasting),是显式的转换。
e.g.对于上述例子中的Student类和Person类。
public class Foo {
public static void main(String[] args){
Human human_1 = new Man(); /Upcasting
Human human_2 = new Woman(); //Upcasting
Man human_1_backup = (Man)human_1; //Downcasting
Woman human_2_backup = (Woman)human_2; //Downcasting
Woman human_mid = (Man) human_1; //Illegal Downcasting
} //main
} //Foo
class Human{
} //Human
class Man extends Human{
} //Man
class Woman extends Human{
} //Woman
为了实现泛型编程,我们将引用类型声明为父类类型是很好的实现方法,因为他可以向下转换到子类中。
有时候我们想要了解这个引用指向什么对象,运算符
instanceof就可以做到,这是个二元运算符,运算结果为true或false。
System.out.println(human_1 instanceof Man);
System.out.println(human_1 instanceof Human);
输出
true
true
即instanceof只要会有一种“向上兼容”的模式。
Protected修饰符:
被
protected修饰的类的成员,可以被该类的子类或位于同个package的其他类所访问。
各个修饰符的透明度递增关系如下:
private -> default(不写) -> protected -> public
这些修饰符中,
public和
default(不写)也可以用于修饰类,其中
default(不写)修饰的类只能被位于同一个package的其他类访问。
在子类中,可以通过覆盖父类的
protected的方法成为
public,然而子类不能降低父类中方法的透明度,比如父类中的方法声明为
public,则子类中若是覆盖该方法,也只能将其声明为
public。
final修饰符:
如果定义一个类不希望被继承的话,可以将这个类定义为
final类型。类似的,父类一个方法也可以被定义为
final类型,则这个方法不能被子类所覆盖。
初始化块(Initialization Blocks):
初始化块要分为静态初始化块(static)和实例初始化块(instance)。
语法分别如下:
public class Test {
public Test() {
} //Test()
{
} // Instance Initialization blocks
static{
} // Static Initialization blocks
} //Test
一个类中可以含有多个同类型初始化块,则按照出现的先后顺序执行。
1.当一个类第一次被使用时,加载这个类,初始化静态数据,然后执行静态初始化块;
2.当这个类的对象被创建时,类的构造函数被调用,而构造函数中又有三个阶段:
2.1.调用父类的构造函数;
2.2.初始化实力数据,并且执行实例初始化块
2.3.执行构造函数的主体部分。
值得注意的是,如果类中有实例变量被赋了初值,实质上是在实例初始化块中完成的;如果类中有静态变量被赋了初值,实质上是在静态初始化块中完成的。
NOTE:
*覆盖(Override)和重载(Overload)是不同的。
*动态绑定和静态匹配机制,这里简单的进行说明:当调用一个实例对象的方法时,这个对象确切的类(the actual class of the object)中的这个方法最优先被调用,这个是在运行时决定的;而在访问类中的数据或者调用一个静态方法时,那个被声明的类型变量(the declared type)决定了哪个方法被调用,这个是在编译的时候决定的。