Java面向对象程序设计OOP的三大特性详解

1. OOP的三大特性有什么

  1. 封装
  2. 继承
  3. 多态

2. 封装

2.1 什么是封装

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。

2.2 为什么要封装

  • 封装可以被认为是一个?;て琳希乐垢美嗟拇牒褪荼煌獠坷喽ㄒ宓拇胨婊梦?。
  • 封装是为了保护数据,这里的数据是指对象的属性(不是方法的局部变量)。

保护数据中的?;な鞘裁匆馑???;な侵付允莸亩列慈ㄏ蕖?/p>

?;な侨绾问迪值模炕蛘咚捣庾笆侨绾问迪值??是通过访问修饰符实现的。

2.3 访问修饰符

访问修饰符共有四个

  • public:公共的
  • protected:受保护的
  • 默认|default(什么都不写)
  • private:私有的
2.3.1 封装之Public

public修饰的属性没有访问限制,是最宽松的封装。

public class Animal{
    public String dog;
}

dog被public修饰,可以在任何地方被访问以及被读写。

public修饰的类、属性和方法在任何地方都能被访问。

2.3.2 封装之protected

protected是为继承而设计的访问修饰符

protected修饰的属性和方法在子类中一定可以访问,同一个包可以访问,子类在不同包也可以访问。

package com.sunmer.tmp;

public class Father {
    protected int age;
    int weight;
}
package com.sunmer.tmp;//同包

public class Sub1 extends Father{
    public static void main(String[] args) {
        Sub1 sub1 = new Sub1();
        sub1.age= 1;//可以访问
        sub1.weight = 1;//可以访问
    }
}
package com.sunmer.oop;//不同包
import com.sunmer.tmp.Father;
public class Sub2 extends Father {
    public static void main(String[] args) {
        Sub2 sub2 = new Sub2();
        sub2.age = 0;//可以访问
    }
}
package com.sunmer.oop;//不同包
import com.sunmer.tmp.Father;
public class Sub3 extends Sub2 {
    public static void main(String[] args) {
        Sub2 sub2 =new Sub2();
        sub2.age = 0;//不可访问
        Sub3 sub3 =new Sub3();
        sub3.age = 0;//可以访问
    }
}
2.3.3 封装之默认(default)

默认访问修饰符是指什么都不写

默认访问修饰符修饰的类、属性和方法只能在同一个包里被访问

default可以修饰类、属性和方法

package packageone;

public class Father {
    int num1=1;
    void print(){
        System.out.println("哒哒哒!");
    }
}
package packageone;//同一个包

public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.num1=1;//可以访问
        father.print();//可以访问
    }
}
package packagetwo;//不同包

import packageone.Father;

public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.num1 = 1;//不可访问
        father.print();//不可访问
    }
}

在不同包里,会提示:

'print()' is not public in 'packageone.Father'. Cannot be accessed from outside package

2.3.4封装之private

private修饰的属性只能在同一个类里被访问,是最严格的封装

public class Father {
    private int num1=1;
    private void print(){
        System.out.println("哒哒哒!");
    }
}
public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.num1=1;//不可访问
        //'num1' has private access in 'packageone.Father'
        father.print();//不可访问
        //'print()' has private access in 'packageone.Father'
    }
}

private不能修饰类

private class Father{//报错,private不能修饰类

}

如果我们非要访问private封装的属性怎么办呢?

可以使用getter和setter方法访问

最典型封装是:共有方法封装私有属性

getter和setter方法

public class Father {
    private int num1=1;

    public int getNum1() {//给私有成员可读权限
        return num1;
    }

    public void setNum1(int num1) {//给私有成员可写权限
        this.num1 = num1;
    }
}
public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.getNum1();//可读取变量,但不能改
        father.setNum1(1);//可以改写变量,给变量赋值
    }
}

2.4 表格展示

位置 public protected 默认修饰符 private
同类访问
同包其他类访问 ×
同包子类访问 ×
不同包子类访问 × ×
不同包非子类访问 × × ×

3. 继承

3.1 生活中的继承

在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,狸花猫和波斯猫继承自猫,而牧羊犬和秋田犬继承自狗。这些动物之间会形成一个继承体系,具体如下图所示。


image-20220728190010378.png

3.2 Java中的继承

类和类之间有三种关系

is a :继承关系,例如:公共汽车 is a 汽车

use a:使用关系,例如:人 use a 钳子

has a:包含关系,例如:人has a 胳膊

3.2.1 继承的基本概念

继承可以解决编程中代码的冗余问题,是实现代码重用的重要手段之一。

继承是软件可重用性的一种表现,新类可以在不增加自身代码的情况下,通过从现有的类中继承其属性和方法,来实现充实自身内容,这种现象或行为就称之为继承。此时新类称之为子类,现有类称为父类。

继承最基本的作用是使得代码可以重用,即一个类只能有一个直接父类。

3.2.2 为什么要继承
  • 继承的出现提高了代码的复用性,提高软件开发效率。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。
3.2.3 继承的语法规则

在程序中,如果想要声明一个类继承另一个类,需要使用extends关键字。

class 子类 extends 父类{
    
}
3.2.4 继承的使用
image-20220728193224717.png

定义鸟类bird

public class Bird {
    private String name;

    public Bird(String name) {
        this.name = name;
    }

    public Bird() {
    }
    public void sound(){
        System.out.println("吱吱吱。。。");
    }
    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

定义猫类cat

public class Cat {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    public Cat() {
    }
    public void sound(){
        System.out.println("喵喵喵");
    }
    public void climbTrees(){
        System.out.println("上树中......");
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

在这两个类中我们看到有很多相同的属性和方法,既然有重复的地方我们可以使用继承了

定义动物类Animal

将鸟类和猫类相同的属性和方法写在动物类中

public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public Animal() {
    }
    public void sound(){
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

然后将两个类作为子类继承动物类,并且将之前的相同代码删除,留下不同属性方法。

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }
    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }
    public void climbTrees(){
        System.out.println("上树中......");
    }
}

注意:

  • 继承中的super(调用父类成员)
  • super调用父类构造函数,必须是子类构造函数的第一行
  • super只能出现在子类(子类的普通方法或构造方法)中,而不是其他位置
  • 具有访问权限的限制,如无法通过super访问父类private成员
  • 无法继承父类的构造方法

验证子类是否继承到了父类的数据

public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();//实例化子类对象
        Cat cat = new Cat();//实例化子类对象
        bird.setName("鸟爷");//通过父类的属性方法为子类赋值
        cat.setName("猫哥");//通过父类的属性方法为子类赋值
        System.out.println(bird.getName());
        System.out.println(cat.getName());
    }
}

结果为:
鸟爷
猫哥

结果说明子类是继承了父类的数据的。

对象的属性可以通过set方法赋值

3.2.5 方法重写

如果子类从父类继承的方法不能满足子类的需要,或者不适合子类的需要。

此时子类可以将从父类继承的方法重写定义成满足自己需要的方法。

重新定义称为重写。

在上边的例子中,鸟和猫都是有一个相同发出叫声的sound方法的,但是鸟叫和猫叫显然是不一样的,不能满足子类的需要,这时可以使用方法重写

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }

    @Override//方法重写
    public void sound() {
        System.out.println("吱吱吱!");
    }

    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }

    @Override//方法重写
    public void sound() {
        System.out.println("喵喵喵");
    }

    public void climbTrees(){
        System.out.println("上树中......");
    }
}

调用一下两个子类sound方法

public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();
        Cat cat = new Cat();
        bird.sound();
        cat.sound();
    }
}

结果为:
吱吱吱!
喵喵喵

实现了具体叫声的不同

方法重写的注意事项

  • 方法重写时,方法的返回值类型 方法名 参数列表都要与父类一样。(同名,同参,同返回)
  • 子类方法覆盖父类方法,必须要保证权限大于等于父类权限
class Fu{   
    void show(){}
    public void method(){}
}
class Zi extends Fu{
    public void show(){}  //扩大show的访问权限,编译运行没问题
    void method(){}       //缩小method的访问权限,编译错误
}
  • 方法重写时,子类不能缩小父类抛出的异常
 Bird bird = new Bird("鸟爷");//构造方法直接赋值
Cat cat = new Cat("猫哥");//构造方法直接赋值
3.2.6 创建子类对象时的运行规则
  1. 第一个方面:构造函数的调用顺序问题
    1. new子类时,首先调用子类构造函数,但是不执行子类构造函数
    2. 子类构造函数被调用后立即调用父类的构造函数。
  2. 第二个方面:属性初始化顺序的问题
    1. 从Object类开始初始化
    2. 然后依次从父到子的顺序初始化(哪个类定义的属性,就由哪个类负责初始化)
image-20220728203550857.png

代码编写步骤:

第一步:重构父类构造

  • 首先为父类添加构造,父类的构造为父类中定义的属性初始化
  • 子类定义的特有属性不是父类负责初始化的。

第二步:重构子类构造

  • 子类构造应该获得所有属性的初始值,本例中共6个属性,其中5个是父类的,1个是自己的
  • 子类构造的第一行必须是调用父类构造,
  • 如果不写super(),那么默认再子类第一行添加调用父类无参的构造super()
  • 子类再调用父类构造代码super()的下面负责给自己的属性赋值

第三步:重构实例化子类对象

  • 再测试类中new子类时,要为子类传入属性的参数

第四步:测试

3.2.7 继承的注意事项
  • 类只支持单继承,不允许多继承
  • 多个类子可以继承一个父类
  • 允许多层继承
class A{}
class B extends A{}   // 类B继承类A,类B是类A的子类
class C extends B{}   // 类C继承类B,类C是类B的子类,同时也是类A的子类
  • 子类和父类是一种相对的概念
  • 所有类都有一个父类object,如果一个类没有显示定义父类,那么默认继承Object类

4. 多态

4.1 认识多态

多态一词的通常含义是指能够呈现出多种不同的形式或形态。而在程序设计术语中,它意味着一个特定类型的变量,可以引用不同类型的对象,并且能自动的调用引用的对象的方法。也就是根据用到的不同对象类型,响应不同的操作。

方法重写是实现多态的基础。

多态的实现方法有两种

  • 方法重写(公认的多态)
  • 方法重载(非公认的多态)

4.2 多态的语法格式

多态的格式:父类引用指向子类实例(即为里氏替换原则)

父类类型  变量名 = new 子类类型();
变量名.方法名();
Pet pet = new Cat();
pet.toString();

4.3 多态的实现

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }

    @Override//方法重写
    public void sound() {
        System.out.println("吱吱吱!");
    }

    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }

    @Override//方法重写
    public void sound() {
        System.out.println("喵喵喵");
    }

    public void climbTrees(){
        System.out.println("上树中......");
    }
}

多态表现为:

public class Test {
    public static void main(String[] args) {
        Animal animal = new Bird("鸟爷");//父类引用指向子类实例
        animal.sound();
        animal = new Cat("猫哥");//父类引用指向子类实例
        animal.sound();
    }
}

结果为:
吱吱吱!
喵喵喵

4.4 多态的转型

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

  • 向上转型就是父类引用指向子类对象
  • 向下转型就是子类引用指向父类对象
向上转型

向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。

使用格式:

父类类型  变量名 = new 子类类型();
向下转型

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

如果是直接创建父类对象,是无法向下转型的!

使用格式:

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

4.5 instanceOf运算符解决子类扩展方法的调用

多态有优势,但是也有劣势,向上转型后,只能使用父类共性的内容,而无法使用子类特有的功能。

可以通过instanceOf运算符来解决这个问题

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }

    @Override//方法重写
    public void sound() {
        System.out.println("吱吱吱!");
    }

    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }

    @Override//方法重写
    public void sound() {
        System.out.println("喵喵喵");
    }

    public void climbTrees(){
        System.out.println("上树中......");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Bird("鸟爷");//构造方法直接赋值
        animal.sound();
        if ( !(animal instanceof Bird) ){
            System.out.println("类型不匹配,不能转换");
            return;
        }else {
            Bird bird = (Bird) animal;//向下转型,实现子类特有的功能
            bird.fly();
        }
    }
}

结果为:
吱吱吱!
时速300km/h中。。。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容