---------------------- <a href="http://www.itheima.com"target="blank">ASP.Net+Unity开发</a>、<a href="http://www.itheima.com"target="blank">.Net培训</a>、期待与您交流! ----------------------
Ø 计算机革命起源于机器,但计算机远非机器那么简单。计算机更像我们头脑的一部分,就如同头脑延伸的工具。通过计算机我们可以写作、绘画、游戏、动画、电影等,它就是一个表达媒介!而面向对象程序设计便是这种以计算机作为表达媒体的大趋势中的组成部分
Ø 操作系统就是个资源的管理器。管理计算机上的全部资源,如CPU的计算时间、内存、IO等。
Ø 是一种能够按照程序运行,自动、高速处理海量数据的现代化智能电子设备
Ø 计算机只识别0和1,使用0和1表示世界万物。计算机最小单位是Bit,使用的是二进制数。一个bit(比特)可以存放0和1,8 Bit=1 Byte,1024 Bytes=1 KB
Ø 计算机硬件系统按功能分为运算器、控制器、存储器、输入设备、输出设备五大部分
Ø 由硬件和软件所组成,没有安装任何软件的计算机称为裸机
中央处理器(CPU):执行所有的处理工作
存储器:内存条、硬盘、光盘、U盘、存储卡
输入设备:输入数据,向计算机发送指令。键盘、鼠标、扫描仪、摄像头
输出设备:查看信息或输出处理的数据。显示器、打印机、音响
其他设备:主板和机箱
系统软件:控制和支持计算机系统,如Windows XP,Linux,Mac
应用软件:通常指一些工具软件,如Office、Photoshop
Java编程思想
程序设计历史
引子:
1. 所有编程语言都提供抽象机制,而抽象的质量直接决定问题的复杂度。汇编语言是对底层机器的轻微抽象,接着出现许多所谓“命令式”语言(如BAsIC、C等)都是对汇编语言的抽象。虽然这些语言在汇编语言的基础上有了大幅的改进,但是它们所作的主要抽象仍要求在解决问题时要基于计算机结构,而不是基于所要解决的问题结构。
2. 而面向对象方式是:将现实世界中遇到的实际问题模拟为计算机上的类似实体。所以程序员通过定义类来适应问题,而不再被迫只能使用现有的、机器中内置的存储单元的数据类型。而且可以根据需求,通过添加新的数据类型使之能完整描述某个特定问题。编程系统会欣然接受新的类,并且像对待内置类型一样地照管它们和进行类型检查。
程序设计历史:程序设计的本质是把人们在现实生活中遇到的问题通过抽象处理,利用编程语言转换到计算机能够理解的层面上去。程序设计大致经历了过程式程序设计、结构化程序设计和面向对象程序设计这三个阶段。
过程式程序设计需要开发者对程序每一步进行精确的设计和严格的控制。过程式编程语言为:程序=算法+数据;
结构化程序设计需要开发者在编码之前将程序进行完整的规划,设计出各种图表,画出各种数据流向,指明各个函数直接的相互作用,是一种自顶向下、逐步求精、使程序结构模块化的程序设计方法。最小程序单元是方法
面向对象程序设计是将对象最为程序的基本单元,并将程序和数据封装到其中,以提高软件重用性、灵活性和扩展性,每一个对象都代表现实世界中的一个具体事物(或者称为实体)。面向对象程序设计是当今主流的程序设计思想。它力图使程序和现实世界的具体实体一致,这样可以使开发者与用户之间能很好的理解和沟通。面向对象编程语言为:程序=对象+消息。
Java历史与特点
Java的诞生源于C++,普及成熟得益于www的快速发展
JAVA 语言特点
Ø 简单性
ü 略去了运算符重载、多重继承等
ü 通过实现自动垃圾收集大大简化了程序设计者的内存管理工作
Ø 面向对象
Ø 强类型语言
q 每个变量、表达式都有类型
q 所有赋值语句都要经过类型检查
Ø 健壮性
ü 自动垃圾收集来进行内存管理,防止程序员在管理内存时容易产生的错误
ü 异常处理机制
Ø 支持分布式网络应用。通过它提供的类库可以处理基于TCP/IP的协议,用户可以通过URL地址很方便地访问在网络上的对象
Ø 安全性和健壮性 Java致力于检查程序在编译和运行时的错误,类型检查能检查出许多开发早期的错误。同时不支持指针,一切对内存的访问都必须通过对象的实例变量来实现,这样就防止程序员访问对象的私有成员,同时也避免了指针操作中容易产生的错误。
Ø 体系结构中立 Java解释器生成与体系结构无关的字节码指令,只要安装了Java运行时系统,Java程序就可在任意的处理 器上运行。
Ø 可移植性与平台无关的特性使Java程序可以方便地被移植到网络上的不同机器。Java运行时系统由标准C实现,这使得Java系统本身具有可移植性。 Java编译器编译一次Java源程序,就可以在安装了java运行系统(JRE)的任意处理器上运行
如何写程序?
管理Java应用
是指创建Java应用的目录结构、编译、运行及发布Java应用的操作。下表显示了Java应用的一种常用开发目录结构。
程序
Ø 应用程序是指在计算机的操作系统支持下运行的程序。应用程序既可以基于GUI,又可以基于命令行界面
Ø 程序的基本功能就是处理数据。数据通过变量表示
Ø 程序是由许多语句组成的,而语句的基本单位是表达式与运算符
Ø 程序就是对象的集合, 通过消息传递,各对象知道自己该做些什么
你可以抽取待求解问题的组成元素,将它表示为程序中的对象。软件系统的复杂功能是由各种对象协同工作来共同完成的。
Ø Java应用由一个或多个扩展名为“.java”的文件构成,这些文件被称为java源文件,被编译后就是一系列.class文件。一个有效的程序就是一系列.class文件,它们可以封装和压缩到一个JAR文件里。Java解释器负责对这些文件的寻找、装载和解释
Ø 建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(接口中的方法)定义的,对象的“类型”或“类”则规定了它的接口形式
写程序前必问自己(将程序分解为对象集合)
该建立怎样的能模拟问题领域的对象模型?
1.”程序涉及到哪些对象?
2.”这些对象JDK API中是否已存在”?
3.对于那些不存在的对象,它们看起来是什么样子?它们能够提供哪些服务?它们需要哪些对象才能调用这些服务?
4.每个对象只是用来很好的完成一项任务,它并不试图做更多的事!一个类代表一个有用的代码单元
客户端程序员目标:收集和熟练各种实现快速应用开发的类.
软件开发
引子:任何一个软件应用都是对某种现实系统的模拟,在现实系统中会包含一个或多个实体,这些实体具有特定的属性和行为。
软件工程:研究的是由许多程序员参与的大型复杂的计算机程序的创建方法。它强调的是程序的整体设计和如何依照最终用户需求而进行设计的问题。软件工程关系着一个软件项目的整个生命周期,包括分析、设计、验证、编码、测试、生产和维护各阶段。
由来:任何事情都要先想清楚了才能做,软件开发更是如此!软件开发过程不可能一上来就开始盲目写代码,写代码之前必须搞清楚要做什么?做成什么样?怎么去做?
软件设计: 把软件开发想清楚的过程.
软件生命周期: 软件的产生直到报废的生命周期
l 软件生命周期内有问题定义, 可行性分析, 总体描述, 系统设计,编码, 调试和测试, 验收与运行, 维护升级到废弃等阶段
l 软件工程可以分为三个大的阶段:需求; 设计; 测试与维护
需求: 开发目标,可行性分析 ,需求分析
1. 问题的定义及规划(可行性分析报告和软件开发计划): 此阶段是软件开发方与需求方共同讨论,主要确定软件的开发目标及其可行性。
2、需求分析(需求分析说明书和初步的用户手册): 在确定软件开发可行的情况下,对软件需要实现的各个功能进行详细分析。需求分析阶段是一个很重要的阶段,这一阶段做得好,将为整个软件开发项目的成功打下良好的基础。
设计: 概要设计,详细设计,编码与单元测试
3、软件设计(概要设计、详细设计): 此阶段主要根据需求分析的结果,对整个软件系统进行设计,如系统框架设计
数据库设计等等。软件设计一般分为总体设计和详细设计。
4、程序编码(提交源程序及清单): 此阶段是将软件设计的结果转换成计算机可运行的程序代码。在程序编码中必须要制定统一,符合标准的编写规范。以保证程序的可读性,易维护性,提高程序的运行效率。
测试与维护: 综合测试,维护
5. 软件测试(提交软件维护测试报告): 在软件设计完成后要经过严密的测试,以发现软件在整个设计过程中存在的问题并加以纠正。整个测试过程分单元测试(白盒)、集成测试(黑盒,功能测试、强度性能测试)以及系统测试三个阶段进行。测试的方法主要有白盒测试和黑盒测试两种。在测试过程中需要建立详细的测试计划并严格按照测试计划进行测试,以减少测试的随意性。
6、运行维护(提交软件维护报告) : 软件维护是软件生命周期中持续时间最长的阶段。在软件
开发完成并投入使后,由于多方面的原因,软件不能继续适应用户的要求。要延续软件的使
用寿命,就必须对软件进行维护。软件的维护包括纠错性维护和改进性维护两个方面。
Ø 可重用性:减少重复代码,避免重复编程
Ø 可维护性:当用户需求发生变化,只需修改局部子系统的少量代码,而不会牵一发而动全身
软件子系统
Ø 结构稳定性:软件的设计阶段,在把一个系统划分成更小的子系统时,设计合理,使得系统的结构比较健壮,能适应用户需求变化
Ø 可扩展性:当软件必须增加新功能时,能在现有系统结构的基础上,方便的创建新子系统,而不用改变系统现有结构。该子系统继承了原子系统的一些特性,并具有一些新特性
Ø 内聚性:每个子系统只完成特定功能,不同子系统之间不会有功能的重叠。为了避免重叠,每个子系统粒度都要尽可能小。
Ø 可组合性:子系统经过组合,就变成了大系统
Ø 松耦合:子系统之间相互独立,修改一个子系统,不会影响其他子系统。当用户需求发生变化,只需修改特定子系统的实现方式,从而提高软件的可维护性
软件的生命周期 ---- RUP
l 统一软件开发过程(Rational Unified Process,RUP): 一个通用的软件流程框架, 以架构为中心, 用例驱动的迭代化开发流程. RUP 是从几千个软件项目的实践经验中总结出来的, 对于实际的项目具有很强的指导意义.
l RUP中的构架: 是一种设计的基线. 建造这样的基线采取的策略是, 从用例出发, 寻找那些稳定的,业务意义重大的, 技术风险可以在早期解决的部分, 构建一个可以运行的程序. 以后的开发, 尽量使用以存在的组件 增量和迭代: 在构建构架的基础上, 添加新的部分, 按照周期性提交最终结果的方式进行开发.
l RUP 用二维坐标来描述. 横轴通过时间来组织, 是过程展开的生命周期特征, 体现开发过程的动态结构; 纵轴以内容来组织, 体现开发过程的静态结构.
Ø 初始阶段: “获得项目的基础”. 该阶段的主要人员是项目经理和系统设计师. 所要完成的主要任务包括对系统的可行性分析; 创建基本的需求; 识别系统的关键任务.
Ø 细化: 主要目标是创建可执行构件基线; 精化风险评估; 捕捉大部分的系统功能需求用例; 为构造阶段创建详细需求. 该阶段并不是要创建可执行的系统, 而是展现用户所期望的需求.
Ø 构建: 完成所有的需求, 分析和设计. 该阶段的制品将演化成最终系统
Ø 交付: 将完整的系统部署到用户所处的环境中.
软件工程: 对软件开发全过程进行建模和管理
模型: 模型是对现实的简化. 通过模型, 人们可以了解所研究事物的本质。例如最杰出的模型: 地图
建模: 对现实系统进行适当的过滤, 用适当的表现规则描述出简洁的模型。建模是一种深入解决问题的方法.
建模的原则: (1).选择建立什么样的模型对如何发现和解决问题具有重要的影响
(2). 每个模型可以有多种表达方式. 使用者的身份和使用的原因是评判模型好坏的关键。
(3). 最好的模型总是能够切合实际. 模型是现实的简化,必须保证简化过程不会掩盖任何重要的细节。
(4). 孤立的模型是不完整的。
建模的作用:是把来源于现实世界的问题转化为计算机可以理解和实现的问题.
建模的实现过程是从需求入手, 用模型表达分析设计过程, 最终将模型映射成软件实现.
UML
UML(United Modeling Language, 统一建模语言):
Ø UML是一种基于面向对象的可视化建模语言. 是一种以可视化观点来看程序系统、将规格和设计重点直接写下来的表现方式
Ø UML 采用一组图形(如类图) 元素来直观地表示对象模型, 使用这些元素可以形象地描述系统的各个方面
Ø UML 通过建立图形之间的各种关系(如类与类之间的关系)来描述模型.
Ø 通过使用UML,开发人员能够阅读和交流系统架构图和设计规划图—就像建筑工人多年来使用建筑设计图一样。
UML 中一共有 10 种图:
静态模型图: 描述系统的静态结构
• 类图:类图是面向对象系统建模中最常用的图,是定义其他图的基础。类图主要是用来描述系统中的类、对象实例、接口以及它们之间的关系。
• 对象图:对象图是类图的一个实例, 用于显示系统执行时的一个快照. 即在某一个时间上系统可能出现的样子
• 包图:由包和包之间的关系组成.
• 组件图:描述系统中各组件之间的关系, 各组件通过功能组织在一起。组件图用来设计系统的整体构架。
• 部署图:用来帮助开发者了解软件中的各个组件驻留在什么硬件位置, 以及这些硬件之间的交互关系。
动态模型图: 描述系统行为的各个方面
• 用例图:也称为用户模型图, 是从软件需求分析的第一步, 它是从客户的角度来描述系统功能. 包含
参与者(Actor), 用例(Use Case),
• 时序图:时序图用于描述对象之间的传递消息的时间顺序, 即用例中的行为顺序。当执行一个用例时, 时序图中
的每条消息对应了一个类操作或者引起转换的触发事件.
• 活动图:活动图本质上就是流程图. 它用于描述系统的活动, 判定点和分支等
• 状态图:描述对象所有可能的状态及导致状态转换的转移条件。只需为个别具有复杂的状态转换过程的类提供状态 图。
• 协作图:(也叫合作图)是一种交互图.协作图表达对象间的交互过程及对象间的关联关系。
UML 中的关系主要包括 4 种:
关联关系(association) 对于两个相对独立的系统,当一个系统的实例与另一个系统的一些特定实例存在固定的对应关系时,这两个系统之间为关联关系。关联关系可以有一个名称, 用于描述该关系的性质.
依赖关系(dependency) 对于两个相对独立的系统,当一个系统负责构造另一个系统的实例,或者依赖另一个系统的服务时,这两个系统之间体现为依赖关系
泛化关系(generalization)表示同一业务目的(父用例)的不同技术实现(各个子用例). 泛化关系用来表示类与类, 接口与接口之间的继承关系.
实现关系(realization) 实现关系用来表示类与接口之间的实现关系.
面向对象的设计原则
单一职责原则:一个类,只有一个引起它变化的原因,如果一个类有一个以上的职责,这些职责就耦合在了一起
– 当一个职责发生变化时,可能会影响其它的职责
– 多个职责耦合在一起,会影响复用性
开闭原则:软件实体(类、模块、函数等)应该是可扩展的,但不可修改
• 遵循开发-封闭原则设计的两个主要特征
– 对于扩展是开放的
– 对于更改是封闭的
• 好处
– 可复用性好
• 软件完成以后,仍然可以对软件进行扩展,加入新的功能,非常灵活 。因此,这个软件系统就可以通过不断地增加新的组件,来满足不断变化的需求。
– 可维护性好
• 由于对于已有的软件系统的组件,特别是它的抽象底层不去修改,因此,我们不用担心软件系统中原有组件的稳定性,这就使变化中的软件系统有一定的稳定性和延续性
里氏代换原则
• 子类必须能够替换掉它们的基类型
• 里氏代换原则是对“开-闭”原则的补充
• 如果两个具体的类A,B之间的关系违反了LSP的设计
– 创建一个新的抽象类C,作为两个具体类的超类,将A,B的共同行为移动到C中来解决问题
– 从B到A的继承关系改为委派关系
• 在进行设计的时候,我们尽量从抽象类继承,而不是从具体类继承
• 长方形和正方形的例子不满足LSP
– OOD的IS-A关系是就行为方式而言的,行为方式是可以进行合理假设的,是客户程序设计所依赖的
依赖倒置原则
• 高层模块不应该依赖于底层模块,二者都应该依赖于抽象
• 抽象不应该依赖于细节。细节应该依赖于抽象
• Hollywood原则: “Don‘t call us, we’ll call you”.程序中所有的依赖关系都应该终止于抽象类和接口针对接口而非实现编程
• 依赖于抽象的启发式规则:
– 任何变量都不应该持有一个指向具体类的指针或引用
– 任何类都不应该从具体类派生
– 任何方法都不应该覆写他任何基类中的已经实现了的方法
接口隔离原则
• 使用多个专门的接口比使用单一的总接口要好
• 一个类对另外一个类的依赖性应当是建立在最小的接口上的
• 一个接口代表一个角色,不应当将不同的角色都交给一个接口
• 不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构
• 满足接口隔离原则,调用者只能访问它自己的方法,不能访问到不应该访问的方法
合成/聚合复用原则
• 合成(Composition)和聚合(Aggregation)都是关联(Association)的特殊种类。聚合表示整体和部分的关系,表示“拥有 ”;合成则是一种更强的“拥有”,部分和整体的生命周期一样
• 在OOD中,有两种基本的办法可以实现复用
– 合成/聚合
– 继承
• HAS-A而非IS-A
迪米特法则
• 迪米特法则可以简单说成:talk only to your immediate friends
• 一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
• 迪米特法则的初衷在于降低类之间的耦合
• 设计模式的门面模式(Facade)和中介模式(Mediator),都是迪米特法则应用的例子
类、对象和方法高效设计原则
类与对象高效设计原则
• 避免创建重复对象
• 消除过期的对象引用
• 避免使用终结函数
• 为所有导出API的元素编写文档注释
• 使类和成员的可访问能力最小化
• 复合优先于继承
继承是复用的用力手段但不是最佳工具
• 对普通类跨包继承,是非常危险的
• 与方法调用不同,继承打破了封装性
• 使用复合的方式可以完全取代继承的复用。
复合类不依赖于原有类的实现细节,原有类修改不会影响到复合类
• 接口优先于抽象类
– 抽象类只允许单继承,作为类型定义受到了极大限制
已有的类可以很容易被更新,实现新的接口,并使得安全的增强一个类的功能成为可能
• 定义内部类:优先考虑静态成员类
方法高效设计
• 谨慎选择方法的名字,要简洁易懂
• 避免长长的参数列表
• 对于参数类型,优先使用接口而不是类
• 返回零长度数组而不是null
• 将局部变量的作用域最小化
– 第一次使用它的地方声明
– 几乎每一个局部变量的声明都应该包含一个初始化表达式
• for循环优先于while循环
• 使方法小而集中
Java环境
JDK&JRE
Java优势:简单、跨平台、面向对象、安全性高、分布式、多线程
JDK:Java开发工具集(Java Development Kit)
Ø JDK提供了Java的开发环境和运行环境,由java虚拟机、java应用编程接口(API ,就是java基础类库rt.jar)和开发工具(开发者用以编译、调试和运行Java程序的工具)组成
Ø Java开发人员可以通过它开发程序或调用java内容
Ø Sun公司认为JDK 1.5的发布是JAVA语言发展史上的又一里程碑事件。为了表示这个版本的重要性,JDK1.5又称为JDK 5.0
JDK安装目录:
bin目录:存放JDK的工具程序是可执行文件;
lib目录:存放工具程序使用的Java类库文件;
include目录:存放用于本地方法的文件;
demo目录:存放演示程序;
jre目录:存放Java运行环境文件;
bin目录及所提供的工具程序提供了一些开发Java程序时必备的工具程序。需将该目录添加到操作系统的系统环境变量PATH中。
JRE:
Ø Java Runtime Enviroment. JRE包含一方面与硬件交互,另一方面与程序交互的JVM。它是java程序运行时所必须的环境集合。主要由JVM、Java平台核心类、若干支持文件(类加载器、字节码校检器等)组成。
Ø 在程序运行时,JRE由类加载器负责查找和加载程序引用到的基本类库和其他类库。操作系统通过PATH环境变量来查找JRE并确定基础类库文件rt.jar的位置;其他类库由类加载器在环境变量CLASSPATH指定的路径中搜索。因此,在java程序启动前最好先把PATH和CLASSPATH环境变量设置好
JVM
JVM:Java虚拟机是一个虚构出来的计算机,可在实际的计算机中模拟各种计算机功能,是一个软件程序。JVM有自己完善的硬件架构,例如处理器、堆栈和寄存器等,还具有相应的指令系统。使用java.exe工具执行Java程序时,操作系统会自动寻找JRE环境
(1)类加载器:为程序的执行加载所需要的全部类。类加载器将本地文件系统的类名空间与来自远程网络源的类名空间相分离,本地类总是首选被加载,用来保证安全性。当全部类被加载后,可执行文件的存储器格式被确定,然后依照这些模板创建对象
(2)字节代码校验器:基于代码的规范包括语法语义的检查以及如上所述的安全性检查。
(3)JAVA运行时解释器:它是JVM的核心内容,实现把抽象的字节码指令映射到本地系统平台下的库引用或指令。
JVM作用:
Ø JVM负责解析和执行Java程序。
Ø 可以运行在各种操作系统平台上。只要在任意操作系统装入JVM,java程序就有了可移植性。Java源码和字节码与机器无关,但JVM与机器相关,装有不同操作系统的机器上,需有专门为该操作系统开发的JVM
Ø 计算机受其存储单元的限制,只能表示和操作一些基本数据类型。对象模型中的类可看作是开发人员自定义的数据类型,jvm的运行时环境封装了把自定义数据类型映射到计算机的内置数据类型的过程,使得开发人员不受计算机内置数据类型的限制,对任意问题领域,都可以方便的根据“先识别对象,再进行分类”的思路来建立对象模型
Ø JVM在整个JDK的最底层,负责与操作系统的交互,用来屏蔽操作系统环境,提供一个完整的java运行环境。操作系统装入JVM是通过JDK中的java.exe来实现。
Ø 是java字节码执行的引擎,为java程序的执行提供必要的支持,程序员编写的程序最终都要在JVM上执行。它还能优化java字节码,使之转换成更高效率的机器指令。
JVM细节
Ø JVM实例对应了一个独立运行的java程序 它是进程级别 。JVM执行引擎实例则对应了属于用户运行程序的线程 它是线程级别的
Ø Java 源代码和字节码与机器无关,但JVM 是与机器相关的。装有不同操作系统的机器上,需要有专门为该操作系统开发的JVM
Ø JVM中类的装载由类加载器和它的子类来实现。ClassLoader是JAVA运行时一个重要的系统组件,负责在运行时查找和装入类文件的类。
Ø 不同平台对应不同的JVM,在执行字节码时,JVM负责将每一条要执行的字节码送给解释器,解释器再将其翻译成特定平台环境的机器指令并执行。使用JVM就是为了支持与操作系统无关,实现跨平台运行。
JVM生命周期
(1)JVM实例的诞生
当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点
(2)JVM实例的运行
main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。
(3)JVM实例的消亡
当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程 序也可以使用Runtime类或者System.exit()来退出。
JVM体系结构
Ø 分为三部分:类装载器子系统、执行引擎、运行时数据区
类装载器
一、JVM两种类装载器包括:启动类装载器和用户自定义类装载器。启动类装 载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是 ClassLoader类的子类。
二、 主要分为以下几类:
(1) Bootstrap ClassLoader 。这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。
(2) Extension ClassLoader 。JVM用此classloader来加载扩展功能的一些jar包
(3) System ClassLoader 。JVM用此classloader来加载启动参数中指定的Classpath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。
(4) User-Defined ClassLoader 。User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录
三、ClassLoader抽象类提供了几个关键的方法:
(1)loadClass此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parent ClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法
(2)findLoadedClass 此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。
(3)findClass 此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。
(4) findSystemClass 此方法负责从System ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如仍然为找到,则返回null。
(5)defineClass 此方法负责将二进制的字节码转换为Class对象
(6) resolveClass 此方法负责完成Class对象的链接,如已链接过,则会直接返回。
执行引擎
一、JVM通过执行引擎来完成字节码的执行,在执行过程中JVM采用的是自己的一套指令系统,每个线程在创建后,都会产生一个程序计数器(pc)和栈(Stack),其中程序计数器中存放了下一条将要执行的指令,Stack中存放Stack Frame,表示的为当前正在执行的方法,每个方法的执行都会产生Stack Frame,Stack Frame中存放了传递给方法的参数、方法内的局部变量以及操作数栈,操作数栈用于存放指令运算的中间结果,指令负责从操作数栈中弹出参与运算的操作数,指令执行完毕后再将计算结果压回到操作数栈,当方法执行完毕后则从Stack中弹出,继续其他方法的执行。
在执行方法时JVM提供了invokestatic、invokevirtual、invokeinterface和invokespecial四种指令来执行
(1)invokestatic:调用类的static方法
(2) invokevirtual: 调用对象实例的方法
(3) invokeinterface:将属性定义为接口来进行调用
(4) invokespecial: JVM对于初始化对象(Java构造器的方法为:<init>)以及调用对象实例中的私有方法时。
二、反射机制是Java的亮点之一,基于反射可动态调用某对象实例中对应的方法、访问查看对象的属性等,而无需在编写代码时就确定需要创建的对象,这使得Java可以实现很灵活的实现对象的调用,代码示例如下:
Class actionClass=Class.forName(外部实现类);
Method method=actionClass.getMethod(“execute”,null);
Object action=actionClass.newInstance();
method.invoke(action,null);
反射的关键:要实现动态的调用,最明显的方法就是动态的生成字节码,加载到JVM中并执行
(1)Class actionClass=Class.forName(外部实现类);
调用本地方法,使用调用者所在的ClassLoader来加载创建出Class对象;
(2)Method method=actionClass.getMethod(“execute”,null);
校验此Class是否为public类型的,以确定类的执行权限,如不是public类型的,则直接抛出 SecurityException;
(1) 调用privateGetDeclaredMethods来获取到此Class中所有的方法,在privateGetDeclaredMethods对此Class中所有的方法的集合做了缓存,在第一次时会调用本地方法去获取; 扫描方法集合列表中是否有相同方法名以及参数类型的方法,如有则复制生成一个新的Method对象返回; 如没有则继续扫描父类、父接口中是否有此方法,如仍然没找到方法则抛出NoSuchMethodException;
(3) Object action=actionClass.newInstance();
一:校验此Class是否为public类型,如权限不足则直接抛出SecurityException;
二:如没有缓存的构造器对象,则调用本地方法获取到构造器,并复制生成一个新的构造器对象,放入缓存,如没有空构造器则抛出InstantiationException;
三:校验构造器对象的权限;
四:执行构造器对象的newInstance方法;构造器对象的newInstance方法判断是否有缓存的ConstructorAccessor对象,如果没有则调用sun.reflect.ReflectionFactory生成新的ConstructorAccessor对象;
五:sun.reflect.ReflectionFactory判断是否需要调用本地代码,可通过sun.reflect.noInflation=true来设置为不调用本地代码,在不调用本地代码的情况下,就转交给MethodAccessorGenerator来处理了;
六:MethodAccessorGenerator中的generate方法根据Java Class格式规范生成字节码,字节码中包括了ConstructorAccessor对象需要的newInstance方法,此newInstance方法对应的指令为invokespecial,所需的参数则从外部压入,生成的Constructor类的名字以:sun/reflect/GeneratedSerializationConstructorAccessor或sun/reflect/GeneratedConstructorAccessor开头,后面跟随一个累计创建的对象的次数;
七:在生成了字节码后将其加载到当前的ClassLoader中,并实例化,完成ConstructorAccessor对象的创建过程,并将此对象放入构造器对象的缓存中;
最后一步:执行获取的constructorAccessor.newInstance,这步和标准的方法调用没有任何区别。
三、执行技术。 主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行
(1)解释属于第一代JVM,
(2)即时编译JIT属于第二代JVM,
(3)自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式
(4)自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。
配置环境变量
PATH与CLASSPATH:
Ø 操作系统通过PATH环境变量来查找并确定基础类库文件rt.jar的位置,通过CLASSPATH来查找并确定其他类库文件的位置
Ø 设置PATH变量是为了让操作系统找到指定的工具程序。设置CLASSPATH变量是为了让Java执行环境找到指定的Java程序对应的class文件以及程序引用到的其他class文件。JDK默认会在当前工作目录和JDK的lib目录下寻找所需class文件,因此若java程序在这两个目录中,不用设置CLASSPATH,但在其他目录下,必须设置CLASSPATH
Ø 安装好JDK后,还需设定好路径信息,以确保计算机能找到javac和java这两个文件
1.打开我的电脑--属性--高级--环境变量
2.新建系统变量JAVA_HOME 和CLASSPATH
变量名:JAVA_HOME(让诸如Tomcat等应用软件找到你Java的安装路径, 因为他们需要用)
变量值:C:\Program Files\Java\jdk1.7.0
变量名:CLASSPATH
变量值:.%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
3. 选择“系统变量”中变量名为“Path”的环境变量,双击该变量,把JDK安装路径中bin目录的绝对路径,添加到Path变。
量的值中,并使用半角的分号和已有的路径进行分隔
变量名:Path
变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
这是java的环境配置,配置完成后直接启动eclipse,它会自动完成java环境的配置
开发Java程序的步骤:
1. 创建java源程序
Java源程序是一个用java语言写成的一个文本文件。一般以.java作为扩展名
2. 编译源程序
Java编译器(javac程序)读取源程序并翻译成java虚拟机能明白的指令集,且以字节码的形式(.class)保存在文件中。编译一个.java文件时,我们会获得一个名字完全相同的输出文件(.class结尾);但对于.java文件中的每个类,它们编译后都有一个.class扩展名
3. 运行字节码文件
Java解释器读取字节码文件,取出指令集并翻译成计算机能执行的代码,完成运行过程
Java语言运行机制
计算机高级编程语言按执行方式分为编译型和解释型语言
编译型语言:指使用专门的编译器,针对特定操作系统将源程序代码一次性翻译成计算机能识别的机器指令。如C、C++
在后续执行时,直接运行第一次编译的结果,减少了编译次数,提高了程序运行效率,但程序第一次编译时与系统平台相对应,因此移植性较差
解释型语言:指使用专门的解释器,将源程序代码逐条解释成特定平台的机器指令,解释一句执行一句,类似“同声翻译”。在程序每次运行时都要将源程序解释成当前系统平台相对应的机器指令,因此每次运行都要解释执行,效率较低,但移植性高
java是编译型和解释型语言的结合体。先采用通用的java编译器将源程序代码编译成与平台无关的字节码文件(即二进制的编码文件),然后由java虚拟机对字节码文件解释成特定平台的机器指令去执行。编译一次,到处运行
字节码文件:Javac.exe把.java源文件编译成.class文件,但.class文件并不是字节码文件。当类加载器把.class文件加载进内存时,会做一些比如安全检查等处理,处理完成后所得到的最终的二进制内容才是字节码文件。JVM的解析器能解析这种字节码
Ø 类加载过程
(1)装载。 把.class文件中的二进制数据加载到JVM的内存中,JVM通过类名、类所在的包名通过ClassLoader(以类名+包名+ClassLoader实例ID标识)来完成类的加载。
(2)连接。 链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。
在完成了校验后,JVM初始化类中的静态变量,并将其值赋为默认值。最后一步为对类中的所有属性、方法进行验证,以确保其需要调用的属性、方法存在,以及应具备的权限(例如public、private等)。可能会造成NoSuchMethodError、NoSuchFieldError等错误信息。
(3)初始化。 初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:调用了new;反射调用了类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类。
Ø 运行
java命令用于运行lava程序,它会启动一个java虚拟机进程(即启动Java虚拟机),该进程首先从classpath中找到相关的类文件,读取这个文件中的二进制数据,把该类的类型信息存放到运行时数据区的方法区中。接着JVM定位到方法区中该类的main()方法并执行下去
运行时数据区
Java程序运行在java虚拟机提供的运行时环境上,虚拟机运行在计算机上
PC寄存器。这是最快的存储区,因为它位于处理器内部。但存储器数量极其有限,所以寄存器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。PC寄存器是用于存储每个线程下一步将执行的JVM指令,若该方法为native的,则PC寄存器中不存储任何信息。
JVM栈。 位于RAM(随机访问存储器)中,可通过栈指针从处理器那里获得直接支持。这是一种快速有效的分配存储方法,仅次于寄存器。当创建程序时,Java系统必须知道存储在栈内所有项的确切生命周期,以便上下移动栈指针,这一约束限制了程序的灵活性。
Ø JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈。栈与栈之间的数据时独立的(不共享)
Ø JVM栈中存放的为当前线程中的局部基本类型变量(存变量名和值)、引用变量(指向堆的地址)、部分的返回结果等
堆:堆不同于栈的好处是:编译器不需要知道存储的数据在堆里存括多久,因此,在堆里分配存储有很大的灵活性。而为这种灵活性付出的代价是:进行存储分配和请理比用栈用的时间更多。它是JVM用来存储对象实例以及其内容的区域, , Java中通过new创建的对象的内存都在此分配。
Ø 堆不同于栈的好处是:编译器不需要知道存储的数据在堆内存活多久。因此,分配存储时有很大灵活性,但用堆进行存储分配和清理比用堆栈需要更多时间
Ø 堆是JVM中所有线程共享的,因此在堆上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销较大,所以对于所创建的线程都会分配一块独立的空间
方法区:方法区存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法等信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同样,方法区域也是全局共享的,在一定的条件下它也会被垃圾回收,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息
运行时常量区:存放类中的固定常量信息、方法和Field的引用信息等,其空间从方法区中分配。
本地方法区(Native Method Stacks)
JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
Java相关概念
API: API是Application Interface的简称。它是指一个软件系统对外提供的所有接口,就是系统对外提供的所有服务。
JAR文件:
Ø JDK的Jar命令能把java应用中相关的class文件及资源文件打包成的一个文件
Ø java命令和javac命令会读取JAR文件中的java类
Ø JDK中的包和类主要在JDK的安装目录下的jre\lib\rt.jar文件中
Ø 只要在命令行中用以下命令就可以将一个包打成一个jar文件
jar –cvf create.jar demo
-c:创建新的存档
-v:生成详细输出到标准输出上
-f:指定存档文件名
create.jar:是生成jar文件的名称
demo:要打成jar文件的包
GC:Garbage Collection。是 JVM中自动的对象内存回收机制
内存处理忘记或错的内存回收都会导致程序或系统的不稳定或崩溃。java为此提供了垃圾自动回收机制
Ø 在程序的运行时环境中,Java虚拟机提供了一个系统级的、低优先级的垃圾回收器线程,它负责自动回收无用对象所占用的内存
Ø java的内存管理其实就是对象的管理,包括对象的分配(new 对象)和释放(对象=null)。GC主要负责内存资源的管理。
Ø 当程序员创建对象时,GC就开始监控这个对象的地址、大小及使用情况,去辨别哪些对象不再被引用,然后释放其内存,以便供其他新的对象使用。
n 为了让GC运行更合理、高效,编程时应尽早释放无用对象的引用(置为null),特别是一些复杂的对象,如数组、集合、队列等。也要尽量少用finalize方法。该方法是java提供给程序员用来释放对象或资源的,但它会加大GC的工作量。也可以手动执行System.gc(),通知GC运行,但并不保证GC一定会执行
特点:
Ø 只有当对象不再被程序中的任何引用变量引用时,它的内存才可能被回收。
Ø 程序无法迫使垃圾回收器立即执行垃圾回收操作。
Ø 当垃圾回收器将要回收无用对象的内存时,先调用该对象的finalize方法,该方法有可能使对象复活,导致垃圾回收器取消回收该对象的内存。
好处:
Ø 把程序员从复杂的内存追踪、监测和释放等工作中解放出来,减轻程序员进行内存管理的负担。
Ø 防止系统内存被非法释放,从而使系统更加健壮和稳定。
Applet程序简介
Ø Applet程序是一个经过编译的Java程序,它既可以在Appletviewer下运行,也可以在支持Java的Web浏览器中运行。
Ø Applet程序可以完成图形显示、声音演奏、接受用户输入、处理输入内容等工作。Applet程序中必须有一个是Applet类的子类
Ø Applet程序中使用的几个基本方法
1. public void init()
init()方法是Applet运行的起点。当启动Applet程序时,系统首先调用此方法,以执行初始化任务
2. public void start()
start()方法是表明Applet程序开始执行的方法。当含有此Applet程序的Web页被再次访问时调用此方法
3. public void stop()
stop()方法使Applet停止执行,当含有该Applet的Web页被其他页代替时也要调用该方法
4. public void destroy()
destroy()方法收回Applet程序的所有资源,即释放已分配给它的所有资源。在Applet程序中,系统总是先调用stop()方法,后调用destroy()方法
5. paint(Graphics g)
paint(Graphics g)方法可以使Applet程序在屏幕上显示某些信息,如文字、色彩、背景或图像等
java基础
Java源文件的组成
Ø 每个Java源文件只能包含零个或一个包声明语句,零个或多个包引入语句,零个或多个类的声明,零个或多个接口声明,注释(空格除外)。每个Java源文件可包含多个类或接口的定义,但最多只有一个类或接口是public的,而且Java源文件必须以其中public类型的类的名字命名。
包:
Ø 包是类的容器,以分层方式保存,用于管理和区分Java类。为了防止不同模块间的名字冲突问题,Java提供了包机制,为每个类提供了唯一标识符。包名全部得小写,一般以因特网域名形式表明包名的惟一性
Ø 包名中一般包含类的创建者或拥有者的信息,类所属的软件项目的信息,类在具体软件项目中所处的位置。
Ø 包有助于实施访问权限控制。当位于不同包之间的类相互访问时,会受到访问权限的约束。
Ø Java用文件系统目录来存储包,将.class文件的位置路径编码到package的名字里。任何你声明的包中的类的.class文件被存储在一个包所指定的目录中。
Ø 包声明语句用于把Java类放到特定的包中。最多包含一个package语句(package 包名)。package语句作为Java源文件的第一句,且必须在第一行(注释除外)
Ø 如果一个类访问了来自另一个包(( java.lang包除外)中的类,那么前者必须通过Import语句把这个类引入。
• 引入包:import java.lang .*; import java.util. Date;
• 在每个程序文件的开头,必须声明import语句,以便引入在文件代码中需要用到的额外类。例外是java.lang包,它默认导入到每个java文件中,所以它的所有类不用import。
• import语句不会导致类的初始化,它并不意味着JAVA虚拟机刽把包中的类加载到内存中并对它们初始化。
注释:在java源代码的任意位置,都可以加入注释语句,编译器会忽视注释语句。java语言提供了3种形式的注释:
JavaDoc:
Ø 若文档与代码分离,那么每次修改代码时,都要修改相应的文档,麻烦又乏味,所以java将代码同文档放在同一文件内。
为使一切都整齐划一,还必须使用一种特殊的注释语法,以便标记出特殊的文档;另外还需要一个工具,用于提取这些注释,并按有价值的形式将其展现出来。这就是javadoc
Ø JavaDoc文档是供Java开发人员阅读的,他们通过JavaDoc文档来了解其他人员开发的类的用法。Java开发人员应该养成经常查阅JavaDac文档的良好习惯。
Ø 文档注释以“/**”开始,并以“*/”结束,里面可以包含普通文本、HTML标记和JavaDoc标记(如@author, @version, @see, @param,@return,@exception,@deprecated等; @see标记允许引用其他类里的文档)。
javadoc能识别注释中用@标识的一些特殊标记。如果我们希望javadoc工具生成更详细的文档信息,则可利用javadoc标记
Ø javadoc命令不仅提取这些javadoc标记指示的信息,还处理类名、接口名、字段、构造方法、方法声明之前的注释。Javadoc就是用于从程序源码中抽取类、方法、成员等文档注释(/**…..*/),形成一个与源码配套的API帮助文档(HTML格式的文件)。注意:Javadoc只能为public和protected成员进行文档注释
Ø javadoc命令可对源文件、包来生成API文档,格式:javadoc选项Java源文件/包
Ø -d <directory>:该选项指定一个路径,用于将生成的API文档放到指定目录下。
Ø -windowtitle <text>:该选项指定一个字符串,用于设置API文档的浏览器窗口标题。
Ø doctitle<html-code>:该选项指定一个HTML格式的文本,用于指定概述页面的标题。
Ø -header <html-code>:该选项指定一个日下ML格式的文本,包含每个页面的页眉。
Ø 此外,javadoc命令还包含了大量其他选项,可以通过在命令行窗口执行j avadoc -help来查看javadoc命令的所有选项。
Ø 异常声明是接口(类中所有能被外部使用者访问的方法构成了类的接口)的一部分,JavaDoc文档中应描述方法可能会抛出某种异常的条件。根据异常声明,方法调用者了解到被调用方法可能抛出的异常,从而采取相应的措施:捕获并处理异常,或声明继续抛出异常。
数据类型
Ø 在计算机中,所有数据都是存放在存储器中。一般把存储器中的一个字节称为一个内存单元
Ø 数据类型确定要存储在内存中的数据的类型
Ø 在内存中只能存放二进制数据,因此各种基本类型的数据在内存中都表示为二进制数据序列。基本类型存储的是实际的数值
Ø 由于计算机的内存能存储二进制数据,因此必须为各个字符进行编码。所谓字符编码,是指用一串二制数据来表示特定的字符。char是字符类型,所以Java语言对char采用Unicode编码。Unicode编码由国际Unicode协会编制,收录了全世界所有语言文字中的字符,是一种跨平台的字符编码。
Ø 当Java编译器把Java源代码编译为字节码时,会用int或byte来表示boolean。在Java虚拟机中,用整数零来表示false,用任意一个非零整数来表示false,这种底层处理方式对Java源程序是不可见的,所以在Java源程序中,不允许把整数或null赋值给Boolean类型的变量,这是有别于其他高级语言(如C语言)的地方。
基本数据类型变量与引用类型变量的区别
Ø 基本类型变量是在栈内存中开辟存储空间存储变量值。而引用类型变量是由存储空间(在堆内存中,负责存放变量值)和引用空间(在栈内存中,负责存放存储空间的首地址)构成
Ø Java允许把任何基本数据类型转换成别的基本数据类型,但布尔型除外,boolean根本不允许进行任何类型的转换处理。
Ø 变量之间赋值时,二者都属于值传递,不同的是简单变量传递的是内容本身,而引用变量传递的是引用地址
引用数据类型
Ø 对于一个引用类型的变量,java编译器按照它声明的类型来处理。例如在
Father father=new Son();
代码中,编译器认为father是Father类型的引用变量,不能调用Son的成员变量和成员方法,否则编译出错。除非强转
Ø 对于一个引用类型的变量,运行时Java虚拟机按照它实际引用的对象处理。
变量与变量作用域
Ø 变量是存储数据的基本单元,用于表示现实系统中的某种数据。当一个现实系统开始运转前,往往需要为一些数据赋予合理的初始值。变量的初始化是指自从变量定义以后,首次给它赋初始值的过程。 Java语言要求变量先定义,再初始化,然后使用。
Ø 在编写程序时,开发人员要为变量确定合理的数据类型(要同时考虑实际需求和程序的性能)和生命周期。总的原则是在保证该变量能正常行使使命的前提下,使它在内存中占用尽可能小的空间和尽可能少的时间。
Ø 局部变量指在方法体内部定义的变量,它的作用域是整个方法,必须先显示初始化才能使用。存储在栈内存中。由于局部变量和成员变量有完全不同的生命周期和作用域,所以局部变量不能被static, private, protected和public等修饰符修饰。
Ø 成员变量的作用域是整个类,会默认初始化(final修饰的成员变量除外,必须显示初始化)。可用public,static等修饰符修饰,存储在堆内存中。成员变量有两种:一种是被static关键字修饰的变量,叫类变量或静态变量:另一种是没有被static关键字修饰的变量,叫实例变量。
Ø 为什么Java虚拟机会对成员变量自动初始化,却要求局部变量必须显式初始化呢?因为局部变量很有可能是由于程序员的疏忽,造成忘记了初始化局部变量,所以Java编译器在编译阶段强制要求程序员给局部变量赋初始值,可避免潜在错误。而对于成员变量,既可在声明时初始化,也可在构造方法中初始化(适用于实例变量),还可在静态代码块中初始化(适用于静态变量),假如成员变量的初始值与该数据类型的默认值不符,程序员总会在构造方法中显式给它初始化。假如程序员没有显示初始化成员变量,Java语言则认为这些变量的初始值刚好和该数据类型的默认值相同,因此提供自动初始化的功能,以简化编程。
作用域
Ø 作用域决定了在其内定义的变量的生命周期(指从一个变量被创建并分配内存空间开始,到这个变量被销毁并清除其所占用内存空间的过程)和存在范围(只有在这个范围内,程序代码才能访问它)。
Ø 当一个变量被定义时,它的作用域就被确定了。按照作用域的不同,变量可分为局部变量和成员变量。
Ø 将局部变量的作用域最小化,可增加代码的可读性和可维护性,并且降低出错的可能性。为了将局部变量的作用域最小化,在需要使用某局部变量时,才定义它。同时使方法小而集中。如果一个方法包含多种操作,尽可能把这个方法分解为多个小方法,每个方法负责一项操作。方法小了,局部变量的作用域自然就小了。
Ø Java对象不具备和基本类型一样的生命周期。当用new创建一个Java对象时,它可以存活于作用域之外,只要你需要,就会一直保留下去。
Ø 在try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量,如果希望能访问到该变量,需将该变量定义在try代码块的外面。
总结:JVM开始运行一个Java程序时,它管辖的那块内存空间就是各种变量登台演出的大舞台。在这个舞台上,局部变量最短命,似昙花一现;实例变量附属于实例,与实例本身共存亡;静态变量寿命最长,只要所属的类没有被卸载,就会长居内存中,直到程序运行结束。内存是宝贵的有限资源,合理有效地利用内存是提高程序运行性能的一个关键因素。每个变量占用多少内存空间,在内存中存在多久,这决定变量命运的大权掌握在开发人员手中,而Java虚拟机只是一个按部就班的执行者。
java关键字、标识符、运算符
关键字:
Ø java语言的关键字是程序代码中的特殊字符,如package, import, public、class、static和void等。
Ø 每个关键字都有特殊的作用,如package用于包的声明,class用于类的声明,void表示方法没有返回值等
Ø 注意:所有的关键字都是小写.且程序中的标识符不能以关键字命名。
标识符
Ø 指程序中包、类、接口、变量或方法的名字
Ø 命名规则:第一个字符是字母、_或$ ,大小写敏感,不能是关键字或保留字,要望名知意
运算符
Ø 程序的基本功能就是处理数据。在最底层,数据是通过使用运算符来处理的。运算符用于操作数据,生成一个新值
Ø 运算符是一个符号,又叫操作符。运算符能与相应类型的数据组成表达式,来完成相应的运算。java虚拟机会根据操作符的优先级来计算表达式
Ø Java利用运算符操纵对象和数据。多数运算符只能操作基本类型的数据,例外的是“=”、“==”和“!=”运算符,它们能操作所有类型。此外,“+”运算符不仅能操作除boolean外的所有基本类型,还能操作String引用类型。
boolean b = false;String s = "hello" + b;System.out.println(s);//结果为hellofalse
Ø 在由操作符与所操纵的数据构成的表达式中,被操纵的数据也称为操作元.例如在表达式“a+b”中,变量a和b是“+”操作符的操作元。在计算表达式时,如果有一个操作元是long型,那么结果也是long型;否则不管操作元是byte, short或char型,运算结果都是int型。
例如byte a=1,b=1;byte c=a+b;//编译出错 正确的做法是int c=a+b;或byte c=(byte)(a+b);
Ø “=”赋值运算符能把右边操作元的值赋给左边操作元,并以右边操作元的值作为运算结果。同种类型的变量之间赋值时,不需要进行类型转换。当在不同类型的变量之间赋值时,或将一个直接数赋给与它不同类型的变量时,需要进行类型转换。类型转换分为自动类型转换和强制类型转换。自动转换是指运行时,Java虚拟机自动把一种类型转换成另一种类型。强制类型转换是指在程序中显式地进行类型转换。boolean类型不能与其他的基本类型进行任何类型转换
Ø 三元操作符:布尔表达式?表达式1:表达式2; 例如:int score=61;String result=score>60?”及格”:”不及格”;
Ø instanceof操作符用于判断一个引用变量所指向的对象是否是一个类或接口的实例。
Ø x++先取得x值,后执行+1操作 x=10;y=x++; x=11 y=10;
Ø ++x先执行+1操作,后取x值 x=10;y=++x; x=11 y=11;
Ø 使用赋值运算符(“=”)时,左值必须是一个明确的已命名的变量,必须有一个物理空间存储等号右边的值
修饰符
引子:灵活正确地运用修饰符,会使软件程序最贴切地模拟真实世界中的系统,并有助于提高软件系统的可重用性、可维护性、可扩展性及系统的运行性能。
类、方法、变量的可用修饰符
Ø 成员方法和成员变量可以有多种修饰符,而局部变量只能用final修饰。
访问控制修饰符
位置 | private | 默认 | protected | public |
同一个类 | 是 | 是 | 是 | 是 |
同一个包内的类 | 否 | 是 | 是 | 是 |
不同包内的子类 | 否 | 否 | 是 | 是 |
不同包并且不是子类 | 否 | 否 | 否 | 是 |
Ø Java的一个源码文件(.java结尾)通常叫作一个编译单元,每个编译单元都必须有一个以.java结尾的名字。而且在编译单元的内部,必须有一个与文件同名的公共(public)类。如果不这样做,编译器就会报错。编译单元剩下的类可在这个包外面的世界前隐藏起来,因为它们并非public,它们用于“支撑”public类。
Ø 顶层类可以被public或默认级别修饰,但不能被protected和private修饰。局部变量不能被访问控制修饰符修饰。
Ø private修饰的方法都默认是final方法,因而不能被子类的方法覆盖。父类private修饰的属性,子类可以继承过来,是可见的,但不可以使用!
final:之所以要禁止改变,可能是考虑到两方面的因素:设计或效率。
Ø 阻止类继承,阻止方法覆盖或重载
Ø 被final修饰的变量表示常量,只能被赋值一次。表示我们不希望它发生变化。如果将引用类型的变量用final修饰,那么该变量只能始终引用一个对象,但可以改变对象的内容
Ø final修饰的成员变量不会默认初始化,必须显示初始化。当final修饰实例变量时,可以在定义变量时或在构造方法中进行初始化;当final修饰静态变量时,只能在定义变量时进行初始化。
Ø final不能修饰构造方法
Ø JVM会对final方法自动优化,其执行效率会比普通方法更高。同时提高程序安全性、可读性和可维护性
abstract:用于修饰类和成员方法
1, abstract不能与static共存,因为抽象类没有方法主体(实现),不能被静态直接调用;
2, abstract不能与final共存,因为若抽象方法不能被重写,就不能被子类实现;
3, abstract不能与private共存,因为根据官方定义,private的属性不能被被继承的,而不能继承,就不能实现该方法
4, 没有抽象构造方法,也没有抽象静态方法
static:
如果需要创建一个脱离于实例的变量或方法(只与整个类有关),那么用static作修饰是再好不过了
Ø 在JVM中,static变量只有一份,通过任何该类的实例或类本身去修改静态变量的值都是在修改同一个值. static变量能被类的所有实例共享, 可作为实例之间进行交流的共享数据。
Ø static修饰的方法和属性,不需要和实例捆绑在一起,这可以提高代码的运行效率。如果类的所有实例都包含同一常量属性,可把这个属性定义为静态常量类型,从而节省内存空间。
Ø 因为静态方法先于任何实例创建之前加载进内存,所以静态方法中不能使用this和super关键字,也不能直接访问所属类的实例变量和实例方法
Ø 静态相关的数据等都存储在JVM内存方法区里的静态区里。在类装载时只分配一块存储空间,所有此类的对象都可以操控这块空间
Ø 当类被加载时,静态代码块只被执行一次。类中不同的静态代码块按它们在类中出现的顺序被依次执行。
控制程序流程
程序是按顺序执行的,控制流语句规定此顺序
⑴ 顺序结构:按书写顺序执行的语句构成的程序段
⑵ 选择结构:根据输入数据或中间结果的不同情况需要选择不同的语句组执行
Ø 条件分支:根据给定的条件进行判断,决定执行哪个分支的程序段.if ````else语句是最常用(else语句并不是必需的)
If(布尔表达式){
程序代码块
}else if{
程序代码块
}else{
程序代码块
}
• 把出现概率最大的条件放在最前面,把出现概率较小的条件放在后面,可以进一步提高程序的运行效率。
• If ````else语句比switch语句功能更强大,它能够灵活地控制各种复杂的流程分支。
Ø 开关分支:根据给定整型表达式的值进行判断,然后决定执行多路分支中的一支。switch语句是多路分支语句
switch (expr){
case 常量1:
程序代码块
break;
case 常量2:
程序代码块
break;
`````````
default:程序代码块
• 在switch (expr)语句中,expr表达式的类型必须是与int类型兼容(就是指能自动转换为int类型)的基本数据类型。因此expr表达式的合法类型包括byte, short, char和int类型。long和浮点类型不能自动转换为int类型,因此不能作为expr表达式的类型。
• case子句后面跟着的必需是不同的常量.
• switch语句中只能有0个或一个default子句
• 如果switch表达式与某个case表达式或default情况匹配,就从这个子句开始执行。遇到break语句,就退出整个switch语句,否则依次执行switch语句中后续的case子句。
• 一般应在每个case子句的末尾提供break语句,以便及时退出整个switch语句。例外是:
switch (expr){
case 常量1:
case 常量2:
case 常量3:
程序代码块
break;
⑶ 循环结构:在给定条件成立时,反复执行某个程序段
Ø 初始化部分:用来设置循环的一些初始条件,比如设置循环控制变量的初始值。
Ø 循环条件:这是一个布尔表达式。每次循环都要判断该表达式到底继续循环还是终止循环。这个布尔表达式中通常会包含 循环控制变量。
Ø 循环体:这是循环操作的主体内容,可以是一条或多条语句。
Ø 迭代部分:通常属于循环体的一部分,用来改变循环控制变量的值,从而改变循环条件表达式的布尔值。
Ø Java有3种循环语句:for语句、while语句和do . . while语句。for和while语句在执行循环体之前测试循环条件,而do . . while语句在执行完循环体之后测试循环条件。这意味着for和while语句可能连一次循环体都未执行,而do . . while循环至少执行一次循环体。
Ø for语句的初始化部分定义的变量的作用域为当前for语句,因此符合“应尽可能地缩小局部变量的作用域”这一规则。也可以根据需要将初始化变量定义在for语句上面
Ø break用于退出本循环或switch语句,continue用于结束本次循环,执行下次循环
Ø 对于循环都应确保提供终止循环的条件,避免死循环(即无限循环)
方法
Ø 方法的基本组成部分包括:名称、参数、返回值和方法体。方法参数决定了一个对象能够接收什么样的消息。
Ø 返回类型描述的是在调用方法之后从方法返回的值,若返回类型是void,return的作用只是用来退出方法。参数列表指定要传给方法什么样的信息(类型和名称)。方法名和参数列表(‘已们合起来被称为“方法签.名”)唯一地标识出某个方法。
Ø 方法只能作为类的一部分来创建。调用方法的行为通常被称为发送消息给对象
Ø java中除了static和final修饰方法外,其他方法都是在运行时绑定(即动态绑定),是指JVM在运行时根据对象的实际类型进行方法的调用
Ø 通常,我们调用一个方法是为了产生返回值,或者用它改变为其调用方法的那个对象的状态
方法定义
Ø 格式 修饰符 返回值类型 方法名(参数列表)throws 异常{
方法主体
}
例如:public int getName(int num1,int num2){
return num1+num2;
}
Ø 方法参数可视为局部变量,它的作用域是整个方法
Ø return:用于结束本方法和为本方法的调用者返回数据。当没有返回值时,返回值类型必须被定义为void;
Ø 参数传递的方式: 基本数据类型按值传递,引用类型按对象的引用传递
main方法
Ø 用来作为程序的入口。用static修饰表明执行main方法无需先创建这个类的实例,这样可以提高效率。args参数是用来存储命令行参数。
Ø 通常建议开发者为自己的每个类都创建一个main()。这样使自己的测试代码能够封装到类内,可方便地为每个类都进行单元测试。而且在完成测试以后,毋需将main()删去;可把它保留下来,用于以后的测试。
方法重载
有时,类的同种功能有多种实现方式,到底采用哪种实现方式,取决于调用者给定的参数。
Ø 方法重载即一个类中, 同方法名、 不同方法参数的现象。不同方法参数包括参数类型、次序、数量至少有一项不同。
Ø 参数签名指参数的类型、个数和顺序。这样JVM在调用重载方法时,就可以通过方法名和参数签名正确区分
Ø 方法重载与返回类型、访问修饰符无关。因为返回类型和修饰符并不足以区分所使用的是哪个方法
方法覆盖
Ø 如果一个子类方法与它父类某个方法有相同返回类型、方法名和参数列表,则称为覆盖(又叫重写)
Ø 方法覆盖时,子类方法必须大于等于父类方法的访问权限。这样才能使动态绑定生效。举例:
Father father=new Son();
father.method();
Java编译器认为以上是合法的代码。但在运行时,根据动态绑定规则,Java虚拟机会调用father变量所引用的子类实例的method()方法,如果子类的method()方法为private类型,java虚拟机就无法访问它。
Ø 方法覆盖时,子类方法抛出的异常必须小于等于父类方法抛出的异常。即子类方法抛出的异常等同于父类方法抛出的异常,或是父类所抛异常的子类。举例:
Father father=new Son();
try{
father.method();
}catch(SonException e){```````}
java编译器认为以上是合法的代码。但在运行时,根据动态绑定规则,Java虚拟机会调用father变量所引用的子类实例的method()方法。假如子类实例的method()方法抛出比父类更大的异常,将导致该异常没有被捕获,使程序异常终止。
Ø 方法覆盖特性与对象相关,所以一个方法用static修饰,将不能实现方法重写。若子类静态方法和父类某个方法有相同返
回类型、方法名和参数列表,也必须不能缩小父类方法的访问权限,不能抛出更多的异常。如果一个方法用static修饰,那么编译器在执行该方法时,由调用该方法的类类型决定。
Ø 父类的非静态方法不能被子类覆盖为静态方法。
Ø 父类的私有方法不能被子类覆盖。
Ø 父类的非抽象方法可以被覆盖为抽象方法
方法覆盖和方法重载的异同
相同点:
Ø 都要求方法同名。
Ø 都可以用于抽象方法和非抽象方法之间。
不同点:
Ø 方法覆盖要求参数签名必须一致,而方法重载要求参数签名必须不一致。
Ø 方法覆盖要求返回类型必须一致,而方法重载对此不做限制
Ø 方法覆盖只能用于子类覆盖父类的方法,方法重载用于同一个类的所有方法(包括从父类继承来的方法)。
Ø 方法覆盖对方法的访问权限和抛出的异常有特殊的要求,而方法重载在这方面没有任何限制
Ø 父类的一个方法只能被子类覆盖一次,而一个方法在所在类中可以被重载多次。
方法调用栈
jvm用方法调用栈来跟踪每个线程中一系列的方法调用过程,该堆栈保存了每个调用方法的本地信息(如方法的局部变量)。每个线程都有一个独立的方法调用栈。对于java应用的主线程,堆栈底部是程序的入口方法main()。当一个新方法被调用时,jvm把描述该方法的栈结构置入栈顶,位于栈项的方法为正在执行的方法。
代码块
1, 静态代码块:随着类的加载而加载的,代码块比成员变量和静态变量都还要先运行
2, 构造函数代码块:比构造函数先运行的
3, 运行的顺序是:静态代码块——》静态变量——》成员变量——》构造代码块——》构造函数
构造方法
Ø 构造方法名必须和类名相同。一个类可以有多个构造方法,但它们的参数列表必须不同。重载构造方法用以表达对象的多种初始化行为
Ø 子类的构造过程中必须调用其基类的构造方法。如果子类的构造方法中没有显示地调用基类的构造方法, 则系统默认调用基类的无参构方法.
如果子类的构造方法中既没有显式调用基类构造方法, 而基类中又没有无参的构造方法, 则编译出错.
Ø 构造方法中没有返回值也没有void,所以是没有return语句的
Ø 构造方法不能被子类继承,所以用finai和abstract修饰没有意义。构造方法用于初始化一个新建对象,所以用static修饰没有意义。多个线程不会同时创建内存地址相同的同一个对象,因此用synchrvniaed修饰没必要。此外,Java语言不支持native修饰的构造方法。
this和super关键字
Ø 子类可以在自已的构造方法中的第一行使用this(可带参数)调用本类的另外构造方法,或调用super(可带参数)调用基类的构造方法.
Ø super和this关键字都可以用来覆盖Java语言的默认作用域,使被屏蔽的方法或变量变为可见。
Ø This()和super()区别?
两个都用于访问构造函数。编译器会强迫我们在子类构建造方法的主体中首先设置对父类建造方法的调用,所以在它之前不能出现任何东西,所以都放在构造函数第一行,所以它们不能同时出现在同一个构造函数中。构造函数第一行默认都会有super(),但当定义了this()时不会出现,但还是会调用父类的构造函数,因为this()调用的构造函数第一行默认还是super(),只是代码中未体现
this
q 在一个实例方法内,当局部变量或参数和实例变量同名时,实例变量被屏蔽,因此采用this.实例变量的方式来指代实例变量
q 在一个实例方法内,访问当前实例的引用
Super
q 当局部变量和父类的成员变量同名时,按照变量的作用域规则,只有局部变量在方法内可见。这时要通过super.变量名指定父类成员变量。
q 当子类中定义了和父类同名的成员变量时,在子类的范围内,父类的成员变量不可见。这时要通过super.变量名指定父类成员变量
q 当子类方法覆盖了父类某个方法时,在子类的范围内,父类的方法不可见。这时要通过super.方法指定父类被覆盖了的方法
构造方法的访问级别
在以下场合之一,可以把类的所有构造方法都声明为private类型
(1) 在这个类中仅仅包含静态方法,没有任何实例方法。其他程序无须创建该类的实例,就能访问它的静态方法。例如j ava.lang.Math类中提供了一系列用于数学运算的公共静态方法,为了禁止外部程序创建Math类的实例,Math类的惟一的构造方法是private类型
(2) 禁止这个类被继承。当一个类的所有构造方法都是private类型时,假如定义了它的子类,那么子类的构造方法无法调用父类的任何构造方法,因此会导致编译错误。
q 如果一个类允许其他程序用new语句构造它的实例,但不允许拥有子类,那么就把类声明为fnal类型。由于大多数类都允许其他程序用new语句构造它的实例,因此用final修饰符来禁止类被继承的做法更常见。
q 如果一个类既不允许其他程序用new语句构造它的实例,又不允许拥有子类,那么就把类的所有构造方法声明为private类型。
(3) 这个类需要把构造自身实例的细节封装起来,不允许其他程序通过new语句创建这个类的实例,并且控制自身实例的数目。于是这个类向其他程序提供了获得自身实例的静态方法,称为静态工厂方法
q 静态工厂方法命名时应尽量遵守约定俗成的规范,例如valueOf或getInstance
valueOf方法返回的实例与它的参数具有同样的值,能执行类型转换操作
如:Integer a=Integer.value0f(100);//返回值为100的Integer对象
getInstatnce方法返回的实例与参数匹配,如:
Calendar cal=Calendar.getinstance(Locale.CHINA);//返回符合中国标准的日历
q 每次执行new语句时,都会创建一个新对象。而静态工厂方法每次被调用时,是否会创建一个新的对象完全取决于方法的实现。
q new语句只能创建当前类的实例,而静态工厂方法可以返回当前类的子类的实例,这一特性可以在创建松耦合的系统接口时发挥作用
q 静态工厂方法最主要的特点是:每次被调用的时候,不一定要创建一个新的对象。利用这一特点,静态工厂方法可用来创建以下类的实例。
• 单例类:只有惟一的实例的类。
• 枚举类:实例的数量有限的类。
• 具有实例缓存的类:能把已经创建的实例暂且存放在缓存中的类。
• 具有实例缓存的不可变类:不可变类的实例一旦创建,其属性值就不会被改变。
类与接口
面向对象
引子:成功的OOP语言,它们不仅集成了这种语言的语法以及一个编译程序(编译器),而且还有一个成功的开发环境,其中包含设计优良、易于使用的库。所以,大多数程序员的首要任务就是用现有的对象解决自己的应用问题。
1. 万物皆对象.
Ø 将对象视为奇特的变量,它可以存储数据,还可以提供特有的服务。是一个包含着数据和与这些数据有关操作的集合。对象具有状态(指某一瞬间其所有属性的取值)、行为和标识。每个对象都可以拥有内部数据(对象的属性)和方法(对象执行的操作)
Ø 并且每个对象都可以唯一地与其他对象区分开来(每个对象都是唯一的。这来自于真实世界中事物的惟一性。Java保证对象唯一性的手段是为它在内存中分配唯一的地址)。
2. 对象是程序所处理数据的最主要的载体,数据以实例变量的形式存放在对象中
3. 每个对象都拥有其类型
对对象信息进行封装就是定义类,类是一种全新的类型。类与类之间的最重要区别就是“可以发送什么样的消息给它”。
4. 每个对象的组成都可以有由其他对象所构成的存储。如组合,这样就将其他对象的复杂性隐藏在这个对象的简单性背后
5. 对象就是服务提供者
Ø 每个对象都有一些特定功能,相对于其他对象而言,它的功能就是为其他对象提供服务。服务由对象的方法实现
Ø 程序本身将向用户提供服务,它通过调用某些对象提供的相应服务(方法)结合在一起实现这一目的
6. 程序就是对象的集合, 通过消息传递,各对象知道自己该做些什么
你可以抽取待求解问题的组成元素,将它表示为程序中的对象。
要想请求一个对象,就必须向该对象发送一条消息(指一个对象为执行某项特定操作而向另一个对象发送的请求)
7. 同一类型的所有对象都可以接受同样的消息。即多态
备注:
1. 从问题领域的事物到对象。分析问题领域中的实体,把它抽象为对象。真实世界中的事物往往有很多属性,应根据事物所处问题领域来抽取出具有特定属性的对象。同时还要分析事物所具有的功能,在对象中定义这些功能的名称,但不必考虑如何实现它们。这种抽象过程使得设计阶段创建的对象模型仅仅用来描述系统应该做什么,而不必关心如何去做,从而清晰地划清软件设计与软件编码的界限。
2. 从对象到类的抽象.在建立对象模型时,把具有相同属性和功能的对象抽象为类。
3. 从子类到父类的抽象。当一些类之间具有相同的属性和功能时,把这部分属性和功能抽象到一个父类中。
封装
封装就是把类内部的逻辑和数据隐藏起来,将与对象相关的状态信息和行为捆绑为一个逻辑单元。
Ø 封装隐藏了对象的属性和实现细节,仅对外公开接口,使用者只需通过特定方法访问该类的对象,而不用关心其内部细节
Ø 提高软件的可重用性,每个系统都是一个相对独立的整体,可以在多种环境中得到重用。例如干电池就是一个可重用的独立系统,在相机、手电筒、电动剃须刀中都能使用。
Ø 便于使用者正确、方便地理解和使用系统,防止使用者错误修改系统的属性。如电视机系统,尽管它本身的实现很复杂,但用户使用起来却非常简单,只要通过遥控器上的几个按钮就能享受电视机提供的服务。电视机的实现细节被藏在它的大壳子里,没必要向用户公开。
Ø 降低了构建大型系统的风险,即使整个系统不成功,个别的独立子系统依然是有价值的。如相机损坏了,它的干电池依然有用,可以安装到手电筒中。
Ø 一个设计良好的系统会封装所有的实现细节,把它的接口与实现清晰地隔离开来,系统之间只通过接口进行通信。面向对象的编程语言主要通过访问控制机制来进行封装。
Ø 封装的两大原则:
1. 把尽可能多的东西隐藏起来,对外提供简捷的接口。系统的封装程度越高,那么它的相对独立性就越高,而且使用起来也更方便。
2. 把所有属性隐藏起来。
如果某种属性允许外界访问,那么只提供访问该属性的公开方法,让外界间接访问。如电视机有个音量属性volume,允许使用者通过setVolume()和getVolume()方法来访问这个属性。为什么这样做呢?
a) 更符合真实世界中外因通过内因起作用的客观规律。一个对象的属性发生变化应该是外因和内因共同作用的结果。外因就是使用者向电视机对象发送消息,请求电视机对象把音量调节到某个高度,即调用电视机对象的setVolume()方法;内因就是电视机对象收到外因信息,调用自身的音量控制装置去调节音量,即执行setValume()方法。
b) 能够灵活控制属性的读和修改的访问级别。对象的有些属性只允许使用者读,但不允许使用者修改,而只有对象内部才能修改。例如电表上显示的用电数就是这样的属性,此时可以公开读方法,隐藏写方法。
c) 防止使用者错误地使用属性。例如银行账户Account对象有一个属性password,当用户设置密码时,要求密码必须是6位数,在Account对象的setPassword()方法中就能强制用户按此要求设置密码。
d) 有助于对象封装实现细节。如果对象的一个属性发生改变,在它的内部就会发生一系列的连锁反应,但这些反应对使用者是不可见的。
继承
指一个类继承另一个类,就重用(继承)了另一个类的属性和方法。在子类中可以新增属性和方法,并且可以重写父类中的方法。除C++,其他面向对象语言都是单继承。为了简化系统结构和动态绑定机制,java语言禁止多继承;
Ø 单根继承结构保证所有对象都具备某些功能。因此系统中你可以在每个对象上执行某些基本操作。所有对象都可以很容易地在堆上创建,而参数传递(多态)也得到了极大的简化。单根继承结构也使垃圾回收器的实现变得容易很多,因为所有对象都共一个父类,因此不会因无法确定对象的类型而陷人僵局。
Ø 继承与扩展同时提高了系统的可重用性和可扩展性。例如,计算机之所以能迅猛地更新换代,具备越来越多的功能,就是因为当厂商在生产新计算机时,不必从头生产,而是在原有计算机的基础上进行升级。
• 继承与扩展也促进了架构类软件系统的发展。从头构建一个复杂软件系统的工作量巨大,为了提高开发效率,一些组织开发了一些通用的软件架构,所以新的软件系统就不必从头开发,只需要在这些通用软件架构的基础上进行扩展即可。
• 如何在这些通用软件架构的基础上进行扩展呢?这些通用软件架构中都提供了一些扩展点,就是专门让用户继承和扩展的类。这些类己经具备了一些功能,并且能和软件架构中其他的类紧密协作。用户只需创建这些类的子类,然后在子类中增加新功能或重新实现某些功能。用户自定义的子类能够和谐地融入软件架构中,顺利地与软件架构中的其他类协作(通过接口和多态)。
• 在开发JavaWeb应用时,不需要从头创建Servlet容器,只需选择第三方提供的Servlet容器,比如Tomcat,它是一个开放源代码的Servlet容器。开发人员的主要任务是扩展javax.servlet.http.HttpServlet接口,创建能提供特定服务的Servlet子类.把开发人员自定义的Servlet类发布到Servlet容器中,Serviet容器就能与这些Servlet协作,从而能根据Web客户的请求,调用相关Servlet对象的方法来响应请求. 由此可见,Servlet是J2EE架构在JavaWeb层的扩展点,通过这个扩展点,开发人员能方便地在JavaWeb层添加与特定问题领域相关的服务。
Ø 继承最大的弱点就是打破了封装。
• 每个类都应该封装它的属性及实现细节,这样,当这个类的实现细节发生变化时,不会对其他依赖它的类造成影响。而在继承关系中,当父类的实现发生变化时,子类的实现也不得不随之变化,这削弱了子类的独立性。子类能够访问父类的实现细节,使子类与父类之间紧密耦合,从而影响子类的可维护性。
• 在建立对象模型时,应先充分考虑软件系统中哪些地方需要扩展,为这些扩展点提供一些专门用于被继承的类。对这种专门用于被继承的类必须精心设计,下面给出一些建议
(1)对这些类必须提供良好的文档说明,使得创建该类的子类的开发人员知道如何正确安全地扩展它。
(2)尽可能地封装父类的实现细节,也就是把代表实现细节的属性和方法定义为private。如果某些实现细节必须被子类访问,可以在父类中把包含这种实现细节的方法定义为protected类型。
(3)把不允许子类覆盖的方法定义为final。
(4)父类的构造方法不允许调用自身可被子类覆盖的方法
(5)对于不是专门为了继承而设计的类,禁止其被继承。可采取以下两种措施:
. 把类声明为final类型和把这个类的所有构造方法声明为private,然后通过静态方法来构造自身实例。
Ø 当父类和子类位于同一包中: 子类继承父类中public, protected和默认访问级别的成员变量和成员方法。位于不同包时: 子类继承父类中public和protected访问级别的成员变量和成员方法。
继承树(不考虑顶层的abject类)的层次应该尽量保持在两到三层。
• 如果继承树的层次很多,会导致以下弊端:
(1)对象模型的结构太复杂,难以理解,增加了设计和开发的难度。
(2)影响系统的可扩展性。继承树的层次越多,当需要在继承树上新增一个继承分支时,需要创建的类就越多。
• 继承树上层的类具有以下作用:
1) 定义了下层子类都拥有的相同属性和方法,并且尽可能地为多数方法提供默认的实现,从而提高程序代码的可重用性。位于继承树最上层的父类描述系统对外提供哪些服务。如果某种服务的实现方式适用于所有子类或者大多数子类,那么在父类中就实现这种服务。如果某种服务的实现方式取决于各个子类的特定属性和实现细节,那么在父类中无法实现这种服务,只能把代表这种服务的方法定义为抽象方法,并且把父类定义为抽象类。
2) 代表系统的接口,描述系统所能提供的服务。
• 继承树的上层称为抽象层。在进行对象模型设计时,应该充分预计系统现在必须具备的功能,以及将来需要新增的功能,然后在抽象层中声明它们。抽象层应该比较稳定,这可以提高与其他系统的松藕合及系统本身的可维护性。
组合:组合是一种用多个简单子系统来组装出复杂系统的有效手段。
• 组合不会破坏封装,而继承会暴露部分实现细节。组合还会使系统具有较好的松耦合性,因此使得系统更加容易维护。优先使用组合
• 组合的缺点是比继承要创建更多的对象。当组合创建整体类的实例时,必须创建其所有局部类的实例;而对于继承,创建子类的实例时,无须创建父类的实例。
1. 在软件分析和设计阶段,能简化复杂系统建立对象模型的过程。在建立对象模型时,将复杂系统进行分解。
2. 在软件编程阶段,能简化创建复杂系统的过程,只需分别创建独立的子系统,然后将它们组合起来,就构成了一个复杂系统。而且允许第三方参与系统的建设,提高了构建复杂系统的效率。
3. 向使用者隐藏系统的复杂性。
4. 提高程序代码的可重用性,一个独立的子系统可以被组合到多个复杂系统中。
多态
指当系统A访问系统H的服务时,系统B可以通过多种实现方式来提供服务。通过一种方法的调用,而实质是对不同的类的对象执行不同的方法。如电动剃须刀需要通电,它既允许使用干电池,也允许插上电源。干电池和电源都具有供电的功能,不妨抽象出父类—电源类
Ø 多态的3个必要条件: (1)必须有继承关系 (2)必须有方法覆盖 (3)父类引用指向子类对象 Person p = new Student();
Ø 一个优秀的OOP程序中,大多数或所有方法都只与基础类接口通信。这样的程序具有“扩展性”,因为可以从通用的基础类衍生新的类类型,从而新添一些功能
Ø 一个类型为A的变量可以指向类型为A或A的任何子类的对象。一个接口类型的变量也可以指向任何该接口实现类的对象。在方法调用中,以多态形式传递参数,可以增强参数类型的灵活性。
Ø 后期绑定:当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查,但并不知道将被执行的确切代码。它意味着绑定在运行期间进行,以对象的类型为基础。
• 抽象机制和动态绑定机制能共同提高系统之间的松耦合性。剃须刀访问的始终是电源,而没有涉及到电源的具体子类,也就是说,剃须刀访问的是电源的接口,而具体的供电细节对剃须刀是不可见的。
Ø 应尽可能的使用多态,以提高二个系统之间的松耦合
抽象类与接口
抽象类
引子:抽象是指从特定角度出发,从已经存在的一些事物中抽取我们所关注的特性,形成一个新的事物的思维过程。抽象是一种由具体到抽象、由复杂到简洁的思维方式
1. 设计程序时,我们经常希望基础类只为自己的衍生类提供一个接口,或不想基础类被创建对象,于是就有了抽象类
2. 抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。它能为不同的子类型作出不同的表示,作用通常仅仅是表达接口,而不是表达一些具体的实施细节
3. 抽象父类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品己经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须更进一步的完善。抽象类就像介于“抽象”和“实现”之间的半成品,抽象类力所能及地完成了部分实现,但还有一些功能有待于它的子类去实现。
抽象类细节
1, 抽象方法仅仅描述该类具有的功能,但没有被实现。通过创建抽象方法,我们可以不必再为那个方法提供可能毫无意义的主体代码。
2, 抽象类中也可以不定义抽象方法,就是为了不让外界创建对象。
接口
引子:在现实世界中,接口是实体,如电源插口。而在面向对象范畴中,接口是一个抽象的概念,是指系统对外提供的所有服务。系统的接口描述系统能够提供哪些服务,但不包含服务的实现细节。这里的系统既可以指整个软件系统,也可以指一个子系统。对象是最小的子系统,每个对象都是服务提供者,因此每个对象都有接口。对象中所有向使用者公开的方法声明构成了对象的接口。使用者在获得服务时,不关心对象到底是如何实现服务的
Ø 接口将抽象类的概念更延伸了一步,所有方法都是抽象的,可将其想象为一个“纯”抽象类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实施细节。
Ø 接口作为系统与外界交互的窗口,体现的是一种规范。接口仅仅描述系统能做什么,但不指明如何去做。接口只提供一种形式,并不提供实施的细节,所以接口中的方法都是抽象的。接口这样描述自己:“对于实现我的所有类,看起来都应该象我现在这个样子”。对于接口的实现者而言,接口规定了实现者向外提供哪些服务(以方法的形式提供);对于接口调用者而言,接口规定了调用者可以调用哪些服务。
Ø 在Java中,接口有两种意思:
1. 一指系统对外提供的所有服务。类中所有能被外部使用者访问的方法构成了类的接口。
2. 二指用interface关键字定义的实实在在的接口,也称为接口类型,它声明了系统对外所能提供的所有服务,但不包含具体的实现。它能很清晰地把系统的实现细节与接口分离。
用法:
Ø 接口的字段都是以 public static final 固定写法,必须被显示初始化,成员方法都是以 public abstract 固定写法。接口只包含这样的字段和成员方法,没有构造方法,因为接口不涉及和任何具体实例相关的实现细节。字段并不是接口的一部分,而是保存在接口的static存储区域中
Ø 一个接口不能实现其他接口,但能继承多个其他接口
Ø 当类实现了某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须被定义为抽象类。
Ø 软件系统之间使用接口进行交互.作为本系统与外界交互的窗口。站在外界使用者(另一个系统)的角度,接口向使用者表明系统能提供哪些服务;站在系统本身的角度,接口指定系统自身必须实现哪些服务。通过接口进行交互,可以提高系统之间的松耦合
Ø 由于外界使用者依赖系统的接口,并且系统内部会实现接口,因此一旦接口被公布,就必须非常稳定,否则会对外界使用者和系统内部造成影响(在接口中添加抽象方法,会影响到所有的实现类)。
Ø
作用:
Ø 接口有助于建立各个系统之间的松耦合关系,提高系统的独立性。当某个系统的实现发生变化时,只要它的接口不变,就不会影响到其他的系统。如电视机向遥控器公开了红外线接收器接口,使得电视机和遥控器之间相互独立,当电视机的内部实现发生变化时(比如电子显示器改为液晶显示器),只要它的红外线接收器接口不变,就不会影响遥控器的实现。
Ø 接口还提高了系统的可扩展性。如台式计算机的主板上预留了许多供扩展的插槽(接口),只要在主板上插上声卡,计算机就会增加播放声音的功能;插上网卡,计算机就会增加联网的功能。
Ø 当一个程序中使用接口时,接口是多个模块间的藕合标准;当多个程序之间使用接口时,接口是多个程序之间的通信标准。接口类似于整个系统的“总纲”,它制定了系统各模块应该遵循的标准。程序设计时,尽量使用接口,只有在必须使用成员变量或方法定义时,才应考虑采用抽象类。
抽象类与接口
相同点:
Ø 接口和抽象类都位于继承树的顶端,用于被其他类实现和继承。
Ø 接口和抽象类都不能被实例化
Ø 接口和抽象类都能包含抽象方法。
不同点:
Ø 接口里只能包含public static 修饰的方法,而抽象类可以包含其他方法。
Ø 接口里只能定义public static fianl属性,且必须初始化。抽象类无此要求,也无需必须初始化。
Ø 接口不包含构造器,抽象类包含。抽象类里的构造器并不是用于创建对象,而是用于给子类对象初始化
Ø 接口里不能包含初始化块,但抽象类可以。
Ø 抽象类中可以为部分方法提供默认实现,从而避免在子类中重复实现它们,提高代码的可重用性,这是抽象类的优势所在:而接口中只能包含抽象方法。
Ø 一个类最多只有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,一个接口也可以继承多接口,以此弥补Java单继承的不足,这是接口的优势所在(一个类即使实现多个接口,jvm也不会把方法与接口绑定,而只会与方法的实现者绑定,所以不会增加jvm进行动态绑定的复杂度)
Ø 对于已经存在的继承树,可以方便地从类中抽象出新接口,但是从类中抽象出新的抽象类却不那么容易,因此接口更利于软件系统的维护与重构。
Ø 接口并不强迫它的实现类在是同种类型。但抽象类的实现类必须是它的子类,即同种类型
Ø 对于两个不同的系统,通过接口交互比通过抽象类交互更能获得良好的松耦合
Ø 抽象类和接口所反映出的设计理念不同。抽象类表示的是"is-a"关系,接口表示的是"like-a"关系。(组合是"has a"关系 )
类
类的由来
Ø 如果一切都是对象,那么该用什么决定某一类对象的外观与行为呢?于是类就出现了
Ø 对象不仅将计算机中的事物与现实世界中的事物联系的更加紧密,而且解决了在过程语言中由全局变量造成的麻烦。然而对于一次编程技术的革命来说,一个全新的对象的概念并不足够。因为在编程中有可能希望得到同属某一类型的好几个对象。例如,为一个企业编写一个职员的程序,在程序中必然会出现许多职员对象。如果为每一个职员都指定一段程序的话会很麻烦。
类的概要
Ø 类是针对一个或多个对象的说明(或蓝图),描述了具有相同特性(数据元素)和行为(功能)的对象集合。所以一个类实际上就是一个数据类型,例如所有浮点型数字具有相同的特性和行为集合。
Ø 一旦定义了一个类,就可以在类中设置两种元素:字段(成员变量)和方法。字段可以是任何类型的对象,也可以是基本类型。一旦类被建立,就可以按需要创建类的任意个对象,然后去操作它们
Ø 类的接口确定了它所能提供给外部的服务,即可访问的方法(有时还有字段)。当向对象发送请求时,与之相关的方法就会被调用。就是向某个对象‘发送消息”(产生请求),这个对象便知道此消息的目的,然后执行对应的程序代码
Ø java提供了大量的现成类型,更重要的是,你可以自行创建类型
Ø 类分为单类(不可继承)和可继承类
Jvm和程序的生命周期
Ø 当通过java命令运行一个Java程序时,就启动了一个jvm进程.jvm进程从启动到终止的过程,称为jvm的生命周期。在以下情况,jvm将结束生命周期。如程序正常执行结束,程序在执行中因为出现异常或错误而异常终止,执行了System.exit()方法,由于操作系统出现错误而导致Java虚拟机进程终止。
Ø jvm处于生命周期的总任务就是运行Java程序。java程序和jvm的生命周期是一致的
类之间的关系
关联
Ø 指的是类之间的特定对应关系,在UML中用带实线的箭头表示
Ø 按照类之间的数量对比,关联可分为:一对一关联,一对多关联,多对多关联。关联还可以分为单向关联和双向关联
Ø 对于两个相对独立的系统,当一个系统的实例与另一个系统的一些特定实例存在固定的对应关系时,这两个系统之间为关联关系。例如自行车和主人,每辆自行车属于特定的主人,每个主人有特定的自行车。而充电电池和充电器之间就不存在固定的对应关系,自行车和打气筒之间也不存在固定的对应关系
依赖
Ø 指的是类之间的调用关系,在UML中用带虚线的箭头表示
Ø 如果类A访问类B的属性或方法,或类A负责实例化类B,那么就说类A依赖类B
Ø 对于两个相对独立的系统,当一个系统负责构造另一个系统的实例,或依赖另一个系统的服务时,这两个系统之间主要体现为依赖关系,例如充电电池和充电器,充电电池通过充电器来充电。再例如自行车和打气筒,通常不会为某一辆自行车配备专门的打气筒,而是在需要充气时,从附近某个修车棚里借个打气筒打气
聚集
Ø 指的是整体与部分之间的关系,在UML中用带实线的菱形箭头表示
Ø 被聚集的子系统允许被拆卸和替换,这是普通聚集关系。例如台灯和灯泡
Ø 被聚集的子系统不允许被拆卸和替换,这种聚集关系称为强聚集关系,或叫组成关系。例如台灯和它的电源线路
Ø 台灯和灯泡之问是普通聚集关系,因此在台灯类中提供了setLight(Light light)方法,通过此方法来更换台灯的灯泡。台灯和电源线路之间是强聚集关系,因此在台灯类中没有提供setCircuit(Circuit circuit]方法
Ø 当系统A被加入到系统B中,成为系统B的组成部分时,系统B和系统A之间为聚集关系。例如自行车和它的轮胎就是聚集关系,因为响铃是自行车的组成部分。而人和自行车不是聚集关系,因为人不是由自行车组成的
Ø 对于具有关联关系的两个对象,通常两者有独立的生命周期。比如自行车和它的主人,当自行车不存在时,它的主人依然存在;对于具有聚集关系的两个对象,整体对象会制约它的组成对象的生命周期。部分类的对象不能单独存在,它的生命周期依赖于整体类的对象的生命周期,当整体消失时,部分也就随之消失
泛化
Ø 指的是类之间的继承关系,在UML中用带实线的三角形箭头表示
实现
Ø 指的是类与接口之间的关系,在UML中用带虚线的三角形箭头表示
类的生命周期
Ø 类的生命周期从类被加载、连接和初始化开始,到类被卸载结束。当类处于生命周期中,它的二进制数据位于方法区,在堆区内还会创建一个描述这个类的Class对象。只有当类处于生命周期中时,,java程序才能使用它,比如调用类的静态属性和方法,或创建类的实例。
Ø 当Java程序需要使用某个类时,jvm会确保这个类已经被加载、连接和初始化。其中连接过程又包括验证、准备和解析
类的加载
Ø 类的加载是指查找并把类的.class文件中的二进制数据加载到运行时数据区的方法区,然后在堆区创建一个java.Iang.Class对象。类的加载的最终产品是位于运行时数据区堆区的Class对象。Class对象封装了类在方法区的数据结构,并且向Java程序提供了访问类在方法区内的数据结构的接口
Ø 类的加载是由类加载器完成。类加载器分为两种:
– jvm自带的加载器,包括启动类加载器、扩展类加载器和系统类加载器。
– 用户自定义的类加载器,是java.lang.ClassLoader类的子类的实例,用户可以通过它来定制类的加载方式。
Ø jvm规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载过程中遇到.class文件缺失或存在错误,类加载器必须等到程序首次主动使用该类时才报告错误(抛出一个LinkageError错误)。如果这个类一直没有被程序主动使用,那么类加载器将不会报告错误
连接阶段
连接就是把已经读入到内存的类二进制数据合并到jvm的运行时环境中去。包括验证、准备和解析类的二进制数据。
一、 类的验证就是确保被加载类的正确性。如果jvm检查到错误,那么就会抛出相应的Error对象。类的验证确保.class文件是由正常的java编译器生成,而不是由黑客特制的(黑客试图通过它来破坏虚拟机的运行时环境)。
Ø 类的验证包括:
类文件的结构检查:确保类文件遵从lava类文件的固定格式。
语义检查:确保类本身符合Java语言的语法规定
字节码验证:确保字节码流(Java静态方法和实例方法)可以被jvm安全地执行。
二进制兼容验证:确保相互引用的类之间协调一致。
二、 类的准备
在准备阶段,jvm为类的静态变量分配内存,并初始化为默认值。
三、 类的解析
在解析阶段,jvm会把类的二进制数据中的符号引用替换为直接引用
类的初始化
Ø 给类的静态变量赋予初始值
Ø 类何时初始化呢?
jvm只有在程序首次主动使用一个类或接口时才会初始化它。下面的活动被看做是程序对类或接口的主动使用。
q 创建类的实例。
q 调用类的静态方法。如调用C1ass.forName("Worker"),假如Worker类还没有被初始化,那么forName()方法就会初始化Worker类,然后返回代表这个Worker类的Class实例。farName()是java.lang.Class类的静态方法。又如作为jvm启动入口的启动类,带有静态main()方法。
q 访问某个类或接口的静态变量,或者对该静态变量赋值
q 初始化一个类的子类。例如对Sub类的初始化,可看做是对它父类Base类的主动使用,因此会先初始化Base类。
对象
new
Ø 类的声明并没有创建这个类的任何对象,必须使用关键字new才能真正创建对象。
Ø 用new语句创建类的对象时,Java虚拟机会从最上层的父类开始,依次执行各个父类及当前类的构造方法,从而保证来自于对象本身及从父类中继承的实例变量都被正确地初始化。
Ø new的作用
1. 为对象分配内存空间,将对象的实例变量自动初始化为其变量类型的默认值。
2. 如果实例变量在声明时被显式初始化,那就把初始化值赋给实例变量。
3. 调用构造方法。
4. 返回对象的引用。在创建对象的同时,一般需要将一个引用存储到一个具有合适类型的变量中。
对象的初始化:若类的某个成员是基本数据类型,即使没有进行初始化,Java也会确保它获得一个默认值,以确保那些是基本类型的成员变量得到初始化,防止产生程序错误。但是,这些初始值对你的程序来说,可能是不正确的,甚至是不合法的。所以最好明确地对变量进行初始化。
引用就是对象的存储地址。操作对象的标识符就是一个引用。
Ø 其实,java只是摆脱了显式表露的指针,指针依旧以存储地址的形式埋藏在程序深处。new在java中返回一个引用,而不是像C++中返回一个指针。取消指针意味着一个更安全的系统,程序员不可能找到引用变量的真实地址,也就不可能意外地破坏它。
Ø 一旦创建了一个引用,就希望它能与一个对象相关联。通常用new操作符来实现。此外,你拥有一个引用,并不一定需要有一个对象与它关联,比如String s;一旦Iava看到nul l ,就知道这个引用还没有指向某个对象,因此在使用任何引用前,必须为其指定一个对象;否则会在运行时报空指针异常。
访问对象方法:声明一个类并创建了几个对象后,程序就有可能需要让这些对象相互作用。这是如何做的呢?需要程序的其他部分通过调用方法与这些对象相互作用。为了向对象发送消息,需要声明对象的名称,并以圆点符号连接一个消息请求。
对象的生命周期
引子:在jvm管辖的运行时数据区,最活跃的就是位于堆区的对象。在jvm的生命周期中,一个个对象被陆续创建,又一个个被销毁。在对象生命周期的开始阶段,需要为对象分配内存,并且初始化它的实例变量,当程序不再使用某个对象时,它就会结束生命周期,然后内存可以被jvm的垃圾回收器回收。
创建对象
Ø 创建一个对象就是指构造一个类的实例,前提条件是这个类已经被初始化
Ø 4种显式创建对象的方式:
q 用new语句创建对象,这是最常用的创建对象的方式。
q 运用反射手段,调用java.lang.Class或java.lang.reflect.Constructor类的newInstance()实例方法。
q 调用对象的clone()方法。
q 运用反序列化手段,调用jva.io.ObjectInputStream对象的readObject()方法
4种隐式创建对象的方式:
q 对于java命令中的每个命令行参数,jvm都会创建相应的String对象,并把它们组织到一个String数组中,再把该数 组作为参数传给程序入口main(String args[])方法
q 把String类型的直接数赋值给一个String引用,例如:String s="Hello"
q 字符串操作符“+”的运算结果为一个新的String对象
q 当jvm加载一个类时,会隐含地创建描述这个类的Class实例
Ø 不管采取哪种方式创建对象,jvm创建一个对象都包含以下步骤。
(1)给对象分配内存。
(2)将对象的实例变量自动初始化为其变量类型的默认值。
(3)初始化对象,给实例变量赋予正确的初始值。
Ø 构造方法负责对象的初始化工作,为实例变量赋予合适的初始值
创建一个对象在内存中做的事情
Person p = new Person();
1,java解释器找到硬盘上指定位置的Person.class文件并加载进内存。
2,在堆内存中开辟一个实体空间并分配一个内存首地址值。
3, 在该实体空间中进行属性的空间分配,并进行默认初始化。
4,对空间中的属性进行显示初始化(即字段定义处的初始化)。(属性的初始化由java虚拟机完成,不是由构造方法完成的!)
5, 调用该实体对应的构造方法,进行构造方法初始化。
6, 将首地址赋值给p。p变量就引用了该实体。(指向了该对象)
对象的序列化
• 能够输入输出对象的流称为对象流。所谓对象流也就是将对象的内容进行流化。对象序列化一种用来处理对象流的机制,是指将对象转换成二进制数据流的一种实现手段,通过将对象序列化,可以方便的实现对象的传输及保存。
• 对象序列化和反序列化的过程就是将对象写入字节流和从字节流中读取对象的过程。
• Java对象的序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象
• Java对象序列化时参与序列化的内容包含类名、对象属性(基本数据类型、数组以及其他对象的引用)等。不能被序列化的内容有对象的方法、transient修饰和static修饰(因为静态数据不在堆内存中,是存储在静态方法区中)的属性。
操作对象的流
ObjectOutputStream;可以将实现了Serializable的接口的对象转成字节写出到流中 writeObject(对象);
ObjectInputStream:可以从流中读取一个ObjectOutputStream流写出的对象 readObject(对象);
对象序列化目的:将一个具体的对象进行持久化,写入到硬盘上。或把对象通过网络传递到别的地方
Ø 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(无论是从磁盘中,还是通过网络获取),都可以将这种二进制流恢复成原来的Java对象。
Ø 所有可能在网络上传输的对象的类都应该是可序列化的,否则程序将出现异常。
Ø 进程间传递对象也要序列化
Serializable: 让某个对象可以支持序列化机制,用于启动对象的序列化功能。实现该接口是该对象可被序列化和反序列化的标识
功能: 1. 强制让指定对象具有序列化功能。
2 只要实现接口就可,不用实现方法,这个接口只是个标记
3 对象序列化以后都有一个uid,根据类成员不同,获取的值也不是。编译器不同,uid号也不同。Uid不同,反序列化就会失败,会出现java.io.InvalidClassException。所以强烈推荐手动指定serialVersionUID id号, 前后序列化的uid保持一致
4. 反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将引发C1assNotFoundException异常。
5. 如果某个类的属性类型不是基本类型或String类型类是不可序列化的,而是另一个引用类型,那么这个引用类必须是可序列化的,否则拥有该引用类型属性的类是不可序列化的
自定义序列化与反序列化过程
Ø 在对象序列化的过程中,如果涉及一些敏感信息,如用户密码、信用卡账号等,通常需要考虑到保密和如何防止在传递过程中泄露的问题。使用transient关键字可以保护这些属性,但这些属性将不会参与序列化过程。
Ø 此时可以考虑显式定制序列化和反序列化的过程,在序列化时加入加密、在反序列化时加入解密等操作,而不是使用默认的序列化方式。要在对象所属类中重写writeObject(ObjectOutputStream out)和readObject(ObjectInputStream in)方法
内部类
内部类:
Ø 在一个类的内部定义的类称为内部类
内部类的用途
Ø 封装类型。
Ø 直接访问外部类的成员,
Ø 回调外部类的方法。
内部类的使用:
Ø 内部类的实例可以访问所有外部类的成员
Ø 当内部类的方法需要访问外部类的实例时,用this(代表内部类实例)报错,要用外部类名.this
Ø 在创建实例内部类的实例时,外部类的实例必须已经存在
Ø 在实例内部类中不能定义静态成员,而只能定义实例成员。
Ø 当放在局部变量的位置上时,如果内部类要调用外部类的属性或方法,要在这个外部类属性或方法前加final,否则程序报错
内部类的调用:
如果内部类没有私有化,那么外界可以调用内部类里的方法,有三种情况
Ø 当内部类是静态的:表明它毋需连接一个外部类即可工作。
外部类对象.内部类对象 变量名 = new 外部类对象.内部类()
变量名.方法
Ø 内部类和方法全是静态:外部类对象.内部类对象变量名 =外部类对象,内部类对象.方法
Ø 内部类和方法都不是静态:外部类对象.内部类对象变量名 = new 外部类对象.new 内部类
变量名.方法
Ø 如果方法是静态,内部类不是静态是会报错的。编译成class文件的时候没调用内部类,没加载进内存,但是外部类运行的时候会自动找到静态static并把他加载进内存,java报错
Ø 如果外部类中有个num ,内部类中一个num ,内部类里的方法列表中也有个num,想调用方法区的直接num,调用内部类用this.num,调用外部类用 外部类对象(外部类名.this). num.
内部类的比较
匿名内部类的定义:
New 父类名() {覆盖或者重写父类的方法}
注意:如果给匿名的内部类赋值,对象的类型只能是他的父类,所以能调用的方法也只有父类的方法,不能调用子类的特有方法。
常用类
JDK常见包
⑴ java.lang ——包含一些Java语言的核心类,如String、Math、Integer、System、Exception和Thread,提供常用功能。在java.lang包中还有一个子包:java.lang.reflect用于实现java类的反射机制。这个包是JVM在程序中自动导入的包
⑵ java.awt —— 包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
⑶ avax.swing——此包用于建立图形用户界面,此包中的组件相对于java.awt包而言是轻量级组件。
⑴ java.applet——包含applet运行所需的一些类。
⑵ java.net——包含与网络相关的类。如Socket、URL
⑶ java.io —— 包含能提供多种输入/输出功能的类。
⑷ java.util——包含一些实用工具类,如日期类、集合类。
⑸ JDK中还有很多其他的包,如用于数据库编程的java.sql包,用于编写网络程序的java.rmi包(Remote Method Invocation)。另外,javax.*包是对基本包的扩展,包括用于编写GUI程序的javax. swing包,以及用于编写声音程序的javax. sound包等·
⑹ JDK的所有包中的类构成了java类库,或者叫做J2SE API。java应用程序的编写都依赖于J2SE API。
Object
equals():
Ø 仅当被比较的两个引用变量指向同一对象时,equals()方法才返fpl true。
Ø 许多Java类都覆盖了这个方法,如java.io.File, .java.util.Date,java.lang.String、包装类等。比较规则为:如果两个对象的类型一致,并且内容一致,则返回true。
hashCode():
Ø 返回对象的哈希码值
Ø HashTable和HashMap会根据对象的哈希码值来决定它的存放位置
Ø 为了使程序更健壮,在编写Java类时要养成以下编程习惯
q 如果Java类重定义了equals()方法,那么这个类也必须重定义hashCode()方法,并且保证当两个对象用equals()方法比较的结果为true时,这两个对象的hashCode()方法的返回值也相等。
q 如果Java类实现了Comparable接口,那么应重定义compareTo(), equals()和hashCode()方法,保证compareTo ()和equals()方法采用相同的比较规则来比较两个对象是否相等,并且保证当两个对象用equals()方法比较的结果为true时,这两个对象的hashCade()方法的返回值相等。
toString():
Ø 返回当前对象的字符串表示,格式为“完整类名@对象的十六进制哈希码”
Ø 许多类,如String、StringBuffer和包装类都覆盖了toString()方法,返回具有实际意义的内容
finalize():
Ø 对于一个已经不被任何引用变量引用的对象,当垃圾回收器准备回收该对象所占用的内存时,将自动调用该对象的finalize()方法。
String
Ø String类用来操作字符串,可以把任意对象和数组转化成字符串
Ø String是字符串常量。对String的操作都是改变赋值地址而不是改变值操作。
“==”与equals的区别:“==”对于两个对象的地址值作比较是否相等,Equals 判断两个对象的内容是否相等
String类的方法:
1, 获取:
1, 获取字符串的长度:length();
2, 指定位置的字符。Char charAt(int index);
3, 获取指定字符的位置。索引不存在返回-1,所以通过返回值-1来判断一个字符在不在的情况。
int indexOf(int ch); int indexOf(int ch,int fromIndex);
int indexOf(String str); int indexOf(String str,int fromIndex);
int lastIndexOf(int ch); int lastIndexOf(int ch,int fromIndex);
int lastIndexOf(String str); int lastIndexOf(String str,int fromIndex);
4,获取子串。
String substring(int start);//从start位开始,到length()-1为止。
String substring(int start,int end);//从start开始到end为止。//包含start不包含end
获取整串 substring(0,str.length());
2,判断
1, 字符串中包含指定的字符串吗?Booleancontains(String substring);
2, 字符串是否以指定字符串开头?BooleanstartsWith(string);
3, 字符串是否以指定字符串结尾啊?boolean endsWith(string);
4, 判断字符串是否相同boolean equals(string);覆盖了Object中的方法,判断字符串内容是否相同。
5, 判断字符串内容是否相同,忽略大小写。boolean equalsIgnoreCase(string):
3 转换:
1,通过构造函数可以将字符数组或者字节数组转成字符串。
2,可以通过字符串中的静态方法,将字符数组转成字符串。
static String copyValueOf(char[] ); static String copyValueOf(char[],int offset,int count);
static String valueOf(char[]); static String valueOf(char[],int offset,int count);
3,将基本数据类型或者对象转成字符串。
static String valueOf(char);
static String valueOf(boolean);
static String valueOf(double);
static String valueOf(float);
static String valueOf(int);
static String valueOf(long);
static String valueOf(Object);
4,将字符串转成大小写。
String toLowerCase(); String toUpperCase();
5,将字符串转成数组。
转成字符数组。char[] toCharArray();
转成字节数组。byte[] getBytes();//可以加入编码表。
6,将字符串转成字符串数组。切割方法。 String[] split(分割的规则-字符串);
7,将字符串进行内容替换。变成新字符串。
String replace(oldChar,newChar);
String replace(oldstring,newstring);
8,对字符串进行追加。 String concat(string);
String s = new String(“a”)与String s = “a”;的区别
Java为String类型提供了缓冲池机制,当使用双引号定义对象时,java环境首先去字符串常量池中寻找相同内容的字符串,如存在就直接拿来应用,如不存在则创建一个新字符串放入常量池。
String a=”tom”;
String b=”tom”;
上述代码中,变量a和b使用的是字符串常量池中同一个存储对象
当使用构造函数定义对象时,每次调用都会创建一个新对象。
//创建了二个“对象”,一个由jvm隐含地创建,放入字符串常量池里,还有一个通过new语句显式创建,在堆里
String a= new String(”tom”);
String b=new String(”tom”);
上述代码中,变量a和b使用的是两个不同的存储对象,只是对象中的内容相同。
正则表达式
StringBuffer
1,添加。
StringBuffer append(data):在缓冲区中追加数据。追加到尾部。
StringBuffer insert(index,data):在指定位置插入数据。
2,删除。
StringBuffer delete(start,end);
StringBuffer deleteCharAt(index);
3,修改。
StringBuffer replace(start,end,string);
void setCharAt(index,char);
void setLength(len);
4,查找。
int indexOf(string);
int indexOf(string,int fromIndex);
int lastIndexOf(string);
int lastIndexOf(string,int fromIndex);
5,获取子串
string substring(start);
string substring(start,end);
6,反转。
StringBuffer reverse()
String、StringBuffer和StringBuilder详解?
n String是字符串常量,String在编译后是final的,所以是定长,内容不可变;StringBuffer和StringBuilder可变。
n 不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了可变版本,即StringBuffer和StringBuilder。
n StringBuffer是JDK1.5以前的,StringBuilder是升级以后的,二者方法都一样,是缓冲区,,容器长度可变。StringBuilder更高效,因为不考虑线程同步Synchronized问题。如果需要同步,则使用 StringBuffer
n 因为String对象建立后不能改变,所以对于每一个不同的字符串(使用内容相同的字符串,不必每次都new一个String),都需要一个String对象来表示,所以经常对字符串进行修改会引起很大的内存开销。而StringBuffer和StringBuilder类可以只修改一个字符串对象,而不用每个不同的字符串都要生成一个新的对象。
n String a = "123";a = a + "456"细节;在用String类对象直接+操作时,JVM会创建一个临时的StringBuffer类对象,并调用其append()方法完成字符串的拼接,这是因为String类是不可变的,拼接操作不得不使用StringBuffer类(并且JVM会将"Hello"和" world!"创建为两个新的String对象)。之后,再将这个临时StringBuffer对象转型为一个String,内存开销不菲
n 操作字符串一般使用String。但经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响。当你需要使用字符串来处理大量数据的时候,建议使用StringBuilder而不是String。
基本数据类型的包装类
Ø 包装类中提供了一系列实用的方法,比如Integer类的静态方法parseInt(String s)能把字符串转换为整数,在java集合中不能存放基本类型数据,如果要存放数字,应使用包装类型
Ø 所有的包装类都是final的,因此没有子类。
关于自动装箱与拆箱的小问题:
Integer a = new Integer(127);
Integer b = new Integer(127);
System.out.println(a == b);//false
System.out.println(a.equals(b));//ture
Integer x = 127;
Integer y = 127;
System.out.println(x == y);//ture
System.out.println(x.equals(y));//ture
// JDK1.5后,如果两次自动装箱的int型数值在1个字节范围内( -128到127), 那么第二次将不会重新开辟内存空间,而会共享第一次创建空间的值。原因在于在Integer类里有个内部类IntegerCache,存放-128~127范围内的对应Integer类对象,而该类的静态方法valueOf的实现是当数在 -128到127之内时直接返回内部类IntegerCache中存放的对应的Integer类对象,否则new一个新对象。这是设计模式中的享元模式。
Math、Random
java.util.Math类
Ø Math是fina类,没有子类。Math类的构造方法是private的,因此Math类不能被实例化。
Ø 包含了所有用于几何和三角的浮点运算方法,这些方法都是静态的
q abs(X x):返回不同类型的绝对值。
q max(两个参数):返回两个数中的最大值。
q min(两个参数):返回最小值。
q random():返回大于等于0小于1的随机数。
q pow(double a, double b):a的b次幂
q round(double a):四舍五入
q ceil(double a):返回大于A的最小整数。
q floor(double a):返回小于A的最大整数。
java. util.Random类
提供了一系列用于生成随机数的方法。
Ø nextInt():返回下一个int类型的随机数,随机数的值大于或等于0
Ø nextInt(int n):返回下一个int类型的随机数,随机数的值大于或等于0且小于参数n
Ø nextLang();返回下一个long类型的随机数,随机数的值位于long类型的取值范围内
Ø nextFioat():返回下一个float类型的随机数,随机数的值大于或等于0且小于1.0
Ø nextDouble():返回下一个double类型的随机数,随机数的值大于或等于0且小于1.0
Ø nextBoolean(}:返回下一个boolean类型的随机数,随机数的值为true或false
BigDecimal
Ø 能作用于int或float的操作,也同样能作用于BigInteger或BigDecimal。只不过必须以方法调用方式取代运算符方式来实现。这样牺牲了速度,但换来了精度。
Ø BigDecimal支持任意精度的整数。在运算中,可以淮确地表示任何大小的整数,而不会丢失任何信息。而BigDecimal支持任何精度的定点数,例如,可以用它进行精确的货币计算。
class Arith {
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
//这个类不能实例化
private Arith() {
}
/**
* 提供精确的加法运算。
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
Date、Calendar、DateFormat类
java.util.Date
Ø 包装了一个long类型数据,表示与1970年1月1日00:00:00这一刻所相距的毫秒数。Date类用于表示日期和时间
Ø 由于开始设计Date时没有考虑到国际化,所以后来又设计了DateFormat类和Calendar类来解决Date类中的问题
Ø Date类的默认构造方法调用System_currentTimeMillis{)方法获取当前时间,
new Date()语句等价于:new Date(System.currentTimeMillis())
Ø Date.getTime():日期对象转成毫秒值。
setTime():毫秒值转成日期对象。
java. text.DateFormat抽象类
Ø 用于定制日期的格式,它有一个具体子类为java.text.SimpleDateFormat
1, format(Date date):将Date格式转化为日期或者时间的字符串
2, DateFormat getDateInstance():获取日期,按照默认的格式。
3, Date parse(String source,ParsePosition pos):根据给定的规则生成日期和时间字符串
4, // 设置日期格式, new Date()为获取当前系统时间
String strTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date());
java.util.Calendar
1, Calendar getInstance():使用静态方法,返回这个类对象
2, Add(int field,int amount):增加或减去指定的时间。
3, Int get(int field):返回给定的字段的值。
4, Set(int year, int month, int date):设置时间
System、Runtime类
Ø Java不支持全局方法和变量,Java设计者将一些系统相关的重要方法和变量收集到了一个统一的类中,这就是System类
Ø Lang型中System类里面都是静态方法,要使用直接用类名.方法就可以调用了。
1,currentTimeMillis():返回以毫秒为单位的当前时间。
2,exit():终止java虚拟机
3,getProperties():返回Properties确定当前系统属性。
4,setProperty(String key, String value):自定义键值的系统属性。
Ø Runtime类封装了Java命令本身的运行进程,其中的许多方法与System中的方法重复,是单例设计模式
String shell;
if (file.isDirectory()) {
shell = "ls " + file.getCanonicalPath() + " -lad";// 获得当前目录的权限
} else {
shell = "ls " + file.getCanonicalPath() + " -l";// 获得当前文件的权限
}
Runtime runtime = Runtime.getRuntime();// 获取runtime对象来执行shell脚本
Process process = runtime.exec(shell); // 执行脚本
InputStream inputStream = process.getInputStream();// 获取脚本执行的结果流
异常
引子:异常情况会改变正常的流程,导致恶劣的后果。为了减少损失,应事先充分预计所有可能出现的异常,然后采取以下解决措施。
Ø 考虑避免异常,彻底杜绝异常的发生;若不能完全避免,则尽可能地减小异常发生的几率。
Ø 如果有些异常不可避免,那么应该预先准备好处理异常的措施,从而降低或弥补异常造成的损失,或者恢复正常的流程。
Ø 对于某个系统遇到的异常,系统本身应尽可能地处理异常,实在没办法处理,才求助于其他系统来处理。因为有些异常需要系统本身及其他系统共同来处理,例如大楼里发生严重火灾,仅靠大楼里的灭火器是无法完全灭火的,还需要消防队的参与才能灭火;还有些异常系统本身不能处理,要完全依靠其他系统来处理。
异常剖析
异常(exception)是在运行时代码中产生的一种异常情况,是一个运行时错误,如数组越界和文件找不到等,这些事件的发生将阻止程序的正常运行。捕获错误最理想的是在编译期间,最好在运行程序以前,但并非所有错误都能在编译期间侦测到,有些问题必须在运行期间解决。这时就需让错误的缔结者通过一些方法向接收者传递一些适当的信息,使其知道该如何正确地处理遇到的问题。解决的方法是在错误控制中排除所有偶然性,强制格式的正确
目的:是使用尽可能精简的代码创建大型、可靠的应用程序,同时排除程序里那些不能控制的错误。
运行原理:
Ø 一个方法的运行过程中,如果发生了例外,则该方法将生成一个相应类型的异常对象,并把其交给运行时系统,这过程被称为抛出。运行时系统在接收到异常后,会寻找相应的代码来处理,这一过程被称为捕获。如果运行时系统未找到异常的处理代码,将终止程序运行:若找到异常处理部分则执行处理代码,使程序继续执行下去。
Ø 当一个方法正常执行完毕,jvm 会从调用栈中弹出该方法的栈结构,然后继续处理前一个方法。如果在执行方法的过程中抛出异常,则jvm必须找到能捕获该异常的catch代码块。它首先查看当前方法是否存在这样的catch代码块,存在就执行该catch代码块;否则,jvm会从调用栈中弹出该方法的栈结构,继续到前一个方法中查找合适的catch代码块。在回溯过程中,如果jvm在某个方法中找到了处理该异常的代码块,则该方法的栈结构将成为栈顶元素,程序流程将转到该方法的异常处理代码部分继续执行。当jvm追溯到调用栈的底部的方法时,如果仍然没有找到处理该异常的代码块,将调用异常对象的printStackTrace()方法,打印来自方法调用栈的异常信息。如果该线程不是主线程,那么终止这个线程,其他线程继续正常运行。如果该线程是主线程(即方法调用栈的底部为main()方法),那么整个应用程序被终止。
java异常处理机制
Ø 要在程序中处理异常,主要考虑两个问题:(1)如何表示异常情况?(2)如何控制处理异常的流程?
好处:
Ø java通过面向对象的方法来处理异常事件。把各种不同类型的异常情况进行分类,把异常情况表示成异常类,可以充分发挥类的可扩展和可重用的优势。
Ø 在一些传统的编程语言,如C语言中,并没有专门处理异常的机制. 程序员通常用方法的特定返回值来表示异常情况,并且程序的正常流程和异常流程都采用同样的流程控制语句。它能加强程序的健壮性。如果不使用异常,就必须靠人为的去检查错误,并在程序中的许多地方去处理它;而使用异常,就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误,并且只需在一个地方处理错误,即所谓的异常处理程序。为了加强程序的健壮性,在程序设计时,必须考虑到可能发生的异常事件并做出相应的处理
Ø 异常流程的代码和正常流程的代码分离,提高了程序的可读性,简化了程序的结构,便于调试。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离,使代码结构清晰
Ø 可以灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理它,否则只需抛出异常,由方法调用者来处理它。
异常使用原则
Ø 异常只能用于非正常情况,不能用异常来控制程序的正常流程
当异常发生时,jvm需要执行额外的操作,来定位处理异常的代码块,这时会降低性能。如果抛出异常的代码块和捕获异常的代码块位于同一个方法中,这种影响就会小一些;如果jvm必须搜索方法调用栈来寻找异常处理代码块,对性能的影响就较大了。因此,不应使用异常处理机制来控制程序的正常流程,而应该确保仅仅在程序中可能出现异常的地方使try```catch语句。此外,如果当前方法具备处理某种异常的能力,就尽量自行处理,不要把自己可以处理的异常推给方法调用者处理。
Ø 应尽可能地避免异常,尤其是运行时异常。许多运行时异常是由于程序代码中的错误引起的,只要修改了程序代码的错误,或者改进了程序的实现方式,就能避免这种错误。有些异常是由于当对象处于某种状态时,不合适某种操作而造成的。例如当高压锅内的水蒸气的压力很大时,突然打开锅盖会导致爆炸。为了避免这类事故,高压锅应提供状态测试功能,让使用者在打开锅盖前,判断高压锅内的水蒸气压力,是否合适进行打开锅盖的操作
Ø 当异常发生后,应尽量使各个对象的状态恢复到异常发生前的初始状态,而不至于停留在某个不合理的中间状态。
Ø 避免过于庞大的try代码块。try代码块越庞大,出现异常的地方就越多,要分析发生异常的原因就越困难。有效的做法是分割各个可能出现异常的程序段落,把它们分别放在单独的try代码块中,从而分别捕获异常。
Ø 在catch子句中指定具体的异常类型。为图省事,许多人用catch(Exception e)子句来捕获所有异常,这不是好的编程习惯,因为这意味着对各种异常采用同样的处理方式,这是不合理的,而且这样会捕获本应抛出的运行时异常,掩盖程序中的错误。
Ø 不要在catch代码块中忽略被捕获的异常。只要异常发生,就意味着某些地方出了问题,应该提供处理异常的措施。假如在catch代码块中不能采取任何措施,那就不要使用catch代码块捕获异常,而是用throws子句将异常抛出。
Error:表示仅靠程序本身无法恢复的严重错误。一般指虚拟机相关的问题,如系统崩溃、虚拟机出错、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。Error类对象由Java虚拟机生成并抛出,遇到这样的错误,建议让程序终止
Exception: 表示程序中可能会出现的错误,是程序本身可以处理的异常。又分为以下二大类
1. 编译时异常:
Ø CheckedException,即Exception中除了RuntimeException体系的Exception类。
Ø 受检查异常表示只要通过处理,就可能使程序恢复运行。
Ø 如果抛出此异常对象没有处理,编译就不会通过。
2. 运行时异常:
Ø RuntimeException及其子类。运行时异常通常是由程序代码中的错误造成的,因此要尽量避免它。
Ø 这种异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常时,即使没有处理它,还是会编译通过。
Ø 产生该异常对象后不用处理它,因为该异常会立刻中止程序。而且假设处理成功使程序恢复运行,也可能会导致程序的业务逻辑错乱,甚至导致更严重的异常,或得到错误的运行结果。
Ø RuntimeException特别有助于调试某些特殊类型的编程错误,那些是编译器检测不到的。
3. 补充:Java认为Checked异常都是可以在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常,而Runtime
异常则无须处理
Error和RuntimeException的异同
相同:
java编译器都不会检查它们,当程序运行时出现它们,都会终止程序。
不同:
Error类及其子类表示的错误通常是由jvm抛出的,在JDK中预定义了一些错误类,一般不会扩展Error类来创建用户自定义的错误类。而RuntimeException类表示程序代码中的错误,它是可以扩展的,用户可以根据特定的问题领域来创建相关的运行时异常类。
Throwable类常用的方法:
getMessage()---返回String类型的异常信息
printStackTrace()—打印跟踪方法调用栈而获得的详细异常信息。在程序调试阶段,此方法可用于跟踪错误。
异常处理原则
Ø 问题出现了,你也许不清楚该如何处理,但你不应置之不理。你要停下来,看看是不是有别的地方能够处理这个问题。只是在当前环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中去作出正确的决定。
异常的二种处理方法
开发时,一般会采取统一方式来处理异常
一种是使用try-catch try{//可能会发生异常的代码} catch(希望捕获的异常类型){ 处理该异常的代码}
1. try{}catch(Exception 对象){} //一般处理原则:有几个具体的异常就有几个catch
2. try{} fianlly{} //用法比如:服务器不管有无异常,都要关闭资源
3. try{} catch(Exception对象){} finally{} //产生的异常对象会逐步与catch语句中的异常类型进行匹配,匹配成功则执行相应处理代码,不成功则最终抛给虚拟机处理
4. 注意:不能单独使用try,必须与catch或finally配对出现。当使用多catch语句时,异常子类必须在它们父类之前。
5. 当try代码块后面有多个catch代码块时,jvm会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,而不会再执行其他的catch代码块。
6. throw语句后面不允许紧跟其他语句,因为这些语句永远不会被执行。
7. finally块用于关闭资源等,执行结果在方法return返回值之前。finally块总会被执行,除非在finally语句之前执行了退出虚拟机的方法 System.exit(1)。exit()方法的参数status表示程序终止时的状态码,按照编程惯例,零表示正常终止,非零数字表示异常终止。如果在执行finally之前的代码块时,电脑突然断电,所有进程都终止运行,也不会执行finally语句。
8. finally代码块虽然在return语句之前被执行,但finally代码块不能通过重新给变量赋值的方式来改变return语句的返回值。
另一种是使用throws。
u 若当前方法不知道应该如何处理这种异常,则该异常应抛给上一级调用者处理,如果main方法也不知道该如何处理,则使用throws声明抛出该异常,该异常最终将交由JVM处理。JVM对异常的处理方法是:打印异常跟踪栈信息,并中止程序运行,这就是程序在遇到异常后自动结束的原因。
u 如果某段代码中调用了一个带throws声明的方法,该方法声明抛出了Checked异常,这表明该方法希望它的调用者来处理该异常。也就是说,这段代码要么放在try块中显式捕获该异常,要么在这段代码所处方法上抛出该异常。
u throws 和 throw的区别:Throws是放在函数上的,用来抛异常类,可以抛多个;throw是放在函数中,用来抛异 常对象(后面跟着new),只能抛一个。
异常转换
Ø 客户层访问业务逻辑层,而业务逻辑层访问数据库层。数据库层把异常抛给业务逻辑层,业务逻辑层把异常抛给客户层,客户层则把异常抛给终端用户。当位于最上层的子系统不需要关心来自底层的异常的细节时,常见的做法是捕获原始的异常,把它转换为一个新的不同类型的异常再抛出,这种处理异常的办法称为异常转换 。
Ø 例如,假设终端用户通过客户界面把一个图像文件上传到数据库中,客户层调用业务逻辑层的uploadImageFile()方法去执行上传图像文件操作,此时可能会抛出IOException,但用户没必要关心IOException异常的细节,他仅需知道上传图像失败就可以了。因此,当uploadImageFile()方法抛出IOException异常后,应在catch代码块中先把该异常信息记入日志然后向用户抛出上传失败异常。具体的调试和排错由系统管理员或者软件开发人员来处理。
Ø 如果某个方法出现了无法处理的异常,则应抛给上级调用者处理。如果判断出上级调用者也处理不了,则应把异常转换成上级调用者能处理的异常。
自定义异常:
当java中没提供想要的异常时,那么就要根据需要自定义异常。
步骤:1. 创建一个类,继承异常类。
2 .自定义构造方法。在程序中可以自己定义一个判断,当条件不满足时就抛出自定义异常
继承时关于异常的处理:
在继承父类或者接口的时候,只能抛和父类异常相等或其子类,不能抛比父类异常还要多的异常。但是如果真的有,那应该要自己处理掉,如果自己没有能力处理就要用RuntimeEcxeption或其子类,让程序中断。
日志&调试器
为什么需要日志
q 监视代码中变量的变化情况,把数据周期性地记录到文件中供其他应用进行统计分析工作。
q 跟踪代码运行进轨迹,作为日后审计的依据。
q 担当集成开发环境中的调试器,向文件或控制台打印代码的调试信息。
日志的使用: 最简单的方法就是建立一个java.util.logging.Logger的实例
7种日志级别
Ø SEVERE,WARNING,INFO,CONFIG,FINE,FINER、FINEST
Ø 其中SEVERE即用于处理错误以及灾难事件。而FINE,FINER和FINEST级是用于处理一些不那么重要的信息,以及用于程序调试。
Ø 缺省的消息记录级别为:INFO。在缺省情况下我们在控制台看不见低于INFO级别的日志消息
Ø 可以设定日志级别。logger.setLevel(Level.FINE)
调试器
q 找到引发错误的代码并修正
q 断点,就是你想让程序运行到哪条语句停止
GUI
引子:图形用户界面由各种组件构成,普通组件被添加到容器类组件中。布局管理器决定如何在容器中放置组件。组件委托事件监听器来响应事件,从而完成与用户的交互。
GUI &CLI
CLI :Command line User Interface (命令行用户接口),就是常见的Dos命令行操作。需要记忆一些常用的命令,操作不直观。
GUI :Graphical User Interface(图形用户接口)。用图形的方式,来显示计算机操作的界面,这样更方便更直观。
Ø GUI提供了容器类、众多的组件类和布局管理器类,都在java.Awt和javax.Swing两个包中,都用于GUI编程
Ø java.Awt:Abstract Window ToolKit (抽象窗口工具包) 当使用AWT编写图形界面应用时,程序仅指定了界面组件的位置和行为,并未提供真正的实现,而是通过JVM调用操作系统本地的图形界面来创建。属重量级控件(依赖于本地平台)。
Ø javax.Swing:在AWT的基础上建立的一套图形界面系统,对原有AWT组件进行了扩展,完全由Java实现。虽然Swing为AWT的每一个组件都提供了替代品,但为组件添加事件处理的操作是相同的。Swing增强了移植性,属轻量级控件(独立于本地平台)。
AWT的继承关系图
AWT构建图形用户界面的机制
Ø 提供了一些容器组件(如Frame和Panel ),用来容纳其他组件(如Button、复选框Checkbox和文本框TextField )
Ø 用布局管理器来管理组件在容器上的布局
Ø 利用监听器来响应各种事件,实现用户与程序的交互
Ø 提供了一套绘图机制,来自动维护或刷新图形界面。在Component类中提供了3个和绘图有关的方法。
q paint(Graphics g):绘制组件的外观。Graghics类代表画笔,提供了绘制各种图形的方法
q update(Graphics g):调用paint()方法,刷新组件的外观。
q repaint():调用update()方法,刷新组件的外观。
组件继承关系图
Ø 不能单独存在,只能存在于其他容器(Window或其子类)中
Ø Window是不依赖于其他容器而独立存在的容器。Window有两个子类:Frame和Dialog。Frame带有标题,而且可以调整大小;Dialog可以被移动,但是不能改变大小
Ø
Ø Frame必须先调用setSize()或pack()方法(会确保Frame容器中的组件都会有与布局相适应的合理大小),然后调用setVisible(true)方法使Frame成为可见
布局管理器
q 容器中的组件的排放方式,就是布局。组件在容器中的位置和尺寸是由布局管理器来决定的。
q 所有的容器都会引用一个布局管理器实例,通过它来自动进行组件的布局管理。所有容器都有默认布局管理器,你也可以通过setLayout()方法来重新设置容器的布局管理器
q 为了使生成的图形用户界面具有良好的平台无关性,java提供了布局管理器LayoutManager对容器进行管理。
q 常见的布局管理器:
Ø FlowLayout(流式布局管理器)
ü 从左到右的顺序排列。遇到边界就折回并换行,重头从左向右排列
ü Panel和Applet默认的布局管理器
Ø BorderLayout(边界布局管理器)
ü 东,南,西,北,中
ü Window、Frame和Dialog的默认布局管理器
Ø GridLayout(网格布局管理器)
ü 规则的矩阵
Ø CardLayout(卡片布局管理器)
ü 选项卡
Ø GridBagLayout(网格包布局管理器)
ü 非规则的矩阵
事件监听
q 如欲获得用户界面事件通知,你需要定义一个事件监听器并将其注册至视图,或为视图覆写一个现有的回调方法(需要你自定义一个View类)
q 事件源:事件发生的场所,就是每个可以触发事件的组件,如按钮、窗口、菜单等。一个事件源可以注册多个不同的事件监听器。一个事件源也可以触发多种事件,它委托给相应的事件监听器来处理,这种设计模式被称为委托模式
q 事件:封装了组件上发生的特定事情(通常就是一次用户操作)。每种事件都有对应的监听器,通过Event对象去获得事件的相关信息
q 事件监听器:负责监听和处理事件源上发生的事件
事件继承图
IO流
引子:输入/输出系统不仅需要考虑三种不同种类(文件、控制台、网络连接)的IO,而且需要通过大量不同的方式与它们通信(顺序、随机访问、二进制、字符、按行、按字等等)。
IO流图解
IO流的定义与分类
定义:可以理解为数据的流动。就是一个数据流。Java程序通过流来完成输入/输出,IO流最终要以对象来体现。
用处:流的概念使得文件、网络、内存等设备可以被统一处理。用来处理设备之间的数据传输.JAVA对数据的操作是通过流的方式.
流的操作只有两种:读和写。
分类:
数据类型 相对内存方向 功能
字节流 输入 节点流
字符流 输出 链接流
字符编码
Ø 计算机里只有数字,字符与数字对应的编码固定下来后,这套编码规则被称为ASCII码
Ø 许多国家都把本地的字符集引入了计算机,扩展了计算机中字符的范围,中国大陆为每一个中文字符都指定了一个对应的数字,这套编码规则称为GBK
Ø 为了解决各个国家和地区使用自不同的本地化字符编码带来的不便,人们将全世界所有的符号进行了统一编码,称之为Unicode编码
为什么要有字符流呢?
字节流:处理字节数据的流对象。设备上的数据无论是图片或dvd,文字等,都是以二进制存储的。二进制最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据。但因为每个国家的字符都不一样。所以涉及到了字符编码问题。那么GBK编码的中文用unicode编码解析是有问题的。所以需要获取中文字节数据的同时+指定的编码表才可以解析正确数据。为了方便于文字的解析。所以将字节流和编码表封装成对象。这个对象就是字符流。这就是字符流的由来。只要操作的是字符数据,优先考虑使用字符流体系。
流的体系
因为功能不同,但有共性内容,所以不断抽取形成基本体系。该体系有四个基类。而且都是抽象类。
共性特点:子类名后缀都是父类名。前缀名都是这个子类的功能名称。
共性方法
read方法用来读入数据,不带参数的read方法一次读入一个字节,返回值表示读入的数据;带byte数组的方法一次可读入多个字节,返回值表示本次读入了多少个字节,但这样得到的是一个字节数组,有时在程序中处理起来并不方便,所以有了缓冲流。当读到一个流的结尾时,这两个方法的返回值为-1,因此一般通过一个循环读入一个输入流中的所有数据:详细范例代码参见FileInputStream。
close方法用来关闭输入流,大多数输入流在使用完毕后都需要调用close方法关闭输入流。
当在程序中创建一个IO流对象时,计算机内存中实际上产生了一个java程序中类的实例对象,一个系统本身产生的某种资源。 Java垃圾回收器只能管理程序中的类的实例对象,没法去管理系统产生的资源,所以程序需要调用close方法,去通知系统释放自身产生的资源。
iO流的读写示例
close()和flush()的区别:
flush():将缓冲区的数据刷到目的地中后,流可以使用。
close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定要做。
--------------------------------------------------------------------------------------------------------------------
读取数据:自定义缓冲区。较为高效。
import java.io.*;
class FileReaderDemo2{
public static void main(String[] args) throws IOException{
//创建读取流对象时会同时调用系统底层资源,在指定的位置创建一个存储数据的文件
FileReader fr = new FileReader("demo.txt");
//使用read(char[])方法,要将读取到的字符存入数组。所以要创建一个一般长度是1024的整数倍的字符数组。。
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
fr.close();//关闭流,其实就是关闭java调用的系统底层资源。在关闭前,会自动先刷新该流。
}
}
--------------------------------------------------------------------------------------------------------------------
将指定目录中的文本文件复制到另一个目录中。
import java.io.*;
class CopyText2{
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try{
fr = new FileReader("FileWriterDemo2.java");
fw = new FileWriter("write2_copy.txt");
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1)
{
fw.write(buf,0,len);
fw.flush();
}
}
catch (IOException e)
{
// System.out.println("write :"+e.toString());
throw new RuntimeException("复制失败");
}
finally
{
if(fw!=null)
try
{
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
}
}
}
流的操作规律
流对象:就是读取和写入。但因为功能的不同,流体系中提供了很多对象。那到底该用哪个对象更为合适呢?这就需要明确流的操作规律。
想要知道开发时用到哪些对象。只需要通过四个明确即可。
1, 明确源和目的(汇).
源:InputStream Reader
目的:OutputStream Writer
2, 明确数据是否是纯文本数据。
源:如果是纯文本:Reader
不是纯文本:InputStream
目的:是纯文本Writer
不是纯文本:OutputStream
到这里,就可以明确需求中具体要使用哪个体系。
3, 明确具体的设备。
源设备:
硬盘:File
键盘:System.in
内存:数组,(其实就是缓冲区)
网络:Socket流。
目的设备:
硬盘:File
控制台:System.out
内存:数组
网络:Socket流。
4, 是否需要其他额外功能。
a, 是否需要高效(缓冲区):
是,就加上buffer。
b,是否需要转换流
需求1:复制一个文本文件。
1, 明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2, 是否是纯文本:是
源:Reader
目的:Writer
3, 明确具体设备。
源:硬盘:File FileReader fr = new FileReader(“a.txt”);
目的:硬盘:File FileWriter fw = new FileWrtiter(“b.txt”);
4, 需要额外功能吗?
需要,需要高效。
---------------------------------------------------------------------------------------------------------------------------------
需求2:读取键盘录入信息,并写入到一个文件中。
1,明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2,是否是纯文本呢?
是。
源:Reader
目的:Writer
3,明确设备:
源:键盘。System.in InputStream in = System.in
目的:硬盘。File FileWriter fw = new FileWriter(“b.txt”);
这样做可以完成,但麻烦。将读取的字节数据转成字符串,再由字符流操作。
4,需要额外的功能吗?
需要:转换。将字节流转换成字符流。因为明确的源是Reader,这样操作文本数据做便捷。所以要将已有的字节流转换成字符流。使用字节转换字符流:InputStreamReader.
InputStreamReader isr = new InputStreamReader(System.in);
FileWriter fw = new FileWriter(“b.txt”);
需要高效吗?需要。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new FileWriter(“b.txt”));
======================================================================
需求3:将以个文本文件数据显示在控制台上。
1, 明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2, 是否是纯文本呢?
是。
源:Reader
目的:Wrter
3, 明确具体设备:
源:硬盘:File
目的:控制台:System.out
FileReader fr = new FileReader(“a.txt”);
OutputStream out = System.out; //PrintStream
4, 需要额外的功能吗?
需要:转换。
FileReader fr = new FileReader(“a.txt”);
OutputStreamWriter osw = new OutputStreamWriter(System.out);
需要高效吗?需要
BufferedReader bufr = new BufferedReader(new FileReder(“a.txt”);
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
======================================================================
需求4:读取键盘录入数据,显示在控制台上。
1, 明确源和目的:
源:InputStream Reader
目的:OutputStream Writer
2, 是否是纯文本呢?
是
源:Reader
目的:Writer
3, 明确设备:
源:键盘:System.in
目的:控制台:System.out
InputStream in = System.in;
OutputStream out = System.out;
4, 明确额外功能?
需要转换,因为都是字节流,但是操作的却是文本数据,所以使用字符流操作起来更为便捷。
InputStreamReader isr = new InputStreamReader(System.in);
OutputStreamWriter isw = new OutputStreamWriter(System.out);
需要将其高效
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in);
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out);
======================================================================
需求5:将一个中文字符串数据按照指定的编码表写入到一个文本文件中。
1, 目的:OutputStream Writer
2, 是纯文本,Writer
3, 设备:硬盘File
FileWriter fw = new FileWriter(“a.txt”);
fw.write(“你好”);
注意:既然需求中已经明确了指定编码表的动作。
那就不可以使用FileWriter,因为FileWriter内部使用的是默认的本地码表。
只能使用其父类。OutputStreamWriter.
OutputStreamWriter接收一个字节输出流对象,既然是操作文件,对象应该是FileOutputStream。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“a.txt”).charsetName);
需要高效吗?
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(“a.txt),charsetName));
转换流
转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
转换流的最强功能就是基于字节流+编码表。凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。
发现转换流有一个子类就是操作文件的字符流对象。
InputStreamReader
|--FileReader
OutputStreamWriter
|--FileWrier
想要操作文本文件,必须要进行编码转换。而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。
但是子类有一个局限性。就是子类中使用的编码是固定的是本机默认的编码表。对于简体中文版的系统默认码表是GBK.
FileReader fr = new FileReader("a.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
这两行代码的功能一摸一样。如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt");
如果需要制定码表,必须用转换流。
标准输入输出流
Ø 语言包java.lang中的System类管理标准输入/输出流和错误流
Ø 标准输入输出是操作系统提供的输入输出流,默认情况下: 标准输入System.in对应键盘的输入,是InputStream类型的,程序使用System.in可以读取从键盘上输入的数据;标准输出System.out对应控制台,是PrintStream类型的;标准错误输出System.err 默认情况下也会输出到控制台,在Eclipse开发环境中,标准错误输出打印出的内容用红色字体显示
Ø System类提供了一些用于重定向流的静态方法。
q setIn(InputStream in):对标准输入流重定向
q setout(PrintStream out):对标准输出流重定向
q setErr(PrintStream out):对标准错误输出流重定向
打印流PrintStream
1:提供了更多的功能,比如打印方法。可以直接打印任意类型的数据。
2:自动刷新。不用手动刷新
3:使用本机默认字符编码.
4:该流的print方法不抛出IOException。
该对象的构造函数。
Ø PrintStream(File file) 创建具有指定文件且不带自动行刷新的新打印流。
Ø PrintStream(String fileName) 创建具有指定文件名称且不带自动行刷新的新打印流。
Ø PrintStream(OutputStream out) 创建新打印流。
Ø 前两个都JDK1.5版本才出现。而且在操作文本文件时,可指定字符编码了。
PrintStream可以操作目的:
1:File对象。
2:文件的字符串路径。
3:字节输出流。
当目的是一个字节输出流时,如果使用的println方法。可以在printStream对象上加入一个true参数。这样对于println方法可以进行自动的刷新。而不是等待缓冲区满了再刷新,最终print方法都将具体的数据转成字符串。而且都对IO异常进行了内部处理。
PrintWriter对象与PrintStream区别?
1, 可以手动指定编码,可以转化成字符来操作
2, 目标地比PrintStream多了一个输出字符流。
3, 两个相对尽量多使用PrintWriter,因为操作字符的多
PrintWriter具备PrintStream的特点,同时还有自身特点。开发时尽量使用PrintWriter。
/*
读取键盘录入将数据转成大写显示在控制台.
*/
//源:
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//目的:又想把数据写到文件中。还想自动刷新。
PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
out.println(line.toUpperCase());
//out.flush();
}
/*
注意:
System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。
随时可以使用。当jvm结束了。这两个流就结束了。
但是,当使用了显示的close方法关闭时,这两个流在提前结束了。
*/
out.close();
bufr.close();
}
}
其他流
管道流:
• 管道流用来把一个程序、线程或代码块的输出连接到另一个程序、线程或代码块的输入。主要作用是可以连接两个线程间的通信。管道流也分为字节流(PipedInputStream、PipedOutputStream)与字符流(PipedReader、PipedWriter)
• 管道读取流和管道写入流可以像管道一样对接上。管道读取流就可以读取管道写入流写入的数据。
通过构造方法连接
PipedInputStream(PipedOutputStream pos); PipedOutputStream(PipedInputStream pis);
通过各自的connect()方法连接
在类PipedInputStream中,connect(PipedOutputStream pos);
在类PipedOutputStream中,connect(PipedInputStream pis);
注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法,是阻塞式的。没有数据的read方法会让线程等待。
public static void main(String[] args) throws IOException
{
PipedInputStream pipin = new PipedInputStream();
PipedOutputStream pipout = new PipedOutputStream();
pipin.connect(pipout);
new Thread(new Input(pipin)).start();
new Thread(new Output(pipout)).start();
}
ByteArrayOutputStream&ByteArrayInputStream:
1, 操作设备目的是File和数组
2, 两个源和目的都是内存。把内存中的一个缓冲区(ByteArray)作为输入流和输出流使用
3, 里面就是封装了数组,本身只有一个length属性,可以读取和设置
数据流类DateInputStream和DateInputStream:是FilterInputStream的子类,专门用于处理java的基本数据类型,必须成对出现
序列流SequenceInputStream
Ø 作用就是将多个读取流合并成一个读取流。表示其他输入流的逻辑串联。这样做,可以更方便的操作多个读取流。
Ø 它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
Ø 序列流的构造函数参数是枚举,想要获取枚举,需要有Vector集合。而枚举和迭代器是功能一样的。所以,可以用迭代替代枚举。
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()
{
public boolean hasMoreElements()
{
return it.hasNext();
}
public FileInputStream nextElement()
{
return it.next();
}
};
这样定义很麻烦。应该想到集合框架中工具类是否有提供相应的转换方法。
Collections.enumeration(Collection c);该方法的原理往上看。
RandomAccessFile:
特点:
1:该对象能操作的源和目的必须是文件。该对象既可读取,又可写入,允许对文件内容同时完成读和写操作
2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组 。
3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。
4:其实该对象内部封装了字节读取流和字节写入流。
注意:实现随机访问,最好是数据有规律。
File类
Ø File类是IO包中唯一代表磁盘文件本身的对象,File类定义了一些与平台无关的方法来操纵文件
Ø File类将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。
Ø Java约定使用“/”来作路径分隔符,或者“\\”
Ø RandomAccessFile类是Java语言中功能最为丰富的文件访问类
RandomAccessFile能以只读或读写方式打开文件
new RandomAccessFile(f,"rw"); // 读写方式
new RandomAccessFile(f,"r"); // 只读方式
File类常见方法:
构造函数
q File(String path) File f1 = new File("/");
q File(String path, String filename) File f2 = new File("/","autoexec.bat");
q File(File file, String filename) File f3 = new File(f1,"autoexec.bat");
1:创建。
boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。
而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir():创建此抽象路径名指定的目录。
boolean mkdirs():创建多级目录。
2:删除。
boolean delete():删除此抽象路径名表示的文件或目录。
void deleteOnExit():在虚拟机退出时删除。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。
注意:window的删除动作,是从里往外删。java删除文件不走回收站,要慎用。
3:获取.
long length():获取文件大小。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
String getParent():返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回null。
long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为“;”。
File.Separator:返回当前系统默认的目录分隔符,windows默认为“\”。
4:判断:
boolean exists():判断文件或者文件夹是否存在。
boolean isDirectory():判断此抽象路径名是否是目录。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。
5:重命名。
boolean renameTo(File dest):可以实现移动的效果。剪切+重命名
String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。
如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。
如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。
FilenameFilter与FileFilter的区别?
当需要文件夹对象的时候可以用过滤器,可以过滤一些自定义的条件
1, 当文件夹的对象以字符串的形式返回子目录的一些文件,需要定义FilenameFilter接口,实现里面的方法accipt(File dir,String name)
2, 当文件夹的对象是以File的形式返回的一些子目录的文件,需要FileFilter这个接口,实现里面的方法accept(File pathname)
递归
Ø 就是一个方法每次用不同的参数值反复调用自己。其实递归就是在栈内存中不断的加载同一个方法。
何时用递归呢?
当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。
递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现StackOverflowError 栈内存溢出错误。
Test:删除一个目录下的所有文件.
public static void del(File f) {
File[] files = f.listFiles();
for(File file:files){
if(file.isDirectory()){
del(file);
}else{
file.delete();
}
}
f.delete();
}
多线程
进程、线程、多线程
引子: 对于大量问题,我们想把问题切分成多个可独立运行的部分(任务),从而提高程序的响应能力。在程序中,这些彼此独立运行的部分称之为线程,上述概念被称为‘并发”。例如当你点击用户界面的按钮时会快速得到响应,而不是等其他程序执行完才响应。最初,程序员用机器底层知识来编写这样的程序,但其难度太大且不能移植,后来就出现了多线程
并行性:并行指在同一时刻,有多条指令在多个处理器上同时执行
并发性:并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
程序、进程和线程的区别:
Ø 几乎所有操作系统都支持同时运行多个程序,一个程序可以启动多个进程。当程序进入内存运行时,即变成进程。进程是计算机执行程序的过程,是一个正在执行的程序。进程的特征如下
q 独立性:进程是系统中独立存在的实体,它拥有自己一组独立的资源和私有的地址空间。在没有经过进程本身的允许 下,一个进程不可以访问其他进程。
q 动态性:程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。进程具有自己的生命周期和各种不同的状态。
q 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
Ø 线程是进程的执行单元。每个线程都是独立的栈内存,可以独立运行,相互之间没有关系。
q 线程是进程的组成部分,这些线程共享进程的全部资源。线程控制着进程的执行,是进程的运行控制单元。线程的调度和管理由进程本身负责完成。
q 线程用于完成一定的任务,相互之间协同来完成进程所要完成的任务。线程之间通过共享的进程数据进行通信
q 在java中,程序通过流控制来执行程序流。程序中单个顺序的流控制称为线程
Ø 线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源
线程补充
Ø 主线程: 当Java程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程(main thread),它是程序开始时就执行 的 。它是产生其它子线程的线程,通常它必须最后完成执行,用于执行各种关闭动作。
Ø 线程池:线程池在系统启动时即创建大量空闲的线程。程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run方法。此外,线程池可以有效地控制系统中并发线程的数量,防止并发线程过多而导致系统性能剧烈下降,甚至导致JVM崩溃
Ø 多线程:就是单个进程中可以同时运行多个不同的线程,执行不同的任务。
因为计算机中机器指令的真正执行者是CPU线程必须获得CPU的使用权,才能执行一条指令。导致了多线程特性:随机性谁抢到谁执行,至于执行多长,cpu说了算。多线程编程优点如下:
q 进程间不能共享内存,而线程可以。
q 系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
q Java语言内置多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程。
Ø 我们可以创建其他线程,和主线程并发运行
开启、停止线程
开启线程的方法:
1. 继承Thread类和实现Runnable接口,并重写run方法,最后调用start()开启线程
2. 当线程开始,run()方法里代码被执行
3.开启方式的区别:.继承方式线程代码存放在Thread子类run方法中,实现方式代码存放在接口的子类run方法中。
实现方式避免了单继承的局限性
4.run方法与start方法的区别:
Ø 必须调用start才能开启线程,run方法不可以。start方法用于启动线程,而run方法包含线程运行时的任务。而直接调用线程的run方法,会被当成一般方法执行,而不是线程执行体。
Ø 一个线程只能启动一次。当新建线程对象多次调用start()或对处于死亡状态的线程调用start()方法,将引发java.lang.IllegalThreadStateException
如何停止线程?
Ø 只有一种方式,就是run方法结束.所以当开启多线程运行时, 只要定义一个标识变量,通过改变它的值来控制住循环(run方法里通常是循环结构),就可以让run方法结束。
public class Runner extends Thread{
private boolean flag=false;
public void shutDown(){
flag=true;
}
public void run(){
while(!flag){
//your business logic
}
}
}
Thread的方法
Ø Thread.currentThread()----------------->返回当前线程对象
Thread.activeCount()------- >返回线程组中目前活动的线程的数目
Thread:setName(String name)-------- >为线程取名
Thread:getName()----------------------->返回线程名
Thread.yield() 当前线程放弃cpu执行权。当前线程会停一会,具体多少时间不确定
Ø 线程操作方法
1、start() 启动线程
2、sleep(int ms) 线程休眠
3、run() 线程的执行内容
4、interrupt() 线程强行中止休眠状态,进入下面程序的执行。
5、stop() 终止线程(已取消,不要使用)
6、线程优先级控制 setPriority(int p) getPriority()
线程的优先级是说执行到的机率会大一点,线程优先级是从1到10的。不同操作系统的优先级不同,而且不能很好地和Java的10个优先级对应,所以我们应尽量避免直接为线程指定优先级,而应使用MAX_PRIORITY, MIN_PRIORITY和NORM_PRIORITY三个静态常量来设置优先级,以保证程序的可移植性。
7、join() 停止当前线程(不是所有线程),直到调用join方法的线程结束
8、currentThread() 获得当前运行的线程的引用
9、isAlive() 测试线程是否处于活动状态
10. setDaemon() 守护线程,其实跟其他线程一样,只是守护线程会随着前台线程的结束而结束。
后台线程特点:开启后与前台线程共同抢夺cpu执行权并运行,但是当所有前台线程结束时它才自动结束
setDaemon(true)必须在start()方法之前调用,否则会引发I1legalThreadStateException异常
sleep和yield方法的区别:
Ø sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。但yield方法只会给优先级相同,或优先级更高的线程执行机会。
Ø sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态。而yield不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield方法暂停之后,立即再次获得处理器资源被执行。
Ø sleep方法会抛出InterruptedException异常,而yield方法不会抛出任何异常。
Ø sleep方法比yield方法有更好的可移植性,通常不要依靠yield来控制并发线程的执行。
线程的状态和相关方法
线程的五种状态:
新建状态:用new语句创建的线程对象处于新建状态,仅在堆区中分配了内存
就绪状态:当一个线程对象创建后,其他线程调用它的start方法,该线程就进入就绪状态,处于这个状态的线程位于可运行池中,等待获得CPU的使用权
运行状态:当线程抢到cpu的运行资格和运行权的时候就可以运行了。一般只有一个CPU,因此只有一个线程处于运行状态。只有处于就绪状态的线程才有机会转到运行状态
阻塞状态:当cpu有线程运行时,那种有运行资格而没有运行权的线程会在临时阻塞的地方等待,直到有运行权。
阻塞状态有三种:
Ø 位于线程等待池:当线程处于运行状态时,如果执行了wait()方法,jvm就会把线程放到等待池中
Ø 位于对象锁池中:当线程处于运行状态,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,jvm就会把这个线程放到这个对象的锁池中
Ø 其他阻塞状态:当前线程执行了sleep()方法,或调用了其他线程的join()方法,或发出了I/O请求时,就会进入这个状态。当发出一个I/O请求时,该线程会放弃CPU,进入阻塞状态,直到I/O处理完毕,该线程才会恢复运行。
死亡状态:当线程退出run()方法自然结束或遇到异常结束。
定时器Timer
Timer表示定时执行特定的任务
Timer类的schedule(TirnerTask task, long delay, long period)方法用来设置定时器需要定时执行的任务。task参数表示任务;delay参数表示延迟执行的时间;period参数表示每次执行任务的间隔时间
TimerTask类是一个抽象类,它实现了Runnable接口,它的run()方法表示定时器需要定时完成的任务
线程的同步
Ø 线程的职责就是执行一些操作,而多数操作都涉及到处理数据。当两个以上的线程需要共享数据,它们需要某种方法来确定数据在某一刻仅被一个线程占用。达到此目的的过程叫做同步(synchronization),同步是为了保证数据完整性
Ø 当一个线程已经在操纵共享资源时,其他线程只能等待,只有当己经在操纵共享资源的线程执行完同步代码块后,其他线程才有机会操纵共享资源。
Ø 线程同步也会降低程序并发性能。所以在使用同步时,应只在有必要的时候(通常在修改数据时)使用,且同步块应尽量短, 避免死锁
同步代码块.
Ø 只让一个线程执行完多条操作共享数据的语句。该线程在执行过程中,其他线程不会参与执行
同步的前提:1.必须有二个以上的线程
2.必须是多个线程使用同一个锁
3.必须保证同步中只能有一个线程在运行
Ø 同步的用法:
1.明确哪些是多线程运行代码
2.明确哪些是共享数据
3.明确多线程运行代码中哪些语句是操作共享数据的
synchronized(对象)
{
需要被同步的代码
}
(对象)如同锁,持有锁的线程可以在同步synchronized中执行,未持有锁的线程即使获得cpu执行权,也进不去
Ø 好处:解决了多线程的安全问题。弊端:多个线程需要判断锁,较为消耗资源
Ø synchronized声明不会被继承。如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不再保持同步,除非也用synchronized修饰。
锁
Ø 由于等待锁的线程只有在获得这把锁之后, 才能恢复运行, 所以让持有锁的线程在不再需要锁时及时释放是很重要的.
释放锁的情况
1.执行完同步代码块, 就会释放锁.
2.在执行同步代码块的过程中, 遇到异常(指没有手动抛出的异常)而导致线程终止, 锁也会被释放
3.在执行同步代码块的过程中, 执行了锁所属对象的wait()方法, 这个线程会释放锁, 进入线程等待池.
不会释放锁的情况
1.在执行同步代码块的过程中, 执行了Thread.sleep()方法, 当前线程放弃CPU,开始睡眠, 在睡眠中不会释放锁.
2.在执行同步代码块的过程中, 执行了Thread.yield()方法, 当前线程放弃CPU,但不会释放锁.
死锁:当一个线程等待由另一个线程持有的锁,而另一个线程正在等待已被第一个线程持有的锁时,就会发生死锁。
同步与锁的区别?
1, 同步升级以后把他封装成了对象,分成了加锁和解锁
2, 同步不能区分唤醒的是本方的线程还是对方的线程,而锁能够区分出来。原因是
一个锁里可以定义多个唤醒线程的监视器,给自己定义一个,对方定义一个
这样的话就可以唤醒定义监视器的线程了。
3, 同步只要用同步函数或者同步代码块就行了,但锁的话要加锁,如果线程出了问题
那么没解锁的话别的线程也进不去这样就成死锁了,所以一般加lock的话要用
Try{}lfinally{},这样的话如果线程了中断了那么也一定会执行解锁的程序。
4, 同步的对象是任意的,所以方法是Object的,但是锁是lock接口的方法,所以
锁可以用的方法都是在继承了lock接口的子类之中。
jdk1.5以后将同步和锁封装成了对象。
并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口:出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作。
同时更为灵活。可以一个锁上加上多组监视器。 lock():获取锁。 unlock():释放锁,通常需要定义finally代码块中。
Condition接口:出现替代了Object中的wait notify notifyAll方法。
将这些监视器方法单独进行了封装,变成Condition监视器对象。可以任意锁进行组合。
await();
signal();
signalAll();
线程的通信
引子:不同的线程执行不同的任务,如果这些任务有某种联系,线程之间必须能够通信,协调完成工作。例如生产者和消费者
java.lang.object类中提供了用于线程通信的方法
Ø wait():执行该方法的线程释放对象锁和CPU的占用,jvm把该线程放到该对象的线程等待池中。该线程等待其他线程将它唤醒。
Ø notify():执行该方法的线程随即唤醒在对象的线程等待池中等待的一个线程,jvm会把它转到对象的锁池中。
Ø notifyAll():执行该方法的线程会唤醒对象的等待池中的所有线程,jvm会把它们都转到对象的锁池中。
Ø sleep() wait() notifyAll()的差异?
这些方法必须定义在共用同一把锁的同步代码块中,因为是用于操作线程状态的方法。使用时必须要明确到底操作的是哪个锁上的线程。sleep()在锁中等待时是不释放锁的,而wait()在等待时会释放锁。notifyAll()会唤醒在同一个锁内,所有在线程池中等待的线程
线程的通讯:就是多个线程在操作同一资源,但是操作的动作不同
Ø 多线程运行机制:所有可运行线程根据优先级保存在池中,当一个被阻塞的线程变成可运行时,它会被放入相应的可运行池,可运行池中的线程都得到执行机会,一般优先级最高的线程机会最大。
Ø New一个线程之后,该线程对象不会表现出任何线程的动态特征,程序也不会执行线程的执行体。线程对象调用了start()方法之后,该线程就处于就绪状态,但该线程并没有开始运行,它只是表示该线程可以运行了。
面试题:写一个死锁程序
就是同步中嵌套同步
class Mylock
{
static Object locka=new Object();
static Object lockb=new Object();
}
class Test
{
private boolean b;
Test(boolean b)
{
this.b=b;
}
public void run()
{
if(b)
{
while(true)
{
synchronized(Mylock.locka)
{
System.out.printIn("if locka");
synchronized(Mylock.lockb)
{
System.out.printIn("if lockb");
}
}
}
}
else
{
while(true)
{
synchronized(Mylock.lockb)
{
System.out.printIn("else lockb");
synchronized(Mylock.locka)
{
System.out.printIn("else locka");
}
}
}
}
}
}
网络编程
网络
网络:就是把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件、软件、数据信息等资源。
Ø 计算机网络是现代通信技术与计算机技术相结合的产物
Ø 分为局域网(LAN)、城域网(MAN)、广域网(WAN)
Ø 计算机网络可以提供资源共享,信息传输与集中处理,均衡负荷与分布处理,综合信息服务等主要功能。
Internet :是网络的最典型例子。是一个全球的、巨大的网络集合,用于Internet中不同计算机系统的互联通信。
网络模型
l TCP/IP参考模型
TCP/IP:即传输控制协议/网际互联协议,是Internet的基础。只有TCP和IP两者的结合,才能保证Internet在复杂的环境下正常运行,因此常把这两个协议统称作TCP/IP协议。IP获取地址而TCP确保数据送达该地址。
通常分为四层:
应用层向用户提供一组应用程序,如电子邮件、文件传输访问、远程登录等。这一层有着很多协议来支持不同的应用,如万维网访问用HTTP协议、文件传输用FTP、电子邮件发送用SMTP、域名解析用DNS协议、远程登录用Telnet协议等。对用户而言,看到的是由一个个软件所构成的、大多为图形化操作的界面,而后台实际上运行的便是这些协议。
传输层的功能主要是提供应用程序间的通信,包括格式化信息流和提供可靠传输等。这一层的协议有TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议)
网络层是非常关键的一层,负责相邻计算机之间的通信,主要定义了IP地址格式,从而使得不同应用类型的数据在Internet上通畅地传愉。IP协议就是一个网络层协议。
网络接口层是最底层,负责接收IP数据包并通过网络进行发送,或者从网络上接收物理帧,抽出IP数据报,交给IP层。
网络通信原理:两台计算机通信,双方必须有唯一的地址。网络通信必须有硬件和软件方面的支持
l 网络通讯要素:IP地址,端口号,传输协议
IP地址:Internet Protocol
• 用于唯一标识网络中的一个通信实体(通常是计算机)。
• IP协议:又称互联网协议,是支持互联网间互连的数据报协议。IP协议负责将消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个的小包。
• IP地址 = 网络ID(标识所在网段)+主机ID(标识本机)
• 不易记忆,可用主机名 主机名:localhost
• 本地回环地址:127.0.0.1,用于本机测试 特殊IP地址:0.0.0.0:本机
端口号
• 用于标识进程的逻辑地址。一个进程可以有多个端口号
• 端口是程序与外界通信的出入口
• 有效端口:0~65535,其中0~1024系统使用或保留端口。
传输协议
• 通讯的规则
• 常见协议:TCP,UDP
Ø UDP(User Datagram Protocol用户数据包协议)
• 将数据及源和目的封装成数据包中,不需要建立连接
• 每个数据报的大小在限制在64k内
• 因无连接,是不可靠协议
• 不需要建立连接,速度快
Ø TCP(Transmission Control Protocol 传输控制协议)
• 建立连接,形成传输数据的通道。
• 在连接中进行大数据量传输
• 通过三次握手完成连接,是可靠协议
• 必须建立连接,效率稍低
路由交换设备:将数据从一台计算机送到另一台计算机
HTTP
Ø HTTP( Hypertext Transfer Protocol )被称为超文本传输协议,用于控制万维网服务器和本地浏览器之间的超文本信息传输。
HTTP是基于TCP/IP协议的
Ø 自万维网诞生后,为了方便浏览器和服务器之间信息的传愉和控制,决定使用超文本作为万维网文档的标准格式,于是1990年,科学家们制定了能够快速查找这些超文本文档的协议,即HTTP协议。
Ø 该协议可以使浏览器访问速率更加高效,减少网络传输。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示等。
Ø 当用户想浏览一个网站时,只要在浏览器的地址栏中输入网站地址就可以了,例如http://www.baidu.com,该地址就是URL(Uniform Resource Locator ,统一资源定位符)。"http://”代表访问服务器的协议。“www”代表一个web服务器。“baidu.com/”代表Web服务器的域名,或服务器站点的名称。
B/S:浏览器/服务器。浏览器将请求发送给Web服务器,Web服务器对请求进行处理,将响应发回浏览器
C/S:客户端/服务器。客户端向服务器发出请求,服务器处理请求并将响应发送给客户端
Ø 将各种元素集中到一起,信息仓库、用于投递信息的软件以及信息及软件所在的那台机器,它们联合起来便叫作“服务器”
Ø 那些驻留在远程机器上的软件,它们需要与服务器通信,取回信息,进行适当的处理,然后在远程机器上显示出来,这些就叫作“客户”
C/S的核心思想是:系统具有一个用于存储数据的中央信息存储池,它通常存在于数据库,你可以根据需要将它分发给某些人员或机器集群。它可以被修改,井且这些修改将被传播给客户。总之,信息存储池、用于分发信息的软件以及信息与软件所在的机器或机群被总称为服务器。客户机器上的软件与服务器进行通信,以获取信息、处理信息,然后将它们显示在客户机上。
DNS(域名解析系统,Domain Name System)
Ø 将域名映射成IP地址
Ø 本机域名解析文件位置C:\Windows\System32\drivers\etc\hosts
Ø 域名解析过程: 1. 本地hosts文件 2. DNS服务器
HTTP工作原理
从用户在浏览器中输入URL到网页显示,HTTP的上作主要经历以下几个过程。
(1)发送请求:用户输入URL地址后,客户机与服务器建立连接,客户机通过HTTP向Web服务器发送请求,请求格式为统一资源标识符(URL)、协议版本号,后边是MIME信息,包括请求修饰符、客户端信息和其他可能的内容。
〔2)响应请求:Web服务器在接收到用户请求后,将请求资源通过HTTP发送给客户浏览器,响应信息的格式为信息的协议版本号、一个成功或错误的代码,后边是MIME信息,包括服务器信息、实体信息和其他可能的内容。
(3)处理响应:客户端接收服务器返回的响应信息,通过浏览器解析并显示,然后客户机与服务器断开连接。
客户机向服务器的请求消息和服务器向客户机的响应消息都是通过H TTP完成的。如果以上过程某一步出错,那么产生错误的信息将返回到客户端,在浏览器中显示输出。对用户来讲,这些过程都是由HTTP自动完成的,用户只需鼠标单击,然后等待信息显示
HTTP请求和响应格式:格式由一个起始行,一个或者多个头域(包括通用头、请求头、响应头和实体头4个部分),一个指示头域结束的空行和可选的消息体组成。
InetAddress&URL
q java.net包含了所有的用于网络操作的类。通过这些网络类,java程序可以很方便的与互联网通信
q 网络编程的基本模型就是客户机到服务器模型,就是两个进程之间相互通讯,其中一个必须提供一个固定的位置(服务器),而另一个(客户端)则只需要知道这个固定的位置,并建立两者之间的联系,去完成数据的通讯
InetAddress
q 用来封装IP地址和该地址的域名
q InetAddress 类没有明显的构造方法 ,可通过三个方法来创建InetAddress的实例
static InetAddress getLocalHost( )throws UnknownHostException
static InetAddress getByName(String hostName)throws UnknownHostException
static InetAddress[ ] getAllByName(String hostName)throws UnknownHostException
q InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostAddress()+ip.getHostName();
InetAddress baidu = InetAddress.getByName("www.baidu.com");
System.out.println(baidu.getHostAddress()+baidu.getHostName());
URL
Ø URL(Uniform Resource Locator)对象代表统一资源定位器,它是指向互联网“资源”的指针。
Ø URL一般由协议名、主机、端口和资源组成。即protocol://host:port/resourceName例如:http://www. baidu.com/Index.htm
Ø java的URI ( Uniform Resource Identifiers)对象代表一个统一资源标识符,不能用于定位任何资源,它的唯一作用就是解析。而URL包含一个可打开连接该资源的输入流
Ø URL对象可以调用如下方法来访问该URL对应的资源:
String getFile():获取此URL的资源名。
String getHost():获取此URL的主机名。
String getPath():获取此URL的路径部分。
int getPort():获取此URL的端口号。
String getProtocol():获取此URL的协议名称。
String getQuery():获取此URL的查询字符串部分。
URLConnection openConnection():返回一个URLConnection对象,它表示到URL所引用的远程对象的连接。
InputStream openStream():打开与此URL的连接,并返回一个用于读取该URL资源的InputStream。
Socket
引子:历史上的网络编程都要求程序员必须掌握与网络有关的大量细节,有时甚至要对硬件有深刻的认识。一般地,我们需要理解连网协议中不同的“层”(Layer)。而且对于每个连网库,一般都包含了数量众多的函数,分别涉及信息块的连接、打包和拆包;这些块的来回运输;以及握手等等。这是一项令人痛苦的工作。Java最出色的一个地方就是它的“无痛苦连网”概念。有关连网的基层细节已被尽可能地提取出去,并隐藏在JVM以及Java的本机安装系统里进行控制。
Socket
Ø “套接字”或者“插座”(Socket)是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。一个Socket由Ip和端口确定。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。线缆的每一端都插入一个“套接字”或者“插座”里。
Ø Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,并通过Socket产生IO流来进行网络通信。基于TCP/IP的网络通信其实就是Socket间的通信。
Ø 通信的两端都有Socket。在Java中Socket可以理解为客户端或者服务器端的一个特殊的对象 。Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket。
Ø 在Java中,我们创建一个套接字,用它建立与其他机器的连接。从套接字得到的结果是一个InputStream以及OutputStream,以便将连接作为一个IO流对象对待。有两个基于数据流的套接字类:ServerSocket(服务器用它“侦听”进入的连接)和Socket(客户用它初始一次连接)。一旦客户(程序)申请建立一个套接字连接,ServerSocket就会返回(通过accept()方法)一个对应的服务器端套接字,以便进行直接通信。从此时起,我们就得到了真正的“套接字-套接字”连接,可以用同样的方式对待连接的两端,因为它们本来就是相同的!此时可以利用getInputStream()以及getOutputStream()从每个套接字产生对应的InputStream和OutputStream对象。这些数据流必须封装到缓冲区内。
Ø 服务器的全部工作就是等候建立一个连接,然后用那个连接产生的Socket创建一个InputStream以及一个OutputStream。在这之后,它从InputStream读入的所有东西都会反馈给OutputStream,直到接收到行中止(END)为止,最后关闭连接。
Ø 每个基于TCP/IP网络通讯程序都被赋予了唯一的端口和端口号
UDP传输
1. 建立发送端,接收端。(DatagramSocket)注意:发送端和接收端是二个独立的运行程序
Ø 在发送端,要在数据包对象中明确目的地IP及端口。
DatagramSocket ds = new DatagramSocket();
byte[] by = “hello,udp”.getBytes();
DatagramPacket dp = new DatagramPacket(by,0,by.length,InetAddress.getByName(“127.0.0.1”),10000);
ds.send(dp);
ds.close();
Ø 在接收端,要指定监听的端口。
DatagramSocket ds = new DatagramSocket(10000);
byte[] by = new byte[1024];
DatagramPacket dp = new DatagramPacket(by,by.length);
ds.receive(dp);
String str = new String(dp.getData(),0,dp.getLength());
System.out.println(str+"--"+dp.getAddress());
ds.close();
2. 建立数据包。(DatagramPacket)
3. 调用Socket的发送接收方法。
4. 关闭Socket。
UDP聊天程序:
1.通过键盘录入获取要发送的信息
2.将发送和接收分别封装到两个线程中
TCP传输
1. 建立客户端(Socket)和服务器端(ServerSocket), 注意:发送端和接收端是二个独立的运行程序
客户端
Ø 客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。
Ø 连接成功,说明客户端与服务端建立了通道,那么通过IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream(),getOutputStream()获取即可。
Ø 与服务端通讯结束后,关闭Socket。
Ø Socket s = new Socket(“192.168.1.1”,9999);
OutputStream out = s.getOutputStream();//getOutputStream()方法获得输出流去发送消息
out.write(“hello”.getBytes());
s.close();
服务端
Ø 服务端需要明确它要处理的数据是从哪个端口进入的。
Ø 当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。
Ø 当该客户端访问结束,关闭该客户端。
Ø ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept ();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
String str = new String(buf,0,num);
System.out.println(s.getInetAddress().toString()+”:”+str);
s.close();
ss.close();
2. 建立连接后,通过Socket中的IO流进行数据的传输
3. 关闭socket
Tcp传输容易出现的问题
Ø 客户端连接上服务端,两端都在等待,没有任何数据传输。
因为read方法或者readLine方法是阻塞式。
Ø 解决办法:自定义结束标记;使用setSoTimeout()。
容器
引子: 通常说来,如果不知道在解决某个特定问题时需要多少个对象,或它们将存活多久,那么就不可能知道如何存储这些对象。
所以有了针对不同需求、不同操作的集合
数组& Arrays工具类
数组:
Ø 是个引用对象,定长。将数组封装到类中可以保护数组不被随意更改。
Ø 考虑到执行效率和类型检查,应尽可能地采用数组。针一对数组的常见操作包括排序和查找等。但当我们编写程序时,通常并不能确切地知道最终需要多少个对象。有些时候甚至想用更复杂的方式来保存对象,就必须使用集合
Ø 数组中的元素可以是任意类型(基本类型和引用类型),但同一个数组里只能存放类型相同的元素。
Ø 对象数组和基本数据类型数组在使用方法上几乎一致。唯一的差别在于对象数组容纳的是句柄,而基本数据类型数组容纳的是具体的数值
创建数组的大致步骤
1. 声明一个数组类型的引用变量,简称为数组变量。
2. 用new语句构造数组的实例。new语句为数组分配内存,并为数组中的每个元素赋予其数据类型的默认值。
3. 初始化,即为数组的每个元素设置合适的初始值。
Arrays工具类
增加:Arrays.asList(T...a)把指定数组转换成该数组支持的固定大小的列表。注意转换后,元素不可进行增删操作
Arrays.copyOfRange(T[] original,int from,int to)将指定数组的指定范围复制到一个新数组
查找:Arrays.binarySerch(byte[] a,byte key)使用二分搜索法搜索指定的数组,以获得指定值的索引. 在调用该方法时,必须保证数组
中的元素己经按照升序排列,这样才能得到正确的结果。
判断:Arrays.deepEquals(Object[] al,Object[] a2)比较二个指定数组彼此的深层相等
Arrays.equals(Object[] a,Object[] a2)若二个指定Object数组彼此相等,则返回true
修改:Arrays.fill(double[] a,double val)将指定的double值分配给指定double型数组的每个元素
Arrays.sort(int[] a,int fromIndex,int toIndex)对指定int型数组的指定范围进行升序排序
Arrays类中的sort方法需要对数组进行排序,必须首先知道怎么判断数组中两个元素的大小。对于基本类型的数组,非常容易比较大小,但对于对象数组, Arrays类无法判断两个数组元素的大小。Java中通过Comparable接口把比较大小的任务交由对象自身,实现了Comparable接口的对象具有同其他对象比较大小的能力。
集合Collection
集合类的由来
对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定。就使用集合容器进行存储。
集合容器因为内部的数据结构不同,有多种数据结构容器。不断的向上抽取共性,就形成了集合框架。
Collection基本方法
1,添加
boolean add(object):向该集合添加一个元素
boolean addAll(Collection);添加一个集合中的所有元素到该集合。
2,删除
void clear():将集合中的元素全部删除,集合长度变为0。
boolean remove(obj):删除集合中指定的对象。
boolean removeAll(collection c):从集合中删除集合c中的所有元素。
3,判断。
boolean contains(obj):集合中是否包含指定元素。
boolean containsAll(Collection c):集合中是否包含集合c中所有的元素。
boolean isEmpty():集合是否为空。
4,获取
int size():返回集合中元素的个数。
5,取交集
boolean retainAll(Collection c):当前集合和指定集合中的元素是否有交集。
6,将集合变成数组
Object[] toArray();把集合转换成数组,所有集合元素转换成对应的数组元素
7,获取集合中所有元素
Iterator iterator():返回一个迭代器对象,用于遍历集合中的元素。
迭代器
public interface Collection<E> extends Iterable<E>
Ø 每个子集合存储的数据结构不同,所以获取集合中的元素的方式也是不同的,所以抽取了共性内容定义了接口,让子集合去实现。Iteratar接口隐藏底层集合的数据结构,向使用者提供了遍历各类集合的统一接口。
Ø Iterator仅用于遍历集合,Iterator本身并不提供盛装对象的能力。Iteator必须依附于Collection对象,它包含对所属集合的数据结构中的数据项的引用。如果需要创建Iterator对象,则必须有一个被迭代的集合。
Ø 当使用Iterator来迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove方法才能删除上一次next方法返回的集合元素,否则将会引发java.util.ConcurrentModificationException异常。一旦在迭代过程中检测到该集合已经被修改(通常是程序中其他线程修改),程序立即引发ConcurrentModifcationException异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。
Ø 使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何改变。
Ø Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口。Iterator接口里的三个共性方法:
》boolean hasNext():如果被迭代的集合元素还没有被遍历,则返回true
》Object next():返回集合里下一个元素。
》void remove():将迭代器新返回的元素删除。
ListIterator扩充的功能:可以逆序遍历,可以在遍历过程中增加或修改集合元素
Interface List extends Collection
Ø 特性:使用数组实现的List,可以操作索引,可以对元素快速的随机访问
Ø List接口主要有两个实现类:ArrayList和LinkedList
Ø List只能对集合中的对象按索引位置排序,如果希望对List中的对象按其他特定方式排序,可以借助Comparator接口和Collections工具类。
• sort(List list):对List中的对象进行自然排序。
• sort(List list,Comparator comparator):对List中的对象进行comparator参数指定的排序规则排序
Ø java.util.Arrays.asList(T...a)把指定数组转换成该数组的List形式。注意转换后,元素不可进行增删操作。因为所有对List对象的操作都会被作用到底层的数组,而数组长度不能改变,因此不能调用这种List对象的add()和remove ()方法,否则会抛出java.lang.UnsupportedOperationException运行时异常。
特有方法:
1,添加
add(index,element):在指定的索引位插入元素。
addAll(index,collection):在指定的索引位插入一堆元素。
2,删除
remove(index):删除指定索引位的元素。返回被删的元素。
3,获取
Object get(index):通过索引获取指定元素。
int indexOf(obj): 获取指定元素第一次出现的索引位,如果该元素不存在返回-1,通过-1,可以判断一个元素是否存在。
int lastIndexOf(Object o):反向索引指定元素的位置。
List subList(start,end):获取子列表。
4,修改
Object set(index,element):对指定索引位进行元素的修改。
5,获取所有元素
ListIterator listIterator():list集合特有的迭代器。
l List比Collection多了修改的功能,当要对List集合中的数据遍历过程中需要修改数据,那就要用到list里的listIterator(),不然会发生异常。因为子类的特有数据修改时子类最清楚,父类是不知道的。
LinkedList集合的特有方法
1,添加
addFirst(); addLast();
jdk1.6以后 offerFirst(); offerLast();
2,删除
removeFirst():获取并移除,如果链表为空,抛出NoSuchElementException.
removeLast();
jdk1.6以后
pollFirst();/获取并移除,如果链表为空,返回null.
pollLast();
3,获取
getFirst():获取但不移除,如果链表为空,抛出NoSuchElementException.getLast()
jdk1.6以后
peekFirst();获取但不移除,如果链表为空,返回null.peekLast()
如何让ArrayList中的元素不重复?
public class MyArrayList extends ArrayList {
@Override
public boolean add(Object obj) {
// 假设obj的类型都是String
String str = (String)obj;
if(this.contains(str)) {
return false;
}
super.add(obj);
return true;
}
}
Interface Set extends Collection
Ø 特性:set集合中的对象无序,唯一。唯一是通过集合元素的equals()方法比较两个元素是否相等来确定的
Ø Set接口主要有两个实现类:Hashset和TreeSet
HashSet
Ø 内部数据结构是哈希表
Ø 不同步,线程高效但不安全
Ø HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。适用于内容规模较大的元素
Ø 取出时是无序的,随机的。
Ø HashSet的子类LinkedHashSet可以实现元素存储和取出的顺序一致,还能提高插入和删除元素的性能。
Ø 可以用HashSet来自定义存入的数据。需重写hashcode()和equals()方法
Hash算法的功能:
它能保证快速查找到对象。它是通过计算该对象的哈希值然后得到该元素的内存地址,从而快速找到该元素,就类似于数组中的索引。因此,当从HashSet中访问元素时,HashSet先计算该元素的hashCode值(就是hashCod()的返回值),然后到该hashCode值对应的位置去取该元素—这就是HashSet速度很快的原因
HashSet如何保证元素唯一性呢?
q 是通过对象的hashCode和equals方法来完成对象唯一性的。必须保证equals() 比较的结果相等, 则 hashCode() 相等; 而hashCode ()相等, equals() 不一定相等。所以如果元素要存储到HashSet集合中,该元素必须覆盖hashCode方法和equals方法,且应尽量保证两个元素通过equals比较返回true时,它们的hashCode方法返回值也相等,而且元素中用作equals比较标准的属性,都应该用来计算hashCode值。
q 如果对象的hashCode值不同,则不用判断equals方法,就直接存储到哈希表中。如果对象的hashCode值相同,那么要再次判断对象的equals方法是否为true。如果为true,视为相同元素,不存。如果为false,那么视为不同元素,就进行存储。
TreeSet
Ø 内部数据结构是二叉树。
Ø 不同步,线程高效但不安全
Ø 主要是用来给集合中的元素排序用的。
Ø 可以确保集合元素处于排序状态(默认采用自然排序)。自然排序就是TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间大小关系,然后将集合元素按升序排列。
q 如果试图把一个对象添加进TreeSet时,则该对象的类必须实现相比较的功能,否则当添加第二个元素时程序将会引发ClassCastException异常。向TreeSet中添加的应是同一个类的对象,否则调用compareTo(Object obj)方法也会引发C1assCastException异常
q 判断元素唯一性的方式:TreeSet会根据比较方法的返回结果是否为0,为0就是相同元素,不存。
元素默认是自然排序,那么如何让元素自定义排序?
1, 让元素自身具备比较功能,实现comparable接口的comparTo(Object obj)方法。
public class Person(集合元素) implements Comparable {
public int compareTo(Object o) {
自定义比较规则
}
}
2, 给集合增加一个比较器,创建集合对象时传入Comparator接口类型对象。
TreeSet treeSet=new TreeSet(new Comparator(){
public int compare(Object o1,Object o2){
自定义比较规则
}
});
3, 如果同时实现上述二种方式,以比较器Comparator排序为主。
为什么一般需要同时存在两种比较方式?
第一、因为有可能放入的对象本身并没有实现Comparable接口,而使用TreeSet集合的人又需要让对象具有比较功能,因此就必须存在Comparator比较器。
第二、因为对象本身的比较方式不能满足使用TreeSet集合的人的要求,这时候也需要比较器Comparator。
Map集合接口
Ø Map(映射)是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一对键对象和值对象,而值对象仍可以是Map类型,依此类推,这样就形成了多级映射。向Map集合中加入元素时,必须提供一对键对象和值对象
共性方法
1,添加
value put(key,value):返回前一个和key关联的值,如果没有返回null.
2,删除
void clear():清空map集合。
value remove(key):根据指定的key翻出这个键值对。
3,判断
boolean containsKey(key):
boolean containsValue(value):
boolean isEmpty();
4,获取
value get(key):通过键获取值,如果没有该键返回null。当然可以通过返回null,来判断是否包含指定键。
int size(): 获取键值对的个数。
数组和map什么时候使用?
查表思想:当要操作的数据对象很多,且存在对应关系时,可以用Map集合来存储。如果其中一组数据是有序的排列时,可以使用数组来存储,将该组数据当做角标来对应另已一组数据。
Map
为了成功地在HashMap, Hashtable中存储、获取对象,用作key的对象必须实现hashCode方法和equals方法。
常用子类:
|--Hashtable :内部结构是哈希表,同步。不允许null作为键或值。
|--Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
|--HashMap : 内部结构是哈希表,不同步。允许null作为键,null作为值。
|--TreeMap : 内部结构是二叉树,不同步。可以对Map集合中的键进行排序。
List list = new ArrayList();//非同步的。
list = MyCollections.synList(list);//返回一个同步的list.
Properties
HashTable类的子类Properties:一个可以将键值进行持久化存储的对象。
特点:
1,可以持久化存储数据。
2,键值都是字符串。
3,一般用于配置文件。
Ø getProperty(String key):返回系统中键对应的值
Ø Object setProperty(String key , String value):系统属性键值对存储Hashtable集合中。
Ø load():从输入流中读取属性列表(键和元素对),将流中的数据加载进集合。
原理:其实就是将读取流和指定文件相关联。并读取一行数据,因为数据是规则的key=value所以获取一行后。通过=对该行数据进行切割。左边就是键,右边就是值,将键值 存储到properties集合中。
Ø store():将此 Properties 中的属性列表写入输出流。写入各个项后,刷新输出流。
Ø list():将集合的键值数据列出到指定的目的地。
------------------------------------------------------------------------------
//Properties的基本功能:
public static void method()
{
Properties prop = new Properties();
//添加元素:
prop.setProperty("02","zhangsan");
//通过键获取值:
//System.out.println(prop.getProperty("01"));
//获取所有的键值对:
Set<String> keySet = prop.stringPropertyNames();
for(String key : keySet)
{
System.out.println(key+":"+prop.getProperty(key));
}
}
集合图解
ArrayList: 数组结构用equals()
LinkedList: 链表结构用equals()
HashSet: 哈希表结构用 HashCode()和equals()方法
TreeSet: 二叉数。用Comparable的ComparTo()方法或者Compartor的Compar()方法
HashMap: 哈希表结构。用HashCode()和equals()方法。
TreeMap: 二叉数。用Comparable()的ComparTo()方法或者Compartor的Compar()方法。
如果需要对键值储存与读写的顺序一致,那么就需要用到LinkedHashMap。
集合的使用技巧
需要唯一吗?
需要:Set
需要制定顺序吗
需要: TreeSet
不需要:HashSet
需要存储和取出的顺序一致吗?LinkedHashSet
不需要:List
需要频繁增删吗?
需要:LinkedList
不需要:ArrayList
如何记录每一个容器的结构和所属体系呢?
看名字!后缀名是该集合所属的体系。前缀名是该集合的数据结构。而且通常这些常用的集合容器都是不同步的。
l 看到array:就要想到数组,就要想到查询快,有角标.
l 看到link:就要想到链表,就要想到增删快,就要想要 add get remove+frist last的方法
l 看到hash:就要想到哈希表,就要想到唯一性,就要想到元素需要覆盖hashcode方法和equals方法。
l 看到tree:就要想到二叉树,就要想要排序,就要想到两个接口Comparable,Comparator 。
数组与集合、不同集合的区别
集合与数组的区别?
1, 集合能自动改变自身的大小,而数组容量固定。数组通过length()知道它的容量,但不知其实际容纳了多少个元素,而集合可以通过size()知道实际容纳了多少个元素
2, 集合只能存放引用数据,数组基本数据和引用数据都可以存放。但当集合存放基本数据类型时,会自动装箱。
3, 集合可以存放不同类型引用数据或映射关系数据,数组只能放相同类型数据。
4, 数组元素的存储是连续的,集合的存储是多种方式的
Map和Collection的区别?
1. Map按键值对存储. Collection是按个存储.是单列集合
2. Map存储的键值唯一. Collection的List集合元素可以重复
3. Map添加元素的方法是put, Collection添加元素的方法是add
4. Map需要通过Map.keySet()或Map.entrySet()方法获得单列集合,然后才可以遍历. Collection可以直接遍历
ArrayList集合,linkedList集合,Vector集合的区别?
1, ArrayList底层是用数组结构,不安全,查改速度快,因为是用角标的。
2, LinkedList底层是用链表结构,不安全,增删速度快。它每个元素都只记住前后两个元素的地址值,
它也有角标,但是不用角标进行查找增删操作,跟没有一样。
3, Vector是老版本的,已经被ArrayList取代,它安全但效率低,底层也是用数组结构。
Collection和Collections的区别
1.Collection是集合中的顶层接口,而Collections是集合的工具类。
2.Collections类中的方法都是静态的,而Collection接口中的方法则不一定是。
3.Collection接口存在子接口List和Set,是在一个集合体系中,而Collections没有子类。
Collecitons工具类
Collections
添加:Collections.addAll(Collection<? super T>c,T...elements)将所有指定元素添加到指定collection中
Collections.copy(List<? super T>dest,List<? extends T>src)将dest集合的所有元素复制到src集合中
Collections.fill(List<? super T>list,T obj)使用指定元素替换list集合中所有元素
查找:Collections.binarySeach(List<? extends Comparable<? super T>>list,T key)使用二分查找法查找指定集合,获取指定对象的索引
Collections.max(Collection<? extends T>coll,Comparator<? super T>comp)根据指定比较器的顺序,返回给定coll的最大元素
修改:Collections.shuffle(List<?> list,Random rnd)使用指定随机源对指定列表进行置换
Collections.sort(List<T> list,Comparator<? super T> c)使用指定比较器的顺序对list进行排序
Collections.swap(List<?> list,int i,int j)在指定列表的指定位置交换元素
Collections.synchronizedSet(Set<T> s)返回指定set支持的同步set
转化成数组:toArray()可以将集合转化成数组。如果数组定义小了,集合就再定义一个跟集合长度一样的数组把元素放入。如果定义长了,把集合元素放入,余下的角标全部为Null
数据结构
概述
q 数据:信息的载体,可以被计算机识别、存储和处理
q 数据元素:数据的基本单位
q 数据项:数据的最小单位,即数据元素是由若干个数据项组成
数据结构
Ø 数据结构是数据之间的相互关系,即数据在计算机存储空间中或磁盘中的组织形式。数据结构包括数组、链表、栈、二叉树、哈希表等等。正确选择数据结构会使程序的效率大大提高。
Ø 算法是指通过对这些结构中的数据进行各种处理,以完成特定任务的过程。在Java中,算法经常通过类的方法实现。例如数组就是一个数据结构,用for循环来顺序访问该数组,这就构造了一个简单的算法。
Ø 数据结构和算法是有关数据存储和编程的细致入微的基本规则。数据结构是为了加快增删改查的速度
Ø 通用数据结构:数组,链表,树,哈希表,它们通过关键字的值来存储并查找数据。数组和链表速度最慢,树相对较快,哈希表最快。
Ø 对于大多数数据结构来说,都需要知道如何寻找某一特定的数据项,如何插入一条新的数据项,如何删除某一特定的数据项以及如何迭代地访问某一数据结构中的各数据项,以便进行显示或其他操作。
Ø 专用数据结构:栈,队列,优先级队列。这些结构不是为了用户可访问的数据库而建立的
数据的存储结构:在进行数据处理时,所有要处理的数据都要存入计算机的存储器中。
数据的逻辑结构:指的是数据元素之间的关系,不同的关系可分为:集合、线性结构、树形结构、图形结构。同种数据的逻辑结构可以根据需要表现成任意一种或多种不同的存储结构
数据结构的使用场景
Ø 当存储和操作数据时,在大多数情况下数组首先考虑。以下是数据结构的使用图解
ü 当数据量较小且数据量的多少可预测时,用数组。如果插入速度很重要,用无序数组;如果查找速度很重要,用有序数组
当数据量较小且数据量不能预知或需要频繁地插入删除数据元素时,用链表。
ü 当确认数组和链表过慢时,考虑二叉树。哈希表速度最快,这使它成为计算机与数据交互时的必需。
数据库与数据结构
Ø 数据库是指由许多类似的记录组成的数据存储的集合。数据库中的每一条数据都被认为是相同格式的。记录是指数据库划分成的单元,它为存储信息提供了一个结构格式。一条记录通常被划分为几个字段,每个字段存储着由这个记录所描述事物的一条特性(某一种特定类型的数据)。在java中,记录经常被表示为一个相应类的对象,该对象的字段被称为字段。一个关键字是一条记录中的一个字段,通过它可以对数据执行增删改查
Ø 数据库一旦建立,就会根据某些需求对数据进行不同方式的排序。比如对姓名按字母序排序,对国内销售品按价格排序,对城市按人口增长率排序等等。对数据进行排序是检索的一个初始步骤。
数组与链表
数组结构
Ø 为什么不用数组表示一切?仅使用数组似乎就可以完成所有工作,为什么不用它们来进行所有的数据存储呢?因为在无序数组中插入快但查找慢,而有序数组查找快但插入慢。而且数组大小尺寸固定,但通常在开始设计程序时,并不知道会有多少数据项将会被放入
链表
Ø 链表是继数组之后第二种使用得最广泛的通用存储结构。除非需要频繁通过下标随机访问各个数据,否则在很多使用数组的地方都可以用链表代替。
Ø 链表包含一个linkedList对象和许多Link对象。linkedList对象包含一个引用叫fiirst,它指向链表的第一个链结点。每个Link对象包含数据和一个引用,叫next,它指向链表的下一个链结点。next字段为null值意味着链表的结尾。遍历链表是从first开始的,然后通过每个链结点的next字段找到下一个链结点。通过遍历可以找到拥有特定值的链结点,然后可以显示、删除或插入新链结点。
Ø 链表查找效率低。在链表中,查找数据项必须从头开始,依次访问链表中的数据项,直到找到该数据项为止。即使是有序链表还是必须从头开始依次访问数据项,因为链表中不能直接访问某个数据项,必须通过数据项间的链式引用才可以。
Ø 链表增、删效率高。因为当插入和删除链结点时,链表不需要移动任何东西。链表比数组优越的另外一个重要方面是链表需要多少内存就用多少内存,容量可以扩展。
Ø 对于编程而言,链表比数组复杂,但比哈希表或树简单。
栈、队列和优先级队列
概述
Ø 程序员的工具:栈、队列和优先级队列数据结构主要作为构思算法的辅助工具,更多的是作为程序员的工具来运用,而不是完全的数据存储工具。这些数据结构的生命周期比那些数据库类型的结构要短得多。在程序操作执行期间它们才被创建,通常用它们去执行某项特殊的任务;当完成任务之后,它们就被销毁。
Ø 受限访问:在数组中可以访问到各数据项。而堆和堆栈的数据结构,访问是受限的,即在特定时刻只有一个数据项可以被读取或被删除
Ø 更加抽象:栈、队列和优先级队列是比其他数据存储结构更为抽象的结构。主要通过接口对栈、队列和优先级队列进行定义,这些接口表明通过它们可以完成的操作,而它们的主要实现机制对用户来说是不可见的。
Ø 栈、队列和优先级队列是经常用于简化某些程序操作的数据结构。在这些数据结构中,只有一个数据项可以被访问。
Ø 栈允许访问最后一个插入的数据项。栈中重要的操作是在栈顶插入(压入)一个数据项,以及从栈顶移除(弹出)一个数据项。
队列只允许访问第一个插入的数据项。队列的重要操作是在队尾插入数据项和在队头移除数据项。优先级队列允许访问优先级最大的数据项。优先级队列的重要操作是有序地插入新数据项和移除优先级最小的数据项。
栈
Ø 栈只允许访问一个数据项:即最后插入的数据项。移除这个数据项后才能访问倒数第二个插入的数据项,依此类推(后进先出)。大部分微处理器运用基于栈的体系结构。当调用一个方法时,把它的返回地址和参数压入栈,当方法结束返回时,那些数据出栈。栈操作就嵌入在微处理器中。
Ø 例如,许多人在工作时收到信后,会随手将它放在桌子上的信堆上,或把它投入一个“专门的”筐里。等他们有空闲时间时,就会从上到下处理这些堆积的邮件。第一封信处理完后,接下来会处理下一封信,此时它正处于信堆的最上面。按此方法处理下去,直到最后轮到信堆最下面的一封(此时它在信堆的最上面)。只有能在合理的时间内处理完所有的信件,这种“先处理最上面邮件”的工作方法才不会产生麻烦。否则,会发生很久也处理不到栈底信件的危险
Ø 栈的容量通常很小,是临时的数据结构,操作效率高。
Ø 栈往往通过数组或链表实现,通过数组实现更有效率,因为最后被插入的数据总是在数组的最后,这个位置的数据很容易被删除。栈的溢出有可能出现,但当数组的大小被合理的规划后,溢出并不常见,因为栈很少会拥有大量的数据。如果栈拥有许多数据,并且数量不可精确预测时,用链表更好,这是由于从表头的位置删除或插入一个元素很方便。除非整个内存满了,栈的溢出不可能出现。链表比数组稍慢一些,因为对于插入一个新链接必须分配内存,从表中某链接点上删除元素后也需回收分配的内存
队列
Ø 只允许在表的一端进行插入,而在另一端进行删除。被称作先进先出的线性表
Ø 队列类似栈,只是在队列中第一个插入的数据项也会最先被移除(先进先出),而在栈中,操作效率高
Ø 在计算机操作系统里,有各种队列在安静地工作着。当在键盘上敲击时,有一个存储键入内容的队列。同样,如果使用文字处理程序敲击一个键,而计算机又暂时要做其他的事,敲击的内容不会丢失,它会排在队列中等待,直到文字处理程序有时间来读取它。利用队列保证了键入内容在处理时其顺序不会改变。
Ø 同栈相比,队同样可以通过数组和链表实现。这两种方法都很有效率。数组需要附加的程序来处理队在数组尾部回绕的情况。链表必须是双端的,这样才能从一端插入从另一端删除。 用数组还是链表来实现队的选择是通过数据量是否确定来决定的。
优先级队列
Ø 优先级队列是比栈和队列更专用的数据结构。它在很多的情况下都很有用。像普通队列一样,优先级队列有一个队头和一个队尾,并且也是从队头移除数据项。不过在优先级队列中,数据项优先级最大的数据项在队头。数据项插入的时候会按照优先级顺序插入到合适的位置以确保队列的顺序。
Ø 优先级队列用在只对访问最高优先级数据项访问的时候。例如,在抢先式多任务操作系统中,程序排列在优先级队列中,这样优先级最高的程序就会先运行。
二叉树、红-黑树与2-3-4树
二叉树
Ø 为什么要用到二叉树呢?因为它结合了有序数组和链表的优点。在二叉树中查找数据项的速度和在有序数组中查找一样快,并且插入和删除数据项的速度也和链表一样快。
Ø 二叉树中,节点表示保存在树中的数据对象,根是树中最顶端的节点。节点最多有两个子节点。
Ø 二叉搜索树中,所有节点左边子孙节点的值都比该节点小,而右边子孙节点的值都>=该节点。
Ø 查找节点需要比较要找的关键字值和节点值,如果要找的节点值比相比较的节点值小就转向比较的这个节点的左子节点,如果大就转向右子节点。插入需要找到要插入新节点的位置并改变它父节点的子字段来指向它。
Ø 遍历二叉树是按某种顺序访问树中所有的节点。
红黑树
Ø 二叉搜索树如果插入的是随机数据,则执行效果很好。但如果插入的是有序的数据,速度就特别慢。因为当插入的数值有序时,二叉树就是非平衡的了。而对于非平衡树,它的快速查找〔插入,删除)指定数据项的能力就丧失了。
Ø 非平衡树是指根左边的后代比右边多或右边的后代比左边多。这些节点近似排列在一条直线上,大部分节点都在根的一侧,没有分支,这棵树属于不平衡的情况。当树没有分支时,它其实就是一个链表,所以查找速度慢
Ø 红一黑树是增加了某些特点的二叉搜索树,用于解决非平衡树问题,在平衡树中使用最广泛。为了保证树大概是平衡的,红一黑树在插入等过程中会检查不会破坏树的平衡,如果破坏了,程序就会进行纠正,根据需要更改树的结构以保持树的平衡。
Ø 在红一黑树中,每个节点或是黑的或是红色,也可以是任意的两种颜色,节点都有颜色。在插入和删除的过程中,要遵循保持这些颜色的不同排列的规则。它们被称为红一黑规则。如果遵循这些规则,树就是平衡的。
Ø 具体规则是:1.每一个节点不是红色就是黑色。2.根总是黑色的。3.如果节点是红色的,则它的子节点必须是黑色的。4,从根到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点。
2-3-4树
Ø 在二叉树中,每个节点有一个数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树。2-3-4树就是多叉树,它的每个节点最多有四个子节点和三个数据项
Ø 2-3-4树名字中的2、3和4的含义是指一个节点可能含有的子节点的个数。有一个数据项的节点总是有两个子节点。 有两个数据项的节点总是有三个子节点。有三个数抿项的节点益早有四个子节占。
Ø 2-3-4树像红一黑树一样是平衡树。它的效率比红一黑树稍差,但编程容易。更重要的是,通过学习2-3-4树可以更容易地理解B-树。B-树是另一种多叉树,专门用在外部存储中来组织数据(主存储的外部;通常指磁盘驱动器)。B一树中的节点可以有几十或几百个子节点。
哈希表
Ø 哈希表可以提供快速的插入和查找操作,不论哈希表中有多少数据。哈希表运算得非常快,通常用于拼写检查器和作为计算机语言编译器中的符号表。哈希表的速度明显比树快,哈希表不仅速度快,编程实现也相对容易。
Ø 哈希表也有一些缺点:它是基于数组的,创建后难于扩展。某些哈希表被基本填满时,性能下降得非常严重,所以程序员必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。
Ø 而且,哈希表并不能提供任何形式的有序遍历,或对最大最小值元素进行存取。如果这些功能重要的话,使用二叉搜索树更好一些。然而,如果可以提前预测数据量的大小,并且不需要有序遍历数据,那么哈希表在速度和易用性方面是无与伦比的。
Ø 哈希化简介:在哈希表中,把关键字转换成数组下标的过程就是哈希化,是通过哈希函数完成的。然而,对于特定的关键字,并不需要哈希函数,关键字的值可以直接用于数组下标。经典的例子是字典。如果想要把一本英文字典的每个单词,从a到zyzzyva(这当然是一个单词),都写入计算机内存,以便快速读写,那么哈希表是一个不错的选择。
Ø 一个关键字哈希化到已占用的数组单元,这种情况叫做冲突。冲突可以用两种方法解决:开放地址法和链地址法。在开放地址法中,把冲突的数据项放在数组的其他位置。在链地址法中,每个数组单元包含一个链表。把所有映射到同一个数组下标的数据项都插在这个链表中。
Ø 哈希表需要有额外的存储空间,尤其是对于开放定址法。因为哈希表用数组作为基本结构,所以,必须预先精确地知道待存储的数据量。用链地址法处理冲突的哈希表是最健壮的实现办法。若能预先精确地知道数据量,用开放定址法编程最简单。
JDBC
JDBC(Java Database Connective),Java数据库连接,是一组专门负责连接并操作数据库的标准
作用
Ø 完成数据库的连接创建
Ø 传送SQL命令给数据库,完成数据库操作及数据表
Ø 接受和处理数据库所执行的结果
JDK新特性
泛型
Ø 泛型就是参数化类型,是一种类型占位符。当我们把一个对象“丢进”集合中后,系统会把该元素向上转型为Object实例,当把它取出来时,就是获取了一个对Object对象的引用。JDK1.5以后,可以使用泛型类限制集合元素的类型,这样就统一了集合放入的元素类型。
Ø 回顾一下数组:int[] a = {1, 2, 3.0};在编译时就已经提示错误,将问题解决在萌芽时期。那么集合也能否像数组那样,把问题放在编译时、而不是在运行时解决呢?泛型的出现将运行时异常提前到编译时异常,减少了安全隐患
Ø 泛型能够带来的两个明显好处是——类型安全(提高安全性)和减少装箱拆箱、向下转型(提高效率)。
泛型的擦除与补偿
n 泛型的擦除 (编译时)
泛型技术是组编译器使用的技术,用于编译时期确保类型的安全。在编译通过之后,完全转换成*.class文件之前,会进行泛型的擦除。这个过程称为泛型的擦除。
n 为什么要有泛型的擦除?
原因是:泛型是JDK1.5后才出现的产物,而某些市面上还遗留着许多使用JDK1.5以下版本的软件系统,为了兼容这些老系统,所以需要泛型的擦除。
n 泛型的补偿(运行时)
在运行时,通过获取元素的类型自动进行转换动作,这就是泛型的补偿。不需要程序员再进行强制类型转换了。泛型的补偿是能过getClass()实现的。
泛型的使用场景:
1, 方法参数是方法名后小括号里的东西,而泛型接收的参数就是指定的数据类型如类、接口或数组,而使用的方式就是<>
2, 没有泛型之前,ArrayList是这样写的:ArrayList<Object>,而这样子需要向上、向下转型,而ArrayList<E>不用。<E>表示直接接收引用数据类型(如:类、接口或数组),不会涉及到转型
3, 用在类上。如自定义类中可以加入泛型。class Person<E1, E2> {} 这个能看懂不?Person<String, Integer> p = new Person<String, Integer>();
4, 用在函数上。函数的泛型与所属类的泛型一样,则不用在函数中另外声明,如果与类的泛型不一样,就要另在函数中声明泛型,public <E> void method(E e){},<E>必须放在修饰符之后后返回值之前。
静态函数一定要另外声明泛型,因为类的泛型是作用于对象,而静态先于对象存在。Public static <T> void method(T t){}
5, 用在接口上。实现接口的类有具体类型,实现的时候就要传具体类型,如果实现接口的类也不确定泛型的类型时,要继续定义泛型,让调用这个类的程序来传具体的类型。
通配符与<T>的区别
1,“?”通配符不需要声明,但是T是另外声明<T>的
2,T还可以当具体的数据类型使用,但是“?”不行。
泛型的限定:
上限:?extends E:可以接收E和E的子类
用法:Object可以接收任何对象,但前提是只用父类的方法
下限:?super E:可以接收E和E的父类
用法:子类的方法,父类都有,只要子类可以接收的父类都可以接收。
高级for循环&可变参数
增强for循环和传统for循环的区别?foreach可以遍历map吗?
增强for循环简化了书写,但是只能遍历元素.传统for循环可以在遍历的过程中对元素进行操作
高级for循环,只用于集合和数组的遍历,集合只能用Collection不能用Map集合只能把Map集合转化成Set集合,才能用for循环。foreach可以间接遍历map,可以通过map.entrySet()或map.keySet()方法获得单列集合,然后进行遍历。
格式:for(元素类型变量名: Collection集合&数组)
可变参数的使用方式,和注意事项?
类型...变量名
注意事项:一般参数必须定义在可变参数前 public void method(int a,int...arr);
其实可变参数的底层实现就是数组
反射
Class.forName("类名")和类名.class的区别?
每个Class只有在它需要的时候(去创建该类对象)才会载入,而static初始化工作是在类载入时执行的,Class.forName("类名")是方法调用,可能会出现异常。而类名.class不仅更简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以执行的效率也会更高。
枚举
由来:在某些情况下,一个类的对象是有限且固定的,如季节类,它只有四个对象, 这种实例有限而且固定的类,在Java里被称为枚举类。
Ø 枚举类(Enum类)是在Java..lang包下定义个的一个公共类,他的作用是用来构造新的枚举类型
Enum类中的方法values()来的到枚举类型中各个对象的取值范围
Ø JDK1.5新增了一个enum关键字,用以定义枚举类。语法:enum 枚举名{ 枚举值表 };
如何手动实现枚举?
》通过private将构造器隐藏起来。
》把这个类的所有可能实例都使用public static final属性来保存。
》提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例。
枚举类与普通类的区别:
》使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。
》枚举类的构造器只能使用private访问控制符,如果省略了其构造器的访问控制符,则默认使
用private修饰。
》枚举类的所有实例必须在枚举类中显式列出。这些实例系统会自动添加public static final修饰
》所有枚举类都提供了一个values方法,该方法可以很方便地遍历所有的枚举值。
Annotation
引言:Annotation实际上表示的是一种注释的语法,在Java中最早的程序是提倡程序与配置代码相分离,而最新的理论是将所有的配置直接写入到程序之中,那么如果要想完成这样的功能,则就要使用Annotation
注解概念
Ø Annotation 其实就是代码里的特殊标记,在程序中加了注解就相当于为程序打上了某种标记。
Ø 一个注解就是一个类,用到注解就相当于创建了该类对象
Ø 用于替代配置文件,原来写在配置文件中的信息,可以通过注解的属性进行描述。也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类
Ø 要在程序中应用某个注解,就得先定义好这个注解类。使用注解就相当于在该程序中调用一个类
三个基本的Annotation:
• @Override:该注解只能用于方法,表示方法是覆写的操作
• @Deprecated(过时): 用于表示某个程序元素(类, 方法等)已过时,是不建议使用的操作
• @SuppressWarnings: 抑制编译器警告. 如果有一些警告信息,则可以压制掉,不出现警告的提示
Rentention和RententionPolicy
RetentionPolicy中规定了以下的三个范围
⑴ 只在源代码中起作用:public static final RetentionPolicy SOURCE
⑵ 只在编译之后的class中起作用:public static final RetentionPolicy CLASS
⑶ 在运行的时候起作用:public static final RetentionPolicy RUNTIME
自定义Annotation
Ø 定义新的Annotation 类型使用@interface 关键字:public @interface MyAnnotation{}
所有的Annotation自动继承java.lang. annotation.Annotation接口
Ø 声明注解的属性
• Annotation 的属性声明方式:String name();
• 属性默认值声明方式:String name() default “xxx”;
• 特殊属性value:如果注解中有一个名称value的属性,那么使用注解时可以省略value=部分,如@MyAnnotation(“xxx")
• 特殊属性value[];
• 注解支持类型:八种基本数据类型和String、Enum、Class、annotation以及以上类型的数组
Ø 自定义的Annotation编写的时候如果要想让其有意义,则必须使用RetentionPolicy.RUNTIME声明范围
import java.lang.annotation.*;
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
public String key() default "LXH";
public String value();
}
Ø 一个自定义的Annotation可以在任意的位置上使用
示例:@MyAnnotation
public class Info {
@MyAnnotation
private String name ;
@MyAnnotation
public String toString() {
return "hello" ;
}
}
Ø Inherited表示一个Annotation能否被其子类继续继承下去,如果没有写上此注释,则此Annotation根本就是无法继承的
元注解
Ø 元Annotation指修饰Annotation的Annotation。JDK中定义了如下元Annotation:
Ø @Retention: 只能用于修饰一个Annotation 定义, 用于指定该Annotation 可以保留的域, @Rentention包含一个RetentionPolicy类型的成员变量, 通过这个变量指定域。
n RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
n RetentionPolicy.CLASS: 编译器将把注解记录在class 文件中. 当运行Java 程序时, JVM 不会保留注解. 这是默认值
n RetentionPolicy.RUNTIME:编译器将把注释记录在class 文件中. 当运行Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注释
Ø @Target:指定注解用于修饰类的哪个成员. @Target 包含了一个名为value,类型为ElementType的成员变量。
java设计模式
标识类型接口
Ø 标识类型接口没有任何方法,仅代表一种抽象类型。
Ø 在JDK中,有如下两个典型的标识类型接口。
Java.io.Serializable接口:实现该接口的类的实例可以被序列化
Java.io.Remote接口:实现该接口的类的实例可以作为远程对象。
常量接口模式
Ø 在一个软件系统中会使用一些常量,一种流行的做法是把相关的常量放在一个专门的常量接口中定义
定制服务模式
当一个系统能对外提供多种类型的服务时,一种方式是把所有的服务放在一个接口中声明,这个接口臃肿庞大,所有的使用者都访问同一接口:还有一种方式是对服务精心分类,把通用的一组服务放在主接口中,通过对主接口的继承,可以派生出新的接口,即针对使用者的不同需求而提供的特定接口。这种方式有利于接口的重用,通过对接口的继承,可以方便地生成针对特定使用者的复合接口。
适配器模式
Ø 笔记本电脑的变压器,就是典型的电源适配器。笔记本电脑只接受15V的电压,因此不能直接和220V的电源插座连接,电源适配器能够把220V的电压转换为15V,它是连接笔记本电脑和普通电源的桥梁。
Ø 用于电话拨号上网的调制解调器(MODEM)也是一种适配器。电脑只接受数字信号,而电话线上传输的是模拟信号,MODEM能够把模拟信号转换为数字信号,它是连接电脑和电话网络的桥梁。
Ø 松耦合的系统之间通过接口来交互,当两个系统之间的接口不匹配时,就需要用适配器来把一个系统的接口转换为与另一个系统匹配的接口。可见,适配器的作用是进行接口转换。
Ø 在面向对象领域,也采用适配器模式来进行接口的转换。例如Hibernate提供了数据库连接池的默认实现类。此外,Hibernate还可以选用由第三方提供的专业的数据库连接池产品,如C3P0和DBCP连接池软件。但是这些第三方生产的连接池软件都有各自的API,它们并没有实现Hibernate制定的数据库连接池的接口。所以Hibernate为C3P0和DBCP连接池创建了相应的适配器,使得Hibernate能够使用这些连接池
Ø 适配器模式有两种实现方式。
一. 继承加实现方式。下面的SourceInterface和TargetInterface分别代表源接口和目标接口,TargetImpl为适配器,它继承了
Sourcelmpl类,并实现了TargetInterface接口,从而能重用SourceImpl类的adds()方法。
public class SourceImpl implements SourceInterface{
public int add( int a , int b) {return a+b; }
}
public class TargetIrnpl extends SourceImpl implements TargetInterface{
public int addOne( int a , int b) {
return add(a+1); //调用SourceImpl父类的add( int a , int b)方法,进行接口的转换
}
}
二.组合实现方式。下面的TargetImpl为适配器,它实现了TargetTFC接口,并且包装了SourceIFC的实现类,从而能重用Sourcelrnpl
类的add()方法。T}rgetlmpl类对Saurcelrnpl类进行了包装,从而生成新的接口。采用这种实现方式的适配器模式称为包装类模式。
public class TargetIrnpl implements TargetInterface{
private SourceInterface source;
public TargetImpl(SourceInterface source){
this.source=source;
}
public int add( int a , int b) {return source.add(a,b); }
public int addOne( int a) {
return source.add(a,1);
}
}
三、组合比继承更有利于系统的维护和扩展,而且组合关系能将多个源接口转换为一个目标接口,而继承关系只能把一个源接口转换为一个目标接口,因此优先使用组合关系来实现适配器。
默认适配器模式
MouseListener接口用来响应用户发出的鼠标事件。用户可以创建MouseListener接口的实现类,来响应各种鼠标事件。但实现类必须实现所有的抽象方法,而在有些情况下,用户可能只想处理按下鼠标键的事件,忽略其他事件,此时实现其他事件就多此一举了。为了简化编程,JDK为MouseListener提供了一个默认适配器MouseAdapter,它实现了MouseListener接口,为所有的方法提供空的方法体。
代理模式
Ø 在日常生活中,会遇到各种各样的中介机构,比如婚姻介绍所和房产公司等。在这些单位工作的人员均可称为代理人。代理人的共同特征是可以代替委托人去和第三方通信。红娘代替委托人去寻找对象,房产代理人代替委托人去出租房屋。
Ø 代理人可以在第三方和委托人之间转发或过滤消息,但是不能取代委托人的任务。例如房屋租赁人交纳租金,房产代理人不会把它私吞到自己腰包,而是让出租人领取租金;再例如一个男应征者要求.与一个女征婚者约会,红娘本身不会参与约会,只会通知双方约会。
Ø 假如租赁人和出租人能很方便地联系,并且出租人有足够的时间去接待所有潜在的租赁人,那么不经过代理反而会提高办事效率。但假如租赁人和出租人联系很不方便,例如出租人在北京工作,但是有一套在上海的房屋要出租,出租人工作繁忙,没有足够的时间去和所有潜在的租赁人谈判租金,那么房产代理人会方便地为出租人和租赁人建立沟通的桥梁。
Ø 代理模式也可以运用到面向对象的软件开发领域,它的特征是:代理类与委托类有同样的接口。代理类主要负责为委托类预处理消息、过滤消息及把消息转发给委托类等,代理类与委托类之间为组合关系。
单例设计模式
1.饿汉式
2.懒汉式
特点:延时加载,若多线程访问时会出现安全问题,可以加同步解决,锁是该类所属的字节码文件对象
Class Single
{
private static Single s=null;
private Single(){}
public static Single getInstance()
{
if(s=null)//减少判断锁的次数,双重判断稍微提高了效率
{
synchronized(Single.class)
{
if(s=null)
{
s=new Single();
}
}
}
}
}
装饰设计模式
IO中使用到的设计模式:装饰设计模式。用于解决一组类功能增强的问题
装饰设计模式和继承的区别。
举例
1. 在进行数据写入时,因为写入不同的数据,需要封装不同的对象。
Writer
|--TextWriter
|--ImgWriter
2. 发现这个体系中的对象可以使用但效率低,想要提高他们的写入效率,得加入缓冲技术。为了不修改源码,通过面向对象的继承特征,可以在每一个子类中进行子类的扩展。
Writer
|--TextWriter
|--BufferTextWriter
|--ImgWriter
|--BufferImgWriter
3. 现在这个体系已经可以使用了。但若这个体系派生了新的功能写入对象,VideoWriter那就意味着,需要在给这个操作视频的写入对象,定义一个子类,以完成高效写入。
Writer
|--TextWriter
|--BufferTextWriter
|--ImgWriter
|--BufferImgWriter
|--VideoWriter
|--BufferVideoWriter
4. 体系的扩展,都会出现一个带有缓冲的子类,导致继承体系臃肿。而且每一个功能对象的子类功能都是使用缓冲技术,没必要总定义每个功能都相同的子类。只需将这个缓冲功能进行对象的封装,单独描述一个缓冲对象。该缓冲对象是为了增强其他对象的功能而出现的。无论增强哪个具体对象,最终提供的都是增强的写方法。所以BufferWriter具备的还是这个体系的功能,所以它也是这个体系中的一员。
class BufferWriter extends Writer
{
BufferWriter(Writer w)
{
}
}
5. 有了缓冲对象后,这个体系就需要重新设计了。
Writer
|--TextWriter
|--ImgWriter
|--BufferWriter
|--VideoWriter
6. 这样体系就变得简单。而且比继承更具有灵活性。所以将这种方案保留下来,起个名字叫做:装饰设计模式。
注意:装饰类和被装饰类肯定都属于同一继承体系。
迭代器模式
Iterator Pattern是指依序遍历并处理多个数字或变量
用java写程序时,常会用到类函数库,而有些类库用到了设计模式。
类库是用来当作组件的程序,而设计模式则是要构思如何组合组件、如何结合不同的组件以及如何发挥出最大功能。
工厂模式
• 工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类
• 几种形态
– 简单工厂(Simple Factory)模式,又称静态工厂方法模式
– 工厂方法(Factory Method)模式,又称多态性工厂(Polymorphic Factory)模式
– 抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式
单例模式
• 优点
– 提供了对惟一实例的受控访问
– 可以节约系统资源
– 允许可变数目的实例
• 缺点
– 单例类的扩展有很大的困难
– 单例类的职责过重(工厂和产品角色)
– 滥用单例将带来一些负面问题
---------------------- <a href="http://www.itheima.com"target="blank">ASP.Net+Unity开发</a>、<a href="http://www.itheima.com"target="blank">.Net培训</a>、期待与您交流! ----------------------