【Rookie初学Java】关于继承与多态
1 继承
我们在写代码的时候经常会出现代码块高度重复的情况,其实SUM公司提供了一种解决办法
-
this关键字
public class Dog { public String name; public String type; public Dog() { this.name="二哈"; this.type="哈士奇"; } public Dog(String name, String type) { this.name=name; this.type=type; } }
对于这一个类,我们希望在调用无参的时候都产生name和type相同的对象,这时候我们可以在无参构造中写附值语句,但是这样的代码有一些不合理性就是代码有重复片段,这是我们不希望看到的
于是我们可以使用在构造方法中使用this(),优化我们的代码。
public class Dog { public String name; public String type; public Dog() { this("二哈","哈士奇"); } public Dog(String name, String type) { this.name=name; this.type=type; } }
但是this()在构造方法中出现的时候必须出现在构造方法的第一个语句,否则报错
public class Dog { public String name; public String type; public Dog() { System.out.println("我是第一"); this("二哈","哈士奇"); } public Dog(String name, String type) { this.name=name; this.type=type; } } /* Dog.java:8: 错误: 对this的调用必须是构造器中的第一个语句 this("二哈","哈士奇"); ^ 1 个错误 */
关于为什么是在第一个语句我认为可能和逻辑有关,例如以下代码
public class Dog
{
public String name;
public String type;
public Dog() {
System.out.println(this.name);
}
public Dog(String name, String type) {
this.name=name;
this.type=type;
}
}
public class Test
{
public static void main(String[] args)
{
Dog dog = new Dog();
}
}
/*
运行结果null
*/
我们仔细想想,如果我们在构造方法中写了一句打印语句,打印出this.name,之后再执行有参构造给它附值,那么这个name到底算哪个呢,因此我认为JVM会首先看构造方法的第一句是否为this(),如果是则不会报错,否则编译通不过
-
extends
先上代码
public class Animal { public String name; public String type; public Dog() { } public Dog(String name, String type) { this.name=name; this.type=type; } public void run() { System.out.println("动物会跑"); } } public class Dog { public String name; public String type; public Dog() { System.out.println(this.name); } public Dog(String name, String type) { this.name=name; this.type=type; } public void run (){ System.out.println("狗四条腿跑"); } }
我们可以看到,上述代码有高度的重复片段,看起来十分冗余,这样的代码不仅没有意义而且十分浪费时间。于是但是SUM公司早就想到了这种情况的出现,于是有了继承。
public class Animal { public String name; public String type; public Dog() { } public Dog(String name, String type) { this.name=name; this.type=type; } public void run() { System.out.println("动物会跑"); } } public class Dog extends Animal { } /* 运行结果 动物会跑 */ 将Dog构造方法改成有参 public class Test { public static void main(String[] args) { Dog dog = new Dog("二哈","哈士奇"); dog.run(); } } /* 编译报错 Test.java:5: 错误: 无法将类 Dog中的构造器 Dog应用到给定类型; Dog dog = new Dog("二哈","哈士奇"); ^ 需要: 没有参数 找到: String,String 原因: 实际参数列表和形式参数列表长度不同 1 个错误 */
在这个过程中,Dog会继承Animal的所有非private方法(其实感觉和private属性一样也被继承了,只不过无法访问),除此之外,构造方法子类也无法继承。
private属性也会被子类继承,但是如果父类没有提供相应接口给外界访问,那么将无法访问该属性。
-
super
public class Dog extends Animal { public Dog() { super("二哈","哈士奇"); System.out.println(this.name); } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.run(); } } /* 运行结果 二哈 动物会跑 */ public class Dog extends Animal { public Dog(String name, String type) { super("三哈","哈士奇"); System.out.println(this.name); } } /* 运行结果 三哈 动物会跑 */
super关键字可以创建父类的特征,写在子类的构造方法中,但是在整个过程中始终只有一个对象产生,这个对象是子类的对象,最后我们可以通过this访问这个对象的属性。
同样,这个关键字也只能出现在子类构造方法的第一个语句。
super关键字并没有产生父类的对象,只是创建了父类的特征,这些特征是能被继承内容。所以对于我们来说,我们在写类的时候尽可能把需要创建的类的共同特征抽象出来写出一个公共的父类,这样可以避免代码冗余。
疑问:最后一段代码为什么打印出的是三哈而不是二哈?我们不禁猜想构造方法的真实顺序可能如下:先对创建子类对象的成员属性,然后执行super()构造父类型特征,包括属性和方法,最后构造子类对象的方法。
但是我们看上述代码仍然有没有实现的业务
比如动物会跑,但是所有的动物跑的动作都不一定一样,于是我们通过重写父类方法来实现该业务
-
override
重写与重载的不同之处在于:
重写的返回值和方法名以及参数都与原方法相同,只有方法体不同。而重载是方法名相同,通过参数的不同来区分不同方法,返回值可以相同也可以不同。
public class Dog extends Animal { public Dog() { } private void run() { System.out.println("狗用四条腿跑"); } } /* Dog.java:8: 错误: Dog中的run()无法覆盖Animal中的run() private void run() { ^ 正在尝试分配更低的访问权限; 以前为public Test.java:6: 错误: run() 在 Dog 中是 private 访问控制 dog.run(); ^ 2 个错误 */
重写的时候我们一定要注意,重写的方法访问权限只能比父类高或者相同,一定不能比父类低
public class Dog extends Animal { public Dog(String name, String type) { } public void run() { System.out.println("狗用四条腿跑"); } } /* 运行结果 狗用四条腿跑 */
-
-
2 多态
多态是Java中最强大的特性之一
继承是多态发生的前提
方法的重写也是多态发生的前提
表现:编译时一种形态,运行时另一种形态
代码表现:父类引用指向子类对象 Animal dog = new Dog();
public class Animal
{
public String name;
public String type;
public Animal() {
}
public Animal(String name, String type) {
this.name=name;
this.type=type;
}
public void run() {
System.out.println("动物会跑");
}
}
public class Dog extends Animal
{
public Dog() {
}
public void run() {
System.out.println("狗用四条腿跑");
}
public void eat() {
System.out.println("狗吃骨头");
}
}
public class Test
{
public static void main(String[] args)
{
Animal dog = new Dog();
dog.run();
dog.eat();
}
}
/*
报错
Test.java:7: 错误: 找不到符号
dog.eat();
^
符号: 方法 eat()
位置: 类型为Animal的变量 dog
1 个错误
*/
为什么报错?其实这种父类引用指向子类对象的情况下:
在编译的过程中,编译器会先看子类对象是否有调用子类特有的方法,如果没有则编译通过,这个过程叫做静态绑定。
在运行过程中,由于编译已经通过了,说明了父类引用可以调用所有方法,然后在执行的时候会先从最小的子类开始寻找该方法并执行该方法,如果没有再去上一级的父类中寻找,不会找不到,因为编译已经通过。这个过程叫做动态绑定。
静态绑定和动态绑定合在一起组成了多态。