面向对象程序三大特性:封装、继承、多态。
1.继承的意义
在我们通过一个类实例化不同对象的时候,这些对象之间可能会有关联,这种情况下就会造成代码的重复。
比如:猫和狗,它们都是动物,我们用代码描述:
class Cat{ public String name; public int age; //name属性 public void eat(){ //age属性 System.out.println(name + "吃饭"); //eat方法 } public void climb(){ System.out.println(name + "在爬树"); } } class Dog{ public String name; //name属性 public int age; //age属性 public void eat(){ System.out.println(name + "吃饭"); //eat方法 } public void swim(){ System.out.println(name + "在游泳"); } } public class Test { public static void main(String[] args) { Cat cat = new Cat(); Dog dog = new Dog(); } }
通过上面代码大家可以发现,猫和狗类中存在大量重复代码,比如name,age属性,eat方法。那我们能不能降低代码的重复性呢?答案是当然可以的,所以我们就引入了继承的概念,而继承的意义就是:抽取共性,减少代码冗余,提高程序运行效率。
2.继承的实现、语法
我们要实现继承,需要借助关键词extends,格式如下:
1. 修饰符 class 子类 extends 父类{ 2. //成员属性 3. //成员方法 4. }
其中子类又叫派生类,父类又叫基类或者超类。
这样我们就可以把上一个例子给简化:
class Animal{ public String name; public int age; public void eat(){ System.out.println(name + "吃饭"); } } class Cat extends Animal{ public void climb(){ System.out.println(name + "在爬树"); } } class Dog extends Animal{ public void swim(){ System.out.println(name + "在游泳"); } } public class Test { public static void main(String[] args) { Cat cat = new Cat(); // Cat类中没有任何成员变量,所以name,age属性都是从父类Animal中继承下来的 cat.name = "花花"; cat.age = 1; Dog dog = new Dog();// Dog类中没有任何成员变量,所以name,age属性都是从父类Animal中继承下来的 dog.name = "多多"; dog.age = 2; cat.eat();// Cat类中没有此成员方法,所以Cat方法也是从Animal类中继承下来的 dog.eat();// Dog同Cat } }
注意:
- 子类继承父类后,必须添加自己特有的成员,不如就没必要继承了;
- 子类继承父类后,会将父类的成员添加到自己的类中;
- 不能多继承;
3.父类成员访问
3.1访问成员变量
class Base{ public int a; public int b; } public class Derive { int b; int c; public static void main(String[] args) { a = 1; //访问从父类继承下来的a b = 2; //访问自己的b c = 3; //访问自己的c //d = 4; //编译失败,父类子类均为定义成员变量d } }
注意:
- 当访问成员变量子类有,优先访问子类的;
- 当访问成员变量子类无,则访问从父类继承的,如果父类无,则编译错误;
- 当访问成员变量子类父类都有,优先访问自己的;
《成员变量访问遵循就近原则》
3.2访问成员方法
class Base{ public void method1(int a){ System.out.println("1"); } public void method2(){ System.out.println("1"); } } public class Derive extends Base{ public void method1(){ System.out.println("2"); } public void method2(){ System.out.println("2"); } public void method3(){ System.out.println("2"); } public void method(){ method1(1); //传参,访问父类成员方法method1 method1(); //没有传参,访问子类中的method1 method2(); //父类子类都有method2()方法,优先访问子类的method2方法 method3(); //子类自己有,访问自己的 //method4(); //编译错误 } public static void main(String[] args) { Derive derive = new Derive(); derive.method(); } }
注意:
成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错;
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错;
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
4.super关键字
当子类和父类存在相同的成员,我们如何访问父类的成员呢?没错,就是通过spuer关键字,该关键字的作用就是:在子类方法中访问父类的成员。
class Base{ int a; int b; public void method1(){ System.out.println("1"); } public void method2(){ System.out.println("1"); } } public class Derive extends Base{ int a; char b; public void method1(int a) { System.out.println("2"); } public void method2() { System.out.println("2"); } public void method3(){ a = 100;//子类 b = 101;//子类 super.a = 200;//通过super,访问父类成员变量 super.b = 201;//通过super,访问父类成员变量 method1(1);//有参,访问子类成员方法 method1(); //无参,访问父类成员方法 method2(); //访问子类 super.method2();//通过super可以直接访问父类中的成员方法 } public static void main(String[] args) { Derive derive = new Derive(); derive.method3(); } }
注意:
- 只能在非静态方法中使用;
- 在子类方法中,访问父类成员;
5.子类构造方法
子类对象构造的时候,需要先调用父类中的构造方法,然后在执行子类的构造的方法。
class Base{ public Base() { System.out.println("1"); } } public class Derive extends Base{ public Derive() { super();//当你没写时,此处编译器会默认生成一个:super();调用父类构造方法 System.out.println("2"); } public static void main(String[] args) { Derive derive = new Derive(); } }
注意:
若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法;
如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败;
在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句;
super()只能在子类构造方法中出现一次,并且不能和this同时出现;
6.继承关系上的执行顺序
class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person:构造方法执行"); } { System.out.println("Person:实例代码块执行"); } static { System.out.println("Person:静态代码块执行"); } } class Student extends Person{ public Student(String name,int age) { super(name,age); System.out.println("Student:构造方法执行"); } { System.out.println("Student:实例代码块执行"); } static { System.out.println("Student:静态代码块执行"); } } public class TestDemo4 { public static void main(String[] args) { Student student1 = new Student("张三",19); System.out.println("==========================="); Student student2 = new Student("李四",20); } }
执行结果:
Person:静态代码块执行 Student:静态代码块执行 Person:实例代码块执行 Person:构造方法执行 Student:实例代码块执行 Student:构造方法执行 =========================== Person:实例代码块执行 Person:构造方法执行 Student:实例代码块执行 Student:构造方法执行
所以我们得出结论:
- 父类静态代码块优先于子类静态代码块执行,且是最早执行;
- 父类实例代码块和父类构造方法紧接着执行;
- 子类的实例代码块和子类构造方法紧接着再执行;
- 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行;
7.final关键字
final关键字可以修饰变量、成员方法、类。
7.1 修饰变量或字段,表示常量(即不可修改)
1. final int a = 10; 2. a = 20; //编译错误
7.2修饰类,表示不可继承
1. final public class Animal{ 2. ........... 3. } 4. public class Dog extends Animal{ 5. .......... 6. } 7. //编译错误
7.3修饰方法,表示不可重写
class Animal{ public Animal() { System.out.println("1"); } final public void eat(){ System.out.println("1"); } } public class Derive extends Base{ @Override public void eat() { super.eat(); } //编译错误 }