版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
《Java组件设计》
本书主要面向软件架构师、设计师、高级开发人员,讲解企业应用中核心组件的设计原则与实践。
本书将澄清设计模式、数据结构、多线程、接口设计等多个领域中的常见误区,通过大量的实例分析,
为读者精彩讲解组件设计这一最具技术含量的领域需要考虑的问题、设计方案与最佳实践.
章节目录,暂定如下:
第一章:组件设计概述
第二章:组件设计原则
第三章:预备知识
第四章:配置文件
第五章:Tcp通信组件
第六章:日志组件
第七章:数据库访问组件
第八章:Web服务组件
1组件设计概述
编写计算机软件的人很多,我们通常都把这些活动称为软件开发。但是软件的种类是不同的,每种软
件都有自身的复杂性和挑战性。本人一直工作在电信行业,电信行业的软件非常复杂,对并发、大数
据量、性能、高可靠性要求很高,这些都对软件的设计和开发提出了严峻的挑战。
1.1应用软件结构
通常,应用软件的总体结构,可以分为两大部分:应用层和平台层,具体如下:
平台层提供基础框架和大量可重用组件,这些组件以一定的接口方式暴露出来,供应用层来调用。平
台层通常不提供与具体业务相关的逻辑处理,而是提供:
1)业务无关的框架/功能组件:比如日志、安全、线程池、连接池、告警监控等
2)多种业务之间可共用的机制:如工作流、事件通知机制等,这部分也与具体的业务无关。
应用层提供具体应用相关的逻辑处理部分,包括页面、应用逻辑、应用数据等。
平台层和应用层,是个逻辑划分的概念,实际软件实现中,平台层和应用层都可以由多个层来实现,
也可以合并到一个程序中,这要视项目的规模和具体需求而定。
从上图可以看出,构建一个高度可重用的平台层,可以使应用开发只需集中精力关注业务逻辑,业务
无关的功能组件和机制都由平台层提供,可以直接使用,这样极大简化了应用开发,缩短了软件交付
周期,保障了软件质量。
而构建一个高度可重用的平台层,最核心的挑战就是设计和开发高度可重用的组件,提取应用的共性
需求,简化接口,真正做到应用开发时可以直接拿来就用,而且非常好用。
1.2组件定义
那么,到底什么是组件呢?框架又是什么意思?类是组件吗?控件又指什么?元件、构件这些概念又
如何理解?
这些概念,都没有一个统一的标准答案,因此在软件开发过程中,这些术语经常被混淆,作者根据自
己的工作体会,对这些概念解释如下:
1)对象:面向对象语言(Object-OrientedLanguage)是一类以对象作为基本程序结构单位的程序设计
语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。对象都具有状态和行
为。对象,也被翻译为实例、物件。
2)类:面向对象的概念。类是一种对包括数据成员、函数成员和嵌套类型进行封装的数据结构。在程
序运行期间,由类创建成对象的过程,叫类的实例化。因此,对象是个运行期的术语,类是个编译期
的术语。类本身就是可重用的直接实现手段.
3)组件:类本身是个细粒度的可重用实现,为了解决功能或机制层面更大粒度重用的问题,又引入了
组件的概念。组件的英文是Component,其逻辑结构如下:
组件对外暴露一个或多个接口,供外界调用。组件内部由多个类来协同实现指定的功能。对于复杂的
组件,会包括很多的类,还可能包含配置文件、界面、依赖的库文件等,组件也可以包含或者使用其
它的组件,构成更大的组件。
一些特定范畴的组件,由软件厂家或者国际权威组织制定并颁布了组件规范,如COM、ActiveX.EJB、
JavaBean等。本书讨论的组件,指一般意义的自定义组件,不包括这些规范化的组件。
4)控件:控件的英文是Control,控件就是具有用户界面的组件。要说的具体一点,就得回顾早期
Windows的历史根源,当时控件指任何子窗口:按钮、列表框、编辑框或者某个对话框中的静态文本。
从概念上讲,这些窗口一一控件一一类似用来操作收音机或小电器的旋钮和按钮。随着控件数量的增
加(组合框、日期时间控件等等),控件逐渐成为子窗口的代名词,无论是用在对话框中还是用在其
它种类的主窗口中。没过多久BASIC程序员开始编写他们自己专用的控件,自然而然地人们便想到
共享这些控件。共享代码的方法之一是通过磁盘拷贝,但那样显然效率低下。必须要有一种机制使开
发者建立的控件能够在其它程序员的应用中轻而易举地插入,这便是VBA控件,OLE控件,OCX和最
后ActiveX控件产生的动机。因此,控件是组件的一个主要样本(并且历史上曾驱动着组件的开发),
控件又不仅仅是唯一的一种组件。
5)元件:元件是个电子行业的术语,是电子元件的简称。也有一些软件借用这个术语,指特定的可重
用控件。
6)构件:构件是系统中实际存在的可更换部分,它实现特定的功能,符合一套接口标准并实现一组接
口。构件代表系统中的一部分物理实施,包括软件代码(源代码、二进制代码或可执行代码)或其等
价物(如脚本或命令文件)。
通常认为,构件是特定软件开发环境中、满足指定规范的软件部分,其涵盖了设计、开发、物理实施
等范畴.
7)框架:框架的英文是Framework,框架是一个应用程序的半成品。框架提供了可在应用程序之间共
享的可复用的公共结构。开发者把框架融入他们自己的应用程序,并加以扩展,以满足他们特定的需
要■框架和工具包的不同之处在于,框架提供了一致的结构,而不仅仅是一组工具类.(摘自«JUnit
inaction中文版》).
8)子系统:子系统是个设计中使用的术语,英文是SubSystem。在软件总体架构中,按照功能划分子
系统.通常一个子系统包含一个或多个进程。
9)模块:模块是个设计中使用的术语,英文是Module.模块指一个子系统内部,按照软件结构分解
的功能部分.模块会包含多个类、使用多个组件,也会与框架交互。
一个真正的软件系统,会涉及到以上的多个概念,典型的软件系统静态结构图如下:
子系统1子系统2
框
架
1
上图展示了一个软件系统,包括2个子系统,子系统1和子系统20子系统1调用子系统2。
子系统1包含2个模块:模块1和模块2。模块1由两个类(Class1和Class2)和一个组件(组件1)构
成,模块2由两个类(Class3和Class4)和两个组件(组件2和组件3)构成,模块2提供一个接
口给模块1调用。模块1和模块2都使用了框架1。
子系统2包含2个模块:模块3和模块4。模块3由两个类(Class5和Class6)和一个组件(组件4)
构成,模块4由一个类(Class7)和一个组件(组件5)构成。模块4提供一个接口给模块3调用。
模块3和模块4都使用了框架2。
2组件设计原则
Java阵营一直倡导开源,开源运动如火如荼展开,催生了无数组件。但是,坦率的讲,这些开源的组件
中,能够直接拿过来,不做任何改造,就能用于商业软件构建,满足功能和性能的要求,这样的优秀
组件不多见。因此,核心软件开发者时常面对的尴尬局面是:大量的开源资源,都不满足我的要求。
实际上,组件设计是软件设计开发最精髓所在,凝聚了数据结构、面向对象、设计模式、线程并发同步、
操作系统等诸多领域的最核心技术,一直是设计开发领域彰显技术水准的最高领地。
一个组件,要想被广泛重用,满足不同系统的使用要求,就要求组件有足够的能力适应不同的应用场合,
提供满足需要的功能,并表现出优秀的性能。因此,组件设计过程中,组件设计者要考虑的问题非常
广泛而复杂,组件设计者需要具备的知识、技能和经验要求非常高,一般工作经验至少在5年以上才
有可能涉足组件设计这个领域.这也就解释了,为什么优秀组件不多的原因了。
本章将对组件设计过程中要考虑的核心要素、设计中要遵循的核心原则进行总体阐述,使读者能从总体
上掌握如何发现、评判、设计一个优秀的组件。
本章将对目前业界存在的诸多技术争论、误区进行澄清,让读者从所谓的“业界潮流”、教条、“黄金
定律”中走出来,真正理解组件设计过程的核心原则.这些核心原则如下:
原则一、精准解决共性问题
原则二、无配置文件
原则三、与使用者概念一致
原则四、业务无关的中立性
原则五、对使用环境无依赖
原则六:单类设计和实现
下面来详细讲解每个核心原则。
2.1组件定位:精准解决共性问题
组件的产生,来源于软件工程实践中,对重复、反复出现、普遍的、有相似性的问题进行分析,剥离掉
各个问题的特性,抽取各个问题之间的共性,然后确定要设计一个或多个组件,这样就确定了组件要
解决的问题,而这个问题必然是共性的问题,不能是个性、特例的问题。如果不是共性的问题,那么
写完后的代码就只能在一种或有限的几种情况下才能被使用,这样就无法实现大规模复用。不能广泛
的被复用,就不能叫组件。
因此,组件首先是业务驱动的,不是技术驱动的。不能因为我们有了新的技术,就想着要写几个新的组
件。共性的业务问题,是组件的产生来源。
另外,即使确定了组件要解决的问题,组件还必须提供解决问题最合理、最有效的方式和方法。如果提
供的方法不是最好的,那么也很难得到使用者的喜欢,最终也难以推广和复用。因此,组件设计上,
必须提供解决问题的精准思路和方案.这是决定同类别的组件中,哪个组件能胜出的最主要原因。
一个组件提供的问题解决方法,是否最有效,主要评价原则如下:
1)技术与业务对齐:技术结构和技术概念要与业务上的模型、概念保持一致,在组件对外暴露的接
口上,最好不要引入新的技术术语和概念,这样的组件最吻合业务的需求,也最容易理解。技术与业
务对齐,这是组件设计的第一位重要原则。
2)技术方案的优势:这是结构设计、技术选型范畴内要考虑的问题。实现同一个功能,可以有很多
不同的结构设计,也有很多不同的技术可以采用,优秀的组件,一定在技术方案上有明显的技术优势。
3)接口简单:组件暴露出来供外界使用的接口,必须足够简单。在概念和模型层面上与业务保持一
致,另外接口封装、抽象上要仔细推敲,保证接口提供给别人使用时,调用者足够简单的使用。
4)功能强大:组件如果提供的功能太少,意味着能帮用户解决的问题比较少,因此这样的组件实用
性大打折扣。如果组件的功能过多,就会让用户觉得这个组件非常臃肿和庞大,用户只用了其中一小
部分功能,其它大部分功能都用不上或者很少用,这样用户要在一本厚厚的使用手册中,仔细寻找自
己需要的部分,去除自己不需要的部分,这也算不上一个优秀的组件。因此,一个组件提供的功能,
既要能满足多种场景下不同客户的需求,还要在不同的需求中进行取舍和合并,使提供的功能集不会
超出客户实际使用需求太多。
2.2组件设计:无配置文件
组件自己不要带任何配置文件,组件所依赖的各种库也不带任何配置文件。
这个原则极为重要!!!
如果一个组件,自己定义了配置文件,无论是XML,Properties,还是其他的文件类型,内部的格式
都是组件设计者自己定义的。我们假设一个中等的项目,会使用10个组件,那么就会有10个配置文
件,这些配置文件的格式各不相同.那么我们的软件安装手册、维护手册就要仔细描述这10个配置
文件的结构,我们的实施维护人员就要学会如何配置这10个文件.如果项目再大,需要集成50个组
件,那就需要50个配置文件,对手册编写人员和实施维护人员,这是怎样的灾难啊!
归纳起来,如果一个组件自己带配置文件,带来的不良影响主要有:
1)不同组件的配置文件格式不统一,开发者、手册编写人员、实施人员、维护人员要学习不同格式、
不同结构、不同的配置方式,学习的成本大大增加;
2)同一个配置(比如数据库连接串,IP地址等),由于各个组件的配置方式不同,则实施人员要在多
个配置文件中进行配置,增加了工作量,又难于保证升级时全部更新一致;
3)如果后续发现更好的组件,要用其替换原有的组件,则新的组件的配置文件与原组件的配置文件
不同,则即使这两个组件的功能一致,我们也必需要更新实施维护手册,实施维护人员又要重新学习。
因此,基于配置文件的组件,是非常不容易被集成的.一个应用,无论其使用多少个组件,应该始终
只有一个配置文件,用于集中配置这个应用的所有参数。而每个组件,都是以接口或者类的方式提供
配置函数,应用集中读取所有配置信息,然后通过组件的配置函数,将配置参数设置到组件上。这样,
将从根本上解决组件集成过程由组件配置文件引起的各种问题。
2.3组件设计:与使用者概念一致
组件提供接口,给应用来使用。在接口上表现出的概念术语、使用方式都应用与使用者的概念术语、
使用方式完全对应,也就是,技术和业务对齐。
组件设计者常犯的毛病是,组件暴露的接口,引入了新的技术术语,这些术语在使用者的概念体系中
根本就不存在,因此使用者理解起来非常困难,造成极大的学习障碍。这些与使用者概念不一致的技
术术语,突出表现在接口命名、类名、函数名、参数名、返回值的含义等。
从本质上讲,组件接口上暴露的与使用者不一致的概念,要么是这个概念本身就是错误的或不恰当的,
其不应该存在,要么就是这是个内部实现的概念,不应该包括在接口上。
2.4组件设计:业务无关的中立性
一个组件,要在不同应用、不同场景下都能广泛的重用,这是组件设计者必需要实现的目标。但一个
组件的产生,往往来源于一个特定的项目、一个特定的业务场景,在实现业务和项目功能的时候,组
件设计者意识到,这个功能部分可以进行抽取,形成一个可重用的组件。因此,这个抽取的过程,组
件设计者务必把与当前这个项目、这个业务特有的部分剥离掉,保留一般的、共性的功能,并重新封
装和调整接口,以满足通用的使用场景。这样,这个组件可以与业务无关,保持自己的中立性,后续
可以在其它的应用中被重用。
如何识别那些是业务特性,那些是一般的共性,这需要依赖组件设计者多年的经验。
2.5组件设计实现:对使用环境无依赖
组件的内部实现,是否可以对将来组件被使用的环境做些假设?比如,是在Servlet容器中运行,
仅使用一个组件的实例,仅处理一个目录…….?
如果组件设计者对组件将来被使用的环境做了一些假设,认为这些条件必然被满足,那么组件的代码
实现就会和这些假设条件相关。如果这些条件不成立,则组件无法被使用。比如,组件设计者认为,
将来应用中一个组件的实例就能满足需要,因此组件就设计成了单实例。当一个特殊的应用出现,需
要使用两个组件实例来处理不同的场景,发现组件已经被设计成单实例,无法满足应用的这种“特殊”
需求。
这种需求,真的很特殊吗?
客观上讲,需求来自于具体的应用,来自客户的使用场景和要解决的问题。需求,是独立与设计与实
现的,尤其是与组件的设计实现无关。组件设计者之所以认为这种需求很“特殊”,是因为这个需求
超出了组件设计者原来所做的假设。根本上讲,组件设计者不应该对组件将来的使用环境做任何假设,
这样组件的使用场合仅受限于组件的能力集,只有应用需要的功能在组件的能力集范围内,各种环境
下的不同应用都可以使用这个组件。
这样,组件才可以被广泛复用。
2.6组件设计实现:单类设计和实现
怎样的组件,才是一个优秀的组件?从组件使用者的角度,组件要简单,易于使用,并且功能强大。
那么组件怎样才能简单,易于使用?
首先,前面讲过,组件提供出来的接口,要与使用者的概念完全一致,这样使用者就非常容易理解组
件的各个类、各个接口、各个方法、各个参数,这样就会易于使用。
但组件怎么才能简单呢?
最简单的组件,就是一个类。
一个类,就会比两个类简单,两个类就会比四个类简单。这个道理显而易见,其核心精髓是面向对象
的基本设计原则:高内聚、低耦合。要严格控制类的数量,逻辑相关度很高的,就不允许拆成多个类。
不支持将来变化的部分,就不提供接口。
组件,作为可重用的软件,不会承载太多的功能,组件的规模不会很大。因此,最理想的情况,组件
就是单独的一个类。组件使用者用起来,是极致的简单。
我们从网上下载开源的项目,打开源码一看,豁,好家伙,一大堆的类和接口,不写几十个、几百个
类好象就显不出作者的水平。其实真有必要写那么的多的类吗?
高内聚,高内聚,单类组件,简单的极致!
3预备知识
本章主要讲解一些组件开发中常用的技术,作为后续章节讲解组件设计的预备知识。
本章假设读者已经熟悉了Java语言的基本语法,并且熟练使用Java语言至少3年,因此,简单的语法知识不再讲
述,仅讲解一些高级主题。
3.1Java语法深入
本节对Java语法中一些高级主题进行讲解.
3.1.1static
static变量
本质上讲,static关键字声明了一个全局变量。
尽管Java是一门纯面向对象的语言,语言规范中没有全局变量这种说法,但static声明的变量,从本质上就等同于
C或C++语言中的全局变量.整个应用程序就一个变量实例,任何对static变量进行的更改,在程序的其它地方都可
见。
举个例子如下:
publicclassStaticExample{
publicstaticintcounter;
}
counter是个整型变量,前面用static关键字修饰后,就变成了一个全局变量,在程序的代码执行之前,这个counter
变量就已经在内存中存在了,而且是唯一的一份实例。
counter静态变量是在StaticExample类中声明的,但实际上counter变量是独立于StaticExample的任何实例的。
也就是说,程序没有创建任何StaticExample类的实例时,counter已经存在。程序创建100个StaticExample实例时,
couner在内存中仍然是一份,而不是100份。当程序中创建的StaticExample类的实例都被虚拟机垃圾回收了,
counter还存在.因此静态变量的生命周期,可以认为程序的第一行代码执行之前,就已经在内存中准备好,在程
序的最后一行代码执行完毕,静态变量还在内存中存在。静态变量独立于任何对象,包括声明静态变量的类实例对
象。
对简单类型是这样,对复杂类型(对象类型)也是这样。
静态变量的这种特性,经常被用在单实例的类实现中,例子如下:
publicclassLogger{
//构造函数声明为private,这样就不能在类的外部用new来创
建对象
privateLogger(){}
privatestaticLoggerlogger=null;//单实例
//暴露给外界调用的方法
publicstaticLoggergetLogger(){
if(null==logger){〃判断静态实例是否初始化,如没有就创
建
logger=newLogger();
I
returnlogger;//返回全局唯一的实例
■
//……其它方法声明
}
static方法
如果一个类方法,仅依赖方法的传入参数、其它static变量,则此方法不依赖于声明方法的类实例,则此方法应该
声明为static,表示此方法是类方法,而非实例方法.例子如下:
publicclassConvertUtil{
publicstaticinttolnt(Strings){
returnInteger.parselnt(s);
}
}
tolnt方法的实现,仅依赖于方法传入的参数s,不依赖ConvertUtil对象的成员变量,因此tolnt方法是个类方法,
不是个实例方法,因此用static来修饰tolnt方法的声明。这样,调用者调用时的代码就象下面这样:
intiValue=ConvertUtil.tolnt(str);
如果不用static修饰,则调用者的代码就需要修改为如下:
ConvertUtilutil=newConvertUtil();
intiValue=util.tolnt(str);
但实际上,tolnt方法根本就不需要创建一个ConvertUtil类的实例,这个对象是个浪费。
组件设计中,常用的工具类方法,基本都是static声明的。
static类
一个普通的Java类声明,用static修饰,是非法的,编译器会提示出错。这一点,与C++的静态类的概念是完全不
同的.在C++中,一个类的声明用static修饰,则这个类中的所有成员变量和成员函数都是静态方法,独立于对象
存在。
对于嵌套类的声明,可以用static修饰,则为另外的含义,在后续的嵌套类中进行讲解。
3.1.2嵌套类
一个类的声明,是在另外一个类中,这种在类中声明的类叫嵌套类,也叫类中类、内部类。例子如下:
publicclassOuter{
privateStringouterld;
privateInnerinner=newlnner();
publicStringgetld(){
//访问内部类的私有成员
returnouterld++inner.innerld;
}
publicvoidsetldfStringid){
String[]strArray=id.split("-");
outerld=strArray[O];
inner.setld(strArray[l]);//调用内部类的私有方法
}
privatevoidprintStr(Stringstr){
System.out.println(str);
)
//内部类定义
publicclassInner{
privateStringinnerld;
privateStringgetld(){returninnerld;}
privatevoidsetld(Stringid){
innerld=id;
}
protectedvoidprintld(){
Stringstr="outerld="+outerld+",innerld="+innerld;//
访问外部类的私有成员
printStr(str);//访问外部类的私有方法
)
)
}
总结如下:
1)内部类可以访问外部类的任何成员变量,包括外部类的私有成员变量;
2)内部类可以调用外部类的任何成员函数,包括外部类私有成员函数;
3)外部类可以访问内部类的任何成员变量,包括内部类的私有成员变量;
4)外部类可以调用内部类的任何成员函数,包括内部类的私有成员函数;
因此,对于内部类的成员变量和成员函数,用private,protected,public,package修饰,就如同没有这些修饰词一样。
另外,内部类是不能独立于外部类而单独存在的,对象构造的顺序是由外向内,先生成外部类,然后生成内部类。
3.1.3静态嵌套类
嵌套类,在类声明的时候用static修饰,就成了静态嵌套类。静态嵌套类与普通嵌套类是完全不同的。这里,static
的唯一作用,就相当于一个分隔符,将内部类和外部类隔离开来,使内部类独立于外部类单独存在。也就是说,内
部类对外部类没有任何的依赖关系。而普通的内部类,是必须依赖于外部类而存在的。
因此,外部类与静态内部类之间的关系,就和两个独立的类之间的关系相同,二者之间互相访问,与两个独立类之
间的访问规则相同。因此,用private,protected,public,package修饰,将直接影响可访问性。示例如下:
publicclassOuterClass{
privateStringouterld;
privateStaticlnnerinner=newStaticlnner();
publicStringgetld(){
//returnouterld++inner.innerld();//私有成员,不允许访
问
returnouterld++inner.getlnnerld();//公有方法,可以访问
)
publicvoidsetld(Stringid){
String[]strArray=id.split("-");
outerld=strArray[O];
inner.setlnnerld(strArray[l]);//公有方法,可以访问
}
privatevoidprintStr(Stringstr){
System.out.println(str);
)
publicstaticclassStaticlnner{
privateStringinnerld;
publicStringgetlnnerld(){
returninnerld;
■)
publicvoidsetlnnerld(Stringinnerld){
this.innerld=innerld;
}
publicvoidprintld(){
//无法访问外部类的私有成员
//Stringstr="outerld="+outerld+",innerld="+innerld;
//printStr(str);//无法访问外部类的私有方法
OuterClassouter=newOuterClass();
outer.printStr(innerld);//同一package中,可以访问私有方
法
I)
I
3.2反射
我们通常写的程序,都是静态的代码,比如:调用classA示例的put方法:
Aa=newA();
a.put("Hello!”);
第二行,a.put就是一种静态的写法。在编译阶段,就完成对a类型的解析,对a是否具有put方法进行了判断。
如果a对象没有put方法,则编译不通过。
可是,在另外一些情况下,我们需要在运行时动态的对一些对象的指定方法进行调用,比如我们想写一个通用的函
数,传入一个对象,把这个对象的属性名和属性值都打印出来,这就需要使用反射技术。
反射,是在JDK1.5引入的,JDK1.4以及以前的版本不支持。
在java.lang.reflect包中,定义了一些类,用于动态解析和调用对象,常用的如下:
Constructor:构造函数
Field:成员变量
Method:成员方法
Type:类型
另外,javaJang包中,Class类是非常值得重视的,通常用它来动态创建对象。对于声明了无参数的构造函数、或
者有默认构造函数的类,动态创建一个对象的代码如下:
Classcis=Class.forName("com.test.ServicelmpI");
Objectobj=cls.newlnstance();
这样,一个com.test.ServicelmpI类的实例就创建出来了。
假如Servicelmpl类有个registerUser方法,实现用户注册功能,如下:
publicintregisterllser(StringuserName,Stringpasswd);
如何动态调用呢?代码示例如下:
//动态加载类
Classcis=Class.forName("com.test.Servicelmpl");
//动态创建对象
Objectobj=cls.newlnstance();
//获取相应的方法
Methodmethod=cls.getDeclaredMethodC?egisterUser",String.class,String.class);
StringuserName="zhangsan";
Stringpasswd="hellol23";
//动态调用对象的方法
Objectresult=method.invoke(obj,newObject[]{userName,passwd});
//强制类型转换,获取函数调用的返回值
intuserid=((lnteger)result).intValue();
System.out.println(nuserld=H+userid);
以上代码,需用try{}包括起来,捕获异常。
3.3数据结构
本节对编程中经常使用的线性数据结构和非线性数据结构进行了简单介绍。线性数据结构介绍两种:ArrayList和
LinkedList.非线性数据结构介绍两种:HashSet和HashMap。这些数据结构,都在java.util包中定义。
3.3.1ArrayList
ArrayList是个线性的数据结构,可以在任何位置对元素进行增加和删除。但请注意以下要点:
ArrayList是非同步的.也就是说,在多线程并发操作ArrayList的场景下,需要我们来写代码对ArrayList进行同
步;
ArrayList内部是用数组来实现的,对元素的增加和删除都会引起数组的内存分配空间动态发生变化。因此,对其
进行插入和删除速度较慢,但检索速度很快。
3)ArrayList中可以同时存放不同的对象;
ArrayList常用的方法有:
booleanadd(Objectelement);//在尾部添加一个对象
voidadd(intindex,Objectelement);//在指定位置插入元素,index从0计起
Objectget(intindex);//获取指定位置的元素
booleancontains(Objectelem);//判断是否包含此元素
intindexOf(Objectelem);//获取指定元素在链表中的位置
Objectremove(intindex);//删除指定位置的元素
booleanremove(Objecto);//删除指定的对象
intsize();//获取链表中元素的数量
voidclear();//清空链表
3.3.2LinkedList
LinkedList也是一个线性的数据结构,可以在任何位置对元素进行增加和删除。使用LinkedList时,需注意以下:
1)LinkedList是非同步的。也就是说,在多线程并发操作LinkedList的场景下,需要我们来写代码对LinkedList进行
同步;
2)LinkedList内部就是用动态链表来实现的,因此与ArrayList相比,其增加和删除元素速度很快,整个List不需要
重新分配空间,但检索速度较慢;
3)LinkedList实现了Queue接口,可以用做一个先进先出的队列来使用;
LinkedList常用的方法与ArrayList类似,增加了Queue接口的方法如下:
booleanoffer(Objectelement);//增加元素到队列尾部
Objectpeek();//获取队列头部的元素,并且不从队列中移出
Objectpoll();//获取队列头部的元素,并且从队列中移出
Objectremove();//将队列头部元素移出
3.3.3HashSet
HashSet是个集合类型的数据结构,实现了Set接口,满足数学上对集合的定义:无序、不重复。使用HashSet
时需要注意:
1)HashSet是非同步的。也就是说,在多线程并发操作HashSet的场景下,需要我们来写代码对HashSet进行同步;
2)HashSet内部是用HashMap来实现的,对元素的增加、删除、检索有很高的性能;
3)HashSet中元素是无序的,因此对HashSet进行遍历,不保证遍历元素的顺序都一致;
4)HashSet中没有重复的元素,因此将相同的多个对象加入到HashSet中,则最终HashSet中只有一个对象;、
HashSet常用的方法如下:
booleanadd(Objecto);//增加元素到集合中
booleanremove(Objecto);//从集合中删除元素
booleancontains(Objecto);//判断集合中是否包含指定的对象
Iteratoriterator();//返回集合的迭代器,用于遍历集合
booleanisEmpty();//判断集合是否为空
intsize();//获取集合中元素的数量
voidclear();//清空集合
3.3.4HashMap
HashMap,又称杂凑表、散列表,它是一个非线性的数据结构。其中每个元素由Key和Value对构成,Key用来
做索引,Value是实际要存储的值。使用HashMap时需要注意:
)HashMap是非同步的。也就是说,在多线程并发操作HashMap的场景下,需要我们来写代码对HashMap进行同
步;
)HashMap对元素插入、删除、检索的速度都非常快,在高性能的情况下被大量使用,其缺点是内存占用较多。
)HashMap中可以存放不同类型的对象,包括null。
HashMap常用的方法如下:
Objectput(Objectkey,Objectvalue);//将Key-Value对放入Map中
Objectget(Objectkey);//用Key获取Value对象
Objectremove(Objectkey);//从Map中删除Key-Value对
booleancontainsKey(Objectkey);//判断是否包括指定的Key
booleancontainsValue(Objectvalue);//判断是否包含指定的Value
SetkeySet();〃获取Key的集合,配合get方法,可以遍历Map
SetentrySet();//获取Map.Entry的集合,可以通过Map.Entry直接遍历每个Key和Value
booleanisEmpty();//判断是否为空
intsize();//判断Map中的元素个数
voidclear();//清空Map
3.4泛型
3.4.1泛型简介
先拿一个例子来说明泛型是什么。
有两个类如下,要构造两个类的对象,并打印出各自的成员X。
publicclassStringFoo{
privateStringx;
publicStringgetX(){
returnx;
1
publicvoidsetX(Stringx){
this.x=x;
)
publicclassDoubleFoo{
privateDoublex;
publicDoublegetX(){
returnx;
)
publicvoidsetX(Doublex){
this.x=x;
)
}
如果要实现对Integer、Long、Date等类型的操作,还要写相应的类,实在是无聊之极.
因此,对上面的两个类进行重构,写成一个类,考虑如下:
上面的类中,成员和方法的逻辑都一样,就是类型不一样。Object是所有类的父类,因此可以考虑用Object做为成
员类型,这样就可以实现通用了。
publicclassObjectFoo{
privateObjectx;
publicObjectgetX(){
returnx;
)
publicvoidsetX(Objectx){
this.x=x;
)
}
调用的代码如下:
publicclassObjectFooDemo{
publicstaticvoidmain(Stringargs[]){
ObjectFoostrFoo=newObjectFoo();
strFoo.setX("HelloGenerics!");
ObjectFoodouFoo=newObjectFoo();
douFoo.setX(newDouble("33"));
ObjectFooobjFoo=newObjectFoo();
objFoo.setX(newObject());
Stringstr=(String)strFoo.getX();
Doubled=(Double)douFoo.getX();
Objectobj=objFoo.getX();
System.out.println(HstrFoo.getX="+str);
System.out.println("douFoo.getX=n+d);
System.out.println(,'strFoo.getX=H+obj);
)
}
以上,是没有泛型的情况下,我们编写的代码,采用最顶层基类Object进行类型声明,然后将值传入,取出时要进
行强制类型转换。
JDK从1.5开始引入了泛型的概念,来优雅解决此类问题。采用泛型技术,编写的代码如下:
publicclassGenericsFoo<T>{
privateTx;
publicTgetX(){
returnx;
}
publicvoidsetX(Tx){
this.x=x;
)
}
调用的代码如下:
publicclassGenericsFooDemo{
publicstaticvoidmain(Stringargs[]){
GenericsFoo<String>strFoo=newGenericsFoo<String>();
strFoo.setX("HelloGenerics!");
GenericsFoo<Double>douFoo=newGenericsFoo<Double>();
douFoo.setX(newDouble("33n);
GenericsFoo<Object>objFoo=newGenericsFoo<Object>();
objFoo.setX(newObject());
Stringstr=strFoo.getX();
Doubled=douFoo.getX();
Objectobj=objFoo.getX();
System.out.println("strFoo.getX="+str);
System.out.println("douFoo.getX=n+d);
System.out.println("strFoo.getX="+obj);
注意,有几点明显的改变:
1.对象创建时,明确给出类型,如GenericsFoo<String>。
2.对象通过getX方法取出时,不需要进行类型转换。
3.对各个方法的调用,如果参数类型与创建时指定的类型不匹配时,编译器就会报错。
那么我们为什么要泛型呢?有两个好处:
1.可以在编译时检查存储的数据是否正确。我们开发有一个趋向就是尽早的发现错误,最好就是在编译阶段,泛
型正好符合这一条件。
2.减少了强制转换,Stringstr=(String)strList.get(O);这样的操作属于一种向下转型,是比较危险的操作,当List
内存储的对象不适String时就会抛出异常.
JDK1.5中,java.util包中的各种数据类型工具类,都支持泛型,在编程中被广泛使用,需要好好掌握。
泛型最常见的应用是应用在类、接口和方法上,下面分别介绍。
3.4.2泛型应用在接口上:
publicinterfaceValuePair<A,B>{
publicAgetA();
publicBgetB();
publicStringtoString();
这里A和B都是代表类型。尖角号<>中,可以使用一个类型,也可以使用多个类型。
3.4.3泛型应用在类上:
publicclassValuePairlmpl<A,B>{
publicfinalAfirst;
publicfinalBsecond;
publicValuePairlmpl(AazBb){first=a;second=b;}
publicAgetA(){returnfirst;}
publicBgetB(){returnsecond;}
publicStringtoString(){
return+first+","+second+
}
)
如果这个类实现泛型接口,则相应的写法为:
publicclassValuePairlmpl<A,B>implementsValuePair<A,B>{
}
3.4.4泛型应用在方法上:
泛型也可以应用在单独的方法上,示例如下:
publicclassGenericMethod{
public<T>voidprintValue(Tv){
Stringstr=v.getClass().getName()+“="+v.toString();
System.out.println(str);
)
)
注意语法:在public修饰符后面是<>,然后是函数返回值,接着是函数名,函数参数。当然,返回值也可以是泛型
的类型。
3.4.5限制泛型的可用类型
以上介绍的三种泛型应用,应用在接口、类、方法上,是一种通用的做法,对泛型可以传入的类型没有任何限制。
但有些场景下,我们希望对可用的类型进行限制,比如希望传入的类型必须从某个类继承(也就是说,必须是某
个类的子类、孙类等),这种情况下就用到了泛型限制的语法。
extends:限制泛型类型必须为某个类的后代,包括本类型。
语法:<TextendsparentClass>
这里,T为泛型类型,extends关键字限制泛型类型必须是parentclass的后代。parentclass指定父类的类型,也
可以是接口。
在Java语言中,对类只能单继承,对接口可以多继承,如果要限制指定类型必须从某个类继承,并且实现了多个
接口,则语法为:
<Textendsparentclass&parentlnterfacel&parentlnterface2>
注意,类必须在接口前面。
举例如下:
publicclassBaseClass{
intvalue;
publicBaseClass(intvalue){
this.value=value;
)
publicintgetValue(){
returnvalue;
)
publicvoidsetValue(intvalue){
this.value=value;
publicclassSubClassextendsBaseClass{
publicSubClass(intvalue){
super(value*2);
publicclassGenericBound<TextendsBaseClass>{
publiclongsum(List<T>tList){
longiValue=0;
for(BaseClassbase:tList){
iValue+=base.getValue();
returniValue;
publicstaticvoidmain(String[]args){
GenericBound<SubClass>obj=new
GenericBound<SubClass>();
List<SubClass>list=newLinkedList<SubClass>();
list.add(newSubClass(5));
list.add(newSubClass(6));
System.out.println(obj.sum(list));
)
)
运行,输出结果为22.
接着,我们再深入探讨一下。把上面的例子该写如下:
publicclassGenericBound<TextendsBaseClass>{
publiclongsum(List<T>tList){
longiValue=0;
for(BaseClassbase:tList){
iValue+=base.getValue();
}
returniValue;
)
publicstaticvoidmain(String[]args){
//注意!!!
//将obj的类型由GenericBoundvSubClass>改为GenericBound〈BaseClass>,无法通过编译
GenericBound<BaseClass>obj=new
GenericBound<SubClass>();
List<SubClass>list=newLinkedList<SubClass>();
list.add(newSubClass(5));
list.add(newSubClass(6));
System.out.println(obj.sum(list));
)
}
语句GenericBound<BaseClass>obj=newGenericBound<SubClass>();无法通过编译,其根本原因在于,GenericBound
类声明处的<TextendsBaseClass>,限制了构造此类实例的时候T是确定的一个类型,这个类型是BaseClass的后代。
但是BaseClass的后代还又很多,如SubClass3,SubClass4,如果针对每一种都要写出具体的子类类型,那也太麻
烦了,干脆还不如用Object通用一下。能不能象普通类那样,用父类的类型引入各种子类的实例,这样不就简单
了很多?答案是肯定的,泛型针对这种情况提供了更好的解决方案,那就是“通配符泛型”,下面详细讲解。
3.4.6通配符泛型
Java的泛型类型如同java.Iang.String,java.io.File一样,属于普通的Java类型。比方说,下面两个变量的类型就
是互不相同的:
Box<Object>boxObj=newBox<Object>();
Box<String>boxStr=newBox<String>();
虽然String是Object的子类,但是Box<String>和Box<Object>之间并没有什么关系----Box<String>不是
Box<Object>的子类或者子类型,因此,以下赋值语句是非法的:
boxObj=boxStr;//无法通过编译
因此,我们希望使用泛型时,能象普通类那样,用父类的类型引入各种子类的实例,从而简化程序的开发。Java的
泛型中,提供?通配符来满足这个要求。
代码示例如下:
publicclassWildcardGeneric{
publicvoidprint(List<?>1st){
for(inti=0;i<lst.size();i++){
System.ouLprintln(lst.get(i));
)
)
publicstaticvoidmain(String[]args){
WildcardGenericwg=newWildcardGeneric();
ArrayList<String>strList=newArrayList<String>();
strList.addf'One");
strList.add("Two");
wg.print(strList);
LinkedList<lnteger>intList=newLinkedList<lnteger>();
intList.add(25);
intList.add(30);
wg.print(intList);
)
)
但是这种情况下,WildcardGeneric.print方法的参数可以接受类型可能对于程序员设计的意图而言太广泛了一点。
因为我们可能只是希望print可以接受一个List,但这个List中的元素必须是Number的后代。因此,我们要对通配
符有所限制,这时可以使用边界通配符(boundedwildcard)形式来满足这个要求。我们将print方法再修改一下:
publicvoidprint(List<?extendsNumber>1st){
for(inti=0;i<lst.size();i++){
System.ouLprintln(lst.get(i));
)
)
it#,List<lnteger>.List<Short>等等类型的变量就可以传给print方法,而储存其他类型元素的List的泛型类型
变量(如List<String>)传给print方法将是非法的。
除了?extends上边界通配符(upperboundedwildcard)以外,我们还可以使用下边界通配符(lowerbounded
wildcard),例如List<?superViewWindow〉。
最后总结一下使用通配符的泛型类型的三种形式:
GenericType<?>
GenericType<?extendsupperBoundType>
GenericType<?superlowerBoundType>
3.4.7泛型深入
我们已经初步掌握了泛型的基本用法,接着再来探讨一下深入的主题。
我们还是先来看一段代码:
publicclassGenericsFoo<T>{
privateTx;
publicTgetX(){
returnx;
)
publicvoidsetX(Tx){
this.x=x;
}
publicstaticvoidmain(String[]args){
GenericsFoo<String>gf=newGenericsFoo<String>();
gf.setX("Hello");
GenericsFoo<?>gf2=gf;
gf2.setX("World");//报错!!!
Stringst
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年度木材行业市场调研与营销策划合同4篇
- 2025年企业投资贷款合同
- 2025年家具家电购买合同
- 2025年分期付款汽车销售合同
- 2025年天然气输气管道合作协议
- 2025版住宅小区水电暖消防系统改造与节能评估服务合同3篇
- 2025年健身健康检测合同
- 2025年二手房合同样本
- 二零二五至二零二五年度通信设备采购合同2篇
- 2025版屋面防水劳务分包合同(含防水检测服务)3篇
- 狮子王影视鉴赏
- 一年级数学加减法口算题每日一练(25套打印版)
- 2024年甘肃省武威市、嘉峪关市、临夏州中考英语真题
- DL-T573-2021电力变压器检修导则
- 绘本《图书馆狮子》原文
- 安全使用公共WiFi网络的方法
- 2023年管理学原理考试题库附答案
- 【可行性报告】2023年电动自行车相关项目可行性研究报告
- 欧洲食品与饮料行业数据与趋势
- 放疗科室规章制度(二篇)
- 中高职贯通培养三二分段(中职阶段)新能源汽车检测与维修专业课程体系
评论
0/150
提交评论