文章有点长,归纳较为详细,如有写的不全面和错误的希望大家热心指正。
class 子类名 extends 父类名{
子类类体
}
使用继承机制设计程序时:
- 先提取公有属性,创建父类
- 提取特殊属性,使用extends关键字创建子类
- 在子类中根据需要增加特有的状态(属性)和行为(方法)
class People {
String name;
int age;
public void selfInfo() {
System.out.println("我的名字是" + name);
System.out.println("我的年龄是" + age);
}
}
//继承父类People
class Student extends People{
private String school;
private int sno;
@Override
public void selfInfo() {
//重写父类方法
super.selfInfo();//调用父类方法
System.out.print(",所在学校"+school);
System.out.println(",学号是"+sno);
}
}
}
根据上面的例子,可以总结出一些继承的优点:
class SubClass extends Base1,Base2{
...}
- 子类有父类方法和属性
- 子类可重写父类的方法,隐藏属性
- 子类可以自定义自己的方法和属性
上面说到继承的一些特点,属性可以被隐藏,方法呢?隐藏是指什么?隐藏了之后怎么调用呢?
关键词都在于同名,定义了同名的属性和方法,一个叫重写,一个叫隐藏,对于同名方法,如何在子类中使用和扩展这些与父类相同的方法呢?
(1)属性被覆盖
class F {
int x = 1;
}
}
class S extends F {
int x = 2;
(2)普通方法被重写
举例:
被覆盖的方法在子类中被调用时,将访问在子类中定义的方法
class Bird{
public void fly(){
System.out.println("鸟儿在天空中自由飞行");
}
}
public class Ostrich extends Bird{
@Override
public void fly() {
System.out.println("鸵鸟不会飞,只会跑");
}
public static void main(String[] args){
Ostrich oc =new Ostrich();
oc.fly();
}
}
运行结果:
鸵鸟不会飞,只会跑
方法的重写需要子类中的方法头和父类的方法头完全相同,即有完全相同的方法名、返回类型和参数列表
如果不希望子类对父类继承来的方法进行重写,则需要在方法名前加关键字final
子类中重写的方法不能比它重写的父类中的方法有更严格的访问权限
class Bird{
public void fly(){
System.out.println("鸟儿在天空中自由飞行");
}
}
public class Ostrich extends Bird{
@Override
protected void fly() {
//错误,子类重写方法比父类的方法权限要小
System.out.println("鸵鸟不会飞,只会跑");
}
public static void main(String[] args){
Ostrich oc =new Ostrich();
oc.fly();
}
}
class People {
String name;
int age;
public People(){
}
public People(String name){
}
public People(String name, int age) {
//同类重载
this.name = name;
this.age = age;
}
public void show(){
System.out.println("People中的show方法");
}
}
public class Example5_2 {
public static void show(String sno) {
//不同类的方法的重载
System.out.println(sno);
}
public static void main(String[] args) {
Example5_2.show("2");
}
}
class People {
public void show() {
System.out.println("People中的show方法");
}
protected void show(int i) {
//同类中可重载
System.out.println("People中的show方法");
}
}
public class Example5_2 extends People {
@Override
public void show() {
System.out.println("子类中的show方法"); //重写
}
protected static void show(String sno) {
//重载,对修饰方法没有要求
System.out.println(sno);
}
public static void main(String[] args) {
new Example5_2().show();
new Example5_2().show("2");
}
}
重写 | 重载 | |
---|---|---|
是否同类 | 不能同类 | 可同类也可不同类 |
方法名的参数形式是否相同 | 必须相同 | 必须不同 |
返回类型 | 必须相同 | 可同可不同 |
访问修饰符 | 子类不能比父类权限小 | 无要求 |
方法体异常 | 不能抛出新的异常或异常不能范围变大 | 无要求 |
构造方法 | 不能被重写 | 可以被重载 |
多态实现方式 | 实现运行时多态 | 实现编译时多态 |
关于最后一点可以详细见多态一节
public class Example5_1 {
public boolean equals(Example5_1 other) {
System.out.println("use Example equals.");
return true;
}
public static void main(String[] args) {
Object o1 = new Example5_1();
Object o2 = new Example5_1();
Example5_1 o3 = new Example5_1();
Example5_1 o4 = new Example5_1();
/**
* 调用的是object的equal方法,比较的是堆地址,new新对象时堆地址不同,返回的是false
* 所以方法体里面的输出执行不到,所以此方法执行结束,什么都不输出
*/
if (o1.equals(o2)) {
System.out.println("o1 is equal with 02");
}
/**
* 调用的是Example5_1类中的equal方法,所有程序先输出equal方法里面的语句;
* 接着返回true;
* 然后执行到方法体里面输出"o3 is equal with 04"
*/
if (o3.equals(o4)) {
System.out.println("o3 is equal with 04");
}
}
}
程序的运行结果是:
use Example equals.
o3 is equal with 04
在子类对父类进行一些扩展之后,对于原来父类中的一些方法和属性,子类中要怎么去使用呢?这就要用到一些关键字了。
在java语言中,final关键字有3种用法:修饰变量、修饰方法和修饰类
(1)修饰变量
一个变量如果被final关键字修饰,则该变量就是常量,只能被赋值一次,不能被重新赋值
(2)修饰方法
一个方法如果不希望被子类重写,就要用final修饰,这种情况下,子类只能从父类继承该方法,无法对父类的此方法进行重写,好处就是防止子类对父类的关键方法进行重写时产生错误。
声明final可增加代码的安全性和正确性,也可提高类的运行效率。
class F{
final void show(){
System.out.println("a final method");
}
}
class S extends F{
void show(){
//错误,不能重写父类用final修饰的方法
System.out.println("override a final method");
}
}
(3 )修饰类
如果一个类不希望被继承,则可在定义该类时加上关键字final
final class F{
...
}
class Y extends F{
...
}
该程序将编译错误,被final修饰的类无法被继承
(4)总结
前面提到了子类隐藏父类的属性或重写了父类的方法,子类无法访问父类的被重写的方法和被隐藏的属性,那么Java中提供了关键字super调用被隐藏的属性和被重写的方法
(1)调用父类属性或方法
super.父类中的的属性;
super.父类中的方法;
class F {
int x = 1;
public void add(){
x++;
}
}
class S extends F {
int x = super.x + 1;
@Override
public void add() {
super.add();
}
}
(2)调用父类的构造方法
super([参数列表])
子类可以继承父类中的普通方法和属性,那么父类的构造方法可以被继承吗?
class People {
String name;
int age;
public People(){
System.out.println("父类的构造方法");
}
}
class Student extends People{
private String school;
private int sno;
public Student(){
System.out.println("子类的构造方法");
}
}
public class Example5_2 {
public static void main(String[] args){
new Student();
}
}
程序的运行结果是:
父类的构造方法
子类的构造方法
该类实例化的是子类Student的对象,但是程序先调用父类People中的无参构造方法,之后才调用了子类本身的构造方法
实际上,在子类构造方法的第一行默认了一个super语句:
public Student(){
super;
System.out.println("子类的构造方法");
}
如果程序中指定了构造方法,默认构造方法不会再生成,需要手动在People类中增加一个无参的构造方法来保证使用无参构造方法实例化子类对象的正确性:
class People {
String name;
int age;
public People() {
}//手动添加的无参构造方法
public People(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends People {
private String school;
private int sno;
public Student() {
}
}
public class Example5_2 {
public static void main(String[] args) {
new Student();
}
}
class People {
String name;
int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends People {
private String school;
private int sno;
public Student(String name,int age,String school) {
super(name,age);
this.school = school;
}
}
public class Example5_2 {
public static void main(String[] args) {
new Student("李白",20,"北京大学");
}
}
注意,用super调用父类的构造方法时只能放在程序中方法体的第一行,使用super调用父类的非构造方法时,是否放在首行根据需要确定。
构造方法不能被继承,子类对象实例化的时候会默认调用父类的无参构造方法,之后再调用本类的相应的构造方法;
若父类有有参的构造方法时而无默认的构造方法时,子类必须在构造方法中指定调用父类中有参的构造方法或者在父类中手动添加默认(即无参)的构造方法,如果子类中不去调用,则程序会报错。
(1)super和this调用构造方法的操作不能同时出现:因为调用构造方法时两者均要放在方法体的首行;
(2)super和this都是相对于某个对象而言,而static相对于类而言,所以不能在类方法中出现this或super
class F{
protected void show(){
System.out.println("F类中的show方法");
}
}
public class S extends F{
@Override
protected void show() {
super.show();
System.out.println("S类中的show方法");
}
public static void main(String[] args){
new S().show();
super.show();//错误,静态方法中不能使用super
}
}
因为有了继承,使用类和类之间有了一种层次关系,可以使用快捷键ctrl+h(其它快捷键可点此链接)查看继承关系
可以发现最底层是Object类,在Java中,Object类是所有类的父类,如果有个类没有直接指出父类,那么默认该类的父类为Object类,所以Object类中的所有Public方法可以被任何类使用,如上述例子中的equals方法。Object类中的常用方法可以查看jdk帮助文档,个人认为查看源码受益很多
继承实现了类的高度复用,减少了代码量,但也增加了子类与父类的耦合性,父类的细节完全暴露给了子类,子类可对父类的一些方法和数据恶意篡改,为了保证父类的良好封装性,应该做到:
最后写个demo,总结上述的一些知识点
/**
* @Author: qp
* @Time: 2021/8/7 20:03
* @Description IT公司有行政、财务、技术和销售4个部门;现在需要招聘技术和销售人员
* 分解:需要两个类:一个公司类,一个部门类,公司类包含部门信息,部门类包含部门名称、岗位、工资
* 公司类中包含部门信息,即把部门类作为公司类的一个组合成分,达到既可以使两个类都具有良好的封装性,又可以在公司类中复用部门类
*/
class Section {
private String scName; //部门名称
private String title; //招聘岗位
private float salary; //工资
public String getScName() {
return scName;
}
public void setScName(String scName) {
this.scName = scName;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
class ITCompany {
private String name; //公司名称
private String city; //所在城市
private Section section; //部门
public ITCompany(String name, String city, Section sc) {
this.name = name;
this.city = city;
this.section = sc;
}
//显示公司招聘信息,组合式输出招聘信息,\t空Tab键
public void showResult() {
System.out.print(name+"\t"+city+"\t");
System.out.println(section.getScName() + "\t" + section.getTitle() + "\t" + section.getSalary());
}
}
public class TestCompose {
public static void main(String[] args) {
Section sc1 = new Section();
sc1.setScName("技术部");
sc1.setTitle("经理");
sc1.setSalary(9000);
Section sc2 = new Section();
sc2.setScName("技术部");
sc2.setTitle("开发人员");
sc2.setSalary(6500);
Section sc3 = new Section();
sc3.setScName("销售部");
sc3.setTitle("销售人员");
sc3.setSalary(5000);
System.out.println("公司名称\t城市\t部门\t岗位\t工资\t");
ITCompany itc1 = new ITCompany("xx公司", "北京", sc1);
itc1.showResult();
ITCompany itc2 = new ITCompany("xx公司", "北京", sc2);
itc2.showResult();
ITCompany itc3 = new ITCompany("xx公司", "北京", sc3);
itc3.showResult();
}
}
输出结果:
公司名称 城市 部门 岗位 工资
xx公司 北京 技术部 经理 9000.0
xx公司 北京 技术部 开发人员 6500.0
xx公司 北京 销售部 销售人员 5000.0
继承和组合都可以实现代码的复用,但是在具体使用的时候根据情况来选择: