1.为什么需要继承
Java中使用类对现实世界中实体进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能存在一些关联,那在设计程序时就需要考虑。
例如:鸟和猫都是动物:
//Bird.java
public class Bird {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭...");
}
public void fly(){
System.out.println(this.name + "正在飞...");
}
}
//Cat.java
public class Cat {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭...");
}
public void mimi(){
System.out.println(this.name + "正在咪咪叫...");
}
}
从鸟类和猫类中,不难看出其中有重复的部分,为
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭...");
}
而在面对对象思想中提出了继承的概念,专门进行重复内容的抽取,实现代码的复用。
2.继承的语法
2.1 表示继承
Java中借助 extends关键字 来表示类之间的继承关系,形式如下:
修饰符 class 子类 extends 父类{
// ....
}
2.2 对1中事例使用继承方式重新设计
//Bird.java
public class Bird extends Animal{
public void fly(){
System.out.println(this.name + "正在飞...");
}
}
//Cat.java
public class Cat extends Animal{
public void mimi(){
System.out.println(this.name + "正在咪咪叫...");
}
}
//Animal.java
public class Animal {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭...");
}
}
2.3 注意
- 子类会将父类中的成员变量或者成员方法继承到子类中。
- 子类继承父类后,必须要添加自己特有的成员,体现出不同性,否则没必要继承了。
3.父类成员访问
3.1 子类中访问父类的成员变量
①子类和父类不存在同名的成员变量
//Advenced.java
public class Advenced {
int a = 10;
int b = 20;
}
//Base.java
public class Base extends Advenced{
int c = 30;
public void func(){
System.out.println("a= " + a);
System.out.println("b= " + b);
System.out.println("c= " + c);
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Base base = new Base();
base.func();
}
}
输出:
a= 10
b= 20
c= 30
看看能不能得出这种情况下的访问情况,后面有结论,可以对照思考一下。
②子类和父类存在同名的成员变量
//Advenced.java
public class Advenced {
int a = 10;
int b = 20;
}
//Base.java
public class Base extends Advenced{
int a = 5;
int c = 30;
public void func(){
System.out.println("a= " + a);
System.out.println("b= " + b);
System.out.println("c= " + c);
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Base base = new Base();
base.func();
}
}
输出:
a= 5
b= 20
c= 30
③总结
在子类方法中 或 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问子类自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问成员变量与父类的成员变量同名,则优先访问子类自己的。
成员变量访问遵循就近原则,自己有优先自己的,没有就向父类中去找。
3.2 子类中访问子类的成员方法
①成员方法名字不同
//Advenced.java
public class Advenced {
public void MethodA(){
System.out.println("Advenced.MethodA");
}
}
//Base.java
public class Base extends Advenced {
public void MethodB(){
System.out.println("Base.MethodB");
}
public void MethodC(){
MethodA();
MethodB();
}
}
public class Test {
public static void main(String[] args) {
Base base = new Base();
base.MethodC();
}
}
输出:
Advenced.MethodA
Base.MethodB
②成员方法名字相同
//Advenced.java
public class Advenced {
public void MethodA(){
System.out.println("Advenced.MethodA");
}
public void MethodB(){
System.out.println("Advenced.MethodB");
}
}
//Base.java
public class Base extends Advenced {
public void MethodA(int a){
System.out.println(a);
}
public void MethodB(){
System.out.println("Base.MethodB");
}
public void MethodC(){
MethodA();
MethodA(10);
MethodB();
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Base base = new Base();
base.MethodC();
}
}
输出:
Advenced.MethodA
10
Base.MethodB
③总结
- 通过子类对象访问父类与子类不同名方法时,优先在子类中,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过子类对象访问父类与子类相同名方法时,如果子类和父类同名方法的参数列表不同(重载),根据调用方法适配传递的参数选择合适的方法访问,如果没有则会报错。
4.super关键字
如果子类中存在和父类相同成员的情况下,想调用父类里的成员该怎么办?super关键字来了。
以3.2中②为例:
//Advenced.java
public class Advenced {
public void MethodB(){
System.out.println("Advenced.MethodB");
}
}
public class Base extends Advenced {
public void MethodB(){
System.out.println("Base.MethodB");
}
public void MethodC(){
super.MethodB();
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Base base = new Base();
base.MethodC();
}
}
输出:
Advenced.MethodB
在子类方法中,想要访问父类中成员方法时,借助super关键字即可。详细可见第6部分
注意事项:
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法
5.子类构造方法
父子父子,先有父再有子,即:子类对象构造时,需要先调用子类构造方法,然后执行子类的构造方法
//Advenced.java
public class Advenced {
public Advenced(){
System.out.println("Advenced....");
}
}
//Base.java
public class Base extends Advenced{
public Base(){
System.out.println("Base...");
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Base base = new Base();
}
}
输出:
Advenced....
Base...
从输出结果可以看出,父类和子类的构造方法都被执行,但在子类中并没有提及到父类的成员方法。但子类构造方法中实际情况是这样:
//Base.java
public class Base extends Advenced{
public Base(){
//super(); //注意子类构造方法中默认调用父类的无参构造方法:super()
//如果用户没有写,编译器会自动添加,而且super()必须是子类构造方法中的一句
//并且只能调用一次
System.out.println("Base...");
}
}
注意:
- 父类显式定义无参或者默认的构造方法中,在子类构造方法第一行默认有super(),调用父类构造方法
- 父类显式定义有参,此时用户需要为子类显式定义构造方法,在子类构造方法中选择合适的父类构造方法调用,否则会调用失败。
- 在子类构造方法中,super()调用父类构造方法时,必须要放在子类构造函数中的第一条语句。
- super()只能在子类构造方法中出现一次,并且不能和this同时出现
补充:若父类方法中含参,代码如下
//Advenced.java
public class Advenced {
public Advenced(int a){
System.out.println(a);
}
}
//Base.java
public class Base extends Advenced{
public Base(int a){
super(a);
System.out.println("Base...");
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Base base = new Base(10);
}
}
输出:
10
Base...
6.super和this
super和this都可以在成员方法中访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那么他们之间有什么区别?
6.1 相同点
- 都是Java关键字
- 只能在类的非静态方法中调用,用来访问非静态成员方法和变量
- 在构造方法调用时,必须时第一条语句,并且两者不可同时调用
6.2 不同点
- this是当前对象的引用,当前对象即调用构造方法的对象。super相当于是对子类从父类继承下来部分成员的调用
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this用来调用本类构造方法,super用来调用父类构造方法,两种调用不能同时出现在构造方法中
- 构造方法中一定会存在super的调用,用户没有写编译器也会增加,但是this不写就没有
7. 再谈初始化
先谈谈几个重要的代码块:静态代码块,实例代码块。还记得它们和构造方法的执行顺序吗?
7.1 没有继承关系时的执行顺序
class Internal{
public String name;
public int age;
public Internal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class Test {
public static void main(String[] args) {
Internal internal = new Internal("zhangsan",16);
System.out.println("============");
Internal internal1 = new Internal("lisi",18);
}
}
输出:
静态代码块执行
实例代码块执行
构造方法执行
============
实例代码块执行
构造方法执行
- 静态代码块先执行,并且只执行一次,在类加载阶段执行
- 当对象创建时,才会执行实例代码块,当实例代码块执行完成后,最后执行方法执行
7.2 有继承关系时的执行顺序
class External{
public String name;
public int age;
public External(String name, int age) {
this.name = name;
this.age = age;
System.out.println("父类构造方法执行");
}
{
System.out.println("父类实例代码块执行");
}
static {
System.out.println("父类静态代码块执行");
}
}
class Internal extends External{
public String name;
public int age;
public Internal(String name, int age) {
super(name,age);
this.name = name;
this.age = age;
System.out.println("子类构造方法执行");
}
{
System.out.println("子类实例代码块执行");
}
static {
System.out.println("子类静态代码块执行");
}
}
public class Test {
public static void main(String[] args) {
Internal internal = new Internal("zhangsan",16);
System.out.println("============");
Internal internal1 = new Internal("lisi",18);
}
}
输出:
父类静态代码块执行
子类静态代码块执行
父类实例代码块执行
父类构造方法执行
子类实例代码块执行
子类构造方法执行
============
父类实例代码块执行
父类构造方法执行
子类实例代码块执行
子类构造方法执行
- 父类静态代码块优先执行,之后是子类静态代码块
- 父类实例代码块和构造方法紧跟其后
- 然后是子类实例代码块和构造方法
- 父类和子类的静态代码块只会执行一次
8.protectd关键字
8.1 四大访问修饰限定符
-
private 只能在当前类使用
-
default/空白 同一个包底下可以访问
-
protected 同一个包底下都可以使用。但是,不同包下的只有子类才能使用
-
public 那里都可以使用
8.2 protected关键字
package demo2;
public class Enternal {
private int a;
int b;
protected int c;
public int d;
package demo1;
import demo2.Enternal;
public class Internal extends Enternal {
public void func(){
super.a = 10;
super.b = 20;
super.c = 30;
super.d = 40;
}
}
图中的super.a和super.b会出现编译报错。原因如下:
- super.a :private 只能在当前类中使用,即只可在Enternal类中使用
- super.b:空白/default 须在一个包里才可访问,即在demo2包里使用
9.继承方式
单继承 多层继承 不同类继承同一个类
Java支持这三种继承方式,不支持多继承
10.final关键字
final可以用来修饰变量,成员方法以及类
1.修饰变量或者字段,表示常量,即不可修改
public class Test {
public static void main(String[] args) {
final int a = 10;
a = 10;
System.out.println(a);
}
}
输出:
java: 无法为最终变量a分配值
2.修饰类:表示此类不能被继承
//Animal.java
final public class Animal {
public String name;
public int age;
}
//Dog.java
public class Dog extends Animal{
public void bark(){
System.out.println(this.name + "正在旺旺叫...");
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.bark();
}
}
输出:
java: 无法从最终Animal进行继承
这次就先到这了,我们下次见。
目录