『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语言赋予了特殊含义的单词. 特点:关键字中所有的字母都为小写. ... 
随机推荐
- 19.9 Boost Asio 同步字典传输
			这里所代指的字典是Python中的样子,本节内容我们将通过使用Boost中自带的Tokenizer分词器实现对特定字符串的切割功能,使用Boost Tokenizer,可以通过构建一个分隔符或正则表达 ... 
- 【scikit-learn基础】--『回归模型评估』之损失分析
			分类模型评估中,通过各类损失(loss)函数的分析,可以衡量模型预测结果与真实值之间的差异.不同的损失函数可用于不同类型的分类问题,以便更好地评估模型的性能. 本篇将介绍分类模型评估中常用的几种损失计 ... 
- 使用window.print进行前端打印,批量打印,设置分页,ie、火狐下设置页眉页脚
			window.print() print() 方法用于打印当前窗口的内容.谷歌调用 print() 方法会产生一个打印预览弹框,让用户可以设置打印请求. 但谷歌貌似不能自定义设置页眉页脚的文字:ie和 ... 
- 【链表】双向链表的介绍和基本操作(C语言实现)【保姆级别详细教学】
			双向链表 文章目录 前言 双向链表的基本介绍 一些链表的分类 带头双向循环链表的基本结构 双向链表的实现 结点的定义.头指针的创建 开辟结点接口 初始化头结点接口 打印接口 尾插接口 尾删接口 头插接 ... 
- 模式识别实验:基于主成分分析(PCA)的人脸识别
			前言 本文使用Python实现了PCA算法,并使用ORL人脸数据集进行了测试并输出特征脸,简单实现了人脸识别的功能. 1. 准备 ORL人脸数据集共包含40个不同人的400张图像,是在1992年4月至 ... 
- P3509 [POI2010] ZAB-Frog 题解
			题目链接:ZAB-Frog 基于一个根据距离第 \(k\) 大的事实: 容易知道,对于红色的点而言,与它相近最近的 \(k\) 个点是连续的.而第 \(k\) 远的要么是最左侧要么是最右侧.而我们注意 ... 
- CF1045G AI robots题解
			题目链接:洛谷 或者 CF 本题考虑转化为 cdq 分治模型 对于 cdq 分治来说,只需要考虑左边对右边的影响,那我们要考虑该怎样设置第一维度的左右对象.很显而易见的是抛开 \(q\) 限制而言,我 ... 
- Git 简明教程(一)
			版本控制工具,早期的vss tfs svn等,这些应该是老一辈程序员常用的工具.目前 git 已经在版本控制领域占主流的地位,因为国外的github 和国内的码云 gitee 均用的是git. git ... 
- MySQL 报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket
			MySQL 报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket 一.错误现场还原: 下面我们通过三种方式 ... 
- 承前启后,Java对象内存布局和对象头
			承前启后,Java对象内存布局和对象头 大家好,我是小高先生.在我之前的一篇文章<并发编程防御装-锁(基础版)>中,我简要介绍了锁的基础知识,并解释了为什么Java中的任何对象都可以作为锁 ... 
