Skip to content

面向对象特性

面向对象的语言中,包含了三大基本特征,即封装、继承和多态

封装

面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的 方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。

属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。

如何封装?

  1. 使用 private 关键字来修饰成员变量。
  2. 对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。

封装操作

private关键字

  1. private是一个权限修饰符,代表最小权限。

  2. 可以修饰成员变量和成员方法。

  3. 被private修饰后的成员变量和成员方法,只在本类中才能访问。

java
private 数据类型 变量名;

封装优化

this关键字

this代表所在类的当前对象的引用(地址值),即对象自己的引用。

方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。

方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写。

构造方法

当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。

无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,

一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。

java
修饰符 构造方法名(参数列表){
    // 方法体 
}

构造方法的写法上,方法名与它所在的类名相同。它没有返回值,所以不需要返回值类型,甚至不需要void。

注意事项:

  1. 如果你不提供构造方法,系统会给出无参数构造方法。

  2. 如果你提供了构造方法,系统将不再提供无参数构造方法。

  3. 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

继承

继承就是子类继承父类的属性行为,使得子类对象具有与父类相同的属性、相同的行为。好处是提高代码的复用性,类与类之间产生了关系,是多态的前提

子类可以直接访问父类中的非私有的属性和行为。

格式

java
class 父类 { 
    ... 
}

class 子类 extends 父类 {
    ... 
}

继承后的变化

成员变量

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量 。

java
super.父类成员变量名

父类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能 直接访问的。

通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员 变量呢?可以在父类中提供公共的getXxx方法和setXxx方法。

成员方法

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。

java
class Fu {
    public void show() {
        System.out.println("Fu show"); 
    } 
}
class Zi extends Fu {
    //子类重写了父类的show方法 
    public void show() { 
        System.out.println("Zi show");
    } 
}
public class ExtendsDemo05{
    public static void main(String[] args) {
        Zi z = new Zi();
        // 子类中有show方法,只执行重写后的show方法
        z.show();// Zi show 
    } 
}

注意事项:

  1. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。

  2. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

构造方法:

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。

  2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。

继承的特点

  1. Java只支持单继承,不支持多继承。

  2. Java支持多层继承(继承体系)。

  3. 子类和父类是一种相对的概念。

super和this

super :代表父类的存储空间标识(可以理解为父亲的引用)。

this :代表当前对象的引用(谁调用就代表谁)。

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。

抽象类

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。

我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类

抽象方法 : 没有方法体的方法。 抽象类:包含抽象方法的类。

抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

abstract关键字

抽象方法

使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

java
修饰符 abstract 返回值类型 方法名 (参数列表);

抽象类 : 如果一个类包含抽象方法,那么该类必须是抽象类。

java
abstract class 类名字 { }

用法

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

java
    public abstract class Animal {
        public abstract void run();
    }

    public class Cat extends Animal {
        public void run() {
            System.out.println("小猫在墙头走~~~");
        }
    }

    public class CatTest {
        public static void main(String[] args) {
            // 创建子类对象
            Cat c = new Cat();
            // 调用run方法
            c.run();
        }
    }

注意事项

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    子类的构造方法中,有默认的super(),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象 类。

    假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有 意义

多态

多态是指同一行为,具有多个不同表现形式。

  1. 继承或者实现【二选一】

  2. 方法的重写【意义体现:不重写,无意义】

  3. 父类引用指向子类对象【格式体现】

java
父类类型 变量名 = new 子类对象;
变量名.方法名();

父类类型:指子类对象继承的父类类型,或者实现的父接口类型

java
Fu f = new Zi();
f.method();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。

java
// 父类
public abstract class Animal {
    public abstract void eat(); 
}

// 子类
class Cat extends Animal {
    public void eat() {
        System.out.println("吃鱼");
    } 
}
// 子类
class Dog extends Animal {
    public void eat() {
        System.out.println("吃骨头");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        // 多态形式,创建对象 
        Animal a1 = new Cat(); 
        // 调用的是 Cat 的 eat 
        a1.eat();
        // 多态形式,创建对象 
        Animal a2 = new Dog();
        // 调用的是 Dog 的 eat 
        a2.eat();
    }
}

引用类型转换

多态的转型分为向上转型与向下转型两种

向上转型

向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。

当父类引用指向一个子类对象时,便是向上转型。

java
父类类型 变量名 = new 子类类型();

向下转型

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。

一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

java
子类类型 变量名 = (子类类型) 父类变量名;

为什么要转型

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。

这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。

java
abstract class Animal {
    abstract void eat();
}

class Cat extends Animal {
    public void eat() {
        System.out.println("吃鱼");
    }
    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

class Dog extends Animal {
    public void eat() {
        System.out.println("吃骨头");
    }
    public void watchHouse() {
        System.out.println("看家");
    }
}

public class Test {
    public static void main(String[] args) {
        // 向上转型 
        Animal a = new Cat();
        a.eat();    // 调用的是 Cat 的 eat 
        // 向下转型 
        Cat c = (Cat)a;
        c.catchMouse(); // 调用的是 Cat 的 catchMouse 
    } 
}

转型的异常

转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

java
public class Test {
    public static void main(String[] args) {
        // 向上转型 
        Animal a = new Cat();
        a.eat();    // 调用的是 Cat 的 eat 
        // 向下转型 
        Dog d = (Dog)a;
        d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】 
    } 
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException,类型转换异常!

这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。

为了避免ClassCastException的发生,Java提供了 instanceof关键字,给引用变量做类型的校验,格式如下:

java
变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。 
如果变量不属于该数据类型,返回false。