HowToDoInJava-Java-教程-二-

HowToDoInJava Java 教程(二)

原文:HowToDoInJava

协议:CC BY-NC-SA 4.0

JVM 内存模型/结构和组件

原文: https://howtodoinjava.com/java/garbage-collection/jvm-memory-model-structure-and-components/

每当执行 Java 程序时,都会保留一个单独的存储区,用于存储应用程序代码的各个部分,这些部分您大致称为 JVM 内存。 尽管不是必需的,但是具有一定的知识对该存储区进行构造是有益的。 当您开始进行更深层次的工作(例如性能调整)时,它变得尤为重要。 如果没有很好地了解 JVM 实际如何使用内存以及垃圾回收器如何使用该内存的不同部分,您可能会错过一些重要的注意事项,以进行更好的内存管理。 从而获得更好的性能。

在本教程中,我将讨论 JVM 内存内部的各个部分,然后您将在以后的一篇文章中讨论如何使用此信息进行应用程序的性能调整。

Table of Contents

JVM memory areas / components
	- Heap area
	- Method area and runtime constant pool
	- JVM stack
	- Native method stacks
	- PC registers

JVM 内存模型/结构

Java 虚拟机定义了在程序执行期间使用的各种运行时数据区域。 其中一些数据区域是在 Java 虚拟机启动时创建的,仅在 Java 虚拟机退出时才被销毁。 其他数据区域是每个线程的。 在创建线程时创建每个线程的数据区域,并在线程退出时销毁每个数据区域。

让我们看一下运行时内存中各个部分的最基本分类。

JVM Memory Area Parts

JVM 内存区域部分

让我们根据 JVM 规范中提到的内容,快速浏览每个组件。

堆区域

堆区代表运行时数据区,从中为所有类实例和数组分配内存,并在虚拟机启动期间创建。

自动存储管理系统回收对象的堆存储。 堆可以是固定大小,也可以是动态大小(基于系统的配置),并且分配给堆区域的内存不必是连续的。

Java 虚拟机实现可以为程序员或用户提供对堆初始大小的控制,并且,如果可以动态扩展或收缩堆,则可以控制最大和最小堆大小。

如果计算需要的堆多于自动存储管理系统所能提供的堆,则Java虚拟机将抛出OutOfMemoryError

方法区域和运行时常量池

方法区域存储每个类的结构,例如运行时常量池;字段和方法数据; 方法和构造器的代码,包括用于类,实例和接口初始化的特殊方法。

方法区域是在虚拟机启动时创建的。 尽管从逻辑上讲它是堆的一部分,但是可以或不能将其进行垃圾收集,而我们已经读到堆中的垃圾收集不是可选的; 这是强制性的。 方法区域可以是固定大小的,或者可以根据计算的需要进行扩展,如果不需要更大的方法区域,则可以缩小。 方法区域的内存不必是连续的。

如果方法区域中的内存不可用于满足分配请求,则 Java 虚拟机将抛出OutOfMemoryError

JVM 栈

每个 JVM 线程都有一个与该线程同时创建的私有栈。 栈存储帧。 框架用于存储数据和部分结果,并执行动态链接,方法的返回值和调度异常。

它保存局部变量和部分结果,并在方法调用和返回中起作用。 因为除了压入和弹出帧外,从不直接操纵此栈,所以可以对帧进行堆分配。 与堆类似,此栈的内存不必是连续的。

该规范允许栈的大小可以是固定的,也可以是动态的。 如果具有固定大小,则在创建该栈时可以独立选择每个栈的大小。

如果线程中的计算所需的 Java 虚拟机栈超出允许的范围,则 Java 虚拟机将引发StackOverflowError。如果可以动态扩展 Java 虚拟机栈,并尝试进行扩展,但是可能没有足够的内存来实现扩展,或者如果没有足够的内存可用于为新线程创建初始 Java 虚拟机栈,则 Java 虚拟机将抛出OutOfMemoryError

本机方法栈

本机方法栈称为 C 栈; 它支持本机方法(用 Java 编程语言以外的其他语言编写的方法),通常在创建每个线程时为每个线程分配。 无法加载本机方法并且自身不依赖于常规栈的 Java 虚拟机实现不需要提供本机方法栈。

本机方法栈的大小可以是固定的,也可以是动态的。

如果线程中的计算所需的本机方法堆栈超出允许的范围,则Java虚拟机将抛出StackOverflowError。如果可以动态扩展本机方法堆栈并尝试进行本机方法堆栈扩展,但可用内存不足,或者如果无法提供足够的内存来为新线程创建初始本机方法堆栈,则Java虚拟机将抛出OutOfMemoryError

PC 寄存器

每个 JVM 线程都有其自己的程序计数器(pc)寄存器。 在任何时候,每个 JVM 线程都在执行单个方法的代码,即该线程的当前方法。

由于 Java 应用程序可以包含一些本机代码(例如,使用本机库),因此本机和非本机方法有两种不同的方式。 如果该方法不是本机的(即 Java 代码),则 PC 寄存器包含当前正在执行的 JVM 指令的地址。 如果该方法是本地方法,则未定义 JVM 的 PC 寄存器的值。

Java 虚拟机的 pc 寄存器足够宽,可以在特定平台上保存返回地址或本机指针。

目前,这一切都与 JVM 内部的内存区域结构有关。 在接下来的文章中,我将提出一些想法,以使用此信息进行性能调整。

祝您学习愉快!

参考http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html

Java 内存管理 – 垃圾回收算法

原文: https://howtodoinjava.com/java/garbage-collection/revisiting-memory-management-and-garbage-collection-mechanisms-in-java/

我们都知道 Java 中垃圾收集器(GC)的工作职责。 但是只有极少数的尝试深入研究垃圾收集的工作原理。 您不是其中之一,这就是为什么您在这里。

在本 Java 内存管理教程中,我们将尝试了解 Java 垃圾回收的最新算法,并了解这些算法的发展。

Table of Contents

1\. Memory management in Java
2\. Reference counting mechanism
3\. Mark and sweep mechanism
4\. Stop and copy GC
5\. Generational stop and copy
6\. How to improve memory utilization in Java

1. Java 中的内存管理

Java 中的内存管理是垃圾收集器的职责。 这与 Java 之前的实践相反,在 Java 之前,程序员负责分配程序中的内存。

正式而言,垃圾收集器负责

  • 分配内存
  • 确保所有引用的对象都保留在内存中,并且
  • 恢复由执行代码中的引用无法访问的对象使用的内存。

在应用程序运行时,应用程序会创建许多对象,每个对象都有其生命周期。 在内存中,被其他对象引用的对象称为活动对象。 不再由任何活动对象引用的对象被视为死对象,并被称为垃圾。 查找和释放(也称为回收)这些对象使用的空间的过程称为垃圾回收

垃圾回收解决了许多但不是全部的内存分配问题。 例如,我们可以无限期地创建对象并继续引用它们,直到没有更多可用的内存为止(内存不足错误)。 垃圾收集是一项复杂的任务,需要花费时间和资源。 它在通常由称为堆的大型内存池分配的空间上运行。

垃圾收集的时机由垃圾收集器决定。 通常,整个堆或堆的一部分会在堆满或达到占用率的百分比时收集。

从 J2SE 5.0 开始,Java HotSpot 虚拟机包括四个垃圾收集器。 所有的收藏家都是世代相传的。 我们将在后面的部分中了解有关世代 GC 的更多信息。

阅读更多:垃圾收集算法(针对 Java 9 更新)

2. 引用计数机制

从初始版本开始,这已经是非常古老的 GC 机制。 在引用计数技术中,每个对象都有从其他对象和栈指向该对象的指针数。 每次引用新对象时,计数器都会增加一。 同样,当任何对象丢失其引用时,计数器将减一。 当计数达到“0”时,垃圾回收器可以取消分配对象。

引用计数算法的主要优势在分配给新对象时,每次写入内存的工作量很少。 但是,它具有数据周期严重问题。 这意味着当第一个对象被第二个对象引用,第二个对象被第一个对象引用(循环引用)时,计数永远不会为零,因此它们永远也不会被垃圾回收。

3. 标记和扫描机制

Mark and sweep GC

标记和扫描算法

标记清除算法是第一个要开发的垃圾收集算法,它能够回收循环数据结构。 在这种算法中,GC 将首先将某些对象标识为默认可达对象,这些对象通常是栈中的全局变量和局部变量。 有所谓的活动对象。

在下一步中,算法开始从这些活动对象中跟踪对象,并将它们也标记为活动对象。 继续执行此过程,直到检查所有对象并将其标记为活动。 完全跟踪后未标记为活动的对象被视为死对象。

使用标记扫描时,未引用的对象不会立即被回收。 取而代之的是,允许垃圾收集累积,直到所有可用内存都用完为止。 发生这种情况时,该程序的执行将暂时暂停(称为 Stop the world ),而标记清除算法将收集所有垃圾。 一旦回收了所有未引用的对象,就可以恢复程序的正常执行。

除了暂停应用程序一段时间外,此技术还需要经常对内存地址空间进行碎片整理,这是另一项开销。

4. 停止和复制 GC

像“标记和清除”一样,该算法还取决于识别活动对象并对其进行标记。 区别在于它处​​理活动对象的方式。

停止和复制技术将整个堆设计在两个半空间中。 一次只有一个半空间处于活动状态,而为新创建的对象分配的内存仅发生在单个半空间中,而另一个保持平静。

GC 运行时,它将开始标记当前半空间中的活动对象,完成后,它将所有活动对象复制到其他半空间中。 当前半空间中的所有其余对象都被视为已死,并已被垃圾回收。

与以前的方法一样,它具有的一些优点,就像它仅接触活动物体一样。 另外,不需要分段,因为在切换半空间时,会完成内存收缩

这种方法的主要缺点是需要将所需的内存大小增加一倍,因为在给定的时间点仅使用了一半。 除此之外,它还需要在切换半空间时停止世界。

5. 分代停止和复制

像“停止并复制”技术一样,它也将内存划分为半空间,但现在它们是三个半空间。 这些半空间在这里称为世代。 因此,该技术中的内存分为三代-新生代老年代永久代

大多数对象最初是在新生代中分配的。 老年代包含的对象在许多新生代集合中幸存下来,还有一些大型对象可以直接在老年代中分配。 永久生成包含 JVM 认为便于垃圾回收器管理的对象,例如描述类和方法的对象,以及类和方法本身。

当新生代填满时,将执行该一代的新生代垃圾回收(有时称为次要垃圾回收)。 当老年代或永久代填满时,通常会完成所谓的完整垃圾收集(有时称为主要收集)。 即,收集了所有的世代。

通常,首先使用专门为该代设计的垃圾收集算法来收集年轻代,因为它通常是识别年轻代中最有效的垃圾算法。 幸存于 GC 跟踪中的对象被推入更早的年代。 出于明显的原因,较老的一代被收集的频率较低,即他们在那里的原因是时间更长。 除上述情况外,如果发生碎片/压缩,则每一代都将单独压缩。

该技术的主要优点是在较年轻的一代中早期回收死对象,而无需每次都扫描整个内存以识别死对象。 较早的对象已经经历了一些 GC 周期,因此假定它们在系统中的存在时间更长,因此无需频繁扫描它们(不是每次都完美的情况,但大多数情况下应该如此)。

缺点仍然相同,即在 GC 运行全扫描时,需要对存储区进行碎片整理,并且需要停止世界(应用程序)。

6. 如何提高 Java 的内存利用率

  1. 不要分配过多的内存。 仅根据需要分配内存。 这特别适用于 Java 数组。
  2. 不要坚持引用。 一旦使用了对象且不再需要该对象,则为其分配null引用。
  3. 查找并解析内存泄漏
  4. 在每个发行版上执行系统性能分析来验证内存增加
  5. 不要依赖System.gc()运行垃圾收集

希望对垃圾收集机制有所帮助,该机制可为 Java 程序实现自动内存管理。 这可以帮助您回答 Java 内存管理面试问题

学习愉快!

Java 序列化教程

Java 序列化 – 执行正确的序列化

原文: https://howtodoinjava.com/java/serialization/a-mini-guide-for-implementing-serializable-interface-in-java/

Java 序列化允许将 Java 对象写入文件系统以进行永久存储,也可以将其写入网络以传输到其他应用程序。 Java 中的序列化是通过Serializable接口实现的。 Java Serializable接口保证可以序列化对象的能力。 此接口建议我们也使用serialVersioUID

现在,即使您在应用程序类中同时使用了两者,您是否知道哪怕现在会破坏您的设计? 让我们确定类中将来的更改,这些更改将是兼容的更改,而其他类别将证明不兼容的更改

Table of contents

1\. Java serialization incompatible changes
2\. Java serialization compatible changes
3\. serialVersionUID
4\. readObject() and writeObject() methods
5\. More serialization best practices
6\. Sample class following serialization best practices
7\. Serialization and deserialization example

1. Java 序列化不兼容的更改

对类的不兼容更改是指不能保持互操作性的那些更改。 下面给出了在演化类时可能发生的不兼容更改(考虑默认序列化或反序列化):

  1. 删除字段 – 如果在类中删除了字段,则写入的流将不包含其值。 当较早的类读取流时,该字段的值将设置为默认值,因为流中没有可用的值。 但是,此默认值可能会不利地损害早期版本履行其契约的能力。
  2. 将类上移或下移 – 不允许这样做,因为流中的数据显示顺序错误。
  3. 将非静态字段更改为静态或将非瞬态字段更改为瞬态 – 当依赖默认序列化时,此更改等效于从类中删除字段。 该版本的类不会将该数据写入流,因此该类的早期版本将无法读取该数据。 与删除字段时一样,早期版本的字段将被初始化为默认值,这可能导致类以意外方式失败。
  4. 更改原始字段的声明类型 – 该类的每个版本都使用其声明类型写入数据。 尝试读取该字段的早期版本的类将失败,因为流中的数据类型与该字段的类型不匹配。
  5. 更改writeObjectreadObject方法,使其不再写入或读取默认字段数据,或对其进行更改,以使其尝试写入或读取以前的版本时不进行读取。 默认字段数据必须一致地出现在流中或不出现在流中。
  6. 将类从Serializable更改为Externalizable,反之亦然是不兼容的更改,因为流将包含与可用类的实现不兼容的数据。
  7. 将类从非枚举类型更改为枚举类型,反之亦然,因为流将包含与可用类的实现不兼容的数据。
  8. 删除SerializableExternalizable是不兼容的更改,因为在编写时它将不再提供该类的较早版本所需的字段。
  9. 如果该行为会产生与该类的任何旧版本不兼容的对象,则将writeReplacereadResolve方法添加到类是不兼容的

2. Java 序列化兼容更改

  1. 添加字段 – 当重构的类具有流中未出现的字段时,对象中的该字段将被初始化为其类型的默认值。 如果需要特定于类的初始化,则该类可以提供一个readObject方法,该方法可以将字段初始化为非默认值。
  2. 添加类 – 流将包含流中每个对象的类型层次结构。 将流中的此层次结构与当前类进行比较可以检测到其他类。 由于流中没有用于初始化对象的信息,因此该类的字段将被初始化为默认值。
  3. 删除类 – 将流中的类层次结构与当前类的层次结构进行比较可以检测到某个类已被删除。 在这种情况下,从该流中读取与该类相对应的字段和对象。 原始字段被丢弃,但是创建了由已删除类引用的对象,因为可以在流中稍后引用它们。 当流被垃圾回收或重置时,它们将被垃圾回收。
  4. 添加writeObject/readObject方法 – 如果读取流的版本具有这些方法,则通常希望readObject读取通过默认序列化写入流中的所需数据。 在读取任何可选数据之前,应先调用defaultReadObject。 通常,writeObject方法将调用defaultWriteObject写入所需的数据,然后再写入可选数据。
  5. 删除writeObject/readObject方法 – 如果读取流的类没有这些方法,则默认情况下将序列化读取所需数据,而可选数据将被丢弃。
  6. 添加java.io.Serializable – 这等同于添加类型。 该类的流中将没有任何值,因此其字段将被初始化为默认值。 对子类化不可序列化类的支持要求该类的超类型具有无参构造器,并且该类本身将被初始化为默认值。 如果无参构造器不可用,则抛出InvalidClassException
  7. 更改对字段的访问 – 公共,包,保护和私有的访问修饰符对序列化为字段分配值的能力没有影响。
  8. 将字段从静态更改为非静态,或将瞬态更改为非瞬态 – 当依赖默认序列化来计算可序列化字段时,此更改等效于将字段添加到类中。 新字段将被写入流,但是较早的类将忽略该值,因为序列化不会为静态瞬态字段分配值。

3. serialVersionUID

serialVersionUIDSerializable类的通用版本标识符。 反序列化使用此数字来确保已加载的类与序列化的对象完全对应。 如果找不到匹配项,则抛出InvalidClassException

  1. 始终将其包含为字段,例如: private static final long serialVersionUID = 7526472295622776147L;,即使在类的第一个版本中也要包含此字段,以提醒其重要性。
  2. 请勿在以后的版本中更改此字段的值,除非您有意对类进行更改,以使其与旧的序列化对象不兼容。 如果需要,请遵循上述给定的准则。

4. readObjectwriteObject方法

  1. 反序列化必须视为任何构造器:在反序列化结束时验证对象状态 – 这意味着readObject几乎应始终在Serializable类中实现,以便执行此验证。
  2. 如果构造器为可变对象字段制作防御性副本,则必须读取对象。

5. 更多序列化最佳实践

  1. 使用 javadoc 的@serial标记表示可序列化字段。
  2. .ser扩展名通常用于表示序列化对象的文件。
  3. 没有静态或瞬态字段接受默认序列化。
  4. 除非必要,否则可扩展类不应是可序列化的。
  5. 内部类很少(如果有的话)实现Serializable
  6. 容器类通常应遵循Hashtable的样式,该样式通过存储键和值来实现Serializable,而不是大型哈希表数据结构。

6. 遵循序列化最佳实践的示例类

package staticTest;

import java.io.Serializable;
import java.text.StringCharacterIterator;
import java.util.*;
import java.io.*;

public final class UserDetails implements Serializable {

/**
* This constructor requires all fields
*
* @param aFirstName
* contains only letters, spaces, and apostrophes.
* @param aLastName
* contains only letters, spaces, and apostrophes.
* @param aAccountNumber
* is non-negative.
* @param aDateOpened
* has a non-negative number of milliseconds.
*/
public UserDetails(String aFirstName, String aLastName, int aAccountNumber,
						Date aDateOpened) 
{
  super();
  setFirstName(aFirstName);
  setLastName(aLastName);
  setAccountNumber(aAccountNumber);
  setDateOpened(aDateOpened);
  // there is no need here to call verifyUserDetails.
}

// The default constructor
public UserDetails() {
  this("FirstName", "LastName", 0, new Date(System.currentTimeMillis()));
}

public final String getFirstName() {
  return fFirstName;
}

public final String getLastName() {
  return fLastName;
}

public final int getAccountNumber() {
  return fAccountNumber;
}

/**
* Returns a defensive copy of the field so that no one can change this
* field.
*/
public final Date getDateOpened() {
  return new Date(fDateOpened.getTime());
}

/**
* Names must contain only letters, spaces, and apostrophes. Validate before
* setting field to new value.
*
* @throws IllegalArgumentException
* if the new value is not acceptable.
*/
public final void setFirstName(String aNewFirstName) {
  verifyNameProperty(aNewFirstName);
  fFirstName = aNewFirstName;
}

/**
* Names must contain only letters, spaces, and apostrophes. Validate before
* setting field to new value.
*
* @throws IllegalArgumentException
* if the new value is not acceptable.
*/
public final void setLastName(String aNewLastName) {
  verifyNameProperty(aNewLastName);
  fLastName = aNewLastName;
}

/**
* Validate before setting field to new value.
*
* @throws IllegalArgumentException
* if the new value is not acceptable.
*/
public final void setAccountNumber(int aNewAccountNumber) {
  validateAccountNumber(aNewAccountNumber);
  fAccountNumber = aNewAccountNumber;
}

public final void setDateOpened(Date aNewDate) {
  // make a defensive copy of the mutable date object
  Date newDate = new Date(aNewDate.getTime());
  validateAccountOpenDate(newDate);
  fDateOpened = newDate;
}

/**
* The client's first name.
*
* @serial
*/
private String fFirstName;

/**
* The client's last name.
*
* @serial
*/
private String fLastName;

/**
* The client's account number.
*
* @serial
*/
private int fAccountNumber;

/**
* The date the account was opened.
*
* @serial
*/
private Date fDateOpened;

/**
* Determines if a de-serialized file is compatible with this class.
* Included here as a reminder of its importance.
*/
private static final long serialVersionUID = 7526471155622776147L;

/**
* Verify that all fields of this object take permissible values
*
* @throws IllegalArgumentException
* if any field takes an unpermitted value.
*/
private void verifyUserDetails() {
  validateAccountNumber(fAccountNumber);
  verifyNameProperty(fFirstName);
  verifyNameProperty(fLastName);
  validateAccountOpenDate(fDateOpened);
}

/**
* Ensure names contain only letters, spaces, and apostrophes.
*
* @throws IllegalArgumentException
* if field takes an unpermitted value.
*/
private void verifyNameProperty(String aName) {
boolean nameHasContent = (aName != null) && (!aName.equals(""));
  if (!nameHasContent) {
    throw new IllegalArgumentException(
    "Names must be non-null and non-empty.");
  }

StringCharacterIterator iterator = new StringCharacterIterator(aName);
char character = iterator.current();
  while (character != StringCharacterIterator.DONE) {
    boolean isValidChar = (Character.isLetter(character)
    || Character.isSpaceChar(character) || character == ''');
    if (isValidChar) {
      // do nothing
    } else {
      String message = "Names can contain only letters, spaces, and apostrophes.";
      throw new IllegalArgumentException(message);
    }
    character = iterator.next();
  }
}

/**
* AccountNumber must be non-negative.
*
* @throws IllegalArgumentException
* if field takes an unpermitted value.
*/
private void validateAccountNumber(int aAccountNumber) {
  if (aAccountNumber < 0) {
    String message = "Account Number must be greater than or equal to 0.";
    throw new IllegalArgumentException(message);
  }
}

/**
* DateOpened must be after 1970.
*
* @throws IllegalArgumentException
* if field takes an unpermitted value.
*/
private void validateAccountOpenDate(Date aDateOpened) {
  if (aDateOpened.getTime() < 0) {
    throw new IllegalArgumentException(
      "Date Opened must be after 1970.");
  }
}

/**
* Always treat deserialization as a full-blown constructor, by validating
* the final state of the de-serialized object.
*/
private void readObject(ObjectInputStream aInputStream)
throws ClassNotFoundException, IOException {
  // always perform the default deserialization first
  aInputStream.defaultReadObject();

  // make defensive copy of the mutable Date field
  fDateOpened = new Date(fDateOpened.getTime());

  // ensure that object state has not been corrupted or tampered with
  // malicious code
  verifyUserDetails();
}

/**
* This is the default implementation of writeObject. Customise if
* necessary.
*/
private void writeObject(ObjectOutputStream aOutputStream)
throws IOException {
  // perform the default serialization for all non-transient, non-static
  // fields
  aOutputStream.defaultWriteObject();
}
}

现在让我们看看如何在 Java 中进行序列化和反序列化。

序列化和反序列化示例

package serializationTest;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.Date;
public class TestUserDetails {
  public static void main(String[] args) {
    // Create new UserDetails object
    UserDetails myDetails = new UserDetails("Lokesh", "Gupta", 102825,
    new Date(Calendar.getInstance().getTimeInMillis()));

    // Serialization code
    try {
      FileOutputStream fileOut = new FileOutputStream("userDetails.ser");
      ObjectOutputStream out = new ObjectOutputStream(fileOut);
      out.writeObject(myDetails);
      out.close();
      fileOut.close();
    } catch (IOException i) {
      i.printStackTrace();
    }

    // deserialization code
    @SuppressWarnings("unused")
    UserDetails deserializedUserDetails = null;
    try {
      FileInputStream fileIn = new FileInputStream("userDetails.ser");
      ObjectInputStream in = new ObjectInputStream(fileIn);
      deserializedUserDetails = (UserDetails) in.readObject();
      in.close();
      fileIn.close();

      // verify the object state
      System.out.println(deserializedUserDetails.getFirstName());
      System.out.println(deserializedUserDetails.getLastName());
      System.out.println(deserializedUserDetails.getAccountNumber());
      System.out.println(deserializedUserDetails.getDateOpened());
    } catch (IOException ioe) {
      ioe.printStackTrace();
    } catch (ClassNotFoundException cnfe) {
      cnfe.printStackTrace();
    }
  }
}
Output:

Lokesh
Gupta
102825
Wed Nov 21 15:06:34 GMT+05:30 2012

参考文献:

http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html

Java serialVersionUID – 如何生成serialVersionUID

原文: https://howtodoinjava.com/java/serialization/serialversionuid/

Java 序列化是将对象转换为字节流的过程,因此我们可以执行类似的操作,例如将其存储在磁盘上或通过网络发送。 反序列化是相反的过程 – 将字节流转换为内存中的对象。

在序列化期间,java 运行时将版本号与每个可序列化的类相关联。 称为serialVersionUID的数字,在反序列化期间用于验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。 如果接收者为对象加载的类serialVersionUID与相应发送者的类不同,则反序列化将导致InvalidClassException

1. Java serialVersionUID语法

可序列化的类可以通过声明一个名为“serialVersionUID”的字段来显式声明其自己的serialVersionUID,该字段必须是静态的,最终的且类型为long

private static final long serialVersionUID = 4L;

在这里,serialVersionUID表示类的版本,如果您对类的当前版本进行了修改,以使其不再与先前的版本向后兼容,则应该对它进行递增。

Serialization-deserialization-demo

2. Java 序列化和反序列化示例

让我们看一个如何将类序列化然后反序列化的示例。

package com.howtodoinjava.demo.serialization;

import java.io.*;
import java.util.logging.Logger;

public class DemoClass implements java.io.Serializable {

	private static final long serialVersionUID = 4L;			//Default serial version uid
	private static final String fileName = "DemoClassBytes.ser"; //Any random name
	private static final Logger logger = Logger.getLogger("");
	//Few data fields
	//Able to serialize
	private static String staticVariable;
	private int intVariable;

	//Not able to serialize
	transient private String transientVariable = "this is a transient instance field";
	private Thread threadClass;

	public static void main(String[] args) throws IOException, ClassNotFoundException 
	{
		//Serialization

	    DemoClass test = new DemoClass();
	    test.intVariable = 1;
	    staticVariable = "this is a static variable";
	    writeOut(test);
	    System.out.println("DemoClass to be saved: " + test);

	    //De-serialization

	    System.out.println("DemoClass deserialized: " + readIn());
	}

	private static Object readIn() throws IOException, ClassNotFoundException {
	    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(fileName)));
	    return ois.readObject();
	}

	private static void writeOut(java.io.Serializable obj) throws IOException {
	    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(fileName)));
	    oos.writeObject(obj);
	    oos.close();
	}

	@Override public String toString() {
	    return "DemoClass: final static fileName=" + fileName + ", final static logger=" + logger
	            + ", non-final static staticVariable=" + staticVariable + ", instance intVariable=" + intVariable
	            + ", transient instance transientVariable=" + transientVariable + ", non-serializable instance field threadClass:=" + threadClass;
	}
}

程序输出。

DemoClass to be saved: DemoClass: 
final static fileName=DemoClassBytes.ser, 
final static logger=java.util.logging.LogManager$RootLogger@1d99a4d, 
non-final static staticVariable=this is a static variable, 
instance intVariable=1, 
transient instance transientVariable=this is a transient instance field, 
non-serializable instance field threadClass:=null

//Execute readIn() function from a separate main() method 
//to get given below output correctly. It will flush out the static fields.

DemoClass deserialized: DemoClass: 
final static fileName=DemoClassBytes.ser, 
final static logger=java.util.logging.LogManager$RootLogger@cd2c3c, 
non-final static staticVariable=null, 
instance intVariable=1, 
transient instance transientVariable=null, 
non-serializable instance field threadClass:=null

如果可序列化的类未显式声明serialVersionUID,则序列化运行时将根据该类的各个方面计算该类的默认serialVersionUID值。

3. 如何生成serialVersionUID

Joshua Bloch 在《Effective Java》中说,自动生成的 UID 是基于类名称,已实现的接口以及所有公共和受保护成员生成的。 以任何方式更改其中任何一个都将更改serialVersionUID

但是,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类详细信息高度敏感,类详细信息可能会根据编译器的实现而有所不同,并且可以在不同的环境中产生不同的serialVersionUID。 这可能导致反序列化期间出现意外的InvalidClassException

因此,为了在不同的 Java 编译器实现中保证一致的serialVersionUID值,可序列化的类必须声明一个显式的serialVersionUID值。 强烈建议在可能的情况下,显式serialVersionUID声明在serialVersionUID中使用private修饰符,因为此类声明仅适用于立即声明的类。

另请注意,serialVersionUID字段不能用作继承成员。

基于我的短暂职业,我可以说长时间存储序列化数据(空间序列化)并不是很常见的用例。 使用序列化机制将数据临时写入(时间序列化)到例如高速缓存,或通过网络将其发送到另一个程序以利用信息,这是更为常见的。

在这种情况下,我们对保持向后兼容性不感兴趣。 我们只关心确保在网络上通信的代码库确实具有相同版本的相关类。 为了方便进行此类检查,我们必须保持serialVersionUID不变,并且不要对其进行更改。 另外,在网络上的两个应用程序上对类进行不兼容的更改时,请不要忘记更新它。

4. 没有serialVersionUID的 Java 类

这不是我们永远想要面对的情况。 但是,这是现实,有时甚至会发生(我应该很少说吗?)。 如果我们需要以不兼容的方式更改此类,但又想使用该类的旧版本维护序列化/反序列化特性,则可以使用 JDK 工具“serialver”。 该工具在旧类上生成serialVersionUID,并在新类上显式设置它。 不要忘记实现readObject()writeObject()方法,因为内置的反序列化机制(in.defaultReadObject())将拒绝从旧版本的数据中反序列化。

如果我们定义自己的readObject()函数,可以读取回旧数据。 此自定义代码应检查serialVersionUID,以便了解数据所在的版本并决定如何对其进行反序列化。 如果我们存储可以在您的代码的多个版本中保留的序列化数据,则此版本控制技术很有用。

阅读更多: Java 序列化兼容和不兼容的更改

5. Java serialVersionUID– 总结

  1. transientstatic字段在序列化中被忽略。 反序列化之后,transient字段和非最终静态字段将为null

    finalstatic字段仍具有值,因为它们是类数据的一部分。

  2. ObjectOutputStream.writeObject(obj)ObjectInputStream.readObject()用于序列化和反序列化。

  3. 在序列化期间,我们需要处理IOException; 在反序列化期间,我们需要处理IOExceptionClassNotFoundException。 因此,反序列化的类类型必须在类路径中。

  4. 允许使用未初始化的,不可序列化的,非瞬态的实例字段。

    添加“ private Thread th;”时,Serializable没有错误。 但是,“private Thread threadClass = new Thread();”将导致异常:

    Exception in thread "main" java.io.NotSerializableException: java.lang.Thread
    at java.io.ObjectOutputStream.writeObject0(Unknown Source)
    at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
    at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
    at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
    at java.io.ObjectOutputStream.writeObject0(Unknown Source)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at com.howtodoinjava.demo.serialization.DemoClass.writeOut(DemoClass.java:42)
    at com.howtodoinjava.demo.serialization.DemoClass.main(DemoClass.java:27)
    
    
  5. 序列化和反序列化可用于复制和克隆对象。 它比常规克隆慢,但是可以很容易地产生深拷贝

  6. 如果我需要序列化SerializableEmployee,但是其超类之一不是可序列化的,Employee类是否仍可以序列化和反序列化? 答案是肯定的,前提是不可序列化的超类具有无参构造器,在反序列化时调用该构造器以初始化该超类。

  7. 我们在修改实现java.io.Serializable的类时必须小心。 如果类不包含serialVersionUID字段,则其serialVersionUID将由编译器自动生成。

    不同的编译器或同一编译器的不同版本将生成潜在的不同值。

  8. serialVersionUID的计算不仅基于字段,而且还基于类的其他方面,例如Implement子句,构造器等。因此,最佳实践是显式声明serialVersionUID字段以保持向后兼容性。 如果我们需要实质性地修改可序列化的类,并希望它与以前的版本不兼容,则需要增加serialVersionUID以避免混合使用不同的版本。

学习愉快!

Java 静态 – 变量,方法,块,类和导入语句

原文: https://howtodoinjava.com/java/basics/java-static-keyword/

Java 中的static关键字可以应用于变量,方法,块,导入和内部类。 在本教程中,我们将通过示例学习在这些地方使用static关键字的效果。

Table of Contents

1\. Static Variable
2\. Static Method
3\. Static Import Statement
4\. Static Block
5\. Static Class
6\. Summary

1. 静态变量

要语句静态变量,请在变量语句中使用static关键字。 静态变量语法为:

ACCESS_MODIFER static DATA_TYPE VARNAME;

例如,以这种方式语句Integer类型的公共静态变量。

public static Integer staticVar;

关于静态变量的最重要的事情是它们属于类级别。 这意味着在运行时中只能有变量的一个副本

在类定义中定义静态变量时,类的每个实例都可以访问该单个副本。 类的单独实例不会像非静态变量一样拥有自己的本地副本。

public class JavaStaticExample 
{
	public static void main(String[] args) 
	{
		DataObject objOne = new DataObject();
		objOne.staticVar = 10;
		objOne.nonStaticVar = 20;

		DataObject objTwo = new DataObject();

		System.out.println(objTwo.staticVar);		//10
		System.out.println(objTwo.nonStaticVar);	//null

		DataObject.staticVar = 30;	//Direct Access

		System.out.println(objOne.staticVar);		//30
		System.out.println(objTwo.staticVar);		//30
	}
}

class DataObject {
	public static Integer staticVar;
	public Integer nonStaticVar;
}

Output:

10
null
30
30

注意我们如何将值更改为 30,并且两个对象现在都看到更新后的值 30。

您应该已经注意到的另一件事是,我们如何能够使用其类名DataObject.staticVar访问静态变量。 我们无需创建任何实例即可访问static变量。 它清楚地表明静态变量属于类范围

2. 静态方法

要语句静态方法,请在方法语句中使用static关键字。 静态方法语法为:

ACCESS_MODIFER static RETURN_TYPE METHOD_NAME;

例如,以这种方式声明了一个Integer类型的公共静态变量。

public static Integer staticVar;

public static Integer getStaticVar(){

	return staticVar;
}

几件事要记住。

  1. 您只能访问静态方法中的静态变量。 如果您尝试访问任何非静态变量,则会生成编译器错误,并显示消息“无法对非静态字段nonStaticVar进行静态引用”。
  2. 静态方法可以通过其类引用进行访问,而无需创建类的实例。 尽管您也可以使用实例引用进行访问,但是与通过类引用进行访问相比,它没有任何区别。
  3. 静态方法也属于类级别范围。
public class JavaStaticExample 
{
	public static void main(String[] args) 
	{
		DataObject.staticVar = 30;	//Direct Access

		Integer value1 = DataObject.getStaticVar();	//access with class reference

		DataObject objOne = new DataObject();
		Integer value2 = objOne.getStaticVar();		//access with instance reference

		System.out.println(value1);
		System.out.println(value2);
	}
}

class DataObject 
{
	public Integer nonStaticVar;
	public static Integer staticVar;	//static variable

	public static Integer getStaticVar(){
		return staticVar;
	}
}

Output:

30
30

3. 静态导入语句

普通的导入语句从包中导入类,因此可以在不引用包的情况下使用它们。 同样,静态导入语句从类导入静态成员,并允许在没有类引用的情况下使用它们。

静态导入语句也有两种形式:单静态导入静态按需导入。 单静态导入语句从类型中导入一个静态成员。 静态按需导入语句将导入类型的所有静态成员。

//Single-static-import declaration:

import static <<package name>>.<<type name>>.<<static member name>>;

//Static-import-on-demand declaration:

import static <<package name>>.<<type name>>.*;

例如,System.out

//Static import statement
import static java.lang.System.out;

public class JavaStaticExample 
{
	public static void main(String[] args) 
	{
		DataObject.staticVar = 30;	

		out.println(DataObject.staticVar); 	//Static import statement example
	}
}
class DataObject 
{
	public static Integer staticVar;	//static variable
}

Output:

30

阅读更多: Java 中的静态导入语句

4. 静态块

静态块是类初始化代码的一部分,并用static关键字包装。

public class Main {

    //static initializer
    static {
        System.out.println("Inside static initializer");
    }   
}

当将类加载到内存中时,将执行静态块。 一个类可以具有多个静态块,并且这些静态块将按照它们在类定义中出现的相同顺序执行。

import static java.lang.System.out;

class DataObject 
{
	public Integer nonStaticVar;
	public static Integer staticVar;	//static variable

	//It will be executed first
	static {
		staticVar = 40;
		//nonStaticVar = 20;	//Not possible to access non-static members
	}

	//It will be executed second
	static {
		out.println(staticVar);
	}
}

Output:

40

5. 静态类

在 Java 中,可以将静态类作为内部类。 就像其他静态成员一样,嵌套类也属于类范围,因此可以在没有外部类对象的情况下访问内部静态类。

public class JavaStaticExample 
{
	public static void main(String[] args) 
	{
		//Static inner class example
		System.out.println( DataObject.StaticInnerClas.innerStaticVar );
	}
}
class DataObject 
{
	public Integer nonStaticVar;
	public static Integer staticVar;	//static variable

	static class StaticInnerClas {
		Integer innerNonStaticVar = 60;	
		static Integer innerStaticVar = 70;		//static variable inside inner class
	}
}

请注意,静态内部类无法访问外部类的非静态成员。 它只能访问外部类的静态成员。

public class JavaStaticExample 
{
	public static void main(String[] args) 
	{
		//Static inner class example
		DataObject.StaticInnerClas.accessOuterClass();
	}
}
class DataObject 
{
	public Integer nonStaticVar;
	public static Integer staticVar;	//static variable

	static {
		staticVar = 40;
		//nonStaticVar = 20;	//Not possible to access non-static members
	}

	public static Integer getStaticVar(){
		return staticVar;
	}

	static class StaticInnerClas 
	{	
		public static void accessOuterClass()
		{
			System.out.println(DataObject.staticVar);		//static variable of outer class
			System.out.println(DataObject.getStaticVar());	//static method of outer class
		}
	}
}

Output:

40

6. 总结

让我们总结一下有关 Java 中static keyword使用情况的所有信息。

  1. 静态成员属于类。 无需创建类实例即可访问静态成员。
  2. 静态成员(变量和方法)只能在静态方法和静态块内访问。
  3. 非静态成员不能在静态方法,块和内部类中访问。
  4. 一个类可以有多个静态块,并且将按照它们在类定义中出现的顺序执行它们。
  5. 一个类只有在外部类内部语句为内部类时才能是静态的。
  6. 静态导入可用于从类中导入所有静态成员。 可以在没有任何类引用的情况下引用这些成员。

学习愉快!

参考文献:

类成员

嵌套类

Java 外部化示例 – 更有效的序列化

原文: https://howtodoinjava.com/java/serialization/java-externalizable-example/

默认的 Java 序列化效率不高。 如果您序列化了一个具有许多属性和属性的胖对象,则您不希望出于任何原因进行序列化(例如,它们始终被分配默认值),您将需要处理繁重的对象并通过网络发送更多字节,这在某些情况下可能会非常昂贵。

要解决此问题,您可以通过实现Externalizable接口并覆盖其方法writeExternal()readExternal()来编写自己的序列化逻辑。 通过实现这些方法,您将告诉 JVM 如何编码/解码对象。

出于示例目的,我创建了这个简单的类,我们将使用writeExternal()readExternal()方法进行序列化和反序列化。

class UserSettings implements Externalizable {

	//This is required
	public UserSettings(){

	}

	private String doNotStoreMe;

	private Integer fieldOne;
	private String fieldTwo;
	private boolean fieldThree;

	public String getDoNotStoreMe() {
		return doNotStoreMe;
	}

	public void setDoNotStoreMe(String doNotStoreMe) {
		this.doNotStoreMe = doNotStoreMe;
	}

	public Integer getFieldOne() {
		return fieldOne;
	}

	public void setFieldOne(Integer fieldOne) {
		this.fieldOne = fieldOne;
	}

	public String getFieldTwo() {
		return fieldTwo;
	}

	public void setFieldTwo(String fieldTwo) {
		this.fieldTwo = fieldTwo;
	}

	public boolean isFieldThree() {
		return fieldThree;
	}

	public void setFieldThree(boolean fieldThree) {
		this.fieldThree = fieldThree;
	}

	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		fieldOne = in.readInt();
		fieldTwo = in.readUTF();
		fieldThree = in.readBoolean();
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeInt(fieldOne);
		out.writeUTF(fieldTwo);
		out.writeBoolean(fieldThree);
	}

	@Override
	public String toString() {
		return "UserSettings [doNotStoreMe=" + doNotStoreMe + ", fieldOne="
				+ fieldOne + ", fieldTwo=" + fieldTwo + ", fieldThree="
				+ fieldThree + "]";
	}
}

ExternalizablewriteExternal()示例

writeExternal()方法用于提供序列化逻辑,即将类的字段写入字节。 在读回序列化的对象之后,您可以自由地仅存储要返回的那些字段,忽略其余字段。

public void writeExternal(ObjectOutput out) throws IOException {

	//We are not storing the field 'doNotStoreMe'

	out.writeInt(fieldOne);
	out.writeUTF(fieldTwo);
	out.writeBoolean(fieldThree);
}

ExternalizablereadExternal()示例

唯一需要记住的是 – readExternal()方法必须读取与writeExternal()写入的序列相同且类型相同的值。

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
	fieldOne = in.readInt();
	fieldTwo = in.readUTF();
	fieldThree = in.readBoolean();
}

完整的例子

现在,让我们编写代码进行序列化并读回字节,以验证 JVM 是否遵守契约。

public class ExternalizableExample 
{
	public static void main(String[] args) 
	{
		UserSettings settings = new UserSettings();
		settings.setDoNotStoreMe("Sensitive info");
		settings.setFieldOne(10000);
		settings.setFieldTwo("HowToDoInJava.com");
		settings.setFieldThree(false);

		//Before
		System.out.println(settings);
		storeUserSettings(settings);
		UserSettings loadedSettings = loadSettings();
		System.out.println(loadedSettings);
	}

	private static UserSettings loadSettings() {
        try {
            FileInputStream fis = new FileInputStream("object.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            UserSettings settings =  (UserSettings) ois.readObject();
            ois.close();
            return settings;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

	private static void storeUserSettings(UserSettings settings)
	{
		try {
            FileOutputStream fos = new FileOutputStream("object.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(settings);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
}

Output:

UserSettings [doNotStoreMe=Sensitive info, fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]
UserSettings [doNotStoreMe=null, fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]

显然,我们可以取回所需的字段,而忽略不需要的字段,这就是Externalizable接口的全部目的。

学习愉快!

Java 中ExternalizableSerializable之间的区别

原文: https://howtodoinjava.com/java/serialization/externalizable-vs-serializable/

知道ExternalizableSerializable之间的差异在两个方面都很重要,一个是可以作为面试问题询问,另外一个是您可以利用该知识做出更明智的决策,将序列化应用到您的应用中来追求性能提升。

1. ExternalizableSerializable之间的区别

让我们列出 Java 中ExternalizableSerializable接口之间的主要区别。

Externalizable Serializable
Serializable是标记接口,即不包含任何方法。 Externalizable接口包含实现类必须覆盖的两个方法writeExternal()readExternal()
Serializable接口将序列化的职责传递给 JVM 及其默认算法。 Externalizable向程序员提供串行化逻辑的控制-编​​写自定义逻辑。
通常,默认序列化易于实现,但具有较高的性能成本。 使用Externalizable完成的序列化为程序员增加了更多责任,但通常会带来更好的性能。
很难分析和修改类结构,因为任何更改都可能会破坏序列化。 由于可以完全控制序列化逻辑,因此分析和修改类结构更加容易。
默认序列化不调用任何类构造器。 使用Externalizable接口时,需要一个公共的无参数构造器。

请注意,Externalizable接口是Serializable的子接口,即Externalizable extends Serializable。 因此,如果任何类实现Externalizable接口并覆盖其writeExternal()readExternal()方法,则这些方法优先于 JVM 提供的默认序列化机制。

阅读更多:如何在 Java 中覆盖默认的序列化机制

2. 阅读有关ExternalizableSerializable的更多信息

请在与 Java 中的ExternalizableSerializable接口有关的评论部分中提出您的问题。

学习愉快!

将 Java 对象序列化为 XML – XMLEncoderXMLDecoder示例

原文: https://howtodoinjava.com/java/serialization/xmlencoder-and-xmldecoder-example/

默认的 Java 序列化将 Java 对象转换为字节以通过网络发送。 但是很多时候,您将需要更多的跨平台媒体来发送此信息,例如 XML,以便使用不同技术的应用程序也可以利用此序列化信息的优势。 在此示例中,我们将学习将 Java 对象序列化为 XML 文件,然后反序列化回原始 Java 对象。

为了演示用法,我创建了一个具有 3 个字段的类UserSettings,我们将序列化为 xml,然后将 xml 反序列化为 Java 对象

public class UserSettings {

	public UserSettings(){}

	private Integer fieldOne;
	private String fieldTwo;
	private boolean fieldThree;

	public Integer getFieldOne() {
		return fieldOne;
	}

	public void setFieldOne(Integer fieldOne) {
		this.fieldOne = fieldOne;
	}

	public String getFieldTwo() {
		return fieldTwo;
	}

	public void setFieldTwo(String fieldTwo) {
		this.fieldTwo = fieldTwo;
	}

	public boolean isFieldThree() {
		return fieldThree;
	}

	public void setFieldThree(boolean fieldThree) {
		this.fieldThree = fieldThree;
	}

	@Override
	public String toString() {
		return "UserSettings [fieldOne=" + fieldOne + ", fieldTwo=" + fieldTwo
				+ ", fieldThree=" + fieldThree + "]";
	}
}

使用XMLEncoder从 Java 对象序列化为 XML 文件

首先来看XMLEncoder类的示例,该类用于将 Java 对象序列化或编码为 XML 文件。

private static void serializeToXML (UserSettings settings) throws IOException
{
	FileOutputStream fos = new FileOutputStream("settings.xml");
	XMLEncoder encoder = new XMLEncoder(fos);
	encoder.setExceptionListener(new ExceptionListener() {
	        public void exceptionThrown(Exception e) {
	        	System.out.println("Exception! :"+e.toString());
	        }
	});
	encoder.writeObject(settings);
	encoder.close();
	fos.close();
}

XMLEncoder使用反射来找出它们包含哪些字段,但不是以二进制形式编写这些字段,而是以 XML 编写。 待编码的对象不需要是可序列化的,但是它们确实需要遵循 Java Beans 规范,例如

  1. 该对象具有一个公共的空(无参数)构造器。
  2. 该对象具有每个受保护/私有财产的公共获取器和设置器。

运行以上代码将生成具有以下内容的 XML 文件:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_92" class="java.beans.XMLDecoder">
	<object class="com.howtodoinjava.log4j2.examples.UserSettings">
		<void property="fieldOne">
			<int>10000</int>
		</void>
		<void property="fieldTwo">
			<string>HowToDoInJava.com</string>
		</void>
	</object>
</java>

请注意,如果要写入的对象的属性的默认值未更改,则XmlEncoder不会将其写出。 例如,在我们的示例中,第三个字段的类型为boolean,其默认值为false – 因此,它已从 XML 内容中省略。 这样可以灵活地更改类版本之间的默认值。

使用XMLDecoder反序列化从 XML 到 Java 对象

现在,我们来看一下XMLDecoder的示例,该示例已用于将 xml 文件反序列化为 java 对象。

private static UserSettings deserializeFromXML() throws IOException {
	FileInputStream fis = new FileInputStream("settings.xml");
	XMLDecoder decoder = new XMLDecoder(fis);
	UserSettings decodedSettings = (UserSettings) decoder.readObject();
	decoder.close();
	fis.close();
	return decodedSettings;
}

XMLEncoderXMLDecoder比序列化框架宽容得多。 解码时,如果属性更改了类型,或者属性被删除/添加/移动/重命名,则解码将“尽其所能”进行解码,同时跳过无法解码的属性。

完整的例子

让我们看一下使用XMLEncoderXMLDecoder的整个示例。

import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class XMLEncoderDecoderExample 
{
	public static void main(String[] args) throws IOException 
	{
		UserSettings settings = new UserSettings();
		settings.setFieldOne(10000);
		settings.setFieldTwo("HowToDoInJava.com");
		settings.setFieldThree(false);

		//Before
		System.out.println(settings);
		serializeToXML ( settings );
		UserSettings loadedSettings = deserializeFromXML();
		//After
		System.out.println(loadedSettings);
	}

	private static void serializeToXML (UserSettings settings) throws IOException
	{
		FileOutputStream fos = new FileOutputStream("settings.xml");
		XMLEncoder encoder = new XMLEncoder(fos);
		encoder.setExceptionListener(new ExceptionListener() {
		        public void exceptionThrown(Exception e) {
		        	System.out.println("Exception! :"+e.toString());
		        }
		});
		encoder.writeObject(settings);
		encoder.close();
		fos.close();
	}

	private static UserSettings deserializeFromXML() throws IOException {
		FileInputStream fis = new FileInputStream("settings.xml");
		XMLDecoder decoder = new XMLDecoder(fis);
		UserSettings decodedSettings = (UserSettings) decoder.readObject();
		decoder.close();
		fis.close();
		return decodedSettings;
    }
}

Output:

UserSettings [fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]
UserSettings [fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]

将我的问题放在评论部分。

学习愉快!

Java 中反序列化过程如何发生?

原文: https://howtodoinjava.com/java/serialization/how-deserialization-process-happen-in-java/

在我上一篇与“在 Java 中实现Serializable接口的指南”相关的文章中,Bitoo 先生问了一个好问题:“在反序列化时,JVM 如何创建 JVM 没有调用构造器的对象?”。 我曾想在评论中的同一帖子中回复他,但又过一会儿,我想到了这个非常有趣的话题,需要另外撰写详细的文章,并与大家进行讨论。 因此,在这里,我将以对这一主题的有限知识开始讨论,并鼓励大家提出您的想法/疑问,以便使我们所有人都清楚这个主题。 我从这里开始。

我们已经介绍了许多与 Java 中的序列化相关的内容,以及与 Java 中的反序列化相关的一些内容。 我将不再重复同一件事,而是直接进入主要讨论主题,即反序列化如何在 Java 中工作?

反序列化是将先前序列化的对象重建回其原始形式(即对象实例)的过程。 反序列化过程的输入是字节流,它是从网络的另一端获取的,或者我们只是从文件系统/数据库中读取字节流。 立即出现一个问题,在此字节流中写入了什么?

阅读更多信息用于实现Serializable接口的迷你指南

确切地说,字节流(或称序列化数据)具有有关由序列化过程序列化的实例的所有信息。 该信息包括类的元数据,实例字段的类型信息以及实例字段的值。 将对象重新构造回新的对象实例时,也需要相同的信息。 在反序列化对象时,JVM 从字节流中读取其类元数据,该字节流指定对象的类是实现Serializable还是Externalizable接口。

请注意,要无缝进行反序列化,执行反序列化的 JVM 中必须存在要反序列化对象的类的字节码。 否则,将抛出ClassNotFoundException。 这不是很明显吗?

如果实例实现了Serializable接口,则将创建类的实例而无需调用其任何构造器。 真? 那么如果不调用构造器,该如何创建对象呢?

让我们看一个简单的电子程序的字节码:

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

Byte code:

public class SimpleProgram extends java.lang.Object{
public SimpleProgram();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	ldc	#3; //String Hello World!
   5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:	return
}

上面的字节码看起来很真实,不是吗? 在第一行中,我们将把“局部变量表”中的值压入栈。 在这种情况下,我们实际上只是将隐式引用推到“this”,因此它并不是最令人兴奋的说明。 第二条指令是主要内容。 实际上,它调用了最高级类的构造器,在上述情况下,它是Object.java。 一旦调用了最高级类的构造器(即本例中的对象),其余的代码就会执行用代码编写的特定指令。

符合以上概念,即最高级的构造器,我们在反序列化中也有类似的概念。 在反序列化过程中,要求实例的所有父类均应可序列化; 如果层次结构中的任何超类都不可序列化,则它必须具有默认构造器。 现在有道理了。 因此,在反序列化时,将首先搜索超类,直到找到任何不可序列化的类。 如果所有超类都可以序列化,那么 JVM 最终会到达Object类本身,并首先创建Object类的实例。 如果在搜索超类之间,发现任何无法序列化的类,则将使用其默认构造器在内存中分配实例。

如果要在不可序列化的情况下反序列化实例的任何超类,并且也没有默认构造函数,那么JVM会抛出“NotSerializableException”。

另外,在继续进行对象重建之前,JVM 会检查字节流中提到的serialVersionUID是否与该对象类的serialVersionUID相匹配。 如果不匹配,则抛出“InvalidClassException”。

阅读更多内容Java 中的SerialVersionUID以及相关的快速知识

因此,到目前为止,我们使用超类的默认构造器之一将实例保存在内存中。 请注意,此后将不会为任何类调用构造器。 执行超类构造器后,JVM 读取字节流并使用实例的元数据来设置实例的类型信息和其他元信息。

创建空白实例后,JVM 首先设置其静态字段,然后调用默认的readObject()方法(如果未覆盖),否则会调用被覆盖的方法,该方法负责将值从字节流设置为空白实例。

阅读更多信息readObject()writeObject()的示例代码

readObject()方法完成后,反序列化过程完成,您可以使用新的反序列化实例了。

请在评论区域中发表您对该主题的想法/看法/查询。 这些绝对值得欢迎。

祝您学习愉快!

使用readObjectwriteObject的 Java 自定义序列化

原文: https://howtodoinjava.com/java/serialization/custom-serialization-readobject-writeobject/

在某些情况下,您可能需要在 Java 中使用自定义序列化。 例如,您有遗留的 Java 类,由于任何原因都不愿意对其进行修改。 也可能存在一些设计约束。 甚至简单地说,该类将在将来的发行版中进行更改,这可能会破坏先前序列化对象的反序列化

Table of Contents

1\. Custom Serialization
2\. Default Serialization with Added Validation
3\. Summary

1. Java 自定义序列化

在大多数情况下,当自定义 Java 序列化时,您将按顺序逐一写入字段。 它最常用的方法将覆盖默认的 Java 序列化进程。

假设我们有一个User对象,我们想自定义它的序列化过程。

public class User implements Serializable {

	private static final long serialVersionUID = 7829136421241571165L;

	private String firstName;
	private String lastName;
	private int accountNumber;
	private Date dateOpened;

	public User(String firstName, String lastName, int accountNumber, Date dateOpened) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.accountNumber = accountNumber;
		this.dateOpened = dateOpened;
	}

	public User() {
		super();
	}

	public final String getFirstName() {
		return firstName;
	}

	public final String getLastName() {
		return lastName;
	}

	public final int getAccountNumber() {
		return accountNumber;
	}

	public final Date getDateOpened() {
		return new Date(dateOpened.getTime());
	}

	public final void setFirstName(String aNewFirstName) {
		firstName = aNewFirstName;
	}

	public final void setLastName(String aNewLastName) {
		lastName = aNewLastName;
	}

	public final void setAccountNumber(int aNewAccountNumber) {
		accountNumber = aNewAccountNumber;
	}

	public final void setDateOpened(Date aNewDate) {
		Date newDate = new Date(aNewDate.getTime());
		dateOpened = newDate;
	}
}

1.1 readObject()writeObject()方法

要自定义序列化和反序列化,请在此类中定义readObject()writeObject()方法。

  • writeObject()方法内部,使用ObjectOutputStream提供的writeXXX方法编写类属性。
  • readObject()方法内部,使用ObjectInputStream提供的readXXX方法读取类属性。
  • 请注意,读写方法中类属性的序列必须与相同。
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {

	private static final long serialVersionUID = 7829136421241571165L;

	private String firstName;
	private String lastName;
	private int accountNumber;
	private Date dateOpened;

	public User(String firstName, String lastName, int accountNumber, Date dateOpened) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.accountNumber = accountNumber;
		this.dateOpened = dateOpened;
	}

	public User() {
		super();
	}

	//Setters and Getters

	private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException 
	{		
		firstName = aInputStream.readUTF();
		lastName = aInputStream.readUTF();
		accountNumber = aInputStream.readInt();
		dateOpened = new Date(aInputStream.readLong());
	}

	private void writeObject(ObjectOutputStream aOutputStream) throws IOException 
	{
		aOutputStream.writeUTF(firstName);
		aOutputStream.writeUTF(lastName);
		aOutputStream.writeInt(accountNumber);
		aOutputStream.writeLong(dateOpened.getTime());
	}
}

现在,我们来测试代码。

1.2 测试自定义序列化

package com.howtodoinjava.io.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.Date;

public class TestCustomSerialization 
{
	public static void main(String[] args) 
	{
		// Create new User object
		User myDetails = new User("Lokesh", "Gupta", 102825, new Date(Calendar.getInstance().getTimeInMillis()));

		// Serialization code
		try 
		{
			FileOutputStream fileOut = new FileOutputStream("User.ser");
			ObjectOutputStream out = new ObjectOutputStream(fileOut);
			out.writeObject(myDetails);
			out.close();
			fileOut.close();
		} 
		catch (IOException i) 
		{
			i.printStackTrace();
		}

		// De-serialization code
		User deserializedUser = null;
		try 
		{
			FileInputStream fileIn = new FileInputStream("User.ser");
			ObjectInputStream in = new ObjectInputStream(fileIn);
			deserializedUser = (User) in.readObject();
			in.close();
			fileIn.close();

			// verify the object state
			System.out.println(deserializedUser.getFirstName());
			System.out.println(deserializedUser.getLastName());
			System.out.println(deserializedUser.getAccountNumber());
			System.out.println(deserializedUser.getDateOpened());
		} 
		catch (IOException ioe) 
		{
			ioe.printStackTrace();
		} 
		catch (ClassNotFoundException cnfe) 
		{
			cnfe.printStackTrace();
		}
	}
}

//输出

Lokesh
Gupta
102825
Wed May 24 13:05:25 IST 2017

2. 覆盖默认序列化以添加验证

有时,您可能只需要执行任何特定的验证,或者在反序列化的对象上运行一些业务规则,而不会影响默认的 Java 序列化机制。 当您决定使用readObject()writeObject()方法时,这也是可能的。

在此用例中,可以在readObject()writeObject()方法中使用defaultReadObject()defaultWriteObject() - 启用默认的序列化和反序列化。 然后,您可以将您的自定义验证或业务规则插入读/写方法中。
这样,在默认的序列化和反序列化过程发生后,JVM 将立即自动调用验证方法。

public class User implements Serializable {

	//class attributes, constructors, setters and getters as shown above

	/**
	 * Always treat de-serialization as a full-blown constructor, by validating the final state of the de-serialized object.
	 */
	private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException 
	{
		// perform the default de-serialization first
		aInputStream.defaultReadObject();

		// make defensive copy of the mutable Date field
		dateOpened = new Date(dateOpened.getTime());

		// ensure that object state has not been corrupted or tampered with malicious code
		//validateUserInfo();
	}

	/**
	 * This is the default implementation of writeObject. Customize as necessary.
	 */
	private void writeObject(ObjectOutputStream aOutputStream) throws IOException {

		//ensure that object is in desired state. Possibly run any business rules if applicable.
		//checkUserInfo();

		// perform the default serialization for all non-transient, non-static fields
		aOutputStream.defaultWriteObject();
	}
}

再次测试代码,您将看到以下输出:

Lokesh
Gupta
102825
Wed May 24 13:10:18 IST 2017

3. 总结

如我们所见,自定义序列化在 Java 中非常容易,并且涉及非常简单的设计,即实现readObject()writeObject()方法; 并添加任何其他逻辑以支持应用程序业务逻辑。

尽管在大多数情况下,默认的序列化/反序列化就足够了; 在需要时仍应在 Java 应用程序中使用自定义序列化。

将我的问题放在评论部分。

学习愉快!

使用内存序列化的 Java 深层复制

原文: https://howtodoinjava.com/java/serialization/how-to-do-deep-cloning-using-in-memory-serialization-in-java/

众所周知,深度克隆(和某些性能开销)或深层复制的最简单方法是序列化。 Java 序列化涉及将对象序列化为字节,然后再次从字节序列化为对象。

我建议您在仅仅需要它且不需要持久保存对象以备将来使用的情况下,使用内存深度克隆。 在这个 Java 深度克隆示例中,我将提出一种内存中深度克隆的机制供您参考。

请记住,对于单例模式来说,深克隆是邪恶的。 它使拥有多个单例类实例成为可能。

阅读更多: Java 对象克隆指南

1. Java 深层复制示例

在演示程序中,我创建了一个名为SerializableClass的演示类。 这具有三个变量,即firstNamelastNamepermissions。 我将向此类添加deepCopy()实例级方法。 每当在SerializableClass实例上调用时,它将返回该实例的精确克隆/深层副本。

对于深度克隆,我们必须先进行序列化,然后进行反序列化。 对于序列化,我使用了 ByteArrayOutputStreamObjectOutputStream。 对于反序列化,我使用了ByteArrayInputStreamObjectInputStream

package serializationTest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class SerializableClass implements Serializable
{

	private static final long serialVersionUID = 1L;

	private String firstName = null;
	private String lastName = null;

	@SuppressWarnings("serial")
	private List permissions = new ArrayList()
											{
												{
													add("ADMIN");
													add("USER");
												}
											};

	public SerializableClass(final String fName, final String lName)
	{
		//validateNameParts(fName);
		//validateNameParts(lName);
		this.firstName = fName;
		this.lastName = lName;
	}

	public SerializableClass deepCopy() throws Exception
    {
    	//Serialization of object
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);
        out.writeObject(this);

        //De-serialization of object
        ByteArrayInputStream bis = new   ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream in = new ObjectInputStream(bis);
        SerializableClass copied = (SerializableClass) in.readObject();

        //Verify that object is not corrupt

        //validateNameParts(fName);
        //validateNameParts(lName);

        return copied;
    }

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return new StringBuilder().append(getFirstName()+",")
								  .append(getLastName()+",")
								  .append(permissions)
								  .toString();
	}
}

2. 演示

让我们测试该类并创建实例的深层副本,以验证其是否按预期工作。

package serializationTest;

public class ImMemoryTest 
{

    public static void main(String[] args) throws Exception 
    {
        //Create instance of serializable object
    	SerializableClass myClass = new SerializableClass("Lokesh","Gupta");

    	//Verify the content
    	System.out.println(myClass);

    	//Now create a deep copy of it
    	SerializableClass deepCopiedInstance = myClass.deepCopy();

       //Again verify the content
    	System.out.println(deepCopiedInstance);
    }
}

程序输出。

Lokesh,Gupta,[ADMIN, USER]
Lokesh,Gupta,[ADMIN, USER]

在考虑应用程序中的内存深层复制对象之前,您可能需要阅读有关序列化指南的信息,这将防止将来的设计中断。

学习愉快!

阅读更多:

Java 中的浅表副本与深表副本

字符串方法

Java String.concat()方法示例

原文: https://howtodoinjava.com/java/string/java-string-concat-method-example/

Java String.concat()方法将方法参数字符串连接到字符串对象的末尾。

1. String.concat(String str)方法

在内部,Java 用字符串对象和参数字符串的组合长度创建一个新的字符数组,并将所有内容从这两个字符串复制到此新数组中。 最后,将合并器字符数组转换为字符串对象。

public String concat(String str) 
{
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

2. Java String.concat示例

Java 程序将连接两个字符串以产生组合的字符串。 我们可以传递空字符串作为方法参数。 在这种情况下,方法将返回原始字符串。

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

程序输出。

Hello world

3. 不允许为null

不允许使用null参数。 它将抛出NullPointerException

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

程序输出:

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.concat(String.java:2014)
	at com.StringExample.main(StringExample.java:9)

学习愉快!

参考文献:

Java String文档

Java String.hashCode()方法示例

原文: https://howtodoinjava.com/java/string/string-hashcode-method/

Java String.hashCode()方法返回String的哈希码。 哈希码值用于基于哈希的集合中,例如HashMapHashTable等。在覆盖equals()方法的每个类中都必须覆盖此方法。

阅读更多: hashCode()equals()方法之间的协定

1. String.hashCode()方法

String对象的哈希码计算为:

s[0] * 31^(n-1) + s[1] * 31^(n-2) + … + s[n-1]

其中:

s[i] – 字符串的第i个字符
n – 字符串的长度,
^ – 表示幂

String.hashCode()覆盖Object.hashCode()。 它以整数值的形式返回哈希码。

2. Java String.hashCode()示例

Java 程序,用于计算字符串的哈希码。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.hashCode() );

        System.out.println( "hello world".hashCode() );
    }
}

程序输出。

1894145264
1794106052

参考:

Java String文档

Java String.contains()方法示例

原文: https://howtodoinjava.com/java/string/java-string-contains-example/

Java String.contains()搜索给定字符串中的子字符串。 如果在该字符串中找到子字符串,则返回true,否则返回false

1. String.contains()方法

使用String.contains()查找给定字符串中是否存在子字符串。 请记住,此方法区分区分大小写

1.1 方法语法

该方法在内部利用indexOf()方法来检查子字符串的索引。 如果存在子字符串,则索引将始终大于'0'

/**
* @param substring - substring to be searched in this string
* 
* @return - true if substring is found,
* 			false if substring is not found
*/
public boolean contains(CharSequence substring) {
    return indexOf(s.toString()) > -1;
}

1.2 null不是有效的方法参数

String.contains()方法不接受null参数。 如果方法参数为null,它将抛出NullPointerException

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.contains(String.java:2120)
	at com.StringExample.main(StringExample.java:7)

2. String.contains()示例

查找给定字符串中是否存在查找子字符串的 Java 程序。

public class StringExample 
{
    public static void main(String[] args) 
    {
        System.out.println("Hello World".contains("Hello"));	//true

        System.out.println("Hello World".contains("World"));	//true

        System.out.println("Hello World".contains("WORLD"));	//false - case-sensitive

        System.out.println("Hello World".contains("Java"));		//false
    }
}

程序输出。

true
true
false
false

在此示例中,我们学会了使用 Java String.contains()方法来检查给定字符串中是否存在子字符串。

参考文献:

Java 字符串指南
String Java 文档

Java 中的静态导入语句

原文: https://howtodoinjava.com/java/basics/static-import-declarations-in-java/

普通的导入语句从包中导入类,因此可以在不引用包的情况下使用它们。 同样,静态导入语句从类中导入静态成员,并允许在不使用类引用的情况下使用它们。

静态导入语句也有两种形式:单静态导入和按需静态导入。 单静态导入语句从类型中导入一个静态成员。 静态按需导入语句将导入类型的所有静态成员。 静态导入语句的一般语法如下:

//Single-static-import declaration:

import static <<package name>>.<<type name>>.<<static member name>>;

//Static-import-on-demand declaration:

import static <<package name>>.<<type name>>.*;

静态导入示例

例如,您记得使用System.out.println()方法在标准输出中打印消息。 Systemjava.lang包中的类,具有一个名为out的静态变量。 当您使用System.out时,您是在System类之外引用该静态变量。 您可以使用静态导入语句从System类中导入out静态变量,如下所示:

import static java.lang.System.out;

您的代码现在可以在程序中使用名称out来表示System.out。 编译器将使用静态导入语句将名称out解析为System.out

public class StaticImportTest {
        public static void main(String[] args) {
                out.println("Hello static import!");
        }
}

静态导入规则

以下是有关静态导入语句的一些重要规则。

1)如果导入了两个具有相同简单名称的静态成员,一个使用单静态导入语句,另一个使用静态按需导入语句,则使用单静态导入语句导入的成员优先。

假设有两个类别,package1.Class1package2.Class2。 这两个类都有一个称为methodA的静态方法。 以下代码将使用package1.Class1.methodA()方法,因为它是使用单静态导入语句导入的:

import static package1.Class1.methodA; // Imports Class1.methodA() method
import static package2.Class2.*;  // Imports Class2.methodA() method too

public class Test {
        public static void main(String[] args) {
                methodA();   // Class1.methodA() will be called
        }
}

2)不允许使用单静态导入语句导入具有相同简单名称的两个静态成员。 以下静态导入语句会产生错误,因为它们都使用相同的简单名称methodA导入静态成员:

import static package1.Class1.methodA;
import static package1.Class2.methodA; // An error

3)如果使用单静态导入语句导入静态成员,并且在同一类中存在具有相同名称的静态成员,则使用该类中的静态成员。

// A.java
package package1;

public class A {
        public static void test() {
                System.out.println("package1.A.test()");
        }
}

// Test.java
package package2;

import static package1.A.test;

public class Test {
        public static void main(String[] args) {
                test(); // Will use package2.Test.test() method, not package1.A.test() method
        }

        public static void test() {
                System.out.println("package2.Test.test()");
        }
}

Output:

package2.Test.test()

静态导入似乎可以帮助您使用静态成员的简单名称来简化程序的编写和读取。 有时,静态导入可能会在程序中引入一些细微的错误,这些错误可能很难调试。 建议您完全不使用静态导入,或仅在极少数情况下使用静态导入。

祝您学习愉快!

Java String.compareTo()方法示例

原文: https://howtodoinjava.com/java/string/java-string-compareto-method/

Java 字符串compareTo()方法按字典顺序比较两个字符串。 我们可以考虑基于字典的比较。

1. 字符串比较

如果字符串'str1'在字典中的另一个字符串'str2'之前,则在字符串比较中str2大于'str1'

string1 > string2string1在字典中出现string2之后。

string1 < string2 - string1在字典中的string2之前。

string1 = string2 - string1string2相等。

2. String.compareTo()方法

compareTo()方法中,按字典顺序(字典顺序)比较两个字符串。 第一个字符串是在其上调用方法的String对象本身。 第二个字符串是方法的参数。

此方法根据字符串中每个字符的 Unicode 值进行字符串比较。

2.1 方法返回类型

此方法的结果为整数值,其中:

  1. 正整数 – 表示按字典顺序字符串对象在变量字符串之后。
  2. 负整数 – 表示按字典顺序的字符串对象在变量字符串之前。
  3. – 表示两个字符串相等。

2.2 方法语法

Java compareTo()方法实现。

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

3. Java String.compareTo()示例

了解如何在 Java 字符串上调用compareTo()方法。

public class Main 
{
    public static void main(String[] args) 
    {
        System.out.println( "apple".compareTo("banana") );  //-1 - apple comes before banana
        System.out.println( "apple".compareTo("cherry") );  //-2 - apple comes before cherry
        System.out.println( "cherry".compareTo("banana") ); //1  - cherry comes after banana
        System.out.println( "cherry".compareTo("cherry") ); //0  - Both strings are equal
    }
}

4. Java String.compareToIgnoreCase()示例

Java 程序以不区分大小写的方式比较两个字符串。 请注意,compareTo()compareToIgnoreCase()方法的行为方式相同,只是后者不区分大小写

在给定的示例中,请注意前两个语句中的字符串比较,如何更改字符串的大小写可能如何更改结果和顺序。

再次注意,在将每个字符转换为 unicode 值之后,将对两个字符串进行逐字符比较。

public class Main 
{
    public static void main(String[] args) 
    {
        System.out.println( "apple".compareTo("BANANA") );                     //31
        System.out.println( "apple".compareToIgnoreCase("banana") );            //-1

        System.out.println( "cherry".compareTo("cherry") );                     //0
        System.out.println( "cherry".compareToIgnoreCase("CHERRY") );           //0
    }
}

学习愉快!

参考:String Java 文档

Java String.compareToIgnoreCase()方法示例

原文: https://howtodoinjava.com/java/string/string-comparetoignorecase-example/

Java 字符串compareToIgnoreCase()方法按字典顺序比较两个字符串,忽略大小写。 该方法与String.compareTo()方法相同,但compareTo()方法区分大小写。

1. String.compareToIgnoreCase()方法

compareToIgnoreCase()方法中,按照字典顺序(字典顺序)忽略大小写比较两个字符串。 第一个字符串是在其上调用方法的String对象本身。 第二个字符串是方法的参数。

此方法根据字符串中每个字符的 Unicode 值进行字符串比较。

1.1 方法返回类型

此方法的结果为整数值,其中:

  1. 正整数 – 表示按字典顺序字符串对象在变量字符串之后。
  2. 负整数 – 表示按字典顺序的字符串对象在变量字符串之前。
  3. – 表示两个字符串相等。

1.2 方法实现

此方法使用CaseInsensitiveComparator类,它是字符串类的静态内部类。 字符串比较是通过compare()方法完成的。

public int compare(String s1, String s2) {
    int n1 = s1.length();
    int n2 = s2.length();
    int min = Math.min(n1, n2);
    for (int i = 0; i < min; i++) {
        char c1 = s1.charAt(i);
        char c2 = s2.charAt(i);
        if (c1 != c2) {
            c1 = Character.toUpperCase(c1);
            c2 = Character.toUpperCase(c2);
            if (c1 != c2) {
                c1 = Character.toLowerCase(c1);
                c2 = Character.toLowerCase(c2);
                if (c1 != c2) {
                    // No overflow because of numeric promotion
                    return c1 - c2;
                }
            }
        }
    }
    return n1 - n2;
}

2. Java String.compareToIgnoreCase()示例

Java 程序以不区分大小写的方式比较两个字符串。 请注意,compareTo()compareToIgnoreCase()方法的行为方式相同,只是后者不区分大小写

public class Main 
{
    public static void main(String[] args) 
    {
        System.out.println( "apple".compareTo("BANANA") );                     //31
        System.out.println( "apple".compareToIgnoreCase("banana") );            //-1

        System.out.println( "cherry".compareTo("cherry") );                     //0
        System.out.println( "cherry".compareToIgnoreCase("CHERRY") );           //0
    }
}

3. compareToIgnoreCase()equalsIgnoreCase()

了解compareToIgnoreCase()equalsIgnoreCase()方法之间的主要区别。

  • compareToIgnoreCase()在字典上进行比较(字典顺序)。
    equalsIgnoreCase()检查两个字符串是否相等的字符串相等性。 虽然两者都不区分大小写。
  • compareToIgnoreCase()的返回类型是整数类型,它表示一个字符串大于,小于或等于另一个字符串。
    equalsIgnoreCase()返回类型是布尔值,这意味着两个字符串相等或不相等。

4. Java String.compareTo()示例

Java 程序使用String.compareTo()方法比较字符串。

public class Main 
{
    public static void main(String[] args) 
    {
        System.out.println( "apple".compareTo("banana") );  //-1 - apple comes before banana
        System.out.println( "apple".compareTo("cherry") );  //-2 - apple comes before cherry
        System.out.println( "cherry".compareTo("banana") ); //1  - cherry comes after banana
        System.out.println( "cherry".compareTo("cherry") ); //0  - Both strings are equal
    }
}

学习愉快!

参考:String Java 文档

Java String.equals()方法 – 字符串比较

原文: https://howtodoinjava.com/java/string/string-equals-method/

Java 字符串equals()方法用于将字符串与方法参数对象进行比较。

1. Java String.equals()方法

/**
* @param  anObject - The object to compare
* @return true -  if the non-null argument object represents the same sequence of characters to this string
*         false - in all other cases       
*/
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

  1. 字符串类将覆盖Object类中的equals()方法。 相等性以区分大小写的方式完成。
  2. 使用equals()方法检查字符串内容的相等性。
  3. 不要使用'=='运算符。 它检查对象引用,这在大多数情况下是不希望的。
  4. 允许将null传递给方法。 它将返回false
  5. 使用equalsIgnoreCase()方法以不区分大小写的方式检查相等的字符串。

2. Java String.equals()方法示例

检查两个字符串是否相等的 Java 程序(区分大小写)。

public class Main 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";
        String authorName = "Lokesh gupta";

        //1 - check two strings for same character sequence
        boolean isEqualString = blogName.equals(authorName);    //false

        //2
        isEqualString = blogName.equals("howtodoinjava.com");   //true

        //3
        isEqualString = blogName.equals(null);                  //false

        //4 - case-sensitive
        isEqualString = blogName.equals("HOWTODOINJAVA.COM");   //false
    }
}

3. Java String.equalsIgnoreCase()示例

检查两个字符串是否相等的 Java 程序(不区分大小写)。 请注意,equals()equalsIgnoreCase()方法的行为方式相同,只是后者不区分大小写

public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";
        String authorName = "Lokesh gupta";

        //1 - check two strings for same character sequence
        boolean isEqualString = blogName.equalsIgnoreCase(authorName);    //false

        //2
        isEqualString = blogName.equalsIgnoreCase("howtodoinjava.com");   //true

        //3
        isEqualString = blogName.equalsIgnoreCase(null);                  //false

        //4 - case-insensitive
        isEqualString = blogName.equalsIgnoreCase("HOWTODOINJAVA.COM");   //TRUE
    }
}

4. Java 中==equals之间的区别

如前所述,==运算符检查相同的对象引用。 它不检查字符串内容。

equals()方法检查字符串内容。

public class Main 
{
    public static void main(String[] args) 
    {
        String blogName1 = new String("howtodoinjava.com");
        String blogName2 = new String("howtodoinjava.com");

        boolean isEqual1 = blogName1.equals(blogName2);         //true
        boolean isEqual2 = blogName1 == blogName2;              //false
    }
}

学习愉快!

参考:

Java String文档

Java String.equalsIgnoreCase()方法 – 不区分大小写的比较

原文: https://howtodoinjava.com/java/string/string-equalsignorecase-method/

Java 字符串equalsIgnoreCase()方法用于将字符串与方法参数对象进行比较,而忽略大小写考虑。

equalsIgnoreCase()方法中,如果两个字符串长度相同,并且忽略大小写,则两个字符串中的对应字符相等,则认为它们相等。

1. Java String.equalsIgnoreCase()方法

/**
* @param  anObject - The object to compare
* @return true -  if the non-null argument string represents the same sequence of characters to this string
*         false - in all other cases       
*/
public boolean equalsIgnoreCase(String anotherString) {
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}

  1. Java equalsIgnoreCase()方法用于以不区分大小写的方式检查相等的字符串。
  2. 不要使用'=='运算符。 它检查对象引用,这在大多数情况下是不希望的。
  3. 允许将null传递给方法。 它将返回false

2. Java String.equalsIgnoreCase()示例

检查两个字符串是否相等的 Java 程序(不区分大小写)。 请注意,equals()equalsIgnoreCase()方法的行为方式相同,只是后者不区分大小写

public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";
        String authorName = "Lokesh gupta";

        //1 - case-insensitive comparison
        isEqualString = blogName.equalsIgnoreCase("HOWTODOINJAVA.COM");   //true

        //2 - case-insensitive comparison
        isEqualString = blogName.equalsIgnoreCase("howtodoinjava.com");   //true

        //3 - check two strings for same character sequence ignoring case
        boolean isEqualString = blogName.equalsIgnoreCase(authorName);    //false

        //4 - null is allowed
        isEqualString = blogName.equalsIgnoreCase(null);                  //false

    }
}

3. Java String.equals()示例

Java 程序使用equals方法检查两个字符串是否相等(区分大小写)。

public class Main 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";
        String authorName = "Lokesh gupta";

        //1 - check two strings for same character sequence
        boolean isEqualString = blogName.equals(authorName);    //false

        //2
        isEqualString = blogName.equals("howtodoinjava.com");   //true

        //3
        isEqualString = blogName.equals(null);                  //false

        //4 - case-sensitive
        isEqualString = blogName.equals("HOWTODOINJAVA.COM");   //false
    }
}

4. equalsequalsIgnoreCase之间的区别

显然,在执行字符串比较时,Java 中equalsequalsIgnoreCase之间的差异是区分大小写。

  1. equals()方法进行区分大小写的比较。
  2. equalsIgnoreCase()方法进行不区分大小写的比较。

学习愉快!

参考:String Java 文档

Java String.charAt()方法示例

原文: https://howtodoinjava.com/java/string/string-charat-method-example/

方法java.lang.String.charAt(int index)返回字符串对象中指定的index变量处的字符。

众所周知,Java 字符串内部存储在char数组中。 此方法仅使用index从字符串对象中的后备char数组获取字符。

1. charAt()方法参数

唯一的方法参数是index。 它必须是int类型。index参数必须为:

  1. 大于等于“0”
  2. 小于字符串字符的长度,即str.length()

任何无效的索引参数将导致StringIndexOutOfBoundsException

2. Java String.charAt()方法示例

让我们学习结合使用String.charAt()方法和实时示例。

public class StringExample 
{
    public static void main(String[] args) throws Exception 
    {
        String blogName = "howtodoinjava.com";

        char c1 = blogName.charAt(0);   //first character
        char c2 = blogName.charAt(blogName.length() - 1);   //last character
        char c3 = blogName.charAt( 5 );        //random character

        System.out.println("Character at 0 index is: "+c1);
        System.out.println("Character at last is: "+c2);
        System.out.println("Character at 5 index is: "+c3);

        char c4 =  blogName.charAt( 50 );        //invalid index
    }
}

程序输出:

Character at 0 index is: h
Character at last is: m
Character at 5 index is: d

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: 
	String index out of range: 50
	at java.lang.String.charAt(String.java:658)
	at com.howtodoinjava.demo.StringExample.main(StringExample.java:17)

在此示例中,我们通过示例了解了String类的charAt()方法。

学习愉快!

参考:

String Java 文档

Java String.indexOf()方法示例

原文: https://howtodoinjava.com/java/string/java-string-indexof-method-example/

Java 字符串indexOf()方法返回给定参数字符或字符串的索引。 如果在字符串中找不到参数,则方法返回-1。 字符串的索引计数器从零开始。

Java String.indexOf()方法语法

String.indexOf()方法具有四种重载形式:

序号 方法语法 描述
1. int indexOf(String substring) 返回给定子字符串的索引位置
2. int indexOf(String substring, int fromIndex) fromIndex位置返回给定子字符串的索引位置
3. int indexOf(int ch) 返回给定char值的索引位置
4. int indexOf(int ch, int fromIndex) 返回给定char值和fromIndex位置的索引位置

不允许使用null参数

不允许将null参数传递给indexOf()方法。 这将导致NullPointerException异常。

String blogName = "howtodoinjava.com";
System.out.println( blogName.indexOf(null) );

//Program output

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.indexOf(String.java:1705)
	at java.lang.String.indexOf(String.java:1685)
	at com.StringExample.main(StringExample.java:9)

1. Java String.indexOf(String substring)示例

Java 程序使用indexOf(String substring)方法在给定的字符串对象中查找子字符串的索引。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.indexOf("java") );			//9

        System.out.println( "hello world".indexOf("world") );	//6

        System.out.println( "hello world".indexOf("earth") );	//-1
    }
}

程序输出。

9
6
-1

2. Java String.indexOf(String substring, int fromIndex)示例

Java 程序,使用给定的indexOf(String substring, int fromIndex, int fromIndex)方法,在给定的fromIndex中查找给定字符串对象中的substring索引。

请注意,找到子字符串时,索引计数仅从 0 索引开始,并且仅从字符串开头开始。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.indexOf("java", 5) );			//9
        System.out.println( "hello world".indexOf("world", 6) );	//6
        System.out.println( "hello world".indexOf("world", 2) );	//6
        System.out.println( "hello world".indexOf("world", 10) );	//-1
    }
}

程序输出:

9
6
6
-1

3. Java String.indexOf(char ch)示例

Java 程序使用indexOf(char ch)方法在给定的字符串对象中查找给定字符'ch'的索引。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.indexOf('j') );            //9
        System.out.println( "hello world".indexOf('w') );       //6
        System.out.println( "hello world".indexOf('k') );       //-1
    }
}

程序输出:

9
6
-1

4. Java String.indexOf(int ch, int fromIndex)示例

Java 程序使用给定的indexOf(String substring, int fromIndex)方法从给定的fromIndex位置开始在给定的字符串对象中查找字符'ch'的索引。

请注意,找到字符后,索引计数仅从 0 索引开始,并且仅从字符串的开头开始。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.indexOf('j', 4) );         //9
        System.out.println( "hello world".indexOf('w', 2) );    //6
        System.out.println( "hello world".indexOf('w', 6) );    //6
        System.out.println( "hello world".indexOf('k') );       //-1
    }
}

程序输出:

9
6
6
-1

学习愉快!

Java String文档

Java String.lastIndexOf()方法示例

原文: https://howtodoinjava.com/java/string/string-lastindexof-method/

Java 字符串lastIndexOf()方法返回指定参数字符或字符串的最后一个索引。 如果在字符串中找不到参数,则方法返回-1。 字符串的索引计数器从零开始。

Java String.lastIndexOf()方法语法

String.lastIndexOf()方法具有四种重载形式:

| 序号 | 方法语法 | 描述 |
| --- | --- |
| 1. | int lastIndexOf(String substring) | 返回给定substring的最后一个索引位置 |
| 2. | int lastIndexOf(String substring, int fromIndex int) | 返回给定substring的最后一个索引位置,从指定的fromIndex开始向后搜索 |
| 3. | int lastIndexOf(int ch) | 返回给定char值的最后一个索引位置 |
| 4. | int lastIndexOf(int ch, int fromIndex) | 返回给定char值的索引位置,从指定的fromIndex开始向后搜索 |

不允许使用null参数

不允许将null参数传递给lastIndexOf()方法。 这将导致NullPointerException异常。

String blogName = "howtodoinjava.com";
System.out.println( blogName.lastIndexOf(null) );

//Program output

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.lastIndexOf(String.java:1705)
	at java.lang.String.lastIndexOf(String.java:1685)
	at com.StringExample.main(StringExample.java:9)

1. Java String.lastIndexOf(String substring)示例

Java 程序使用lastIndexOf(String substring)方法在给定的字符串对象中查找子字符串的最后一个索引。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.lastIndexOf("java") );			//9

        System.out.println( "hello world".lastIndexOf("world") );	//6

        System.out.println( "hello world".lastIndexOf("earth") );	//-1
    }
}

程序输出。

9
6
-1

2. Java String.lastIndexOf(String substring, int fromIndex)示例

Java 程序查找给定字符串对象中substring的最后一个索引,并使用indexOf(String substring, int fromIndex)方法从指定的fromIndex开始向后搜索。

请注意,找到子字符串时,索引计数仅从 0 索引开始,并且仅从字符串开头开始。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.indexOf("java", 5) );			//9
        System.out.println( "hello world".indexOf("world", 6) );	//6
        System.out.println( "hello world".indexOf("world", 2) );	//6
        System.out.println( "hello world".indexOf("world", 10) );	//-1
    }
}

程序输出:

9
6
6
-1

3. Java String.lastIndexOf(char ch)示例

Java 程序使用lastIndexOf(char ch)方法在给定的字符串对象中找到给定字符'ch'的最后一个索引。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.lastIndexOf('j') );            //9
        System.out.println( "hello world".lastIndexOf('w') );       //6
        System.out.println( "hello world".lastIndexOf('k') );       //-1
    }
}

程序输出:

9
6
-1

4. Java String.lastIndexOf(int ch, int fromIndex)示例

Java 程序在给定的字符串对象中查找字符'ch'的最后一个索引,然后使用lastIndexOf(String substring, int fromIndex)方法从指定的fromIndex开始向后搜索。

请注意,找到字符后,索引计数仅从 0 索引开始,并且仅从字符串的开头开始。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.lastIndexOf('j', 4) );         //9
        System.out.println( "hello world".lastIndexOf('w', 2) );    //6
        System.out.println( "hello world".lastIndexOf('w', 6) );    //6
        System.out.println( "hello world".lastIndexOf('k') );       //-1
    }
}

程序输出:

9
6
6
-1

学习愉快!

参考:

Java String文档

Java String.intern()方法示例

原文: https://howtodoinjava.com/java/string/java-string-intern-method-example/

Java String.intern()返回字符串池中存在的相等字符串字面值的引用。 如果字符串池中存在现有的字符串字面值,则返回其引用。 否则,将创建具有相同内容的新字符串,并返回对新字符串的引用。

使用String.equals()方法检查字符串是否相等。

1. 字符串池

字符串池是堆内存中的保留内存区域,Java 用于存储字符串常量。 请注意,默认情况下 Java 字符串是不可变的。

Java 在字符串池中仅存储每个不同String值的一个副本。 它有助于在程序执行期间重用String对象以节省内存。 在运行的程序中可能有很多对字符串的引用,但是在字符串池中只有字符串的副本。

1.1 两种创建字符串的方法

在 Java 中,我们可以通过两种方式创建字符串。

String str1 = new String("hello world");

String str2 = "hello world";

在上面的示例中,两种方法都用于创建字符串,但是建议稍后使用字符串字面值。 字符串字面值总是进入字符串池

当我们使用new关键字创建字符串时,将创建两个对象,即一个在堆区中,另一个在字符串常量池中。 创建的字符串对象引用始终指向堆区域对象。

要获取在字符串池中创建的相同对象的引用,请使用intern()方法。

2. Java String.intern()方法

String.intern()返回对字符串池中存在的相等字符串字面值的引用。

众所周知,所有字符串字面值都是在字符串池中自动创建的,因此intern()方法适用于通过'new'关键字创建的String对象。

String.intern()原生方法。 借助intern()方法,可以获得原始字符串对象对应的String常量池对象的引用。

3. Java String.intern()示例

Java 程序使用String.intern()方法插入字符串。

public class StringExample 
{
    public static void main(String[] args) 
    {
        //String object in heap
        String str1 = new String("hello world");

        //String literal in pool
        String str2 = "hello world";

        //String literal in pool
        String str3 = "hello world";

        //String object interned to literal
        //It will refer to existing string literal
        String str4 = str1.intern();

        System.out.println(str1 == str2);       //false
        System.out.println(str2 == str3);       //true
        System.out.println(str2 == str4);       //true
    }
}

程序输出。

false
true
true

在此示例中,我们学习了内联 Java 中的字符串。 这是本机方法,可提供高性能

参考文献:

Java 字符串指南

String Java 文档

Java String.split()方法示例

原文: https://howtodoinjava.com/java/string/java-string-split-example/

Java String.split()在将字符串拆分为方法参数正则表达式的匹配项之后,返回字符串数组。

1. String.split()方法

通过使用分隔符子字符串或正则表达式进行分割,可以使用String.split()将字符串拆分为字符串数组

1.1 方法语法

String.split()方法已重载,有两种变体。

/**
* @param regex - the delimiting regular expression
* @param limit - the result threshold
* 
* @return - the array of strings
*/
public String[] split(String regex);

public String[] split(String regex, int limit);

1.2 抛出PatternSyntaxException

请注意,如果正则表达式的语法无效,则split()会引发PatternSyntaxException。 在给定的示例中,"["是无效的正则表达式。

public class StringExample 
{
    public static void main(String[] args) 
    {       
        String[] strArray = "hello world".split("[");
    }
}

程序输出。

Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed character class near index 0
[
^
	at java.util.regex.Pattern.error(Pattern.java:1955)
	at java.util.regex.Pattern.clazz(Pattern.java:2548)
	at java.util.regex.Pattern.sequence(Pattern.java:2063)
	at java.util.regex.Pattern.expr(Pattern.java:1996)
	at java.util.regex.Pattern.compile(Pattern.java:1696)
	at java.util.regex.Pattern.<init>(Pattern.java:1351)
	at java.util.regex.Pattern.compile(Pattern.java:1028)
	at java.lang.String.split(String.java:2367)
	at java.lang.String.split(String.java:2409)
	at com.StringExample.main(StringExample.java:9)

1.3 null不是有效的方法参数

方法不接受null参数。 如果方法参数为null,它将抛出NullPointerException

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.split(String.java:2324)
	at com.StringExample.main(StringExample.java:11)

2. Java String.split(String regex)示例

2.1 Java 用字或分隔符分割字符串

Java 程序根据某些标记拆分字符串。 在给定的示例中,我为分隔符连字符"-"拆分字符串。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "how to do-in-java-provides-java-tutorials";

        String[] strArray = str.split("-");

        System.out.println(Arrays.toString(strArray));
    }
}

程序输出:

[how to do, in, java, provides, java, tutorials]

与之相似:

2.2 Java 按空格分割字符串

Java 程序按空格分割字符串。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "how to do in java provides java tutorials";

        String[] strArray = str.split("\\s");

        System.out.println(Arrays.toString(strArray));
    }
}

程序输出:

[how, to, do, in, java, provides, java, tutorials]

2.2 Java 用多个分隔符分割字符串

Java 程序用多个分隔符分割字符串。 在多个分隔符之间使用正则表达式或运算符'|')符号。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "how-to-do-in-java. provides-java-tutorials.";

        String[] strArray = str.split("-|\\.");

        System.out.println(Arrays.toString(strArray));
    }
}

程序输出:

[how, to, do, in, java, provides, java, tutorials]

3. Java String.split(String regex, int limit)示例

此版本的方法也拆分字符串,但是标记的最大数量不能超过limit参数。

Java 程序以空格分割字符串,例如最大标记数不能超过'5'

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "how to do in java provides java tutorials";

        String[] strArray = str.split("\\s", 5);

        System.out.println(strArray.length);	//5
        System.out.println(Arrays.toString(strArray));
    }
}

程序输出:

5

[how, to, do, in, java provides java tutorials]

在此示例中,我们学习了在 Java 中将字符串拆分为数组。 我们看到了示例如何使用分隔符在 Java 中拆分字符串。

参考文献:

Java 字符串指南

String Java 文档

Java String.replace()方法示例

原文: https://howtodoinjava.com/java/string/java-string-replace-method/

Java String.replace()方法替换与目标子字符串匹配的该字符串的每个子字符串。 子字符串匹配过程从字符串的开头(索引 0)开始。

1. String.replace()方法

String.replace()方法是 Java 中的重载方法。 它有两个变体。

  1. public String replace(char oldChar, char newChar) – 返回一个字符串,该字符串是用newChar替换此字符串中所有出现的oldChar的结果。
  2. public String replace(CharSequence target, CharSequence replacement) – 返回将此字符串中所有出现的target子字符串替换为replacement子字符串而产生的字符串。

2. Java String.replace(char oldChar, char newChar)示例

Java 程序,用新字符替换所有出现的给定字符。 在给定的示例中,我将所有出现的字母“o”(小写)替换为字母“O”(大写)。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String originalString = "Hello world !!";

        String newString = originalString.replace('o', 'O');	//HellO wOrld !!

        System.out.println(originalString);
        System.out.println(newString);
    }
}

程序输出。

Hello world !!
HellO wOrld !!

2. Java String.replace(CharSequence target, CharSequence replacement)示例

Java 程序,用新的子字符串replacement替换所有出现的给定子字符串'target'

在给定的示例中,我将所有出现的子字符串“java”替换为大写的“JAVA”字符串。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String originalString = "how to do in java - java tutotials";

        String newString = originalString.replace("java", "JAVA");

        System.out.println(originalString);
        System.out.println(newString);
    }
}

程序输出:

how to do in java - java tutotials
how to do in JAVA - JAVA tutotials

请注意,不允许正则表达式作为方法参数。 如果要使用正则表达式,请使用 String replaceAll()方法。

3. 不允许为null

两个方法的参数均不允许使用null。 它将抛出NullPointerException

public class StringExample 
{
    public static void main(String[] args) 
    {
        String newString = "hello world".replace("world", null);

        //or

        //String newString = "hello world".replace(null, "world");
    }
}

程序输出:

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.replace(String.java:2227)
	at com.StringExample.main(StringExample.java:7)

学习愉快!

参考文献:

Java String方法和示例

Java String Doc

Java hashCode()equals() – 契约,规则和最佳实践

原文: https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/

了解 Java hashCode()equals()方法,它们的默认实现以及如何正确覆盖它们的信息。 另外,请学习使用 Apache Commons 包的工具类HashCodeBuilderEqualsBuilder实现这些方法。

hashCode()equals()方法已在Object类中定义,该类是 Java 对象的父类。 因此,所有 java 对象都继承这些方法的默认实现。

Table of Contents:

1) Usage of hashCode() and equals() Methods
2) Override the default behavior
3) EqualsBuilder and HashCodeBuilder
4) Generate hashCode() and equals() using Eclipse
5) Important things to remember
6) Special Attention When Using in ORM

1. hashCode()equals()方法的用法

  1. equals(Object otherObject) – 顾名思义,该方法用于简单地验证两个对象的相等性。 默认实现是简单地检查两个对象的对象引用以验证它们的相等性。 默认情况下,当且仅当两个对象存储在相同的内存地址中时,两个对象才相等。

  2. hashcode() – 在运行时为对象返回唯一的整数值。 默认情况下,整数值主要来自堆中对象的内存地址(但并非总是强制性的)。

    当此对象需要存储在某些 HashTable 之类的数据结构中时,此哈希码用于确定存储桶位置。

1.1 hashCode()equals()之间的协定

通常,无论何时覆盖equals()方法,都必须覆盖hashCode()方法,以维护hashCode()方法的常规协定,该协定规定相等的对象必须具有相等的哈希码

  • 在 Java 应用程序执行期间,只要在同一个对象上多次调用它,hashCode方法必须一致地返回相同的整数,前提是未修改该对象在equals比较中使用的信息。
    从一个应用程序的一次执行到同一应用程序的另一次执行,此整数不必保持一致。
  • 如果根据equals(Object)方法两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
  • 如果不是,则根据 equals(java.lang.Object) 方法如果两个对象不相等,则在两个对象中的每一个上调用hashCode方法必须产生不同的整数结果。
    但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

2. 覆盖hashCode()equals()的默认行为

一切正常,直到您没有在类中覆盖这些方法中的任何一个。 但是,有时应用程序需要更改某些对象的默认行为。 让我们理解为什么我们需要覆盖equalshashCode方法

2.1 默认行为

让我们以您的应用程序具有Employee对象的示例为例。 让我们创建Employee类的最小可能结构:

public class Employee
{
	private Integer id;
	private String firstname;
	private String lastName;
	private String department;

	//Setters and Getters
}

Employee类之上具有一些非常基本的属性及其访问器方法。 现在考虑一个简单的情况,您需要比较两个员工对象

public class EqualsTest {
	public static void main(String[] args) {
		Employee e1 = new Employee();
		Employee e2 = new Employee();

		e1.setId(100);
		e2.setId(100);

		System.out.println(e1.equals(e2));	//false
	}
}

没有猜中奖。 上述方法将打印false。 但是,在知道两个对象代表同一位员工之后,这真的正确吗? 在实时应用程序中,这应该返回true

2.2 我们应该只覆盖equals()方法吗?

为了实现正确的应用程序行为,我们需要覆盖equals()方法,如下所示:

public boolean equals(Object o) {
	if(o == null)
	{
		return false;
	}
	if (o == this)
	{
		return true;
	}
	if (getClass() != o.getClass())
	{
		return false;
	}

	Employee e = (Employee) o;
	return (this.getId() == e.getId());
}

将此方法添加到Employee类中,EqualsTest将开始返回true

我们完成了吗? 还没。 让我们以不同的方式再次在修改后的Employee类上进行测试。

import java.util.HashSet;
import java.util.Set;

public class EqualsTest
{
	public static void main(String[] args)
	{
		Employee e1 = new Employee();
		Employee e2 = new Employee();

		e1.setId(100);
		e2.setId(100);

		//Prints 'true'
		System.out.println(e1.equals(e2));

		Set<Employee> employees = new HashSet<Employee>();
		employees.add(e1);
		employees.add(e2);

		System.out.println(employees);	//Prints two objects
	}
}

上面的类在第二个打印语句中打印两个对象。 如果两个员工对象都相等,则在仅存储唯一对象的Set中,在所有两个对象都引用同一员工之后,HashSet中必须只有一个实例。 我们缺少什么?

2.3 还覆盖hashCode()方法

我们缺少第二种重要方法hashCode()。 如 Java 文档所述,如果您覆盖equals()方法,则必须覆盖hashCode()方法。 因此,让我们在Employee类中添加另一个方法。

@Override
public int hashCode()
{
	final int PRIME = 31;
	int result = 1;
	result = PRIME * result + getId();
	return result;
}

Employee类中添加上述方法后,第二条语句仅开始打印第二条语句中的单个对象,并且从而验证e1e2 的真实相等性。

3. EqualsBuilderHashCodeBuilder工具类

Apache Commons 提供了两个出色的工具类,HashCodeBuilderEqualsBuilder ,用于生成hashCodeequals方法。 以下是其用法:

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Employee
{
	private Integer id;
	private String firstname;
	private String lastName;
	private String department;

	//Setters and Getters

	@Override
	public int hashCode()
	{
		final int PRIME = 31;
		return new HashCodeBuilder(getId()%2==0?getId()+1:getId(), PRIME).toHashCode();
	}

	@Override
	public boolean equals(Object o) {
	if (o == null)
	   return false;

	if (o == this)
	   return true;

	if (o.getClass() != getClass())
	   return false;

	Employee e = (Employee) o;

	return new EqualsBuilder().
			  append(getId(), e.getId()).
			  isEquals();
	}
}

4. 使用 Eclipse 生成hashCode()equals()

如果您使用任何代码编辑器,那么大多数编辑器也能够为您生成一些良好的结构。 例如, Eclipse IDE 可以为您生成hashCode()equals()的非常好的实现。

右键单击“Java 文件->源->生成hashCode()equals()…”

Generate HashCode and Equals In Eclipse

在 Eclipse 中生成hashCode()equals()

5. Java hashCode()equals()最佳实践

  1. 始终使用对象的相同属性来生成hashCode()equals()。 在本例中,我们使用了id雇员。
  2. equals()必须与一致(如果未修改对象,则它必须保持返回相同的值)。
  3. 每当a.equals(b)时,则a.hashCode()必须与b.hashCode()相同。
  4. 如果覆盖一个,则应覆盖另一个。

6. 在 ORM 中使用时应特别注意

如果您要处理 ORM,请确保始终使用获取器,并且切勿在hashCode()equals() 中使用字段引用。 这是有原因的,在 ORM 中,字段有时是延迟加载的,直到调用它们的获取器方法才可用。

例如,在我们的Employee类中,如果我们使用e1.id == e2.idid字段很可能是延迟加载的。 因此,在这种情况下,一个可能为零或为null,从而导致错误的行为。

但是如果使用*e1.getId() == e2.getId()*,即使字段是延迟加载,我们也可以确保; 调用获取器将首先填充该字段。

这就是我对 hashCode()equals()方法所了解的全部。 我希望这会对某人有所帮助。

如果您感觉到我在某处缺少任何东西或有什么不对,请发表评论。 我将再次更新此帖子以帮助他人。

学习愉快!

下载源码

Java String.replaceFirst()方法示例

原文: https://howtodoinjava.com/java/string/java-string-replacefirst-example/

Java String replaceFirst()方法用给定的替换子字符串替换找到的第一个子字符串'regex',该子字符串与给定的参数子字符串(或正则表达式)匹配。 子字符串匹配过程从字符串的开头(索引 0)开始。

1. String.replaceFirst(String regex,String replacement)方法

String.replaceFirst()方法使用正则表达式查找子字符串并将其替换为replacement子字符串参数。

/**
* @param regex - the regular expression to which this string is to be matched
* @param replacement - the string to be substituted for the first match
*
* @return The resulting string after replacement is done
*/

public String replaceFirst(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}

2. Java String.replaceFirst()示例

Java 程序,用新的子字符串替换字符串中给定字符串或正则表达式的第一个匹配项。 在给定的示例中,我将大写的“JAVA”字符串替换为第一次出现的子字符串“java”。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "Java says hello world. Java String tutorial";

        //Replace first occurrence of substring "Java" with "JAVA"
        String newStr = str.replaceFirst("Java", "JAVA");

        //Replace first occurrence of substring "a" with "A"
        String regexResult = str.replaceFirst("[a]", "A");

        System.out.println(newStr);
        System.out.println(regexResult);
    }
}

程序输出。

JAVA says hello world. Java String tutorial
JAva says hello world. Java String tutorial

3. 不允许为null

两个方法的参数均不允许使用null。 它将抛出NullPointerException

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "Java says hello world. Java String tutorial";

        String newStr = str.replaceFirst("Java", null);

        System.out.println(newStr);
    }
}

程序输出:

Exception in thread "main" java.lang.NullPointerException: replacement
	at java.util.regex.Matcher.replaceFirst(Matcher.java:999)
	at java.lang.String.replaceFirst(String.java:2165)
	at com.StringExample.main(StringExample.java:9)

在此示例中,我们学习了替换 Java 中字符串中首次出现的字符。

学习愉快!

参考文献:

Java String方法和示例
Java String Doc

Java String.replaceAll()方法示例

原文: https://howtodoinjava.com/java/string/java-string-replaceall-example/

Java String.replaceAll()用给定的替换替换与给定的正则表达式匹配的每个子字符串后,将返回一个字符串。

1. String.replaceAll()方法

使用String.replaceAll(String regex, String replacement)将所有出现的子字符串(匹配参数regex)替换为replacement字符串。

1.1 方法语法

/**
* @param regex - regular expression to match in given string
* @param replacement : replacement string to be replaced
* 
* @return result string after replacing all occurrence of 
* matching 'regex' with replacement 'substring'
*/
public String replaceAll(String regex, String replacement) 
{
	return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

1.2 抛出PatternSyntaxException

请注意,如果正则表达式的语法无效,则replaceAll()会引发PatternSyntaxException。 在给定的示例中,"["是无效的正则表达式。

public class StringExample 
{
    public static void main(String[] args) 
    {       
        String newStr = "hello world".replaceAll("[", "");
    }
}

程序输出。

Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed character class near index 0
[
^
	at java.util.regex.Pattern.error(Pattern.java:1955)
	at java.util.regex.Pattern.clazz(Pattern.java:2548)
	at java.util.regex.Pattern.sequence(Pattern.java:2063)
	at java.util.regex.Pattern.expr(Pattern.java:1996)
	at java.util.regex.Pattern.compile(Pattern.java:1696)
	at java.util.regex.Pattern.<init>(Pattern.java:1351)
	at java.util.regex.Pattern.compile(Pattern.java:1028)
	at java.lang.String.replaceAll(String.java:2210)
	at com.StringExample.main(StringExample.java:9)

2. Java String.replaceAll()示例

2.1 替换所有出现的子串或单词

Java 程序替换字符串中所有出现的单词。 在此示例中,我们将单词“java”替换为“scala”。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "how to do in java provides java tutorials";

        String newStr = str.replaceAll("java", "scala");

        System.out.println(newStr);
    }
}

程序输出:

how to do in scala provides scala tutorials

2.2 替换所有空白

Java 程序替换字符串中所有出现的空格。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String str = "how to do in java provides java tutorials";

        String newStr = str.replaceAll("\\s", "");

        System.out.println(newStr);
    }
}

程序输出:

howtodoinjavaprovidesjavatutorials

参考文献:

Java 字符串指南
String Java 文档

Java String.substring()方法示例

原文: https://howtodoinjava.com/java/string/java-string-substring-example/

Java 字符串substring()方法返回一个新字符串,该字符串是该字符串的子字符串。 substring()方法是重载方法,有两种变体:

  1. String substring(int beginIndex)
  2. String substring(int beginIndex, int endIndex)

其中方法参数为:

  • beginIndex – 起始索引,包括端点。
  • endIndex – 结束索引,不包括。

1. Java String.substring(int beginIndex)示例

它返回一个字符串,该字符串是该字符串的子字符串。 子字符串从指定'beginIndex'处的字符开始,到字符串的末尾。

  1. 请注意,索引从“0”开始,指的是字符串的第一个字符。
  2. 如果参数索引不是有效索引,则方法将引发StringIndexOutOfBoundsException错误。 有效索引始终大于或等于零; 小于或等于字符串的长度。

0 >=有效索引<=字符串的长度

Java 程序从给定索引获取字符串的子字符串。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println(blogName.substring(3));	//todoinjava.com

        System.out.println("hello world".substring(6));	//world
    }
}

程序输出。

todoinjava.com
world

2. Java String.substring(int beginIndex, int endIndex)示例

它返回一个字符串,该字符串是该字符串的子字符串。 子字符串从指定'beginIndex'包括)的字符开始,到'endIndex'排除)位置。

Java 程序,用于从给定的开始索引到结束索引获取字符串的子字符串。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println(blogName.substring(14, blogName.length()));	//com

        System.out.println("hello world".substring(6,9));	//wor

        System.out.println("0123456789".substring(3, 7));	//3456
    }
}

程序输出:

com
wor
3456

在最后一条语句中,示例更有意义,因为每个字符都表示其在字符串中的索引位置。 第 0 个索引为“0”,第一个索引为“1”,依此类推,直到第 9 个位置为“9”。

当我们找到从索引 3 到索引 7 的子字符串时,我们得到字符串3456。 包括开始索引,排除了结束索引“7”。

学习愉快!

参考文献:

Java String文档

Java String.startsWith()示例

原文: https://howtodoinjava.com/java/string/java-string-startswith-example/

Java 字符串startsWith()方法用于检查字符串的前缀。 它验证给定的字符串是否以参数字符串开头。

startsWith()方法是重载方法,具有两种形式:

  1. boolean startsWith(String str) – 如果str是字符串的前缀,则返回true
  2. boolean startsWith(String str, int fromIndex) – 如果字符串从指定索引fromIndex开始以str开头,则返回true

1. String.startsWith(String str)示例

检查字符串是否以前缀参数字符串开头的 Java 程序。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.startsWith("how") );               //true

        System.out.println( "howtodoinjava.com".startsWith("howto") );  //true

        System.out.println( "howtodoinjava.com".startsWith("hello") );  //false
    }
}

程序输出。

true
true
false

String.startsWith()方法不接受正则表达式作为参数。 如果我们以正则表达式模式作为参数传递,它将仅被视为普通字符串。

1.1 不允许使用null方法参数

请注意,不允许null作为方法参数。 如果传递了null,它将抛出NullPointerException

public class StringExample 
{
    public static void main(String[] args) 
    {
    	String blogName = "howtodoinjava.com";

        blogName.startsWith(null);
    }
}

程序输出:

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.startsWith(String.java:1392)
	at java.lang.String.startsWith(String.java:1421)
	at com.StringExample.main(StringExample.java:9)

2. Java String.startsWith(String str, int fromIndex)示例

startsWith(str)方法类似,此方法也检查前缀。 区别在于它检查从指定的fromIndex开始的前缀str

此方法也不接受该方法的null参数。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.startsWith("howto", 0) );                  //true

        System.out.println( "howtodoinjava.com".startsWith("howto", 2) );       //false
    }
}

程序输出:

true
false

参考:

Java String文档

Java String.endsWith()方法示例

原文: https://howtodoinjava.com/java/string/java-string-endswith-method/

Java 字符串endsWith()方法用于检查字符串的后缀。 它验证给定的字符串是否以参数字符串结尾。

1. Java String.endsWith(String str)方法

检查字符串是否以后缀参数字符串'str'结尾的 Java 程序。

public class Main 
{
    public static void main(String[] args) 
    {
        String blogName = "howtodoinjava.com";

        System.out.println( blogName.endsWith("com") );  //true

        System.out.println( blogName.endsWith("net") );  //false
    }
}

程序输出。

true
false

String.endsWith()方法不接受正则表达式作为参数。 如果我们以正则表达式模式作为参数传递,它将仅被视为普通字符串。

1.1 不允许使用null方法参数

请注意,不允许将null作为endsWith()方法的方法参数。 如果传递了null,它将抛出NullPointerException

public class StringExample 
{
    public static void main(String[] args) 
    {
    	String blogName = "howtodoinjava.com";

        System.out.println( blogName.endsWith(null) );
    }
}

程序输出:

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.endsWith(String.java:1436)
	at com.StringExample.main(Main.java:11)

参考:

Java String文档

Java String.toUpperCase()方法示例

原文: https://howtodoinjava.com/java/string/java-string-touppercase-method/

Java String.toUpperCase()返回一个字符串,该字符串是将给定字符串中的所有字符转换为大写的结果。

阅读更多: Java 示例将字符串大写

1. String.toUpperCase()方法

使用String.toUpperCase()将任何字符串转换为大写字母。

1.1 方法语法

String.toUpperCase()方法已重载,有两种变体。

/**
* @param locale - locale use the case transformation rules for given locale
* 
* @return - string converted to uppercase
*/
public String toUpperCase();

public String toUpperCase(Locale locale);

1.2 null不是有效的方法参数

方法不接受null参数。 如果方法参数为null,它将抛出NullPointerException

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.toUpperCase(String.java:2710)
	at com.StringExample.main(StringExample.java:11)

2. Java 将字符串转换为大写示例

Java 程序使用默认语言环境规则将字符串转换为大写。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String string = "hello world";

        String uppercaseString = string.toUpperCase();

        System.out.println(uppercaseString);
    }
}

程序输出。

HELLO WORLD

toUpperCase()方法等于调用toUpperCase(Locale.getDefault())

3. Java String.toUpperCase(Locale)示例

Java 程序使用默认语言环境规则将字符串转换为大写。

public class StringExample 
{
    public static void main(String[] args) 
    {
        System.out.println("hello world".toUpperCase(Locale.getDefault()));
        System.out.println("Γειά σου Κόσμε".toUpperCase(Locale.US));
    }
}

程序输出:

HELLO WORLD
ΓΕΙΆ ΣΟΥ ΚΌΣΜΕ

在此示例中,我们学会了将字符串转换为大写

参考文献:

Java 字符串指南
String Java 文档

Java String.toLowerCase()方法示例

原文: https://howtodoinjava.com/java/string/java-string-tolowercase-method/

Java String.toLowerCase()返回一个字符串,该字符串是将给定字符串中的所有字符转换为小写字母的结果。

1. String.toLowerCase()方法

使用String.toLowerCase()将任何字符串转换为小写字母。

1.1 方法语法

String.toLowerCase()方法已重载,有两种变体。

/**
* @param locale - locale use the case transformation rules for given locale
* 
* @return - string converted to lowercase
*/
public String toLowerCase();

public String toLowerCase(Locale locale);

1.2 null不是有效的方法参数

方法不接受null参数。 如果方法参数为null,它将抛出NullPointerException

Exception in thread "main" java.lang.NullPointerException
	at java.lang.String.toLowerCase(String.java:2710)
	at com.StringExample.main(StringExample.java:11)

2. Java 将字符串转换为小写示例

Java 程序使用默认语言环境规则将字符串转换为小写。

public class StringExample 
{
    public static void main(String[] args) 
    {
        String string = "Hello World";

        String lowercaseString = string.toLowerCase();

        System.out.println(lowercaseString);
    }
}

程序输出。

hello world

toLowerCase()方法等于调用toLowerCase(Locale.getDefault())

3. Java String.toLowerCase(Locale locale)示例

Java 程序使用默认语言环境规则将字符串转换为小写。

public class StringExample 
{
    public static void main(String[] args) 
    {
        System.out.println("hello world".toLowerCase(Locale.getDefault()));
        System.out.println("Γειά σου Κόσμε".toLowerCase(Locale.US));
    }
}

程序输出:

hello world
γειά σου κόσμε

在此示例中,我们学会了将字符串转换为小写

参考文献:

Java 字符串指南
String Java 文档

Java 正则表达式教程

Java 正则表达式教程

原文: https://howtodoinjava.com/java-regular-expression-tutorials/

正则表达式用作字符串的搜索模式。 使用正则表达式,我们也可以找到一个或多个匹配项。 我们可以在字符串中查找匹配的任何国王,例如简单字符,固定字符串或任何复杂的字符模式,例如电子邮件,SSN 或域名。

1. 正则表达式

正则表达式是强大,灵活和高效的文本处理的关键。 它允许您描述和解析文本。 正则表达式可以添加,删除,隔离和折叠,纺锤并破坏各种文本和数据。

1.1 元字符和字面值

完整的正则表达式由两种类型的字符组成。

  • 特殊字符(类似于文件名中的*)称为元字符
  • 其余的称为字面值普通文本字符

正则表达式从其元字符提供的高级表达能力中受益。 我们可以将文本视为字面值,将元字符视为语法。 根据一组规则将单词与语法结合在一起,以创建表达思想的表达。

1.2 Java 正则表达式示例

让我们看一个使用正则表达式作为参考的 Java 快速示例。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main 
{
 public static void main(String[] args) 
 {
  Pattern pattern = Pattern.compile("Alex|Brian");
  Matcher matcher = pattern.matcher("Generally, Alex and Brian share a great bonding.");

  while (matcher.find()) {
            System.out.print("Start index: " + matcher.start());
            System.out.print(" End index: " + matcher.end() + " ");
            System.out.println(" - " + matcher.group());
        }
 }
}

程序输出。

Start index: 11 End index: 15  - Alex
Start index: 20 End index: 25  - Brian

2. 正则表达式元字符

让我们探索常用的元字符以更好地理解它们。

2.1 行的起点和终点

起点和终点分别用'^'(脱字符)和'$'(美元)符号表示。 插入号和美元的特殊之处在于它们与行中的位置匹配,而不是与任何实际的文本字符本身匹配。

例如,正则表达式“cat”在字符串中的任何位置都可以找到“cat”,但是仅当“cat”位于行首时,“^cat”才匹配。 例如诸如“category”或“catalog”之类的词。

同样,“cat$”仅在“cat”位于行尾时匹配。 例如像“scat”之类的词。

2.2 字符类

通常称为字符类的正则表达式构造"[···]"让我们列出了比赛中该点要允许的字符。 字符类在创建拼写检查器时很有用。

例如,“e”仅与e匹配,而“a”仅与a匹配,而正则表达式[ea]则与之匹配。 例如sep[ea]r[ea]te将匹配所有单词“separate”,“separate”和“separete”。

另一个示例可以是允许将单词的第一个字母大写,例如[Ss]mith将同时允许使用smithSmith这两个字。

同样,<[hH][123456]>将匹配所有标题标签,即H1H2H3H4H5H6

2.2.1 字符范围

破折号"-"表示字符范围。 <[hH][1-6]><[hH][123456]>相似。 其他有用的字符范围是[0-9][a-z],它们匹配数字和英文小写字母。

我们可以在单个结构中指定多个范围 [0123456789abcdefABCDEF]可以写为[0-9a-fA-F]。 请注意,给出范围的顺序无关紧要。

请注意,破折号仅是字符类中的元字符,否则它与常规破折号匹配。 另外,如果它是范围中列出的第一个字符,则它不可能表示范围,因此在这种情况下将不是元字符。

2.2.2 否定字符类

如果我们在字符类中使用否定符号^,则该类与未列出的任何字符匹配。 例如 [^1-6]匹配的字符不是 1 到 6。

2.3 将任何字符与点匹配

元字符.是与任何字符匹配的字符类的简写。 请注意,在字符类中使用点时,它们不是元字符。 在字符类中,它只是一个简单的字符。

例如,06.24.2019将匹配06/24/201906-24-201906.24.2019。 但是
06[.]24[.]2019仅与06.24.2019匹配。

2.4 匹配交替 - 几个子表达式中的任何一个

管道符号'|'允许您将多个表达式组合成一个与任何单个表达式匹配的表达式。

例如,“Alex”和“Brian”是单独的表达式,但是Alex|Brian是一个与两者都匹配的表达式。

与点类似,在字符类中使用管道时,管道也不是元字符。 在字符类中,它只是一个简单的字符。

例如,要匹配单词“First”或“1st”,我们可以编写正则表达式 – “(First|1st)”或简写为"(Fir|1)st"

3. Java 正则表达式 API

Java 具有内置的 API(java.util.regex)以使用正则表达式。 我们不需要任何第三方库就可以对 Java 中的任何字符串运行正则表达式。

Java 正则表达式 API 提供 1 个接口和 3 个类:

  • Pattern – 必须将指定为字符串的正则表达式首先编译为此类的实例。 然后,可以使用所得的模式来创建Matcher对象,该对象可以将任意字符序列与正则表达式进行匹配。

    Pattern p = Pattern.compile("abc");
    Matcher m = p.matcher("abcabcabcd");
    boolean b = m.matches(); //true
    
    
  • Matcher – 此类提供执行匹配操作的方法。

  • MatchResult(接口) – 这是匹配操作的结果。 它包含用于确定与正则表达式匹配的结果的查询方法。

  • PatternSyntaxException – 引发非受检的异常,表示正则表达式模式中的语法错误。

详细了解这些类和重要方法。

3.1 Pattern

它表示正则表达式的编译表示。 要使用 Java 正则表达式 API,我们必须将正则表达式编译为此类。

编译后,其实例可用于创建Matcher对象,该对象可以将行/字符串与正则表达式匹配。

请注意,许多匹配器可以共享同一模式。 处理期间的状态信息保存在Matcher实例中。

此类的实例为不可变的,可以安全地由多个并发线程使用。

  • Predicate asPredicate() - 创建可用于匹配字符串的 Java 8 谓词
  • static Pattern compile(String regex) – 用于将给定的正则表达式编译为模式。
  • static Pattern compile(String regex, int flags) – 用于将给定的正则表达式编译为带有给定标志的模式。
  • int flags() - 用于返回此模式的匹配标志。
  • Matcher matcher(CharSequence input) – 用于创建匹配器,该匹配器将根据该模式匹配给定的输入。
  • static boolean match(String regex, CharSequence input) – 用于编译给定的正则表达式,并尝试将给定的输入与其匹配。
  • String.pattern() – 用于返回从中编译此模式的正则表达式。
  • static String quote(String s) – 用于返回指定字符串的字面模式串。
  • String[] split(CharSequence input) – 用于在此模式的匹配项附近拆分给定的输入序列。
  • String[] split(CharSequence input, int limit) – 用于在此模式的匹配项附近分割给定的输入序列。
  • Stream splitAsStream(CharSequence input) – 根据该模式的匹配从给定的输入序列创建

3.2 Matcher

它是通过解释Pattern在字符串/行上执行匹配操作的主要类。 创建匹配器后,可将其用于执行各种匹配操作。

此类还定义了用新字符串替换匹配子序列的方法,如果需要,可以根据匹配结果计算其内容。

此类的实例是不是线程安全的

  • boolean find() – 主要用于搜索文本中多个出现的正则表达式。
  • boolean find(int start) – 用于从给定索引开始搜索文本中正则表达式的出现。
  • int start() – 用于获取使用find()方法找到的匹配项的开始索引。
  • int end() – 用于获取使用find()方法找到的匹配项的结束索引。 它返回最后一个匹配字符旁边的字符索引。
  • int groupCount() – 用于查找匹配的子序列的总数。
  • String.group() – 用于查找匹配的子序列。
  • boolean matchs() – 用于测试正则表达式是否与模式匹配。
  • boolean lookingAt() – 尝试从区域的开头开始,将模式与输入序列进行匹配。
  • String quoteReplacement(String s) – 返回指定字符串的字面替换字符串。
  • Matcher reset() – 重置此匹配器。
  • MatchResult toMatchResult() – 以MatchResult的形式返回此匹配器的匹配状态。

4. Java 正则表达式示例

阅读下面给出的示例,以了解正则表达式在解决应用程序中这些特定问题方面的用法。

电子邮件地址的正则表达式

学习使用 Java 中的正则表达式匹配电子邮件地址

^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$

用于密码验证的正则表达式

学习使用 Java 中的正则表达式匹配密码

((?=.*[a-z])(?=.*d)(?=.*[@#$%])(?=.*[A-Z]).{6,16})

商标符号的正则表达式

学习使用 Java 中的正则表达式匹配商标符号

\u2122

任何货币符号的正则表达式

学习使用 Java 中的正则表达式匹配货币符号

\\p{Sc}

“希腊扩展”或希腊语脚本中任何字符的正则表达式

使用 Java 中的正则表达式学习在希腊扩展和希腊脚本中匹配字符

\\p{InGreek} and \\p{InGreekExtended}

北美电话号码的正则表达式

学习使用 Java 中的正则表达式匹配北美电话号码

^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$

国际电话号码的正则表达式

学习使用 Java 中的正则表达式匹配国际电话号码

^\+(?:[0-9] ?){6,14}[0-9]$

日期格式的正则表达式

学习使用 Java 中的正则表达式匹配日期格式

^[0-3]?[0-9]/[0-3]?[0-9]/(?:[0-9]{2})?[0-9]{2}$

社会安全号码(SSN)的正则表达式

学习使用 Java 中的正则表达式匹配 SSN

^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$

国际标准书号(ISBN)的正则表达式

学习使用 Java 中的正则表达式匹配 ISBN

^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})
[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)
(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$

美国邮政编码的正则表达式

学习使用 Java 中的正则表达式匹配美国邮政编码

^[0-9]{5}(?:-[0-9]{4})?$

加拿大邮政编码的正则表达式

学习使用 Java 中的正则表达式匹配加拿大邮政编码

^(?!.*[DFIOQU])[A-VXY][0-9][A-Z] ?[0-9][A-Z][0-9]$

英国邮政编码(邮政编码)的正则表达式

学习使用 Java 中的正则表达式匹配英国邮政编码

^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}$

信用卡号码的正则表达式

学习使用 Java 中的正则表达式匹配信用卡号

^(?:(?4[0-9]{12}(?:[0-9]{3})?)|
		(?5[1-5][0-9]{14})|
		(?6(?:011|5[0-9]{2})[0-9]{12})|
		(?3[47][0-9]{13})|
		(?3(?:0[0-5]|[68][0-9])?[0-9]{11})|
		(?(?:2131|1800|35[0-9]{3})[0-9]{11}))$

更多正则表达式示例

匹配字符串的开头或结尾(行锚)

匹配任何字符或字符集

请在注释中向我发送与此 java 正则表达式教程有关的问题。

学习愉快!

参考文献:

java.util.regex

Java 仅允许字母数字字符的正则表达式

原文: https://howtodoinjava.com/regex/regex-alphanumeric-characters/

我们可以使用给定的正则表达式来验证用户输入,使其仅允许字母数字字符。 字母数字字符都是字母和数字,即字母A–Za–z和数字0–9

1. 字母数字正则表达式模式

使用字母数字正则表达式,解决方案非常简单。 字符类可以设置允许的字符范围。 添加了一个可重复一次或多次重复字符类的量词,并将定位符绑定到字符串的开头和结尾的锚点,我们就可以开始了。

正则表达式:^[a-zA-Z0-9]+$

2. 字母数字正则表达式示例

List<String> names = new ArrayList<String>();

names.add("Lokesh");  
names.add("LOkesh123");  
names.add("LOkesh123-");  //Incorrect

String regex = "^[a-zA-Z0-9]+$";

Pattern pattern = Pattern.compile(regex);

for (String name : names)
{
  Matcher matcher = pattern.matcher(name);
  System.out.println(matcher.matches());
}

程序输出。

true
true
false

了解基础知识非常容易。是吧?

学习愉快!

Java thissuper之间的区别

原文: https://howtodoinjava.com/java/basics/this-vs-super/

thissuper在 Java 中是保留的关键字this引用一个类的当前实例,而super引用该类的父类,其中使用了super关键字。

1. Java this关键字

this关键字自动保留对类的当前实例的引用。 在我们要将方法从父类继承到子类,并要专门从子类调用方法的情况下,这非常有用。

我们也可以使用此关键字来访问类中的静态字段,但是建议的方法是使用类引用来访问静态字段,例如MyClass.STATIC_FIELD

2. Java super关键字

this关键字相似,super在 Java 中也是保留关键字。 它始终保存对任何给定类的父类的引用。

使用super关键字,我们可以访问任何子类中父类的字段和方法。

3. Java thissuper关键字示例

在此示例中,我们有两个类ParentClassChildClass,其中ChildClass扩展ParentClass。 我在父类中创建了一个方法showMyName(),并覆盖了它的子类。

现在,当我们尝试使用thissuper关键字在子类中调用showMyName()方法时,它将分别从当前类和父类中调用方法。

public class ParentClass 
{	
	public void showMyName() 
	{
		System.out.println("In ParentClass");
	}
}

public class ChildClass extends ParentClass 
{
	public void showMyName() 
	{
		System.out.println("In ChildClass");
	}

	public void test() 
	{
		this.showMyName();

		super.showMyName();
	}
}

public class Main 
{
	public static void main(String[] args) 
	{
		ChildClass childObj = new ChildClass();

		childObj.test();
	}
}

程序输出。

In ChildClass
In ParentClass

在此 Java 教程中,我们学习了this以及super关键字。 我们还学习了在 Java 应用程序中使用这两个关键字。

学习愉快!

Java 正则表达式 – 信用卡号验证

原文: https://howtodoinjava.com/regex/java-regex-validate-credit-card-numbers/

在此 Java 正则表达式教程中,我们将学习使用正则表达式来验证信用卡号。 我们将从多个提供商(例如 VISA,Mastercard,Amex 和 Diners 等)了解号码格式和信用卡号验证。

1. 有效的信用卡号格式

在实际的信用卡上,压纹卡号的数字通常分为四组。 这使得卡号更易于人类阅读。 每个信用卡公司都使用此数字格式。

我们将利用每个公司之间的格式差异来允许用户输入数字而无需指定公司。 可以从号码中确定公司。 每个公司的格式为:

  • Visa:13 或 16 位数字,以 4 开头。
  • Mastercard:从 51 到 55 开头的 16 位数字。
  • Discover:从 6011 或 65 开始的 16 位数字。
  • American Express:15 位数字,以 34 或 37 开头。
  • Diners Club :14 位数字,从 300 到 305、36 或 38 开头。
  • JCB :15 位数字,以 2131 或 1800 开头,或 16 位数字,以 35 开头。

下面给出的正则表达式假设在执行有效数字检查之前,我们将明确搜索并替换所有空格和连字符。

输入中去除了空格和连字符后,下一个正则表达式将检查信用卡号是否使用六家主要信用卡公司中的任何一家的格式。 它使用命名捕获来检测客户拥有的信用卡品牌

如果不需要确定卡的类型,则可以删除围绕每种卡类型的模式的六个捕获组,因为它们没有任何其他用途。

如果您仅接受某些品牌的信用卡,则可以从正则表达式中删除不接受的信用卡。 例如,删除 JCB 时,请确保删除最后剩余的“|” 在正则表达式中也是如此。 如果您的正则表达式以“|”结尾,它也将接受空字符串作为有效的卡号。

Regex : ^(?:(?<visa>4[0-9]{12}(?:[0-9]{3})?)|
		(?<mastercard>5[1-5][0-9]{14})|
		(?<discover>6(?:011|5[0-9]{2})[0-9]{12})|
		(?<amex>3[47][0-9]{13})|
		(?<diners>3(?:0[0-5]|[68][0-9])?[0-9]{11})|
		(?<jcb>(?:2131|1800|35[0-9]{3})[0-9]{11}))$

在此 Wiki 页面中详细了解信用卡号码格式。

2. 信用卡号码验证示例

public static void main(String[] args)
{
List<String> cards = new ArrayList<String>();

//Valid Credit Cards
cards.add("xxxx-xxxx-xxxx-xxxx");  //Masked to avoid any inconvenience unknowingly

//Invalid Credit Card
cards.add("xxxx-xxxx-xxxx-xxxx"); //Masked to avoid any inconvenience unknowingly

String regex = "^(?:(?<visa>4[0-9]{12}(?:[0-9]{3})?)|" +
		"(?<mastercard>5[1-5][0-9]{14})|" +
		"(?<discover>6(?:011|5[0-9]{2})[0-9]{12})|" +
		"(?<amex>3[47][0-9]{13})|" +
		"(?<diners>3(?:0[0-5]|[68][0-9])?[0-9]{11})|" +
		"(?<jcb>(?:2131|1800|35[0-9]{3})[0-9]{11}))$";

Pattern pattern = Pattern.compile(regex);

for (String card : cards)
{
	//Strip all hyphens
	card = card.replaceAll("-", "");

	//Match the card
	Matcher matcher = pattern.matcher(card);

	System.out.println(matcher.matches());

	if(matcher.matches()) {
		//If card is valid then verify which group it belong 
		System.out.println(matcher.group("mastercard"));
	}
}

3. 使用 Luhn 算法进行校验和验证

在处理顺序之前,您可以对信用卡号进行额外的验证检查。 信用卡号的最后一位数字是根据 Luhn 算法计算得出的校验和。 由于此算法需要基本的算术运算,因此无法使用正则表达式来实现。

下面是可用于使用 Luhn 算法运行校验和验证的方法。 此函数采用一个以信用卡号为参数的字符串。 卡号只能由数字组成。

实际算法在数字数组上运行,计算校验和。 如果总和模 10 为零,则卡号有效。 如果不是,则该数字无效。

我从 Google 代码中获取了 Luhn Algo 的参考实现。

public class Luhn
{
	public static boolean Check(String ccNumber)
	{
		int sum = 0;
		boolean alternate = false;
		for (int i = ccNumber.length() - 1; i >= 0; i--)
		{
			int n = Integer.parseInt(ccNumber.substring(i, i + 1));
			if (alternate)
			{
				n *= 2;
				if (n > 9)
				{
					n = (n % 10) + 1;
				}
			}
			sum += n;
			alternate = !alternate;
		}
		return (sum % 10 == 0);
	}
}

如果需要,可以随意修改上述代码示例以匹配上述正则表达式中的其他验证规则。

学习愉快!

Java 正则表达式 – 加拿大邮政编码验证

原文: https://howtodoinjava.com/regex/canada-postal-code-validation/

在此 Java 正则表达式教程中,我们将学习使用正则表达式来验证加拿大的邮政编码。 您也可以修改正则表达式以使其适合任何其他格式。

1. 什么是有效的加拿大邮政编码

加拿大邮政编码是一个六位字符的字符串,构成加拿大邮政地址的一部分。

有效的加拿大邮政编码为:

  • 格式为A1A 1A1,其中 A 是字母,1 是数字。
  • 用空格分隔第三个和第四个字符。
  • 不要包含字母 D,F,I,O,Q 或 U。
  • 第一个位置不使用字母 W 或 Z。

正则表达式:^(?!.*[DFIOQU])[A-VXY][0-9][A-Z] ?[0-9][A-Z][0-9]$

在正则表达式之上,此正则表达式开头的负前瞻可防止在主题字符串中的任何位置出现 D,F,I,O,Q 或 U。[A-VXY]字符类进一步防止 W 或 Z 作为第一个字符。

2. 加拿大邮政编码验证示例

List<String> zips = new ArrayList<String>();

//Valid ZIP codes
zips.add("K1A 0B1");  
zips.add("B1Z 0B9");  

//Invalid ZIP codes
zips.add("K1A 0D1");  
zips.add("W1A 0B1");  
zips.add("Z1A 0B1");

String regex = "^(?!.*[DFIOQU])[A-VXY][0-9][A-Z] ?[0-9][A-Z][0-9]$";

Pattern pattern = Pattern.compile(regex);

for (String zip : zips)
{
	Matcher matcher = pattern.matcher(zip);
	System.out.println(matcher.matches());
}

Output:

true
true

false
false

那很容易,对吗? 向我提供有关如何使用正则表达式验证加拿大邮政编码的问题。

学习愉快!

货币符号的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-match-any-currency-symbol/

在本教程中,我们将学习匹配所有可用的货币符号,例如美元,欧元,日元,日元。

解决方案正则表达式:\\p{Sc}

使用 Java 正则表达式匹配任何货币符号的示例代码

String content = "Let's find the symbols or currencies : $ Dollar, € Euro, ¥ Yen";

String regex = "\\p{Sc}";

Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find())
{
 System.out.print("Start index: " + matcher.start());
 System.out.print(" End index: " + matcher.end() + " ");
 System.out.println(" : " + matcher.group());
}

Output:

Start index: 39 End index: 40  : $
Start index: 49 End index: 50  : €
Start index: 57 End index: 58  : ¥

祝您学习愉快!

使用 Java 正则表达式进行日期验证

原文: https://howtodoinjava.com/regex/java-regex-date-format-validation/

在使用正则表达式进行的 Java 日期验证中,我们将学习验证简单的日期格式,例如mm/dd/yymm/dd/yyyydd/mm/yydd mm/yyyy。 在这里,我们想使用一个正则表达式来简单地检查输入是否为有效日期,而不尝试消除诸如 2 月 31 日这样的东西。

我们可能会认为,对于日期表达式而言,从概念上讲微不足道的事情应该是一件容易的事。 但事实并非如此。 主要问题是正则表达式不能直接处理数字。

我们不能告诉正则表达式“匹配 1 到 31 之间的数字”。 而是正则表达式逐个字符地工作。

我们使用'3[01]|[12][0-9]|0?[1-9]'来匹配 3,后跟 0 或 1,或者匹配 1 或 2,后跟任意数字,或者匹配一个可选的 0,后跟 1 到 9。因此,您必须选择您希望您的正则表达式的简单或准确的程度。

1. Java 日期验证正则表达式 – 允许省略前导零

正则表达式:^[0-3]?[0-9]/[0-3]?[0-9]/(?:[0-9]{2})?[0-9]{2}$

List dates = new ArrayList();
dates.add("1/1/11");
dates.add("01/01/11");
dates.add("01/01/2011");
dates.add("01/1/2011");
dates.add("1/11/2011");
dates.add("1/11/11");
dates.add("11/1/11");

String regex = "^[0-3]?[0-9]/[0-3]?[0-9]/(?:[0-9]{2})?[0-9]{2}$";

Pattern pattern = Pattern.compile(regex);

for(String date : dates)
{
	Matcher matcher = pattern.matcher(date);
	System.out.println(date +" : "+ matcher.matches());
}

程序输出。

1/1/11 : 		true
01/01/11 : 		true
01/01/2011 : 	true
01/1/2011 : 	true
1/11/2011 : 	true
1/11/11 : 		true
11/1/11 : 		true

2. Java 日期验证正则表达式 – 要求前导零

正则表达式:^[0-3][0-9]/[0-3][0-9]/(?:[0-9][0-9])?[0-9][0-9]$

List dates = new ArrayList();

//With leading zeros
dates.add("01/01/11");
dates.add("01/01/2011");

//Missing leading zeros
dates.add("1/1/11");
dates.add("01/1/2011");
dates.add("1/11/2011");
dates.add("1/11/11");
dates.add("11/1/11");

String regex = "^[0-3][0-9]/[0-3][0-9]/(?:[0-9][0-9])?[0-9][0-9]$";

Pattern pattern = Pattern.compile(regex);

for(String date : dates)
{
	Matcher matcher = pattern.matcher(date);
	System.out.println(date +" : "+ matcher.matches());
}

程序输出:

01/01/11 : 		true
01/01/2011 : 	true

1/1/11 : 		false
01/1/2011 : 	false
1/11/2011 : 	false
1/11/11 : 		false
11/1/11 : 		false

3. Java 日期验证正则表达式 – 将mm/dd/yyyy与所需的前导零匹配

正则表达式:^(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])/[0-9]{4}$

验证日期格式mm/dd/yyyy的 Java 程序。

List dates = new ArrayList();

//With leading zeros
dates.add("01/01/11");
dates.add("01/01/2011");

//Missing leading zeros
dates.add("1/1/11");
dates.add("01/1/2011");

String regex = "^(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])/[0-9]{4}$";

Pattern pattern = Pattern.compile(regex);

for(String date : dates)
{
	Matcher matcher = pattern.matcher(date);
	System.out.println(date +" : "+ matcher.matches());
}

程序输出:

01/01/11 : 		false
01/01/2011 : 	true
1/1/11 : 		false
01/1/2011 : 	false

4. Java 日期验证正则表达式 – 将dd/mm/yyyy与所需的前导零匹配

用于验证日期格式dd/mm/yyyy的正则表达式。

正则表达式:^(3[01]|[12][0-9]|0[1-9])/(1[0-2]|0[1-9])/[0-9]{4}$

List dates = new ArrayList();
//With leading zeros
dates.add("07/13/2011");
dates.add("13/07/2011");
//Missing leading zeros
dates.add("1/1/11");
dates.add("01/1/2011");

String regex = "^(3[01]|[12][0-9]|0[1-9])/(1[0-2]|0[1-9])/[0-9]{4}$";

Pattern pattern = Pattern.compile(regex);

for(String date : dates)
{
	Matcher matcher = pattern.matcher(date);
	System.out.println(date +" : "+ matcher.matches());
}

程序输出:

07/13/2011 : 	false
13/07/2011 : 	true
1/1/11 : 		false
01/1/2011 : 	false

可以随意使用和编辑上述正则表达式来进行日期验证,以满足您的需求。

学习愉快!

使用 Java 正则表达式进行电子邮件验证

原文: https://howtodoinjava.com/regex/java-regex-validate-email-address/

使用正则表达式的电子邮件验证是常见的任务,在任何将电子邮件地址作为注册信息中所需信息的应用程序中都可能需要。 可能会有更多用例,但这里不再讨论。

让我们直接进入主要讨论,即使用正则表达式验证 Java 中的电子邮件。

1. 验证电子邮件的最简单的正则表达式

正则表达式:^(.+)@(.+)$

这是最简单的,只关心@符号。 在@符号前后,可以有任意数量的字符。 让我们看一个简单的例子,了解我的意思。

List emails = new ArrayList();
emails.add("user@domain.com");
emails.add("user@domain.co.in");
emails.add("user1@domain.com");
emails.add("user.name@domain.com");
emails.add("user#@domain.co.in");
emails.add("user@domaincom");

//Invalid emails
emails.add("user#domain.com");
emails.add("@yahoo.com");

String regex = "^(.+)@(.+)$";

Pattern pattern = Pattern.compile(regex);

for(String email : emails){
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

程序输出。

user@domain.com : 		true
user@domain.co.in : 	true
user1@domain.com : 		true
user.name@domain.com : 	true
user#@domain.co.in : 	true
user@domaincom : 		true

user#domain.com : 		false
@yahoo.com : 			false

Common lang 的EmailValidator类中提供了此模式。 因此,如果您需要它,您可以直接使用此类。

2. 对用户名添加限制部分

正则表达式:^[A-Za-z0-9+_.-]+@(.+)$

在此正则表达式中,我们在电子邮件地址的用户名部分添加了一些限制。 上述正则表达式的限制为:

1)允许使用A-Z字符

2)允许使用a-z字符

3)允许使用0-9数字

4)另外,电子邮件中只能包含点(.),破折号(-)和下划线(_

5)其余所有字符均不允许

让我们根据上述正则表达式测试一些电子邮件地址。

List emails = new ArrayList();
emails.add("user@domain.com");
emails.add("user@domain.co.in");
emails.add("user1@domain.com");
emails.add("user.name@domain.com");
emails.add("user_name@domain.co.in");
emails.add("user-name@domain.co.in");
emails.add("user@domaincom");

//Invalid emails
emails.add("@yahoo.com");

String regex = "^[A-Za-z0-9+_.-]+@(.+)$";

Pattern pattern = Pattern.compile(regex);

for(String email : emails){
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

程序输出:

user@domain.com : 			true
user@domain.co.in : 		true
user1@domain.com : 			true
user.name@domain.com : 		true
user_name@domain.co.in : 	true
user-name@domain.co.in : 	true
user@domaincom : 			true

@yahoo.com : 				false

请注意,类似的限制也可以适用于域名部分。 然后正则表达式将变成这样。

^[A-Z0-9+_.-]+@[A-Z0-9.-]+$

3. RFC 5322 允许的 Java 电子邮件验证

正则表达式:^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$

此正则表达式示例使用 RFC 5322 允许的所有字符,这些字符控制电子邮件格式。 如果允许的字符中有一些,如果直接从用户输入传递到 SQL 语句,则会带来安全风险,例如单引号(')和管道字符(|)。

在将电子邮件地址插入传递给另一个程序的字符串中时,应确保转义敏感字符,以防止诸如 SQL 注入攻击之类的安全漏洞。

List emails = new ArrayList();
emails.add("user@domain.com");
emails.add("user@domain.co.in");
emails.add("user.name@domain.com");
emails.add("user?name@domain.co.in");
emails.add("user'name@domain.co.in");

//Invalid emails
emails.add("@yahoo.com");

String regex = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$";

Pattern pattern = Pattern.compile(regex);

for(String email : emails){
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

程序输出:

user@domain.com : 			true
user@domain.co.in : 		true
user.name@domain.com : 		true
user?name@domain.co.in : 	true
user'name@domain.co.in : 	true
@yahoo.com : 				false

4. 限制电子邮件中的前导,尾随或连续点的正则表达式

正则表达式:^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&’*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$

本地部分和域名都可以包含一个或多个点,但是两个点之间不能紧挨出现。 此外,本地部分和域名中的第一个和最后一个字符不得为点号:

List emails = new ArrayList();
emails.add("user@domain.com");
emails.add("user@domain.co.in");
emails.add("user.name@domain.com");
emails.add("user'name@domain.co.in");

//Invalid emails
emails.add(".username@yahoo.com");
emails.add("username@yahoo.com.");
emails.add("username@yahoo..com");

String regex = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$";

Pattern pattern = Pattern.compile(regex);

for(String email : emails){
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

程序输出:

user@domain.com : 			true
user@domain.co.in : 		true
user.name@domain.com : 		true
user'name@domain.co.in : 	true

.username@yahoo.com : 		false
username@yahoo.com. : 		false
username@yahoo..com : 		false

5. 限制顶级域中的字符数的正则表达式(推荐)

现在,让我们修改正则表达式,以使域名必须至少包含一个点,并且域名中最后一个点之后的部分只能由字母组成。

假设域名类似于secondlevel.comthirdlevel.secondlevel.com。顶级域(在这些示例中为.com)必须仅包含 2 至 6 个字母。

正则表达式:^[\\w!#$%&’*+/=?`{|}~^-]+(?:\\.[\\w!#$%&’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$

List emails = new ArrayList();
emails.add("user@domain.com");
emails.add("user@domain.co.in");
emails.add("user.name@domain.com");
emails.add("user_name@domain.com");
emails.add("username@yahoo.corporate.in");

//Invalid emails
emails.add(".username@yahoo.com");
emails.add("username@yahoo.com.");
emails.add("username@yahoo..com");
emails.add("username@yahoo.c");
emails.add("username@yahoo.corporate");

String regex = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";

Pattern pattern = Pattern.compile(regex);

for(String email : emails){
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

程序输出:

user@domain.com : 				true
user@domain.co.in : 			true
user.name@domain.com : 			true
user_name@domain.com : 			true
username@yahoo.corporate.in : 	true

.username@yahoo.com : 			false
username@yahoo.com. : 			false
username@yahoo..com : 			false
username@yahoo.c : 				false
username@yahoo.corporate : 		false

最后一个正则表达式是我对 Java 中的简单电子邮件验证的建议。 请注意,可以在 Java 中不使用正则表达式进行电子邮件验证,但不建议这样做。 在需要处理模式的任何地方,正则表达式都是您的朋友。

请随时使用此正则表达式并根据您的应用程序的其他需求对其进行编辑。

学习愉快!

参考: http://www.rfc-editor.org/rfc/rfc5322.txt

Java 正则表达式密码验证示例

原文: https://howtodoinjava.com/regex/how-to-build-regex-based-password-validator-in-java/

密码验证是当今几乎所有应用程序的需求。 通过手动编写所有内容以使用第三方可用的 API,可以通过多种方法来验证密码。 在此 Java 正则表达式密码验证教程中,我们正在使用正则表达式构建密码验证器

1. 用于密码验证的正则表达式

((?=.*[a-z])(?=.*d)(?=.*[@#$%])(?=.*[A-Z]).{6,16})

上面的正则表达式有以下几节:

(?=.*[a-z])     : This matches the presence of at least one lowercase letter.
(?=.*d)         : This matches the presence of at least one digit i.e. 0-9.
(?=.*[@#$%]) 	: This matches the presence of at least one special character.
((?=.*[A-Z])    : This matches the presence of at least one capital letter.
{6,16}          : This limits the length of password from minimum 6 letters to maximum 16 letters.

前 4 个部分的顺序可以更改,甚至可以从最终正则表达式中删除。 这个事实可以用来以编程方式构建我们的密码验证器。

2. 使用正则表达式验证密码的 Java 程序

我们正在使验证器可配置,以便可以根据需要设置限制。 就像我们要强制使用至少一个特殊字符而不是任何大写字母一样,我们可以相应地传递所需的参数。

package com.howtodoinjava.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PasswordValidator
{
	private static PasswordValidator INSTANCE = new PasswordValidator();
	private static String pattern = null;

	/**
	 * No one can make a direct instance
	 * */
	private PasswordValidator()
	{
		//do nothing
	}

	/**
	 * Force the user to build a validator using this way only
	 * */
	public static PasswordValidator buildValidator( boolean forceSpecialChar,
													boolean forceCapitalLetter,
													boolean forceNumber,
													int minLength,
													int maxLength)
	{
		StringBuilder patternBuilder = new StringBuilder("((?=.*[a-z])");

		if (forceSpecialChar)
		{
			patternBuilder.append("(?=.*[@#$%])");
		}

		if (forceCapitalLetter)
		{
			patternBuilder.append("(?=.*[A-Z])");
		}

		if (forceNumber)
		{
			patternBuilder.append("(?=.*d)");
		}

		patternBuilder.append(".{" + minLength + "," + maxLength + "})");
		pattern = patternBuilder.toString();

		return INSTANCE;
	}

	/**
	 * Here we will validate the password
	 * */
	public static boolean validatePassword(final String password)
	{
		Pattern p = Pattern.compile(pattern);
		Matcher m = p.matcher(password);
		return m.matches();
	}
}

3. 密码验证的单元测试

因此,我们的密码验证程序已准备就绪。 让我们用一些 JUnit 测试用例进行测试。

package com.howtodoinjava.regex;

import junit.framework.Assert;

import org.junit.Test;

@SuppressWarnings("static-access")
public class TestPasswordValidator
{
	@Test
	public void testNormalPassword()
	{
		PasswordValidator validator = PasswordValidator.buildValidator(false, false, false, 6, 14);

		Assert.assertTrue(validator.validatePassword("howtodoinjava"));
		Assert.assertTrue(validator.validatePassword("howtodoin"));
		//Sort on length
		Assert.assertFalse(validator.validatePassword("howto"));
	}

	@Test
	public void testForceNumeric()
	{
		PasswordValidator validator = PasswordValidator.buildValidator(false,false, true, 6, 16);
		//Contains numeric
		Assert.assertTrue(validator.validatePassword("howtodoinjava12"));
		Assert.assertTrue(validator.validatePassword("34howtodoinjava"));
		Assert.assertTrue(validator.validatePassword("howtodo56injava"));
		//No numeric
		Assert.assertFalse(validator.validatePassword("howtodoinjava"));
	}

	@Test
	public void testForceCapitalLetter()
	{
		PasswordValidator validator = PasswordValidator.buildValidator(false,true, false, 6, 16);
		//Contains capitals
		Assert.assertTrue(validator.validatePassword("howtodoinjavA"));
		Assert.assertTrue(validator.validatePassword("Howtodoinjava"));
		Assert.assertTrue(validator.validatePassword("howtodOInjava"));
		//No capital letter
		Assert.assertFalse(validator.validatePassword("howtodoinjava"));
	}

	@Test
	public void testForceSpecialCharacter()
	{
		PasswordValidator validator = PasswordValidator.buildValidator(true,false, false, 6, 16);
		//Contains special char
		Assert.assertTrue(validator.validatePassword("howtod@injava"));
		Assert.assertTrue(validator.validatePassword("@Howtodoinjava"));
		Assert.assertTrue(validator.validatePassword("howtodOInjava@"));
		//No special char
		Assert.assertFalse(validator.validatePassword("howtodoinjava"));
	}
}

在这篇文章中,我们学习了使用 Java 正则表达式进行密码验证的方法,该规则能够验证字母数字和特殊字符,包括最大和最小密码长度。

学习愉快!

适用于希腊语扩展或希腊语脚本的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-match-any-character-in-greek-extended-or-greek-script/

在本教程中,我们将学习匹配“希腊扩展” unicode 块或希腊语脚本一部分的任何字符。

解决方案正则表达式:\\p{InGreek}\p{InGreekExtended}

匹配希腊文字中的任何字符

让我们看一个示例程序,该程序能够匹配字符串中希腊语脚本中的任何字符。

&nbsp;String content = "A math equation might be α + β = λ + γ";
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;
String regex = "\\p{InGreek}";

Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find())
{
&nbsp;System.out.print("Start index: " + matcher.start());
&nbsp;System.out.print(" End index: " + matcher.end() + " ");
&nbsp;System.out.println(" : " + matcher.group());
}

Output:

Start index: 25 End index: 26&nbsp; : α
Start index: 29 End index: 30&nbsp; : β
Start index: 33 End index: 34&nbsp; : λ
Start index: 37 End index: 38&nbsp; : γ

匹配“希腊扩展” unicode 块中的任何字符

让我们看一个示例程序,该程序能够匹配字符串中希腊语脚本中的任何字符。

String content = "Let's learn some new greek extended characters : ᾲ , ᾨ etc.";
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;
String regex = "\\p{InGreekExtended}";

Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find())
{
&nbsp;System.out.print("Start index: " + matcher.start());
&nbsp;System.out.print(" End index: " + matcher.end() + " ");
&nbsp;System.out.println(" : " + matcher.group());
}

Output:

Start index: 49 End index: 50&nbsp; : ᾲ
Start index: 53 End index: 54&nbsp; : ᾨ

参考:

http://en.wikipedia.org/wiki/Greek_alphabet

http://www.alanwood.net/unicode/greek_extended.html

https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html

验证 ISBN(国际标准书号)的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-validate-international-standard-book-number-isbns/

在此 Java 正则表达式教程中,我们将学习使用正则表达式来测试用户是否输入了有效的国际标准书号(ISBN)。

有效的国际标准书号(ISBN)

国际标准书号(ISBN)是一个 13 位(或 10 位也是如此)的编号,用于唯一标识国际上出版的书籍和类书籍产品。 ISBN 的目的是从一个特定的出版商那里建立和识别一个书名或一个书名版本,并且该版本是唯一的,从而允许书商,图书馆,大学,批发商和发行商更有效地营销产品。

每个 ISBN 均由 13 位数字(或 10 位数字)组成,并且每打印一次它都在字母 ISBN 之前。 该数字分为可变长度的四个部分,每个部分之间用连字符分隔。

ISBN 的四个部分如下:

  • 标识发布者的国家或地区分组的组或国家/地区标识符;
  • 发布者标识符,用于标识组中的特定发布者;
  • 标题标识符,用于标识特定标题或标题版本;
  • 校验位是用于验证 ISBN 的 ISBN 末尾的一位数字。

原始的 9 位标准书号(SBN)没有注册组标识符,但是将零(0)作为 9 位 SBN 的前缀会创建一个有效的 10 位 ISBN。

以下所有内容均可以视为有效的 ISBN 的示例:

ISBN 978-0-596-52068-7
ISBN-13: 978-0-596-52068-7
978 0 596 52068 7
9780596520687
ISBN-10 0-596-52068- 9
0-596-52068-9

验证 ISBN 的正则表达式

为了验证 ISBN,我们的正则表达式为:

ISBN-10 的正则表达式:^(?:ISBN(?:-10)?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$)[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$

ISBN-13 的正则表达式:^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)97[89][- ]?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9]$

ISBN-10 或 ISBN-13 的正则表达式:^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$

注意:您不能仅使用正则表达式来验证 ISBN,因为最后一位是使用校验和算法计算的。 本节中的正则表达式仅验证 ISBN 的格式。

现在,我们使用一些演示 ISBN 编号测试我们的 ISBN 正则表达式。

仅验证 ISBN-10 格式

List<String> isbns = new ArrayList<String>();

//Valid ISBNs
isbns.add("0-596-52068-9");  
isbns.add("0 512 52068 9");  
isbns.add("ISBN-10 0-596-52068-9");
isbns.add("ISBN-10: 0-596-52068-9");

//Invalid ISBNs
isbns.add("0-5961-52068-9");  
isbns.add("11 5122 52068 9");  
isbns.add("ISBN-13 0-596-52068-9");
isbns.add("ISBN-10- 0-596-52068-9");

String regex = "^(?:ISBN(?:-10)?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$)[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$";

Pattern pattern = Pattern.compile(regex);

for (String isbn : isbns)
{
	Matcher matcher = pattern.matcher(isbn);
	System.out.println(matcher.matches());
}

Output:

true
true
true
true

false
false
false
false

仅验证 ISBN-13 格式

List<String> isbns = new ArrayList<String>();

//Valid ISBNs
isbns.add("ISBN 978-0-596-52068-7");  
isbns.add("ISBN-13: 978-0-596-52068-7");  
isbns.add("978 0 596 52068 7");
isbns.add("9780596520687");

//Invalid ISBNs
isbns.add("ISBN 11978-0-596-52068-7");  
isbns.add("ISBN-12: 978-0-596-52068-7");  
isbns.add("978 10 596 52068 7");
isbns.add("119780596520687");

String regex = "^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)97[89][- ]?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9]$";

Pattern pattern = Pattern.compile(regex);

for (String isbn : isbns)
{
	Matcher matcher = pattern.matcher(isbn);
	System.out.println(matcher.matches());
}

Output:

true
true
true
true

false
false
false
false

同时验证 ISBN-10 和 ISBN-13 格式

List<String> isbns = new ArrayList<String>();

//Valid ISBNs
isbns.add("ISBN 978-0-596-52068-7");  
isbns.add("ISBN-13: 978-0-596-52068-7");  
isbns.add("978 0 596 52068 7");
isbns.add("9780596520687");
isbns.add("0-596-52068-9");  
isbns.add("0 512 52068 9");  
isbns.add("ISBN-10 0-596-52068-9");
isbns.add("ISBN-10: 0-596-52068-9");

//Invalid ISBNs
isbns.add("ISBN 11978-0-596-52068-7");  
isbns.add("ISBN-12: 978-0-596-52068-7");  
isbns.add("978 10 596 52068 7");
isbns.add("119780596520687");
isbns.add("0-5961-52068-9");  
isbns.add("11 5122 52068 9");  
isbns.add("ISBN-11 0-596-52068-9");
isbns.add("ISBN-10- 0-596-52068-9");

String regex = "^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$";

Pattern pattern = Pattern.compile(regex);

for (String isbn : isbns)
{
	Matcher matcher = pattern.matcher(isbn);
	System.out.println(matcher.matches());
}

Output:

true
true
true
true
true
true
true
true

false
false
false
false
false
false
false
false

我建议您使用上述简单的正则表达式尝试更多的 ISBN 变体,并让我知道您的发现。

祝您学习愉快!

参考:

http://en.wikipedia.org/wiki/International_Standard_Book_Number

http://www.isbn.org/faqs_general_questions

检查输入文本的最小/最大长度的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-validate-the-minmax-length-of-input-text/

在此 Java 正则表达式教程中,我们将学习测试输入文本的长度是否在最小和最大限制之间。

所有编程语言都提供了一种有效的方法来检查文本的长度。 但是,在某些情况下,使用正则表达式检查文本长度会很有用,特别是当长度只是确定主题文本是否适合所需样式的多个规则之一时。

例如,遵循正则表达式可确保文本长度在 1 到 10 个字符之间,并另外将文本限制为大写字母A–Z。 您可以修改正则表达式以允许任何最小或最大文本长度,或允许使用除A–Z之外的其他字符。

正则表达式:^[A-Z]{1,10}$

List<String> names = new ArrayList<String>();

names.add("LOKESH");  
names.add("JAVACRAZY");  
names.add("LOKESHGUPTAINDIA");  //Incorrect
names.add("LOKESH123");  //Incorrect

String regex = "^[A-Z]{1,10}$";

Pattern pattern = Pattern.compile(regex);

for (String name : names)
{
	Matcher matcher = pattern.matcher(name);
	System.out.println(matcher.matches());
}

Output:

true
true
false
false

我建议您使用上述简单的正则表达式尝试更多的变化。

祝您学习愉快!

限制文本中的行数的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-validate-limit-the-number-of-lines-in-text/

在此 Java 正则表达式教程中,我们将学习测试输入文本中的行数是否在某个最小和最大限制之间,而不考虑字符串中出现了多少个总字符。

用于匹配行数的正则表达式将取决于用作行分隔符的确切字符或字符序列。 实际上,行分隔符可能会根据操作系统的约定,应用程序或用户首选项等而有所不同。 因此,编写理想的解决方案取决于应支持哪些约定来指示新行的开始。

本教程中讨论的以下解决方案支持标准 MS-DOS/Windows(“\r\n”),旧版 MacOS(“\r”)和 Unix/Linux/BSD/OSX(“\n”)行中断约定。

正则表达式:\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z

正则表达式的说明

\A          # Assert position at the beginning of the string.
(?>         # Group but don't capture or keep backtracking positions:
  [^\r\n]*  #   Match zero or more characters except CR and LF.
  (?>       #   Group but don't capture or keep backtracking positions:
    \r\n?   #     Match a CR, with an optional following LF (CRLF).
   |        #    Or:
    \n      #     Match a standalone LF character.
  )         #   End the noncapturing, atomic group.
){0,4}      # End group; repeat between zero and four times.
[^\r\n]*    # Match zero or more characters except CR and LF.
\z          # Assert position at the end of the string.

CR : Carriage Return (\r\n)
LF : Line Feed (\n)

在正则表达式之上,验证内容具有最少零行和最多三行。 让我们验证解决方案正则表达式。

零行验证

StringBuilder builder = new StringBuilder();

String regex = "\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z";

Pattern pattern = Pattern.compile(regex);

Matcher matcher = pattern.matcher(builder.toString());
System.out.println(matcher.matches());

Output : true

两行验证

StringBuilder builder = new StringBuilder();
builder.append("Test Line 1");
builder.append("\n");
builder.append("Test Line 2");
builder.append("\n");

String regex = "\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z";

Pattern pattern = Pattern.compile(regex);

Matcher matcher = pattern.matcher(builder.toString());
System.out.println(matcher.matches());

Output : true

六行验证

StringBuilder builder = new StringBuilder();
builder.append("Test Line 1");
builder.append("\n");
builder.append("Test Line 2");
builder.append("\n");
builder.append("Test Line 3");
builder.append("\n");
builder.append("Test Line 4");
builder.append("\n");
builder.append("Test Line 5");
builder.append("\n");
builder.append("Test Line 6");
builder.append("\n");

String regex = "\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z";

Pattern pattern = Pattern.compile(regex);

Matcher matcher = pattern.matcher(builder.toString());
System.out.println(matcher.matches());

Output : false

我建议您使用上述简单的正则表达式尝试更多的变化。

祝您学习愉快!

32 位 Java 与 64 位 Java 之间的区别

原文: https://howtodoinjava.com/java/basics/difference-between-32-bit-java-vs-64-bit-java/

在计算机架构中, 64 位计算是使用具有 64 位(8 个八位位组/字节)的数据路径宽度,整数大小和内存地址宽度的处理器。 同样,64 位 CPU 和 ALU 架构是基于该大小的寄存器,地址总线或数据总线的架构。 从软件的角度来看,64 位计算意味着使用具有 64 位虚拟内存地址的代码。 类似地, 32 位计算,CPU 或 32 位编程将 32 位(四个八位位组/字节)用于上述所有目的。

如果转到 java 下载页面,它会列出各种安装包,其中提及针对各种平台(例如 Linux 或 Windows)的 32 位包或 64 位包。很多时候,我们担心哪些包可以在系统中下载并安装,以使我们的 Java 代码正常运行? 在这篇文章中,我将尝试阐明这些不同的术语,并且还将尝试回答一些明显的问题。

Discussion Points

Understanding 32-bit architecture in detail
How 64-bit architecture is different?
Which versions of java you should install on 32-bit/64-bit machines?
Can a .class file generated using a 32-bit java compiler be used on 64-bit java?
What's maximum amount of RAM that will be allocated to java on a 32-bit machine vs. 64-bit machine?

您已经阅读了 64 位和 32 位计算/架构之间的基本区别。 现在,让我们加深理解,深入了解比特和字节。

详细了解 32 位架构

您可能已经知道,在任何 32 位操作系统中,都被限制为 4096 MB(4 GB)的 RAM 。 这很简单,因为 32 位值的大小将不允许在内存中添加更多引用。

2 ^ 32 = 4,294,967,296,即约 4.29 GB

因此,理论上,在 32 位系统中,每个进程最多可以分配 4GB 的内存。 在 Windows 上打破这一点的是如何处理进程地址空间。 Windows 将进程地址空间减少一半。 其中一半保留给操作系统(用户进程无法使用),另一半保留给用户。 盒中有多少 RAM 无关紧要,一个 32 位进程只能使用 2GB RAM。 更糟糕的是 – 地址空间必须是连续的,因此实际上,在 Windows 计算机上通常只剩下 1.5-1.8GB 的堆空间。

精通技术的读者可能知道,现代芯片支持 PAE ,这是一种处理器技术,它允许操作系统使用更多的内存(最大为 64 GB),但它也需要特殊的应用程序支持,大多数应用程序没有或不一定需要它。

Windows 的 4 GB 限制至少也是许可的因素。 32 位 Windows 的家庭版本在技术上能够支持 PAE,但出于许可和驱动程序兼容性方面的考虑,硬限制为 4 GB。 我要指出“ 驱动程序兼容性原因”,因为某些使用本地文件(例如防病毒软件)的特定应用程序是专门为 32 位/ 64 位计算机构建的,而本机文件不与其他机器兼容

要记住的另一件事是,您的 BIOS 和主板中的其他设备芯片(例如视频卡)也占用相同的 4 GB 空间中的一些内存,因此可供您的应用程序使用的实际内存进一步减少到大约 1.5 GB。

64 位架构有何不同?

虽然 32 位信息只能访问 4 GB 的 RAM,但至少在理论上, 64 位计算机可以访问 172 亿 GB 的系统内存。 因此,它必须消除系统中所有内存消耗的障碍,对吗? 但事实并非如此。

Windows 64 位家庭版仍然限制为 16 GB RAM(全部是出于许可原因),但是由于各种兼容性问题,专业版和旗舰版目前最多可以使用 192 GB RAM。

RAM 的每个进程限制也大大提高了 -- 在 64 位 Windows 上,而不是 2 GB 限制,每个应用程序可以访问高达 8 TB 的虚拟内存,而无需任何特殊配置(必须存在于您的系统中)。 当考虑可能需要使用大量 RAM 的视频编辑或虚拟机等应用程序时,这是选择下一台计算机的重要因素。

因此,现在我们对 32 位计算机和 64 位计算机有了很好的了解。 让我们关注与 Java 主要相关的内容。

您应该在 32 位/ 64 位计算机上安装哪个版本的 Java?

严格来说,在 32 位 CPU 架构计算机上,应该安装 32 位 Java / JRE。 另一方面,在 64 位 CPU 架构计算机上,您可以自由选择在 32 位 Java / JRE 和 64 位 Java / JRE 之间。 两者都可以正常工作。 实际上,在 64 位计算机上,JRE 版本的决定取决于其他因素,例如在高负载情况下运行应用程序所需的最大内存。

请注意,高可用内存并非免费提供。 它确实会花费运行时间,例如

1)与 32 位相比,在 64 位上需要更多 30-50% 的堆。 为什么? 主要是由于 64 位架构中的内存布局。 首先 – 在 64 位 JVM 上,对象标头是 12 个字节。 其次,对象引用可以是 4 个字节,也可以是 8 个字节,具体取决于 JVM 标志和堆的大小。 与 32 位标头上的 8 个字节和引用标本上的 4 个字节相比,这无疑增加了一些开销。

2)较长的垃圾收集暂停。 建立更多的堆意味着 GC 在清除未使用的对象时还有更多工作要做。 在现实生活中,这意味着在构建大于 12-16GB 的堆时,您必须格外小心。 如果不进行微调和测量,则很容易在几分钟内引入完整的 GC 暂停,这可能会导致显示停止。

使用 32 位 Java 编译器生成的.class文件可以在 64 位 Java 上使用吗?

绝对是。 Java 字节码独立于 32 位或 64 位系统。这就是为什么编译的 Java 代码应在“任何”系统上可执行的原因。 请记住,因为虚拟机是打包在捆绑包中的一些本机文件,所以它只是为特殊的系统架构而编译的,而本机文件从不独立于平台。

如果是,那么 32 位应用程序如何在 64 位系统上运行?答案是 64 位系统包含一个称为 WoW64 的兼容层,实际上可以在 32 位和 64 位之间来回切换处理器的位模式,取决于需要执行的线程; 使 32 位软件即使在 64 位环境中也能平稳运行。

在 32 位计算机和 64 位计算机上,可分配给 Java 的最大 RAM 数量是多少?

我们已经在本文的前面的讨论中了解到两个版本都允许的限制。 在 64 位系统上,理论上对于今天可用的任何配置(172 亿 GB 内存),其限制都非常高。 供应商仍然出于各种目的施加限制,主要包括许可和与其他本机应用程序的兼容性。

同样,在 32 位计算机上,限制为 4 GB,出于上述原因,用户应用程序实际上仅可使用约 1.5 GB。

您可以使用 32 位窗口来减少内核空间并增加用户空间,这是一个技巧。 您可以在boot.ini中使用/3GB参数。 但是,要实际利用此机会,必须使用/LARGEADDRESSAWARE开关来编译/链接 JVM。

不幸的是,至少对于 Hotspot JVM 并非如此。 直到最新的 JDK 版本,才使用此选项编译 JVM。 如果您在 2006 年后版本的 jRockit 上运行,则更加幸运。 在这种情况下,您可以享受高达 2.8-2.9 GB 的堆大小。

仅此而已。 如果不清楚,请发表评论。 或者你只是不同意我。

祝您学习愉快!

参考:

限制输入中的单词数的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-validate-limit-the-number-of-words-in-input/

在此 Java 正则表达式教程中,我们将学习测试输入文本中的单词数是否在最小和最大限制之间

以下正则表达式与的先前教程非常相似,它限制了非空白字符的数量,不同之处在于每次重复都匹配整个单词而不是单个非空白字符。 它匹配 2 到 10 个单词,跳过所有非单词字符,包括标点和空格:

正则表达式:^\\W*(?:\\w+\\b\\W*){2,10}$

List<String> inputs = new ArrayList<String>();

inputs.add("LOKESH");  //Incorrect
inputs.add("JAVA CRAZY");  
inputs.add("LOKESH GUPTA INDIA");  
inputs.add("test whether number of words in input text is between some minimum and maximum limit");  //Incorrect

String regex = "^\\W*(?:\\w+\\b\\W*){2,10}$";

Pattern pattern = Pattern.compile(regex);

for (String input : inputs)
{
    Matcher matcher = pattern.matcher(input);
    System.out.println(matcher.matches());
}

Output:

false
true
true
false

我建议您使用上述简单的正则表达式尝试更多的变化。

祝您学习愉快!

验证 SSN(社会安全号码)的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-validate-social-security-numbers-ssn/

在此 java 正则表达式教程中,我们将学习使用正则表达式来测试用户是否在您的应用程序或网站表单中输入了有效的社会安全号码。

有效的 SSN 编号格式

美国社会安全号码是九位数字,格式为AAA-GG-SSSS,并具有以下规则。

  • 前三位数字称为区域号。 区域号不能为 000、666 或 900 到 999 之间。
  • 数字 4 和 5 称为组号,范围从 01 到 99。
  • 后四位数字是从 0001 到 9999 的序列号。

为了验证以上 3 条规则,我们的正则表达式为:

正则表达式:^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$

验证 SSN 正则表达式的说明

^            # Assert position at the beginning of the string.
(?!000|666)  # Assert that neither "000" nor "666" can be matched here.
[0-8]        # Match a digit between 0 and 8.
[0-9]{2}     # Match a digit, exactly two times.
-            # Match a literal "-".
(?!00)       # Assert that "00" cannot be matched here.
[0-9]{2}     # Match a digit, exactly two times.
-            # Match a literal "-".
(?!0000)     # Assert that "0000" cannot be matched here.
[0-9]{4}     # Match a digit, exactly four times.
$            # Assert position at the end of the string.

现在,我们使用一些演示 SSN 编号测试我们的 SSN 验证正则表达式。

List<String> ssns = new ArrayList<String>();

//Valid SSNs
ssns.add("123-45-6789");  
ssns.add("856-45-6789");  

//Invalid SSNs
ssns.add("000-45-6789");  
ssns.add("666-45-6789");  
ssns.add("901-45-6789");  
ssns.add("85-345-6789"); 
ssns.add("856-453-6789"); 
ssns.add("856-45-67891"); 
ssns.add("856-456789"); 

String regex = "^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$";

Pattern pattern = Pattern.compile(regex);

for (String number : ssns)
{
	Matcher matcher = pattern.matcher(number);
	System.out.println(matcher.matches());
}

Output:

true
true

false
false
false
false
false
false
false

我建议您使用上述简单的正则表达式尝试更多的变化。

学习愉快!

Java 正则表达式 – 英国邮政编码验证

原文: https://howtodoinjava.com/regex/uk-postcode-validation/

在此 java 正则表达式教程中,我们将学习使用正则表达式来验证特定于英国的邮政编码。 您也可以修改正则表达式以使其适合任何其他格式。

1. 什么是有效的英国邮政编码

英国的邮递区号(或邮递区号)由 5 到 7 个字母数字字符组成,中间用空格隔开。 这两个部分是外向代码和内向代码。

外部代码包括邮政编码区域和邮政编码区域。 内向代码包括邮政编码扇区和邮政编码单元。

邮政编码的示例包括“SW1W 0NY”,“PO16 7GZ”,“GU16 7HF”或“L1 8JQ”。

覆盖哪些字符可以出现在特定位置的规则并不复杂,并且充满了异常情况。 因此,此处给出的正则表达式仅遵循基本规则。

正则表达式^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}$

要检查英国邮政编码上的验证规则,请遵循此维基百科页面。

2. 英国邮政编码验证示例

List<String> zips = new ArrayList<String>();

//Valid ZIP codes
zips.add("SW1W 0NY");  
zips.add("PO16 7GZ");  
zips.add("GU16 7HF");  
zips.add("L1 8JQ");  

//Invalid ZIP codes
zips.add("Z1A 0B1");
zips.add("A1A 0B11");

String regex = "^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}$";

Pattern pattern = Pattern.compile(regex);

for (String zip : zips)
{
	Matcher matcher = pattern.matcher(zip);
	System.out.println(matcher.matches());
}

Output:

true
true
true
true

false
false

随意提出与上述英国邮政编码验证示例有关的问题。

学习愉快!

Java 正则表达式 – 美国邮政编码验证

原文: https://howtodoinjava.com/regex/us-postal-zip-code-validation/

在此 Java 正则表达式教程中,我们将学习使用正则表达式来验证美国邮政编码。 您也可以修改正则表达式以使其适合任何其他格式。

1. 有效的美国邮政编码模式

美国邮政编码(美国邮政编码)允许五位数和九位数(称为ZIP + 4)格式。

例如。 有效的邮政编码应匹配1234512345-6789,但不能匹配12341234561234567891234-56789

正则表达式:^[0-9]{5}(?:-[0-9]{4})?$

^           # Assert position at the beginning of the string.
[0-9]{5}    # Match a digit, exactly five times.
(?:         # Group but don't capture:
  -         #   Match a literal "-".
  [0-9]{4}  #   Match a digit, exactly four times.
)           # End the non-capturing group.
  ?         #   Make the group optional.
$           # Assert position at the end of the string.

2. 美国邮政编码验证示例

List<String> zips = new ArrayList<String>();

//Valid ZIP codes
zips.add("12345");  
zips.add("12345-6789");  

//Invalid ZIP codes
zips.add("123456");  
zips.add("1234");  
zips.add("12345-678");

zips.add("12345-67890");

String regex = "^[0-9]{5}(?:-[0-9]{4})?$";

Pattern pattern = Pattern.compile(regex);

for (String zip : zips)
{
	Matcher matcher = pattern.matcher(zip);
	System.out.println(matcher.matches());
}

Output:

true
true

false
false
false
false

那很容易,对吗? 向我提供有关如何使用正则表达式验证美国邮政编码的问题。

学习愉快!

验证商标符号的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-match-trademark-symbol/

在此 java 正则表达式示例中,我们将学习匹配商标符号

Unicode 代码点U+2122代表“商标符号”字符。 您可以将其与“\u2122”,“\u{2122}”或“\x{2122}”匹配,具体取决于您使用的正则表达式。

解决方案 Java 正则表达式:\u2122

String data1 = "Searching in trademark character ™ is so easy when you know it.";

  String regex = "\u2122";

  Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
  Matcher matcher = pattern.matcher(data1);
  while (matcher.find())
  {
     System.out.print("Start index: " + matcher.start());
     System.out.print(" End index: " + matcher.end() + " ");
     System.out.println(matcher.group());
  }
}

Output:

Start index: 33 End index: 34 ™

祝您学习愉快!

验证国际电话号码的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-validate-international-phone-numbers/

在本正则表达式教程中,我们将学习根据 ITU-T E.123 指定的行业标准符号来验证国际电话号码

全球范围内用于打印国际电话号码的规则和约定千差万别,因此除非您采用严格的格式,否则很难为国际电话号码提供有意义的验证。 幸运的是,ITU-T E.123 指定了一种简单的行业标准符号。 此符号要求国际电话号码包含一个加号(称为国际前缀符号),并且只能使用空格分隔数字组。

同样,由于国际电话编号计划(ITU-T E.164),电话号码不能包含超过 15 位的数字。 使用的最短国际电话号码包含七个数字。

1. 验证国际电话号码的正则表达式

正则表达式:^\+(?:[0-9] ?){6,14}[0-9]$

^ # Assert position at the beginning of the string.
 \+ # Match a literal "+" character.
 (?: # Group but don't capture:
 [0-9] # Match a digit.
 \\s # Match a space character
 ? # between zero and one time.
 ) # End the noncapturing group.
 {6,14} # Repeat the group between 6 and 14 times.
 [0-9] # Match a digit.
 $ # Assert position at the end of the string.

以上正则表达式可用于验证基于 ITU-T 标准的国际电话号码。 让我们看一个例子。

List phoneNumbers = new ArrayList();
phoneNumbers.add("+1 1234567890123");
phoneNumbers.add("+12 123456789");
phoneNumbers.add("+123 123456");

String regex = "^\\+(?:[0-9] ?){6,14}[0-9]$";

Pattern pattern = Pattern.compile(regex);

for(String email : phoneNumbers)
{
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

Output:

+1 1234567890123 : true
+12 123456789 : true
+123 123456 : true

2. 验证 EPP 格式的国际电话号码

此正则表达式遵循可扩展配置协议(EPP)指定的国际电话号码符号。 EPP 是一个相对较新的协议(于 2004 年最终确定),旨在用于域名注册机构和注册商之间的通信。 越来越多的域名注册机构使用它,包括.com.info.net.org.us。 这样做的意义在于,越来越多的人使用和认可 EPP 样式的国际电话号码,因此提供了一种很好的替代格式来存储(和验证)国际电话号码。

EPP 样式的电话号码使用+CCC.NNNNNNNNNNxEEEE格式,其中C是 1-3 位国家/地区代码,N最多 14 位数字,E是(可选)扩展名。 国家/地区代码后必须加上加号和加点。 仅在提供扩展名时才需要字面“x”字符。

正则表达式:^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$

List phoneNumbers = new ArrayList();
phoneNumbers.add("+123.123456x4444");
phoneNumbers.add("+12.1234x11");
phoneNumbers.add("+1.123456789012x123456789");

String regex = "^\\+[0-9]{1,3}\\.[0-9]{4,14}(?:x.+)?$";

Pattern pattern = Pattern.compile(regex);

for(String email : phoneNumbers)   
{
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

Output:

+123.123456x4444 : true
+12.1234x11 : true
+1.123456789012x123456789 : true

您可以随时在正则表达式上方进行编辑并使用它来匹配更严格的电话号码格式。

学习愉快!

北美电话号码的 Java 正则表达式

原文: https://howtodoinjava.com/regex/java-regex-validate-and-format-north-american-phone-numbers/

在此正则表达式教程中,我们将学习验证用户输入的电话号码是否为特定格式(在此示例中为北美格式),如果号码正确,则将其重新格式化为标准格式以进行显示。 我测试了以下格式,包括1234567890123-456-7890123.456.7890123 456 7890(123) 456 7890以及所有此类组合。

使用正则表达式验证北美电话号码

正则表达式:^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$

以上正则表达式可用于验证电话号码的所有格式,以检查它们是否为有效的北美电话号码。

List phoneNumbers = new ArrayList();
phoneNumbers.add("1234567890");
phoneNumbers.add("123-456-7890");
phoneNumbers.add("123.456.7890");
phoneNumbers.add("123 456 7890");
phoneNumbers.add("(123) 456 7890");

//Invalid phone numbers
phoneNumbers.add("12345678");
phoneNumbers.add("12-12-111");

String regex = "^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$";

Pattern pattern = Pattern.compile(regex);

for(String email : phoneNumbers)
{
	Matcher matcher = pattern.matcher(email);
	System.out.println(email +" : "+ matcher.matches());
}

Output:

1234567890 : 		true
123-456-7890 : 		true
123.456.7890 : 		true
123 456 7890 : 		true
(123) 456 7890 : 	true

12345678 : 			false
12-12-111 : 		false

使用正则表达式格式化北美电话号码

正则表达式:($1) $2-$3

使用上面的正则表达式来重新格式化在上述步骤中验证过的电话号码,以便以一致的方式重新格式化以存储/显示目的。

List phoneNumbers = new ArrayList();
phoneNumbers.add("1234567890");
phoneNumbers.add("123-456-7890");
phoneNumbers.add("123.456.7890");
phoneNumbers.add("123 456 7890");
phoneNumbers.add("(123) 456 7890");

//Invalid phone numbers
phoneNumbers.add("12345678");
phoneNumbers.add("12-12-111");

String regex = "^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$";

Pattern pattern = Pattern.compile(regex);

for(String email : phoneNumbers)
{
	Matcher matcher = pattern.matcher(email);
	//System.out.println(email +" : "+ matcher.matches());
	//If phone number is correct then format it to (123)-456-7890
	if(matcher.matches())
	{
	   System.out.println(matcher.replaceFirst("($1) $2-$3"));
	}
}

Output:

(123) 456-7890
(123) 456-7890
(123) 456-7890
(123) 456-7890
(123) 456-7890

上面的正则表达式也可以在 Java 脚本中工作。 因此,下次需要它们时,请务必将它们放在方便的地方。

祝您学习愉快!

Java NIO 教程

NIO 教程

原文: https://howtodoinjava.com/java-nio-tutorials/

Java NIO(新 IO)是 Java 的替代 IO API(来自 Java 1.4),意味着可以替代标准 Java IO API。 与标准 IO API 相比,Java NIO 提供了另一种使用 IO 的方式。 在此页面中,我将列出此博客中与 NIO 相关的所有可用帖子。

java nio

学习 NIO 的先决条件

Java I/O 在较低级别内部如何工作?

这篇博客文章主要讨论与 I/O 相关的事物在较低级别的工作方式。 这篇文章供那些想知道如何在机器级别映射 Java I/O 操作的读者使用; 以及您的应用程序在运行时,硬件在所有时间内的所有工作。 我假设您熟悉基本的 IO 操作,例如读取文件,通过 Java I/O API 写入文件; 因为那超出了这篇文章的范围。

标准 IO 和 NIO 之间的差异

在本教程中,我将专注于确定最明显的区别,在决定在下一个项目中使用哪个区别之前,您必须知道这些区别。

NIO 基础知识

如何在 Java NIO 中定义路径

如果您的应用程序使用 NIO,则应了解有关此类中可用特性的更多信息。 在本教程中,我列出了在 NIO 中创建Path的 6 种方法。

NIO 缓冲区

缓冲区类是构建java.nio的基础。 在本教程中,我们将仔细研究缓冲区,发现各种类型,并学习如何使用它们。 然后,我们将了解java.nio缓冲区与java.nio.channels的通道类之间的关系。

NIO 通道

通道是继缓冲区之后的java.nio的第二项重大创新,我们在我之前的教程中已详细了解到。 通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通常是文件或套接字)之间有效传输数据的介质。 通常,通道与操作系统文件描述符具有一对一的关系。 通道类提供了维持平台独立性所需的抽象,但仍可以对现代操作系统的本机 I/O 能力进行建模。 通道是网关,通过它可以以最小的开销访问操作系统的本机 I/O 服务,而缓冲区是通道用来发送和接收数据的内部端点。

如何在应用程序中使用 NIO

使用 NIO 逐行读取文件

在本文中,我将举例说明一个非常有用的日常编程任务,即使用 Java IO 逐行读取文件并执行一些行操作。 在继续之前,请允许我提及本文中所有示例中将要阅读的文件内容。

我将逐行读取文件的内容,并检查是否有任何行包含单词"password",然后进行打印。

3 种使用 Java NIO 读取文件的方法

在这篇文章中,我展示了几种从文件系统读取文件的方法。

如何在通道之间传输数据?

与通常在输入源和输出目标之间发生 IO 的普通 Java 应用程序一样,在 NIO 中,您也可能需要非常频繁地将数据从一个通道传输到另一通道。 文件数据从一个地方到另一个地方的批量传输非常普遍,以至于FileChannel类添加了两种优化方法,以使其效率更高。

让我们了解这些方法。

内存映射文件和MappedByteBuffer

内存映射的 I/O 使用文件系统来建立从用户空间直接到适用文件系统页面的虚拟内存映射。 使用内存映射文件,您可以假装整个文件都在内存中,并且可以通过将其视为一个非常大的数组来访问它。 这种方法极大地简化了您为修改文件而编写的代码。

分散/收集或向量 IO

从通道读取的散射是将数据读取到多个缓冲区中的读取操作。因此,通道将来自通道的数据分散到多个缓冲区中。对通道的聚集写入是一种将来自多个缓冲区的数据写入单个通道的写入操作。因此,通道将来自多个缓冲器的数据收集到一个通道中。 在需要分别处理传输数据的各个部分的情况下,分散/收集可能非常有用。

参考:

http://docs.oracle.com/javase/tutorial/essential/io/fileio.html

如何创建路径 – Java NIO

原文: https://howtodoinjava.com/java7/nio/how-to-define-path-in-java-nio/

众所周知,Java SE 7 发行版中引入的Path类是java.nio.file包的主要入口点之一。 如果您的应用程序使用 NIO,则应了解有关此类中可用特性的更多信息。 我开始NIO 教程,并定义 NIO 2 中的路径

java nio

在本教程中,我列出了在 NIO 中创建Path的 6 种方法。

注意:我正在为以下位置的文件构建路径-“C:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt”。 我已经事先创建了此文件,并将在以下示例中创建此文件的路径。

Section in this post:

Define absolute path
Define path relative to file store root
Define path relative to current working directory
Define path from URI scheme
Define path using file system default
Using System.getProperty() to build path

让我们一一看一下以上所有技术的示例代码:

定义绝对路径

绝对路径始终包含根元素和查找文件所需的完整目录列表。 不再需要更多信息来访问文件或路径。 我们将使用带有以下签名的getPath()方法。

/**
* Converts a path string, or a sequence of strings that when joined form a path string,
* to a Path. If more does not specify any elements then the value of the first parameter
* is the path string to convert. If more specifies one or more elements then each non-empty
* string, including first, is considered to be a sequence of name elements and is
* joined to form a path string.
*/
public static Path get(String first, String… more);

让我们看下面的代码示例。

//Starts with file store root or drive
Path absolutePath1 = Paths.get("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
Path absolutePath2 = Paths.get("C:/Lokesh/Setup/workspace", "NIOExamples/src", "sample.txt");
Path absolutePath3 = Paths.get("C:/Lokesh", "Setup/workspace", "NIOExamples/src", "sample.txt");

定义相对于文件存储根的路径

相对于文件存储根的路径以正斜杠(“/”)字符开头。

//How to define path relative to file store root (in windows it is c:/)
Path relativePath1 = FileSystems.getDefault().getPath("/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
Path relativePath2 = FileSystems.getDefault().getPath("/Lokesh", "Setup/workspace/NIOExamples/src", "sample.txt");

定义相对于当前工作目录的路径

要定义相对于当前工作目录的 NIO 路径,请不要同时使用文件系统根目录(在 Windows 中为c:/)或斜杠(“/”)。

//How to define path relative to current working directory
Path relativePath1 = Paths.get("src", "sample.txt");

定义 URI 方案的路径

并不经常,但是有时我们可能会遇到这样的情况,我们想将格式为“file:///src/someFile.txt”的文件路径转换为 NIO 路径。 让我们来看看如何做。

        //URI uri = URI.create("file:///c:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt"); //OR
	URI uri = URI.create("file:///Lokesh/Setup/workspace/NIOExamples/src/sample.txt");

	String scheme =  uri.getScheme();
	if (scheme == null)
		throw new IllegalArgumentException("Missing scheme");

	//Check for default provider to avoid loading of installed providers
	if (scheme.equalsIgnoreCase("file"))
	{
		System.out.println(FileSystems.getDefault().provider().getPath(uri).toAbsolutePath().toString());
	}

	//If you do not know scheme then use this code. This code check file scheme as well if available.
	for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
		if (provider.getScheme().equalsIgnoreCase(scheme)) {
			System.out.println(provider.getPath(uri).toAbsolutePath().toString());
			break;
		}
	}

使用文件系统默认值定义路径

这是上述示例的另一种变体,其中可以使用FileSystems.getDefault().getPath()方法代替使用Paths.get()
绝对路径和相对路径的规则与上述相同。

FileSystem fs = FileSystems.getDefault();
Path path1 = fs.getPath("src/sample.txt");
Path path2 = fs.getPath("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");

使用System.getProperty()构建路径

好吧,这是不可能的,但是很高兴知道。 您还可以使用系统特定的System.getProperty()来构建特定文件的路径。

Path path1 = FileSystems.getDefault().getPath(System.getProperty("user.home"), "downloads", "somefile.txt");

因此,这是创建 NIO 路径的 6 种方法。 让我们合并并运行它们以检查其输出。

package com.howtodoinjava.nio;

import static java.nio.file.FileSystems.getDefault;

import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.spi.FileSystemProvider;

public class WorkingWithNIOPath 
{
	public static void main(String[] args)
	{
		 defineAbsolutePath();
		 defineRelativePathToRoot();
		 defineRelativePathToWorkingFolder();
		 definePathFromURI();
		 UsingFileSystemGetDefault();
		 UsingSystemProperty();
	}

	//Starts with file store root or drive
	private static void defineAbsolutePath() 
	{
		//Starts with file store root or drive
		Path absolutePath1 = Paths.get("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
		Path absolutePath2 = Paths.get("C:/Lokesh/Setup/workspace", "NIOExamples/src", "sample.txt");
		Path absolutePath3 = Paths.get("C:/Lokesh", "Setup/workspace", "NIOExamples/src", "sample.txt");

		System.out.println(absolutePath1.toString());
		System.out.println(absolutePath2.toString());
		System.out.println(absolutePath3.toString());
	}

	//Starts with a "/"
	private static void defineRelativePathToRoot() 
	{
		//How to define path relative to file store root (in windows it is c:/)
		Path relativePath1 = FileSystems.getDefault().getPath("/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
		Path relativePath2 = FileSystems.getDefault().getPath("/Lokesh", "Setup/workspace/NIOExamples/src", "sample.txt");

		System.out.println(relativePath1.toAbsolutePath().toString());
		System.out.println(relativePath2.toAbsolutePath().toString());
	}

	//Starts without a "/"
	private static void defineRelativePathToWorkingFolder() 
	{
		//How to define path relative to current working directory
		Path relativePath1 = Paths.get("src", "sample.txt");
		System.out.println(relativePath1.toAbsolutePath().toString());
	}

	private static void definePathFromURI() 
	{
		//URI uri = URI.create("file:///c:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt"); //OR
		URI uri = URI.create("file:///Lokesh/Setup/workspace/NIOExamples/src/sample.txt");

		String scheme =  uri.getScheme();
        if (scheme == null)
            throw new IllegalArgumentException("Missing scheme");

        //check for default provider to avoid loading of installed providers
        if (scheme.equalsIgnoreCase("file"))
            System.out.println(FileSystems.getDefault().provider().getPath(uri).toAbsolutePath().toString());

        // try to find provider
        for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
            if (provider.getScheme().equalsIgnoreCase(scheme)) {
            	System.out.println(provider.getPath(uri).toAbsolutePath().toString());
            	break;
            }
        }
	}

	private static void UsingFileSystemGetDefault() {
		FileSystem fs = getDefault();
		Path path1 = fs.getPath("src/sample.txt");
		Path path2 = fs.getPath("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");

		System.out.println(path1.toAbsolutePath().toString());
		System.out.println(path2.toAbsolutePath().toString());
	}

	private static void UsingSystemProperty() {
		 Path path1 = FileSystems.getDefault().getPath(System.getProperty("user.home"), "downloads", "somefile.txt");
		 System.out.println(path1.toAbsolutePath().toString());
	}

}

Output in console:

****defineAbsolutePath****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****defineRelativePathToRoot****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****defineRelativePathToWorkingFolder****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****definePathFromURI****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****UsingFileSystemGetDefault****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****UsingSystemProperty****
C:Usershug13902downloadssomefile.txt

祝您学习愉快!

java.exejavaw.exe之间的区别

原文: https://howtodoinjava.com/java/basics/difference-between-java-exe-and-javaw-exe/

java.exe”和“javaw.exe”都是 Windows 平台上的 Java 可执行文件。 这些文件几乎是 Java 应用程序加载器工具的相同版本。 两种版本的启动器都采用相同的参数和选项。 启动器通过“java”或“javaw”调用,后跟启动器选项,类或 Java 归档(JAR)文件名和应用程序参数。

javaw.exe

应用程序启动器的非控制台版本通常用于通过图形用户界面(GUI)启动 Java 应用程序。 这些应用程序具有带有菜单,按钮和其他交互元素的窗口。 本质上,当您不希望出现命令提示符窗口以进行进一步输入或显示输出时,请使用javaw.exe

但是,如果由于某种原因启动 Java 应用程序失败,则javaw.exe启动器将显示一个对话框,其中包含错误信息。

java.exe

java.exejavaw.exe非常相似。 启动器的控制台版本用于具有基于文本的界面或输出文本的应用程序。 使用“java”启动的任何应用程序都将导致命令行等待应用程序响应,直到关闭为止。

使用javaw启动时,应用程序启动,并且命令行立即退出并为下一条命令做好准备。

这只是java.exejavaw.exe之间的区别。 如果您知道其他明显的区别,请与我们所有人分享。

祝您学习愉快!

使用缓冲区 – Java NIO 2.0

原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-working-with-buffers/

缓冲区类是构建java.nio的基础。 在本教程中,我们将仔细研究缓冲区,发现各种类型,并学习如何使用它们。 然后,我们将了解java.nio缓冲区与java.nio.channels的通道类之间的关系。 我们将在下一个教程中探索 NIO 通道。

Table Of Contents

Buffer Attributes
Creating Buffers
Working With Buffers
	Accessing
	Filling
	Flipping
	Draining
	Compacting
	Marking 
	Comparing
	Bulk Data Movement
Duplicating Buffers
Some Examples Using Buffers

Buffer对象可以称为固定数据量的容器。 它充当存储箱或临时暂存区,可以在其中存储数据并在以后检索。 缓冲带通道的手套。 通道是进行 I/O 传输的实际门户。 缓冲区是这些数据传输的源或目标。 对于向外传输,要将要发送的数据放在缓冲区中,该缓冲区将传递到输出通道。 对于向内传输,通道将数据存储在您提供的缓冲区中,然后将数据从缓冲区复制到通道中。 协作对象之间缓冲区的移交是在 NIO API 下有效处理数据的关键。

Buffer类专业化层次结构中,顶部是通用Buffer类。 缓冲区定义所有缓冲区类型共有的操作,而不管它们包含的数据类型或它们可能具有的特殊行为。

java nio buffer classes

缓冲区属性

从概念上讲,缓冲区是包装在对象内部的原始数据元素的数组。 与简单数组相比,Buffer类的优点是它将数据内容和有关数据(即元数据)的信息封装到单个对象中。 所有缓冲区都具有四个属性,这些属性提供有关所包含数据元素的信息。 这些是:

  1. 容量:缓冲区可容纳的最大数据元素数。 容量是在创建缓冲区时设置的,无法更改。
  2. 限制:不应读取或写入的缓冲区的第一个元素。 换句话说,缓冲区中活动元素的数量。
  3. 位置:要读取或写入的下一个元素的索引。 该位置由相对的get()put()方法自动更新。
  4. 标记:记忆位置。 调用mark()设置标记等于位置。 调用reset()设置位置等于标记。 该标记在设置之前是不确定的。

这四个属性之间的以下关系始终成立:

0 <= mark <= position <= limit <= capacity

下图是容量为 10 的新创建的ByteBuffer的逻辑视图。位置设置为 0,容量和限制设置为 10,恰好超过缓冲区可以容纳的最后一个字节。 该标记最初是未定义的。

Newly created buffer attributes

创建缓冲区

正如我们在上面看到的,有七个主要的缓冲区类,对于 Java 语言中的每个非布尔原始类型数据类型一个。 最后一个是MappedByteBuffer,它是用于内存映射文件的ByteBuffer的一种特殊形式。 这些类都不能直接实例化。 它们都是抽象类,但是每个都包含静态工厂方法来创建相应类的新实例。

通过分配或包装来创建新的缓冲区。 分配将创建一个缓冲区对象,并分配私有空间来容纳容量数据元素。 包装会创建一个缓冲区对象,但不会分配任何空间来容纳数据元素。 它使用您提供的数组作为后备存储来保存缓冲区的数据元素。

例如,要分配一个可容纳 100 个字符的CharBuffer

CharBuffer charBuffer = CharBuffer.allocate (100);

这从堆中隐式分配了一个char数组,以充当 100 个char的后备存储。 如果要提供自己的数组用作缓冲区的后备存储,请调用wrap()方法:

char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);

这意味着通过调用put()对缓冲区所做的更改将反映在数组中,而直接对数组所做的任何更改将对缓冲区对象可见。

您还可以根据您提供的偏移量和长度值构造一个带有位置和限制设置的缓冲区。 例如

char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray , 12, 42);

上面的语句将创建一个CharBuffer,其位置为 12,限制为 54,容量为myArray.length,即 100。

此方法不会创建仅占用数组子范围的缓冲区。 缓冲区将有权访问数组的整个范围。 offsetlength参数仅设置初始状态。 在以此方式创建的缓冲区上调用clear(),然后将其填充到其限制将覆盖数组的所有元素。 但是,slice()方法可以产生仅占用后备数组一部分的缓冲区。

allocate()wrap()创建的缓冲区始终是非直接的,即它们具有支持数组。 布尔方法hasArray()告诉您缓冲区是否具有可访问的后备数组。 如果返回true,则array()方法将返回对缓冲区对象使用的数组存储的引用。 如果hasArray()返回false,则不要调用array()arrayOffset()。 如果这样做,您会得到UnsupportedOperationException

使用缓冲区

现在,让我们看看如何使用Buffer API 提供的方法与缓冲区进行交互。

访问缓冲区

据我们了解,缓冲区管理固定数量的数据元素。 但是在任何给定时间,我们可能只关心缓冲区中的某些元素。 也就是说,在我们想耗尽缓冲区之前,可能只填充了一部分缓冲区。 我们需要一些方法来跟踪已添加到缓冲区中的数据元素的数量,下一个元素的放置位置等。

为了访问 NIO 中的缓冲区,每个缓冲区类都提供get()put()方法。 例如:

public abstract class ByteBuffer extends Buffer implements Comparable
{
        // This is a partial API listing
        public abstract byte get();
        public abstract byte get (int index);
        public abstract ByteBuffer put (byte b);
        public abstract ByteBuffer put (int index, byte b);
}

在这些方法的背面,位置属性位于中心。 它指示调用put()时应在下一个数据元素插入的位置,或调用get()时应从中检索下一个元素的位置。

获取和放置可以是相对的或绝对的。 相对版本是不带索引参数的版本。 调用相对方法时,返回时该位置加 1。 如果位置前进太远,相对操作可能会引发异常。 对于put(),如果该操作将导致位置超出限制,则将抛出BufferOverflowException。 对于get(),如果位置不小于限制,则抛出BufferUnderflowException。 绝对访问不会影响缓冲区的位置,但是如果您提供的索引超出范围(负数或不小于限制),则可能会抛出java.lang.IndexOutOfBoundsException

填充缓冲区

要了解如何使用put()方法填充缓冲区,请看以下示例。 下图表示使用put()方法将字母Hello推入缓冲区后的缓冲区状态。

char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray , 12, 42);
buffer.put('H').put('e').put('l').put('l').put('o');

Filling buffer

现在我们已经有一些数据保存在缓冲区中,如果我们想进行一些更改而不丢失位置该怎么办? put()的绝对版本允许我们这样做。 假设我们要将缓冲区的内容从Hello的 ASCII 等效值更改为Mellow。 我们可以这样做:

buffer.put(0, 'M').put('w');

这样做是绝对的,用十六进制值0x4D替换位置 0 处的字节,将0x77放置在当前位置的字节中(不受绝对put()的影响),然后将该位置加 1。

Again filled buffer

翻转缓冲区

我们已经填充了缓冲区,现在必须准备将其耗尽。 我们希望将此缓冲区传递给通道,以便可以读取内容。 但是,如果通道现在在缓冲区上执行get(),则它将获取未定义的数据,因为position属性当前指向空白点。

如果将位置重新设置为 0,通道将在正确的位置开始提取,但是如何知道何时到达插入数据的末尾? 这是limit属性进入的地方。limit指示活动缓冲区内容的结尾。 我们需要将限制设置为当前位置,然后将位置重置为 0。我们可以使用以下代码手动进行操作:

buffer.limit( buffer.position() ).position(0);

Flipping the Buffer

或者,您可以使用flip()方法。 flip()方法将缓冲区从可以附加数据元素的填充状态翻转到耗尽状态,以准备读取元素

buffer.flip();

再一种方法rewind()方法类似于flip(),但不影响限制。 仅将位置设置回 0。 您可以使用rewind()返回并重新读取已翻转的缓冲区中的数据。如果两次翻转缓冲区该怎么办? 它实际上变为零大小。 对缓冲区应用与上述相同的步骤,即将限制设置为位置并将位置设置为 0。 限制和位置都变为 0。 在位置和限制为 0 的缓冲区上尝试get()会导致BufferUnderflowExceptionput()会导致BufferOverflowException(现在限制为零)。

清空缓冲区

根据我们在翻转时阅读的逻辑,如果您收到一个在其他地方填充的缓冲区,则可能需要先翻转它,然后再检索内容。 例如,如果channel.read()操作已完成,并且您想查看通道放置在缓冲区中的数据,则需要在调用buffer.get()之前翻转缓冲区。 请注意,通道对象在内部调用缓冲区上的put()以添加数据,即channel.read()操作。

接下来,您可以使用两种方法hasRemaining()remaining()来确定排水时是否已达到缓冲区的限制。 以下是将元素从缓冲区转移到数组的方法。

for (int i = 0; buffer.hasRemaining(), i++) 
{
	myByteArray [i] = buffer.get();
}

/////////////////////////////////

int count = buffer.remaining(  );
for (int i = 0; i > count, i++) 
{
	myByteArray [i] = buffer.get();
}

缓冲区不是线程安全的。 如果要从多个线程同时访问给定的缓冲区,则需要进行自己的同步。

一旦缓冲区被填充和清空,就可以重新使用它。 clear()方法将缓冲区重置为空状态。 它不会更改缓冲区的任何数据元素,而只是将限制设置为容量,并将位置重新设置为 0。这样就可以再次填充缓冲区了。

填充和清空缓冲区的完整示例如下:

import java.nio.CharBuffer;

public class BufferFillDrain
{
    public static void main (String [] argv)
        throws Exception
    {
        CharBuffer buffer = CharBuffer.allocate (100);

        while (fillBuffer (buffer)) {
            buffer.flip(  );
            drainBuffer (buffer);
            buffer.clear();
        }
    }

    private static void drainBuffer (CharBuffer buffer)
    {
        while (buffer.hasRemaining()) {
            System.out.print (buffer.get());
        }

        System.out.println("");
    }

    private static boolean fillBuffer (CharBuffer buffer)
    {
        if (index >= strings.length) {
            return (false);
        }

        String string = strings [index++];

        for (int i = 0; i > string.length(  ); i++) {
            buffer.put (string.charAt (i));
        }

        return (true);
    }

    private static int index = 0;

    private static String [] strings = {
        "Some random string content 1",
        "Some random string content 2",
        "Some random string content 3",
        "Some random string content 4",
        "Some random string content 5",  
        "Some random string content 6",
    };
}

压缩缓冲区

有时,您可能希望从缓冲区中清空部分而非全部数据,然后恢复填充。 为此,需要将未读数据元素下移,以使第一个元素的索引为零。 如果重复执行此操作可能会效率低下,但有时是有必要的,API 提供了一种compact()方法来为您执行此操作。

buffer.compact();

您可以通过这种方式将缓冲区用作先进先出(FIFO)队列。 当然存在更有效的算法(缓冲区移位不是执行队列的非常有效的方法),但是压缩可能是将缓冲区与从套接字读取的流中的逻辑数据块(数据包)进行同步的便捷方法。

请记住,如果要在压缩后清空缓冲区内容,则需要翻转缓冲区。 无论随后是否将任何新数据元素添加到缓冲区中,这都是事实。

标记缓冲区

如文章开头所述,属性“标记”允许缓冲区记住位置并稍后返回。 在调用mark()方法之前,缓冲区的标记是不确定的,此时标记设置为当前位置reset()方法将位置设置为当前标记。 如果标记未定义,则调用reset()将产生InvalidMarkException如果设置了某个缓冲方法,某些缓冲方法将丢弃该标记(rewind()clear()flip()始终丢弃该标记)。 如果设置的新值小于当前标记,则调用带有索引参数的limit()position()版本会丢弃该标记。

注意不要混淆reset()clear()clear()方法使缓冲区为空,而reset()将位置返回到先前设置的标记。

比较缓冲区

有时有必要将一个缓冲区中的数据与另一个缓冲区中的数据进行比较。 所有缓冲区均提供用于测试两个缓冲区的相等性的自定义equals()方法和用于比较缓冲区的compareTo()方法:

可以使用以下代码测试两个缓冲区的相等性:

if (buffer1.equals (buffer2)) {
        doSomething();
}

如果每个缓冲区的剩余内容相同,则equals()方法返回true;否则,返回false。当且仅当以下情况,才认为两个缓冲区相等:

  • 这两个对象是同一类型。 包含不同数据类型的缓冲区永远不相等,并且任何Buffer都不等于非Buffer对象。
  • 两个缓冲区具有相同数量的剩余元素。 缓冲区容量不必相同,缓冲区中剩余数据的索引也不必相同。 但是每个缓冲区中剩余的元素数量(从位置到限制)必须相同。
  • get()返回的剩余数据元素的顺序在每个缓冲区中必须相同。

如果这些条件中的任何一个不成立,则返回false

缓冲区还通过compareTo()方法支持字典比较。 如果buffer参数分别小于,等于或大于在其上调用了compareTo()的对象实例,则此方法将返回一个负数,零或正数的整数。 这些是java.lang.Comparable接口的语义,所有类型的缓冲区都实现这些语义。 这意味着可以通过调用java.util.Arrays.sort()根据缓冲区的内容对缓冲区数组进行排序。

equals()一样,compareTo()不允许在不同对象之间进行比较。 但是compareTo()更为严格:如果传入错误类型的对象,它将抛出ClassCastException,而equals()只会返回false

以与equals()相同的方式,对每个缓冲区的其余元素执行比较,直到找到不等式或达到任一缓冲区的限制为止。 如果在发现不等式之前耗尽了一个缓冲区,则认为较短的缓冲区要小于较长的缓冲区。 与equals()不同,compareTo()不是可交换的:顺序很重要。

if (buffer1.compareTo (buffer2) > 0) {
        doSomething();
}

缓冲区中的批量数据移动

缓冲区的设计目标是实现有效的数据传输。 一次移动一个数据元素不是很有效。 因此,Buffer API 提供了用于将数据元素大量移入或移出缓冲区的方法。

例如,CharBuffer类提供了以下用于批量数据移动的方法。

public abstract class CharBuffer
        extends Buffer implements CharSequence, Comparable
{
        // This is a partial API listing

        public CharBuffer get (char [] dst)
        public CharBuffer get (char [] dst, int offset, int length)

        public final CharBuffer put (char[] src)
        public CharBuffer put (char [] src, int offset, int length)
        public CharBuffer put (CharBuffer src)

        public final CharBuffer put (String src)
        public CharBuffer put (String src, int start, int end)
}

get()有两种形式,用于将数据从缓冲区复制到数组。 第一个仅将数组作为参数,将缓冲区排入给定的数组。 第二个参数使用offsetlength参数来指定目标数组的子范围。 使用这些方法代替循环可能会更有效,因为缓冲区实现可能会利用本机代码或其他优化来移动数据。

批量传输始终为固定大小。 省略长度意味着将填充整个数组。 即“buffer.get(myArray)”等于“buffer.get(myArray, 0, myArray.length)”。

如果请求的元素数量无法传输,则不会传输任何数据,缓冲区状态保持不变,并抛出BufferUnderflowException。 如果缓冲区中至少没有足够的元素来完全填充数组,则会出现异常。 这意味着,如果要将小型缓冲区传输到大型数组中,则需要显式指定缓冲区中剩余数据的长度。

要将缓冲区耗尽到更大的数组中,请执行以下操作:

char [] bigArray = new char [1000];

// Get count of chars remaining in the buffer
int length = buffer.remaining(  );

// Buffer is known to contain > 1,000 chars
buffer.get (bigArrray, 0, length);

// Do something useful with the data
processData (bigArray, length);

另一方面,如果缓冲区中存储的数据量超出了数组中的数据量,则可以使用以下代码对数据进行迭代和分块提取:

char [] smallArray = new char [10];

while (buffer.hasRemaining()) {
        int length = Math.min (buffer.remaining(  ), smallArray.length);

        buffer.get (smallArray, 0, length);
        processData (smallArray, length);
}

put()的批量版本的行为类似,但是将数据从数组移到缓冲区的方向相反。 关于转移量,它们具有相似的语义。 因此,如果缓冲区有足够的空间接受数组中的数据(buffer.remaining() >= myArray.length),则数据将从当前位置开始复制到缓冲区中,并且缓冲区的位置将增加所添加数据元素的数量。 如果缓冲区中没有足够的空间,则不会传输任何数据,并且会抛出BufferOverflowException

通过以缓冲区引用作为参数调用put(),也可以将数据从一个缓冲区批量转移到另一个缓冲区:

dstBuffer.put (srcBuffer);

两个缓冲区的位置将提前传输的数据元素数量。 范围检查与数组一样进行。 具体来说,如果srcBuffer.remaining()大于dstBuffer.remaining(),则不会传输任何数据,并且将抛出BufferOverflowException。 如果您想知道,如果您将缓冲区传递给自身,则会收到大而胖的java.lang.IllegalArgumentException

复制缓冲区

缓冲区不限于管理数组中的外部数据。 他们还可以从外部在其他缓冲区中管理数据。 创建用于管理另一个缓冲区中包含的数据元素的缓冲区时,它被称为视图缓冲区

始终通过在现有缓冲区实例上调用方法来创建视图缓冲区。 在现有缓冲区实例上使用工厂方法意味着视图对象将专有于原始缓冲区的内部实现细节。 无论将数据元素存储在数组中还是通过其他方式,它都将能够直接访问这些数据元素,而无需通过原始缓冲区对象的get()/put() API。

可以对任何主要缓冲区类型执行以下操作:

public abstract CharBuffer duplicate();
public abstract CharBuffer asReadOnlyBuffer();
public abstract CharBuffer slice();

duplicate()方法创建一个类似于原始缓冲区的新缓冲区。 两个缓冲区共享数据元素并具有相同的容量,但是每个缓冲区将具有自己的位置,限制和标记。 对一个缓冲区中的数据元素所做的更改将反映在另一个缓冲区中。 复制缓冲区与原始缓冲区具有相同的数据视图。 如果原始缓冲区是只读缓冲区或直接缓冲区,则新缓冲区将继承这些属性。

您可以使用asReadOnlyBuffer()方法制作缓冲区的只读视图。 除了新缓冲区将不允许put()且其isReadOnly()方法将返回true之外,这与plicate()相同。 尝试在只读缓冲区上调用put()将抛出ReadOnlyBufferException

如果只读缓冲区与可写缓冲区共享数据元素,或者由包装的数组支持,则对可写缓冲区或直接对数组所做的更改将反映在所有关联的缓冲区中,包括只读缓冲区。

分割缓冲区类似于复制,但是slice()创建一个新缓冲区,该缓冲区从原始缓冲区的当前位置开始,其容量为原始缓冲区中剩余的元素数(限制-位置)​​。 切片缓冲区还将继承只读和直接属性。

CharBuffer buffer = CharBuffer.allocate(8);
buffer.position (3).limit(5);
CharBuffer sliceBuffer = buffer.slice();

Slicing a buffer

类似地,要创建一个映射到预先存在的数组的位置 12-20(九个元素)的缓冲区,可以使用如下代码:

char [] myBuffer = new char [100];
CharBuffer cb = CharBuffer.wrap (myBuffer);
cb.position(12).limit(21);
CharBuffer sliced = cb.slice();

一些使用缓冲区的例子

使用ByteBuffer创建字符串

import java.nio.ByteBuffer;
import java.nio.CharBuffer;

public class FromByteBufferToString 
{
	public static void main(String[] args) 
	{

		// Allocate a new non-direct byte buffer with a 50 byte capacity
		// set this to a big value to avoid BufferOverflowException
		ByteBuffer buf = ByteBuffer.allocate(50);

		// Creates a view of this byte buffer as a char buffer
		CharBuffer cbuf = buf.asCharBuffer();

		// Write a string to char buffer
		cbuf.put("How to do in java");

		// Flips this buffer. The limit is set to the current position and then
		// the position is set to zero. If the mark is defined then it is
		// discarded
		cbuf.flip();

		String s = cbuf.toString(); // a string

		System.out.println(s);
	}
}

使用FileChannel和间接缓冲区复制文件

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileCopyUsingFileChannelAndBuffer 
{
	public static void main(String[] args) 
	{
		String inFileStr = "screen.png";
		String outFileStr = "screen-out.png";
		long startTime, elapsedTime; 
		int bufferSizeKB = 4;
		int bufferSize = bufferSizeKB * 1024;

		// Check file length
		File fileIn = new File(inFileStr);
		System.out.println("File size is " + fileIn.length() + " bytes");
		System.out.println("Buffer size is " + bufferSizeKB + " KB");
		System.out.println("Using FileChannel with an indirect ByteBuffer of " + bufferSizeKB + " KB");

		try (	FileChannel in = new FileInputStream(inFileStr).getChannel();
				FileChannel out = new FileOutputStream(outFileStr).getChannel()	) 
		{
			// Allocate an indirect ByteBuffer
			ByteBuffer bytebuf = ByteBuffer.allocate(bufferSize);

			startTime = System.nanoTime();

			int bytesCount = 0;
			// Read data from file into ByteBuffer
			while ((bytesCount = in.read(bytebuf)) > 0) { 
				// flip the buffer which set the limit to current position, and position to 0.
				bytebuf.flip();
				out.write(bytebuf); // Write data from ByteBuffer to file
				bytebuf.clear(); // For the next read
			}

			elapsedTime = System.nanoTime() - startTime;
			System.out.println("Elapsed Time is " + (elapsedTime / 1000000.0) + " msec");
		} 
		catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

如果您在文章中有不清楚的地方或错误的地方,请随时发表您的看法。

祝您学习愉快!

Java 通道教程 – NIO 2.0

原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-channels/

通道是继缓冲区之后的java.nio的第二项重大创新,我们在我之前的教程中已详细了解到。 通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通常是文件或套接字)之间有效传输数据的介质。 通常,通道与操作系统文件描述符具有一对一的关系。 通道类提供了维持平台独立性所需的抽象,但仍可以对现代操作系统的本机 I/O 能力进行建模。 通道是网关,通过它可以以最小的开销访问操作系统的本机 I/O 服务,而缓冲区是通道用来发送和接收数据的内部端点。

NIO 通道基础

在层次结构的顶部,有Channel接口,如下所示:

package java.nio.channels;

public interface Channel
{
	public boolean isOpen();
	public void close() throws IOException;
}

由于依赖底层平台的各种因素,Channel实现在操作系统之间存在根本性的差异,因此通道 API(或接口)仅描述了可以完成的工作。 Channel实现通常使用本机代码执行实际工作。 通过这种方式,通道接口使您能够以受控且可移植的方式访问低级 I/O 服务。

从顶级Channel接口可以看到,所有通道只有两个共同的操作:检查通道是否打开(isOpen())和关闭打开的通道(close())。

打开通道

众所周知,I/O 分为两大类:文件 I/O 和流 I/O 。 因此,通道有两种类型也就不足为奇了:文件和套接字。 FileChannel类和SocketChannel类用于处理这两个类别。

仅可以通过在打开的RandomAccessFileFileInputStreamFileOutputStream对象上调用getChannel()方法来获得FileChannel对象。 您不能直接创建FileChannel对象。

RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel();

FileChannel相反,套接字通道具有工厂方法来直接创建新的套接字通道。 例如

//How to open SocketChannel
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("somehost", someport));

//How to open ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind (new InetSocketAddress (somelocalport));

//How to open DatagramChannel
DatagramChannel dc = DatagramChannel.open();

上面的方法返回一个相应的套接字通道对象。 它们不是RandomAccessFile.getChannel()的新通道的来源。 如果已经存在,则它们返回与套接字关联的通道。 他们从不创造新的渠道。

使用通道

正如我们已经学习到缓冲区的教程一样,该教程介绍了通道与ByteBuffer对象之间的数据传输。 大多数读/写操作由从下面的接口实现的方法执行。

public interface ReadableByteChannel extends Channel
{
        public int read (ByteBuffer dst) throws IOException;
}

public interface WritableByteChannel extends Channel
{
        public int write (ByteBuffer src) throws IOException;
}

public interface ByteChannel extends ReadableByteChannel, WritableByteChannel
{
}

通道可以是单向或双向。 给定的通道类可能实现ReadableByteChannel,它定义了read()方法。 另一个可能会实现WritableByteChannel以提供write()。 实现这些接口中的一个或另一个的类是单向的:它只能在一个方向上传输数据。 如果一个类实现两个接口(或扩展两个接口的ByteChannel),则它是双向的,可以双向传输数据。

如果您查看通道类,则会发现每个文件和套接字通道都实现了这三个接口。 就类定义而言,这意味着所有文件和套接字通道对象都是双向的。 对于套接字来说,这不是问题,因为它们始终是双向的,但是对于文件来说,这是一个问题。 从FileInputStream对象的getChannel()方法获得的FileChannel对象是只读的,但是在接口声明方面是双向的,因为 FileChannel实现了ByteChannel 。 在这样的通道上调用write()会抛出未选中的NonWritableChannelException,因为FileInputStream总是打开具有只读权限的文件。 因此,请记住,当通道连接到特定的 I/O 服务时,通道实例的特性将受到与其连接的服务的特性的限制。 连接到只读文件的Channel实例无法写入,即使该Channel实例所属的类可能具有write()方法。 程序员要知道如何打开通道,而不要尝试执行底层 I/O 服务不允许的操作。

FileInputStream input = new FileInputStream ("readOnlyFile.txt");
FileChannel channel = input.getChannel();

// This will compile but will throw an IOException 
// because the underlying file is read-only
channel.write (buffer);

ByteChannelread()write()方法将ByteBuffer对象作为参数。 每个都返回传输的字节数,该字节数可以小于缓冲区中的字节数,甚至为零。 缓冲区的位置将增加相同的量。 如果执行了部分传输,则可以将缓冲区重新提交给通道以继续传输数据,并从该处停止传输。 重复直到缓冲区的hasRemaining()方法返回false

在下面的示例中,我们将数据从一个通道复制到另一个通道(或从一个文件复制到另一个文件):

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

public class ChannelCopyExample
{
	public static void main(String args[]) throws IOException 
	{
		FileInputStream input = new FileInputStream ("testIn.txt");
		ReadableByteChannel source = input.getChannel();

		FileOutputStream output = new FileOutputStream ("testOut.txt");
		WritableByteChannel dest = output.getChannel();

		copyData(source, dest);

		source.close();
		dest.close();
	}

	private static void copyData(ReadableByteChannel src, WritableByteChannel dest) throws IOException 
	{
		ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);

		while (src.read(buffer) != -1) 
		{
			// Prepare the buffer to be drained
			buffer.flip();

			// Make sure that the buffer was fully drained
			while (buffer.hasRemaining()) 
			{
				dest.write(buffer);
			}

			// Make the buffer empty, ready for filling
			buffer.clear();
		}
	}
}

通道可以在阻塞或非阻塞模式下运行。 处于非阻塞模式的通道永远不会使调用线程进入睡眠状态。 所请求的操作要么立即完成,要么返回结果表明未完成任何操作。 只能将面向流的通道(例如套接字和管道)置于非阻塞模式。

关闭通道

要关闭通道,请使用其close()方法。 与缓冲区不同,通道在关闭后不能重新使用。 开放通道表示与特定 I/O 服务的特定连接,并封装了该连接的状态。 关闭通道后,该连接将丢失,并且该通道不再连接任何东西。

在通道上多次调用close()是没有害处的。 随后在关闭通道上对close()的调用无济于事,并立即返回。

可以想到,套接字通道可能需要大量时间才能关闭,具体取决于系统的网络实现。 某些网络协议栈可能会在输出耗尽时阻止关闭。

通道的打开状态可以使用isOpen()方法进行测试。 如果返回true,则可以使用该通道。 如果为false,则该通道已关闭,无法再使用。 尝试读取,写入或执行任何其他需要通道处于打开状态的操作将导致ClosedChannelException

祝您学习愉快!

3 种读取文件的方法 – Java NIO

原文: https://howtodoinjava.com/java7/nio/3-ways-to-read-files-using-java-nio/

新的 I/O,通常称为 NIO ,是一组 API,它们为密集的 I/O 操作提供附加能力。 它是由 Sun 微系统的 Java 1.4 发行版引入的,以补充现有的标准 I/O。 随 Java SE 7(“海豚”)一起发布的扩展 NIO 提供了进一步的新文件系统 API,称为 NIO2。

java 面试 中,与 NIO 相关的问题非常流行。

NIO2 提供了两种主要的读取文件的方法:

  • 使用缓冲区和通道类
  • 使用路径和文件类

在这篇文章中,我展示了几种从文件系统读取文件的方法。 因此,让我们从首先展示旧的著名方法入手,以便我们可以看到真正的变化。

古老的著名 I/O 方式

此示例说明我们如何使用旧的 I/O 库 API 读取文本文件。 它使用BufferedReader对象进行读取。 另一种方法可以使用InputStream实现。

package com.howtodoinjava.test.nio;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class WithoutNIOExample
{
	public static void main(String[] args)
	{
		BufferedReader br = null;
		String sCurrentLine = null;
		try
		{
			br = new BufferedReader(
			new FileReader("test.txt"));
			while ((sCurrentLine = br.readLine()) != null)
			{
				System.out.println(sCurrentLine);
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				if (br != null)
				br.close();
			} catch (IOException ex)
			{
				ex.printStackTrace();
			}
		}
	}
}

1)在文件大小的缓冲区中读取一个小文件

package com.howtodoinjava.test.nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ReadFileWithFileSizeBuffer
{
	public static void main(String args[])
	{
		try 
		{
			RandomAccessFile aFile = new RandomAccessFile(
							"test.txt","r");
			FileChannel inChannel = aFile.getChannel();
			long fileSize = inChannel.size();
			ByteBuffer buffer = ByteBuffer.allocate((int) fileSize);
			inChannel.read(buffer);
			//buffer.rewind();
			buffer.flip();
			for (int i = 0; i < fileSize; i++)
			{
				System.out.print((char) buffer.get());
			}
			inChannel.close();
			aFile.close();
		} 
		catch (IOException exc)
		{
			System.out.println(exc);
			System.exit(1);
		}
	}
}

2)使用固定大小的缓冲区分块读取大文件

package com.howtodoinjava.test.nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ReadFileWithFixedSizeBuffer 
{
	public static void main(String[] args) throws IOException 
	{
		RandomAccessFile aFile = new RandomAccessFile
				("test.txt", "r");
	    FileChannel inChannel = aFile.getChannel();
	    ByteBuffer buffer = ByteBuffer.allocate(1024);
	    while(inChannel.read(buffer) > 0)
	    {
	    	buffer.flip();
	    	for (int i = 0; i < buffer.limit(); i++)
			{
				System.out.print((char) buffer.get());
			}
	    	buffer.clear(); // do something with the data and clear/compact it.
	    }
	    inChannel.close();
	    aFile.close();
	}
}

3)使用映射的字节缓冲区的更快的文件复制

package com.howtodoinjava.test.nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class ReadFileWithMappedByteBuffer 
{
	public static void main(String[] args) throws IOException 
	{
		RandomAccessFile aFile = new RandomAccessFile
				("test.txt", "r");
	    FileChannel inChannel = aFile.getChannel();
	    MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
	    buffer.load();	
    	for (int i = 0; i < buffer.limit(); i++)
		{
			System.out.print((char) buffer.get());
		}
	    buffer.clear(); // do something with the data and clear/compact it.
	    inChannel.close();
	    aFile.close();
	}
}

所有上述技术将读取文件的内容并将其打印到控制台。 阅读后,您可以做任何您想做的事情。

祝您学习愉快!

Java 8 – 逐行读取文件

原文: https://howtodoinjava.com/java8/read-file-line-by-line/

在本 Java 8 教程中,学习使用流 API 逐行读取文件。 另外,还要学习遍历行并根据某些条件过滤文件内容。

1. Java 8 读取文件 – 逐行

在此示例中,我将读取文件内容为stream,并一次读取每一行,并检查其中是否包含单词"password"

Path filePath = Paths.get("c:/temp", "data.txt");

//try-with-resources
try (Stream<String> lines = Files.lines( filePath )) 
{
	lines.forEach(System.out::println);
} 
catch (IOException e) 
{
	e.printStackTrace();
}

上面的程序输出将在控制台中逐行打印文件的内容。

Never
store
password
except
in mind.

2. Java 8 读取文件 – 过滤行流

在此示例中,我们将文件内容读取为行流。 然后,我们将过滤掉所有带有单词"password"的行。

Path filePath = Paths.get("c:/temp", "data.txt");

try (Stream<String> lines = Files.lines(filePath)) 
{

	 List<String> filteredLines = lines
	 				.filter(s -> s.contains("password"))
	 				.collect(Collectors.toList());

	 filteredLines.forEach(System.out::println);

} 
catch (IOException e) {

	e.printStackTrace();
}

程序输出。

password

我们将读取给定文件的内容,并检查是否有任何一行包含单词"password",然后将其打印出来。

3. Java 7 – 使用FileReader读取文件

到 Java 7 为止,我们可以通过FileReader以各种方式读取文件。

private static void readLinesUsingFileReader() throws IOException 
{
    File file = new File("c:/temp/data.txt");

    FileReader fr = new FileReader(file);
    BufferedReader br = new BufferedReader(fr);

    String line;
    while((line = br.readLine()) != null)
    {
        if(line.contains("password")){
            System.out.println(line);
        }
    }
    br.close();
    fr.close();
}

这就是 Java 逐行读取文件示例的全部。 请在评论部分提出您的问题。

学习愉快!

Java 内存映射文件 – Java MappedByteBuffer

原文: https://howtodoinjava.com/java7/nio/memory-mapped-files-mappedbytebuffer/

了解 Java 内存映射文件,并借助RandomAccessFileMemoryMappedBuffer从内存映射文件中读取和写入内容。

1. 内存映射的 IO

如果您知道 Java IO 在较低级别的工作方式,那么您将了解缓冲区处理,内存分页和其他此类概念。 对于传统的文件 I/O,用户进程在其中发出read()write()系统调用来传输数据,几乎总是存在一个或多个复制操作,以在内核空间中的这些文件系统页面和内存中的内存区域之间移动数据。 用户空间。 这是因为文件系统页面和用户缓冲区之间通常没有一对一的对齐方式。

但是,大多数操作系统都支持一种特殊类型的 I/O 操作,它允许用户进程最大程度地利用系统 I/O 的面向页面的特性,并完全避免缓冲区复制。 这称为内存映射的 I/O ,我们将在此处学习有关内存映射文件的一些知识。

2. Java 内存映射文件

内存映射的 I/O 使用文件系统来建立从用户空间直接到适用文件系统页面的虚拟内存映射。 使用内存映射文件,我们可以假装整个文件都在内存中,并且可以通过简单地将其视为非常大的数组来访问它。 这种方法极大地简化了我们为修改文件而编写的代码。

阅读更多信息:使用缓冲区

为了在内存映射文件中进行写入和读取,我们从RandomAccessFile开始,获取该文件的通道。 存储器映射的字节缓冲区是通过FileChannel.map()方法创建的。 此类通过特定于内存映射文件区域的操作扩展了ByteBuffer类。

映射的字节缓冲区及其表示的文件映射将保持有效,直到缓冲区本身被垃圾回收为止。 请注意,必须指定文件中要映射的区域的起点和长度。 这意味着您可以选择映射大文件的较小区域。

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MemoryMappedFileExample 
{
	static int length = 0x8FFFFFF; 	

	public static void main(String[] args) throws Exception 
	{
		try(RandomAccessFile file = new RandomAccessFile("howtodoinjava.dat", "rw")) 
		{
			MappedByteBuffer out = file.getChannel()
										.map(FileChannel.MapMode.READ_WRITE, 0, length);

			for (int i = 0; i < length; i++) 
			{
				out.put((byte) 'x');
			}

			System.out.println("Finished writing");
		}
	}
}

使用上述程序创建的文件的长度为 128 MB,可能大于操作系统允许的空间。 该文件似乎可以一次访问,因为只有部分文件被带入内存,而其他部分被换出。 这样,可以轻松地修改非常大的文件(最大 2 GB)。

文件模式

像常规文件句柄一样,文件映射可以是可写的或只读的。

  • 前两个映射模式MapMode.READ_ONLY** 和 **MapMode.READ_WRITE相当明显。 它们指示您是要映射为只读映射还是允许修改映射文件。
  • 第三种模式MapMode.PRIVATE表示您需要写时复制映射。 这意味着您通过put()进行的任何修改都将导致只有MappedByteBuffer实例可以看到的数据的私有副本。 不会对基础文件进行任何更改,并且对缓冲区进行垃圾回收时所做的任何更改都将丢失。 即使写时复制映射阻止了对基础文件的任何更改,您也必须已打开文件以进行读/写以设置MapMode.PRIVATE映射。 这对于返回的MappedByteBuffer对象允许put()是必需的。

您会注意到,没有unmap()方法。 建立后,映射将一直有效,直到MappedByteBuffer对象被垃圾回收为止。 同样,映射缓冲区不绑定到创建它们的通道。 关闭关联的FileChannel不会破坏映射。 仅处理缓冲区对象本身会破坏映射。

MemoryMappedBuffer具有固定大小,但映射到的文件是弹性的。 具体来说,如果在映射生效时文件大小发生变化,则部分或全部缓冲区可能变得不可访问,可能会返回未定义的数据,或者会引发未经检查的异常。 内存映射文件时,请注意其他线程或外部进程如何处理文件。

3. 内存映射文件的好处

与常规 I/O 相比,内存映射 IO 具有以下优点:

  1. 用户进程将文件数据视为内存,因此无需发出read()write()系统调用。
  2. 当用户进程触摸映射的内存空间时,将自动生成页面错误,以从磁盘引入文件数据。 如果用户修改了映射的内存空间,则受影响的页面会自动标记为脏页面,随后将刷新到磁盘以更新文件。
  3. 操作系统的虚拟内存子系统将执行页面的智能缓存,并根据系统负载自动管理内存。
  4. 数据总是页面对齐的,不需要缓冲区复制。
  5. 可以映射非常大的文件,而无需消耗大量内存来复制数据。

4. 如何读取内存映射文件

要使用内存映射的 IO 读取文件,请使用以下代码模板:

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MemoryMappedFileReadExample 
{
	private static String bigExcelFile = "bigFile.xls";

	public static void main(String[] args) throws Exception 
	{
		try (RandomAccessFile file = new RandomAccessFile(new File(bigExcelFile), "r"))
		{
			//Get file channel in read-only mode
			FileChannel fileChannel = file.getChannel();

	        //Get direct byte buffer access using channel.map() operation
	        MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());

	        // the buffer now reads the file as if it were loaded in memory. 
	        System.out.println(buffer.isLoaded()); 	//prints false
	        System.out.println(buffer.capacity());	//Get the size based on content size of file

	        //You can read the file from this buffer the way you like.
	        for (int i = 0; i < buffer.limit(); i++)
	        {
	            System.out.print((char) buffer.get()); //Print the content of file
	        }
		}
	}
}

5. 如何写入内存映射文件

要使用内存映射的 IO 将数据写入文件,请使用以下代码模板:

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MemoryMappedFileWriteExample {
	private static String bigTextFile = "test.txt";

	public static void main(String[] args) throws Exception 
	{
		// Create file object
		File file = new File(bigTextFile);

		//Delete the file; we will create a new file
		file.delete();

		try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"))
		{
			// Get file channel in read-write mode
			FileChannel fileChannel = randomAccessFile.getChannel();

			// Get direct byte buffer access using channel.map() operation
			MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096 * 8 * 8);

			//Write the content using put methods
			buffer.put("howtodoinjava.com".getBytes());
		}
	}
}

在评论部分中将您的评论和想法留给我。

学习愉快!

Java NIO – 分散/聚集或向量 IO

原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-scatter-gather-or-vectored-io/

通道提供了一项重要的新特性,称为分散/聚集(在某些圈子中称为向量 I/O)。 分散/聚集是一个简单而强大的概念。 分散/聚集是一种技术,通过该技术,可以通过一次read()调用将字节从流中读取到一组缓冲区(向量)中,并且可以通过一次write()调用,将字节从一组缓冲区中写入到流中。大多数现代操作系统都支持本机向量 I/O。 当您请求通道上的分散/聚集操作时,该请求将转换为适当的本机调用以直接填充或耗尽缓冲区。 这是一个巨大的胜利,因为减少了或消除了缓冲区副本和系统调用。 直到最近,Java 还没有执行向量 I/O 操作的能力。 因此,我们习惯于将字节直接读取到单个字节数组中,或者进行多次读取以获取数据。

分散/聚集 API

从通道读取的散射是将数据读取到多个缓冲区中的读取操作。因此,通道将来自通道的数据分散到多个缓冲区中。对通道的聚集写入是一种将来自多个缓冲区的数据写入单个通道的写入操作。因此,通道将来自多个缓冲器的数据聚集到一个通道中。在需要分别处理传输数据的各个部分的情况下,分散/聚集可能非常有用。

public interface ScatteringByteChannel extends ReadableByteChannel
{
	public long read (ByteBuffer [] dsts) throws IOException;
	public long read (ByteBuffer [] dsts, int offset, int length) throws IOException;
}

public interface GatheringByteChannel extends WritableByteChannel
{
	public long write(ByteBuffer[] srcs) throws IOException;
	public long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
}

您可以看到每个接口都添加了两个新方法,这些新方法将缓冲区数组作为参数。

现在,让我们写一个简单的例子来了解如何使用此特性。

分散/聚集 IO 示例

在此示例中,我创建了两个缓冲区。 一个缓冲区将存储一个随机数,另一个缓冲区将存储一个随机字符串。 我将使用GatheringByteChannel读写文件通道中两个缓冲区中存储的数据。 然后,我将使用ScatteringByteChannel将文件中的数据读回到两个单独的缓冲区中,并在控制台中打印内容以验证存储和检索的数据是否匹配。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;

public class ScatteringAndGatheringIOExample 
{
	public static void main(String params[]) 
	{
		String data = "Scattering and Gathering example shown in howtodoinjava.com";

		gatherBytes(data);
		scatterBytes();
	}

	/*
	 * gatherBytes() reads bytes from different buffers and writes to file
	 * channel. Note that it uses a single write for both the buffers.
	 */
	public static void gatherBytes(String data) 
	{
		//First Buffer holds a random number
		ByteBuffer bufferOne = ByteBuffer.allocate(4);

		//Second Buffer holds data we want to write
		ByteBuffer buffer2 = ByteBuffer.allocate(200);

		//Writing Data sets to Buffer
		bufferOne.asIntBuffer().put(13);
		buffer2.asCharBuffer().put(data);

		//Calls FileOutputStream(file).getChannel()
		GatheringByteChannel gatherer = createChannelInstance("test.txt", true);

		//Write data to file
		try 
		{
			gatherer.write(new ByteBuffer[] { bufferOne, buffer2 });
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	/*
	 * scatterBytes() read bytes from a file channel into a set of buffers. Note that
	 * it uses a single read for both the buffers.
	 */
	public static void scatterBytes() 
	{
		//First Buffer holds a random number
		ByteBuffer bufferOne = ByteBuffer.allocate(4);

		//Second Buffer holds data we want to write
		ByteBuffer bufferTwo = ByteBuffer.allocate(200);

		//Calls FileInputStream(file).getChannel()
		ScatteringByteChannel scatterer = createChannelInstance("test.txt", false);

		try 
		{
			//Reading from the channel
			scatterer.read(new ByteBuffer[] { bufferOne, bufferTwo });
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}

		//Read the buffers seperately
		bufferOne.rewind();
		bufferTwo.rewind();

		int bufferOneContent = bufferOne.asIntBuffer().get();
		String bufferTwoContent = bufferTwo.asCharBuffer().toString();

		//Verify the content
		System.out.println(bufferOneContent);
		System.out.println(bufferTwoContent);
	}

	public static FileChannel createChannelInstance(String file, boolean isOutput) 
	{
		FileChannel fc = null;
		try 
		{
			if (isOutput) {
				fc = new FileOutputStream(file).getChannel();
			} else {
				fc = new FileInputStream(file).getChannel();
			}
		} 
		catch (Exception e) {
			e.printStackTrace();
		}
		return fc;
	}
}

总结

正确使用分散/聚集工具可能会非常强大。 它使您可以将将读取的数据分离到多个存储桶中或将不同的数据块组装成一个整体的繁琐工作委托给操作系统。 这是一个巨大的胜利,因为操作系统已针对此类事物进行了高度优化。 它节省了您四处移动的工作,从而避免了缓冲区复制,并减少了编写和调试所需的代码量。 由于基本上是通过提供对数据容器的引用来组装数据,因此可以通过以不同组合构建多个缓冲区引用数组来以不同的方式来组装各种块。

祝您学习愉快!

通道之间的数据传输 – Java NIO

原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-how-to-transfer-copy-data-between-channels/

与通常在输入源和输出目标之间发生 IO 的普通 Java 应用程序一样,在 NIO 中,您也可能需要非常频繁地将数据从一个通道传输到另一通道。 文件数据从一个地方到另一个地方的批量传输非常普遍,以至于FileChannel类添加了两种优化方法,以使其效率更高。

FileChannel.transferTo()FileChannel.transferFrom()方法

transferTo()transferFrom()方法使您可以将一个通道交叉连接到另一个通道,而无需通过中间缓冲区传递数据。 这些方法仅在FileChannel类上存在,因此,在通道间传输中涉及的通道之一必须是FileChannel

public abstract class FileChannel
        extends AbstractChannel
        implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
{
        // There are more other methods
        public abstract long transferTo (long position, long count, WritableByteChannel target);
        public abstract long transferFrom (ReadableByteChannel src, long position, long count);
}

您无法在套接字通道之间进行直接传输,但是套接字通道实现了WritableByteChannelReadableByteChannel,因此可以使用transferTo()将文件的内容传输到套接字,或者可以使用transferFrom()从套接字直接读取数据到文件中。

另外,请记住,如果在传输过程中遇到问题,这些方法可能会抛出java.io.IOException

阅读更多: NIO 通道

通道之间的传输可能非常快,尤其是在底层操作系统提供本机支持的情况下。 某些操作系统可以执行直接传输,而无需在用户空间中传递数据。 对于大量数据传输而言,这可能是一个巨大的胜利。

通道间数据传输示例

在此示例中,我从 3 个不同的文件中读取文件内容,并将它们的合并输出写入第四个文件。

package com.howtodoinjava.nio;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

public class ChannelTransferExample 
{
	public static void main(String[] argv) throws Exception 
	{
		//Input files
		String[] inputFiles = new String[]{"inputFile1.txt","inputFile2.txt","inputFile3.txt"};

		//Files contents will be written in these files
		String outputFile = "outputFile.txt";

		//Get channel for output file
		FileOutputStream fos = new FileOutputStream(new File(outputFile));
		WritableByteChannel targetChannel = fos.getChannel();

		for (int i = 0; i < inputFiles.length; i++)
		{
			//Get channel for input files
			FileInputStream fis = new FileInputStream(inputFiles[i]);
			FileChannel inputChannel = fis.getChannel();

			//Transfer data from input channel to output channel
			inputChannel.transferTo(0, inputChannel.size(), targetChannel);

			//close the input channel
			inputChannel.close();
			fis.close();
		}

		//finally close the target channel
		targetChannel.close();
		fos.close();
	}
}

将您的评论和建议放在评论部分。

祝您学习愉快!

Java 查看/生成类文件的字节码

原文: https://howtodoinjava.com/java/basics/how-to-view-generate-bytecode-for-a-java-class-file/

很多时候,我们需要了解编译器在后台执行的操作。 我们正在编写的 Java 语句将如何重新排序和执行。 另外,我们也需要查看字节码以用于学习目的,我很少这样做。 在本教程中,我将给出一个示例,说明如何在 Java 中为类文件生成字节码。

为了演示该示例,我使用了为我的其他教程创建的 java 文件,该教程与 java 7 中的自动资源管理有关。

步骤 1)使用命令javac(可选)编译文件ResourceManagementInJava7.java

这是可选的,因为您可能已经具有.class文件。

prompt > javac C:tempjavatestResourceManagementInJava7.java

这将生成.class文件ResourceManagementInJava7.class

步骤 2)执行javap命令并将输出重定向到.bc文件

C:>javap -c C:tempjavatestResourceManagementInJava7.class > C:tempjavatestbytecode.bc

Folder view

资料夹检视

让我们看一下在命令提示符下运行的命令。

java_byte_code_javap_command_window

命令窗口视图

文件bytecode.bc文件将在给定位置生成。 将会是这样的:

Compiled from "ResourceManagementInJava7.java"
public class com.howtodoinjava.java7.tryCatch.ResourceManagementInJava7 {
  public com.howtodoinjava.java7.tryCatch.ResourceManagementInJava7();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/io/BufferedReader
       3: dup           
       4: new           #3                  // class java/io/FileReader
       7: dup           
       8: ldc           #4                  // String C:/temp/test1.txt
      10: invokespecial #5                  // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
      13: invokespecial #6                  // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
      16: astore_1      
      17: aconst_null   
      18: astore_2      
      19: new           #2                  // class java/io/BufferedReader
      22: dup           
      23: new           #3                  // class java/io/FileReader
      26: dup           
      27: ldc           #7                  // String C:/temp/test2.txt
      29: invokespecial #5                  // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
      32: invokespecial #6                  // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
      35: astore_3      
      36: aconst_null   
      37: astore        4
      39: new           #2                  // class java/io/BufferedReader
      42: dup           
      43: new           #3                  // class java/io/FileReader
      46: dup           
      47: ldc           #8                  // String C:/temp/test3.txt
      49: invokespecial #5                  // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
      52: invokespecial #6                  // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
      55: astore        5
      57: aconst_null   
      58: astore        6
      60: aload         5
      62: ifnull        138
      65: aload         6
      67: ifnull        90
      70: aload         5
      72: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
      75: goto          138
      78: astore        7
      80: aload         6
      82: aload         7
      84: invokevirtual #11                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      87: goto          138
      90: aload         5
      92: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
      95: goto          138
      98: astore        8
     100: aload         5
     102: ifnull        135
     105: aload         6
     107: ifnull        130
     110: aload         5
     112: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     115: goto          135
     118: astore        9
     120: aload         6
     122: aload         9
     124: invokevirtual #11                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     127: goto          135
     130: aload         5
     132: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     135: aload         8
     137: athrow        
     138: aload_3       
     139: ifnull        219
     142: aload         4
     144: ifnull        166
     147: aload_3       
     148: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     151: goto          219
     154: astore        5
     156: aload         4
     158: aload         5
     160: invokevirtual #11                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     163: goto          219
     166: aload_3       
     167: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     170: goto          219
     173: astore        5
     175: aload         5
     177: astore        4
     179: aload         5
     181: athrow        
     182: astore        10
     184: aload_3       
     185: ifnull        216
     188: aload         4
     190: ifnull        212
     193: aload_3       
     194: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     197: goto          216
     200: astore        11
     202: aload         4
     204: aload         11
     206: invokevirtual #11                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     209: goto          216
     212: aload_3       
     213: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     216: aload         10
     218: athrow        
     219: aload_1       
     220: ifnull        290
     223: aload_2       
     224: ifnull        243
     227: aload_1       
     228: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     231: goto          290
     234: astore_3      
     235: aload_2       
     236: aload_3       
     237: invokevirtual #11                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     240: goto          290
     243: aload_1       
     244: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     247: goto          290
     250: astore_3      
     251: aload_3       
     252: astore_2      
     253: aload_3       
     254: athrow        
     255: astore        12
     257: aload_1       
     258: ifnull        287
     261: aload_2       
     262: ifnull        283
     265: aload_1       
     266: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     269: goto          287
     272: astore        13
     274: aload_2       
     275: aload         13
     277: invokevirtual #11                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     280: goto          287
     283: aload_1       
     284: invokevirtual #9                  // Method java/io/BufferedReader.close:()V
     287: aload         12
     289: athrow        
     290: goto          298
     293: astore_1      
     294: aload_1       
     295: invokevirtual #13                 // Method java/io/IOException.printStackTrace:()V
     298: return        
    Exception table:
       from    to  target type
          70    75    78   Class java/lang/Throwable
         110   115   118   Class java/lang/Throwable
          98   100    98   any
         147   151   154   Class java/lang/Throwable
          39   138   173   Class java/lang/Throwable
          39   138   182   any
         193   197   200   Class java/lang/Throwable
         173   184   182   any
         227   231   234   Class java/lang/Throwable
          19   219   250   Class java/lang/Throwable
          19   219   255   any
         265   269   272   Class java/lang/Throwable
         250   257   255   any
           0   290   293   Class java/io/IOException
}

学习愉快!

什么是 Java 编程语言?

原文: https://howtodoinjava.com/java/basics/what-is-java-programming-language/

Java 是通用计算机编程语言,它是并发,基于类,面向对象,并且经过专门设计,以尽可能减少实现依赖 。 旨在让应用程序开发人员“编写一次,随处运行”(WORA),这意味着已编译的 Java 代码可以在支持 Java 的所有平台上运行,而无需重新编译。

例如,您可以在 UNIX 上编写和编译 Java 程序,然后在 Microsoft Windows,Macintosh 或 UNIX 计算机上运行它,而无需对源代码进行任何修改。 通过将 Java 程序编译为称为字节码的中间语言来实现WORA。 字节码的格式是与平台无关的。 称为 Java 虚拟机(JVM)的虚拟机用于在每个平台上运行字节码。

JDK vs JRE vs JVM

JDK vs JRE vs JVM

Java 的历史

Java 最初由 James GoslingSun Microsystems (已由 Oracle Corporation 收购)开发,并于 1995 年作为 Sun Microsystems Java 平台的核心组件发布。 该语言的大部分语法均来自 C 和 C++,但与任何一种相比,它的低级特性都更少。

Oracle 公司是 Java SE 平台的正式实现的当前所有者,此后于 2010 年 1 月 27 日收购了 Sun Microsystems。该实现基于 Sun 最初的 Java 实现。 Oracle 实现可用于 Microsoft Windows,Mac OS X,Linux 和 Solaris。

Oracle 实现打包为两个不同的发行版:

  1. Java 运行时环境(JRE)包含运行 Java 程序所需的 Java SE 平台部分,供最终用户使用。
  2. Java 开发工具包(JDK)供软件开发人员使用,包括 Java 编译器,Javadoc,Jar 和调试器之类的开发工具。

垃圾收集

Java 使用自动垃圾收集器来管理对象生命周期中的内存。 程序员确定何时创建对象,一旦不再使用对象,Java 运行时将负责恢复内存。 一旦没有对对象的引用,则垃圾回收器将有资格自动释放无法访问的内存。

如果程序员的代码持有对不再需要的对象的引用,则通常仍会发生类似于内存泄漏的情况,通常是将不再需要的对象存储在仍在使用的容器中时。 如果调用了不存在的对象的方法,则会引发NullPointerException

垃圾收集可能随时发生。 理想情况下,它将在程序空闲时发生。 如果堆上的可用内存不足以分配新对象,则可以保证触发该事件。 这可能会导致程序暂时停止。 在 Java 中无法进行显式内存管理。

Java Hello World 程序

传统的“你好,世界!” 程序可以用 Java 编写为:

public class HelloWorldApplication {
    public static void main(String[] args) {
        System.out.println("Hello World!"); 	// Prints Hello World! to the console.
    }
}

Java 类文件

  1. Java 源文件必须以它们包含的公共类命名,并在其后附加.java后缀,例如HelloWorldApplication.java
  2. 必须首先使用 Java 编译器将其编译为字节码,然后生成一个名为HelloWorldApplication.class的文件。 只有这样才能执行或“启动”。
  3. Java 源文件只能包含一个公共类,但是可以包含多个类,除了公共访问权限和任何数量的公共内部类之外。
  4. 当源文件包含多个类时,将一个类设为public,然后使用该公共类名命名源文件。

在下一组教程中,我们将了解有关其他语言特性的更多信息。

学习愉快!

Java 中的小端和大端

原文: https://howtodoinjava.com/java/basics/little-endian-and-big-endian-in-java/

您必须在工程课程中多次听到这些术语 小端和大端。 让我们快速回顾一下这些词背后的概念。

这两个术语与 CPU 架构中一个字的字节的方向有关。 计算机内存由正整数的地址引用。 将数字的最低有效字节存储在计算机内存中的最高有效字节之前是“自然”的。 有时,计算机设计人员喜欢使用这种表示形式的逆序版本。

“自然”顺序在内存中的低有效字节先于高有效字节,被称为小端。 像 IBM,CRAY 和 Sun 这样的许多供应商都喜欢相反的顺序,当然,这种顺序被称为大端。

例如,将 32 位十六进制值0x45679812如下存储在内存中:

Address         00  01  02  03
-------------------------------
Little-endian   12  98  67  45
Big-endian      45  67  98  12

在两台机器之间传输数据时,字节序差异可能是一个问题。

Java 二进制格式文件中的所有内容均按大端顺序存储。 有时也称为网络顺序。 这意味着,如果仅使用 Java,则在所有平台(Mac,PC,UNIX 等)上,所有文件的处理方式都相同。您可以自由地电子交换二进制数据,而无需担心字节顺序。

问题是当您必须与某些不是用 Java 编写的使用低端顺序的程序交换数据文件时,该程序通常使用 C 编写。某些平台在内部使用了大端顺序(Mac,IBM 390)。 有些使用小端序(Intel)。

Java 对您隐藏了内部字节序。

祝您学习愉快!

Java 命令行参数

原文: https://howtodoinjava.com/java/basics/command-line-args/

启动 Java 程序时传递的程序参数称为命令行参数

可以从控制台或从编辑器启动 Java 程序。 要启动程序,我们必须使用命令提示符或系统控制台中的java className命令。 启动程序时,我们可以使用以下语法传递附加参数(无参数数量限制)。

1. 命令行参数语法

在下面给出的语法中,我们将 5 个参数传递给启动类MyClassMyClass具有main()方法,该方法以字符串数组的形式接受这些参数。

$ java MyClass arg1 arg2 arg3 arg4 arg5

2. Java 命令行参数示例

让我们创建一个示例,以了解命令行程序参数如何在 Java 中工作。 此类简单地接受参数并将其打印在控制台中。

作为程序员,我们可以将这些参数用作启动参数来自定义应用程序在运行时的行为。

package app;

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

现在从控制台运行此类。

$ java Main 1 2 3 4

#prints

1
2
3
4

3. 总结

  • 命令行参数可用于在启动应用程序时指定配置信息。
  • 对参数的最大数量没有限制。 我们可以指定任意数量的参数。
  • 参数作为字符串传递。
  • 传递的参数将作为main()方法参数中的字符串数组检索。

学习愉快!

在 Java 中比较浮点数或双精度数的正确方法

原文: https://howtodoinjava.com/java/basics/correctly-compare-float-double/

正确地比较浮点比较双不仅是 Java 特定的问题。 当今几乎所有编程语言都可以观察到它。 使用 IEEE 754 标准格式存储浮点数和双精度数。 实际的存储和转换如何工作,不在本文的讨论范围之内。

现在,只需了解在计算和转换期间,可以在这些数字中引入较小的舍入误差。 这就是为什么不建议仅依赖相等运算符(==比较浮点数的原因。

让我们学习如何比较 Java 中的浮点值。

Table of Contents

1\. Simple comparison [Not recommended]
2\. Threshold based comparison [Recommended]
3\. Compare with BigDecimal [Recommended]

1. 双精度比较 -- 简单比较(不推荐)

首先看一下简单比较,以了解用==运算符比较double到底有什么问题。 在给定的程序中,我使用两种方法创建相同的浮点数(即1.1):

  1. 添加.1 11 次。
  2. .1乘以 11。

理论上,两个操作都应产生数字1.1。 当我们比较这两种方法的结果时,它应该匹配。

private static void simpleFloatsComparison() 
{
	//Method 1
	double f1 = .0;
	for (int i = 1; i <= 11; i++) {
		f1 += .1;
	}

	//Method 2
	double f2 = .1 * 11;

	System.out.println("f1 = " + f1);
	System.out.println("f2 = " + f2);

	if (f1 == f2)
		System.out.println("f1 and f2 are equal\n");
	else
		System.out.println("f1 and f2 are not equal\n");
}

程序输出。

f1 = 1.0999999999999999
f2 = 1.1
f1 and f2 are not equal

查看控制台中打印的两个值。 将f1计算为1.0999999999999999。 其舍入问题恰恰是内部引起的。 因此,不建议将浮点数用'=='运算符进行比较

2. 双精度比较 -- 基于阈值的比较[推荐]

现在,当我们知道相等运算符的问题时,让我们解决它。 使用编程,我们无法更改存储或计算这些浮点数的方式。 因此,我们必须采用一种解决方案,在该解决方案中,我们同意确定两个可以容忍的值的差异,并且仍然认为数字相等。 该商定的值差被称为阈值ε

因此,现在要使用“基于阈值的浮点比较”,我们可以使用Math.abs()方法计算两个数字之间的差并将其与阈值进行比较。

private static void thresholdBasedFloatsComparison() 
{
	final double THRESHOLD = .0001;

	//Method 1
	double f1 = .0;
	for (int i = 1; i <= 11; i++) {
		f1 += .1;
	}

	//Method 2
	double f2 = .1 * 11;

	System.out.println("f1 = " + f1);
	System.out.println("f2 = " + f2);

	if (Math.abs(f1 - f2) < THRESHOLD)
		System.out.println("f1 and f2 are equal using threshold\n");
	else
		System.out.println("f1 and f2 are not equal using threshold\n");
}

程序输出:

f1 = 1.0999999999999999
f2 = 1.1
f1 and f2 are equal using threshold

3. 比较doubleBigDecimal(推荐)

BigDecimal类中,可以指定要使用的舍入模式精确度。 使用精确的精度限制,可以解决舍入误差。

最好的部分是BigDecimal数字是不可变的,即,如果创建具有"1.23"值的BigDecimal,则该对象将保持"1.23"且不能更改。 此类提供了许多方法,可用于对其值进行数值运算。

您可以使用compareTo()方法与BigDecimal数字进行比较。 比较时会忽略比例。

a.compareTo(b);

方法返回:

-1 – 如果是a < b

0 – 如果a == b

1 – 如果是a > b

切勿使用equals()方法比较BigDecimal实例。这是因为此equals函数将比较比例。 如果比例不同,即使在数学上相同,equals()也将返回false

Java 程序将BigDecimal类与double进行比较。

private static void testBdEquality() 
{
	 BigDecimal a = new BigDecimal("2.00");
	 BigDecimal b = new BigDecimal("2.0");

	 System.out.println(a.equals(b)); 			// false

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

现在,为了验证,让我们使用BigDecimal类解决原始问题。

private static void bigDecimalComparison() 
{
	//Method 1
	BigDecimal f1 = new BigDecimal("0.0");
	BigDecimal pointOne = new BigDecimal("0.1");
	for (int i = 1; i <= 11; i++) {
		f1 = f1.add(pointOne);
	}

	//Method 2
	BigDecimal f2 = new BigDecimal("0.1");
	BigDecimal eleven = new BigDecimal("11");
	f2 = f2.multiply(eleven);

	System.out.println("f1 = " + f1);
	System.out.println("f2 = " + f2);

	if (f1.compareTo(f2) == 0)
		System.out.println("f1 and f2 are equal using BigDecimal\n");
	else
		System.out.println("f1 and f2 are not equal using BigDecimal\n");
}

程序输出:

f1 = 1.1
f2 = 1.1
f1 and f2 are equal using BigDecimal

这就是比较 Java 中的浮点数的全部。 在评论部分分享您的想法。

学习愉快!

Java 递归指南

原文: https://howtodoinjava.com/java/basics/java-recursion/

递归是一种编程样式,其中方法重复调用其自身,直到满足某个预定条件为止。 这种方法调用也称为递归方法

1. 递归方法的语法

基于递归的方法必须具有两个基本组成部分才能正确解决问题。

  1. 递归方法调用
  2. 打破递归的前提

请记住,如果无法达到或未定义前提条件,则会发生栈溢出问题。

method(T parameters...) 
{
    if(precondition == true)      //precondition
    {
        return result;
    }

    return method(T parameters...);      //recursive call
}

2. 递归类型

根据何时进行递归方法调用,递归有两种类型。

1. 尾递归

当递归方法调用是该方法内部执行的最后一条语句时(通常与return语句一起使用),则递归方法为尾递归

method(T parameters...) 
{
    if(precondition == true)      
    {
        return result;
    }

    return method(T parameters...);     
}

2. 头递归

不是尾递归的任何递归都可以称为头递归。

method(T parameters...) 
{
    if(some condition)      
    {
        return method(T parameters...);
    }

    return result;     
}

3. Java 递归示例

3.1 斐波那契序列

斐波那契数列是一个数字序列,其中每个数字都定义为两个数字之和。

例如 -1、1、2、3、5、8、13、21、34,依此类推…

此函数为我们提供从 1 开始的第 n 个位置的斐波那契数。

public int fibonacci(int n) 
{
    if (n <= 1) {
        return n;
    }
    return fibonacci(n-1) + fibonacci(n-2);
}

让我们测试一下此函数以打印n = 10的斐波那契数列;

public static void main(String[] args) 
{
  int number = 10;

  for (int i = 1; i <= number; i++) 
  {
    System.out.print(fibonacci(i) + " ");
  }

}

程序输出。

1 1 2 3 5 8 13 21 34 55 

3.2 最大公约数

两个正整数的最大公约数(gcd)是最大的整数,该整数平均分为两个整数。

public static int gcd(int p, int q) {
    if (q == 0) return p;
    else return gcd(q, p % q);
}

让我们测试一下此函数以打印n = 10的斐波那契数列:

public static void main(String[] args) 
{
  int number1 = 40;
  int number2 = 500;

  System.out.print( gcd(number1, number2) );
}

程序输出:

20

让我知道您有关 Java 中递归的问题

学习愉快!

Java 偶对

原文: https://howtodoinjava.com/java/basics/pairs-in-java/

使用偶对类,例如javafx.util.PairImmutablePairMmutablePair(通用语言)和io.vavr.Tuple2类,在 Java 中学习如何使用键值对

阅读更多: Java 中的元组

1. 为什么需要偶对?

偶对提供了一种将简单键值关联的便捷方法。 在 Java 中,映射用于存储键值对。 映射存储成对的集合并作为整体进行操作。

有时,我们需要处理一些要求,其中键值对应独立存在。 例如

  • 偶对需要在方法中作为参数传递
  • 方法需要以偶对形式返回两个值

2. javafx.util.Pair

Java 核心 API 具有javafx.util.Pair作为最接近的匹配,用于具有两个值作为名称-值对的目的。 单击此链接以了解在 Eclipse 中添加 JavaFx 支持。

Pair类提供以下方法。

  • boolean equals​(Object o) – 测试此对与另一个对象的相等性。
  • K getKey() – 获取该对的键。
  • V getValue() – 获取该对的值。
  • int hashCode() – 为此对生成哈希码。
  • String.toString() – 此对的字符串表示形式。

让我们看一个 Java 程序来创建和使用对。

Pair<Integer, String> pair = new Pair<>(100, "howtodoinjava.com");

Integer key = pair.getKey();		//100
String value = pair.getValue();		//howtodoinjava.com

pair.equals(new Pair<>(100, "howtodoinjava.com"));	//true - same name and value

pair.equals(new Pair<>(222, "howtodoinjava.com"));	//false	- different name

pair.equals(new Pair<>(100, "example.com"));		//false	- different value

3. PairImmutablePairMutablePair – Apache 公共语言

Commons lang 库有一个有用的类,可以用于偶对,即org.apache.commons.lang3.tuple.Pair。 它有两个子类,也可以用于相同目的,即ImmutablePairMutablePair

  • Pair类是由两个元素组成的偶对。
  • Pair将元素称为“左”和“右”。
  • Pair还实现了Map.Entry接口,其中键为“左”,值为“右”。
  • ImmutablePairPair上的不可变表示。 如果将可变的对象存储在该对中,那么该对本身将有效地变为可变的。 该类也不是final,因此子类可能会添加不良行为。
  • 如果存储的对象是线程安全的,则ImmutablePair线程安全的
ImmutablePair<Integer, String> pair = ImmutablePair.of(100, "howtodoinjava.com");

Integer key = pair.getKey();			//100
String value = pair.getValue();			//howtodoinjava.com

//Integer key = pair.getLeft();			//100
//String value = pair.getRight();		//howtodoinjava.com

pair.equals(ImmutablePair.of(100, "howtodoinjava.com"));	//true - same name and value

pair.equals(ImmutablePair.of(222, "howtodoinjava.com"));	//false	- different name

pair.equals(ImmutablePair.of(100, "example.com"));		//false	- different value

不要忘记将库导入应用程序类路径。

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.8.1</version>
</dependency>

4. io.vavr.Tuple2 - Vavr

用于存储键值对的另一个有用的类是Tuple2

Tuple2提供了许多有用的方法来处理其中存储的数据。 例如:

  • T1 _1() – 此元组的第一个元素的获取器。
  • T2 _2() – 此元组的第二个元素的获取器。
  • Tuple2 update1(T1 value) – 将此元组的第一个元素设置为给定值。
  • Tuple2 update2(T2 value) – 将此元组的第二个元素设置为给定值。
  • Map.Entry toEntry() – 将元组转换为java.util.Map.Entry元组。
  • Tuple2 swap() – 交换此元组的元素。
  • Tuple2 map(BiFunction mapper) – 使用映射器函数映射该元组的组件。
  • int compareTo(Tuple2 that) – 比较两个Tuple2实例。
Tuple2<Integer, String> pair = new Tuple2<>(100, "howtodoinjava.com");

Integer key = pair._1();			//100
String value = pair._2();			//howtodoinjava.com

pair.equals(new Tuple2<>(100, "howtodoinjava.com"));	//true - same name and value

pair.equals(new Tuple2<>(222, "howtodoinjava.com"));	//false	- different name

pair.equals(new Tuple2<>(100, "example.com"));		//false	- different value

不要忘记将库导入应用程序类路径。

<dependency>
	<groupId>io.vavr</groupId>
	<artifactId>vavr</artifactId>
	<version>0.10.2</version>
</dependency>

向我提供有关 Java 中的名称/值对的问题。

学习愉快!

Java 元组 – 使用 Java 中的元组

原文: https://howtodoinjava.com/java/basics/java-tuples/

在本 Java 教程中,我们将学习 Java 元组 – 通用数据结构,以及如何在 Java 程序中使用元组。 默认情况下,元组在 Java 编程语言中不作为数据结构出现,因此我们将使用一个不错的第三方库javatuples

Table of Contents

1\. What is tuple
2\. Tuple in Java
3\. Introduction of Javatuples 
4\. Common Operations on Javatuples
5\. Conclusion

1. 什么是元组?

元组可以看作是有序的不同类型的对象的集合。 这些对象不一定以任何方式相互关联,但是它们总的来说将具有一定的意义。

例如,["Sajal Chakraborty", "IT Professional", 32]可以是一个元组,其中该元组中的每个值都没有任何关系,但是整个值集在应用程序中可能具有某些意义。 例如,给定元组可以代表具有姓名,部门和年龄的员工数据。

让我们来看更多 java 元组示例

["Java", 1.8, "Windows"]
["Alex", 32, "New York", true]
[3, "Alexa", "howtodoinjava.com", 37000]

2. Java 中的元组

Java 没有任何这样的内置数据结构来支持元组。 无论何时需要,我们显然都可以创建一个类似于元组的类。

同样,在 Java 中,可以使用列表或数组来编写元组特性的一部分,但这些不允许我们通过设计保存不同类型的数据类型。 因此,可以说在 Java 中使用标准数据结构的异构元组是不可能的。

2.1 元组与列表/数组的比较

通常将元组与列表进行比较,因为它看起来非常像一个列表。 但是它们在某些方面有所不同。

  1. 元组是可以包含异构数据的对象。 列表旨在存储单一类型的元素。
  2. 在所有数据结构中,元组被认为是最快的,并且它们消耗的内存量最少。
  3. 虽然数组和列表是可变的,这意味着您可以更改其数据值并修改其结构,但元组是不可变的。
  4. 像数组一样,元组的大小也是固定的。 这就是为什么元组旨在完全替换数组的原因,因为它们在所有参数上都更有效率。
  5. 如果您有一个在生命周期内仅分配一次的数据集,并且其值不应再次更改,则需要一个元组。

3. javatuples

3.1 javatuples maven 依赖

javatuples库存在于 Maven 中央存储库中,我们可以添加此依赖项以使用该库。

<dependency>
	<groupId>org.javatuples</groupId>
	<artifactId>javatuples</artifactId>
	<version>1.2</version>
</dependency>

3.2 javatuples - 类

Java 元组支持最大为10的元组,并且对于每种大小,它都提供了如下的元组实现。

  • Unit(一个元素)
  • Pair(两个元素)
  • Triplet(三个元素)
  • Quartet(四个元素)
  • Quintet(五个元素)
  • Sextet(六个元素)
  • Septet(七个元素)
  • Octet(八个元素)
  • Ennead(九个元素)
  • Decade(十个元素)

除了上述类之外,它还提供了另外两个类来方便表示对。 大部分与Pair相同,但语法更详细。

  1. KeyValue
  2. LabelValue

3.3 javatuples – 特性

不同类型的 Java 元组是:

  1. 输入安全
  2. 不可变
  3. 可迭代的
  4. 可序列化
  5. 可比(实现Comparable
  6. 实现equals()hashCode()
  7. 实现toString()

4. javatuples的常见操作

4.1 创建元组

4.1.1 工厂方法

元组对象是通过每个元组类提供的工厂方法with() 构造的。 例如,我们可以使用创建Pair的元组。

Pair<String, Integer> pair = Pair.with("Sajal", 12);
4.1.2 构造器

我们还可以使用Pair构造器

Pair<String, Integer> person = new Pair<String, Integer>("Sajal", 12);
4.1.3 集合或可迭代对象

我们可以从CollectionIterable创建元组,前提是该集合具有确切数量的对象。 在这种情况下,请记住,集合中的项目数应与我们要创建的元组的类型相匹配。

//Collection of 4 elements will create Quartet
List<String> listOf4Names = Arrays.asList("A1","A2","A3","A4");

Quartet<String, String, String, String> quartet = Quartet.fromCollection(listOf4Names);

System.out.println(quartet);

//Create pair with items starting from the specified index.
List<String> listOf4Names = Arrays.asList("A1","A2","A3","A4");

Pair<String, String> pair1 = Pair.fromIterable(listOf4Names, 2);

System.out.println(pair1);

程序输出。

[A1, A2, A3, A4]
[A3, A4]

同样,我们可以根据需要创建任何元组类的对象。

4.2 检索值

4.2.1 getValueX()方法

我们可以使用其索引的getValueX()方法从元组中获取值,其中'X'表示元组内部的元素位置。 例如getValue0()getValue1()等。

Pair<String, Integer> pair = Pair.with("Sajal", 12);

System.out.println("Name : " + pair.getValue0());
System.out.println("Exp : " + pair.getValue1());

程序输出:

Name : Sajal
Exp : 12

请注意,这些获取方法是类型安全的。 这意味着编译器已经基于用于初始化元组的元素值知道方法的返回类型。

4.2.2 getValue(int index)方法

元组还有另一种类型不安全的方法getValue(int index)。 因此,在分配变量时,需要将值转换为期望的类型。

Pair<String, Integer> pair = Pair.with("Sajal", 12);

System.out.println("Name : " + pair.getValue(0));
System.out.println("Exp : " + pair.getValue(1));

程序输出:

Name : Sajal
Exp : 12

KeyValueLabelValue的方法分别为getKey()/getValue()getLabel()/getValue()

4.3 设定值

创建元组后,我们可以设置值。 我们可以通过setAtX()方法执行此操作,其中'X'是我们要设置值的索引位置。

Pair<String, Integer> pair = Pair.with("Sajal", 12);

//Modify the value
Pair<String, Integer> modifiedPair = pair.setAt0("Kajal");

System.out.println(pair);
System.out.println(modifiedPair);

程序输出:

[Sajal, 12]
[Kajal, 12]

请注意,元组是不可变的。 因此,setAt()方法返回具有修改后值的相同类型的元组。 原始元组不变。

4.4 添加或删除元素

4.4.1 add()方法

我们还可以在元组中添加元素,这将返回与元素数量匹配的新元组类型。 例如,如果我们将元素值添加到Pair,那么我们将获得一个Triplet对象作为回报。

元组的末尾添加了新元素。

Pair<String, Integer> pair = Pair.with("Sajal", 12);

Triplet<String, Integer, String> triplet = pair.add("IT Professional");

System.out.println(pair);
System.out.println(triplet);

程序输出:

[Sajal, 12]
[Sajal, 12, IT Professional]

我们也可以将一个元组对象添加到另一个元组中。 它将根据添加后存在的元素数返回元组的类型。

Triplet<String, String, String> triplet = Triplet.with("Java", "C", "C++");
Quartet<String, String, String, String> quartet = triplet.addAt1("Python");

Septet septet = quartet.add(triplet);   //3 + 4 = 7

System.out.println(triplet);
System.out.println(quartet);
System.out.println(septet);

程序输出:

[Java, C, C++]
[Java, Python, C, C++]
[Java, Python, C, C++, Java, C, C++]

4.4.2 join()方法

默认情况下,新元素添加到元组的末尾。 但是我们也可以使用addAtX()方法在元组的其他位置添加元素。

Triplet<String, String, String> triplet = Triplet.with("Java", "C", "C++");
Quartet<String, String, String, String> quartet = triplet.addAt1("Python");

System.out.println(triplet);
System.out.println(quartet);

程序输出:

[Java, C, C++]
[Java, Python, C, C++]

4.5 将元组转换为集合或数组

每个元组类提供asList()toArray()方法,它们分别返回ListArray

//Convert to list
Quartet<String, Integer, String, Double> quartet1 = Quartet.with("A1",1,"A3",2.3);

List<Object> quartletList = quartet1.toList();

System.out.println(quartletList);

//Convert to array
Object[] quartletArr = quartet1.toArray();

System.out.println(Arrays.toString(quartletArr));

程序输出:

[A1, 1, A3, 2.3]
[A1, 1, A3, 2.3]

请注意,元组可以包含异构类型,因此相应的结果类型将为List<Object>Object[]

4.6 迭代

javatuples中的所有元组类都实现Iterable接口,因此可以与集合或数组相同的方式对其进行迭代。

Quartet<String, Integer, String, Double> quartet1 = Quartet.with("A1",1,"A3",2.3);

for(Object obj : quartet1) {
	System.out.println(obj);
}

程序输出:

A1
1
A3
2.3

4.7 Java 元组中的更多操作

所有元组类都有以下实用方法,例如集合,我们可以根据需要使用它们。

  • contains() – 如果该元组包含指定的元素,则返回true
  • containsAll() – 如果该元组包含所有指定的元素,则返回true
  • indexOf() – 返回指定元素首次出现的索引。
  • lastIndexOf() – 返回指定元素最后一次出现的索引。

元组还提供hashCode()equals()compareTo()方法的通用实现,这些方法可以很好地处理包装程序和字符串类。

5. Java 元组 - 总结

在此 Java 元组教程中,我们了解了如何通过javatuple库在 Java 中使用元组。 因此,如果您对存储固定数量的异构元素的数据结构有任何要求,则可以使用此库。 它非常简单,易于使用并提供良好的性能。

下载源码

学习愉快!

参考文献:

Javatuples的官方页面

sun.misc.Unsafe类的用法

原文: https://howtodoinjava.com/java/basics/usage-of-class-sun-misc-unsafe/

这篇文章是有关 Java 鲜为人知的特性的讨论顺序的下一个更新。 请通过电子邮件订阅,以在下一次讨论进行时进行更新。 并且不要忘记在评论部分表达您的观点。

Java 是一种安全的编程语言,可以防止程序员犯很多愚蠢的错误,这些错误大多数是基于内存管理的。 但是,如果您决定将其弄乱,则可以使用Unsafe类。 此类是sun.*API,其中并不是 J2SE 的真正组成部分,因此您可能找不到任何正式文档。 可悲的是,它也没有任何好的代码文档。

sun.misc.Unsafe的实例化

如果尝试创建Unsafe类的实例,则由于两个原因,将不允许您这样做。

1)不安全类具有私有构造器。
2)它也具有静态的getUnsafe()方法,但是如果您朴素地尝试调用Unsafe.getUnsafe(),则可能会得到SecurityException。 此类只能从受信任的代码实例化。

但是总有一些解决方法。 创建实例的类似的简单方法是使用反射:

	Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
	f.setAccessible(true);
	Unsafe unsafe = (Unsafe) f.get(null);

注意:您的 IDE,例如 eclipse 可能显示与访问限制有关的错误。 不用担心,继续运行程序。 它将运行。

现在到主要部分。 使用此对象,您可以执行“有趣的”任务。

sun.misc.Unsafe的使用

1)创建不受限制的实例

使用allocateInstance()方法,您可以创建类的实例,而无需调用其构造器代码,初始化代码,各种 JVM 安全检查以及所有其他低级内容。 即使类具有私有构造器,也可以使用此方法创建新实例。

所有单例爱好者的噩梦。 伙计们,您只是无法轻松应对这种威胁。

举个例子:

public class UnsafeDemo 
{
	public static void main(String[] args) throws NoSuchFieldException, SecurityException, 
							IllegalArgumentException, IllegalAccessException, InstantiationException 
	{
		Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
		f.setAccessible(true);
		Unsafe unsafe = (Unsafe) f.get(null);

		//This creates an instance of player class without any initialization
		Player p = (Player) unsafe.allocateInstance(Player.class);
		System.out.println(p.getAge());		//Print 0

		p.setAge(45);						//Let's now set age 45 to un-initialized object
		System.out.println(p.getAge());		//Print 45

		System.out.println(new Player().getAge());	//This the normal way to get fully initialized object; Prints 50
	}
}

class Player{
	private int age = 12;

	public Player(){		//Even if you create this constructor private; 
							//You can initialize using Unsafe.allocateInstance()
		this.age = 50;
	}
	public int getAge(){
		return this.age;
	}
	public void setAge(int age){
		this.age = age;
	}
}

Output:

0
45
50

2)使用直接内存访问的浅克隆

通常如何进行浅克隆? 在clone(){..}方法中调用super.clone()吧? 这里的问题是,您必须实现Cloneable接口,然后在要实现浅层克隆的所有类中覆盖clone()方法。 懒惰的开发人员要付出太多努力。

我不建议这样做,但是使用不安全的方法,我们可以在几行中创建浅表克隆,最好的部分是它可以与任何类一起使用,就像某些工具方法一样。

诀窍是将对象的字节复制到内存中的另一个位置,然后将该对象类型转换为克隆的对象类型。

3)黑客的密码安全性

这看起来很有趣吗? 是的,是的。 开发人员创建密码或将密码存储在字符串中,然后在应用程序代码中使用它们。 使用密码后,更聪明的开发人员将字符串引用设置为NULL,以便不再对其进行引用,并且可以轻松地对其进行垃圾回收。

但是从那时起,您对垃圾回收器启动时的引用为null,该字符串实例位于字符串池中。 对您的系统进行的复杂攻击将能够读取您的内存区域,从而也可以访问密码。 机会很低,但他们在这里。

因此,建议使用char []存储密码,以便在使用后可以遍历数组并使每个字符变脏/变空。
另一种方法是使用我们的魔术类“不安全”。 在这里,您将创建另一个长度与密码相同的临时字符串,并存储“?”或为临时密码中的每个字符输入“*”(或任何字母)。 完成密码逻辑操作后,您只需将临时密码(例如????????)的字节复制到原始密码上即可。 这意味着用临时密码覆盖原始密码。

示例代码如下所示。

	String password = new String("l00k@myHor$e");
	String fake = new String(password.replaceAll(".", "?"));
	System.out.println(password); // l00k@myHor$e
	System.out.println(fake); // ????????????

	getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));

	System.out.println(password); // ????????????
	System.out.println(fake); // ????????????

在运行时动态创建类

我们可以在运行时创建类,例如从已编译的.class文件中。 要将读取的类内容执行到字节数组,然后将其传递给defineClass方法。

	//Sample code to craeet classes
	byte[] classContents = getClassContent();
	Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);
    c.getMethod("a").invoke(c.newInstance(), null); 

	//Method to read .class file
	private static byte[] getClassContent() throws Exception {
		File f = new File("/home/mishadoff/tmp/A.class");
		FileInputStream input = new FileInputStream(f);
		byte[] content = new byte[(int)f.length()];
		input.read(content);
		input.close();
		return content;
	}

4)超大数组

如您所知,Integer.MAX_VALUE常量是 java 数组的最大大小。 如果要构建真正的大数组(尽管在常规应用程序中没有实际需要),则可以为此使用直接内存分配。

以此类的示例为例,该类创建的顺序存储器(数组)的大小是允许的大小的两倍。

class SuperArray {
    private final static int BYTE = 1;
    private long size;
    private long address;

    public SuperArray(long size) {
        this.size = size;
        address = getUnsafe().allocateMemory(size * BYTE);
    }
    public void set(long i, byte value) {
        getUnsafe().putByte(address + i * BYTE, value);
    }
    public int get(long idx) {
        return getUnsafe().getByte(address + idx * BYTE);
    }
    public long size() {
        return size;
    }
}

用法示例:

long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) { 
	array.set((long)Integer.MAX_VALUE + i, (byte)3); 
	sum += array.get((long)Integer.MAX_VALUE + i); 
} 
System.out.println("Sum of 100 elements:" + sum); // 300

请注意,这会导致 JVM 崩溃。

总结

sun.misc.Unsafe提供了几乎无限的能力来探索和修改 VM 的运行时数据结构。 尽管事实上这些特性几乎不能在 Java 开发本身中使用,但是对于希望在不进行 C++代码调试的情况下研究 HotSpot VM 或需要创建临时分析工具的任何人来说,Unsafe都是一个不错的工具。

参考:

http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

祝您学习愉快!

Java UUID 生成器示例

原文: https://howtodoinjava.com/java/basics/java-uuid/

了解什么是 UUID 及其版本和变体。 学习使用UUID.randomUUID() API 在 Java 中生成 UUID。 另请学习用 Java 生成版本 5 UUID。

1. 什么是 UUID?

UUID通用唯一标识符),也称为 GUID全局唯一标识符)是128 bits长标识符,相对于所有其他 UUID 的空间而言。 它不需要中央注册过程。 结果,按需生成可以完全自动化,并用于多种目的。

要了解 UUID 的独特性,您应该知道 UUID 生成算法支持非常高的分配速率,每台机器每秒可支持高达 1000 万的速度,因此甚至可以用作事务 ID。

我们可以应用排序,排序并将它们存储在数据库中。 通常,它在编程中很有用。

由于 UUID 是唯一且持久的,因此与其他替代方法相比,它们具有出色的统一资源名称(URN),挖掘成本最低。

空的 UUID(UHTID)是 UUID 的一种特殊形式,它指定将所有 128 位都设置为零。

不要以为 UUID 很难猜测; 它们不应用作安全特性。 可预测的随机数源将加剧这种情况。 人类没有能力仅仅看一眼就能轻易地检查 UUID 的完整性。

2. UUID 类型

典型的 UUID 以8-4-4-4-12的形式显示在 5 组中,由连字符分隔,总共 36 个字符(32 个字母数字字符和 4 个连字符)。

123e4567-e89b-12d3-a456-426655440000

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

在此'M'表示 UUID 版本'N'表示 UUID 变体

  • 变体字段包含一个值,该值标识 UUID 的布局。
  • 版本字段包含一个描述此 UUID 类型的值。 UUID 有五种不同的基本类型。
    1. 基于时间的 UUID(版本 1) – 根据时间和节点 ID 生成
    2. DCE(分布式计算环境)安全性(版本 2) – 由标识符(通常是组或用户 ID),时间和节点 ID 生成
    3. 基于名称(版本 3) – 由 MD5 (128 位)的名称空间标识符和名称的哈希值生成
    4. 随机生成的 UUID(版本 4) – 使用随机或伪随机数生成
    5. 基于名称的使用 SHA-1 哈希(版本 5)推荐的 – 由 SHA-1 (160 位)哈希处理的名称空间标识符和名称。 Java 不提供其实现。 我们将创造自己的。

3. Java UUID 示例

3.1 UUID.randomUUID() – 版本 4

默认 API randomUUID()是一个静态工厂,用于检索类型 4(伪随机生成的)UUID。 对于大多数用例来说已经足够了。

UUID uuid = UUID.randomUUID();

System.out.println(uuid);
System.out.println(uuid.variant());		//2
System.out.println(uuid.version());		//4

程序输出。

17e3338d-344b-403c-8a87-f7d8006d6e33
2
4

3.2 生成版本 5 UUID

Java 没有提供内置 API 来生成版本 5 UUID,因此我们必须创建自己的实现。 以下是一种这样的实现。 (参考)。

import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

public class UUID5 
{
	public static UUID fromUTF8(String name) {
		return fromBytes(name.getBytes(Charset.forName("UTF-8")));
	}

	private static UUID fromBytes(byte[] name) {
		if (name == null) {
			throw new NullPointerException("name == null");
		}
		try {
			MessageDigest md = MessageDigest.getInstance("SHA-1");
			return makeUUID(md.digest(name), 5);
		} catch (NoSuchAlgorithmException e) {
			throw new AssertionError(e);
		}
	}

	private static UUID makeUUID(byte[] hash, int version) {
		long msb = peekLong(hash, 0, ByteOrder.BIG_ENDIAN);
		long lsb = peekLong(hash, 8, ByteOrder.BIG_ENDIAN);
		// Set the version field
		msb &= ~(0xfL << 12);
		msb |= ((long) version) << 12;
		// Set the variant field to 2
		lsb &= ~(0x3L << 62);
		lsb |= 2L << 62;
		return new UUID(msb, lsb);
	}

	private static long peekLong(final byte[] src, final int offset, final ByteOrder order) {
		long ans = 0;
		if (order == ByteOrder.BIG_ENDIAN) {
			for (int i = offset; i < offset + 8; i += 1) {
				ans <<= 8;
				ans |= src[i] & 0xffL;
			}
		} else {
			for (int i = offset + 7; i >= offset; i -= 1) {
				ans <<= 8;
				ans |= src[i] & 0xffL;
			}
		}
		return ans;
	}
}

UUID uuid = UUID5.fromUTF8("954aac7d-47b2-5975-9a80-37eeed186527");

System.out.println(uuid);
System.out.println(uuid.variant());
System.out.println(uuid.version());

d1d16b54-9757-5743-86fa-9ffe3b937d78
2
5

向我提供有关在 Java 中生成 UUID 的问题。

学习愉快!

参考: Rfc 4122

Java 12 教程

Java 12 – 新特性和增强特性

原文: https://howtodoinjava.com/java12/new-features-enhancements/

Java 12(于 2019 年 3 月 19 日发布)是 JDK 的最新版本。 让我们看看它为开发人员和建筑师带来的新特性和改进。

1. 流 API 中的Collectors.teeing()

teeing收集器已作为静态方法公开Collectors::teeing。 在将其结果与函数合并之前,此收集器将其输入转发给其他两个收集器。

teeing(Collector, Collector, BiFunction)接受两个收集器和一个函数来合并其结果。 传递给结果收集器的每个元素都由两个下游收集器处理,然后使用指定的合并函数将其结果合并为最终结果。

例如,在给定的雇员列表中,如果我们想找出最高薪水和最低薪水的雇员,则可以使用teeing收集器在单个语句中完成。

SalaryRange salaryRange = Stream
            .of(56700, 67600, 45200, 120000, 77600, 85000)
            .collect(teeing(
                    minBy(Integer::compareTo), 
                    maxBy(Integer::compareTo), 
                    SalaryRange::fromOptional));

阅读更多:Collectors.teeing()

2. 字符串 API 的更改

2.1 String.indent()

缩进方法有助于更改字符串的缩进。 我们可以传递一个正值或一个负值,具体取决于我们是要添加更多的空白还是要删除现有的空白。

String result = "foo\nbar\nbar2".indent(4);

System.out.println(result);

//    foo
//    bar
//    bar2

请注意,indent()方法会自动添加换行符(如果尚未提供)。 这是预料之中的,并且是新方法的特性。

每个空格字符都被视为一个字符。 特别是,制表符"\t" (U+0009)被视为单个字符; 它不会扩展。

2.2 String.transform()

transform()方法采用String并借助Function将其转换为新的String

在给定的示例中,我们有一个名称列表。 我们正在使用transform()方法执行两个操作(修剪空白并使所有名称都大写)。

 List<String> names = List.of(
		            	"   Alex",
		            	"brian");

List<String> transformedNames = new ArrayList<>();

for (String name : names) 
{
    String transformedName = name.transform(String::strip)
            					.transform(StringUtils::toCamelCase);

    transformedNames.add(transformedName);
}

2.3 字符串常量

从 Java 12 开始,String类实现了两个附加接口java.lang.constant.Constablejava.lang.constant.ConstantDesc

String类还引入了两个附加的低级方法describeConstable()resolveConstantDesc(MethodHandles.Lookup)

它们是低级 API,适用于提供字节码解析和生成特性的库和工具,例如 ByteBuddy。

请注意,Constable类型是一种值,该值是可以在 Java 类文件的常量池中表示的常量,如 JVMS 4.4 中所述,并且其实例可以名义上将自己描述为ConstantDesc

resolveConstantDesc()describeConstable()相似,不同之处在于此方法改为返回ConstantDesc的实例。

3. Files.mismatch(Path, Path)

有时,我们想确定两个文件是否具有相同的内容。 该 API 有助于比较文件的内容。

mismatch()方法比较两个文件路径并返回long值。 long表示两个文件内容中第一个不匹配字节的位置。 如果文件“相等”,则返回值为 – 1

Path helloworld1 = tempDir.resolve("helloworld1.txt");

Path helloworld2 = tempDir.resolve("helloworld2.txt");

long diff = Files.mismatch(helloworld1, helloworld2);	//returns long value

4. 精简数字格式

用户界面或命令行工具提供的大量数字始终很难解析。 使用数字的缩写形式更为常见。 精简数字表示形式更易于阅读,并且在屏幕上占用的空间更少,而不会失去其原始含义。

例如。 3.6 M3,600,000 更容易阅读。

Java 12 引入了一种方便的方法,称为NumberFormat.getCompactNumberInstance(Locale,NumberFormat.Style),用于创建精简数字表示形式。

NumberFormat formatter = NumberFormat.getCompactNumberInstance(Locale.US,
            											NumberFormat.Style.SHORT);

String formattedString = formatter.format(25000L);		//25K

5. 支持 Unicode 11

在表情符号在社交媒体渠道交流中发挥关键作用的时代,支持最新的 Unicode 规范比以往任何时候都更为重要。 Java 12 保持同步并支持 Unicode 11。

Unicode 11 增加了 684 个字符,总计 137,374 个字符 – 以及七个新脚本,总共 146 个脚本。

6. switch表达式(预览)

此更改扩展了 switch语句 ,以便可以将其用作语句或表达式。

不必为每个case块定义break语句,我们只需使用箭头语法即可。 箭头语法在语义上看起来像 lambda,并且将case标签与表达式分开。

使用新的switch表达式,我们可以将switch语句直接分配给变量。

boolean isWeekend = switch (day) 
{
	case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;

	case SATURDAY, SUNDAY -> true;

	default -> throw new IllegalStateException("Illegal day entry :: " + day);
};

System.out.println(isWeekend); 	//true or false - based on current day

要使用此预览特性,请记住,我们必须在应用程序启动期间使用 – enable-preview标志明确指示 JVM。

向我提供有关 Java 12 中这些新 API 更改的问题

学习愉快!

什么是 Java JDK,JRE 和 JVM – 深入分析

原文: https://howtodoinjava.com/java/basics/jdk-jre-jvm/

了解 JDK,JRE 和 JVM 之间的区别。 JVM 是如何工作的? 什么是类加载器解释器JIT 编译器。 还要签出一些面试问题

Table of Contents

1\. Execution of a Java Program
2\. What is JVM?
3\. What is JRE?
4\. What is JDK?
5\. Differences between JDK, JRE and JVM
6\. Interview questions related to JDK, JRE and JVM
7\. JDK and JRE downloads

1. 执行 Java 程序

在深入了解 Java 内部之前,让我们了解如何执行 Java 源文件。

  1. 我们使用编辑器或 IDE(集成开发环境)在Simple.Java文件中编写 Java 源代码。 EclipseIntelliJ Idea
  2. 程序必须编译成字节码。 Java 编译器(javac)将源代码编译为Simple.class文件。
  3. JVM(Java 虚拟机)可以在任何平台/操作系统中执行此类文件。
  4. JVM 将字节码转换为机器可执行的本机机器代码。

Java Execution Flow

Java 执行流程

2. 什么是 JVM?

Java 虚拟机(JVM)是​​运行 Java 字节码的虚拟机。 您可以通过将.java文件编译为.class文件来获得此字节码。 .class文件包含 JVM 可以理解的字节码。

在现实世界中,JVM 是提供可在其中执行 Java 字节码的运行时环境的规范。 不同的供应商提供此规范的不同实现。 例如,此 Wiki 页面列出了不同的 JVM 实现

JVM 最流行的实现是 Hotspot ,它由 Oracle Corporation 拥有和提供。 (先前由 Sun Microsystems,Inc. )。

JVM 使用许多先进技术为 Java 应用程序提供最佳性能,这些技术结合了最新的内存模型,垃圾收集器自适应优化器

JVM 具有两种不同的风格-客户端服务器。 尽管服务器 VM 和客户端 VM 相似,但已经对服务器 VM 进行了特殊调整,以最大程度地提高峰值运行速度。 它用于执行长时间运行的服务器应用程序,这些应用程序需要比快速启动时间或较小的运行时内存占用更多​​的最快的运行速度。 开发人员可以通过指定-client-server选择他们想要的系统。

JVM 之所以称为虚拟,是因为它提供的机器接口不依赖于底层操作系统和机器硬件架构。 这种与硬件和操作系统的独立性是 Java 程序一次写入,随处运行的价值的基石。

2.1 JVM 架构

JVM Architecture

JVM 架构

2.1.1 类加载器

类加载器是用于加载类文件的子系统。 它执行三个主要功能,即类加载,链接和初始化。

  1. 载入
    • 为了加载类,JVM 有 3 种类加载器。 引导程序扩展名应用程序类加载器。
    • 加载类文件时,JVM 会发现某个任意类XYZ.class 的依赖项。
    • 第一个引导程序类加载器尝试查找该类。 它将扫描 JRE lib文件夹中的rt.jar文件。
    • 如果找不到类,那么扩展类加载器会在jre\lib\ext文件夹中搜索类文件。
    • 同样,如果未找到类,则应用程序类加载器将在系统的CLASSPATH环境变量中搜索所有 Jar 文件和类。
    • 如果任何加载程序找到了类,则由类加载程序加载类; 否则抛出ClassNotFoundException
  2. 链接

    由类加载器加载类后,将执行链接。 字节码验证器将验证生成的字节码是否正确,如果验证失败,我们将收到验证错误。 它还对类中的静态变量和方法执行内存分配。

  3. 初始化

    这是类加载的最后阶段,此处将为所有静态变量分配原始值,并执行静态块。

2.1.2 JVM 内存区域

JVM 中的内存区域分为多个部分,以存储应用程序数据的特定部分。

  • 方法区,用于存储类结构,如元数据,常量运行时池和方法代码。
  • 存储在应用程序执行期间创建的所有对象。
  • 存储局部变量和中间结果。 所有这些变量对于创建它们的线程都是本地的。 每个线程都有自己的 JVM 栈,并在创建线程时同时创建。 因此,所有此类局部变量都称为线程局部变量
  • PC 寄存器存储当前正在执行的语句的物理内存地址。 在 Java 中,每个线程都有其单独的 PC 寄存器。
  • Java 也支持并使用本机代码。 许多底层代码都是用 C 和 C++ 等语言编写的。 本机方法栈保存本机代码的指令。

2.2 JVM 执行引擎

分配给 JVM 的所有代码均由执行引擎执行。 执行引擎读取字节码并一一执行。 它使用两个内置的解释器JIT 编译器 将字节码转换为机器代码并执行

Platform Specific Interpreters

平台特定的解释器

使用 JVM,解释器和编译器均会生成本机代码。 不同之处在于它们如何生成本机代码,其优化程度以及优化的代价。

2.2.1 解释器

JVM 解释器通过查找预定义的 JVM 指令到机器指令的映射,几乎将每个字节码指令转换为相应的本机指令。 它直接执行字节码,并且不执行任何优化。

2.2.2 JIT 编译器

为了提高性能,JIT 编译器在运行时与 JVM 交互,并将适当的字节码序列编译为本地机器代码。 通常,JIT 编译器采用一段代码(每次一次都没有一个语句作为解释器),优化代码,然后将其转换为优化的机器代码。

默认情况下启用 JIT 编译器。 您可以禁用 JIT 编译器,在这种情况下,将解释整个 Java 程序。 除了诊断或解决 JIT 编译问题外,不建议禁用 JIT 编译器。

3. 什么是 JRE?

Java 运行时环境(JRE)是一个包,它将库(jar)和 Java 虚拟机以及其他组件捆绑在一起,以运行用 Java 编写的应用程序。 JVM 只是 JRE 发行版的一部分。

要执行任何 Java 应用程序,您需要在计算机中安装 JRE。 在任何计算机上执行 Java 应用程序都是最低要求。

JRE 捆绑了以下组件:

  1. Java HotSpot 客户端虚拟机使用的 DLL 文件。
  2. Java HotSpot 服务器虚拟机使用的 DLL 文件。
  3. Java 运行时环境使用的代码库属性设置资源文件。 例如rt.jarcharsets.jar
  4. Java 扩展文件,例如localedata.jar
  5. 包含用于安全管理的文件。 这些文件包括安全策略java.policy)和安全属性java.security)文件。
  6. 包含小程序的支持类的 Jar 文件。
  7. 包含 TrueType 字体文件供平台使用。

JRE 可以作为 JDK 的一部分下载,也可以单独下载。 JRE 与平台有关。 这意味着您必须根据计算机的类型(操作系统和架构)选择要导入和安装的 JRE 包。

例如,您不能在32-bit计算机上安装64-bit JRE 发行版。 同样, Windows 的 JRE 分发在 Linux 中将不起作用; 反之亦然。

4. 什么是 JDK?

JDK 是 JRE 的超集。 JDK 包含 JRE 拥有的所有内容以及用于开发,调试和监视 Java 应用程序的开发工具。 需要开发 Java 应用程序时就需要 JDK。

JDK 附带的几个重要组件如下:

  • appletviewer – 此工具可用于在没有 Web 浏览器的情况下运行和调试 Java applet
  • apt – 注释处理工具
  • extcheck – 一种检测 JAR 文件冲突的工具
  • javadoc – 文档生成器,可从源代码注释自动生成文档
  • jar – 归档程序,它将相关的类库打包到单个 JAR 文件中。 该工具还有助于管理 JAR 文件
  • jarsigner – jar 签名和验证工具
  • javap – 类文件反汇编程序
  • javaws -用于 JNLP 应用程序的 Java Web Start 启动器
  • JConsole -Java 监视和管理控制台
  • jhat – Java 堆分析工具
  • jrunscript – Java 命令行脚本外壳
  • jstack – 打印 Java 线程的 Java 栈跟踪的工具
  • keytool – 用于操作密钥库的工具
  • policytool – 策略创建和管理工具
  • xjc – XML 绑定 Java API(JAXB)API 的一部分。 它接受 XML 模式并生成 Java 类

与 JRE 一样,JDK 也依赖于平台。 因此,在为您的计算机下载 JDK 包时请多加注意。

5. JDK,JRE 和 JVM 之间的区别

基于以上讨论,我们可以得出以下三个方面的关系:

JRE = JVM + 运行 Java 应用程序的库。

JDK = JRE + 开发 Java 应用程序的工具。

JDK vs JRE vs JVM

JDK vs JRE vs JVM

简而言之,如果您是编写代码的 Java 应用程序开发人员,则需要在计算机中安装 JDK。 但是,如果只想运行用 Java 内置的应用程序,则只需要在计算机上安装 JRE。

6. 与 JDK,JRE 和 JVM 有关的面试问题

如果您了解我们到目前为止在本文中讨论的内容,那么面对任何面试问题都将很困难。 尽管如此,请准备回答以下问题:

  1. 什么是 JVM 架构?

    已经详细解释了。

  2. Java 中有几种类型的类加载器?

    有 3 种装载机。 引导程序,扩展程序和应用程序类加载器。

  3. Java 中的类加载器如何工作?

    类加载器会在其预定义位置扫描 jar 文件和类。 他们扫描路径中的所有那些类文件,并查找所需的类。 如果找到它们,请加载,链接并初始化类文件。

  4. JRE 和 JVM 之间的区别?

    JVM 是用于运行 Java 应用程序的运行时环境的规范。 热点 JVM 是规范的这样一种实现。 它加载类文件,并使用解释器和 JIT 编译器将字节码转换为机器代码并执行。

  5. 解释器和 JIT 编译器之间的区别?

    解释器逐行解释字节码并顺序执行。 这会导致性能下降。 JIT 编译器通过分析块中的代码来为该过程添加优化,然后准备更多优化的机器代码。

7. JDK 和 JRE 下载

您可以在 Oracle 的 Java 发行页面中找到特定于平台的 JDK 和 JRE 包。

例如,此列出了 Java 8 的所有可用 JDK 发行版。

JDK 8 Distributions

JDK 8 发行版

类似地,此页中提供了 JRE 8 发行版。

JRE 8 Distributions

JRE 8 发行版

学习愉快!

进一步阅读:

JIT 编译器如何优化代码

JIT 编译

了解 JIT 编译器

JDK 和 JRE 文件结构

收集器teeing()方法示例

原文: https://howtodoinjava.com/java12/collectors-teeing-example/

了解Collectors.teeing()方法(在 Java 12 中添加),方法语法以及如何在 Java 的各种用例中应用teeing()方法。

1. teeing()的目的

这是java.util.stream.Collectors接口的新静态方法,它允许使用两个独立的收集器进行收集,然后使用提供的BiFunction合并其结果。

传递给结果收集器的每个元素都由两个下游收集器处理,然后使用指定的合并函数将其结果合并为最终结果。

请注意,此函数有助于一步完成特定任务。 如果不使用teeing()函数,我们已经可以分两步执行给定的任务。 它只是一个帮助函数,可以帮助减少冗长程度。

2. 语法

/**
* downstream1 - the first downstream collector
* downstream2 - the second downstream collector
* merger - the function which merges two results into the single one

* returns - a Collector which aggregates the results of two supplied collectors.
*/

public static Collector teeing​ (Collector downstream1, 
								Collector downstream2, 
								BiFunction merger);

3. 使用teeing()查找薪水最高和最低的员工

在此Collectors.teeing()示例中,我们有一个雇员列表。 我们要一步找到最高薪的员工和最低薪的员工。

以下 Java 程序执行查找最大和最小操作,然后将两个项目收集到Map中。

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;

public class Main 
{
	public static void main(String[] args) 
	{
		List<Employee> employeeList = Arrays.asList(
										new Employee(1, "A", 100),
										new Employee(2, "B", 200),
										new Employee(3, "C", 300),
										new Employee(4, "D", 400)); 

		HashMap<String, Employee> result = employeeList.stream().collect( 
							Collectors.teeing(
									Collectors.maxBy(Comparator.comparing(Employee::getSalary)),
									Collectors.minBy(Comparator.comparing(Employee::getSalary)),
									(e1, e2) -> {
										HashMap<String, Employee> map = new HashMap();
										map.put("MAX", e1.get());
										map.put("MIN", e2.get());
										return map;
									}
							));

		System.out.println(result);
	}
}

程序输出。

C:\BAML\DFCCUI\installs\jdk-12.0.1\bin>java Main.java

{	
	MIN=Employee [id=1, name=A, salary=100.0], 
	MAX=Employee [id=4, name=D, salary=400.0]
}

这里的Employee类是这样的。

class Employee 
{
	private long id;
	private String name;
	private double salary;

	public Employee(long id, String name, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}

	//Getters and setters

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}
}

4. 使用teeing()过滤项目并计数

在此示例中,我们将使用同一组员工。 在这里,我们将找到所有薪水高于 200 的员工,然后我们还将计算这些员工的数量。

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;

public class Main 
{
	public static void main(String[] args) 
	{
		List<Employee> employeeList = Arrays.asList(
										new Employee(1, "A", 100),
										new Employee(2, "B", 200),
										new Employee(3, "C", 300),
										new Employee(4, "D", 400)); 

		HashMap<String, Object> result = employeeList.stream().collect( 
							Collectors.teeing(
									Collectors.filtering(e -> e.getSalary() > 200, Collectors.toList()),
									Collectors.filtering(e -> e.getSalary() > 200, Collectors.counting()),
									(list, count) -> {
										HashMap<String, Object> map = new HashMap();
										map.put("list", list);
										map.put("count", count);
										return map;
									}
							));

		System.out.println(result);
	}
}

程序输出:

C:\BAML\DFCCUI\installs\jdk-12.0.1\bin>java Main.java

{
	count=2, 
	list=[Employee [id=3, name=C, salary=300.0], Employee [id=4, name=D, salary=400.0]]
}

5. 总结

上面的Collectors.teeing()方法示例非常简单,为便于基本理解而编写。 您需要使用非常适合您自己需要的函数。

只需记住,当您需要执行两次流操作并在两个不同的收集器中收集结果时,请考虑使用teeing()方法。 它并不总是适合用例,但适合时可能会有用。

学习愉快!

参考: Java 文档

字符串indent(count) – Java 中的行左缩进

原文: https://howtodoinjava.com/java12/string-left-indent-lines/

学习使用String.indent()API 在 Java 中缩进(左缩进)字符串。 此 API 已在 Java 12 中引入。

1. String.indent(count) API

此方法根据count的值调整给定字符串的每一行的缩进,并标准化行终止符。

/**
* count - number of leading white space characters to add or remove
* returns - string with indentation adjusted and line endings normalized
*/
public String indent​(int count)

注意,count的值可以是正数或负数。

  • 正数 – 如果count > 0,则在每行的开头插入的空格。
  • 负数 – 如果count < 0,则在每行的开头将删除空格。
  • 负数 – 如果count > available white spaces,则删除所有前导空格

每个空格字符都被视为一个字符。 特别是,制表符\t被视为单个字符; 它不会扩展。

2. String.indent()示例

Java 程序将空白字符串转换成缩进 8 个字符的文件。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.stream.Stream;

public class Main 
{
	public static void main(String[] args) 
	{
		try 
		{
			Path file = Files.createTempFile("testOne", ".txt");

			//Write strings to file indented to 8 leading spaces
			Files.writeString(file, "ABC".indent(8), StandardOpenOption.APPEND);
			Files.writeString(file, "123".indent(8), StandardOpenOption.APPEND);
			Files.writeString(file, "XYZ".indent(8), StandardOpenOption.APPEND);	

			//Verify the content
			Stream<String> lines = Files.lines(file);

			lines.forEach(System.out::println);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

程序输出。

        ABC
        123
        XYZ

请把关于将文件读入流的行中的问题提供给我。

学习愉快!

参考: Java 文档

精简数字格式

原文: https://howtodoinjava.com/java12/compact-number-format/

了解如何将语言环境敏感的精简/较短格式应用于通用编号,例如小数,货币和百分比。 它是在 Java 12 中的CompactNumberFormat类中添加的。

例如,可以将数字(例如 1000)格式化为“ 1K”(短样式)或“ 1000”(长样式)。

1. CompactNumberFormat

CompactNumberFormatNumberFormat的具体子类,它以精简形式格式化十进制数。 精简数字格式设计用于空间受限的环境,并且格式化的字符串可以在该有限的空间中显示。

精简数字格式是指基于为给定语言环境提供的模式,以较短的形式表示数字。

1.1 创建新的CompactNumberFormat实例

要获取语言环境的CompactNumberFormat,请使用NumberFormat给出的工厂方法之一。

NumberFormat fmt = NumberFormat.getCompactNumberInstance(
                        new Locale("hi", "IN"), NumberFormat.Style.SHORT);

NumberFormat fmt = NumberFormat.getCompactNumberInstance(
                        Locale.US, NumberFormat.Style.LONG);                        

1.2 自定义CompactNumberFormat实例

我们还可以创建自定义的数字格式,在其中可以定义如何使用CompactNumberFormat(String, DecimalFormatSymbols, String[])构造器以较短的形式表示数字。

final String[] compactPatterns
            = {"", "", "", "0k", "00k", "000k", "0m", "00m", "000m", 
                "0b", "00b", "000b", "0t", "00t", "000t"}; 

final DecimalFormat decimalFormat = (DecimalFormat)
            NumberFormat.getNumberInstance(Locale.GERMANY);

final CompactNumberFormat customCompactNumberFormat  
            = new CompactNumberFormat( decimalFormat.toPattern(),                 
                                       decimalFormat.getDecimalFormatSymbols(),  
                                       compactPatterns);                      

  • 精简数字compactPatterns以一系列模式表示,其中每个模式用于格式化一系列数字。
  • 数组中最多可以提供 15 个样式,但是第一个提供的样式始终对应于10 ^ 0
  • 基于数组元素的数量,这些值的范围为10 ^ 010 ^ 14

2. 精简数字格式示例

2.1 简单格式化

Java 程序以精简数字格式格式化数字。

import java.text.NumberFormat;
import java.util.Locale;

public class Main 
{
	public static void main(String[] args) 
	{
		NumberFormat fmt = NumberFormat
		        .getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);

		System.out.println( fmt.format(100) );
		System.out.println( fmt.format(1000) );
		System.out.println( fmt.format(10000) );
		System.out.println( fmt.format(100000) );

		NumberFormat fmtShort = NumberFormat
		        .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);

		System.out.println( fmtShort.format(100) );
		System.out.println( fmtShort.format(1000) );
		System.out.println( fmtShort.format(10000) );
		System.out.println( fmtShort.format(100000) );
	}
}

程序输出。

100
1 thousand
10 thousand
100 thousand

100
1K
10K
100K

2.2 设置小数

设置数字的小数部分中允许的最小位数。 默认情况下,小数部分设置为0个数字。

import java.text.NumberFormat;
import java.util.Locale;

public class Main 
{
	public static void main(String[] args) 
	{
		NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
		fmt.setMinimumFractionDigits(3);

		System.out.println( fmt.format(10000) );
		System.out.println( fmt.format(10012) );
		System.out.println( fmt.format(100201) );
		System.out.println( fmt.format(1111111) );
	}
}

程序输出:

10.000K
10.012K
100.201K
1.111M

3. 精简数字解析示例

Java 程序将精简数字解析为长模式。

import java.text.NumberFormat;
import java.util.Locale;

public class Main 
{
	public static void main(String[] args) throws Exception
	{
		NumberFormat fmt = NumberFormat
		        .getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);

		System.out.println( fmt.parse("100") );
		System.out.println( fmt.parse("1 thousand") );
		System.out.println( fmt.parse("10 thousand") );
		System.out.println( fmt.parse("100 thousand") );
	}
}

程序输出:

100
1000
10000
100000

向我提供有关 Java 12 中精简数字格式的问题

学习愉快!

Java 11 教程

Java 11 的新特性和增强特性

原文: https://howtodoinjava.com/java11/features-enhancements/

Java 11(于 2018 年 9 月发布)包含许多重要且有用的更新。 让我们看看它为开发人员和建筑师带来的新特性和改进。

1. HTTP 客户端 API

Java 长时间使用HttpURLConnection类进行 HTTP 通信。 但是随着时间的流逝,要求变得越来越复杂,对应用程序的要求也越来越高。 在 Java 11 之前,开发人员不得不诉诸特性丰富的库,例如 Apache HttpComponentsOkHttp 等。

我们看到 Java 9 版本包含HttpClient实现作为实验特性。 随着时间的流逝,它已经成为 Java 11 的最终特性。现在,Java 应用程序可以进行 HTTP 通信,而无需任何外部依赖。

1.1 如何使用HttpClient

java.net.http模块的典型 HTTP 交互看起来像:

  • 创建HttpClient的实例,并根据需要对其进行配置。
  • 创建HttpRequest的实例并填充信息。
  • 将请求传递给客户端,执行请求,并检索HttpResponse的实例。
  • 处理HttpResponse中包含的信息。

HTTP API 可以处理同步和异步通信。 让我们来看一个简单的例子。

1.2 同步请求示例

请注意,http 客户端 API 如何使用构建器模式创建复杂的对象。

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

HttpClient httpClient = HttpClient.newBuilder()
				        .connectTimeout(Duration.ofSeconds(10))
				        .build();                                  

try 
{
    String urlEndpoint = "https://postman-echo.com/get";
    URI uri = URI.create(urlEndpoint + "?foo1=bar1&foo2=bar2");
    HttpRequest request = HttpRequest.newBuilder()
						            .uri(uri)
						            .build();                              
    HttpResponse<String> response = httpClient.send(request,
            							HttpResponse.BodyHandlers.ofString()); 
} catch (IOException | InterruptedException e) {
    throw new RuntimeException(e);
}

System.out.println("Status code: " + response.statusCode());                            
System.out.println("Headers: " + response.headers().allValues("content-type"));               
System.out.println("Body: " + response.body()); 

1.2 异步请求示例

如果我们不想等待响应,那么异步通信很有用。 我们提供了回调处理程序,可在响应可用时执行。

注意使用sendAsync()方法发送异步请求。

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;

final List<URI> uris = Stream.of(
				        "https://www.google.com/",
				        "https://www.github.com/",
				        "https://www.yahoo.com/"
				        ).map(URI::create).collect(toList());      

HttpClient httpClient = HttpClient.newBuilder()
				        .connectTimeout(Duration.ofSeconds(10))
				        .followRedirects(HttpClient.Redirect.ALWAYS)
				        .build();

CompletableFuture[] futures = uris.stream()
					        .map(uri -> verifyUri(httpClient, uri))
					        .toArray(CompletableFuture[]::new);     

CompletableFuture.allOf(futures).join();           

private CompletableFuture<Void> verifyUri(HttpClient httpClient, 
                                          URI uri) 
{
    HttpRequest request = HttpRequest.newBuilder()
						            .timeout(Duration.ofSeconds(5))
						            .uri(uri)
						            .build();

    return httpClient.sendAsync(request,HttpResponse.BodyHandlers.ofString())
			            .thenApply(HttpResponse::statusCode)
			            .thenApply(statusCode -> statusCode == 200)
			            .exceptionally(ex -> false)
			            .thenAccept(valid -> 
			            {
			                if (valid) {
			                    System.out.println("[SUCCESS] Verified " + uri);
			                } else {
			                    System.out.println("[FAILURE] Could not " + "verify " + uri);
			                }
			            });                                    
}

2. 启动不编译的单文件程序

传统上,对于我们要执行的每个程序,我们都需要先对其进行编译。 出于测试目的,小型程序似乎不必要地耗时。

Java 11 对其进行了更改,现在我们可以执行单个文件中包含的 Java 源代码,而无需先对其进行编译。

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

要执行上述类,请直接使用java命令运行它。

$ java HelloWorld.java

Hello World!

注意,程序不能使用java.base module之外的任何外部依赖项。 并且该程序只能是单文件程序

3. 字符串 API 的更改

3.1 String.repeat(int)

此方法仅重复字符串n次。 它返回一个字符串,其值是重复 N 次给定字符串的连接。

如果此字符串为空或count为零,则返回空字符串。

public class HelloWorld 
{
    public static void main(String[] args) 
    {
    	String str = "1".repeat(5);

        System.out.println(str);	//11111
    }
}

3.2 String.isBlank()

此方法指示字符串是空还是仅包含空格。 以前,我们一直在 Apache 的StringUtils.java中使用它。

public class HelloWorld 
{
    public static void main(String[] args) 
    {
    	"1".isBlank();	//false

        "".isBlank();	//true

        "    ".isBlank();	//true
    }
}

3.3 String.strip()

此方法需要除去前导和尾随空白。 通过使用String.stripLeading()仅删除开头字符,或者使用String.stripTrailing()仅删除结尾字符,我们甚至可以更加具体。

public class HelloWorld 
{
    public static void main(String[] args) 
    {
    	"   hi  ".strip();	//"hi"

       "   hi  ".stripLeading();	//"hi   "

       "   hi  ".stripTrailing();	//"   hi"
    }
}

3.4 String.lines()

此方法有助于将多行文本作为来处理。

public class HelloWorld 
{
    public static void main(String[] args) 
    {
    	String testString = "hello\nworld\nis\nexecuted";

	    List<String> lines = new ArrayList<>();

	    testString.lines().forEach(line -> lines.add(line));

	    assertEquals(List.of("hello", "world", "is", "executed"), lines);
    }
}

4. Collection.toArray(IntFunction)

在 Java 11 之前,将集合转换为数组并不容易。 Java 11 使转换更加方便。

public class HelloWorld 
{
    public static void main(String[] args) 
    {
    	List<String> names = new ArrayList<>();
	    names.add("alex");
	    names.add("brian");
	    names.add("charles");

	    String[] namesArr1 = names.toArray(new String[names.size()]);		//Before Java 11

	    String[] namesArr2 = names.toArray(String[]::new);					//Since Java 11
    }
}

5. Files.readString()Files.writeString()

使用这些重载方法,Java 11 的目标是减少大量样板代码,从而使文件读写更加容易。

public class HelloWorld 
{
    public static void main(String[] args) 
    {
    	//Read file as string
    	URI txtFileUri = getClass().getClassLoader().getResource("helloworld.txt").toURI();

    	String content = Files.readString(Path.of(txtFileUri),Charset.defaultCharset());

    	//Write string to file
    	Path tmpFilePath = Path.of(File.createTempFile("tempFile", ".tmp").toURI());

    	Path returnedFilePath = Files.writeString(tmpFilePath,"Hello World!", 
    								Charset.defaultCharset(), StandardOpenOption.WRITE);
    }
}

6. Optional.isEmpty()

Optional是一个容器对象,可能包含也可能不包含非null值。 如果不存在任何值,则该对象被认为是空的。

如果存在值,则先前存在的方法isPresent()返回true,否则返回false。 有时,它迫使我们编写不可读的负面条件。

isEmpty()方法与isPresent()方法相反,如果存在值,则返回false,否则返回true

因此,在任何情况下我们都不会写负面条件。 适当时使用这两种方法中的任何一种。

public class HelloWorld 
{
    public static void main(String[] args) 
    {
    	String currentTime = null;

	    assertTrue(!Optional.ofNullable(currentTime).isPresent());	//It's negative condition
	    assertTrue(Optional.ofNullable(currentTime).isEmpty());		//Write it like this

	    currentTime = "12:00 PM";

	    assertFalse(!Optional.ofNullable(currentTime).isPresent());	//It's negative condition
	    assertFalse(Optional.ofNullable(currentTime).isEmpty());	//Write it like this
    }
}

向我提供有关 Java 11 中这些新 API 更改的问题

学习愉快!

参考: Java 11 发行文档

String.isBlank() – 在 Java 中检查空白或空字符串

原文: https://howtodoinjava.com/java11/check-blank-string/

学会使用String.isBlank()方法确定给定的字符串是空白还是空,或者仅包含空格。isBlank()方法已添加到 Java 11 中。

要检查给定的字符串是否没有偶数空格,请使用String.isEmpty()方法。

1. String.isBlank()方法

如果给定的字符串为空或仅包含空格代码点,则此方法返回true,否则返回false

它使用Character.isWhitespace(char)方法确定空白字符。

/**
* returns 	- true if the string is empty or contains only white space codepoints
*			- otherwise false
*/

public boolean isBlank()

2. String.isBlank()示例

检查给定字符串是否为空的 Java 程序。

public class Main 
{
	public static void main(String[] args) 
	{
		System.out.println( "ABC".isBlank() );			//false

		System.out.println( " ABC ".isBlank() );		//false

		System.out.println( "  ".isBlank() );			//true

		System.out.println( "".isBlank() );				//true
	}
}

程序输出。

false
false
true
true

3. isBlank()isEmpty()

两种方法都用于检查 Java 中的空白或空字符串。 两种方法之间的区别在于,当且仅当字符串长度为 0 时,isEmpty()方法返回true

isBlank()方法仅检查非空白字符。 它不检查字符串长度。

public class Main 
{
	public static void main(String[] args) 
	{
		System.out.println( "ABC".isBlank() );		//false
		System.out.println( "  ".isBlank() );		//true

		System.out.println( "ABC".isEmpty() );		//false
		System.out.println( "  ".isEmpty() );		//false
	}
}

程序输出:

false
true
false
false

学习愉快!

String.lines() – 获取行流 – Java 11

原文: https://howtodoinjava.com/java11/string-to-stream-of-lines/

学习 Java 11 中的使用 String.lines()方法将多行字符串转换为行

当我们要从文件中读取内容并分别处理每个字符串时,此方法很有用。

1. String.lines() API

lines()方法是静态方法。 它返回从行终止符分隔的给定多行字符串中提取的行流

/**
* returns - the stream of lines extracted from given string
*/
public Stream<String> lines()

行终止符是以下之一:

  • 换行符(\n
  • 回车符(\r
  • 回车符后紧跟换行符(\r\n

根据定义,行是零个或多个字符,后接行终止符。 一行不包括行终止符。

lines()方法返回的流包含此字符串中的行,其顺序与多行中出现的顺序相同。

2. Java 程序获取行流

Java 程序读取文件,并以行流的形式获取内容。

import java.io.IOException;
import java.util.stream.Stream;

public class Main 
{
	public static void main(String[] args) 
	{
		try 
		{
			String str = "A \n B \n C \n D"; 

			Stream<String> lines = str.lines();

			lines.forEach(System.out::println);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

程序输出。

A
B
C
D

将有关将字符串读入流的行中的问题,提供给我。

学习愉快!

String.repeat() – 在 Java 中重复字符串 N 次

原文: https://howtodoinjava.com/java11/repeat-string-n-times/

通过简单的 Java 程序,学习将给定的字符串重复 N 次,以产生包含所有重复的新字符串。 我们将使用方法Sting.repeat(N)(自 Java 11 起),并使用可用于 Java 10 的正则表达式。

1. String.repeat() API(自 Java 11 起)

此方法返回一个字符串,该字符串的值是重复count次的给定字符串的连接。 如果字符串为空或count为零,则返回空字符串。

1.1 语法

/**
* Parameters:
* count - number of times to repeat
* 
* Returns:
* A string composed of this string repeated count times or the empty string if this string is empty or count is zero
* 
* Throws:
* IllegalArgumentException - if the count is negative.
*/

public String repeat​(int count)

1.2 示例

Java 程序将字符串"Abc"重复 3 次。

public class Main 
{
	public static void main(String[] args) 
	{
		String str = "Abc";

		System.out.println( str.repeat(3) );
	}
}

程序输出。

AbcAbcAbc

2. 使用正则表达式重复字符串(直到 Java 10)

如果您正在使用 JDK < = 10,则可以考虑使用正则表达式将字符串重复 N 次。

Java program to repeat string ‘Abc’ to 3 times.

public class Main 
{
	public static void main(String[] args) 
	{
		String str = "Abc";

		String repeated = new String(new char[3]).replace("\0", str);

		System.out.println(repeated);
	}
}

程序输出:

AbcAbcAbc

3. Apache Common 的StringUtils

如果不是正则表达式,则可以使用StringUtils类及其方法repeat(times)

import org.apache.commons.lang3.StringUtils;

public class Main 
{
	public static void main(String[] args) 
	{
		String str = "Abc";

		String repeated = StringUtils.repeat(str, 3);

		System.out.println(repeated);
	}
}

程序输出:

AbcAbcAbc

向我提供有关如何在 Java 中将字符串重复 N 次的问题。

学习愉快!

String.strip() – 删除开头和结尾的空格

原文: https://howtodoinjava.com/java11/strip-remove-white-spaces/

Java 11 中,学习如何使用String类的strip()stripLeading()stripTrailing()方法从给定的字符串中删除不需要的空格。

1. String.strip()API

从 Java 11 开始,String类包含 3 个以上的方法,这些方法有助于消除多余的空白。 这些方法使用Character.isWhitespace(char) 方法确定空白字符。

  • String.strip() – 返回其值为字符串的字符串,其中删除了所有前导和尾随空白。 请注意,String.trim()方法也会产生相同的结果。
  • String.stripLeading() – 返回其值为字符串的字符串,其中删除了所有前导空白
  • String.stripTrailing() – 返回其值为字符串的字符串,其中删除了所有尾随空白
public class Main 
{
	public static void main(String[] args) 
	{
		String str = "  Hello World !!   ";

		System.out.println( str.strip() );			//"Hello World !!"

		System.out.println( str.stripLeading() );	//"Hello World !!   "

		System.out.println( str.stripTrailing() );	//"  Hello World !!"
	}
}

2. 使用正则表达式修剪空白(包括制表符)

在不使用 Java 11 的情况下,可以使用正则表达式来修剪字符串周围的空白。

正则表达式 描述
[1]+|[\t]+$ 删除开头和结尾的空格
^[\t]+ 仅删除开头的空格
[\t]+$ 仅删除结尾的空格
public class Main 
{
	public static void main(String[] args) 
	{
		String str = "  Hello World !!   ";

		System.out.println( str.replaceAll("^[ \t]+|[ \t]+$", "") );	//"Hello World !!"

		System.out.println( str.replaceAll("^[ \t]+", "") );			//"Hello World !!   "

		System.out.println( str.replaceAll("[ \t]+$", "") );			//"  Hello World !!"
	}
}

向我提供有关如何在 Java 中从字符串中删除空格和制表符的问题。

学习愉快!

参考:

regular-expressions.info

Java String.strip() API 文档

文件readString() API – 将文件读取为 Java 中的字符串

原文: https://howtodoinjava.com/java11/files-readstring-read-file-to-string/

学习使用Files.readString(path)方法将文件读取为 Java 字符串。 该 API 已在 Java 11 中引入。

1. 文件readString()方法

java.nio.file.Files类具有两个重载方法。

public static String readString​(Path path) throws IOException

public static String readString​(Path path, Charset cs) throws IOException

  • 第一种方法将文件中的所有内容读取为字符串,然后使用 UTF-8 字符集将其从字节解码为字符。

    该方法可确保在读取所有内容或引发 I/O 错误或其他运行时异常时关闭文件。

  • 第一种方法等效于readString(path, StandardCharsets.UTF_8)

  • 第二种方法与仅使用指定字符集的方法相同。

  • 请注意,这些方法不适用于读取非常大的文件。 否则,如果文件过大(例如,文件大小大于 2GB),它们可能会抛出OutOfMemoryError

2. 文件readString()示例

Java 程序使用Files.readString()方法将文件读取为字符串。

import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.IOException;

public class Main 
{
	public static void main(String[] args) 
	{
		Path filePath = Paths.get("C:/", "temp", "test.txt");

		try 
		{
			String content = Files.readString(filePath);

			System.out.println(content);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

程序输出。

Hello Java Learner !!

文件c:/temp/test.txt的位置在这里。

Hello Java Learner !!

将我的问题放在评论部分。

学习愉快!

Java 命名约定

原文: https://howtodoinjava.com/java/basics/java-naming-conventions/

Java 命名约定是应用程序程序员应遵循的准则,以在整个应用程序中产生一致且可读的代码。 如果团队不遵循这些约定,他们可能会集体编写难以阅读和理解的应用程序代码。

Java 大量使用 Camel Case 表示法来命名方法,变量等,并为类和接口使用 TitleCase 表示法。

让我们通过示例详细了解这些命名约定。

1. 包命名约定

套件名称必须是一组以所有小写域名开头的字词(例如comorgnet等)。 根据组织自己的内部命名约定,包名称的后续部分可能会有所不同。

package com.howtodoinjava.webapp.controller;

package com.company.myapplication.web.controller;

package com.google.search.common;

2. 类命名约定

在 Java 中,类名通常应为名词,在标题情况下,每个单独单词的首字母大写。 例如

public class ArrayList {}

public class Employee {}

public class Record {}

public class Identity {}

3. 接口命名约定

在 Java 中,接口名称通常应为形容词。 接口应使用大写字母,每个单独单词的首字母大写。 在相同情况下,当接口提供一类类别时,接口也可以是名词ListMap

public interface Serializable {}

public interface Clonable {}

public interface Iterable {}

public interface List {}

4. 方法命名约定

方法始终应为动词。 它们代表一个动作,方法名称应清楚说明它们执行的动作。 为了清楚地表示操作,方法名称可以是单个单词,也可以是 2-3 个单词。 单词应使用驼峰式大写。

public Long getId() {}

public void remove(Object o) {}

public Object update(Object o) {}

public Report getReportById(Long id) {}

public Report getReportByName(String name) {}

5. 变量命名约定

所有实例,静态和方法参数变量名称均应使用驼峰表示法。 它们应该简短,足以描述其目的。 临时变量可以是单个字符,例如循环中的计数器。

public Long id;

public EmployeeDao employeeDao;

private Properties properties;

for (int i = 0; i < list.size(); i++) {

}

6. 常量命名约定

Java 常量应全部为大写,其中单词用下划线字符(_)分隔。 确保将final修饰符与常量变量一起使用。

public final String SECURITY_TOKEN = "...";

public final int INITIAL_SIZE = 16;

public final Integer MAX_SIZE = Integer.MAX;

7. 泛型类型命名约定

泛型类型参数名称应为大写单字母。 通常建议使用字母'T'作为类型。 在 JDK 类中,E用于收集元素,S用于服务加载程序,K and V用于映射键和值。

public interface Map <K,V> {}

public interface List<E> extends Collection<E> {}

Iterator<E> iterator() {}

8. 枚举命名约定

类似于类常量,枚举名称应全部为大写字母。

enum Direction {NORTH, EAST, SOUTH, WEST}

9. 注解命名约定

注解名称遵循标题大小写。 根据要求,它们可以是形容词,动词或名词。

public @interface FunctionalInterface {}

public @interface Deprecated {}

public @interface Documented {}

public @Async Documented {

public @Test Documented {

在本文中,我们讨论了一致的代码编写所遵循的 Java 命名约定,这使代码更具可读性和可维护性。

在使用任何编程语言编写简洁的代码时,命名约定可能是遵循的第一个最佳实践。

学习愉快!

文件writeString() API – 用 Java 将字符串写入文件

原文: https://howtodoinjava.com/java11/write-string-to-file/

学习使用Files.writeString(path, string, options)方法将字符串写入 Java 文件中。 该 API 已在 Java 11 中引入。

1. 文件writeString()方法

java.nio.file.Files类具有两个重载的静态方法,用于将内容写入文件。

public static Path writeString​(Path path, CharSequence csq, 
							OpenOption... options) throws IOException

public static Path writeString​(Path path, CharSequence csq, 
							Charset cs, OpenOption... options) throws IOException

  • 第一种方法使用 UTF-8 字符集将所有内容写入文件。
  • 第一种方法等效于writeString(path, string, StandardCharsets.UTF_8, options)
  • 第二种方法与仅使用指定字符集的方法相同。
  • options指定如何打开文件。

2. 文件writeString()示例

使用Files.writeString()方法将String写入文件的 Java 程序。

import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.IOException;
import java.nio.file.StandardOpenOption;

public class Main 
{
	public static void main(String[] args) 
	{
		Path filePath = Paths.get("C:/", "temp", "test.txt");

		try 
		{
			//Write content to file
			Files.writeString(filePath, "Hello World !!", StandardOpenOption.APPEND);

			//Verify file content
			String content = Files.readString(filePath);

			System.out.println(content);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

程序输出。

Hello World !!

文件c:/temp/test.txt最初为空。

将我的问题放在评论部分。

学习愉快!

Java 10 教程

Java 10 特性和增强特性

原文: https://howtodoinjava.com/java10/java10-features/

Java 9 发布之后,Java 10 很快问世。 与以前的版本不同,Java 10 并没有那么多令人兴奋的特性,但仍然没有几个重要的更新会改变您的编码方式以及其他将来的 Java 版本。

Table of Contents

JEP 286: Local Variable Type Inference
JEP 322: Time-Based Release Versioning
JEP 304: Garbage-Collector Interface
JEP 307: Parallel Full GC for G1
JEP 316: Heap Allocation on Alternative Memory Devices
JEP 296: Consolidate the JDK Forest into a Single Repository
JEP 310: Application Class-Data Sharing
JEP 314: Additional Unicode Language-Tag Extensions
JEP 319: Root Certificates
JEP 317: Experimental Java-Based JIT Compiler
JEP 312: Thread-Local Handshakes
JEP 313: Remove the Native-Header Generation Tool
New Added APIs and Options
Removed APIs and Options

JEP 286:局部变量类型推断

Java 现在具有var样式声明。 它允许您声明局部变量而无需指定其类型。 变量的类型将从创建的实际对象的类型推断出来。 它声称是 JDK 10 中开发人员唯一真正的特性。

var str = "Hello world";

//or

String str = "Hello world";

在上面的示例中,两个语句都是等效的。 在第一句话中,str的类型由分配类型String的类型确定。

阅读更多: Java var – 局部变量类型推断

JEP 322:基于时间的发行版本控制

从 Java 10 开始,Oracle 调整了基于时间的版本字符串方案。 版本号的新格式为:

$FEATURE.$INTERIM.$UPDATE.$PATCH

与旧版本不同,新的基于时间的版本不会延迟,并且特性将每六个月发布一次,并且对发布中的哪些特性没有限制。

也有长期发行(LTS)。 它主要针对企业客户。 LTS 版本的产品将提供 Oracle 的首要和持续的支持,目标是每三年一次。 此外,这些版本的更新将至少提供三年。

阅读更多: Java 版本 – 基于时间的发行版本控制

JEP 304:垃圾收集器接口

在早期的 JDK 结构中,构成垃圾收集器(GC)实现的组件分散在代码库的各个部分。 它已在 Java 10 中进行了更改。现在,它是 JVM 源代码中的一个干净接口,可以快速轻松地集成替代收集器。 它将改善不同垃圾收集器的源代码隔离。

这纯粹是重构。 之前工作的所有内容都需要事后进行工作,并且性能不应降低。

JEP 307:用于 G1 的并行全 GC

Java 9 引入了 G1(垃圾优先)垃圾收集器。 G1 垃圾收集器的设计避免了完整的收集,但是当并发收集不能足够快地回收内存时。 进行此更改后,将发生后备完整 GC。

G1 的完整 GC 的当前实现使用单线程的 mark-sweep-compact 算法。 此更改将使 mark-sweep-compact 算法并行化,并使用相同数量的线程。 当用于收集的并发线程无法足够快地恢复内存时,将触发该事件。

线程数可以通过-XX:ParallelGCThreads选项控制。

JEP 316:备用存储设备上的堆分配

进行此更改的目的是使 HotSpot VM 能够在用户指定的备用存储设备(例如 NV-DIMM)上分配 Java 对象堆。

要在此类内存中分配堆,我们可以添加一个新选项-XX:AllocateHeapAt=<path>。 该选项将采用文件系统的路径,并使用内存映射来实现在存储设备上分配对象堆的预期结果。 现有的与堆相关的标志,例如-Xmx-Xms等,以及与垃圾回收相关的标志将继续像以前一样工作。

JEP 296:将 JDK 森林整合到单个存储库中

作为此更改的一部分,为了简化和简化开发,将 JDK 森林的许多存储库合并到一个存储库中。

在 JDK 9 中,有八个存储库:rootcorbahotspotjaxpjaxwsjdklangtoolsnashorn。 在统一森林中,通常将 Java 模块的代码合并在单个顶级src目录下。 例如,今天在 JDK 森林中,存在基于模块的目录,例如

$ROOT/jdk/src/java.base
...
$ROOT/langtools/src/java.compiler
...

在合并林中,此代码改为组织为:

$ROOT/src/java.base
$ROOT/src/java.compiler
...

JEP 310:应用程序类 - 数据共享

此特性的目标是改善启动范围,扩展现有的类数据共享(“ CDS”)特性,以允许将应用程序类放置在共享档案中。

JDK 5 中引入的类数据共享允许将一组类预处理为共享的存档文件,然后可以在运行时对其进行内存映射以减少启动时间。 当多个 JVM 共享同一个存档文件时,它还可以减少动态内存占用。

当前,CDS 仅允许引导类加载器加载归档的类。 应用程序 CDS 允许内置系统类加载器,内置平台类加载器和自定义类加载器加载归档的类。

指定-XX:+UseAppCDS命令行选项可为系统类加载器,平台类加载器和其他用户定义的类加载器启用类数据共享。

JEP 314:其他 Unicode 语言标签扩展

目的是增强java.util.Locale和相关 API,以实现 BCP 47 语言标签的其他 Unicode 扩展。 最初在 Java SE 7 中添加了对 BCP 47 语言标签的支持,并且对 Unicode 语言环境扩展的支持仅限于日历和数字。 该 JEP 将在相关的 JDK 类中实现最新的 LDML 规范中指定的更多扩展。

该 JEP 将增加对以下附加扩展的支持:

  • cu(货币类型)
  • fw(一周的第一天)
  • rg(区域替代)
  • tz(时区)

修改后的相关 API 为:

java.text.DateFormat::get*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of
java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider

JEP 319:根证书

cacerts 密钥库是 JDK 的一部分,旨在包含一组根证书,这些根证书可用于建立对各种安全协议中使用的证书链的信任。 但是,JDK 源代码中的 cacerts 密钥库当前为空。

cacerts 密钥库将填充由 Oracle Java SE Root CA Program 的 CA 颁发的一组根证书。 许多供应商已经签署了所需的协议,并且每个供应商都将包含一份根证书列表。 那些没有签署协议的人目前不会包括在内。 下一个版本将包含处理时间较长的内容。

这也意味着两个 Oracle & Open JDK 二进制文件在特性上都相同。 以后,默认的 TLS 等关键安全组件将在 OpenJDK 构建中正常工作。

JEP 317:基于 Java 的实验性 JIT 编译器

此特性使基于 Java 的 JIT 编译器 Graal 可用作 Linux / x64 平台上的实验性 JIT 编译器。 Graal 将使用 JDK 9 中引入的 JVM 编译器接口(JVMCI)。Graal 已经存在于 JDK 中,因此将其作为实验性 JIT 启用将主要是一项测试和调试工作。

要将 Graal 用作 JIT 编译器,请在 Java 命令行上使用以下选项:

-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

Graal 是从头开始完全覆盖 Java 中的 JIT 编译器。 以前的 JIT 编译器是用 C++编写的。

JEP 312:线程本地握手

通过使无需执行全局 VM 安全点就可以在应用程序线程上执行回调成为可能,该 JEP 为提高 VM 性能奠定了基础。 这意味着 JVM 可以停止单个线程,而不仅仅是所有线程。

线程本地握手将首先在 x64 和 SPARC 上实现。 其他平台将恢复到正常的安全点。 新的产品选项-XX:ThreadLocalHandshakes(默认值true)使用户可以在受支持的平台上选择常规安全点。

JEP 313:删除本机头生成工具

它将从 JDK 中删除javah工具,这是一个单独的工具,可在编译 JNI 代码时生成头文件,因为可以通过javac来完成。

这是 Java 10 的另一项着重于内务处理的特性。

新增的 API 和选项

Java 10 中添加了 73 个新 API。让我们来看看其中的几个:

API 描述
Optional.orElseThrow() 新方法orElseThrow已添加到Optional类。 它与现有的get方法同义,并且现在是它的首选替代方法。
List.copyOfSet.copyOfMap.copyOf 这些方法从现有实例创建新的集合实例。
Collectors.toUnmodifiableListCollectors.toUnmodifiableSetCollectors.toUnmodifiableMap 这些方法允许将Stream的元素收集到不可修改的集合中
--jdk.disableLastUsageTracking 要为正在运行的 VM 禁用 JRE 上次使用情况跟踪。
--add-stylesheet 在生成的文档中提供对使用多个样式表的支持。
--main-stylesheet 为了帮助将主要样式表与任何其他样式表区分开。
@summary 添加以明确指定用作 API 描述摘要的文本。 默认情况下,从第一句话推断出 API 描述的摘要。

删除的 API 和选项

API 描述
LookAndFeels
Runtime.getLocalizedInputStreamRuntime.getLocalizedOutputStream 已过时的国际化机制的一部分,并且没有已知用途。
RMI 服务器端多路复用协议支持 它已在 JDK 9 中禁用,现在已被删除。
常见的 DOM API com.sun.java.browser.plugin2.DOMsun.plugin.dom.DOMObject API 已被删除。 应用程序可以使用netscape.javascript.JSObject来操作 DOM。
FlatProfiler 在 JDK 9 中已弃用,已通过删除实现代码来作废。
-Xoss-Xsqnopause-Xoptimize-Xboundthreads-Xusealtsigs 选项已删除。
policytool 策略工具安全工具已从 JDK 中删除。
com.sun.security.auth.**中不推荐使用的类 下面的类现在已移除:com.sun.security.auth.PolicyFilecom.sun.security.auth.SolarisNumericGroupPrincipalcom.sun.security.auth.SolarisNumericUserPrincipalcom.sun.security.auth.SolarisPrincipalcom.sun.security.auth.X500Principalcom.sun.security.auth.module.SolarisLoginModulecom.sun.security.auth.module.SolarisSystem

总体而言,Java 10 具有许多我们日常编程中可能不会使用的特性,但是它仍然具有许多在幕后起作用的特性,从而使其成为重要的里程碑。

学习愉快!

Java 版本 – 基于时间的发行版本控制

原文: https://howtodoinjava.com/java10/java-version/

从 Java 10 开始,Oracle 调整了基于时间的版本字符串方案(JEP 322)。 新的基于时间的模型已取代了过去基于特性的多年发布模型。 与旧版本不同,新的基于时间的版本不会延迟,并且特性将每六个月发布一次,而对于该版本中可以发布的特性没有任何限制。

更新版本将在每个季度(1 月,4 月,7 月,10 月)发布。 更新版本将严格限于安全性问题,回归和较新特性中的错误的修复。 按照进度计划,可以说每个特性版本在下一个特性版本发布之前都会收到两个更新。

Java 版本格式

如果在逗号和提示/终端中运行命令java -version,您将获得以下输出版本信息:

C:\Users\Lokesh>java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)

版本号的新格式为:

$FEATURE.$INTERIM.$UPDATE.$PATCH

名称 描述
$FEATURE | 它会每 6 个月增加一次,具体取决于特性发布版本,例如:JDK 10,JDK11。(以前为$MAJOR。)
$INTERIM | 通常为零,因为六个月内不会有任何中期发布。 对于包含兼容的错误修复和增强特性但不兼容的更改,不删除的特性以及对标准 API 的更改的非特性版本,它将增加。 (以前为$MINOR。)
$UPDATE | 对于解决安全问题,回归和较新特性中的错误的兼容更新版本,它将递增。 (以前为$SECURITY。)
$PATCH 仅在需要紧急发布以解决关键问题时才会增加。

版本号中的数字序列与其他此类序列以数字逐点方式进行比较; 例如10.0.4小于10.1.2 。 如果一个序列比另一个序列短,则认为较短序列的缺失元素少于较长序列的对应元素; 例如10.0.2小于10.0.2.1

Java 版本 API

Runtime.version()可用于以编程方式获取版本计数器值。 例如

Version version = Runtime.version();
version.feature();
version.interim();
version.update();
version.patch();

Output:

10
0
1
0

解析现有版本

Version version = Runtime.Version.parse("10.0.1");

version.feature();
version.interim();
version.update();
version.patch();

长期发行(LTS)

它主要针对企业客户。 LTS 版本的产品将提供 Oracle 的首要和持续的支持,目标是每三年一次。 此外,这些版本的更新将至少提供三年。

这将导致“LTS”在java –versions的输出中突出显示。 例如11.0.2+13-LTS

学习愉快!

参考: http://openjdk.java.net/jeps/322

Java var – 局部变量类型推断

原文: https://howtodoinjava.com/java10/var-local-variable-type-inference/

Java 一直在努力减少语法的冗长性。 首先是菱形运算符,现在是var(局部变量类型 – JEP 286)在 Java 中声明变量。 当您使用var声明变量时,基本上,而不是声明变量类型,而是根据设置的类型假定其类型。 例如

var str = "Hello world";

//or

String str = "Hello world";

在上面的示例中,在第一个语句中,您将String设置为变量str,因此隐式假定其为String类型。 在上面的示例中,第一条语句基本上等同于第二条语句。

var同时声明和初始化

使用var时,必须在同一位置初始化变量。 您不能将声明和初始化放在不同的位置。 如果未在适当位置初始化变量,则会出现编译错误 – Cannot use 'var' on variable without initializer

var i;	//Invalid Declaration - - Cannot use 'var' on variable without initializer

var j = 10; //Valid Declaration

System.out.println(i);

var不是关键字

虽然看起来像var并不是保留的 Java 关键字。 因此,您可以创建名称为var的变量,允许使用。

var var = 10; 	//Valid Declaration

int var = 10; 	//Also valid Declaration

var用法

使用var仅限于具有初始化器的局部变量,增强的for循环中的索引以及在传统的for循环中声明的局部变量; 它不适用于方法形式,构造器形式,方法返回类型,字段,catch形式或任何其他类型的变量声明。

用法如下:

  • 带有初始化器的局部变量
  • 增强的for循环中的索引
  • 在传统的for循环中声明的局部变量
var blogName = "howtodoinjava.com";

for ( var object : dataList){
    System.out.println( object );
}

for ( var i = 0 ; i < dataList.size(); i++ ){
    System.out.println( dataList.get(i) );
}

不允许的用法如下:

  • 方法参数
  • 构造器参数
  • 方法返回类型
  • 类字段
  • 捕获形式(或任何其他类型的变量声明)
public class Application {

	//var firstName;	//Not allowed as class fields

	//public Application(var param){ 	//Not allowed as parameter 

    //}

	/*try{

    } catch(var ex){	//Not allowed as catch formal 

    }*/

    /*public var demoMethod(){	//Not allowed in method return type
    	return null;
    }*/

    /*public Integer demoMethod2( var input ){	//Not allowed in method parameters
    	return null;
    }*/
}

var不向后兼容

由于这是新的语言特性,使用var编写的代码将不会在较低的 JDK 版本(小于 10)中编译。 因此,只有在确定时才使用此特性。

var不会影响性能

请记住,在 Java 中,类型不是在运行时推断的,而是在编译时推断的。 这意味着生成的字节码与显式类型声明相同 – 它确实包含有关类型的信息。 这意味着在运行时无需额外的处理。

学习愉快!

Java 9 教程

Java 9 特性和增强特性

原文: https://howtodoinjava.com/java9/java9-new-features-enhancements/

Java 9 带来了许多新的增强特性,它们将在很大程度上影响您的编程风格和习惯。 最大的变化是 Java 的模块化。 这是 Java 8 中的 Lambdas 之后的又一重大变化。 在本文中,我列出了将作为 JDK 9 版本的一部分的更改。

What is new in Java 9

Java platform module system
Interface Private Methods
HTTP 2 Client
JShell - REPL Tool
Platform and JVM Logging
Process API Updates
Collection API Updates
Stream API Improvements
Multi-Release JAR Files
@Deprecated Tag Changes
Stack Walking
Java Docs Updates
Miscellaneous Other Features

Java 平台模块系统

JPMS(Java 平台模块系统)是新 Java 9 版本的核心亮点。 它也被称为 Jigshaw 项目。 模块是新的结构,就像我们已经有了包一样。 使用新的模块化编程开发的应用程序可以看作是交互模块的集合,这些模块之间具有明确定义的边界和依赖性。

JPMS 包括为编写模块化应用程序以及模块化 JDK 源代码提供支持。 JDK 9 随附约 92 个模块(GA 版本中可能会进行更改)。 Java 9 模块系统具有一个“java.base”模块。 它被称为基本模块。 这是一个独立模块,不依赖于任何其他模块。 默认情况下,所有其他模块都依赖于“java.base”。

在 Java 模块化编程中:

  1. 模块通常只是一个 jar 文件,其根目录具有module-info.class文件。
  2. 要使用模块,请将 jar 文件而不是classpath包含在modulepath中。 添加到类路径的模块化 jar 文件是普通的 jar 文件,module-info.class文件将被忽略。

典型的module-info.java类如下所示:

module helloworld {
    exports com.howtodoinjava.demo;
}

module test {
    requires helloworld;
}

阅读更多: Java 9 模块教程

接口私有方法

Java 8 允许您在接口中编写默认方法,这是广受赞赏的特性。 因此,在此之后,接口仅缺少一些东西,只有非私有方法是其中之一。 从 Java 9 开始,您可以在接口中包含私有方法。

这些私有方法将改善接口内部的代码可重用性。 举例来说,如果需要两个默认方法来共享代码,则可以使用私有接口方法来共享代码,但不必将该私有方法暴露给实现类。

在接口中使用私有方法有四个规则:

  1. 接口私有方法不能是抽象的。
  2. 私有方法只能在接口内部使用。
  3. 私有静态方法可以在其他静态和非静态接口方法中使用。
  4. 私有非静态方法不能在私有静态方法内部使用。

在接口中使用私有方法的示例:

public interface CustomCalculator 
{
    default int addEvenNumbers(int... nums) {
        return add(n -> n % 2 == 0, nums);
    }

    default int addOddNumbers(int... nums) {
        return add(n -> n % 2 != 0, nums);
    }

    private int add(IntPredicate predicate, int... nums) { 
        return IntStream.of(nums)
                .filter(predicate)
                .sum();
    }
}

Java 9 – 接口中的私有方法

HTTP/2 客户端

HTTP/1.1 客户端于 1997 年发布。此后发生了很多变化。 因此,对于 Java 9,引入了新的 API,该 API 使用起来更加简洁明了,并且还增加了对 HTTP/2 的支持。 新 API 使用 3 个主要类,即HttpClientHttpRequestHttpResponse

要发出请求,就像获取客户,建立请求并发送它一样简单,如下所示。

HttpClient httpClient = HttpClient.newHttpClient(); 
HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI("//howtodoinjava.com/")).GET().build(); 
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandler.asString()); 
System.out.println( httpResponse.body() ); 

上面的代码看起来更简洁易读。

新 API 还使用httpClient.sendAsync()方法支持异步 HTTP 请求。 它返回CompletableFuture对象,该对象可用于确定请求是否已完成。 请求完成后,它还使您可以访问HttpResponse。 最好的部分是,如果您愿意,甚至可以在请求完成之前取消它。 例如:

if(httpResponse.isDone()) {
    System.out.println(httpResponse.get().statusCode());
    System.out.println(httpResponse.get().body());
} else {
    httpResponse.cancel(true);
}

JShell – REPL 工具

JShellJDK 9 发行版(JEP 222)附带的新命令行交互工具,用于求值用 Java 编写的声明,语句和表达式。 JShell 允许我们执行 Java 代码段并获得即时结果,而无需创建解决方案或项目。

Jshell 非常类似于 linux OS 中的命令窗口。 区别在于 JShell 是 Java 特定的。 除了执行简单的代码片段外,它还有许多其他特性。 例如

  • 在单独的窗口中启动内置代码编辑器
  • 在单独的窗口中启动您选择的代码编辑器
  • 在这些外部编辑器中发生“保存”操作时执行代码
  • 从文件系统加载预编写的类

Java 9 JShell 教程

平台和 JVM 日志记录

JDK 9 通过新的日志记录 API 改进了平台类(JDK 类)和 JVM 组件中的日志记录。 它使您可以指定所选的日志记录框架(例如 Log4J2)作为日志记录后端,用于记录来自 JDK 类的消息。 关于此 API,您应该了解几件事:

  1. 该 API 是由 JDK 中的类而非应用程序类使用的。
  2. 对于您的应用程序代码,您将像以前一样继续使用其他日志记录 API。
  3. 该 API 不允许您以编程方式配置记录器。

该 API 包含以下内容:

  • 服务接口java.lang.System.LoggerFinder,它是抽象的静态类
  • 接口java.lang.System.Logger,它提供日志记录 API
  • java.lang.System类中的重载方法getLogger(),它返回记录器实例。

JDK 9 还添加了一个新的命令行选项-Xlog,使您可以单点访问从 JVM 所有类记录的所有消息。 以下是使用-Xlog选项的语法:

-Xlog[:][:[

<output>][:[<decorators>][:<output-options>]]]</output-options></decorators></output> 

所有选项都是可选的。 如果缺少-Xlog中的前一部分,则必须对该部分使用冒号。 例如,-Xlog::stderr表示所有部件均为默认设置,输出设置为stderr

我将在单独的文章中深入讨论该主题。

进程 API 更新

在 Java 5 之前,产生新进程的唯一方法是使用Runtime.getRuntime().exec()方法。 然后在 Java 5 中,引入了ProcessBuilder API,该 API 支持一种更干净的方式产生新进程。 现在,Java 9 添加了一种获取有关当前进程和任何衍生进程的信息的新方法。

要获取任何进程的信息,现在应该使用java.lang.ProcessHandle.Info接口。 此接口在获取大量信息方面很有用,例如

  • 用于启动过程的命令
  • 命令的参数
  • 进程开始的瞬间
  • 它和创建它的用户所花费的总时间
ProcessHandle processHandle = ProcessHandle.current();
ProcessHandle.Info processInfo = processHandle.info();

System.out.println( processHandle.getPid() );
System.out.println( processInfo.arguments().isPresent() );
System.out.println( pprocessInfo.command().isPresent() );
System.out.println( processInfo.command().get().contains("java") );
System.out.println( processInfo.startInstant().isPresent() );

要获取新生成的进程的信息,请使用process.toHandle()方法获取ProcessHandle实例。 其余的一切都如上所述。

String javaPrompt = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaPrompt, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();

也可以使用ProcessHandle.allProcesses()获取系统中所有可用进程的ProcessHandle流。

要获取所有子进程的列表(直接和深层次的子进程),请使用children()descendants()方法。

Stream<ProcessHandle> children    = ProcessHandle.current().children();
Stream<ProcessHandle> descendants = ProcessHandle.current().descendants();

集合 API 更新

从 Java 9 开始,您可以使用新的工厂方法创建不可变集合,例如不可变列表,不可变集合和不可变映射。 例如

import java.util.List;

public class ImmutableCollections 
{
    public static void main(String[] args) 
    {
        List<String> namesList = List.of("Lokesh", "Amit", "John");

        Set<String> namesSet = Set.of("Lokesh", "Amit", "John");

        Map<String, String> namesMap = Map.ofEntries(
					                Map.entry("1", "Lokesh"),
					                Map.entry("2", "Amit"),
					                Map.entry("3", "Brian"));
    }
}

Java 9 集合 API 的改进

流 API 的改进

Java 9 引入了两种与流进行交互的新方法,即takeWhile/dropWhile方法。 此外,它还添加了两个重载方法,即ofNullableiterate方法。

新方法takeWhiledropWhile允许您基于谓词获取流的一部分。

  1. 在有序流上,takeWhile返回流的“最长前缀”,其元素匹配给定谓词。 dropWhile返回匹配给定谓词的“最长前缀”之后的其余项。
  2. 在无序流上,takeWhile返回流的子集,从流的开头开始,其元素匹配给定谓词(但不是全部)。 dropWhile返回不匹配给定谓词的剩余的流元素。

同样,在 Java 8 之前,流中不能具有null值。 会导致NullPointerException。 从 Java 9 开始,Stream.ofNullable()方法使您可以创建一个单元素流,如果不包含null,则包装一个值,否则将为空流。 从技术上讲,Stream.ofNullable()与流条件上下文中的空条件检查非常相似。

Java 9 流 API 的改进

多版本 JAR 文件

此增强与将应用程序类打包到 jar 文件中的方式有​​关。 以前,您必须将所有类打包到一个 jar 文件中,并放到另一个要使用它的应用程序的类路径中。

现在,通过使用多发行版特性,一个 jar 可以包含一个类的不同版本 – 与不同的 JDK 版本兼容。 有关类的不同版本以及通过加载的类应选择哪个类的 JDK 版本的信息存储在MANIFEST.MF文件中。 在这种情况下,MANIFEST.MF文件在其主要部分中包含条目Multi-Release: true

此外,META-INF包含一个versions子目录,该目录的整数子目录(从 Java 9 开始)存储特定于版本的类和资源文件。 例如:

JAR content root
  A.class
  B.class
  C.class
  D.class
  META-INF
     MANIFEST.MF
     versions
        9
           A.class
           B.class

假设在 JDK 10 中,A.class已更新为利用 Java 10 的某些特性,则可以如下所示更新此 Jar 文件:

JAR content root
  A.class
  B.class
  C.class
  D.class
  META-INF
     MANIFEST.MF
     versions
        9
           A.class
           B.class
        10
           A.class

解决具有多种版本的 jar 彼此不兼容的大型应用程序中经常出现的依赖地狱,这看起来确实是很有前途的一步。 此特性可能对解决这些情况有很大帮助。

@Deprecated的标签更改

从 Java 9 开始,@Deprecated注解将具有两个属性,即forRemovalsince

  1. forRemoval – 指示带注释的元素是否在将来的版本中会被删除。
  2. since – 它返回已弃用带注释元素的版本。

强烈建议在文档中使用@deprecated javadoc 标记说明不赞成使用程序元素的原因。 该文档还应该建议并链接到建议的替代 API(如果适用)。 替换 API 的语义通常会有所不同,因此也应讨论此类问题。

栈的遍历

栈是后进先出(LIFO)数据结构。 在 JVM 级别,栈存储帧。 每次调用方法时,都会创建一个新框架并将其推入栈的顶部。 方法调用完成后,框架将被破坏(从栈中弹出)。 栈上的每个帧都包含其自己的局部变量数组,其自己的操作数栈,返回值以及对当前方法类的运行时常量池的引用。

在给定线程中,任何时候只有一帧处于活动状态。 活动帧称为当前帧,其方法称为当前方法。(阅读更多

直到 Java 8,StackTraceElement代表一个栈帧。 要获得完整的栈,您必须使用Thread.getStackTrace()Throwable.getStackTrace()。 它返回了StackTraceElement数组,您可以对其进行迭代以获取所需的信息。

在 Java 9 中,引入了新类StackWalker。 该类使用当前线程的栈帧顺序流提供简单有效的栈遍历。 StackWalker类非常有效,因为它懒惰地求值栈帧。

// Prints the details of all stack frames of the current thread

StackWalker.getInstance().forEach(System.out::println);

您可以使用此信息流执行其他许多操作,我们将在其他一些专用文章中介绍这些内容。

Java 文档更新

Java 9 增强了javadoc工具以生成 HTML5 标记。 当前,它以 HTML 4.01 生成页面。

为了生成 HTML5 Javadoc,需要在命令行参数中加入参数-html5。 要在命令行上生成文档,可以运行:

javadoc [options] [packagenames] [sourcefiles] [@files]

使用 HTML5 可以带来更轻松的 HTML5 结构的好处。 它还实现了 WAI-ARIA 标准的可访问性。 这样做的目的是使身体或视觉障碍的人更容易使用屏幕阅读器等工具访问 javadocs 页面。

JEP 225 提供了在 Javadoc 中搜索程序元素以及标记的单词和短语的特性。

以下内容将被索引并可以搜索:

  • 声明的模块名称
  • 配套
  • 类型和成员
  • 方法参数类型的简单名称

这是通过新的search.js Javascript 文件以及在生成 Javadoc 时生成的索引在客户端实现的。 在生成的 HTML5 API 页面上有一个搜索框。

请注意,默认情况下将添加“搜索”选项,但可以使用参数-noindex将其关闭。

其他特性

Java 9 中还有其他特性,我在这里列出以供快速参考。 我们将在以后的文章中讨论所有这些特性。

  • 反应式流 API
  • GC(垃圾收集器)改进
  • 筛选传入的序列化数据
  • 弃用 Applet API
  • 字符串化连接
  • 增强的方法句柄
  • 精简字符串
  • Nashorn 的解析器 API

Java 9 计划于 2017/09/21 发行。 对于最新更改,请遵循此链接

学习愉快!

Java 9 – 精简字符串改进 [JEP 254]

原文: https://howtodoinjava.com/java9/compact-strings/

直到 Java 8,Java 中的字符串内部都由char[]表示。 每个char都以 2 个字节存储在内存中。 oracle 的 JDK 开发人员分析了许多客户端的应用程序堆转储,他们注意到大多数字符串只能使用 Latin-1 字符集表示。 拉丁 1 个字符可以存储在一个字节中,比char数据类型存储少 50%(1 个字节)。

因此,JDK 开发人员将String类的内部存储从char[]缺省设置为byte[]。 通常,这导致节省了堆内存的大量空间,因为字符串对象实际上占据了堆内存的很大一部分。(来源

您可以使用java命令的-XX:-CompactStrings参数来控制应用程序中此特性的使用。

Java 9 之前的字符串类

在 Java 9 之前,字符串数据存储为char数组。 每个字符需要 16 位。

public final class String
   	implements java.io.Serializable, Comparable<String>, CharSequence {

   	//The value is used for character storage.
	private final char value[];

}

Java 9 之后的字符串类

从 Java 9 开始,现在使用字节数组以及用于编码引用的标志字段在内部表示字符串。

public final class String
   	implements java.io.Serializable, Comparable<String>, CharSequence {

    /** The value is used for character storage. */
	@Stable
	private final byte[] value;

	/**
	 * The identifier of the encoding used to encode the bytes in
	 * {@code value}. The supported values in this implementation are
	 *
	 * LATIN1
	 * UTF16
	 *
	 * @implNote This field is trusted by the VM, and is a subject to
	 * constant folding if String instance is constant. Overwriting this
	 * field after construction will cause problems.
	 */
	private final byte coder;

}

java命令参考

众所周知,java命令用于启动 Java 应用程序。 它可以具有许多参数来定制应用程序运行时。 下面是一个这样的命令:

-XX:-CompactStrings

禁用精简字符串特性。 默认情况下,启用此选项。 启用此选项后,内部仅包含单字节字符的 Java 字符串将使用 ISO-8859-1/Latin-1 编码在内部表示并存储为每个字符的单字节字符串。 这将只包含单字节字符的字符串减少了 50% 的空间。 对于包含至少一个多字节字符的 Java 字符串:这些字符串使用 UTF-16 编码表示并存储为每个字符 2 个字节。 禁用精简字符串特性将强制使用 UTF-16 编码作为所有 Java 字符串的内部表示。

禁用精简字符串可能有益的情况包括:

  1. 当知道应用程序将大量分配多字节字符字符串时
  2. 在从 Java SE 8 迁移到 Java SE 9 的过程中观察到性能下降的意外事件中,分析表明精简字符串引入了回归。

在这两种情况下,禁用精简字符串都是有意义的。

这纯粹是实现更改,不更改现有的公共接口。

将我的问题放在评论部分。

学习愉快!

Java 模块教程

原文: https://howtodoinjava.com/java9/java-9-modules-tutorial/

JPMS(Java 平台模块系统)是 Java 9 的主要增强特性。它也被称为 Jigsaw 项目。 在此 Java 9 模块示例中,我们将学习有关模块的一般知识以及将来开始编写模块化代码时编程风格的变化。

Table of Contents

What is a Module
Introduction to Java 9 Modules
How to Write Modular Code
Summary

什么是一般模块

在任何编程语言中,模块都是包含代码的(类似包的)工件,元数据描述了模块及其与其他模块的关系。 理想情况下,从编译时一直到运行时都可以识别这些工件。 通常,任何应用程序都是多个模块的组合,这些模块可以一起执行业务目标。

就应用程序架构而言,模块应代表特定的业务能力。 它应具有该特性的自足能力,并且应仅公开使用模块特性的接口。 要完成其任务,它可能依赖于其他模块,应明确声明其他模块。

因此,简而言之,一个模块应遵循三个核心原则:

  • 强封装

    封装意味着隐藏实现细节,这些细节对于正确使用模块不是必需的。 目的是封装的代码可以自由更改,而不会影响模块的用户。

  • 稳定的抽象

    抽象有助于使用接口(即公共 API)公开模块特性。 任何时候,您想更改模块代码中的业务逻辑或实现,更改对模块用户都是透明的。

  • 显式依赖

    模块也可以依赖于其他模块。 这些外部依赖项必须是模块定义本身的一部分。 模块之间的这些依赖关系通常表示为图形。 在应用程序级别查看该图后,您将更好地了解应用程序的架构。

Java 9 模块简介

在 Java 9 之前,您已经拥有“”来根据业务特性对相关类进行分组。 与包一起,您还具有“访问修饰符”,以控制其他类或包的可见内容和隐藏内容。 到目前为止,它一直运行良好。 Java 对封装和抽象有强大的支持。

但是,显式依赖关系是事情开始崩溃的地方。 在 Java 中,依存关系用import语句声明; 但严格来说,它们是“编译时”结构。 编译代码后,没有任何机制可以清楚地说明其运行时依赖项。 实际上,Java 运行时相关性解析是一个问题很大的领域,以至于已经创建了专门的工具来解决此问题,例如 gradlemaven 。 同样,很少有框架开始捆绑其完整的运行时依赖关系,例如 SpringBoot 项目。

使用新的 Java 9 模块,我们将具有更好的特性来编写结构良好的应用程序。 此增强特性分为两个区域:

  1. 模块化 JDK 本身。
  2. 提供一个模块系统供其他应用程序使用。

Java 9 模块系统具有一个“java.base”模块。 称为“基本模块”。 这是一个独立模块,不依赖于任何其他模块。 默认情况下,所有其他模块都依赖于“java.base”。

在 Java 9 中,模块可帮助您封装包和管理依赖项。 所以通常

  • 类是字段和方法的容器
  • 包是类和接口的容器
  • 模块是包的容器

如果您不知道要寻找的特定内容,您将不会感觉到普通代码和模块化代码之间的主要区别。 例如:

  1. 模块通常只是一个 jar 文件,其根目录具有module-info.class文件。
  2. 要使用模块,请将 jar 文件而不是classpath包含在modulepath中。 添加到类路径的模块化 jar 文件是普通的 jar 文件,module-info.class文件将被忽略。

如何编写模块化代码

阅读以上所有概念后,让我们看看如何在现实中编写模块化代码。 我正在使用 Netbeans IDE,因为它具有对 Java 9 的良好的早期支持(截至今天)。

创建 Java 模块化项目

创建新的模块化项目。 我已经创建了名称JavaAppOne

Create Java Modular Project

创建 Java 模块化项目

Create Java Modular Project - Step 2

创建 Java 模块化项目 – 步骤 2

创建 Java 模块

现在,在此项目中添加一个或两个模块。

Create New Module

创建新模块

我添加了两个模块helloworldtest。 让我们看看他们的代码和项目结构。

Java 9 Modules Project Structure

Java 9 模块项目结构

/helloworld/module-info.java

module helloworld {
}

HelloWorldApp.java

package com.howtodoinjava.demo;

public class HelloWorldApp {
    public static void sayHello() {
        System.out.println("Hello from HelloWorldApp");
    }
}

/test/module-info.java

module test {
}

TestApp.java

package com.test;

public class TestApp {
    public static void main(String[] args) {
        //some code
    }
}

到目前为止,模块是独立的。 现在假设,我们想在TestApp类中使用HelloWorldApp.sayHello()方法。 如果尝试使用该类而不导入模块,则会出现编译时错误“包com.howtodoinjava.demo包不可见”。

导出包和导入模块

为了能够导入HelloWorldApp,您必须首先从helloworld模块中导出“com.howtodoinjava.demo”包,然后在test模块中包含helloworld模块。

module helloworld {
    exports com.howtodoinjava.demo;
}

module test {
    requires helloworld;
}

在上面的代码中,requires关键字表示依赖性,exports关键字标识可导出到其他模块的包。 仅当显式导出包时,才能从其他模块访问它。 默认情况下,无法从其他模块访问模块中未导出的包。

现在您将可以在TestApp类中使用HelloWorldApp类。

package com.test;

import com.howtodoinjava.demo.HelloWorldApp;

public class TestApp {
    public static void main(String[] args) {
        HelloWorldApp.sayHello();
    }
}

Output:

Hello from HelloWorldApp

让我们来看一下模块图。

Module Graph

模块图

从 Java 9 开始,“public”仅对该模块内部的所有其他包表示“public”。 仅当导出包含“public”类型的包时,其他模块才能使用它。

总结

模块化应用程序具有许多优点,当您遇到具有非模块化代码库的应用程序时,您会更加欣赏。 您必须听说过诸如“意大利面条架构”或“凌乱的整体”之类的术语。 模块化不是灵丹妙药,但它是一种架构原理,如果正确应用,可以在很大程度上防止这些问题。

借助 JPMS,Java 已迈出了一大步,成为了模块化语言。 决定是对还是错,只有时间会证明一切。 第三方库和框架如何适应和使用模块系统将会很有趣。 以及它如何影响开发工作,我们每天都在做。

下载源码

学习愉快!

Java 9 – JShell

原文: https://howtodoinjava.com/java9/complete-jshell-tutorial-examples/

JShell 是新的命令行交互式 REPL读取-求值-打印循环)控制台,随附 JDK 9 发行版(JEP 222)来求值用 Java 编写的声明,语句和表达式。 JShell 允许我们执行 Java 代码段并获得即时结果,而无需创建解决方案或项目。

在本教程中,我们将通过示例学习可以在 JShell 中完成的各种任务。

Table of Contents

1\. Launch JShell
2\. Write and Execute Java Code
3\. Edit Code in JShell Edit Pad
4\. Launch Code in External Editor
5\. Load Code from External Files

1. 启动 JShell

首先是将 JDK 9 安装到您的计算机中。 从此链接下载 JDK 9 并进行安装。

转到安装位置并查看/jdk-9/bin文件夹。 您可以在这里找到jshell.exe文件。

JShell location in JDK 9

JDK 9 中的 JShell 位置

现在启动一个新的命令窗口并检查 Java 版本。

>> java -version

它应该指向 JDK 9 版本。 如果不是,则使用相应的值更新环境属性JAVA_HOMEPATH

JAVA_HOME=C:\Program Files\Java\jdk-9
PATH=C:\Program Files\Java\jdk-9\bin	//Path till bin folder

现在再次启动新的命令提示符窗口,然后键入命令jshell。 它将光标更改为jshell

Jshell Launched Window

Jshell 启动窗口

恭喜,您已准备好在 JShell REPL (读取-求值-打印循环)中玩游戏。

2. 在 REPL 中编写和执行 Java 代码

Jshell 允许创建小的代码段并对其进行测试,而无需创建和构建复杂的项目。 这就是应该使用它的方式。 在 JShell 上进行操作使其易于使用和快速。 让我们看看如何?

2.1 变量

您可以像在实际编程中一样,定义变量。 唯一的区别是您不必编写类或main方法即可开始。

jshell> int i = 10;
i ==> 10

打印变量的值,只需键入变量名称并按ENTER。 它将打印变量的值。

jshell> i
i ==> 10

要将重新分配给新值,只需按常规方法即可。

jshell> i=20;
i ==> 20

列出所有声明的变量,请使用命令/vars

jshell> /vars
|    int i = 20
|    int j = 30

Working with Variables in JShell

在 JShell 中使用变量

2.2 方法

与变量非常相似,方法也很简单。

要在 jshell 中创建方法,定义带有返回类型,方法名称,参数和方法主体的方法。 不需要访问修饰符。

jshell> int sum (int a, int b) {
   ...> return a+b;
   ...> }
|  created method sum(int,int)

列出所有定义的方法,请使用命令/methods

jshell> /methods
|    int sum(int,int)

要调用该方法,请像普通编程一样调用它。

jshell> sum(2,2)
$6 ==> 4

如果要查看方法代码,请使用/list命令。 它将显示当前方法的源代码。

jshell> /list sum

1 : int sum (int a, int b) {
   return a+b;
   }

要更改方法代码,您将需要使用相同的方法名称覆盖新的修改后的代码。

jshell> int sum (int a, int b) {
   ...> int c = a+b;
   ...> return c;
   ...> }
|  modified method sum(int,int)

jshell> /list sum

   3 : int sum (int a, int b) {
       int c = a+b;
       return c;
       }

Working with Methods in JShell

在 JShell 中使用方法

请记住方法重载规则。 如果您更改了方法参数数量或它们的数据类型,那么它将是一个新方法,并且在 jshell 中将注册两个方法。

3. 在 JShell 编辑板上编辑代码

到那时,您正在处理几行代码,JShell 内联编辑器已经足够了。 但是,当代码开始变大时,则可能需要文件编辑器来修改代码。

在这里您可以使用 JShell 编辑板。 要启动编辑板,请使用/edit命令和方法名称。

JShell Edit Pad

JShell 编辑板

在这里根据需要更改方法代码,然后单击“接受”按钮。 修改后的代码将在 Jshell 中更新,您将在提示符下收到确认消息。 您可以根据需要多次更改代码,保存代码然后退出窗口。

Save Operation in Jshell Edit Pad

在 Jshell 编辑板中保存操作

4. 在外部编辑器中启动代码

实际上,编辑板足以满足大多数需求,即使您想在任何特定的编辑器上进行编码,也可以使用它。 JShell 允许轻松配置任何外部编辑器来编辑代码段。 您只需要获取我们要使用的编辑器的完整路径,并在 JShell 中运行/set editor命令来配置编辑器。

/set editor "C:\\Program Files\\Sublime Text 3\\sublime_text.exe"

现在再次执行/edit命令。 现在它将在崇高编辑器中打开代码。

Launch Sublime Editor from JShell

从 JShell 启动 Sublime 编辑器

随时编辑代码并保存在编辑板中。

5. 将代码从外部 Java 文件加载到 REPL 中

很多时候,您已经在任何 Java 文件中编写了一些代码,并且希望将其执行到 JShell 中。 要在 JShell 中加载文件,请使用/open命令。

假设我在c://temp文件夹中有一个文件Demo.java。 内容是:

int i1 = 10;
int i2 = 20;
int i3 = 30;
int i4 = 40;

int sum(int a, int b) {
	return a+b;
}

int sum(int a, int b, int c) {
	return a+b;
}

现在,将文件加载到 JShell 中。

/open c:\\temp\\demo.java

验证在 Jshell 中加载的变量和方法。

Java Code loaded in JShell

JShell 中加载的 Java 代码

使用 Java 9 中的 REPL 工具时,您必须了解的一切。

将您的问题放在评论部分中。

学习愉快!

Java 类路径

原文: https://howtodoinjava.com/java/basics/java-classpath/

了解如何将类路径设置为环境变量并作为命令行参数传递。 在任何 Java 应用程序的运行期间,CLASSPATH是一个告诉 JVM 在何处查找类和包的参数,可以使用环境变量或命令行参数进行设置。

类路径分隔符

Windows;(分号)

Linux / Unix:(冒号)

1. 将 Java 类路径设置为环境变量

当您设置了在应用程序运行期间始终需要的 jar 文件集时,最好将它们添加到计算机的环境变量'CLASSPATH'中。 在应用程序运行时,应用程序类加载器将始终在此变量的指定路径下扫描 jar 文件和类。

要设置类路径环境变量,请在您的计算机中查找用户变量的位置,并添加存储 Jar 文件的所有路径。 在两个不同的文件夹,jar 文件或类之间使用分隔符。

例如,您可以通过以下方式找到环境变量:

  1. 在桌面上,右键单击计算机图标。
  2. 从上下文菜单中选择属性
  3. 单击高级系统设置链接。
  4. 单击环境变量。 在系统变量部分中,找到CLASSPATH环境变量并将其选中。 点击编辑。 如果CLASSPATH环境变量不存在,请单击New
  5. 添加所有用分隔符分隔的文件夹。 单击 OK。 通过单击 OK 关闭所有剩余的窗口。

System Properties

系统属性

如果是第一次创建CLASSPATH,则需要在窗口中指定变量名的名称。 使用'.'(点)表示当前目录

2. 从命令行设置 Java 类路径

使用-classpath参数从命令提示符/控制台设置类路径。 使用以下给定的命令来设置不同需求的类路径。 假设我们有一个名为dependency的文件夹,用于放置 JAR 文件和其他类。

2.1 在类路径中添加单个 jar 文件

下面的语法示例将在类路径中添加单个 jar 文件。

//WINDOWS
$ set CLASSPATH=.;C:\dependency\framework.jar

//Linux/Unix
$ export CLASSPATH=.:/dependency/framework.jar

2.2 在类路径中添加多个 jar 文件

以下语法示例将在类路径中添加多个 jar 文件。 为此,只需将操作系统的分隔符(;:)用作为CLASSPATH指定的位置之间的分隔符。

要向添加目录中存在的所有 JAR 文件,请使用通配符('*')。

//WINDOWS
$ set CLASSPATH=C:\dependency\framework.jar;C:\location\otherFramework.jar 				
$ set CLASSPATH=C:\dependency\framework.jar;C:\location\*.jar

//Linux/Unix
$ export CLASSPATH=/dependency/framework.jar:/location/otherFramework.jar  	
$ export CLASSPATH=/dependency\framework.jar:/location/*.jar

2.3 将类添加到类路径

很多时候,您可能还需要在classpath中添加单个类。 为此,只需添加存在类文件的文件夹。 例如假设location文件夹中存在五个.class文件,您希望将它们包括在类路径中。

//WINDOWS
$ set CLASSPATH=C:\dependency\*;C:\location

//Linux/Unix
$ export CLASSPATH=/dependency/*:/location

最佳做法是,始终将所有 JAR 文件和应用程序类组织在一个根文件夹中。 这可能是应用程序的工作空间。

请注意,CLASSPATH中包含的子目录不会被加载。 为了加载子目录中包含的文件,必须在CLASSPATH中显式列出这些目录和/或文件。

3. 使用-classpath参数执行 Java 程序

除了将classpath设置为环境变量之外,您还可以在使用 – classpath参数启动应用程序时将其他classpath传递给 Java 运行时。

$ javac – classpath C:\dependency\framework.jar MyApp.Java
$ java – classpath C:\dependency\framework.jar MyApp

4. 如何检查类路径

每当您希望验证CLASSPATH变量中的所有路径条目时,都可以使用echo命令进行验证。

//Windows
c:/> echo %CLASSPATH%

//Linux/Unix
$ echo $CLASSPATH

如果未设置CLASSPATH,则控制台将显示“CLASSPATH:未定义的变量”错误(Solaris 或 Linux),或仅在 Windows 命令提示符中打印%CLASSPATH%

学习愉快!

阅读更多:

Oracle Java 文档

Java – 日期流

原文: https://howtodoinjava.com/java9/stream-dates-datesuntil/

对于 Java 开发人员而言,日期和时间处理一直是一个痛苦的领域。 Java 8 中添加的新的 Date-Time API 更改了在 Java 中与日期进行交互的方式。 这是一项非常强大且急需的改进。 唯一缺少的是获得日期的,在两个后续日期之间存在一些共同的差异(尽管有可能,但没有简便的方法)。

Java 9 引入了一种新方法LocalDate.datesUntil(),该方法可以提供日期流。 使用datesUntil()可以轻松创建具有固定偏移量的日期流。

1. LocalDate.datesUntil()的语法

此方法有两种重载形式:

Stream<LocalDate> datesUntil(LocalDate end)
Stream<LocalDate> datesUntil(LocalDate end, Period step)

第一个版本(即不带Period)内部调用带有Period.ofDays(1)的第二个方法,并生成日期流,两者之间相差 1 天。

使用LocalDate.datesUntil()的日期流示例

创建日期流非常简单明了。

import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.stream.Collectors;

public class Java9StreamExamples {

    public static void main(String[] args) {
        System.out.println( getDaysInJava9(LocalDate.now(), LocalDate.now().plusDays(10)) );
        System.out.println( getDaysInJava9Weeks(LocalDate.now(), LocalDate.now().plusWeeks(10)) );
    }

    //Stream of dates with 1 day difference
    public static List<LocalDate> getDaysInJava9(LocalDate start, LocalDate end) {
        return start.datesUntil(end).collect(Collectors.toList());
    }

    //Stream of dates with 1 week difference
    public static List<LocalDate> getDaysInJava9Weeks(LocalDate start, LocalDate end) {
        return start.datesUntil(end, Period.ofWeeks(1)).collect(Collectors.toList());
    }
}

Output:

[2017-07-31, 2017-08-01, 2017-08-02, 2017-08-03, 2017-08-04, 
2017-08-05, 2017-08-06, 2017-08-07, 2017-08-08, 2017-08-09]

[2017-07-31, 2017-08-07, 2017-08-14, 2017-08-21, 2017-08-28, 
2017-09-04, 2017-09-11, 2017-09-18, 2017-09-25, 2017-10-02]

2. Java 8 中的日期流

如果您仍未使用 Java 9,则可以使用以下给定的方法生成 Date 流。 该代码与 Java 8 兼容。

import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Java9StreamExamples {

    public static void main(String[] args) 
    {
        System.out.println( getDaysInJava8(LocalDate.now(), 10) );
    }

    //Stream of dates with 1 day difference
    public static List<LocalDate> getDaysInJava8(LocalDate start, int days) 
    {
        return Stream.iterate(start, date -> date.plusDays(1))
                .limit(days)
                .collect(Collectors.toList());
    }
}

Output:

[2017-07-31, 2017-08-01, 2017-08-02, 2017-08-03, 2017-08-04, 
2017-08-05, 2017-08-06, 2017-08-07, 2017-08-08, 2017-08-09]

将我的问题放在评论部分。

学习愉快!

Java 9 Stream API 的改进

原文: https://howtodoinjava.com/java9/stream-api-improvements/

通过示例了解 API(即takeWhile/dropWhile方法,ofNullableiterate方法)中 Java 9 的新改进。

Table of Contents

Limiting Stream with takeWhile() and dropWhile() methods
Overloaded Stream iterate method
New Stream ofNullable() method

使用takeWhile()dropWhile()方法限制Stream

新方法takeWhiledropWhile允许您基于谓词获取流的一部分。 这里的流可以是有序的也可以是无序的,所以:

  1. 在有序流上,takeWhile返回流的“最长前缀”,其元素匹配给定谓词。
  2. 在无序流上,takeWhile返回流的子集,从流的开头开始,其元素匹配给定谓词(但不是全部)。

dropWhile方法与takeWhile方法相反。

  1. 在有序流上,dropWhile返回匹配给定谓词的“最长前缀”之后的其余项。
  2. 在无序流上,dropWhile返回不匹配给定谓词的剩余的流元素。

takeWhiledropWhile示例

在此示例中,我们具有从ai的字符列表。 我希望所有可能在迭代中出现在字符d之前的字符。

List<String> alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");

List<String> subset1 = alphabets
        .stream()
        .takeWhile(s -> !s.equals("d"))
        .collect(Collectors.toList());

System.out.println(subset1);

Output:

[a, b, c]

如前所述,dropWhile的作用与takeWhile方法相反,因此在上面的示例中,如果使用dropWhile,它将返回takeWhile谓词留下的所有字符。

List<String> alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");

List<String> subset2 = alphabets
        .stream()
        .dropWhile(s -> !s.equals("d"))
        .collect(Collectors.toList());

System.out.println(subset2);

Output:

[d, e, f, g, h, i]

重载Stream迭代方法

iterate()方法用于创建以单个元素(种子)开头的流,并通过依次应用一元运算符来生成后续元素。 结果是无限的流。 为了终止流,使用限制或其他一些短路函数,例如findFirstfindAny

Java 8 中的iterate方法具有签名:

static Stream iterate(final T seed, final UnaryOperator f)

在 Java 9 中,新的iterate重载版本将谓词作为第二个参数:

static Stream iterate(T seed, Predicate super T> hasNext, UnaryOperator next)

让我们看看从 Java 8 到 Java 9 的iterate方法的使用不同。

Java 8 中的迭代方法

List<Integer> numbers = Stream.iterate(1, i -> i+1)
                            .limit(10)
                            .collect(Collectors.toList());

System.out.println(numbers);

Output:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Java 9 中的迭代方法

List<Integer> numbers = Stream.iterate(1, i -> i <= 10 ,i -> i+1)
                                .collect(Collectors.toList());

System.out.println(numbers);

Output:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

在上面的示例中,第一个流是 Java 8 使用限制迭代的方式。 第二个使用谓词作为第二个参数。

新的Stream ofNullable()方法

在 Java 8 之前,流中不能具有null值。 会导致NullPointerException

在 Java 9 中,ofNullable方法使您可以创建单元素流,该流将包装一个值(如果不为null),否则为空流。

Stream<String> stream = Stream.ofNullable("123");
System.out.println(stream.count());

stream = Stream.ofNullable(null);
System.out.println(stream.count());

Output:

1
0

此处,count方法返回流中非空元素的数量。

从技术上说,Stream.ofNullable()与流条件上下文中的空条件检查非常相似。

将我的问题放在评论部分。

学习愉快!

Java 9 中的不可变集合和工厂方法

原文: https://howtodoinjava.com/java9/create-immutable-collections-factory-methods/

学习使用新的创建不可变集合,例如不可变列表不可变集不可变映射 Java 9 中的工厂方法。

Table of Contents

Create Immutable List
Create Immutable Set
Create Immutable Map

创建不可变列表

使用List.of()静态工厂方法创建不可变列表。 它具有以下不同的重载版本:

static <E> List<E>	of()
static <E> List<E>	of(E e1)
static <E> List<E>	of(E e1, E e2)
static <E> List<E>	of(E e1, E e2, E e3)
static <E> List<E>	of(E e1, E e2, E e3, E e4)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
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)
//varargs
static <E> List<E>	of(E... elements)

这些方法创建的List实例具有以下特征:

  1. 这些列表是不可变的。 不能在这些列表中添加,删除或替换元素。 调用任何可变方法(即addaddAllclearremoveremoveAllreplaceAll)将始终导致抛出UnsupportedOperationException
  2. 它们不允许null元素。 尝试添加null元素将导致NullPointerException
  3. 如果所有元素都是可序列化的,它们可以可序列化的
  4. 列表中元素的顺序与提供的参数或提供的数组中的元素的顺序相同。

让我们看一些使用不可变列表的例子。

package com.howtodoinjava;

import java.util.List;

public class ImmutableCollections 
{
    public static void main(String[] args) 
    {
        List<String> names = List.of("Lokesh", "Amit", "John");

        //Preserve the elements order
        System.out.println(names);

        //names.add("Brian"); //UnsupportedOperationException occured

        //java.lang.NullPointerException
        //List<String> names2 = List.of("Lokesh", "Amit", "John", null); 
    }
}

Output:

[Lokesh, Amit, John]

创建不可变集

Set的行为与List非常相似,只是差异很小。 例如:

  1. Set也不允许重复元素。 传递的任何重复元素将导致IllegalArgumentException
  2. 集合元素的迭代顺序未指定,可能会发生变化。

所有Set工厂方法都具有与List相同的签名。

static <E> Set<E>	of()
static <E> Set<E>	of(E e1)
static <E> Set<E>	of(E e1, E e2)
static <E> Set<E>	of(E e1, E e2, E e3)
static <E> Set<E>	of(E e1, E e2, E e3, E e4)
static <E> Set<E>	of(E e1, E e2, E e3, E e4, E e5)
static <E> Set<E>	of(E e1, E e2, E e3, E e4, E e5, E e6)
static <E> Set<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
static <E> Set<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
static <E> Set<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
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)
//varargs
static <E> Set<E>	of(E... elements)

我们来看几个不可变集的例子。

import java.util.Set;

public class ImmutableCollections {
    public static void main(String[] args) {
        Set<String> names = Set.of("Lokesh", "Amit", "John");

        //Elements order not fixed
        System.out.println(names);

        //names.add("Brian"); //UnsupportedOperationException occured

        //java.lang.NullPointerException
        //Set<String> names2 = Set.of("Lokesh", "Amit", "John", null); 

        //java.lang.IllegalArgumentException
        //Set<String> names3 = Set.of("Lokesh", "Amit", "John", "Amit"); 
    }
}

创建不可变映射

Map工厂方法与ListSet重载工厂方法相同。 唯一的区别是方法的签名采用交替的键和值作为参数。 例如:

static <K,V> Map<K,V>	of()
static <K,V> Map<K,V>	of(K k1, V v1)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2)
...
...
static <K,V> Map<K,V>	ofEntries(Map.Entry<? extends K,? extends V>... entries)

Java 9 还提供了一种特殊的方法来创建 Map 条目实例。

static <K,V> Map.Entry<K,V>	entry​(K k, V v)

让我们举一个在 Java 9 中创建不可变Map的示例。

import java.util.Map;

public class ImmutableCollections {
    public static void main(String[] args) {
        Map<String, String> names = Map.ofEntries(
                Map.entry("1", "Lokesh"),
                Map.entry("2", "Amit"),
                Map.entry("3", "Brian"));

        System.out.println(names);

        //UnsupportedOperationException
        //names.put("2", "Ravi");
    }
}

Output:

{1=Lokesh, 2=Amit, 3=Brian}

显然,在 Java 9 中创建不可变集合的新工厂方法具有很高的可读性和易用性

请在评论部分中将您对这些不可变集合的观点放下。

学习愉快!

接口中的私有方法 – Java 9

原文: https://howtodoinjava.com/java9/java9-private-interface-methods/

从 Java 9 开始,您可以在接口中包含私有方法。 使用私有方法,现在也可以在接口中实现封装

在此 Java 9 教程中,我们将详细了解接口私有方法

Table of Contents

Interfaces till Java 7
Static and defaults methods since Java 8
Private methods since java 9
Java 9 Private Interface Method Example
Summary

Java 7 接口

在 Java 7 和所有早期版本中,接口非常简单。 它们只能包含公共抽象方法。 这些接口方法必须由选择实现接口的类实现。

public interface CustomInterface {
    public abstract void method();
}

public class CustomClass implements CustomInterface {
    @Override
    public void method() {
        System.out.println("Hello World");
    }

    public static void main(String[] args){
        CustomInterface instance = new CustomClass();
        instance.method();
    }
}

Output:

Hello World

自 Java 8 以来的静态方法和默认方法

Java 8 中,除了公共抽象方法之外,您还可以使用公共静态方法和公共默认方法。

public interface CustomInterface {

    public abstract void method1();

    public default void method2() {
        System.out.println("default method");
    }

    public static void method3() {
        System.out.println("static method");
    }
}

public class CustomClass implements CustomInterface {

    @Override
    public void method1() {
        System.out.println("abstract method");
    }

    public static void main(String[] args){
        CustomInterface instance = new CustomClass();
        instance.method1();
        instance.method2();
        CustomInterface.method3();
    }
}

Output:

abstract method
default method
static method

访问修饰符public在以上所有接口方法声明中都是可选的。 我添加它们只是为了提高可读性。

自 Java 9 以来的私有方法

从 Java 9 开始,您将可以在接口中添加私有方法私有静态方法

这些私有方法将改善接口内部的代码可重用性。 举例来说,如果需要两个默认方法来共享代码,则可以使用私有接口方法来共享代码,但不必将该私有方法暴露给实现类。

在接口中使用私有方法有四个规则:

  1. 接口私有方法不能是抽象的。
  2. 私有方法只能在接口内部使用。
  3. 私有静态方法可以在其他静态和非静态接口方法中使用。
  4. 私有非静态方法不能在私有静态方法内部使用。
public interface CustomInterface {

    public abstract void method1();

    public default void method2() {
        method4();  //private method inside default method
        method5();  //static method inside other non-static method
        System.out.println("default method");
    }

    public static void method3() {
        method5(); //static method inside other static method
        System.out.println("static method");
    }

    private void method4(){
        System.out.println("private method");
    } 

    private static void method5(){
        System.out.println("private static method");
    } 
}

public class CustomClass implements CustomInterface {

    @Override
    public void method1() {
        System.out.println("abstract method");
    }

    public static void main(String[] args){
        CustomInterface instance = new CustomClass();
        instance.method1();
        instance.method2();
        CustomInterface.method3();
    }
}

Output:

abstract method
private method
private static method
default method
private static method
static method

Java 9 接口私有方法示例

让我们看一个演示,以了解接口私有方法的用法。

我正在创建具有两个函数的计算器类。 第一个函数将接受一些整数并将所有偶数相加。 第二个函数将接受一些整数并将所有奇数相加。

CustomCalculator.java – 接口

import java.util.function.IntPredicate;
import java.util.stream.IntStream;

public interface CustomCalculator 
{
    default int addEvenNumbers(int... nums) {
        return add(n -> n % 2 == 0, nums);
    }

    default int addOddNumbers(int... nums) {
        return add(n -> n % 2 != 0, nums);
    }

    private int add(IntPredicate predicate, int... nums) { 
        return IntStream.of(nums)
                .filter(predicate)
                .sum();
    }
}

Main.java – 类

public class Main implements CustomCalculator {

    public static void main(String[] args) {
        CustomCalculator demo = new Main();

        int sumOfEvens = demo.addEvenNumbers(1,2,3,4,5,6,7,8,9);
        System.out.println(sumOfEvens);

        int sumOfOdds = demo.addOddNumbers(1,2,3,4,5,6,7,8,9);
        System.out.println(sumOfOdds);
    } 
}

Output:

20
25

总结

简而言之, java 9 私有接口方法可以是静态的也可以是实例的。 在这两种情况下,私有方法都不会被子接口或实现继承。 它们主要是为了提高代码仅在接口内的可重用性,从而改善封装。

让我们回顾一下 Java 9 中所有允许的方法类型。

方法类型 从何时起
公共抽象 Java 7
公共默认 Java 8
公共静态 Java 8
私有 Java 9
私有静态 Java 9

将您的问题放在评论部分中。

学习愉快!

参考: JEP 213

下载源码

Java 8 教程

Java 8 教程

原文: https://howtodoinjava.com/java-8-tutorial/

Java 8 教程列出了重要的 Java 8 特性,并提供了此发行版中引入的示例。 所有特性均具有指向详细教程的链接,例如 lambda 表达式,Java 流,函数式接口和日期时间 API 更改。

Java SE 8于 2014 年初发布的。 在 Java 8 中,最受关注的特性是 lambda 表达式。 它还具有许多其他重要特性,例如默认方法,流 API 和新的日期/时间 API。 让我们通过示例了解 Java 8 中的这些新特性

Table of Contents

1\. Lambda Expression
2\. Functional Interface
3\. Default Methods
4\. Streams
5\. Date/Time API Changes

1. Lambda 表达式

Lambda 表达式对于使用 Scala 等其他流行编程语言的我们很多人并不陌生。 在 Java 编程语言中,Lambda 表达式(或函数)只是匿名函数,即不带名称且不受标识符限制的函数。 它们准确地写在需要的地方,通常以作为其他函数的参数。

Lambda 表达式的基本语法为:

either
(parameters) -> expression
or
(parameters) -> { statements; }
or 
() -> expression

典型的 lambda 表达式示例如下所示:

(x, y) -> x + y  //This function takes two parameters and return their sum.

请注意,根据xy的类型,方法可能会在多个地方使用。 参数可以匹配intInteger或简单地也可以匹配String。 根据上下文,它将添加两个整数或连接两个字符串。

编写 Lambda 表达式的规则

  1. Lambda 表达式可以具有零个,一个或多个参数。
  2. 参数的类型可以显式声明,也可以从上下文中推断出来。
  3. 多个参数用强制括号括起来,并用逗号分隔。 空括号用于表示空参数集。
  4. 当有单个参数时,如果推断出其类型,则不强制使用括号。 例如a -> a * a
  5. Lambda 表达式的主体可以包含零个,一个或多个语句。
  6. 如果 lambda 表达式的主体具有单个语句,则不必使用大括号,并且匿名函数的返回类型与主体表达式的返回类型相同。 如果正文中的语句多于一个,则这些语句必须用大括号括起来。

阅读更多: Java 8 Lambda 表达式教程

2. 函数式接口

函数式接口也称为单一抽象方法接口(SAM 接口)。 顾名思义,它们允许其中恰好是一种抽象方法。 Java 8 引入了一个注解,即@FunctionalInterface,当您注解的接口违反函数式接口的约定时,该注解可用于编译器级错误。

典型的函数式接口示例:

@FunctionalInterface
public interface MyFirstFunctionalInterface {
	public void firstWork();
}

请注意,即使省略@FunctionalInterface注释,函数式接口也有效。 它仅用于通知编译器在接口内部强制执行单个抽象方法。

另外,由于默认方法不是抽象的,因此可以随意向函数式接口添加任意数量的默认方法

要记住的另一个重要点是,如果接口声明了一个覆盖java.lang.Object的公共方法之一的抽象方法,那么该接口的任何实现都将具有java.lang.Object的实现,因此这也不会计入接口的抽象方法数量。 例如,下面是完全有效的函数式接口。

@FunctionalInterface
public interface MyFirstFunctionalInterface 
{
	public void firstWork();

	@Override
	public String toString();                //Overridden from Object class

	@Override
	public boolean equals(Object obj);        //Overridden from Object class
}

阅读更多: Java 8 函数式接口教程

3. 默认方法

Java 8 允许您在接口中添加非抽象方法。 这些方法必须声明为默认方法。 Java 8 中引入了默认方法以启用 lambda 表达式的特性。

默认方法使您可以向库的接口添加新特性,并确保与为这些接口的较早版本编写的代码二进制兼容。

让我们看一个例子:

public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}

Moveable接口定义了一种方法move(),并提供了默认实现。 如果有任何类实现了此接口,则无需实现其自己的move()方法版本。 它可以直接调用instance.move()。 例如:

public class Animal implements Moveable{
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

Output: I am moving

如果类愿意自定义move()方法的行为,则它可以提供自己的自定义实现并覆盖该方法。

重做更多: Java 8 默认方法教程

4. Java 8 流

引入的另一项重大更改是 Java 8 流 API ,它提供了一种以各种方式处理一组数据的机制,这些方式可以包括过滤,转换或任何其他可能对应用程序有用的方式。

Java 8 中的 流 API 支持不同类型的迭代,您可以在其中简单地定义要处理的项目集,对每个项目执行的操作,以及存储这些操作的输出。

流 API 的示例。 在此示例中,itemsString值的集合,您想删除以某些前缀文本开头的条目。

List<String> items;
String prefix;
List<String> filteredList = items.stream().filter(e -> (!e.startsWith(prefix))).collect(Collectors.toList());

这里items.stream()表示我们希望使用 流 API 处理items集合中的数据。

阅读更多: Java 8 内部和外部迭代

5. Java 8 日期/时间 API 更改

新的日期和时间 API /类(JSR-310),也称为 ThreeTen ,它们仅改变了您在 Java 应用程序中处理日期的方式。

日期

Date类甚至已过时。 打算替换Date类的新类是LocalDateLocalTimeLocalDateTime

  1. LocalDate类表示日期。 没有时间或时区的表示。
  2. LocalTime类表示时间。 没有日期或时区的表示。
  3. LocalDateTime类表示日期时间。 没有时区的表示。

如果您想将日期特性与区域信息一起使用,则 Lambda 会为您提供额外的 3 类,类似于上面的一种,即OffsetDateOffsetTimeOffsetDateTime。 时区偏移可以以“+05:30”或“Europe/Paris”格式表示。 这是通过使用另一个类即ZoneId完成的。

LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.of(12, 20);
LocalDateTime localDateTime = LocalDateTime.now(); 
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Paris"));

时间戳和持续时间

为了随时表示特定的时间戳,需要使用的类是InstantInstant类表示精确到纳秒的时间瞬间。 即时操作包括与另一个Instant进行比较,以及增加或减少持续时间。

Instant instant = Instant.now();
Instant instant1 = instant.plus(Duration.ofMillis(5000));
Instant instant2 = instant.minus(Duration.ofMillis(5000));
Instant instant3 = instant.minusSeconds(10);

Duration类是用 Java 语言首次带来的全新概念。 它表示两个时间戳之间的差异。

Duration duration = Duration.ofMillis(5000);
duration = Duration.ofSeconds(60);
duration = Duration.ofMinutes(10);

Duration处理较小的时间单位,例如毫秒,秒,分钟和小时。 它们更适合与应用程序代码进行交互。 要与人互动,您需要使持续时间更长,该类与Period类一起提供。

Period period = Period.ofDays(6);
period = Period.ofMonths(6);
period = Period.between(LocalDate.now(), LocalDate.now().plusDays(60));

阅读更多: Java 8 日期和时间 API 更改

在注释部分中,将您对此 Java 8 教程的问题留给我。

学习愉快!

Java 8 forEach

原文: https://howtodoinjava.com/java8/foreach-method-example/

Java forEach是一种工具方法,可迭代诸如(列表,集合或映射)和之类的集合,并对它的每个元素执行特定操作。

1. Java 8 forEach 方法

1.1 Iterable.forEach()

下面的代码片段显示了Iterable接口中forEach的默认实现。 它使Iterable.forEach()方法可用于除Map 之外的所有集合类。 下一节将讨论Map中的 forEach()方法。

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

上面的方法Iterable的每个元素执行给定的操作,直到所有元素都已处理或该操作引发异常。

action表示接受单个输入参数且不返回结果的操作。 它是Consumer接口的实例。

List<String> names = Arrays.asList("Alex", "Brian", "Charles");

names.forEach(System.out::println);

//Console output
Alex
Brian
Charles

可以使用此简单语法创建自定义消费者操作。 这里Object类型应替换为集合或流中元素的类型。

List<String> names = Arrays.asList("Alex", "Brian", "Charles");

Consumer<String> makeUpperCase = new Consumer<String>()
{
    @Override
    public void accept(String t) 
    {
    	System.out.println(t.toUpperCase());
    }
};

names.forEach(makeUpperCase);	

//Console output
ALEX
BRIAN
CHARLES

1.2 Map.forEach()

此方法对此映射中的每个条目执行给定的 BiConsumer 操作,直到已处理所有条目或该操作引发异常。

default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch(IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}

Map<String, String> map = new HashMap<String, String>();

map.put("A", "Alex");
map.put("B", "Brian");
map.put("C", "Charles");

map.forEach((k, v) -> 
	System.out.println("Key = " + k + ", Value = " + v));

//Console Output

Key = A, Value = Alex
Key = B, Value = Brian
Key = C, Value = Charles

List示例类似,我们可以创建自定义双消费者操作,该操作从映射中获取键值对,然后一次处理每个条目。

BiConsumer<String, Integer> action = (a, b) -> 
{ 
    System.out.println("Key is : " + a); 
    System.out.println("Value is : " + b); 
}; 

Map<String, Integer> map = new HashMap<>();

map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

map.forEach(action);

程序输出。

Key is : A
Value is : 1

Key is : B
Value is : 2

Key is : C
Value is : 3

2. 使用ListforEach示例

Java 程序迭代流的所有元素并执行操作。 在此示例中,我们将打印所有偶数。

List<Integer> numberList = Arrays.asList(1,2,3,4,5);

Consumer<Integer> action = System.out::println;

numberList.stream()
	.filter(n -> n%2  == 0)
	.forEach( action );

程序输出:

2
4

3. 使用MapforEach示例

我们已经看到上面的程序遍历HashMap的所有条目并执行操作。

我们还可以遍历映射键和值,并对所有元素执行任何操作。

HashMap<String, Integer> map = new HashMap<>();

map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

//1\. Map entries
Consumer<Map.Entry<String, Integer>> action = System.out::println;

map.entrySet().forEach(action);

//2\. Map keys
Consumer<String> actionOnKeys = System.out::println;

map.keySet().forEach(actionOnKeys);

//3\. Map values
Consumer<Integer> actionOnValues = System.out::println;

map.values().forEach(actionOnValues);

程序输出:

A=1
B=2
C=3

A
B
C

1
2
3

学习愉快!

Java 8 流 API

原文: https://howtodoinjava.com/java8/java-streams-by-examples/

可以将 Java 中的流定义为源中支持其聚合操作的元素序列。 此处的来源是指向流提供数据的集合数组

流保持数据在源中的顺序。 聚合操作批量操作是使我们能够轻松,清晰地表达对流元素的常用操作的操作。

java_8_lambdas

Table of Contents

1\. Streams vs. Collections
2\. Different ways to create streams
3\. Converting streams to collections
4\. Core stream operations
    4.1\. Intermediate operations
    4.2\. Terminal operations
5\. Short-circuit operations
6\. Parallelism

在继续之前,重要的是要了解 Java 8 流的设计方式是大多数流操作仅返回流。 这有助于我们创建流操作链。 这称为管道内衬。 在这篇文章中,我将多次使用该术语,因此请牢记。

1. Java 流与集合

我们所有人都已经在 youtube 或其他此类网站上观看了在线视频。 当您开始观看视频时,文件的一小部分首先会加载到计算机中并开始播放。 开始播放之前,您无需下载完整的视频。 这称为流式传输。 我将尝试将此概念与集合相关联,并通过流进行区分。

在基本级别上,集合和流之间的差异与计算事物时有关。 集合是内存中的数据结构,它保存该数据结构当前具有的所有值-集合中的每个元素必须先计算,然后才能添加到集合中。 流是概念上固定的数据结构,其中元素按需计算。 这带来了显着的编程优势。 想法是,用户将仅从流中提取他们需要的值,并且仅在需要时才对用户无形地生成这些元素。 这是生产者-消费者关系的一种形式。

在 java 中,java.util.Stream表示可以在其上执行一个或多个操作的流。 流操作是中间或终止。 尽管终止操作返回某个类型的结果,但是中间操作返回流本身,因此您可以连续链接多个方法调用。 流是在源上创建的,例如类似于列表或集合的java.util.Collection(不支持映射)。 流操作可以顺序执行,也可以并行执行。

基于以上几点,如果我们列出Stream的各种特征,它们将如下所示:

  • 不是数据结构
  • 专为 lambdas 设计
  • 不支持索引访问
  • 可以轻松输出为数组或列表
  • 支持延迟访问
  • 可并行化

2. 创建流的不同方法

以下是从集合构建流的最流行的不同方法。

2.1 Stream.of(val1, val2, val3 ....)

public class StreamBuilders 
{
     public static void main(String[] args)
     {
         Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9);
         stream.forEach(p -> System.out.println(p));
     }
}

2.2 Stream.of(arrayOfElements)

public class StreamBuilders 
{
     public static void main(String[] args)
     {
         Stream<Integer> stream = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} );
         stream.forEach(p -> System.out.println(p));
     }
}

2.3. List.stream()

public class StreamBuilders 
{
     public static void main(String[] args)
     {
         List<Integer> list = new ArrayList<Integer>();

         for(int i = 1; i< 10; i++){
             list.add(i);
         }

         Stream<Integer> stream = list.stream();
         stream.forEach(p -> System.out.println(p));
     }
}

2.4 Stream.generate()Stream.iterate()

public class StreamBuilders 
{
     public static void main(String[] args)
     {
         Stream<Date> stream = Stream.generate(() -> { return new Date(); });
         stream.forEach(p -> System.out.println(p));
     }
}

2.5 字符串字符或字符串标记

public class StreamBuilders 
{
     public static void main(String[] args)
     {
        IntStream stream = "12345_abcdefg".chars();
        stream.forEach(p -> System.out.println(p));

		//OR

		Stream<String> stream = Stream.of("A$B$C".split("\\$"));
        stream.forEach(p -> System.out.println(p));
     }
}

除了上述列表以外,还有其他一些方法,例如使用Stream.Buider或使用中间操作。 我们将不时在单独的文章中了解它们。

3. 将流转换为集合

我应该说将流转换为其他数据结构。

请注意,这不是真正的转换。 它只是将流中的元素收集到集合或数组中。

3.1 将流转换为列表 – Stream.collect(Collectors.toList())

public class StreamBuilders {
     public static void main(String[] args){
         List<Integer> list = new ArrayList<Integer>();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         Stream<Integer> stream = list.stream();
         List<Integer> evenNumbersList = stream.filter(i -> i%2 == 0).collect(Collectors.toList());
         System.out.print(evenNumbersList);
     }
}

3.2 将流转换为数组 – Stream.toArray(EntryType[]::new)

public class StreamBuilders {
     public static void main(String[] args){
         List<Integer> list = new ArrayList<Integer>();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         Stream<Integer> stream = list.stream();
         Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
         System.out.print(evenNumbersArr);
     }
}

还有很多其他方式可以将流收集到SetMap或多种方式中。 只需通过收集器类并尝试牢记它们。

4. 核心流操​​作

流抽象为您提供了一长串有用的函数。 我不会覆盖所有内容,但是我打算在这里列出所有最重要的内容,您必须第一手知道。

在继续之前,让我们预先构建String的集合。 我们将在此列表上构建示例,以便易于联系和理解。

List<String> memberNames = new ArrayList<>();
memberNames.add("Amitabh");
memberNames.add("Shekhar");
memberNames.add("Aman");
memberNames.add("Rahul");
memberNames.add("Shahrukh");
memberNames.add("Salman");
memberNames.add("Yana");
memberNames.add("Lokesh");

这些核心方法分为以下两个部分:

4.1 中间操作

中间操作返回流本身,因此您可以连续链接多个方法调用。 让我们学习重要的东西。

4.1.1 Stream.filter()

Filter接受谓词以过滤流中的所有元素。 此操作是中间操作,使我们能够对结果调用另一个流操作(例如forEach)。

memberNames.stream().filter((s) -> s.startsWith("A"))
                    .forEach(System.out::println);

Output: 

Amitabh
Aman

4.1.2. Stream.map()

中间操作映射通过给定的函数将每个元素转换为另一个对象。 下面的示例将每个字符串转换为大写字符串。 但是您也可以使用map()将每个对象转换为另一种类型。

 memberNames.stream().filter((s) -> s.startsWith("A"))
                     .map(String::toUpperCase)
                     .forEach(System.out::println);

Output: 

AMITABH
AMAN

4.1.2 Stream.sorted()

Sorted是一个中间操作,它返回流的排序视图。 除非您通过自定义比较器,否则元素将以自然顺序排序。

memberNames.stream().sorted()
                    .map(String::toUpperCase)
                    .forEach(System.out::println);
Output:

AMAN
AMITABH
LOKESH
RAHUL
SALMAN
SHAHRUKH
SHEKHAR
YANA

请记住,sorted只会创建流的排序视图,而不会操纵支持的集合的顺序。 memberNames的顺序保持不变。

4.2 终止操作

终止操作返回某个类型的结果,而不是流。

4.2.1 Stream.forEach()

此方法有助于迭代流的所有元素,并对每个元素执行一些操作。 该操作作为 lambda 表达式参数传递。

memberNames.forEach(System.out::println);

4.2.2 Stream.collect()

collect()方法用于从流中接收元素并将其存储在集合中,并在参数函数中提到。

List<String> memNamesInUppercase = memberNames.stream().sorted()
                            .map(String::toUpperCase)
                            .collect(Collectors.toList());

System.out.print(memNamesInUppercase);

Output: [AMAN, AMITABH, LOKESH, RAHUL, SALMAN, SHAHRUKH, SHEKHAR, YANA]

4.2.3 Stream.match()

各种匹配操作可用于检查某个谓词是否与流匹配。 所有这些操作都是终止操作,并返回布尔结果。

boolean matchedResult = memberNames.stream()
					.anyMatch((s) -> s.startsWith("A"));

System.out.println(matchedResult);

matchedResult = memberNames.stream()
					.allMatch((s) -> s.startsWith("A"));

System.out.println(matchedResult);

matchedResult = memberNames.stream()
					.noneMatch((s) -> s.startsWith("A"));

System.out.println(matchedResult);

Output: 

true
false
false

4.2.4 Stream.count()

Count 是一个终止操作,将流中的元素数作为long值返回。

long totalMatched = memberNames.stream()
					.filter((s) -> s.startsWith("A"))
					.count();

System.out.println(totalMatched);

Output: 2

4.2.5 Stream.reduce()

此终止操作使用给定函数对流的元素进行归约。 结果是一个保留归约的可选值。

Optional<String> reduced = memberNames.stream()
					.reduce((s1,s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);

Output: Amitabh#Shekhar#Aman#Rahul#Shahrukh#Salman#Yana#Lokesh

5. 流短路操作

尽管对满足谓词的集合内的所有元素执行流操作,但通常需要在迭代过程中遇到匹配元素时中断操作。 在外部迭代中,将使用if-else块。 在内部迭代中,可以使用某些方法来实现此目的。 让我们看一下两种方法的示例:

5.1 Stream.anyMatch()

一旦作为谓词传递的条件满足,此方法将返回true。 它不会再处理任何元素。

boolean matched = memberNames.stream()
					.anyMatch((s) -> s.startsWith("A"));

System.out.println(matched);

Output: true

5.2 Stream.findFirst()

它将从流中返回第一个元素,然后不再处理任何其他元素。

String firstMatchedName = memberNames.stream()
				.filter((s) -> s.startsWith("L"))
				.findFirst().get();

System.out.println(firstMatchedName);

Output: Lokesh

6. Java 流中的并行性

借助 Java SE 7 中添加的派生/连接框架,我们有了有效的机制来在应用程序中实现并行操作。 但是,实现此框架本身是一项复杂的任务,如果没有正确执行,那么这是一项艰巨的任务。 它是可能导致应用程序崩溃的复杂多线程错误的来源。 通过引入内部迭代,我们可以并行执行操作。

要启用并行性,您要做的就是创建并行流,而不是顺序流。 令您惊讶的是,这确实非常容易。 在上面列出的任何流示例中,只要您想在并行内核中使用多个线程来完成特定作业,您都必须调用方法parallelStream()方法而不是stream()方法。

public class StreamBuilders {
     public static void main(String[] args){
        List<Integer> list = new ArrayList<Integer>();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
		 //Here creating a parallel stream
         Stream<Integer> stream = list.parallelStream();	
         Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
         System.out.print(evenNumbersArr);
     }
}

这项工作的主要推动力是使并行性更易于开发人员使用。 尽管 Java 平台已经为并发和并行性提供了强大的支持,但是开发人员在根据需要将其代码从顺序迁移到并行时会遇到不必要的障碍。 因此,重要的是要鼓励顺序友好和并行友好的习语。 通过将重点转移到描述应该执行什么计算,而不是应该如何执行计算,可以方便地进行操作。

同样重要的是要在使并行性变得更容易但不至于使其变得不可见之间取得平衡。 使并行性透明将引入不确定性,并可能在用户可能不期望的情况下进行数据竞争。

关于 Java 8 中引入的流抽象的基础知识,这就是我要分享的全部内容。 我将在以后的文章中讨论与流相关的其他各种事情。

学习愉快!

阅读更多:

流操作

中间操作

终止操作

其他例子

Java 8 – 流map()flatMap()

Java 8 – 无限流

Java 8 – 流最大和最小

Java 8 – 随机数流

Java 8 – 流元素数

Java 8 – 获取流的最后一个元素

Java 8 – 在流中查找或删除重复项

Java 8 – IntStream

Java 8 – IntStream到集合

Java 8 – IntPredicate

Java 8 – 流去重

Java 8 – 按多个字段的流去重

Java 8 – 行流

Java 8 – 流if-else逻辑

Java 8 – 流重用 – 遍历流多次?

Java 8 – 将流转换为映射

Java 8 – 将流转换为数组

Java 8 – 将流转换为列表

Java 8 – 将IterableIterator转换为流

Java 8 – 对数字和字符串排序

Java 8 – 对多个字段上的对象排序

Java 8 – 连接字符串流

Java 8 – 合并流

Java 9 – 流 API 改进

Java 流装箱示例

原文: https://howtodoinjava.com/java8/java8-boxed-intstream/

Java 8 中,如果要将对象流转换为集合,则可以使用Collector类中的静态方法之一。

//It works perfect !!
List<String> strings = Stream.of("how", "to", "do", "in", "java")
    				.collect(Collectors.toList());

但是,相同的过程不适用于原始类型流。

//Compilation Error !!
IntStream.of(1,2,3,4,5)
    .collect(Collectors.toList());

要转换原始流,必须首先将包装类中的元素装箱,然后收集它们。 这种类型的流称为装箱流

1. IntStream – 整数流

示例将int流转换为Integer列表

//Get the collection and later convert to stream to process elements
List<Integer> ints = IntStream.of(1,2,3,4,5)
				.boxed()
				.collect(Collectors.toList());

System.out.println(ints);

//Stream operations directly
Optional<Integer> max = IntStream.of(1,2,3,4,5)
				.boxed()
				.max(Integer::compareTo);

System.out.println(max);

程序输出:

[1, 2, 3, 4, 5]
5

2. LongStream – 长整数流

示例将long流转换为Long列表

List<Long> longs = LongStream.of(1l,2l,3l,4l,5l)
				.boxed()
				.collect(Collectors.toList());

System.out.println(longs);

Output:

[1, 2, 3, 4, 5]

3. DoubleStream – 双精度流

示例将double流转换为Double列表

List<Double> doubles = DoubleStream.of(1d,2d,3d,4d,5d)
				.boxed()
				.collect(Collectors.toList());

System.out.println(doubles);

Output:

[1.0, 2.0, 3.0, 4.0, 5.0]

Java 流 API 中与装箱流原始类型有关的评论部分中,向我提出您的问题。

学习愉快!

Lambda 表达式

原文: https://howtodoinjava.com/java8/lambda-expressions/

java lambda 表达式是 Java 8 附带的一个非常令人兴奋的新特性。 对于我们许多使用 scala 之类的高级语言工作的人来说,它们并不陌生。

实际上,如果您回顾历史并尝试找出过去 20 年中 Java 语言的任何改进,您将无法回忆起许多令人兴奋的事情。 在过去的十年中,java 中只有很少的并发类,泛型,如果您也同意,那么注释也是如此。 Lambda 表情打破了这种干旱,感觉就像是一件令人愉快的礼物。

lambda

在本文中,我将分三部分介绍 lambda 表达式及其概念。 以下是继续进行的计划。

 Table of Contents
1\. What is a lambda expression in Java?
2\. Java 8 functional interface
3\. Lambda expression examples

让我们坚持计划并逐步进行。

1. Java 中的 lambda 表达式是什么?

在编程中, Lambda 表达式(或函数)只是一个匿名函数,即不带名称且不受标识符约束的函数。

换句话说,lambda 表达式是无名称的函数,以常量值形式给出,并精确地写在需要的地方,通常作为其他函数的参数

Lambda 表达式的最重要特征是它们在其出现的上下文中执行。 因此,类似的 lambda 表达式可以在其他一些上下文中以不同的方式执行(即逻辑是相同的,但基于传递给函数的不同参数,结果将有所不同)。

上面的定义充满了关键字,只有当您已经深入了解什么是 lambda 表达式时才能理解。 因此,一旦您对下一部分中的 lambda 表达式有了更好的了解,我建议您重新阅读以上段落。

因此,很明显 lambda 是某种没有名称和标识符的函数。 好吧,有什么大不了的? 为什么每个人都这么兴奋?

答案在于,与面向对象编程(OOP)相比,函数式编程所带来的好处。 大多数 OOP 语言围绕对象和实例发展,并且仅将它们作为头等公民对待。 另一个重要的实体,即职能倒退。 在 Java 中尤其如此,在 Java 中,函数不能存在于对象外部。 函数本身在 Java 中没有任何意义,直到它与某个对象或实例相关为止。

但是在函数式编程中,您可以定义函数,为它们提供引用变量并将其作为方法参数传递等等。 JavaScript 是一个很好的例子,您可以在其中传递回调方法,例如到 Ajax 调用。 这是非常有用的特性,并且从一开始就在 Java 中缺少。 现在使用 Java 8,我们也可以使用这些 lambda 表达式。

1.1 Lambda 语法

典型的 lambda 表达式语法如下所示:

(x, y) -> x + y 	//This function takes two parameters 
					//and return their sum.

现在基于xy的类型,可以在多个地方使用方法。 参数可以匹配intInteger或简单地也可以匹配String。 根据上下文,它将添加两个整数或连接两个字符串。

语法:

lambda 表达式的其他可能语法为:

either

(parameters) -> expression 			//1

or

(parameters) -> { statements; } 	//2

or 

() -> expression 					//3

1.2 Lambda 示例

我们还来看一些 lambda 表达式的示例:

(int a, int b) -> 	a * b          		// takes two integers and returns their multiplication

(a, b) 			-> 	a - b               // takes two numbers and returns their difference

() -> 99                                // takes no values and returns 99

(String a) -> System.out.println(a)     // takes a string, prints its value to the console, and returns nothing 

a -> 2 * a                               // takes a number and returns the result of doubling it

c -> { //some complex statements }  	// takes a collection and do some procesing

1.3 Lambda 表达式的特性

让我们确定 lambda 表达式的一些特性:

  1. Lambda 表达式可以具有零个,一个或多个参数。
  2. 参数的类型可以显式声明,也可以从上下文中推断出来。
  3. 多个参数用强制括号括起来,并用逗号分隔。 空括号用于表示空参数集。
  4. 当有单个参数时,如果推断出其类型,则不强制使用括号。 例如a -> a * a
  5. Lambda 表达式的主体可以包含零个,一个或多个语句。
  6. 如果 lambda 表达式的主体具有单个语句,则不必使用大括号,并且匿名函数的返回类型与主体表达式的返回类型相同。 如果正文中的语句多于一个,则这些语句必须用大括号括起来。

因此,我们简要了解了什么是 lambda 表达式。 如果您感到迷茫且无法联系,请耐心等待,如何在 Java 编程语言中使用它。 我们将在接下来的 30 分钟内解决所有问题。 让我们开始吧。

在深入研究 lambda 表达式和 Java 编程之间的关系之前,您还必须了解函数式接口。 这太重要了。

2. Java 8 函数式接口

单一抽象方法接口(SAM 接口)不是一个新概念。 这意味着仅使用一种方法进行接口。 在 Java 中,我们已经有许多此类 SAM 接口的示例。 从 Java 8 开始,它们也将也称为函数式接口。 Java 8 通过使用新的注释(即@FunctionalInterface)标记这些接口来实现单一职责规则。

例如,Runnable接口的新定义是这样的:

@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

如果尝试在任何函数式接口中添加新方法,则编译器将不允许您执行此操作,并且会引发编译时错误。

到目前为止,一切都很好。 但是,它们与 Lambda 表达式有何关系? 让我们找出答案。

我们知道 Lambda 表达式是不带名称的匿名函数,它们(大多数)作为参数传递给其他函数。 好吧,在 Java 方法中,参数总是具有类型,并且在确定方法重载甚至是简单方法调用的情况下,都会寻找此类型信息以确定需要调用哪个方法。 因此,基本上每个 lambda 表达式也必须都可以转换为某种类型才能被接受为方法参数。 好吧,用于转换 lambda 表达式的类型始终是函数式接口类型

让我们通过一个例子来理解它。 如果我们必须编写一个在控制台上打印"howtodoinjava"的线程,那么最简单的代码将是:

new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("howtodoinjava");
	}
}).start();

如果我们使用 lambda 表达式执行此任务,则代码将为:

new Thread(
			() ->   { 
						System.out.println("My Runnable"); 
					}
		 ).start();

我们还看到Runnable是具有单个方法run()的函数式接口。 因此,当您将 lambda 表达式传递给Thread类的构造器时,编译器将尝试将该表达式转换为等效的Runnable代码,如第一个代码示例所示。 如果编译器成功,则一切运行良好,如果编译器无法将表达式转换为等效的实现代码,它将抱怨。 在此,在上面的示例中,lambda 表达式被转换为Runnable类型。

简单来说,lambda 表达式是函数式接口的实例。 但是,lambda 表达式本身并不包含有关它正在实现哪个函数接口的信息。 该信息是从使用它的上下文中推导出来的。

3. Java 8 lambda 表达式示例

我列出了一些代码示例,您可以阅读并分析如何在日常编程中使用 lambda 表达式。

1)遍历一个列表并执行一些操作

List<String> pointList = new ArrayList();
pointList.add("1");
pointList.add("2");

pointList.forEach(p ->  {
							System.out.println(p);
							//Do more work
						}
				 );

2)创建一个新的可运行对象,并将其传递给线程

new Thread(
    () -> System.out.println("My Runnable"); 
).start();

3)按员工名称对其排序

public class LambdaIntroduction {

  public static void main (String[] ar){
          Employee[] employees  = {
              new Employee("David"),
              new Employee("Naveen"),
              new Employee("Alex"),
              new Employee("Richard")};

          System.out.println("Before Sorting Names: "+Arrays.toString(employees));
          Arrays.sort(employees, Employee::nameCompare);
          System.out.println("After Sorting Names "+Arrays.toString(employees));
      }
}

class Employee {
  String name;

  Employee(String name) {
    this.name = name;
  }

  public static int nameCompare(Employee a1, Employee a2) {
    return a1.name.compareTo(a2.name);
  }

  public String toString() {
    return name;
  }
}

Output:

Before Sorting Names: [David, Naveen, Alex, Richard]
After Sorting Names [Alex, David, Naveen, Richard]

4)将事件监听器添加到 GUI 组件

JButton button =  new JButton("Submit");
button.addActionListener((e) -> {
    System.out.println("Click event triggered !!");
});           

上面是 Java 8 中 lambda 表达式的非常基本的示例。我将不时提出一些更有用的示例和代码示例。

学习愉快!

Java 变量

原文: https://howtodoinjava.com/java/basics/java-variables/

了解 Java 中的 Java 变量类型的变量,如何声明变量的示例以及有关变量命名约定的最佳做法的示例。

Java 编程语言使用“ 字段”和“变量”作为其术语的一部分。 字段引用在方法外部声明的变量,而变量引用在方法内部的声明,包括方法参数。

1. 什么是变量

顾名思义,变量的值在运行时可以变化。 在 Java 中,变量是对存储变量值的存储区的命名引用。

Java Variable Example

变量如何工作

1.1 变量声明语法

变量声明具有以下语法:

[data_type] [variable_name] = [variable_value];
  • data_type – 指存储在存储区中的信息类型。
  • variable_name – 引用变量的名称。
  • variable_value – 引用要存储在存储区中的值。

例如,以下语句是 Java 中有效的变量声明。

int i = 10;         //Variable of int type

String str = "howtodoinjava.com";   //Variable of string type

Object obj = new Object();      //Variable of object type

int[] scores = [1,2,3,4,5,6,7,8,9];         //Variable of int type

1.2 Java 变量示例

int i = 10;
int j = 10;

int sum = i + j;

System.out.println( sum );  

程序输出。

20

2. 扩大和缩小

2.1 加宽

当较小的原始类型值自动容纳在较大/较宽的原始数据类型中时,这称为变量的扩展。 在给定的示例中,将int类型变量分配给long类型变量,而没有任何数据丢失或错误。

int i = 10;
long j = i;

System.out.println( i );  
System.out.println( j );  

程序输出:

10
10

2.2 缩小

如果在较小的原始数据类型中分配了较大的原始类型值,则称为变量变窄。 由于可用于存储数据的位数较少,可能会导致某些数据丢失。 它需要显式类型广播到所需的数据类型。

在给定的示例中,将int类型变量分配给具有数据丢失的byte类型变量。

int i=198;  
byte j=(byte)i;  

System.out.println( i );  
System.out.println( j );  

程序输出:

198
-58

3. 变量类型

在 Java 中,有四种类型的变量。 这些变量可以是基本类型类类型数组类型之一。

根据变量的范围划分所有变量,可以在其中访问它们

  1. 实例变量

    没有static关键字声明的变量(在类中)。 非静态字段也称为实例变量,因为它们的值对于类的每个实例都是唯一的。它们也称为状态变量

    public class VariableExample
    {
        int counter = 20;         //1 - Instance variable
    }
    
    
  2. 静态变量

    也称为类变量。 它是使用static修饰符声明的任何字段。 这意味着无论实例已被实例化多少次,该变量的确切副本只有一个。

    public class VariableExample
    {
        static float PI = 3.14f;    //2 - Class variable
    }
    
    

    在 Java 中,可以将声明为public static的变量视为全局变量。

  3. 局部变量

    这些在方法内部使用,因为在方法执行期间存在临时变量。 声明局部变量的语法类似于声明字段。 局部变量只对声明它们的方法可见; 在班上的其他同学中无法访问它们。

    public class VariableExample
    {
        public static void main( String[] args ) {
    
            int age = 30;     //3 - Local variable (inside method body)
        }
    }
    
    
  4. 方法参数

    参数是在调用方法时传递给方法的变量。 尽管在调用方法时会为其分配值,但参数也只能在声明它们的方法内部访问。

    public class VariableExample
    {
        public static void main( String[] args ) {
    
            print( 40 );
        }
    
        public static void print ( int param ) {      //4 - Method Argument
    
            System.out.println ( param );
        }
    }
    
    

4. 实例变量与类变量

  • 实例变量(非静态字段)对于类的每个实例都是唯一的。

  • 类变量(静态字段)是使用static修饰符声明的字段; 无论实例已被实例化多少次,类变量都只有一个副本。

  • 要访问实例变量,必须创建一个新的类实例。 类变量可通过类引用进行访问,并且不需要创建对象实例。

    举个例子。 我们有一个Data类,它具有一个实例变量和一个类变量。

    public class Data 
    {
        int counter = 20;
    
        static float PI = 3.14f;
    }
    
    

    我们可以以给定的方式访问两个变量。

    public class Main 
    {
        public static void main(String[] args) 
        {
            Data dataInstance = new Data();
    
            //Need new instance
    
            System.out.println( dataInstance.counter );    //20
    
            //Can access using class reference
    
            System.out.println( Data.PI );                 //3.14 
        }
    }
    
    

5. Java 变量命名约定

有一些规则和约定与有关如何定义变量名

  1. Java 变量名称是区分大小写。 变量名称employeeEmployeeEMPLOYEE不同。
  2. Java 变量名称必须以字母$_ 字符开头。
  3. Java 变量名称中的第一个字符之后,名称也可以包含数字$_ 字符。
  4. 变量名在 Java 中不能为保留关键字。 例如,单词break
    continue是 Java 中的保留字。 因此,您不能为它们命名变量。
  5. 变量名称应以小写编写。 例如,variableapple
  6. 如果变量名由多个单词组成,请遵循驼峰表示法。 例如,deptNamefirstName
  7. 静态最终字段(常量)应在所有大写字母中命名,通常使用 _ 分隔名称中的单词。 例如LOGGERINTEREST_RATE

学习愉快!

Java 8 – 函数式接口

原文: https://howtodoinjava.com/java8/functional-interface-tutorial/

了解 Java 8 函数式接口以及围绕一个接口允许的一种抽象方法的规则。 了解如何通过函数式接口中的默认方法添加更多方法。

Table of Contents 

1\. What is functional interface
2\. Do's and Don't's in functional interfaces

1. 什么是函数式接口

函数式接口是 java 8 中的新增特性,其中恰好允许其中的一种抽象方法。 这些接口也称为单一抽象方法接口(SAM 接口)

在 Java 8 中,函数式接口也可以使用 lambda 表达式,方法引用和构造器引用表示。

Java 8 也引入了一个注解,即@FunctionalInterface,当您注解的接口违反了一种抽象方法的约定时,该注解也可用于编译器级错误。

让我们构建第一个函数式接口:

@FunctionalInterface
public interface MyFirstFunctionalInterface 
{
	public void firstWork();
}

让我们尝试添加另一个抽象方法:

@FunctionalInterface
public interface MyFirstFunctionalInterface 
{
	public void firstWork();
	public void doSomeMoreWork();	//error
}

以上将导致编译器错误,如下所示:

Unexpected @FunctionalInterface annotation
@FunctionalInterface ^ MyFirstFunctionalInterface is not a functional interface
multiple non-overriding abstract methods found in interface MyFirstFunctionalInterface

Functional-Interface-Error

阅读更多:通用函数式接口

2. 函数式接口中的“是”与“否”

以下是函数式接口中允许和不允许的事物的列表。

  • 如上所述, 在任何函数式接口中仅允许使用一种抽象方法 。 在函数式接口中不允许使用第二种抽象方法。 如果删除@FunctionInterface注解,则可以添加另一个抽象方法,但是它将使该接口成为非函数式接口。

  • 即使@FunctionalInterface注释将被省略 ,函数式接口也有效。 它仅用于通知编译器在接口内部强制使用单个抽象方法

  • 从概念上讲,函数式接口仅具有一种抽象方法。 由于默认方法具有实现,因此它们不是抽象的。 由于默认方法不是抽象的,因此您可以自由地向函数式接口添加任意数量的默认方法。

    以下是有效的函数式接口:

    @FunctionalInterface
    public interface MyFirstFunctionalInterface 
    {
        public void firstWork();
    
        default void doSomeMoreWork1(){
        //Method body
        }
    
        default void doSomeMoreWork2(){
        //Method body
        }
    }
    
    
  • 如果接口声明了覆盖java.lang.Object的公共方法之一的抽象方法,则该方法也不计入接口的抽象方法数量,因为该接口的任何实现都有来自java.lang.Object或其他地方的实现。 例如Comparator是函数式接口,即使它声明了两个抽象方法。 为什么? 因为这些抽象方法之一equals()具有与Object类中的public方法相同的签名。

    例如下方的接口是有效的函数式接口。

    @FunctionalInterface
    public interface MyFirstFunctionalInterface 
    {
    	public void firstWork();
    
    	@Override
    	public String toString();                //Overridden from Object class
    
    	@Override
    	public boolean equals(Object obj);        //Overridden from Object class
    }
    
    

这就是 Java 8 中函数式接口的全部内容

学习愉快!

Java 8 方法引用示例

原文: https://howtodoinjava.com/java8/lambda-method-references-example/

Java 8 中,我们可以使用class::methodName类型的语法从类或对象中引用方法。 让我们学习一下 Java 8 中可用的方法引用的不同类型

Table of Contents

1\. Types of Method References
2\. Reference to static method - Class::staticMethodName
3\. Reference to instance method from instance - ClassInstance::instanceMethodName
4\. Reference to instance method from class type - Class::instanceMethodName
5\. Reference to constructor - Class::new

1. 方法引用的类型

Java 8 允许四种类型的方法引用。

方法引用 描述 方法引用示例
静态方法的引用 用于从类中引用静态方法 Math::max等同于(x, y) -> Math.max(x,y)
来自实例的实例方法的引用 使用所提供对象的引用来引用实例方法 System.out::println等同于x -> System.out.println(x)
来自类类型的实例方法的引用 在上下文提供的对象的引用上调用实例方法 String::length等同于str -> str.length()
构造器的引用 引用构造器 ArrayList::new等同于() -> new ArrayList()

2. 静态方法的引用 – Class::staticMethodName

使用Math.max()是静态方法的示例。

List<Integer> integers = Arrays.asList(1,12,433,5);

Optional<Integer> max = integers.stream().reduce( Math::max ); 

max.ifPresent(value -> System.out.println(value)); 

输出:

433

3. 来自实例的实例方法的引用 – ClassInstance::instanceMethodName

在上面的示例中,我们使用System.out.println(value)打印找到的最大值。 我们可以使用System.out::println打印该值。

List<Integer> integers = Arrays.asList(1,12,433,5);

Optional<Integer> max = integers.stream().reduce( Math::max ); 

max.ifPresent( System.out::println ); 

Output:

433

4. 来自类类型的实例方法的引用 – Class::instanceMethodName

在此示例中,s1.compareTo(s2)称为String::compareTo

List<String> strings = Arrays
		.asList("how", "to", "do", "in", "java", "dot", "com");

List<String> sorted = strings
		.stream()
		.sorted((s1, s2) -> s1.compareTo(s2))
		.collect(Collectors.toList());

System.out.println(sorted);

List<String> sortedAlt = strings
		.stream()
		.sorted(String::compareTo)
		.collect(Collectors.toList());

System.out.println(sortedAlt);

输出:

[com, do, dot, how, in, java, to]
[com, do, dot, how, in, java, to]

5. 构造器的引用 – Class::new

可以更新第一种方法,以创建一个从 1 到 100 的整数列表。使用 lambda 表达式非常简单。 要创建ArrayList的新实例,我们已经使用ArrayList::new

List<Integer> integers = IntStream
				.range(1, 100)
				.boxed()
				.collect(Collectors.toCollection( ArrayList::new ));

Optional<Integer> max = integers.stream().reduce(Math::max); 

max.ifPresent(System.out::println); 

输出:

99

这是 Java 8 lambda 增强特性中的方法引用的 4 种类型

学习愉快!

Java 默认方法教程

原文: https://howtodoinjava.com/java8/default-methods-in-java-8/

在上一篇文章中,我们了解了 Lambda 表达式和函数式接口 。 现在,让我们继续进行讨论,并讨论另一个相关特性,即默认方法。 好吧,这对于 Java 开发人员来说确实是革命性的。 到 Java 7 为止,我们已经学到了很多关于接口的知识,而在编写代码或设计应用程序时,所有这些事情都已经牢记在心。 在引入默认方法之后,其中一些概念将从 Java 8 发生巨大变化。

I will discuss following points in this post:

What are default methods in java 8?
Why default methods were needed in java 8?
How conflicts are resolved while calling default methods?

Java 8 中的默认方法是什么?

默认方法使您可以向库的接口添加新功能,并确保与为这些接口的较早版本编写的代码二进制兼容。

顾名思义,java 8 中的默认方法就是默认的。 如果不覆盖它们,则它们是调用者类将调用的方法。 它们在接口中定义。

让我们看一个例子:

public interface Moveable {
    default void move(){
        System.out.println("I am moving");
    }
}

可移动接口定义方法move(); 并提供了默认实现。 如果有任何类实现此接口,则无需实现其自己的move()方法版本。 它可以直接调用instance.move();

public class Animal implements Moveable{
    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

Output: I am moving

而且,如果类愿意定制行为,那么它可以提供自己的自定义实现并覆盖该方法。 现在将调用它自己的自定义方法。

public class Animal implements Moveable{

    public void move(){
        System.out.println("I am running");
    }

    public static void main(String[] args){
        Animal tiger = new Animal();
        tiger.move();
    }
}

Output: I am running

这还没有全部完成。 最好的部分是以下好处:

  1. 静态默认方法:您可以在接口中定义静态默认方法,这些方法将对实现该接口的所有类实例可用。 这使您可以更轻松地在库中组织帮助程序方法。 您可以将特定于接口的静态方法保留在同一接口中,而不是在单独的类中。 这使您可以在类之外定义方法,但仍可与所有子类共享。
  2. 它们为您提供了一种非常理想的特性,可以在不触及其代码的情况下为多个类添加功能。 只需在它们都实现的接口中添加默认方法即可。

为什么 Java 8 需要默认方法?

这是您下一个面试问题的不错的选择。 最简单的答案是在 Java 中启用 lambda 表达式的特性。 Lambda 表达本质上是函数式接口的类型。 为了无缝支持 lambda 表达式,必须修改所有核心类。 但是,这些核心类(例如java.util.List)不仅在 JDK 类中实现,而且在数千个客户端代码中也实现。 核心类中任何不兼容的更改都将肯定会产生反作用,并且根本不会被接受。

默认方法打破了这种僵局,并允许在核心类中添加对函数式接口的支持。 让我们来看一个例子。 以下是已添加到java.lang.Iterable的方法。

default void forEach(Consumer<? super T> action) {
	Objects.requireNonNull(action);
	for (T t : this) {
		action.accept(t);
	}
}

在 Java 8 之前,如果必须迭代 Java 集合,则将获得一个迭代器实例,并调用它的下一个方法,直到hasNext()返回false。 这是常见的代码,我们在日常编程中已经使用了数千次。 语法也总是相同的。 因此,我们可以使其精简吗,使其仅占用一行代码,仍然像以前一样为我们完成工作。 上面的函数可以做到这一点。

现在要迭代并对列表中的每个项目执行一些简单的操作,您需要做的是:

import java.util.ArrayList;
import java.util.List;

public class Animal implements Moveable{
    public static void main(String[] args){
        List<Animal> list = new ArrayList();
        list.add(new Animal());
        list.add(new Animal());
        list.add(new Animal());

        //Iterator code reduced to one line
        list.forEach((Moveable p) -> p.move());
    }
}

因此,这里,在不破坏列表的任何自定义实现的情况下,已将其他方法添加到List中。 长期以来,它一直是 Java 中非常需要的特性。 现在就在我们身边。

调用默认方法时如何解决冲突?

到目前为止,一切都很好。 我们的所有基础知识都很好。 现在转到复杂的事情。 在 Java 中,一个类可以实现 N 个接口。 另外,一个接口也可以扩展另一个接口。 如果在两个这样的接口中声明了默认方法,则该接口由单个类实现。 那么很明显,类会混淆调用哪个方法。

解决此冲突的规则如下:

1)最优选的是类中的覆盖方法。 如果在匹配任何内容之前找到它们,它们将被匹配并被调用。
2)选择在“最特定的默认提供接口”中具有相同签名的方法。 这意味着如果Animal类实现了两个接口,即MoveableWalkable,从而Walkable扩展了Moveable。 那么Walkable是最具体的接口,如果方法签名匹配,则将从此处选择默认方法。
3)如果MoveableWalkable是独立的接口,则会发生严重的冲突情况,并且编译器将抱怨并无法确定。 您必须通过提供额外的信息来帮助编译器,该信息应从哪个接口调用默认方法。 例如:

	Walkable.super.move();
	//or 
	Moveable.super.move();

这就是这里的全部内容。 下次,当我想到一些有趣的事情时,我将继续讨论。 不要忘记发表您的评论/想法或问题。

祝您学习愉快!

Java 8 Optional:完整参考

原文: https://howtodoinjava.com/java8/java-8-Optional-complete-reference/

我们所有人都必须在我们的应用程序中遇到NullPointerException。 当您尝试利用尚未初始化的对象引用,使用null初始化或根本不指向任何实例的对象引用时,会发生此异常。NULL仅表示“不存在值”。 罗马人很可能只是一个人,没有遇到这个空缺的问题,而是从 I,II,III 开始计数(不为零)。 也许,他们无法模拟市场上苹果的缺乏。😃

“我称之为我十亿美元的错误。” – C. A. R. Hoare Hoare,他发明了空引用

在本文中,我将针对该特定用例(即Optional)讨论 java 8 特性之一。 为了清楚和区分多个概念,本文分为多个部分。

Discussion Points

1) What is the Type of Null?
2) What is wrong with just returning null?
3) How Java 8 Optional provide the solution?
    a) Creating Optional objects
    b) Do something if a value is present
    c) Default/Absent values and actions
    d) Rejecting certain values Using the filter method
4) What is inside Optional make it work?
5) What is Optional trying to solve?
6) What is Optional not trying to solve?
7) How should Optional be used?
8) Conclusion

1)什么是空值?

在 Java 中,我们使用引用类型来访问对象,而当我们没有特定的对象作为引用点时,则将此类引用设置为null表示没有值。是吧?

null的使用是如此普遍,以至于我们很少对此多加思考。 例如,对象的字段成员会自动初始化为null,而程序员通常会在没有初始值的引用类型时将引用类型初始化为null,并且通常在每次我们不知道或不知道的时候都使用null没有参考价值。

仅供参考,在 Java 中null实际上是一种类型,是一种特殊的类型。 它没有名称,因此我们不能声明其类型的变量或将任何变量强制转换为它。 实际上,只有一个可以与之关联的值(即字面值为null)。 请记住,与 Java 中的任何其他类型不同,可以将空引用安全地分配给任何其他引用类型,而不会出现任何错误(见 JLS 3.10.74.1)。

2)仅返回null有什么问题?

通常,API 设计人员将描述性的 Java 文档放入 API 中,并在其中提到 API 可以返回空值,在这种情况下,返回值可以是null。 现在,问题在于 API 的调用者可能由于任何原因而错过了读取 Javadoc 的操作,并且忘记了处理空值的情况。 肯定的将来这将是一个错误。

并且相信我,这经常发生,并且是空指针异常的主要原因之一,尽管不是唯一的原因。 因此,在这里上一课,当您第一次使用它时,请务必(至少)阅读它的 Javadocs。

现在我们知道在大多数情况下null是一个问题,什么是最好的处理方式?

一个好的解决方案是始终始终使用某个值初始化您的对象引用,而永远不要使用null进行初始化。 这样,您将永远不会遇到NullPointerException。 很公平。 但实际上,我们始终没有默认值作为参考。 那么,这些案件应该如何处理?

上述问题在许多方面都是正确的。 好吧, java 8 Optional 是这里的答案。

3)Java 8 Optional如何提供解决方案?

可选的是用非空值替换可空T引用的方法。 Optional可以包含非空T引用(在这种情况下,我们称该引用为“存在”),也可以不包含任何内容(在这种情况下,我们称该引用为“不存在”)。

请记住,从未说过Optional“包含null

Optional<Integer> canBeEmpty1 = Optional.of(5);
canBeEmpty1.isPresent(); 					// returns true
canBeEmpty1.get(); 							// returns 5

Optional<Integer> canBeEmpty2 = Optional.empty();
canBeEmpty2.isPresent(); 					// returns false

您也可以Optional看做为包含或不包含值的单值容器。

重要的是要注意,Optional类的目的不是替换每个单个空引用。 相反,它的目的是帮助设计更易于理解的 API ,以便仅通过读取方法的签名即可知道是否可以期望一个可选值。 这迫使您从Optional中获取值并对其进行处理,同时处理Optional为空的情况。 好吧,这正是空引用/返回值的解决方案,最终导致NullPointerException

以下是一些示例,以了解有关应如何在应用程序代码中创建和使用Optional的更多信息。

a)创建Optional对象

创建Optional的主要方法有 3 种。

i)使用Optional.empty()创建空的Optional

Optional<Integer> possible = Optional.empty(); 

ii)使用Optional.of()创建具有默认非空值的Optional。 如果在of()中传递null,则立即引发NullPointerException

Optional<Integer> possible = Optional.of(5); 

iii)使用Optional.ofNullable()创建一个可能包含空值的Optional对象。 如果参数为null,则生成的Optional对象将为空(请记住,该值不存在;请勿将其读取为null)。

Optional<Integer> possible = Optional.ofNullable(null); 
//or
Optional<Integer> possible = Optional.ofNullable(5); 

b)如果存在Optional值,则执行某些操作

第一步是获取Optional对象。 现在,在检查其内部是否包含任何值之后,再使用它。

Optional<Integer> possible = Optional.of(5); 
possible.ifPresent(System.out::println);

您也可以将以下代码覆盖为上面的代码。 但是,不建议您使用Optional,因为与嵌套的空检查相比,它没有太大的改进。 它们看起来确实完全相似。

if(possible.isPresent()){
	System.out.println(possible.get());
}

如果Optional对象为空,则不会打印任何内容。

c)默认/不存在的值和动作

如果确定操作结果为空,则编程中的典型模式是返回默认值。 通常,您可以使用三元运算符。 但是使用Optional,您可以编写如下代码:

//Assume this value has returned from a method
Optional<Company> companyOptional = Optional.empty();

//Now check optional; if value is present then return it, 
//else create a new Company object and retur it
Company company = companyOptional.orElse(new Company());

//OR you can throw an exception as well
Company company = companyOptional.orElseThrow(IllegalStateException::new);

d)使用过滤方法拒绝某些值

通常,您需要在对象上调用方法并检查某些属性。 例如在下面的示例代码中,检查公司是否设有“财务”部门; 如果有,请打印出来。

Optional<Company> companyOptional = Optional.empty();
companyOptional.filter(department -> "Finance".equals(department.getName())
                    .ifPresent(() -> System.out.println("Finance is present"));

过滤方法谓词为参数。 如果Optional对象中存在一个值,并且该值与谓词匹配,则过滤方法将返回该值;否则,该方法将返回该值。 否则,它返回一个空的Optional对象。 如果在Stream接口中使用了过滤方法,则可能已经看到了类似的模式。

很好,这段代码看起来更接近问题语句,并且没有妨碍我们进行冗长的null检查!

哇!! 从编写痛苦的嵌套null检查到编写可编写,可读且可更好地免受null指针异常保护的声明性代码,我们已经走了很长一段路。

4)Optional内部有什么使它起作用?

如果打开Optional.java的源代码,则会发现Optional持有的值定义为:

/**
 * If non-null, the value; if null, indicates no value is present
 */
private final T value;

并且,如果您定义一个空的Optional,则它声明如下。 静态关键字可确保每个 VM 通常仅存在一个空实例。

/**
 * Common instance for {@code empty()}.
 */
private static final Optional<?> EMPTY = new Optional<>();

默认的无参构造器定义为私有 ,因此您无法创建Optional的实例,但上述三种方法除外。

当您创建一个Optional时,最终会发生以下调用,并将传递的值分配给“value”属性。

	this.value = Objects.requireNonNull(value);

当您尝试从Optional获取值时,如果没有抛出NoSuchElementException,则会获取该值:

public T get() {
	if (value == null) {
		throw new NoSuchElementException("No value present");
	}
	return value;
}

同样,在Optional类中定义的其他函数仅在“value”属性附近起作用。 浏览Optional.java源代码以获取更多信息。

5)Optional试图解决什么?

Optional尝试通过考虑到有时缺少返回值来增加构建更具表现力的 API 的可能性,从而减少 Java 系统中空指针异常的数量。 如果从一开始就存在Optional,那么今天大多数库和应用程序可能会更好地处理缺少的返回值,从而减少了空指针异常的数量以及总体上的错误总数。

通过使用Optional,用户不得不考虑特殊情况。 除了因提供空名而提高了可读性之外,Optional最大优点是其防白痴。 如果您要完全编译程序,它会迫使您积极考虑不存在的情况,因为您必须主动展开Optional并解决该失败情况。

6)Optional不尝试解决的问题是什么?

Optional并不是要避免所有类型的空指针的机制。 例如方法和构造器的强制输入参数仍然必须进行测试。

就像使用null时一样,Optional不能帮助传达缺失值的含义。 因此,该方法的调用者仍然必须检查 API 的 javadoc,以了解缺少Optional的含义,以便对其进行正确处理。

请注意,Optional不能在以下情况下使用,因为它可能不会给我们带来任何好处:

  • 在域模型层(不可序列化)
  • 在 DTO 中(不可序列化)
  • 在方法的输入参数中
  • 在构造器参数中

7)应该如何使用Optional

几乎在所有时间都应该使用Optional作为可能不返回值的函数的返回类型。

这是来自 OpenJDK 邮件列表的引言:

JSR-335 EG 相当强烈地认为Optional不应仅用于支持 option-return 惯例。

有人建议甚至将其重命名为“OptionalReturn”。

从本质上讲,这意味着Optional仅应在确实达到目的的情况下用作某些服务,存储库或工具方法的返回类型。

8)总结

在本文中,我们了解了如何采用新的 Java SE 8 java.util.OptionalOptional的目的不是替换代码库中的每个单个null引用,而是帮助您设计更好的 API,其中,仅通过读取方法的签名,用户就可以知道是否期待并妥善处理Optional值。

仅此特性就可以了。 在评论部分让我知道您的想法。

祝您学习愉快!

Java 谓词示例 – 谓词过滤器

原文: https://howtodoinjava.com/java8/how-to-use-predicate-in-java-8/

在数学中,谓词通常被理解为布尔值函数'P: X? {true, false}',称为X谓词。 可以将其视为返回truefalse值的运算符或函数。

Java 8 谓词用法

在 Java 8 中,谓词函数式接口,因此可以用作 lambda 表达式 或方法参考的分配目标。 那么,您认为如何,我们可以在日常编程中使用这些true/false返回函数? 我会说,您可以在需要求值类似对象的组/集合上的条件的任何地方使用谓词,以便求值可以得出truefalse

例如,您可以在这些实时用例中使用谓词:

  1. 查找在特定日期之后出生的所有孩子
  2. 特定时间定下的披萨
  3. 超过一定年龄的员工等

Java 谓词类

因此, java 谓词似乎很有趣。 让我们更深入。

如我所说,Predicate函数式接口。 这意味着我们可以在需要谓词的任何地方传递 lambda 表达式。 例如,一种这样的方法是来自接口的filter()方法。

/**
 * Returns a stream consisting of the elements of this stream that match
 * the given predicate.
 *
 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
 * operation</a>.
 *
 * @param predicate a non-interfering stateless predicate to apply to each element to determine if it
 * should be included in the new returned stream.
 * @return the new stream
 */
Stream<T> filter(Predicate<? super T> predicate);

我们可以将流假定为一种机制,以创建支持顺序和并行聚合操作的元素序列。 这意味着我们可以随时通过一次调用收集并执行流中存在的所有元素的某些操作。

因此,从本质上讲,我们可以使用流和谓词来:

  • 首先从组中过滤某些元素,然后
  • 然后对过滤后的元素执行一些操作。

在集合上使用谓词

为了演示,我们有一个Employee类,如下所示:

package predicateExample;

public class Employee {

   public Employee(Integer id, Integer age, String gender, String fName, String lName){
       this.id = id;
       this.age = age;
       this.gender = gender;
       this.firstName = fName;
       this.lastName = lName;
   } 

   private Integer id;
   private Integer age;
   private String gender;
   private String firstName;
   private String lastName;

   //Please generate Getter and Setters

   //To change body of generated methods, choose Tools | Templates.
    @Override
    public String toString() {
        return this.id.toString()+" - "+this.age.toString(); 
    }
}

1. 所有年龄在 21 岁以上的男性员工

public static Predicate<Employee> isAdultMale() 
{
    return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}

2. 所有年龄在 18 岁以上的女性员工

public static Predicate<Employee> isAdultFemale() 
{
    return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}

3. 所有年龄超过给定年龄的员工

public static Predicate<Employee> isAgeMoreThan(Integer age) 
{
    return p -> p.getAge() > age;
}

您可以在需要时构建更多它们。 到目前为止,一切都很好。 到目前为止,我已经在EmployeePredicates.java中包括了上述 3 种方法:

package predicateExample;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class EmployeePredicates 
{
    public static Predicate<Employee> isAdultMale() {
        return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
    }

    public static Predicate<Employee> isAdultFemale() {
        return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
    }

    public static Predicate<Employee> isAgeMoreThan(Integer age) {
        return p -> p.getAge() > age;
    }

    public static List<Employee> filterEmployees (List<Employee> employees, 
                                                Predicate<Employee> predicate) 
    {
        return employees.stream()
        			.filter( predicate )
        			.collect(Collectors.<Employee>toList());
    }
}   

您会看到我创建了另一个工具方法filterEmployees(),以显示 java 谓词过滤器。 基本上是使代码整洁并减少重复。 您也可以编写多个谓词来构成谓词链,就像在构建器模式中所做的那样。

因此,在此函数中,我们传递employees的列表并传递谓词,然后此函数将返回满足参数谓词中提到的条件的employees的新集合。

package predicateExample;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static predicateExample.EmployeePredicates.*;

public class TestEmployeePredicates 
{
    public static void main(String[] args)
    {
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");

        List<Employee> employees = new ArrayList<Employee>();
        employees.addAll(Arrays.asList(new Employee[]{e1,e2,e3,e4,e5,e6,e7,e8,e9,e10}));

        System.out.println(	filterEmployees(employees, isAdultMale()) );

        System.out.println( filterEmployees(employees, isAdultFemale()) );

        System.out.println( filterEmployees(employees, isAgeMoreThan(35)) );

		//Employees other than above collection of "isAgeMoreThan(35)" 
		//can be get using negate()
        System.out.println(filterEmployees(employees, isAgeMoreThan(35).negate()));
    }
}

Output:

[1 - 23, 3 - 43, 4 - 26, 8 - 79, 10 - 45]
[5 - 19, 7 - 68]
[3 - 43, 7 - 68, 8 - 79, 10 - 45]
[1 - 23, 2 - 13, 4 - 26, 5 - 19, 6 - 15, 9 - 15]

谓词在 Java 8 中确实是非常好的添加,只要有机会,我都会使用它。

关于 Java 8 中谓词的最终思考

  1. 他们将您的条件(有时是业务逻辑)转移到中心位置。 这有助于分别对它们进行单元测试。
  2. 无需将任何更改复制到多个位置。 Java 谓词改善了代码维护。
  3. 代码例如“filterEmployees(employees, isAdultFemale())”比编写if-else块更具可读性。

好的,这些是我在 Java 8 谓词中的想法。 您如何看待此特性? 在评论部分与我们所有人分享。

学习愉快!

阅读更多 :

Java 谓词取反示例

Java 链接谓词 – 逻辑 AND 和逻辑 OR 运算

Java 8 – 日期和时间示例

原文: https://howtodoinjava.com/java8/date-and-time-api-changes-in-java-8-lambda/

开发人员社区的很大一部分一直在抱怨日期和日历类。 原因很多,例如难以理解,难以使用和不灵活。 Date类甚至已经过时,并且 Java 文档建议使用Calendar类而不是Date类。 最重要的是,日期比较有很多问题,我过去也遇到过这样的问题。

Java 8 date api changes

展望未来,JAVA 8(Lambda)有望发布新的日期和时间 API / 类(JSR-310),也称为 ThreeTen ,它将仅更改您到目前为止的操作方式。 这的一个关键部分是提供一个新的 API,该 API 显着易于使用且不易出错。

它将提供一些非常需要的特性,例如:

  • 所有关键的公共类都是不可变的并且是线程安全的
  • 其他计算领域可以采用的定义的术语和行为

我在 2013 年 5 月 15 日撰写了这篇文章。 现在是 2014 年 3 月 18 日,今天终于可以发布 Java 8 了,可以早期使用。 我已经重新验证并验证了示例中的所有输出。 与去年 5 月一样,它们像魔术一样工作。 唯一遇到的更改是在TemporalAdjuster.java中。 以前是一个类,现在是@FunctionalInterface。 因此,我更正了相关示例,并使用了TemporalAdjusters.java类。

Table of Contents

New classes to represent local date and timezone
New classes to represent timestamp and duration
Added utility classes over existing enums
Date adjusters introduced
Building dates will be easier
New class to simulate system/machine clock
Timezone handling related changes
Date formatting changes
References

代表本地日期和时区的新类

打算替换Date类的新类是LocalDateLocalTimeLocalDateTime

LocalDate

LocalDate类表示日期。 没有时间或时区的表示。

LocalDate localDate = LocalDate.now();
System.out.println(localDate.toString());                //2013-05-15
System.out.println(localDate.getDayOfWeek().toString()); //WEDNESDAY
System.out.println(localDate.getDayOfMonth());           //15
System.out.println(localDate.getDayOfYear());            //135
System.out.println(localDate.isLeapYear());              //false
System.out.println(localDate.plusDays(12).toString());   //2013-05-27

LocalTime

LocalTime类表示时间。 没有日期或时区的表示。

//LocalTime localTime = LocalTime.now();     //toString() in format 09:57:59.744
LocalTime localTime = LocalTime.of(12, 20);
System.out.println(localTime.toString());    //12:20
System.out.println(localTime.getHour());     //12
System.out.println(localTime.getMinute());   //20
System.out.println(localTime.getSecond());   //0
System.out.println(localTime.MIDNIGHT);      //00:00
System.out.println(localTime.NOON);          //12:00

LocalDateTime

LocalDateTime类表示日期时间。 没有时区的表示。

LocalDateTime localDateTime = LocalDateTime.now(); 
System.out.println(localDateTime.toString());      //2013-05-15T10:01:14.911
System.out.println(localDateTime.getDayOfMonth()); //15
System.out.println(localDateTime.getHour());       //10
System.out.println(localDateTime.getNano());       //911000000

如果您想将日期特性与区域信息一起使用,则 Lambda 会为您提供额外的 3 类,类似于上面的一种,即OffsetDateOffsetTimeOffsetDateTime。 时区偏移可以以“+05:30”或“Europe/Paris”格式表示。 这是通过使用另一个类即ZoneId完成的。

OffsetDateTime offsetDateTime = OffsetDateTime.now();
System.out.println(offsetDateTime.toString());            	//2013-05-15T10:10:37.257+05:30

offsetDateTime = OffsetDateTime.now(ZoneId.of(&quot;+05:30&quot;));
System.out.println(offsetDateTime.toString());            	//2013-05-15T10:10:37.258+05:30

offsetDateTime = OffsetDateTime.now(ZoneId.of(&quot;-06:30&quot;));
System.out.println(offsetDateTime.toString());            	//2013-05-14T22:10:37.258-06:30

ZonedDateTime zonedDateTime = 
				ZonedDateTime.now(ZoneId.of(&quot;Europe/Paris&quot;));
System.out.println(zonedDateTime.toString());     			//2013-05-15T06:45:45.290+02:00[Europe/Paris]

代表时间戳和持续时间的新类

Instant

为了随时表示特定的时间戳,需要使用的类是InstantInstant类表示精确到纳秒的时间点。 对Instant的操作包括与另一个Instant的比较以及持续时间的增加或减少。

Instant instant = Instant.now();
System.out.println(instant.toString());                                 //2013-05-15T05:20:08.145Z
System.out.println(instant.plus(Duration.ofMillis(5000)).toString());   //2013-05-15T05:20:13.145Z
System.out.println(instant.minus(Duration.ofMillis(5000)).toString());  //2013-05-15T05:20:03.145Z
System.out.println(instant.minusSeconds(10).toString());				//2013-05-15T05:19:58.145Z

Duration

Duration类是用 Java 语言首次带来的全新概念。 它表示两个时间戳之间的差异。

Duration duration = Duration.ofMillis(5000);
System.out.println(duration.toString());     //PT5S

duration = Duration.ofSeconds(60);
System.out.println(duration.toString());     //PT1M

duration = Duration.ofMinutes(10);
System.out.println(duration.toString());     //PT10M

duration = Duration.ofHours(2);
System.out.println(duration.toString());     //PT2H

duration = Duration.between(Instant.now(), Instant.now().plus(Duration.ofMinutes(10)));
System.out.println(duration.toString());  //PT10M

Duration处理较小的时间单位,例如毫秒,秒,分钟和小时。 它们更适合与应用程序代码进行交互。

Period

要与人互动,您需要获得更大的持续时间,并以Period类给出。

Period period = Period.ofDays(6);
System.out.println(period.toString());    //P6D

period = Period.ofMonths(6);
System.out.println(period.toString());    //P6M

period = Period.between(LocalDate.now(), 
			LocalDate.now().plusDays(60));
System.out.println(period.toString());   //P1M29D

在现有枚举上添加的工具类

当前的 Java SE 平台使用int常量表示月份,星期几和上午下午等。现在,添加了许多额外的工具类,它们在这些枚举的基础上起作用。 我以这样的类为例:DayOfWeek。 该类是每周日期枚举的包装,并且可以与其他类一致使用。

DayOfWeek

//day-of-week to represent, from 1 (Monday) to 7 (Sunday)
System.out.println(DayOfWeek.of(2));        			//TUESDAY 

DayOfWeek day = DayOfWeek.FRIDAY;
System.out.println(day.getValue());         			//5

LocalDate localDate = LocalDate.now();
System.out.println(localDate.with(DayOfWeek.MONDAY));  //2013-05-13  i.e. when was monday in current week ?

其他此类类别是MonthMonthDayYearYearMonth等。

日期调整器

日期调节器是日期处理工具中另一个美观实用的特性。 它可以轻松解决以下问题:您如何查找一个月的最后一天? 还是下一个工作日? 还是星期二一个星期?

让我们看一下代码。

LocalDate date = LocalDate.of(2013, Month.MAY, 15);						//Today

LocalDate endOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
System.out.println(endOfMonth.toString()); 								//2013-05-31

LocalDate nextTue = date.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println(nextTue.toString());									//2013-05-21

创建日期对象

现在也可以使用构建器模式创建日期对象。 构建器模式允许使用单例来构建您想要的对象。 这是通过使用以at为前缀的方法来实现的。

//Builder pattern used to make date object
 OffsetDateTime date1 = Year.of(2013)
						.atMonth(Month.MAY).atDay(15)
						.atTime(0, 0)
						.atOffset(ZoneOffset.of(&quot;+03:00&quot;));
 System.out.println(date1);   									//2013-05-15T00:00+03:00

//factory method used to make date object
OffsetDateTime date2 = OffsetDateTime.
						of(2013, 5, 15, 0, 0, 0, 0, ZoneOffset.of(&quot;+03:00&quot;));
System.out.println(date2);										//2013-05-15T00:00+03:00

模拟系统/机器时钟的新类

在新版本中提出了新的类时钟。 该模拟系统时钟特性。 我最喜欢这个特性。 原因是在进行单元测试时。 您通常需要在将来的日期测试 API。 为此,我们已经将系统时钟转发到下一个日期,然后再次重新启动服务器并测试应用程序。

现在,无需这样做。 使用Clock类可以模拟这种情况。

Clock clock = Clock.systemDefaultZone();
System.out.println(clock);						//SystemClock[Asia/Calcutta]
System.out.println(clock.instant().toString());	//2013-05-15T06:36:33.837Z
System.out.println(clock.getZone());			//Asia/Calcutta

Clock anotherClock = Clock.system(ZoneId.of(&quot;Europe/Tiraspol&quot;));
System.out.println(anotherClock);						//SystemClock[Europe/Tiraspol]
System.out.println(anotherClock.instant().toString());	//2013-05-15T06:36:33.857Z
System.out.println(anotherClock.getZone());				//Europe/Tiraspol

Clock forwardedClock  = Clock.tick(anotherClock, Duration.ofSeconds(600));
System.out.println(forwardedClock.instant().toString());  //2013-05-15T06:30Z

时区变更

与时区相关的处理由 3 个主要类别完成。 这些是ZoneOffsetTimeZoneZoneRules

  • ZoneOffset类表示与 UTC 的固定偏移量,以秒为单位。 通常以±hh:mm格式的字符串表示。
  • TimeZone类表示在其中定义了指定时区规则的区域的标识符。
  • ZoneRules是定义区域偏移量何时更改的实际规则集。
//Zone rules
System.out.println(ZoneRules.of(ZoneOffset.of(&quot;+02:00&quot;)).isDaylightSavings(Instant.now()));
System.out.println(ZoneRules.of(ZoneOffset.of(&quot;+02:00&quot;)).isFixedOffset());

日期格式化

日期格式化通过两个类别(主要是DateTimeFormatterBuilderDateTimeFormatter)支持。 DateTimeFormatterBuilder使用构建器模式来构建自定义模式,因为DateTimeFormatter为此提供了必要的输入。

DateTimeFormatterBuilder formatterBuilder = new DateTimeFormatterBuilder();
formatterBuilder.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
				.appendLiteral(&quot;-&quot;)
				.appendZoneOrOffsetId();
DateTimeFormatter formatter = formatterBuilder.toFormatter();
System.out.println(formatter.format(ZonedDateTime.now()));

这些是我能够识别并进行的重大更改。

参考文献

祝您学习愉快!

Java 8 列出目录中的所有文件 – 六个示例

原文: https://howtodoinjava.com/java8/java-8-list-all-files-example/

学习将 Java 8 API 与Files.list()DirectoryStream一起使用,以递归方式列出目录中存在的所有文件,包括隐藏文件。

1. 使用Files.list()列出所有文件和子目录

Files.list()方法列出当前目录中的所有文件名和子目录

Files.list(Paths.get("."))
		.forEach(System.out::println);

 Output:

.\filename1.txt
.\directory1
.\filename2.txt
.\Employee.java

2. 使用过滤器表达式仅列出目录内的文件

您可以使用过滤器过滤出子目录,并在需要时仅打印文件名

Files.list(Paths.get("."))
		.filter(Files::isRegularFile)
		.forEach(System.out::println);

 Output:

.\filename1.txt
.\filename2.txt
.\Employee.java

要列出其他目录中的文件,我们可以将"."替换为所需目录的完整路径。

3. 使用Files.newDirectoryStream()列出文件和子目录

Java 提供了一种更灵活的使用Files.newDirectoryStream()遍历目录内容的方式。

请注意,如果我们使用的是大型目录,则使用DirectoryStream实际上可以使代码更快。

Files.newDirectoryStream(Paths.get("."))
		.forEach(System.out::println);

 Output:

.\filename1.txt
.\directory1
.\filename2.txt
.\Employee.java

4. 使用Files.newDirectoryStream()仅列出文件

要仅列出文件并从流中排除所有目录,请使用路径过滤器作为第二个参数。

Files.newDirectoryStream(Paths.get("."), path -> path.toFile().isFile())
		.forEach(System.out::println);

Output:

.\filename1.txt
.\filename2.txt
.\Employee.java

5. 使用Files.newDirectoryStream()列出一定范围的文件

您可以更改在第二个参数中传递的路径过滤器表达式,以仅获取具有特定扩展名的文件。

Files.newDirectoryStream(Paths.get("."),
		path -> path.toString().endsWith(".java"))
		.forEach(System.out::println);

Output:

.\Employee.java

6. 在目录中查找所有隐藏文件

要查找所有隐藏文件,可以在上述任何示例中使用过滤器表达式file -> file.isHidden()

或者,您可以使用此快捷方式。

final​ ​File​​[]​ files = ​new​ ​File​(​"."​).listFiles(file -> file.isHidden());
//or
final​ ​File​​[]​ files = ​new​ ​File​(​"."​).listFiles(​File​::isHidden);

在以上示例中,我们学习了使用 Java 8 API 列表或根据各种搜索条件递归地迭代目录中的文件。 随意修改代码并使用它。

学习愉快!

参考文献:

DirectoryStream

Files.list()方法

Java 8 – 逐行读取文件

原文: https://howtodoinjava.com/java8/read-file-line-by-line/

在本 Java 8 教程中,学习使用流 API 逐行读取文件。 另外,还要学习遍历行并根据某些条件过滤文件内容。

1. Java 8 读取文件 – 逐行

在此示例中,我将读取文件内容为stream,并一次读取每一行,并检查其中是否包含单词"password"

Path filePath = Paths.get("c:/temp", "data.txt");

//try-with-resources
try (Stream<String> lines = Files.lines( filePath )) 
{
	lines.forEach(System.out::println);
} 
catch (IOException e) 
{
	e.printStackTrace();
}

上面的程序输出将在控制台中逐行打印文件的内容。

Never
store
password
except
in mind.

2. Java 8 读取文件 – 过滤行流

在此示例中,我们将文件内容读取为行流。 然后,我们将过滤掉所有带有单词"password"的行。

Path filePath = Paths.get("c:/temp", "data.txt");

try (Stream<String> lines = Files.lines(filePath)) 
{

	 List<String> filteredLines = lines
	 				.filter(s -> s.contains("password"))
	 				.collect(Collectors.toList());

	 filteredLines.forEach(System.out::println);

} 
catch (IOException e) {

	e.printStackTrace();
}

程序输出。

password

我们将读取给定文件的内容,并检查是否有任何一行包含单词"password",然后将其打印出来。

3. Java 7 – 使用FileReader读取文件

到 Java 7 为止,我们可以通过FileReader以各种方式读取文件。

private static void readLinesUsingFileReader() throws IOException 
{
    File file = new File("c:/temp/data.txt");

    FileReader fr = new FileReader(file);
    BufferedReader br = new BufferedReader(fr);

    String line;
    while((line = br.readLine()) != null)
    {
        if(line.contains("password")){
            System.out.println(line);
        }
    }
    br.close();
    fr.close();
}

这就是 Java 示例逐行读取文件的全部。 请在评论部分提出您的问题。

学习愉快!

Java 8 写入文件示例

原文: https://howtodoinjava.com/java8/java-8-write-to-file-example/

Java 8 示例将内容导入文件。 您可以在链接的博客文章中找到使用 Java 8 API 读取文件的示例。

1. Java 8 使用BufferedWriter写入文件

BufferedWriter用于将文本写入字符或字节流。 在打印字符之前,它将字符存储在缓冲区中并成束打印。 如果不进行缓冲,则每次调用print()方法都会导致将字符转换为字节,然后将这些字节立即写入文件中,这可能会非常低效。

Java 程序使用 Java 8 API 将内容写入文件:

//Get the file reference
Path path = Paths.get("c:/output.txt");

//Use try-with-resource to get auto-closeable writer instance
try (BufferedWriter writer = Files.newBufferedWriter(path)) 
{
    writer.write("Hello World !!");
}

2. 使用Files.write()写入文件

使用Files.write()方法也是非常干净的代码。

String content = "Hello World !!";

Files.write(Paths.get("c:/output.txt"), content.getBytes());

以上两种方法都适用于几乎所有需要用 Java 8 编写文件的用例

学习愉快!

Java WatchService API 教程

原文: https://howtodoinjava.com/java8/java-8-watchservice-api-tutorial/

在此示例中,我们将学习使用 Java 8 WatchService API 观察目录及其中的所有子目录和文件。

如何注册 Java 8 WatchService

要注册WatchService,请获取目录路径并使用path.register()方法。

Path path = Paths.get(".");
WatchService watchService =  path.getFileSystem().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

观察变化事件

要获取目录及其中文件的更改,请使用watchKey.pollEvents()方法以流的形式返回所有更改事件的集合。

WatchKey watchKey = null;
while (true) {
	watchKey = watchService.poll(10, TimeUnit.MINUTES);
	if(watchKey != null) {
		watchKey.pollEvents().stream().forEach(event -> System.out.println(event.context()));
	}
	watchKey.reset();
}

该密钥一直有效,直到:

  • 通过调用其cancel方法显式地取消它,或者
  • 隐式取消,因为该对象不再可访问,或者
  • 通过关闭观察服务。

如果您要重复使用同一键在一个循环中多次获取更改事件,请不要忘记调用watchKey.reset()方法,该方法会将键再次设置为就绪状态。

请注意,诸如如何检测事件,其及时性以及是否保留其顺序之类的几件事高度依赖于底层操作系统。 某些更改可能导致一个操作系统中的单个条目,而类似的更改可能导致另一操作系统中的多个事件。

监视目录,子目录和文件中的更改示例

在此示例中,我们将看到一个观看目录的示例,该目录中包含所有子目录和文件。 我们将维护监视键和目录Map<WatchKey, Path> keys的映射,以正确识别已修改的目录。

下面的方法将向观察者注册一个目录,然后将目录和密钥存储在映射中。

private void registerDirectory(Path dir) throws IOException 
{
	WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
	keys.put(key, dir);
}

在遍历目录结构并为遇到的每个目录调用此方法时,将递归调用此方法。

private void walkAndRegisterDirectories(final Path start) throws IOException {
	// register directory and sub-directories
	Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
		@Override
		public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
			registerDirectory(dir);
			return FileVisitResult.CONTINUE;
		}
	});
}

请注意,无论何时创建新目录,我们都会在观察服务中注册该目录,并将新密钥添加到映射中。

WatchEvent.Kind kind = event.kind();
if (kind == ENTRY_CREATE) {
	try {
		if (Files.isDirectory(child)) {
			walkAndRegisterDirectories(child);
		}
	} catch (IOException x) {
		// do something useful
	}
}

将以上所有内容与处理事件的逻辑放在一起,完整的示例如下所示:

package com.howtodoinjava.examples;

import static java.nio.file.StandardWatchEventKinds.*;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;

public class Java8WatchServiceExample {

	private final WatchService watcher;
	private final Map<WatchKey, Path> keys;

	/**
	 * Creates a WatchService and registers the given directory
	 */
	Java8WatchServiceExample(Path dir) throws IOException {
		this.watcher = FileSystems.getDefault().newWatchService();
		this.keys = new HashMap<WatchKey, Path>();

		walkAndRegisterDirectories(dir);
	}

	/**
	 * Register the given directory with the WatchService; This function will be called by FileVisitor
	 */
	private void registerDirectory(Path dir) throws IOException 
	{
		WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
		keys.put(key, dir);
	}

	/**
	 * Register the given directory, and all its sub-directories, with the WatchService.
	 */
	private void walkAndRegisterDirectories(final Path start) throws IOException {
		// register directory and sub-directories
		Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
			@Override
			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
				registerDirectory(dir);
				return FileVisitResult.CONTINUE;
			}
		});
	}

	/**
	 * Process all events for keys queued to the watcher
	 */
	void processEvents() {
		for (;;) {

			// wait for key to be signalled
			WatchKey key;
			try {
				key = watcher.take();
			} catch (InterruptedException x) {
				return;
			}

			Path dir = keys.get(key);
			if (dir == null) {
				System.err.println("WatchKey not recognized!!");
				continue;
			}

			for (WatchEvent<?> event : key.pollEvents()) {
				@SuppressWarnings("rawtypes")
				WatchEvent.Kind kind = event.kind();

				// Context for directory entry event is the file name of entry
				@SuppressWarnings("unchecked")
				Path name = ((WatchEvent<Path>)event).context();
				Path child = dir.resolve(name);

				// print out event
				System.out.format("%s: %s\n", event.kind().name(), child);

				// if directory is created, and watching recursively, then register it and its sub-directories
				if (kind == ENTRY_CREATE) {
					try {
						if (Files.isDirectory(child)) {
							walkAndRegisterDirectories(child);
						}
					} catch (IOException x) {
						// do something useful
					}
				}
			}

			// reset key and remove from set if directory no longer accessible
			boolean valid = key.reset();
			if (!valid) {
				keys.remove(key);

				// all directories are inaccessible
				if (keys.isEmpty()) {
					break;
				}
			}
		}
	}

	public static void main(String[] args) throws IOException {
		Path dir = Paths.get("c:/temp");
		new Java8WatchServiceExample(dir).processEvents();
	}
}

运行该程序并在给定输入中更改文件和目录后,您将在控制台中注意到捕获的事件。

Output:

ENTRY_CREATE: c:\temp\New folder
ENTRY_DELETE: c:\temp\New folder
ENTRY_CREATE: c:\temp\data
ENTRY_CREATE: c:\temp\data\New Text Document.txt
ENTRY_MODIFY: c:\temp\data
ENTRY_DELETE: c:\temp\data\New Text Document.txt
ENTRY_CREATE: c:\temp\data\tempFile.txt
ENTRY_MODIFY: c:\temp\data
ENTRY_MODIFY: c:\temp\data\tempFile.txt
ENTRY_MODIFY: c:\temp\data\tempFile.txt
ENTRY_MODIFY: c:\temp\data\tempFile.txt

这就是使用 Java 8 WatchService API 监视文件更改并进行处理的简单示例。

学习愉快!

资源:

WatchService Java 文档

遍历文件树

Java 8 路径

Lambda 表达式

Java 运算符指南

原文: https://howtodoinjava.com/java/basics/operators-in-java/

了解可用的 Java 运算符优先顺序,并通过示例了解其用法。 我们还将尝试了解何时使用哪个运算符以及期望得到什么。

Table of Contents

1\. What Is an Operator?
2\. Assignment Operator (=)
3\. Arithmetic Operators
    3.1\. Unary Arithmetic Operators
    3.2\. Binary Arithmetic Operators
4\. String Concatenation Operator (+)
5\. Relational Operators
6\. Boolean Logical Operators
7\. Bitwise Operators
8\. Ternary Operator (? :)
9\. Java Operator Precedence Table

1. 什么是运算符?

运算符是符号,它对一个,两个或三个操作数执行特定类型的运算,并产生结果。 运算符及其操作数的类型确定对操作数执行的运算的类型以及产生的结果的类型。

1.1 Java 运算符的分类

Java 中的运算符可以基于两个条件进行分类:

  • 操作数的数量 – 基于操作数的数量,共有三种类型的运算符。 根据操作数的数量,运算符称为一元,二元或三元运算符。 如果一个运算符采用一个操作数,则它称为一元运算符; 如果它使用两个操作数,则它称为二元运算符; 如果需要三个操作数,则将其称为三元运算符
  • 他们执行的操作类型 – 运算符称为算术运算符关系运算符逻辑运算符按位运算符,具体取决于它对操作数执行的运算类型。

2. 赋值运算符(=

  • 赋值运算符(=)用于为变量赋值。
  • 它是一个二元运算符。 它需要两个操作数。
  • 右侧操作数的值已赋值给左侧操作数。
  • 左侧操作数必须是变量。
//26 is the right-hand operand. 
//counter is the left-hand operand, which is a variable of type int.

int counter = 26; 

Java 确保赋值运算符的右侧操作数的值与左侧操作数的数据类型兼容。 否则,会出现编译时错误。 如果是引用变量,则如果右侧操作数表示的对象与作为左侧操作数的参考变量不兼容,则可以编译源代码并获得运行时ClassCastException错误。

3. 算术运算符

  • +(加号),(减号),*(乘号),/(除号)之类的运算符称为算术 Java 中的运算符。
  • 它只能与数字类型的操作数一起使用。 这意味着,算术运算符的两个操作数都必须是byteshortcharintlongfloatdouble类型之一。
  • 这些运算符不能具有boolean基本类型和引用类型的操作数。
int sum = 10 + 20; 

int difference = 50 - 20; 

long area = 20l * 30l;    

int percentage = 20 / 100;

3.1 一元算术运算符

运算符 说明
'+' 一元加运算符; 没有任何作用
'-' 一元减运算符; 取反表达式值
'++' 增量运算符; 将值加 1
'--' 减量运算符; 将值减 1
'!' 逻辑补运算符; 反转布尔值

3.2 二元算术运算符

运算符 描述
'+' 加法 – 将运算符的两侧的值相加
'-' 减法 - 从左操作数中减去右操作数
'*' 乘法 – 将运算符两侧的值相乘
'/' 除法 – 用左操作数除以右操作数
'%' 模数 - 左操作数除以右操作数,然后返回余数

4. 字符串连接运算符(+

Java 中'+'运算符已重载。 如果一个运算符用于执行多个功能,则称该运算符为重载运算符

4.1 连接两个字符串

到目前为止,您已经看到它用作算术加法运算符来将两个数字相加。 它也可以用于连接两个字符串

String str1 = "Hello";
String str2 = " World";

String str3 = str1 + str2;      // Assigns "Hello World" to str3

3.2 将原始类型连接到字符串

字符串连接运算符还用于将原始类型和引用类型值连接到字符串。

int num = 26;

String str1 = "Alphabets";

String str2 = num + str1;    // Assigns "26Alphabets" to str2

4.2 连接null

如果引用变量包含null引用,则连接运算符将使用字符串"null"

String str1 = "I am ";

String str2 = null;

String str3 = str1 + str2;    // Assigns "I am null" to str3

5. 关系运算符

  • 所有关系运算符都是二元运算符。
  • 他们采用两个操作数。
  • 关系运算符产生的结果始终是布尔值truefalse
  • 它们主要用于 Java 控制语句中,例如if语句while语句等。

下面让我们看看 Java 中所有可用的关系运算符。

运算符 描述
'==' 等于 – 检查两个操作数的值是否相等,如果是,则条件成立。
'!=' 不等于 – 检查两个操作数的值是否相等,如果值不相等,则条件成立。
'>' 大于 – 检查左操作数的值是否大于右操作数的值,如果是,则条件成立。
'<' 小于 – 检查左操作数的值是否小于右操作数的值,如果是,则条件成立。
'>=' 大于或等于 – 检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成立。
'<=' 小于或等于 – 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成立。
int result = 20; 

if( result > 10) {                  //true
    //some operation
}

boolean isEqual = ( 10 == 20 );     //false

6. 布尔逻辑运算符

  • 所有布尔逻辑运算符只能与布尔操作数一起使用。
  • 它们主要用于控制语句中以比较两个(或多个)条件。
运算符 描述
'!' 如果操作数为false,则返回true;如果操作数为true,则返回false
'&&' 如果两个操作数均为true,则返回true。 如果任一操作数为false,则返回false
'&' 如果两个操作数均为true,则返回true。 如果任一操作数为false,则返回false
'||' 如果任一操作数为true,则返回true。 如果两个操作数均为false,则返回false
'|' 如果任一操作数为true,则返回true。 如果两个操作数均为false,则返回false
'^' 如果其中一个操作数为true,则返回true,但两个都不为真。 如果两个操作数相同,则返回false
'&=;' 如果两个操作数都为true,则&=返回true。 否则,它返回false
'|=' 如果任一操作数的结果为true,则!=返回true。 否则,它返回false
'^=' 如果两个操作数的求值均不同,即其中一个操作数为true,但不是两个都为true,则^=返回true。 否则,它返回false
int result = 20; 

if( result > 10 && result < 30) {      
    //some operation
}

if( result > 10 || result < 30) {      
    //some operation
}

  1. 逻辑 AND 运算符&)的工作方式与逻辑短路 AND 运算符( && )相同,不同之处在于。 逻辑 AND 运算符( & )会求值其右侧操作数,即使其左侧操作数的求值结果为false
  2. 逻辑或运算符 的工作方式与逻辑短路或运算符相同,只是有一个区别。 逻辑或运算符将求值其右侧操作数,即使其左侧操作数的求值结果为true

7. 按位运算符

按位运算符操纵其操作数的各个位。 Java 定义了几个按位运算符,它们可以应用于整数类型longintshortcharbyte

运算符 描述符
'&' 二元与运算符。如果位存在于两个操作数中,则将其复制到结果中。
'&#124;' 二元或运算符。如果位存在于任一操作数中,则复制它。
'^' 二元 XOR 运算符。如果在一个操作数中设置了位,但未在两个操作数中都设置,则会复制它。
'~' 二元补码运算符。它是一元的,具有“翻转”位的作用。
<< 二元左移运算符。 左操作数的值向左移动右操作数指定的位数。
>> 二元右移运算符。 左操作数的值向右移动右操作数指定的位数。
>>> 右移零填充运算符。 左操作数的值向右移动右操作数指定的位数,并且移位后的值用零填充。

8. 三元运算符(?:

  • Java 有一个条件运算符。 它被称为三元运算符,因为它需要三个操作数?:的两个符号成为三元运算符。如果boolean-expression的计算结果为true,则计算为true-expression。 否则,它将求值false-expression

8.1 语法

boolean-expression ? true-expression : false-expression

8.2 三元运算符示例

int number1 = 40;
int number2 = 20;

int biggerNumber = (number1 > number2) ? number1 : number2;

//Compares both numbers and return which one is bigger

9. Java 运算符优先级表

Java 具有明确定义的规则,用于指定当表达式具有多个运算符时对表达式中的运算符求值的顺序。 例如,乘法和除法的优先级高于加法和减法。

优先规则可以由显式括号覆盖。

当两个运算符共享一个操作数时,优先级较高的运算符优先。 例如,将1 + 2 * 3视为1 + (2 * 3),因为乘法的优先级高于加法。

在上面的表达式中,如果要首先添加值,则使用这样的显式括号 – (1 + 2) * 3

优先顺序 运算符 类型 关联性
15 () [] . 括号、数组下标、成员选择 左到右
14 ++ -- 一元后自增、一元后自减 右到左
13 ++ -- + – ! ~ (类型) 一元前自增、一元前自减、一元加、一元减、一元逻辑否定、一元按位补码、一元类型转换 右到左
12 * / % 乘法、除法、模数 左到右
11 + – 加法、减法 左到右
10 << >> >>> 按位左移、带符号扩展的按位右移、带零扩展的按位右移 左到右
9 < <= > >= instanceof 关系小于、关系小于或等于、关系大、关系大于或等于、类型比较(仅对象) 左到右
8 == != 关系不等于、关系不等于 左到右
7 & 按位与 左到右
6 ^ 按位异或 左到右
5 | 按位或 左到右
4 && Logical AND 左到右
3 || 逻辑或 左到右
2 ?: 三元条件 右到左
1 = += -= *= /= %= 赋值、加法赋值、减法赋值、乘法赋值、除法赋值、模赋值 右到左

Java 中的运算符就这些了。

学习愉快!

阅读更多:

Oracle Java 文档

链接

优先级表

Java 8 解析字符串为日期

原文: https://howtodoinjava.com/java8/java-8-parse-string-to-date/

让我们看看如何在 Java 8 中从字符串转换为日期。

1)将字符串转换为 ISO8601 格式的日期

默认情况下,java 日期采用 ISO8601 格式,因此,如果您有任何表示 ISO8601 格式的日期的字符串,则可以直接使用LocalDate.parse()LocalDateTime.parse()方法。

String armisticeDate = "2016-04-04";

LocalDate aLD = LocalDate.parse(armisticeDate);
System.out.println("Date: " + aLD);

String armisticeDateTime = "2016-04-04T11:50";

LocalDateTime aLDT = LocalDateTime.parse(armisticeDateTime);
System.out.println("Date/Time: " + aLDT);

Output:

Date: 2016-04-04
Date/Time: 2016-04-04T11:50

2)以自定义格式将字符串转换为日期

如果日期采用某种自定义格式,则还需要使用DateTimeFormatter.ofPattern()放置其他逻辑来处理格式。


String anotherDate = "04 Apr 2016";

DateTimeFormatter df = DateTimeFormatter.ofPattern("dd MMM yyyy");
LocalDate random = LocalDate.parse(anotherDate, df);

System.out.println(anotherDate + " parses as " + random);

学习愉快!

参考文献:

DateTimeFormatter

LocalDateTime

LocalDate

Java 8 – 连接字符串数组 – 将数组转换为字符串

原文: https://howtodoinjava.com/java8/java-8-join-string-array-example/

Java 示例连接字符串数组以产生单个 String。 此代码可用于将数组转换为 Java 中的字符串。 在开发期间,特别是在从 JSON 或 XML 解析内容时,我们可能会多次需要此信息。

1. 连接字符串数组 – Java 8 String.join()

String.join()方法具有两种重载形式。

连接多个字符串参数

此方法采用可变参数格式的所有字符串,并且所有字符串均作为方法中的参数传递。 通过附加由参数分隔符分隔的所有字符串来接收返回字符串。

String join(CharSequence delimiter, CharSequence... elements)

此方法可用于连接尚未以集合或数组形式出现的多个字符串。

String joinedString = String.join(", ", "How", "To", "Do", "In", "Java");
System.out.println(joinedString);

Output:

How, To, Do, In, Java

连接数组或字符串列表

String join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

此方法用于连接字符串的数组或字符串列表

Java 程序连接字符串列表

List<String> strList = Arrays.asList("How", "To", "Do", "In", "Java");

String joinedString = String.join(", ", strList);

System.out.println(joinedString);

Output:

How, To, Do, In, Java

Java 程序连接字符串数组

String[] strArray = { "How", "To", "Do", "In", "Java" };

String joinedString = String.join(", ", strArray);

System.out.println(joinedString);

Output:

How, To, Do, In, Java

2. Java 8 StringJoiner用于格式化输出

使用StringJoiner类,我们可以生成格式的连接字符串输出。 在使用 lambda 收集器时,这特别有用。

2.1 方法语法

它的构造器采用三个参数 – delimiter(必需),以及可选的prefixsuffix

StringJoiner(CharSequence delimiter)
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

2.2 StringJoiner示例

使用与以上示例类似的输入来运行示例,以连接多个字符串。 我们想要将输出格式化为[How, To, Do, In, Java],然后可以使用以下代码:

StringJoiner joiner = new StringJoiner(", ", "[", "]");

joiner.add("How")
		.add("To")
		.add("Do")
		.add("In")
		.add("Java");

Output:

[How, To, Do, In, Java]

3. 使用Collectors.joining()的字符串列表

在使用 Java 8 lambda 时,我们可以使用Collectors.joining()将列表转换为字符串**。

List<String> numbers = Arrays.asList("How", "To", "Do", "In", "Java");

String joinedString = 	numbers
						.stream()
						.collect(Collectors.joining(", ","[","]"));

System.out.println(joinedString);

Output:

[How, To, Do, In, Java]

4. 使用StringUtils.join()连接字符串数组

Commons Langs 库的StringUtils类具有几种join()方法,可用于将数组或字符串列表组合为单个字符串。

4.1 Maven 依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>

4.2 StringUtils.join()示例

参见给出的例子。 首先,我们将与空字符串连接在一起。 在第二个示例中,我们将与逗号连接在一起。

String[] strArray = { "How", "To", "Do", "In", "Java" };

String joinedString = StringUtils.join(strArray);
System.out.println(joinedString);

String joinedString2 = StringUtils.join(strArray, ", ");
System.out.println(joinedString2);

Output:

HowToDoInJava
How, To, Do, In, Java

使用上面给出的示例在 Java 中连接到字符串数组

学习愉快!

参考:

Java 8 String文档

StringJoiner JavaDoc

Apache Commons StringUtils文档

Java Base64 编码和解码示例

原文: https://howtodoinjava.com/java8/base64-encoding-and-decoding-example-in-java-8/

在 Java 8 学习系列中,我们已经学习了使用流逐行读取文件的新方法。 Java 8 引入了与 IO 操作相关的另一项很好的附加特性,即 Base64 支持。 在这篇文章中,我们将学习它。

什么是 Base64 编码?

当您有一些二进制数据要通过网络传输时,通常不这样做,只是将数据以原始格式通过网络转换为比特流。 为什么? 因为某些媒体仅用于流文本。 这些协议可能会将您的二进制数据解释为不是的控制字符。

Base64编码将您的二进制数据转换为 64 个可打印的ASCII字符。 通常,它是对电子邮件消息中的二进制数据和"basic" HTTP 身份验证完成的。 这 64 个可打印字符是:

  • 26 个大写字母[A…Z]
  • 26 个小写字母[a…z]
  • 10 位数字[0…9]
  • 2 个符号(阅读更多

具有上述字符的编码字符串可以安全地在支持文本数据的网络上传输,而不必担心由于控制字符的混乱而丢失数据。

Java 8 之前的 Base64 支持

多年来,java 通过非公共类(因此不可使用)java.util.prefs.Base64和未记录的类sun.misc.BASE64Encoder为 Base64 提供了支持。 此类在公共领域中的信息也非常有限。

Java 8 对 Base64 的支持

Java 8 添加了一个用于 Base64 编码和解码目的的类,即java.util.Base64。 我们将在下面的代码示例中使用它。

1)将字符串编码为 Base64

这就像获取编码器实例并将字符串作为字节输入以对其进行编码一样简单。

Base64.Encoder encoder = Base64.getEncoder();
String normalString = "username:password";
String encodedString = encoder.encodeToString( 
        normalString.getBytes(StandardCharsets.UTF_8) );

Output:

dXNlcm5hbWU6cGFzc3dvcmQ=

2)解码 base64 的编码字符串

这也很简单。 只需获取Base64.Decoder的实例,并使用它来解码以 base64 编码的字符串。

String encodedString = "dXNlcm5hbWU6cGFzc3dvcmQ=";
Base64.Decoder decoder = Base64.getDecoder();
byte[] decodedByteArray = decoder.decode(encodedString);
//Verify the decoded string
System.out.println(new String(decodedByteArray));

Output:

username:password

3)包装 base64 的编码输出流

如果您不想直接使用数据,而更喜欢使用流,则可以包装输出流,以使写入此输出流的所有数据都将自动以 base64 编码。

Path originalPath = Paths.get("c:/temp", "mail.txt");
Path targetPath = Paths.get("c:/temp", "encoded.txt");
Base64.Encoder mimeEncoder = Base64.getMimeEncoder();
try(OutputStream output = Files.newOutputStream(targetPath)){
    //Copy the encoded file content to target file
    Files.copy(originalPath, mimeEncoder.wrap(output));
    //Or simply use the encoded output stream
    OutputStream encodedStrem = mimeEncoder.wrap(output);
}

仅此而已。 这已经足够简单了。

祝您学习愉快!

Math 类中的 Java 精确算术运算支持

原文: https://howtodoinjava.com/java8/java-8-exact-airthmetic-operations-supported-in-math-class/

Java 8 为 Java 开发人员带来了许多很棒的特性。 我已经在比较器更改流示例内部与外部迭代谓词函数式接口默认方法lambda 表达式日期和时间 API 更改。 以上所有更改都与 lambda 表达式有关,lambda 表达式是最受关注的获取者,也是改变游戏规则的人。

除了上述更改之外,我们还获得了非 lambda 表达式更改。 我已经在上一篇文章中讨论过String.join()方法。 在本文中,我将讨论在`Math类中进行的更改,以支持精确的算术。

Sections in this post:

1) (add|substract|multiply|increment|decrement|negate)Exact methods
2) floorMod and floorDiv methods
3) toIntExact method
4) nextDown method

让我们一一讨论。

1)加减乘除和求反的精确方法

Math类提供了这些新方法,每次操作结果溢出到最大限制时,这些方法都会引发java.lang.ArithmeticException异常。 以上所有方法都将参数作为intlong原始类型。

例如考虑乘以100000 * 100000。通常,这样做会“无声地”给您带来错误的回报,并且在应用程序运行时,您有责任每次检查最大限制以避免错误的计算。

在 Java 8 中,multiPlyExact方法将为您完成此工作,并且如果结果超出最大限制,则将引发异常。


//Normal multiplication
System.out.println( 100000 * 100000 );

//Using multiPlyExact
System.out.println( Math.multiplyExact( 100000 , 100000 ));

Output:

1410065408 //Incorrect result
Exception in thread "main" java.lang.ArithmeticException: integer overflow
    at java.lang.Math.multiplyExact(Math.java:867)
    at java8features.MathematicalFuctions.main(MathematicalFuctions.java:8)

其他操作也会发生相同的情况。 例如添加操作与addExact


//Normal add operation
System.out.println( Integer.MAX_VALUE + Integer.MAX_VALUE );

//Using addExact
System.out.println( Math.addExact( Integer.MAX_VALUE , Integer.MAX_VALUE ));

Output:

-2          //incorrect result
Exception in thread "main" java.lang.ArithmeticException: integer overflow
    at java.lang.Math.addExact(Math.java:790)
    at java8features.MathematicalFuctions.main(MathematicalFuctions.java:11)

2)floorModfloorDiv方法

Java 8 已经解决了很长的整数余数问题。 大家都知道n % 2

i) 0:如果数字是偶数

ii) 1:如果是奇数

如果数字为负怎么办。 上面的表达式可以/不能返回 -1。 如果事实为负数,结果对我来说是不可预测的。

System.out.println( 10 % 2 );
System.out.println( 11 % 2 );
System.out.println( -15 % 2 );
System.out.println( -16 % 2 );

Output:

0
1
-1
0

现在,使用 Java 8 附带的精确数学执行上述取模操作。

System.out.println( Math.floorMod(10 , 2) );
System.out.println( Math.floorMod(11 , 2) );
System.out.println( Math.floorMod(-15 , 2) );
System.out.println( Math.floorMod(-16 , 2) );

Output:

0
1
1
0

类似地,另一个问题可能是获取时钟的时针位置。 假设当前时间是 10 点。 您已经调整了 n 小时,现在想获得排名。 公式很简单:

Current Position = (position + adjustment) % 12

如果将(位置+调整)计算为位置编号,则效果很好。 但是,如果时针逆时针移动,从而(位置+调整)变为负数,该怎么办。 让我们检查。


System.out.println( (10+3) % 12 );
System.out.println( (5+6) % 12 );
System.out.println( (10-27) % 12 );

Output:

1
11
-5 //This is incorrect

现在,使用精确的浮点方法floorMod


System.out.println( Math.floorMod(10+3 , 12) );
System.out.println( Math.floorMod(5+6 , 12) );
System.out.println( Math.floorMod(10-27 , 12) );

Output:

1
11
7 //This is correct

floorDiv()方法中也进行了类似的更改。

3)toIntExact方法

当您尝试为int类型变量分配一个long值时,此方法很方便。 虽然这种情况并非易事,但在极少数情况下可能会需要。 如果long值大于最大int值,则正常的longint转换会导致数据错误。 如果发生类似这种情况,toIntExact()方法将引发异常。


System.out.println( Long.MAX_VALUE );
System.out.println( (int)Long.MAX_VALUE );
System.out.println( Math.toIntExact(10_00_00_000) );
System.out.println( Math.toIntExact(Long.MAX_VALUE) );

Output:

9223372036854775807
-1
100000000
Exception in thread "main" java.lang.ArithmeticException: integer overflow
    at java.lang.Math.toIntExact(Math.java:1011)
    at java8features.MathematicalFuctions.main(MathematicalFuctions.java:46)

4)nextDown方法

这也是 Java 8 套件中的一个明显新增特性。 当您需要返回小于 n 的数字时,这非常有用。 您计算的返回数字恰好是 n。 然后,您可以使用此方法查找最接近 n(但仍小于 n)的数字。


System.out.println( Math.nextDown(100) );
System.out.println( Math.nextDown(100.365) );

Output:

99.99999
100.36499999999998

请注意,自 Java 6 起,Math.nextUp()已经存在。在 Java 8 中仅添加了Math.nextDown()

就是这样。 希望您喜欢阅读。 您的想法和评论总是受到欢迎。

学习愉快!

Java 8 带有 lambda 的Comparator示例

原文: https://howtodoinjava.com/java8/comparator-example-lambda/

当我们想要对可以相互比较的对象的集合进行排序时,使用比较器。 也可以使用Comparable接口完成此比较,但是它限制了您只能以一种特定的方式比较这些对象。 如果要基于多个条件/字段对该集合进行排序,则仅需使用Comparator

快速参考:

//Compare by Id
Comparator<Employee> compareById_1 = Comparator.comparing(e -> e.getId());

Comparator<Employee> compareById_2 = (Employee o1, Employee o2) -> o1.getId().compareTo( o2.getId() );

//Compare by firstname
Comparator<Employee> compareByFirstName = Comparator.comparing(e -> e.getFirstName());

//how to use comparator
Collections.sort(employees, compareById);

1)概述

为了演示该概念,我将使用具有四个属性的类Employee。 我们将使用它来理解各种用例。

public class Employee {
    private Integer id;
    private String firstName;
    private String lastName;
    private Integer age;

    public Employee(Integer id, String firstName, String lastName, Integer age){
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

	//Other getter and setter methods

	@Override
    public String toString() {
        return "\n["+this.id+","+this.firstName+","+this.lastName+","+this.age+"]"; 
    }
}

另外,我编写了一种方法,该方法始终以未排序的顺序返回Employees的列表。

private static List<Employee> getEmployees(){
	List<Employee> employees  = new ArrayList<>();
	employees.add(new Employee(6,"Yash", "Chopra", 25));
	employees.add(new Employee(2,"Aman", "Sharma", 28));
	employees.add(new Employee(3,"Aakash", "Yaadav", 52));
	employees.add(new Employee(5,"David", "Kameron", 19));
	employees.add(new Employee(4,"James", "Hedge", 72));
	employees.add(new Employee(8,"Balaji", "Subbu", 88));
	employees.add(new Employee(7,"Karan", "Johar", 59));
	employees.add(new Employee(1,"Lokesh", "Gupta", 32));
	employees.add(new Employee(9,"Vishu", "Bissi", 33));
	employees.add(new Employee(10,"Lokesh", "Ramachandran", 60));
	return employees;
}

2)按名字排序

基本用例,其中将根据员工的名字对员工列表进行排序。

	List<Employee> employees  = getEmployees();

	//Sort all employees by first name
	employees.sort(Comparator.comparing(e -> e.getFirstName()));

	//OR you can use below
	employees.sort(Comparator.comparing(Employee::getFirstName));

	//Let's print the sorted list
	System.out.println(employees);

Output: //Names are sorted by first name

[
	[3,Aakash,Yaadav,52], 
	[2,Aman,Sharma,28], 
	[8,Balaji,Subbu,88], 
	[5,David,Kameron,19], 
	[4,James,Hedge,72], 
	[7,Karan,Johar,59], 
	[1,Lokesh,Gupta,32], 
	[10,Lokesh,Ramachandran,60], 
	[9,Vishu,Bissi,33], 
	[6,Yash,Chopra,25]
]

3)按名字逆序排序

如果我们想按姓氏排序但又受尊敬的顺序怎么办。 这真的很容易; 使用reversed()方法。

	List<Employee> employees  = getEmployees();

	//Sort all employees by first name; And then reversed
	Comparator<Employee> comparator = Comparator.comparing(e -> e.getFirstName());
	employees.sort(comparator.reversed());

	//Let's print the sorted list
	System.out.println(employees);

Output: //Names are sorted by first name

[[6,Yash,Chopra,25], 
[9,Vishu,Bissi,33], 
[1,Lokesh,Gupta,32], 
[10,Lokesh,Ramachandran,60], 
[7,Karan,Johar,59], 
[4,James,Hedge,72], 
[5,David,Kameron,19], 
[8,Balaji,Subbu,88], 
[2,Aman,Sharma,28], 
[3,Aakash,Yaadav,52]]

4)按姓氏排序

我们也可以使用类似的代码对姓氏进行排序。

	List<Employee> employees  = getEmployees();

	//Sort all employees by first name
	employees.sort(Comparator.comparing(e -> e.getLastName()));

	//OR you can use below
	employees.sort(Comparator.comparing(Employee::getLastName));

	//Let's print the sorted list
	System.out.println(employees);

Output: //Names are sorted by first name

[[9,Vishu,Bissi,33], 
[6,Yash,Chopra,25], 
[1,Lokesh,Gupta,32], 
[4,James,Hedge,72], 
[7,Karan,Johar,59], 
[5,David,Kameron,19], 
[10,Lokesh,Ramachandran,60], 
[2,Aman,Sharma,28], 
[8,Balaji,Subbu,88], 
[3,Aakash,Yaadav,52]]

5)在多个字段上排序 – thenComparing()

在这里,我们首先按员工的名字对他们的名单进行排序,然后再对姓氏的列表进行再次排序。 就像我们对 SQL 语句应用排序一样。 这实际上是一个非常好的特性。

现在,您无需始终对 SQL select语句中的多个字段使用排序,也可以在 Java 中对其进行排序。

List<Employee> employees  = getEmployees();

//Sorting on multiple fields; Group by.
Comparator<Employee> groupByComparator = Comparator.comparing(Employee::getFirstName)
													.thenComparing(Employee::getLastName);
employees.sort(groupByComparator);

System.out.println(employees);

Output:

[3,Aakash,Yaadav,52], 
[2,Aman,Sharma,28], 
[8,Balaji,Subbu,88], 
[5,David,Kameron,19], 
[4,James,Hedge,72], 
[7,Karan,Johar,59], 
[1,Lokesh,Gupta,32], 		 //These both employees are 
[10,Lokesh,Ramachandran,60], //sorted on last name as well
[9,Vishu,Bissi,33], 
[6,Yash,Chopra,25]

5)并行排序(具有多个线程)

您还可以使用多个线程并行地对对象集合进行排序。 如果集合足够大,可以容纳数千个对象,那么它将非常快。 对于少量对象,常规排序就足够了,建议使用。

//Parallel Sorting
Employee[] employeesArray = employees.toArray(new Employee[employees.size()]);

//Parallel sorting
Arrays.parallelSort(employeesArray, groupByComparator);

System.out.println(employeesArray);

Output:

[3,Aakash,Yaadav,52], 
[2,Aman,Sharma,28], 
[8,Balaji,Subbu,88], 
[5,David,Kameron,19], 
[4,James,Hedge,72], 
[7,Karan,Johar,59], 
[1,Lokesh,Gupta,32], 		 //These both employees are 
[10,Lokesh,Ramachandran,60], //sorted on last name as well
[9,Vishu,Bissi,33], 
[6,Yash,Chopra,25]

这就是使用带有Comparator的 lambda 对对象进行排序的全部。 如果您了解有关此概念的更多技术,请与我们所有人共享。

学习愉快!

使用Pattern.compile()方法将 Java 正则表达式作为谓词

原文: https://howtodoinjava.com/java8/regex-predicate-using-pattern-compile/

学习将正则表达式编译为java.util.function.Predicate。 当您要对匹配的标记执行某些操作时,此特性很有用。

将正则表达式转换为谓词

我有不同域的电子邮件列表,我只想对域名为example.com的电子邮件 ID 执行某些操作。

现在使用Pattern.compile().asPredicate()方法从编译的正则表达式中获取谓词。 该谓词可与 lambda 流一起使用,以将其应用于每个标记中。

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class RegexPredicateExample {
	public static void main(String[] args) {
		// Compile regex as predicate
		Predicate<String> emailFilter = Pattern
										.compile("^(.+)@example.com$")
										.asPredicate();

		// Input list
		List<String> emails = Arrays.asList("alex@example.com", "bob@yahoo.com", 
							"cat@google.com", "david@example.com");

		// Apply predicate filter
		List<String> desiredEmails = emails
									.stream()
									.filter(emailFilter)
									.collect(Collectors.<String>toList());

		// Now perform desired operation
		desiredEmails.forEach(System.out::println);
	}
}

输出:

alex@example.com
david@example.com

使用Pattern.matcher()使用正则表达式

如果要使用旧的Pattern.matcher(),请使用以下代码模板。

public static void main(String[] args) 
{

	Pattern pattern = Pattern.compile("^(.+)@example.com$");

	// Input list
	List<String> emails = Arrays.asList("alex@example.com", "bob@yahoo.com", 
						"cat@google.com", "david@example.com");

	for(String email : emails)
	{
	    Matcher matcher = pattern.matcher(email);

	    if(matcher.matches()) 
	    {
	    	System.out.println(email);
	    }
	}
}

Output:

alex@example.com
david@example.com

将我的问题放在评论部分。

学习愉快!

Java 字符串连接(CSV)示例

原文: https://howtodoinjava.com/java8/java-8-string-join-csv-example/

到目前为止,直到 java 7 为止,我们都具有String.spli()方法,该方法可以基于作为参数传递的某些标记来分割字符串 。 它返回字符串标记列表作为字符串数组。 但是,如果您想通过使用字符串之间的分隔符来连接字符串标记或通过使用字符串之间的分隔符来创建 CSV,则必须遍历字符串列表或字符串数​​组,然后使用StringBuilderStringBuffer对象连接这些字符串标记并最终获得 CSV

使用join()的字符串连接(CSV)

Java 8 使这项任务变得容易。 现在,您有了String.join()方法,其中第一个参数是分隔符,然后您可以传递多个字符串或具有字符串实例的Iterable实例作为第二个参数。 它将返回 CSV 作为返回值。

package java8features;

import java.time.ZoneId;

public class StringJoinDemo {
public static void main(String[] args){
String joined = String.join("/","usr","local","bin");
System.out.println(joined);

String ids = String.join(", ", ZoneId.getAvailableZoneIds());
System.out.println(ids);
}
}

Output:

usr/local/bin
Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8.....

因此,下次使用 Java 8 并希望合并字符串时,工具包中将提供一个方便的方法。 用它。

祝您学习愉快!

Java 8 两个日期之间的差异

原文: https://howtodoinjava.com/java/date-time/calculate-difference-between-two-dates-in-java/

Java 示例计算 Java 8 中两个日期之间的差。 首先,我们将学习使用甚至在 Java 8 发行之前就可用的 Jodatime API 计算差异。 如果您仍未使用 Java 8,那么 JodaTime 应该是您的首选。

后 3 种方法使用的是 Java 8 以来可用的新的日期时间 API 特性。

1. JodaTime – 两个日期之间的差异(Java 8 之前)

Maven

<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10</version>
</dependency>

示例

大家都更喜欢可读性,我建议使用 Jodatime 库(实际上启发了 Java 8 日期/时间 API)。 例如:

public void difference_between_two_dates_duration_Joda()
 {
     DateTime dateOfBirth = new DateTime(1988, 7, 4, 0, 0, GregorianChronology.getInstance());
     DateTime currentDate = new DateTime();
     Days diffInDays = Days.daysBetween(dateOfBirth, currentDate);
     Hours diffInHours = Hours.hoursBetween(dateOfBirth, currentDate);
     Minutes diffInMinutes = Minutes.minutesBetween(dateOfBirth, currentDate);
     Seconds seconds = Seconds.secondsBetween(dateOfBirth, currentDate);
 }

2. Java 8 – 两个日期之间的差异

Java Date一直缺乏足够的支持来有效地表达日期和时间段。 Java 8 首次尝试升级此日期/时间 API。 如果您在项目中使用 Java 8,则一定要使用以下两种方法之一来计算两个日期之间的日期/时间差。

2.1 java.time.Period示例,以了解日/月/年的差异

Java 程序使用Period类获取在两天之间的差异

LocalDate endofCentury = LocalDate.of(2014, 01, 01);
LocalDate now = LocalDate.now();

Period diff = Period.between(endofCentury, now);

System.out.printf("Difference is %d years, %d months and %d days old", 
					diff.getYears(), diff.getMonths(), diff.getDays());

2.2 java.time.temporal.ChronoUnit示例,以了解日/月/年的差异

Java 程序使用ChronoUnit类来获取两个月之间的日期之间的差异

public void difference_between_two_dates_java8()
{
     LocalDate dateOfBirth = LocalDate.of(1980, Month.JULY, 4);
     LocalDate currentDate = LocalDate.now();
     long diffInDays = ChronoUnit.DAYS.between(dateOfBirth, currentDate);
     long diffInMonths = ChronoUnit.MONTHS.between(dateOfBirth, currentDate);
     long diffInYears = ChronoUnit.YEARS.between(dateOfBirth, currentDate);
}

您可以使用ChronoUnit了解较小时间单位中的差异,例如毫秒分钟等。但是在这种情况下,您将不得不使用LocalDateTime代替在第一个示例中使用的LocalDate

public void difference_between_two_dates_duration()
 {
     LocalDateTime dateTime = LocalDateTime.of(1988, 7, 4, 0, 0);
     LocalDateTime dateTime2 = LocalDateTime.now();
     long diffInNano = ChronoUnit.NANOS.between(dateTime, dateTime2);
     long diffInSeconds = ChronoUnit.SECONDS.between(dateTime, dateTime2);
     long diffInMilli = ChronoUnit.MILLIS.between(dateTime, dateTime2);
     long diffInMinutes = ChronoUnit.MINUTES.between(dateTime, dateTime2);
     long diffInHours = ChronoUnit.HOURS.between(dateTime, dateTime2);
 }

2.3 java.time.Duration示例了解毫秒/秒/分钟等的差异

Java 程序使用Duration类来获取以毫秒为单位的两个日期之间的

public void difference_between_two_dates_duration()
 {
     LocalDateTime dateTime = LocalDateTime.of(1988, 7, 4, 0, 0);
     LocalDateTime dateTime2 = LocalDateTime.now();
    int diffInNano = java.time.Duration.between(dateTime, dateTime2).getNano();
     long diffInSeconds = java.time.Duration.between(dateTime, dateTime2).getSeconds();
     long diffInMilli = java.time.Duration.between(dateTime, dateTime2).toMillis();
     long diffInMinutes = java.time.Duration.between(dateTime, dateTime2).toMinutes();
     long diffInHours = java.time.Duration.between(dateTime, dateTime2).toHours();
 }

最重要的是,这三种方式都具有足够的可读性和灵活性,可以知道多个时间单位之间的日期差异。 可以随意在用例中使用以上程序,以计算 Java 中两个日期之间的天数。

学习愉快!

Java – 内部与外部迭代

原文: https://howtodoinjava.com/java8/java-8-tutorial-internal-vs-external-iteration/

外部迭代

直到 Java 7 为止,集合框架都依赖于外部迭代的概念,其中集合通过实现Iterable提供枚举其元素(即Iterator)的方法,并且客户端使用它顺序地遍历了集合。 例如,如果我们想将所有字符串都大写,则可以这样写:

public class IterationExamples {
	public static void main(String[] args){
		List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});

		for(String letter: alphabets){
			System.out.println(letter.toUpperCase());
		}
	}
}

或者我们可以这样写:

public class IterationExamples {
	public static void main(String[] args){
		List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});

		Iterator<String> iterator = alphabets.listIterator();
		while(iterator.hasNext()){
			System.out.println(iterator.next().toUpperCase());
		}
	}
}

以上两个代码段均用于外部迭代。 外部迭代非常简单,但是存在几个问题:

1)Java 的for-each循环/迭代器本质上是顺序的,必须按集合指定的顺序处理元素。

2)它限制了管理控制流的机会,该控制流可能能够通过利用数据的重新排序,并行性,短路或惰性来提供更好的性能。

内部迭代

有时需要for-each循环的强力保证(顺序,顺序),但通常只是性能的劣势。 外部迭代的替代方法是内部迭代,客户端让它由库处理而不是控制迭代,而仅提供必须对所有/某些数据元素执行的代码。

上一个示例的内部迭代等效项是:

public class IterationExamples {
	public static void main(String[] args){
		List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});

		alphabets.forEach(l -> l.toUpperCase());
	}
}

在外部迭代将“做怎么”(大写)和“怎么做”(for循环/迭代器)混合使用的情况下,内部迭代使客户端仅提供“做怎么”,而由库控制“怎么做”。 这提供了许多潜在的好处:客户端代码可以更加清晰,因为它只需要关注于陈述问题,而不是解决问题的细节,并且我们可以将复杂的优化代码移到可以使所有用户受益的库中。

这就是关于内部与外部迭代的全部内容。

祝您学习愉快!

Java 中的安全随机数生成

原文: https://howtodoinjava.com/java8/secure-random-number-generation/

如果您已经开发软件已有一段时间,那么您就会知道如何使用 Java 的SecureRandom类甚至甚至安全地生成随机数。 不幸的是,生成安全随机数并不像简单地使用SecureRandom那样容易。

在此 Java 示例中,我们组装了一个简单的清单,以帮助您在应用程序中使用安全随机数时获得成功。

阅读更多:用 Java 生成安全哈希

1. 如何生成安全的随机数

通常,随机数的生成取决于熵(随机性)的来源,例如信号,设备或硬件输入。 在 Java 中,java.security.SecureRandom类被广泛用于生成密码强的随机数。

确定性随机数已成为许多软件安全漏洞的根源。 这个想法是,给定几个随机数样本,对手(黑客)应该无法确定原始种子。 如果违反了此限制,则对手可能会成功预测所有将来的随机数。

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

public class Main 
{
	public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException 
	{
		SecureRandom secureRandomGenerator = SecureRandom.getInstance("SHA1PRNG", "SUN");

		// Get 128 random bytes
		byte[] randomBytes = new byte[128];
		secureRandomGenerator.nextBytes(randomBytes);

		//Get random integer
		int r = secureRandomGenerator.nextInt();

		//Get random integer in range
		int randInRange = secureRandomGenerator.nextInt(999999);
	}
}

2. 安全随机数 - 最佳做法

2.1 确定性能标准和工作负载平衡

如果性能是首要考虑因素,请使用SHA1PRNG,它是/dev/urandom的种子。SHA1PRNG可以比NativePRNG快 17 倍,但播种选项是固定的。

使用NativePRNG进行播种更加灵活,但是如果服务器上的熵不够大(因为它从/dev/random读取),它将阻止。 如果您不知道从哪里开始,请从SHA1PRNG开始。

2.2 不接受默认值; 指定 PRNG 和要使用的供应器

像这样指定您的条件:

SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");

以下是有关受支持的 PRNG 和供应器的其他信息。

以下是 SUN 供应器的 PRNG 列表:

  • SHA1PRNG(目前,初始播种是通过系统属性和java.security熵收集设备的组合完成的)
  • NativePRNGnextBytes()使用/dev/urandomgenerateSeed()使用/dev/random
  • NativePRNGBlockingnextBytes()generateSeed()使用/dev/random
  • NativePRNGNonBlockingnextBytes()generateSeed()使用/dev/urandom
  • NativePRNGBlockingNativePRNGNonBlocking在 JRE 8+ 中可用。

有关其他供应器的信息,请参见 Java 密码架构 JDK 8 的 Oracle 供应器文档。 Oracle 还提供了按 OS 平台描述供应器的文档。

2.3 提供更多机会增加熵

定期创建SecureRandom的新实例并重新设定种子,如下所示:

SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
sr.setSeed(SecureRandom.generateSeed(int))

如果种子泄漏,定期重新播种可防止数据泄露。 如果使用SHA1PRNG,则始终在创建 PRNG 的新实例后立即调用java.security.SecureRandom.nextBytes(byte[])

2.4 降低可预测性

如果为egdSourcejava.security文件中的配置参数或java.security.egd系统属性分配了可预测的文件/ URL,则SecureRandom可以变为可预测的。

2.5 旧版 Java 的补丁

低于 1.4.2 的 JRE 版本在生成SHA1PRNG安全种子方面存在已知问题。 再说一次,如果您使用的 Java 版本如此之旧,则存在更大的安全隐患。

Java 的旧版本非常不安全,因此更新补丁程序非常重要。 确保客户安全的最佳方法之一是针对可能出现的最新 Oracle 重要补丁更新快速认证您的软件。

学习愉快!

Java 关键字

原文: https://howtodoinjava.com/java-keywords/

Java 关键字是 50 个保留字,在应用程序代码中具有非常特定的含义。 程序员不应将这些关键字用于其预期的其他目的。

1. Java 关键字特性

  • 给定的是列表具有所有保留的 java 关键字。 我们不能在程序中使用以下任何内容作为标识符。
  • 关键字constgoto被保留,即使它们当前未被使用。
  • truefalsenull看起来像关键字,但它们实际上是字面值。 我们不能在程序中使用它们作为标识符。
  • strictfp已在 JDK 1.2 中添加。
  • assert已在 JDK 1.4 中添加。
  • enum已在 JDK 1.5 中添加。

2. Java 关键字列表

abstract continue for new switch
assert default goto package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while

无法在单个页面中了解所有关键字。 我们将在专用教程中了解每个 Java 关键字。

学习愉快!

Java 7 教程

Java 7 的更改,特性和增强

原文: https://howtodoinjava.com/java7/java-7-changes-features-and-enhancements/

我已经介绍了许多 java 7 更改,这些更改在发行版中是新的。 在这篇文章中,我将对它们进行总结,以便有兴趣的人可以在短时间内快速了解所有特性。

Features covered in this post

Improved type Inference Or Diamond Operator
Automatic resource management with try-with-resources
NIO 2.0
Exception handling improvements
    Suppressed exceptions
    Catch Multiple Exceptions in catch block
Number formatting enhancement
String class support in switch statement
Binary Literals with prefix "0b"
ForkJoin Framework
Automatic reloading with WatchService
G1 Garbage Collector

改进的类型推断

在 Java 7 之前,使用泛型时,必须为变量类型及其实际类型提供类型参数。 现在,此新的 Java 7 特性已使它有所缓解,并且声明右侧的空白菱形将可以正常工作。

编译器在 Java 7 中足够聪明,可以识别出空白菱形推断出在声明左侧定义的类型。

public class ElvisOperatorTest {
    public static void main(String[] args) {
        @SuppressWarnings("unused")
        Map params = new HashMap&lt;&gt;();
    }
}

使用try-with-resources的自动资源管理

在 Java 7 之前,我们必须使用finally块来清理资源。 finally块不是强制性的,但是清理资源是为了防止系统损坏。 使用 Java 7,无需显式的资源清理。 它是自动完成的。 在try-with-resources块(try(…){…})中初始化资源时,将完成自动资源清除。

由于新接口AutoCloseable而进行清理。 try块完成后,JVM 将立即调用其 close方法。 您不应在代码中调用close()方法。 这应该自动称为 JVM。 手动调用它可能会导致意外结果。

public class ResourceManagementInJava7
{
    public static void main(String[] args)
    {
        try (BufferedReader br = new BufferedReader(new FileReader("C:/temp/test.txt")))
        {
            String sCurrentLine;
            while ((sCurrentLine = br.readLine()) != null)
            {
                System.out.println(sCurrentLine);
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}

NIO 2.0

Java SE 7 引入了java.nio.file包及其相关包java.nio.file.attribute,为文件 I/O 和访问默认文件系统提供了全面的支持。 路径类是一个很大的补充,它允许您以统一的方式表示操作系统中的任何路径。 新的 API 是对旧 API 的补充,并提供了一些有用的方法检查,删除,复制和移动文件。 您也可以像在 Linux 中那样创建符号链接和硬链接。 JDK 7 新文件 API 还能够使用通配符/正则表达式搜索文件。 您还将获得支持以查看目录中的更改。

继续探索链接网页中的所有这些更改。

异常处理方面的改进

Java 7 在异常处理方面也带来了一些不错的增强。 这些可以大致分为两个特性:

受抑制的异常

顾名思义,抑制的异常是在代码中引发的异常,但是以某种方式被忽略。 如果您还记得try-catch-finally块的执行顺序以及它们如何返回任何值或异常,则您会记得,在try块中也抛出异常的情况下,抑制了finally块中引发的异常。 在 Java 7 之前的版本中,通过记录日志了解了这些异常(如果已实现),但是一旦finally块结束,您就无法控制这些异常类型。 使用 Java 7 中的新特性,您还可以控制这些受抑制的异常。

用法示例如下:

public class SuppressedExceptionDemoWithTryFinallyNew
{
    /**
    * Executable member function demonstrating suppressed exceptions
    * Suppressed expression is added back in primary exception
    */
    public static void memberFunction() throws Exception
    {
        Throwable th = null;
        DirtyResource resource= new DirtyResource();
        try
        {
              resource.accessResource();
        }
        catch(Exception e)
        {
            th = e;
            throw e;
        }
        finally
        {
            try
            {
                resource.close();
            }
            catch(Exception e)
            {
                if(th != null)
                {
                    e.addSuppressed(th); //Add to primary exception
                    throw e;
                }
            }
        }
    }
   /**
    * Executable function demonstrating suppressed exceptions.
    */
   public static void main(String[] arguments) throws Exception
   {
      try
      {
          memberFunction();
      }
      catch(Exception ex)
      {
          err.println("Exception encountered: " + ex.toString());
          final Throwable[] suppressedExceptions = ex.getSuppressed();
          final int numSuppressed = suppressedExceptions.length;
          if (numSuppressed &gt; 0)
          {
              err.println("tThere are " + numSuppressed + " suppressed exceptions:");
              for (final Throwable exception : suppressedExceptions)
              {
                  err.println("tt" + exception.toString());
              }
          }
      }
   }
}

Output:

Exception encountered: java.lang.NullPointerException: Remember me. I am your worst nightmare !! I am Null pointer exception !!
    There are 1 suppressed exceptions:
        java.lang.RuntimeException: I wanted to access this resource. Bad luck. Its dirty resource !!!

阅读有关链接文章的更多信息。

catch块中捕获多个异常

在此特性中,现在您可以在单个catch块中捕获多个异常。 在 Java 7 之前,您只能捕获一个。 要指定期望的异常列表,请使用竖线(|)。

让我们来看一个例子。

try
{
       //Do some processing which throws NullPointerException; I am sending directly
       throw new NullPointerException();
}

//You can catch multiple exception added after 'pipe' character
catch(NullPointerException | IndexOutOfBoundsException ex)
{
       throw ex;
}

请记住:如果catch块处理多个异常类型,则catch参数隐式为final。 在此示例中,catch参数ex是最终的,因此您不能在catch块中为其分配任何值。

数字格式增强特性

如果必须读取数字“1000000”,那么在第一个站点中读取该数字有多方便。 不多吧? 我们习惯于以10,00,000格式读取数字。 好消息是 Java 已经开始支持以这种格式写数字。 好吧,不完全是这样,而是匹配的格式。

现在,您可以像上面这样写上面的数字:10_00_000。 足够好了,不是吗?

/**
 * Supported in int
 * */
int improvedInt = 10_00_000;
/**
 * Supported in float
 * */
float improvedFloat = 10_00_000f;
/**
 * Supported in long
 * */
float improvedLong = 10_00_000l;
/**
 * Supported in double
 * */
float improvedDouble = 10_00_000;

switch语句中的字符串类支持

如果您还记得 java 7 之前的switch语句,则它仅支持intenum类型。 现在,随着 Java 7 的发布,还添加了对String类的支持。 让我们来看一个例子。

switch (token)
{
	case ("one"):
		return "Token one identified";

	case ("two"):
		return "Token one identified";

	case ("three"):
		return "Token one identified";

	case ("four"):
		return "Token one identified";

	default:
		return "No token was identified";
}

前缀为0b的二进制字面值

在 JDK 7 中,您可以为整数类型(字节,短型,整型和长型)以前缀0b(或0B)以二进制形式表示字面值。 在 JDK 7 之前,您只能使用八进制值(前缀为0)或十六进制值(前缀为0x0X)。 例如:

int sameVarOne = 0b01010000101;

or if use the number formatting feature as well.

int sameVarTwo = 0B01_010_000_101;

ForkJoin 框架

在 Java 程序中有效使用并行内核一直是一个挑战。 很少有本地框架可以将工作分配到多个核心,然后将它们加入以返回结果集。 Java 7 已将此特性作为 Fork 和 Join 框架合并。

基本上,Fork-Join 将手头的任务分解为多个小任务,直到该小任务足够简单,可以将其解决而无需进一步拆分。 这就像分而治之的算法。 在此框架中要注意的一个重要概念是,理想情况下,没有工作线程处于空闲状态。 他们实现了一种工作窃取算法,即闲置的工作器从忙碌的工作器那里窃取了工作。

它基于 Java 并发性思想领袖 Doug Lea 的工作。 Fork/Join 处理线程的麻烦; 您只需要向框架指出可以分解并递归处理的部分。 它采用伪代码(摘自 Doug Lea 关于该主题的论文):

Result solve(Problem problem) {
	if (problem is small)
		directly solve problem
	else {
		split problem into independent parts
		fork new subtasks to solve each part
		join all subtasks
		compose result from subresults
	}
}

使用WatchService自动重新加载

每个应用程序都有一些配置,预期配置文件中的每次更改都会刷新该配置。 解决该问题的过去方法包括拥有一个线程,该线程根据配置文件的“最新更新时间戳”定期轮询文件更改。

现在使用 Java 7,情况已经改变。 Java 7 引入了一项出色的特性:WatchServiceWatchService是 JDK 的内部服务,监视注册对象的更改。 这些注册的对象必定是Watchable接口的实例。 向WatchService注册Watchable实例时,我们需要指定我们感兴趣的更改事件的类型。

链接的文章中提供了WatchService的示例用法。

G1 垃圾收集器

JDK 7 引入了一个称为 G1 的新垃圾收集器,它是垃圾的缩写形式。 G1 垃圾收集器在垃圾最多的地方执行清理。 为此,它将 Java 堆内存划分为多个区域,而不是 Java 7 版本之前的 3 个区域(新旧空间)。 G1 完全可以预测,并且可以为内存密集型应用程序提供更高的吞吐量。

这就是此快速摘要的全部内容。 您可以在此链接中找到特性的完整列表。

学习愉快!

Java 菱形运算符 – Java 中的<>运算符

原文: https://howtodoinjava.com/java7/improved-type-inference-in-java-7/

Java 7 之前,在使用泛型时,我们必须为变量类型及其实际类型提供类型参数。 现在,此新的 Java 7 特性已使它有所缓解。 声明右侧的空白 Java 菱形运算符可以正常工作。

菱形运算符用两个角度'< >'表示。

1. 在泛型之前 – 原始类型声明

如果您使用的是 Java 的早期版本(1.5 之前的版本),则当泛型不是 Java 特性时,开发人员必须使用原始类型声明和初始化。 例如,下面给出的是HashMap声明。

Map params = new HashMap();

这种方法的问题是,您可以将任何对象类型放入键和值中,并且只有在运行时,如果对象不是所需类型,您将得到错误。 没有可以警告开发人员的编译时安全性,即哪些类型允许,哪些类型不允许。

2. 泛型 – 参数化类型

JDK 1.5 带来了泛型。 它必须具有许多急需的特性,并且完全改变了开发人员编写代码的方式。 它启用了编译时安全性。 它有助于大量减少运行时错误。

Map<String, Integer> params = new HashMap<String, Integer>();

该语法解决了编译时类型安全性的问题。 实际上,以上语法几乎适用于所有用例。 在上面的示例中,如果尝试添加任何其他类型的键或值,则编译器将给您错误。

您需要修复代码才能通过编译器。

3. 菱形运算符

参数化类型可以解决问题,但由于双方重复使用相同的类型信息,因此看起来很繁琐。 如果我们可以在一侧提供类型信息,而另一侧可以检测并应用类型信息,则可以减少语法。

Java 中的菱形运算符执行完全相同的操作。 也称为 Elvis 运算符。 在下面查看菱形运算符语法

Map<String, Integer> params = new HashMap<>();

在上面的代码中,编译器足够聪明,可以识别菱形运算符推断声明左侧的类型。 它将类型信息也应用于右侧对象。 它有助于向 Java 添加类型推断特性。

4. 向后兼容性

为了向后兼容,原始类型和参数化类型仍然存在。 但是,新编译器会在看到原始类型时发出警告。 如果从 Java 5 开始编译原始类型,则会收到如下警告:

ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized

学习快乐!

阅读更多:

维基中的菱形运算符

带字符串的 Java switch case

原文: https://howtodoinjava.com/java7/strings-in-switch-statement/

Switch语句也早于 Java 7,但它仅支持intenum类型。 在 Java 7 发布之后,Switch语句也支持字符串类。

1. 带字符串的 Java switch case

switch case语句中使用字符串类的 Java 程序

public class StringSupportedInSwitch 
{
	public static void main(String[] args)
	{
		System.out.println(getExpendedMessage("one"));
		System.out.println(getExpendedMessage("three"));
		System.out.println(getExpendedMessage("five"));
	}

	static String getExpendedMessage(final String token) 
    {
        String value = null;

        switch (token) 
        {
            case ("one"):
                value = "Token one identified";
                break;

            case ("two"):
                value = "Token two identified";
                break;

            case ("three"):
                value = "Token three identified";
                break;

            case ("four"):
                value = "Token four identified";
                break;

            default:
                value = "No token was identified";
       }

        return value;
    }
}

程序输出。

Token one identified
Token three identified
No token was identified

2. Java switch case处理多个条件

有时,我们想对switch语句中的多个case执行某些操作。 在这种情况下,我们可以在单独的情况下写入每个值,并且只有在所有情况都写完之后,才写下应用程序逻辑。

例如,在给定程序中,所有奇数标记将由第一个切换条件处理,偶数标记将由第二个切换条件处理。

具有多个条件的switch case的 Java 示例。

public class StringSupportedInSwitch {

	public static void main(String[] args) 
	{
		System.out.println(getExpendedMessage("one"));
		System.out.println(getExpendedMessage("two"));
	}

	static String getExpendedMessage(final String token) 
	{
		String value = null;

		switch (token) 
		{
			case ("one"):
			case ("three"):
				value = "Odd token identified";
				break;

			case ("two"):
			case ("four"):
				value = "Even token identified";
				break;

			default:
				value = "No token was identified";
		}

		return value;
	}
}

程序输出:

Odd token identified
Even token identified

在此示例中,我们学习了将 Java switch语句与字符串结合使用。 Java 7 中引入了此特性

学习愉快!

Java 7 中的try-with-resources

原文: https://howtodoinjava.com/java7/try-with-resources/

Java 7 为懒惰的 Java 开发人员带来了一些非常好的特性。try-with-resource是这样的特性之一,它可以减少代码行,并使代码更健壮。 在本教程中,我将讨论有关此特性的内容。

java 7 features

Sections in this post: 
The old way of resource cleanup (Before java 7)
The new fancy way with try-with-resources (syntax example)
How actually it works?
Adding functionality to custom resources
Final notes

资源清除的旧方法(在 Java 7 之前)

我们长期以来一直在这样做。 例如从文件系统读取文件。 代码可能看起来有所不同,但流程如下例所示:

public class ResourceManagementBeforeJava7 
{
	public static void main(String[] args) 
	{
		BufferedReader br = null;
		try 
		{
			String sCurrentLine;
			br = new BufferedReader(new FileReader("C:/temp/test.txt"));
			while ((sCurrentLine = br.readLine()) != null) 
			{
				System.out.println(sCurrentLine);
			}
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
		finally 
		{
			try
			{
				if (br != null)
					br.close();
			} 
			catch (IOException ex) {
				ex.printStackTrace();
			}
		}
	}
}

这些类型的代码在具有大量 IO 操作的应用程序代码库中非常常见。

trycatch块中的代码本质上很重要,并且具有一些特定于应用程序的逻辑。 但是,finally块呢? 在大多​​数情况下,最后只是复制粘贴了finally块,目的是通过关闭它们来避免损坏资源。

当您有 3-4 个这样的资源要在单个finally块中关闭时,这些finally块看起来更难看。 当我们知道时,您是否认为这些finally块不必要地存在,我们必须以任何方式关闭资源而没有任何异常情况?

Java 7 通过try-with-resources特性解决了这个问题。

使用try-with-resources的新方法(语法示例)

现在看看在 Java 7 中打开和关闭资源的新方法。

public class ResourceManagementInJava7 
{
	public static void main(String[] args) 
	{
		try (BufferedReader br = new BufferedReader(new FileReader("C:/temp/test.txt")))
		{
			String sCurrentLine;
			while ((sCurrentLine = br.readLine()) != null) 
			{
				System.out.println(sCurrentLine);
			}
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

有两件事需要密切注意:

  1. 文件资源(BufferedReader)以特殊方式在try块中打开(在小括号内)。
  2. finally块完全消失了。

最后但并非最不重要的一点是,代码看起来很漂亮且易于阅读。 很好,对吗? 但是实际上是如何工作的?

实际上如何运作?

在 Java 7 中,我们有一个新的超接口java.lang.AutoCloseable。 此接口有一种方法:

void close() throws Exception;

Java 文档建议将此接口实现在不再需要它时必须关闭的任何资源上

当我们在特殊的try-with-resource块中打开任何此类AutoCloseable资源时,在try块完成后, JVM 会对在try()中初始化的所有资源调用此close()方法。

例如,BufferedReader已实现close()方法文件如下:

public void close() throws IOException {
	synchronized (lock) {
		if (in == null)
			return;
		in.close();
		in = null;
		cb = null;
	}
}

由于上述方法定义,当 JVM 调用此方法时,所有基础流或 IO 资源都将关闭。

向自定义资源添加功能

好吧,这是一个很好的资源清理设计。 但是它仅适用于 JDK 本机类吗? 没有。 您也可以将其用于自定义资源。

例如,我在以下代码中创建了一个自定义资源:

public class CustomResource implements AutoCloseable 
{
	public void accessResource() {
		System.out.println("Accessing the resource");
	}

	@Override
	public void close() throws Exception {
		System.out.println("CustomResource closed automatically");
	}
}

现在,我将在示例代码中使用它:

public class TryWithCustomResource 
{
	public static void main(String[] args)
	{
		try(CustomResource cr = new CustomResource())
		{
			cr.accessResource();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
}

Putput in console:

Accessing the resource
CustomResource closed automatically

控制台中的输出清楚地证明,try块完成后,资源将自动关闭。

最后注意事项

这就是 Java 7 中使用try-with-resources进行自动资源管理的全部内容。让我们逐点记下重点:

  • 在 Java 7 之前,我们必须使用finally块来清理资源。 finally块不是强制性的,但是清理资源是为了防止系统损坏。
  • 使用 Java 7,无需显式的资源清理。 它是自动完成的。
  • try-with-resources块(try(…){…})中初始化资源时完成自动资源清理。
  • 由于新接口AutoCloseable而发生清除。 try块完成后,JVM 将立即调用其close方法。
  • 如果要在自定义资源中使用此特性,则必须实现AutoCloseable接口。 否则程序将无法编译。
  • 您不应在代码中调用close()方法。 这应该自动称为 JVM。 手动调用它可能会导致意外结果。

祝您学习愉快!

Java 7 中数字字面值的下划线

原文: https://howtodoinjava.com/java7/improved-formatted-numbers-in-java-7/

Java 7 通过允许下划线数字,以非常令人愉快的方式改进了数字字面值格式。 它使读取数字变得容易,并且当我们在应用程序日志中编写数字字面值时,它们看起来不错。

例如,如果我们必须读取数字“1000000”,那么它乍看之下有多方便。 不多吧?

我们有一种阅读10,00,000格式的数字的习惯。 好消息是 Java 已经开始支持以这种格式写数字。 好吧,不完全是这样,而是匹配的格式。

1. 数字字面值下划线 – 示例

在 Java 7 发布之后,现在我们可以像上面这样写上面的数字:10_00_000。 足够好了,不是!

下面给出了带有下划线的新格式数字的 Java 程序。

public class ImprovedNumbersInJava7
{
	@SuppressWarnings("unused")
	public static void main(String[] args)
	{
		/**
		 * Supported in int
		 * */
		int improvedInt = 10_00_000;

		/**
		 * Supported in float
		 * */
		float improvedFloat = 10_00_000f;

		/**
		 * Supported in long
		 * */
		float improvedLong = 10_00_000l;

		/**
		 * Supported in double
		 * */
		float improvedDouble = 10_00_000;
	}
}

2. 数字字面值下划线 – 注释

  1. 下划线只能放在数字之间。 我们不能在小数点“.”旁边加上下划线。
  2. 允许连续的下划线。 10___00是有效数字。
  3. 不允许在数字的最后加上下划线。1000_不是有效数字。 它将产生编译时错误。
  4. 在 Java 中,允许在变量名称之前加下划线。 当您在数字开头加上下划线时,请当心。 它是一个变量,而不是数字。

学习愉快!

阅读更多:

Java 文档

Java 抑制异常示例

原文: https://howtodoinjava.com/java7/java-suppressed-exceptions/

顾名思义, 被抑制的异常是在代码中引发的异常,但是以某种方式被忽略了。 如果您记得try-catch-finally块的执行顺序以及它们如何返回任何值或异常,那么您会记得,如果try中引发了异常,则也将抑制finally块中引发的异常

在 Java 7 之前的版本中,您通过记录日志了解了这些异常(如果已实现),但是一旦finally块结束,您就无法控制这些类型的异常。

好吧,借助 Java 7 中的新特性,您还可以控制这些受抑制的异常。

Table of contents

1\. What are suppressed exceptions?
2\. Suppressed exception example
3\. Demonstration in different scenarios

1. 什么是禁止的异常?

在 Java 7 中,遇到抑制异常的最常见用例是 try-with-resources 语句。 当我们在try块中遇到异常时,应用程序将尝试关闭资源。 如果它遇到关闭AutoCloseable资源时可能发生的多个异常,则会将其他异常作为抑制的异常附加到主要异常上。

为了支持抑制的异常,在 JDK 7 中向Throwable类(ExceptionError类的父级)添加了新的构造器和两个新方法。

Throwable.getSupressed(); // Returns Throwable[]
Throwable.addSupressed(aThrowable);

2. 抑制异常的例子

例如,在写入输出流时,可以从try块引发一个异常,当try-with-resources语句尝试关闭流时,可以从该异常中引发最多两个异常。

如果从try块引发一个异常,并且从try-with-resources语句引发一个或多个异常,则将从try-with-resources语句引发的那些异常抑制,并且由该块引发的异常由closeStream()方法抛出。

您可以通过从try块引发的异常中调用Throwable.getSuppressed()方法来检索这些受抑制的异常。

3. 在不同情况下的示例

例如,我正在编写一个自动关闭的资源(即DirtyResource.java),无论我们尝试访问它还是关闭它,它都会引发异常。 这样,当以不同方式访问时,我们将能够看到不同的行为。

public class DirtyResource implements AutoCloseable
{
	/**
	 * Need to call this method if you want to access this resource
	 * @throws RuntimeException no matter how you call this method
	 * */
	public void accessResource()
	{
		throw new RuntimeException("I wanted to access this resource. Bad luck. Its dirty resource !!!");
	}

	/**
	 * The overridden closure method from AutoCloseable interface
	 * @throws Exception which is thrown during closure of this dirty resource
	 * */
	@Override
	public void close() throws Exception
	{
		throw new NullPointerException("Remember me. I am your worst nightmare !! I am Null pointer exception !!");
	}
}

3.1 抑制异常之前的特性

package com.howtodoinjava.demo.core;

import static java.lang.System.err;

public class SuppressedExceptionDemoWithTryFinallyPrevious
{
	/**
    * Executable member function demonstrating suppressed exceptions
    * One exception is lost if not added in suppressed exceptions list
    */
	public static void memberFunction() throws Exception
	{
		DirtyResource resource= new DirtyResource();
		try
	    {
	    	  resource.accessResource();
	    }
		finally
		{
			resource.close();
		}
	}

	public static void main(String[] arguments) throws Exception
   {
      try
      {
    	  memberFunction();
      }
      catch(Exception ex)
      {
    	  err.println("Exception encountered: " + ex.toString());
    	  final Throwable[] suppressedExceptions = ex.getSuppressed();
    	  final int numSuppressed = suppressedExceptions.length;
    	  if (numSuppressed > 0)
    	  {
    		  err.println("tThere are " + numSuppressed + " suppressed exceptions:");
	    	  for (final Throwable exception : suppressedExceptions)
	    	  {
	    		  err.println("tt" + exception.toString());
	    	  }
    	  }
      }
   }
}

Output:

Exception encountered: java.lang.NullPointerException: Remember me. I am your worst nightmare !! I am Null pointer exception !!

如您所见,仅捕获了一个异常,而第二个RuntimeException被抑制。

3.2 在 Java 7 中抑制异常之后

package com.howtodoinjava.demo.core;

import static java.lang.System.err;

public class SuppressedExceptionDemoWithTryFinallyNew
{
	/**
    * Executable member function demonstrating suppressed exceptions
    * Suppressed expression is added back in primary exception
    */
	public static void memberFunction() throws Exception
	{
		Throwable th = null;
		DirtyResource resource= new DirtyResource();
		try
	    {
	    	  resource.accessResource();
	    }
		catch(Exception e)
		{
			th = e;
		}
		finally
		{
			try
			{
				resource.close();
			}
			catch(Exception e)
			{
				if(th != null)
				{
					e.addSuppressed(th); //Add to primary exception
					throw e;
				}
			}
		}
	}
   /**
    * Executable function demonstrating suppressed exceptions.
    */
   public static void main(String[] arguments) throws Exception
   {
      try
      {
    	  memberFunction();
      }
      catch(Exception ex)
      {
    	  err.println("Exception encountered: " + ex.toString());
    	  final Throwable[] suppressedExceptions = ex.getSuppressed();
    	  final int numSuppressed = suppressedExceptions.length;
    	  if (numSuppressed > 0)
    	  {
    		  err.println("tThere are " + numSuppressed + " suppressed exceptions:");
	    	  for (final Throwable exception : suppressedExceptions)
	    	  {
	    		  err.println("tt" + exception.toString());
	    	  }
    	  }
      }
   }
}

Output:

Exception encountered: java.lang.NullPointerException: Remember me. I am your worst nightmare !! I am Null pointer exception !!
	There are 1 suppressed exceptions:
		java.lang.RuntimeException: I wanted to access this resource. Bad luck. Its dirty resource !!!

在这里,在catch块中,我们可以访问两个异常。 一个作为主要异常,第二个作为受抑制异常。

3.3 成员函数中的try-with-resource块并捕获异常

package com.howtodoinjava.demo.core;

import static java.lang.System.err;

public class SuppressedExceptionDemoWithTryCatch
{
	public static void memberFunction() throws Exception
	{
		try (DirtyResource resource= new DirtyResource())
	      {
	    	  resource.accessResource();
	      }
	}
   /**
    * Executable member function demonstrating suppressed exceptions using try-with-resources
    */
   public static void main(String[] arguments) throws Exception
   {
      try
      {
    	  memberFunction();
      }
      catch(Exception ex)
      {
    	  err.println("Exception encountered: " + ex.toString());
    	  final Throwable[] suppressedExceptions = ex.getSuppressed();
    	  final int numSuppressed = suppressedExceptions.length;
    	  if (numSuppressed > 0)
    	  {
    		  err.println("tThere are " + numSuppressed + " suppressed exceptions:");
	    	  for (final Throwable exception : suppressedExceptions)
	    	  {
	    		  err.println("tt" + exception.toString());
	    	  }
    	  }
      }
   }
}

Output:

Exception encountered: java.lang.RuntimeException: I wanted to access this resource. Bad luck. Its dirty resource !!!
	There are 1 suppressed exceptions:
		java.lang.NullPointerException: Remember me. I am your worst nightmare !! I am Null pointer exception !!

太好了! 在成员函数中使用try-with-resource时,我们能够看到这两种异常。

3.4 默认try-with-resource

package com.howtodoinjava.demo.core;

public class SuppressedExceptionDemoWithTryWithResource
{
   /**
    * Demonstrating suppressed exceptions using try-with-resources
    */
   public static void main(String[] arguments) throws Exception
   {
      try (DirtyResource resource= new DirtyResource())
      {
    	  resource.accessResource();
      }
   }
}

Output:

Exception in thread "main" java.lang.RuntimeException: I wanted to access this resource. Bad luck. Its dirty resource !!!
	at DirtyResource.accessResource(DirtyResource.java:9)
	at SuppressedExceptionDemoWithTryWithResource.main(SuppressedExceptionDemoWithTryWithResource.java:12)
	Suppressed: java.lang.NullPointerException: Remember me. I am your worst nightmare !! I am Null pointer exception !!
		at DirtyResource.close(DirtyResource.java:19)
		at SuppressedExceptionDemoWithTryWithResource.main(SuppressedExceptionDemoWithTryWithResource.java:13)

好吧,非常高兴看到包含抑制异常的完整信息的输出。

学习愉快!

Java 7 – 异常处理增强

原文: https://howtodoinjava.com/java7/improved-exception-handling/

在 Java 7 发行版中,oracle 在异常处理机制上也做了一些不错的更改。 主要是改进的 catch 块冗余throws子句。 让我们看看他们是如何改变的。

1. Java 7 中改进的catch

在此特性中,现在您可以在单个catch中捕获多个异常。 在 Java 7 之前,您只能在每个catch块中仅捕获一个异常。

要指定期望的异常列表,请使用竖线(|)。

Java 程序在单个 catch 块中捕获多个异常。

try
{
    //Do some processing which throws NullPointerException;
    throw new NullPointerException();
}
//You can catch multiple exception added after 'pipe' character
catch( NullPointerException npe | IndexOutOfBoundsException iobe )
{
       throw ex;
}

如果catch块处理多个异常类型,则 catch参数隐式为final 。 在此示例中,catch参数exfinal,因此您无法在catch块内为其分配任何值。

2. Java 7 中的冗余throws子句

此特性使您免于在方法声明中使用throws子句。 请参见下面的示例:

public class MultipleExceptionsInCatchBlock {

	public static void main(String[] args)
	{
			sampleMethod();
	}

	public static void sampleMethod()
					//throws Throwable	//No need to do this
	{
		try
		{
			//Do some processing which throws NullPointerException; I am sending directly
			throw new NullPointerException();
		}
		//You can catch multiple exception added after 'pipe' character
		catch(NullPointerException | IndexOutOfBoundsException ex)
		{
			throw ex;
		}
		//Now method sampleMethod() do not need to have 'throws' clause
		catch(Throwable ex)
		{
			throw ex;
		}
	}
}

Java 运行时足够智能,可以在调用方方法中标识确切的异常类。

如果您使用 Java 6 或更早的版本编译上述代码,它将产生编译错误并要求您声明 throws 子句。

我认为,对于 Java 开发人员而言,首先的改进绝对是一个不错的选择。 但是,我真的没有看到第二个补充如此重要。 再次,这是我的看法。

学习愉快!

Fork/Join 框架教程:ForkJoinPool示例

原文: https://howtodoinjava.com/java7/forkjoin-framework-tutorial-forkjoinpool-example/

在 Java 程序中有效使用并行内核一直是一个挑战。 很少有本地框架可以将工作分配到多个核心,然后将它们加入以返回结果集。 Java 7 已将此特性作为 Fork 和 Join 框架合并。

基本上, Fork-Join 将手头的任务分解为多个微型任务,直到微型任务足够简单,可以解决而无需进一步分解。 就像分而治之算法一样。 在此框架中要注意的一个重要概念是,理想情况下,没有工作线程处于空闲状态。 他们实现了工作窃取算法,因为空闲工作器steal从忙碌的工作器那里进行工作。

Fork Join Framework

ForkJoin 框架

它基于 Java 并发性思想领袖 Doug Lea 的工作。 Fork/Join 处理线程的麻烦; 您只需要向框架指出可以分解并递归处理的部分。 它采用伪代码(摘自 Doug Lea 关于该主题的论文):

Result solve(Problem problem) {
	if (problem is small)
		directly solve problem
	else {
		split problem into independent parts
		fork new subtasks to solve each part
		join all subtasks
		compose result from subresults
	}
}
Discussion Points

1) Core Classes used in Fork/Join Framework
    i)  ForkJoinPool
    ii) ForkJoinTask
2) Example Implementations of Fork/Join Pool Framework
    i)  Implementation Sourcecode
    ii) How it works?
3) Difference between Fork/Join Framework And ExecutorService
4) Existing Implementations in JDK
5) Conclusion

Fork/Join 框架中使用的核心类

支持 Fork-Join 机制的核心类是ForkJoinPoolForkJoinTask

让我们详细了解他们的角色。

ForkJoinPool

ForkJoinPool基本上是ExecutorService的一种特殊实现,用于实现我们上面讨论的窃取算法。 我们通过提供目标并行度,即处理器的数量,来创建ForkJoinPool的实例,如下所示:

ForkJoinPool pool = new ForkJoinPool(numberOfProcessors);

Where numberOfProcessors = Runtime.getRunTime().availableProcessors();

如果使用无参数构造函数,则默认情况下,它将创建一个大小等于使用上述技术获得的可用处理器数量的大小的池。

尽管您指定了任何初始池大小,但池会动态调整其大小,以尝试在任何给定时间点维护足够的活动线程。 与其他ExecutorService相比,另一个重要区别是该程序池无需在程序退出时显式关闭,因为其所有线程均处于守护程序模式。

ForkJoinPool提交任务的方式有三种。

1)execute()方法:所需的异步执行; 调用其fork方法在多个线程之间分配工作。

2)invoke()方法:等待获得结果; 在池上调用invoke方法。

3)Submit()方法:返回一个Future对象,可用于检查状态并在完成时获取结果。

ForkJoinTask

这是用于创建在ForkJoinPool中运行的任务的抽象类。 RecursiveactionRecursiveTaskForkJoinTask的仅有的两个直接的已知子类。 这两类之间的唯一区别是RecursiveAction不返回值,而RecursiveTask确实具有返回值并返回指定类型的对象。

在这两种情况下,您都需要在子类中实现compute方法,该方法执行任务所需的主要计算。

ForkJoinTask类提供了几种检查任务执行状态的方法。 如果任务以任何方式完成,则isDone()方法返回true。 如果任务未取消就完成或没有遇到异常,则isCompletedNormally()方法返回true;如果任务被取消,则isCancelled()返回true。 最后,如果任务被取消或遇到异常,则isCompletedabnormally()返回true

ForkJoinPool框架的示例实现

在此示例中,您将学习如何使用ForkJoinPoolForkJoinTask类提供的异步方法来管理任务。 您将实现程序,该程序将在文件夹及其子文件夹中搜索具有确定扩展名的文件。 您将要实现的ForkJoinTask类将处理文件夹的内容。 对于该文件夹中的每个子文件夹,它将以异步方式将新任务发送到ForkJoinPool类。 对于该文件夹中的每个文件,任务将检查文件的扩展名并将其继续添加到结果列表中。

上述问题的解决方案在FolderProcessor类中实现,如下所示:

实现的源代码

FolderProcessor.java

package forkJoinDemoAsyncExample;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;

public class FolderProcessor extends RecursiveTask<List<String>>
{
   private static final long serialVersionUID = 1L;
   //This attribute will store the full path of the folder this task is going to process.
   private final String      path;
   //This attribute will store the name of the extension of the files this task is going to look for.
   private final String      extension;

   //Implement the constructor of the class to initialize its attributes
   public FolderProcessor(String path, String extension)
   {
      this.path = path;
      this.extension = extension;
   }

   //Implement the compute() method. As you parameterized the RecursiveTask class with the List<String> type, 
   //this method has to return an object of that type.
   @Override
   protected List<String> compute()
   {
      //List to store the names of the files stored in the folder.
      List<String> list = new ArrayList<String>();
      //FolderProcessor tasks to store the subtasks that are going to process the subfolders stored in the folder
      List<FolderProcessor> tasks = new ArrayList<FolderProcessor>();
      //Get the content of the folder.
      File file = new File(path);
      File content[] = file.listFiles();
      //For each element in the folder, if there is a subfolder, create a new FolderProcessor object 
      //and execute it asynchronously using the fork() method.
      if (content != null)
      {
         for (int i = 0; i < content.length; i++)
         {
            if (content[i].isDirectory())
            {
               FolderProcessor task = new FolderProcessor(content[i].getAbsolutePath(), extension);
               task.fork();
               tasks.add(task);
            }
            //Otherwise, compare the extension of the file with the extension you are looking for using the checkFile() method 
            //and, if they are equal, store the full path of the file in the list of strings declared earlier.
            else
            {
               if (checkFile(content[i].getName()))
               {
                  list.add(content[i].getAbsolutePath());
               }
            }
         }
      }
      //If the list of the FolderProcessor subtasks has more than 50 elements, 
      //write a message to the console to indicate this circumstance.
      if (tasks.size() > 50)
      {
         System.out.printf("%s: %d tasks ran.\n", file.getAbsolutePath(), tasks.size());
      }
      //add to the list of files the results returned by the subtasks launched by this task.
      addResultsFromTasks(list, tasks);
      //Return the list of strings
      return list;
   }

   //For each task stored in the list of tasks, call the join() method that will wait for its finalization and then will return the result of the task. 
   //Add that result to the list of strings using the addAll() method.
   private void addResultsFromTasks(List<String> list, List<FolderProcessor> tasks)
   {
      for (FolderProcessor item : tasks)
      {
         list.addAll(item.join());
      }
   }

   //This method compares if the name of a file passed as a parameter ends with the extension you are looking for.
   private boolean checkFile(String name)
   {
      return name.endsWith(extension);
   }
}

并在FolderProcessor以上使用,请遵循以下代码:

Main.java

package forkJoinDemoAsyncExample;

import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

public class Main
{
   public static void main(String[] args)
   {
      //Create ForkJoinPool using the default constructor.
      ForkJoinPool pool = new ForkJoinPool();
      //Create three FolderProcessor tasks. Initialize each one with a different folder path.
      FolderProcessor system = new FolderProcessor("C:\\Windows", "log");
      FolderProcessor apps = new FolderProcessor("C:\\Program Files", "log");
      FolderProcessor documents = new FolderProcessor("C:\\Documents And Settings", "log");
      //Execute the three tasks in the pool using the execute() method.
      pool.execute(system);
      pool.execute(apps);
      pool.execute(documents);
      //Write to the console information about the status of the pool every second 
      //until the three tasks have finished their execution.
      do
      {
         System.out.printf("******************************************\n");
         System.out.printf("Main: Parallelism: %d\n", pool.getParallelism());
         System.out.printf("Main: Active Threads: %d\n", pool.getActiveThreadCount());
         System.out.printf("Main: Task Count: %d\n", pool.getQueuedTaskCount());
         System.out.printf("Main: Steal Count: %d\n", pool.getStealCount());
         System.out.printf("******************************************\n");
         try
         {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e)
         {
            e.printStackTrace();
         }
      } while ((!system.isDone()) || (!apps.isDone()) || (!documents.isDone()));
      //Shut down ForkJoinPool using the shutdown() method.
      pool.shutdown();
      //Write the number of results generated by each task to the console.
      List<String> results;
      results = system.join();
      System.out.printf("System: %d files found.\n", results.size());
      results = apps.join();
      System.out.printf("Apps: %d files found.\n", results.size());
      results = documents.join();
      System.out.printf("Documents: %d files found.\n", results.size());
   }
}

上面程序的输出将如下所示:

Main: Parallelism: 2
Main: Active Threads: 3
Main: Task Count: 1403
Main: Steal Count: 5551
******************************************
******************************************
Main: Parallelism: 2
Main: Active Threads: 3
Main: Task Count: 586
Main: Steal Count: 5551
******************************************
System: 337 files found.
Apps: 10 files found.
Documents: 0 files found.

怎么运行的?

FolderProcessor类中,每个任务都处理文件夹的内容。 如您所知,此内容包含以下两种元素:

  • 档案
  • 其他文件夹

如果任务找到文件夹,它将创建另一个Task对象来处理该文件夹,并使用fork()方法将其发送到池中。 如果该任务具有空闲的工作线程或可以创建新的工作线程,则此方法会将任务发送到执行该任务的池。 方法将立即返回,因此任务可以继续处理文件夹的内容。 对于每个文件,任务都会将其扩展名与要查找的扩展名进行比较,如果它们相等,则将文件名添加到结果列表中。

任务处理完分配的文件夹的所有内容后,它将等待使用join()方法完成发送给池的所有任务的完成。 在任务中调用的此方法等待其执行完成,并返回compute()方法返回的值。 该任务将其发送的所有任务的结果与自己的结果分组,并将该列表作为compute()方法的返回值返回。

Fork/Join 框架和ExecutorService之间的区别

Fork/Join 和Executor框架之间的主要区别是工作窃取算法。 与Executor框架不同,当任务正在等待使用 join 操作创建的子任务完成时,正在执行该任务的线程(称为工作器线程)将寻找尚未执行的其他任务并开始执行它。 通过这种方式,线程可以充分利用其运行时间,从而提高了应用程序的性能。

JDK 中的现有实现

Java SE 中有一些通常有用的特性,已经使用 Fork/Join 框架实现了。

1)Java SE 8 中引入的一种此类实现由java.util.Arrays类用于其parallelSort()方法。 这些方法类似于sort(),但是通过 Fork/Join 框架利用并发性。 在多处理器系统上运行时,大型数组的并行排序比顺序排序要快。

2)在Stream.parallel()中使用的并行性。 阅读有关 Java 8 中此并行流操作的更多信息

总结

设计好的多线程算法很困难,并且Fork/Join并非在每种情况下都有效。 它在其自身的适用范围内非常有用,但是最后,您必须确定您的问题是否适合该框架,否则,您必须准备在java.util.concurrent包提供的一流工具基础上开发自己的解决方案

参考

http://gee.cs.oswego.edu/dl/papers/fj.pdf

http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

http://www.packtpub.com/java-7-concurrency-cookbook/book

祝您学习愉快!

自动重新加载属性的 Java WatchService示例

原文: https://howtodoinjava.com/java7/auto-reload-of-configuration-when-any-change-happen/

每当配置文件中发生任何更改时,都会自动刷新这些文件 – 这是大多数应用程序中常见的一个常见问题。 每个应用程序都有一些配置,预期该配置文件中的每次更改都会刷新。 解决该问题的过去方法包括使用Thread,它根据配置文件的最后更新时间戳定期轮询文件更改。

现在使用 Java 7,情况已经改变。 Java 7 引入了一项出色的特性:WatchService。 我将尽力为您解决上述问题。 这可能不是最好的实现,但是肯定会为您的解决方案提供一个很好的开始。 我敢打赌!

Table of Contents:

1) A brief overview of WatchService
2) Writing our configuration provider
3) Introducing configuration change listener
4) Testing our code
5) Key notes

1. Java WatchService API

WatchService是 JDK 的内部服务,监视注册对象的更改。 这些注册的对象必定是Watchable接口的实例。 在WatchService中注册Watchable实例时,我们需要指定我们感兴趣的变更事件的类型。

到目前为止,有四种类型的事件:

  1. ENTRY_CREATE
  2. ENTRY_DELETE
  3. ENTRY_MODIFY
  4. OVERFLOW

您可以在提供的链接中了解这些事件。

WatchService接口扩展了Closeable接口,表示可以根据需要关闭服务。 通常,应该使用 JVM 提供的关闭挂钩来完成。

2. 应用程序配置供应器

配置供应器只是用于包装java.util.Properties实例中的属性集的包装器。 它还提供了使用来获取已配置属性的方法。

package testWatchService;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class ApplicationConfiguration {
	private final static ApplicationConfiguration INSTANCE = new ApplicationConfiguration();

	public static ApplicationConfiguration getInstance() {
		return INSTANCE;
	}

	private static Properties configuration = new Properties();

	private static Properties getConfiguration() {
		return configuration;
	}

	public void initilize(final String file) {
		InputStream in = null;
		try {
			in = new FileInputStream(new File(file));
			configuration.load(in);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public String getConfiguration(final String key) {
		return (String) getConfiguration().get(key);
	}

	public String getConfigurationWithDefaultValue(final String key,
			final String defaultValue) {
		return (String) getConfiguration().getProperty(key, defaultValue);
	}
}

3. 配置更改监听器 – 文件监视器

现在,当我们有了配置属性的内存中缓存的基本包装器时,我们需要一种机制,只要存储在文件系统中的配置文件发生更改,就可以在运行时重新加载此缓存。

我已经写了一个示例工作代码来为您提供帮助:


package testWatchService;

import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class ConfigurationChangeListner implements Runnable {
	private String configFileName = null;
	private String fullFilePath = null;

	public ConfigurationChangeListner(final String filePath) {
		this.fullFilePath = filePath;
	}

	public void run() {
		try {
			register(this.fullFilePath);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void register(final String file) throws IOException {
		final int lastIndex = file.lastIndexOf("/");
		String dirPath = file.substring(0, lastIndex + 1);
		String fileName = file.substring(lastIndex + 1, file.length());
		this.configFileName = fileName;

		configurationChanged(file);
		startWatcher(dirPath, fileName);
	}

	private void startWatcher(String dirPath, String file) throws IOException {
		final WatchService watchService = FileSystems.getDefault()
				.newWatchService();
		Path path = Paths.get(dirPath);
		path.register(watchService, ENTRY_MODIFY);

		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				try {
					watchService.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});

		WatchKey key = null;
		while (true) {
			try {
				key = watchService.take();
				for (WatchEvent<?> event : key.pollEvents()) {
					if (event.context().toString().equals(configFileName)) {
						configurationChanged(dirPath + file);
					}
				}
				boolean reset = key.reset();
				if (!reset) {
					System.out.println("Could not reset the watch key.");
					break;
				}
			} catch (Exception e) {
				System.out.println("InterruptedException: " + e.getMessage());
			}
		}
	}

	public void configurationChanged(final String file) {
		System.out.println("Refreshing the configuration.");
		ApplicationConfiguration.getInstance().initilize(file);
	}
}

上面的类是使用线程创建的,该线程将使用WatchService监听配置属性文件的更改。

一旦检测到文件中的任何修改,它便会刷新配置中的内存缓存。

上述监听器的构造器仅采用一个参数,即受监视的配置文件的标准路径。 在文件系统中更改配置文件时,会立即通知Listener类。

然后,此监听器类调用ApplicationConfiguration.getInstance().initilize(file);以重新加载到内存缓存中。

4. 测试我们的代码

现在,当我们准备好类时,我们将对其进行测试。

首先,将test.properties文件及其以下内容存储在'C:/Lokesh/temp'文件夹中。

TEST_KEY=TEST_VALUE

现在,让我们使用以下代码测试以上类。

package testWatchService;

public class ConfigChangeTest {
	private static final String FILE_PATH = "C:/Lokesh/temp/test.properties";

	public static void main(String[] args) {
		ConfigurationChangeListner listner = new ConfigurationChangeListner(
				FILE_PATH);
		try {
			new Thread(listner).start();
			while (true) {
				Thread.sleep(2000l);
				System.out.println(ApplicationConfiguration.getInstance()
						.getConfiguration("TEST_KEY"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

//Output of the above program (Change the TEST_VALUE to TEST_VALUE1 and TEST_VALUE2 using any file editor and save).

Refreshing the configuration.

TEST_VALUE

TEST_VALUE

TEST_VALUE

Refreshing the configuration.

TEST_VALUE1

Refreshing the configuration.

TEST_VALUE2

以上输出表明,每次我们对属性文件进行任何更改时,刷新的属性都会刷新,并且可以使用新的属性值。 到目前为止,已经做好了!

5. 重要说明

  1. 如果在新项目中使用 Java 7,并且没有在使用老式方法重新加载属性,则说明操作不正确。

  2. WatchService提供了两个方法take()poll()。当take()方法等待下一次更改发生并被阻止之前,poll()立即检查更改事件。

    如果上次poll()调用没有任何变化,它将返回 nullpoll()方法不会阻止执行,因此应在具有一些睡眠时间的线程中调用。

将我的问题/建议放在评论区。

学习愉快!


  1. \t ↩︎

posted @ 2024-10-24 18:13  绝不原创的飞龙  阅读(15)  评论(0编辑  收藏  举报