一、继承
1.1 什么是继承?为什么需要继承?
继承(inheritance)是面向对象编程中实现代码复用最重要的机制之一。它允许程序员在保留某个类原有功能和特性的基础上,对其进行扩展 或增加新能力 ,从而创建一个新的类,这个新类被称为派生类。
通过继承,类之间形成由简单到复杂的层次结构,反映了人们对事物从共性到个性、逐步细化的认识过程。
继承主要解决的问题是:抽取共性、减少重复代码,实现更高程度的代码复用。
java
public class Dog{
String name;
int age;
float weight;
public void eat() {
System.out.println(name+ "正在吃饭");
}
public void sleep() {
System.out.println(name+ "正在睡觉");
}
public void brak() {
System.out.println(name+ "汪汪汪~~~");
}
}
public class Cat{
String name;
int age;
float weight;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep()
{
System.out.println(name + "正在睡觉");
}
public void mew(){
System.out.println(name + "喵喵喵~~~");
}
}
通过观察上述代码,可以发现猫类和狗类中有许多重复内容,例如 name、age、weight 这些属性,以及 eat()、sleep() 这些方法。
继承的作用正是为了从中抽取这些共性部分,让它们集中到一个父类中,从而实现代码的复用。

1.2 继承的语法格式
在Java中,如果要表示两个类之间的继承关系,需要使用关键字extends
其基本语法格式如下:
java
修饰符 class 子类 extends 父类 {
// ...
}
java
// Animal.java
public class Animal {
String name;
int age;
public void eat() {
System.out.println(name+ "正在吃饭");
}
public void sleep() {
System.out.println(name+ "正在睡觉");
}
}
java
// Dog.java
public class Dog extends Animal{
public Dog() {
super();
}
void bark() {
System.out.println(name+ "汪汪汪~~~");
}
}
java
// Cat.Java
public class Cat extends Animal{
public Cat() {
super();
}
void mew() {
System.out.println(name+ "喵喵喵~~~");
}
}
注意:
-
子类会自动继承父类中的成员变量和成员方法,使它们在子类中也能直接使用。
-
继承之后,子类通常还需要加入自己特有的属性或方法,以体现与父类的区别,否则就没有必要进行继承。
1.3 访问父类成员
1.3.1 子类中访问父类的成员变量
1. 当子类和父类不存在同名变量时
java
public class Animal {
String name;
int age;
}
java
public class Dog extends Animal{
double weight;
String color;
public Dog(String name,int age,double weight,String color) {
this.name = name;//从父类中继承下来的name
this.age = age;//从父类中继承下来的age
this.weight = weight;
this.color = color;
}
}
java
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog("阿狗",3,20.5,"黑色");
//dog类中并没有定义任何成员变量,name和age属性是从父类Animal中继承下来的
System.out.println(dog.name);
System.out.println(dog.age);
System.out.println(dog.weight);
System.out.println(dog.color);
}
}
2. 当子类和父类存在同名变量时
java
public class Animal {
String name;
int age;
int a = 3;
}
java
public class Dog extends Animal{
double weight;
String color;
int a = 0;
public void method() {
System.out.println(a);//此时a是访问父类继承的a还是子类自己的a呢?
}
}
java
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
dog.method();//输出结果为0,说明method()中的a是子类自己的
}
}
在子类的方法中,或通过子类对象访问成员变量时,需要遵循以下规则:
-
如果子类中定义了该成员变量,则优先访问子类自己的成员。
-
如果子类中没有该成员变量,则会访问从父类继承下来的成员;若父类也没有,则会在编译阶段报错。
-
如果子类和父类存在同名成员变量,仍然以子类的成员为准。
总之就是遵循就近原则:先找子类自身的,再查找父类的。
1.3.2子类中访问父类的成员方法
java
public class Animal {
public void methodA() {
System.out.println("父类中的A方法");
}
public void methodB() {
System.out.println("父类中的B方法");
}
}
java
public class Dog extends Animal{
double weight;
String color;
public void methodA(int a) {
System.out.println("子类中的A(a)方法");
}
public void methodB() {
System.out.println("子类中的B方法");
}
public void methodC() {
methodA();
methodA(2);
methodB();
//输出结果:
父类中的A方法
子类中的A(a)方法
子类中的B方法
}
}
java
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
dog.methodC();
}
}
【说明】
当使用子类对象访问方法时,如果父类和子类的方法名称不同,那么会按照以下顺序查找:
先在子类中查找该方法;如果找到了就直接调用;如果子类中不存在,则继续到父类中查找;若父类也没有该方法,则编译器会报错。
当父类与子类存在同名方法,但参数列表不同(即方法重载)时,系统会根据调用时传入的参数来选择最匹配的方法。如果没有任何一个方法匹配,则会报错。
当父类和子类拥有同名的成员变量时,如果想访问父类中的变量,则需要使用super.变量名 的方式进行访问。
1.4 super关键字
如果想在子类方法中访问与子类同名的父类成员,不能直接通过名字访问。为了解决这一问题,Java提供了super关键字,用于在子类的方法中明确调用父类的成员。
java
public class Animal {
String name = "父类";
int age = 1;
public void methodA() {
System.out.println("父类中的A方法");
}
public void methodB() {
System.out.println("父类中的B方法");
}
}
java
public class Dog extends Animal{
String name = "子类";
int age = 2;
public void methodA(int a) {
System.out.println("子类中的A(a)方法");
}
public void methodB() {
System.out.println("子类中的B方法");
}
public void methodC() {
System.out.println(name);//访问子类的name,
System.out.println(age);//访问子类的age
System.out.println(super.name);//访问父类的name
System.out.println(super.age);//访问父类的age
}
}
java
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
dog.methodC();
}
}
在子类的方法中,如果需要明确访问父类中的成员,可以使用super关键字。
【注意事项】
-
super 只能在非静态方法中使用。
-
在子类方法中,可以通过super来访问父类的成员变量 和成员方法。
1.5 子类构造方法
在创建子类对象时,Java 会先调用父类(基类)的构造方法,然后再执行子类自己的构造方法。
java
public class Animal {
public Animal() {
System.out.println("父类构造方法");
}
}
public class Dog extends Animal{
public Dog() {
System.out.println("子类构造方法");
}
}
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
// 输出结果为:父类构造方法
// 子类构造方法
// 说明先调用了基类(父类)构造方法,然后执行子类的构造方法。
注意:
1.如果父类显式定义了无参构造方法(或使用默认构造方法),那么在子类的构造方法中,第一行会隐含地调用super(),也就是自动调用父类的构造方法。
java
public Dog() {
//super();super();没写时,Java在第一行默认有隐含的super();
System.out.println("子类构造方法");
}
2.如果父类的构造方法带有参数,那么子类必须显式定义自己的构造方法,并在其中选择合适的父类构造方法进行调用,否则编译会失败。
3.在子类构造方法中,使用 super(...) 调用父类构造方法时,必须放在子类构造方法的第一条语句。
4.super(...) 在子类构造方法中只能出现一次,且不能与 this(...) 同时使用。
1.6 super 和 this
super和this都可以在成员方法中用来访问成员变量或调用其他成员方法,也都可以作为构造方法的第一条语句。那么它们之间有什么区别呢?
相同点:
-
都是 Java 中的关键字。
-
都只能在非静态方法中使用,用于访问非静态成员方法和字段。
-
在构造方法中调用时,必须是构造方法的第一条语句,并且不能同时出现。
区别:
-
引用对象不同: this 表示当前对象的引用,即调用实例方法的对象;而super表示子类对象中继承自父类的那部分成员的引用。
-
访问成员不同 :在非静态成员方法中,this 用于访问本类的方法和属性,super用于访问从父类继承的方法和属性。
-
构造方法中调用不同:this(...) 用于调用本类的其他构造方法,**super(...)**用于调用父类的构造方法,两者不能同时出现在同一个构造方法中。
-
调用默认行为不同:构造方法中总会存在对super(...)的调用,即使用户没有写,编译器也会自动加上;而this(...)如果用户不写,则不会自动调用。
1.7 初始化
java
public class Animal {
public int age;
public Animal(int age) {
this.age = age;
System.out.println("构造方法");
}
{
System.out.println("实例代码块");
}
static{
System.out.println("静态代码块");
}
}
java
public class TestExtend {
public static void main(String[] args) {
Animal animal1 = new Animal(2);
System.out.println("==================");
Animal animal2 = new Animal(2);
}
}
//输出结果
静态代码块
实例代码块
构造方法
==================
实例代码块
构造方法
-
静态代码块 会在类加载阶段执行,并且只执行一次。
-
实例代码块 只有在创建对象时才会执行,实例代码块执行完成后,才会执行对应的构造方法。
在继承关系中,这些代码块的执行顺序如下:
java
public class Animal {
public int age;
public Animal(int age) {
this.age = age;
System.out.println("构造方法");
}
{
System.out.println("实例代码块");
}
static{
System.out.println("静态代码块");
}
}
java
public class Dog extends Animal{
public Dog(int age) {
super(age);
System.out.println("子类构造方法");
}
{
System.out.println("子类实例代码块");
}
static{
System.out.println("子类静态代码块");
}
}
java
public class TestExtend {
public static void main(String[] args) {
Dog dog1 = new Dog(2);
System.out.println("==================");
Dog dog2 = new Dog(2);
}
}
//输出结果:
静态代码块
子类静态代码块
实例代码块
构造方法
子类实例代码块
子类构造方法
==================
实例代码块
构造方法
子类实例代码块
子类构造方法
结论:
-
父类的静态代码块最先执行,优先于子类的静态代码块。
-
父类的实例代码块紧接着执行,随后执行父类的构造方法。
-
然后执行子类的实例代码块,最后执行子类的构造方法。
-
当再次创建子类对象时,父类和子类的静态代码块都不会再执行。
1.8 final关键字
final关键字 可以用来修饰变量、成员方法以及类。
1.当final 修饰变量或字段时,表示该变量是常量,其值在初始化后不可修改。
java
final int a = 10;
a = 20; // 编译出错
2.修饰类:表示此类不能被继承
java
final public class Animal {
...
}
public class Bird extends Animal {
...
} //语法错误
3.修饰方法:表示该方法不能被重写