继承
简单来说就是利用已存在的类构造一个新类,构造的新类复用父类的方法和域。同时,还可以在此基础上,添加自己的域和方法,这个已存在的类叫做父类(超类或基类)、新类称为孩子类(子类或派生类)。
继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次,在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链,通常一个祖先类可以拥有多个子孙继承链。
特点:
- java只支持单继承,不支持 多继承。如类A只能有一个父类
- java支持多重继承,即类B可以继承A,类C又可以继承B
注:在通过扩展超类定义子类时,应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。
有以下两个类(来自《java核心技术——卷一》):Empoyee(员工类)、Manager(经理类)。显然,每一名经理是一名员工,经理类和员工类存在着is-a的关系。类似这种关系可以使用继承,这样可以提高代码的复用性。
//员工类
class Employee
{
//实例域
private String name; //姓名
private double salary; //薪水
private int hireDay; //工龄
//构造器
public Employee()
{
System.out.println("父类无参构造器");
}
public Employee(String name,double salary,int hireDay)
{
this.name=name;
this.salary=salary;
this.hireDay=hireDay;
System.out.println("带三个参数的父类构造器");
}
//成员方法
public void setSalary(double salary)
{
this.salary=salary;
}
public double getSalary()
{
return salary;
}
}
/*
定义子类的格式:
[修饰符] class 子类名 extends 父类名{
添加的域
添加的方法
}
*/
class Manager extends Employee
{
/*
由于类Manager继承了类Employee,所以Manager类的对象就有四个域,即
父类的 name、salary、hireDay
子类的 bonus
*/
//添加的域
private double bonus; //奖金
//添加的方法
public void setBonus(double bonus)
{
this.bonus=bonus;
}
//构造器
public Manager()
{
System.out.println("这是子类的无参构造方法");
}
public Manager(String name,double salary,int hireDay,double bonus)
{
System.out.println("子类带四个参数的构造器");
}
/*覆盖方法:在父类中的一些方法对子类来说并不一定适合(也称覆盖方法)如
在Manager继承了父类返回工资的方法getSalary(),但是,Manager类的对象工资还应该加上bonus。这时,就可以重写父类的getSalary()方法。
*/
public double getSalary()
{
return bonus+super.getSalary();
}
}
方法重写(又称覆盖方法):
如果子类出现了和父类一样的方法声明(方法名、返回类型和参数列表都相同),则称子类重写了父类的方法
在Manager类中,虽然继承了父类返回工资的方法getSalary(),但是,Manager类的对象工资还应该加上bonus。父类的方法就不适合子类,此时就可以重写父类的getSalary()方法。如下:
public double getSalary()
{
return bonus+salary;
}
但是上述的方法存在着问题,salary是父类Employee的私有域,只能被自身的方法访问。如何解决这个问题呢?
- 把父类的实例域salary定义为非私有的,但是这样就破坏了Employee的封装性。
- 调用父类的公有方法来获得salary,下面的代码就是采用这样的方法
public double getSalary()
{
//super.方法(...) 表示调用父类的方法
return bonus+super.getSalary();
}
注意事项
- 在覆盖方法时,允许将子类将覆盖方法的返回类型的定义为原方法返回类型的子类型
如:
父类Employee有方法 public Employee get(){...}
子类Manager覆盖这个方法 public Manager get(){...}
我们称这两个方法具有可协变的返回类型
- 在覆盖一个方法时,子类的访问权限不能低于父类方法的访问权限,但可以大于父类的访问权限,最好保持一致。
class Employee
{
public Employee get()
{
System.out.println("父类的get方法");
return new Employee();
}
protected int show()
{
System.out.println("父类中的show方法");
return 40;
}
}
class Manager extends Employee
{
//Manager get() //error:Manager中的get()无法覆盖Employee中的get(),正在尝试分配更低的访问权限;以前为protected
public Manager get()
{
System.out.println("子类的get方法");
return new Manager();
}
//int show() //error:Manager中的show()无法覆盖Employee中的show(),正在尝试分配更低的访问权限;以前为protected
//public String show() //error:返回类型String与int不兼容
public int show() //子类覆盖方法的权限大于父类方法的访问权限
{
System.out.println("子类中的show方法");
return 23;
}
}
class TestDemo
{
public static void main(String[] args)
{
Employee s=new Manager();
s.get();
System.out.println(s.show());
}
}
运行结果
子类构造器和父类构造器
由于子类的构造方法不能访问父类的私有域,所以必须利用父类的构造器对这部分私有域进行初始化。调用父类的构造器格式为
super(....);
如果子类的构造器没有显示的调用父类的构造器,则系统会自动调用父类的无参构造。如果父类没有无参构造器,编译器将会报错。
注意:super语句必须是构造方法的第一条语句。
//构造器
public Manager()
{
//super(); //隐含
System.out.println("这是子类的无参构造方法");
}
public Manager(String name,double salary,int hireDay,double bonus)
{
//super(); //隐含
System.out.println("子类带四个参数的构造器");
}
public class ManagerTest
{
public static void main(String[] args)
{
Manager m1=new Manager();
Manager m2=new Manager("叶叶",3000,1000,4);
}
}
运行结果
final关键字
修饰变量:该变量一旦被赋值就不能被修改
- 变量是基本数据类型:一旦赋值就不能修改它的值
- 变量是引用数据类型:一旦引用对象就不能改变其引用的对象,但是可以改变其引用对象的实例域。(引用数据类型存储的是对象在堆内存中的地址,一旦被final修饰,它存储的地址就不能改变,但是它所引用的堆内存中饭对象的属性可以改变)
注:可以将实例域定义为final,但是这个final域必须在构造方法执行完毕被初始化,且在初始化后不能再对它进行修改
class Student
{
String name;
int age;
final int lag;
/*
{
lag=2;
}
*/
public Student(){
lag=2;
}
public Student(String name,int age)
{
this.name=name;
this.age=age;
this.lag=2;
}
public String toString()
{
return "名字:"+name+",年龄:"+age;
}
}
class FinalDemo
{
public static void main(String[] args)
{
//final修饰基本数据类型
final int x;
x=1;
System.out.println(x);
//x=45; //error:无法为最终变量x分配值
//System.out.println(x);
System.out.println("----------------");
//fianl修饰引用数据类型
final Student s1=new Student("叶叶",18);
System.out.println(s1.toString());
s1.name="李**";
s1.age=12;
System.out.println(s1.toString());
//s1=new Studnet("花花",20); //error:无法为最终变量s1分配值
System.out.println(s1.lag);
}
}
运行结果
修饰方法(final方法):表示该方法不能被覆盖(或重写)
修饰类(最终类、final类):阻止继承,表示该类不允许创建子类,一旦一个类用final修饰,该类所有的方法自动的变为final方法,但是该类的实例域并不改为final变量
/*final //error:无法从最终A进行继承*/ class A
{
public final int num=2;
public final void show() //error:B中的show()无法覆盖A中的show()
{
}
}
class B extends A
{
public void show()
{
num=34;
System.out.println(num); //error:无法为最终变量num分配值
}
}