java程序设计_大牛带你深入Java核心技术,进行面向对象程序设计深剖,可以一学...

前言

在这一章里,将主要介绍

• 面向对象程序设计。

• 如何创建标准Java类库中的类对象。

• 如何编写自己的类。

如果没有面向对象程序设计的应用背景,就一定要认真地阅读这一章的内容。面向对象程序设计与面向过程程序设计在思维方式上存在着很大的差别。改变一种思维方式不是一件很容易的事情,而且要想继续学习Java就需要熟悉对象的概念。

对于具有C++编程经验的程序员来说,与上一章相同,对这一章的内容不会感到太陌生;但是,在两种语言之间还是存在着很多不同之处,因此应该认真地阅读本章的后半部分内容。你会发现“C++注意”十分有助于学习Java语言。

面向对象程序设计概述

面向对象程序设计(简称OOP)是当今主流的程序设计范型,它已经取代了70年代早期的“结构化”过程化程序设计开发技术。Java 是完全面向对象的,绝对不能使用人们熟悉的过程式风格设计Java程序。我们希望能够在这一节中,通过书中和网上的示例程序,提供充足的OOP信息,以便辅助读者编写Java程序。

下面让我们从一个看似与程序设计无关的例子开始,这个例子是这样说的:为什么Compaq 、Dell、Gateway以及其他一些主流的PC机制造商能够这样快地发展壮大?很多人可能会说:他们能够生产具有良好性能的计算机,并以低廉的价格销售,从而满足了人们对计算机需要的迅猛增长,顺应了时代的需求。然而,让我们进一步地思考一下,他们为什么能够制造出这么多种型号的计算机,并以如此快的速度对需求变化做出响应?

其中主要原因是这些公司将大量的工作交给了他人。他们从信誉较好的厂商那里购买组件,然后将它们组装起来。这样一来,往往不需要在设计与制作电源、磁盘驱动器、主板以及其他组件上花费过多的时间和资金。与自行研制相比,这样能够以更快的速度、更少的资金生产产品,并快速地适应需求变化。

个人计算机制造商所购买的就是“预包装功能”(prepackaged functionality)。例如,当购买一个电源的时候,实际上购买的是具有一些属性(大小、形状等等)和功能(稳压输出、实际功率等)的东西。Compaq公司就是一个应用这种高效生产过程的典范。当他们从自行研制大部分零部件转向主要依靠购买部件时,其盈利获得了大幅度地提升。

OOP也是基于同样的想法。它认为程序是由对象组成的,这些对象有自己的属性和能够执行的操作。是自己构造对象还是从外界购买对象完全取决于预算或时间。但是,从根本上说,只要对象能够满足要求,就不必关心其功能的具体实现过程。在OOP中,只需要关注对象的外表,就如同计算机制造商只关心电源是否符合要求,不必关心其内部结构一样;绝大多数的Java程序员不必关心对象的具体实现,只要保证对象满足他们的需求就可以了。

传统的结构化程序设计通过设计一系列的过程(即算法)来求解问题。这些过程一旦被确定,下一步就要开始寻找存储数据的方式。这就是Pascal语言的设计者Niklaus Wirth将其编著的有关程序设计的著名书籍命名为《Algorithms+Data Structure=Programs》(算法+数据结构=程序)(Prentice Hall, 1975)的原因。请注意,在Wirth命名的书名中,算法是第一位的,而数据结构是第二位的。这就生动地表述了程序员当时的工作方式。首先要确定如何操纵数据;然后再决定如何组织数据的结构,以便操纵数据。OOP却调换了这个次序,将数据放在第一位,然后再考虑操纵数据的算法。

OOP的关键是让每一个对象负责执行一组相关的任务。如果一个对象依赖于另一个对象负责处理的任务,那么它就去访问那个包括该任务的对象。此时,第一个对象请求第二个对象执行任务。这种工作方式实际上是由过程化程序设计中人们所熟悉的过程调用实现的。(在Java程序设计语言中,这些过程调用被称为方法调用。)

特别需要说明的是,一个对象既不能直接访问另一个对象内部的数据,也不能让其他对象直接访问自己内部的数据。对象之间的所有通信都是通过方法调用完成的。通过封装对象数据,最大限度地提高可重用性,减少数据的依赖性,并将程序的调试时间降到最短。

当然,如同过程化程序设计语言中的模块一样,不要让一个对象承担的任务过多。创建一个仅执行少量任务的小型对象,会使设计和调试过程简化;而创建一个内部含有复杂数据和数百个过程的对象,会使设计与调试过程变得非常困难。

OOP词汇表

在进一步学习Java之前,有必要弄清一些OOP的术语。其中最重要的是类,有关类的使用在第3章中的示例代码中已经看到过。类是构造对象的模板或蓝图。我们可以把类想象为小甜饼的切割机,把对象想象为小甜饼。由类构造对象的过程被称为创建类的实例。

正如所看到的那样,用Java编写的全部代码都位于某个类的内部。标准的Java类库提供了数千个类,可以用于用户界面设计、日期、日历和网络程序设计等多种用途。尽管如此,仍然需要用Java创建自己的类来描述问题域中的对象,或使标准类库中提供的类适应用户的需求。

封装(encapsulation,有时称为数据隐藏)是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为结合在一个包中,并对对象的使用者隐藏了数据的实现方式。对象中的数据被称为实例域(instance field),操纵数据的过程被称为方法(method)。对于每个特定的类对象实例都有一组特定的实例域值。这些值的集合就是这个对象的当前状态(state)。无论何时,只要向对象发送一个消息,它的状态就有可能发生改变。

实现封装的关键在于绝对不能让类中的方法直接访问其他类的实例域,但可以访问它自己类的实例域,这一点要反复强调。程序仅通过对象的方法与对象数据进行交互。封装给予对象“黑盒”特征,这是提高重用性和可靠性的关键。这意味着一个类可以全面地改变存储数据的方式,只要依旧使用同样的方法操作数据,就不会有其他的对象知晓或在意操作过程所发生的变化。

OOP的另一个原则会让用户自定义Java类变得轻而易举,这就是:类可以通过扩展另一个类来建立。事实上,在Java中,所有的类都源自于一个“神通广大的超类”,它就是Object。在下一章中,将可以看到有关Object类的详细介绍。

在扩展一个已有的类时,新的类具有所扩展的类的全部属性和方法。在新的类中,只需要提供那些应用于新类的新方法和数据域就可以了。通过扩展一个类来建立另外一个类的过程被称为继承,有关继承的更加详细的内容请参阅下一章。

对象

要想使用OOP,一定要清楚对象的三个主要特性:

• 对象的行为(behavior)—可以对对象施加哪些操作,或可以对对象施加哪些方法?

• 对象的状态(state)—当施加那些方法时,对象如何响应?

• 对象的标识(identity)—如何区分具有相同行为与状态的不同对象?

同一个类的所有对象实例由于支持相同的行为而具有家族的相似性。对象的行为是用可调用的方法定义的。

此外,每个对象都保存着描述当前特征的信息。这就是对象的状态。对象的状态可能会随着时间而发生改变,但这种改变不会是自发的。对象状态的改变必须通过调用方法实现。(如果没有经过方法调用而改变对象状态,就说明封装性一定遭到了破坏。)

但是,对象的状态并不能完全描述一个对象。每个对象都有一个唯一的身份。例如,在一个订单处理系统中,任何两个订单都存在着不同之处,即使所订购的货物完全相同也是如此。

需要注意,作为一个类的实例,每个对象的标识永远是不同的,但状态却常常存在着差异。

对象的这些关键特性在彼此之间相互影响着。例如,对象的状态影响它的行为。(如果一个订单“已送货”或“已付款”,那么就应该拒绝调用具有增删订单中条目的方法。反过来,如果订单是“空的”,即还没有加入预订的物品,那么这个订单就不应该进入“已送货”状态。)

传统的过程化程序设计,必须从顶部的main函数开始编写程序。当设计一个面向对象的系统时,没有所谓的“顶部”。对于学习OOP的初学者来说常常会感觉无从下手。答案是首先从设计类开始,然后再向每个类中添加方法。

这些名词很可能形成类Item、Order等。

接下来,查看动词。物品项目被添加到订单中。订单被发送或取消。订单货款被支付。对于每一个动词如:“添加”、“发送”、“取消”以及“支付”,都要标识出主要负责完成相应动作的对象。例如,当一个新的条目添加到订单中时,那个订单对象就是被指定的对象,因为它知道如何存储条目以及如何对条目进行排序。也就是说,add应该是Order类的一个方法,而Item对象是一个参数。

当然,所谓“名词与动词”原则只是一种粗略的方法,在建立类的时候,哪些名词和动词是重要的完全取决于个人的开发经验。

类之间的关系

在类之间,最常见的关系有

• 依赖(“uses-a”)

• 聚合(“has-a”)

• 继承(“is-a”)

依赖,即“uses-a”关系,是一种最明显的、最常见的关系。例如,Order类使用Account类是因为Order对象需要访问Account对象查看信用状态。但是Item类不依赖于Account类,这是因为Item对象与客户账户无关。因此,如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。

40425eb117c57f955fb42231981056c0.png

继承,即“is-a”关系,是一种用来表示特殊与一般关系的。例如,RushOrder类由Order类继承而来。在具有特殊性的RushOrder类中包含了一些用于优先处理的特殊方法,以及一个计算运费的不同方法;而其他的方法,如添加条目、生成账单等等都是从Order类继承来的。一般而言,如果类A扩展类B,类A不但包含从类B继承的方法,还会有一些额外的功能。(在下一章中,我们将详细讨论继承,其中会用较多的篇幅讲解这个重要的概念。)

很多程序员都使用UML(Unified Modeling Language)绘制描述类之间关系的类图。图4-1就是这样一个例子。类用矩形表示,类之间的关系用带有各种修饰的箭头表示。表4-1给出了UML中最常见的箭头样式。

159020fbb497cd6ce88b8362035e0182.png

OOP与传统的过程化程序设计技术对比

我们想用OOP与读者也许更熟悉的过程式模型的对比来结束对OOP的简要介绍。在过程化程序设计中,首先需要确定要完成什么任务,然后:

• 使用逐步求精的处理过程:将要完成的任务分解成若干个子任务,子任务再分解成更小的子任务,直到子任务小到可以直接实现为止(这是自顶向下的方法)。

• 编写求解简单任务的过程,并将它们组装成更加复杂的过程,直到完成需要实现的功能为止(这是自底向上的方法)。

当然,大多数程序员使用自顶向下与自底向上相结合的策略来解决程序设计的问题。在过程化程序设计中,确定过程的手段与OOP中确定方法的方式基本一样,即查看问题描述中的动词或者动作。而在OOP中的最重要区别是首先从项目中分离出类,然后再找出这个类中需要定义哪些方法。除此之外,传统的过程和OOP方法的另一个主要区别是:在OOP中,每个方法都与负责执行这个操作的类相关联。

对于规模较小的问题,使用过程化程序设计方法比较合适。但是,对于规模较大的问题,使用类和方法将会带来两点好处。一是类提供了一种便于将众多的方法聚集在一起的机制。要想实现一个简单的Web浏览器可能需要2000个过程,而可能只需要100个类,每个类平均20个方法。对于程序员来说,后者结构更易于驾驭,也更易于在开发团队中分配任务。二是类的封装机制将有助于对其他的类方法隐藏数据表示。如图4-2所示,这意味着如果出现了搞乱数据的bug,就在访问这些数据的20个方法中检查错误,这样做总会比从2000个过程中查找错误要容易得多。

0f65093c777bef4ee71f78576859a8ea.png

也许有人会说:这与模块化设计没有太大的区别。我们过去可能编写过这样的程序:

将程序划分成模块,模块之间通过相互调用,而不是共享数据进行通信。这种做法(如果做得很好的话)也可以达到封装的目的。但是,在许多程序设计语言中,哪怕是编写程序中出现的很小失误也有可能导致对另一模块中的数据进行操作,这将很容易使得封装性遭到破坏。

还有一个更严峻的问题。类可以拥有相同行为的多个对象,但我们却不能得到某个模块的多个拷贝。假定有一个模块封装了一组订单,以及一个能够快速访问的平衡二叉树模块。现在,有可能发现实际需要两个订单集合,一个用于表示未处理的订单,一个用于表示已处理的订单。我们无法让订单树模块实现两次链接,也不希望为链接操作建立一个副本,并为链接重新命名所有的过程。类没有这样的限制。一旦定义了类,就可以轻松地构造该类的任意多个实例(而模块只能有一个实例)。

前面只浅显地介绍了一些概念。在本章最后,有一小节专门用于介绍“类设计技巧”。要想深入理解OO的设计思想,请参考相关的OO以及UML的书籍。这里向大家推荐Grady Booch、Ivar Jacobson和James Rumbaugh 编著的《The Unified Modeling Language User Guide》 (UML参考手册)(Addison-Wesley, 1999)一书。

觉得文章不错的话,可以转发此文关注小编,之后持续更新干货文章!!!

希望能够帮助到大家学习。

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值