目录
面向对象程序三大特性:封装、继承、多态。
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,格式如下:
修饰符 class 子类 extends 父类{
//成员属性
//成员方法
}
其中子类又叫派生类,父类又叫基类或者超类。
这样我们就可以把上一个例子给简化:
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 static int a;
public static int b;
}
public class Derive {
static int b;
static 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 修饰变量或字段,表示常量(即不可修改)
final int a = 10;
a = 20; //编译错误
7.2修饰类,表示不可继承
final public class Animal{
...........
}
public class Dog extends Animal{
..........
}
//编译错误
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();
}
//编译错误
}