继承小例子
class Human {
public String name = "小黄"; // 姓名
public int age = 22; // 年龄
}
class Student extends Human {
public double score = 66.6; // 分数
}
class Graduate extends Student {
public String mentor = "小勇"; // 导师
}
public class TestExtends {
public static void main(String[] args) {
Graduate gd = new Graduate();
System.out.printf("%s %d %f %s\n", gd.name, gd.age, gd.score, gd.mentor);
}
}
继承
- 一个新类从已有的类那里获得其已有的属性和方法,这种现象叫类的继承
- 这个新类被称为子类,也叫派生类,已有的那个类叫做父类,也叫做基类
- 继承的好处
- 代码得到极大的重用
- 形成一种类的层次体系结构
- 为多肽创造条件
- 继承的实现方式
- class SubClass extends SuperClass {}
- class SubClass extends SuperClass {}
- Java只支持单继承,不允许多重继承
- 单继承就是一个类只能有一个父类
- 多继承就是一个类可以有多个父类
- 可以有多层继承,即一个类可以继承某一个类的子类,如类B继承了类A,类C又可以继承类B,那么类C也间接继承了类A。
- 子类可以继承父类所有的成员变量和成员方法,但子类永远无法继承父类的构造方法。在子类的构造方法中可使用语句super(参数列表)调用父类的构造方法。
继承的访问控制权限
同包继承权限问题(重点)
- 子类的所有方法内部都可以访问父类除私有成员以外的所有成员,所谓子类的所有方法也包括子类的私有方法
- 通过子类对象名可以访问
- 父类除私有成员外的所有成员
- 子类本身的除私有成员外的所有成员
- 附注:私有成员包括私有属性和私有方法
- 子类可以继承父类除私有成员以外的所有成员
- 父类的私有成员不可以被子类继承,其他的成员都可以被子类继承
- 不同包之间的继承问题,后面还会讨论
// 说明了:子类内部可以访问父类非私有的成员,私有成员无法被子类方法访问
// 通过子类对象名只能访问从父类继承过来的非私有成员
// 总结:私有不能被继承
// 私有物理上已经被继承过来了,只不过逻辑上程序员不能去访问它
// 因此继承必须慎重,否则会浪费内存
class A {
public int i;
protected int j;
private int k;
public void g() {
}
private void s() {
}
protected void b() {}
}
class B extends A {
private void g() {
i = 10;
j = 20;
// k = 30; // error 私有属性不能被继承
g();
b();
// s(); // error 私有方法不能被继承
}
public void f() {
i = 10;
j = 20;
// k = 30; // error 私有属性不能被继承
g();
b();
// s(); // error 私有方法不能被继承
}
}
class M {
public static void main(String[] args) {
B bb = new B();
bb.i = 20;
bb.j = 30;
bb.b();
bb.g();
// bb.s(); // error
// bb.k = 22; // error
}
}
继承的原则
- 何时选择继承?
- 一个很好的经验:“B是一个A吗?”
- 如果是则让B做A的子类
- 注意:经常犯的错误,A有一个B吗?例如让汽车轮子成为汽车的子类是错误的
不同访问修饰符
public | protected | default | private | |
---|---|---|---|---|
同包同类 | √ | √ | √ | √ |
访问同包不同类 | √ | √ | √ | |
同包不同类继承 | √ | √ | √ | |
不同包继承 | √ | √ | ||
访问不同包无任何关系的类 | √ |
super关键字
class A {
public int i;
public int j;
}
class B extends A {
public int k;
public B() {}
public B(int i, int j, int k) {
this.i = i; // 一定要明白为什么i前面可以加this
this.j = j; // 因为i是通过A继承过来的,所以B里面也有i,所以可以加this
this.k = k;
}
}
public class TestSuper {
public static void main(String[] args) {
B bb = new B(1, 2, 3);
System.out.printf("%d, %d, %d\n", bb.i, bb.j, bb.k);
}
}
// 说明了:子类B的构造函数中调用super,可以调用父类A的构造函数
class A {
public int i;
public int j;
}
class B extends A {
public int k;
public B() {}
public B(int i, int j, int k) {
// A(i, j); // error
super(i, j); // OK
this.k = k;
}
}
public class TestSuper {
public static void main(String[] args) {
B bb = new B(1, 2, 3);
System.out.printf("%d, %d, %d\n", bb.i, bb.j, bb.k); // => 1, 2, 3
}
}
// super常犯错误
class A {
public int i;
}
class B extends A {
public int j;
public B(int i, int j) {
super(i); // error 因为父类A中没有有参构造方法
this.j = j;
}
public void f(int i) { // 普通方法
// super(i); // error 普通方法中不能调用父类的构造方法
}
}
public class TestSuper {
public static void main(String[] args) {
B bb = new B(1, 2, 3);
System.out.printf("%d, %d, %d\n", bb.i, bb.j, bb.k);
}
}
// super常犯错误
class A {
public int i;
public A(int i) {
this.i = i;
}
}
class B extends A {
public int j;
public B(int i, int j) {
//当你不写任何super时,系统会自动调用super(),这个时候因为父类A中有一个有参的构造方法,没有无参的,所有报错
this.j = j;
}
public void f(int i) { // 普通方法
// super(i); // error 普通方法中不能调用父类的构造方法
}
}
public class TestSuper {
public static void main(String[] args) {
B bb = new B(1, 2, 3);
System.out.printf("%d, %d, %d\n", bb.i, bb.j, bb.k);
}
}
// super常犯错误
class A {
public int i;
public A() {}
public A(int i) {
this.i = i;
}
}
class B extends A {
public int j;
public B(int i, int j) {
this.j = j;
super(i); // error 因为super()要为第一条语句
super(); // error 因为一个构造函数里面只能调用一个supper
}
public void f(int i) { // 普通方法
// super(i); // error 普通方法中不能调用父类的构造方法
}
}
public class TestSuper {
public static void main(String[] args) {
B bb = new B(1, 2, 3);
System.out.printf("%d, %d, %d\n", bb.i, bb.j, bb.k);
}
}
总结:
- 每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错
- 如果显式的写出super()语句,则必须保证该语句是第一条语句,否则会出错
- super()如果不写,则编译器会自动添加,所以此时如果父类没有无参的构造函数就会出错。
- 既可以显式的写super(),前提是父类必须有无参的构造函数;也可以显式地写super(实参),前提是父类必须有带参数的构造函数
- 调用父类的构造函数的语句必须借助于supper,不能直接写父类的类名
- 一个构造函数不能写多个super语句
方法的重写
class A {
public void f() {
System.out.printf("AAAA\n");
}
}
class B extends A {
public void f() {
super.f(); // 调用从父类继承过来的f方法
System.out.printf("BBBB\n");
}
}
public class TestOver {
public static void main(String[] args) {
B bb = new B();
bb.f(); // => AAAA BBBB
}
}
class A {
public void f() {
System.out.printf("AAAA\n");
}
}
class B extends A {
public void f() { // 重写父类方法
System.out.printf("BBBB\n");
}
}
public class TestOver {
public static void main(String[] args) {
B bb = new B();
bb.f(); // => BBBB
}
}
- 方法重写:指在子类中重新定义父类中已有的方法
- 重写方法必须二和被重写方法具有相同的方法名称、参数列表和返回值类型
- 子类中不允许出现于父类同名同参数但不同返回类型的方法,如果出现,编译时会报错。
- 重写方法时,不能使用比父类中被覆盖的方法更严格的访问权限
方法重写实例
class Human {
private String name;
private int age;
public Human() {
}
public Human(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo() {
String strInf = name + ":" + age;
return strInf;
}
}
class Student extends Human {
public String school;
public Student() {}
public Student(String name, int age, String school) {
// this.name = name; //error 因为他们是私有的
// this.age = age;
super(name, age);
this.school = school;
}
public void setSchool(String school) {
this.school = school;
}
public String getInfo() {
// String strInf = name + ":" + age + ":" + school; // error ,因为是私有的
String strInf = super.getInfo() + ":" + school;
return strInf;
}
}
public class TestStudent {
public static void main(String[] args) {
Student st1 = new Student("张三", 22, "酱菜");
System.out.printf("%s\n", st1.getInfo()); // => 张三:22:酱菜
}
}