『Java 语法基础』面向对象有哪些特性

面向对象编程(OOP) 是一个将现实世界抽象为一系列对象的编程范式,这些对象通过消息传递机制来互相交流和协作。
OOP 的主要特性包括四个基本概念:封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)以及抽象(Abstraction)。
封装(Encapsulation)
封装是一种将数据(属性)和行为(方法)绑定在一起的方法。
通过封装,可以隐藏对象的具体实现细节,仅暴露出有限的接口供外界访问。
优势
封装的优势:
- 增强安全性:隐藏内部实现细节,防止外部直接访问对象内部的数据,减少因误用导致的错误
这里我编写了一个 UserCredentials 类,来进行演示一下 增强安全性,分别体现在什么地方,代码如下:
/**
* @author BNTang
* @description 用户凭证类
*/
public class UserCredentials {
// 私有属性,外部无法直接访问
private String username;
private String password;
// 公有的构造函数,用于初始化用户名和密码
public UserCredentials(String username, String password) {
this.username = username;
this.password = password;
}
// 公有的方法,用于验证密码是否正确
public boolean authenticate(String inputPassword) {
return inputPassword != null && inputPassword.equals(this.password);
}
// 获得用户名的公有方法
public String getUsername() {
return this.username;
}
// 重置密码的方法,增加安全性校验
public void resetPassword(String oldPassword, String newPassword) {
if (authenticate(oldPassword)) {
this.password = newPassword;
System.out.println("密码重置成功。");
} else {
System.out.println("旧密码不正确,密码重置失败。");
}
}
// 私有的设置密码方法,外部无法访问
private void setPassword(String password) {
this.password = password;
}
}
在我提供的 UserCredentials 类的代码中,隐藏内部实现细节、防止外部直接访问对象内部的数据以及减少因误用导致的错误的概念都得到了实现。
- 隐藏内部实现细节:
private void setPassword(String password) {
this.password = password;
}
setPassword 方法是私有的 (private),意味着它只能在类内部被调用。外部代码不能直接调用此方法来设置密码,这正是隐藏内部实现细节的体现。
- 防止外部直接访问对象内部的数据
private String username;
private String password;
这段代码中,用户名 (username) 和密码 (password) 被声明为私有变量 (private),这意味着它们不能从类的外部直接访问,只能通过类提供的公有方法(如构造方法、getUsername、authenticate 和 resetPassword 方法等)来间接访问或修改。这种机制有效地保护了类的内部数据。
- 减少因误用导致的错误
public void resetPassword(String oldPassword, String newPassword) {
if (authenticate(oldPassword)) {
this.password = newPassword;
System.out.println("密码重置成功。");
} else {
System.out.println("旧密码不正确,密码重置失败。");
}
}
在 resetPassword 方法中,通过 authenticate 方法校验旧密码是否正确,只有在旧密码正确的情况下才允许用户设置新密码。这样的设计减少了因为外部代码错误使用(如直接设置密码而不进行旧密码验证)导致的安全问题,同时也确保了类内部数据的完整性和安全性。
- 提高复用性:封装后的对象可以作为一个黑盒被重复使用,无需关心对象内部的复杂逻辑
- 封装后的对象作为一个黑盒被重复使用体现在:
UserCredentials adminCredentials = new UserCredentials("admin", "adminPass");
UserCredentials userCredentials = new UserCredentials("user", "userPass");
// 在不同场景中重复使用对象:
if (adminCredentials.authenticate("adminPass")) {
// 执行管理员操作
}
if (userCredentials.authenticate("userPass")) {
// 执行用户操作
}
adminCredentials 和 userCredentials 是 UserCredentials 的实例,在创建它们之后可以多次使用其 authenticate 方法来验证密码,这里的实例就像是提供认证功能的黑盒,使用者不必关心里面的逻辑是怎样的。
- 无需关心对象内部的复杂逻辑体现在 :
private String username;
private String password;
private void setPassword(String password) {
this.password = password;
}
由于 username 和 password 属性被声明为私有的,外部代码不能直接访问或修改它们。设置密码的逻辑被隐藏在 setPassword 方法中,而这个方法也是私有的。外部代码需要通过公有方法如构造函数或 resetPassword 这些公有接口进行操作,因此外部代码不必关心如何存储或验证密码的内部逻辑,只需调用这些公有方法即可实现功能。
- 易于维护:封装的代码更易理解与修改,修改内部实现时不会影响到使用该对象的代码
- 封装的代码更易理解与修改体现在:
public boolean authenticate(String inputPassword) {
return inputPassword != null && inputPassword.equals(this.password);
}
public void resetPassword(String oldPassword, String newPassword) {
if (authenticate(oldPassword)) {
this.password = newPassword;
System.out.println("密码重置成功。");
} else {
System.out.println("旧密码不正确,密码重置失败。");
}
}
在 authenticate 和 resetPassword 这两个公有方法中,封装的代码很易于理解:一个用于验证密码,一个用于重新设置密码。如果我们需要修改密码的存储逻辑,只需修改这些方法的内部逻辑,而无需修改方法的签名或其他使用这些方法的代码。
- 修改内部实现时不会影响到使用该对象的代码体现在:
private String username;
private String password;
因为 username 和 password 是私有属性,所以它们对外部代码是不可见和不可访问的。我们可以在不改变任何使用 UserCredentials 对象的代码的情况下,自由改变这些属性的内部表示方法(比如对密码进行加密存储)。因为任何这样的改变都会被 UserCredentials 类的公共接口所封装和抽象化,从而不会泄露出去或者影响到依赖于这些公共接口的代码。
- 接口与实现分离:提供清晰的接口,使得对象之间的依赖关系只基于接口,降低了耦合度
- 提供清晰的接口体现在:
public boolean authenticate(String inputPassword);
public void resetPassword(String oldPassword, String newPassword);
public String getUsername();
这些公共方法形成了 UserCredentials 类的接口,它为外部代码提供了清晰的通信协议,明确了可以进行的操作。使用这个类的代码只需要知道这些方法的声明和预期行为,不需要了解它们背后的具体实现。
- 使得对象之间的依赖关系只基于接口体现在:
UserCredentials credentials = new UserCredentials("username", "password");
boolean valid = credentials.authenticate("password");
只要 authenticate 方法的接口保持不变,外部代码就可以正常工作,完全无须关心 UserCredentials 内部是如何处理认证逻辑的。
- 降低了耦合度体现在:
private void setPassword(String password) {
// 假设这里改用了一种新的加密方式来设置密码
this.password = encryptPassword(password);
}
即使改变了 setPassword 方法的内部实现(如加密),由于这个方法是私有的,外部代码不会受到影响。这种隔离提高了系统的模块化,使得各个部分可以独立变化而不互相干扰,从而降低了耦合度。
- 隐藏实现细节,简化接口:用户只需知道对象公开的方法,不必了解其内部的复杂过程
应用场景
封装的应用场景:
- 类的设计:在类定义时,通常将属性私有化(private),通过公共的方法(public methods)来访问和修改这些属性
- 模块化组件:在设计模块化的系统时,每个组件都通过封装来定义自己的行为和接口,使得系统更易于组合和扩展
- 库和框架的开发:开发者提供库和框架时,会通过封装隐藏复杂逻辑,只暴露简洁的 API 接口给其他开发者使用
- 隔离变化:将可能变化的部分封装起来,变化发生时,只需修改封装层内部,不影响外部使用
通过封装,能够构建出结构清晰、易于管理和维护的代码。
完整代码可在此查阅:GitHub
继承(Inheritance)
继承是一种能够让新创建的类(子类或派生类)接收另一个类(父类或基类)的属性和方法的机制。
在 Java 中,继承是通过使用 extends 关键字来实现的。从理论上解释一下,然后再通过代码示例来加深理解。
IS-A 关系
IS-A 是一种表达类之间关系的方式,主要用来表明一个实体(子类)是另一个实体(父类)的一种特殊类型。例如,Cat(猫)是 Animal(动物)的一种特殊类型。因此,可以说 Cat IS-A Animal。
里氏替换原则(Liskov Substitution Principle)
这是一个面向对象设计的原则,它表明如果 S 是 T 的一个子类型(在 Java 中意味着 S 类继承自 T 类),那么任何期望 T 类的对象的地方都可以用 S 类的对象来替换,而不会影响程序的行为。
向上转型(Upcasting)
向上转型是指子类类型的引用自动转换成父类类型。向上转型在多态中是常见的,它允许将子类的对象赋值给父类的引用。例如,可以将 Cat 类型的对象赋值给 Animal 类型的引用。
以代码形式展示上述概念:
/**
* 动物
*
* @author BNTang
* @date 2024/03/10 09:36:41
* @description 创建一个表示动物的基类(父类)
*/
class Animal {
// 动物类有一个叫的方法
public void makeSound() {
System.out.println("动物发出声音");
}
}
// 创建一个 Cat 类(子类),继承自 Animal 类
class Cat extends Animal {
// 重写父类的 makeSound 方法
@Override
public void makeSound() {
// 这里的调用体现了多态性,即 Cat 的叫声不同于一般 Animal
System.out.println("猫咪喵喵叫");
}
}
public class InheritanceExample {
public static void main(String[] args) {
// Upcasting: 将 Cat 对象向上转型为 Animal 类型
Animal myAnimal = new Cat();
// 虽然 myAnimal 在编译时是 Animal 类型,但实际执行的是 Cat 的 makeSound 方法
myAnimal.makeSound();
// 创建一个 Animal 类型的对象,调用 makeSound 方法
Animal anotherAnimal = new Animal();
anotherAnimal.makeSound();
// 这里可以看到,Cat 对象(myAnimal)能够替换 Animal 对象(anotherAnimal)的位置,
// 并且程序的行为没有发生错误,体现了里氏替换原则
}
}
定义了两个类:Animal 和 Cat。
Cat 类继承自 Animal 类,并重写了 makeSound 方法。在 main 方法中,创建了一个 Cat 对象,并将其向上转型为 Animal 类型的引用 myAnimal。调用 myAnimal 的 makeSound 方法时,会执行 Cat 类的重写方法而不是 Animal 类的方法,这就体现了多态性和里氏替换原则。同时,Cat 对象(向上转型后的 myAnimal)可以在任何需要 Animal 对象的地方使用,这也满足了 IS-A 关系的定义
完整代码可在此查阅:GitHub
多态(Polymorphism)
多态可以允许使用一个统一的接口来操作不同的底层数据类型或对象。多态分为 编译时 多态和 运行时 多态两种类型。
编译时多态(方法的重载),也被称为静态多态,主要是通过 方法重载(Method Overloading)来实现的。方法重载指的是在同一个类中存在多个同名的方法,但这些方法的参数列表不同(参数数量或类型不同)。
编译器根据方法被调用时传入的参数类型和数量,来决定具体调用哪个方法。这种决策是在编译时做出的,因此称为编译时多态。
方法重载
代码示例:
/**
* 打印机类
* 用于演示方法重载
*
* @author BNTang
*/
class Printer {
/**
* 打印字符串
*
* @param content 要打印的字符串
*/
public void print(String content) {
System.out.println("打印字符串: " + content);
}
/**
* 重载 print 方法,参数类型为 int,与打印字符串的方法区分开来
*
* @param number 要打印的数字
*/
public void print(int number) {
System.out.println("打印数字: " + number);
}
}
public class OverloadingExample {
public static void main(String[] args) {
Printer printer = new Printer();
// 调用 print 方法打印字符串
printer.print("Hello, World!");
// 调用重载的 print 方法打印数字
printer.print(12345);
// 编译器根据参数类型来决定调用哪个方法
}
}
运行时多态,也被称为动态多态或动态绑定,是通过 方法覆盖(Method Overriding)实现的。
运行时多态是在继承的基础上工作的,所以只要其中子类覆盖父类的方法。
运行时多态的决策是在程序执行期间进行的,即虚拟机在运行时刻根据对象的实际类型来确定调用哪个类中的方法。
方法覆盖
代码示例:
/**
* 动物
* 创建一个表示动物的基类(父类)
*
* @author BNTang
*/
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
// 向上转型
Animal animal = new Dog();
// 运行时多态,调用的是 Dog 类的 makeSound 方法
animal.makeSound();
}
}
虽然在编译时 animal 的类型是 Animal,但是在运行时 JVM 会调用实际对象类型(也就是 Dog)的 makeSound 方法,因此输出的将是 "汪汪汪",而不是 "动物发出声音"。这就是运行时多态的体现。
运行时多态的三个条件
- 继承:子类需要继承父类
- 方法覆盖:子类需要提供一个具体的实现,这个实现覆盖了父类的方法
- 向上转型:你可以将子类类型的引用转换为父类类型的引用(即将子类对象赋值给父类引用),之后通过这个父类引用来调用方法时,执行的将是子类的覆盖实现
利用多态写出可扩展性和可维护性更佳的代码,能够应对不断变化的需求。使得可以通过相同的接口来调用不同类的实现,提供了软件设计的灵活性。
完整代码可在此查阅:GitHub
抽象(Abstraction)

『Java 语法基础』面向对象有哪些特性的更多相关文章
- Java语法基础(1)
Java语法基础(1) 1. Java是一门跨平台(也就是跨操作系统)语言,其跨平台的本质是借助java虚拟机 (也就是JVM(java virtual mechinal))进行跨平台使用. ...
- 2018.3.5 Java语言基础与面向对象编程实践
Java语言基础与面向对象编程实践 第一章 初识Java 1.Java特点 http://www.manew.com/blog-166576-20164.html Java语言面向对象的 Java语言 ...
- C#语法基础和面向对象编程
1.C#语法基础 http://www.cnblogs.com/tonney/archive/2011/03/16/1986456.html 2.C#与面向对象基础 很棒的资源,简明扼要,介绍的非常清 ...
- java语法基础(三)
类和对象 面向对象语言概述 java是一种面向对象的语言,什么是面向对象的语言? 要搞清楚什么是面向对象语言,我们需要相对的了解一下面向过程的语言. java入门阶段,我们又给大家说过一些语言的分类, ...
- Java语法基础(一)----关键字、标识符、常量、变量
一.关键字: 关键字:被Java语言赋予特定含义的单词.组成关键字的字母全部小写.注:goto和const作为保留字存在,目前并不使用.main并不是关键字. 二.标识符: 标识符:就是给类,接口,方 ...
- java语法基础(一)
这个是自己前两年做java视频教程时候的课件.感兴趣的同学可以参考下. 这里是纯粹的语法行总结. editplus的使用 选择项目目录 打开editplus 左侧目录导航栏 可以打开盘符,文件夹 可以 ...
- JAVA语法基础作业——动手动脑以及课后实验性问题 (八)
一.动手动脑 运行AboutException.java示例,了解Java中实现异常处理的基础知识. 1)源代码 import javax.swing.*; class AboutException ...
- java语法基础
Java的基本符号(token) Java的单词符号有五种:关键字.标识符.常量.分隔符和操作符. Java的字符集 Java 采用一种称为unicode的字符集,该字符集合是一种新的编码标准,与常见 ...
- Java语法基础(三)----选择结构的if语句、switch语句
[前言] 流程控制语句: 在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的.也就是说程序的流程对运行结果有直接的影响.所以,我们必须清楚每条语句的执行流程.而且,很多时候我们要通过 ...
- 黑马程序员——【Java基础】——Java语法基础
---------- android培训.java培训.期待与您交流! ---------- 一.关键字 1.定义和特点 定义:被Java语言赋予了特殊含义的单词. 特点:关键字中所有的字母都为小写. ...
随机推荐
- BigDecimal详解和精度问题
JavaGuide :「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识. BigDecimal 是大厂 Java 面试常问的一个知识点. <阿里巴巴 Java 开发 ...
- 驱动开发:内核读取SSDT表基址
在前面的章节<X86驱动:挂接SSDT内核钩子>我们通过代码的方式直接读取 KeServiceDescriptorTable 这个被导出的表结构从而可以直接读取到SSDT表的基址,而在Wi ...
- TF-VAEGAN:添加潜在嵌入(Latent Embedding)的VAEGAN处理零样本学习
前面介绍了将VAE+GAN解决零样本学习的方法:f-VAEGAN-D2,这里继续讨论引入生成模型处理零样本学习(Zero-shot Learning, ZSL)问题.论文"Latent Em ...
- Spring Cloud Config核心功能和原理解析
配置管理的前世今生 随着技术的发展,配置项管理变得越来越简单,尽管如今它只限于管理业务属性或者配置初始化参数等等,但是当年它可肩负着 Spring IOC 的光荣使命,风光无限. 想当年刚入行的时候还 ...
- 超简单实用的4个PPT操作技巧
作为我们IT岗位的兄弟姐妹们,一定少不了各种PPT的展示,很多IT大佬总是不屑于PPT的美观,认为只要演讲有干货,格式无所谓,甚至都不需要PPT. 话是这样说,但其实无非就是觉得调整美化实在是浪费时间 ...
- Jackson objectMapper.readValue 方法 详解
直接说结论方便一目了然: 1. 简单的直接Bean.class 2. 复杂的用 TypeReference 这样就完事了. public class TestMain2 { public static ...
- Linux-查看文本中第m行到n行的内容
如何查看文件第6749行到6758行? 方式一:使用sed命令 不显示行号:sed -n 'M,Np' fileName 例如:sed -n '6749,6758p' hive-default.x ...
- 【leetcode】合并 k 个有序链表,我给了面试官这 5 种解法
开胃菜 在进入本节的正题之前,我们先来看一道开胃菜. 题目 21. 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1 ...
- patch命令
patch命令 patch指令让用户利用设置修补文件的方式.修改.更新原始文件,倘若一次仅修改一个文件,可直接在指令列中下达指令依序执行,如果配合修补文件的方式则能一次修补大批文件,这也是Linux系 ...
- [技术选型与调研] 流程引擎(工作流引擎|BPM引擎):Activiti、Flowable、Camunda
1 概述:流程与流程引擎 低代码平台.办公自动化(OA).BPM平台.工作流系统均需要[流程引擎]功能 BPM平台与工作流系统的区别,参见本文档:3.2 章节 流程引擎是任务分配软件(例如业务流程管理 ...