复用类(1):组合、继承

        复用代码是java众多引人注目的功能之一。但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情。

        上述方法常为C这类过程型语言所使用,但收效不是很好。正如java中所有事物一样,问题解决都是围绕着类展开的。可以通过创建新类来复用代码,而不必再从头开始编写。可以使用别人已开发并调试好的类。

        此方法的窍门在于使用类而不破坏现有程序代码。有两种达到这一目的方法。第一种方法非常直观:只需在新的类中产生现有类的对象。由于新的类是由现有类的对象所组成的,所以这种方法称为组合。该方法只是复用了现有程序代码的功能,而非它的形式。

        第二种方法则更细致一些,它按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的形式并在其中添加新代码。这种神奇的方式称为继承,而且编译器可以完成其中大部分工作。继承是面向对象程序设计的基石之一。

        就组合和继承而言,其语法和行为大多是相似的。由于它们是利用现有类型生成新类型,所有这样做极富意义。接下来将会了解这两种代码重用机制。

1 组合语法

        组合,只需将对象引用置于新类中即可。例如,假设你需要某个对象,他要具有多个String对象、几个基本类型数据,以及另一个类的对象。对于非基本类型的对象,必须将其引用置于新的类中,但可以直接定义基类类型数据:

class WaterSource {private String s;WaterSource() {System.out.println("WaterSource()");s = "Construccted";}public String toString() {return s;}
}public class SprinklerSystem {private String value1, value2, value3, value4;private WaterSource source = new WaterSource();private int i;private float f;public String toString() {return "value1 = " + value1 + " " + "value2 = " + value2 + " " + "value3 = " + value3 + " " + "value4 = "+ value4 + "\n" + "i = " + i + " " + " f = " + f + " source = " + source;}public static void main(String[] args) {SprinklerSystem sprinklers = new SprinklerSystem();System.out.println(sprinklers);}
}

        在上面两个类所定义的方法中,有一个很特殊:toString()。每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用。所以在SprinklerSystem的toString()的表达式中: " source = " + source;编译器将会得知你想要将一个String对象(“source = ”)同WaterSource相加。由于只能将一个String对象和另一个String对象相加,因此编译器会告诉你:“我将调用toString(),把source转成一个String!”这样做之后,他就能够将两个String连接到一起并将结果传递给System.out.println()。每当想要使所创建的类具备这样的行为时,仅需要编写一个toString()方法即可。

        类中域为基本类型时能够自动被初始化为零。但是对象引用会被初始化为null,而且如果你试图为它们调用任何方法,都会得到一个异常,运行时错误。很方便的是,在不抛出异常的情况下仍旧可以打印一个null引用。

        编译器并不是简单地为每一个引用都创建默认对象,这一点很有意义的,因为若真要那样做的话,就会在许多情况下增加不必要的负担。如果想初始化这些引用,可以在代码中的下列位置进行:

  1. 在定义对象的地方。这意味着它们总是能够在构造器被调用之前被初始化。
  2. 在类的构造器中。
  3. 就在正要使用这些对象之前,这种方式称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
  4. 使用实例初始化。

        以下是这四种方式的示例:

class Soap {private String s;Soap() {System.out.println("Soap()");s = "Constructed";}public String toString() {return s;}
}public class Bath {private String // Initializing at point of definition;s1 = "Happy", s2 = "Happy", s3, s4;private Soap castille;private int i;private float toy;public Bath() {System.out.println("Inside Bath()");s3 = "Joy";toy = 3.14f;castille = new Soap();}// Instance initialization{i = 47;}public String toString() {if (s4 == null)// Delayed initializations4 = "Joy";return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n"+ "toy = " + toy + "\n" + "castille = " + castille;}public static void main(String[] args) {Bath b = new Bath();System.out.println(b);}
}

        请注意,在Bath的构造器中,有一行语句在所有初始化产生之前就已经执行了。如果没有在定义处初始化,那么除非发生了不可避免的运行期异常,否则将不能保证信息在发送给对象引用之前已经被初始化。

        当toString()被调用时,它将填充s4的值,以确保所有的域在使用之时已被妥善初始化。

2 继承语法

        继承是所有OOP语言和Java语言不可缺少的组成部分。当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。

        组合的语法比较平实,但是继承使用的是一种特殊的语法。在继承过程中,需要先声明“新类与旧类相似”。这种声明是通过在类主体的左边花括号之前,书写后面紧随基类名称的关键字extends而实现的。当这么做时,会自动得到基类中所有的域和方法。例如:

class Cleanser {private String s = "Cleanser";public void append(String a) {s += a;}public void dilute() {append("dilute()");}public void apply() {append("apply()");}public void scrub() {append("scrub()");}public String toString() {return s;}public static void main(String[] args) {Cleanser x = new Cleanser();x.dilute();x.apply();x.scrub();System.out.println(x);}
}public class Detergent extends Cleanser {// Change a methodpublic void scrub() {append("Detergent的scrub()");super.scrub();}// Add methods to the interfacepublic void foam() {append("foam()");}public static void main(String[] args) {Detergent x = new Detergent();x.dilute();x.apply();x.scrub();x.foam();System.out.println(x);System.out.println("Testing base Class:");Cleanser.main(args);}
}

        这个程序示范了java的许多特性。首先,在Cleanser的append()方法中,我们用“+=”操作符将几个String对象连接成s,此操作符是被java设计者重载用以处理String对象的操作符之一(另一个是“+”)。

        其次,Cleanser和Detergent均含有main()方法。可以为每个类都创建一个main()方法。这种在每个类中都设置一个main()方法的技术可使每个类的单元测试都变得简便易行。而且在完成单元测试之后,也无需删除main(),可以将其留待下次测试。

        即使是一个程序中含有多个类,也只有命令行所调用的那个类的main()方法会被调用。因此,在此例中,如果命令行是java Detergent,那么Detergent.main()会被调用。即使Cleanser不是一个public类,如果命令行是java Cleanser,那么Cleanser.main()仍然会被调用。即使一个类只具有包访问权限,其public main()仍然是可访问的。

        在此例中,可以看到Detergent.main()明确调用了Cleanser.main(),并将从命令行获取的参数传递给它。当然,也可以向其传递任意的String数组。

        Cleanser中所有的方法都必须是public的,这一点非常重要。请记住,如果没有加任何访问权限修饰词,那么成员默认的访问权限是包访问权限,它仅允许包内的成员访问。因此,在此包中,如果没有访问权限修饰词,任何人都可以使用这些方法。例如,Detergent就不成问题。但是,其他包中的某个类若要从Cleanser中继承,则只能访问public成员。所以,为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public(protected成员也可以借助导出类来访问)。当然,在特殊情况下,必须做出调整,但上述方法的确是一个很有用的规则。

        在Cleanser的接口有一组方法:append()、dilute()、apply()、scrub()和toString()。由于Detergent是由关键字extends从Cleanser导出的,所以它可以在其接口中自动获得这些方法,尽管并不能看到这些方法在Detergent中的显式定义。因此,可以将继承视作是对类的复用。

        正如我们在scrub()中所见,使用基类中定义的方法及对它进行修改是可行的。在此例中,你可能想要在新版本中调用从基类继承而来的方法。但是在surub()中,并不能直接调用scrub()中,并不能直接调用scrub(),因为这样做将会产生递归,而这并不是你所期望的。为解决此问题,java用super关键字表示超类的意思,当前类就是从超类继承来的。为此,表达式super.scrub()将调用基类版本的scrub()方法。

        在继承过程中,并不一定非得使用基类的方法。也可以在导出类中添加新方法,其添加方式与在类中添加任意方法一样,即对其加以定义即可,foam()方法即为一例。

        在Detergent.main()中会发现,对于一个Detergent对象而言,除了可以调用Detergent的方法(即foam())之外,还可以调用Cleanser中所有可用的方法。

3 初始化基类

        由于现在涉及基类和导出类这两个类,而不是只有一个类,所以要试着想象导出类所产生的结果对象,会有点困惑。从外部来看,它就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域。但继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部。

        当然,对基类子对象的正确初始化也是至关重要的,而且也仅有一种方法来保证这一点:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。java会自动在导出类的构造器中插入对基类构造器的调用。下例展示了上述机制在三层继承关系上是如何工作的:

class Art {Art() {System.out.println("Art constructor");}
}class Drawing extends Art {Drawing() {System.out.println("Drawing constructor");}
}public class Cartoon extends Drawing {public Cartoon() {System.out.println("Cartoon constructor");}public static void main(String[] args) {Cartoon x = new Cartoon();}
}

        你会发现,构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。即使你不为Cartoon()创建构造器,编译器也会为你合成一个默认的构造器,该构造器将调用基类的构造器。

4 带参数的构造器

        上例中各个类均含有默认的构造器,即这些构造器都不带参数。编译器可以轻松地调用它们是因为不必考虑要传递什么样的参数的问题。但是,如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显式地编写调用基类构造器的语句,并且配以适当的参数列表:

class Game {Game(int i) {System.out.println("Game constructor");}
}class BoardGame extends Game {BoardGame(int i) {super(i);System.out.println("BoardGame constructor");}}public class Chess extends BoardGame {Chess() {super(11);System.out.println("Chess constructor");}public static void main(String[] args) {Chess x = new Chess();}
}

        如果不在BoardGame()中调用基类构造器,编译器将“抱怨”无法找到符合Game()形式的构造器。而且,调用基类构造器必须是你在导出类构造器中要做的第一件事(如果你做错了,编译器会提醒你)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67619.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.Net Core微服务入门全纪录(五)——Ocelot-API网关(下)

系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…

RV1126+FFMPEG推流项目(9)AI和AENC模块绑定,并且开启线程采集

前面两篇已经交代AI和AENC模块的配置,这篇就让这两个模块绑定起来,绑定的原因是,Aenc从Ai模块拿到采集的原始数据进行编码。 使用 RK_MPI_SYS_Bind 把 AI 节点和 AENC 进行绑定,其中 enModId 是模块 ID 号选择的是 RK_ID_AI、s32C…

2.5G PoE交换机 TL-SE2109P 简单开箱评测,8个2.5G电口+1个10G光口(SFP+)

TPLINK(普联)的万兆上联的2.5G网管交换机TL-SE2109P简单开箱测评。8个PoE 2.5G电口,1个万兆SFP上联口。 2.5G交换机 TL-SE2420 简单开箱评测,16个2.5G电口4个10G光口(SFP):https://blog.zeruns.com/archives/837.html…

若依框架搭建学习

按这位大神的教程一步一步来。我只写注意事项。 https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5OTgxOTg0Ng&actiongetalbum&album_id2441331662295973890&scene173&from_msgid2247483925&from_itemidx1&count3&nolastread1#wechat_redirect…

学成在线_内容管理模块_创建模块工程

学成在线模块工程 1.各个微服务依赖基础工程2.每个微服务都是一个前后端分离的项目3.xuecheng-plus-content:内容管理模块工程xuecheng-plus-content-modelxuecheng-plus-content-servicexuecheng-plus-content-api 1.各个微服务依赖基础工程 2.每个微服务都是一个前…

GCPAAS/DashBoard:完全免费的仪表盘设计,基于Vue+ElementUI+G2Plot+Echarts,开源代码,简单易用!还在等什么呢

嗨,大家好,我是小华同学,关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 GCPAAS/DashBoard,一款基于SpringBoot、MyBatisPlus、ElementUI、G2Plot、Echarts等技术栈的仪表盘设计器,具备仪表盘目录管理…

登录校验Cookie、Session、JWT

目录 基础知识:登录校验的场景 基础知识:会话 ​编辑方案一:Cookie 方案二:Session 方案三:令牌技术 JWT 令牌 基础知识:登录校验的场景 基础知识:会话 什么是会话?什么是会话跟…

Android BitmapShader实现狙击瞄具十字交叉线准星,Kotlin

Android BitmapShader实现狙击瞄具十字交叉线准星&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.…

gitignore忽略已经提交过的

已经在.gitignore文件中添加了过滤规则来忽略bin和obj等文件夹&#xff0c;但这些文件夹仍然出现在提交中&#xff0c;可能是因为这些文件夹在添加.gitignore规则之前已经被提交到Git仓库中了。要解决这个问题&#xff0c;您需要从Git的索引中移除这些文件夹&#xff0c;并确保…

Docker 中安装 Redis 并开启远程访问

在 Docker 中安装 Redis 并开启远程访问&#xff0c;以便本机可以连接的详细步骤&#xff1a; 一、拉取 Redis 镜像 首先&#xff0c;你需要从 Docker Hub 拉取 Redis 的镜像。使用以下命令&#xff1a; bash docker pull redis:latest这将拉取最新版本的 Redis 镜像。如果你…

ABP - 缓存模块(1)

ABP - 缓存模块&#xff08;1&#xff09; 1. 与 .NET Core 缓存的关系和差异2. Abp 缓存的使用2.1 常规使用2.2 非字符串类型的 Key2.3 批量操作 3. 额外功能 1. 与 .NET Core 缓存的关系和差异 ABP 框架中的缓存系统核心包是 Volo.Abp.Caching &#xff0c;而对于分布式缓存…

技术洞察:C++在后端开发中的前沿趋势与社会影响

文章目录 引言C在后端开发中的前沿趋势1. 高性能计算的需求2. 微服务架构的兴起3. 跨平台开发的便利性 跨领域技术融合与创新实践1. C与人工智能的结合2. C与区块链技术的融合 C对社会与人文的影响1. 提升生产力与创新能力2. 促进技术教育与人才培养3. 技术与人文的深度融合 结…

浅谈云计算22 | Kubernetes容器编排引擎

Kubernetes容器编排引擎 一、Kubernetes管理对象1.1 Kubernetes组件和架构1.2 主要管理对象类型 二、Kubernetes 服务2.1 服务的作用与原理2.2 服务类型 三、Kubernetes网络管理3.1 网络模型与目标3.2 网络组件3.2.1 kube-proxy3.2.2 网络插件 3.3 网络通信流程 四、Kubernetes…

Redis 和 MySQL 结合使用

Redis 和 MySQL 结合使用的场景非常常见&#xff0c;通常是利用 Redis 作为缓存层来提升 MySQL 数据库的性能&#xff0c;减少数据库的压力&#xff0c;同时提高系统的响应速度。下面是它们结合使用的几种常见方法&#xff1a; 1. 缓存数据库&#xff08;Cache-Aside&#xff…

【HarmonyOS NAPI 深度探索9】发布到 npm 并管理版本

【HarmonyOS NAPI 深度探索9】发布到 npm 并管理版本 开发了一个强大的 N-API 模块后&#xff0c;下一步就是将它发布到 npm&#xff0c;让更多开发者可以使用。同时&#xff0c;随着模块的更新迭代&#xff0c;版本管理也非常重要。今天&#xff0c;我们将讲解如何将 N-API 模…

如何在linux系统上完成定时开机和更新github端口的任务

任务背景 1.即使打开代理&#xff0c;有的时候github去clone比较大的文件时也会出问题。这时需要每小时更新一次github的host端口&#xff1b; 2.马上要放假&#xff0c;想远程登录在学校的台式电脑&#xff0c;但学校内网又不太好穿透。退而求其次&#xff0c;选择定时启动电…

MATLAB基础应用精讲-【数模应用】三维海浪模型仿真(附MATLAB和python代码实现)

目录 前言 ​算法原理 动态海面的建模方法 海浪谱理论 海洋水体构建技术 (一)波形模型 (二)水体着色 三维海浪模型建模 二维不规则长峰波海浪仿真 三维不规则短峰波海浪仿真 海浪建模的理论分析 谐波海面模型 Gerstner波模型 波动方程 代码实现 MATLAB p…

vector迭代器的使用以及迭代器失效

一、iterator的使用注意 begin与end 遵循左闭右开的原则&#xff0c;begin 指向vector的第一个元素&#xff0c;end 指向vector的最后一个元素的往下一个位置。 rbegin 与 rend rbegin指向最后一个元素的位置&#xff0c;rend指向第一个元素的往前一个位置。 二、vector的常…

无降智o1 pro——一次特别的ChatGPT专业模式探索

这段时间和朋友们交流 ChatGPT 的使用心得&#xff0c;大家都提到一个很“神秘”的服务&#xff1a;它基于 O1 Pro 模型&#xff0c;能够在对话里一直保持相对高水平的理解和回复&#xff0c;不会突然变得“降智”。同时&#xff0c;整体使用还做了免折腾的网络设置——简单一点…

Web前端开发技术之HTMLCSS知识点总结

学习路线 一、新闻网界面1. 代码示例2. 效果展示3. 知识点总结3.1 HTML标签和字符实体3.2 超链接、颜色描述与标题元素3.3 关于图片和视频标签&#xff1a;3.4 CSS引入方式3.5 CSS选择器优先级 二、flex布局1. 代码示例2. 效果展示3. 知识点总结3.1 span标签和flex容器的区别3.…