我们先来观察一下这俩个类(为了方便演示,先不对类进行封装)
Dog类
public class Dog {
int age;
String name;
public void shout() {
System.out.println("发出叫声");
}
}
Cat类
public class Cat {
int age;
String name;
public void shout() {
System.out.println("发出叫声");
}
public void eat() {
System.out.println("吃东西");
}
}
这个两个类Dog类和Cat类,他们除了类名不同以外,其他代码完全一致。他们都有着相同的属性:name,age。他们都有着共同的行为:shout,eat。既然Dog和Cat都有着相同的属性和行为,那么我们就可以把他们归为一类,Animal类,让Dog类和Cat类都沿用Animal中的变量和方法。这就叫做继承,Animal为父类,Dog类和Cat类为子类。
继承要用到关键字extends
extends
格式:class 子类 extends 父类 {
}
通过继承,可以使我们的代码变得简洁明了,并且容易维护,如果想修改一处属性,那么只需要在父类中修改即可。
下面是继承后的代码:
Animal类
public class Animal {
int age;
String name;
public void shout() {
System.out.println("发出叫声");
}
public void eat() {
System.out.println("吃东西");
}
}
Dog类
public class Dog extends Animal{
}
Cat类
public class Cat extends Animal{
}
这样一来,Dog类和Cat类就继承了Animal类。
我们在测试类中创建一个Dog对象和一个Cat对象,就可以对父类中的变量和方法进行调用了。
1.Java只支持单继承,如下的代码是错误的
class A{}
class B{}
class C extends A,B{}
2.多个类可继承一个类
class A{}
class B extends A {}
class C extends A {}
3.允许多重继承
class A {}
class B extends A {}
class C extends B {}
我们将父类中的name成员变量赋值为"动物"
public class Animal {
int age;
String name = "动物";
public void shout() {
System.out.println("发出叫声");
}
public void eat() {
System.out.println("吃东西");
}
}
再在Dog类中定义一个String型的name,并赋值为"旺财"
public class Dog extends Animal{
String name = "旺财";
}
在测试类中输出name,那么他的值应该是多少呢?
控制台输出的值为"旺财"
如果仅在Dog类中定义一个name,不赋值,观察控制台的输出情况
public class Dog extends Animal{
String name;
}
这时输出的值为null
这就说明,继承中变量的访问特点为,先在子类中寻找该成员变量,并优先使用子类中有的成员变量,如果在子类中找不到该成员变量,再到父类中寻找。
猫和狗在行为上有着不同的特点,如狗的叫声为“旺旺”,而猫的叫声为“喵喵”;狗吃的是骨头,而猫吃的是鱼。如果再调用用Animal类中的方法显然就不会合适。
在继承中,子类会自动继承父类的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写,重写的方法要与父类中被重写的方法的方法名、参数列表、以及返回值类型相同。
下面我们就对shout方法和eat方法进行重写
public class Dog extends Animal{
@Override
public void shout(){
System.out.println("狗汪汪叫");
}
}
只需要以相同的方法名、参数列表、返回值类型,在子类中把自己想要的功能写出即可。
在重写方法的上方加入@Override,编译器可自动检测方法重写的是否正确,若错误后自动报错。
在测试类中调用重写后的方法,即运行重写后的方法,父类中被重写的方法不会被执行。
注:重写方法时,不能使用比被重写方法更加严格的访问权限,如父类方法的访问权限是public,子类重写后就不能是private。
根据前面的知识,测试类调用对象的成员方法和成员变量时,会优先在子类中寻找。那么如果我们想要使用父类中的成员方法和变量,该怎么办呢?Java中专门为我们提供了一个关键字:super
super
- super.成员变量 //调用父类的成员变量
- super.成员方法(参数) //调用父类的成员方法
- super(参数) //调用父类的构造方法
但调用构造方法时要注意,只能在构造方法中使用this调用构造方法,不可在成员方法中调用,且this调用构造方法必须是该方法的第一条执行,并且只能出现一次。
super的使用方法可以类比于this关键字。
在父类中定义一个带参构造法
public class Animal {
int age;
String name;
public Animal(int age){
System.out.println("父类带参构造法");
}
public void shout() {
System.out.println("发出叫声");
}
public void eat() {
System.out.println("吃东西");
}
}
此时子类必须有一个带参构造法,否则会报错。原因是在子类的构造方法中,一定会调用父类的某个构造方法,当父类中产生了带参构造法后,默认的无参构造法就会消失,所以子类必须写一个带参构造法。
修改后的Dog类:
public class Dog extends Animal{
public Dog(int age) {
super(age); //继承父类的带参构造法
System.out.println("子类带参构造法");
}
@Override
public void shout(){
System.out.println("狗汪汪叫");
super.shout(); //调用父类的方法
}
}
super(age)调用了父类中的带参构造法,super.shout调用了父类中的shout方法。来看测试类运行后的结果
创建对象时,调用子类的带参构造法,但是子类的带参构造法的第一条执行了父类的带参构造法,所以输出了父类带参构造法,然后输出子类带参构造法。
在子类的shout方法中调用了父类中的shout方法,所以会输出一个“发出叫声”。
在Java中,Object类是所有类的父类,所有的类都直接或间接的继承自Object类。Object类又称为超类,基类或根类。
既然类都继承自Object类,那么Object类中的方法也是可以被重写的。
如果我们直接调用toSring方法,返回的结果会是一串地址
在Dog类中重写toString方法
public class Dog extends Animal{
@Override
public String toString (){
return "我是Dog类";
}
public Dog(int age) {
super(age); //继承父类的带参构造法
System.out.println("子类带参构造法");
}
@Override
public void shout(){
System.out.println("狗汪汪叫");
super.shout(); //调用父类的方法
}
}
小问题:为什么构造方法不写时,默认为无参构造呢?
答:因为Object类中含有无参构造法,而所有类都直接或间接的继承自Object类,所以构造法不写时就是继承了Object类中的无参构造法。