乔山办公网我们一直在努力
您的位置:乔山办公网 > word文档 > Java 基础知识文档分享给大家,觉得好的话,来波关注,留言发给你们,字数限制不能全发!-word打不开发送错误报告

Java 基础知识文档分享给大家,觉得好的话,来波关注,留言发给你们,字数限制不能全发!-word打不开发送错误报告

作者:乔山办公网日期:

返回目录:word文档

4一、如何学习Java

4二、基础Java

4第1章:Java语言概述

41.1 Java语言的发展历史

61.2 Java程序运行机制

71.3 运行Java程序的JVM

71.4 开发Java的准备

81.5 第一个Java程序

81.6 Java程序的组织形式

91.7 垃圾回收机制

9第2章:理解面向对象

92.1 封装

112.2 继承

112.3 多态

122.4 Java面向对象特征

122.5类和对象

12第3章:数据类型及运算符

133.1 注释

133.2 标识符

143.3 标识符规则

143.4 Java关键字

153.5 数据类型

173.6 基本数据类型转换

183.7 运算符

21第4章:流程控制与数组

214.1 顺序结构

224.2 分支结构

244.3 循环结构

264.4 控制循环结构

284.5 数组类型

294.6 深入数组

344.7 数组的应用

35第5章:面向对象(上)

355.1 类和对象

405.2 Java中的方法

425.3 成员变量和局部变量

445.4 隐藏与封装

465.5 深入构造器

485.6 类的继承

525.7 多态

545.8 继承与组合

585.9 初始化块

60第6章:面向对象(下)

606.1 Java增强的包装类

626.2 处理对象

636.3 类成员

656.4 final修饰符

686.5 抽象类

716.6 接口

766.7 内部类

796.8 枚举类

80第7章:与运行环境交互

807.1 与用户互动

817.2 系统相关

837.3 常用类

877.4 日期处理类

917.5 正则表达式

957.6 国际化与格式化

96第8章:Java集合框架

968.1 集合概述

988.2 Collection和Iterator接口

1018.3 Set集合

1068.4 List集合

1108.5 Queue集合

1138.6 Map

1198.7 操作集合的工具类Collections

125第9章:泛型

1259.1 泛型入门

127第10章:异常处理

12710.1 异常概述

12710.2 异常处理机制

131第11章:输入/输出

13211.1 File类

13311.2 Java的IO流

13411.3 InputStream和Reader

13511.4 OutputStream和Writer

13611.5 InputStreamReader和OutputStreamWriter

13611.6 BufferedReader和BufferedWriter

13711.7 PrintStream和PrintWriter

13711.8 内存流、数据流

13811.9 Object对象流

14411.10 ZipOutputStream、ZipFile、ZipInputStream

14511.11 RandomAccessFile

146第12章:AWT编程

14612.1 GUI(图形用户界面)和AWT

14812.2 AWT容器

15012.3 AWT常用组件

16012.4 布局管理器

17912.5 事件处理

18712.6 AWT菜单

19112.7 AWT中的绘图

206Java小游戏项目

206游戏1:拼图

210游戏2:赛马

216第13章:Swing编程

21613.1 Swing简述

21613.2 MVC设计模式

21613.3 Swing组件体系

21713.4 创建基本的Swing应用程序

21813.5 Swing常用的组件

22613.6 Swing中的特色组件

26413.7 JTextPane和JFormattedTextField创建格式文本

277Swing项目总结

277第14章:MySQL数据库与JDBC编程

27814.1 JDBC基础

一、如何学习Java

1、打牢基础,切勿好高骛远

2、从基础入手、多敲多练

3、按照Java本身的学习规律、踏踏实实的学习

4、扎实的基本功是学习一切Java的关键

二、基础Java

第1章:Java语言概述

1.1 Java语言的发展历史

Java语言历史十多年,已经发展成为人类计算机史上影响深远的编程语言,从某种程度上说,它甚至超出了编程语言的范畴,成为一种开发平台,一种开发规范。

Java语言的诞生具有一定的戏剧性,它并不是经过精心策划、制作而产生的划时代的产品,从某个角度来看,Java语言的诞生完全是一个误会。

在1990年末,Sun公司预料嵌入式系统将会在未来家电器领域中占有重要地位。于是成立了由詹姆斯.高林斯(James Gosling)领导的“Green计划”,准备为下一代智能极爱单编写一个通用的控制系统。

该团队最初考虑使用C++语言,但是很多成员其中包括Sun的首席科学家比尔.乔伊发现C++语言还有可用的API(Application Programming Interface,应用程序编程接口。是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。)在某些方面存在很大问题。而且工作小组使用的是嵌入式平台,可用的系统资源极其有限。并且C++语言太复杂,以至于开发者很容易出错,而且C++缺少垃圾回收机制、可移植性、分布式和多线程等功能。所以比尔.乔伊决定开发一种新语言,它提议在C++的基础上,开发一种面向对象的环境。于是詹姆斯试图修改和扩展C++的功能来满足这个要求,但是后来他放弃了,决定创造一种全新的语言:OaK。

1992年夏天,Green计划已经完成了新平台的部分功能,包括Green操作系统、Oak的程序设计语言、类库等,同年11月,Green计划被转化成为“FirstPerson有限公司”,一个Sun公司的全资子公司。公司致力于创建一种高度互动的设备,当时代华纳公司发布了一个关于电视机顶盒的征求提议书时,FirstPerson改变了他们的目标,作为对征求提议书的响应,提出了一个机顶盒平台的提议,但是有线电视业界觉得FirstPerson的平台给与用户过多的控制权,因此FirstPerson的投标失败给了SGI,同时与3DO公司的另外一笔关于机顶盒的交易也没有成功。

在Green项目几乎夭折的情况下,1994年夏天互联网和浏览器的出现不仅给广大互联网用户带来了福音,同时也为Oak语言带来新的生机。詹姆斯意识到这是一个机会,于是对Oak进行了小规模的改造,到了94年秋天,小组中成员诺顿(Naughton)和乔纳森.佩恩(Jonathan Payne)完成了第一个Java语言的网页浏览器:WebRunner。当时Oak这个商标已被别人注册,于是Java诞生了。

Sun公司在1995年年初发布了Java语言,Sun公司直接把Java放到互联网上,免费给大家使用。甚至源代码也不保留,也放在互联网上向所有人公开。几个月后,让所有人都大吃一惊的事发生了,Java成为互联网上最热门的语言。10多万人次访问了Sun公司的网页,下载Java语言。然后互联网上立即就有数不清的的Java小程序,演示各种小动画、小游戏等。

在Java语言出现之前,互联网的网页实质上就是一张纸,不会由任何动态的内容,有了Java语言之后,浏览器的功能被扩大了,Java程序可以直接在浏览器中运行,可以直接与远程服务器进行交互。

1995年,Sun虽然推出了Java,但这只是一种语言,如果想开发复杂的应用程序,必须要有一个强大的开发类库,因此在1996年年初发布了JDK1.0,这个版本包括两部分:运行环境(JRE)和开发环境(JDK)。运行环境和开发环境共同包括核心API、集成API、用户界面API、发布技术、Java虚拟机(JVM)五部分。而开发环境还包括编译Java程序的编译器,即javac命令。

Sun在1997年2月18日发布了JDK1.1。JDK1.1增加了JIT(即时编译)编译器,JIT和传统的编译器有所不同,传统的编译器是编译一条,运行完成后将其扔掉;而JIT会将经常用到的指令保存在内存中,当下次调用时就不需要重新编译了,通过这种方式让JDK在效率上有了较大提升。一直以来,Java主要的应用就是网页上的Applet以及一些移动设备。到了1996年年底,Flash面世,这是一种更加简单的动画设计软件,使用Flash几乎无须任何编程语言知识,就可以作出丰富多彩的动画。随后Flash还增加ActionScript编程脚本,Flash逐渐蚕食了Java在网页上的应用。

从1995年Java的诞生到1998年年底,Java语言虽然成为了互联网上广泛使用的编程单元,但它并没有找到一个准确的定位,也没有找到它必须存在的理由:Java语言可以编写Applet,而Flash一样可以做到,而且更快,开发成本更低。

直到1998年12月,Sun公司发布了Java历史上最重要的JDK版本:JDK1.2,伴随JDK1.2一同发布的还有JSP/Servlet、EJB等规范,并将Java分成了标准版(J2SE)、企业版(J2EE)和微缩版(J2ME)三个版本。

J2SE就是Java2的标准版,主要用于桌面应用软件的编程;

J2ME主要应用于嵌入式系统开发,如手机和PDA的编程;

J2EE是Java2的企业版,主要用于分布式的网络程序的开发,如电子商务网站和ERP系统。

这标志着Java已经吹响了向企业、桌面和移动3个领域进军的号角,标志着Java已经进入了Java2时代,这个时期也是Java飞速发展的时期。

不仅如此,JDK1.2还把它的API分成了三类:

核心API:由Sun公司制定的基本的API,所有的Java平台都应该提供。这就是Java核心类库。

可选API:这是Sun公司为JDK提供的扩充的API,这些API因平台的不同而不同。

特殊的API:用于满足特殊要求的API。如JCA和JCE的第三方加密类库。

2002年2月,sun公司发布了JDK历史上最为成熟的版本:JDK1.4。此时由于康柏(Compaq)、富士通(Fujitsu)、SAS、塞班(Symbian)、IBM等公司的参与,使得JDK1.4成为发展最快的一个JDK版本。到JDK1.4为止,我们已经可以使用Java实现大多数的应用了。

在此期间,Java语言在企业应用领域大放光彩,涌现了大量基于Java语言的开源框架:Struts、WebWork、Hibernate、Spring等,大量企业应用服务器中间件也开始涌现:WebLogic、WebSphere、Jboss等,这些都标志着Java语言进入了飞速发展时期。

2004年10月,Sun发布了万众期待的JDK1.5,同时,Sun将JDK1.5改名为Java SE5.0,J2EE、J2ME也相应的改名为Java EE和Java ME。JDK增加了诸如泛型、增强for语句、可变数量的形参、注释、自动拆箱和装箱等功能。同时也发布了新的企业级平台规范,如通过注释等新特性来简化EJB的复杂性,并推出了EJB3.0规范。还推出了自己的MVC框架规范:JSF,JSP规范。

2006年12月,Sun公司发布了JDK1.6,一直以来Sun公司维持着大约2年发布一次JDK新版本的习惯。

但在2009年4月20日,Oracle公司宣布将以每股9.5美元的价格收购Sun公司,该交易总价值为74亿美元。而Oracle通过收购Sun公司获得了两项软件资产:Java和Solaris。

同年java之父詹姆斯离开了Sun公司,进入到了Google公司。

Sun倒下了,不过Java的大旗依然在树立着,2007年11月,Google宣布推出了一款基于Linux平台的开源手机操作系统:Android。Android的出现顺应了即将出现的移动互联网潮流,而且Android系统的用户体验非常好,因此迅速成为手机操作系统中的中坚力量。Android平台使用了Dalvik虚拟机来运行.dex文件,Dalvik虚拟机的作用类似于JVM虚拟机,只是它并未遵循JVM规范而已。Android使用Java语言来开发应用程序。这也给Java语言一个新的机会。

2011年7月28日,oracle公司如约发布了Java SE7.0版本,这次版本升级经过了将近5年时间,Java SE7.0也是Oracle发布的第一个Java版本。JavaSE7.0虽然并未完全满足所有人的期望,不过他也加入了不少新的特性,至此Java发展历程到此结束,而我们学习的也是基于JDK1.6版本,目前大多数公司开发也是基于JDK1.6版本进行的。

1.2 Java程序运行机制

Java语言是一种特殊的高级语言,它既具有解释型语言的特性,也具有编译型语言的特征,因为Java程序要经过先编译,后解释两个步骤。

计算机高级语言按程序的执行方式可以分为编译型和解释型两种。

编译型语言是指使用专门的编译器,针对特定的平台(操作系统)将某种高级语言源代码一次性“翻译”成可被执行的机器码,并包装成该平台所能识别的可执行性程序的格式,这个转换过程成为编译。编译生成的可执行程序可以脱离开开发环境,在特定的平台上独立运行。

举例:First.java javac.exe First.class

解释型语言是指使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行的语言。解释型语言通常不会进行整体性的编译,解释型语言相当于把编译型语言中编译和解释过程混合到一起同时完成。

1.3 运行Java程序的JVM

Java语言比较特殊,由Java语言编写的程序需要经过编译步骤,但这个编译步骤并不会生成特定平台的机器码,而是生成一种与平台无关的字节码(*.class)。这种字节码不是可执行的,必须使用Java解释器来解释执行。如下图所示:

Java语言中负责解释执行字节码文件(*.class)的是Java虚拟机,即JVM(Java Virtual Machine)。JVM是可运行Java字节码文件的虚拟计算。JVM是一个抽象的计算机,和实际的计算机一样,它具有指令集并使用不同的存储区域。它负责执行指令,还要管理数据、内存和寄存器。

1.4 开发Java的准备

第一步:下载并安装、配置Java程序需要的JDK(Java SE Development Kit),即Java标准版开发包。它提供了编译、运行Java程序所需要的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库。

甲骨文官方中文版:

下载JDK的地址:

第二步:配置环境变量path

环境变量path,在windows系统下path不区分大小写,在Linux系统下区分大小写,所以一般写成PATH。

Path: C:\\Java\\jdk1.6.0\\bin;

1.5 第一个Java程序

使用记事本书写,保存为HelloWorld.java

public class HelloWorld{

public static void main(String args[]){

System.out.println(“My First Java Program.”);

}

}

注意:文件名要与类名相同

使用Javac命令编译此文件: javac HelloWorld.java,会在当期目录下生成一个与此文件名相同的类文件HelloWorld.class。运行此文件:java HelloWorld

1.6 Java程序的组织形式

Java程序是一种纯粹的面向对象的程序设计语言,因此Java程序必须以类(class)的形式存在,类是Java程序的最小程序单位。Java程序不允许可执行性语句、方法等成分独立存在。

Java程序源文件的命名不是随意的,后缀名必须是.java,在通常情况下Java程序源文件名是可以任意命的,但有一种情况例外:如果Java程序源代码中定义了一个public的类,那么该源文件名必须与该public类的类名相同,所以一个Java源文件里最多只能定义一个public类。

为了提供更好的可读性,建议一个Java源文件只定义一个类,不同的类使用不同的源文件定义;让Java源文件名与源文件中定义的public类同名。

Java语言中是严格区分大小写的语言。

如果需要使用Java解释器直接运行一个Java类,那么这个Java类必须包含main方法,这个方法必须使用public和static类进行修饰。

1.7 垃圾回收机制

传统的C/C++等编程语言,需要程序员负责回收已经分配出去的内存。显示进行垃圾回收是一件令人头疼的事情,因为程序员并不总是知道内存应该何时进行释放。如果一些分配出去的内存不能及时的回收就会引起系统运行速度下降,甚至导致程序瘫痪,这种现象称为内存泄露。

与C/C++语言不通,Java语言不需要程序员自己去控制内存回收,Java程序的内存分配和回收都是由JRE在后台自动进行的。JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收机制(Garbage Collection,也被称为GC)。通常JRE会提供一个后台线程来进行检测和控制,一般都是在CPU空闲或者内存不足时自动进行垃圾回收。

Java的堆内存是一个运行时数据区,用来保存类的实例对象,Java虚拟机的堆内存中存储着正在运行应用程序所建立的所有对象,这些对象不需要程序通过代码来显示地释放。所以GC回收的是Java堆内存空间。当一个对象不再被引用的时,内存回收它占领的空间。

垃圾回收的潜在缺点是它的开销影响程序性能,Java虚拟机必须跟踪程序中有用的对象,才能确定哪些对象是无用的,并最终释放这些无用的对象,这个过程需要花费处理器的时间。

垃圾回收机制的工作目标是回收无用对象的内存空间,这些内存空间都是JVM堆内存里的内存空间。

为了更快的让垃圾回收机制回收那些不再使用的对象,可以将对象的引用变量设置为null。

垃圾回收发生的不可预知性,有可能是当CPU空闲是发生,也有可能等到内存消耗出现极限时发生。

垃圾回收的精确性主要包括两个方面:一是垃圾回收能够精确的标记活着的对象;二是垃圾回收器能够精确的定位对象之间的引用关系。

现在的JVM有多种不同的垃圾回收实现,每种回收机制因其算法差异而表现各异。

第2章:理解面向对象

Java语言是纯粹的面向对象的程序设计语言,主要表现为Java完全支持面向对象的三种基本特性:封装、继承和多态。

2.1 封装

封装:将对象的实现细节隐藏起来,然后通过一些公用的方法来暴露该对象的功能。

举例:

package com.langsin.test;

publicclass Car {

private String name;

private String color;

private String speed;

public String getName() {

return name;

}

publicvoid setName(String name) {

this.name = name;

}

public String getColor() {

return color;

}

publicvoid setColor(String color) {

this.color = color;

}

public String getSpeed() {

return speed;

}

publicvoid setSpeed(String speed) {

this.speed = speed;

}

publicvoid run(){

System.out.println("车的名字:"+this.getName());

System.out.println("车的颜色:"+this.getColor());

System.out.println("车的时速:"+this.getSpeed());

}

publicstaticvoid main(String args[]){

Car car = new Car();

car.setColor("绿色");

car.setName("解放");

car.setSpeed("60码/时");

car.run();

}

}

输出结果:

车的名字:解放

车的颜色:绿色

车的时速:60码/时

2.2 继承

继承:继承是面向对象实现软件重用的重要手段,当子类继承父类后,子类将直接获得父类的属性和方法。

举例:

package com.langsin.test;

publicclass BmwCar extends Car {

publicstaticvoid main(String[] args) {

BmwCar car = new BmwCar();

car.setColor("黑色");

car.setName("BMW");

car.setSpeed("300码/时");

car.run();

}

}

输出结果:

车的名字:BMW

车的颜色:黑色

车的时速:300码/时

2.3 多态

多态:多态指的是子类的实例对象可以直接赋给父类类型的变量,但运行时依然体现出子类的行为特征,这就意味着同一个类型的对象在执行同一个方法时,表现出多种行为特征。

存在多态的条件:

1:有继承

2:要有重写

3:父类引用指向子类对象

举例:

publicclass TestCar {

publicstaticvoid main(String[] args) {

Car carOne = new BenzCar();

carOne.setColor("蓝色");

carOne.setName("奔驰");

carOne.setSpeed("300码/时");

carOne.run();

System.out.println("-----------------------------");

Car carTwo = new BmwCar();

carTwo.setColor("黑色");

carTwo.setName("BMW");

carTwo.setSpeed("300码/时");

carTwo.run();

}

}

输出结果:

车的名字:奔驰

车的颜色:蓝色

车的时速:300码/时

-----------------------------

车的名字:BMW

车的颜色:黑色

车的时速:300码/时

2.4 Java面向对象特征

在Java语言中,除了8个基本的数据类型外(byte、short、int、long、char、boolean、float、double),一切都是对象,而对象就是面向对象程序设计的中心。

对象具有状态,一个对象用数据值来描述它的状态。对象实现了数据和操作的结合,对象把数据和对数据的操作封装成一个有机的整体。对象是Java程序的核心,所以Java里的对象具有唯一性。

2.5类和对象

具有相同或相似性质的一组对象的抽象化就是类,类是对一类事物的描述,是抽象的、概念上的定义,对象是实际存在的该类事物的个体。对象的抽象化是类,类的具体化就是对象,也可以说类的实例时对象。

Java语言使用class关键字定义类,Java语言使用extends关键字来表示继承关系。使用new关键字来创建指定类的具体实例对象。

举例:

Public class Car{

}

Public class BmwCar extends Car{

}

Car car = new Car();

第3章:数据类型及运算符

Java语言是一门强类型语言,强类型语言包含两方面含义:1、所有的变量必须先声明后使用;2、指定类型的变量只能配置指定类型的值。

举例:Car car = null; car = new Car();

car.run();

3.1 注释

单行注释:Java语言中使用双斜杠(//)放在要注释的内容之前。

多行注释:使用“/*”和“*/”将程序中需要注释的内容包括起来。

文档注释:文档注释是已“/**”开始,以星号紧跟一个斜杠(*/)结尾。

举例:

package com.langsin.test;

publicclass Car {

//定义String类型的变量表示车的名称

private String name;

//定义String类型的变量表示车的颜色

private String color;

/*

* 多行注释,可进行换行,注释既可以描述代码含义

* 也可以注释代码块,使被注释的代码不在被执行

*/

private String speed;

/**

* Description <br>

* 获取车的名称

* @authorpengjianming

* @version 1.0

* */

public String getName() {

return name;

}

}

3.2 标识符

Java语言里的分号(;)、花括号({})、小括号(())方括号([])、空格、圆点(.)都具有特殊的分隔作用因此被统称为分隔符。

举例1:

int age = 25; String name = “zhangsan”;

举例2:

Public void run(){

//花括号用来包含代码块,代码块在逻辑上是一个整体。

}

举例3:

privateinta[] =newint[4];//定义一个4个长度的int类型的数组对象,数组里面必须存放int类型的数值

a[0] = 1; //数组的访问是以索引值(index)开始的,也称为下标,索引(下标)是以0为起始值的。

举例4:

圆括号是一个功能丰富的分隔符,定义方法是必须使用圆括号来包含形参声明,调用方法是也必须使用圆括号来传入参数;例如:

Class类:Car

private String name;

public void setName(String name){

this.name = name;

}

public static void main(String args[]){

Car car = new Car(); //生成类的实例对象时也必须带有小括号,默认的动作是调用类的构造方法。

car.setName(“BMW”);

}

举例5:

Java语言使用空格分隔一条语句的不同部分,如上例所示。

举例6:

圆点通常用做(类/对象)和它的成员之间的分隔符,表明调用某个类或者某个实例的指定成员。

3.3 标识符规则

标识符就是用于给程序中变量、类、方法名的命名的符号,因为Java是区分大小写的所以abc和Abc是两个不同的标识符,规则如下:

标识符可以由字母、数字、下划线(_)和美元符号($)组成,其中数字不能打头。

标识符不能是Java的关键字和保留字,可以包含关键字和保留字,例如String abcString = “abc”;

标识符不能包含空格。例如:String a bc = “123”;

标识符只能包含美元符号($)不能包含其他特殊字符,如:@ #

3.4 Java关键字

Java语言中有一些具有特殊用途的单词被称为关键字,定义标识符时不要让标识符和关键字相同,否则会引起错误。Java的关键字都是小写,Java一共包含48个关键字。

3.5 数据类型

Java语言支持的数据类型分为两类:基本数据类型和引用类型

1、基本数据类型共8中,分为布尔类型(boolean)和数值类型,数值类型又分为整数类型和浮点类型。整数类型包括byte、short、int、long、char,浮点类型包括float(单精度浮点)和double(双精度浮点)

2、引用类型包括类、接口、和数组类型,还有一种特殊null类型。引用数据类型就是对一个对象的引用,对象包括实例和数组两种。空类型(null)就是null值类型,这种类型没有名称,所以不能声明一个null类型的变量或者转换到null类型。

3.5.1 整型

byte:byte(字节)类型的整数在内存里占8位,表示的范围是:-128(-2的7次方) ~ 127 (2的7次方-1)

short:short(2个字节长度)类型整数在内存里占16位,表示范围-32768 ~ 32767

int:int(4个字节长度)类型在内存中32位,表示范围-2147483648 ~ 2147483647

long:long(8个字节长度)类型在内存中64位,表示范围-9223372036854775808 ~ 9223372036854775807

int类型是常用的整数类型,Java整数常量默认的就是int类型。使用byte或者short类型时,整数的范围不能超过类型的范围,Java直接将整数常量当成byte或者short类型。例如:

byte a = 24; short b = 1255;

但是使用几个巨大的整数常量时,Java不会自动把整个整数常量当成long类型,所以需要在整数常量后面加上L作为后缀,例如

long a = 99999999999999999L;

Java中整数常量有4种表示方式:十进制,二进制(0b或者0B开头),八进制(以0开头),十六进制(以0x或者0X开头)。

举例:int octalValue = 013; //八进制转换成十进制表示11

int hexValue1 = 0X13; //十六进制转换成十进制表示19

int hexValue2 = 0XAF; //十六进制转换成十进制表示175

int binValue1 = 0B11011; //二进制转换成十进制表示27(JDK1.7中新增特性)

3.5.2 补码、反码、原码

所有的数字在计算机底层都是以二进制形式存在,但是计算机以补码的形式保存所有的整数。

定义32位的二进制整数时,最高位其实是符号位。符号位是1表示负数,0表示正数

举例:

int a = 0B10000000000000000000000000000011; //这是一个补码表示-2147483645

原码:直接将一个数值换算成二进制数。

补码:正数的补码和原码完全相同,负数的补码是其反码加1。

反码:是对原码按位取反,只是最高符号位保持不变。

举例:

1000 0000 0000 0000 0000 0000 0000 0011 为补码,要获得它的具体指需要先获取它的反码

负数的补码是其反码+1,那么其反码就是补码-1;

1000 0000 0000 0000 0000 0000 0000 0010 为反码,原码是对反码取反

1111 1111 1111 1111 1111 1111 1111 1101 为原码,//-2147483645

3.5.3 字符型

字符型常用于表示单个字符,字符常量必须使用单引号括起来,例如:char a = ‘a’;char类型的值也可以直接作为整数类型的值来使用,是一个16位的无符号整数,范围是0 ~ 65535 表示方式如下:

直接通过单个字符来指定字符常量,例如’A’‘a’‘0’

通过转义字符表示特殊字符型常量,例如:’\\n’‘\\t’

直接使用Unicode值来表示字符常量,例如:’\\\\uXXXX’,XXXX表示一个十六进制的整数。

举例:

char aChar = 'a';

char enterChar = '\\t';

char ch = '\\\\u667F';

char name = '张';

int value = name;

System.out.println(aChar);

System.out.println(enterChar);

System.out.println(ch);

System.out.println(value);

3.5.4 浮点型

Java浮点类型:float和double。float占4个字节(byte)32位,double占8个字节(byte)64个长度。

float类型的常量后面必须以f或者F结尾,例如:float a = 12.55f; double类型的常量后面可以d或者D结尾,但是不强制追加,不写也没有问题。

特殊情况:正无穷大、负无穷大、非数分别用基本数据类型对应的封装类来表示,

浮点数的表示方式分为两种:

十进制方式 float a = 12.55f;

科学计数法形式: float a = 12.5e2f;

举例:

floata = 12.555f;

doubleb = 12.55;

floata = 12.55e5f;

floatc = Float.NaN; //不是一个数

floatd = Float.NEGATIVE_INFINITY; //负无穷大

inte = Integer.MIN_VALUE; //最大值

3.5.5 布尔型

布尔型只有一个boolean类型,用于表示逻辑上的“真”或者“假”,数值只能是true或者false

举例:

boolean a = true; boolean b = false;

常用在逻辑判断上,例如:

if(a){

System.out.println(“条件为真”);

}else{

System.out.println(“条件为假”);

}

3.6 基本数据类型转换

Java程序中,不同的基本类型的值经常需要进行相互转换,Java提供了两种类型转换方式:自动类型转换和强制类型转换。

3.6.1、自动类型转换

将一种基本数据类型的值直接赋给另一种基本数据类型,则这种方式被称为自动类型转换。前提是把一个范围小的数值或变量直接赋值给另一个数据范围大的变量时,系统可以进行自动类型转换。

举例:

byte a = 25;

shortb = a;

longc = a;

float d = a;

System.out.println(d);

注意:不能将数据范围大的变量或数值赋值给范围小的变量,例如:int a = 25; byte b = a; 会直接报错。

换行顺序:byte->short->int->long->float->double

char->

3.6.2、强制类型转换

将大范围的数据向小范围的数据进行转换则需要进行强制转换,语法格式为:(targetType)value;

例如:

float d = 12.5F;

inte = (int)d; //变量e的值为整数12,不会保留小数

如果大范围的数据值超过了小数据的的范围,则按位进行截取。

int a = 327775;

short b = (short)a;

System.out.println(b); //输出结果95

3.7 运算符

运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。Java语言使用运算符将一个或多个操作数据连接成执行性语句,用以实现特定功能。

算术运算符

赋值运算符

比较运算符

逻辑运算符

位运算符

类型相关运算符

3.7.1 算术运算符

加法运算(+)、减法运算(-)、乘法运算(*)、除法运算(/)、取余(取模)运算(%)、自加运算(++)、自减运算(--)、(+)还可以做为字符串的连接符。

举例:

int a = 12;

int b = 24;

System.out.println("加法运算:"+(a+b)); // 36

System.out.println("减法运算:"+(a-b)); //-12

System.out.println("乘法运算:"+a*b); //288

System.out.println("除法运算:"+a/b); //0

System.out.println("取模运算:"+a%b); //12

int c = --a;

System.out.println(c); //11

System.out.println(a); //11

c = b--;

System.out.println(c); //24

System.out.println(b); //23

3.7.2 赋值运算符

Java使用“=”作为赋值运算符,为变量指定变量值。举例:

int a = 5; String b = “abc”;

3.7.3 位运算符

Java支持的运算符有7个:按位与(&)、按位或(|)、按位非(~)、按位异或(^)、左移运算符(<<)、右移运算符(>>)、无符号右移运算符(>>>)

基本运算法则:

按位非运算,举例:

Int a = 19; System.out.println(~a); //输出结果为-20

运算原理:19的二进制表示方式为:00010011 按位非运算为:11101100 按位计算后的值为补码,(计算机都是按照补码进行表示的),根据补码获取它的反码,前面为1表示为负数,负数的反码是:补码减1,因此其反码为11101011,其原码为10010100,转换成十进制为-20。

右移运算:Java的右移运算符有两个>>和>>>,对于>>运算符,把第一个操作数的二进制码向右移动指定位数后,左边空出来的位数以原来的的符号位填充,如果是正数用0填充,如果是负数用1填充,>>>运算符移动后,只用0填充。

举例:

-5>>2

-5的原码是1000 0101,它的反码为:1111 1010,它的补码为:1111 1011,右移两位为:1111 10,前面用符号位填充为:1111 1110(补码)。将补码转换成十进制数—》先变成反码:1111 1101,其原码为:1000 0010,表示未十进制数为:-2。

-5>>>2用32表示(原因:计算自动转换类型为int,int类型为32位)

-5的原码为:1000 0000 0000 0000 0000 0000 0000 0101,

其反码为:1111 1111 1111 1111 1111 1111 1111 1010

其补码为:1111 1111 1111 1111 1111 1111 1111 1011

右移后为:0011 1111 1111 1111 1111 1111 1111 1110(补码)

其原码为:0011 1111 1111 1111 1111 1111 1111 1110

转换成十进制为:2的30次方减2为:Math.pow(2,30)-2。

3.7.4 扩展后的赋值运算符

赋值运算符可以与算术运算符、位移运算符结合,扩展成功能更加强大的运算符。

+=:对于x += y,表示 x = x + y;

-=:对于 x-= y,表示 x = x-y;

*=: x*= y,表示 x = x * y;

/= ; %=; &=; |=; ^=; <<= ; >>=; >>>=;

3.7.5 比较运算符

比较运算符用于判断两个变量或者常量的大小,比较运算的结果是一个boolean值(true或者false)

>:大于,只支持左右两边操作数是数值类型。

>=:同上

<:同上

<=:同上

==:同上,即使他们数据类型不相同,只要它们的值相同,也返回true,例如:97==’a’,返回true

!=:不等于,如果进行比较的两个操作数都是数值类型,只要值不同就返回true,如果比较的是引用类型,只有当两个引用变量相同类型的实例时才可以比较,只要两个引用指向不是同一个对象就返回true,

举例1:

Car car1 = new Car();

TestCar car2 = new TestCar();

boolean result = (car1 ==car2); //两个类型不同不能进行比较

举例2:

Car car1 = new Car();

Car car2 = new Car();

boolean result1 = car1 !=car2;

boolean result2 = car1 ==car2;

System.out.println(result1); //输出结果为true

System.out.println(result2); //输出结果为false

举例3:

Car car1 = new Car();

Car car2 = car1;

boolean result = car1 ==car2;

System.out.println(result); //输出结果为true

3.7.6 逻辑运算符

逻辑运算符用于操作两个布尔型的变量或常量,逻辑返回的值为boolean类型,主要有6个,

&&:前后两个操作数都为true时,返回true,否则返回false,例如:32>24 &&“abc”==”abc”,返回true

||:前后两个操作数,有个为true就返回true,举例:32>24 || “abc”!=”abc”,返回true

&(不短路与),|(不短路或),!(非),^(异或)

3.7.7 三目运算符

三目运算符只有一个:(?:),举例:int a = 5>3 ? 6 : 7;//成立输出6不成立输出7

3.7.8 运算符的优先级

//从高到底的

第4章:流程控制与数组

任何一门编程语言都会提供两种基本的流程控制结构:分支结构和循环结构。其中分支结构根据条件来选择性地执行某段代码,循环结构则用于实现根据循环条件重复执行某段代码。

Java中的分支结构语句为:if、switch

Java中的循环结构语句为:while、do while、for、foreach

Java中提供了break、continue来控制程序的循环结构

4.1 顺序结构

顺序结构就是程序从上到下逐行的执行,中间没有任何判断和跳转。

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

int a = 3;

int b = 5;

int c = a+b;

System.out.println("输出结果:"+c);

}

}

4.2 分支结构

Java语言使用if语句和switch语句来进行分支控制,其中if语句使用boolean表达式或boolean值来作为分支条件进行分支控制;switch语句则用于对多个整型值进行匹配,从而实现分支。

4.2.1 if条件语句

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

boolean flag = 5>3;

//使用变量,此种方式为常用方式

if(flag){

System.out.println("条件为真执行");

}

//使用常量

if(true){

System.out.println("使用常量,此代码永远会被执行");

}

//使用表达式,此种方式为常见使用方式

if(5>3){

System.out.println("表达式的结果为true时,代码会被执行");

}

//完整的if分支结构

int a = 5;

int b = 8;

if(a>b){

System.out.println("a的值大于b的值");

}else{

System.out.println("a的值不大于b的值");

}

//分支的嵌套使用

if(a>b){

if(b>0){

System.out.println("a的值大于b的值,并且b的值大于0");

}

}

//此代码段等价于上面的代码

if(a>b && b>0){

System.out.println("a的值大于b的值,并且b的值大于0");

}

//完整结构下的嵌套

if(a>b){

if(b>0){

System.out.println("a的值大于b的值,并且b的值大于0");

}else{

System.out.println("a的值大于b的值,但是b的值不大于0");

}

}else{

if(a>0){

System.out.println("a的值不大于b,并且a的值大于0");

}else{

System.out.println("a的值不大于b,并且a的值不大于0");

}

}

//if语句串联使用

if(a>b){

System.out.println("a的值大于b的值");

}elseif(a>0){

System.out.println("a的值不大于b的值,并且a的值大于0");

}

}

}

4.2.2 switch分支语句

Switch语句由一个控制表达式和多个case标签组成,表达式的数据类型只能是byte、short、int、char四个整数类型和枚举类型。

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

int a = 5;

switch (a) {

case 1:

System.out.println("当key值为1时的运行代码段");

int x = 2;

int y = 3;

int z = x + y;

System.out.println("计算加法后的结果:"+z);

break;

case 2:

System.out.println("当key值为2时的运行代码段");

int x1 = 2;

int y1 = 3;

int z1 = x1 - y1;

System.out.println("计算减法后的结果:"+z1);

break;

default:

System.out.println("当key值不符合任意条件时,默认执行的代码段");

break;

}

}

}

4.3 循环结构

循环语句可以在满足循环条件的情况下,反复执行某一段代码,被重复执行的代码段称为循环体,在反复执行循环体的时候,需要在合适的情况下将条件改为假,否则循环将一直执行下去,形成可怕的死循环。

4.3.1 while循环语句

当while条件为真(true)时,执行循环体中的代码

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

int count = 1;

while(count<10){

System.out.println("当前值为:"+count);

count++;

}

}

}

4.3.2 do while 循环语句

do代码段中为循环体,先执行循环体中的代码,然后再去判断条件是否为真(true),如果为真再执行do代码段中的循环体。

举例2:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

int count = 1;

do {

System.out.println("当前的数值为:"+count);

count++;

} while (count<10);

}

}

4.3.3 for循环

For循环体是更加简洁的循环语句,在大部分情况下for循环可以代替while循环、do while循环。

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

for(int i=0;i<10;i++){

System.out.println("当前的数值为:"+i);

}

System.out.println("---------------for循环结束---------------");

}

}

4.3.4 foreach循环

Foreach循环更加简洁,循环的必须是个集合或者数组。For表达式中定义的变量执行了数组或集合中的每一个具体的值。

举例:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

System.out.println("--------------开始for循环------------");

int num[] ={11,20,33,40,55};

for(int i : num){

System.out.println("当前数值为:"+i);

}

System.out.println("---------------for循环结束---------------");

}

}

4.4 控制循环结构

Java语言提供了continue语句和break语句来控制循环结构,除此之外还可以使用return语句来结束整个方法体。

4.4.1 break结束循环

在代码体中,一旦执行到break语句,那么就结束整个循环,跳出循环体,不在执行,即使还没有执行完成也将不再执行。

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

for(int i=0;i<10;i++){

System.out.println("当前执行第"+(i+1)+"次");

if(i==5){

break;//当执行第6次的时候跳出循环体

}

}

System.out.println("循环结束");

}

}

4.4.2 continue结束本次循环

与break有点类似,区别是终止后面代码的执行,跳出本次循环,继续执行下一次循环。

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

for(int i=0;i<5;i++){

if(i==2){

continue;

}

System.out.println("当前执行第"+(i+1)+"次");

}

System.out.println("循环结束");

}

}

4.4.3 return结束方法

return关键字,并不是专门用于结束循环体的,return的功能时结束一个方法。return关键字后面还可以跟变量、常量、表达式用于对方法的返回值。

举例1:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

for(int i=0;i<5;i++){

if(i==2){

return;

}

System.out.println("当前执行第"+(i+1)+"次");

}

System.out.println("循环结束");

}

}

举例2:

package com.langsin.test;

publicclass TestCar {

publicint getNum(int a,int b){

return a+b;

}

publicstaticvoid main(String[] args) {

TestCar test = new TestCar();

int result = test.getNum(5, 6);

System.out.println("计算后的结果为:"+result);

}

}

4.5 数组类型

数组是编程语言中最常见的一种数据结构,可以存储多个数据,每个数组元素存放一个数据,通过数组元素的索引(或者称为下标index)来访问数组元素。数组也是一种类型,它是引用类型。

4.5.1 定义数组

Java语言支持两种语法格式来定义数组:

举例1:

int a[]; int[] a; //int类型的数组,只能放int类型的值

int a[] = newint[5] ;

a[0] = 10; //如果a[0] = 12.5f;则报错,因为12.5f是float类型的常量

System.out.println(a[0]);

基本数据类型、引用数据类型都可以定义数组,举例

bouble b[] ; Car car[];

4.5.2 数组的初始化

举例1:只初始化数组的长度,不带有默认值

int a[] = new int[10];

举例2:初始化长度,同时带有默认值

int a[] = new int[]{11,12,13,14,15};

或者 int a[] = {11,12,13,14,15};

4.5.3 使用数组

数组常用的方法就是访问数组元素,包括对数组元素进行赋值、取出数组元素的值。访问数组元素都是通过在数组引用变量后紧跟一个方括号[],方括号中是数组元素的索引值,索引值都是从0开始的。一个数组的索引值最大等于这个数组的长度减1。

举例1:int a[] = new int[10],这个int类型的数组长度为10,那么取第10个数就是a[9],如果编写代码的时候写成了a[10],编译不会出错,但是在运行时会出现异常:java.lang.ArrayIndexOutOfBoundsException:异常,这个就是数组索引越界异常。

举例2:

package com.langsin.test;

publicclass TestCar {

publicstaticvoid main(String[] args) {

int num[] = {11,22,33,44,55} ;

for(int i=0;i<num.length;i++){

System.out.println("当前数值中的第"+(i+1)+"元素的值为"+num[i]);

}

}

}

输出结果为:当前数值中的第1元素的值为11

当前数值中的第2元素的值为22

当前数值中的第3元素的值为33

当前数值中的第4元素的值为44

当前数值中的第5元素的值为55

举例3:使用foreach进行遍历

publicstaticvoid main(String[] args) {

int num[] = {11,22,33,44,55} ;

int i=1;

for(int n : num){

System.out.println("当前数值中的第"+i+"元素的值为"+n);

i++;

}

}

4.6 深入数组

数组是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的。

4.6.1 内存中的数组

数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当引用指向有效内存后,才可通过该数组变量来访问数组元素。

举例1:int a[] = {1,3,5,4} a[0]的值为1, int a[] = null; a[0]则报错。

数组对象被存储在堆内存中(heap),而引用该数组对象的数组引用变量如上例中的a变量,则被存储在栈内存中(stack)。如下图:

当一个方法执行时,每个方法都会建立自己的栈内存,这个方法中定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也就会自然销毁,因此所有在方法中定义的局部变量都是放在栈内存中的,当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用,这是因为对象的创建成本通常是比较大的,这个运行时数据区就是堆内存。堆内存中的对象不会随着方法的结束而销毁,即使方法结束后,这个对象可能被另一个变量引用,则这个对象依然不会存在。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制(GC)才会在合适的时候回收它,即销毁它。

为了让垃圾回收机制回收一个数组所占的内存空间,可以将该数组变量赋值为null,也就切断了数组引用变量和实际数组之间的引用关系,实际的数组也就成了垃圾信息。

举例2:

int a[] = {1,5,6,8,10,12};

System.out.println(a[0]);

int b[] = a;

a = null;//切断引用数据类型变量a与数组之间的联系,但是变量b又指向了这个数组对象,所以该数组对象不会被GC回收,仍然在堆内存中存在。

b = null; //同上切断变量b与数组之间的联系,没有变量指向这个数组,因此GC会在合适的时候将对象回收销毁。

举例3:

publicstaticvoid main(String[] args) {

int a[] = {1,12,13,22};

int b[] = newint[5];

System.out.println("数组b的长度为:"+b.length);

for(int i=0;i<a.length;i++){

b[i] = a[i];

}

for(int num : b){

System.out.print(""+num);

}

b = a;

System.out.println("数组b的长度为:"+b.length);

}

举例4:定义一个引用数据类型数组

package com.langsin.test;

publicclass Person {

Person(String name,String phone,int age){

this.name = name;

this.phone = phone;

this.age = age;

}

private String name;

private String phone;

privateint age;

public String getName() {

return name;

}

publicvoid setName(String name) {

this.name = name;

}

public String getPhone() {

return phone;

}

publicvoid setPhone(String phone) {

this.phone = phone;

}

publicint getAge() {

return age;

}

publicvoid setAge(int age) {

this.age = age;

}

publicstaticvoid main(String[] args) {

Person p[] = new Person[5];

for(int i=0;i<p.length;i++){

Person person = new Person("zhangsan"+i,"859949"+i,25+i);

p[i] = person;

}

for(int i=0;i<p.length;i++){

System.out.print("姓名:"+p[i].getName());

System.out.print("年龄:"+p[i].getAge());

System.out.println("电话:"+p[i].getPhone());

System.out.println("--------------------------------------------------------");

}

}

}

基本数据类型数组与引用数据类型数组的差别:基本数据类型数组在初始化时,在堆内存中的数组对象里面已经初始化好数据值并且进行存放。引用数据类型的数组在初始化时,在堆内存中的数组对象实际存放的是引用数据类型的引用变量。

4.6.2 多维数组

Java语言中提供了支持多维数组的语法。多维数组在本质上是有一维数组组合而成,只是外层的数组中存放的是内存数组的引用变量而已。

多维数组的初始化同一维数组相同。

举例1:

publicstaticvoid main(String[] args) {

int num[][] = {{1,3,5,7},{2,4,6,9}};

//num是二维数组,num的长度是最外层数组的长度,它存放的值为里面一层数组的引用地址

System.out.println(num.length);

int num2[][] = newint[5][6];

//给数组num里面的第一个数组对象的第一个对象值赋值

num2[0][0] = 1;

//给数组num里面的第一个数组对象的第二个对象赋值

num2[0][1] = 3;

}

如下图所示:

4.6.3 操作数组的工具类

java.lang.Arrays类,包含了一些用static修饰的方法可以直接操作数组,常用的方法如下:

int binarySearch(type[] a,type value):使用二分法查询value元素值在a数组中出现的索引位置,如果a数组不包含value元素值,则返回负数。该方法要求数组已经按照升序进行了排列。

int binarySearch(type[] a,int fromeIndex,int toIndex,type value):与上一个方法名相同,参数不同,称为方法重载,同样要求数组已经按照升序进行了排列。

举例1:

publicstaticvoid main(String[] args) {

int[] a = {1,3,4,6,7,2,8};

int index = Arrays.binarySearch(a,10); //输出-8

System.out.println(index);

index = Arrays.binarySearch(a,2);

System.out.println(index); //输出-2

index = Arrays.binarySearch(a,3);

System.out.println(index); //输出1

index = Arrays.binarySearch(a,1,6,3);

System.out.println(index); //输出1

index = Arrays.binarySearch(a,1,6,1);

System.out.println(index); //输出负数

}

type[] copyOf(type[] original,int newLength):这个方法会把original数组复制成一个新数组,其中newLength是新数组的长度,当newLength大于original的长度时,新数组的前面元素就是original的所有元素,后面所有元素进行补充,0(int类型),false(boolean类型)、null(引用类型)。

type[] copyOfRange(type[] original,int fromIndex,int toIndex):这个方法只复制original数值的from索引到to索引的元素。

举例2:

publicstaticvoid main(String[] args) {

int[] a = {1,3,4,6,7,2,8};

int[] b = Arrays.copyOf(a,5);

System.out.println(b[1]); //输出:3

b = Arrays.copyOfRange(a, 2, 5);

System.out.println(b.length+"===="+b[2]); //输出:3====7

}

boolean equals(type[] a1,type[] a2):如果数组a1与数组a2的长度相同,并且数值a1中元素值与a2中元素值相同,该方法返回true。排序值也得一样!

举例3:

publicstaticvoid main(String[] args) {

int[] a = {1,3,4,6,7,2,8};

int[] b = {1,3,4,6,7,2,8};

System.out.println(Arrays.equals(a, b)); //输出true

}

void fill(type[] a,type val):该方法对数组进行赋值,将所有元素赋值为val

void fill(type[] a,int fromIndex,int toIndex,type val):该方法仅仅对从fromIndex开始到toIndex索引的的元素进行赋值,赋值为val

举例4:

publicstaticvoid main(String[] args) {

int[] a = newint[5];

Arrays.fill(a, 12);

System.out.println(a[1]);

int[] b = newint[5];

Arrays.fill(b,1,b.length,13);

System.out.println(b[0]);

System.out.println(b[4]);

}

void sort(type[] a):该方法对数组a中的元素进行排序。

void sort(type[] a,int fromIndex,int toIndex):对数组a中的从fromIndex开始到toIndex的元素进行排序。

String toString(type[] a):该方法将一个数组转换成一个字符串。该方法顺序把多个数组元素连缀在一起,多个数组元素使用英文逗号和空格隔开。

举例5:

publicstaticvoid main(String[] args) {

int a[] = {1,6,3,7,2,8,9,10};

Arrays.sort(a);

System.out.println(Arrays.toString(a)); //输出 [1, 2, 3, 6, 7, 8, 9, 10]

}

4.7 数组的应用

将字符串形式的浮点数“1245.45”转换成大写格式 壹仟贰佰肆拾伍点肆伍

提示:从字符串中取出每个位置的值的方法:

char c = String.charAt(index);

字符型的变量也是整数类型,字符‘0’对应的整数值为48

获取字符串的长度为的方法:

Int length = String.length();

第5章:面向对象(上)

JAVA是面向对象的程序设计语言,Java语言提供了定义类(class)、属性(Field)、方法(method)等最基本的功能。类是一个自定义类型的数据类型。所有使用类定义的变量都是引用变量,变量都指向一个具体的类的对象。

面向对象的三大特征:封装、继承、多态。Java提供了private、protected、public三种访问控制修饰符来实现良好的封装,提供了extends关键字来实现继承关系。子类继承父类就可以继承父类的属性和方法。如果访问控制允许,子类实例就可以直接调用父类里定义的方法。继承是实现类复用的重要手段。

构造器也称构造方法用于对类实例进行初始化,构造器支持重载,其实也就是构造方法的重载。

5.1 类和对象

Java是面向对象的程序设计语言,类是面向对象的重要内容,可以把类当成一种自定义的数据类型,可以使用类来定义变量,这种类型的变量统称为引用变量,所有的类是引用数据类型。

5.1.1 定义类

面向对象的程序设计过程中有两个重要概念:类(class)和对象(object),其中类是某一批对象的抽象,可以把类理解成某种概念。

比如:车跟宝马就是类与对象的关系,车是类,宝马是具体的对象。

一、Java语言里定义类的语法格式:

[修饰符] class 类名{

0到多个构造方法

0到多个变量

0到多个方法

}

修饰类的关键字可以是public(公共的)、final(最终的)、abstract(抽象的),或者完全省略这三个修饰符。类名是一个合法的标识符即可。但这仅仅是符合Java的语法要求,实际上从程序的可读性来说,Java的类名必须是由一个或多个有意义的单词连接而成,每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符,例如: public class ProductService{},从类名上就能看出这是一个为产品操作提供服务的类。

对于一个类定义而言,可以包含三种最常见的成员,也称为类成员,构造器、变量、方法,三种成员都可以定义0到多个。

类里面,各成员之间的定义顺序没有任何影响,各成员之间可以相互调用,但是用static修饰的成员只能访问static修饰的成员。

举例1:

package com.langsin.test;

publicclass Student {

private String sex = "";

static String name = "";

publicstaticvoid main(String[] args) {

name = "zhangsan";

sex = "man"; //此行代码直接报错

}

}

二、Filed(变量)用于定义该类或该类的实例所包含的状态数据,方法则用于定义该类或该类实例的行为特征或者功能实现。构造器(构造方法)用于该类的实例,Java语言使用new关键字来调用构造器(构造方法)从而返回这个类的实例。构造器是一个类创建对象的根本,如果一个类没有构造器,那么Java就会为该类提供一个默认的构造器。

举例2:

public class Student{

private String name = null;

private int age = null;

private String gender = null;

Student(String name,int age,String gender){

this.name = name;

this.age = age;

this.gender = gender;

}

public void introduce(){

System.out.println(“My name is ”+this.name+”, age is ”+this.age);

}

}

Student st1 = new Student(“zhangsan”,25,”男”);

st1.introduce();

Student st2 = new Student(“lili”,25,”女”);

St2.introduce();

定义类变量(Field)语法格式如下:

1、修饰符可以省略,也可以是public、protected、private、static、final,其中public、protected、private三个只能出现其中一个,可以与static、final组合起来修饰field

2、变量类型:可以是Java语言中的任何数据类型:基本数据类型、引用数据类型。

3、Field名只要是一个合法的Java标识符即可,这是从语法角度来说,如果从程序可读性来说应该是每个单词首字母小写,后面每个单词首字母大写,与类名定义相似。

举例3:public static String name = “abc”;

private static age = “23”;

private final String classNo = “20130607”;

三、定义类方法(method)语法格式如下:

1、修饰符:修饰符可以省略,也可以是public、protected、private、static、final、abstract,其中public、protected、private、三个只能出现一个,abstract和final最多只能出现其中之一,可以与static组合起来。

2、方法返回值类型:返回值类型是Java语言允许的任何类型,即:基本类型,引用类型。如果有返回类型,必须有一个有效的return语句,该语句返回一个变量或一个表达式。这个变量或表达式的类型必须与此方法声明的类型一致。

3、方法名:方法名命名规则与Field命名规则进本相同

4、形参列表:形参列表用于定义方法可以接受的参数,形参类型和形参名之间用英文空格隔开。

举例4::

public int getTotalValue(int a,int b){

int totalNum = a + b;

return totalNum;

}

Static是一个特殊的关键字,它可以用于修饰方法,变量(Field)等成员,static修饰的成员表名它属于这个类本身,而不是该类的单个实例,通常把static修饰的变量(Field)和方法(method)称为类变量,类方法。不使用static修饰的普通方法、变量则属于该类的单个实例。

举例5:

publicclass Student {

private String sex = "";

privatestatic String name = "";

publicstaticvoid main(String[] args) {

name = "zhangsan";

System.out.println(name);

Student st = new Student();

st.name = "lisi";

System.out.println(st.name); //实例的名称

System.out.println(name); //类的名称

st.sex = "男";

System.out.println(st.sex);

}

}

四、构造器(构造方法)是一个特殊的方法,定义构造器的语法样式与定义方法的语法格式很像,定义构造器的语法格式如下:

[修饰符] 构造器名(形参列表){

//0到多个可执行语句组成的构造器执行体

}

修饰符:可以省略,也可以是public、protected、private其中之一。

构造器名:必须和类名一样。

形参列表:和定义方法形参列表格式完全相同。

注意:构造器既不能定义返回值类型,也不能使用void定义构造器没有返回值。这是Java规范定义的,实际上类的构造器是有返回值的,当使用new关键字来调用构造器时,构造器返回该类的的实例。

举例6:

package com.langsin.test;

publicclass Student {

privateintage = 29;

privatestatic String name = "";

privatevoid say(){

System.out.println("仅供自身调用");

}

publicstaticvoid main(String[] args) {

Student st = new Student();

st.say();

}

}

5.1.2 对象、引用和指针

如上例所示:在创建Student对象时,实际创建了两个东西,一个是栈内存中的变量st,存储的是类Student对象的地址值。一个是堆内存(数据区域)中的Student对象本身。在堆内存中的对象本身中,自身的属性变量(Field)并不是存放在栈内存中的,同样也是存放在堆内存中的。对于基本数据类型的变量(Filed)来说,变量存放的是具体的值,对于引用数据类型的变量(Field)来说,存放的同样是该类型指向的具体类型的地址值。引用变量都是指向堆内存里的对象。引用变量与C语言里的指针很像,都是存储一个地址值。

5.1.3 对象的this引用

Java提供了一个this关键字,this关键字总是指向调用该方法的对象。根据this出现位置的不同,this作为对象的默认引用有两种情况:

构造器中引用,this指的是该构造器正在初始化的对象。

在方法中引用,this指的是调用该方法的对象。

this关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或Field。

举例1:

package com.langsin.test;

publicclass Student {

Student(){

this.sex = "男";

}

private String sex = "";

privatestatic String name = "";

publicvoid standUp(){

this.say();

}

privatevoid say(){

System.out.println("仅供自身调用");

}

publicstaticvoid main(String[] args) {

Student st = new Student();

System.out.println(st.sex);

st.standUp();

}

}

注意:在默认情况下,因为调用关系都是对象本身的方法或者Field,所以this可以省略。

5.2 Java中的方法

方法是类或者对象的行为特征抽象,方法是类或者对象最重要的组成部分。Java中的方法不能独立存在,所有的方法要么属于类,要么属于对象。

5.2.1 方法的所属性

在Java语言中,方法不能独立存在,方法必须属于类或者对象。因此如果定义方法,那么方法只能定义在类体内,不能独立定义一个方法。如果方法使用static修饰,那么这个方法属于这个类,否则这个方法属于这个类的实例。因此方法的执行者要么是类或者类的实例对象。

5.2.2 方法的参数传递机制

在方法声明时如果包含了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时实际传递给形参的参数值也被称为实参。Java里方法的参数传递方式只有一种:值传递。所谓的值传递就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受任何影响。

举例1:

package com.langsin.test;

publicclass Test {

publicvoid getSum(int a,int b){

a = a+b;

System.out.println("参数a的值改变为:"+a); // 10

}

publicstaticvoid main(String[] args) {

int a = 5;

int b = 5;

Test test = new Test();

test.getSum(a, b);

System.out.println(a); // 5

}

}

基本数据类型为参数时的传递,引用数据类型为参数时的传递,传递的同样是实际值的副本,但要注意的是引用数据类型的值存放的是地址值,即指向实际对象的那个地址值。所以调用方法时将地址值传递给了方法,方法操作时根据地址值找到了具体的对象,将具体对象的信息发生了变化。

举例2:

Student类:

package com.langsin.test;

publicclass Student {

protected String name = "zhangsan";

}

Test类:

package com.langsin.test;

publicclass Test {

privatestaticvoid changeValue(Student st){

st.name = "李四";

}

publicstaticvoid main(String[] args) {

Student st = new Student();

System.out.println(st.name); //zhangsan

Test.changeValue(st);

System.out.println(st.name); //李四

}

}

5.2.3 递归方法

一个方法体内调用它自身,被称为方法递归。方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无需循环控制。

例如1:计算1+2+3+4+…..+100的和为多少?

publicclass Test {

publicstaticint getSum(int num){

if(num>1){

return num+getSum(num-1);

}else{

return num;

}

}

publicstaticvoid main(String[] args) {

int total = Test.getSum(100);

System.out.println(total);

}

}

练习:已知有一个数列,f(0)=1,f(1)=4,f(2) =2*f(1)+f(0),f(n+2) = 2*f(n+1)+f(n) 3 2 1

其中n是大于等于0的整数,求f(10)的值。

5.2.4 方法重载

Java允许同一个类里定义多个同名方法,只要形式参数列表不同就行。如果同一个类中包含了两个或两个以上方法的方法名相同,但形参不同,则被称为方法重载。至于方法的其他部分,如方法返回值类型、修饰符等于方法重载没有任何关系。

举例1:

publicclass Test {

publicvoid printMsg(){

System.out.println("输出打印信息");

}

public String printMsg(String msg){

System.out.println("输出打印信息:"+msg);

return msg;

}

publicstaticvoid main(String[] args) {

Test test = new Test();

test.printMsg();

test.printMsg("你好");

}

}

5.3 成员变量和局部变量

在Java语言中,根据定义变量位置的不同,可以将变量分成两大类:成员变量和局部变量。成员变量和局部变量的运行机制存在较大差异。

5.3.1 成员变量和局部变量

成员变量指的是在类范围里定义的变量,也就是介绍的Field;局部变量指的是在方法里定义的变量。成员变量又分为类变量与实例变量。其中使用static修饰的就是类变量,可以通过类直接访问,没有用static修饰的是实例变量。类变量从这个类的准备阶段开始就存在,直到系统完全销毁这个类。实例变量从该类的实例被创建时开始存在,直到系统销毁这个类的实例。

举例1:

package com.langsin.test;

publicclass Test {

publicstatic String name = "zhangsan"; //类变量

public String age = "25"; //实例变量

publicstaticvoid main(String[] args) {

Test test = new Test();

test.age = "36";

test.name = "张三";

System.out.println(test.age);

System.out.println(test.name);

Test test1 = new Test();

System.out.println(test1.age);

System.out.println(test1.name);

}

}

注意:实例Field随着实例的存在而存在,而类Field则随着类的存在而存在。实例也可以访问类Field,同一个类的所有实例访问类Field时,实际上访问的是该类本身的同一个Field。

局部变量的分类:

形参:在定义方法名时定义的变量,形参的作用域在整个方法内有效。

方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效。

代码块局部变量:由代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到该代码块结束时失效。

举例2:

publicvoid getSum(int a,int b){ //a,b为形参局部变量

int num = a+b; // num为方法局部变量

for(int i=0;i<5;i++){ // i 为代码块局部变量,for循环结束时,i失效

num = num + i;

}

System.out.println(num);

}

在Java中,成员变量的作用域是整个类内有效,一个类里不能定义两个同名的成员变量,一个方法里不能定义两个相同的成员变量,但是Java允许局部变量和成员变量同名,如果方法里的局部变量跟成员变量同名,那么在一个方法内使用成员变量时,需要使用this关键字。

举例3:

publicclass Test {

public String name = "zhangsan";

publicvoid printName(String name){

String result = this.name + name;

System.out.println(result);

}

publicvoid printName(){

System.out.println(this.name);

}

publicstaticvoid main(String[] args) {

Test test = new Test();

test.printName("是张三");

test.printName();

}

}

5.3.2 变量的使用规则

在Java中定义变量时,需要根据不同情况来定义变量的范围。

如果变量是用来描述类或者对象的固有信息,比如一些固定不变的值的信息,需要将变量定义成类变量。

如果在某个类中需要定义一个变量来保存该类或者实例运行时的状态信息。或者变量需要在整个类范围内有效,则定义成成员变量。

如果只需要在方法中运行,则变量需要定义成局部变量。

在程序中,应该尽可能地缩小局部变量的作用范围,局部变量的作用范围越小,在内存中停留的时间越短,程序运行性能就越好。

举例1:

publicclass Test {

publicstaticintRUN_SUCCESS = 0;

publicstaticintRUN_FAILURE = 1;

publicint runStatus(int a){

if(a>0){

return Test.RUN_SUCCESS;

}else{

return Test.RUN_FAILURE;

}

}

publicstaticvoid main(String[] args) {

Test test = new Test();

int result = test.runStatus(10);

if(result==Test.RUN_SUCCESS){

System.out.println("程序运行正常");

}else{

System.out.println("程序运行失败");

}

}

}

5.4 隐藏与封装

在程序中经常出现通过某个对象的直接访问其Field的情况,这会存在一系列的问题,比如定义一个Person的对象,其中有age这样一个属性,如果将属性值赋值为1000,虽然程序不会出错,但是逻辑是错误的,人不可能活1000岁,因此通常将成员变量定义使用private关键字进行修饰,然后通过方法的方式向外提供服务

举例1:

package com.langsin.test;

publicclass Test {

privateint age = 0;

publicint getAge() {

return age;

}

publicvoid setAge(int age) {

if(age>100){

this.age = 100;

}else{

this.age = age;

}

}

publicstaticvoid main(String[] args) {

Test test = new Test();

test.setAge(125);

System.out.println(test.getAge());

}

}

5.4.1 理解封装

封装是面向对象的三大特征之一,它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。

封装可以实现以下目的:

隐藏类的实现细节

使用者只能通过提供的方法来访问数据,从而可以在方法中加入控制逻辑,限制对变量的不合理的访问。

可进行数据检查,从而有利于保证对象信息的完整性。

便于修改,提高代码的可维护性。

封装的实际含义就是该隐藏的隐藏,该暴漏的暴漏。

5.4.2 package、import

Oracle公司提供的JDK、各种软件厂商、众多的开发商,会提供成千上万、具有各种用途的类,那么类名肯定会出现同名的这种情况,为了处理重名问题,Java引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理问题。

Java源文件中使用了pageckage语句,就意味着该源文件里定义的所有类属于这个包下。位于包中的每个类的完整类名都应该是包名和类名的组合。

举例1:

publicclass Test {

publicstaticvoid main(String[] args) {

com.langsin.vo.Page page1 = new com.langsin.vo.Page();

}

}

为了简化编程,Java引入了import关键字,import可以向某个Java文件中导入指定包层次下的某个类或全部类,import语句出现在package语句之后,类定义之前。

举例2:

package com.langsin.test;

import com.langsin.vo.Page;

publicclass Test {

publicstaticvoid main(String[] args) {

Page page = new Page();

System.out.println(page.getLineNum());

}

}

Import语句可以简化编程,可以导入报销某个类或者全部类,导入全部类的方式用*表示,

举例3:

import com.langsin.vo.*;

表示导入com.langsin.vo包下的所有类。

5.4.3 Java的常用包

Java的核心类都放在java这个包以及其子包下,Java扩展的许多类都放在javax包以及其子包下,这些实用类也就是前面所说的API。

java.lang:这个包下包含了Java语言的核心类,如String、Math、System、和Thread类等,使用这个包下的类无须使用import语句导入,系统会自动导入这个包下的所有类。

java.util:这个包下包含了Java的大量工具类/接口和集合框架类/接口,例如Arrays和List、Set等。

java.net:这个包下包含了一些Java网络编程相关的类和接口。

java.io:这个包下包含了一些Java输入/输出编程相关的类和接口。

java.text:这个包下包含了一些Java格式化相关的类。

java.sql:这个包下包含了Java进行JDBC数据库编程的相关类和接口。

java.awt:这个包下包含了抽象窗口工具集的相关类和接口,这些类主要用于构建图形用户界面(GUI)程序。

java.swing:这个包下包含了Swing图形用户界面编程的相关类和接口,这些类可用于构建平台无关的GUI程序。

5.5 深入构造器

构造器是一个特殊的方法,这个特殊方法用于创建实例时执行初始化。构造器是创建对象的重要途径,因此Java类必须包含一个或一个以上的构造器。

5.5.1 使用构造器进行初始化

构造器最大的用处就是在创建对象时执行初始化,所谓的初始化就是创建对象时,系统对这个对象的Field进行赋值处理。

举例1:

package com.langsin.test;

publicclass Test {

private String name = null;

privateint age = 0;

Test(String name,int age){

this.name = name;

this.age = age;

}

public String getName(){

returnthis.name;

}

publicvoid setName(String name){

this.name = name;

}

publicint getAge(){

returnthis.age;

}

publicvoid setAge(int age){

this.age = age;

}

publicstaticvoid main(String[] args) {

Test test = new Test("张三",35);

System.out.println(test.getName());

System.out.println(test.getAge());

}

}

注意:如果程序员提供了自定义的构造器,系统就不再提供默认的构造器,因此上例中Test类中提供了构造器因此不能再通过new Test()代码来创建实例,因为该类不再包含无参数的构造器。

5.5.2 构造器重载

同一个类里具有多个构造器,多个构造器的形参列表不同,即被称为构造器重载。构造器重载允许Java类里包含多个初始化逻辑,从而允许使用不同的构造器来初始化java对象。

举例1:

package com.langsin.test;

publicclass Test {

private String name = null;

privateint age = 0;

public Test(String name,int age){

this.name = name;

this.age = age;

}

public Test(){

}

public String getName(){

returnthis.name;

}

publicvoid setName(String name){

this.name = name;

}

publicint getAge(){

returnthis.age;

}

publicvoid setAge(int age){

this.age = age;

}

publicstaticvoid main(String[] args) {

Test test = new Test("张三",35);

System.out.println(test.getName());

Test test1 = new Test();

System.out.println(test1.getName());

}

}

5.6 类的继承

继承是面向对象的又一大特征,也是实现软件复用的重要手段。Java的继承具有单继承的特点,每个子类只有一个直接父类。

5.6.1 继承的特点

Java的继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类被称为父类(或者称为基类、超类)。父类和子类的关系是一种一般和特殊的关系。子类扩展了父类,将可以获得父类全部的Filed成员变量和方法。例如车跟宝马的关系,宝马继承了车,宝马是车的子类,则宝马是一种特殊的车。

因为子类是一种特殊的父类,因此父类包含的范围总比子类包含的范围要大,所以可以认为父类是大类,而子类是小类。例如:车包含很多种车,宝马、陆虎、博兰基尼都是车的子类,你可以说这些都是车,但是你不能说车是宝马、陆虎、博兰基尼。

Java类只能有一个直接的父类,但是Java类可以有无限多个间接父类。例如:

public class HighGradeCar extends Car{}

public class BmwCar extends HighGradeCar{}

BmwCar类有两个父类,一个是Car,一个是HighGradeCar。

如果定义一个Java类时并未显示指定这个类的直接父类,则这个类默认扩展java.lang.Object类,因此java.lang.Object类是所有类的父类,要么是其直接父类,要么是其间接父类。

5.6.2 重写父类的方法

子类扩展了父类,子类是一个特殊的父类。大部分情况下子类总是以父类为基础,额外增加新的Field和方法,但是有一种特殊的情况例外:子类需要重写父类的方法。

举例1:父类Bird类

package com.langsin.test;

publicclass Bird {

publicvoid fly(){

System.out.println("可以在天空中飞翔....");

}

}

package com.langsin.test;

publicclass Ostrich extends Bird {

@Override

publicvoid fly() {

System.out.println("只能在地上跑......");

}

publicstaticvoid main(String[] args) {

Ostrich os = new Ostrich();

os.fly();

}

}

程序执行时,不再执行父类Bird中fly()方法,而是执行了自身的fly()方法,这种子类包含与父类同名方法的现象称为重写,也称为方法覆盖(Override)。方法的重写要遵循如下规则:

方法名相同、形参列表相同

子类方法返回值类型应比父类方法返回值类型相等或者更小

子类方法声明抛出的异常类应该比父类方法更小或相等。

当子类覆盖了父类方法后,子类对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法,需要使用super关键字。如果父类方法具有private访问权限,则该方法对其子类是隐藏的,也就无法重写该方法。如果子类中定义了一个与父类private方法相同的方法名、相同形参列表、相同返回值类型的方法,依然不是重写,只是在子类中定义了一个新的方法而已。

举例2:

package com.langsin.test;

publicclass Bird {

publicvoid fly(){

System.out.println("可以在天空中飞翔....");

}

privatevoidgetFood(){

System.out.println("得到小虫子");

}

}

package com.langsin.test;

publicclass Ostrich extends Bird {

@Override

publicvoid fly() {

super.fly();

System.out.println("只能在地上跑......");

}

publicstaticvoid main(String[] args) {

Ostrich os = new Ostrich();

os.fly();

}

}

父类中定义的fly方法,使用super可以调用到,但是getFood()方法缺无法调用到,因为此方法是私有方法,只有父类本是可调用。使用super关键字调用fly()方法,就可以执行父类中被重写的方法。

5.6.3 super限定

Super是Java提供的关键字,super用于限定该对象调用从父类继承得到的Field成员变量或方法。如果在构造器中使用super,则super用于限定该构造器初始化的是该对象从父类继承得到的Field,而不是该类自己定义的Field。

在子类进行实例化时,会首先调用父类的构造方法,对父类中的Field成员变量或者方法进行初始化,并创建会父类对象。

举例1:

package com.langsin.test;

publicclass Bird {

Bird(){

System.out.println("=========");

}

private String name = "";

publicvoid setName(String name){

this.name = name;

}

public String getName(){

returnthis.name;

}

publicvoid fly(){

System.out.println("可以在天空中飞翔....");

}

}

子类:

package com.langsin.test;

publicclass Ostrich extends Bird {

Ostrich(){

super.setName("小鸟");

}

private String name = "鸵鸟";

public String getName(){

returnthis.name;

}

@Override

publicvoid fly() {

System.out.println("只能在地上跑");

System.out.println(super.getName());

System.out.println(this.getName());

}

publicstaticvoid main(String[] args) {

Ostrich os = new Ostrich();

os.fly();

}

}

5.6.4 调用父类构造器

子类继承父类,子类在实例化时会首先调用父类的构造方法,对父类成员进行初始化。如果父类的构造方法是隐式的,那么Java会帮我们进行自动调用。而如果父类的构造方法是显式的,那么子类就必须声明一个显示的构造方法,同时在构造器中显示的调用父类的构造器。

举例1:

package com.langsin.test;

publicclass Bird {

Bird(String name){

System.out.println("=========");

}

}

package com.langsin.test;

publicclass Ostrich extends Bird {

Ostrich() {

super("小鸟");

}

}

5.7 多态

Java引用变量有两个类型,一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现多态。所谓的多态就是同样一个东西表现出多种不同方式。

5.7.1 多态性

Java中的多态性也是由Java面向对象中继承特征导致的。因为子类是一种特殊的父类,因此Java允许把一个子类对象直接赋值给父类引用变量,无须任何类型转换。或者称为向上转型,向上转型由系统自动完成。如果子类重写了父类的方法,那么使用父类引用变量指向不同的子类时,调用方法相同,但表现形式有所不同,这种现象就成为多态。

举例1:

package com.langsin.test;

publicclass BmwCar extends Car {

publicvoid run() {

System.out.println("我是宝马车。。。。");

}

}

=======================================================

package com.langsin.test;

publicclass BenzCar extends Car {

publicvoid run() {

System.out.println("我是兰博基尼车。。。。");

}

}

========================================================

package com.langsin.test;

publicclass Car {

publicvoid run(){

System.out.println("我是所有车的父类......");

}

publicstaticvoid main(String args[]){

//编译时类型与运行时类型完全一致,所以不存在多态

Car car = new Car();

car.run();

//编译时类型与运行时类型不一致,所以存在多态

car = new BmwCar();

car.run();

//编译时类型与运行时类型不一致,所以存在多态

car = new BenzCar();

car.run();

}

}

5.7.2 引用变量的强制类型转换

编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际引用的对象确实包含该方法,也无法引用到。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制转换成运行时类型,强制类型转换需要借助于类型转换运算符。

举例1:

package com.langsin.test;

publicclass BmwCar extends Car {

publicvoid run() {

System.out.println("我是宝马车。。。。");

}

publicvoid description(){

System.out.println("高端、大气、上档次");

}

}

package com.langsin.test;

publicclass Car {

publicvoid run(){

System.out.println("我是所有车的父类......");

}

publicstaticvoid main(String args[]){

Car car = new BmwCar();

car.run(); //调用本身类所定义的方法

((BmwCar)car).description(); //进行强制转换后调用子类对象中的方法

}

}

强制类型转换不是万能的,当进行强制类型转换时需要注意:

基本类型之间的转换只能在数值类型之间进行,数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。

引用类型之间的转换只能具有继承关系的两个类之间进行,如果两个没有任何继承关系的类型,则无法进行类型转换。如果将一个父类类型转换成子类类型,那么这个父类类型指向的实例必须是子类类型的实例才行,否则在运行时会引发类型转换异常(ClassCastException)。

举例1:

package com.langsin.test;

publicclass Test {

publicstaticvoid main(String[] args) {

double d = 13.4;

//将浮点型转换成长整型

longl = (long)d;

inta = 4;

//无法将一个整型强制转换成boolean型,下面这行代码编译报错

//boolean b = (boolean)a;

//因为Object是所有类的父类,所以String是它的子类

Object obj = "Hello World";

//obj引用类型指向的是一个具体的String类,所以可以进行强制转换

String str = (String)obj;

//Integer类是int数据类型的具体类,它也是Object的子类

obj = new Integer(5);

//因为obj已经指向了一个具体的整型数据类,所以再进行强制转换时就报ClassCastExcetpion异常

str = (String)obj;

}

}

5.7.3 instanceof运算符

Instanceof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口,接口也是类,是一种特殊的类),它用于判断前面引用变量所指向的具体对象是否是后面这个类,或者后面这个类的子类、后面这个类的实现类的一个实例对象。是则返回ture,否则返回false。

举例1:

package com.langsin.test;

publicclass Test {

publicstaticvoid main(String[] args) {

Object obj = "hello";

//obj指向了一个具体的String类,Object是所有类的父类,String是它的子类,所以返回true

System.out.println(obj instanceof Object);

//obj为String类,是String类型的实例,所有返回true

System.out.println(obj instanceof String);

//obj为String类,Math是Object的子类,但不是String的子类,所以返回false

System.out.println(obj instanceof Math);

String a = "hello";

//String既不是Math的父类也不是Math类,所有下面编译报错

System.out.println(a instanceof Math);

}

}

5.8 继承与组合

继承是实现类重用的重要手段,但是继承带来了一个最大的坏处:破坏封装。相比之下,组合也是实现类重用的重要方式,而采用组合方式来实现类重用则能提供更好的封装。下面详细介绍继承和组合之间的联系与区别。

5.8.1 使用继承的注意点

子类扩展父类时,子类可以从父类继承得到Filed和方法。如果访问权限允许,子类可以直接访问父类的Field和方法,相当于子类可以直接复用父类的Field和方法。继承带来了高度复用的同时,也带来了一个严重的问题:那就是继承严重破坏了父类的封装性。封装:每个类都应该访问父类的Field和方法,而只需要暴漏必要的方法给其他类使用。但是在继承关系中,子类可以直接访问父类的Field和方法,从而造成子类与父类的严重耦合。

父类的实现细节对于子类来说不再透明,子类可以访问父类的Field和方法,并可以改变父类的方法的实现细节,从而导致子类可以任意的篡改父类的方法。

举例1:

package com.langsin.test;

publicclass Bird {

public String name = "小鸟";

publicvoid fly(){

System.out.println(this.name+"可以在天空中自由飞翔。。。");

}

Public void run(){

this.fily();

}

}

篡改属性:

package com.langsin.test;

publicclass Ostrich extends Bird {

publicvoid test(){

this.name = "大象"; //任意篡改了父类的属性信息

this.fly();

}

publicstaticvoid main(String[] args) {

Ostrich os = new Ostrich();

os.test();

}

}

篡改方法:

package com.langsin.test;

publicclass Ostrich extends Bird {

publicvoid test(){

this.fly();

}

//任意篡改方法

publicvoid fly(){

System.out.println(this.name+"在水中游来游去。。。");

}

publicstaticvoid main(String[] args) {

Ostrich os = new Ostrich();

os.test();

}

}

对于此种问题,基于保证父类有良好的封装,不会被子类随意改变,设计父类时通常遵循如下规则。

尽量隐藏父类的内部数据,尽量把父类的所有Field都设置成private访问类型,不让子类直接访问父类的Field.

不要让子类可以随意访问、修改父类的方法。父类中作为辅助其他方法的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法。如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望被子类重写该方法,可以使用final修饰符来修饰,表示最终的方法。如果父类的方法可以让子类重写,但是又不希望被其他类自由访问,可以使用protected来修饰该方法。

不要在父类构造方法中调用将会被子类重写的方法。因为如果方法被子类重写,那么父类初始化时,构造方法中调用的是被子类重写的方法,而不是本身的那个方法。

举例2:

package com.langsin.test;

publicclass Bird {

Bird(){

this.fly();

}

publicvoid fly(){

System.out.println("小鸟可以在天空中自由飞翔。。。");

}

}

package com.langsin.test;

publicclass Ostrich extends Bird {

publicvoid fly(){

System.out.println("鱼儿在水中游来游去。。。");

}

publicstaticvoid main(String[] args) {

Ostrich os = new Ostrich(); //在创建子类对象时,父类构造方法调用了被子类重写的方法。

}

}

注意:如果把某些类设置成最终类,即使用final修饰符修饰这个类,那么这个类将不能被当成父类。例如JDK所提供的java.lang.String类,java.lang.System类,都是最终类。

5.8.2 利用组合实现复用

如果需要复用一个类,除了把这个类当成基类来继承之外,还可以把该类当成另外一个类的组成部分,从而允许新类直接复用该类的public方法。因此,不管是继承还是组合,都允许在新类中直接复用旧类的方法。

举例1:

package com.langsin.test;

publicclass Person {

private String sex;

privateint age;

private String name;

public String getSex() {

return sex;

}

publicvoid setSex(String sex) {

this.sex = sex;

}

publicint getAge() {

return age;

}

publicvoid setAge(int age) {

this.age = age;

}

public String getName() {

return name;

}

publicvoid setName(String name) {

this.name = name;

}

}

Student学生

package com.langsin.test;

publicclass Student {

private String schoolName;

private Person pseron = new Person();

publicstaticvoid main(String[] args) {

Student student = new Student();

student.schoolName = "山东轻工业大学";

student.person.setAge(22);

student.person.setName("张三");

student.person.setSex("男");

System.out.println("姓名:"+student.person.getName());//组合的使用

System.out.println("年龄:"+student.person.getAge());

System.out.println("性别:"+student.person.getSex());

System.out.println("学校:"+student.schoolName);

}

}

Worker工人类

package com.langsin.test;

publicclass Worker {

private String factoryName;

private Person person = new Person();

publicstaticvoid main(String[] args) {

Worker worker = new Worker();

worker.factoryName = "山东齐鲁制药厂";

worker.person.setName("李四");

worker.person.setAge(25);

worker.person.setSex("男");

System.out.println("姓名:"+worker.person.getName());

System.out.println("年龄:"+worker.person.getAge());

System.out.println("性别:"+worker.person.getSex());

System.out.println("学校:"+worker.factoryName);

}

}

5.9 初始化块

Java使用构造器来对单个对象进行初始化操作,使用构造器先完成整个Java对象的状态初始化,然后将Java对象返回给程序,从而让该Java对象的信息更加完整。与构造器作用非常类似的是初始化块,也可以对Java对象进行初始化操作。

5.9.1 使用初始化块

初始化块是Java类里可以出现的第四种成员,一个类里可以有多个初始化块,相同类型的的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块后执行。

语法格式如下:

[修饰符]{

//初始化块的可执行代码

}

初始化块的修饰符只能是static,使用static修饰的初始化块被称为静态初始化块。初始化块里的代码可以包含任何可执行性语句,包括定义语句变量、调用其他对象的方法,以及使用分支、循环语句等。

举例1:

package com.langsin.test;

publicclass Person {

Person(){

System.out.println(this.gender);

}

//一行代码的初始化块,先执行下面代码

private String gender = "女";

//初始化块

{

System.out.println(this.gender); // 女

this. gender = "男";

System.out.println(this.gender); // 男

}

publicstaticvoid main(String[] args) {

Person person = new Person();

//对象创建之后,首先执行初始化块,private String gender = “女”;也是初始化块

}

}

//先处理静态的

5.9.2 初始化块和构造器

初始化块先于构造器执行,系统可以同样使用初始化块来进行对象的初始化操作。初始化块是一段固定执行的代码,它不能接收任何参数,因此如果对象有一段初始化处理代码对所有对象都完全相同,且无须接收任何参数,就可以把这段初始化代码提取到初始化块中。

举例1:

publicclass Person {

Person(){

this.currentCityName = "青岛";

System.out.println(this.nation+":"+this.currentCityName);

}

//如果仅仅是定义变量且初始化的代码块,则可以不放到{}中

private String nation = "中国";

private String currentCityName = "济南";

//代码块会被首先执行,如果是可执行的语句必须将代码放到代码块中,即{}中

{

System.out.println(this.nation+":"+this.currentCityName);

This.currentCityName = “淄博”;

System.out.println(this.nation+":"+this.currentCityName);

}

publicstaticvoid main(String[] args) {

Person person = new Person();

}

}

输出信息:中国 济南

中国 淄博

中国 青岛

5.9.3 静态初始化块

定义初始化块时使用了static修饰符,则这个初始化块就变成了静态初始化块,也被称为类初始化块。静态初始化块是类相关的,系统在类初始化阶段时执行静态初始化块,而不是在创建对象时才执行。因此静态初始化块总是比普通初始化块先执行。

举例1:

package com.langsin.test;

publicclass Person {

Person(){

this.currentCityName = "青岛";

System.out.println(this.nation+":"+this.currentCityName);

}

private String nation = "中国";

private String currentCityName = "济南";

{

System.out.println(this.nation+":"+this.currentCityName);

}

privatestatic String flag = "success";

static{

System.out.println(Person.flag);

Person.flag = "false";

System.out.println(Person.flag);

}

publicstaticvoid main(String[] args) {

Person person = new Person();

}

}

第6章:面向对象(下)

Java提供了final关键字来修饰变量、方法和类,系统不允许为final变量重新赋值,子类不允许覆盖父类的final方法,final类不能派生子类。通过使用final关键字、允许Java实现不可变类,不可变类会让系统更加安全。

abstract和interface两个关键字分别用于定义抽象类和接口,抽象类和接口都是从多个子类中抽象出来的共同特征。但抽象类主要作为多个类的模板,而接口则定义了多类应该遵守的规范。enum关键字用于创建枚举类,枚举类是一种不能自由创建对象的类,枚举类的对象在定义类时已经固定下来。

6.1 Java增强的包装类

Java是面向对象编程语言,但同样提供了8中基本数据类型,这8种基本数据类型不支持面向对象的编程机制,基本数据类型同样也不具备“对象”的特征:没有Field成员变量、没有方法可以被调用。8种基本数据类型带来一定的方便性,即:可以进行简单、有效的常规数据处理。但在某些时候基本数据类型会有一些制约。例如:所有的引用类型的变量都继承Object类,都可以当成Object类型变量使用,但是基本数据类型则不可以,如果某些类提供的方法需要Object类型的参数,但实际的数值确是1、2、3、4等数值,这种情况就难以处理。

为了解决8中基本数据类型的变量不能当成Object类型变量使用的问题,Java提供了包装类的概念,为8中基本数据类型分别定义了相应的引用类型,并称为基本数据类型的包装类。

举例1:如何将基本数据类型转换成对应的包装类

publicstaticvoid main(String[] args) {

int a = 5;

Integer ia = new Integer(a);

System.out.println(ia);

boolean b = true;

Boolean bl = new Boolean(b);

System.out.println(bl);

Float fl = new Float(12.5);

System.out.println(fl);

float f = 12.5f;

fl = new Float(f);

System.out.println(fl);

fl = new Float("12.5");

System.out.println(fl);

}

所有基本数据类型包装类,都提供通过向包装类构造器中传入一个字符串参数的方式来创建包装类对象。但是如果传入的字符串参数不符合基本数据类型格式,那么将引发java.lang.NumberFormatException异常。数据转换格式化异常。

例如:Integer a = new Integer(“aaa”);就会引发数据转换格式化异常。

装箱与拆箱

Java提供的基本数据类型与包装类之间的转换有点繁琐,在JDK1.5版本中提供了自动装箱与拆箱的功能。所谓的自动装箱与拆箱就是可以把一个基本类型的数据变量赋值为包装类变量,或者赋值给Object变量,子类对象可以直接赋值给父类变量就是装箱的一种体现。

自动拆箱则与之相反,允许直接把包装类对象直接赋值给一个对应的基本类型变量。

举例2:

publicstaticvoid main(String[] args) {

//自动装箱

Integer a = 5;//引用行的变量只能接受一个对象!

Object obj = new Float("12.5");

//自动拆箱

intb = new Integer(6);

}

基本数据类型与字符串之间的转换

利用包装类提供的parseXxx(String s)的静态方法,将String类型的数据转换成相应的基本数据类型

利用包装类提供的Xxx(String s)构造器

String类型提供了多个valueOf()方法,用于将基本类型变量转换成字符串。

举例3:

publicstaticvoid main(String[] args) {

int a = Integer.parseInt("125");

float f = Float.parseFloat("12.5");

boolean b = Boolean.parseBoolean("true");

String str = String.valueOf(a);

str = String.valueOf(f);

str = String.valueOf(b);

}

6.2 处理对象

Java对象都是Object类的实例,都可以直接调用Object类中定义的方法,这些方法提供了处理Java对象的通用方法。

6.2.1 打印对象和toString方法

创建一个对象,并将对象在控制台打印出来。

package com.langsin.test;

publicclass Test {

publicstaticvoid main(String[] args) {

Test test = new Test();

System.out.println(test);

}

}

输出结果:com.langsin.test.Test@de6ced

System.out.println()方法只能在控制台输出字符串,而Test是内存中的一个对象,实际上输出Test的时候默认的调用的是Test从Object对象中继承的toString()方法。等同于System.out.println(test.toString());

Object类提供的toString方法是对该对象的“自我描述”信息,该方法总是返回实现类的“类名+@+hashCode”值,这个返回值并不能真正实现“自我描述”,就必须重写Object类的toString方法。

举例2:

publicclass Test {

public String toString(){

return"自我描述";

}

publicstaticvoid main(String[] args) {

Test test = new Test();

System.out.println(test);

}

}

6.2.2 ==和equals方法

Java中判断两个变量是否相等有两种方法:一种是利用==运算符,另一种是利用equals方法。当使用==来判断两个变量是否相等时,如果两个变量是基本类型变量,并且是数值类型,只要两个值相等则返回true。对于引用类型,它们指向同一个对象时,“==”判断才返回true。==不可用于比较类型上没有父子关系的两个对象。

举例1:

publicstaticvoid main(String[] args) {

int a = 1;

int b = 1;

System.out.println(a==b); // true

String s1 = "abc";

String s2 = "abc";

System.out.println(s1==s2); // true

s2 = new String("abc");

System.out.println(s1==s2); // false

Object obj = "abc";

System.out.println(s1==obj); // true

obj = new String("abc");

System.out.println(s1==obj); // false

System.out.println(s2==obj); // false

Integer it = 5;

System.out.println(s2==it); //编译出错//类型

}

程序判断两个引用变量是否相等时,equals是进行一种“值相等”的判断,并不严格要求两个引用变量指向同一个对象。例如:对于两个字符串变量,可能只是要求它们引用字符串对象里包含的字符串序列相同即可。

举例2:String s1 = new String(“abc”); String s2 = new String(“abc”);

判断两个变量所包含的值是否相等时,就可以用equals方法来进行判断。

publicstaticvoid main(String[] args) {

String s1 = new String("abc");

String s2 = new String("abc");

System.out.println(s1.equals(s2));

}

注意:使用equals方法常用于两个字符串之间的值比较。

6.3 类成员

Static关键字修饰的成员就是类成员,其中有类变量Field、类方法、静态初始化块。Static修饰的类成员属于整个类,不属于单个实例。

6.3.1 理解类成员

在Java类里只能包含Field、方法、构造器、初始化块、内部类(接口、枚举)5中成员,其中static可以修饰Field、方法、初始化块、内部类。以static修饰的成员就是类成员。类成员属于整个类,而不是属于单个对象。

类Field既可以通过类来访问,也可以通过类的对象来访问。但是通过类的对象来访问类Field时,实际上访问的并不是该对象所拥有的Field,而是类所拥有的Field。可以理解为:当通过对象来访问类Field时,系统会在底层转换为通过该类来访问类Field。

举例1:

publicclass Test {

Public static inta = 1;

publicvoid run(){

a++;

System.out.println(a);

}

publicstaticvoid main(String[] args) {

Test t1 = new Test();

t1.run(); //

Test t2 = new Test();

t2.run(); //

}

}

类方法也是类成员的一种,类方法属于类的,通常直接使用类作为调用者来调用类方法,但是可以使用对象来调用类方法,与类Field累世。

静态初始化块也是类成员的一种,静态初始化块用于执行类初始化动作,在类的初始化阶段,系统会调用该类的静态初始化来对类进行初始化。一旦该类初始化结束后,静态初始化块将永远不会获得执行的机会。

对于static关键字而言,有一条非常重要的规则:类成员不能访问实例成员。因为类成员是属于类的,类成员的作用域比实例成员更大,完全可能出现类成员已经初始化完成,但实例成员还不曾初始化的情况。如果允许类成员访问实例成员将会引发大量的错误。

6.3.2 单例类

在Java中,类的构造器默认为public的权限,允许任何类自由的创建该类的对象。但是在某些情况下一些类自由的创建该类的实例对象没有任何意义,比如说:一个系统的窗口管理器、一个数据库引擎访问点,此时如果在系统中为这些类创建多个对象就没有太大的实际意义。

所以在这种情况下,可以设计一个类只能创建一个实例,则这个类被称为单例类。

创建单例类的步骤:

使用private关键字修饰构造器

提供一个public的static的类方法调用构造器

创建一个static的成员变量来保存类对象,同时判断对象是否已经创建

举例1:

package com.langsin.test;

publicclass Singleton {

private Singleton(){

}

privatestatic Singleton single = null;

publicstatic Singleton getInstance(){

if(single==null){

single = new Singleton();

}

returnsingle;

}

publicstaticvoid main(String[] args) {

Singleton s1 = Singleton.getInstance();

Singleton s2 = Singleton.getInstance();

System.out.println(s1==s2);

}

}

6.4 final修饰符

final关键字可用于修饰类、变量和方法,用于表示它修饰的类、方法和变量不可改变。final修饰变量时,表示该变量一旦获得了初始值,就不可被改变,final既可以修饰成员变量,也可以修饰局部变量、形参。

6.4.1 final成员变量

成员变量是随着类初始化或者对象的初始化而初始化的。那么成员变量的初始值可以在定义变量时指定默认的初始值也可以在初始化块或者构造器中指定初始值。

对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值。如果没有在定义成员变量是指定初始值,也没有在初始化块、构造器中指定初始值,那么这些成员变量也就失去了意义。因此Java语法规定:final修饰的成员变量必须由开发人员显示的指定默认值,系统不会对final成员变量进行隐式赋值。

使用final修饰的成员变量初始化归纳如下:

类Field:必须在静态初始化块中或声明该Field时指定初始值。

实例Field:必须在非静态初始化块、声明该Field或构造器中指定初始值.

举例1:

package com.langsin.test;

publicclass Test {

public Test(){

s3 = "abc"; //变量s3在构造器中初始化

}

publicstaticfinalinta; //类变量a定义时没有初始化,但在静态化块中进行初始化

publicstaticfinalintb = 2; //类变量b在定义时进行了初始化

static{

a = 1; //在静态化块中对a进行了初始化

}

publicfinal String s1 = "abc"; //普通成员变量在定义时进行了初始化

publicfinal String s2; //普通成员变量在s2在初始化块中进行了初始化。

{

s2 = "123";

}

publicfinal String s3; //普通成员变量s3在定义时没有初始化,但是在构造器中进行了初始化。

}

6.4.2 final局部变量

系统不会对局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。

如果final修饰的局部变量在定义时没有指定默认值,则可以在后面代码中对该final变量赋初始值,但只能赋值一次,不能重复赋值。

举例1:

publicclass Test {

publicvoid run(){

Final int a = 1;

for(int i=0;i<5;i++){

a = i; //程序编译时报错,提示去掉final

}

System.out.println(a);

}

}

6.4.3 final修饰基本类型和引用类型变量的区别

使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只能保证这个引用类型变量所引用的地址不会改变,即一直指向这个对象,但这个对象完全可以改变。

举例1:

package com.langsin.test;

publicclass Test {

privatefinal Student st = new Student();

publicvoid run(){

st.setAge(36);

st.setName("王二");

System.out.println(st.getAge());

System.out.println(st.getName());

}

publicstaticvoid main(String[] args) {

Test test = new Test();

test.run();

}

}

class Student{

privateint age = 25;

private String name = "张三";

publicint getAge() {

return age;

}

publicvoid setAge(int age) {

this.age = age;

}

public String getName() {

return name;

}

publicvoid setName(String name) {

this.name = name;

}

}

6.4.4 final方法

Final修饰的方法不可被重写,出于某些原因,不希望子类重写父类的某些方法,则可以使用final修饰该方法。

Java提供的Object类里中提供的getClass()方法就是使用final进行修饰的,因为Java不希望任何类重写这个方法,所以使用final把这个方法封闭起来。但对于该类提供的toString()和equals()方法,都允许子类重写,因此没有使用final修饰他们。

举例1:

package com.langsin.test;

publicclass Bird {

publicfinalvoid fly(){

System.out.println("小鸟可以在天空中自由飞翔。。。");

}

}

getclass

package com.langsin.test;

publicclass Ostrich extends Bird {

publicvoidfly(){

System.out.println("鱼儿在水中游来游去。。。");

}

}

子类Ostrich在重写fly方法时编译报错。

注意:对于使用private修饰的方法,表示私有方法,只能是对象本身调用,那么子类在继承时是无法访问该方法的,所以子类无法重写该方法。如果子类中定义一个与父类private方法相同方法名、相同返回值类型、相同参数列表的方法,也不是重写,而是定义了一个新的方法。因此即使使用final修饰一个使用private访问权限的的方法,依然可以在其子类中定义一个与该方法一模一样的方法。

package com.langsin.test;

publicclass Bird {

privatefinalvoidfly(){

System.out.println("小鸟可以在天空中自由飞翔。。。");

}

}

package com.langsin.test;

publicclass Ostrich extends Bird {

publicvoidfly(){

System.out.println("鱼儿在水中游来游去。。。");

}

}

此时,此类中定义的方法将不会编译报错。

6.4.5 final类

final修饰的类不可以有子类,例如:java.lang.Math类就是一个final类,它不可以有子类。

当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类的方法来改变父类方法的实现细节,这可能会导致一些不安全的因素。为了确保某个类不被继承,则可以使用final类修饰这个类。

6.4.6 不可变类

不可变类的意思是创建该类的实例后,该实例的Field是不可改变的。Java提供的8个数据类型包装类和java.lang.String类都是不可变类,当创建他们的实例后,其实例的Field不可改变。

举例:

Double d = new Double("12.5");

String str = new String("abc");

创建两个对象,并传入了两个字符串作为参数,那么Double类和String类肯定需要提供实例成员变量来存放这个两个参数,但程序无法修改这两个实例成员的值。

因此,如果需要创建自定义的不可变类,可遵循如下规则:

使用private和final修饰符来修饰该类的Field

提供参数构造器,用于根据传入参数来初始化类里的Field

仅为该类的Field提供getter方法。

6.5 抽象类

当编写一个类时,常常会为该类定义一些方法,这些方法用于描述这个类的行为。但在某些情况下只需要定义出一些方法,而不需要具体的去实现这些行为。也就是说这些方法没有方法体,只是一些签名而已,这样的方法被称为抽象方法,包含抽象方法的类被称为抽象类。

6.5.1 抽象方法与抽象类

抽象方法与抽象类必须使用abstract关键字进行修饰,有抽象方法的类必须被定义成抽象类,抽象类里面可以没有抽象方法。

抽象类与抽象方法的规则如下:

抽象类与抽象方法必须使用abstract关键字进行修饰,抽象方法不能有方法体。

抽象类不能被实例化。即使抽象类不包含抽象方法,也不能被实例化。

抽象类可以包含Field、方法、构造器、初始化块、内部类、枚举类6种成分。

包含抽象方法的类,只能被定义成抽象类。

举例1:

package com.langsin.test;

publicabstractclass Test {

public String name = "zhangsan";

publicabstractvoid setName();

publicabstract String getName();

publicvoid run(){

System.out.println("让类跑起来");

}

}

举例2:创建一个类继承Test类

package com.langsin.test;

publicclass TestAbstract extends Test {

publicvoid setName(String name) {

this.name = name;

}

public String getName() {

returnthis.name;

}

publicstaticvoid main(String[] args) {

TestAbstract test = new TestAbstract();

System.out.println(test.getName()); //打印的是zhangsan

}

}

举例3:在上面这个类中重写name属性

package com.langsin.test;

publicclass TestAbstract extends Test {

public String name;

publicvoid setName(String name) {

this.name = name;

}

public String getName() {

returnthis.name;

}

publicvoid test(){

super.name = "李四";

this.name = "张三";

super.run();

System.out.println(this.name);

}

publicstaticvoid main(String[] args) {

TestAbstract ta = new TestAbstract();

ta.test();

}

}

利用抽象类和抽象方法的优势,可以更好的发挥多态的优势,使得程序更加灵活。

使用abstract修饰类时,表明这个类只能被继承,当使用abstract修饰方法时,表明这个方法必须由子类实现。而final修饰的类不能被继承,final修饰的方法不能被重写。因此final和abstract永远不能同时使用。除此之外,使用static修饰一个方法时,表示此方法属于该类本身,即通过类就可以调用该方法,但是如果此方法被定义成抽象方法了,则将导致通过该类来调用该方法时出现错误,因此static和abstract也不能同时修饰某个方法,也就是没有类抽象方法。

6.5.2 抽象类的作用

抽象类不能创建实例,只能当成父类来被继承。抽象类可以看成是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出来的一个抽象类,以这个抽象类作为其子类的模板,从而避免子类设计的随意性。

抽象类的体现就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,这就是一种模板模式,模板模式也是十分常见的设计模式。

举例1:

package com.langsin.test;

Public abstract class Test {

publicabstract String getColor();

}

package com.langsin.test;

publicclass TestOne extends Test {

public String getColor() {

return"red";

}

}

package com.langsin.test;

publicclass TestTwo extends Test {

public String getColor() {

return"green";

}

publicstaticvoid main(String[] args) {

TestOne one = new TestOne();

System.out.println("车的颜色是:"+one.getColor());

TestTwo two = new TestTwo();

System.out.println("车的颜色是:"+two.getColor());

}

}

模板模式在面向对象的软件中很常用,其原理简单,实现也很简单。使用模板模式有如下规则:

抽象父类可以只定义需要使用的方法,把不能实现的部分抽象成抽象方法留给子类去实现。

6.6 接口

抽象类是从多个类中抽象出来的模板,如果将这种抽象进行得更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(interface)。接口里不能包含普通方法,接口里的所有方法都是抽象方法。

implement

6.6.1 接口的概念

Java中的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

6.6.2 接口的定义

和类定义不同,定义接口不在使用class关键字,而是使用interface关键字。接口定义的基本语法如下:

[修饰符] interface 接口名 extends 父接口1,父接口2{

零到多个常量定义

零到多个抽象方法定义

}

修饰符可以是public或者protected省略,省略采用默认包权限访问控制符

接口名应与类名采用相同的命名规则

一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类

接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。接口里可以包含Field、方法、内部类定义。因为接口没有构造器与初始化块,因此系统不能为Field进行默认的初始化操作,只能由程序编写人员为Field指定默认的值,所以Field只能是常量。又因为Field只能是常量,所有系统自动为这些Field增加了static和final两个修饰符。也就是说在接口中定义的Field不管是否使用了public static final修饰符,接口里的Field总是默认的使用这三个修饰符来进行修饰。

举例1:

int MAX_SIZE = 50;

public static final int MAX_SIZE = 50;

两行代码的运行结果完全一致。

接口里定义的方法都是抽象方法,因此系统会自动为方法增加public abstract修饰符。因此不管定义接口方法时是否使用了public abstract修饰符,系统都会默认的使用public abstract修饰符来进行修饰。

举例2:

package com.langsin.test;

publicinterface TestInterface {

publicstaticfinalintWIDTH = 5; //与下行代码运行结果一致

intLENGTH = 10;

publicabstractvoid run(); //与下行代码运行结果一致

void fly();

}

6.6.3 接口的继承

接口的继承与类的继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口。和继承相似,子接口扩展父接口,将会获得父接口里定义的所有抽象方法、Field、内部类和枚举定义。

一个接口继承多个父接口时,多个父接口排在extends关键字之后,多个父接口之间使用英文逗号(,)进行分隔。

举例1:

package com.langsin.test;

publicinterface InterfaceA {

publicabstractvoid testA();

}

package com.langsin.test;

publicinterface InterfaceB {

publicabstractvoid testB();

}

package com.langsin.test;

publicinterface InterfaceC extends InterfaceA, InterfaceB {

publicabstractvoid testC();

}

6.6.4 使用接口

接口不能用于创建实例,但接口可以用于声明引用类型变量。当使用接口来声明引用类型变量时,这个引类型变量必须引用到其实现类的对象。除此之外,接口的主要用途就是被实现类进行实现。

一个类可以实现多个接口,继承使用extends关键字,而实现则使用implements关键字。

实现接口与继承类相似,一样可以获得所实现接口里定义的常量Field、抽象方法、内部类和枚举类定义。

让类实现接口需要在类定义后面增加implements部分,当需要实现多个接口时,多个接口之间以英文逗号(,)隔开。一个类可以继承一个父类并同时实现多个接口,implements部分必须放在extends部分之后。

一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法,否则该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。

举例1:

package com.langsin.test;

publicinterface Product {

publicstaticfinalintMAX_SIZE = 10;

publicabstractint getProductNum();

}

package com.langsin.test;

publicinterface OutPut {

Public abstract void out();

Public void addData(String msg);

}

package com.langsin.test;

publicclass Printer implements Product, OutPut {

private String[] printData = new String[MAX_SIZE];

privateint currentNum = 0;

publicvoid out() {

while(currentNum>0){

System.out.println(printData[--currentNum]);

}

}

publicint getProductNum() {

return 45;

}

publicvoid addData(String msg){

if(currentNum>=MAX_SIZE){

System.out.println("队列已满,添加失败");

}else{

printData[currentNum++] = msg;

}

}

publicstaticvoid main(String[] args) {

OutPut out = new Printer();

out.addData("浪曦云团");

out.addData("浪曦算法");

out.addData("浪曦coreJava");

out.out();

Product product = new Printer();

int num = product.getProductNum();

System.out.println(num);

}

}

6.6.5 接口和抽象类差异

1、接口和抽象类都不能进行实例化,它们都位于继承树的顶端,用于被其他类实现和继承。

2、接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务。对于接口的调用者而言,接口规定了调用者可以调用哪些服务。当在一个程序中使用接口时,接口是多个模块间的耦合标准,当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。

接口类似于系统的总纲,一旦接口发生变化,对于整个系统是辐射式的,所有实现这个接口的普通类都要进行改写。

抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模版式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品。这个中间产品已经实现了系统的部分功能,但这个类不能称为最终产品,必须有更进一步的完善,这种完善可能有几种不同的方式来实现。

接口与抽象类在用法上也存在如下差异:

接口里只能包含抽象方法,不包含已经提供实现的方法,抽象类则完全可以包含普通方法。

接口里不能定义静态方法,抽象类里可以定义静态方法。

接口里只能定义静态常量Field,不能定义普通的Field,抽象类里则都可以

接口里不包含构造器,抽象类里可以包含构造器,抽象类里的构造器并不是用来创建对象,而是让其子类调用这些构造器完成属于抽象类的初始化操作。

接口里不能包含初始化块,但抽象类则完全可以包含初始化块。

一个类最多只有一个父类,包括抽象类,但是一个类可以实现多个接口。

6.6.6 面向接口编程

接口体现的是一种规范和实现分离的设计模式,充分利用接口可以很好降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。

基于这种原则,软件架构设计理论都倡导“面向接口”编程,而不是面向实现类编程,希望通过面向接口编程来降低程序的耦合。下面两种常用场景来示范面向接口编程的优势。

简单工厂模式

假设我们在系统中有个Computer类需要一个输出类Printer类,那么普通的情况下是我们在Computer类中new一个Printer类这个对象就可以,但是如果以后我们的系统进行重构,让BetterPrinter类来代替原有的Printer类,那么我们就需要打开Computer类进行源码的修改,如果系统中只有一个Computer类用到了Printer类还好,如果有10个、100个、1000个Computer类用到了Printer类,那么这将是一个非常庞大的工作量。

如果使用面向接口的方式来处理这种问题,将会变的非常简单。

例如:我们创建输出接口Out,让我们的Printer类来实现这接口,我们使用工厂模式来创建返回的结果。在我们的Computer类中使用工厂来进行创建,实现Computer类与Printer类的一个分离,对Computer类屏蔽到Printer类的实现细节。

package com.langsin.test;

publicinterface Out {

publicabstract Out printer ();

}

=======================================================================

package com.langsin.test;

publicclass Printer implements Out {

publicvoid printer() {

System.out.println("普通打印机");

}

}

=======================================================================

package com.langsin.test;

publicclass BetterPrinter implements Out {

publicvoid printer() {

System.out.println("高级打印机");

}

}

=======================================================================

package com.langsin.test;

publicclass PrinterFactory {

publicstatic Out getInstance(){

returnnew BetterPrinter();

//returnnew Printer();

}

}

========================================================================

package com.langsin.test;

publicclass Computer {

publicstaticvoid main(String[] args) {

Out out = PrinterFactory.getInstance();

out.printer();

}

}

在进行重构时,我们只需要将工厂源码打开,将返回对象进行修改,所有调用这个方法的类获得的将不再是Printer类对象,而是新的BetterPrinter类对象。

6.7 内部类

在定义类的时候,我们一般把类定义成一个独立的程序单元。但是在某些情况下,我们会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,也可以称为嵌套类。包含内部类的类也被称为外部类,也可以称为宿主类。Java从JDK1.1开始引入内部类,内部类的主要作用如下:

内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中其他类访问该类。

内部类成员可以直接访问外部类的私有数据,因为内部类被当成外部类的成员,同一个类成员之间可以相互访问。但外部类不能访问内部类的实现细节,例如内部类的成员变量。

匿名内部类适合用于创建那些仅需要一次使用的类。

有对象存在了就可以访问一些方法了

举例1:

package com.langsin.test;

publicclassStudent {

private String name = "zhangsan";

publicint age = 25;

//============内部类开始=======================

class Action{

//没有什么特殊意义,就是一个内部类的成员变量

public String flag = "abc";

publicvoid change(){

Student.this.name = "lisi";

Student.this.age = 35;

}

}

//=============内部类结束========================

publicvoid run(){

Action action = new Action();

action.change();

System.out.println(this.name);

System.out.println(this.age);

}

publicstaticvoid main(String[] args) {

Studentsty = newStudent ();

stu.run();

}

}

6.7.1 非静态内部类

定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的“内部类”包括类中的任何位置,甚至在方法中也可以定义内部类,在方法中定义的内部类叫做局部内部类。

通常情况下,内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与Field、方法、构造器和初始化块相似的类成员。

成员内部类分为:静态内部类和非静态内部类两种,使用static修饰的成员内部类就是静态内部类,没有使用static修饰的成员内部类就是非静态内部类。

因为内部类作为其外部类的成员,所以可以使用任意访问控制符:private、protected、public修饰的Field成员。

如上例所示:不能包含静态的方法

6.7.2 静态内部类

使用static修饰符来修饰内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为静态内部类。

静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。

举例1:

package com.langsin.test;

publicclass Car {

privatestatic String name = "zhangsan";

publicint age = 25;

staticclass Action{

publicstatic String flag = "abc";

public String gender = "1";

publicvoid change(){

Car.name = "lisi";

//下面这行代码编译报错

Car.this.age = 34;

}

}

publicvoid run(){

Action action = new Action();

action.change();

System.out.println(name);

System.out.println(this.age);

System.out.println(Action.flag);

}

publicstaticvoid main(String[] args) {

Car car = new Car();

car.run();

}

}

外部类不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。

举例2:

package com.langsin.test;

publicclass Test {

staticclass InnerTest{

publicstaticintnum = 1;

publicint num2 = 5;

}

publicvoid run(){

InnerTest.num = 4;

System.out.println(InnerTest.num); //4

System.out.println(new InnerTest().num); // 4

}

}

6.7.3 局部内部类

如果把一个内部类定义在方法里面定义,则这个内部类就是一个局部内部类。还是可以定义在

初始化模块中,作用是随着方法运行使用的结束自动结束!

举例1:

package com.langsin.test;

publicclass Test {

publicint aa = 12;

publicvoid run(){

class InnerTest{

publicint num2 = 5;

publicvoid run(){

System.out.println(num2);

}

}

InnerTest it = new InnerTest();

it.run();//注意创建对象

}

publicstaticvoid main(String[] args) {

Test te = new Test();

te.run();

}

}

6.7.4 匿名内部类

匿名内部类的语法有些特别,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。因此匿名内部类适合创建那种只需要一次使用的类。

匿名内部类的格式如下:

new 父类构造器|实现接口(){

//匿名内部类的类体部分

}

匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,实现一个接口。

匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。

匿名内部类不能定义构造器,因为匿名内部类没有类名,也就无法定义构造器,但是匿名内部类可以定义实例初始化块,通过初始化块来完成初始化操作。

举例1:

package com.langsin.test;

publicclass Test {

publicint aa = 12;

publicvoid run(InnerTest it){

System.out.println(it.num2);

it.run();

}

publicstaticvoid main(String[] args) {

Test te = new Test();

te.run(new InnerTest(){

{

System.out.println("飞起来。。。");

}

});

}

}

class InnerTest{

publicint num2 = 5;

publicvoid run(){

System.out.println(num2);

}

}

6.8 枚举类

一个类的对象是有限而且固定的,比如季节,只有四个对象;比如行星,只有8个对象。这种实例有限而且固定的类,在Java里被称为枚举类。

6.8.1 枚举类定义

package com.langsin.test;

publicenum Planet {用num定义num类

MERCURY,

VENUS,

EARTH,

MARS,

JUPITER,

SATURN,

URANUS,

NEPTUNE,

}

使用方式:

package com.langsin.test;

publicclass Test {

publicstaticvoid main(String[] args) {

System.out.println(Planet.EARTH); // Planet.EARTH返回的是对象,对象的值为枚举值

Planet jupiter = Planet.JUPITER;

System.out.println(jupiter);

}

}

第7章:与运行环境交互

Java提供了丰富的基础类库,Java7提供了4000多个基础类,通过这些基础类可以提高开发效率,降低开发难度,对于一个合格程序员来说,至少要熟悉Java SE大部分的类的功能。

7.1 与用户互动

如果一个程序总是按照既定的流程运行,无序处理用户动作,这个程序总是比较简单的。实际上,绝大部分程序都需要处理用户的动作,包括接收用户的键盘输入、鼠标动作等。后面会讲到图形用户接口(GUI)编程就是基于图形面板输入框的程序编程,本章主要讲解如何获得键盘的输入。

7.1.1 运行Java程序的参数

main函数详解:

Public修饰符:Java类由JVM调用,为了让JVM可以自由的调用这个main方法,所以使用public修饰符把这个方法给暴漏出来。

Static修饰符:JVM调用这个方法时,不会先创建该主类的对象,然后通过对象来调用该方法。而是直接通过该类来调用主方法,因此使用static修饰该方法。

Void返回值:因为主方法被JVM调用,该方法的返回值将返回给JVM,这没有任何意义,因此main方法没有返回值。

举例1:

publicclass Test {

publicstaticvoid main(String[] args) {

System.out.println(args.length);

}

}

使用java命令来调用该类,JVM会默认执行该类的main方法。 java Test

在后面没有追加参数的情况下,打印长度为0

如果在调用该方法时传入参数,方式如下: java Test hello word ,在类名后面追加字符串参数,用空格隔开。

这时打印长度为2.

7.1.2 使用Scanner获取键盘输入

使用Scanner类可以很方便的获取用户的键盘输入,Scanner是一个基于正则表达式的文本扫描器,它可以从文件、输入流、字符串中解析出基本类型值和字符串值。

Scanner主要提供了两个方法来扫描输入。

hasNextXxx():是否还有下一个输入项,其中Xxx可以是Int、Long等代表基本数据类型的字符串。如果需要判断是否包含下一个字符串,可以省略Xxx。

nextXxx():获取下一个输入项。Xxx的含义与前一个方法中的Xxx相同。

举例1:

publicstaticvoid main(String[] args) {

Scanner sc = new Scanner(System.in); //表示标准键盘输入

while(sc.hasNext()){

System.out.println(sc.next());

}

}

使用ctrl+Z命令表示不再输入,即sc.hasNext()返回false。

举例2:读取当前文件,将文件输入到控制台

import java.util.Scanner;

import java.io.File;

public class Test{

public static void main(String args[]) throws Exception{

Scanner sn = new Scanner(new File("./Test.java"));

while(sn.hasNextLine()){

System.out.println(sn.nextLine());

}

}

}

7.2 系统相关

Java程序在不同操作系统上运行时,可能需要取得平台相关的属性,或者调用平台命令来完成特定的功能,Java提供了System类和Runtime类来与程序的运行平台进行交互。

7.2.1 System类

System类代表当前Java程序的运行平台,程序不能创建System类的对象,System类提供了一些类变量、类方法,允许直接通过System类来调用这些Field和方法。

static Map<String,String>getenv():返回一个不能修改的当前系统环境的字符串映射视图。

static String getenv(String name):获取指定的环境变量值。

static Properties getProperties():确定当前的系统属性。

static String getProperty(String key):获取指定键指示的系统属性。

static long currentTimeMillis():返回以毫秒为单位的当前时间。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Map<String,String> map = System.getenv();

for(String name : map.keySet()){

System.out.println(name+":"+map.get(name));

}

System.out.println("=====================");

System.out.println(System.getenv("JAVA_HOME"));

System.out.println(System.getenv("CLASSPATH"));

System.out.println("=====================");

Properties prop = System.getProperties();

System.out.println(prop.toString());

System.out.println(prop.getProperty("sun.boot.library.path"));

prop.store(new FileOutputStream("./props.properties"), "System properties");

}

举例2:

publicstaticvoid main(String[] args) throws Exception{

long begin = System.currentTimeMillis();

Map<String,String> map = System.getenv();

for(String name : map.keySet()){

System.out.println(name+":"+map.get(name));

}

long end = System.currentTimeMillis();

System.out.println("共耗时:"+(end-begin));

}

7.2.2 Runtime类

Runtime类代表Java程序的运行时环境,每个Java程序都有一个与之对应的Runtime实例,应用程序通过该对象与其运行时环境相连。应用程序不能创建自己的Runtime实例,但可以通过getRuntime()方法获取与之关联的Runtime对象。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Runtime rt = Runtime.getRuntime();

System.out.println("处理器数量:"+rt.availableProcessors());

System.out.println("空闲内存数:"+rt.freeMemory());

System.out.println("总内存数:"+rt.totalMemory());

System.out.println("可用最大内存数:"+rt.maxMemory());

}

7.3 常用类

String、Math、BigDecimal、Random等的用法

7.3.1 Object类

Object类是所有类、数组、枚举类的父类,也就是说,Java允许把任何类型的对象赋给Object类型的变量,当定义一个类时没有使用extends关键字为它显示指定父类,则该类默认继承Object类。可以说所有的Java类都是Object的子类,所以任何Java对象都可以调用Object类的方法。

常用方法:

boolean equals(Object obj):判断指定对象与该对象是否相等。

protected void finalize():当系统中没有引用变量指向该对象时,垃圾回收器调用此方法来清理该对象占用的资源。

Class<?> getClass():返回该对象的运行时类。

int hashCode():返回该对象的hashCode值。在默认情况下该方法根据此对象的的地址来计算。

String toString():返回该对象的字符串表示,Object类的toString()方法返回“运行时类名@十六进制hashCode值”格式的字符串。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Object obj = new Object();

System.out.println(obj.toString()); // java.lang.Object@de6ced

System.out.println(obj); // java.lang.Object@de6ced

System.out.println("======================");

System.out.println(obj.getClass()); // class java.lang.Object

Object obj2 = new Integer(12);

System.out.println(obj2.getClass()); // class java.lang.Integer

System.out.println("======================");

System.out.println(obj.hashCode()); //14576877

System.out.println(obj2.hashCode()); //12

}

7.3.2 String、StringBuffer和StringBuilder类

字符串就是一连串的字符序列,Java提供了String和StringBuffer两个类来封装字符串,并提供了一系列方法来操作字符串对象。

String类是不可变类,即一旦一个String对象被创建后,包含在这个对象中的字符序列是不可改变的,直至这个对象的销毁。

StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。

StringBuilder类,也代表了字符串对象。StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。不同的是StringBuffer是线程安全的,StringBuilder没有实现线程安全。因此在通常情况下优先考虑使用StringBuffer。

String类的构造器:

String():创建一个包含0个字符串序列的String对象。

String(byte[] bytes,Charset charset):使用指定的字符集将指定的byte[]数组解码成一个新的String对象。

String(byte[] bytes,int offset,int length):使用平台的默认字符集将从指定的byte[]数组的offset开始,长度为length的子数组成一个新的String对象。

String(byte[] bytes,int offset,int length,String charsetName):使用指定的字符集将指定的byte[]数组从offset开始,长度为length的子数组解码成一个新的String对象。

String(byte[] bytes,String charsetName):使用指定的字符集将指定的byte[]数组解码成一个新的String对象。

String(char[] value,int offset,int count):将指定的字符数组从offset开始,长度为count的字符元素连接成一个字符串。

String(String original):根据字符串常量来创建一个String对象。也就是说新创建的String对象是该参数字符串的副本。

String(StringBuffer buffer):根据StringBuffer对象来创建一个String对象

String(StringBuilder builder):根据StringBuilder对象来创建一个String对象。

String类提供大量方法来操作字符串对象:

char charAt(int index):获取字符串中指定位置的字符。

int compareTo(String anotherString):比较两个字符串大小,如果两个字符串的字符序列相等,则返回0,不相等时,从两个字符串第0个字符开始比较,返回第一个不相等的字符差。如果较长的字符串的前面部分与较短的字符串一样,则返回他们的长度差。

举例1:

String s1 = “abcdef”;

String s2 = “abcdefjhi”;

String s3 = “abcdefk”;

System.out.println(s2.compareTo(s1)); //返回3

System.out.println(s2.compareTo(s3)); //返回 -1

String concat(String str):将该String对象与str连接在一起。与Java提供的字符串连接运算符“+”相同。

boolean contentEquals(StringBuffer sb):将该String对象与StringBuffer对象sb进行比较,当他们包含的字符序列相同时返回true。

static String copyValueOf(char[] data):将字符数组组成一个字符串,与构造器String(char[] content)功能相同。

static String copyValueOf(char[] data,int offset,int count):将char数组的子数组中的元素连缀成字符串,与String(char[] value,int offset,int count)构造器功能相同。

boolean endsWith(String str):返回String对象是否以str字符串结尾。

boolean equals(Object anObject):将该字符串与指定对象进行比较,如果二者包含序列相等则返回true,否则返回false。

boolean equalsIgnoreCase(String str):将字符串与指定的对象进行比较,二者包含序列相同则返回true,否则返回false。只是该方法忽略大小写。

byte[] getBytes():将该String对象转换成byte数组。

String str = "abcd";

byte[] data = str.getBytes();

System.out.println(data.length); //4

System.out.println((char)data[0]); //a

void getChars(int srcBegin,int srcEnd,char[] dst,int dstBegin):该方法将字符串中从srcBegin开始到srcEnd结束的字符复制到dst字符数组中,其中dstBegin为目标字符数组的要拷贝的起始位置。

public static void main(String[] args) throws Exception{

char[] c1 = {'我','爱','北','京'};

String str = "济南";

str.getChars(0, 2, c1, 2);

System.out.println(c1);

}

int indexOf(int ch):找出ch字符在该字符串中第一次出现的位置。

int indexOf(int ch,int fromIndex):找出ch字符在该字符串中从fromIndex索引后面第一次出现的位置。

public static void main(String[] args) throws Exception{

String str = "你好,我好,他也好";

System.out.println(str.indexOf('好'));

System.out.println(str.indexOf('好', 3));

}

int indexOf(String str):找出str子字符串在该字符中第一次出现的位置。

int indexOf(String str,int fromIndex):找出str子字符串在该字符串中从fromIndex索引后第一次出现的位置。

public static void main(String[] args) throws Exception{

String str = "中华民族是一个56个民族的总称!";

System.out.println(str.indexOf("民族"));

System.out.println(str.indexOf("民族", 3));

}

int lastIndexOf(int ch):找出ch字符在该字符串中最后一次出现的位置。

int lastIndexOf(int ch,int fromIndex):找出ch字符在该字符串中从fromIndex开始最后一次出现的位置。

public static void main(String[] args) throws Exception{

String str = "中华民族是一个56个民族的总称!";

System.out.println(str.lastIndexOf('民'));

System.out.println(str.lastIndexOf('民', 3));

}

int lastIndexOf(String str):找出str字符串在该字符串中最后一次出现的位置。

int lastIndexOf(String str,int formIndex):找出str字符串在该字符串中从fromIndex索引开始后最后一次出现的位置。

int length():返回当前字符串的长度。

public static void main(String[] args) throws Exception{

String str = "中华民族是一个56个民族的总称!";

int length = str.length();

System.out.println(length);

}

String replace(String oldChar,String newChar):将字符串中的所有的oldChar替换成newChar。

String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式regex的子字符串。

publicstaticvoid main(String[] args) throws Exception{

String msg = "你好,我也好,大家好才是真的好";

String oldChar = "你好";

String newChar = "伱好";

msg = msg.replace(oldChar, newChar);

System.out.println(msg);

oldChar = "好";

newChar = "坏";

msg = msg.replaceAll(oldChar, newChar);

System.out.println(msg);

}

char[] toCharArray():将该字符串对象转换成字符数组。

String toLowerCase():将字符串转换成小写。

String toUpperCase():将字符串转换成大写。

String[] split(String reg):将字符串按照指定的正则表达式进行拆分。

String subString(int index):从指定的索引的位置开始直到字符串结束位置,将其作为字符串进行返回。

String subString(int beginIndex,int endIndex)

7.3.3 Math类

Java提供了最简单加、减、乘、除、取模等基本运算。同时还提供了Math类来进行更复杂的数学运算。

static abs(Xxx xx):将基本数据类型转换成其绝对值返回。

static max(Xxx a,Xxx b):返回两个值中较大的一个

static min(Xxx a,Xxx b):返回两个值中较小的一个

static pow(double a,double b):返回第一个参数的第二个参数次幂的值。

static double random():返回带正号的 double 值,该值大于等于 0.0 且小于 1.0。

publicstaticvoid main(String[] args) throws Exception{

//取某一个范围内的随机数

for(int i=0;i<5;i++){

System.out.println((int)(Math.random()*100));

}

}

7.3.4 Random类

Random类专门用于生成一个随机数。主要方法为:nextXxx(Xxx xxx)

static Xxx nextXxx(Xxx xxx):返回该类型的一个随机数。

7.3.5 BigDecimal类

double、float浮点数在进行数据基本运算时会发生数据丢失现象,这种情况不仅仅是在Java语言中,在其他语言中同样存在。例如:

public static void main(String[] args) throws Exception{

System.out.println(0.05+0.01);

System.out.println(1.0-0.42);

System.out.println(4.015*100);

System.out.println(123.3/100);

}

Java提供了BigDecimal类,用于处理进行基本运算发生的数据丢失问题。

使用BigDecimal构造器创建对象时,建议优先使用String类型对象作为参数传入进行初始化。

举例: BigDecimal decimal = new BigDecimal(“12.5”);

BigDecimal类提供了add()、subtract()、multiply()、divide()、pow()、等方法对精确浮点数进行常规算术运算。

举例1:

publicstaticvoid main(String[] args) throws Exception{

BigDecimal b1 = new BigDecimal("0.05");

BigDecimal b2 = new BigDecimal("0.01");

System.out.println(b1.add(b2).floatValue());

b1 = new BigDecimal("1.0");

b2 = new BigDecimal("0.42");

System.out.println(b1.subtract(b2).floatValue());

b1 = new BigDecimal("4.015");

b2 = new BigDecimal("100");

System.out.println(b1.multiply(b2).floatValue());

b1 = new BigDecimal("123.3");

b2 = new BigDecimal("100");

System.out.println(b1.divide(b2).floatValue());

}

7.4 日期处理类

Java提供了一系列用于处理日期、时间的类,包括创建日期、时间对象,获取系统当前日期、时间等操作。

7.4.1 Date类

Java提供了Date类来处理日期、时间,Date类既包含日期,也包含时间。Date类从JDK1.0版本就开始存在,存在时间久远,提供的6个构造器中,已有4个建议放弃使用,目前使用的为:

Date():生成一个以系统当前时间日期为准的Date对象。

Date(long date):根据指定的long整型数来生成一个Date对象。

boolean after(Date when):判断日期是否在指定的日期when之后

boolean before(Date when):判断日期是否在指定的日期when之前。

int compareTo(Date antherDate):比较两个日期大小,后面时间大于前面时间返回-1,否则返回1.

Boolean equals(Object obj):两个时间表示同一时间是返回true

long getTiem():返回该对象对应long型整数

void setTime(long time):设置该对象的时间。

publicstaticvoid main(String[] args) throws Exception{

//创建一个当前系统时间对象

Date date1 = new Date();

//将当前线程休眠4秒钟

Thread.sleep(4000);

//休眠4秒钟后,创建date2时间对象

Date date2 = new Date();

//

System.out.println(date1.before(date2));

System.out.println(date1.after(date2));

System.out.println(date1.compareTo(date2));

date2.setTime(date1.getTime());

System.out.println(date1.equals(date2));

}

7.4.2 Calendar类

Calendar是一个抽象类,用于表示日历。因为Date类在设计上存在一些缺陷,所以Java提供Calendar类来更好的处理日期和时间。

Calendar类不能直接创建,可以通过类方法来创建一个Calendar的子类,Java本身提供了GregorianCalendar子类,通过getInstance()方法返回默认时区和语言环境获得一个日历。返回的实例就是Java提供的默认子类

举例1:

publicstaticvoid main(String[] args) throws Exception{

Calendar cal = Calendar.getInstance();

System.out.println(cal.getClass()); //class java.util.GregorianCalendar

}

Calendar与Date都是表示日期的工具类,他们之间可以相互转换。

举例2:

publicstaticvoid main(String[] args) throws Exception{

Calendar cal = Calendar.getInstance();

Date date = cal.getTime();

System.out.println(date);

Calendar cale = Calendar.getInstance();

cale.setTime(date);

}

Calendar类提供的常用方法:

void add(int field,int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。

int get(int field):返回指定日历字段的值。

int getActualMaximum(int field):返回指定日历字段的可能拥有的最大值。

int getActualMinimum(int field):返回指定日历字段的可能拥有的最小值。

void roll(int field,int amout):与add方法相似,区别在于超过该字段的最大范围时,也不会向上一个字段进位。

void set(int field,int value):将给定的日历字段设置为给定值。

void set(int year,int month,int date):设置Calendar对象的年、月、日3个字段值。

void set(int year,int month,int date,int hourOfDay,int minute,int second):设置Calendar对象的年、月、日、时、分、秒6个字段的值。

publicstaticvoid main(String[] args) throws Exception{

Calendar cal = Calendar.getInstance();

System.out.println(cal.get(Calendar.YEAR)); //2014

cal.add(Calendar.MONTH, 12);

System.out.println(cal.get(Calendar.YEAR)); //2015

System.out.println("==================================");

cal = Calendar.getInstance();

System.out.println(cal.get(Calendar.YEAR)); //2014

cal.roll(Calendar.MONTH, 12);

System.out.println(cal.get(Calendar.YEAR)); //2014

System.out.println("==================================");

System.out.println(cal.getActualMaximum(Calendar.MONTH)); //11

System.out.println(cal.getActualMinimum(Calendar.MONTH)); //0

}

7.4.3 使用DateFormat格式化日期

DateFormat是一个抽象类,它也提供了几个工厂方法用于获取DateFormat对象。返回的都是DateFormat对象的子类实例,是同一个类型的实例。

举例1:

publicstaticvoid main(String[] args) throws Exception{

DateFormat format = DateFormat.getInstance();

System.out.println(format.getClass());

format = DateFormat.getDateInstance();

System.out.println(format.getClass());

format = DateFormat.getTimeInstance();

System.out.println(format.getClass());

}

输出信息:class java.text.SimpleDateFormat

虽然返回的都是同一个对象类型的实例,但是根据不同的工厂方法,返回的对象在格式化时间对象时,处理不同部分的信息。

getDateInstance():返回一个日期格式器,只对日期进行格式化。

getTimeInstance():返回一个时间格式器,只对时间进行格式化。

getDateTimeInstance():返回一个日期、时间格式器,既对时间又对日期进行格式化。

getInstance():返回一个默认的系统相关的日期、时间格式器。

publicstaticvoid main(String[] args) throws Exception{

Date date = new Date();

//默认为中国常用格式

DateFormat format = DateFormat.getInstance();

System.out.println(format.format(date));

//返回日期、时间格式器国际统一标准

format = DateFormat.getDateTimeInstance();

System.out.println(format.format(date));

//返回日期格式器,同时指定格式类型,为简洁型,指定要显示哪个国家的类型

format = DateFormat.getDateInstance(DateFormat.SHORT, Locale.CHINA);

System.out.println(format.format(date));

//返回日期格式器,同时指定格式类型,为中等型,指定要显示哪个国家的类型

format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.CHINA);

System.out.println(format.format(date));

//返回日期格式器,同时指定格式类型,为完整型,指定要显示哪个国家的类型

format = DateFormat.getDateInstance(DateFormat.LONG, Locale.CHINA);

System.out.println(format.format(date));

//返回日期格式器,同时指定格式类型,为复杂型,指定要显示哪个国家的类型

format = DateFormat.getDateInstance(DateFormat.FULL, Locale.CHINA);

System.out.println(format.format(date));

}

7.4.4 使用SimpleDateFormat格式化日期

SimpleDateFormat是DateFormat的子类,是一种更简单的日期格式,以为我们指定的格式对日期进行格式化。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Date date = new Date();

SimpleDateFormat simple = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");

System.out.println(simple.format(date));

simple = new SimpleDateFormat("yyyy/MM/dd");

System.out.println(simple.format(date));

}

7.5 正则表达式

正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割替换等操作。String类提供的关于正则表达式的处理方法:

boolean matches(String regex):判断该字符串是否匹配指定的正则表达式。

String replaceFirst(String regex,String replacement):将该字符串中第一个匹配regex的子串替换成replacement。

String[] split(String regex):将regex作为分隔符将字符串拆分字符数组。

7.5.1 创建正则表达式

正则表达式就是一个用于匹配字符串的模板,可以匹配一批字符串,所以创建正则表达式就是创建一个特殊的字符串。

正则表达式的特殊字符及含义 :

举例1:

“”匹配a

“\\?\\[” 匹配 ?[

预定义字符

方括号表达式

花括号表达式

举例1:

1.验证用户名和密码:("^[a-zA-Z]\\w{5,15}$")正确格式:"[A-Z][a-z]_[0-9]"组成,并且第一个字必须为字母6~16位;

2.验证电话号码:("^(\\d{3,4}-)\\d{7,8}$")正确格式:xxx/xxxx-xxxxxxx/xxxxxxxx;

3.验证身份证号(15位或18位数字):("^[1-9]\\d{14}|[1-9]\\d{16}(\\d|X|x)$");

4.验证Email地址:("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");

5.只能输入由数字和26个英文字母组成的字符串:("^[A-Za-z0-9]+$") ;

6.整数或者小数:^[0-9]+\\.{0,1}[0-9]{0,2}$

7.只能输入数字:"^[0-9]*$"。

8.只能输入n位的数字:"^\\d{n}$"。

9.只能输入至少n位的数字:"^\\d{n,}$"。

10.只能输入m~n位的数字:。"^\\d{m,n}$"

11.只能输入零和非零开头的数字:"^(0|[1-9][0-9]*)$"。

12.只能输入有两位小数的正实数:"^[0-9]+(.[0-9]{2})?$"。

13.只能输入有1~3位小数的正实数:"^[0-9]+(.[0-9]{1,3})?$"。

14.只能输入非零的正整数:"^+?[1-9][0-9]*$"。

15.只能输入非零的负整数:"^-[1-9][]0-9"*$。

16.只能输入长度为3的字符:"^.{3}$"。

17.只能输入由26个英文字母组成的字符串:"^[A-z]+$"。

18.只能输入由26个大写英文字母组成的字符串:"^[A-Z]+$"。

19.只能输入由26个小写英文字母组成的字符串:"^[a-z]+$"。

20.验证是否含有^%&',;=?$\\"等字符:"[^%&',;=?$\\\\x22]+"。

21.只能输入汉字:"^[\\\\u4e00-\\\\u9fa5]{0,}$"

22.验证URL:"^http://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?$"。

23.验证一年的12个月:"^(0?[1-9]|1[0-2])$"正确格式为:"01"~"09"和"1"~"12"。

24.验证一个月的31天:"^((0?[1-9])|((1|2)[0-9])|30|31)$"正确格式为;"01"~"09"和"1"~"31"。

25.获取日期正则表达式:\\d{4}[年|\\-|\\.]\\d{\\1-\\12}[月|\\-|\\.]\\d{\\1-\\31}日?

7.5.2 使用正则表达式

在程序中使用了正则表达式,就可以使用Java提供的Pattern和Matcher类来使用正则表达式。

Pattern对象是正则表达式编译后在内存中的表示形式,因此正则表达式字符串必须先被编译为Pattern对象,然后再利用该Pattern对象创建对应的Matcher对象。执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可共享同一个Pattern对象。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Pattern pattern = Pattern.compile("^-[1-9][0-9]*$");

Matcher m = pattern.matcher("12");

System.out.println(m.matches());

}

如果某个正则表达式仅需要一次使用,则可直接使用Pattern类的静态matches方法,此方法自动把指定字符串编译成匿名的Pattern对象,并执行匹配

举例2:

publicstaticvoid main(String[] args) throws Exception{

boolean flag = Pattern.matches("^+?[1-9][0-9]*$", "12");

System.out.println(flag);

}

Pattern同String一样,也是不可变类,可提供多个并发线程安全使用。

Matcher类提供的常用方法:

find():返回目标字符串是否包含于Pattern匹配的子串。

matches():返回整个目标字符串与Pattern是否匹配。

reset(String newStr):将现有的Matcher对象重新应用于一个新的newStr对象。

lookingAt():返回目标字符串是否以Pattern开头。

举例3:

publicstaticvoid main(String[] args) throws Exception{

String[] mails = {"zhangsan@163.com","lisi@csdn.net","wangwu@google.org"};

Pattern pattern = Pattern.compile("\\\\w{3,20}@\\\\w+\\\\.com|net|org|gov)");

Matcher matcher = null;

for(String mail : mails){

if(matcher==null){

matcher = pattern.matcher(mail);

}else{

matcher.reset(mail);

}

System.out.println(mail+(matcher.matches()?"是":"不是")+"一个有效的邮件地址!");

}

}

练习:1、写一个手机验证 130 131 132 133 134 135 136 137 138 139 150 151 152 155 158 159 180 181 182 183

2、写一个浮点数的正则表达式 0.12 10.11 123.34

7.6 国际化与格式化

国际化是指应用程序运行时,可根据客户端请求来自的国家/地区、语言的不同而显示不同的界面。例如,如果请求来自中文操作系统的客户端,则应用程序中的各个提示信息和帮助等都使用中文文字;如果客户端使用英文操作系统,则应用程序能自动识别,并做出英文的响应。

7.6.1 Java国际化的思路

Java程序的国际化思路是将程序中的标签、提示等信息放在资源文件中,程序需要支持哪些国家、语言环境,就对应提供相应的资源文件。

Java程序的国际化主要通过三个类来完成:

java.util.ResourceBundle:用于加载国家、语言资源包

java.util.Locale:用于封装特定的国家/区域、语言环境。

java.text.MessageFormat:用于格式化带占位符的字符串。

为了实现程序的国际化,必须先提供程序所需的资源文件。资源文件的内容时很多key-value对,其中key是程序使用的部分,而value则是程序界面的显示字符串。

资源文件的命名可以有如下三种形式:

baseName_language_country.properties

baseName_language.properties

baseName.properties

其中baseName是资源文件的基本名,可随意指定,而language和country都不可以随意变化而必须是Java所支持的语言和国家。

7.6.2 Java支持的国家和语言

Java支持绝大部分的国家和语言,如果需要获取Java所支持的国家和语言,则可调用Locale类的getAvailableLocales()方法,该方法返回个Locale数组,该数组包含了Java所支持的国家和语言。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Locale[] locales = Locale.getAvailableLocales();

for(int i=0;i<locales.length;i++){

System.out.println(locales[i].getDisplayCountry()+":"+locales[i].getCountry()+""+locales[i].getDisplayLanguage()+":"+locales[i].getLanguage());

}

}

获取系统默认的国家/语言环境

Locale.getDefaule()

举例2:

publicstaticvoid main(String[] args) throws Exception{

Locale china = Locale.getDefault();

System.out.println(china.getLanguage()+"_"+china.getCountry());

}

7.6.3 完成程序国际化

对下面程序完成国际化

publicstaticvoid main(String[] args) throws Exception{

System.out.println("Hello world");

}

为上面程序提供如下两个文件

1、mess.propterites

文件内容为:

Hello world = Hello,world!

2、mess_ch_CN.propterties,此文件由native2ascii工具来生成,此工具的作用是将资源文件中的编码转换成系统使用Unicode字符编码。

文件内容为:

Hello world = 世界,你好!

命令格式如下:native2ascii mess.properties mess_zh_CN.properties

在MyEclipse中创建属性文件时,编辑工具已经提供了此功能,因此可以直接写资源文件,而略过第一步

此文件放置在源码文件src下。

3、程序修改如下:

publicstaticvoid main(String[] args) throws Exception{

Locale china = Locale.getDefault();

ResourceBundle bundle = ResourceBundle.getBundle("mess",china);

System.out.println(bundle.getString("HelloWorld"));

}

讲解:ResourceBundle.getBundle("mess",china);当不指定系统默认的语言环境时,即使用ResourceBundle.getBundle("mess");系统会默认查找当前语言环境下的资源文件,例如当前语言环境时中国,则会查找mess_zh_CN.properties文件,在系统中查找不到此文件时,才会找mess.properties属性文件。

第8章:Java集合框架

Java集合类是一种特别有用的工具类,可以用于存储数量不等的多个对象,并可以实现常用的数据结构,如栈、队列等。除此之外,Java集合还可以用于保存具有映射关系的关联数组。Java集合分为:Set、List和Map三种体系,其中Set代表无序、不可重复的集合;List代表有序、可重复的集合;Map则代表具有映射关系的集合。

8.1 集合概述

在编程时,常常需要集中存放多个数据,起初我们使用数组来保存多个对象,但数组长度不可变化,一旦在初始化数组时指定了长度,那么这个数组长度就是不可变的。另外如果我们要保存具有映射关系的的数据,例如:成绩表:语文—79,数学—80,数组就有点不能胜任。

为了保存数量不确定的数据,以及保存具有映射关系的数据。Java提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所有的集合类都位于java.util包下。

集合类与数组不一样,数组元素可以是基本类型的数据,也可以是对象,而集合里只能保存对象。

Java的集合类主要由两个接口派生而来:Collection和Map。Collection和Map是Java集合框架的根接口,这两个接口有包含了一些子接口或实现类。

上图显示了Collection体系里的集合,其中Set和List接口都是Collection接口派生出的两个子接口,他们分别代表了无序集合和有序集合;Queue是Java提供的队列实现。

上图为Map体系结构示意图,所有的Map实现类用于保存具有映射关系的数据。Map保存的每项数据都是key-value两个值组成。Map中的key值是不可重复的,key用于标识集合里的每项数据。

对于Java的所有集合分类,可以分为三大类,

set集合:类似于一个罐子,将对象存放在Set集合时,Set集合无法记住这个元素的顺序,所以Set里的元素不能重复。

List集合:非常类似于数组,它可以记住每次添加的元素的顺序,不同于数组的是List的长度是可变的。

Map集合:类似于中药橱柜,每个橱柜上的抽屉都带有标签,标签相当于key,抽屉相当于对象。可以根据key来找到对象。

对于Set、List、Queue和Map四种集合,最常用的类为:HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap、TreeMap。

8.2 Collection和Iterator接口

Collection接口是List、Set、Queue接口的父接口,该接口里定义的方法可用于操作Set集合、List集合、Queue集合。其中定义的操作集合的常用方法如下:

boolean add(Object obj):该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了,则返回true。

boolean addAll(Collection c):该方法把集合C里的所有元素添加到指定集合里。如果集合对象被添加操作改变了,则返回true。

void clear():清除集合里的所有元素,将集合长度变为0。

boolean contains(Object o):返回集合里是否包含指定元素。

boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素。

boolean isEmpty():返回集合是否为空。当长度为0时返回true,否则返回false。

Iterator iterator():返回一个Iterator对象,用于遍历集合里的元素。

boolean remove(Object o):删除集合中的指定元素o,当集合中包含了一个或多个元素o时,这些元素将被删除,该方法返回true。

boolean removeAll(Collection c):从集合中删除集合c里包含的所有元素,如果删除一个或一个以上 的元素,则该方法返回true。

boolean retainAll(Collection c):从集合中删除集合c里不包含的元素,如果该操作改变了调用该方法的集合,则该方法返回true。

int size():返回集合里元素的个数。

Object[] toArray():该方法把集合转换成一个数组。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Collection c = newArrayList();

c.add("abc");

c.add(1);

System.out.println("集合C的长度"+c.size()); 2

c.remove(1);

System.out.println("集合C的长度"+c.size()); 1

System.out.println("集合c中是否包含abc"+c.contains("abc"));

Collection c1 = newHashSet();

c1.add("abc");

c1.add("abc");

c1.add(1);

System.out.println("集合C1的长度"+c1.size()); 2

System.out.println("集合C是否包含C1集合:"+c.containsAll(c1));

c.removeAll(c1);

System.out.println("集合C的长度"+c.size()); // 0

c.clear();

System.out.println("集合C的长度"+c.size()); //0

c1.retainAll(c);

System.out.println("集合C1的长度"+c1.size()); //0

}

8.2.1 使用Iterator接口遍历集合元素

Iterator接口也是Java集合框架的成员,但它与Collection、Map集合不一样。Collection、Map集合主要用来盛装对象,而Iterator则主要用来遍历(迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。

Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口。

boolean hasNext():如果被迭代的集合元素还没有被遍历,则返回true。

Object next():返回集合里的下一个元素。

Void remove():删除集合里上一次next方法返回的元素。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Set set = newHashSet();

set.add("张三");

set.add("李四");

set.add("王五");

Iterator iterator = set.iterator();

while(iterator.hasNext()){

if("张三".equals(iterator.next())){

iterator.remove();

}

}

System.out.println(set);

}

当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只能通过Iterator的remove方法删除上一次next方法返回的集合元素才可以,否则将会引发并发修改异常:java.util.ConcurrentModificationException。

举例2:

publicstaticvoid main(String[] args) throws Exception{

Set set = newHashSet();

set.add("张三");

set.add("李四");

set.add("王五");

Iterator iterator = set.iterator();

while(iterator.hasNext()){

if("张三".equals(iterator.next())){

set.remove("张三");

}

}

System.out.println(set);

}

这一点类似于windows系统中,当我们打开一个文件时,又对这个文件执行了删除操作。Windows总会提示我们文件正在被使用,不能删除一个道理。

8.2.2 使用foreach循环遍历集合元素

除了使用Iterator接口迭代访问Collection集合里的元素之外,还可以使用foreach循环迭代访问集合元素更加便捷。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Set set = newHashSet();

set.add("张三");

set.add("李四");

set.add("王五");

for(Object obj : set){

System.out.println(obj);

}

}

注意:使用foreach循环来遍历Collection集合时,与Iterator接口一样,迭代对象并不是元素对象本身,而是系统依次把集合元素对象的值赋给了迭代变量而已。

8.3 Set集合

Set集合是Collection集合的子类,与Collection基本上完全一样,它没有提供额外的方法,只是在行为上略有不同。

Set集合不允许包含相同的元素,如果把两个相同的元素加入到同一个Set集合中去,则添加操作失败,add方法返回false,且新元素不会被加入。

Set判断两个对象相同不是使用==运算符,而是使用equals方法。也就是说,只要两个对象equals方法比较返回true,Set就不会接受这两个对象;反之,则可以。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Set set = newHashSet();

set.add("zhangsan");

set.add("zhangsan");

System.out.println(set.size()); //1

set.add(new String("zhangsan"));

set.add(new String("zhangsan"));

System.out.println(set.size()); //2

}

因为:String对象使用equals比较是相同所以,只添加了一个。

publicstaticvoid main(String[] args) throws Exception{

Car car1 = new Car();

Car car2 = new Car();

Set set = newHashSet();

set.add(car1);

set.add(car2);

System.out.println(set.size()); //2

}

car1与car2分别指向了两个不同的对象,因此使用equals比较时返回false,所以set添加了两个对象。

具体来看下HashSet、TreeSet和EnumSet三个实现类。

8.3.1 HashSet类

HashSet是Set接口的典型实现,大多数时候使用Set集合时就是使用这个实现类。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。

HashSet具有以下特点:

不能保证元素的排列顺序,排列顺序可能与添加顺序不同。

HashSet不是同步的,如果多个线程同时访问一个HashSet时,假设有一个或多个线程同时修改了HashSet集合时,则必须通过代码块来保证其同步。

集合元素可以是null。

当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的值,然后根据该HashCode()值决定该对象在HashSet中的存储位置。如果两个元素通过equals()方法比较返回true,而他们的hashCode()方法返回值不同,HashSet会将他们存储在不同的位置,依然添加成功。这就与Set集合的规则有些出入了。

publicclass TestOne {

privateint num = 1;

public TestOne(int num){

this.num = num;

}

publicboolean equals(Object obj) {

returntrue;

}

publicint hashCode() {

returnthis.num;

}

}

publicstaticvoid main(String[] args) throws Exception{

TestOne one = new TestOne(1);

TestOne two = new TestOne(2);

System.out.println(one.equals(two));

Set set = newHashSet();

set.add(one);

set.add(two);

System.out.println(set.size()); // 1

}

注意:如果通过equals方法比较返回true,同时根据hashCode()方法获取的返回值也相同,则只能存储一个对象。

当把一个对象放入到HashSet中时,如果重写了这个对象的equals方法,那么也必须重写这个对象的hashCode方法。其规则就是如果equals方法返回true,那么这两个对象的hashCode的值也应该相同。

如果两个对象通过equals方法比较返回false,而hashCode值返回一样,这就有点违背了HashSet的设计规则,本来通过Hash算法我们可以计算对象的存储位置,现在却成了在同一个位置上存储了两个对象。从而减低了HashSet快速查找对象的功能。

8.3.2 LinkedHashSet类

LinkedHashSet是HashSet的子类,LinkedHashSet同样是根据元素的hashCode值来决定元素的存储位置,但与HashSet不同的是,LinkedHashSet在存储对象时同时使用链表维护了元素的次序,即:当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按照元素的添加顺序来访问集合里的元素。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Set set = newLinkedHashSet();

set.add("张三");

set.add("李四");

set.add("王五");

System.out.println(set);

}

输出结果:[张三, 李四, 王五]

8.3.3 TreeSet类

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。与HashSet相比,TreeSet还提供了额外方法:

Comparator comparator():如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator,如果TreeSet采用了自然排序,则返回null。

Object first():返回集合中的第一个元素。

Object last():返回集合中的最后一个元素。

Object lower(Object obj):返回集合中位于指定元素之前的元素。

Object higher(Object obj):返回集合中位于指定元素之后的元素。

SortedSet subSet(fromElement,toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。

SortedSet headSet(toElement):返回Set的子集,由小于toElement的元素组成。

SortedSet tailSet(fromElement):返回Set的子集,由大于或等于fromElement的元素组成。

举例1:

publicstaticvoid main(String[] args) throws Exception{

TreeSet set = newTreeSet();

set.add(12);

set.add(3);

set.add(6);

set.add(8);

System.out.println(set);

System.out.println(set.first());//3

System.out.println(set.last()); //12

System.out.println(set.headSet(8)); // 6 3

System.out.println(set.tailSet(6)); //6 8 12

System.out.println(set.subSet(3, 8)); // 3 6

}

HashSet集合采用hash算法来决定元素的存储位置,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序。

自然排序

TreeSet会调用集合元素的compareTo(Object obj):方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。

Java中提供了一个Comparable接口,此接口中定义了一个方法compareTo(Object obj),该方法返回一个整数值,实现了该接口的类的对象就可以比较大小。由于在向TreeSet集合中存放数据时,TreeSet会调用该元素对象的compareTo方法,因此往TreeSet集合中存放是元素对象必须实现了该方法。

大部分类在实现compareTo方法时,都需要将被比较对象obj强制转换成相同类型,因为只有相同类型的实例才能比较大小。因此TreeSet集合中存放的元素必须是同一类型的实例。

否则将会抛出ClassCastException(强制类型转换异常)

举例1:

package com.langsin.test;

publicclass A {

}

package com.langsin.test;

publicclass B {

}

publicstaticvoid main(String[] args) throws Exception{

TreeSet set = newTreeSet();

set.add(new A());

set.add(new B());

System.out.println(set);

}

举例2:将A类与B继承接口Compareable,存储时不会报错

package com.langsin.test;

publicclass A implementsComparable{

publicint compareTo(Object o) {

return 1;

}

}

package com.langsin.test;

publicclass B implementsComparable{

publicint compareTo(Object o) {

return 1;

}

}

publicstaticvoid main(String[] args) throws Exception{

TreeSet set = newTreeSet();

set.add(new A());

set.add(new B());

System.out.println(set);

}

举例3:

package com.langsin.test;

publicclass Student implements Comparable<Student>{

public Student(String classNo){

this.classNo = classNo;

}

private String classNo=null;

publicint compareTo(Student st){

returnthis.classNo.compareTo(st.classNo);

}

public String getClassNo(){

returnthis.classNo;

}

}

publicstaticvoid main(String[] args) throws Exception{

Student st1 = new Student("20140701");

Student st2 = new Student("20140702");

Student st3 = new Student("20140703");

TreeSet<Student> set = new TreeSet<Student>();

set.add(st3);

set.add(st1);

set.add(st2);

System.out.println(set.first().getClassNo());

}

定制排序

TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排序。如果实现定制排序,比如降序,需要通过Comparator接口来实现。在创建TreeSet实例的时候,不在使用TreeSet默认的比较,通过Comparator接口实现自己的比较器实例,将比较器的实例作为参数通过TreeSet的构造器传递给集合,那么在往集合元素中存放数据的时候就会按照我们的指定顺序进行排序。实现Comparator接口的int compare(T t1,T t2)方法,此方法在往集合元素中添加元素对象时被调用,该方法返回一个int类型值,返回正整数,表示t1大于t2,返回负整数,表示t1小于t2,返回0表示,t1等于t2.

举例1:

publicstaticvoid main(String[] args) throws Exception{

Comparator<Integer> comparator = new Comparator<Integer>(){

publicint compare(Integer num1, Integer num2) {

if(num1>num2){

return -1;

}elseif(num1<num2){

return 1;

}else{

return 0;

}

} ///梅花2 黑桃2 红桃5 黑桃5 方块6 黑桃 红桃 梅花 方块

};

TreeSet set = new TreeSet(comparator);

set.add(1);

set.add(12);

set.add(5);

System.out.println(set);

}

8.3.4 EnumSet类

EnumSet类是枚举集合类,集合中存放的元素都必须是指定枚举类型的枚举值,该值在创建EnumSet时显示或隐式地指定。

EnumSet类没有提供公有属性的构造器来创建该类的实例,程序应该通过EnumSet提供是static方法来创建EnumSet对象。常用的方法如下:

Static EnumSet allOf(Class elementType):创建一个包含了枚举类中所有枚举值的EnumSet集合。

Static EnumSet complementOf(EnumSet s):创建一个其元素类型与指定的EnumSet里元素类型相同的EnumSet集合,新的EnumSet集合中包含了原EnumSet集合中所不包含的,剩下的所有枚举值。

Static EnumSet copyOf(Collection c):使用一个普通集合来创建一个EnumSet集合。普通集合Collection参数不能为空,里面必须含有元素对象。否则抛出java.lang.IllegalArgumentException(参数不合理异常)。

Static EnumSet copyOf(EnumSet s):创建一个与指定的EnumSet具有相同元素类型、相同集合元素的EnumSet集合。完整的copy一份。

Static EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet。

Static EnumSet range(E from,E to):创建一个包含了从from开始到to结束的范围内所有的枚举值集合。

举例1:

枚举类

package com.langsin.test;

publicenum Planet {

MERCURY,

VENUS,

EARTH,

MARS,

JUPITER,

SATURN,

URANUS,

NEPTUNE,

PLUTO

}

测试类:

publicstaticvoid main(String[] args) throws Exception{

EnumSet<Planet> set = EnumSet.allOf(Planet.class);

System.out.println(set.add(Planet.EARTH));

System.out.println(set.size());

Collection<Planet> collection = new HashSet<Planet>();

collection.add(Planet.MARS);

EnumSet<Planet> set1 = EnumSet.copyOf(collection);

System.out.println(set1.size());

EnumSet<Planet> set2 = EnumSet.noneOf(Planet.class);

System.out.println(set2.size());

EnumSet<Planet> set3 = EnumSet.range(Planet.MERCURY, Planet.EARTH);

System.out.println(set3.size());

}

8.4 List集合

List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List集合默认按元素的添加顺序设置元素的索引。

8.4.1 List接口和ListIterator接口

List作为Collection接口的子接口,可以使用Collection接口里的全部方法。由于List又是一个有序的集合,所以List集合中又增加了一些根据索引来操作集合元素的方法。

Void add(int index,Object element):将元素element插入到List集合的index处。如果当前索引位置有元素对象,则将此元素对象及后面所有元素对象都向后移动一个索引位置。

Boolean addAll(int index,Collection c):将集合c所包含的所有元素都插入到List集合中的index索引位置。

Object get(int index):返回集合index索引位置的元素。

Int indexOf(Object o):返回对象o在List集合中第一次出现的位置索引。

Int lastIndexOf(Object o):返回对象o在List集合中最后一次出现的位置索引。

Object remove(int index):删除index索引位置的元素,并将此对象返回。同时此元素后面的所有元素索引向前前进一位。

Object set(int index,Object element):将index索引处的元素替换成element对象,并将被替换的元素返回。

List subList(int fromIndex,int toIndex):返回从元素索引fromIndex到索引toIndex处所有集合元素组成的子集合。

举例1:

publicstaticvoid main(String[] args) throws Exception{

List list = newArrayList();

list.add("123");

list.add("456");

list.add(1, "789");

System.out.println(list.get(1));

System.out.println(list.get(2));

System.out.println("=================");

System.out.println(list.indexOf("456"));

System.out.println("=================");

Object obj = list.remove(1);

System.out.println(obj);

System.out.println(list.size());

System.out.println("=================");

obj = list.set(1,"abc");

System.out.println(list.get(1));

List list1 = list.subList(0, 2);

System.out.println(list1.size());

}

ListIterator接口:List提供了一个listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,还提供了专门操作List的方法。方法如下:

Boolean hasPrevious():返回该迭代器关联的集合是否含有上一个元素。

Object previous():返回迭代器的上一个元素。

void add():在指定位置插入一个元素。

ListIterator与普通的Iterator进行对比,ListIterator增加向前迭代的功能,而且还通过add方法向List集合中添加元素。

举例1:

publicstaticvoid main(String[] args) throws Exception{

List list = newArrayList();

list.add("123");

list.add("456");

list.add("789");

ListIterator iter = list.listIterator();

while(iter.hasNext()){

System.out.println(iter.next());

iter.add("abc");

}

System.out.println("===================");

while(iter.hasPrevious()){

System.out.println(iter.previous());

}

}

8.4.2 ArrayList和Vector实现类

ArrayList和Vector是List接口的典型实现,都是基于数组实现的List类。ArrayList和Vector都封装了一个动态的允许再分配的Object[]数组。

ArrayList和Vector的用法几乎完全一样,但是Vector与ArrayList相比较而言是属于古董级别,在JDK1.0时就已经存在。那时Java还没有提供系统的集合框架,Vector中提供了很多方法名很长的方法。从JDK1.2以后,Java提出系统集合框架之后,将Vector改为实现List接口,作为List的实现类之一,从而导致了Vector中存在了一些功能重复的方法。

因此尽量少用Vector,因为能用Vector的地方就能用ArrayList来代替。除此之外,ArrayList与Vector的显著区别是:ArrayList是线程不安全的,Vector是线程安全,所以Vector的性能要比ArrayList要低一些。即使是为了保证List集合的线程安全,同样不推荐使用Vector实现类,后面会介绍Collections工具类,它会是ArrayList变成线程安全的。

举例1:

package com.langsin.test;

publicclass Product {

//商品名称

private String p_name;

//商品编码

private String p_code;

//商品名称

private String p_price;

/**

* @return商品名称

*/

public String getP_name() {

return p_name;

}

/**

* @param商品名称

*/

publicvoid setP_name(String p_name) {

this.p_name = p_name;

}

/**

* @return商品编码

*/

public String getP_code() {

return p_code;

}

/**

* @param商品编码

*/

publicvoid setP_code(String p_code) {

this.p_code = p_code;

}

/**

* @return商品价格

*/

public String getP_price() {

return p_price;

}

/**

* @param商品价格

*/

publicvoid setP_price(String p_price) {

this.p_price = p_price;

}

}

publicstaticvoid main(String[] args) throws Exception{

List<Product> list = new ArrayList<Product>();

Product p1 = new Product();

p1.setP_code("JD001");

p1.setP_name("康佳32吋");

p1.setP_price("1900");

list.add(p1);

Product p2 = new Product();

p2.setP_code("JD002");

p2.setP_name("康佳32吋");

p2.setP_price("1900");

list.add(p2);

Product p3 = new Product();

p3.setP_code("JD003");

p3.setP_name("康佳32吋");

p3.setP_price("1900");

list.add(p3);

System.out.println("============================");

System.out.println("| 商品名称 | 商品编码 | 商品价格 |");

System.out.println("============================");

for(Product pt : list){

System.out.println("| "+pt.getP_name()+" | "+pt.getP_code()+" | "+pt.getP_price()+" |");

System.out.println("============================");

}

}

Vector还提供了一个Stack子类,用于模拟“栈”数据结构。“栈”也是一种容器,遵循先进后出,后进先出的规范。提供了如下常用方法:

Object peek():返回“栈”的第一个元素,单并不将元素弹出“栈”,仍在“栈”中存放。

Object pop():返回“栈”的第一个元素,同时将元素对象弹出“栈”。

Void push(Object item):将元素对象加入到“栈”中。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Stack stack = newStack();

stack.push("123");

stack.push("456");

stack.push("789");

Object obj = stack.peek(); //返回最后一个对象,对象仍然保存在栈中

System.out.println(obj);

System.out.println(stack.size()); //所以长度不发生变化

obj = stack.pop(); //返回最后一个对象,并将对象在栈中剔除

System.out.println(obj);

System.out.println(stack.size());//所以长度减一

}

8.5 Queue集合

Queue用于模拟队列数据结构,队列是遵循“先进先出”,“后进后出”的存储规范。第一个存放的元素对象存放在队列队首位置,后进入的元素插入到队尾的位置,队列不允许随机访问队列中元素。

Queue接口定义的方法:

Void add(Object obj):将指定元素加入此队列的尾部。

Object element():获取队列头部的元素,但不删除该元素。

Boolean offer(Object obj):将指定元素加入此队列的尾部,当使用有容量限制的队列时,此方法比add()方法更好。

Object peek():获取队列头部的元素,但不删除该元素。如果此队列为空,返回null。

Object poll():获取队列头部的元素,并删除该元素,如果此队列为空,返回Null。

Object remove():获取队列头部的元素,并删除该元素。

8.5.1 PriorityQueue实现类

PriorityQueue是一个比较标准的Queue实现类,而不是绝对标准的队列实现,是因为PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序进行保存。而是按照队列元素的大小进行重新一次排序。因此使用peek()或者poll()方法获取元素队列位置时,并不一定就是首先存入的元素对象,而是队列中最小的元素。

举例1:

publicstaticvoid main(String[] args) throws Exception{

PriorityQueue pq = newPriorityQueue();

pq.offer(6);

pq.offer(-3);

pq.offer(3);

pq.offer(9);

System.out.println(pq.toString()); //

while(pq.size()>0){

System.out.print(pq.poll() + ""); // -3 3 6 9

}

}

注意:PriorityQueue不允许插入null值,它需要对队列元素进行排序,PriorityQueue的元素排序分两种情况,自然排序和定制排序,参照TreeSet。

8.5.2 Deque接口与ArrayDeque实现类

Deque接口是Queue接口的子接口,它代表一个双端队列,Deque接口里定义了一些双端队列的方法,这些方法允许从两端来操作队列元素。

Void addFirst(Object obj):将指定元素插入到该队列的首部。

Void addlast(Object obj):将指定元素插入到该队列的尾部。

Iterator desceningIterator():返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。

Object getFirst():获取双端队列的第一个元素。

Object getLast():获取双端队列的最后一个元素。

Boolean offerFirst(Object obj):将指定元素插入到双端队列的开头。

Boolean offLast(Object obj):将指定元素插入到双端队列的末尾。

Object peekFrist():获取但不删除双端队列的第一个元素。

Object peekLast():获取但不删除双端队列的最后一个元素。

Object pollFrist():获取并删除双端队列的第一个元素。

Object pollLast():获取并删除双端队列的最后一个元素。

Object pop():获取并删除该双端队列的第一个元素。

Void push(Object obj):将一个元素插入到该双端队列的队首位置,相当于addFrist()

Object removeFirst():获取并删除该双端队列的第一个元素。

Object removeLast():获取并删除该双端队列的最后一个元素。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Deque deque = newArrayDeque();

deque.offerFirst("123");

deque.offer("456");//相当于offerLast()

deque.offerLast("789");

System.out.println(deque);

deque.push("abc"); //相当于offerFirst()

System.out.println(deque);

System.out.println(deque.size());

Object obj = deque.peek(); //相当于peekFirst()

System.out.println(obj);

System.out.println(deque.size());

obj = deque.poll();//相当于pollFist()

System.out.println(obj);

System.out.println(deque.size());

}

8.5.3 LinkedList实现类

LinkedList类是List接口的实现类,因此可根据索引来随机访问集合中元素。LinkedList还实现了Deque接口,因此可被当做双端队列来使用,同样也可以被当做“栈”来使用。

举例1:

publicstaticvoid main(String[] args) throws Exception{

LinkedList list = newLinkedList();

list.add("123"); abc 789 123 456

list.offer("456");

//System.out.println(list);

list.offerFirst("789");

//System.out.println(list);

list.push("abc");

System.out.println(list);

Object obj = list.pop();

System.out.println(obj);

System.out.println(list.size());

}

LinkedList与ArrayList、ArrayDeque的实现机制不同,ArrayList和ArrayDeque内部以数组的形式来保存集合中元素,因此随机方位集合元素时有较好的性能。而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能不如ArrayList和ArrayDeque。而插入、删除元素时性能非常出色,只需要改变指针所指向的地址即可。

8.6 Map

Map用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组用于保存Map里的key,另外一组用于保存Map里的value,key和value都可以是任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。

Key和value是一一对应的关系,即通过指定的key,总能找到唯一的value对象。Map里的key放在一可以看成一个Set集合,实际上Map中包含了一个keySet()方法,用于返回Map里所有key组成的Set集合。

Map中的所有value放在一起可以看成一个list集合,元素与元素之间可以重复,每个元素可以根据索引来查找。只是Map中的索引不再使用整数值,而是以另外一个对象作为索引。如果需要从Map中取出元素,则需要提供该元素的key索引。

Map接口中定义了如下常用方法:

Void clear():删除Map对象中所有的key-value对。

Boolean containsKey(Object key):查询Map中是否包含指定的key,如果有则返回true。

Boolean containsValue(Object value):查询Map中是否包含一个或多个Value,如果有返回true。

Set entrySet():返回Map中包含的key-value对所组成的set集合,每个集合元素都是Map.Entry(Entry是Map的内部类)对象。

Object get(Object key):返回指定key所对应的value,如果此Map中不包含该key,返回null。

Boolean isEmpty():查询Map是否为空,如果为空返回true。

Set keySet():返回该Map中所有的key组成的Set集合。

Object put(Object key,Object value):添加一个key-value对,如果当前Map中已有一个与该key相当的key-value对,则新的key-value对会覆盖原来的key-value对。

void putAll(Map m):将Map的实例对象m中的key-value对复制到本Map中。

Object remove(Object key):删除指定key-value对,返回被删除key所关联的value,如果该key不存在,返回null。

Int size():返回该Map里key-value对的个数。

Collections values():返回该Map中所有value组成的Collection。

Map中包含一个内部类Entry,该类封装了一个key-value对。Entry包含了如下三个方法:

Object getKey():返回Entry里包含的key值。

Object getValue():返回Entry里包含的value值。

Object setValue(V value):设置该Entry里包含的value值,并返回新的value值。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Map<String,String> map = new HashMap<String,String>();

//首先往map中存放数据

map.put("one","zhangsan");

map.put("two", "lisi");

map.put("three","wangwu");

//取得map中key-value的数量

System.out.println(map.size());

//判断map中是否存在value为lisi的对象

System.out.println(map.containsValue("lisi"));

//判断map中是否存在key为one的对象

System.out.println(map.containsKey("one"));

//判断当前map对象是否是空对象,即不含任何元素对象

System.out.println(map.isEmpty());

//创建一个新的Map对象m2将m2中的key-value复制到map中去

Map<String,String> m2 = new HashMap<String,String>();

m2.put("four","zhaoliu");

m2.put("five", "qianqi");

map.putAll(m2);

//重新获取map的key-value的个数

System.out.println(map.size());

//删除指定key的key-value对

String one = map.remove("one");

System.out.println(one);

System.out.println(map.size());

//遍历map

//常用的方式1,此种遍历完全

for(Map.Entry<String, String> entry : map.entrySet()){

System.out.println(entry.getKey()+"===="+entry.getValue());

}

//常用的方式2,根据key值进行遍历

Iterator<String> iter = map.keySet().iterator();

while(iter.hasNext()){

System.out.println(iter.next());

}

}

8.6.1 HashMap和Hashtable实现类

HashMap和Hashtable都是Map的典型实现类,两者之间的关系完全类似于ArrayList和Vector。除此之外两者之间的典型区别:

Hashtable是一个线程安全的Map实现,但HashMap是线程不安全。所以HashMap比Hashtable性能更高一点。

Hashtable不允许使用null作为key和value,而HashMap则可以。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Map map = newHashMap();

map.put(null, null);

System.out.println(map.size());

Map m2 = newHashtable();

m2.put(null, null);

System.out.println(m2.size());

}

HashMap正常执行,而Hashtable则报空指针异常错误。

注意:尽量不要使用可变对象作为HashMap的key,如果使用了可变对象作为了map的key,则在程序中尽量不要去修改变量。

举例2:

publicstaticvoid main(String[] args) throws Exception{

String str = "abc";

Map map = newHashMap();

map.put(str, "zhangsan");

str = "123";

System.out.println(map.get(str)); //输出null

System.out.println(map.get("abc")); //输出zhangsan

}

8.6.2 LinkedHashMap实现类

LinkedHashMap是HashMap的子类,同LinkedHashSet一样,LinkedHashMap在存储数据元素时同样使用了链表来维护key-value对的存放顺序,该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Map<String,String> map = new HashMap<String,String>();

map.put("1", "zhangsan1");

map.put("2", "zhangsan2");

map.put("3", "zhangsan3");

map.put("10", "zhangsan1");

map.put("20", "zhangsan20");

map.put("30", "zhangsan30");

for(Map.Entry<String,String> entry : map.entrySet()){

System.out.println(entry.getKey()+"====="+entry.getValue());

}

}

输出结果:

3=====zhangsan3

20=====zhangsan20

2=====zhangsan2

10=====zhangsan1

1=====zhangsan1

30=====zhangsan30

举例2:

替换上例程序的中第一行代码

LinkedHashMap<String,String> map = new LinkedHashMap<String,list<String>();

输出结果:

1=====zhangsan1

2=====zhangsan2

3=====zhangsan3

10=====zhangsan1

20=====zhangsan20

30=====zhangsan30

8.6.3 Properties属性类

Properties类是Hashtable类的子类,该对象在处理属性文件时非常方便。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入到属性文件中,也可以把属性文件中的“属性名=属性值”加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所有Properties里的key、value都是字符串类型。该类的常用方法如下

String getProperty(String key):获取Properties中指定属性名对应的属性值。

String getProperty(String key,String defaultValue):方法重载,获取Properties中指定属性名对应的属性值,如果此属性名不存在时,返回默认的defaultValue值。

Object setProperty(String key,String value):设置属性值,类似于Map的put()方法。

void load(InputStream inStream):从属性文件中加载key-value对。

void store(OutputStream out,String comments):将Properties中的key-value对输入到指定的属性文件中。

属性文件格式:在windows系统下属性文件常以.ini进行结尾,在Java中属性文件以.properties结尾

举例1:

publicstaticvoid main(String[] args) throws Exception{

Properties prop = new Properties();

prop.setProperty("name","zhangsan");

prop.put("age","24");

prop.store(new FileOutputStream("./test.ini"), "注释内容");

Properties prop2 = new Properties();

//FileInputStream文件输入流,使用prop的load()方法进行加载文件

prop2.load(new FileInputStream("./test.ini"));

prop2.setProperty("gender", "male");

System.out.println(prop2.get("age"));

System.out.println(prop2.getProperty("name"));

//FileOutputStream文件输出流,使用prop的store()方法将porp对象的信息保存到指定的文件中

prop2.store(new FileOutputStream("./test.properties"), "属性文件");

}

bankCode = 62202 1602 0131 34567

bankPass = 123456

userName = zhangsan

userAge = 24

8.6.4 SortedMap接口与TreeMap实现类

如同Set接口派生出SortedSet子接口,SortedSet接口有一个实现类TreeSet一样,Map接口也有一个SortedMap接口,SortedMap接口也有一个实现类TreeMap。TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对时,需要根据key对节点进行排序。TreeMap保证所有的key-value对都处于有序的状态。

TreeMap的两种排序方式:

自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则抛出ClassCastException(类型转换异常)。

定制排序:创建TreeMap对象时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。采用定制排序时不需要key实现Comparable接口。

TreeMap提供的常用方法如下:

Map.Entry firstEntry():返回该Map中最小key所对应的key-value对,如果Map为空,则返回null。

Object firstKey():返回该Map中最小key值,如果该Map为空,则返回null。

Map.Entry lastEntry():返回Map中最大key所对应的key-value对,如果Map为空,则返回null。

Object lastKey():返回该Map中最大key值,如果该Map为空,则返回null。

Map.Entry higherEntry(Object key):返回该Map中位于key后一位的key-value对。如果后面不存在则返回为null。

Object higherKey(Object key):返回该Map中位于key后一位的key值,如果后面不存在则返回为null。

Map.Entry lowerEntry(Object key):返回该Map中位于key前一位的key-value对。如果前面不存在则返回为null。

Object lowerKey(Object key):返回该Map中位于key前一位的key值。如果前面不存在则返回为null。

SortedMap subMap(Object fromKey,Object toKey):返回该Map的子Map,其key的范围是从fromKey(包括)到toKey(不包括)。

SortedMap tailMap(Object fromKey):返回该Map的子Map,其key的范围是大于fromkey(包括)的所有key。

SortedMap headMap(Object toKey):返回该Map的子Map,其key的范围是小于toKey(不包括)的所有key。

举例1:

package com.langsin.test;

import java.util.Comparator;

import java.util.TreeMap;

class MyTest implements Comparable<Object>{

int count;

public MyTest(int count){

this.count = count;

}

publicint compareTo(Object obj) {

MyTest te = (MyTest)obj;

return count>te.count?1:count<te.count?-1:0;

}

}

publicclass TestTreeMap {

publicstaticvoid main(String[] args) {

Comparator<MyTest> comp = new Comparator<MyTest>(){

publicint compare(MyTest m1, MyTest m2) {

if(m1.count>m2.count){

return-1;

}

if(m1.count<m2.count){

return1;

}

return 0;

}

};

//首先实现treeMap的定制排序

TreeMap<MyTest,String> map = new TreeMap<MyTest,String>(comp);

map.put(new MyTest(-3), "123");

map.put(new MyTest(1), "456");

map.put(new MyTest(3), "789");

System.out.println(map);

System.out.println(map.firstEntry());

}

}

8.6.5 WeakHashMap实现类

WeakHashMap与HashMap的用法基本相似,区别在于HashMap的key保留了对实际对象的强引用。即:只要HashMap不被销毁,该HashMap的所有key所引用的对象就不会被垃圾回收,HashMap也不会自动删除这些key所对应key-value对。WeakHashMap的key只保留了对实际对象的弱引用,即:WeakHashMap的对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。

对于WeakHashMap,大家仅作了解,知道与HashMap的区别即可。

举例1:

publicstaticvoid main(String[] args) throws Exception{

WeakHashMap map = newWeakHashMap();

map.put(new String("123"), "123");

String str = newString("456");

map.put(str, "456");

map.put("456", "abc");

System.out.println(map); //输出内容

System.gc();//垃圾回收机制

System.out.println(map);

}

8.6.7 EnumMap实现类

EnumMap是一个与枚举类一起使用的Map实现,EnumMap中所有的key都必须是单个枚举类的枚举值。创建EnumMap时必须显示或隐式指定它对应的枚举类。

EnumMap根据key的自然顺序,即在枚举类中的定义顺序来维护key-value对的顺序。调用Map的方法keySet()、entrySet()、values()遍历EnumMap时可以看到这种顺序。

EnumMap不允许使用null作为key,但允许使用null作为value。

创建EnumMap时必须指定一个枚举类,从而与使EnumMap和指定枚举类关联起来。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Map map = new EnumMap(Planet.class);

map.put(Planet.EARTH, "地球");

map.put(Planet.MARS, "火星");

map.put(Planet.PLUTO, "冥王星");

map.put(Planet.VENUS, "金星");

map.put(Planet.SATURN, "土星");

map.put(Planet.JUPITER, "木星");

map.put(Planet.MERCURY, "水星");

map.put(Planet.NEPTUNE, "海王星");

map.put(Planet.URANUS, "天王星");

System.out.println(map);

}

8.7 操作集合的工具类Collections

Java提供了一个操作Set、List和Map等集合的工具类:Collections,该工具类里提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变,对集合对象实现同步控制等方法。

8.7.1 排序操作

Collections提供了如下几个方法用于对List集合元素进行排序。

Static void reverse(List list):反转指定List集合中元素的顺序。

Static void shuffle(List list):对List集合元素进行随机排序。

Static void sort(List list):根据元素的自然顺序对指定list集合的元素按升序进行排序。

Static void sort(List list,Comparator comp):根据指定Comperator产生的顺序对List集合元素进行排序。

Static void swap(List list,int i,int j):将指定List集合中的i处元素和j处元素进行交换。

Static void rotate(List list,int distance):当distance为正数时,将list集合的后distance个元素“整体”移动到前面,当distance为负数时,将list集合的前distance个元素“整体”移动到后面。

package com.langsin.product;

import java.util.ArrayList;

import java.util.Collections;

import java.util.Comparator;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Set;

import java.util.TreeSet;

public class ShowHand {

//该游戏支持的最多玩家数量

private final int PALY_NUM = 5;

//该游戏实际参与的玩家数量

private int factNum = 0;

//定义所有扑克的花色和数值

private String[] types = {"♠","♥","♣","♦"};

private String[] values = {"8","9","10","J","Q","K","A"};

//在一局中所有牌的数量

private List<String> cards = new ArrayList<String>();

//定义所有的玩家

private String[] players = new String[PALY_NUM];

//定义所有玩家手上的扑克牌

private Map<String,Set<String>> playerCards = new HashMap<String,Set<String>>();

/**

*初始化扑克牌,并将其随机排序,相当于洗牌

**/

public void initCards(){

//首先将牌放到集合中去

for(String type : types){

for(String value : values){

this.cards.add(type+value);

}

}

Collections.shuffle(cards);

}

/**

* 初始化玩家

* */

public boolean initPlays(String... names){

if(names.length>this.PALY_NUM || names.length<2){

System.out.println("玩家数量不正确");

return false;

}else{

//初始化玩家

for(int i=0;i<names.length;i++){

this.players[i] = names[i];

playerCards.put(names[i], this.getSortSet());

}

this.factNum = names.length;

return true;

}

}

/**

* 创建一个自定义的Set集合,用于存放扑克牌,按照我们定制的顺序排放

* */

private Set<String> getSortSet(){

//自定义排序器,让扑克牌按照我们的方式进行排放

Comparator<String> comp = new Comparator<String>(){

/**

* 扑克牌的大小及排放规则,花色按照黑、红、花、片,排序,大小8、9、10、J、Q、K、A

* */

public int compare(String s1, String s2) {

int num1 = this.getSize(s1.charAt(1));

int num2 = this.getSize(s2.charAt(1));

if(num1>num2){

return 1;

}else if(num1<num2){

return -1;

}else{

int type1 = this.getType(s1.charAt(0));

int type2 = this.getType(s2.charAt(0));

if(type1>type2){

return -1;

}else{

return 1;

}

}

}

/**

* 判断牌的点数大小

* */

private int getSize(char c){

switch (c) {

case '8':

return 8;

case '9':

return 9;

case 'J':

return 11;

case 'Q':

return 12;

case 'K':

return 13;

case 'A':

return 14;

default:

return 10;

}

}

/**

* 判断牌的花色

* */

private int getType(char c){

switch (c) {

case '♦':

return 1;

case '♣':

return 2;

case '♥':

return 3;

default:

return 4;

}

}

};

Set<String> set = new TreeSet<String>(comp);

return set;

}

/**

* 为每个玩家初始化扑克牌

* */

public void deliverCard(){

for(int i=0;i<5;i++){

for(int j=0;j<factNum;j++){

playerCards.get(this.players[j]).add(cards.get(0));

cards.remove(0);

}

}

}

/**

* 测试每个人手中的牌

* */

public void print(){

for(int i=0;i<this.factNum;i++){

System.out.println(this.playerCards.get(this.players[i]));

}

}

public static void main(String[] args) {

ShowHand test = new ShowHand();

test.initCards();

test.initPlays("张三","李四","王五");

test.deliverCard();

test.print();

}

}

8.7.2 查找、替换操作

Collections还提供了如下用于查找、替换集合元素的常用方法。

Static int binarySearch(List list,Object key):使用二分法搜索指定的List集合,用来获得指定对象key在List集合中的索引。如果要使该方法可以正常工作,则必须保证List中的元素已经处于有序的状态。

Static Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素。

Static Object max(Collection coll,Comparator comp):根据Comparator指定的顺序,返回给集合中的最大的元素。

Static Object min(Collection coll):根据元素的自然顺序,返回给集合中的最小元素。

Static Object min(Collection coll,Comparator comp):根据Comparator指定的顺序,返回给集合中的最小的元素。

Static void fill(List list,Object obj):使用指定元素obj替换指定List集合中的所有元素。

Static int frequency(Collection c,Object o):返回指定集合中指定元素的出现次数。

Static int indexOfSubList(List source,List target):返回子List对象在父List对象中第一次出现的位置索引;如果父List中没有出现这样的子List,则返回-1。

Static int lastIndexOfSubList(List source,List target):返回子List对象在父List对象中最后一次出现的位置索引,如果父List中没有出现这样的子List,则返回-1。

Static boolean replaceAll(List list,Object oldVal,Object newVal):使用一个新值newVal替换List对象的所有旧值oldVal。

举例1:

publicstaticvoid main(String[] args) throws Exception{

List list = newArrayList();

list.add(2);

list.add(-3);

list.add(5);

list.add(1);

System.out.println(list);

System.out.println(Collections.max(list));

System.out.println(Collections.min(list));

Collections.replaceAll(list, 1, 6);

System.out.println(list);

System.out.println(Collections.frequency(list, 5));

Collections.sort(list);

System.out.println(list);

System.out.println(Collections.binarySearch(list, 6));

}

8.7.3 同步控制

Collections类中提供了多个synchronizedXxx()方法,该方法可以将指定的集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的多线程安全问题。

Java中常用的集合框架中的实现类,HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap、和TreeMap都是线程不安全的。如果有多个线程访问它们,而且有超过一个的线程试图修改它们,则可能会出现错误。Collections提供了多个静态方法可以把它们包装成线程同步的集合。

举例1:

publicstaticvoid main(String[] args) throws Exception{

Collectioncoll = Collections.synchronizedCollection(new ArrayList());

Listlist = Collections.synchronizedList(new ArrayList());

Setset = Collections.synchronizedSet(new HashSet());

Mapmap = Collections.synchronizedMap(new HashMap());

}

通过synchronizedXxx()方法,就可以直接获取List、Set、Map的线程安全实现版本。

8.7.4 设置不可变集合

Collections提供如下三个类方法来返回一个不可变的集合。

emptyXxx():返回一个空的,不可变的结合对象,此处的集合即可以是List,也可以是Set,还可以是Map。

singletonXxx():返回一个包含指定对象的,不可变的集合对象,此处的集合即可以是List,也可以是Set,还可以是Map。

unmodifiableXxx():返回指定集合对象的不可变视图,此处的集合既可以是List,也可以是Set,还可以是Map。

举例1:

publicstaticvoid main(String[] args) {

//创建一个空的,不可变的Set对象

Setset = Collections.emptySet();

//创建一个只包含特定对象的List集合

List<String>list = Collections.singletonList("abc");

//创建一个普通的Map集合,将Map集合转变为一个不能在更改的Map对象

Map map = newHashMap();

map.put("123", "abc");

map.put("456", "def");

MapunmodifiableMap = Collections.unmodifiableMap(map);

}

第9章:泛型

在Java发布的JDK1.5版本中增加了泛型支持,所谓的泛型就是为了让集合在很大程度上记住器元素的数据类型。在没有使用泛型之前,一旦把对象存放进Java集合中,集合只会将元素作为Object对象进行处理。当从集合中取出对象时就需要进行强制类型转换。如果转换错误,则将会引起ClassCastException异常(类型转换异常)。

增加泛型支持后的集合,则可以让集合记住元素的类型,并在编译时检查集合中元素的类型,如果往集合中添加不满足类型要求的对象,编译器则会提示错误。增加泛型后的集合,可以让代码更加简洁,程序更加健壮。

9.1 泛型入门

Java集合之所以被设计成以存放Object对象为基础的类,是因为设计之初,程序员并不知道我们要用集合来存放什么类型的对象,所以把集合设计成能保存任何数据类型的对象,只要求有很好的通用性。因此而导致了下面两个问题:

集合对元素类型没有任何限制,只要是Object类型就都可以存放。

把对象存放进集合时,集合丢失了对象的状态信息,集合只知道存放的是Object,因此取出时还需要对对象进行一个强制类型转换,增加了编程的复杂度,还有可能会引发ClassCastException异常。

举例1:

publicstaticvoid main(String[] args) {

List list = newArrayList();

list.add("abc");

list.add("def");

list.add(5);

for(int i=0;i<list.size();i++){

String s = (String)list.get(i);

System.out.println(s);

}

}

9.1.1 使用泛型

publicstaticvoid main(String[] args) {

List<String> list = new ArrayList<String>();

list.add("abc");

list.add("def");

list.add(5); //编译报错

for(int i=0;i<list.size();i++){

String s = list.get(i); //无需进行强制类型转换

System.out.println(s);

}

}

上面程序创建了一个特殊的List集合,这个List集合只能保存字符串对象,不能保存其他类型的对象。创建这种特殊集合的方法是:在集合接口、类后面增加“<>”尖括号,尖括号中写入一个数据类型,即表明这个集合接口、集合类只能保存这种特定类型的对象。

9.1.2 定义泛型接口、类

所谓的泛型就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态的指定。

举例1:

publicinterface MyInterface<T>{

public T getType();

publicvoid setType(T t);

}

publicclass MyClass implements MyInterface<String> {

public String getType() {

returnnull;

}

publicvoid setType(String t) {

}

}

当创建了带有泛声明的接口、父类之后,可以为该接口创建实现类,或者从该父类派生子类,但是注意一点:当使用这些接口、父类是不能再包含类型形参,而是必须指出具体的实际类型。如上例所示。

9.1.3 不存在泛型类

在程序中我们为一个ArrayList<String>指定了泛型后,可以把这个ArrayList<String>看成ArrayList的子类,这个类只能存储String类型的元素,但是实际上,系统并没有为ArrayList<String>生成新的class文件,也不会把ArrayList<String>当成新类,因为并不存在这种泛型类。

第10章:异常处理

Java的异常机制主要依赖于try、catch、finally(始终都会被运行)、throw和throws五个关键字,其中try关键字后面紧跟着一个花括号括起来的代码块,它里面放置可能会引发异常的代码块。Catch后面对应异常类型和一个代码块,用于表明该catch块用于处理这种类型的代码块,多个catch块后面还可以跟一个finally块,用于回收在try块里打开的物理资源,异常机制会保证finally块总会被执行。throws关键字主要在方法签名中使用,用于声明该方法可能抛出的异常;而throw用于抛出一个实际的异常,throw可以单独作为语句使用,抛出一个具体的异常对象。

10.1 异常概述

异常处理已经成为衡量一门语言是否成熟的标准之一,目前的主流编程语言如C++,C#,Ruby等大都提供了异常处理机制。增加异常处理机制后的程序有更好的容错性,更加健壮。

举例1:

publicstaticvoid main(String[] args) {

List<String> list = new ArrayList<String>();

list.add("abc");

list.add("def");

try {

for(int i=0;i<4;i++){

System.out.println(list.get(i));

}

} catch (IndexOutOfBoundsException e) {

System.out.println("已经超出了集合的范围");

}

}

10.2 异常处理机制

Java的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序出现意外情形时,系统会自动生成一个Exception对象来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。

10.2.1 使用try…catch捕获异常

在Java中提出了一种假设:如果程序可以顺利完成,那就“一切OK”,把系统的业务实现代码放在try块中定义,所有的异常处理逻辑放在catch块中进行处理。下面是Java异常处理机制的语法规则。

try{

//业务实现代码

}catch(ExceptionClass1 e1){

//错误处理代码

}catch(ExceptionClass2 e2){

//错误处理代码

}finally{

//始终会执行的代码

}

Try块后可以有多个catch块,这是为了针对不同的异常类提供不同的异常处理方式。当系统发生不同的意外情况时,系统会生成不同的异常对象,Java运行时就会根据该异常对象所属的异常类来决定使用哪个catch块来处理。

举例1:

publicstaticvoid main(String[] args) {

List<String> list = new ArrayList<String>();

list.add("abc");

list.add("def");

list.add(null);

try {

for(int i=0;i<4;i++){

list.get(i).equals("abc");

System.out.println(list.get(i));

}

} catch (IndexOutOfBoundsException e) {

System.out.println("索引值越界异常"); 2

}catch(NullPointerException e){

System.out.println("空指针异常处理"); 1

}finally{

System.out.println("始终会被执行的代码块"); 3

}

}

10.2.2 异常类的继承体系

Java提供了丰富的异常类,这些异常类之间有严格的继承关系,如下图所示:

在Java中把所有的非正常情况分为两种:异常(Exception)和错误(Error),他们都继承Throwable父类。Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不能试图使用catch块来捕获Error对象,在定义方法时也无须其throws子句中声明该方法可能抛出Error及其任何子类。

10.2.3 访问异常信息

如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch块后的异常形参来获得。当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息。

所有异常对象都包含了如下几个常用方法。

getMessage():返回该异常的详细描述字符串。

printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。

printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流中。

getStackTrace():返回该异常的跟踪栈信息。

举例1:

publicstaticvoid main(String[] args) {

List<String> list = new ArrayList<String>();

list.add("abc");

list.add("def");

list.add(null);

try {

for(int i=0;i<4;i++){

list.get(i).equals("abc");

System.out.println(list.get(i));

}

} catch (IndexOutOfBoundsException e) {

System.out.println("已经超出了集合的范围");

}catch(NullPointerException e){

e.printStackTrace();

System.out.println(e.getMessage());

System.out.println("空指针异常处理");

}finally{

System.out.println("始终会被执行的代码块");

}

}

输出信息:

10.2.4 使用finally回收资源

程序在try块里打开了一些物理资源,比如:数据库连接、网络连接、和磁盘文件等,这些物理资源都必须显示回收。

如果在try块的某条语句引起了异常,该语句后的其他语句通常不会获得执行的机会,这将导致该语句之后的资源回收语句得不到执行。如果在catch块里进行资源回收,但catch块完全有可能得不到执行,这将导致不能及时回收这些物理资源。

为了保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或者catch块中执行了return语句,finally块总会被执行。因此回收资源执行语句应该在finally块中执行。

举例1:

publicstaticvoid main(String[] args) {

FileInputStream fis = null;

try {

fis = new FileInputStream("./a.txt");

} catch (Exception e) {

e.printStackTrace();

return;

}finally{

try {

fis.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

10.2.5 使用throws声明抛出异常

使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方法是,打印异常的跟踪栈信息,并终止程序执行。

举例1:

publicvoid dealFile() throws Exception{

FileInputStream fis = null;

try {

fis = new FileInputStream("./a.txt");

} catch (Exception e) {

fis.close();

e.printStackTrace();

}

}

publicstaticvoid main(String[] args) throws Exception {

Test test = new Test();

test.dealFile();

}

10.2.6 使用throw抛出异常

如果需要在程序中自主的抛出异常,则应该使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。

举例1:

publicstaticvoid main(String[] args){

for(int num=5;num>0;num--){

try {

if(num<2){

thrownew Exception("数字的值小于2了,停止运行");

}

} catch (Exception e) {

System.out.println("======");

System.out.println(e.getMessage());

}

}

System.out.println("最终执行完成");

}

第11章:输入/输出

IO(输入/输出)是所有程序都必须的部分。输入机制:允许程序读取外部数据(包括来自磁盘、光盘等存储设备的数据)、用户输入数据;输出机制:允许程序记录运行状态,将程序数据输出到磁盘、光盘等存储设备中。

Java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入、输出两种IO流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作。

11.1 File类

File是java.io包下代表与平台无关的文件和目录,则程序中操作文件和目录,都可以通过File类来完成。File能新建、删除、重命名文件和目录。File不能访问文件内容本身,如果访问文件内容本身,则需要使用输入/输出流。

11.1.1 访问文件和目录

File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。在默认情况下,系统总是依据用户的工作路径来解释相对路径。

创建File对象后,可以调用File对象的方法来访问。常用的方法如下:

1、访问文件名相关的方法

String getName():返回此文件对象所表示的文件名或路径名。

String getPath():返回此File对象所对应的路径名。

File getAbsoluteFile():返回此File对象所对应的绝对路径指向的File对象。

String getAbsolutePath():返回此File对象所对应的绝对路径。

String getParent():返回此File对象所对应的目录的父目录,以工作路径为准,如果已经在工作路径的根目录下,则返回null,否则返回父目录。

boolean renameTo(File newName):重命名此File对象所对应的文件或目录,成功返回true,否则返回false。

2、文件检测相关的方法

boolean exists():判断File对象所对应的文件或目录是否存在。

boolean canWrite():判断File对象所对应的文件和目录是否可写。

boolean canRead():判断File对象所对应的文件和目录是否可读。

boolean isFile():判断File对象所对应的是否是文件。

boolean isDirectory():判断File对象所对应的是否是目录。

boolean isAbsolute():判断File对象所对应的文件或目录是否绝对路径。例如:如果在Unix/Linux等系统上,如果路径名的开头是/,则表明File对象对应一个绝对路径,在Windows等系统上,如果路径开头是盘符,则说明它是一个绝对路径。

获取常规文件信息

long lastModified():返回文件的最后修改时间。

long length():返回文件内容的长度。

文件操作相关的方法

boolean createNewFile():当此File对象对应的文件不存在时,创建一个该File对象所对应的文件,创建成功返回true,否则返回false。

boolean delete():删除File对象所在的文件或路径。

目录操作相关的方法

boolean mkdir():创建一个File对象所对应的目录,创建的是目录而不是文件。

boolean mkdirs():创建一个File对象所对应的所有目录,如果上次目录不存在,会同时将上级目录创建出来。

File[] listFile():列出File对象的所有子文件和路径,返回File数组。

Static File[] listRoots():列出系统所有根路径。

举例1:

publicstaticvoid main(String[] args) throws Exception {

File file = new File("./abc/aaa");

//返回文件的文件名或路径名

System.out.println(file.getName());

//返回文件所在的相对目录

System.out.println(file.getPath());

//返回文件所在的绝对目录

System.out.println(file.getAbsolutePath());

//返回文件对象的父目录

System.out.println(file.getParent());

//判断文件是否存在

if(!file.exists()){

//连同不存在的父级目录一同创建出来

file.mkdirs();

}

System.out.println(file.canWrite());

System.out.println(file.canRead());

System.out.println(file.isDirectory());

System.out.println(file.isFile());

file = new File("./abc/aa.txt");

if(!file.exists()){

file.createNewFile();

}

//判断文件是否是绝对路径

System.out.println(file.isAbsolute());

file = new File(file.getParent());

File[] files = file.listFiles();

for(File fi : files){

System.out.println(fi.getName());

}

}

11.2 Java的IO流

Java的IO流是实现输入/输出的基础,它可以方便的实现数据的输入/输出操作,Java把所有传统的流类型都放在java.io包中,用以实现输入/输出功能。

11.2.1 流的分类

输入流和输出流

按照流向来分,可以分为输入流和输出流

输入流:只能从中读取数据,而不能向其写入数据;基本上是从磁盘文件到系统内存。

输出流:只能向其写入数据,而不能从中读取数据;基本上是从系统内存到磁盘文件。

Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类。他们都是一些抽象基类,能直接创建实例对象。

2、字节流与字符流

字节流和字符流的用法几乎一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。

英文字母代表一个字符代表一个字节,一个汉字字符是2个字节

一个字节等于8位

字节流主要由InputStream和OutStream作为基类,字符流主要由Reader和Writer作为基类。

11.3 InputStream和Reader

InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,它们是所有输入流的模板,它们的方法是所有输入流都可以使用的方法。

在InputStream里包含如下方法:

int read():从输入流中读取单个字节,返回所读取的字节数据。

int read(byte[] b):从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。

int read(byte[] b,int off,int len):从输入流中最多读取len个字节的数据,并将其存储在数据组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。

aa.txt文件中的数据为:测试数据信息

举例1:

publicstaticvoid main(String[] args) throws Exception {

InputStream is = new FileInputStream(new File("./abc/aa.txt"));

byte[] b = newbyte[1024];

int size = 0;

while((size=is.read(b))>0){

System.out.println(new String(b,0,size));

}

//最后不要忘记关闭流

is.close();

}

举例2:

publicstaticvoid main(String[] args) throws Exception {

InputStream is = new FileInputStream(new File("./abc/aa.txt"));

byte[] b = newbyte[1024];

int size = 0;

//从输入流中读取数据放入到字节数组中,从2位置开始放,从输入流中读取6个长度

while((size=is.read(b,2,6))>0){

System.out.println(new String(b,2,size));

}

//最后不要忘记关闭流

is.close();

}

在Reader里包含如下方法:

int read():从输入流中读取单个字符,返回所读取的字符数据

int read(char[] ch):从输入流中最多读取ch.length个字符的数据,并将其存储在字符数组ch中,返回实际读取的字符数。

int read(char[] ch,int off,int len):从输入流中最多读取len个字符的数据,并将其存储在字符数组ch中,放入数组时,从数组的off位置开始,返回实际读取的字符数。

举例3:

publicstaticvoid main(String[] args) throws Exception {

Reader rd = new FileReader(new File("./abc/aa.txt"));

char[] ch = newchar[1024];

int size = 0;

while((size=rd.read(ch))>0){

System.out.println(new String(ch,0,size));

}

//最后不要忘记关闭流

rd.close();

}

举例4:

publicstaticvoid main(String[] args) throws Exception {

Reader rd = new FileReader(new File("./abc/aa.txt"));

char[] ch = newchar[1024];

int size = 0;

while((size=rd.read(ch,2,4))>0){

System.out.println(new String(ch,2,size));

}

//最后不要忘记关闭流

rd.close();

}

11.4 OutputStream和Writer

OutputStream和Writer也非常相似,两个流都提供了如下方法进行输出:

void write(int c):将指定的字节/字符输出到输出流中,其中c既可以表示字节也可以表示字符。

void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到指定输出流中。

void write(byte[]/char[] buf,int off,int len):将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。

因为字符流直接以字符作为操作单位,所有Writer可以用字符串来代替字符数组,即以String对象作为参数。Writer里还包含了如下两个方法。

void write(String str):将str字符串里包含的字符输出到指定输出流中。

void write(String str,int off,int len):将字符串从off位置开始,长度为len的字符输出到指定输出流中。

举例1:将会覆盖原有的文件内容从开始位置写入

publicstaticvoid main(String[] args) throws Exception {

OutputStream os = new FileOutputStream(new File("./abc/aa.txt"));

byte[] b = {55,56,57,58};

os.write(b);

os.close();

}

举例2:

publicstaticvoid main(String[] args) throws Exception {

Writer writer = new FileWriter(new File("./abc/aa.txt"));

char[] ch = {'你','们','都','是','很','棒','的'};

writer.write(ch);

writer.write(Arrays.toString(ch));

writer.close();

}

11.5 InputStreamReader和OutputStreamWriter

输出/输出体系中提供了两个转换流InputStreamReader和OutputStreamWriter,这两个转换流用于实现将字节流转换成字符流,其中InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流。此类用法例子详见:11.6

11.6 BufferedReader和BufferedWriter

BufferedReader和java.io.BufferedWriter类各拥有8192字符的缓冲区。当BufferedReader在读取文本文件时,会先尽量从文件中读入字符数据并置入缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取,使用BufferedWriter时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。如果要在缓冲区中数据未满的情况下将数据写入到目的地,需要使用flush()方法,将数据刷新一下。

举例1:

publicstaticvoid main(String[] args) throws Exception {

//System.in返回的是一个字节流的是对象,将字节流转换成一个字符流

InputStreamReader isr = new InputStreamReader(System.in);

//是否还有一种更好的流来处理字符流内,答案是肯定的:缓冲流

BufferedReader br = new BufferedReader(isr);

//缓冲流可以将文本中的字符信息按一行来进行处理,即读取一行,写入一行,

//而不在需要定义个数组来存放读取内容,读取内容时以换行符为标志,如果没有读取到换行符则程序

//阻塞,一直读取到换行符为止。

String line = null;

//将所有信息写入到文件中去

BufferedWriter writer = new BufferedWriter(new FileWriter(new File("./abc/aa.txt"),true));

while((line=br.readLine()) != null){

if("exit".equals(line)){

//停止写入,退出

System.exit(1);

}

writer.write(line);

writer.newLine();

//将内容从缓存中刷新出去,写入到目的地

writer.flush();

}

writer.close();

br.close();

}

11.7 PrintStream和PrintWriter

PrintStream的构造函数:

public PrintStream(File file)

创建具有指定文件新打印流。

public void print(Object obj)

这个方法功能是非常强大的,它可以输出任何对象,而不必另加说明。此外print()方法有许多重载形式,即有多种参数。它们是字符串(String)、字符数组(char[])、字符(char)、整数(int)、长整数(long)、浮点数(float)、双精度浮点数(double)、布尔值(boolean)。

public void println(Object obj)

此方法同上,区别在于打印之后追加一个换行符。

举例1:

publicstaticvoid main(String[] args) throws Exception {

//如果文件不存在,则将文件直接创建

PrintStream ps = new PrintStream(new File("./abc/bb.txt"));

ps.print("你好");

ps.close();

}

PrintWriter是与字节流相对应的字符流。PrintWriter用于大多数输出,比PrintStream更为合适。建议新开发的代码使用PrintWriter类。 PrintWriter类与PrintStream类的方法是对应的。

举例2:

publicstaticvoid main(String[] args) throws Exception {

PrintWriter writer = new PrintWriter(new File("./abc/bb.txt"));

writer.print("新的内容");

writer.close();

}

11.8 内存流、数据流

ByteArrayInputStream和ByteArrayOutputStream为内存流或者称为数组流。

ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read()方法要提供的下一个字节。ByteArrayOutputStream类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。

ByteArrayInputStream和ByteArrayOutputStream,用于以IO流的方式来完成对字节数组内容的读写,来支持类似内存虚拟文件或者内存映射文件的功能。

DataInputStream和DataOutputStream数据流,用来处理基本数据的IO流。

举例1:

客户端和服务器端交互,当连接建立之后,如果只是根据协议用DataInputStream的readInt() 或者readByte() 等固定方法一个一个的读,那是相当消耗资源的,如果一次将所需要的数据读取完毕,然后剩下的就是本地解析,那就省了不少交互资源

服务器端处理完毕需要往客户端返回数据,如果使用DataOutputStream的writeInt(int v)或者writeByte(int v)等方法一个一个的写,那么和读取的场景性质就一样了:交互资源是相当消耗的;因此如果现在本地把数据组织完毕之后,进行一次传输。

publicstaticvoid main(String[] args) throws Exception {

//创建一个字节流或者内存流,将内容写入到这个流中

ByteArrayOutputStream baos = new ByteArrayOutputStream();

//Date数据流写入的内容被存储在内存流baos中

DataOutputStream dos = new DataOutputStream(baos);

dos.writeFloat(13.5f);

dos.writeDouble(12.50);

dos.writeFloat(14.6f);

//写完之后可以将baos流包装好进行传输

//客户端接收到服务器端返回的字节流,进行一次接收,然后进行处理

DataInputStream dis = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));

//读取字节流的时候,必须按照写入的方式进行读取,例如:第一个写入了float数据,那么读取的时

//候必须先读取Float,第二写入的是Double,那么读取第二数据的时候必须读入Double。

System.out.println(dis.readFloat());

System.out.println(dis.readDouble());

System.out.println(dis.readFloat());

}

11.9 Object对象流

对象序列化:对象序列化可以将对象保存在磁盘中,或者将对象在网络中进行传输。序列化的机制是可以将对象脱离程序的运行而独立存在。如果让一个类的对象支持序列化机制,则需要让这个类实现Serializable接口就可以了。该接口无须实现任何方法,它只是表明该类的实例是可序列化的。

使用对象流ObjectInputStream和ObjectOutputStream将对象保存到磁盘上或者通过网络进行传输。

举例1:

publicstaticvoid main(String[] args) throws Exception {

//创建一个person对象,此对象已经支持序列化

Person person = new Person();

person.setAge(25);

person.setName("张三");

//创建一个Object对象输出流,将此对象写入到文件中去

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./abc/person.txt")));

oos.writeObject(person);

//创建一个Object对象输入流,将对象从文件中读取出来

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./abc/person.txt")));

Person ps = (Person)ois.readObject();

System.out.println(ps.getName());

System.out.println(ps.getAge());

}

举例2:将对象写入到内存流中

publicstaticvoid main(String[] args) throws Exception {

//创建一个person对象,此对象已经支持序列化

Person person = new Person();

person.setAge(25);

person.setName("张三");

//创建一个Object对象输出流,将此对象写入到内存流中去

ByteArrayOutputStream baos =new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(baos);

oos.writeObject(person);

//创建一个内存输入流,将对象从内存输出流中获取出来

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

//创建一个Object对象输入流,将对象从内存输入中读取出来

ObjectInputStream ois = new ObjectInputStream(bais);

Person ps = (Person)ois.readObject();

System.out.println(ps.getName());

System.out.println(ps.getAge());

}

11.9.1 浅克隆与深克隆

1、什么是克隆

Object对象clone()方法,此方法将会产生此对象的一个副本返回,类似于拷贝了一份当前的对象,clone方法是protected进行修饰的(受保护的),如果要想使我们自己的类来实现的这个方法,首先要实现Cloneable这个接口,然后重写Object对象的这个方法,所谓的重写就是在本类中调用父类的方法。这是因为:如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常。

2、浅克隆

假如有两个类,一个是A类,一个是B类,A类中包含了B类的这样一个引用,例如class B{} ,

class A{

B b=null;

}

在调用A类的clone方法时,返回一个此类的对象副本。需注意的是对于A类中引用类型变量b跟副本中的引用类型b,他们指向了同一个对象。因此无论是通过A类中的引用变量b还是副本中的引用变量b来操作B对象实际上是对同一个对象进行操作,这种不完全的克隆,称为浅克隆。

举例1:

//学生的课程分数类

package com.langsin.item;

publicclass Course {

Course(String chinese,String english,String math){

this.chinese = chinese;

this.english = english;

this.math = math;

}

private String chinese;

private String english;

private String math;

public String getChinese() {

return chinese;

}

publicvoid setChinese(String chinese) {

this.chinese = chinese;

}

public String getEnglish() {

return english;

}

publicvoid setEnglish(String english) {

this.english = english;

}

public String getMath() {

return math;

}

publicvoid setMath(String math) {

this.math = math;

}

}

// 学生的信息类

package com.langsin.item;

publicclass Student implements Cloneable{

Student(String name,String age,Course cour){

this.name = name;

this.age = age;

this.cour = cour;

}

private String name;

private String age;

private Course cour;

public String getName() {

return name;

}

publicvoid setName(String name) {

this.name = name;

}

public String getAge() {

return age;

}

publicvoid setAge(String age) {

this.age = age;

}

public Course getCour() {

return cour;

}

publicvoid setCour(Course cour) {

this.cour = cour;

}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

// 测试方法

publicstaticvoid main(String[] args) throws Exception{

Student st1 = new Student("张三","24",new Course("85","85","85"));

System.out.println(st1.getName()); // 张三

System.out.println(st1.getCour().getChinese()); //85

Student st2 = (Student)st1.clone();

st2.setName("李四");

System.out.println(st2.getName()); // 李四

System.out.println(st2.getCour().getChinese()); //85

System.out.println("====================");

st1.getCour().setChinese("36");

System.out.println(st1.getCour().getChinese()); //36

System.out.println(st2.getCour().getChinese()); //36

st2.getCour().setEnglish("88");

System.out.println(st1.getCour().getEnglish()); // 88

System.out.println(st2.getCour().getEnglish()); //88

}

2、深克隆

对于IO流中的对象流ObjectInputStrean、ObjectOutputStream来说,当把对象从内存中写入到文件中时,写入的对象保持了对象当时的一个状态。同样如果有两个类,一个A类,一个B类,A类中包含了对B类的一个引用,如果把A类型的对象a1写入到文件中时,因为要保存状态,因此会把a1中的引用类型的B对象也写入到文件中去,当我们把a1对象读取出来时, 此时这个对象是以完全独立的方式存在,与a1没有什么关系了,这时此对象可以成为a1的副本对象a2。通过a1操作它的引用类型b时,a2中的引用类型并不发生变化。这种克隆成为深克隆。

举例2:

// 学生课程类

package com.langsin.item;

import java.io.Serializable;

publicclass Course implements Serializable{

Course(String chinese,String english,String math){

this.chinese = chinese;

this.english = english;

this.math = math;

}

private String chinese;

private String english;

private String math;

public String getChinese() {

return chinese;

}

publicvoid setChinese(String chinese) {

this.chinese = chinese;

}

public String getEnglish() {

return english;

}

publicvoid setEnglish(String english) {

this.english = english;

}

public String getMath() {

return math;

}

publicvoid setMath(String math) {

this.math = math;

}

}

//学生信息类

package com.langsin.item;

import java.io.Serializable;

publicclass Student implements Serializable{

Student(String name,String age,Course cour){

this.name = name;

this.age = age;

this.cour = cour;

}

private String name;

private String age;

private Course cour;

public String getName() {

return name;

}

publicvoid setName(String name) {

this.name = name;

}

public String getAge() {

return age;

}

publicvoid setAge(String age) {

this.age = age;

}

public Course getCour() {

return cour;

}

publicvoid setCour(Course cour) {

this.cour = cour;

}

}

// 测试方法

publicstaticvoid main(String[] args) throws Exception{

Student st1 = new Student("张三","24",new Course("85","85","85"));

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./abc/aa.txt")));

oos.writeObject(st1);

oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./abc/aa.txt")));

Student st2 = (Student)ois.readObject();

System.out.println("=====================");

System.out.println(st1.getName()); // 张三

System.out.println(st2.getName()); // 张三

st2.setName("李四");

System.out.println(st1.getName()); // 张三

System.out.println(st2.getName()); // 李四

System.out.println("=============================");

st1.getCour().setChinese("78");

System.out.println(st1.getCour().getChinese()); //78

System.out.println(st2.getCour().getChinese()); // 85

st2.getCour().setEnglish("99");

System.out.println(st1.getCour().getEnglish()); // 85

System.out.println(st2.getCour().getEnglish()); //99

}

11.10 ZipOutputStream、ZipFile、ZipInputStream

在软件开发中我们会经常处理一些文件的传送问题,如果文件内容较多,那么文件就比较大,在传送过程中就会花费较多的时间去等待文件的传送完毕,如果我们将文件先一步进行压缩为一个较小的文件,那么将会大大缩小文件传递所花费的时间。

在日常中我们常见的压缩文件格式为:zip、jar、GZ

在java中同样可以实现对文件的压缩,zip是一种常见的压缩文件,java中实现zip的压缩需要导入java.util.zip包,可以用此包下的ZipFile、ZipOutputStream、ZipInputStream、ZipEntry来实现压缩。

ZipEntry代表的是压缩包中的一个文件实体。

举例1:将一个文件压缩为一个新的压缩文件

publicstaticvoid main(String[] args) throws Exception {

// 创建一个文件实例对象

File file = new File("./abc/aa.txt");

// 创建一个压缩文件的实例对象

File zipFile = new File("./abc/one.zip");

// 将要压缩的文件首先读入到输入流中

InputStream is = new FileInputStream(file);

// 创建一个压缩流对象

ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));

zipOut.putNextEntry(new ZipEntry(file.getName()));

int temp = 0;

while((temp=is.read())!=-1){

zipOut.write(temp);

}

is.close();

zipOut.close();

}

举例2:将一个文件夹压缩为一个压缩文件

publicstaticvoid main(String[] args) throws Exception {

// 创建一个文件实例对象

File file = new File("./abc/bb");

// 创建一个压缩文件的实例对象

File zipFile = new File("./abc/bb.zip");

// 创建一个压缩流对象

ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));

// 如果要压缩的文件是一个文件夹,只需要创建一个ZipEntity的实例即可

zipOut.putNextEntry(new ZipEntry(new ZipEntry(file.getName()+"/")));

// 创建完成后不需要写入,因为文件夹是空文件,

// 因此既不需要读入文件也不需要写入文件,关闭即可

zipOut.close();

}

举例3:将一个含有文件、文件夹、文件夹中含有文件的复杂文件夹压缩为一个文件

publicstaticvoid main(String[] args) throws Exception {

// 创建一个要压缩的文件目录的实例对象

File file = new File("./abc");

// 创建一个最后要生成的压缩文件的实例对象

File zipFile = new File("./abc.zip");

// 创建一个压缩流对象,所有的信息都要放到此这个流中

ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));

// 调用一个对文件目录或文件进行压缩的的处理方法

dealFile(zos,file,"");

// 压缩流要在方法调用完成之后关闭,因为在方法使用了递归处理,

// 并不能确定压缩流在何时要处理完成

zos.close();

}

publicstaticvoid dealFile(ZipOutputStream zos,File file,String filePath) throws Exception{

// 首先判断要处理的是个文件还是个文件夹

if(file.isDirectory()){

//如果是个文件夹,则添加文件夹的zipEntity对象即可,然后再处理文件夹下的文件

zos.putNextEntry(new ZipEntry(filePath+file.getName()+"/"));

File[] fileList = file.listFiles();

for(int i=0;i<fileList.length;i++){

dealFile(zos,fileList[i],filePath+file.getName()+"/");

}

}else{

// 对于文件的处理,首先要将文件读取出来,然后写入到压缩文件中去

InputStream is = new FileInputStream(file);

zos.putNextEntry(new ZipEntry(filePath+file.getName()));

int temp = 0;

while((temp=is.read())!=-1){

zos.write(temp);

}

is.close();

}

}

11.11 RandomAccessFile

RandomAccessFile的唯一父类是Object,与其他流父类不同。是用来访问那些保存数据记录的文件的,这样你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。

RandomAccessFile竟然会是不属于InputStream和OutputStream类系的。实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫不相干,甚至都没有用InputStream和OutputStream已经准备好的功能;它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是从零开始写的。这可能是因为RandomAccessFile能在文件里面前后移动,所以它的行为与其它的I/O类有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。

基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream粘起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )。此外,它的还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件,从这一点上看,假如RandomAccessFile继承了DataInputStream,它也许会干得更好。

举例1:

publicstaticvoid main(String[] args) throws Exception {

// 创建一个RandomAccessFile类,根据构造方法,传入一个要操作的文件

// 同时说明对此文件是读写操作,"r"表示对文件只读

RandomAccessFile file = new RandomAccessFile(new File("./abc/aa.txt"), "rw");

// 往文件中写时可以指定从文件中那个位置开始写入,即跨过多少个字符开始写

file.seek(10);

// 如果输入中文时,需要将中文转换成字节数组进行写入防止乱码

file.write("你好".getBytes());

file.close();

}

注意:如果写入的文件信息跨行,需要在上一行的长度基础上+1个长度,即(\\r)回车符,在文件中一个中文字符代表2个长度,普通英文字符代表一个长度。

IO作业:附件—控制台下操作学生管理信息系统说明书

第12章:AWT编程

现代的编程已经脱离了单调的黑窗体,即Dos环境下运行程序。现代的用户也早已经习惯了GUI(Graphics User Interface)图形化用户界面。

Java使用AWT和Swing类完成图形用户界面编程,其中AWT的全称是抽象窗口工具集(Abstract Window Toolkit),它是Sun最早提供的GUI(Graphics User Interface图形化用户界面)库,这个GUI库提供了一些基本功能,但这个GUI库的功能比较有限,所有后来有提供了Swing库。通过使用AWT和Swing提供的图形界面组件库,Java的图形用户界面编程非常简单,程序只需要依次创建所需要的图形组件,并以合适的方式将这些组件组织在一起,就可以开发出非常美观的用户界面。

要创建GUI程序,可以大概的分为以下几步:

创建顶层窗口

创建组件、如:文本框、标签、按钮

确定窗口中各组件的排列方式

将组件添加到窗口

12.1 GUI(图形用户界面)和AWT

JDK1.0发布时,Sun提供了一套基本的GUI类库,这个GUI类库能够在所有平台下都运行,这套基本类库被称为“抽象窗口工具集”,它为Java应用程序提供了基本的图形组件。AWT是窗口框架,它从不同的窗口系统中抽取出共同组件,当程序运行时,将这些组件的创建和动作委托给程序所在的运行平台。AWT编写图形界面应用时,程序仅指定了界面组件的位置和行为,并未提供真正的实现,JVM调用操作系统本地的图形界面来创建和平台一致的对等体。

使用AWT创建的图形界面应用和所在的运行平台有相同的界面风格,比如在Windows操作系统,它就表现出Windows风格,在Unix操作系统上,就表现出Unix风格。

但是在实际应用中,AWT出现了几个如下问题:

使用AWT做出的图形用户界面在所有的平台上都显得很丑陋,功能也非常有限。

AWT为了迎合所有主流操作系统的界面设计,AWT组件只能使用这些操作系统上图形界面组件的交集,所以不能使用特定操作系统上复杂的图形界面组件。

AWT用的是非常笨拙的、非面向对象的编程模式。

1996年,网景公司开发了一套工作方式完全不同的GUI库,简称IFC(Internet Foundation Classes),这套GUI库的所有图形界面组件,例如文本框、按钮等都是在空白窗口上的,之后窗口本身借助于操作系统的窗口实现。IFC真正实现了各种平台上的界面一致性。

之后,Sun公司和网景公司合作完善了这种方法,并创建了一套新的用户界面库:Swing。AWT、Swing、辅助功能API、2DAPI以及拖放API共同组成JFC(Java Foundation Classes),其中Swing组件全面代替了AWT组件,保留了AWT组件的事件模型。总体上,AWT是图形用户界面编程的基础,Swing组件替代了绝大部分AWT组件,对AWT图形用户界面编程有极好的补充和加强。

所有的和AWT编程相关的类都放在java.awt包以及它的子包中,AWT编程中有两个基类:Component和MenuComponent。

在java.awt包中提供了两种基类表示图形界面元素:Component(普通组件)和MenuComponent(菜单相关的组件),其中Component代表了一个能以图形化方式显示出来,并可与用户交互的对象,例如Button代表了一个按钮,TextField代表一个文本框等;而MenuComponent则代表图形界面的菜单组件,包括MenuBar菜单条,MenuItem菜单项等子类。

除此之外,AWT图形用户界面编程里还有两个重要的概念:Container和LayoutManager,其中Container是一种特殊的Component,它代表一种容器,可以盛装普通的Component;而LayoutManager则是容器管理其他组件布局的方式。

12.2 AWT容器

任何窗口都可被分解成一个空的容器,容器里盛装了大量的基本组件,通过设置这些基本组件的大小、位置等属性,就可以将该空的容器和基本组件组成一个整体的窗口。图形界面编程非常简单,类似于拼图游戏,容器类相当于“模版”,而普通组件则类似于拼图的图块。创建图形用户界面的过程就是完成拼图的过程。

容器(Container)是Component的子类,因此容器对象本身也是一个组件,具有组件的所有性质,可以调用Component类的所有方法。Component类提供了如下几个常用方法来设置组件的大小、位置和可见性等。

setLocation(int x,int y):设置组件的位置。

setSize(int width;int height):设置组件的大小。

setBounds(int x,int y,int width,int height):同时设置组件的位置、大小。

setVisible(Boolean b):设置该组件是否可见。

容器还可以盛装其他组件,容器类(Container)提供了如下几个常用方法来访问容器里的组件:

Component add(Component comp):向容器中添加其他组件,并返回被添加的组件。

Component getComponent(int x,int y):返回指定点的组件。

int getComponentCount():返回该容器组件内的数量

Component[] getComponent():返回该容器内的所有组件。

AWT主要提供了如下两种主要的容器类型。

Window:可独立存在的顶级窗口。

Panel:可作为容器容纳其他组件,单不能独立存在,必须被添加到其他容器中(如:window、Panel或Applet等)

AWT容器之间的继承关系如下图所示:

12.2.1 框架(Frame)

Frame代表常见的窗体,它是Window类的子类,具有如下几个特点

Frame对象有标题,允许通过拖拉来改变窗口的位置、大小。

初始化时不可见,可以使用setVisible(true)使其显示出来。

默认使用BorderLayout作为其布局管理器。

举例1:

package com.langsin.awt;

import java.awt.Frame;

publicclass MyFrame {

publicstaticvoid main(String[] args) {

Frame myFrame = new Frame("我的第一个窗口");

// setBounds()方法,移动组件并设置其大小,可以用来处理组件的显示位置以及设置组件大小

myFrame.setBounds(50,50,500,300);

myFrame.setVisible(true);

}

}

12.2.2 面板(Panel)

Panel是AWT组件中的另外一个典型的容器,它代表不能独立存在、必须放在其他容器中的容器。Panel的外在表现为一个矩形区域,该区域内可盛装其他组件。Panel容器存在的意义在于为其他组件提供空间,Panel容器具有如下特点:

可作为容器来盛装其他组件,为放置组件提供空间。

不能单独存在,必须放置到其他容器中。

默认使用FlowLayout作为其布局管理器。

举例2:

package com.langsin.awt;

import java.awt.Button;

import java.awt.Frame;

import java.awt.Panel;

import java.awt.TextField;

publicclass MyFrame {

publicstaticvoid main(String[] args) {

//创建一个窗体,构造函数接收的是窗体的名称

Frame myFrame = new Frame("第一个窗口");

//设置窗体的初始坐标位置以及宽度,高度

myFrame.setBounds(50,50,500,300);

//创建一个Panel面板组件

Panel panel = new Panel();

//在Panel中添加个文本域

panel.add(new TextField(20));

//在Panel中添加给按钮

panel.add(new Button("点击"));

//将Panel对象加入到Frame窗体中,因为Panel不能独立存在

myFrame.add(panel);

//让窗体可见

myFrame.setVisible(true);

}

}

ScrollPane是一个带有滚动条的容器,它也不能独立存在,必须被添加到其他容器中。ScrollPane容器具有如下特点:

可作为容器来盛装其他组件,当组件占用空间过大时,ScrollPane自动产生滚动条。当然也可以通过构造器参数来指定默认具有滚动条。

不能独立存在,必须放置在其他容器中。

默认使用BorderLayout作为其布局管理器。ScrollPane通常用于盛装其他容器,所以通常不允许改变ScrollPane的布局管理器。

举例3:

package com.langsin.awt;

import java.awt.Button;

import java.awt.Frame;

import java.awt.ScrollPane;

import java.awt.TextField;

publicclass MyFrame {

publicstaticvoid main(String[] args) {

//创建一个窗体,构造函数接收的是窗体的名称

Frame myFrame = new Frame("第一个窗口");

//设置窗体的初始坐标位置以及宽度,高度

myFrame.setBounds(50,50,500,300);

//创建一个Panel面板组件

ScrollPane panel = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);

//在Panel中添加个文本域

panel.add(new TextField(20));

//在Panel中添加给按钮

panel.add(new Button("点击"));

//将Panel对象加入到Frame窗体中,因为Panel不能独立存在

myFrame.add(panel);

//让窗体可见

myFrame.setVisible(true);

}

}

执行结果我们只能看到一个按钮,而看不到文本框,这是由于布局管理器的缘故,BorderLayout导致了该容器中只有一个组件被显示出来。

12.3 AWT常用组件

AWT组件需要调用运行平台的图形界面来创建和平台一致的对等体,因此AWT只能使用所有平台都支持的公共组件,所以AWT只提供了一些常用的GUI组件。

12.3.1 基本组件

AWT提供的基本组件:

Button:按钮,可以接收单击事件

Canvas:用于绘图的画布

CheckBox:复选框组件(也可变成单选框组件)

CheckBoxGroup:用于将多个CheckBox组件组合成一组,一组Checkbox组件只有一个可以被选中,即全部变成单选框组件。

Choice:下拉选择框组件

Frame:窗口,在GUI程序里通过该类创建一个窗口。

Label:标签类,用于放置提示性文本

List:列表框组件,可以添加多项条目

Panel:不能单独存在的基本容器类,必须放到其他容器中去

Scrollbar:滑动条组件,如果需要用户输入位于某个范围的值,可以使用滑动条组件。当创建一个滑动条时,必须制定它的方向,初始值、滑块的大小、最小值和最大值

ScrollPane:带水平及垂直滚动条的容器组件。

TextArea:多行文本域

TextField:单行文本框

12.3.2 Button(按钮)

常用的构造方法:

Button():创建一个按钮,按钮上的标签没有任何内容

Button():创建一个按钮,自定义按钮标签上的内容

常用的方法:

setBackground(Color color):设置按钮的背景颜色

setEnable(boolean b):设置按钮是否可用

setFont(Font f):设置按钮标签的字体

setForeground(Color color):设置按钮的前景色

setLabel(String text):设置按钮标签的内容

setVisible(boolean b):设置按钮是否可见

举例1:

publicstaticvoid main(String[] args) {

Frame frame = new Frame("我的窗体");

Button bt1 = new Button("苹果");

bt1.setBackground(Color.GREEN);

bt1.setForeground(Color.BLUE);

frame.add(bt1,BorderLayout.NORTH);

Button bt2 = new Button("香蕉");

bt2.setBackground(Color.YELLOW);

Font font = new Font("微软雅黑", Font.PLAIN, 15);

bt2.setFont(font);

frame.add(bt2);

frame.setSize(500, 500);

frame.setLocation(100, 100);

frame.setVisible(true);

}

12.3.3 Label(标签)

常用的构造方法:

Label():创建一个标签,标签上没有任何文字

Label(String text):创建一个标签,并且自定义标签上的文字。

Label(String text,int alignment):创建一个标签,并且自定义标签上的文字及对齐方式。

常用方法:

setAlignment(int alignment):设置标签文本的对齐方式

setBackground(Color color):设置标签的背景色

setEnable(boolean b):设置标签是否可用

setFont(Font font):设置标签文本的字体

setForeground(Color color):设置标签的前景色

setText(String text):设置标签的内容

setVisible(boolean b):设置标签是否可见

举例1:

package com.langsin.awt;

import java.awt.Frame;

import java.awt.GridLayout;

import java.awt.Label;

publicclass LabelDemo extends Frame {

Label lab1 = new Label();

Label lab2 = new Label("Second",Label.LEFT);

Label lab3 = new Label("Third",Label.RIGHT);

Label lab4 = new Label();

LabelDemo(){

this.setLayout(new GridLayout(2,2));

this.lab4.setText("Fourth");

this.lab4.setAlignment(Label.RIGHT);

this.add(lab1);

this.add(lab2);

this.add(lab3);

this.add(lab4);

this.setSize(300,200);

this.setVisible(true);

}

publicstaticvoid main(String[] args) {

new LabelDemo();

}

}

12.3.4 TextField(文本域)

常用的构造方法:

TextField():创建一个文本域

TextField(String text):创建一个文本域,并且有初始值

TextField(String text,int columns):创建一个文本域,有初始值,并且设置列数

TextField(int columns):创建一个文本域,没有初始内容,可设置列数。

常用方法:

requestFocus():为文本域请求焦点

setBackground(Color color):设置标签的背景色

setColumns(int columns):设置文本域的列数

setEditable(boolean b):设置文本域是否可编辑

setEnable(boolean b):设置文本是否可用

setFont(Font f):设置文本域文字的字体

setForeground(Color color):设置文本域的前景色

setText(String text):设置文本域的文本内容

setVisible(boolean b):设置文本域是否可见

举例1:

package com.langsin.awt;

import java.awt.FlowLayout;

import java.awt.Frame;

import java.awt.TextField;

publicclass TextFieldDemo extends Frame {

TextField textField1 = new TextField();

TextField textField2 = new TextField("second");

TextField textField3 = new TextField("Third",10);

TextField textField4 = new TextField();

TextFieldDemo(){

this.setLayout(new FlowLayout());

this.textField1.setEnabled(false);

this.textField2.setEditable(false);

this.textField4.setText("Fourth");

this.add(textField1);

this.add(textField2);

this.add(textField3);

this.add(textField4);

this.setSize(300, 400);

this.setLocation(100, 200);

this.setVisible(true);

}

publicstaticvoid main(String[] args) {

new TextFieldDemo();

}

}

12.3.5 TextArea(多行文本域/文本区)

常用的构造方法

TextArea():创建一个默认大小的空文本区

TextArea(String text):创建一个默认大小的文本区,并初始其内容

TextArea(String text,int rows,int columns):创建一个含有初始内容的文本区域,并设定文本区域的行数跟列数。

TextArea(String text,int rows,int columns,int scrollbars):创建一个文本区,并且自定义文本内容,设定文本区域的内容,设定文本区的行数跟列数,设置滚动条的状态

TextArea(int rows,int columns):创建一个文本区,并且自定义文本区的行数跟列数。

常用的方法:

append(String text):在文本结尾追加自定义的文本。

insert(String text,int begin):在指定的位置插入自定义文本

replaceRange(String text,int begin,int end):将指定范围内的文本替换为自定义的文本

setRows(int rows):设置文本的行数

setColumns(int columns):设置文本的列数

举例1:

package com.langsin.awt;

import java.awt.FlowLayout;

import java.awt.Frame;

import java.awt.TextArea;

publicclass TextAreaDemo extends Frame {

TextArea textArea1 = new TextArea();

TextArea textArea2 = new TextArea("Second", 3 , 15);

TextArea textArea3 = new TextArea("Third",2 , 10);

TextArea textArea4 = new TextArea("Fourth",2 , 10 ,TextArea.SCROLLBARS_BOTH);

TextAreaDemo(){

this.setLayout(new FlowLayout());

this.textArea2.replaceRange("Hello World", 0, 5);

this.textArea4.insert("你好", 6);

this.add(textArea1);

this.add(textArea2);

this.add(textArea3);

this.add(textArea4);

this.setSize(400, 400);

this.setLocation(100, 200);

this.setVisible(true);

}

publicstaticvoid main(String[] args) {

new TextAreaDemo();

}

}

12.3.6 Checkbox(复选框/单选按钮)

常用的构造方法:

Checkbox():创建一个复选框

Checkbox(String text):创建一个复选框,并自定义标签

Checkbox(String text,CheckboxGroup group,boolean state):创建一个复选框,并将其加入到一个复选框组中,设置复选框是否是被选中,加入到复选框组中的复选框将变成单选。

Checkbox(String text,boolean state):创建一个复选框,并设置其是否被选中

常用的方法:

setCheckboxGroup(CheckboxGroup group):将一个复选框加入大一个组中去,将其变为一个单选

setLabel(String label):为复选框添加一个文本标签

setState(boolean b):设置浮选框的状态,是否被选中。

举例1:

package com.langsin.awt;

import java.awt.Checkbox;

import java.awt.CheckboxGroup;

import java.awt.Frame;

import java.awt.GridLayout;

publicclass CheckboxDemo extends Frame {

CheckboxGroup group = new CheckboxGroup();

Checkbox box1 = new Checkbox("First", group, false);

Checkbox box2 = new Checkbox("Second",group, false);

Checkbox box3 = new Checkbox("Third",group, false);

Checkbox box4 = new Checkbox("Fourth", false);

Checkbox box5 = new Checkbox("Fifth", false);

CheckboxDemo(){

this.setLayout(new GridLayout(3,2));

this.box3.setCheckboxGroup(null);

this.box4.setLabel("第四个");

this.box5.setState(true);

this.add(box1);

this.add(box2);

this.add(box3);

this.add(box4);

this.add(box5);

this.setSize(300,200);

this.setVisible(true);

}

publicstaticvoid main(String[] args) {

new CheckboxDemo();

}

}

12.3.7 List(列表框)

常用的构造函数:

List():创建一个空的列表框

List(int rows):创建一个空的列表框,并设置其行数

List(int rows,boolean multipleModal):创建一个空的列表框,并设置其是否使用多选模式。

常用的方法:

add(String item):为列表框追加项目

add(String item,int index):在列表框的index位置添加项目,如果此位置上已有项目,则已有的项目以及项目后面的内容相应的向后移动一个位置。

clear():清楚列表框的所有项目

countItems():返回列表框的项目总数

delItem(int index):在列表框的index位置处,删除项目

delItem(int start,int end):删除从列表框的start位置开始到end结束的所有项目

deselect(int index):排除已选中项目的index位置上的项目

getSelectedItem():返回String值,返回一个选中的项目

getSelectedItems():返回String[]数组,返回所有被选中的项目

10、removeAll():清除列表框的所有项目

11、select(int index):选中列表框index位置上的项目

12、setMultipleMode(boolean b):设置是否采用多行选择模式

举例1:

package com.langsin.awt;

import java.awt.FlowLayout;

import java.awt.Frame;

import java.awt.List;

publicclass ListDemo extends Frame {

private List list = new List(10,true);

private List list2 = new List(6);

ListDemo(){

this.list.add("apple");

this.list.add("orange");

this.list.add("banana",1);

this.list.setMultipleMode(false);

this.list2.add("1111");

this.list2.add("2222");

this.setLayout(new FlowLayout());

this.add(this.list);

this.add(this.list2);

this.setSize(400, 400);

this.setLocation(100, 100);

this.setVisible(true);

}

publicstaticvoid main(String[] args) {

new ListDemo();

}

}

12.3.8 Choice(下拉框)

常用的构造方法:

Choice():创建一个下拉框

常用的方法:

addItem(String item):为选择框添加一个项目

getItem(int index):返回下拉框中index位置上的项目的文本标签

getItemCount():返回下拉框中所有的项目总数。

getSelectedItem():返回以选中的项目的文本标签

insert(String item,int index):在index位置插入文本标签为item的项目

remove(int index):删除index位置上的项目

removeAll():删除所有项目

select(int index):选中index位置上的项目

举例1:

package com.langsin.awt;

import java.awt.Choice;

import java.awt.FlowLayout;

import java.awt.Frame;

publicclass ChoiceDemo extends Frame {

private Choice ch1 = new Choice();

ChoiceDemo(){

this.ch1.getSelectedItem();

this.ch1.add("1111");

this.ch1.add("2222");

this.ch1.add("3333");

this.ch1.add("4444");

this.setLayout(new FlowLayout());

this.add(this.ch1);

this.setSize(300, 300);

this.setLocation(100, 200);

this.setVisible(true);

}

publicstaticvoid main(String[] args) {

new ChoiceDemo();

}

}

12.3.9 Dialog(对话框)

Dialog是Windows类的子类,是一个容器类,属于特殊组件。对话框是可以独立存在的顶级窗口,用法与普通窗口没什么区别。使用对话框时需注意以下两点:

对话框通常依赖于其他窗口,即含有一个父窗口(parent)

对话框有非模式(non-modal)和模式(modal)两种,当某个模式对话框被打开之后,该模式对话框总是位于parent之上;模式对话框被关闭之前,它的依赖窗口无法获得焦点。

Dialog对话框有多个重载的构造器,参数如下:

owner:指定该对话框所依赖的窗口,即可以是窗口,有可以是对话框。

title:指定该对话框的窗口标题

modal:指定该对话框是否是模式的,是Boolean值,true表示模式,false表示非模式。

举例1:

package com.langsin.awt;

import java.awt.BorderLayout;

import java.awt.Button;

import java.awt.Dialog;

import java.awt.Frame;

import java.awt.TextField;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

publicclass Test {

// 创建一个父窗体

static Frame frame = new Frame("测试窗口");

// 创建两个Dialog对话框,一个为模式,一个为非模式

static Dialog d1 = new Dialog(frame, "模式对话框", true);

static Dialog d2 = new Dialog(frame, "非模式对话框", false);

publicstaticvoid main(String[] args) {

frame.setBounds(100, 100, 400, 400);

d1.setBounds(200, 200, 100, 100);

d2.setBounds(300, 300, 100, 100);

// 创建两个按钮,通过按钮事件将窗体显示出来

Button b1 = new Button("模式对话框");

Button b2 = new Button("非模式对话框");

// 为了区分模式与非模式的区别,添加一个文本域进行验证

TextField text = new TextField(40);

// 为按钮加上事件,通过事件将模式对话框调用出来

b1.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

d1.setVisible(true);

}

});

b2.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

d2.setVisible(true);

}

});

frame.add(text,BorderLayout.NORTH);

frame.add(b1,BorderLayout.EAST);

frame.add(b2,BorderLayout.WEST);

frame.pack();`

frame.setVisible(true);

}

}

12.3.10 FileDialog(文件对话框)

FileDialog是一个文件对话框,它是Dialog的子类,用于打开或者保存文件。FileDialog提供了几个构造器,分别支持parent、title、mode三个构造参数,其中parent、title指定文件对话框的所属父窗口和标题;mode指定该窗口用于打开文件或保存文件,此参数支持两个值:FileDialog.LOAD、FileDialog.SAVE。

FileDialog提供了两个方法来获取被打开/保存文件的路径。

getDirectory():获取FileDialog被打开/保存文件的绝对路径。

getFile():获取FileDialog被打开/保存文件的文件名。

举例1:

publicclass Test {

// 创建一个父窗体

static Frame frame = new Frame("测试窗口");

// 创建两个FileDialog对话框,一个为模式,一个为非模式

static FileDialog d1 = new FileDialog(frame, "选择需要打开的文件", FileDialog.LOAD);

static FileDialog d2 = new FileDialog(frame, "选择需要保存的文件", FileDialog.SAVE);

publicstaticvoid main(String[] args) {

frame.setBounds(100, 100, 400, 400);

d1.setBounds(200, 200, 100, 100);

d2.setBounds(300, 300, 100, 100);

// 创建两个按钮,通过按钮事件将FileDialog显示出来

Button b1 = new Button("打开文件");

Button b2 = new Button("保存文件");

// 为按钮加上事件,通过事件将文件对话框调用出来

b1.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

d1.setVisible(true);

// 打印要保存的文件的路径,以及文件的名称

System.out.println(d1.getDirectory()+"========="+d1.getFile());

}

});

b2.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

d2.setVisible(true);

// 打印要保存的文件的路径,以及文件的名称

System.out.println(d1.getDirectory()+"========="+d1.getFile());

}

});

frame.add(b1,BorderLayout.EAST);

frame.add(b2,BorderLayout.WEST);

frame.pack();

frame.setVisible(true);

}

}

12.4 布局管理器

为了使生成的图形用户界面具有良好外观与运行平台没有任何关系,Java提供了布局管理器这个工具类来管理组件在容器中的布局,而不使用直接设置组件位置和大小的方式。

例如:Lable lable = new Lable(“Hello World”);如果让lable标签能够刚好容纳Hello World字符串,也就是实现该标签的最佳大小,即:在Windows平台中可能应该设置长100px,高20px;而在Unix系统上需要设置长120px;高24px;如果让程序员去手动控制每个组件的大小、位置,这将给编程带来巨大的困难,为了解决这个问题,Java提供了LayoutManager布局管理器类,LayoutManager可以根据运行平台来调整组件的大小,程序员要做的是就是为容器选择合适的布局管理器。

所有的AWT容器都有默认的布局管理器,如果没有为容器指定布局管理器,则该容器使用默认的布局管理器。为容器指定布局管理器通过调用容器的setLayout(LayoutManager lm)方法来完成。

AWT提供了FlowLayout、BorderLayout、GridLayout、GridBagLayout、CardLayout5个常用的布局管理器,Swing还提供了一个BoxLayout布局管理器。

12.4.1 FlowLayout布局管理器

在FlowLayout(浮动布局管理器)布局管理器中,组件像水流一样向某方向流动(排列),遇到障碍(边界)就折回,重头开始排列。在默认情况下,FlowLayout布局管理器从左向右排列所有组件,遇到边界就会折回下一行重新开始。

FlowLayout有3个常用的构造方法:默认是居中

FlowLayout():使用默认的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器。

FlowLayout(int align):使用指定的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器。

FlowLayout(int align,int hgap,int vgap):使用指定的对齐方式以及指定的垂直间距、水平间距创建一个FlowLayout布局管理器。

在Panel(面板)和Applet(Java小应用程序)中默认为使用FlowLayout布局管理器,Frame默认使用BorderLayout布局管理器,下面将使用FlowLayout布局管理器来管理Frame容器中的组件。

举例1:

publicstaticvoid main(String[] args) {

//创建一个窗口

Frame frame = new Frame("测试窗体");

frame.setLayout(new FlowLayout(FlowLayout.LEFT, 20, 10));

for(int i=0;i<5;i++){

frame.add(new Button("按钮"+(i+1)+"号"));

}

//设置窗体的最佳大小,如果子组件未显示开,则适当的将包含组件的容器加宽或加高

frame.pack();

//将窗体显示出来

frame.setVisible(true);

}

举例2:

publicstaticvoid main(String[] args) {

// 创建一个窗口

Frame frame = new Frame("测试窗口");

// 创建一个浮动布局管理器,并且指定容器中的组件的排列方式是居中排列,同时

// 组件之间的水平间隔与垂直间隔为10个像素

FlowLayout fl = new FlowLayout(FlowLayout.CENTER, 10, 10);

// 为窗体设置浮动布局管理器

frame.setLayout(fl);

// 添加按钮组件

frame.add(new Button("button1"));

frame.add(new Button("button2"));

frame.add(new Button("button3"));

frame.add(new Button("button4"));

frame.add(new Button("button5"));

frame.add(new Button("button6"));

// 设置窗体的显示位置,以及窗体容器的宽度与高度

frame.setBounds(40, 40, 400, 400);

frame.setVisible(true);

}

12.4.2 BorderLayout布局管理器

BorderLayout(边界布局管理器)将容器分为EAST、SOUTH、WEST、NORTH、CENTER5个区域,普通组件可以被放置在这个5个区域的任意一个里面。

改变BorderLayout的容器大小时,NORTH、SOUTH会在水平方向上调整,而EAST、WEST在垂直方向上调整,CENTER会根据调整在水平、垂直方向上都进行调整。使用BorderLayout布局管理器时需注意以下两点:

1、向使用BorderLayout布局管理器的容器中添加组件时,需要指定要添加到哪个区域中。如果没有指定添加哪个区域中,则默认添加到中间区域中。

2、如果向同一个区域中添加多个组件时,后放入的组件会覆盖先放入的组件。

Frame、Dialog、ScrollPane默认使用BorderLayout布局管理器,BorderLayout有如下两个构造器。

BorderLayout():使用默认的水平间距、垂直间距创建BorderLayout布局管理器。

BorderLayout(int hgap,int vgap):使用指定的水平间距、垂直间距创建BorderLayout布局管理器。

使用BorderLayout布局管理器时,应该使用BorderLayout类的几个静态常量来指定组件添加到哪个区域内。对应的常量为:EAST、SOUTH、WEST、NORTH、CENTER。

举例1:

publicstaticvoid main(String[] args) {

//创建一个窗口

Frame frame = new Frame("测试窗体");

//为窗体创建一个边界布局管理器,同时指定边界的水平方向上的组件之间的间距

//与垂直方向上组件之间的间距

frame.setLayout(new BorderLayout(5, 5));

//向北面的边界中添加一个按钮组件

frame.add(new Button("北面"), BorderLayout.NORTH);

//向西面的边界中添加一个按钮组件

frame.add(new Button("西面"), BorderLayout.WEST);

//向中间的区域内添加一个按钮组件

frame.add(new Button("中央"), BorderLayout.CENTER);

//向东面的区域内添加一个按钮组件

frame.add(new Button("东面"), BorderLayout.EAST);

//向南面的区域内添加一个按钮组件

frame.add(new Button("南面"), BorderLayout.SOUTH);

//将窗体变得更合适一点

frame.pack();

frame.setVisible(true);

}

使用BorderLayout布局管理器时,每个区域的组件都会尽可能大的去占据整个区域,BorderLayout最多只能放置5个组件,但可以放置少于5个组件,如果某个区域没有放置组件,该区域并不会出现空白,旁边的组件会自动占据该区域,从而保证窗口有较好的外观。

另外,虽然BorderLayout布局管理器最多只能放5个组件,但因为容器也是一个组件,所以我们可以先把组件放到一个容器内,在把容器放到BorderLayout布局管理器中。

举例2:

publicstaticvoid main(String[] args) {

//创建一个窗口

Frame frame = new Frame("测试窗体");

//为窗体创建一个边界布局管理器,同时指定边界的水平方向上的组件之间的间距与

//垂直方向上组件之间的间距

frame.setLayout(new BorderLayout(5, 3));

//创建一个面板容器

Panel panel = new Panel();

//在面板中添加一个文本框

panel.add(new TextField(30));

//将面板添加到窗体中

frame.add(panel,BorderLayout.NORTH);

//再创建一个面板,添加按钮

Panel butPanel = new Panel();

//设置面板的宽度与高度

butPanel.setSize(5, 20);

String[] names = {"0","1","2","3","4","5","6","7","8","9","+","-","*","/","="};

for(String flag : names){

butPanel.add(new Button(""+flag+""));

}

//将按钮面板添加到窗体中去

frame.add(butPanel,BorderLayout.CENTER);

frame.pack();

frame.setVisible(true);

}

12.4.3 GridLayout网格布局管理器

GridLayout(网格布局管理器)布局管理器将容器分割成纵横分隔的网格,每个网格所占的区域大小相同,当向使用GridLayout布局管理器的容器中添加组件时,默认为从左向右,从上向下依次添加到每个网格中,与FlowLayout不同的是,放置在GridLayout布局管理器中的各个组件的大小由组件所处的区域来决定。

GridLayout有如下两个构造器:

GridLayout(int rows,int cols):采用指定的行数与列数,以及默认的横向间距、纵向间距将容器分割成多个网格。

GridLayout(int rows,int cols,int hgap,int vgap):采用指定的行数、列数,以及指定的水平间距,垂直间距将容器分割成多个网格。

举例1:

publicstaticvoid main(String[] args) {

//创建一个窗口

Frame frame = new Frame("计算器");

//创建一个面板容器

Panel panel = new Panel();

//在面板中添加一个文本框

panel.add(new TextField(30));

//将面板添加到窗体中

frame.add(panel,BorderLayout.NORTH);

//再创建一个面板,添加按钮

Panel butPanel = new Panel(new GridLayout(3,5,4,4));

//设置面板的宽度与高度

butPanel.setSize(5, 20);

String[] names = {"0","1","2","3","4","5","6","7","8","9","+","-","*","/","="};

for(String flag : names){

butPanel.add(new Button(""+flag+""));

}

//将按钮面板添加到窗体中去

frame.add(butPanel);

frame.pack();

frame.setVisible(true);

}

12.4.4 GridBagLayout网格包布局管理器比较复杂

GridBagLayout(网格包布局管理器)布局管理器的功能最强大,同时也最复杂,与GridLayout布局管理器不同的是,在GridBagLayout布局管理器中,一个组件可以跨一个或多个网格,并可以设置各个网格的大小互不相同,从而增加了布局的灵活性。当窗体的大小发生变化时,GridBagLayout布局管理器也可以准确的控制窗口各部分的拉伸。

为了处理GridBagLayout中组件的大小,跨越性,Java提供了GridBagConstraints对象,该对象与特定的组件关联,用于控制该GUI组件的大小、跨越性。

GridBagLayout布局管理器的使用方式如下:

创建GridBagLayout布局管理器对象,并指定容器使用该布局管理器。

GridBagLayout gridBagLayout = new GridBagLayout();

container.setLayout(gridBagLayout);

创建GridBagConstrains对象,并设置该对象的相关属性,此对象用于控制要添加的GUI组件的大小及跨越性。

GridBagConstraints gbc = new GridBagConstraints();

gbc.gridx = 2; //设置受该对象控制的GUI组件位于网格中横向索引的位置

gbc.gridy = 1; //设置受该对象控制的GUI组件位于网格中纵向索引的位置

gbc.gridwidth = 2; //设置受该对象控制的GUI组件横向跨越多少个网格

gbc.gridhight = 1; //设置受该对象控制的GUI组件纵向跨越多少个网格

调用GridBagLayout对象的方法来建立GridBagConstraints对象和受控组件之间的关联。

container.setConstraints(comp,gbc);

添加组件,与普通布局管理器添加组件的方法完全一致。

container.add(comp);

注意:如果需要向一个容器中添加多个GUI组件,则需要重复执行2-4的步骤,GridBagConstraints对象可以重复使用,因此在程序中只需要创建此类的一个实例对象即可。每次添加GUI组件时,重新修改GridBagConstraints对象的属性即可。

使用GridBagLayout布局管理器,重点在于GridBagConstraints对象,它能精确控制每个GUI组件与容器之间的布局关系,该类具有如下属性:

gridx、gridy:设置受该对象控制的GUI组件从左上角开始在网格的横向索引、纵向索引。这两个值还可以是GridBagConstraints.RELATIVE(默认值),表示当前组件紧跟随在上一个组件之后。

gridwidth、gridheight:设置受该对象控制的GUI组件横向、纵向跨越多少个网格,两个属性值默认都是1,表示不进行跨越。如果设置这两个的属性值为:GridBagConstraints.REMAINDER,表示受该对象控制的GUI组件是横向、纵向上的最后一个组件。

fill:设置受该对象控制的GUI组件如何占据空白区域。

GridBagConstraints.NONE:表示组件不进行扩大。

GridBagConstraints.HORIZONTAL:GUI组件水平扩大占据空白区域

GridBagConstraints.VERTICAL:GUI组件垂直扩大占据空白区域

GridBagConstraints.BOTH:GUI组件水平、垂直扩大占据空白区域

ipadx、ipady:设置GUI组件在区域内横向、纵向的大小。如果设置了这两个属性,则组件会在默认的宽度和高度的基础上增加,最小宽度+ipdax*2个像素,最小高度+ipady*2个像素。

insets:设置受该对象控制的GUI组件的外部填充的大小,即该组件边界和显示区域边间之间的距离。

anchor:设置受该对象控制的GUI组件在其显示区域的布局方式。

GridBagConstraints.CENTER:居中显示

GridBagConstraints.NORTH:上中显示(即:北面)

GridBagConstraints.NORTHWEST:左上显示(西北角)

GridBagConstraints.NORTHEAST:右上显示(东北角)

GridBagConstraints.SOUTH:下中显示(南面)

GridBagConstraints.SOUTHWEST:左下显示(西南角)

GridBagConstraints.SOUTHEAST:右下显示(东南角)

GridBagConstraints.WEST:左中显示(西面)

GridBagConstraints.EAST:右中显示(东面)

weightx、weighty:用来设置窗口变大时,各组件跟着变大的比例,当数字越大,表示组件能到更多的空间,默认值为0。

举例1:

publicstaticvoid main(String[] args) {

//创建一个窗口

Frame frame = new Frame("测试窗口");

//创建一个网个包布局管理器

GridBagLayout gb = new GridBagLayout();

frame.setLayout(gb);

//创建一个网格包布局管理器的约束对象

GridBagConstraints gbc = new GridBagConstraints();

//设置所有的组件都可以随着窗口的扩大而扩大

gbc.fill = GridBagConstraints.BOTH;

//设置组件在水平位置上的扩展权重

gbc.weightx = 1;

//创建一个组件,并将组件与约束对象建立管理,最后将组件加入到容器中

for(int i=0;i<4;i++){

Button button = new Button("按钮"+i);

gb.setConstraints(button, gbc);

frame.add(button);

}

//设置下一个组件为容器中横向位置上的最后一个组件

Button button = new Button("按钮4");

gbc.gridwidth = GridBagConstraints.REMAINDER;

gb.setConstraints(button, gbc);

frame.add(button);

//创建一个新的组件,设置此组件在水平位置上不会进行扩展,此组件为新的一行的开始位置

button = new Button("按钮5");

gbc.weightx = 0;

gb.setConstraints(button, gbc);

frame.add(button);

//设置下一个组件,此组件占据后面以为网格

button = new Button("按钮6");

gbc.gridwidth = 2;

gbc.gridheight = 2;

gb.setConstraints(button, gbc);

frame.add(button);

//设置下一个组件,此组件占据后面以为网格

button = new Button("按钮7");

//gbc.gridwidth = 3;

gbc.gridheight = 2;

gbc.gridwidth = GridBagConstraints.REMAINDER;

gb.setConstraints(button, gbc);

frame.add(button);

//创建一个新的按钮放置在第三行

button = new Button("按钮8");

gbc.gridwidth = 1;

//设置此行的组件跨两行

gbc.gridheight = 2;

gbc.weighty = 1;

gb.setConstraints(button, gbc);

frame.add(button);

gbc.weighty = 0;

gbc.gridwidth = GridBagConstraints.REMAINDER;

//设置此行的组件重新跨一行

gbc.gridheight = 1;

button = new Button("按钮9");

gb.setConstraints(button, gbc);

frame.add(button);

button = new Button("按钮10");

gb.setConstraints(button, gbc);

frame.add(button);

frame.pack();

frame.setVisible(true);

}

12.4.5 CardLayout布局管理器

CardLayout(卡片布局管理器)布局管理器以时间来管理它里面的组件,它将加入到容器中的组件看做一叠卡片,每次只能看到最上面的那个容器。CardLayout提供了两个构造器:

CardLayout():创建一个默认的CardLayout布局管理器。

CardLayout(int hgap,int vgap):通过指定卡片与容器左右边界的间距(hgap)、上下边界(vgap)的间距来创建CardLayout布局管理器。

CardLayout用于控制组件可见的5个常用方法:

first(Container target):显示容器target中的第一张卡片

last(Container target):显示容器target中的最后一张卡片

previous(Container target):显示容器target中的前一张卡片

next(Container target):显示容器target中的下一张卡片

show(Container target,String name):显示容器target中指定名称的卡

举例1:

publicstaticvoid main(String[] args) {

// 创建一个窗口

Frame frame = new Frame("测试窗口");

// 创建一个卡片布局管理器

final CardLayout cl = new CardLayout();

// 创建一个panel面板使用卡片布局管理器

final Panel panel1 = new Panel();

panel1.setLayout(cl);

// 往面板中加入几个按钮

for (int i = 0; i < 5; i++) {

panel1.add("按钮" + (i + 1), new Button("按钮" + (i + 1)));

}

// 将此面板加入到窗体中

frame.add(panel1);

// 再创建一个面板,加几个按钮,同时对按钮添加事件机制

Panel panel2 = new Panel();

Button button = new Button("上一张");

button.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

cl.previous(panel1);

}

});

panel2.add(button);

// 在面板中添加第二个按钮

button = new Button("下一张");

button.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

cl.next(panel1);

}

});

panel2.add(button);

// 在面板中添加第三个按钮

button = new Button("第一张");

button.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

cl.first(panel1);

}

});

panel2.add(button);

// 在面板中添加第四个按钮

button = new Button("最后张");

button.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

cl.last(panel1);

}

});

panel2.add(button);

// 在面板中添加第五个按钮

button = new Button("第三张");

button.addActionListener(new ActionListener() {

publicvoid actionPerformed(ActionEvent e) {

cl.show(panel1,"按钮3");

}

});

panel2.add(button);

//在往窗体中加入第二个组件及之后的组件必须制定位置,不然会将之前的组件覆盖掉

frame.add(panel2,BorderLayout.SOUTH);

frame.pack();

frame.setVisible(true);

}

12.4.6 绝对定位

Java也可以对GUI组件进行绝对定位,在Java容器中采用绝对定位的步骤如下:

将Container的布局管理器设置为null:container.setLayout(null)

向容器中添加组件时,先调用setBounds()或setSize()方法来设置组件的大小、位置,或者直接创建GUI组件时通过构造参数指定该组件的大小、位置,然后将该组件添加到容器中。

举例1:

publicstaticvoid main(String[] args) {

// 创建一个窗口

Frame frame = new Frame("测试窗口");

//设置窗体不使用布局管理器

frame.setLayout(null);

//创建一个按钮,同时指定它的绝对位置,在X坐标上的值为40,Y坐标上的值为40

//按钮的宽度为50,高度为20,

Button button1 = new Button("按钮1");

button1.setBounds(40, 40, 50, 20);

frame.add(button1);

//创建一个按钮2,同时指定它的绝对位置,将按钮放置在第一个按钮的下面

//则X坐标保持一致,Y坐标在原来的基础上加上第一个按钮的高度

Button button2 = new Button("按钮2");

button2.setBounds(40, 60, 50, 20);

frame.add(button2);

//设置窗体的出现位置,以及窗体的宽度,高度

frame.setBounds(40, 40, 600, 600);

frame.setVisible(true);

}

Java 基础知识文档分享给大家,觉得好的话,来波关注,留言发给你们,字数限制不能全发!

相关阅读

关键词不能为空
极力推荐

ppt怎么做_excel表格制作_office365_word文档_365办公网