Java 教程
- Java - 首页
- Java - 概述
- Java - 历史
- Java - 特性
- Java 与 C++
- JVM - Java 虚拟机
- Java - JDK 与 JRE 与 JVM
- Java - Hello World 程序
- Java - 环境设置
- Java - 基本语法
- Java - 变量类型
- Java - 数据类型
- Java - 类型转换
- Java - Unicode 系统
- Java - 基本运算符
- Java - 注释
- Java - 用户输入
- Java - 日期与时间
Java 控制语句
- Java - 循环控制
- Java - 决策制定
- Java - if-else
- Java - switch
- Java - for 循环
- Java - for-each 循环
- Java - while 循环
- Java - do-while 循环
- Java - break
- Java - continue
面向对象编程
- Java - OOPs 概念
- Java - 对象与类
- Java - 类属性
- Java - 类方法
- Java - 方法
- Java - 变量作用域
- Java - 构造函数
- Java - 访问修饰符
- Java - 继承
- Java - 聚合
- Java - 多态
- Java - 方法覆盖
- Java - 方法重载
- Java - 动态绑定
- Java - 静态绑定
- Java - 实例初始化块
- Java - 抽象
- Java - 封装
- Java - 接口
- Java - 包
- Java - 内部类
- Java - 静态类
- Java - 匿名类
- Java - 单例类
- Java - 包装类
- Java - 枚举
- Java - 枚举构造函数
- Java - 枚举字符串
Java 内置类
Java 文件处理
Java 错误与异常
- Java - 异常
- Java - try-catch 块
- Java - try-with-resources
- Java - 多重 catch 块
- Java - 嵌套 try 块
- Java - finally 块
- Java - throw 异常
- Java - 异常传播
- Java - 内置异常
- Java - 自定义异常
Java 多线程
- Java - 多线程
- Java - 线程生命周期
- Java - 创建线程
- Java - 启动线程
- Java - 线程连接
- Java - 线程命名
- Java - 线程调度器
- Java - 线程池
- Java - 主线程
- Java - 线程优先级
- Java - 守护线程
- Java - 线程组
- Java - 关闭钩子
Java 同步
Java 网络编程
- Java - 网络编程
- Java - 套接字编程
- Java - URL 处理
- Java - URL 类
- Java - URLConnection 类
- Java - HttpURLConnection 类
- Java - Socket 类
- Java - 泛型
Java 集合
Java 接口
Java 数据结构
Java 集合算法
高级 Java
- Java - 命令行参数
- Java - Lambda 表达式
- Java - 发送邮件
- Java - Applet 基础
- Java - Javadoc 注释
- Java - 自动装箱和拆箱
- Java - 文件不匹配方法
- Java - REPL (JShell)
- Java - 多版本 Jar 文件
- Java - 私有接口方法
- Java - 内部类菱形运算符
- Java - 多分辨率图像 API
- Java - 集合工厂方法
- Java - 模块系统
- Java - Nashorn JavaScript
- Java - Optional 类
- Java - 方法引用
- Java - 函数式接口
- Java - 默认方法
- Java - Base64 编码解码
- Java - switch 表达式
- Java - Teeing 收集器
- Java - 微基准测试
- Java - 文本块
- Java - 动态 CDS 归档
- Java - Z 垃圾收集器 (ZGC)
- Java - 空指针异常
- Java - 打包工具
- Java - 密封类
- Java - 记录类
- Java - 隐藏类
- Java - 模式匹配
- Java - 简洁的数字格式化
- Java - 垃圾回收
- Java - JIT 编译器
Java 杂项
- Java - 递归
- Java - 正则表达式
- Java - 序列化
- Java - 字符串
- Java - Process API 改进
- Java - Stream API 改进
- Java - 增强的 @Deprecated 注解
- Java - CompletableFuture API 改进
- Java - 流
- Java - 日期时间 API
- Java 8 - 新特性
- Java 9 - 新特性
- Java 10 - 新特性
- Java 11 - 新特性
- Java 12 - 新特性
- Java 13 - 新特性
- Java 14 - 新特性
- Java 15 - 新特性
- Java 16 - 新特性
Java APIs 与框架
Java 类引用
- Java - Scanner
- Java - 数组
- Java - 字符串
- Java - Date
- Java - ArrayList
- Java - Vector
- Java - Stack
- Java - PriorityQueue
- Java - LinkedList
- Java - ArrayDeque
- Java - HashMap
- Java - LinkedHashMap
- Java - WeakHashMap
- Java - EnumMap
- Java - TreeMap
- Java - IdentityHashMap
- Java - HashSet
- Java - EnumSet
- Java - LinkedHashSet
- Java - TreeSet
- Java - BitSet
- Java - Dictionary
- Java - Hashtable
- Java - Properties
- Java - Collection
- Java - Array
Java 有用资源
Java 快速指南
Java - 概述
Java 编程语言最初由 Sun Microsystems 开发,由 James Gosling 发起,于 1995 年作为 Sun Microsystems Java 平台 (Java 1.0 [J2SE]) 的核心组件发布。
Java 标准版的最新版本是 Java SE 8。随着 Java 的发展及其广泛流行,构建了多种配置以适应各种类型的平台。例如:用于企业应用程序的 J2EE,用于移动应用程序的 J2ME。
新的 J2 版本分别更名为 Java SE、Java EE 和 Java ME。Java 保证了 **一次编写,随处运行。**
Java 是 −
**面向对象的** − 在 Java 中,一切都是对象。Java 基于对象模型,因此易于扩展。
**平台无关的** − 与包括 C 和 C++ 在内的许多其他编程语言不同,Java 编译后,不会编译成特定于平台的机器码,而是编译成平台无关的字节码。此字节码通过网络分发,并在运行它的任何平台上由虚拟机 (JVM) 解释。
**简单的** − Java 的设计易于学习。如果您理解 OOP Java 的基本概念,那么掌握它就很容易。
**安全的** − Java 的安全特性使其能够开发无病毒、防篡改的系统。身份验证技术基于公钥加密。
**与架构无关的** − Java 编译器生成与架构无关的对象文件格式,这使得编译后的代码在许多处理器上都可以执行,前提是存在 Java 运行时系统。
**可移植的** − 由于架构中立且规范中没有与实现相关的方面,因此 Java 是可移植的。Java 中的编译器是用 ANSI C 编写的,具有清晰的可移植性边界,这是一个 POSIX 子集。
**健壮的** − Java 通过主要强调编译时错误检查和运行时检查来努力消除易出错的情况。
**多线程的** − 利用 Java 的多线程特性,可以编写可以同时执行许多任务的程序。此设计特性允许开发人员构建可以平稳运行的交互式应用程序。
**解释型的** − Java 字节码会即时转换为本机机器指令,并且不会存储在任何地方。由于链接是一个增量且轻量级的过程,因此开发过程更快且更具分析性。
**高性能的** − 通过使用即时编译器,Java 实现了高性能。
**分布式的** − Java 是为互联网的分布式环境而设计的。
**动态的** − Java 比 C 或 C++ 更动态,因为它旨在适应不断变化的环境。Java 程序可以携带大量的运行时信息,这些信息可用于验证和解决运行时对对象的访问。
使用 Java 编程的 Hello World。
为了让您对 Java 编程有一点兴奋感,我将为您提供一个小型传统的 C 编程 Hello World 程序,您可以使用演示链接进行尝试。
public class MyFirstJavaProgram { /* This is my first java program. * This will print 'Hello World' as the output */ public static void main(String []args) { System.out.println("Hello World"); // prints Hello World } }
Java 的历史
James Gosling 于 1991 年 6 月启动了 Java 语言项目,用于他的众多机顶盒项目之一。这种语言最初被称为“Oak”(Gosling 办公室外的一棵橡树),也称为“Green”,后来从一个随机词列表中更名为 Java。
Sun公司于1995年发布了第一个公开实现版本Java 1.0。它承诺实现一次编写,到处运行(WORA),在流行的平台上提供免费的运行时环境。
2006年11月13日,Sun公司在GNU通用公共许可证(GPL)的条款下,将大部分Java代码作为自由和开放源代码软件发布。
2007年5月8日,Sun公司完成了这一进程,使所有Java核心代码都成为自由和开放源代码,除了Sun公司不拥有版权的一小部分代码。
您需要的工具
要执行本教程中讨论的示例,您需要一台至少具有64MB RAM(推荐128MB RAM)的奔腾200MHz计算机。
您还需要以下软件:
- Linux 7.1或Windows xp/7/8操作系统
- Java JDK 8
- Microsoft Notepad或任何其他文本编辑器
本教程将提供创建使用Java的GUI、网络和Web应用程序的必要技能。
下一步是什么?
下一章将指导您如何获取Java及其文档。最后,它将指导您如何安装Java并准备开发Java应用程序的环境。
Java - 环境设置
在线实时演示选项
我们已在线设置了Java编程环境,以便您可以在线编译和执行所有可用的示例。这使您可以确信您正在阅读的内容,并使您可以使用不同的选项验证程序。随意修改任何示例并在线执行。
尝试使用以下示例,在下面示例代码框的右上角使用实时演示选项:
public class MyFirstJavaProgram { public static void main(String []args) { System.out.println("Hello World"); } }
对于本教程中给出的大多数示例,您会在我们网站代码部分的右上角找到一个尝试选项,该选项将带您到在线编译器。因此,只需使用它并享受您的学习。
本地环境设置
如果您想为Java编程语言设置自己的环境,那么本节将指导您完成整个过程。请按照以下步骤设置您的Java环境。
Java SE可免费下载。要下载,请点击此处,请下载与您的操作系统兼容的版本。
按照说明下载Java,并运行.exe文件以将Java安装到您的计算机上。将Java安装到您的计算机后,您需要设置环境变量以指向正确的安装目录。
为Windows 2000/XP设置路径
假设您已将Java安装在c:\Program Files\java\jdk目录下:
右键单击“我的电脑”,然后选择“属性”。
单击“高级”选项卡下的“环境变量”按钮。
现在,编辑“Path”变量,并在其末尾添加指向Java可执行文件目录的路径。例如,如果当前路径设置为C:\Windows\System32,则按如下方式编辑:
C:\Windows\System32;c:\Program Files\java\jdk\bin.
为Windows 95/98/ME设置路径
假设您已将Java安装在c:\Program Files\java\jdk目录下:
编辑“C:\autoexec.bat”文件,并在末尾添加以下行:
SET PATH=%PATH%;C:\Program Files\java\jdk\bin
为Linux、UNIX、Solaris、FreeBSD设置路径
应将环境变量PATH设置为指向已安装Java二进制文件的路径。如果您遇到问题,请参考您的shell文档。
例如,如果您使用bash作为您的shell,则您会在.bashrc文件的末尾添加以下行:
export PATH=/path/to/java:$PATH
常用的Java编辑器
要编写Java程序,您需要一个文本编辑器。市场上甚至还有更复杂的IDE。下面简要介绍最常用的IDE:
Notepad − 在Windows机器上,您可以使用任何简单的文本编辑器,如Notepad(本教程推荐)或WordPad。Notepad++也是一个免费的文本编辑器,具有增强的功能。
Netbeans − 它是一个开源且免费的Java IDE,可以从www.netbeans.org/index.html下载。
Eclipse − 它也是一个由Eclipse开源社区开发的Java IDE,可以从www.eclipse.org下载。
IDE或集成开发环境,提供所有常用的工具和功能来帮助编程,例如源代码编辑器、构建工具和调试器等。
下一步是什么?
下一章将教你如何编写和运行你的第一个Java程序,以及开发应用程序所需的一些重要的Java基本语法。
Java - 基本语法
当我们考虑一个Java程序时,它可以定义为一组通过调用彼此的方法来通信的对象。现在让我们简要地了解一下类、对象、方法和实例变量的含义。
对象 − 对象具有状态和行为。例如:狗具有状态——颜色、名字、品种,以及行为,如摇尾巴、吠叫、吃东西。对象是类的实例。
类 − 类可以定义为一个模板/蓝图,它描述了其类型对象支持的行为/状态。
方法 − 方法基本上是一种行为。一个类可以包含许多方法。在方法中编写逻辑,操作数据并执行所有操作。
实例变量 − 每个对象都有自己唯一的一组实例变量。对象的状体是由分配给这些实例变量的值创建的。
第一个Java程序
让我们来看一段简单的代码,它将打印单词Hello World。
示例
public class MyFirstJavaProgram { /* This is my first java program. * This will print 'Hello World' as the output */ public static void main(String []args) { System.out.println("Hello World"); // prints Hello World } }
让我们看看如何保存文件、编译和运行程序。请按照以下步骤操作:
打开记事本并添加上面的代码。
将文件保存为:MyFirstJavaProgram.java。
打开一个命令提示符窗口,并转到保存类的目录。假设它是C:\。
键入“javac MyFirstJavaProgram.java”并按Enter键编译代码。如果代码中没有错误,命令提示符将带您进入下一行(假设:已设置path变量)。
现在,键入“java MyFirstJavaProgram”来运行您的程序。
您将能够看到窗口上打印的“Hello World”。
输出
C:\> javac MyFirstJavaProgram.java C:\> java MyFirstJavaProgram Hello World
基本语法
关于Java程序,记住以下几点非常重要。
大小写敏感性 − Java 区分大小写,这意味着标识符Hello和hello在Java中具有不同的含义。
类名 − 所有类名的第一个字母都应大写。如果使用多个单词来构成类名,则每个内部单词的第一个字母都应大写。
示例:class MyFirstJavaClass
方法名 − 所有方法名都应以小写字母开头。如果使用多个单词来构成方法名,则每个内部单词的第一个字母都应大写。
示例:public void myMethodName()
程序文件名 − 程序文件名应与类名完全匹配。
保存文件时,应使用类名保存它(记住Java区分大小写),并在名称末尾附加“.java”(如果文件名和类名不匹配,则程序将无法编译)。
但请注意,如果您在文件中没有公共类,则文件名可以与类名不同。在文件中也不必一定要有公共类。
示例:假设“MyFirstJavaProgram”是类名。然后文件应保存为'MyFirstJavaProgram.java'
public static void main(String args[]) − Java程序处理从main()方法开始,它是每个Java程序的必需部分。
Java标识符
所有Java组件都需要名称。用于类、变量和方法的名称称为标识符。
在Java中,关于标识符,有几点需要注意。如下所示:
所有标识符都应以字母(A到Z或a到z)、货币字符($)或下划线(_)开头。
第一个字符之后,标识符可以包含任何字符组合。
关键字不能用作标识符。
最重要的是,标识符区分大小写。
合法标识符示例:age, $salary, _value, __1_value。
非法标识符示例:123abc, -salary。
Java修饰符
与其他语言一样,可以使用修饰符来修改类、方法等。修饰符分为两类:
访问修饰符 − default, public, protected, private
非访问修饰符 − final, abstract, strictfp
我们将在下一节中更详细地了解修饰符。
Java变量
以下是Java中的变量类型:
- 局部变量
- 类变量(静态变量)
- 实例变量(非静态变量)
Java数组
数组是存储相同类型多个变量的对象。但是,数组本身是堆上的一个对象。我们将在接下来的章节中学习如何声明、构造和初始化。
Java枚举
枚举在Java 5.0中引入。枚举将变量限制为仅具有几个预定义值中的一个。此枚举列表中的值称为枚举。
使用枚举可以减少代码中的错误数量。
例如,如果我们考虑一个鲜榨果汁店的应用程序,则可以将杯子大小限制为小、中、大。这将确保不允许任何人订购小、中、大以外的任何尺寸。
示例
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 ; System.out.println("Size: " + juice.size); } }
输出
Size: MEDIUM
注意 − 枚举可以单独声明或在类内声明。方法、变量、构造函数也可以在枚举内定义。
Java关键字
以下列表显示了Java中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称。
序号 | 保留字和描述 |
---|---|
1 | abstract
根据字典,抽象是指处理思想而不是事件的特性。 |
2 | assert
assert关键字用于在Java中定义断言。断言是在Java中的一种语句,它确保程序中所做的任何假设的正确性。 |
3 | boolean
布尔型数据类型是 Java 支持的八种基本数据类型之一。它提供了一种创建布尔类型变量的方法,这些变量可以接受 true 或 false 的布尔值。 |
4 | break
Java 编程语言中的break语句有以下两种用法:
|
5 | byte
byte 数据类型是 Java 支持的八种基本数据类型之一。它提供了一种创建 byte 类型变量的方法,这些变量可以接受 byte 值。 |
6 | case
case关键字是switch语句的一部分,它允许将变量与一系列值进行相等性测试。 |
7 | catch
异常(或异常事件)是在程序执行过程中出现的错误。 |
8 | char
char 数据类型是 Java 支持的八种基本数据类型之一。 |
9 | class
Java 是一种面向对象的语言。它具有面向对象的特性。 |
10 | const
final关键字用于在 Java 中定义常量值或 final 方法/类。 |
11 | continue
continue关键字可以用于任何循环控制结构。 |
12 | default
default关键字是switch语句的一部分,它允许将变量与一系列值进行相等性测试。 |
13 | do
do...while循环类似于while循环,不同之处在于do...while循环至少执行一次。 |
14 | double
double 数据类型是 Java 支持的八种基本数据类型之一。 |
15 | if
if语句后面可以跟一个可选的else语句,当布尔表达式为false时执行。 |
16 | enum
Java Enum类是所有 Java 语言枚举类型的公共基类。 |
17 | extends
extends是用于继承类属性的关键字。以下是extends关键字的语法。 |
18 | final
final关键字用于在 Java 中定义常量值或 final 方法/类。 |
19 | finally
finally关键字用于定义finally块。finally块跟在try块或catch块之后。finally块中的代码总是执行,无论是否发生异常。 |
20 | float
float 数据类型是 Java 支持的八种基本数据类型之一。它提供了一种创建 float 类型变量的方法,这些变量可以接受 float 值。 |
21 | for
for循环是一种重复控制结构,允许你高效地编写需要执行特定次数的循环。 |
22 | goto
Java目前不支持goto语句。它被保留为将来使用的保留关键字。作为替代,Java支持带有break和continue语句的标签。 |
23 | if
if语句由一个布尔表达式和一个或多个语句组成。 |
24 | implements
通常,implements关键字与类一起使用,以继承接口的属性。 |
25 | import
import关键字用于包的上下文中。 |
26 | instanceof
instanceof关键字是一个运算符,仅用于对象引用变量。 |
27 | int
int 数据类型是 Java 支持的八种基本数据类型之一。 |
28 | interface
接口是 Java 中的一种引用类型。它类似于类。它是抽象方法的集合。 |
29 | long
long 数据类型是 Java 支持的八种基本数据类型之一。 |
30 | package
在 Java 中使用包是为了防止命名冲突,控制访问,使查找/定位和使用类、接口、枚举和注解更容易等等。 |
31 | private
声明为 private 的方法、变量和构造函数只能在其声明的类中访问。 |
32 | protected
protected 访问修饰符不能应用于类和接口。 |
33 | public
声明为 public 的类、方法、构造函数、接口等可以从任何其他类访问。 |
34 | short
通过为变量分配不同的数据类型,你可以在这些变量中存储整数、小数或字符。 |
35 | static
static关键字用于创建独立于为类创建的任何实例存在的变量。 |
36 | super
super关键字类似于this关键字。 |
37 | switch
switch语句允许将变量与一系列值进行相等性测试。 |
38 | this
this关键字是一个非常重要的关键字,用于标识对象。以下是此关键字的用法。 |
39 | throw
如果方法不处理已检查异常,则该方法必须使用throws关键字声明它。 |
40 | transient
序列化是一个概念,使用它我们可以将对象的状体写入字节流中,以便我们可以通过网络传输它(使用JPA和RMI等技术)。 |
41 | try
方法使用try和catch关键字的组合来捕获异常。 |
42 | while
Java 编程语言中的while循环语句重复执行目标语句,只要给定的条件为真。 |
Java中的注释
Java支持单行和多行注释,与C和C++非常相似。Java编译器会忽略任何注释中包含的所有字符。
示例
public class MyFirstJavaProgram { /* This is my first java program. * This will print 'Hello World' as the output * This is an example of multi-line comments. */ public static void main(String []args) { // This is an example of single line comment /* This is also an example of single line comment. */ System.out.println("Hello World"); } }
输出
Hello World
使用空行
仅包含空格(可能带有注释)的行称为空行,Java完全忽略它。
继承
在 Java 中,类可以从类派生。基本上,如果你需要创建一个新类,并且这里已经存在一个类具有你所需的一些代码,那么可以从已经存在的代码派生你的新类。
这个概念允许你重用现有类的字段和方法,而无需在新类中重写代码。在这种情况下,现有类称为超类,派生类称为子类。
接口
在 Java 语言中,接口可以定义为对象之间关于如何相互通信的契约。在继承的概念中,接口起着至关重要的作用。
接口定义了派生类(子类)应该使用的方法。但是方法的实现完全取决于子类。
下一步是什么?
下一节将解释 Java 编程中的对象和类。在本节结束时,你将能够清楚地了解 Java 中的对象和类是什么。
Java - 变量类型
变量为我们提供了程序可以操作的命名存储。Java 中的每个变量都有一个特定的类型,它决定变量内存的大小和布局;可以存储在该内存中的值的范围;以及可以应用于变量的操作集。
必须在使用变量之前声明它们。以下是变量声明的基本形式:
data type variable [ = value][, variable [ = value] ...] ;
这里数据类型是 Java 的数据类型之一,变量是变量的名称。要声明多个指定类型变量,可以使用逗号分隔的列表。
以下是 Java 中变量声明和初始化的有效示例:
示例
int a, b, c; // Declares three ints, a, b, and c. int a = 10, b = 10; // Example of initialization byte B = 22; // initializes a byte type variable B. double pi = 3.14159; // declares and assigns a value of PI. char a = 'a'; // the char variable a iis initialized with value 'a'
本章将解释 Java 语言中提供的各种变量类型。Java 中有三种变量:
- 局部变量
- 实例变量
- 类/静态变量
局部变量
局部变量在方法、构造函数或代码块中声明。
进入方法、构造函数或代码块时创建局部变量,一旦退出方法、构造函数或代码块,变量就会被销毁。
访问修饰符不能用于局部变量。
局部变量仅在其声明的方法、构造函数或代码块中可见。
局部变量在内部以堆栈级别实现。
局部变量没有默认值,因此局部变量应该在第一次使用之前声明并赋值。
示例
这里,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
示例
下面的示例在未初始化的情况下使用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 initialized age = age + 7; ^ 1 error
实例变量
实例变量在类中声明,但在方法、构造函数或任何代码块之外声明。
当在堆中为对象分配空间时,会为每个实例变量值创建一个槽。
使用关键字'new'创建对象时创建实例变量,对象销毁时销毁实例变量。
实例变量保存必须被多个方法、构造函数或代码块引用的值,或者对象状态的必要部分,这些部分必须存在于整个类中。
实例变量可以在类级别在使用之前或之后声明。
可以为实例变量提供访问修饰符。
实例变量对类中的所有方法、构造函数和代码块可见。通常,建议将这些变量设为私有(访问级别)。但是,可以使用访问修饰符为子类提供这些变量的可见性。
实例变量具有默认值。对于数字,默认值为 0;对于布尔值,为 false;对于对象引用,为 null。可以在声明期间或在构造函数中赋值。
可以通过在类内部调用变量名来直接访问实例变量。但是,在静态方法中(当实例变量具有可访问性时),应该使用完全限定名来调用它们。ObjectReference.VariableName。
示例
import java.io.*; public class Employee { // this instance variable is visible for any child class. public String name; // salary variable is visible in Employee class only. private double salary; // The name variable is assigned in the constructor. public Employee (String empName) { name = empName; } // The salary variable is assigned a value. public void setSalary(double empSal) { salary = empSal; } // This method prints the employee details. 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 : Ransika salary :1000.0
类/静态变量
类变量也称为静态变量,它们在类中使用 static 关键字声明,但在方法、构造函数或代码块之外声明。
无论从类中创建多少个对象,每个类变量都只有一个副本。
静态变量很少使用,除非声明为常量。常量是声明为 public/private、final 和 static 的变量。常量变量的值永远不会改变。
静态变量存储在静态内存中。除了声明为 final 并用作公共或私有常量之外,很少使用静态变量。
程序启动时创建静态变量,程序停止时销毁静态变量。
可见性与实例变量相似。但是,大多数静态变量都声明为 public,因为它们必须可供类的用户使用。
默认值与实例变量相同。对于数字,默认值为 0;对于布尔值,为 false;对于对象引用,为 null。可以在声明期间或在构造函数中赋值。此外,可以在特殊的静态初始化块中赋值。
可以使用类名ClassName.VariableName访问静态变量。
当将类变量声明为 public static final 时,变量名(常量)全部大写。如果静态变量不是 public 和 final,则命名语法与实例变量和局部变量相同。
示例
import java.io.*; public class Employee { // salary variable is a private static variable private static double salary; // DEPARTMENT is a constant 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 访问常量。
下一步是什么?
你已经在本节中使用了访问修饰符(public 和 private)。下一节将详细解释访问修饰符和非访问修饰符。
Java - 基本数据类型
变量不过是在内存中保留的存储值的内存位置。这意味着当您创建一个变量时,您会在内存中保留一些空间。
根据变量的数据类型,操作系统分配内存并决定可以在保留的内存中存储什么。因此,通过为变量分配不同的数据类型,您可以在这些变量中存储整数、小数或字符。
Java中有两种数据类型:
- 原始数据类型
- 引用/对象数据类型
原始数据类型
Java支持八种原始数据类型。原始数据类型由语言预定义,并由关键字命名。现在让我们详细了解这八种原始数据类型。
byte
字节数据类型是8位有符号二进制补码整数
最小值为 -128 (-2^7)
最大值为 127(包含)(2^7 -1)
默认值为 0
字节数据类型用于在大型数组中节省空间,主要用于代替整数,因为字节的大小是整数的四分之一。
示例 - byte a = 100, byte b = -50
short
短整型数据类型是16位有符号二进制补码整数
最小值为 -32,768 (-2^15)
最大值为 32,767(包含)(2^15 -1)
短整型数据类型也可用于像字节数据类型一样节省内存。短整型是整数大小的一半。
默认值为 0。
示例 - short s = 10000, short r = -20000
int
整型数据类型是32位有符号二进制补码整数。
最小值为 -2,147,483,648 (-2^31)
最大值为 2,147,483,647(包含)(2^31 -1)
除非担心内存问题,否则整型通常用作整数值的默认数据类型。
默认值为 0
示例 - int a = 100000, int b = -200000
long
- 长整型数据类型是64位有符号二进制补码整数
- 最小值为 -9,223,372,036,854,775,808(-2^63)
- 最大值为 9,223,372,036,854,775,807(包含)(2^63 -1)
- 当需要比int更宽的范围时使用此类型
- 默认值为 0L
- 示例 - long a = 100000L, long b = -200000L
float
浮点型数据类型是单精度32位IEEE 754浮点数
浮点型主要用于节省大型浮点数数组的内存
默认值为 0.0f
浮点型数据类型永远不要用于精确值,例如货币
示例 - float f1 = 234.5f
double
双精度型数据类型是双精度64位IEEE 754浮点数
此数据类型通常用作十进制值的默认数据类型,通常是默认选择
双精度型数据类型永远不要用于精确值,例如货币
默认值为 0.0d
示例 - double d1 = 123.4
boolean
布尔型数据类型表示一位信息
只有两个可能的值:true 和 false
此数据类型用于跟踪真/假条件的简单标志
默认值为 false
示例 - boolean one = true
char
字符型数据类型是单个16位Unicode字符
最小值为 '\u0000' (或 0)
最大值为 '\uffff' (或 65,535 包含)
字符型数据类型用于存储任何字符
示例 - char letterA = 'A'
示例
以下示例显示了我们上面讨论的各种原始数据类型的用法。我们在数值数据类型上使用了加法运算,而布尔型和字符型变量则按原样打印。
public class JavaTester { public static void main(String args[]) { byte byteValue1 = 2; byte byteValue2 = 4; byte byteResult = (byte)(byteValue1 + byteValue2); System.out.println("Byte: " + byteResult); short shortValue1 = 2; short shortValue2 = 4; short shortResult = (short)(shortValue1 + shortValue2); System.out.println("Short: " + shortResult); int intValue1 = 2; int intValue2 = 4; int intResult = intValue1 + intValue2; System.out.println("Int: " + intResult); long longValue1 = 2L; long longValue2 = 4L; long longResult = longValue1 + longValue2; System.out.println("Long: " + longResult); float floatValue1 = 2.0f; float floatValue2 = 4.0f; float floatResult = floatValue1 + floatValue2; System.out.println("Float: " + floatResult); double doubleValue1 = 2.0; double doubleValue2 = 4.0; double doubleResult = doubleValue1 + doubleValue2; System.out.println("Double: " + doubleResult); boolean booleanValue = true; System.out.println("Boolean: " + booleanValue); char charValue = 'A'; System.out.println("Char: " + charValue); } }
输出
Byte: 6 Short: 6 Int: 6 Long: 6 Float: 6.0 Double: 6.0 Boolean: true Char: A
引用数据类型
引用变量是使用类的已定义构造函数创建的。它们用于访问对象。这些变量声明为特定类型,不能更改。例如,Employee、Puppy 等。
类对象和各种类型的数组变量属于引用数据类型。
任何引用变量的默认值为 null。
引用变量可以用来引用声明类型的任何对象或任何兼容类型。
示例:Animal animal = new Animal("giraffe");
Java字面量
字面量是固定值的源代码表示。它们直接在代码中表示,无需任何计算。
字面量可以赋值给任何原始类型变量。例如:
byte a = 68; char a = 'A';
byte、int、long 和 short 可以用十进制(基数 10)、十六进制(基数 16)或八进制(基数 8)数字系统表示。
使用这些数字系统表示字面量时,前缀 0 用于表示八进制,前缀 0x 用于表示十六进制。例如:
int decimal = 100; int octal = 0144; int hexa = 0x64;
Java 中的字符串字面量像大多数其他语言一样,通过用一对双引号括起字符序列来指定。字符串字面量的示例如下:
示例
"Hello World" "two\nlines" "\"This is in quotes\""
字符串和字符类型的字面量可以包含任何 Unicode 字符。例如:
char a = '\u0001'; String a = "\u0001";
示例
以下示例显示了我们上面讨论的各种字面量的用法。
public class JavaTester { public static void main(String args[]) { int decimal = 100; int octal = 0144; int hexa = 0x64; System.out.println(decimal); System.out.println(octal); System.out.println(hexa); String msg1 = "Hello World"; String msg2 = "two\nlines"; String msg3 = "\"This is in quotes\""; System.out.println(msg1); System.out.println(msg2); System.out.println(msg3); char a = '\u0064'; String msg4 = "\u0064"; System.out.println(a); System.out.println(msg4); } }
输出
100 100 100 Hello World two lines "This is in quotes" d d
Java 语言也支持一些用于字符串和字符字面量的特殊转义序列。它们是:
表示法 | 表示的字符 |
---|---|
\n | 换行符 (0x0a) |
\r | 回车符 (0x0d) |
\f | 换页符 (0x0c) |
\b | 退格符 (0x08) |
\s | 空格 (0x20) |
\t | 制表符 |
\" | 双引号 |
\' | 单引号 |
\\ | 反斜杠 |
\ddd | 八进制字符 (ddd) |
\uxxxx | 十六进制 UNICODE 字符 (xxxx) |
下一步是什么?
本章解释了各种数据类型。下一个主题解释不同的变量类型及其用法。这将使您很好地了解如何在 Java 类、接口等中使用它们。
Java - 基本运算符
Java 提供了一套丰富的运算符来操作变量。我们可以将所有 Java 运算符划分为以下几组:
- 算术运算符
- 关系运算符
- 位运算符
- 逻辑运算符
- 赋值运算符
- 其他运算符
算术运算符
算术运算符在数学表达式中的使用方式与在代数中使用的方式相同。下表列出了算术运算符:
假设整数变量 A 保持 10,变量 B 保持 20,则:
运算符 | 描述 | 示例 |
---|---|---|
+ (加法) | 将运算符两侧的值相加。 | A + B 将得到 30 |
- (减法) | 从左操作数中减去右操作数。 | A - B 将得到 -10 |
* (乘法) | 将运算符两侧的值相乘。 | A * B 将得到 200 |
/ (除法) | 将左操作数除以右操作数。 | B / A 将得到 2 |
% (取模) | 将左操作数除以右操作数并返回余数。 | B % A 将得到 0 |
++ (自增) | 将操作数的值增加 1。 | B++ 得到 21 |
-- (自减) | 将操作数的值减少 1。 | B-- 得到 19 |
关系运算符
Java 语言支持以下关系运算符。
假设变量 A 保持 10,变量 B 保持 20,则:
运算符 | 描述 | 示例 |
---|---|---|
== (等于) | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
!= (不等于) | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> (大于) | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 为假。 |
< (小于) | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= (大于或等于) | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 为假。 |
<= (小于或等于) | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
位运算符
Java 定义了几个位运算符,这些运算符可以应用于整数类型、long、int、short、char 和 byte。
位运算符作用于位并执行逐位运算。假设 a = 60 且 b = 13;现在以二进制格式,它们将如下所示:
a = 0011 1100 b = 0000 1101 a&b = 0000 1100 a|b = 0011 1101 a^b = 0011 0001 ~a = 1100 0011
下表列出了位运算符:
假设整数变量 A 保持 60,变量 B 保持 13,则:
运算符 | 描述 | 示例 |
---|---|---|
& (按位与) | 二进制 AND 运算符如果位同时存在于两个操作数中,则将该位复制到结果中。 | (A & B) 将得到 12,即 0000 1100 |
| (按位或) | 二进制 OR 运算符如果位存在于任一操作数中,则复制该位。 | (A | B) 将得到 61,即 0011 1101 |
^ (按位异或) | 二进制 XOR 运算符如果位在一个操作数中设置但不在两个操作数中都设置,则复制该位。 | (A ^ B) 将得到 49,即 0011 0001 |
~ (按位非) | 二进制反码运算符是单目运算符,其作用是“翻转”位。 | (~A) 将得到 -61,由于是有符号二进制数,因此在二进制补码形式下为 1100 0011。 |
<< (左移) | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 | A << 2 将得到 240,即 1111 0000 |
>> (右移) | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 | A >> 2 将得到 15,即 0000 1111 |
>>> (零填充右移) | 右移零填充运算符。左操作数的值向右移动右操作数指定的位数,并用零填充移位的值。 | A >>> 2 将得到 15,即 0000 1111 |
逻辑运算符
下表列出了逻辑运算符:
假设布尔变量 A 保持 true,变量 B 保持 false,则:
运算符 | 描述 | 示例 |
---|---|---|
&& (逻辑与) | 称为逻辑 AND 运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假 |
|| (逻辑或) | 称为逻辑或运算符。如果两个操作数中任何一个非零,则条件为真。 | (A || B) 为真 |
! (逻辑非) | 称为逻辑非运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑非运算符将使其为假。 | !(A && B) 为真 |
赋值运算符
以下是 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 语言还支持一些其他运算符。
条件运算符 (? :)
条件运算符也称为**三元运算符**。此运算符包含三个操作数,用于评估布尔表达式。运算符的目标是决定应为变量赋值哪个值。运算符写为:
variable x = (expression) ? value if true : value if false
以下是一个示例:
示例
在此示例中,我们创建了两个变量 a 和 b,并使用**三元运算符**确定了 b 的值并打印出来。
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 : 30 Value of b is : 20
instanceof 运算符
此运算符仅用于对象引用变量。运算符检查对象是否为特定类型(类类型或接口类型)。instanceof 运算符写为:
( Object reference variable ) instanceof (class/interface type)
如果运算符左侧变量引用的对象通过右侧类/接口类型的 IS-A 检查,则结果为真。以下是一个示例:
示例
在此示例中,我们创建了一个 String 变量 name,然后使用**instanceof 运算符**检查 name 是否为 String 类型。
public class Test { public static void main(String args[]) { String name = "James"; // following will return true since name is type of String boolean result = name instanceof String; System.out.println( result ); } }
输出
true
如果被比较的对象与右侧的类型赋值兼容,则此运算符仍将返回 true。以下是一个额外的示例:
示例
在此示例中,我们创建了一个 Vehicle 类的变量**a**,然后使用**instanceof 运算符**检查 name 是否为 Car 类型。
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 运算符的优先级
运算符优先级决定了表达式中项的组合方式。这会影响表达式的计算方式。某些运算符比其他运算符具有更高的优先级;例如,乘法运算符的优先级高于加法运算符:
例如,x = 7 + 3 * 2;此处 x 赋值为 13,而不是 20,因为运算符 * 的优先级高于 +,所以它首先与 3 * 2 相乘,然后加到 7 中。
此处,优先级最高的运算符出现在表的上方,优先级最低的出现在下方。在表达式中,将首先计算优先级较高的运算符。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | expression++ expression-- | 从左到右 |
一元 | ++expression --expression +expression -expression ⁓ ! | 从右到左 |
乘法 | * / % | 从左到右 |
加法 | + - | 从左到右 |
移位 | << >> >>> | 从左到右 |
关系 | < > <= >= instanceof | 从左到右 |
相等 | == != | 从左到右 |
按位与 | & | 从左到右 |
按位异或 | ^ | 从左到右 |
按位或 | | | 从左到右 |
逻辑与 | && | 从左到右 |
逻辑或 | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %= ^= |= <<= >>= >>>= | 从右到左 |
下一步是什么?
下一章将解释 Java 编程中的循环控制。本章将描述各种类型的循环以及如何在 Java 程序开发中使用这些循环以及它们的使用目的。
Java - 循环控制
可能需要多次执行一段代码的情况。通常,语句按顺序执行:函数中的第一个语句首先执行,然后是第二个语句,依此类推。
编程语言提供各种控制结构,允许更复杂的执行路径。
**循环**语句允许我们多次执行语句或语句组,以下是大多数编程语言中循环语句的一般形式:
Java 编程语言提供以下类型的循环来处理循环需求。点击以下链接查看详细信息。
序号 | 循环和描述 |
---|---|
1 | while 循环
在给定条件为真时重复语句或语句组。它在执行循环体之前测试条件。 |
2 | for 循环
多次执行一系列语句,并缩写管理循环变量的代码。 |
3 | do...while 循环
类似于 while 语句,不同之处在于它在循环体末尾测试条件。 |
循环控制语句
循环控制语句会更改其正常顺序的执行。当执行离开作用域时,在该作用域中创建的所有自动对象都会被销毁。
Java 支持以下控制语句。点击以下链接查看详细信息。
序号 | 控制语句和描述 |
---|---|
1 | break 语句
终止**循环**或**switch**语句,并将执行转移到循环或switch之后的语句。 |
2 | continue 语句
导致循环跳过其主体其余部分,并在重新迭代之前立即重新测试其条件。 |
Java 中的增强型 for 循环
从 Java 5 开始,引入了增强型 for 循环。这主要用于遍历集合元素,包括数组。
语法
以下是增强型 for 循环的语法:
for(declaration : expression) { // Statements }
**声明** - 新声明的块变量,其类型与您要访问的数组的元素兼容。该变量将在 for 块中可用,其值将与当前数组元素相同。
**表达式** - 此表达式计算出您需要循环遍历的数组。表达式可以是数组变量或返回数组的方法调用。
示例
在此示例中,我们展示了使用 foreach 循环打印数组内容的方法。在这里,我们创建了一个整数数组 numbers 并为其初始化了一些值。然后使用 foreach 循环打印每个数字。创建另一个字符串数组 names 并迭代它以使用 foreach 循环打印每个元素。
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("\n"); 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,
更多关于For each Loop的示例
下一步是什么?
在下一章中,我们将学习 Java 编程中的决策语句。
Java - 决策制定
决策结构具有一个或多个条件,由程序进行评估或测试,以及如果确定条件为真则要执行的语句或语句,以及可选地,如果确定条件为假则要执行的其他语句。
以下是大多数编程语言中典型的决策结构的一般形式:
Java 编程语言提供以下类型的决策语句。点击以下链接查看详细信息。
序号 | 语句和描述 |
---|---|
1 | if 语句
**if 语句**由一个布尔表达式和一个或多个语句组成。 |
2 | if...else 语句
**if 语句**可以后跟可选的**else 语句**,当布尔表达式为假时执行。 |
3 | 嵌套 if 语句
可以在另一个**if**或**else if**语句中使用一个**if**或**else if**语句。 |
4 | switch 语句
switch语句允许将变量与一系列值进行相等性测试。 |
? : 运算符
我们在上一章中介绍了**条件运算符 ? :**,它可以用来代替**if...else**语句。它具有以下一般形式:
Exp1 ? Exp2 : Exp3;
其中 Exp1、Exp2 和 Exp3 是表达式。注意冒号的使用和位置。
要确定整个表达式的值,首先计算 exp1。
如果 exp1 的值为真,则 Exp2 的值将成为整个表达式的值。
如果 exp1 的值为假,则计算 Exp3,其值成为整个表达式的值。
示例
在此示例中,我们创建了两个变量 a 和 b,并使用**三元运算符**确定了 b 的值并打印出来。
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 : 30 Value of b is : 20
下一步是什么?
在下一章中,我们将讨论 Java 语言中的 Number 类(在 java.lang 包中)及其子类。
我们将研究一些使用这些类的实例而不是原始数据类型的情况,以及在使用数字时需要了解的格式化、数学函数等类。
Java - if-else 语句
if语句后面可以跟一个可选的else语句,当布尔表达式为false时执行。
语法
以下是 if...else 语句的语法:
if(Boolean_expression) { // Executes when the Boolean expression is true }else { // Executes when the Boolean expression is false }
如果布尔表达式计算结果为真,则执行 if 代码块,否则执行 else 代码块。
流程图
示例 1
在此示例中,我们展示了 if else 语句的使用。我们创建了一个变量 x 并将其初始化为 30。然后在 if 语句中,我们用 20 检查 x。由于 if 语句为假,因此执行 else 块中的语句。
public class Test { public static void main(String args[]) { int x = 30; if( x < 20 ) { System.out.print("This is if statement"); }else { System.out.print("This is else statement"); } } }
输出
This is else statement
if...else if...else 语句
if 语句可以后跟可选的else if...else语句,这对于使用单个 if...else if 语句测试各种条件非常有用。
使用 if、else if、else 语句时,需要注意以下几点。
if 可以有零个或一个 else,并且它必须放在任何 else if 之后。
if 可以有零到多个 else if,并且它们必须放在 else 之前。
一旦 else if 成功,将不会测试任何剩余的 else if 或 else。
语法
以下是 if...else 语句的语法:
if(Boolean_expression 1) { // Executes when the Boolean expression 1 is true }else if(Boolean_expression 2) { // Executes when the Boolean expression 2 is true }else if(Boolean_expression 3) { // Executes when the Boolean expression 3 is true }else { // Executes when the none of the above condition is true. }
示例 2
在此示例中,我们展示了 if...else if...else 语句的使用。我们创建了一个变量 x 并将其初始化为 30。然后在 if 语句中,我们用 10 检查 x。由于 if 语句为假,控制跳转到 else if 语句,用 x 检查另一个值,依此类推。
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("This is else statement"); } } }
输出
Value of X is 30
示例 3
在此示例中,我们展示了 if...else if...else 语句的使用。我们创建了一个变量 x 并将其初始化为 30.0。然后在 if 语句中,我们用 10.0 检查 x。由于 if 语句为假,控制跳转到 else if 语句,用 x 检查另一个值,依此类推。
public class Test { public static void main(String args[]) { double x = 30.0; if( x == 10.0 ) { System.out.print("Value of X is 10.0"); }else if( x == 20.0 ) { System.out.print("Value of X is 20.0"); }else if( x == 30.0 ) { System.out.print("Value of X is 30.0"); }else { System.out.print("This is else statement"); } } }
输出
Value of X is 30.0
Java - switch 语句
**switch**语句允许测试变量与值的列表是否相等。每个值都称为一个 case,并且会针对每个 case 检查正在切换的变量。
语法
增强型 switch 语句的语法为:
switch(expression) { case value : // Statements break; // optional case value : // Statements break; // optional // You can have any number of case statements. default : // Optional // Statements }
以下规则适用于**switch**语句:
switch 语句中使用的变量只能是整数、可转换整数(byte、short、char)、字符串和枚举。
switch 中可以有任意数量的 case 语句。每个 case 后跟要比较的值和一个冒号。
case的值必须与switch语句中变量的数据类型相同,并且必须是常量或字面量。
当被切换的变量等于某个case时,紧跟在这个case后面的语句将被执行,直到遇到break语句。
遇到break语句时,switch语句终止,控制流跳转到switch语句后的下一行。
并非每个case都需要包含break。如果没有break,控制流将贯穿到后续的case,直到遇到break。
switch语句可以有一个可选的default case,它必须出现在switch的末尾。当没有一个case为真时,可以使用default case执行任务。default case不需要break。
流程图
示例 1
在这个例子中,我们展示了switch语句的使用,其中case基于char类型。我们创建了一个变量grade。根据grade的值,检查每个case。如果一个case满足条件并且存在break语句,则不会检查后面的case。
public class Test { public static void main(String args[]) { char grade = 'C'; switch(grade) { case 'A' : System.out.println("Excellent!"); break; case 'B' : case 'C' : System.out.println("Well done"); break; case 'D' : System.out.println("You passed"); case 'F' : System.out.println("Better try again"); break; default : System.out.println("Invalid grade"); } System.out.println("Your grade is " + grade); } }
使用各种命令行参数编译并运行上述程序。这将产生以下结果:
输出
Well done Your grade is C
示例 2
在这个例子中,我们展示了switch语句的使用,其中case基于int类型。我们创建了一个变量grade。根据grade的值,检查每个case。如果一个case满足条件并且存在break语句,则不会检查后面的case。
public class Test { public static void main(String args[]) { int grade = 3; switch(grade) { case 1 : System.out.println("Excellent!"); break; case 2 : case 3 : System.out.println("Well done"); break; case 4 : System.out.println("You passed"); case 5 : System.out.println("Better try again"); break; default : System.out.println("Invalid grade"); } System.out.println("Your grade is " + grade); } }
使用各种命令行参数编译并运行上述程序。这将产生以下结果:
输出
Well done Your grade is 3
示例 3
在这个例子中,我们展示了switch语句的使用,其中case基于String类型。我们创建了一个变量grade。根据grade的值,检查每个case。如果一个case满足条件并且存在break语句,则不会检查后面的case。
public class Test { public static void main(String args[]) { String grade = "C"; switch(grade) { case "A" : System.out.println("Excellent!"); break; case "B" : case "C" : System.out.println("Well done"); break; case "D" : System.out.println("You passed"); case "F" : System.out.println("Better try again"); break; default : System.out.println("Invalid grade"); } System.out.println("Your grade is " + grade); } }
使用各种命令行参数编译并运行上述程序。这将产生以下结果:
输出
Well done Your grade is C
Java - for循环
for循环是一种重复控制结构,允许你高效地编写需要执行特定次数的循环。
当你知道任务需要重复多少次时,for循环非常有用。
语法
for循环的语法如下:
for(initialization; Boolean_expression; update) { // Statements }
以下是for循环的控制流程:
初始化步骤首先执行,并且只执行一次。此步骤允许你声明和初始化任何循环控制变量,此步骤以分号(;)结尾。
接下来,布尔表达式将被评估。如果为真,则执行循环体。如果为假,则不会执行循环体,控制流跳转到for循环后的下一条语句。
for循环体执行完毕后,控制流跳回到更新语句。此语句允许你更新任何循环控制变量。此语句可以留空,并在末尾添加分号。
布尔表达式现在再次被评估。如果为真,则循环执行,并重复此过程(循环体,然后更新步骤,然后布尔表达式)。布尔表达式为假后,for循环终止。
流程图
示例 1
在这个例子中,我们展示了for循环的使用,用于打印从10到19的数字。在这里,我们在for循环的初始化块中用值10初始化了一个int变量x。然后在表达式块中,我们检查x是否小于20,最后在更新块中,我们将x加1。在for循环体中,我们打印x的值。for循环将运行直到x变为20。一旦x为20,循环将停止执行,程序退出。
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("\n"); } } }
输出
value of x : 10 value of x : 11 value of x : 12 value of x : 13 value of x : 14 value of x : 15 value of x : 16 value of x : 17 value of x : 18 value of x : 19
示例 2
在这个例子中,我们展示了for循环的使用,用于打印数组的内容。在这里,我们创建一个整数数组numbers并初始化一些值。我们创建了一个名为index的变量来表示for循环中数组的索引,将其与数组的大小进行比较,并将其加1。在for循环体中,我们使用索引表示法打印数组的元素。一旦index与数组大小相同,for循环退出,程序结束。
public class Test { public static void main(String args[]) { int [] numbers = {10, 20, 30, 40, 50}; for(int index = 0; index < numbers.length; index++) { System.out.print("value of item : " + numbers[index] ); System.out.print("\n"); } } }
输出
value of item : 10 value of item : 20 value of item : 30 value of item : 40 value of item : 50
示例 3
在这个例子中,我们展示了使用for循环实现无限循环。它将一直打印数字,直到你按下ctrl+c终止程序。
public class Test { public static void main(String args[]) { int x = 10; for( ;; ) { System.out.print("value of x : " + x ); x++; System.out.print("\n"); } } }
输出
value of item : 10 value of item : 11 value of item : 12 value of item : 13 value of item : 14 ... ctrl+c
Java - foreach循环
foreach循环是一种特殊的重复控制结构,允许你高效地编写需要执行特定次数的循环。
即使你不知道任务需要重复多少次,foreach循环也很有用。
语法
以下是增强型 for 循环的语法:
for(declaration : expression) { // Statements }
**声明** - 新声明的块变量,其类型与您要访问的数组的元素兼容。该变量将在 for 块中可用,其值将与当前数组元素相同。
**表达式** - 此表达式计算出您需要循环遍历的数组。表达式可以是数组变量或返回数组的方法调用。
示例 1
在这个例子中,我们展示了foreach循环的使用,用于打印整数列表的内容。在这里,我们创建一个整数列表numbers并初始化一些值。然后使用foreach循环打印每个数字。
import java.util.Arrays; import java.util.List; public class Test { public static void main(String args[]) { List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50); for(Integer x : numbers ) { System.out.print( x ); System.out.print(","); } } }
输出
10, 20, 30, 40, 50,
示例 2
在这个例子中,我们展示了foreach循环的使用,用于打印字符串列表的内容。在这里,我们创建一个字符串数组names并初始化一些值。然后使用foreach循环打印每个name。
import java.util.Arrays; import java.util.List; public class Test { public static void main(String args[]) { List<String> names = Arrays.asList("James", "Larry", "Tom", "Lacy"); for( String name : names ) { System.out.print( name ); System.out.print(","); } } }
输出
James, Larry, Tom, Lacy,
示例 3
在这个例子中,我们展示了foreach循环的使用,用于打印Student对象的数组内容。在这里,我们创建一个Student对象的数组students并初始化一些值。然后使用foreach循环打印每个name。
public class Test { public static void main(String args[]) { Student[] students = { new Student(1, "Julie"), new Student(3, "Adam"), new Student(2, "Robert") }; for( Students student : students ) { System.out.print( student ); System.out.print(","); } } } class Student { int rollNo; String name; Student(int rollNo, String name){ this.rollNo = rollNo; this.name = name; } @Override public String toString() { return "[ " + this.rollNo + ", " + this.name + " ]"; } }
输出
[ 1, Julie ],[ 3, Adam ],[ 2, Robert ],
Java - while循环
Java 编程语言中的while循环语句重复执行目标语句,只要给定的条件为真。
语法
while循环的语法如下:
while(Boolean_expression) { // Statements }
这里,语句可以是单个语句或语句块。条件可以是任何表达式,非零值表示真。
执行时,如果布尔表达式结果为真,则将执行循环内的操作。只要表达式的结果为真,这将继续进行。
当条件变为假时,程序控制将传递到紧跟在循环后面的行。
流程图
这里,while循环的关键点是循环可能永远不会运行。当表达式被测试且结果为假时,循环体将被跳过,并将执行while循环后的第一条语句。
示例 1
在这个例子中,我们展示了while循环的使用,用于打印从10到19的数字。在这里,我们用值10初始化了一个int变量x。然后在while循环中,我们检查x是否小于20,在while循环内,我们打印x的值并将x的值加1。while循环将运行直到x变为20。一旦x为20,循环将停止执行,程序退出。
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("\n"); } } }
输出
value of x : 10 value of x : 11 value of x : 12 value of x : 13 value of x : 14 value of x : 15 value of x : 16 value of x : 17 value of x : 18 value of x : 19
示例 2
在这个例子中,我们展示了while循环的使用,用于打印数组的内容。在这里,我们创建一个整数数组numbers并初始化一些值。我们创建了一个名为index的变量来表示迭代数组时的数组索引。在while循环中,我们检查index是否小于数组的大小,并使用索引表示法打印数组的元素。index变量加1,循环继续直到index变为数组大小,循环退出。
public class Test { public static void main(String args[]) { int [] numbers = {10, 20, 30, 40, 50}; int index = 0; while( index < 5 ) { System.out.print("value of item : " + numbers[index] ); index++; System.out.print("\n"); } } }
输出
value of item : 10 value of item : 20 value of item : 30 value of item : 40 value of item : 50
示例 3
在这个例子中,我们展示了使用while循环实现无限循环。它将一直打印数字,直到你按下ctrl+c终止程序。
public class Test { public static void main(String args[]) { int x = 10; while( true ) { System.out.print("value of x : " + x ); x++; System.out.print("\n"); } } }
输出
value of item : 10 value of item : 20 value of item : 30 value of item : 40 value of item : 50 ... ctrl+c
Java - do...while循环
do...while循环类似于while循环,不同之处在于do...while循环至少执行一次。
语法
do...while循环的语法如下:
do { // Statements }while(Boolean_expression);
请注意,布尔表达式出现在循环的末尾,因此循环中的语句将在测试布尔表达式之前执行一次。
如果布尔表达式为真,控制流将跳回到do语句,循环中的语句将再次执行。这个过程重复直到布尔表达式为假。
流程图
示例 1
在这个例子中,我们展示了while循环的使用,用于打印从10到19的数字。在这里,我们用值10初始化了一个int变量x。然后在do while循环中,我们在do while循环体之后检查x是否小于20。在do while循环体中,我们打印x的值并将x的值加1。while循环将运行直到x变为20。一旦x为20,循环将停止执行,程序退出。
public class Test { public static void main(String args[]) { int x = 10; do { System.out.print("value of x : " + x ); x++; System.out.print("\n"); }while( x < 20 ); } }
输出
value of x : 10 value of x : 11 value of x : 12 value of x : 13 value of x : 14 value of x : 15 value of x : 16 value of x : 17 value of x : 18 value of x : 19
示例 2
在这个例子中,我们展示了do while循环的使用,用于打印数组的内容。在这里,我们创建一个整数数组numbers并初始化一些值。我们创建了一个名为index的变量来表示迭代数组时的数组索引。在do while循环中,我们在循环体之后检查index是否小于数组的大小,并使用索引表示法打印数组的元素。在循环体内,index变量加1,循环继续直到index变为数组大小,循环退出。
public class Test { public static void main(String args[]) { int [] numbers = {10, 20, 30, 40, 50}; int index = 0; do { System.out.print("value of item : " + numbers[index] ); index++; System.out.print("\n"); } while( index < 5 ); } }
输出
value of item : 10 value of item : 20 value of item : 30 value of item : 40 value of item : 50
示例 3
在这个例子中,我们展示了使用while循环实现无限循环。它将一直打印数字,直到你按下ctrl+c终止程序。
public class Test { public static void main(String args[]) { int x = 10; do { System.out.print("value of x : " + x ); x++; System.out.print("\n"); } while( true ); } }
输出
value of item : 10 value of item : 20 value of item : 30 value of item : 40 value of item : 50 ... ctrl+c
Java - break语句
Java 编程语言中的break语句有以下两种用法:
当在循环内遇到break语句时,循环会立即终止,程序控制会在循环后的下一条语句处继续。
它可以用来终止switch语句中的case(在下一章中介绍)。
语法
break的语法是在任何循环内的一个单一语句:
break;
流程图
示例 1
在这个例子中,我们展示了break语句的使用,用于中断while循环,打印从10到14的数字,否则将打印到19的元素。在这里,我们用值10初始化了一个int变量x。然后在while循环中,我们检查x是否小于20,在while循环内,我们打印x的值并将x的值加1。while循环将运行直到x变为15。一旦x为15,break语句将中断while循环,程序退出。
public class Test { public static void main(String args[]) { int x = 10; while( x < 20 ) { if(x == 15){ break; } System.out.print("value of x : " + x ); x++; System.out.print("\n"); } } }
输出
value of x : 10 value of x : 11 value of x : 12 value of x : 13 value of x : 14
示例 2
在这个例子中,我们展示了break语句在for循环中的使用,用于打印数组的几个元素而不是所有元素。在这里,我们创建一个整数数组numbers并初始化一些值。我们创建了一个名为index的变量来表示for循环中数组的索引,将其与数组的大小进行比较,并将其加1。在for循环体中,我们使用索引表示法打印数组的元素。一旦遇到值30,break语句将中断for循环的流程,程序结束。
public class Test { public static void main(String args[]) { int [] numbers = {10, 20, 30, 40, 50}; for(int index = 0; index < numbers.length; index++) { if(numbers[index] == 30){ break; } System.out.print("value of item : " + numbers[index] ); System.out.print("\n"); } } }
输出
value of item : 10 value of item : 20
示例 3
在这个例子中,我们展示了break语句用于中断使用while循环的无限循环。它将一直打印数字,直到x的值变为15。
public class Test { public static void main(String args[]) { int x = 10; while( true ) { System.out.print("value of x : " + x ); x++; if(x == 15) { break; } System.out.print("\n"); } } }
输出
value of item : 10 value of item : 11 value of item : 12 value of item : 13 value of item : 14
Java - continue语句
continue关键字可以在任何循环控制结构中使用。它导致循环立即跳转到循环的下一个迭代。
在for循环中,continue关键字导致控制立即跳转到更新语句。
在while循环或do/while循环中,控制立即跳转到布尔表达式。
语法
continue的语法是在任何循环内的一个单一语句:
continue;
流程图
示例 1
在这个例子中,我们展示了continue语句的使用,用于跳过while循环中的元素15,该循环用于打印从10到19的元素。在这里,我们用值10初始化了一个int变量x。然后在while循环中,我们检查x是否小于20,在while循环内,我们打印x的值并将x的值加1。while循环将运行直到x变为15。一旦x为15,continue语句将跳过while循环,同时跳过主体的执行,循环继续。
public class Test { public static void main(String args[]) { int x = 10; while( x < 20 ) { x++; if(x == 15){ continue; } System.out.print("value of x : " + x ); System.out.print("\n"); } } }
输出
value of x : 11 value of x : 12 value of x : 13 value of x : 14 value of x : 16 value of x : 17 value of x : 18 value of x : 19 value of x : 20
示例 2
本例展示了如何在 for 循环中使用 continue 语句跳过数组元素的打印。这里我们创建了一个整数数组 numbers 并初始化了一些值。我们创建了一个名为 index 的变量来表示 for 循环中数组的索引,将其与数组的大小进行比较,并将其递增 1。在 for 循环体中,我们使用索引表示法打印数组的元素。一旦遇到值为 30,continue 语句就会跳转到 for 循环的更新部分,循环继续。
public class Test { public static void main(String args[]) { int [] numbers = {10, 20, 30, 40, 50}; for(int index = 0; index < numbers.length; index++) { if(numbers[index] == 30){ continue; } System.out.print("value of item : " + numbers[index] ); System.out.print("\n"); } } }
输出
value of item : 10 value of item : 20 value of item : 40 value of item : 50
示例 3
本例展示了如何在 do while 循环中使用 continue 语句跳过元素 15,该循环用于打印从 10 到 19 的元素。这里我们用值 10 初始化了一个 int 变量 x。然后在 do while 循环中,我们在循环体之后检查 x 是否小于 20,在 while 循环中,我们打印 x 的值并将 x 的值递增 1。while 循环将运行直到 x 变成 15。一旦 x 为 15,continue 语句将跳过 while 循环,跳过循环体的执行,循环继续。
public class Test { public static void main(String args[]) { int x = 10; do { x++; if(x == 15){ continue; } System.out.print("value of x : " + x ); System.out.print("\n"); } while( x < 20 ); } }
输出
value of x : 11 value of x : 12 value of x : 13 value of x : 14 value of x : 16 value of x : 17 value of x : 18 value of x : 19 value of x : 20
Java - 对象和类
Java 是一种面向对象的语言。作为一种具有面向对象特性的语言,Java 支持以下基本概念:
- 多态性
- 继承
- 封装
- 抽象
- 类
- 对象
- 实例
- 方法
- 消息传递
本章我们将深入研究类和对象这两个概念。
对象 - 对象具有状态和行为。例如:狗具有状态——颜色、名字、品种,以及行为——摇尾巴、吠叫、吃东西。对象是类的实例。
类 - 类可以定义为一种模板/蓝图,它描述了其类型对象支持的行为/状态。
Java 中的对象
现在让我们深入了解一下什么是对象。如果我们考虑现实世界,我们可以发现我们周围有很多对象,例如汽车、狗、人等等。所有这些对象都具有状态和行为。
如果我们考虑一只狗,那么它的状态是——名字、品种、颜色,而行为是——吠叫、摇尾巴、奔跑。
如果你将软件对象与现实世界中的对象进行比较,它们具有非常相似的特征。
软件对象也具有状态和行为。软件对象的状态存储在字段中,行为通过方法显示。
因此,在软件开发中,方法操作对象的内部状态,对象与对象之间的通信通过方法完成。
Java 中的类
类是一个蓝图,从中创建单个对象。
下面是一个类的示例。
示例
public class Dog { String breed; int age; String color; void barking() { } void hungry() { } void sleeping() { } }
类可以包含以下任何类型的变量。
局部变量 - 在方法、构造函数或代码块内定义的变量称为局部变量。该变量将在方法内声明和初始化,并在方法完成后销毁。
实例变量 - 实例变量是在类内但在任何方法之外的变量。这些变量在类实例化时初始化。实例变量可以从该特定类的任何方法、构造函数或代码块中访问。
类变量 - 类变量是在类内、任何方法之外使用 static 关键字声明的变量。
类可以拥有任意数量的方法来访问各种方法的值。在上面的例子中,barking()、hungry() 和 sleeping() 是方法。
在研究 Java 语言的类时,需要讨论以下一些重要主题。
构造函数
在讨论类时,最重要的子主题之一就是构造函数。每个类都有一个构造函数。如果我们没有显式地为类编写构造函数,Java 编译器将为该类构建一个默认构造函数。
每次创建一个新对象时,至少会调用一个构造函数。构造函数的主要规则是它们应该与类名相同。一个类可以有多个构造函数。
下面是一个构造函数的例子:
示例
public class Puppy { public Puppy() { } public Puppy(String name) { // This constructor has one parameter, name. } }
Java 还支持单例类,你只能创建一个类的实例。
注意 - 我们有两种不同类型的构造函数。我们将在后续章节中详细讨论构造函数。
创建对象
如前所述,类为对象提供蓝图。所以基本上,对象是由类创建的。在 Java 中,new 关键字用于创建新对象。
从类创建对象时,有三个步骤:
声明 - 使用对象类型的变量名声明变量。
实例化 - 使用 'new' 关键字创建对象。
初始化 - 'new' 关键字后跟对构造函数的调用。此调用初始化新对象。
下面是创建对象的示例:
示例
在这个例子中,我们创建了一个名为 Puppy 的类。在 Puppy 类的构造函数中,打印幼犬的名字,以便在创建对象时打印其名字。在 main 方法中,使用 new 运算符创建一个对象。
public class Puppy { public Puppy(String name) { // This constructor has one parameter, <i>name</i>. System.out.println("Passed Name is :" + name ); } public static void main(String []args) { // Following statement would create an object myPuppy Puppy myPuppy = new Puppy( "tommy" ); } }
如果我们编译并运行上面的程序,它将产生以下结果:
输出
Passed Name is :tommy
访问实例变量和方法
实例变量和方法通过创建的对象访问。要访问实例变量,完整的限定路径如下:
/* First create an object */ ObjectReference = new Constructor(); /* Now call a variable as follows */ ObjectReference.variableName; /* Now you can call a class method as follows */ ObjectReference.MethodName();
示例
在这个例子中,我们创建了一个名为 Puppy 的类。在 Puppy 类的构造函数中,打印幼犬的名字,以便在创建对象时打印其名字。添加了一个实例变量 puppyAge,并使用 getter/setter 方法,我们可以操作年龄。在 main 方法中,使用 new 运算符创建一个对象。使用 setAge() 方法更新年龄,并使用 getAge() 打印年龄。
public class Puppy { int puppyAge; public Puppy(String name) { // This constructor has one parameter, <i>name</i>. System.out.println("Name chosen 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) { /* Object creation */ Puppy myPuppy = new Puppy( "tommy" ); /* Call class method to set puppy's age */ myPuppy.setAge( 2 ); /* Call another class method to get puppy's age */ myPuppy.getAge( ); /* You can access instance variable as follows as well */ System.out.println("Variable Value :" + myPuppy.puppyAge ); } }
如果我们编译并运行上面的程序,它将产生以下结果:
输出
Name chosen is :tommy Puppy's age is :2 Variable Value :2
源文件声明规则
作为本节的最后一部分,现在让我们来看看源文件声明规则。在源文件中声明类、import 语句和package 语句时,这些规则至关重要。
每个源文件只能有一个公共类。
源文件可以有多个非公共类。
公共类名也应该与源文件名相同,源文件名末尾应附加.java。例如:类名是public class Employee{},则源文件应为 Employee.java。
如果类在包内定义,则包语句应该是源文件中的第一条语句。
如果存在 import 语句,则必须将它们写在包语句和类声明之间。如果没有包语句,则 import 语句应该是源文件中的第一行。
Import 和 package 语句将应用于源文件中所有存在的类。不可能为源文件中的不同类声明不同的 import 和/或 package 语句。
类有几个访问级别,并且有不同类型的类;抽象类、最终类等。我们将在访问修饰符章节中解释所有这些。
除了上述类型的类之外,Java 还有一些特殊的类,称为内部类和匿名类。
Java 包
简单来说,它是一种对类和接口进行分类的方法。在使用 Java 开发应用程序时,将编写数百个类和接口,因此对这些类进行分类是必须的,并且使生活更轻松。
导入语句
在 Java 中,如果给出了一个完全限定名(包括包名和类名),则编译器可以轻松找到源代码或类。导入语句是为编译器提供正确位置以查找特定类的一种方法。
例如,以下行将要求编译器加载 java_installation/java/io 目录中可用的所有类:
import java.io.*;
一个简单的案例研究
对于我们的案例研究,我们将创建两个类。它们是 Employee 和 EmployeeTest。
首先打开记事本并添加以下代码。记住这是一个 Employee 类,并且该类是一个公共类。现在,将此源文件保存为 Employee.java。
Employee 类有四个实例变量 - name、age、designation 和 salary。该类有一个显式定义的构造函数,它接受一个参数。
示例
import java.io.*; public class Employee { String name; int age; String designation; double salary; // This is the constructor of the class Employee public Employee(String name) { this.name = name; } // Assign the age of the Employee to the variable age. public void empAge(int empAge) { age = empAge; } /* Assign the designation to the variable designation.*/ public void empDesignation(String empDesig) { designation = empDesig; } /* Assign the salary to the variable salary.*/ public void empSalary(double empSalary) { salary = empSalary; } /* Print the Employee details */ public void printEmployee() { System.out.println("Name:"+ name ); System.out.println("Age:" + age ); System.out.println("Designation:" + designation ); System.out.println("Salary:" + salary); } }
如本教程前面提到的,处理从 main 方法开始。因此,为了运行此 Employee 类,应该有一个 main 方法,并且应该创建对象。我们将为这些任务创建一个单独的类。
以下是EmployeeTest 类,它创建 Employee 类的两个实例,并调用每个对象的方法为每个变量赋值。
将以下代码保存在 EmployeeTest.java 文件中。
import java.io.*; public class EmployeeTest { public static void main(String args[]) { /* Create two objects using constructor */ Employee empOne = new Employee("James Smith"); Employee empTwo = new Employee("Mary Anne"); // Invoking methods for each object created 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.java C:\> javac EmployeeTest.java C:\> java EmployeeTest Name:James Smith Age:26 Designation:Senior Software Engineer Salary:1000.0 Name:Mary Anne Age:21 Designation:Software Engineer Salary:500.0
下一步是什么?
在下一节中,我们将讨论 Java 中的基本数据类型以及在开发 Java 应用程序时如何使用它们。
Java - 方法
Java 方法是将语句组合在一起以执行操作的语句集合。例如,当你调用 System.out.println() 方法时,系统实际上执行多个语句以在控制台上显示消息。
现在,你将学习如何创建带有或不带有返回值的方法,调用带有或不带有参数的方法,以及在程序设计中应用方法抽象。
创建方法
考虑以下示例来解释方法的语法:
语法
public static int methodName(int a, int b) { // body }
这里,
public static - 修饰符
int - 返回类型
methodName - 方法名称
a, b - 形式参数
int a, int b - 参数列表
方法定义由方法头和方法体组成。在以下语法中显示相同的内容:
语法
modifier returnType nameOfMethod (Parameter List) { // method body }
上面显示的语法包括:
修饰符 - 它定义方法的访问类型,使用是可选的。
returnType - 方法可能返回一个值。
nameOfMethod − 这是方法名。方法签名由方法名和参数列表组成。
参数列表 − 参数的列表,包括参数的类型、顺序和数量。参数是可选的,方法可以不包含任何参数。
方法体 − 方法体定义了方法使用语句执行的操作。
示例
以下是上面定义的名为min()的方法的源代码。此方法接受两个参数num1和num2,并返回两者之间的最大值:
/** the snippet returns the minimum between two numbers */ public static int minFunction(int n1, int n2) { int min; if (n1 > n2) min = n2; else min = n1; return min; }
方法调用
要使用方法,必须先调用它。调用方法有两种方式:方法返回值或不返回值(无返回值)。
方法调用的过程很简单。当程序调用一个方法时,程序控制权会转移到被调用的方法。然后,被调用的方法会在两种情况下将控制权返回给调用者:
- 执行return语句。
- 到达方法结束的闭合大括号。
返回void的方法被视为对语句的调用。让我们来看一个例子:
System.out.println("This is tutorialspoint.com!");
返回有值的方法可以通过以下示例理解:
int result = sum(6, 9);
以下示例演示了如何定义方法以及如何调用方法:
示例
public class ExampleMinNumber { public static void main(String[] args) { int a = 11; int b = 6; int c = minFunction(a, b); System.out.println("Minimum Value = " + c); } /** returns the minimum of two numbers */ public static int minFunction(int n1, int n2) { int min; if (n1 > n2) min = n2; else min = n1; return min; } }
输出
Minimum value = 6
void关键字
void关键字允许我们创建不返回值的方法。在下面的示例中,我们考虑一个void方法`methodRankPoints`。此方法是一个void方法,不返回任何值。对void方法的调用必须是一个语句,例如`methodRankPoints(255.7);`。这是一个Java语句,以分号结尾,如下例所示。
示例
public class ExampleVoid { public static void main(String[] args) { methodRankPoints(255.7); } public static void methodRankPoints(double points) { if (points >= 202.5) { System.out.println("Rank:A1"); }else if (points >= 122.4) { System.out.println("Rank:A2"); }else { System.out.println("Rank:A3"); } } }
输出
Rank:A1
按值传递参数
在调用过程中,需要传递参数。这些参数的顺序应与其在方法规范中相应参数的顺序相同。参数可以按值传递或按引用传递。
按值传递参数意味着使用参数调用方法。通过这种方式,参数值将传递给参数。
示例
下面的程序显示了一个按值传递参数的示例。即使在方法调用之后,参数的值也保持不变。
public class swappingExample { public static void main(String[] args) { int a = 30; int b = 45; System.out.println("Before swapping, a = " + a + " and b = " + b); // Invoke the swap method swapFunction(a, b); System.out.println("\n**Now, Before and After swapping values will be same here**:"); System.out.println("After swapping, a = " + a + " and b is " + b); } public static void swapFunction(int a, int b) { System.out.println("Before swapping(Inside), a = " + a + " b = " + b); // Swap n1 with n2 int c = a; a = b; b = c; System.out.println("After swapping(Inside), a = " + a + " b = " + b); } }
输出
Before swapping, a = 30 and b = 45 Before swapping(Inside), a = 30 b = 45 After swapping(Inside), a = 45 b = 30 **Now, Before and After swapping values will be same here**: After swapping, a = 30 and b is 45
方法重载
当一个类具有两个或多个同名但参数不同的方法时,这称为方法重载。它与重写不同。在重写中,方法具有相同的方法名、类型、参数数量等。
让我们考虑前面讨论的用于查找整数类型最小数字的示例。如果,假设我们想要找到双精度类型的最小数字。然后将引入重载的概念来创建两个或多个同名但参数不同的方法。
以下示例解释了这一点:
示例
public class ExampleOverloading { public static void main(String[] args) { int a = 11; int b = 6; double c = 7.3; double d = 9.4; int result1 = minFunction(a, b); // same function name with different parameters double result2 = minFunction(c, d); System.out.println("Minimum Value = " + result1); System.out.println("Minimum Value = " + result2); } // for integer public static int minFunction(int n1, int n2) { int min; if (n1 > n2) min = n2; else min = n1; return min; } // for double public static double minFunction(double n1, double n2) { double min; if (n1 > n2) min = n2; else min = n1; return min; } }
输出
Minimum Value = 6 Minimum Value = 7.3
重载方法使程序更易读。这里给出了两个同名但参数不同的方法。结果是从整数和双精度类型中获得的最小数字。
使用命令行参数
有时,您可能希望在运行程序时向程序传递一些信息。这是通过向`main()`传递命令行参数来实现的。
命令行参数是在执行程序时直接跟在程序名称后面的信息。在Java程序中访问命令行参数非常容易。它们作为字符串存储在传递给`main()`的String数组中。
示例
下面的程序显示了它调用的所有命令行参数:
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]: this args[1]: is args[2]: a args[3]: command args[4]: line args[5]: 200 args[6]: -100
this关键字
this是Java中的一个关键字,用作当前类对象的引用,位于实例方法或构造函数中。使用`this`,您可以引用类的成员,例如构造函数、变量和方法。
注意 − `this`关键字仅在实例方法或构造函数中使用。
通常,`this`关键字用于:
在构造函数或方法中,如果实例变量和局部变量具有相同的名称,则区分它们。
class Student { int age; Student(int age) { this.age = age; } }
在一个类中从另一个构造函数(参数化构造函数或默认构造函数)调用一种类型的构造函数。这称为显式构造函数调用。
class Student { int age Student() { this(20); } Student(int age) { this.age = age; } }
示例
以下是一个使用`this`关键字访问类成员的示例。将以下程序复制并粘贴到名为This_Example.java的文件中。
public class This_Example { // Instance variable num int num = 10; This_Example() { System.out.println("This is an example program on keyword this"); } This_Example(int num) { // Invoking the default constructor this(); // Assigning the local variable <i>num</i> to the instance variable <i>num</i> this.num = num; } public void greet() { System.out.println("Hi Welcome to Tutorialspoint"); } public void print() { // Local variable num int num = 20; // Printing the local variable System.out.println("value of local variable num is : "+num); // Printing the instance variable System.out.println("value of instance variable num is : "+this.num); // Invoking the greet method of a class this.greet(); } public static void main(String[] args) { // Instantiating the class This_Example obj1 = new This_Example(); // Invoking the print method obj1.print(); // Passing a new value to the num variable through parametrized constructor This_Example obj2 = new This_Example(30); // Invoking the print method again obj2.print(); } }
输出
This is an example program on keyword this value of local variable num is : 20 value of instance variable num is : 10 Hi Welcome to Tutorialspoint This is an example program on keyword this value of local variable num is : 20 value of instance variable num is : 30 Hi Welcome to Tutorialspoint
可变参数(var-args)
JDK 1.5允许您向方法传递相同类型的可变数量的参数。方法中的参数声明如下:
typeName... parameterName
在方法声明中,您指定类型后跟省略号(...)。在一个方法中只能指定一个变长参数,并且此参数必须是最后一个参数。任何常规参数都必须在其前面。
示例
public class VarargsDemo { public static void main(String args[]) { // Call method with variable 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.5 The max value is 3.0
finalize()方法
可以定义一个方法,该方法将在垃圾收集器最终销毁对象之前被调用。此方法称为finalize(),可用于确保对象干净地终止。
例如,您可以使用`finalize()`来确保关闭该对象拥有的打开的文件。
要向类添加终结器,只需定义`finalize()`方法即可。Java运行时会在它即将回收该类的对象时调用该方法。
在`finalize()`方法中,您将指定在销毁对象之前必须执行的操作。
`finalize()`方法具有以下一般形式:
protected void finalize( ) { // finalization code here }
这里,关键字`protected`是一个说明符,它阻止其类外部定义的代码访问`finalize()`。
这意味着您无法知道何时甚至是否会执行`finalize()`。例如,如果您的程序在垃圾收集发生之前结束,则`finalize()`将不会执行。
Java - 构造函数
构造函数在创建对象时对其进行初始化。它的名称与其类相同,并且在语法上类似于方法。但是,构造函数没有显式的返回类型。
通常,您将使用构造函数为类定义的实例变量赋初始值,或执行创建完整对象所需的任何其他启动过程。
所有类都有构造函数,无论您是否定义了一个构造函数,因为Java都会自动提供一个默认构造函数,该构造函数将所有成员变量初始化为零。但是,一旦您定义了自己的构造函数,默认构造函数将不再使用。
语法
以下是构造函数的语法:
class ClassName { ClassName() { } }
Java允许两种类型的构造函数:
- 无参数构造函数
- 参数化构造函数
无参数构造函数
顾名思义,Java的无参数构造函数不接受任何参数,而是使用这些构造函数,方法的实例变量将为所有对象初始化为固定值。
示例
Public class MyClass { Int num; MyClass() { num = 100; } }
您将按如下方式调用构造函数来初始化对象:
public class ConsDemo { public static void main(String args[]) { MyClass t1 = new MyClass(); MyClass t2 = new MyClass(); System.out.println(t1.num + " " + t2.num); } }
输出
100 100
参数化构造函数
大多数情况下,您需要一个接受一个或多个参数的构造函数。参数添加到构造函数的方式与添加到方法的方式相同,只需在构造函数名称后的括号内声明它们。
示例
这是一个使用构造函数的简单示例:
// A simple constructor. class MyClass { int x; // Following is the constructor 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
Java - 访问修饰符
Java提供许多访问修饰符来设置类、变量、方法和构造函数的访问级别。四个访问级别是:
- 对包可见,默认为此。不需要修饰符。
- 仅对类可见(private)。
- 对全世界可见(public)。
- 对包和所有子类可见(protected)。
默认访问修饰符 - 无关键字
默认访问修饰符意味着我们没有为类、字段、方法等显式声明访问修饰符。
未声明任何访问控制修饰符的变量或方法可用于同一包中的任何其他类。接口中的字段隐式为`public static final`,接口中的方法默认为`public`。
示例
变量和方法可以不带任何修饰符声明,如下例所示:
String version = "1.5.1"; boolean processOrder() { return true; }
私有访问修饰符 - 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`变量是私有的,因此其他类无法直接检索或设置其值。
因此,为了使此变量对外部世界可用,我们定义了两个公共方法:`getFormat()`,它返回`format`的值,以及`setFormat(String)`,它设置其值。
公共访问修饰符 - public
声明为公共的类、方法、构造函数、接口等可以从任何其他类访问。因此,在公共类中声明的字段、方法、块可以从属于Java宇宙的任何类访问。
但是,如果我们尝试访问的公共类位于不同的包中,则仍然需要导入公共类。由于类继承,类的所有公共方法和变量都由其子类继承。
示例
以下函数使用公共访问控制:
public static void main(String[] arguments) { // ... }
应用程序的`main()`方法必须是公共的。否则,Java解释器(例如java)将无法调用它来运行该类。
受保护访问修饰符 - protected
在超类中声明为受保护的变量、方法和构造函数只能由其他包中的子类或受保护成员类的包中的任何类访问。
`protected`访问修饰符不能应用于类和接口。可以声明方法和字段为`protected`,但是接口中的方法和字段不能声明为`protected`。
`protected`访问使子类有机会使用辅助方法或变量,同时阻止不相关的类尝试使用它。
示例
以下父类使用受保护的访问控制,以允许其子类覆盖`openSpeaker()`方法:
class AudioPlayer { protected boolean openSpeaker(Speaker sp) { // implementation details } } class StreamingAudioPlayer extends AudioPlayer { boolean openSpeaker(Speaker sp) { // implementation details } }
在这里,如果我们将`openSpeaker()`方法定义为私有的,那么除了`AudioPlayer`之外,任何其他类都无法访问它。如果我们将其定义为公共的,那么它将对整个外部世界可见。但是我们的目的是仅将其方法暴露给其子类,这就是我们使用`protected`修饰符的原因。
访问控制和继承
强制执行以下继承方法的规则:
在超类中声明为公共的方法也必须在所有子类中是公共的。
在超类中声明为受保护的方法在子类中必须是受保护的或公共的;它们不能是私有的。
私有方法根本不会被继承,因此没有针对它们的规则。
示例
在这个例子中,我们创建了一个类,其中包含一个私有变量age和一个具有默认作用域的变量name。使用setter/getter方法,我们正在更新age并获取值,而name则直接更新。
public class Puppy { private int age; String name; public Puppy() { } public void setAge( int age ) { this.age = age; } public int getAge( ) { return age; } public static void main(String []args) { Puppy myPuppy = new Puppy(); // update age variable using method call myPuppy.setAge( 2 ); // update name directly myPuppy.name = "Tommy"; System.out.println("Age: " + myPuppy.getAge() +", name: " + myPuppy.name ); } }
输出
Age: 2, name: Tommy
Java - 继承
继承可以定义为一个类获取另一个类的属性(方法和字段)的过程。通过使用继承,信息以分层顺序进行管理。
继承其他类属性的类称为子类(派生类、子类),其属性被继承的类称为超类(基类、父类)。
extends关键字
extends是用于继承类属性的关键字。以下是extends关键字的语法。
语法
class Super { ..... ..... } class Sub extends Super { ..... ..... }
示例代码
下面是一个演示Java继承的例子。在这个例子中,你可以看到两个类,分别是Calculation和My_Calculation。
使用extends关键字,My_Calculation继承了Calculation类的addition()和Subtraction()方法。
将下面的程序复制粘贴到名为My_Calculation.java的文件中
示例
class Calculation { int z; public void addition(int x, int y) { z = x + y; System.out.println("The sum of the given numbers:"+z); } public void Subtraction(int x, int y) { z = x - y; System.out.println("The difference between the given numbers:"+z); } } public class My_Calculation extends Calculation { public void multiplication(int x, int y) { z = x * y; System.out.println("The product of the given numbers:"+z); } public static void main(String args[]) { int a = 20, b = 10; My_Calculation demo = new My_Calculation(); demo.addition(a, b); demo.Subtraction(a, b); demo.multiplication(a, b); } }
编译并执行上面的代码,如下所示。
javac My_Calculation.java java My_Calculation
程序执行后,将产生以下结果:
输出
The sum of the given numbers:30 The difference between the given numbers:10 The product of the given numbers:200
在给定的程序中,当创建My_Calculation类的对象时,会在其中创建超类内容的副本。这就是为什么可以使用子类对象访问超类的成员。
超类引用变量可以持有子类对象,但是使用该变量只能访问超类的成员,因此为了访问两个类的成员,建议始终为子类创建引用变量。
如果你考虑上面的程序,你可以像下面这样实例化类。但是使用超类引用变量(本例中为cal)你不能调用属于子类My_Calculation的方法multiplication()。
Calculation demo = new My_Calculation(); demo.addition(a, b); demo.Subtraction(a, b);
注意:子类继承其超类的所有成员(字段、方法和嵌套类)。构造函数不是成员,因此不会被子类继承,但是可以从子类调用超类的构造函数。
super关键字
super关键字类似于this关键字。以下是使用super关键字的场景。
它用于区分超类的成员和子类的成员,如果它们具有相同的名称。
它用于从子类调用超类的构造函数。
区分成员
如果一个类继承另一个类的属性,并且超类的成员与子类的名称相同,为了区分这些变量,我们使用super关键字,如下所示。
super.variable super.method();
示例代码
本节提供一个演示super关键字用法的程序。
在给定的程序中,你有两个类,分别是Sub_class和Super_class,两者都有一个名为display()的方法,但实现不同,还有一个名为num的变量,值也不同。我们调用这两个类的display()方法并打印这两个类的变量num的值。在这里你可以观察到我们使用了super关键字来区分超类的成员和子类的成员。
将程序复制粘贴到名为Sub_class.java的文件中。
示例
class Super_class { int num = 20; // display method of superclass public void display() { System.out.println("This is the display method of superclass"); } } public class Sub_class extends Super_class { int num = 10; // display method of sub class public void display() { System.out.println("This is the display method of subclass"); } public void my_method() { // Instantiating subclass Sub_class sub = new Sub_class(); // Invoking the display() method of sub class sub.display(); // Invoking the display() method of superclass super.display(); // printing the value of variable num of subclass System.out.println("value of the variable named num in sub class:"+ sub.num); // printing the value of variable num of superclass System.out.println("value of the variable named num in super class:"+ super.num); } public static void main(String args[]) { Sub_class obj = new Sub_class(); obj.my_method(); } }
使用以下语法编译并执行上述代码。
javac Super_Demo java Super
执行程序后,你将得到以下结果:
输出
This is the display method of subclass This is the display method of superclass value of the variable named num in sub class:10 value of the variable named num in super class:20
调用超类构造函数
如果一个类继承另一个类的属性,子类会自动获取超类的默认构造函数。但是,如果你想调用超类的参数化构造函数,你需要使用super关键字,如下所示。
super(values);
示例代码
本节给出的程序演示了如何使用super关键字调用超类的参数化构造函数。此程序包含一个超类和一个子类,其中超类包含一个接受整数值的参数化构造函数,我们使用super关键字调用超类的参数化构造函数。
将以下程序复制粘贴到名为Subclass.java的文件中
示例
class Superclass { int age; Superclass(int age) { this.age = age; } public void getAge() { System.out.println("The value of the variable named age in super class is: " +age); } } public class Subclass extends Superclass { Subclass(int age) { super(age); } public static void main(String args[]) { Subclass s = new Subclass(24); s.getAge(); } }
使用以下语法编译并执行上述代码。
javac Subclass java Subclass
输出
The value of the variable named age in super class is: 24
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关键字,子类将能够继承超类的所有属性,除了超类的私有属性。
我们可以用instance运算符来确保Mammal实际上是一个Animal。
示例
class Animal { } class Mammal extends Animal { } class Reptile extends 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); } }
输出
true true true
既然我们已经很好地理解了extends关键字,让我们看看如何使用implements关键字来获得IS-A关系。
通常,implements关键字与类一起使用以继承接口的属性。接口永远不能被类扩展。
示例
public interface Animal { } public class Mammal implements Animal { } public class Dog extends Mammal { }
instanceof关键字
让我们使用instanceof运算符来检查Mammal是否实际上是Animal,以及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); } }
输出
true true true
HAS-A关系
这些关系主要基于用法。这决定了某个类是否HAS-A某些东西。这种关系有助于减少代码重复和错误。
让我们来看一个例子:
示例
public class Vehicle{} public class Speed{} public class Van extends Vehicle { private Speed sp; }
这表明Van类HAS-A Speed。通过为Speed创建一个单独的类,我们不必将属于speed的整个代码放在Van类中,这使得可以在多个应用程序中重用Speed类。
在面向对象特性中,用户不需要关心哪个对象正在执行实际工作。为了实现这一点,Van类向Van类的用户隐藏了实现细节。所以,基本上发生的事情是用户会要求Van类执行某个动作,而Van类会自己执行工作,或者要求另一个类执行该动作。
继承的类型
如下所示,继承有多种类型。
需要记住一个非常重要的事实是,Java不支持多重继承。这意味着一个类不能扩展多个类。因此,以下是非法的:
示例
public class extends Animal, Mammal{}
但是,一个类可以实现一个或多个接口,这帮助Java摆脱了多重继承的不可能。
Java - 多态
多态性是对象能够采用多种形式的能力。OOP中多态性的最常见用法是当父类引用用于引用子类对象时。
任何能够通过多个IS-A测试的Java对象都被认为是多态的。在Java中,所有Java对象都是多态的,因为任何对象都将通过其自身类型和Object类的IS-A测试。
重要的是要知道,访问对象的唯一可能方法是通过引用变量。引用变量只能是一种类型。一旦声明,引用变量的类型就不能更改。
如果引用变量没有声明为final,则可以将其重新分配给其他对象。引用变量的类型将决定它可以在对象上调用的方法。
引用变量可以引用其声明类型的任何对象或其声明类型的任何子类型。引用变量可以声明为类或接口类型。
示例
让我们来看一个例子。
public interface Vegetarian{} public class Animal{} public class Deer extends Animal implements Vegetarian{}
现在,Deer类被认为是多态的,因为它具有多重继承。对于上面的例子,以下说法是正确的:
- A Deer IS-A Animal
- A Deer IS-A Vegetarian
- A Deer IS-A Deer
- A Deer IS-A Object
当我们将引用变量事实应用于Deer对象引用时,以下声明是合法的:
示例
Deer d = new Deer(); Animal a = d; Vegetarian v = d; Object o = d;
所有引用变量d、a、v、o都引用堆中的同一个Deer对象。
示例
在这个例子中,我们通过创建Deer的对象并将相同的对象分配给超类或已实现接口的引用来展示上述概念。
interface Vegetarian{} class Animal{} public class Deer extends Animal implements Vegetarian{ public static void main(String[] args) { Deer d = new Deer(); Animal a = d; Vegetarian v = d; Object o = d; System.out.println(d instanceof Deer); System.out.println(a instanceof Deer); System.out.println(v instanceof Deer); System.out.println(o instanceof Deer); } }
输出
true true true true
虚方法
在本节中,我将向您展示Java中重写方法的行为如何使您能够在设计类时利用多态性。
我们已经讨论过方法重写,其中子类可以重写其父类中的方法。重写的方法实质上隐藏在父类中,除非子类在重写方法中使用super关键字,否则不会调用。
示例
/* File name : 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类如下:
/* File name : 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; } }
现在,仔细研究以下程序,并尝试确定其输出:
/* File name : 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("\n Call mailCheck using Employee reference--"); e.mailCheck(); } } 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; } } 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; } }
输出
Constructing an Employee Constructing an Employee Call mailCheck using Salary reference -- Within mailCheck of Salary class Mailing check to Mohd Mohtashim with salary 3600.0 Call mailCheck using Employee reference-- Within mailCheck of Salary class Mailing check to John Adams with salary 2400.0
在这里,我们实例化两个Salary对象。一个使用Salary引用s,另一个使用Employee引用e。
调用s.mailCheck()时,编译器在编译时看到Salary类中的mailCheck(),JVM在运行时调用Salary类中的mailCheck()。
e上的mailCheck()则完全不同,因为e是Employee引用。当编译器看到e.mailCheck()时,编译器会看到Employee类中的mailCheck()方法。
在这里,在编译时,编译器使用Employee中的mailCheck()来验证此语句。然而,在运行时,JVM调用Salary类中的mailCheck()。
这种行为被称为虚方法调用,这些方法被称为虚方法。重写的方法是在运行时调用的,无论在编译时的源代码中使用了什么数据类型的引用。
Java - 方法覆盖
在上一章中,我们讨论了超类和子类。如果一个类从其超类继承了一个方法,那么就有机会重写该方法,前提是该方法没有标记为final。
重写的优点是:能够定义特定于子类类型的行为,这意味着子类可以根据其需求实现父类方法。
在面向对象的术语中,重写意味着重写现有方法的功能。
示例
让我们来看一个例子。
class Animal { public void move() { System.out.println("Animals can move"); } } class Dog extends Animal { public void move() { System.out.println("Dogs can walk and run"); } } public class TestDog { public static void main(String args[]) { Animal a = new Animal(); // Animal reference and object Animal b = new Dog(); // Animal reference but Dog object a.move(); // runs the method in Animal class b.move(); // runs the method in Dog class } }
输出
Animals can move Dogs can walk and run
在上面的例子中,你可以看到,即使b是Animal类型,它也会运行Dog类中的move方法。原因是:在编译时,检查是在引用类型上进行的。但是,在运行时,JVM会确定对象类型,并运行属于该特定对象的方法。
因此,在上面的例子中,程序将正确编译,因为Animal类具有move方法。然后,在运行时,它运行特定于该对象的方法。
考虑以下示例:
示例
class Animal { public void move() { System.out.println("Animals can move"); } } class Dog extends Animal { public void move() { System.out.println("Dogs can walk and run"); } public void bark() { System.out.println("Dogs can bark"); } } public class TestDog { public static void main(String args[]) { Animal a = new Animal(); // Animal reference and object Animal b = new Dog(); // Animal reference but Dog object a.move(); // runs the method in Animal class b.move(); // runs the method in Dog class b.bark(); } }
输出
TestDog.java:26: error: cannot find symbol b.bark(); ^ symbol: method bark() location: variable b of type Animal 1 error
此程序将抛出编译时错误,因为b的引用类型Animal没有名为bark的方法。
方法重写的规则
参数列表必须与被重写方法的参数列表完全相同。
返回类型必须与超类中原始重写方法中声明的返回类型相同或为其子类型。
访问级别不能比重写方法的访问级别更严格。例如:如果超类方法声明为public,则子类中的重写方法不能为private或protected。
实例方法只能在子类继承它们时才能被重写。
声明为final的方法不能被重写。
声明为static的方法不能被重写,但可以被重新声明。
如果一个方法不能被继承,那么它就不能被重写。
与实例的超类位于同一个包中的子类可以重写任何未声明为private或final的超类方法。
不同包中的子类只能重写声明为public或protected的非final方法。
重写方法可以抛出任何未检查的异常,无论重写方法是否抛出异常。但是,重写方法不应抛出比重写方法声明的新异常或更广泛的异常。重写方法可以抛出比重写方法更窄或更少的异常。
构造函数不能被重写。
使用 super 关键字
调用被重写方法的超类版本时,使用super关键字。
示例
class Animal { public void move() { System.out.println("Animals can move"); } } class Dog extends Animal { public void move() { super.move(); // invokes the super class method System.out.println("Dogs can walk and run"); } } public class TestDog { public static void main(String args[]) { Animal b = new Dog(); // Animal reference but Dog object b.move(); // runs the method in Dog class } }
输出
Animals can move Dogs can walk and run
Java - 抽象
根据字典的定义,抽象是处理思想而不是事件的特性。例如,当您考虑电子邮件的情况时,发送电子邮件后发生的情况、电子邮件服务器使用的协议等复杂细节对用户是隐藏的。因此,要发送电子邮件,您只需要输入内容、填写接收者的地址并点击发送。
同样,在面向对象编程中,抽象是向用户隐藏实现细节的过程,只向用户提供功能。换句话说,用户将了解对象的功能,而不是其工作方式。
在Java中,抽象是使用抽象类和接口实现的。
抽象类
在其声明中包含abstract关键字的类称为抽象类。
抽象类可以包含也可以不包含抽象方法,即没有方法体的方法(public void get();)
但是,如果一个类至少有一个抽象方法,那么该类必须声明为抽象类。
如果一个类被声明为抽象类,则不能实例化它。
要使用抽象类,必须从另一个类继承它,并为其中的抽象方法提供实现。
如果您继承了一个抽象类,则必须为其中的所有抽象方法提供实现。
示例
本节提供了一个抽象类的示例。要创建一个抽象类,只需在类声明中,在class关键字前使用abstract关键字。
/* File name : 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类与Java中的普通类相同。该类现在是抽象的,但它仍然具有三个字段、七个方法和一个构造函数。
现在您可以尝试以下方式实例化Employee类:
/* File name : AbstractDemo.java */ public class AbstractDemo { public static void main(String [] args) { /* Following is not allowed and would raise error */ Employee e = new Employee("George W.", "Houston, TX", 43); System.out.println("\n Call mailCheck using Employee reference--"); e.mailCheck(); } } 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.java:46: Employee is abstract; cannot be instantiated Employee e = new Employee("George W.", "Houston, TX", 43); ^ 1 error
继承抽象类
我们可以像在具体类中一样继承Employee类的属性,方法如下:
示例
/* File name : 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类的所有三个字段和七个方法,如下所示。
/* File name : 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("\n Call mailCheck using Employee reference--"); e.mailCheck(); } } 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; } } 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; } }
输出
Constructing an Employee Constructing an Employee Call mailCheck using Salary reference -- Within mailCheck of Salary class Mailing check to Mohd Mohtashim with salary 3600.0 Call mailCheck using Employee reference-- Within mailCheck of Salary class Mailing check to John Adams with salary 2400.0
抽象方法
如果您希望一个类包含特定方法,但又希望该方法的实际实现由子类确定,则可以在父类中将该方法声明为抽象方法。
abstract关键字用于将方法声明为抽象方法。
您必须在方法声明中的方法名称之前放置abstract关键字。
抽象方法包含方法签名,但不包含方法体。
抽象方法的结尾使用分号 (;),而不是大括号。
以下是一个抽象方法的示例。
示例
public abstract class Employee { private String name; private String address; private int number; public abstract double computePay(); // Remainder of class definition }
将方法声明为抽象方法有两个结果:
包含它的类必须声明为抽象类。
任何继承当前类的类都必须重写抽象方法或将自身声明为抽象类。
注意 - 最终,子类必须实现抽象方法;否则,您将拥有一个无法实例化的抽象类层次结构。
假设Salary类继承Employee类,则它应该实现computePay()方法,如下所示:
/* File name : 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; } // Remainder of class definition }
示例
下面的示例展示了抽象方法的概念。
/* File name : AbstractDemo.java */ public class AbstractDemo { public static void main(String [] args) { Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00); System.out.println("salary: " + s.computePay()); } } 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 abstract double computePay(); // Remainder of class definition public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } class Salary extends Employee { private double salary; // Annual salary public Salary(String name, String address, int number, double salary) { super(name, address, number); this.salary = salary; } public double computePay() { System.out.println("Computing salary pay for " + getName()); return salary/52; } // Remainder of class definition }
输出
Constructing an Employee Computing salary pay for Mohd Mohtashim salary: 69.23076923076923
Java - 封装
封装是面向对象编程的四个基本概念之一。另外三个是继承、多态和抽象。
Java中的封装是一种将数据(变量)和作用于数据(方法)的代码包装在一起作为单个单元的机制。在封装中,类的变量对其他类是隐藏的,只能通过其当前类的方法访问。因此,它也被称为数据隐藏。
要在Java中实现封装:
将类的变量声明为私有。
提供公共setter和getter方法来修改和查看变量值。
示例
以下是一个演示如何在Java中实现封装的示例:
/* File name : 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; } }
公共setXXX()和getXXX()方法是EncapTest类实例变量的访问点。通常,这些方法被称为getter和setter。因此,任何想要访问变量的类都应该通过这些getter和setter访问它们。
可以使用以下程序访问EncapTest类的变量:
/* File name : 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()); } } 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; } }
输出
Name : James Age : 20
封装的好处
类的字段可以设置为只读或只写。
类可以完全控制其字段中存储的内容。
Java - 接口
接口是Java中的引用类型。它类似于类。它是抽象方法的集合。一个类实现一个接口,从而继承接口的抽象方法。
除了抽象方法外,接口还可以包含常量、默认方法、静态方法和嵌套类型。方法体仅存在于默认方法和静态方法中。
编写接口类似于编写类。但是类描述对象的属性和行为。接口包含类实现的行为。
除非实现接口的类是抽象类,否则必须在类中定义接口的所有方法。
接口在以下方面类似于类:
接口可以包含任意数量的方法。
接口以.java扩展名写在文件中,接口名称与文件名匹配。
接口的字节码出现在.class文件中。
接口出现在包中,其对应的字节码文件必须位于与包名匹配的目录结构中。
但是,接口在几个方面与类不同,包括:
您不能实例化接口。
接口不包含任何构造函数。
接口中的所有方法都是抽象的。
接口不能包含实例字段。接口中可以出现的唯一字段必须同时声明为静态和final。
接口不是由类扩展的;它是类实现的。
接口可以扩展多个接口。
声明接口
interface关键字用于声明接口。以下是一个声明接口的简单示例:
示例
以下是一个接口的示例:
/* File name : NameOfInterface.java */ import java.lang.*; // Any number of import statements public interface NameOfInterface { // Any number of final, static fields // Any number of abstract method declarations\ }
接口具有以下属性:
接口隐式地是抽象的。声明接口时不需要使用abstract关键字。
接口中的每个方法也是隐式抽象的,因此不需要abstract关键字。
接口中的方法隐式地是公共的。
示例
/* File name : Animal.java */ interface Animal { public void eat(); public void travel(); }
实现接口
当一个类实现一个接口时,您可以认为该类签署了一份合同,同意执行接口的特定行为。如果一个类没有执行接口的所有行为,则该类必须将自身声明为抽象类。
类使用implements关键字来实现接口。implements关键字出现在类声明中extends部分之后。
示例
/* File name : 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(); } } interface Animal { public void eat(); public void travel(); }
输出
Mammal eats Mammal travels
重写接口中定义的方法时,需要遵循以下几条规则:
除了接口方法声明的检查异常或接口方法声明的子类外,不应该在实现方法上声明检查异常。
重写方法时,应保持接口方法的签名和相同或子类型的返回类型。
实现类本身可以是抽象类,如果是这样,则不需要实现接口方法。
实现接口时,有一些规则:
一个类可以同时实现多个接口。
一个类只能扩展一个类,但可以实现多个接口。
接口可以扩展另一个接口,就像类可以扩展另一个类一样。
扩展接口
接口可以扩展另一个接口,就像类可以扩展另一个类一样。extends关键字用于扩展接口,子接口继承父接口的方法。
Hockey和Football接口扩展了以下Sports接口。
示例
// Filename: Sports.java public interface Sports { public void setHomeTeam(String name); public void setVisitingTeam(String name); } // Filename: Football.java public interface Football extends Sports { public void homeTeamScored(int points); public void visitingTeamScored(int points); public void endOfQuarter(int quarter); } // Filename: Hockey.java public interface Hockey extends Sports { public void homeGoalScored(); public void visitingGoalScored(); public void endOfPeriod(int period); public void overtimePeriod(int ot); }
Hockey接口有四个方法,但它继承了Sports的两个方法;因此,实现Hockey的类需要实现所有六个方法。类似地,实现Football的类需要定义Football的三个方法和Sports的两个方法。
示例
interface Sports { public void setHomeTeam(String name); public void setVisitingTeam(String name); } interface Football extends Sports { public void homeTeamScored(int points); public void visitingTeamScored(int points); public void endOfQuarter(int quarter); } interface Hockey extends Sports { public void homeGoalScored(); public void visitingGoalScored(); public void endOfPeriod(int period); public void overtimePeriod(int ot); } public class HockeyDemo implements Hockey { public void setHomeTeam(String name) { System.out.println("Home team: " + name); } public void setVisitingTeam(String name) {} public void homeGoalScored() {} public void visitingGoalScored() {} public void endOfPeriod(int period) {} public void overtimePeriod(int ot) {} public static void main(String[] args) { Hockey hockeyDemo = new HockeyDemo(); hockeyDemo.setHomeTeam("India"); } }
输出
Home team: India
扩展多个接口
Java类只能扩展一个父类。不允许多重继承。然而,接口不是类,接口可以扩展多个父接口。
extends关键字只使用一次,父接口以逗号分隔的列表声明。
例如,如果Hockey接口扩展了Sports和Event,则声明如下:
示例
public interface Hockey extends Sports, Event
interface Sports { public void setHomeTeam(String name); public void setVisitingTeam(String name); } interface Football extends Sports { public void homeTeamScored(int points); public void visitingTeamScored(int points); public void endOfQuarter(int quarter); } interface Hockey extends Sports { public void homeGoalScored(); public void visitingGoalScored(); public void endOfPeriod(int period); public void overtimePeriod(int ot); } interface Event { public void organize(); } public class HockeyDemo implements Hockey, Event { public void setHomeTeam(String name) { System.out.println("Home team: " + name); } public void setVisitingTeam(String name) {} public void homeGoalScored() {} public void visitingGoalScored() {} public void endOfPeriod(int period) {} public void overtimePeriod(int ot) {} public static void main(String[] args) { HockeyDemo hockeyDemo = new HockeyDemo(); hockeyDemo.setHomeTeam("India"); hockeyDemo.organize(); } public void organize() { System.out.println("Match organized. "); } }
输出
Home team: India Match organized.
标记接口
扩展接口最常见的用法是父接口不包含任何方法。例如,java.awt.event包中的MouseListener接口扩展了java.util.EventListener,其定义如下:
示例
package java.util; public interface EventListener {}
没有方法的接口被称为标记接口。标记接口有两个基本设计目的:
创建公共父级 - 与EventListener接口一样,它被Java API中的数十个其他接口扩展,您可以使用标记接口在一组接口之间创建公共父级。例如,当接口扩展EventListener时,JVM知道这个特定接口将用于事件委托场景。
向类添加数据类型 - 这就是术语“标记”的来源。实现标记接口的类不需要定义任何方法(因为接口没有任何方法),但是类通过多态性成为接口类型。
Java - 包
在 Java 中使用包是为了防止命名冲突,控制访问,使查找/定位和使用类、接口、枚举和注解更容易等等。
包可以定义为相关类型(类、接口、枚举和注释)的组合,提供访问保护和命名空间管理。
Java中的一些现有包包括:
java.lang - 包含基本类
java.io - 此包中捆绑了输入、输出功能的类
程序员可以定义自己的包来捆绑一组类/接口等。这是一个很好的做法,将您实现的相关类分组,以便程序员可以轻松确定类、接口、枚举和注释是相关的。
由于包创建了一个新的命名空间,因此不会与其他包中的名称发生任何名称冲突。使用包,更容易提供访问控制,也更容易找到相关的类。
创建包
创建包时,应为包选择一个名称,并在包含您想要包含在包中的类、接口、枚举和注释类型的每个源文件的顶部包含带有该名称的package语句。
package语句应为源文件中的第一行。每个源文件只能有一个package语句,它适用于文件中的所有类型。
如果不使用package语句,则类、接口、枚举和注释类型将放在当前默认包中。
要使用package语句编译Java程序,必须使用-d选项,如下所示。
javac -d Destination_folder file_name.java
然后,在指定的目的地创建一个具有给定包名称的文件夹,并将编译后的类文件放在该文件夹中。
示例
让我们来看一个创建名为animals包的例子。最好使用小写字母为包命名,以避免与类和接口的名称冲突。
下面的包示例包含一个名为animals的接口:
/* File name : Animal.java */ package animals; interface Animal { public void eat(); public void travel(); }
现在,让我们在同一个animals包中实现上述接口:
package animals; /* File name : 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(); } } interface Animal { public void eat(); public void travel(); }
现在,按照以下步骤编译Java文件:
$ javac -d . Animal.java $ javac -d . MammalInt.java
现在,当前目录中将创建一个名为animals的包/文件夹,并将这些类文件放在其中,如下所示。
您可以执行包内的类文件并获得以下结果。
Mammal eats Mammal travels
import关键字
如果一个类想要使用同一个包中的另一个类,则不需要使用包名。同一个包中的类无需任何特殊语法就可以相互找到。
示例
这里,名为Boss的类被添加到已经包含Employee的payroll包中。然后,Boss可以引用Employee类而无需使用payroll前缀,如下面的Boss类所示。
package payroll; public class Boss { public void payEmployee(Employee e) { e.mailCheck(); } }
如果Employee类不在payroll包中会发生什么?那么Boss类必须使用以下技术之一来引用不同包中的类。
- 可以使用类的完全限定名。例如:
payroll.Employee
可以使用import关键字和通配符(*)导入包。例如:
import payroll.*;
- 可以使用import关键字导入类本身。例如:
import payroll.Employee;
示例
package payroll; public class Employee { public void mailCheck() { System.out.println("Pay received."); } }
package payroll; import payroll.Employee; public class Boss { public void payEmployee(Employee e) { e.mailCheck(); } public static void main(String[] args) { Boss boss = new Boss(); Employee e = new Employee(); boss.payEmployee(e); } }
输出
Pay received.
注意 - 一个类文件可以包含任意数量的import语句。import语句必须出现在package语句之后,类声明之前。
包的目录结构
将类放入包中会产生两个主要结果:
正如我们在上一节中讨论的那样,包名成为类名的一部分。
包名必须与相应的字节码所在的目录结构匹配。
这是一个在Java中管理文件的简单方法:
将类、接口、枚举或注解类型的源代码放在一个文本文件中,其名称为类型的简单名称,扩展名为.java。
例如:
// File Name : Car.java package vehicle; public class Car { // Class implementation. }
现在,将源文件放在一个目录中,该目录的名称反映了类所属的包的名称:
....\vehicle\Car.java
现在,限定类名和路径名如下所示:
- 类名 → vehicle.Car
- 路径名 → vehicle\Car.java (在Windows中)
一般来说,公司使用其反转的互联网域名作为其包名。
示例 - 如果一家公司的互联网域名是apple.com,那么其所有包名都将以com.apple开头。包名的每个组件对应一个子目录。
示例 - 如果该公司有一个com.apple.computers包,其中包含一个Dell.java源文件,它将包含在一系列子目录中,如下所示:
....\com\apple\computers\Dell.java
在编译时,编译器为其中定义的每个类、接口和枚举创建一个不同的输出文件。输出文件的基名是类型的名称,其扩展名为.class。
例如:
// File Name: Dell.java package com.apple.computers; public class Dell { } class Ups { }
现在,使用-d选项编译此文件,如下所示:
$javac -d . Dell.java
文件将按如下方式编译:
.\com\apple\computers\Dell.class .\com\apple\computers\Ups.class
您可以按如下方式导入\com\apple\computers\中定义的所有类或接口:
import com.apple.computers.*;
与.java源文件一样,编译后的.class文件也应该在一系列反映包名的目录中。但是,.class文件的路径不必与.java源文件的路径相同。您可以分别安排源目录和类目录,如下所示:
<path-one>\sources\com\apple\computers\Dell.java <path-two>\classes\com\apple\computers\Dell.class
通过这样做,可以向其他程序员提供对classes目录的访问权限,而无需公开您的源代码。您还需要以这种方式管理源文件和类文件,以便编译器和Java虚拟机(JVM)可以找到程序使用到的所有类型。
classes目录的完整路径,<path-two>\classes,称为类路径,并使用CLASSPATH系统变量设置。编译器和JVM都通过将包名添加到类路径来构造到.class文件的路径。
假设<path-two>\classes是类路径,包名是com.apple.computers,那么编译器和JVM将在<path-two>\classes\com\apple\computers中查找.class文件。
类路径可能包含多个路径。多个路径应以分号(Windows)或冒号(Unix)分隔。默认情况下,编译器和JVM搜索当前目录和包含Java平台类的JAR文件,以便这些目录自动位于类路径中。
设置CLASSPATH系统变量
要显示当前CLASSPATH变量,请在Windows和UNIX(Bourne shell)中使用以下命令:
- 在Windows中 → C:\> set CLASSPATH
- 在UNIX中 → % echo $CLASSPATH
要删除CLASSPATH变量的当前内容,请使用:
- 在Windows中 → C:\> set CLASSPATH =
- 在UNIX中 → % unset CLASSPATH; export CLASSPATH
要设置CLASSPATH变量:
- 在Windows中 → set CLASSPATH = C:\users\jack\java\classes
- 在UNIX中 → % CLASSPATH = /home/jack/java/classes; export CLASSPATH
Java - 内部类
在本章中,我们将讨论Java的内部类。
嵌套类
在Java中,就像类的方法一样,类的变量也可以有另一个类作为其成员。在Java中允许在一个类中编写另一个类。在内部编写的类称为嵌套类,包含内部类的类称为外部类。
语法
以下是编写嵌套类的语法。这里,类Outer_Demo是外部类,类Inner_Demo是嵌套类。
class Outer_Demo { class Inner_Demo { } }
嵌套类分为两种类型:
非静态嵌套类 - 这些是非静态的类成员。
静态嵌套类 - 这些是类的静态成员。
内部类(非静态嵌套类)
内部类是Java中的安全机制。我们知道类不能与访问修饰符private关联,但是如果我们将类作为其他类的成员,那么内部类可以设置为private。这也用于访问类的私有成员。
内部类根据定义方式和位置分为三种类型:它们是:
- 内部类
- 方法局部内部类
- 匿名内部类
内部类
创建内部类非常简单。您只需要在一个类中编写一个类即可。与类不同,内部类可以是私有的,一旦将内部类声明为私有的,就不能从类外部的对象访问它。
以下程序创建了一个内部类并访问它。在给定的示例中,我们将内部类设置为private,并通过方法访问该类。
示例
class Outer_Demo { int num; // inner class private class Inner_Demo { public void print() { System.out.println("This is an inner class"); } } // Accessing he inner class from the method within void display_Inner() { Inner_Demo inner = new Inner_Demo(); inner.print(); } } public class My_class { public static void main(String args[]) { // Instantiating the outer class Outer_Demo outer = new Outer_Demo(); // Accessing the display_Inner() method. outer.display_Inner(); } }
在这里您可以观察到Outer_Demo是外部类,Inner_Demo是内部类,display_Inner()是在其中实例化内部类的那个方法,并且这个方法是从main方法调用的。
如果您编译并执行上述程序,您将得到以下结果:
输出
This is an inner class.
访问私有成员
如前所述,内部类也用于访问类的私有成员。假设一个类具有私有成员来访问它们。在其中编写一个内部类,从内部类中的一个方法(例如getValue())返回私有成员,最后从另一个类(您想从中访问私有成员的类)调用内部类的getValue()方法。
要实例化内部类,首先必须实例化外部类。此后,使用外部类的对象,以下是实例化内部类的方法。
Outer_Demo outer = new Outer_Demo(); Outer_Demo.Inner_Demo inner = outer.new Inner_Demo();
以下程序演示了如何使用内部类访问类的私有成员。
示例
class Outer_Demo { // private variable of the outer class private int num = 175; // inner class public class Inner_Demo { public int getNum() { System.out.println("This is the getnum method of the inner class"); return num; } } } public class My_class2 { public static void main(String args[]) { // Instantiating the outer class Outer_Demo outer = new Outer_Demo(); // Instantiating the inner class Outer_Demo.Inner_Demo inner = outer.new Inner_Demo(); System.out.println(inner.getNum()); } }
如果您编译并执行上述程序,您将得到以下结果:
输出
This is the getnum method of the inner class: 175
方法局部内部类
在Java中,我们可以在方法中编写一个类,这将是一个局部类型。与局部变量一样,内部类的作用域也限制在方法内。
方法局部内部类只能在其定义的方法内实例化。以下程序演示了如何使用方法局部内部类。
示例
public class Outerclass { // instance method of the outer class void my_Method() { int num = 23; // method-local inner class class MethodInner_Demo { public void print() { System.out.println("This is method inner class "+num); } } // end of inner class // Accessing the inner class MethodInner_Demo inner = new MethodInner_Demo(); inner.print(); } public static void main(String args[]) { Outerclass outer = new Outerclass(); outer.my_Method(); } }
如果您编译并执行上述程序,您将得到以下结果:
输出
This is method inner class 23
匿名内部类
未声明类名的内部类称为匿名内部类。对于匿名内部类,我们同时声明和实例化它们。通常,每当需要重写类或接口的方法时,都会使用它们。匿名内部类的语法如下:
语法
AnonymousInner an_inner = new AnonymousInner() { public void my_method() { ........ ........ } };
以下程序演示了如何使用匿名内部类重写类的方法。
示例
abstract class AnonymousInner { public abstract void mymethod(); } public class Outer_class { public static void main(String args[]) { AnonymousInner inner = new AnonymousInner() { public void mymethod() { System.out.println("This is an example of anonymous inner class"); } }; inner.mymethod(); } }
如果您编译并执行上述程序,您将得到以下结果:
输出
This is an example of anonymous inner class
同样,您可以使用匿名内部类重写具体类和接口的方法。
匿名内部类作为参数
通常,如果方法接受接口、抽象类或具体类的对象,那么我们可以实现接口、扩展抽象类并将对象传递给方法。如果是类,我们可以直接将其传递给方法。
但在所有三种情况下,都可以将匿名内部类传递给方法。以下是将匿名内部类作为方法参数传递的语法:
obj.my_Method(new My_Class() { public void Do() { ..... ..... } });
以下程序演示了如何将匿名内部类作为方法参数传递。
示例
// interface interface Message { String greet(); } public class My_class { // method which accepts the object of interface Message public void displayMessage(Message m) { System.out.println(m.greet() + ", This is an example of anonymous inner class as an argument"); } public static void main(String args[]) { // Instantiating the class My_class obj = new My_class(); // Passing an anonymous inner class as an argument obj.displayMessage(new Message() { public String greet() { return "Hello"; } }); } }
如果您编译并执行上述程序,它将给出以下结果:
输出
Hello, This is an example of anonymous inner class as an argument
静态嵌套类
静态内部类是一个嵌套类,它是外部类的静态成员。可以使用其他静态成员访问它,而无需实例化外部类。就像静态成员一样,静态嵌套类无法访问外部类的实例变量和方法。静态嵌套类的语法如下:
语法
class MyOuter { static class Nested_Demo { } }
静态嵌套类的实例化与内部类的实例化略有不同。以下程序演示了如何使用静态嵌套类。
示例
public class Outer { static class Nested_Demo { public void my_method() { System.out.println("This is my nested class"); } } public static void main(String args[]) { Outer.Nested_Demo nested = new Outer.Nested_Demo(); nested.my_method(); } }
如果您编译并执行上述程序,您将得到以下结果:
输出
This is my nested class
Java - 字符类
通常,当我们处理字符时,我们使用原始数据类型char。
示例
char ch = 'a'; // Unicode for uppercase Greek omega character char uniChar = '\u039A'; // an array of chars char[] charArray ={ 'a', 'b', 'c', 'd', 'e' };
然而,在开发过程中,我们会遇到需要使用对象而不是原始数据类型的情况。为了实现这一点,Java 为原始数据类型 char 提供了包装类Character。
Character 类提供了一些有用的类(即静态)方法来操作字符。您可以使用 Character 构造函数创建 Character 对象:
Character ch = new Character('a');
在某些情况下,Java 编译器也会为您创建 Character 对象。例如,如果您将原始 char 传递给期望对象的某个方法,编译器会自动为您将 char 转换为 Character。如果转换反过来进行,则此特性称为自动装箱或拆箱。
示例
// Here following primitive char 'a' // is boxed into the Character object ch Character ch = 'a'; // Here primitive 'x' is boxed for method test, // return is unboxed to char 'c' char c = test('x');
转义序列
以反斜杠 (\) 开头的字符是转义序列,对编译器具有特殊含义。
换行符 (\n) 在本教程中经常用于 System.out.println() 语句,以在字符串打印后换到下一行。
下表显示了 Java 转义序列:
转义序列 | 描述 |
---|---|
\t | 在此处文本中插入制表符。 |
\b | 在此处文本中插入退格符。 |
\n | 在此处文本中插入换行符。 |
\r | 在此处文本中插入回车符。 |
\f | 在此处文本中插入换页符。 |
\' | 在此处文本中插入单引号字符。 |
\" | 在此处文本中插入双引号字符。 |
\\ | 在此处文本中插入反斜杠字符。 |
当在打印语句中遇到转义序列时,编译器会相应地解释它。
示例
如果要在引号内添加引号,则必须在内部引号上使用转义序列 \":
public class Test { public static void main(String args[]) { System.out.println("She said \"Hello!\" to me."); } }
输出
She said "Hello!" to me.
字符方法
以下是 Character 类所有子类实现的重要实例方法列表:
序号 | 方法和描述 |
---|---|
1 | isLetter() 确定指定的 char 值是否为字母。 |
2 | isDigit() 确定指定的 char 值是否为数字。 |
3 | isWhitespace() 确定指定的 char 值是否为空格。 |
4 | isUpperCase() 确定指定的 char 值是否为大写。 |
5 | isLowerCase() 确定指定的 char 值是否为小写。 |
6 | toUpperCase() 返回指定 char 值的大写形式。 |
7 | toLowerCase() 返回指定 char 值的小写形式。 |
8 | toString() 返回表示指定字符值的 String 对象,即一个字符的字符串。 |
有关完整的方法列表,请参阅 java.lang.Character API 规范。
下一步是什么?
在下一节中,我们将学习 Java 中的 String 类。您将学习如何高效地声明和使用字符串,以及 String 类中一些重要的方法。
Java - 文件和 I/O
java.io 包包含几乎所有可能需要的类,用于在 Java 中执行输入和输出 (I/O)。所有这些流都代表输入源和输出目标。java.io 包中的流支持多种数据,例如基本类型、对象、本地化字符等。
流
流可以定义为数据序列。有两种类型的流:
InputStream - InputStream 用于从源读取数据。
OutputStream - OutputStream 用于将数据写入目标。
Java 为与文件和网络相关的 I/O 提供了强大而灵活的支持,但本教程涵盖了与流和 I/O 相关的非常基本的功能。我们将逐一查看最常用的示例:
字节流
Java 字节流用于执行 8 位字节的输入和输出。虽然有许多与字节流相关的类,但最常用的类是FileInputStream 和FileOutputStream。以下是一个示例,它使用这两个类将输入文件复制到输出文件:
示例
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class CopyFile { public static void main(String args[]) throws IOException { FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("input.txt"); out = new FileOutputStream("output.txt"); int c; while ((c = in.read()) != -1) { out.write(c); } }finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } }
现在让我们有一个名为input.txt的文件,其内容如下:
This is test for copy file.
下一步,编译上述程序并执行它,这将导致创建一个 output.txt 文件,其内容与 input.txt 中的内容相同。所以让我们将上述代码放在 CopyFile.java 文件中并执行以下操作:
$javac CopyFile.java $java CopyFile
字符流
Java 字节流用于执行 8 位字节的输入和输出,而 Java 字符流用于执行 16 位 Unicode 的输入和输出。虽然有许多与字符流相关的类,但最常用的类是FileReader 和FileWriter。虽然 FileReader 在内部使用 FileInputStream,FileWriter 使用 FileOutputStream,但这里的主要区别在于 FileReader 一次读取两个字节,而 FileWriter 一次写入两个字节。
我们可以重写上面的例子,它使用这两个类将一个输入文件(包含 Unicode 字符)复制到一个输出文件:
示例
import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class CopyFile { public static void main(String args[]) throws IOException { FileReader in = null; FileWriter out = null; try { in = new FileReader("input.txt"); out = new FileWriter("output.txt"); int c; while ((c = in.read()) != -1) { out.write(c); } }finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } }
现在让我们有一个名为input.txt的文件,其内容如下:
This is test for copy file.
下一步,编译上述程序并执行它,这将导致创建一个 output.txt 文件,其内容与 input.txt 中的内容相同。所以让我们将上述代码放在 CopyFile.java 文件中并执行以下操作:
$javac CopyFile.java $java CopyFile
标准流
所有编程语言都支持标准 I/O,其中用户的程序可以从键盘获取输入,然后在计算机屏幕上产生输出。如果您了解 C 或 C++ 编程语言,那么您一定了解三个标准设备 STDIN、STDOUT 和 STDERR。类似地,Java 提供以下三个标准流:
标准输入 - 用于将数据馈送到用户程序,通常使用键盘作为标准输入流,表示为System.in。
标准输出 - 用于输出用户程序产生的数据,通常使用计算机屏幕作为标准输出流,表示为System.out。
标准错误 - 用于输出用户程序产生的错误数据,通常使用计算机屏幕作为标准错误流,表示为System.err。
以下是一个简单的程序,它创建InputStreamReader 来读取标准输入流,直到用户键入“q”:
示例
import java.io.InputStreamReader; public class ReadConsole { public static void main(String args[]) throws IOException { InputStreamReader cin = null; try { cin = new InputStreamReader(System.in); System.out.println("Enter characters, 'q' to quit."); char c; do { c = (char) cin.read(); System.out.print(c); } while(c != 'q'); }finally { if (cin != null) { cin.close(); } } } }
让我们将上述代码保存在 ReadConsole.java 文件中,并尝试编译和执行它,如下面的程序所示。此程序会继续读取和输出相同的字符,直到我们按下 'q':
$javac ReadConsole.java $java ReadConsole Enter characters, 'q' to quit. 1 1 e e q q
读取和写入文件
如前所述,流可以定义为数据序列。InputStream 用于从源读取数据,OutputStream 用于将数据写入目标。
这是一个处理输入和输出流的类层次结构。
两个重要的流是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 对象,就可以使用一系列辅助方法来读取流或对流执行其他操作。
序号 | 方法和描述 |
---|---|
1 | public void close() throws IOException{} 此方法关闭文件输出流。释放与文件关联的任何系统资源。抛出 IOException。 |
2 | protected void finalize()throws IOException {} 此方法清理与文件的连接。确保当不再引用此流时,调用此文件输出流的 close 方法。抛出 IOException。 |
3 | public int read(int r)throws IOException{} 此方法从 InputStream 读取指定字节的数据。返回一个 int。返回下一字节数据,如果文件结束则返回 -1。 |
4 | public int read(byte[] r) throws IOException{} 此方法将从输入流读取 r.length 个字节到数组中。返回读取的总字节数。如果是文件结尾,则返回 -1。 |
5 | public int available() throws IOException{} 给出可以从此文件输入流读取的字节数。返回一个 int。 |
FileOutputStream
FileOutputStream 用于创建文件并将数据写入其中。如果该文件尚不存在,则流会在打开它进行输出之前创建它。
以下有两个构造函数可用于创建 FileOutputStream 对象。
以下构造函数采用文件名作为字符串,以创建用于写入文件的输入流对象:
OutputStream f = new FileOutputStream("C:/java/hello")
以下构造函数采用文件对象来创建用于写入文件的输出流对象。首先,我们使用 File() 方法创建一个文件对象,如下所示:
File f = new File("C:/java/hello"); OutputStream f = new FileOutputStream(f);
一旦您掌握了OutputStream 对象,就可以使用一系列辅助方法来写入流或对流执行其他操作。
序号 | 方法和描述 |
---|---|
1 | public void close() throws IOException{} 此方法关闭文件输出流。释放与文件关联的任何系统资源。抛出 IOException。 |
2 | protected void finalize()throws IOException {} 此方法清理与文件的连接。确保当不再引用此流时,调用此文件输出流的 close 方法。抛出 IOException。 |
3 | public void write(int w)throws IOException{} 此方法将指定的字节写入输出流。 |
4 | public void write(byte[] w) 将提到的字节数组中的 w.length 个字节写入 OutputStream。 |
示例
以下示例演示了 InputStream 和 OutputStream:
import java.io.OutputStream; 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 并以二进制格式写入给定的数字。标准输出屏幕上的输出也将相同。
Java 中的目录
目录是一个 File,它可以包含其他文件和目录的列表。您可以使用File 对象来创建目录,列出目录中可用的文件。有关完整详细信息,请查看可以在 File 对象上调用的所有方法列表以及与目录相关的哪些方法。
创建目录
有两个有用的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); // Create directory now. d.mkdirs(); } }
编译并执行上述代码以创建“/tmp/user/java/bin”。
注意 - Java 根据约定自动处理 UNIX 和 Windows 上的路径分隔符。如果您在 Windows 版本的 Java 上使用正斜杠 (/),路径仍然可以正确解析。
列出目录
您可以使用File对象提供的list( )方法列出目录中所有可用的文件和目录,如下所示:
示例
import java.io.File; public class ReadDir { public static void main(String[] args) { File file = null; String[] paths; try { // create new file object file = new File("/tmp"); // array of files and directory paths = file.list(); // for each name in the path array for(String path:paths) { // prints filename and directory name System.out.println(path); } } catch (Exception e) { // if any error occurs e.printStackTrace(); } } }
这将根据您的/tmp目录中可用的目录和文件产生以下结果:
输出
test1.txt test2.txt ReadDir.java ReadDir.class
Java - 异常
异常(或异常事件)是在程序执行期间出现的问题。当发生异常时,程序的正常流程会被中断,程序/应用程序会异常终止,这是不推荐的,因此,这些异常需要被处理。
异常可能由于许多不同的原因发生。以下是发生异常的一些场景。
用户输入了无效数据。
需要打开的文件找不到。
网络连接在通信过程中断开,或者JVM内存不足。
其中一些异常是由用户错误引起的,另一些是由程序员错误引起的,还有一些是由以某种方式发生故障的物理资源引起的。
基于这些,我们有三种类型的异常。您需要了解它们才能知道Java中的异常处理是如何工作的。
已检查异常 - 已检查异常是在编译时由编译器检查(通知)的异常,这些也称为编译时异常。这些异常不能简单地被忽略,程序员应该处理这些异常。
例如,如果您在程序中使用FileReader类从文件读取数据,如果其构造函数中指定的文件不存在,则会发生FileNotFoundException,编译器会提示程序员处理该异常。
示例
import java.io.File; import java.io.FileReader; public class FilenotFound_Demo { public static void main(String args[]) { File file = new File("E://file.txt"); FileReader fr = new FileReader(file); } }
如果您尝试编译上述程序,将会得到以下异常。
输出
C:\>javac FilenotFound_Demo.java FilenotFound_Demo.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown FileReader fr = new FileReader(file); ^ 1 error
注意 - 由于FileReader类的read()和close()方法抛出IOException,您可以观察到编译器通知处理IOException以及FileNotFoundException。
未检查异常 - 未检查异常是在执行时发生的异常。这些也称为运行时异常。这些包括编程错误,例如逻辑错误或API的错误使用。运行时异常在编译时被忽略。
例如,如果您在程序中声明了一个大小为5的数组,并尝试调用数组的第6个元素,则会发生ArrayIndexOutOfBoundsException异常。
示例
public class Unchecked_Demo { public static void main(String args[]) { int num[] = {1, 2, 3, 4}; System.out.println(num[5]); } }
如果您编译并执行上述程序,将会得到以下异常。
输出
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
错误 - 这根本不是异常,而是超出用户或程序员控制范围的问题。在您的代码中通常会忽略错误,因为您很少能够处理错误。例如,如果发生堆栈溢出,则会发生错误。它们在编译时也会被忽略。
异常层次结构
所有异常类都是java.lang.Exception类的子类型。异常类是Throwable类的子类。除了异常类之外,还有一个名为Error的子类,它派生自Throwable类。
错误是在严重故障情况下发生的异常情况,Java程序不处理这些错误。生成错误是为了指示运行时环境生成的错误。例如:JVM内存不足。通常,程序无法从错误中恢复。
Exception类有两个主要的子类:IOException类和RuntimeException类。
以下是大多数常见的已检查和未检查Java内置异常列表。
异常方法
以下是Throwable类中可用的一些重要方法列表。
序号 | 方法和描述 |
---|---|
1 | public String getMessage() 返回有关发生的异常的详细消息。此消息在Throwable构造函数中初始化。 |
2 | public Throwable getCause() 返回由Throwable对象表示的异常原因。 |
3 | public String toString() 返回类的名称与getMessage()的结果连接在一起。 |
4 | public void printStackTrace() 将toString()的结果以及堆栈跟踪打印到System.err(错误输出流)。 |
5 | public StackTraceElement [] getStackTrace() 返回一个包含堆栈跟踪中每个元素的数组。索引为0的元素代表调用堆栈的顶部,数组中的最后一个元素代表调用堆栈底部的方法。 |
6 | public Throwable fillInStackTrace() 用当前堆栈跟踪填充此Throwable对象的堆栈跟踪,添加到堆栈跟踪中的任何先前信息。 |
捕获异常
方法使用try和catch关键字的组合来捕获异常。try/catch块放置在可能生成异常的代码周围。try/catch块内的代码称为受保护代码,使用try/catch的语法如下所示:
语法
try { // Protected code } catch (ExceptionName e1) { // Catch block }
易于发生异常的代码放在try块中。当发生异常时,该异常由与其关联的catch块处理。每个try块都应该紧跟一个catch块或finally块。
catch语句涉及声明您试图捕获的异常类型。如果在受保护的代码中发生异常,则检查try后面的catch块(或块)。如果发生的异常类型在catch块中列出,则异常将像参数传递到方法参数一样传递到catch块。
示例
在下面的示例中,声明了一个包含2个元素的数组。然后代码尝试访问数组的第3个元素,这将引发异常。
// File Name : ExcepTest.java import 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: 3 Out of the block
多个Catch块
try块可以后跟多个catch块。多个catch块的语法如下所示:
语法
try { // Protected code } catch (ExceptionType1 e1) { // Catch block } catch (ExceptionType2 e2) { // Catch block } catch (ExceptionType3 e3) { // Catch block }
前面的语句演示了三个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; }
捕获多种类型的异常
从Java 7开始,您可以使用单个catch块处理多个异常,此功能简化了代码。以下是执行此操作的方法:
catch (IOException|FileNotFoundException ex) { logger.log(ex); throw ex;
Throws/Throw关键字
如果方法不处理已检查异常,则该方法必须使用throws关键字声明它。throws关键字出现在方法签名末尾。
您可以使用throw关键字抛出异常,无论是新实例化的异常还是您刚刚捕获的异常。
尝试理解throws和throw关键字之间的区别,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块或catch块之后。无论是否发生异常,finally块中的代码始终都会执行。
使用finally块允许您运行任何您想要执行的清理类型语句,无论受保护代码中发生了什么。
finally块出现在catch块的末尾,并具有以下语法:
语法
try { // Protected code } catch (ExceptionType1 e1) { // Catch block } catch (ExceptionType2 e2) { // Catch block } catch (ExceptionType3 e3) { // Catch block }finally { // The finally block always executes. }
示例
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: 3 First element value: 6 The finally statement is executed
注意以下几点:
catch子句不能没有try语句。
无论何时出现try/catch块,都不强制要求使用finally子句。
try块不能没有catch子句或finally子句。
try、catch、finally块之间不能存在任何代码。
try-with-resources
通常,当我们使用任何资源(如流、连接等)时,我们必须使用finally块显式关闭它们。在下面的程序中,我们使用FileReader从文件读取数据,并使用finally块关闭它。
示例
import java.io.File; import java.io.FileReader; import java.io.IOException; public class ReadData_Demo { public static void main(String args[]) { FileReader fr = null; try { File file = new File("file.txt"); fr = new FileReader(file); char [] a = new char[50]; fr.read(a); // reads the content to the array for(char c : a) System.out.print(c); // prints the characters one by one } catch (IOException e) { e.printStackTrace(); }finally { try { fr.close(); } catch (IOException ex) { ex.printStackTrace(); } } } }
try-with-resources(也称为自动资源管理)是Java 7中引入的一种新的异常处理机制,它会自动关闭try catch块中使用的资源。
要使用此语句,您只需在括号内声明所需的资源,创建的资源将在块末尾自动关闭。以下是try-with-resources语句的语法。
语法
try(FileReader fr = new FileReader("file path")) { // use the resource } catch () { // body of catch } }
以下是使用try-with-resources语句读取文件数据的程序。
示例
import java.io.FileReader; import java.io.IOException; public class Try_withDemo { public static void main(String args[]) { try(FileReader fr = new FileReader("E://file.txt")) { char [] a = new char[50]; fr.read(a); // reads the contentto the array for(char c : a) System.out.print(c); // prints the characters one by one } catch (IOException e) { e.printStackTrace(); } } }
使用try-with-resources语句时,需要注意以下几点。
要将类与try-with-resources语句一起使用,它必须实现AutoCloseable接口,并且它的close()方法会在运行时自动调用。
您可以在try-with-resources语句中声明多个类。
当您在try-with-resources语句的try块中声明多个类时,这些类将以相反的顺序关闭。
除了在括号内声明资源外,其他所有内容都与try块的普通try/catch块相同。
try中声明的资源在try块开始之前被实例化。
在try块中声明的资源隐式声明为final。
用户定义的异常
您可以在Java中创建自己的异常。编写自己的异常类时,请记住以下几点:
所有异常都必须是Throwable的子类。
如果您想编写一个由Handle or Declare规则自动强制执行的已检查异常,则需要扩展Exception类。
如果您想编写运行时异常,则需要扩展RuntimeException类。
我们可以如下定义我们自己的异常类:
class MyException extends Exception { }
您只需要扩展预定义的Exception类即可创建您自己的异常。这些被认为是已检查异常。以下InsufficientFundsException类是一个用户定义的异常,它扩展了Exception类,使其成为已检查异常。异常类与任何其他类一样,包含有用的字段和方法。
示例
// File Name InsufficientFundsException.java import java.io.*; public class InsufficientFundsException extends Exception { private double amount; public InsufficientFundsException(double amount) { this.amount = amount; } public double getAmount() { return amount; } }
为了演示使用我们用户定义的异常,以下CheckingAccount类包含一个抛出InsufficientFundsException的withdraw()方法。
// File Name CheckingAccount.java import 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()方法。
// File Name BankDemo.java public 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("\nWithdrawing $100..."); c.withdraw(100.00); System.out.println("\nWithdrawing $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.0 InsufficientFundsException at CheckingAccount.withdraw(CheckingAccount.java:25) at BankDemo.main(BankDemo.java:13)
常见异常
在Java中,可以定义两类异常和错误。
JVM异常 - 这些是由JVM专门或逻辑上抛出的异常/错误。例如:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。
编程异常 - 这些异常是由应用程序或API程序员显式抛出的。例如:IllegalArgumentException、IllegalStateException。
Java - 多线程
Java是一种多线程编程语言,这意味着我们可以使用Java开发多线程程序。多线程程序包含两个或多个可以并发运行的部分,每个部分可以同时处理不同的任务,从而最佳地利用可用资源,尤其是在您的计算机具有多个CPU的情况下。
根据定义,多任务处理是指多个进程共享公共处理资源(如CPU)。多线程将多任务处理的概念扩展到应用程序,您可以在单个应用程序中将特定操作细分为各个线程。每个线程都可以并行运行。操作系统不仅在不同的应用程序之间划分处理时间,还在应用程序中的每个线程之间划分处理时间。
多线程允许您编写程序,使其中的多个活动能够并发进行。
线程的生命周期
线程在其生命周期中会经历不同的阶段。例如,一个线程会诞生、启动、运行,然后消亡。下图显示了线程的完整生命周期。
以下是生命周期的各个阶段:
新建 - 新线程从新建状态开始其生命周期。它保持在此状态,直到程序启动线程。它也被称为新生线程。
可运行 - 新生线程启动后,线程变为可运行状态。处于此状态的线程被认为正在执行其任务。
等待 - 有时,当线程等待另一个线程执行任务时,线程会转换到等待状态。只有当另一个线程向等待线程发出继续执行的信号时,线程才会转换回可运行状态。
计时等待 - 可运行线程可以进入计时等待状态,持续指定的时间间隔。处于此状态的线程在该时间间隔到期或其等待的事件发生时转换回可运行状态。
终止(死亡) - 可运行线程在其任务完成或以其他方式终止时进入终止状态。
线程优先级
每个 Java 线程都有一个优先级,这有助于操作系统确定线程调度的顺序。
Java 线程优先级的范围在 MIN_PRIORITY(常量值为 1)和 MAX_PRIORITY(常量值为 10)之间。默认情况下,每个线程都被赋予 NORM_PRIORITY(常量值为 5)的优先级。
优先级较高的线程对程序更重要,应在优先级较低的线程之前分配处理器时间。但是,线程优先级不能保证线程执行的顺序,并且很大程度上依赖于平台。
通过实现 Runnable 接口创建线程
如果您的类旨在作为线程执行,那么您可以通过实现Runnable接口来实现这一点。您需要遵循三个基本步骤:
步骤 1
第一步,您需要实现Runnable接口提供的 run() 方法。此方法为线程提供了一个入口点,您将在此方法中放入完整的业务逻辑。以下是 run() 方法的简单语法:
public void run( )
步骤 2
第二步,您将使用以下构造函数实例化一个Thread对象:
Thread(Runnable threadObj, String threadName);
其中,threadObj 是实现Runnable接口的类的实例,threadName 是赋予新线程的名称。
步骤 3
创建 Thread 对象后,您可以通过调用start()方法启动它,该方法会调用 run() 方法。以下是 start() 方法的简单语法:
void start();
示例
这是一个创建新线程并启动运行它的示例:
class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // Let the thread sleep for a while. Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo( "Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo( "Thread-2"); R2.start(); } }
输出
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
通过扩展 Thread 类创建线程
创建线程的第二种方法是创建一个扩展Thread类的新的类,使用以下两个简单的步骤。这种方法在处理使用 Thread 类中可用方法创建的多个线程时提供了更大的灵活性。
步骤 1
您需要重写 Thread 类中可用的run()方法。此方法为线程提供了一个入口点,您将在此方法中放入完整的业务逻辑。以下是 run() 方法的简单语法:
public void run( )
步骤 2
创建 Thread 对象后,您可以通过调用start()方法启动它,该方法会调用 run() 方法。以下是 start() 方法的简单语法:
void start( );
示例
以下是重写为扩展 Thread 的前面程序:
class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // Let the thread sleep for a while. Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo( "Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo( "Thread-2"); T2.start(); } }
输出
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
线程方法
以下是 Thread 类中可用的一些重要方法。
序号 | 方法和描述 |
---|---|
1 | public void start() 在单独的执行路径中启动线程,然后在此 Thread 对象上调用 run() 方法。 |
2 | public void run() 如果此 Thread 对象是使用单独的 Runnable target 实例化的,则会在该 Runnable 对象上调用 run() 方法。 |
3 | public final void setName(String name) 更改 Thread 对象的名称。还有一个 getName() 方法用于检索名称。 |
4 | public final void setPriority(int priority) 设置此 Thread 对象的优先级。可能的值介于 1 和 10 之间。 |
5 | public final void setDaemon(boolean on) 值为 true 表示此线程为守护线程。 |
6 | public final void join(long millisec) 当前线程在此第二个线程上调用此方法,导致当前线程阻塞,直到第二个线程终止或指定的毫秒数过去。 |
7 | public void interrupt() 中断此线程,如果它由于任何原因被阻塞,则使其继续执行。 |
8 | public final boolean isAlive() 如果线程处于活动状态,则返回 true,即线程启动后但完成运行之前的任何时间。 |
前面这些方法是在特定的 Thread 对象上调用的。Thread 类中的以下方法是静态的。调用其中一个静态方法会在当前运行的线程上执行操作。
序号 | 方法和描述 |
---|---|
1 | public static void yield() 导致当前运行的线程让位于任何其他具有相同优先级且正在等待调度的线程。 |
2 | public static void sleep(long millisec) 导致当前运行的线程至少阻塞指定的毫秒数。 |
3 | public static boolean holdsLock(Object x) 如果当前线程持有给定对象的锁,则返回 true。 |
4 | public static Thread currentThread() 返回对当前运行线程的引用,该线程是调用此方法的线程。 |
5 | public static void dumpStack() 打印当前运行线程的堆栈跟踪,这在调试多线程应用程序时非常有用。 |
示例
以下 ThreadClassDemo 程序演示了 Thread 类的一些这些方法。考虑一个实现Runnable的类DisplayMessage:
// File Name : DisplayMessage.java // Create a thread to implement 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); } } }
以下是另一个扩展 Thread 类的类:
// File Name : GuessANumber.java // Create a thread to extentd 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.**"); } }
以下是主程序,它使用了上面定义的类:
// File Name : ThreadClassDemo.java public 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..."); } } class DisplayMessage implements Runnable { private String message; public DisplayMessage(String message) { this.message = message; } public void run() { while(true) { System.out.println(message); } } } 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.**"); } }
输出
Starting hello thread... Starting goodbye thread... Hello Hello Hello Hello Hello Hello Goodbye Goodbye Goodbye Goodbye Goodbye .......
主要的 Java 多线程概念
在 Java 中进行多线程编程时,您需要掌握以下概念:
Java - 线程同步
当我们在程序中启动两个或多个线程时,可能会出现多个线程试图访问相同资源的情况,最终由于并发问题而产生不可预见的结果。例如,如果多个线程试图写入同一个文件,那么它们可能会损坏数据,因为其中一个线程可能会覆盖数据,或者当一个线程正在打开同一个文件时,另一个线程可能正在关闭同一个文件。
因此,需要同步多个线程的操作,并确保在给定时间点只有一个线程可以访问资源。这是使用称为监视器的概念实现的。Java 中的每个对象都与一个监视器相关联,线程可以锁定或解锁该监视器。一次只有一个线程可以持有监视器的锁。
Java 编程语言提供了一种非常方便的方法来创建线程并使用synchronized块同步其任务。您将共享资源保存在此块中。以下是 synchronized 语句的一般形式:
语法
synchronized(objectidentifier) { // Access shared variables and other shared resources }
这里,objectidentifier 是对对象的引用,其锁与 synchronized 语句表示的监视器相关联。现在我们将看到两个示例,我们将使用两个不同的线程打印计数器。当线程不同步时,它们打印的计数器值不是按顺序的,但是当我们将计数器放在 synchronized() 块内打印时,它会为两个线程按顺序打印计数器。
无同步的多线程示例
这是一个简单的示例,它可能会也可能不会按顺序打印计数器值,并且每次运行它时,它都会根据 CPU 对线程的可用性产生不同的结果。
示例
class PrintDemo { public void printCount() { try { for(int i = 5; i > 0; i--) { System.out.println("Counter --- " + i ); } } catch (Exception e) { System.out.println("Thread interrupted."); } } } class ThreadDemo extends Thread { private Thread t; private String threadName; PrintDemo PD; ThreadDemo( String name, PrintDemo pd) { threadName = name; PD = pd; } public void run() { PD.printCount(); System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { PrintDemo PD = new PrintDemo(); ThreadDemo T1 = new ThreadDemo( "Thread - 1 ", PD ); ThreadDemo T2 = new ThreadDemo( "Thread - 2 ", PD ); T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println("Interrupted"); } } }
每次运行此程序都会产生不同的结果:
输出
Starting Thread - 1 Starting Thread - 2 Counter --- 5 Counter --- 4 Counter --- 3 Counter --- 5 Counter --- 2 Counter --- 1 Counter --- 4 Thread Thread - 1 exiting. Counter --- 3 Counter --- 2 Counter --- 1 Thread Thread - 2 exiting.
带同步的多线程示例
这是一个相同的示例,它按顺序打印计数器值,并且每次运行它时,它都会产生相同的结果。
示例
class PrintDemo { public void printCount() { try { for(int i = 5; i > 0; i--) { System.out.println("Counter --- " + i ); } } catch (Exception e) { System.out.println("Thread interrupted."); } } } class ThreadDemo extends Thread { private Thread t; private String threadName; PrintDemo PD; ThreadDemo( String name, PrintDemo pd) { threadName = name; PD = pd; } public void run() { synchronized(PD) { PD.printCount(); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { PrintDemo PD = new PrintDemo(); ThreadDemo T1 = new ThreadDemo( "Thread - 1 ", PD ); ThreadDemo T2 = new ThreadDemo( "Thread - 2 ", PD ); T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println("Interrupted"); } } }
每次运行此程序都会产生相同的结果:
输出
Starting Thread - 1 Starting Thread - 2 Counter --- 5 Counter --- 4 Counter --- 3 Counter --- 2 Counter --- 1 Thread Thread - 1 exiting. Counter --- 5 Counter --- 4 Counter --- 3 Counter --- 2 Counter --- 1 Thread Thread - 2 exiting.
Java - 线程间通信
如果您了解进程间通信,那么理解线程间通信将很容易。当您开发两个或多个线程交换某些信息的应用程序时,线程间通信非常重要。
有三种简单的方法和一个小技巧可以实现线程通信。所有三种方法都列在下面:
序号 | 方法和描述 |
---|---|
1 | public void wait() 导致当前线程等待,直到另一个线程调用 notify()。 |
2 | public void notify() 唤醒正在此对象的监视器上等待的单个线程。 |
3 | public void notifyAll() 唤醒所有在同一对象上调用 wait() 的线程。 |
这些方法已在 Object 中实现为final方法,因此它们在所有类中都可用。所有三种方法只能在synchronized上下文中调用。
示例
此示例显示了两个线程如何使用wait()和notify()方法进行通信。您可以使用相同的概念创建复杂的系统。
class Chat { boolean flag = false; public synchronized void Question(String msg) { if (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(msg); flag = true; notify(); } public synchronized void Answer(String msg) { if (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(msg); flag = false; notify(); } } class T1 implements Runnable { Chat m; String[] s1 = { "Hi", "How are you ?", "I am also doing fine!" }; public T1(Chat m1) { this.m = m1; new Thread(this, "Question").start(); } public void run() { for (int i = 0; i < s1.length; i++) { m.Question(s1[i]); } } } class T2 implements Runnable { Chat m; String[] s2 = { "Hi", "I am good, what about you?", "Great!" }; public T2(Chat m2) { this.m = m2; new Thread(this, "Answer").start(); } public void run() { for (int i = 0; i < s2.length; i++) { m.Answer(s2[i]); } } } public class TestThread { public static void main(String[] args) { Chat m = new Chat(); new T1(m); new T2(m); } }
输出
Hi Hi How are you ? I am good, what about you? I am also doing fine! Great!
Java - 线程死锁
死锁描述了一种情况,其中两个或多个线程永远阻塞,互相等待。当多个线程需要相同的锁但以不同的顺序获取它们时,就会发生死锁。Java 多线程程序可能会出现死锁情况,因为synchronized关键字会导致执行线程在等待与指定对象关联的锁或监视器时阻塞。这是一个例子。
示例
public class TestThread { public static Object Lock1 = new Object(); public static Object Lock2 = new Object(); public static void main(String args[]) { ThreadDemo1 T1 = new ThreadDemo1(); ThreadDemo2 T2 = new ThreadDemo2(); T1.start(); T2.start(); } private static class ThreadDemo1 extends Thread { public void run() { synchronized (Lock1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Thread 1: Waiting for lock 2..."); synchronized (Lock2) { System.out.println("Thread 1: Holding lock 1 & 2..."); } } } } private static class ThreadDemo2 extends Thread { public void run() { synchronized (Lock2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Thread 2: Waiting for lock 1..."); synchronized (Lock1) { System.out.println("Thread 2: Holding lock 1 & 2..."); } } } } }
编译并执行上述程序时,您会发现死锁情况,以下是程序生成的输出:
输出
Thread 1: Holding lock 1... Thread 2: Holding lock 2... Thread 1: Waiting for lock 2... Thread 2: Waiting for lock 1...
上述程序将永远挂起,因为没有一个线程能够继续执行并且互相等待释放锁,因此您可以通过按 CTRL+C 来退出程序。
死锁解决方案示例
让我们更改锁的顺序和同一程序的运行,看看这两个线程是否仍然互相等待:
示例
public class TestThread { public static Object Lock1 = new Object(); public static Object Lock2 = new Object(); public static void main(String args[]) { ThreadDemo1 T1 = new ThreadDemo1(); ThreadDemo2 T2 = new ThreadDemo2(); T1.start(); T2.start(); } private static class ThreadDemo1 extends Thread { public void run() { synchronized (Lock1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Thread 1: Waiting for lock 2..."); synchronized (Lock2) { System.out.println("Thread 1: Holding lock 1 & 2..."); } } } } private static class ThreadDemo2 extends Thread { public void run() { synchronized (Lock1) { System.out.println("Thread 2: Holding lock 1..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Thread 2: Waiting for lock 2..."); synchronized (Lock2) { System.out.println("Thread 2: Holding lock 1 & 2..."); } } } } }
只需更改锁的顺序就可以防止程序陷入死锁情况,并以以下结果完成:
输出
Thread 1: Holding lock 1... Thread 1: Waiting for lock 2... Thread 1: Holding lock 1 & 2... Thread 2: Holding lock 1... Thread 2: Waiting for lock 2... Thread 2: Holding lock 1 & 2...
上面的例子只是为了使概念清晰,但是,这是一个复杂的概念,在开发应用程序以处理死锁情况之前,您应该深入研究它。
Java - 线程控制
核心 Java 提供对多线程程序的完全控制。您可以开发一个多线程程序,该程序可以根据您的需求被挂起、恢复或完全停止。您可以对线程对象使用各种静态方法来控制其行为。下表列出了这些方法:
序号 | 方法和描述 |
---|---|
1 | public void suspend() 此方法将线程置于挂起状态,可以使用 resume() 方法恢复。 |
2 | public void stop() 此方法完全停止线程。 |
3 | public void resume() 此方法恢复使用 suspend() 方法挂起的线程。 |
4 | public void wait() 导致当前线程等待,直到另一个线程调用 notify()。 |
5 | public void notify() 唤醒正在此对象的监视器上等待的单个线程。 |
请注意,最新版本的 Java 已弃用 suspend()、resume() 和 stop() 方法的使用,因此您需要使用可用的替代方法。
示例
class RunnableDemo implements Runnable { public Thread t; private String threadName; boolean suspended = false; RunnableDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 10; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // Let the thread sleep for a while. Thread.sleep(300); synchronized(this) { while(suspended) { wait(); } } } } catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } void suspend() { suspended = true; } synchronized void resume() { suspended = false; notify(); } } public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo( "Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo( "Thread-2"); R2.start(); try { Thread.sleep(1000); R1.suspend(); System.out.println("Suspending First Thread"); Thread.sleep(1000); R1.resume(); System.out.println("Resuming First Thread"); R2.suspend(); System.out.println("Suspending thread Two"); Thread.sleep(1000); R2.resume(); System.out.println("Resuming thread Two"); } catch (InterruptedException e) { System.out.println("Main thread Interrupted"); }try { System.out.println("Waiting for threads to finish."); R1.t.join(); R2.t.join(); } catch (InterruptedException e) { System.out.println("Main thread Interrupted"); } System.out.println("Main thread exiting."); } }
上述程序产生以下输出:
输出
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 10 Running Thread-2 Thread: Thread-2, 10 Thread: Thread-1, 9 Thread: Thread-2, 9 Thread: Thread-1, 8 Thread: Thread-2, 8 Thread: Thread-1, 7 Thread: Thread-2, 7 Suspending First Thread Thread: Thread-2, 6 Thread: Thread-2, 5 Thread: Thread-2, 4 Resuming First Thread Suspending thread Two Thread: Thread-1, 6 Thread: Thread-1, 5 Thread: Thread-1, 4 Thread: Thread-1, 3 Resuming thread Two Thread: Thread-2, 3 Waiting for threads to finish. Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting. Main thread exiting.
Java - 网络编程
术语网络编程是指编写跨多个设备(计算机)执行的程序,其中所有设备都使用网络相互连接。
J2SE API 的 java.net 包包含一组类和接口,这些类和接口提供了低级通信细节,允许您编写专注于解决当前问题的程序。
java.net 包为两种常见的网络协议提供支持:
TCP − TCP 代表传输控制协议 (Transmission Control Protocol),它允许两个应用程序之间进行可靠的通信。TCP 通常在互联网协议 (Internet Protocol) 之上使用,称为 TCP/IP。
UDP − UDP 代表用户数据报协议 (User Datagram Protocol),这是一个无连接的协议,允许在应用程序之间传输数据包。
本章将对以下两个主题进行深入讲解:
套接字编程 (Socket Programming) − 这是网络中最常用的概念,本章将对其进行详细解释。
URL 处理 − 这将在单独章节中介绍。点击此处学习 Java 语言中的 URL 处理。
套接字编程
套接字使用 TCP 提供了两个计算机之间通信的机制。客户端程序在其通信端创建一个套接字,并尝试将该套接字连接到服务器。
连接建立后,服务器在其通信端创建一个套接字对象。客户端和服务器现在可以通过写入和读取套接字来进行通信。
java.net.Socket 类表示一个套接字,java.net.ServerSocket 类为服务器程序提供了一种监听客户端并与之建立连接的机制。
使用套接字在两台计算机之间建立 TCP 连接时,会发生以下步骤:
服务器实例化一个 ServerSocket 对象,指定通信将发生在哪个端口号上。
服务器调用 ServerSocket 类的 accept() 方法。此方法将等待直到客户端连接到给定端口上的服务器。
服务器等待后,客户端实例化一个 Socket 对象,指定要连接到的服务器名称和端口号。
Socket 类的构造函数尝试将客户端连接到指定的服务器和端口号。如果建立了通信,则客户端现在拥有一个能够与服务器通信的 Socket 对象。
在服务器端,accept() 方法返回对服务器上连接到客户端套接字的新套接字的引用。
建立连接后,可以使用 I/O 流进行通信。每个套接字都具有 OutputStream 和 InputStream。客户端的 OutputStream 连接到服务器的 InputStream,客户端的 InputStream 连接到服务器的 OutputStream。
TCP 是一种双向通信协议,因此数据可以同时通过这两个流发送。以下是提供实现套接字的完整方法集的一些有用类。
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 与之前的构造函数类似,InetAddress 参数指定要绑定的本地 IP 地址。InetAddress 用于可能具有多个 IP 地址的服务器,允许服务器指定其要接受客户端请求的哪个 IP 地址。 |
4 | public ServerSocket() throws IOException 创建一个未绑定的服务器套接字。使用此构造函数时,当准备好绑定服务器套接字时,请使用 bind() 方法。 |
如果 ServerSocket 构造函数没有抛出异常,则表示您的应用程序已成功绑定到指定的端口,并已准备好接收客户端请求。
以下是 ServerSocket 类的一些常用方法:
序号 | 方法和描述 |
---|---|
1 | public int getLocalPort() 返回服务器套接字正在侦听的端口。如果您在构造函数中传入 0 作为端口号并让服务器为您查找端口,则此方法很有用。 |
2 | public Socket accept() throws IOException 等待传入客户端。此方法会阻塞,直到客户端连接到指定端口上的服务器或套接字超时(假设已使用 setSoTimeout() 方法设置超时值)。否则,此方法将无限期阻塞。 |
3 | public void setSoTimeout(int timeout) 设置服务器套接字在 accept() 期间等待客户端的时间超时值。 |
4 | public void bind(SocketAddress host, int backlog) 将套接字绑定到 SocketAddress 对象中指定的服务器和端口。如果您使用无参数构造函数实例化了 ServerSocket,则使用此方法。 |
当 ServerSocket 调用 accept() 时,该方法会在客户端连接之前不会返回。客户端连接后,ServerSocket 会在未指定的端口上创建一个新的 Socket,并返回对这个新 Socket 的引用。现在客户端和服务器之间存在 TCP 连接,可以开始通信。
Socket 类方法
java.net.Socket 类表示客户端和服务器用于相互通信的套接字。客户端通过实例化一个 Socket 对象来获得它,而服务器则从 accept() 方法的返回值中获得一个 Socket 对象。
Socket 类有五个构造函数,客户端可以使用它们来连接到服务器:
序号 | 方法和描述 |
---|---|
1 | public Socket(String host, int port) throws UnknownHostException, IOException. 此方法尝试连接到指定端口上的指定服务器。如果此构造函数没有抛出异常,则连接成功,并且客户端已连接到服务器。 |
2 | public Socket(InetAddress host, int port) throws IOException 此方法与之前的构造函数相同,只是主机由 InetAddress 对象表示。 |
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. 此方法与之前的构造函数相同,只是主机由 InetAddress 对象而不是 String 表示。 |
5 | public Socket() 创建一个未连接的套接字。使用 connect() 方法将此套接字连接到服务器。 |
当 Socket 构造函数返回时,它不仅会实例化一个 Socket 对象,还会尝试连接到指定的服务器和端口。
这里列出了一些 Socket 类中令人感兴趣的方法。请注意,客户端和服务器都具有 Socket 对象,因此客户端和服务器都可以调用这些方法。
序号 | 方法和描述 |
---|---|
1 | public void connect(SocketAddress host, int timeout) throws IOException 此方法将套接字连接到指定的主机。仅当您使用无参数构造函数实例化 Socket 时,才需要此方法。 |
2 | public InetAddress getInetAddress() 此方法返回此套接字连接到的另一台计算机的地址。 |
3 | public int getPort() 返回套接字在远程计算机上绑定的端口。 |
4 | public int getLocalPort() 返回套接字在本地计算机上绑定的端口。 |
5 | public SocketAddress getRemoteSocketAddress() 返回远程套接字的地址。 |
6 | public InputStream getInputStream() throws IOException 返回套接字的输入流。输入流连接到远程套接字的输出流。 |
7 | public OutputStream getOutputStream() throws IOException 返回套接字的输出流。输出流连接到远程套接字的输入流。 |
8 | public void close() throws IOException 关闭套接字,这使得此 Socket 对象不再能够再次连接到任何服务器。 |
InetAddress 类方法
此类表示互联网协议 (IP) 地址。以下是一些在进行套接字编程时可能需要用到的方法:
序号 | 方法和描述 |
---|---|
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 InetAddress getLocalHost() 返回本地主机。 |
7 | String toString() 将此 IP 地址转换为字符串。 |
套接字客户端示例
下面的 GreetingClient 是一个客户端程序,它使用套接字连接到服务器,发送问候语,然后等待响应。
示例
// File Name GreetingClient.java import 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(); } } }
套接字服务器示例
下面的 GreetingServer 程序是一个服务器应用程序示例,它使用 Socket 类在命令行参数指定的端口号上监听客户端:
示例
// File Name GreetingServer.java import 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() + "\nGoodbye!"); 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 GreetingServer 6066 Waiting for client on port 6066...
检查客户端程序,如下所示:
输出
$ java GreetingClient localhost 6066 Connecting to localhost on port 6066 Just connected to localhost/127.0.0.1:6066 Server says Thank you for connecting to /127.0.0.1:6066 Goodbye!
Java - URL 处理
URL 代表统一资源定位符 (Uniform Resource Locator),它表示万维网上的资源,例如网页或 FTP 目录。
本节将向您展示如何编写与 URL 通信的 Java 程序。URL 可以分解成以下几个部分:
protocol://host:port/path?query#ref
协议示例包括 HTTP、HTTPS、FTP 和 File。路径也称为文件名,主机也称为授权。
以下是协议为 HTTP 的网页的 URL:
https://www.amrood.com/index.htm?language=en#j2se
请注意,此 URL 未指定端口,在这种情况下,将使用协议的默认端口。对于 HTTP,默认端口为 80。
构造函数
java.net.URL 类表示一个 URL,并有一套完整的用于在 Java 中操作 URL 的方法。
URL 类有几个用于创建 URL 的构造函数,包括以下这些:
序号 | 构造函数和描述 |
---|---|
1 | public URL(String protocol, String host, int port, String file) throws MalformedURLException 通过组合给定的部分创建一个URL。 |
2 | public URL(String protocol, String host, String file) throws MalformedURLException 与之前的构造函数相同,只是使用了给定协议的默认端口。 |
3 | public URL(String url) throws MalformedURLException 根据给定的字符串创建一个URL。 |
4 | public URL(URL context, String url) throws MalformedURLException 通过解析URL和字符串参数来创建一个URL。 |
URL类包含许多用于访问URL各个部分的方法。URL类中的一些方法包括:
序号 | 方法和描述 |
---|---|
1 | public String getPath() 返回URL的路径。 |
2 | public String getQuery() 返回URL的查询部分。 |
3 | public String getAuthority() 返回URL的授权部分。 |
4 | public int getPort() 返回URL的端口。 |
5 | public int getDefaultPort() 返回URL协议的默认端口。 |
6 | public String getProtocol() 返回URL的协议。 |
7 | public String getHost() 返回URL的主机。 |
8 | public String getHost() 返回URL的主机。 |
9 | public String getFile() 返回URL的文件名。 |
10 | public String getRef() 返回URL的引用部分。 |
11 | public URLConnection openConnection() throws IOException 打开到URL的连接,允许客户端与资源通信。 |
示例
下面的URLDemo程序演示了URL的各个部分。在命令行中输入一个URL,URLDemo程序将输出给定URL的每个部分。
// File Name : URLDemo.java import java.io.IOException; import java.net.URL; public class URLDemo { public static void main(String [] args) { try { URL url = new URL("https://tutorialspoint.com/index.htm?language=en#j2se"); System.out.println("URL is " + url.toString()); System.out.println("protocol is " + url.getProtocol()); System.out.println("authority is " + url.getAuthority()); System.out.println("file name is " + url.getFile()); System.out.println("host is " + url.getHost()); System.out.println("path is " + url.getPath()); System.out.println("port is " + url.getPort()); System.out.println("default port is " + url.getDefaultPort()); System.out.println("query is " + url.getQuery()); System.out.println("ref is " + url.getRef()); } catch (IOException e) { e.printStackTrace(); } } }
该程序的示例运行将产生以下结果:
输出
URL is https://tutorialspoint.com/index.htm?language=en#j2se protocol is https authority is www.tutorialspoint.com file name is /index.htm?language=en host is www.tutorialspoint.com path is /index.htm port is -1 default port is 443 query is language=en ref is j2se
URLConnection类的方法
openConnection()方法返回一个**java.net.URLConnection**,这是一个抽象类,其子类代表各种类型的URL连接。
例如:
如果连接到协议为HTTP的URL,则openConnection()方法返回一个HttpURLConnection对象。
如果连接到表示JAR文件的URL,则openConnection()方法返回一个JarURLConnection对象,等等。
URLConnection类有很多方法可以设置或确定有关连接的信息,包括:
序号 | 方法和描述 |
---|---|
1 | Object getContent() 检索此URL连接的内容。 |
2 | Object getContent(Class[] classes) 检索此URL连接的内容。 |
3 | String getContentEncoding() 返回content-encoding头字段的值。 |
4 | int getContentLength() 返回content-length头字段的值。 |
5 | String getContentType() 返回content-type头字段的值。 |
6 | int getLastModified() 返回last-modified头字段的值。 |
7 | long getExpiration() 返回expired头字段的值。 |
8 | long getIfModifiedSince() 返回此对象的ifModifiedSince字段的值。 |
9 | public void setDoInput(boolean input) 传入true表示连接将用于输入。默认值为true,因为客户端通常从URLConnection读取。 |
10 | public void setDoOutput(boolean output) 传入true表示连接将用于输出。默认值为false,因为许多类型的URL不支持写入。 |
11 | public InputStream getInputStream() throws IOException 返回用于从资源读取的URL连接的输入流。 |
12 | public OutputStream getOutputStream() throws IOException 返回用于写入资源的URL连接的输出流。 |
13 | public URL getURL() 返回此URLConnection对象连接到的URL。 |
示例
下面的URLConnectionDemo程序连接到从命令行输入的URL。
如果URL表示HTTP资源,则连接被转换为HttpURLConnection,并且资源中的数据一次读取一行。
// File Name : URLConnDemo.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; public static void main(String [] args) { try { URL url = new URL("https://tutorialspoint.com"); URLConnection urlConnection = url.openConnection(); HttpURLConnection connection = null; if(urlConnection instanceof HttpURLConnection) { connection = (HttpURLConnection) urlConnection; }else { System.out.println("Please enter an HTTP URL."); return; } BufferedReader in = new BufferedReader( new InputStreamReader(connection.getInputStream())); String urlString = ""; String current; while((current = in.readLine()) != null) { urlString += current; } System.out.println(urlString); } catch (IOException e) { e.printStackTrace(); } } }
该程序的示例运行将产生以下结果:
输出
$ java URLConnDemo .....a complete HTML content of home page of tutorialspoint.com.....
Java - 泛型
如果我们可以编写一个单一的排序方法,可以对Integer数组、String数组或任何支持排序类型的数组中的元素进行排序,那就太好了。
Java的**泛型**方法和泛型类使程序员能够用单个方法声明指定一组相关方法,或者用单个类声明指定一组相关类型。
泛型还提供编译时类型安全,允许程序员在编译时捕获无效类型。
使用Java泛型概念,我们可以编写一个用于排序对象数组的泛型方法,然后使用Integer数组、Double数组、String数组等调用泛型方法来排序数组元素。
泛型方法
您可以编写单个泛型方法声明,可以使用不同类型的参数调用该方法。根据传递给泛型方法的参数类型,编译器将适当地处理每个方法调用。以下是定义泛型方法的规则:
所有泛型方法声明都有一个类型参数部分,该部分由尖括号(<和>)分隔,位于方法的返回类型之前(在下面的示例中为< E >)。
每个类型参数部分包含一个或多个用逗号分隔的类型参数。类型参数,也称为类型变量,是一个标识符,它指定一个泛型类型名称。
类型参数可以用来声明返回类型,并作为传递给泛型方法的参数类型的占位符,这些参数称为实际类型参数。
泛型方法的主体与任何其他方法的声明方式相同。请注意,类型参数只能表示引用类型,不能表示原始类型(如int、double和char)。
示例
以下示例说明如何使用单个泛型方法打印不同类型的数组:
public class GenericMethodTest { // generic method printArray public static < E > void printArray( E[] inputArray ) { // Display array elements for(E element : inputArray) { System.out.printf("%s ", element); } System.out.println(); } public static void main(String args[]) { // Create arrays of Integer, Double and 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); // pass an Integer array System.out.println("\nArray doubleArray contains:"); printArray(doubleArray); // pass a Double array System.out.println("\nArray characterArray contains:"); printArray(charArray); // pass a Character array } }
输出
Array integerArray contains: 1 2 3 4 5 Array doubleArray contains: 1.1 2.2 3.3 4.4 Array characterArray contains: H E L L O
有界类型参数
有时您可能希望限制允许传递给类型参数的类型的种类。例如,对数字进行操作的方法可能只想接受Number或其子类的实例。这就是有界类型参数的目的。
要声明有界类型参数,请列出类型参数的名称,后跟extends关键字,然后是其上限。
示例
以下示例说明extends如何以一般意义上表示“extends”(如类)或“implements”(如接口)。此示例是一个泛型方法,用于返回三个Comparable对象的最大的一个:
public class MaximumTest { // determines the largest of three Comparable objects public static <T extends Comparable<T>> T maximum(T x, T y, T z) { T max = x; // assume x is initially the largest if(y.compareTo(max) > 0) { max = y; // y is the largest so far } if(z.compareTo(max) > 0) { max = z; // z is the largest now } return max; // returns the largest object } public static void main(String args[]) { System.out.printf("Max of %d, %d and %d is %d\n\n", 3, 4, 5, maximum( 3, 4, 5 )); System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n", 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 )); System.out.printf("Max of %s, %s and %s is %s\n","pear", "apple", "orange", maximum("pear", "apple", "orange")); } }
输出
Max of 3, 4 and 5 is 5 Max of 6.6,8.8 and 7.7 is 8.8 Max 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\n\n", integerBox.get()); System.out.printf("String Value :%s\n", stringBox.get()); } }
输出
Integer Value :10 String Value :Hello World
Java - 集合框架
在Java 2之前,Java提供了一些临时类,如**Dictionary、Vector、Stack**和**Properties**来存储和操作对象组。尽管这些类非常有用,但它们缺乏一个核心统一的主题。因此,使用Vector的方式与使用Properties的方式不同。
集合框架的设计目标包括:
框架必须是高性能的。基本集合(动态数组、链表、树和哈希表)的实现必须非常高效。
框架必须允许不同类型的集合以类似的方式工作,并具有高度的互操作性。
框架必须能够轻松地扩展和/或适配集合。
为此,整个集合框架都是围绕着一组标准接口设计的。提供了几个这些接口的标准实现,例如**LinkedList、HashSet**和**TreeSet**,您可以按原样使用,也可以根据需要实现您自己的集合。
集合框架是表示和操作集合的统一架构。所有集合框架都包含以下内容:
**接口** - 这些是表示集合的抽象数据类型。接口允许独立于其表示细节来操作集合。在面向对象的语言中,接口通常形成层次结构。
**实现,即类** - 这些是集合接口的具体实现。从本质上说,它们是可重用的数据结构。
**算法** - 这些是在实现集合接口的对象上执行有用计算(如搜索和排序)的方法。算法被称为多态的:也就是说,相同的方法可以用于许多不同实现的相应集合接口。
除了集合之外,框架还定义了几个映射接口和类。映射存储键/值对。虽然映射并非集合(按术语的正确用法),但它们与集合完全集成。
集合接口
集合框架定义了几个接口。本节概述每个接口:
序号 | 接口和描述 |
---|---|
1 | Collection接口
这使您可以使用对象组;它位于集合层次结构的顶部。 |
2 | List接口
这扩展了**Collection**,List的实例存储有序的元素集合。 |
3 | Set
这扩展了Collection来处理集合,集合必须包含唯一元素。 |
4 | SortedSet
这扩展了Set来处理排序的集合。 |
5 | Map
这将唯一键映射到值。 |
6 | Map.Entry
这描述了映射中的一个元素(键/值对)。这是Map的内部类。 |
7 | SortedMap
这扩展了Map,以便键以升序维护。 |
8 | Enumeration
这个遗留接口定义了您可以枚举(一次获取一个)对象集合中的元素的方法。这个遗留接口已被Iterator取代。 |
集合类
Java提供了一组标准集合类来实现集合接口。一些类提供完整的实现,可以按原样使用,而另一些类是抽象类,提供骨架实现,用作创建具体集合的起点。
标准集合类在以下表格中总结:
序号 | 类和描述 |
---|---|
1 | AbstractCollection 实现了Collection接口的大部分内容。 |
2 | AbstractList 扩展AbstractCollection并实现了List接口的大部分内容。 |
3 | AbstractSequentialList 扩展AbstractList供使用集合,该集合使用其元素的顺序访问而不是随机访问。 |
4 | AbstractSet 扩展AbstractCollection并实现了Set接口的大部分内容。 |
5 | AbstractMap 实现了Map接口的大部分内容。 |
AbstractCollection、AbstractSet、AbstractList、AbstractSequentialList和AbstractMap类提供了核心集合接口的骨架实现,以最大限度地减少实现它们的所需工作量。
在上一章中讨论了java.util定义的以下遗留类:
序号 | 类和描述 |
---|---|
1 | Queue
队列接口在java.util包中提供,它实现了Collection接口。队列实现FIFO,即先进先出。这意味着先输入的元素是先删除的元素。 |
集合算法
集合框架定义了几个可以应用于集合和映射的算法。这些算法在Collections类中定义为静态方法。
一些方法可能会抛出ClassCastException异常,该异常发生在尝试比较不相容的类型时;或者抛出UnsupportedOperationException异常,该异常发生在尝试修改不可修改的集合时。
集合定义了三个静态变量:EMPTY_SET、EMPTY_LIST和EMPTY_MAP。所有这些都是不可变的。
序号 | 算法与描述 |
---|---|
1 | 集合算法
以下是所有算法实现的列表。 |
如何使用迭代器?
通常,您可能希望遍历集合中的元素。例如,您可能想要显示每个元素。
最简单的方法是使用迭代器,迭代器是一个实现Iterator或ListIterator接口的对象。
迭代器使您可以遍历集合,获取或删除元素。ListIterator扩展了Iterator,允许双向遍历列表和修改元素。
序号 | 迭代器方法与描述 |
---|---|
1 | 使用Java迭代器
以下是Iterator和ListIterator接口提供的所有方法及其示例的列表。 |
如何使用比较器?
TreeSet和TreeMap都按排序顺序存储元素。但是,正是比较器定义了“排序顺序”的确切含义。
此接口允许我们以任意多种不同的方式对给定集合进行排序。此接口还可以用于对任何类的任何实例进行排序(甚至是我们无法修改的类)。
序号 | 迭代器方法与描述 |
---|---|
1 | 使用Java比较器
以下是Comparator接口提供的所有方法及其示例的列表。 |
如何使用可比较对象?
TreeSet和TreeMap都按排序顺序存储元素。我们可以使用Comparable接口来精确定义“排序顺序”的含义。
此接口允许我们以任意多种不同的方式对给定集合进行排序。此接口还可以用于对任何类的任何实例进行排序(甚至是我们无法修改的类)。
序号 | 迭代器方法与描述 |
---|---|
1 | 使用Java Comparable
以下是Comparable接口提供的所有方法及其示例的列表。 |
总结
Java集合框架使程序员可以访问预打包的数据结构以及用于操作它们的方法。
集合是可以保存对其他对象的引用的对象。集合接口声明可以对每种类型的集合执行的操作。
集合框架的类和接口位于java.util包中。
Java - List接口
List接口扩展了Collection,并声明了存储元素序列的集合的行为。
可以使用基于零的索引按其在列表中的位置插入或访问元素。
列表可以包含重复元素。
除了Collection定义的方法外,List还定义了一些它自己的方法,这些方法在下面的表中进行了总结。
如果集合无法修改,则列表的几个方法将抛出UnsupportedOperationException异常;当一个对象与另一个对象不兼容时,将生成ClassCastException异常。
序号 | 方法和描述 |
---|---|
1 | void add(int index, Object obj) 将obj插入到调用列表中index指定的位置。插入点及其后的任何现有元素都将向上移动。因此,不会覆盖任何元素。 |
2 | boolean addAll(int index, Collection c) 将c的所有元素插入到调用列表中index指定的位置。插入点及其后的任何现有元素都将向上移动。因此,不会覆盖任何元素。如果调用列表发生更改,则返回true;否则返回false。 |
3 | Object get(int index) 返回存储在调用集合中指定索引处的对象。 |
4 | int indexOf(Object obj) 返回obj在调用列表中第一次出现的索引。如果obj不是列表的元素,则返回-1。 |
5 | int lastIndexOf(Object obj) 返回obj在调用列表中最后一次出现的索引。如果obj不是列表的元素,则返回-1。 |
6 | ListIterator listIterator( ) 返回指向调用列表开头的迭代器。 |
7 | ListIterator listIterator(int index) 返回指向调用列表的迭代器,该迭代器从指定的索引开始。 |
8 | Object remove(int index) 从调用列表中删除index位置的元素并返回已删除的元素。生成的列表将被压缩。也就是说,后续元素的索引将递减1。 |
9 | Object set(int index, Object obj) 将obj赋值给调用列表中index指定的位置。 |
10 | List subList(int start, int end) 返回一个列表,其中包含调用列表中从start到end-1的元素。返回列表中的元素也由调用对象引用。 |
示例 1
以上接口已使用ArrayList实现。以下示例解释了上述集合方法的各种类实现中的几种方法:
import java.util.ArrayList; import java.util.List; public class CollectionsDemo { public static void main(String[] args) { List<String> a1 = new ArrayList<>(); a1.add("Zara"); a1.add("Mahnaz"); a1.add("Ayan"); System.out.println(" ArrayList Elements"); System.out.print("\t" + a1); } }
输出
ArrayList Elements [Zara, Mahnaz, Ayan]
示例 2
以上接口已使用LinkedList实现。以下示例解释了上述集合方法的各种类实现中的几种方法:
import java.util.LinkedList; import java.util.List; public class CollectionsDemo { public static void main(String[] args) { List<String> a1 = new LinkedList<>(); a1.add("Zara"); a1.add("Mahnaz"); a1.add("Ayan"); System.out.println(" LinkedList Elements"); System.out.print("\t" + a1); } }
输出
LinkedList Elements [Zara, Mahnaz, Ayan]
示例 3
以上接口已使用ArrayList实现。以下另一个示例解释了上述集合方法的各种类实现中的几种方法:
import java.util.ArrayList; import java.util.List; public class CollectionsDemo { public static void main(String[] args) { List<String> a1 = new ArrayList<>(); a1.add("Zara"); a1.add("Mahnaz"); a1.add("Ayan"); System.out.println(" ArrayList Elements"); System.out.print("\t" + a1); // remove second element a1.remove(1); System.out.println("\n ArrayList Elements"); System.out.print("\t" + a1); } }
输出
ArrayList Elements [Zara, Mahnaz, Ayan] ArrayList Elements [Zara, Ayan]
Java - Queue 接口
Queue接口位于java.util包中,它实现了Collection接口。队列实现FIFO,即先进先出。这意味着先输入的元素是先删除的元素。队列通常用于在处理元素之前保存元素。一旦处理完一个元素,它就会从队列中删除,然后选择下一个项目进行处理。
声明
public interface Queue<E> extends Collection<E>
队列方法
以下是Queue接口的所有实现类都实现的重要队列方法列表:
序号 | 方法和描述 |
---|---|
1 | boolean add(E e)
如果可以在不违反容量限制的情况下立即将指定的元素插入到此队列中,则此方法插入指定的元素,成功时返回true,如果没有空间可用则抛出IllegalStateException异常。 |
2 | E element()
此方法检索但不删除此队列的头部。 |
3 | boolean offer(E e)
此方法如果可以在不违反容量限制的情况下立即将指定的元素插入到此队列中,则插入指定的元素。 |
4 | E peek()
此方法检索但不删除此队列的头部,或者如果此队列为空,则返回null。 |
5 | E poll()
此方法检索并删除此队列的头部,或者如果此队列为空,则返回null。 |
6 | E remove()
此方法检索并删除此队列的头部。 |
继承的方法
此接口继承自以下接口的方法:
- java.util.Collection
- java.lang.Iterable
示例
在此示例中,我们使用LinkedList实例来显示队列的add、peek和size操作。
package com.tutorialspoint; import java.util.LinkedList; import java.util.Queue; public class QueueDemo { public static void main(String[] args) { Queue<Integer> q = new LinkedList<>(); q.add(6); q.add(1); q.add(8); q.add(4); q.add(7); System.out.println("The queue is: " + q); int num1 = q.remove(); System.out.println("The element deleted from the head is: " + num1); System.out.println("The queue after deletion is: " + q); int head = q.peek(); System.out.println("The head of the queue is: " + head); int size = q.size(); System.out.println("The size of the queue is: " + size); } }
输出
The queue is: [6, 1, 8, 4, 7] The element deleted from the head is: 6 The queue after deletion is: [1, 8, 4, 7] The head of the queue is: 1 The size of the queue is: 4
Java - Map接口
Map接口将唯一的键映射到值。键是您以后用来检索值的对象。
给定一个键和一个值,您可以将值存储在Map对象中。存储值后,您可以使用其键检索它。
当调用Map中不存在项目时,一些方法会抛出NoSuchElementException异常。
当对象与Map中的元素不兼容时,将抛出ClassCastException异常。
如果尝试使用空对象并且Map不允许空值,则将抛出NullPointerException异常。
如果尝试更改不可修改的Map,则将抛出UnsupportedOperationException异常。
序号 | 方法和描述 |
---|---|
1 | void clear( ) 从调用Map中删除所有键值对。 |
2 | boolean containsKey(Object k) 如果调用Map包含k作为键,则返回true。否则,返回false。 |
3 | boolean containsValue(Object v) 如果Map包含v作为值,则返回true。否则,返回false。 |
4 | Set entrySet( ) 返回一个包含Map中条目的Set。该Set包含Map.Entry类型的对象。此方法提供调用Map的Set视图。 |
5 | boolean equals(Object obj) 如果obj是一个Map并且包含相同的条目,则返回true。否则,返回false。 |
6 | Object get(Object k) 返回与键k关联的值。 |
7 | int hashCode( ) 返回调用Map的哈希码。 |
8 | boolean isEmpty( ) 如果调用Map为空,则返回true。否则,返回false。 |
9 | Set keySet( ) 返回一个包含调用Map中键的Set。此方法提供调用Map中键的Set视图。 |
10 | Object put(Object k, Object v) 将一个条目放入调用Map中,覆盖与该键关联的任何先前值。键和值分别为k和v。如果键不存在,则返回null。否则,返回与该键链接的先前值。 |
11 | void putAll(Map m) 将m中的所有条目放入此Map中。 |
12 | Object remove(Object k) 删除键等于k的条目。 |
13 | int size( ) 返回Map中键值对的数量。 |
14 | Collection values( ) 返回一个包含Map中值的集合。此方法提供Map中值的集合视图。 |
示例 1
Map在各种类中都有实现,例如HashMap。以下是一个示例,用于解释Map的功能:
import java.util.HashMap; import java.util.Map; public class CollectionsDemo { public static void main(String[] args) { Map<String, String> m1 = new HashMap<>(); m1.put("Zara", "8"); m1.put("Mahnaz", "31"); m1.put("Ayan", "12"); m1.put("Daisy", "14"); System.out.println(); System.out.println(" Map Elements"); System.out.print("\t" + m1); } }
输出
Map Elements {Daisy = 14, Ayan = 12, Zara = 8, Mahnaz = 31}
示例 2
Map在各种类中都有实现,例如TreeMap,它根据键对条目进行排序。以下是一个示例,使用TreeMap解释Map的功能:
import java.util.Map; import java.util.TreeMap; public class CollectionsDemo { public static void main(String[] args) { Map<String, String> m1 = new TreeMap<>(); m1.put("Zara", "8"); m1.put("Mahnaz", "31"); m1.put("Ayan", "12"); m1.put("Daisy", "14"); System.out.println(); System.out.println(" Map Elements"); System.out.print("\t" + m1); } }
输出
Map Elements {Ayan=12, Daisy=14, Mahnaz=31, Zara=8}
示例 3
Map在各种类中都有实现,例如HashMap。以下是一个示例,使用HashMap解释Map函数,以向Map添加和删除元素:
import java.util.HashMap; import java.util.Map; public class CollectionsDemo { public static void main(String[] args) { Map<String, String> m1 = new HashMap<>(); m1.put("Zara", "8"); m1.put("Mahnaz", "31"); m1.put("Ayan", "12"); m1.put("Daisy", "14"); System.out.println(); System.out.println(" Map Elements"); System.out.print("\t" + m1); m1.remove("Daisy"); System.out.println(" Map Elements"); System.out.print("\t" + m1); } }
输出
Map Elements {Daisy=14, Ayan=12, Zara=8, Mahnaz=31} Map Elements {Ayan=12, Zara=8, Mahnaz=31}
Java - SortedMap接口
SortedMap接口扩展了Map。它确保条目按升序键顺序维护。
当调用Map中没有项目时,一些方法会抛出NoSuchElementException异常。当对象与Map中的元素不兼容时,将抛出ClassCastException异常。如果尝试使用空对象而Map不允许空值,则将抛出NullPointerException异常。
SortedMap声明的方法在下面的表中进行了总结:
序号 | 方法和描述 |
---|---|
1 | Comparator comparator( ) 返回调用SortedMap的比较器。如果对调用Map使用自然排序,则返回null。 |
2 | Object firstKey( ) 返回调用Map中的第一个键。 |
3 | SortedMap headMap(Object end) 返回一个SortedMap,用于那些键小于end的Map条目。 |
4 | Object lastKey( ) 返回调用Map中的最后一个键。 |
5 | SortedMap subMap(Object start, Object end) 返回一个映射,其中包含键大于或等于start且小于end的条目。 |
6 | SortedMap tailMap(Object start) 返回一个映射,其中包含键大于或等于start的条目。 |
示例 1
以下是一个示例,展示如何使用TreeMap获取SortedMap的值:
import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; public class MapDemo { public static void main(String args[]) { // Create a hash map SortedMap<String, Double> map = new TreeMap<>(); // Put elements to the map map.put("Zara", Double.valueOf(3434.34)); map.put("Mahnaz", Double.valueOf(123.22)); map.put("Ayan", Double.valueOf(1378.00)); map.put("Daisy", Double.valueOf(99.22)); map.put("Qadir", Double.valueOf(-19.08)); // Get a set of the entries Set<Map.Entry<String, Double>> set = map.entrySet(); // Get an iterator Iterator<Map.Entry<String, Double>> i = set.iterator(); // Display elements while(i.hasNext()) { Map.Entry<String, Double> me = i.next(); System.out.print(me.getKey() + ": "); System.out.println(me.getValue()); } } }
输出
Daisy: 99.22 Ayan: 1378.0 Zara: 3434.34 Qadir: -19.08 Mahnaz: 123.22
示例 2
以下是一个示例,展示如何使用TreeMap设置SortedMap的值:
import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; public class MapDemo { public static void main(String args[]) { // Create a hash map SortedMap<String, Double> map = new TreeMap<>(); // Put elements to the map map.put("Zara", Double.valueOf(3434.34)); map.put("Mahnaz", Double.valueOf(123.22)); map.put("Ayan", Double.valueOf(1378.00)); map.put("Daisy", Double.valueOf(99.22)); map.put("Qadir", Double.valueOf(-19.08)); // Get a set of the entries Set<Map.Entry<String, Double>> set = map.entrySet(); // Get an iterator Iterator<Map.Entry<String, Double>> i = set.iterator(); // Display elements while(i.hasNext()) { Map.Entry<String, Double> me = i.next(); me.setValue(me.getValue() * 10); System.out.print(me.getKey() + ": "); System.out.println(me.getValue()); } } }
输出
Daisy: 992.2 Ayan: 13780.0 Zara: 34343.4 Qadir: -190.79999999999998 Mahnaz: 1232.2
示例 3
以下是一个示例,展示如何使用TreeMap获取SortedMap条目的键:
import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; public class MapDemo { public static void main(String args[]) { // Create a hash map SortedMap<String, Double> map = new TreeMap<>(); // Put elements to the map map.put("Zara", Double.valueOf(3434.34)); map.put("Mahnaz", Double.valueOf(123.22)); map.put("Ayan", Double.valueOf(1378.00)); map.put("Daisy", Double.valueOf(99.22)); map.put("Qadir", Double.valueOf(-19.08)); // Get a set of the entries Set<Map.Entry<String, Double>> set = map.entrySet(); // Get an iterator Iterator<Map.Entry<String, Double>> i = set.iterator(); // Display elements while(i.hasNext()) { Map.Entry<String, Double> me = i.next(); System.out.println(me.getKey()); } } }
输出
Daisy Ayan Zara Qadir Mahnaz
Java - Set接口
Set是一个不能包含重复元素的集合。它模拟了数学集合的抽象。
Set接口只包含从Collection继承的方法,并增加了不允许重复元素的限制。
Set还对equals和hashCode操作的行为增加了更强的约定,允许即使Set实例的实现类型不同也能进行有意义的比较。
Set声明的方法总结在下表中:
序号 | 方法和描述 |
---|---|
1 | add( ) 将对象添加到集合中。 |
2 | clear( ) 删除集合中的所有对象。 |
3 | contains( ) 如果指定的对象是集合中的元素,则返回true。 |
4 | isEmpty( ) 如果集合没有元素,则返回true。 |
5 | iterator( ) 返回集合的Iterator对象,可用于检索对象。 |
6 | remove( ) 从集合中删除指定的对象。 |
7 | size( ) 返回集合中元素的数量。 |
示例 1
Set在HashSet、TreeSet、LinkedHashSet等各种类中都有实现。以下是一个使用HashSet解释Set功能的示例:
import java.util.HashSet; import java.util.Set; public class SetDemo { public static void main(String args[]) { int count[] = {34, 22,10,60,30,22}; Set<Integer> set = new HashSet<>(); try { for(int i = 0; i < 5; i++) { set.add(count[i]); } System.out.println(set); } catch(Exception e) {} } }
输出
[34, 22, 10, 60, 30] The sorted list is: [10, 22, 30, 34, 60] The First element of the set is: 10 The last element of the set is: 60
示例 2
Set在HashSet、TreeSet、LinkedHashSet等各种类中都有实现。以下是一个使用TreeSet解释Set功能的示例:
import java.util.HashSet; import java.util.Set; import java.util.TreeSet; public class SetDemo { public static void main(String args[]) { int count[] = {34, 22,10,60,30,22}; Set<Integer> set = new HashSet<>(); try { for(int i = 0; i < 5; i++) { set.add(count[i]); } System.out.println(set); TreeSet<Integer> sortedSet = new TreeSet<>(set); System.out.println("The sorted list is:"); System.out.println(sortedSet); System.out.println("The First element of the set is: "+ (Integer)sortedSet.first()); System.out.println("The last element of the set is: "+ (Integer)sortedSet.last()); } catch(Exception e) {} } }
输出
[34, 22, 10, 60, 30] The sorted list is: [10, 22, 30, 34, 60] The First element of the set is: 10 The last element of the set is: 60
示例 3
Set在HashSet、TreeSet、LinkedHashSet等各种类中都有实现。以下是一个使用TreeSet操作解释Set功能的示例:
import java.util.HashSet; import java.util.Set; import java.util.TreeSet; public class SetDemo { public static void main(String args[]) { int count[] = {34, 22,10,60,30,22}; Set<Integer> set = new HashSet<>(); try { for(int i = 0; i < 5; i++) { set.add(count[i]); } System.out.println(set); TreeSet<Integer> sortedSet = new TreeSet<>(set); System.out.println("The sorted list is:"); System.out.println(sortedSet); sortedSet.clear(); System.out.println("The sorted list is:"); System.out.println(sortedSet); } catch(Exception e) {} } }
输出
[34, 22, 10, 60, 30] The sorted list is: [10, 22, 30, 34, 60] The sorted list is: []
Java - SortedSet接口
SortedSet接口扩展了Set,并声明了按升序排序的集合的行为。除了Set定义的方法外,SortedSet接口还声明了下表中总结的方法:
当调用集合中不包含任何项目时,几个方法会抛出NoSuchElementException异常。当对象与集合中的元素不兼容时,会抛出ClassCastException异常。
如果尝试使用空对象并且集合中不允许为空,则会抛出NullPointerException异常。
序号 | 方法和描述 |
---|---|
1 | Comparator comparator( ) 返回调用SortedSet的比较器。如果对该集合使用自然排序,则返回null。 |
2 | Object first( ) 返回调用SortedSet中的第一个元素。 |
3 | SortedSet headSet(Object end) 返回一个SortedSet,其中包含调用SortedSet中小于end的元素。返回的SortedSet中的元素也由调用SortedSet引用。 |
4 | Object last( ) 返回调用SortedSet中的最后一个元素。 |
5 | SortedSet subSet(Object start, Object end) 返回一个SortedSet,其中包含start和end之间的元素。1. 返回的集合中的元素也由调用对象引用。 |
6 | SortedSet tailSet(Object start) 返回一个SortedSet,其中包含SortedSet中大于或等于start的元素。返回的集合中的元素也由调用对象引用。 |
示例 1
SortedSet在TreeSet等各种类中都有实现。以下是一个带有add操作的TreeSet类的示例:
import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; public class SortedSetDemo { public static void main(String[] args) { // Create the sorted set SortedSet<String> set = new TreeSet<>(); // Add elements to the set set.add("b"); set.add("c"); set.add("a"); // Iterating over the elements in the set Iterator it = set.iterator(); while (it.hasNext()) { // Get element Object element = it.next(); System.out.println(element.toString()); } } }
输出
a b c
示例 2
SortedSet在TreeSet等各种类中都有实现。以下是一个带有add和remove操作的TreeSet类的示例:
import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; public class SortedSetDemo { public static void main(String[] args) { // Create the sorted set SortedSet<String> set = new TreeSet<>(); // Add elements to the set set.add("b"); set.add("c"); set.add("a"); set.add("d"); set.add("e"); set.add("f"); // remove elements set.remove("c"); set.remove("f"); // Iterating over the elements in the set Iterator it = set.iterator(); while (it.hasNext()) { // Get element Object element = it.next(); System.out.println(element.toString()); } } }
输出
a b d e
示例 3
SortedSet在TreeSet等各种类中都有实现。以下是一个带有add和clear操作的TreeSet类的示例:
import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; public class SortedSetDemo { public static void main(String[] args) { // Create the sorted set SortedSet<String> set = new TreeSet<>(); // Add elements to the set set.add("b"); set.add("c"); set.add("a"); set.add("d"); set.add("e"); set.add("f"); System.out.println(set); // remove elements set.clear(); System.out.println(set); } }
输出
[a, b, c, d, e, f] []
Java - 数据结构
Java实用程序包提供的数据结构非常强大,并且执行各种功能。这些数据结构包括以下接口和类:
- Enumeration
- BitSet
- Vector
- Stack
- Dictionary
- Hashtable
- Properties
所有这些类现在都是遗留类,Java-2引入了名为Collections Framework的新框架,这将在下一章中讨论。
Enumeration
Enumeration接口本身并不是数据结构,但在其他数据结构的上下文中非常重要。Enumeration接口定义了一种从数据结构中检索连续元素的方法。
例如,Enumeration定义了一个名为nextElement的方法,用于获取包含多个元素的数据结构中的下一个元素。
示例
以下是一个示例,展示了Enumeration在Vector中的用法。
import java.util.Vector; import java.util.Enumeration; public class EnumerationTester { public static void main(String args[]) { Enumeration<String> days; Vector<String> dayNames = new Vector<>(); dayNames.add("Sunday"); dayNames.add("Monday"); dayNames.add("Tuesday"); dayNames.add("Wednesday"); dayNames.add("Thursday"); dayNames.add("Friday"); dayNames.add("Saturday"); days = dayNames.elements(); while (days.hasMoreElements()) { System.out.println(days.nextElement()); } } }
输出
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
要了解更多关于此接口的信息,请查看Enumeration。
BitSet
BitSet类实现了一组位或标志,可以分别设置和清除。
当需要跟踪一组布尔值时,此类非常有用;只需为每个值分配一位,并根据需要设置或清除它。
示例
以下程序说明了此数据结构支持的几种方法:
import java.util.BitSet; public class BitSetDemo { public static void main(String args[]) { BitSet bits1 = new BitSet(16); BitSet bits2 = new BitSet(16); // set some bits for(int i = 0; i < 16; i++) { if((i % 2) == 0) bits1.set(i); if((i % 5) != 0) bits2.set(i); } System.out.println("Initial pattern in bits1: "); System.out.println(bits1); System.out.println("\nInitial pattern in bits2: "); System.out.println(bits2); // AND bits bits2.and(bits1); System.out.println("\nbits2 AND bits1: "); System.out.println(bits2); // OR bits bits2.or(bits1); System.out.println("\nbits2 OR bits1: "); System.out.println(bits2); // XOR bits bits2.xor(bits1); System.out.println("\nbits2 XOR bits1: "); System.out.println(bits2); } }
输出
Initial pattern in bits1: {0, 2, 4, 6, 8, 10, 12, 14} Initial pattern in bits2: {1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14} bits2 AND bits1: {2, 4, 6, 8, 12, 14} bits2 OR bits1: {0, 2, 4, 6, 8, 10, 12, 14} bits2 XOR bits1: {}
Vector
Vector类类似于传统的Java数组,不同之处在于它可以根据需要增长以容纳新元素。
与数组一样,Vector对象中的元素可以通过向量中的索引访问。
使用Vector类的好处是,不必担心在创建时将其设置为特定大小;它会在必要时自动收缩和增长。
示例
以下程序说明了此集合支持的几种方法:
import java.util.*; public class VectorDemo { public static void main(String args[]) { // initial size is 3, increment is 2 Vector v = new Vector(3, 2); System.out.println("Initial size: " + v.size()); System.out.println("Initial capacity: " + v.capacity()); v.addElement(new Integer(1)); v.addElement(new Integer(2)); v.addElement(new Integer(3)); v.addElement(new Integer(4)); System.out.println("Capacity after four additions: " + v.capacity()); v.addElement(new Double(5.45)); System.out.println("Current capacity: " + v.capacity()); v.addElement(new Double(6.08)); v.addElement(new Integer(7)); System.out.println("Current capacity: " + v.capacity()); v.addElement(new Float(9.4)); v.addElement(new Integer(10)); System.out.println("Current capacity: " + v.capacity()); v.addElement(new Integer(11)); v.addElement(new Integer(12)); System.out.println("First element: " + (Integer)v.firstElement()); System.out.println("Last element: " + (Integer)v.lastElement()); if(v.contains(new Integer(3))) System.out.println("Vector contains 3."); // enumerate the elements in the vector. Enumeration vEnum = v.elements(); System.out.println("\nElements in vector:"); while(vEnum.hasMoreElements()) System.out.print(vEnum.nextElement() + " "); System.out.println(); } }
输出
Initial size: 0 Initial capacity: 3 Capacity after four additions: 5 Current capacity: 5 Current capacity: 7 Current capacity: 9 First element: 1 Last element: 12 Vector contains 3. Elements in vector: 1 2 3 4 5.45 6.08 7 9.4 10 11 12
Stack
Stack类实现了一个后进先出(LIFO)的元素堆栈。
可以将堆栈从字面上理解为对象的垂直堆栈;添加新元素时,它会堆叠在其他元素之上。
从堆栈中提取元素时,它会从顶部取出。换句话说,添加到堆栈中的最后一个元素是第一个返回的元素。
示例
以下程序说明了此集合支持的几种方法:
import java.util.*; public class StackDemo { static void showpush(Stack st, int a) { st.push(new Integer(a)); System.out.println("push(" + a + ")"); System.out.println("stack: " + st); } static void showpop(Stack st) { System.out.print("pop -> "); Integer a = (Integer) st.pop(); System.out.println(a); System.out.println("stack: " + st); } public static void main(String args[]) { Stack st = new Stack(); System.out.println("stack: " + st); showpush(st, 42); showpush(st, 66); showpush(st, 99); showpop(st); showpop(st); showpop(st); try { showpop(st); } catch (EmptyStackException e) { System.out.println("empty stack"); } } }
输出
stack: [ ] push(42) stack: [42] push(66) stack: [42, 66] push(99) stack: [42, 66, 99] pop -> 99 stack: [42, 66] pop -> 66 stack: [42] pop -> 42 stack: [ ] pop -> empty stack
Dictionary
Dictionary类是一个抽象类,它定义了一个用于将键映射到值的数据结构。
这在需要通过特定键而不是整数索引访问数据的情况下非常有用。
由于Dictionary类是抽象类,它只提供键映射数据结构的框架,而不是具体的实现。
示例
以下示例显示了Java Dictionary keys()方法的用法。我们使用Integer, Integer的Hashtable对象创建一个字典实例。然后我们向其中添加了一些元素。使用keys()方法检索枚举,然后迭代枚举以打印字典的键。
package com.tutorialspoint; import java.util.Enumeration; import java.util.Dictionary; import java.util.Hashtable; public class DictionaryDemo { public static void main(String[] args) { // create a new hashtable Dictionary<Integer, Integer> dictionary = new Hashtable<>(); // add 2 elements dictionary.put(1, 1); dictionary.put(2, 2); Enumeration<Integer> enumeration = dictionary.keys(); while(enumeration.hasMoreElements()) { System.out.println(enumeration.nextElement()); } } }
输出
2 1
Hashtable
Hashtable类提供了一种基于某些用户定义的键结构组织数据的方法。
例如,在地址列表哈希表中,可以根据邮政编码等键存储和排序数据,而不是根据人的姓名。
关于哈希表而言,键的具体含义完全取决于哈希表的用法及其包含的数据。
示例
以下示例显示了Java Hashtable contains()方法的用法,用于检查Hashtable中是否存在值。我们创建了一个Integer,Integer的Hashtable对象。然后添加了一些条目,打印表,并使用contains()检查表中的两个值。
package com.tutorialspoint; import java.util.Hashtable; public class HashtableDemo { public static void main(String args[]) { // create hash table Hashtable<Integer,Integer> hashtable = new Hashtable<>(); // populate hash table hashtable.put(1, 1); hashtable.put(2, 2); hashtable.put(3, 3); System.out.println("Initial table elements: " + hashtable); System.out.println("Hashtable contains 2 as value: " + hashtable.contains(2)); System.out.println("Hashtable contains 4 as value: " + hashtable.contains(4)); } }
输出
Initial table elements: {3=3, 2=2, 1=1} Hashtable contains 2 as value: true Hashtable contains 4 as value: false
Properties
Properties是Hashtable的子类。它用于维护值的列表,其中键是字符串,值也是字符串。
许多其他Java类都使用Properties类。例如,它是System.getProperties( )在获取环境值时返回的对象类型。
示例
以下示例显示了Java Properties getProperty(String key)方法的用法,该方法用于从Properties中根据键获取值。我们创建了一个Properties对象。然后添加了一些条目。使用getProperty()方法,检索并打印值。
package com.tutorialspoint; import java.util.Properties; public class PropertiesDemo { public static void main(String[] args) { Properties properties = new Properties(); //populate properties object properties.put("1", "tutorials"); properties.put("2", "point"); properties.put("3", "is best"); System.out.println("Properties elements: " + properties); System.out.println("Value: " + properties.getProperty("1")); } }
输出
Properties elements: {1=tutorials, 2=point, 3=is best} Value: tutorials
Java - Enumeration接口
Enumeration接口定义了可以枚举(一次获取一个)对象集合中的元素的方法。
这个遗留接口已被Iterator取代。虽然没有被弃用,但Enumeration被认为对于新代码来说已经过时。但是,它被Vector和Properties等遗留类定义的几种方法使用,被其他几个API类使用,并且目前在应用程序代码中被广泛使用。
Enumeration声明的方法总结在下表中:
序号 | 方法和描述 |
---|---|
1 | boolean hasMoreElements( ) 实现时,如果仍有更多元素要提取,则必须返回true;如果所有元素都已枚举,则返回false。 |
2 | Object nextElement( ) 这将枚举中的下一个对象作为通用Object引用返回。 |
示例 1
以下是一个示例,展示了Enumeration在Vector中的用法。
import java.util.Vector; import java.util.Enumeration; public class EnumerationTester { public static void main(String args[]) { Enumeration<String> days; Vector<String> dayNames = new Vector<>(); dayNames.add("Sunday"); dayNames.add("Monday"); dayNames.add("Tuesday"); dayNames.add("Wednesday"); dayNames.add("Thursday"); dayNames.add("Friday"); dayNames.add("Saturday"); days = dayNames.elements(); while (days.hasMoreElements()) { System.out.println(days.nextElement()); } } }
输出
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
示例 2
以下是一个示例,展示了Enumeration在Properties中的用法,用于打印值。
import java.util.Vector; import java.util.Enumeration; import java.util.Properties; public class EnumerationTester { public static void main(String args[]) { Enumeration<Object> days; Properties dayNames = new Properties(); dayNames.put(1, "Sunday"); dayNames.put(2,"Monday"); dayNames.put(3,"Tuesday"); dayNames.put(4,"Wednesday"); dayNames.put(5,"Thursday"); dayNames.put(6,"Friday"); dayNames.put(7,"Saturday"); days = dayNames.elements(); while (days.hasMoreElements()) { System.out.println(days.nextElement()); } } }
输出
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
Java - 集合算法
集合框架定义了几种可以应用于集合和映射的算法。
这些算法在Collections类中定义为静态方法。一些方法可能会抛出**ClassCastException**异常(当尝试比较不兼容的类型时发生),或者抛出**UnsupportedOperationException**异常(当尝试修改不可修改的集合时发生)。
集合框架算法中定义的方法总结在下表中:
序号 | 方法和描述 |
---|---|
1 | static int binarySearch(List list, Object value, Comparator c) 根据**c**在列表中搜索value。返回value在列表中的位置,如果未找到value,则返回-1。 |
2 | static int binarySearch(List list, Object value) 在列表中搜索value。列表必须已排序。返回value在列表中的位置,如果未找到value,则返回-1。 |
3 | static void copy(List list1, List list2) 将list2的元素复制到list1。 |
4 | static Enumeration enumeration(Collection c) 返回**c**的枚举。 |
5 | static void fill(List list, Object obj) 将obj分配给列表的每个元素。 |
6 | static int indexOfSubList(List list, List subList) 在列表中搜索subList的第一次出现。返回第一次匹配的索引,如果未找到匹配项,则返回-1。 |
7 | static int lastIndexOfSubList(List list, List subList) 在列表中搜索subList的最后一次出现。返回最后一次匹配的索引,如果未找到匹配项,则返回-1。 |
8 | static ArrayList list(Enumeration enum) 返回一个包含enum元素的ArrayList。 |
9 | static Object max(Collection c, Comparator comp) 返回**c**中由comp确定的最大元素。 |
10 | static Object max(Collection c) 返回**c**中由自然排序确定的最大元素。集合不必排序。 |
11 | static Object min(Collection c, Comparator comp) 返回**c**中由comp确定的最小元素。集合不必排序。 |
12 | static Object min(Collection c) 返回**c**中由自然排序确定的最小元素。 |
13 | static List nCopies(int num, Object obj) 返回包含在不可变列表中的num个obj副本。num必须大于或等于零。 |
14 | static boolean replaceAll(List list, Object old, Object new) 替换列表中所有 old 对象为 new 对象。如果至少替换了一个对象,则返回 true;否则返回 false。 |
15 | static void reverse(List list) 反转列表中的序列。 |
16 | static Comparator reverseOrder( ) 返回一个反向比较器。 |
17 | static void rotate(List list, int n) 将列表向右旋转 n 个位置。要向左旋转,请对 n 使用负值。 |
18 | static void shuffle(List list, Random r) 使用 r 作为随机数源,对列表中的元素进行洗牌(即随机化)。 |
19 | static void shuffle(List list) 对列表中的元素进行洗牌(即随机化)。 |
20 | static Set singleton(Object obj) 将 obj 作为不可变集合返回。这是一种将单个对象转换为集合的简便方法。 |
21 | static List singletonList(Object obj) 将 obj 作为不可变列表返回。这是一种将单个对象转换为列表的简便方法。 |
22 | static Map singletonMap(Object k, Object v) 将键值对 k/v 作为不可变映射返回。这是一种将单个键值对转换为映射的简便方法。 |
23 | static void sort(List list, Comparator comp) 根据 comp 对列表的元素进行排序。 |
24 | static void sort(List list) 根据列表元素的自然顺序对其进行排序。 |
25 | static void swap(List list, int idx1, int idx2) 交换列表中由 idx1 和 idx2 指定的索引处的元素。 |
26 | static Collection synchronizedCollection(Collection c) 返回一个由 c 支持的线程安全集合。 |
27 | static List synchronizedList(List list) 返回一个由 list 支持的线程安全列表。 |
28 | static Map synchronizedMap(Map m) 返回一个由 m 支持的线程安全映射。 |
29 | static Set synchronizedSet(Set s) 返回一个由 s 支持的线程安全集合。 |
30 | static SortedMap synchronizedSortedMap(SortedMap sm) 返回一个由 sm 支持的线程安全排序映射。 |
31 | static SortedSet synchronizedSortedSet(SortedSet ss) 返回一个由 ss 支持的线程安全排序集合。 |
32 | static Collection unmodifiableCollection(Collection c) 返回一个由 c 支持的不可修改集合。 |
33 | static List unmodifiableList(List list) 返回一个由 list 支持的不可修改列表。 |
34 | static Map unmodifiableMap(Map m) 返回一个由 m 支持的不可修改映射。 |
35 | static Set unmodifiableSet(Set s) 返回一个由 s 支持的不可修改集合。 |
36 | static SortedMap unmodifiableSortedMap(SortedMap sm) 返回一个由 sm 支持的不可修改排序映射。 |
37 | static SortedSet unmodifiableSortedSet(SortedSet ss) 返回一个由 ss 支持的不可修改排序集合。 |
示例 1
以下是一个示例,它演示了使用 LinkedList 添加元素并按反向顺序对其元素进行排序的各种算法。使用迭代器迭代列表,然后进行洗牌,最后检索并打印最小值和最大值。
import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class AlgorithmsDemo { public static void main(String args[]) { // Create and initialize linked list List<Integer< ll = new LinkedList<<(); ll.add(Integer.valueOf(-8)); ll.add(Integer.valueOf(20)); ll.add(Integer.valueOf(-20)); ll.add(Integer.valueOf(8)); // Create a reverse order comparator Comparator<Integer< r = Collections.reverseOrder(); // Sort list by using the comparator Collections.sort(ll, r); // Get iterator Iterator<Integer< li = ll.iterator(); System.out.print("List sorted in reverse: "); while(li.hasNext()) { System.out.print(li.next() + " "); } System.out.println(); } }
输出
List sorted in reverse: 20 8 -8 -20
示例 2
以下是一个示例,它演示了使用 LinkedList 添加元素并按自然顺序对其元素进行排序的各种算法。使用迭代器迭代列表,然后进行洗牌,最后检索并打印最小值和最大值。
import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class AlgorithmsDemo { public static void main(String args[]) { // Create and initialize linked list List<Integer> ll = new LinkedList<>(); ll.add(Integer.valueOf(-8)); ll.add(Integer.valueOf(20)); ll.add(Integer.valueOf(-20)); ll.add(Integer.valueOf(8)); // Sort list by using the default comparator Collections.sort(ll); // Get iterator Iterator<Integer> li = ll.iterator(); System.out.print("List sorted: "); while(li.hasNext()) { System.out.print(li.next() + " "); } System.out.println(); Collections.shuffle(ll); // display randomized list li = ll.iterator(); System.out.print("List shuffled: "); while(li.hasNext()) { System.out.print(li.next() + " "); } System.out.println(); System.out.println("Minimum: " + Collections.min(ll)); System.out.println("Maximum: " + Collections.max(ll)); } }
输出
List sorted: -20 -8 8 20 List shuffled: -20 -8 20 8 Minimum: -20 Maximum: 20
示例 3
以下是一个示例,它演示了使用 LinkedList 添加字符串元素并按自然顺序对其元素进行排序的各种算法。打印列表,然后进行洗牌,最后检索并打印最小值和最大值。
import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class AlgorithmsDemo { public static void main(String args[]) { // Create and initialize linked list List<String> list = new LinkedList<>(); list.add("Ram"); list.add("Mohan"); list.add("Julie"); list.add("Raheem"); // Sort list by using the default comparator Collections.sort(list); // print the sorted list System.out.println("Sorted List: " + list); // shuffle the list Collections.shuffle(list); // print the shuffled list System.out.println("Shuffled List: " + list); System.out.println("Minimum: " + Collections.min(list)); System.out.println("Maximum: " + Collections.max(list)); } }
输出
Sorted List: [Julie, Mohan, Raheem, Ram] Shuffled List: [Mohan, Raheem, Julie, Ram] Minimum: Julie Maximum: Ram
Java - 如何使用迭代器?
通常,您需要遍历集合中的元素。例如,您可能需要显示每个元素。最简单的方法是使用迭代器,它是一个实现 Iterator 或 ListIterator 接口的对象。
迭代器使您可以遍历集合,获取或删除元素。ListIterator 扩展了 Iterator,允许对列表进行双向遍历以及修改元素。
在您可以通过迭代器访问集合之前,必须先获取一个迭代器。每个集合类都提供一个 iterator( ) 方法,该方法返回指向集合开头的迭代器。通过使用此迭代器对象,您可以一次访问集合中的每个元素。
一般来说,要使用迭代器遍历集合的内容,请按照以下步骤操作:
通过调用集合的 iterator( ) 方法,获取指向集合开头的迭代器。
设置一个循环,该循环调用 hasNext( )。只要 hasNext( ) 返回 true,循环就一直迭代。
在循环中,通过调用 next( ) 获取每个元素。
对于实现 List 的集合,您还可以通过调用 ListIterator 获取迭代器。
Iterator声明的方法
序号 | 方法和描述 |
---|---|
1 | boolean hasNext( ) 如果还有更多元素,则返回 true;否则返回 false。 |
2 | Object next( ) 返回下一个元素。如果没有下一个元素,则抛出 NoSuchElementException。 |
3 | void remove( ) 删除当前元素。如果尝试调用 remove( ) 之前没有调用 next( ),则抛出 IllegalStateException。 |
ListIterator声明的方法
序号 | 方法和描述 |
---|---|
1 | void add(Object obj) 在下一个 next( ) 调用将返回的元素前面,将 obj 插入列表中。 |
2 | boolean hasNext( ) 如果存在下一个元素,则返回 true;否则返回 false。 |
3 | boolean hasPrevious( ) 如果存在前一个元素,则返回 true;否则返回 false。 |
4 | Object next( ) 返回下一个元素。如果没有下一个元素,则抛出 NoSuchElementException。 |
5 | int nextIndex( ) 返回下一个元素的索引。如果没有下一个元素,则返回列表的大小。 |
6 | Object previous( ) 返回前一个元素。如果没有前一个元素,则抛出 NoSuchElementException。 |
7 | int previousIndex( ) 返回前一个元素的索引。如果没有前一个元素,则返回 -1。 |
8 | void remove( ) 从列表中删除当前元素。如果在调用 next( ) 或 previous( ) 之前调用 remove( ),则抛出 IllegalStateException。 |
9 | void set(Object obj) 将 obj 分配给当前元素。这是上次通过调用 next( ) 或 previous( ) 返回的元素。 |
示例 1
这是一个演示 Iterator 的示例。它使用 ArrayList 对象,但一般原则适用于任何类型的集合。
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorDemo { public static void main(String args[]) { // Create an array list List<String> al = new ArrayList<>(); // add elements to the array list al.add("C"); al.add("A"); al.add("E"); al.add("B"); al.add("D"); al.add("F"); // Use iterator to display contents of al System.out.print("Original contents of al: "); Iteratoritr = al.iterator(); while(itr.hasNext()) { Object element = itr.next(); System.out.print(element + " "); } System.out.println(); } }
输出
Original contents of al: C A E B D F
示例 2
这是一个演示 ListIterator 的示例。它使用 ArrayList 对象,但一般原则适用于任何类型的集合。
当然,只有那些实现 List 接口的集合才可以使用 ListIterator。
import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class IteratorDemo { public static void main(String args[]) { // Create an array list List<String> al = new ArrayList<>(); // add elements to the array list al.add("C"); al.add("A"); al.add("E"); al.add("B"); al.add("D"); al.add("F"); // Use iterator to display contents of al System.out.print("Original contents of al: "); Iterator<String> itr = al.iterator(); while(itr.hasNext()) { Object element = itr.next(); System.out.print(element + " "); } } }
输出
Original contents of al: C A E B D F
示例 3
这是一个演示 ListIterator 在迭代时修改列表的示例。它使用 ArrayList 对象,但一般原则适用于任何类型的集合。
import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class IteratorDemo { public static void main(String args[]) { // Create an array list List<String> al = new ArrayList<>(); // add elements to the array list al.add("C"); al.add("A"); al.add("E"); al.add("B"); al.add("D"); al.add("F"); // Use iterator to display contents of al System.out.print("Original contents of al: "); Iterator<String> itr = al.iterator(); while(itr.hasNext()) { Object element = itr.next(); System.out.print(element + " "); } System.out.println(); // Modify objects being iterated ListIterator<String> litr = al.listIterator(); while(litr.hasNext()) { Object element = litr.next(); litr.set(element + "+"); } System.out.print("Modified contents of al: "); itr = al.iterator(); while(itr.hasNext()) { Object element = itr.next(); System.out.print(element + " "); } System.out.println(); // Now, display the list backwards System.out.print("Modified list backwards: "); while(litr.hasPrevious()) { Object element = litr.previous(); System.out.print(element + " "); } System.out.println(); } }
输出
Original contents of al: C A E B D F Modified contents of al: C+ A+ E+ B+ D+ F+ Modified list backwards: F+ D+ B+ E+ A+ C+
Java - 如何使用 Comparator?
TreeSet 和 TreeMap 都以排序顺序存储元素。但是,正是比较器定义了“排序顺序”的确切含义。
Comparator 接口定义了两个方法:compare( ) 和 equals( )。此处显示的 compare( ) 方法比较两个元素的顺序:
compare 方法
int compare(Object obj1, Object obj2)
obj1 和 obj2 是要比较的对象。如果对象相等,此方法返回零。如果 obj1 大于 obj2,则返回正值;否则返回负值。
通过覆盖 compare( ),您可以更改对象的排序方式。例如,要按反向顺序排序,您可以创建一个反转比较结果的比较器。
equals 方法
此处显示的 equals( ) 方法测试对象是否等于调用比较器:
boolean equals(Object obj)
obj 是要测试相等性的对象。如果 obj 和调用对象都是 Comparator 对象并使用相同的排序,则该方法返回 true;否则返回 false。
无需覆盖 equals( ),大多数简单的比较器都不会这样做。
示例 1
在此示例中,我们使用 Comparator 接口根据比较条件对自定义对象 Dog 进行排序。
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; class Dog implements Comparator<Dog>, Comparable<Dog> { private String name; private int age; Dog() { } Dog(String n, int a) { name = n; age = a; } public String getDogName() { return name; } public int getDogAge() { return age; } // Overriding the compareTo method public int compareTo(Dog d) { return (this.name).compareTo(d.name); } // Overriding the compare method to sort the age public int compare(Dog d, Dog d1) { return d.age - d1.age; } @Override public String toString() { return this.name + "," + this.age; } } public class ComparatorDemo { public static void main(String args[]) { // Takes a list o Dog objects List<Dog> list = new ArrayList<>(); list.add(new Dog("Shaggy", 3)); list.add(new Dog("Lacy", 2)); list.add(new Dog("Roger", 10)); list.add(new Dog("Tommy", 4)); list.add(new Dog("Tammy", 1)); Collections.sort(list); // Sorts the array list System.out.println("Sorted by name:"); // printing the sorted list of names System.out.print(list); // Sorts the array list using comparator Collections.sort(list, new Dog()); System.out.println(" "); System.out.println("Sorted by age:"); // printing the sorted list of ages System.out.print(list); } }
输出
Sorted by name: [Lacy,2, Roger,10, Shaggy,3, Tammy,1, Tommy,4] Sorted by age: [Tammy,1, Lacy,2, Shaggy,3, Tommy,4, Roger,10]
注意 - Arrays 类的排序与 Collections 相同。
示例 2
在此示例中,我们使用 Comparator 接口对 Dog 对象进行反向排序。
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; class Dog implements Comparator<Dog>, Comparable<Dog> { private String name; private int age; Dog() { } Dog(String n, int a) { name = n; age = a; } public String getDogName() { return name; } public int getDogAge() { return age; } // Overriding the compareTo method public int compareTo(Dog d) { return (this.name).compareTo(d.name); } // Overriding the compare method to sort the age public int compare(Dog d, Dog d1) { return d.age - d1.age; } @Override public String toString() { return this.name + "," + this.age; } } public class ComparatorDemo { public static void main(String args[]) { // Takes a list o Dog objects List<Dog> list = new ArrayList<>(); list.add(new Dog("Shaggy", 3)); list.add(new Dog("Lacy", 2)); list.add(new Dog("Roger", 10)); list.add(new Dog("Tommy", 4)); list.add(new Dog("Tammy", 1)); Collections.sort(list, Collections.reverseOrder()); // Sorts the array list System.out.println("Sorted by name in reverse order:"); // printing the sorted list of names System.out.print(list); } }
输出
Sorted by name in reverse order: [Tommy,4, Tammy,1, Shaggy,3, Roger,10, Lacy,2]
在此示例中,我们使用 Comparator 接口按反向顺序对字符串值进行排序。
示例 3
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ComparatorDemo { public static void main(String args[]) { // Takes a list o Dog objects List<String> list = new ArrayList<>(); list.add("Shaggy"); list.add("Lacy"); list.add("Roger"); list.add("Tommy"); list.add("Tammy"); Collections.sort(list, Collections.reverseOrder()); // Sorts the array list System.out.println("Sorted by name in reverse order:"); // printing the sorted list of names System.out.print(list); } }
输出
Sorted by name in reverse order: [Tommy, Tammy, Shaggy, Roger, Lacy]
Java - 正则表达式
Java 提供 java.util.regex 包用于使用正则表达式进行模式匹配。Java 正则表达式与 Perl 编程语言非常相似,非常容易学习。
正则表达式是一系列特殊的字符序列,它可以帮助您使用模式中包含的专门语法来匹配或查找其他字符串或字符串集。它们可用于搜索、编辑或操作文本和数据。
java.util.regex 包主要包含以下三个类:
Pattern 类 - Pattern 对象是正则表达式的编译表示形式。Pattern 类没有提供公共构造函数。要创建模式,您必须首先调用其公共静态 compile() 方法之一,然后它将返回一个 Pattern 对象。这些方法接受正则表达式作为第一个参数。
Matcher 类 - Matcher 对象是解释模式并对输入字符串执行匹配操作的引擎。与 Pattern 类一样,Matcher 没有定义公共构造函数。您可以通过在 Pattern 对象上调用 matcher() 方法来获得 Matcher 对象。
PatternSyntaxException - PatternSyntaxException 对象是一个未经检查的异常,它指示正则表达式模式中的语法错误。
捕获组
捕获组是一种将多个字符视为单个单元的方法。它们是通过将要分组的字符放在一组括号内来创建的。例如,正则表达式 (dog) 创建一个包含字母“d”、“o”和“g”的单个组。
捕获组的编号是根据其左括号从左到右的顺序进行计数的。例如,在表达式 ((A)(B(C))) 中,共有四个这样的组:
- ((A)(B(C)))
- (A)
- (B(C))
- (C)
要找出表达式中存在多少个组,请对匹配器对象调用 groupCount 方法。groupCount 方法返回一个 **int** 值,表示匹配器模式中存在的捕获组的数量。
还有一个特殊的组,即第 0 组,它始终代表整个表达式。此组未包含在 groupCount 报告的总数中。
示例
以下示例演示如何从给定的字母数字字符串中查找数字字符串:
import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegexMatches { public static void main( String args[] ) { // String to be scanned to find the pattern. String line = "This order was placed for QT3000! OK?"; String pattern = "(.*)(\\d+)(.*)"; // Create a Pattern object Pattern r = Pattern.compile(pattern); // Now create matcher object. 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 QT300 Found value: 0
正则表达式语法
以下是 Java 中所有可用正则表达式元字符语法的列表:
子表达式 | 匹配 |
---|---|
^ | 匹配行首。 |
$ | 匹配行尾。 |
. | 匹配除换行符以外的任何单个字符。使用 **m** 选项允许它也匹配换行符。 |
[...] | 匹配括号中任何单个字符。 |
[^...] | 匹配括号中不存在的任何单个字符。 |
\A | 整个字符串的开头。 |
\z | 整个字符串的结尾。 |
\Z | 整个字符串的结尾,但不包括允许的最终行终止符。 |
re* | 匹配前一个表达式的 0 次或多次出现。 |
re+ | 匹配前一个表达式的 1 次或多次出现。 |
re? | 匹配前一个表达式的 0 次或 1 次出现。 |
re{ n} | 匹配前一个表达式的恰好 n 次出现。 |
re{ n,} | 匹配前一个表达式的 n 次或多次出现。 |
re{ n, m} | 匹配前一个表达式的至少 n 次至多 m 次出现。 |
a| b | 匹配 a 或 b。 |
(re) | 对正则表达式进行分组并记住匹配的文本。 |
(?: re) | 对正则表达式进行分组,但不记住匹配的文本。 |
(?> re) | 匹配独立模式,不回溯。 |
\w | 匹配单词字符。 |
\W | 匹配非单词字符。 |
\s | 匹配空格。等效于 [\t\n\r\f]。 |
\S | 匹配非空格。 |
\d | 匹配数字。等效于 [0-9]。 |
\D | 匹配非数字。 |
\A | 匹配字符串的开头。 |
\Z | 匹配字符串的结尾。如果存在换行符,则匹配换行符之前的部分。 |
\z | 匹配字符串的结尾。 |
\G | 匹配上次匹配结束的位置。 |
\n | 对捕获组编号为“n”的组的反向引用。 |
\b | 在括号外部匹配单词边界。在括号内部匹配退格键 (0x08)。 |
\B | 匹配非单词边界。 |
\n, \t, etc. | 匹配换行符、回车符、制表符等。 |
\Q | 转义(引用)直到 \E 的所有字符。 |
\E | 结束以 \Q 开始的引用。 |
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 方法中用作字面替换 **s**。 |
start 和 end 方法
以下示例计算单词“cat”在输入字符串中出现的次数:
示例
import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegexMatches { private static final String REGEX = "\\bcat\\b"; 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); // get a matcher object 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 1 start(): 0 end(): 3 Match number 2 start(): 4 end(): 7 Match number 3 start(): 8 end(): 11 Match number 4 start(): 19 end(): 22
您可以看到,此示例使用单词边界来确保字母“c”、“a”、“t”不仅仅是较长单词中的子字符串。它还提供了一些关于匹配在输入字符串中发生位置的有用信息。
start 方法返回在上一次匹配操作期间由给定组捕获的子序列的起始索引,end 方法返回最后一个匹配字符的索引加一。
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: foo Current INPUT is: fooooooooooooooooo lookingAt(): true matches(): 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); // get a matcher object 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 - 序列化
Java 提供了一种称为对象序列化的机制,其中对象可以表示为字节序列,其中包括对象的数据以及有关对象类型和存储在对象中的数据类型的信息。
将序列化对象写入文件后,可以从文件读取并反序列化该对象,也就是说,表示对象及其数据的信息和字节类型可以用来在内存中重新创建该对象。
最令人印象深刻的是,整个过程与 JVM 无关,这意味着对象可以在一个平台上序列化,并在完全不同的平台上反序列化。
**ObjectInputStream** 和 **ObjectOutputStream** 类是包含序列化和反序列化对象方法的高级流。
ObjectOutputStream 类包含许多用于写入各种数据类型的方法,但有一种方法尤其突出:
public final void writeObject(Object x) throws IOException
上述方法序列化一个对象并将其发送到输出流。类似地,ObjectInputStream 类包含以下用于反序列化对象的方法:
public final Object readObject() throws IOException, ClassNotFoundException
此方法从流中检索下一个对象并将其反序列化。返回值为 Object,因此您需要将其强制转换为其适当的数据类型。
为了演示 Java 中的序列化工作方式,我将使用我们在本书前面讨论过的 Employee 类。假设我们有以下实现了 Serializable 接口的 Employee 类:
示例
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 接口。
该类中的所有字段都必须是可序列化的。如果某个字段不可序列化,则必须将其标记为 **transient**。
如果您想知道 Java 标准类是否可序列化,请检查该类的文档。测试很简单:如果该类实现了 java.io.Serializable,则它是可序列化的;否则,它不可序列化。
序列化对象
ObjectOutputStream 类用于序列化对象。以下 SerializeDemo 程序实例化一个 Employee 对象并将其序列化到文件。
程序执行完毕后,将创建一个名为 employee.ser 的文件。程序不会生成任何输出,但请研究代码并尝试确定程序在做什么。
**注意** - 将对象序列化到文件时,Java 中的标准约定是为文件提供 **.ser** 扩展名。
示例
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; 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("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(); } } } class Employee implements java.io.Serializable { private static final long serialVersionUID = 1L; 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); } }
输出
Serialized data is saved in employee.ser
反序列化对象
以下 DeserializeDemo 程序反序列化在前面程序中创建的 Employee 对象。研究程序并尝试确定其输出:
示例
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class DeserializeDemo { public static void main(String [] args) { Employee e = null; try { FileInputStream fileIn = new FileInputStream("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); } } class Employee implements java.io.Serializable { private static final long serialVersionUID = 1L; 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); } }
输出
Deserialized Employee... Name: Reyan Ali Address:Phokka Kuan, Ambehta Peer SSN: 0 Number:101
以下是需要注意的重要几点:
try/catch 块尝试捕获 ClassNotFoundException,该异常由 readObject() 方法声明。对于 JVM 能够反序列化对象,它必须能够找到该类的字节码。如果 JVM 在对象的序列化过程中找不到类,则它会抛出 ClassNotFoundException。
请注意,readObject() 的返回值强制转换为 Employee 引用。
对象序列化时 SSN 字段的值为 11122333,但由于该字段是瞬态的,因此该值未发送到输出流。反序列化的 Employee 对象的 SSN 字段为 0。
Java - 发送邮件
使用您的 Java 应用程序发送电子邮件非常简单,但首先您应该在您的机器上安装**JavaMail API** 和 **Java Activation Framework (JAF)**。
您可以从 Java 的标准网站下载最新版本的JavaMail (1.2 版)。
您可以从 Java 的标准网站下载最新版本的JAF (1.1.1 版)。
下载并解压缩这些文件,在新建的顶级目录中,您将找到这两个应用程序的多个 jar 文件。您需要将 **mail.jar** 和 **activation.jar** 文件添加到您的 CLASSPATH 中。
发送简单的电子邮件
这是一个从您的机器发送简单电子邮件的示例。假设您的 **localhost** 已连接到互联网,并且能够发送电子邮件。
示例
// File Name SendEmail.java import java.util.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; public class SendEmail { public static void main(String [] args) { // Recipient's email ID needs to be mentioned. String to = "[email protected]"; // Sender's email ID needs to be mentioned String from = "[email protected]"; // Assuming you are sending email from localhost String host = "localhost"; // Get system properties Properties properties = System.getProperties(); // Setup mail server properties.setProperty("mail.smtp.host", host); // Get the default Session object. Session session = Session.getDefaultInstance(properties); try { // Create a default MimeMessage object. MimeMessage message = new MimeMessage(session); // Set From: header field of the header. message.setFrom(new InternetAddress(from)); // Set To: header field of the header. message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); // Set Subject: header field message.setSubject("This is the Subject Line!"); // Now set the actual message message.setText("This is actual message"); // Send message Transport.send(message); System.out.println("Sent message successfully...."); } catch (MessagingException mex) { mex.printStackTrace(); } } }
输出
编译并运行此程序以发送简单的电子邮件:
$ java SendEmail Sent message successfully....
如果您想向多个收件人发送电子邮件,则可以使用以下方法指定多个电子邮件 ID:
void addRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException
以下是参数的描述:
**type** - 这将设置为 TO、CC 或 BCC。其中 CC 代表抄送,BCC 代表密送。例如:Message.RecipientType.TO
**addresses** - 这是一个电子邮件 ID 数组。在指定电子邮件 ID 时,您需要使用 InternetAddress() 方法。
发送 HTML 电子邮件
这是一个从您的机器发送 HTML 电子邮件的示例。这里假设您的 **localhost** 已连接到互联网,并且能够发送电子邮件。
此示例与前一个示例非常相似,不同之处在于这里我们使用 setContent() 方法来设置内容,其第二个参数是“text/html”,用于指定邮件中包含 HTML 内容。
使用此示例,您可以发送任意大小的 HTML 内容。
示例
// File Name SendHTMLEmail.java import java.util.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; public class SendHTMLEmail { public static void main(String [] args) { // Recipient's email ID needs to be mentioned. String to = "[email protected]"; // Sender's email ID needs to be mentioned String from = "[email protected]"; // Assuming you are sending email from localhost String host = "localhost"; // Get system properties Properties properties = System.getProperties(); // Setup mail server properties.setProperty("mail.smtp.host", host); // Get the default Session object. Session session = Session.getDefaultInstance(properties); try { // Create a default MimeMessage object. MimeMessage message = new MimeMessage(session); // Set From: header field of the header. message.setFrom(new InternetAddress(from)); // Set To: header field of the header. message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); // Set Subject: header field message.setSubject("This is the Subject Line!"); // Send the actual HTML message, as big as you like message.setContent("<h1>This is actual message</h1>", "text/html"); // Send message Transport.send(message); System.out.println("Sent message successfully...."); } catch (MessagingException mex) { mex.printStackTrace(); } } }
编译并运行此程序以发送 HTML 电子邮件:
输出
$ java SendHTMLEmail Sent message successfully....
在电子邮件中发送附件
这是一个从您的机器发送包含附件的电子邮件的示例。这里假设您的 **localhost** 已连接到互联网,并且能够发送电子邮件。
示例
// File Name SendFileEmail.java import java.util.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; public class SendFileEmail { public static void main(String [] args) { // Recipient's email ID needs to be mentioned. String to = "[email protected]"; // Sender's email ID needs to be mentioned String from = "[email protected]"; // Assuming you are sending email from localhost String host = "localhost"; // Get system properties Properties properties = System.getProperties(); // Setup mail server properties.setProperty("mail.smtp.host", host); // Get the default Session object. Session session = Session.getDefaultInstance(properties); try { // Create a default MimeMessage object. MimeMessage message = new MimeMessage(session); // Set From: header field of the header. message.setFrom(new InternetAddress(from)); // Set To: header field of the header. message.addRecipient(Message.RecipientType.TO,new InternetAddress(to)); // Set Subject: header field message.setSubject("This is the Subject Line!"); // Create the message part BodyPart messageBodyPart = new MimeBodyPart(); // Fill the message messageBodyPart.setText("This is message body"); // Create a multipar message Multipart multipart = new MimeMultipart(); // Set text message part multipart.addBodyPart(messageBodyPart); // Part two is attachment messageBodyPart = new MimeBodyPart(); String filename = "file.txt"; DataSource source = new FileDataSource(filename); messageBodyPart.setDataHandler(new DataHandler(source)); messageBodyPart.setFileName(filename); multipart.addBodyPart(messageBodyPart); // Send the complete message parts message.setContent(multipart ); // Send message Transport.send(message); System.out.println("Sent message successfully...."); } catch (MessagingException mex) { mex.printStackTrace(); } } }
编译并运行此程序以发送 HTML 电子邮件:
输出
$ java SendFileEmail Sent message successfully....
用户身份验证部分
如果需要向电子邮件服务器提供用户 ID 和密码进行身份验证,则可以按如下方式设置这些属性:
props.setProperty("mail.user", "myuser"); props.setProperty("mail.password", "mypwd");
其余的电子邮件发送机制将保持如上所述。
Java - Applet 基础
**Applet** 是一个在 Web 浏览器中运行的 Java 程序。Applet 可以是一个功能齐全的 Java 应用程序,因为它可以使用整个 Java API。
Applet 和独立 Java 应用程序之间存在一些重要区别,包括以下几点:
Applet 是扩展 java.applet.Applet 类的 Java 类。
不会在 Applet 上调用 main() 方法,并且 Applet 类不会定义 main()。
Applet 设计为嵌入在 HTML 页面中。
当用户查看包含 Applet 的 HTML 页面时,Applet 的代码将下载到用户的机器上。
查看 Applet 需要 JVM。JVM 可以是 Web 浏览器的插件或单独的运行时环境。
用户机器上的 JVM 创建 Applet 类的实例,并在 Applet 的生命周期中调用各种方法。
Applet 具有由 Web 浏览器强制执行的严格安全规则。Applet 的安全性通常被称为沙箱安全性,将 Applet 比作在沙箱中玩耍的孩子,必须遵守各种规则。
Applet 需要 的其他类可以打包在一个 Java 归档 (JAR) 文件中下载。
Applet 的生命周期
Applet 类中的四种方法为您构建任何严肃的 Applet 提供了框架:
**init** - 此方法用于 Applet 所需的任何初始化。在处理 applet 标记内的 param 标记后调用它。
**start** - 在浏览器调用 init 方法后,此方法会自动调用。当用户在访问其他页面后返回包含 Applet 的页面时,也会调用它。
**stop** - 当用户离开包含 Applet 的页面时,此方法会自动调用。因此,它可以在同一个 Applet 中重复调用。
**destroy** - 此方法仅在浏览器正常关闭时调用。因为 Applet 旨在存在于 HTML 页面上,所以用户离开包含 Applet 的页面后,您通常不应留下资源。
**paint** - 在 start() 方法之后立即调用,以及任何时候 Applet 需要在浏览器中重新绘制自身时。paint() 方法实际上是从 java.awt 继承而来的。
一个“Hello, World”Applet
下面是一个名为 HelloWorldApplet.java 的简单 Applet:
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 类引用的 Applet 和 Graphics 类。
Applet 类
每个 Applet 都是 _java.applet.Applet 类_ 的扩展。基本的 Applet 类提供派生 Applet 类可以调用的方法,以从浏览器上下文获取信息和服务。
这些包括执行以下操作的方法:
- 获取 Applet 参数
- 获取包含 Applet 的 HTML 文件的网络位置
- 获取 Applet 类目录的网络位置
- 在浏览器中打印状态消息
- 获取图像
- 获取音频剪辑
- 播放音频剪辑
- 调整 Applet 大小
此外,Applet 类提供了一个接口,查看器或浏览器可以通过该接口获取有关 Applet 的信息并控制 Applet 的执行。查看器可以:
- 请求有关 Applet 的作者、版本和版权的信息
- 请求 Applet 识别的参数的描述
- 初始化 Applet
- 销毁 Applet
- 启动 Applet 的执行
- 停止 Applet 的执行
Applet 类为每种方法提供了默认实现。可以根据需要覆盖这些实现。
“Hello, World”Applet 现状已完整。唯一被覆盖的方法是 paint 方法。
调用 Applet
可以通过在 HTML 文件中嵌入指令并在 Applet 查看器或支持 Java 的浏览器中查看该文件来调用 Applet。
<applet>
标记是将 Applet 嵌入 HTML 文件的基础。以下是一个调用“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 Tag 以了解有关从 HTML 调用 Applet 的更多信息。
<applet>
标记的 code 属性是必需的。它指定要运行的 Applet 类。宽度和高度也是必需的,用于指定 Applet 运行的面板的初始大小。Applet 指令必须用 </applet>
标记关闭。
如果 Applet 需要参数,则可以通过在 <applet>
和 </applet>
之间添加 <param>
标记来传递参数的值。浏览器忽略 applet 标记之间的文本和其他标记。
不支持 Java 的浏览器不会处理 <applet>
和 </applet>
。因此,标记之间出现的任何与 Applet 无关的内容都可以在不支持 Java 的浏览器中看到。
查看器或浏览器会在文档的位置查找已编译的 Java 代码。要指定其他位置,请使用 <applet>
标记的 codebase 属性,如下所示:
<applet codebase = "https://amrood.com/applets" code = "HelloWorldApplet.class" width = "320" height = "120">
如果 Applet 位于默认包以外的包中,则必须使用句点字符 (.) 在 code 属性中指定保存包,以分隔包/类组件。例如:
<applet = "mypackage.subpackage.TestApplet.class" width = "320" height = "120">
获取 Applet 参数
以下示例演示如何使 Applet 对文档中指定的安装参数做出响应。此 Applet 显示黑色和第二种颜色的棋盘图案。
第二种颜色和每个正方形的大小可以作为文档中 Applet 的参数指定。
CheckerApplet 在 init() 方法中获取其参数。它也可以在 paint() 方法中获取其参数。但是,在 Applet 启动时一次获取值并保存设置,而不是在每次刷新时获取值和保存设置,这很方便且高效。
Applet 查看器或浏览器会调用其运行的每个 Applet 的 init() 方法。查看器会调用 init() 一次,立即加载 Applet 后。(Applet.init() 实现不执行任何操作。)覆盖默认实现以插入自定义初始化代码。
Applet.getParameter() 方法获取给定参数名称的参数(参数的值始终是字符串)。如果该值是数字或其他非字符数据,则必须分析该字符串。
以下是 CheckerApplet.java 的框架:
import java.applet.*; import java.awt.*; public class CheckerApplet extends Applet { int squareSize = 50; // initialized to default size 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) { // Let default value remain } }
Applet 调用 parseSquareSize() 来分析 squareSize 参数。parseSquareSize() 调用库方法 Integer.parseInt(),该方法分析字符串并返回整数。每当其参数无效时,Integer.parseInt() 都会引发异常。
因此,parseSquareSize() 会捕获异常,而不是允许 Applet 因无效输入而失败。
Applet 调用 parseColor() 将颜色参数解析为 Color 值。parseColor() 执行一系列字符串比较,以将参数值与预定义颜色的名称匹配。您需要实现这些方法才能使此 Applet 工作。
指定 Applet 参数
以下是一个 HTML 文件的示例,其中嵌入了 CheckerApplet。HTML 文件通过 <param>
标记指定 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 程序启动器启动的应用程序)转换为可以嵌入网页的 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>
小程序最初会显示“正在初始化小程序。正在启动小程序”。然后,单击矩形内部后,还会显示“鼠标单击”。
显示图像
小程序可以显示 GIF、JPEG、BMP 等格式的图像。要在小程序中显示图像,可以使用 `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>
播放音频
小程序可以播放由 `java.applet` 包中的 `AudioClip` 接口表示的音频文件。`AudioClip` 接口具有三个方法,包括:
`public void play()` - 从头开始播放音频剪辑一次。
`public void loop()` - 使音频剪辑连续重播。
`public void stop()` - 停止播放音频剪辑。
要获得 `AudioClip` 对象,必须调用 `Applet` 类的 `getAudioClip()` 方法。`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> </html>
您可以使用电脑上的 test.wav 文件测试上述示例。
Java - 文档注释
Java 语言支持三种类型的注释:
序号 | 注释和描述 |
---|---|
1 | /* text */ 编译器会忽略从 /* 到 */ 之间的所有内容。 |
2 | //text 编译器会忽略从 // 到行尾的所有内容。 |
3 | /** documentation */ 这是一个文档注释,通常称为 **doc 注释**。**JDK javadoc** 工具在准备自动生成的文档时会使用 *doc 注释*。 |
本章将详细解释 Javadoc。我们将了解如何使用 Javadoc 为 Java 代码生成有用的文档。
什么是 Javadoc?
Javadoc 是 JDK 附带的一个工具,用于根据 Java 源代码生成 HTML 格式的 Java 代码文档,这需要以预定义格式编写文档。
下面是一个简单的示例,其中 /*….*/ 内部的行是 Java 多行注释。类似地,//之前的行是 Java 单行注释。
示例
/** * The HelloWorld program implements an application that * simply displays "Hello World!" to the standard output. * * @author Zara Ali * @version 1.0 * @since 2014-03-31 */ public class HelloWorld { public static void main(String[] args) { // Prints Hello, World! on standard output. System.out.println("Hello World!"); } }
输出
Hello World!
您可以在描述部分包含所需的 HTML 标签。例如,以下示例使用 <h1>....</h1> 用于标题,<p> 用于创建段落换行:
示例
/** * <h1>Hello, World!</h1> * The HelloWorld program implements an application that * simply displays "Hello World!" to the standard output. * <p> * Giving proper comments in your program makes it more * user friendly and it is assumed as a high quality code. * * * @author Zara Ali * @version 1.0 * @since 2014-03-31 */ public class HelloWorld { public static void main(String[] args) { // Prints Hello, World! on standard output. System.out.println("Hello World!"); } }
输出
Hello World!
Javadoc 标签
Javadoc 工具识别以下标签:
标签 | 描述 | 语法 |
---|---|---|
@author | 添加类的作者。 | @author name-text |
{@code} | 以代码字体显示文本,而不将其解释为 HTML 标记或嵌套的 javadoc 标签。 | {@code text} |
{@docRoot} | 表示从任何生成的页面到生成的文档根目录的相对路径。 | {@docRoot} |
@deprecated | 添加一条注释,指示不再应使用此 API。 | @deprecated deprecatedtext |
@exception | 在生成的文档中添加一个 **Throws** 小标题,其中包含类名和描述文本。 | @exception class-name description |
{@inheritDoc} | 从最近的可继承类或可实现接口继承注释。 | 从直接父类继承注释。 |
{@link} | 插入一个内联链接,其可见文本标签指向指定包、类或引用的类的成员名称的文档。 | {@link package.class#member label} |
{@linkplain} | 与 {@link} 相同,只是链接的标签以纯文本而不是代码字体显示。 | {@linkplain package.class#member label} |
@param | 在“参数”部分添加一个参数,其中包含指定的 parameter-name 后跟指定的描述。 | @param parameter-name description |
@return | 添加一个包含描述文本的“返回”部分。 | @return description |
@see | 添加一个“另请参见”标题,其中包含指向引用的链接或文本条目。 | @see reference |
@serial | 用于默认可序列化字段的 doc 注释中。 | @serial field-description | include | exclude |
@serialData | 记录由 writeObject( ) 或 writeExternal( ) 方法写入的数据。 | @serialData data-description |
@serialField | 记录 ObjectStreamField 组件。 | @serialField field-name field-type field-description |
@since | 在生成的文档中添加一个包含指定 since-text 的“自”标题。 | @since release |
@throws | @throws 和 @exception 标签是同义词。 | @throws class-name description |
{@value} | 当 {@value} 用于静态字段的 doc 注释中时,它会显示该常量的值。 | {@value package.class#field} |
@version | 当使用 -version 选项时,在生成的文档中添加一个包含指定 version-text 的“版本”小标题。 | @version version-text |
示例
以下程序使用了一些可用于文档注释的重要标签。您可以根据需要使用其他标签。
关于 AddNum 类的文档将生成在 HTML 文件 AddNum.html 中,但同时也会创建一个名为 index.html 的主文件。
import java.io.*; /** * <h1>Add Two Numbers!</h1> * The AddNum program implements an application that * simply adds two given integer numbers and Prints * the output on the screen. * <p> * <b>Note:</b> Giving proper comments in your program makes it more * user friendly and it is assumed as a high quality code. * * @author Zara Ali * @version 1.0 * @since 2014-03-31 */ public class AddNum { /** * This method is used to add two integers. This is * a the simplest form of a class method, just to * show the usage of various javadoc Tags. * @param numA This is the first paramter to addNum method * @param numB This is the second parameter to addNum method * @return int This returns sum of numA and numB. */ public int addNum(int numA, int numB) { return numA + numB; } /** * This is the main method which makes use of addNum method. * @param args Unused. * @return Nothing. * @exception IOException On input error. * @see IOException */ public static void main(String args[]) throws IOException { AddNum obj = new AddNum(); int sum = obj.addNum(10, 20); System.out.println("Sum of 10 and 20 is :" + sum); } }
让我们编译并运行上述程序,这将产生以下结果:
Sum of 10 and 20 is :30
现在,使用 javadoc 实用程序处理上述 AddNum.java 文件,如下所示:
$ javadoc AddNum.java Loading source file AddNum.java... Constructing Javadoc information... Standard Doclet version 1.7.0_51 Building tree for all the packages and classes... Generating /AddNum.html... AddNum.java:36: 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... 1 warning $
您可以在此处查看所有生成的文档:AddNum。如果您使用的是 JDK 1.7,则 javadoc 不会生成很好的 **stylesheet.css**,因此我们建议您从以下地址下载并使用标准样式表:
https://docs.oracle.com/javase/7/docs/api/stylesheet.css