当前位置: 首页 > news >正文

Java OOP

Java 相比于其他面向对象语言(比如C++)在设计上很大的不同,准确理解 Java 的基本语法元素是写好Java程序的第一步。

面向对象三大特点:封装、继承、多态。封装隐藏了事务内部的实现细节,提供了合理的边界,避免外部调用者接触到内部细节。继承为了代码复用,它是紧耦合的,父类代码修改,子类行为也会跟着变。实践中过度滥用继承可能会起到反效果。多态为了接口复用,一个接口,多种实现。这里可能会立即联想到重写(override)和重载(overload)、向上转型。重写是父子类中相同名字和参数的方法,不同的实现;重载则是相同名字的方法,但是不同的参数,本质上这些方法签名是不一样的。特别说明的一点,方法名称和参数一致,但返回值不同,不是有效的重载,这点Java和C++一致,编译都会报错。

Java的权限控制相对严格,引入 package 包,有更好的封装性,没有像C++中的 friend 关系来提供更宽松的访问权限。通过关键字private、default(不写任何关键字)、protected和public来控制类、方法和变量的访问权限。类(包括抽象类)只有单继承,没有 C++ 中的多重继承。接口 interface 可以实现多继承,具体差异和C++类机制比较。

public class Parent {private int privateField = 0; // 只能在Parent类内部访问int packageField = 0; // 默认权限,同一个包内可以访问protected int protectedField = 0; // 子类和同一个包内可以访问public int publicField = 0; // 任何地方都可以访问private void privateMethod() { // 只能在Parent类内部调用System.out.println("privateMethod");}void packageMethod() { // 默认权限,同一个包内可以调用System.out.println("packageMethod");}protected void protectedMethod() { // 子类和同一个包内可以调用System.out.println("protectedMethod");}public void publicMethod() { // 任何地方都可以调用System.out.println("publicMethod");}
}public class Child extends Parent {void test() {int i = privateField; // 错误:不能从Parent类外部访问privateFieldi = packageField; // 错误:默认权限不包括子类i = protectedField; // 正确:子类可以访问i = publicField; // 正确:任何地方都可以访问privateMethod(); // 错误:不能从Parent类外部调用privateMethodpackageMethod(); // 错误:默认权限不包括子类protectedMethod(); // 正确:子类可以调用publicMethod(); // 正确:任何地方都可以调用}
}

接口是对行为的抽象,它是抽象方法的集合(声明性的),方法定义和实现分离。接口不能实例化;不能包含任何非常量成员,任何 field 都是隐含着 public static final 的意义;没有非静态方法实现(要么抽象方法,要么静态方法,先不考虑 default method 新特性)。比如java.util.List。

抽象类是不能实例化的类,用 abstract 修饰 class,主要为了代码重用。除了不能实例化,形式上和一般Java类没有太大区别,可以有一个或者多个抽象方法,也可以没有抽象方法。抽象类大多用于抽取Java类的共用方法或共同成员变量,通过继承实现代码复用,比如java.util.AbstractList。

Java类实现 interface 使用 implements 关键词,继承 abstract class 使用 extends 关键词。

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//...
}

注:

1. 有些特定场景需要抽象出与具体实现、实例化无关的通用逻辑,或者纯调用关系,使用传统抽象类会陷入到单继承的窘境,常见做法是由静态方法组成工具类(Utils),比如java.util.Collections。

2. 为接口添加任何抽象方法,所有实现了这个接口的类必须实现新增方法,否则会编译错误。对于抽象类,如果我们添加非抽象方法,其子类只会享受到能力扩展,不用担心编译出问题。

3. 接口的职责也不仅仅限于抽象方法的集合,也有各种不同的实践。有一类没有任何方法的接口,通常叫作 Marker Interface,它是为了声明某些东西,比如Cloneable、Serializable等。这种用法也存在于业界其他Java产品代码中。这似乎和Annotation异曲同工,但它的好处是简单直接。因为Annotation可以指定参数和值,在表达能力上要更强大,更多人选择使用Annotation。

4. Java 8 以后接口也可以有方法实现。从 Java 8 开始,interface 增加对 default method 的支持。Java 9 以后甚至可以定义 private default method。Default method 提供了一种二进制兼容扩展已有接口的办法,比如 java.util.Collection,它是 collection 体系的 root interface,在 Java 8 中添加了一系列 default method,主要是增加 Lambda、Stream 相关的功能。类似 Collections 的工具类,很多方法都适合作为 default method 实现在基础接口里面。

public interface Collection<E> extends Iterable<E> {/*** Returns a sequential Stream with this collection as its source * ...**/default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}}

Java 8增加支持了函数式编程(functional interface),就是只有一个抽象方法的接口,建议使用@FunctionalInterface Annotation来标记。Lambda表达式本身可以看作是一类functional interface,某种程度上这和面向对象算是两码事。我们熟知的Runnable、Callable等都是functional interface。

面向对象必须掌握的基本设计原则——S.O.L.I.D原则,这些原则更多应作为参考,并不是完全遵守这些原则,实践中要根据具体情况权衡。

  • 单一职责(Single Responsibility),类或者对象最好是只有单一职责,某个类承担着多种职责(职责过重)可以考虑拆分。
  • 开关原则(Open-Close, Open for extension, close for modification),设计要对扩展开放,对修改关闭。程序设计应保证平滑的扩展性,尽量避免因为新增同类功能去修改已有实现,可以少些回归(regression)问题。
  • 里氏替换(Liskov Substitution),面向对象的基本要素之一,继承关系抽象时凡是可以用父类的地方,都可以用子类替换。
  • 接口分离(Interface Segregation),设计类和接口时,如果一个接口定义了太多方法,子类很可能面临两难,只有部分方法对它有意义,破坏了内聚性。这种可以拆分多个接口,将行为进行解耦。某个接口的变化不会对使用其他接口的子类造成影响。
  • 依赖反转(Dependency Inversion),实体应该依赖于抽象而不是实现。高层次模块不应该依赖于低层次模块,而是应该基于抽象,更好保证产品代码之间适当耦合度。

Java 10 中引入了本地方法类型推断和 var 类型,这种语法上的便利增强了程序对实现的依赖,但微小的类型泄漏却带来了便利的书写和更高的代码可读性。

// 按照里氏替换原则,我们应该这样定义变量,
List<String> list = new ArrayList<>();// 使用 var 类型可以简化
var list = new ArrayList<String>();// list 实际会被推断为 ArrayList <String>
ArrayList<String> list = new ArrayList<String>();

找个实际 case 遵循上面的原则改造代码实现,看看下面代码怎么优化更好些?

public class PushCenter {void servicePush(T extend Message msg>) {if (msg instanceof HuaweiPush) {// HUAWEI 通道// do somthing} else if(msg instanceof XiaomiPush) {// Xiaomi 通道// do somthing}// ...}
}

这个 case 中业务逻辑集中在一起,servicePush 方法较大,不利于测试维护;增加新的机型Push(比如给 Vivo 手机用户下行 push 消息)需要直接修改服务方法代码,违反开闭原则,可能会意外影响不相关的某个类型逻辑,灵活性和可扩展性较差;代码复用性低。

遵循开关原则和单一职责,把算法的定义和实现分离(听起来好像策略模式,但这里只是简单的分开,没有完全按照策略模式实现),改造如下,

public class PushCenter {private Map<Message.TYPE, ServiceProvider> providers;void servicePush(T extend Message msg) {providers.get(msg.getType()).service(msg);}
}interface ServiceProvider{void service(T extend Message msg) ;
}class XiaomiPushServiceProvider implements ServiceProvider{void service(T extend Message msg){// do somthing}
}class HuaweiPushServiceProvider implements ServiceProvider{void service(T extend Message msg) {// do something}
} 

参考:

Java中的委托(Delegation)_javadelegate-CSDN博客

Have Fun


http://www.mrgr.cn/news/78955.html

相关文章:

  • LeetCode 64. 最小路径和(HOT100)
  • STM32 ADC --- 多通道序列采样
  • 什么是sfp,onu,​为什么PON(​俗称“光猫”​)模块使用SC光纤接口
  • leetcode——移除数组
  • 【实体配置】.NET开源 ORM 框架 SqlSugar 系列
  • 【算法】时间复杂度空间复杂度
  • Spring 中的 @Component 注解
  • golang的wails框架在macos下的问题
  • NViST运行笔记
  • HHO-CNN-BiGRU-Attention哈里斯鹰优化算法卷积神经网络结合双向门控循环单元时间序列预测,含优化前后对比
  • 多模态(Multimodal)通常指的是系统或过程能够处理、整合和理解来自两个或多个不同模式(modality)的信息。
  • C语言——自我介绍_Gitee的基本使用
  • linux(centos) 环境部署,安装JDK,docker(mysql, redis,nginx,minio,nacos)
  • 开发系统准备与开发环境配置总结
  • 计算机网络复习1——导言和概论
  • 【Point-LIO】基于Ubuntu20.04的ROS1平台的Point-LIO部署Mid-360激光雷达
  • cocotb pytest
  • C++编写静态库
  • 【webApp之h5端实战】项目基础结构搭建及欢迎页面的实现
  • 【小白学机器学习42】进行多次抽样,样本的分布参数和总体的分布参数的关系
  • Python办公——openpyxl处理Excel每个sheet每行 修改为软雅黑9号剧中+边框线
  • HCIA-openGauss_1
  • 华为HarmonyOS 让应用快速拥有账号能力 -- 3 获取用户手机号
  • 【0347】Postgres内核 startup XLOG 之 核实 pg_wal 、 pg_wal/archive_status (1)
  • 树莓派明明安装了opencv和numpy,却找不到
  • Linux:内存文件 基础io