Java 面向对象的【三大特征】,你都掌握了吗
创始人
2024-03-14 23:49:43
0

文章目录

  • 前言
  • 一、封装
    • 1.封装的概念
    • 2.访问修饰限定符
    • 3.初识 “ 包 ”
    • 4. static 成员
      • static修饰成员变量
      • static修饰成员方法
      • static成员初始化
  • 二、继承
    • 1.为什么要继承
    • 2.继承的概念
    • 3.语法
    • 4.父类成员访问
      • 1.子类中访问父类的成员变量
      • 2.子类中访问父类的成员方法
    • 5. super 关键字
    • 6. 子类构造方法
    • 7. super 和 this
    • 8. protected 访问限定符
  • 三、多态
    • 1.什么是多态
    • 2.多态实现的条件
    • 3.重写
    • 4.向上转型
  • 总结


前言

上篇文章介绍了Java里【类和对象】的相关知识,了解了什么是面向对象,什么是对象,如何创建对象等等

在面向对象程序中,有 三大特点:封装,继承, 多态

本篇将介绍这三大特点的相关知识


提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎评论区指出讨论~ 废话不多说,发车

一、封装

1.封装的概念

简单来说:就是套壳屏蔽细节

比如:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务

但实际上:电脑真正工作的却是CPU、显卡、内存等一些硬件元件

在这里插入图片描述

计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可

那么封装就是如此:
将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互

那么如何进行隐藏呢?下面来认识一下“ 访问修饰限定符 ”


2.访问修饰限定符

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用

Java中提供了四种访问限定符:
述

protected 主要是用在继承中( 继承部分详细介绍 )
default 权限指:什么都不写时的 默认权限
访问权限除了可以限定类中成员的可见性,也可以控制的可见性


我们看这段代码:在这里插入图片描述
因为在 PetDog 这个类当中,定义“ 名字 ”这个成员变量时用 private 修饰,所以在 Test 这个类中就不能通过对象名 + 点号 直接访问这个变量了

那怎么办呢?
不能直接访问,那就 间 接 访 问

活人还能让尿憋死?

定义 getter 和 setter 这两个成员方法

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

快捷键:Alt + Insert —— 点击 Getter and Setter 即可

然后就可以通过 get 和 set 这两个成员方法进行访问:

    PetDog dog1 = new PetDog();dog1.setName("坦克");System.out.println(dog1.getName());

我们把 name 这个成员变量隐藏起来,不对外公开,只提供 setter 和 getter 这两个方法可以访问 name ,而用户也不需要关心 setter 和 getter 是如何实现的,这就是封装

一般情况下成员变量设置为 private,成员方法设置为 public,具体还得看业务需求


3.初识 “ 包 ”

在面向对象体系中,提出了一个软件包的概念即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包

有点类似于文件夹。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类
在这里插入图片描述

在Java中也引入了,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。

包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。

所以同一个包中不允许出现相同名称的类


4. static 成员

我们拿学生这个类来举例:

class Student{// 成员属性public String name;public String classroom;//构造方法public Student(String name, String classroom) {this.name = name;this.classroom = classroom;}// 成员方法public void doClass(){System.out.println(this.name + "在" + this.classroom + "上课");}}
public class Test {public static void main(String[] args) {Student student1 = new Student("张三", "高三一班");Student student2 = new Student("李四", "高三一班");Student student3 = new Student("王五", "高三一班");student1.doClass();student2.doClass();student3.doClass();}
}
//输出结果:
//张三在高三一班上课
//李四在高三一班上课
//王五在高三一班上课

这样的代码合适吗?

这三个同学是同一个班的,那么他们上课肯定是在同一个教室,那既然在同一个教室,那 能否给类中再加一个成员变量 classroom,来保存同学上课时的教室呢

在 Student 类中定义的成员变量( 例如name ),每个对象中都会包含一份( 称之为实例变量 ),因为需要使用这些信息来描述具体的某个学生

而现在要表示学生上课的教室,这个 classroom 并不需要每个学生对象中都存储一份,而是需要让所有的学生来 共享

在Java中,被 static 修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所 共享

所以这里的 classroom 应该用 static 修饰:

	public static String classroom;

static修饰成员变量

static 修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的

静态成员变量特性:
不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间( 堆 )中
既可以通过 对象 访问,也可以通过 类名 + 静态变量名 访问,但一般更推荐使用类名访问
存储在方法区当中
生命周期伴随类的一生( 随 类的加载而创建,随 类的卸载而销毁 )

既然 static 修饰的成员属性不属于对象了,那么刚才的代码中:" this.classroom "就是错误的写法

那么 doClass 的方法体就要变成:

public void doClass(){System.out.println(this.name + "在" + classroom + "上课");
}

static修饰成员方法

一般类中的数据成员都设置为 private ,而成员方法设置为 public ,那设置之后,Student 类中 classRoom 属性如何在类外访问呢?

	private static String classroom;

静态成员一般是通过静态方法来访问的

Java中,被 static 修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的

	...public static String getClassRoom(){return classRoom;}	public static void main(String[] args) {System.out.println(Student.getClassRoom());
}

不需要实例化对象,只需要用 类名 + 点号 就可以访问到 classroom 了

静态方法特性:
不属于某个具体的对象,是类方法
可以通过对象调用,也可以通过 类名 + 点号静态方法名 的方式调用,更推荐使用后者
不能在静态方法中直接访问任何非静态成员变量
静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用


static成员初始化

注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性

静态成员变量的初始化分为两种:就地初始化 和 静态代码块初始化

就地初始化:

	private static String classroom = "高三一班";

静态代码块初始化:
使用 static 定义的代码块称为静态代码块。一般用于初始化静态成员变量

	...static {classRoom = "高三一班";}...

二、继承

1.为什么要继承

Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。

比如:狗和猫,它们都是一个动物。使用Java语言来进行描述,就会设计出:

// Dog 类
public class Dog {string name;int age;float weight;public void eat() {System.out.println(name + "正在吃饭");}public void sleep() {System.out.println(name + "正在睡觉");}void Bark() {System.out.println(name + "汪汪汪~~~");}
}// Cat 类
public class Cat {string name;int age;float weight;public void eat() {System.out.println(name + "正在吃饭");}public void sleep() {System.out.println(name + "正在睡觉");}void mew() {System.out.println(name + "喵喵喵~~~");}
}

通过观察上述代码会发现,猫和狗的类中存在大量重复
比如猫和狗这两个类都定义了 name 、 age 、 weight 这些成员属性,也都定义了 eat 和 sleep 这两个成员方法
有一处不同的是:狗是汪汪叫,猫是喵喵叫

那能否将这些共性抽取呢?
面向对象思想中提出了 继承 的概念,专门用来进行共性抽取,实现代码 复用


2.继承的概念

继承( inheritance )机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展增加新功能,这样产生新的类,称派生类
继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用

在这里插入图片描述
上述图示中,Dog 和 Cat 都继承了 Animal 类
其中:Animal 类称为 父类 / 基类 / 超类
Dog 和 Cat 可以称为 Animal 的 子类 / 派生类
继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态( 后序讲 )


3.语法

在 Java 中如果要表示类之间的继承关系,需要借助** extends 关键字**,具体如下:

修饰符 class 子类 extends 父类 {// ... 
}

我们把刚刚的代码用 继承 体现一下:

// Animal 类
public class Animal {String name;int age;public void eat() {System.out.println(name + "正在吃饭");}public void sleep() {System.out.println(name + "正在睡觉");}
}// Dog 类
public class Dog extends Animal {void bark() {System.out.println(name + "汪汪汪~~~");}
}// Cat 类
public class Cat extends Animal {void mew() {System.out.println(name + "喵喵喵~~~");}
}// TestExtend 类
public class TestExtend {public static void main(String[] args) {Dog dog = new Dog();// dog 类中并没有定义任何成员变量// name 和 age 属性肯定是从父类 Animal 中继承下来的System.out.println(dog.name);System.out.println(dog.age);// dog 访问的 eat() 和 sleep() 方法也是从 Animal 中继承下来的dog.eat();dog.sleep();// bark()方法是自己特有的dog.bark();}
}

注意:
子类会将父类中的成员变量或者成员方法继承到子类中了
子类继承父类之后,必须要新添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了


4.父类成员访问

在继承体系中,子类将父类中的方法和字段( 属性 )继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?

1.子类中访问父类的成员变量

当子类和父类中的成员变量不重名时:

class A {int a = 10;int b = 20;
}
class B extends A {int c = 30;public void method() {System.out.println(this.a);// 访问从父类中继承下来的aSystem.out.println(this.b);// 访问从父类中继承下来的bSystem.out.println(this.c);// 访问子类自己特有的c}
}
public class Test {public static void main(String[] args) {B b = new B();b.method();}
}
// 输出结果是:
// 10
// 20
// 30

当子类和父类中的成员变量重名时:

class A {int a = 10;int b = 20;int c = 30;
}
class B extends A {int c = 40;public void method() {System.out.println(this.a);// 访问从父类中继承下来的 aSystem.out.println(this.b);// 访问从父类中继承下来的 bSystem.out.println(this.c);// 访问从父类中继承下来的 c ?还是子类自己的 c ?}
}
public class Test {public static void main(String[] args) {B b = new B();b.method();}
}

此时 输出的 c 的结果是 30 还是 40 呢?----答案是 40

总结
在子类方法中 或者 通过子类对象访问成员时
如果访问的成员变量子类中有,优先访问自己的成员变量。
如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
如果访问的成员变量与父类中成员变量同名,则优先访问自己的。

变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找


2.子类中访问父类的成员方法

原则和上文所总结的一致

但我们有了一个问题:
如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?

下面我们来认识一下 super 关键字


5. super 关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?

直接访问是无法做到的,Java提供了 super 关键字

该关键字主要作用:在子类方法中访问父类的成员

class A {int a = 10;int b = 20;int c = 30;public void func() {System.out.println(this.c);}
}
class B extends A {int c = 40;public void func() {System.out.println(this.c);}public void method() {super.func();// 访问父类的 func 方法System.out.println(super.c);// 访问从父类中继承下来的 c}
}
public class Test {public static void main(String[] args) {B b = new B();b.method();}
}
// 运行结果:
// 30
// 30

在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可

注意事项:
只能在非静态方法中使用
在子类方法中,访问父类的成员变量和方法


6. 子类构造方法

子类是类,所以在子类中同样也可以有构造方法
父子父子,先有父再有子
即:子类对象构造时,需要先调用父类构造方法,然后执行子类的构造方法

在这里插入图片描述

子类对象中成员是有两部分组成的, 基类继承下来的 以及 子类新增加的 两部分

在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整

注意:
若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super() 调用,即调用基类构造方法
如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败
在子类构造方法中,super(…) 调用父类构造时,必须是子类构造函数中第一条语句
super(…) 只能在子类构造方法中出现一次,并且不能和 this 同时出现


7. super 和 this

super 和 this 都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?

相同点:
都是Java中的关键字
只能在类的非静态方法中使用,用来访问非静态成员方法和字段
在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

不同点:
this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
在非静态成员方法中,this 用来访问本类的方法和属性,super 用来访问父类继承下来的方法和属性
在构造方法中:this(…) 用于调用本类构造方法,super(…) 用于调用父类构造方法,两种调用不能同时在构造方法中出现
子类的构造方法中一定会存在 super(…) 的调用,用户没有写编译器也会增加,但是 this(…) 用户不写则没有


8. protected 访问限定符

在类和对象章节中,为了实现封装特性, Java 中引入了访问限定符,主要限定: 或者 类中成员能否在类外 或者 其他包中被访问
述

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了// extend01 包中
public class B {private int a;protected int b;public int c;int d;
}// extend01 包中
// 同一个包中的子类
public class D extends B {public void method() {// super.a = 10; // 编译报错,父类private成员在相同包子类中不可见super.b = 20; // 父类中protected成员在相同包子类中可以直接访问super.c = 30; // 父类中public成员在相同包子类中可以直接访问super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问}
}// extend02包中
// 不同包中的子类
public class C extends B  {public void method() {// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问}
}// extend02包中
// 不同包中的类
public class TestC {public static void main(String[] args) {C c = new C();c.method();// System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问}
}

注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了

什么时候下用哪一种呢?
我们希望类要尽量做到 “封装”,即隐藏内部实现细节,只暴露出必要的信息给类的调用者
因此我们在使用的时候应该尽可能的使用比较严格的访问权限,例如如果一个方法能用 private,就尽量不要用 public


三、多态

1.什么是多态

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

听不懂,说人话:
举例1:打印机,都是具备打印功能,但是可以打印出黑白图片,和彩色图片
举例2:吃,都是吃这个动作,人吃饭,狗吃 shit,猫吃 fish

耐心往下看,看完之后,回过头再看多态的概念,你会恍然大悟的


2.多态实现的条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:
必须在继承体系下
子类必须要对父类中方法进行重写
通过向上转型调用重写的方法

重写是啥?向上转型是啥?往下看!


3.重写

重写( override )
也称为覆盖。重写是子类对父类非静态、非 private 修饰,非 final 修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。 即外壳不变,核心重写!

比如你和你老婆吵架了,老婆让你写一份检讨书,你写完了给老婆大人过目
老婆大人说:“ 不合格,重写!”
那你重写的当然还是这份检讨书,开头还是“ 亲爱的老婆大人 ”,格式还是检讨书的格式,结尾还是“ 老婆别生气了~ ”,但内容重写了
难不成你要重写成一篇《逍遥游》交给你老婆吗?

在程序中,重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

class A {public void func() {System.out.println("在父类中打印");}
}
class B extends A {public void func() {System.out.println("在子类中打印");}
}

方法重写的规则:
子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名和参数列表要完全一致
被重写的方法返回值类型可以不同,但是必须是具有父子关系的
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被 public 修饰,则子类中重写该方法就不能声明为 protected;父类被 static、private 修饰的方法、构造方法都不能被重写。
重写的方法, 可以使用 @Override 注解来显式指定,有了这个注解能帮我们进行一些合法性校验,例如不小心将方法名字拼写错了 ( 比如写成 aet ),那么此时编译器就会发现父类中没有 aet 方法,就会编译报错,提示无法构成重写

在这里插入图片描述

重写的设计原则:
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容

例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等

在这个过程当中,我们不应该在原来的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。

我们来看一段体现多态的代码:

public class Animal {String name;int age;public Animal(String name, int age) {this.name = name;this.age = age;}public void eat() {System.out.println(name + "吃饭");}
}public class Cat extends Animal { public Cat(String name, int age) {super(name, age);}@Override // 重写public void eat() {System.out.println(name+"吃鱼~~~");}
}public class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Override // 重写public void eat() {System.out.println(name+"吃骨头~~~");}
}// ---------------------分割线--------------------------------public class TestAnimal {// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法// 注意:此处的形参类型必须时父类类型才可以public static void eat(Animal a) { // 这里发生了向上转型a.eat();}public static void main(String[] args) {Cat cat = new Cat("元宝",2);Dog dog = new Dog("小七", 1);eat(cat);eat(dog);}
}

在上述代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的
当类的调用者在编写 eat 这个方法的时候,参数类型为 Animal (父类), 此时在该方法内部并不知道,也不关注当前的 a 引用指向的是哪个类型( 哪个子类 )的实例,此时 a 这个引用调用 eat 方法可能会有多种不同的表现( 和 a 引用的实例
相关 ),这种行为就称为 多态

	public static void eat(Animal a) { // 这里发生了向上转型a.eat();}

这里可能有人就迷惑了,什么玩意,看不懂
这就是接下来要讲的:向上转型


4.向上转型

实际就是创建一个子类对象,将其当成父类对象来使用

语法格式:父类类型 对象名 = new 子类类型()

	Animal animal = new Cat();

Animal 是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换

上面这行代码可以理解为,我 new 了一只猫,这个猫当然是动物,或者我 new 一条狗,这个狗当然也是动物,这样就是向上转型

当然,这是在实例化对象的时候发生的向上转型,也可以在方法传参以及返回值接收的时候发生向上转型
意思就是,我实参是猫,形参是动物 这里也发生了向上转型(见上述代价),或者我方法的返回值返回的是 new Cat() ,接收返回值用Animal animal = 方法名 这里也是向上转型

小伙伴们可以自己试一下~


总结

以上就是今天要讲的关于Java 面向对象的【三大特征】的内容,封装,继承,多态,相关的知识尤为重要,与君共勉

如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦🤪🤪🤪


上山总比下山辛苦
下篇文章见

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...