JavaTutorialNetwork-中文系列教程-一-
JavaTutorialNetwork 中文系列教程(一)
原文:JavaTutorialNetwork
协议:CC BY-NC-SA 4.0
Java 基础
Java 概述
原文: https://javatutorial.net/java-overview
这是面向初学者的一系列教程中的第一个 Java 教程。 如果您想以简单的方式学习 Java,那么您来对地方了。 在本教程中,我不会详细介绍。 我的目标是教您如何立即编写 Java 代码! 如果您需要详细信息,请访问我们网站 javatutorial.net 上的高级部分。
这是 Java 的官方徽标
Java 的好处
为什么 Java 是当今编程语言的最佳选择? 当前 Java 是世界上排名第一的开发平台! 它无处不在 - 在台式机,移动设备,企业服务器上……甚至 DVD 播放器都运行 Java。 如果您仍然不相信,那么这里有一些有趣的统计信息(*来自 Oracle 时间轴):
- 全球有超过 900 万开发人员使用 Java
- 每年 10 亿次 Java 下载
- 30 亿台设备运行 Java
- 97% 的企业台式机运行 Java
为什么要使用 Java?
- Java 简单易学 – 它的设计语法与 C/C++类似。 每个人都可以学习。 您不需要大学学位或昂贵的课程,只需继续阅读本教程,您便会很快建立起来。
- Java 是安全的 – Java Applet 在这里是一个例外,我们将在另一篇文章中讨论。
- Java 是可移植的 - 编写一次即可在任何地方运行。 您的代码将在 Windows,Linux 和 Mac 上运行,而无需进行任何更改。
- Java 是可重用的 – 您将在线查找大量库。 您无需成为每个编程主题的专家,只需找到合适的库并使用它即可。
Java 与其他编程语言相比如何?
有许多编程语言,例如 C/C++,PHP,PEARL 等。它们通常专门致力于最好地完成特定任务。 是什么使 Java 与众不同? Java 是面向对象的,基于类的编程语言,并且是通用的! 您可以编写桌面应用程序,前端和后端服务器逻辑,连接和操作数据库,通过网络发送和接收对象或为手机或 Android 设备编写移动应用程序。 您可以使用 Java 进行任何编程!
请不要将 Java 与 JavaScript 混淆! JavaScript 是 Web 浏览器解释的脚本语言,而不是独立的编程语言。 Java 和 JavaScript 除了名称外没有其他共同之处。
Java 的历史
Sun 的员工 James Gosling,Mike Sheridan 和 Patrick Naughton 于 1991 年 6 月开始了创建 Java 的项目。 创作者首先将项目命名为 Oak。 一段时间后,名称更改为绿色。 最终,开发人员从 Java 咖啡中为编程语言命名为 Java,据说该语言的创造者大量使用了 Java 咖啡。 Sun Microsystems 于 1996 年公开发布了 Java 1.0 的第一个版本。 它很快吸引了一大批开发人员。 Sun Microsystems 于 2009 年被 Oracle Corporation 收购。现在 Oracle 拥有 Java 的权利和功能开发。
James Gosling – Java 之父
Java 版本
如果您不熟悉 Java,请不用担心! 我们将在下一个教程中说明适合您的版本以及在何处下载。
- JDK 1.0(1996 年 1 月 21 日)
- JDK 1.1(1997 年 2 月 19 日)
- J2SE 1.2(1998 年 12 月 8 日)
- J2SE 1.3(2000 年 5 月 8 日)
- J2SE 1.4(2002 年 2 月 6 日)
- J2SE 5.0(2004 年 9 月 30 日)
- Java SE 6(2006 年 12 月 11 日)
- Java SE 7(2011 年 7 月 28 日)
- Java SE 8(2014 年 3 月 18 日)
- Java SE 9(2017 年 9 月 21 日)
- Java SE 10(2018 年 3 月)
- Java SE 11(2018 年 9 月)
- Java SE 12(2019 年 3 月)
在我们的下一个教程简单的 Java 示例中,我们将说明如何下载和安装 Java。 我们还将向您展示如何编写,编译和运行一个简单的 Java 程序。
在 Ubuntu 上安装 Java 8 JDK
原文: https://javatutorial.net/install-java-8-jdk-on-ubuntu
在本教程中,您将学习如何通过 PPA 在 Ubuntu 或 LinuxMint 上安装最新的 Java 8。
如果您使用的是 Windows,请改用 Windows Java 安装指南。
Oracle Java PPA 安装可在 Ubuntu 18.04 LTS,16.04 LTS,15.10、14.04 LTS,12.04 LTS 和 LinuxMint 发行版上运行。
首先添加 PPA 存储库
$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer
完成安装后,您可以通过键入以下内容进行验证
$ java -version
您应该会看到类似的输出(取决于您的 Java 版本)
验证 Java 安装
PPA 存储库还提供了一个实用程序来设置环境变量:
$ sudo apt-get install oracle-java8-set-default
Java Eclipse 教程
原文: https://javatutorial.net/java-eclipse-tutorial
在本教程中,我将向您展示如何使用 Eclipse IDE 简化 Java 编程。
什么是 Eclipse IDE
IDE 代表集成开发环境。 换句话说,Eclipse 是功能强大且高级的文本编辑器,具有许多插件和工具。 使用 Eclipse,您可以更快地编写 Java 程序并按一下按钮即可执行它们。 但是 Eclipse 的功能更多。 插件架构使您能够不断添加所需的开发工具。
您始终可以以“老式方式”编译和运行程序。 有关更多详细信息,请阅读我们以前的教程简单 Java 示例。 但是,如果您想像专业人士一样编码,则必须学习如何使用行业标准 IDE(例如 Eclipse)。
安装 Eclipse
安装 Eclipse 既简单又直接。 如果您的计算机上尚未安装 Java JDK,请立即安装。 您可以在本教程简单 Java 示例中找到有关安装 Java 的更多详细信息。
现在转到 http://eclipse.org/downloads/ 。 找到“面向 Java 开发人员的 Eclipse IDE”。 您必须下载与您的 Java 版本相对应的 Eclipse 版本。 例如,如果您已安装 Java x64 Bit,则采用 Eclipse x64 Bit 和 x32 Bit 版本。
您将下载 Windows 的.zip
文件或 MacOS 或 Linux 操作系统的tar.gz
文件。
下载完成后,只需将文件解压缩到硬盘上即可。 打开解压缩的文件夹并启动可执行文件。 对于 Windows,它是eclipse.exe
现在,您必须为项目选择一个工作区。 工作区是硬盘驱动器上 Eclipse 存储 Java 项目的文件夹。 我将使用D:\Development\Workspace\javatutorial
作为我所有教程项目和示例的主文件夹。
就是这样了! 您已经成功安装了 Eclipse。 您将看到一个欢迎屏幕,其中包含指南和帮助材料的链接。 您可以芦苇其中一些,或暂时关闭此屏幕。
使用 Eclipse 创建 Java 项目
Java 代码是在 Eclipse 的项目中组织的。 因此,让我们从第一个项目开始。 单击“文件 -> 新建 -> Java 项目”
在下一个屏幕中,为您的项目命名。 在本教程中,我将使用FirstEclipseProject
。
点击“完成”按钮。 Eclipse 将为您创建项目的结构。 现在,您将在屏幕左侧的“Package Explorer”中看到您的新项目。
展开项目。
右键单击src
文件夹,然后转到“新建 -> 类”
在下一个屏幕中,输入新 Java 类的名称。 我将使用MyClass
作为本教程的名称。 我还将选中public static void main(String[] args)
框,以使 Eclipse 为我创建一个main
方法。
Eclipse 使用main
方法为我们创建了一个新类。 现在更换
// TODO Auto-generated method stub
与
System.out.println("Programming with Eclipse is easy!");
您的完整代码应如下所示:
public class MyClass {
public static void main(String[] args) {
System.out.println("Programming with Eclipse is easy!");
}
}
保存您的工作。 右键单击“程序包资源管理器”中的MyClass.java
,然后单击“-> 运行方式 -> Java 应用程序”
瞧……在控制台选项卡中,您应该看到程序的结果
好的! 我告诉过你它将变得轻松有趣
在我们的下一个教程中,我将向您展示如何在 Eclipse 中使用快捷键和组合键。
Eclipse 快捷方式
原文: https://javatutorial.net/eclipse-shortcuts
此列表包含 Windows,Linux 和 Mac OS 最常见的 Eclipse 快捷方式。 使用快捷方式将提高您的编码效率。 您可以将此列表用作参考,并在以后需要时再次使用。
Eclipse 文件管理快捷方式
Windows / Linux | Mac | 说明 |
---|---|---|
Ctrl + N |
⌘ + N |
启动向导以创建新的类,接口,java 项目等。 |
Alt + F |
在 Eclipse 中打开文件 | |
Ctrl + Shift + R |
⌘ + Shift + R |
开放资源 |
Alt + Enter |
Alt + Enter |
显示文件属性 |
Ctrl + S |
⌘ + S |
保存当前文件 |
Ctrl + Shift + S |
⌘ + Shift + S |
保存所有文件 |
Ctrl + W |
⌘ + W |
关闭当前文件 |
Ctrl + Shift + W |
⌘ + Shift + W |
关闭所有文件 |
在 Eclipse 编辑器快捷方式中导航
Windows / Linux | Mac | 说明 |
---|---|---|
Ctrl + Shift + ↑ |
跳至上一个方法 | |
Ctrl + Shift + ↓ |
跳到下一个方法 | |
Ctrl + L |
⌘ + L |
转到行号 |
Ctrl + Q |
⌘ + Q |
转到最后编辑的位置 |
Eclipse 格式化和注释快捷方式
Windows / Linux | Mac | 说明 |
---|---|---|
Ctrl + Shift + F |
⌘ + Shift + F |
选择区域自动排版(调整缩进,换行等)。 如果没有选择,所有代码将自动排版 |
Ctrl + / |
⌘ + / |
切换当前行或节的注释// |
Ctrl + Shift + / |
Shift + Shift + / |
在选择区域周围添加块注释/* */ |
Eclipse 编辑源代码快捷方式
Windows / Linux | Mac | 说明 |
---|---|---|
Ctrl + Space |
⌘ + Space |
打开内容辅助(允许您选择方法或字段名称) |
Ctrl + 1 |
⌘ + 1 |
打开“快速修复”,并提供有关解决问题的建议 |
Alt + / |
Alt + / |
为您完成一个词。 您必须至少输入一个字母才能起作用。 您可以在同一单词上多次使用此快捷方式,直到找到所需的单词 |
Eclipse 代码信息快捷方式
Windows / Linux | Mac | 说明 |
---|---|---|
Ctrl + T |
⌘ + T |
显示所选项目的类型层次结构 |
Ctrl + Shift + T |
⌘ + Shift + T |
搜索可用的类型 |
Ctrl + Shift + G |
⌘ + Shift + G |
显示所选方法或构造函数的所有调用 |
简单的 Java 示例
原文: https://javatutorial.net/simple-java-example
本教程将说明如何在计算机上下载和安装 Java。 我们还将向您展示编写有效的 Java 程序有多么简单。
下载并安装 Java
Java 可以从 Oracle 网站免费下载。 您需要 Java JDK 才能编写自己的 Java 程序。 JDK 代表 Java Development Kit,它包含完成小型或大型项目所需的一切。
如果您需要在 Ubuntu Linux 或 LinuxMint 上安装 Java,请查看 Java Ubuntu 安装指南
在本教程中,我将向您展示如何安装 Java 版本 8。首先转到 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
向下滚动到 Java SE Development Kit。
现在,单击操作系统旁边的下载链接以下载安装文件。 请注意,您必须接受许可协议才能执行此操作。 下载后,开始安装并进入安装向导。 注意安装 Java 的路径。
安装向导将在您的计算机上安装 JDK 和 JRE。 JDK 是您需要编写自己的 Java 程序的程序,而 JRE(Java 运行时环境)是在计算机上执行 Java 程序的解释程序。 您需要这两个示例都可以工作。
安装完成后,在 PC(或 Mac 和 Linux 等基于 UNIX 的系统上的终端)上打开命令提示符。 输入
java -version
如果该命令返回内部版本号(请参见下面的屏幕截图),则一切正常。
Windows 上的Java -version
命令的结果
您的第一个 Java 程序
现在您可以编写第一个 Java 程序了。 打开您喜欢的文本编辑器程序。 请不要使用 Microsoft Word 之类的程序进行富文本编辑。 使用记事本(或 NotePad++,UltraEdit,gEdit 等)。
将以下代码写入新文件并将其另存为MyFirstJavaProgram.java
。 注意文件扩展名必须是.java
public class MyFirstJavaProgram {
public static void main(String[] args) {
System.out.println("Hello, this is my first java program");
}
}
我们将使用javac
(Java 编译器)来构建此程序。 现在再次打开命令提示符并输入:
C:\>d:\Development\Java\jdk8\bin\javac.exe d:\Development\javatutorial.net\MyFir
stJavaProgram.java
第一个命令是javac.exe
(java 编译器)的路径,在我的情况下为D:\Development\Java\jdk8\bin\javac.exe
,第二个命令是您的.java
文件的路径。
您必须知道的一件事 - 文件名和 Java 类名必须是相同的。 在本教程中,它是MyFirstJavaProgram
现在您可以执行第一个程序了。 仍然在命令提示符下浏览到您的工作文件夹(在我的情况下是D:\Development\javatutorial.net
),然后键入:
java MyFirstJavaProgram
您应该看到以下结果
执行我们的第一个 Java 程序的结果
解释代码
现在让我们回到示例的源代码并仔细查看。
public class MyFirstJavaProgram
在 Java 中,您需要一个公共类来使程序可执行。 Java 中的所有内容都基于类和接口。 您将在我们的下一个教程中了解有关此的更多信息。
public static void main(String[] args) {
这是我们程序的主要方法。 执行程序时,Java 会自动调用它。 在您编写的每个 Java 程序中,它都必须具有上面显示的结构。
System.out.println("Hello, this is my first java program");
System.out.println
在控制台中显示文本,并在其后添加新行。
在我们的下一个教程 Java Eclipse 教程中,我将向您展示如何使用 Eclipse IDE 简化 Java 程序的代码编写,构建和执行。
Java 基本类型
原文: https://javatutorial.net/java-primitive-types
在本教程中,您将学习 Java 基本类型以及如何选择正确的基本类型。
变量分配计算机内存中的空间。 选择正确的原始类型以节省内存是很好的选择。 例如:要将大量从 -128 到 127 的数字存储到数组中,请将变量声明为byte
,而不是int
或long
int intVariable = 24; // uses 32 bits of memory
long longVariable = 24L; // uses 64 bits of memory
byte byteVariable = 24; // uses 8 bits of memory
在上面的示例中,我们将数字 24 存储在具有不同原始类型的 3 个变量中。 注意这三种类型的内存分配量。 byte
比int
小 4 倍,比long
小 8 倍
如果您来自其他编程语言(例如 C/C++),请注意 Java 中没有未签名的数据类型。 Java 中的所有原始类型均已签名。
字节
- 使用
byte
可以存储很多小数字。 - 使用 8 位内存
- 值范围:最小值 -128 和最大值 127
- 默认值为 0
短整型
- 使用
short
存储很多小数字,对于byte
来说太大了 - 使用 16 位内存
- 值范围:最小值 -32,768 和最大值 32,767
- 默认值为 0
整型
- 用于存储或计算整数。 这是最常用的类型
- 使用 32 位内存
- 值范围:最小值 -2,147,483,648 和最大值 2,147,483,647
- 默认值为 0
长整型
- 使用
long
计算的整数对于int
太大 - 使用 64 位内存
- 值范围:最小值 -9,223,372,036,854,775,808 和最大值 9,223,372,036,854,775,807
- 默认值为 0L
浮点
- 使用
float
存储或计算有理数(带小数点或科学计数法的数字)。float
的精度为 6 个十进制数字 - 使用 32 位内存
- 默认值为 0.0f
双精度浮点
- 与
float
相似,但范围更大,精度为 14 个十进制数字 - 使用 64 位内存
- 默认值为 0.0d
布尔值
- 只有两个可能的值:
true
和false
。 使用boolean
标记正确/错误条件 - 使用 1 位内存
- 默认值为
false
字符
- 将
char
用于 Unicode 字符代码,包括'\\'
,'\n'
,'\r'
,'\t'
,'\''
和'\uxxxx'
- 使用 16 位内存
- 值范围:最小值 0 和最大值 65535
- 默认值为 0
在我们的下一个教程中,您将学习 Java 对象和类以及如何使用它们。
Java 循环
原文: https://javatutorial.net/java-loops
本教程介绍了 Java 循环的创建方式及其工作方式。
很多时候,您将需要多次在程序中执行代码片段,直到满足某些条件为止。 这就是循环的目的。
Java for
循环
借助for
循环,您可以多次执行一系列代码行。 Java 中的for
循环与其他编程语言(例如 C/C++)完全一样。for
循环功能非常强大,但学习起来很简单。 在要迭代数据集合的所有情况下,它也主要用于搜索或排序算法。 这是for
循环的一个简单示例:
public class ForLoopExample {
public static void main(String[] args) {
for(int i=0; i<5; i++) {
System.out.println("Iteration # " + i);
}
}
}
该示例的输出如下:
Iteration # 0
Iteration # 1
Iteration # 2
Iteration # 3
Iteration # 4
for
语句的一般形式可以表示如下:
for (initialization; termination; increment) {
statement(s)
}
初始化部分是您声明循环变量的地方。 请注意,此处初始化的变量仅在循环中可见,并在循环结束后销毁。 在我们的示例中,我们初始化了一个名为i
的新int
变量,并为其分配了零值。
for(int i=0; i < 5; i++)
Termination
是布尔语句,它告诉循环执行多长时间。 在终止语句为真之前,循环将继续。 在我们的示例中,我们正在检查i
的值是否小于 5
for(int i=0; i < 5; i++)
第三个参数 - 增量 - 在循环的每个循环之后执行。 在这里,我们可以增加初始化部分中声明的变量的值。
在我们的示例中,我们要做的是在每个循环周期后将i
的值增加 1。 i++
是一种简短形式,其功能与i=i+1
完全相同。
for(int i=0; i < 5; i++)
Java while
循环
Java 中的另一个循环是while
循环。
while
循环的一般形式可以表示如下:
while(condition) {
// execute code here
}
条件是布尔值。 这意味着在条件为真之前,将执行while
循环。 要了解有关布尔表达式的更多信息,请阅读本教程。 这次我将使用while
循环而不是for
循环重新创建第一个示例。
public class WhileLoopExample {
public static void main(String[] args) {
int i=0;
while(i<5) {
System.out.println("Iteration # " + i);
i++;
}
}
}
现在看上面的例子。 我们的控制变量i
的初始化是在循环之外完成的,但递增 1 是在循环内部完成的。 输出与我们的第一个示例完全相同:
Iteration # 0
Iteration # 1
Iteration # 2
Iteration # 3
Iteration # 4
循环通常与数组结合使用。 在我们的下一个教程 Java 数组中,我将说明如何创建,使用和遍历数组。
Java 数组
原文: https://javatutorial.net/java-array
在本教程中,我将向您展示如何在 Java 中创建和使用数组
数组是同一类型的变量的集合。 数组有多种用途。 例如,您可能希望将所有价格存储在一个数组中。 但是使数组真正有用的是可以使用存储在数组中的值的方式。
在 Java 中声明新数组的一般形式如下:
type arrayName[] = new type[numberOfElements];
其中type
是基本类型(请在教程中了解有关基本类型的更多信息)或对象。numberOfElements
是将存储到数组中的元素数。 此值不能更改。 Java 不支持动态数组。 如果需要灵活,动态的结构来保存对象,则可能需要使用某些 Java 集合。 有关更多详细信息,请参见 Java 集合教程。
总览
Java 支持一维或多维数组。
数组中的每个项目都称为元素,并且每个元素都通过其数字索引进行访问。 如上图所示,编号从 0 开始。例如,第 4 个元素将在索引 3 处访问。
初始化数组
让我们创建一个数组来存储一个 5 人的小公司中所有雇员的薪水。
int salaries[] = new int[5];
数组的类型(在本例中为int
)适用于数组中的所有值。 您不能在一个数组中混合类型。
将值放入数组
现在我们已经初始化了salaries
数组,我们想要在其中添加一些值。 我们可以在初始化期间执行以下操作:
int salaries[] = {50000, 75340, 110500, 98270, 39400};
或稍后再执行以下操作:
int salaries[] = new int[5];
salaries[0] = 50000;
salaries[1] = 75340;
salaries[2] = 110500;
salaries[3] = 98270;
salaries[4] = 39400;
遍历数组
您可以像这样调用特定元素的值来使用它:
System.out.println("The value of the 4th element in the array is " + salaries[3]);
这将产生输出:
The value of the 4th element in the array is 98270
也可以使用for
循环或while
循环遍历数组中所有元素的值。 在我们先前的教程 Java 循环中进一步了解循环
public class ArrayExample {
public static void main(String[] args) {
int salaries[] = {50000, 75340, 110500, 98270, 39400};
for(int i=0; i<salaries.length; i++) {
System.out.println("The element at index " + i + " has the value of " + salaries[i]);
}
}
}
上面程序产生的输出是:
The element at index 0 has the value of 50000
The element at index 1 has the value of 75340
The element at index 2 has the value of 110500
The element at index 3 has the value of 98270
The element at index 4 has the value of 39400
注意salaries.length
的使用。 Java 中的数组具有length
属性,该属性返回数组的长度。
Java 读取文件示例
原文: https://javatutorial.net/java-read-file-example
在此示例中,我将向您展示如何使用 Java 编程语言读取文件。
以下示例显示了如何逐行读取文件。 这种方法对于读取大型文本文件是安全的,因为一次只能将一行加载到内存中。 尽管有多种读取 Java 文件的方法,但是此示例在新版本以及早期 Java 版本上都可以很好地工作。
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader("file.txt");
br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
// process the line
System.out.println(line);
}
} catch (FileNotFoundException e) {
System.err.println("Can not find specified file!");
e.printStackTrace();
} catch (IOException e) {
System.err.println("Can not read from file!");
e.printStackTrace();
} finally {
if (br != null) try { br.close(); } catch (IOException e) { /* ensure close */ }
if (fr != null) try { fr.close(); } catch (IOException e) { /* ensure close */ }
}
在 Java 中,关闭流很重要。 因此,我在此示例中包含了正确的异常处理。
Java 对象和类教程
原文: https://javatutorial.net/java-objects-and-classes-tutorial
在本教程中,我将解释 Java 类和对象的基础。
Java 是一种面向对象的编程语言。 这意味着,除了基本类型之外,Java 中的所有内容都是一个对象。 但是,对象到底是什么? 使用类和对象的概念是将状态和行为封装到单个编程单元中。 这个概念称为封装。 Java 对象类似于真实世界的对象。 例如,我们可以用 Java 创建一个汽车对象,该对象将具有当前速度和颜色等属性。 和行为,例如:加速和停车。
创建类
Java 类是创建对象的蓝图。 让我们创建一个代表汽车的类。
public class Car {
int currentSpeed;
String name;
public void accelerate() {
}
public void park() {
}
public void printCurrentSpeed() {
}
}
看上面的代码。 汽车对象状态(当前速度和名称)存储在字段中,并通过方法显示对象的行为(加速和停车)。 在此示例中,方法是accelerator()
,park()
和printCurrentSpeed()
。
让我们在这些方法中实现一些功能。
1. 每次调用加速方法时,我们将每小时增加 10 英里的当前速度。
2. 调用驻车方法会将当前速度设置为零
3. printCurrentSpeed
方法将显示汽车的速度。
为了实现这三个要求,我们将创建一个名为Car
的类并将文件存储为Car.java
。
public class Car {
int currentSpeed;
String name;
public Car(String name) {
this.name = name;
}
public void accelerate() {
// add 10 miles per hour to current speed
currentSpeed = currentSpeed + 10;
}
public void park() {
// set current speed to zero
currentSpeed = 0;
}
public void printCurrentSpeed() {
// display the current speed of this car
System.out.println("The current speed of " + name + " is " + currentSpeed + " mpH");
}
}
类名
创建 Java 类时,必须遵循以下规则:文件名和该类的名称必须相同。 在我们的示例中 – Car
类必须存储在名为Car.java
的文件中。 Java 也是区分大小写的:用大写C
编写的汽车与用小写c
编写的汽车不同。
Java 类构造函数
构造函数是特殊的方法。 当我们创建对象的新实例时会调用它们。 在上面的示例中,构造函数为:
public Car(String name) {
this.name = name;
}
构造函数必须与类本身具有相同的名称。 它们可以带或不带参数。 此示例中的参数为“名称”。 我们使用如下构造函数创建一个新的car
对象(我将在本教程的后面部分对此进行详细说明):
Car audi = new Car("Audi");
Java 注释
您是否注意到第 11、16 和 21 行前面的//
标记? 这就是我们用 Java 编写注释的方式。 执行程序时,标记为注释的行将被忽略。 您可以编写注释以进一步解释代码中发生的事情。 撰写评论是一种好习惯,它将帮助其他人理解您的代码。 稍后再返回代码时,它也将为您提供帮助。
创建对象
现在让我们继续我们的汽车示例。 我们将创建第二个名为CarTest
的类,并将其存储到名为CarTest.java
的文件中
public class CarTest {
public static void main(String[] args) {
// create new Audi car
Car audi = new Car("Audi");
// create new Nissan car
Car nissan = new Car("Nissan");
// print current speed of Audi - it is 0
audi.printCurrentSpeed();
// call the accelerate method twice on Audi
audi.accelerate();
audi.accelerate();
// call the accelerate method once on Nissan
nissan.accelerate();
// print current speed of Audi - it is now 20 mpH
audi.printCurrentSpeed();
// print current speed of Nissan - it is 10 mpH
nissan.printCurrentSpeed();
// now park the Audi car
audi.park();
// print current speed of Audi - it is now 0, because the car is parked
audi.printCurrentSpeed();
}
}
在上面的代码中,我们首先创建 2 个类型为Car
的新对象 – Audi
和Nissan
。 这是Car
类的两个单独实例(两个不同的对象),当我们调用Audi
对象的方法时,这不会影响Nissan
对象。
执行CarTest
的结果将如下所示:
The current speed of Audi is 0 mpH
The current speed of Audi is 20 mpH
The current speed of Nissan is 10 mpH
The current speed of Audi is 0 mpH
我鼓励您尝试使用该代码。 尝试向Car
类添加新方法或编写新类。
在我们的下一个教程中,您将了解有关面向对象编程的概念的更多信息。
什么是面向对象编程(OOP)
原文: https://javatutorial.net/java-oop
遵循本教程可了解面向对象编程的原理,并编写可重用和简洁的代码
本文旨在解释有关 OOP 的原理和概念,以及在讨论 OOP 时您需要了解的 4 个主要术语:封装,继承,抽象和多态。 尽管这些原则对于每种面向对象的编程语言(例如 Java,Python,C++,Ruby 等)都具有完全的威力,但我将提供 Java 中的示例。 为什么? 因为首先是 Java 教程网站,其次 Java 是面向对象的,所以 Java 中的所有东西都是对象。 等待! 您可能不会说诸如int
,double
,long
等原始类型。 是的,是的,但是即使基本类型也具有 Java 中的 Object 表示形式。 int
是Integer
,double
是Double
,依此类推。
也准备在下次 Java 求职面试中遇到面向对象的问题。 它们是如此普遍,以至于几乎有 100% 的机会会询问您有关一项或多项 OOP 原则的信息。
什么是面向对象
Wikipedia 将 OOP 定义为:基于对象的概念的编程示例,其中可能包含字段形式的字段,通常称为属性;和程序形式的代码,通常称为方法。
面向对象编程(OOP)是一种编程语言模型,它使用对象和数据作为核心组件。 如果您想了解更多细节并更好地理解类和对象,请参阅本主题的详细教程。 单击此处的首先阅读它。
OOP 背后的主要思想是用对象而不是动作或函数来表示数据和逻辑。 可以将对象视为现实生活中的对象,例如汽车,建筑物,动物,苹果等。还有诸如HttpConnection
或UserDataParser
之类的抽象对象(我们看不到或不能吃的东西)。 所有这些都具有可操纵和访问存储在其中的数据的属性和方法。 最终,我们可以将所有内容“转换”为对象。
那么,总体上拥有所有这些对象和 OOP 有什么大不了的呢? 嗯,没有人能阻止您在一个具有数百个函数的大文件中编写 Java 程序(当然,除了团队负责人或老板),但是一旦遵循了面向对象编程的原理,您将产生可重用,可维护,可扩展和可重用的代码。 干净的代码。
这是 4 个主要的 Java OOP 原则:
封装形式
封装的目的是将变量和方法包装在一个单元中,其唯一目的是从外部类中隐藏数据。 阅读完整的 Java 封装示例,了解更多详细信息。
继承
继承是一种 OOP 特性,它允许 Java 类从其他类派生。 阅读完整的 Java 继承示例了解更多详细信息。
抽象
抽象是向用户隐藏实现细节的过程。只有函数将提供给用户。 阅读完整的 Java 抽象示例了解更多详细信息。
多态
Java 中的多态允许类的子类定义其自己的独特行为,而又共享父类的某些相同函数。 阅读完整的 Java 多态示例,了解更多详细信息。
Java 封装示例
原文: https://javatutorial.net/java-encapsulation-example
此示例演示了 Java 编程语言中封装的用法
什么是封装
封装就是将变量和方法包装在一个单元中。 封装也称为数据隐藏。 为什么? 因为,当您设计类时,您可以(并且应该)使变量对其他类隐藏,而提供处理数据的方法。 您的班级应设计成黑匣子。 您可以从外部(类)访问多个方法,并且可以访问每个方法的返回类型。 您需要了解的有关此类的所有信息是方法的名称和返回类型。 换句话说,您为类提供了一些数据并获得了新数据作为响应,而无需关心用于数据处理的内部机制。
封装是面向对象编程(OOP)背后的四个主要概念之一。 OOP 问题在求职面试中很常见,因此您可能会在下次 Java 求职面试中遇到有关封装的问题。
要实现 Java 封装:
- 将一个类的变量声明为私有。
- 提供公共的 setter 和 getter 方法来修改和查看变量值。
Java 封装示例
Java 封装示例
下面的示例演示 Java 中的封装。 Car
类具有两个字段 - name
和topSpeed
。 两者都声明为私有,这意味着它们不能在类外部直接访问。 吸气和设置方法:getName
,setName
和setTopSpeed
等已声明为公开。 这些方法对“外部人员”开放,可用于更改和从Car
对象检索数据。 请注意,我们有一种方法可以设置车辆的最高速度,而有两种吸气方法可以以 MPH 或 KMH 检索最大速度值。 这就是封装的作用–它隐藏了实现并为我们提供了所需的值
package net.javatutorial;
public class Car {
private String name;
private double topSpeed;
public Car() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setTopSpeed(double speedMPH) {
topSpeed = speedMPH;
}
public double getTopSpeedMPH() {
return topSpeed;
}
public double getTopSpeedKMH() {
return topSpeed * 1.609344;
}
}
主程序使用给定名称创建Car
对象,并使用 setter 方法存储此实例的最高速度。 现在,我们可以轻松获得 MPH 或 KMH 中的速度,而无需关心Car
类中的速度转换方式。
package net.javatutorial;
public class EncapsulationExample {
public static void main(String[] args) {
Car car = new Car();
car.setName("Porsche Cayenne 4.8-litre V8");
car.setTopSpeed(173.0d);
System.out.println(car.getName() + " top speed in MPH is " + car.getTopSpeedMPH());
System.out.println(car.getName() + " top speed in KMH is " + car.getTopSpeedKMH());
}
}
这是示例的输出
Porsche Cayenne 4.8-litre V8 top speed in MPH is 173.0
Porsche Cayenne 4.8-litre V8 top speed in KMH is 278.416512
让我们总结一下封装的好处:
- 可以将类的字段设置为只读或只写。
- 一个类可以完全控制其字段中存储的内容。
- 类的用户不知道该类如何存储其数据。 类可以更改字段的数据类型,并且该类的用户不需要更改任何代码。
Java 接口示例
原文: https://javatutorial.net/java-interface-example
在本教程中,我将向您展示如何创建和使用 Java 接口。 与往常一样,我将演示 Java 接口的实际示例。
什么是 Java 接口?
与许多其他 Java 概念一样,接口是从真实场景中派生而来的,其主要目的是按照严格的规则使用对象。 例如,如果您想打开洗衣机来洗衣服,则需要按开始按钮。 此按钮是您与洗衣机内部电子设备之间的接口。 Java 接口具有相同的行为:它们对如何与对象进行交互设置了严格的规则。 要查找有关 Java 对象的更多信息,请阅读本教程。
Java 接口表示一组带有空主体的方法。 嗯,在接口中包含完整的方法列表不是强制性的 - 它们可以为 0 或更多…但是,无论方法的数量如何,它们都应该为空。
创建一个接口
以洗衣机为例,使用一种方法startButtonPressed()
创建一个名为WashingMachine
的接口
public interface WashingMachine {
public void startButtonPressed();
}
定义接口就这些了。 注意关键字interface
的用法。 方法startButtonPressed()
没有正文。 它以结尾; 当然,您也可以使用具有返回类型和参数的方法,例如:public int changeSpeed(int speed);
如何实现接口
现在,我们将创建一个实现我们的接口的类。 继续该示例,我们将创建具有“开始”按钮的特定品牌的洗衣机。
public class SamsungWashingMachine implements WashingMachine {
@Override
public void startButtonPressed() {
System.out.println("The Samsung washing machine is now running.");
}
}
我们在类声明中使用implements
关键字。 我们需要实现startButtonPressed
方法(为它提供一些功能),否则我们的类将无法编译。
请注意,您可以在一类中实现多个接口。 您只需要在类声明中用逗号分隔接口名称,如下所示:
public class SamsungWashingMachine implements WashinMachine, Serializable, Comparable<WashinMachine> { ... }
测试你的接口
现在让我们创建一个小程序来测试我们的接口和实现
public class Test {
public static void main(String[] args) {
WashinMachine samsungWashinMachine = new SamsungWashingMachine();
samsungWashinMachine.startButtonPressed();
}
}
该程序的输出将是:
The Samsung washing machine is now running.
使用接口声明特定的对象特征
Java 接口还有另一种常见用法 - 告诉对象具有特定用途或特征。
让我们再举一个真实的例子。 你是在树林里生存。 您会发现其他物体,并将它们放在背包中以备后用。 休息时,您会浏览找到的物体并食用一次可食用的物体。
首先,让我们定义一个根本没有方法的名为FoundObject
的接口。 这些都是我们在树林里发现的物体:
public interface FoundObject {
}
现在我们定义第二个接口Eatable
。 我们将仅使用它来表示对象是否可食用
public interface Eatable {
public void eat();
}
通过以下三个类,我们将定义我们在树林中找到的对象–苹果,树莓和石头
public class Apple implements FoundObject, Eatable {
private String name;
public Apple(String name) {
this.name = name;
}
@Override
public void eat() {
System.out.println("Yummy! you eat some " + this.name);
}
}
public class Raspberry implements FoundObject, Eatable {
private String name;
public Raspberry(String name) {
this.name = name;
}
@Override
public void eat() {
System.out.println("Yummy! you eat some " + this.name);
}
}
public class Stone implements FoundObject {
private String name;
public Stone(String name) {
this.name = name;
}
}
现在让我们编写生存程序。 我们将在背包(数组)中收集找到的物品,并尝试吃掉它们
public class WoodsSurvival {
public static void main(String[] args) {
// create an array of type FoundObject
FoundObject backpack [] = new FoundObject[3];
// create the objects we found in the woods
FoundObject apple = new Apple("apple");
FoundObject stone = new Stone("stone");
FoundObject raspberry = new Raspberry("raspberry");
// add the found objects to the backpack
backpack[0] = apple;
backpack[1] = stone;
backpack[2] = raspberry;
// iterate over the found objects
for (int i=0; i<backpack.length; i++) {
FoundObject currentObject = backpack[i];
// check if object is eatable
if (currentObject instanceof Eatable) {
// cast the object to eatable and execute eat method
((Eatable) currentObject).eat();
}
}
}
}
该程序的输出为:
Yummy! you eat some apple
Yummy! you eat some raspberry
代码说明了
首先,我们创建接口FoundObject
的唯一目的是表示特定类型的对象,因此我们可以将它们放在同一数组中。 我们创建Eatable
接口以标记可以食用的对象。
当我们创建三个对象(苹果,树莓和石头)时,我们将所有对象的类都声明为implements FoundObject
,而我们可以食用的对象也实现了Eatable
接口。
在WoodsSurvival
类中,我们首先创建一个FoundObject
类型的数组。 我们稍后创建的三个对象的类型也都是FoundObject
,因此我们可以将它们放在同一数组中。 关注本文,以了解有关 Java 数组的更多信息。
迭代数组时,我们检查当前对象是否为Eatable
类型。 我们在instanceof keyford
的帮助下进行此操作。 如果两个对象属于同一类型,则instanceof
返回true
。 在我们的案例中,使用instanceof Eatable
进行检查时,苹果和树莓将返回 true,因为两者均实现了Eatable
接口。 为了能够执行eat()
方法,我们需要首先将对象显式类型转换为Eatable
。 我们使用以下代码行实现此目的:
((Eatable) currentObject).eat();
我们无法执行石头对象的eat
方法,因为它不是Eatable
类型。
结论
可以使用抽象类,集合和继承以更时髦的方式编写上面的代码示例。 其中一些是更高级的主题,将在下一个教程中进行说明。 这是一个初学者教程,旨在仅说明 Java 接口。
Java 继承示例
原文: https://javatutorial.net/java-inheritance-example
此示例演示了 Java 编程语言中继承的用法
什么是继承
继承是一种 OOP 功能,它允许 Java 类从其他类派生。 父类称为超类,而派生类称为子类。 子类从其超类继承字段和方法。
继承是面向对象编程(OOP)背后的四个主要概念之一。 OOP 问题在工作面试中很常见,因此您可能会在下一次 Java 工作面试中遇到有关继承的问题。
Java 中的“所有类的母亲”是Object
类。 Java 中的每个类都继承自Object
。 在层次结构的顶部,Object
是所有类中最通用的。 层次结构底部附近的类提供了更特殊的行为。
Java 平台中的所有类都是对象的后代(图像来自 Oracle )
Java 具有单个继承模型,这意味着每个类都只有一个并且只有一个直接超类。
子类继承其父级的所有公共和受保护的成员,无论该子类位于哪个包中。如果该子类与其父级位于同一包中,则它也继承父成员的私有成员。 您可以按原样使用继承的成员,替换它们,隐藏它们,或用新成员补充它们:
- 继承的字段可以像其他任何字段一样直接使用。
- 您可以在子类中声明一个与超类中的名字相同的字段,因此隐藏了(不推荐)。
- 您可以在子类中声明不在超类中的新字段。
- 继承的方法可以直接使用。
- 您可以在子类中编写一个新的实例方法,该方法具有与超类中的签名相同的签名,因此将覆盖。
- 您可以在子类中编写一种新的静态方法,该方法具有与超类中的签名相同的签名,因此隐藏了。
- 您可以在子类中声明不在超类中的新方法。
- 您可以编写一个隐式或使用关键字
super
来调用超类的构造函数的子类构造函数。
继承是一种强大的技术,可让您编写干净且可维护的代码。 例如,假设您有一个具有多个后继类的超类。 更改超类中的几行代码,并更改每个继承者的功能,而不是在每个子类的每一端这样做,要容易得多。
Java 继承示例
在下面的示例中,我们创建 3 个类。 超类Point
表示二维空间中具有x
和y
坐标的点。
package net.javatutorial;
public class Point {
// fields marking X and Y position of the point
public int x;
public int y;
// one constructor
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
// getter and setter methods
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
ColoredPoint
是一个子类,它扩展了Point
的所有属性和方法,并添加了一个附加字段 – colorName
。 注意这是如何完成的–我们使用关键字extends
来告诉我们要从哪个类派生
package net.javatutorial;
public class ColoredPoint extends Point {
// new field added to store the color name
public String colorName;
public ColoredPoint(int x, int y, String colorName) {
super(x, y);
this.colorName = colorName;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
}
最后是一个测试继承的程序。 首先,我们创建一个类型为ColoredPoint
的新Point
。 请注意关键字instanceof
的用法。 这样,我们可以检查对象是否为某种类型。 一旦确定点的类型为ColoredPoint
,就可以使用以下方法显式地进行类型转换:
ColoredPoint coloredPoint = (ColoredPoint)point;
现在我们可以访问新属性colorName
package net.javatutorial;
public class InheritanceExample {
public static void main(String[] args) {
Point point = new ColoredPoint(2, 4, "red");
if (point instanceof ColoredPoint) {
ColoredPoint coloredPoint = (ColoredPoint)point;
System.out.println("the color of the point is: " + coloredPoint.getColorName());
System.out.println("with coordinates x=" + coloredPoint.getX() +
" y=" + coloredPoint.getY());
}
}
}
运行上面的示例将产生以下输出
the color of the point is: red
with coordinates x=2 y=4
参考文献
官方 Oracle 继承教程
Java 抽象示例
原文: https://javatutorial.net/java-abstraction-example
此示例演示了 Java 编程语言中抽象的用法
什么是抽象
抽象是向用户隐藏实现细节的过程。 只有功能将提供给用户。 在 Java 中,使用抽象类和接口来实现抽象。 我们对 Java 接口有更详细的说明,如果您需要有关接口的更多信息,请首先阅读本教程。
抽象是面向对象编程(OOP)背后的四个主要概念之一。 OOP 问题在求职面试中很常见,因此您可能会在下次 Java 求职面试中遇到有关抽象的问题。
Java 抽象示例
举一个抽象的例子,我们将创建一个名为Employee
的超类和两个子类 - Contractor
和FullTimeEmployee
。 这两个子类都有共同的属性可以共享,例如雇员的姓名和每小时将支付给该人的金额。 承包商和全职员工之间的主要区别是 - 他们在公司工作的时间。 全职员工每天持续工作 8 个小时,承包商的工作时间可能会有所不同。
Java 抽象类示例
首先创建超类Employee
。 注意在类定义中abstract
关键字的用法。 这将类标记为抽象,这意味着它不能直接实例化。 我们将称为calculateSalary()
的方法定义为抽象方法。 这样,您可以将此方法的实现留给Employee
类的继承者。
package net.javatutorial;
public abstract class Employee {
private String name;
private int paymentPerHour;
public Employee(String name, int paymentPerHour) {
this.name = name;
this.paymentPerHour = paymentPerHour;
}
public abstract int calculateSalary();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPaymentPerHour() {
return paymentPerHour;
}
public void setPaymentPerHour(int paymentPerHour) {
this.paymentPerHour = paymentPerHour;
}
}
Contractor
类继承了其父级Employee
的所有属性,但必须提供其自己的calculateSalary()
方法的实现。 在这种情况下,我们将每小时的付款额乘以给定的工作时间。
package net.javatutorial;
public class Contractor extends Employee {
private int workingHours;
public Contractor(String name, int paymentPerHour, int workingHours) {
super(name, paymentPerHour);
this.workingHours = workingHours;
}
@Override
public int calculateSalary() {
return getPaymentPerHour() * workingHours;
}
}
FullTimeEmployee
也有自己的calculateSalary()
方法实现。 在这种情况下,我们只需乘以恒定的 8 小时即可。
package net.javatutorial;
public class FullTimeEmployee extends Employee {
public FullTimeEmployee(String name, int paymentPerHour) {
super(name, paymentPerHour);
}
@Override
public int calculateSalary() {
return getPaymentPerHour() * 8;
}
}
Java 多态示例
原文: https://javatutorial.net/java-polymorphism-example
该示例演示了 Java 编程语言中多态的用法
什么是多态
多态一词来自希腊语,意思是“许多形式”。 Java 中的多态允许类的子类定义其自己的独特行为,而又共享父类的某些相同功能。 我将从继承的角度讨论多态,在继承中,名称相同的多个方法的功能稍有不同。 此技术也称为方法覆盖。
多态是面向对象编程(OOP)背后的四个主要概念之一。 OOP 问题在求职面试中很常见,因此您可能会在下次 Java 求职面试中遇到有关多态的问题。
Java 多态示例
在此示例中,我们将创建 3 个类来演示多态,并创建一个类来测试该概念。 我们的超类称为Animal
。 动物类的后继者是Dog
和Cat
类。 这些也是动物,对不对? 这就是多态所在 - 同一对象的多种形式具有不同的行为。 为了说明这一点,我们将使用一种名为makeSound()
的方法,并在后继类中重写此方法的输出。
Java 多态示例
当我们调用makeSound()
方法时,通用动物类将输出一些抽象文本:
package net.javatutorial;
public class Animal {
public void makeSound() {
System.out.println("the animal makes sounds");
}
}
扩展了Animal
的Dog
类将产生稍微不同的结果–狗将吠叫。 为此,我们扩展了Animal
类并覆盖了makeSound()
方法
package net.javatutorial;
public class Dog extends Animal{
@Override
public void makeSound() {
System.out.println("the dog barks");
}
}
显然,我们必须对Cat
类执行相同的操作才能制作出猫叫声。
package net.javatutorial;
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("the cat meows");
}
}
最后,让我们测试一下我们的创作。
package net.javatutorial;
public class PolymorphismExample {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound();
Dog dog = new Dog();
dog.makeSound();
animal = new Cat();
animal.makeSound();
}
}
首先,我们创建一个常规的Animal
对象并调用makeSound()
方法。 我们对新创建的Dog
对象执行相同的操作。 现在请注意对animal = new Cat()
的调用 - 我们将新的Cat
对象分配给Animal
对象。 猫是动物,还记得吗? 因此,我们总是可以这样做:
Animal animal = new Cat();
通过调用此对象的makeSound()
方法,实际上将调用Cat
类中的重写的makeSound()
方法。
最后,这是程序的输出
the animal makes sounds
the dog barks
the cat meows
参考文献
官方 Oracle 多态示例
Java 中的方法重载与方法覆盖
原文: https://javatutorial.net/method-overloading-vs-method-overriding-in-java
本文通过示例演示了 Java 中方法重载和方法覆盖之间的区别
方法重载和方法覆盖都是在各种 Java 实现中高度使用的 OOP(面向对象编程)概念。 在本文中,我们已经写了关于 OOP 的 4 个主要概念的文章。 如果您不熟悉 OOP,请先查看此文章。
方法重载与方法重载是一个常见的 Java 工作面试问题。 去面试,我们不能不知道有什么区别。
什么是方法重载?
如下面的示例所示,方法重载使我们能够使用相同名称,相同或不同返回类型的多个方法。 方法重载的重要之处在于,对于同一个返回类型,所有这些方法必须具有不同的参数。 通常的做法是保留参数最多的方法的实现,而其他方法(参数较少的)仅重定向到较多方法,为缺少的参数提供默认值,如下所示:
public int calculate(int data[]) {
return calculate("Default calculation", data, 0);
}
public int calculate(String name, int data[]) {
return calculate(name, data, 0);
}
public int calculate(String name, int data[], int offset) {
// do all the calculations here
}
什么是方法覆盖?
方法覆盖是 Java 继承 OOP 原理的一部分。 方法覆盖的思想是在子类中更改给定方法的实现。 换句话说,您使用方法的相同签名(名称,返回类型,参数)“替代”了父类方法的实现,但是在覆盖方法内部实现了不同的功能。
Java 中的方法覆盖
Java 中的方法重载示例
下面的示例演示方法重载的用法。 我们有 4 个名称相同,参数和返回类型不同的方法
package net.javatutorial;
public class OverloadingExample {
static int sumOf(int a, int b) {
return a+b;
}
static int sumOf(int a, int b, int c) {
return a+b+c;
}
static double sumOf(double a, double b) {
return a+b;
}
static double sumOf(double a, double b, double c) {
return a+b+c;
}
public static void main(String[] args) {
System.out.println(sumOf(1,2));
System.out.println(sumOf(10d,20d,30d));
}
}
该程序的输出为:
3
60.0
如您在上面的示例中看到的,基于我们使用的参数调用了另一种方法。 这是增加程序可读性的好方法。
Java 中的方法覆盖示例
动物超类
package net.javatutorial;
public class Animal {
public void makeSound() {
System.out.println("the animal makes sounds");
}
}
狗子类具有相同的方法,但行为不同
package net.javatutorial;
public class Dog extends Animal{
@Override
public void makeSound() {
System.out.println("the dog barks");
}
}
请注意@Override
注解的用法。 这不是强制性的,但是最好对被覆盖的方法进行注解,以提高代码的可读性。
Java 控制流语句
原文: https://javatutorial.net/java-control-flow-statements
Java 中的控制流语句允许您在满足特殊条件时运行或跳过代码块。 您将在程序中大量使用控制语句,本教程将说明如何执行此操作。
if
语句
Java 中的if
语句的工作原理与大多数编程语言完全相同。 借助if
,您可以选择在满足预定义条件时执行特定的代码块。 Java 中if
语句的结构如下所示:
if (condition) {
// execute this code
}
条件是布尔值。 布尔值意味着它可以是true
或false
。 例如,您可以将数学方程式作为条件。 看这个完整的例子:
public class FlowControlExample {
public static void main(String[] args) {
int age = 2;
System.out.println("Peter is " + age + " years old");
if (age < 4) {
System.out.println("Peter is a baby");
}
}
}
输出为:
Peter is 2 years old
Peter is a baby
在上面的示例中,我们检查年龄是否小于 4。将年龄设置为 2,布尔条件2 < 4
为true
,结果我们打印"Peter is a baby"
。 如果我们将年龄更改为大于 3 的任何值,则该块中的代码将不再执行,并且不会打印"Peter is a baby"
。
Java 中的比较运算符
使用此运算符创建布尔结果
<
小于
<=
小于或等于
>
大于
>=
大于或等于
==
等于
!=
不等于
Java 中的条件运算符
&&
和||
运算符对两个布尔表达式执行有条件 AND 和有条件 OR 运算。
int a = 2;
int b = 2;
int c = 5;
if (a == 2 && b == 2) {
System.out.println("A and B are equeal to 2");
}
if (a == 5 || c == 5) {
System.out.println("A or C is equal to 5");
}
结果是
A and B are equeal to 2
A or C is equal 5
if-else
语句
通过此语句,您可以控制满足条件的情况以及其他情况。 看下面的代码
public class FlowControlExample {
public static void main(String[] args) {
int age = 10;
System.out.println("Peter is " + age + " years old");
if (age < 4) {
System.out.println("Peter is a baby");
} else {
System.out.println("Peter is not a baby anymore");
}
}
}
结果是
Peter is 10 years old
Peter is not a baby anymore
因为我们给年龄的值大于 3,所以执行else
语句
我将再展示一个带有if else
语句和条件运算符的示例
public class FlowControlExample {
public static void main(String[] args) {
int age = 14;
System.out.println("Peter is " + age + " years old");
if (age < 4) {
System.out.println("Peter is a baby");
} else if (age >= 4 && age < 14) {
System.out.println("Peter is a child");
} else if (age >= 14 && age < 18) {
System.out.println("Peter is a teenager");
} else if (age >= 18 && age < 68) {
System.out.println("Peter is adult");
} else {
System.out.println("Peter is an old men");
}
}
}
switch
语句
在某些情况下,您可以避免在代码中使用多个 if-s 并使代码看起来更好。 为此,可以使用switch
语句。 看下面的 java switch
示例
public class SwitchExample {
public static void main(String[] args) {
int numOfAngles = 3;
switch (numOfAngles) {
case 3:
System.out.println("triangle");
break;
case 4:
System.out.println("rectangle");
break;
case 5:
System.out.println("pentagon");
break;
default:
System.out.println("Unknown shape");
}
}
}
switch
有一把键和一个或多个情况。 在我们的示例中,键为numOfAngles
,当我们将 3、4 和 5 用作switch
语句的值时,我们处理停止点。 如果我们传递的值不同于 3、4 或 5 ,则默认执行。 另请注意,在每种情况下,中断。 如果我们不包括break
,则程序将运行到下一种情况。 例如,如果我们删除情况 3 的中断,则在上面的示例中将执行情况 3 和情况 4。
在我的下一个教程 Java 循环中,我将解释如何在 Java 中使用循环。
Java 核心
如何在 Windows,Linux 和 Mac 上安装 Maven
https://javatutorial.net/how-to-install-maven-on-windows-linux-and-mac
在本教程中,我们将介绍 Maven 的安装过程。
如果您是 WINDOWS 用户(如果不是,则向下滚动以查看如何安装 Maven(如果您是 Linux 或 Mac 用户)):
在继续下一步之前,请确保您已在系统上安装了 JDK。 如果没有,您可以查看本文,以获取快速的分步指南。 要查看 Maven 的所有必要最低要求,请在此处单击或查看下面的图像
单击此处的并下载最新版本。 您可以选择二进制版本或完整版本(也可以包含源代码和文档)。 我建议下载二进制文件,因为无论如何您都可以在线找到所有源代码和文档。
如果您不希望包含源文件和文档,请选择二进制版本。 当您单击链接时,它将自动开始下载 zip 文件。 如果仍然无法刷新页面,请重试。
下载完成后,将存档解压缩到 C,最好在 C 中创建一个名为 Maven 的文件夹,然后将文件解压缩到其中。
创建 Maven 文件夹后,将文件从下载的 zip 文件解压缩到该文件夹中。
现在我们必须设置M2_HOME
。 在能够运行 Maven 之前,我们需要在环境变量中进行设置并将bin
添加到PATH
。
添加M2_HOME
- 右键单击此 PC 上的某个位置
-
选择属性
-
在右侧,选择高级系统设置
-
之后,单击“环境变量…”
-
在“系统变量”下,选择“新建…”
-
输入框打开时,键入
M2_HOME
作为变量名和 Maven 的路径 -
在系统变量下,选择“路径”,然后单击“编辑”。
-
添加路径
C:\Maven\apache-maven-3.6.1\bin
-
验证安装是否成功
要知道安装是否成功,请在 CMD 中键入以下内容:
mvn --version
如果您得到以下信息:
Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-04T20:00:29+01:00)
Maven home: C:\Maven\apache-maven-3.6.1\bin\..
Java version: 1.8.0_211, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk1.8.0_211\jre
Default locale: en_US, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
那你就好了
Linux
要求:
- Oracle JDK 11
- 下载
apache-maven-3.6.1-bin.tar.gz
二进制文件(如果您不希望包含源代码和文档)。 - 在终端中输入
cd /opt
- 将 apache-maven 存档解压缩到
opt
目录中:sudo tar -xvzf ~/Downloads/apache-maven-3.6.0-bin.tar.gz
- 添加为环境变量:
M2_HOME="/opt/apache-maven-3.6.0"
并将bin
目录附加到Path
变量:/opt/apache-maven-3.6.0/bin
- 更新
mvn
命令:sudo update-alternatives --install "/usr/bin/mvn" "mvn" "/opt/apache-maven-3.6.0/bin/mvn" 0
sudo update-alternatives --set mvn /opt/apache-maven-3.6.0/bin/mvn
- 将 bash 完成添加到
mvn
:sudo wget https://raw.github.com/dimaj/maven-bash-completion/master/bash_completion.bash --output-document /etc/bash_completion.d/mvn
。 这将使您可以通过单击 Tab 两次来完成 Maven 命令。 - 键入
mvn –version
以验证其是否有效
MAC
- 打开终端并转到提取文件的目录并以超级用户身份登录
- 通过键入删除
tar.gz
存档。rm Downloads/apache-maven*bin.tar.gz
- 修复权限:
chown -R root:wheel Downloads/apache-maven*
- 切换 Maven 内容:
mv Downloads/apache-maven* /opt/apache-maven
- 存档管理会话:
exit
- 将 Maven 二进制文件添加到路径,然后追加:
nano $HOME/.profile
export PATH=$PATH:/opt/apache-maven/bin
Ctrl + x
保存并退出nano
- 要加载新设置,请执行 bash
- 要测试安装是否成功,请键入
mvn -version
如何使用 Maven 配置文件
原文: https://javatutorial.net/how-to-use-maven-profiles
简而言之, Maven 配置文件是一组覆盖默认值的配置值。 通过使用它,您可以为不同的环境(生产/开发)创建自定义版本。
在继续学习本教程的内容之前,假定您已经安装了 Maven。 如果您不这样做,请按照本教程 的逐步指南进行操作。
要在 Maven 中指定配置文件,您需要使用pom.xml
文件中的activeProfiles
或配置文件元素。pom.xml
在运行时时被修改。
有 3 种构建配置文件类型。
- 每个项目
- 在
pom.xml
文件中定义
- 在
- 每位使用者
- 在 Maven 设置 xml 文件(
%USER_HOME%/.m2/settings.xml
)中定义
- 在 Maven 设置 xml 文件(
- 全球
- 在 Maven 全局设置 xml 文件(
%M2_HOME%/conf/settings.xml
)中定义
- 在 Maven 全局设置 xml 文件(
如何提示 Maven Build 配置文件? 有两种方法:
- 终端–本教程涵盖
- Maven 设置–本教程涵盖
- 环境变量–在本教程中涵盖
- 操作系统设置
- 存在或缺少文件
显式激活配置文件
创建您的 Maven 项目(如果尚未创建),然后创建第一个简单的配置文件test1
。
这是我添加的pom.xml
代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>mavenprofilesdemo</groupId>
<artifactId>mavenprofilesdemo</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>Maven Quick Start Archetype</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>test1</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<compilerVersion>1.5</compilerVersion>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
<profiles>
-> <profile>
-> id
;那是我们指定如何引用配置文件的地方。 不要错过该行,这很重要,因为它不仅是必填项,而且如果您省略它,则将无法访问您的个人资料。
我们在pom.xml
文件中所做的是,我们已覆盖了编译器插件设置。 我们已将编译器的版本设置为 1.5,并将fork
设置为true
。
请记住,在这种情况下,我们仅创建了 1 个配置文件,但是我们也可以在<profiles>
标签内添加更多<profile>
标签。
覆盖所需的插件之后,该运行我们的配置文件了。 您可以通过在命令行中输入mvn test -P <id>
来运行它
在我们的例子中,我们需要编写mvn test -Ptest1
,因为我们创建的个人资料给我们提供了值为test1
的 ID。
现在转到项目的文件夹位置,然后输入mvn test -P <您的配置文件 ID>
。如果我在上面的示例中运行此命令,则得到的结果是:
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< mavenprofilesdemo:mavenprofilesdemo >-----------------
[INFO] Building Maven Quick Start Archetype 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mavenprofilesdemo ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Eclipse Projects\mavenprofilesdemo\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mavenprofilesdemo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mavenprofilesdemo ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\Eclipse Projects\mavenprofilesdemo\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mavenprofilesdemo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mavenprofilesdemo ---
[INFO] Surefire report directory: D:\Eclipse Projects\mavenprofilesdemo\target\surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running mavenprofilesdemo.mavenprofilesdemo.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.072 s
[INFO] Finished at: 2019-08-18T09:15:55+01:00
[INFO] ------------------------------------------------------------------------
使用 Maven 设置激活配置文件
转到您的用户主目录,然后打开.m2
文件夹。 如果那里没有settings.xml
文件,请创建一个。
然后将我们创建的配置文件添加为活动配置文件。 使用以下代码:
<settings xmlns = "http://maven.apache.org/POM/4.0.0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>maven.dev.snaponglobal.com</id>
<name>Internal Artifactory Maven repository</name>
<url>http://repo1.maven.org/maven2/</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
<activeProfiles>
<activeProfile>test</activeProfile>
</activeProfiles>
</settings>
现在,转到包含pom.xml
文件的文件夹并执行mvn test
。
使用环境变量激活配置文件
删除settings.xml
文件并在name
标签中添加env
值。 像这样:
<profile>
<id>test</id>
<activation>
<property>
<name>env</name>
<value>test1</value>
</property>
</activation>
</profile>
您必须创建一个称为env
的环境变量,并将其值设置为test1
。
转到包含pom.xml
的文件夹,然后键入mvn test
。
如果您希望将自定义库包含到 maven 本地存储库中,可以遵循本文。
如何将自定义库包含到 Maven 本地存储库中
原文: https://javatutorial.net/how-to-include-custom-library-into-maven-local-repository
如果您曾经想过是否可以使用 Maven 作为依赖项上传自己的库,答案是肯定的,可以。 这真的很简单。
这是一个两步过程。
第 1 步
在命令行中转到您的 Maven 项目路径,然后上传一个库,这就是 maven 命令的结构:
mvn install:install-file
-Dfile=<path-to-file>
-DgroupId=<group-id>
-DartifactId=<artifact-id>
-Dversion=<version>
-Dpackaging=<packaging>
使用上面的模板,只需填充与您的库相对应的字段。Dfile =>
文件路径,groupdId
,artifactId
和版本完全取决于您。Dpackaging =>
包类型,例如 JAR 和 WAR 等。
第 2 步
使用上述命令指定要包含的库后,需要将依赖项添加到“依赖项”选项卡中。
<dependency>
<groupId>group-id</groupId>
<artifactId>artifact-id</artifactId>
<version>version</version>
<type>jar</type>
</dependency>
在将其添加为依赖项之后,只需使用mvn package
来构建项目并添加繁荣,就可以向 Maven 项目中添加自定义库了。
如何使用 JUnit 进行单元测试
原文: https://javatutorial.net/how-to-use-junit-for-unit-testing
什么是软件测试,为什么有用?
最简单的测试软件是评估其是否满足所有要求,并检查与当前状态相关的任何不良行为和错误。
用 Java 测试
Java 有多种可用的测试框架,但是,在本教程中,我们将重点介绍 JUnit,因为它是最受欢迎的框架之一。
关于 JUnit 的一些知识
JUnit 是一个 Java 框架,它允许我们编写和运行自动测试。 随着框架的最新更新,JUnits 的可读性大大提高。
设置 JUnit
如果您使用的是通用的 IDE ,例如 Eclipse,IntelliJ 或 NetBeans,则默认情况下已安装 JUnit。 如果您不使用 IDE,而仅使用 Maven 或 Gradle,则需要在pom.xml
或 gradle 文件中将其安装。
如果在 Eclipse 中创建 Maven 项目,然后选择archetype-quickstart
并转到pom.xml
文件,您将看到 Eclipse 自动将 JUnit 添加为依赖项。
但是,版本是 3.8.1。 我们不想要那样,我们想要 JUnit 4 或 JUni5。
JUnit 4
因此,让我们删除默认版本,然后将其替换为较新的版本,如下所示:
但是,在将其添加为依赖项并且无法识别org.junit
之后,请执行以下操作:
在 Eclipse 中:属性 -> Java 构建路径 -> 库 -> 添加库 -> JUnit
如何使用 JUnit 示例
这是我的简单程序,我们将在以下程序上运行 JUnit 测试:
我的测试类
好的! 我们只在exmipeMethod
方法上运行了 2 个测试。
分解
注意,方法testConversion()
带有 JUnit @Test
注释。 这样做是为了使单元测试运行程序知道此方法代表单元测试,应该执行。 如果没有此注释,则不会将其表示为Test
类,而且不会由测试运行者运行。
assertEquals()
是执行实际测试的方法。 您真正关心的是第 13 和 17 行。assertEquals()
方法有两个参数,第一个参数是预期结果,第二个参数是实际结果。 您可以看到这是多么有用并且非常容易实现。
重要的是要指出,如果预期结果与实际结果匹配,则什么也不会发生,但是,如果预期结果与实际结果不匹配,则assertEquals
会引发异常,并且测试方法将停止进一步执行。
您可以使用任意数量的测试方法。
运行一个 JUnit
在 Eclipse 中运行
在 Eclipse 中,在 Package Explorer 上,找到 JUnit 测试,右键单击,然后选择“Run As -> JUnit Test”。
在 Maven 中运行
如果要使用 Maven 运行 JUnit,则可以使用mvn test
运行整个测试套件,也可以运行特定的测试:mvn -Dtest=TestName test
。
在 Gradle 中运行
在 gradle 中,对于整个测试套件:gradlew test
,对于特定测试:gradlew -Dtest.single=testName test
。
如何使用 Maven 运行 JUnit 测试
原文: https://javatutorial.net/how-to-run-junit-test-with-maven
本教程将介绍如何使用 Maven 的 Surefire 插件运行单元测试。 如果您不熟悉单元测试,则可以按照本教程快速了解。
在我们的 Maven 项目中,我们需要以下强制性依赖项:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
</dependencies>
junit-jupiter-engine
依赖项包含运行我们的单元测试的 JUnit Jupiter 测试引擎的实现。
junit-jupiter-api
依赖项提供了 API,使我们能够编写使用 JUnit 5 的测试和扩展。
因此,让我们创建一个非常简单的 Java 文件:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example1 {
public static int getNumber() {
return 5;
}
public static String getMeaningfulText() {
return "Hello World";
}
}
现在,为它创建一个 Test 类:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestExample1 {
@Test
public void testNumber() {
assertEquals(5, Example1.getNumber());
}
@Test
public void testMeaningfulText () {
assertEquals(“Hello World”, Exampe1.getMeaningfulText ());
}
}
最后,我们可以使用mvn clean build
运行程序,并且应该看到 Superfire 插件正在运行我们的单元测试
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ running-unit-tests ---
[INFO]------------------------------------------------------- T E S T S-------------------------------------------------------Running net.javatutorial.junit5.JUnit5Example1Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.039 sec -in net.javatutorial.junit5.JUnit5Example1Results :Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------[
INFO] BUILD SUCCESS[
INFO] ------------------------------------------------------------------------
如何在 Java 中使用 Maven 创建子模块
原文: https://javatutorial.net/how-to-create-submodules-with-maven-in-java
您将需要什么。
- IDE 或文本编辑器
- JDK 1.8 或更高版本
- Maven
Maven 的多模块到底是什么?
这个多模块项目是基于处理多个子模块的 POM 聚合器构建的。 聚合器通常位于项目的根目录中,并且必须具有 pom 类型的包装。 子模块也是 Maven 项目,很酷的事情是它们可以单独构建,也可以通过同一聚合器 POM 构建。
我们为什么要使用多模块?
减少重复。 如果我们有一个包含多个模块的项目并对其进行更改,则不必分别构建所有模块。 取而代之的是,我们可以运行一个 Maven 命令来为我们执行此操作。
什么是父级?
maven 项目中的父项是 pom 打包类型,它使该项目充当聚合器,这意味着它将不再产生其他工件。
什么是模块?
将模块视为子项目,可以有效地继承父项目(聚合器)的属性。 同样,所有模块都可以使用单个命令构建,从而帮助我们节省了大量时间。 不同的孩子可以有不同的包装类型,这太棒了! 一个子项目可能是 JAR 项目,可以打包到 WAR 项目中,依此类推。
让我们开始构建我们的应用程序!
要在 Eclipse 中的项目中创建pom.xml
文件和整个 Maven,请执行以下步骤:
- 点击“新建 -> 其他”
- 之后,选择“Maven Project”
- 点击“下一步”
选择快速入门,如下所示:
- 为其提供一个组 ID 和工件 ID:
- 单击“完成”。
- 好的,太好了! 现在,我们建立了 Maven 项目。
要使用终端创建pom.xml
文件,请键入以下内容:
mvn archetype:generate -DgroupId=com.submoduledemo
-DartifactId= submoduledemo
-DarchetypeArtifactId=maven-archetype-quickstart
-DinteractiveMode=false
现在,将pom.xml
的文件打包类型更改为pom
。 这将在父级中进行。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>submoduledemo</groupId>
<artifactId>submoduledemo</artifactId>
<version>0.0.1-SNAPSHOT</version> <
packaging>pom</packaging>
<name>submoduledemo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
现在该添加一些子模块(子项目)了。 转到父级的目录并运行以下命令:
mvn archetype:generate -DgroupId= submoduledemo -DartifactId=example1
mvn archetype:generate -DgroupId= submoduledemo -DartifactId=example2
mvn archetype:generate -DgroupId= submoduledemo -DartifactId=example3
这些命令中的每一个执行后,您应该看到类似以下的内容:
Maven 之所以知道这些是子模块或子项目而不是父项目,是因为我们在父文件夹中创建了这最后 3 个子目录,因此请注意。
自动运行这三个命令(生成三个子项目)后, Maven 将生成它们并通过添加以下内容为我们修改pom.xml
文件:
<modules>
<module>example1</module>
<module>example2</module>
<module>example3</module>
</modules>
现在,关于构建子模块的最酷的事情是,当我们在父项目目录中运行mvn package
命令时,Maven 将测试作为子级创建的所有三个模块,并以此结果进行构建。 如果一个子模块依赖于另一个子模块,例如example1
依赖于example2
,则 Maven 将在构建example1
之前构建example2
。
构建整个项目之前的最后一件事,如果我们想在子模块之间共享配置,则应在其pom.xml
文件中声明父对象,如下所示:
<parent>
<groupId>submoduledemo</groupId>
<artifactId>submoduledemo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
最后,建立项目
在 Eclipse 中,右键单击“项目 Run As -> Maven 构建”,如下所示:
然后输入包:
您应该看到类似以下内容:
该构建为我们所有的子模块生成了三个.jar
文件。
要在命令行中执行相同的操作,只需键入mvn package
。
如何使用 Maven 创建 Java JAR 文件
原文: https://javatutorial.net/how-to-create-java-jar-file-with-maven
创建可执行文件 jar 时,通常我们不需要其他依赖项。 我们需要做的就是创建一个 Maven Java 项目,并在main
方法旁边至少有 1 个类。
请注意,我们假定您已在计算机上安装了 Maven。 如果您不这样做,请按照本教程的分步指南。
让我们从在 Eclipse 中创建 Maven 项目开始。
步骤 1 – 打开 Eclipse 并创建一个新的 Maven 项目(文件 -> 新建 -> 其他 -> Maven 项目)
步骤 2 – 创建 Maven 项目后,在新窗口中单击“下一步”,如下所示:
步骤 3 – 选择maven-archetype-quickstart
,然后单击“Next”,如下所示:
步骤 4 – 提供组和工件 ID,然后点击“完成”:
现在,我们的 Maven 项目已在 Eclipse 中创建。
打开pom.xml
时,它应如下所示:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jarfilecreate</groupId>
<artifactId>jarfilecreate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jarfilecreate</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
xml 文件中突出显示的行指定将要创建的文件为 jar 。 您也可以删除它,因为默认情况下,包为 jar。
到目前为止,我们已经在 Eclipse 中创建了 Maven 项目,并且还指定了将要创建的文件,其类型为 jar。
步骤 5 – 让我们添加一个具有main
方法的类。 重要提示:我们需要将其添加到src/main/
中:
在main
方法中添加一条打印语句。将会用于本教程。
步骤 7 – 要最终创建 jar 文件,请右键单击“Project -> Run As -> Maven build -> input package”:
之后,只需输入包:
点击“运行”,您应该看到类似以下内容的内容:
蓝色突出显示的文本显示了我的 jar 文件的存储位置。 通常,它存储在目标文件夹中。 让我们从此处复制path
文件夹并将其粘贴,然后查看 jar 文件是否在那里。
那里是:
另外,如果您使用终端,要获取 jar 文件,则需要键入mvn package
,这将导致相同的结果。
如何使用 Maven 创建 Java WAR 文件
原文: https://javatutorial.net/how-to-create-java-war-file-with-maven
在继续进行下一步之前,请确保已在系统上安装了 JDK 和 Maven。
如果您尚未安装 JDK,请点击此处。
如果您尚未安装 Maven,请点击此处。
使用 Eclipse 生成 WAR 文件
步骤 1 – 打开 Eclipse 并创建一个新的 Maven 项目(文件 -> 新建 -> 其他 -> Maven 项目)
步骤 2 – 创建 Maven 项目后,在新窗口中单击“下一步”,如下所示:
步骤 3 – 选择maven-archetype-webapp
并单击“Next”,如下所示:
步骤 4 – 输入详细信息,例如我的,然后单击“完成”
您的 Maven 项目目录应类似于以下内容:
并且pom.xml
应该看起来像这样:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>createwar</groupId>
<artifactId>createwar</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>createwar Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>createwar</finalName>
</build>
</project>
步骤 6 – 将pom.xml
替换为以下代码:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>CrunchifyTutorial</groupId>
<artifactId>CrunchifyTutorial</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
</project>
最重要的几行是:
<packaging>war</packaging>
和:
<artifactId>maven-compiler-plugin</artifactId>
这就是我们有效地将其转换为 WAR 的地方。
步骤 7 – 右键点击“Project- > Run As -> Maven build…”
步骤 8 – 在“目标”部分中键入clean install
,然后单击“运行”,如下所示:
步骤 9 – 您应该看到BUILD SUCCESS
,像这样:
恭喜! 您有您的.war
文件。
使用 CMD 生成 WAR 文件
步骤 1 – 通过在我们安装了 Java 的地方添加编译器来修改pom.xml
文件。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>craetewar</groupId>
<artifactId>craetewar</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>craetewar</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<finalName>createwarexample</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<fork>true</fork>
<executable>C:\Program Files\Java\jdk1.8.0_211\bin\javac.exe</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>
注意:<executable>
是重要的部分。 您的 Java 版本可能有所不同,请确保牢记这一点并放置正确的文件夹路径。
步骤 2 – 在终端中转到项目的文件夹路径,例如:
步骤 3 - 运行mvn clean install
,它将负责创建 WAR 文件:
有我们的 WAR 文件:
好的! 我们已经成功使用 Eclipse 和 Terminal 生成了 WAR 文件。
如果您有兴趣生成 JAR 文件,我已经有关于该主题的文章。 您可以通过单击此处来遵循它。
JVM 解释
原文: https://javatutorial.net/jvm-explained
本文介绍了 Java 虚拟机(JVM)及其架构
JVM 代表 Java 虚拟机。 它为您提供了执行已编译程序的环境,称为字节码。 来自不同供应商的 JVM 有多种实现,可用于各种平台。 在本文中,我将解释 JVM 的主要组件,包括内存管理,类加载和垃圾收集器。
通常,我们不深入探讨 JVM 的内部机制。 如果我们的代码行得通,那么我们就不会在乎内部机制了……直到那天出了问题,我们需要调整 JVM 或修复内存泄漏。
Java 虚拟机问题在求职面试中非常受欢迎。 采访者喜欢提出有关 JVM 的各种问题,以证明您对 Java 平台的一般理解。
什么是 Java 虚拟机
Java 被设计为可在各种平台上运行,其概念为“一次编写,随处运行”。 这是什么意思? 例如,与像 C++ 这样的编程语言不同,在 C++ 中,代码是针对特定平台进行编译并在其上本地运行的,而 Java 源代码则首先被编译为字节码.class
文件。 编译后,类文件将由虚拟机(VM)解释。 看下图
在不同平台上运行 Java 字节码–“一次编写,随处运行”的概念
首先,我们将 Java 源代码(.java
文件)编译为字节码(.class
文件)。 字节码是 Java 和机器语言之间的中间语言。 您可以在任何 JVM 实现上执行相同的字节码,而无需调整一个或另一个 OS 或平台的代码。
Java 虚拟机架构
Java 虚拟机包含三个主要区域:
- 类加载器子系统
- 运行时数据区
- 执行引擎
我们将更详细地介绍每个
Java 虚拟机架构图
类加载器子系统
我们已经在单独的教程中介绍了类加载器。 您可能需要查看 Java 类加载器,以了解更多详细信息。
载入
编译的类存储为.class
文件。 当我们尝试使用类时,Java 类加载器将该类加载到内存中。 在已经运行的类中通过名称引用类时,这些类将引入 Java 环境。 一旦第一个类运行,以后将由类加载器完成加载类的尝试。 通常,通过声明并使用静态main()
方法来完成第一类的运行。
有三种类型的类加载器:
- 自举类加载器 - 它加载 JDK 内部类,通常加载
rt.jar
和其他核心类,例如java.lang.*
包类 - 扩展类加载器 - 它从 JDK 扩展目录(通常是 JRE 的
lib/ext
目录)加载类。 - 系统类加载器 - 从系统类路径加载类,可以在使用
-cp
或-classpath
命令行选项调用程序时进行设置。
链接
链接类或接口涉及验证和准备该类或接口,其直接超类,其直接超接口以及必要时其元素类型。
JVM 要求维护以下所有属性:
- 类或接口在链接之前已完全加载。
- 在初始化类或接口之前,必须对其进行完全验证和准备。
- 链接期间检测到的错误会抛出到程序中某个位置,在该位置上,程序将采取某些操作,这些操作可能直接或间接地需要链接到错误所涉及的类或接口。
初始化
类或接口的初始化包括执行其类或接口的初始化方法或调用该类的构造函数。
因为 Java 虚拟机是多线程的,所以类或接口的初始化需要仔细的同步,因为某些其他线程可能试图同时初始化同一类或接口。
这是类加载的最后阶段,在这里所有静态变量都将被分配原始值,并且将执行静态块。
运行时数据区
运行时数据区域内有五个组件:
方法区
所有类级别的数据(包括静态变量)都将存储在此处。 每个 JVM 只有一个方法区域,它是共享资源。
堆区
所有对象及其对应的实例变量和数组都将存储在此处。 每个 JVM 还有一个堆区。 由于“方法”和“堆”区域共享多个线程的内存,因此存储的数据不是线程安全的。
栈区
对于每个线程,将创建一个单独的运行时栈。 对于每个方法调用,将在栈存储器中创建一个条目,称为栈帧。 所有局部变量都将在栈存储器中创建。 栈区域是线程安全的,因为它不是共享资源。 栈框架分为三个子实体:
- 局部变量数组–与该方法有关,涉及多少局部变量,并且相应的值将存储在此处。
- 操作数栈–如果需要执行任何中间操作,则操作数栈充当执行该操作的运行时工作区。
- 帧数据–与该方法相对应的所有符号都存储在此处。 在任何例外情况下,捕获块信息都将保留在帧数据中。
PC 寄存器
每个线程将具有单独的 PC 寄存器,以保存当前执行指令的地址,一旦执行了该指令,PC 寄存器将被下一条指令更新。
本机方法栈
本机方法栈保存本机方法信息。 对于每个线程,将创建一个单独的本机方法栈。
执行引擎
分配给运行时数据区的字节码将由执行引擎执行。 执行引擎读取字节码并逐段执行。
解释器
解释器解释字节码的速度较快,但执行速度较慢。 解释器的缺点是,当多次调用一种方法时,每次都需要新的解释。
JIT 编译器
JIT 编译器消除了解释器的缺点。 执行引擎将使用解释器的帮助来转换字节码,但是当发现重复的代码时,它将使用 JIT 编译器,该编译器将编译整个字节码并将其更改为本地代码。 此本地代码将直接用于重复的方法调用,从而提高系统的性能。
垃圾收集器
垃圾收集器(GC)收集并删除未引用的对象。 可以通过调用System.gc()
来触发垃圾收集,但是不能保证执行。 JVM 的垃圾收集收集创建的对象。
Java 本机接口(JNI)
JNI 将与本机方法库进行交互,并提供执行引擎所需的本机库。
本机方法库
它是执行引擎所需的本机库的集合。
参考文献
Oracle 的 Java 虚拟机规范
Java 内存模型解释示例
原文: https://javatutorial.net/java-memory-examples
内存可以描述为字节数组,您可以在其中单独访问每个字节。 就像在 Java 中的数组中一样,在内存中的每个字节或更确切的位置上,都有可以访问的数据。 在 32 位架构中,每个内存“插槽”包含 32 位,也称为 1 个字,或仅 4 个字节。
内存视觉展示
在上面的表示中,4000、4004、4008 等表示内存插槽的地址,或者在与数组,数据的索引或内存位置进行的比较中。 这些“随机值”中的每一个代表 32 位。 由于每个内存插槽占用 32 位或 4 个字节,因此内存地址每次增加 4。
通常,在 Java 和一般的编程中,有两种类型的可变范围 – 全局和局部。 全局变量是可以从程序中的任何地方访问的变量,而局部变量是只能在给定函数中创建它们的地方访问的变量。 因此,这两种不同类型的变量作用域存储在不同的存储区域中 - 栈和数据。
堆
public void doSomething() {
int v = 0;
System.out.println(v);
}
在上面的简单 Java 示例中,v
存储在栈存储区域中。 这是因为v
是局部变量。
静态数据
public class Example {
int globalVar = 3;
public int showVar() {
return globalVar;
}
}
在上面的 Java 示例中,globalVar
位于静态数据存储区中。 因为如您所见,即使未在其中创建方法,也可以通过该方法进行访问。 此外,可以在整个程序中对其进行访问。
堆
public class Person {
int pid;
String name;
public Person(int id, String name) {
this.pid = id;
this.name = name;
}
}
public class Driver {
public static void main(String[] args) {
int id = 1;
String pName = "Rick";
Person p = new Person(id, pName);
}
}
在上面的 Java 示例中,我们创建了Person
类的新实例并将其存储在p
中,实际上,我们使用堆区域为动态分配我们创建此类内存所需的内存。 使用new
关键字的实例。 换句话说,它不固定为一定大小。 无论实例有多少字节,如果有足够的内存(可能),将创建该实例,并且该实例将仅保留创建该实例所需的字节数。
Java 在动态内存分配方面为我们省去了很多麻烦,因为在某些其他语言(例如 C)中,您必须手动分配内存并在不再需要该内存时手动“释放”相同的内存, 而在 Java 中,一切都在幕后发生,只需调用关键字new
。
我将建议以下有关如何调整 JVM 以使用特定内存量的教程
Java 增加内存
捕获 Java 堆转储的前 3 种方法
原文: https://javatutorial.net/capture-java-heap-dump
在本文中,我将教您多种捕获 Java 堆转储的方法。 对于优化内存消耗至关重要,堆转储被描述为 Java 进程的内存打印。
Java 堆转储是诊断与内存相关的问题的重要对象,这些问题包括java.lang.OutOfMemoryError
,垃圾收集问题和内存泄漏(缓慢),这些都是 Java Web 开发的一部分。
为了清楚起见,在进行快照的瞬间,堆转储包含诸如 Java 类和堆中的对象之类的信息。
可以帮助您分析堆转储的工具包括 Heap Hero 和 Eclipse MAT 。 但是,您仍然需要为工具提供在正确的时间和正确格式下捕获的堆转储。
1. jmap –XX:+HEAPDUMPONOUTOFMEMORYERROR
值得注意的是,jmap
会将堆转储打印到特定的文件位置。jmap
工具通常打包在 JDK 中。 您可以在以下文件夹中找到它:<JAVA_HOME>\bin
。
要调用jmap
,请执行以下过程。
jmap -dump: live, file=<file-path> <pid>
,其中pid
是 Java 进程 ID,将为其捕获堆转储。此外,file-path
是其中打印堆转储的文件路径。
请注意,传递“实时”选择至关重要。 如果选项“通过”,则只会将活动对象写入堆转储文件。 但是,如果您无法通过该选项,则所有对象(包括未设置为垃圾收集的对象)都将被打印到堆转储中。 这样的错误会过多且不必要地增加其堆转储的大小。 通过将您的移动开发需求与 Java Development Company 签订合同,可以避免此类错误。
2. HeapDumpOnOutOfMemoryError
当应用程序遇到java.lang.OutOfMemoryError
时,捕获瞬时堆转储至关重要。
这样的过程将有助于确定内存中占用的对象以及它们在java.lang.OutOfMemoryError
发生时的位置所占用的空间量(百分比)。
但是,由于操作和技术很多,操作组可能无法捕获堆转储。 此外,团队可能还重新启动了该应用程序。 因此,堆转储捕获已成为系统应用程序的关键方面,尤其是在涉及内存问题时。
幸运的是,XX:+HeapDumpOnOutOfMemoryError
选项将在该过程中提供帮助。 您只需在应用程序启动时传递系统属性(XX:+HeapDumpOnOutOfMemoryError
)。 然后, JVM 将通过在 JVM 面临OutOfMemoryError
的确切时间捕获堆转储来完成其余工作。
值得注意的是,在上述情况下将捕获的头转储将打印在名为-XX:HeapDumpPath
的系统属性概述的位置中
3. jcmd
jcmd
工具用于发送命令请求以诊断 Java JVM 。 同样,jcmd
工具包含在 JDK 软件包中。 您可以在名为bin
的文件夹中获取它。
这是调用jcmd
时需要使用的过程;
- 转到
jcmd <pid> GC.heap_dump <file-path>
- 其中
pid
:是一个 Java 进程 ID,将为其捕获堆转储 - 另外,
file-path
是在其中打印堆转储的文件路径。
结论
在本文中,我讨论了可用于捕获 Java 堆转储的三个主要过程:(1)jmap –XX:+HEAPDUMPONOUTOFMEMORYERROR
(2)HeapDumpOnOutOfMemoryError
和(3)jcmd
。
Java 垃圾收集
原文: https://javatutorial.net/java-garbage-collection
本文讨论了有关 Java 垃圾收集(GC)的知识,该垃圾收集被视为 Java 编程语言中的复杂主题之一。
顾名思义,java 中的垃圾收集功能可以自动从内存中搜索,发现和删除垃圾,而无需用户明确执行此工作。 这里的垃圾是指未引用的对象。
有多种方法可以使对象成为未引用和无用的对象。 一些是:
- 引用的清零
- 将引用分配给另一个
- 匿名对象等等
因此,垃圾收集功能会找出这些对象并自动将其从内存中删除并删除它们,从而实现高效的内存使用和管理。
如果要用 C 语言进行相同的垃圾收集和优化,则可以使用free()
函数,而在 C++ 中,则可以使用delete()
方法。 因此,Java 中此过程实现了自动化,从而为用户减少了麻烦。
从技术上讲,Java 垃圾收集处理的是跟踪 JVM(Java 虚拟机)堆空间中的每个对象,并删除(删除/取消分配)未使用的对象。
垃圾收集(GC)实现有 4 种类型:
- 串行垃圾收集器
- 并行垃圾收集器
- CMS 垃圾收集器
- G1 垃圾收集器
串行垃圾收集器
GC 的此实现仅使用一个线程,并且在运行时冻结所有应用程序线程。 因此,它不是在多线程应用程序中使用的最佳选择。 所有垃圾收集事件都在一个线程中串行进行。
它用于对暂停时间要求不高且在客户端样式的计算机上运行的应用程序中。 它是为单线程系统设计的,是 JVM 中较小堆大小的首选。
并行垃圾收集器
此变体是 JVM 的默认垃圾收集器,也称为吞吐量收集器。 并行 GC 在管理堆时利用多个线程,而串行 GC 则不然。 但是,像串行 GC 一样,它在运行时也会冻结其余的应用程序线程。
可以指定最大垃圾收集线程以及暂停时间,吞吐量和占用空间(堆大小)。
CMS 垃圾收集器
CMS 代表并发标记扫描,并同时使用多个 GC 线程来扫描堆并标记未引用的对象,这些对象随后在扫描中被删除/取消分配。 对于那些需要短时间垃圾收集暂停并能够负担与 GC 共享资源的应用程序,它是首选。 使用该 GC 实现的应用程序比不使用 GC 的应用程序要慢,但是在执行垃圾收集时它们不会完全暂停整个应用程序。
在两种情况下,垃圾收集器的这种含义进入了“世界停止”(STW)模式:
- 在初始化根的初始标记时
- 当算法同时运行时,应用程序更改了堆的状态时; 强制其返回以确保标记了正确的对象。
当对象从年轻一代移动到老一代并且收集器没有足够的时间在老一代中腾出空间时,会遇到升级失败。
G1 垃圾收集器
G1 代表垃圾优先 GC,适用于在具有大内存的多处理器计算机上运行的应用程序。 与 CMS 收集器相比,它具有更高的性能效率。
G1 GC 将堆划分为一组大小相等的堆区域,并标记并发的全局标记阶段,以确定是否正在使用堆中的对象。
标记完成后,GC 知道哪些区域大部分为空。 然后,GC 首先从这些区域收集垃圾(这称为清扫阶段),从而产生大量可用空间。 因此,该名称首先是垃圾。
垃圾收集的好处:
- 没有手动的内存分配/取消分配处理,因为 GC 会自动处理未使用的内存空间
- 没有处理悬空指针的开销
- 自动内存泄漏管理
垃圾收集的缺点:
- 跟踪对象引用的创建/删除需要更多的 CPU 能力,并且可能会影响需要大内存的请求的性能
- 程序员无法控制专用于释放不再需要的对象的 CPU 时间的调度
- 使用某些 GC 实现可能会导致应用程序意外停止
- 在某些情况下,自动内存管理可能不如适当的手动内存分配/取消分配那样有效。
一个简单的程序,显示 Java 垃圾收集的实现:
public class TryingGrabageCollection{
public void finalize(){
System.out.println("Object has been collected by the garbage collector!");
}
public static void main(String args[]){
TryingGrabageCollection obj1 = new TryingGrabageCollection ();
TryingGrabageCollection obj2 = new TryingGrabageCollection ();
obj1 =null;
obj2 =null;
System.gc();
}
}
输出:
Object has been collected by the garbage collector!
Object has been collected by the garbage collector!
Java 互斥量示例
原文: https://javatutorial.net/java-mutex-example
在更深入地了解互斥量之前,先给出一个示例:
想想一个队列。 不管长短,都没关系。 现在想想一辆正在出售游乐园门票的卡车。 一次一个人可以买票。 该人买了票后,就该排队了。
这个小故事与理解互斥量有什么关系? 让我解释。
互斥量允许每个线程具有 1 个许可。换句话说,一次只能有 1 个线程可以访问资源。 在上面的类比中,两个人不能同时购买门票。互斥量也是如此。 它不是线程,而是人员,而是票证。 同样的事情或多或少..
互斥量与Semaphore
略有不同,因此Semaphore
允许多个线程访问资源。意思是,多个人可以同时购买门票。
构造器
public Semaphore(int permits)
;public Semaphore(int permits, boolean fair)
第一个构造函数是我们实际上可以区分互斥量和Semaphore
的地方。 如果那里有 1 作为参数,则意味着将只允许 1 个线程获取锁。 请记住,由于它没有第二个参数boolean fair
,因此您正在使Semaphore
类以任何顺序访问任何线程。
第二个构造函数如果传递true
(公平),则确保按线程请求访问并在队列中等待的顺序给出访问。
互斥量基本代码实现
import java.util.concurrent.Semaphore;
public class MutexDemo {
// create a Semaphore instance that makes it so only 1 thread can access resource at a time
private static Semaphore mutex = new Semaphore(1);
static class ThreadDemo extends Thread {
private String name = "";
public ThreadDemo(String name) {
this.name = name;
}
@Override
public void run() {
try {
// check the above mentioned analogy in the article for reference
System.out.println("How many people can buy a ticket at a time: " + mutex.availablePermits());
System.out.println(name + " is buying a ticket...");
mutex.acquire();
try {
Thread.sleep(1000);
System.out.println(name + " is still buying a ticket. How many people can still buy the ticket alongside him: " + mutex.availablePermits());
} finally {
mutex.release();
System.out.println(name + " bought the ticket.");
System.out.println("How many people can buy tickets after " + name + " has finished buying the ticket: " + mutex.availablePermits());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadDemo thread1 = new ThreadDemo("Bob");
thread1.start();
ThreadDemo thread2 = new ThreadDemo("Charlie");
thread2.start();
ThreadDemo thread3 = new ThreadDemo("Christie");
thread3.start();
}
}
输出
How many people can buy a ticket at a time: 1
Bob is buying a ticket...
How many people can buy a ticket at a time: 0
Charlie is buying a ticket...
How many people can buy a ticket at a time: 0
Christie is buying a ticket...
Bob is still buying a ticket. How many people can still buy the ticket alongside him: 0
Bob bought the ticket.
How many people can buy tickets after Bob has finished buying the ticket: 1
Charlie is still buying a ticket. How many people can still buy the ticket alongside him: 0
Charlie bought the ticket.
How many people can buy tickets after Charlie has finished buying the ticket: 1
Christie is still buying a ticket. How many people can still buy the ticket alongside him: 0
Christie bought the ticket.
How many people can buy tickets after Christie has finished buying the ticket: 1
从输出中可以看到,当有人买票时,没有其他人可以买。这是显示以下内容的行之一:
Bob 仍在买票。 还有多少人可以和他一起买票: 0
但是,在他“购买”了机票之后,其他人立即购买了机票。
归根结底,它涉及acquire()
和release()
。acquire()
是指该人开始“购买机票”的时间,release()
是指该人“购买机票”的时间。
Java 信号量示例
原文: https://javatutorial.net/java-semaphore-example
信号量可用于限制并发线程的数量,并且实质上,此类维护一组许可。
acquire()
从信号量获取许可,然后release()
将许可返回信号量。 如果没有许可证,则acuire()
将阻塞直到可用
信号量用于控制对特定资源的访问。
工作流程
信号量设置为一个计数值。 然后线程尝试获取许可,如果计数为 0 或小于 0,则线程将被阻塞,并且它将等待下一个许可(如果有)。 这将一直保持下去,直到数量大于 0。如果是,则信号量将提供对线程资源的访问。 然后线程将释放许可,计数将增加 1。
构造器
Semaphore(int permits)
:使用给定数量的许可和不公平的公平设置创建一个信号量Semaphore(int permits, boolean fair)
:创建具有给定许可数量和给定公平性设置的信号量
主要方法
void acquire()
:从当前信号量获取许可,阻塞直到可用,否则线程被中断。void acquire(int permits)
:从当前信号量获取指定的许可数量,直到所有可用或线程中断为止一直阻塞。int availablePermites()
:返回当前信号量中可用的当前许可数量
要查看所有方法,请单击此处,您将被重定向到 Oracle 官方文档。
信号量可用于锁定对特定资源的访问。 每个线程都必须请求“权限”,因此需要在访问资源之前调用方法acquire()
。 当线程不再需要资源时,它必须调用release()
来释放锁。
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) throws Exception {
Semaphore semExample = new Semaphore(1);
System.out.println("Available permits: " + semExample.availablePermits());
semExample.release();
System.out.println("Available permits: " + semExample.availablePermits());
semExample.acquire();
System.out.println("Available permits: " + semExample.availablePermits());
semExample.acquire();
System.out.println("Available permits: " + semExample.availablePermits());
semExample.acquire();
System.out.println("Available permits: " + semExample.availablePermits());
}
}
输出
Available permits: 1
Available permits: 2
Available permits: 1
Available permits: 0
从上面的示例中可以看到,当您调用release()
时,您正在向Semaphore
实例添加许可。 当您调用acquire()
时,您正在删除permit()
。 如果没有许可证,而您打电话给获取,它将等待直到许可证被释放,因此在上例中将永远不会执行最后一个打印语句。
如果您有 1 个许可,然后又有一个acquire()
调用,紧接着是一个release()
调用,则称为锁。
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
Semaphore thread = new Semaphore(1);
public static void main(String args[]) {
final SemaphoreDemo test = new SemaphoreDemo();
new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();
new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();
}
private void mutualExclusion() {
try {
System.out.println(Thread.currentThread().getName() + " is waiting to acquire a permit.");
thread.acquire();
System.out.println("Permit has been acquired to " + Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " is releasing the permit...");
thread.release();
System.out.println("Released.");
}
}
}
输出
Thread-0 is waiting to acquire a permit.
Permit has been acquired to Thread-0
Thread-1 is waiting to acquire a permit.
Thread-0 is releasing the permit...
Released.
Permit has been acquired to Thread-1
Thread-1 is releasing the permit...
Released.
从输出中可以看到,线程 0 允许了一个获取,线程 1 开始等待为其本身获取一个许可,但是它(线程 1)仅在线程 0 释放其许可时才获得它。
Java 并行流示例
原文: https://javatutorial.net/java-parallel-streams-example
通过流 API,开发人员可以通过创建并行流并提高程序执行操作的速度来利用多核架构并提高 Java 程序的性能。
有两种创建并行流的方法:
- 通过使用
parallelStream()
方法 - 通过使用
parallel()
方法
当您使用并行流时,它们实际上会使用更多的 CPU 能力,这将使整个输出或处理(如果您愿意)的整体速度更快。 更快! 另外,如果您不使用并行流进行大型计算,则您的程序将仅使用 16 个内核中的 1 个内核。 多么低效!
顺序
并行流将问题分为多个小子集(或子问题),它们可以同时解决(不同于顺序排列的顺序,在这些子集中一个一个地执行每个小问题)。 在计算了子问题之后,将所有对它们的解决方案组合在一起。
如何实现
Java 允许您通过使用诸如 map 之类的聚合操作来实现并行流。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static int compute(int n) {
for (int i = 0; i < 10000; i++) {
n = n + i;
}
return n;
}
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
arrayList.add(i*6);
}
System.out.println("Added all the numbers to the arraylist.");
System.out.println("Starting to add numbers...");
int result = arrayList.parallelStream().map(i -> compute(i)).reduce(Integer::sum).get();
System.out.println(reuslt);
}
}
在此示例中,我们有一个示例方法,该方法仅循环 10000 次并将i
加到n
。 最后,我们简单地返回n
。 在我们的主要函数中,我们创建一个包含整数的ArrayList
。 然后我们创建一个巨大的循环,循环 1000000 次,然后将每个增量i * 6
加到arraylist
中。 添加完之后,我们得到了这两个打印语句,它们指出这个大循环已经完成。
在这些打印语句下面是有趣的部分。 更具体地说,这是我们使用映射函数创建并行流的地方。 我们在arraylist
上调用parallelStream()
,对于arraylist
中的每个i
,我们都在调用计算函数,并将arraylist
的元素作为参数传递。 最后,我们将所有结果结合在一起。
何时应使用并行流
- 当有大量数据要处理时
- 如果循序渐进的方法使您付出了代价
- N(元素数量)Q(每元素成本)应大
Java 线程同步
原文: https://javatutorial.net/java-thread-synchronization
本文讨论了 Java 中线程同步的重要性以及如何在程序中实现线程同步。
当您的程序有多个线程时,当不同的线程尝试访问相同的资源或同时执行相同的操作时,可能会出现问题。 这种情况会导致错误和并发问题。 让我们以一个示例为例,您的程序中有两个线程试图读取/写入文本文件。 当两个线程都在写入文件时,一个线程的数据可能会被另一个线程覆盖,并且在读取文件时会发现相同的问题。
因此,线程同步是必需的,以便多个线程在任何给定时间点一次访问一个线程。
为什么要使用线程同步?
- 防止线程干扰
- 避免一致性问题和并发问题
- 防止数据丢失
线程同步有互斥和线程间通信两种类型。
- 互斥
- 同步方法。
- 同步块。
- 静态同步。
- 合作(Java 中的线程间通信)
同步块
同步块用于线程同步。
synchronized
关键字用于标识 Java 中的同步块,并基于某些对象进行同步。 其背后的主要概念是,在同一对象上同步的所有同步块一次只能在其中一个线程内执行,这会阻止多个线程同时运行和执行。 尝试进入同步块的所有其他线程将被阻止,直到同步块内的线程退出该块为止。
共享资源保留在此同步块内,以便在给定时间点只有一个线程可以访问特定资源。
同步块的语法如下所示:
synchronized(referencetoobject) {
// Shared variables and other shared resources are placed here
}
监视器
在尝试理解和实现 Java 同步时,监视器的概念很重要。
- Java 中的每个对象都与一个监视器关联
- 线程可以锁定或解锁监视器
- 在给定时间,只有一个线程可以拥有监视器。
- 一次只能有一个线程可以锁定监视器
线程同步示例
//We write a program with a simple counter execution.
class Countings {
public void printing() {
try {
for(int i = 5; i > 0; i--) {
System.out.println( i );
}
} catch (Exception e) {
System.out.println("Thread interrupted.");
}
}
}
class Threadings extends Thread {
private Thread thrd;
private String thrdName;
Countings gg;
Threadings( String name, Countings abc) {
thrdName = name;
gg = abc;
}
public void run() {
gg.printing();
System.out.println("Thread " + thrdName + " exiting.");
}
public void start () {
System.out.println("Starting " + thrdName );
if (thrd == null) {
thrd = new Thread (this, thrdName);
thrd.start ();
}
}
}
public class Tests {
public static void main(String args[]) {
Countings gg = new Countings();
Countings T1 = new Countings ( "Thread - 1 ", gg );
Countings T2 = new Countings ( "Thread - 2 ", gg );
T1.start();
T2.start();
// threads take some time to end
try {
T1.join();
T2.join();
} catch ( Exception e) {
System.out.println("Interrupted");
}
}
}
输出:(每次运行都会产生不同的结果)
Starting Thread - 1
Starting Thread - 2
5
4
3
5
2
1
4
Thread Thread - 1 exiting.
3
2
1
Thread Thread - 2 exiting.
相同的示例,但是这次具有线程同步:
class Countings {
public void printings() {
try {
for(int i = 5; i > 0; i--) {
System.out.println( i );
}
} catch (Exception e) {
System.out.println("Thread interrupted.");
}
}
}
class Threadings extends Thread {
private Thread t;
private String thrdName;
Countings gg;
Threadings( String name, Countings abc) {
thrdName = name;
gg = abc;
}
public void run() {
synchronized(gg) {
gg.printings();
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + thrdName );
if (thrd == null) {
thrd = new Thread (this, thrdName);
thrd.start ();
}
}
}
public class Testings{
public static void main(String args[]) {
Countings gg = new Countings();
Threadings T1 = new Threadings ( "Thread - 1 ", gg );
Threadings T2 = new Threadings ( "Thread - 2 ", gg );
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
5
4
3
2
1
Thread Thread - 1 exiting.
5
4
3
2
1
Thread Thread - 2 exiting.
Java 线程池示例
原文: https://javatutorial.net/java-thread-pool-example
活动线程消耗系统资源,这可能导致 JVM 创建太多线程,这意味着系统将很快用尽内存。
这就是 Java 中的线程池有助于解决的问题。
线程池如何工作?
线程池将先前创建的线程重用于当前任务。 这就解决了需要太多线程的问题,因此内存不足不是一个选择。 您甚至可以将线程池视为回收系统。 它不仅消除了用尽内存的选项,而且还使应用程序非常快速地响应,因为当请求到达时已经存在一个线程。
上图的工作流不仅可以控制应用程序正在创建的线程数,还可以控制计划任务的执行并将传入的任务保持在队列中。
Executor
,Runnable
和ExecutorService
Java 提供了Executor
框架,这意味着您只需要实现Runnable
对象并将其发送给执行器即可执行。
要使用线程池,首先我们需要创建一个ExecutorService
对象并将任务传递给它。ThreadPoolExecutor
类设置核心和最大池大小。 然后,可运行对象将顺序执行。
不同的Executor
线程池方法
newFixedThreadPool(int size) - creates a fixed size thread pool
newCachedThreadPool() - creates a thread pool that creates new threads if needed but will also use previous threads if they are available
newSingleThreadExecutor() - creates a single thread
ExecutorService
接口包含许多方法,这些方法用于控制任务的进度并管理服务的终止。 您可以使用Future
实例控制任务的执行。 有关如何使用Future
的示例:
ExecutorService execService = Executors.newFixedThreadPool(6);
Future<String> future = execService.submit(() -> "Example");
String result = future.get();
ThreadPoolExecutor
使您可以实现具有许多参数的可扩展线程池,这些参数包括corePoolSize
,maximumPoolSize
,keepAliveTime
,unit
,workQueue
,handler
,threadFactor
。 但是,corePoolSize
,maximumPoolSize
和keepAliveTime
是主要变量,因为它们在每个构造函数中都使用。
corePoolSize
是要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
。
maximumPoolSize
是池中允许的最大线程数。
keepAliveTime
是当线程数大于内核数时,这是多余的空闲线程将在终止之前等待新任务的最长时间。
有关其他参数的更多信息,请访问原始 Oracle 文档。
线程池实现示例
工作流程步骤:
- 创建要执行的任务
- 使用执行程序创建执行程序池
- 将任务传递给执行程序池
- 关闭执行程序池
Task.java
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// (Step 1)
public class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
public void run() {
try {
for (int i = 0; i < 5; i++) {
if (i == 1) {
Date date = new Date();
SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
System.out.println("Time initialization for task " + this.name + " is " + ft.format(date));
}
else {
Date date = new Date();
SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
System.out.println("Execution time for task " + this.name + " is " + ft.format(date));
}
Thread.sleep(1000);
}
}
catch(InterruptedException error) {
error.printStackTrace();
}
System.out.println(this.name + " completed");
}
}
Main.java
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
Runnable task1 = new Task("task 1");
Runnable task2 = new Task("task 2");
Runnable task3 = new Task("task 3");
Runnable task4 = new Task("task 4");
Runnable task5 = new Task("task 5");
// (Step 2)
ExecutorService pool = Executors.newFixedThreadPool(3);
// (Step 3)
pool.execute(task1);
pool.execute(task2);
pool.execute(task3);
pool.execute(task4);
pool.execute(task5);
// (Step 4)
pool.shutdown();
}
}
输出:
Time initialization for task task 2 is 10:18:40
Time initialization for task task 1 is 10:18:40
Time initialization for task task 3 is 10:18:40
Execution time for task task 3 is 10:18:41
Execution time for task task 1 is 10:18:41
Execution time for task task 2 is 10:18:41
Execution time for task task 2 is 10:18:42
Execution time for task task 3 is 10:18:42
Execution time for task task 1 is 10:18:42
Execution time for task task 1 is 10:18:43
Execution time for task task 3 is 10:18:43
Execution time for task task 2 is 10:18:43
Execution time for task task 3 is 10:18:44
Execution time for task task 1 is 10:18:44
Execution time for task task 2 is 10:18:44
task 2 completed
task 1 completed
task 3 completed
Time initialization for task task 4 is 10:18:45
Time initialization for task task 5 is 10:18:45
Execution time for task task 4 is 10:18:46
Execution time for task task 5 is 10:18:46
Execution time for task task 4 is 10:18:47
Execution time for task task 5 is 10:18:47
Execution time for task task 5 is 10:18:48
Execution time for task task 4 is 10:18:48
Execution time for task task 4 is 10:18:49
Execution time for task task 5 is 10:18:49
task 4 completed
task 5 completed
上面的代码实现的细分:
Task.java
表示任务类。 每个任务都有一个名称实例变量,并且每个任务都使用构造函数实例化。 此类有 1 个方法,称为run
。 在run
方法的主体内,有一个for
循环,该循环根据存在的任务数进行迭代。 在我们的例子中,有 5 个任务,这意味着它将运行 5 次。 第一次迭代,显示当前任务初始化的时间。 其他迭代,打印执行时间。 打印完成后,有一个Thread.sleep()
方法调用,该方法用于以 1 秒的延迟显示每个迭代消息。 注意,像这样调用的方法名称run
非常重要,因为它是来自Task
类正在实现的Runnable
的抽象方法。
仅在池中的某个胎面变得空闲时才执行任务 4 和 5。 在此之前,额外的任务将放置在队列中。
执行完所有任务后,请关闭线程池。
线程池何时有用
组织服务器应用程序时。 如本文开头所述,在组织服务器应用程序时非常有用,因为使用线程池非常有效,就像有许多任务一样,它会自动将它们放入队列中。 不仅如此,它还可以防止内存不足,或者至少可以显着减慢这样做的速度。 使用ExecutorService
使其更易于实现。
Java ThreadLocal
示例
原文: https://javatutorial.net/threadlocal-java-example
ThreadLocal
是提供线程局部变量的类,用于实现线程安全。 存储的数据只能由特定线程访问。
ThreadLocal
扩展了Object
类,并提供了线程限制,它是局部变量的“一部分”。
创建ThreadLocal
变量
ThreadLocal threadLocalExample = new ThreadLocal();
上面代码中ThreadLocal
对象的实例化仅需要针对每个线程进行。
就像大多数类一样,一旦有了ThreadLocal
的实例,就可以在其上调用方法。 一些方法是:
get()
:返回此线程局部变量的当前线程副本中的值initialValue()
:返回当前线程局部变量的当前线程初始值remove()
:从当前线程中删除当前线程局部变量的值set(T value)
:将当前线程局部变量的当前线程副本设置为指定值
有关这些方法的更多详细信息,请访问原始 Oracle 文档。
ThreadLocal
实例是希望将状态与线程相关联的类中的私有静态字段(在大多数情况下)
实现示例
public class ThreadLocalExample {
public static class Example implements Runnable {
private ThreadLocal<String> example = new ThreadLocal<String>();
// override the run() method that comes from implementing Runnable class
@Override
public void run() {
try {
System.out.println("Getting values...");
Thread.sleep(2000);
}
catch (InterruptedException e) {
System.out.println(e);
}
example.set("Just a random text that will be displayed before the remove function");
System.out.println("Before remove: " + example.get());
example.remove();
System.out.println("After remove: " + example.get());
}
}
public static void main(String[] args) {
/* EXAMPLE THAT DOES NOT HAVE TO DO ANYTHING WITH THE STATIC CLASS ABOVE main*/
ThreadLocal<String> local = new ThreadLocal<String>();
local.set("First");
System.out.println("Value: " + local.get());
local.set("Second");
System.out.println("Value: " + local.get());
local.remove();
System.out.println("Value: " + local.get());
/* NEW EXAMPLE THAT USES THE STATIC CLASS DECLARED ABOVE main */
Example runnable = new Example();
Thread thread = new Thread(runnable);
thread.start();
}
}
输出
Value: First
Value: Second
Value: null
Getting values...
Before remove: Just a random text that will be displayed before the remove function
After remove: null
分解
上面的代码显示了两种使它起作用的方法:一种是通过拥有Runnable
对象并将其传递给Thread
实例,然后覆盖run()
方法,或者您可以简单地创建一个ThreadLocal
实例并为其设置值,然后 可以获取或删除它。 从上面的示例中可以看到,即使它是相同的变量(局部变量),也可以包含不同的值。 在第一种情况下,它包含值"First"
。 在第二种情况下,它包含值"Second"
。 对于其他实现,我只显示了一个线程。 但是,每个线程都是独立的–意味着,如果您要创建另一个Thread
实例(例如thread2
),并对其进行start()
,它将独立运行,并且与其他Thread
实例变量无关。 要进行检查,可以在静态类中创建一个ThreadLocal
实例,然后在重写的run()
方法中创建一个随机数,然后使用set()
方法将其传递给当前线程。 您将看到,如果您在两个或多个不同的线程上调用它,则它们都将具有不同的值。
Java 中的活锁和死锁
原文: https://javatutorial.net/livelock-and-deadlock-in-java
以下文章讨论了 Java 中的活锁和死锁状态,它们如何发生以及如何避免它们。
活锁
Java 中的活锁是一种递归条件,其中两个或多个线程不断重复一段特定的代码。
当一个线程不断响应另一个线程并且另一个线程也执行相同操作时,就会发生活锁。
要对其进行分解,我们可以总结以下几点:
- 一个线程响应另一个线程的行为而运行,而另一个线程也响应先前的线程而运行,则可能发生活锁。
- 活锁线程无法继续进行。
- 线程没有被阻塞; 他们只是忙于互相回应。
活锁也被称为资源匮乏的特例
让我们通过将其与现实世界联系起来来理解该概念。 考虑两辆车在狭窄桥梁的相对两侧。 一次只能乘一辆车通过桥。 这两辆车的驾驶员都很有礼貌,正在等待对方先通过桥。 他们互相鸣叫,让他们知道他们想让对方先通过。 但是,两者都没有越过桥梁并互相鸣喇叭。 这种情况类似于活锁。
现在,通过一些编码来尝试这种实际情况:
第一辆等待过桥的汽车的等级:
public class Car1 {
private boolean honking = true;
public void passBridge(Car2 car2) {
while (car2.hasPassedBridge()) {
System.out.println("Car1 waiting to pass the bridge");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println("Passed bridge");
this.honking= false;
}
public boolean hasPassedBridge() {
return this.honking;
}
}
等待通过桥的第二辆车的类别:
public class Car2 {
private boolean honking = true;
public void passBridge(Car1 car1) {
while (car1.hasPassedBridge()) {
System.out.println("Car 2 is waiting to pass the bridge!");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println("Car 2 has passed the bridge!");
this.honking = false;
}
public boolean hasPassedBridge() {
return this.honking;
}
}
主要测试类别:
public class BridgeCheck {
static final Car2 car2 = new Car2();
static final Car1 car1 = new Car1();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
public void run() {
car2.passBridge(car1);
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
public void run() {
car1.passBridge(car2);
}
});
t2.start();
}
}
输出:
这导致了非终止循环。
死锁
死锁与活锁有些不同。 死锁是一种状态,其中每个成员都在等待其他成员释放锁。
可能存在以下情况:一个线程正在等待另一个线程获取的对象锁,而第二个线程正在等待第一个线程获取的对象锁。 由于两个线程都在互相等待释放锁,因此这种情况称为死锁。
图:死锁状态
让我们看一下发生死锁的情况:
public class DeadlockExample{
private static String A = "Something A";
private static String B = "Something B";
public void someFunction(){
synchronized(A){//may deadlock here
synchronized(B){
// function does some work here
}
}
}
public void someOtherFunction(){
synchronized(B){//may deadlock here
synchronized(A){
// the function does something here
}
}
}
}
考虑两个线程 T1 和 T2,T1 获取 A 并等待 B 完成其功能。 但是,T2 获取 B 并等待 A 完成其自身的功能。 在这里,T1 和 T2 正在等待被其他线程锁定的资源。 因此,这是一个僵局方案。
避免死锁:
- 第一个建议是避免同时使用多线程,但这在许多情况下可能不切实际。 因此,这种解决方案不是很明智。
- 分析并确保在事先访问资源时没有锁。
- 在上面的编码示例中,为避免死锁,只需管理访问资源的顺序(访问 A 和 B 的顺序)。
- 避免一次持有多个锁,以防万一您必须始终以相同的顺序获得这些锁。
- 避免在持有锁的同时执行外来代码。
- 尝试使用可中断的锁,以便即使遇到死锁,也可以中断这些锁,并且可以毫无问题地执行该过程。
Java Future
示例
原文: https://javatutorial.net/java-future-example
异步计算的结果称为Future
,更具体地说,它具有此名称,因为它(结果)将在将来的中稍后的时间点完成。 创建异步任务后,将创建将来的对象。 提供了许多方法来帮助检索结果(使用get
方法(这是唯一的检索方法)),使用cancel
方法进行取消以及检查是否检查结果的方法。 任务成功完成或被取消。
Future
的主要优点在于,它使我们能够同时执行其他进程,同时等待Future
中嵌入的主要任务完成。 这意味着当我们面对大型计算时,使用Future
是个好主意。
方法
boolean cancel(boolean mayInterruptIfRunning)
V get()
V get(long timeout, TimeUnit unit)
- 这与上面的 get 方法不同,因为在此方法中,指定了超时作为等待条件
boolean isCancelled()
boolean isDone()
使用get(timeout, unit)
的Future
的基本实现
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class FutureDemo
{
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
Thread.sleep(5000);
return "Done";
});
try {
while(!future.isDone()) {
System.out.println("Task completion in progress...");
Thread.sleep(500);
}
System.out.println("Task completed!");
String result = future.get(3000, TimeUnit.MILLISECONDS); // that's the future result
System.out.println(result);
executorService.shutdown();
}
catch (InterruptedException e) {
}
catch (ExecutionException e) {
}
catch (TimeoutException e) { // this will be thrown if the task has not been completed within the specified time
future.cancel(true); // if this task has not started when cancel is called, this task should never run
future.isDone(); // will return true
future.isCancelled(); // will return true
}
}
}
输出:
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completion in progress...
Task completed!
Done
细分
在ExecutorService
上调用commit()
方法,返回Future
。 现在有了Future
,我们可以调用上面的方法了。 但是,您应该特别注意的是,它在提交的任务正在“运行”时打印“正在完成任务…”。
如果任务没有在指定的时间内完成(如注释所示),将抛出TimeoutException catch
。
Java equals()
方法示例
原文: https://javatutorial.net/java-equals-method-example
Java equals()
方法和==
运算符都用于比较对象是否相等。 但是,他们以非常不同的方式进行检查,从而产生不同的结果。
它们之间的主要区别是==
检查两个对象是否都指向相同的内存位置,并且equals()
求和对象中包含的实际值的比较。
一个示例将为您提供更多线索:
Animal.java
public class Animal {
private String name;
private int age;
public Animal (String n, int a) {
this.name = n;
this.age = a;
}
}
EqualsDemo.java
public class EqualsDemo {
public static void main(String[] args) {
Animal animal1 = new Animal("Vic", 4);
Animal animal2 = new Animal("Vic", 4);
if (animal1 == animal2)
System.out.println("These objects are equal.");
else
System.out.println("These objects are not equal.");
}
}
您认为会打印出什么?
输出:
These objects are not equal.
即使两个对象都是同一个类的实例并包含相同的值,但它们并不引用相同的对象。 每当您键入new
的关键字时,它都会自动创建一个新的对象引用。 当我们使用new
关键字创建两个对象时,即使它们包含相同的值,它们也不相同。 它们指向不同的存储位置。
使用equals()
方法和==
运算符比较字符串
EqualsDemo.java
public class EqualsDemo {
public static void main(String[] args) {
String str1 = "First string";
String str2 = "First string";
if (str1 == str2)
System.out.println("Equal");
else
System.out.println("Not equal");
}
}
您认为将在屏幕上显示什么?
输出:
Equal
如果您说“等于”,那么您将是正确的。 当字符串包含相同的内容
Not equal
它们指向相同的存储位置。
现在让我们做与上面完全相同的示例,但改用new
关键字。
使用new
关键字创建相同内容的字符串
EqualsDemo.java
public class EqualsDemo {
public static void main(String[] args) {
String str1 = new String("First string");
String str2 = new String("First string");
if (str1 == str2)
System.out.println("Equal");
else
System.out.println("Not equal");
}
}
您认为现在将打印什么?
输出:
Not equal
如我上面指出的,打印不等于的原因是因为,当您使用new
关键字创建对象时,会创建一个指向其自身存储位置的新指针。
这是一个直观的示例。 内存位置刚刚组成。 但是从示例中可以看到,当创建str1
和str2
时,它们指向不同的内存位置。 因此,当您使用==
运算符比较它们时,无论如何,您都会得到假。
覆盖equals()
方法以符合条件
假设您要在两个对象上调用.equals()
,并且如果它们包含相同的名称和年龄,则应返回true
。
Animal.java
public class Animal {
private String name;
private int age;
public Animal (String n, int a) {
this.name = n;
this.age = a;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public boolean equals(Animal a) {
if (this.name.equals(a.getName()) && this.age == a.getAge())
return true;
else
return false;
}
}
EqualsDemo.java
public class EqualsDemo {
public static void main(String[] args) {
Animal animal1 = new Animal("Vic", 4);
Animal animal2 = new Animal("Vic", 4);
if (animal1.equals(animal2))
System.out.println("These objects are equal.");
else
System.out.println("Not equal");
}
}
输出:
These objects are equal.
我们在Animal
类中重写equals
方法,因此它符合我们自己的条件。 如果我们不重写它,而只是对两个对象调用equals
方法,它将不会返回true
。
在字符串上调用equals()
EqualsDemo.java
public class EqualsDemo {
public static void main(String[] args) {
String str1 = "str";
String str2 = "str";
if(str1.equals(str2)) {
System.out.println("equal");
}
else {
System.out.println("not equal");
}
}
}
输出
equal
在字符串上调用equals()
时,它将检查每个字符是否在两个字符串中都相同。 意思是,比较字符串时应始终使用equals
,而不是==
。
对使用new
关键字创建的字符串的调用equals
EqualsDemo.java
public class EqualsDemo {
public static void main(String[] args) {
String str1 = new String("str");
String str2 = new String("str");
if(str1.equals(str2)) {
System.out.println("equal");
}
else {
System.out.println("not equal");
}
}
}
输出:
equal
如您所见,使用equals
时两个对象(字符串)是否指向不同的存储位置并不重要。 如果两个字符串中的内容相同,则返回true
。
结论
比较字符串时,应始终使用.equals()
而不是==
。
Java Lambda 表达式教程
原文: https://javatutorial.net/java-lambda-expressions-tutorial
Java 8 引入了 Lambda 表达式,它是引入的最大(甚至不是最大)功能之一,因为 Lambda 表达式使功能编程成为可能,并且使您的代码更整洁,并从整体上极大地简化了整个代码的实现。
例如,当您必须使用匿名类并且该类非常简单(例如仅包含几个(或更少)方法)时,您会遇到非常不清楚且难以阅读/维护的语法。 这是 Lambda 表达式发挥作用的时候。
让我们比较一下使用 Lambda 表达式和不使用 Lambda 表达式
假设有一个类,该类具有默认情况下按升序对元素进行排序的方法(例如Comparator
)。 此类使用compare
来对元素进行排序。 让我们覆盖它。
不使用 Lambda 表达式
Comparator<Double> sorter = new Comparator<Double>() {
@Override
public int compare(Double x, Double y) {
return y.compareTo(x);
}
};
使用 Lambda 表达式
Comparator<Double> lambdaComparator = (x, y) -> y.compareTo(x);
我相信您会注意到两种方法之间的区别。 两者都导致同一件事,这颠倒了该方法对元素进行排序的顺序(从升序到降序,对您的比较器开玩笑!)。
因此,仅通过查看此示例,使用 lambda 表达式的一个关键优势就是可以减少键入的代码。 对于我们懒惰的程序员来说,这是一个很好的例子。 但是,人们可能会争辩说,由于这种简单性,实际上使记住语法变得更加困难。 没错,这将是一个缺点。 最初,语法可能有点难以记住,但是一旦您记住它,就可以编写简洁的代码,该代码也允许您实现Functional
接口。
让我们看看 Lambda 表达式的更多示例。
首先,如我之前所说,语法起初有点令人生畏。
如果您要“覆盖”的函数没有任何参数,则只需键入() -> {body}
。 另一方面,如果只有一个参数,则键入x -> {body}
。 对于两个或更多参数,您将具有(x, y, z -> {body}
。
循环播放
import java.util.*;
public class LambdaExpressionDemo {
public static void main(String[] args) {
List<int> listOfNumbers = new ArrayList<int>();
listOfNumbers.add(1);
listOfNumbers.add(2);
listOfNumbers.add(3);
listOfNumbers.add(4);
listOfNumbers.add(5);
listOfNumbers.add(6);
listOfNumbers.add(7);
listOfNumbers.add(8);
listOfNumbers.forEach(
(num) -> System.out.println(num)
);
}
}
注意,在forEach
循环的主体中,我省略了{}
花括号。 这是因为主体仅包含一行。 如果我有两行或更多行,假设另一个打印语句,那么我将拥有大括号,如下所示:
import java.util.*;
public class LambdaExpressionDemo {
public static void main(String[] args) {
List<int> listOfNumbers = new ArrayList<int>();
listOfNumbers.add(1);
listOfNumbers.add(2);
listOfNumbers.add(3);
listOfNumbers.add(4);
listOfNumbers.add(5);
listOfNumbers.add(6);
listOfNumbers.add(7);
listOfNumbers.add(8);
listOfNumbers.forEach(
(num) -> {
System.out.println(num);
System.out.println(num);
);
}
}
从 Lambda 表达式返回值
您也可以像使用方法一样使用 lambda 表达式返回值。 这里要注意的一个关键是return
关键字是可选的。 您可以忽略它,编译器知道它的工作。
语法类似于我们之前使用的语法。
(x, y) -> {
return x > y;
}
因此,如果我们再次查看 Lambda 表达式提供的内容,我们可以得出结论,它为我们提供了可选的类型声明(无需声明参数的类型),可选的花括号,可选的return
关键字(因为编译器自动返回值) (如果主体具有返回值的符号表达式)和参数周围的可选括号。
Java Optional
示例
原文: https://javatutorial.net/java-optional-example
Java 8 引入了Optional
类,该类用于根据值是否存在来操纵数据。 您可以不使用此类而具有相同的功能,但是最终会产生混乱的代码。 换句话说,您的空检查将更少,而NullPointerException
则不会。
让我举一个例子,说明为什么不使用Optional
会导致问题。
未使用Optional
public class NonOptionalImplementation {
public static void main(String[] args) {
Animal dog = new Animal(null, 5);
String dogModified = dog.getName().toLowerCase();
System.out.println(dogModified);
}
}
class Animal {
private String name;
private int age;
Animal(String n, int a) {
this.name = n;
this.age = a;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
您能发现哪里会出问题吗? 显然,当我们到达print
语句时,由于对象名称被故意设置为null
,它将抛出NullPointerException
。
输出
Exception in thread "main" java.lang.NullPointerException
at OptionalImplementation.main(OptionalImplementation.java:6)
使用Optional
解决了此问题
import java.util.Optional;
import java.util.*;
public class OptionalImplementation {
public static void main(String[] args) {
Animal dog = new Animal(null, 5);
System.out.println(Optional.ofNullable(dog.getName()).orElse("Name not provided"));
}
}
class Animal {
private String name;
private int age;
Animal(String n, int a) {
this.name = n;
this.age = a;
}
public String getName() {
return name;
}
public int getAge() {
return this.age;
}
}
输出
Name not provided
上述实现的简要拆分
- 我们正在创建一个非常简单的类,称为
Animal
,我们有 2 个具有构造函数的 getter 方法 - 在我们的主方法中,我们正在创建该
Animal
类的实例,但是我们还将第一个参数(恰好是名称)分配给null
Optional.ofNullable
检查传递的字符串是否为null
- 之后,我们尝试打印值
- 我们有这个
.orElse()
方法调用,这基本上意味着:如果您要在其上调用该方法的字符串为 null,那么我将使用给出的参数进行打印 - 如果不为空,那么我将打印原始字符串
- 换句话说,如果字符串为
null
,它将打印默认值
是不是很酷? 只要看看我们检查空值时代码的外观如何优雅即可。 而且安全!
为了进行比较,让我们尝试不使用Optional
类而获得相同的结果。
public class OptionalImplementation {
public static void main(String[] args) {
Animal dog = new Animal(null, 5);
if (dog.getName() == null) {
System.out.println("Name not provided");
}
else {
System.out.println(dog.getName().toLowerCase());
}
}
}
class Animal {
private String name;
private int age;
Animal(String n, int a) {
this.name = n;
this.age = a;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
输出
Name not provided
它有效..但是干净吗? 我不这么认为!
您也可以使用isPresent()
方法检查对象中是否存在值:
import java.util.Optional;
public class OptionalImplementation {
public static void main(String[] args) {
Optional<Object> n = Optional.empty(); // creating an empty string
System.out.println(n.isPresent());
System.out.println(n);
}
}
输出
false
Optional.empty
而已。 不是那么难,不是吗? 甚至它都不是很容易理解,但是它也大大简化了您的代码,并且也非常易于阅读和维护。 如果要浏览Optional
提供的所有方法,请查看官方 Oracle 文档。
Java 11 HTTP 客户端示例
原文: https://javatutorial.net/java-11-http-client-example
Java 11 引入了 HTTP 客户端,该客户端可用于通过网络发送请求并检索其响应。 HTTP 客户端取代了旧的HttpUrlConnection
类,并且不支持易用性。 HTTP 客户端 API 同时支持 HTTP/1.1 和 HTTP/2。
HttpClient
也是不可变的,这意味着它可以用于发送多个请求。
每个HttpRequest
必须提供一个BodyHandler
,并且其(BodyHandler
)函数用于确定如何处理响应(如果有的话)。
可以同步或异步发送请求
send(HttpRequest, BodyHandler)
阻塞,直到发送了请求并接收到响应为止sendAsync(HttpRequest, BodyHandler)
发送请求并同时接收响应(异步)。 此方法返回CompletableFuture
,仅当响应可用时才完成。
HttpResponse.BodyHandler
- 允许在收到实际的响应主体之前检查响应代码
HttpResponse.BodyHandlers
存在BodyHandlers
的唯一目的是处理响应主体类型。 一些例子:
BodyHandlers.ofByteArray()
BodyHandlers.ofString()
BodyHandlers.ofFile()
BodyHandlers.ofInputStream()
- 等等
数据作为响应流
HttpClient
实际上是请求正文的订阅者和响应正文字节的发布者- 请求和响应主体作为响应流(具有无阻塞背压的数据流)公开
HttpRequest.BodyPublisher
- 将 Java 对象转换为适合作为请求正文发送的字节缓冲区
HttpRequest.BodyPublishers
BodyPublishers::ofByteArray(byte[])
BodyPublishers::ofFIle(Path)
BodyPublishers::ofString(String)
- 等等
HttpRequest.BodySubscriber
- 使用响应主体字节并将其转换为 Java 类型
顺便说一句,让我们看看一些实现。 首先,我们将发送 GET 请求:
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://javatutorial.net/"))
.build();
现在,让我们发送一个 HTTP 请求。 我将向您展示同步和异步示例:
同步(在HttpResponse
可用之前将一直阻塞)GET
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpClient.newBuilder()
.uri(URI.create("https://javatutorial.net/")
.version(Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(20))
.authenticator(Authenticator.getDefault())
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
异步 GET
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://javatutorial.net/"))
.connectTimeout(Duration.ofSeconds(20))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
POST
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://javatutorial.net/"))
.POST(BodyPublishers.ofString(data))
.build();
HttpResponse<?> response = client.send(request, discarding());
总结
HTTP 客户端的角色是替换URLConnection
API,并且在 Java 11 中作为 Java SE 平台的一部分进行了标准化,并且位于java.net.http
包中。 一个主要优点是它使用了现代 Java 语言以及 API 功能。
Java 类加载器介绍
原文: https://javatutorial.net/java-class-loaders-explained
本文介绍了 Java 类加载器的关键组件。
Java 类加载器是 Java 虚拟机(JVM)的重要组成部分。 它们用于加载类和接口。 Althaout 类装入器是 JVM 不可或缺的一部分,它们对于 Java 的内部工作非常重要,因此您在作为 Java 开发人员的日常工作中不太可能需要创建自定义类装入器。 例如,如果要创建将在诸如 Tomcat 之类的容器上执行的应用程序,则可以使用自定义类加载器的实际应用程序。 Tomcat 要做的是为每个 Web 应用程序创建一个类加载器(以便它以后可以卸载 Web 应用程序并释放内存)。
本文旨在解释类加载器的工作方式,并列出 Java 类加载器的关键组件。 您下次 Java 面试时可能会遇到有关类加载器的问题。
什么是 Java 类加载器
我们知道 Java 程序在 Java 虚拟机(JVM)上运行。 当我们编译 Java 类时,它将转换为平台和机器无关的字节码。 编译的类存储为.class
文件。 当我们尝试使用类时,Java 类加载器将该类加载到内存中。 在已经运行的类中通过名称引用类时,这些类将引入 Java 环境。 一旦第一个类运行,以后将由类加载器完成加载类的尝试。 通常,通过声明并使用静态main()
方法来完成第一类的运行。
类加载器的层次结构
Java 类加载器的类型
- 自举类加载器 - 它加载 JDK 内部类,通常加载
rt.jar
和其他核心类,例如java.lang.*
包类 - 扩展类加载器 - 它从 JDK 扩展目录(通常是 JRE 的
lib / ext
目录)加载类。 - 系统类加载器 - 从系统类路径加载类,可以在使用
-cp
或-classpath
命令行选项调用程序时进行设置。
何时以及如何加载类
何时加载类? 确实有两种情况:
- 当执行新的字节码时(例如,
MyClass mc = new MyClass()
😉 - 当字节码静态引用一个类时(例如
System.out
)。
类加载器是分层的。 第一个类是在类中声明的静态main()
方法的帮助下专门加载的。 所有随后加载的类均由已加载并正在运行的类加载。
进一步的类加载器在加载类时遵循以下规则:
- 检查该类是否已经加载。
- 如果未加载,请要求父类加载器加载该类。
- 如果父类加载器无法加载类,请尝试在该类加载器中加载它。
静态与动态类加载
使用 Java 的new
运算符静态加载类。 动态加载是一种使用Class.forName()
在运行时以编程方式调用类加载器的功能的技术。
loadClass
和Class.forName
之间的区别
loadClass
仅加载类,但不初始化对象,而Class.forName
在加载对象后初始化对象。 例如,如果您使用ClassLoader.loadClass
加载 JDBC 驱动程序,则该驱动程序将无法注册,并且您将无法使用 JDBC
java.lang.Class.forName(String className)
方法返回与具有给定字符串名称的类或接口关联的Class
对象。 如果找不到该类,则此方法引发ClassNotFoundException
下面的示例演示Class.forName
的用法
package net.javatutorial;
import java.lang.reflect.Method;
public class ClassForNameExample {
public static void main(String[] args) {
try {
Class<?> c = Class.forName("java.awt.Point");
System.out.println("name = " + c.getName());
System.out.println("package = " + c.getPackage());
Method[] methods = c.getDeclaredMethods();
System.out.println("----- Class methods ---------------");
for (Method method : methods) {
System.out.println(method.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
加载java.awt.Point
类。 比我们打印类名称,包和该类所有可用方法的名称。 这是在 Java 8 中执行程序的结果:
name = java.awt.Point
package = package java.awt, Java Platform API Specification, version 1.8
----- Class methods ---------------
equals
toString
getLocation
getX
getY
setLocation
setLocation
setLocation
move
translate
Java 枚举示例
原文: https://javatutorial.net/java-enum-example
枚举类型是一种特殊的数据类型,它具有不同的常量,例如WHITE
,BLACK
,RED
。 约定是,它们应以大写字母命名,因为它们又是常量。 在 Java 中,您可以使用enum
关键字定义枚举类型。
public enum Macronutrients {
FATS, CARBOHYDRATES, PROTEIN
}
如果您知道编译时程序的所有可能常量,则应以枚举类型表示它们。
Java 在 1.5 中引入了 enum 数据类型。
其他编程语言(例如 C++ 甚至 C)也具有枚举数据类型,但是在 Java 中,它更强大。 例如,在 C/C++ 中,枚举只是整数值的列表,而在 Java 中,它是扩展Enum
的类本身,并且通常对读取和写入都更好。 最重要的是,由于枚举是一个类的事实,它还提供了允许在枚举成员上进行迭代的不同方法。
在 Java 中,您还可以使用name()
方法,该方法将为您提供枚举数据类型的成员值:
public class Main {
public enum Macronutrients {
FAT, CARBOHYDRATES, PROTEIN;
}
public static void main(String[] args) {
Macronutrients m = Macronutrients.PROTEIN;
System.out.println(m.name()); // PROTEIN
}
}
输出
PROTEIN
Java 中的枚举还为我们提供了灵活性–可以在类内部或外部声明它们。
在类之外声明的枚举:
enum Macronutrients {
FAT, CARBOHYDRATES, PROTEIN, NONE
}
public class Main {
public static void main(String[] args) {
Macronutrients m = Macronutrients.NONE;
System.out.println(m.name()); // PROTEIN
}
}
输出
NONE
在类内声明的枚举:
public class Main {
public enum Macronutrients {
FAT, CARBOHYDRATES, PROTEIN, NONE
}
public static void main(String[] args) {
Macronutrients m = Macronutrients.NONE;
System.out.println(m.name()); // PROTEIN
}
}
输出
NONE
每个枚举都是枚举类型的对象。 还记得我说的枚举赋予我们灵活性吗? 好吧,它也可以作为参数传递给switch
语句。
使用switch
语句的示例
enum Macronutrients
{
FAT, CARBOHYDRATES, PROTEIN, NONE
}
public class Main
{
Macronutrients macro;
public Main(Macronutrients macro)
{
this.macro = macro;
}
public void whichMacro()
{
switch (macro)
{
case FAT:
System.out.println("You've chosen FAT. A bit unhealthy, if it is the bad type of it.");
break;
case CARBOHYDRATES:
System.out.println("You've chosen CARBOHYDRATES. Let's hope it's not sugars, right?");
break;
case PROTEIN:
System.out.println("You've chosen PROTEIN. Smart decision.");
break;
default:
System.out.println("You have not chosen anything. You must be starving..");
break;
}
}
public static void main(String[] args)
{
Macronutrients carbs = Macronutrients.CARBOHYDRATES;
Macronutrients fats = Macronutrients.FAT;
Macronutrients protein = Macronutrients.PROTEIN;
Macronutrients nothing = Macronutrients.NONE;
Main instance1 = new Main(carbs);
instance1.whichMacro();
Main instance2 = new Main(fats);
instance2.whichMacro();
Main instance3 = new Main(protein);
instance3.whichMacro();
Main instance4 = new Main(nothing);
instance4.whichMacro();
}
}
输出
You've chosen CARBOHYDRATES. Let's hope it's not sugars, right?
You've chosen FAT. A bit unhealthy, if it is the bad type of it.
You've chosen PROTEIN. Smart decision.
You have not chosen anything. You must be starving..
要记住的关键事项:
- 由于枚举是公共静态最终,这意味着可以使用枚举名称来访问它,而且由于它是最终的,因此我们无法创建该枚举的子代。
类和枚举之间到底有什么区别? 这是您可能会遇到的一个问题,它将是完全有效的!
类和枚举之间的主要区别
- 枚举扩展了
java.land.Enum
,并为它提供了人类可读的.toString
方法,.name
和.ordinal
方法等。 - 枚举可用于
switch
语句 - 枚举构造函数为我们提供了一些额外的支持方法,例如
values()
,valueOf()
等,这些方法被证明确实有用。
Java hashCode()
方法示例
原文: https://javatutorial.net/java-hashcode-method-example
Java java.lang.Object
中的超类提供了两种比较对象的重要方法:equals()
和hashcode()
。当面对实现类之间的交互时,这些方法被广泛使用。 在本教程中,我们仅看一下hashCode()
。
方法定义与实现
hashCode()
:默认情况下,此方法返回每次都是唯一的随机整数。 例如,如果第二次执行应用程序,则该值将不同。hashCode
值主要用于哈希格式的集合中,例如 HashSet,HashMap
等。请注意,在覆盖equals()
方法的每个类中都必须覆盖此方法。
hashCode
的简单说明
hashCode()
可以使用的集合列表
HashSet
TreeSet
LinkedHashSet
CopyOnWriteArraySet
什么时候应该使用hashCode()
方法
如果我们想执行equals()
方法,则需要确保这些对象具有相同的唯一哈希码 ID。当哈希码 ID 不同时,我们永远不要执行equals()
。
注意:当hashCode()
比较返回假时,equals()
方法还必须返回假。 如果哈希码不同,则对象不等。
hashCode()
和equals()
的实际示例
import java.lang.*;
public class hashCodeExample {
public static void main(String[] args){
Car BMW = new Car(1, "BMW"); // 1 --> ID, 2 --> the name of the car brand
Car mercedes = new Car(2, "Mercedes"); // 1 --> ID, 2 --> the name of the car brand
boolean isHashcodeEqual = BMW.hashCode() == mercedes.hashCode();
if (isHashcodeEqual) {
System.out.println("Equal");
} else {
System.out.println("No need to compare with equals() method as it is clear " +
"that the id of these objects is not equal.");
}
}
static class Car {
int id;
String brand;
public Car (int id, String brand) {
this.id = id;
this.brand = brand;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
else if(obj == null || getClass() != obj.getClass()) {
return false;
}
Car car = (Car) obj;
return id == car.id && brand.equals(car.brand);
}
@Override
public int hashCode() {
return id;
}
}
}
上面代码的简要分解
在前几行中,我们将创建两个Car
对象,并传递一个id
和一个品牌名称。
Car BMW = new Car(1, "BMW"); // 1 --> ID, 2 --> the name of the car brand
Car mercedes = new Car(2, "Mercedes"); // 1 --> ID, 2 --> the name of the car brand
然后,我们将一个布尔值存储在名为isHashCodeEqual
的变量中,该变量根据两个对象的 ID 是否相等而为true
或false
。
boolean isHashcodeEqual = BMW.hashCode() == mercedes.hashCode();
在那之后,我们有一个条件可以检查isHashCodeEqual
是true
还是false
。 如果为true
,则表示两个对象的 ID 相等。 如果不是,则意味着相反。 如果它们相等,我们只需打印“相等”。 如果不是,我们将打印一条有用的消息,该消息基本上会说,如果它们不相等,则无需使用相等进行检查,因为这两个对象不共享相同的 ID。
if (isHashcodeEqual) {
System.out.println("Equal");
} else {
System.out.println("No need to compare with equals() method as it is clear " +
"that the id of these objects is not equal.");
}
接下来是我们的静态类Car
。
static class Car {
int id;
String brand;
public Car (int id, String brand) {
this.id = id;
this.brand = brand;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
else if(obj == null || getClass() != obj.getClass()) {
return false;
}
Car audi = (Car) obj;
return id == audi.id && brand.equals(audi.brand);
}
@Override
public int hashCode() {
return id;
}
}
hashCode()
的常见错误
- 在
hashCode()
方法中返回一个常量值,而不是为每个对象返回唯一的值。 - 使用
HashMap
之类的哈希集合时,不覆盖equals()
和hashCode()
。 - 忘记使用
equals()
方法或其他方法覆盖hashCode()
。
有关hashCode()
的重要注意事项
- 使用有效的算法,以便生成唯一的哈希码
- 覆盖
equals()
方法时,请始终确保也覆盖了hashCode()
方法。 - 如果比较两个对象的哈希码的结果为
false
,则equals()
方法也应为false
。 (请参见上面的代码示例) - 如果在起诉哈希集合时没有覆盖
equals()
和hashCode()
,则该集合将具有重复项。
如何测试独立的 Java 应用程序
原文: https://javatutorial.net/how-to-profile-standalone-java-applications
什么是单元测试?
单元测试是一种软件测试方法,其中正在测试 Java 应用程序的小组件。 其目的是确认每个软件的行为均符合预期。 即使是这样,您也可以使用单元测试来确定另一种实现是否在内存和性能方面会带来更好的结果。 如果您对单元测试不是很熟悉,建议您参考本主题的本教程。
什么是性能分析?
性能分析会检查应用程序,并尝试查找与 Java 应用程序相关的内存或性能问题。它允许您执行的操作是通过监视 JVM(Java)来获取有关性能,方法时序,对象分配等的数据。 虚拟机)。
您可以使用 IDE 来剖析这些类型的 Java 应用程序:
- Java EE / Web 应用程序
- Java 自由格式项目
- Java SE 项目
- NetBeans 模块和模块套件
JProfiler(Ej 技术)
对于分析工具,JProfiler 是许多开发人员的最佳选择。 它具有相对易于使用的界面,可帮助检查系统性能,内存使用情况,内存泄漏(如果有)和线程分析。
通过提供所有这些信息,我们可以诊断我们的应用程序的行为,并在需要时进行更改。 请注意,下图显示了正在运行的应用程序。
JProfiler 界面概述
从上图可以看到,它显示了与性能,内存直接相关的不同组件,可以轻松地帮助您优化程序。
让我们尝试一个使用 JProfiler(或一般情况下进行概要分析)的实际用例 - 内存泄漏检测
什么是内存泄漏?
要了解内存泄漏,如果您具有有关内存的基本知识,那将是一个好主意。 如果您不希望阅读,请随时阅读本文和此文章。
如果您之前已经编写过 Java 应用程序,则可能会遇到这个普遍存在的问题。 真正的实质是尚未释放回池中的内存。
考虑以下示例:想象您必须创建所需的对象。 但是,当您不再需要它时,请继续前进,而不是从内存中“释放”它。 幕后发生的事情是,仍然引用该对象。 当应用程序开始消耗更多资源时,它很快就会耗尽资源,并导致OutOfMemoryError
。
这是垃圾收集无法从内存中删除这些未使用的对象的时候。
上图给出的主要结论是 – 未使用的对象仍然占据运行中的内存,应用程序拥有的资源越多,它得到的性能越差,最终导致一个结果 – OutOfMemoryError
。
既然您知道什么是内存泄漏,并且实际上在编写应用程序时要考虑到这一点,那么让我们看看性能分析如何帮助我们识别并消除内存泄漏。
要在 Java 应用程序中轻松跟踪内存泄漏,请导航至左侧空白处的堆查询器:
堆沃克 Jprofiler
要启动示例项目,请单击左上角的“启动中心”:
然后选择第一个选项并选择开始:
您应该会看到以下窗口:
我们感兴趣的是“内存泄露”。但是在单击它之前,请选择“标记堆”以表明我们对新分配的对象感兴趣。
现在,这是应用程序开始创建未进行垃圾收集的对象的过程。
现在我们已经完成了,单击“Memory Leak”,然后等待一些时间来创建新对象。
现在,让我们进行堆快照:
现在,当我们单击“Heap Walker”时,我们将看到自上次标记堆操作以来已创建了多少个实例:
通过查看此窗口,我们不知道哪些对象正完全参与内存泄漏。 为此,我们需要单击“使用新的”。
然后我们将看到:
选择最通用的类并选择传入的引用,然后单击“确定”:
从那里开始,查找内存泄漏的最佳方法是在“显示 GC 根目录的路径”上进行选择:
我们设法找到实例的泄漏。
结论
通常,测试是程序员必须能够使用的最重要的技术之一。 有许多工具可以帮助测试和调试。 有些比其他的简单,例如 Eclipse 中的 Debug 模式,有些则更复杂,例如 JProfiler。 但是,如果您关心性能,优化和无内存问题,有时您需要深入研究给定 Java 应用程序的内部工作。
SWING JFrame
基础知识,如何创建JFrame
原文: https://javatutorial.net/swing-jframe-basics-create-jframe
本教程介绍了从创建到定制的JFrame
基础知识。
什么是JFrame
?
JFrame
是java.awt.frame
扩展的javax.swing
包类,它增加了对 JFC/SWING 组件架构的支持。 这是顶层窗口,带有边框和标题栏。JFrame
类具有许多可用于自定义它的方法。
创建一个JFrame
JFrame
类具有许多用于创建JFrame
的构造函数。 以下是描述。
JFrame()
:创建一个不可见的框架
JFrame(GraphicsConfiguration gc)
:创建带有空白标题和屏幕设备的图形配置的框架。
JFrame(String title)
:创建带有标题的JFrame
。
JFrame(String title, GraphicsConfiguration gc)
:创建具有特定Graphics
配置和指定标题的JFrame
。
这是创建JFrame
的最简单示例。
package Example;
import java.awt.GraphicsConfiguration;
import javax.swing.JFrame;
public class JFrameExample {
static GraphicsConfiguration gc;
public static void main(String[] args){
JFrame frame= new JFrame(gc);
frame.setVisible(true);
}
}
这是显示方式
设置JFrame
的标题
要设置JFrame
的标题,可以使用JFrame.setTitle(String title)
。
这是代码
package Example;
import java.awt.GraphicsConfiguration;
import javax.swing.JFrame;
public class JFrameExample {
static GraphicsConfiguration gc;
public static void main(String[] args){
JFrame frame= new JFrame(gc);
frame.setTitle("Welecome to JavaTutorial.net");
frame.setVisible(true);
}
}
这里看起来如何
设置JFrame
的标题
更改JFrame
的窗口大小
要调整框架的大小,JFrame
提供了JFrame.setSize(int width, int height)
方法,它需要两个参数width
和height
。 这是现在的代码外观
package Example;
import java.awt.GraphicsConfiguration;
import javax.swing.JFrame;
public class JFrameExample {
static GraphicsConfiguration gc;
public static void main(String[] args){
JFrame frame= new JFrame(gc);
frame.setTitle("Welecome to JavaTutorial.net");
frame.setSize(600, 400);
frame.setVisible(true);
}
}
调整JFrame
的大小
设置JFrame
的大小后,您会注意到您仍然可以通过简单地将光标放在角落并拖动它来更改它的大小。 或者,如果按右上角关闭旁边的“调整大小”选项,它将最大化到全屏大小。 发生这种情况是因为默认情况下调整大小设置为true
。 您可以简单地将false
设置为
JFrame.setResizable(false)
,现在它将根据您在代码中给定的尺寸显示,并且不会通过图形界面调整大小。
更改屏幕上的位置
要更改JFrame
在屏幕上的位置,JFranme
提供了一种方法JFrame.setlocation(int x, int y)
,它使用两个参数x
表示沿x
轴的位置,y
表示沿y
轴的位置。 屏幕的左上角是(0, 0)
。
关闭JFrame
您可以通过单击JFrame
左上角的X
(十字)来轻松关闭JFrame
。 但是JFrame.setDefaultCloseOperation(int)
是JFrmae
类提供的方法,您可以设置当用户单击叉号时将发生的操作。 如果给定参数 0,则即使单击十字,JFrame
也不会关闭。
最佳实践是使用JFrame.EXIT_ON_CLOSE
,它退出应用程序(JFrame
)并释放内存。
JFrame.HIDE_ON_CLOSE
:它不会关闭JFrame
,只是将其隐藏。
JFrame.DISPOSE_ON_CLOSE
:关闭框架,但它继续运行并消耗内存。
JFrame.DO_NOTHING_ON_CLOSE
:当用户单击“关闭”时,它什么也不做。
最终代码如下所示
package Example;
import java.awt.GraphicsConfiguration;
import javax.swing.JFrame;
public class JFrameExample {
static GraphicsConfiguration gc;
public static void main(String[] args){
JFrame frame= new JFrame(gc);
frame.setTitle("Welecome to JavaTutorial.net");
frame.setSize(600, 400);
frame.setLocation(200, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
您可以从链接下载源代码
Java SWING JFrame
布局示例
原文: https://javatutorial.net/java-swing-jframe-layouts
本教程通过示例和用法说明了各种 JFrmae 布局。
解释
Java AWT 包提供了许多不同的布局,例如边框布局,框布局,流式布局,网格布局等。这些布局管理器用于以特定方式排列组件。 布局用于按特定顺序管理组件。 以下是 Java 中一些常用布局的描述和示例。
边框布局
边框布局是最常用的布局之一。 这是JFrame
中的默认布局。 它可以将组件定位在五个不同的区域,例如顶部,底部,左侧,右侧和中心。 在边框布局中,每个区域仅包含一个组件。 所有可用空间都放在中间。
使用:使用边框布局初始化内容窗格,并通过add
方法向其中添加组件,并将布局作为参数。
以下示例显示以边框布局排列的组件。
package javatutorial.net;
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
public class BorderLayoutExample {
BorderLayoutExample(){
JFrame frame = new JFrame("Border Layout");
JButton button,button1, button2, button3,button4;
button = new JButton("left");
button1 = new JButton("right");
button2 = new JButton("top");
button3 = new JButton("bottom");
button4 = new JButton("center");
frame.add(button,BorderLayout.WEST);
frame.add(button1, BorderLayout.EAST);
frame.add(button2, BorderLayout.NORTH);
frame.add(button3, BorderLayout.SOUTH);
frame.add(button4, BorderLayout.CENTER);
frame.setSize(300,300);
frame.setVisible(true);
}
public static void main(String[] args){
new BorderLayoutExample();
}
}
这是边框布局的输出
边框布局
流式布局
流式布局是常用的布局。 这是JPanel
使用的默认布局。 它用于将组件排成一行或一行,例如从左到右或从右到左。 如果没有剩余空间,则它将组件排成一行。 Align 属性确定组件的对齐方式,如左,右,居中等。
使用:使用JFrame.setLayout(layout)
设置JFrame
布局,将流式布局作为参数传递。
下例显示以流式布局排列的组件
package javatutorial.net;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
public class FlowLayoutExample {
FlowLayoutExample(){
JFrame frame = new JFrame("Flow Layout");
JButton button,button1, button2, button3,button4;
button = new JButton("button 1");
button1 = new JButton("button 2");
button2 = new JButton("button 3");
button3 = new JButton("button 4");
button4 = new JButton("button 5");
frame.add(button);
frame.add(button1);
frame.add(button2);
frame.add(button3);
frame.add(button4);
frame.setLayout(new FlowLayout());
frame.setSize(300,300);
frame.setVisible(true);
}
public static void main(String[] args) {
new FlowLayoutExample();
}
}
这是流式布局的示例
流式布局
网格布局
网格布局将组件排列在矩形网格中。 它在单元中排列组件,每个单元具有相同的大小。 组件放置在列和行中。 GridLayout(int row, int column)
采用两个参数,即column
和row
。
使用:使用JFrame.setLayout(layout)
设置JFrame
布局,将网格布局作为参数传递。
下面的示例显示以网格布局(2 行 3 列)排列的组件。
package javatutorial.net;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
public class gridLayoutExample {
gridLayoutExample(){
JFrame frame = new JFrame("Flow Layout");
JButton button,button1, button2, button3,button4;
button = new JButton("button 1");
button1 = new JButton("button 2");
button2 = new JButton("button 3");
button3 = new JButton("button 4");
button4 = new JButton("button 5");
frame.add(button);
frame.add(button1);
frame.add(button2);
frame.add(button3);
frame.add(button4);
frame.setLayout(new GridLayout(2,3));
frame.setSize(300,300);
frame.setVisible(true);
}
public static void main(String[] args) {
new gridLayoutExample();
}
}
这是网格布局的输出
网格布局
这是您可以下载的链接完整代码。
在JFrame
上显示文本和图形
原文: https://javatutorial.net/display-text-and-graphics-java-jframe
本教程介绍了如何在JFrmae
上显示文本和图形,例如线条,圆形和矩形。
背景
Java 为我们提供了一种使用 GUI 绘制文本和图形的简便方法。 AWT 包中的Graphics
类允许我们绘制基本的几何类型,如直线和圆。 除此之外,它还可以显示文本。 本教程将说明用于绘制形状和文本的Graphics
类的各种功能。
绘制直线
Graphics
类提供了Graphics.drawline(int x1, int y1, int x2, int y2)
方法在屏幕上绘制一条线。 x1
是线的第一点的x
坐标,而y1
是线的第一点的y
坐标。 类似地,x2
和y2
是第二个线点的坐标。
这是显示一行的程序。
package javatutorial.net;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JFrmaeGraphics extends JPanel{
public void paint(Graphics g){
g.drawLine(10, 10, 200, 300);
}
public static void main(String[] args){
JFrame frame= new JFrame("Welecome to JavaTutorial.net");
frame.getContentPane().add(new JFrmaeGraphics());
frame.setSize(600, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
这是此代码的输出
行示例
绘制圆
您可以借助Graphics.drawOval(int x, int y, int width, int height)
方法绘制圆形和椭圆形。 此功能有两个目的。x
和y
是位置,屏幕上的起点以及宽度和高度是设置椭圆形或圆形的宽度和高度的参数。 对于圆设置相同的宽度和高度。
此处,程序显示在屏幕上绘制圆圈的代码。
package javatutorial.net;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JFrmaeGraphics extends JPanel{
public void paint(Graphics g){
g.drawOval(100, 100, 100, 100);
}
public static void main(String[] args){
JFrame frame= new JFrame("JavaTutorial.net");
frame.getContentPane().add(new JFrmaeGraphics());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
这是此代码的输出
圈子的例子
绘制矩形
Graphics
类提供了Graphics.drawRect(int x, int y, int width, int height)
方法来绘制矩形或正方形。 前两个参数显示起点,后两个参数显示矩形或正方形的宽度和高度。 对于正方形,宽度和高度应相同。
这是绘制矩形的代码
package javatutorial.net;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JFrmaeGraphics extends JPanel{
public void paint(Graphics g){
g.drawRect(10, 10, 100, 100);
}
public static void main(String[] args){
JFrame frame= new JFrame("JavaTutorial.net");
frame.getContentPane().add(new JFrmaeGraphics());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
这是此代码的输出
矩形示例
绘制多边形
绘制多边形非常容易。Graphics
类提供了Graphics.drawPolygon(int [], int [], int points)
的方法。 第一个参数是包含多边形所有点的x
值的数组,第二个也是包含多边形所有点的y
值的数组,而第三个参数显示点的数量。
package javatutorial.net;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JFrmaeGraphics extends JPanel{
public void paint(Graphics g){
int xValues[] = {25, 145, 25, 145, 25};
int yValues[] = {25, 25, 145, 145, 25};
int points = 5;
g.drawPolygon(xValues, yValues, points);
}
public static void main(String[] args){
JFrame frame= new JFrame("JavaTutorial.net");
frame.getContentPane().add(new JFrmaeGraphics());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
这是此代码的输出
多边形示例
绘制文字
要在屏幕上绘制文本,可以使用Graphics.drawText(string text, int x, int y)
方法。 第一个参数是您要显示的字符串,最后两个参数是此文本将开始的点的值。
这是示例代码
package javatutorial.net;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JFrmaeGraphics extends JPanel{
public void paint(Graphics g){
g.drawString("Hello to JavaTutorial.net", 10, 10);
}
public static void main(String[] args){
JFrame frame= new JFrame("JavaTutorial.net");
frame.getContentPane().add(new JFrmaeGraphics());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
这是此代码的输出
文字示例
绘制图像
Graphics 类提供了Graphics.drawImage(Image, int x, int y, ImageOberver observer)
方法来绘制图像。 虽然Image
是类,但是您可以使用getDafaultKit()
方法获取图像的地址。 将图像放置在项目的文件夹中。
这是示例代码
package javatutorial.net;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class JFrmaeGraphics extends JPanel{
public void paint(Graphics g){
Image image = Toolkit.getDefaultToolkit().getImage("example.jpg");
g.drawImage(image, 10, 10, this);
}
public static void main(String[] args){
JFrame frame= new JFrame("JavaTutorial.net");
frame.getContentPane().add(new JFrmaeGraphics());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
}
这是此代码的输出
图片示例
这是链接,您可以下载完整的代码。
与JFrame
交互 – 按钮,监听器和文本区域
原文: https://javatutorial.net/jframe-buttons-listeners-text-fields
本教程说明了如何通过使用按钮,监听器和文本区域与JFrame
进行交互。
背景
交互性是用户在每个应用程序中想要的。 为了在程序中增加交互性,Java 为我们提供了一种非常简单的方法。Javax.swing.JButton
类为我们提供了一种添加按钮的方法,并且在单击按钮后发生事件。 同样,借助javax.swing.JTextfield
的帮助,我们可以向JFrame
添加文本区域。
添加文本区域
您可以使用JTextfield()
方法创建一个文本区域。 此类具有许多构造函数,如JTextField()
:构造一个新的文本区域
JTextField(String text)
:使用指定的文本构造一个文本区域。
JTextField(String text, int colomn)
:它使用指定的文本和列数创建一个新的文本区域。
以下程序显示了向JFrame
添加文本区域的示例。
package javatutorial.net;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JTextField;
public class Example {
static JTextField textfield1, textfield2, textfield3;
public static void main(String[] args) {
JFrame f = new JFrame("Text Field Examples");
f.getContentPane().setLayout(new FlowLayout());
textfield1 = new JTextField("Text field 1",10);
textfield2 = new JTextField("Text field 2",10);
textfield3 = new JTextField("Text field 3",10);
f.getContentPane().add(textfield1);
f.getContentPane().add(textfield2);
f.getContentPane().add(textfield3);
f.pack();
f.setVisible(true);
}
}
这是此代码的输出
文本区域示例
您可以使用JTextfield
提供的不同方法进行自定义。
JTextField.setfont(Font f)
:它设置文本的字体
JTextField.setHorizontalAlignment(int alignment)
:它设置文本的水平对齐方式。
JTextField.setScrollOffset(int scrolloffset)
:它以像素为单位设置滚动偏移量。
添加按钮并应用动作监听器
同样,您可以在JFrame
中添加按钮。 JButton
为我们提供了一种添加按钮和动作监听器的简便方法。 它有很多像
JButton()
:创建一个没有文本且没有图标的按钮。
JButton(String text)
:创建一个带有指定文本的按钮。
JButton(Icon icon)
:创建一个带有指定图标的按钮。
JButton(String text, Icon icon)
:创建带有指定文本和图标的按钮。
这是带有图像图标的JButton
的简单示例。
package javatutorial.net;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
public class SimpleJButton {
SimpleJButton(){
JFrame f=new JFrame("Button Example");
JButton b=new JButton("Play", new ImageIcon("play.png"));
b.setBounds(100,100,140, 40);
f.add(b);
f.setSize(300,400);
f.setLayout(null);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new SimpleJButton();
}
}
这是此代码的输出
带有图标的按钮
添加动作监听器
在JButton
上添加动作监听器非常简单。 JButton
类提供方法JButton.addActionListener()
,该方法实现重写方法actionPerformed()
。 在下面的示例中,我编写了一个简单的程序,要求用户输入名称,当用户单击“提交”按钮时,将显示一条消息“名称已提交”。这是代码。
package javatutorial.net;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
public class SimpleJButton {
SimpleJButton(){
JFrame f=new JFrame("Button Example");
//submit button
JButton b=new JButton("Submit");
b.setBounds(100,100,140, 40);
//enter name label
JLabel label = new JLabel();
label.setText("Enter Name :");
label.setBounds(10, 10, 100, 100);
//empty label which will show event after button clicked
JLabel label1 = new JLabel();
label1.setBounds(10, 110, 200, 100);
//textfield to enter name
JTextField textfield= new JTextField();
textfield.setBounds(110, 50, 130, 30);
//add to frame
f.add(label1);
f.add(textfield);
f.add(label);
f.add(b);
f.setSize(300,300);
f.setLayout(null);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//action listener
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
label1.setText("Name has been submitted.");
}
});
}
public static void main(String[] args) {
new SimpleJButton();
}
}
这是此代码的输出
行为监听器
这是完整代码的链接,您可以下载。
如何使用 Maven 创建 Java JAR 文件
原文: https://javatutorial.net/create-java-jar-file-with-maven
本教程将逐步说明如何使用 Maven 创建 Java JAR 文件。
为什么需要 Java JAR 文件?
JAR 或 Java 存档文件是一个可执行文件,带有与元数据和资源相关的库的集合。 它实际上是一个扩展名为.JAR
的 ZIP 文件。 由于它还包含编译后的主类(.class
格式),因此即使不使用 IDE 也可以执行压缩和使用代码,而无需执行。 它用于通过网络分发软件程序。
因此,请按照本教程学习如何使用 Maven 创建 Java JAR 文件以及如何设置项目的依赖项。 下一节逐步说明所有要求和准则。
使用 Maven 创建 JAR 文件的要求
- Maven (它是软件管理和综合工具)
- JDK (Java 软件开发套件)
- Eclipse(集成开发环境)
注意:
如果您没有 Maven,请安装适用于 Eclipse 的 Maven 插件。 您可以使用 Eclipse 中的帮助菜单轻松安装 Maven 插件。
步骤 1 创建一个新的 Maven 项目
第一步是在 Eclipse IDE 中创建一个新的 Maven 项目。 为了这,
- 打开你的 IDE
- 转到“文件”,选择“新建”,然后选择“Project”
- 将出现一个新窗口,选择“Maven 项目”,如下所示
选择 Maven 项目
- 单击“Maven”,然后选择“Maven 项目”
- 在“New Maven Project Wizard”中,填写工件信息组 ID 和工件 ID(保留所有默认设置),如下所示
Maven 工件信息
- 单击“完成”,将创建一个新的 Maven 项目。
- 将所有 Java 源文件放在
src/main/java
目录中,并在src/test/resources
下创建测试文件
步骤 2:添加依赖项
要添加构建依赖关系,Maven 允许我们在pom.xml
文件中使用最简单的方法。 因此,此依赖项将自动下载并添加到您的 Maven 项目中。
转到pom.xml
文件
将 JUnit 的依赖项元素添加为
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
您的pom.xml
看起来像
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example1</groupId>
<artifactId>maven</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
单击“保存”将添加所有 jar 文件。
步骤 3:清理您的项目
现在清理您的 Maven 项目。 为此,转到项目的根文件夹,右键单击,在列表中找到“运行为”,然后单击“Maven clean”命令。
替代:
如果使用命令行,则可以键入mvn clean
命令。
步骤 4:编译所有 Java 类
清理完 Maven 项目后,现在编译所有源文件。 右键单击项目的根目录,转到“运行为”,然后单击“Maven 编译”。 它将编译所有源代码。 在项目浏览器中,您将看到目标文件夹已创建。
替代:
如果使用命令行,则可以键入mvn compile
命令
步骤 5:编译测试文件
现在,下一步是编译所有测试文件。 为此,转到您项目的根名称,然后右键单击,转到“运行为”,然后单击“Maven 测试”。 构建成功后,您将在目标文件夹下看到surefire-reports
文件夹。 如果打开此文件夹,您将看到一个包含构建信息的文本文件,如下图所示。
报告文件显示构建信息。
替代:
如果使用命令行,则可以键入mvn test
命令
步骤 6:运行您的 Maven 项目
现在,最后一步是运行您的项目以制作 JAR 文件。 右键单击项目的根目录,转到“运行为”,然后单击“Maven 安装”。 构建成功后,它将在目标文件夹下创建一个 JAR 文件。
替代:
如果使用命令行,则可以键入mvn install
命令
这里是
JAR 文件位置
就这样,使用 Maven 创建 JAR 文件就完成了。 这是您可以下载的完整项目。
Java Collection
新手指南
原文: https://javatutorial.net/java-collection-tutorial
Java 中的“集合”框架随着 JDK 1.2 的发布而生效,并在 Java 1.4 和 Java 5 中进行了多次扩展,然后在 Java 6 中进行了扩展。
它为您(程序员)提供列表,集合,映射和队列,以满足您大多数的编码需求。 而且,无论您选择哪一个,都一定会给您至少一个合理的表现。 在深入探讨事物之前,让我们先整理一些事物。
collection
(小写c
):这表示存储和迭代对象的不同类型的数据结构。Collection
(大写c
):这是集合,列表和队列扩展的接口。 “语法帮助:java.util.Collection
”Collections
(大写c
并以字符s
结尾):这是一个类,其中包含所有与集合一起使用的“静态”实用程序方法。
Java 集合层次结构
现在出现的问题是……我该如何处理某个Collection
?
- 对于初学者,您可以将对象添加到集合中。
- 从集合中删除对象
- 您可以检查特定集合中是否有给定的对象
- 从集合中检索任何对象而不将其删除。 等等
现在,不用多说,让我们开始使用收藏集。 它们通常有四个变体,即:
- 列表:事物列表
- 集合:独特的事物
- 映射:具有唯一 ID 的事物
- 队列:先进先出。
列表接口
List
接口中重要的首要因素是索引。 拥有索引可以为程序员提供整个索引方法库。 其次,所有三个不同的列表实现均按索引位置排序,这些实现的名称为:
ArrayList
Vector
LinkedList
(我没有忘记在上述名称中按空格...)
-
ArrayList
:可以将其视为动态数组,所谓动态数组,是指可以根据需要进行扩展和收缩而无需明确指定其大小的数组。ArrayList
帮助的区域位于快速迭代和快速随机访问领域。 但是要注意的重要一点是,默认情况下未对ArrayList
进行排序。 -
Vector
:Vector
有点像较慢的ArrayList
,主要区别在于,为了线程安全,Vector
方法都已同步。 (现在,线程安全性本身就是一个大话题,我们现在不再讨论。) -
LinkedList
:我们又有了类似于ArrayList
的内容。 这里的主要区别在于,LinkedList
中的元素是双向链接的。LinkedList
的迭代速度比ArrayList
慢,但是当需要快速插入或删除时,它是更好的选择。 我们将在我们的教程[中更详细地说明这一点 Java 中的ArrayList
和LinkedList
之间的区别
集合接口
这里的词组类似于您在学校数学中学习过的词组,如果您不喜欢该章,请不要着急。 这里唯一重要的是在Set
中我们不允许重复。 就像列表接口一样,集合接口也具有三种不同的实现:
HashSet
LinkedHashSet
TreeSet
HashSet
:这只是一个未排序的无序集合。 这里重要的是LinkedHashSet
使用传入对象的哈希码。LinkedHashSet
:您现在必须已经猜到它有点类似于HashSet
,并且绝对正确,因此,我只想让您知道这两者之间的区别(在此处输入鼓声) 是HashSet
的有序版本,该版本在所有元素上维护一个双向链表。TreeSet
:这是一个有序集合,并使用红黑树结构,该结构保证元素必定根据自然顺序处于升序。
映射接口
映射接口只关心以下事实:本质上,集合中已经存在或要添加到集合中的所有对象的所有标识符都是完全唯一的。 这里的键和值都是对象。
像上面给出的两个接口一样,Map 接口也具有三个不同的实现:
HashMap
HashTable
LinkedHashMap
HashMap
:这为您提供了一个未分类的,未排序的映射,其中键在映射中的位置取决于键的hashCode()
函数的值。HashTable
:它是HashMap
的同步副本。 不允许输入NULL
。LinkedHashMap
:此实现可维持插入顺序,这在我们讨论迭代及其其他用途时是一项非常重要的功能,但令人怀疑的缺点是速度太慢。
您可能也对本教程感兴趣,该教程更详细地说明了HashMap
和TreeMap
之间的差异
队列接口
是的,我们在这里谈论的是普通队列。 您几乎在任何地方都遇到的问题,包括在您进入本文之前向您展示的结果队列。
它基本上遵循 FIFO(先进先出)原则
PriorityQueue
:存在PriorityQueue
的原因是要创建一个“优先输入,优先输出”队列,而不是典型的 FIFO。
其次,在此实现中,元素通过自然排序或根据比较器排序。
这些是 Java 中Collection
的基本类型,该主题仍然涉及很多内容,并且还需要进行更多的解释,因此,如果您不这样做,请不要犹豫不决。 首次获得不同Collection
实现之间的所有差异。
参考:官方 Oracle Collections 教程
选择合适的 Java 集合
原文: https://javatutorial.net/choose-the-right-java-collection
Java 为您提供了多种收集实现供您选择。 通常,您将始终为您的编程任务寻找性能最佳的集合,在大多数情况下为ArrayList
,HashSet
或HashMap
。但是请注意,如果您需要某些特殊功能(例如排序或排序),则可能需要进行特殊的实现。 该 Java 集合教程不包括WeakHashMap
等很少使用的类,因为它们是为非常特定或特殊任务设计的,因此在 99% 的情况下都不应该选择它们。
如果您需要深入了解特定集合的实现方式,则可能需要阅读官方的集合框架教程。
首先让我们看下面的流程图。 它将帮助您根据要存储到此集合中的数据确定要使用的集合接口。
这里的主要规则是:如果您需要存储映射了键的值,请转到Map
接口,否则,请使用List
表示可能重复的值,如果您不想在集合中使用重复的值,请最终使用Set
接口。
选择正确的 Java Map
接口
HashMap
–如果迭代时项目的顺序对您不重要,请使用此实现。与TreeMap
和LinkedHashMap
相比,HashMap
具有更好的性能。
TreeMap
– 已排序和排序,但比HashMap
慢。TreeMap
根据其比较器具有键的升序
LinkedHashMap
– 在插入过程中按键对项目排序
选择正确的 Java List
接口
ArrayList
–插入期间对项目进行排序。与对LinkedLists
的搜索操作相比,对ArrayLists
的搜索操作更快
LinkedList
– 已快速添加到列表的开头,并通过迭代从内部快速删除
选择正确的 Java Set
接口
HashSet
– 如果迭代时项目的顺序对您不重要,请使用此实现。与TreeSet
和LinkedHashSet
相比,HashSet
具有更好的性能
LinkedHashSet
– 在插入过程中排序元素
TreeSet
– 根据其比较器,按键的升序排序
Java ArrayList
示例
原文: https://javatutorial.net/java-arraylist-example
Java ArrayList
类是实现List
接口的可调整大小的数组。 它允许所有元素(包括null
),并且实现所有可选列表操作。 可以在ArrayList
上运行的大多数操作,例如size
,isEmpty
,get
,set
,iterator
和listIterator
都是恒定时间。 但是,add
操作的复杂度为O(n)
时间。与LinkedList
相比,常数因子低。
ArrayList
的优点
- 动态添加或删除元素的能力。
ArrayList
使用数组作为其基础实现,这使ArrayList
类可以更快地访问元素。- 灵活性。
ArrayList
是面向对象的,因此您可以从中扩展并添加功能(如果需要)。- 向
ArrayList
添加自定义行为是可能的,甚至很容易实现。
ArrayList
的局限性
- 添加元素需要
O(n)
时间。 ArrayList
将数据顺序存储在内存中,因此,如果列表很大,则将需要大量连续的内存块。ArrayList
的初始容量为 10,如果不指定容量,则将受到性能限制。 每当ArrayList
达到其自身容量时,数据将以 50% 以上的容量从旧空间复制到新空间。
ArrayList
的简单说明
Java 中的ArrayList
从上图可以看到,ArrayList
中当前有 2 个元素,可以添加的最大元素数量(容量)为 8(或 6 个以上)。
ArrayList
中的构造方法
ArrayList()
– 构造一个初始容量为 10 的空列表。ArrayList(Collection <? extended E> c)
– 构造一个列表,该列表包含指定集合的元素,并按集合的迭代器返回它们的顺序。ArrayList(int initialCapacity)
– 构造一个具有指定初始容量的空列表。
ArrayList
类中的方法
boolean add(E e)
:将指定的元素附加到列表的末尾。void add(int index, E element)
:将指定的元素插入列表中的指定位置。boolean addAll(Collection <? extends E> c)
:将指定集合中的所有元素附加到列表的末尾,以指定集合的Iterator
返回它们的顺序。boolean addAll(int index, Collection <? extends E> c)
:从指定位置开始,将集合中的所有元素插入列表。void clear()
:从列表中删除所有元素。boolean contains(Object o)
:如果列表包含指定的元素,则返回true
;否则返回false
。void ensureCapacity(int minCapacity)
:如果有必要确保列表可以容纳最小容量参数指定的所有元素,则增加列表的容量。void forEach(Consumer<? super E> action)
:对已循环通过的每个元素执行给定的动作。int indexOf(Object o)
:如果列表不包含元素,则返回 -1。 但是,如果元素存在于列表中,则它返回此列表中指定元素首次出现的索引。boolean isEmpty()
:如果列表不包含任何元素,则返回true
。Iterator<E> iterator()
:以适当的顺序返回列表中元素的迭代器。int lastIndexOf(Object o)
:返回列表中指定元素最后一次出现的索引;如果列表中不存在该元素,则返回 -1。boolean remove(Object o)
:从列表中删除第一次出现的指定对象。boolean removeAll(Collection<?> c)
:从列表中删除指定集合中包含的所有元素。boolean removeIf(Predicate<? super E> filter)
:移除此集合中所有满足给定谓词的元素。void removeRange(int fromIndex, int toIndex)
:删除列表中fromIndex
和toIndex
之间的所有元素。(fromIndex
–包括在内,toIndex
–排除在外)void replaceAll(UnaryOperator<E> operator)
:使用将运算符应用于该元素的结果替换列表中的每个元素。boolean retainAll(Collection<?> c)
:仅返回指定集合中包含的列表中的元素。int size()
:返回列表的大小。void sort(Comparator<? super E> c)
:根据Comparator
指定的顺序对列表进行排序。Object[] toArray()
:返回包含列表中包含的元素的数组。
有关所有方法的文档,请访问 Oracle 官方文档页面。
使用add()
在ArrayList
中添加元素
import java.util.* ;
public class ArrayListEgTwo
{
public static void main ( String[] args)
{
// Create an ArrayList that consists of Strings
ArrayList<String> animals = new ArrayList<String>();
// Capacity starts at 10, but size starts at 0
System.out.println("initial size: " + animals.size());
// Populating some of the arraylist
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("new size: " + animals.size());
}
输出:
size: 0
new size: 3
使用remove()
移除ArrayList
中的元素
import java.util.* ;
public class RemoveExample
{
public static void main ( String[] args)
{
ArrayList<String> animals = new ArrayList<String>();
animals.add( "Elephant" );
animals.add( "Tiger" );
animals.add( "Lion" );
names.remove(1);
for ( int i=0; i < animals.size(); i++ )
System.out.println( i + ": " + animals.elementAt(j) );
}
}
输出:
0: Elephant
1: Lion
使用isEmpty()
检查ArrayList
是否包含元素
import java.util.* ;
public class isEmptyExample
{
public static void main ( String[] args)
{
ArrayList<String> animals = new ArrayList<String>();
System.out.println( "Case 0:" + animals.isEmpty());
animals.add("Tiger");
System.out.println( "Case 1:" + animals.isEmpty() );
nobby.clear();
System.out.println( "Case 2:" + animals.isEmpty() );
}
}
输出:
Case 0: true
Case 1: false
Case 2: true
使用indexOf(Object o)
在ArrayList
中搜索元素
import java.util.* ;
public class IndexOfExample
{
public static void main (String[] args)
{
ArrayList<String> animals = new ArrayList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("Index of 'Elephant': " + animals.indexOf("Elephant" ));
System.out.println("Index of 'Lion': " + animals.indexOf("Lion"));
}
}
输出:
Index of 'Elephant': 0
Index of 'Lion': 2
使用Iterator()
遍历ArrayList
中的元素
import java.util.* ;
public class IteratorExample
{
public static void main ( String[] args)
{
ArrayList<String> animals = new ArrayList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Initializing an iterator
Iterator<String> iterator = animals.iterator();
// Using the iterator to visit each element
while(iterator.hasNext())
System.out.println(iterator.next());
}
}
输出:
Elephant
Tiger
Lion
使用增强的For
循环遍历ArrayList
中的元素
import java.util.* ;
public class ForLoopExample
{
public static void main ( String[] args)
{
ArrayList<String> animals = new ArrayList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
for (String animal : animals)
System.out.println(animal);
}
}
输出:
Elephant
Tiger
Lion
使用size()
获取ArrayList
的大小
import java.util.* ;
public class ForLoopExample
{
public static void main ( String[] args)
{
ArrayList<String> animals = new ArrayList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("Size of ArrayList: " + animals.size());
}
}
输出:
Size of ArrayList: 3
Java LinkedList
示例
原文: https://javatutorial.net/java-linkedlist-example
Java 中的LinkedList
类使用双链表来存储元素,并且还提供了链表数据结构。 它实现了List
和Deque
接口,就像ArrayList
类一样。 就像数组一样,LinkedList
是线性数据结构,但是与数组不同,LinkedList
中的元素是使用指针链接在一起的。 有一个指向第一个元素的指针,通常称为“头部”。 最后一个元素指向null
元素。 此null
元素意味着链表中没有任何其他元素。
关于 Java 中的LinkedList
的重要了解是什么
- Java 中的
LinkedList
类允许重复的元素。 - Java 中的
LinkedList
类维护插入顺序。 - Java 中的
LinkedList
类是不同步的。 - Java 中的
LinkedList
类允许快速操作,因为不需要进行任何移位。 - Java 中的
LinkedList
类可以是列表,栈甚至队列。
LinkedList
的局限性
LinkedList
类比数组需要更多的内存,因为它们的指针使用存储。- 由于我们不能仅通过它们的索引访问每个元素,因此很难遍历链表中的元素。
LinkedList
的简单说明
Java 中的LinkedList
这里的头是我上面提到的指针,该指针指向第一个元素。 第一个元素(以及链表中的每个单个元素)本质上具有两件事 - 数据和下一个。 数据只是指定元素包含的内容,其次是指向下一个元素的指针。 最后,在图示中我们可以看到 D 指向null
,在链表中,null
表示列表的末尾。
LinkedList
中的构造方法
LinkedList()
:用于构造一个空列表。LinkedList(Collection<? extends E> c)
:用于构造一个包含指定集合元素的列表,这些元素按顺序由集合的迭代器返回。
LinksList
中的方法
boolean add(E e)
:将指定的元素添加到列表的末尾。void add(int index, E element)
:将指定的元素添加到指定的位置。void clear()
:从列表中删除所有元素。Object clone()
:返回ArrayList
的浅表副本。boolean contains(Object o)
:如果指定的元素在列表中,则返回true
。Iterator<E> endingIterator()
:以反向双端队列的反向顺序返回元素上的迭代器。E element()
:从列表中检索第一个元素。E get(int index)
:返回列表中指定位置的元素。int indexOf(Object o)
返回列表中指定元素首次出现的索引,如果不存在则返回 -1。boolean offer(E e)
:将指定的元素添加为列表的最后一个元素。E pop()
:从列表表示的栈中弹出一个元素。void push(E e)
:将元素压入列表表示的栈。E remove()
:用于检索和删除列表的第一个元素。E remove(int index)
:删除列表中指定位置的元素。int size()
:返回列表中的元素数。
有关所有方法的文档,请访问 Oracle 官方文档页面。
使用add()
将元素添加到LinkedList
语法:boolean add(E e)
import java.io.*;
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String args[]) {
LinkedList animals = new LinkedList();
animals.add("Elephant");
animals.add("Tiger");
System.out.println("The list is:" + animals);
list.add("Lion");
// printing the new list
System.out.println("The new List is:" + animals);
}
}
输出:
The list is:[Elephant, Tiger]
The new List is:[Elephant, Tiger, Lion]
使用push()
将元素推送到LinkedList
语法:LinkedListObject.push(E e)
import java.util.LinkedList;
public class PushExample {
// Main method
public static void main(String[] args)
{
LinkedList<String> animals = new LinkedList<>();
animals.push("Elephant");
animals.push("Tiger");
animals.push("Lion");
// Printing the complete stack.
System.out.println(animals);
}
}
输出:
[Lion, Tiger, Elephant]
使用pop()
从LinkedList
弹出元素
语法:LinkedListObject.pop()
import java.io.*;
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String args[]) {
LinkedList animals = new LinkedList();
/ Pushing an element in the stack
animals.push("Elephant");
// Pushing an element in the stack
animals.push("Tiger");
// Pop an element from stack
String s = animals.pop();
System.out.println(s);
// Pushing an element in the stack
animals.push("Lion");
// Printing the complete stack.
System.out.println(animals);
}
}
输出:
Elephant
[Elephant, Lion]
使用remove()
从LinkedList
移除元素
语法:LinkedList.remove()
import java.io.*;
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String args[])
{
LinkedList<String> animals = new LinkedList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("LinkedList:" + animals);
// Remove the head using remove()
animals.remove();
// Print the final list
System.out.println("Final LinkedList:" + animals);
}
}
输出:
LinkedList:[Elephant, Tiger, Lion]
Final LinkedList:[Tiger, Lion]
使用clear()
清除链表
import java.io.*;
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String args[])
{
LinkedList<String> animals = new LinkedList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("Before clear:" + animals);
animals.clear();
System.out.println("After clear: " + animals);
// Adding elements after clearing the list
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("After adding elements to empty list:" + animals);
}
}
输出:
Before clear:[Elephant, Tiger, Lion]
After clear: []
After adding elements to empty list:[Elephant, Tiger, Lion]
使用contains()
检查LinkedList
中是否存在元素
语法:boolean contains(Object o)
import java.io.*;
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String args[]) {
LinkedList<String> animals = new LinkedList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("\nIs 'Lion' in the linkedlist: "
+ animals.contains("Lion"));
System.out.println("Is'Cat' in the linkedlist: "
+ animals.contains("Cat"));
}
}
输出:
Is 'Lion' in the linkedlist: true
Is 'Cat' in the linkedlist: false
使用size()
获取LinkedList
的大小
语法:LinkedList.size()
import java.io.*;
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String args[])
{
LinkedList<String> animals = new LinkedList<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("The size of the linked list is: "
+ animals.size());
}
}
输出:
The size of the linked list is: 3
如何遍历链表
有 5 种方法:
For
循环- 增强的
For
循环 While
循环- 迭代器
- 集合的
stream()
(Java 8)
For
循环
LinkedList<String> linkedList = new LinkedList<>();
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
增强For
循环
for (String temp : linkedList) {
System.out.println(temp);
}
While
循环
int i = 0;
while (i < linkedList.size()) {
System.out.println(linkedList.get(i));
i++;
}
迭代器
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
集合stream()
(Java 8)
linkedList.forEach((temp) -> {
System.out.println(temp);
});
Java HashSet
示例
原文: https://javatutorial.net/java-hashset-example
使用哈希表进行存储的集合通常由 Java HashSet
类创建。 顾名思义,HashSet
实现Set
接口,并且还使用一个哈希表,该哈希表是HashMap
实例。HashSet
中元素的顺序是随机的。 此类允许使用null
元素。 就复杂度而言,HashSet
为基本操作(如添加,删除,包含和大小)提供恒定的时间性能,前提是假定元素已被函数正确分散。
有关HashSet
的重要信息
HashSet
通过使用称为散列的机制来存储元素。HashSet
中不能存在重复的元素。HashSet
允许为空值。HashSet
类不同步。HashSet
的顺序不由插入顺序维护。 元素(在此类中)是根据其哈希码插入的。- 就搜索操作而言,由于
HashSet
具有恒定的时间复杂度,因此它是最好的方法。 HashSet
的初始默认容量为 16,而负载系数为 0.75。
HashSet
简单的结构图
Java 中的HashSet
我们放入HashMap
中的每个对象都首先通过哈希算法发送。 该算法的唯一目的是为传递给它的每个对象生成一个称为哈希的唯一编号。 在上图中,此算法为字符串Lisa Morgan
生成了数字 3,为Bob Wiliams
生成了数字 2,为Jane Smith
生成了数字 1。 以后,这些数字将作为索引存储在数组中。 每当您要对HashSet
中的元素执行任何类型的操作时,您都将通过由哈希算法生成的索引来解决它们。 这就是HashSet
以随机顺序返回元素的原因。 哈希号是HashSet
知道的唯一顺序。
HashSet
中的构造方法
HashSet hashSet = new HashSet();
HashSet hashSet = new HashSet(int initialCapacity);
HashSet hashSet = new HashSet(int initialCapacity, float loadFactor);
HashSet hashSet = new HashSet(Collection c);
这些构造函数之间的主要区别在于,在 #1 构造函数中,初始容量为 16,默认负载因子为 0.75,但在 #2 中,您实际上可以设置容量。 负载系数的默认值仍为 0.75。 在构造函数 3 中,您可以设置容量和负载系数。
HashSet
类中的方法
boolean add(Object o)
:用于添加作为参数提供的元素,如果不存在,则返回false
。void clear()
:用于删除所有元素。boolean contains(Object o)
:如果指定的Object
在HashSet
中,则返回true
;否则,返回false
。boolean remove(Object o)
:用于从HashSet
中删除指定的Object
(如果存在)。Iterator iterator()
:用于返回集合中元素上的迭代器。boolean isEmpty()
:用于检查HashSet
是否为空。 如果为空,则返回true
;否则为false
。int size()
:返回集合的大小。Object clone()
:创建集合的副本。
有关所有方法的文档,请访问 Oracle 官方文档页面。
使用add()
在HashSet
中添加元素
语法:HashSet.add(Object o);
import java.io.*;
import java.util.HashSet;
public class HashSetExample {
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Displaying the HashSet
System.out.println("HashSet: " + animals);
}
}
输出:
HashSet: [Elephant, Tiger, Lion]
使用clear()
清空HashSet
语法:HashSet.clear();
输出:
import java.io.*;
import java.util.HashSet;
public class HashSetExample{
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Displaying the HashSet
System.out.println("HashSet: " + animals);
// Clearing the hash set
animals.clear();
// Displaying the final Set after clearing;
System.out.println("The final set: " + animals);
}
}
HashSet: [Elephant, Tiger, Lion]
The final set: []
使用contains()
检查HashSet
中是否存在元素
语法:Hash_Set.contains(Object o)
import java.io.*;
import java.util.HashSet;
public class HashSetExample {
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Displaying the HashSet
System.out.println("HashSet: " + animals);
// Checking for "Lion" in the hash set
System.out.println("Does the HashSet contain 'Lion'? " + animals.contains("Lion"));
// Checking for "Elephant" in the hash set
System.out.println("Does the HashSet contain 'Elephant'? " + animals.contains("Elephant"));
// Checking for "Tiger" in the hash set
System.out.println("Does the HashSet contain 'Tiger'? " + animals.contains("Tiger"));
// Checking for "Chicken" in the hash set
System.out.println("Does the HashSet contain 'Chicken'? " + animals.contains("Chicken"));
}
}
输出:
HashSet: [Elephant, Tiger, Lion]
Does the Set contain 'Lion'? true
Does the Set contain 'Elephant? true
Does the Set contain 'Tiger'? true
Does the Set contain 'Chicken'? false
使用remove()
从HashSet
中删除元素
语法:HashSet.remove(Object o)
import java.util.*;
import java.util.HashSet;
public class HashSetExample {
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Displaying the HashSet
System.out.println("HashSet: " + animals);
set.remove("Elephant");
set.remove("Lion");
// Displaying the HashSet after removal
System.out.println("HashSet after removing elements: " + animals);
}
}
输出:
HashSet: [Elephant, Tiger, Lion]
HashSet after removing elements: [Tiger]
Iterator()
方法
语法:Iterator iterator = HashSet.iterator()
;
import java.util.*;
import java.util.HashSet;
public class HashSetExample {
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Displaying the HashSet
System.out.println("HashSet: " + animals);
// Creating an iterator
Iterator iterator = animals.iterator();
// Displaying the values after iterating through the set
System.out.println("The iterator values are: ");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
输出:
HashSet: [Elephant, Tiger, Lion]
The iterator values are:
Elephant
Tiger
Lion
使用isEmpty()
检查HashSet
是否为空
语法:HashSet.isEmpty()
;
import java.io.*;
import java.util.HashSet;
public class HashSetExample {
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Displaying the HashSet
System.out.println("HashSet: " + animals);
// Check for the empty set
System.out.println("Is the hash set empty: " + animals.isEmpty());
set.clear();
// Checking after we've cleared it out
System.out.println("Is the hash set empty: " + animals.isEmpty());
}
}
输出:
HashSet: [Elephant, Tiger, Lion]
Is the hash set empty: false
Is the hash set empty: true
使用size()
获取HashSet
的大小
语法:HashSet.size()
;
import java.util.*;
import java.util.HashSet;
public class HashSetExample {
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
// Displaying the HashSet
System.out.println("HashSet: " + animals);
// Get the size of the hash set
System.out.println("The size of the hash set is: " + animals.size());
}
}
输出:
HashSet: [Elephant, Tiger, Lion]
The size of the hash set is: 3
使用clone()
克隆HashSet
语法:HashSet.clone()
import java.io.*;
import java.util.HashSet;
public class HashSetExample {
public static void main(String args[])
{
// Creating an empty HashSet
HashSet<String> animals = new HashSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("HashSet: " + animals);
// Creating a new set
HashSet clonedSet = new HashSet();
// Cloning the set using clone() method
clonedSet = (HashSet)animals.clone();
// Displaying the new hashset;
System.out.println("The new set: " + clonedSet);
}
}
输出:
HashSet: [Elephant, Tiger, Lion]
The new set: [Elephant, Tiger, Lion]
如何迭代HashSet
有两种方法可以遍历HashSet
:
- 使用迭代器
- 不使用迭代器
1)使用迭代器
import java.util.HashSet;
import java.util.Iterator;
class IterateHashSetExample{
public static void main(String[] args) {
HashSet<String> animals= new HashSet<String>();
//add elements to HashSet
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
Iterator<String> iterator = animals.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
上面的代码只是将迭代器“附加”到动物散列集上,然后仅打印每一个迭代器,直到没有更多为止。 另外,此方法将忽略重复项。 如果有重复项,则重复项仅打印一次。
输出:
Elephant
Tiger
Lion
2)不使用迭代器
import java.util.HashSet;
import java.util.Set;
class IterateHashSetExample{
public static void main(String[] args) {
Set<String> animals = new HashSet<String>();
//add elements to HashSet
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
for (String animal : animals) {
System.out.println(animal);
}
}
}
输出:
Elephant
Tiger
Lion
Java TreeSet
示例
原文: https://javatutorial.net/java-treeset-example
Java TreeSet
类是一个基于TreeMap
的NavigableSet
实现。 元素可以通过Comparator
进行排序,也可以仅通过其自然排序进行排序。 在复杂度方面,此实现为所有基本操作(例如添加,删除,包含)提供了log(n)
时间成本。
关于 Java 中的TreeSet
的重要知识
TreeSet
实现SortedSe
接口,这意味着不允许重复值。- 异构对象不能插入
TreeSet
中,因为它不允许这样做。 它将在运行时引发classCastException
。 TreeSet
类不保留元素的插入顺序,但是元素按其键排序。TreeSet
中的元素以升序和排序顺序存储。
Java 中TreeSet
的局限性
- 值必须是可比较的,或者需要在构造函数中提供比较器
TreeSet
的简单说明
继承图
TreeSet
中的构造方法
TreeSet()
:构造一个新的空树集,并根据其元素的自然顺序进行排序TreeSet(Collection<? extends E> c)
:构造一个新树集,该树集包含指定集合中的元素,并根据其元素的自然顺序对其进行排序。TreeSet(Comparator<? super E> comparator)
:构造一个空树集,该树集根据指定的比较器排序。TreeSet(SortedSet<E> s)
:构造一个与指定集合完全相同的新树集,包括元素的顺序。
TreeSet
中的方法
void add(Object o)
:根据TreeSet
中的某些排序顺序添加指定的元素。 不会添加重复项。void clear()
:从树集中删除所有元素。boolean contains(Object o)
:如果树集中存在给定元素,则返回true
,否则返回false
。Object first()
:返回树集中的第一个元素。Object last()
:返回树集中的最后一个元素。boolean isEmpty()
:如果树集不包含任何元素,则返回true
,否则返回false
。Object clone()
:返回树集的浅表副本。int size()
:返回树集中的元素数量。Iterator iterator()
:返回用于迭代树集元素的迭代器。Comparator comparator()
:返回用于对TreeSet
中的元素进行排序的Comparator
。
有关所有方法的文档,请访问 Oracle 官方文档页面。
使用add()
在TreeSet
中添加元素
语法:treeSet.add(Object o)
import java.io.*;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String args[])
{
TreeSet<String> animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("TreeSet: " + animals);
}
}
输出:
TreeSet: [Elephant, Tiger, Lion]
使用first()
和last()
获取TreeSet
的第一个和最后一个元素
语法:
treeSet.first()
treeSet.last()
import java.io.*;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String args[])
{
TreeSet<String> animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("TreeSet: " + animals);
System.out.println("The first element: " + animals.first());
System.out.println("The last element: " + animals.last());
}
}
输出:
TreeSet: [Elephant, Tiger, Lion]
The first element: Elephant
The last element: Lion
使用contains()
检查元素是否在树集中
语法:treeSet.contains(Object element)
import java.io.*;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String args[])
{
TreeSet<String> animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("TreeSet: " + animals);
System.out.println("Is 'Cat' in the tree set?" + animals.contains("Cat"));
System.out.println("Is 'Lion' in the tree set?" + animals.contains("Lion"));
}
}
输出:
TreeSet: [Elephant, Tiger, Lion]
Is 'Cat' in the tree set? false
Is 'Lion' in the tree set? true
使用remove()
从树集中删除一个元素
语法:treeSet.remove(Object o)
import java.io.*;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String args[])
{
TreeSet<String> animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("TreeSet: " + animals);
animals.remove("Lion");
animals.remove("Elephant");
System.out.println("TreeSet: " + animals);
}
}
输出:
TreeSet: [Elephant, Tiger, Lion]
TreeSet: [Tiger]
使用size()
获取树集中的元素数量
语法:treeSet.size()
import java.util.*;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String args[])
{
TreeSet<String> animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("TreeSet: " + animals);
System.out.println("The size of the tree set is: " + animals.size());
}
}
输出:
TreeSet: [Elephant, Tiger, Lion]
The size of the tree set is: 3
使用isEmpty()
检查树集是否为空
语法:treeSet.isEmpty()
import java.util.*;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String args[])
{
TreeSet<String> animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("TreeSet: " + animals);
System.out.println("Empty: " + animals.isEmpty());
animals.clear();
System.out.println("Empty: " + animals.isEmpty());
}
}
输出:
TreeSet: [Elephant, Tiger, Lion]
Empty: false
Empty: true
使用Iterator()
遍历树集
语法:
Iterator iterator = ts.iterator();
while (iterator.hasNext())
System.out.print(iterator.next());
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args)
{
TreeSet<String>
animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
Iterator<String> iterator = animals.iterator();
System.out.println("TreeSet: ");
System.out.println();
while (iterator.hasNext())
System.out.print(iterator.next()
+ "\n");
}
}
输出:
TreeSet:
Elephant
Tiger
Lion
使用增强的for
循环遍历树集
语法:
for (String animal : animals)
System.out.print(animal);
}
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args)
{
TreeSet<String>
animals = new TreeSet<String>();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
System.out.println("TreeSet: ");
System.out.println();
for (String animal : animals)
System.out.print(animal + "\n");
}
}
输出:
TreeSet:
Elephant
Tiger
Lion
Java LinkedHashSet
示例
原文: https://javatutorial.net/java-linkedhashset-example
Java 中的LinkedHashSet
类与HashSet
不同,因为其实现维护了所有元素上的双链表。 此链表定义了迭代顺序,即将元素插入到集合中的顺序。 这称为插入顺序。 如果将元素重新插入到集合中,则插入顺序不受其影响。 某些人更喜欢此实现,因为它使用户摆脱了HashSet
提供的未指定且混乱的排序。
LinkedHashSet
的优点
LinkedHashSet
维护元素的插入顺序- 插入,删除和检索操作的时间复杂度为
O(1)
(恒定时间)。 LinkedHashSet
允许 1 个null
元素。LinkedHashSet
使用equals()
和hashCode()
,因此可以删除可能的重复元素。
继承图
LinkedHashSet
的继承图
LinkedHashSet
中的构造方法
LinkedHashSet()
:初始化一个新的链接哈希集,默认初始容量为 16,负载因子为 0.75。LinkedHashSet(Collection <? extends E> c)
:使用与指定集合相同的元素初始化一个新的链接哈希集。LinkedHashSet(int initialCapacity)
:使用指定的初始容量初始化一个新的链接哈希集。LinkedHashSet(int initialCapacity, float loadFactor)
:使用指定的容量和负载因子初始化一个新的链接哈希集。
LinkedHashSet
中的方法
- 从类
java.util.HashSet
继承的方法:add
,clear
,clone
,contains
,isEmpty
,iterator
,remove
,size
- 从类
java.util.AbstractSet
继承的方法:equals
,hashCode
,removeAll
- 从类
java.util.AbstractCollection
继承的方法:addAll
,containsAll
,keepAll
,toArray
,toArray(T[])
,toString
- 从类
java.lang.Object
继承的方法:finalize
,getClass
,notify
,notifyAll
,wait
,wait(long)
,wait(long, int)
- 从接口
java.util.Set
继承的方法:add
,addAll
,clear
,contains
,containsAll
,equals
,hashCode
,isEmpty
,iterator
,remove
,removeAll
,retainAll
,size
,toArray
,toArray(T[])
有关所有方法的文档,请访问 Oracle 官方文档页面。
使用Iterator()
迭代LinkedHashSet
import java.util.*;
class LinkedHashSetExample{
public static void main(String args[]){
LinkedHashSet<String> animals=new LinkedHashSet();
animals.add("Elephant");
animals.add("Tiger");
animals.add("Lion");
Iterator<String> iterator=animals.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
输出:
Elephant
Tiger
Lion
使用For
循环遍历LinkedHashSet
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<String>();
for (int i = 0; i < linkedHashSet.size(); i++) {
System.out.println(linkedHashSet.get(i));
}
使用增强For
循环遍历LinkedHashSet
for (String temp : linkedHashSet) {
System.out.println(temp);
}
使用While
循环遍历LinkedHashSet
int i = 0;
while (i < linkedHashSet.size()) {
System.out.println(linkedHashSet.get(i));
i++;
}
LinkedHashSet
的示例程序
import java.util.LinkedHashSet;
public class LinkedHashSetExample
{
public static void main(String[] args)
{
LinkedHashSet<String> coins =
new LinkedHashSet<String>();
// Adding element to LinkedHashSet
coins.add("5");
coins.add("10");
coins.add("20");
coins.add("50");
coins.add("100");
// Adding a duplicate would result in no addition of the element
coins.add("5");
// Adding another coin value
coins.add("75");
System.out.println("Size of the list = " +
coins.size());
System.out.println("Original LinkedHashSet:" + coins);
System.out.println("Removing 75 from LinkedHashSet: " +
coins.remove("75"));
System.out.println("Checking if 50 is present=" +
coins.contains("50"));
System.out.println("Updated LinkedHashSet: " + coins);
}
}
输出:
Size of LinkedHashSet=5
Original LinkedHashSet:[5, 10, 20, 50, 75, 100]
Removing 75 from LinkedHashSet: true
Checking if 50 is present=true
Updated LinkedHashSet:[5, 10, 20, 50, 100]
Java EnumSet
示例
原文: https://javatutorial.net/java-enumset-example
Java EnumSet
类实现Set
并将其与枚举类型一起使用。EnumSet
(顾名思义)只能包含枚举值,并且所有值都属于同一个枚举。 此外,EnumSet
不允许使用空值,这意味着它会抛出NullPointerException
尝试添加空值。 它不是线程安全的,这意味着,如果需要,我们需要在外部进行同步。
继承图
枚举集
为什么使用EnumSet
每当我们必须存储枚举值时,EnumSet
应该始终是最佳的Set
实现。 所有基本操作都以固定的time(1)
复杂度执行,这非常快。 这是由于EnumSet
类中的所有方法都是使用算术按位运算(<<
,>>
,&
等)实现的。 总而言之,EnumSet
非常有效,因为它使用较少的内存并且速度也很快。
EnumSet
中的方法
EnumSet<E> close()
:返回当前集合的副本。static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)
:创建一个枚举集,该枚举集包含该枚举中的所有元素 指定的元素类型。static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
:创建与指定元素类型相同的枚举集 枚举集合,最初包含此类型的所有元素,这些元素不包含在指定集合中。static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
:创建一个从指定集合初始化的集合。static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)
:创建一个与指定元素类型相同的枚举集,包含相同的元素(如果有)。static <E extends Enum<E>> EnumSet<E> of(E e)
:创建一个包含指定元素的枚举集。atic <E extends Enum<E>> EnumSet<E> range(E from, E to)
:创建一个枚举集,该枚举集包含在由两个参数指定的范围内的所有元素。
从类java.util.AbstractSet
继承的方法:
equals
, hashCode
, removeAll
从类java.util.AbstractCollection
继承的方法:
add
, addAll
,clear
,include
, containsAll
, isEmpty
,iterator
,remove
, keepAll
,size
, toArray
, toArray(T[])
, toString
从类java.lang.Object
继承的方法:
finalize
, getClass
,notify
, notifyAll
,wait
,wait(long)
。
从接口java.util.Set
继承的方法:
add
, addAll
,clear
,contains
, containsAll
, isEmpty
,iterator
,remove
, retainAll
,size
, toArray
, toArray(T[])
有关EnumSet
主要方法的更多信息,请随时访问原始 Oracle 文档。
import java.util.EnumSet;
enum Student
{
NAME, AGE, MAJOR, YEAR
};
public class EnumSetExample
{
public static void main(String[] args)
{
// initializing set
EnumSet<Student> set1, set2, set3;
// populating the sets using of(E e)
enumSet1 = EnumSet.of(Student.NAME, Student.MAJOR, Student.YEAR);
// will get all of the properties that have not been initialized to the
// specified set (if any)
enumSet2 = EnumSet.complementOf(enumSet1);
// will get all of the properties that are present in the enumset
enumSet3 = EnumSet.allOf(Student.class);
// will get from age to year and everything in between the properties of the
// enumset class
enumSet4 = EnumSet.range(Student.AGE, Student.YEAR);
System.out.println("Set 1: " + enumSet1);
System.out.println("Set 2: " + enumSet2);
System.out.println("Set 3: " + enumSet3);
System.out.println("Set 4: " + enumSet4);
}
}
输出:
Set 1: {NAME, MAJOR, YEAR}
Set 2: {AGE}
Set 3: {NAME, AGE, MAJOR, YEAR}
Set 4: {AGE, MAJOR, YEAR}
Java ConcurrentHashSet
示例
原文: https://javatutorial.net/java-concurrenthashset-example
Java 8 最终允许我们(程序员)在 Java 中创建线程安全的ConcurrentHashSet
。 在此之前,这根本是不可能的。 有一些变种试图简化上述类的实现,其中之一就是使用带有虚拟值ConcurrentHashMap
。 但是,您可能已经猜到,ConcurrentHashMap
的所有即兴创作都有其局限性和风险。
Java 8 允许我们使用keySet(defaultVal)
和newKeySet()
方法来返回 Set,这恰好是一个合适的集合。 这样可以访问用户可以使用的许多必要功能:contains()
,remove()
等。 注意: 这些方法在ConcurrentHashMap
中可用,而不是在ConcurrentMap
接口中可用, 意味着必须创建ConcurrentHashMap
类型的变量,并将其用作引用。 另一种方法是对象的简单转换。
Java 并发 API 中包含许多Collection
类,例如ArrayList
的CopyOnArrayList
,HashMap
的ConcurrentHashMap
和HashSet
的CopyOnWriteArraySet
。 但是,尽管有所有这些示例,但没有类似ConcurrentHashSet
的东西。 许多开发人员说,他们可以使用具有相同值的ConcurrentHashMap
来实现所需的集合,但是这种方法的问题在于,您拥有的是映射而不是集合。 因此,这将导致无法使用伪值对ConcurrentHashMap
执行设置操作。 简而言之,它不是Set
。
还有其他尝试创建ConcurrentHashSet
的即兴创作,但现在它们都已过去,因为 Java 8 添加了newKeySet()
,它返回由ConcurrentHashMap
支持的Set
。
如何在 Java 8 中创建ConcurrentHashSet
ConcurrentHashMap<String, Integer> example = new ConcurrentHashMap<>();
Set<String> exampleSet = example.newKeySet();
exampleSet.add("example");
exampleSet.add("example2");
exampleSet.contains("example2");
exampleSet.remove("example");
在上面的示例中,我们创建了集合,并向其中添加了 2 个元素,检查它是否包含某个元素并删除了某个元素。
注意: 这是在 Java 中创建线程安全Set
的唯一方法。
Java 程序使用java.util.concurrent.ConcurrentHashMap
类上添加的新方法创建ConcurrentHashSet
。
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class Example {
public static void main(String[] args) throws Exception {
ConcurrentHashMap shoesCost = new ConcurrentHashMap<>();
shoesCost.put("Nike", 80);
shoesCost.put("Adidas", 40);
shoesCost.put("Reebok", 76);
Set shoeCostSet = shoesCost.keySet();
shoeCostSet = shoesCost.newKeySet();
System.out.println("before adding element into concurrent set: " + shoeCostSet);
shoeCostSet.add("Puma");
System.out.println("after adding element into concurrent set: " + shoeCostSet);
shoeCostSet.contains("Adidas");
shoeCostSet.remove("Reebok");
}
}
输出:
before adding an element into the concurrent set: [Nike, Adidas, Reebok]
after adding an element into the concurrent set: [Nike, Adidas, Reebok, Puma]
Java HashMap
示例
原文: https://javatutorial.net/java-hashmap-example
数组的项目存储为有序集合,我们可以通过索引访问它们。 另一方面,Java 中的HashMap
类将项存储为组对,即键/值。 可以通过其他类型的索引访问它们。 此类不能保证随着时间的流逝会有恒定的顺序。 假设正确实现,HashMap
可为诸如get
和put
之类的基本操作提供恒定时间的性能。 就像HashSet
类一样,HashMap
具有初始容量和负载因子。 容量是哈希表中的存储桶数,负载因子只是衡量哈希表在自动增加其容量之前可以容纳多少的度量。像HashSet
一样,默认加载因子为 0.75。
为什么HashMap
重要和有用
- 由于其键/值对,因此易于组织数据。
HashMap
允许 1 个null
键和多个null
值。HashMap
不允许重复的键,但允许重复的值。HashMap
扩展了抽象类AbstractMap
。
继承图
HashMap
中的构造方法摘要
HashMap()
:使用默认的初始容量 16 和默认的加载因子 0.75 初始化一个空的HashMap
。HashMap(int initialCapacity)
:使用指定的初始容量和默认加载因子 0.75 初始化一个空的HashMap
。HashMap(int initialCapacity, float loadFactor)
:使用指定的初始容量和负载因子初始化一个空的HashMap
。HashMap(Map <? extends K, ? extends V> m)
:使用与指定Map
相同的映射初始化一个新的HashMap
。
HashMap
类中的方法
void clear()
:从此映射中删除所有映射。Object clone()
:克隆另一个HashMap
,但是不会克隆键和值本身。boolean containsKey(Object key)
:如果key
在哈希图中,则返回true
,否则返回false
。boolean containsValue(Object value)
:如果值在哈希映射中的任何键中,则返回true
,否则返回false
。V get(Object key)
:返回指定键所映射到的值;如果此映射不包含该键的映射,则返回null
。boolean isEmpty()
:如果地图不包含任何元素,则返回true
。V put(K key, V value)
:将指定的值添加到指定的键。V remove(Object key)
:从哈希图中删除键。V replace(K key, V value)
:仅当当前映射到某个值时,才替换指定键的条目。int size()
:返回此映射中的键/值对的数量。
使用HashMap
进行基本操作,以及它们如何工作以及如何相互交互
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
HashMap<String, String> animals = new HashMap<String, String>();
// putting a key-value pairs within a HashMap
// animal -> name
animals.put("Elephant", "Dicky");
animals.put("Tiger", "Sammy");
animals.put("Lion", "Sim");
System.out.println(animals);
// accessing an item using get()
// gives back the value to the specified key, which means it will return back "Sim"
System.out.println("The name of 'Lion' is: " + animals.get("Lion");
// removing an item using remove()
animals.remove("Elephant");
// getting the size of the hash map
System.out.println("The size of the hash map before clearing: " + animals.size());
// clearing/deleting a whole hash map using clear()
animals.clear()
// getting the size of the hash map
System.out.println("The size of the hash map after clearing: " + animals.size());
}
}
输出:
[Lion=Sam, Tiger=Sammy, Elephant=Dicky]
The name of 'Lion' is: Sam
The size of the hash map before clearing: 2
The size of the hash map after clearing: 0
Java LinkedHashMap
示例
原文: https://javatutorial.net/java-linkedhashmap-example
LinkedHashMap
是哈希表和链表的组合,以可预测的迭代顺序实现Map
接口。HashMap
和LinkedHashMap
之间的区别在于LinkedHashMap
维护着一个双向链表,该列表允许来回扫描所有条目。 顺序保持不变,这意味着将键插入映射的顺序。 如果将重新插入到映射中,则顺序不会受到影响。LinkedHashMap
提供所有可选的Map
操作,还允许空元素。 就时间复杂度而言,就像HashMap
一样,它为诸如add
,contains
和remove
之类的基本操作提供了恒定时间的性能。LinkedHashMap
有两个影响其性能的参数,它们是初始容量和负载系数。 此类中的迭代不受容量的影响,因此在为初始容量选择过高的值方面,它比HashMap
效率更高。
为什么LinkedHashMap
有用
- 它将按照输入到映射中的顺序遍历所有条目。
- 就像在
HashMap
中一样,允许使用空值。 - 使用双向链接的链表,使扫描效率更高。
- 它对添加或访问项目的顺序有额外的了解。
继承图
继承图
LinkedHashMap
中的构造方法摘要
LinkedHashMap()
:构造一个空的插入顺序的 LinkedHashMap,其默认初始容量(16)和默认负载因子(0.75)。LinkedHashMap(int initialCapacity)
:构造一个空的插入顺序的LinkedHashMap
,并将容量设置为指定的 initialCapacity 参数和默认负载因子(0.75)。LinkedHashMap(int initialCapacity, float loadFactor)
:使用指定的容量和负载因子构造一个空的插入顺序的LinkedHashMap
。LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
:使用指定的初始容量,负载系数和排序方式构造一个空的LinkedHashMap
实例。LinkedHashMap(Map <? extends K, ? extends V> m)
:构造一个插入顺序的LinkedHashMap
实例,该实例具有与指定映射相同的映射。
LinkedHashMap
类中的方法
void clear()
:从此映射中删除所有映射。boolean containsValue(Object value)
:如果LinkedHashMap
包含指定的值,则返回true
,否则返回false
。Set<Map.Entry<K,V>> entrySet()
:返回当前映射中包含的映射的设置视图。V get(Object key)
:返回指定键所映射到的值;如果该映射不包含该键的映射,则返回null
。V getOrDefault(Object key, V defaultValue)
:返回指定键所映射到的值;如果该映射不包含该键的映射,则返回defaultValue
。Set<K> keySet
:返回此映射中包含的键的设置视图。protected boolean removeEldestEntry(Map. Entry<K, V> eldest)
:返回此映射中包含的值的集合视图。void put(K key, V value)
:将指定的Value
与指定的Key
相关联。 (它是从 Java 中的Map
类继承的)
有关EnumSet
主要方法的更多信息,请随时访问原始 Oracle 文档。
获取LinkedHashMap
的大小,检查其在特定键中是否包含某个值,并检查其是否为空,最后从LinkedHashMap
中删除键:
import java.util.*;
public class LinkedHashMapExample
{
public static void main(String args[])
{
LinkedHashMap<String, String> student =
new LinkedHashMap<String, String>();
student.put("name", "Joe");
student.put("major", "Computer Science");
student.put("marital status", "Single");
System.out.println(student);
// printing the value of the key called name
System.out.println("Key 'name's value: " + student.get("name"));
// getting the size of the linkedHashMap (size() is inherited from Map)
System.out.println("Size of the LinkedHashMap: " + student.size());
// checking whether the map is empty or not
System.out.println("Is the map empty: " + student.isEmpty());
// checking whether the linkedHashMap contains the key specified as an argument
System.out.println("Does it contain 'marital status'? "+ student.containsKey("marital status"));
// deleting/removing an element from the linkedHashMap works by using the
//remove method
System.out.println("Deleting element 'name': " + student.remove("name"));
}
}
输出:
{name=Joe, major=Computer Science, marital status = Single}
Key 'name's value: Joe
Size of the LinkedHashMap: 3
Is the map empty: false
Does it contain 'marital status'? true
Deleting element 'name': "Joe"
使用clear()
清除LinkedHashMap
import java.util.*;
public class LinkedHashMapExample {
public static void main(String[] args)
{
LinkedHashMap<String, String> student =
new LinkedHashMap<String, String>();
li_hash_map.put("name", "Joe");
li_hash_map.put("major", "Computer Science");
li_hash_map.put("marital status", "Single");
System.out.println("Current stage of linkedHashMap: " + student);
// Clearing the linked hash map using clear()
li_hash_map.clear();
System.out.println("Stage after the clear() method: " + student);
}
}
输出:
Current stage of linkedHashMap: {"name"="Joe", "major"="Computer Science", "marital status ="Single"}
Stage after the clear() method: {}
Java TreeMap
示例
原文: https://javatutorial.net/java-treemap-example
TreeMap
实现了Map
接口,还实现了NavigableMap
以及Abstract
类。 映射是根据其键的自然顺序或通过提供初始化时间的比较器进行排序的。 就时间复杂度而言,此实现为containsKey
,get
,put
和remove
操作提供了log(n)
成本。 请务必注意,TreeMap
不会同步,因为如果地图被多个线程访问,并且如果至少胎面在结构上修改了地图,则必须在外部进行同步。
有关TreeMap
的要点
TreeMap
实现Map
接口。TreeMap
不允许使用空键。 而是,抛出NullPointerException
。 虽然,多个空值可以与不同的键关联。TreeMap
是 Java 集合框架的成员。
TreeMap
的继承图
TreeMap
继承图
TreeMap
中的构造方法摘要
TreeMap()
:使用其键的自然顺序构造一个新的空树映射。TreeMap(Comparator<? super K> comparator)
:构造一个新的空树映射,根据给定的比较器排序。TreeMap(Map<? extends K, ?extends V> m):
:构造一个新的树映射,该树映射包含与给定图相同的映射,并根据其键的自然顺序进行排序。TreeMap(SortedMap<K, ? extends V> m)
:构造一个新的树映射,该树映射包含与指定排序图相同的映射并使用相同的排序。
TreeMap
类中的方法
void clear()
:删除树形图中的所有元素。Object clone()
:返回TreeMap
实例的浅表副本。Comprator<? super K> comparator
:返回用于对当前映射中的键进行排序的比较器;如果映射使用其键的自然排序,则返回null
。boolean containsKey(Object key)
:如果当前树映射中存在指定的键,则返回true
。boolean containsValue(Object value)
:如果当前任意一个地图键都存在指定值,则返回true
。V put(K key, V value)
:将指定的值放入指定的键。V remove(Object key)
:从当前映射中删除指定的键。V replace(K key, V value)
:仅当当前映射到某个值时,才替换指定键的条目。int size()
:获取当前树形图具有的元素数。Collection<V> values()
:返回当前映射中包含的值的集合视图。
有关EnumSet
主要方法的更多信息,请随时访问原始 Oracle 文档。
使用containsKey()
和containsValue()
检查当前树形图是否包含指定的键或值,并使用put()
填充TreeMap
import java.util.*;
public class TreeMapExample {
public static void main(String[] args)
{
TreeMap<Integer, String> treeMapExample =
new TreeMap<Integer, String>();
// assing values to keys
treeMapExample.put(5, "java");
treeMapExample.put(15, "tutorial");
treeMapExample.put(20, "dot");
treeMapExample.put(25, "net");
System.out.println("Current stage of the treeMap: " + treeMapExample);
// Checking for the key '15'
System.out.println("Is key 15 present in the map: " +
treeMapExample.containsKey(15);
// Checking for the key '5'
System.out.println("Is the key 5 present? " +
treeMapExample.containsKey(5));
// Checking for value "java"
System.out.println("Is the value 'java' present? " +
treeMapExample.containsValue("java"));
// Checking for value "tutorial"
System.out.println("Is the value 'tutorial' present? " +
treeMapExample.containsValue("tutorial"));
}
}
输出:
Is the key 15 present? true
Is the key 5 present? true
Is the value 'java' present? true
Is the value 'tutorial' present? true
使用remove()
从TreeMap
中删除元素
import java.util.*;
public class treeMapExample {
public static void main(String args[]) {
TreeMap<Integer,String> treeMap=new TreeMap<Integer,String>();
// populating the tree map using put()
map.put(5,"Joe");
map.put(10,"Mike");
map.put(15,"Antony");
System.out.println("Before remove(): ");
// looping through the tree map so we can get each element
for(Map.Entry m:map.entrySet())
{
// print key and value
System.out.println(m.getKey()+" "+m.getValue());
}
map.remove(15);
System.out.println("After remove(): ");
for(Map.Entry m:map.entrySet())
{
// print key and value
System.out.println(m.getKey()+" "+m.getValue());
}
}
}
输出:
Before remove:
5 Joe
10 Mike
15 Antony
After Remove
5 Joe
10 Mike
Java EnumMap
示例
原文: https://javatutorial.net/java-enummap-example
EnumMap
类实现Map
类,并允许使用枚举类型的键。 枚举映射按其键的自然顺序进行维护。 重要的是要注意不允许使用空键。 如果尝试添加空键,则将抛出NullPointerException
。
但是,即使不允许null
键,也允许null
值。 由于所有可能的密钥都是预先已知的,因此可以更快地进行哈希计算。
EnumMap
不同步。EnumMap
比HashMap
快得多
EnumMap
图
EnumMap
继承图
EnumMap
中的构造方法
EnumMap(Class<K> keyType)
:使用指定的密钥类型创建一个空的枚举映射。EnumMap(EnumMap<K, ? extends V> m)
:创建一个与指定枚举图具有相同键类型的枚举图,最初包含相同的映射(如果有)。EnumMap(Map<K, ? extends V> m)
:创建一个从指定映射初始化的枚举映射。
EnumMap
中的方法
void clear()
:从此映射中删除所有映射。EnumMap<K, V> clone()
:返回一个EnumMap
,它表示另一个副本。boolean containsKey(Object key)
:如果当前映射包含指定的键,则返回 true,否则返回 false。boolean containsValue(Object value)
:如果当前映射包含指定值,则返回 true,否则返回 false。boolean equals(Object o)
:比较指定对象与当前映射是否相等。V get(Object key)
:返回指定键所映射到的值;如果键不包含任何值,则返回 null。int hashCode()
:返回当前地图的哈希码。V put(K key, V value)
:将指定的值映射到指定的键。V remove(Object key)
:从地图上移除指定的键。int size()
:返回地图中的对数。
有关所有方法的文档,请访问 Oracle 官方文档页面。
一个程序中某些方法的示例
import java.util.EnumMap;
public class EnumMapExample
{
public enum Hardware
{
MONITOR, MOUSE, KEYBOARD;
}
public static void main(String args[])
{
EnumMap<Hardware, String> hardwareMapInstance = new EnumMap<Hardware, String>(Hardware.class);
/* hardwareMapInstance is empty. Let's populate it. */
hardwareMapInstance.put(Hardware.MONITOR, "Samsung");
hardwareMapInstance.put(Hardware.MOUSE, "Logitech g403");
hardwareMapInstance.put(Hardware.KEYBOARD, "Razer");
/* After having put items, let's see the map size. */
System.out.println("Size: " + hardwareMapInstance.size()); // we could use .size() to loop through an enummap
/* This would print the EnumMap in natural order. (MONITOR => MOUSE => KEYBOARD) */
System.out.println("EnumMap: " + hardwareMapInstance);
/* Getting a specific value from an EnumMap. */
System.out.println("Key: " + Hardware.MONITOR +" Value: " + hardwareMapInstance.get(Hardware.MONITOR));
/* Checking if the EnumMap contains a particular key. */
System.out.println("Does Hardware has " + Hardware.MOUSE + ": " + hardwareMapInstance.containsKey(Hardware.MOUSE));
/* Checking if EnumMap contains a particular value */
System.out.println("Does Hardware has " + Hardware.KEYBOARD + " : " + hardwareMapInstance.containsValue("Razer"));
/* Let's clear the EnumMap */
System.out.println("Clearing the map...");
hardwareMapInstance.clear();
/* Let's get the size now again */
System.out.println("The size after clear() is: " + hardwareMapInstance.size());
}
}
输出:
Size: 3
EnumMap: {MONITOR=Samsung, MOUSE=Logitech g403, KEYBOARD=Razer}
Key: MONITOR Value: Samsung
Does Hardware has MOUSE: true
Does Hardware has KEYBOARD : true
Clearing the map...
The size after clear() is: 0
Java WeakHashMap
示例
原文: https://javatutorial.net/java-weakhashmap-example
Java 中的WeakHashMap
实现了Map
接口,并表示一个具有弱键的哈希表。 如果按键不是通常使用的,则将从地图中自动删除该条目。 这就是它与其他Map
实现的区别。
支持空值和非空值,并且在初始容量和负载因子方面,其性能类似于HashMap
类。
默认情况下,此类不同步。
HashMap
和WeakHashMap
之间的主要区别:
HashMap
具有强引用,而WeakHashMap
具有弱引用。HashMap
在垃圾收集器中占主导地位。WeakHashMap
类中没有clone()
方法,因为它仅实现Map
接口。
WeakHashMap
实现Map
并扩展AbstractMap
。
WeakHashMap
中的构造方法
WeakHashMap()
:创建一个空的WeakHashMap
,默认初始容量为 16,负载系数为 0.75。WeakHashMap(int initialCapacity)
:创建具有指定容量的空WeakHashMap
。WeakHashMap(int initialCapacity, float loadFactor)
:创建具有指定容量和负载因子的空WeakHashMap
。WeakHashMap(Map<? extends K, ? extends V> m)
:创建一个具有与指定映射相同的映射的新WeakHashMap
。
WeakHashMap
中的方法
void clear()
:从当前映射中删除所有映射。boolean containsKey(Object key)
:如果当前映射包含指定键的映射,则返回true
。boolean containsValue(Object value)
:如果当前映射中有映射到指定值的一个或多个键,则返回true
。V get(Object key)
:返回指定键所映射到的值;如果该映射不包含指定键的映射,则返回null
。boolean isEmpty()
:如果映射为空,则返回true
,否则返回false
。V put(K key, V value)
:将指定值“放入”当前映射中的指定键。V remove(Object key)
:如果存在,则从此WeakHashMap
中删除键的映射。int size()
:返回映射中的映射数。
有关所有方法的文档,请访问 Oracle 官方文档页面。
一个执行上述某些方法的示例程序:
// importing the necessary library which is under java.util.*
import java.util.*;
public class WeakHashMapExample
{
public static void main(String args[])throws Exception
{
// declaration of an instance of WeakHashMap that takes a number as a key and string as a value
Map<Number, String> animals = new WeakHashMap<Number, String>();
// populating the map
animals.put(1, "Elephant");
animals.put(2, "Tiger");
animals.put(3, "Lion");
// condition that checks for a certain value
if(animals.containsValue("Tiger"))
System.out.println("Tiger exists.");
// condition that checks for a certain key
if(animals.containsKey(3))
System.out.println("'3' key exists.");
// removing a specific key
animals.remove(1);
System.out.println(animals);
// deletes all mappings
animals.clear();
// check if weakhashmap is empty
if(animals.isEmpty())
System.out.println(animals);
}
}
输出:
Tiger exists.
'3' key exists.
{3=Lion, 2=Tiger}
{}
Java IdentityHashMap
示例
原文: https://javatutorial.net/java-identityhashmap-example
IdentityHashMap
实现Map
接口,并且当检查为k1 == k2
时,两个键被认为是相等的(不是通过使用等于方法)。 这本身违反了Map
的一般合同,这意味着IdentityHashMap
显然不是而非通用Map
的实现。 在很多情况下,此类会很有用。
IdentityHashMap
允许空值和空键以及所有可选的映射操作。 但是,此类不能保证顺序会随着时间的推移保持恒定。
就时间复杂度而言,此类为基本操作提供了恒定时间的性能(例如get
和put
)。
另外,需要注意的是,在必须指定最大大小的意义上,此类不是动态(就像在数组中一样)。 如果映射的大小被充分超过,它将非常昂贵,因此这就是始终提供较大的最大大小的一个好主意。 (比简单的错误要便宜得多!)
IdentityHashMap
:实现和扩展的内容。
IdentityHashMap
中的构造方法
IdentityHashMap()
:创建一个新的身份哈希映射,默认最大大小为 21。IndetityHashMap(int ExpectedMaxSize)
:创建一个具有预期最大大小的新空映射。IdentityHashMap(Map<? extends K, ? extends V> m)
:创建一个新的Identity
,其映射包含与指定映射相同的映射。
IdentityHashMap
中的方法
void clear()
:从此映射中删除所有映射。Object clone()
:返回此标识哈希图的副本(重要的是要注意,键和值本身未克隆)。boolean containsKey(Object key)
:如果映射包含指定的键,则返回true
,否则返回false
。boolean containsValue(Object value)
:如果映射包含指定值,则返回true
,否则返回false
。boolean equals(Object o)
:比较指定对象与此映射是否相等。V get(Object key)
:返回指定键所映射到的值;如果映射不包含对该键的映射,则返回null
。int hashCode()
:返回此映射的哈希码值。boolean isEmpty()
:如果映射为空,则返回true
,否则返回false
。V put(K key, V value)
:将指定值与映射中的指定键关联。V remove(Object key)
:删除到映射中指定键的映射(如果存在)。int size()
:返回此标识哈希图中的键值映射数。
有关所有方法的文档,请访问 Oracle 官方文档页面。
使用上述大多数方法的程序示例:
import java.util.Map;
import java.util.HashMap;
import java.util.IdentityHashMap;
public class IdentityHashMapExample
{
public static void main(String[] args)
{
Map identityHashMap = new IdentityHashMap();
identityHashMap.put("key", "value");
System.out.println("Size of IdentityHashMap: " + identityHashMap.size());
identityHashMap.put("key1", "value1");
System.out.println("Size of IdentityHashMap: " + identityHashMap.size());
System.out.println("Does it contain key 'key1': " + identityHashMap.containsKey("key1"));
System.out.println("Value of key 'key1': " + identityHashMap.get("key1"));
System.out.println("Size of map before clear: " + identityHashMap.size());
identityHashMap.clear();
System.out.println("Size of map after clear: " + identityHashMap.size());
}
}
输出:
Size of IdentityHashMap: 1
Size of IdentityHashMap: 2
Does it contain key 'key1': true
Value of key 'key1': value1
Size of map before clear: 2
Size of map after clear: 0
Java SortedMap
示例
原文: https://javatutorial.net/java-sortedmap-example
SortedMap
接口扩展了映射,并确保所有条目都按升序排列(因此,SortedMap
)。
如果要按降序排列它,则需要重写SortedMap
中的Compare
方法,我们将在稍后进行操作。TreeMap
实现SortedMap
,并按其自然顺序或指定的比较器对键进行排序。在TreeMap
中,不允许使用空键和空值。
方法摘要
Comparator <? super K> comparator()
:返回用于对当前映射中的键进行排序的比较器;如果当前映射使用其键的自然顺序,则返回null
。Set<Map.Entry<K,V>> entrySet()
:返回当前映射中包含的映射的Set
视图。K firstKey()
:返回映射中当前的第一个键(最低还是最高,取决于实现映射的方式(升序还是降序)。SortedMap<K,V> headMap(K toKey)
:返回当前映射中其键严格小于toKey
的部分的视图。Set<K> keySet()
:返回当前映射中包含的键的Set
视图K lastKey()
:返回映射中当前的最后一个(最高或最低)键SortedMap<K,V> subMap(K fromKey, K toKey)
:返回当前映射的部分视图,其有效范围从fromKey
到toKey
SortedMap<K,V> tailMap(K fromKey)
:返回当前映射中键大于或等于fromKey
的部分的视图Collection <V> values()
:返回当前映射中包含的值的集合视图
有关这些方法的更多详细信息,请查阅官方 Oracle 文档。
代码实现
import java.util.*;
public class SortedHashMapExample {
public static void main(String args[]) {
Map<Double, String> players = new TreeMap<Double, String>();
// health, name
players.put(new Double(100.00), "Hill");
players.put(new Double(120.00), "John");
players.put(new Double(150.00), "Sabrina");
players.put(new Double(105.00), "Caitlyn");
players.put(new Double(110.00), "Rachel");
players.put(new Double(130.00), "Michael");
players.put(new Double(140.00), "Mark");
// get a set of the entries
Set setOfEntries = players.entrySet();
// get an iterator
Iterator iterator = setOfEntries.iterator();
while(iterator.hasNext()) {
// create an entry of the map
Map.Entry entry = (Map.Entry)iterator.next();
System.out.println("Key: " + entry.getKey());
System.out.println("Value: " + entry.getValue());
}
}
}
输出
Key: 100.0
Value: Hill
Key: 105.0
Value: Caitlyn
Key: 110.0
Value: Rachel
Key: 120.0
Value: John
Key: 130.0
Value: Michael
Key: 140.0
Value: Mark
Key: 150.0
Value: Sabrina
如您所见,它将自动按升序对它们进行分组。 它的生命值从 100.00 开始,直到 150.00。 我将健康作为关键,并将名称作为值的原因只是为了向您表明它提升了他们。
但是,如果我们希望按降序排列它们怎么办?
使用降序实现
import java.util.*;
public class SortedHashMapExample {
public static void main(String args[]) {
Map<Double, String> players = new TreeMap<Double, String>(new Comparator<Double>() {
@Override
public int compare(Double x, Double y) {
return y.compareTo(x);
}
});
// name, health
players.put(new Double(100.00), "Hill");
players.put(new Double(120.00), "John");
players.put(new Double(150.00), "Sabrina");
players.put(new Double(105.00), "Caitlyn");
players.put(new Double(110.00), "Rachel");
players.put(new Double(130.00), "Michael");
players.put(new Double(140.00), "Mark");
// get a set of the entries
Set setOfEntries = players.entrySet();
// get an iterator
Iterator iterator = setOfEntries.iterator();
while(iterator.hasNext()) {
// create an entry of the map
Map.Entry entry = (Map.Entry)iterator.next();
System.out.println("Key: " + entry.getKey());
System.out.println("Value: " + entry.getValue());
}
}
}
输出
Key: 150.0
Value: Sabrina
Key: 140.0
Value: Mark
Key: 130.0
Value: Michael
Key: 120.0
Value: John
Key: 110.0
Value: Rachel
Key: 105.0
Value: Caitlyn
Key: 100.0
Value: Hill
走你,再简单不过了吧? 我们所做的只是覆盖比较方法,而不是x => y
(升序),我们将其更改为y => x
(降序)。
Java ConcurrentMap
示例
原文: https://javatutorial.net/java-concurrentmap-example
即使Map
由许多类实现,但其中许多不是线程安全的,或者其中一些效率不高。 这就是为什么在 Java 1.5 中引入ConcurrentMap
的原因。 它是线程安全且高效的。
覆盖的默认实现:
count
replaceAll
forEach
getOrDefault
computerIfAbsent
computerIfPresent
ConcurrentMap
由表示为表存储桶的节点数组组成,它们在第一次插入后初始化。
ConcurrentMap
和HashMap
之间的效率比较
- 如果要尽快处理数据,则必须使用所有线程,因此这意味着
ConcurrentMap
在这里更有效。 - 如果只需要单线程访问,则
HashMap
更快。 - 如果由
HashMap
实现,则添加方法的速度快 3 倍。 - 如果由
ConcurrentMap
实现,则 get 方法会更快。
如果程序需要多个线程访问,则ConcurrentMap
是更好的选择。 但是,如果程序仅使用 1 个线程,则HashMap
将是更好的选择。
ConcurrentMap
ConcurrentMap
中的方法
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
:尝试计算指定键及其当前映射值的映射。default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
:如果作为参数给出的键与值不相关(或为null
),则尝试计算其值并输入 进入此映射,除非为null
。default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
:如果指定键的值存在且非空,则尝试计算新映射,给定键及其当前映射值。default void forEach(BiConsumer<? super K, ? super V> action)
:对当前映射中的每个条目执行给定的操作,直到所有条目都已处理。default V getOrDefault(Object key, V defaultValue)
:返回指定键所映射到的值或如果映射不包含该键的映射关系,则返回defaultValue
(作为第二个参数)。default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
:如果键尚未与值关联或与null
关联,则它将关联指定的非null
值。V putIfAbsent(K key, V value)
:如果指定的键尚未与值关联,则将其与给定的值关联。boolean remove(Object key, Object value)
:仅当当前映射到给定值时,才删除键的条目。V replace(K key, V value)
:仅当当前映射到某个值时才替换键的条目。boolean replace(K key, V oldValue, V newValue)
:仅当当前映射到给定值时才替换项的条目。
有关 EnumSet 主要方法的更多信息,请随时访问原始 Oracle 文档。
使用上述某些方法的示例程序
import java.util.concurrent.*;
class ConcurrentHashMapExample {
public static void main(String[] args)
{
ConcurrentHashMap conCurrHashMap = new ConcurrentHashMap();
conCurrHashMap.put(100, "Elephant");
conCurrHashMap.put(101, "Tiger");
conCurrHashMap.put(102, "Lion");
conCurrHashMap.put(103, "Cow");
// since 103 already exists, this won't work
conCurrHashMap.putIfAbsent(103, "Goat");
conCurrHashMap.remove(103, "Goat");
System.out.println("After removal: " + conCurrHashMap);
// since 103 was removed, this now works
conCurrHashMap.putIfAbsent(103, "Leopard");
System.out.println("After put: " + conCurrHashMap);
// changing Goat to Cheetah
conCurrHashMap.replace(103, "Leopard", "Cheetah");
System.out.println("Final: " + conCurrHashMap);
}
}
输出:
After removal: {100=Elephant, 101=Tiger, 102=Lion, 103=Cow}
After put: {100=Elephant, 101=Tiger, 102=Lion, 103=Cow}
Final: {100=Elephant, 101=Tiger, 102=Lion, 103=Cow}
Java Hashtable
示例
原文: https://javatutorial.net/java-hashtable-example
Hashtable
实现一个哈希表(顾名思义),并将键映射到值(例如LinkedHashMap
)。Hashtable
类允许将非null
对象用作键或值。就像HashMap
一样,Hashtable
有两个影响其性能的参数:初始容量和负载因子。 容量是哈希表中存储桶的数量,初始容量是创建哈希表时存在的容量。 负载因子是在容量自身增加之前允许哈希表获得多大容量的度量。
它是如何工作的?
哈希表使用哈希函数,其目的是计算到插槽(或存储桶)数组中的索引,从中可以找到正确的值。
哈希表工作流程
好处
- 平均查找成本与表中存储的元素数量无关。
- 允许任意插入和删除键值对。
- 在许多情况下搜索树或任何其他表时效率更高。
Hashtable
中的构造方法摘要
Hashtable()
:构造一个具有默认初始容量(11)(与HashMap
或LinkedHashMap
不同)和负载因子(0.75)的新哈希表。Hashtable(int initialCapacity)
:构造一个具有指定容量和默认负载因子(0.75)的新哈希表。Hashtable(int initialCapacity, float loadFactor)
:构造一个具有指定容量和指定负载因子的新哈希表。Hashtable (Map<? extends K, ? extends V> t)
:使用与给定Map
相同的映射构造一个新的哈希表。
Hashtable
类中的方法
void clear
:清除当前哈希表,该哈希表删除/删除所有键/值对。Object clone()
:创建此哈希表的浅表副本。V contains(Object value)
:测试当前值是否映射到哈希表中的任何键。boolean containsKey(Object key)
:测试哈希表中是否存在指定的键。boolean containsValue(Object value)
:测试指定的值是否映射到哈希表中的任何键。boolean equals(Object o)
:将指定的Object
与该Map
比较是否相等。V get(Object key)
:返回键所映射到的值;如果此表/映射不包含键的映射关系,则返回null
。int hashCode()
:根据 Map 接口中的定义,返回此表/地图的哈希码值。boolean isEmpty()
:测试哈希表是否没有键映射到值。V put(K key, V value)
:将指定的值映射到指定的键。V remove(Object key)
:从此哈希表中删除指定的键。V replace(K key, V value)
:仅当当前映射到某个值时,才替换指定键的条目。int size()
:返回此哈希表中的键数。
有关Hashtable
类的主要方法的更多信息,请随时访问原始 Oracle 文档。
从Hashtable
中删除所有键并克隆Hashtable
import java.util.*;
class HashTableExample {
public static void main(String[] arg)
{
Hashtable<Integer, String> hashTable =
new Hashtable<Integer, String>();
Hashtable<Integer, String> hashTableCopy =
new Hashtable<Integer, String>();
hashTable.put(1, "javatutorial");
hashTable.put(2, "dot");
hashTable.put(3, "net");
// create a clone of hashtable 'hashTable'
hashTableCopy= (Hashtable<Integer, String>)hashTable.clone();
System.out.println("values in clone: " + hashTableCopy);
hashTable.clear();
System.out.println("after clearing: " + hashTable);
}
}
输出:
values in clone: {1="javatutorial", 2="dot", 3="net"}
after clearing: {}
Java 中ArrayList
和LinkedList
之间的区别
原文: https://javatutorial.net/difference-between-arraylist-and-linkedlist-in-java
本文介绍了ArrayList
和LinkedList
之间的区别,在这种情况下,我们应该优先选择一个。
由于继承了相同的接口 – List
,因此ArrayList
和LinkedList
共享相同的属性。 但是ArrayList
和LinkedList
有什么区别? 简单地说 – ArrayList
对于一次写入多次读取操作很有用,但不利于从前端或中间进行添加/删除。 另一方面,LinkedList
更适合插入和删除数据。
性能表现
下表通过对LinkedList
和ArrayList
执行不同的操作来显示平均算法复杂度
ArrayList
与LinkedList
操作的复杂度
ArrayList
与LinkedList
性能示例
下面的示例演示使用ArrayList
和LinkedList
上的相同数据进行add
,set
和remove
操作的性能
package javatutorial.net;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ArrayListVsLinkedListExample {
private static final int ELCOUNT = 50000;
public static void main(String[] args) {
List<String> alist = new ArrayList<String>();
List<String> llist = new LinkedList<String>();
// Insertion ////////////////
// ArrayList
long start = System.currentTimeMillis();
for (int i = 0; i < ELCOUNT; i++) {
alist.add("element #" + i);
}
long totalTimeMs = System.currentTimeMillis() - start;
System.out.println("Adding 50K elements in ArrayList took " + totalTimeMs + " ms");
// LinkedList
start = System.currentTimeMillis();
for (int i = 0; i < ELCOUNT; i++) {
llist.add("element #" + i);
}
totalTimeMs = System.currentTimeMillis() - start;
System.out.println("Adding 50K elements in LinkedList took " + totalTimeMs + " ms");
// Modification /////////////
// ArrayList
start = System.currentTimeMillis();
for (int i = 0; i < ELCOUNT; i++) {
alist.set(i, "modified element #" + i);
}
totalTimeMs = System.currentTimeMillis() - start;
System.out.println("Modifying 50K elements in ArrayList took " + totalTimeMs + " ms");
// LinkedList
start = System.currentTimeMillis();
for (int i = 0; i < ELCOUNT; i++) {
llist.set(i, "modified element #" + i);
}
totalTimeMs = System.currentTimeMillis() - start;
System.out.println("Modifying 50K elements in LinkedList took " + totalTimeMs + " ms");
// Removal //////////////////
System.out.println("ArrayList size before removal " + alist.size());
System.out.println("LinkedList size before removal " + llist.size());
// ArrayList
start = System.currentTimeMillis();
for (int i = 0; i < ELCOUNT; i++) {
alist.remove(0);
}
totalTimeMs = System.currentTimeMillis() - start;
System.out.println("Removing 50K elements in ArrayList took " + totalTimeMs + " ms");
// LinkedList
start = System.currentTimeMillis();
for (int i = 0; i < ELCOUNT; i++) {
llist.remove(0);
}
totalTimeMs = System.currentTimeMillis() - start;
System.out.println("Removing 50K elements in LinkedList took " + totalTimeMs + " ms");
System.out.println("ArrayList size after removal " + alist.size());
System.out.println("LinkedList size after removal " + llist.size());
}
}
这是执行示例代码的输出。 结果将因不同的计算机配置而异
Adding 50K elements in ArrayList took 10 ms
Adding 50K elements in LinkedList took 7 ms
Modifying 50K elements in ArrayList took 7 ms
Modifying 50K elements in LinkedList took 6315 ms
ArrayList size before removal 50000
LinkedList size before removal 50000
Removing 50K elements in ArrayList took 135 ms
Removing 50K elements in LinkedList took 4 ms
ArrayList size after removal 0
LinkedList size after removal 0
如您在上面的输出中看到的:
LinkedList
在访问和修改元素方面明显较慢LinkedList
添加元素的速度较慢LinkedList
从列表开头删除元素的速度要快得多ArrayList
插入新元素的速度较慢ArrayList
在访问和修改元素方面明显更快ArrayList
从列表开头删除元素的速度明显较慢
结论
在所有情况下,算法复杂度和运算性能都不是恒定的。 您必须考虑两个主要因素 - 列表的大小以及我们使用的元素在列表中的放置位置(在开头,中间或结尾)。 唯一不变的规则是:如果要更快地检索元素,请使用ArrayList
,如果要更快地处理数据,请使用LinkedList
。
Java HashMap
迭代示例
原文: https://javatutorial.net/java-iterate-hashmap-example
本示例说明如何在 Java 中遍历HashMap
在 Java 中遍历集合或数据集是一项非常常见的任务。 您可以使用它来打印或处理数据。 以下示例显示了有关如何遍历HashMap
的三种不同方法。 根据您的 Java 版本,您可以选择其中之一。
使用for
遍历HashMap
这是建议的方法。 它使您可以完全控制地图中的键和值。 使用这种方法,您实际上遍历了映射的EntrySet
,并获得了循环内每个条目的键和值。
每个版本在 Java 版本 1.5 中均可用
import java.util.HashMap;
import java.util.Map;
public class IterateHashMap {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
在 Java 8 中使用 Lambda 表达式遍历HashMap
Java 8+ 版本中提供了这种方法。
import java.util.HashMap;
import java.util.Map;
public class IterateHashMap {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.forEach((key,value) -> System.out.println(key + " = " + value));
}
}
使用Iterator
遍历HashMap
此方法使用java.util.Iterator
浏览HashMap
。 这是 Java 1.4 和更早版本中的默认方法。 尽管下面的示例使用泛型,所以您需要 Java 1.5+ 才能执行它
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class IterateHashMap {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
Iterator<Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> pair = (Map.Entry<String, String>) it.next();
System.out.println(pair.getKey() + " = " + pair.getValue());
}
}
}
Java HashMap
内联初始化
原文: https://javatutorial.net/java-hashmap-inline-initialization
下面的示例演示如何使用标准方法和内联方法初始化 Java HashMap 。
为什么我们需要直接初始化HashMap
?
尽管Map
被广泛用于以动态方式收集和处理数据,但通常您需要创建一个带有预定义键和值的小映射,以测试一种简短的算法或您正在研究的概念。 手动初始化 Java HashMap
的另一个用例是测试程序或算法如何使用特定值执行。 在编写简短的演示程序时,您很可能更愿意直接初始化映射,而不是从文件或某种流中读取数据并用值填充映射。 您可以想象,与单行HashMap
初始化相比,您需要花费更多的精力。
Java 初始化HashMap
初始化HashMap
的必要方法
直接的解决方案是声明一个Map
,然后只需put()
进入Map
。 我们将使用此示例作为参考,以与其他更节省类型的技术进行比较。
Map<String,String> mymap = new HashMap<String, String>();
test.put("A","one");
test.put("B","two");
如您在上面的示例中看到的,首先创建一个映射对象,然后在其中放置条目。 这里要提到的另一重要事项是,通过使用此方法,您可以创建动态映射,并且可以编辑,删除或创建新条目。
使用匿名子类初始化HashMap
这将创建HashMap
的匿名子类,其实例初始化程序将这些值放入。 换句话说,它将创建一个继承自HashMap
的新类。 这是创建和初始化动态HashMap
的最短方法
Map<Integer, String> mymap = new HashMap<Integer, String>() {
{
put(1, "one");
put(2, "two");
}
};
初始化不可变映射
与动态映射(如上例所示)相比,不可变映射是不可编辑的。 这意味着您不能在其中添加新条目,删除条目或更新它们。 如果您想测试一个简单的算法,它们将对您很好。
在 Java SE 8 和更早版本中初始化不可变映射
以下示例演示了如何在 Java 8 或更早版本的 Java 中初始化不可变Map
:
Map<Integer,String> mymap = new HashMap<>();
mymap.put(1,"one");
mymap.put(2,"two");
mymap.put(3,"three");
Map<Integer,String> immutableMap = Collections.unmodifiableMap(mymap);
如您所见,您以老式方式创建和初始化Map
。 使映射不可变的原因是对Collections.unmodifiableMap(Map map)
的调用。 好吧,它看起来不太像内联初始化。 不用担心,下一个示例可以。
以 Guava 方式内联初始化HashMap
Guava 是第一个引入真正的单行Map
初始化的人。 参见下面的例子
Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
用 Java 9 方式内联初始化HashMap
最后,在 Java 9 中,我们可以使用单行语句来初始化不可变的HashMap
,而无需第三方库。
Map<Integer,String> map = Map.of(1, "A", 2, "B", 3, "C");
您可能需要阅读完整的 Java 9 不可变映射示例,以了解更多详细信息。
Java 中HashMap
和TreeMap
之间的区别
原文: https://javatutorial.net/difference-between-hashmap-and-treemap-in-java
在本文中,我将解释 java HashMap
和 java TreeMap
之间的区别
尽管两者都实现了Map
接口并提供了大多数相同的功能,但是HashMap
和TreeMap
具有不同的实现。 最重要的区别是通过条目进行迭代的顺序。
查看下表,直观了解HashMap
和TreeMap
之间的区别
Java 中HashMap
和TreeMap
之间的区别
HashMap
和TreeMap
之间的主要区别
TreeMap
是SortedMap
的示例,由红黑树实现,这意味着对键的顺序进行了排序。 遍历键时,您可以依靠它们会井然有序的事实。 键的顺序由元素的compareTo()
方法或外部提供的比较器确定。
HashMap
则不做任何保证。 它由哈希表实现。 因此,当迭代HashMap
的键时,您不能确定它们将以什么顺序排列。
看下面的例子:
package javatutorial.net;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class HashMapVsTreeMapExample {
public static void main(String[] args) {
Map<Integer, String> hMap = new HashMap<Integer, String>();
hMap.put(5, "A");
hMap.put(11, "C");
hMap.put(4, "Z");
hMap.put(77, "Y");
hMap.put(9, "P");
hMap.put(66, "Q");
hMap.put(0, "R");
Map<Integer, String> tMap = new TreeMap<Integer, String>();
tMap.put(5, "A");
tMap.put(11, "C");
tMap.put(4, "Z");
tMap.put(77, "Y");
tMap.put(9, "P");
tMap.put(66, "Q");
tMap.put(0, "R");
System.out.println("HashMap iteration order =======");
for (Map.Entry<Integer, String> entry : hMap.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
System.out.println("\nTreeMap iteration order =======");
for (Map.Entry<Integer, String> entry : tMap.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
现在看一下该程序的输出:
HashMap iteration order =======
0 = R
66 = Q
4 = Z
5 = A
9 = P
11 = C
77 = Y
TreeMap iteration order =======
0 = R
4 = Z
5 = A
9 = P
11 = C
66 = Q
77 = Y
如您所见,在HashMap
上进行迭代时,我们以“随机”顺序获得条目。 另一方面,TreeMap
迭代以其自然顺序返回条目。
实现复杂度差异
由于HashMap
实现的复杂度为O(1)
,因此通常可以认为HashMap
效率更高,因此无论您何时在乎键的顺序,都可以使用它。 另一方面,TreeMap
中获取,放置和删除操作的复杂度为 O(log n)
允许的键和值的差异
另一个重要的区别是,HashMap
允许使用null
键和值,而TreeMap
仅允许将null
用作其值。
同步(无差异)
请注意,两个实现都不同步,这意味着在这些映射上进行操作不是线程安全的。 如果需要线程安全的Map
,则可能要从java.util.concurrent
包中选择ConcurrentHashMap
类。 这是Map
的线程安全实现,比Collections.synchronizedMap(Map<K,V> m)
提供更好的并发性
Java 图示例
原文: https://javatutorial.net/graphs-java-example
图通常由顶点和弧线组成。 有时,它们也称为节点(而不是顶点)和边(而不是弧)。 为了本教程的缘故,我将使用节点和边作为参考。
图通常看起来像这样:
图可视化
在许多情况下,节点和边被分配了值。 一个非常有用的图的著名示例是,当节点代表城市并且边沿代表这两个节点(或与此有关的城市)之间的距离时。 这样的例子可以在下面看到:
从上图判断,很容易理解它代表什么,也很容易阅读。 芝加哥到纽约的距离是 791.5 英里,纽约和华盛顿特区的距离是 227.1 英里。
这只是一个简单的示例,说明如何使用图很有用,但是还有更多示例。
图的其他有用示例可能是表示家谱,facebook 联系人,甚至是旅行路线。
无向图
当图无向时,这意味着可以在两个方向上遍历边。
无向图
有向图
定向图时,这意味着只能沿其“指向”的方向遍历这些边。
有向图
Java 中的图实现
Node.java
import java.util.*;
public class Node {
private int id;
private List<Edge> neighbours = new ArrayList<Edge>();
public int getNodeId() {
return this.id;
}
public void addNeighbour(Edge e) {
if(this.neighbours.contains(e)) {
System.out.println("This edge has already been used for this node.");
} else {
System.out.println("Successfully added " + e);
this.neighbours.add(e);
}
}
public void getNeighbours() {
System.out.println("List of all edges that node " + this.id +" has: ");
System.out.println("=================================");
for (int i = 0; i < this.neighbours.size(); i++ ){
System.out.println("ID of Edge: " + neighbours.get(i).getId() + "\nID of the first node: " + neighbours.get(i).getIdOfStartNode() +
"\nID of the second node: " + neighbours.get(i).getIdOfEndNode());
System.out.println();
}
System.out.println(neighbours);
}
public Node(int id) {
this.id = id;
}
}
Node.java
有 3 个方法和 1 个构造函数。
getNodeId()
仅返回每个节点的 ID。
addNeighbour(Edge e)
通过边创建连接,该边作为参数传递到另一个节点。 这是通过将指定的边添加到Node
类的边列表中来完成的。 注意,存在一个if
条件,用于检查此节点的当前边中是否已经存在指定的边e
。
getNeighbours()
仅用于显示目的。 查看输出,以查看此方法显示信息的精确程度。
构造函数将id
作为参数。
Edge.java
public class Edge {
private Node start;
private Node end;
private double weight;
private int id;
public int getId() {
return this.id;
}
public Node getStart() {
return this.start;
}
public int getIdOfStartNode() {
return this.start.getNodeId();
}
public Node getEnd() {
return this.end;
}
public int getIdOfEndNode() {
return this.end.getNodeId();
}
public double getWeight() {
return this.weight;
}
public Edge(Node s, Node e, double w, int id) {
this.start = s;
this.end = e;
this.weight = w;
this.id = id;
}
}
Edge.java
有 6 个方法和 1 个构造函数。
getId()
仅返回当前边的 ID。
getStart()
返回边从其开始的Node
对象。
getIdOfStartNode()
返回边从其开始的Node
对象的 ID。
getEnd()
返回边“停止”在的Node
对象。
getIdOfEndNode()
返回边“停止”在的Node
对象的 ID。
getWeight()
获取当前Node
对象的权重。
Edge
构造函数采用 4 个参数,并使用它们初始化构造函数。
Graph.java
import java.util.*;
public class Graph {
private List<Node> nodes = new ArrayList<Node>();
private int numberOfNodes = 0;
public boolean checkForAvailability() { // will be used in Main.java
return this.numberOfNodes > 1;
}
public void createNode(Node node) {
this.nodes.add(node);
this.numberOfNodes++; // a node has been added
}
public int getNumberOfNodes() {
return this.numberOfNodes;
}
}
Graph.java
只有 3 个方法,没有构造函数。
checkForAvailability()
检查是否有多个节点。 如果节点数不超过 1 个,则无法建立连接,因为节点本身不能具有优势。 它必须与另一个节点建立连接。
createNode(Node node)
接受类型为Node
的参数,并将该节点添加到节点List
中。 添加节点后,当前图会将节点数增加 1。这样,我们就可以在某个时候将checkForAvailability()
方法评估为true
。
getNumberOfNodes()
返回节点数。
Main.java
public class Main {
public static void main(String args[]) {
Graph graph = new Graph();
Node node1 = new Node(1); // create a new node that contains id of 1
Node node2 = new Node(2); // create a new node that contains id of 2
Node node3 = new Node(3); // create a new node that contains id of 3
graph.createNode(node1); // numberOfNodes should increment by 1
graph.createNode(node2); // numberOfNodes should increment by 1
graph.createNode(node3); // numberOfNodes should increment by 1
Edge e12 = new Edge(node1, node2, 5, 1); // create an edge that connects node1 to node2 and contains weight of 5
Edge e13 = new Edge(node1, node3, 10, 2); // create an edge that connects node1 to node3 and contains weight of 10
if (graph.checkForAvailability()) {
// two nodes can be connected via edge
node1.addNeighbour(e12); // connect 1 and 2 (nodes)
node1.addNeighbour(e13);
node1.getNeighbours();
} else {
System.out.println("There are less than 2 nodes. Add more to connect.");
}
}
}
Main.java
只有一个main
方法。
在main
方法中创建一个图。 之后,将创建 3 个Node
实例。 然后,使用createNode(Node node)
方法将这些Node
实例添加到图中。 之后,将创建 2 个Edge
实例。 第一个将节点 1 连接到节点 2。第二个将节点 1 连接到节点 3。
此后,存在一个if
条件,该条件检查节点数是否大于 1,如果超过,则将Neighbour
添加到node1
。 (e12
是连接node1
和node2
的边。)(e13
是连接node1
和node3
的边)。
输出
Successfully added Edge@15db9742
Successfully added Edge@6d06d69c
List of all edges that node 1 has:
=================================
ID of Edge: 1
ID of the first node: 1
ID of the second node: 2
ID of Edge: 2
ID of the first node: 1
ID of the second node: 3
[Edge@15db9742, Edge@6d06d69c]
可视化以上输出:
问题:是上述程序生成的无向还是有向图? 如果它生成未定义的图,您可以修改 API 来生成定向的图吗? 如果生成有向图,您是否可以修改 API 以生成无向?
答案:上面的图产生一个定向的图,因为顾名思义,弧线“指向”某个位置。 要使其成为无向,您只需删除圆弧的“箭头”,然后将其作为一条简单的线即可。 就像下面的图片代表无向图一样。
Java 深度优先搜索示例
原文: https://javatutorial.net/depth-first-search-example-java
当涉及从 Java 中的给定数据结构访问数据时,搜索和/或遍历同样重要。 图和树是可以使用不同方法搜索和/或遍历的数据结构的示例。
深度优先搜索(简称 DFS)从一个未访问的节点开始,然后开始选择一个相邻节点,直到没有剩余节点为止。 执行完该“过程”之后,您将回溯到另一个选择节点的选择,如果没有,则只需选择另一个未访问的节点即可。
使用栈实现
上图访问的节点的顺序为:5 10 25 30 35 40 15 20
使用栈数据结构实现 DFS
Node.java
代表上图中的每个“球”或“圆”。 它有一个值 ,它代表每个球的“值”。 它也有一个名为Visited
的布尔变量,顾名思义,它表示遍历是否访问了Node
。 第三个实例变量Node
类具有一个ArrayList
,它表示当前节点与的所有相邻节点(或相邻节点)。 (如果您想了解有关ArrayList
的更多信息,可以查看本教程。)
就此类中的方法而言,有一个简单的构造函数(该函数接受一个值并创建一个空的ArrayList
),Setter 和 Getter 方法以及允许添加相邻Node
的方法。
Node.java
import java.util.*;
public class Node{
private int val;
private boolean visited;
private List<Node> adjecents;
public Node(int val) {
this.val = val;
this.adjecents = new ArrayList<>();
}
public void addAdjecents(Node n) {
this.adjecents.add(n);
}
public List<Node> getAdjacenets() {
return adjecents;
}
public int getVal() {
return this.val;
}
public boolean isVisited() {
return this.visited;
}
public void setVal(int v) {
this.val = v;
}
public void setVisited(boolean visited) {
this.visited = visited;
}
}
DFS.java
此类只有一种方法:解决方案。
它使用栈数据结构,并以节点为元素。 它将指定的元素添加到节点,然后将其标记为已访问。 在那之后,有一个while
循环,不断检查栈是否为空。 如果不是,则从栈中删除一个元素,获取要删除的元素的邻居。 然后,存在另一个循环,其目的是将每个邻居节点标记为已访问,并将该邻居节点添加到栈中。
import java.util.*;
public class DFS {
public void stackSolution(Node node) {
Stack<Node> DFS_stack = new Stack<Node>();
DFS_stack.add(node);
node.setVisited(true);
while (!DFS_stack.isEmpty()) {
Node nodeRemove = DFS_stack.pop();
System.out.print(nodeRemove.getVal() + " ");
List<Node> adjs = nodeRemove.getAdjacenets();
for (int i = 0; i < adjs.size(); i++) {
Node currentNode = adjs.get(i);
if(currentNode != null && !currentNode.isVisited()) {
DFS_stack.add(currentNode);
currentNode.setVisited(true);
}
}
}
}
}
Main.java
在此类中,主要方法是创建Node
类的 8 个实例并传递一些值。 (请记住,下面的示例使用上图(图像)。我们将不同的节点作为邻居添加到不同的节点。此后,我们从node5
开始并遍历它)。
import java.util.*;
public class Main {
public static void main(String [] args) {
Node node5 = new Node(5);
Node node10 = new Node(10);
Node node15 = new Node(15);
Node node20 = new Node(20);
Node node25 = new Node(25);
Node node30 = new Node(30);
Node node35 = new Node(35);
Node node40 = new Node(40);
node5.addAdjecents(node10);
node10.addAdjecents(node15);
node15.addAdjecents(node20);
node10.addAdjecents(node25);
node25.addAdjecents(node35);
node35.addAdjecents(node40);
node25.addAdjecents(node30);
DFS demo = new DFS();
System.out.println("DFS traversal of above graph: ");
demo.stackSolution(node5);
}
}
输出:
DFS traversal of above graph:
5 10 25 30 35 40 15 20
Java 广度优先搜索示例
原文: https://javatutorial.net/breadth-first-search-example-java
当涉及从给定数据结构访问数据时,搜索或遍历非常重要。 在这些数据结构中,例如图和树,有多种遍历/搜索元素的方法。
广度优先搜索是这些方法的一个示例。 BFS 是一种遍历树或图形的算法,它从树的根(树中的最高节点)开始或仅从顶部开始,并在当前深度扫描所有相邻节点,然后再继续移动到节点或元素。 下一个深度级别。
简而言之,BFS 必须完成一层,然后继续进行下一层直到没有剩余的任何层。
BFS 使用与深度优先搜索完全相反的工作流程,反之亦然。
在 BFS 和 DFS 之间的实现方面,一个很大的不同是 BFS 使用队列,而 DFS 使用栈。
BFS 的工作流程
BFS 的实现
实现 BFS 时要遵循两个简单的规则:
- 访问给定层上的每个元素
- 移至下一层
一个例子:
在继续进行第 2 层之前,必须先通过第 1 层。
在那之后,这是应该怎么做的:
伪代码
public void breadthFirstSearch(Graph G, Object S)
// G => Graph ; S => Source node
{
define a queue named as Queue;
insert the source node in the Q
mark s as visited
perform a while loop that keeps looping until the queue is not empty
removing the current element from the queue as it will be visited now
perform a for loop that goes through all neighbours of the current element
if condition that checks if the current element/node/vertex is not visited
if it has not been visited, enqueue it and mark it as visited
}
实际代码实现
public void BFS(int s, int l)
{
// create an array that holds boolean values that will have length 'l'
boolean visited[] = new boolean[l];
// create a queue
LinkedList<Integer> q = new LinkedList<Integer>();
// mark the current node as visited and add it to the queue
visited[s]=true;
q.add(s);
while (q.size() != 0)
{
// dequeuing a vertex from queue
s = q.poll();
// get all adjacent vertices of the dequeued vertex if a adjacent has not
// been visited, then mark it visited and enqueue it
Iterator<Integer> k = adj[s].listIterator();
while (k.hasNext())
{
int j = k.next();
if (!visited[j])
{
visited[j] = true;
q.add(j);
}
}
}
}
不同的算法时间复杂度
原文: https://javatutorial.net/algorithm-time-complexities
在开始解释不同的时间复杂度之前,让我们首先看一下实际的算法是什么。
算法的正式定义是“在计算或其他解决问题的操作(尤其是计算机)中要遵循的过程或规则集”。 因此,换句话说,算法是定义的路径,例如计算机使用该算法来完成给定问题的解决方案。
算法工作流程
尽管听起来很简单且不复杂,但事实恰恰相反。 许多算法要花费大量时间才能完成某件事,而有些则没有。 这就是为什么了解给定算法的复杂度非常重要的原因。 那是因为通过了解算法的复杂度,可以使我们了解算法的“价值”,或者说效率如何。
算法示例:
- 图片搜寻
- 语音输入
- 意见建议
- 谷歌地图
- 谷歌新闻
- 等等
具有讽刺意味的算法示例
不同类型的算法复杂度
-
恒定时间:
O(1)
如果时间量不取决于输入大小,则可以说算法大小以恒定时间运行。
一个例子是从数组访问元素。 您只需“调用”数组的索引即可访问数组的元素。
-
线性时间:
O(n)
线性时间是指算法取决于的输入大小。 如果输入大小为 n ,则复杂度也将为 n。
具有这种时间复杂度的算法的一个著名示例是线性搜索。
-
对数时间:
O(log n)
如果执行时间与输入大小的对数成正比,则可以说该算法以对数时间运行。
这种时间复杂度的算法的一个著名示例是二分搜索。
-
二次时间:
O(n^2)
二次时间是指执行时间为输入大小的平方。
例如冒泡排序,选择排序,插入排序。
-
“大 Ω”的定义
大欧米茄,也称为下界,用Ω
符号表示。
大 O
如果执行时间与输入大小的对数成正比,则可以说该算法以对数时间运行。 例如,如果 Java 中有一个数组,其中包含 5 个苹果,并且您需要打印每个苹果,则该数组将为O(5)
或换句话说,为O(数组长度)
或O(n)
。
这种时间复杂度的算法的一个著名示例是二分搜索。
大 Θ
如果T(n)
是Θ(f(n))
,则意味着T(n)
增长(精确)与f(n)
一样快。n + n
仍然是 n。 是不是有点满嘴? 让我们尝试一个更简单的解释。
您可以将大 Θ 视为:
“花费的时间不会超过且不短于”
如何确定给定程序的复杂度
int sumArray(int[] aiNumbers)
{
int iSum = 0;
for (int i=0; i<aiNumbers.length; i++)
iSum += aiNumbers[i];
return iSum;
}
该程序的复杂度只是aiNumbers.length
。 因此,如果此数组的长度为 4,则复杂度为 4。如果aiNumbers.length
为 6,则复杂度为 6。
复杂度是aiNumbers.length
的原因是因为它会循环aiNumbers.length
次。 因此,复杂度为O(N)
。
N = in.length;
i = 0;
while (i < N) {
for (int i=N-2; i<N; i++) {
System.out.println("Do something.");
}
}
上面程序的复杂度为N * N
,即 N 乘以 2 的幂。这是因为for
循环每次将运行 N 次,而整个循环将运行 N 次。 因此,N *N
。因此,该算法的复杂度是二次(O(n^2)
)
for (int i = 0; i < n; i++) {
// do something
}
for (int i = 0; i < n; i++) {
// do something
}
在上面的示例中,算法的时间复杂度为 n 。 这样做的原因是因为有 2 个循环 n 次循环 – n + n
。 简而言之,n + n
就是 n 。
每种算法的可视化表示
视觉表示
图片来源: https://adrianmejia.com/most-popular-algorithms-time-complexity-every-programmer-should-know-free-online-tutorial-course/
在算法和复杂度方面,请尽可能尝试优化算法。 这样做的一个好方法是使用集合等在输入数据中找到共同点。请记住:内存很昂贵,您的时间也很昂贵。
Java 序列化示例
原文: https://javatutorial.net/java-serialization-example
序列化的对象。 这意味着什么? Java 提供了一种功能,该功能将对象表示为字节序列,其中包括对象的数据以及有关对象类型和该对象中存储的数据类型的信息。
将序列化的对象写入文件后,可以稍后对其进行反序列化,这反过来意味着–表示对象及其数据的类型信息和字节可用于在内存中重新创建对象。
创建的字节流与平台无关。 这意味着,如果对象在一个平台上序列化,则可以在另一个平台上反序列化。
ObjectInputStream
和ObjectOutputStrem
是高级流,其中包含用于序列化和反序列化对象的方法。
ObjectInputStream
提供了一个称为readObject()
的方法,该方法可检索对象并将其反序列化。 它返回一个Object
值,这意味着它将需要强制转换为适当的数据类型。 它抛出IOException
。
public final Object readObject()
ObjectOutputStream
提供了许多写入方法,但使用最广泛的是
public final void writeObject(Object object)
writeObject()
方法序列化对象并将其发送到输出流。 如果序列化出错,则可能会引发IOException
。
因此,为了可视化这些方法的确切位置,让我们在上面的图片中显示它们。
Java 序列化示例
DemoObject.java
import java.io.*;
public class DemoObject implements java.io.Serializable {
private String name;
private int age;
public DemoObject (String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
Main.java
import java.io.*;
public class Main {
public static void main(String[] args) {
DemoObject objectExample = new DemoObject("John", 19);
String filename = "file.ser";
DemoObject obj = null;
// serialization
try {
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream output = new ObjectOutputStream(file);
output.writeObject(objectExample);
output.close();
file.close();
System.out.println("Serialization of object has been completed");
}
catch(IOException err) {
System.out.println("IOException occurred");
}
// Deserialization
try {
FileInputStream file = new FileInputStream(filename);
ObjectInputStream input = new ObjectInputStream(file);
obj = (DemoObject) input.readObject(); // cast to the appropriate type
input.close();
file.close();
System.out.println("Deserialization of object has been completed");
System.out.println();
System.out.println("Values of deserialized object are:");
System.out.println("==================================");
System.out.println("Name = " + obj.getName());
System.out.println("Age = " + obj.getAge());
System.out.println("==================================");
}
catch(IOException err) {
System.out.println("IOException is caught");
}
catch(ClassNotFoundException err) {
System.out.println("ClassNotFoundException is caught");
}
}
}
输出:
Serialization of object has been completed
Deserialization of object has been completed
Values of deserialized object are:
==================================
Name = John
Age = 19
==================================
以上的代码实现的细分
我们有一个DemoObject
类,它是一个虚拟类,将对其进行序列化然后反序列化。 它有 2 个实例变量,我们称为名称和年龄。 我们可以将这些变量设置为公开,但是我们希望始终保持安全,因此在上面的示例中将它们声明为私有。
Main.java
强制转换writeObject
和readObject
并基本上将错误处理并打印到控制台。 在示例中,我们有一个设置为null
的对象。 设置为null
是因为它充当反序列化对象的“占位符”。 基本上,我们可以将反序列化的对象复制到该空对象中。
注意:如果您的编辑器显示此警告:
您可以忽略它或将您的 IDE 配置为自动生成 ID。 我总是建议让您的 IDE 为您创建唯一的标识符。
什么时候需要序列化
序列化是一种通用且有效的协议,可在组件之间传输对象。 序列化用于遵循此协议传输对象。
最后一点:请记住,使用一个 Java 版本进行序列化的对象可能无法在另一 Java 版本上运行。 例如:用 Java 6 序列化对象不是一个好主意,用 Java 8 反序列化它们,反之亦然。
Java 反射示例
原文: https://javatutorial.net/java-reflection-example
反射(这是 Java 的功能)允许执行 Java 程序检查自身(或其他代码)并操纵程序的内部属性,例如获取成员的名称并对其执行某些操作,例如删除或显示它们。
默认情况下,Java 中的每个对象都有getClass()
,它基本上确定当前对象的类(即使在编译时未知)。
反射使您可以编写在编译时不必“识别”所有内容的程序,而可以使程序动态化,因为它们可以在运行时链接在一起。
Java 反射的简短示例
import java.lang.reflect.*;
public class Main {
public void print() {
System.out.println("print");
}
public static void main(String args[])
{
try {
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
} catch (Throwable e) {
// manipulate e
System.out.println();
}
}
}
在这里,class.forName
为我们提供了指定的类,然后,它调用getDeclaredMethods
,其目的是检索在该类中定义的方法的列表。
方法m[]
存储我们要在其上调用该方法的类的所有已声明方法,在这种情况下为String
。
尝试并在这里捕获是必需的,就好像您没有它一样,您会收到此错误:
输出
public boolean java.lang.String.equals(java.lang.Object)
public java.lang.String java.lang.String.toString()
public int java.lang.String.hashCode()
public int java.lang.String.compareTo(java.lang.String)
public int java.lang.String.compareTo(java.lang.Object)
public int java.lang.String.indexOf(java.lang.String,int)
public int java.lang.String.indexOf(java.lang.String)
public int java.lang.String.indexOf(int,int)
public int java.lang.String.indexOf(int)
static int java.lang.String.indexOf(char[],int,int,char[],int,int,int)
static int java.lang.String.indexOf(char[],int,int,java.lang.String,int)
public static java.lang.String java.lang.String.valueOf(int)
public static java.lang.String java.lang.String.valueOf(long)
public static java.lang.String java.lang.String.valueOf(float)
public static java.lang.String java.lang.String.valueOf(boolean)
public static java.lang.String java.lang.String.valueOf(char[])
public static java.lang.String java.lang.String.valueOf(char[],int,int)
public static java.lang.String java.lang.String.valueOf(java.lang.Object)
public static java.lang.String java.lang.String.valueOf(char)
public static java.lang.String java.lang.String.valueOf(double)
public char java.lang.String.charAt(int)
private static void java.lang.String.checkBounds(byte[],int,int)
public int java.lang.String.codePointAt(int)
public int java.lang.String.codePointBefore(int)
public int java.lang.String.codePointCount(int,int)
public int java.lang.String.compareToIgnoreCase(java.lang.String)
public java.lang.String java.lang.String.concat(java.lang.String)
public boolean java.lang.String.contains(java.lang.CharSequence)
public boolean java.lang.String.contentEquals(java.lang.CharSequence)
public boolean java.lang.String.contentEquals(java.lang.StringBuffer)
public static java.lang.String java.lang.String.copyValueOf(char[])
public static java.lang.String java.lang.String.copyValueOf(char[],int,int)
public boolean java.lang.String.endsWith(java.lang.String)
public boolean java.lang.String.equalsIgnoreCase(java.lang.String)
public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[])
public void java.lang.String.getBytes(int,int,byte[],int)
public byte[] java.lang.String.getBytes(java.nio.charset.Charset)
public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException
public byte[] java.lang.String.getBytes()
public void java.lang.String.getChars(int,int,char[],int)
void java.lang.String.getChars(char[],int)
private int java.lang.String.indexOfSupplementary(int,int)
public native java.lang.String java.lang.String.intern()
public boolean java.lang.String.isEmpty()
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[])
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable)
public int java.lang.String.lastIndexOf(int)
public int java.lang.String.lastIndexOf(java.lang.String)
static int java.lang.String.lastIndexOf(char[],int,int,java.lang.String,int)
public int java.lang.String.lastIndexOf(java.lang.String,int)
public int java.lang.String.lastIndexOf(int,int)
static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int)
private int java.lang.String.lastIndexOfSupplementary(int,int)
public int java.lang.String.length()
public boolean java.lang.String.matches(java.lang.String)
private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder)
public int java.lang.String.offsetByCodePoints(int,int)
public boolean java.lang.String.regionMatches(int,java.lang.String,int,int)
public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int)
public java.lang.String java.lang.String.replace(char,char)
public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence)
public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String)
public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String,int)
public boolean java.lang.String.startsWith(java.lang.String,int)
public boolean java.lang.String.startsWith(java.lang.String)
public java.lang.CharSequence java.lang.String.subSequence(int,int)
public java.lang.String java.lang.String.substring(int)
public java.lang.String java.lang.String.substring(int,int)
public char[] java.lang.String.toCharArray()
public java.lang.String java.lang.String.toLowerCase(java.util.Locale)
public java.lang.String java.lang.String.toLowerCase()
public java.lang.String java.lang.String.toUpperCase()
public java.lang.String java.lang.String.toUpperCase(java.util.Locale)
public java.lang.String java.lang.String.trim()
从输出中可以看到,String
有很多方法。
设置反射
-
首先,必须获得一个类对象。 获得它的最常见方法是
Class class = Class.forName("java.lang.'class name, for example String'");
-
下一步是调用方法(例如
getDeclaredMethods()
,getFields()
等) -
下一步是使用反射 API 来操纵/更改信息
另一个示例,遵循上述的步骤
import java.lang.reflect.*;
class Demo
{
private double exampleVariable;
public Demo() {
exampleVariable = 2.3;
}
public void method1() {
System.out.println("The instance variable: " + exampleVariable);
}
public void method2(int n) {
System.out.println("The number passed: " + n);
}
private void method3() { // private method
System.out.println("Private method has been called");
}
}
class Main
{
public static void main(String args[]) throws Exception
{
Demo classToBeTested = new Demo();
// creating class object from the class using getClass()
Class classObject = classToBeTested.getClass();
System.out.println("Name of class: " + classObject.getName());
// getting the constructor using getConstructor()
Constructor constructor = classObject.getConstructor();
System.out.println("Name of constructor: " + constructor.getName());
// stores all the methods the class has
Method[] methods = classObject.getMethods();
// printing all method names
for (Method method : methods)
System.out.println(method.getName());
}
}
输出
Name of class: Demo
Name of constructor: Demo
method1
method2
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
Java 中的弱引用
原文: https://javatutorial.net/weak-references-in-java
本文讨论 Java 中弱引用的概念。
在开始之前,让我们先了解一些基础知识。
Java 中有 4 种引用类型:
- 强引用
- 弱引用
- 软引用
- 幻影引用
在这里,我们将讨论弱引用。
弱引用与 Java 中的垃圾收集有关。 垃圾收集只是从内存中自动取消分配未引用对象的方法。
弱引用是指不足以使对象保留在内存中的引用。 因此,弱引用可以让垃圾收集器确定对象的可及性以及所讨论的对象是否应保留在内存中。
弱引用需要显式声明,因为默认情况下,Java 将引用标记为强引用。
什么是弱可达性?
这意味着一个对象既没有强引用也没有软引用指向它,并且只能通过遍历弱引用来访问。
因此,如果该对象被弱引用,则垃圾收集器会将其从内存中删除,这将清除更多空间并实现更好的内存管理。
垃圾收集器删除了弱引用之后,将该引用放入引用队列中,并最终确定以前弱可访问的对象。
弱引用在哪里使用?
-
弱引用主要用于规范化映射的实现中。规范化映射是指映射仅包含特定值的一个实例。
-
弱引用还广泛用于
WeakHashMap
类中。 这是Map
接口的实现,其中每个键值都存储为弱引用。 键值对扩展了WeakReference
类。 因此,垃圾收集器删除此键也会导致实体也被删除。
代码示例:
私有静态类TryingOut<K, V>
扩展了WeakReference<Object>
实现Map.Entry<K, V>
- 失效监听器问题也使用弱引用。 在这种情况下,可以通过弱引用来处理内存泄漏问题。
实现弱引用:
java.lang.ref.WeakReference
类在处理和创建弱引用时使用。
可以使用弱引用的一种实际的实际场景是在建立数据库连接时使用,当使用数据库的应用程序关闭时,垃圾收集器可能会清除该数据库连接。
Java 中的弱引用的编码示例如下所示:
// Illustrating Weak references in Java
import java.lang.ref.WeakReference;
class WeakestRef
{
//coding starts from here
public void something()
{
System.out.println("This is printed out on the screen");
}
}
public class TryingOutWeak
{
public static void main(String[] args)
{
// Strong Reference
WeakestRef obj1 = new WeakestRef ();
obj1.something();
// Creating Weak Reference to WeakestRef -type object to which 'obj1' is also pointing.
WeakReference< WeakestRef > weakref = new WeakReference< WeakestRef >(obj1);
//Now, WeakestRef -type object to which 'obj1' was pointing earlier is not available for garbage //collection. But will be only be garbage collected when JVM needs memory.
Obj1 = null;
// Note: You can also retrieve back the object which has been weakly referenced. It succesfully //calls the method.
Obj1 = weakref.get();
Obj1.something();
}
}
代码的输出:
这被打印在屏幕上
这被打印在屏幕上
Java 8 日期时间 API
原文: https://javatutorial.net/java-8-date-time-api
Java 8 引入了新的日期时间 API,其目的是弥补旧的日期时间 API 的缺点。
以前的日期时间 api 不是线程安全的,新日期时间 api 的替代品是它没有任何设置方法。 新 API 修复的另一个缺点是设计不佳。 旧的 API 具有较少直接的日期操作方法。 而且旧 API 的另一个缺点是程序员必须编写大量代码来处理时区问题。
新的 API 不仅解决了所有这些问题,而且还引入了java.time
包中的 2 个重要类:
- 本地 – 时区处理没有复杂度
- 时区 – 处理各种时区的更复杂的日期时间 API
本地时间 API 在不需要时区时应使用
使用主要方法调用的本地日期时间 API 的示例
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Main {
public static void LocalDateTimeAPI() {
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
String formatedTime = now.format(format);
Month month = now.getMonth();
int day = now.getDayOfMonth();
int seconds = now.getSecond();
System.out.println("Current date: " + date);
System.out.println("Current time: " + time);
System.out.println("Current date and time: " + now);
System.out.println("in foramatted manner " + formatedTime);
System.out.println("Month: " + month + "\nDay: " + day + "\nSeconds: " + seconds);
}
public static void main(String[] args) {
LocalDateTimeAPI();
}
}
输出:
Current date: 2019-07-20
Current time: 14:10:58.492
Current date and time: 2019-07-20T14:10:58.492
in foramatted manner 20-07-2019 14:10:58
Month: JULY
Day: 20
Seconds: 58
时区日期时间 API 示例
import java.time.ZonedDateTime;
import java.time.ZoneId;
public class ZonedTime {
public void testZonedDateTime() {
ZoneId zone = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + zone);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("CurrentZone: " + currentZone);
}
public static void main(String args[]) {
ZonedTime zonedTimeExample = new ZonedTime();
zonedTimeExample.testZonedDateTime();
}
}
输出
ZoneId: Europe/Paris
CurrentZone: Europe/London
如您所见,时区日期时间 API 可以让您访问特定时区,还可以为您提供时区或系统的默认时区。
计时单位示例
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class ChronoUnits {
public void chromoUnits() {
LocalDate today = LocalDate.now();
System.out.println("Current date: " + today);
LocalDate week = today.plus(1, ChronoUnit.WEEKS);
System.out.println("1 week from now: " + week);
LocalDate month = today.plus(1, ChronoUnit.MONTHS);
System.out.println("1 month from now: " + month);
LocalDate year = today.plus(1, ChronoUnit.YEARS);
System.out.println("1 year from now: " + year);
LocalDate decade = today.plus(1, ChronoUnit.DECADES);
System.out.println("1 decade from now: " + decade);
}
public static void main(String args[]) {
ChronoUnits ChronoUnitsExample = new ChronoUnits();
ChronoUnitsExample.chromoUnits();
}
}
输出:
Current date: 2019-07-20
1 week from now: 2019-07-27
1 month from now: 2019-08-20
1 year from now: 2020-07-20
1 decade from now: 2029-07-20
周期和持续时间
周期处理基于日期的时间,而持续时间处理基于时间的时间。
import java.time.temporal.ChronoUnit;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Duration;
import java.time.Period;
public class PeriodDuration {
public void testDuration() {
LocalTime currentTime = LocalTime.now();
Duration offtime5 = Duration.ofHours(5);
LocalTime timeOff5Hours = currentTime.plus(offtime5);
Duration duration = Duration.between(currentTime, timeOff5Hours);
System.out.println("Duration: " + duration);
}
public void testPeriod() {
LocalDate currentDate = LocalDate.now();
System.out.println("Current date: " + currentDate);
LocalDate nextMonth = currentDate.plus(1, ChronoUnit.MONTHS);
System.out.println("Next month: " + nextMonth);
Period timePeriod = Period.between(nextMonth, currentDate);
System.out.println("Period: " + timePeriod);
}
public static void main(String args[]) {
PeriodDuration periodDuration = new PeriodDuration();
periodDuration.testPeriod();
periodDuration.testDuration();
}
}
输出
Current date: 2019-07-20
Next month: 2019-08-20
Period: P-1M
Duration: PT-19H
Java 基本正则表达式
原文: https://javatutorial.net/basic-java-regular-expressions
今天,我将向您展示正则表达式的基础知识以及如何在 Java 中使用它们。
这不会是详尽且详细的正则表达式教程。 正则表达式本身就是一个巨大的话题。 我将仅向您展示基础知识,因此您可以快速开始在项目中使用正则表达式。 没有冗长的解释,没有仓促……仅举几个例子,您就可以开始了。
字符串类中的正则表达式方法
Java 8 中的String
类具有一些使用正则表达式(或 regex)的方法。 这是此方法的列表。 有关更多详细信息,您可以在中检查字符串 javadoc。
boolean String.matches(String regex)
– 判断此字符串是否与给定的正则表达式匹配String replaceAll(String regex, String replacement)
– 用给定的替换项替换与给定的正则表达式匹配的此字符串的每个子字符串String replaceFirst(String regex, String replacement)
– 用给定的替换替换与给定的正则表达式匹配的此字符串的第一个子字符串String[] split(String regex)
– 围绕给定正则表达式的匹配项拆分此字符串String[] split(String regex, int limit)
– 围绕给定正则表达式的匹配项拆分此字符串。 模式被“限制”应用了次数
匹配一个字符
假设我们有以下字符串:"Hello, it’s me"
,我们想用空格分隔其中的单词。
String str = "Hello, it's me";
String[] words = str.split(" ");
for (String word : words) {
System.out.println(word);
}
这将产生以下输出
Hello,
it's
me
但是,如果我们像这样在"it's"
和"me"
之间放置两个空格怎么办:"Hello, it’s me"
?
我们程序的输出将是:
Hello,
it's
me
那么如何避免这种情况呢? 继续阅读,您会发现🙂
重复
+
号表示在同一行中匹配 1 个或多个。
让我们回到前面的示例"Hello, it’s me"
,在"it’s"
和"me"
之间有两个空格。
在我们的代码中,我们将模式更改为 +
。 在这种情况下,我们将连续匹配 1 个或多个空格
String str = "Hello, it's me";
String[] words = str.split(" +");
for (String word : words) {
System.out.println(word);
}
输出为:
Hello,
it's
me
连接
使用正则表达式,我们可以连续匹配一个字符串或多个字符,并以此作为正则表达式模式。 例如,模式ing
将匹配如下:
"Earning money is easy as counting 1 2 33. Nah!"
***
换句话说,如果在我们的字符串中提供ing
,则该字符串匹配。
结合连接和重复
模式:ar+
将匹配
"arrows are not as fast as bullets"
*** **
匹配零个或多个
模式:"it*"
将匹配
"it‘s fun to learn just sitting in front of my computer"
** **** *
*
(星号)将匹配i
,后跟零个或多个t
。 在我们的示例中,它将匹配it
,itt
,i
,i
交替
模式:ea|in
将匹配
"earning money is easy as counting 1 2 33. Nah!"
** ** ** **
|
(竖线符号)将匹配ea
或in
。
字符类
模式:[123]
将匹配
"Earning money is easy as counting 1 2 33. Nah!"
* * **
[]
匹配集合中的任何字符
匹配范围
模式:[1-3]
将匹配
"Earning money is easy as counting 1 2 33. Nah!"
* * **
模式:[a-f]
将匹配
"Earning money is easy as counting 1 2 33. Nah!"
* * * * *
排除字符
模式:[^a-z123]
将匹配
"Earning money is easy as counting 1 2 33. Nah!"
* * * *
^
表示此集合中没有任何字符
验证电子邮件地址正则表达式
我将举一个示例,说明如何使用 Java 验证电子邮件地址。 这更复杂,但常用的正则表达式模式:
^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$;
和实现:
String email = "info@javatutorial.net";
if (email.matches("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$;")) {
System.out.println(email + "is a valid e-mail address");
} else {
System.out.println(email + "is invalid e-mail address");
}
使用 Java 检索可用磁盘空间
原文: https://javatutorial.net/free-disk-space-java
有时您想知道硬盘上还剩下多少磁盘空间。 有几种方法可以做到这一点。 在此示例中,我将向您展示如何使用 Apache Commons 来实现此目的。
您将需要commons-io
来运行此示例。 您可以在 apache 下载页面上获得它,也可以使用 Maven 依赖项:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
这是一个示例方法,返回执行应用程序的驱动器的可用驱动器空间(以 KB 为单位)。
private long getFreeSpaceKb() {
try {
return FileSystemUtils.freeSpaceKb(new File(".").getAbsolutePath());
} catch (IOException e) {
return 0;
}
}
您可以在第 3 行上更改代码以检索特定驱动器上的可用空间:
FileSystemUtils.freeSpaceKb(new File("c:")); // in windows
FileSystemUtils.freeSpaceKb(new File("/")); // in Unix/Linux
Java 生成 MD5 哈希和
原文: https://javatutorial.net/java-generate-md5-hash-sum
在此示例中,我将向您展示如何使用 Java 为给定的字符串生成 MD5 十六进制和
有时您可能需要为给定的字符串创建 MD5 和。 这通常用于混淆数据库中的密码或验证字符串的内容。 例如,如果字符串已更改,则 MD5 十六进制总和可以显示给您。
什么是 MD5
MD5 算法是一种加密哈希函数,它产生 128 位(16 字节)的哈希值,通常以文本格式表示为 32 位十六进制数。 不要将哈希算法与加密算法相混淆。 哈希和加密之间的区别在于,哈希字符串无法还原为原始值,加密内容可以解密为原始值。
生成 MD5 哈希值
用 Java 生成 MD5 哈希
一种简单的生成字符串哈希值的方法是使用 Apache commons 编解码器类DiagestUtils
中的静态方法。
如果使用 Maven,请将以下依赖项放入 POM 文件中:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.2</version>
</dependency>
或者您可以从 apache 的网站下载 jar 文件: Apache 通用编解码器
您可以使用以下静态方法使用 MD5 对字符串进行哈希处理:
/**
* Returns the MD5 hex sum of given string
* @param str - the string to be hashed
* @return MD5 hex sum
*/
public static String md5(String str) {
return DigestUtils.md5Hex(str);
}
Java 增加内存
原文: https://javatutorial.net/java-increase-memory
Java 内存模型指定 Java 虚拟机如何与计算机的内存(RAM)配合使用。 在本教程中,我将向您展示如何配置 Java 使用的内存。
Java 内存模型
JVM 内部使用的 Java 内存模型在线程栈和堆之间分配内存。 JVM 中运行的每个线程都有自己的线程栈。 线程栈包含有关线程调用了哪些方法以达到当前执行点的信息。 它还包含每个正在执行的方法的所有局部变量。
基本类型的所有局部变量(boolean
,byte
,short
,char
,int
,long
,float
,double
)都完全存储在线程栈中,因此没有 对其他线程可见。
堆包含 Java 应用程序中创建的所有对象,而不管创建该对象的线程如何。 这包括原始类型的对象版本(例如Byte
,Integer
,Long
等)。
增加堆大小
默认情况下,JVM 使用的最大内存小于物理内存的 1/4。 您可以通过执行以下 Java 代码来检查最大内存:
long maxBytes = Runtime.getRuntime().maxMemory();
System.out.println("Max memory: " + maxBytes / 1024 / 1024 + " MB");
您可以通过设置初始和最大堆大小来配置 Java 程序的内存使用量,如下所示:
java -Xms<initial heap size> -Xmx<maximum heap size>
例如:
java -Xms500m -Xmx6g myprogram
会将初始堆大小设置为 500 MB,将最大堆大小设置为 6 GB。
增加栈大小
在 Windows 上,默认线程栈大小是从二进制文件(java.exe
)中读取的。 从 Java SE 6 开始,此值在 32 位 VM 中为 320k,在 64 位 VM 中为 1024k。 在 x86 Solaris/Linux 上,在 32 位 VM 中为 320k,在 64 位 VM 中为 1024k。
您可以使用–Xss
自变量来增加栈大小。
例如:
java -Xss4m myprogram
将栈大小设置为 4 MB。
Java 属性文件示例
原文: https://javatutorial.net/java-properties-file-example
在本教程中,我将向您展示如何在 Java 中从属性文件存储和检索值。
在许多情况下,您可能希望为 Java 程序准备一个配置文件。 Java 在java.util.Properties
中具有内置机制,可让您轻松访问和更改配置文件中的值。
属性由键和值对构成,二者均表示为String
对象。 您可能会将属性视为持久性哈希表。
将数据写入属性文件
Properties properties = new Properties();
properties.setProperty("server_name", "javatutorial.net");
properties.setProperty("request_timeout", "5000");
OutputStream output = new FileOutputStream("config.properties");
properties.store(output, null);
从属性文件读取数据
InputStream input = new FileInputStream("config.properties");
Properties properties = new Properties();
properties.load(input);
String serverNamere = properties.getProperty("server_name");
在此示例中,我没有包括异常处理以提高可见性。 完成读/写属性后,请不要忘记在程序中添加适当的异常处理并关闭文件输入和输出流。
如何在 Eclipse 上安装 Java 9 Beta
原文: https://javatutorial.net/install-java-9-eclipse
本教程演示了如何在 Eclipse Oxygen 上安装和配置 Java 9 Beta
本教程旨在帮助开发人员使用早期访问 Java 9 构建在正式发布之前测试 Java 9 功能。 本教程已在 Eclipse Oxygen 4.7.0 上使用 JDK 9 build 181 进行了测试。
1.安装 Java 9 Early Access 构建
转到 Java 9 下载页面并下载安装程序包
下载 Java JDK 9
运行可执行文件,按照步骤操作并安装 Java 9
2.安装 Eclipse Oxygen
转到 Eclipse 下载页面并下载“Java 开发人员的 Eclipse IDE”或“Java EE 开发人员的 Eclipse IDE”
未归档的 Eclipse
注意:您将需要预先安装在系统上的 JDK 8 或更早版本。
如果 Java 9 是仅安装的 Java 版本 – Eclipse 将不会启动。 您将在 eclipse 日志文件中发现以下错误:
eclipse.buildId=4.7.0.I20170612-0950
java.version=9
java.vendor=Oracle Corporation
BootLoader constants: OS=win32, ARCH=x86_64, WS=win32, NL=en_US
Framework arguments: -product org.eclipse.epp.package.jee.product
Command-line arguments: -os win32 -ws win32 -arch x86_64 -product org.eclipse.epp.package.jee.product
!ENTRY org.eclipse.osgi 4 0 2017-09-08 12:17:02.768
!MESSAGE Application error
!STACK 1
org.eclipse.e4.core.di.InjectionException: java.lang.NoClassDefFoundError: javax/annotation/PostConstruct
at org.eclipse.e4.core.internal.di.InjectorImpl.internalMake(InjectorImpl.java:410)
at org.eclipse.e4.core.internal.di.InjectorImpl.make(InjectorImpl.java:318)
at org.eclipse.e4.core.contexts.ContextInjectionFactory.make(ContextInjectionFactory.java:162)
at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createDefaultHeadlessContext(E4Application.java:491)
at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createDefaultContext(E4Application.java:505)
at org.eclipse.e4.ui.internal.workbench.swt.E4Application.createE4Workbench(E4Application.java:204)
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:614)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:336)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:594)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:148)
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:151)
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:388)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:243)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:653)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:590)
at org.eclipse.equinox.launcher.Main.run(Main.java:1499)
at org.eclipse.equinox.launcher.Main.main(Main.java:1472)
Caused by: java.lang.NoClassDefFoundError: javax/annotation/PostConstruct
at org.eclipse.e4.core.internal.di.InjectorImpl.inject(InjectorImpl.java:124)
at org.eclipse.e4.core.internal.di.InjectorImpl.internalMake(InjectorImpl.java:399)
... 23 more
Caused by: java.lang.ClassNotFoundException: javax.annotation.PostConstruct cannot be found by org.eclipse.e4.core.di_1.6.100.v20170421-1418
at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:433)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:395)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:387)
at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:150)
at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
... 25 more
3.添加对 Eclipse 的 Java 9 支持
通过将“安装”按钮拖动到正在运行的 Eclipse 窗口中,进入 Eclipse Marketplace 并安装对 Eclipse Oxygen 的 Java 9 支持。
安装对 Eclipse Oxygen 的 Java 9 支持
出现提示时重新启动 Eclipse
4.创建一个 Java 9 Eclipse 项目
打开 Eclipse 并转到“File -> New -> Java Project”
在“新建 Java 项目窗口”中,单击“配置 JRE ...”。
Eclipse 新的 Java 项目窗口
在“Installed JRE”窗口中,按“Add…”按钮
Eclipse 添加新的 JRE
选择“标准 VM”,然后按“下一步 >”按钮
Eclipse 选择标准 VM
在“JRE 主目录”下,按“目录…”按钮,然后浏览并选择您的 JRE 9 根目录。 注意:如果您选择 JDK 目录,则将无法构建项目
选择 JRE 9 目录
按下“完成”按钮进行确认
完成 Java 9 安装
现在,在列表中选择jre-9
并应用更改
选择 Java 9 作为默认 JRE
返回“创建 Java 项目”窗口,使用“使用执行环境 JRE”下拉菜单为新项目选择 Java 9。
选择 Java 9 执行环境
现在您准备尝试一些 Java 9 新功能
您将在此处找到的许多教程都是基于 Edward Lavieri 博士和 Peter Verhas 博士撰写的 Mastering Java 9。 本书将为您提供对 Java 9 新概念和工具的完整而深刻的理解。
Java 9 JShell 示例
原文: https://javatutorial.net/java-9-jshell-example
本教程说明了如何在 Java 9 中使用 JShell 工具
JShell 工具(也称为 REPL(读取评估打印循环))使您能够执行 Java 代码,并立即获得结果。 您可以快速评估表达式或简短的算法,而无需创建,编译或构建新项目。 借助 JShell,您可以执行表达式,使用导入,定义类,方法和变量以及使用导入。 请注意,JShell 是 Java 9 JDK 的一部分,而不是 JRE。
启动 JShell
打开控制台或终端(取决于您的操作系统)。 浏览到 Java 9 JDK 安装文件夹,然后将cd
放入/bin
文件夹。 运行 jshell 可执行文件 – jshell.exe
(如果在 Windows OS 上运行)
启动 JShell
简单的算术运算
使用 JShell,您可以通过将其输入为数学表达式来轻松测试算术运算。 以下示例演示了此概念。 您无需将分号(;
)放在行尾
JShell 算术运算
注意37/2 = 18
的输出。之所以发生这种情况,是因为 JShell 将 37 视为Integer
,因此结果强制转换为(int
)。 将小数点加到 37 将产生Double
结果
JShell 内部变量
JShell 通过打印输入结果为您提供即时反馈。 例如:
jshell> 1 + 3
$1 ==> 4
表达式1 + 3
的结果为 4。JShell 还将此值分配给内部变量$1
。 进一步的命令被分配给递增的$
变量,例如$2
,$3
等。
以下屏幕截图演示了 JShell 内部变量的用法:
jshell 内部变量
您可以通过输入内部变量的名称来打印它的值,如下所示:
jshell> $1
$1 ==> 4
您可以为内部变量分配一个新值:
jshell> $1 = 20
$1 ==> 20
表达式1 + 3
会生成一个整数,对吗? 那么,如果您尝试为不同类型的内部变量分配值,会发生什么? 让我们尝试为$1
变量赋双精度值 15.8
jshell> $1 = 15.8
如您在上面的屏幕截图中所见,这解决了“不兼容的类型错误”。 创建内部变量后,无法再更改类型。
内部变量不仅用于存储基本类型。 它们还可以处理对象。 在以下示例中,我们创建一个String
类型的内部变量:
jshell> new String("Hello")
$4 ==> "Hello"
JShell 自动补全
JShell 具有内置的自动补全功能(也称为 Tab 补全功能),通过该功能,您只需按 Tab 键就可以最小化键入。 开始键入并按 Tab 键。 这将完成您开始键入的单词(如果只有一个选项可用)或显示可能的选项列表。 看下面的例子。 我们开始输入Str
,然后按 Tab 键:
jshell 自动补全功能
JShell 自定义导入
默认情况下,以下软件包被导入到 JShell 中:
java.io. *
java.math.*
java.net.*
java.nio.file.*
java.util.*
java.util.concurrent.*
java.util.function.*
java.util.prefs.*
java.util.regex.*
java.util.stream.*
提示:您可以使用/imports
命令列出导入
您可以轻松导入 Java SE 中的软件包。 如果要查找软件包名称,请检查官方 Java 9 SE javadoc 网站
下面的示例演示如何导入java.time.LocalDate
以创建新的LocalDate
对象
jshell 定制导入
JShell 示例程序
测试List
反向方法的典型 Java 类如下所示:
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ListUtils {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C");
System.out.println(reverse(list));
}
public static List<String> reverse(List<String> list) {
List<String> listCopy = list.subList(0, list.size());
Collections.reverse(listCopy);
return listCopy;
}
}
上面显示的代码具有导入,main
方法和System.out.println()
来显示结果。 我们可以在 JShell 中跳过所有 3 个。 这就是我们可以使用 JShell 测试相同功能的方法:
jshell 的例子
您将在上面的示例中注意到,我们照常创建类。 此外,我们的输入列表是在类之外创建的。 创建列表后查看立即输出,显示其值
list ==> [A, B, C]
现在我们只在类中调用Utility
方法
jshell> ListUtils.reverse(list)
并显示结果输出:
$3 ==> [C, B, A]
如何退出 JShell
您可以随时输入以下内容退出 jshell
jshell> /exit
或按Ctrl + C
您在此处找到的许多教程都是基于 Edward Lavieri 博士和 Peter Verhas 撰写的 Mastering Java 9 。 本书将为您提供对 Java 9 新概念和工具的完整而深刻的理解。
如果您喜欢我的帖子或有任何问题/建议/类型错误,请给我评论。
感谢您阅读我的教程。
Java SE 9 学习愉快!
Java 9 不可变列表示例
原文: https://javatutorial.net/java-9-immutable-list-example
本示例演示了如何使用新的 Java 9 Collections
工厂方法创建不可变列表。
在 Java 9 发行版中,Oracle 从其他流行的 JVM 语言(例如 Kotlin,Guava,Scala 等)获取工作实践方面迈出了第一步。做到这一点时要适当考虑到跨语言的向后兼容性和语法一致性。 JDK 。 这些 Java 9 新功能之一是创建不可变(不可修改)列表。
Java 9 不可变列表示例
在 Java 9 之前,创建不可变List
是某种冗长的任务。 例如,要使用 Java 9 之前的版本创建一个空的不可变列表,我们使用以下代码:
package javatutorial.net;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UnmodifiableListExample {
public static void main(String[] args) {
List<String> emptyList = new ArrayList<>();
List<String> immutableList = Collections.unmodifiableList(emptyList);
}
}
什么是不可变列表
一旦在 Java 中创建了不可变对象,就无法更改值。 不可变列表也不例外。 您不能:
- 从列表中添加或删除条目。 如果您尝试这样做,将在
java.lang.UnsupportedOperationException
中解决 - 修改列表的元素。 尝试这样做会导致
java.lang.UnsupportedOperationException
- 将空元素添加到列表中。 如果您尝试将
null
元素添加到列表中,则会以java.lang.NullPointerException
结尾
在 Java 9 中创建空的不可变列表
要在 Java 9 中创建一个空列表,我们需要做的就是调用List
工厂方法of()
,请参见下面的示例
package javatutorial.net;
import java.util.List;
public class Java9EmptyImmutableListExample {
public static void main(String[] args) {
List<String> emptyList = List.of();
}
}
在 Java 9 中使用元素创建不可变列表
有 10 种工厂方法可创建最多 10 个元素的不可变列表(来源:Java 9 List
接口 Javadoc):
static <E> List<E> of(E e1)
Returns an immutable list containing one element.
static <E> List<E> of(E e1, E e2)
Returns an immutable list containing two elements.
static <E> List<E> of(E e1, E e2, E e3)
Returns an immutable list containing three elements.
static <E> List<E> of(E e1, E e2, E e3, E e4)
Returns an immutable list containing four elements.
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5)
Returns an immutable list containing five elements.
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6)
Returns an immutable list containing six elements.
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
Returns an immutable list containing seven elements.
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
Returns an immutable list containing eight elements.
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
Returns an immutable list containing nine elements.
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
Returns an immutable list containing ten elements.
还有一种具有可变数量参数的方法,可让您创建具有令人讨厌的元素数量的不可变列表:
static <E> List<E> of(E... elements)
下面的示例创建一个包含 3 个元素的不可变列表:
package javatutorial.net;
import java.util.List;
public class Java9ImmutableListExample {
public static void main(String[] args) {
List<String> list = List.of("A", "B", "C");
}
}
您将在此处找到的许多教程都是基于 Edward Lavieri 博士和 Peter Verhas 博士撰写的 Mastering Java 9 。 本书将为您提供对 Java 9 新概念和工具的完整而深刻的理解。
Java 9 不可变集示例
原文: https://javatutorial.net/java-9-immutable-set-example
本示例演示了如何使用新的 Java 9 Collection
工厂方法创建不可变的Set
。
在 Java 9 发行版中,Oracle 从其他流行的 JVM 语言(例如 Kotlin,Guava,Scala 等)获取工作实践方面迈出了第一步。做到这一点时要适当考虑到跨语言的向后兼容性和语法一致性。 JDK 。 那些 Java 9 新功能之一是不变(不可修改)集的创建。
Java 9 不可变集示例
在 Java 9 之前,创建不可变Set
是某种冗长的任务。 例如,要使用 Java 9 之前的版本创建一个空的不可变Set
,我们使用以下代码:
package javatutorial.net;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class UnmodifiableSetExample {
public static void main(String[] args) {
Set<String> emptySet = new HashSet<String>();
Set<String> immutableSet = Collections.unmodifiableSet(emptySet);
}
}
什么是不可变集
一旦在 Java 中创建了不可变对象,就无法更改值。 不可变集也不例外。 您不能:
- 从集合中添加或删除条目。 如果您尝试这样做,将在
java.lang.UnsupportedOperationException
中解决 - 修改
Set
元素。 尝试这样做会导致java.lang.UnsupportedOperationException
- 将空元素添加到集合中。 如果您尝试向集合中添加一个
null
元素,则结果为java.lang.NullPointerException
在 Java 9 中创建空的不可变集
要在 Java 9 中创建一个空列表,我们需要做的就是调用List
工厂方法of()
,请参见下面的示例
package javatutorial.net;
import java.util.Set;
public class Java9EmptyImmutableSetExample {
public static void main(String[] args) {
Set<String> emptySet = Set.of();
}
}
在 Java 9 中使用元素创建不可变集
有 10 种工厂方法可创建不可变的Set
,最多可包含 10 个元素(来源: Java 9 Set
接口 Javadoc ):
static <E> Set<E> of(E e1)
Returns an immutable set containing one element.
static <E> Set<E> of(E e1, E e2)
Returns an immutable set containing two elements.
static <E> Set<E> of(E e1, E e2, E e3)
Returns an immutable set containing three elements.
static <E> Set<E> of(E e1, E e2, E e3, E e4)
Returns an immutable set containing four elements.
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5)
Returns an immutable set containing five elements.
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6)
Returns an immutable set containing six elements.
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
Returns an immutable set containing seven elements.
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
Returns an immutable set containing eight elements.
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
Returns an immutable set containing nine elements.
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
Returns an immutable set containing ten elements.
以及一种具有可变数量参数的方法,可让您创建具有任意数量元素的不可变集:
static <E> Set<E> of(E... elements)
下面的示例创建了一个由 3 个元素组成的不可变的Set
:
package javatutorial.net;
import java.util.Set;
public class Java9ImmutableSetExample {
public static void main(String[] args) {
Set set = Set.of("A", "B", "C");
}
}
您将在此处找到的许多教程都是基于 Edward Lavieri 博士和 Peter Verhas 博士撰写的 Mastering Java 9 。 本书将为您提供对 Java 9 新概念和工具的完整而深刻的理解。
Java 9 不可变映射示例
原文: https://javatutorial.net/java-9-immutable-map-example
本示例演示了如何使用新的 Java 9 Collections
工厂方法创建不可变的Map
。
在 Java 9 发行版中,Oracle 从其他流行的 JVM 语言(例如 Kotlin,Guava,Scala 等)获取工作实践方面迈出了第一步。做到这一点时要适当考虑到跨语言的向后兼容性和语法一致性。 JDK 。 那些 Java 9 新功能之一是不变(不可修改)映射的创建。
Java 9 不可变映射示例
在 Java 9 之前,创建不变 Map 是某种冗长的任务。 例如,要使用 Java 9 之前的版本创建一个空的不可变Map
,我们使用以下代码:
package javatutorial.net;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class UnmodifiableMapExample {
public static void main(String[] args) {
Map<Integer, String> emptyMap = new HashMap<>();
Map<Integer, String> immutableEmptyMap = Collections.unmodifiableMap(emptyMap);
}
}
什么是不可变映射
一旦在 Java 中创建了不可变对象,就无法更改值。 不可变的映射也不例外。 您不能:
- 键和值无法添加,删除或更新。 如果您尝试这样做,将在
java.lang.UnsupportedOperationException
中解决 - 修改映射条目。 尝试这样做会导致
java.lang.UnsupportedOperationException
- 在映射中使用
null
键和值。 如果您尝试向映射添加空键或空值,则最终会显示java.lang.NullPointerException
在 Java 9 中创建空的不可变映射
要使用 Java 9 创建一个空Map
,我们需要做的就是调用工厂Map
方法of()
,请参见下面的示例
package javatutorial.net;
import java.util.Map;
public class Java9EmptyImmutableMapExample {
public static void main(String[] args) {
Map<Integer,String> emptyImmutableMap = Map.of();
}
}
在 Java 9 中使用条目创建不可变映射
有 10 种工厂方法可创建多达 10 个键值对的不可变Map
(来源: Java 9 Map 接口 Javadoc ):
static <K,V> Map<K,V> of(K k1, V v1)
Returns an immutable map containing a single mapping.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2)
Returns an immutable map containing two mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3)
Returns an immutable map containing three mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4)
Returns an immutable map containing four mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5)
Returns an immutable map containing five mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6)
Returns an immutable map containing six mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7)
Returns an immutable map containing seven mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8)
Returns an immutable map containing eight mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9)
Returns an immutable map containing nine mappings.
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10)
Returns an immutable map containing ten mappings.
以下示例创建具有 3 个条目的不可变Map
:
package javatutorial.net;
import java.util.Map;
public class Java9ImmutableMapExample {
public static void main(String[] args) {
Map<Integer,String> map = Map.of(1, "A", 2, "B", 3, "C");
}
}
使用Map.ofEntries()
方法在 Java 9 中创建不可变映射
Java 9 中的Map
接口提供了另一种实用程序方法来创建不可变映射 – ofEntries
。 该方法的签名如下所示:
static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)
这使我们可以向Map
插入任意数量的条目。 以下示例创建了 3 个条目,并在ofEntries()
方法的帮助下创建了一个不变的Map
:
package javatutorial.net;
import java.util.Map;
public class Java9ImmutableMapOfEntriesExample {
public static void main(String[] args) {
Map.Entry<Integer,String> e1 = Map.entry(1, "A");
Map.Entry<Integer,String> e2 = Map.entry(2, "B");
Map.Entry<Integer,String> e3 = Map.entry(3, "C");
Map<Integer,String> map = Map.ofEntries(e1, e2, e3);
}
}
您可以通过静态导入java.util.Map.entry
大大缩短代码,请参见以下示例:
package javatutorial.net;
import static java.util.Map.entry;
import java.util.Map;
public class Java9ImmutableMapOfEntriesExample {
public static void main(String[] args) {
Map<Integer,String> map = Map.ofEntries(entry(1,"A"), entry(2,"B"), entry(3,"C"));
}
}
您将在此处找到的许多教程都是基于 Edward Lavieri 博士和 Peter Verhas 博士撰写的 Mastering Java 9 。 本书将为您提供对 Java 9 新概念和工具的完整而深刻的理解。
Java 单例设计模式示例
原文: https://javatutorial.net/java-singleton-design-pattern-example
众所周知,设计模式是为解决特定问题而创建的。单例解决了仅创建一个类的一个对象的问题。 单例设计模式是最流行的设计模式之一,它限制任何类只能有一个对象。 并在需要时使用该对象。 在某些情况下,我们只需要一个类的对象,例如对于数据库连接,我们只需要一个对象来处理与查询相关的每个数据库,而不是每次都创建新对象。 我们仅创建一个数据库连接对象并为其分配全局访问权限,因此可以在任何地方访问它。
立即初始化的单例设计模式
顾名思义,立即初始化意味着在实际需要一个类的实例之前就已经创建了该实例。 通常,它是在系统启动时完成的。 在热切的初始化单例模式中,无论是否有其他类实际请求其实例,都将创建单例实例。 这种方法效果很好,但是有一个缺点。 不论是否在运行时都需要创建实例。 如果此实例不是大对象,并且您可以在不使用它的情况下生存下去,那么这是最好的方法。
具有延迟初始化的单例设计模式
正如我们非常了解延迟初始化一样,它仅遵循延迟人的逻辑,他从来不做任何事情,直到它迫切需要时为止,这与延迟初始化相同。 类的对象仅在需要时才创建,而不像上面那样。 因此,在单例对象中,仅在第一次需要创建对象时才创建一次。
使用单例设计模式的好处
节省内存:很明显,每当我们创建任何类的对象时,它都会占用内存空间,因此创建许多对象意味着会占用更多内存空间。 由于单例类可能只有一个对象,因此可以节省内存。
灵活性:由于实例化过程是由类控制的,因此,类具有更改实例化过程的灵活性。
此设计模式的工作方式
单例模式具有私有构造函数,因为我们知道私有只能访问其类。 因此,它限制了在类之外创建类的对象。
单例模式具有静态成员,因为静态成员仅在内存中获得一次空间。 并且此成员包含单例类的实例。
最后,单例模式具有静态工厂方法,该方法可以全局访问仅单例对象。 它负责将对象返回给调用者,以便他们可以使用它。
单例设计模式的现实生活场景
由于我们大家都对大学非常熟悉,其结构,几乎每个人都上过大学。 在设计大学管理系统时,副校长班将是单身。 因为一所大学只有一名语音校长,所以副校长的角色不能超过一个。 将有员工班级,员工可以是老师,牡丹,实验室助理等。 所有这些都可以具有多个对象,因为任何一所大学中都有许多老师,牡丹,实验室助手。 但是只有一位副总裁,因此通过使用单例模式,我们可以对其进行限制。
单例模式图
上图显示并说明了单例模式的实现
立即初始化的单例设计模式示例
public class EagerSingleton {
// private static member of class which holds the object of the class.
private static EagerSingleton instance = new EagerSingleton ();
// private constructor
private EagerSingleton () {
}
//public method which is responsible making the global access of object
public static EagerSingleton get Instance () {
return instance;
}
}
延迟初始化的单例设计模式示例
public class Lazy Singleton {
// private static member which will hold the object when initialized
private static Lazy Singleton instance = null;
// private constructor
private LazySingleton () {
}
// While any request comes it checks when object is created already or else it creates
// return the object
public static LazySingleton getInstance () {
if (instance == null) {
synchronized (LazySingleton.Class) {
// Double check
if (instance == null) {
instance = new LazySingleton ();
}
}
}
return instance;
}
}
Java 代理设计模式示例
原文: https://javatutorial.net/java-proxy-design-pattern-example
本文介绍了 Java 中的代理设计模式。
代理设计模式是“四人帮”设计模式中的基本计划设计。 辅助结构设计管理如何确认对象之间的连接以改进计划。
代理设计模式如何工作?
代理模式为另一个对象提供代理或占位符,以控制对其的访问。 在计算机编程中,代理设计是产品配置设计。 代理在其最广泛的结构中是充当与其他事物的接口的类。
代理可以连接任何东西:系统关联,内存中的巨大对象,记录或其他昂贵或难以复制的资产。 简而言之,代理是客户调用的包装器或运算符对象,以在后台访问真正的服务对象。 中介的使用基本上可以发送给正品,也可以提供更多的理由。 在代理中,可以授予其他用户的权限,例如,保留对正品项目的活动严重影响资产的时间,或者在对正品对象的任务被构想之前检查前提条件。
对于客户而言,代理对象的使用就像利用正品一样,因为两者都实现了相似的接口。
代理模式图
现实场景中代理设计模式的实现
我们可以创建一个Image
接口,并执行Image
接口的实体类。 ProxyImage
是一个代理类,用于减少RealImage
对象栈的内存印象。
Java 示例的代理模式图
Java 中的代理设计模式示例
创建一个接口
Image.java
public interface Image {
void display();
}
创建实现相同接口的具体类。
RealImage.java
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
ProxyImage.java
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
必要时,使用ProxyImage
获取RealImage
类的对象。
ProxyPatternDemo.java
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
//image will be loaded from disk
image.display();
System.out.println();
//image will not be loaded from disk
image.display();
}
}
验证输出。
Loading test_10mb.jpg
Displaying test_10mb.jpg
Displaying test_10mb.jpg
Java 观察者设计模式示例
原文: https://javatutorial.net/java-observer-design-pattern-example
观察者模式是一种软件设计模式,其中一个称为主题的对象维护着一个依赖于它的所有其他对象(主题)的列表。 这些依赖者称为观察者。 如果主题(所有受抚养者(观察者)的维护者)的状态发生变化,则会自动通知他们。 它通常通过方法调用来完成。
何时使用观察者模式
- 通常在对象之间存在一对多关系时使用。
- 如果一个对象(另一个相关)修改/更改了其“行为”,则需要更新该对象。
- 当主题的观察者数量未知时。
怎么运行的
通常,会有一个Subject.java
文件,其中包含执行所有观察者的添加,删除和更新的所有方法,或者仅初始化它们,并在不同的.java
文件中执行所有方法的功能。 按照约定,还有一个Observer.java
文件,该文件包含一个每次主题更改时都会调用的update()
方法。 之后,您必须创建实现Subject
类或Observer
类的.java
文件。 请注意,这取决于您的类是应实现Subject
还是Observer
类文件。
观察者设计模式
现实生活中的例子
想想您最喜欢的会员服务。 也许是体育馆,或者是 Netflix,或者完全不同。 在本示例中,我们将使用 Netflix,但基本上任何其他成员资格服务都可以使用。 如果 Netflix 更改了其每月会员资格的价格,而您要为此付费,则它应通知您。 实际上,它应该通知所有注册/订阅人员服务。
Java 观察者设计模式示例
Subject.java
public interface Subject {
public double getCurrentPrice();
public void setNewPrice(double newPrice);
public void addNewSubscriber(Observer newSuObserver);
public void removeSubscriber(Observer unsubscriber);
public void notifyAllSubscribers();
}
在Subject.java
中,我们将初始化将在SubscriptionGrabber.java
文件中使用的方法,我将在稍后向您介绍。 这些方法处理添加,删除以及主题状态更改时触发的消息。
Observer.java
public interface Observer {
public void update(double price);
}
这是我们的Observer
类。 它具有将在类SubscriptionObserver.java
文件中创建的update(double price)
方法。 请注意:每次订阅价格更改时,都会调用此方法。
SubscriptionGrabber.java
import java.util.*;
public class SubscriptionGrabber implements Subject {
private List<Observer> subscribers = new ArrayList<Observer>();
private double price;
public SubscriptionGrabber() {
subscribers = new ArrayList<Observer>();
}
public double getCurrentPrice() {
return price;
}
public void setNewPrice(double newPrice) {
this.price = newPrice;
notifyAllSubscribers();
}
public void addNewSubscriber(Observer newSubscriber) {
subscribers.add(newSubscriber);
}
public void removeSubscriber(Observer unsubscriber) {
int indexOfUnsubscriber = subscribers.indexOf(unsubscriber);
subscribers.remove(indexOfUnsubscriber);
}
public void notifyAllSubscribers() {
for (Observer subscriber : subscribers){
subscriber.update(price);
}
}
}
此.java
文件使用Subject
接口。 请注意,在setNewPrice(double newPrice)
方法中,我们在设置新价格后会通知用户。 重要的是,我们必须在价格变化后执行此操作,否则以后,如果其中有更多代码块,则可能会出现问题。 我们的addSubscriber(Observer newSubscriber)
和removeSubscriber(Observer subscriber)
只是在观察者列表中添加人或从观察者列表中删除人。 我们需要在我们的模式中具有删除功能,因为我们的目的是仅通知订阅者,或者仅通知使用我们服务的人员。 我们无需通知不再使用我们服务的人员。 最后,此.java
文件中的final
方法仅对每个订户调用update(double price)
方法(我们仍未创建)。
SubscriptionObserver.java
public class SubscriptionObserver implements Observer {
private double price;
private Subject subscriberGrabber;
private int subscriptionID;
public SubscriptionObserver(Subject subscriptionGrabber) {
this.subscriberGrabber = subscriptionGrabber;
this.subscriptionID+=1;
subscriberGrabber.addNewSubscriber(this);
System.out.println("New Observer: " + this.subscriptionID);
}
public void update(double price) {
System.out.println("Price changed from $" + this.price + " to $" + price);
this.price = price;
}
}
这是实际上(最终)具有更新功能的.java
文件。 请注意我是如何在其中放入打印语句的。 这非常重要(在update
方法中包含打印语句),因为您需要直观地显示用户所发生的事情。 在构造函数中,该参数始终是一个空列表(您将在下一个.java
文件中看到),并且subscriptionGrabber
将是我们的新列表,其中将包含订阅者。 在第 9 行,您可以看到我们正在添加此新订户。
ObserverPattern.java
public class ObserverPattern {
public static void main(String[] args) {
SubscriptionGrabber subscriptionGrabber = new SubscriptionGrabber();
SubscriptionObserver subscriptionObserver = new SubscriptionObserver(subscriptionGrabber);
SubscriptionObserver subscriptionObserver2 = new SubscriptionObserver(subscriptionGrabber);
SubscriptionObserver subscriptionObserver3 = new SubscriptionObserver(subscriptionGrabber);
subscriptionGrabber.setNewPrice(5);
}
}
在这里我们创建抓取程序(列表),然后将该列表作为参数传递给订阅观察者变量作为构造函数参数。 然后,我们将订阅的新价格设置为 5 美元。 预期结果将是通知所有用户价格确实已更改为 5 美元。 试试吧!
Java 观察者模式结果 1
它按预期工作。 现在,我们尝试再创建 2 个订阅,并查看消息是否会弹出 3 次(因为我们将有 3 个订阅)。
Java 观察者模式结果 2
它确实打印了三遍。
Java 工厂设计模式
原文: https://javatutorial.net/java-factory-design-pattern
本文讨论 Java 中的工厂设计模式,这是 Java 中流行的设计模式之一。
Java 是一种非常强大的语言。 每个人都喜欢它的主要原因是通过面向对象的编程实现了更高的可靠性和安全性。
工厂设计模式在 Java 中被广泛使用,考虑到它的使用,它理所当然地赢得了欢迎。 这种设计模式的方法也称为虚拟构造函数。
在用 Java 创建对象时,您可能不知道可能需要什么类型的对象以及在什么地方实现它们。 工厂设计模式正好解决了这个问题。 通过了解设计模式本身的名称,您可以更好地理解。 考虑一个每天生产不同产品的工厂环境。 库存中包含制造产品所需的所有材料,但是事先不知道要制造什么类型的产品。
用编程术语,您可以将清单中的组件作为类进行比较,但是您不知道要在运行时实例化哪些组件(制造什么产品)。
“工厂”一词来自设计模式,它生成许多不同类型的对象,而不必知道创建哪种对象以及如何创建对象。 当我们具有超类和各种子类时,它将投入使用。
工厂设计模式被认为是一种创新的设计模式,并部署在 JDK 和 Spring 和 Struts 等框架中。
为什么要使用工厂设计模式?
- 允许子类选择在运行时创建的对象的类型
- 促进松散耦合,因为它消除了将特定于应用程序的类绑定到代码中的需要
什么时候使用工厂设计模式?
- 当您不知道您的班级需要哪些子班级时
- 当您希望子类指定要生成的对象时
超类充当创建对象的公共接口或抽象类,但让子类决定在运行时实例化哪个类。 因此,可以说子类负责创建该类的实例。
Java 工厂设计模式示例
让我们看一个通过实际示例并对其进行编码来实现工厂设计模式的示例。
工厂设计模式示例
步骤 1:创建一个名为Courses
的抽象类
abstract class Courses{
protected int duration;
protected double fee;
abstract void getDuration();
abstract void getFeePerSemester();
public void calculateTotalFee(){
System.out.println(duration*fee);
}
}//end of Course class
步骤 2:创建扩展课程抽象类的类,这里提供了三门课程,分别是计算机,公共服务和卫生
class Computer extends Courses{
//@override
public void getDuration(){
duration=8; // duration in semesters
}
public void getFeePerSemester(){
fee = 3000; // fee in dollars
}
}//end of Computer class.
class CivilServices extends Courses{
//@override
public void getDuration(){
duration=6; // duration in semesters
}
public void getFeePerSemester(){
fee = 2000; // fee in dollars
}
}//end of CivilServices class.
class Health extends Courses{
//@override
public void getDuration(){
duration=10; // duration in semesters
}
public void getFeePerSemester(){
fee = 5000; // fee in dollars
}
}//end of Health class.
步骤 3:创建一个Admission
类以生成子类的对象
class Admission{
//use admittingCourse method to get object of type Course
public Course admittingCourse (String courseName){
if(courseName == null){
return null;
}
if(courseName.equalsIgnoreCase("Computer")) {
return new Computer();
}
else if(courseName.equalsIgnoreCase("CivilServices")){
return new CivilServices();
}
else if(courseName.equalsIgnoreCase("Health")) {
return new Health();
}
return null;
}
}//end of Admission class.
步骤 4:通过传递所需的信息(例如正在申请的课程,例如计算机,公务员系统和培训课程),使用招生类别获取子类别的对象,从而生成有关课程持续时间和费用的信息。
import java.io.*;
class CourseInformation {
public static void main(String args[]) throws IOException {
Admission newApplication = new Admission();
System.out.print("Enter the course you are trying to admit in: ");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String cousreName = br.readLine();
Course c = newApplication.admittingCourse(courseName);
System.out.print("For " + courseName + " the required duration you would have to study is: ");
c.getDuration();
System.out.print("For " + courseName + " the required fee you would have to pay per semester is: ");
c.getFeePerSemester();
System.out.print("The total amount you will have to pay for the entire course duration is:”);
c.calculateTotalFee();
}
} //end of CourseInformation class.
这是 Java 中工厂设计模式的实际应用程序的源代码。 现在,您知道什么是工厂设计模式,何时使用它以及使用它的好处是什么。
Java 构建器设计模式
原文: https://javatutorial.net/java-builder-design-pattern
在这里,我们将讨论 Java 构建器设计模式,以及在何处以及如何使用它。
这种设计模式也称为创建设计模式,用于从简单对象创建和配置复杂对象。 因此,基本上,它可以帮助我们在生成复杂对象的同时编写可读性,可管理性和可理解性的代码。
构建器模式生成一个构建对象,该构建对象用于构建称为产品的复杂对象。 为了给出教科书的定义,“构建器设计模式将复杂对象的构造与其表示分开,以便相同的构造过程可以创建不同的表示。”
工厂设计模式也是一种创新设计模式。 在使用简单对象生成复杂对象时,必须严格遵循分步方法。 创建此设计模式是为了解决当对象包含许多属性时工厂和抽象工厂设计模式所产生的问题。
我们通常在哪里使用构建器设计模式?
- 当需要对象的多种表示时
- 客户传递的论点太多
- 对象创建包含可选参数
这种类型的设计模式通常使用流畅的界面来实现。
构建器设计模式的实现
Java 中的每个类都有一个由用户显式设置或默认设置的构造函数。 当可以借助许多不同的参数(可能是强制性的,而其他参数也可能是可选的)创建对象时,将使用构建器模式。 在这种情况下,事情变得复杂并且容易出错。 因此,在这种情况下,构建器模式会派上用场。
实现构建器设计模式的步骤:
- 使用所有必填字段创建一个构建器类
Builder
类应具有带有所有必需参数的公共构造函数- 创建方法以获取可选参数的值。 设置可选属性后,这些方法应返回相同的构建器对象。
- 最后,在
Builder
类中提供一个build()
方法,该方法将返回所需的对象。
让我们看一个编码示例,该示例说明在创建复杂对象时如何实现构建器模式。 我们考虑了一个学生数据收集系统,并创建了一个处理此类特定数据收集的Student
类。 如图所示,已生成并实现了一个名为StudentBuilder
的构建器类。
让我们看一个例子:
public class Student {
private int id;
private String firstName;
private String lastName;
private int age;
private String phone;
private String address;
private String course;
public Student(StudentBuilder studentBuilder) {
this.id = studentBuilder.id;
this.firstName = studentBuilder.firstName;
this.lastName = studentBuilder.lastName;
this.age = studentBuilder.age;
this.phone = studentBuilder.phone;
this.address = studentBuilder.address;
this.course = studentBuilder.course;
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public String getCourse() {
return course;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
", course='" + course + '\'' +
'}';
}
public static class StudentBuilder {
private int id;
private String firstName;
private String lastName;
private int age;
private String phone;
private String address;
private String course;
public StudentBuilder(int id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public StudentBuilder withOptionalAge(int age) {
this.age = age;
return this;
}
public StudentBuilder withOptionalPhone(String phone) {
this.phone = phone;
return this;
}
public StudentBuilder withOptionalAddress(String address) {
this.address = address;
return this;
}
public Student buildStudent() {
validateStudentData();
return new Student(this);
}
private boolean validateStudentData() {
//Validation process, check if student is registered in the database
return true;
}
}
}
在主要代码中使用构建器:
Student stu1 = new Student.StudentBuilder(12341, "Jack", "Harrison")
.withOptionalAddress("Address")
.withOptionalAge(21)
.withOptionalPhone("874116073648")
.buildStudent();
System.out.println("Student : " + stu1.toString());
Student stu2 = new Student.StudentBuilder(1225, "Diana", "Daniels")
.withOptionalAge(18)
.buildStudent();
System.out.println("Student : " + stu2);
Java 比较器示例
原文: https://javatutorial.net/java-comparator-example
在本教程中,我们将讨论 Java 比较器及其示例。
什么是 Java 比较器?
Java Comparator
是用于比较 Java 对象的接口。 在java.util.comparator
包中,Java Comparator
的compare(Object 01, Object 02)
方法比较两个 Java 对象。
通过使用可配置的方法,Java Comparator
可以以正数、零或负数返回比较结果。 由于不限于检查数字,因此可以将 Java Comparator
用于比较任何对象。 使用java.io.Serializable
,可以将 Java 比较器同样用于有效地比较序列化的信息结构。
Java Comparator
类似于Comparable
接口,但可用于交换排序顺序,其中Comparable
可通过常规顺序(如字典序)进行排序。
语法
public int compare(Object obj1, Object obj2)
如何使用 Java 比较器?
TreeSet
和TreeMap
都以自然顺序存储元素。 不管怎样,比较器明确地描述了用于排序的顺序。
比较器接口具有两种技术的特征:compare()
和equals()
。存在compare()
方法,专注于请求的两个组成部分:
compare
方法
int compare(Object obj1,Object obj2)
obj1
和obj2
得到比较。 如果对象相等,则此方法返回零。 如果obj1
比obj2
更大,它将返回正数。 否则返回负数。
通过实现compare()
,可以修改比较的方式。 例如,要按反向进行排序,可以使比较器反转比较结果。
equals
方法
存在equals()
方法,用于测试对象是否与调用比较器相符 -
boolean equals(Object obj)
obj
是检查是否相等的对象。 如果obj
和被调用对象都是Comparator
,并且使用类似的方法,则该方法返回真。 否则,它返回false
。
废除equals()
是多余的,大多数直接比较器都不会这样做。
Java 比较器示例
import java.util.*;
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;
}
}
public class Example {
public static void main(String args[]) {
// Takes a list o Dog objects
List<Dog> list = new ArrayList<Dog>();
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
for(Dog a: list) // printing the sorted list of names
System.out.print(a.getDogName() + ", ");
// Sorts the array list using comparator
Collections.sort(list, new Dog());
System.out.println(" ");
for(Dog a: list) // printing the sorted list of ages
System.out.print(a.getDogName() +" : "+ a.getDogAge() + ", ");
}
}
OUTPUT:
Lacy, Roger, Shaggy, Tammy, Tommy,
Tammy : 1, Lacy : 2, Shaggy : 3, Tommy : 4, Roger : 10,
Java 比较器的工作示例。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Comparator;
class Student {
// instance member variables
String Name;
int Age;
// parameterized constructor
public Student(String Name, Integer Age) {
this.Name = Name;
this.Age = Age;
}
public String getName() {
return Name;
}
public void setName(String Name) {
this.Name = Name;
}
public Integer getAge() {
return Age;
}
public void setAge(Integer Age) {
this.Age = Age;
}
// overriding toString() method
@Override
public String toString() {
return "Customer{" + "Name=" + Name + ", Age=" + Age + '}';
}
static class CustomerSortingComparator implements Comparator<Student> {
@Override
public int compare(Student customer1, Student customer2) {
// for comparison
int NameCompare = customer1.getName().compareTo(customer2.getName());
int AgeCompare = customer1.getAge().compareTo(customer2.getAge());
// 2-level comparison using if-else block
if (NameCompare == 0) {
return ((AgeCompare == 0) ? NameCompare : AgeCompare);
} else {
return NameCompare;
}
}
}
public static void main(String[] args) {
// create ArrayList to store Student
List<Student> al = new ArrayList<>();
// create customer objects using constructor initialization
Student obj1 = new Student("Ajay", 27);
Student obj2 = new Student("Sneha", 23);
Student obj3 = new Student("Simran", 37);
Student obj4 = new Student("Ajay", 22);
Student obj5 = new Student("Ajay", 29);
Student obj6 = new Student("Sneha", 22);
// add customer objects to ArrayList
al.add(obj1);
al.add(obj2);
al.add(obj3);
al.add(obj4);
al.add(obj5);
al.add(obj6);
// before Sorting arraylist: iterate using Iterator
Iterator<Student> custIterator = al.iterator();
System.out.println("Before Sorting:\n");
while (custIterator.hasNext()) {
System.out.println(custIterator.next());
}
// sorting using Collections.sort(al, comparator);
Collections.sort(al, new CustomerSortingComparator());
// after Sorting arraylist: iterate using enhanced for-loop
System.out.println("\n\nAfter Sorting:\n");
for (Student customer : al) {
System.out.println(customer);
}
}
}
输出:
Unsorted
111 bbbb london
131 aaaa nyc
121 cccc jaipur
Sorted by rollno
111 bbbb london
121 cccc jaipur
131 aaaa nyc
Sorted by name
131 aaaa nyc
111 bbbb london
121 cccc jaipu
Java 发送电子邮件示例
原文: https://javatutorial.net/java-send-mail-example
JavaMail API 使平台和协议无关的框架,旨在帮助构建消息传递和邮件应用程序。 它是可选软件包,可用于 Java SE 平台,也包含在 Java EE 平台中。
JavaMail 在许多情况下都非常有用。 例如,当用户单击“忘记密码”(通过向用户设置的相应电子邮件发送电子邮件),注册网站中的事件(通过电子邮件发送有关该事件的更多详细信息)时,可以使用该功能。
JavaMail API 中使用的主要协议
- SMTP
- POP
- IMAP
如果您有使用计算机网络的经验,则可能听说过其中的一些(如果不是全部)。 但是,如果您没有计算机网络的经验,那么让我为您分解这些协议。
应用层
在具体介绍电子邮件协议的解释之前,让我首先为您介绍计算机网络中各层的简单示意图。
网络层
您可以看到 SMTP,POP,IMAP 和基本上所有电子邮件协议都在应用程序层下。
传输层的协议为应用程序提供主机到主机的通信服务。
网络层是一组方法,协议和规范,用于在必要时通过网络将源数据包从源主机传输到 IP 地址指定的目标主机。
网络访问层中的协议为系统提供了将数据传递到直接连接的网络上的其他设备的方法。
既然您知道每一层的用途是什么,我们就可以开始更深入地了解什么是 SMTP,POP,IMAP。
SMTP
SMTP 代表简单邮件传输协议。 它是一种标准的互联网协议,用于跨互联网协议网络的电子邮件传输。 还有本身不是协议的 SMTPS。 SMTPS 本质上是受 SSL 保护的 SMTP 连接。
POP
POP 代表邮局协议,是本地电子邮件客户端用来通过 TCP / IP 连接从远程服务器检索电子邮件的应用程序层互联网标准协议。 POP 支持下载和删除访问远程邮箱的要求。
IMAP
IMAP 代表互联网消息访问协议。 电子邮件客户端使用的一种互联网标准协议,它通过 TCP / IP 连接从邮件服务器检索电子邮件。 它还允许电子邮件客户端访问远程邮件服务器上的电子邮件。 传入的电子邮件被发送到电子邮件服务器,该服务器将消息存储在收件人的电子邮件框中。
JavaMail 架构
现在该介绍 Java 邮件架构了。
使用 JavaMail API 的 Java 应用程序工作流程
从图中可以看到,JavaMail API 是客户端层。 它直接与 SPI(服务器/协议层)相关联,然后在 SMTP,IMAP 或 POP 之间进行选择,并在选择协议后发送消息。
使用 JavaMail API 发送电子邮件的示例
在我们开始之前,请确保您已经安装了必要的.jar
文件。 也可以在 Oracle 网站上找到它们。 去那里下载最新版本。 另外,您可能需要下载 fakeSTMP 。 fakeSTMP 用于测试目的。 对于此示例,您需要在计算机上安装 SMTP。
为了能够通过您的 gmail 帐户发送邮件,您应该在 Google 帐户安全设置中允许使用不安全的应用程序(从 gmail 的角度来说就是您的应用程序)。
EmailSender.java
要导入所有必需的库,您需要导入 4 件事。
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
之后,我只需要创建一个主要方法即可将变量放入其中。 将接收电子邮件,发件人电子邮件和主机的电子邮件。
public class EmailSender {
public static void main(String[] args) {
String destinationEmail = "yanicha93@gmail.com";
String senderEmail = "yanicha93@gmail.com";
String host = "localhost";
}
}
接下来的几行将负责获取会话对象。
Properties properties = System.getProperties():
properties.setProperty("mail.smtp.host", host);
Session session = Session.getDefaultInstance(properties);
创建会话对象的目的是什么?
该会话是您如何与邮件主机进行交互的上下文。 这可能包括调试邮件主机的输出,超时和身份验证机制。
最后,我们正在发送实际的消息。
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(senderEmail));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(destinationEmail));
message.setSubject("Hello");
message.setText("Hey, ignore this email, this is just an example");
Transport.send(message);
System.out.println("Sent Successfully");
}
catch (MessagingException mex) {
mex.printStackTrace();
}
在这里,我们正在创建一条消息,紧接着设置发送电子邮件的人(在本例中为senderEmail
。此后,我们只需使用message.addRecipient()
方法添加目标电子邮件即可。然后,设置邮件的主题 电子邮件(您可以将其设置为任意内容),然后我们设置文本(同样,您可以将其设置为任意内容)。然后,我们只需发送邮件即可。
好的! 我们发送了消息。
完整的代码段:
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
public class EmailSender {
public static void main(String[] args) {
String destinationEmail = "yanicha93@gmail.com";
String senderEmail = "yanicha93@gmail.com";
String host = "localhost";
Properties properties = System.getProperties();
properties.setProperty("mail.smtp.host", host);
Session session = Session.getDefaultInstance(properties);
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(senderEmail));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(destinationEmail));
message.setSubject("Hello");
message.setText("Hey, ignore this email, this is just an example");
Transport.send(message);
System.out.println("Sent Successfully");
}
catch (MessagingException mex) {
mex.printStackTrace();
}
}
}
Java volatile
示例
原文: https://javatutorial.net/java-volatile-example
在本教程中,我们将讨论 Java volatile
示例。
什么是 Java volatile
?
Java volatile
关键字用于检查 Java 变量是否为“被存放在基本内存中”。 更具决定性的意义是,将无法从 PC 的主内存中读取每次无法预测的变量,而不是从 CPU 储备中读取,并且每次与不稳定变量保持联系都将与基本内存保持联系,而不是 只需到 CPU 存储。
实际上,由于 Java 5,不稳定的标语确保了除了不可预测因素之外还可以从基本内存中读取内容。 我将在随附领域中对此进行澄清。
如何在 Java 中使用volatile
关键字?
什么是 Java 中的volatile
变量?何时使用 Java 中的volatile
变量是 Java 访谈中众所周知的多字符串查询问题? 尽管有许多软件工程师认识到什么是易失性变量,但是他们在第二部分进行了轰炸,例如在 Java 中利用易失性变量的地方很少,因为在 Java 中很少有明确的理解和动手操作的易失性。 在本教学练习中,我们将通过简单说明 Java 中volatile
变量的情况并检查何时使用 Java 中的volatile
变量来解决此漏洞。 无论如何,Java 中不可预测的流行语被用作指向 Java 编译器和Thread
的指针,这些编译器和Thread
不存储该变量的估计值,而可靠地从主内存中仔细研究了该变量。 因此,如果您需要共享任何因执行而导致读写活动至关重要的因素,例如细读和写int
或布尔变量,则可以将它们声明为volatile
变量。
除了 Java 5 以及诸如自动装箱,枚举,泛型和变量争用之类的实际更改之外,Java 还在 Java 内存模型(JMM)中进行了一些调整,从而确保了感知从一个字符串到另一个字符串产生的更改的可能性,就像“以前发生过的事情”一样,它照顾了 出现在一个字符串中的内存组合问题可能“溢出”并被另一字符串看到。
Java volatile
关键字不能与策略或类一起使用,而必须与变量一起使用。 Java volatile
关键字同样确保可感知性和请求,在 Java 5 与任何不稳定变量保持联系之后,再对不稳定变量进行任何读取。 通过volatile
关键字的路由利用,同样可以预期编译器或 JVM 从代码的重新排序或移动而不会从同步边界结束它们。
/**
* Java program to demonstrate where to use Volatile keyword in Java.
* In this example Singleton Instance is declared as volatile variable to ensure
* every thread see updated value for _instance.
*
* @author Javin Paul
*/
public class Singleton{
private static volatile Singleton _instance; //volatile variable
public static Singleton getInstance(){
if(_instance == null){
synchronized(Singleton.class){
if(_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
当您谨慎地仔细阅读代码时,您可能会发现:
1)我们只是做一个案例
2)在主要招募的季节来临时,使我们昏昏欲睡。
如果我们不使_instance
变量变得比Thread
更易变,这使单例无法传递其他字符串,那么这种情况一直到它离开Singleton
区域为止,所以如果Thread
一个正在处理的Singleton
情况,在创建后不久就失去了 CPU,所有其他字符串可能不会观察到_instance
的估计为无效,因此他们会相信_instance
仍然无效。
Java volatile
示例
为什么? 由于Peruser
字符串不做任何保留,并且在作者字符串离开同步的区域之前,内存不会同步,并且_instance
的估计在原则上不会刷新。 使用 Java 中的Volatile
关键字,此问题由 Java 自己负责,并且所有用户字符串均会注意到这种更新。
因此,在摘要中,与 Java 中的同步流行语分开,另外使用volatile
关键字在字符串之间传递内存的内容。
volatile
可见性问题
Java 不稳定的流行语确保跨字符串的因素变化的可感知性。 这听起来可能有些消化,所以给我一个扩展的机会。
在字符串处理不稳定因素的多线程应用程序中,由于执行原因,每个字符串可能会将基本存储器中的因素复制到 CPU 存储中,同时又将它们切掉。 如果您的 PC 包含一个以上的 CPU,则每个字符串可能会在另一个 CPU 上继续运行。 这意味着,每个字符串可能会将这些因素复制到各种 CPU 的 CPU 储备中。 如图所示:
Java volatile
示例
由于存在非易失性因素,因此没有关于 Java 虚拟机(JVM)何时将基本存储器中的信息细读到 CPU 存储中或将信息从 CPU 保留区组成的信息写入主存储器的证明。 这可能会引起一些问题,我将在随附的区域中予以澄清。
设想一种情况,其中至少两个字符串接近共同的文章,其中包含以这种方式宣布的反因素:
public class SharedObject {
public int counter = 0;
}
Java Docker 和 Docker 容器简介
原文: https://javatutorial.net/introduction-to-docker-and-docker-containers-in-java
简而言之,Docker 是一个工具,通过使用所谓的容器,您可以轻松地构建,部署和运行应用程序。 这些容器使我们可以打包所有必不可少的要素,例如库和依赖项。
另外,这些容器在主机操作系统上运行。
当我们使用 Docker 时会带来很多好处。 它
- 增强了应用程序的可移植性,并且可以非常轻松地创建测试应用程序,而不必依赖于环境
- 优化基础设施
- 将应用程序及其依赖项打包到标准化单元中
容器
不,我不是在谈论现实世界中的容器。 但是,既然您正在考虑它,我不妨作个比喻。 现实生活中的容器的目的是存储需要运输到另一个地方的货物或物品。 现在,这些商品具有不同的存储要求,例如,可能存在牛奶的有效期比例如西红柿短的牛奶。 这就是为什么在现实生活中容器是非常有用的原因–容器保留了内部环境,例如敏感商品和/或物品的温度。 话虽如此,运输业不必担心这些物品,而将重点放在将它们从 A 运送到 B 上。
现在,如果您已经注意了以上段落,那么 Docker 中的容器就是一回事。 多亏了 Docker 的容器,我们可以轻松地将容器化应用程序组件从一种环境迁移到另一种环境,希望不会出现任何问题。
就像现实生活中的容器一样,Docker 中的容器为我们提供了一个隔离的安全的应用程序组件环境。 与现实生活中的容器相比,应用组件是商品。
Docker 容器与虚拟机
根据映像,VM 方法的问题在于它附带了很多东西 - 二进制和库(必不可少的),但最主要的是 - 整个来宾操作系统的总容量为几个 GB。
另一方面,Docker 容器包含应用程序的所有依赖关系,与其他容器共享内核。 此外,Docker 容器可以在任何计算机,基础架构和云上运行。 现在,您可以再次阅读真实容器的类比并看到相似之处。
安装 Docker
如果使用 Windows 或 Mac,请单击此处。
安装 Docker 之后,您将拥有以下内容:
- 用于运行
docker-machine
命令的 Docker Machine - 用于运行 docker 命令的容器引擎
- Docker Compose 用于运行
docker-compose
命令 - Kitematic,Docker GUI
- 为 Docker 命令行环境预先配置的 Shell
- 甲骨文 VirtualBox
要检查您是否已成功安装 docker,请输入以下内容:
docker --version
要查看更多信息,例如容器,正在运行,已暂停或已停止的容器数,映像等,可以键入:
docker info
就是这样! 您的机器上有 Docker。 要编写一个简单的 hello world “程序”,可以键入以下内容:
docker run hello-world
响应:
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1\. The Docker client contacted the Docker daemon.
2\. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3\. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4\. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
安装和配置 MySQL 数据库和服务器以供 Spring 使用
https://javatutorial.net/installing-and-configuring-mysql-database-and-server-for-spring-usage
在本教程的最后,您将安装在 Spring 中开发应用程序所需的正确的 MySQL 产品。 您将需要两件事:MySQL 数据库和 MySQL 服务器。 请按照以下步骤操作。
配置 MySQL 数据库
访问 https://www.mysql.com/downloads/ ,然后选择“社区下载”,如下所示:
社区下载 MySQL
在下一页上,您将看到许多列表。 我们感兴趣的是 MySQL 社区服务器和 Connector/J ,其中 J 代表 Java。
社区服务器和 Java 开发 MySQL
配置 MySQL 服务器
从下载页面单击“MySQL Community Server”之后,您可以看到不同的安装选项。 选择您的选项并下载。 如果要下载 MSI 安装程序,请单击“转到下载页面”:
然后,在下一页上,选择mysql-installer-community
:
这将下载一个可执行文件。 下载完成后,双击文件,您将看到安装界面。 接受许可条款和条件后,您将看到子窗口“选择安装类型”。 单击“自定义”,因为我们不需要所有 MySQL 产品。 更具体地说,我们只需要“MySQL Server – X64”和“MySQL Workbench – X64”。
在“安装”子窗口上,单击“执行”以开始安装。
安装向导中的下一步是选择服务器配置。 这可能包括添加用户,设置密码和密码。 尽可能使用默认说明。
选择所有配置选项后,您将看到一个带有“执行”按钮的子窗口。 单击它继续。
之后,只需单击“完成”。 您将具有“安装后启动 MySQL 工作台”选项,这意味着当您单击“完成”时,它将启动工作台。
所以你去了! 您已成功将 MySQL 数据库和 MySQL 服务器安装到计算机上。 这是 MySQL Workbench 的样子:
MySQL 工作台界面
您可以尝试运行一个简单的查询,看看它是如何工作的。
如何在 Java 中使用 MySQL 连接器
https://javatutorial.net/how-to-use-the-mysql-connector-in-java
在通过 Java 程序测试 MySQL 连接之前,我们需要将 MySQL JDBC 库添加到类路径中。 我们将需要从下载页面下载mysql-connector-java-*.jar
文件:
现在,根据您的工作环境(例如 Eclipse 或命令行),您将必须执行以下任一操作:
- 如果使用 Eclipse IDE,请将 JAR 文件作为库添加到项目属性中的“构建路径”。
- 如果使用命令控制台,请在执行应用程序时在
-cp
或-classpath
参数中指定 JAR 文件的路径。
命令行执行的示例如下所示:
java -cp .;/xxx/xxx/xxx/mysql-connector-java-*.jar com.nameapp.ClassName
现在我们已经澄清了这一点,让我们从 Java 类连接到 MySQL 数据库。
在 Eclipse 中创建项目,然后右键单击项目名称并选择“属性”。 之后,转到“Java 构建路径”并通过单击“添加外部 JAR ..”添加.jar
文件。 您可以查看以下图像以显示步骤:
(1)
(2)
做完了 现在我们添加了 JAR 文件,是时候创建我们的 MySQL 数据库了。 我假设您已经下载了 MySQL 工作台。 如果不是,请从此处下载。
要创建数据库,只需键入
create database demoapp;
然后,要验证确实已创建数据库,请键入
show databases;
您应该看到类似以下的内容:
如您所见,demoapp
显示为数据库。
现在,最后一步是测试 Java 类与该数据库的连接。
这是一个带有主要方法的示例 Java 类,该方法尝试建立与数据库的连接:
package mysqlconnection.mysqlconnection;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class App
{
public static void main( String[] args )
{
String url = "jdbc:mysql://localhost:3306/demoapp";
String username = "root";
String password = "admin123";
// try to establish the connection to the database
try {
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println("Connection established successfully!");
}
// if the connection couldn't be established raise an exception
catch (SQLException e) {
throw new IllegalStateException("Unable to connect to the database. " + e.getMessage());
}
}
}
输出:
现在忽略警告。
细分
我们将本地主机的 url 存储在一个名为url
的变量中。jdbc
部分是 API,demoapp
是数据库的名称。 然后,我们存储用户名和密码。 这里的主要注意事项是,密码必须与安装 MySQL 时选择的密码相同。 之后,我们有一个Try {} Catch {}
块。 如果无法建立数据库连接,则将执行catch
块。 在try
块中,我们获得 url 和用户名/密码的连接。 之后,我们只需打印“连接成功建立!”。
只是一件事 - 如果您忘记在项目的构建路径中添加.jar
文件,则将无法建立连接,并且catch
块将被执行。 因此,请不要忘记添加 JAR 文件,这一点非常重要。
如何使用 Eclipse 调试 Java
原文: https://javatutorial.net/how-to-debug-java-with-eclipse
调试–一种使用最多且不可避免的技术。 如果只有一种工具可以使我们使这个有时繁琐的任务变得更加轻松和不那么繁琐……等等。
Eclipse 允许在所谓的调试模式中启动 Java 程序。 最有用的是它允许您使用调试命令来控制执行流程。
您可以设置行断点,条件断点和/或异常断点。
开始调试过程
要在代码中初始化一个断点,请在 Java 编辑器中右键单击左边界,然后选择“Toggle Breakpoint”,如下所示:
您还可以做的是(我想这是一种较快的方法)再次双击左边缘(红色箭头指向的位置)。 无论哪种方法适合您,它们都是完全相同的方法。
就我而言,我在for
循环的开头添加了一个断点
现在是时候在“调试”模式下运行程序了。 有 3 种方法:
- 单击主菜单中的“运行”命令,然后选择“调试”
- 按
F11
- 单击顶部面板上的类似 bug 的图标,然后选择“调试为 Java 应用程序”
请注意,要调试程序,您需要定义断点。
从上方的图片中选择“Java 应用程序”后,将提示此屏幕。 单击“确定”。
之所以会调用此窗口,是因为 Eclipse 想知道在到达停止点后是否要切换到调试透视图。 点击“切换”。
控制调试过程
您将在顶部面板上找到按钮,这些按钮的目的是帮助您控制决定调试的程序的执行。
但是,您也可以使用以下键绑定:
:此按钮的作用是恢复程序的执行,直到到达下一个断点。(
F8
)
:终止当前正在运行的服务器,而不是 Eclipse。(
CTRL + F2
)
:执行当前突出显示的行并跳过下一行。 重要的是要注意,如果当前突出显示的行是函数调用,则调试器将进入功能。(
F5
)
:执行一种方法,而调试器实际上不会进入该方法,也称为“跳过”。(
F6
)
:完成当前方法的执行并跳回到该方法的调用者。(
F7
)
调试视图
变量&表达式视图
这些视图提供了已声明变量及其关联值的展示。
断点视图
它基本上执行其名称,即显示所有断点。 就我而言,我只有 1 个断点,恰好在第 6 行。
控制台视图
使用此视图,您可能已经很熟悉–这是程序的实际输出。
使用这些视图的简单示例
从上面的代码的第 6 行开始(从for
循环开始),单击F5
后,我们得到以下结果:
因此,点击F5
后,我们进入循环(第 7 行),如果您查看“变量”视图,则会看到i
已初始化为 0(第 6 行)。
现在,让我们再次单击F5
。
没什么变化,因为我们在exampleVariable
中添加了 1,仅此而已。 现在我们在第 8 行,让我们单击F5
看看会发生什么。
我们得到了第一输出! 那是因为我们运行了System.out.println()
语句。 当我按F5
键时,您认为现在会发生什么?
如果您说i
将更改为 1,那么您将是对的! 您可以在右侧看到i
随着其值的变化而突出显示。
那是调试的基本过程。
让我们看一下 Eclipse 的调试器工具提供的更多功能。
观察点
与断点不同,观察点用于在文件字段的值发生更改(不指定要更改)时停止执行复制。
您可以通过在“断点属性”中指定监视点来轻松启用监视点。 要访问断点属性,请右键单击您在代码左侧放置的断点,然后选择“断点属性”,如下所示:
然后,您有两个选择。 您可以勾选“视情况而定”,然后选择“在‘true’时暂停”或“在值更改时暂停”。 甚至,您可以在下面的空间中指定一个条件,如果条件为true
,它将终止终止。
这些称为“条件断点”。
Java EE
如何在 Windows 10 中设置JAVA_HOME
原文: https://javatutorial.net/set-java-home-windows-10
在本教程中,我将向您展示如何在 Windows 10 操作系统中将JAVA_HOME
设置为环境变量。
为什么需要设置JAVA_HOME
?
许多基于 Java 的程序(例如 Tomcat)都要求将JAVA_HOME
设置为环境变量才能正常工作。 请注意JAVA_HOME
应该指向一个 JDK 目录而不是 JRE 目录。 设置环境变量的目的是让程序知道可以在其中找到诸如javac
之类的目录可执行文件。
1.打开高级系统设置
在 Windows 10 中,按Win+Pause
键,这将打开“系统设置”窗口。 转到Change settings
并选择Advanced
标签。
或者:
打开“Windows 搜索” – 您会在 Windows 徽标旁边找到它
打开 Windows 10 搜索
1.在搜索字段中输入 – advanced system settings
2.单击列表顶部的匹配项
在 Windows 10 中搜索高级系统设置
2.设置JAVA_HOME
环境变量
在“系统属性窗口”中,单击“环境变量...”
系统属性窗口环境变量
在“系统变量”下,单击“新建…”按钮,然后输入JAVA_HOME
作为“变量名称”,并在“变量值”下输入 Java JDK 目录的路径。
将JAVA_HOME
添加为系统变量
3.更新系统路径
1.在“环境变量”窗口的“系统变量”下,选择路径
2.点击“编辑...”
3.在“编辑环境变量”窗口中,单击“新建”
4.输入%JAVA_HOME%\bin
更新系统路径
4.测试您的配置
打开一个新的命令提示符,然后键入:
echo %JAVA_HOME%
如果环境变量设置不正确,它将打印出目录JAVA_HOME
指向的目录或为空行
现在输入:
javac -version
如果正确设置了Path
变量,则将打印出 Java 编译器的版本,否则为“无法将javac
识别为内部或外部命令…”
测试JAVA_HOME
和系统路径
JavaBeans 及其组件简介
原文: https://javatutorial.net/introduction-javabeans-components
本教程介绍了 JavaBeans 的基础知识,其组件以及 JavaBeans 的帮助方式。
简介
软件组件模型中最重要的主题之一是可重用性。 在“一次开发,在任何地方运行和重用它们”的主题下编写自包含软件是最需要和赞赏的。 因此,借助于 JavaBeans,可重用性被添加到 Java 编程语言中。
什么是 JavaBean?
JavaBeans 由 Sun Microsystem 于 1996 年引入,其定义为
“JavaBean 是可重用的,平台无关的组件,可以在构建器工具中直观地对其进行操作。”
在计算中,基于 Java 平台的 JavaBean 是将许多对象封装为单个对象(bean)的类。 使用 Builder 工具,您可以创建和使用 bean 来进行应用程序开发。 简单来说,JavaBean 就是 Java 类。 在其他应用程序中使用这些 JavaBean 时,这些组件的内部工作对应用程序开发人员来说是隐藏的。
示例:
所有的 Swing 和 AWT 类都是 JavaBean。 GUI 组件是理想的 JavaBean。
JavaBean
JavaBeans 的组件
包含 bean 定义的类称为 JavaBeans 组件。 这些类遵循某些设计约定。 它包括属性,事件,方法和持久性。 有两种类型的组件,基于 GUI 和非基于 GUI。 例如,JButton
是组件的示例,而不是类。
属性(日期成员):属性是 Bean 的命名属性,它包括颜色,标签,字体,字体大小,显示大小。 它确定 bean 的外观,行为和状态。
方法:JavaBean 中的方法与类中的常规 Java 方法相同。 它没有遵循任何特定的命名约定。 所有属性都应具有 setter 和 getter 方法。
事件:JavaBean 中的事件与 SWING/AWT 事件处理相同。
持久性:可序列化的接口使 JavaBean 可以存储其状态。
JavaBean 没有参数构造函数。
JavaBean 组件
JavaBeans 属性
对象的用户可以访问 JavaBean 属性,它可以被读取,写入,只读或仅写入。 我们可以借助在 bean 的实现类中编写的getPropertyName()
方法(也称为 getter 或 accessor)和setPropertyName()
方法(即 setter)来访问这些 JavaBeans 属性。
GetPropertyName()
:例如,如果属性名称为标题,则您的方法名称为geTitle()
。
SetPropertyName()
:例如,如果属性名称为标题,则您的方法名称将为setTitle()
。
JavaBeans 的示例
在编写 JavaBean 之前,这里有一些基本规则。 JavaBean 应该是公共的,应该没有参数默认构造函数,并且应该实现可序列化的接口。 在编写 JavaBean 之前,请牢记这些基本规则。
这是 JavaBean 的简单示例。
public class BankAccount implements java.io.Serializable {
private String accountNumber = null;
private String totalAmount = null;
private String accountHolderName = null;
private int accountHolderAge = 0;
private String accountHolderAddress = null;
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(String totalAmount) {
this.totalAmount = totalAmount;
}
public String getAccountHolderName() {
return accountHolderName;
}
public void setAccountHolderName(String accountHolderName) {
this.accountHolderName = accountHolderName;
}
public int getAccountHolderAge() {
return accountHolderAge;
}
public void setAccountHolderAge(int accountHolderAge) {
this.accountHolderAge = accountHolderAge;
}
public String getAccountHolderAddress() {
return accountHolderAddress;
}
public void setAccountHolderAddress(String accountHolderAddress) {
this.accountHolderAddress = accountHolderAddress;
}
}
JavaBeans 的优点
以下是 JavaBeans 的一些优点:
- 在不同环境中的可重用性。
- 用于创建小程序,servlet,应用程序或其他组件。
- JavaBeans 是动态的,可以自定义。
- 可以部署在网络系统中
这是源代码的链接,您可以从此处下载
如何安装和配置 Tomcat 8
原文: https://javatutorial.net/how-to-install-and-configure-tomcat-8
Apache Tomcat 是当今最常见,最流行的基于 Java 的 Web 容器。 在本教程中,我将向您展示如何安装 Tomcat 8 并调整配置文件。
如何安装和配置 Apache Tomcat
Tomcat 的主要优点是占地面积小,配置简单以及社区参与的历史悠久。 通常情况下,开发人员可以在 5 到 10 分钟内(包括下载时间)启动功能正常的 Tomcat 安装并运行。 Tomcat 几乎不需要开箱即用的配置就可以在开发机器上正常运行,但是也可以对其进行重大调整以在高负载,高可用性的生产环境中实现出色的性能。 您可以创建大型 Tomcat 集群来可靠地处理大量流量。 由于 Tomcat 的简单性和轻巧性,它经常在商业生产环境中使用。
下表显示了 Tomcat 版本及其规格
Tomcat 版本 | Servlet | JSP | EL | WebSocket | 所需的最低 Java 版本 |
---|---|---|---|---|---|
3.3.x | 2.2 | 1.1 | – | – | 1.1 |
4.1.x | 2.3 | 1.2 | – | – | 1.3 |
5.5.x | 2.4 | 2.0 | – | – | 1.4 |
6.0.x | 2.5 | 2.1 | 2.1 | – | 5.0 |
7.0.x | 3.0 | 2.2 | 2.2 | – | 6 |
8.0.x | 3.1 | 2.3 | 3.0 | 1.0 | 7 |
下载 Tomcat
转到 http://tomcat.apache.org/download-80.cgi 并向下滚动到“二进制发行版 -> 核心”。 对于 Windows,您可以在服务安装程序或 32 位或 64 位 ZIP 版本之间进行选择。 如果您想将 Tomcat 作为 Windows 服务运行而不是安装程序,那么如果要手动运行服务器或与 IDE 集成,请选择 ZIP 下载。
如果您在 Linux 或 OSX 上运行,请下载非 Windows zip,即 zip。
启用 Tomcat 管理器
Tomcat 管理器使您可以轻松管理应用程序并监视服务器状态。
要启用它,请在您喜欢的文本编辑器中打开文件conf/tomcat-users.xml
,然后在<tomcat-users>
和</tomcat-users>
标签之间放置以下行:
<user username="admin" password="admin" roles="manager-gui,admin-gui" />
当然,您可以根据需要设置用户名和密码。
启动服务器后,可以在http://localhost:8080/manager
下访问管理器应用程序。
更改 Servlet 编译器以使用 Java 8
默认情况下,即使 Tomcat 8.0 在 Java SE 8 上运行,它也会编译具有 Java SE 6 语言支持的 JavaServer Pages(JSP)。您可以在conf/web.xml
文件中更改此设置。 在文件中搜索文本org.apache.jasper.servlet.JspServlet
。 包含此文本的标签下方是两个<init-param>
标签。 将两个值都从 1.6 更改为 1.8
<init-param>
<param-name>compilerSourceVM</param-name>
<param-value>1.8</param-value>
</init-param>
<init-param>
<param-name>compilerTargetVM</param-name>
<param-value>1.8</param-value>
</init-param>
如何启动和停止 Tomcat 服务器
完成所有配置后,需要启动 Tomcat 才能使用它。 如果您使用 Windows 作为托管 OS,则可能需要先阅读本教程如何在 Windows 10 中设置JAVA_HOME
,然后再继续启动 Tomcat 服务器。
启动 Tomcat 很容易,只需进入/bin
文件夹并为 Windows 执行startup.bat
或为 Linux 或 OSX 运行startup.sh
。
您还可以选择从 Eclipse IDE 中启动 tomcat。 请参考本教程,以获取有关如何从 eclipse 配置和运行 Tomcat 的更多信息。
要停止 Tomcat,请在 Windows 操作系统上执行shutdown.bat
,在 Linux 或 OSX 系统上执行shutdown.sh
。
如何在 Tomcat 中部署和取消部署应用程序
https://javatutorial.net/how-to-deploy-and-undeploy-applications-in-tomcat
在本教程中,我将向您展示如何使用两种不同的方法在 Apache Tomcat 中部署和取消部署 Java EE Web 应用程序。
在 Tomcat 中手动部署和取消部署应用程序
在 Tomcat 中手动部署应用程序很简单 - 只需将.war
文件放在 Tomcat 的webapp
目录中即可。 如果您的 Tomcat 正在运行,它将把.war
文件解压缩到一个没有 WAR 扩展名的同名文件夹中。 如果它没有运行,则部署将在启动服务器之后进行。
在部署过程中检查服务器日志中的错误始终是一个好习惯。 可能是因为您的应用程序缺少某些依赖项,或者您构建的文件.war
文件的结构不正确–在这种情况下,部署将不会成功。
取消部署应用程序就像逆转流程一样简单。 只需从webapp
目录中删除.war
文件,稍后 Tomcat 会为您清除所有解压缩的文件。
使用 Tomcat 管理器部署和取消部署应用程序
您可以使用图形管理器界面来部署或取消部署应用程序。 如果您没有启用管理器,则可以在我们之前的 Tomcat 配置教程中了解更多有关如何执行此操作的信息。
使用 Tomcat 管理器部署和取消部署应用程序
1. 打开浏览器,然后转到http://localhost:8080/manager/html
2. 输入在conf/tomcat-users.xml
文件中指定的用户名和密码
3. 单击“要部署的 WAR 文件”部分中的“选择文件”按钮。 浏览您的.war
文件,然后单击“部署”按钮
4. 要取消部署应用程序,请单击其旁边的“取消部署”按钮
从 Eclipse 运行 Tomcat
原文: https://javatutorial.net/run-tomcat-from-eclipse
在本教程中,我将向您展示如何从 Eclipse IDE 设置和运行 Apache Tomcat 8。
那么,为什么要从 Eclipse 中运行 Tomcat 呢? 有几个原因。 首先,您可以直接调试应用程序,而无需在调试模式下运行 Tomcat 独立服务器。 其次,您可以在部署应用程序之前在其应用程序上运行 JUnit 测试。 第三,您可以直接从 Eclipse 部署应用程序。
如果尚未安装 Apache Tomcat 8,请查看我以前关于如何安装和配置 Tomcat 8 的教程。
在 Eclipse 中设置 Tomcat
注意:在执行以下步骤之前,请确保您正在运行较新版本的 Eclipse IDE。 早于 Eclipse Luna 4.4 的 Eclipse 版本默认不支持 Tomcat 8。 在本教程中,我已将 Eclipse Mars 2 用于 Java EE 开发人员。
1. 在 Eclipse for Java EE 中,转到 Windows 操作系统中的“窗口 -> 首选项”(或在 Mac 上为“Eclipse -> 首选项”)
2. 转到“服务器”,然后单击“运行时环境”。
3. 单击“添加...”按钮。
4. 在 Apache 树中,选择 Tomcat 8,然后选中“创建新的本地服务器”复选框。
5. 在下一个屏幕上,单击“浏览...”,然后选择您的 Tomcat 安装目录。 在 JRE 下拉列表中,选择本地 Java 8 SE 安装,然后单击“完成”按钮进行确认。
提示:Eclipse 将使用内置浏览器打开您的 Web 应用程序。 您可以将此设置更改为更方便的浏览器,例如 Google Chrome 或 Firefox。 为此,请转到“窗口 -> Web 浏览器”,然后选择“默认系统 Web 浏览器”
在本地服务器上从 Eclipse 运行应用程序
现在,您已经配置了 Tomcat 以在 Eclipse 中运行,您可以轻松地在此服务器上运行应用程序。 为此,请在“项目资源管理器”中右键单击您的项目,然后转到“运行方式 -> 在服务器上运行”
在下一个屏幕上,选择您的 Apache Tomcat8。这将部署您的 App,并在浏览器窗口中为您打开它。
Java Servlet 示例
原文: https://javatutorial.net/java-servlet-example
在本教程中,我将向您展示如何创建 Servlet 并将其部署在 Apache Tomcat 8 中
什么是 Servlet?
Servlet 是几乎每个 Java Web 应用程序的构建块。 它们提供了接受 HTTP 请求并将 HTTP 响应返回给用户的核心功能。 即使您使用 JSP 来构建网页,JSP 文件最终还是会被应用服务器或 Web 容器(例如 Glassfish 或 Tomcat)编译为 Servlet。 Servlet 对于您的 Web 容器完成其工作,服务 GET,POST,HEAD,PUT,DELETE,OPTIONS 和 TRACE 请求并向 Web 客户端返回响应至关重要。 您最可能熟悉 GET 和 POST 请求。 我强烈建议您花些时间阅读上面列出的其他类型的请求。 如果您还不了解它们,请立即阅读本文 https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html 。
Servlet 项目结构
在我们的第一个示例中,我们将构建一个简单的 Servlet,在您的浏览器中打印消息"Hello world"
,这不是很简单。 我们将使用 Maven 构建项目。 我们将创建一个 Servlet 类和一个web.xml
文件。 web.xml
文件也称为部署描述符,它向 Web 容器提供有关如何处理 servlet 的信息。 将其视为配置文件。 下图显示了我们项目的文件结构
项目文件结构
您可以在 GitHub https://github.com/JavaTutorialNetwork/Tutorials/tree/master/SimpleServlet 中找到我们 Servlet 示例的完整源代码。
项目 POM 文件
我们的 Servlet 项目的 maven 构建pom.xml
文件如下所示:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>SimpleServlet</artifactId>
<version>1</version>
<packaging>war</packaging>
<name>SimpleServlet</name>
<url>https://javatutorial.net</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
让我们拆解pom
文件,看一下重要的东西:
- 我们需要将包装设置为
war
。 Tomcat 中的 Web 应用程序打包到 WAR 文件(Web 归档文件)中,封装了该应用程序的所有数据。 WAR 文件的名称基于您在 maven pom 文件中设置的arifactId
和version
参数建立。 在这种情况下,我们的 WAR 文件的名称为SimpleServlet-1.war
- 我们创建 servlet 所需的依赖关系为
javax.servlet-api
。 我们将范围设置为“已提供”,因为 Tomcat 已经内置了所需的所有库。 - 我们需要 Maven WAR 插件才能创建 WAR 文件
- 在 WAR 插件的编译器部分中设置 1.8。 这意味着将使用 Java 8 编译类
简单的 Servlet 示例
让我们看一个非常基本的 servlet 的源代码,在 Web 浏览器中打印消息"Hello World!"
。
package net.javatutorial.tutorials;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleServlet extends HttpServlet {
private static final long serialVersionUID = -4751096228274971485L;
@Override
protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().println("Hello World!");
}
@Override
public void init() throws ServletException {
System.out.println("Servlet " + this.getServletName() + " has started");
}
@Override
public void destroy() {
System.out.println("Servlet " + this.getServletName() + " has stopped");
}
}
所有 servlet 都需要扩展抽象类HttpServlet
。HttpServlet
提供了构建 HTTP 响应的所有方法 - 我们在这里所做的是重写某些方法以放置我们自己的逻辑。
当客户端(浏览器)向我们的 Servlet 发出 GET 请求时,将执行doGet
方法。 在我们的示例中,它将仅返回文本响应"Hello World!"
。
当 Web 容器首次启动 servlet 时,将一次调用init()
方法。 这发生在应用程序部署或第一次调用此 servlet 时。 初始化方法可用于设置数据库连接或其他耗时的初始化操作。
当 Web 容器终止 Servlet 时,将调用destroy()
方法。 我们可以使用这种方法来关闭与数据库或其他开放资源的连接。
创建web.xml
部署描述符
web.xml
文件用于告诉 Web 容器如何处理 Web 应用程序中的文件。 将其视为配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Simple Servlet Application</display-name>
<servlet>
<servlet-name>simpleServlet</servlet-name>
<servlet-class>net.javatutorial.tutorials.SimpleServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>simpleServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
<display-name>
:该应用程序将在 Tomcat 管理器中以该名称列出。 阅读本教程,学习如何配置 Tomcat 管理器和如何使用 Tomcat 管理器部署应用程序。
<servlet>
标签保存了我们 Servlet 的配置。<servlet-name>
是我们 Servlet 的别名,用于在web.xml
文件中配置 Servlet 的其他属性。<servlet-class>
是我们的 Servlet 的路径。<load-on-startup>1</load-on-startup>
表示,当 Tomcat 启动时,该 servlet 将被加载到 Web 容器中。 如果您的项目中还有其他 servlet,则可以设置启动时加载的值,例如 2,3, 100 等。值最低的 servlet 具有更高的优先级,并且将首先加载。
<servlet-mapping>
用于为 servlet 提供 URL。 在我们的示例中,将/hello
模式赋予我们的 servlet,这意味着访问您本地服务器上的 servlet 的完整 URL 为http://localhost:8080/SimpleServlet/hello
构建和部署示例
构建源代码后,您将准备好部署 WAR 文件。 WAR 将包含已编译的 servlet 类和部署描述符文件web.xml
。
WAR 文件结构
如果您需要有关如何部署 WAR 文件的其他信息,请参考本教程如何在 Tomcat 中部署和取消部署应用程序
Java Servlet POST 示例
原文: https://javatutorial.net/java-servlet-post-example
本示例演示了如何使用 Servlet 的doPost()
方法来处理 POST 请求
在我们以前的教程的 Java Servlet 实现示例中我展示了doGet()
方法的使用。 现在,我将向您展示如何使用doPost()
方法来处理表单 POST 提交。 Java Servlet 可以处理各种类型的请求。 下面的列表显示了所有方法及其用途
方法 | SERVLET 方法 | 目的 |
---|---|---|
GET | doGet() |
在指定的 URL 检索资源 |
HEAD | doHead() |
与 GET 相同,只返回标头 |
POST | doPost() |
通常用于 Web 表单提交 |
PUT | doPut() |
将提供的实体存储在 URL |
DELETE | doDelete() |
删除 URL 标识的资源 |
OPTIONS | doOptions() |
返回允许的 HTTP 方法 |
TRACE | doTrace() |
用于诊断 |
项目结构
在我们的项目中,我们确实需要三个文件。pom.xml
– 设置 Maven 依赖关系和构建属性,web.xml
– 将 Servlet 和 Servlet 本身配置为 java 类
Servlet POST 示例项目结构
pom.xml
文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>ServletPOSTExample</artifactId>
<version>1</version>
<packaging>war</packaging>
<name>Servlet POST Example</name>
<url>https://javatutorial.net</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>servletpost</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
与上一教程一样,我们将 Servlet 的依赖项javax.servlet-api
和maven-war-plugin
的依赖关系用于构建网络应用
在web.xml
文件中映射 Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Simple Servlet Application</display-name>
<servlet>
<servlet-name>servletPost</servlet-name>
<servlet-class>net.javatutorial.tutorials.ServletPOST</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletPost</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
</web-app>
Servlet 被赋予一个名为ServletPost
的名称,该名称指向 Java 类ServletPOST
。
在 servlet 映射中,我们将 url /welcome
分配给 servlet
Servlet 类
package net.javatutorial.tutorials;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletPOST extends HttpServlet {
private static final long serialVersionUID = -1641096228274971485L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// set response headers
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// create HTML form
PrintWriter writer = response.getWriter();
writer.append("<!DOCTYPE html>\r\n")
.append("<html>\r\n")
.append(" <head>\r\n")
.append(" <title>Form input</title>\r\n")
.append(" </head>\r\n")
.append(" <body>\r\n")
.append(" <form action=\"welcome\" method=\"POST\">\r\n")
.append(" Enter your name: \r\n")
.append(" <input type=\"text\" name=\"user\" />\r\n")
.append(" <input type=\"submit\" value=\"Submit\" />\r\n")
.append(" </form>\r\n")
.append(" </body>\r\n")
.append("</html>\r\n");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String user = request.getParameter("user");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// create HTML response
PrintWriter writer = response.getWriter();
writer.append("<!DOCTYPE html>\r\n")
.append("<html>\r\n")
.append(" <head>\r\n")
.append(" <title>Welcome message</title>\r\n")
.append(" </head>\r\n")
.append(" <body>\r\n");
if (user != null && !user.trim().isEmpty()) {
writer.append(" Welcome " + user + ".\r\n");
writer.append(" You successfully completed this javatutorial.net example.\r\n");
} else {
writer.append(" You did not entered a name!\r\n");
}
writer.append(" </body>\r\n")
.append("</html>\r\n");
}
}
在上面的代码中,doGet()
方法用于显示表单。 客户端(浏览器)使用 GET 请求调用网址 http://yoururl.com:8080/servletpost/welcome ,它显示以下形式
输入表单
该 servlet 在客户端的浏览器中呈现为 HTML,如下所示:
<!DOCTYPE html>
<html>
<head>
<title>Form input</title>
</head>
<body>
<form action="welcome" method="POST">
Enter your name:
<input type="text" name="user" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
提交后,将调用 servlet 中的doPost()
方法。 在这里,我们根据用户的输入来建立响应。 如果正确填写了姓名字段,则用户会收到问候消息
响应后
…或警告消息(如果表单中的名称字段留空)
如果缺少名称,则会出现错误消息
您可以在我们的 GitHub 存储库中找到项目源。
Servlet 请求信息示例
原文: https://javatutorial.net/servlet-request-info-example
在本教程中,我将向您展示如何从 Servlet Request
对象检索各种数据,例如标头,参数,路径和会话数据
当客户端(浏览器)向 Servlet 发出 GET,POST,PUT 等请求时,HttpServletRequest
对象将保存有关客户端和请求本身的有价值的信息。 在下面的示例中,我将列出请求对象的一些最有趣的方法以及如何提取信息。
package net.javatutorial.tutorials;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletInfo extends HttpServlet {
private static final long serialVersionUID = -2383814320847175129L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter pr = response.getWriter();
pr.println("=== Paths ===\n");
pr.println("Request URL : " + request.getRequestURL());
pr.println("Request URI : " + request.getRequestURI());
pr.println("Servlet path : " + request.getServletPath());
pr.println("\n=== Headers ===\n");
Enumeration<String> e = request.getHeaderNames();
while(e.hasMoreElements()){
String param = (String) e.nextElement();
pr.println(param + " : " + request.getHeader(param));
}
pr.println("\n=== Parameters ===\n");
Map<String, String[]> paramsMap = request.getParameterMap();
for (String key : paramsMap.keySet()) {
pr.println(key + " : " + request.getParameter(key));
}
pr.println("\n=== Session ===\n");
// returns 0:0:0:0:0:0:0:1 if executed from localhost
pr.println("Client IP address : " + request.getRemoteAddr());
pr.println("Session ID : " + request.getRequestedSessionId());
// Cookie objects the client sent with this request
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
pr.print(cookie.getName() + ";");
}
}
}
}
从我的 Google Chrome 版本 51.0.2704.103 请求http://localhost:8080/ServletInfo/nfo?section=tutorials&lang=java
的localhost
Tomcat 服务器上的 servlet 显示以下输出:
请求信息输出
HttpServletRequest
方法
getRequestURL()
– 返回客户端用于发出请求的整个 URL,包括协议(http 或 https),服务器名称,端口号和服务器路径,但不包括查询参数
getRequestURI()
– 仅返回 URL 的服务器路径部分
getServletPath()
– 返回在 Tomcat 中部署的应用程序路径(在上面的示例中为/info
)
getHeaderNames()
– 返回与请求一起传递的所有标头中的名称的枚举
getHeader(headerName)
– 返回具有给定名称的标头的值
getParameterMap()
– 返回带有所有参数及其值的HashMap
getParameter(key)
– 返回具有给定键(名称)的参数的值
getRemoteAddr()
– 返回客户端的 IP 地址。 如果请求是在本地主机上执行的,则返回值为0:0:0:0:0:0:0:1
或127.0.0.1
的 IPv6 等效值
getRequestedSessionId()
– 返回远程会话 ID(如果提供)
getCookies()
– 返回与此请求一起发送的客户端的Cookie
对象数组
您可以在 GitHub 中找到整个项目: https://github.com/JavaTutorialNetwork/Tutorials/tree/master/ServletInfo
Servlet 注解示例
原文: https://javatutorial.net/servlet-annotation-example
本示例演示了注解的用法,以便配置 Servlet。
在之前的教程中,我们使用部署描述符(web.xml
文件)来配置我们的 servlet。 从 Servlet 3.0 开始,您可以改用@WebServlet
注解。 该注解使您可以为 servlet 设置几个属性,例如名称,URL 等。
注解与部署描述符
有什么不同? 好吧,很明显,部署描述符是一个单独的文件,您可以在其中以 XML 格式设置配置值,其中注解直接嵌入到源代码中。 如果您希望将代码和配置放在同一位置以提高可读性,请使用注解。 部署描述符恰恰相反 - 您将代码和配置分开。 这样,如果您要更改单个配置值,则无需重新编译整个项目。
对于许多 Java Enterprise 组件,都有两个版本可用 - 注解或描述符。 但其他配置则只能使用注解或通过部署描述符进行配置。 对于 Servlet,可以选择一种或另一种方法。
WebServlet
注解属性
您可以选择几种属性来配置 Servlet。
必要
value
或urlPatterns
String[]
– 指定该 servlet 的一个或多个 URL 模式。 可以使用任何一个属性,但不能同时使用。urlPatterns
的默认值为{}
,value
默认值为""
可选
asyncSupported
boolean
– 声明 Servlet 是否支持异步操作模式。 默认值为假name
String
– Servlet 的名称。 默认值为""
description
String
– Servlet 的描述。 默认值为""
displayName
String
– Servlet 的显示名称。 默认值为""
initParams
WebInitParam[]
– Servlet 的初始化参数。 默认值为{}
largeIcon
String
– Servlet 的大图标。 默认值为""
smallIcon
String
– Servlet 的小图标。 默认值为""
loadOnStartup
int
– Servlet 的启动时加载顺序。 默认值为 -1
用于 Servlet 的 POM 文件
我们将使用以下pom.xml
文件构建本教程后面显示的示例。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>ServletAnnotation</artifactId>
<version>1</version>
<packaging>war</packaging>
<name>ServletAnnotation</name>
<url>https://javatutorial.net</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>servletannotation</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用注解时是否需要web.xml
?
简短答案–不! 如果您选择对所有 Servlet 配置依赖注解,则可以完全删除web.xml
文件。 但我仍然鼓励您保留该文件,或者至少保留一个如以下示例所示的文件。Web.xml
用于许多其他配置,因此您迟早需要在项目中使用它。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Servlet with Annotations Application</display-name>
</web-app>
如果您希望完全跳过web.xml
,则需要在 Maven 的pom.xml
中告诉 war 插件停止搜索该文件。 您可以通过将failOnMissingWebXml
设置为false
来实现,如下所示:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
用一个 URL 模式注解的 Servlet
以下示例仅使用一个属性来注解 servlet 的 URL。 假设您在本地主机上运行应用程序并将其作为servletannotation.war
部署,则 servlet 的路径将为http://localhost:8080/servletannotation/hello
package net.javatutorial.tutorials;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class ServletWithAnnotations extends HttpServlet {
private static final long serialVersionUID = -3462096228274971485L;
@Override
protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().println("Hello World!");
}
}
具有多个属性的 Servlet 注解
在此示例中,我们将设置 servlet 名称,URL 和load-0n-startup
加载优先级。 在我们的简单 Servlet 示例中,我们在web.xml
文件中执行了以下操作:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Servlet with Annotations Application</display-name>
<servlet>
<servlet-name>simpleServlet</servlet-name>
<servlet-class>net.javatutorial.tutorials.ServletWithAnnotations</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>simpleServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
…现在我们将使用WebServlet
注解实现相同的配置:
package net.javatutorial.tutorials;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "simpleServlet", urlPatterns = { "/hello" }, loadOnStartup = 1)
public class ServletWithAnnotations extends HttpServlet {
private static final long serialVersionUID = -3462096228274971485L;
@Override
protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().println("Hello World!");
}
}
具有多个 URL 模式的 Servlet 注解
urlPatterns
属性接受一个数组作为值,因此您可以设置多个指向同一 servlet 的 URL:
@WebServlet(urlPatterns = {"/hello", "/wellcome"})
public class ServletWithAnnotations extends HttpServlet {
@Override
protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
throws ServletException, IOException {
// ... implementation goes here
}
}
假设您在本地主机上运行应用程序并将其作为servletannotation.war
部署,则可以访问http://localhost:8080/servletannotation/hello
以及http://localhost:8080/wellcome
上的 servlet
您可以在我们的 GitHub 存储库中找到此示例的源代码
使用初始化参数配置 Java Web 应用程序
原文: https://javatutorial.net/java-init-parameters
本示例演示了使用初始化参数来配置 Java Web 应用程序
初始化参数非常适合设置变量,将来可能需要更改。 例如,您可以使用初始化参数来存储数据库连接或存储外部 API 密钥或 URL。 使用初始化参数的好处是,您可以轻松更改配置,而无需重新编译整个应用程序。
使用init-param
和context-param
配置 Java Web 应用程序
注解与部署描述符
在前面的示例中,我们演示了使用注解配置 Servlet 的用法。 尽管您可以使用注解来设置初始化参数,但我不建议您这样做。 原因是,如果使用注解来设置初始化参数,则每次需要更改参数时都必须重新编译应用程序。 使用部署描述符来设置初始化参数要好得多。 一旦将初始化参数值放入web.xml
文件中并需要更改它们,您要做的就是用更改后的值重新启动应用程序。 无需重新编译,无需重新部署!
@WebInitParam
注解
下面的示例显示 Servlet 声明中@WebInitParam
注解的用法
@WebServlet(
name = "servletParamAnnotation",
urlPatterns = {"/servletParamAnnotation"},
initParams = {
@WebInitParam(name = "server", value = "https://javatutorial.net"),
@WebInitParam(name = "api-key", value = "h6Thd5guE4Kl12g3")
} )
public class InitParamServletAnnotations extends HttpServlet {
// ...
}
如前所述,如果您选择设置初始化参数,则需要重新编译和重新部署应用程序。
上下文初始化参数
如果要在整个应用程序中共享参数,那么上下文初始化参数是一个很好的选择。 应用程序中的每个 Servlet 都共享这些初始化参数,并且它们的值在所有 Servlet 中都相同。 您可以使用以下<context-param>
,<param-name>
和<param-value>
标记在web.xml
文件中设置上下文参数:
<context-param>
<param-name>url</param-name>
<param-value>https://javatutorial.net</param-value>
</context-param>
<context-param>
<param-name>api-key</param-name>
<param-value>h6Thd5guE4Kl12g3</param-value>
</context-param>
稍后,您可以像这样简单地在 servlet 或其他组件中使用此参数:
package net.javatutorial.tutorials;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletContextParams extends HttpServlet {
private static final long serialVersionUID = -3462096555274971485L;
@Override
protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
throws ServletException, IOException {
ServletContext c = this.getServletContext();
PrintWriter writer = response.getWriter();
writer.append("URL: ").append(c.getInitParameter("url")).append(", API KEY: ")
.append(c.getInitParameter("api-key"));
}
}
Servlet 初始化参数
web.xml
文件中的<init-param>
标记创建特定于给定 Servlet 的初始化参数。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Servlet with Init Parameters</display-name>
<servlet>
<servlet-name>servletInitParams</servlet-name>
<servlet-class>net.javatutorial.tutorials.ServletInitParams</servlet-class>
<init-param>
<param-name>url</param-name>
<param-value>https://javatutorial.net</param-value>
</init-param>
<init-param>
<param-name>api-key</param-name>
<param-value>h6Thd5guE4Kl12g3</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>servletInitParams</servlet-name>
<url-pattern>/servletInitParams</url-pattern>
</servlet-mapping>
</web-app>
在您的 Servlet 中,您可以像这样调用参数:
package net.javatutorial.tutorials;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletInitParams extends HttpServlet {
private static final long serialVersionUID = -1242096555274971485L;
@Override
protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
throws ServletException, IOException {
ServletConfig c = this.getServletConfig();
PrintWriter writer = response.getWriter();
writer.append("URL: ").append(c.getInitParameter("url")).append(", API KEY: ")
.append(c.getInitParameter("api-key"));
}
}
注意上下文参数之间的区别,它们的用法如下:
ServletContext c = this.getServletContext();
和初始化参数:
ServletConfig c = this.getServletConfig();
Java Servlet 文件上传
原文: https://javatutorial.net/java-servlet-file-upload
本示例演示如何使用 Java Servlet 上传文件
始终可以将文件上传到 Java EE Servlet,但是要完成该工作需要付出很大的努力。 Apache Foundation 甚至构建了一个称为 Commons FileUpload 的库,以使此任务更易于实现。 尽管如此,Servlet 3.0 规范弥补了这一差距,并且自 Java EE 6 起,多部分配置选项已添加到 Servlet 中,从而在HttpServletRequest
中引入了getPart
和getParts
方法。
如果您有兴趣使用 WebServices 将文件上传到服务器,则可以查看本教程。
Servlet 文件上传示例
Servlet 文件上载示例演示了MultipartConfig
注解的用法,并使用户能够上载一个或两个文件。
该示例项目的结构非常简单。 它由一个 servlet 文件FileUploadServlet.java
,pom.xml
和可选的web.xml
组成,这些文件用于在构建时处理依赖项。 正如我们在 Servlet 注释示例中讨论的那样,您可以在注释和部署描述符之间进行选择,以设置 Servlet 配置。 本示例使用注释。
下图显示了项目结构
Servlet 文件上传项目结构
在 Maven 的pom.xml
文件中,我们需要声明的唯一依赖项是javax.servlet
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>ServletFileUpload</artifactId>
<version>1</version>
<packaging>war</packaging>
<name>ServletFileUpload</name>
<url>https://javatutorial.net</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>fileuploader</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
文件上传 servlet 是我们项目的核心。 它只有两种方法 - goGet
(显示上载表格)和doPost
(完成整个上载工作)。
package net.javatutorial.tutorials;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@WebServlet(name = "uploadServlet", urlPatterns = { "/upload" }, loadOnStartup = 1)
@MultipartConfig(fileSizeThreshold = 6291456, // 6 MB
maxFileSize = 10485760L, // 10 MB
maxRequestSize = 20971520L // 20 MB
)
public class FileUploadServlet extends HttpServlet {
private static final long serialVersionUID = 5619951677845873534L;
private static final String UPLOAD_DIR = "uploads";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.append("<!DOCTYPE html>\r\n")
.append("<html>\r\n")
.append(" <head>\r\n")
.append(" <title>File Upload Form</title>\r\n")
.append(" </head>\r\n")
.append(" <body>\r\n");
writer.append("<h1>Upload file</h1>\r\n");
writer.append("<form method=\"POST\" action=\"upload\" ")
.append("enctype=\"multipart/form-data\">\r\n");
writer.append("<input type=\"file\" name=\"fileName1\"/><br/><br/>\r\n");
writer.append("<input type=\"file\" name=\"fileName2\"/><br/><br/>\r\n");
writer.append("<input type=\"submit\" value=\"Submit\"/>\r\n");
writer.append("</form>\r\n");
writer.append(" </body>\r\n").append("</html>\r\n");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// gets absolute path of the web application
String applicationPath = request.getServletContext().getRealPath("");
// constructs path of the directory to save uploaded file
String uploadFilePath = applicationPath + File.separator + UPLOAD_DIR;
// creates upload folder if it does not exists
File uploadFolder = new File(uploadFilePath);
if (!uploadFolder.exists()) {
uploadFolder.mkdirs();
}
PrintWriter writer = response.getWriter();
// write all files in upload folder
for (Part part : request.getParts()) {
if (part != null && part.getSize() > 0) {
String fileName = part.getSubmittedFileName();
String contentType = part.getContentType();
// allows only JPEG files to be uploaded
if (!contentType.equalsIgnoreCase("image/jpeg")) {
continue;
}
part.write(uploadFilePath + File.separator + fileName);
writer.append("File successfully uploaded to "
+ uploadFolder.getAbsolutePath()
+ File.separator
+ fileName
+ "<br>\r\n");
}
}
}
}
@MultipatrtConfig
注解使 Servlet 可以接受文件上传。 有 3 个重要属性:
fileSizeThreshold
– 在将文件写入temp
目录之前要超出的文件大小。 如果文件小于此阈值,则文件将在请求完成之前驻留在内存中。maxFileSize
– 这是允许上传的文件的最大大小。 在上面的示例中,不允许用户上传大于 10 MB 的文件maxRequestSize
– 是我们尝试通过一个请求上传的所有文件的大小的总和。 在上面的示例中,我们将该值设置为 20 MB,这意味着无论文件数量多少,我们总共可以上传 20MB
您可能要指定或不指定第四个属性。 它称为location
,它指向 Web 容器应存储临时文件的目录。 但是,如果您未指定此属性,则容器将使用默认的temp
文件夹。
我们重写doGet
方法以显示具有两个文件选择器字段的简单形式。 您可能需要添加其他输入字段,因为多部分附件允许这样做。
表格上传文件
在doPost
方法中,我们首先构造要存储上载文件的文件夹的路径。 比我们使用request.getParts()
遍历用户选择上传的文件,最后将它们存储到所需位置。
构建并部署后,您可以在以下浏览器中访问应用程序:http://localhost:8080/fileuploader/upload
您可以在我们的 GitHub 存储库的中找到该项目。
Java JSP 示例
原文: https://javatutorial.net/java-jsp-example
本示例演示如何创建一个简单的 JSP 页面
在之前的教程中,我向您展示了如何使用 Servlet 处理请求,响应,请求参数和上传文件。 但是,您可能已经注意到,使用 servlet 呈现 Web 应用程序的 HTML 内容是多么不便。 重复调用ServletOutputStream
或PrintWriter
类上的方法以输出内容,并且必须将 HTML 内容放入 Java 字符串中,这需要转义引号,这确实是一个难题。 在此示例中,您将了解 Java Server Pages 以及它们如何使您的生活更加轻松。
什么是 JSP?
Java EE 规范的创建者意识到,使用 servlet 生成纯 HTML 内容需要付出的精力比需要的多。 纯 HTML 页面的问题在于它们是静态的。 使用 servlet 时,我们可以生成动态内容并将其呈现为 HTML。 JavaServer Pages(也称为 JSP)是解决此问题的方法。 JSP 本质上是一种混合解决方案,结合了 Java 代码和 HTML 标签。 JSP 除了 Java 代码外,还可以包含任何 HTML 标记。
JSP 示例
在下面的示例中,我们将显示一个简单的 JSP 页面,其中显示了当前时间。
首先让我们看一下项目结构:
JSP 项目结构
如您所见,结构非常简单。 一个 Maven POM 文件,用于处理依赖关系和构建属性:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>JSPExample</artifactId>
<version>1</version>
<packaging>war</packaging>
<name>JSPExample</name>
<url>https://javatutorial.net</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>jspexample</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
一个仅包含应用程序显示名称的简单web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>JSP Example</display-name>
</web-app>
和实际的 JSP 文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.time.LocalDateTime" %>
<!DOCTYPE html>
<html>
<head>
<title>Simple JSP Application</title>
</head>
<body>
<h1>Hello world!</h1>
<h2>Current time is <%= LocalDateTime.now() %></h2>
</body>
</html>
现在让我们仔细看看index.jsp
JSP 中可以使用几种不同类型的标签。 在上面的示例中,我们使用指令类型<% @page… %>
将页面编码设置为 UTF-8
<%@ page contentType="text/html;charset=UTF-8" %>
默认情况下(如果我们的 JSP 文件中不包括此行),字符编码将设置为 ISO-8859-1,如果我们要使用非拉丁字母的特殊字符和字母,这将很不方便。
我们使用另一个指令导入LocalDateTime
<%@ page import="java.time.LocalDateTime" %>
最后,为了显示当前时间,我们使用了一个名为表达式的特殊标签<%= … %>
。 表达式包含简单的 Java 代码,这些代码返回可以写到客户端输出的内容,并且表达式将该代码的返回变量输出到客户端:
<h2>Current time is <%= LocalDateTime.now() %></h2>
部署应用程序后,转到http://localhost:8080/jspexample/index.jsp
您应该会看到与此页面相似的页面
JSP 页面输出
您可以在我们的 GitHub 存储库中找到此示例的源文件。
Glassfish 启用安全管理
原文: https://javatutorial.net/glassfish-enable-secure-admin
在本教程中,我将向您展示如何启用 Glassfish 或 Payara 的安全管理,以便您可以远程访问它。
当您在远程域(而不是localhost
)上部署 Glassfish 或 Payara 并希望远程访问 Glassfish Admin 时,需要首先启用它。 默认情况下,Glassfish Admin 在端口 4848 上运行。
如果看到此消息
Configuration Error
Secure Admin must be enabled to access the DAS remotely.
比按照此步骤。
设置管理员密码
在能够启用 DAS(域管理服务器)之前,系统可能会提示您一条消息,提示您的管理员密码为空并且无法启用安全性。 如果尚未设置管理员密码,请通过以下方式更改(或初始设置)管理员密码来解决该问题:
asadmin --host localhost --port 4848 change-admin-password
要求您输入用户名和密码:
Glassfish 更改管理员密码
输入管理员用户名(默认值:admin),如果要保留admin
,请按Enter
键或输入另一个
如果尚未设置管理员密码,请输入管理员密码,只需按Enter
键
输入新的管理员密码,输入新密码
启用安全管理员
在运行的服务器上,输入以下命令:
asadmin --host localhost --port 4848 enable-secure-admin
系统将提示您输入管理员用户名和密码
Glassfish 启用安全管理员
您将需要重新启动所有正在运行的服务器,以使更改生效。
如何使用 MySQL 配置 Glassfish 4
原文: https://javatutorial.net/configure-glassfish-mysql
本文介绍了如何使用 Glassfish 应用服务器 设置和配置 MySQL 数据库
如今,MySQL 已在生产和开发环境中广泛使用。 Glassfish 4(及先前版本)具有易于管理数据库连接的界面。 您可以使用 Glassfish 的管理控制台来设置和配置 MySQL 数据库,或者,如果您愿意,可以编辑包含数据库首选项的配置文件。 我将在本教程中向您展示这两种方法。 核心部分本教程也适用于 Payara。
要求
MySQL Server 安装在本地或远程计算机上。 另外,您将需要一个现有用户(带有密码)和一个对该用户具有读/写权限的数据库
Glassfish(或 Payara)4 应用程序服务器
在 Glassfish 上安装 MySQL Java Connector
MySQL 的官方 JDBC(Java 数据库连接)驱动程序称为 MySQL Connector/J。 您可以在 https://dev.mysql.com/downloads/connector/j/ 找到下载页面。
- 下载 TAR 或 ZIP 归档文件 - 无论您喜欢使用什么文件。 解压缩档案后,您会发现一个名为
mysql-connector-java-X.X.XX-bin.jar
的 JAR 文件(其中X.X.XX
是版本号)。 - 将 JAR 文件粘贴到您的
GLASSFISH_HOME/glassfish/bin
目录中 - 重新启动 Glassfish,以使更改生效
创建 Glassfish JDBC 连接池
在本地主机上打开 Glassfish 管理控制台:4848
展开 JDBC,然后单击“JDBC 连接池”
单击“新建…”按钮以创建新的连接池
Glassfish 创建新的 JDBC 连接池
在“池名称”中输入连接池的名称
选择javax.sql.DataSource
作为“资源类型”
选择 MySql 作为“数据库驱动程序供应商”
点击“下一步”按钮进行下一步
创建 JDBC 连接池
在屏幕 #2 上,检查“数据源类名”,应将其设置为com.mysql.jdbc.jdbc2.optional.MysqlDataSource
启用“Ping”
创建 JDBC 连接池的步骤 2
现在在同一屏幕上向下滚动。
填写以下必填字段:
User
– 输入数据库用户名DatabaseName
–输入数据库的名称Password
– 是您在第一个字段中输入的用户密码URL
和Url
– 在jdbc:mysql://:3306/
之后添加数据库的名称,因此它成为jdbc:mysql://:3306/tutorials
您可能要根据项目的偏好填写其他一些字段,这完全取决于您!
单击“完成”按钮以保存您的配置。 发送到数据库的ping
命令以检查连接。 如果一切正常,您将看到以下消息:
如果看到错误消息,请单击“编辑”,然后检查输入的数据是否正确。
在 Glassfish 中创建 JDBC 资源
创建 JDBC 资源不一定是 MySQL 配置教程的一部分,但是您将需要使用它来将项目连接到数据库,因此在这里进行介绍:
选择“JDBC 资源”,然后单击“新建...”按钮
Glassfish JDBC 资源
在“JNDI 名称”下,输入jdbc/
,然后输入您要为此资源指定的名称。 例如jdbc/tutorialsDS
在“池名称”下,选择您在上一步中刚刚创建的连接池的名称
保存新创建的 JDBC 资源
Glassfish 创建新的 JDBC 资源
好的! 现在,您可以将 MySQL 数据库与在 Glassfish 服务器上部署的应用程序一起使用了。
替代配置方法
如果您希望通过编辑配置文件来创建连接池和 JDBC 资源,则可以通过在<resources>...</resources>
标记之间的以下行中为选定域的GLASSFISH_HOME/glassfish/domains/domain1/config/domain.xml
插入 Glassfish 的配置文件中来轻松完成此操作
<jdbc-connection-pool ping="true" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" name="MySQLPool" res-type="javax.sql.DataSource">
<property name="SelfDestructOnPingSecondsLifetime" value="0"></property>
<property name="UseUsageAdvisor" value="false"></property>
<property name="AllowSlaveDownConnections" value="false"></property>
<property name="LoadBalanceBlacklistTimeout" value="0"></property>
<property name="QueryTimeoutKillsConnection" value="false"></property>
<property name="CacheServerConfiguration" value="false"></property>
<property name="RoundRobinLoadBalance" value="false"></property>
<property name="UseCursorFetch" value="false"></property>
<property name="JdbcCompliantTruncation" value="true"></property>
<property name="UseOnlyServerErrorMessages" value="true"></property>
<property name="AllowPublicKeyRetrieval" value="false"></property>
<property name="DefaultAuthenticationPlugin" value="com.mysql.jdbc.authentication.MysqlNativePasswordPlugin"></property>
<property name="DontTrackOpenResources" value="false"></property>
<property name="UseInformationSchema" value="false"></property>
<property name="UseNanosForElapsedTime" value="false"></property>
<property name="UseCompression" value="false"></property>
<property name="EnableEscapeProcessing" value="true"></property>
<property name="PasswordCharacterEncoding" value="UTF-8"></property>
<property name="AutoDeserialize" value="false"></property>
<property name="TcpRcvBuf" value="0"></property>
<property name="CallableStatementCacheSize" value="100"></property>
<property name="AutoSlowLog" value="true"></property>
<property name="IgnoreNonTxTables" value="false"></property>
<property name="UseJDBCCompliantTimezoneShift" value="false"></property>
<property name="AllowNanAndInf" value="false"></property>
<property name="TcpSndBuf" value="0"></property>
<property name="ContinueBatchOnError" value="true"></property>
<property name="Logger" value="com.mysql.jdbc.log.StandardLogger"></property>
<property name="MaxAllowedPacket" value="-1"></property>
<property name="PrepStmtCacheSqlLimit" value="256"></property>
<property name="AllowMultiQueries" value="false"></property>
<property name="StrictFloatingPoint" value="false"></property>
<property name="PreparedStatementCacheSqlLimit" value="256"></property>
<property name="CachePreparedStatements" value="false"></property>
<property name="InitialTimeout" value="2"></property>
<property name="UseUnicode" value="true"></property>
<property name="AutoClosePStmtStreams" value="false"></property>
<property name="UseServerPrepStmts" value="false"></property>
<property name="IncludeThreadNamesAsStatementComment" value="false"></property>
<property name="PreparedStatementCacheSize" value="25"></property>
<property name="CreateDatabaseIfNotExist" value="false"></property>
<property name="RollbackOnPooledClose" value="true"></property>
<property name="SocketFactoryClassName" value="com.mysql.jdbc.StandardSocketFactory"></property>
<property name="LoadBalanceValidateConnectionOnSwapServer" value="false"></property>
<property name="CompensateOnDuplicateKeyUpdateCounts" value="false"></property>
<property name="EnablePacketDebug" value="false"></property>
<property name="JdbcCompliantTruncationForReads" value="true"></property>
<property name="PadCharsWithSpace" value="false"></property>
<property name="NoAccessToProcedureBodies" value="false"></property>
<property name="UseTimezone" value="false"></property>
<property name="ClientCertificateKeyStoreType" value="JKS"></property>
<property name="Port" value="3306"></property>
<property name="ClientInfoProvider" value="com.mysql.jdbc.JDBC4CommentClientInfoProvider"></property>
<property name="IsInteractiveClient" value="false"></property>
<property name="CachePrepStmts" value="false"></property>
<property name="ProfileSQL" value="false"></property>
<property name="ProfileSql" value="false"></property>
<property name="MaxQuerySizeToLog" value="2048"></property>
<property name="LoadBalanceHostRemovalGracePeriod" value="15000"></property>
<property name="TreatUtilDateAsTimestamp" value="true"></property>
<property name="DynamicCalendars" value="false"></property>
<property name="DatabaseName" value="tutorials"></property>
<property name="UseSSL" value="false"></property>
<property name="DisconnectOnExpiredPasswords" value="true"></property>
<property name="DontCheckOnDuplicateKeyUpdateInSQL" value="false"></property>
<property name="TrustCertificateKeyStoreType" value="JKS"></property>
<property name="MaxRows" value="-1"></property>
<property name="SlowQueryThresholdMillis" value="2000"></property>
<property name="ResultSetSizeThreshold" value="100"></property>
<property name="UseBlobToStoreUTF8OutsideBMP" value="false"></property>
<property name="TransformedBitIsBoolean" value="false"></property>
<property name="Pedantic" value="false"></property>
<property name="SocksProxyPort" value="1080"></property>
<property name="UseUltraDevWorkAround" value="false"></property>
<property name="EnableQueryTimeouts" value="true"></property>
<property name="LogXaCommands" value="false"></property>
<property name="PrepStmtCacheSize" value="25"></property>
<property name="Password" value="YOUR_DB_PASSWORD_HERE"></property>
<property name="SelfDestructOnPingMaxOperations" value="0"></property>
<property name="ZeroDateTimeBehavior" value="exception"></property>
<property name="CallableStmtCacheSize" value="100"></property>
<property name="SecondsBeforeRetryMaster" value="30"></property>
<property name="EmulateLocators" value="false"></property>
<property name="LoginTimeout" value="0"></property>
<property name="GatherPerfMetrics" value="false"></property>
<property name="RetriesAllDown" value="120"></property>
<property name="DetectCustomCollations" value="false"></property>
<property name="NoDatetimeStringSync" value="false"></property>
<property name="LoadBalanceStrategy" value="random"></property>
<property name="UseOldUTF8Behavior" value="false"></property>
<property name="LoadBalanceAutoCommitStatementThreshold" value="0"></property>
<property name="UltraDevHack" value="false"></property>
<property name="UseFastIntParsing" value="true"></property>
<property name="StrictUpdates" value="true"></property>
<property name="EmptyStringsConvertToZero" value="true"></property>
<property name="UseLocalSessionState" value="false"></property>
<property name="LoggerClassName" value="com.mysql.jdbc.log.StandardLogger"></property>
<property name="NullNamePatternMatchesAll" value="true"></property>
<property name="PopulateInsertRowWithDefaultValues" value="false"></property>
<property name="ProfilerEventHandler" value="com.mysql.jdbc.profiler.LoggingProfilerEventHandler"></property>
<property name="UseStreamLengthsInPrepStmts" value="true"></property>
<property name="RunningCTS13" value="false"></property>
<property name="UseOldAliasMetadataBehavior" value="false"></property>
<property name="ReadOnlyPropagatesToServer" value="true"></property>
<property name="SocketFactory" value="com.mysql.jdbc.StandardSocketFactory"></property>
<property name="MaxReconnects" value="3"></property>
<property name="ReportMetricsIntervalMillis" value="30000"></property>
<property name="BlobsAreStrings" value="false"></property>
<property name="SendFractionalSeconds" value="true"></property>
<property name="CacheResultSetMetadata" value="false"></property>
<property name="Paranoid" value="false"></property>
<property name="AllowUrlInLocalInfile" value="false"></property>
<property name="NoTimezoneConversionForTimeType" value="false"></property>
<property name="EmulateUnsupportedPstmts" value="true"></property>
<property name="ReconnectAtTxEnd" value="false"></property>
<property name="RequireSSL" value="false"></property>
<property name="UseHostsInPrivileges" value="true"></property>
<property name="UseSSPSCompatibleTimezoneShift" value="false"></property>
<property name="UseReadAheadInput" value="true"></property>
<property name="ParseInfoCacheFactory" value="com.mysql.jdbc.PerConnectionLRUFactory"></property>
<property name="DefaultFetchSize" value="0"></property>
<property name="URL" value="jdbc:mysql://localhost:3306/tutorials"></property>
<property name="Url" value="jdbc:mysql://localhost:3306/tutorials"></property>
<property name="AllowMasterDownConnections" value="false"></property>
<property name="CacheDefaultTimezone" value="true"></property>
<property name="QueriesBeforeRetryMaster" value="50"></property>
<property name="FunctionsNeverReturnBlobs" value="false"></property>
<property name="DumpQueriesOnException" value="false"></property>
<property name="LoadBalanceExceptionChecker" value="com.mysql.jdbc.StandardLoadBalanceExceptionChecker"></property>
<property name="VerifyServerCertificate" value="true"></property>
<property name="NetTimeoutForStreamingResults" value="600"></property>
<property name="ProcessEscapeCodesForPrepStmts" value="true"></property>
<property name="UseAffectedRows" value="false"></property>
<property name="GatherPerformanceMetrics" value="false"></property>
<property name="TinyInt1isBit" value="true"></property>
<property name="MetadataCacheSize" value="50"></property>
<property name="RewriteBatchedStatements" value="false"></property>
<property name="CacheCallableStatements" value="false"></property>
<property name="ServerName" value="localhost"></property>
<property name="GetProceduresReturnsFunctions" value="true"></property>
<property name="UseGmtMillisForDatetimes" value="false"></property>
<property name="CapitalizeTypeNames" value="true"></property>
<property name="ServerConfigCacheFactory" value="com.mysql.jdbc.PerVmServerConfigCacheFactory"></property>
<property name="NoTimezoneConversionForDateType" value="true"></property>
<property name="TcpTrafficClass" value="0"></property>
<property name="AutoGenerateTestcaseScript" value="false"></property>
<property name="CacheCallableStmts" value="false"></property>
<property name="FailOverReadOnly" value="true"></property>
<property name="LoadBalancePingTimeout" value="0"></property>
<property name="LocatorFetchBufferSize" value="1048576"></property>
<property name="RelaxAutoCommit" value="false"></property>
<property name="UseFastDateParsing" value="true"></property>
<property name="LoadBalanceEnableJMX" value="false"></property>
<property name="OverrideSupportsIntegrityEnhancementFacility" value="false"></property>
<property name="HoldResultsOpenOverStatementClose" value="false"></property>
<property name="InteractiveClient" value="false"></property>
<property name="UseJvmCharsetConverters" value="false"></property>
<property name="PortNumber" value="3306"></property>
<property name="UseDynamicCharsetInfo" value="true"></property>
<property name="LogSlowQueries" value="false"></property>
<property name="IncludeThreadDumpInDeadlockExceptions" value="false"></property>
<property name="ReplicationEnableJMX" value="false"></property>
<property name="SlowQueryThresholdNanos" value="0"></property>
<property name="UseDirectRowUnpack" value="true"></property>
<property name="UseSqlStateCodes" value="true"></property>
<property name="IncludeInnodbStatusInDeadlockExceptions" value="false"></property>
<property name="MaintainTimeStats" value="true"></property>
<property name="PinGlobalTxToPhysicalConnection" value="false"></property>
<property name="TcpNoDelay" value="true"></property>
<property name="TraceProtocol" value="false"></property>
<property name="AlwaysSendSetIsolation" value="true"></property>
<property name="NullCatalogMeansCurrent" value="true"></property>
<property name="YearIsDateType" value="true"></property>
<property name="SocketTimeout" value="0"></property>
<property name="UseServerPreparedStmts" value="false"></property>
<property name="UseLocalTransactionState" value="false"></property>
<property name="GenerateSimpleParameterMetadata" value="false"></property>
<property name="ExplainSlowQueries" value="false"></property>
<property name="UseColumnNamesInFindColumn" value="false"></property>
<property name="ConnectTimeout" value="0"></property>
<property name="ElideSetAutoCommits" value="false"></property>
<property name="PacketDebugBufferSize" value="20"></property>
<property name="RetainStatementAfterResultSetClose" value="false"></property>
<property name="DumpMetadataOnColumnNotFound" value="false"></property>
<property name="BlobSendChunkSize" value="1048576"></property>
<property name="UseLegacyDatetimeCode" value="true"></property>
<property name="UseUnbufferedInput" value="true"></property>
<property name="AllowLoadLocalInfile" value="true"></property>
<property name="ReadFromMasterWhenNoSlaves" value="false"></property>
<property name="AutoReconnectForPools" value="false"></property>
<property name="TcpKeepAlive" value="true"></property>
<property name="ClobberStreamingResults" value="false"></property>
<property name="User" value="root"></property>
</jdbc-connection-pool>
<jdbc-resource pool-name="MySQLPool" jndi-name="jdbc/tutorialsDS"></jdbc-resource>
还要在<server>...</server>
标签中添加以下行
<resource-ref ref="jdbc/tutorialsDS"></resource-ref>
谢谢阅读。 如果您对许多配置属性有任何疑问或建议,请使用下面的注释部分。
Java 文件上传 REST 服务
原文: https://javatutorial.net/java-file-upload-rest-service
在本教程中,我将解释如何构建 Java REST Web 服务以通过 HTTP 从任何客户端上载文件。
将文件上传到 Web 应用程序是当今的一项常见任务。 许多服务都支持在其网站上上传图片或文档。 使用 Java Web 服务,这很容易实现。 除了 Java Web 容器(由 Tomcat,GlassFish 或 JBoss 等应用服务器提供)之外,我们还需要 Jersey 使它运行。 首先,我将向您展示如何实现 Web 服务,然后为您提供两个使用该服务的客户端示例。
Java 文件上传表格
构建文件上传 REST 服务
该文件通过 HTTP POST 以编码类型multipart/form-data
从客户端推送到我们的 Web 服务。 这样,除了文件之外,您还可以向 POST 请求添加多个参数。 让我们从需求开始。 您将需要 Web 应用程序服务器(例如 Tomcat,GlassFish 或 JBoss)来部署服务。 另外,我们将使用 jersey 框架来构建我们的服务端点。 请注意,GlassFish 4.x 版本需要 jersey 版本 2 库,因此如果使用 GlassFish 4,则请在您的 POM 文件中使用 jersey 2.x 依赖项。
为了快速参考,您可以在我们的 GitHub 存储库中的 https://github.com/JavaTutorialNetwork/Tutorials/tree/master/FileUploaderRESTService 下找到整个项目
我将在此处发布整个 POM 文件,但您需要考虑的是 Jersey 依赖项
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>FileUploaderRESTService</artifactId>
<version>1</version>
<packaging>war</packaging>
<name>File Uploader Rest Service</name>
<url>https://javatutorial.net</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
<version>1.17.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<inherited>true</inherited>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
<webResources>
<resource>
<!-- this is relative to the pom.xml directory -->
<directory>${project.basedir}/src/main/resources
</directory>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
现在拥有所有必需的库,让我们继续实现 REST 服务。 下面的代码中有几个地方我想提醒您注意。 首先请注意@Consumes(MediaType.MULTIPART_FORM_DATA)
作为请求的编码类型的用法。 其次,如果愿意,您可能希望向该方法添加其他参数。 例如,您可能要在上传时传递一些描述或其他文本数据。 最后,如果您尝试将文件上传到不存在的目录中,Java 将抛出异常。 为了避免这个问题,我创建了方法createFolderIfNotExists(StringdirName)
。
package net.javatutorial.tutorials.services;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;
/**
* This example shows how to build Java REST web-service to upload files
* accepting POST requests with encoding type "multipart/form-data". For more
* details please read the full tutorial on
* https://javatutorial.net/java-file-upload-rest-service
*
* @author javatutorial.net
*/
@Path("/upload")
public class FileUploadService {
/** The path to the folder where we want to store the uploaded files */
private static final String UPLOAD_FOLDER = "c:/uploadedFiles/";
public FileUploadService() {
}
@Context
private UriInfo context;
/**
* Returns text response to caller containing uploaded file location
*
* @return error response in case of missing parameters an internal
* exception or success response if file has been stored
* successfully
*/
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(
@FormDataParam("file") InputStream uploadedInputStream,
@FormDataParam("file") FormDataContentDisposition fileDetail) {
// check if all form parameters are provided
if (uploadedInputStream == null || fileDetail == null)
return Response.status(400).entity("Invalid form data").build();
// create our destination folder, if it not exists
try {
createFolderIfNotExists(UPLOAD_FOLDER);
} catch (SecurityException se) {
return Response.status(500)
.entity("Can not create destination folder on server")
.build();
}
String uploadedFileLocation = UPLOAD_FOLDER + fileDetail.getFileName();
try {
saveToFile(uploadedInputStream, uploadedFileLocation);
} catch (IOException e) {
return Response.status(500).entity("Can not save file").build();
}
return Response.status(200)
.entity("File saved to " + uploadedFileLocation).build();
}
/**
* Utility method to save InputStream data to target location/file
*
* @param inStream
* - InputStream to be saved
* @param target
* - full path to destination file
*/
private void saveToFile(InputStream inStream, String target)
throws IOException {
OutputStream out = null;
int read = 0;
byte[] bytes = new byte[1024];
out = new FileOutputStream(new File(target));
while ((read = inStream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
out.close();
}
/**
* Creates a folder to desired location if it not already exists
*
* @param dirName
* - full path to the folder
* @throws SecurityException
* - in case you don't have permission to create the folder
*/
private void createFolderIfNotExists(String dirName)
throws SecurityException {
File theDir = new File(dirName);
if (!theDir.exists()) {
theDir.mkdir();
}
}
}
最后,我们需要配置web.xml
以将我们的类注册为 Web 服务并使其在启动时运行。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>net.javatutorial.tutorials.services</display-name>
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>net.javatutorial.tutorials.services</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
好的! 现在,您可以构建和部署 WAR 文件。 如果您使用上面代码中提供的完全相同的名称,则您的服务 URL(假设您在localhost
上运行)将是:http://localhost:8080/FileUploaderRESTService-1/rest/upload
文件上传 HTML 表格
您可以使用非常简单的 HTML post 表单作为客户端将文件发送到服务器。
请注意multipart/form-data
作为编码类型的用法。 您还需要添加文件类型为[file]
的输入
Choose file to upload<br>
<form action="http://localhost:8080/FileUploaderRESTService-1/rest/upload" method="post" enctype="multipart/form-data">
<input name="file" id="filename" type="file" /><br><br>
<button name="submit" type="submit">Upload</button>
</form>
如前所述,您可以向请求中添加其他数据。 在这种情况下,别忘了在网络服务中处理它🙂
Java 文件上传客户端
您可以为 Android 或 Java 中的独立程序创建文件上传客户端。 在下面的示例中,我将使用 Apache http 库,您将需要以下五个:
- commonlog
- httpclient
- httpclient cache
- httpcore
- httpmime
package net.javatutorial.tutorials.clienst;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.util.EntityUtils;
/**
* This example shows how to upload files using POST requests
* with encoding type "multipart/form-data".
* For more details please read the full tutorial
* on https://javatutorial.net/java-file-upload-rest-service
* @author javatutorial.net
*/
public class FileUploaderClient {
public static void main(String[] args) {
// the file we want to upload
File inFile = new File("C:\\Users\\admin\\Desktop\\Yana-make-up.jpg");
FileInputStream fis = null;
try {
fis = new FileInputStream(inFile);
DefaultHttpClient httpclient = new DefaultHttpClient(new BasicHttpParams());
// server back-end URL
HttpPost httppost = new HttpPost("http://localhost:8080/FileUploaderRESTService-1/rest/upload");
MultipartEntity entity = new MultipartEntity();
// set the file input stream and file name as arguments
entity.addPart("file", new InputStreamBody(fis, inFile.getName()));
httppost.setEntity(entity);
// execute the request
HttpResponse response = httpclient.execute(httppost);
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity responseEntity = response.getEntity();
String responseString = EntityUtils.toString(responseEntity, "UTF-8");
System.out.println("[" + statusCode + "] " + responseString);
} catch (ClientProtocolException e) {
System.err.println("Unable to make connection");
e.printStackTrace();
} catch (IOException e) {
System.err.println("Unable to read file");
e.printStackTrace();
} finally {
try {
if (fis != null) fis.close();
} catch (IOException e) {}
}
}
}
您将在我们的 GitHub 存储库中找到项目文件 https://github.com/JavaTutorialNetwork/Tutorials/tree/master/FileUploaderJavaClient
谢谢阅读。 一如既往欢迎评论🙂
Glassfish 和 Jetty 的 Java WebSockets 教程
原文: https://javatutorial.net/java-websockets-tutorial
在本教程中,我将向您展示如何制作一个在 Glassfish 4 上运行的简单聊天应用程序。我将使用两个 API 来实现此目的:Jetty 和 JSON API。
我们将首先从基于 Web 的客户端开始。 看下面的图片。 它的用户名有一个字段,您可以在其中输入聊天消息的一个字段,以及到目前为止保存对话的文本区域。
网络客户端和 Javascript
我们可以在 JavaScript 中使用WebSocket
在客户端和服务器之间创建全双工连接。 JavaScript Websocket
具有 3 种方法
onopen
– 在客户端和服务器创建连接时调用
onmessage
– 服务器向客户端发送消息时执行此方法
onclose
– 客户端和服务器之间的连接被破坏时调用此方法
用户单击“发送”按钮后,用户名和消息本身将转换为 JSON 格式并发送到服务器。
您将在下面找到客户端的完整代码:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>WebSockets Chat</title>
<meta name="author" content="javatutorial.net" />
<script type="text/javascript" charset="utf-8" src="js/jquery-1.3.2.js"></script>
<link type="text/css" rel="stylesheet" href="css/style.css" />
<script type="text/javascript">
var ws;
$(document).ready(
function() {
ws = new WebSocket("ws://localhost:8080/chat");
ws.onopen = function(event) {
}
ws.onmessage = function(event) {
var $textarea = $('#messages');
var json = JSON.parse(event.data);
$textarea.val($textarea.val() + json.username + ": " + json.message + "\n");
$textarea.animate({
scrollTop : $textarea.height()
}, 1000);
}
ws.onclose = function(event) {
}
});
function sendMessage() {
var message = {
"username": $('#username').val(),
"message": $('#message').val()
}
ws.send(JSON.stringify(message));
$('#message').val('');
}
</script>
</h:head>
<h:body>
<div id="body">
<div id="menu">
<p class="welcome">
User: <input id="username" value="anonymous" />
</p>
<div style="clear: both"></div>
</div>
<div id="chatbox">
<textarea id="messages" rows="16" cols="50" readonly="readonly"></textarea>
</div>
<form name="message" action="">
<input name="usermsg" type="text" id="message" size="63" /> <input
type="button" name="submitmsg" value="Send..."
onclick="sendMessage();" />
</form>
</div>
</h:body>
</html>
实现 Websocket 服务器
我将使用 Glassfish 4.1 部署 WebSocket 服务器。 我们将使用 Jetty 9.x 作为 Websocket 服务器,而 Glassfish 已在 Jetty 中内置。 我们将使用的另一个 API 是 JSON API,它也是 Glassfish 4 的一部分。请查看下面的 Maven pom 文件以获取所需的依赖关系
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial</groupId>
<artifactId>ChatServer</artifactId>
<version>0.0.1</version>
<packaging>war</packaging>
<name>ChatServer</name>
<url>http://javatutorial.net</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.2.7.v20150116</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-server-impl</artifactId>
<version>9.2.7.v20150116</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>9.2.7.v20150116</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.2.7.v20150116</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
ChatMessage
对象保存用户名和消息字符串,以提供Decoder
和Encoder
将结构转换为 JSON 格式。
package net.javatutorial.chatserver.pojos;
import java.io.StringReader;
import java.util.Collections;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonReaderFactory;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
public class ChatMessage {
public static class MessageEncoder implements Encoder.Text<ChatMessage> {
@Override
public void init(EndpointConfig config) {
}
@Override
public String encode(ChatMessage message) throws EncodeException {
return Json.createObjectBuilder()
.add("username", message.getUsername())
.add("message", message.getMessage()).build().toString();
}
@Override
public void destroy() {
}
}
public static class MessageDecoder implements Decoder.Text<ChatMessage> {
private JsonReaderFactory factory = Json
.createReaderFactory(Collections.<String, Object> emptyMap());
@Override
public void init(EndpointConfig config) {
}
@Override
public ChatMessage decode(String str) throws DecodeException {
ChatMessage message = new ChatMessage();
JsonReader reader = factory.createReader(new StringReader(str));
JsonObject json = reader.readObject();
message.setUsername(json.getString("username"));
message.setMessage(json.getString("message"));
return message;
}
@Override
public boolean willDecode(String str) {
return true;
}
@Override
public void destroy() {
}
}
private String username;
private String message;
public ChatMessage() {
}
public ChatMessage(String username, String message) {
super();
this.username = username;
this.message = message;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
最后,我们将需要 Websocket 服务器端点。
package net.javatutorial.chatserver.sockets;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import net.javatutorial.chatserver.pojos.ChatMessage;
import net.javatutorial.chatserver.pojos.ChatMessage.MessageDecoder;
import net.javatutorial.chatserver.pojos.ChatMessage.MessageEncoder;
@ServerEndpoint(value = "/chat", encoders = { MessageEncoder.class }, decoders = { MessageDecoder.class })
public class ChatServerEndpoint {
private static final Set<Session> sessions = Collections
.synchronizedSet(new HashSet<Session>());
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
@OnMessage
public void onMessage(ChatMessage message, Session client)
throws IOException, EncodeException {
for (Session session : sessions) {
session.getBasicRemote().sendObject(message);
}
}
}
您可以在此处下载完整的源代码和 Eclipse 项目。
基于 Glassfish 表单的身份验证示例
原文: https://javatutorial.net/glassfish-form-based-authentication-example
在本教程中,我将向您展示如何使用内置的 Glassfish 身份验证机制通过用户登录创建基于 Web 的应用程序。
如今,身份验证已广泛用于所有类型的 Web 应用程序中。 从网上商店,低谷社交媒体和要求用户登录的其他服务。Glassfish 带有集成的身份验证和授权机制,可让用户登录并维护应用程序中的会话。 我们将利用此功能并构建和应用程序,以允许用户注册和登录以访问授权的内容。 最后,我将向您展示如何创建注销功能以允许用户终止会话。
这是一个完整的 Java EE 教程,它要求 DB 设置,Glassfish 配置,身份验证后端逻辑的开发以及创建前端部分。 我将使用 Glassfish 4,MySQL 数据库和 JSF 2.2 来构建前端。 该代码也可以在 Payara 4 Server 和其他数据库(MSSQL,Oracle 等)上运行,而无需进行任何修改。
项目描述
我们将创建一个简单的 Web 应用程序,其中包含一个注册表单,一个登录表单和一个私人页面。 用户登录后即可访问该私人页面。 表单中的所有字段都将根据错误的用户输入进行验证。
创建数据库表
在继续之前,请确保已使用数据库配置了 Glassfish 服务器。 如果尚未执行此操作,请先阅读如何使用 MySQL 配置 Glassfish 4。 当然,您可以使用您喜欢的任何其他数据库来学习本教程。 您不仅限于 MySQL。
您可以在多个位置存储用户凭据,例如数据库,文件,LDAP 等。在本教程中,我们将使用数据库存储用户名和密码。 在 Glassfish 中,这称为 JDBC 安全领域。 创建安全领域需要两张表 - 第一个用于存储用户凭据,第二个用于将特定用户映射到角色。 Glassfish 并未预定义角色,这意味着我们可以创建自己的角色 - 管理员,用户,主持人,查看者等。为了使本教程尽可能简短,我将只实现一个角色:用户
第一个表称为users
,具有以下字段:
email
– 唯一的主要字段。 我们将使用用户的电子邮件地址作为用户名,如果您愿意,可以使用简单的旧用户名password
– SHA-256 编码的用户密码。 切勿将用户密码存储为纯文本格式 - 不好😉name
– 用户名
Glassfishe 的安全领域只有用户名和密码字段为必填项。 我在此处添加了名称,以说明您可以在同一表单中为用户添加其他信息
CREATE TABLE `users` (
`email` varchar(255) NOT NULL,
`password` varchar(64) NOT NULL,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
user_groups
表具有 2 个必填字段:
email
- 这是用户名groupname
– 存储用户的角色,例如管理员,用户,主持人,查看者等。
CREATE TABLE `user_groups` (
`email` VARCHAR(255) NOT NULL,
`groupname` VARCHAR(32) NOT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
项目结构
在开始实现项目的后端部分之前,我想向您展示项目结构。 这样,您将知道所有文件所在的位置
您可以在 GitHub 上找到完整的源代码,网址为 https://github.com/JavaTutorialNetwork/Tutorials/tree/master/GlassfishFormBasedAuthentication
项目依赖关系(pom.xml
文件)
这是纯 Java EE 7 的实现,除了 Java 企业 API 本身之外,我们在项目中不需要其他依赖项。 该 API 已包含在 Glassfish 中,因此您应将依赖项标记为provided
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javatutorial.tutorials</groupId>
<artifactId>GlassfishFormBasedAuthentication</artifactId>
<version>1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>authexample</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
用户和组实体
在数据库中添加表users
和user_groups
后,必须为这两个表创建 JPA 实体。 实体会将数据库表映射到 Java 对象,因此您可以使用实体对象中的 getter 和 setter 方法轻松地修改或获取数据库。 使用@Entity
注解将 Java 类标记为实体。 该类还必须实现Serializable
接口。
这是用户实体的代码
package net.javatutorial.tutorials.gfauthexample.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@NamedQueries({
@NamedQuery(name = "findUserById", query = "SELECT u FROM User u WHERE u.email = :email")
})
@Table(name="users")
public class User implements Serializable {
private static final long serialVersionUID = -5892169641074303723L;
@Id
@Column(name="email", nullable=false, length=255)
private String email;
@Column(name="password", nullable=false, length=64)
private String password;
@Column(name="name", nullable=false, length=30)
private String name;
public User(){}
public User(String email, String password, String name) {
this.email = email;
this.password = password;
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在第 14 行,定义了NamedQuery "findUserById"
。 我们稍后将在项目中使用此查询来:
- 验证注册期间提供的电子邮件是否尚未用于其他注册
- 登录后显示用户名和电子邮件
在第 11 行,用户实体通过@Table
注释映射到数据库表users
在第 18 行,@Id
注释用于将“电子邮件”表示为主字段
@Column
注解用于将字段从 Java 类映射到表中的字段
团体实体
package net.javatutorial.tutorials.gfauthexample.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="user_groups")
public class Group implements Serializable {
private static final long serialVersionUID = 1528447384986169065L;
public static final String USERS_GROUP = "users";
@Id
@Column(name="email", nullable=false, length=255)
private String email;
@Column(name="groupname", nullable=false, length=32)
private String groupname;
public Group() {}
public Group(String email, String groupname) {
this.email = email;
this.groupname = groupname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGroupname() {
return groupname;
}
public void setGroupname(String groupname) {
this.groupname = groupname;
}
}
字段groupname
用于给定用户角色。 该表是一个映射表,用户 ID 映射到某个角色。 为了使此示例简单,在本教程中创建的所有帐户都将具有“用户”角色。 您可以根据需要添加更多角色。 角色不是预定义的,您可以随意命名。 角色之间的区别在于实现。 例如,如果您具有“管理员”角色,则将要赋予它比“用户”更多的特权。 您必须以编程方式限制一个角色,并以编程方式为另一个角色提供更多选择,并在您自己的代码中进行管理。
persistence.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="tutorialsPU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/tutorialsDS</jta-data-source>
<class>net.javatutorial.tutorials.gfauthexample.entity.User</class>
<class>net.javatutorial.tutorials.gfauthexample.entity.Group</class>
<properties>
<!-- tables will be created only if they do not exist. Use for production
<property name="eclipselink.ddl-generation" value="create-tables"/>
-->
<!-- will first drop the existing table, and then create the new table. Use for development
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
-->
<property name="eclipselink.logging.level" value="INFO"/>
</properties>
</persistence-unit>
</persistence>
在此文件中,我们指定持久层属性。 首先,我们创建一个命名的持久性单元tutorialsPU
。 我们将在 EJB 中使用此持久性单元为EntityManager
创建隔离的PersistenceContext
。 其次,我们指定将用于此持久性单元的数据源。 数据源在 Glassfish 管理控制台中配置(有关更多详细信息,请阅读如何使用 MySQL 配置 Glassfish 4)。 第三,我们列出要包含在持久性单元中的实体类。 在我们的例子中,这是我们在上一步中创建的User
和Group
类。
我还包括了两个其他有用的属性。 您可以根据需要将其注释掉:
如果数据库表不存在,此属性将生成数据库表
<property name="eclipselink.ddl-generation" value="create-tables"/>
部署应用程序时,此属性将擦除现有表,并根据您的实体类创建新的(空)表。 您可以将其用于开发
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
编写业务层 – UserEJB
在这一步中,我们将编写一个 Enterprise Java Bean(EJB),以在数据库中插入新用户并对其进行查询。 EJB 通常用作持久层(数据库实体)和表示层(托管 Bean 和 JSF 页面)之间的中介。
package net.javatutorial.tutorials.gfauthexample.ejb;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import net.javatutorial.tutorials.gfauthexample.entity.Group;
import net.javatutorial.tutorials.gfauthexample.entity.User;
import net.javatutorial.tutorials.gfauthexample.utils.AuthenticationUtils;
@Stateless
public class UserEJB {
@PersistenceContext(unitName="tutorialsPU")
private EntityManager em;
public User createUser(User user) {
try {
user.setPassword(AuthenticationUtils.encodeSHA256(user.getPassword()));
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
e.printStackTrace();
}
Group group = new Group();
group.setEmail(user.getEmail());
group.setGroupname(Group.USERS_GROUP);
em.persist(user);
em.persist(group);
return user;
}
public User findUserById(String id) {
TypedQuery<User> query = em.createNamedQuery("findUserById", User.class);
query.setParameter("email", id);
User user = null;
try {
user = query.getSingleResult();
} catch (Exception e) {
// getSingleResult throws NoResultException in case there is no user in DB
// ignore exception and return NULL for user instead
}
return user;
}
}
请注意,第 16 行的 Bean 被注释为@Stateless
。
在第 19 行,EntityManager
指向"tutorialsPU"
作为持久性单元,全部通过@PersistenceContext
注解完成。 我们在persistence.xml
文件中指定了持久性单元
createUser
方法首先将纯文本密码编码为 SHA-256 字符串,然后将两个对象(用户和组)存储到数据库中。
如果在数据库中找不到该用户,则findUserById
方法将返回一个User
对象或null
请注意此处try-catch
的用法。 如果未找到具有给定 ID(电子邮件)的用户,则对query.getSingleResult()
的调用将引发NoResultException
。 我们将忽略该异常,而是返回一个null
用户。 稍后,我们将在托管 Bean 中处理null
。
实用程序将密码编码为 SHA-256 的方法
package net.javatutorial.tutorials.gfauthexample.utils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
public final class AuthenticationUtils {
/**
* Returns SHA-256 encoded string
* @param password - the string to be encoded
* @return SHA-256 encoded string
* @throws UnsupportedEncodingException if UTF-8 is not supported by the system
* @throws NoSuchAlgorithmException if SHA-256 is not supported by the system
*/
public static String encodeSHA256(String password)
throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(password.getBytes("UTF-8"));
byte[] digest = md.digest();
return DatatypeConverter.printBase64Binary(digest).toString();
}
}
配置 Glassfish 安全领域
我们需要在 Glassfish 中创建和配置一个新的 JDBC 安全领域。
在localhost:4848
打开 Glassfish 管理控制台。 打开“配置 -> 服务器配置 -> 安全 -> 领域”,然后单击“新建…”按钮以创建新的安全领域。
Glassfish 服务器配置安全领域
创建新的 JDBC 安全领域
填写以下字段:
Name
–jdbc-realm
Class Name
–com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
JAAS Context
–jdbcRealm
JNDI
–jdbc/tutorialsDS
User Table
– 用户User Name Column
– 电子邮件Password Column
- 密码Group Table
– user_groupsGroup Table User Name Column
– 电子邮件Group Name Column
– 组名Password Encryption Algorithm
- 无(我们通过编程方式对密码进行加密)Digest Algorithm
– SHA-256Encoding
– Base64
glassfish-web.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
<security-role-mapping>
<role-name>users</role-name>
<group-name>users</group-name>
</security-role-mapping>
<class-loader delegate="true" />
<jsp-config>
<property name="keepgenerated" value="true">
<description>Keep a copy of the generated servlet class' java code.</description>
</property>
</jsp-config>
<parameter-encoding default-charset="UTF-8" />
</glassfish-web-app>
我们必须在glassfish-web.xml
文件中指定security-role-mapping
,并添加角色名称和组名称
web.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Form Based Auth Example</display-name>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
300
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>signin.xhtml</welcome-file>
</welcome-file-list>
<!-- SECURITY -->
<login-config>
<auth-method>FORM</auth-method>
<realm-name>jdbc-realm</realm-name>
<form-login-config>
<form-login-page>/signin.xhtml</form-login-page>
<form-error-page>/signin.xhtml</form-error-page>
</form-login-config>
</login-config>
<security-role>
<description/>
<role-name>users</role-name>
</security-role>
<security-constraint>
<display-name>Restricted to users</display-name>
<web-resource-collection>
<web-resource-name>Restricted Access</web-resource-name>
<url-pattern>/user/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>users</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
web.xml
部署描述符也需要进行一些更改。 我们将在此处配置安全规则。
首先,我们配置会话超时。 这是使用户会话保持活动状态的时间。 当会话超时时,用户不必再次登录。 该值以分钟为单位。 300 表示用户会话将持续 5 个小时。
其次,我们将welcome-file
设置为signin.xhtml
。 当用户打开应用程序时,将显示登录屏幕。
在<login-config>
中设置以下内容:
auth-method
是FORM
– 用户使用 Web 表单输入用户名和密码realm-name
是jdbc-realm
– 这是我们在上一步中配置的安全领域的名称form-login-page
和form-error-page
是带有登录表单的 xhtml 文件和在发生登录错误时显示的 xhtml 文件(我们都指向signin.xhtml
,因为在此文件中进行了错误处理)
我们只有一个安全角色–用户
在<security-constraints>
标签中,我们为特定用户角色定义了受限资源。 换句话说 – user
目录中的每个文件都需要登录才能访问。 我们将在其中放置私有 XHTML 文件。
创建展示层
注册表单
注册表单页面
在注册表单中,我们要求提供用户名,电子邮件地址和密码。 我们还要求用户确认密码。 我们将验证电子邮件,密码和确认密码字段中的用户输入。
首先让我们看一下 JSF XHTML 文件和ManagedBean
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>register</title>
</h:head>
<h:body>
<h:form>
<!-- register a PostValidateEvent -->
<f:event listener="#{registerView.validatePassword}"
type="postValidate" />
<h3>Create new account</h3>
<h:panelGrid columns="2">
<h:outputLabel for="name">Name:</h:outputLabel>
<h:inputText id="name" value="#{registerView.name}" required="true"
requiredMessage="Please enter your name" maxlength="30"></h:inputText>
<h:outputLabel for="email">E-Mail:</h:outputLabel>
<h:inputText id="email" value="#{registerView.email}" required="true"
requiredMessage="Please enter your e-mail address">
<f:validator validatorId="emailValidator" />
</h:inputText>
<h:outputLabel for="password">Password:</h:outputLabel>
<h:inputSecret id="password" value="#{registerView.password}"
required="true" requiredMessage="Please enter password"
validatorMessage="Password must contain atleast one lowercase character,
uppercase character, a digit and it's length must be between 6 and 20 characters">
<f:validateRegex pattern="((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})" />
</h:inputSecret>
<h:outputLabel for="confirmpassword">Confirm password:</h:outputLabel>
<h:inputSecret id="confirmpassword"
value="#{registerView.confirmPassword}" required="true"
requiredMessage="Please confirm your password"></h:inputSecret>
<h:commandButton action="#{registerView.register}" value="Register"></h:commandButton>
</h:panelGrid>
</h:form>
<br />
<br />
<h:link value="I already have an account" outcome="signin" />
</h:body>
</html>
注册视图
package net.javatutorial.tutorials.gfauthexample.managedbeans;
import java.io.Serializable;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;
import javax.inject.Inject;
import net.javatutorial.tutorials.gfauthexample.ejb.UserEJB;
import net.javatutorial.tutorials.gfauthexample.entity.User;
@ManagedBean
@SessionScoped
public class RegisterView implements Serializable {
private static final long serialVersionUID = 1685823449195612778L;
private static Logger log = Logger.getLogger(RegisterView.class.getName());
@Inject
private UserEJB userEJB;
private String name;
private String email;
private String password;
private String confirmPassword;
public void validatePassword(ComponentSystemEvent event) {
FacesContext facesContext = FacesContext.getCurrentInstance();
UIComponent components = event.getComponent();
// get password
UIInput uiInputPassword = (UIInput) components.findComponent("password");
String password = uiInputPassword.getLocalValue() == null ? "" : uiInputPassword.getLocalValue().toString();
String passwordId = uiInputPassword.getClientId();
// get confirm password
UIInput uiInputConfirmPassword = (UIInput) components.findComponent("confirmpassword");
String confirmPassword = uiInputConfirmPassword.getLocalValue() == null ? ""
: uiInputConfirmPassword.getLocalValue().toString();
// Let required="true" do its job.
if (password.isEmpty() || confirmPassword.isEmpty()) {
return;
}
if (!password.equals(confirmPassword)) {
FacesMessage msg = new FacesMessage("Confirm password does not match password");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
facesContext.addMessage(passwordId, msg);
facesContext.renderResponse();
}
if (userEJB.findUserById(email) != null) {
FacesMessage msg = new FacesMessage("User with this e-mail already exists");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
facesContext.addMessage(passwordId, msg);
facesContext.renderResponse();
}
}
public String register() {
User user = new User(email, password, name);
userEJB.createUser(user);
log.info("New user created with e-mail: " + email + " and name: " + name);
return "regdone";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
}
用户输入验证
验证规则:
- 所有字段均为必填项(不允许使用任何空字段)
- 合法的邮件地址
- 密码必须至少包含 1 个小写字符,1 个大写字符,1 个数字
- 密码长度必须在 6 到 20 个字符之间
- 确认密码必须等于密码
- 用户电子邮件应该是唯一的(不用于其他注册)
用户输入表单验证
有 3 种可能的方法来验证 JSF 2 中的字段。我将演示全部 3 种方法。
使用JSFvalidateRegex
我们使用此方法验证密码字段。 我们要做的就是插入具有给定正则表达式模式的f:validateRegex
标签。 如果您想了解更多有关正则表达式的信息,请阅读基本 Java 正则表达式教程
<f:validateRegex pattern="((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20})" />
我们还在validatorMessage
属性内的 JSF 文件中指定了错误消息
在系统事件(PostValidateEvent
)中验证
确认密码字段通过名为PostValidateEvent
的系统事件进行验证。
我们在 XHTML 文件中注册了一个新事件:
<f:event listener="#{registerView.validatePassword}" type="postValidate" />
用户单击“注册”按钮时,将触发RegisterView
bean 中的public void validatePassword(ComponentSystemEvent event)
方法
使用自定义验证程序类
对于电子邮件验证,我们使用一个自定义类,该类实现javax.faces.validator.Validator
接口
Validator
类必须实现一个称为validate
的方法
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
在此方法中,我们根据正则表达式模式验证用户输入,如果该模式不正确,我们将抛出ValidatorException
package net.javatutorial.tutorials.gfauthexample.validators;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
@FacesValidator("emailValidator")
public class EmailValidator implements Validator {
private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\."
+ "[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*" + "(\\.[A-Za-z]{2,})$";
private Pattern pattern;
private Matcher matcher;
public EmailValidator() {
pattern = Pattern.compile(EMAIL_PATTERN);
}
@Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
matcher = pattern.matcher(value.toString());
if (!matcher.matches()) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid e-mail address", null));
}
}
}
我们必须使用以下行在 XHTML 文件中包含给定字段的自定义验证器
<f:validator validatorId="emailValidator" />
检查电子邮件是否已在另一个注册中使用
为此,我们不需要验证器。 您可以在ManagedBean
– RegisterView
中使用类似这样的简单检查
if (userEJB.findUserById(email) != null) { // email is already used }
成功注册
如果用户在注册表单中输入正确的数据,则将创建一个新的User
对象并将其插入数据库中。
User user = new User(email, password, name);
userEJB.createUser(user);
请注意,我们还在UserEJB#createUser
方法内为该用户创建了Group
对象
Group group = new Group();
group.setEmail(user.getEmail());
group.setGroupname(Group.USERS_GROUP);
em.persist(user);
em.persist(group);
数据库条目如下所示:
用户表中的条目
用户组表中的条目
最后,用户被重定向到regdone.xhtml
页面
regdone.xhtml
页面
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html" >
<h:head>
<title>Registration done</title>
</h:head>
<h:body>
<h3>Your account has been successfully created</h3>
<br/><br/>
<h:link value="Sign in with your new account" outcome="signin" />
</h:body>
</html>
登录表单
在此表单中,我们需要用户的电子邮件地址和密码。 表单针对空白字段进行了验证。 在LoginView
中检查用户名和密码的有效性。
登录表单
如果用户输入了有效的登录数据,则会创建一个新的java.security.Principal
对象。 我们还将用户添加到externalContext
的会话地图中
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
Map<String, Object> sessionMap = externalContext.getSessionMap();
sessionMap.put("User", user);
signin.xhtml
文件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>login</title>
</h:head>
<h:body>
<h:form>
<h3>Please Sign In</h3>
<h:panelGrid columns="2">
<h:outputLabel for="email">E-Mail:</h:outputLabel>
<h:inputText id="email" value="#{loginView.email}" required="true"
requiredMessage="Please enter your e-mail address"></h:inputText>
<h:outputLabel for="password">Password:</h:outputLabel>
<h:inputSecret id="password" value="#{loginView.password}"
required="true" requiredMessage="Please enter password"></h:inputSecret>
<h:commandButton action="#{loginView.login}" value="Login"></h:commandButton>
</h:panelGrid>
</h:form>
<br />
<br />
<h:link value="Create new account" outcome="register" />
</h:body>
</html>
LoginView ManagedBean
package net.javatutorial.tutorials.gfauthexample.managedbeans;
import java.io.Serializable;
import java.security.Principal;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import net.javatutorial.tutorials.gfauthexample.ejb.UserEJB;
import net.javatutorial.tutorials.gfauthexample.entity.User;
@ManagedBean
@SessionScoped
public class LoginView implements Serializable {
private static final long serialVersionUID = 3254181235309041386L;
private static Logger log = Logger.getLogger(LoginView.class.getName());
@Inject
private UserEJB userEJB;
private String email;
private String password;
private User user;
public String login() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
request.login(email, password);
} catch (ServletException e) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Login failed!", null));
return "signin";
}
Principal principal = request.getUserPrincipal();
this.user = userEJB.findUserById(principal.getName());
log.info("Authentication done for user: " + principal.getName());
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
Map<String, Object> sessionMap = externalContext.getSessionMap();
sessionMap.put("User", user);
if (request.isUserInRole("users")) {
return "/user/privatepage?faces-redirect=true";
} else {
return "signin";
}
}
public String logout() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
this.user = null;
request.logout();
// clear the session
((HttpSession) context.getExternalContext().getSession(false)).invalidate();
} catch (ServletException e) {
log.log(Level.SEVERE, "Failed to logout user!", e);
}
return "/signin?faces-redirect=true";
}
public User getAuthenticatedUser() {
return user;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
最后,用户被重定向到/user/privatepage.xhtml
私人页面
登出功能
注销很简单。 我们只需要执行注销请求并使会话无效
public String logout() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
this.user = null;
request.logout();
// clear the session
((HttpSession) context.getExternalContext().getSession(false)).invalidate();
} catch (ServletException e) {
log.log(Level.SEVERE, "Failed to logout user!", e);
}
return "/signin?faces-redirect=true";
}
故障排除
对安全代码进行故障排除非常困难。 大多数组件不会引发有意义的异常。 这样做是故意进行的,以防止黑客在尝试破解应用程序的安全性时获得有用的信息。 不幸的是,我们也没有获得这些有用的信息。 如果发现一些问题,请尝试遵循例外。 两次检查您的安全领域配置,并检查所有密码编码是否正确。
如何使用 Java EE 和 Angular 构建单页应用程序
原文: https://javatutorial.net/how-to-build-single-page-application-with-java-ee-and-angular
本文介绍如何使用 Angular 和 Java EE 构建自己的单页应用程序
网络世界一直令人眼花乱。 如今,可见网络上有超过 18 亿个页面,其中大多数页面提供了某种想法,业务,原因或互联网上您拥有的任何内容。 每天都有越来越多的人对 Web 感兴趣,而第二者正在创建内容。 然后,您可以了解为什么 Web 开发是目前最繁荣的工作之一。 该技术本身正在迅速发展,以满足用户的需求,并通过更好的沉浸式用户体验超越了它。 这种追求促使 SPA 等技术成为行业领先的技术。 他们的增长惊人,用户似乎对新的升级感到非常满意。 今天,我们将使用 Angular 和 Java EE 构建自己的单页应用程序,为您自己的实现奠定基础。
图像源
什么是 SPA?
单页应用程序是一种设计模式,特别是一种确定程序流程的架构设计模式。 这样做的想法是一次加载所有数据和元素,以防止在用户使用页面时刷新页面。 例如,您可能已经注意到,首次加载 Gmail 后,它甚至可以脱机运行,而且运行速度也很快。 Gmail,Facebook,GitHub 和许多其他应用程序使用 SPA 来提供自然的 UX,以保持不受干扰的用法。 如今,这种想法已逐渐发展为一种设计方向,PWA 则采用了相同的概念,即提供几乎自然的离线体验。
传统页面生命周期与 SPA 生命周期
图像源
实际上,SPA 的许多优点使其成为开发人员的青睐选择。
例如,使用 SPA,随着编写服务器渲染代码的需求随着 SPA 的消失,开发过程变得越来越容易。 SPA 应用程序直接呈现到浏览器。 将移动应用程序与您的应用程序集成起来比较容易,因为您可以使用相同的网络后端。 当然,大多数资源(如 HTML,CSS 和脚本)在应用程序的生命周期内仅加载一次,这一事实使应用程序变得更快,因为仅在需要时来回传输数据。 这全都归功于 JavaScript 框架为我们完成了大部分繁重的工作。
使用 SPA 确实有一些弊端,例如 SEO 变得有些难以管理,但是在完成应用程序后,我们将讨论如何解决该问题。 今天要使用的框架是著名的 Angular。
为什么要 Angular?
好吧,您可能想知道为什么每个人都对 Angular 狂热。 毕竟,目前的排名表明 Angular 是 JS 框架王国的统治者。 它是迄今为止使用最广泛的框架,并且拥有最大的社区之一和大量文档。 Angular 允许我们使用双向数据绑定来构建动态 Web 应用程序。 在电子商务网站上浏览时,您不想每次签出新产品或同一产品的其他版本时都重新加载页面,而 Angular 允许您这样做。 它也很容易构建和调试,因此使用 Angular 可以使测试变得更加容易,这是 Web 开发的核心步骤。 为此,互联网上有许多 Angular 教程和课程,可以从中找到基本信息。
为什么选择 Java EE?
Java 企业版是健壮的技术和 API 的分组集合,用于企业级别,以构建可满足关键业务需求和约束的可伸缩,事务性和分布式应用程序。 它旨在使 Java 开发保持最新状态,以满足当今应用程序的标准。 Java EE 带有大量的规范,可以满足您的需求。 这就是为什么 Java EE 是理想的开始我们创建第一个 SPA 的旅程的原因。 我们将使用 Java 为 Angular 前端创建 REST 服务以供使用。
因此,让我们开始吧。 我们将设计一个简单的应用程序,在表格中显示演员姓名及其著名的昵称。 请注意,为了避免将本文变成 Java/Angular 课程,并且将重点放在 SPA 开发上,将省略一些代码。
后端
Java 持久化 API 将使我们能够管理和操纵应用程序和服务器之间的数据。 通过使用@Entity
注解,我们可以将对象类建模为数据库内部的数据库实体。 您可能已经猜到@ID
为此关系设置了主键,这是事实。 我们可以使用@table
表示法来指定某些表属性,例如唯一性约束(数据成员必须是唯一的,即候选键)和表名。 @namedqueries
允许我们准备某些查询以用于该关系。 然后,我们继续使用构造函数,setter 和 getter 构建基本类。
演员实体类
我们首先创建 JPA 所需的persistence.xml
文件,它看起来像这样。 javax.persistence.schema-generation
不仅使我们能够以自然的方式运行 SQL,而且还可以连接到数据源(如果我们尚未连接到 SQL 源)。 因此,这样做可以省去您的麻烦。
persistence.xml
文件
这里要做的一件事是创建Resources
类,以将该数据库公开为资源类。@Get
表示法定义 HTTP Get 谓词行为,并以一种非常不错的方式格式化 JSON 响应。
网络服务类
前端
现在该跳到我们的前端了。 我们将分两个步骤进行操作,首先是 HTML 文件,因为 Angular 扩展了典型的 HTML,并允许我们按照 MVC 设计,使用 Javascript 变量向组件添加自定义数据绑定。
HTML 文件中没有太多代码,这使 Angular 相当有效。 Angular 拥有我们将在此处使用的网格组件,Bootstrap 还提供了外观漂亮的组件集,可供我们使用。
Angular HTML 文件
现在剩下的唯一事情就是制作一个 Javascript 文件,将所有内容整合在一起。
Javascript 文件将定义客户端行为,并使用我们之前创建的 REST 服务发出 HTTP 请求。 这是程序中真正魔术的体现。
Angular 代码
Angular SEO 优化
SPA 的核心问题之一是 SEO 优化,这对于某些 SPA 的成功至关重要。 Google 在抓取 Javascript 内容时遇到了问题,尽管他们正在努力,但它还不完全兼容 JS。 有多种方法可以解决此问题,可以手动或使用自定义服务。 这个想法是从您的应用程序创建一个 HTML 快照以直接提供给搜寻器,而不是希望它可以正确理解和索引您的 Angular 代码。 如果您无法手动完成任务,也可以减轻压力,则可以使用预渲染平台来完成任务。 您还可以执行一些辅助操作,例如获取有意义的 URL 名称而不是随机生成的 URL 名称。 维护用户友好的 URL 会反过来给您的用户和应用程序增加很多。 您应该使用一些工具来监视 SPA 的性能,并监视所有需要的更改。
总结
回顾一下,我们首先通过 Java 持久化 API 构建数据库,然后创建了 Java 资源类以作为 REST 服务访问数据库,尽管这里我们仅演示了 GET 方法。 在完成了后端之后,我们继续进行前端,我们创建了 HTML 组件文件,这要归功于 AngularJS,它非常简单明了。 最后,Javascript 文件将所有内容组合在一起,为应用程序注入了生命。
Angular 是一个非常强大的框架,它允许您仅用几行代码即可创建完全不同的 UX,这非常令人惊讶。 能够通过 Java RESTful 后端扩展 JS 前端可以增加更多的表格,并提高您作为开发人员的技能。 Angular 越来越流行,谁知道它何时会成为开发标准,与此同时,它是 SPA 领域的第一大统治框架。 请记住,传统网页也有其优点,因此您需要事先确定要遵循的模式。 例如,MPA 更安全,因为它们涉及的客户端脚本更少,并且正如我们之前提到的,它们更易于针对 SEO 进行优化。 在开发中总会有这种折衷,您必须选择一个选择。 但是,SPA 将继续是一个非常强大的选择。
Spring
在 Eclipse 中安装 Spring STS
原文: https://javatutorial.net/install-spring-sts-in-eclipse
本指南向您展示如何在 Eclipse 中安装 Spring Tool Suite
STS 代表 Spring Tool Suite。 它提供了使用 Eclipse IDE 来实现,运行,调试和部署 Spring 应用程序的现成环境。 它是一个强大的环境,它将帮助您使 Spring 开发更快,更轻松。
如何在 Eclipse 中安装 Spring Tool Suite
1. 打开您的 Eclipse IDE
2. 转到“帮助”,然后选择“Eclipse Marketplace…”
Eclipse 开放市场
3. 在 Eclipse Marketplace 窗口的“查找:”字段中键入“Spring STS”,然后单击“执行”按钮。
搜索 Spring STS
4. 在列表中找到“Spring Tool Suite”,然后单击“安装”按钮。 撰写本指南时的最新版本是3.8.4.RELEASE
安装 Spring Tool Suite
5. 在下一个屏幕上,选择所需的功能,然后按“确定”按钮
STS 功能选择
6. 接受条款和许可协议
7. 等待软件安装
8. 安装完成后,系统将提示您重新启动 Eclipse 以使更改生效
确认安装成功
如果安装顺利,并且您在功能列表中选择了“仪表板”(请参阅步骤 5),那么您将在 Eclipse 工具栏中看到一个新图标
工具栏中的 Spring 仪表板图标
您可能还需要创建一个简单的应用程序,以测试所有配置是否正常。 阅读我们的下一个教程,以了解如何使用 Spring 和 STS 创建一个简单应用
使用 STS 创建简单的 Spring Web App
原文: https://javatutorial.net/spring-web-app-sts
本教程将向您展示如何使用 Spring Framework 和 STS(Spring 工具套件)构建一个简单的 Web 应用程序。 这是一个入门指南,如果您是 Spring 或 STS 的新手,它将为您提供帮助。
注意:本教程要求使用 Eclipse IDE 安装和配置 Spring STS。 如果尚未完成此步骤,请先阅读在 Eclipse 中安装 Spring STS。
什么是 Spring Boot
本教程使用 Spring Boot 创建一个简单的 Web 应用程序,并在嵌入式 Tomcat 服务器上运行。
Spring Boot 是 Spring 的解决方案,用于创建易于构建和运行的独立应用程序。 它使用预配置的 Spring 平台和第三方库,因此您可以在数分钟内开始使用。 大多数 Spring Boot 应用程序只需要很少的 Spring 配置。 此特定示例完全不需要任何配置。
Spring Boot 的功能:
- 创建独立的 Spring 应用程序
- 直接嵌入 Tomcat 或 Jetty(无需部署 WAR 文件)
- 提供 POM 文件以简化您的 Maven 配置
- 尽可能自动配置 Spring
- 提供生产就绪的功能,例如指标,运行状况检查和外部配置
- 完全没有代码生成,也不需要 XML 配置
创建新的 Spring Starter 项目
**1. 启动 Eclipse 并转到“文件 -> 新建 -> 其他…”或按键盘上的Ctrl + N
在搜索字段中输入spring
。 这将列出几个 Spring 选项。 我们需要本教程的一个是“Spring Starter Project”。 选择它并单击“下一步”确认
创建新的 Spring 入门项目
2. 使用下面的屏幕快照中所示的设置。
为您的项目选择一个名称。 选择 Maven 作为构建工具,JAR 打包以及您的 Java 版本。
在“包”下,键入包名称。“Artifact”是您要构建的 JAR 文件的名称。 例如,如果您将springexample
用作工件,则最终的 JAR 文件将称为springexample.jar
Spring Starter 项目设置
3. 在“New Spring Starter 项目依赖项”窗口中,选择“Web”。
如果您在列表中找不到它,请使用搜索字段并在其中键入web
创建 Spring Web 项目
单击“完成”按钮确认。 STS 将为您创建项目并下载所有需要的依赖项。
4. 创建项目后,您将看到一个名为SpringBootExampleApplication.java
的主要 Java 类,一个用于测试的 Java 类,空属性文件,一个 Maven POM 文件以及两个文件,可从命令行执行该应用程序 。 在下面的屏幕截图中,您将在左侧看到项目结构,在右侧看到主类的内容。
Spring Boot Web 项目结构
运行您的 Spring Boot 应用程序
右键单击您的项目,转到“运行方式”,然后选择“Spring Boot App”
作为 Spring Boot 应用程序运行项目
这将引导嵌入式 Tomcat 服务器,部署您的应用程序并映射 URL。 在 Eclipse 控制台中,您将看到类似于以下内容的输出:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.8.RELEASE)
2017-11-28 11:15:29.068 INFO 3428 --- [ main] n.j.t.SpringBootExampleApplication : Starting SpringBootExampleApplication on Filips-MacBook-Air.local with PID 3428 (/Users/filip/Development/GitHub/JavaTutorialNet/SpringBootExample/target/classes started by filip in /Users/filip/Development/GitHub/JavaTutorialNet/SpringBootExample)
2017-11-28 11:15:29.081 INFO 3428 --- [ main] n.j.t.SpringBootExampleApplication : No active profile set, falling back to default profiles: default
2017-11-28 11:15:29.163 INFO 3428 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4461c7e3: startup date [Tue Nov 28 11:15:29 EET 2017]; root of context hierarchy
2017-11-28 11:15:30.753 INFO 3428 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-11-28 11:15:30.773 INFO 3428 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2017-11-28 11:15:30.775 INFO 3428 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.23
2017-11-28 11:15:30.949 INFO 3428 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-11-28 11:15:30.949 INFO 3428 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1791 ms
2017-11-28 11:15:31.201 INFO 3428 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2017-11-28 11:15:31.206 INFO 3428 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-11-28 11:15:31.207 INFO 3428 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-11-28 11:15:31.208 INFO 3428 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-11-28 11:15:31.208 INFO 3428 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2017-11-28 11:15:31.684 INFO 3428 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4461c7e3: startup date [Tue Nov 28 11:15:29 EET 2017]; root of context hierarchy
2017-11-28 11:15:31.777 INFO 3428 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-11-28 11:15:31.779 INFO 3428 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-11-28 11:15:31.820 INFO 3428 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-28 11:15:31.820 INFO 3428 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-28 11:15:31.871 INFO 3428 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-11-28 11:15:32.144 INFO 3428 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-11-28 11:15:32.254 INFO 3428 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-11-28 11:15:32.271 INFO 3428 --- [ main] n.j.t.SpringBootExampleApplication : Started SpringBootExampleApplication in 13.607 seconds (JVM running for 19.742)
启动应用程序后,打开浏览器并转到http://localhost:8080
您将看到一个标签为“Whitelabel Error Page”的页面。 很好 不用担心 它指示服务器已启动,但是 URL /
没有可用的映射。 我们将在下一步中解决此问题。
Spring WhiteLabel 错误页面
创建请求映射
现在,为了能够在http://localhost:8080
下看到一些“有意义的”内容,我们需要更改出价SpringBootExampleApplication.java
的代码。 首先使用@Controller
注解对类进行注解。 其次,创建一个映射到 URL /
的请求 - 这会将home()
方法映射到该 URL。 换句话说,当浏览器向http://localhost:8080
发出 GET 请求时,将通过此方法提供服务。 最后,为了能够将响应返回给浏览器,您需要使用@ResponseBody
注释home()
方法。 您将在下面找到所有更改:
package net.javavatutorial.tutorials;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@SpringBootApplication
public class SpringBootExampleApplication {
@RequestMapping("/")
@ResponseBody
String home() {
return "Greetings from Java Tutorial Network";
}
public static void main(String[] args) {
SpringApplication.run(SpringBootExampleApplication.class, args);
}
}
重新启动 Spring Boot App
在看到上一步中所做的更改之前,您需要重新部署已更改的应用程序。 为此,请使用以下屏幕截图中显示的“Spring 重新启动按钮”
Spring 重新启动按钮
现在转到浏览器并刷新页面。 您应该在屏幕上看到我们的home()
方法返回的字符串
程序输出
该项目的完整源代码可以在我们的 GitHub 存储库中找到: https://github.com/JavaTutorialNetwork/Tutorials/tree/master/SpringBootExample
Spring Web Framework 简介
原文: https://javatutorial.net/introduction-to-spring-web-framework
在本文中,我将解释 Spring Framework 背后的关键概念以及如何进一步使用它来构建 Web 应用程序。
什么是 Spring Framework?
Spring Framework 是 Java 的开源应用程序容器,提供了许多有用的功能,例如控制反转,依赖注入,抽象数据访问,事务管理等。 Spring 最初由 Rod Johnson 于 2002 年作为他的书“专家一对一 J2EE 设计和开发”的三万行代码框架而引入。 该框架最初被称为“interface21”,但后来被重命名并作为开源项目发布,名称为“Spring”。 您可以在 Spring 的博客中阅读全文。
Rod Johnson – Spring 的创造者
Spring 的第一个主要发行版本于 2004 年发布,它是 Java EE 应用程序的重大改变。 此时,J2EE 规范缺少功能且难以使用。 Spring 被接受为 J2EE 的替代品(甚至是替代品),现在仍然如此。 即使到了今天,它也具有 Java 企业版规范中没有的许多功能。
春季版本和发布时间:
- 2006 年版本 2.0
- 2007 年版本 2.5
- 2009 年版本 3.0
- 2011 年版本 3.1
- 2012 年版本 3.2
- 2013 年版本 4.0
- 2015 年版本 4.2
- 2016 年版本 4.3
- 2017 年版本 5.0,这是最新版本
注意:尽管这是 Spring 教程,但与 Java EE 相比,我并不主张使用 Spring。 在此页面(Java 教程网络)上,您还将找到 Java EE 教程的整个部分。 两种技术各有利弊。 Spring 提供了许多功能,甚至可以在企业容器之外运行,例如应用程序服务器等。另一方面,Java EE 已经发展成为具有可靠实现的成熟且可靠的规范。 由您决定是要实现纯 Java EE,Spring 还是两者的结合。 大型公司仍然喜欢使用 Java EE,而中型组织和初创公司更喜欢 Spring(当然,“规则”是有例外的)。 评论部分开放对此主题的争议。
什么是控制反转(IoC)?
Spring 的核心功能之一就是使用了控制反转(IoC)概念。 IoC 旨在颠覆传统编程模型过去在高层对象上所具有的控制流,从而处理较低层对象生命周期的创建和管理。 在 IoC 编程模型中,更高级别的对象宁愿从调用对象或外部框架接收这些依赖项的一个或多个实例。 这就是为什么 IOC 也称为“依赖项注入”的原因,其中依赖项被适当地注入,并且对象仅在程序执行时而不是在对象创建时才会麻烦自己。
简而言之,控制反转(IoC)和依赖注入(DI)模式都是要从代码中删除依赖关系。
例如,假设您的应用程序有一个地图组件,而您想提供一个解析器来翻译地图坐标。 您的标准代码如下所示:
public class Map {
private CoordinatesParser parser;
public Map() {
this.parser = new CoordinatesParser();
}
}
在上面的代码中,我们通过使用this.parser = new CoordinatesParser();
在Map
和CoordinatesParser
之间创建了依赖项。正如您所看到的,Map
类直接依赖于CoordinatesParser
类。
下一个示例显示如何通过使用接口和构造函数注入来避免这种情况
public class Map {
private ICoordinatesParser parser;
public Map(ICoordinatesParser parser) {
this.parser = parser;
}
}
在上面的示例中,我们使用接口ICoordinatesParser
而不是使用类。 这样,我们通过在Map
构造函数签名中具有CoordinatesParser
依赖关系类来创建抽象(而不是在类中初始化依赖关系)。 这使我们可以调用依赖项,然后将其传递给Map
类,如下所示:
ICoordinatesParser parser = new CoordinatesParser(); // dependency
Map map = new Map(parser);
这只是一个简单的例子。 IoC 太笼统,无法用一个严格的例子来描述。 您可以通过使用 setter 注入,构造函数注入或接口注入来实现 DI 的多种方案。
下一步是什么?
在接下来的教程中,我将演示如何使用 Spring 框架来构建 Web 应用程序。 您将学习如何构建 RESTful Web 服务,连接到数据库以及保护应用程序安全。
Java Docker 和 Docker 容器简介
原文: https://javatutorial.net/introduction-to-docker-and-docker-containers-in-java
简而言之,Docker 是一个工具,通过使用所谓的容器,您可以轻松地构建,部署和运行应用程序。 这些容器使我们可以打包所有必不可少的要素,例如库和依赖项。
另外,这些容器在主机操作系统上运行。
当我们使用 Docker 时会带来很多好处。 它
- 增强了应用程序的可移植性,并且可以非常轻松地创建测试应用程序,而不必依赖于环境
- 优化基础设施
- 将应用程序及其依赖项打包到标准化单元中
容器
不,我不是在谈论现实世界中的容器。 但是,既然您正在考虑它,我不妨作个比喻。 现实生活中的容器的目的是存储需要运输到另一个地方的货物或物品。 现在,这些商品具有不同的存储要求,例如,可能存在牛奶的有效期比例如西红柿短的牛奶。 这就是为什么在现实生活中容器是非常有用的原因–容器保留了内部环境,例如敏感商品和/或物品的温度。 话虽如此,运输业不必担心这些物品,而将重点放在将它们从 A 运送到 B 上。
现在,如果您已经注意了以上段落,那么 Docker 中的容器就是一回事。 多亏了 Docker 的容器,我们可以轻松地将容器化应用程序组件从一种环境迁移到另一种环境,希望不会出现任何问题。
就像现实生活中的容器一样,Docker 中的容器为我们提供了一个隔离的安全的应用程序组件环境。 与现实生活中的容器相比,应用组件是商品。
Docker 容器与虚拟机
根据映像,VM 方法的问题在于它附带了很多东西 - 二进制和库(必不可少的),但最主要的是 - 整个来宾操作系统的总容量为几个 GB。
另一方面,Docker 容器包含应用程序的所有依赖关系,与其他容器共享内核。 此外,Docker 容器可以在任何计算机,基础架构和云上运行。 现在,您可以再次阅读真实容器的类比并看到相似之处。
安装 Docker
如果使用 Windows 或 Mac,请单击此处。
安装 Docker 之后,您将拥有以下内容:
- 用于运行
docker-machine
命令的 Docker Machine - 用于运行 docker 命令的容器引擎
- Docker Compose 用于运行
docker-compose
命令 - Kitematic,Docker GUI
- 为 Docker 命令行环境预先配置的 Shell
- 甲骨文 VirtualBox
要检查您是否已成功安装 docker,请输入以下内容:
docker --version
要查看更多信息,例如容器,正在运行,已暂停或已停止的容器数,图像等,可以键入:
docker info
就是这样! 您的机器上有 Docker。 要编写一个简单的“hello world”程序,可以键入以下内容:
docker run hello-world
响应:
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1\. The Docker client contacted the Docker daemon.
2\. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3\. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4\. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
在 Spring 中实现控制器
原文: https://javatutorial.net/implementing-controllers-in-spring
控制器在 Spring 中的主要目的是拦截传入的 http 请求,将数据发送到模型进行处理,最后从模型中获取处理后的数据,然后将完全相同的数据传递给要呈现它的视图。
上面所写内容的顶级概述:
Spring 工作流程
现在,让我们构建一个简单的应用程序,作为在 Spring 中实现控制器的示例。
当您要“声明”一个类作为控制器时,就像使用@Controller
对其进行注释一样简单。 在类级别使用时,控制器现在可以处理 REST API 请求。
GetMapping
注解
当使用@Controller
注解时,可以通过使用该方法的RequestMapping
注解来提供请求映射。 还有@RequestMapping
,@PostMapping
,@PutMapping
可以简化常见 HTTP 方法类型的映射。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@RestController
@SpringBootApplication
public class DemoApplication {
@GetMapping("/")
String home() {
return "Greetings from Java Tutorial Network";
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
上面的代码段,我们创建了一个DemoApplication
,其注释为@Controller
和@SpringBootApplication
。 请注意@GetRequest("/")
的使用。 这就是说home()
方法将显示注释中放置的内容,在本例中为"/"
,即http://localhost:8080/
。
在示例中,我使用的是@RestConroller
,它基本上是控制器的专用版本,其中包括@Controller
和@ResponseBody
注解。 它只是简化了控制器的实现。
当我们运行上面的代码时,我们得到以下结果:
输出量
如果我们想实现一个简单的登录功能,我们将执行以下操作:
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/account/*")
public class AccountController {
@RequestMapping
public String login() {
return "login";
}
}
我们在这里所做的是,我们对类AccountController
和方法login()
都进行了注释。 当访问http://localhost:8080/account/
时,我们将被带到登录页面。 请注意网址末尾的"/"
。 如果不存在,例如http://localhost:8080/account
,将导致错误。
我在account/
之后加上*
的原因是,如果我们想向其添加更多内容(例如注册),则该 URL 将能够处理具有不同路径的所有请求。
但就目前而言,我们将仅坚持登录。 接下来,我们需要为登录创建一个类,并将其注释为@Controller
:
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/account/login")
public class Login {
@GetMapping
public String login() {
return "login";
}
}
现在,当我们访问http://localhost:8080/account/login
时,它将返回login.jsp
文件中的内容。
Spring 中的PathVariable
注解
原文: https://javatutorial.net/pathvariable-annotation-in-spring
就像@RequestParam
一样,@PathVariable
注解用于从 HTTP request 中提取数据。 但是,它们略有不同。 区别在于@RequestParam
从 URL 获取参数,而@PathVariable
只是从 URI 中提取参数。
示例
假设您有一个支持以下 URL 的网站:
http://www.yourwebsite.net/employee/1
上面的 URL 中的1
代表员工的 ID。 到目前为止,一切都很好。 现在,从您的 Spring 控制器开始,路径看起来像(取决于您给id
赋予的名称):
/employee/{id}
以上网址路径对我们有何帮助? 好吧,由于有了{}
语法(碰巧称为 URI 模板),您现在可以使用@PathVariable
将其传递给方法,并且您的方法以及注释将类似于以下内容:
@RequestMapping(value="/employee/{id}", method=RequestMethod.GET)
<Access Modifier> <Return Type> <Method Name> (@PathVariable <Type> id) { <body> }
从上面的代码片段中可以看到,现在id
将从路径中引用{id}
。 让我们尝试一个真实的例子:
@Controller
@SpringBootApplication
public class EmployeeManager {
@RequestMapping(value=" /employee/{id}")
public String pathVariableDemo(Model model, @PathVariable int id) {
model.addAttribute("id", id);
return "demo";
}
}
现在,Spring 查看id
参数,并将其与模板变量id
进行匹配。
请记住,如果我的代码将id
(参数)命名为其他名称,则将无法使用。 但是,如果您不想为参数和模板变量使用相同的名称,则可以指定PathVariable
注解的名称,如下所示:
@Controller
@SpringBootApplication
public class EmployeeManager {
@RequestMapping(value=" /employee/{id}")
public String pathVariableDemo(Model model, @PathVariable("id") int someRandomName) {
model.addAttribute("id", someRandomName);
return "demo";
}
}
从上面的示例中可以看到,我将参数名称更改为someRandomName
,但我还添加了@PathVariable("id")
,它再次指定了我们要引用的模板变量。
因此,最终您有两个选择:
- 方法参数使用相同的名称
- 在
@PathVariable
注解中指定模板变量的名称
多个@PathVariable
注解
如果您可以有多个@PathVariable
,该怎么办? 我们可以做到吗? 我们可以! 实际上,这与添加单个@PathVariable
非常相似。
我来给你展示。
@Controller
@SpringBootApplication
public class EmployeeManager {
@RequestMapping(value="/{company}/employee/{id}", method=RequestMethod.GET)
public String pathVariableDemo(@PathVariable("company") String companyName, @PathVariable("id") int employeeId) {
// handle the code
return "demo";
}
}
从上面的代码片段中可以看到,我们有两个模板变量:
company
id
然后,我们提取每个模板变量,然后通过指定方法参数所引用的临时变量将它们“分配”给方法参数。
@PathVariable
vs @RequestParam
结论
尽管@PathVariable
和@RequestParam
都用于从 URL 提取值,但是可以根据 URL 的设计使用它们。
通常,@PathVariable
通常用于 RESTful Web 服务中,而@RequestParam
用于从查询参数中提取数据。
Spring 中的RequestBody
注解
原文: https://javatutorial.net/requestbody-annotation-in-spring
@RequestBody
注解可用于处理 Web 请求。
更具体地说,它用于将方法参数与请求的主体绑定,其工作方式是HttpMessageConverter
根据请求内容的类型转换请求的主体。
语法
<modifier> <return-type> <method-name> (@RequestBody <type> <name>) {
}
以上语法的示例:
public String congratulateEmployee(@RequestBody Employee emp) {
}
包含上述方法的完整Controller
类:
@RestController
public class CongratulationsController {
@PostMapping("/congratulations")
public Manager assignToManager(@RequestBody Employee emp) {
String name = emp.getName();
int yearsWorked = emp.getYearsWorked();
String message = "Congratulations, " + name + "! You have been working here for " + yearsWorked + ".";
Manager manager = new Manager();
manager.setEmployee(emp.getName()); // now this employee has been assigned to this manager
return manager;
}
}
我们的Employee
类如下所示:
public class Employee {
private String name;
private int yearsWorked;
public String getName() {
return name;
}
public int getYearsWorked() {
return yearsWorked;
}
}
我们的Manager
类如下所示:
public class Manager {
private String employee;
public void setEmployee(String name) {
employee = name;
}
public String getEmployee() {
return employee;
}
}
分解
就像我上面说的,我们收到的 JSON 格式反序列化为 Java 类型。
当我们说@RequestBody Employee emp
时,我们将Employee
类型的方法参数与 Web 请求的主体绑定在一起。RequestBody
到达如下:
{
"name": "John Doe",
"yearsWorked": "3"
}
再次感谢HttpMessageConverter
方法,我们可以将该RequestBody
JSON 响应转换为Employee
对象,该对象包含公共方法getName()
和getYearsWorked()
。 这就是为什么我们可以在RequestBody
参数上调用这些方法的原因:
String name = emp.getName();
int yearsWorked = emp.getYearsWorked();
作为该方法的结果,我们返回了类型为Manager
的数据,由于HttpMessageConverter
,我们已将返回类型转换为以下响应格式:
{
"employee": "the name of the employee that was contained in the @RequestBody"
}
附带一提,RequestBody
注释与RestController
注释一样,主要用于 REST API。
Spring 中的RequestParam
注解
原文: https://javatutorial.net/requestparam-annotation-in-spring
当我们想在控制器类中读取 Web 请求参数时,将使用RequestParam
注解。 换句话说,前端正在向我们发送一些带有键的参数(例如,从填充的表单中)。
用例
假设我们有一个表单,目的是向数据库中添加一个雇员。 每位员工将具有:
- ID
- 名
- 姓
因此,我们的表单如下所示(假设 ID 不会自动递增):
员工表单示例
想象一下,上面看起来丑陋的表单中填充了以下信息:
Id=1
First Name=Joe
Last Name=Doe
现在假设我们有以下代码:
@Controller
@RequestMapping("/employee/*")
public class EmployeeController {
@GetMapping("/fill")
public String fill() {
return "employeeForm";
}
@PostMapping("/fill/process")
public String processForm(ModelMap model, @RequestParam("id") int id, @RequestParam("First Name") String firstName, @RequestParam("Last Name") String lastName) {
model.addAttribute("id", id);
model.addAttribute("firstName", firstName);
model.addAttribute("lastName", lastName);
return "index";
}
}
我们只是将 Web 请求参数绑定到方法参数(id
,firstName
,lastName
)。 但是,为了使此示例正常工作,我们需要从前端接收相应的密钥,否则它们将为null
。
在上面的示例代码中,我们为请求参数提供了值,例如@RequestParam("id")
。 但是,如果目标变量名称与参数名称相同,则可以忽略该值。 因此,我们可以将上述代码片段进行如下操作:
@Controller
@RequestMapping("/employee/*")
public class EmployeeController {
@GetMapping("/fill")
public String fill() {
return "employeeForm";
}
@PostMapping("/fill/process")
public String processForm(ModelMap model, @RequestParam int id, @RequestParam("First Name") String firstName, @RequestParam("Last Name") String lastName) {
model.addAttribute("id", id);
model.addAttribute("firstName", firstName);
model.addAttribute("lastName", lastName);
return "index";
}
}
它之所以起作用,是因为id
与我们作为方法参数给出的名称相同,实际上是id
。 此外,我们无法对其他参数执行相同的操作,因为First Name
与firstName
不同。
@RequestParam
的必需元素
RequestPara
还支持required
元素,该元素几乎完成了所要表达的内容–它指定是否需要特定的参数。 对于我们的示例,我们可以说名字不是必需的。 默认情况下,requried
将设置为true
。
@Controller
@RequestMapping("/employee/*")
public class EmployeeController {
@GetMapping("/fill")
public String fill() {
return "employeeForm";
}
@PostMapping("/fill/process")
public String processForm(ModelMap model, @RequestParam int id, @RequestParam(value = "First Name", requried=false) String firstName, @RequestParam("Last Name") String lastName) {
model.addAttribute("id", id);
model.addAttribute("lastName", lastName);
return "index";
}
}
现在,firstName
不是必需的参数,因此我们没有将其添加到模型/地图中。
@RequestParam
还具有defaultValue
元素
如果我们需要在表单中填写一个值,但是我们并不真正在意那个值是什么,则可以将其设置为默认值,这样,如果用户没有填写它,它将只包含我们为它设置的任何内容。 请参阅以下代码片段作为参考:
@Controller
@RequestMapping("/employee/*")
public class EmployeeController {
@GetMapping("/fill")
public String fill() {
return "employeeForm";
}
@PostMapping("/fill/process")
public String processForm(ModelMap model, @RequestParam int id, @RequestParam(value = "First Name", requried=false) String firstName, @RequestParam(value = "Last Name", defaultValue="Doe") String lastName) {
model.addAttribute("id", id);
model.addAttribute("lastName", lastName);
return "index";
}
}
现在,即使表单没有完全填写(用户省略了姓氏字段),如果我们引用了lastName
参数,也将在其中存储Doe
。 我们为什么要使用defaultValue
元素的一个著名示例是日期。 假设我们具有与上面相同的形式,但是那里也有一个“日期”字段。 好吧,如果用户/员工没有在该字段中输入任何内容,我们可以假设是今天,因此我们将今天的日期作为默认值输入。 这只是许多例子中的一个。
Spring 拦截器
原文: https://javatutorial.net/interceptors-in-spring
顾名思义,在 Spring,拦截器拦截我们通过实现HandlerInterceptor
接口来请求。 它为我们提供了一些方法,使我们能够拦截控制器类正在处理的传入请求或控制器类已经处理的响应。
接口提供给我们的方法有:
preHandle()
– 返回true
或false
。 如果返回true
,则处理程序执行链继续,否则停止。postHandle()
– 处理程序执行后调用。afterCompletion()
– 在请求完成并生成视图之后调用。
HandlerInterceptor
与HandlerInterceptorAdapter
首先,我说过我们需要实现HandlerInterceptor
接口,但是我们也可以实现HandlerInterceptorAdapter
。 它们之间有 1 个区别,就是HandlerInterceptor
我们必须覆盖我上面提到的所有三种方法,而HandlerInterceptorAdapter
允许我们仅覆盖所需的方法。
代码实现
通常,这 3 种方法的工作流程会引发Exception
或返回true
。
@Component
public class EmployeeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
// Basic validation of password and username
String username = request.getParameter("username");
String password = request.getParameter("password");
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
// throw the exception
throw new Exception("Empty username or password.");
}
// if no exception has been thrown, return true
return true;
}
@Override
public boolean postHandle(HttpServletRequest request, HttpServletResponse response,Object handler, ModelAndView modelAndView) throws Exception {
log.info(request);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exc) throws Exception {
if (exc != null)
exc.printStackTrace();
else
log.info("Request: " + request + " / Exception: " + exc);
}
}
让我们分解上面的代码示例。
首先我们创建我们的类并实现HandlerInterceptor
,因为它会覆盖所有三个方法。 按照惯例,类名必须在初始名称之后具有Interceptor
。 然后,我们重写preHandle()
方法。 它包含 3 个参数 - 请求,响应和处理程序,并且不要忘记throws Exception
。
preHandle()
我的preHandle()
方法非常简单–它从请求中获取用户名和密码,然后检查它们是否为空以及是否为空,然后抛出异常,指出“空用户名或密码”。 如果它们不为空,则返回true
。 在正常环境中,您当然会做更多的验证,但是为了简单起见,我这样做了。
postHandle()
如果没有引发异常并记录请求,我的postHandle()
方法从返回true
不会起到什么作用。 它包含 4 个参数 - 请求,响应,处理程序和modelAndView
。 它还throws Exception
。通常,此方法用于修改ModelAndView
(通常通过添加其他属性)或简单地确定处理程序方法处理客户请求所花费的时间。
afterCompletion()
我的afterCompletion()
方法记录了请求和异常,但是在此之前,它通过说exc != null
来检查是否有异常,如果存在,那么我们说exc.printStackTrace()
。
配置
我们的拦截器尚未添加到 Spring 配置中。 要添加它,我们必须实现一个自定义@Configuration
文件,该文件扩展了WebMvcConfigurerAdapter
,该文件在addInterceptors
方法内添加了拦截器。
@Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new EmployeeInterceptor()).addPathPatterns("/account/signin/process");
}
}
另外,请记住,您必须在addInterceptors
方法中指定适当的路径模式。
Spring IOC
原文: https://javatutorial.net/ioc-in-spring
解释控制反转(IOC)概念的一个著名示例是霍利伍德(Holywood)原则,该原则规定“不要打电话给我们,我们会打电话给您”。 毫无疑问,这是一个非常准确的类比,因此有很多人将其称为类比。
通常,类创建依赖关系。 但是,IOC 为我们提供了相反的功能–只要类需要这些依赖关系,它们就可以在运行时提供给类。 您现在知道为什么霍利伍德类比如此准确吗?
课堂上没有说“不要打电话给我们,我们会打电话给您”,而是依赖项说的。
为什么这么重要? 好吧,这很重要,因为这些类可以仅专注于提供功能,而不是在正确的时间调用正确的依赖项。
IOC 到底是什么?
除了上面已经解释的内容外,还可以将控制反转(IOC)视为通常的总体工作流程的反转器,例如 高级对象处理依赖关系的方式。
反转控制(IOC)的另一个著名名称是依赖注入(DI),这意味着依赖已被正确注入,并且对象/类在对象创建中不会受到青睐,而是专注于执行正确的功能。
不使用 IOC 的顶级概述代码实现
public class WithoutIOCDemo {
static class Creature {
public void determineRaceAndSpawn(String _race) {
if (_race.equals("Human")) {
Human hum = new Human();
human.spawn();
}
else if (_race.equals("Sark")) {
Sark sark = new Sark();
sark.spawn();
}
else if (_race.equals("Mavvek")) {
Mavvek mav = new Mavvek();
mav.spawn();
}
else {
System.out.println("No such race spported. The only options are: Human, Sark and Mavvek.");
}
}
}
public static void main(String [] args) {
WithoutIOCDemo noIOC = new WithoutIOCDemo();
noIOC.determineRaceAndSpawn("Human"); // would work
}
}
从该简单示例中可以看到,defineRace
方法依赖于诸如Human
,Sark
和Mavvek
之类的较低级对象。
请注意:方法spawn()
刚刚组成,我们可以假装它存在并且按照它说的去做。
现在,让我们看一下如何通过使用 IOC 并使得defineRace
方法不依赖于较低级别的对象来获得与上述示例相同的结果。
使用 IOC 的顶层概述代码实现
public class WithIOCDemo {
Creature creature;
public WithIOCDemo(Creature creature) {
this.creature = creature;
}
public void determineRaceAndSpawn() {
this.creature.spawn();
}
public static void main(String [] args) {
Creature humanCreature = new Human();
WithIOCDemo noIOC = new WithIOCDemo(humanCreature);
noIOC.determineRaceAndSpawn(); // would work
}
}
乍一看,这种方法为我们节省了很多代码行,而且确实如此。 但是更详细一点,这也使我们的determineRaceAndSpawn()
方法不依赖于可用的Creature
种族(人类,Sark 和 Mavvek(请参考上面的示例))。 “不依赖”是指与没有 IOC 实现的示例不同,我们的方法不需要在方法中实例化相应的生物。 现在所有方法所做的就是生成生物。
简而言之,我们消除了实例化以及生成生物的负担。 现在,它只会生成它。
因此,如果将函数从determineRaceAndSpawn()
重命名为:spawnCreature()
,这将是有意义的。
Java Spring IoC 容器示例
原文: https://javatutorial.net/java-spring-ioc-container-example
简而言之,IoC 容器负责实例化/创建和配置对象以及组装对象之间的依赖关系。
您可能想知道.. IoC 容器如何接收执行上述操作的数据? 答案来自 3 个地方中的 1 个:XML 文件,Java 代码或 Java 注解。
IoC 容器是 Spring 中的一个框架,用于管理 POJO(普通的旧 Java 对象)的生命周期,并在需要时将其插入 Java 程序中。
通常,Java 对象通过两种方式声明其依赖项:
- 通过将它们作为参数传递给构造函数
- 通过将它们作为参数传递给对象的 setter 方法
Spring IoC 容器的基础有两个软件包:org.springframework.beans
和org.springframework.context
。
BeanFactory
接口管理许多 Bean,同时,这些 Bean 之间也具有依赖关系。 它提供了基本功能,更重要的是,它提供了ApplicationContext
,它是BeanFactory
的子接口。 在应用程序运行时,它是只读的;如果实现允许并管理 Bean 的生命周期,则可以重新加载它。
此时,依赖关系不是由 Java 对象管理,而是由框架管理。 倒置 (如果您知道我的意思)。
配置元数据是赋予与我在本教程开始时提到的任务相关的指令的术语 - 对象的实例化,配置和组装。
从上图可以看到,本质上发生的是配置元数据(对象的实例化,配置和组装),并且 JOVO 正在传递到 IOC 容器(很可能是ApplicationContext
的实例)。
如何实例化ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml");
将ApplicationContext
也视为简单的配置。 它从 XML 文件或注释中加载“配置”。 需要在应用程序的开头创建ApplicationContext
,以便它读取应用程序读取所需的所有内容。
以防万一,您可以在应用程序中包含许多ApplicationContext
。 您甚至可以使它们从同一配置文件读取。 这是个好习惯吗? 取决于您要实现的目标。 大多数人会建议将所有 bean 配置在一个位置(通过 XML 或另一个位置)并由单个应用程序上下文加载。
以下示例取自原始 Spring 文档,请随时查看以获取有关 Spring 的深入了解。
services.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore"
class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
同样,您可以将services.xml
视为配置文件,然后将ApplicationContext
加载/读取。
结论
当您只用简单的 DI(依赖注入)而无法使用时,为什么还要使用 IoC 容器?
好吧,这里有几个原因:
- 优雅
- 易于在生产和测试模式之间进行切换
- 只需更改配置
但是有一个缺点,那就是有时正确地实现 IoC 容器有些棘手。
Spring 中的DispatcherServlet
原文: https://javatutorial.net/dispatcher-servlet-in-spring
调度器 Servlet 是 Spring Web MVC 中最重要的组件。
为什么调度器 servlet 是最重要的组件? 因为它充当胶水,这意味着它接收到传入的 URL 并找到正确的方法和视图。 它通过 HTTP 请求接收 URL。 您还可以将其视为中间件,因为它与两端进行通信 - HTTP 请求的发送方和 Spring 应用程序。调度器 Servlet 完全集成在 IoC 容器中,并允许您使用 Spring 拥有的所有功能。
DispatcherServlet
工作流程
以上介绍是顶级概述。 现在,让我们更具体地了解它在幕后的发生方式。
- 服务器请求到达服务器,并且调度器 Servlet 收到。
- 为调度器 Servlet 授予了处理程序对象,在大多数情况下,它将是
HandlerExecutionChain
的实例,该实例来自HandlerMapping
对象,该对象基于 URL 映射 。- 有两种定义 URL 映射的方法:
web.xml
文件- 控制器的方法注释
- 有两种定义 URL 映射的方法:
- 已从处理程序对象中检索到
HandlerInterceptor
对象的一个或多个实例。 - 从处理程序对象中检索
HandlerAdapter
的实例。- 由于这个实例,我们可以调用句柄方法,该方法导致执行控制器类具有的任何逻辑。
HandlerInterceptor
实例上的后处理逻辑已执行。 这是调用渲染方法之前的最后一步。- 由
ViewResolver
实例完成返回给响应的适当视图组件。 - 最后,在该视图的实例上调用了渲染方法。
分派器 Servlet 继承自HttpServlet
,并在 Web 应用程序的web.xml
文件中声明。 同样,如果您从上面阅读了步骤 2,您将看到我们需要 URL 映射。 首先,URL 映射将请求映射到分派器处理程序进行处理。 现在,使用 URL 映射的一种方法是将其存储在相同的web.xml
文件中。
<web-app>
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>*.form</url-pattern>
</servlet-mapping>
</web-app>
此代码段示例摘自 Spring 文档。
示例代码段显示,所有以.form
结尾的请求都将由示例调度器 servlet 处理。
初始化调度器 servlet 后,框架将在应用程序的web-inf
目录中查找名称为[servlet-name]-servlet.xml
的文件,并在该目录中创建 Bean 并覆盖在全局范围内以相同名称定义的 bean 的所有定义。
Spring 示例中的依赖注入
原文: https://javatutorial.net/dependency-injection-in-spring-example
在本教程中,您将学习什么是 Spring 中的依赖注入,它如何工作以及如何使用它。
什么是依赖注入?
依赖注入是您必须了解的 Spring 基础知识之一。
当您创建一个复杂的应用程序时,您将有可能使不同的对象一起工作。 理想情况下,您希望这些对象彼此独立。 此设计原则是控制反转的原则,它强调 Java 类彼此之间保持独立,然后容器会将其从对象创建和维护中解放出来。 如果您不熟悉 IoC,强烈建议您阅读关于该主题的文章。
考虑一下您将如何按照传统方法创建对象依赖关系。 您将必须创建对象类型的实例变量,然后使用构造函数和设置方法或一个(构造方法/设置方法)。 像这样:
public class Employee{
private Company company;
public Employee() {
company = new Company();
}
}
但是,当我们使用 DI 时,它将看起来像这样:
public class Employee{
private Company company;
public Employee(Company company) {
this.company= company;
}
}
您能明白为什么第二个例子更好吗? 因为现在员工无需担心公司的实现,所以它将独立于员工实现,并通过构造函数提供给员工,换句话说,是在Employee
初始化时。 换句话说,如果没有公司的一部分,员工将无法生存。 否则,他就不会当雇员。
依赖项(公司)通过构造函数注入到Employee
类中。
DI 有 2 种类型:
- 构造器注入
- 通过类构造函数注入时
- 基于 Setter 的依赖注入
- 使用 setter 方法注入时
让我们看看如何使用 setter 方法实现 DI。
基于 Setter 的依赖注入
当使用基于 setter 的注入时,容器将在调用无参数构造函数或方法实例化 bean 之后调用该类的 setter 方法。 如果您不熟悉 Spring 中的 bean,则可以阅读本文。
这是基于 setter 的 DI 的实现方式:
@Bean
public void setCompany(Company company) {
this.company = company;
}
至于使用哪种类型,通常建议对强制性依赖项使用构造器 DI,对可选性依赖项使用 setter。
多亏了 DI,现在员工类不需要查找公司,并且它既不知道位置,也不知道它的类别。
实现 Spring MVC 控制器
原文: https://javatutorial.net/implementing-spring-mvc-controllers
本教程描述了实现 Spring MVC 控制器的不同方法,并提供了示例。
在我以前的教程,使用 STS 创建简单的 Spring Web App 中,我向您展示了如何构建引入控制器的 Spring Boot App。 本教程是对上一个教程的扩展。
在开始实现之前,让我们快速概述一下控制器如何参与 MVC 工作流程。
Spring MVC 架构工作流程
- 来自客户端的传入请求由调度器 Servlet 解释
- 调度器 Servlet 通过解析请求属性并使对象对处理程序可用来进行初始处理。
- 确定并调用适当的处理程序以进一步处理请求。 确定适当控制器上的适当方法
- 控制器处理请求并返回
ModelAndView
的实例 - 调度器 Servlet 进一步处理
ModelAndView
的实例,以将响应发送给客户端
在 Spring Boot 应用程序中启用 JSP
如果要启用 JSP,则必须执行以下操作:
在pom.xml
文件中添加以下依赖项:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
在src/main/resources/application.properties
中添加这两行
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
创建文件夹src/main/resources/META-INF/resources/WEB-INF/jsp/
并将 JSP 文件放入其中
实现 Spring 控制器返回 JSP 页面
以下示例演示如何在 Spring Controller
方法中返回 JSP 页面。 请注意@Controller
注释和@RequestMapping
注释的用法。 如果我们想返回一个 JSP 页面,我们将不使用@ResponseBody
注释(如第二个示例所示)。
package net.javatutorial.tutorials;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@SpringBootApplication
public class ControllerExampleJSP {
@RequestMapping("/hellojsp")
String helloJSP() {
return("index");
}
public static void main(String[] args) {
SpringApplication.run(ControllerExampleJSP.class, args);
}
}
@RequestMapping
注释将网址http://localhost:8080/hellojsp
插入到控制器的方法helloJSP()
中。 此方法返回index.jsp
的解析内容
使用 Spring 控制器渲染 JSP 页面
用ResponseBody
实现控制器
与前面的示例不同,此示例将返回由方法而不是 JSP 页面生成的String
。 我们唯一需要更改的就是将@ResponseBody
注解添加到我们的控制器方法中
package net.javatutorial.tutorials;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@SpringBootApplication
public class ControllerResponseBodyExample {
@RequestMapping("/helloresponsebody")
@ResponseBody
String helloResponseBody() {
return("Hello World. This is produced by a method annotated with ResponseBody");
}
public static void main(String[] args) {
SpringApplication.run(ControllerResponseBodyExample.class, args);
}
}
在浏览器中调用http://localhost:8080/helloresponsebody
将产生以下输出:
使用 Spring Controller
和ResponseBody
进行输出
实现 Spring RestController
@RestController
注释用作方便注释,以表示诸如@Controller
和@ResponseBody
之类的注释。 在类级别使用时,控制器可以处理 REST API 请求。
package net.javatutorial.tutorials;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class RestControllerExample {
@RequestMapping("/hellorest")
String helloRest() {
return("Hello World. This is produced by the rest conntroller method");
}
public static void main(String[] args) {
SpringApplication.run(RestControllerExample.class, args);
}
}
在浏览器中调用http://localhost:8080/hellorest
将产生以下输出:
使用 Spring RestController
输出
在方法和类级别使用@RequestMapping
注释
Spring 4.3 引入了诸如@GetMapping
,@PostMapping
和@PutMapping
等注解,以指定常见 HTTP 方法类型(如 GET,POST 和 PUT)的映射。 这些注释增强了代码的可读性。
以下示例演示了如何在方法和类级别上使用映射注释。
package net.javatutorial.tutorials;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user/*")
@SpringBootApplication
public class MethodAndClassLevelAnnotations {
@RequestMapping
String login() {
return("Login method called");
}
@GetMapping("/logout")
String logout() {
return("Logout method called");
}
public static void main(String[] args) {
SpringApplication.run(MethodAndClassLevelAnnotations.class, args);
}
}
向以下网址http://localhost:8080/user/
发出请求,将调用login()
方法。 注意,注释login()
方法的@RequestMapping
没有参数。
在类级别使用的@RequestMapping("/user/*")
注释用作兜底方法,以使用/*
表示的不同路径来处理所有请求。
请求http://localhost:8080/user/logout
将调用logout()
方法。 @GetMapping
注释是一个组合的注释,用作@RequestMapping(method = RequestMethod.GET)
您可以在我们的 GitHub 存储库中找到本教程中的代码示例。
Spring ORM 简介
原文: https://javatutorial.net/introduction-to-spring-orm
在本教程中,您将学习什么是 Spring ORM 以及如何使用它。
什么是 Spring ORM?
Spring ORM 涵盖了 Hibernate,iBatis 和 JPA 等许多技术。 Spring 提供了集成类,因此,所提到的每种技术都可以按照 Spring 的配置原则来实现。
推荐的集成方式是针对 Hibernate,JPA 和 JDO 制作 DAO。
因为所有内容都是作为一组可重用的 JavaBean 设计的,所以我们可以从 ORM 支持中提取与从库中提取一样多的功能。
使用 ORM 的优点
由于我们使用名为“实体”的 java 对象映射到数据库表,因此我们可以使用诸如继承和封装之类的 OOP 概念与环境进行交互。 这是一个巨大的好处,因为我们已经熟悉 OOP 概念,并且不必花时间学习新概念等。
- 更容易的测试
- 常见的数据访问异常
- 一般资源管理
- 整合交易管理
要完全深入了解上述优点,请单击此处以获取文档。
实践
首先,我们需要在 Spring 容器中设置SessionFactory
。 这样做是为了避免将应用程序对象与硬编码的资源查找绑定在一起。 我们在应用程序上下文中将 Hibernate SessionFactory
定义为 bean。 如果您不熟悉 bean,可以单击此处。
<beans>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingResources">
<list>
<value>product.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
</beans>
以下示例显示了 Spring 容器中的 DAO 定义,该定义引用了上面定义的SessionFactory
和 DAO 方法实现的示例。
<beans>
<bean id="myProductDao" class="product.ProductDaoImpl">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
</beans>
public class ProductDaoImpl implements ProductDao {
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public Collection loadProductsByCategory(String category) throws DataAccessException {
return this.hibernateTemplate.find("from test.Product product where product.category=?", category);
}
}
以上代码段摘自原始文档。
我们在这里所做的是,创建了一个实现ProductDao
接口的ProductDaoImpl cla
,并在其中包含HibernateTemplate
实例变量以及setSessionFactory()
和loadProductsByCategory()
方法。
如您所见,在setSessionFactory()
方法中,我们将hibernateTemplate
设置为指定的sessionFactory
。
什么是HibernateTemplate
?
HibernateTemplate
是一个类,可帮助简化数据访问代码。 此类将HibernateExceptions
转换为DataAccessExceptions
,这是未经检查的异常。HibernateTemplate
用于实现数据访问或业务逻辑服务。
如果您对 ORM API 支持的所有方法感兴趣,则可以查看 Oracle javadoc 。
什么是 DAO 以及如何使用它
原文: https://javatutorial.net/what-is-dao-and-how-to-to-use-it
在开始实现这些类之前,我们首先要了解什么是 DAO。 如果您已经知道 DAO 是什么,请随时跳转至代码示例。 如果没有,请忍受我。
DAO 代表数据访问对象,它是一种结构化模式,它通过使用抽象 API 将业务层(逻辑)与持久层(例如数据库)隔离开来。
简而言之,对象(DAO)提供了数据库的抽象接口。 肯定的是,应用程序(业务层/逻辑)不知道有关数据库的任何详细信息,因为这是 DAO 的工作。 它分离业务逻辑和数据。
让我们实现 DAO 模式。
使用 DAO 时,最著名的示例是用户 - 名称,电子邮件,密码。 在本文中,我将使用员工示例 – 名称,电子邮件。
Employee.java
public class Employee {
//members
private String name;
private String email;
// constructor
Employee(String n, String e) {
name = n;
email = e;
}
// setter methods
public void setName(String n) {
name = n;
}
public void setEmail(String e) {
email = e;
}
// getter methods
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
细分
上一个类没什么大不了的。 一个标准的构造函数 /setter/getter 方法类。
在编写EmployeeDAO
类之前,我们首先来看一下Dao<T>
接口包含的内容。
public interface Dao<T> {
Optional<T> get(long id);
List<T> getAll();
void save(T t);
void update(T t, String[] params);
void delete(T t);
}
它具有get
,getAll
,save
,update
,delete
方法。 简而言之,对类型T
的对象执行 CRUD 操作。现在,我们的下一个目标是在EmployeeDAO.java
类中实现该接口。
EmployeeDAO.java
public class EmployeeDAO implements Dao<Employee> {
// will act as a "mini-database"
private List<Employee> employees = new ArrayList<>();
// Constructor
public EmployeeDAO() {
// Populate our list of employees with 3 Demos
employees.add(new Employee("Demo1", "Demo1@example.com"));
employees.add(new Employee("Demo2", "Demo2@example.com"));
employees.add(new Employee("Demo3", "Demo3@example.com"));
}
// Overriding the Dao interface methods
@Override
public Employee get(long id) {
return employees.get((int) id));
}
@Override
public List<Employee> getAll() {
return employees;
}
@Override
public void save(Employee emp) {
employees.add(emp);
}
@Override
public void update(Employee employee, String[] params) {
// Check for validity
if (params[0].length() != 0|| params[1].length() != 0) {
// Initialize the employee
employee.setName(params[0]);
employee.setEmail(params[1]);
// Add the Initialized employee to the list of employees (a.k.a. DB)
employees.add(employee);
}
}
@Override
public void delete(Employee employee) {
employees.remove(employee);
}
}
细分
由于实现了 DaoEmployee
类和EmployeeDAO
而不相互依赖。 如果您熟悉 OO(面向对象)概念,则可以将此关系与 OOP 世界中的聚合相关联。
现在是时候创建应用程序了,也就是业务层或逻辑。
DemoApplication.java
public class DemoApplication {
// Declare an instance of Dao
private static Dao employeeDao;
public static void main (String[] args) {
employeeDao = new EmployeeDAO();
Employee demoEmployee1 = getEmployee(0);
employeeDao.update(demoEmployee1, new String[] {
"Hugh",
"hugh.demo@demo.com"
});
Employee demoEmployee2 = getEmployee(1);
employeeDao.update(demoEmployee2, new String[] {
"Marry",
"marry.demo@demo.com"
});
Employee demoEmployee3 = getEmployee(2);
employeeDao.update(demoEmployee3, new String[] {
"Sharry",
"sharry.demo@demo.com"
});
// print all the employee in the database
for (Employee emp : employeeDao.getAll()) {
System.out.println(emp.getName());
}
// add an entry to the database
Employee newDemoEmp = new Employee("Slim", "slimmy@demo.com");
employeeDao.save(newDemoEmp);
// print all the employee in the database after we have added another entry
for (Employee emp : employeeDao.getAll()) {
System.out.println(emp.getName());
}
}
private static Employee getEmployee(long id) {
// Fetch the employee from the Database based on the id provided
// Keep in mind that the .get method used below is coming from the DAO class
Employee emp = employeeDao.get(id);
// Return that employee
return emp;
}
}
细分
即使上面的代码被大量注释,我仍将添加一些注意事项。 在我们的main
方法之上,我们声明一个 Dao 实例,该实例将用于我们之前使用的被覆盖的方法。
注意我们如何在此类中创建另一个名为getEmployee(long id)
的 getter 方法。 此方法使用另一种get(long id)"
方法。 内部的get
方法是我们在EmployeeDAO
类中覆盖的方法。 在获取了具有给定 ID 的员工之后,我们对其进行分配,然后我们立即使用update
方法来取代。 我们传递了要更新的员工实例以及要给该员工的新名称和新电子邮件。
此后,我们使用覆盖的getAll()
方法,这使我们返回了员工列表。 之后,我们只需将一个新创建的Employee
添加到列表中,然后再次打印该雇员,现在再添加 1 个条目。
结论
DAO 应该执行数据库操作并以类外可以访问的方式组织数据。通常,使用 DAO 类的类是Application
本身或您拥有的Service
类。
简而言之, DAO 为您的服务提供数据。
如何对 DAO 组件进行单元测试
原文: https://javatutorial.net/how-to-unit-test-dao-components
在本教程中,您将学习如何为 DAO 创建单元测试。 前提条件是,您需要具备 DAO 的基本知识。
在测试 DAO 组件时,我们确实有两种方法。一种是使用模拟框架Mockito
,另一种是创建两个可以协同工作的类。
在本教程中,我们将使用Mockito
。
EmployeeDAO
public class EmployeeDAO implements Dao<Employee> {
// will act as a "mini-database"
private List<Employee> employees = new ArrayList<>();
private SessionFactory sessFactory;
// Constructor
public EmployeeDAO(SessionFactory s) {
// Populate our list of employees with 3 Demos
employees.add(new Employee("Demo1", "Demo1@example.com"));
employees.add(new Employee("Demo2", "Demo2@example.com"));
employees.add(new Employee("Demo3", "Demo3@example.com"));
sessFactory = s;
}
// Overriding the Dao interface methods
@Override
public Employee get(long id) {
return employees.get((int) id));
}
@Override
public List<Employee> getAll() {
return employees;
}
@Override
public void save(Employee emp) {
employees.add(emp);
}
@Override
public void update(Employee employee, String[] params) {
// Check for validity
if (params[0].length() != 0|| params[1].length() != 0) {
// Initialize the employee
employee.setName(params[0]);
employee.setEmail(params[1]);
// Add the Initialized employee to the list of employees (a.k.a. DB)
employees.add(employee);
}
}
@Override
public void delete(Employee employee) {
employees.remove(employee);
}
}
如果您想知道Employee
类的外观,则为:
Employee.java
public class Employee {
//members
private String name;
private String email;
// constructor
Employee(String n, String e) {
name = n;
email = e;
}
// setter methods
public void setName(String n) {
name = n;
}
public void setEmail(String e) {
email = e;
}
// getter methods
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
雇员类只是一个标准的构造器/获取器/设置器方法。
现在是时候为 DAO 类创建测试类了。
EmployeeDAOTest
@ExtendWith(SpringExtension.class)
@Tag("DAO")
public class EmployeeDAOTest {
@MockBean
private SessionFactory sessionFactory;
@MockBean
private Session session;
private EmployeeDAO employeeDAO;
@BeforeEach
public void prepare() throws Exception {
Mockito.when(sessionFactory.getCurrentSession()).thenReturn(session);
employeeDAO = new EmployeeDAO(sessionFactory);
}
@Test
public void should_returnNull_ifNonExistent() {
Query q = Mockito.mock(Query.class);
Mockito.when(session.getNamedQuery("get")).thenReturn(q);
Mockito.when(q.getResultList()).thenReturn(new ArrayList());
List l = employeeDAO.getAll();
assertAll("Employees",
() -> assertNotEquals(l, null),
() -> assertEquals(l.size(), 0));
}
}
细分
在上一课中有两件事需要分解。
首先,请注意,我们使用的@MockBean 注解只是将模拟对象添加到应用程序上下文中。 这意味着它将替换任何现有的相同类型的 bean。 如果没有现有的 bean,将创建一个新的 bean。
然后,我们使用@BeforeEach
注解,该注解将在所有单元测试运行之前执行。 因此,该方法的名称为prepare
,我们正在为单元测试准备“环境”。
在prepare
方法中,我们有两件事。 由于SessionFactory
是一个功能接口,因此可以将其用作 lambda 表达式的分配。
我们在prepare()
方法中使用Mockito.when
方法。 它用于在调用过程中给出异常的模拟方法。 所以线
Mockito.when(sessionFactory.getCurrentSession()).thenReturn(session);
确实是在说“让我获得当前会话,如果没有异常,请给我返回该会话”。 然后,我们只需将 DAO 实例分配给一个全新的实例:
employeeDAO = new EmployeeDAO(sessionFactory);
之后,我们就有了测试目标方法,称为should_returnNull_ifNonExistent()
,它的名称如实:返回null
,如果没有则返回空的ArrayList
要返回的列表。 但是在EmployeeDAO
实现中,我们永远不会冒为空的风险,因为一旦创建EmployeeDAO()
实例,我们便在列表中添加了三个Employee
条目:
public EmployeeDAO(SessionFactory s) {
// Populate our list of employees with 3 Demos
employees.add(new Employee("Demo1", "Demo1@example.com"));
employees.add(new Employee("Demo2", "Demo2@example.com"));
employees.add(new Employee("Demo3", "Demo3@example.com"));
sessFactory = s;
}
注意方法的@Test
注解。 这指定此方法用于测试目的。 我们在EmployeeDAO
类中获得了被我们覆盖的get
方法,如果没有异常,则返回q
类型的查询。 然后我们简单地返回一个新的空数组列表。
之后,我们使用getAll()
方法至少应返回 3 个条目,然后使用assertAll()
方法,该方法结合了assertNotEquals
和assertEquals
。 这些行:
assertAll("Employees",
() -> assertNotEquals(l, null),
() -> assertEquals(l.size(), 0));
确实是在说检查l
(包含从getAll()
方法返回的条目的列表)是否为空,并检查列表的大小是否为 0。如果是,则返回true
,assertEquals
的评估结果为true
。
如何对控制器和服务执行单元测试
原文: https://javatutorial.net/how-to-perform-unit-testing-for-controllers-and-services
如您所知,测试非常重要。 因此,在本教程中,您将学习如何完成测试!更具体地说,您将学习如何在控制器和服务上执行该测试。
控制器
普通控制器执行 2 件事之一:
- 渲染视图
要么
- 处理表单提交
让我们看一个代码示例:
@RestController
@RequestMapping("/employee/account/*")
public class EmployeeAccountController {
private EmployeeService employeeService;
// define the logger to which we will be writing stuff for debugging purposes
private static Logger log = LoggerFactory.getLogger(EmployeeAccountController.class);
@Autowired
public EmployeeAccountController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@PostMapping(value="/register/process", produces="application/json")
public Response registrationProcess(ModelMap model, @RequestBody Employee reqEmployee) {
Employee employee = null;
try {
// try to validate the user using the validate() function defined in the EmployeeService class
employee = employeeService.validate(employee.getEmail(), employee.getUsername(), employee.getPassword());
}
catch (Exception e) {
// if the given information did not match the validate() method criteria then print it out to the console
log.debug(e.getMessage(), e);
}
// return appropriate string message
return employee != null ? new Response("Successful registration.") : new Response("Unsuccessful registration.");
}
}
通过查看上面的代码片段,您将看到这只是一个普通的Controller
,它对用户以表单形式给出的信息进行有效性检查。 最后,它会根据有效性检查的响应返回一个字符串,即“成功消息”或“失败消息”。
很棒! 但它缺少一些东西。 那就是单元测试! 让我们添加它。
但是,在向您展示代码之前,我想向您解释什么是MockMvc
。 这是一个 Spring MVC 组件,主要用于为控制器组件创建单元测试。 如何使用MockMvc
的示例代码片段:
private MockMvc name;
@MockBean
private Service service;
@BeforeEach
public string doSomething() throws Exception {
service = MockMvcBuilders.standaloneSetup(className(service)).build();
}
@MockBean 注释可帮助我们在控制器中模拟依赖项。
EmployeeAccountControllerTest.java
@ExtendWith(SpringExtension.class)
@Tag("Controller")
public class EmployeeAccountControllerTest {
private MockMvc mockMvc;
@MockBean
private EmployeeService employeeService;
// define the logger to which we will be writing stuff for debugging purposes
@BeforeEach
public void test(TestInfo info) throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(new EmployeeAccountController(employeeService)).build();
}
@Test
@DisplayName("Return some error message text")
public void ReturnErrorMessage() throws Exception {
Employee emp = new Employee();
emp.setEmail("demo@demo.com");
emp.setUsername("demoname");
emp.setPassword("demo123");
Gson gSon = new Gson();
String gsonEmployee = gSon.toJson(emp);
Mockito.when(employeeService.validate("demo@demo.com", "demoname", "demo123")).thenReturn(null);
// the result
MvcResult result = mockMvc.perform(post("/employee/account/register/process").contentType(MediaType.APPLICATION_JSON).content(gsonEmployee)).andExpect(status().isOk()).andReturn();
MockHttpServletResponse response = result.getResponse();
ObjectMapper mapper = new ObjectMapper();
Response responseString = mapper.readValue(response.getContentAsString(), Response.class);
assertTrue(responseString.getCode().equals("Successful registration."));
assertTrue(responseString.getMessage().equals("Please try again!"));
}
}
基于类的名称,您已经知道这是控制器的测试类。
细分
@BeforeEach
:在每次单元测试之前运行的代码@Test
:单元测试本身@DisplayName
:JUnit5 注解,用于为单元测试分配描述性文本@Tag
:可用于测试发现和执行@ExtendWith(.class)
:将 Spring 5 测试上下文框架与 JUnit 5 集成
因此,在上面的代码中,首先我们使用@ExtendWith
注解,该注解用于将 Spring 5 测试框架与 JUnit 5 集成在一起。然后,我们使用@Tag 注解,用于指定这是Test
类。 然后,我们再次使用@Mockbean
注解,它可以帮助我们模拟依赖项。 然后我们使用@BeforeEach
注解,该注解表示将在每个单元测试之前在 之前运行的代码。 在我们的案例中,建立员工服务。 然后,我们使用@Test 注解指定以下方法为Test
,然后我们也使用@DisplayName
注解,如上所述,该注解为单元测试方法提供了描述性文本(在代码段中不是最具描述性的)。
在该方法中,我们将Employee
实例转换为 JSON,然后将 Mock 行为设置为每次调用validate()
时都返回null
。 然后我们调用控制器方法。
服务
在Controller
测试示例中,我使用EmployeeService
作为服务。 现在在本小节中,我们将看到该服务的实现。
EmployeeServiceImpl.java
@Service
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeDAO employeeDAO;
@Autowired
public EmployeeServiceImpl(EmployeeDAO empDAO) {
employeeDAO = empDAO;
}
@Override
public void update(Employee emp) {
employeeDAO.update(emp);
}
// method that checks whether the employee exists or not
public boolean exists(String username) throws Exception {
List<Employee> employees = (List<Employee>) employeeDAO.findByUsername(username);
// check if there are employees matching the given username
if (employees.getSize() != 0) {
return true;
}
// throw exception
throw new Exception("Employee does not exist in database.");
// return false if there are no matches
return false;
}
}
细分
该类的实现非常简单–首先,我们创建一个构造函数,该构造函数接受Employee
数据访问对象(DAO),并用于分配给我们的私有成员变量。 然后,我们得到了一个update(Employee)
方法,该方法可以执行此操作–根据作为参数提供的信息(Employee
)更新雇员记录。 最后,我们获得了exist(String)
方法,该方法检查具有指定用户名的员工是否存在。 返回true
或false
。
现在,我们为实现创建测试类。
EmployeeServiceTest.java
@ExtendWith(SpringExtension.class)
@Tag("Service")
public class EmployeeServiceTest {
@MockBean
private EmployeeDAO employeeDAO;
private EmployeeService employeeService;
// code run before unit tests
@BeforeEach
public void test(TestInfo info) throws Exception {
employeeService = new EmployeeServiceImpl(employeeDAO);
assertTrue(testInfo.getDisplayName().equals("Error message"));
}
@TestInfo
@DisplayName("Error message")
public void throwException_When_EmployeeDoesNotExist() {
String username = "employee123";
Mockito.when(employeeDao.findByUsername(username)).thenReturn(new ArrayList<User>());
assertThatThrownBy(() -> employeeService.exists(username)).isInstanceOf(Exception.class).hasMessage("Employee does not exist in database.");
}
}
细分
我们为EmployeeService
创建一个Test
类。 在每个单元测试之前,我们运行test(TestInfo)
方法,然后运行throwException_When_EmployeeDoesNotExist()
方法。
安装和配置 MySQL 数据库和服务器以供 Spring 使用
https://javatutorial.net/installing-and-configuring-mysql-database-and-server-for-spring-usage
在本教程的最后,您将安装在 Spring 中开发应用程序所需的正确的 MySQL 产品。 您将需要两件事:MySQL 数据库和 MySQL 服务器。 请按照以下步骤操作。
配置 MySQL 数据库
访问 https://www.mysql.com/downloads/ ,然后选择“社区下载”,如下所示:
社区下载 MySQL
在下一页上,您将看到许多列表。 我们感兴趣的是 MySQL Community 服务器和 Connector/J ,其中 J 代表 Java。
社区服务器和 Java 开发 MySQL
配置 MySQL 服务器
从下载页面单击“MySQL Community Server”之后,您可以看到不同的安装选项。 选择您的选项并下载。 如果要下载 MSI 安装程序,请单击“转到下载页面”:
然后,在下一页上,选择“mysql-installer-community”:
这将下载一个可执行文件。 下载完成后,双击文件,您将看到安装界面。 接受许可条款和条件后,您将看到子窗口“选择安装类型”。 单击“自定义”,因为我们不需要所有 MySQL 产品。 更具体地说,我们只需要“MySQL Server – X64”和“MySQL Workbench – X64”。
在“安装”子窗口上,单击“执行”以开始安装。
安装向导中的下一步是选择服务器配置。 这可能包括添加用户,设置密码和密码。 尽可能使用默认说明。
选择所有配置选项后,您将看到一个带有“执行”按钮的子窗口。 单击它继续。
之后,只需单击“完成”。 您将具有“安装后启动 MySQL 工作台”选项,这意味着当您单击“完成”时,它将启动工作台。
所以你完成了! 您已成功将 MySQL 数据库和 MySQL 服务器安装到计算机上。 这是 MySQL Workbench 的样子:
MySQL 工作台界面
您可以尝试运行一个简单的查询,看看它是如何工作的。
如何在 Spring 中处理登录身份验证
原文: https://javatutorial.net/how-to-handle-login-authentication-in-spring
在本文中,您将学习如何使用 Spring Security 实现登录身份验证功能。
登录页面表示一种表单,要求用户名和密码之类的详细信息。 可以在 Angular 中完成相同的登录页面,并且身份验证过程本身将由 Spring Security 执行。
从服务器中获取一个令牌,然后将其发送回客户端。 此外,服务器希望相同的令牌用于依维签名请求及以后。
工作流程
如上所述,当用户提交登录表单时,他必须输入用户名和密码(最常见)。 当他提交表单时,会有一个 API 调用,该 API 调用来自 Spring Web 。 该路径可能类似于/login
,并通过 HTTP POST 方法调用。
您可以在前端(例如 Angular)中编写身份验证逻辑。 但是在本文中,我们将在服务器端(也称为 Spring)上编写它。 我们可以通过两种方法来实现我们的目标。 一种是通过编写控制器类,另一种是通过扩展UsernamePasswordAuthenticationFilter
。我们将使用第一种方法 - 通过创建login
方法来验证用户身份并返回true
或false
。
ControllerClass.java
// the POST method
@PostMapping(value="/login")
public boolean login(@RequsetBody Employee reqEmployee, HttpServletRequest request) {
Authentication auth = null;
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(reqEmployee.getUsername(), reqEmployee.getPassword());
try {
auth = authenticationProvider.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
Employee employee = (Employee) authentication.getPrincipal();
employee.setPassword(null);
return true;
}
catch (BadCredentialsException exception) {
// you can also log the exception message, for example using Logger
return false;
}
}
这是一种简单的 POST 方法,根据用户是否已成功通过身份验证,它会返回true
或false
。
细分
首先,我们创建身份验证以充当身份验证实例,首先我们将其设为null
。 然后,我们为用户输入的用户名和密码生成一个身份验证令牌。 然后我们有一个try{} catch(){}
块,其中try
根据令牌对用户进行身份验证,如果一切成功,则返回true
。 另一方面,如果未通过身份验证并返回false
,则将运行catch
。
同样,此方法应在Controller
类中。
Spring Security 简介及其设置
https://javatutorial.net/introduction-to-spring-security-and-how-to-to-set-up
在本教程中,您将学习如何使用 Spring Security 来保护 Web 应用程序的安全,包括用户身份验证,授权等。
Spring Security 简介
Spring Security 是一个可定制的身份验证框架。 在保护基于 Spring 的应用程序时,这是标准。 它提供身份验证和授权。 授权也称为“访问控制”。
Spring Security 的核心构建块
SecurityContextHolder
:将当前SecurityContext
与当前执行线程相关联。 它包含当前正在与应用程序交互的SecurityContext
的信息。 安全上下文信息存储在ThreadLocal
对象中,该对象可用于当前线程中运行的所有成员。SecurityContext
:接口,用于定义与当前执行线程关联的最小安全信息。 换句话说,它包含身份验证对象和特定于请求的安全性信息。- 身份验证:表示身份验证请求所需的令牌。 一旦请求通过身份验证,令牌通常将存储在由
SecurityContextHolder
管理的线程本地SecurityContext
中。 用户身份验证之后,将使用UserDetails
对象构建Authentication
对象,然后将其存储在SecurityContextHolder
中。 UserDetails
:提供有关用户的核心信息。UserDetailsService
:加载特定的用户数据。 它是一个接口,允许从数据库加载与用户相关的信息。 该接口仅需要一种只读方法,从而简化了对新数据访问策略的支持。GrantedAuthority
:通常由UserDetailsService
加载。 表示授予身份验证对象的权限。
认证方式
身份验证(如前所述)表示与应用程序交互的主体信息(用户,设备,外部系统等)。 处理用户身份验证时,将从传入请求中检索用户名和密码,然后将其作为UsernamePasswordAuthenticationToken
的实例。 然后将此令牌传递给AuthenticationManager
,该管理器针对数据库中的主体身份验证主体身份。 如果身份验证成功,则AuthenticationManager
返回存储主体详细信息的身份验证对象。 最后,在SecurityContextHolder
方法的帮助下,将身份验证对象填充到安全上下文中。
让我们看一下AuthenticationManager
界面的外观:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
如您所见,它只有一种方法。 但是,这一方法可以用来完成三件事:
- 返回身份验证(请参阅上面的文本)
- 抛出
AuthenticationException
- 返回
null
AuthenticationManager
通常用作ProviderManager
的实现,然后将其委派给AuthenticationProvider
实例链。AuthenticationProvider
与AuthenticationManager
非常相似,只是它有另外一种方法:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
授权书
授权的工作是确定是否允许用户访问给定的资源,换句话说,是否具有许可权。 在此阶段,Spring 为我们提供了授权组件,称为 AccessDecisionManager
。 这对我们有帮助,是为我们提供了 API。
多亏了 AOP,我们可以实现授权。 AOP 决定是否允许调用该方法的用户进行方法调用。
如何在 Spring 应用程序中设置 Spring Security
在 Maven pom.xml
文件中,粘贴以下内容:
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifcatId>spring-security-web</artifactId>
<version>4.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.3</version>
</dependency>
</dependencies>
如何使用 Spring 创建 RESTful Web 服务
原文: https://javatutorial.net/how-to-create-restful-web-services-with-spring
在处理 RESTful Web 服务时,我们需要使用@RestController
注释,该注释基本上表示@Controller
和@ResponseBody
注释。
当我们在方法中使用@RequestMapping
时,我们可以添加一个名为产生的属性,该属性指定发送给用户的输出将为 JSON 格式。
示例
Employee.java
public class Employee {
private int id;
private String firstName;
private String lastName;
public Employee(int id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public setFirstName(String fName) {
firstName = fName;
}
public setLastName(String lName) {
lastName = lName;
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
EmployeeController.java
控制器类将可用于处理 HTTP 请求,这是我们构建 RESTful Web 服务时的约定。
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
@RequestMapping("/employee")
public Employee createEmployee(@RequestParam(value="id") int employeeId, @RequestParam(value="firstName") String fName, @RequestParam(value="lastName") String lName) {
// return the new Employee object with that id, first name and last name
return new Employee(employeeId, fName, lName);
}
}
让我们分解Controller
类。 乍一看,它看起来很简单,但是在幕后发生了很多事情。 让我确切解释一下。
感谢@RequestMapping
,我们将路径/employee
映射到我们的getEmployeeId
方法,如果您不熟悉@RequestMapping
注释,则当我们不指定方法请求时,默认情况下它将通过。 如果我们只想将它作为 GET 方法使用(在我们的示例中最好),则可以将上面的代码更改为以下代码:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
@RequestMapping("/employee", method=RequestMethod.GET)
public Employee createEmployee(@RequestParam(value="id") int employeeId, @RequestParam(value="firstName") String fName, @RequestParam(value="lastName") String lName) {
// return the new Employee object with that id, first name and last name
return new Employee(employeeId, fName, lName);
}
}
因此现在我们将@RequestMapping
注解更改为@RequestMapping("/employee", method=RequestMethod.GET)
。正如我在本教程开始时所说的,我们还指定了我们希望结果为 JSON 格式。 因此我们可以将@RequestMapping("/employee", method=RequestMethod.GET)
更改为@RequestMapping("/employee", method=RequestMethod.GET, Produces="application/json")
。
我们将查询参数id
提取到employeeId
,将firstName
提取到fName
,将lastName
提取到lName
,然后实例化一个新的Employee
对象并首先传递该 ID。 我们作为查询参数得到的名字和姓氏。
要检索这些参数并使用此方法创建Employee
对象,URL 如下所示:
http://localhost:8080/employee?id=x&firstName=xx&lastName=xxx
再一次,感谢@RequestParam
,我们得到了x
,并将它们存储在方法参数中。
运行应用程序
要运行该应用程序,我们将使用main()
方法,该方法如下所示:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SpringApplication.run()
启动应用程序并执行您的所有操作。 这是最简单的方法,但是,如果愿意,可以将其设置为可执行的 JAR,也可以使用 Gradle 或 Maven 从命令行运行它。 你的选择。
响应
JSON 格式的响应主体如下所示:
{
"id":x, firstName="xx", lastName="xxx"
}
Spring CSRF 保护
原文: https://javatutorial.net/csrf-protection-in-spring
在本教程中,您将学习如何保护应用程序免受 CSRF 的侵害。
什么是 CSRF?
如果您已经知道什么 CSRF,请随时继续阅读本分点。 但是,如果您不这样做,CSRF 代表跨站点请求伪造,简单地说,就是攻击者使经过身份验证的用户在网站上执行操作。 结果 – 未经授权的用户执行 Web 应用程序信任的操作。
一个典型的例子是,当用户登录时,网站发送分配给 cookie 的令牌信息作为响应的一部分。 现在用户已经登录,攻击者将尝试使用户从 CSRF 攻击开始的地方访问攻击者的网站。 为了使用户访问其网站,攻击者通常通过电子邮件发送电子邮件。 如果用户单击该链接,则网站上有 1 个或什至更多的 API。 与用户访问过的 Cookie 信息一起发送了请求。 现在,攻击者可以对用户的数据/帐户进行未经授权的更改,例如转移资金。
如何预防 CSRF?
有两种方法可以防止 CSRF。
其中之一是通过实现相同的来源策略。 根据此策略,Web 浏览器允许一个网页中包含的脚本访问另一个网页中的数据,但前提是两个网页具有相同的来源。 此方法取决于 HTTP Cookies 。
另一个是通过应用每个会话/每个请求令牌来过滤作为 CSRF 攻击而来的请求。
Spring 实现中如何防止 CSRF
public class TokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrfToken = (CsrfToken).request.getAttribute(CsrfToken.class.getName());
if (csrfToken != null) {
Cookie cookie = WebUtils.getCoookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || !(token.equals(cookie.getValue())) && token != null) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
细分
我们创建了一个名为TokenFilter
的类来查找特定的 cookie,在这种情况下,它称为XSRF-TOKEN
。
Cookie cookie = WebUtils.getCoookie(request, "XSRF-TOKEN");
如果没有,则创建一个并分配令牌值,然后将 cookie 添加到服务器响应中。
if (cookie == null || !(token.equals(cookie.getValue())) && token != null) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
因此,我们设法编写了自己的Filter
类来查找特定的 cookie,以防止 CSRF;如果该 cookie 存在,则将其添加到服务器响应中;如果不存在,则使用给定名称创建一个新的 cookie。
Spring 中基于 OAuth2 的身份验证和授权
原文: https://javatutorial.net/what-is-oauth2-based-authentication-and-authorization-in-spring
OAuth2 允许第三方应用程序代表资源所有者或通过允许第三方应用程序代表自己获取对 HTTP 服务的有限访问权限,该服务是代表资源所有者的。 借助 OAuth2,服务提供商和消费者应用程序可以安全地相互交互。
工作流程
从外部应用程序访问用户的受保护数据之前,需要执行几个步骤。
- 用户被带到服务提供商服务器
- 例如 Facebook 或 LinkedIn
- 用户必须授予外部应用程序许可才能访问有关其数据的资源,例如读取甚至写入。
- 授权服务器将访问令牌发送到消费者应用程序。
- 现在,外部应用程序可以从资源服务器访问用户的受保护数据。
不同角色的术语
在 OAuth2 中,有 4 个角色:
- 资源所有者
- 用户
- 资源服务器
- 托管受保护资源并根据访问令牌提供对其访问权限的服务器
- 客户
- 寻求许可的外部应用
- 授权服务器
- 在验证用户身份后发出访问令牌
不同的令牌
有两种类型的令牌:
- 访问令牌
- 授权服务器根据用户身份验证提供
- 允许第三方应用程序访问用户数据
- 刷新令牌
- 用于在原始令牌到期时获取新的访问令牌,因此名称
- 但是由于安全原因,并非总是可能获得此令牌
@EnableOAuth2Sso
@Configuration
@EnableZuulProxy
@EnableOAuth2Sso
@Order(value = 0)
public class AppConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private ResourceServerTokenServices resourceServerTokenServices;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/auth-server/**", "/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/");
}
}
@EnableOAuth2Sso
注释通知 Spring 配置OAuth2TokenRelayFilter
。 此过滤器从用户的 HTTP 会话中检索已获取的访问令牌,并填充它们。
@Order
注解的工作是确保由我们的WebSecurityConfigurerAdapter
创建的过滤器优先于由另一个WebSecurityConfigurerAdapter
创建的过滤器。
@EnableResourceServer
现在,让我们设置资源服务器。
@SpringBootApplication
@EnableResourceServer
@Controller
@RequestMapping("/")
class ResourceServerImplementation {
public static void main(String[] args) {
SpringApplication.run(ResourceServerImplementation.class, args);
}
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public String greetPrincipal(Principal principal) {
return "Greetings, " + principal.getName();
}
}
该应用程序返回发起该请求的主体的名称。 同样,我们需要一个有效的访问令牌才能访问资源服务器的端点。
这 2 个代码段摘自此处。
Spring Boot 简介
原文: https://javatutorial.net/introduction-to-spring-boot
在本教程中,您将学习什么是 Spring Boot 以及如何开始使用它。
先决条件
- 您的时间 10-20 分钟
- 熟悉 Maven
什么是 Spring Boot?
Spring Boot 使创建独立的基于 Spring 的应用程序的过程变得非常简单。 通过使用基于 Java 的 Spring Boot 框架,您将不需要太多的配置。
如果您总体上熟悉 Spring,那么您就会知道 Spring 中 XML 配置文件的庞大程度和不可管理性。 好吧,多亏了 Spring Boot,您不会遇到这个问题。
使用 Spring Boot 的另一个好处是,它大大减少了开发时间,并提供了一种更简单的入门方法。
好处
- 易于部署
- 容易拿起
- 节省时间
- 不需要太多配置
- 提供依赖关系管理
- 提供基于注解的 spring 应用程序
既然您知道 Spring Boot 是什么,并且已经熟悉了它的优势,那么让我向您展示如何立即开始使用它。
使用 Maven 构建
在您的项目目录中,创建一个子目录。
例:
mkdir -p src/main/java/demo
这就是您的pom.xml
文件的外观:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-spring-boot</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
请注意,我们正在使用 spring boot maven 插件。 简而言之,它搜索main()
方法,提供内置的依赖项解析器,该解析器设置版本号与 Spring Boot 依赖项相匹配,最后,它收集类路径上的所有 jar,然后构建可运行的“uber-jar”,这使得执行起来更加方便。
让我们构建一个演示 Web 应用程序
让我们创建一个控制器。 控制器的路径为:
src/main/java/demo/DemoController.java
DemoController.java
package demo;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class DemoController {
@RequestMapping("/")
public String handle() {
return "Hello World!";
}
}
对于此控制器类,我们使用@RestController
注解,如果您不熟悉该注解,则它基本上可以供 Spring MVC 使用,以能够处理 Web 请求。 因此,所有到"/"
路径的 Web 请求都将由我们的handle()
方法处理。 将针对此特定路径("/"
)处理 Web 请求的原因是因为我们使用@RequestMapping
注解将其映射到此 Web 路径。
现在是时候创建我们的Application
类了。
让我们构建一个DemoApplication
类
让我们创建我们的DemoApplication
类。 应用程序类的路径为:
src/main/java/demo/DemoApplication.java
DemoApplication.java
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
// that's the main method
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
感谢@SpringBootApplication
注解,我们正在初始化 Spring Boot Application
的入口点。 请注意,我们在此类中有main
方法。
@SpringBootApplication
注解包含@EnableAutoConfiguration
,@ComponentScan
和@SpringBootConfiguration
。 使用@SpringBootApplication
注解时,我们“调用”了所有上述注解。
运行应用程序
要使用 maven 运行应用程序,请执行以下行:
mvn package && java -jar target/gs-spring-boot-0.1.0.jar
Spring MVC 框架介绍
原文: https://javatutorial.net/introduction-to-mvc-framework-in-spring
MVC 代表模型-视图-控制器,Spring 支持它。 MVC 模式的优点在于,它将应用程序的不同方面分开,例如输入,业务逻辑和用户界面。 这种模式使用非常广泛,甚至还带有歌曲。
MVC 模式中的三个组件是模型,视图,控制器 ,因此是首字母缩写。
角色
模型
简而言之,模型将存储 UI 呈现给用户的数据。
视图
简而言之,可视化数据。
控制器
简而言之,控制器管理状态机的行为,也就是响应事件,命令执行和更新模型。 例如,当用户单击按钮时会发生什么。
分派器
Spring 中的整个 MVC 模式都是围绕DispatcherServlet
设计的,该DispatcherServlet
处理所有 HTTP 请求和响应。
MVC 的好处
- 可重用性
- 灵活性
- 强大
让我们看看如何实现上述目标。
可重用性 – 通过使用已经存在的对象来形成其他对象,而不是复制它们。
灵活性 – 可以利用参数注释(如@RequestParam
,@RequestHeader
等)。
强大 – MVC 架构支持异步技术,这意味着使用此模式构建的应用程序加载速度明显加快。
如何定义一个控制器?
@Controller
注释用于使类的行为类似于控制器。 我们还可以在@Controller
旁边使用@RequestMapping
注解,以指定类或方法的 URL 路由。 在我们的例子中,我们将为整个Controller
类指定 URL。
@Controller
@RequestMapping("/")
public class ControllerDemo {
@RequestMapping(method = RequestMethod.GET)
public String helloWorld(ModelMap model) {
model.addAttribute("message", "Hello World");
return "helloWorld";
}
}
由于@RequestMapping("/")
,**该类中存在的所有方法都将相对于"/"
路径。 我们还指定我们的helloWorld
方法处理 GET 请求。
当然,如果需要,您可以添加更多表示 POST 的方法。 为了简单起见,我仅包含 GET。
如何创建视图?
上面,我提到了灵活性是该架构的优势之一。 好吧,Spring MVC 支持多种视图技术 – HTML,PDF,Excel,XML 等。 因此,您的技术将得到支持。 这给您灵活性(无双关语),可以选择您最喜欢使用的任何技术。
就我们而言,我们将使用 HTML 创建视图。
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1> Message coming from helloWorld() method that resides in ControllerDemo class: ${ message } </h1>
</body>
</html>
我们将这个 html 文件(我们的视图)存储在/WEB-INF/helloWorld/
中,并为其命名为helloWorld.jsp
。
您能猜出${message}
给我们带来什么吗? 它将返回我们添加为模型属性的任何消息。 让我们看看示例的情况:
model.addAttribute("message", "Hello World");
因此,${message}
的输出为"Hello World"
。
Spring JDBC 简介
原文: https://javatutorial.net/introduction-to-jdbc-in-spring
在本教程中,您将学习什么是 JDBC 模块,并希望在阅读完之后可以找到用例。
现在,让我们创建一个代表员工的非常简单的表。
CREATE TABLE Employee (
ID INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
AGE INT NOT NULL,
DEPARTMENT VARCHAR(20) NOT NULL,
PRIMARY KEY(ID)
);
Spring JDBC Frameworks 处理底层细节,例如初始化连接,执行 SQL 查询,关闭连接等,因此为我们节省了很多时间和精力。在这种情况下,您可能想知道为我们保留的内容 去做。 好了,我们必须定义连接参数,并指定要执行的 SQL 查询,最后,我们必须在从数据库中获取数据的同时编写所有迭代的逻辑。
有许多方法和已经编写好的类可以帮助我们实现 JDBC。 在我们的例子中,我们将坚持经典的 JDBC 模板类。 它将管理所有数据库通信。
您需要了解有关 JDBC 模板类的哪些知识? 简而言之,它可以捕获 JDBC 异常,执行 SQL 查询和更新语句。 还需要注意的是,JDBC 模板类的所有实例都是线程安全的,这意味着我们可以配置 JDBC 模板类的实例,并将其作为共享引用安全地注入到多个 DAO 中。
如果您不熟悉什么是 DAO,则可以查看我关于该主题的文章。
现在到有趣的部分。 让我们来看看如何实现所有理论。 我们将使用该教程开头显示的表格。
EmployeeDAO.java
package com.tutorialnet;
import java.util.List;
import javax.sql.DataSource;
public interface EmployeeDAO {
public void setDataSource(DataSource ds);
public void create(String name, Integer age, String department);
public Employee getEmployee(Integer id);
public List<Employee> getEmployees();
public void delete(Integer id);
public void update(Integer id, Integer age, String department);
}
这是我们的 DAO 界面。 它包含所有方法声明,并且所有这些方法声明都与 CRUD 功能有关。
setDataSource()
:建立数据库连接。create()
:将用于在数据库中创建新的Employee
条目。getEmployee()
:将根据提供的 ID 返回一名员工。getEmployees()
:将返回数据库中所有雇员的列表。delete()
:将根据提供的 ID 删除员工。update()
:将更新现有员工。
Employee.java
package com.javatutorial;
public class Employee {
private Integer id;
private String name;
private Integer age;
private String department;
public void setId(Integer id) {
this.id = id;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setDepartment(String department) {
this.department = department;
}
public Integer getId() {
return this.id;
}
public Integer getAge() {
return this.age;
}
public String getName() {
return this.name;
}
public String getDepartment() {
return this.department;
}
}
EmployeeMapper.java
package com.javatutorial;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class EmployeeMapper implements RowMapper<Employee> {
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee employee = new Employee();
employee.setId(rs.getInt("id"));
employee.setName(rs.getString("name"));
employee.setAge(rs.getInt("age"));
employee.setDepartment(rs.getString("department"));
return employee;
}
}
EmployeeJDBCTemplate.java
package com.javatutorial;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class EmployeeJDBCTemplate implements EmployeeDAO {
private DataSource dataSource;
private JdbcTemplate jdbcTemplateObject;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public void create(String name, Integer age, String department) {
String SQL = "insert into Employee (name, age, department) values (?, ?)";
jdbcTemplateObject.update( SQL, name, age, department);
}
public Employee getEmployee(Integer id) {
String SQL = "select * from Employee where id = ?";
Employee employee = jdbcTemplateObject.queryForObject(SQL,
new Object[]{id}, new EmployeeMapper());
return employee;
}
public List<Employee> getEmployees() {
String SQL = "select * from Employee";
List <Employee> employees = jdbcTemplateObject.query(SQL, new EmployeeMapper());
return employees;
}
public void delete(Integer id) {
String SQL = "delete from Employee where id = ?";
jdbcTemplateObject.update(SQL, id);
System.out.println("Deleted Record with ID = " + id );
}
public void update(Integer id, Integer age){
String SQL = "update Employee set age = ? where id = ?";
jdbcTemplateObject.update(SQL, age, id);
}
}
JDBC 类定义了我们在上面定义的接口中的所有方法声明。
Main.java
package com.javatutorial;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.tutorialspoint.EmployeeJDBCTemplate;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
EmployeeJDBCTemplate employeeJDBCTemplate = (EmployeeJDBCTemplate)context.getBean("employeeJDBCTemplate");
System.out.println("Creating records..." );
employeeJDBCTemplate.create("Jack", 11, "Software engineering");
employeeJDBCTemplate.create("Joanna", 2, "Finance");
employeeJDBCTemplate.create("Derek", 15, "Hardware engineering");
System.out.println("Listing employee entries from the database..");
List<Employee> employees = employeeJDBCTemplate.getEmployees();
for (Employee employee : employees) {
System.out.print("ID: " + employee.getId());
System.out.print("Name: " + employee.getName());
System.out.println("Age: " + employee.getAge());
System.out.println("Age: " + employee.getDepartment());
}
System.out.println("Updating a record with an id of 1");
employeeJDBCTemplate.update(1, 29, "Marketing");
System.out.println("Displaying information about record with an id of 1");
Employee employee = employeeJDBCTemplate.getEmployee(1);
System.out.print("ID: " + employee.getId());
System.out.print("Name : " + employee.getName() );
System.out.println("Age : " + employee.getAge());
ystem.out.println("Department : " + employee.getDepartment());
}
}
在这里,我们将调用在接口中定义的所有方法。
Beans.xml
的配置文件:
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">
<bean id="dataSource"
class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name = "driverClassName" value = "com.mysql.jdbc.Driver"/>
<property name = "url" value = "jdbc:mysql://localhost:3306/DEMO"/>
<property name = "username" value = "root"/>
<property name = "password" value = "admin123"/>
</bean>
<bean id = "employeeJDBCTemplate"
class = "com.javatutorial.EmployeeJDBCTemplate">
<property name = "dataSource" ref = "dataSource" />
</bean>
</beans>
如何 docker 化 Spring 应用程序
原文: https://javatutorial.net/how-to-dockerize-a-spring-application
在本教程中,您将学习什么是 Docker 以及如何使用它来对 Spring 应用程序进行 Dockerize 。
Docker 文件
Dockerfile 只是.txt
文件。 Dockerfile 允许您运行帮助您构建映像的命令。 如果要指定映像的图层,这些命令将很有用。 要注意的一件事是,我们也可以通过传递这些命令来运行这些命令,而不是将它们包含在文件中。
使用 dockerfiles 时,您需要了解几个关键字:
FROM
:此关键字告诉 Docker 使用给定的基本映像。 但是,如果映像不是本地映像,则 Docker 将在 DockerHub 上执行在线搜索。MAINTAINER
:标识映像的作者。RUN
:在目标系统内执行 Shell 命令行。COPY
:将文件从本地文件系统复制到映像中。WORKDIR
:设置当前工作目录。
让我们看一个简单的例子:
FROM alpine:3.2
MAINTAINER javatutorial.net
ADD target/demo-0.0.1-SNAPSHOT.jar app.jar
RUN sh -c 'touch /app.jar'
您应将 Docker 文件保存在根文件夹中。
现在,我们需要一个.jar
文件,该文件将用于创建 Docker 映像。 要创建.jar
文件,请运行:
mvn clean install
如果您正在使用 Maven 。
保存 Docker 文件后,就该构建我们的 Docker 映像了。
转到您的 Spring 应用程序根文件夹,然后输入以下内容:
docker build -t springboot-app:latest
但是,如果要从另一个文件夹执行build
命令,则build
命令的结构应如下所示:
docker built -t springboot-app:latest -f path-to-the-dockerfile
做完了! 我们构建了 Docker 映像。 现在,要启动容器,请键入以下内容:
docker run -p 8080:8080 app
上面的命令细分:
- 容器是一个隔离的环境,这意味着我们需要映射设置为 8080(第一个)的主机的端口以及容器本身内部的端口(8080 第二个)。
-p 8080
:8080
要访问它,请键入以下内容:
docker exec -ti app bash
要访问日志:
docker logs app
Spring 的@Autowired
注解
原文: https://javatutorial.net/autowired-annotation-in-spring
@Autowired
注解是实现依赖注入的一种相对较新的样式。 它允许您将其他豆注入另一个所需的豆中。 与@Required
的注解类似,@Autowired
注解可用于在 setter 方法以及构造函数和属性上“自动装配” bean。
setter 方法上的@Autowired
请注意,在 setter 方法上使用@Autowired
注解时,它会自动摆脱 XML 配置文件中的<property>
元素。 相反,当 Spring 发现使用@Autowired
注释的 setter 方法时,它将对该特定方法执行按类型“自动装配”。
让我们看看@Autowired
在实践中。 更具体地说,在设置方法上使用@Autowired
。
ExampleService.java
public class ExampleService {
private Employee employee;
@Autowired
public void setEmployee(Employee emp) {
// setting the employee
employee = emp;
}
}
分解
上面的示例中没有什么花哨的。 我们只有一个名为ExampleService
的服务类,该类具有一个Employee
类型的实例变量,并且有一个名为setEmployee(Employee emp)
的设置方法,该方法仅用于设置雇员为参数给出的任何东西。
感谢@Autowired
注解,在实例化ExampleService
时,将Employee
的实例作为该方法的参数注入。
构造函数上的@Autowired
注释
public class ExampleService {
private Employee employee;
@Autowired
public ExampleService(Employee employee) {
this.employee = employee;
}
@Autowired
public void setEmployee(Employee emp) {
// setting the employee
employee = emp;
}
}
再次感谢@Autowired
注释,当实例化ExampleService
时,将Employee
的实例作为构造函数的参数注入。
属性上的@Autowired
注释
自动装配属性节省了我们的时间和代码行。 怎么样? 好吧,当我们在属性上使用该注解时,这些属性不再需要 getter 和 setter。 酷吧?
public class ExampleService {
@Autowired
private Employee employee;
@Autowired
public ExampleService(Employee employee) {
this.employee = employee;
}
@Autowired
public void setEmployee(Employee emp) {
// setting the employee
employee = emp;
}
}
在上面的代码片段中,我们自动连接了名为employee
的属性。 因此,它不再需要 setter 和 getter 方法。employee
在创建ExampleService
时被 Spring 注入。
可选依赖项
@Autowired
也可以是可选的。 像这样:
public class ExampleService {
@Autowired(required = false)
private Employee employee;
@Autowired
public ExampleService(Employee employee) {
this.employee = employee;
}
@Autowired
public void setEmployee(Employee emp) {
// setting the employee
employee = emp;
}
}
required = false
使其不是必需的依赖项。
我们需要可选依赖项的原因是因为 Spring 希望在构造依赖项 bean 时@Autowired
的依赖项可用。 否则,将引发错误。 由于required = false
,我们可以解决此问题。
总结
通过使用@Autowired
注解,我们节省了几行代码,还节省了一些时间,因为我们不需要指定属性和构造函数参数。
Spring AOP 中的核心概念和建议类型
原文: https://javatutorial.net/core-concepts-and-advice-types-in-aop-in-spring
如果您熟悉 Spring,可能已经听说过面向方面的编程(AOP)。 那是 Spring 框架的主要组成部分之一。
但是,不需要以前的 AOP 经验。 它面向希望了解 Spring 中 AOP 框架如何工作的完整初学者。
在面向对象的编程中,可以通过使用类实现应用程序的模块化。 在面向方面的编程中,它是通过切片实现的。 这些方面允许分离切片 。换句话说,它可以向现有代码添加功能,而无需实际对其进行修改。 此外,我们可以分别声明新功能和这些新行为。
AOP 中的核心概念
AOP 中有 7 个核心概念。
- 业务对象:正常的业务逻辑。
- 方面:实现跨多个类的企业应用程序关注点。
- 连接点:应用程序中的特定点,例如变量值,异常处理,方法执行等。
- 建议:针对特定连接点采取的操作。
- 切入点:与连接点匹配的表达式,其目的是确定是否需要执行建议。
- 目标对象:建议适用于此。
- AOP 代理:使用 JDK 动态代理创建的 AOP 实现类,以使用目标类和建议调用/调用创建代理类。
- 编织:将方面与其他对象链接在一起的过程,目的是创建建议的代理对象。
有关这些概念的更多信息,请单击此处。
要实现Business
对象类,请看下面的示例:
public class Example {
public String printHelloWorld() {
return "Hello World!";
}
public String printMessage(String msg) {
return msg;
}
}
这是完全普通的类,没有任何与 Spring 相关的注释。
AOP 建议类型
- 在建议之前:这些建议在执行连接点方法之前运行。 要将建议类型标记为之前,我们可以使用
@Before
注释。 - 建议之后:这些建议在执行连接点方法之后运行。 要将建议类型标记为
After
,可以使用@After
注解。 - 返回建议之后:仅在连接点方法正常执行(不表示异常)时运行这些建议。 要将建议类型标记为“返回后”,我们可以使用
@AfterReturning
注解。 - 抛出建议后:仅在连接点方法抛出异常时才运行这些建议(与“返回建议之后”相反)。 要将建议类型标记为“抛后”,我们可以使用
@AfterThrowing
注解。 - 围绕建议:这些建议为我们提供了灵活性,因为借助它们的使用,我们可以选择是否执行连接点方法。
让我们看看如何实现这些不同的建议类型。
建议之前
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeAdviceDemo {
@Before("com.xyz.demoapp.SystemArchitecture.dataAccessOperation()")
public void methodName() {
// write code here
}
}
通过使用@Before
注解,我们指定这是一个BeforeAdvice
。
建议后
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AfterAdviceDemo{
@After("com.xyz.demoapp.SystemArchitecture.dataAccessOperation()")
public void methodName() {
// write code here
}
}
与“建议之前”实现非常相似,只有我们将“之前”替换为“之后”。
返回建议后
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AfterReturningAdviceDemo{
@AfterReturning("com.xyz.demoapp.SystemArchitecture.dataAccessOperation()")
public void methodName() {
// write code here
}
}
你看到模式了吗?
提出建议后
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AfterThrowingAdviceDemo {
@AfterThrowing("com.xyz.demoapp.SystemArchitecture.dataAccessOperation()")
public void methodName() {
// write code here
}
}
建议
Around
类型的实现有些棘手。 由于这种类型的建议可以在方法执行之前和之后执行工作,因此它需要以线程安全的方式共享该方法执行之前和之后的状态。
要指定建议类型,我们使用@Around
注释,该建议方法的第一个参数必须为ProceedingJoinPoint
类型。 要执行基础方法,我们需要调用proced()
。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundAdviceDemo {
@Around("com.xyz.demoapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
Object value = pjp.proceed();
// return the object
return value;
}
}
Sping Bean 简介
原文: https://javatutorial.net/introduction-to-spring-bean
在本教程中,您将学习什么是 Sping Bean 以及如何使用它。
什么是 Sping Bean?
Bean 是构成应用程序的对象,并由 Spring IoC 容器管理。 Spring Framework 文档的正式定义是:
在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 Bean。 Bean 是由 Spring IoC 容器实例化,组装和以其他方式管理的对象。
如果您不熟悉 Spring IoC 容器,强烈建议您单击此处。您将被重定向到我撰写的有关该主题的文章。
bean 的定义包含称为配置元数据的内容。容器需要知道如何创建 Bean,Bean 的生命周期详细信息以及 Bean 的依赖关系。
如何声明一个 Bean?
我们可以简单地通过使用@Bean
注解来声明一个 bean。 声明带有@Bean
注解的方法的示例:
@Configuration
public class DemoConfig{
@Bean
public String demoService() {
return "Hello World!";
}
}
值得一提的是,当 JavaConfig 遇到@Bean
方法时,它将执行该方法并将返回值注册为BeanFactory
中的 bean。
域类
假设我们有一个名为Company
的类,该类具有一个将Employee
实例分配给实例变量的构造函数。 它还具有该员工的相应的 setter 和 getter 方法。
Company.java
public class Company {
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
现在,让我们看看每个员工的结构:
public class Employee {
private int id;
private String firstName;
private String lastName;
public Employee(int id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public setFirstName(String fName) {
firstName = fName;
}
public setLastName(String lName) {
lastName = lName;
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
具有单个构造函数和 setters/getters 方法的标准方法。
从以上两个类别中,您将如何将员工分配到特定公司? 好吧,首先您将实例化一个Employee
对象,然后将实例化一个Company
对象,并将Employee
对象作为构造函数参数传递。 像这样:
Emplyoee employee = new Employee(1, "John", "Smith");
Company company = new Company(employee);
这是通常的做法。 这种方法有什么问题? 想象一下,如果我们有数百堂课。 这将很难管理,甚至可能无法管理。
由于这个问题,我们有控制反转。 对象可以从 IoC 容器中检索其依赖关系,而不必自己构造依赖关系。
让我们看看如何通过包含本文的主要主题@Bean
来改进上述示例。
AppConfig.java
@Configuration
@ComponentScan(basePackageClasses = Company.class)
public class AppConfig {
@Bean
public Employee getEmployee() {
return new Eployee(1, "John", "Smith");
}
}
现在我们已经编写了配置类,我们需要创建AnnotationConfigApplicationContext
类的实例:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
现在完成! 我们已经使 IoC 容器初始化了 bean。 还有另一件事要做。 检查是否正确创建了 bean。
为此,只需实例化一个Company
实例,然后我们可以使用assertEquals
方法检查更正。
Company company = context.getBean("company", Company.class);
assertEquals("John", company.getEmployee().getFirstName());
assertEquals(1, company.getEmployee().getId());