java

Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。

Java可运行于多个平台,如Windows, Mac OS,及其他多种UNIX版本的系统。

本教程通过简单的实例将让大家更好的了解JAVA编程语言。

Java 在线工具   JDK 在线中文手册


我的第一个JAVA程序

以下我们通过一个简单的实例来展示Java编程,本实例输出"Hello World",这也是所有语言入门的第一个实例程序:

public class Main{    public static void main(String []args) {       System.out.println("Hello World");    }} 

开始学习JAVA编程


Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称。由詹姆斯·高斯林(James Gosling)和同事们共同研发,并在1995年正式推出。

Java分为三个体系:

  • JavaSE(J2SE)(Java2 Platform Standard Edition,java平台标准版)
  • JavaEE(J2EE)(Java 2 Platform,Enterprise Edition,java平台企业版)
  • JavaME(J2ME)(Java 2 Platform Micro Edition,java平台微型版)

2005年6月,JavaOne大会召开,SUN公司公开Java SE 6。此时,Java的各种版本已经更名以取消其中的数字"2":J2EE更名为Java EE, J2SE更名为Java SE,J2ME更名为Java ME。

2009年,sun公司被oracle收购.

2018年,开源组织Eclipse基金会宣布将JavaEE(Enterprise Edition)被更名为JakartaEE(雅加达)。


主要特性

  • Java语言是简单的:

    Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java丢弃了C++中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java语言不使用指针,而是引用。并提供了自动的内存回收管理机制,使得程序员不必为内存管理而担忧。

  • Java语言是面向对象的:

    Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为implements)。Java语言全面支持动态绑定,而C++语言只对虚函数使用动态绑定。总之,Java语言是一个纯的面向对象程序设计语言。

  • Java语言是分布式的:

    Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。

  • Java语言是健壮的:

    Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。Java的安全检查机制使得Java更具健壮性。

  • Java语言是安全的:

    Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。除了Java语言具有的许多安全特性以外,Java对通过网络下载的类具有一个安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类SecurityManager)让Java应用设置安全哨兵。

  • Java语言是体系结构中立的:

    Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。

  • Java语言是可移植的:

    这种可移植性来源于体系结构中立性,另外,Java还严格规定了各个基本数据类型的长度。Java系统本身也具有很强的可移植性,Java编译器是用Java实现的,Java的运行环境是用ANSI C实现的。

  • Java语言是解释型的:

    如前所述,Java程序在Java平台上被编译为字节码格式,然后可以在实现这个Java平台的任何系统中运行。在运行时,Java平台中的Java解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。

  • Java是高性能的:

    与那些解释型的高级脚本语言相比,Java的确是高性能的。事实上,Java的运行速度随着JIT(Just-In-Time)编译器技术的发展越来越接近于C++。

  • Java语言是多线程的:

    在Java语言中,线程是一种特殊的对象,它必须由Thread类或其子(孙)类来创建。通常有两种方法来创建线程:其一,使用型构为Thread(Runnable)的构造子将一个实现了Runnable接口的对象包装成一个线程,其二,从Thread类派生出子类并重写run方法,使用该子类创建的对象即为线程。值得注意的是Thread类已经实现了Runnable接口,因此,任何一个线程均有它的run方法,而run方法中包含了线程所要运行的代码。线程的活动由一组方法来控制。Java语言支持多个线程的同时执行,并提供多线程之间的同步机制(关键字为synchronized)。

  • Java语言是动态的:

    Java语言的设计目标之一是适应于动态变化的环境。Java程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java中的类有一个运行时刻的表示,能进行运行时刻的类型检查。


发展历史

  • 1995年5月23日,Java语言诞生
  • 1996年1月,第一个JDK-JDK1.0诞生
  • 1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入JAVA技术
  • 1996年9月,约8.3万个网页应用了JAVA技术来制作
  • 1997年2月18日,JDK1.1发布
  • 1997年4月2日,JavaOne会议召开,参与者逾一万人,创当时全球同类会议规模之纪录
  • 1997年9月,JavaDeveloperConnection社区成员超过十万
  • 1998年2月,JDK1.1被下载超过2,000,000次
  • 1998年12月8日,JAVA2企业平台J2EE发布
  • 1999年6月,SUN公司发布Java的三个版本:标准版(JavaSE,以前是J2SE)、企业版(JavaEE以前是J2EE)和微型版(JavaME,以前是J2ME)
  • 2000年5月8日,JDK1.3发布
  • 2000年5月29日,JDK1.4发布
  • 2001年6月5日,NOKIA宣布,到2003年将出售1亿部支持Java的手机
  • 2001年9月24日,J2EE1.3发布
  • 2002年2月26日,J2SE1.4发布,自此Java的计算能力有了大幅提升
  • 2004年9月30日18:00PM,J2SE1.5发布,成为Java语言发展史上的又一里程碑。为了表示该版本的重要性,J2SE1.5更名为Java SE 5.0
  • 2005年6月,JavaOne大会召开,SUN公司公开Java SE 6。此时,Java的各种版本已经更名,以取消其中的数字"2":J2EE更名为Java EE,J2SE更名为Java SE,J2ME更名为Java ME
  • 2006年12月,SUN公司发布JRE6.0
  • 2009年04月20日,甲骨文74亿美元收购Sun。取得java的版权。
  • 2010年11月,由于甲骨文对于Java社区的不友善,因此Apache扬言将退出JCP。
  • 2011年7月28日,甲骨文发布java7.0的正式版。
  • 2014 年 3 月 18 日,Oracle 公司发表 Java SE 8。
  • 2017 年 9 月 21 日,Oracle 公司发表 Java SE 9
  • 2018 年 3 月 21 日,Oracle 公司发表 Java SE 10
  • 2018 年 9 月 25 日,Java SE 11 发布
  • 2019 年 3 月 20 日,Java SE 12 发布

Java开发工具

Java语言尽量保证系统内存在1G以上,其他工具如下所示:

  • Linux 系统或者Windows 95/98/2000/XP,WIN 7/8/10/11系统
  • Java JDK 8以上版本(这是必须的)
  • Notepad编辑器或者其他编辑器(虽然使用记事本也可以进行代码编写,但一个舒适的工具可以提高编程的效率,小编这里推荐使用vscode)。
  • IDE:Eclipse或者IntelliJ IDEA(对于初学者而言,这两款IDE都很不错,但却不是必要的(初学者使用到的功能比较少,不需要用到这么多功能))

安装好以上的工具后,我们就可以输出Java的第一个程序"Hello World!"

public class MyFirstJavaProgram {    public static void main(String []args) {       System.out.println("Hello World");    }} 

在下一章节我们将介绍如何配置java开发环境。


在本章节中我们将为大家介绍如何搭建Java开发环境,以及不同系统下的环境变量怎么配置。

 本站提供java在线运行工具:https://www.51coolma.cn/tryrun/runcode?lang=java-openjdk,但由于在线环境权限不足,大部分代码都不能直接在线运行,所以搭好一个本地可运行的环境相当重要!!!


window系统安装java

下载JDK

首先我们需要下载java开发工具包JDK,下载地址:https://www.oracle.com/java/technologies/downloads/#java11-windows

点击如下下载按钮:

java下载页面

在下载页面中你需要选择接受许可,并根据自己的系统选择对应的版本,本文以 Window 64位系统为例:

选择版本

下载后JDK的安装根据提示进行,还有安装JDK的时候也会安装JRE,一并安装就可以了。

安装JDK,安装过程中可以自定义安装目录等信息,例如我们选择安装目录为 C:Program Files (x86)Javajdk11.0.1。

配置环境变量

1.右击“我的电脑”→“属性”→“高级系统设置”→“高级”→“环境变量”;


高级系统设置


2.选择"高级"选项卡,点击"环境变量";
4

3. 新建“JAVA_HOME”系统变量(点击“系统变量”下方的“新建”按钮,填写变量名与变量值,点击“确定”)

12



8


4. 同上,新建“CLASSPATH”系统变量,变量值为“.;%JAVA_HOME%lib;%JAVA_HOME%libdt.jar;%JAVA_HOME%lib ools.jar;”。(引号内的全部内容,注意最前方是点不是逗号)

9


5. 双击“系统变量”下的“Path”变量进行编辑。(此时可以看到JAVA_HOME已经存在于系统变量中),(有的电脑"Path"也写作“PATH”)

10


这是 Java 的环境配置,配置完成后,你可以启动 Eclipse 来编写代码,它会自动完成java环境的配置。

在"系统变量"中设置3项属性,JAVA_HOME,PATH,CLASSPATH(大小写无所谓),若已存在则点击"编辑",不存在则点击"新建"。

变量设置参数如下:

  • 变量名:JAVA_HOME
  • 变量值:C:Program Files (x86)Javajdk1.8.0_91        // 要根据自己的实际路径配置
  • 变量名:CLASSPATH
  • 变量值:.;%JAVA_HOME%libdt.jar;%JAVA_HOME%lib ools.jar;         //记得前面有个"."
  • 变量名:Path
  • 变量值:%JAVA_HOME%in;%JAVA_HOME%jrein;

注意:如果使用1.5以上版本的JDK,不用设置CLASSPATH环境变量,也可以正常编译和运行Java程序。

通过控制台测试JDK是否安装成功

1、同时按键盘上“win”、“R”两个键打开运行,输入“cmd”确定打开控制台。

11

2、键入命令: java -versionjavajavac 几个命令,出现以下信息,说明环境变量配置成功;


jdk安装

Linux,UNIX,Solaris,FreeBSD环境变量设置

环境变量PATH应该设定为指向Java二进制文件安装的位置。如果设置遇到困难,请参考shell文档。

例如,假设你使用bash作为shell,你可以把下面的内容添加到你的 .bashrc文件结尾: export PATH=/path/to/java:$PATH


流行JAVA开发工具

正所谓工欲善其事必先利其器,我们在开发java语言过程中同样需要一款不错的开发工具,目前市场上的IDE很多,本文为大家推荐以下几款java开发工具:

13

  • IntelliJ IDEA(推荐):一个好用的java IDE,专业版功能强大但需要付费,开源版基础功能足够雄厚让你有更好的代码开发体验

      下载地址:https://www.jetbrains.com/zh-cn/idea/

Notepad++ : Notepad++ 是在微软windows环境之下的一个免费的代码编辑器,下载地址: http://notepad-plus-plus.org/

此外还有很多优秀的代码编辑器,比如vscode,sublime,vim等,在此不做过多介绍。

使用 Eclipse 运行第一个 Java 程序

HelloWorld.java 文件代码:

public class HelloWorld {    public static void main(String []args) {       System.out.println("Hello World");    }}


一个Java程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • :类是一个模板,它描述一类对象的行为和状态。
  • 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
  • 实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。

第一个Java程序

下面看一个简单的 Java 程序,它将打印字符串 Hello World

public class MyFirstJavaProgram {   /* 第一个Java程序.      * 它将打印字符串 Hello World    */    public static void main(String []args) {       System.out.println("Hello World"); // 打印 Hello World    }} 

下面将逐步介绍如何保存、编译以及运行这个程序:

  • 打开Notepad,把上面的代码添加进去;
  • 把文件名保存为:MyFirstJavaProgram.java;
  • 打开cmd命令窗口,进入目标文件所在的位置,假设是C:
  • 在命令行窗口键入 javac MyFirstJavaProgram.java  按下 enter 键编译代码。如果代码没有错误,cmd 命令提示符会进入下一行。(假设环境变量都设置好了)。
  • 再键入 java MyFirstJavaProgram 按下 Enter 键就可以运行程序了

你将会在窗口看到 Hello World

C : > javac MyFirstJavaProgram.javaC : > java MyFirstJavaProgram Hello World

基本语法

编写Java程序时,应注意以下几点:

  • 大小写敏感:Java是大小写敏感的,这就意味着标识符Hello与hello是不同的。
  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass 。
  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
  • 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记Java是大小写敏感的),文件名的后缀为.java。(如果文件名和类名不相同则会导致编译错误)。
  • 主方法入口:所有的Java 程序由public static void main(String[] args)​ 方法开始执行。

Java标识符

Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

关于Java标识符,有以下几点需要注意:

  • 所有的标识符都应该以字母(A-Z或者a-z),美元符($)、或者下划线(_)开始
  • 首字符之后可以是字母(A-Z 或者 a-z),美元符($)、下划线(_)或数字的任何字符组合
  • 关键字不能用作标识符
  • 标识符是大小写敏感的
  • 合法标识符举例:age、$salary、_value、__1_value
  • 非法标识符举例:123abc、-salary

Java修饰符

像其他语言一样,Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:

  • 访问控制修饰符 : default, public , protected, private
  • 非访问控制修饰符 : final, abstract, static,synchronized 和 volatile

在后面的章节中我们会深入讨论Java修饰符。

Java变量

Java中主要有如下几种类型的变量

  • 局部变量
  • 类变量(静态变量)
  • 成员变量(非静态变量)

Java数组

数组是储存在堆上的对象,可以保存多个同类型变量。在后面的章节中,我们将会学到如何声明、构造以及初始化一个数组。


Java枚举

Java 5.0引入了枚举,枚举限制变量只能是预先设定好的值。使用枚举可以减少代码中的 bug 。

例如,我们为果汁店设计一个程序,它将限制果汁为小杯、中杯、大杯。这就意味着它不允许顾客点除了这三种尺寸外的果汁。


实例

class FreshJuice {   enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }   FreshJuiceSize size;}public class FreshJuiceTest {   public static void main(String args[]){      FreshJuice juice = new FreshJuice();      juice.size = FreshJuice. FreshJuiceSize.MEDIUM ;   }}

注意:枚举可以单独声明或者声明在类里面。方法、变量、构造函数也可以在枚举中定义。


Java关键字

下面列出了Java保留字。这些保留字不能用于常量、变量、和任何标识符的名称。

关键字 描述
abstract 抽象方法,抽象类的修饰符
assert 断言条件是否满足
boolean 布尔数据类型
break 跳出循环或者label代码段
byte 8-bit 有符号数据类型
case switch语句的一个条件
catch 和try搭配捕捉异常信息
char 16-bit Unicode字符数据类型
class 定义类
const 未使用
continue 不执行循环体剩余部分
default switch语句中的默认分支
do 循环语句,循环体至少会执行一次
double 64-bit双精度浮点数
else if条件不成立时执行的分支
enum 枚举类型
extends 表示一个类是另一个类的子类
final 表示一个值在初始化之后就不能再改变了
表示方法不能被重写,或者一个类不能有子类
finally 为了完成执行的代码而设计的,主要是为了程序的健壮性和完整性,无论有没有异常发生都执行代码。
float 32-bit单精度浮点数
for for循环语句
goto 未使用
if 条件语句
implements 表示一个类实现了接口
import 导入类
instanceof 测试一个对象是否是某个类的实例
int 32位整型数
interface 接口,一种抽象的类型,仅有方法和常量的定义
long 64位整型数
native 表示方法用非java代码实现
new 分配新的类实例
package 一系列相关类组成一个包
private 表示私有字段,或者方法等,只能从类内部访问
protected 表示字段只能通过类或者其子类访问
子类或者在同一个包内的其他类
public 表示共有属性或者方法
return 方法返回值
short 16位数字
static 表示在类级别定义,所有实例共享的
strictfp 浮点数比较使用严格的规则
super 表示基类
switch 选择语句
synchronized 表示同一时间只能由一个线程访问的代码块
this 表示调用当前实例
或者调用另一个构造函数
throw 抛出异常
throws 定义方法可能抛出的异常
transient 修饰不要序列化的字段
try 表示代码块要做异常处理或者和finally配合表示是否抛出异常都执行finally中的代码
void 标记方法不返回任何值
volatile 标记字段可能会被多个线程同时访问,而不做同步
while while循环

Java注释

类似于C/C++,Java也支持单行以及多行注释。注释中的字符将被Java编译器忽略。

public class MyFirstJavaProgram{   /* 这是第一个Java程序    *它将打印Hello World    * 这是一个多行注释的示例    */    public static void main(String []args){       // 这是单行注释的示例       /* 这个也是单行注释的示例 */       System.out.println("Hello World");     }} 

Java 空行

空白行,或者只有注释的行,Java编译器都会忽略掉。


继承

在Java中,一个类可以由其他类派生。如果你要创建一个类,而且已经存在一个类具有你所需要的属性或方法,那么你可以将新创建的类继承该类。

利用继承的方法,可以重用已存在类的方法和属性,而不用重写这些代码。被继承的类称为超类(super class),派生类称为子类(subclass)。

接口

在Java中,接口可理解为对象间相互通信的协议。接口在继承中扮演着很重要的角色。

接口只定义派生要用到的方法,但是方法的具体实现完全取决于派生类。

下一节介绍Java编程中的类和对象。之后你将会对Java中的类和对象有更清楚的认识。


在理解Java的类和对象之前,先简单介绍一下面向对象的程序设计。程序设计是通过对象对程序进行设计,对象代表一个实体,实体可以清楚地被识别。

Java作为一种面向对象语言。支持以下基本概念:

  • 多态
  • 继承
  • 封装
  • 抽象
  • 对象
  • 实例
  • 方法
  • 消息解析

本节我们重点研究对象和类的概念。

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • :类是一个模板,它描述一类对象的行为和状态。

Java中的对象

现在让我们深入了解什么是对象。看看周围真实的世界,会发现身边有很多对象,车,狗,人等等。所有这些对象都有自己的状态和行为。

拿一条狗来举例,它的状态有:名字、品种、颜色,行为有:叫、摇尾巴和跑。

对比现实对象和软件对象,它们之间十分相似。

软件对象也有状态和行为。软件对象的状态就是属性,行为通过方法体现。

在软件开发中,方法操作对象内部状态的改变,对象的相互调用也是通过方法来完成。

Java中的类

类可以看成是创建 Java 对象的模板。

通过下面一个简单的类来理解下 Java 中类的定义:

public class Dog{   String breed;   int age;   String color;   void barking(){   }      void hungry(){   }      void sleeping(){   }}

一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。

一个类可以拥有多个方法,在上面的例子中:​barking()​、​hungry()​和 ​sleeping()​都是 Dog 类的方法。


构造方法

每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。

下面是一个构造方法示例:

public class Puppy{   public Puppy(){   }   public Puppy(String name){      // 这个构造器仅有一个参数:name   }}

创建对象

对象是根据类创建的。在 Java 中,使用关键字 ​new​ 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字 ​new​ 来创建一个对象。
  • 初始化:使用 ​new​ 创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子:

public class Puppy{   public Puppy(String name){      //这个构造器仅有一个参数:name      System.out.println("Puppy Name is :" + name );    }   public static void main(String []args){      // 下面的语句将创建一个Puppy对象      Puppy myPuppy = new Puppy( "tommy" );   }}

编译并运行上面的程序,会打印出下面的结果:

Puppy Name is :tommy

访问实例变量和方法

通过已创建的对象来访问成员变量和成员方法,如下所示:

/* 实例化对象 */ObjectReference = new Constructor();/* 访问其中的变量 */ObjectReference.variableName;/* 访问类中的方法 */ObjectReference.MethodName();

实例

下面的例子展示如何访问实例变量和调用成员方法:

public class Puppy{   int puppyAge;   public Puppy(String name){      // 这个构造器仅有一个参数:name      System.out.println("Passed Name is :" + name );    }   public void setAge( int age ){       puppyAge = age;   }   public int getAge( ){       System.out.println("Puppy's age is :" + puppyAge );        return puppyAge;   }   public static void main(String []args){      /* 创建对象 */      Puppy myPuppy = new Puppy( "tommy" );      /* 通过方法来设定age */      myPuppy.setAge( 2 );      /* 调用另一个方法获取age */      myPuppy.getAge( );      /*你也可以像下面这样访问成员变量 */      System.out.println("Variable Value :" + myPuppy.puppyAge );    }}

编译并运行上面的程序,产生如下结果:

Passed Name is :tommyPuppy's age is :2Variable Value :2

源文件声明规则

在本节的最后部分,我们将学习源文件的声明规则。当在一个源文件中定义多个类,并且还有​import​语句和​package​ 语句时,要特别注意这些规则。

  • 一个源文件中只能有一个 ​public​ 类
  • 一个源文件可以有多个非​public​类
  • 源文件的名称应该和​public​类的类名保持一致。例如:源文件中​public​类的类名是​Employee​,那么源文件应该命名为​Employee.java​。
  • 如果一个类定义在某个包中,那么​package​语句应该在源文件的首行。
  • 如果源文件包含​import​语句,那么应该放在​package​语句和类定义之间。如果没有​package​语句,那么​import​语句应该在源文件中最前面。
  • import​语句和​package​语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

类有若干种访问级别,并且类也分不同的类型:抽象类和​final​类等。这些将在访问控制章节介绍。

除了上面提到的几种类型,Java 还有一些特殊的类,如:内部类、匿名类。


Java包

包主要用来对类和接口进行分类。当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。

Import语句

在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。​Import​ 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。

例如,下面的命令行将会命令编译器载入 java_installation/java/io 路径下的所有类

import java.io.*;

一个简单的例子

在该例子中,我们创建两个类:Employee 和 EmployeeTest。

首先打开文本编辑器,把下面的代码粘贴进去。注意将文件保存为 Employee.java。

Employee 类有四个成员变量:name、age、designation 和 salary。该类显式声明了一个构造方法,该方法只有一个参数。

import java.io.*;public class Employee{   String name;   int age;   String designation;   double salary;   // Employee 类的构造器   public Employee(String name){      this.name = name;   }   // 设置age的值   public void empAge(int empAge){      age =  empAge;   }   /* 设置designation的值*/   public void empDesignation(String empDesig){      designation = empDesig;   }   /* 设置salary的值*/   public void empSalary(double empSalary){      salary = empSalary;   }   /* 打印信息 */   public void printEmployee(){      System.out.println("Name:"+ name );      System.out.println("Age:" + age );      System.out.println("Designation:" + designation );      System.out.println("Salary:" + salary);   }}

程序都是从 ​main​方法开始执行。为了能运行这个程序,必须包含 ​main​ 方法并且创建一个实例对象。

下面给出 EmployeeTest 类,该类实例化2个 Employee 类的实例,并调用方法设置变量的值。

将下面的代码保存在 EmployeeTest.java 文件中。

import java.io.*;public class EmployeeTest{   public static void main(String args[]){      /* 使用构造器创建两个对象 */      Employee empOne = new Employee("James Smith");      Employee empTwo = new Employee("Mary Anne");      // 调用这两个对象的成员方法      empOne.empAge(26);      empOne.empDesignation("Senior Software Engineer");      empOne.empSalary(1000);      empOne.printEmployee();      empTwo.empAge(21);      empTwo.empDesignation("Software Engineer");      empTwo.empSalary(500);      empTwo.printEmployee();   }}

编译这两个文件并且运行 EmployeeTest 类,可以看到如下结果:

C :> javac Employee.javaC :> vi EmployeeTest.javaC :> javac  EmployeeTest.javaC :> java EmployeeTestName:James SmithAge:26Designation:Senior Software EngineerSalary:1000.0Name:Mary AnneAge:21Designation:Software EngineerSalary:500.0


变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。

内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。

因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。

Java的两大数据类型:

  • 内置数据类型
  • 引用数据类型

内置数据类型

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

byte型:

  • byte数据类型是8位、有符号的,以二进制补码表示的整数;
  • 最小值是-128(-2^7);
  • 最大值是127(2^7-1);
  • 默认值是0;
  • byte类型用在大型数组中节约空间,主要代替整数,因为byte变量占用的空间只有int类型的四分之一;
  • 例子:byte a = 100,byte b = -50。

short型(短整型):

  • short数据类型是16位、有符号的以二进制补码表示的整数
  • 最小值是-32768(-2^15);
  • 最大值是32767(2^15 - 1);
  • Short数据类型也可以像byte那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是0;
  • 例子:short s = 1000,short r = -20000。

int型(整型):

  • int数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是-2,147,483,648(-2^31);
  • 最大值是2,147,483,647(2^31 - 1);
  • 一般地整型变量默认为int类型;
  • 默认值是0;
  • 例子:int a = 100000, int b = -200000。

long(长整型):

  • long数据类型是64位、有符号的以二进制补码表示的整数;
  • 最小值是-9,223,372,036,854,775,808(-2^63);
  • 最大值是9,223,372,036,854,775,807(2^63 -1);
  • 这种类型主要使用在需要比较大整数的系统上;
  • 默认值是0L;
  • 例子: long a = 100000L,long b = -200000L。

float(单精度浮点型):

  • float数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float在储存大型浮点数组的时候可节省内存空间;
  • 默认值是0.0f;
  • 浮点数不能用来表示精确的值,如货币;
  • 例子:float f1 = 234.5f。

double(双精度浮点型):

  • double数据类型是双精度、64位、符合IEEE 754标准的浮点数;
  • 浮点数的默认类型为double类型;
  • double类型同样不能表示精确的值,如货币;
  • 默认值是0.0d;
  • 例子:double d1 = 123.4。

boolean(布尔型):

  • boolean数据类型表示一位的信息;
  • 只有两个取值:true和false;
  • 这种类型只作为一种标志来记录true/false情况;
  • 默认值是false;
  • 例子:boolean one = true。

char(字符型):

  • char类型是一个单一的16位Unicode字符;
  • 最小值是’u0000’(即为0);
  • 最大值是’uffff’(即为65,535);
  • char数据类型可以储存任何字符;
  • 例子:char letter = ‘A’。

实例

对于数值类型的基本类型的取值范围,我们无需强制去记忆,因为它们的值都已经以常量的形式定义在对应的包装类中了。请看下面的例子:

public class PrimitiveTypeTest {    public static void main(String[] args) {    // byte    System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE);    System.out.println("包装类:java.lang.Byte");    System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE);    System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE);    System.out.println();        // short    System.out.println("基本类型:short 二进制位数:" + Short.SIZE);    System.out.println("包装类:java.lang.Short");    System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE);    System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE);    System.out.println();    // int    System.out.println("基本类型:int 二进制位数:" + Integer.SIZE);    System.out.println("包装类:java.lang.Integer");    System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE);    System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE);    System.out.println();    // long    System.out.println("基本类型:long 二进制位数:" + Long.SIZE);    System.out.println("包装类:java.lang.Long");    System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE);    System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE);    System.out.println();    // float    System.out.println("基本类型:float 二进制位数:" + Float.SIZE);    System.out.println("包装类:java.lang.Float");    System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE);    System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE);    System.out.println();    // double    System.out.println("基本类型:double 二进制位数:" + Double.SIZE);    System.out.println("包装类:java.lang.Double");    System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE);    System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE);    System.out.println();    // char    System.out.println("基本类型:char 二进制位数:" + Character.SIZE);    System.out.println("包装类:java.lang.Character");    // 以数值形式而不是字符形式将Character.MIN_VALUE输出到控制台    System.out.println("最小值:Character.MIN_VALUE="            + (int) Character.MIN_VALUE);    // 以数值形式而不是字符形式将Character.MAX_VALUE输出到控制台    System.out.println("最大值:Character.MAX_VALUE="            + (int) Character.MAX_VALUE);}}  

编译以上代码输出结果如下所示:

基本类型:byte 二进制位数:8包装类:java.lang.Byte最小值:Byte.MIN_VALUE=-128最大值:Byte.MAX_VALUE=127基本类型:short 二进制位数:16包装类:java.lang.Short最小值:Short.MIN_VALUE=-32768最大值:Short.MAX_VALUE=32767基本类型:int 二进制位数:32包装类:java.lang.Integer最小值:Integer.MIN_VALUE=-2147483648最大值:Integer.MAX_VALUE=2147483647基本类型:long 二进制位数:64包装类:java.lang.Long最小值:Long.MIN_VALUE=-9223372036854775808最大值:Long.MAX_VALUE=9223372036854775807基本类型:float 二进制位数:32包装类:java.lang.Float最小值:Float.MIN_VALUE=1.4E-45最大值:Float.MAX_VALUE=3.4028235E38基本类型:double 二进制位数:64包装类:java.lang.Double最小值:Double.MIN_VALUE=4.9E-324最大值:Double.MAX_VALUE=1.7976931348623157E308基本类型:char 二进制位数:16包装类:java.lang.Character最小值:Character.MIN_VALUE=0最大值:Character.MAX_VALUE=65535

Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的“数字”次幂。比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。

实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们进行操作。


引用类型

  • 引用类型变量由类的构造函数创建,可以使用它们访问所引用的对象。这些变量在声明时被指定为一个特定的类型,比如Employee、Pubby等。变量一旦声明后,类型就不能被改变了。
  • 对象、数组都是引用数据类型。
  • 所有引用类型的默认值都是null。
  • 一个引用变量可以用来引用与任何与之兼容的类型。
  • 例子:Animal animal = new Animal(“giraffe”)。

Java常量

常量就是一个固定值。它们不需要计算,直接代表相应的值。

常量指不能改变的量。 在Java中用final标志,声明方式和变量类似:

final double PI = 3.1415927;

虽然常量名也可以用小写,但为了便于识别,通常使用大写字母表示常量。

字面量可以赋给任何内置类型的变量。例如:

byte a = 68;char a = 'A'

byte、int、long、和short都可以用十进制、16进制以及8进制的方式来表示。

当使用常量的时候,前缀0表明是8进制,而前缀0x代表16进制。例如:

int decimal = 100;int octal = 0144;int hexa =  0x64;

和其他语言一样,Java的字符串常量也是包含在两个引号之间的字符序列。下面是字符串型字面量的例子:

"Hello World""two
lines"""This is in quotes""

字符串常量和字符常量都可以包含任何Unicode字符。例如:

char a = 'u0001';String a = "u0001";

Java语言支持一些特殊的转义字符序列。

符号字符含义
换行 (0x0a)
回车 (0x0d)
f换页符(0x0c)
退格 (0x08)
空字符(0x0)
s字符串
制表符
"双引号
'单引号
反斜杠
ddd八进制字符 (ddd)
uxxxx16进制Unicode字符 (xxxx)

这一节讲解了Java的基本数据类型。下一节将探讨不同的变量类型以及它们的用法。


在 Java 语言中,所有的变量在使用前必须声明。声明变量的基本格式如下:

type identifier [ = value][, identifier [= value] ...] ;

格式说明:type 为 Java 数据类型。identifier 是变量名。可以使用逗号隔开来声明多个同类型变量。

以下列出了一些变量的声明实例。注意有些包含了初始化过程。

int a, b, c;         // 声明三个int型整数:a、b、c。int d = 3, e, f = 5; // 声明三个整数并赋予初值。byte z = 22;         // 声明并初始化z。double pi = 3.14159; // 声明了pi。char x = 'x';        // 变量x的值是字符'x'。

Java 语言支持的变量类型有:

  • 局部变量:类的方法中的变量。
  • 实例变量:独立于方法之外的变量,不过没有 static 修饰。
  • 类变量:独立于方法之外的变量,用 static 修饰。

public class Variable{    static int allClicks=0;    // 类变量    String str="hello world";  // 实例变量     public void method(){        int i =0;  // 局部变量    }}

Java局部变量

  • 局部变量声明在方法、构造方法或者语句块中;
  • 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
  • 访问修饰符不能用于局部变量;
  • 局部变量只在声明它的方法、构造方法或者语句块中可见;
  • 局部变量是在栈上分配的。
  • 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

实例1

在以下实例中 age 是一个局部变量。定义在 pupAge() 方法中,它的作用域就限制在这个方法中。

public class Test{    public void pupAge(){      int age = 0;      age = age + 7;      System.out.println("Puppy age is : " + age);   }      public static void main(String args[]){      Test test = new Test();      test.pupAge();   }}

以上实例编译运行结果如下:

Puppy age is: 7

实例2

在下面的例子中 age 变量没有初始化,所以在编译时出错。

public class Test{    public void pupAge(){      int age;      age = age + 7;      System.out.println("Puppy age is : " + age);   }      public static void main(String args[]){      Test test = new Test();      test.pupAge();   }}

以上实例编译运行结果如下:

Test.java:4:variable number might not have been initializedage = age + 7;         ^1 error

实例变量

  • 实例变量声明在一个类中,但在方法、构造方法和语句块之外;
  • 当一个对象被实例化之后,每个实例变量的值就跟着确定;
  • 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
  • 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
  • 实例变量可以声明在使用前或者使用后;
  • 访问修饰符可以修饰实例变量;
  • 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
  • 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定;
  • 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName。

实例:

import java.io.*;public class Employee{   // 这个成员变量对子类可见   public String name;   // 私有变量,仅在该类可见   private double salary;   //在构造器中对name赋值   public Employee (String empName){      name = empName;   }   //设定salary的值   public void setSalary(double empSal){      salary = empSal;   }     // 打印信息   public void printEmp(){      System.out.println("name  : " + name );      System.out.println("salary :" + salary);   }   public static void main(String args[]){      Employee empOne = new Employee("Ransika");      empOne.setSalary(1000);      empOne.printEmp();   }}

以上实例编译运行结果如下:

name  : Ransikasalary :1000.0

类变量(静态变量)

  • 类变量也称为静态变量,在类中以 static 关键字声明,但必须在方法、构造方法和语句块之外。
  • 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。
  • 静态变量除了被声明为常量外很少使用。常量是指声明为 public/private,final 和 static 类型的变量。常量初始化后不可改变。
  • 静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。
  • 静态变量在程序开始时创建,在程序结束时销毁。
  • 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
  • 默认值和实例变量相似。数值型变量默认值是0,布尔型默认值是 false,引用类型默认值是 null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。
  • 静态变量可以通过:ClassName.VariableName 的方式访问。
  • 类变量被声明为 public static final 类型时,类变量名称必须使用大写字母。如果静态变量不是 public 和 final 类型,其命名方式与实例变量以及局部变量的命名方式一致。

实例:

import java.io.*;public class Employee{   //salary是静态的私有变量   private static double salary;   // DEPARTMENT是一个常量   public static final String DEPARTMENT = "Development ";   public static void main(String args[]){      salary = 1000;      System.out.println(DEPARTMENT+"average salary:"+salary);   }}

以上实例编译运行结果如下:

Development average salary:1000

注意:如果其他类想要访问该变量,可以这样访问:Employee.DEPARTMENT。

本章节中我们学习了 Java 的变量类型,下一章节中我们将介绍Java修饰符的使用。


Java 语言提供了很多修饰符,主要分为以下两类:

  • 访问修饰符
  • 非访问修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:

public class className {   // ...}private boolean myFlag;static final double weeks = 9.5;protected static final int BOXWIDTH = 42;public static void main(String[] arguments) {   // 方法体}

访问控制修饰符

Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持4种不同的访问权限。

默认的,也称为 default,在同一包内可见,不使用任何修饰符。

私有的,以 private 修饰符指定,在同一类内可见。

公有的,以 public 修饰符指定,对所有类可见。

受保护的,以 protected 修饰符指定,对同一包内的类和所有子类可见。

默认访问修饰符-不使用任何关键字

使用默认访问修饰符声明的变量和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为​public static final​,而接口里的方法默认情况下访问权限为 ​public​。

实例:

如下例所示,变量和方法的声明可以不使用任何修饰符。

String version = "1.5.1";boolean processOrder() {   return true;}

私有访问修饰符-private

私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。

声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。

Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。

下面的类使用了私有访问修饰符:

public class Logger {   private String format;   public String getFormat() {      return this.format;   }   public void setFormat(String format) {      this.format = format;   }}

实例中,Logger 类中的 format 变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个​public​方法:​getFormat() ​(返回format的值)和​setFormat(String)​(设置format的值)

公有访问修饰符-public

被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。

如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。

以下函数使用了公有访问控制:

public static void main(String[] arguments) {   // ...}

Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。

受保护的访问修饰符-protected

被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。

Protected 访问修饰符不能修饰类和接口,方法和成员变量能够声明为 protected,但是接口的成员变量和成员方法不能声明为 protected。

子类能访问 Protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。

下面的父类使用了 protected 访问修饰符,子类重载了父类的 openSpeaker() 方法。

class AudioPlayer {   protected boolean openSpeaker(Speaker sp) {      // 实现细节   }}class StreamingAudioPlayer {   boolean openSpeaker(Speaker sp) {      // 实现细节   }}

如果把 openSpeaker() 方法声明为private,那么除了 AudioPlayer 之外的类将不能访问该方法。

如果把 openSpeaker() 声明为 public,那么所有的类都能够访问该方法。

如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。

访问控制和继承

请注意以下方法继承的规则:

  • 父类中声明为 public 的方法在子类中也必须为 public。

  • 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public。不能声明为 private。

  • 父类中声明为 private 的方法,不能够被继承。


非访问修饰符

为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

static 修饰符,用来创建类方法和类变量。

final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。

abstract 修饰符,用来创建抽象类和抽象方法。

synchronized 和 volatile 修饰符,主要用于线程的编程。

static修饰符

  • 静态变量:

    static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。静态变量也被称为类变量。局部变量不能被声明为static变量。

  • 静态方法:

    static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用 ​classname.variablename​ 和 ​classname.methodname​ 的方式访问。

如下例所示,static 修饰符用来创建类方法和类变量。

public class InstanceCounter {   private static int numInstances = 0;   protected static int getCount() {      return numInstances;   }   private static void addInstance() {      numInstances++;   }   InstanceCounter() {      InstanceCounter.addInstance();   }   public static void main(String[] arguments) {      System.out.println("Starting with " +      InstanceCounter.getCount() + " instances");      for (int i = 0; i < 500; ++i){          new InstanceCounter();      }      System.out.println("Created " +       InstanceCounter.getCount() + " instances");   }} 

以上实例运行编辑结果如下:

Started with 0 instancesCreated 500 instances

final 修饰符

final 变量:

final 变量能被显式地初始化并且只能初始化一次。被声明为final的对象的引用不能指向不同的对象。但是 final 对象里的数据可以被改变。也就是说 final 对象的引用不能改变,但是里面的值可以改变。

final 修饰符通常和 static 修饰符一起使用来创建类常量。

实例:

public class Test{  final int value = 10;  // 下面是声明常量的实例  public static final int BOXWIDTH = 6;  static final String TITLE = "Manager";  public void changeValue(){     value = 12; //将输出一个错误  }}

final 方法

类中的 Final 方法可以被子类继承,但是不能被子类修改。

声明 final 方法的主要目的是防止该方法的内容被修改。

如下所示,使用 final 修饰符声明方法。

public class Test{    public final void changeName(){       // 方法体    }}

final 类

final 类不能被继承,没有类能够继承 final 类的任何特性。

实例:

public final class Test {   // 类体}

abstract 修饰符

抽象类:

抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。

一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。

抽象类可以包含抽象方法和非抽象方法。

实例:

abstract class Caravan{   private double price;   private String model;   private String year;   public abstract void goFast(); //抽象方法   public abstract void changeColor();}

抽象方法

抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。抽象方法不能被声明成 final 和 static。

任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。

抽象方法的声明以分号结尾,例如:public abstract sample();

实例:

public abstract class SuperClass{    abstract void m(); //抽象方法} class SubClass extends SuperClass{     //实现抽象方法      void m(){          .........      }}

synchronized 修饰符

synchronized 关键字声明的方法同一时间只能被一个线程访问。Synchronized 修饰符可以应用于四个访问修饰符。

实例:

public synchronized void showDetails(){.......} 

transient 修饰符

序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机 (JVM) 跳过该特定的变量。

该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

实例:

public transient int limit = 55;   // will not persistpublic int b; // will persist

volatile修饰符

volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

一个 volatile 对象引用可能是 null。

实例:

public class MyRunnable implements Runnable{    private volatile boolean active;    public void run()    {        active = true;        while (active) // line 1        {            // 代码        }    }    public void stop()    {        active = false; // line 2    }}

一般地,在一个线程中调用run()方法,在另一个线程中调用stop()方法。如果line 1中的active位于缓冲区的值被使用,那么当把line 2中的active设置成false时,循环也不会停止。


计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量。我们可以把运算符分成以下几组:

  • 算术运算符
  • 关系运算符
  • 位运算符
  • 逻辑运算符
  • 赋值运算符
  • 其他运算符

算术运算符

算术运算符用在数学表达式中,它们的作用和在数学中的作用一样。下表列出了所有的算术运算符。

表格中的实例假设整数变量A的值为10,变量B的值为20:

操作符描述例子
+加法 - 相加运算符两侧的值A + B等于30
-减法 - 左操作数减去右操作数A – B等于-10
*乘法 - 相乘操作符两侧的值A * B等于200
/除法 - 左操作数除以右操作数B / A等于2
取模 - 左操作数除以右操作数的余数B%A等于0
++自增 - 操作数的值增加1B++ 或 ++B 等于 21
--自减 - 操作数的值减少1B-- 或 --B 等于 19

 虽然都是自增(他们的运算结果都是等于B+1),但B++和++B还是有所区别,++B是左值,直接原地操作(可以理解为直接在变量B上+1),B++是右值,在编译器中运算的时候会先构建一个临时变量,用临时变量运算+1后再赋值给B。

所以,在下面实例代码中,打印d++的时候发现结果并没有+1(这个时候打印的是B这个变量,运算的是B的临时变量),但是后一条打印的语句显示的结果又是+1后的结果(临时变量的值赋给变量B了)。而打印++d的结果是直接+1的。

实例

下面的简单示例程序演示了算术运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 10;     int b = 20;     int c = 25;     int d = 25;     System.out.println("a + b = " + (a + b) );     System.out.println("a - b = " + (a - b) );     System.out.println("a * b = " + (a * b) );     System.out.println("b / a = " + (b / a) );     System.out.println("b % a = " + (b % a) );     System.out.println("c % a = " + (c % a) );     System.out.println("a++   = " +  (a++) );     System.out.println("a--   = " +  (a--) );     // 查看  d++ 与 ++d 的不同     System.out.println("d++   = " +  (d++) );     System.out.println("d     = " +  d);     System.out.println("++d   = " +  (++d) );  }} 

以上实例编译运行结果如下:

a + b = 30a - b = -10a * b = 200b / a = 2b % a = 0c % a = 5a++   = 10a--   = 11d++   = 25d     =26++d   = 27

关系运算符

下表为Java支持的关系运算符

表格中的实例整数变量A的值为10,变量B的值为20:

运算符描述例子
==检查如果两个操作数的值是否相等,如果相等则条件为真。(A == B)为假(非真)。
!=检查如果两个操作数的值是否相等,如果值不相等则条件为真。(A != B) 为真。
检查左操作数的值是否大于右操作数的值,如果是那么条件为真。(A> B)非真。
检查左操作数的值是否小于右操作数的值,如果是那么条件为真。(A <B)为真。
>=检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。(A> = B)为假。
<=检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。(A <= B)为真。

实例

下面的简单示例程序演示了关系运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 10;     int b = 20;     System.out.println("a == b = " + (a == b) );     System.out.println("a != b = " + (a != b) );     System.out.println("a > b = " + (a > b) );     System.out.println("a < b = " + (a < b) );     System.out.println("b >= a = " + (b >= a) );     System.out.println("b <= a = " + (b <= a) );   } }  

以上实例编译运行结果如下:

a == b = falsea != b = truea > b = falsea < b = true b >= a = trueb <= a = false 

位运算符

Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。

位运算符作用在所有的位上,并且按位运算。假设a = 60,和b = 13;它们的二进制格式表示将如下:

A = 0011 1100B = 0000 1101-----------------A&B = 0000 1100A | B = 0011 1101A ^ B = 0011 0001~A= 1100 0011

下表列出了位运算符的基本运算,假设整数变量A的值为60和变量B的值为13:

操作符描述例子
按位与操作符,当且仅当两个操作数的某一位都非0时候结果的该位才为1。(A&B),得到12,即0000 1100
|按位或操作符,只要两个操作数的某一位有一个非0时候结果的该位就为1。(A | B)得到61,即 0011 1101
^按位异或操作符,两个操作数的某一位不相同时候结果的该位就为1。(A ^ B)得到49,即 0011 0001
按位补运算符翻转操作数的每一位。(〜A)得到-61,即1100 0011
<< 按位左移运算符。左操作数按位左移右操作数指定的位数。A << 2得到240,即 1111 0000
>> 按位右移运算符。左操作数按位右移右操作数指定的位数。A >> 2得到15即 1111
>>> 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。A>>>2得到15即0000 1111

实例

下面的简单示例程序演示了位运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 60; /* 60 = 0011 1100 */      int b = 13; /* 13 = 0000 1101 */     int c = 0;     c = a & b;       /* 12 = 0000 1100 */     System.out.println("a & b = " + c );     c = a | b;       /* 61 = 0011 1101 */     System.out.println("a | b = " + c );     c = a ^ b;       /* 49 = 0011 0001 */     System.out.println("a ^ b = " + c );     c = ~a;          /*-61 = 1100 0011 */     System.out.println("~a = " + c );     c = a << 2;     /* 240 = 1111 0000 */     System.out.println("a << 2 = " + c );     c = a >> 2;     /* 215 = 1111 */     System.out.println("a >> 2  = " + c );       c = a >>> 2;     /* 215 = 0000 1111 */     System.out.println("a >>> 2 = " + c );  }} 

以上实例编译运行结果如下:

a & b = 12a | b = 61a ^ b = 49~a = -61a << 2 = 240 a >> 2 = 15a >>> 2 = 15

逻辑运算符

下表列出了逻辑运算符的基本运算,假设布尔变量A为真,变量B为假

操作符描述例子
&&称为逻辑与运算符。当且仅当两个操作数都为真,条件才为真。(A && B)为假。
| |称为逻辑或操作符。如果任何两个操作数任何一个为真,条件为真。(A | | B)为真。
称为逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。!(A && B)为真。

实例

下面的简单示例程序演示了逻辑运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     boolean a = true;     boolean b = false;     System.out.println("a && b = " + (a&&b));     System.out.println("a || b = " + (a||b) );     System.out.println("!(a && b) = " + !(a && b));  }} 

以上实例编译运行结果如下:

a && b = falsea || b = true!(a && b) = true

赋值运算符

下面是Java语言支持的赋值运算符:

操作符描述例子
=简单的赋值运算符,将右操作数的值赋给左侧操作数C = A + B将把A + B得到的值赋给C
+ =加和赋值操作符,它把左操作数和右操作数相加赋值给左操作数C + = A等价于C = C + A
- =减和赋值操作符,它把左操作数和右操作数相减赋值给左操作数C - = A等价于C = C -
 A
* =乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数C * = A等价于C = C * A
/ =除和赋值操作符,它把左操作数和右操作数相除赋值给左操作数C / = A等价于C = C / A
(%)=取模和赋值操作符,它把左操作数和右操作数取模后赋值给左操作数C%= A等价于C = C%A
<< =左移位赋值运算符C << = 2等价于C = C << 2
>> =右移位赋值运算符C >> = 2等价于C = C >> 2
&=按位与赋值运算符C&= 2等价于C = C&2
^ =按位异或赋值操作符C ^ = 2等价于C = C ^ 2
| =按位或赋值操作符C | = 2等价于C = C | 2

实例

下面的简单示例程序演示了赋值运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 10;     int b = 20;     int c = 0;     c = a + b;     System.out.println("c = a + b = " + c );     c += a ;     System.out.println("c += a  = " + c );     c -= a ;     System.out.println("c -= a = " + c );     c *= a ;     System.out.println("c *= a = " + c );     a = 10;     c = 15;     c /= a ;     System.out.println("c /= a = " + c );     a = 10;     c = 15;     c %= a ;     System.out.println("c %= a  = " + c );     c <<= 2 ;      System.out.println("c <<= 2 = " + c );      c >>= 2 ;     System.out.println("c >>= 2 = " + c );     c >>= 2 ;     System.out.println("c >>= a = " + c );     c &= a ;     System.out.println("c &= a = " + c );     c ^= a ;     System.out.println("c ^= a = " + c );     c |= a ;     System.out.println("c |= a = " + c );  }} 

以上实例编译运行结果如下:

c = a + b = 30c += a  = 40c -= a = 30c *= a = 300c /= a = 1c %= a  = 5c <<= 2 = 20 c >>= 2 = 5c >>= 2 = 1c &= a  = 0c ^= a   = 10c |= a   = 10

条件运算符(?:)

条件运算符也被称为三元运算符。该运算符有3个操作数,并且需要判断布尔表达式的值。该运算符的主要是决定哪个值应该赋值给变量。

variable x = (expression) ? value if true : value if false

实例

public class Test {   public static void main(String args[]){      int a , b;         a = 10;          b = (a == 1) ? 20: 30;          System.out.println( "Value of b is : " +  b );      b = (a == 10) ? 20: 30;          System.out.println( "Value of b is : " + b );   }}

以上实例编译运行结果如下:

Value of b is : 30Value of b is : 20

instanceof 运算符

该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)。

instanceof运算符使用格式如下:

( Object reference variable ) instanceof  (class/interface type)

如果运算符左侧变量所指的对象,是操作符右侧类或接口(class/interface)的一个对象,那么结果为真。

下面是一个例子:

String name = 'James';boolean result = name instanceof String; // 由于name是String类型,所以返回真

如果被比较的对象兼容于右侧类型,该运算符仍然返回true。

看下面的例子:

class Vehicle {}public class Car extends Vehicle {   public static void main(String args[]){      Vehicle a = new Car();      boolean result =  a instanceof Car;      System.out.println( result);   }}

以上实例编译运行结果如下:

true

Java运算符优先级

当多个运算符出现在一个表达式中,谁先谁后呢?这就涉及到运算符的优先级别的问题。在一个多运算符的表达式中,运算符优先级不同会导致最后得出的结果差别甚大。

例如,(1+3)+(3+2)*2,这个表达式如果按加号最优先计算,答案就是 18,如果按照乘号最优先,答案则是 14。

再如,x = 7 + 3 * 2;这里x得到13,而不是20,因为乘法运算符比加法运算符有较高的优先级,所以先计算3 * 2得到6,然后再加7。

下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。

类别操作符关联性
后缀() [] . (点操作符)左到右
一元+ + - !〜从右到左
乘性 * /%左到右
加性 + -左到右
移位 >> >>>  << 左到右
关系 >> = << = 左到右
相等 ==  !=左到右
按位与左到右
按位异或^左到右
按位或|左到右
逻辑与&&左到右
逻辑或| |左到右
条件?:从右到左
赋值= + = - = * = / =%= >> = << =&= ^ = | =从右到左
逗号左到右

顺序结构的程序语句只能被执行一次。如果您想要同样的操作执行多次,,就需要使用循环结构。

Java中有三种主要的循环结构:

  • while循环
  • do…while循环
  • for循环

在Java5中引入了一种主要用于数组的增强型for循环。


while循环

while是最基本的循环,它的结构为:

while( 布尔表达式 ) {	//循环内容}

只要布尔表达式为true,循环体会一直执行下去。

实例

public class Test {   public static void main(String args[]) {      int x = 10;      while( x < 20 ) {          System.out.print("value of x : " + x );          x++;          System.out.print("
");      }   } } 

以上实例编译运行结果如下:

value of x : 10value of x : 11value of x : 12value of x : 13value of x : 14value of x : 15value of x : 16value of x : 17value of x : 18value of x : 19

do…while循环

对于while语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次。

do…while循环和while循环相似,不同的是,do…while循环至少会执行一次。

do {       //代码语句}while(布尔表达式);

注意:布尔表达式在循环体的后面,所以语句块在检测布尔表达式之前已经执行了。 如果布尔表达式的值为true,则语句块一直执行,直到布尔表达式的值为false。

实例

public class Test {   public static void main(String args[]){      int x = 10;      do{         System.out.print("value of x : " + x );         x++;         System.out.print("
");      }while( x < 20 );    } } 

以上实例编译运行结果如下:

value of x : 10value of x : 11value of x : 12value of x : 13value of x : 14value of x : 15value of x : 16value of x : 17value of x : 18value of x : 19

for循环

虽然所有循环结构都可以用while或者do...while表示,但Java提供了另一种语句 —— for循环,使一些循环结构变得更加简单。

for循环执行的次数是在执行前就确定的。语法格式如下:

for(初始化; 布尔表达式; 更新) {    //代码语句}

关于for循环有以下几点说明:

  • 最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,也可以是空语句。
  • 然后,检测布尔表达式的值。如果为true,循环体被执行。如果为false,循环终止,开始执行循环体后面的语句。
  • 执行一次循环后,更新循环控制变量。
  • 再次检测布尔表达式。循环执行上面的过程。

实例

public class Test {   public static void main(String args[]) {      for(int x = 10; x < 20; x = x+1) {          System.out.print("value of x : " + x );          System.out.print("
");      }   } } 

以上实例编译运行结果如下:

value of x : 10value of x : 11value of x : 12value of x : 13value of x : 14value of x : 15value of x : 16value of x : 17value of x : 18value of x : 19

Java增强for循环

Java5引入了一种主要用于数组的增强型for循环。

Java增强for循环语法格式如下:

for(声明语句 : 表达式){   //代码句子}

声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。

表达式:表达式是要访问的数组名,或者是返回值为数组的方法。

实例

public class Test {   public static void main(String args[]){      int [] numbers = {10, 20, 30, 40, 50};      for(int x : numbers ){         System.out.print( x );         System.out.print(",");      }      System.out.print("
");      String [] names ={"James", "Larry", "Tom", "Lacy"};      for( String name : names ) {         System.out.print( name );         System.out.print(",");      }   }}

以上实例编译运行结果如下:

10,20,30,40,50,James,Larry,Tom,Lacy,

break关键字

break主要用在循环语句或者switch语句中,用来跳出整个语句块。

break跳出最里层的循环,并且继续执行该循环下面的语句。

语法

break的用法很简单,就是循环结构中的一条语句:

break;

实例

public class Test {   public static void main(String args[]) {      int [] numbers = {10, 20, 30, 40, 50};      for(int x : numbers ) {         if( x == 30 ) {	      break;         }         System.out.print( x );         System.out.print("
");      }   }}

以上实例编译运行结果如下:

1020

continue关键字

continue适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。

在for循环中,continue语句使程序立即跳转到更新语句。

在while或者do…while循环中,程序立即跳转到布尔表达式的判断语句。

语法

continue就是循环体中一条简单的语句:

continue;

实例

public class Test {   public static void main(String args[]) {      int [] numbers = {10, 20, 30, 40, 50};      for(int x : numbers ) {         if( x == 30 ) {	      continue;         }         System.out.print( x );         System.out.print("
");      }   }}

以上实例编译运行结果如下:

10204050


顺序结构只能顺序执行,不能进行判断和选择,因此需要分支结构。

Java有两种分支结构:

  • if语句
  • switch语句

if语句

一个if语句包含一个布尔表达式和一条或多条语句。

语法

If 语句的用语法如下:

if(布尔表达式){   //如果布尔表达式为true将执行的语句}

如果布尔表达式的值为 true,则执行if语句中的代码块。否则执行 If 语句块后面的代码。

public class Test {   public static void main(String args[]){      int x = 10;      if( x < 20 ){          System.out.print("这是 if 语句");       }   }} 

以上代码编译运行结果如下:

这是 if 语句

if...else 语句

if 语句后面可以跟 else 语句,当if语句的布尔表达式值为 false 时,else 语句块会被执行。

语法

if…else 的用法如下:

if(布尔表达式){   //如果布尔表达式的值为true}else{   //如果布尔表达式的值为false}

实例

public class Test {   public static void main(String args[]){      int x = 30;      if( x < 20 ){          System.out.print("这是 if 语句");       }else{          System.out.print("这是 else 语句");       }   }} 

以上代码编译运行结果如下:

这是 else 语句

if...else if...else 语句

if 语句后面可以跟 else if…else 语句,这种语句可以检测到多种可能的情况。

使用if,else if,else语句的时候,需要注意下面几点:

  • if 语句至多有 1 个 else 语句,else 语句在所有的 else if 语句之后。
  • If 语句可以有若干个 else if 语句,它们必须在 else 语句之前。
  • 一旦其中一个 else if 语句检测为 true,其他的 else if 以及 else 语句都将跳过执行。

语法

if...else 语法格式如下:

if(布尔表达式 1){   //如果布尔表达式 1的值为true执行代码}else if(布尔表达式 2){   //如果布尔表达式 2的值为true执行代码}else if(布尔表达式 3){   //如果布尔表达式 3的值为true执行代码}else {   //如果以上布尔表达式都不为true执行代码}

实例

public class Test {   public static void main(String args[]){      int x = 30;      if( x == 10 ){         System.out.print("Value of X is 10");      }else if( x == 20 ){         System.out.print("Value of X is 20");      }else if( x == 30 ){         System.out.print("Value of X is 30");      }else{         System.out.print("这是 else 语句");      }   }}

以上代码编译运行结果如下:

Value of X is 30

嵌套的 if…else 语句

使用嵌套的 if-else 语句是合法的。也就是说你可以在另一个 if 或者 else if 语句中使用 if 或者 else if 语句。

语法

嵌套的 if…else 语法格式如下:

if(布尔表达式 1){   ////如果布尔表达式 1的值为true执行代码   if(布尔表达式 2){      ////如果布尔表达式 2的值为true执行代码   }}

你可以像 if 语句一样嵌套 else if...else。

实例

public class Test {   public static void main(String args[]){      int x = 30;      int y = 10;      if( x == 30 ){         if( y == 10 ){             System.out.print("X = 30 and Y = 10");          }       }    }}

以上代码编译运行结果如下:

X = 30 and Y = 10

switch 语句

switch 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。

语法

switch 语法格式如下:

switch(expression){    case value :       //语句       break; //可选    case value :       //语句       break; //可选    //你可以有任意数量的case语句    default : //可选       //语句}

switch 语句有如下规则:

  • switch 语句中的变量类型只能为 byte、short、int 或者 char。从 Java SE 7 开始,switch 支持字符串 String 类型了,同时 case 标签必须为字符串常量或字面量。
  • switch 语句可以拥有多个 case 语句。每个 case 后面跟一个要比较的值和冒号。
  • case 语句中的值的数据类型必须与变量的数据类型相同,而且只能是常量或者字面常量。
  • 当变量的值与 case 语句的值相等时,那么 case 语句之后的语句开始执行,直到break语句出现才会跳出 switch 语句。
  • 当遇到 break 语句时,switch 语句终止。程序跳转到 switch 语句后面的语句执行。case 语句不必须要包含break 语句。如果没有 break 语句出现,程序会继续执行下一条 case 语句,直到出现 break 语句。
  • switch 语句可以包含一个 default 分支,该分支必须是 switch 语句的最后一个分支。default 在没有 case 语句的值和变量值相等的时候执行。default 分支不需要 break 语句。

实例

public class Test {   public static void main(String args[]){      //char grade = args[0].charAt(0);      char grade = 'C';      switch(grade)      {         case 'A' :            System.out.println("优秀");             break;         case 'B' :         case 'C' :            System.out.println("良好");            break;         case 'D' :            System.out.println("及格");         case 'F' :            System.out.println("你需要继续努力");            break;         default :            System.out.println("无效等级");      }      System.out.println("你的等级是 " + grade);   }}

以上代码编译运行结果如下:

良好你的等级是 C


一般情况下我们会使用数据的基本数据类型:byte、int、short、long、double、float、boolean、char;

对应的包装类型也有八种:Byte、Integer、Short、Long、Double、Float、Character、Boolean;

包装类型都是用 final 声明了,不可以被继承重写;在实际情况中编译器会自动的将基本数据类型装箱成对象类型,或者将对象类型拆箱成基本数据类型;如下:

public static void main(String[] args) {	int num1 = 1;	//将基本数据类型装箱成对象包装类型	Integer num2 = num1;	Integer num3 = 3;	//将对象数据类拆箱	int num4 = num3;}

Number 类是 java.lang 包下的一个抽象类,提供了将包装类型拆箱成基本类型的方法,所有基本类型(数据类型)的包装类型都继承了该抽象类,并且是final声明不可继承改变;

package java.lang;public abstract class Number implements java.io.Serializable {    public abstract int intValue();    public abstract long longValue();    public abstract float floatValue();    public abstract double doubleValue();    public byte byteValue() {        return (byte)intValue();    }    public short shortValue() {        return (short)intValue();    }    private static final long serialVersionUID = -8742448824652078965L;}
包装类基本数据类型
Booleanboolean
Bytebyte
Shortshort
Integerint
Longlong
Characterchar
Floatfloat
Doubledouble

这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang 包。

下面是一个装箱与拆箱的例子:

public class Test{   public static void main(String args[]){      Integer x = 5; // boxes int to an Integer object      x =  x + 10;   // unboxes the Integer to a int      System.out.println(x);    }}

以上实例编译运行结果如下:

15

当x被赋为整型值时,由于 x 是一个对象,所以编译器要对x进行装箱。然后,为了使x能进行加运算,所以要对x进行拆箱。


Java Math类

Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。

Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。

实例

public class Test {      public static void main (String []args)      {          System.out.println("90 度的正弦值:" + Math.sin(Math.PI/2));          System.out.println("0度的余弦值:" + Math.cos(0));          System.out.println("60度的正切值:" + Math.tan(Math.PI/3));          System.out.println("1的反正切值: " + Math.atan(1));          System.out.println("π/2的角度值:" + Math.toDegrees(Math.PI/2));          System.out.println(Math.PI);      }  }

以上实例编译运行结果如下:

90 度的正弦值:1.00度的余弦值:1.060度的正切值:1.73205080756887671的反正切值: 0.7853981633974483π/2的角度值:90.03.141592653589793


Number & Math 类方法

下面的表中列出的是常用的 Number 类和 Math 类的方法:

序号方法与描述
1xxxValue()
将number对象转换为xxx数据类型的值并返回。
2compareTo()
将number对象与参数比较。
3equals()
判断number对象是否与参数相等。
4valueOf()
返回一个Integer对象指定的内置数据类型
5toString()
以字符串形式返回值。
6parseInt()
将字符串解析为int类型。
7abs()
返回参数的绝对值。
8ceil()
返回大于等于( >= )给定参数的的最小整数,类型为双精度浮点型。
9floor()
返回小于等于(<=)给定参数的最大整数 。
10rint()
返回与参数最接近的整数。返回类型为double。
11round()
返回一个最接近的int、long型值。
12min()
返回两个参数中的最小值。
13max()
返回两个参数中的最大值。
14exp()
返回自然数底数e的参数次方。
15log()
返回参数的自然数底数的对数值。
16pow()
返回第一个参数的第二个参数次方。
17sqrt()
求参数的算术平方根。
18sin()
求指定double类型参数的正弦值。
19cos()
求指定double类型参数的余弦值。
20tan()
求指定double类型参数的正切值。
21asin()
求指定double类型参数的反正弦值。
22acos()
求指定double类型参数的反余弦值。
23atan()
求指定double类型参数的反正切值。
24atan2()
将笛卡尔坐标转换为极坐标,并返回极坐标的角度值。
25toDegrees()
将参数转化为角度。
26toRadians()
将角度转换为弧度。
27random()
返回一个随机数。


本章节我们主要向大家介绍一下Java Character类,以及Character类的用法。

Java Character类

使用字符时,我们通常使用的是内置数据类型 char。

实例

char ch = 'a';// Unicode for uppercase Greek omega characterchar uniChar = 'u039A'; // 字符数组char[] charArray = { 'a', 'b', 'c', 'd', 'e' }; 

然而,在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型的情况。为了解决这个问题,Java 语言为内置数据类型 char 提供了包装类 Character 类。

Character类的用法:Character 类提供了一系列方法来操纵字符,你可以使用 Character 的构造方法创建一个 Character 类对象,例如:

Character ch = new Character('a');

在某些情况下,Java 编译器会自动创建一个 Character 对象。

例如,将一个 char 类型的参数传递给需要一个 Character 类型参数时,那么编译器会自动地将 char 类型参数转换为 Character 对象。 这种特征称为装箱,反过来称为拆箱。 

实例

// Here following primitive char 'a'// is boxed into the Character object chCharacter ch = 'a';// Here primitive 'x' is boxed for method test,// return is unboxed to char 'c'char c = test('x');

转义序列

前面有反斜杠()的字符代表转义字符,它对编译器来说是有特殊含义的。

下面列表展示了 Java 的转义序列:

转义序列描述
在文中该处插入一个tab键
在文中该处插入一个后退键
在文中该处换行
在文中该处插入回车
f在文中该处插入换页符
'在文中该处插入单引号
"在文中该处插入双引号
在文中该处插入反斜杠

实例

当打印语句遇到一个转义序列时,编译器可以正确地对其进行解释。

public class Test {   public static void main(String args[]) {      System.out.println("She said "Hello!" to me.");   }}

以上实例编译运行结果如下:

She said "Hello!" to me.

Character 方法

下面是 Character 类的方法:

序号方法与描述
1isLetter()
是否是一个字母
2isDigit()
是否是一个数字字符
3isWhitespace()
是否一个空格
4isUpperCase()
是否是大写字母
5isLowerCase()
是否是小写字母
6toUpperCase()
指定字母的大写形式
7toLowerCase()
指定字母的小写形式
8toString()
返回字符的字符串形式,字符串的长度仅为1


初学者会经常使用的几个方法

public static boolean isUpperCase(char ch): 判断给定的字符是否是大写字符;


public static boolean isLowerCase(char ch): 判断给定的字符是否是小写字符;


public static boolean isDigit(char ch): 判断给定的字符是否是数字字符;


这三个句子里的boolean代表,这三个方法使用后的返回值是 boolean 型。


实例

public class Java {	public static void main(String[] args) {		Character ch = new Character('X');				System.out.println(Character.isUpperCase(ch));		//Character.isUpperCase(ch) 用于判断括号里的字母是否为大写		System.out.println(Character.isLowerCase(ch));		//Character.isLowerCase(ch) 用于判断括号里的字母是否为小写		System.out.println(Character.isDigit(ch));		//Character.isDigit(ch) 用于判断括号里的内容是否为数字	}}

运行结果为:

truefalsefalse

对于方法的完整列表,请参考的 java.lang.Character API 规范。


字符串广泛应用在Java编程中,在Java中字符串属于对象,Java提供了String类来创建和操作字符串。


创建字符串

创建字符串最简单的方式如下:

String greeting = "Hello world!";

在代码中遇到字符串常量时,这里的值是 "Hello world!" ,编译器会使用该值创建一个 String 对象。

和其它对象一样,可以使用关键字和构造方法来创建String对象。

String 类有 11 种构造方法,这些方法提供不同的参数来初始化字符串,比如提供一个字符数组参数:

public class StringDemo{   public static void main(String args[]){      char[] helloArray = { 'h', 'e', 'l', 'l', 'o', '.'};      String helloString = new String(helloArray);        System.out.println( helloString );   }}

以上实例编译运行结果如下:

hello.

注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。 如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类


字符串长度

用于获取有关对象的信息的方法称为访问器方法。

String 类的一个访问器方法是 length() 方法,它返回字符串对象包含的字符数。

下面的代码执行后,len 变量等于 17:

public class StringDemo {   public static void main(String args[]) {      String palindrome = "Dot saw I was Tod";      int len = palindrome.length();      System.out.println( "String Length is : " + len );   }}

以上实例编译运行结果如下:

String Length is : 17

连接字符串

String 类提供了连接两个字符串的方法:

string1.concat(string2);

返回 string2 连接 string1 的新字符串。也可以对字符串常量使用 concat() 方法,如:

"My name is ".concat("Zara");

更常用的是使用'+'操作符来连接字符串,如:

"Hello," + " world" + "!"

结果如下:

"Hello, world!"

下面是一个例子:

public class StringDemo {   public static void main(String args[]) {           String string1 = "saw I was ";           System.out.println("Dot " + string1 + "Tod");     }}

以上实例编译运行结果如下:

Dot saw I was Tod

创建格式化字符串

我们知道输出格式化数字可以使用 printf() 和 format() 方法。String 类使用静态方法 format() 返回一个 String 对象而不是 PrintStream 对象。

String 类的静态方法 format() 能用来创建可复用的格式化字符串,而不仅仅是用于一次打印输出。如下所示:

System.out.printf("The value of the float variable is " +                  "%f, while the value of the integer " +                  "variable is %d, and the string " +                  "is %s", floatVar, intVar, stringVar);

你也可以这样写

String fs;fs = String.format("The value of the float variable is " +                   "%f, while the value of the integer " +                   "variable is %d, and the string " +                   "is %s", floatVar, intVar, stringVar);System.out.println(fs);

String 方法

下面是 String 类支持的常用方法,更多详细,参看 Java API 文档:

SN(序号)方法描述
1char charAt(int index)
返回指定索引处的 char 值。
2int compareTo(Object o)
把这个字符串和另一个对象比较。
3int compareTo(String anotherString)
按字典顺序比较两个字符串。
4int compareToIgnoreCase(String str)
按字典顺序比较两个字符串,不考虑大小写。
5String concat(String str)
将指定字符串连接到此字符串的结尾。
6boolean contentEquals(StringBuffer sb)
当且仅当字符串与指定的StringButter有相同顺序的字符时候返回真。
7static String copyValueOf(char[] data)
返回指定数组中表示该字符序列的 String。
8static String copyValueOf(char[] data, int offset, int count)
返回指定数组中表示该字符序列的 String。
9boolean endsWith(String suffix)
测试此字符串是否以指定的后缀结束。
10boolean equals(Object anObject)
将此字符串与指定的对象比较。
11boolean equalsIgnoreCase(String anotherString)
将此 String 与另一个 String 比较,不考虑大小写。
12byte[] getBytes()
 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
13byte[] getBytes(String charsetName)
使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
14void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 将字符从此字符串复制到目标字符数组。
15int hashCode()
返回此字符串的哈希码。
16int indexOf(int ch)
返回指定字符在此字符串中第一次出现处的索引。
17int indexOf(int ch, int fromIndex)
返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。
18int indexOf(String str)
 返回指定子字符串在此字符串中第一次出现处的索引。
19int indexOf(String str, int fromIndex)
返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
20String intern()
 返回字符串对象的规范化表示形式。
21int lastIndexOf(int ch)
 返回指定字符在此字符串中最后一次出现处的索引。
22int lastIndexOf(int ch, int fromIndex)
返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。
23int lastIndexOf(String str)
返回指定子字符串在此字符串中最右边出现处的索引。
24int lastIndexOf(String str, int fromIndex)
 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
25int length()
返回此字符串的长度。
26boolean matches(String regex)
告知此字符串是否匹配给定的正则表达式。
27boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
28boolean regionMatches(int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
29String replace(char oldChar, char newChar)
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
30String replaceAll(String regex, String replacement
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
31String replaceFirst(String regex, String replacement)
 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
32String[] split(String regex)
根据给定正则表达式的匹配拆分此字符串。
33String[] split(String regex, int limit)
根据匹配给定的正则表达式来拆分此字符串。
34boolean startsWith(String prefix)
测试此字符串是否以指定的前缀开始。
35boolean startsWith(String prefix, int toffset)
测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
36CharSequence subSequence(int beginIndex, int endIndex)
 返回一个新的字符序列,它是此序列的一个子序列。
37String substring(int beginIndex)
返回一个新的字符串,它是此字符串的一个子字符串。
38String substring(int beginIndex, int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。
39char[] toCharArray()
将此字符串转换为一个新的字符数组。
40String toLowerCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
41String toLowerCase(Locale locale)
 使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。
42String toString()
 返回此对象本身(它已经是一个字符串!)。
43String toUpperCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
44String toUpperCase(Locale locale)
使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。
45String trim()
返回字符串的副本,忽略前导空白和尾部空白。
46static String valueOf(primitive data type x)
返回给定data type类型x参数的字符串表示形式。


当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

和String类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

实例

public class Test{    public static void main(String args[]){       StringBuffer sBuffer = new StringBuffer(" test");       sBuffer.append(" String Buffer");       System.out.println(sBuffer);     }}

以上实例编译运行结果如下:

test String Buffer

StringBuffer 方法

以下是 StringBuffer 类支持的主要方法:

序号 方法描述
1 public StringBuffer append(String s)
将指定的字符串追加到此字符序列。
2 public StringBuffer reverse()
 将此字符序列用其反转形式取代。
3 public delete(int start, int end)
移除此序列的子字符串中的字符。
4 public insert(int offset, int i)
int 参数的字符串表示形式插入此序列中。
5 replace(int start, int end, String str)
使用给定 String 中的字符替换此序列的子字符串中的字符。

下面的列表里的方法和 String 类的方法类似:

序号 方法描述
1 int capacity()
返回当前容量。
2 char charAt(int index)
返回此序列中指定索引处的 char 值。
3 void ensureCapacity(int minimumCapacity)
确保容量至少等于指定的最小值。
4 void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
将字符从此序列复制到目标字符数组 dst
5 int indexOf(String str)
返回第一次出现的指定子字符串在该字符串中的索引。
6 int indexOf(String str, int fromIndex)
从指定的索引处开始,返回第一次出现的指定子字符串在该字符串中的索引。
7 int lastIndexOf(String str)
返回最右边出现的指定子字符串在此字符串中的索引。
8 int lastIndexOf(String str, int fromIndex)
返回最后一次出现的指定子字符串在此字符串中的索引。
9 int length()
 返回长度(字符数)。
10 void setCharAt(int index, char ch)
将给定索引处的字符设置为 ch
11 void setLength(int newLength)
设置字符序列的长度。
12 CharSequence subSequence(int start, int end)
返回一个新的字符序列,该字符序列是此序列的子序列。
13 String substring(int start)
返回一个新的 String,它包含此字符序列当前所包含的字符子序列。
14 String substring(int start, int end)
返回一个新的 String,它包含此序列当前所包含的字符子序列。
15 String toString()
返回此序列中数据的字符串表示形式。


数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。

Java 语言中提供的数组是用来存储固定大小的同类型元素。

你可以声明一个数组变量,如 numbers[100] 来代替直接声明 100 个独立变量 number0,number1,....,number99。

本教程将为大家介绍Java数组的声明、创建和初始化,并给出其对应的代码。


声明数组变量

首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:

dataType[] arrayRefVar;   // 首选的方法或dataType arrayRefVar[];  // 效果相同,但不是首选方法

注意: 建议使用 dataType[] arrayRefVar 的声明风格声明数组变量。 dataType arrayRefVar[] 风格是来自 C/C++ 语言 ,在Java中采用是为了让  C/C++  程序员能够快速理解 java 语言。

实例

下面是这两种语法的代码示例:

double[] myList;         // 首选的方法或double myList[];         //  效果相同,但不是首选方法

创建数组

Java 语言使用 new操作符来创建数组,语法如下:

arrayRefVar = new dataType[arraySize];

上面的语法语句做了两件事:

  • 一、使用 dataType[arraySize] 创建了一个数组。

  • 二、把新创建的数组的引用赋值给变量 arrayRefVar。

数组变量的声明,和创建数组可以用一条语句完成,如下所示:

dataType[] arrayRefVar = new dataType[arraySize];

另外,你还可以使用如下的方式创建数组。

dataType[] arrayRefVar = {value0, value1, ..., valuek};

数组的元素是通过索引访问的。数组索引从0开始,所以索引值从 0 到 arrayRefVar.length-1。

那么当数组开辟空间之后,就可以采用如下的方式的操作:

  • 数组的访问通过索引完成,即:“数组名称[索引]”,但是需要注意的是,数组的索引从0开始,所以索引的范围就是0 ~ 数组长度-1,例如开辟了3个空间的数组,所以可以使用的索引是:0,1,2,如果此时访问的时候超过了数组的索引范围,会产生 java.lang.ArrayIndexOutOfBoundsException 异常信息;
  • 当我们数组采用动态初始化开辟空间后,数组里面的每一个元素都是该数组对应数据类型的默认值;
  • 数组本身是一个有序的集合操作,所以对于数组的内容操作往往会采用循环的模式完成,数组是一个有限的数据集合,所以应该使用 for 循环。
  • 在 Java 中提供有一种动态取得数组长度的方式:数组名称.length;

示例: 定义一个int型数组

public class ArrayDemo {	public static void main(String args[]) {		int data[] = new int[3]; /*开辟了一个长度为3的数组*/		data[0] = 10; // 第一个元素		data[1] = 20; // 第二个元素		data[2] = 30; // 第三个元素		for(int x = 0; x < data.length; x++) {			System.out.println(data[x]); //通过循环控制索引		}	}}

数组本身除了声明并开辟空间之外还有另外一种开辟模式。

示例: 采用分步的模式开辟数组空间

public class ArrayDemo {	public static void main(String args[]) {		int data[] = null; 		data = new int[3]; /*开辟了一个长度为3的数组*/		data[0] = 10; // 第一个元素		data[1] = 20; // 第二个元素		data[2] = 30; // 第三个元素		for(int x = 0; x < data.length; x++) {			System.out.println(data[x]); //通过循环控制索引		}	}}

但是千万要记住,数组属于引用数据类型,所以在数组使用之前一定要开辟空间(实例化),如果使用了没有开辟空间的数组,则一定会出现 NullPointerException 异常信息:

public class ArrayDemo {	public static void main(String args[]) {		int data[] = null; 		System.out.println(data[x]);	}}

这一原则和之前讲解的对象是完全相同的。

数组在开发之中一定会使用,但是像上面的操作很少。在以后的实际开发之中,会更多的使用数组概念,而直接使用,大部分情况下都只是做一个 for 循环输出。


处理数组

数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本循环或者 foreach 循环。

示例

该实例完整地展示了如何创建、初始化和操纵数组:

public class TestArray {   public static void main(String[] args) {      double[] myList = {1.9, 2.9, 3.4, 3.5};      // 打印所有数组元素      for (int i = 0; i < myList.length; i++) {          System.out.println(myList[i] + " ");       }       // 计算所有元素的总和       double total = 0;       for (int i = 0; i < myList.length; i++) {          total += myList[i];       }       System.out.println("Total is " + total);       // 查找最大元素       double max = myList[0];       for (int i = 1; i < myList.length; i++) {          if (myList[i] > max) max = myList[i];      }      System.out.println("Max is " + max);   }}

以上实例编译运行结果如下:

1.92.93.43.5Total is 11.7Max is 3.5

foreach 循环

JDK 1.5 引进了一种新的循环类型,被称为 foreach 循环或者加强型循环,它能在不使用下标的情况下遍历数组。

语法格式如下:

for(type element: array){    System.out.println(element);}

示例

该实例用来显示数组 myList 中的所有元素:

public class TestArray {   public static void main(String[] args) {      double[] myList = {1.9, 2.9, 3.4, 3.5};      // 打印所有数组元素      for (double element: myList) {         System.out.println(element);      }   }}

以上实例编译运行结果如下:

1.92.93.43.5

数组作为函数的参数

数组可以作为参数传递给方法。例如,下面的例子就是一个打印 int 数组中元素的方法。

public static void printArray(int[] array) {  for (int i = 0; i < array.length; i++) {     System.out.print(array[i] + " ");   } }

下面例子调用 printArray 方法打印出 3,1,2,6,4和2:

printArray(new int[]{3, 1, 2, 6, 4, 2});

数组作为函数的返回值

public static int[] reverse(int[] list) {  int[] result = new int[list.length];  for (int i = 0, j = result.length - 1; i < list.length; i++, j--) {     result[j] = list[i];   }   return result; }

以上实例中result数组作为函数的返回值。


Arrays 类

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。具有以下功能:

  • 给数组赋值:通过 fill 方法。

  • 对数组排序:通过 sort 方法,按升序。

  • 比较数组:通过 equals 方法比较数组中元素值是否相等。

  • 查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。

具体说明请查看下表:

序号方法和说明
1public static int binarySearch(Object[] a, Object key)
用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
2public static boolean equals(long[] a, long[] a2)
如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
3public static void fill(int[] a, int val)
将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
4public static void sort(Object[] a)
对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。


java.util包提供了Date类来封装当前的日期和时间。 Date类提供两个构造函数来实例化Date对象。

第一个构造函数使用当前日期和时间来初始化对象。

Date( )

第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。

Date(long millisec)

Date对象创建以后,可以调用下面的方法。

序号 方法和描述
1 boolean after(Date date)
若当调用此方法的Date对象在指定日期之后返回true,否则返回false。
2 boolean before(Date date)
若当调用此方法的Date对象在指定日期之前返回true,否则返回false。
3 Object clone( )
返回此对象的副本。
4 int compareTo(Date date)
比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。
5 int compareTo(Object obj)
若obj是Date类型则操作等同于compareTo(Date) 。否则它抛出ClassCastException。
6 boolean equals(Object date)
当调用此方法的Date对象和指定日期相等时候返回true,否则返回false。
7 long getTime( )
返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
8 int hashCode( )
 返回此对象的哈希码值。
9 void setTime(long time)
 
用自1970年1月1日00:00:00 GMT以后time毫秒数设置时间和日期。
10 String toString( )
转换Date对象为String表示形式,并返回该字符串。

获取当前日期时间

Java中获取当前日期和时间很简单,使用Date对象的 toString()方法来打印当前日期和时间,如下所示:

import java.util.Date;  public class DateDemo {   public static void main(String args[]) {       // 初始化 Date 对象       Date date = new Date();               // 使用 toString() 函数显示日期时间       System.out.println(date.toString());   }}

以上实例编译运行结果如下:

Mon May 04 09:51:52 CDT 2013

日期比较

Java使用以下三种方法来比较两个日期:

  • 使用getTime( ) 方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。
  • 使用方法before(),after()和equals()。例如,一个月的12号比18号早,则new Date(99, 2, 12).before(new Date (99, 2, 18))返回true。
  • 使用compareTo()方法,它是由Comparable接口定义的,Date类实现了这个接口。

使用SimpleDateFormat格式化日期

SimpleDateFormat是一个以语言环境敏感的方式来格式化和分析日期的类。SimpleDateFormat允许你选择任何用户自定义日期时间格式来运行。例如:

import java.util.*;import java.text.*;public class DateDemo {   public static void main(String args[]) {      Date dNow = new Date( );      SimpleDateFormat ft =       new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");      System.out.println("Current Date: " + ft.format(dNow));   }}

以上实例编译运行结果如下:

Current Date: Sun 2004.07.18 at 04:14:09 PM PDT

简单的DateFormat格式化编码

时间模式字符串用来指定时间格式。在此模式中,所有的ASCII字母被保留为模式字母,定义如下:

字母 描述 示例
G 纪元标记 AD
y 四位年份 2001
M 月份 July or 07
d 一个月的日期 10
h  A.M./P.M. (1~12)格式小时 12
H 一天中的小时 (0~23) 22
m 分钟数 30
s 秒数 55
S 毫秒数 234
E 星期几 Tuesday
D 一年中的日子 360
F 一个月中第几周的周几 2 (second Wed. in July)
w 一年中第几周 40
W 一个月中第几周 1
a A.M./P.M. 标记 PM
k 一天中的小时(1~24) 24
K  A.M./P.M. (0~11)格式小时 10
z 时区 Eastern Standard Time
' 文字定界符 Delimiter
" 单引号 `

使用printf格式化日期

printf方法可以很轻松地格式化时间和日期。使用两个字母格式,它以t开头并且以下面表格中的一个字母结尾。例如:

import java.util.Date;public class DateDemo {  public static void main(String args[]) {     // 初始化 Date 对象     Date date = new Date();     // 使用toString()显示日期和时间     String str = String.format("Current Date/Time : %tc", date );     System.out.printf(str);  }}

以上实例编译运行结果如下:

Current Date/Time : Sat Dec 15 16:37:57 MST 2012

如果你需要重复提供日期,那么利用这种方式来格式化它的每一部分就有点复杂了。因此,可以利用一个格式化字符串指出要被格式化的参数的索引。

索引必须紧跟在%后面,而且必须以$结束。例如:

import java.util.Date;  public class DateDemo {   public static void main(String args[]) {       // 初始化 Date 对象       Date date = new Date();               // 使用toString()显示日期和时间       System.out.printf("%1$s %2$tB %2$td, %2$tY",                          "Due date:", date);   }}

以上实例编译运行结果如下:

Due date: February 09, 2004

或者,你可以使用<标志。它表明先前被格式化的参数要被再次使用。例如:

import java.util.Date;  public class DateDemo {   public static void main(String args[]) {       // 初始化 Date 对象       Date date = new Date();               // 显示格式化时间       System.out.printf("%s %tB %<te, %<tY",                           "Due date:", date);   }} 

以上实例编译运行结果如下:

Due date: February 09, 2004

日期和时间转换字符

字符 描述 例子
c 完整的日期和时间 Mon May 04 09:51:52 CDT 2009
F ISO 8601 格式日期 2004-02-09
D U.S. 格式日期 (月/日/年) 02/09/2004
T 24小时时间 18:05:19
r 12小时时间 06:05:19 pm
R 24小时时间,不包含秒 18:05
Y 4位年份(包含前导0) 2004
y 年份后2位(包含前导0) 04
C 年份前2位(包含前导0) 20
B 月份全称 February
b 月份简称 Feb
n 2位月份(包含前导0) 02
d 2位日子(包含前导0) 03
e 2位日子(不包含前导0) 9
A 星期全称 Monday
a 星期简称 Mon
j 3位年份(包含前导0) 069
H 2位小时(包含前导0), 00 到 23 18
k 2位小时(不包含前导0),  0 到 23 18
I 2位小时(包含前导0), 01 到 12 06
l 2位小时(不包含前导0),  1 到 12 6
M 2位分钟(包含前导0) 05
S 2位秒数(包含前导0) 19
L 3位毫秒(包含前导0) 047
N 9位纳秒(包含前导0) 047000000
P 大写上下午标志 PM
p 小写上下午标志 pm
z 从GMT的RFC 822数字偏移 -0800
Z 时区 PST
s 自 1970-01-01 00:00:00 GMT的秒数 1078884319
Q 自 1970-01-01 00:00:00 GMT的毫妙 1078884319047

还有其他有用的日期和时间相关的类。对于更多的细节,你可以参考到Java标准文档。


解析字符串为时间

SimpleDateFormat 类有一些附加的方法,特别是parse(),它试图按照给定的SimpleDateFormat 对象的格式化存储来解析字符串。例如:

import java.util.*;import java.text.*;  public class DateDemo {   public static void main(String args[]) {      SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd");       String input = args.length == 0 ? "1818-11-11" : args[0];       System.out.print(input + " Parses as ");       Date t;       try {           t = ft.parse(input);           System.out.println(t);       } catch (ParseException e) {           System.out.println("Unparseable using " + ft);       }   }}

以上实例编译运行结果如下:

$ java DateDemo1818-11-11 Parses as Wed Nov 11 00:00:00 GMT 1818$ java DateDemo 2007-12-012007-12-01 Parses as Sat Dec 01 00:00:00 GMT 2007

Java 休眠(sleep)

你可以让程序休眠一毫秒的时间或者到您的计算机的寿命长的任意段时间。例如,下面的程序会休眠3秒:

import java.util.*;  public class SleepDemo {   public static void main(String args[]) {      try {          System.out.println(new Date( ) + "
");          Thread.sleep(5*60*10);          System.out.println(new Date( ) + "
");       } catch (Exception e) {           System.out.println("Got an exception!");       }   }}

以上实例编译运行结果如下:

Sun May 03 18:04:41 GMT 2009Sun May 03 18:04:44 GMT 2009

测量时间

下面的一个例子表明如何测量时间间隔(以毫秒为单位):

import java.util.*;  public class DiffDemo {   public static void main(String args[]) {      try {         long start = System.currentTimeMillis( );         System.out.println(new Date( ) + "
");         Thread.sleep(5*60*10);         System.out.println(new Date( ) + "
");         long end = System.currentTimeMillis( );         long diff = end - start;         System.out.println("Difference is : " + diff);      } catch (Exception e) {         System.out.println("Got an exception!");      }   }}

以上实例编译运行结果如下:

Sun May 03 18:16:51 GMT 2009Sun May 03 18:16:57 GMT 2009Difference is : 5993

Calendar类

我们现在已经能够格式化并创建一个日期对象了,但是我们如何才能设置和获取日期数据的特定部分呢,比如说小时,日,或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类。

Calendar类的功能要比Date类强大很多,而且在实现方式上也比Date类要复杂一些。

Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。

创建一个代表系统当前日期的Calendar对象

Calendar c = Calendar.getInstance();//默认是当前日期

创建一个指定日期的Calendar对象

使用Calendar类代表特定的时间,需要首先创建一个Calendar的对象,然后再设定该对象中的年月日参数来完成。

//创建一个代表2009年6月12日的Calendar对象Calendar c1 = Calendar.getInstance();c1.set(2009, 6 - 1, 12);

Calendar类对象字段类型

Calendar类中用一下这些常量表示不同的意义,jdk内的很多类其实都是采用的这种思想

常量描述
Calendar.YEAR年份
Calendar.MONTH月份
Calendar.DATE日期
Calendar.DAY_OF_MONTH日期,和上面的字段意义完全相同
Calendar.HOUR12小时制的小时
Calendar.HOUR_OF_DAY24小时制的小时
Calendar.MINUTE分钟
Calendar.SECOND
Calendar.DAY_OF_WEEK星期几

Calendar类对象信息的设置

Set设置

如:

Calendar c1 = Calendar.getInstance();

调用:

public final void set(int year,int month,int date)
c1.set(2009, 6 - 1, 12);//把Calendar对象c1的年月日分别设这为:2009、5、12

利用字段类型设置

如果只设定某个字段,例如日期的值,则可以使用如下set方法:

public void set(int field,int value)

把 c1对象代表的日期设置为10号,其它所有的数值会被重新计算

c1.set(Calendar.DATE,10);

把c1对象代表的年份设置为2008年,其他的所有数值会被重新计算

c1.set(Calendar.YEAR,2008);

其他字段属性set的意义以此类推

Add设置

Calendar c1 = Calendar.getInstance();

把c1对象的日期加上10,也就是c1所表的日期的10天后的日期,其它所有的数值会被重新计算

c1.add(Calendar.DATE, 10);

把c1对象的日期减去10,也就是c1所表的日期的10天前的日期,其它所有的数值会被重新计算

c1.add(Calendar.DATE, -10);

其他字段属性的add的意义以此类推

Calendar类对象信息的获得

Calendar c1 = Calendar.getInstance();// 获得年份int year = c1.get(Calendar.YEAR);// 获得月份int month = c1.get(Calendar.MONTH) + 1;// 获得日期int date = c1.get(Calendar.DATE);// 获得小时int hour = c1.get(Calendar.HOUR_OF_DAY);// 获得分钟int minute = c1.get(Calendar.MINUTE);// 获得秒int second = c1.get(Calendar.SECOND);// 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)int day = c1.get(Calendar.DAY_OF_WEEK);

GregorianCalendar类

Calendar类实现了公历日历,GregorianCalendar是Calendar类的一个具体实现。

Calendar 的getInstance()方法返回一个默认用当前的语言环境和时区初始化的GregorianCalendar对象。GregorianCalendar定义了两个字段:AD和BC。这些代表公历定义的两个时代。

下面列出GregorianCalendar对象的几个构造方法:

序号 构造函数和说明
1 GregorianCalendar()
在具有默认语言环境的默认时区内使用当前时间构造一个默认的 GregorianCalendar。
2 GregorianCalendar(int year, int month, int date)
在具有默认语言环境的默认时区内构造一个带有给定日期设置的 GregorianCalendar
3 GregorianCalendar(int year, int month, int date, int hour, int minute)
为具有默认语言环境的默认时区构造一个具有给定日期和时间设置的 GregorianCalendar。
4 GregorianCalendar(int year, int month, int date, int hour, int minute, int second)
  为具有默认语言环境的默认时区构造一个具有给定日期和时间设置的 GregorianCalendar。
5 GregorianCalendar(Locale aLocale)
在具有给定语言环境的默认时区内构造一个基于当前时间的 GregorianCalendar。
6 GregorianCalendar(TimeZone zone)
在具有默认语言环境的给定时区内构造一个基于当前时间的 GregorianCalendar。
7 GregorianCalendar(TimeZone zone, Locale aLocale)
 在具有给定语言环境的给定时区内构造一个基于当前时间的 GregorianCalendar。

这里是GregorianCalendar 类提供的一些有用的方法列表:

序号 方法和说明
1 void add(int field, int amount)
根据日历规则,将指定的(有符号的)时间量添加到给定的日历字段中。
2 protected void computeFields()
转换UTC毫秒值为时间域值
3 protected void computeTime()
覆盖Calendar ,转换时间域值为UTC毫秒值
4 boolean equals(Object obj)
比较此 GregorianCalendar 与指定的 Object。
5 int get(int field)
获取指定字段的时间值
6 int getActualMaximum(int field)
返回当前日期,给定字段的最大值
7 int getActualMinimum(int field)
返回当前日期,给定字段的最小值
8 int getGreatestMinimum(int field)
 返回此 GregorianCalendar 实例给定日历字段的最高的最小值。
9 Date getGregorianChange()
获得格里高利历的更改日期。
10 int getLeastMaximum(int field)
返回此 GregorianCalendar 实例给定日历字段的最低的最大值
11 int getMaximum(int field)
返回此 GregorianCalendar 实例的给定日历字段的最大值。
12 Date getTime()
获取日历当前时间。
13 long getTimeInMillis()
获取用长整型表示的日历的当前时间
14 TimeZone getTimeZone()
获取时区。
15 int getMinimum(int field)
返回给定字段的最小值。
16 int hashCode()
重写hashCode.
17 boolean isLeapYear(int year)
确定给定的年份是否为闰年。
18 void roll(int field, boolean up)
在给定的时间字段上添加或减去(上/下)单个时间单元,不更改更大的字段。
19 void set(int field, int value)
用给定的值设置时间字段。
20 void set(int year, int month, int date)
设置年、月、日的值。
21 void set(int year, int month, int date, int hour, int minute)
设置年、月、日、小时、分钟的值。
22 void set(int year, int month, int date, int hour, int minute, int second)
设置年、月、日、小时、分钟、秒的值。
23 void setGregorianChange(Date date)
设置 GregorianCalendar 的更改日期。
24 void setTime(Date date)
用给定的日期设置Calendar的当前时间。
25 void setTimeInMillis(long millis)
用给定的long型毫秒数设置Calendar的当前时间。
26 void setTimeZone(TimeZone value)
用给定时区值设置当前时区。
27 String toString()
返回代表日历的字符串。

实例

import java.util.*;  public class GregorianCalendarDemo {   public static void main(String args[]) {      String months[] = {      "Jan", "Feb", "Mar", "Apr",      "May", "Jun", "Jul", "Aug",      "Sep", "Oct", "Nov", "Dec"};            int year;      // 初始化 Gregorian 日历      // 使用当前时间和日期      // 默认为本地时间和时区      GregorianCalendar gcalendar = new GregorianCalendar();      // 显示当前时间和日期的信息      System.out.print("Date: ");      System.out.print(months[gcalendar.get(Calendar.MONTH)]);      System.out.print(" " + gcalendar.get(Calendar.DATE) + " ");      System.out.println(year = gcalendar.get(Calendar.YEAR));      System.out.print("Time: ");      System.out.print(gcalendar.get(Calendar.HOUR) + ":");      System.out.print(gcalendar.get(Calendar.MINUTE) + ":");      System.out.println(gcalendar.get(Calendar.SECOND));            // 测试当前年份是否为闰年      if(gcalendar.isLeapYear(year)) {         System.out.println("当前年份是闰年");      }      else {         System.out.println("当前年份不是闰年");      }   }}

以上实例编译运行结果如下:

Date: Apr 22 2009Time: 11:25:27当前年份不是闰年

关于Calender 类的完整列表,你可以参考标准的Java文档。


在前面几个章节中我们经常使用到System.out.println(),那么它是什么呢?

println()是一个方法(Method),而System是系统类(Class),out是标准输出对象(Object)。这句话的用法是调用系统类System中的标准输出对象out中的方法println()。

那么什么是方法呢?

Java方法是语句的集合,它们在一起执行一个功能。

  • 方法是解决一类问题的步骤的有序组合

  • 方法包含于类或对象中

  • 方法在程序中被创建,在其他地方被引用


方法的定义

一般情况下,定义一个方法包含以下语法:

修饰符 返回值类型 方法名 (参数类型 参数名){    ...    方法体    ...    return 返回值;}

方法包含一个方法头和一个方法体。下面是一个方法的所有部分:

  • 修饰符:修饰符,这是可选的,告诉编译器如何调用该方法。定义了该方法的访问类型。

  • 返回值类型 :方法可能会返回值。returnValueType是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值。在这种情况下,returnValueType是关键字void

  • 方法名:是方法的实际名称。方法名和参数表共同构成方法签名。

  • 参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。

  • 方法体:方法体包含具体的语句,定义该方法的功能。

如:

public static int age(int birthday){...}

参数可以有多个:

static float interest(float principal, int year){...}

注意: 在一些其它语言中方法指过程和函数。一个返回非void类型返回值的方法称为函数;一个返回void类型返回值的方法叫做过程。

实例

下面的方法包含2个参数num1和num2,它返回这两个参数的最大值。

/** 返回两个整型变量数据的较大值 */public static int max(int num1, int num2) {   int result;   if (num1 > num2){      result = num1;   }else{      result = num2;   }   return result; }


方法调用

Java支持两种调用方法的方式,根据方法是否返回值来选择。

当程序调用一个方法时,程序的控制权交给了被调用的方法。当被调用方法的返回语句执行或者到达方法体闭括号时候交还控制权给程序。

当方法返回一个值的时候,方法调用通常被当做一个值。例如:

int larger = max(30, 40);

如果方法返回值是void,方法调用一定是一条语句。例如,方法println返回void。下面的调用是个语句:

System.out.println("Welcome to Java!");

示例

下面的例子演示了如何定义一个方法,以及如何调用它:

public class TestMax {   /** 主方法 */   public static void main(String[] args) {      int i = 5;      int j = 2;      int k = max(i, j);      System.out.println("The maximum between " + i +                    " and " + j + " is " + k);   }   /** 返回两个整数变量较大的值 */   public static int max(int num1, int num2) {      int result;      if (num1 > num2){         result = num1;      }else{         result = num2;      }      return result;    }}

以上实例编译运行结果如下:

The maximum between 5 and 2 is 5

这个程序包含main方法和max方法。Main方法是被JVM调用的,除此之外,main方法和其它方法没什么区别。

main方法的头部是不变的,如例子所示,带修饰符public和static,返回void类型值,方法名字是main,此外带个一个String[]类型参数。String[]表明参数是字符串数组。


void 关键字

本节说明如何声明和调用一个void方法。

下面的例子声明了一个名为printGrade的方法,并且调用它来打印给定的分数。

示例

public class TestVoidMethod {   public static void main(String[] args) {      printGrade(78.5);   }   public static void printGrade(double score) {      if (score >= 90.0) {         System.out.println('A');      }      else if (score >= 80.0) {         System.out.println('B');      }      else if (score >= 70.0) {         System.out.println('C');      }      else if (score >= 60.0) {         System.out.println('D');      }      else {         System.out.println('F');      }   }}

以上实例编译运行结果如下:

C

这里printGrade方法是一个void类型方法,它不返回值。

一个void方法的调用一定是一个语句。 所以,它被在main方法第三行以语句形式调用。就像任何以分号结束的语句一样。


通过值传递参数

调用一个方法时候需要提供参数,你必须按照参数列表指定的顺序提供。

例如,下面的方法连续n次打印一个消息:

public static void nPrintln(String message, int n) {   for (int i = 0; i < n; i++)      System.out.println(message);}

示例

下面的例子演示按值传递的效果。

该程序创建一个方法,该方法用于交换两个变量。

public class TestPassByValue {   public static void main(String[] args) {      int num1 = 1;      int num2 = 2;      System.out.println("Before swap method, num1 is " +                          num1 + " and num2 is " + num2);      // 调用swap方法      swap(num1, num2);      System.out.println("After swap method, num1 is " +                         num1 + " and num2 is " + num2);   }   /** 交换两个变量的方法 */   public static void swap(int n1, int n2) {      System.out.println("	Inside the swap method");      System.out.println("		Before swapping n1 is " + n1                           + " n2 is " + n2);      // 交换 n1 与 n2的值      int temp = n1;      n1 = n2;      n2 = temp;      System.out.println("		After swapping n1 is " + n1                           + " n2 is " + n2);   }}

以上实例编译运行结果如下:

Before swap method, num1 is 1 and num2 is 2        Inside the swap method                Before swapping n1 is 1 n2 is 2                After swapping n1 is 2 n2 is 1After swap method, num1 is 1 and num2 is 2

传递两个参数调用swap方法。有趣的是,方法被调用后,实参的值并没有改变。


方法的重载

上面使用的max方法仅仅适用于int型数据。但如果你想得到两个浮点类型数据的最大值呢?

解决方法是创建另一个有相同名字但参数不同的方法,如下面代码所示:

public static double max(double num1, double num2) {  if (num1 > num2){    return num1;  }else{    return num2;  }}

如果你调用max方法时传递的是int型参数,则 int型参数的max方法就会被调用;

如果传递的是double型参数,则double类型的max方法体会被调用,这叫做方法重载;

就是说一个类的两个方法拥有相同的名字,但是有不同的参数列表。

Java编译器根据方法签名判断哪个方法应该被调用。

方法重载可以让程序更清晰易读。执行密切相关任务的方法应该使用相同的名字。

重载的方法必须拥有不同的参数列表。你不能仅仅依据修饰符或者返回类型的不同来重载方法。


变量作用域

变量的范围是程序中该变量可以被引用的部分。

方法内定义的变量被称为局部变量。

局部变量的作用范围从声明开始,直到包含它的块结束。

局部变量必须声明才可以使用。

方法的参数范围涵盖整个方法。参数实际上是一个局部变量。

for循环的初始化部分声明的变量,其作用范围在整个循环。

但循环体内声明的变量其适用范围是从它声明到循环体结束。它包含如下所示的变量声明:

你可以在一个方法里,不同的非嵌套块中多次声明一个具有相同的名称局部变量,但你不能在嵌套块内两次声明局部变量。

命令行参数的使用

有时候你希望运行一个程序时候再传递给它消息。这要靠传递命令行参数给main()函数实现。

命令行参数是在执行程序时候紧跟在程序名字后面的信息。

实例

下面的程序打印所有的命令行参数:

public class CommandLine {   public static void main(String args[]){       for(int i=0; i<args.length; i++){                    System.out.println("args [" + i + "]: " + args[i]);      }    } }

如下所示,运行这个程序:

java CommandLine this is a command line 200 -100

运行结果如下:

args[0]: thisargs[1]: isargs[2]: aargs[3]: commandargs[4]: lineargs[5]: 200args[6]: -100


构造方法

当一个对象被创建时候,构造方法用来初始化该对象。构造方法和它所在类的名字相同,但构造方法没有返回值。

通常会使用构造方法给一个类的实例变量赋初值,或者执行其它必要的步骤来创建一个完整的对象。

不管你是否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个默认构造方法,它把所有成员初始化为0。

一旦你定义了自己的构造方法,默认构造方法就会失效。

实例

下面是一个使用构造方法的例子:

// 一个简单的构造函数static class MyClass {   int x;      // 以下是构造函数   MyClass() {      x = 10;   }}

你可以像下面这样调用构造方法来初始化一个对象:

public class ConsDemo {   public static void main(String args[]) {      MyClass t1 = new MyClass();      MyClass t2 = new MyClass();      System.out.println(t1.x + " " + t2.x);   }}

大多时候需要一个有参数的构造方法。

实例

下面是一个使用构造方法的例子:

// 一个简单的构造函数class MyClass {   int x;      // 以下是构造函数   MyClass(int i ) {      x = i;   }}

你可以像下面这样调用构造方法来初始化一个对象:

public class ConsDemo {   public static void main(String args[]) {      MyClass t1 = new MyClass( 10 );      MyClass t2 = new MyClass( 20 );      System.out.println(t1.x + " " + t2.x);   }}

运行结果如下:

10 20

可变参数

JDK 1.5 开始,Java支持传递同类型的可变参数给一个方法。

方法的可变参数的声明如下所示:

typeName... parameterName

在方法声明中,在指定参数类型后加一个省略号(...) 。

一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。

实例

public class VarargsDemo {   public static void main(String args[]) {      // 调用可变参数的方法	  printMax(34, 3, 3, 2, 56.5);      printMax(new double[]{1, 2, 3});   }   public static void printMax( double... numbers) {   if (numbers.length == 0) {      System.out.println("No argument passed");      return;   }   double result = numbers[0];   for (int i = 1; i <  numbers.length; i++)       if (numbers[i] >  result){          result = numbers[i];       }      System.out.println("The max value is " + result);   }}

以上实例编译运行结果如下:

The max value is 56.5The max value is 3.0


finalize() 方法

Java允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,这个方法叫做finalize( ),它用来清除回收对象。

例如,你可以使用finalize()来确保一个对象打开的文件被关闭了。

在finalize()方法里,你必须指定在对象销毁时候要执行的操作。

finalize()一般格式是:

protected void finalize(){   // 在这里终结代码}

关键字protected是一个限定符,它确保finalize() 方法不会被该类以外的代码调用。

当然,Java的内存回收可以由JVM来自动完成。如果你手动使用,则可以使用上面的方法。

实例

public class FinalizationDemo {      public static void main(String[] args) {          Cake c1 = new Cake(1);          Cake c2 = new Cake(2);          Cake c3 = new Cake(3);                    c2 = c3 = null;          System.gc(); //调用Java垃圾收集器    }  }    class Cake extends Object {      private int id;      public Cake(int id) {          this.id = id;          System.out.println("Cake Object " + id + "is created");      }            protected void finalize() throws java.lang.Throwable {          super.finalize();          System.out.println("Cake Object " + id + "is disposed");      }  }

运行以上代码,输出结果如下:

C:1>java FinalizationDemo  Cake Object 1is created  Cake Object 2is created  Cake Object 3is created  Cake Object 3is disposed  Cake Object 2is disposed


Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。

Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。

一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。

Java为I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。

但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。


读取控制台输入

Java 的控制台输入由 System.in 完成。

为了获得一个绑定到控制台的字符流,你可以把 System.in 包装在一个 BufferedReader 对象中来创建一个字符流。

下面是创建 BufferedReader 的基本语法:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

BufferedReader 对象创建后,我们便可以使用 read() 方法从控制台读取一个字符,或者用 readLine() 方法读取一个字符串。


从控制台读取多字符输入

从 BufferedReader 对象读取一个字符要使用 read() 方法,它的语法如下:

int read() throws IOException

每次调用 read() 方法,它从输入流读取一个字符并把该字符作为整数值返回。 当流结束的时候返回 -1。该方法抛出 IOException。

下面的程序示范了用 read() 方法从控制台不断读取字符直到用户输入 "q" 。

// 使用 BufferedReader 在控制台读取字符import java.io.*;public class BRRead {   public static void main(String args[]) throws IOException   {      char c;      // 使用 System.in 创建 BufferedReader       BufferedReader br = new BufferedReader(new                          InputStreamReader(System.in));      System.out.println("输入字符, 按下 'q' 键退出.");      // 读取字符      do {         c = (char) br.read();         System.out.println(c);      } while(c != 'q');   }}

以上实例编译运行结果如下:

输入字符, 按下 'q' 键退出.123abcq123abcq

从控制台读取字符串

从标准输入读取一个字符串需要使用 BufferedReader 的 readLine() 方法。

它的一般格式是:

String readLine() throws IOException

下面的程序读取和显示字符行直到你输入了单词 "end"。

// 使用 BufferedReader 在控制台读取字符import java.io.*;public class BRReadLines {   public static void main(String args[]) throws IOException   {      // 使用 System.in 创建 BufferedReader       BufferedReader br = new BufferedReader(new                              InputStreamReader(System.in));      String str;      System.out.println("Enter lines of text.");      System.out.println("Enter 'end' to quit.");      do {         str = br.readLine();         System.out.println(str);      } while(!str.equals("end"));   }}

以上实例编译运行结果如下:

Enter lines of text.Enter 'end' to quit.This is line oneThis is line oneThis is line twoThis is line twoendend

控制台输出

在此前已经介绍过,控制台的输出由 print() 和 println() 完成。这些方法都由类 PrintStream 定义,System.out 是该类对象的一个引用。

PrintStream 继承了 OutputStream 类,并且实现了方法 write()。这样,write() 也可以用来往控制台写操作。

PrintStream 定义 write() 的最简单格式如下所示:

void write(int byteval)

该方法将 byteval 的低八位字节写到流中。

实例

下面的例子用 write() 把字符 "A" 和紧跟着的换行符输出到屏幕:

import java.io.*;// 演示 System.out.write().public class WriteDemo {   public static void main(String args[]) {      int b;       b = 'A';      System.out.write(b);      System.out.write('
');   }}

运行以上实例在输出窗口输出 "A" 字符

A

注意:write() 方法不经常使用,因为 print() 和 println() 方法用起来更为方便。


读写文件

如前所述,一个流被定义为一个数据序列。输入流用于从源读取数据,输出流用于向目标写数据。

下图是一个描述输入流和输出流的类层次图。

下面将要讨论的两个重要的流是 FileInputStream 和 FileOutputStream:


FileInputStream

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。

有多种构造方法可用来创建对象。

可以使用字符串类型的文件名来创建一个输入流对象来读取文件:

InputStream f = new FileInputStream("C:/java/hello");

也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:

File f = new File("C:/java/hello");InputStream f = new FileInputStream(f);

创建了 InputStream 对象,就可以使用下面的方法来读取流或者进行其他的流操作。

序号方法及描述
1public void close() throws IOException{}
关闭此文件输入流并释放与此流有关的所有系统资源。抛出 IOException 异常。
2protected void finalize()throws IOException {}
这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出 IOException 异常。
3public int read(int r)throws IOException{}
这个方法从 InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回 -1。
4public int read(byte[] r) throws IOException{}
这个方法从输入流读取 r.length 长度的字节。返回读取的字节数。如果是文件结尾则返回 -1。
5public int available() throws IOException{}
返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值。

除了 InputStream 外,还有一些其他的输入流,更多的细节参考下面链接:


FileOutputStream

该类用来创建一个文件并向文件中写数据。

如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。

有两个构造方法可以用来创建 FileOutputStream 对象。

使用字符串类型的文件名来创建一个输出流对象:

OutputStream f = new FileOutputStream("C:/java/hello")

也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用 File() 方法来创建一个文件对象:

File f = new File("C:/java/hello");OutputStream f = new FileOutputStream(f);

创建 OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。

序号方法及描述
1public void close() throws IOException{}
关闭此文件输入流并释放与此流有关的所有系统资源。抛出 IOException 异常。
2protected void finalize()throws IOException {}
这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出 IOException 异常。
3public void write(int w)throws IOException{}
这个方法把指定的字节写到输出流中。
4public void write(byte[] w)
把指定数组中 w.length 长度的字节写到 OutputStream 中。

除了 OutputStream 外,还有一些其他的输出流,更多的细节参考下面链接:

实例

下面是一个演示 InputStream 和 OutputStream 用法的例子:

import java.io.*; public class fileStreamTest {    public static void main(String args[]) {        try {            byte bWrite[] = { 11, 21, 3, 40, 5 };            OutputStream os = new FileOutputStream("test.txt");            for (int x = 0; x < bWrite.length; x++) {                os.write(bWrite[x]); // writes the bytes            }            os.close();             InputStream is = new FileInputStream("test.txt");            int size = is.available();             for (int i = 0; i < size; i++) {                System.out.print((char) is.read() + "  ");            }            is.close();        } catch (IOException e) {            System.out.print("Exception");        }    }}

上面的程序首先创建文件 test.txt,并把给定的数字以二进制形式写进该文件,同时输出到控制台上。

以上代码由于是二进制写入,可能存在乱码,你可以使用以下代码实例来解决乱码问题:

//文件名 :fileStreamTest2.javaimport java.io.*;public class fileStreamTest2{	public static void main(String[] args) throws IOException {				File f = new File("a.txt");		FileOutputStream fop = new FileOutputStream(f);		// 构建FileOutputStream对象,文件不存在会自动新建				OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");		// 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk				writer.append("中文输入");		// 写入到缓冲区				writer.append("
");		//换行				writer.append("English");		// 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入				writer.close();		//关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉				fop.close();		// 关闭输出流,释放系统资源		FileInputStream fip = new FileInputStream(f);		// 构建FileInputStream对象				InputStreamReader reader = new InputStreamReader(fip, "UTF-8");		// 构建InputStreamReader对象,编码与写入相同		StringBuffer sb = new StringBuffer();		while (reader.ready()) {			sb.append((char) reader.read());			// 转成char加到StringBuffer对象中		}		System.out.println(sb.toString());		reader.close();		// 关闭读取流				fip.close();		// 关闭输入流,释放系统资源	}}

文件和I/O

还有一些关于文件和 I/O 的类,我们也需要知道:


Java中的目录

创建目录:

File 类中有两个方法可以用来创建文件夹:

  • mkdir( ) 方法创建一个文件夹,成功则返回 true,失败则返回 false。失败表明 File 对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建。

  • mkdirs( ) 方法创建一个文件夹和它的所有父文件夹。

下面的例子创建 "/tmp/user/java/bin" 文件夹:

import java.io.File;public class CreateDir {   public static void main(String args[]) {      String dirname = "/tmp/user/java/bin";      File d = new File(dirname);      // 现在创建目录      d.mkdirs();  }}

编译并执行上面代码来创建目录 "/tmp/user/java/bin"。

注意:Java 在 UNIX 和 Windows 自动按约定分辨文件路径分隔符。如果你在 Windows 版本的 Java 中使用分隔符(/) ,路径依然能够被正确解析。


读取目录

一个目录其实就是一个 File 对象,它包含其他文件和文件夹。

如果创建一个 File 对象并且它是一个目录,那么调用 isDirectory( ) 方法会返回 true。

可以通过调用该对象上的 list() 方法,来提取它包含的文件和文件夹的列表。

下面展示的例子说明如何使用 list() 方法来检查一个文件夹中包含的内容:

import java.io.File;public class DirList {   public static void main(String args[]) {      String dirname = "/tmp";      File f1 = new File(dirname);      if (f1.isDirectory()) {         System.out.println( "Directory of " + dirname);         String s[] = f1.list();         for (int i=0; i < s.length; i++) {                         File f = new File(dirname + "/" + s[i]);                         if (f.isDirectory()) {                                System.out.println(s[i] + "是一个目录");                         } else {                System.out.println(s[i] + "是一个文件");             }        }     } else {          System.out.println(dirname + "不是一个目录");     }  }}

以上实例编译运行结果如下:

目录 /tmpbin 是一个目录lib 是一个目录demo 是一个目录test.txt 是一个文件README 是一个文件index.html 是一个文件include 是一个目录

删除目录或文件

删除文件可以使用 java.io.File.delete() 方法。

以下代码会删除目录 /tmp/java/,需要注意的是当删除某一目录时,必须保证该目录下没有其他文件才能正确删除,否则将删除失败。

测试目录结构:

/tmp/java/|-- 1.log|-- test

DeleteFileDemo.java 文件代码:

import java.io.File;public class DeleteFileDemo {    public static void main(String args[]) {        // 这里修改为自己的测试目录        File folder = new File("/tmp/java/");        deleteFolder(folder);    }    // 删除文件及目录    public static void deleteFolder(File folder) {        File[] files = folder.listFiles();        if (files != null) {            for (File f : files) {                if (f.isDirectory()) {                    deleteFolder(f);                } else {                    f.delete();                }            }        }        folder.delete();    }}


java.util.Scanner是Java5的新特征,我们可以通过 Scanner 类来获取用户的输入。

下面是创建 Scanner 对象的基本语法:

 Scanner s = new Scanner(System.in); 

接下来我们演示一个最简单的的数据输入,并通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要使用 hasNext 与 hasNextLine 判断是否还有输入的数据:

使用 next 方法:

import java.util.Scanner; public class ScannerDemo {      public static void main(String[] args) {          Scanner scan = new Scanner(System.in); 		// 从键盘接收数据  		//next方式接收字符串        System.out.println("next方式接收:");        // 判断是否还有输入        if(scan.hasNext()){           	String str1 = scan.next();        	System.out.println("输入的数据为:"+str1);          }      }  } 

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemonext方式接收:youj com输入的数据为:youj

可以看到 com 字符串并未输出,接下来我们看 nextLine。

使用 nextLine 方法:

import java.util.Scanner; public class ScannerDemo {      public static void main(String[] args) {          Scanner scan = new Scanner(System.in); 		// 从键盘接收数据  		//nextLine方式接收字符串        System.out.println("nextLine方式接收:");        // 判断是否还有输入        if(scan.hasNextLine()){           	String str2 = scan.nextLine();        	System.out.println("输入的数据为:"+str2);          }      }  } 

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemonextLine方式接收:youj com输入的数据为:youj com

可以看到 com 字符串输出。

next()与nextLine()区别

next():

  • 1、一定要读取到有效字符后才可以结束输入。
  • 2、对输入有效字符之前遇到的空白,next()方法会自动将其去掉。
  • 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  • next()不能得到带有空格的字符串。

nextLine():

  • 1、以Enter为结束符,也就是说nextLine()方法返回的是输入回车之前的所有字符。
  • 2、可以获得空白。

如果要输入int或float类型的数据,在Scanner类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取:

import java.util.Scanner;  public class ScannerDemo {      public static void main(String[] args) {          Scanner scan = new Scanner(System.in);  		// 从键盘接收数据          int i = 0 ;          float f = 0.0f ;          System.out.print("输入整数:");          if(scan.hasNextInt()){                 			// 判断输入的是否是整数              i = scan.nextInt() ;                			// 接收整数              System.out.println("整数数据:" + i) ;          }else{                                 			// 输入错误的信息              System.out.println("输入的不是整数!") ;          }          System.out.print("输入小数:");          if(scan.hasNextFloat()){              			// 判断输入的是否是小数              f = scan.nextFloat() ;             			// 接收小数              System.out.println("小数数据:" + f) ;          }else{                                			// 输入错误的信息              System.out.println("输入的不是小数!") ;          }      }  } 

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemo输入整数:12整数数据:12输入小数:1.2小数数据:1.2

以下实例我们可以输入多个数字,并求其总和与平均数,每输入一个数字用回车确认,通过输入非数字来结束输入并输出执行结果:

import java.util.Scanner; class ScannerDemo   {      public static void main(String[] args)       {          Scanner scan = new Scanner(System.in);            double sum = 0;          int m = 0;            while(scan.hasNextDouble())          {              double x = scan.nextDouble();              m = m + 1;              sum = sum + x;          }            System.out.println(m+"个数的和为"+sum);          System.out.println(m+"个数的平均值是"+(sum/m));      }  }  

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemo12231521.4end4个数的和为71.44个数的平均值是17.85

更多内容可以参考 API 文档://www.51coolma.cn/manual/jdk1.6/


什么是异常?

程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?在Java中,异常就是Java在编译或运行或者运行过程中出现的错误。 

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用 System.out.println(11/0),那么你是因为你用 0 做了除数,会抛出 java.lang.ArithmeticException 的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。

  • 要打开的文件不存在。

  • 网络通信时连接中断,或者 JVM 内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。-

要理解 Java 异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。


Exception 类的层次

所有的异常类是从 java.lang.Exception 类继承的子类。

Exception 类是 Throwable 类的子类。除了Exception类外,Throwable 还有一个子类 Error 。

Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在 Java 程序处理的范畴之外。

Error 用来指示运行时环境发生的错误。

例如,JVM 内存溢出。一般地,程序不会从错误中恢复。

异常类有两个主要的子类:IOException 类和 RuntimeException 类。

在 Java 内置类中(接下来会说明),有大部分常用检查性和非检查性异常。


Java 内置异常类

Java 语言定义了一些异常类在 java.lang 标准包中。

标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。

Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。

异常描述
ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException当不支持请求的操作时,抛出该异常。

下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。

异常描述
ClassNotFoundException应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException拒绝访问一个类的时候,抛出该异常。
InstantiationException当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException请求的变量不存在
NoSuchMethodException请求的方法不存在

异常方法

下面的列表是 Throwable 类的主要方法:

序号方法及说明
1public String getMessage()
返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
2public Throwable getCause()
返回一个Throwable 对象代表异常原因。
3public String toString()
使用getMessage()的结果返回类的串级名字。
4public void printStackTrace()
打印toString()结果和栈层次到System.err,即错误输出流。
5public StackTraceElement [] getStackTrace()
返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
6public Throwable fillInStackTrace()
用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。

捕获异常

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。

try/catch 代码块中的代码称为保护代码,使用  try/catch 的语法如下:

try{   // 程序代码}catch(ExceptionName e1){   //Catch 块}

Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。

如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。

实例

下面的例子中声明有两个元素的一个数组,当代码试图访问数组的第三个元素的时候就会抛出一个异常。

// 文件名 : ExcepTest.javaimport java.io.*;public class ExcepTest{   public static void main(String args[]){      try{         int a[] = new int[2];         System.out.println("Access element three :" + a[3]);      }catch(ArrayIndexOutOfBoundsException e){         System.out.println("Exception thrown  :" + e);      }      System.out.println("Out of the block");   }}

以上代码编译运行输出结果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3Out of the block

多重捕获块

一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。

多重捕获块的语法如下所示:

 try{    // 程序代码 }catch(异常类型1 异常的变量名1){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }

上面的代码段包含了 3 个 catch 块。

可以在 try 语句后面添加任意数量的 catch 块。

如果保护代码中发生异常,异常被抛给第一个 catch 块。

如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。

如果不匹配,它会被传递给第二个 catch 块。

如此,直到异常被捕获或者通过所有的 catch 块。

实例

该实例展示了怎么使用多重 try/catch。

try{   file = new FileInputStream(fileName);   x = (byte) file.read();}catch(IOException i){   i.printStackTrace();   return -1;}catch(FileNotFoundException f) //Not valid!{   f.printStackTrace();   return -1;}

throws/throw关键字:

如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。

也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

下面方法的声明抛出一个 RemoteException 异常:

import java.io.*;public class className{   public void deposit(double amount) throws RemoteException   {      // Method implementation      throw new RemoteException();   }   //Remainder of class definition}

一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

例如,下面的方法声明抛出 RemoteException 和 InsufficientFundsException:

import java.io.*;public class className{   public void withdraw(double amount) throws RemoteException,                              InsufficientFundsException   {       // Method implementation   }   //Remainder of class definition}

finally 关键字

finally 关键字用来创建在 try 代码块后面执行的代码块。

无论是否发生异常,finally 代码块中的代码总会被执行。

在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

finally 代码块出现在 catch 代码块最后,语法如下:

 try{    // 程序代码 }catch(异常类型1 异常的变量名1){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }finally{    // 程序代码 }

实例

 public class ExcepTest{   public static void main(String args[]){      int a[] = new int[2];      try{         System.out.println("Access element three :" + a[3]);      }catch(ArrayIndexOutOfBoundsException e){         System.out.println("Exception thrown  :" + e);      }      finally{         a[0] = 6;         System.out.println("First element value: " +a[0]);         System.out.println("The finally statement is executed");      }   }}

以上实例编译运行结果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3First element value: 6The finally statement is executed

注意下面事项:

  • catch 不能独立于 try 存在。

  • 在 try/catch 后面添加 finally 块并非强制性要求的。

  • try 代码后不能既没 catch 块也没 finally 块。

  • try, catch, finally 块之间不能添加任何代码。


声明自定义异常

在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。

  • 所有异常都必须是 Throwable 的子类。

  • 如果希望写一个检查性异常类,则需要继承 Exception 类。

  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

可以像下面这样定义自己的异常类:

class MyException extends Exception{}

只继承 Exception 类来创建的异常类是检查性异常类。

下面的 InsufficientFundsException 类是用户定义的异常类,它继承自 Exception。

一个异常类和其它任何类一样,包含有变量和方法。

实例

// 文件名InsufficientFundsException.javaimport java.io.*;public class InsufficientFundsException extends Exception{   private double amount;   public InsufficientFundsException(double amount)   {      this.amount = amount;   }    public double getAmount()   {      return amount;   }}

为了展示如何使用我们自定义的异常类,

在下面的 CheckingAccount 类中包含一个 withdraw() 方法抛出一个 InsufficientFundsException 异常。

// 文件名称 CheckingAccount.javaimport java.io.*;public class CheckingAccount{   private double balance;   private int number;   public CheckingAccount(int number)   {      this.number = number;   }   public void deposit(double amount)   {      balance += amount;   }   public void withdraw(double amount) throws                              InsufficientFundsException   {      if(amount <= balance)       {          balance -= amount;       }       else       {          double needs = amount - balance;          throw new InsufficientFundsException(needs);       }    }    public double getBalance()    {       return balance;    }    public int getNumber()    {       return number;    } }

下面的 BankDemo 程序示范了如何调用 CheckingAccount 类的 deposit() 和 withdraw() 方法。

//文件名称 BankDemo.javapublic class BankDemo{   public static void main(String [] args)   {      CheckingAccount c = new CheckingAccount(101);      System.out.println("Depositing $500...");      c.deposit(500.00);      try      {         System.out.println("
Withdrawing $100...");         c.withdraw(100.00);         System.out.println("
Withdrawing $600...");         c.withdraw(600.00);      }catch(InsufficientFundsException e)      {         System.out.println("Sorry, but you are short $"                                  + e.getAmount());         e.printStackTrace();      }    }}

编译上面三个文件,并运行程序 BankDemo,得到结果如下所示:

Depositing $500...Withdrawing $100...Withdrawing $600...Sorry, but you are short $200.0InsufficientFundsException        at CheckingAccount.withdraw(CheckingAccount.java:25)        at BankDemo.main(BankDemo.java:13)

通用异常

在 Java 中定义了两种类型的异常和错误。

  • JVM(Java虚拟机) 异常:由 JVM 抛出的异常或错误。例如:NullPointerException类ArrayIndexOutOfBoundsException类ClassCastException类

  • 程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException类IllegalStateException类

学习完本教程,建议您进行实战练习来巩固您新学到的知识:点击进入实战


继承是所有 OOP 语言和 Java 语言不可缺少的组成部分。

继承是 Java 面向对象编程技术的一块基石,是面向对象的三大特征之一,也是实现软件复用的重要手段,继承可以理解为一个对象从另一个对象获取属性的过程。

如果类 A 是类 B 的父类,而类  B 是类 C 的父类,我们也称类 C 是 A 的子类,类 C 是从类 A 继承而来的。在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类。

继承中最常使用的两个关键字是 extends implements

这两个关键字的使用决定了一个对象和另一个对象是否是 IS-A (是一个)关系。

通过使用这两个关键字,我们能实现一个对象获取另一个对象的属性。

所有 Java 的类均是由 java.lang.Object 类继承而来的,所以 Object 是所有类的祖先类,而除了 Object 外,所有类必须有一个父类。

通过 extends 关键字可以申明一个类是继承另外一个类而来的,一般形式如下:

// A.javapublic class A {    private int i;    protected int j;     public void func() {     }} // B.javapublic class B extends A {    public int z;    public void fund(){    }    }

以上的代码片段说明,类 B 由类 A 继承而来的,类 B 是类 A 的子类。而类 A 是 Object 的子类,这里可以不显示地声明。

作为子类,类 B 的实例拥有类 A 所有的成员变量,但对于 private 类型的成员变量类 B 却没有访问权限,这保障了类 A 的封装性。


IS-A 关系

IS-A 就是说:一个对象是另一个对象的一个分类。

下面是使用关键字 extends 实现继承。

public class Animal{}public class Mammal extends Animal{}public class Reptile extends Animal{}public class Dog extends Mammal{}

基于上面的例子,以下说法是正确的:

  • Animal 类是 Mammal 类的父类。
  • Animal 类是 Reptile 类的父类。
  • Mammal 类和 Reptile 类是 Animal 类的子类。
  • Dog 类既是 Mammal 类的子类又是 Animal 类的子类。

分析以上示例中的 IS-A 关系,如下:

  • Mammal IS-A Animal
  • Reptile IS-A Animal
  • Dog IS-A Mammal

因此 : Dog IS-A Animal

通过使用关键字 extends ,子类可以继承父类的除 private 属性外所有的属性。

我们通过使用 instanceof 操作符,能够确定 Mammal IS-A Animal

实例

public class Dog extends Mammal{   public static void main(String args[]){      Animal a = new Animal();      Mammal m = new Mammal();      Dog d = new Dog();      System.out.println(m instanceof Animal);      System.out.println(d instanceof Mammal);      System.out.println(d instanceof Animal);   }}

以上实例编译运行结果如下:

truetruetrue

介绍完 extends 关键字之后,我们再来看下 implements 关键字是怎样使用来表示 IS-A 关系。

Implements 关键字在类继承接口的情况下使用, 这种情况不能使用关键字 extends 

实例

public interface Animal {}public class Mammal implements Animal{}public class Dog extends Mammal{}

instanceof 关键字

可以使用 instanceof 运算符来检验 Mammal 和 dog 对象是否是 Animal 类的一个实例。

interface Animal{}class Mammal implements Animal{}public class Dog extends Mammal{   public static void main(String args[]){      Mammal m = new Mammal();      Dog d = new Dog();      System.out.println(m instanceof Animal);      System.out.println(d instanceof Mammal);      System.out.println(d instanceof Animal);   }} 

以上实例编译运行结果如下:

truetruetrue

HAS-A 关系

HAS-A 代表类和它的成员之间的从属关系。这有助于代码的重用和减少代码的错误。

例子

public class Vehicle{}public class Speed{}public class Van extends Vehicle{	private Speed sp;} 

Van 类和 Speed 类是 HAS-A 关系( Van 有一个 Speed ),这样就不用将 Speed 类的全部代码粘贴到 Van 类中了,并且 Speed 类也可以重复利用于多个应用程序。

在面向对象特性中,用户不必担心类的内部怎样实现。

Van 类将实现的细节对用户隐藏起来,因此,用户只需要知道怎样调用 Van 类来完成某一功能,而不必知道 Van 类是自己来做还是调用其他类来做这些工作。

Java 只支持单继承,也就是说,一个类不能继承多个类。

下面的做法是不合法的:

public class extends Animal, Mammal{} 

Java 只支持单继承(继承基本类和抽象类),但是我们可以用接口来实现(多继承接口来实现),代码结构如下:

public class Apple extends Fruit implements Fruit1, Fruit2{}

一般我们继承基本类和抽象类用 extends 关键字,实现接口类的继承用 implements 关键字。


重写 (Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。

也就是说子类能够根据需要实现父类的方法。

在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:

class Animal{   public void move(){      System.out.println("动物可以移动");   }}class Dog extends Animal{   public void move(){      System.out.println("狗可以跑和走");   }}public class TestDog{   public static void main(String args[]){      Animal a = new Animal(); // Animal 对象      Animal b = new Dog(); // Dog 对象      a.move();// 执行 Animal 类的方法      b.move();//执行 Dog 类的方法   }}

以上实例编译运行结果如下:

动物可以移动狗可以跑和走

在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move 方法。

这是由于在编译阶段,只是检查参数的引用类型。

然而在运行时,Java 虚拟机 (JVM) 指定对象的类型并且运行该对象的方法。

因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。

思考以下例子:

class Animal{   public void move(){      System.out.println("动物可以移动");   }}class Dog extends Animal{   public void move(){      System.out.println("狗可以跑和走");   }   public void bark(){      System.out.println("狗可以吠叫");   }}public class TestDog{   public static void main(String args[]){      Animal a = new Animal(); // Animal 对象      Animal b = new Dog(); // Dog 对象      a.move();// 执行 Animal 类的方法      b.move();//执行 Dog 类的方法      a.bark();//执行 Animal 类的方法   }}

以上实例编译运行结果如下:

TestDog.java:30: cannot find symbolsymbol  : method bark()location: class Animal                a.bark();                 ^

该程序将抛出一个编译错误,因为 a 的引用类型 Animal 没有 bark 方法。


方法重写的规则

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 子类方法的访问权限必须大于或等于父类方法的访问权限。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个方法,则不能重写这个方法。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

Super 关键字的使用

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

class Animal{   public void move(){      System.out.println("动物可以移动");   }}class Dog extends Animal{   public void move(){      super.move(); // 应用super类的方法      System.out.println("狗可以跑和走");   }}public class TestDog{   public static void main(String args[]){      Animal b = new Dog(); //      b.move(); //执行 Dog类的方法   }}

以上实例编译运行结果如下:

动物可以移动狗可以跑和走

重载 (Overload)

重载 (overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

重载规则

  • 被重载的方法必须改变参数列表;
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

实例

public class Overloading { 	public int test(){		System.out.println("test1");		return 1;	} 	public void test(int a){		System.out.println("test2");	}	 	//以下两个参数类型顺序不同	public String test(int a,String s){		System.out.println("test3");		return "returntest3";	}	 	public String test(String s,int a){		System.out.println("test4");		return "returntest4";	}	 	public static void main(String[] args){		Overloading o = new Overloading();		System.out.println(o.test());		o.test(1);		System.out.println(o.test(1,"test3"));		System.out.println(o.test("test4",1));	}}

以上实例编译运行结果如下:

test11test2test3returntest3test4returntest4

重写与重载之间的区别

区别点重载方法重写方法
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)


总结

方法的重写 (Overriding) 和重载 (Overloading) 是 java 多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载 (Overloading)。
  • (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写 (Overriding)。
  • (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。



本章主要为大家介绍java多态的概念,以及便于理解的多态简单例子。

Java 多态


多态是同一个行为具有多个不同表现形式或形态的能力。

多态性是对象多种表现形式的体现。

比如我们说"宠物"这个对象,它就有很多不同的表达或实现,比如有小猫、小狗、蜥蜴等等。那么我到宠物店说"请给我一只宠物",服务员给我小猫、小狗或者蜥蜴都可以,我们就说"宠物"这个对象就具备多态性。

接下来让我们通过实例来了解Java的多态。

简单的例子

public interface Vegetarian{}public class Animal{}public class Deer extends Animal implements Vegetarian{}

因为Deer类具有多重继承,所以它具有多态性。以上实例解析如下:

  • 一个 Deer IS-A(是一个) Animal
  • 一个 Deer IS-A(是一个) Vegetarian
  • 一个 Deer IS-A(是一个) Deer
  • 一个 Deer IS-A(是一个)Object

在Java中,所有的对象都具有多态性,因为任何对象都能通过IS-A测试的类型和Object类。

访问一个对象的唯一方法就是通过引用型变量。

引用型变量只能有一种类型,一旦被声明,引用型变量的类型就不能被改变了。

引用型变量不仅能够被重置为其他对象,前提是这些对象没有被声明为final。还可以引用和它类型相同的或者相兼容的对象。它可以声明为类类型或者接口类型。

当我们将引用型变量应用于Deer对象的引用时,下面的声明是合法的:

Deer d = new Deer();Animal a = d;Vegetarian v = d;Object o = d;

所有的引用型变量d,a,v,o都指向堆中相同的Deer对象。


虚方法

我们将介绍在Java中,当设计类时,被重写的方法的行为怎样影响多态性。

我们已经讨论了方法的重写,也就是子类能够重写父类的方法。

当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。

要想调用父类中被重写的方法,则必须使用关键字super。

/* 文件名 : Employee.java */public class Employee{   private String name;   private String address;   private int number;   public Employee(String name, String address, int number)   {      System.out.println("Constructing an Employee");      this.name = name;      this.address = address;      this.number = number;   }   public void mailCheck()   {      System.out.println("Mailing a check to " + this.name       + " " + this.address);   }   public String toString()   {      return name + " " + address + " " + number;   }   public String getName()   {      return name;   }   public String getAddress()   {      return address;   }   public void setAddress(String newAddress)   {      address = newAddress;   }   public int getNumber()   {     return number;   }}

假设下面的类继承Employee类:

/* 文件名 : Salary.java */public class Salary extends Employee{   private double salary; //Annual salary   public Salary(String name, String address, int number, double      salary)   {       super(name, address, number);       setSalary(salary);   }   public void mailCheck()   {       System.out.println("Within mailCheck of Salary class ");       System.out.println("Mailing check to " + getName()       + " with salary " + salary);   }   public double getSalary()   {       return salary;   }   public void setSalary(double newSalary)   {       if(newSalary >= 0.0)       {          salary = newSalary;       }   }   public double computePay()   {      System.out.println("Computing salary pay for " + getName());      return salary/52;   }}

现在我们仔细阅读下面的代码,尝试给出它的输出结果:

/* 文件名 : VirtualDemo.java */public class VirtualDemo{   public static void main(String [] args)   {      Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);      Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);      System.out.println("Call mailCheck using Salary reference --");      s.mailCheck();      System.out.println("
 Call mailCheck using Employee reference--");      e.mailCheck();    }}

以上实例编译运行结果如下:

Constructing an EmployeeConstructing an EmployeeCall mailCheck using Salary reference --Within mailCheck of Salary classMailing check to Mohd Mohtashim with salary 3600.0Call mailCheck using Employee reference--Within mailCheck of Salary classMailing check to John Adams with salary 2400.0

例子中,我们实例化了两个Salary对象。一个使用Salary引用s,另一个使用Employee引用。

编译时,编译器检查到mailCheck()方法在Salary类中的声明。

在调用s.mailCheck()时,Java虚拟机(JVM)调用Salary类的mailCheck()方法。

因为e是Employee的引用,所以调用e的mailCheck()方法则有完全不同的结果。

当编译器检查e.mailCheck()方法时,编译器检查到Employee类中的mailCheck()方法。

在编译的时候,编译器使用Employee类中的mailCheck()方法验证该语句, 但是在运行的时候,Java虚拟机(JVM)调用的是Salary类中的mailCheck()方法。

该行为被称为虚拟方法调用,该方法被称为虚拟方法。

Java中所有的方法都能以这种方式表现,借此,重写的方法能在运行时调用,不管编译的时候源代码中引用变量是什么数据类型。

多态的实现方式

方式一:重写:

这个内容已经在上一章节详细讲过,就不再阐述,详细可访问:Java 重写(Override)与重载(Overload)

方式二:接口

  • 1. 生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。
  • 2. java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。

方式三:抽象类和抽象方法

详情请看 Java 抽象类 章节


在 Java 面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。


抽象类

在Java语言中使用abstract class来定义抽象类。如下实例:

/* 文件名 : Employee.java */public abstract class Employee{   private String name;   private String address;   private int number;   public Employee(String name, String address, int number)   {      System.out.println("Constructing an Employee");      this.name = name;      this.address = address;      this.number = number;   }   public double computePay()   {     System.out.println("Inside Employee computePay");     return 0.0;   }   public void mailCheck()   {      System.out.println("Mailing a check to " + this.name       + " " + this.address);   }   public String toString()   {      return name + " " + address + " " + number;   }   public String getName()   {      return name;   }   public String getAddress()   {      return address;   }   public void setAddress(String newAddress)   {      address = newAddress;   }   public int getNumber()   {     return number;   }}

注意到该Employee类没有什么不同,尽管该类是抽象类,但是它仍然有3个成员变量,7个成员方法和1个构造方法。 现在如果你尝试如下的例子:

/* 文件名 : AbstractDemo.java */public class AbstractDemo{   public static void main(String [] args)   {      /* 以下是不允许的,会引发错误 */      Employee e = new Employee("George W.", "Houston, TX", 43);      System.out.println("
 Call mailCheck using Employee reference--");      e.mailCheck();    }}

当你尝试编译AbstractDemo类时,会产生如下错误:

Employee.java:46: Employee is abstract; cannot be instantiated      Employee e = new Employee("George W.", "Houston, TX", 43);                   ^1 error

继承抽象类

我们能通过一般的方法继承Employee类:

/* 文件名 : Salary.java */public class Salary extends Employee{   private double salary; //Annual salary   public Salary(String name, String address, int number, double      salary)   {       super(name, address, number);       setSalary(salary);   }   public void mailCheck()   {       System.out.println("Within mailCheck of Salary class ");       System.out.println("Mailing check to " + getName()       + " with salary " + salary);   }   public double getSalary()   {       return salary;   }   public void setSalary(double newSalary)   {       if(newSalary >= 0.0)       {          salary = newSalary;       }   }   public double computePay()   {      System.out.println("Computing salary pay for " + getName());      return salary/52;   }}

尽管我们不能实例化一个Employee类的对象,但是如果我们实例化一个Salary类对象,该对象将从Employee类继承3个成员变量和7个成员方法。

/* 文件名 : AbstractDemo.java */public class AbstractDemo{   public static void main(String [] args)   {      Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);      Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);      System.out.println("Call mailCheck using Salary reference --");      s.mailCheck();      System.out.println("
 Call mailCheck using Employee reference--");      e.mailCheck();    }}

以上程序编译运行结果如下:

Constructing an EmployeeConstructing an EmployeeCall mailCheck using  Salary reference --Within mailCheck of Salary classMailing check to Mohd Mohtashim with salary 3600.0Call mailCheck using Employee reference--Within mailCheck of Salary classMailing check to John Adams with salary 2400.

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。

Abstract关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

public abstract class Employee{   private String name;   private String address;   private int number;      public abstract double computePay();      //其余代码}

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

如果Salary类继承了Employee类,那么它必须实现computePay()方法:

/* 文件名 : Salary.java */public class Salary extends Employee{   private double salary; // Annual salary     public double computePay()   {      System.out.println("Computing salary pay for " + getName());      return salary/52;   }   //其余代码}


在面向对象程式设计方法中,封装(英语:Encapsulation)是指,一种将抽象性函式接口的实作细节部份包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

实例

让我们来看一个java封装类的例子:

/* 文件名: EncapTest.java */public class EncapTest{   private String name;   private String idNum;   private int age;   public int getAge(){      return age;   }   public String getName(){      return name;   }   public String getIdNum(){      return idNum;   }   public void setAge( int newAge){      age = newAge;   }   public void setName(String newName){      name = newName;   }   public void setIdNum( String newId){      idNum = newId;   }}

以上实例中public方法是外部类访问该类成员变量的入口。

通常情况下,这些方法被称为getter和setter方法。

因此,任何要访问类中私有成员变量的类都要通过这些getter和setter方法。

通过如下的例子说明EncapTest类的变量怎样被访问:

/* F文件名 : RunEncap.java */public class RunEncap{   public static void main(String args[]){      EncapTest encap = new EncapTest();      encap.setName("James");      encap.setAge(20);      encap.setIdNum("12343ms");      System.out.print("Name : " + encap.getName()+                              " Age : "+ encap.getAge());    }}

以上代码编译运行结果如下:

Name : James Age : 20


接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在Java中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口与类相似点:

  • 一个接口可以有多个方法。
  • 接口文件保存在.java结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在.class结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了static和final变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多重继承。

接口的声明

接口的声明语法格式如下:

[可见度] interface 接口名称 [extends 其他的类名] {        // 声明变量        // 抽象方法}

Interface关键字用来声明一个接口。下面是接口声明的一个简单例子。

/* 文件名 : NameOfInterface.java */import java.lang.*;//引入包public interface NameOfInterface{   //任何类型 final, static 字段   //抽象方法}

接口有以下特性:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键子。
  • 接口中的方法都是公有的。

实例

/* 文件名 : Animal.java */interface Animal {   public void eat();   public void travel();}

接口的实现

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。

类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。

实现一个接口的语法,可以使用这个公式:

... implements 接口名称[, 其他接口, 其他接口..., ...] ...

实例

/* 文件名 : MammalInt.java */public class MammalInt implements Animal{   public void eat(){      System.out.println("Mammal eats");   }   public void travel(){      System.out.println("Mammal travels");   }    public int noOfLegs(){      return 0;   }   public static void main(String args[]){      MammalInt m = new MammalInt();      m.eat();      m.travel();   }} 

以上实例编译运行结果如下:

Mammal eatsMammal travels

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

下面的Sports接口被Hockey和Football接口继承:

// 文件名: Sports.javapublic interface Sports{   public void setHomeTeam(String name);   public void setVisitingTeam(String name);}// 文件名: Football.javapublic interface Football extends Sports{   public void homeTeamScored(int points);   public void visitingTeamScored(int points);   public void endOfQuarter(int quarter);}// 文件名: Hockey.javapublic interface Hockey extends Sports{   public void homeGoalScored();   public void visitingGoalScored();   public void endOfPeriod(int period);   public void overtimePeriod(int ot);}

Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。

相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。


接口的多重继承

在Java中,类的多重继承是不合法,但接口允许多重继承,。

在接口的多重继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

public interface Hockey extends Sports, Event

以上的程序片段是合法定义的子接口,与类不同的是,接口允许多重继承,而 Sports及 Event 可能定义或是继承相同的方法


标记接口

最常用的继承接口是没有包含任何方法的接口。

标识接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。

标识接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。

例如:java.awt.event包中的MouseListener接口继承的java.util.EventListener接口定义如下:

package java.util;public interface EventListener{}

没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:

  • 建立一个公共的父接口:

    正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。

  • 向一个类添加数据类型:

    这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。


为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。

包的作用

  • 1 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 2 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  • 3 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

包语句的语法格式为:

package pkg1[.pkg2[.pkg3…]];

例如,一个Something.java 文件它的内容

package net.java.utilpublic class Something{   ...}

那么它的路径应该是 net/java/util/Something.java 这样保存的。 package(包)的作用是把不同的java程序分类保存,更方便的被其他java程序调用。

一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。

以下是一些Java中的包:

  • java.lang-打包基础的类
  • java.io-包含输入输出功能的函数

开发者可以自己把一组类和接口等打包,并定义自己的package。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。

由于package创建了新的命名空间(namespace),所以不会跟其他package中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。


创建包

创建package的时候,你需要为这个package取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个package的声明放在这个源文件的开头。

包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。

如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。

例子

让我们来看一个例子,这个例子创建了一个叫做animals的包。通常使用小写的字母来命名避免与类、接口名字的冲突。

在animals包中加入一个接口(interface):

/* 文件名: Animal.java */package animals;interface Animal {   public void eat();   public void travel();}

接下来,在同一个包中加入该接口的实现:

package animals;/* 文件名 : MammalInt.java */public class MammalInt implements Animal{   public void eat(){      System.out.println("Mammal eats");   }   public void travel(){      System.out.println("Mammal travels");   }    public int noOfLegs(){      return 0;   }   public static void main(String args[]){      MammalInt m = new MammalInt();      m.eat();      m.travel();   }} 

然后,编译这两个文件,并把他们放在一个叫做animals的子目录中。 用下面的命令来运行:

$ mkdir animals$ cp Animal.class  MammalInt.class animals$ java animals/MammalIntMammal eatsMammal travel

import关键字

为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用"import"语句可完成此功能。

在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:

import package1[.package2…].(classname|*);

如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

例子

下面的payroll包已经包含了Employee类,接下来向payroll包中添加一个Boss类。Boss类引用Employee类的时候可以不用使用payroll前缀,Boss类的实例如下。

package payroll;public class Boss{   public void payEmployee(Employee e)   {      e.mailCheck();   }}

如果Boss类不在payroll包中又会怎样?Boss类必须使用下面几种方法之一来引用其他包中的类

使用类全名描述,例如:

payroll.Employee

用import关键字引入,使用通配符"*"

import payroll.*;

使用import关键字引入Employee类

import payroll.Employee;

注意:

类文件中可以包含任意数量的import声明。import声明必须在包声明之后,类声明之前。


package的目录结构

类放在包中会有两种主要的结果:

  • 包名成为类名的一部分,正如我们前面讨论的一样。
  • 包名必须与相应的字节码所在的目录结构相吻合。

下面是管理你自己java中文件的一种简单方式:

将类、接口等类型的源码放在一个文件中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:

// 文件名 :  Car.javapackage vehicle;public class Car {   // 类实现  }

接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。

....vehicleCar.java

现在,正确的类名和路径将会是如下样子:

  • 类名 -> vehicle.Car

  • 路径名 -> vehicleCar.java (in windows)

通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是apple.com,所有的包名都以com.apple开头。包名中的每一个部分对应一个子目录。

例如:这个公司有一个com.apple.computers的包,这个包包含一个叫做Dell.java的源文件,那么相应的,应该有如下面的一连串子目录:

....comapplecomputersDell.java

编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上.class作为扩展后缀。 例如:

// 文件名: Dell.javapackage com.apple.computers;public class Dell{      }class Ups{      }

现在,我们用-d选项来编译这个文件,如下:

$javac -d . Dell.java

这样会像下面这样放置编译了的文件:

.comapplecomputersDell.class.comapplecomputersUps.class

你可以像下面这样来导入所有 comapplecomputers中定义的类、接口等:

import com.apple.computers.*;

编译之后的.class文件应该和.java源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求.class文件的路径跟相应的.java的路径一样。你可以分开来安排源码和类的目录。

<path-one>sourcescomapplecomputersDell.java<path-two>classescomapplecomputersDell.class

这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java虚拟机(JVM)可以找到你程序中使用的所有类型。

类目录的绝对路径叫做class path。设置在系统变量CLASSPATH中。编译器和java虚拟机通过将package名字加到class path后来构造.class文件的路径。

<path- two>classes是class path,package名字是com.apple.computers,而编译器和JVM会在 <path-two>classescomapplecompters中找.class文件。

一个class path可能会包含好几个路径。多路径应该用分隔符分开。默认情况下,编译器和JVM查找当前目录。JAR文件按包含Java平台相关的类,所以他们的目录默认放在了class path中。


设置CLASSPATH系统变量

用下面的命令显示当前的CLASSPATH变量:

  • Windows平台(DOS 命令行下)-> C:> set CLASSPATH
  • UNIX平台(Bourne shell下)-> % echo $CLASSPATH

删除当前CLASSPATH变量内容:


  • Windows平台(DOS 命令行下)-> C:> set CLASSPATH=
  • UNIX平台(Bourne shell下)-> % unset CLASSPATH; export CLASSPATH

设置CLASSPATH变量:

  • Windows平台(DOS 命令行下)-> set CLASSPATH=C:usersjackjavaclasses
  • UNIX平台(Bourne shell下)-> % CLASSPATH=/home/jack/java/classes; export CLASSPATH


Java 数据结构

Java工具包提供了强大的数据结构。在Java中的数据结构主要包括以下几种接口和类:

  • 枚举(Enumeration)
  • 位集合(BitSet)
  • 向量(Vector)
  • 栈(Stack)
  • 字典(Dictionary)
  • 哈希表(Hashtable)
  • 属性(Properties)

以上这些类是传统遗留的,在Java2中引入了一种新的框架-集合框架(Collection),我们后面再讨论。


枚举(Enumeration)

枚举(Enumeration)接口虽然它本身不属于数据结构,但它在其他数据结构的范畴里应用很广。 枚举(The Enumeration)接口定义了一种从数据结构中取回连续元素的方式。

例如,枚举定义了一个叫nextElement 的方法,该方法用来得到一个包含多元素的数据结构的下一个元素。

关于枚举接口的更多信息,请参见枚举(Enumeration)


位集合(BitSet)

位集合类实现了一组可以单独设置和清除的位或标志。

该类在处理一组布尔值的时候非常有用,你只需要给每个值赋值一"位",然后对位进行适当的设置或清除,就可以对布尔值进行操作了。

关于该类的更多信息,请参见位集合(BitSet)


向量(Vector)

向量(Vector)类和传统数组非常相似,但是Vector的大小能根据需要动态的变化。

和数组一样,Vector对象的元素也能通过索引访问。

使用Vector类最主要的好处就是在创建对象的时候不必给对象指定大小,它的大小会根据需要动态的变化。

关于该类的更多信息,请参见向量(Vector)


栈(Stack)

栈(Stack)实现了一个后进先出(LIFO)的数据结构。

你可以把栈理解为对象的垂直分布的栈,当你添加一个新元素时,就将新元素放在其他元素的顶部。

当你从栈中取元素的时候,就从栈顶取一个元素。换句话说,最后进栈的元素最先被取出。

关于该类的更多信息,请参见栈(Stack)


字典(Dictionary)

字典(Dictionary) 类是一个抽象类,它定义了键映射到值的数据结构。

当你想要通过特定的键而不是整数索引来访问数据的时候,这时候应该使用Dictionary。

由于Dictionary类是抽象类,所以它只提供了键映射到值的数据结构,而没有提供特定的实现。

关于该类的更多信息,请参见字典( Dictionary)


哈希表(Hashtable)

Hashtable类提供了一种在用户定义键结构的基础上来组织数据的手段。

例如,在地址列表的哈希表中,你可以根据邮政编码作为键来存储和排序数据,而不是通过人的名字。

哈希表键的具体含义完全取决于哈希表的使用情景和它包含的数据。

关于该类的更多信息,请参见哈希表(HashTable)


属性(Properties)

Properties 继承于 Hashtable.Properties 类表示了一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。

Properties 类被许多Java类使用。例如,在获取环境变量时它就作为System.getProperties()方法的返回值。

关于该类的更多信息,请参见属性(Properties)

早在 Java 2 中之前,Java 就提供了特设类。比如:Dictionary, Vector, Stack, 和 Properties 这些类用来存储和操作对象组。

虽然这些类都非常有用,但是它们缺少一个核心的,统一的主题。由于这个原因,使用 Vector 类的方式和使用 Properties 类的方式有着很大不同。

集合框架被设计成要满足以下几个目标。

  • 该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。
  • 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
  • 对一个集合的扩展和适应必须是简单的。

为此,整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现,诸如: LinkedList, HashSet, 和 TreeSet 等,除此之外你也可以通过这些接口实现自己的集合。

简化图:

1

说明:对于以上的框架图有如下几点说明

  1. 所有集合类都位于 java.util 包下。Java的集合类主要由两个接口派生而出:Collection 和 Map,Collection 和 Map 是 Java 集合框架的根接口,这两个接口又包含了一些子接口或实现类。
  2. 集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。
  3. 抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。
  4. 实现类:8个实现类(实线表示),对接口的具体实现。
  5. Collection 接口是一组允许重复的对象。
  6. Set 接口继承 Collection,集合元素不重复。
  7. List 接口继承 Collection,允许重复,维护元素插入顺序。
  8. Map接口是键-值对象,与Collection接口没有什么关系。
  9. Set、List 和 Map 可以看做集合的三大类:
    List 集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
    Set 集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
    Map 集合中保存 Key-value 对形式的元素,访问时只能根据每项元素的 key 来访问其 value。

集合框架图如图所示:


Java 集合框架提供了一套性能优良,使用方便的接口和类,java 集合框架位于 java.util 包中, 所以当使用集合框架的时候需要进行导包。


集合接口

集合框架定义了一些接口。本节提供了每个接口的概述:

序号接口描述
1Collection 接口
允许你使用一组对象,是Collection层次结构的根接口。
2List 接口
继承于Collection和一个 List实例存储一个有序集合的元素。
3Set
继承于 Collection,是一个不包含重复元素的集合。
4SortedSet
继承于Set保存有序的集合。
5Map
将唯一的键映射到值。
6Map.Entry
描述在一个Map中的一个元素(键/值对)。是一个Map的内部类。
7SortedMap
继承于Map,使Key保持在升序排列。
8Enumeration
这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。

集合类

Java 提供了一套实现了 Collection 接口的标准集合类。其中一些是具体类,这些类可以直接拿来使用,而另外一些是抽象类,提供了接口的部分实现。

标准集合类汇总于下表:

序号类描述
1AbstractCollection 
实现了大部分的集合接口。
2AbstractList 
继承于 AbstractCollection 并且实现了大部分List接口。
3AbstractSequentialList 
继承于  AbstractList ,提供了对数据元素的链式访问而不是随机访问。
4LinkedList
继承于 AbstractSequentialList,实现了一个链表。
5ArrayList
通过继承 AbstractList,实现动态数组。
6AbstractSet 
继承于 AbstractCollection 并且实现了大部分Set接口。
7HashSet
继承了 AbstractSet,并且使用一个哈希表。
8LinkedHashSet
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。
9TreeSet
继承于AbstractSet,使用元素的自然顺序对元素进行排序.
10AbstractMap 
实现了大部分的 Map 接口。
11HashMap
继承了 HashMap,并且使用一个哈希表。
12TreeMap
继承了 AbstractMap,并且使用一颗树。
13WeakHashMap
继承 AbstractMap类,使用弱密钥的哈希表。
14LinkedHashMap
继承于 HashMap,使用元素的自然顺序对元素进行排序.
15IdentityHashMap
继承 AbstractMap 类,比较文档时使用引用相等。

在前面的教程中已经讨论通过 java.util 包中定义的类,如下所示:

序号类描述
1Vector
Vector 类实现了一个动态数组。和 ArrayList 和相似,但是两者是不同的。
2Stack
栈是 Vector 的一个子类,它实现了一个标准的后进先出的栈。
3Dictionary
Dictionary 类是一个抽象类,用来存储键/值对,作用和 Map 类相似。
4Hashtable
Hashtable 是原始的 java.util 的一部分, 是一个 Dictionary 具体的实现 。
5Properties
Properties 继承于 Hashtable.表示一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。
6BitSet
一个 Bitset 类创建一种特殊类型的数组来保存位值。BitSet 中数组大小会随需要增加。

一个 Bitset 类创建一种特殊类型的数组来保存位值。BitSet 中数组大小会随需要增加。


集合算法

集合框架定义了几种算法,可用于集合和映射。这些算法被定义为集合类的静态方法。

在尝试比较不兼容的类型时,一些方法能够抛出 ​ClassCastException​异常。当试图修改一个不可修改的集合时,抛出​UnsupportedOperationException​异常。

集合定义三个静态的变量:EMPTY_SET EMPTY_LIST,EMPTY_MAP 的。这些变量都不可改变。

序号算法描述
1Collection Algorithms
这里是一个列表中的所有算法实现。

如何使用迭代器

通常情况下,你会希望遍历一个集合中的元素。例如,显示集合中的每个元素。

做到这一点最简单的方法是采用一个迭代器,它是一个对象,实现了 Iterator 接口或 ListIterator 接口。

迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了 Iterator,以允许双向遍历列表和修改元素。

这里通过实例列出 Iterator 和 listIterator 接口提供的所有方法。


概述

泛型在 java 中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。

什么是泛型?为什么要使用泛型?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),

然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,

操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

特性

泛型只在编译阶段有效。看下面的代码:

List<String> stringArrayList = new ArrayList<String>();List<Integer> integerArrayList = new ArrayList<Integer>();Class classStringArrayList = stringArrayList.getClass();Class classIntegerArrayList = integerArrayList.getClass();if(classStringArrayList.equals(classIntegerArrayList)){    Log.d("泛型测试","类型相同");}

输出结果:D/ 泛型测试: 类型相同。

通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息输出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同的基本类型。


泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int,double,char 的等)。

实例

下面的例子演示了如何使用泛型方法打印不同字符串的元素:

public class GenericMethodTest{   // 泛型方法 printArray                            public static < E > void printArray( E[] inputArray )   {      // 输出数组元素                     for ( E element : inputArray ){                    System.out.printf( "%s ", element );         }         System.out.println();    }    public static void main( String args[] )    {        // 创建不同类型数组: Integer, Double 和 Character        Integer[] intArray = { 1, 2, 3, 4, 5 };        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };        System.out.println( "Array integerArray contains:" );        printArray( intArray  ); // 传递一个整型数组        System.out.println( "
Array doubleArray contains:" );        printArray( doubleArray ); // 传递一个双精度型数组        System.out.println( "
Array characterArray contains:" );        printArray( charArray ); // 传递一个字符型型数组    } }

编译以上代码,运行结果如下所示:

Array integerArray contains:1 2 3 4 5 6 Array doubleArray contains:1.1 2.2 3.3 4.4 Array characterArray contains:H E L L O

有界的类型参数:

可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受 Number 或者 Number 子类的实例。这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟 extends 关键字,最后紧跟它的上界。

实例

下面的例子演示了 "extends" 如何使用在一般意义上的意思 "extends"(类)或者"implements"(接口)。该例子中的泛型方法返回三个可比较对象的最大值。

public class MaximumTest{   // 比较三个值并返回最大值   public static <T extends Comparable<T>> T maximum(T x, T y, T z)   {                           T max = x; // 假设x是初始最大值      if ( y.compareTo( max ) > 0 ){         max = y; //y 更大      }      if ( z.compareTo( max ) > 0 ){         max = z; // 现在 z 更大                 }      return max; // 返回最大对象   }   public static void main( String args[] )   {      System.out.printf( "Max of %d, %d and %d is %d

",                   3, 4, 5, maximum( 3, 4, 5 ) );       System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f

",                   6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );       System.out.printf( "Max of %s, %s and %s is %s
","pear",         "apple", "orange", maximum( "pear", "apple", "orange" ) );   }}

编译以上代码,运行结果如下所示:

Maximum of 3, 4 and 5 is 5 Maximum of 6.6, 8.8 and 7.7 is 8.8 Maximum of pear, apple and orange is pear

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

实例

如下实例演示了我们如何定义一个泛型类:

public class Box<T> {   private T t;   public void add(T t) {    this.t = t;  }   public T get() {    return t;  }   public static void main(String[] args) {     Box<Integer> integerBox = new Box<Integer>();     Box<String> stringBox = new Box<String>();        integerBox.add(new Integer(10));     stringBox.add(new String("Hello World"));      System.out.printf("Integer Value :%d

", integerBox.get());     System.out.printf("String Value :%s
", stringBox.get());  }}

编译以上代码,运行结果如下所示:

Integer Value :10 String Value :Hello World


Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。

整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

类ObjectInputStream 和ObjectOutputStream是高层次的数据流,它们包含序列化和反序列化对象的方法。

ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:

public final void writeObject(Object x) throws IOException

上面的方法序列化一个对象,并将它发送到输出流。相似的ObjectInputStream 类包含如下反序列化一个对象的方法:

public final Object readObject() throws IOException,                                  ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。

为了演示序列化在Java中是怎样工作的,我将使用之前教程中提到的Employee类,假设我们定义了如下的Employee类,该类实现了Serializable 接口。

public class Employee implements java.io.Serializable{   public String name;   public String address;   public transient int SSN;   public int number;   public void mailCheck()   {      System.out.println("Mailing a check to " + name                           + " " + address);   }}

请注意,一个类的对象要想序列化成功,必须满足两个条件:

该类必须实现 java.io.Serializable 对象。

该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。

如果你想知道一个Java标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现java.io.Serializable接口。


序列化对象

ObjectOutputStream 类用来序列化一个对象,如下的SerializeDemo例子实例化了一个Employee对象,并将该对象序列化到一个文件中。

该程序执行后,就创建了一个名为employee.ser文件。该程序没有任何输出,但是你可以通过代码研读来理解程序的作用。

注意: 当序列化一个对象到文件时, 按照Java的标准约定是给文件一个.ser扩展名。

import java.io.*;public class SerializeDemo{   public static void main(String [] args)   {      Employee e = new Employee();      e.name = "Reyan Ali";      e.address = "Phokka Kuan, Ambehta Peer";      e.SSN = 11122333;      e.number = 101;      try      {         FileOutputStream fileOut =         new FileOutputStream("/tmp/employee.ser");         ObjectOutputStream out = new ObjectOutputStream(fileOut);         out.writeObject(e);         out.close();         fileOut.close();         System.out.printf("Serialized data is saved in /tmp/employee.ser");      }catch(IOException i)      {          i.printStackTrace();      }   }}

反序列化对象

下面的DeserializeDemo程序实例了反序列化,/tmp/employee.ser存储了Employee对象。

import java.io.*;public class DeserializeDemo{   public static void main(String [] args)   {      Employee e = null;      try      {         FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");         ObjectInputStream in = new ObjectInputStream(fileIn);         e = (Employee) in.readObject();         in.close();         fileIn.close();      }catch(IOException i)      {         i.printStackTrace();         return;      }catch(ClassNotFoundException c)      {         System.out.println("Employee class not found");         c.printStackTrace();         return;      }      System.out.println("Deserialized Employee...");      System.out.println("Name: " + e.name);      System.out.println("Address: " + e.address);      System.out.println("SSN: " + e.SSN);      System.out.println("Number: " + e.number);    }}

以上程序编译运行结果如下所示:

Deserialized Employee...Name: Reyan AliAddress:Phokka Kuan, Ambehta PeerSSN: 0Number:101

这里要注意以下要点:

readObject() 方法中的try/catch代码块尝试捕获 ClassNotFoundException异常。对于JVM可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException异常。

注意,readObject()方法的返回值被转化成Employee引用。

当对象被序列化时,属性SSN的值为111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后Employee对象的SSN属性为0。


网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

java.net包中J2SE的API包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。

java.net包中提供了两种常见的网络协议的支持:

  • TCP: TCP是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称TCP / IP。
  • UDP:UDP是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。

本教程主要讲解以下两个主题。

  • Socket 编程: 这是使用最广泛的网络概念,它已被解释地非常详细
  • URL 处理: 这部分会在另外的篇幅里讲,点击这里更详细地了解在Java语言中的URL处理

Socket 编程

套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。

当连接建立时,服务器会创建一个Socket对象。客户端和服务器现在可以通过对Socket对象的写入和读取来进行通信。

java.net.Socket类代表一个套接字,并且java.net.ServerSocket类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

  • 服务器实例化一个ServerSocket对象,表示通过服务器上的端口通信。
  • 服务器调用 ServerSocket类 的accept()方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
  • 服务器正在等待时,一个客户端实例化一个Socket对象,指定服务器名称和端口号来请求连接。
  • Socket类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个Socket对象能够与服务器进行通信。
  • 在服务器端,accept()方法返回服务器上一个新的socket引用,该socket连接到客户端的socket。

连接建立后,通过使用I/O流在进行通信。每一个socket都有一个输出流和一个输入流。客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

TCP是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现sockets。


ServerSocket 类的方法

服务器应用程序通过使用java.net.ServerSocket类以获取一个端口,并且侦听客户端请求。

ServerSocket类有四个构造方法:

序号 方法描述
1 public ServerSocket(int port) throws IOException
创建绑定到特定端口的服务器套接字。
2 public ServerSocket(int port, int backlog) throws IOException
利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
3 public ServerSocket(int port, int backlog, InetAddress address) throws IOException
使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
4 public ServerSocket() throws IOException
创建非绑定服务器套接字。

创建非绑定服务器套接字。 如果ServerSocket构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。

这里有一些ServerSocket类的常用方法:

序号 方法描述
1 public int getLocalPort()
  返回此套接字在其上侦听的端口。
2 public Socket accept() throws IOException
侦听并接受到此套接字的连接。
3 public void setSoTimeout(int timeout)
 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
4 public void bind(SocketAddress host, int backlog)
将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

Socket 类的方法

java.net.Socket类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个Socket对象通过实例化 ,而 服务器获得一个Socket对象则通过accept()方法的返回值。

Socket类有五个构造方法.

序号 方法描述
1 public Socket(String host, int port) throws UnknownHostException, IOException.
创建一个流套接字并将其连接到指定主机上的指定端口号。
2 public Socket(InetAddress host, int port) throws IOException
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
3 public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程主机上的指定远程端口。
4 public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程地址上的指定远程端口。
5 public Socket()
通过系统默认类型的 SocketImpl 创建未连接套接字

当Socket构造方法返回,并没有简单的实例化了一个Socket对象,它实际上会尝试连接到指定的服务器和端口。

下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个Socket对象,所以无论客户端还是服务端都能够调用这些方法。

序号 方法描述
1 public void connect(SocketAddress host, int timeout) throws IOException
将此套接字连接到服务器,并指定一个超时值。
2 public InetAddress getInetAddress()
 返回套接字连接的地址。
3 public int getPort()
返回此套接字连接到的远程端口。
4 public int getLocalPort()
返回此套接字绑定到的本地端口。
5 public SocketAddress getRemoteSocketAddress()
返回此套接字连接的端点的地址,如果未连接则返回 null。
6 public InputStream getInputStream() throws IOException
返回此套接字的输入流。
7 public OutputStream getOutputStream() throws IOException
返回此套接字的输出流。
8 public void close() throws IOException
关闭此套接字。

InetAddress 类的方法

这个类表示互联网协议(IP)地址。下面列出了Socket编程时比较有用的方法:

序号 方法描述
1 static InetAddress getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。
2 static InetAddress getByAddress(String host, byte[] addr)
根据提供的主机名和 IP 地址创建 InetAddress。
3 static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
4 String getHostAddress() 
返回 IP 地址字符串(以文本表现形式)。
5 String getHostName() 
 获取此 IP 地址的主机名。
6 static InetAddress getLocalHost()
返回本地主机。
7 String toString()
将此 IP 地址转换为 String。

Socket 客户端实例

如下的GreetingClient 是一个客户端程序,该程序通过socket连接到服务器并发送一个请求,然后等待一个响应。

// 文件名 GreetingClient.javaimport java.net.*;import java.io.*; public class GreetingClient{   public static void main(String [] args)   {      String serverName = args[0];      int port = Integer.parseInt(args[1]);      try      {         System.out.println("Connecting to " + serverName                             + " on port " + port);         Socket client = new Socket(serverName, port);         System.out.println("Just connected to "                      + client.getRemoteSocketAddress());         OutputStream outToServer = client.getOutputStream();         DataOutputStream out =                       new DataOutputStream(outToServer);          out.writeUTF("Hello from "                      + client.getLocalSocketAddress());         InputStream inFromServer = client.getInputStream();         DataInputStream in =                        new DataInputStream(inFromServer);         System.out.println("Server says " + in.readUTF());         client.close();      }catch(IOException e)      {         e.printStackTrace();      }   }}

Socket 服务端实例

如下的GreetingServer 程序是一个服务器端应用程序,使用Socket来监听一个指定的端口。

// 文件名 GreetingServer.javaimport java.net.*;import java.io.*;public class GreetingServer extends Thread{   private ServerSocket serverSocket;      public GreetingServer(int port) throws IOException   {      serverSocket = new ServerSocket(port);      serverSocket.setSoTimeout(10000);   }   public void run()   {      while(true)      {         try         {            System.out.println("Waiting for client on port " +            serverSocket.getLocalPort() + "...");            Socket server = serverSocket.accept();            System.out.println("Just connected to "                  + server.getRemoteSocketAddress());            DataInputStream in =                  new DataInputStream(server.getInputStream());            System.out.println(in.readUTF());            DataOutputStream out =                 new DataOutputStream(server.getOutputStream());            out.writeUTF("Thank you for connecting to "              + server.getLocalSocketAddress() + "
Goodbye!");            server.close();         }catch(SocketTimeoutException s)         {            System.out.println("Socket timed out!");            break;         }catch(IOException e)         {            e.printStackTrace();            break;         }      }   }   public static void main(String [] args)   {      int port = Integer.parseInt(args[0]);      try      {         Thread t = new GreetingServer(port);         t.start();      }catch(IOException e)      {         e.printStackTrace();      }   }}

编译以上 java 代码,并执行以下命令来启动服务,使用端口号为 6066:

$ java GreetingServer 6066Waiting for client on port 6066...

像下面一样开启客户端:

$ java GreetingClient localhost 6066Connecting to localhost on port 6066Just connected to localhost/127.0.0.1:6066Server says Thank you for connecting to /127.0.0.1:6066Goodbye!


Java 发送邮件

使用Java应用程序发送E-mail十分简单,但是首先你应该在你的机器上安装JavaMail API 和Java Activation Framework (JAF) 。

你可以在 JavaMail (Version 1.2) 下载最新的版本。

你可以再 在JAF (Version 1.1.1)下载最新的版本。

下载并解压这些文件,最上层文件夹你会发现很多的jar文件。你需要将mail.jar和activation.jar 添加到你的CLASSPATH中。

如果你使用第三方邮件服务器如QQ的SMTP服务器,可查看文章底部用户认证完整的实例。


发送一封简单的 E-mail

下面是一个发送简单E-mail的例子。假设你的localhost已经连接到网络。

// 文件名 SendEmail.java import java.util.*;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*; public class SendEmail{   public static void main(String [] args)   {         // 收件人电子邮箱      String to = "abcd@gmail.com";       // 发件人电子邮箱      String from = "web@gmail.com";       // 指定发送邮件的主机为 localhost      String host = "localhost";       // 获取系统属性      Properties properties = System.getProperties();       // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);       // 获取默认session对象      Session session = Session.getDefaultInstance(properties);       try{         // 创建默认的 MimeMessage 对象         MimeMessage message = new MimeMessage(session);          // Set From: 头部头字段         message.setFrom(new InternetAddress(from));          // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));          // Set Subject: 头部头字段         message.setSubject("This is the Subject Line!");          // 设置消息体         message.setText("This is actual message");          // 发送消息         Transport.send(message);         System.out.println("Sent message successfully....");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

编译并运行这个程序来发送一封简单的E-mail:

$ java SendEmailSent message successfully....

如果你想发送一封e-mail给多个收件人,那么使用下面的方法来指定多个收件人ID:

void addRecipients(Message.RecipientType type,                   Address[] addresses)throws MessagingException

下面是对于参数的描述:

  • type:要被设置为TO, CC 或者BCC. 这里CC 代表抄送、BCC 代表秘密抄送y. 举例:Message.RecipientType.TO

  • addresses: 这是email ID的数组。在指定电子邮件ID时,你将需要使用InternetAddress()方法。


发送一封HTML E-mail

下面是一个发送HTML E-mail的例子。假设你的localhost已经连接到网络。

和上一个例子很相似,除了我们要使用setContent()方法来通过第二个参数为"text/html",来设置内容来指定要发送HTML内容。

// 文件名 SendHTMLEmail.java import java.util.*;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*; public class SendHTMLEmail{   public static void main(String [] args)   {           // 收件人电子邮箱      String to = "abcd@gmail.com";       // 发件人电子邮箱      String from = "web@gmail.com";       // 指定发送邮件的主机为 localhost      String host = "localhost";       // 获取系统属性      Properties properties = System.getProperties();       // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);       // 获取默认的 Session 对象。      Session session = Session.getDefaultInstance(properties);       try{         // 创建默认的 MimeMessage 对象。         MimeMessage message = new MimeMessage(session);          // Set From: 头部头字段         message.setFrom(new InternetAddress(from));          // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));          // Set Subject: 头字段         message.setSubject("This is the Subject Line!");          // 发送 HTML 消息, 可以插入html标签         message.setContent("<h1>This is actual message</h1>",                            "text/html" );          // 发送消息         Transport.send(message);         System.out.println("Sent message successfully....");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

编译并运行此程序来发送HTML e-mail:

$ java SendHTMLEmailSent message successfully....

发送带有附件的 E-mail

下面是一个发送带有附件的 E-mail的例子。假设你的localhost已经连接到网络。

// 文件名 SendFileEmail.java import java.util.*;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*; public class SendFileEmail{   public static void main(String [] args)   {           // 收件人电子邮箱      String to = "abcd@gmail.com";       // 发件人电子邮箱      String from = "web@gmail.com";       // 指定发送邮件的主机为 localhost      String host = "localhost";       // 获取系统属性      Properties properties = System.getProperties();       // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);       // 获取默认的 Session 对象。      Session session = Session.getDefaultInstance(properties);       try{         // 创建默认的 MimeMessage 对象。         MimeMessage message = new MimeMessage(session);          // Set From: 头部头字段         message.setFrom(new InternetAddress(from));          // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));          // Set Subject: 头字段         message.setSubject("This is the Subject Line!");          // 创建消息部分         BodyPart messageBodyPart = new MimeBodyPart();          // 消息         messageBodyPart.setText("This is message body");                 // 创建多重消息         Multipart multipart = new MimeMultipart();          // 设置文本消息部分         multipart.addBodyPart(messageBodyPart);          // 附件部分         messageBodyPart = new MimeBodyPart();         String filename = "file.txt";         DataSource source = new FileDataSource(filename);         messageBodyPart.setDataHandler(new DataHandler(source));         messageBodyPart.setFileName(filename);         multipart.addBodyPart(messageBodyPart);          // 发送完整消息         message.setContent(multipart );          //   发送消息         Transport.send(message);         System.out.println("Sent message successfully....");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

编译并运行你的程序来发送一封带有附件的邮件。

$ java SendFileEmailSent message successfully....

用户认证部分

如果需要提供用户名和密码给e-mail服务器来达到用户认证的目的,你可以通过如下设置来完成:

 props.put("mail.smtp.auth", "true"); props.setProperty("mail.user", "myuser"); props.setProperty("mail.password", "mypwd");

e-mail其他的发送机制和上述保持一致。

需要用户名密码验证邮件发送实例:

本实例以QQ邮件服务器为例,你需要在登录QQ邮箱后台在"设置"=》账号中开启POP3/SMTP服务 ,如下图所示:

qqmailset

Java 代码如下:

// 需要用户名密码邮件发送实例//文件名 SendEmail2.java//本实例以QQ邮箱为例,你需要在qq后台设置import java.util.Properties;import javax.mail.Authenticator;import javax.mail.Message;import javax.mail.MessagingException;import javax.mail.PasswordAuthentication;import javax.mail.Session;import javax.mail.Transport;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeMessage;public class SendEmail2{   public static void main(String [] args)   {      // 收件人电子邮箱      String to = "xxx@qq.com";      // 发件人电子邮箱      String from = "xxx@qq.com";      // 指定发送邮件的主机为 localhost      String host = "smtp.qq.com";  //QQ 邮件服务器      // 获取系统属性      Properties properties = System.getProperties();      // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);      properties.put("mail.smtp.auth", "true");      // 获取默认session对象      Session session = Session.getDefaultInstance(properties,new Authenticator(){	    public PasswordAuthentication getPasswordAuthentication(){	     return new PasswordAuthentication("xxx@qq.com", "qq邮箱密码"); //发件人邮件用户名、密码	    }	   });      try{         // 创建默认的 MimeMessage 对象         MimeMessage message = new MimeMessage(session);         // Set From: 头部头字段         message.setFrom(new InternetAddress(from));         // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));         // Set Subject: 头部头字段         message.setSubject("This is the Subject Line!");         // 设置消息体         message.setText("This is actual message");         // 发送消息         Transport.send(message);         System.out.println("Sent message successfully....from 51coolma.cn");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

Java 给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。

多线程是多任务的一种特别的形式。多线程比多任务需要更小的开销。

这里定义和线程相关的另一个术语:进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守候线程都结束运行后才能结束。

多线程能满足程序员编写非常有效率的程序来达到充分利用 CPU 的目的,因为 CPU 的空闲时间能够保持在最低限度。


一个线程的生命周期

线程经过其生命周期的各个阶段。下图显示了一个线程完整的生命周期。

线程 

  • 新建(new Thread)

    当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
    例如:Thread  t1=new Thread();
  • 就绪(runnable)
    线程已经被启动,正在等待被分配给 CPU 时间片,也就是说此时线程正在就绪队列中排队等候得到 CPU 资源。
    例如:t1.start();

  • 运行(running)
    线程获得 CPU 资源正在执行任务( run() 方法),此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入,线程将一直运行到结束。

  • 堵塞(blocked)由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

    正在睡眠:用 sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

    正在等待:调用 wait() 方法。(调用 motify() 方法回到就绪状态)

    被另一个线程所阻塞:调用 suspend() 方法。(调用 resume() 方法恢复)

  • 死亡(dead)当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

    自然终止:正常运行 run() 方法后终止

    异常终止:调用 stop() 方法让一个线程终止运行


线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 优先级在 MIN_PRIORITY(1)和 MAX_PRIORITY(10)之间的范围内。默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器时间。然而,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。


创建一个线程

Java 提供了三种创建线程方法:

  • 通过实现 Runnable 接口;

  • 通过继承 Thread 类本身;

  • 通过 Callable 和 Future 创建线程。


通过实现 Runnable 接口来创建线程

创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。

为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:

public void run()

你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。

在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。

Thread定义了几个构造方法,下面的这个是我们经常使用的:

Thread(Runnable threadOb,String threadName);

这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。

新线程创建之后,你调用它的start()方法它才会运行。

void start();

实例

下面是一个创建线程并开始让它执行的实例:

// 创建一个新的线程class NewThread implements Runnable {   Thread t;   NewThread() {      // 创建第二个新线程      t = new Thread(this, "Demo Thread");      System.out.println("Child thread: " + t);      t.start(); // 开始线程   }     // 第二个线程入口   public void run() {      try {         for(int i = 5; i > 0; i--) {            System.out.println("Child Thread: " + i);            // 暂停线程            Thread.sleep(50);         }     } catch (InterruptedException e) {         System.out.println("Child interrupted.");     }     System.out.println("Exiting child thread.");   }} public class ThreadDemo {   public static void main(String args[]) {      new NewThread(); // 创建一个新线程      try {         for(int i = 5; i > 0; i--) {           System.out.println("Main Thread: " + i);           Thread.sleep(100);         }      } catch (InterruptedException e) {         System.out.println("Main thread interrupted.");      }      System.out.println("Main thread exiting.");   }}

编译以上程序运行结果如下:

Child thread: Thread[Demo Thread,5,main]Main Thread: 5Child Thread: 5Child Thread: 4Main Thread: 4Child Thread: 3Child Thread: 2Main Thread: 3Child Thread: 1Exiting child thread.Main Thread: 2Main Thread: 1Main thread exiting.

通过继承 Thread 来创建线程

创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。

继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。

该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

实例

// 通过继承 Thread 创建线程class NewThread extends Thread {   NewThread() {      // 创建第二个新线程      super("Demo Thread");      System.out.println("Child thread: " + this);      start(); // 开始线程   }    // 第二个线程入口   public void run() {      try {         for(int i = 5; i > 0; i--) {            System.out.println("Child Thread: " + i);                            // 让线程休眠一会            Thread.sleep(50);         }      } catch (InterruptedException e) {         System.out.println("Child interrupted.");      }      System.out.println("Exiting child thread.");   }} public class ExtendThread {   public static void main(String args[]) {      new NewThread(); // 创建一个新线程      try {         for(int i = 5; i > 0; i--) {            System.out.println("Main Thread: " + i);            Thread.sleep(100);         }      } catch (InterruptedException e) {         System.out.println("Main thread interrupted.");      }      System.out.println("Main thread exiting.");   }}

编译以上程序运行结果如下:

Child thread: Thread[Demo Thread,5,main]Main Thread: 5Child Thread: 5Child Thread: 4Main Thread: 4Child Thread: 3Child Thread: 2Main Thread: 3Child Thread: 1Exiting child thread.Main Thread: 2Main Thread: 1Main thread exiting.

Thread 方法

下表列出了Thread类的一些重要方法:

序号方法描述
1public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3public final void setName(String name)
改变线程名称,使之与参数 name 相同。
4public final void setPriority(int priority)
 更改线程的优先级。
5public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
6public final void join(long millisec)
等待该线程终止的时间最长为 millis 毫秒。
7public void interrupt()
中断线程。
8public final boolean isAlive()
测试线程是否处于活动状态。

测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。

序号方法描述
1public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
2public static void sleep(long millisec)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3public static boolean holdsLock(Object x)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4public static Thread currentThread()
返回对当前正在执行的线程对象的引用。
5public static void dumpStack()
将当前线程的堆栈跟踪打印至标准错误流。

实例

如下的ThreadClassDemo 程序演示了Thread类的一些方法:

// 文件名 : DisplayMessage.java// 通过实现 Runnable 接口创建线程public class DisplayMessage implements Runnable{   private String message;   public DisplayMessage(String message)   {      this.message = message;   }   public void run()   {      while(true)      {         System.out.println(message);      }   }}

GuessANumber.java 文件代码:

// 文件名 : GuessANumber.java// 通过继承 Thread 类创建线程public class GuessANumber extends Thread{   private int number;   public GuessANumber(int number)   {      this.number = number;   }   public void run()   {      int counter = 0;      int guess = 0;      do      {          guess = (int) (Math.random() * 100 + 1);          System.out.println(this.getName()                       + " guesses " + guess);          counter++;      }while(guess != number);      System.out.println("** Correct! " + this.getName()                       + " in " + counter + " guesses.**");   }}

ThreadClassDemo.java 文件代码:

// 文件名 : ThreadClassDemo.javapublic class ThreadClassDemo{   public static void main(String [] args)   {      Runnable hello = new DisplayMessage("Hello");      Thread thread1 = new Thread(hello);      thread1.setDaemon(true);      thread1.setName("hello");      System.out.println("Starting hello thread...");      thread1.start();           Runnable bye = new DisplayMessage("Goodbye");      Thread thread2 = new Thread(bye);      thread2.setPriority(Thread.MIN_PRIORITY);      thread2.setDaemon(true);      System.out.println("Starting goodbye thread...");      thread2.start();       System.out.println("Starting thread3...");      Thread thread3 = new GuessANumber(27);      thread3.start();      try      {         thread3.join();      }catch(InterruptedException e)      {         System.out.println("Thread interrupted.");      }      System.out.println("Starting thread4...");      Thread thread4 = new GuessANumber(75);                thread4.start();      System.out.println("main() is ending...");   }}

运行结果如下,每一次运行的结果都不一样。

Starting hello thread...Starting goodbye thread...HelloHelloHelloHelloHelloHelloHelloHelloHelloStarting thread3...HelloHelloStarting thread4...HelloHellomain() is ending...

通过 Callable 和 Future 创建线程

  • 1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  • 2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  • 3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  • 4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

实例

public class CallableThreadTest implements Callable<Integer> {    public static void main(String[] args)      {          CallableThreadTest ctt = new CallableThreadTest();          FutureTask<Integer> ft = new FutureTask<>(ctt);          for(int i = 0;i < 100;i++)          {              System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);              if(i==20)              {                  new Thread(ft,"有返回值的线程").start();              }          }          try          {              System.out.println("子线程的返回值:"+ft.get());          } catch (InterruptedException e)          {              e.printStackTrace();          } catch (ExecutionException e)          {              e.printStackTrace();          }        }    @Override      public Integer call() throws Exception      {          int i = 0;          for(;i<100;i++)          {              System.out.println(Thread.currentThread().getName()+" "+i);          }          return i;      }  }

创建线程的三种方式的对比

  • 1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  • 2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步

  • 线程间通信

  • 线程死锁

  • 线程控制:挂起、停止和恢复


多线程的使用

有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。

通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。

请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!


applet是一种Java程序。它一般运行在支持Java的Web浏览器内。因为它有完整的Java API支持,所以applet是一个全功能的Java应用程序。

如下所示是独立的Java应用程序和applet程序之间重要的不同:

  • Java中applet类继承了 java.applet.Applet类
  • Applet类没有定义main(),所以一个 Applet程序不会调用main()方法,
  • Applets被设计为嵌入在一个HTML页面。
  • 当用户浏览包含Applet的HTML页面,Applet的代码就被下载到用户的机器上。
  • 要查看一个applet需要JVM。 JVM可以是Web浏览器的一个插件,或一个独立的运行时环境。
  • 用户机器上的JVM创建一个applet类的实例,并调用Applet生命周期过程中的各种方法。
  • Applets有Web浏览器强制执行的严格的安全规则,applet的安全机制被称为沙箱安全。
  • applet需要的其他类可以用Java归档(JAR)文件的形式下载下来。

Applet的生命周期

Applet类中的四个方法给你提供了一个框架,你可以再该框架上开发小程序:

  • init: 该方法的目的是为你的applet提供所需的任何初始化。在Applet标记内的param标签被处理后调用该方法。
  • start: 浏览器调用init方法后,该方法被自动调用。每当用户从其他页面返回到包含Applet的页面时,则调用该方法。
  • stop:当用户从包含applet的页面移除的时候,该方法自动被调用。因此,可以在相同的applet中反复调用该方法。
  • destroy: 此方法仅当浏览器正常关闭时调用。因为applets只有在HTML网页上有效,所以你不应该在用户离开包含Applet的页面后遗漏任何资源.
  • paint: 该方法在start()方法之后立即被调用,或者在applet需要重绘在浏览器的时候调用。paint()方法实际上继承于java.awt。

"Hello, World" Applet:

下面是一个简单的Applet程序HelloWorldApplet.java:

import java.applet.*;import java.awt.*; public class HelloWorldApplet extends Applet{   public void paint (Graphics g)   {      g.drawString ("Hello World", 25, 50);   }}

这些import语句将以下类导入到我们的applet类中:

java.applet.Applet.java.awt.Graphics.

没有这些import语句,Java编译器就识别不了Applet和Graphics类。


Applet 类

每一个applet都是java.applet.Applet 类的子类,基础的Applet类提供了供衍生类调用的方法,以此来得到浏览器上下文的信息和服务。

这些方法做了如下事情:

  • 得到applet的参数
  • 得到包含applet的HTML文件的网络位置
  • 得到applet类目录的网络位置
  • 打印浏览器的状态信息
  • 获取一张图片
  • 获取一个音频片段
  • 播放一个音频片段
  • 调整此 applet 的大小

除此之外,Applet类还提供了一个接口,该接口供Viewer或浏览器来获取applet的信息,并且来控制applet的执行。

Viewer可能是:

  • 请求applet作者、版本和版权的信息
  • 请求applet识别的参数的描述
  • 初始化applet
  • 销毁applet
  • 开始执行applet
  • 结束执行applet

Applet类提供了对这些方法的默认实现,这些方法可以在需要的时候重写。

"Hello,World"applet都是按标准编写的。唯一被重写的方法是paint方法。


Applet的调用

applet是一种Java程序。它一般运行在支持Java的Web浏览器内。因为它有完整的Java API支持,所以applet是一个全功能的Java应用程序。

<applet>标签是在HTML文件中嵌入applet的基础。以下是一个调用"Hello World"applet的例子;

<html><title>The Hello, World Applet</title><hr><applet code="HelloWorldApplet.class" width="320" height="120">If your browser was Java-enabled, a "Hello, World"message would appear here.</applet><hr></html>

注意: 你可以参照HTML Applet标签来更多的了解从HTML中调用applet的方法。

<applet>标签的属性指定了要运行的Applet类。Width和height用来指定applet运行面板的初始大小。applet必须使用</applet>标签来关闭。

如果applet接受参数,那么参数的值需要在标签里添加,该标签位于<applet>和</applet>之间。浏览器忽略了applet标签之间的文本和其他标签。

不支持Java的浏览器不能执行<applet>和</applet>。因此,在标签之间显示并且和applet没有关系的任何东西,在不支持的Java的浏览器里是可见的。

Viewer或者浏览器在文档的位置寻找编译过的Java代码,要指定文档的路径,得使用<applet>标签的codebase属性指定。

如下所示:

<applet codebase="http://amrood.com/applets" code="HelloWorldApplet.class" width="320" height="120">

如果applet所在一个包中而不是默认包,那么所在的包必须在code属性里指定,例如:

<applet code="mypackage.subpackage.TestApplet.class" width="320" height="120">

获得applet参数

下面的例子演示了如何使用一个applet响应来设置文件中指定的参数。该Applet显示了一个黑色棋盘图案和第二种颜色。

第二种颜色和每一列的大小通过文档中的applet的参数指定。

CheckerApplet 在init()方法里得到它的参数。也可以在paint()方法里得到它的参数。然而,在applet开始得到值并保存了设置,而不是每一次刷新的时候都得到值,这样是很方便,并且高效的。

applet viewer或者浏览器在applet每次运行的时候调用init()方法。在加载applet之后,Viewer立即调用init()方法(Applet.init()什么也没做),重写该方法的默认实现,添加一些自定义的初始化代码。

Applet.getParameter()方法通过给出参数名称得到参数值。如果得到的值是数字或者其他非字符数据,那么必须解析为字符串类型。

下例是CheckerApplet.java的梗概:

import java.applet.*;import java.awt.*;public class CheckerApplet extends Applet{   int squareSize = 50;// 初始化默认大小   public void init () {}   private void parseSquareSize (String param) {}   private Color parseColor (String param) {}   public void paint (Graphics g) {}}

下面是CheckerApplet类的init()方法和私有的parseSquareSize()方法:

public void init (){   String squareSizeParam = getParameter ("squareSize");   parseSquareSize (squareSizeParam);   String colorParam = getParameter ("color");   Color fg = parseColor (colorParam);   setBackground (Color.black);   setForeground (fg);}private void parseSquareSize (String param){   if (param == null) return;   try {      squareSize = Integer.parseInt (param);   }   catch (Exception e) {     // 保留默认值   }}

该applet调用parseSquareSize(),来解析squareSize参数。parseSquareSize()调用了库方法Integer. parseInt(),该方法将一个字符串解析为一个整数,当参数无效的时候,Integer.parseInt()抛出异常。

因此,parseSquareSize()方法也是捕获异常的,并不允许applet接受无效的输入。

Applet调用parseColor()方法将颜色参数解析为一个Color值。parseColor()方法做了一系列字符串的比较,来匹配参数的值和预定义颜色的名字。你需要实现这些方法来使applet工作。


指定applet参数

如下的例子是一个HTML文件,其中嵌入了CheckerApplet类。HTML文件通过使用标签的方法给applet指定了两个参数。

<html><title>Checkerboard Applet</title><hr><applet code="CheckerApplet.class" width="480" height="320"><param name="color" value="blue"><param name="squaresize" value="30"></applet><hr></html>

注意: 参数名字大小写不敏感。


应用程序转换成Applet

将图形化的Java应用程序(是指,使用AWT的应用程序和使用java程序启动器启动的程序)转换成嵌入在web页面里的applet是很简单的。

下面是将应用程序转换成applet的几个步骤:

  • 编写一个HTML页面,该页面带有能加载applet代码的标签。
  • 编写一个JApplet类的子类,将该类设置为public。否则,applet不能被加载。
  • 消除应用程序的main()方法。不要为应用程序构造框架窗口,因为你的应用程序要显示在浏览器中。
  • 将应用程序中框架窗口的构造方法里的初始化代码移到applet的init()方法中,你不必显示的构造applet对象,浏览器将通过调用init()方法来实例化一个对象。
  • 移除对setSize()方法的调用,对于applet来讲,大小已经通过HTML文件里的width和height参数设定好了。
  • 移除对 setDefaultCloseOperation()方法的调用。Applet不能被关闭,它随着浏览器的退出而终止。
  • 如果应用程序调用了setTitle()方法,消除对该方法的调用。applet不能有标题栏。(当然你可以给通过html的title标签给网页自身命名)
  • 不要调用setVisible(true),applet是自动显示的。

事件处理

Applet类从Container类继承了许多事件处理方法。Container类定义了几个方法,例如:processKeyEvent()和processMouseEvent(),用来处理特别类型的事件,还有一个捕获所有事件的方法叫做processEvent。

为了响应一个事件,applet必须重写合适的事件处理方法。

import java.awt.event.MouseListener;import java.awt.event.MouseEvent;import java.applet.Applet;import java.awt.Graphics; public class ExampleEventHandling extends Applet                             implements MouseListener {     StringBuffer strBuffer;     public void init() {         addMouseListener(this);         strBuffer = new StringBuffer();        addItem("initializing the apple ");    }     public void start() {        addItem("starting the applet ");    }     public void stop() {        addItem("stopping the applet ");    }     public void destroy() {        addItem("unloading the applet");    }     void addItem(String word) {        System.out.println(word);        strBuffer.append(word);        repaint();    }     public void paint(Graphics g) {         //Draw a Rectangle around the applet's display area.        g.drawRect(0, 0,                      getWidth() - 1,                      getHeight() - 1);          //display the string inside the rectangle.        g.drawString(strBuffer.toString(), 10, 20);    }       public void mouseEntered(MouseEvent event) {    }    public void mouseExited(MouseEvent event) {    }    public void mousePressed(MouseEvent event) {    }    public void mouseReleased(MouseEvent event) {    }     public void mouseClicked(MouseEvent event) {         addItem("mouse clicked! ");    }}

如下调用该applet:

<html><title>Event Handling</title><hr><applet code="ExampleEventHandling.class" width="300" height="300"></applet><hr></html>

最开始运行,applet显示 "initializing the applet. Starting the applet.",然后你一点击矩形框,就会显示"mouse clicked" 。


显示图片

applet能显示GIF,JPEG,BMP等其他格式的图片。为了在applet中显示图片,你需要使用java.awt.Graphics类的drawImage()方法。

如下实例演示了显示图片的所有步骤:

import java.applet.*;import java.awt.*;import java.net.*;public class ImageDemo extends Applet{  private Image image;  private AppletContext context;  public void init()  {      context = this.getAppletContext();      String imageURL = this.getParameter("image");      if(imageURL == null)      {         imageURL = "java.jpg";      }      try      {         URL url = new URL(this.getDocumentBase(), imageURL);         image = context.getImage(url);      }catch(MalformedURLException e)      {         e.printStackTrace();         // Display in browser status bar         context.showStatus("Could not load image!");      }   }   public void paint(Graphics g)   {      context.showStatus("Displaying image");      g.drawImage(image, 0, 0, 200, 84, null);      g.drawString("www.javalicense.com", 35, 100);   } }

如下调用该applet:

<html><title>The ImageDemo applet</title><hr><applet code="ImageDemo.class" width="300" height="200"><param name="image" value="java.jpg"></applet><hr></html>

播放音频

Applet能通过使用java.applet包中的AudioClip接口播放音频。AudioClip接口定义了三个方法:

  • public void play(): 从一开始播放音频片段一次。
  • public void loop(): 循环播放音频片段
  • public void stop(): 停止播放音频片段

为了得到AudioClip对象,你必须调用Applet类的getAudioClip()方法。无论URL指向的是否是一个真实的音频文件,该方法都会立即返回结果。

直到要播放音频文件时,该文件才会下载下来。

如下实例演示了播放音频的所有步骤:

import java.applet.*;import java.awt.*;import java.net.*;public class AudioDemo extends Applet{   private AudioClip clip;   private AppletContext context;   public void init()   {      context = this.getAppletContext();      String audioURL = this.getParameter("audio");      if(audioURL == null)      {         audioURL = "default.au";      }      try      {         URL url = new URL(this.getDocumentBase(), audioURL);         clip = context.getAudioClip(url);      }catch(MalformedURLException e)      {         e.printStackTrace();         context.showStatus("Could not load audio file!");      }   }   public void start()   {      if(clip != null)      {         clip.loop();      }   }   public void stop()   {      if(clip != null)      {         clip.stop();      }   }}

如下调用applet:

<html><title>The ImageDemo applet</title><hr><applet code="ImageDemo.class" width="0" height="0"><param name="audio" value="test.wav"></applet><hr>

你可以使用你电脑上的test.wav来测试上面的实例。


Java支持三种注释方式。前两种分别是// 和/* */,第三种被称作说明注释,它以/** 开始,以 */结束。

说明注释允许你在程序中嵌入关于程序的信息。你可以使用javadoc工具软件来生成信息,并输出到HTML文件中。

说明注释,使你更加方便的记录你的程序的信息。


javadoc 标签

javadoc工具软件识别以下标签:

标签 描述 示例
@author 标识一个类的作者 @author description
@deprecated 指名一个过期的类或成员 @deprecated description
{@docRoot} 指明当前文档根目录的路径 Directory Path
@exception 标志一个类抛出的异常 @exception exception-name explanation
{@inheritDoc} 从直接父类继承的注释 Inherits a comment from the immediate surperclass.
{@link} 插入一个到另一个主题的链接 {@link name text}
{@linkplain} 插入一个到另一个主题的链接,但是该链接显示纯文本字体 Inserts an in-line link to another topic.
@param 说明一个方法的参数 @param parameter-name explanation
@return 说明返回值类型 @return explanation
@see 指定一个到另一个主题的链接 @see anchor
@serial 说明一个序列化属性 @serial description
@serialData 说明通过writeObject( ) 和 writeExternal( )方法写的数据 @serialData description
@serialField 说明一个ObjectStreamField组件 @serialField name type description
@since 标记当引入一个特定的变化时 @since release
@throws 和 @exception标签一样. The @throws tag has the same meaning as the @exception tag.
{@value} 显示常量的值,该常量必须是static属性。 Displays the value of a constant, which must be a static field.
@version 指定类的版本 @version info

文档注释

在开始的/**之后,第一行或几行是关于类、变量和方法的主要描述.

之后,你可以包含一个或多个何种各样的@标签。每一个@标签必须在一个新行的开始或者在一行的开始紧跟星号(*).

多个相同类型的标签应该放成一组。例如,如果你有三个@see标签,可以将它们一个接一个的放在一起。

下面是一个类的说明注释的示例:

/*** This class draws a bar chart.* @author Zara Ali* @version 1.2*/

javadoc输出什么

javadoc工具将你Java程序的源代码作为输入,输出一些包含你程序注释的HTML文件。

每一个类的信息将在独自的HTML文件里。javadoc也可以输出继承的树形结构和索引。

由于javadoc的实现不同,工作也可能不同,你需要检查你的Java开发系统的版本等细节,选择合适的Javadoc版本。

实例

下面是一个使用说明注释的简单实例。注意每一个注释都在它描述的项目的前面。

在经过javadoc处理之后,SquareNum类的注释将在SquareNum.html中找到。

import java.io.*; /*** This class demonstrates documentation comments.* @author Ayan Amhed* @version 1.2*/public class SquareNum {   /**   * This method returns the square of num.   * This is a multiline description. You can use   * as many lines as you like.   * @param num The value to be squared.   * @return num squared.   */   public double square(double num) {      return num * num;   }   /**   * This method inputs a number from the user.   * @return The value input as a double.   * @exception IOException On input error.   * @see IOException   */   public double getNumber() throws IOException {      InputStreamReader isr = new InputStreamReader(System.in);      BufferedReader inData = new BufferedReader(isr);      String str;      str = inData.readLine();      return (new Double(str)).doubleValue();   }   /**   * This method demonstrates square().   * @param args Unused.   * @return Nothing.   * @exception IOException On input error.   * @see IOException   */   public static void main(String args[]) throws IOException   {      SquareNum ob = new SquareNum();      double val;      System.out.println("Enter value to be squared: ");      val = ob.getNumber();      val = ob.square(val);      System.out.println("Squared value is " + val);   }}

如下,使用javadoc工具处理SquareNum.java文件:

$ javadoc SquareNum.javaLoading source file SquareNum.java...Constructing Javadoc information...Standard Doclet version 1.5.0_13Building tree for all the packages and classes...Generating SquareNum.html...SquareNum.java:39: warning - @return tag cannot be used                      in method with void return type.Generating package-frame.html...Generating package-summary.html...Generating package-tree.html...Generating constant-values.html...Building index for all the packages and classes...Generating overview-tree.html...Generating index-all.html...Generating deprecated-list.html...Building index for all classes...Generating allclasses-frame.html...Generating allclasses-noframe.html...Generating index.html...Generating help-doc.html...Generating stylesheet.css...1 warning$


本章节我们将为大家介绍 Java 常用的实例,通过实例学习我们可以更快的掌握 Java 的应用。


Java 环境设置实例

  1. Java 实例 – 如何编译一个Java 文件?
  2. Java 实例 – Java 如何运行一个编译过的类文件?
  3. Java 实例 - 如何执行指定class文件目录(classpath)?
  4. Java 实例 – 如何查看当前 Java 运行的版本?

Java 字符串

  1. Java 实例 – 字符串比较
  2. Java 实例 - 查找字符串最后一次出现的位置
  3. Java 实例 - 删除字符串中的一个字符
  4. Java 实例 - 字符串替换
  5. Java 实例 - 字符串反转
  6. Java 实例 - 字符串查找
  7. Java 实例 - 字符串分割
  8. Java 实例 - 字符串小写转大写
  9. Java 实例 - 测试两个字符串区域是否相等
  10. Java 实例 - 字符串性能比较测试
  11. Java 实例 - 字符串优化
  12. Java 实例 - 字符串格式化
  13. Java 实例 - 连接字符串

Java 数组

  1. Java 实例 – 数组排序及元素查找
  2. Java 实例 – 数组添加元素
  3. Java 实例 – 获取数组长度
  4. Java 实例 – 数组反转
  5. Java 实例 – 数组输出
  6. Java 实例 – 数组获取最大和最小值
  7. Java 实例 – 数组合并
  8. Java 实例 – 数组填充
  9. Java 实例 – 数组扩容
  10. Java 实例 – 数组排序及查找
  11. Java 实例 – 删除数组元素
  12. Java 实例 – 数组差集
  13. Java 实例 – 数组交集
  14. Java 实例 – 在数组中查找指定元素
  15. Java 实例 – 判断数组是否相等
  16. Java 实例 - 数组并集

Java 时间处理

  1. Java 实例 - 格式化时间(SimpleDateFormat)
  2. Java 实例 - 获取当前时间
  3. Java 实例 - 获取年份、月份等
  4. Java 实例 - 时间戳转换成时间

Java 方法

  1. Java 实例 – 方法重载
  2. Java 实例 – 输出数组元素
  3. Java 实例 – 汉诺塔算法
  4. Java 实例 – 斐波那契数列
  5. Java 实例 – 阶乘
  6. Java 实例 – 方法覆盖
  7. Java 实例 – instanceOf 关键字用法
  8. Java 实例 – break 关键字用法
  9. Java 实例 – continue 关键字用法
  10. Java 实例 – 标签(Label)
  11. Java 实例 – enum 和 switch 语句使用
  12. Java 实例 – Enum(枚举)构造函数及方法的使用
  13. Java 实例 – for 和 foreach循环使用
  14. Java 实例 – Varargs 可变参数使用
  15. Java 实例 – 重载(overloading)方法中使用 Varargs

Java 文件操作

  1. Java 实例 - 文件写入
  2. Java 实例 - 读取文件内容
  3. Java 实例 - 删除文件
  4. Java 实例 - 将文件内容复制到另一个文件
  5. Java 实例 - 向文件中追加数据
  6. Java 实例 - 创建临时文件
  7. Java 实例 - 修改文件最后的修改日期
  8. Java 实例 - 获取文件大小
  9. Java 实例 - 文件重命名
  10. Java 实例 - 设置文件只读
  11. Java 实例 - 检测文件是否存在
  12. Java 实例 - 在指定目录中创建文件
  13. Java 实例 - 获取文件修改时间
  14. Java 实例 - 创建文件
  15. Java 实例 - 文件路径比较

Java 目录操作

  1. Java 实例 - 递归创建目录
  2. Java 实例 - 删除目录
  3. Java 实例 - 判断目录是否为空
  4. Java 实例 - 判断文件是否隐藏
  5. Java 实例 - 获取目录大小
  6. Java 实例 - 在指定目录中查找文件
  7. Java 实例 - 获取文件的上级目录
  8. Java 实例 - 获取目录最后修改时间
  9. Java 实例 - 打印目录结构
  10. Java 实例 - 遍历指定目录下的所有目录
  11. Java 实例 - 遍历指定目录下的所有文件
  12. Java 实例 - 在指定目录中查找文件
  13. Java 实例 - 遍历系统根目录
  14. Java 实例 - 查看当前工作目录
  15. Java 实例 - 遍历目录

Java 异常处理

  1. Java 实例 - 异常处理方法
  2. Java 实例 - 多个异常处理(多个catch)
  3. Java 实例 - Finally的用法
  4. Java 实例 - 使用 catch 处理异常
  5. Java 实例 - 多线程异常处理
  6. Java 实例 - 获取异常的堆栈信息
  7. Java 实例 - 重载方法异常处理
  8. Java 实例 - 链试异常
  9. Java 实例 - 自定义异常

Java 数据结构

  1. Java 实例 – 数字求和运算
  2. Java 实例 – 利用堆栈将中缀表达式转换成后缀
  3. Java 实例 – 在链表(LinkedList)的开头和结
  4. Java 实例 – 获取链表(LinkedList)的第一个
  5. Java 实例 – 删除链表中的元素
  6. Java 实例 – 获取链表的元素
  7. Java 实例 – 获取向量元素的索引值
  8. Java 实例 – 栈的实现
  9. Java 实例 – 链表元素查找
  10. Java 实例 – 压栈出栈的方法实现字符串反转
  11. Java 实例 – 队列(Queue)用法
  12. Java 实例 – 获取向量的最大元素
  13. Java 实例 – 链表修改
  14. Java 实例 – 旋转向量

Java 集合

  1. Java 实例 – 数组转集合
  2. Java 实例 – 集合比较
  3. Java 实例 – HashMap遍历
  4. Java 实例 – 集合长度
  5. Java 实例 – 集合打乱顺序
  6. Java 实例 – 集合遍历
  7. Java 实例 – 集合反转
  8. Java 实例 – 删除集合中指定元素
  9. Java 实例 – 只读集合
  10. Java 实例 – 集合输出
  11. Java 实例 – 集合转数组
  12. Java 实例 – List 循环移动元素
  13. Java 实例 – 查找 List 中的最大最小值
  14. Java 实例 – 遍历 HashTable 的键值
  15. Java 实例 – 使用 Enumeration 遍历 HashTable
  16. Java 实例 – 集合中添加不同类型元素
  17. Java 实例 – List 元素替换
  18. Java 实例 – List 截取

Java 网络实例

  1. Java 实例 – 获取指定主机的IP地址
  2. Java 实例 – 查看端口是否已使用
  3. Java 实例 – 获取本机ip地址及主机名
  4. Java 实例 – 获取远程文件大小
  5. Java 实例 – Socket 实现多线程服务器程序
  6. Java 实例 – 查看主机指定文件的最后修改时间
  7. Java 实例 – 使用 Socket 连接到指定主机
  8. Java 实例 – 网页抓取
  9. Java 实例 – 获取 URL响应头的日期信息
  10. Java 实例 – 获取 URL 响应头信息
  11. Java 实例 – 解析 URL
  12. Java 实例 – ServerSocket 和 Socket 通信实例

Java 线程

  1. Java 实例 – 查看线程是否存活
  2. Java 实例 – 获取当前线程名称
  3. Java 实例 – 状态监测
  4. Java 实例 – 线程优先级设置
  5. Java 实例 – 死锁及解决方法
  6. Java 实例 – 获取线程id
  7. Java 实例 – 线程挂起
  8. Java 实例 – 终止线程
  9. Java 实例 – 生产者/消费者问题
  10. Java 实例 – 获取当前线程名称
  11. Java 实例 – 状态监测
  12. Java 实例 – 线程优先级设置
  13. Java 实例 – 死锁及解决方法
  14. Java 实例 – 获取线程状态
  15. Java 实例 – 获取所有线程
  16. Java 实例 – 查看线程优先级
  17. Java 实例 – 中断线程


Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。


新特性

Java8 新增了非常多的特性,我们主要讨论以下几个:

  • Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。

  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。

  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

  • Date Time API − 加强对日期与时间的处理。

  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

更多的新特性可以参阅官网:What's New in JDK 8

在关于 Java 8 文章的实例,我们均使用 jdk 1.8 环境,你可以使用以下命令查看当前 jdk 的版本:

$ java -versionjava version "1.8.0_31"Java(TM) SE Runtime Environment (build 1.8.0_31-b13)Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)

编程风格

Java 8 希望有自己的编程风格,并与 Java 7 区别开,以下实例展示了 Java 7 和 Java 8 的编程格式:

import java.util.Collections;import java.util.List;import java.util.ArrayList;import java.util.Comparator;public class Java8Tester {   public static void main(String args[]){         List<String> names1 = new ArrayList<String>();      names1.add("Google ");      names1.add("W3CSchool ");      names1.add("Taobao ");      names1.add("Baidu ");      names1.add("Sina ");		      List<String> names2 = new ArrayList<String>();      names2.add("Google ");      names2.add("W3CSchool ");      names2.add("Taobao ");      names2.add("Baidu ");      names2.add("Sina ");		      Java8Tester tester = new Java8Tester();      System.out.println("使用 Java 7 语法: ");		      tester.sortUsingJava7(names1);      System.out.println(names1);      System.out.println("使用 Java 8 语法: ");		      tester.sortUsingJava8(names2);      System.out.println(names2);   }      // 使用 java 7 排序   private void sortUsingJava7(List<String> names){         Collections.sort(names, new Comparator<String>() {         @Override         public int compare(String s1, String s2) {            return s1.compareTo(s2);         }      });   }      // 使用 java 8 排序   private void sortUsingJava8(List<String> names){      Collections.sort(names, (s1, s2) -> s1.compareTo(s2));   }}

执行以上脚本,输出结果为:

$ javac Java8Tester.java$ java Java8Tester使用 Java 7 语法: [Baidu , Google , W3CSchool , Sina , Taobao ]使用 Java 8 语法: [Baidu , Google , W3CSchool , Sina , Taobao ]

接下来我们将详细为大家简介 Java 8 的新特性:

各个细节实例请点击下方链接

序号特性
1Lambda 表达式
2方法引用
3函数式接口
4默认方法
5Stream
6Optional 类
7Nashorn, JavaScript 引擎
8新的日期时间 API
9Base64


Java 9 发布于 2017 年 9 月 22 日,带来了很多新特性,其中最主要的变化是已经实现的模块化系统。接下来我们会详细介绍 Java 9 的新特性。

Java 9 新特性

  • 模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。
  • REPL (JShell):交互式编程环境。
  • HTTP 2 客户端:HTTP/2标准是HTTP协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流以及服务器推送特性。
  • 改进的 Javadoc:Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。
  • 多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
  • 集合工厂方法:List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
  • 私有接口方法:在接口中使用private私有方法。我们可以使用 private 访问修饰符在接口中编写私有方法。
  • 进程 API: 改进的 API 来控制和管理操作系统进程。引进 java.lang.ProcessHandle 及其嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。
  • 改进的 Stream API:改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
  • 改进 try-with-resources:如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
  • 改进的弃用注解 @Deprecated:注解 @Deprecated 可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。
  • 改进钻石操作符(Diamond Operator) :匿名类可以使用钻石操作符(Diamond Operator)。
  • 改进 Optional 类:java.util.Optional 添加了很多新的有用方法,Optional 可以直接转为 stream。
  • 多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。
  • 改进的 CompletableFuture API : CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。
  • 轻量级的 JSON API:内置了一个轻量级的JSON API
  • 响应式流(Reactive Streams) API: Java 9中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。

更多的新特性可以参阅官网:What's New in JDK 9

JDK 9 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk9-doc-downloads-3850606.html

在关于 Java 9 文章的实例,我们均使用 jdk 1.9 环境,你可以使用以下命令查看当前 jdk 的版本:

$ java -versionjava version "9-ea"Java(TM) SE Runtime Environment (build 9-ea+163)Java HotSpot(TM) 64-Bit Server VM (build 9-ea+163, mixed mode)


Java10它号称有109项新特性,包含12个JEP。

需要注意的是,本次Java10并不是Oracle的官方LTS版本,所以咱们可以先了解新特性。然后坐等java11的发布再考虑在生产中使用吧。

特性列表

局部变量的类型推断 var关键字

GC改进和内存管理 并行全垃圾回收器 G1

垃圾回收器接口

线程-局部变量管控

合并 JDK 多个代码仓库到一个单独的储存库中

新增API:ByteArrayOutputStream

新增API:List、Map、Set

新增API:java.util.Properties

新增API: Collectors收集器

其它特性

1、局部变量的类型推断 var关键字

这个新功能将为Java增加一些语法糖 - 简化它并改善开发者体验。新的语法将减少与编写Java相关的冗长度,同时保持对静态类型安全性的承诺。

这可能是Java10给开发者带来的最大的一个新特性。下面主要看例子:

  public static void main(String[] args) {        var list = new ArrayList<String>();        list.add("hello,world!");        System.out.println(list);    }

这是最平常的使用。注意赋值语句右边,最好写上泛型类型,否则会有如下情况:

public static void main(String[] args) {        var list = new ArrayList<>();        list.add("hello,world!");        list.add(1);        list.add(1.01);        System.out.println(list);    }

么都可以装,非常的不安全了。和js等语言不同的是,毕竟Java还是强类型的语言,所以下面语句是编译报错的:

public static void main(String[] args) {        var list = new ArrayList<String>();        list.add("hello,world!");        System.out.println(list);        list = new ArrayList<Integer>(); //编译报错    }

注意:注意:注意:下面几点使用限制

局部变量初始化

for循环内部索引变量

传统的for循环声明变量

public static void main(String[] args) {        //局部变量初始化        var list = new ArrayList<String>();        //for循环内部索引变量        for (var s : list) {            System.out.println(s);        }        //传统的for循环声明变量        for (var i = 0; i < list.size(); i++) {            System.out.println(i);        }    }

下面这几种情况,都是不能使用var的

方法参数

全局变量


public static var list = new ArrayList<String>(); //编译报错    public static List<String> list = new ArrayList<>(); //正常编译通过

构造函数参数

方法返回类型

字段

捕获表达式(或任何其他类型的变量声明)

2、GC改进和内存管理 并行全垃圾回收器 G1

JDK 10中有2个JEP专门用于改进当前的垃圾收集元素。

Java 10的第二个JEP是针对G1的并行完全GC(JEP 307),其重点在于通过完全GC并行来改善G1最坏情况的等待时间。G1是Java 9中的默认GC,并且此JEP的目标是使G1平行。

3、垃圾回收器接口

这不是让开发者用来控制垃圾回收的接口;而是一个在 JVM 源代码中的允许另外的垃圾回收器快速方便的集成的接口。

4、线程-局部变量管控

这是在 JVM 内部相当低级别的更改,现在将允许在不运行全局虚拟机安全点的情况下实现线程回调。这将使得停止单个线程变得可能和便宜,而不是只能启用或停止所有线程。

5、合并 JDK 多个代码仓库到一个单独的储存库中

在 JDK9 中,有 8 个仓库: root、corba、hotspot、jaxp、jaxws、jdk、langtools 和 nashorn 。在 JDK10 中这些将被合并为一个,使得跨相互依赖的变更集的存储库运行 atomic commit (原子提交)成为可能。

6、新增API:ByteArrayOutputStream

String toString(Charset): 重载 toString(),通过使用指定的字符集解码字节,将缓冲区的内容转换为字符串。

7、新增API:List、Map、Set

这3个接口都增加了一个新的静态方法,copyOf(Collection)。这些函数按照其迭代顺序返回一个不可修改的列表、映射或包含给定集合的元素的集合。

8、新增API:java.util.Properties

增加了一个新的构造函数,它接受一个 int 参数。这将创建一个没有默认值的空属性列表,并且指定初始大小以容纳指定的元素数量,而无需动态调整大小。还有一个新的重载的 replace 方法,接受三个 Object 参数并返回一个布尔值。只有在当前映射到指定值时,才会替换指定键的条目。

9、新增API: Collectors收集器

toUnmodifiableList():

toUnmodifiableSet():

toUnmodifiableMap(Function, Function):

toUnmodifiableMap(Function, Function, BinaryOperator):

这四个新方法都返回 Collectors ,将输入元素聚集到适当的不可修改的集合中。

10、其它特性

线程本地握手(JEP 312)

其他Unicode语言 - 标记扩展(JEP 314)

基于Java的实验性JIT编译器

根证书颁发认证(CA)

删除工具javah(JEP 313)

从JDK中移除了javah工具,这个很简单并且很重要。

最后

JDK10的升级幅度其实主要还是以优化为主,并没有带来太多对使用者惊喜的特性。所以建议广大开发者还是研究一下2018年9月份到来Java11吧,最重要的是它是LTS版本哦,所以是可以运用在生产上的。

//www.51coolma.cn/java/java-keywords.html
//www.51coolma.cn/java/java-factory-pattern.html
//www.51coolma.cn/java/java-class-instance.html
//www.51coolma.cn/java/java-data-type.html
//www.51coolma.cn/java/java-io-file.html
//www.51coolma.cn/java/java-xml-api.html
//www.51coolma.cn/java/java-collections-traversing.html
//www.51coolma.cn/java/java-regex-character-classes.html
//www.51coolma.cn/java/java-date-format-class.html
//www.51coolma.cn/java/java-java-lang-class.html
//www.51coolma.cn/java/jsf-basic-tags.html
//www.51coolma.cn/java/jpa-entitymanager.html
http://www.51coolma.cn/java/lucene-helloworld.html
/java/log4j-installation.html
/java/java-json-syntax.html
/java/java-thread-multiple.html
/java/java-network-tcp-server.html
/java/javafx-line.html
/java/java-stream-operation.html
/java/java-date-time-methods.html
/java/java-lambda-syntax.html
/java/scripting-in-java-engine.html

数组是一种非常有用和常用的数据类型,存在于每种程序语言之中,java中的数组是一种最简单的复合数据类型,刚学习java数组的小白们大多都会听到一句这样的话:java是纯面向对象的语言,它的数组也是一个对象。所以很多人就按照一个对象的方式来使用数组,后来你会发现,将数组作为一个类来使用在实现上是多么的“不自然”。下面就来全面了解一下关于java中数组的知识。


什么是数组

什么是数组

数组是同一种类型数据的集合,其实就是一个容器。运算的时候有很多数据参与运算,那么首先需要做的是什么。不是如何运算而是如何保存这些数据以便于后期的运算,那么数组就是一种用于存储数据的方式,能存数据的地方我们称之为容器,容器里装的东西就是数组的元素,数组可以装任意类型的数据,虽然可以装任意类型的数据,但是定义好的数组只能装一种元素, 也就是数组一旦定义,那么里边存储的数据类型也就确定了。

数组的特点

1.在Java中,无论使用数组或集合,都有边界检查。如果越界操作就会得到一个RuntimeException异常。

2.数组只能保存特定类型。数组可以保存原生数据类型,集合则不能。集合不以具体的类型来处理对象,它们将所有对象都按Object类型处理,集合中存放的是对象的引用而不是对象本身。

3.集合类只能保存对象的引用。而数组既可以创建为直接保存原生数据类型,也可以保存对象的引用。在集合中可以使用包装类(Wrapper Class),如Integer、Double等来实现保存原生数据类型值。

4.对象数组和原生数据类型数组在使用上几乎是相同的;唯一的区别是对象数组保存的是引用,原生数据类型数组保存原生数据类型的值。

int a = 10; Integer integer = new Integer(a); int b = integer.intValue(); System.out.println(a = b);

数组的正确使用

如果需要存储大量的数据,例如如果需要读取100个数,那么就需要定义100个变量,显然重复写100次代码,是没有太大意义的。如何解决这个问题,Java语言提供了数组(array)的数据结构,是一个容器可以存储相同数据类型的元素,可以将100个数存储到数组中。这时候数组就有很大的帮助了~


数组的格式

格式一:

元素类型[]数组名 = new元素类型[元素个数或数组长度];
int[] arr = new int[5]; arr[0] = 1; arr[1] = 2;

格式二:

元素类型[]数组名 = new元素类型[]{元素,元素,……};
int[] arr = new int[]{3,5,1,7}; int[] arr = {3,5,1,7};

注意:给数组分配空间时,必须指定数组能够存储的元素个数来确定数组大小。创建数组之后不能修改数组的大小。可以使用length属性获取数组的大小。

声明数组变量

为了使用数组必须在程序中声明数组,并指定数组的元素类型=左半部分:
先写左边明确了元素类型 是int ,容器使用数组,那么如何来标识数组?.那么用一个特殊的符号[]中括号来表示。想要使用数组是需要给数组起一个名字的,那么我们在这里给这个数组起名字为arr .接着跟上等号。

代码体现: 
int [] arr
示例:
String[] aArray = new String[5];  String[] bArray = {"a","b","c", "d", "e"};  String[] cArray = new String[]{"a","b","c","d","e"}; 
注意:int arr[] 也是一种创建数组的格式。推荐使用int [] arr 的形式声明数组。

创建数组的三种方式及区别

public static void main(String[] args) {      // 1.方式一  声明 分配空间并赋值      int[] arr1 = {1,2,3};      // 2.方式二 显示初始化      int[] arr2;      arr2 = new int[]{1,2,3};       // 3.方式三 显示初始化()      int[] arr3;      arr3 = new int[3];  }  
他们的区别,方式一:在声明的时候直接就已经分配空间,并赋值,方式一是不能写成如下这种形式的。
int[] arr1;  arr1 = {1,2,3};//错误写法 编译不同过  
方式二和方式三,声明和内存分配是分开的,如上面的例子,
int[] arr2;  和  int[] arr3;  
这一步是在栈空间分配一个引用,存放的是一个引用,null
arr2 = new int[]{1,2,3};<span style="font-family: Arial, Helvetica, sans-serif;">arr3 = new int[3];</span>  
到这一步的时候jvm才开始在内存堆区域分配空间,并赋值,方式二直接赋值 1,2,3  方式三 默认初始化,基本类型是 0  布尔类型是 false 引用类型为null,

注:内存一旦分配不能改变,所有说数组长度固定

创建数组

数组初始化

方式一:不使用运算符new
int[]arr = { 1, 2, 3, 4, 5 };
方式二:使用运算符new
int[] arr2 = new int[] { 1, 2, 3, 4, 5 };int[] arr3=new int[3];arr3[0]=1;arr3[1]=5;arr3[2]=6;
如果数组初始化中不使用运算符new。需要注意:下列写法是错误的。
int[] arr;arr={1,2,3,4,5};

此时初始化数组,必须将声明,创建,初始化都放在一条语句中个,分开会产生语法错误。

所以只能如下写:

int[] arr={1,2,3,4,5};


java.util.Arrays

Arrays类是一个非常有用数组工具类,里面有很多工具方法,检索、填充、排序、比较、toString()等。

下面给个例子:
import java.util.Arrays; /** * 数组综合测试 * * @author leizhimin 2009-7-28 12:35:41 */ public class TestArrays {         public static void main(String[] args) {                 int[] i = new int[10];                 //填充数组                 Arrays.fill(i, 2);                 //遍历数组                 for (int x : i) {                         System.out.print(x + " ");                 }                 //toString()数组                 System.out.println("
" + Arrays.toString(i));                 //复制数组                 int[] b = new int[12];                 System.arraycopy(i, 0, b, 2, 5);                 System.out.println(Arrays.toString(b));                 //一维数组的比较                 int[] c = new int[3];                 int[] d = new int[3];                 Arrays.fill(c, 3);                 Arrays.fill(d, 3);                 System.out.println(c.equals(d));                 System.out.println(Arrays.equals(c, d));                 System.out.println("-------------");                 int[][] a1 = {{1, 2, 3}, {4, 5, 6}};                 int[][] a2 = {{1, 2, 3}, {4, 5, 6}};                 System.out.println(a1.equals(a2));                 System.out.println(Arrays.equals(a1, a2));                 System.out.println(Arrays.deepEquals(a1, a2));                 //深度toString()                 System.out.println(Arrays.toString(a1));                 System.out.println(Arrays.deepToString(a1));                 //数组的排序                 int[] a3 = {3, 2, 5, 4, 1};                 System.out.println(Arrays.toString(a3));                 Arrays.sort(a3);                 System.out.println(Arrays.toString(a3));                 //一维数组数值检索                 int index1 = Arrays.binarySearch(a3, 4);                 int index2 = Arrays.binarySearch(a3, -12);                 int index3 = Arrays.binarySearch(a3, 8);                 System.out.println(index1 + " " + index2 + " " + index3);         } }

执行结果:
2 2 2 2 2 2 2 2 2 2    [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] [0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0] false true ------------- false false true [[I@3e25a5, [I@19821f] [[1, 2, 3], [4, 5, 6]] [3, 2, 5, 4, 1] [1, 2, 3, 4, 5] 3 -1 -6 Process finished with exit code 0

数组的遍历

public static void main(String[] args) {int[] x = { 1, 2, 3 };for (int y = 0; y < 3; y++) {System.out.println(x[y]);// System.out.println("x["+y+"]="+x[y]); 打印效果 x[0]=1;} // 那么这就是数组的第一个常见操作.遍历}

数组中有一个属性可以获取到数组中元素的个数,也就是数组的长度.数组名.length
public static void main<

随机数是任何一种编程语言最基本的特征之一,在技术开发中应用很广泛,因为有时我们需要随机生成一个固定长度的数字、字符串亦或者是需要随机生成一个不定长度的数字、或者进行一个模拟的随机选择等。Java就为我们提供了最基本的工具,可以帮助开发者生成不同条件下需要的随机数。java中产生随机数和c的差不多,一般有两种随机数,一个是Math中random()方法,一个是Random类。不过不管是c还是java,要产生随机数都需要设置随机数种子,如果设置的是一样的话,每次获得的随机数是一样的。下面来汇总一下常见的不同类型的java随机数是如何生成的。


java随机数

java产生随机数的基本方法

方法一:在j2se里我们可以使用Math.random()方法来产生一个随机数,这个产生的随机数是0-1之间的一个double,我们可以把他乘以一定的数,比如说乘以100,他就是个100以内的随机,这个在j2me中没有。


(数据类型)(最小值+Math.random()*(最大值-最小值+1))

例1:从1到10的int型随数

(int)(1+Math.random()*(10-1+1))


例2:

随机生成0~100中的其中一个数

在上面我们已经知道了Math.random()返回的只是从0到1之间的小数,如果要50到100,就先放大50倍,即0到50之间,这里还是小数,如果要整数,就强制转换int,然后再加上50即为50~100。


最终代码:

(int)(Math.random()*50) + 50


方法二:在java.util这个包里面提供了一个Random的类,我们可以新建一个Random的对象来产生随机数,他可以产生随机整数、随机float、随机double,随机long,这个也是我们在j2me的程序里经常用的一个取随机数的方法。

Random random = new Random();//默认构造方法
Random random = new Random(1000);//指定种子数字

在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。


相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。


例:获取[0, 100)之间的int整数。

int i2 = random.nextInt(100);

Random 的函数接口:

// 构造函数(一): 创建一个新的随机数生成器。  Random() // 构造函数(二): 使用单个 long 种子创建一个新随机数生成器: public Random(long seed) { setSeed(seed); } next 方法使用它来保存随机数生成器的状态。Random(long seed)  boolean nextBoolean()     // 返回下一个“boolean类型”伪随机数。 void  nextBytes(byte[] buf) // 生成随机字节并将其置于字节数组buf中。 double nextDouble()     // 返回一个“[0.0, 1.0) 之间的double类型”的随机数。 float  nextFloat()      // 返回一个“[0.0, 1.0) 之间的float类型”的随机数。 int   nextInt()       // 返回下一个“int类型”随机数。 int   nextInt(int n)    // 返回一个“[0, n) 之间的int类型”的随机数。 long  nextLong()      // 返回下一个“long类型”随机数。   synchronized double nextGaussian()  // 返回下一个“double类型”的随机数,它是呈高斯(“正常地”)分布的 double 值,其平均值是 0.0,标准偏差是 1.0。 synchronized void setSeed(long seed) // 使用单个 long 种子设置此随机数生成器的种子。

Random类中的常用方法:

Random 类中的方法比较简单,每个方法的功能也很容易理解。需要说明的是,Random类中各方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的几率是均等的。下面对这些方法做一下基本的介绍:


a 、public boolean nextBoolean()
该方法的作用是生成一个随机的boolean值,生成true和false的值几率相等,也就是都是50%的几率。


b 、public double nextDouble()
该方法的作用是生成一个随机的double值,数值介于[0,1.0)之间,这里中括号代表包含区间端点,小括号代表不包含区间端点,也就是0到1之间的随机小数,包含0而不包含1.0。


c 、public int nextInt()
该方法的作用是生成一个随机的int值,该值介于int的区间,也就是-2的31次方到2的31次方-1之间。
如果需要生成指定区间的int值,则需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。


d 、public int nextInt(int n)
该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。
如果想生成指定区间的int值,也需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。


e 、public void setSeed(long seed)
该方法的作用是重新设置Random对象中的种子数。设置完种子数以后的Random对象和相同种子数使用new关键字创建出的Random对象相同。


Random类


3 、Random类使用示例
使用Random类,一般是生成指定区间的随机数字,下面就一一介绍如何生成对应区间的随机数字。以下生成随机数的代码均使用以下Random对象r进行生成:
Random r = new Random();
a 、生成[0,1.0)区间的小数
 double d1 = r.nextDouble();


直接使用nextDouble方法获得。
b、生成[0,5.0)区间的小数
double d2 = r.nextDouble() * 5;
因为nextDouble方法生成的数字区间是[0,1.0),将该区间扩大5倍即是要求的区间。
同理,生成[0,d)区间的随机小数,d为任意正的小数,则只需要将nextDouble方法的返回值乘以d即可。


c、生成[1,2.5)区间的小数  [n1,n2]
double d3 = r.nextDouble() * 1.5 + 1;【也就是 r.nextDouble() * (n2-n1)+n1】
生成[1,2.5)区间的随机小数,则只需要首先生成[0,1.5)区间的随机数字,然后将生成的随机数区间加1即可。
同理,生成任意非从0开始的小数区间[d1,d2)范围的随机数字(其中d1不等于0),则只需要首先生成[0,d2-d1)区间的随机数字,然后将生成的随机数字区间加上d1即可。

d、生成任意整数
int n1 = r.nextInt();
直接使用nextInt方法即可。

e、生成[0,10)区间的整数
int n2 = r.nextInt(10);
n2 = Math.abs(r.nextInt() % 10);
以上两行代码均可生成[0,10)区间的整数。

第一种实现使用Random类中的nextInt(int n)方法直接实现。
第二种实现中,首先调用nextInt()方法生成一个任意的int数字,该数字和10取余以后生成的数字区间为(-10,10),因为按照数学上的规定余数的绝对值小于除数,然后再对该区间求绝对值,则得到的区间就是[0,10)了。
同理,生成任意[0,n)区间的随机整数,都可以使用如下代码:
int n2 = r.nextInt(n);
n2 = Math.abs(r.nextInt() % n);

f、生成[0,10]区间的整数
int n3 = r.nextInt(11);
n3 = Math.abs(r.nextInt() % 11);
相对于整数区间,[0,10]区间和[0,11)区间等价,所以即生成[0,11)区间的整数。

g、生成[-3,15)区间的整数
int n4 = r.nextInt(18) - 3;   【也就是 r.nextInt() * (n2-n1)+n1】 n1是个负数
n4 = Math.abs(r.nextInt() % 18) - 3;    
生成非从0开始区间的随机整数,可以参看上面非从0开始的小数区间实现原理的说明。

方法三:通过System.currentTimeMillis()来获取一个当前时间毫秒数的long型数字。

通过System.currentTimeMillis()来获取随机数。实际上是获取当前时间毫秒数,它是long类型。使用方法如下:

final long l = System.currentTimeMillis();


若要获取int类型的整数,只需要将上面的结果转行成int类型即可。比如,获取[0, 100)之间的int整数。方法如下:

final long l = System.currentTimeMillis();final int i = (int)( l % 100 );

实例学习:

下面通过示例演示上面3种获取随机数的使用方法。 源码如下(RandomTest.java):

import java.util.Random;import java.lang.Math; /** * java 的随机数测试程序。共3种获取随机数的方法: *  (01)、通过System.currentTimeMillis()来获取一个当前时间毫秒数的long型数字。 *  (02)、通过Math.random()返回一个0到1之间的double值。 *  (03)、通过Random类来产生一个随机数,这个是专业的Random工具类,功能强大。 * * @author skywang * @email kuiwu-wang@163.com */public class RandomTest{   public static void main(String args[]){     // 通过System的currentTimeMillis()返回随机数    testSystemTimeMillis();     // 通过Math的random()返回随机数    testMathRandom();     // 新建“种子为1000”的Random对象,并通过该种子去测试Random的API    testRandomAPIs(new Random(1000), " 1st Random(1000)");    testRandomAPIs(new Random(1000), " 2nd Random(1000)");    // 新建“默认种子”的Random对象,并通过该种子去测试Random的API    testRandomAPIs(new Random(), " 1st Random()");    testRandomAPIs(new Random(), " 2nd Random()");  }   /**   * 返回随机数-01:测试System的currentTimeMillis()   */  private static void testSystemTimeMillis() {    // 通过    final long l = System.currentTimeMillis();    // 通过l获取一个[0, 100)之间的整数    final int i = (int)( l % 100 );     System.out.printf("
---- System.currentTimeMillis() ----
 l=%s i=%s
", l, i);  }   /**   * 返回随机数-02:测试Math的random()   */  private static void testMathRandom() {    // 通过Math的random()函数返回一个double类型随机数,范围[0.0, 1.0)    final double d = Math.random();    // 通过d获取一个[0, 100)之间的整数    final int i = (int)(d*100);     System.out.printf("
---- Math.random() ----
 d=%s i=%s
", d, i);  }    /**   * 返回随机数-03:测试Random的API   */  

在众多语言中,java都以较大优势领先其他语言,跻身最热语言排名前列,学习java的人不计其数。除了学校和培训机构,知识来源的最好途径就是看书了。对于想要成为java程序员或者已经成为java程序员的人来说,最纠结的一件事可能就是想要看一些自学的java书籍但是选择的范围实在是太大了,不知从何读起才能进阶提升自己的技术,当然,经验老道的程序员已经为我们整理出来一些适合自学的java书籍并按照由浅至深的顺序进行推荐,一起来看看详细的介绍吧:


一、入门基础类

1、Head First Java 第2版·中文版

如果你没有学过其他语言亦或是转行到计算机行业,可以先看看《Head First Java》这本书,此书是根据学习理论所设计的,非常适合零基础的小白, 读起来轻松搞笑,让你可以从程序语言的基础开始一直学习到包括线程、网络与分布式程序等项目。最重要的是,你将学会如何像个面向对象开发者一样去思考。

本书的亮点在于不是让你只是读死书,你可以通过玩游戏、拼图、解谜题以及一些意想不到的方式与Java交互。在这些活动中,你会写出一堆真正的Java程序,包括了一个船舰炮战游戏和一个网络聊天程序。本书图文并茂的学习方式能让你快速地在脑海中掌握住java知识。


Head First Java


点此下载PDF版电子书


2、Head First 设计模式(中文版)

看完了《HeadFirst Java》,还推荐另一本HeadFirst系列的书《HeadFirst 设计模式》。简单有趣、还能把关键的东西说明白,又不会被突然出现的一堆概念绕晕。入门书最关键的一点,是别把学习者吓走!本书可以让读者快速掌握概念、培养兴趣。《HeadFirst Java》作为一本设计模式的入门学习书籍,绝对没错。


3、java从入门到精通 第4版

本书从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用Java语言进行程序开发需要掌握的知识。书中所有知识都结合具体实例进行介绍,涉及的程序代码给出了详细的注释,可以使读者轻松领会Java程序开发的精髓,快速提高开发技能。

本书内容详尽,实例丰富,非常适合作为编程初学者的学习用书,也适合作为开发人员的查阅、参考资料。


java从入门到精通


点此下载PDF版电子书(第3版)

4、Java编程思想

在有了一定的Java编程经验之后,你需要“知其所以然”了。这个时候《Java编程思想》是一本让你知其所以然的好书,它对于基本的面向对象知识有比较清楚的交待,对Java基本语法,基本类库有比较清楚的讲解,可以帮你打一个良好的Java编程基础。这本书的缺点是实在太厚,也比较罗嗦,不适合现代人快节奏学习,因此看这本书要懂得取舍,不是每章每节都值得一看的,挑重点的深入看就可以了。


Java编程思想

提取码:java

5、Java 核心技术:卷1 基础知识

口碑最好的官方机构Java教程系统全面讲解Java语言的核心概念、语法、重要特性和开发方法,内有大量程序实例,内容翔实、客观准确,不拖泥带水极具实用价值,你怎么也得有一本。这本书比较全面而且易懂,放在案旁用到的时候查一查,看一看,是Java初学者和Java程序员的必备参考书。

Java 核心技术:卷1 基础知识


点此下载PDF版电子书 

密码:jv8t


6、Java数据结构和算法 第2版

《Java数据结构和算法》以一种易懂的方式教授如何安排和操纵数据的问题,其中不乏一些难题:了解这些知识以期使计算机的应用获得最好的表现。不管使用何种语言或平台,掌握了数据结构和算法将改进程序的质量和性能。 

这本书提供了一套独创的可视讨论专题用以阐明主要的论题:它使用Java语言说明重要的概念,而避免了C/C++语言的复杂性,以便集中精力论述数据结构和算法。经验丰富的作者RorbertLafore先生提供了许多简单明了的例子,避免了对于这类例题常见的冗长、繁锁的数学证明。在每一章后都有问题和练习,使读者有机会测试自己的理解程度。

这本书目前基本断货,足以说明抢手程度。作者主要使用Java语言描述了我们常用的数据结构,值得一看。

Java数据结构和算法

提取码:71xr

7、Java开发实战经典

这本书中的代码和案例较多,知识点也比较全面,在实际开发的过程中来讲解一些基础内容,对于新手而言很实用。

《Java开发实战经典》是一本综合讲解Java核心技术的书籍,在书中使用大量的代码及案例进行知识点的分析与运用,并且给出一些比较成熟的开发步骤,帮助读者更好地进行Java的开发。本书真正地做到了让每一位读者都能清楚地知道每个知识点的来龙去脉,不仅可以很容易地看懂一个程序,而且能真正地灵活运用程序,编写代码,让每一位读者真正做到“轻松学Java、从零开始学Java”。

“注意”、“提示”、“问答”是《Java开发实战经典(名师讲坛)》的一大特色,通过这样的方式,可以让读者进行更加全面的思考,这些特色中还包含了不少在Java面试中有可能遇到的问题,这让每位读者在打好基础、巩固技术之余,也能为面试提供强有力的支持。

Java开发实战经典


二、中级进阶类


1. Java并发编程实战

本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容,最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类,非常适合Java程序开发人员阅读。

Java并发编程实战


2. 编写高质量代码:改善Java程序的151个建议

国人原创作品。内容全部由Java编码的最佳实践组成,为Java程序员如何编写高质量的Java代码提出了151条极为宝贵的建议。对于每一个问题,不仅以建议的方式从正反两面给出了被实践证明为十分优秀的解决方案和非常糟糕的解决方案,而且还分析了问题产生的根源,犹如醍醐灌顶,让人豁然开朗。

编写高质量代码

提取码:java

3. 重构 改善既有代码的设计

重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。多年前,正是本书原版的出版,使重构终于从编程高手们的小圈子走出,成为众多普通程序员日常开发工作中不可或缺的一部分。本书也因此成为与《设计模式》齐名的经典著作,被译为中、德、俄、日等众多语言,在世界范围内畅销不衰。

本书凝聚了软件开发社区专家多年摸索而获得的宝贵经验,拥有不因时光流逝而磨灭的价值。今天,无论是重构本身,业界对重构的理解,还是开发工具对重构的支持力度,都与本书最初出版时不可同日而语,但书中所蕴涵的意味和精华,依然值得反复咀嚼,而且往往能够常读常新。

重构 改善既有代码的设计


4. 深入分析Java Web技术内幕

作者是2009年加入淘宝的许令波。全面、深入地阐述了Web前端、Java和Java服务端技术。

《深入分析Java Web技术内幕》围绕JavaWeb相关技术从三方面全面深入地进行阐述。首先介绍前端知识,主要介绍JavaWeb开发中涉及的一些基本知识,包括Web请求过程、HTTP协议、DNS技术和CDN技术。其次深入介绍Java技术,包括I/O技术、中文编码问题、Javac编译原理、class文件结构解析、ClassLoader工作机制及JVM的内存管理等。最后介绍Java服务端技术,主要包括Servlet、Session与Cookie、Tomcat与Jetty服务器、Spring容器、Ibatis框架和Velocity框架等原理介绍。

深入分析Java Web技术内幕


5. 大型网站系统与Java中间件实践

作者是蘑菇街技术副总曾宪杰,曾长期负责淘宝主站。通过这本书可以了解大型网站架构变迁过程中的较为通用的问题和解法,并了解构建支撑大型网站的 Java 中间件的实践经验。

对于有一定网站开发、设计经验,并想了解大型网站架构和支撑这种架构的系统的开发、测试等的相关工程人员,本书有很大的参考意义;对于没有网站开发设计经验的人员,通过本书也能宏观了解大型网站的架构及相关问题的解决思路和方案。

大型网站系统与Java中间件实践


三、高级深入类

1、深入理解Java虚拟机

非常难得的国人原创JVM实践性图书。“其中穿插的经验、技巧、案例、实战处处都可见作者的实践之中积累的功力。”此书与Bill Venners的老书《深入Java虚拟机》很大程度上是互补的,可以参看。

第1版两年内印刷近10次,4家网上书店的评论近4?000条,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的改进:根据最新的JDK 1.7对全书内容进行了全面的升级和补充;增加了大量处理各种常见JVM问题的技巧和最佳实践;增加了若干与生产环境相结合的实战案例;对第1版中的错误和不足之处的修正;等等。第2版不仅技术更新、内容更丰富,而且实战性更强。


《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》共分为五大部分,围绕内存管理、执行子系统、程序编译与优化、高效并发等核心主题对JVM进行了全面而深入的分析,深刻揭示了JVM的工作原理。


深入理解Java虚拟机


点此下载PDF版电子书  

密码:hgxm


2、企业应用架构模式

作者将40多种经常出现的解决方案转化成模式,最终写成这本能够应用于任何一种企业应用平台的、关于解决方案的、不可或缺的手册。

《企业应用架构模式》获得了2003年度美国软件开发杂志图书类的生产效率奖和读者选择奖。《企业应用架构模式》分为两大部分。第一部分是关于如何开发企业应用的简单介绍。第二部分是《企业应用架构模式》的主体,是关于模式的详细参考手册,每个模式都给出使用方法和实现信息,并配以详细的Java代码或C#代码示例。此外,整《企业应用架构模式》中还用了大量UML图来进一步阐明有关概念。


《企业应用架构模式》是为致力于设计和构建企业应用的软件架构师、设计人员和编程人员而写的,同时也可作为高等院校计算机专业及软件学院相关课程的参考教材。


企业应用架构模式

提取码:java

3、Java性能权威指南

Java性能方面的新书,可能也是最好的一本。不仅讲述了对什么进行优化,如何优化,还阐述了大量然后编写高效代码的最佳实践。虽然篇幅小一些,但比Oracle官方的那本内容博杂的《Java性能优化权威指南》其实更深入。

市面上介绍Java的书有很多,但专注于Java性能的并不多,能游刃有余地展示Java性能优化难点的更是凤毛麟角,本书即是其中之一。通过使用JVM和Java平台,以及Java语言和应用程序接口,本书详尽讲解了Java性能调优的相关知识,帮助读者深入理解Java平台性能的各个方面,最终使程序如虎添翼。

Java性能权威指南

点此下载PDF版电子书  

密码:urpm



在java开发中有一个非常重要的概念就是java反射机制,也是java的重要特征之一。反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,通过反射可以调用私有方法和私有属性,大部分框架也都是运用反射原理的。java通常是先有类再有对象,有对象就可以调用方法或者属性,java中的反射其实是通过Class对象来调用类里面的方法。掌握了反射的知识,才能更好的学习java高级课程。


java

反射简介:

主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

一个类有多个组成部分,例如:成员变量、方法、构造方法等,反射就是加载类,并解剖出类的各个组成部分。

反射机制主要提供以下功能:

①在运行时判断任意一个对象所属的类;

②在运行时构造任意一个类的对象;

③在运行时判断任意一个类所具有的成员变量和方法;

④在运行时调用任意一个对象的方法;

⑤生成动态代理。

java中的反射及作用:

假如有两个程序员,一个程序员在写程序的时需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码是不能通过编译的。此时,利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。

Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。如eclipse中,一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。

java反射机制中有哪些类:

java.lang.Class;                java.lang.reflect.Constructor; java.lang.reflect.Field;        java.lang.reflect.Method;java.lang.reflect.Modifier;

反射机制的相关API

通过一个对象获得完整的包名和类名 :

package net.xsoftlab.baike;public class TestReflect {    public static void main(String[] args) throws Exception {        TestReflect testReflect = new TestReflect();        System.out.println(testReflect.getClass().getName());        // 结果 net.xsoftlab.baike.TestReflect    }}

实例化Class类对象

package net.xsoftlab.baike;public class TestReflect {    public static void main(String[] args) throws Exception {        Class<?> class1 = null;        Class<?> class2 = null;        Class<?> class3 = null;        // 一般采用这种形式        class1 = Class.forName("net.xsoftlab.baike.TestReflect");        class2 = new TestReflect().getClass();        class3 = TestReflect.class;        System.out.println("类名称   " + class1.getName());        System.out.println("类名称   " + class2.getName());        System.out.println("类名称   " + class3.getName());    }}

获取一个对象的父类与实现的接口

package net.xsoftlab.baike;import java.io.Serializable;public class TestReflect implements Serializable {    private static final long serialVersionUID = -2862585049955236662L;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        // 取得父类        Class<?> parentClass = clazz.getSuperclass();        System.out.println("clazz的父类为:" + parentClass.getName());        // clazz的父类为: java.lang.Object        // 获取所有的接口        Class<?> intes[] = clazz.getInterfaces();        System.out.println("clazz实现的接口有:");        for (int i = 0; i < intes.length; i++) {            System.out.println((i + 1) + ":" + intes[i].getName());        }        // clazz实现的接口有:        // 1:java.io.Serializable    }}

反射

通过反射机制实例化一个类的对象

package net.xsoftlab.baike;import java.lang.reflect.Constructor;public class TestReflect {    public static void main(String[] args) throws Exception {        Class<?> class1 = null;        class1 = Class.forName("net.xsoftlab.baike.User");        // 第一种方法,实例化默认构造方法,调用set赋值        User user = (User) class1.newInstance();        user.setAge(20);        user.setName("Rollen");        System.out.println(user);        // 结果 User [age=20, name=Rollen]        // 第二种方法 取得全部的构造函数 使用构造函数赋值        Constructor<?> cons[] = class1.getConstructors();        // 查看每个构造方法需要的参数        for (int i = 0; i < cons.length; i++) {            Class<?> clazzs[] = cons[i].getParameterTypes();            System.out.print("cons[" + i + "] (");            for (int j = 0; j < clazzs.length; j++) {                if (j == clazzs.length - 1)                    System.out.print(clazzs[j].getName());                else                    System.out.print(clazzs[j].getName() + ",");            }            System.out.println(")");        }        // 结果        // cons[0] (java.lang.String)        // cons[1] (int,java.lang.String)        // cons[2] ()        user = (User) cons[0].newInstance("Rollen");        System.out.println(user);        // 结果 User [age=0, name=Rollen]        user = (User) cons[1].newInstance(20, "Rollen");        System.out.println(user);        // 结果 User [age=20, name=Rollen]    }}class User {    private int age;    private String name;    public User() {        super();    }    public User(String name) {        super();        this.name = name;    }    public User(int age, String name) {        super();        this.age = age;        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "User [age=" + age + ", name=" + name + "]";    }}

获取某个类的全部属性

package net.xsoftlab.baike;import java.io.Serializable;import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class TestReflect implements Serializable {    private static final long serialVersionUID = -2862585049955236662L;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        System.out.println("===============本类属性===============");        // 取得本类的全部属性        Field[] field = clazz.getDeclaredFields();        for (int i = 0; i < field.length; i++) {            // 权限修饰符            int mo = field[i].getModifiers();            String priv = Modifier.toString(mo);            // 属性类型            Class<?> type = field[i].getType();            System.out.println(priv + " " + type.getName() + " " + field[i].getName() + ";");        }                 System.out.println("==========实现的接口或者父类的属性==========");        // 取得实现的接口或者父类的属性        Field[] filed1 = clazz.getFields();        for (int j = 0; j < filed1.length; j++) {            // 权限修饰符            int mo = filed1[j].getModifiers();            String priv = Modifier.toString(mo);            // 属性类型            Class<?> type = filed1[j].getType();            System.out.println(priv + " " + type.getName() + " " + filed1[j].getName() + ";");        }    }}

获取某个类的全部方法

package net.xsoftlab.baike;import java.io.Serializable;import java.lang.reflect.Method;import java.lang.reflect.Modifier;public class TestReflect implements Serializable {    private static final long serialVersionUID = -2862585049955236662L;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        Method method[] = clazz.getMethods();        for (int i = 0; i < method.length; ++i) {            Class<?> returnType = method[i].getReturnType();            Class<?> para[] = method[i].getParameterTypes();            int temp = method[i].getModifiers();            System.out.print(Modifier.toString(temp) + " ");            System.out.print(returnType.getName() + "  ");            System.out.print(method[i].getName() + " ");            System.out.print("(");            for (int j = 0; j < para.length; ++j) {                System.out.print(para[j].getName() + " " + "arg" + j);                if (j < para.length - 1) {                    System.out.print(",");                }            }            Class<?> exce[] = method[i].getExceptionTypes();            if (exce.length > 0) {                System.out.print(") throws ");                for (int k = 0; k < exce.length; ++k) {                    System.out.print(exce[k].getName() + " ");                    if (k < exce.length - 1) {                        System.out.print(",");                    }                }            } else {                System.out.print(")");            }            System.out.println();        }    }}

通过反射机制调用某个类的方法

package net.xsoftlab.baike;import java.lang.reflect.Method;public class TestReflect {    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        // 调用TestReflect类中的reflect1方法        Method method = clazz.getMethod("reflect1");        method.invoke(clazz.newInstance());        // Java 反射机制 - 调用某个类的方法1.        // 调用TestReflect的reflect2方法        method = clazz.getMethod("reflect2", int.class, String.class);        method.invoke(clazz.newInstance(), 20, "张三");        // Java 反射机制 - 调用某个类的方法2.        // age -> 20. name -> 张三    }    public void reflect1() {        System.out.println("Java 反射机制 - 调用某个类的方法1.");    }    public void reflect2(int age, String name) {        System.out.println("Java 反射机制 - 调用某个类的方法2.");        System.out.println("age -> " + age + ". name -> " + name);    }}

通过反射机制操作某个类的属性

package net.xsoftlab.baike;import java.lang.reflect.Field;public class TestReflect {    private String proprety = null;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        Object obj = clazz.newInstance();        // 可以直接对 private 的属性赋值        Field field = clazz.getDeclaredField("proprety");        field.setAccessible(true);        field.set(obj, "Java反射机制");        System.out.println(field.get(obj));    }}

反射机制的动态代理

// 获取类加载器的方法TestReflect testReflect = new TestReflect();        System.out.println("类加载器  " + testReflect.getClass().getClassLoader().getClass().getName());package net.xsoftlab.baike;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;//定义项目接口interface Subject {    public String say(String name, int age);}// 定义真实项目class RealSubject implements Subject {    public String say(String name, int age) {        return name + "  " + age;    }}class MyInvocationHandler implements InvocationHandler {    private Object obj = null;    public Object bind(Object obj) {        this.obj = obj;        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        Object temp = method.invoke(this.obj, args);        return temp;    }}/** * 在java中有三种类类加载器。 *  * 1)Bootstrap ClassLoader 此加载器采用c++编写,一般开发中很少见。 *  * 2)Extension ClassLoader 用来进行扩展类的加载,一般对应的是jrelibext目录中的类 *  * 3)AppClassLoader 加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。 *  * 如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,已完成代理的具体操作。 *  * @author xsoftlab.net *  */public class TestReflect {    public static void main(String[] args) throws Exception {        MyInvocationHandler demo = new MyInvocationHandler();        Subject sub = (Subject) demo.bind(new RealSubject());        String info = sub.say("Rollen", 20);        System.out.println(info);    }}


射机制的应用实例


在泛型为Integer的ArrayList中存放一个String类型的对象

package net.xsoftlab.baike;import java.lang.reflect.Method;import java.util.ArrayList;public class TestReflect {    public static void main(String[] args) throws Exception {        ArrayList<Integer> list = new ArrayList<Integer>();        Method method = list.getClass().getMethod("add", Object.class);        method.invoke(list, "Java反射机制实例。");        System.out.println(list.get(0));    }}

修改数组

通过反射取得并修改数组的信息

package net.xsoftlab.baike;import java.lang.reflect.Array;public class TestReflect {    public static void main(String[] args) throws Exception {        int[] temp = { 1, 2, 3, 4, 5 };        Class<?> demo = temp.getClass().getComponentType();        System.out.println("数组类型: " + demo.getName());        System.out.println("数组长度  " + Array.getLength(temp));        System.out.println("数组的第一个元素: " + Array.get(temp, 0));        Array.set(temp, 0, 100);        System.out.println("修改之后数组第一个元素为: " + Array.get(temp, 0));    }}

通过反射机制修改数组的大小

package net.xsoftlab.baike;import java.lang.reflect.Array;public class TestReflect {    public static void main(String[] args) throws Exception {        int[] temp = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };        int[] newTemp = (int[]) arrayInc(temp, 15);        print(newTemp);        String[] atr = { "a", "b", "c" };        String[] str1 = (String[]) arrayInc(atr, 8);        print(str1);    }    // 修改数组大小    public static Object arrayInc(Object obj, int len) {        Class<?> arr = obj.getClass().getComponentType();        Object newArr = Array.newInstance(arr, len);        int co = Array.getLength(obj);        System.arraycopy(obj, 0, newArr, 0, co);        return newArr;    }    // 打印    public static void print(Object obj) {        Class<?> c = obj.getClass();        if (!c.isArray()) {            return;        }        System.out.println("数组长度为: " + Array.getLength(obj));        for (int i = 0; i < Array.getLength(obj); i++) {            System.out.print(Array.get(obj, i) + " ");        }        System.out.println();    }}

将反射机制应用于工厂模式

package net.xsoftlab.baike;interface fruit {    public abstract void eat();}class Apple implements fruit {    public void eat() {        System.out.println("Apple");    }}class Orange implements fruit {    public void eat() {        System.out.println("Orange");    }}class Factory {    public static fruit getInstance(String ClassName) {        fruit f = null;        try {            f = (fruit) Class.forName(ClassName).newInstance();        } catch (Exception e) {            e.printStackTrace();        }        return f;    }}/** * 对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦。 * Java 工厂模式可以参考 * http://baike.xsoftlab.net/view/java-factory-pattern *  * 现在我们利用反射机制实现工厂模式,可以在不修改工厂类的情况下添加任意多个子类。 *  * 但是有一点仍然很麻烦,就是需要知道完整的包名和类名,这里可以使用properties配置文件来完成。 *  * java 读取 properties 配置文件 的方法可以参考 * http://baike.xsoftlab.net/view/java-read-the-properties-configuration-file *  * @author xsoftlab.net */public class TestReflect {    public static void main(String[] args) throws Exception {        fruit f = Factory.getInstance("net.xsoftlab.baike.Apple");        if (f != null) {            f.eat();        }    }}

使用java反射的优势与弊端


java反射的优势与弊端

反射虽然很灵活,能够使得写的代码,变的大幅精简,所以在用的时候,一定要注意具体的应用场景,反射的优缺点如下: 

优点: 
(1)能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。 
(2)与Java动态编译相结合,可以实现无比强大的功能 


缺点: 
(1)使用反射的性能较低 
(2)使用反射相对来说不安全 
(3)破坏了类的封装性,可以通过反射获取这个类的私有方法和属性 

任何事物,都有两面性,反射的优点,也同是就是它的缺点,所以,没有好与坏,只有最合适的场景。

在开发工作中,我们常常会需要一些周期性的操作,比如每5分钟执行一次某个程序,又比如定时检查数据库连接池中的连接数,每晚定时备份数据等等,在java中,最方便、最高效的实现方式就是用java.util.Timer工具类,再通过调度java.util.TimerTask任务,不过,使用这种方式虽然可以让你的程序按照某一个频度执行,但不能在指定时间运行。下面就具体了解一下java定时器设置的几种常用方法及使其停止的方法。


java.util.Timer和java.util.TimerTask基本介绍:

Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所拥有的TimerTasks。

TimerTask是一个抽象类,它的子类由 Timer 安排为一次执行或重复执行的任务。实际上就是一个拥有run方法的类,需要定时执行的代码放到run方法体内。

java定时任务的基本方法:

1、创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果;


thread

2、 用Timer和TimerTask与第一种方法相比有如下好处:

  • 当启动和去取消任务时可以控制

  • 第一次执行任务时可以指定你想要的delay时间


TimerTask

3、 用ScheduledExecutorService是从的java.util.concurrent里做为并发工具类被引进的,这是最理想的定时任务实现方式,相比于上两个方法,它有以下好处:

  • 相比于Timer的单线程,它是通过线程池的方式来执行任务的

  • 可以很灵活的去设定第一次执行任务delay时间

  • 提供了良好的约定,以便设定执行的时间间隔


ScheduledExecutorService

4、基于spring框架的定时任务来实现:

可以使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行。


从作业类的继承方式来讲,可以分为两类:
作业类需要继承自特定的作业类基类,如Quartz中需要继承自org.springframework.scheduling.quartz.QuartzJobBean;java.util.Timer中需要继承自java.util.TimerTask。
作业类即普通的java类,不需要继承自任何基类。
 
注:比较推荐使用第二种方式,因为这样所以的类都是普通类,不需要事先区别对待。


从任务调度的触发时机来分,这里主要是针对作业使用的触发器,主要有以下两种:


每隔指定时间则触发一次,在Quartz中对应的触发器为:org.springframework.scheduling.quartz.SimpleTriggerBean

每到指定时间则触发一次,在Quartz中对应的调度器为:org.springframework.scheduling.quartz.CronTriggerBean
 
注:并非每种任务都可以使用这两种触发器,如java.util.TimerTask任务就只能使用第一种。Quartz和spring task都可以支持这两种触发条件。


用法说明:

第一种,作业类继承自特定的基类:org.springframework.scheduling.quartz.QuartzJobBean

第一步:定义作业类

Java代码

import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class Job1 extends QuartzJobBean {  private int timeout; private static int i = 0; //调度工厂实例化后,经过timeout时间开始执行调度 public void setTimeout(int timeout) { this.timeout = timeout; }  /*** 要调度的具体任务*/ @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException {   System.out.println("定时任务执行中…"); } } 


第二步:spring配置文件中配置作业类JobDetailBean


Xml代码

<bean name="job1" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.gy.Job1" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="0" /> </map> </property> </bean> 
说明:org.springframework.scheduling.quartz.JobDetailBean有两个属性,jobClass属性即我们在java代码中定义的任务类,jobDataAsMap属性即该任务类中需要注入的属性值。

第三步:配置作业调度的触发方式(触发器)

Quartz的作业触发器有两种,分别是
 
org.springframework.scheduling.quartz.SimpleTriggerBean
 
org.springframework.scheduling.quartz.CronTriggerBean
 
第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。
 
配置方式如下:


Xml代码

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job1" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 

第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。

配置方式如下:


Xml代码 

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job1" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 * * ?" /> </bean>
 

关于cronExpression表达式的语法参见附录。

第四步:配置调度工厂

Xml代码

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 

说明:该参数指定的就是之前配置的触发器的名字。

 

第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。


第二种,作业类不继承特定基类。

Spring能够支持这种方式,归功于两个类:

 

org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean

 

org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean

 

这两个类分别对应spring支持的两种实现任务调度的方式,即前文提到到java自带的timer task方式和Quartz方式。这里我只写MethodInvokingJobDetailFactoryBean的用法,使用该类的好处是,我们的任务类不再需要继承自任何类,而是普通的pojo。

 

第一步:编写任务类


Java代码

public class Job2 { public void doJob2() { System.out.println("不继承QuartzJobBean方式-调度进行中..."); } } 

可以看出,这就是一个普通的类,并且有一个方法。

第二步:配置作业类


Xml代码 

<bean id="job2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"> <bean class="com.gy.Job2" /> </property> <property name="targetMethod" value="doJob2" /> <property name="concurrent" value="false" /><!-- 作业不并发调度 --> </bean> 

说明:这一步是关键步骤,声明一个MethodInvokingJobDetailFactoryBean,有两个关键属性:targetObject指定任务类,targetMethod指定运行的方法。往下的步骤就与方法一相同了,为了完整,同样贴出。

 

第三步:配置作业调度的触发方式(触发器)

 

Quartz的作业触发器有两种,分别是

 

org.springframework.scheduling.quartz.SimpleTriggerBean

 

org.springframework.scheduling.quartz.CronTriggerBean

 

第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。

 

配置方式如下:


Xml代码

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job2" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 
 

第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。

 

配置方式如下:


Xml代码

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <

在编写代码时,逻辑判断是避免不了的,switch case语句在很多编程语言中的功能都是用于条件判断,java中为多路分支选择流程专门提供了switch语句,switch语句根据一个表达式的值,选择运行多个操作中的一个。当需要对选项进行等值判断时,使用switch语句更加简洁明了。switch的case语句可以处理int,short,byte,char类型的值,但是不能处理long,String等类型。说起来你应该也会想到另一个常用的条件判断语句if else,本文重点介绍java中switch case语句的用法,但是会在篇末对两种语句的区别做一个总结。


java switch


java switch基础语法

switch(表达式){ case 表达式常量1:语句1breakcase 表达式常量2:语句2break; ...... case 表达式常量n:语句n; break; [default:语句n+1;] } 

其中,一个case表达式常量成为标号,代表一个case分支的入口。switch语句在运行时首先计算switch圆括号中“表达式”的值,这个值必须是整型或字符型的,同时后面各个case表达式常量的值的类型应与switch圆括号中“表达式”的值类型一致。一个case语句代表一个制定操作,然后转向结构出口。default子句是可选的,当表达式的值与case表达式常量的值都不匹配时,就运行default子句,转向结构出口。 

java里switch的执行顺序

switch表达式的值决定选择哪个case分支,如果找不到相应的分支,就直接从"default" 开始输出。
当程序执行一条case语句后,因为例子中的case分支中没有break 和return语句,所以程序会执行紧接于其后的语句。  

public class Switch {    public static void main(String[] args)     {        int x=0;       switch(x)       {        default:            System.out.println("default");        case 1:            System.out.println(1);        case 2:            System.out.println(2);        }    }}

 输出结果如下:

default
1
2

public class Switch {    public static void main(String[] args) {        int x = 0;        switch (x) {        default:            System.out.println("default");        case 0:            System.out.println(0);        case 1:            System.out.println(1);       case 2:            System.out.println(2);        }    }}
输出结果如下:
0
1
2

public class Switch {    public static void main(String[] args) {        int x = 0;        switch (x) {        case 0:            System.out.println(0);        case 1:            System.out.println(1);        case 2:            System.out.println(2);        default:            System.out.println("default");        }    }}
输出结果如下:
0
1
2
default

java switch语句注意事项

java switch语句注意事项

switch(A),括号中A的取值只能是整型或者可以转换为整型的数值类型,比如byte、short、int、char、还有枚举;需要强调的是:long和String类型是不能作用在switch语句上的。

     

case B:C;case是常量表达式,也就是说B的取值只能是常量(需要定义一个final型的常量,后面会详细介绍原因)或者int、byte、short、char(比如1、2、3、200000000000(注意了这是整型)),如果你需要在此处写一个表达式或者变量,那么就要加上单引号; case后的语句可以不用大括号,就是C不需要用大括号包裹着;

     

default就是如果没有符合的case就执行它,default并不是必须的。


案例分析:

1.标准型(case后面都有break语句,case后的值都是整数)

int i=3; switch(i) { case 1: System.out.println(1); break; case 2: System.out.println(2); break;  default: System.out.println("default"); break; } 

2.常量型(case后面都有break语句,case后的值都是常量)

private final int NUM1=1private final int NUM2=1int i=3; switch(i) { case NUM1: System.out.println(1); break; case NUM2: System.out.println(2); break;  default: System.out.println("default"); break; } 

3.表达式型(case后面都有break语句,case后的值都是表达式)

int i=3; int b = 2;switch(i) { case ‘类名.getId()‘: System.out.println(1); break; case ‘b‘ System.out.println(2); break;  default: System.out.println("default"); break; }

实例:java 用switch语句解决月薪范围问题
public class SwitchDemo {    public static void main(String[] args) {         int month = 8;        String monthString;        switch (month) {            case 1:  monthString = "January";                     break;            case 2:  monthString = "February";                     break;            case 3:  monthString = "March";                     break;            case 4:  monthString = "April";                     break;            case 5:  monthString = "May";                     break;            case 6:  monthString = "June";                     break;            case 7:  monthString = "July";                     break;            case 8:  monthString = "August";                     break;            case 9:  monthString = "September";                     break;            case 10: monthString = "October";                     break;            case 11: monthString = "November";                     break;            case 12: monthString = "December";                     break;            default: monthString = "Invalid month";                     break;        }        System.out.println(monthString);    }}

switch和if语句的区别

Java中switch和if语句的区别

switch和if语句都是Java的选择语句,这两种语句都是允许在程序运行时控制程序的执行过程。

switch和if-else相比,由于使用了Binary Tree算法,绝大部分情况下switch会快一点,除非是if-else的第一个条件就为true。 


编译器编译switch与编译if...else...不同。不管有多少case,都直接跳转,不需逐个比较查询。 

 

相比于if-else结构,switch的效率绝对是要高很多的,但是switch使用查找表的方式决定了case的条件必须是一个连续的常量。而if-else则可以灵活的多。

  
switch效率高,从汇编代码可以看出来。switch只计算一次值,然后都是test。
  

switch的效率与分支数无关。当只有分支比较少的时候,if效率比switch高(因为switch有跳转表)。分支比较多,那当然是使用switch。


有一个问题困扰着许多初学java的新手们,那就是有必要去学习数据结构吗?虽然你可能没有特意去看一些数据结构的专业书籍,你仍然可以用java做一份还过得去的工作,不过并不是说你完全没有接触到数据结构,因为java已经在底层帮你做了太多,在你和数据结构打交道的时候,你所做更多的是在调用 API 。当你的代码量累积到一定程度的时候,就会想要去加强数据结构和算法的相关知识了。


打个比方,你可以把java看做是自动档轿车,数据结构呢就是变速箱的工作原理。你完全可以不知道变速箱怎样工作,就把自动档的车子开上路,而且未必就比懂得的人慢。写程序这件事,和开车一样,经验可以起到很大作用,但如果你不知道底层是怎么工作的,就永远只能开车,既不会修车,也不能造车。如果你对这两件事都不感兴趣也就罢了,数据结构懂得用就好。但若你此生在编程领域还有点更高的追求,数据结构是绕不开的课题。


此外,很重要的一点是,数据结构也是通向各种实用算法的基石,所以学习数据结构都是提升内力的事情。这里推荐一本书《Java数据结构和算法》,这本书以一种易懂的方式教授如何安排和操纵数据的问题,它使用java语言说明重要的概念,而避免了C/C++语言的复杂性,以便集中精力论述数据结构和算法。经验丰富的作者RorbertLafore先生提供了许多简单明了的例子,避免了对于这类例题常见的冗长、繁锁的数学证明。在本书的每一章后都有问题和练习,使读者有机会测试自己的理解程度。


Java数据结构和算法



java中的数据结构总结

线性表,链表,哈希表是常用的数据结构,在进行java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。下面通过简单的描述,为你阐述各个类的作用以及如何正确使用这些类。

Collection
Collection

Map
Map

Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。

所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。

如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子  while(it.hasNext()) {  Object obj = it.next(); // 得到下一个元素  } 
由Collection接口派生的两个接口是List和Set。


主要方法:

1、boolean add(Object o)添加对象到集合


2、boolean remove(Object o)删除指定的对象


3、int size()返回当前集合中元素的数量

4、boolean contains(Object o)查找集合中是否有指定的对象

5、boolean isEmpty()判断集合是否为空

6、Iterator iterator()返回一个迭代器

7、boolean containsAll(Collection c)查找集合中是否有集合c中的元素

8、boolean addAll(Collection c)将集合c中所有的元素添加给该集合

9、void clear()删除集合中所有元素

10、void removeAll(Collection c)从集合中删除c集合中也有的元素

11、void retainAll(Collection c)从集合中删除集合c中不包含的元素


List接口

List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

和下面要提到的Set不同,List允许有相同的元素。

除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。

实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。


主要方法:

1、void add(int index,Object element)在指定位置上添加一个对象

2、boolean addAll(int index,Collection c)将集合c的元素添加到指定的位置

3、Object get(int index)返回List中指定位置的元素

4、int indexOf(Object o)返回第一个出现元素o的位置.

5、Object removeint(int index)删除指定位置的元素

6、Object set(int index,Object element)用元素element取代位置index上的元素,返回被取代的元素


LinkedList类

LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(...)); 

ArrayList类

ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。

size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。

每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

和LinkedList一样,ArrayList也是非同步的(unsynchronized)。


主要方法:

1、Boolean add(Object o)将指定元素添加到列表的末尾

2、Boolean add(int index,Object element)在列表中指定位置加入指定元素

3、Boolean addAll(Collection c)将指定集合添加到列表末尾

4、Boolean addAll(int index,Collection c)在列表中指定位置加入指定集合

5、Boolean clear()删除列表中所有元素

6、Boolean clone()返回该列表实例的一个拷贝

7、Boolean contains(Object o)判断列表中是否包含元素

8、Boolean ensureCapacity(int m)增加列表的容量,如果必须,该列表能够容纳m个元素

9、Object get(int index)返回列表中指定位置的元素

10、Int indexOf(Object elem)在列表中查找指定元素的下标

11、Int size()返回当前列表的元素个数


Vector类

Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。


Stack 类
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Set接口
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。

很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。

请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。


主要方法:

1、boolean equals(Object o)比较对象

2、boolean remove(Object o)删除一个对象

3、put(Object key,Object value)添加key和value


Hashtable类

Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。

添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。

Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。

使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:

Hashtable numbers = new Hashtable();  numbers.put(“one”, new Integer(1));  numbers.put(“two”, new Integer(2));  numbers.put(“three”, new Integer(3)); 


要取出一个数,比如2,用相应的key:

Integer n = (Integer)numbers.get(“two”);  System.out.println(“two = ” + n); 

由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。

如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。

Hashtable是同步的。


HashMap类
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

WeakHashMap类
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。

总结
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。

如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。

要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。

尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

XML (eXtensible Markup Language) 意为可扩展标记语言,被多数技术人员用以选择作为数据传输的载体,成为一种通用的数据交换格式,xml的平台无关性,语言无关性,系统无关性,给数据集成与交互带来了极大的便利。在不同的语言中,解析xml的方式都是一样的,只不过实现的语法不同而已。众所周知,现在解析XML的方法越来越多,但主流的方法也就四种,即:DOM、SAX、JDOM和DOM4J。


这四种方法的jar包下载地址:

①DOM:在现在的Java JDK里都自带了,在xml-apis.jar包里

②SAXhttp://sourceforge.net/projects/sax/

③JDOM:http://jdom.org/downloads/index.html

④DOM4J:http://sourceforge.net/projects/dom4j/


java


下面以一个实例来具体说明这4种方法:


xml文件:

<?xml version="1.0" encoding="GB2312"?><RESULT><VALUE>   <NO>A1234</NO>   <ADDR>四川省XX县XX镇XX路X段XX号</ADDR></VALUE><VALUE>   <NO>B1234</NO>   <ADDR>四川省XX市XX乡XX村XX组</ADDR></VALUE></RESULT>


1、使用DOM(JAXP Crimson解析器)

DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。DOM以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。


实现方法:

import java.io.*;import java.util.*;import org.w3c.dom.*;import javax.xml.parsers.*;public class MyXMLReader{ public static void main(String arge[]){  long lasting =System.currentTimeMillis();  try{   File f=new File("data_10k.xml");   DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();   DocumentBuilder builder=factory.newDocumentBuilder();   Document doc = builder.parse(f);   NodeList nl = doc.getElementsByTagName("VALUE");   for (int i=0;i<nl.getLength();i++){    System.out.print("车牌号码:" + doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue());    System.out.println("车主地址:" + doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue());   }  }catch(Exception e){   e.printStackTrace();}
【优点】
①允许应用程序对数据和结构做出更改。
②访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据。


【缺点】
通常需要加载整个XML文档来构造层次结构,消耗资源大。


2. 使用SAX

SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。

选择DOM还是选择SAX? 对于需要自己编写代码来处理XML文档的开发人员来说, 选择DOM还是SAX解析模型是一个非常重要的设计决策。 DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。

DOM解析器把XML文档转化为一个包含其内容的树,并可以对树进行遍历。用DOM解析模型的优点是编程容易,开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用DOM解析器的时候需要处理整个XML文档,所以对性能和内存的要求比较高,尤其是遇到很大的XML文件的时候。由于它的遍历能力,DOM解析器常用于XML文档需要频繁的改变的服务中。

SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag.特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展能力得到了更好的体现。但用SAX解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。

实现方法:
import org.xml.sax.*;import org.xml.sax.helpers.*;import javax.xml.parsers.*;public class MyXMLReader extends DefaultHandler { java.util.Stack tags = new java.util.Stack(); public MyXMLReader() {  super();} public static void main(String args[]) {  long lasting = System.currentTimeMillis();  try {   SAXParserFactory sf = SAXParserFactory.newInstance();   SAXParser sp = sf.newSAXParser();   MyXMLReader reader = new MyXMLReader();   sp.parse(new InputSource("data_10k.xml"), reader);  } catch (Exception e) {   e.printStackTrace();  }   System.out.println("运行时间:" + (System.currentTimeMillis() - lasting) + "毫秒");}  public void characters(char ch[], int start, int length) throws SAXException {  String tag = (String) tags.peek();  if (tag.equals("NO")) {   System.out.print("车牌号码:" + new String(ch, start, length));}if (tag.equals("ADDR")) {  System.out.println("地址:" + new String(ch, start, length));}}   public void startElement(String uri,String localName,String qName,Attributes attrs) {  tags.push(qName);}}
【优点】
①不需要等待所有数据都被处理,分析就能立即开始。
②只在读取数据时检查数据,不需要保存在内存中。
③可以在某个条件得到满足时停止解析,不必解析整个文档。
④效率和性能较高,能解析大于系统内存的文档。

【缺点】
①需要应用程序自己负责TAG的处理逻辑(例如维护父/子关系等),文档越复杂程序就越复杂。
②单向导航,无法定位文档层次,很难同时访问同一文档的不同部分数据,不支持XPath。

JDOM

3、使用JDOM

JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一个Java特定模型,JDOM一直得到大力推广和促进。正在考虑通过“Java规范请求JSR-102”将它最终用作“Java标准扩展”。从2000年初就已经开始了JDOM开发。

JDOM与DOM主要有两方面不同。首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。

JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”(根据学习曲线假定为20%)。JDOM对于大多数Java/XML应用程序来说当然是有用的,并且大多数开发者发现API比DOM容易理解得多。JDOM还包括对程序行为的相当广泛检查以防止用户做任何在XML中无意义的事。然而,它仍需要您充分理解XML以便做一些超出基本的工作(或者甚至理解某些情况下的错误)。这也许是比学习DOM或JDOM接口都更有意义的工作。

JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器以将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。JDOM是在Apache许可证变体下发布的开放源码。

实现方法:
import java.io.*;import java.util.*;import org.jdom.*;import org.jdom.input.*;public class MyXMLReader { public static void main(String arge[]) {  long lasting = System.currentTimeMillis();  try {   SAXBuilder builder = new SAXBuilder();   Document doc = builder.build(new File("data_10k.xml"));   Element foo = doc.getRootElement();   List allChildren = foo.getChildren();   for(int i=0;i<allChildren.size();i++) {    System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());    System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());   }  } catch (Exception e) {   e.printStackTrace();} }
【优点】
①使用具体类而不是接口,简化了DOM的API。
②大量使用了Java集合类,方便了Java开发人员。

【缺点】
①没有较好的灵活性。
②性能较差。

4、使用DOM4J

虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。

为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。

在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。

DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。

实现方法:
import java.io.*;import java.util.*;import org.dom4j.*;import org.dom4j.io.*; public class MyXMLReader {  public static void main(String arge[]) {  long lasting = System.currentTimeMillis();  try {   File f = new File("data_10k.xml");   SAXReader reader = new SAXReader();   Document doc = reader.read(f);   Element root = doc.getRootElement();   Element foo;   for (Iterator i = root.elementIterator("VALUE"); i.hasNext() {    foo = (Element) i.next();    System.out.print("车牌号码:" + foo.elementText("NO"));    System.out.println("车主地址:" + foo.elementText("ADDR"));   }  } catch (Exception e) {   e.printStackTrace();})
【优点】
①大量使用了Java集合类,方便Java开发人员,同时提供一些提高性能的替代方法。
②支持XPath。
③有很好的性能。

【缺点】
①大量使用了接口,API较为复杂。

DOM4J

4种方法综合对比

1. DOM4J性能最好,连Sun的JAXM也在用DOM4J。目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J。
     
2. JDOM和DOM在性能测试时表现不佳,在测试10M文档时内存溢出,但可移植。在小文档情况下还值得考虑使用DOM和JDOM.虽然JDOM的开发者已经说明他们期望在正式发行版前专注性能问题,但是从性能观点来看,它确实没有值得推荐之处。另外,DOM仍是一个非常好的选择。DOM实现广泛应用于多种编程语言。它还是许多其它与XML相关的标准的基础,因为它正式获得W3C推荐(与基于非标准的Java模型相对),所以在某些类型的项目中可能也需要它(如在JavaScript中使用DOM)。
     
3. SAX表现较好,这要依赖于它特定的解析方式-事件驱动。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。
     
建议:如果XML文档较大且不考虑移植性问题建议采用DOM4J;如果XML文档较小则建议采用JDOM;如果需要及时处理而不需要保存数据则考虑SAX。但无论如何,还是那句话:适合自己的才是最好的,如果时间允许,建议大家讲这四种方法都尝试一遍然后选择一种适合自己的即可。

读取XML配置文件

首先我们需要通过DocumentBuilderFactory获取xml文件的工厂实例。
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);

创建文档对象
DocumentBuilder db = dbf.newDocumentBuilder();            Document doc = db.parse(xmlPath); // 使用dom解析xml文件

最后遍历列表,进行数据提取
NodeList sonlist = doc.getElementsByTagName("son");             for (int i = 0; i < sonlist.getLength(); i++) // 循环处理对象            {                Element son = (Element)sonlist.item(i);;                                for (Node node = son.getFirstChild(); node != null; node = node.getNextSibling()){                      if (node.getNodeType() == Node.ELEMENT_NODE){                          String name = node.getNodeName();                          String value = node.getFirstChild().getNodeValue();                          System.out.println(name+" : "+value);                    }                  }              }

完整实例:
public static void getFamilyMemebers(){        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);        try {            DocumentBuilder db = dbf.newDocumentBuilder();            Document doc = db.parse(xmlPath); // 使用dom解析xml文件            NodeList sonlist = doc.getElementsByTagName("son");             for (int i = 0; i < sonlist.getLength(); i++) // 循环处理对象            {                Element son = (Element)sonlist.item(i);;                                for (Node node = son.getFirstChild(); node != null; node = node.getNextSibling()){                      if (node.getNodeType() == Node.ELEMENT_NODE){                          String name = node.getNodeName();                          String value = node.getFirstChild().getNodeValue();                          System.out.println(name+" : "+value);                    }                  }              }        } catch (Exception e) {            e.printStackTrace();        }    }

在XML文件中增加节点

用差不多同样的步骤,先获取根节点,创建一个新的节点,向其中添加元素信息,最后把这个新节点添加到根节点中
Element root = xmldoc.getDocumentElement();                        //删除指定节点                        Element son =xmldoc.createElement("son");            son.setAttribute("id", "004");                        Element name = xmldoc.createElement("name");            name.setTextContent("小儿子");            son.appendChild(name);            Element age = xmldoc.createElement("name");            age.setTextContent("0");            son.appendChild(age);                        root.appendChild(son);

最后不要忘记保存新增的文件,对源文件进行覆盖
TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));

完整实例:
public static void createSon() {        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(false);                try{                    DocumentBuilder db=dbf.newDocumentBuilder();            Document xmldoc=db.parse(xmlPath);                    Element root = xmldoc.getDocumentElement();                        //删除指定节点                        Element son =xmldoc.createElement("son");            son.setAttribute("id", "004");                        Element name = xmldoc.createElement("name");            name.setTextContent("小儿子");            son.appendChild(name);            Element age = xmldoc.createElement("name");            age.setTextContent("0");            son.appendChild(age);                        root.appendChild(son);            //保存            TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));                    }catch(Exception e){            e.printStackTrace();        }    }


在XML中修改节点信息

通过XPath来获取目标节点
public static Node selectSingleNode(String express, Element source) {        Node result=null;        XPathFactory xpathFactory=XPathFactory.newInstance();        XPath xpath=xpathFactory.newXPath();        try {            result=(Node) xpath.evaluate(express, source, XPathConstants.NODE);        } catch (XPathExpressionException e) {            e.printStackTrace();        }                return result;    }

获取目标节点,进行修改,完成后,保存文件
Element root = xmldoc.getDocumentElement();                        Element per =(Element) selectSingleNode("/father/son[@id='001']", root);            per.getElementsByTagName("age").item(0).setTextContent("27");                        TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));

完整实例:
public static void modifySon(){        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);        try{                    DocumentBuilder db=dbf.newDocumentBuilder();            Document xmldoc=db.parse(xmlPath);                    Element root = xmldoc.getDocumentElement();                        Element per =(Element) selectSingleNode("/father/son[@id='001']", root);            per.getElementsByTagName("age").item(0).setTextContent("27");                        TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));        }catch(Exception e){            e.printStackTrace();        }    }

删除XML中的节点

通过XPath获取目标节点, 进行删除,最后保存
Element root = xmldoc.getDocumentElement();                        Element son =(Element) selectSingleNode("/father/son[@id='002']", root);            root.removeChild(son);            TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));

完整实例:
public static void discardSon(){                    DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);                try{                    DocumentBuilder db=dbf.newDocumentBuilder();            Document xmldoc=db.parse(xmlPath);                    Element root = xmldoc.getDocumentElement();                        Element son =(Element) selectSingleNode("/father/son[@id='002']", root);            root.removeChild(son);            TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));                    }catch(Exception e){            e.printStackTrace();        }    }



协程(Coroutine)这个词其实有很多叫法,比如有的人喜欢称为纤程(Fiber),或者绿色线程(GreenThread)。其实究其本质,对于协程最直观的解释是线程的线程。虽然读上去有点拗口,但本质上就是这样。

协程的核心在于调度那块由他来负责解决,遇到阻塞操作,立刻放弃掉,并且记录当前栈上的数据,阻塞完后立刻再找一个线程恢复栈并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为Fiber。


java协程的实现

早期,在JVM上实现协程一般会使用kilim,不过这个工具已经很久不更新了,现在常用的工具是Quasar,而本文章会全部基于Quasar来介绍。

下面尝试通过Quasar来实现类似于go语言的coroutine以及channel。

为了能有明确的对比,这里先用go语言实现一个对于10以内自然数分别求平方的例子。

func counter(out chan<- int) {  for x := 0; x < 10; x++ {    out <- x  }  close(out)}func squarer(out chan<- int, in <-chan int) {  for v := range in {    out <- v * v  }  close(out)}func printer(in <-chan int) {  for v := range in {    fmt.Println(v)  }}func main() {  //定义两个int类型的channel  naturals := make(chan int)  squares := make(chan int)  //产生两个Fiber,用go关键字  go counter(naturals)  go squarer(squares, naturals)  //获取计算结果  printer(squares)}

上面这个例子,通过channel两解耦两边的数据共享。对于这个channel,大家可以理解为Java里的SynchronousQueue。下面我直接上Quasar版JAVA代码的,几乎可以原封不动的复制go语言的代码。

public class Example {  private static void printer(Channel<Integer> in) throws SuspendExecution,  InterruptedException {    Integer v;    while ((v = in.receive()) != null) {      System.out.println(v);    }  }  public static void main(String[] args) throws ExecutionException, InterruptedException, SuspendExecution {    //定义两个Channel    Channel<Integer> naturals = Channels.newChannel(-1);    Channel<Integer> squares = Channels.newChannel(-1);    //运行两个Fiber实现.    new Fiber(() -> {      for (int i = 0; i < 10; i++)        naturals.send(i);      naturals.close();    }).start();    new Fiber(() -> {      Integer v;      while ((v = naturals.receive()) != null)        squares.send(v * v);      squares.close();    }).start();    printer(squares);  }}

两者对比,看上去Java似好像更复杂些,没办法这就是Java的风格,而且这还是通过第三方的库来实现的。

说到这里各位肯定对Fiber很好奇了。也许你会表示怀疑Fiber是不是如上面所描述的那样,下面我们尝试用Quasar建立一百万个Fiber,看看内存占用多少,我先尝试了创建百万个Thread。

for (int i = 0; i < 1_000_000; i++) {  new Thread(() -> {    try {      Thread.sleep(10000);    } catch (InterruptedException e) {      e.printStackTrace();    }  }).start();}

很不幸,直接报Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread,这是情理之中的。下面是通过Quasar建立百万个Fiber。

public static void main(String[] args) throws ExecutionException, InterruptedException, SuspendExecution {  int FiberNumber = 1_000_000;  CountDownLatch latch = new CountDownLatch(1);  AtomicInteger counter = new AtomicInteger(0);  for (int i = 0; i < FiberNumber; i++) {    new Fiber(() -> {      counter.incrementAndGet();      if (counter.get() == FiberNumber) {        System.out.println("done");      }      Strand.sleep(1000000);    }).start();  }  latch.await();}

我这里加了latch,阻止程序跑完就关闭,Strand.sleep其实跟Thread.sleep一样,只是这里针对的是Fiber。

最终控制台是可以输出done的,说明程序已经创建了百万个Fiber,设置Sleep是为了让Fiber一直运行,从而方便计算内存占用。官方宣称一个空闲的Fiber大约占用400Byte,那这里应该是占用400MB堆内存,但是这里通过jmap -heap pid显示大约占用了1000MB,也就是说一个Fiber占用1KB。


Quasar是怎么实现Fiber的

其实Quasar实现的coroutine的方式与Go语言很像,只不过前者是使用框架来实现,而go语言则是语言内置的功能。

不过如果你熟悉了Go语言的调度机制的话,那么对于Quasar的调度机制就会好理解很多了,因为两者有很多相似之处。

Quasar里的Fiber其实是一个continuation,他可以被Quasar定义的scheduler调度,一个continuation记录着运行实例的状态,而且会被随时中断,并且也会随后在他被中断的地方恢复。

Quasar其实是通过修改bytecode来达到这个目的,所以运行Quasar程序的时候,你需要先通过java-agent在运行时修改你的代码,当然也可以在编译期间这么干。go语言的内置了自己的调度器,而Quasar则是默认使用ForkJoinPool这个具有work-stealing功能的线程池来当调度器。work-stealing非常重要,因为你不清楚哪个Fiber会先执行完,而work-stealing可以动态的从其他的等等队列偷一个context过来,这样可以最大化使用CPU资源。

那这里你会问了,Quasar怎么知道修改哪些字节码呢,其实也很简单,Quasar会通过java-agent在运行时扫描哪些方法是可以中断的,同时会在方法被调用前和调度后的方法内插入一些continuation逻辑,如果你在方法上定义了@Suspendable注解,那Quasar会对调用该注解的方法做类似下面的事情。

这里假设你在方法f上定义了@Suspendable,同时去调用了有同样注解的方法g,那么所有调用f的方法会插入一些字节码,这些字节码的逻辑就是记录当前Fiber栈上的状态,以便在未来可以动态的恢复。(Fiber类似线程也有自己的栈)。在suspendable方法链内Fiber的父类会调用Fiber.park,这样会抛出SuspendExecution异常,从而来停止线程的运行,好让Quasar的调度器执行调度。这里的SuspendExecution会被Fiber自己捕获,业务层面上不应该捕获到。如果Fiber被唤醒了(调度器层面会去调用Fiber.unpark),那么f会在被中断的地方重新被调用(这里Fiber会知道自己在哪里被中断),同时会把g的调用结果(g会return结果)插入到f的恢复点,这样看上去就好像g的return是f的local variables了,从而避免了callback嵌套。

上面说了一大堆,其实简单点来讲就是,想办法让运行中的线程栈停下来,然后让Quasar的调度器介入。

JVM线程中断的条件有两个:

1、抛异常

2、return。

而在Quasar中,一般就是通过抛异常的方式来达到的,所以你会看到上面的代码会抛出SuspendExecution。但是如果你真捕获到这个异常,那就说明有问题了,所以一般会这么写。

@Suspendablepublic int f() {  try {    // do some stuff    return g() * 2;  } catch(SuspendExecution s) {    //这里不应该捕获到异常.    throw new AssertionError(s);  }}


Coroutine in Java - Quasar Fiber实现 

Quasar Fiber则是通过字节码修改技术在编译或载入时织入必要的上下文保存/恢复代码,通过抛异常来暂停,恢复的时候根据保存的上下文(Continuation),恢复jvm的方法调用栈和局部变量,Quasar Fiber提供相应的Java类库来实现,对应用有一定的侵入性(很小)

Quasar Fiber 主要有 Instrument + Continuation + Scheduler几个部分组成

  • Instrument 做一些代码的植入,如park前后上下文的保存/恢复等
  • Continuation 保存方法调用的信息,如局部变量,引用等,用户态的stack,这个也是跟akka等基于固定callback接口的异步框架最大的区别
  • Scheduler 调度器,负责将fiber分配到具体的os thread执行



相关阅读:

JAVA微课——像玩游戏般学习java

JAVA多线程编程


这份备忘单是 Java 初学者的速成课程,有助于复习 Java 语言的基本语法。

开始

hello.java

public class Hello {  // main methord  public static void main(String[] args)  {    // Output: Hello, world!    System.out.println("Hello, world!");  }}

编译和运行

$ javac Hello.java$ java HelloHello, world!

变量

int num = 5;float floatNum = 5.99f;char letter = 'D';boolean bool = true;String site = "quickref.me";

原始数据类型

数据类型尺寸默认范围
byte1 字节0-128 127
short2 字节0-2 15 ~ 2 15 -1
int4 字节0-2 31 ~ 2 31 -1
long8 字节0-2 63 ~ 2 63 -1
float4 字节0.0fN/A
double8 字节0.0dN/A
char2 字节u00000 ~ 65535
booleanN/A
fasletrue/false

字符串

String first = "John";String last = "Doe";String name = first + " " + last;System.out.println(name);

请参阅:字符串

循环

String word = "QuickRef";for (char c: word.toCharArray()) {  System.out.print(c + "-");}// Outputs: Q-u-i-c-k-R-e-f-

请参阅:循环

数组

char[] chars = new char[10];chars[0] = 'a'chars[1] = 'b'String[] letters = {"A", "B", "C"};int[] mylist = {100, 200};boolean[] answers = {true, false};

请参阅:数组

交换

int a = 1;int b = 2;System.out.println(a + " " + b); // 1 2int temp = a;a = b;b = temp;System.out.println(a + " " + b); // 2 1

类型转换

// Widening// byte<short<int<long<float<doubleint i = 10;long l = i;               // 10// Narrowing double d = 10.02;long l = (long)d;         // 10String.valueOf(10);       // "10"Integer.parseInt("10");   // 10Double.parseDouble("10"); // 10.0

条件句

int j = 10;if (j == 10) {  System.out.println("I get printed");} else if (j > 10) {  System.out.println("I don't");} else {  System.out.println("I also don't");}

请参阅:条件

用户输入

Scanner in = new Scanner(System.in);String str = in.nextLine();System.out.println(str);int num = in.nextInt();System.out.println(num);

Java 字符串

基本的

String str1 = "value"; String str2 = new String("value");String str3 = String.valueOf(123);

级联

String s = 3 + "str" + 3;     // 3str3String s = 3 + 3 + "str";     // 6strString s = "3" + 3 + "str";   // 33strString s = "3" + "3" + "23";  // 3323String s = "" + 3 + 3 + "23"; // 3323String s = 3 + 3 + 23;        // 29

字符串生成器

StringBuilder sb = new StringBuilder(10);

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐|   |   |   |   |   |   |   |   |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.append("QuickRef");

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| Q | u | i | c | k | R | e | f |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.delete(5, 9);

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| Q | u | i | c | k |   |   |   |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.insert(0, "我的");

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| M | y |   | Q | u | i | c | k |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.append("!");

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| M | y |   | Q | u | i | c | k | ! |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

比较

String s1 = new String("QuickRef"); String s2 = new String("QuickRef"); s1 == s2          // falses1.equals(s2)     // true"AB".equalsIgnoreCase("ab")  // true

操作

String str = "Abcd";str.toUpperCase();     // ABCDstr.toLowerCase();     // abcdstr.concat("#");       // Abcd#str.replace("b", "-"); // A-cd"  abc ".trim();       // abc"ab".toCharArray();    // {'a', 'b'}

信息

String str = "abcd";str.charAt(2);       // cstr.indexOf("a")     // 0str.indexOf("z")     // -1str.length();        // 4str.toString();      // abcdstr.substring(2);    // cdstr.substring(2,3);  // cstr.contains("c");   // truestr.endsWith("d");   // truestr.startsWith("a"); // truestr.isEmpty();       // false

不可变

String str = "hello";str.concat("world");// Outputs: helloSystem.out.println(str);

String str = "hello";String concat = str.concat("world");// Outputs: helloworldSystem.out.println(concat);

一旦创建不能修改,任何修改都会创建一个新的String

Java 数组

声明

int[] a1;int[] a2 = {1, 2, 3};int[] a3 = new int[]{1, 2, 3};int[] a4 = new int[3];a4[0] = 1;a4[2] = 2;a4[3] = 3;

调整

int[] a = {1, 2, 3};System.out.println(a[0]); // 1a[0] = 9;System.out.println(a[0]); // 9System.out.println(a.length); // 3

循环(读取和修改)

int[] arr = {1, 2, 3};for (int i=0; i < arr.length; i++) {    arr[i] = arr[i] * 2;    System.out.print(arr[i] + " ");}// Outputs: 2 4 6

循环(读取)

String[] arr = {"a", "b", "c"};for (int a: arr) {    System.out.print(a + " ");}// Outputs: a b c 

多维数组

int[][] matrix = { {1, 2, 3}, {4, 5} };int x = matrix[1][0];  // 4// [[1, 2, 3], [4, 5]]Arrays.deepToString(matrix)for (int i = 0; i < a.length; ++i) {  for(int j = 0; j < a[i].length; ++j) {    System.out.println(a[i][j]);  }}// Outputs: 1 2 3 4 5 6 7 

类型

char[] chars = {'b', 'a', 'c'};Arrays.sort(chars);// [a, b, c]Arrays.toString(chars);

Java 条件

运算符

if-else 语句

int k = 15;if (k > 20) {  System.out.println(1);} else if (k > 10) {  System.out.println(2);} else {  System.out.println(3);}

switch 语句

int month = 3;String str;switch (month) {  case 1:    str = "January";    break;  case 2:    str = "February";    break;  case 3:    str = "March";    break;  default:    str = "Some other month";    break;}// Outputs: Result MarchSystem.out.println("Result " + str);

三元运算符

int a = 10;int b = 20;int max = (a > b) ? a : b;// Outputs: 20System.out.println(max);

Java 循环

For循环

for (int i = 0; i < 10; i++) {  System.out.print(i);}// Outputs: 0123456789

for (int i = 0,j = 0; i < 3; i++,j--) {  System.out.print(j + "|" + i + " ");}// Outputs: 0|0 -1|1 -2|2

For-each 循环

int[] numbers = {1,2,3,4,5};for (int number: numbers) {  System.out.print(number);}// Outputs: 12345

用于循环数组或列表

While 循环

int count = 0;while (count < 5) {  System.out.print(count);  count++;}// Outputs: 01234

Do While 循环

int count = 0;do{  System.out.print(count);  count++;} while (count < 5);// Outputs: 01234

Continue

for (int i = 0; i < 5; i++) {  if (i == 3) {    continue;  }  System.out.print(i);}// Outputs: 01245

Break

for (int i = 0; i < 5; i++) {  System.out.print(i);  if (i == 3) {    break;  }}// Outputs: 0123

Java 集合框架

Java 集合

集合接口可组织可排序线程安全可复制可为空
ArrayListListYNNYY
VectorListYNYYY
LinkedListList, DequeYNNYY
HashSetSetNNNNOne null
LinkedHashSetSetYNNNOne null
TreeSetSetYYNNN
HashMapMapNNNN (key)One null (key)
HashTableMapNNYN (key)N (key)
LinkedHashMapMapYNNN (key)One null (key)
TreeMapMapYYNN (key)N (key)
ArrayDequeDequeYNNYN
PriorityQueueQueueYNNYN

数组列表

List<Integer> nums = new ArrayList<>();// Addingnums.add(2);nums.add(5);nums.add(8);// RetrievingSystem.out.println(nums.get(0));// Indexed for loop iterationfor (int i = 0; i < nums.size(); i++) {    System.out.println(nums.get(i));}nums.remove(nums.size() - 1);nums.remove(0); // VERY slowfor (Integer value : nums) {    System.out.println(value);}

哈希表

Map<Integer, String> m = new HashMap<>();m.put(5, "Five");m.put(8, "Eight");m.put(6, "Six");m.put(4, "Four");m.put(2, "Two");// RetrievingSystem.out.println(m.get(6));// Lambda forEachm.forEach((key, value) -> {    String msg = key + ": " + value;    System.out.println(msg);});

哈希集

Set<String> set = new HashSet<>();if (set.isEmpty()) {    System.out.println("Empty!");}set.add("dog");set.add("cat");set.add("mouse");set.add("snake");set.add("bear");if (set.contains("cat")) {    System.out.println("Contains cat");}set.remove("cat");for (String element : set) {    System.out.println(element);}

数组Deque

Deque<String> a = new ArrayDeque<>();// Using add()a.add("Dog");// Using addFirst()a.addFirst("Cat");// Using addLast()a.addLast("Horse");// [Cat, Dog, Horse]System.out.println(a);// Access elementSystem.out.println(a.peek());// Remove elementSystem.out.println(a.pop());

杂项

访问修饰符

修饰符子类全局
publicYYYY
protectedYYYN
no modifierYYNN
privateYNNN

常用表达

String text = "I am learning Java";// Removing All Whitespacetext.replaceAll("s+", "");// Splitting a Stringtext.split("|");text.split(Pattern.quote("|"));

请参阅:Java 中的正则表达式

文本注释

// I am a single line comment! /*And I am a multi-line comment!*//** * This   * is   * documentation   * comment  */

关键词

  • abstract
  • continue
  • for
  • new
  • switch
  • assert
  • default
  • goto
  • package
  • synchronized
  • boolean
  • do
  • if
  • private
  • this
  • break
  • double
  • implements
  • protected
  • throw
  • byte
  • else
  • import
  • public
  • throws
  • case
  • enum
  • instanceof
  • return
  • transient
  • catch
  • extends
  • int
  • short
  • try
  • char
  • final
  • interface
  • static
  • void
  • class
  • finally
  • long
  • strictfp
  • volatile
  • const
  • float
  • native
  • super
  • while

数学方法

方法描述
Math.max(a,b)a 和 b 的最大值
Math.min(a,b)a 和 b 的最小值
Math.abs(a)绝对值a
Math.sqrt(a)a 的平方根
Math.pow(a,b)a的b次方
Math.round(a)最接近的整数
Math.sin(ang)正弦
Math.cos(ang)ang的余弦
Math.tan(ang)ang 的切线
Math.asin(ang)ang 的反正弦
Math.log(a)a 的自然对数
Math.toDegrees(rad)角度 rad
Math.toRadians(deg)以弧度为单位的角度度

try/catch/finally

try {  // something} catch (Exception e) {  e.printStackTrace();} finally {  System.out.println("always printed");}



java

Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。

Java可运行于多个平台,如Windows, Mac OS,及其他多种UNIX版本的系统。

本教程通过简单的实例将让大家更好的了解JAVA编程语言。

Java 在线工具   JDK 在线中文手册


我的第一个JAVA程序

以下我们通过一个简单的实例来展示Java编程,本实例输出"Hello World",这也是所有语言入门的第一个实例程序:

public class Main{    public static void main(String []args) {       System.out.println("Hello World");    }} 

开始学习JAVA编程


Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称。由詹姆斯·高斯林(James Gosling)和同事们共同研发,并在1995年正式推出。

Java分为三个体系:

  • JavaSE(J2SE)(Java2 Platform Standard Edition,java平台标准版)
  • JavaEE(J2EE)(Java 2 Platform,Enterprise Edition,java平台企业版)
  • JavaME(J2ME)(Java 2 Platform Micro Edition,java平台微型版)

2005年6月,JavaOne大会召开,SUN公司公开Java SE 6。此时,Java的各种版本已经更名以取消其中的数字"2":J2EE更名为Java EE, J2SE更名为Java SE,J2ME更名为Java ME。

2009年,sun公司被oracle收购.

2018年,开源组织Eclipse基金会宣布将JavaEE(Enterprise Edition)被更名为JakartaEE(雅加达)。


主要特性

  • Java语言是简单的:

    Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java丢弃了C++中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java语言不使用指针,而是引用。并提供了自动的内存回收管理机制,使得程序员不必为内存管理而担忧。

  • Java语言是面向对象的:

    Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为implements)。Java语言全面支持动态绑定,而C++语言只对虚函数使用动态绑定。总之,Java语言是一个纯的面向对象程序设计语言。

  • Java语言是分布式的:

    Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。

  • Java语言是健壮的:

    Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。Java的安全检查机制使得Java更具健壮性。

  • Java语言是安全的:

    Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。除了Java语言具有的许多安全特性以外,Java对通过网络下载的类具有一个安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类SecurityManager)让Java应用设置安全哨兵。

  • Java语言是体系结构中立的:

    Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。

  • Java语言是可移植的:

    这种可移植性来源于体系结构中立性,另外,Java还严格规定了各个基本数据类型的长度。Java系统本身也具有很强的可移植性,Java编译器是用Java实现的,Java的运行环境是用ANSI C实现的。

  • Java语言是解释型的:

    如前所述,Java程序在Java平台上被编译为字节码格式,然后可以在实现这个Java平台的任何系统中运行。在运行时,Java平台中的Java解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。

  • Java是高性能的:

    与那些解释型的高级脚本语言相比,Java的确是高性能的。事实上,Java的运行速度随着JIT(Just-In-Time)编译器技术的发展越来越接近于C++。

  • Java语言是多线程的:

    在Java语言中,线程是一种特殊的对象,它必须由Thread类或其子(孙)类来创建。通常有两种方法来创建线程:其一,使用型构为Thread(Runnable)的构造子将一个实现了Runnable接口的对象包装成一个线程,其二,从Thread类派生出子类并重写run方法,使用该子类创建的对象即为线程。值得注意的是Thread类已经实现了Runnable接口,因此,任何一个线程均有它的run方法,而run方法中包含了线程所要运行的代码。线程的活动由一组方法来控制。Java语言支持多个线程的同时执行,并提供多线程之间的同步机制(关键字为synchronized)。

  • Java语言是动态的:

    Java语言的设计目标之一是适应于动态变化的环境。Java程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java中的类有一个运行时刻的表示,能进行运行时刻的类型检查。


发展历史

  • 1995年5月23日,Java语言诞生
  • 1996年1月,第一个JDK-JDK1.0诞生
  • 1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入JAVA技术
  • 1996年9月,约8.3万个网页应用了JAVA技术来制作
  • 1997年2月18日,JDK1.1发布
  • 1997年4月2日,JavaOne会议召开,参与者逾一万人,创当时全球同类会议规模之纪录
  • 1997年9月,JavaDeveloperConnection社区成员超过十万
  • 1998年2月,JDK1.1被下载超过2,000,000次
  • 1998年12月8日,JAVA2企业平台J2EE发布
  • 1999年6月,SUN公司发布Java的三个版本:标准版(JavaSE,以前是J2SE)、企业版(JavaEE以前是J2EE)和微型版(JavaME,以前是J2ME)
  • 2000年5月8日,JDK1.3发布
  • 2000年5月29日,JDK1.4发布
  • 2001年6月5日,NOKIA宣布,到2003年将出售1亿部支持Java的手机
  • 2001年9月24日,J2EE1.3发布
  • 2002年2月26日,J2SE1.4发布,自此Java的计算能力有了大幅提升
  • 2004年9月30日18:00PM,J2SE1.5发布,成为Java语言发展史上的又一里程碑。为了表示该版本的重要性,J2SE1.5更名为Java SE 5.0
  • 2005年6月,JavaOne大会召开,SUN公司公开Java SE 6。此时,Java的各种版本已经更名,以取消其中的数字"2":J2EE更名为Java EE,J2SE更名为Java SE,J2ME更名为Java ME
  • 2006年12月,SUN公司发布JRE6.0
  • 2009年04月20日,甲骨文74亿美元收购Sun。取得java的版权。
  • 2010年11月,由于甲骨文对于Java社区的不友善,因此Apache扬言将退出JCP。
  • 2011年7月28日,甲骨文发布java7.0的正式版。
  • 2014 年 3 月 18 日,Oracle 公司发表 Java SE 8。
  • 2017 年 9 月 21 日,Oracle 公司发表 Java SE 9
  • 2018 年 3 月 21 日,Oracle 公司发表 Java SE 10
  • 2018 年 9 月 25 日,Java SE 11 发布
  • 2019 年 3 月 20 日,Java SE 12 发布

Java开发工具

Java语言尽量保证系统内存在1G以上,其他工具如下所示:

  • Linux 系统或者Windows 95/98/2000/XP,WIN 7/8/10/11系统
  • Java JDK 8以上版本(这是必须的)
  • Notepad编辑器或者其他编辑器(虽然使用记事本也可以进行代码编写,但一个舒适的工具可以提高编程的效率,小编这里推荐使用vscode)。
  • IDE:Eclipse或者IntelliJ IDEA(对于初学者而言,这两款IDE都很不错,但却不是必要的(初学者使用到的功能比较少,不需要用到这么多功能))

安装好以上的工具后,我们就可以输出Java的第一个程序"Hello World!"

public class MyFirstJavaProgram {    public static void main(String []args) {       System.out.println("Hello World");    }} 

在下一章节我们将介绍如何配置java开发环境。


在本章节中我们将为大家介绍如何搭建Java开发环境,以及不同系统下的环境变量怎么配置。

 本站提供java在线运行工具:https://www.51coolma.cn/tryrun/runcode?lang=java-openjdk,但由于在线环境权限不足,大部分代码都不能直接在线运行,所以搭好一个本地可运行的环境相当重要!!!


window系统安装java

下载JDK

首先我们需要下载java开发工具包JDK,下载地址:https://www.oracle.com/java/technologies/downloads/#java11-windows

点击如下下载按钮:

java下载页面

在下载页面中你需要选择接受许可,并根据自己的系统选择对应的版本,本文以 Window 64位系统为例:

选择版本

下载后JDK的安装根据提示进行,还有安装JDK的时候也会安装JRE,一并安装就可以了。

安装JDK,安装过程中可以自定义安装目录等信息,例如我们选择安装目录为 C:Program Files (x86)Javajdk11.0.1。

配置环境变量

1.右击“我的电脑”→“属性”→“高级系统设置”→“高级”→“环境变量”;


高级系统设置


2.选择"高级"选项卡,点击"环境变量";
4

3. 新建“JAVA_HOME”系统变量(点击“系统变量”下方的“新建”按钮,填写变量名与变量值,点击“确定”)

12



8


4. 同上,新建“CLASSPATH”系统变量,变量值为“.;%JAVA_HOME%lib;%JAVA_HOME%libdt.jar;%JAVA_HOME%lib ools.jar;”。(引号内的全部内容,注意最前方是点不是逗号)

9


5. 双击“系统变量”下的“Path”变量进行编辑。(此时可以看到JAVA_HOME已经存在于系统变量中),(有的电脑"Path"也写作“PATH”)

10


这是 Java 的环境配置,配置完成后,你可以启动 Eclipse 来编写代码,它会自动完成java环境的配置。

在"系统变量"中设置3项属性,JAVA_HOME,PATH,CLASSPATH(大小写无所谓),若已存在则点击"编辑",不存在则点击"新建"。

变量设置参数如下:

  • 变量名:JAVA_HOME
  • 变量值:C:Program Files (x86)Javajdk1.8.0_91        // 要根据自己的实际路径配置
  • 变量名:CLASSPATH
  • 变量值:.;%JAVA_HOME%libdt.jar;%JAVA_HOME%lib ools.jar;         //记得前面有个"."
  • 变量名:Path
  • 变量值:%JAVA_HOME%in;%JAVA_HOME%jrein;

注意:如果使用1.5以上版本的JDK,不用设置CLASSPATH环境变量,也可以正常编译和运行Java程序。

通过控制台测试JDK是否安装成功

1、同时按键盘上“win”、“R”两个键打开运行,输入“cmd”确定打开控制台。

11

2、键入命令: java -versionjavajavac 几个命令,出现以下信息,说明环境变量配置成功;


jdk安装

Linux,UNIX,Solaris,FreeBSD环境变量设置

环境变量PATH应该设定为指向Java二进制文件安装的位置。如果设置遇到困难,请参考shell文档。

例如,假设你使用bash作为shell,你可以把下面的内容添加到你的 .bashrc文件结尾: export PATH=/path/to/java:$PATH


流行JAVA开发工具

正所谓工欲善其事必先利其器,我们在开发java语言过程中同样需要一款不错的开发工具,目前市场上的IDE很多,本文为大家推荐以下几款java开发工具:

13

  • IntelliJ IDEA(推荐):一个好用的java IDE,专业版功能强大但需要付费,开源版基础功能足够雄厚让你有更好的代码开发体验

      下载地址:https://www.jetbrains.com/zh-cn/idea/

Notepad++ : Notepad++ 是在微软windows环境之下的一个免费的代码编辑器,下载地址: http://notepad-plus-plus.org/

此外还有很多优秀的代码编辑器,比如vscode,sublime,vim等,在此不做过多介绍。

使用 Eclipse 运行第一个 Java 程序

HelloWorld.java 文件代码:

public class HelloWorld {    public static void main(String []args) {       System.out.println("Hello World");    }}


一个Java程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • :类是一个模板,它描述一类对象的行为和状态。
  • 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
  • 实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。

第一个Java程序

下面看一个简单的 Java 程序,它将打印字符串 Hello World

public class MyFirstJavaProgram {   /* 第一个Java程序.      * 它将打印字符串 Hello World    */    public static void main(String []args) {       System.out.println("Hello World"); // 打印 Hello World    }} 

下面将逐步介绍如何保存、编译以及运行这个程序:

  • 打开Notepad,把上面的代码添加进去;
  • 把文件名保存为:MyFirstJavaProgram.java;
  • 打开cmd命令窗口,进入目标文件所在的位置,假设是C:
  • 在命令行窗口键入 javac MyFirstJavaProgram.java  按下 enter 键编译代码。如果代码没有错误,cmd 命令提示符会进入下一行。(假设环境变量都设置好了)。
  • 再键入 java MyFirstJavaProgram 按下 Enter 键就可以运行程序了

你将会在窗口看到 Hello World

C : > javac MyFirstJavaProgram.javaC : > java MyFirstJavaProgram Hello World

基本语法

编写Java程序时,应注意以下几点:

  • 大小写敏感:Java是大小写敏感的,这就意味着标识符Hello与hello是不同的。
  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass 。
  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
  • 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记Java是大小写敏感的),文件名的后缀为.java。(如果文件名和类名不相同则会导致编译错误)。
  • 主方法入口:所有的Java 程序由public static void main(String[] args)​ 方法开始执行。

Java标识符

Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

关于Java标识符,有以下几点需要注意:

  • 所有的标识符都应该以字母(A-Z或者a-z),美元符($)、或者下划线(_)开始
  • 首字符之后可以是字母(A-Z 或者 a-z),美元符($)、下划线(_)或数字的任何字符组合
  • 关键字不能用作标识符
  • 标识符是大小写敏感的
  • 合法标识符举例:age、$salary、_value、__1_value
  • 非法标识符举例:123abc、-salary

Java修饰符

像其他语言一样,Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:

  • 访问控制修饰符 : default, public , protected, private
  • 非访问控制修饰符 : final, abstract, static,synchronized 和 volatile

在后面的章节中我们会深入讨论Java修饰符。

Java变量

Java中主要有如下几种类型的变量

  • 局部变量
  • 类变量(静态变量)
  • 成员变量(非静态变量)

Java数组

数组是储存在堆上的对象,可以保存多个同类型变量。在后面的章节中,我们将会学到如何声明、构造以及初始化一个数组。


Java枚举

Java 5.0引入了枚举,枚举限制变量只能是预先设定好的值。使用枚举可以减少代码中的 bug 。

例如,我们为果汁店设计一个程序,它将限制果汁为小杯、中杯、大杯。这就意味着它不允许顾客点除了这三种尺寸外的果汁。


实例

class FreshJuice {   enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }   FreshJuiceSize size;}public class FreshJuiceTest {   public static void main(String args[]){      FreshJuice juice = new FreshJuice();      juice.size = FreshJuice. FreshJuiceSize.MEDIUM ;   }}

注意:枚举可以单独声明或者声明在类里面。方法、变量、构造函数也可以在枚举中定义。


Java关键字

下面列出了Java保留字。这些保留字不能用于常量、变量、和任何标识符的名称。

关键字 描述
abstract 抽象方法,抽象类的修饰符
assert 断言条件是否满足
boolean 布尔数据类型
break 跳出循环或者label代码段
byte 8-bit 有符号数据类型
case switch语句的一个条件
catch 和try搭配捕捉异常信息
char 16-bit Unicode字符数据类型
class 定义类
const 未使用
continue 不执行循环体剩余部分
default switch语句中的默认分支
do 循环语句,循环体至少会执行一次
double 64-bit双精度浮点数
else if条件不成立时执行的分支
enum 枚举类型
extends 表示一个类是另一个类的子类
final 表示一个值在初始化之后就不能再改变了
表示方法不能被重写,或者一个类不能有子类
finally 为了完成执行的代码而设计的,主要是为了程序的健壮性和完整性,无论有没有异常发生都执行代码。
float 32-bit单精度浮点数
for for循环语句
goto 未使用
if 条件语句
implements 表示一个类实现了接口
import 导入类
instanceof 测试一个对象是否是某个类的实例
int 32位整型数
interface 接口,一种抽象的类型,仅有方法和常量的定义
long 64位整型数
native 表示方法用非java代码实现
new 分配新的类实例
package 一系列相关类组成一个包
private 表示私有字段,或者方法等,只能从类内部访问
protected 表示字段只能通过类或者其子类访问
子类或者在同一个包内的其他类
public 表示共有属性或者方法
return 方法返回值
short 16位数字
static 表示在类级别定义,所有实例共享的
strictfp 浮点数比较使用严格的规则
super 表示基类
switch 选择语句
synchronized 表示同一时间只能由一个线程访问的代码块
this 表示调用当前实例
或者调用另一个构造函数
throw 抛出异常
throws 定义方法可能抛出的异常
transient 修饰不要序列化的字段
try 表示代码块要做异常处理或者和finally配合表示是否抛出异常都执行finally中的代码
void 标记方法不返回任何值
volatile 标记字段可能会被多个线程同时访问,而不做同步
while while循环

Java注释

类似于C/C++,Java也支持单行以及多行注释。注释中的字符将被Java编译器忽略。

public class MyFirstJavaProgram{   /* 这是第一个Java程序    *它将打印Hello World    * 这是一个多行注释的示例    */    public static void main(String []args){       // 这是单行注释的示例       /* 这个也是单行注释的示例 */       System.out.println("Hello World");     }} 

Java 空行

空白行,或者只有注释的行,Java编译器都会忽略掉。


继承

在Java中,一个类可以由其他类派生。如果你要创建一个类,而且已经存在一个类具有你所需要的属性或方法,那么你可以将新创建的类继承该类。

利用继承的方法,可以重用已存在类的方法和属性,而不用重写这些代码。被继承的类称为超类(super class),派生类称为子类(subclass)。

接口

在Java中,接口可理解为对象间相互通信的协议。接口在继承中扮演着很重要的角色。

接口只定义派生要用到的方法,但是方法的具体实现完全取决于派生类。

下一节介绍Java编程中的类和对象。之后你将会对Java中的类和对象有更清楚的认识。


在理解Java的类和对象之前,先简单介绍一下面向对象的程序设计。程序设计是通过对象对程序进行设计,对象代表一个实体,实体可以清楚地被识别。

Java作为一种面向对象语言。支持以下基本概念:

  • 多态
  • 继承
  • 封装
  • 抽象
  • 对象
  • 实例
  • 方法
  • 消息解析

本节我们重点研究对象和类的概念。

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • :类是一个模板,它描述一类对象的行为和状态。

Java中的对象

现在让我们深入了解什么是对象。看看周围真实的世界,会发现身边有很多对象,车,狗,人等等。所有这些对象都有自己的状态和行为。

拿一条狗来举例,它的状态有:名字、品种、颜色,行为有:叫、摇尾巴和跑。

对比现实对象和软件对象,它们之间十分相似。

软件对象也有状态和行为。软件对象的状态就是属性,行为通过方法体现。

在软件开发中,方法操作对象内部状态的改变,对象的相互调用也是通过方法来完成。

Java中的类

类可以看成是创建 Java 对象的模板。

通过下面一个简单的类来理解下 Java 中类的定义:

public class Dog{   String breed;   int age;   String color;   void barking(){   }      void hungry(){   }      void sleeping(){   }}

一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。

一个类可以拥有多个方法,在上面的例子中:​barking()​、​hungry()​和 ​sleeping()​都是 Dog 类的方法。


构造方法

每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。

下面是一个构造方法示例:

public class Puppy{   public Puppy(){   }   public Puppy(String name){      // 这个构造器仅有一个参数:name   }}

创建对象

对象是根据类创建的。在 Java 中,使用关键字 ​new​ 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字 ​new​ 来创建一个对象。
  • 初始化:使用 ​new​ 创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子:

public class Puppy{   public Puppy(String name){      //这个构造器仅有一个参数:name      System.out.println("Puppy Name is :" + name );    }   public static void main(String []args){      // 下面的语句将创建一个Puppy对象      Puppy myPuppy = new Puppy( "tommy" );   }}

编译并运行上面的程序,会打印出下面的结果:

Puppy Name is :tommy

访问实例变量和方法

通过已创建的对象来访问成员变量和成员方法,如下所示:

/* 实例化对象 */ObjectReference = new Constructor();/* 访问其中的变量 */ObjectReference.variableName;/* 访问类中的方法 */ObjectReference.MethodName();

实例

下面的例子展示如何访问实例变量和调用成员方法:

public class Puppy{   int puppyAge;   public Puppy(String name){      // 这个构造器仅有一个参数:name      System.out.println("Passed Name is :" + name );    }   public void setAge( int age ){       puppyAge = age;   }   public int getAge( ){       System.out.println("Puppy's age is :" + puppyAge );        return puppyAge;   }   public static void main(String []args){      /* 创建对象 */      Puppy myPuppy = new Puppy( "tommy" );      /* 通过方法来设定age */      myPuppy.setAge( 2 );      /* 调用另一个方法获取age */      myPuppy.getAge( );      /*你也可以像下面这样访问成员变量 */      System.out.println("Variable Value :" + myPuppy.puppyAge );    }}

编译并运行上面的程序,产生如下结果:

Passed Name is :tommyPuppy's age is :2Variable Value :2

源文件声明规则

在本节的最后部分,我们将学习源文件的声明规则。当在一个源文件中定义多个类,并且还有​import​语句和​package​ 语句时,要特别注意这些规则。

  • 一个源文件中只能有一个 ​public​ 类
  • 一个源文件可以有多个非​public​类
  • 源文件的名称应该和​public​类的类名保持一致。例如:源文件中​public​类的类名是​Employee​,那么源文件应该命名为​Employee.java​。
  • 如果一个类定义在某个包中,那么​package​语句应该在源文件的首行。
  • 如果源文件包含​import​语句,那么应该放在​package​语句和类定义之间。如果没有​package​语句,那么​import​语句应该在源文件中最前面。
  • import​语句和​package​语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

类有若干种访问级别,并且类也分不同的类型:抽象类和​final​类等。这些将在访问控制章节介绍。

除了上面提到的几种类型,Java 还有一些特殊的类,如:内部类、匿名类。


Java包

包主要用来对类和接口进行分类。当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。

Import语句

在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。​Import​ 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。

例如,下面的命令行将会命令编译器载入 java_installation/java/io 路径下的所有类

import java.io.*;

一个简单的例子

在该例子中,我们创建两个类:Employee 和 EmployeeTest。

首先打开文本编辑器,把下面的代码粘贴进去。注意将文件保存为 Employee.java。

Employee 类有四个成员变量:name、age、designation 和 salary。该类显式声明了一个构造方法,该方法只有一个参数。

import java.io.*;public class Employee{   String name;   int age;   String designation;   double salary;   // Employee 类的构造器   public Employee(String name){      this.name = name;   }   // 设置age的值   public void empAge(int empAge){      age =  empAge;   }   /* 设置designation的值*/   public void empDesignation(String empDesig){      designation = empDesig;   }   /* 设置salary的值*/   public void empSalary(double empSalary){      salary = empSalary;   }   /* 打印信息 */   public void printEmployee(){      System.out.println("Name:"+ name );      System.out.println("Age:" + age );      System.out.println("Designation:" + designation );      System.out.println("Salary:" + salary);   }}

程序都是从 ​main​方法开始执行。为了能运行这个程序,必须包含 ​main​ 方法并且创建一个实例对象。

下面给出 EmployeeTest 类,该类实例化2个 Employee 类的实例,并调用方法设置变量的值。

将下面的代码保存在 EmployeeTest.java 文件中。

import java.io.*;public class EmployeeTest{   public static void main(String args[]){      /* 使用构造器创建两个对象 */      Employee empOne = new Employee("James Smith");      Employee empTwo = new Employee("Mary Anne");      // 调用这两个对象的成员方法      empOne.empAge(26);      empOne.empDesignation("Senior Software Engineer");      empOne.empSalary(1000);      empOne.printEmployee();      empTwo.empAge(21);      empTwo.empDesignation("Software Engineer");      empTwo.empSalary(500);      empTwo.printEmployee();   }}

编译这两个文件并且运行 EmployeeTest 类,可以看到如下结果:

C :> javac Employee.javaC :> vi EmployeeTest.javaC :> javac  EmployeeTest.javaC :> java EmployeeTestName:James SmithAge:26Designation:Senior Software EngineerSalary:1000.0Name:Mary AnneAge:21Designation:Software EngineerSalary:500.0


变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。

内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。

因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。

Java的两大数据类型:

  • 内置数据类型
  • 引用数据类型

内置数据类型

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

byte型:

  • byte数据类型是8位、有符号的,以二进制补码表示的整数;
  • 最小值是-128(-2^7);
  • 最大值是127(2^7-1);
  • 默认值是0;
  • byte类型用在大型数组中节约空间,主要代替整数,因为byte变量占用的空间只有int类型的四分之一;
  • 例子:byte a = 100,byte b = -50。

short型(短整型):

  • short数据类型是16位、有符号的以二进制补码表示的整数
  • 最小值是-32768(-2^15);
  • 最大值是32767(2^15 - 1);
  • Short数据类型也可以像byte那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是0;
  • 例子:short s = 1000,short r = -20000。

int型(整型):

  • int数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是-2,147,483,648(-2^31);
  • 最大值是2,147,483,647(2^31 - 1);
  • 一般地整型变量默认为int类型;
  • 默认值是0;
  • 例子:int a = 100000, int b = -200000。

long(长整型):

  • long数据类型是64位、有符号的以二进制补码表示的整数;
  • 最小值是-9,223,372,036,854,775,808(-2^63);
  • 最大值是9,223,372,036,854,775,807(2^63 -1);
  • 这种类型主要使用在需要比较大整数的系统上;
  • 默认值是0L;
  • 例子: long a = 100000L,long b = -200000L。

float(单精度浮点型):

  • float数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float在储存大型浮点数组的时候可节省内存空间;
  • 默认值是0.0f;
  • 浮点数不能用来表示精确的值,如货币;
  • 例子:float f1 = 234.5f。

double(双精度浮点型):

  • double数据类型是双精度、64位、符合IEEE 754标准的浮点数;
  • 浮点数的默认类型为double类型;
  • double类型同样不能表示精确的值,如货币;
  • 默认值是0.0d;
  • 例子:double d1 = 123.4。

boolean(布尔型):

  • boolean数据类型表示一位的信息;
  • 只有两个取值:true和false;
  • 这种类型只作为一种标志来记录true/false情况;
  • 默认值是false;
  • 例子:boolean one = true。

char(字符型):

  • char类型是一个单一的16位Unicode字符;
  • 最小值是’u0000’(即为0);
  • 最大值是’uffff’(即为65,535);
  • char数据类型可以储存任何字符;
  • 例子:char letter = ‘A’。

实例

对于数值类型的基本类型的取值范围,我们无需强制去记忆,因为它们的值都已经以常量的形式定义在对应的包装类中了。请看下面的例子:

public class PrimitiveTypeTest {    public static void main(String[] args) {    // byte    System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE);    System.out.println("包装类:java.lang.Byte");    System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE);    System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE);    System.out.println();        // short    System.out.println("基本类型:short 二进制位数:" + Short.SIZE);    System.out.println("包装类:java.lang.Short");    System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE);    System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE);    System.out.println();    // int    System.out.println("基本类型:int 二进制位数:" + Integer.SIZE);    System.out.println("包装类:java.lang.Integer");    System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE);    System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE);    System.out.println();    // long    System.out.println("基本类型:long 二进制位数:" + Long.SIZE);    System.out.println("包装类:java.lang.Long");    System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE);    System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE);    System.out.println();    // float    System.out.println("基本类型:float 二进制位数:" + Float.SIZE);    System.out.println("包装类:java.lang.Float");    System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE);    System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE);    System.out.println();    // double    System.out.println("基本类型:double 二进制位数:" + Double.SIZE);    System.out.println("包装类:java.lang.Double");    System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE);    System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE);    System.out.println();    // char    System.out.println("基本类型:char 二进制位数:" + Character.SIZE);    System.out.println("包装类:java.lang.Character");    // 以数值形式而不是字符形式将Character.MIN_VALUE输出到控制台    System.out.println("最小值:Character.MIN_VALUE="            + (int) Character.MIN_VALUE);    // 以数值形式而不是字符形式将Character.MAX_VALUE输出到控制台    System.out.println("最大值:Character.MAX_VALUE="            + (int) Character.MAX_VALUE);}}  

编译以上代码输出结果如下所示:

基本类型:byte 二进制位数:8包装类:java.lang.Byte最小值:Byte.MIN_VALUE=-128最大值:Byte.MAX_VALUE=127基本类型:short 二进制位数:16包装类:java.lang.Short最小值:Short.MIN_VALUE=-32768最大值:Short.MAX_VALUE=32767基本类型:int 二进制位数:32包装类:java.lang.Integer最小值:Integer.MIN_VALUE=-2147483648最大值:Integer.MAX_VALUE=2147483647基本类型:long 二进制位数:64包装类:java.lang.Long最小值:Long.MIN_VALUE=-9223372036854775808最大值:Long.MAX_VALUE=9223372036854775807基本类型:float 二进制位数:32包装类:java.lang.Float最小值:Float.MIN_VALUE=1.4E-45最大值:Float.MAX_VALUE=3.4028235E38基本类型:double 二进制位数:64包装类:java.lang.Double最小值:Double.MIN_VALUE=4.9E-324最大值:Double.MAX_VALUE=1.7976931348623157E308基本类型:char 二进制位数:16包装类:java.lang.Character最小值:Character.MIN_VALUE=0最大值:Character.MAX_VALUE=65535

Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的“数字”次幂。比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。

实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们进行操作。


引用类型

  • 引用类型变量由类的构造函数创建,可以使用它们访问所引用的对象。这些变量在声明时被指定为一个特定的类型,比如Employee、Pubby等。变量一旦声明后,类型就不能被改变了。
  • 对象、数组都是引用数据类型。
  • 所有引用类型的默认值都是null。
  • 一个引用变量可以用来引用与任何与之兼容的类型。
  • 例子:Animal animal = new Animal(“giraffe”)。

Java常量

常量就是一个固定值。它们不需要计算,直接代表相应的值。

常量指不能改变的量。 在Java中用final标志,声明方式和变量类似:

final double PI = 3.1415927;

虽然常量名也可以用小写,但为了便于识别,通常使用大写字母表示常量。

字面量可以赋给任何内置类型的变量。例如:

byte a = 68;char a = 'A'

byte、int、long、和short都可以用十进制、16进制以及8进制的方式来表示。

当使用常量的时候,前缀0表明是8进制,而前缀0x代表16进制。例如:

int decimal = 100;int octal = 0144;int hexa =  0x64;

和其他语言一样,Java的字符串常量也是包含在两个引号之间的字符序列。下面是字符串型字面量的例子:

"Hello World""two
lines"""This is in quotes""

字符串常量和字符常量都可以包含任何Unicode字符。例如:

char a = 'u0001';String a = "u0001";

Java语言支持一些特殊的转义字符序列。

符号字符含义
换行 (0x0a)
回车 (0x0d)
f换页符(0x0c)
退格 (0x08)
空字符(0x0)
s字符串
制表符
"双引号
'单引号
反斜杠
ddd八进制字符 (ddd)
uxxxx16进制Unicode字符 (xxxx)

这一节讲解了Java的基本数据类型。下一节将探讨不同的变量类型以及它们的用法。


在 Java 语言中,所有的变量在使用前必须声明。声明变量的基本格式如下:

type identifier [ = value][, identifier [= value] ...] ;

格式说明:type 为 Java 数据类型。identifier 是变量名。可以使用逗号隔开来声明多个同类型变量。

以下列出了一些变量的声明实例。注意有些包含了初始化过程。

int a, b, c;         // 声明三个int型整数:a、b、c。int d = 3, e, f = 5; // 声明三个整数并赋予初值。byte z = 22;         // 声明并初始化z。double pi = 3.14159; // 声明了pi。char x = 'x';        // 变量x的值是字符'x'。

Java 语言支持的变量类型有:

  • 局部变量:类的方法中的变量。
  • 实例变量:独立于方法之外的变量,不过没有 static 修饰。
  • 类变量:独立于方法之外的变量,用 static 修饰。

public class Variable{    static int allClicks=0;    // 类变量    String str="hello world";  // 实例变量     public void method(){        int i =0;  // 局部变量    }}

Java局部变量

  • 局部变量声明在方法、构造方法或者语句块中;
  • 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
  • 访问修饰符不能用于局部变量;
  • 局部变量只在声明它的方法、构造方法或者语句块中可见;
  • 局部变量是在栈上分配的。
  • 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

实例1

在以下实例中 age 是一个局部变量。定义在 pupAge() 方法中,它的作用域就限制在这个方法中。

public class Test{    public void pupAge(){      int age = 0;      age = age + 7;      System.out.println("Puppy age is : " + age);   }      public static void main(String args[]){      Test test = new Test();      test.pupAge();   }}

以上实例编译运行结果如下:

Puppy age is: 7

实例2

在下面的例子中 age 变量没有初始化,所以在编译时出错。

public class Test{    public void pupAge(){      int age;      age = age + 7;      System.out.println("Puppy age is : " + age);   }      public static void main(String args[]){      Test test = new Test();      test.pupAge();   }}

以上实例编译运行结果如下:

Test.java:4:variable number might not have been initializedage = age + 7;         ^1 error

实例变量

  • 实例变量声明在一个类中,但在方法、构造方法和语句块之外;
  • 当一个对象被实例化之后,每个实例变量的值就跟着确定;
  • 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
  • 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
  • 实例变量可以声明在使用前或者使用后;
  • 访问修饰符可以修饰实例变量;
  • 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
  • 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定;
  • 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName。

实例:

import java.io.*;public class Employee{   // 这个成员变量对子类可见   public String name;   // 私有变量,仅在该类可见   private double salary;   //在构造器中对name赋值   public Employee (String empName){      name = empName;   }   //设定salary的值   public void setSalary(double empSal){      salary = empSal;   }     // 打印信息   public void printEmp(){      System.out.println("name  : " + name );      System.out.println("salary :" + salary);   }   public static void main(String args[]){      Employee empOne = new Employee("Ransika");      empOne.setSalary(1000);      empOne.printEmp();   }}

以上实例编译运行结果如下:

name  : Ransikasalary :1000.0

类变量(静态变量)

  • 类变量也称为静态变量,在类中以 static 关键字声明,但必须在方法、构造方法和语句块之外。
  • 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。
  • 静态变量除了被声明为常量外很少使用。常量是指声明为 public/private,final 和 static 类型的变量。常量初始化后不可改变。
  • 静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。
  • 静态变量在程序开始时创建,在程序结束时销毁。
  • 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
  • 默认值和实例变量相似。数值型变量默认值是0,布尔型默认值是 false,引用类型默认值是 null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。
  • 静态变量可以通过:ClassName.VariableName 的方式访问。
  • 类变量被声明为 public static final 类型时,类变量名称必须使用大写字母。如果静态变量不是 public 和 final 类型,其命名方式与实例变量以及局部变量的命名方式一致。

实例:

import java.io.*;public class Employee{   //salary是静态的私有变量   private static double salary;   // DEPARTMENT是一个常量   public static final String DEPARTMENT = "Development ";   public static void main(String args[]){      salary = 1000;      System.out.println(DEPARTMENT+"average salary:"+salary);   }}

以上实例编译运行结果如下:

Development average salary:1000

注意:如果其他类想要访问该变量,可以这样访问:Employee.DEPARTMENT。

本章节中我们学习了 Java 的变量类型,下一章节中我们将介绍Java修饰符的使用。


Java 语言提供了很多修饰符,主要分为以下两类:

  • 访问修饰符
  • 非访问修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:

public class className {   // ...}private boolean myFlag;static final double weeks = 9.5;protected static final int BOXWIDTH = 42;public static void main(String[] arguments) {   // 方法体}

访问控制修饰符

Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持4种不同的访问权限。

默认的,也称为 default,在同一包内可见,不使用任何修饰符。

私有的,以 private 修饰符指定,在同一类内可见。

公有的,以 public 修饰符指定,对所有类可见。

受保护的,以 protected 修饰符指定,对同一包内的类和所有子类可见。

默认访问修饰符-不使用任何关键字

使用默认访问修饰符声明的变量和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为​public static final​,而接口里的方法默认情况下访问权限为 ​public​。

实例:

如下例所示,变量和方法的声明可以不使用任何修饰符。

String version = "1.5.1";boolean processOrder() {   return true;}

私有访问修饰符-private

私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。

声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。

Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。

下面的类使用了私有访问修饰符:

public class Logger {   private String format;   public String getFormat() {      return this.format;   }   public void setFormat(String format) {      this.format = format;   }}

实例中,Logger 类中的 format 变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个​public​方法:​getFormat() ​(返回format的值)和​setFormat(String)​(设置format的值)

公有访问修饰符-public

被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。

如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。

以下函数使用了公有访问控制:

public static void main(String[] arguments) {   // ...}

Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。

受保护的访问修饰符-protected

被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。

Protected 访问修饰符不能修饰类和接口,方法和成员变量能够声明为 protected,但是接口的成员变量和成员方法不能声明为 protected。

子类能访问 Protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。

下面的父类使用了 protected 访问修饰符,子类重载了父类的 openSpeaker() 方法。

class AudioPlayer {   protected boolean openSpeaker(Speaker sp) {      // 实现细节   }}class StreamingAudioPlayer {   boolean openSpeaker(Speaker sp) {      // 实现细节   }}

如果把 openSpeaker() 方法声明为private,那么除了 AudioPlayer 之外的类将不能访问该方法。

如果把 openSpeaker() 声明为 public,那么所有的类都能够访问该方法。

如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。

访问控制和继承

请注意以下方法继承的规则:

  • 父类中声明为 public 的方法在子类中也必须为 public。

  • 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public。不能声明为 private。

  • 父类中声明为 private 的方法,不能够被继承。


非访问修饰符

为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

static 修饰符,用来创建类方法和类变量。

final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。

abstract 修饰符,用来创建抽象类和抽象方法。

synchronized 和 volatile 修饰符,主要用于线程的编程。

static修饰符

  • 静态变量:

    static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。静态变量也被称为类变量。局部变量不能被声明为static变量。

  • 静态方法:

    static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用 ​classname.variablename​ 和 ​classname.methodname​ 的方式访问。

如下例所示,static 修饰符用来创建类方法和类变量。

public class InstanceCounter {   private static int numInstances = 0;   protected static int getCount() {      return numInstances;   }   private static void addInstance() {      numInstances++;   }   InstanceCounter() {      InstanceCounter.addInstance();   }   public static void main(String[] arguments) {      System.out.println("Starting with " +      InstanceCounter.getCount() + " instances");      for (int i = 0; i < 500; ++i){          new InstanceCounter();      }      System.out.println("Created " +       InstanceCounter.getCount() + " instances");   }} 

以上实例运行编辑结果如下:

Started with 0 instancesCreated 500 instances

final 修饰符

final 变量:

final 变量能被显式地初始化并且只能初始化一次。被声明为final的对象的引用不能指向不同的对象。但是 final 对象里的数据可以被改变。也就是说 final 对象的引用不能改变,但是里面的值可以改变。

final 修饰符通常和 static 修饰符一起使用来创建类常量。

实例:

public class Test{  final int value = 10;  // 下面是声明常量的实例  public static final int BOXWIDTH = 6;  static final String TITLE = "Manager";  public void changeValue(){     value = 12; //将输出一个错误  }}

final 方法

类中的 Final 方法可以被子类继承,但是不能被子类修改。

声明 final 方法的主要目的是防止该方法的内容被修改。

如下所示,使用 final 修饰符声明方法。

public class Test{    public final void changeName(){       // 方法体    }}

final 类

final 类不能被继承,没有类能够继承 final 类的任何特性。

实例:

public final class Test {   // 类体}

abstract 修饰符

抽象类:

抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。

一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。

抽象类可以包含抽象方法和非抽象方法。

实例:

abstract class Caravan{   private double price;   private String model;   private String year;   public abstract void goFast(); //抽象方法   public abstract void changeColor();}

抽象方法

抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。抽象方法不能被声明成 final 和 static。

任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。

抽象方法的声明以分号结尾,例如:public abstract sample();

实例:

public abstract class SuperClass{    abstract void m(); //抽象方法} class SubClass extends SuperClass{     //实现抽象方法      void m(){          .........      }}

synchronized 修饰符

synchronized 关键字声明的方法同一时间只能被一个线程访问。Synchronized 修饰符可以应用于四个访问修饰符。

实例:

public synchronized void showDetails(){.......} 

transient 修饰符

序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机 (JVM) 跳过该特定的变量。

该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

实例:

public transient int limit = 55;   // will not persistpublic int b; // will persist

volatile修饰符

volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

一个 volatile 对象引用可能是 null。

实例:

public class MyRunnable implements Runnable{    private volatile boolean active;    public void run()    {        active = true;        while (active) // line 1        {            // 代码        }    }    public void stop()    {        active = false; // line 2    }}

一般地,在一个线程中调用run()方法,在另一个线程中调用stop()方法。如果line 1中的active位于缓冲区的值被使用,那么当把line 2中的active设置成false时,循环也不会停止。


计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量。我们可以把运算符分成以下几组:

  • 算术运算符
  • 关系运算符
  • 位运算符
  • 逻辑运算符
  • 赋值运算符
  • 其他运算符

算术运算符

算术运算符用在数学表达式中,它们的作用和在数学中的作用一样。下表列出了所有的算术运算符。

表格中的实例假设整数变量A的值为10,变量B的值为20:

操作符描述例子
+加法 - 相加运算符两侧的值A + B等于30
-减法 - 左操作数减去右操作数A – B等于-10
*乘法 - 相乘操作符两侧的值A * B等于200
/除法 - 左操作数除以右操作数B / A等于2
取模 - 左操作数除以右操作数的余数B%A等于0
++自增 - 操作数的值增加1B++ 或 ++B 等于 21
--自减 - 操作数的值减少1B-- 或 --B 等于 19

 虽然都是自增(他们的运算结果都是等于B+1),但B++和++B还是有所区别,++B是左值,直接原地操作(可以理解为直接在变量B上+1),B++是右值,在编译器中运算的时候会先构建一个临时变量,用临时变量运算+1后再赋值给B。

所以,在下面实例代码中,打印d++的时候发现结果并没有+1(这个时候打印的是B这个变量,运算的是B的临时变量),但是后一条打印的语句显示的结果又是+1后的结果(临时变量的值赋给变量B了)。而打印++d的结果是直接+1的。

实例

下面的简单示例程序演示了算术运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 10;     int b = 20;     int c = 25;     int d = 25;     System.out.println("a + b = " + (a + b) );     System.out.println("a - b = " + (a - b) );     System.out.println("a * b = " + (a * b) );     System.out.println("b / a = " + (b / a) );     System.out.println("b % a = " + (b % a) );     System.out.println("c % a = " + (c % a) );     System.out.println("a++   = " +  (a++) );     System.out.println("a--   = " +  (a--) );     // 查看  d++ 与 ++d 的不同     System.out.println("d++   = " +  (d++) );     System.out.println("d     = " +  d);     System.out.println("++d   = " +  (++d) );  }} 

以上实例编译运行结果如下:

a + b = 30a - b = -10a * b = 200b / a = 2b % a = 0c % a = 5a++   = 10a--   = 11d++   = 25d     =26++d   = 27

关系运算符

下表为Java支持的关系运算符

表格中的实例整数变量A的值为10,变量B的值为20:

运算符描述例子
==检查如果两个操作数的值是否相等,如果相等则条件为真。(A == B)为假(非真)。
!=检查如果两个操作数的值是否相等,如果值不相等则条件为真。(A != B) 为真。
检查左操作数的值是否大于右操作数的值,如果是那么条件为真。(A> B)非真。
检查左操作数的值是否小于右操作数的值,如果是那么条件为真。(A <B)为真。
>=检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。(A> = B)为假。
<=检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。(A <= B)为真。

实例

下面的简单示例程序演示了关系运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 10;     int b = 20;     System.out.println("a == b = " + (a == b) );     System.out.println("a != b = " + (a != b) );     System.out.println("a > b = " + (a > b) );     System.out.println("a < b = " + (a < b) );     System.out.println("b >= a = " + (b >= a) );     System.out.println("b <= a = " + (b <= a) );   } }  

以上实例编译运行结果如下:

a == b = falsea != b = truea > b = falsea < b = true b >= a = trueb <= a = false 

位运算符

Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。

位运算符作用在所有的位上,并且按位运算。假设a = 60,和b = 13;它们的二进制格式表示将如下:

A = 0011 1100B = 0000 1101-----------------A&B = 0000 1100A | B = 0011 1101A ^ B = 0011 0001~A= 1100 0011

下表列出了位运算符的基本运算,假设整数变量A的值为60和变量B的值为13:

操作符描述例子
按位与操作符,当且仅当两个操作数的某一位都非0时候结果的该位才为1。(A&B),得到12,即0000 1100
|按位或操作符,只要两个操作数的某一位有一个非0时候结果的该位就为1。(A | B)得到61,即 0011 1101
^按位异或操作符,两个操作数的某一位不相同时候结果的该位就为1。(A ^ B)得到49,即 0011 0001
按位补运算符翻转操作数的每一位。(〜A)得到-61,即1100 0011
<< 按位左移运算符。左操作数按位左移右操作数指定的位数。A << 2得到240,即 1111 0000
>> 按位右移运算符。左操作数按位右移右操作数指定的位数。A >> 2得到15即 1111
>>> 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。A>>>2得到15即0000 1111

实例

下面的简单示例程序演示了位运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 60; /* 60 = 0011 1100 */      int b = 13; /* 13 = 0000 1101 */     int c = 0;     c = a & b;       /* 12 = 0000 1100 */     System.out.println("a & b = " + c );     c = a | b;       /* 61 = 0011 1101 */     System.out.println("a | b = " + c );     c = a ^ b;       /* 49 = 0011 0001 */     System.out.println("a ^ b = " + c );     c = ~a;          /*-61 = 1100 0011 */     System.out.println("~a = " + c );     c = a << 2;     /* 240 = 1111 0000 */     System.out.println("a << 2 = " + c );     c = a >> 2;     /* 215 = 1111 */     System.out.println("a >> 2  = " + c );       c = a >>> 2;     /* 215 = 0000 1111 */     System.out.println("a >>> 2 = " + c );  }} 

以上实例编译运行结果如下:

a & b = 12a | b = 61a ^ b = 49~a = -61a << 2 = 240 a >> 2 = 15a >>> 2 = 15

逻辑运算符

下表列出了逻辑运算符的基本运算,假设布尔变量A为真,变量B为假

操作符描述例子
&&称为逻辑与运算符。当且仅当两个操作数都为真,条件才为真。(A && B)为假。
| |称为逻辑或操作符。如果任何两个操作数任何一个为真,条件为真。(A | | B)为真。
称为逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。!(A && B)为真。

实例

下面的简单示例程序演示了逻辑运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     boolean a = true;     boolean b = false;     System.out.println("a && b = " + (a&&b));     System.out.println("a || b = " + (a||b) );     System.out.println("!(a && b) = " + !(a && b));  }} 

以上实例编译运行结果如下:

a && b = falsea || b = true!(a && b) = true

赋值运算符

下面是Java语言支持的赋值运算符:

操作符描述例子
=简单的赋值运算符,将右操作数的值赋给左侧操作数C = A + B将把A + B得到的值赋给C
+ =加和赋值操作符,它把左操作数和右操作数相加赋值给左操作数C + = A等价于C = C + A
- =减和赋值操作符,它把左操作数和右操作数相减赋值给左操作数C - = A等价于C = C -
 A
* =乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数C * = A等价于C = C * A
/ =除和赋值操作符,它把左操作数和右操作数相除赋值给左操作数C / = A等价于C = C / A
(%)=取模和赋值操作符,它把左操作数和右操作数取模后赋值给左操作数C%= A等价于C = C%A
<< =左移位赋值运算符C << = 2等价于C = C << 2
>> =右移位赋值运算符C >> = 2等价于C = C >> 2
&=按位与赋值运算符C&= 2等价于C = C&2
^ =按位异或赋值操作符C ^ = 2等价于C = C ^ 2
| =按位或赋值操作符C | = 2等价于C = C | 2

实例

下面的简单示例程序演示了赋值运算符。复制并粘贴下面的Java程序并保存为Test.java文件,然后编译并运行这个程序:

public class Test {  public static void main(String args[]) {     int a = 10;     int b = 20;     int c = 0;     c = a + b;     System.out.println("c = a + b = " + c );     c += a ;     System.out.println("c += a  = " + c );     c -= a ;     System.out.println("c -= a = " + c );     c *= a ;     System.out.println("c *= a = " + c );     a = 10;     c = 15;     c /= a ;     System.out.println("c /= a = " + c );     a = 10;     c = 15;     c %= a ;     System.out.println("c %= a  = " + c );     c <<= 2 ;      System.out.println("c <<= 2 = " + c );      c >>= 2 ;     System.out.println("c >>= 2 = " + c );     c >>= 2 ;     System.out.println("c >>= a = " + c );     c &= a ;     System.out.println("c &= a = " + c );     c ^= a ;     System.out.println("c ^= a = " + c );     c |= a ;     System.out.println("c |= a = " + c );  }} 

以上实例编译运行结果如下:

c = a + b = 30c += a  = 40c -= a = 30c *= a = 300c /= a = 1c %= a  = 5c <<= 2 = 20 c >>= 2 = 5c >>= 2 = 1c &= a  = 0c ^= a   = 10c |= a   = 10

条件运算符(?:)

条件运算符也被称为三元运算符。该运算符有3个操作数,并且需要判断布尔表达式的值。该运算符的主要是决定哪个值应该赋值给变量。

variable x = (expression) ? value if true : value if false

实例

public class Test {   public static void main(String args[]){      int a , b;         a = 10;          b = (a == 1) ? 20: 30;          System.out.println( "Value of b is : " +  b );      b = (a == 10) ? 20: 30;          System.out.println( "Value of b is : " + b );   }}

以上实例编译运行结果如下:

Value of b is : 30Value of b is : 20

instanceof 运算符

该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)。

instanceof运算符使用格式如下:

( Object reference variable ) instanceof  (class/interface type)

如果运算符左侧变量所指的对象,是操作符右侧类或接口(class/interface)的一个对象,那么结果为真。

下面是一个例子:

String name = 'James';boolean result = name instanceof String; // 由于name是String类型,所以返回真

如果被比较的对象兼容于右侧类型,该运算符仍然返回true。

看下面的例子:

class Vehicle {}public class Car extends Vehicle {   public static void main(String args[]){      Vehicle a = new Car();      boolean result =  a instanceof Car;      System.out.println( result);   }}

以上实例编译运行结果如下:

true

Java运算符优先级

当多个运算符出现在一个表达式中,谁先谁后呢?这就涉及到运算符的优先级别的问题。在一个多运算符的表达式中,运算符优先级不同会导致最后得出的结果差别甚大。

例如,(1+3)+(3+2)*2,这个表达式如果按加号最优先计算,答案就是 18,如果按照乘号最优先,答案则是 14。

再如,x = 7 + 3 * 2;这里x得到13,而不是20,因为乘法运算符比加法运算符有较高的优先级,所以先计算3 * 2得到6,然后再加7。

下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。

类别操作符关联性
后缀() [] . (点操作符)左到右
一元+ + - !〜从右到左
乘性 * /%左到右
加性 + -左到右
移位 >> >>>  << 左到右
关系 >> = << = 左到右
相等 ==  !=左到右
按位与左到右
按位异或^左到右
按位或|左到右
逻辑与&&左到右
逻辑或| |左到右
条件?:从右到左
赋值= + = - = * = / =%= >> = << =&= ^ = | =从右到左
逗号左到右

顺序结构的程序语句只能被执行一次。如果您想要同样的操作执行多次,,就需要使用循环结构。

Java中有三种主要的循环结构:

  • while循环
  • do…while循环
  • for循环

在Java5中引入了一种主要用于数组的增强型for循环。


while循环

while是最基本的循环,它的结构为:

while( 布尔表达式 ) {	//循环内容}

只要布尔表达式为true,循环体会一直执行下去。

实例

public class Test {   public static void main(String args[]) {      int x = 10;      while( x < 20 ) {          System.out.print("value of x : " + x );          x++;          System.out.print("
");      }   } } 

以上实例编译运行结果如下:

value of x : 10value of x : 11value of x : 12value of x : 13value of x : 14value of x : 15value of x : 16value of x : 17value of x : 18value of x : 19

do…while循环

对于while语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次。

do…while循环和while循环相似,不同的是,do…while循环至少会执行一次。

do {       //代码语句}while(布尔表达式);

注意:布尔表达式在循环体的后面,所以语句块在检测布尔表达式之前已经执行了。 如果布尔表达式的值为true,则语句块一直执行,直到布尔表达式的值为false。

实例

public class Test {   public static void main(String args[]){      int x = 10;      do{         System.out.print("value of x : " + x );         x++;         System.out.print("
");      }while( x < 20 );    } } 

以上实例编译运行结果如下:

value of x : 10value of x : 11value of x : 12value of x : 13value of x : 14value of x : 15value of x : 16value of x : 17value of x : 18value of x : 19

for循环

虽然所有循环结构都可以用while或者do...while表示,但Java提供了另一种语句 —— for循环,使一些循环结构变得更加简单。

for循环执行的次数是在执行前就确定的。语法格式如下:

for(初始化; 布尔表达式; 更新) {    //代码语句}

关于for循环有以下几点说明:

  • 最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,也可以是空语句。
  • 然后,检测布尔表达式的值。如果为true,循环体被执行。如果为false,循环终止,开始执行循环体后面的语句。
  • 执行一次循环后,更新循环控制变量。
  • 再次检测布尔表达式。循环执行上面的过程。

实例

public class Test {   public static void main(String args[]) {      for(int x = 10; x < 20; x = x+1) {          System.out.print("value of x : " + x );          System.out.print("
");      }   } } 

以上实例编译运行结果如下:

value of x : 10value of x : 11value of x : 12value of x : 13value of x : 14value of x : 15value of x : 16value of x : 17value of x : 18value of x : 19

Java增强for循环

Java5引入了一种主要用于数组的增强型for循环。

Java增强for循环语法格式如下:

for(声明语句 : 表达式){   //代码句子}

声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。

表达式:表达式是要访问的数组名,或者是返回值为数组的方法。

实例

public class Test {   public static void main(String args[]){      int [] numbers = {10, 20, 30, 40, 50};      for(int x : numbers ){         System.out.print( x );         System.out.print(",");      }      System.out.print("
");      String [] names ={"James", "Larry", "Tom", "Lacy"};      for( String name : names ) {         System.out.print( name );         System.out.print(",");      }   }}

以上实例编译运行结果如下:

10,20,30,40,50,James,Larry,Tom,Lacy,

break关键字

break主要用在循环语句或者switch语句中,用来跳出整个语句块。

break跳出最里层的循环,并且继续执行该循环下面的语句。

语法

break的用法很简单,就是循环结构中的一条语句:

break;

实例

public class Test {   public static void main(String args[]) {      int [] numbers = {10, 20, 30, 40, 50};      for(int x : numbers ) {         if( x == 30 ) {	      break;         }         System.out.print( x );         System.out.print("
");      }   }}

以上实例编译运行结果如下:

1020

continue关键字

continue适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。

在for循环中,continue语句使程序立即跳转到更新语句。

在while或者do…while循环中,程序立即跳转到布尔表达式的判断语句。

语法

continue就是循环体中一条简单的语句:

continue;

实例

public class Test {   public static void main(String args[]) {      int [] numbers = {10, 20, 30, 40, 50};      for(int x : numbers ) {         if( x == 30 ) {	      continue;         }         System.out.print( x );         System.out.print("
");      }   }}

以上实例编译运行结果如下:

10204050


顺序结构只能顺序执行,不能进行判断和选择,因此需要分支结构。

Java有两种分支结构:

  • if语句
  • switch语句

if语句

一个if语句包含一个布尔表达式和一条或多条语句。

语法

If 语句的用语法如下:

if(布尔表达式){   //如果布尔表达式为true将执行的语句}

如果布尔表达式的值为 true,则执行if语句中的代码块。否则执行 If 语句块后面的代码。

public class Test {   public static void main(String args[]){      int x = 10;      if( x < 20 ){          System.out.print("这是 if 语句");       }   }} 

以上代码编译运行结果如下:

这是 if 语句

if...else 语句

if 语句后面可以跟 else 语句,当if语句的布尔表达式值为 false 时,else 语句块会被执行。

语法

if…else 的用法如下:

if(布尔表达式){   //如果布尔表达式的值为true}else{   //如果布尔表达式的值为false}

实例

public class Test {   public static void main(String args[]){      int x = 30;      if( x < 20 ){          System.out.print("这是 if 语句");       }else{          System.out.print("这是 else 语句");       }   }} 

以上代码编译运行结果如下:

这是 else 语句

if...else if...else 语句

if 语句后面可以跟 else if…else 语句,这种语句可以检测到多种可能的情况。

使用if,else if,else语句的时候,需要注意下面几点:

  • if 语句至多有 1 个 else 语句,else 语句在所有的 else if 语句之后。
  • If 语句可以有若干个 else if 语句,它们必须在 else 语句之前。
  • 一旦其中一个 else if 语句检测为 true,其他的 else if 以及 else 语句都将跳过执行。

语法

if...else 语法格式如下:

if(布尔表达式 1){   //如果布尔表达式 1的值为true执行代码}else if(布尔表达式 2){   //如果布尔表达式 2的值为true执行代码}else if(布尔表达式 3){   //如果布尔表达式 3的值为true执行代码}else {   //如果以上布尔表达式都不为true执行代码}

实例

public class Test {   public static void main(String args[]){      int x = 30;      if( x == 10 ){         System.out.print("Value of X is 10");      }else if( x == 20 ){         System.out.print("Value of X is 20");      }else if( x == 30 ){         System.out.print("Value of X is 30");      }else{         System.out.print("这是 else 语句");      }   }}

以上代码编译运行结果如下:

Value of X is 30

嵌套的 if…else 语句

使用嵌套的 if-else 语句是合法的。也就是说你可以在另一个 if 或者 else if 语句中使用 if 或者 else if 语句。

语法

嵌套的 if…else 语法格式如下:

if(布尔表达式 1){   ////如果布尔表达式 1的值为true执行代码   if(布尔表达式 2){      ////如果布尔表达式 2的值为true执行代码   }}

你可以像 if 语句一样嵌套 else if...else。

实例

public class Test {   public static void main(String args[]){      int x = 30;      int y = 10;      if( x == 30 ){         if( y == 10 ){             System.out.print("X = 30 and Y = 10");          }       }    }}

以上代码编译运行结果如下:

X = 30 and Y = 10

switch 语句

switch 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。

语法

switch 语法格式如下:

switch(expression){    case value :       //语句       break; //可选    case value :       //语句       break; //可选    //你可以有任意数量的case语句    default : //可选       //语句}

switch 语句有如下规则:

  • switch 语句中的变量类型只能为 byte、short、int 或者 char。从 Java SE 7 开始,switch 支持字符串 String 类型了,同时 case 标签必须为字符串常量或字面量。
  • switch 语句可以拥有多个 case 语句。每个 case 后面跟一个要比较的值和冒号。
  • case 语句中的值的数据类型必须与变量的数据类型相同,而且只能是常量或者字面常量。
  • 当变量的值与 case 语句的值相等时,那么 case 语句之后的语句开始执行,直到break语句出现才会跳出 switch 语句。
  • 当遇到 break 语句时,switch 语句终止。程序跳转到 switch 语句后面的语句执行。case 语句不必须要包含break 语句。如果没有 break 语句出现,程序会继续执行下一条 case 语句,直到出现 break 语句。
  • switch 语句可以包含一个 default 分支,该分支必须是 switch 语句的最后一个分支。default 在没有 case 语句的值和变量值相等的时候执行。default 分支不需要 break 语句。

实例

public class Test {   public static void main(String args[]){      //char grade = args[0].charAt(0);      char grade = 'C';      switch(grade)      {         case 'A' :            System.out.println("优秀");             break;         case 'B' :         case 'C' :            System.out.println("良好");            break;         case 'D' :            System.out.println("及格");         case 'F' :            System.out.println("你需要继续努力");            break;         default :            System.out.println("无效等级");      }      System.out.println("你的等级是 " + grade);   }}

以上代码编译运行结果如下:

良好你的等级是 C


一般情况下我们会使用数据的基本数据类型:byte、int、short、long、double、float、boolean、char;

对应的包装类型也有八种:Byte、Integer、Short、Long、Double、Float、Character、Boolean;

包装类型都是用 final 声明了,不可以被继承重写;在实际情况中编译器会自动的将基本数据类型装箱成对象类型,或者将对象类型拆箱成基本数据类型;如下:

public static void main(String[] args) {	int num1 = 1;	//将基本数据类型装箱成对象包装类型	Integer num2 = num1;	Integer num3 = 3;	//将对象数据类拆箱	int num4 = num3;}

Number 类是 java.lang 包下的一个抽象类,提供了将包装类型拆箱成基本类型的方法,所有基本类型(数据类型)的包装类型都继承了该抽象类,并且是final声明不可继承改变;

package java.lang;public abstract class Number implements java.io.Serializable {    public abstract int intValue();    public abstract long longValue();    public abstract float floatValue();    public abstract double doubleValue();    public byte byteValue() {        return (byte)intValue();    }    public short shortValue() {        return (short)intValue();    }    private static final long serialVersionUID = -8742448824652078965L;}
包装类基本数据类型
Booleanboolean
Bytebyte
Shortshort
Integerint
Longlong
Characterchar
Floatfloat
Doubledouble

这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang 包。

下面是一个装箱与拆箱的例子:

public class Test{   public static void main(String args[]){      Integer x = 5; // boxes int to an Integer object      x =  x + 10;   // unboxes the Integer to a int      System.out.println(x);    }}

以上实例编译运行结果如下:

15

当x被赋为整型值时,由于 x 是一个对象,所以编译器要对x进行装箱。然后,为了使x能进行加运算,所以要对x进行拆箱。


Java Math类

Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。

Math 的方法都被定义为 static 形式,通过 Math 类可以在主函数中直接调用。

实例

public class Test {      public static void main (String []args)      {          System.out.println("90 度的正弦值:" + Math.sin(Math.PI/2));          System.out.println("0度的余弦值:" + Math.cos(0));          System.out.println("60度的正切值:" + Math.tan(Math.PI/3));          System.out.println("1的反正切值: " + Math.atan(1));          System.out.println("π/2的角度值:" + Math.toDegrees(Math.PI/2));          System.out.println(Math.PI);      }  }

以上实例编译运行结果如下:

90 度的正弦值:1.00度的余弦值:1.060度的正切值:1.73205080756887671的反正切值: 0.7853981633974483π/2的角度值:90.03.141592653589793


Number & Math 类方法

下面的表中列出的是常用的 Number 类和 Math 类的方法:

序号方法与描述
1xxxValue()
将number对象转换为xxx数据类型的值并返回。
2compareTo()
将number对象与参数比较。
3equals()
判断number对象是否与参数相等。
4valueOf()
返回一个Integer对象指定的内置数据类型
5toString()
以字符串形式返回值。
6parseInt()
将字符串解析为int类型。
7abs()
返回参数的绝对值。
8ceil()
返回大于等于( >= )给定参数的的最小整数,类型为双精度浮点型。
9floor()
返回小于等于(<=)给定参数的最大整数 。
10rint()
返回与参数最接近的整数。返回类型为double。
11round()
返回一个最接近的int、long型值。
12min()
返回两个参数中的最小值。
13max()
返回两个参数中的最大值。
14exp()
返回自然数底数e的参数次方。
15log()
返回参数的自然数底数的对数值。
16pow()
返回第一个参数的第二个参数次方。
17sqrt()
求参数的算术平方根。
18sin()
求指定double类型参数的正弦值。
19cos()
求指定double类型参数的余弦值。
20tan()
求指定double类型参数的正切值。
21asin()
求指定double类型参数的反正弦值。
22acos()
求指定double类型参数的反余弦值。
23atan()
求指定double类型参数的反正切值。
24atan2()
将笛卡尔坐标转换为极坐标,并返回极坐标的角度值。
25toDegrees()
将参数转化为角度。
26toRadians()
将角度转换为弧度。
27random()
返回一个随机数。


本章节我们主要向大家介绍一下Java Character类,以及Character类的用法。

Java Character类

使用字符时,我们通常使用的是内置数据类型 char。

实例

char ch = 'a';// Unicode for uppercase Greek omega characterchar uniChar = 'u039A'; // 字符数组char[] charArray = { 'a', 'b', 'c', 'd', 'e' }; 

然而,在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型的情况。为了解决这个问题,Java 语言为内置数据类型 char 提供了包装类 Character 类。

Character类的用法:Character 类提供了一系列方法来操纵字符,你可以使用 Character 的构造方法创建一个 Character 类对象,例如:

Character ch = new Character('a');

在某些情况下,Java 编译器会自动创建一个 Character 对象。

例如,将一个 char 类型的参数传递给需要一个 Character 类型参数时,那么编译器会自动地将 char 类型参数转换为 Character 对象。 这种特征称为装箱,反过来称为拆箱。 

实例

// Here following primitive char 'a'// is boxed into the Character object chCharacter ch = 'a';// Here primitive 'x' is boxed for method test,// return is unboxed to char 'c'char c = test('x');

转义序列

前面有反斜杠()的字符代表转义字符,它对编译器来说是有特殊含义的。

下面列表展示了 Java 的转义序列:

转义序列描述
在文中该处插入一个tab键
在文中该处插入一个后退键
在文中该处换行
在文中该处插入回车
f在文中该处插入换页符
'在文中该处插入单引号
"在文中该处插入双引号
在文中该处插入反斜杠

实例

当打印语句遇到一个转义序列时,编译器可以正确地对其进行解释。

public class Test {   public static void main(String args[]) {      System.out.println("She said "Hello!" to me.");   }}

以上实例编译运行结果如下:

She said "Hello!" to me.

Character 方法

下面是 Character 类的方法:

序号方法与描述
1isLetter()
是否是一个字母
2isDigit()
是否是一个数字字符
3isWhitespace()
是否一个空格
4isUpperCase()
是否是大写字母
5isLowerCase()
是否是小写字母
6toUpperCase()
指定字母的大写形式
7toLowerCase()
指定字母的小写形式
8toString()
返回字符的字符串形式,字符串的长度仅为1


初学者会经常使用的几个方法

public static boolean isUpperCase(char ch): 判断给定的字符是否是大写字符;


public static boolean isLowerCase(char ch): 判断给定的字符是否是小写字符;


public static boolean isDigit(char ch): 判断给定的字符是否是数字字符;


这三个句子里的boolean代表,这三个方法使用后的返回值是 boolean 型。


实例

public class Java {	public static void main(String[] args) {		Character ch = new Character('X');				System.out.println(Character.isUpperCase(ch));		//Character.isUpperCase(ch) 用于判断括号里的字母是否为大写		System.out.println(Character.isLowerCase(ch));		//Character.isLowerCase(ch) 用于判断括号里的字母是否为小写		System.out.println(Character.isDigit(ch));		//Character.isDigit(ch) 用于判断括号里的内容是否为数字	}}

运行结果为:

truefalsefalse

对于方法的完整列表,请参考的 java.lang.Character API 规范。


字符串广泛应用在Java编程中,在Java中字符串属于对象,Java提供了String类来创建和操作字符串。


创建字符串

创建字符串最简单的方式如下:

String greeting = "Hello world!";

在代码中遇到字符串常量时,这里的值是 "Hello world!" ,编译器会使用该值创建一个 String 对象。

和其它对象一样,可以使用关键字和构造方法来创建String对象。

String 类有 11 种构造方法,这些方法提供不同的参数来初始化字符串,比如提供一个字符数组参数:

public class StringDemo{   public static void main(String args[]){      char[] helloArray = { 'h', 'e', 'l', 'l', 'o', '.'};      String helloString = new String(helloArray);        System.out.println( helloString );   }}

以上实例编译运行结果如下:

hello.

注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。 如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类


字符串长度

用于获取有关对象的信息的方法称为访问器方法。

String 类的一个访问器方法是 length() 方法,它返回字符串对象包含的字符数。

下面的代码执行后,len 变量等于 17:

public class StringDemo {   public static void main(String args[]) {      String palindrome = "Dot saw I was Tod";      int len = palindrome.length();      System.out.println( "String Length is : " + len );   }}

以上实例编译运行结果如下:

String Length is : 17

连接字符串

String 类提供了连接两个字符串的方法:

string1.concat(string2);

返回 string2 连接 string1 的新字符串。也可以对字符串常量使用 concat() 方法,如:

"My name is ".concat("Zara");

更常用的是使用'+'操作符来连接字符串,如:

"Hello," + " world" + "!"

结果如下:

"Hello, world!"

下面是一个例子:

public class StringDemo {   public static void main(String args[]) {           String string1 = "saw I was ";           System.out.println("Dot " + string1 + "Tod");     }}

以上实例编译运行结果如下:

Dot saw I was Tod

创建格式化字符串

我们知道输出格式化数字可以使用 printf() 和 format() 方法。String 类使用静态方法 format() 返回一个 String 对象而不是 PrintStream 对象。

String 类的静态方法 format() 能用来创建可复用的格式化字符串,而不仅仅是用于一次打印输出。如下所示:

System.out.printf("The value of the float variable is " +                  "%f, while the value of the integer " +                  "variable is %d, and the string " +                  "is %s", floatVar, intVar, stringVar);

你也可以这样写

String fs;fs = String.format("The value of the float variable is " +                   "%f, while the value of the integer " +                   "variable is %d, and the string " +                   "is %s", floatVar, intVar, stringVar);System.out.println(fs);

String 方法

下面是 String 类支持的常用方法,更多详细,参看 Java API 文档:

SN(序号)方法描述
1char charAt(int index)
返回指定索引处的 char 值。
2int compareTo(Object o)
把这个字符串和另一个对象比较。
3int compareTo(String anotherString)
按字典顺序比较两个字符串。
4int compareToIgnoreCase(String str)
按字典顺序比较两个字符串,不考虑大小写。
5String concat(String str)
将指定字符串连接到此字符串的结尾。
6boolean contentEquals(StringBuffer sb)
当且仅当字符串与指定的StringButter有相同顺序的字符时候返回真。
7static String copyValueOf(char[] data)
返回指定数组中表示该字符序列的 String。
8static String copyValueOf(char[] data, int offset, int count)
返回指定数组中表示该字符序列的 String。
9boolean endsWith(String suffix)
测试此字符串是否以指定的后缀结束。
10boolean equals(Object anObject)
将此字符串与指定的对象比较。
11boolean equalsIgnoreCase(String anotherString)
将此 String 与另一个 String 比较,不考虑大小写。
12byte[] getBytes()
 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
13byte[] getBytes(String charsetName)
使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
14void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 将字符从此字符串复制到目标字符数组。
15int hashCode()
返回此字符串的哈希码。
16int indexOf(int ch)
返回指定字符在此字符串中第一次出现处的索引。
17int indexOf(int ch, int fromIndex)
返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。
18int indexOf(String str)
 返回指定子字符串在此字符串中第一次出现处的索引。
19int indexOf(String str, int fromIndex)
返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
20String intern()
 返回字符串对象的规范化表示形式。
21int lastIndexOf(int ch)
 返回指定字符在此字符串中最后一次出现处的索引。
22int lastIndexOf(int ch, int fromIndex)
返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。
23int lastIndexOf(String str)
返回指定子字符串在此字符串中最右边出现处的索引。
24int lastIndexOf(String str, int fromIndex)
 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
25int length()
返回此字符串的长度。
26boolean matches(String regex)
告知此字符串是否匹配给定的正则表达式。
27boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
28boolean regionMatches(int toffset, String other, int ooffset, int len)
测试两个字符串区域是否相等。
29String replace(char oldChar, char newChar)
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
30String replaceAll(String regex, String replacement
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
31String replaceFirst(String regex, String replacement)
 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
32String[] split(String regex)
根据给定正则表达式的匹配拆分此字符串。
33String[] split(String regex, int limit)
根据匹配给定的正则表达式来拆分此字符串。
34boolean startsWith(String prefix)
测试此字符串是否以指定的前缀开始。
35boolean startsWith(String prefix, int toffset)
测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
36CharSequence subSequence(int beginIndex, int endIndex)
 返回一个新的字符序列,它是此序列的一个子序列。
37String substring(int beginIndex)
返回一个新的字符串,它是此字符串的一个子字符串。
38String substring(int beginIndex, int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。
39char[] toCharArray()
将此字符串转换为一个新的字符数组。
40String toLowerCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
41String toLowerCase(Locale locale)
 使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。
42String toString()
 返回此对象本身(它已经是一个字符串!)。
43String toUpperCase()
使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
44String toUpperCase(Locale locale)
使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。
45String trim()
返回字符串的副本,忽略前导空白和尾部空白。
46static String valueOf(primitive data type x)
返回给定data type类型x参数的字符串表示形式。


当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

和String类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

实例

public class Test{    public static void main(String args[]){       StringBuffer sBuffer = new StringBuffer(" test");       sBuffer.append(" String Buffer");       System.out.println(sBuffer);     }}

以上实例编译运行结果如下:

test String Buffer

StringBuffer 方法

以下是 StringBuffer 类支持的主要方法:

序号 方法描述
1 public StringBuffer append(String s)
将指定的字符串追加到此字符序列。
2 public StringBuffer reverse()
 将此字符序列用其反转形式取代。
3 public delete(int start, int end)
移除此序列的子字符串中的字符。
4 public insert(int offset, int i)
int 参数的字符串表示形式插入此序列中。
5 replace(int start, int end, String str)
使用给定 String 中的字符替换此序列的子字符串中的字符。

下面的列表里的方法和 String 类的方法类似:

序号 方法描述
1 int capacity()
返回当前容量。
2 char charAt(int index)
返回此序列中指定索引处的 char 值。
3 void ensureCapacity(int minimumCapacity)
确保容量至少等于指定的最小值。
4 void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
将字符从此序列复制到目标字符数组 dst
5 int indexOf(String str)
返回第一次出现的指定子字符串在该字符串中的索引。
6 int indexOf(String str, int fromIndex)
从指定的索引处开始,返回第一次出现的指定子字符串在该字符串中的索引。
7 int lastIndexOf(String str)
返回最右边出现的指定子字符串在此字符串中的索引。
8 int lastIndexOf(String str, int fromIndex)
返回最后一次出现的指定子字符串在此字符串中的索引。
9 int length()
 返回长度(字符数)。
10 void setCharAt(int index, char ch)
将给定索引处的字符设置为 ch
11 void setLength(int newLength)
设置字符序列的长度。
12 CharSequence subSequence(int start, int end)
返回一个新的字符序列,该字符序列是此序列的子序列。
13 String substring(int start)
返回一个新的 String,它包含此字符序列当前所包含的字符子序列。
14 String substring(int start, int end)
返回一个新的 String,它包含此序列当前所包含的字符子序列。
15 String toString()
返回此序列中数据的字符串表示形式。


数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。

Java 语言中提供的数组是用来存储固定大小的同类型元素。

你可以声明一个数组变量,如 numbers[100] 来代替直接声明 100 个独立变量 number0,number1,....,number99。

本教程将为大家介绍Java数组的声明、创建和初始化,并给出其对应的代码。


声明数组变量

首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:

dataType[] arrayRefVar;   // 首选的方法或dataType arrayRefVar[];  // 效果相同,但不是首选方法

注意: 建议使用 dataType[] arrayRefVar 的声明风格声明数组变量。 dataType arrayRefVar[] 风格是来自 C/C++ 语言 ,在Java中采用是为了让  C/C++  程序员能够快速理解 java 语言。

实例

下面是这两种语法的代码示例:

double[] myList;         // 首选的方法或double myList[];         //  效果相同,但不是首选方法

创建数组

Java 语言使用 new操作符来创建数组,语法如下:

arrayRefVar = new dataType[arraySize];

上面的语法语句做了两件事:

  • 一、使用 dataType[arraySize] 创建了一个数组。

  • 二、把新创建的数组的引用赋值给变量 arrayRefVar。

数组变量的声明,和创建数组可以用一条语句完成,如下所示:

dataType[] arrayRefVar = new dataType[arraySize];

另外,你还可以使用如下的方式创建数组。

dataType[] arrayRefVar = {value0, value1, ..., valuek};

数组的元素是通过索引访问的。数组索引从0开始,所以索引值从 0 到 arrayRefVar.length-1。

那么当数组开辟空间之后,就可以采用如下的方式的操作:

  • 数组的访问通过索引完成,即:“数组名称[索引]”,但是需要注意的是,数组的索引从0开始,所以索引的范围就是0 ~ 数组长度-1,例如开辟了3个空间的数组,所以可以使用的索引是:0,1,2,如果此时访问的时候超过了数组的索引范围,会产生 java.lang.ArrayIndexOutOfBoundsException 异常信息;
  • 当我们数组采用动态初始化开辟空间后,数组里面的每一个元素都是该数组对应数据类型的默认值;
  • 数组本身是一个有序的集合操作,所以对于数组的内容操作往往会采用循环的模式完成,数组是一个有限的数据集合,所以应该使用 for 循环。
  • 在 Java 中提供有一种动态取得数组长度的方式:数组名称.length;

示例: 定义一个int型数组

public class ArrayDemo {	public static void main(String args[]) {		int data[] = new int[3]; /*开辟了一个长度为3的数组*/		data[0] = 10; // 第一个元素		data[1] = 20; // 第二个元素		data[2] = 30; // 第三个元素		for(int x = 0; x < data.length; x++) {			System.out.println(data[x]); //通过循环控制索引		}	}}

数组本身除了声明并开辟空间之外还有另外一种开辟模式。

示例: 采用分步的模式开辟数组空间

public class ArrayDemo {	public static void main(String args[]) {		int data[] = null; 		data = new int[3]; /*开辟了一个长度为3的数组*/		data[0] = 10; // 第一个元素		data[1] = 20; // 第二个元素		data[2] = 30; // 第三个元素		for(int x = 0; x < data.length; x++) {			System.out.println(data[x]); //通过循环控制索引		}	}}

但是千万要记住,数组属于引用数据类型,所以在数组使用之前一定要开辟空间(实例化),如果使用了没有开辟空间的数组,则一定会出现 NullPointerException 异常信息:

public class ArrayDemo {	public static void main(String args[]) {		int data[] = null; 		System.out.println(data[x]);	}}

这一原则和之前讲解的对象是完全相同的。

数组在开发之中一定会使用,但是像上面的操作很少。在以后的实际开发之中,会更多的使用数组概念,而直接使用,大部分情况下都只是做一个 for 循环输出。


处理数组

数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本循环或者 foreach 循环。

示例

该实例完整地展示了如何创建、初始化和操纵数组:

public class TestArray {   public static void main(String[] args) {      double[] myList = {1.9, 2.9, 3.4, 3.5};      // 打印所有数组元素      for (int i = 0; i < myList.length; i++) {          System.out.println(myList[i] + " ");       }       // 计算所有元素的总和       double total = 0;       for (int i = 0; i < myList.length; i++) {          total += myList[i];       }       System.out.println("Total is " + total);       // 查找最大元素       double max = myList[0];       for (int i = 1; i < myList.length; i++) {          if (myList[i] > max) max = myList[i];      }      System.out.println("Max is " + max);   }}

以上实例编译运行结果如下:

1.92.93.43.5Total is 11.7Max is 3.5

foreach 循环

JDK 1.5 引进了一种新的循环类型,被称为 foreach 循环或者加强型循环,它能在不使用下标的情况下遍历数组。

语法格式如下:

for(type element: array){    System.out.println(element);}

示例

该实例用来显示数组 myList 中的所有元素:

public class TestArray {   public static void main(String[] args) {      double[] myList = {1.9, 2.9, 3.4, 3.5};      // 打印所有数组元素      for (double element: myList) {         System.out.println(element);      }   }}

以上实例编译运行结果如下:

1.92.93.43.5

数组作为函数的参数

数组可以作为参数传递给方法。例如,下面的例子就是一个打印 int 数组中元素的方法。

public static void printArray(int[] array) {  for (int i = 0; i < array.length; i++) {     System.out.print(array[i] + " ");   } }

下面例子调用 printArray 方法打印出 3,1,2,6,4和2:

printArray(new int[]{3, 1, 2, 6, 4, 2});

数组作为函数的返回值

public static int[] reverse(int[] list) {  int[] result = new int[list.length];  for (int i = 0, j = result.length - 1; i < list.length; i++, j--) {     result[j] = list[i];   }   return result; }

以上实例中result数组作为函数的返回值。


Arrays 类

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。具有以下功能:

  • 给数组赋值:通过 fill 方法。

  • 对数组排序:通过 sort 方法,按升序。

  • 比较数组:通过 equals 方法比较数组中元素值是否相等。

  • 查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。

具体说明请查看下表:

序号方法和说明
1public static int binarySearch(Object[] a, Object key)
用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
2public static boolean equals(long[] a, long[] a2)
如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
3public static void fill(int[] a, int val)
将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
4public static void sort(Object[] a)
对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。


java.util包提供了Date类来封装当前的日期和时间。 Date类提供两个构造函数来实例化Date对象。

第一个构造函数使用当前日期和时间来初始化对象。

Date( )

第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。

Date(long millisec)

Date对象创建以后,可以调用下面的方法。

序号 方法和描述
1 boolean after(Date date)
若当调用此方法的Date对象在指定日期之后返回true,否则返回false。
2 boolean before(Date date)
若当调用此方法的Date对象在指定日期之前返回true,否则返回false。
3 Object clone( )
返回此对象的副本。
4 int compareTo(Date date)
比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。
5 int compareTo(Object obj)
若obj是Date类型则操作等同于compareTo(Date) 。否则它抛出ClassCastException。
6 boolean equals(Object date)
当调用此方法的Date对象和指定日期相等时候返回true,否则返回false。
7 long getTime( )
返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
8 int hashCode( )
 返回此对象的哈希码值。
9 void setTime(long time)
 
用自1970年1月1日00:00:00 GMT以后time毫秒数设置时间和日期。
10 String toString( )
转换Date对象为String表示形式,并返回该字符串。

获取当前日期时间

Java中获取当前日期和时间很简单,使用Date对象的 toString()方法来打印当前日期和时间,如下所示:

import java.util.Date;  public class DateDemo {   public static void main(String args[]) {       // 初始化 Date 对象       Date date = new Date();               // 使用 toString() 函数显示日期时间       System.out.println(date.toString());   }}

以上实例编译运行结果如下:

Mon May 04 09:51:52 CDT 2013

日期比较

Java使用以下三种方法来比较两个日期:

  • 使用getTime( ) 方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。
  • 使用方法before(),after()和equals()。例如,一个月的12号比18号早,则new Date(99, 2, 12).before(new Date (99, 2, 18))返回true。
  • 使用compareTo()方法,它是由Comparable接口定义的,Date类实现了这个接口。

使用SimpleDateFormat格式化日期

SimpleDateFormat是一个以语言环境敏感的方式来格式化和分析日期的类。SimpleDateFormat允许你选择任何用户自定义日期时间格式来运行。例如:

import java.util.*;import java.text.*;public class DateDemo {   public static void main(String args[]) {      Date dNow = new Date( );      SimpleDateFormat ft =       new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");      System.out.println("Current Date: " + ft.format(dNow));   }}

以上实例编译运行结果如下:

Current Date: Sun 2004.07.18 at 04:14:09 PM PDT

简单的DateFormat格式化编码

时间模式字符串用来指定时间格式。在此模式中,所有的ASCII字母被保留为模式字母,定义如下:

字母 描述 示例
G 纪元标记 AD
y 四位年份 2001
M 月份 July or 07
d 一个月的日期 10
h  A.M./P.M. (1~12)格式小时 12
H 一天中的小时 (0~23) 22
m 分钟数 30
s 秒数 55
S 毫秒数 234
E 星期几 Tuesday
D 一年中的日子 360
F 一个月中第几周的周几 2 (second Wed. in July)
w 一年中第几周 40
W 一个月中第几周 1
a A.M./P.M. 标记 PM
k 一天中的小时(1~24) 24
K  A.M./P.M. (0~11)格式小时 10
z 时区 Eastern Standard Time
' 文字定界符 Delimiter
" 单引号 `

使用printf格式化日期

printf方法可以很轻松地格式化时间和日期。使用两个字母格式,它以t开头并且以下面表格中的一个字母结尾。例如:

import java.util.Date;public class DateDemo {  public static void main(String args[]) {     // 初始化 Date 对象     Date date = new Date();     // 使用toString()显示日期和时间     String str = String.format("Current Date/Time : %tc", date );     System.out.printf(str);  }}

以上实例编译运行结果如下:

Current Date/Time : Sat Dec 15 16:37:57 MST 2012

如果你需要重复提供日期,那么利用这种方式来格式化它的每一部分就有点复杂了。因此,可以利用一个格式化字符串指出要被格式化的参数的索引。

索引必须紧跟在%后面,而且必须以$结束。例如:

import java.util.Date;  public class DateDemo {   public static void main(String args[]) {       // 初始化 Date 对象       Date date = new Date();               // 使用toString()显示日期和时间       System.out.printf("%1$s %2$tB %2$td, %2$tY",                          "Due date:", date);   }}

以上实例编译运行结果如下:

Due date: February 09, 2004

或者,你可以使用<标志。它表明先前被格式化的参数要被再次使用。例如:

import java.util.Date;  public class DateDemo {   public static void main(String args[]) {       // 初始化 Date 对象       Date date = new Date();               // 显示格式化时间       System.out.printf("%s %tB %<te, %<tY",                           "Due date:", date);   }} 

以上实例编译运行结果如下:

Due date: February 09, 2004

日期和时间转换字符

字符 描述 例子
c 完整的日期和时间 Mon May 04 09:51:52 CDT 2009
F ISO 8601 格式日期 2004-02-09
D U.S. 格式日期 (月/日/年) 02/09/2004
T 24小时时间 18:05:19
r 12小时时间 06:05:19 pm
R 24小时时间,不包含秒 18:05
Y 4位年份(包含前导0) 2004
y 年份后2位(包含前导0) 04
C 年份前2位(包含前导0) 20
B 月份全称 February
b 月份简称 Feb
n 2位月份(包含前导0) 02
d 2位日子(包含前导0) 03
e 2位日子(不包含前导0) 9
A 星期全称 Monday
a 星期简称 Mon
j 3位年份(包含前导0) 069
H 2位小时(包含前导0), 00 到 23 18
k 2位小时(不包含前导0),  0 到 23 18
I 2位小时(包含前导0), 01 到 12 06
l 2位小时(不包含前导0),  1 到 12 6
M 2位分钟(包含前导0) 05
S 2位秒数(包含前导0) 19
L 3位毫秒(包含前导0) 047
N 9位纳秒(包含前导0) 047000000
P 大写上下午标志 PM
p 小写上下午标志 pm
z 从GMT的RFC 822数字偏移 -0800
Z 时区 PST
s 自 1970-01-01 00:00:00 GMT的秒数 1078884319
Q 自 1970-01-01 00:00:00 GMT的毫妙 1078884319047

还有其他有用的日期和时间相关的类。对于更多的细节,你可以参考到Java标准文档。


解析字符串为时间

SimpleDateFormat 类有一些附加的方法,特别是parse(),它试图按照给定的SimpleDateFormat 对象的格式化存储来解析字符串。例如:

import java.util.*;import java.text.*;  public class DateDemo {   public static void main(String args[]) {      SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd");       String input = args.length == 0 ? "1818-11-11" : args[0];       System.out.print(input + " Parses as ");       Date t;       try {           t = ft.parse(input);           System.out.println(t);       } catch (ParseException e) {           System.out.println("Unparseable using " + ft);       }   }}

以上实例编译运行结果如下:

$ java DateDemo1818-11-11 Parses as Wed Nov 11 00:00:00 GMT 1818$ java DateDemo 2007-12-012007-12-01 Parses as Sat Dec 01 00:00:00 GMT 2007

Java 休眠(sleep)

你可以让程序休眠一毫秒的时间或者到您的计算机的寿命长的任意段时间。例如,下面的程序会休眠3秒:

import java.util.*;  public class SleepDemo {   public static void main(String args[]) {      try {          System.out.println(new Date( ) + "
");          Thread.sleep(5*60*10);          System.out.println(new Date( ) + "
");       } catch (Exception e) {           System.out.println("Got an exception!");       }   }}

以上实例编译运行结果如下:

Sun May 03 18:04:41 GMT 2009Sun May 03 18:04:44 GMT 2009

测量时间

下面的一个例子表明如何测量时间间隔(以毫秒为单位):

import java.util.*;  public class DiffDemo {   public static void main(String args[]) {      try {         long start = System.currentTimeMillis( );         System.out.println(new Date( ) + "
");         Thread.sleep(5*60*10);         System.out.println(new Date( ) + "
");         long end = System.currentTimeMillis( );         long diff = end - start;         System.out.println("Difference is : " + diff);      } catch (Exception e) {         System.out.println("Got an exception!");      }   }}

以上实例编译运行结果如下:

Sun May 03 18:16:51 GMT 2009Sun May 03 18:16:57 GMT 2009Difference is : 5993

Calendar类

我们现在已经能够格式化并创建一个日期对象了,但是我们如何才能设置和获取日期数据的特定部分呢,比如说小时,日,或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类。

Calendar类的功能要比Date类强大很多,而且在实现方式上也比Date类要复杂一些。

Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。

创建一个代表系统当前日期的Calendar对象

Calendar c = Calendar.getInstance();//默认是当前日期

创建一个指定日期的Calendar对象

使用Calendar类代表特定的时间,需要首先创建一个Calendar的对象,然后再设定该对象中的年月日参数来完成。

//创建一个代表2009年6月12日的Calendar对象Calendar c1 = Calendar.getInstance();c1.set(2009, 6 - 1, 12);

Calendar类对象字段类型

Calendar类中用一下这些常量表示不同的意义,jdk内的很多类其实都是采用的这种思想

常量描述
Calendar.YEAR年份
Calendar.MONTH月份
Calendar.DATE日期
Calendar.DAY_OF_MONTH日期,和上面的字段意义完全相同
Calendar.HOUR12小时制的小时
Calendar.HOUR_OF_DAY24小时制的小时
Calendar.MINUTE分钟
Calendar.SECOND
Calendar.DAY_OF_WEEK星期几

Calendar类对象信息的设置

Set设置

如:

Calendar c1 = Calendar.getInstance();

调用:

public final void set(int year,int month,int date)
c1.set(2009, 6 - 1, 12);//把Calendar对象c1的年月日分别设这为:2009、5、12

利用字段类型设置

如果只设定某个字段,例如日期的值,则可以使用如下set方法:

public void set(int field,int value)

把 c1对象代表的日期设置为10号,其它所有的数值会被重新计算

c1.set(Calendar.DATE,10);

把c1对象代表的年份设置为2008年,其他的所有数值会被重新计算

c1.set(Calendar.YEAR,2008);

其他字段属性set的意义以此类推

Add设置

Calendar c1 = Calendar.getInstance();

把c1对象的日期加上10,也就是c1所表的日期的10天后的日期,其它所有的数值会被重新计算

c1.add(Calendar.DATE, 10);

把c1对象的日期减去10,也就是c1所表的日期的10天前的日期,其它所有的数值会被重新计算

c1.add(Calendar.DATE, -10);

其他字段属性的add的意义以此类推

Calendar类对象信息的获得

Calendar c1 = Calendar.getInstance();// 获得年份int year = c1.get(Calendar.YEAR);// 获得月份int month = c1.get(Calendar.MONTH) + 1;// 获得日期int date = c1.get(Calendar.DATE);// 获得小时int hour = c1.get(Calendar.HOUR_OF_DAY);// 获得分钟int minute = c1.get(Calendar.MINUTE);// 获得秒int second = c1.get(Calendar.SECOND);// 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)int day = c1.get(Calendar.DAY_OF_WEEK);

GregorianCalendar类

Calendar类实现了公历日历,GregorianCalendar是Calendar类的一个具体实现。

Calendar 的getInstance()方法返回一个默认用当前的语言环境和时区初始化的GregorianCalendar对象。GregorianCalendar定义了两个字段:AD和BC。这些代表公历定义的两个时代。

下面列出GregorianCalendar对象的几个构造方法:

序号 构造函数和说明
1 GregorianCalendar()
在具有默认语言环境的默认时区内使用当前时间构造一个默认的 GregorianCalendar。
2 GregorianCalendar(int year, int month, int date)
在具有默认语言环境的默认时区内构造一个带有给定日期设置的 GregorianCalendar
3 GregorianCalendar(int year, int month, int date, int hour, int minute)
为具有默认语言环境的默认时区构造一个具有给定日期和时间设置的 GregorianCalendar。
4 GregorianCalendar(int year, int month, int date, int hour, int minute, int second)
  为具有默认语言环境的默认时区构造一个具有给定日期和时间设置的 GregorianCalendar。
5 GregorianCalendar(Locale aLocale)
在具有给定语言环境的默认时区内构造一个基于当前时间的 GregorianCalendar。
6 GregorianCalendar(TimeZone zone)
在具有默认语言环境的给定时区内构造一个基于当前时间的 GregorianCalendar。
7 GregorianCalendar(TimeZone zone, Locale aLocale)
 在具有给定语言环境的给定时区内构造一个基于当前时间的 GregorianCalendar。

这里是GregorianCalendar 类提供的一些有用的方法列表:

序号 方法和说明
1 void add(int field, int amount)
根据日历规则,将指定的(有符号的)时间量添加到给定的日历字段中。
2 protected void computeFields()
转换UTC毫秒值为时间域值
3 protected void computeTime()
覆盖Calendar ,转换时间域值为UTC毫秒值
4 boolean equals(Object obj)
比较此 GregorianCalendar 与指定的 Object。
5 int get(int field)
获取指定字段的时间值
6 int getActualMaximum(int field)
返回当前日期,给定字段的最大值
7 int getActualMinimum(int field)
返回当前日期,给定字段的最小值
8 int getGreatestMinimum(int field)
 返回此 GregorianCalendar 实例给定日历字段的最高的最小值。
9 Date getGregorianChange()
获得格里高利历的更改日期。
10 int getLeastMaximum(int field)
返回此 GregorianCalendar 实例给定日历字段的最低的最大值
11 int getMaximum(int field)
返回此 GregorianCalendar 实例的给定日历字段的最大值。
12 Date getTime()
获取日历当前时间。
13 long getTimeInMillis()
获取用长整型表示的日历的当前时间
14 TimeZone getTimeZone()
获取时区。
15 int getMinimum(int field)
返回给定字段的最小值。
16 int hashCode()
重写hashCode.
17 boolean isLeapYear(int year)
确定给定的年份是否为闰年。
18 void roll(int field, boolean up)
在给定的时间字段上添加或减去(上/下)单个时间单元,不更改更大的字段。
19 void set(int field, int value)
用给定的值设置时间字段。
20 void set(int year, int month, int date)
设置年、月、日的值。
21 void set(int year, int month, int date, int hour, int minute)
设置年、月、日、小时、分钟的值。
22 void set(int year, int month, int date, int hour, int minute, int second)
设置年、月、日、小时、分钟、秒的值。
23 void setGregorianChange(Date date)
设置 GregorianCalendar 的更改日期。
24 void setTime(Date date)
用给定的日期设置Calendar的当前时间。
25 void setTimeInMillis(long millis)
用给定的long型毫秒数设置Calendar的当前时间。
26 void setTimeZone(TimeZone value)
用给定时区值设置当前时区。
27 String toString()
返回代表日历的字符串。

实例

import java.util.*;  public class GregorianCalendarDemo {   public static void main(String args[]) {      String months[] = {      "Jan", "Feb", "Mar", "Apr",      "May", "Jun", "Jul", "Aug",      "Sep", "Oct", "Nov", "Dec"};            int year;      // 初始化 Gregorian 日历      // 使用当前时间和日期      // 默认为本地时间和时区      GregorianCalendar gcalendar = new GregorianCalendar();      // 显示当前时间和日期的信息      System.out.print("Date: ");      System.out.print(months[gcalendar.get(Calendar.MONTH)]);      System.out.print(" " + gcalendar.get(Calendar.DATE) + " ");      System.out.println(year = gcalendar.get(Calendar.YEAR));      System.out.print("Time: ");      System.out.print(gcalendar.get(Calendar.HOUR) + ":");      System.out.print(gcalendar.get(Calendar.MINUTE) + ":");      System.out.println(gcalendar.get(Calendar.SECOND));            // 测试当前年份是否为闰年      if(gcalendar.isLeapYear(year)) {         System.out.println("当前年份是闰年");      }      else {         System.out.println("当前年份不是闰年");      }   }}

以上实例编译运行结果如下:

Date: Apr 22 2009Time: 11:25:27当前年份不是闰年

关于Calender 类的完整列表,你可以参考标准的Java文档。


在前面几个章节中我们经常使用到System.out.println(),那么它是什么呢?

println()是一个方法(Method),而System是系统类(Class),out是标准输出对象(Object)。这句话的用法是调用系统类System中的标准输出对象out中的方法println()。

那么什么是方法呢?

Java方法是语句的集合,它们在一起执行一个功能。

  • 方法是解决一类问题的步骤的有序组合

  • 方法包含于类或对象中

  • 方法在程序中被创建,在其他地方被引用


方法的定义

一般情况下,定义一个方法包含以下语法:

修饰符 返回值类型 方法名 (参数类型 参数名){    ...    方法体    ...    return 返回值;}

方法包含一个方法头和一个方法体。下面是一个方法的所有部分:

  • 修饰符:修饰符,这是可选的,告诉编译器如何调用该方法。定义了该方法的访问类型。

  • 返回值类型 :方法可能会返回值。returnValueType是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值。在这种情况下,returnValueType是关键字void

  • 方法名:是方法的实际名称。方法名和参数表共同构成方法签名。

  • 参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。

  • 方法体:方法体包含具体的语句,定义该方法的功能。

如:

public static int age(int birthday){...}

参数可以有多个:

static float interest(float principal, int year){...}

注意: 在一些其它语言中方法指过程和函数。一个返回非void类型返回值的方法称为函数;一个返回void类型返回值的方法叫做过程。

实例

下面的方法包含2个参数num1和num2,它返回这两个参数的最大值。

/** 返回两个整型变量数据的较大值 */public static int max(int num1, int num2) {   int result;   if (num1 > num2){      result = num1;   }else{      result = num2;   }   return result; }


方法调用

Java支持两种调用方法的方式,根据方法是否返回值来选择。

当程序调用一个方法时,程序的控制权交给了被调用的方法。当被调用方法的返回语句执行或者到达方法体闭括号时候交还控制权给程序。

当方法返回一个值的时候,方法调用通常被当做一个值。例如:

int larger = max(30, 40);

如果方法返回值是void,方法调用一定是一条语句。例如,方法println返回void。下面的调用是个语句:

System.out.println("Welcome to Java!");

示例

下面的例子演示了如何定义一个方法,以及如何调用它:

public class TestMax {   /** 主方法 */   public static void main(String[] args) {      int i = 5;      int j = 2;      int k = max(i, j);      System.out.println("The maximum between " + i +                    " and " + j + " is " + k);   }   /** 返回两个整数变量较大的值 */   public static int max(int num1, int num2) {      int result;      if (num1 > num2){         result = num1;      }else{         result = num2;      }      return result;    }}

以上实例编译运行结果如下:

The maximum between 5 and 2 is 5

这个程序包含main方法和max方法。Main方法是被JVM调用的,除此之外,main方法和其它方法没什么区别。

main方法的头部是不变的,如例子所示,带修饰符public和static,返回void类型值,方法名字是main,此外带个一个String[]类型参数。String[]表明参数是字符串数组。


void 关键字

本节说明如何声明和调用一个void方法。

下面的例子声明了一个名为printGrade的方法,并且调用它来打印给定的分数。

示例

public class TestVoidMethod {   public static void main(String[] args) {      printGrade(78.5);   }   public static void printGrade(double score) {      if (score >= 90.0) {         System.out.println('A');      }      else if (score >= 80.0) {         System.out.println('B');      }      else if (score >= 70.0) {         System.out.println('C');      }      else if (score >= 60.0) {         System.out.println('D');      }      else {         System.out.println('F');      }   }}

以上实例编译运行结果如下:

C

这里printGrade方法是一个void类型方法,它不返回值。

一个void方法的调用一定是一个语句。 所以,它被在main方法第三行以语句形式调用。就像任何以分号结束的语句一样。


通过值传递参数

调用一个方法时候需要提供参数,你必须按照参数列表指定的顺序提供。

例如,下面的方法连续n次打印一个消息:

public static void nPrintln(String message, int n) {   for (int i = 0; i < n; i++)      System.out.println(message);}

示例

下面的例子演示按值传递的效果。

该程序创建一个方法,该方法用于交换两个变量。

public class TestPassByValue {   public static void main(String[] args) {      int num1 = 1;      int num2 = 2;      System.out.println("Before swap method, num1 is " +                          num1 + " and num2 is " + num2);      // 调用swap方法      swap(num1, num2);      System.out.println("After swap method, num1 is " +                         num1 + " and num2 is " + num2);   }   /** 交换两个变量的方法 */   public static void swap(int n1, int n2) {      System.out.println("	Inside the swap method");      System.out.println("		Before swapping n1 is " + n1                           + " n2 is " + n2);      // 交换 n1 与 n2的值      int temp = n1;      n1 = n2;      n2 = temp;      System.out.println("		After swapping n1 is " + n1                           + " n2 is " + n2);   }}

以上实例编译运行结果如下:

Before swap method, num1 is 1 and num2 is 2        Inside the swap method                Before swapping n1 is 1 n2 is 2                After swapping n1 is 2 n2 is 1After swap method, num1 is 1 and num2 is 2

传递两个参数调用swap方法。有趣的是,方法被调用后,实参的值并没有改变。


方法的重载

上面使用的max方法仅仅适用于int型数据。但如果你想得到两个浮点类型数据的最大值呢?

解决方法是创建另一个有相同名字但参数不同的方法,如下面代码所示:

public static double max(double num1, double num2) {  if (num1 > num2){    return num1;  }else{    return num2;  }}

如果你调用max方法时传递的是int型参数,则 int型参数的max方法就会被调用;

如果传递的是double型参数,则double类型的max方法体会被调用,这叫做方法重载;

就是说一个类的两个方法拥有相同的名字,但是有不同的参数列表。

Java编译器根据方法签名判断哪个方法应该被调用。

方法重载可以让程序更清晰易读。执行密切相关任务的方法应该使用相同的名字。

重载的方法必须拥有不同的参数列表。你不能仅仅依据修饰符或者返回类型的不同来重载方法。


变量作用域

变量的范围是程序中该变量可以被引用的部分。

方法内定义的变量被称为局部变量。

局部变量的作用范围从声明开始,直到包含它的块结束。

局部变量必须声明才可以使用。

方法的参数范围涵盖整个方法。参数实际上是一个局部变量。

for循环的初始化部分声明的变量,其作用范围在整个循环。

但循环体内声明的变量其适用范围是从它声明到循环体结束。它包含如下所示的变量声明:

你可以在一个方法里,不同的非嵌套块中多次声明一个具有相同的名称局部变量,但你不能在嵌套块内两次声明局部变量。

命令行参数的使用

有时候你希望运行一个程序时候再传递给它消息。这要靠传递命令行参数给main()函数实现。

命令行参数是在执行程序时候紧跟在程序名字后面的信息。

实例

下面的程序打印所有的命令行参数:

public class CommandLine {   public static void main(String args[]){       for(int i=0; i<args.length; i++){                    System.out.println("args [" + i + "]: " + args[i]);      }    } }

如下所示,运行这个程序:

java CommandLine this is a command line 200 -100

运行结果如下:

args[0]: thisargs[1]: isargs[2]: aargs[3]: commandargs[4]: lineargs[5]: 200args[6]: -100


构造方法

当一个对象被创建时候,构造方法用来初始化该对象。构造方法和它所在类的名字相同,但构造方法没有返回值。

通常会使用构造方法给一个类的实例变量赋初值,或者执行其它必要的步骤来创建一个完整的对象。

不管你是否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个默认构造方法,它把所有成员初始化为0。

一旦你定义了自己的构造方法,默认构造方法就会失效。

实例

下面是一个使用构造方法的例子:

// 一个简单的构造函数static class MyClass {   int x;      // 以下是构造函数   MyClass() {      x = 10;   }}

你可以像下面这样调用构造方法来初始化一个对象:

public class ConsDemo {   public static void main(String args[]) {      MyClass t1 = new MyClass();      MyClass t2 = new MyClass();      System.out.println(t1.x + " " + t2.x);   }}

大多时候需要一个有参数的构造方法。

实例

下面是一个使用构造方法的例子:

// 一个简单的构造函数class MyClass {   int x;      // 以下是构造函数   MyClass(int i ) {      x = i;   }}

你可以像下面这样调用构造方法来初始化一个对象:

public class ConsDemo {   public static void main(String args[]) {      MyClass t1 = new MyClass( 10 );      MyClass t2 = new MyClass( 20 );      System.out.println(t1.x + " " + t2.x);   }}

运行结果如下:

10 20

可变参数

JDK 1.5 开始,Java支持传递同类型的可变参数给一个方法。

方法的可变参数的声明如下所示:

typeName... parameterName

在方法声明中,在指定参数类型后加一个省略号(...) 。

一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。

实例

public class VarargsDemo {   public static void main(String args[]) {      // 调用可变参数的方法	  printMax(34, 3, 3, 2, 56.5);      printMax(new double[]{1, 2, 3});   }   public static void printMax( double... numbers) {   if (numbers.length == 0) {      System.out.println("No argument passed");      return;   }   double result = numbers[0];   for (int i = 1; i <  numbers.length; i++)       if (numbers[i] >  result){          result = numbers[i];       }      System.out.println("The max value is " + result);   }}

以上实例编译运行结果如下:

The max value is 56.5The max value is 3.0


finalize() 方法

Java允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,这个方法叫做finalize( ),它用来清除回收对象。

例如,你可以使用finalize()来确保一个对象打开的文件被关闭了。

在finalize()方法里,你必须指定在对象销毁时候要执行的操作。

finalize()一般格式是:

protected void finalize(){   // 在这里终结代码}

关键字protected是一个限定符,它确保finalize() 方法不会被该类以外的代码调用。

当然,Java的内存回收可以由JVM来自动完成。如果你手动使用,则可以使用上面的方法。

实例

public class FinalizationDemo {      public static void main(String[] args) {          Cake c1 = new Cake(1);          Cake c2 = new Cake(2);          Cake c3 = new Cake(3);                    c2 = c3 = null;          System.gc(); //调用Java垃圾收集器    }  }    class Cake extends Object {      private int id;      public Cake(int id) {          this.id = id;          System.out.println("Cake Object " + id + "is created");      }            protected void finalize() throws java.lang.Throwable {          super.finalize();          System.out.println("Cake Object " + id + "is disposed");      }  }

运行以上代码,输出结果如下:

C:1>java FinalizationDemo  Cake Object 1is created  Cake Object 2is created  Cake Object 3is created  Cake Object 3is disposed  Cake Object 2is disposed


Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。

Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。

一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。

Java为I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。

但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。


读取控制台输入

Java 的控制台输入由 System.in 完成。

为了获得一个绑定到控制台的字符流,你可以把 System.in 包装在一个 BufferedReader 对象中来创建一个字符流。

下面是创建 BufferedReader 的基本语法:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

BufferedReader 对象创建后,我们便可以使用 read() 方法从控制台读取一个字符,或者用 readLine() 方法读取一个字符串。


从控制台读取多字符输入

从 BufferedReader 对象读取一个字符要使用 read() 方法,它的语法如下:

int read() throws IOException

每次调用 read() 方法,它从输入流读取一个字符并把该字符作为整数值返回。 当流结束的时候返回 -1。该方法抛出 IOException。

下面的程序示范了用 read() 方法从控制台不断读取字符直到用户输入 "q" 。

// 使用 BufferedReader 在控制台读取字符import java.io.*;public class BRRead {   public static void main(String args[]) throws IOException   {      char c;      // 使用 System.in 创建 BufferedReader       BufferedReader br = new BufferedReader(new                          InputStreamReader(System.in));      System.out.println("输入字符, 按下 'q' 键退出.");      // 读取字符      do {         c = (char) br.read();         System.out.println(c);      } while(c != 'q');   }}

以上实例编译运行结果如下:

输入字符, 按下 'q' 键退出.123abcq123abcq

从控制台读取字符串

从标准输入读取一个字符串需要使用 BufferedReader 的 readLine() 方法。

它的一般格式是:

String readLine() throws IOException

下面的程序读取和显示字符行直到你输入了单词 "end"。

// 使用 BufferedReader 在控制台读取字符import java.io.*;public class BRReadLines {   public static void main(String args[]) throws IOException   {      // 使用 System.in 创建 BufferedReader       BufferedReader br = new BufferedReader(new                              InputStreamReader(System.in));      String str;      System.out.println("Enter lines of text.");      System.out.println("Enter 'end' to quit.");      do {         str = br.readLine();         System.out.println(str);      } while(!str.equals("end"));   }}

以上实例编译运行结果如下:

Enter lines of text.Enter 'end' to quit.This is line oneThis is line oneThis is line twoThis is line twoendend

控制台输出

在此前已经介绍过,控制台的输出由 print() 和 println() 完成。这些方法都由类 PrintStream 定义,System.out 是该类对象的一个引用。

PrintStream 继承了 OutputStream 类,并且实现了方法 write()。这样,write() 也可以用来往控制台写操作。

PrintStream 定义 write() 的最简单格式如下所示:

void write(int byteval)

该方法将 byteval 的低八位字节写到流中。

实例

下面的例子用 write() 把字符 "A" 和紧跟着的换行符输出到屏幕:

import java.io.*;// 演示 System.out.write().public class WriteDemo {   public static void main(String args[]) {      int b;       b = 'A';      System.out.write(b);      System.out.write('
');   }}

运行以上实例在输出窗口输出 "A" 字符

A

注意:write() 方法不经常使用,因为 print() 和 println() 方法用起来更为方便。


读写文件

如前所述,一个流被定义为一个数据序列。输入流用于从源读取数据,输出流用于向目标写数据。

下图是一个描述输入流和输出流的类层次图。

下面将要讨论的两个重要的流是 FileInputStream 和 FileOutputStream:


FileInputStream

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。

有多种构造方法可用来创建对象。

可以使用字符串类型的文件名来创建一个输入流对象来读取文件:

InputStream f = new FileInputStream("C:/java/hello");

也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:

File f = new File("C:/java/hello");InputStream f = new FileInputStream(f);

创建了 InputStream 对象,就可以使用下面的方法来读取流或者进行其他的流操作。

序号方法及描述
1public void close() throws IOException{}
关闭此文件输入流并释放与此流有关的所有系统资源。抛出 IOException 异常。
2protected void finalize()throws IOException {}
这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出 IOException 异常。
3public int read(int r)throws IOException{}
这个方法从 InputStream 对象读取指定字节的数据。返回为整数值。返回下一字节数据,如果已经到结尾则返回 -1。
4public int read(byte[] r) throws IOException{}
这个方法从输入流读取 r.length 长度的字节。返回读取的字节数。如果是文件结尾则返回 -1。
5public int available() throws IOException{}
返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取的字节数。返回一个整数值。

除了 InputStream 外,还有一些其他的输入流,更多的细节参考下面链接:


FileOutputStream

该类用来创建一个文件并向文件中写数据。

如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。

有两个构造方法可以用来创建 FileOutputStream 对象。

使用字符串类型的文件名来创建一个输出流对象:

OutputStream f = new FileOutputStream("C:/java/hello")

也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用 File() 方法来创建一个文件对象:

File f = new File("C:/java/hello");OutputStream f = new FileOutputStream(f);

创建 OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。

序号方法及描述
1public void close() throws IOException{}
关闭此文件输入流并释放与此流有关的所有系统资源。抛出 IOException 异常。
2protected void finalize()throws IOException {}
这个方法清除与该文件的连接。确保在不再引用文件输入流时调用其 close 方法。抛出 IOException 异常。
3public void write(int w)throws IOException{}
这个方法把指定的字节写到输出流中。
4public void write(byte[] w)
把指定数组中 w.length 长度的字节写到 OutputStream 中。

除了 OutputStream 外,还有一些其他的输出流,更多的细节参考下面链接:

实例

下面是一个演示 InputStream 和 OutputStream 用法的例子:

import java.io.*; public class fileStreamTest {    public static void main(String args[]) {        try {            byte bWrite[] = { 11, 21, 3, 40, 5 };            OutputStream os = new FileOutputStream("test.txt");            for (int x = 0; x < bWrite.length; x++) {                os.write(bWrite[x]); // writes the bytes            }            os.close();             InputStream is = new FileInputStream("test.txt");            int size = is.available();             for (int i = 0; i < size; i++) {                System.out.print((char) is.read() + "  ");            }            is.close();        } catch (IOException e) {            System.out.print("Exception");        }    }}

上面的程序首先创建文件 test.txt,并把给定的数字以二进制形式写进该文件,同时输出到控制台上。

以上代码由于是二进制写入,可能存在乱码,你可以使用以下代码实例来解决乱码问题:

//文件名 :fileStreamTest2.javaimport java.io.*;public class fileStreamTest2{	public static void main(String[] args) throws IOException {				File f = new File("a.txt");		FileOutputStream fop = new FileOutputStream(f);		// 构建FileOutputStream对象,文件不存在会自动新建				OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");		// 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk				writer.append("中文输入");		// 写入到缓冲区				writer.append("
");		//换行				writer.append("English");		// 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入				writer.close();		//关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉				fop.close();		// 关闭输出流,释放系统资源		FileInputStream fip = new FileInputStream(f);		// 构建FileInputStream对象				InputStreamReader reader = new InputStreamReader(fip, "UTF-8");		// 构建InputStreamReader对象,编码与写入相同		StringBuffer sb = new StringBuffer();		while (reader.ready()) {			sb.append((char) reader.read());			// 转成char加到StringBuffer对象中		}		System.out.println(sb.toString());		reader.close();		// 关闭读取流				fip.close();		// 关闭输入流,释放系统资源	}}

文件和I/O

还有一些关于文件和 I/O 的类,我们也需要知道:


Java中的目录

创建目录:

File 类中有两个方法可以用来创建文件夹:

  • mkdir( ) 方法创建一个文件夹,成功则返回 true,失败则返回 false。失败表明 File 对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建。

  • mkdirs( ) 方法创建一个文件夹和它的所有父文件夹。

下面的例子创建 "/tmp/user/java/bin" 文件夹:

import java.io.File;public class CreateDir {   public static void main(String args[]) {      String dirname = "/tmp/user/java/bin";      File d = new File(dirname);      // 现在创建目录      d.mkdirs();  }}

编译并执行上面代码来创建目录 "/tmp/user/java/bin"。

注意:Java 在 UNIX 和 Windows 自动按约定分辨文件路径分隔符。如果你在 Windows 版本的 Java 中使用分隔符(/) ,路径依然能够被正确解析。


读取目录

一个目录其实就是一个 File 对象,它包含其他文件和文件夹。

如果创建一个 File 对象并且它是一个目录,那么调用 isDirectory( ) 方法会返回 true。

可以通过调用该对象上的 list() 方法,来提取它包含的文件和文件夹的列表。

下面展示的例子说明如何使用 list() 方法来检查一个文件夹中包含的内容:

import java.io.File;public class DirList {   public static void main(String args[]) {      String dirname = "/tmp";      File f1 = new File(dirname);      if (f1.isDirectory()) {         System.out.println( "Directory of " + dirname);         String s[] = f1.list();         for (int i=0; i < s.length; i++) {                         File f = new File(dirname + "/" + s[i]);                         if (f.isDirectory()) {                                System.out.println(s[i] + "是一个目录");                         } else {                System.out.println(s[i] + "是一个文件");             }        }     } else {          System.out.println(dirname + "不是一个目录");     }  }}

以上实例编译运行结果如下:

目录 /tmpbin 是一个目录lib 是一个目录demo 是一个目录test.txt 是一个文件README 是一个文件index.html 是一个文件include 是一个目录

删除目录或文件

删除文件可以使用 java.io.File.delete() 方法。

以下代码会删除目录 /tmp/java/,需要注意的是当删除某一目录时,必须保证该目录下没有其他文件才能正确删除,否则将删除失败。

测试目录结构:

/tmp/java/|-- 1.log|-- test

DeleteFileDemo.java 文件代码:

import java.io.File;public class DeleteFileDemo {    public static void main(String args[]) {        // 这里修改为自己的测试目录        File folder = new File("/tmp/java/");        deleteFolder(folder);    }    // 删除文件及目录    public static void deleteFolder(File folder) {        File[] files = folder.listFiles();        if (files != null) {            for (File f : files) {                if (f.isDirectory()) {                    deleteFolder(f);                } else {                    f.delete();                }            }        }        folder.delete();    }}


java.util.Scanner是Java5的新特征,我们可以通过 Scanner 类来获取用户的输入。

下面是创建 Scanner 对象的基本语法:

 Scanner s = new Scanner(System.in); 

接下来我们演示一个最简单的的数据输入,并通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要使用 hasNext 与 hasNextLine 判断是否还有输入的数据:

使用 next 方法:

import java.util.Scanner; public class ScannerDemo {      public static void main(String[] args) {          Scanner scan = new Scanner(System.in); 		// 从键盘接收数据  		//next方式接收字符串        System.out.println("next方式接收:");        // 判断是否还有输入        if(scan.hasNext()){           	String str1 = scan.next();        	System.out.println("输入的数据为:"+str1);          }      }  } 

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemonext方式接收:youj com输入的数据为:youj

可以看到 com 字符串并未输出,接下来我们看 nextLine。

使用 nextLine 方法:

import java.util.Scanner; public class ScannerDemo {      public static void main(String[] args) {          Scanner scan = new Scanner(System.in); 		// 从键盘接收数据  		//nextLine方式接收字符串        System.out.println("nextLine方式接收:");        // 判断是否还有输入        if(scan.hasNextLine()){           	String str2 = scan.nextLine();        	System.out.println("输入的数据为:"+str2);          }      }  } 

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemonextLine方式接收:youj com输入的数据为:youj com

可以看到 com 字符串输出。

next()与nextLine()区别

next():

  • 1、一定要读取到有效字符后才可以结束输入。
  • 2、对输入有效字符之前遇到的空白,next()方法会自动将其去掉。
  • 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  • next()不能得到带有空格的字符串。

nextLine():

  • 1、以Enter为结束符,也就是说nextLine()方法返回的是输入回车之前的所有字符。
  • 2、可以获得空白。

如果要输入int或float类型的数据,在Scanner类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取:

import java.util.Scanner;  public class ScannerDemo {      public static void main(String[] args) {          Scanner scan = new Scanner(System.in);  		// 从键盘接收数据          int i = 0 ;          float f = 0.0f ;          System.out.print("输入整数:");          if(scan.hasNextInt()){                 			// 判断输入的是否是整数              i = scan.nextInt() ;                			// 接收整数              System.out.println("整数数据:" + i) ;          }else{                                 			// 输入错误的信息              System.out.println("输入的不是整数!") ;          }          System.out.print("输入小数:");          if(scan.hasNextFloat()){              			// 判断输入的是否是小数              f = scan.nextFloat() ;             			// 接收小数              System.out.println("小数数据:" + f) ;          }else{                                			// 输入错误的信息              System.out.println("输入的不是小数!") ;          }      }  } 

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemo输入整数:12整数数据:12输入小数:1.2小数数据:1.2

以下实例我们可以输入多个数字,并求其总和与平均数,每输入一个数字用回车确认,通过输入非数字来结束输入并输出执行结果:

import java.util.Scanner; class ScannerDemo   {      public static void main(String[] args)       {          Scanner scan = new Scanner(System.in);            double sum = 0;          int m = 0;            while(scan.hasNextDouble())          {              double x = scan.nextDouble();              m = m + 1;              sum = sum + x;          }            System.out.println(m+"个数的和为"+sum);          System.out.println(m+"个数的平均值是"+(sum/m));      }  }  

执行以上程序输出结果为:

$ javac ScannerDemo.java$ java ScannerDemo12231521.4end4个数的和为71.44个数的平均值是17.85

更多内容可以参考 API 文档://www.51coolma.cn/manual/jdk1.6/


什么是异常?

程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?在Java中,异常就是Java在编译或运行或者运行过程中出现的错误。 

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用 System.out.println(11/0),那么你是因为你用 0 做了除数,会抛出 java.lang.ArithmeticException 的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。

  • 要打开的文件不存在。

  • 网络通信时连接中断,或者 JVM 内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。-

要理解 Java 异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。


Exception 类的层次

所有的异常类是从 java.lang.Exception 类继承的子类。

Exception 类是 Throwable 类的子类。除了Exception类外,Throwable 还有一个子类 Error 。

Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在 Java 程序处理的范畴之外。

Error 用来指示运行时环境发生的错误。

例如,JVM 内存溢出。一般地,程序不会从错误中恢复。

异常类有两个主要的子类:IOException 类和 RuntimeException 类。

在 Java 内置类中(接下来会说明),有大部分常用检查性和非检查性异常。


Java 内置异常类

Java 语言定义了一些异常类在 java.lang 标准包中。

标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。

Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。

异常描述
ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException当不支持请求的操作时,抛出该异常。

下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。

异常描述
ClassNotFoundException应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException拒绝访问一个类的时候,抛出该异常。
InstantiationException当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException请求的变量不存在
NoSuchMethodException请求的方法不存在

异常方法

下面的列表是 Throwable 类的主要方法:

序号方法及说明
1public String getMessage()
返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
2public Throwable getCause()
返回一个Throwable 对象代表异常原因。
3public String toString()
使用getMessage()的结果返回类的串级名字。
4public void printStackTrace()
打印toString()结果和栈层次到System.err,即错误输出流。
5public StackTraceElement [] getStackTrace()
返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
6public Throwable fillInStackTrace()
用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。

捕获异常

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。

try/catch 代码块中的代码称为保护代码,使用  try/catch 的语法如下:

try{   // 程序代码}catch(ExceptionName e1){   //Catch 块}

Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。

如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。

实例

下面的例子中声明有两个元素的一个数组,当代码试图访问数组的第三个元素的时候就会抛出一个异常。

// 文件名 : ExcepTest.javaimport java.io.*;public class ExcepTest{   public static void main(String args[]){      try{         int a[] = new int[2];         System.out.println("Access element three :" + a[3]);      }catch(ArrayIndexOutOfBoundsException e){         System.out.println("Exception thrown  :" + e);      }      System.out.println("Out of the block");   }}

以上代码编译运行输出结果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3Out of the block

多重捕获块

一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。

多重捕获块的语法如下所示:

 try{    // 程序代码 }catch(异常类型1 异常的变量名1){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }

上面的代码段包含了 3 个 catch 块。

可以在 try 语句后面添加任意数量的 catch 块。

如果保护代码中发生异常,异常被抛给第一个 catch 块。

如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。

如果不匹配,它会被传递给第二个 catch 块。

如此,直到异常被捕获或者通过所有的 catch 块。

实例

该实例展示了怎么使用多重 try/catch。

try{   file = new FileInputStream(fileName);   x = (byte) file.read();}catch(IOException i){   i.printStackTrace();   return -1;}catch(FileNotFoundException f) //Not valid!{   f.printStackTrace();   return -1;}

throws/throw关键字:

如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。

也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

下面方法的声明抛出一个 RemoteException 异常:

import java.io.*;public class className{   public void deposit(double amount) throws RemoteException   {      // Method implementation      throw new RemoteException();   }   //Remainder of class definition}

一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

例如,下面的方法声明抛出 RemoteException 和 InsufficientFundsException:

import java.io.*;public class className{   public void withdraw(double amount) throws RemoteException,                              InsufficientFundsException   {       // Method implementation   }   //Remainder of class definition}

finally 关键字

finally 关键字用来创建在 try 代码块后面执行的代码块。

无论是否发生异常,finally 代码块中的代码总会被执行。

在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

finally 代码块出现在 catch 代码块最后,语法如下:

 try{    // 程序代码 }catch(异常类型1 异常的变量名1){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }finally{    // 程序代码 }

实例

 public class ExcepTest{   public static void main(String args[]){      int a[] = new int[2];      try{         System.out.println("Access element three :" + a[3]);      }catch(ArrayIndexOutOfBoundsException e){         System.out.println("Exception thrown  :" + e);      }      finally{         a[0] = 6;         System.out.println("First element value: " +a[0]);         System.out.println("The finally statement is executed");      }   }}

以上实例编译运行结果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3First element value: 6The finally statement is executed

注意下面事项:

  • catch 不能独立于 try 存在。

  • 在 try/catch 后面添加 finally 块并非强制性要求的。

  • try 代码后不能既没 catch 块也没 finally 块。

  • try, catch, finally 块之间不能添加任何代码。


声明自定义异常

在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。

  • 所有异常都必须是 Throwable 的子类。

  • 如果希望写一个检查性异常类,则需要继承 Exception 类。

  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

可以像下面这样定义自己的异常类:

class MyException extends Exception{}

只继承 Exception 类来创建的异常类是检查性异常类。

下面的 InsufficientFundsException 类是用户定义的异常类,它继承自 Exception。

一个异常类和其它任何类一样,包含有变量和方法。

实例

// 文件名InsufficientFundsException.javaimport java.io.*;public class InsufficientFundsException extends Exception{   private double amount;   public InsufficientFundsException(double amount)   {      this.amount = amount;   }    public double getAmount()   {      return amount;   }}

为了展示如何使用我们自定义的异常类,

在下面的 CheckingAccount 类中包含一个 withdraw() 方法抛出一个 InsufficientFundsException 异常。

// 文件名称 CheckingAccount.javaimport java.io.*;public class CheckingAccount{   private double balance;   private int number;   public CheckingAccount(int number)   {      this.number = number;   }   public void deposit(double amount)   {      balance += amount;   }   public void withdraw(double amount) throws                              InsufficientFundsException   {      if(amount <= balance)       {          balance -= amount;       }       else       {          double needs = amount - balance;          throw new InsufficientFundsException(needs);       }    }    public double getBalance()    {       return balance;    }    public int getNumber()    {       return number;    } }

下面的 BankDemo 程序示范了如何调用 CheckingAccount 类的 deposit() 和 withdraw() 方法。

//文件名称 BankDemo.javapublic class BankDemo{   public static void main(String [] args)   {      CheckingAccount c = new CheckingAccount(101);      System.out.println("Depositing $500...");      c.deposit(500.00);      try      {         System.out.println("
Withdrawing $100...");         c.withdraw(100.00);         System.out.println("
Withdrawing $600...");         c.withdraw(600.00);      }catch(InsufficientFundsException e)      {         System.out.println("Sorry, but you are short $"                                  + e.getAmount());         e.printStackTrace();      }    }}

编译上面三个文件,并运行程序 BankDemo,得到结果如下所示:

Depositing $500...Withdrawing $100...Withdrawing $600...Sorry, but you are short $200.0InsufficientFundsException        at CheckingAccount.withdraw(CheckingAccount.java:25)        at BankDemo.main(BankDemo.java:13)

通用异常

在 Java 中定义了两种类型的异常和错误。

  • JVM(Java虚拟机) 异常:由 JVM 抛出的异常或错误。例如:NullPointerException类ArrayIndexOutOfBoundsException类ClassCastException类

  • 程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException类IllegalStateException类

学习完本教程,建议您进行实战练习来巩固您新学到的知识:点击进入实战


继承是所有 OOP 语言和 Java 语言不可缺少的组成部分。

继承是 Java 面向对象编程技术的一块基石,是面向对象的三大特征之一,也是实现软件复用的重要手段,继承可以理解为一个对象从另一个对象获取属性的过程。

如果类 A 是类 B 的父类,而类  B 是类 C 的父类,我们也称类 C 是 A 的子类,类 C 是从类 A 继承而来的。在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类。

继承中最常使用的两个关键字是 extends implements

这两个关键字的使用决定了一个对象和另一个对象是否是 IS-A (是一个)关系。

通过使用这两个关键字,我们能实现一个对象获取另一个对象的属性。

所有 Java 的类均是由 java.lang.Object 类继承而来的,所以 Object 是所有类的祖先类,而除了 Object 外,所有类必须有一个父类。

通过 extends 关键字可以申明一个类是继承另外一个类而来的,一般形式如下:

// A.javapublic class A {    private int i;    protected int j;     public void func() {     }} // B.javapublic class B extends A {    public int z;    public void fund(){    }    }

以上的代码片段说明,类 B 由类 A 继承而来的,类 B 是类 A 的子类。而类 A 是 Object 的子类,这里可以不显示地声明。

作为子类,类 B 的实例拥有类 A 所有的成员变量,但对于 private 类型的成员变量类 B 却没有访问权限,这保障了类 A 的封装性。


IS-A 关系

IS-A 就是说:一个对象是另一个对象的一个分类。

下面是使用关键字 extends 实现继承。

public class Animal{}public class Mammal extends Animal{}public class Reptile extends Animal{}public class Dog extends Mammal{}

基于上面的例子,以下说法是正确的:

  • Animal 类是 Mammal 类的父类。
  • Animal 类是 Reptile 类的父类。
  • Mammal 类和 Reptile 类是 Animal 类的子类。
  • Dog 类既是 Mammal 类的子类又是 Animal 类的子类。

分析以上示例中的 IS-A 关系,如下:

  • Mammal IS-A Animal
  • Reptile IS-A Animal
  • Dog IS-A Mammal

因此 : Dog IS-A Animal

通过使用关键字 extends ,子类可以继承父类的除 private 属性外所有的属性。

我们通过使用 instanceof 操作符,能够确定 Mammal IS-A Animal

实例

public class Dog extends Mammal{   public static void main(String args[]){      Animal a = new Animal();      Mammal m = new Mammal();      Dog d = new Dog();      System.out.println(m instanceof Animal);      System.out.println(d instanceof Mammal);      System.out.println(d instanceof Animal);   }}

以上实例编译运行结果如下:

truetruetrue

介绍完 extends 关键字之后,我们再来看下 implements 关键字是怎样使用来表示 IS-A 关系。

Implements 关键字在类继承接口的情况下使用, 这种情况不能使用关键字 extends 

实例

public interface Animal {}public class Mammal implements Animal{}public class Dog extends Mammal{}

instanceof 关键字

可以使用 instanceof 运算符来检验 Mammal 和 dog 对象是否是 Animal 类的一个实例。

interface Animal{}class Mammal implements Animal{}public class Dog extends Mammal{   public static void main(String args[]){      Mammal m = new Mammal();      Dog d = new Dog();      System.out.println(m instanceof Animal);      System.out.println(d instanceof Mammal);      System.out.println(d instanceof Animal);   }} 

以上实例编译运行结果如下:

truetruetrue

HAS-A 关系

HAS-A 代表类和它的成员之间的从属关系。这有助于代码的重用和减少代码的错误。

例子

public class Vehicle{}public class Speed{}public class Van extends Vehicle{	private Speed sp;} 

Van 类和 Speed 类是 HAS-A 关系( Van 有一个 Speed ),这样就不用将 Speed 类的全部代码粘贴到 Van 类中了,并且 Speed 类也可以重复利用于多个应用程序。

在面向对象特性中,用户不必担心类的内部怎样实现。

Van 类将实现的细节对用户隐藏起来,因此,用户只需要知道怎样调用 Van 类来完成某一功能,而不必知道 Van 类是自己来做还是调用其他类来做这些工作。

Java 只支持单继承,也就是说,一个类不能继承多个类。

下面的做法是不合法的:

public class extends Animal, Mammal{} 

Java 只支持单继承(继承基本类和抽象类),但是我们可以用接口来实现(多继承接口来实现),代码结构如下:

public class Apple extends Fruit implements Fruit1, Fruit2{}

一般我们继承基本类和抽象类用 extends 关键字,实现接口类的继承用 implements 关键字。


重写 (Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。

也就是说子类能够根据需要实现父类的方法。

在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:

class Animal{   public void move(){      System.out.println("动物可以移动");   }}class Dog extends Animal{   public void move(){      System.out.println("狗可以跑和走");   }}public class TestDog{   public static void main(String args[]){      Animal a = new Animal(); // Animal 对象      Animal b = new Dog(); // Dog 对象      a.move();// 执行 Animal 类的方法      b.move();//执行 Dog 类的方法   }}

以上实例编译运行结果如下:

动物可以移动狗可以跑和走

在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move 方法。

这是由于在编译阶段,只是检查参数的引用类型。

然而在运行时,Java 虚拟机 (JVM) 指定对象的类型并且运行该对象的方法。

因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。

思考以下例子:

class Animal{   public void move(){      System.out.println("动物可以移动");   }}class Dog extends Animal{   public void move(){      System.out.println("狗可以跑和走");   }   public void bark(){      System.out.println("狗可以吠叫");   }}public class TestDog{   public static void main(String args[]){      Animal a = new Animal(); // Animal 对象      Animal b = new Dog(); // Dog 对象      a.move();// 执行 Animal 类的方法      b.move();//执行 Dog 类的方法      a.bark();//执行 Animal 类的方法   }}

以上实例编译运行结果如下:

TestDog.java:30: cannot find symbolsymbol  : method bark()location: class Animal                a.bark();                 ^

该程序将抛出一个编译错误,因为 a 的引用类型 Animal 没有 bark 方法。


方法重写的规则

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 子类方法的访问权限必须大于或等于父类方法的访问权限。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个方法,则不能重写这个方法。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

Super 关键字的使用

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

class Animal{   public void move(){      System.out.println("动物可以移动");   }}class Dog extends Animal{   public void move(){      super.move(); // 应用super类的方法      System.out.println("狗可以跑和走");   }}public class TestDog{   public static void main(String args[]){      Animal b = new Dog(); //      b.move(); //执行 Dog类的方法   }}

以上实例编译运行结果如下:

动物可以移动狗可以跑和走

重载 (Overload)

重载 (overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

重载规则

  • 被重载的方法必须改变参数列表;
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

实例

public class Overloading { 	public int test(){		System.out.println("test1");		return 1;	} 	public void test(int a){		System.out.println("test2");	}	 	//以下两个参数类型顺序不同	public String test(int a,String s){		System.out.println("test3");		return "returntest3";	}	 	public String test(String s,int a){		System.out.println("test4");		return "returntest4";	}	 	public static void main(String[] args){		Overloading o = new Overloading();		System.out.println(o.test());		o.test(1);		System.out.println(o.test(1,"test3"));		System.out.println(o.test("test4",1));	}}

以上实例编译运行结果如下:

test11test2test3returntest3test4returntest4

重写与重载之间的区别

区别点重载方法重写方法
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)


总结

方法的重写 (Overriding) 和重载 (Overloading) 是 java 多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载 (Overloading)。
  • (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写 (Overriding)。
  • (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。



本章主要为大家介绍java多态的概念,以及便于理解的多态简单例子。

Java 多态


多态是同一个行为具有多个不同表现形式或形态的能力。

多态性是对象多种表现形式的体现。

比如我们说"宠物"这个对象,它就有很多不同的表达或实现,比如有小猫、小狗、蜥蜴等等。那么我到宠物店说"请给我一只宠物",服务员给我小猫、小狗或者蜥蜴都可以,我们就说"宠物"这个对象就具备多态性。

接下来让我们通过实例来了解Java的多态。

简单的例子

public interface Vegetarian{}public class Animal{}public class Deer extends Animal implements Vegetarian{}

因为Deer类具有多重继承,所以它具有多态性。以上实例解析如下:

  • 一个 Deer IS-A(是一个) Animal
  • 一个 Deer IS-A(是一个) Vegetarian
  • 一个 Deer IS-A(是一个) Deer
  • 一个 Deer IS-A(是一个)Object

在Java中,所有的对象都具有多态性,因为任何对象都能通过IS-A测试的类型和Object类。

访问一个对象的唯一方法就是通过引用型变量。

引用型变量只能有一种类型,一旦被声明,引用型变量的类型就不能被改变了。

引用型变量不仅能够被重置为其他对象,前提是这些对象没有被声明为final。还可以引用和它类型相同的或者相兼容的对象。它可以声明为类类型或者接口类型。

当我们将引用型变量应用于Deer对象的引用时,下面的声明是合法的:

Deer d = new Deer();Animal a = d;Vegetarian v = d;Object o = d;

所有的引用型变量d,a,v,o都指向堆中相同的Deer对象。


虚方法

我们将介绍在Java中,当设计类时,被重写的方法的行为怎样影响多态性。

我们已经讨论了方法的重写,也就是子类能够重写父类的方法。

当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。

要想调用父类中被重写的方法,则必须使用关键字super。

/* 文件名 : Employee.java */public class Employee{   private String name;   private String address;   private int number;   public Employee(String name, String address, int number)   {      System.out.println("Constructing an Employee");      this.name = name;      this.address = address;      this.number = number;   }   public void mailCheck()   {      System.out.println("Mailing a check to " + this.name       + " " + this.address);   }   public String toString()   {      return name + " " + address + " " + number;   }   public String getName()   {      return name;   }   public String getAddress()   {      return address;   }   public void setAddress(String newAddress)   {      address = newAddress;   }   public int getNumber()   {     return number;   }}

假设下面的类继承Employee类:

/* 文件名 : Salary.java */public class Salary extends Employee{   private double salary; //Annual salary   public Salary(String name, String address, int number, double      salary)   {       super(name, address, number);       setSalary(salary);   }   public void mailCheck()   {       System.out.println("Within mailCheck of Salary class ");       System.out.println("Mailing check to " + getName()       + " with salary " + salary);   }   public double getSalary()   {       return salary;   }   public void setSalary(double newSalary)   {       if(newSalary >= 0.0)       {          salary = newSalary;       }   }   public double computePay()   {      System.out.println("Computing salary pay for " + getName());      return salary/52;   }}

现在我们仔细阅读下面的代码,尝试给出它的输出结果:

/* 文件名 : VirtualDemo.java */public class VirtualDemo{   public static void main(String [] args)   {      Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);      Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);      System.out.println("Call mailCheck using Salary reference --");      s.mailCheck();      System.out.println("
 Call mailCheck using Employee reference--");      e.mailCheck();    }}

以上实例编译运行结果如下:

Constructing an EmployeeConstructing an EmployeeCall mailCheck using Salary reference --Within mailCheck of Salary classMailing check to Mohd Mohtashim with salary 3600.0Call mailCheck using Employee reference--Within mailCheck of Salary classMailing check to John Adams with salary 2400.0

例子中,我们实例化了两个Salary对象。一个使用Salary引用s,另一个使用Employee引用。

编译时,编译器检查到mailCheck()方法在Salary类中的声明。

在调用s.mailCheck()时,Java虚拟机(JVM)调用Salary类的mailCheck()方法。

因为e是Employee的引用,所以调用e的mailCheck()方法则有完全不同的结果。

当编译器检查e.mailCheck()方法时,编译器检查到Employee类中的mailCheck()方法。

在编译的时候,编译器使用Employee类中的mailCheck()方法验证该语句, 但是在运行的时候,Java虚拟机(JVM)调用的是Salary类中的mailCheck()方法。

该行为被称为虚拟方法调用,该方法被称为虚拟方法。

Java中所有的方法都能以这种方式表现,借此,重写的方法能在运行时调用,不管编译的时候源代码中引用变量是什么数据类型。

多态的实现方式

方式一:重写:

这个内容已经在上一章节详细讲过,就不再阐述,详细可访问:Java 重写(Override)与重载(Overload)

方式二:接口

  • 1. 生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。
  • 2. java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。

方式三:抽象类和抽象方法

详情请看 Java 抽象类 章节


在 Java 面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。


抽象类

在Java语言中使用abstract class来定义抽象类。如下实例:

/* 文件名 : Employee.java */public abstract class Employee{   private String name;   private String address;   private int number;   public Employee(String name, String address, int number)   {      System.out.println("Constructing an Employee");      this.name = name;      this.address = address;      this.number = number;   }   public double computePay()   {     System.out.println("Inside Employee computePay");     return 0.0;   }   public void mailCheck()   {      System.out.println("Mailing a check to " + this.name       + " " + this.address);   }   public String toString()   {      return name + " " + address + " " + number;   }   public String getName()   {      return name;   }   public String getAddress()   {      return address;   }   public void setAddress(String newAddress)   {      address = newAddress;   }   public int getNumber()   {     return number;   }}

注意到该Employee类没有什么不同,尽管该类是抽象类,但是它仍然有3个成员变量,7个成员方法和1个构造方法。 现在如果你尝试如下的例子:

/* 文件名 : AbstractDemo.java */public class AbstractDemo{   public static void main(String [] args)   {      /* 以下是不允许的,会引发错误 */      Employee e = new Employee("George W.", "Houston, TX", 43);      System.out.println("
 Call mailCheck using Employee reference--");      e.mailCheck();    }}

当你尝试编译AbstractDemo类时,会产生如下错误:

Employee.java:46: Employee is abstract; cannot be instantiated      Employee e = new Employee("George W.", "Houston, TX", 43);                   ^1 error

继承抽象类

我们能通过一般的方法继承Employee类:

/* 文件名 : Salary.java */public class Salary extends Employee{   private double salary; //Annual salary   public Salary(String name, String address, int number, double      salary)   {       super(name, address, number);       setSalary(salary);   }   public void mailCheck()   {       System.out.println("Within mailCheck of Salary class ");       System.out.println("Mailing check to " + getName()       + " with salary " + salary);   }   public double getSalary()   {       return salary;   }   public void setSalary(double newSalary)   {       if(newSalary >= 0.0)       {          salary = newSalary;       }   }   public double computePay()   {      System.out.println("Computing salary pay for " + getName());      return salary/52;   }}

尽管我们不能实例化一个Employee类的对象,但是如果我们实例化一个Salary类对象,该对象将从Employee类继承3个成员变量和7个成员方法。

/* 文件名 : AbstractDemo.java */public class AbstractDemo{   public static void main(String [] args)   {      Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);      Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);      System.out.println("Call mailCheck using Salary reference --");      s.mailCheck();      System.out.println("
 Call mailCheck using Employee reference--");      e.mailCheck();    }}

以上程序编译运行结果如下:

Constructing an EmployeeConstructing an EmployeeCall mailCheck using  Salary reference --Within mailCheck of Salary classMailing check to Mohd Mohtashim with salary 3600.0Call mailCheck using Employee reference--Within mailCheck of Salary classMailing check to John Adams with salary 2400.

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。

Abstract关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

public abstract class Employee{   private String name;   private String address;   private int number;      public abstract double computePay();      //其余代码}

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

如果Salary类继承了Employee类,那么它必须实现computePay()方法:

/* 文件名 : Salary.java */public class Salary extends Employee{   private double salary; // Annual salary     public double computePay()   {      System.out.println("Computing salary pay for " + getName());      return salary/52;   }   //其余代码}


在面向对象程式设计方法中,封装(英语:Encapsulation)是指,一种将抽象性函式接口的实作细节部份包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

实例

让我们来看一个java封装类的例子:

/* 文件名: EncapTest.java */public class EncapTest{   private String name;   private String idNum;   private int age;   public int getAge(){      return age;   }   public String getName(){      return name;   }   public String getIdNum(){      return idNum;   }   public void setAge( int newAge){      age = newAge;   }   public void setName(String newName){      name = newName;   }   public void setIdNum( String newId){      idNum = newId;   }}

以上实例中public方法是外部类访问该类成员变量的入口。

通常情况下,这些方法被称为getter和setter方法。

因此,任何要访问类中私有成员变量的类都要通过这些getter和setter方法。

通过如下的例子说明EncapTest类的变量怎样被访问:

/* F文件名 : RunEncap.java */public class RunEncap{   public static void main(String args[]){      EncapTest encap = new EncapTest();      encap.setName("James");      encap.setAge(20);      encap.setIdNum("12343ms");      System.out.print("Name : " + encap.getName()+                              " Age : "+ encap.getAge());    }}

以上代码编译运行结果如下:

Name : James Age : 20


接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在Java中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口与类相似点:

  • 一个接口可以有多个方法。
  • 接口文件保存在.java结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在.class结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了static和final变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多重继承。

接口的声明

接口的声明语法格式如下:

[可见度] interface 接口名称 [extends 其他的类名] {        // 声明变量        // 抽象方法}

Interface关键字用来声明一个接口。下面是接口声明的一个简单例子。

/* 文件名 : NameOfInterface.java */import java.lang.*;//引入包public interface NameOfInterface{   //任何类型 final, static 字段   //抽象方法}

接口有以下特性:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键子。
  • 接口中的方法都是公有的。

实例

/* 文件名 : Animal.java */interface Animal {   public void eat();   public void travel();}

接口的实现

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。

类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。

实现一个接口的语法,可以使用这个公式:

... implements 接口名称[, 其他接口, 其他接口..., ...] ...

实例

/* 文件名 : MammalInt.java */public class MammalInt implements Animal{   public void eat(){      System.out.println("Mammal eats");   }   public void travel(){      System.out.println("Mammal travels");   }    public int noOfLegs(){      return 0;   }   public static void main(String args[]){      MammalInt m = new MammalInt();      m.eat();      m.travel();   }} 

以上实例编译运行结果如下:

Mammal eatsMammal travels

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

下面的Sports接口被Hockey和Football接口继承:

// 文件名: Sports.javapublic interface Sports{   public void setHomeTeam(String name);   public void setVisitingTeam(String name);}// 文件名: Football.javapublic interface Football extends Sports{   public void homeTeamScored(int points);   public void visitingTeamScored(int points);   public void endOfQuarter(int quarter);}// 文件名: Hockey.javapublic interface Hockey extends Sports{   public void homeGoalScored();   public void visitingGoalScored();   public void endOfPeriod(int period);   public void overtimePeriod(int ot);}

Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。

相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。


接口的多重继承

在Java中,类的多重继承是不合法,但接口允许多重继承,。

在接口的多重继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

public interface Hockey extends Sports, Event

以上的程序片段是合法定义的子接口,与类不同的是,接口允许多重继承,而 Sports及 Event 可能定义或是继承相同的方法


标记接口

最常用的继承接口是没有包含任何方法的接口。

标识接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。

标识接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。

例如:java.awt.event包中的MouseListener接口继承的java.util.EventListener接口定义如下:

package java.util;public interface EventListener{}

没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:

  • 建立一个公共的父接口:

    正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。

  • 向一个类添加数据类型:

    这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。


为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。

包的作用

  • 1 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 2 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  • 3 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

包语句的语法格式为:

package pkg1[.pkg2[.pkg3…]];

例如,一个Something.java 文件它的内容

package net.java.utilpublic class Something{   ...}

那么它的路径应该是 net/java/util/Something.java 这样保存的。 package(包)的作用是把不同的java程序分类保存,更方便的被其他java程序调用。

一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。

以下是一些Java中的包:

  • java.lang-打包基础的类
  • java.io-包含输入输出功能的函数

开发者可以自己把一组类和接口等打包,并定义自己的package。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。

由于package创建了新的命名空间(namespace),所以不会跟其他package中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。


创建包

创建package的时候,你需要为这个package取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个package的声明放在这个源文件的开头。

包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。

如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。

例子

让我们来看一个例子,这个例子创建了一个叫做animals的包。通常使用小写的字母来命名避免与类、接口名字的冲突。

在animals包中加入一个接口(interface):

/* 文件名: Animal.java */package animals;interface Animal {   public void eat();   public void travel();}

接下来,在同一个包中加入该接口的实现:

package animals;/* 文件名 : MammalInt.java */public class MammalInt implements Animal{   public void eat(){      System.out.println("Mammal eats");   }   public void travel(){      System.out.println("Mammal travels");   }    public int noOfLegs(){      return 0;   }   public static void main(String args[]){      MammalInt m = new MammalInt();      m.eat();      m.travel();   }} 

然后,编译这两个文件,并把他们放在一个叫做animals的子目录中。 用下面的命令来运行:

$ mkdir animals$ cp Animal.class  MammalInt.class animals$ java animals/MammalIntMammal eatsMammal travel

import关键字

为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用"import"语句可完成此功能。

在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:

import package1[.package2…].(classname|*);

如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

例子

下面的payroll包已经包含了Employee类,接下来向payroll包中添加一个Boss类。Boss类引用Employee类的时候可以不用使用payroll前缀,Boss类的实例如下。

package payroll;public class Boss{   public void payEmployee(Employee e)   {      e.mailCheck();   }}

如果Boss类不在payroll包中又会怎样?Boss类必须使用下面几种方法之一来引用其他包中的类

使用类全名描述,例如:

payroll.Employee

用import关键字引入,使用通配符"*"

import payroll.*;

使用import关键字引入Employee类

import payroll.Employee;

注意:

类文件中可以包含任意数量的import声明。import声明必须在包声明之后,类声明之前。


package的目录结构

类放在包中会有两种主要的结果:

  • 包名成为类名的一部分,正如我们前面讨论的一样。
  • 包名必须与相应的字节码所在的目录结构相吻合。

下面是管理你自己java中文件的一种简单方式:

将类、接口等类型的源码放在一个文件中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:

// 文件名 :  Car.javapackage vehicle;public class Car {   // 类实现  }

接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。

....vehicleCar.java

现在,正确的类名和路径将会是如下样子:

  • 类名 -> vehicle.Car

  • 路径名 -> vehicleCar.java (in windows)

通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是apple.com,所有的包名都以com.apple开头。包名中的每一个部分对应一个子目录。

例如:这个公司有一个com.apple.computers的包,这个包包含一个叫做Dell.java的源文件,那么相应的,应该有如下面的一连串子目录:

....comapplecomputersDell.java

编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上.class作为扩展后缀。 例如:

// 文件名: Dell.javapackage com.apple.computers;public class Dell{      }class Ups{      }

现在,我们用-d选项来编译这个文件,如下:

$javac -d . Dell.java

这样会像下面这样放置编译了的文件:

.comapplecomputersDell.class.comapplecomputersUps.class

你可以像下面这样来导入所有 comapplecomputers中定义的类、接口等:

import com.apple.computers.*;

编译之后的.class文件应该和.java源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求.class文件的路径跟相应的.java的路径一样。你可以分开来安排源码和类的目录。

<path-one>sourcescomapplecomputersDell.java<path-two>classescomapplecomputersDell.class

这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java虚拟机(JVM)可以找到你程序中使用的所有类型。

类目录的绝对路径叫做class path。设置在系统变量CLASSPATH中。编译器和java虚拟机通过将package名字加到class path后来构造.class文件的路径。

<path- two>classes是class path,package名字是com.apple.computers,而编译器和JVM会在 <path-two>classescomapplecompters中找.class文件。

一个class path可能会包含好几个路径。多路径应该用分隔符分开。默认情况下,编译器和JVM查找当前目录。JAR文件按包含Java平台相关的类,所以他们的目录默认放在了class path中。


设置CLASSPATH系统变量

用下面的命令显示当前的CLASSPATH变量:

  • Windows平台(DOS 命令行下)-> C:> set CLASSPATH
  • UNIX平台(Bourne shell下)-> % echo $CLASSPATH

删除当前CLASSPATH变量内容:


  • Windows平台(DOS 命令行下)-> C:> set CLASSPATH=
  • UNIX平台(Bourne shell下)-> % unset CLASSPATH; export CLASSPATH

设置CLASSPATH变量:

  • Windows平台(DOS 命令行下)-> set CLASSPATH=C:usersjackjavaclasses
  • UNIX平台(Bourne shell下)-> % CLASSPATH=/home/jack/java/classes; export CLASSPATH


Java 数据结构

Java工具包提供了强大的数据结构。在Java中的数据结构主要包括以下几种接口和类:

  • 枚举(Enumeration)
  • 位集合(BitSet)
  • 向量(Vector)
  • 栈(Stack)
  • 字典(Dictionary)
  • 哈希表(Hashtable)
  • 属性(Properties)

以上这些类是传统遗留的,在Java2中引入了一种新的框架-集合框架(Collection),我们后面再讨论。


枚举(Enumeration)

枚举(Enumeration)接口虽然它本身不属于数据结构,但它在其他数据结构的范畴里应用很广。 枚举(The Enumeration)接口定义了一种从数据结构中取回连续元素的方式。

例如,枚举定义了一个叫nextElement 的方法,该方法用来得到一个包含多元素的数据结构的下一个元素。

关于枚举接口的更多信息,请参见枚举(Enumeration)


位集合(BitSet)

位集合类实现了一组可以单独设置和清除的位或标志。

该类在处理一组布尔值的时候非常有用,你只需要给每个值赋值一"位",然后对位进行适当的设置或清除,就可以对布尔值进行操作了。

关于该类的更多信息,请参见位集合(BitSet)


向量(Vector)

向量(Vector)类和传统数组非常相似,但是Vector的大小能根据需要动态的变化。

和数组一样,Vector对象的元素也能通过索引访问。

使用Vector类最主要的好处就是在创建对象的时候不必给对象指定大小,它的大小会根据需要动态的变化。

关于该类的更多信息,请参见向量(Vector)


栈(Stack)

栈(Stack)实现了一个后进先出(LIFO)的数据结构。

你可以把栈理解为对象的垂直分布的栈,当你添加一个新元素时,就将新元素放在其他元素的顶部。

当你从栈中取元素的时候,就从栈顶取一个元素。换句话说,最后进栈的元素最先被取出。

关于该类的更多信息,请参见栈(Stack)


字典(Dictionary)

字典(Dictionary) 类是一个抽象类,它定义了键映射到值的数据结构。

当你想要通过特定的键而不是整数索引来访问数据的时候,这时候应该使用Dictionary。

由于Dictionary类是抽象类,所以它只提供了键映射到值的数据结构,而没有提供特定的实现。

关于该类的更多信息,请参见字典( Dictionary)


哈希表(Hashtable)

Hashtable类提供了一种在用户定义键结构的基础上来组织数据的手段。

例如,在地址列表的哈希表中,你可以根据邮政编码作为键来存储和排序数据,而不是通过人的名字。

哈希表键的具体含义完全取决于哈希表的使用情景和它包含的数据。

关于该类的更多信息,请参见哈希表(HashTable)


属性(Properties)

Properties 继承于 Hashtable.Properties 类表示了一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。

Properties 类被许多Java类使用。例如,在获取环境变量时它就作为System.getProperties()方法的返回值。

关于该类的更多信息,请参见属性(Properties)

早在 Java 2 中之前,Java 就提供了特设类。比如:Dictionary, Vector, Stack, 和 Properties 这些类用来存储和操作对象组。

虽然这些类都非常有用,但是它们缺少一个核心的,统一的主题。由于这个原因,使用 Vector 类的方式和使用 Properties 类的方式有着很大不同。

集合框架被设计成要满足以下几个目标。

  • 该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。
  • 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
  • 对一个集合的扩展和适应必须是简单的。

为此,整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现,诸如: LinkedList, HashSet, 和 TreeSet 等,除此之外你也可以通过这些接口实现自己的集合。

简化图:

1

说明:对于以上的框架图有如下几点说明

  1. 所有集合类都位于 java.util 包下。Java的集合类主要由两个接口派生而出:Collection 和 Map,Collection 和 Map 是 Java 集合框架的根接口,这两个接口又包含了一些子接口或实现类。
  2. 集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。
  3. 抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。
  4. 实现类:8个实现类(实线表示),对接口的具体实现。
  5. Collection 接口是一组允许重复的对象。
  6. Set 接口继承 Collection,集合元素不重复。
  7. List 接口继承 Collection,允许重复,维护元素插入顺序。
  8. Map接口是键-值对象,与Collection接口没有什么关系。
  9. Set、List 和 Map 可以看做集合的三大类:
    List 集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
    Set 集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
    Map 集合中保存 Key-value 对形式的元素,访问时只能根据每项元素的 key 来访问其 value。

集合框架图如图所示:


Java 集合框架提供了一套性能优良,使用方便的接口和类,java 集合框架位于 java.util 包中, 所以当使用集合框架的时候需要进行导包。


集合接口

集合框架定义了一些接口。本节提供了每个接口的概述:

序号接口描述
1Collection 接口
允许你使用一组对象,是Collection层次结构的根接口。
2List 接口
继承于Collection和一个 List实例存储一个有序集合的元素。
3Set
继承于 Collection,是一个不包含重复元素的集合。
4SortedSet
继承于Set保存有序的集合。
5Map
将唯一的键映射到值。
6Map.Entry
描述在一个Map中的一个元素(键/值对)。是一个Map的内部类。
7SortedMap
继承于Map,使Key保持在升序排列。
8Enumeration
这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。

集合类

Java 提供了一套实现了 Collection 接口的标准集合类。其中一些是具体类,这些类可以直接拿来使用,而另外一些是抽象类,提供了接口的部分实现。

标准集合类汇总于下表:

序号类描述
1AbstractCollection 
实现了大部分的集合接口。
2AbstractList 
继承于 AbstractCollection 并且实现了大部分List接口。
3AbstractSequentialList 
继承于  AbstractList ,提供了对数据元素的链式访问而不是随机访问。
4LinkedList
继承于 AbstractSequentialList,实现了一个链表。
5ArrayList
通过继承 AbstractList,实现动态数组。
6AbstractSet 
继承于 AbstractCollection 并且实现了大部分Set接口。
7HashSet
继承了 AbstractSet,并且使用一个哈希表。
8LinkedHashSet
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。
9TreeSet
继承于AbstractSet,使用元素的自然顺序对元素进行排序.
10AbstractMap 
实现了大部分的 Map 接口。
11HashMap
继承了 HashMap,并且使用一个哈希表。
12TreeMap
继承了 AbstractMap,并且使用一颗树。
13WeakHashMap
继承 AbstractMap类,使用弱密钥的哈希表。
14LinkedHashMap
继承于 HashMap,使用元素的自然顺序对元素进行排序.
15IdentityHashMap
继承 AbstractMap 类,比较文档时使用引用相等。

在前面的教程中已经讨论通过 java.util 包中定义的类,如下所示:

序号类描述
1Vector
Vector 类实现了一个动态数组。和 ArrayList 和相似,但是两者是不同的。
2Stack
栈是 Vector 的一个子类,它实现了一个标准的后进先出的栈。
3Dictionary
Dictionary 类是一个抽象类,用来存储键/值对,作用和 Map 类相似。
4Hashtable
Hashtable 是原始的 java.util 的一部分, 是一个 Dictionary 具体的实现 。
5Properties
Properties 继承于 Hashtable.表示一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。
6BitSet
一个 Bitset 类创建一种特殊类型的数组来保存位值。BitSet 中数组大小会随需要增加。

一个 Bitset 类创建一种特殊类型的数组来保存位值。BitSet 中数组大小会随需要增加。


集合算法

集合框架定义了几种算法,可用于集合和映射。这些算法被定义为集合类的静态方法。

在尝试比较不兼容的类型时,一些方法能够抛出 ​ClassCastException​异常。当试图修改一个不可修改的集合时,抛出​UnsupportedOperationException​异常。

集合定义三个静态的变量:EMPTY_SET EMPTY_LIST,EMPTY_MAP 的。这些变量都不可改变。

序号算法描述
1Collection Algorithms
这里是一个列表中的所有算法实现。

如何使用迭代器

通常情况下,你会希望遍历一个集合中的元素。例如,显示集合中的每个元素。

做到这一点最简单的方法是采用一个迭代器,它是一个对象,实现了 Iterator 接口或 ListIterator 接口。

迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了 Iterator,以允许双向遍历列表和修改元素。

这里通过实例列出 Iterator 和 listIterator 接口提供的所有方法。


概述

泛型在 java 中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。

什么是泛型?为什么要使用泛型?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),

然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,

操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

特性

泛型只在编译阶段有效。看下面的代码:

List<String> stringArrayList = new ArrayList<String>();List<Integer> integerArrayList = new ArrayList<Integer>();Class classStringArrayList = stringArrayList.getClass();Class classIntegerArrayList = integerArrayList.getClass();if(classStringArrayList.equals(classIntegerArrayList)){    Log.d("泛型测试","类型相同");}

输出结果:D/ 泛型测试: 类型相同。

通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息输出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同的基本类型。


泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int,double,char 的等)。

实例

下面的例子演示了如何使用泛型方法打印不同字符串的元素:

public class GenericMethodTest{   // 泛型方法 printArray                            public static < E > void printArray( E[] inputArray )   {      // 输出数组元素                     for ( E element : inputArray ){                    System.out.printf( "%s ", element );         }         System.out.println();    }    public static void main( String args[] )    {        // 创建不同类型数组: Integer, Double 和 Character        Integer[] intArray = { 1, 2, 3, 4, 5 };        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };        System.out.println( "Array integerArray contains:" );        printArray( intArray  ); // 传递一个整型数组        System.out.println( "
Array doubleArray contains:" );        printArray( doubleArray ); // 传递一个双精度型数组        System.out.println( "
Array characterArray contains:" );        printArray( charArray ); // 传递一个字符型型数组    } }

编译以上代码,运行结果如下所示:

Array integerArray contains:1 2 3 4 5 6 Array doubleArray contains:1.1 2.2 3.3 4.4 Array characterArray contains:H E L L O

有界的类型参数:

可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受 Number 或者 Number 子类的实例。这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟 extends 关键字,最后紧跟它的上界。

实例

下面的例子演示了 "extends" 如何使用在一般意义上的意思 "extends"(类)或者"implements"(接口)。该例子中的泛型方法返回三个可比较对象的最大值。

public class MaximumTest{   // 比较三个值并返回最大值   public static <T extends Comparable<T>> T maximum(T x, T y, T z)   {                           T max = x; // 假设x是初始最大值      if ( y.compareTo( max ) > 0 ){         max = y; //y 更大      }      if ( z.compareTo( max ) > 0 ){         max = z; // 现在 z 更大                 }      return max; // 返回最大对象   }   public static void main( String args[] )   {      System.out.printf( "Max of %d, %d and %d is %d

",                   3, 4, 5, maximum( 3, 4, 5 ) );       System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f

",                   6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );       System.out.printf( "Max of %s, %s and %s is %s
","pear",         "apple", "orange", maximum( "pear", "apple", "orange" ) );   }}

编译以上代码,运行结果如下所示:

Maximum of 3, 4 and 5 is 5 Maximum of 6.6, 8.8 and 7.7 is 8.8 Maximum of pear, apple and orange is pear

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

实例

如下实例演示了我们如何定义一个泛型类:

public class Box<T> {   private T t;   public void add(T t) {    this.t = t;  }   public T get() {    return t;  }   public static void main(String[] args) {     Box<Integer> integerBox = new Box<Integer>();     Box<String> stringBox = new Box<String>();        integerBox.add(new Integer(10));     stringBox.add(new String("Hello World"));      System.out.printf("Integer Value :%d

", integerBox.get());     System.out.printf("String Value :%s
", stringBox.get());  }}

编译以上代码,运行结果如下所示:

Integer Value :10 String Value :Hello World


Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。

整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

类ObjectInputStream 和ObjectOutputStream是高层次的数据流,它们包含序列化和反序列化对象的方法。

ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:

public final void writeObject(Object x) throws IOException

上面的方法序列化一个对象,并将它发送到输出流。相似的ObjectInputStream 类包含如下反序列化一个对象的方法:

public final Object readObject() throws IOException,                                  ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。

为了演示序列化在Java中是怎样工作的,我将使用之前教程中提到的Employee类,假设我们定义了如下的Employee类,该类实现了Serializable 接口。

public class Employee implements java.io.Serializable{   public String name;   public String address;   public transient int SSN;   public int number;   public void mailCheck()   {      System.out.println("Mailing a check to " + name                           + " " + address);   }}

请注意,一个类的对象要想序列化成功,必须满足两个条件:

该类必须实现 java.io.Serializable 对象。

该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。

如果你想知道一个Java标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现java.io.Serializable接口。


序列化对象

ObjectOutputStream 类用来序列化一个对象,如下的SerializeDemo例子实例化了一个Employee对象,并将该对象序列化到一个文件中。

该程序执行后,就创建了一个名为employee.ser文件。该程序没有任何输出,但是你可以通过代码研读来理解程序的作用。

注意: 当序列化一个对象到文件时, 按照Java的标准约定是给文件一个.ser扩展名。

import java.io.*;public class SerializeDemo{   public static void main(String [] args)   {      Employee e = new Employee();      e.name = "Reyan Ali";      e.address = "Phokka Kuan, Ambehta Peer";      e.SSN = 11122333;      e.number = 101;      try      {         FileOutputStream fileOut =         new FileOutputStream("/tmp/employee.ser");         ObjectOutputStream out = new ObjectOutputStream(fileOut);         out.writeObject(e);         out.close();         fileOut.close();         System.out.printf("Serialized data is saved in /tmp/employee.ser");      }catch(IOException i)      {          i.printStackTrace();      }   }}

反序列化对象

下面的DeserializeDemo程序实例了反序列化,/tmp/employee.ser存储了Employee对象。

import java.io.*;public class DeserializeDemo{   public static void main(String [] args)   {      Employee e = null;      try      {         FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");         ObjectInputStream in = new ObjectInputStream(fileIn);         e = (Employee) in.readObject();         in.close();         fileIn.close();      }catch(IOException i)      {         i.printStackTrace();         return;      }catch(ClassNotFoundException c)      {         System.out.println("Employee class not found");         c.printStackTrace();         return;      }      System.out.println("Deserialized Employee...");      System.out.println("Name: " + e.name);      System.out.println("Address: " + e.address);      System.out.println("SSN: " + e.SSN);      System.out.println("Number: " + e.number);    }}

以上程序编译运行结果如下所示:

Deserialized Employee...Name: Reyan AliAddress:Phokka Kuan, Ambehta PeerSSN: 0Number:101

这里要注意以下要点:

readObject() 方法中的try/catch代码块尝试捕获 ClassNotFoundException异常。对于JVM可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException异常。

注意,readObject()方法的返回值被转化成Employee引用。

当对象被序列化时,属性SSN的值为111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后Employee对象的SSN属性为0。


网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

java.net包中J2SE的API包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。

java.net包中提供了两种常见的网络协议的支持:

  • TCP: TCP是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称TCP / IP。
  • UDP:UDP是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。

本教程主要讲解以下两个主题。

  • Socket 编程: 这是使用最广泛的网络概念,它已被解释地非常详细
  • URL 处理: 这部分会在另外的篇幅里讲,点击这里更详细地了解在Java语言中的URL处理

Socket 编程

套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。

当连接建立时,服务器会创建一个Socket对象。客户端和服务器现在可以通过对Socket对象的写入和读取来进行通信。

java.net.Socket类代表一个套接字,并且java.net.ServerSocket类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

  • 服务器实例化一个ServerSocket对象,表示通过服务器上的端口通信。
  • 服务器调用 ServerSocket类 的accept()方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
  • 服务器正在等待时,一个客户端实例化一个Socket对象,指定服务器名称和端口号来请求连接。
  • Socket类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个Socket对象能够与服务器进行通信。
  • 在服务器端,accept()方法返回服务器上一个新的socket引用,该socket连接到客户端的socket。

连接建立后,通过使用I/O流在进行通信。每一个socket都有一个输出流和一个输入流。客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

TCP是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现sockets。


ServerSocket 类的方法

服务器应用程序通过使用java.net.ServerSocket类以获取一个端口,并且侦听客户端请求。

ServerSocket类有四个构造方法:

序号 方法描述
1 public ServerSocket(int port) throws IOException
创建绑定到特定端口的服务器套接字。
2 public ServerSocket(int port, int backlog) throws IOException
利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
3 public ServerSocket(int port, int backlog, InetAddress address) throws IOException
使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
4 public ServerSocket() throws IOException
创建非绑定服务器套接字。

创建非绑定服务器套接字。 如果ServerSocket构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。

这里有一些ServerSocket类的常用方法:

序号 方法描述
1 public int getLocalPort()
  返回此套接字在其上侦听的端口。
2 public Socket accept() throws IOException
侦听并接受到此套接字的连接。
3 public void setSoTimeout(int timeout)
 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
4 public void bind(SocketAddress host, int backlog)
将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

Socket 类的方法

java.net.Socket类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个Socket对象通过实例化 ,而 服务器获得一个Socket对象则通过accept()方法的返回值。

Socket类有五个构造方法.

序号 方法描述
1 public Socket(String host, int port) throws UnknownHostException, IOException.
创建一个流套接字并将其连接到指定主机上的指定端口号。
2 public Socket(InetAddress host, int port) throws IOException
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
3 public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程主机上的指定远程端口。
4 public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程地址上的指定远程端口。
5 public Socket()
通过系统默认类型的 SocketImpl 创建未连接套接字

当Socket构造方法返回,并没有简单的实例化了一个Socket对象,它实际上会尝试连接到指定的服务器和端口。

下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个Socket对象,所以无论客户端还是服务端都能够调用这些方法。

序号 方法描述
1 public void connect(SocketAddress host, int timeout) throws IOException
将此套接字连接到服务器,并指定一个超时值。
2 public InetAddress getInetAddress()
 返回套接字连接的地址。
3 public int getPort()
返回此套接字连接到的远程端口。
4 public int getLocalPort()
返回此套接字绑定到的本地端口。
5 public SocketAddress getRemoteSocketAddress()
返回此套接字连接的端点的地址,如果未连接则返回 null。
6 public InputStream getInputStream() throws IOException
返回此套接字的输入流。
7 public OutputStream getOutputStream() throws IOException
返回此套接字的输出流。
8 public void close() throws IOException
关闭此套接字。

InetAddress 类的方法

这个类表示互联网协议(IP)地址。下面列出了Socket编程时比较有用的方法:

序号 方法描述
1 static InetAddress getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。
2 static InetAddress getByAddress(String host, byte[] addr)
根据提供的主机名和 IP 地址创建 InetAddress。
3 static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
4 String getHostAddress() 
返回 IP 地址字符串(以文本表现形式)。
5 String getHostName() 
 获取此 IP 地址的主机名。
6 static InetAddress getLocalHost()
返回本地主机。
7 String toString()
将此 IP 地址转换为 String。

Socket 客户端实例

如下的GreetingClient 是一个客户端程序,该程序通过socket连接到服务器并发送一个请求,然后等待一个响应。

// 文件名 GreetingClient.javaimport java.net.*;import java.io.*; public class GreetingClient{   public static void main(String [] args)   {      String serverName = args[0];      int port = Integer.parseInt(args[1]);      try      {         System.out.println("Connecting to " + serverName                             + " on port " + port);         Socket client = new Socket(serverName, port);         System.out.println("Just connected to "                      + client.getRemoteSocketAddress());         OutputStream outToServer = client.getOutputStream();         DataOutputStream out =                       new DataOutputStream(outToServer);          out.writeUTF("Hello from "                      + client.getLocalSocketAddress());         InputStream inFromServer = client.getInputStream();         DataInputStream in =                        new DataInputStream(inFromServer);         System.out.println("Server says " + in.readUTF());         client.close();      }catch(IOException e)      {         e.printStackTrace();      }   }}

Socket 服务端实例

如下的GreetingServer 程序是一个服务器端应用程序,使用Socket来监听一个指定的端口。

// 文件名 GreetingServer.javaimport java.net.*;import java.io.*;public class GreetingServer extends Thread{   private ServerSocket serverSocket;      public GreetingServer(int port) throws IOException   {      serverSocket = new ServerSocket(port);      serverSocket.setSoTimeout(10000);   }   public void run()   {      while(true)      {         try         {            System.out.println("Waiting for client on port " +            serverSocket.getLocalPort() + "...");            Socket server = serverSocket.accept();            System.out.println("Just connected to "                  + server.getRemoteSocketAddress());            DataInputStream in =                  new DataInputStream(server.getInputStream());            System.out.println(in.readUTF());            DataOutputStream out =                 new DataOutputStream(server.getOutputStream());            out.writeUTF("Thank you for connecting to "              + server.getLocalSocketAddress() + "
Goodbye!");            server.close();         }catch(SocketTimeoutException s)         {            System.out.println("Socket timed out!");            break;         }catch(IOException e)         {            e.printStackTrace();            break;         }      }   }   public static void main(String [] args)   {      int port = Integer.parseInt(args[0]);      try      {         Thread t = new GreetingServer(port);         t.start();      }catch(IOException e)      {         e.printStackTrace();      }   }}

编译以上 java 代码,并执行以下命令来启动服务,使用端口号为 6066:

$ java GreetingServer 6066Waiting for client on port 6066...

像下面一样开启客户端:

$ java GreetingClient localhost 6066Connecting to localhost on port 6066Just connected to localhost/127.0.0.1:6066Server says Thank you for connecting to /127.0.0.1:6066Goodbye!


Java 发送邮件

使用Java应用程序发送E-mail十分简单,但是首先你应该在你的机器上安装JavaMail API 和Java Activation Framework (JAF) 。

你可以在 JavaMail (Version 1.2) 下载最新的版本。

你可以再 在JAF (Version 1.1.1)下载最新的版本。

下载并解压这些文件,最上层文件夹你会发现很多的jar文件。你需要将mail.jar和activation.jar 添加到你的CLASSPATH中。

如果你使用第三方邮件服务器如QQ的SMTP服务器,可查看文章底部用户认证完整的实例。


发送一封简单的 E-mail

下面是一个发送简单E-mail的例子。假设你的localhost已经连接到网络。

// 文件名 SendEmail.java import java.util.*;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*; public class SendEmail{   public static void main(String [] args)   {         // 收件人电子邮箱      String to = "abcd@gmail.com";       // 发件人电子邮箱      String from = "web@gmail.com";       // 指定发送邮件的主机为 localhost      String host = "localhost";       // 获取系统属性      Properties properties = System.getProperties();       // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);       // 获取默认session对象      Session session = Session.getDefaultInstance(properties);       try{         // 创建默认的 MimeMessage 对象         MimeMessage message = new MimeMessage(session);          // Set From: 头部头字段         message.setFrom(new InternetAddress(from));          // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));          // Set Subject: 头部头字段         message.setSubject("This is the Subject Line!");          // 设置消息体         message.setText("This is actual message");          // 发送消息         Transport.send(message);         System.out.println("Sent message successfully....");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

编译并运行这个程序来发送一封简单的E-mail:

$ java SendEmailSent message successfully....

如果你想发送一封e-mail给多个收件人,那么使用下面的方法来指定多个收件人ID:

void addRecipients(Message.RecipientType type,                   Address[] addresses)throws MessagingException

下面是对于参数的描述:

  • type:要被设置为TO, CC 或者BCC. 这里CC 代表抄送、BCC 代表秘密抄送y. 举例:Message.RecipientType.TO

  • addresses: 这是email ID的数组。在指定电子邮件ID时,你将需要使用InternetAddress()方法。


发送一封HTML E-mail

下面是一个发送HTML E-mail的例子。假设你的localhost已经连接到网络。

和上一个例子很相似,除了我们要使用setContent()方法来通过第二个参数为"text/html",来设置内容来指定要发送HTML内容。

// 文件名 SendHTMLEmail.java import java.util.*;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*; public class SendHTMLEmail{   public static void main(String [] args)   {           // 收件人电子邮箱      String to = "abcd@gmail.com";       // 发件人电子邮箱      String from = "web@gmail.com";       // 指定发送邮件的主机为 localhost      String host = "localhost";       // 获取系统属性      Properties properties = System.getProperties();       // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);       // 获取默认的 Session 对象。      Session session = Session.getDefaultInstance(properties);       try{         // 创建默认的 MimeMessage 对象。         MimeMessage message = new MimeMessage(session);          // Set From: 头部头字段         message.setFrom(new InternetAddress(from));          // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));          // Set Subject: 头字段         message.setSubject("This is the Subject Line!");          // 发送 HTML 消息, 可以插入html标签         message.setContent("<h1>This is actual message</h1>",                            "text/html" );          // 发送消息         Transport.send(message);         System.out.println("Sent message successfully....");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

编译并运行此程序来发送HTML e-mail:

$ java SendHTMLEmailSent message successfully....

发送带有附件的 E-mail

下面是一个发送带有附件的 E-mail的例子。假设你的localhost已经连接到网络。

// 文件名 SendFileEmail.java import java.util.*;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*; public class SendFileEmail{   public static void main(String [] args)   {           // 收件人电子邮箱      String to = "abcd@gmail.com";       // 发件人电子邮箱      String from = "web@gmail.com";       // 指定发送邮件的主机为 localhost      String host = "localhost";       // 获取系统属性      Properties properties = System.getProperties();       // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);       // 获取默认的 Session 对象。      Session session = Session.getDefaultInstance(properties);       try{         // 创建默认的 MimeMessage 对象。         MimeMessage message = new MimeMessage(session);          // Set From: 头部头字段         message.setFrom(new InternetAddress(from));          // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));          // Set Subject: 头字段         message.setSubject("This is the Subject Line!");          // 创建消息部分         BodyPart messageBodyPart = new MimeBodyPart();          // 消息         messageBodyPart.setText("This is message body");                 // 创建多重消息         Multipart multipart = new MimeMultipart();          // 设置文本消息部分         multipart.addBodyPart(messageBodyPart);          // 附件部分         messageBodyPart = new MimeBodyPart();         String filename = "file.txt";         DataSource source = new FileDataSource(filename);         messageBodyPart.setDataHandler(new DataHandler(source));         messageBodyPart.setFileName(filename);         multipart.addBodyPart(messageBodyPart);          // 发送完整消息         message.setContent(multipart );          //   发送消息         Transport.send(message);         System.out.println("Sent message successfully....");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

编译并运行你的程序来发送一封带有附件的邮件。

$ java SendFileEmailSent message successfully....

用户认证部分

如果需要提供用户名和密码给e-mail服务器来达到用户认证的目的,你可以通过如下设置来完成:

 props.put("mail.smtp.auth", "true"); props.setProperty("mail.user", "myuser"); props.setProperty("mail.password", "mypwd");

e-mail其他的发送机制和上述保持一致。

需要用户名密码验证邮件发送实例:

本实例以QQ邮件服务器为例,你需要在登录QQ邮箱后台在"设置"=》账号中开启POP3/SMTP服务 ,如下图所示:

qqmailset

Java 代码如下:

// 需要用户名密码邮件发送实例//文件名 SendEmail2.java//本实例以QQ邮箱为例,你需要在qq后台设置import java.util.Properties;import javax.mail.Authenticator;import javax.mail.Message;import javax.mail.MessagingException;import javax.mail.PasswordAuthentication;import javax.mail.Session;import javax.mail.Transport;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeMessage;public class SendEmail2{   public static void main(String [] args)   {      // 收件人电子邮箱      String to = "xxx@qq.com";      // 发件人电子邮箱      String from = "xxx@qq.com";      // 指定发送邮件的主机为 localhost      String host = "smtp.qq.com";  //QQ 邮件服务器      // 获取系统属性      Properties properties = System.getProperties();      // 设置邮件服务器      properties.setProperty("mail.smtp.host", host);      properties.put("mail.smtp.auth", "true");      // 获取默认session对象      Session session = Session.getDefaultInstance(properties,new Authenticator(){	    public PasswordAuthentication getPasswordAuthentication(){	     return new PasswordAuthentication("xxx@qq.com", "qq邮箱密码"); //发件人邮件用户名、密码	    }	   });      try{         // 创建默认的 MimeMessage 对象         MimeMessage message = new MimeMessage(session);         // Set From: 头部头字段         message.setFrom(new InternetAddress(from));         // Set To: 头部头字段         message.addRecipient(Message.RecipientType.TO,                                  new InternetAddress(to));         // Set Subject: 头部头字段         message.setSubject("This is the Subject Line!");         // 设置消息体         message.setText("This is actual message");         // 发送消息         Transport.send(message);         System.out.println("Sent message successfully....from 51coolma.cn");      }catch (MessagingException mex) {         mex.printStackTrace();      }   }}

Java 给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。

多线程是多任务的一种特别的形式。多线程比多任务需要更小的开销。

这里定义和线程相关的另一个术语:进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守候线程都结束运行后才能结束。

多线程能满足程序员编写非常有效率的程序来达到充分利用 CPU 的目的,因为 CPU 的空闲时间能够保持在最低限度。


一个线程的生命周期

线程经过其生命周期的各个阶段。下图显示了一个线程完整的生命周期。

线程 

  • 新建(new Thread)

    当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
    例如:Thread  t1=new Thread();
  • 就绪(runnable)
    线程已经被启动,正在等待被分配给 CPU 时间片,也就是说此时线程正在就绪队列中排队等候得到 CPU 资源。
    例如:t1.start();

  • 运行(running)
    线程获得 CPU 资源正在执行任务( run() 方法),此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入,线程将一直运行到结束。

  • 堵塞(blocked)由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

    正在睡眠:用 sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

    正在等待:调用 wait() 方法。(调用 motify() 方法回到就绪状态)

    被另一个线程所阻塞:调用 suspend() 方法。(调用 resume() 方法恢复)

  • 死亡(dead)当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

    自然终止:正常运行 run() 方法后终止

    异常终止:调用 stop() 方法让一个线程终止运行


线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 优先级在 MIN_PRIORITY(1)和 MAX_PRIORITY(10)之间的范围内。默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器时间。然而,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。


创建一个线程

Java 提供了三种创建线程方法:

  • 通过实现 Runnable 接口;

  • 通过继承 Thread 类本身;

  • 通过 Callable 和 Future 创建线程。


通过实现 Runnable 接口来创建线程

创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。

为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:

public void run()

你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。

在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。

Thread定义了几个构造方法,下面的这个是我们经常使用的:

Thread(Runnable threadOb,String threadName);

这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。

新线程创建之后,你调用它的start()方法它才会运行。

void start();

实例

下面是一个创建线程并开始让它执行的实例:

// 创建一个新的线程class NewThread implements Runnable {   Thread t;   NewThread() {      // 创建第二个新线程      t = new Thread(this, "Demo Thread");      System.out.println("Child thread: " + t);      t.start(); // 开始线程   }     // 第二个线程入口   public void run() {      try {         for(int i = 5; i > 0; i--) {            System.out.println("Child Thread: " + i);            // 暂停线程            Thread.sleep(50);         }     } catch (InterruptedException e) {         System.out.println("Child interrupted.");     }     System.out.println("Exiting child thread.");   }} public class ThreadDemo {   public static void main(String args[]) {      new NewThread(); // 创建一个新线程      try {         for(int i = 5; i > 0; i--) {           System.out.println("Main Thread: " + i);           Thread.sleep(100);         }      } catch (InterruptedException e) {         System.out.println("Main thread interrupted.");      }      System.out.println("Main thread exiting.");   }}

编译以上程序运行结果如下:

Child thread: Thread[Demo Thread,5,main]Main Thread: 5Child Thread: 5Child Thread: 4Main Thread: 4Child Thread: 3Child Thread: 2Main Thread: 3Child Thread: 1Exiting child thread.Main Thread: 2Main Thread: 1Main thread exiting.

通过继承 Thread 来创建线程

创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。

继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。

该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

实例

// 通过继承 Thread 创建线程class NewThread extends Thread {   NewThread() {      // 创建第二个新线程      super("Demo Thread");      System.out.println("Child thread: " + this);      start(); // 开始线程   }    // 第二个线程入口   public void run() {      try {         for(int i = 5; i > 0; i--) {            System.out.println("Child Thread: " + i);                            // 让线程休眠一会            Thread.sleep(50);         }      } catch (InterruptedException e) {         System.out.println("Child interrupted.");      }      System.out.println("Exiting child thread.");   }} public class ExtendThread {   public static void main(String args[]) {      new NewThread(); // 创建一个新线程      try {         for(int i = 5; i > 0; i--) {            System.out.println("Main Thread: " + i);            Thread.sleep(100);         }      } catch (InterruptedException e) {         System.out.println("Main thread interrupted.");      }      System.out.println("Main thread exiting.");   }}

编译以上程序运行结果如下:

Child thread: Thread[Demo Thread,5,main]Main Thread: 5Child Thread: 5Child Thread: 4Main Thread: 4Child Thread: 3Child Thread: 2Main Thread: 3Child Thread: 1Exiting child thread.Main Thread: 2Main Thread: 1Main thread exiting.

Thread 方法

下表列出了Thread类的一些重要方法:

序号方法描述
1public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3public final void setName(String name)
改变线程名称,使之与参数 name 相同。
4public final void setPriority(int priority)
 更改线程的优先级。
5public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
6public final void join(long millisec)
等待该线程终止的时间最长为 millis 毫秒。
7public void interrupt()
中断线程。
8public final boolean isAlive()
测试线程是否处于活动状态。

测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。

序号方法描述
1public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
2public static void sleep(long millisec)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3public static boolean holdsLock(Object x)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4public static Thread currentThread()
返回对当前正在执行的线程对象的引用。
5public static void dumpStack()
将当前线程的堆栈跟踪打印至标准错误流。

实例

如下的ThreadClassDemo 程序演示了Thread类的一些方法:

// 文件名 : DisplayMessage.java// 通过实现 Runnable 接口创建线程public class DisplayMessage implements Runnable{   private String message;   public DisplayMessage(String message)   {      this.message = message;   }   public void run()   {      while(true)      {         System.out.println(message);      }   }}

GuessANumber.java 文件代码:

// 文件名 : GuessANumber.java// 通过继承 Thread 类创建线程public class GuessANumber extends Thread{   private int number;   public GuessANumber(int number)   {      this.number = number;   }   public void run()   {      int counter = 0;      int guess = 0;      do      {          guess = (int) (Math.random() * 100 + 1);          System.out.println(this.getName()                       + " guesses " + guess);          counter++;      }while(guess != number);      System.out.println("** Correct! " + this.getName()                       + " in " + counter + " guesses.**");   }}

ThreadClassDemo.java 文件代码:

// 文件名 : ThreadClassDemo.javapublic class ThreadClassDemo{   public static void main(String [] args)   {      Runnable hello = new DisplayMessage("Hello");      Thread thread1 = new Thread(hello);      thread1.setDaemon(true);      thread1.setName("hello");      System.out.println("Starting hello thread...");      thread1.start();           Runnable bye = new DisplayMessage("Goodbye");      Thread thread2 = new Thread(bye);      thread2.setPriority(Thread.MIN_PRIORITY);      thread2.setDaemon(true);      System.out.println("Starting goodbye thread...");      thread2.start();       System.out.println("Starting thread3...");      Thread thread3 = new GuessANumber(27);      thread3.start();      try      {         thread3.join();      }catch(InterruptedException e)      {         System.out.println("Thread interrupted.");      }      System.out.println("Starting thread4...");      Thread thread4 = new GuessANumber(75);                thread4.start();      System.out.println("main() is ending...");   }}

运行结果如下,每一次运行的结果都不一样。

Starting hello thread...Starting goodbye thread...HelloHelloHelloHelloHelloHelloHelloHelloHelloStarting thread3...HelloHelloStarting thread4...HelloHellomain() is ending...

通过 Callable 和 Future 创建线程

  • 1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  • 2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  • 3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  • 4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

实例

public class CallableThreadTest implements Callable<Integer> {    public static void main(String[] args)      {          CallableThreadTest ctt = new CallableThreadTest();          FutureTask<Integer> ft = new FutureTask<>(ctt);          for(int i = 0;i < 100;i++)          {              System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);              if(i==20)              {                  new Thread(ft,"有返回值的线程").start();              }          }          try          {              System.out.println("子线程的返回值:"+ft.get());          } catch (InterruptedException e)          {              e.printStackTrace();          } catch (ExecutionException e)          {              e.printStackTrace();          }        }    @Override      public Integer call() throws Exception      {          int i = 0;          for(;i<100;i++)          {              System.out.println(Thread.currentThread().getName()+" "+i);          }          return i;      }  }

创建线程的三种方式的对比

  • 1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  • 2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步

  • 线程间通信

  • 线程死锁

  • 线程控制:挂起、停止和恢复


多线程的使用

有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。

通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。

请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!


applet是一种Java程序。它一般运行在支持Java的Web浏览器内。因为它有完整的Java API支持,所以applet是一个全功能的Java应用程序。

如下所示是独立的Java应用程序和applet程序之间重要的不同:

  • Java中applet类继承了 java.applet.Applet类
  • Applet类没有定义main(),所以一个 Applet程序不会调用main()方法,
  • Applets被设计为嵌入在一个HTML页面。
  • 当用户浏览包含Applet的HTML页面,Applet的代码就被下载到用户的机器上。
  • 要查看一个applet需要JVM。 JVM可以是Web浏览器的一个插件,或一个独立的运行时环境。
  • 用户机器上的JVM创建一个applet类的实例,并调用Applet生命周期过程中的各种方法。
  • Applets有Web浏览器强制执行的严格的安全规则,applet的安全机制被称为沙箱安全。
  • applet需要的其他类可以用Java归档(JAR)文件的形式下载下来。

Applet的生命周期

Applet类中的四个方法给你提供了一个框架,你可以再该框架上开发小程序:

  • init: 该方法的目的是为你的applet提供所需的任何初始化。在Applet标记内的param标签被处理后调用该方法。
  • start: 浏览器调用init方法后,该方法被自动调用。每当用户从其他页面返回到包含Applet的页面时,则调用该方法。
  • stop:当用户从包含applet的页面移除的时候,该方法自动被调用。因此,可以在相同的applet中反复调用该方法。
  • destroy: 此方法仅当浏览器正常关闭时调用。因为applets只有在HTML网页上有效,所以你不应该在用户离开包含Applet的页面后遗漏任何资源.
  • paint: 该方法在start()方法之后立即被调用,或者在applet需要重绘在浏览器的时候调用。paint()方法实际上继承于java.awt。

"Hello, World" Applet:

下面是一个简单的Applet程序HelloWorldApplet.java:

import java.applet.*;import java.awt.*; public class HelloWorldApplet extends Applet{   public void paint (Graphics g)   {      g.drawString ("Hello World", 25, 50);   }}

这些import语句将以下类导入到我们的applet类中:

java.applet.Applet.java.awt.Graphics.

没有这些import语句,Java编译器就识别不了Applet和Graphics类。


Applet 类

每一个applet都是java.applet.Applet 类的子类,基础的Applet类提供了供衍生类调用的方法,以此来得到浏览器上下文的信息和服务。

这些方法做了如下事情:

  • 得到applet的参数
  • 得到包含applet的HTML文件的网络位置
  • 得到applet类目录的网络位置
  • 打印浏览器的状态信息
  • 获取一张图片
  • 获取一个音频片段
  • 播放一个音频片段
  • 调整此 applet 的大小

除此之外,Applet类还提供了一个接口,该接口供Viewer或浏览器来获取applet的信息,并且来控制applet的执行。

Viewer可能是:

  • 请求applet作者、版本和版权的信息
  • 请求applet识别的参数的描述
  • 初始化applet
  • 销毁applet
  • 开始执行applet
  • 结束执行applet

Applet类提供了对这些方法的默认实现,这些方法可以在需要的时候重写。

"Hello,World"applet都是按标准编写的。唯一被重写的方法是paint方法。


Applet的调用

applet是一种Java程序。它一般运行在支持Java的Web浏览器内。因为它有完整的Java API支持,所以applet是一个全功能的Java应用程序。

<applet>标签是在HTML文件中嵌入applet的基础。以下是一个调用"Hello World"applet的例子;

<html><title>The Hello, World Applet</title><hr><applet code="HelloWorldApplet.class" width="320" height="120">If your browser was Java-enabled, a "Hello, World"message would appear here.</applet><hr></html>

注意: 你可以参照HTML Applet标签来更多的了解从HTML中调用applet的方法。

<applet>标签的属性指定了要运行的Applet类。Width和height用来指定applet运行面板的初始大小。applet必须使用</applet>标签来关闭。

如果applet接受参数,那么参数的值需要在标签里添加,该标签位于<applet>和</applet>之间。浏览器忽略了applet标签之间的文本和其他标签。

不支持Java的浏览器不能执行<applet>和</applet>。因此,在标签之间显示并且和applet没有关系的任何东西,在不支持的Java的浏览器里是可见的。

Viewer或者浏览器在文档的位置寻找编译过的Java代码,要指定文档的路径,得使用<applet>标签的codebase属性指定。

如下所示:

<applet codebase="http://amrood.com/applets" code="HelloWorldApplet.class" width="320" height="120">

如果applet所在一个包中而不是默认包,那么所在的包必须在code属性里指定,例如:

<applet code="mypackage.subpackage.TestApplet.class" width="320" height="120">

获得applet参数

下面的例子演示了如何使用一个applet响应来设置文件中指定的参数。该Applet显示了一个黑色棋盘图案和第二种颜色。

第二种颜色和每一列的大小通过文档中的applet的参数指定。

CheckerApplet 在init()方法里得到它的参数。也可以在paint()方法里得到它的参数。然而,在applet开始得到值并保存了设置,而不是每一次刷新的时候都得到值,这样是很方便,并且高效的。

applet viewer或者浏览器在applet每次运行的时候调用init()方法。在加载applet之后,Viewer立即调用init()方法(Applet.init()什么也没做),重写该方法的默认实现,添加一些自定义的初始化代码。

Applet.getParameter()方法通过给出参数名称得到参数值。如果得到的值是数字或者其他非字符数据,那么必须解析为字符串类型。

下例是CheckerApplet.java的梗概:

import java.applet.*;import java.awt.*;public class CheckerApplet extends Applet{   int squareSize = 50;// 初始化默认大小   public void init () {}   private void parseSquareSize (String param) {}   private Color parseColor (String param) {}   public void paint (Graphics g) {}}

下面是CheckerApplet类的init()方法和私有的parseSquareSize()方法:

public void init (){   String squareSizeParam = getParameter ("squareSize");   parseSquareSize (squareSizeParam);   String colorParam = getParameter ("color");   Color fg = parseColor (colorParam);   setBackground (Color.black);   setForeground (fg);}private void parseSquareSize (String param){   if (param == null) return;   try {      squareSize = Integer.parseInt (param);   }   catch (Exception e) {     // 保留默认值   }}

该applet调用parseSquareSize(),来解析squareSize参数。parseSquareSize()调用了库方法Integer. parseInt(),该方法将一个字符串解析为一个整数,当参数无效的时候,Integer.parseInt()抛出异常。

因此,parseSquareSize()方法也是捕获异常的,并不允许applet接受无效的输入。

Applet调用parseColor()方法将颜色参数解析为一个Color值。parseColor()方法做了一系列字符串的比较,来匹配参数的值和预定义颜色的名字。你需要实现这些方法来使applet工作。


指定applet参数

如下的例子是一个HTML文件,其中嵌入了CheckerApplet类。HTML文件通过使用标签的方法给applet指定了两个参数。

<html><title>Checkerboard Applet</title><hr><applet code="CheckerApplet.class" width="480" height="320"><param name="color" value="blue"><param name="squaresize" value="30"></applet><hr></html>

注意: 参数名字大小写不敏感。


应用程序转换成Applet

将图形化的Java应用程序(是指,使用AWT的应用程序和使用java程序启动器启动的程序)转换成嵌入在web页面里的applet是很简单的。

下面是将应用程序转换成applet的几个步骤:

  • 编写一个HTML页面,该页面带有能加载applet代码的标签。
  • 编写一个JApplet类的子类,将该类设置为public。否则,applet不能被加载。
  • 消除应用程序的main()方法。不要为应用程序构造框架窗口,因为你的应用程序要显示在浏览器中。
  • 将应用程序中框架窗口的构造方法里的初始化代码移到applet的init()方法中,你不必显示的构造applet对象,浏览器将通过调用init()方法来实例化一个对象。
  • 移除对setSize()方法的调用,对于applet来讲,大小已经通过HTML文件里的width和height参数设定好了。
  • 移除对 setDefaultCloseOperation()方法的调用。Applet不能被关闭,它随着浏览器的退出而终止。
  • 如果应用程序调用了setTitle()方法,消除对该方法的调用。applet不能有标题栏。(当然你可以给通过html的title标签给网页自身命名)
  • 不要调用setVisible(true),applet是自动显示的。

事件处理

Applet类从Container类继承了许多事件处理方法。Container类定义了几个方法,例如:processKeyEvent()和processMouseEvent(),用来处理特别类型的事件,还有一个捕获所有事件的方法叫做processEvent。

为了响应一个事件,applet必须重写合适的事件处理方法。

import java.awt.event.MouseListener;import java.awt.event.MouseEvent;import java.applet.Applet;import java.awt.Graphics; public class ExampleEventHandling extends Applet                             implements MouseListener {     StringBuffer strBuffer;     public void init() {         addMouseListener(this);         strBuffer = new StringBuffer();        addItem("initializing the apple ");    }     public void start() {        addItem("starting the applet ");    }     public void stop() {        addItem("stopping the applet ");    }     public void destroy() {        addItem("unloading the applet");    }     void addItem(String word) {        System.out.println(word);        strBuffer.append(word);        repaint();    }     public void paint(Graphics g) {         //Draw a Rectangle around the applet's display area.        g.drawRect(0, 0,                      getWidth() - 1,                      getHeight() - 1);          //display the string inside the rectangle.        g.drawString(strBuffer.toString(), 10, 20);    }       public void mouseEntered(MouseEvent event) {    }    public void mouseExited(MouseEvent event) {    }    public void mousePressed(MouseEvent event) {    }    public void mouseReleased(MouseEvent event) {    }     public void mouseClicked(MouseEvent event) {         addItem("mouse clicked! ");    }}

如下调用该applet:

<html><title>Event Handling</title><hr><applet code="ExampleEventHandling.class" width="300" height="300"></applet><hr></html>

最开始运行,applet显示 "initializing the applet. Starting the applet.",然后你一点击矩形框,就会显示"mouse clicked" 。


显示图片

applet能显示GIF,JPEG,BMP等其他格式的图片。为了在applet中显示图片,你需要使用java.awt.Graphics类的drawImage()方法。

如下实例演示了显示图片的所有步骤:

import java.applet.*;import java.awt.*;import java.net.*;public class ImageDemo extends Applet{  private Image image;  private AppletContext context;  public void init()  {      context = this.getAppletContext();      String imageURL = this.getParameter("image");      if(imageURL == null)      {         imageURL = "java.jpg";      }      try      {         URL url = new URL(this.getDocumentBase(), imageURL);         image = context.getImage(url);      }catch(MalformedURLException e)      {         e.printStackTrace();         // Display in browser status bar         context.showStatus("Could not load image!");      }   }   public void paint(Graphics g)   {      context.showStatus("Displaying image");      g.drawImage(image, 0, 0, 200, 84, null);      g.drawString("www.javalicense.com", 35, 100);   } }

如下调用该applet:

<html><title>The ImageDemo applet</title><hr><applet code="ImageDemo.class" width="300" height="200"><param name="image" value="java.jpg"></applet><hr></html>

播放音频

Applet能通过使用java.applet包中的AudioClip接口播放音频。AudioClip接口定义了三个方法:

  • public void play(): 从一开始播放音频片段一次。
  • public void loop(): 循环播放音频片段
  • public void stop(): 停止播放音频片段

为了得到AudioClip对象,你必须调用Applet类的getAudioClip()方法。无论URL指向的是否是一个真实的音频文件,该方法都会立即返回结果。

直到要播放音频文件时,该文件才会下载下来。

如下实例演示了播放音频的所有步骤:

import java.applet.*;import java.awt.*;import java.net.*;public class AudioDemo extends Applet{   private AudioClip clip;   private AppletContext context;   public void init()   {      context = this.getAppletContext();      String audioURL = this.getParameter("audio");      if(audioURL == null)      {         audioURL = "default.au";      }      try      {         URL url = new URL(this.getDocumentBase(), audioURL);         clip = context.getAudioClip(url);      }catch(MalformedURLException e)      {         e.printStackTrace();         context.showStatus("Could not load audio file!");      }   }   public void start()   {      if(clip != null)      {         clip.loop();      }   }   public void stop()   {      if(clip != null)      {         clip.stop();      }   }}

如下调用applet:

<html><title>The ImageDemo applet</title><hr><applet code="ImageDemo.class" width="0" height="0"><param name="audio" value="test.wav"></applet><hr>

你可以使用你电脑上的test.wav来测试上面的实例。


Java支持三种注释方式。前两种分别是// 和/* */,第三种被称作说明注释,它以/** 开始,以 */结束。

说明注释允许你在程序中嵌入关于程序的信息。你可以使用javadoc工具软件来生成信息,并输出到HTML文件中。

说明注释,使你更加方便的记录你的程序的信息。


javadoc 标签

javadoc工具软件识别以下标签:

标签 描述 示例
@author 标识一个类的作者 @author description
@deprecated 指名一个过期的类或成员 @deprecated description
{@docRoot} 指明当前文档根目录的路径 Directory Path
@exception 标志一个类抛出的异常 @exception exception-name explanation
{@inheritDoc} 从直接父类继承的注释 Inherits a comment from the immediate surperclass.
{@link} 插入一个到另一个主题的链接 {@link name text}
{@linkplain} 插入一个到另一个主题的链接,但是该链接显示纯文本字体 Inserts an in-line link to another topic.
@param 说明一个方法的参数 @param parameter-name explanation
@return 说明返回值类型 @return explanation
@see 指定一个到另一个主题的链接 @see anchor
@serial 说明一个序列化属性 @serial description
@serialData 说明通过writeObject( ) 和 writeExternal( )方法写的数据 @serialData description
@serialField 说明一个ObjectStreamField组件 @serialField name type description
@since 标记当引入一个特定的变化时 @since release
@throws 和 @exception标签一样. The @throws tag has the same meaning as the @exception tag.
{@value} 显示常量的值,该常量必须是static属性。 Displays the value of a constant, which must be a static field.
@version 指定类的版本 @version info

文档注释

在开始的/**之后,第一行或几行是关于类、变量和方法的主要描述.

之后,你可以包含一个或多个何种各样的@标签。每一个@标签必须在一个新行的开始或者在一行的开始紧跟星号(*).

多个相同类型的标签应该放成一组。例如,如果你有三个@see标签,可以将它们一个接一个的放在一起。

下面是一个类的说明注释的示例:

/*** This class draws a bar chart.* @author Zara Ali* @version 1.2*/

javadoc输出什么

javadoc工具将你Java程序的源代码作为输入,输出一些包含你程序注释的HTML文件。

每一个类的信息将在独自的HTML文件里。javadoc也可以输出继承的树形结构和索引。

由于javadoc的实现不同,工作也可能不同,你需要检查你的Java开发系统的版本等细节,选择合适的Javadoc版本。

实例

下面是一个使用说明注释的简单实例。注意每一个注释都在它描述的项目的前面。

在经过javadoc处理之后,SquareNum类的注释将在SquareNum.html中找到。

import java.io.*; /*** This class demonstrates documentation comments.* @author Ayan Amhed* @version 1.2*/public class SquareNum {   /**   * This method returns the square of num.   * This is a multiline description. You can use   * as many lines as you like.   * @param num The value to be squared.   * @return num squared.   */   public double square(double num) {      return num * num;   }   /**   * This method inputs a number from the user.   * @return The value input as a double.   * @exception IOException On input error.   * @see IOException   */   public double getNumber() throws IOException {      InputStreamReader isr = new InputStreamReader(System.in);      BufferedReader inData = new BufferedReader(isr);      String str;      str = inData.readLine();      return (new Double(str)).doubleValue();   }   /**   * This method demonstrates square().   * @param args Unused.   * @return Nothing.   * @exception IOException On input error.   * @see IOException   */   public static void main(String args[]) throws IOException   {      SquareNum ob = new SquareNum();      double val;      System.out.println("Enter value to be squared: ");      val = ob.getNumber();      val = ob.square(val);      System.out.println("Squared value is " + val);   }}

如下,使用javadoc工具处理SquareNum.java文件:

$ javadoc SquareNum.javaLoading source file SquareNum.java...Constructing Javadoc information...Standard Doclet version 1.5.0_13Building tree for all the packages and classes...Generating SquareNum.html...SquareNum.java:39: warning - @return tag cannot be used                      in method with void return type.Generating package-frame.html...Generating package-summary.html...Generating package-tree.html...Generating constant-values.html...Building index for all the packages and classes...Generating overview-tree.html...Generating index-all.html...Generating deprecated-list.html...Building index for all classes...Generating allclasses-frame.html...Generating allclasses-noframe.html...Generating index.html...Generating help-doc.html...Generating stylesheet.css...1 warning$


本章节我们将为大家介绍 Java 常用的实例,通过实例学习我们可以更快的掌握 Java 的应用。


Java 环境设置实例

  1. Java 实例 – 如何编译一个Java 文件?
  2. Java 实例 – Java 如何运行一个编译过的类文件?
  3. Java 实例 - 如何执行指定class文件目录(classpath)?
  4. Java 实例 – 如何查看当前 Java 运行的版本?

Java 字符串

  1. Java 实例 – 字符串比较
  2. Java 实例 - 查找字符串最后一次出现的位置
  3. Java 实例 - 删除字符串中的一个字符
  4. Java 实例 - 字符串替换
  5. Java 实例 - 字符串反转
  6. Java 实例 - 字符串查找
  7. Java 实例 - 字符串分割
  8. Java 实例 - 字符串小写转大写
  9. Java 实例 - 测试两个字符串区域是否相等
  10. Java 实例 - 字符串性能比较测试
  11. Java 实例 - 字符串优化
  12. Java 实例 - 字符串格式化
  13. Java 实例 - 连接字符串

Java 数组

  1. Java 实例 – 数组排序及元素查找
  2. Java 实例 – 数组添加元素
  3. Java 实例 – 获取数组长度
  4. Java 实例 – 数组反转
  5. Java 实例 – 数组输出
  6. Java 实例 – 数组获取最大和最小值
  7. Java 实例 – 数组合并
  8. Java 实例 – 数组填充
  9. Java 实例 – 数组扩容
  10. Java 实例 – 数组排序及查找
  11. Java 实例 – 删除数组元素
  12. Java 实例 – 数组差集
  13. Java 实例 – 数组交集
  14. Java 实例 – 在数组中查找指定元素
  15. Java 实例 – 判断数组是否相等
  16. Java 实例 - 数组并集

Java 时间处理

  1. Java 实例 - 格式化时间(SimpleDateFormat)
  2. Java 实例 - 获取当前时间
  3. Java 实例 - 获取年份、月份等
  4. Java 实例 - 时间戳转换成时间

Java 方法

  1. Java 实例 – 方法重载
  2. Java 实例 – 输出数组元素
  3. Java 实例 – 汉诺塔算法
  4. Java 实例 – 斐波那契数列
  5. Java 实例 – 阶乘
  6. Java 实例 – 方法覆盖
  7. Java 实例 – instanceOf 关键字用法
  8. Java 实例 – break 关键字用法
  9. Java 实例 – continue 关键字用法
  10. Java 实例 – 标签(Label)
  11. Java 实例 – enum 和 switch 语句使用
  12. Java 实例 – Enum(枚举)构造函数及方法的使用
  13. Java 实例 – for 和 foreach循环使用
  14. Java 实例 – Varargs 可变参数使用
  15. Java 实例 – 重载(overloading)方法中使用 Varargs

Java 文件操作

  1. Java 实例 - 文件写入
  2. Java 实例 - 读取文件内容
  3. Java 实例 - 删除文件
  4. Java 实例 - 将文件内容复制到另一个文件
  5. Java 实例 - 向文件中追加数据
  6. Java 实例 - 创建临时文件
  7. Java 实例 - 修改文件最后的修改日期
  8. Java 实例 - 获取文件大小
  9. Java 实例 - 文件重命名
  10. Java 实例 - 设置文件只读
  11. Java 实例 - 检测文件是否存在
  12. Java 实例 - 在指定目录中创建文件
  13. Java 实例 - 获取文件修改时间
  14. Java 实例 - 创建文件
  15. Java 实例 - 文件路径比较

Java 目录操作

  1. Java 实例 - 递归创建目录
  2. Java 实例 - 删除目录
  3. Java 实例 - 判断目录是否为空
  4. Java 实例 - 判断文件是否隐藏
  5. Java 实例 - 获取目录大小
  6. Java 实例 - 在指定目录中查找文件
  7. Java 实例 - 获取文件的上级目录
  8. Java 实例 - 获取目录最后修改时间
  9. Java 实例 - 打印目录结构
  10. Java 实例 - 遍历指定目录下的所有目录
  11. Java 实例 - 遍历指定目录下的所有文件
  12. Java 实例 - 在指定目录中查找文件
  13. Java 实例 - 遍历系统根目录
  14. Java 实例 - 查看当前工作目录
  15. Java 实例 - 遍历目录

Java 异常处理

  1. Java 实例 - 异常处理方法
  2. Java 实例 - 多个异常处理(多个catch)
  3. Java 实例 - Finally的用法
  4. Java 实例 - 使用 catch 处理异常
  5. Java 实例 - 多线程异常处理
  6. Java 实例 - 获取异常的堆栈信息
  7. Java 实例 - 重载方法异常处理
  8. Java 实例 - 链试异常
  9. Java 实例 - 自定义异常

Java 数据结构

  1. Java 实例 – 数字求和运算
  2. Java 实例 – 利用堆栈将中缀表达式转换成后缀
  3. Java 实例 – 在链表(LinkedList)的开头和结
  4. Java 实例 – 获取链表(LinkedList)的第一个
  5. Java 实例 – 删除链表中的元素
  6. Java 实例 – 获取链表的元素
  7. Java 实例 – 获取向量元素的索引值
  8. Java 实例 – 栈的实现
  9. Java 实例 – 链表元素查找
  10. Java 实例 – 压栈出栈的方法实现字符串反转
  11. Java 实例 – 队列(Queue)用法
  12. Java 实例 – 获取向量的最大元素
  13. Java 实例 – 链表修改
  14. Java 实例 – 旋转向量

Java 集合

  1. Java 实例 – 数组转集合
  2. Java 实例 – 集合比较
  3. Java 实例 – HashMap遍历
  4. Java 实例 – 集合长度
  5. Java 实例 – 集合打乱顺序
  6. Java 实例 – 集合遍历
  7. Java 实例 – 集合反转
  8. Java 实例 – 删除集合中指定元素
  9. Java 实例 – 只读集合
  10. Java 实例 – 集合输出
  11. Java 实例 – 集合转数组
  12. Java 实例 – List 循环移动元素
  13. Java 实例 – 查找 List 中的最大最小值
  14. Java 实例 – 遍历 HashTable 的键值
  15. Java 实例 – 使用 Enumeration 遍历 HashTable
  16. Java 实例 – 集合中添加不同类型元素
  17. Java 实例 – List 元素替换
  18. Java 实例 – List 截取

Java 网络实例

  1. Java 实例 – 获取指定主机的IP地址
  2. Java 实例 – 查看端口是否已使用
  3. Java 实例 – 获取本机ip地址及主机名
  4. Java 实例 – 获取远程文件大小
  5. Java 实例 – Socket 实现多线程服务器程序
  6. Java 实例 – 查看主机指定文件的最后修改时间
  7. Java 实例 – 使用 Socket 连接到指定主机
  8. Java 实例 – 网页抓取
  9. Java 实例 – 获取 URL响应头的日期信息
  10. Java 实例 – 获取 URL 响应头信息
  11. Java 实例 – 解析 URL
  12. Java 实例 – ServerSocket 和 Socket 通信实例

Java 线程

  1. Java 实例 – 查看线程是否存活
  2. Java 实例 – 获取当前线程名称
  3. Java 实例 – 状态监测
  4. Java 实例 – 线程优先级设置
  5. Java 实例 – 死锁及解决方法
  6. Java 实例 – 获取线程id
  7. Java 实例 – 线程挂起
  8. Java 实例 – 终止线程
  9. Java 实例 – 生产者/消费者问题
  10. Java 实例 – 获取当前线程名称
  11. Java 实例 – 状态监测
  12. Java 实例 – 线程优先级设置
  13. Java 实例 – 死锁及解决方法
  14. Java 实例 – 获取线程状态
  15. Java 实例 – 获取所有线程
  16. Java 实例 – 查看线程优先级
  17. Java 实例 – 中断线程


Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。


新特性

Java8 新增了非常多的特性,我们主要讨论以下几个:

  • Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。

  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。

  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

  • Date Time API − 加强对日期与时间的处理。

  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

更多的新特性可以参阅官网:What's New in JDK 8

在关于 Java 8 文章的实例,我们均使用 jdk 1.8 环境,你可以使用以下命令查看当前 jdk 的版本:

$ java -versionjava version "1.8.0_31"Java(TM) SE Runtime Environment (build 1.8.0_31-b13)Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)

编程风格

Java 8 希望有自己的编程风格,并与 Java 7 区别开,以下实例展示了 Java 7 和 Java 8 的编程格式:

import java.util.Collections;import java.util.List;import java.util.ArrayList;import java.util.Comparator;public class Java8Tester {   public static void main(String args[]){         List<String> names1 = new ArrayList<String>();      names1.add("Google ");      names1.add("W3CSchool ");      names1.add("Taobao ");      names1.add("Baidu ");      names1.add("Sina ");		      List<String> names2 = new ArrayList<String>();      names2.add("Google ");      names2.add("W3CSchool ");      names2.add("Taobao ");      names2.add("Baidu ");      names2.add("Sina ");		      Java8Tester tester = new Java8Tester();      System.out.println("使用 Java 7 语法: ");		      tester.sortUsingJava7(names1);      System.out.println(names1);      System.out.println("使用 Java 8 语法: ");		      tester.sortUsingJava8(names2);      System.out.println(names2);   }      // 使用 java 7 排序   private void sortUsingJava7(List<String> names){         Collections.sort(names, new Comparator<String>() {         @Override         public int compare(String s1, String s2) {            return s1.compareTo(s2);         }      });   }      // 使用 java 8 排序   private void sortUsingJava8(List<String> names){      Collections.sort(names, (s1, s2) -> s1.compareTo(s2));   }}

执行以上脚本,输出结果为:

$ javac Java8Tester.java$ java Java8Tester使用 Java 7 语法: [Baidu , Google , W3CSchool , Sina , Taobao ]使用 Java 8 语法: [Baidu , Google , W3CSchool , Sina , Taobao ]

接下来我们将详细为大家简介 Java 8 的新特性:

各个细节实例请点击下方链接

序号特性
1Lambda 表达式
2方法引用
3函数式接口
4默认方法
5Stream
6Optional 类
7Nashorn, JavaScript 引擎
8新的日期时间 API
9Base64


Java 9 发布于 2017 年 9 月 22 日,带来了很多新特性,其中最主要的变化是已经实现的模块化系统。接下来我们会详细介绍 Java 9 的新特性。

Java 9 新特性

  • 模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。
  • REPL (JShell):交互式编程环境。
  • HTTP 2 客户端:HTTP/2标准是HTTP协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流以及服务器推送特性。
  • 改进的 Javadoc:Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。
  • 多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
  • 集合工厂方法:List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
  • 私有接口方法:在接口中使用private私有方法。我们可以使用 private 访问修饰符在接口中编写私有方法。
  • 进程 API: 改进的 API 来控制和管理操作系统进程。引进 java.lang.ProcessHandle 及其嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。
  • 改进的 Stream API:改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
  • 改进 try-with-resources:如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
  • 改进的弃用注解 @Deprecated:注解 @Deprecated 可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。
  • 改进钻石操作符(Diamond Operator) :匿名类可以使用钻石操作符(Diamond Operator)。
  • 改进 Optional 类:java.util.Optional 添加了很多新的有用方法,Optional 可以直接转为 stream。
  • 多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。
  • 改进的 CompletableFuture API : CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。
  • 轻量级的 JSON API:内置了一个轻量级的JSON API
  • 响应式流(Reactive Streams) API: Java 9中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。

更多的新特性可以参阅官网:What's New in JDK 9

JDK 9 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk9-doc-downloads-3850606.html

在关于 Java 9 文章的实例,我们均使用 jdk 1.9 环境,你可以使用以下命令查看当前 jdk 的版本:

$ java -versionjava version "9-ea"Java(TM) SE Runtime Environment (build 9-ea+163)Java HotSpot(TM) 64-Bit Server VM (build 9-ea+163, mixed mode)


Java10它号称有109项新特性,包含12个JEP。

需要注意的是,本次Java10并不是Oracle的官方LTS版本,所以咱们可以先了解新特性。然后坐等java11的发布再考虑在生产中使用吧。

特性列表

局部变量的类型推断 var关键字

GC改进和内存管理 并行全垃圾回收器 G1

垃圾回收器接口

线程-局部变量管控

合并 JDK 多个代码仓库到一个单独的储存库中

新增API:ByteArrayOutputStream

新增API:List、Map、Set

新增API:java.util.Properties

新增API: Collectors收集器

其它特性

1、局部变量的类型推断 var关键字

这个新功能将为Java增加一些语法糖 - 简化它并改善开发者体验。新的语法将减少与编写Java相关的冗长度,同时保持对静态类型安全性的承诺。

这可能是Java10给开发者带来的最大的一个新特性。下面主要看例子:

  public static void main(String[] args) {        var list = new ArrayList<String>();        list.add("hello,world!");        System.out.println(list);    }

这是最平常的使用。注意赋值语句右边,最好写上泛型类型,否则会有如下情况:

public static void main(String[] args) {        var list = new ArrayList<>();        list.add("hello,world!");        list.add(1);        list.add(1.01);        System.out.println(list);    }

么都可以装,非常的不安全了。和js等语言不同的是,毕竟Java还是强类型的语言,所以下面语句是编译报错的:

public static void main(String[] args) {        var list = new ArrayList<String>();        list.add("hello,world!");        System.out.println(list);        list = new ArrayList<Integer>(); //编译报错    }

注意:注意:注意:下面几点使用限制

局部变量初始化

for循环内部索引变量

传统的for循环声明变量

public static void main(String[] args) {        //局部变量初始化        var list = new ArrayList<String>();        //for循环内部索引变量        for (var s : list) {            System.out.println(s);        }        //传统的for循环声明变量        for (var i = 0; i < list.size(); i++) {            System.out.println(i);        }    }

下面这几种情况,都是不能使用var的

方法参数

全局变量


public static var list = new ArrayList<String>(); //编译报错    public static List<String> list = new ArrayList<>(); //正常编译通过

构造函数参数

方法返回类型

字段

捕获表达式(或任何其他类型的变量声明)

2、GC改进和内存管理 并行全垃圾回收器 G1

JDK 10中有2个JEP专门用于改进当前的垃圾收集元素。

Java 10的第二个JEP是针对G1的并行完全GC(JEP 307),其重点在于通过完全GC并行来改善G1最坏情况的等待时间。G1是Java 9中的默认GC,并且此JEP的目标是使G1平行。

3、垃圾回收器接口

这不是让开发者用来控制垃圾回收的接口;而是一个在 JVM 源代码中的允许另外的垃圾回收器快速方便的集成的接口。

4、线程-局部变量管控

这是在 JVM 内部相当低级别的更改,现在将允许在不运行全局虚拟机安全点的情况下实现线程回调。这将使得停止单个线程变得可能和便宜,而不是只能启用或停止所有线程。

5、合并 JDK 多个代码仓库到一个单独的储存库中

在 JDK9 中,有 8 个仓库: root、corba、hotspot、jaxp、jaxws、jdk、langtools 和 nashorn 。在 JDK10 中这些将被合并为一个,使得跨相互依赖的变更集的存储库运行 atomic commit (原子提交)成为可能。

6、新增API:ByteArrayOutputStream

String toString(Charset): 重载 toString(),通过使用指定的字符集解码字节,将缓冲区的内容转换为字符串。

7、新增API:List、Map、Set

这3个接口都增加了一个新的静态方法,copyOf(Collection)。这些函数按照其迭代顺序返回一个不可修改的列表、映射或包含给定集合的元素的集合。

8、新增API:java.util.Properties

增加了一个新的构造函数,它接受一个 int 参数。这将创建一个没有默认值的空属性列表,并且指定初始大小以容纳指定的元素数量,而无需动态调整大小。还有一个新的重载的 replace 方法,接受三个 Object 参数并返回一个布尔值。只有在当前映射到指定值时,才会替换指定键的条目。

9、新增API: Collectors收集器

toUnmodifiableList():

toUnmodifiableSet():

toUnmodifiableMap(Function, Function):

toUnmodifiableMap(Function, Function, BinaryOperator):

这四个新方法都返回 Collectors ,将输入元素聚集到适当的不可修改的集合中。

10、其它特性

线程本地握手(JEP 312)

其他Unicode语言 - 标记扩展(JEP 314)

基于Java的实验性JIT编译器

根证书颁发认证(CA)

删除工具javah(JEP 313)

从JDK中移除了javah工具,这个很简单并且很重要。

最后

JDK10的升级幅度其实主要还是以优化为主,并没有带来太多对使用者惊喜的特性。所以建议广大开发者还是研究一下2018年9月份到来Java11吧,最重要的是它是LTS版本哦,所以是可以运用在生产上的。

Java教程 - Java关键字

Java中的关键字完整列表

关键词是其含义由编程语言定义的词。 Java关键字和保留字:

abstract class    extends implements null      strictfp     trueassert   const    false   import     package   super        tryboolean  continue final   instanceof private   switch       voidbreak    default  finally int        protected synchronized volatilebyte     do       float   interface  public    this         whilecase     double   for     long       return    throwcatch    else     goto    native     short     throwschar     enum     if      new        static    transient

标识符是程序员用来命名变量,方法,类或标签的单词。关键字和保留字不能用作标识符。标识符必须以字母,美元符号($)或下划线(_)开头;后续字符可以是字母,美元符号,下划线或数字。

一些例子是:

foobar          // legalMyclass         // legal$a              // legal3_a             // illegal: starts with a digit!theValue       // illegal: bad 1st  char

Java标识符区分大小写。例如, myValue MyValue 是不同的标识符。



使用标识符

标识符用于类名,方法名和变量名。标识符可以是大写和小写字母,数字或下划线和美元符号字符的任何序列。标识符不能以数字开头。Java标识符区分大小写。以下代码说明了有效标识符的一些示例:

public class Main {  public static void main(String[] argv) {    int ATEST, count, i1, $Atest, this_is_a_test;  }}

以下代码显示无效的变量名包括:

public class Main {  public static void main(String[] argv){     int 2count, h-l, a/b,   }}

如果尝试编译此代码,您将收到以下错误消息:



Java设计模式 - 工厂模式


工厂模式是一种创建模式,因为此模式提供了更好的方法来创建对象。

在工厂模式中,我们创建对象而不将创建逻辑暴露给客户端。

例子

在以下部分中,我们将展示如何使用工厂模式创建对象。

由工厂模式创建的对象将是形状对象,如圆形,矩形。

首先,我们设计一个接口来表示Shape。

public interface Shape {   void draw();}

然后我们创建实现接口的具体类。

以下代码用于Rectangle.java

public class Rectangle implements Shape {   @Override   public void draw() {      System.out.println("Inside Rectangle::draw() method.");   }}

Square.java

public class Square implements Shape {   @Override   public void draw() {      System.out.println("Inside Square::draw() method.");   }}

Circle.java

public class Circle implements Shape {   @Override   public void draw() {      System.out.println("Inside Circle::draw() method.");   }}

核心工厂模式是一个Factory类。以下代码显示了如何为Shape对象创建Factory类。

ShapeFactory类基于传递给getShape()方法的String值创建Shape对象。如果String值为CIRCLE,它将创建一个Circle对象。

public class ShapeFactory {     //use getShape method to get object of type shape    public Shape getShape(String shapeType){      if(shapeType == null){         return null;      }          if(shapeType.equalsIgnoreCase("CIRCLE")){         return new Circle();      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){         return new Rectangle();      } else if(shapeType.equalsIgnoreCase("SQUARE")){         return new Square();      }      return null;   }}

以下代码具有main方法,并且它使用Factory类通过传递类型等信息来获取具体类的对象。

public class Main {   public static void main(String[] args) {      ShapeFactory shapeFactory = new ShapeFactory();      //get an object of Circle and call its draw method.      Shape shape1 = shapeFactory.getShape("CIRCLE");      //call draw method of Circle      shape1.draw();      //get an object of Rectangle and call its draw method.      Shape shape2 = shapeFactory.getShape("RECTANGLE");      //call draw method of Rectangle      shape2.draw();      //get an object of Square and call its draw method.      Shape shape3 = shapeFactory.getShape("SQUARE");      //call draw method of circle      shape3.draw();   }}

上面的代码生成以下结果。

Java面向对象设计 - Java类实例


以下是创建类的实例的一般语法:

new <Class Constructor>;

new 运算符后面是对构造函数的调用。

new 运算符通过分配堆上的内存来创建类的实例。以下语句创建Dog类的实例:

new Dog();

Dog()是对Dog类的构造函数的调用。

当我们不向类添加构造函数时,Java编译器为我们添加一个。

Java编译器添加的构造函数称为默认构造函数。默认构造函数不接受参数。

类的构造函数的名称与类名称相同。

new运算符为类的每个实例字段分配内存。类静态变量在创建类的实例时不会分配内存。

要访问类的实例的实例变量,我们必须有它的引用。

类的名称在Java中定义了一个新的引用类型。特定引用类型的变量可以存储相同引用类型的实例的引用。

声明一个引用变量,它将存储Dog类的实例的引用。

Dog anInstance;

Dog是类名,它也是一个引用类型,并且 anInstance 是该类型的变量。

anInstance是Dog类型的引用变量。anInstance变量可用于存储Dog类的实例的引用。

new运算符为类的新实例分配内存,并返回对该实例的引用。

我们需要将由新运算符返回的引用存储在引用变量中。

anInstance = new Dog();

null引用类型

我们可以为任何引用类型的变量分配一个空值。空值意味着引用变量是指没有对象。

Dog  obj  = null;  // obj  is not  referring to any  objectobj  = new Dog();  // Now, obj  is referring to a  valid Dog  object

您可以使用一个空文字与比较运算符来检查是否相等和不等。

if  (obj == null)  {    //obj is null}if  (obj !=  null)  {    //obj is not null}

Java不会混合引用类型和原始类型。我们不能给一个原始类型变量赋null。



访问类的字段的点表示法

点符号用于引用实例变量。

点符号语法的一般形式是

<Reference Variable Name>.<Instance Variable Name>

obj.name引用obj引用变量引用的实例的名称实例变量。

要为名称实例变量分配值,请使用

obj.name = "Rectangle";

以下语句将name实例变量的值分配给String变量aName:

String aName = obj.name;

要引用类变量,请使用类的名称。

ClassName.ClassVariableName

例如,我们可以使用Dog.count来引用Dog类的计数类变量。

向计数类变量分配新值

Dog.count  = 1;

要将count类变量的值读取到变量中

long count = Dog.count;

以下代码显示如何使用类字段

class Dog {  static int count = 0;  String name;  String gender;}public class Main {  public static void main(String[] args) {    Dog obj = new Dog();    // Increase count by one    Dog.count++;    obj.name = "Java";    obj.gender = "Male";    obj.name = "XML";    String changedName = obj.name;  }}

字段的默认初始化

类的所有字段(静态以及非静态)都将初始化为默认值。

字段的默认值取决于其数据类型。

数字字段(字节,短,char,int,long,float和double)初始化为零。布尔字段初始化为false。引用类型字段初始化为null。

下面的代码演示了字段的默认初始化。

public class Main {  byte b;  short s;  int i;  long l;  float f;  double d;  boolean bool;  String str;  public static void main(String[] args) {    Main obj = new Main();    System.out.println("byte is initialized to " + obj.l);    System.out.println("short is initialized to " + obj.s);    System.out.println("int is initialized to " + obj.i);    System.out.println("long is initialized to " + obj.l);    System.out.println("float is initialized to " + obj.f);    System.out.println("double is initialized to " + obj.d);    System.out.println("boolean is initialized to " + obj.bool);    System.out.println("String is initialized to " + obj.str);  }}

上面的代码生成以下结果。

Java数据类型教程 - Java数据类型


以下两行Java代码定义了两个整数:num1和num2:

int num1;int num2;

num1和num2是两个int变量。

int关键字表示后面的名称表示整数值,例如10,15,70,1000等。

因为您已经声明了num数据类型的num1和num2变量,我们不能存储一个实数,如10.1。

下面的代码在num1中存储5,在num2中存储7:

num1 = 5;num2 = 7;

两种数据类型

Java支持两种数据类型:

  • 原始数据类型
  • 引用数据类型

基本数据类型的变量保持一个值,而引用数据类型的变量保持对存储器中的对象的引用。

String是在Java库中定义的类,我们可以使用它来处理字符序列。

您将String类型的引用变量str声明为

String str;

有一个引用常量 null ,可以分配给任何引用变量。

如果为引用变量分配了null,则意味着引用变量不是指存储器中的任何对象。

空引用文本可以分配给str。

str = null;

使用new运算符创建一个String对象。

字符串经常使用,有一个快捷方式来创建一个字符串对象。

所有字符串文字,一个用双引号括起来的字符序列,被视为String对象。

我们可以使用字符串文字如下:

// Assigns "Hello" to str1String str1 = "Hello";// Assigns the   reference of  a  String object with  text  "Hello" to str1String str1 = new String ("Hello");


Java IO教程 - Java文件


File类的对象是文件或目录的路径名的抽象表示。

创建文件

我们可以从中创建一个 File 对象

  • 路径名
  • 父路径名和子路径名
  • URI(统一资源标识符)

我们可以使用File类的以下构造函数之一创建一个文件:

File(String pathname)File(File parent, String child)File(String parent, String child)File(URI uri)

如果我们有一个文件路径名字符串test.txt,我们可以创建一个抽象路径名作为下面的代码。

File dummyFile = new File("test.txt");

名为test.txt的文件不必存在,以使用此语句创建File对象。

dummyFile对象表示抽象路径名,它可能指向或可能不指向文件系统中的真实文件。

File类有几个方法来处理文件和目录。

使用File对象,我们可以创建新文件,删除现有文件,重命名文件,更改文件的权限等。

File类中的isFile()和isDirectory()告诉File对象是否表示文件或目录。


当前工作目录

JVM的当前工作目录是根据我们如何运行java命令来设置的。

我们可以通过读取user.dir系统属性来获取JVM的当前工作目录,如下所示:

String  workingDir = System.getProperty("user.dir");

使用System.setProperty()方法更改当前工作目录。

System.setProperty("user.dir", "C:myDir");

要在Windows上指定C: test作为user.dir系统属性值,我们运行如下所示的程序:

java -Duser.dir=C:	est your-java-class

文件的存在

我们可以使用File类的exists()方法检查File对象的抽象路径名是否存在。

boolean fileExists = dummyFile.exists();

完整源代码

import java.io.File;public class Main {  public static void main(String[] argv) {    // Create a File object    File dummyFile = new File("dummy.txt");    // Check for the file"s existence    boolean fileExists = dummyFile.exists();    if (fileExists) {      System.out.println("The dummy.txt  file exists.");    } else {      System.out.println("The dummy.txt  file does  not  exist.");    }  }}

上面的代码生成以下结果。


路径

绝对路径在文件系统上唯一标识文件。规范路径是唯一标识文件系统上文件的最简单路径。

我们可以使用getAbsolutePath()和getCanonicalPath()方法来分别获得由File对象表示的绝对路径和规范路径。

import java.io.File;import java.io.IOException;public class Main {  public static void main(String[] args) {    printFilePath("dummy.txt");    printFilePath(".." + File.separator + "notes.txt");  }  public static void printFilePath(String pathname) {    File f = new File(pathname);    System.out.println("File  Name: " + f.getName());    System.out.println("File  exists: " + f.exists());    System.out.println("Absolute Path: " + f.getAbsolutePath());    try {      System.out.println("Canonical Path: " + f.getCanonicalPath());    }    catch (IOException e) {      e.printStackTrace();    }  }}

上面的代码生成以下结果。


文件分隔符

不同的操作系统使用不同的字符来分隔路径名中的两个部分。

例如,Windows在路径名中使用反斜杠()作为名称分隔符,而UNIX使用正斜杠(/)。

File类定义了一个名为分隔符Char的常量,它是系统相关的名称分隔符。

我们可以使用File.separator Char常量来获取名称分隔符作为字符。

File.separator 常量将我们的名称分隔符作为String。

在程序中使用名称分隔符将使您的Java代码在不同的平台上工作。

Java XML教程 - Java XML API

SAX API

下面是关键的SAX API的摘要:

用法
SAXParserFactory创建由系统属性javax.xml.parsers.SAXParserFactory确定的解析器的实例。
SAXParserSAXParser接口定义了几个重载的parse()方法。
SAXReaderSAXParser包装一个SAXReader,并从SAXParser的getXMLReader()方法返回。
DefaultHandlerDefaultHandler实现了ContentHandler,ErrorHandler,DTDHandler,和EntityResolver接口。 通过使用DefaultHandler,我们可以只覆盖我们需要的那些。
ContentHandler此接口定义回调方法,如startDocument,endDocument,startElement和endElement。 这些方法在识别XML标记时调用。它还定义了被调用的方法characters()当解析器遇到XML元素中的文本时。它定义被调用的processingInstruction()当解析器遇到内联处理指令时。
ErrorHandler它使用error(),fatalError()和warning()方法来响应各种解析错误。 默认的错误处理程序只会抛出致命错误和的异常忽略验证错误。
DTDHandler用于处理DTD
EntityResolver它的resolveEntity()方法用于标识数据。

我们通常实现大多数 ContentHandler 方法。

为了提供更稳健的实现,我们可以从ErrorHandler实现方法。


SAX包

SAX解析器在下表中列出的软件包中定义。

描述
org.xml.sax定义SAX接口。
org.xml.sax.ext定义用于更高级SAX处理的SAX扩展。
org.xml.sax.helpers定义SAX API的辅助类。
javax.xml.parsers定义SAXParserFactory类,它返回SAXParser。

DOM API

javax.xml.parsers.DocumentBuilderFactory 类返回一个 DocumentBuilder 实例。

我们使用 DocumentBuilder 实例来产生一个 Document 对象退出XML文档。

构建器由系统属性 javax.xml.parsers.DocumentBuilderFactory 确定。

DocumentBuilder 中的 newDocument()方法可以创建一个实现 org.w3c.dom.Document 接口的空Document。

我们可以使用其中一个构建器的解析方法来创建一个 Document 从现有的XML文档。


DOM包

文档对象模型实现在中定义下表中列出的软件包。

描述
org.w3c.dom定义XML文档的DOM编程接口。
javax.xml.parsers定义DocumentBuilderFactory类和DocumentBuilder类。

XSLT API

TransformerFactory 创建一个 Transformer 对象。

XSLT API在下表中显示的包中定义。

描述
javax.xml.transform定义TransformerFactory和Transformer类。 我们可以从变换器对象调用transform()方法来进行变换。
javax.xml.transform.dom用于从DOM创建输入和输出对象的类。
javax.xml.transform.sax用于从SAX解析器创建输入对象和从SAX事件处理程序输出对象的类。
javax.xml.transform.stream用于从I / O流创建输入对象和输出对象的类。

StAX APIs

StAX为开发人员提供了SAX和DOM解析器的替代方法。

StAX可以用更少的内存进行高性能流过滤,处理和修改。

StAX是用于流式XML处理的标准的双向拉解析器接口。

StAX提供比SAX更简单的编程模型,并且比DOM更高的内存效率。

StAX可以解析和修改XML流作为事件。

StAX包

StAX APIs在下表中显示的包中定义。

描述
javax.xml.stream定义迭代XML文档元素的XMLStreamReader接口。 定义XMLStreamWriter接口,指定如何写入XML。
javax.xml.transform.stax提供StAX特定的转换API。

Java集合教程 - Java集合遍历


在Java Collections Framework中,不同类型的集合使用不同类型的数据结构以不同的方式存储它们的元素。

一些集合对它们的元素有排序,有些没有。集合框架提供了遍历集合的以下方法:

  • 使用迭代器
  • 使用for-each循环
  • 使用forEach()方法

使用迭代器

集合提供了一个迭代器来遍历其所有元素。

迭代器可以对集合执行以下三个操作:

  • 检查是否有尚未访问的元素。
  • 访问集合中的下一个元素。
  • 删除集合的最后访问元素。

Java中的迭代器是 Iterator< E> 接口的一个实例。

我们可以使用Collection接口中的iterator()方法获取集合的迭代器。

以下代码创建一个字符串列表,并获取列表的迭代器:

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class Main {  public static void main(String[] args) {    // Create a list of strings    List<String> names = new ArrayList<>();    // Get an iterator for the list    Iterator<String> nameIterator = names.iterator();  }}

迭代器< E> 接口包含以下方法:

boolean hasNext()E next()default void  remove()default void  forEachRemaining(Consumer<? super  E> action)

如果集合中有更多元素要迭代, hasNext()方法将返回true。否则,它返回false。

next()方法返回集合中的下一个元素。我们应该在调用 next()方法之前调用 hasNext()方法。如果没有, next()方法会抛出NoSuchElementException异常。


例子

通常, hasNext() next()方法在循环中一起使用。以下代码使用迭代器打印列表的所有元素:

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class Main {  public static void main(String[] args) {    // Create a list of strings    List<String> names = new ArrayList<>();    names.add("A");    names.add("B");    names.add("C");    // Get an iterator for the list    Iterator<String> nameIterator = names.iterator();    // Iterate over all elements in the list    while (nameIterator.hasNext()) {      // Get the next element from the list      String name = nameIterator.next();      System.out.println(name);    }  }}

上面的代码生成以下结果。

remove()方法删除 next()方法最后返回的元素。每次调用next()方法只能调用一次 remove()方法。

如果对于每个 next()方法或在第一次调用next()之前被多次调用 remove()方法,它会抛出一个 IllegalStateException异常。

remove()方法的支持是可选的。remove()方法可能会抛出一个 UnsupportedOperationException 异常。

例2

以下代码使用迭代器遍历列表的所有元素,并使用remove()方法删除该元素。

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class Main {  public static void main(String[] args) {    // Create a list of strings    List<String> names = new ArrayList<>();    names.add("A");    names.add("B");    names.add("C");    Iterator<String> nameIterator = names.iterator();    // Iterate over all elements in the list    while (nameIterator.hasNext()) {      // Get the next element from the list      String name = nameIterator.next();      System.out.println(name);      nameIterator.remove();    }    System.out.println(names);  }}

上面的代码生成以下结果。

例3

forEachRemaining()方法对集合中尚未由迭代器访问的每个元素执行操作。

action指定为 Consumer

以下代码显示如何打印列表的所有元素。

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class Main {  public static void main(String[] args) {    // Create a list of strings    List<String> names = new ArrayList<>();    names.add("A");    names.add("B");    names.add("C");    Iterator<String> nameIterator = names.iterator();    nameIterator.forEachRemaining(System.out::println);  }}

上面的代码生成以下结果。

迭代器注意事项

迭代器是一次性对象。我们不能重置迭代器,它不能被重用。

要再次遍历同一集合的元素,请通过调用集合的iterator()方法来创建一个新的Iterator。

使用for-each循环

我们可以使用for-each循环遍历集合的元素。

我们可以使用for-each循环遍历任何实现类实现Iterable接口的集合。

for-each循环的一般语法如下:

Collection<T> yourCollection  = ;for(T  element : yourCollection)  {}

在幕后,for-each循环获取迭代器并调用hasNext()和next()方法。

import java.util.ArrayList;import java.util.List;public class Main {  public static void main(String[] args) {    // Create a list of strings    List<String> names = new ArrayList<>();    names.add("A");    names.add("B");    names.add("C");    for (String name : names) {      System.out.println(name);    }  }}

上面的代码生成以下结果。

for-each注意事项

for-each循环有几个限制。

我们不能使用for-each循环从集合中删除元素。

以下代码将抛出ConcurrentModificationException异常:

List<String> names = get   a  list;for(String name : names)  {    names.remove(name);// Throws a  ConcurrentModificationException }

对于for-each循环,我们没有办法从集合的中间开始。

for-each循环不提供访问先前访问的元素的方式。

使用forEach()方法

Iterable接口包含一个新的 forEach(Consumer action)方法。

该方法遍历所有元素并应用操作。

forEach()方法在从 Collection 接口继承的所有集合类型中都可用。

import java.util.ArrayList;import java.util.List;public class Main {  public static void main(String[] args) {    // Create a list of strings    List<String> names = new ArrayList<>();    names.add("A");    names.add("B");    names.add("C");    names.forEach(System.out::println);  }}

上面的代码生成以下结果。


Java正则表达式教程 - Java正则表达式元字符


元字符是在Java正则表达式中具有特殊含义的字符。

Java中的正则表达式支持的元字符如下:

( ) [ ] { {  ^ $ | ? * + . < > - = !

字符类

元字符 [] 指定正则表达式中的字符类。

字符类是一组字符。正则表达式引擎将尝试匹配集合中的一个字符。

字符类“[ABC]"将匹配字符A,B或C.例如,字符串“woman"或“women"将匹配正则表达式“wom [ae] n"。

我们可以使用字符类指定一个字符范围。

范围使用连字符 - 字符表示。

例如, [A-Z] 表示任何大写英文字母;“[0-9]"表示0和9之间的任何数字。

^ 表示不是。

例如, [^ ABC] 表示除A,B和C以外的任何字符。

字符类 [^ A-Z] 表示除大写字母之外的任何字符。

如果 ^ 出现在字符类中,除了开头,它只匹配一个 ^ 字符。

例如,“[ABC ^]"将匹配A,B,C或^。

您还可以在一个字符类中包含两个或多个范围。例如, [a-zA-Z] 匹配任何字符a到z和A到Z.

[a-zA-Z0-9] 匹配任何字符a到z(大写和小写)和任何数字0到9。

下表列出了字符类的示例

字符a到z
字符类 含义
[abc] 字符a,b或c
[^xyz] 除x,y和z以外的字符
[a-z]  
[a-cx-z] 字符a到c或x到z,其将包括a,b,c,x,y或z。
[0-9&&[4-8]] 两个范围(4,5,6,7或8)的交叉,
[a-z&&[^aeiou]] 所有小写字母减元音
   
   

预定义字符类

下表列出了一些常用的预定义字符类。

预定义
字符
  含义
. 任何字符
d 数字。 与[0-9]相同
D 非数字。 与[^ 0-9]相同
s 空格字符。 包括与[ t n x0B f r]相同。
  • 空格
  • 标签
  • 换行符
  • 垂直标签
  • 表单Feed
  • 回车字符
S 非空白字符。 与[^ s]相同
w 一个字符。 与[a-zA-Z_0-9]相同。
W 非字字符。 与[^ w]相同。

例子

以下代码使用 d 匹配所有数字。

d 在字符串中用于转义

import java.util.regex.Matcher;import java.util.regex.Pattern;public class Main {    public static void main(String args[]) {        Pattern p = Pattern.compile("Java d");        String candidate = "Java 4";        Matcher m = p.matcher(candidate);        if (m != null)             System.out.println(m.find());   }}

上面的代码生成以下结果。

例2

以下代码 w+ 匹配任何单词。

双斜杠用于转义

import java.util.regex.Matcher;import java.util.regex.Pattern;public class Main {    public static void main(String args[]) {        String regex = "w+";        Pattern pattern = Pattern.compile(regex);        String candidate = "asdf Java2s.com";        Matcher matcher = pattern.matcher(candidate);        if (matcher.find()) {           System.out.println("GROUP 0:" + matcher.group(0));        }   }}

上面的代码生成以下结果。


Java格式 - Java日期格式类


Java 8有新的Date-Time API来处理日期和时间。 我们应该使用新的Java 8 Date-Time API来格式化和解析日期时间值。

如果我们正在编写与日期和时间相关的新代码,我们应该使用新的Date-Time API。

使用新的Java 8日期时间API格式化日期和时间。

此部分适用于使用旧日期和日历类的旧代码。

Java库提供了两个类来格式化日期:

  • java.text.DateFormat
  • java.text.SimpleDateFormat

DateFormat 类是一个抽象类并且我们可以使用 DateFormat 类以预定义的格式来格式化日期。

因为它是抽象的,所以我们不能创建一个 DateFormat 类的实例使用 new 运算符。

我们必须使用它的一个 getXxxInstance()方法来创建新的实例。Xxx可以是日期,日期时间或时间。

要格式化日期时间值,我们使用 format()方法 DateFormat 类。

DateFormat类的格式化文本取决于两件事:

  • 样式
  • 语言环境

格式的样式决定了包括多少日期时间信息在格式化的文本

语言环境确定要使用的语言环境。

格式样式

Date Format 类将五个样式定义为常量:

  • DateFormat.DEFAULT
  • DateFormat.SHORT
  • DateFormat.MEDIUM
  • DateFormat.LONG
  • DateFormat.FULL

DEFAULT 格式与 MEDIUM 相同。getInstance()使用 SHORT

下表显示了对于美国区域设置以不同样式格式化的相同日期。

样式格式化日期
DEFAULTMar 27, 2014
SHORT3/27/14
MEDIUMMar 26, 2014
LONGMarch 26, 2014
FULLSunday, November 2, 2014

例子

以下代码显示如何以简体中文格式显示语言环境的默认日期,法国和德国。

import java.text.DateFormat;import java.util.Date;import java.util.Locale;public class Main {  public static void main(String[] args) {    Date today = new Date();    // Print date in the default locale format    Locale defaultLocale = Locale.getDefault();    printLocaleDetails(defaultLocale);    printDate(defaultLocale, today);    // Print date in French format    printLocaleDetails(Locale.FRANCE);    printDate(Locale.FRANCE, today);    // Print date in German format. We could also use Locale.GERMANY    // instead of new Locale ("de", "DE").    Locale germanLocale = new Locale("de", "DE");    printLocaleDetails(germanLocale);    printDate(germanLocale, today);  }  public static void printLocaleDetails(Locale locale) {    String languageCode = locale.getLanguage();    String languageName = locale.getDisplayLanguage();    String countryCode = locale.getCountry();    String countryName = locale.getDisplayCountry();    // Print the locale info    System.out.println("Language: " + languageName + "(" + languageCode + "); "        + "Country: " + countryName + "(" + countryCode + ")");  }  public static void printDate(Locale locale, Date date) {    DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT, locale);    String formattedDate = formatter.format(date);    System.out.println("SHORT: " + formattedDate);    formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);    formattedDate = formatter.format(date);    System.out.println("MEDIUM: " + formattedDate+"
");  }}

上面的代码生成以下结果。

java.util.Locale 类包含常见语言环境的常量。

我们可以使用 Locale.getDefault()方法获取系统的默认区域设置。

SimpleDateFormat类

要创建自定义日期格式,我们可以使用 SimpleDateFormat 类。

SimpleDateFormat类是对语言环境敏感的。

它的默认构造函数创建一个格式化程序,默认日期格式为默认语言环境。

SimpleDateFormat 类中的 format()方法执行日期格式。

例2

要更改后续格式化的日期格式,可以通过将新日期格式作为参数传递来使用applyPattern()方法。

import java.text.SimpleDateFormat;import java.util.Date;public class Main {  public static void main(String[] args) {    SimpleDateFormat simpleFormatter = new SimpleDateFormat("dd/MM/yyyy");    Date today = new Date();    String formattedDate = simpleFormatter.format(today);    System.out.println("Today is (dd/MM/yyyy):  " + formattedDate);    simpleFormatter.applyPattern("MMMM dd, yyyy");    formattedDate = simpleFormatter.format(today);    System.out.println("Today is  (MMMM dd, yyyy): " + formattedDate);  }}

上面的代码生成以下结果。

Class类的实例对象,用于记录类描述信息。

Class类没有公共的构造方法,无法通过new运算符实例化;只能通过对象的getClass方法,或是通过Class.forName(…)来获得实例。

方法目的
static ClassforName(String className)throws ClassNotFoundException使用参数className来指定具体的类,来获得相关的类描述对象,该方法有可能抛出类加载异常(ClassNotFoundException),必须捕捉
Class getSuperclass()获得当前类描述对象的父类的描述对象
String getName()返回当前类描述对象的类名称

获取Class对象的三种方式:

public class _T11 {	// Class:类描述对象	public static void main(String[] args) {		Class<?> _class;		// ***1*对象.getClass()		String str = "";		_class = str.getClass();		System.out.println(_class + "-----对象名.getClass()");		// ***2*类.class		_class = String.class;		System.out.println(_class + "-----类名.class");		// ***3*Class.forName("")		try {			_class = Class.forName("java.lang.String");			System.out.println(_class + "-----Class.forName(...)");		} catch (ClassNotFoundException e) {			e.printStackTrace();		}	}}
class java.lang.String-----对象名.getClass()
class java.lang.String-----类名.class
class java.lang.String-----Class.forName(...) 

Class类的常用方法:

  • getName()
    一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 
  • newInstance()
    Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()。例如:x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。
  • getClassLoader()
    返回该类的类加载器。
  • getComponentType()
    返回表示数组组件类型的 Class。
  • getSuperclass()
    返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
  • isArray()
    判定此 Class 对象是否表示一个数组类。


JSF教程 - JSF基本标签


JSF提供了一个标准的HTML标签库,它们被渲染成相应的html输出。

为了使用这些标签,我们需要在html节点中使用以下URI的命名空间。

<html    xmlns="http://www.w3.org/1999/xhtml"    xmlns:h="http://java.sun.com/jsf/html" >

JSF基本标签

以下是JSF 2.0中的重要基本标签。

标签描述
h:inputTexttype =“text"的HTML输入,文本框。
h:inputSecrettype =“password"的HTML输入,文本框。
h:inputTextareaHTML textarea字段。
h:inputHiddentype =“hidden”的HTML输入。
h:selectBooleanCheckbox单个HTML复选框
h:selectManyCheckbox一组HTML复选框
h:selectOneRadio单个HTML单选按钮。
h:selectOneListbox单个HTML列表框。
h:selectManyListbox多个HTML列表框。
h:selectOneMenuHTML组合框。
h:outputTextHTML文本。
h:outputFormatHTML文本。
h:graphicImageHTML图像。
h:outputStylesheetHTML CSS样式表。
h:outputScriptHTML脚本输出。
h:commandButtontype =“submit"按钮的HTML输入。
h:LinkHTML锚点。
h:commandLinkHTML锚点。
h:outputLinkHTML锚点。
h:panelGridHTML表格形式的网格。
h:messageJSF消息
h:messages许多JSF消息。
f:paramJSF UI组件的参数。
f:attributeJSF UI组件的属性。
f:setPropertyActionListener设置受管Bean的属性的值。


实体管理器概述

实体管理器(EntityManager)用于管理系统中的实体,它是实体与数据库之间的桥梁,通过调用实体管理器的相关方法可以把实体持久化到数据库中,同时也可以把数据库中的记录打包成实体对象。

实体的四种状态

在此之前我们要先了解实体的状态及其转换,见下图

JPA 实体生命周期有四种状态

  • 新建状态(New):对象在保存进数据库之前为临时状态。此时数据库中没有该对象的信息,该对象的ID属性也为空。如果没有被持久化,程序退出时临时状态的对象信息将丢失。
  • 托管状态(Managed):对象在保存进数据库后或者从数据库中加载后、并且没有脱离Session时为持久化状态。这时候数据库中有对象的信息,改对象的id为数据库中对应记录的主键值。由于还在 Session中,持久化状态的对象可以执行任何有关数据库的操作,例如获取集合属性的值等。
  • 游离状态(Datached):是对象曾经处于持久化状态、但是现在已经离开Session了。虽然分离状态的对象有id值,有对应的数据库记录,但是已经无法执行有关数据库的操作。例如,读取延迟加载的集合属性,可能会抛出延迟加载异常。
  • 删除状态(Removed):删除的对象,有id值,尚且和 Persistence  Context 有关联,但是已经准备好从数据库中删除。

创建实体管理器

所有实体管理器都来自类型​javax.persistence.EntityManagerFactory​的工厂。

以下示例演示为名为“EmployeeService”的持久性单元创建一个​EntityManagerFactory​:

EntityManagerFactory emf =     Persistence.createEntityManagerFactory("EmployeeService");

以下示例演示如何在上一个示例中获取的工厂创建实体管理器:

EntityManager em = emf.createEntityManager();

保存实体

我们使用实体管理器来持久化​Employee​的实例。

Employee emp = new Employee(158);em.persist(emp);

以下代码显示如何在创建新员工并将其保留到数据库的方法中使用​EntityManager​。

public Employee createEmployee(int id, String name, long salary) {    Employee emp = new Employee(id);    emp.setName(name);    emp.setSalary(salary);    em.persist(emp);    return emp;}

查找实体

一旦实体在数据库中,下一行代码显示如何找到它。

Employee emp = em.find(Employee.class, 1);

删除实体

要从数据库中删除实体,请从​EntityManager​调用​remove​方法。

Employee emp = em.find(Employee.class, 1);em.remove(emp);

更新实体

要更新实体,我们可以在被管实体上调用​setter​方法。被管实体是从​EntityManager​返回的实体。

Employee emp = em.find(Employee.class, 1);emp.setName("new Name");

事务

以下代码显示如何启动和提交事务。

em.getTransaction().begin(); Employee emp = new Employee(158); em.persist(emp); em.getTransaction().commit();

查询

在​JPA​中,有一种称为​Java​持久性查询语言(JP QL)的新的查询语言。

以下示例显示如何创建动态查询,然后执行它以获取数据库中的所有员工。

TypedQuery<Employee> query =      em.createQuery("SELECT e FROM Employee e",                     Employee.class);List<Employee> emps = query.getResultList();

我们通过在​EntityManager​上发出​createQuery()​调用并传入 JP QL 字符串来创建一个​TypedQuery<Employee>​对象。

JP QL 字符串不是指​EMPLOYEE​数据库表,而是Employee实体。

例子

以下代码显示了一个简单的完全功能类,可用于对​Employee​实体发出典型的创建,读取,更新和删除(CRUD)操作。

import java.util.List;import javax.persistence.EntityManager;import javax.persistence.TypedQuery;public class EmployeeService {  protected EntityManager em;  public EmployeeService(EntityManager em) {    this.em = em;  }  public Employee createEmployee(int id, String name, long salary) {    Employee emp = new Employee(id);    emp.setName(name);    emp.setSalary(salary);    em.persist(emp);    return emp;  }  public void removeEmployee(int id) {    Employee emp = findEmployee(id);    if (emp != null) {      em.remove(emp);    }  }  public Employee raiseEmployeeSalary(int id, long raise) {    Employee emp = em.find(Employee.class, id);    if (emp != null) {      emp.setSalary(emp.getSalary() + raise);    }    return emp;  }  public Employee findEmployee(int id) {    return em.find(Employee.class, id);  }  public List<Employee> findAllEmployees() {    TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employee e",        Employee.class);    return query.getResultList();  }}

主类

import java.util.List;import javax.persistence.EntityManager;import javax.persistence.EntityManagerFactory;import javax.persistence.Persistence;public class Main {  public static void main(String[] args) {    EntityManagerFactory emf = Persistence        .createEntityManagerFactory("EmployeeService");    EntityManager em = emf.createEntityManager();    EmployeeService service = new EmployeeService(em);    em.getTransaction().begin();    Employee emp = service.createEmployee(1, "Tom", 5000);    em.getTransaction().commit();    System.out.println("Persisted " + emp);    emp = service.findEmployee(1);    System.out.println("Found " + emp);    List<Employee> emps = service.findAllEmployees();    for (Employee e : emps)      System.out.println("Found employee: " + e);    em.getTransaction().begin();    emp = service.raiseEmployeeSalary(1, 1000);    em.getTransaction().commit();    System.out.println("Updated " + emp);    em.getTransaction().begin();    service.removeEmployee(158);    em.getTransaction().commit();    System.out.println("Removed Employee 158");    em.close();    emf.close();  }}

持久性单元

描述持久性单元的配置在名为 persistence.xml 的XML文件中定义。

每个持久性单元都被命名。单个 persistence.xml 文件可以包含一个或多个命名的持久性单元配置。

以下代码显示了 persistence.xml 文件的演示

<persistence>   <persistence-unit name="EmployeeService" transaction-type="RESOURCE_LOCAL">      <properties>         <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>         <property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/EmpServDB;create=true"/>         <property name="javax.persistence.jdbc.user" value="APP"/>         <property name="javax.persistence.jdbc.password" value="APP"/>      </properties>   </persistence-unit></persistence>

persistence.xml 文件应放在 META-INF 目录中。


Lucene教程 - Lucene HelloWorld


我们可以使用Lucene为您的应用程序添加全文搜索功能。

索引

我们将从一些字符串创建一个内存索引。

StandardAnalyzer analyzer = new StandardAnalyzer(Version.LATEST);Directory index = new RAMDirectory();IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);IndexWriter w = new IndexWriter(index, config);addDoc(w, "Lucene in Action", "1");addDoc(w, "Lucene for Dummies", "2");addDoc(w, "Java", "3");addDoc(w, "Oracle", "4");w.close();private static void addDoc(IndexWriter w, String title, String isbn) throws IOException {  Document doc = new Document();  doc.add(new TextField("title", title, Field.Store.YES));  doc.add(new StringField("isbn", isbn, Field.Store.YES));  w.addDocument(doc);} 

TextField对内容进行标记化,而StringField不对其内容进行标记化。


查询

以下代码显示了如何从Java字符串构建查询。

String querystr = "lucene";Query q = new QueryParser(Version.LATEST, "title", analyzer).parse(querystr);

当进行搜索时,我们首先打开索引,这是一个内存索引。TopScoreDocCollector用于收集前10个评分点击。

int hitsPerPage = 10;IndexReader reader = IndexReader.open(index);IndexSearcher searcher = new IndexSearcher(reader);TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage, true);searcher.search(q, collector);ScoreDoc[] hits = collector.topDocs().scoreDocs;

显示

以下是显示结果的逻辑。

    System.out.println("Found " + hits.length + " hits.");    for(int i=0;i<hits.length;++i) {      int docId = hits[i].doc;      Document d = searcher.doc(docId);      System.out.println(d.get("isbn") );      System.out.println(d.get("title") );    }

完整代码

import org.apache.lucene.analysis.standard.StandardAnalyzer;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.document.StringField;import org.apache.lucene.document.TextField;import org.apache.lucene.index.DirectoryReader;import org.apache.lucene.index.IndexReader;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.index.IndexWriterConfig;import org.apache.lucene.queryparser.classic.ParseException;import org.apache.lucene.queryparser.classic.QueryParser;import org.apache.lucene.search.IndexSearcher;import org.apache.lucene.search.Query;import org.apache.lucene.search.ScoreDoc;import org.apache.lucene.search.TopScoreDocCollector;import org.apache.lucene.store.Directory;import org.apache.lucene.store.RAMDirectory;import org.apache.lucene.util.Version;import java.io.IOException;public class Main {@SuppressWarnings("deprecation")public static void main(String[] args) throws IOException, ParseException {      StandardAnalyzer analyzer = new StandardAnalyzer(Version.LATEST);    Directory index = new RAMDirectory();    IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);    IndexWriter w = new IndexWriter(index, config);    addDoc(w, "Lucene in Action", "1");    addDoc(w, "Lucene for Dummies", "2");    addDoc(w, "Java", "3");    addDoc(w, "Oracle", "4");    w.close();    String querystr = "lucene";    Query q = new QueryParser(Version.LATEST, "title", analyzer).parse(querystr);    int hitsPerPage = 10;    IndexReader reader = DirectoryReader.open(index);    IndexSearcher searcher = new IndexSearcher(reader);    TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage, true);    searcher.search(q, collector);    ScoreDoc[] hits = collector.topDocs().scoreDocs;        System.out.println("Found " + hits.length + " hits.");    for(int i=0;i<hits.length;++i) {      int docId = hits[i].doc;      Document d = searcher.doc(docId);      System.out.println(d.get("isbn") );      System.out.println(d.get("title") );    }    reader.close();  }  private static void addDoc(IndexWriter w, String title, String isbn) throws IOException {    Document doc = new Document();    doc.add(new TextField("title", title, Field.Store.YES));    doc.add(new StringField("isbn", isbn, Field.Store.YES));    w.addDocument(doc);  }}

下载

下载源代码和库。

下载 Lucene_HelloWorld.zip

Log4j教程 - Log4j安装


Log4j API包是根据Apache软件许可证分发的。

最新的log4j版本,包括全源代码,类文件和文档可以在http://logging.apache.org/log4j/找到。

我们可以从上面的链接下载apache-log4j-x.x.x.tar.gz或zip文件。

 注意:log4j以下版本存在0day漏洞,尽量避免使用这些版本!!!

    Apache log4j2 2.0 - 2.14.1

支持库

我们可以使用log4j将信息记录到各种目的地,例如发送电子邮件,数据库或文件。

有一个我们需要放到classpath的库的列表,以便log4j可以拿起它并使用它。

例如,当从log4j发出电子邮件时,我们需要电子邮件库jar文件。

库是可选的,并且取决于我们将要与log4j框架一起使用的功能。

  • JavaMail API(mail.jar): 从https://glassfish.dev.java.net/javaee5/mail/用于基于电子邮件的日志记录。

  • JavaBeans Activation Framework(activation.jar): 来自http://java.sun.com/products/javabeans/jaf/index.jsp。

  • Java Message Service: 用于JMS和JNDI。

  • XML Parser(Xerces.jar): 来自http://xerces.apache.org/xerces-j/install.html。

Maven和Log4j

首先,使用以下maven命令创建一个空的Maven项目。

C:mvn_test>mvn archetype:generate -DgroupId=com.51coolma.ide -DartifactId=MyTest -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

然后,转到项目文件夹并找到pom.xml,添加以下依赖关系。

<dependency>    <groupId>log4j</groupId>    <artifactId>log4j</artifactId>    <version>1.2.17</version></dependency>

之后,将以下代码添加到在resources文件夹下创建的log4j.properties。

  MyTest | +-src    |    +-main       |       +-java       |  |       |  +-com       |    |       |    +-51coolma       |       |       |       +-ide       |       +-resources          |          +- log4j.properties                       

如上面的文件夹结构所示,资源位于java文件夹的保存级别。

对于Java Web应用程序,将log4j.properties文件存储在WEB-INF/classes目录下 将以下配置保存到log4j.properties文件中。
# Root logger optionlog4j.rootLogger=DEBUG, stdout, file # Redirect log messages to consolelog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.Target=System.outlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n # Redirect log messages to a log file, support file rolling.log4j.appender.file=org.apache.log4j.RollingFileAppenderlog4j.appender.file.File=C:log4j.loglog4j.appender.file.MaxFileSize=5MBlog4j.appender.file.MaxBackupIndex=10log4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

最后一个%m%n 配置log4j以添加换行符。

%L设置从记录请求的行号。

%c{1}引用通过 getLogger()设置的日志记录名称。

%-5p设置日志记录优先级,如DEBUG或ERROR。

最后添加以下代码App.java并运行应用程序。

package com.51coolma.ide; import org.apache.log4j.Logger; public class App{   final static Logger logger = Logger.getLogger(App.class);   public static void main(String[] args) {     App obj = new App();    obj.runMe("51coolma");   }   private void runMe(String parameter){     if(logger.isDebugEnabled()){      logger.debug("This is debug : " + parameter);    }     if(logger.isInfoEnabled()){      logger.info("This is info : " + parameter);    }     logger.warn("This is warn : " + parameter);    logger.error("This is error : " + parameter);    logger.fatal("This is fatal : " + parameter);   } }

以下代码显示如何记录异常。

import org.apache.log4j.Logger;public class App {  final static Logger logger = Logger.getLogger(App.class);  public static void main(String[] args) {    App obj = new App();    try {      obj.divide();    } catch (ArithmeticException ex) {      logger.error("Sorry, something wrong!", ex);    }  }  private void divide() {    int i = 10 / 0;  }}



Java JSON教程 - JSON语法


JSON 语法是 JavaScript 对象表示语法的子集。

  • 数据以名称/值对表示。
  • 数据由逗号分隔
  • 花括号括住对象,名称/值对由,分隔。
  • 方括号保存数组,值用,分隔。

JSON支持以下两个数据结构:

数据结构描述
名称/值对的集合key:value,key:value,
有序的值列表[1,2,3,4]


JSON 值

在 JSON 中,值必须是以下数据类型之一:

  • 字符串(在双引号中)
  • 数字(整数或浮点数)
  • 对象(在大括号中)
  • 数组(在小括号中)
  • 逻辑值(true 或 false)
  • null

在 JavaScript 中,以上所列均可为值,外加其他有效的 JavaScript 表达式,包括:

  • 函数
  • 日期
  • undefined

在 JSON 中,字符串值必须由双引号编写

例子

JSON 数据- 名称和值

JSON 数据写为名称/值对。

名称/值由字段名称构成,后跟冒号和值:

实例

"name":"W3Cschool"

等价于:

name = "W3Cschool"

JSON 名称需要双引号。而 JavaScript 名称不需要。

JSON – 求值为 JavaScript 对象

JSON 格式几乎等同于 JavaScript 对象。

在 JSON 中,键必须是字符串,由双引号包围:

JSON

{ "name":"W3Cschool" }

在 JavaScript 中,键可以是字符串、数字或标识符名称:

JavaScript

{ name:"W3Cschool" }

例子

以下JSON数据指定书籍。

{    "book": [    {       "id":"01",       "language": "Java",       "edition": "third",       "author": "java2s.com"    },    {       "id":"02",       "language": "JSON",       "edition": "second"       "author": "java2s.com"    }]}


Java线程教程 - Java多线程


以下代码显示了如何在程序中运行多线程。

public class Main {  public static void main(String[] args) {    // Create two Thread objects    Thread t1 = new Thread(Main::print);    Thread t2 = new Thread(Main::print);    // Start both threads    t1.start();    t2.start();  }  public static void print() {    for (int i = 1; i <= 500; i++) {      System.out.println(i);    }  }}

上面的代码生成以下结果。

线程同步

Java编程语言内置了两种线程同步:

  • 互斥同步
  • 条件同步

在互斥同步中,在一个时间点只允许一个线程访问代码段。

条件同步通过条件变量和三个操作来实现:等待,信号和广播。


同步关键字

synchronized关键字用于声明需要同步的关键部分。

有两种方法可以使用synchronized关键字:

  • 将方法声明为关键部分
  • 将语句块声明为关键段

我们可以通过在方法的返回类型之前使用关键字synchronized来声明一个方法作为临界段。

public class Main {  public synchronized void someMethod_1() {    // Method code goes here  }  public static synchronized void someMethod_2() {    // Method code goes here  }}

我们可以声明一个实例方法和一个静态方法同步。构造函数不能声明为同步。

以下代码说明了使用关键字synchronized:

public class Main {  public synchronized void someMethod_1() {    // only one thread can execute here at a time  }  public void someMethod_11() {    synchronized (this) {      // only one thread can execute here at a time    }  }  public void someMethod_12() {    // multiple threads can execute here at a time    synchronized (this) {      // only one thread can execute here at a time    }    // multiple threads can execute here at a time  }  public static synchronized void someMethod_2() {    // only one thread can execute here at a time  }  public static void someMethod_21() {    synchronized (Main.class) {      // only one thread can execute here at a time    }  }  public static void someMethod_22() {    // multiple threads can execute here at a time    synchronized (Main.class) {      // only one thread can execute here at a time    }    // multiple threads can execute here at a time  }}

wait()方法

对wait()方法的调用必须放在synchronized方法或同步块中。

对于当前线程已经获取监视器的对象,必须调用wait()方法。

notify()方法

没有办法唤醒等待集中的特定线程。

例子

public class Main {  private static int myValue = 1;  public static void main(String[] args) {    Thread t = new Thread(() -> {      while (true) {        updateBalance();      }    });    t.start();    t = new Thread(() -> {      while (true) {        monitorBalance();      }    });    t.start();  }  public static synchronized void updateBalance() {    System.out.println("start:" + myValue);    myValue = myValue + 1;    myValue = myValue - 1;    System.out.println("end:" + myValue);  }  public static synchronized void monitorBalance() {    int b = myValue;    if (b != 1) {      System.out.println("Balance  changed: " + b);      System.exit(1);     }  }}

上面的代码生成以下结果。

例2

以下代码显示了上述代码的非同步版本。

public class Main {  private static int myValue = 1;  public static void main(String[] args) {    Thread t = new Thread(() -> {      while (true) {        updateBalance();      }    });    t.start();    t = new Thread(() -> {      while (true) {        monitorBalance();      }    });    t.start();  }  public static  void updateBalance() {    System.out.println("start:" + myValue);    myValue = myValue + 1;    myValue = myValue - 1;    System.out.println("end:" + myValue);  }  public static synchronized void monitorBalance() {    int b = myValue;    if (b != 1) {      System.out.println("Balance  changed: " + b);      System.exit(1);     }  }}

上面的代码生成以下结果。

Java网络教程 - Java TCP服务器


ServerSocket 类的一个对象表示Java中的TCP服务器套接字。

ServerSocket 对象可以接受来自远程客户端的连接请求。

我们可以使用no-args构造函数创建一个未绑定的服务器套接字,并使用其bind()方法将其绑定到本地端口和本地IP地址。

例子

以下代码显示如何创建服务器套接字:

import java.net.InetSocketAddress;import java.net.ServerSocket;public class Main {  public static void main(String[] argv) throws Exception {    // Create an unbound server socket    ServerSocket serverSocket = new ServerSocket();    // Create a socket address object    InetSocketAddress endPoint = new InetSocketAddress("localhost", 12900);    // Set the wait queue size to 100    int waitQueueSize = 100;    // Bind the server socket to localhost and at port 12900 with    // a wait queue size of 100    serverSocket.bind(endPoint, waitQueueSize);  }}

例2

您可以通过使用以下任何构造函数在一个步骤中组合create,bind和listen操作。

等待队列大小的默认值为50。

本地IP地址的缺省值是通配符地址,即服务器计算机的所有IP地址。

ServerSocket(int port)ServerSocket(int port, int waitQueueSize)ServerSocket(int port, int waitQueueSize,  InetAddress  bindAddr)

您可以将套接字创建和绑定步骤合并为一个语句。

以下代码显示如何在端口12900创建服务器套接字,其中100作为等待队列大小,并在localhost回送地址。

ServerSocket serverSocket  = new ServerSocket(12900, 100, InetAddress.getByName("localhost"));

要接受远程连接请求,请调用服务器套接字上的 accept()方法。

accept()方法调用阻塞执行,直到来自远程客户端的请求到达其等待队列。

The following code calls on ServerSocket will wait  for a  new remote  connection request.
Socket activeSocket = serverSocket.accept();

Socket类包含两个方法 getInputStream() getOutputStream()用于读取和写入连接的套接字。

BufferedReader br  = new BufferedReader(new InputStreamReader(activeSocket.getInputStream()));BufferedWriter bw  = new BufferedWriter(new OutputStreamWriter(activeSocket.getOutputStream()));String s = br.readLine();bw.write("hello"); bw.flush();

最后,使用套接字的close()方法关闭连接。关闭套接字还会关闭其输入和输出流。

activeSocket.close();

以下代码显示如何创建服务器套接字。

import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.InetAddress;import java.net.ServerSocket;import java.net.Socket;public class Main {  public static void main(String[] args) throws Exception {    ServerSocket serverSocket = new ServerSocket(12900, 100,        InetAddress.getByName("localhost"));    System.out.println("Server started  at:  " + serverSocket);    while (true) {      System.out.println("Waiting for a  connection...");      final Socket activeSocket = serverSocket.accept();      System.out.println("Received a  connection from  " + activeSocket);      Runnable runnable = () -> handleClientRequest(activeSocket);      new Thread(runnable).start(); // start a new thread    }  }  public static void handleClientRequest(Socket socket) {    try{      BufferedReader socketReader = null;      BufferedWriter socketWriter = null;      socketReader = new BufferedReader(new InputStreamReader(          socket.getInputStream()));      socketWriter = new BufferedWriter(new OutputStreamWriter(          socket.getOutputStream()));      String inMsg = null;      while ((inMsg = socketReader.readLine()) != null) {        System.out.println("Received from  client: " + inMsg);        String outMsg = inMsg;        socketWriter.write(outMsg);        socketWriter.write("
");        socketWriter.flush();      }      socket.close();    }catch(Exception e){      e.printStackTrace();    }  }}


JavaFX教程 - JavaFX线


为了在JavaFX场景上渲染图形,我们需要基本的形状和颜色。

Node 类是所有JavaFX场景图形节点的基本基类。它提供了转换,翻译和应用效果到任何节点的能力。

javafx.scene.shape.Shape 类是 Node 类的后代。

所有较旧的JavaFX 2.x Builder类在JavaFX 8中已弃用。

JavaFX线

当在JavaFX场景图形上绘制时,使用屏幕坐标空间(系统)渲染线。

屏幕坐标系将(0,0)放在左上角。

x坐标沿x轴移动点。从上到下移动点时,y坐标值增加。

下图显示了右侧的屏幕坐标系。

null

在JavaFX中,场景图形对象(如线,圆和矩形)是Shape类的派生类。

所有形状对象可以在两个成形区域之间执行几何操作,例如减法,相交和并集。

要在JavaFX中绘制线条,我们将使用 javafx.scene.shape.Line 类。

要创建一个 Line 对象,我们需要指定一个开始(x,y)坐标和结束坐标。

创建线节点时,有两种方法来设置起点和终点。

第一种方法使用具有参数startX,startY,endX和endY的构造函数所有参数的数据类型为 double

以下代码使用构造函数创建具有起点(100,10)和终点(10,110)的线。

Line line = new Line(100, 10,   10,   110);

创建行节点的第二种方法是使用空构造函数来实例化 Line 类,然后使用setter方法设置每个属性。

以下代码显示如何创建线对象和使用setter方法设置行的起点和终点。

Line line = new Line(); line.setStartX(100); line.setStartY(10); line.setEndX(10); line.setEndY(110);

在场景图上绘制的线节点默认为1.0的笔触宽度和黑色的笔触颜色。

所有形状的笔触颜色都为null,这意味着除了Line,Polyline和Path节点之外没有颜色。

要创建不同种类的线,我们可以设置属性继承自父类 javafx.scene.shape.Shape 的属性。

下表显示了我们可以在一行上设置的属性。

要检索或修改每个属性,您将使用其适当的getter和setter方法。

属性数据类型 / 说明
filljavafx.scene.paint.Paint
用于填充形状内的颜色。
smoothBoolean
True表示打开反锯齿,false表示关闭反锯齿。
strokeDashOffsetDouble
将距离设置为虚线图案。
strokeLineCapjavafx.scene.shape.StrokeLineCap
在线或路径的末尾设置帽样式。有三种样式:
  • StrokeLineCap.BUTT
  • StrokeLineCap.ROUND
  • StrokeLineCap.SQUARE
strokeLineJoinjavafx.scene.shape.StrokeLineJoin
当线相遇时设置装饰。有三种类型:
  • StrokeLineJoin.MITER
  • StrokeLineJoin.BEVEL
  • StrokeLineJoin.ROUND
strokeMiterLimitDouble
设置斜角接缝的限制以及斜角接缝装饰StrokeLineJoin.MITER。
strokejavafx.scene.paint.Paint
设置形状的笔划线的颜色。
strokeTypejavafx.scene.shape.StrokeType
设置在Shape节点的边界周围绘制描边的位置。有三种类型:
  • StrokeType.CENTERED
  • StrokeType.INSIDE
  • StrokeType.OUTSIDE
strokeWidthDouble
设置线的宽度。

例子

以下代码创建一个Line对象,并使用setter方法设置开始和结束坐标。

import javafx.application.Application;import javafx.scene.Scene;import javafx.scene.layout.VBox;import javafx.scene.shape.Line;import javafx.stage.Stage;public class Main extends Application {    @Override    public void start(Stage stage) {        VBox box = new VBox();        final Scene scene = new Scene(box,300, 250);        scene.setFill(null);                Line line = new Line();        line.setStartX(0.0f);        line.setStartY(0.0f);        line.setEndX(100.0f);        line.setEndY(100.0f);                box.getChildren().add(line);                stage.setScene(scene);        stage.show();    }    public static void main(String[] args) {        launch(args);    }}

上面的代码生成以下结果。

null

例2

以下代码设置更多的线属性,包括笔触颜色,笔触宽度和线帽。

之后,它还设置了线的破折号样式。

import javafx.application.Application;import javafx.scene.Group;import javafx.scene.Scene;import javafx.scene.paint.Color;import javafx.scene.shape.Line;import javafx.scene.shape.StrokeLineCap;import javafx.stage.Stage;public class Main extends Application {  @Override  public void start(Stage primaryStage) {    primaryStage.setTitle("Drawing Lines");    Group root = new Group();    Scene scene = new Scene(root, 300, 150, Color.GRAY);    Line redLine = new Line(10, 10, 200, 10);    redLine.setStroke(Color.RED);    redLine.setStrokeWidth(10);    redLine.setStrokeLineCap(StrokeLineCap.BUTT);    redLine.getStrokeDashArray().addAll(15d, 5d, 15d, 15d, 20d);    redLine.setStrokeDashOffset(10);    root.getChildren().add(redLine);    primaryStage.setScene(scene);    primaryStage.show();  }  public static void main(String[] args) {    launch(args);  }}

上面的代码生成以下结果。

null

Java流 - Java流操作

外部迭代

当使用Java集合时,我们使用外部迭代。

在外部迭代中,我们为每个循环使用for或,或者为序列中的集合的集合和过程元素获取迭代器。

以下代码计算列表中所有奇整数的平方和。

它使用每个循环访问列表中的每一个元素,然后使用if语句来过滤奇整数。

之后,它计算平方,最后存储平方和与和变量。

import java.util.Arrays;import java.util.List;public class Main {  public static void main(String[] args) {    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);    int sum = 0;    for (int n : numbers) {      if (n % 2 == 1) {        int square = n * n;        sum = sum + square;      }    }    System.out.println(sum);  }}

上面的代码生成以下结果。


内部迭代

我们可以使用stream重写上面的代码。 它做的完全一样。

import java.util.Arrays;import java.util.List;public class Main {  public static void main(String[] args) {    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);    int sum = numbers.stream()        .filter(n -> n % 2  == 1)        .map(n  -> n * n)        .reduce(0, Integer::sum);    System.out.println(sum);  }}

在上面的代码中,我们没有使用循环语句来遍历List。 我们通过流在内部执行循环。

对于奇整数计算,我们使用lambda表达式。 我们首先做了过滤,然后映射然后减少。

上面的代码生成以下结果。


顺序

外部迭代通常意味着顺序代码。顺序代码只能由一个线程执行。

流被设计为并行处理元素。

以下代码并行计算列表中奇整数的平方和。

import java.util.Arrays;import java.util.List;public class Main {  public static void main(String[] args) {    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);    int sum = numbers.parallelStream()        .filter(n -> n % 2  == 1)        .map(n  -> n * n)        .reduce(0, Integer::sum);    System.out.println(sum);  }}

我们做的只是用parallelStream()替换stream()。

使用流提供的内部迭代时,并行计算很容易。

上面的代码生成以下结果。

命令式与功能式

在命令式编程中,我们不仅控制要做什么,还要如何做。

例如,当使用命令性编程来对列表中的整数求和时。 我们必须决定如何迭代列表中的每个元素。 我们可以使用for循环,for-each循环,或者我们可以从list中获取一个Iterator对象,并使用while循环。 然后我们也要做总和。

在声明性编程中,我们只需要告诉该做什么,该部分如何由系统本身处理。

集合支持命令式编程,而流支持声明式编程。

Streams API通过使用lambda表达式支持函数式编程。

我们要对流元素执行的操作通常通过传递lambda表达式完成。

流上的操作产生结果而不修改数据源。

中间业务终端业务

流支持两种类型的操作:

  • 中间操作
  • 终端操作

中间操作也称为惰性操作。

终端操作也称为急切操作。

惰性操作不处理元素,直到在流上调用热切操作。

流上的中间操作产生另一流。

Streams链接操作以创建流管道。

在下面的代码中filter()和map()都是惰性操作。 而reduce()是急切的操作。

import java.util.Arrays;import java.util.List;public class Main {  public static void main(String[] args) {    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);    int sum = numbers.parallelStream()        .filter(n -> n % 2  == 1)        .map(n  -> n * n)        .reduce(0, Integer::sum);    System.out.println(sum);  }}

上面的代码生成以下结果。

Java日期时间方法


在 Java Date-Time API 中的方法名称尽可能在类之间保持一致。

例如, now 方法返回当前时刻的日期或时间值。from方法允许从一个类转换到另一个类。

下表列出了常用的前缀:

  • of
    静态工厂方法
    工厂方法创建一个实例并验证输入参数
  • from
    静态工厂方法将输入参数转换为目标类的实例。
  • parse
    静态工厂方法
    解析输入字符串以创建目标类的实例。
  • format
    实例方法
    格式化临时对象以产生字符串。
  • get
    实例方法
    返回目标对象的一部分。
  • is
    实例方法
    查询目标对象。
  • with
    实例方法
    返回一个元素已更改的目标对象的副本。
  • plus
    实例方法
    返回具有添加的时间量的对象的副本。
  • minus
    实例方法
    返回具有减去时间量的对象的副本。
  • to
    实例方法
    将此对象转换为另一种类型。
  • at
    实例方法
    将此对象与另一个对象组合。

of()方法

Java 日期时间 API 的 XXX()方法用于创建对象。

以下代码显示了如何创建 LocalDate 类的对象:

import java.time.LocalDate;import java.time.Month;public class Main {  public static void main(String[] args) {    LocalDate localDate1  = LocalDate.of(2014, 5, 21);     System.out.println(localDate1);        LocalDate localDate2  = LocalDate.of(2014, Month.MARCH, 4);    System.out.println(localDate2);        LocalDate localDate3  = LocalDate.ofEpochDay(2014);    System.out.println(localDate3);        LocalDate localDate4  = LocalDate.ofYearDay(2014, 39);    System.out.println(localDate4);  }}

上面的代码生成以下结果。

from()方法

from()是一个静态工厂方法,用于从指定的参数派生 datetime 对象。

of()不同, from()需要对指定参数进行数据转换。

以下代码显示如何从 LocalDateTime 派生 LocalDate:

import java.time.LocalDate;import java.time.LocalDateTime;public class Main {  public static void main(String[] args) {    LocalDateTime  localDateTime = LocalDateTime.of(2015, 6,  21,  13, 40);    System.out.println(localDateTime);        LocalDate localDate  = LocalDate.from(localDateTime);    System.out.println(localDate);  }}

上面的代码生成以下结果。

with()方法

要更改 datetime 对象中的字段,我们可以使用带有前缀的方法。

withXXX()方法返回一个对象的副本,指定的字段已更改,因为 Date Time API 中的大多数对象都是不可变的。

以下代码显示如何从另一个 LocalDate 获取 LocalDate,并更改年份:

import java.time.LocalDate;import java.time.Month;public class Main {  public static void main(String[] args) {    LocalDate localDate1  = LocalDate.of(2014, Month.MAY,  2);    System.out.println(localDate1);        LocalDate localDate2  = localDate1.withYear(2015);    System.out.println(localDate2);        LocalDate localDate3  = localDate1.withYear(2014).withMonth(7);    System.out.println(localDate3);      }}

上面的代码生成以下结果。

getXXX()方法

getXXX()返回对象的指定元素。

以下代码显示如何从 LocalDate 对象获取年,月和日:

import java.time.LocalDate;import java.time.Month;public class Main {  public static void main(String[] args) {    LocalDate localDate = LocalDate.of(2014, 6, 21);    int year = localDate.getYear();    System.out.println(year);    Month month = localDate.getMonth();    System.out.println(month);    int day = localDate.getDayOfMonth();    System.out.println(day);  }}

上面的代码生成以下结果。

toXXX()方法

toXXX()将对象转换为相关类型。

以下代码显示了使用 toXXX() 方法的一些示例。

import java.time.LocalDate;public class Main {  public static void main(String[] args) {    LocalDate localDate = LocalDate.of(2014, 6, 21);     long days = localDate.toEpochDay();     System.out.println(days);  }}

上面的代码生成以下结果。

atXXX()方法

atXXX()从带有附加信息的现有 datetime 对象创建一个新的 datetime 对象。

以下代码在方法中使用以向日期对象添加附加信息。

import java.time.LocalDate;import java.time.LocalDateTime;public class Main {  public static void main(String[] args) {    LocalDate localDate  = LocalDate.of(2014, 6, 21);    System.out.println(localDate);    LocalDateTime  localTime1 = localDate.atStartOfDay();    System.out.println(localTime1);    LocalDateTime  localTime2 = localDate.atTime(16, 21);    System.out.println(localTime2);  }}

上面的代码生成以下结果。

以下代码显示了如何使用支持构建器模式来构建本地日期的atXXX()方法:

import java.time.LocalDate;import java.time.Year;public class Main {  public static void main(String[] args) {    LocalDate localDate  = Year.of(2014).atMonth(6).atDay(21);    System.out.println(localDate);  }}

上面的代码生成以下结果。

plusXXX()方法

plusXXX()通过添加指定的值来返回对象的副本。

以下代码显示如何使用plus方法向本地添加更多时间日期对象。

import java.time.LocalDate;public class Main {  public static void main(String[] args) {    LocalDate localDate  = LocalDate.of(2014, 6, 21);     LocalDate localDate1  = localDate.plusDays(5);       System.out.println(localDate1);    LocalDate localDate2  = localDate.plusMonths(3);    System.out.println(localDate2);    LocalDate localDate3  = localDate.plusWeeks(3);            System.out.println(localDate3);  }}

上面的代码生成以下结果。

minusXXX()方法

minusXXX()通过减去指定的值来返回对象的副本。

以下代码显示如何从本地日期对象中减去时间。

import java.time.LocalDate;public class Main {  public static void main(String[] args) {    LocalDate localDate  = LocalDate.of(2014, 6, 21);     LocalDate localDate1  = localDate.minusMonths(5);       System.out.println(localDate1);    LocalDate localDate2  = localDate.minusWeeks(3);    System.out.println(localDate2);  }}

上面的代码生成以下结果。

now()方法

now() 方法返回各种类的当前时间,例如 LocalDate,LocalTime,LocalDateTime,ZonedDateTime。

以下代码显示如何使用 now()方法返回当前日期和时间。

import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.ZonedDateTime;public class Main {  public static void main(String[] args) {    LocalDate localDate = LocalDate.now();     System.out.println(localDate);        LocalTime  localTime  = LocalTime.now();    System.out.println(localTime);        LocalDateTime  dateTime  = LocalDateTime.now();    System.out.println(dateTime);        ZonedDateTime dateTimeWithZone  = ZonedDateTime.now();    System.out.println(dateTimeWithZone);  }}

上面的代码生成以下结果。

Java Lambda语法


使用lambda表达式的一般语法是

(Parameters) -> { Body }

-> 分隔参数和lambda表达式主体。

参数括在括号中,与方法相同,而lambda表达式主体是用大括号括起来的代码块。

注意

lambda表达式主体可以有局部变量,语句。我们可以在lambda表达式主体中使用break,continue和return。我们甚至可以从lambda表达式主体中抛出异常。

lambda表达式没有名称,因为它表示匿名内部类。

lambda表达式的返回类型由编译器推断。

lambda表达式不能像方法一样有throws子句。

lambda表达式不能是泛型,而泛型在函数接口中定义。


显式和隐式lambda表达式

未声明其参数类型的lambda表达式称为隐式lambda表达式。

显式lambda表达式是一个lambda表达式,它声明其参数的类型。

编译器将推断用于隐式lambda表达式的参数类型

例子

以下代码使用单一方法创建接口,并将其用作lambda表达式类型。当创建lambda表达式时,我们声明参数 s1 的类型Integer类型。

public class Main {  public static void main(String[] args) {    MyIntegerCalculator myIntegerCalculator = (Integer s1) -> s1 * 2;    System.out.println("1- Result x2 : " + myIntegerCalculator.calcIt(5));  }}interface MyIntegerCalculator {  public Integer calcIt(Integer s1);}

上面的代码生成以下结果。


例2

这里是没有使用类型的演示。当忽略类型时,编译器必须计算出来。

public class Main {  public static void main(String[] args) {    MyIntegerCalculator myIntegerCalculator = (s1) -> s1 * 2;    System.out.println("1- Result x2 : " + myIntegerCalculator.calcIt(5));  }}interface MyIntegerCalculator {  public Integer calcIt(Integer s1);}

上面的代码生成以下结果。

省略参数类型

我们可以选择省略lambda表达式中的参数类型。

在lambda表达式 (int x, int y) -> { return x + y; }声明的参数类型。

我们可以安全地重写lambda表达式,省略参数类型

(x, y) -> { return x + y; }

如果我们选择省略参数类型,我们必须省略所有参数的类型。

public class Main {  public static void main(String[] argv) {    Processor stringProcessor = (str) -> str.length();    String name = "Java Lambda";    int length = stringProcessor.getStringLength(name);    System.out.println(length);  }}@FunctionalInterfaceinterface Processor {  int getStringLength(String str);}

上面的代码生成以下结果。

单参数

对于单个参数lambda表达式,我们可以省略括号,因为我们省略了参数类型。

lambda表达式 (String msg) -> {System.out.println(msg);}
有一切。

然后我们可以省略参数类型

(msg)->{System.out.println(msg);}

我们可以进一步省略参数类型和括号,如下所示。

msg -> { System.out.println(msg); }
public class Main {  public static void main(String[] argv) {    Processor stringProcessor = str -> str.length();    String name = "Java Lambda";    int length = stringProcessor.getStringLength(name);    System.out.println(length);  }}@FunctionalInterfaceinterface Processor {  int getStringLength(String str);}

上面的代码生成以下结果。

无参数

对于没有参数的lambda表达式,我们仍然需要括号。

() -> { System.out.println("hi"); }

以下示例显示如何使用 BooleanSupplier

import java.util.function.BooleanSupplier;public class Main {  public static void main(String[] args) {    BooleanSupplier bs = () -> true;    System.out.println(bs.getAsBoolean());    int x = 0, y= 1;    bs = () -> x > y;    System.out.println(bs.getAsBoolean());  }}

上面的代码生成以下结果。

final修饰符

您可以在参数声明中为表达式lambda表达式使用 final 修饰符。

以下lambda表达式使用final修饰符。

(final int x, final int y) -> { return x + y; }

我们可以只使用一个修饰符如下。

(int x, final int y) -> {return x + y;}
public class Main {  public static void main(String[] argv) {    Processor stringProcessor = (final String str) -> str.length();    String name = "Java Lambda";    int length = stringProcessor.getStringLength(name);    System.out.println(length);  }}@FunctionalInterfaceinterface Processor {  int getStringLength(String str);}

上面的代码生成以下结果。

Lambda表达式主体

lambda表达式主体可以是块语句或单个表达式。

块语句用大括号括起来,而单个表达式可以没有大括号。

在块语句中,我们可以使用 return 语句返回值。

以下lambda表达式使用块语句并使用 return 语句返回总和。

(int x, int y) -> { return x + y; }

下面的lambda使用了一个表达式:

(int x, int y) -> x + y

表达式不需要大括号。

lambda不必返回值。以下两个lambda表达式只是将参数输出到标准输出,不返回任何内容。

(String msg)->{System.out.println(msg);}// a  block   statement(String msg)->System.out.println(msg)   //an expression

例:

public class Main {  public static void main(String[] argv) {    Processor stringProcessor = (String str) -> str.length();    String name = "Java Lambda";    int length = stringProcessor.getStringLength(name);    System.out.println(length);// www . j a  va 2  s. co m  }}@FunctionalInterfaceinterface Processor {  int getStringLength(String str);}

上面的代码生成以下结果。

Java脚本教程 - Java脚本引擎


Java Scripting API有许多类和接口。它们在javax.script包中。

ScriptEngine 接口是其实例执行以脚本语言编写的脚本的接口

ScriptEngineFactory执行两个任务:

  • 创建脚本引擎的实例。
  • 提供有关脚本引擎的信息,如引擎名称,版本,语言等。

AbstractScriptEngine类是一个抽象类,并为ScriptEngine接口提供了部分实现。

ScriptEngineManager类发现和实例化脚本引擎。

可用的引擎

以下代码显示如何列出所有可用的脚本引擎。

import java.util.List;import javax.script.ScriptEngineFactory;import javax.script.ScriptEngineManager;public class Main {  public static void main(String[] args) {    ScriptEngineManager manager = new ScriptEngineManager();    // Get the list of all available engines    List<ScriptEngineFactory> list = manager.getEngineFactories();    // Print the details of each engine    for (ScriptEngineFactory f : list) {      System.out.println("Engine Name:" + f.getEngineName());      System.out.println("Engine Version:" + f.getEngineVersion());      System.out.println("Language Name:" + f.getLanguageName());      System.out.println("Language Version:" + f.getLanguageVersion());      System.out.println("Engine Short Names:" + f.getNames());      System.out.println("Mime Types:" + f.getMimeTypes());      System.out.println("===");    }  }}

上面的代码生成以下结果。

例子

以下代码显示了如何使用JavaScript,Groovy,Jython和JRuby在标准输出上打印消息。

如果脚本引擎不可用,程序会向用户提示错误消息。

import javax.script.ScriptEngine;import javax.script.ScriptEngineManager;import javax.script.ScriptException;public class Main {  public static void main(String[] args) {    // Get the script engine manager    ScriptEngineManager manager = new ScriptEngineManager();    // Try executing scripts in Nashorn, Groovy, Jython, and JRuby    execute(manager, "JavaScript", "print("Hello JavaScript")");    execute(manager, "Groovy", "println("Hello Groovy")");    execute(manager, "jython", "print "Hello Jython"");    execute(manager, "jruby", "puts("Hello JRuby")");  }  public static void execute(ScriptEngineManager manager, String engineName,      String script) {    ScriptEngine engine = manager.getEngineByName(engineName);    if (engine == null) {      System.out.println(engineName + " is not available.");      return;    }    try {      engine.eval(script);    } catch (ScriptException e) {      e.printStackTrace();    }  }}

上面的代码生成以下结果。

找出语法

要获得用于在标准输出上打印消息的语法,请使用ScriptEngineFactory类中的方法getOutputStatement(String toDisplay)。

以下代码段显示了如何获取Nashorn的语法。

import javax.script.ScriptEngine;import javax.script.ScriptEngineFactory;import javax.script.ScriptEngineManager;import javax.script.ScriptException;public class Main {  public static void main(String[] args) {        // Get the script engine factory for Nashorn        ScriptEngineManager manager = new ScriptEngineManager();        ScriptEngine engine = manager.getEngineByName("JavaScript");        ScriptEngineFactory factory = engine.getFactory();        // Get the script        String script = factory.getOutputStatement(""51coolma"");        System.out.println("Syntax: " + script);        // Evaluate the script        try {      engine.eval(script);    } catch (ScriptException e) {      // TODO Auto-generated catch block      e.printStackTrace();    }  }}

上面的代码生成以下结果。

数组是一种非常有用和常用的数据类型,存在于每种程序语言之中,java中的数组是一种最简单的复合数据类型,刚学习java数组的小白们大多都会听到一句这样的话:java是纯面向对象的语言,它的数组也是一个对象。所以很多人就按照一个对象的方式来使用数组,后来你会发现,将数组作为一个类来使用在实现上是多么的“不自然”。下面就来全面了解一下关于java中数组的知识。


什么是数组

什么是数组

数组是同一种类型数据的集合,其实就是一个容器。运算的时候有很多数据参与运算,那么首先需要做的是什么。不是如何运算而是如何保存这些数据以便于后期的运算,那么数组就是一种用于存储数据的方式,能存数据的地方我们称之为容器,容器里装的东西就是数组的元素,数组可以装任意类型的数据,虽然可以装任意类型的数据,但是定义好的数组只能装一种元素, 也就是数组一旦定义,那么里边存储的数据类型也就确定了。

数组的特点

1.在Java中,无论使用数组或集合,都有边界检查。如果越界操作就会得到一个RuntimeException异常。

2.数组只能保存特定类型。数组可以保存原生数据类型,集合则不能。集合不以具体的类型来处理对象,它们将所有对象都按Object类型处理,集合中存放的是对象的引用而不是对象本身。

3.集合类只能保存对象的引用。而数组既可以创建为直接保存原生数据类型,也可以保存对象的引用。在集合中可以使用包装类(Wrapper Class),如Integer、Double等来实现保存原生数据类型值。

4.对象数组和原生数据类型数组在使用上几乎是相同的;唯一的区别是对象数组保存的是引用,原生数据类型数组保存原生数据类型的值。

int a = 10; Integer integer = new Integer(a); int b = integer.intValue(); System.out.println(a = b);

数组的正确使用

如果需要存储大量的数据,例如如果需要读取100个数,那么就需要定义100个变量,显然重复写100次代码,是没有太大意义的。如何解决这个问题,Java语言提供了数组(array)的数据结构,是一个容器可以存储相同数据类型的元素,可以将100个数存储到数组中。这时候数组就有很大的帮助了~


数组的格式

格式一:

元素类型[]数组名 = new元素类型[元素个数或数组长度];
int[] arr = new int[5]; arr[0] = 1; arr[1] = 2;

格式二:

元素类型[]数组名 = new元素类型[]{元素,元素,……};
int[] arr = new int[]{3,5,1,7}; int[] arr = {3,5,1,7};

注意:给数组分配空间时,必须指定数组能够存储的元素个数来确定数组大小。创建数组之后不能修改数组的大小。可以使用length属性获取数组的大小。

声明数组变量

为了使用数组必须在程序中声明数组,并指定数组的元素类型=左半部分:
先写左边明确了元素类型 是int ,容器使用数组,那么如何来标识数组?.那么用一个特殊的符号[]中括号来表示。想要使用数组是需要给数组起一个名字的,那么我们在这里给这个数组起名字为arr .接着跟上等号。

代码体现: 
int [] arr
示例:
String[] aArray = new String[5];  String[] bArray = {"a","b","c", "d", "e"};  String[] cArray = new String[]{"a","b","c","d","e"}; 
注意:int arr[] 也是一种创建数组的格式。推荐使用int [] arr 的形式声明数组。

创建数组的三种方式及区别

public static void main(String[] args) {      // 1.方式一  声明 分配空间并赋值      int[] arr1 = {1,2,3};      // 2.方式二 显示初始化      int[] arr2;      arr2 = new int[]{1,2,3};       // 3.方式三 显示初始化()      int[] arr3;      arr3 = new int[3];  }  
他们的区别,方式一:在声明的时候直接就已经分配空间,并赋值,方式一是不能写成如下这种形式的。
int[] arr1;  arr1 = {1,2,3};//错误写法 编译不同过  
方式二和方式三,声明和内存分配是分开的,如上面的例子,
int[] arr2;  和  int[] arr3;  
这一步是在栈空间分配一个引用,存放的是一个引用,null
arr2 = new int[]{1,2,3};<span style="font-family: Arial, Helvetica, sans-serif;">arr3 = new int[3];</span>  
到这一步的时候jvm才开始在内存堆区域分配空间,并赋值,方式二直接赋值 1,2,3  方式三 默认初始化,基本类型是 0  布尔类型是 false 引用类型为null,

注:内存一旦分配不能改变,所有说数组长度固定

创建数组

数组初始化

方式一:不使用运算符new
int[]arr = { 1, 2, 3, 4, 5 };
方式二:使用运算符new
int[] arr2 = new int[] { 1, 2, 3, 4, 5 };int[] arr3=new int[3];arr3[0]=1;arr3[1]=5;arr3[2]=6;
如果数组初始化中不使用运算符new。需要注意:下列写法是错误的。
int[] arr;arr={1,2,3,4,5};

此时初始化数组,必须将声明,创建,初始化都放在一条语句中个,分开会产生语法错误。

所以只能如下写:

int[] arr={1,2,3,4,5};


java.util.Arrays

Arrays类是一个非常有用数组工具类,里面有很多工具方法,检索、填充、排序、比较、toString()等。

下面给个例子:
import java.util.Arrays; /** * 数组综合测试 * * @author leizhimin 2009-7-28 12:35:41 */ public class TestArrays {         public static void main(String[] args) {                 int[] i = new int[10];                 //填充数组                 Arrays.fill(i, 2);                 //遍历数组                 for (int x : i) {                         System.out.print(x + " ");                 }                 //toString()数组                 System.out.println("
" + Arrays.toString(i));                 //复制数组                 int[] b = new int[12];                 System.arraycopy(i, 0, b, 2, 5);                 System.out.println(Arrays.toString(b));                 //一维数组的比较                 int[] c = new int[3];                 int[] d = new int[3];                 Arrays.fill(c, 3);                 Arrays.fill(d, 3);                 System.out.println(c.equals(d));                 System.out.println(Arrays.equals(c, d));                 System.out.println("-------------");                 int[][] a1 = {{1, 2, 3}, {4, 5, 6}};                 int[][] a2 = {{1, 2, 3}, {4, 5, 6}};                 System.out.println(a1.equals(a2));                 System.out.println(Arrays.equals(a1, a2));                 System.out.println(Arrays.deepEquals(a1, a2));                 //深度toString()                 System.out.println(Arrays.toString(a1));                 System.out.println(Arrays.deepToString(a1));                 //数组的排序                 int[] a3 = {3, 2, 5, 4, 1};                 System.out.println(Arrays.toString(a3));                 Arrays.sort(a3);                 System.out.println(Arrays.toString(a3));                 //一维数组数值检索                 int index1 = Arrays.binarySearch(a3, 4);                 int index2 = Arrays.binarySearch(a3, -12);                 int index3 = Arrays.binarySearch(a3, 8);                 System.out.println(index1 + " " + index2 + " " + index3);         } }

执行结果:
2 2 2 2 2 2 2 2 2 2    [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] [0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0] false true ------------- false false true [[I@3e25a5, [I@19821f] [[1, 2, 3], [4, 5, 6]] [3, 2, 5, 4, 1] [1, 2, 3, 4, 5] 3 -1 -6 Process finished with exit code 0

数组的遍历

public static void main(String[] args) {int[] x = { 1, 2, 3 };for (int y = 0; y < 3; y++) {System.out.println(x[y]);// System.out.println("x["+y+"]="+x[y]); 打印效果 x[0]=1;} // 那么这就是数组的第一个常见操作.遍历}

数组中有一个属性可以获取到数组中元素的个数,也就是数组的长度.数组名.length
public static void main<

随机数是任何一种编程语言最基本的特征之一,在技术开发中应用很广泛,因为有时我们需要随机生成一个固定长度的数字、字符串亦或者是需要随机生成一个不定长度的数字、或者进行一个模拟的随机选择等。Java就为我们提供了最基本的工具,可以帮助开发者生成不同条件下需要的随机数。java中产生随机数和c的差不多,一般有两种随机数,一个是Math中random()方法,一个是Random类。不过不管是c还是java,要产生随机数都需要设置随机数种子,如果设置的是一样的话,每次获得的随机数是一样的。下面来汇总一下常见的不同类型的java随机数是如何生成的。


java随机数

java产生随机数的基本方法

方法一:在j2se里我们可以使用Math.random()方法来产生一个随机数,这个产生的随机数是0-1之间的一个double,我们可以把他乘以一定的数,比如说乘以100,他就是个100以内的随机,这个在j2me中没有。


(数据类型)(最小值+Math.random()*(最大值-最小值+1))

例1:从1到10的int型随数

(int)(1+Math.random()*(10-1+1))


例2:

随机生成0~100中的其中一个数

在上面我们已经知道了Math.random()返回的只是从0到1之间的小数,如果要50到100,就先放大50倍,即0到50之间,这里还是小数,如果要整数,就强制转换int,然后再加上50即为50~100。


最终代码:

(int)(Math.random()*50) + 50


方法二:在java.util这个包里面提供了一个Random的类,我们可以新建一个Random的对象来产生随机数,他可以产生随机整数、随机float、随机double,随机long,这个也是我们在j2me的程序里经常用的一个取随机数的方法。

Random random = new Random();//默认构造方法
Random random = new Random(1000);//指定种子数字

在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。


相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。


例:获取[0, 100)之间的int整数。

int i2 = random.nextInt(100);

Random 的函数接口:

// 构造函数(一): 创建一个新的随机数生成器。  Random() // 构造函数(二): 使用单个 long 种子创建一个新随机数生成器: public Random(long seed) { setSeed(seed); } next 方法使用它来保存随机数生成器的状态。Random(long seed)  boolean nextBoolean()     // 返回下一个“boolean类型”伪随机数。 void  nextBytes(byte[] buf) // 生成随机字节并将其置于字节数组buf中。 double nextDouble()     // 返回一个“[0.0, 1.0) 之间的double类型”的随机数。 float  nextFloat()      // 返回一个“[0.0, 1.0) 之间的float类型”的随机数。 int   nextInt()       // 返回下一个“int类型”随机数。 int   nextInt(int n)    // 返回一个“[0, n) 之间的int类型”的随机数。 long  nextLong()      // 返回下一个“long类型”随机数。   synchronized double nextGaussian()  // 返回下一个“double类型”的随机数,它是呈高斯(“正常地”)分布的 double 值,其平均值是 0.0,标准偏差是 1.0。 synchronized void setSeed(long seed) // 使用单个 long 种子设置此随机数生成器的种子。

Random类中的常用方法:

Random 类中的方法比较简单,每个方法的功能也很容易理解。需要说明的是,Random类中各方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的几率是均等的。下面对这些方法做一下基本的介绍:


a 、public boolean nextBoolean()
该方法的作用是生成一个随机的boolean值,生成true和false的值几率相等,也就是都是50%的几率。


b 、public double nextDouble()
该方法的作用是生成一个随机的double值,数值介于[0,1.0)之间,这里中括号代表包含区间端点,小括号代表不包含区间端点,也就是0到1之间的随机小数,包含0而不包含1.0。


c 、public int nextInt()
该方法的作用是生成一个随机的int值,该值介于int的区间,也就是-2的31次方到2的31次方-1之间。
如果需要生成指定区间的int值,则需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。


d 、public int nextInt(int n)
该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。
如果想生成指定区间的int值,也需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。


e 、public void setSeed(long seed)
该方法的作用是重新设置Random对象中的种子数。设置完种子数以后的Random对象和相同种子数使用new关键字创建出的Random对象相同。


Random类


3 、Random类使用示例
使用Random类,一般是生成指定区间的随机数字,下面就一一介绍如何生成对应区间的随机数字。以下生成随机数的代码均使用以下Random对象r进行生成:
Random r = new Random();
a 、生成[0,1.0)区间的小数
 double d1 = r.nextDouble();


直接使用nextDouble方法获得。
b、生成[0,5.0)区间的小数
double d2 = r.nextDouble() * 5;
因为nextDouble方法生成的数字区间是[0,1.0),将该区间扩大5倍即是要求的区间。
同理,生成[0,d)区间的随机小数,d为任意正的小数,则只需要将nextDouble方法的返回值乘以d即可。


c、生成[1,2.5)区间的小数  [n1,n2]
double d3 = r.nextDouble() * 1.5 + 1;【也就是 r.nextDouble() * (n2-n1)+n1】
生成[1,2.5)区间的随机小数,则只需要首先生成[0,1.5)区间的随机数字,然后将生成的随机数区间加1即可。
同理,生成任意非从0开始的小数区间[d1,d2)范围的随机数字(其中d1不等于0),则只需要首先生成[0,d2-d1)区间的随机数字,然后将生成的随机数字区间加上d1即可。

d、生成任意整数
int n1 = r.nextInt();
直接使用nextInt方法即可。

e、生成[0,10)区间的整数
int n2 = r.nextInt(10);
n2 = Math.abs(r.nextInt() % 10);
以上两行代码均可生成[0,10)区间的整数。

第一种实现使用Random类中的nextInt(int n)方法直接实现。
第二种实现中,首先调用nextInt()方法生成一个任意的int数字,该数字和10取余以后生成的数字区间为(-10,10),因为按照数学上的规定余数的绝对值小于除数,然后再对该区间求绝对值,则得到的区间就是[0,10)了。
同理,生成任意[0,n)区间的随机整数,都可以使用如下代码:
int n2 = r.nextInt(n);
n2 = Math.abs(r.nextInt() % n);

f、生成[0,10]区间的整数
int n3 = r.nextInt(11);
n3 = Math.abs(r.nextInt() % 11);
相对于整数区间,[0,10]区间和[0,11)区间等价,所以即生成[0,11)区间的整数。

g、生成[-3,15)区间的整数
int n4 = r.nextInt(18) - 3;   【也就是 r.nextInt() * (n2-n1)+n1】 n1是个负数
n4 = Math.abs(r.nextInt() % 18) - 3;    
生成非从0开始区间的随机整数,可以参看上面非从0开始的小数区间实现原理的说明。

方法三:通过System.currentTimeMillis()来获取一个当前时间毫秒数的long型数字。

通过System.currentTimeMillis()来获取随机数。实际上是获取当前时间毫秒数,它是long类型。使用方法如下:

final long l = System.currentTimeMillis();


若要获取int类型的整数,只需要将上面的结果转行成int类型即可。比如,获取[0, 100)之间的int整数。方法如下:

final long l = System.currentTimeMillis();final int i = (int)( l % 100 );

实例学习:

下面通过示例演示上面3种获取随机数的使用方法。 源码如下(RandomTest.java):

import java.util.Random;import java.lang.Math; /** * java 的随机数测试程序。共3种获取随机数的方法: *  (01)、通过System.currentTimeMillis()来获取一个当前时间毫秒数的long型数字。 *  (02)、通过Math.random()返回一个0到1之间的double值。 *  (03)、通过Random类来产生一个随机数,这个是专业的Random工具类,功能强大。 * * @author skywang * @email kuiwu-wang@163.com */public class RandomTest{   public static void main(String args[]){     // 通过System的currentTimeMillis()返回随机数    testSystemTimeMillis();     // 通过Math的random()返回随机数    testMathRandom();     // 新建“种子为1000”的Random对象,并通过该种子去测试Random的API    testRandomAPIs(new Random(1000), " 1st Random(1000)");    testRandomAPIs(new Random(1000), " 2nd Random(1000)");    // 新建“默认种子”的Random对象,并通过该种子去测试Random的API    testRandomAPIs(new Random(), " 1st Random()");    testRandomAPIs(new Random(), " 2nd Random()");  }   /**   * 返回随机数-01:测试System的currentTimeMillis()   */  private static void testSystemTimeMillis() {    // 通过    final long l = System.currentTimeMillis();    // 通过l获取一个[0, 100)之间的整数    final int i = (int)( l % 100 );     System.out.printf("
---- System.currentTimeMillis() ----
 l=%s i=%s
", l, i);  }   /**   * 返回随机数-02:测试Math的random()   */  private static void testMathRandom() {    // 通过Math的random()函数返回一个double类型随机数,范围[0.0, 1.0)    final double d = Math.random();    // 通过d获取一个[0, 100)之间的整数    final int i = (int)(d*100);     System.out.printf("
---- Math.random() ----
 d=%s i=%s
", d, i);  }    /**   * 返回随机数-03:测试Random的API   */  

在众多语言中,java都以较大优势领先其他语言,跻身最热语言排名前列,学习java的人不计其数。除了学校和培训机构,知识来源的最好途径就是看书了。对于想要成为java程序员或者已经成为java程序员的人来说,最纠结的一件事可能就是想要看一些自学的java书籍但是选择的范围实在是太大了,不知从何读起才能进阶提升自己的技术,当然,经验老道的程序员已经为我们整理出来一些适合自学的java书籍并按照由浅至深的顺序进行推荐,一起来看看详细的介绍吧:


一、入门基础类

1、Head First Java 第2版·中文版

如果你没有学过其他语言亦或是转行到计算机行业,可以先看看《Head First Java》这本书,此书是根据学习理论所设计的,非常适合零基础的小白, 读起来轻松搞笑,让你可以从程序语言的基础开始一直学习到包括线程、网络与分布式程序等项目。最重要的是,你将学会如何像个面向对象开发者一样去思考。

本书的亮点在于不是让你只是读死书,你可以通过玩游戏、拼图、解谜题以及一些意想不到的方式与Java交互。在这些活动中,你会写出一堆真正的Java程序,包括了一个船舰炮战游戏和一个网络聊天程序。本书图文并茂的学习方式能让你快速地在脑海中掌握住java知识。


Head First Java


点此下载PDF版电子书


2、Head First 设计模式(中文版)

看完了《HeadFirst Java》,还推荐另一本HeadFirst系列的书《HeadFirst 设计模式》。简单有趣、还能把关键的东西说明白,又不会被突然出现的一堆概念绕晕。入门书最关键的一点,是别把学习者吓走!本书可以让读者快速掌握概念、培养兴趣。《HeadFirst Java》作为一本设计模式的入门学习书籍,绝对没错。


3、java从入门到精通 第4版

本书从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用Java语言进行程序开发需要掌握的知识。书中所有知识都结合具体实例进行介绍,涉及的程序代码给出了详细的注释,可以使读者轻松领会Java程序开发的精髓,快速提高开发技能。

本书内容详尽,实例丰富,非常适合作为编程初学者的学习用书,也适合作为开发人员的查阅、参考资料。


java从入门到精通


点此下载PDF版电子书(第3版)

4、Java编程思想

在有了一定的Java编程经验之后,你需要“知其所以然”了。这个时候《Java编程思想》是一本让你知其所以然的好书,它对于基本的面向对象知识有比较清楚的交待,对Java基本语法,基本类库有比较清楚的讲解,可以帮你打一个良好的Java编程基础。这本书的缺点是实在太厚,也比较罗嗦,不适合现代人快节奏学习,因此看这本书要懂得取舍,不是每章每节都值得一看的,挑重点的深入看就可以了。


Java编程思想

提取码:java

5、Java 核心技术:卷1 基础知识

口碑最好的官方机构Java教程系统全面讲解Java语言的核心概念、语法、重要特性和开发方法,内有大量程序实例,内容翔实、客观准确,不拖泥带水极具实用价值,你怎么也得有一本。这本书比较全面而且易懂,放在案旁用到的时候查一查,看一看,是Java初学者和Java程序员的必备参考书。

Java 核心技术:卷1 基础知识


点此下载PDF版电子书 

密码:jv8t


6、Java数据结构和算法 第2版

《Java数据结构和算法》以一种易懂的方式教授如何安排和操纵数据的问题,其中不乏一些难题:了解这些知识以期使计算机的应用获得最好的表现。不管使用何种语言或平台,掌握了数据结构和算法将改进程序的质量和性能。 

这本书提供了一套独创的可视讨论专题用以阐明主要的论题:它使用Java语言说明重要的概念,而避免了C/C++语言的复杂性,以便集中精力论述数据结构和算法。经验丰富的作者RorbertLafore先生提供了许多简单明了的例子,避免了对于这类例题常见的冗长、繁锁的数学证明。在每一章后都有问题和练习,使读者有机会测试自己的理解程度。

这本书目前基本断货,足以说明抢手程度。作者主要使用Java语言描述了我们常用的数据结构,值得一看。

Java数据结构和算法

提取码:71xr

7、Java开发实战经典

这本书中的代码和案例较多,知识点也比较全面,在实际开发的过程中来讲解一些基础内容,对于新手而言很实用。

《Java开发实战经典》是一本综合讲解Java核心技术的书籍,在书中使用大量的代码及案例进行知识点的分析与运用,并且给出一些比较成熟的开发步骤,帮助读者更好地进行Java的开发。本书真正地做到了让每一位读者都能清楚地知道每个知识点的来龙去脉,不仅可以很容易地看懂一个程序,而且能真正地灵活运用程序,编写代码,让每一位读者真正做到“轻松学Java、从零开始学Java”。

“注意”、“提示”、“问答”是《Java开发实战经典(名师讲坛)》的一大特色,通过这样的方式,可以让读者进行更加全面的思考,这些特色中还包含了不少在Java面试中有可能遇到的问题,这让每位读者在打好基础、巩固技术之余,也能为面试提供强有力的支持。

Java开发实战经典


二、中级进阶类


1. Java并发编程实战

本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容,最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类,非常适合Java程序开发人员阅读。

Java并发编程实战


2. 编写高质量代码:改善Java程序的151个建议

国人原创作品。内容全部由Java编码的最佳实践组成,为Java程序员如何编写高质量的Java代码提出了151条极为宝贵的建议。对于每一个问题,不仅以建议的方式从正反两面给出了被实践证明为十分优秀的解决方案和非常糟糕的解决方案,而且还分析了问题产生的根源,犹如醍醐灌顶,让人豁然开朗。

编写高质量代码

提取码:java

3. 重构 改善既有代码的设计

重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。多年前,正是本书原版的出版,使重构终于从编程高手们的小圈子走出,成为众多普通程序员日常开发工作中不可或缺的一部分。本书也因此成为与《设计模式》齐名的经典著作,被译为中、德、俄、日等众多语言,在世界范围内畅销不衰。

本书凝聚了软件开发社区专家多年摸索而获得的宝贵经验,拥有不因时光流逝而磨灭的价值。今天,无论是重构本身,业界对重构的理解,还是开发工具对重构的支持力度,都与本书最初出版时不可同日而语,但书中所蕴涵的意味和精华,依然值得反复咀嚼,而且往往能够常读常新。

重构 改善既有代码的设计


4. 深入分析Java Web技术内幕

作者是2009年加入淘宝的许令波。全面、深入地阐述了Web前端、Java和Java服务端技术。

《深入分析Java Web技术内幕》围绕JavaWeb相关技术从三方面全面深入地进行阐述。首先介绍前端知识,主要介绍JavaWeb开发中涉及的一些基本知识,包括Web请求过程、HTTP协议、DNS技术和CDN技术。其次深入介绍Java技术,包括I/O技术、中文编码问题、Javac编译原理、class文件结构解析、ClassLoader工作机制及JVM的内存管理等。最后介绍Java服务端技术,主要包括Servlet、Session与Cookie、Tomcat与Jetty服务器、Spring容器、Ibatis框架和Velocity框架等原理介绍。

深入分析Java Web技术内幕


5. 大型网站系统与Java中间件实践

作者是蘑菇街技术副总曾宪杰,曾长期负责淘宝主站。通过这本书可以了解大型网站架构变迁过程中的较为通用的问题和解法,并了解构建支撑大型网站的 Java 中间件的实践经验。

对于有一定网站开发、设计经验,并想了解大型网站架构和支撑这种架构的系统的开发、测试等的相关工程人员,本书有很大的参考意义;对于没有网站开发设计经验的人员,通过本书也能宏观了解大型网站的架构及相关问题的解决思路和方案。

大型网站系统与Java中间件实践


三、高级深入类

1、深入理解Java虚拟机

非常难得的国人原创JVM实践性图书。“其中穿插的经验、技巧、案例、实战处处都可见作者的实践之中积累的功力。”此书与Bill Venners的老书《深入Java虚拟机》很大程度上是互补的,可以参看。

第1版两年内印刷近10次,4家网上书店的评论近4?000条,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的改进:根据最新的JDK 1.7对全书内容进行了全面的升级和补充;增加了大量处理各种常见JVM问题的技巧和最佳实践;增加了若干与生产环境相结合的实战案例;对第1版中的错误和不足之处的修正;等等。第2版不仅技术更新、内容更丰富,而且实战性更强。


《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》共分为五大部分,围绕内存管理、执行子系统、程序编译与优化、高效并发等核心主题对JVM进行了全面而深入的分析,深刻揭示了JVM的工作原理。


深入理解Java虚拟机


点此下载PDF版电子书  

密码:hgxm


2、企业应用架构模式

作者将40多种经常出现的解决方案转化成模式,最终写成这本能够应用于任何一种企业应用平台的、关于解决方案的、不可或缺的手册。

《企业应用架构模式》获得了2003年度美国软件开发杂志图书类的生产效率奖和读者选择奖。《企业应用架构模式》分为两大部分。第一部分是关于如何开发企业应用的简单介绍。第二部分是《企业应用架构模式》的主体,是关于模式的详细参考手册,每个模式都给出使用方法和实现信息,并配以详细的Java代码或C#代码示例。此外,整《企业应用架构模式》中还用了大量UML图来进一步阐明有关概念。


《企业应用架构模式》是为致力于设计和构建企业应用的软件架构师、设计人员和编程人员而写的,同时也可作为高等院校计算机专业及软件学院相关课程的参考教材。


企业应用架构模式

提取码:java

3、Java性能权威指南

Java性能方面的新书,可能也是最好的一本。不仅讲述了对什么进行优化,如何优化,还阐述了大量然后编写高效代码的最佳实践。虽然篇幅小一些,但比Oracle官方的那本内容博杂的《Java性能优化权威指南》其实更深入。

市面上介绍Java的书有很多,但专注于Java性能的并不多,能游刃有余地展示Java性能优化难点的更是凤毛麟角,本书即是其中之一。通过使用JVM和Java平台,以及Java语言和应用程序接口,本书详尽讲解了Java性能调优的相关知识,帮助读者深入理解Java平台性能的各个方面,最终使程序如虎添翼。

Java性能权威指南

点此下载PDF版电子书  

密码:urpm



在java开发中有一个非常重要的概念就是java反射机制,也是java的重要特征之一。反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,通过反射可以调用私有方法和私有属性,大部分框架也都是运用反射原理的。java通常是先有类再有对象,有对象就可以调用方法或者属性,java中的反射其实是通过Class对象来调用类里面的方法。掌握了反射的知识,才能更好的学习java高级课程。


java

反射简介:

主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

一个类有多个组成部分,例如:成员变量、方法、构造方法等,反射就是加载类,并解剖出类的各个组成部分。

反射机制主要提供以下功能:

①在运行时判断任意一个对象所属的类;

②在运行时构造任意一个类的对象;

③在运行时判断任意一个类所具有的成员变量和方法;

④在运行时调用任意一个对象的方法;

⑤生成动态代理。

java中的反射及作用:

假如有两个程序员,一个程序员在写程序的时需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码是不能通过编译的。此时,利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。

Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。如eclipse中,一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。

java反射机制中有哪些类:

java.lang.Class;                java.lang.reflect.Constructor; java.lang.reflect.Field;        java.lang.reflect.Method;java.lang.reflect.Modifier;

反射机制的相关API

通过一个对象获得完整的包名和类名 :

package net.xsoftlab.baike;public class TestReflect {    public static void main(String[] args) throws Exception {        TestReflect testReflect = new TestReflect();        System.out.println(testReflect.getClass().getName());        // 结果 net.xsoftlab.baike.TestReflect    }}

实例化Class类对象

package net.xsoftlab.baike;public class TestReflect {    public static void main(String[] args) throws Exception {        Class<?> class1 = null;        Class<?> class2 = null;        Class<?> class3 = null;        // 一般采用这种形式        class1 = Class.forName("net.xsoftlab.baike.TestReflect");        class2 = new TestReflect().getClass();        class3 = TestReflect.class;        System.out.println("类名称   " + class1.getName());        System.out.println("类名称   " + class2.getName());        System.out.println("类名称   " + class3.getName());    }}

获取一个对象的父类与实现的接口

package net.xsoftlab.baike;import java.io.Serializable;public class TestReflect implements Serializable {    private static final long serialVersionUID = -2862585049955236662L;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        // 取得父类        Class<?> parentClass = clazz.getSuperclass();        System.out.println("clazz的父类为:" + parentClass.getName());        // clazz的父类为: java.lang.Object        // 获取所有的接口        Class<?> intes[] = clazz.getInterfaces();        System.out.println("clazz实现的接口有:");        for (int i = 0; i < intes.length; i++) {            System.out.println((i + 1) + ":" + intes[i].getName());        }        // clazz实现的接口有:        // 1:java.io.Serializable    }}

反射

通过反射机制实例化一个类的对象

package net.xsoftlab.baike;import java.lang.reflect.Constructor;public class TestReflect {    public static void main(String[] args) throws Exception {        Class<?> class1 = null;        class1 = Class.forName("net.xsoftlab.baike.User");        // 第一种方法,实例化默认构造方法,调用set赋值        User user = (User) class1.newInstance();        user.setAge(20);        user.setName("Rollen");        System.out.println(user);        // 结果 User [age=20, name=Rollen]        // 第二种方法 取得全部的构造函数 使用构造函数赋值        Constructor<?> cons[] = class1.getConstructors();        // 查看每个构造方法需要的参数        for (int i = 0; i < cons.length; i++) {            Class<?> clazzs[] = cons[i].getParameterTypes();            System.out.print("cons[" + i + "] (");            for (int j = 0; j < clazzs.length; j++) {                if (j == clazzs.length - 1)                    System.out.print(clazzs[j].getName());                else                    System.out.print(clazzs[j].getName() + ",");            }            System.out.println(")");        }        // 结果        // cons[0] (java.lang.String)        // cons[1] (int,java.lang.String)        // cons[2] ()        user = (User) cons[0].newInstance("Rollen");        System.out.println(user);        // 结果 User [age=0, name=Rollen]        user = (User) cons[1].newInstance(20, "Rollen");        System.out.println(user);        // 结果 User [age=20, name=Rollen]    }}class User {    private int age;    private String name;    public User() {        super();    }    public User(String name) {        super();        this.name = name;    }    public User(int age, String name) {        super();        this.age = age;        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "User [age=" + age + ", name=" + name + "]";    }}

获取某个类的全部属性

package net.xsoftlab.baike;import java.io.Serializable;import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class TestReflect implements Serializable {    private static final long serialVersionUID = -2862585049955236662L;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        System.out.println("===============本类属性===============");        // 取得本类的全部属性        Field[] field = clazz.getDeclaredFields();        for (int i = 0; i < field.length; i++) {            // 权限修饰符            int mo = field[i].getModifiers();            String priv = Modifier.toString(mo);            // 属性类型            Class<?> type = field[i].getType();            System.out.println(priv + " " + type.getName() + " " + field[i].getName() + ";");        }                 System.out.println("==========实现的接口或者父类的属性==========");        // 取得实现的接口或者父类的属性        Field[] filed1 = clazz.getFields();        for (int j = 0; j < filed1.length; j++) {            // 权限修饰符            int mo = filed1[j].getModifiers();            String priv = Modifier.toString(mo);            // 属性类型            Class<?> type = filed1[j].getType();            System.out.println(priv + " " + type.getName() + " " + filed1[j].getName() + ";");        }    }}

获取某个类的全部方法

package net.xsoftlab.baike;import java.io.Serializable;import java.lang.reflect.Method;import java.lang.reflect.Modifier;public class TestReflect implements Serializable {    private static final long serialVersionUID = -2862585049955236662L;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        Method method[] = clazz.getMethods();        for (int i = 0; i < method.length; ++i) {            Class<?> returnType = method[i].getReturnType();            Class<?> para[] = method[i].getParameterTypes();            int temp = method[i].getModifiers();            System.out.print(Modifier.toString(temp) + " ");            System.out.print(returnType.getName() + "  ");            System.out.print(method[i].getName() + " ");            System.out.print("(");            for (int j = 0; j < para.length; ++j) {                System.out.print(para[j].getName() + " " + "arg" + j);                if (j < para.length - 1) {                    System.out.print(",");                }            }            Class<?> exce[] = method[i].getExceptionTypes();            if (exce.length > 0) {                System.out.print(") throws ");                for (int k = 0; k < exce.length; ++k) {                    System.out.print(exce[k].getName() + " ");                    if (k < exce.length - 1) {                        System.out.print(",");                    }                }            } else {                System.out.print(")");            }            System.out.println();        }    }}

通过反射机制调用某个类的方法

package net.xsoftlab.baike;import java.lang.reflect.Method;public class TestReflect {    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        // 调用TestReflect类中的reflect1方法        Method method = clazz.getMethod("reflect1");        method.invoke(clazz.newInstance());        // Java 反射机制 - 调用某个类的方法1.        // 调用TestReflect的reflect2方法        method = clazz.getMethod("reflect2", int.class, String.class);        method.invoke(clazz.newInstance(), 20, "张三");        // Java 反射机制 - 调用某个类的方法2.        // age -> 20. name -> 张三    }    public void reflect1() {        System.out.println("Java 反射机制 - 调用某个类的方法1.");    }    public void reflect2(int age, String name) {        System.out.println("Java 反射机制 - 调用某个类的方法2.");        System.out.println("age -> " + age + ". name -> " + name);    }}

通过反射机制操作某个类的属性

package net.xsoftlab.baike;import java.lang.reflect.Field;public class TestReflect {    private String proprety = null;    public static void main(String[] args) throws Exception {        Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");        Object obj = clazz.newInstance();        // 可以直接对 private 的属性赋值        Field field = clazz.getDeclaredField("proprety");        field.setAccessible(true);        field.set(obj, "Java反射机制");        System.out.println(field.get(obj));    }}

反射机制的动态代理

// 获取类加载器的方法TestReflect testReflect = new TestReflect();        System.out.println("类加载器  " + testReflect.getClass().getClassLoader().getClass().getName());package net.xsoftlab.baike;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;//定义项目接口interface Subject {    public String say(String name, int age);}// 定义真实项目class RealSubject implements Subject {    public String say(String name, int age) {        return name + "  " + age;    }}class MyInvocationHandler implements InvocationHandler {    private Object obj = null;    public Object bind(Object obj) {        this.obj = obj;        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        Object temp = method.invoke(this.obj, args);        return temp;    }}/** * 在java中有三种类类加载器。 *  * 1)Bootstrap ClassLoader 此加载器采用c++编写,一般开发中很少见。 *  * 2)Extension ClassLoader 用来进行扩展类的加载,一般对应的是jrelibext目录中的类 *  * 3)AppClassLoader 加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。 *  * 如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,已完成代理的具体操作。 *  * @author xsoftlab.net *  */public class TestReflect {    public static void main(String[] args) throws Exception {        MyInvocationHandler demo = new MyInvocationHandler();        Subject sub = (Subject) demo.bind(new RealSubject());        String info = sub.say("Rollen", 20);        System.out.println(info);    }}


射机制的应用实例


在泛型为Integer的ArrayList中存放一个String类型的对象

package net.xsoftlab.baike;import java.lang.reflect.Method;import java.util.ArrayList;public class TestReflect {    public static void main(String[] args) throws Exception {        ArrayList<Integer> list = new ArrayList<Integer>();        Method method = list.getClass().getMethod("add", Object.class);        method.invoke(list, "Java反射机制实例。");        System.out.println(list.get(0));    }}

修改数组

通过反射取得并修改数组的信息

package net.xsoftlab.baike;import java.lang.reflect.Array;public class TestReflect {    public static void main(String[] args) throws Exception {        int[] temp = { 1, 2, 3, 4, 5 };        Class<?> demo = temp.getClass().getComponentType();        System.out.println("数组类型: " + demo.getName());        System.out.println("数组长度  " + Array.getLength(temp));        System.out.println("数组的第一个元素: " + Array.get(temp, 0));        Array.set(temp, 0, 100);        System.out.println("修改之后数组第一个元素为: " + Array.get(temp, 0));    }}

通过反射机制修改数组的大小

package net.xsoftlab.baike;import java.lang.reflect.Array;public class TestReflect {    public static void main(String[] args) throws Exception {        int[] temp = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };        int[] newTemp = (int[]) arrayInc(temp, 15);        print(newTemp);        String[] atr = { "a", "b", "c" };        String[] str1 = (String[]) arrayInc(atr, 8);        print(str1);    }    // 修改数组大小    public static Object arrayInc(Object obj, int len) {        Class<?> arr = obj.getClass().getComponentType();        Object newArr = Array.newInstance(arr, len);        int co = Array.getLength(obj);        System.arraycopy(obj, 0, newArr, 0, co);        return newArr;    }    // 打印    public static void print(Object obj) {        Class<?> c = obj.getClass();        if (!c.isArray()) {            return;        }        System.out.println("数组长度为: " + Array.getLength(obj));        for (int i = 0; i < Array.getLength(obj); i++) {            System.out.print(Array.get(obj, i) + " ");        }        System.out.println();    }}

将反射机制应用于工厂模式

package net.xsoftlab.baike;interface fruit {    public abstract void eat();}class Apple implements fruit {    public void eat() {        System.out.println("Apple");    }}class Orange implements fruit {    public void eat() {        System.out.println("Orange");    }}class Factory {    public static fruit getInstance(String ClassName) {        fruit f = null;        try {            f = (fruit) Class.forName(ClassName).newInstance();        } catch (Exception e) {            e.printStackTrace();        }        return f;    }}/** * 对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦。 * Java 工厂模式可以参考 * http://baike.xsoftlab.net/view/java-factory-pattern *  * 现在我们利用反射机制实现工厂模式,可以在不修改工厂类的情况下添加任意多个子类。 *  * 但是有一点仍然很麻烦,就是需要知道完整的包名和类名,这里可以使用properties配置文件来完成。 *  * java 读取 properties 配置文件 的方法可以参考 * http://baike.xsoftlab.net/view/java-read-the-properties-configuration-file *  * @author xsoftlab.net */public class TestReflect {    public static void main(String[] args) throws Exception {        fruit f = Factory.getInstance("net.xsoftlab.baike.Apple");        if (f != null) {            f.eat();        }    }}

使用java反射的优势与弊端


java反射的优势与弊端

反射虽然很灵活,能够使得写的代码,变的大幅精简,所以在用的时候,一定要注意具体的应用场景,反射的优缺点如下: 

优点: 
(1)能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。 
(2)与Java动态编译相结合,可以实现无比强大的功能 


缺点: 
(1)使用反射的性能较低 
(2)使用反射相对来说不安全 
(3)破坏了类的封装性,可以通过反射获取这个类的私有方法和属性 

任何事物,都有两面性,反射的优点,也同是就是它的缺点,所以,没有好与坏,只有最合适的场景。

在开发工作中,我们常常会需要一些周期性的操作,比如每5分钟执行一次某个程序,又比如定时检查数据库连接池中的连接数,每晚定时备份数据等等,在java中,最方便、最高效的实现方式就是用java.util.Timer工具类,再通过调度java.util.TimerTask任务,不过,使用这种方式虽然可以让你的程序按照某一个频度执行,但不能在指定时间运行。下面就具体了解一下java定时器设置的几种常用方法及使其停止的方法。


java.util.Timer和java.util.TimerTask基本介绍:

Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所拥有的TimerTasks。

TimerTask是一个抽象类,它的子类由 Timer 安排为一次执行或重复执行的任务。实际上就是一个拥有run方法的类,需要定时执行的代码放到run方法体内。

java定时任务的基本方法:

1、创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果;


thread

2、 用Timer和TimerTask与第一种方法相比有如下好处:

  • 当启动和去取消任务时可以控制

  • 第一次执行任务时可以指定你想要的delay时间


TimerTask

3、 用ScheduledExecutorService是从的java.util.concurrent里做为并发工具类被引进的,这是最理想的定时任务实现方式,相比于上两个方法,它有以下好处:

  • 相比于Timer的单线程,它是通过线程池的方式来执行任务的

  • 可以很灵活的去设定第一次执行任务delay时间

  • 提供了良好的约定,以便设定执行的时间间隔


ScheduledExecutorService

4、基于spring框架的定时任务来实现:

可以使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行。


从作业类的继承方式来讲,可以分为两类:
作业类需要继承自特定的作业类基类,如Quartz中需要继承自org.springframework.scheduling.quartz.QuartzJobBean;java.util.Timer中需要继承自java.util.TimerTask。
作业类即普通的java类,不需要继承自任何基类。
 
注:比较推荐使用第二种方式,因为这样所以的类都是普通类,不需要事先区别对待。


从任务调度的触发时机来分,这里主要是针对作业使用的触发器,主要有以下两种:


每隔指定时间则触发一次,在Quartz中对应的触发器为:org.springframework.scheduling.quartz.SimpleTriggerBean

每到指定时间则触发一次,在Quartz中对应的调度器为:org.springframework.scheduling.quartz.CronTriggerBean
 
注:并非每种任务都可以使用这两种触发器,如java.util.TimerTask任务就只能使用第一种。Quartz和spring task都可以支持这两种触发条件。


用法说明:

第一种,作业类继承自特定的基类:org.springframework.scheduling.quartz.QuartzJobBean

第一步:定义作业类

Java代码

import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class Job1 extends QuartzJobBean {  private int timeout; private static int i = 0; //调度工厂实例化后,经过timeout时间开始执行调度 public void setTimeout(int timeout) { this.timeout = timeout; }  /*** 要调度的具体任务*/ @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException {   System.out.println("定时任务执行中…"); } } 


第二步:spring配置文件中配置作业类JobDetailBean


Xml代码

<bean name="job1" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.gy.Job1" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="0" /> </map> </property> </bean> 
说明:org.springframework.scheduling.quartz.JobDetailBean有两个属性,jobClass属性即我们在java代码中定义的任务类,jobDataAsMap属性即该任务类中需要注入的属性值。

第三步:配置作业调度的触发方式(触发器)

Quartz的作业触发器有两种,分别是
 
org.springframework.scheduling.quartz.SimpleTriggerBean
 
org.springframework.scheduling.quartz.CronTriggerBean
 
第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。
 
配置方式如下:


Xml代码

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job1" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 

第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。

配置方式如下:


Xml代码 

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job1" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 * * ?" /> </bean>
 

关于cronExpression表达式的语法参见附录。

第四步:配置调度工厂

Xml代码

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 

说明:该参数指定的就是之前配置的触发器的名字。

 

第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。


第二种,作业类不继承特定基类。

Spring能够支持这种方式,归功于两个类:

 

org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean

 

org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean

 

这两个类分别对应spring支持的两种实现任务调度的方式,即前文提到到java自带的timer task方式和Quartz方式。这里我只写MethodInvokingJobDetailFactoryBean的用法,使用该类的好处是,我们的任务类不再需要继承自任何类,而是普通的pojo。

 

第一步:编写任务类


Java代码

public class Job2 { public void doJob2() { System.out.println("不继承QuartzJobBean方式-调度进行中..."); } } 

可以看出,这就是一个普通的类,并且有一个方法。

第二步:配置作业类


Xml代码 

<bean id="job2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"> <bean class="com.gy.Job2" /> </property> <property name="targetMethod" value="doJob2" /> <property name="concurrent" value="false" /><!-- 作业不并发调度 --> </bean> 

说明:这一步是关键步骤,声明一个MethodInvokingJobDetailFactoryBean,有两个关键属性:targetObject指定任务类,targetMethod指定运行的方法。往下的步骤就与方法一相同了,为了完整,同样贴出。

 

第三步:配置作业调度的触发方式(触发器)

 

Quartz的作业触发器有两种,分别是

 

org.springframework.scheduling.quartz.SimpleTriggerBean

 

org.springframework.scheduling.quartz.CronTriggerBean

 

第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。

 

配置方式如下:


Xml代码

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job2" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 
 

第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。

 

配置方式如下:


Xml代码

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <

在编写代码时,逻辑判断是避免不了的,switch case语句在很多编程语言中的功能都是用于条件判断,java中为多路分支选择流程专门提供了switch语句,switch语句根据一个表达式的值,选择运行多个操作中的一个。当需要对选项进行等值判断时,使用switch语句更加简洁明了。switch的case语句可以处理int,short,byte,char类型的值,但是不能处理long,String等类型。说起来你应该也会想到另一个常用的条件判断语句if else,本文重点介绍java中switch case语句的用法,但是会在篇末对两种语句的区别做一个总结。


java switch


java switch基础语法

switch(表达式){ case 表达式常量1:语句1breakcase 表达式常量2:语句2break; ...... case 表达式常量n:语句n; break; [default:语句n+1;] } 

其中,一个case表达式常量成为标号,代表一个case分支的入口。switch语句在运行时首先计算switch圆括号中“表达式”的值,这个值必须是整型或字符型的,同时后面各个case表达式常量的值的类型应与switch圆括号中“表达式”的值类型一致。一个case语句代表一个制定操作,然后转向结构出口。default子句是可选的,当表达式的值与case表达式常量的值都不匹配时,就运行default子句,转向结构出口。 

java里switch的执行顺序

switch表达式的值决定选择哪个case分支,如果找不到相应的分支,就直接从"default" 开始输出。
当程序执行一条case语句后,因为例子中的case分支中没有break 和return语句,所以程序会执行紧接于其后的语句。  

public class Switch {    public static void main(String[] args)     {        int x=0;       switch(x)       {        default:            System.out.println("default");        case 1:            System.out.println(1);        case 2:            System.out.println(2);        }    }}

 输出结果如下:

default
1
2

public class Switch {    public static void main(String[] args) {        int x = 0;        switch (x) {        default:            System.out.println("default");        case 0:            System.out.println(0);        case 1:            System.out.println(1);       case 2:            System.out.println(2);        }    }}
输出结果如下:
0
1
2

public class Switch {    public static void main(String[] args) {        int x = 0;        switch (x) {        case 0:            System.out.println(0);        case 1:            System.out.println(1);        case 2:            System.out.println(2);        default:            System.out.println("default");        }    }}
输出结果如下:
0
1
2
default

java switch语句注意事项

java switch语句注意事项

switch(A),括号中A的取值只能是整型或者可以转换为整型的数值类型,比如byte、short、int、char、还有枚举;需要强调的是:long和String类型是不能作用在switch语句上的。

     

case B:C;case是常量表达式,也就是说B的取值只能是常量(需要定义一个final型的常量,后面会详细介绍原因)或者int、byte、short、char(比如1、2、3、200000000000(注意了这是整型)),如果你需要在此处写一个表达式或者变量,那么就要加上单引号; case后的语句可以不用大括号,就是C不需要用大括号包裹着;

     

default就是如果没有符合的case就执行它,default并不是必须的。


案例分析:

1.标准型(case后面都有break语句,case后的值都是整数)

int i=3; switch(i) { case 1: System.out.println(1); break; case 2: System.out.println(2); break;  default: System.out.println("default"); break; } 

2.常量型(case后面都有break语句,case后的值都是常量)

private final int NUM1=1private final int NUM2=1int i=3; switch(i) { case NUM1: System.out.println(1); break; case NUM2: System.out.println(2); break;  default: System.out.println("default"); break; } 

3.表达式型(case后面都有break语句,case后的值都是表达式)

int i=3; int b = 2;switch(i) { case ‘类名.getId()‘: System.out.println(1); break; case ‘b‘ System.out.println(2); break;  default: System.out.println("default"); break; }

实例:java 用switch语句解决月薪范围问题
public class SwitchDemo {    public static void main(String[] args) {         int month = 8;        String monthString;        switch (month) {            case 1:  monthString = "January";                     break;            case 2:  monthString = "February";                     break;            case 3:  monthString = "March";                     break;            case 4:  monthString = "April";                     break;            case 5:  monthString = "May";                     break;            case 6:  monthString = "June";                     break;            case 7:  monthString = "July";                     break;            case 8:  monthString = "August";                     break;            case 9:  monthString = "September";                     break;            case 10: monthString = "October";                     break;            case 11: monthString = "November";                     break;            case 12: monthString = "December";                     break;            default: monthString = "Invalid month";                     break;        }        System.out.println(monthString);    }}

switch和if语句的区别

Java中switch和if语句的区别

switch和if语句都是Java的选择语句,这两种语句都是允许在程序运行时控制程序的执行过程。

switch和if-else相比,由于使用了Binary Tree算法,绝大部分情况下switch会快一点,除非是if-else的第一个条件就为true。 


编译器编译switch与编译if...else...不同。不管有多少case,都直接跳转,不需逐个比较查询。 

 

相比于if-else结构,switch的效率绝对是要高很多的,但是switch使用查找表的方式决定了case的条件必须是一个连续的常量。而if-else则可以灵活的多。

  
switch效率高,从汇编代码可以看出来。switch只计算一次值,然后都是test。
  

switch的效率与分支数无关。当只有分支比较少的时候,if效率比switch高(因为switch有跳转表)。分支比较多,那当然是使用switch。


有一个问题困扰着许多初学java的新手们,那就是有必要去学习数据结构吗?虽然你可能没有特意去看一些数据结构的专业书籍,你仍然可以用java做一份还过得去的工作,不过并不是说你完全没有接触到数据结构,因为java已经在底层帮你做了太多,在你和数据结构打交道的时候,你所做更多的是在调用 API 。当你的代码量累积到一定程度的时候,就会想要去加强数据结构和算法的相关知识了。


打个比方,你可以把java看做是自动档轿车,数据结构呢就是变速箱的工作原理。你完全可以不知道变速箱怎样工作,就把自动档的车子开上路,而且未必就比懂得的人慢。写程序这件事,和开车一样,经验可以起到很大作用,但如果你不知道底层是怎么工作的,就永远只能开车,既不会修车,也不能造车。如果你对这两件事都不感兴趣也就罢了,数据结构懂得用就好。但若你此生在编程领域还有点更高的追求,数据结构是绕不开的课题。


此外,很重要的一点是,数据结构也是通向各种实用算法的基石,所以学习数据结构都是提升内力的事情。这里推荐一本书《Java数据结构和算法》,这本书以一种易懂的方式教授如何安排和操纵数据的问题,它使用java语言说明重要的概念,而避免了C/C++语言的复杂性,以便集中精力论述数据结构和算法。经验丰富的作者RorbertLafore先生提供了许多简单明了的例子,避免了对于这类例题常见的冗长、繁锁的数学证明。在本书的每一章后都有问题和练习,使读者有机会测试自己的理解程度。


Java数据结构和算法



java中的数据结构总结

线性表,链表,哈希表是常用的数据结构,在进行java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。下面通过简单的描述,为你阐述各个类的作用以及如何正确使用这些类。

Collection
Collection

Map
Map

Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。

所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。

如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子  while(it.hasNext()) {  Object obj = it.next(); // 得到下一个元素  } 
由Collection接口派生的两个接口是List和Set。


主要方法:

1、boolean add(Object o)添加对象到集合


2、boolean remove(Object o)删除指定的对象


3、int size()返回当前集合中元素的数量

4、boolean contains(Object o)查找集合中是否有指定的对象

5、boolean isEmpty()判断集合是否为空

6、Iterator iterator()返回一个迭代器

7、boolean containsAll(Collection c)查找集合中是否有集合c中的元素

8、boolean addAll(Collection c)将集合c中所有的元素添加给该集合

9、void clear()删除集合中所有元素

10、void removeAll(Collection c)从集合中删除c集合中也有的元素

11、void retainAll(Collection c)从集合中删除集合c中不包含的元素


List接口

List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

和下面要提到的Set不同,List允许有相同的元素。

除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。

实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。


主要方法:

1、void add(int index,Object element)在指定位置上添加一个对象

2、boolean addAll(int index,Collection c)将集合c的元素添加到指定的位置

3、Object get(int index)返回List中指定位置的元素

4、int indexOf(Object o)返回第一个出现元素o的位置.

5、Object removeint(int index)删除指定位置的元素

6、Object set(int index,Object element)用元素element取代位置index上的元素,返回被取代的元素


LinkedList类

LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(...)); 

ArrayList类

ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。

size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。

每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

和LinkedList一样,ArrayList也是非同步的(unsynchronized)。


主要方法:

1、Boolean add(Object o)将指定元素添加到列表的末尾

2、Boolean add(int index,Object element)在列表中指定位置加入指定元素

3、Boolean addAll(Collection c)将指定集合添加到列表末尾

4、Boolean addAll(int index,Collection c)在列表中指定位置加入指定集合

5、Boolean clear()删除列表中所有元素

6、Boolean clone()返回该列表实例的一个拷贝

7、Boolean contains(Object o)判断列表中是否包含元素

8、Boolean ensureCapacity(int m)增加列表的容量,如果必须,该列表能够容纳m个元素

9、Object get(int index)返回列表中指定位置的元素

10、Int indexOf(Object elem)在列表中查找指定元素的下标

11、Int size()返回当前列表的元素个数


Vector类

Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。


Stack 类
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Set接口
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。

很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。

请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。


主要方法:

1、boolean equals(Object o)比较对象

2、boolean remove(Object o)删除一个对象

3、put(Object key,Object value)添加key和value


Hashtable类

Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。

添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。

Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。

使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:

Hashtable numbers = new Hashtable();  numbers.put(“one”, new Integer(1));  numbers.put(“two”, new Integer(2));  numbers.put(“three”, new Integer(3)); 


要取出一个数,比如2,用相应的key:

Integer n = (Integer)numbers.get(“two”);  System.out.println(“two = ” + n); 

由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。

如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。

Hashtable是同步的。


HashMap类
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

WeakHashMap类
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。

总结
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。

如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。

要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。

尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

XML (eXtensible Markup Language) 意为可扩展标记语言,被多数技术人员用以选择作为数据传输的载体,成为一种通用的数据交换格式,xml的平台无关性,语言无关性,系统无关性,给数据集成与交互带来了极大的便利。在不同的语言中,解析xml的方式都是一样的,只不过实现的语法不同而已。众所周知,现在解析XML的方法越来越多,但主流的方法也就四种,即:DOM、SAX、JDOM和DOM4J。


这四种方法的jar包下载地址:

①DOM:在现在的Java JDK里都自带了,在xml-apis.jar包里

②SAXhttp://sourceforge.net/projects/sax/

③JDOM:http://jdom.org/downloads/index.html

④DOM4J:http://sourceforge.net/projects/dom4j/


java


下面以一个实例来具体说明这4种方法:


xml文件:

<?xml version="1.0" encoding="GB2312"?><RESULT><VALUE>   <NO>A1234</NO>   <ADDR>四川省XX县XX镇XX路X段XX号</ADDR></VALUE><VALUE>   <NO>B1234</NO>   <ADDR>四川省XX市XX乡XX村XX组</ADDR></VALUE></RESULT>


1、使用DOM(JAXP Crimson解析器)

DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。DOM以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。


实现方法:

import java.io.*;import java.util.*;import org.w3c.dom.*;import javax.xml.parsers.*;public class MyXMLReader{ public static void main(String arge[]){  long lasting =System.currentTimeMillis();  try{   File f=new File("data_10k.xml");   DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();   DocumentBuilder builder=factory.newDocumentBuilder();   Document doc = builder.parse(f);   NodeList nl = doc.getElementsByTagName("VALUE");   for (int i=0;i<nl.getLength();i++){    System.out.print("车牌号码:" + doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue());    System.out.println("车主地址:" + doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue());   }  }catch(Exception e){   e.printStackTrace();}
【优点】
①允许应用程序对数据和结构做出更改。
②访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据。


【缺点】
通常需要加载整个XML文档来构造层次结构,消耗资源大。


2. 使用SAX

SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。

选择DOM还是选择SAX? 对于需要自己编写代码来处理XML文档的开发人员来说, 选择DOM还是SAX解析模型是一个非常重要的设计决策。 DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。

DOM解析器把XML文档转化为一个包含其内容的树,并可以对树进行遍历。用DOM解析模型的优点是编程容易,开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用DOM解析器的时候需要处理整个XML文档,所以对性能和内存的要求比较高,尤其是遇到很大的XML文件的时候。由于它的遍历能力,DOM解析器常用于XML文档需要频繁的改变的服务中。

SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag.特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展能力得到了更好的体现。但用SAX解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。

实现方法:
import org.xml.sax.*;import org.xml.sax.helpers.*;import javax.xml.parsers.*;public class MyXMLReader extends DefaultHandler { java.util.Stack tags = new java.util.Stack(); public MyXMLReader() {  super();} public static void main(String args[]) {  long lasting = System.currentTimeMillis();  try {   SAXParserFactory sf = SAXParserFactory.newInstance();   SAXParser sp = sf.newSAXParser();   MyXMLReader reader = new MyXMLReader();   sp.parse(new InputSource("data_10k.xml"), reader);  } catch (Exception e) {   e.printStackTrace();  }   System.out.println("运行时间:" + (System.currentTimeMillis() - lasting) + "毫秒");}  public void characters(char ch[], int start, int length) throws SAXException {  String tag = (String) tags.peek();  if (tag.equals("NO")) {   System.out.print("车牌号码:" + new String(ch, start, length));}if (tag.equals("ADDR")) {  System.out.println("地址:" + new String(ch, start, length));}}   public void startElement(String uri,String localName,String qName,Attributes attrs) {  tags.push(qName);}}
【优点】
①不需要等待所有数据都被处理,分析就能立即开始。
②只在读取数据时检查数据,不需要保存在内存中。
③可以在某个条件得到满足时停止解析,不必解析整个文档。
④效率和性能较高,能解析大于系统内存的文档。

【缺点】
①需要应用程序自己负责TAG的处理逻辑(例如维护父/子关系等),文档越复杂程序就越复杂。
②单向导航,无法定位文档层次,很难同时访问同一文档的不同部分数据,不支持XPath。

JDOM

3、使用JDOM

JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一个Java特定模型,JDOM一直得到大力推广和促进。正在考虑通过“Java规范请求JSR-102”将它最终用作“Java标准扩展”。从2000年初就已经开始了JDOM开发。

JDOM与DOM主要有两方面不同。首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。

JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”(根据学习曲线假定为20%)。JDOM对于大多数Java/XML应用程序来说当然是有用的,并且大多数开发者发现API比DOM容易理解得多。JDOM还包括对程序行为的相当广泛检查以防止用户做任何在XML中无意义的事。然而,它仍需要您充分理解XML以便做一些超出基本的工作(或者甚至理解某些情况下的错误)。这也许是比学习DOM或JDOM接口都更有意义的工作。

JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器以将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。JDOM是在Apache许可证变体下发布的开放源码。

实现方法:
import java.io.*;import java.util.*;import org.jdom.*;import org.jdom.input.*;public class MyXMLReader { public static void main(String arge[]) {  long lasting = System.currentTimeMillis();  try {   SAXBuilder builder = new SAXBuilder();   Document doc = builder.build(new File("data_10k.xml"));   Element foo = doc.getRootElement();   List allChildren = foo.getChildren();   for(int i=0;i<allChildren.size();i++) {    System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());    System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());   }  } catch (Exception e) {   e.printStackTrace();} }
【优点】
①使用具体类而不是接口,简化了DOM的API。
②大量使用了Java集合类,方便了Java开发人员。

【缺点】
①没有较好的灵活性。
②性能较差。

4、使用DOM4J

虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。

为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。

在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。

DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。

实现方法:
import java.io.*;import java.util.*;import org.dom4j.*;import org.dom4j.io.*; public class MyXMLReader {  public static void main(String arge[]) {  long lasting = System.currentTimeMillis();  try {   File f = new File("data_10k.xml");   SAXReader reader = new SAXReader();   Document doc = reader.read(f);   Element root = doc.getRootElement();   Element foo;   for (Iterator i = root.elementIterator("VALUE"); i.hasNext() {    foo = (Element) i.next();    System.out.print("车牌号码:" + foo.elementText("NO"));    System.out.println("车主地址:" + foo.elementText("ADDR"));   }  } catch (Exception e) {   e.printStackTrace();})
【优点】
①大量使用了Java集合类,方便Java开发人员,同时提供一些提高性能的替代方法。
②支持XPath。
③有很好的性能。

【缺点】
①大量使用了接口,API较为复杂。

DOM4J

4种方法综合对比

1. DOM4J性能最好,连Sun的JAXM也在用DOM4J。目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J。
     
2. JDOM和DOM在性能测试时表现不佳,在测试10M文档时内存溢出,但可移植。在小文档情况下还值得考虑使用DOM和JDOM.虽然JDOM的开发者已经说明他们期望在正式发行版前专注性能问题,但是从性能观点来看,它确实没有值得推荐之处。另外,DOM仍是一个非常好的选择。DOM实现广泛应用于多种编程语言。它还是许多其它与XML相关的标准的基础,因为它正式获得W3C推荐(与基于非标准的Java模型相对),所以在某些类型的项目中可能也需要它(如在JavaScript中使用DOM)。
     
3. SAX表现较好,这要依赖于它特定的解析方式-事件驱动。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。
     
建议:如果XML文档较大且不考虑移植性问题建议采用DOM4J;如果XML文档较小则建议采用JDOM;如果需要及时处理而不需要保存数据则考虑SAX。但无论如何,还是那句话:适合自己的才是最好的,如果时间允许,建议大家讲这四种方法都尝试一遍然后选择一种适合自己的即可。

读取XML配置文件

首先我们需要通过DocumentBuilderFactory获取xml文件的工厂实例。
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);

创建文档对象
DocumentBuilder db = dbf.newDocumentBuilder();            Document doc = db.parse(xmlPath); // 使用dom解析xml文件

最后遍历列表,进行数据提取
NodeList sonlist = doc.getElementsByTagName("son");             for (int i = 0; i < sonlist.getLength(); i++) // 循环处理对象            {                Element son = (Element)sonlist.item(i);;                                for (Node node = son.getFirstChild(); node != null; node = node.getNextSibling()){                      if (node.getNodeType() == Node.ELEMENT_NODE){                          String name = node.getNodeName();                          String value = node.getFirstChild().getNodeValue();                          System.out.println(name+" : "+value);                    }                  }              }

完整实例:
public static void getFamilyMemebers(){        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);        try {            DocumentBuilder db = dbf.newDocumentBuilder();            Document doc = db.parse(xmlPath); // 使用dom解析xml文件            NodeList sonlist = doc.getElementsByTagName("son");             for (int i = 0; i < sonlist.getLength(); i++) // 循环处理对象            {                Element son = (Element)sonlist.item(i);;                                for (Node node = son.getFirstChild(); node != null; node = node.getNextSibling()){                      if (node.getNodeType() == Node.ELEMENT_NODE){                          String name = node.getNodeName();                          String value = node.getFirstChild().getNodeValue();                          System.out.println(name+" : "+value);                    }                  }              }        } catch (Exception e) {            e.printStackTrace();        }    }

在XML文件中增加节点

用差不多同样的步骤,先获取根节点,创建一个新的节点,向其中添加元素信息,最后把这个新节点添加到根节点中
Element root = xmldoc.getDocumentElement();                        //删除指定节点                        Element son =xmldoc.createElement("son");            son.setAttribute("id", "004");                        Element name = xmldoc.createElement("name");            name.setTextContent("小儿子");            son.appendChild(name);            Element age = xmldoc.createElement("name");            age.setTextContent("0");            son.appendChild(age);                        root.appendChild(son);

最后不要忘记保存新增的文件,对源文件进行覆盖
TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));

完整实例:
public static void createSon() {        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(false);                try{                    DocumentBuilder db=dbf.newDocumentBuilder();            Document xmldoc=db.parse(xmlPath);                    Element root = xmldoc.getDocumentElement();                        //删除指定节点                        Element son =xmldoc.createElement("son");            son.setAttribute("id", "004");                        Element name = xmldoc.createElement("name");            name.setTextContent("小儿子");            son.appendChild(name);            Element age = xmldoc.createElement("name");            age.setTextContent("0");            son.appendChild(age);                        root.appendChild(son);            //保存            TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));                    }catch(Exception e){            e.printStackTrace();        }    }


在XML中修改节点信息

通过XPath来获取目标节点
public static Node selectSingleNode(String express, Element source) {        Node result=null;        XPathFactory xpathFactory=XPathFactory.newInstance();        XPath xpath=xpathFactory.newXPath();        try {            result=(Node) xpath.evaluate(express, source, XPathConstants.NODE);        } catch (XPathExpressionException e) {            e.printStackTrace();        }                return result;    }

获取目标节点,进行修改,完成后,保存文件
Element root = xmldoc.getDocumentElement();                        Element per =(Element) selectSingleNode("/father/son[@id='001']", root);            per.getElementsByTagName("age").item(0).setTextContent("27");                        TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));

完整实例:
public static void modifySon(){        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);        try{                    DocumentBuilder db=dbf.newDocumentBuilder();            Document xmldoc=db.parse(xmlPath);                    Element root = xmldoc.getDocumentElement();                        Element per =(Element) selectSingleNode("/father/son[@id='001']", root);            per.getElementsByTagName("age").item(0).setTextContent("27");                        TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));        }catch(Exception e){            e.printStackTrace();        }    }

删除XML中的节点

通过XPath获取目标节点, 进行删除,最后保存
Element root = xmldoc.getDocumentElement();                        Element son =(Element) selectSingleNode("/father/son[@id='002']", root);            root.removeChild(son);            TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));

完整实例:
public static void discardSon(){                    DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();        dbf.setIgnoringElementContentWhitespace(true);                try{                    DocumentBuilder db=dbf.newDocumentBuilder();            Document xmldoc=db.parse(xmlPath);                    Element root = xmldoc.getDocumentElement();                        Element son =(Element) selectSingleNode("/father/son[@id='002']", root);            root.removeChild(son);            TransformerFactory factory = TransformerFactory.newInstance();            Transformer former = factory.newTransformer();            former.transform(new DOMSource(xmldoc), new StreamResult(new File(xmlPath)));                    }catch(Exception e){            e.printStackTrace();        }    }



协程(Coroutine)这个词其实有很多叫法,比如有的人喜欢称为纤程(Fiber),或者绿色线程(GreenThread)。其实究其本质,对于协程最直观的解释是线程的线程。虽然读上去有点拗口,但本质上就是这样。

协程的核心在于调度那块由他来负责解决,遇到阻塞操作,立刻放弃掉,并且记录当前栈上的数据,阻塞完后立刻再找一个线程恢复栈并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为Fiber。


java协程的实现

早期,在JVM上实现协程一般会使用kilim,不过这个工具已经很久不更新了,现在常用的工具是Quasar,而本文章会全部基于Quasar来介绍。

下面尝试通过Quasar来实现类似于go语言的coroutine以及channel。

为了能有明确的对比,这里先用go语言实现一个对于10以内自然数分别求平方的例子。

func counter(out chan<- int) {  for x := 0; x < 10; x++ {    out <- x  }  close(out)}func squarer(out chan<- int, in <-chan int) {  for v := range in {    out <- v * v  }  close(out)}func printer(in <-chan int) {  for v := range in {    fmt.Println(v)  }}func main() {  //定义两个int类型的channel  naturals := make(chan int)  squares := make(chan int)  //产生两个Fiber,用go关键字  go counter(naturals)  go squarer(squares, naturals)  //获取计算结果  printer(squares)}

上面这个例子,通过channel两解耦两边的数据共享。对于这个channel,大家可以理解为Java里的SynchronousQueue。下面我直接上Quasar版JAVA代码的,几乎可以原封不动的复制go语言的代码。

public class Example {  private static void printer(Channel<Integer> in) throws SuspendExecution,  InterruptedException {    Integer v;    while ((v = in.receive()) != null) {      System.out.println(v);    }  }  public static void main(String[] args) throws ExecutionException, InterruptedException, SuspendExecution {    //定义两个Channel    Channel<Integer> naturals = Channels.newChannel(-1);    Channel<Integer> squares = Channels.newChannel(-1);    //运行两个Fiber实现.    new Fiber(() -> {      for (int i = 0; i < 10; i++)        naturals.send(i);      naturals.close();    }).start();    new Fiber(() -> {      Integer v;      while ((v = naturals.receive()) != null)        squares.send(v * v);      squares.close();    }).start();    printer(squares);  }}

两者对比,看上去Java似好像更复杂些,没办法这就是Java的风格,而且这还是通过第三方的库来实现的。

说到这里各位肯定对Fiber很好奇了。也许你会表示怀疑Fiber是不是如上面所描述的那样,下面我们尝试用Quasar建立一百万个Fiber,看看内存占用多少,我先尝试了创建百万个Thread。

for (int i = 0; i < 1_000_000; i++) {  new Thread(() -> {    try {      Thread.sleep(10000);    } catch (InterruptedException e) {      e.printStackTrace();    }  }).start();}

很不幸,直接报Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread,这是情理之中的。下面是通过Quasar建立百万个Fiber。

public static void main(String[] args) throws ExecutionException, InterruptedException, SuspendExecution {  int FiberNumber = 1_000_000;  CountDownLatch latch = new CountDownLatch(1);  AtomicInteger counter = new AtomicInteger(0);  for (int i = 0; i < FiberNumber; i++) {    new Fiber(() -> {      counter.incrementAndGet();      if (counter.get() == FiberNumber) {        System.out.println("done");      }      Strand.sleep(1000000);    }).start();  }  latch.await();}

我这里加了latch,阻止程序跑完就关闭,Strand.sleep其实跟Thread.sleep一样,只是这里针对的是Fiber。

最终控制台是可以输出done的,说明程序已经创建了百万个Fiber,设置Sleep是为了让Fiber一直运行,从而方便计算内存占用。官方宣称一个空闲的Fiber大约占用400Byte,那这里应该是占用400MB堆内存,但是这里通过jmap -heap pid显示大约占用了1000MB,也就是说一个Fiber占用1KB。


Quasar是怎么实现Fiber的

其实Quasar实现的coroutine的方式与Go语言很像,只不过前者是使用框架来实现,而go语言则是语言内置的功能。

不过如果你熟悉了Go语言的调度机制的话,那么对于Quasar的调度机制就会好理解很多了,因为两者有很多相似之处。

Quasar里的Fiber其实是一个continuation,他可以被Quasar定义的scheduler调度,一个continuation记录着运行实例的状态,而且会被随时中断,并且也会随后在他被中断的地方恢复。

Quasar其实是通过修改bytecode来达到这个目的,所以运行Quasar程序的时候,你需要先通过java-agent在运行时修改你的代码,当然也可以在编译期间这么干。go语言的内置了自己的调度器,而Quasar则是默认使用ForkJoinPool这个具有work-stealing功能的线程池来当调度器。work-stealing非常重要,因为你不清楚哪个Fiber会先执行完,而work-stealing可以动态的从其他的等等队列偷一个context过来,这样可以最大化使用CPU资源。

那这里你会问了,Quasar怎么知道修改哪些字节码呢,其实也很简单,Quasar会通过java-agent在运行时扫描哪些方法是可以中断的,同时会在方法被调用前和调度后的方法内插入一些continuation逻辑,如果你在方法上定义了@Suspendable注解,那Quasar会对调用该注解的方法做类似下面的事情。

这里假设你在方法f上定义了@Suspendable,同时去调用了有同样注解的方法g,那么所有调用f的方法会插入一些字节码,这些字节码的逻辑就是记录当前Fiber栈上的状态,以便在未来可以动态的恢复。(Fiber类似线程也有自己的栈)。在suspendable方法链内Fiber的父类会调用Fiber.park,这样会抛出SuspendExecution异常,从而来停止线程的运行,好让Quasar的调度器执行调度。这里的SuspendExecution会被Fiber自己捕获,业务层面上不应该捕获到。如果Fiber被唤醒了(调度器层面会去调用Fiber.unpark),那么f会在被中断的地方重新被调用(这里Fiber会知道自己在哪里被中断),同时会把g的调用结果(g会return结果)插入到f的恢复点,这样看上去就好像g的return是f的local variables了,从而避免了callback嵌套。

上面说了一大堆,其实简单点来讲就是,想办法让运行中的线程栈停下来,然后让Quasar的调度器介入。

JVM线程中断的条件有两个:

1、抛异常

2、return。

而在Quasar中,一般就是通过抛异常的方式来达到的,所以你会看到上面的代码会抛出SuspendExecution。但是如果你真捕获到这个异常,那就说明有问题了,所以一般会这么写。

@Suspendablepublic int f() {  try {    // do some stuff    return g() * 2;  } catch(SuspendExecution s) {    //这里不应该捕获到异常.    throw new AssertionError(s);  }}


Coroutine in Java - Quasar Fiber实现 

Quasar Fiber则是通过字节码修改技术在编译或载入时织入必要的上下文保存/恢复代码,通过抛异常来暂停,恢复的时候根据保存的上下文(Continuation),恢复jvm的方法调用栈和局部变量,Quasar Fiber提供相应的Java类库来实现,对应用有一定的侵入性(很小)

Quasar Fiber 主要有 Instrument + Continuation + Scheduler几个部分组成

  • Instrument 做一些代码的植入,如park前后上下文的保存/恢复等
  • Continuation 保存方法调用的信息,如局部变量,引用等,用户态的stack,这个也是跟akka等基于固定callback接口的异步框架最大的区别
  • Scheduler 调度器,负责将fiber分配到具体的os thread执行



相关阅读:

JAVA微课——像玩游戏般学习java

JAVA多线程编程


这份备忘单是 Java 初学者的速成课程,有助于复习 Java 语言的基本语法。

开始

hello.java

public class Hello {  // main methord  public static void main(String[] args)  {    // Output: Hello, world!    System.out.println("Hello, world!");  }}

编译和运行

$ javac Hello.java$ java HelloHello, world!

变量

int num = 5;float floatNum = 5.99f;char letter = 'D';boolean bool = true;String site = "quickref.me";

原始数据类型

数据类型尺寸默认范围
byte1 字节0-128 127
short2 字节0-2 15 ~ 2 15 -1
int4 字节0-2 31 ~ 2 31 -1
long8 字节0-2 63 ~ 2 63 -1
float4 字节0.0fN/A
double8 字节0.0dN/A
char2 字节u00000 ~ 65535
booleanN/A
fasletrue/false

字符串

String first = "John";String last = "Doe";String name = first + " " + last;System.out.println(name);

请参阅:字符串

循环

String word = "QuickRef";for (char c: word.toCharArray()) {  System.out.print(c + "-");}// Outputs: Q-u-i-c-k-R-e-f-

请参阅:循环

数组

char[] chars = new char[10];chars[0] = 'a'chars[1] = 'b'String[] letters = {"A", "B", "C"};int[] mylist = {100, 200};boolean[] answers = {true, false};

请参阅:数组

交换

int a = 1;int b = 2;System.out.println(a + " " + b); // 1 2int temp = a;a = b;b = temp;System.out.println(a + " " + b); // 2 1

类型转换

// Widening// byte<short<int<long<float<doubleint i = 10;long l = i;               // 10// Narrowing double d = 10.02;long l = (long)d;         // 10String.valueOf(10);       // "10"Integer.parseInt("10");   // 10Double.parseDouble("10"); // 10.0

条件句

int j = 10;if (j == 10) {  System.out.println("I get printed");} else if (j > 10) {  System.out.println("I don't");} else {  System.out.println("I also don't");}

请参阅:条件

用户输入

Scanner in = new Scanner(System.in);String str = in.nextLine();System.out.println(str);int num = in.nextInt();System.out.println(num);

Java 字符串

基本的

String str1 = "value"; String str2 = new String("value");String str3 = String.valueOf(123);

级联

String s = 3 + "str" + 3;     // 3str3String s = 3 + 3 + "str";     // 6strString s = "3" + 3 + "str";   // 33strString s = "3" + "3" + "23";  // 3323String s = "" + 3 + 3 + "23"; // 3323String s = 3 + 3 + 23;        // 29

字符串生成器

StringBuilder sb = new StringBuilder(10);

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐|   |   |   |   |   |   |   |   |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.append("QuickRef");

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| Q | u | i | c | k | R | e | f |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.delete(5, 9);

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| Q | u | i | c | k |   |   |   |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.insert(0, "我的");

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| M | y |   | Q | u | i | c | k |   |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

sb.append("!");

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐| M | y |   | Q | u | i | c | k | ! |└───┴───┴───┴───┴───┴───┴───┴───┴───┘0   1   2   3   4   5   6   7   8   9

比较

String s1 = new String("QuickRef"); String s2 = new String("QuickRef"); s1 == s2          // falses1.equals(s2)     // true"AB".equalsIgnoreCase("ab")  // true

操作

String str = "Abcd";str.toUpperCase();     // ABCDstr.toLowerCase();     // abcdstr.concat("#");       // Abcd#str.replace("b", "-"); // A-cd"  abc ".trim();       // abc"ab".toCharArray();    // {'a', 'b'}

信息

String str = "abcd";str.charAt(2);       // cstr.indexOf("a")     // 0str.indexOf("z")     // -1str.length();        // 4str.toString();      // abcdstr.substring(2);    // cdstr.substring(2,3);  // cstr.contains("c");   // truestr.endsWith("d");   // truestr.startsWith("a"); // truestr.isEmpty();       // false

不可变

String str = "hello";str.concat("world");// Outputs: helloSystem.out.println(str);

String str = "hello";String concat = str.concat("world");// Outputs: helloworldSystem.out.println(concat);

一旦创建不能修改,任何修改都会创建一个新的String

Java 数组

声明

int[] a1;int[] a2 = {1, 2, 3};int[] a3 = new int[]{1, 2, 3};int[] a4 = new int[3];a4[0] = 1;a4[2] = 2;a4[3] = 3;

调整

int[] a = {1, 2, 3};System.out.println(a[0]); // 1a[0] = 9;System.out.println(a[0]); // 9System.out.println(a.length); // 3

循环(读取和修改)

int[] arr = {1, 2, 3};for (int i=0; i < arr.length; i++) {    arr[i] = arr[i] * 2;    System.out.print(arr[i] + " ");}// Outputs: 2 4 6

循环(读取)

String[] arr = {"a", "b", "c"};for (int a: arr) {    System.out.print(a + " ");}// Outputs: a b c 

多维数组

int[][] matrix = { {1, 2, 3}, {4, 5} };int x = matrix[1][0];  // 4// [[1, 2, 3], [4, 5]]Arrays.deepToString(matrix)for (int i = 0; i < a.length; ++i) {  for(int j = 0; j < a[i].length; ++j) {    System.out.println(a[i][j]);  }}// Outputs: 1 2 3 4 5 6 7 

类型

char[] chars = {'b', 'a', 'c'};Arrays.sort(chars);// [a, b, c]Arrays.toString(chars);

Java 条件

运算符

if-else 语句

int k = 15;if (k > 20) {  System.out.println(1);} else if (k > 10) {  System.out.println(2);} else {  System.out.println(3);}

switch 语句

int month = 3;String str;switch (month) {  case 1:    str = "January";    break;  case 2:    str = "February";    break;  case 3:    str = "March";    break;  default:    str = "Some other month";    break;}// Outputs: Result MarchSystem.out.println("Result " + str);

三元运算符

int a = 10;int b = 20;int max = (a > b) ? a : b;// Outputs: 20System.out.println(max);

Java 循环

For循环

for (int i = 0; i < 10; i++) {  System.out.print(i);}// Outputs: 0123456789

for (int i = 0,j = 0; i < 3; i++,j--) {  System.out.print(j + "|" + i + " ");}// Outputs: 0|0 -1|1 -2|2

For-each 循环

int[] numbers = {1,2,3,4,5};for (int number: numbers) {  System.out.print(number);}// Outputs: 12345

用于循环数组或列表

While 循环

int count = 0;while (count < 5) {  System.out.print(count);  count++;}// Outputs: 01234

Do While 循环

int count = 0;do{  System.out.print(count);  count++;} while (count < 5);// Outputs: 01234

Continue

for (int i = 0; i < 5; i++) {  if (i == 3) {    continue;  }  System.out.print(i);}// Outputs: 01245

Break

for (int i = 0; i < 5; i++) {  System.out.print(i);  if (i == 3) {    break;  }}// Outputs: 0123

Java 集合框架

Java 集合

集合接口可组织可排序线程安全可复制可为空
ArrayListListYNNYY
VectorListYNYYY
LinkedListList, DequeYNNYY
HashSetSetNNNNOne null
LinkedHashSetSetYNNNOne null
TreeSetSetYYNNN
HashMapMapNNNN (key)One null (key)
HashTableMapNNYN (key)N (key)
LinkedHashMapMapYNNN (key)One null (key)
TreeMapMapYYNN (key)N (key)
ArrayDequeDequeYNNYN
PriorityQueueQueueYNNYN

数组列表

List<Integer> nums = new ArrayList<>();// Addingnums.add(2);nums.add(5);nums.add(8);// RetrievingSystem.out.println(nums.get(0));// Indexed for loop iterationfor (int i = 0; i < nums.size(); i++) {    System.out.println(nums.get(i));}nums.remove(nums.size() - 1);nums.remove(0); // VERY slowfor (Integer value : nums) {    System.out.println(value);}

哈希表

Map<Integer, String> m = new HashMap<>();m.put(5, "Five");m.put(8, "Eight");m.put(6, "Six");m.put(4, "Four");m.put(2, "Two");// RetrievingSystem.out.println(m.get(6));// Lambda forEachm.forEach((key, value) -> {    String msg = key + ": " + value;    System.out.println(msg);});

哈希集

Set<String> set = new HashSet<>();if (set.isEmpty()) {    System.out.println("Empty!");}set.add("dog");set.add("cat");set.add("mouse");set.add("snake");set.add("bear");if (set.contains("cat")) {    System.out.println("Contains cat");}set.remove("cat");for (String element : set) {    System.out.println(element);}

数组Deque

Deque<String> a = new ArrayDeque<>();// Using add()a.add("Dog");// Using addFirst()a.addFirst("Cat");// Using addLast()a.addLast("Horse");// [Cat, Dog, Horse]System.out.println(a);// Access elementSystem.out.println(a.peek());// Remove elementSystem.out.println(a.pop());

杂项

访问修饰符

修饰符子类全局
publicYYYY
protectedYYYN
no modifierYYNN
privateYNNN

常用表达

String text = "I am learning Java";// Removing All Whitespacetext.replaceAll("s+", "");// Splitting a Stringtext.split("|");text.split(Pattern.quote("|"));

请参阅:Java 中的正则表达式

文本注释

// I am a single line comment! /*And I am a multi-line comment!*//** * This   * is   * documentation   * comment  */

关键词

  • abstract
  • continue
  • for
  • new
  • switch
  • assert
  • default
  • goto
  • package
  • synchronized
  • boolean
  • do
  • if
  • private
  • this
  • break
  • double
  • implements
  • protected
  • throw
  • byte
  • else
  • import
  • public
  • throws
  • case
  • enum
  • instanceof
  • return
  • transient
  • catch
  • extends
  • int
  • short
  • try
  • char
  • final
  • interface
  • static
  • void
  • class
  • finally
  • long
  • strictfp
  • volatile
  • const
  • float
  • native
  • super
  • while

数学方法

方法描述
Math.max(a,b)a 和 b 的最大值
Math.min(a,b)a 和 b 的最小值
Math.abs(a)绝对值a
Math.sqrt(a)a 的平方根
Math.pow(a,b)a的b次方
Math.round(a)最接近的整数
Math.sin(ang)正弦
Math.cos(ang)ang的余弦
Math.tan(ang)ang 的切线
Math.asin(ang)ang 的反正弦
Math.log(a)a 的自然对数
Math.toDegrees(rad)角度 rad
Math.toRadians(deg)以弧度为单位的角度度

try/catch/finally

try {  // something} catch (Exception e) {  e.printStackTrace();} finally {  System.out.println("always printed");}



正则表达式定义了字符串的模式。

正则表达式可以用来搜索、编辑或处理文本。

正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。

Java正则表达式和Perl的是最为相似的。

java.util.regex包主要包括以下三个类:

  • Pattern类:

    pattern对象是一个正则表达式的编译表示。Pattern类没有公共构造方法。要创建一个Pattern对象,你必须首先调用其公共静态编译方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。

  • Matcher类:

    Matcher对象是对输入字符串进行解释和匹配操作的引擎。与Pattern类一样,Matcher也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个Matcher对象。

  • PatternSyntaxException:

    PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。


捕获组

捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。

例如,正则表达式(dog) 创建了单一分组,组里包含"d","o",和"g"。

捕获组是通过从左至右计算其开括号来编号。例如,在表达式((A)(B(C))),有四个这样的组:

  • ((A)(B(C)))
  • (A)
  • (B(C))
  • (C)

可以通过调用matcher对象的groupCount方法来查看表达式有多少个分组。groupCount方法返回一个int值,表示matcher对象当前有多个捕获组。

还有一个特殊的组(组0),它总是代表整个表达式。该组不包括在groupCount的返回值中。

实例

下面的例子说明如何从一个给定的字符串中找到数字串:

import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexMatches{    public static void main( String args[] ){      // 按指定模式在字符串查找      String line = "This order was placed for QT3000! OK?";      String pattern = "(.*)(d+)(.*)";      // 创建 Pattern 对象      Pattern r = Pattern.compile(pattern);      // 现在创建 matcher 对象      Matcher m = r.matcher(line);      if (m.find( )) {         System.out.println("Found value: " + m.group(0) );         System.out.println("Found value: " + m.group(1) );         System.out.println("Found value: " + m.group(2) );      } else {         System.out.println("NO MATCH");      }   }}

以上实例编译运行结果如下:

Found value: This order was placed for QT3000! OK?Found value: This order was placed for QT300Found value: 0

正则表达式语法

字符

说明

将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,"n"匹配字符"n"。" "匹配换行符。序列""匹配"","("匹配"("。

^

匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与" "或" "之后的位置匹配。

$

匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与" "或" "之前的位置匹配。

*

零次或多次匹配前面的字符或子表达式。例如,zo* 匹配"z"和"zoo"。* 等效于 {0,}。

+

一次或多次匹配前面的字符或子表达式。例如,"zo+"与"zo"和"zoo"匹配,但与"z"不匹配。+ 等效于 {1,}。

?

零次或一次匹配前面的字符或子表达式。例如,"do(es)?"匹配"do"或"does"中的"do"。? 等效于 {0,1}。

{n}

n 是非负整数。正好匹配 n 次。例如,"o{2}"与"Bob"中的"o"不匹配,但与"food"中的两个"o"匹配。

{n,}

n 是非负整数。至少匹配 n 次。例如,"o{2,}"不匹配"Bob"中的"o",而匹配"foooood"中的所有 o。"o{1,}"等效于"o+"。"o{0,}"等效于"o*"。

{n,m}

Mn 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,"o{1,3}"匹配"fooooood"中的头三个 o。'o{0,1}' 等效于 'o?'。注意:您不能将空格插入逗号和数字之间。

?

当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是"非贪心的"。"非贪心的"模式匹配搜索到的、尽可能短的字符串,而默认的"贪心的"模式匹配搜索到的、尽可能长的字符串。例如,在字符串"oooo"中,"o+?"只匹配单个"o",而"o+"匹配所有"o"。

.

匹配除" "之外的任何单个字符。若要匹配包括" "在内的任意字符,请使用诸如"[sS]"之类的模式。

(pattern)

匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果"匹配"集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用"("或者")"。

(?:pattern)

匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符 (|) 组合模式部件的情况很有用。例如,'industr(?:y|ies) 是比 'industry|industries' 更经济的表达式。

(?=pattern)

执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,'Windows (?=95|98|NT|2000)' 匹配"Windows 2000"中的"Windows",但不匹配"Windows 3.1"中的"Windows"。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。

(?!pattern)

执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,'Windows (?!95|98|NT|2000)' 匹配"Windows 3.1"中的 "Windows",但不匹配"Windows 2000"中的"Windows"。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。

x|y

匹配 xy。例如,'z|food' 匹配"z"或"food"。'(z|f)ood' 匹配"zood"或"food"。

[xyz]

字符集。匹配包含的任一字符。例如,"[abc]"匹配"plain"中的"a"。

[^xyz]

反向字符集。匹配未包含的任何字符。例如,"[^abc]"匹配"plain"中"p","l","i","n"。

[a-z]

字符范围。匹配指定范围内的任何字符。例如,"[a-z]"匹配"a"到"z"范围内的任何小写字母。

[^a-z]

反向范围字符。匹配不在指定的范围内的任何字符。例如,"[^a-z]"匹配任何不在"a"到"z"范围内的任何字符。



匹配一个字边界,即字与空格间的位置。例如,"er"匹配"never"中的"er",但不匹配"verb"中的"er"。

B

非字边界匹配。"erB"匹配"verb"中的"er",但不匹配"never"中的"er"。

cx

匹配 x 指示的控制字符。例如,cM 匹配 Control-M 或回车符。x 的值必须在 A-Z 或 a-z 之间。如果不是这样,则假定 c 就是"c"字符本身。

d

数字字符匹配。等效于 [0-9]。

D

非数字字符匹配。等效于 [^0-9]。

f

换页符匹配。等效于 x0c 和 cL。

换行符匹配。等效于 x0a 和 cJ。

匹配一个回车符。等效于 x0d 和 cM。

s

匹配任何空白字符,包括空格、制表符、换页符等。与 [ f v] 等效。

S

匹配任何非空白字符。与 [^ f v] 等效。

制表符匹配。与 x09 和 cI 等效。

v

垂直制表符匹配。与 x0b 和 cK 等效。

w

匹配任何字类字符,包括下划线。与"[A-Za-z0-9_]"等效。

W

与任何非单词字符匹配。与"[^A-Za-z0-9_]"等效。

xn

匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,"x41"匹配"A"。"x041"与"x04"&"1"等效。允许在正则表达式中使用 ASCII 代码。

num

匹配 num,此处的 num 是一个正整数。到捕获匹配的反向引用。例如,"(.)1"匹配两个连续的相同字符。

n

标识一个八进制转义码或反向引用。如果 n 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。

nm

标识一个八进制转义码或反向引用。如果 nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 nm 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符 m。如果两种前面的情况都不存在,则 nm 匹配八进制值 nm,其中 n m 是八进制数字 (0-7)。

nml

n 是八进制数 (0-3),ml 是八进制数 (0-7) 时,匹配八进制转义码 nml

un

匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,u00A9 匹配版权符号 (©)。

Matcher类的方法

索引方法

索引方法提供了有用的索引值,精确表明输入字符串中在哪能找到匹配:

序号 方法及说明
1 public int start()
返回以前匹配的初始索引。
2 public int start(int group)
 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
3 public int end()
返回最后匹配字符之后的偏移量。
4 public int end(int group)
返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。

研究方法

研究方法用来检查输入字符串并返回一个布尔值,表示是否找到该模式:

序号 方法及说明
1 public boolean lookingAt()
 尝试将从区域开头开始的输入序列与该模式匹配。
2 public boolean find()
尝试查找与该模式匹配的输入序列的下一个子序列。
3 public boolean find(int start)
重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
4 public boolean matches()
尝试将整个区域与模式匹配。

替换方法

替换方法是替换输入字符串里文本的方法:

序号 方法及说明
1 public Matcher appendReplacement(StringBuffer sb, String replacement)
实现非终端添加和替换步骤。
2 public StringBuffer appendTail(StringBuffer sb)
实现终端添加和替换步骤。
3 public String replaceAll(String replacement)
 替换模式与给定替换字符串相匹配的输入序列的每个子序列。
4 public String replaceFirst(String replacement)
 替换模式与给定替换字符串匹配的输入序列的第一个子序列。
5 public static String quoteReplacement(String s)
返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher类的appendReplacement 方法一个字面字符串一样工作。

start 和end 方法

下面是一个对单词"cat"出现在输入字符串中出现次数进行计数的例子:

import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexMatches{    private static final String REGEX = "cat";    private static final String INPUT =                                    "cat cat cat cattie cat";    public static void main( String args[] ){       Pattern p = Pattern.compile(REGEX);       Matcher m = p.matcher(INPUT); // 获取 matcher 对象       int count = 0;       while(m.find()) {         count++;         System.out.println("Match number "+count);         System.out.println("start(): "+m.start());         System.out.println("end(): "+m.end());      }   }}

以上实例编译运行结果如下:

Match number 1start(): 0end(): 3Match number 2start(): 4end(): 7Match number 3start(): 8end(): 11Match number 4start(): 19end(): 22

可以看到这个例子是使用单词边界,以确保字母 "c" "a" "t" 并非仅是一个较长的词的子串。它也提供了一些关于输入字符串中匹配发生位置的有用信息。

Start方法返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引,end方法最后一个匹配字符的索引加1。

matches 和lookingAt 方法

matches 和lookingAt 方法都用来尝试匹配一个输入序列模式。它们的不同是matches要求整个序列都匹配,而lookingAt 不要求。

这两个方法经常在输入字符串的开始使用。

我们通过下面这个例子,来解释这个功能:

import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexMatches{    private static final String REGEX = "foo";    private static final String INPUT = "fooooooooooooooooo";    private static Pattern pattern;    private static Matcher matcher;    public static void main( String args[] ){       pattern = Pattern.compile(REGEX);       matcher = pattern.matcher(INPUT);       System.out.println("Current REGEX is: "+REGEX);       System.out.println("Current INPUT is: "+INPUT);       System.out.println("lookingAt(): "+matcher.lookingAt());       System.out.println("matches(): "+matcher.matches());   }}

以上实例编译运行结果如下:

Current REGEX is: fooCurrent INPUT is: fooooooooooooooooolookingAt(): truematches(): false

replaceFirst 和replaceAll 方法

replaceFirst 和replaceAll 方法用来替换匹配正则表达式的文本。不同的是,replaceFirst 替换首次匹配,replaceAll 替换所有匹配。

下面的例子来解释这个功能:

import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexMatches{    private static String REGEX = "dog";    private static String INPUT = "The dog says meow. " +                                    "All dogs say meow.";    private static String REPLACE = "cat";    public static void main(String[] args) {       Pattern p = Pattern.compile(REGEX);       // get a matcher object       Matcher m = p.matcher(INPUT);        INPUT = m.replaceAll(REPLACE);       System.out.println(INPUT);   }}

以上实例编译运行结果如下:

The cat says meow. All cats say meow.

appendReplacement 和 appendTail 方法

Matcher 类也提供了appendReplacement 和appendTail 方法用于文本替换:

看下面的例子来解释这个功能:

import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexMatches{   private static String REGEX = "a*b";   private static String INPUT = "aabfooaabfooabfoob";   private static String REPLACE = "-";   public static void main(String[] args) {      Pattern p = Pattern.compile(REGEX);      // 获取 matcher 对象      Matcher m = p.matcher(INPUT);      StringBuffer sb = new StringBuffer();      while(m.find()){         m.appendReplacement(sb,REPLACE);      }      m.appendTail(sb);      System.out.println(sb.toString());   }}

以上实例编译运行结果如下:

-foo-foo-foo-

PatternSyntaxException 类的方法

PatternSyntaxException 是一个非强制异常类,它指示一个正则表达式模式中的语法错误。

PatternSyntaxException 类提供了下面的方法来帮助我们查看发生了什么错误。

序号 方法及说明
1 public String getDescription()
获取错误的描述。
2 public int getIndex()
 获取错误的索引。
3 public String getPattern()
获取错误的正则表达式模式。
4 public String getMessage()
返回多行字符串,包含语法错误及其索引的描述、错误的正则表达式模式和模式中错误索引的可视化指示。


Java String类Java String类


substring() 方法返回字符串的子字符串。可以理解为字符串切片。

语法

public String substring(int beginIndex)

public String substring(int beginIndex, int endIndex)

参数

  • beginIndex -- 起始索引(包括)。

  • endIndex -- 结束索引(不包括)。

注意:这两个参数都为int类型

返回值

子字符串。

实例

public class Test {    public static void main(String args[]) {    	String Str = new String("www.51coolma.cn");    	System.out.print("返回值 :" );    	System.out.println(Str.substring(4) );    	System.out.print("返回值 :" );    	System.out.println(Str.substring(4, 11) );	}}

以上程序执行结果为:

返回值 :51coolma.cn返回值 :51coolma

Java String类Java String类


JavaFX教程 - JavaFX区域图


区域图是另一种类型的双轴图表。

import javafx.application.Application;import javafx.scene.Group;import javafx.scene.Scene;import javafx.scene.chart.NumberAxis;import javafx.scene.chart.StackedAreaChart;import javafx.scene.chart.XYChart;import javafx.stage.Stage;public class Main extends Application {  public static void main(String[] args) {    launch(args);  }  @Override    public void start(Stage primaryStage) {        Group root = new Group();        final NumberAxis xAxis = new NumberAxis(1, 12, 1);                 final NumberAxis yAxis = new NumberAxis();        final StackedAreaChart<Number,Number> stackedAreaChart = new StackedAreaChart<Number,Number>(xAxis,yAxis);        final XYChart.Series<Number,Number> series1 = new XYChart.Series<Number,Number>();                  xAxis.setLabel("Month");        yAxis.setLabel("Value");         stackedAreaChart.setTitle("StackedAreaChart");        series1.setName("XYChart.Series 1");                  series1.getData().add(new XYChart.Data(1, 100));        series1.getData().add(new XYChart.Data(2, 200));        series1.getData().add(new XYChart.Data(10, 150));         XYChart.Series<Number,Number> series2 = new XYChart.Series();        series2.setName("XYChart.Series 2");                  series2.getData().add(new XYChart.Data(1, 50));        series2.getData().add(new XYChart.Data(2, 200));        series2.getData().add(new XYChart.Data(10, 260));                 stackedAreaChart.getData().addAll(series1, series2);                      root.getChildren().addAll(stackedAreaChart);          primaryStage.setScene(new Scene(root, 500, 400));        primaryStage.show();    }}

上面的代码生成以下结果。

null

创建区域图

要创建区域图,请使用AreaChart对象和使用XYChart.Series类创建一个或多个数据系列,并将数据分配给图表。

import javafx.application.Application;import javafx.scene.Group;import javafx.scene.Scene;import javafx.scene.chart.AreaChart;import javafx.scene.chart.CategoryAxis;import javafx.scene.chart.NumberAxis;import javafx.scene.chart.XYChart;import javafx.stage.Stage; public class Main extends Application {     public static void main(String[] args) {        launch(args);    }         @Override    public void start(Stage primaryStage) {        primaryStage.setTitle("");        Group root = new Group();                 final CategoryAxis xAxis = new CategoryAxis();        final NumberAxis yAxis = new NumberAxis();                 xAxis.setLabel("Month");        yAxis.setLabel("Value");                 final AreaChart<String,Number> areaChart = new AreaChart<String,Number>(xAxis,yAxis);         areaChart.setTitle("AreaChart");        XYChart.Series series = new XYChart.Series();        series.setName("XYChart.Series");                 series.getData().add(new XYChart.Data("January", 100));        series.getData().add(new XYChart.Data("February", 200));        series.getData().add(new XYChart.Data("March", 50));                 areaChart.getData().add(series);                     root.getChildren().add(areaChart);         primaryStage.setScene(new Scene(root, 500, 400));        primaryStage.show();    }     }

上面的代码生成以下结果。

null

向AreaChart添加更多系列

/* * Copyright (c) 2011, 2012 Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * *  - Redistributions of source code must retain the above copyright *    notice, this list of conditions and the following disclaimer. *  - Redistributions in binary form must reproduce the above copyright *    notice, this list of conditions and the following disclaimer in *    the documentation and/or other materials provided with the distribution. *  - Neither the name of Oracle nor the names of its *    contributors may be used to endorse or promote products derived *    from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *///package areachartsample;import javafx.application.Application;import javafx.scene.Scene;import javafx.scene.chart.AreaChart;import javafx.scene.chart.NumberAxis;import javafx.scene.chart.XYChart;import javafx.stage.Stage;public class Main extends Application {    @Override public void start(Stage stage) {        stage.setTitle("Area Chart Sample");        final NumberAxis xAxis = new NumberAxis(1, 30, 1);        final NumberAxis yAxis = new NumberAxis(-5, 27, 5);        final AreaChart<Number,Number> ac =             new AreaChart<Number,Number>(xAxis,yAxis);        xAxis.setForceZeroInRange(true);                ac.setTitle("Temperature Monitoring (in Degrees C)");               XYChart.Series series1 = new XYChart.Series();        series1.setName("March");        series1.getData().add(new XYChart.Data(0, -2));        series1.getData().add(new XYChart.Data(3, -4));        series1.getData().add(new XYChart.Data(6, 0));        series1.getData().add(new XYChart.Data(9, 5));                XYChart.Series series2 = new XYChart.Series();        series2.setName("April");        series2.getData().add(new XYChart.Data(0, 4));        series2.getData().add(new XYChart.Data(3, 10));        series2.getData().add(new XYChart.Data(6, 15));        series2.getData().add(new XYChart.Data(9, 8));                XYChart.Series series3 = new XYChart.Series();        series3.setName("May");        series3.getData().add(new XYChart.Data(0, 20));        series3.getData().add(new XYChart.Data(3, 15));        series3.getData().add(new XYChart.Data(6, 13));        series3.getData().add(new XYChart.Data(9, 12));                Scene scene  = new Scene(ac,800,600);        //scene.getStylesheets().add("areachartsample/Chart.css");        ac.setHorizontalZeroLineVisible(true);        ac.getData().addAll(series1, series2, series3);        stage.setScene(scene);        stage.show();    }    public static void main(String[] args) {        launch(args);    }}

上面的代码生成以下结果。

null

创建堆叠区域图

import javafx.application.Application;import javafx.scene.Scene;import javafx.scene.chart.NumberAxis;import javafx.scene.chart.StackedAreaChart;import javafx.scene.chart.XYChart;import javafx.stage.Stage;public class Main extends Application {    final NumberAxis xAxis = new NumberAxis(1, 10, 1);    final NumberAxis yAxis = new NumberAxis();    final StackedAreaChart<Number, Number> sac =        new StackedAreaChart<>(xAxis, yAxis);     @Override    public void start(Stage stage) {        sac.setTitle("title");        XYChart.Series<Number, Number> seriesApril =            new XYChart.Series<>();        seriesApril.setName("April");        seriesApril.getData().add(new XYChart.Data(1, 4));        seriesApril.getData().add(new XYChart.Data(3, 10));        seriesApril.getData().add(new XYChart.Data(6, 15));        XYChart.Series<Number, Number> seriesMay =            new XYChart.Series<>();        seriesMay.setName("May");        seriesMay.getData().add(new XYChart.Data(1, 23));        seriesMay.getData().add(new XYChart.Data(7, 26));        seriesMay.getData().add(new XYChart.Data(10, 26));        Scene scene = new Scene(sac, 800, 600);        sac.getData().addAll(seriesApril, seriesMay);        stage.setScene(scene);        stage.show();    }     public static void main(String[] args) {        launch(args);    }}

上面的代码生成以下结果。




Java 8 Stream

Java 8 新特性Java 8 新特性


Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

+--------------------+       +------+   +------+   +---+   +-------+| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|+--------------------+       +------+   +------+   +---+   +-------+

以上的流程转换为 Java 代码为:

List<Integer> transactionsIds = widgets.stream()             .filter(b -> b.getColor() == RED)             .sorted((x,y) -> x.getWeight() - y.getWeight())             .mapToInt(Widget::getWeight)             .sum();

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

生成流

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。
  • parallelStream() − 为集合创建并行流。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

Random random = new Random();random.ints().limit(10).forEach(System.out::println);

map

map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); // 获取对应的平方数 List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); // 获取空字符串的数量 long count = strings.stream().filter(string -> string.isEmpty()).count();

limit

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

Random random = new Random(); random.ints().limit(10).forEach(System.out::println);

sorted

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println);

并行(parallel)程序

parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); // 获取空字符串的数量 long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

我们可以很容易的在顺序运行和并行直接切换。

Collectors

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("筛选列表: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString);

统计

另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("所有数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage());

Stream 完整实例

将以下代码放入 Java8Tester.java 文件中:

Java8Tester.java 文件

将以下代码放入 Java8Tester.java 文件中:

import java.util.ArrayList;import java.util.Arrays;import java.util.IntSummaryStatistics;import java.util.List;import java.util.Random;import java.util.stream.Collectors;import java.util.Map; public class Java8Tester {   public static void main(String args[]){      System.out.println("使用 Java 7: ");              // 计算空字符串      List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");      System.out.println("列表: " +strings);      long count = getCountEmptyStringUsingJava7(strings);              System.out.println("空字符数量为: " + count);      count = getCountLength3UsingJava7(strings);              System.out.println("字符串长度为 3 的数量为: " + count);              // 删除空字符串      List<String> filtered = deleteEmptyStringsUsingJava7(strings);      System.out.println("筛选后的列表: " + filtered);              // 删除空字符串,并使用逗号把它们合并起来      String mergedString = getMergedStringUsingJava7(strings,", ");      System.out.println("合并字符串: " + mergedString);      List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);              // 获取列表元素平方数      List<Integer> squaresList = getSquares(numbers);      System.out.println("平方数列表: " + squaresList);      List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);              System.out.println("列表: " +integers);      System.out.println("列表中最大的数 : " + getMax(integers));      System.out.println("列表中最小的数 : " + getMin(integers));      System.out.println("所有数之和 : " + getSum(integers));      System.out.println("平均数 : " + getAverage(integers));      System.out.println("随机数: ");              // 输出10个随机数      Random random = new Random();              for(int i=0; i < 10; i++){         System.out.println(random.nextInt());      }              System.out.println("使用 Java 8: ");      System.out.println("列表: " +strings);              count = strings.stream().filter(string->string.isEmpty()).count();      System.out.println("空字符串数量为: " + count);              count = strings.stream().filter(string -> string.length() == 3).count();      System.out.println("字符串长度为 3 的数量为: " + count);              filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());      System.out.println("筛选后的列表: " + filtered);              mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));      System.out.println("合并字符串: " + mergedString);              squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());      System.out.println("Squares List: " + squaresList);      System.out.println("列表: " +integers);              IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();              System.out.println("列表中最大的数 : " + stats.getMax());      System.out.println("列表中最小的数 : " + stats.getMin());      System.out.println("所有数之和 : " + stats.getSum());      System.out.println("平均数 : " + stats.getAverage());      System.out.println("随机数: ");              random.ints().limit(10).sorted().forEach(System.out::println);              // 并行处理      count = strings.parallelStream().filter(string -> string.isEmpty()).count();      System.out.println("空字符串的数量为: " + count);   }       private static int getCountEmptyStringUsingJava7(List<String> strings){      int count = 0;              for(String string: strings){                 if(string.isEmpty()){            count++;         }      }      return count;   }       private static int getCountLength3UsingJava7(List<String> strings){      int count = 0;              for(String string: strings){                 if(string.length() == 3){            count++;         }      }      return count;   }       private static List<String> deleteEmptyStringsUsingJava7(List<String> strings){      List<String> filteredList = new ArrayList<String>();              for(String string: strings){                 if(!string.isEmpty()){             filteredList.add(string);         }      }      return filteredList;   }       private static String getMergedStringUsingJava7(List<String> strings, String separator){      StringBuilder stringBuilder = new StringBuilder();              for(String string: strings){                 if(!string.isEmpty()){            stringBuilder.append(string);            stringBuilder.append(separator);         }      }      String mergedString = stringBuilder.toString();      return mergedString.substring(0, mergedString.length()-2);   }       private static List<Integer> getSquares(List<Integer> numbers){      List<Integer> squaresList = new ArrayList<Integer>();              for(Integer number: numbers){         Integer square = new Integer(number.intValue() * number.intValue());                     if(!squaresList.contains(square)){            squaresList.add(square);         }      }      return squaresList;   }       private static int getMax(List<Integer> numbers){      int max = numbers.get(0);              for(int i=1;i < numbers.size();i++){                 Integer number = numbers.get(i);                     if(number.intValue() > max){            max = number.intValue();         }      }      return max;   }       private static int getMin(List<Integer> numbers){      int min = numbers.get(0);              for(int i=1;i < numbers.size();i++){         Integer number = numbers.get(i);                 if(number.intValue() < min){            min = number.intValue();         }      }      return min;   }       private static int getSum(List numbers){      int sum = (int)(numbers.get(0));              for(int i=1;i < numbers.size();i++){         sum += (int)numbers.get(i);      }      return sum;   }       private static int getAverage(List<Integer> numbers){      return getSum(numbers) / numbers.size();   }}

执行以上脚本,输出结果为:

$ javac Java8Tester.java $ java Java8Tester使用 Java 7: 列表: [abc, , bc, efg, abcd, , jkl]空字符数量为: 2字符串长度为 3 的数量为: 3筛选后的列表: [abc, bc, efg, abcd, jkl]合并字符串: abc, bc, efg, abcd, jkl平方数列表: [9, 4, 49, 25]列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]列表中最大的数 : 19列表中最小的数 : 1所有数之和 : 85平均数 : 9随机数: -393170844-963842252447036679-1043163142-881079698221586850-1101570113576190039-10451845781647841045使用 Java 8: 列表: [abc, , bc, efg, abcd, , jkl]空字符串数量为: 2字符串长度为 3 的数量为: 3筛选后的列表: [abc, bc, efg, abcd, jkl]合并字符串: abc, bc, efg, abcd, jklSquares List: [9, 4, 49, 25]列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]列表中最大的数 : 19列表中最小的数 : 1所有数之和 : 85平均数 : 9.444444444444445随机数: -1743813696-1301974944-1299484995-7799811861365449025557920231243315896126492084914720771351706423674空字符串的数量为: 2



Java面向对象设计 - Java Object.Clone方法


Java不提供克隆(复制)对象的自动机制。

克隆对象意味着逐位复制对象的内容。

要支持克隆操作,请在类中实现clone()方法。

Object类中的clone()方法的声明如下:

protected  Object clone()  throws   CloneNotSupportedException

clone()方法声明为protected。因此,我们不能从客户端代码调用它。以下代码无效:

Object obj  = new Object();Object clone = obj.clone(); // Error. Cannot  access protected clone() method

我们需要在类中声明clone()方法public克隆类的对象。

它的返回类型是Object。这意味着您将需要转换clone()方法的返回值。

假设MyClass是可克隆的。克隆代码将如下所示:

MyClass mc  = new MyClass();MyClass clone = (MyClass)mc.clone(); // Need to use  a  cast

Object类中的clone()方法会抛出CloneNotSupportedException。

要调用clone()方法,我们需要将调用放在try-catch块中,或者重新抛出异常。


例子

以下代码显示了如何实现克隆方法。

class MyClass implements Cloneable {  private double value;  public MyClass(double value) {    this.value = value;  }  public void setValue(double value) {    this.value = value;  }  public double getValue() {    return this.value;  }  public Object clone() {    MyClass copy = null;    try {      copy = (MyClass) super.clone();    } catch (CloneNotSupportedException e) {      e.printStackTrace();    }    return copy;  }}public class Main {  public static void main(String[] args) {    MyClass dh = new MyClass(100.00);    MyClass dhClone = (MyClass) dh.clone();    System.out.println("Original:" + dh.getValue());    System.out.println("Clone :" + dhClone.getValue());    dh.setValue(200.00);    dhClone.setValue(400.00);    System.out.println("Original:" + dh.getValue());    System.out.println("Clone :" + dhClone.getValue());  }}

上面的代码生成以下结果。

Original:100.0Clone :100.0Original:200.0Clone :400.0

例2

以下代码不从clone方法返回对象类型,该方法仅在Java 5或更高版本中编译。

class MyClass  implements Cloneable  {    public MyClass clone()  {        Object copy  = null;       return  (MyClass)copy;    }}

下面的代码展示了如何做浅层克隆。

class MyClass implements Cloneable {  private double value;  public MyClass(double value) {    this.value = value;  }  public void setValue(double value) {    this.value = value;  }  public double getValue() {    return this.value;  }  public Object clone() {    MyClass copy = null;    try {      copy = (MyClass) super.clone();    } catch (CloneNotSupportedException e) {      e.printStackTrace();    }    return copy;  }}class ShallowClone implements Cloneable {  private MyClass holder = new MyClass(0.0);  public ShallowClone(double value) {    this.holder.setValue(value);  }  public void setValue(double value) {    this.holder.setValue(value);  }  public double getValue() {    return this.holder.getValue();  }  public Object clone() {    ShallowClone copy = null;    try {      copy = (ShallowClone) super.clone();    } catch (CloneNotSupportedException e) {      e.printStackTrace();    }    return copy;  }}public class Main {  public static void main(String[] args) {    ShallowClone sc = new ShallowClone(100.00);    ShallowClone scClone = (ShallowClone) sc.clone();    System.out.println("Original:" + sc.getValue());    System.out.println("Clone :" + scClone.getValue());    sc.setValue(200.00);    System.out.println("Original:" + sc.getValue());    System.out.println("Clone :" + scClone.getValue());  }}

上面的代码生成以下结果。

例3

ShallowClone类的clone()方法中的代码与MyClass类的clone()方法相同。

当ShallowClone类使用super.clone()调用Object类的clone()方法时,它会接收自身的浅拷贝。也就是说,它与其克隆共享其实例变量中使用的DoubleHolder对象。

在深层克隆中,您需要克隆对象的所有引用实例变量引用的所有对象。

class MyClass implements Cloneable {  private double value;  public MyClass(double value) {    this.value = value;  }  public void setValue(double value) {    this.value = value;  }  public double getValue() {    return this.value;  }  public Object clone() {    MyClass copy = null;    try {      copy = (MyClass) super.clone();    } catch (CloneNotSupportedException e) {      e.printStackTrace();    }    return copy;  }}class DeepClone implements Cloneable {  private MyClass holder = new MyClass(0.0);  public DeepClone(double value) {    this.holder.setValue(value);  }  public void setValue(double value) {    this.holder.setValue(value);  }  public double getValue() {    return this.holder.getValue();  }  public Object clone() {    DeepClone copy = null;    try {      copy = (DeepClone) super.clone();      copy.holder = (MyClass) this.holder.clone();    } catch (CloneNotSupportedException e) {      e.printStackTrace();    }    return copy;  }}public class Main {  public static void main(String[] args) {    DeepClone sc = new DeepClone(100.00);    DeepClone scClone = (DeepClone) sc.clone();    System.out.println("Original:" + sc.getValue());    System.out.println("Clone :" + scClone.getValue());    sc.setValue(200.00);    System.out.println("Original:" + sc.getValue());    System.out.println("Clone :" + scClone.getValue());  }}

上面的代码生成以下结果。


Java流 - Java创建流


已将新方法添加到Java库以返回流。

我们可以通过以下方式创建流。

  • 从值创建流
  • 从空流创建流
  • 从函数创建流
  • 从数组创建流
  • 从集合创建流
  • 从文件创建流
  • 从其他来源创建流

在以下部分中,我们将学习如何创建流。


从值创建流

我们可以使用Stream接口的()从单个值和多个值创建顺序流。

<T> Stream<T> of(T  t)<T> Stream<T> of(T...values)

从方法签名,我们可以看到第一个of()方法从单个值创建流,而第二个of()方法从不同长度参数创建流

以下代码创建包含单个值的流。

import java.util.stream.Stream;public class Main {  public static void main(String[] args) {    Stream<String> stream  = Stream.of("www.51coolma.cn");    stream.forEach(System.out::println);  }}

上面的代码生成以下结果。


例2

以下代码创建具有四个字符串的流。

import java.util.stream.Stream;public class Main {  public static void main(String[] args) {    Stream<String> stream  = Stream.of("XML", "Java",  "CSS", "SQL");    stream.forEach(System.out::println);  }}

上面的代码生成以下结果。

例3

以下代码从对象数组创建流。

import java.util.stream.Stream;public class Main {  public static void main(String[] args) {    String[] names = { "XML", "Java", "SQL", "CSS" };    Stream<String> stream = Stream.of(names);    stream.forEach(System.out::println);  }}

上面的代码生成以下结果。

流构建器

我们可以使用Stream.Builder<T>创建流。

以下代码创建流构建器。

Stream.Builder<String> builder = Stream.builder();
import java.util.stream.Stream;public class Main {  public static void main(String[] args) {    Stream<String> stream  = Stream.<String>builder()        .add("XML")        .add("Java")        .add("CSS")        .add("SQL")        .build();    stream.forEach(System.out::println);  }}

上面的代码生成以下结果。

IntStream范围

我们可以使用IntStream接口中的以下两种方法从一系列int值创建IntStream。

IntStream range(int start, int end)IntStream rangeClosed(int start, int end).

它们创建一个包含开始和结束之间的有序整数的IntStream。

指定的结束在range()方法中是独占的,并且在rangeClosed()方法中是包含的。

以下代码使用这两种方法创建一个IntStream,它的整数为1,2,3,4和5作为其元素:

import java.util.stream.IntStream;public class Main {  public static void main(String[] args) {    IntStream oneToFive  = IntStream.range(1, 6);    //IntStream oneToFive  = IntStream.rangeClosed(1, 5);    oneToFive.forEach(System.out::println);  }}

像IntStream接口一样,LongStream类还包含range()和rangeClosed()方法,它们接受类型为long的参数,并返回一个LongStream。

上面的代码生成以下结果。

空流

空流没有元素。

我们可以使用empty()静态方法从Stream接口以创建空的顺序流。

import java.util.stream.Stream;public class Main {  public static void main(String[] args) {    Stream<String> stream  = Stream.empty();    stream.forEach(System.out::println);  }}

IntStream,LongStream和DoubleStream接口还包含一个empty()静态方法来创建一个空的基本类型流。

以下代码创建一个空的整数流。

IntStream numbers = IntStream.empty();

Java教程 - Java continue语句


continue 语句强制循环的早期迭代。在 while do-while 循环中, continue 语句使控制转移到条件语句表达式控制循环。在 for 循环中,控制首先进行迭代for语句的部分,然后到条件表达式。

Java continue语句

continue 语句的语法

continue;

或者

continue labelName;

以下代码显示如何使用continue语句。

 public class Main {  public static void main(String[] argv) {    for (int i = 0; i < 10; i++) {      System.out.print(i + " ");      if (i % 2 == 0)        continue;      System.out.println("");    }  }}

上面的代码生成以下结果。

例子

continue 可以指定 label 来描述要继续的封闭循环。

 public class Main {  public static void main(String args[]) {    outer: for (int i = 0; i < 10; i++) {      for (int j = 0; j < 10; j++) {        if (j > i) {          System.out.println();          continue outer;        }        System.out.print(" " + (i * j));      }    }    System.out.println();  }}

这里是这个程序的输出:

例2

下面的代码显示了如何使用标签while循环。

public class Main {  public static void main(String[] args) {    int i = 0;    outer: while (true) {      System.out.println("Outer while loop");      while (true) {        i++;        System.out.println("i = " + i);        if (i == 1) {          System.out.println("continue");          continue;        }        if (i == 3) {          System.out.println("continue outer");          continue outer;        }        if (i == 5) {          System.out.println("break");          break;        }        if (i == 7) {          System.out.println("break outer");          break outer;        }      }    }  }}

上面的代码生成以下结果。

EXAMPLE_2__4174CE9BE4A9482D79EA

例3

下面的代码显示了如何使用continue语句和标签计算Primes。

public class Main {  public static void main(String[] args) {    int nValues = 50;    OuterLoop: for (int i = 2; i <= nValues; i++) {      for (int j = 2; j < i; j++) {        if (i % j == 0) {          continue OuterLoop;        }      }      System.out.println(i);    }  }}

上面的代码生成以下结果。

例4

下面的代码显示了如何使用Labeled continue语句来计算阶乘数。

public class Main {  public static void main(String[] args) {    int limit = 20;    int factorial = 1;    OuterLoop: for (int i = 1; i <= limit; i++) {      factorial = 1;      for (int j = 2; j <= i; j++) {        if (i > 10 && i % 2 == 1) {          continue OuterLoop;        }        factorial *= j;      }      System.out.println(i + "! is " + factorial);    }  }}

上面的代码生成以下结果。




Java教程 - Java For语句


Java for 循环语句提供了一种强大的写循环语句的方法。

for循环的最简单形式如下所示:

  for(initialization; condition; iteration)     statement; 

Java for循环语句有三个部分:

  • 初始化将循环控制变量设置为初始值。
  • condition 是测试循环控制变量的布尔表达式。 如果condition为true,for循环继续迭代。 如果condition为false,循环终止。
  • 迭代决定了每次循环迭代时循环控制变量的变化。

这里是一个简短的程序,说明了for循环。 i 是循环控制变量, i 被初始化为零初始化。在每次迭代的开始,条件测试 x 10 。如果该测试的结果为真,则执行 println()语句,然后执行循环的迭代部分。此过程继续,直到条件测试为 false

public class Main {  public static void main(String args[]) {    int i;    for (i = 0; i < 10; i = i + 1)      System.out.println("This is i: " + i);  }}

此程序生成以下输出:

例子

下面的代码再次从上面写入代码逻辑,但是循环反转:

 public class Main {  public static void main(String args[]) {    for (int n = 10; n > 0; n--)      System.out.println("n:" + n);  }}  ]]>

输出:

例2

这里是一个程序,使用 for 循环语句测试素数。

 public class Main {  public static void main(String args[]) {    int num;    boolean isPrime = true;    num = 50;    for (int i = 2; i <= num / 2; i++) {      if ((num % i) == 0) {        isPrime = false;        break;      }    }    if (isPrime)      System.out.println("Prime");    else      System.out.println("Not Prime");  }}

输出:


例3

Java允许两个或多个变量控制 for 循环。并且可以在初始化和迭代部分中包含多个语句 for 循环。每个语句通过逗号与下一个语句分隔。这里是一个例子:

 public class Main {  public static void main(String args[]) {    for (int a = 1, b = 4; a < b; a++, b--) {      System.out.println("a = " + a);      System.out.println("b = " + b);    }  }}

程序生成以下输出:

例4

for 的三个部分可用于任何目的和部分 for loop可以为空。

 public class Main {  public static void main(String args[]) {    int i = 0;    boolean done = false;    for (; !done;) {      System.out.println("i is " + i);      if (i == 9)        done = true;      i++;    }  }}

输出:

例5

for 循环可以嵌套生成强大的逻辑,例如,我们可以使用嵌套 for 循环来迭代一个二维数组。例如,这里是一个嵌套for循环的程序:

 public class Main {  public static void main(String args[]) {    for (int i = 0; i < 10; i++) {      for (int j = i; j < 10; j++)        System.out.print(".");      System.out.println();    }  }}

此程序产生的输出如下所示:

Java for each循环

for each循环对一个序列中的元素进行迭代,而不使用循环计数器。

for each循环的的语法是:

for (type variable_name:array){       }

类型必须与数组类型兼容。

以下代码显示了如何为每个循环使用。

public class Main {  public static void main(String args[]) {    String[] arr = new String[]{"www.51coolma.cn","a","b","c"};    for(String s:arr){      System.out.println(s);    }  }}

输出:

Java for each循环输出

例6

下面的代码使用 for-each 样式循环来迭代一个二维数组。

public class Main {  public static void main(String args[]) {    int sum = 0;    int nums[][] = new int[3][5];    for (int i = 0; i < 3; i++){      for (int j = 0; j < 5; j++){        nums[i][j] = (i + 1) * (j + 1);      }      }    // use for-each for to display and sum the values    for (int x[] : nums) {      for (int y : x) {        System.out.println("Value is: " + y);        sum += y;      }    }    System.out.println("Summation: " + sum);  }}

此程序的输出如下所示:


例7

for-each 样式循环在搜索数组中的元素时非常有用。

public class Main {  public static void main(String args[]) {    int nums[] = { 6, 8, 3, 7, 5, 6, 1, 4 };    int val = 5;    boolean found = false;    // use for-each style for to search nums for val    for (int x : nums) {      if (x == val) {        found = true;        break;      }    }    if (found)      System.out.println("Value found!");  }}

上面的代码生成以下结果。


Java 实例Java 实例

以下实例演示了如何遍历从Collection接口延伸出的List、Set和以键值对形式作存储的Map类型的集合,以下我们分别使用了普通for,增强型的 for ,iterator 等方式来遍历集合:

List与Set类型集合的遍历

/* author by w3cschool.cn Main.java */import java.util.ArrayList;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Set;public class Main {   public static void main(String[] args) {      // List集合的遍历      listTest();      // Set集合的遍历      setTest();   }   private static void setTest() {      Set<String> set = new HashSet<String>();      set.add("JAVA");      set.add("C");      set.add("C++");      // 重复数据添加失败      set.add("JAVA");      set.add("JAVASCRIPT");      // 使用iterator遍历set集合      Iterator<String> it = set.iterator();      while (it.hasNext()) {         String value = it.next();         System.out.println(value);      }            // 使用增强for循环遍历set集合      for(String s: set){         System.out.println(s);      }   }   // 遍历list集合   private static void listTest() {      List<String> list = new ArrayList<String>();      list.add("编");      list.add("程");      list.add("狮");      list.add("www.w3cschool.cn");      // 使用iterator遍历      Iterator<String> it = list.iterator();      while (it.hasNext()) {         String value = it.next();         System.out.println(value);      }      // 使用传统for循环进行遍历      for (int i = 0, size = list.size(); i < size; i++) {         String value = list.get(i);         System.out.println(value);      }      // 使用增强for循环进行遍历      for (String value : list) {         System.out.println(value);      }   }}

以上代码运行输出结果为:

www.w3cschool.cn

www.w3cschool.cn

www.w3cschool.cn

JAVA

JAVASCRIPT      

C++

C

JAVA

JAVASCRIPT      

C++

C

关于Map类型集合的遍历

以下实例我们使用了 HashMap 的 keySet()与entrySet()方法来遍历集合:

/* author by w3cschool.cc Main.java */import java.util.Map;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Set;import java.util.Map.Entry;//增强For循环public class Main {   public static void main(String[] args) {      // 创建一个HashMap对象,并加入了一些键值对。      Map<String, String> maps = new HashMap<String, String>();      maps.put("1", "PHP");      maps.put("2", "Java");      maps.put("3", "C");      maps.put("4", "C++");      maps.put("5", "HTML");            // 传统的遍历map集合的方法1; keySet()      //traditionalMethod1(maps);      // 传统的遍历map集合的方法2; entrySet()      //traditionalMethod2(maps);      // 使用增强For循环来遍历map集合方法1; keySet()      //strongForMethod1(maps);      // 使用增强For循环来遍历map集合方法2; entrySet()      strongForMethod2(maps);   }   private static void strongForMethod2(Map<String, String> maps) {      Set<Entry<String, String>> set = maps.entrySet();      for (Entry<String, String> entry : set) {         String key = entry.getKey();         String value = entry.getValue();         System.out.println(key + " : " + value);      }   }   private static void strongForMethod1(Map<String, String> maps) {      Set<String> set = maps.keySet();      for (String s : set) {         String key = s;         String value = maps.get(s);         System.out.println(key + " : " + value);      }   }   // 使用entrySet()方法,获取maps集合中的每一个键值对,   private static void traditionalMethod2(Map<String, String> maps) {      Set<Map.Entry<String, String>> sets = maps.entrySet();      // 取得迭代器遍历出对应的值。      Iterator<Entry<String, String>> it = sets.iterator();      while (it.hasNext()) {         Map.Entry<String, String> entry = (Entry<String, String>) it.next();         String key = entry.getKey();         String value = entry.getValue();         System.out.println(key + " : " + value);      }   }   // 使用keySet()方法,获取maps集合中的所有键,遍历键取得所对应的值。   private static void traditionalMethod1(Map<String, String> maps) {      Set<String> sets = maps.keySet();      // 取得迭代器遍历出对应的值      Iterator<String> it = sets.iterator();      while (it.hasNext()) {         String key = it.next();         String value = maps.get(key);         System.out.println(key + " : " + value);      }   }}

以上代码运行输出结果为:

1 : PHP2 : Java3 : C4 : C++5 : HTML

Java 实例Java 实例


Java lastIndexOf() 方法

Java String类Java String类


lastIndexOf() 方法有以下四种形式:

  • public int lastIndexOf(int ch): 返回指定字符在此字符串中最后一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。

  • public int lastIndexOf(int ch, int fromIndex): 返返回指定字符在此字符串中最后一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。

  • public int lastIndexOf(String str): 返回指定字符在此字符串中最后一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。

  • public int lastIndexOf(String str, int fromIndex): 返回指定字符在此字符串中最后一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。

语法

public int lastIndexOf(int ch)或public int lastIndexOf(int ch, int fromIndex)或public int lastIndexOf(String str)或public int lastIndexOf(String str, int fromIndex)

参数

  • ch -- 字符。

  • fromIndex -- 开始搜索的索引位置。

  • str -- 要搜索的子字符串。

返回值

指定子字符串在字符串中第一次出现处的索引值。

实例

public class Test {	public static void main(String args[]) {		String Str = new String("W3Cschool教程:www.w3cschool.cn");		String SubStr1 = new String("youj");		String SubStr2 = new String("com");		System.out.print("查找字符 o 最后出现的位置 :" );		System.out.println(Str.lastIndexOf( 'o' ));		System.out.print("从第14个位置查找字符 o 最后出现的位置 :" );		System.out.println(Str.lastIndexOf( 'o', 14 ));		System.out.print("子字符串 SubStr1 最后出现的位置:" );		System.out.println( Str.lastIndexOf( SubStr1 ));		System.out.print("从第十五个位置开始搜索子字符串 SubStr1最后出现的位置 :" );		System.out.println( Str.lastIndexOf( SubStr1, 15 ));		System.out.print("子字符串 SubStr2 最后出现的位置 :" );		System.out.println(Str.lastIndexOf( SubStr2 ));	}}

以上程序执行结果为:

查找字符 o 最后出现的位置 :23
从第14个位置查找字符 o 最后出现的位置 :7
子字符串 SubStr1 最后出现的位置:-1
从第十五个位置开始搜索子字符串 SubStr1最后出现的位置 :-1
子字符串 SubStr2 最后出现的位置 :-1

Java String类Java String类


Java数据类型教程 - Java字符串字符


索引字符

您可以使用charAt()方法从String对象中获取特定索引处的字符。索引从零开始。

下面的代码打印索引值和字符在“W3CSCHOOL.CN"字符串中的每个索引处:

public class Main {  public static void main(String[] args) {    String str = "W3CSCHOOL.CN";    // Get the length of string    int len = str.length();    // Loop through all characters and print their indexes    for (int i = 0; i < len; i++) {      System.out.println(str.charAt(i) + "  has  index   " + i);    }  }}

上面的代码生成以下结果。

Snipaste_2024-01-08_15-01-10

测试字符串是否为空

测试String对象是否为空。空字符串的长度为零。

有三种方法可以检查空字符串:

  • isEmpty()方法。
  • equals()方法。
  • 获取字符串的长度,并检查它是否为零。

以下代码显示如何使用三种方法:

public class Main {  public static void main(String[] args) {    String str1 = "Hello";    String str2 = "";    // Using the isEmpty() method    boolean empty1 = str1.isEmpty(); // Assigns false to empty1    boolean empty2 = str2.isEmpty(); // Assigns true to empty1    // Using the equals() method    boolean empty3 = "".equals(str1); // Assigns false to empty3    boolean empty4 = "".equals(str2); // Assigns true to empty4    // Comparing length of the string with 0    boolean empty5 = str1.length() == 0; // Assigns false to empty5    boolean empty6 = str2.length() == 0; // Assigns true to empty6  }}

更改案例

要将字符串的内容转换为小写和大写,请分别使用toLowerCase()和toUpperCase()方法。

String str1 = new String("Hello"); // str1  contains "Hello" String str2 = str1.toUpperCase();   // str2 contains "HELLO" String str3 = str1.toLowerCase();   // str3 contains "hello"


Java数据类型教程 - Java字符串比较


String类覆盖了Object类的equals()方法,并提供了自己的实现,它根据它们的内容比较两个字符串的相等性。

等于

例如,我们可以比较两个字符串的相等性,如下所示:

String str1 = new String("Hello"); String str2 = new String("Hi"); String str3 = new String("Hello");boolean b1,   b2;b1  = str1.equals(str2); // false will be  assigned to b1 b2  = str1.equals(str3); // true will be  assigned to b2

我们还可以将字符串字面量与字符串字面量或字符串对象进行比较,如下所示:

b1  = str1.equals("Hello");   // true will be  assigned to b1 b2  = "Hello".equals(str1);   // true will be  assigned to b2 b1  = "Hello".equals("Hi");   // false will be  assigned to b1

==操作符总是比较内存中两个对象的引用。

str1 == str2和str1 == str3将返回false,因为str1,str2和str3是内存中三个不同String对象的引用。


比较

要根据字符的Unicode值比较两个字符串,请使用compareTo()方法。它的签名是

public int compareTo(String anotherString)

它返回一个整数,它可以是0(零),正整数或负整数。

该方法返回这两个字符的Unicode值之间的差异。

例如,“a”.compareTo(“b”)将返回-1。 a的Unicode值为97,b为98。它返回差值97 - 98,它是-1。

以下是字符串比较的示例:

"abc".compareTo("abc") will  return  0"abc".compareTo("xyz") will  return  -23  (value of  "a" -  "x") "xyz".compareTo("abc") will  return  23  (value of  "x" -  "a")

以下代码显示如何进行字符串比较。

public class Main {  public static void main(String[] args) {    String apple = new String("Apple");    String orange = new String("Orange");    System.out.println(apple.equals(orange));    System.out.println(apple.equals(apple));    System.out.println(apple == apple);    System.out.println(apple == orange);    System.out.println(apple.compareTo(apple));    System.out.println(apple.compareTo(orange));  }}

上面的代码生成以下结果。

字符串池

Java维护一个所有字符串文字的池。它在字符串池中为每个字符串文字创建一个String对象。

当遇到字符串字面量时,它在字符串池中查找具有相同内容的字符串对象。如果在字符串池中找不到匹配项,它将创建一个新的String对象并将其添加到字符串池。

如果它在字符串池中找到匹配项,它将使用池中找到的String对象的引用替换字符串字面值。

我们可以使用其intern()方法向字符串池添加一个String对象。

如果发现匹配,intern()方法从字符串池返回对象的引用。否则,它将一个新的String对象添加到字符串池,并返回新对象的引用。

字符串比较

要比较两个字符串是否相等,忽略它们的情况,请使用equalsIgnoreCase()方法。

要对等同性执行区分大小写的比较,请使用equals()方法。

public class Main {  public static void main(String[] args) {    String str1 = "Hello";    String str2 = "HELLO";    if (str1.equalsIgnoreCase(str2)) {      System.out.println("Ignoring case str1 and str2 are equal");    } else {      System.out.println("Ignoring case str1 and str2 are not equal");    }    if (str1.equals(str2)) {      System.out.println("str1 and str2 are equal");    } else {      System.out.println("str1 and str2 are not equal");    }  }}

上面的代码生成以下结果。

语言敏感字符串比较

String类别根据字符的Unicode值比较字符串。

要根据字典顺序比较字符串,请使用java.text.Collat​​or类的compare()方法执行语言敏感(字典顺序)字符串比较。

该方法需要两个字符串作为参数进行比较。如果两个字符串相同,返回0,如果第一个字符串在第二个字符串之后返回1,如果第一个字符串在第二个字符串之前,返回-1。

以下代码说明了Collat​​or类的使用。

import java.text.Collator;import java.util.Locale;public class Main {  public static void main(String[] args) {    Locale USLocale = new Locale("en", "US");    Collator c = Collator.getInstance(USLocale);    String str1 = "Java";    String str2 = "HTML";    int diff = c.compare(str1, str2);    System.out.print("Comparing using Collator  class: ");    if (diff > 0) {      System.out.println(str1 + "  comes after " + str2);    } else if (diff < 0) {      System.out.println(str1 + "  comes before " + str2);    } else {      System.out.println(str1 + "  and  " + str2 + "  are   the   same.");    }  }}

上面的代码生成以下结果。

Java面向对象的设计 - Java继承隐藏

方法隐藏

类从其超类继承所有非私有静态方法。

重定义类中继承的静态方法称为方法隐藏。

子类中的重定义静态方法隐藏其超类的静态方法。

在类中重定义非静态方法称为方法覆盖。

关于方法隐藏的重定义方法(名称,访问级别,返回类型和异常)的所有规则与方法覆盖相同。

class MySuper {  public static void print() {    System.out.println("Inside MySuper.print()");  }}class MySubclass extends MySuper {  public static void print() {    System.out.println("Inside MySubclass.print()");  }}public class Main {  public static void main(String[] args) {    MySuper mhSuper = new MySub();    MySubclass mhSub = new MySubclass();    MySuper.print();    MySubclass.print();    ((MySuper) mhSub).print();    mhSuper = mhSub;    mhSuper.print();    ((MySubclass) mhSuper).print();  }}

上面的代码生成以下结果。

字段隐藏

类中的字段声明(静态或非静态)在其父类中隐藏具有相同名称的继承字段。

在字段隐藏的情况下,不考虑字段的类型及其访问级别。

字段隐藏仅基于字段名称。

class MySuper {  protected int num = 100;  protected String name = "Tom";}class MySub extends MySuper {  public void print() {    System.out.println("num: " + num);    System.out.println("name: " + name);  }}class MySub2 extends MySuper {  // Hides num field in MySuper class  private int num = 200;  // Hides name field in MySuper class  private String name = "Jack";  public void print() {    System.out.println("num: " + num);    System.out.println("name: " + name);  }}public class Main {  public static void main(String[] args) {    MySub fhSub = new MySub();    fhSub.print();    MySub2 fhSub2 = new MySub2();    fhSub2.print();  }}

上面的代码生成以下结果。

例子

以下代码显示了如何使用super关键字访问超类的隐藏字段

class MySuper {  protected int num = 100;  protected String name = "Tom";}class MySub extends MySuper {  // Hides the num field in MySuper class  private int num = 200;  // Hides the name field in MySuper class  private String name = "Jack";  public void print() {    System.out.println("num: " + num);    System.out.println("super.num: " + super.num);    System.out.println("name: " + name);    System.out.println("super.name: " + super.name);  }}public class Main {  public static void main(String[] args) {    MySub s = new MySub();    s.print();  }}

上面的代码生成以下结果。

字段隐藏发生在一个类声明一个变量与来自其超类的继承变量具有相同名称的时候。

字段隐藏仅基于字段的名称。

类应该使用关键字super来访问超类的隐藏字段。

类可以使用简单的名称来访问其主体中的重定义字段