java类关系之继承
接着上篇的类关系讲解,本篇继续详细介绍java类关系之继承。
概念
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
为什么需要继承
使用继承可以减少大量重复的代码,相同的代码,如一些方法都可以定义到父类中,由子类去继承即可使用,而一个父类可以拥有多个子类,因此不需要在每次子类中都定义重复的方法,这样的好处就是减少代码的臃肿,提升代码的可维护性。
就比如你爸爸有钱,你可以继承你爸爸的财产,你还非要花那么大力气去自己挣钱干什么?当然有一些人会想靠自己的力量,用不同的方式去达到父亲那样,那么在代码中我们也是可以去自己实现父类中的方法的,这种行为叫做方法的重写。
单继承
在java中,要记住的一个就是,只能够单继承!java中的一个类只能够有一个父类,就好比不管你是谁,你只能有一个亲生父亲。
extends关键字
在java中使用继承,只需要在定义类的时候,在后面加上 extends 类名 即可。
实例:
- 定义动物类,用有eat,run,sleep三个方法
/**
* 动物类
*/
public class Animal {
void eat(){
System.out.println("动物正在吃。。。");
}
void run(){
System.out.println("动物正在跑。。。");
}
void sleep(){
System.out.println("动物正在睡觉。。。");
}
}
- 定义狮子类
/**
* 狮子类
*/
public class Lion extends Animal{
void bark(){
System.out.println("狮子正在叫。。。");
}
void walk(){
System.out.println("狮子正在走路。。。");
}
}
- main方法测试一下
public static void main(String[] args) {
Lion lion = new Lion();
lion.eat();
lion.run();
lion.sleep();
lion.bark();
lion.walk();
}
输出结果:
动物正在吃。。。
动物正在跑。。。
动物正在睡觉。。。
狮子正在叫。。。
狮子正在走路。。。
可以看到,这个时候,狮子类是动物类的子类,因此狮子类拥有动物类除了private修饰以外的所有方法,因此可以进行调用,输出了以上结果。
可是观察一下输出的结果,前三行输出的是父类的方法,因此会输出“动物正在。。。”,那么实际上,我想全部输出的是“狮子正在。。。”,那么这种情况要怎么办呢?下面继续引用一个概念——重写。
父类方法重写
在子类——狮子类中,我们可以去重新写一下父类的方法(父亲的我不想要,我只想要它的方法名而不是具体的实现),比如都是赚钱,父亲靠体力赚钱,而我靠脑力赚钱,都是赚钱只不过实现赚钱的方式不同。
看下面的代码:
/**
* 狮子类
*/
public class Lion extends Animal{
void bark(){
System.out.println("狮子正在叫。。。");
}
void walk(){
System.out.println("狮子正在走路。。。");
}
@Override
void eat(){ // 重写父类eat方法
System.out.println("狮子正在吃。。。");
}
@Override
void run(){ // 重写父类run方法
System.out.println("狮子正在跑。。。");
}
@Override
void sleep(){ // 重写父类sleep方法
System.out.println("狮子正在睡觉。。。");
}
public static void main(String[] args) {
Lion lion = new Lion();
lion.eat();
lion.run();
lion.sleep();
lion.bark();
lion.walk();
}
}
输出结果:
狮子正在吃。。。
狮子正在跑。。。
狮子正在睡觉。。。
狮子正在叫。。。
狮子正在走路。。。
这样的结果就是我们想要的了!注意上面有一个@Override,就是标示我们这个方法是重写的。
父类非private属性继承
在父类中的属性,只要不是private修饰的,那么子类中也会将它继承过来,说通俗点,爸爸的就是我的!
- 我们给爸爸一些资产如下,一百万,海景别墅,宾利,这些都是共有的,钱财乃身外之物,还有一个老婆,这个是私有的
public class Father {
double money = 1000000;
String house = "海景别墅";
String car = "宾利";
private String wife = "老婆";
}
-
儿子是个富二代,拥有爸爸所有的公开资产,而爸爸的老婆是他妈,不能乱来,因此不能继承
public class Son extends Father{
public static void main(String[] args) {
Son s = new Son();
System.out.println("我有爸爸的"+s.money);
System.out.println("我有爸爸的"+s.house);
System.out.println("我有爸爸的"+s.car);
}
}
输出结果:
我有爸爸的1000000.0
我有爸爸的海景别墅
我有爸爸的宾利
super 与 this 关键字
- super:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
- this:指向自己的引用。
下面,我们在狮子类中定义一个walkAndRun方法,这个方法里我们分别调用狮子类的walk及动物类的run
/**
* 狮子类
*/
public class Lion extends Animal{
void bark(){
System.out.println("狮子正在叫。。。");
}
void walk(){
System.out.println("狮子正在走路。。。");
}
void walkAndRun(){
this.walk(); // 调用本类的walk方法
super.run(); // 调用父类的run方法
}
public static void main(String[] args) {
Lion lion = new Lion();
lion.walkAndRun();
}
}
输出结果:
狮子正在走路。。。
动物正在跑。。。
继承中的类构造器
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
我们先来看实例,如何自动调用父类的无参构造器:
- 我们定义两个类,父类——Father,子类——Son,并且给他们定义两个无参构造器
public class Father {
public Father(){
System.out.println("我是爸爸");
}
}
public class Son extends Father{
public Son(){ // 会隐式调用父类构造器
System.out.println("我是儿子");
}
public static void main(String[] args) {
Son s = new Son();
}
}
输出结果:
我是爸爸
我是儿子
观察输出,确实,在子类实例化的时候,默认的隐式调用了父类的无参构造器,输出了“我是爸爸”.
当然我们也可以利用super关键字进行显式调用:
public class Son extends Father{
public Son(){
super(); // 显式调用父类构造器
System.out.println("我是儿子");
}
public static void main(String[] args) {
Son s = new Son();
}
}
输出结果:
我是爸爸
我是儿子
然后我们再看看有参构造器是什么情况:
public class Son extends Father{
public Son(){
super();
System.out.println("我是儿子");
}
public Son(String name){
System.out.println("我是儿子("+name+")");
}
public static void main(String[] args) {
Son s = new Son("Tom");
}
}
输出结果:
我是爸爸
我是儿子(Tom)
可以看到,这里也进行了自动调用,但是!调用的却不是父类的有参构造器,而还是无参构造器!这里我们就可以总结一下:
只要子类构造器没有显式调用父类构造器,那么默认都会隐式调用父类的无参构造器!
那么,我们再来显式调用一下父类的有参构造器看看:
public class Son extends Father{
public Son(){
super();
System.out.println("我是儿子");
}
public Son(String name){
super("Jerry");
System.out.println("我是儿子("+name+")");
}
public static void main(String[] args) {
Son s = new Son("Tom");
}
}
输出结果:
我是爸爸(Jerry)
我是儿子(Tom)
可以看到,假如显示调用了父类构造器,那么将不会自动调用父类的无参构造器!而调用顺序都是先调用父类,再调用子类!