继承
java中实现继承需要用到extends关键字,在java中,一个子类只能继承自一个父类,即java不支持多继承,但支持继承的多代传递。子类默认继承父类的无参构造方法,如果父类没有则报错,如果父类的构造方法是带参的,则必须在子类中显式调用super()方法以调用父类的构造方法。在子类中,利用super关键字可调用父类成员。
简单区分super与this:
- super :指向当前对象的父类的引用;
- this :指向自己的引用。
为了让后续的讲解更方便些,这里举一个例子,如下定义一个父类Animal,子类Dog。
public class Main {
public static void main(String[] args){
Dog dg = new Dog("tom",5);
dg.run();
dg.info();
}
}
class Animal {
protected String name;
protected int age;
protected Animal(String name,int age) {
this.name = name;
this.age = age;
}
protected void info() {
String mes;
mes = name.substring(0,1).toUpperCase() + name.substring(1) + " is " + age + ".";
System.out.println(mes);
}
}
class Dog extends Animal {
protected Dog(String name,int age) {
super(name,age);
}
protected void run() {
String mes;
mes = name.substring(0,1).toUpperCase() + name.substring(1) + " is running.";
System.out.println(mes);
}
}
注:java里面没有让字符串直接变为首字符大写的标题形式的函数,在Python里可以用title()直接转换,但java里就比较麻烦,除了上面那一种用substring()函数结合toUppercase()函数的方法之外,还用一种方法提供参考:
String str;
char[] cr = str.toCharArray();
cr[0] -= 32;
str = str.valueOf(cr);
向上转型
观察这样一条语句:
Animal am = new Dog("tom",3);
父类型的变量指向了一个子类型的实例,即将子类型变为更加抽象的父类型,这种指向是允许的,它被称为向上转型。
向上转型之后,该变量便只能调用父类型的方法和成员,但不要认为变量am就此变为Animal类型了,也不要单纯的认为它还是Dog类型,事实上,变量am既是Animal类型,也是Dog类型,因为Dog本就是Animal的子类。即子类型变量可以看成父类型变量,但父类型变量不可以看成子类型变量。
在上例的继承树 :Dog > Animal > Object 中,只要是从左向右的转型,均为向上转型,均是被允许的。
向下转型
与向上转型相反,你觉得下面这条语句编译会通过么?
Dog dg = new Animal("tom",3);
肯定是不会通过的,把父类型转换成更加具体化的子类,子类的额外功能父类是无法凭空变出来的,所以这种向下转型就会失败。
但是如果这样的话就会被允许:
Animal am = new Dog("tom",3);
Dog dg = (Dog)am;
也就是说,那些先经历过向上转型后的引用变量,才有可能向下转型成功,否则这种向下转型就很有可能是不被允许的。
为了避免向下转型出错,也可以使用java提供的instanceof()操作符判断一个引用变量指向的实例是否是某种类型。
比方上面的语句:
Animal am = new Dog("tom",3);
System.out.println(am instanceof Dog); //true
注意instanceof是java中的关键字,不是函数,在用法上不要出错。
final关键字
之前提到数值型变量被final关键字修饰后便不能再修改,那只是final关键字的很小的用法,final关键字还有如下用法:
#1
final修饰的类无法被其它类继承。如果该final类中有方法,那么这些方法都会被隐式的指定为final方法,final类中成员变量可以根据实际需要决定是否要使用final。final方法无法被重写,final变量无法重新修改值。
#2
一个类中的private方法会被隐式的指定为final方法。private方法只能在该类中被调用,无法在该类之外被调用,当然也无法被继承。如果父类中有final修饰的方法,那么子类无法去重写该方法,但可以被继承,只有private方法才无法被继承,这里不要把final与private混为一谈。
#3
final修饰的成员变量必须要赋初始值,但如果该成员变量被用于类的构造方法的参数中,则可以在生成类的实例时赋值。即final修饰的变量的值只有在两种情况下才会被确定:一种是在定义后就初始化,之后无法再被修改;一种是定义之后在类的构造方法中被初始化,之后无法被修改。两种情况不能在同一个变量中同时出现。另外,final不能用来修饰类的构造方法。
#4
final修饰值类型变量与引用类型变量的区别:final修饰的如果时值类型变量,则该变量的值无法被改变;如果修饰的是引用类型,则该引用指向的堆中的地址的值无法被修改,但该地址里面存的内容还是可以改变的。
implements关键字
尽管java仅支持单继承,但一个类可以通过implements关键字继承多个接口,基本语法如下:
class A extends B implements C,D,E {
}
这里仅作了解,有关接口将在下一篇介绍。
多态
多态使用的前提是继承与方法重写,接下来先介绍java中的方法重写。
方法重写
方法重写,也叫做方法覆写或者覆盖,即Override,方法重载是Overload。
方法重载要求方法参数可以不同,但方法名必须相同,返回类型通常相同,方法重载使功能相同的方法使用同一个名字,更容易被记住,调用起来也更简单。方法重写则要求方法参数与返回类型必须与被重写方法相同。
简而言之,Overload方法是一个新方法,而Override方法则是重新写入的原有方法。
举个例子:
class Animal{
public void run(){
System.out.println("The animal is running.");
}
}
class Dog extends Animal{
@Override
public void run(){
System.out.println("The dog is running.");
}
}
@Override作为装饰器可以让编译器检查是否进行了正确的方法重写,如果没有进行正确的Override就会报错。
方法的重写规则补充:
- 访问权限不能比被重写方法的权限低。比如父类方法被声明为public,则重写该方法时就不能声明为比public权限低的权限修饰符,比如protected。
- final修饰的方法无法被重写。
- static修饰的方法无法被重写,但可以再次声明。
- 构造方法不能被重写。
- 方法重写建立在继承的基础上,没有继承该方法则不能重写该方法。
多态
我就不捡抽象的语句来说明了,java中的多态必须在继承的基础上来实现,它指的是同一个方法在处理问题时具有不同表现形式的能力。
举个例子:
class Animal{
protected void info(){
System.out.println("I am a animal.");
}
}
class Dog extends Animal{
protected void info(){
System.out.println("I am a dog.");
}
}
class Cat extends Animal{
protected void info(){
System.out.println("I am a cat.");
}
}
class Bird extends Animal{
protected void info(){
System.out.println("I am a bird.");
}
}
public class Main {
public static void main(String[] args){
Animal[] animals = new Animal[]{
new Dog(),
new Cat(),
new Bird()
};
infos(animals);
}
public static void infos(Animal ... animals){
for(Animal animal:animals){
animal.info();
}
}
}
运行结果:
I am a dog.
I am a cat.
I am a bird.
在上例中,利用多态,主函数中的infos方法根本不必去知道Animal的子类,就能够得到正确的结果,这就是处理一个问题的不同表现形式。同时,多态允许添加更多类型的子类实现功能上的拓展,却不需要修改基于父类的代码。