HowToDoInJava-Java-教程-一-

HowToDoInJava Java 教程(一)

原文:HowToDoInJava

协议:CC BY-NC-SA 4.0

Java 中的数据类型

原文: https://howtodoinjava.com/java/basics/data-types-in-java/

了解 Java 数据类型基本数据类型非基本数据类型(或引用数据类型)之间的差异。 知道数据类型大小和最佳实践以在 Java 中使用数据类型。

1. 什么是数据类型

在 Java 中,通常数据类型与变量关联。 变量具有三个属性:

  1. 变量名(也称为标识符),用于引用存储器位置
  2. 存储在存储位置的数据的类型(称为数据类型)
  3. 存储器位置以保存该值

Java Data Type

Java 数据类型

第二个属性(标记为红色)称为数据类型。 变量的数据类型决定了存储位置可以保存的值的范围。 因此,为变量分配的内存量取决于其数据类型。 例如,为'int'数据类型的变量分配了 32 位内存。

Java 是一种静态类型的语言。 这意味着必须先声明所有变量,然后才能使用

boolean flag = true;

int counter = 20;

2. Java 中的数据类型

Java 支持两种数据类型,即基本数据类型非基本引用数据类型

2.1 原始数据类型

直接的原始数据类型在内存中保存一个值。 例如,数字或字符。 原始数据类型不是
对象,也不是对对象的引用。

存储在原始类型中的值称为字面值字面值是固定值的源代码表示; 字面值直接在您的代码中表示,无需计算。

在 Java 中,我们有 8 种原始数据类型。

数据类型 描述 默认值 内存大小
boolean truefalse的二元值 false 1 位
char 任何 Unicode 字符 \u0000(0) 16 位 Unicode 字符
byte 值从 -128 到 127 0 8 位有符号值
short 值从 -32768 到 32767 0 16 位有符号值
int 值从-2 ^ 312 ^ 31 -1 0 32 位有符号值
long 值从-2 ^ 632 ^ 63 -1 0 64 位有符号值
float IEEE 754 浮点 0.0 32 位浮点值
double IEEE 754 浮点 0.0 64 位浮点值

在 Java SE 7 和更高版本中,任何数量的下划线字符('_')都可以出现在数字字面值中数字之间的任何位置。 例如 10_000_000是 Java 中的有效数字。

2.1.1 原始类型之间的类型转换

boolean之外,您可以将一个原始值分配给另一个原始类型。 但是,当将较大存储容量的原始类型分配给具有较小存储容量的原始类型时,有时可能会导致数据丢失。 就像您要从大容器中转移水,然后将小容器中的水转​​移一样,所以自然而然地就会流失水。

int counter = 20_000_000;

//Assign int to short (data loss)
short shortCounter = (short) counter;

//assign int to long (no data loss)
long longCounter = counter;

System.out.println(counter);            //20000000
System.out.println(shortCounter);       //11520
System.out.println(longCounter);        //20000000

请注意,当 Java 检测到类型转换可能会导致数据丢失(较大的数据类型转换为较小的数据类型)时,会给出类型不匹配错误,并明确要求类型转换(例如intshort赋值)。 它有助于检测和解决意外的数据丢失。

2.2 非原始数据类型

非原始或引用数据类型将对对象的引用保存在内存中。 使用存储在变量中的引用,您可以访问引用对象的字段和方法。

例如,java.lang.String是 Java 库中定义的类,您可以使用它来处理文本(字符序列)。 您将类型为String的引用变量声明为:

String str = new String( "Hello World !!" );

执行此代码会怎样? 首先,分配一个存储块,并将变量str的名称与该存储位置关联。 此过程与声明原始数据类型变量的过程相同。

代码的第二部分在内存中使用文本"Hi"创建一个新的String对象,并将String对象的引用(或内存地址)存储到变量'str'中。

2.2.1 多个引用可以引用同一对象

您还可以将存储在一个引用变量中的对象的引用分配给另一引用变量。 在这种情况下,两个引用变量都将引用内存中的同一对象。

// Declares String reference variable str1 and str2
String str1;
String str2;

// Assigns the reference of a String object "Hello" to str1
str1 = new String( "Hello World !!" );

// Assigns the reference stored in str1 to str2
str2 = str1;

System.out.println( str1 );         //Hello World !!
System.out.println( str2 );         //Hello World !!

有一个引用常量(也称为引用字面值)null,可以将其分配给任何参考变量。 如果将null分配给引用变量,则意味着该引用变量没有引用内存中的任何对象。

2.2.2 包装类

包装类是其对象包装或包含原始数据类型的类。 换句话说,我们可以将原始值包装到包装类对象中。

请注意,Java 有一个包装类,映射到每种原始数据类型。 例如,java.lang.Integer类是int数据类型的对象版本。 同样,对于所有 8 种基本数据类型,我们总共有 8 个包装器类。

包装器类名称与原始数据类型名称相同,仅以大写字母开头。

这些包装器类别是BooleanByteShortCharacterIntegerLongFloatDouble

2.2.3 自动装箱

在 Java 中,可以直接将原始类型值分配给包装器类。 例如,您可以将int值分配给Interger类引用。

Integer counter = 20;

static Float PI = 3.14f;

值得一提的是,所有包装器类实例都是不可变的。 由于性能原因,它们还维护内部缓存

3. 原始和非原始数据类型之间的区别

  1. 原始类型直接存储值,称为字面值。 引用类型将对实际对象的引用存储在存储区中。
  2. 有 8 种固定的原始数据类型。 在 Java 中,每个类都是包含包装器类的数据类型。

4. 最佳做法

  1. 使用 Java 变量命名约定并遵循最佳实践。
  2. 将原始类型用于范围内局部变量。 例如内部方法,用于循环和中间结果的计数器。
  3. 在方法或类之间传输数据时,最好使用对象,因为只会复制它们的引用,而不会增加内存开销。
  4. 在处理集合(需要对象)时,应使用对象。
  5. 通过网络发送数据时,请使用对象并使它们成为可序列化的。 包装器类可自动序列化。
  6. 始终知道您将需要的数据类型的大小。 使用适当的数据大小。 使用int存储boolean值(0 和 1)会浪费内存。
  7. 在数字中使用下划线(Java 7 以上)。 这使它们更具可读性

学习愉快!

阅读更多:

SO 帖子

Oracle 文档

面向对象原则

Java OOP 概念 – 面向对象的原则

原文: https://howtodoinjava.com/oops/object-oriented-principles/

在本 Java OOP 概念教程中,我们将学习四个主要的面向对象原理抽象封装继承多态。 它们也被称为面向对象编程范式的四个支柱。

  1. 抽象是公开实体基本细节的过程,同时忽略了无关紧要的细节,从而为用户降低了复杂性。
  2. 封装是将数据和对数据的操作捆绑在一起的过程。
  3. 继承用于从现有类型派生新类型,从而建立父子关系。
  4. 多态使实体在不同的上下文中具有不同的含义。
Table of Contents

1\. Abstraction
2\. Encapsulation
3\. Inheritance
4\. Polymorphism

1. 抽象

将 OOP 中的与实时示例相关联时,很容易理解。例如,当您开车时,您不必担心汽车的内部运行情况。 您所关心的是通过方向盘,制动踏板,油门踏板等接口与汽车交互。在这里,您对汽车的了解是抽象的。

在计算机科学中,抽象是这样的过程,在该过程中,数据和程序以其形式(语义)的形式类似于其含义的形式定义,同时隐藏了实现细节。

用更简单的术语来说,抽象是隐藏与上下文不相关的信息,或者仅显示相关信息,并通过将其与现实世界中的相似内容进行比较来简化该信息。

抽象仅捕获与当前视角相关的对象的那些细节。

通常,可以通过两种方式查看抽象:

  1. 数据抽象

    数据抽象是从多个较小的数据类型中创建复杂的数据类型的方法,该类型更接近于现实生活中的实体。 例如 Employee类可能是具有各种小型关联的复杂对象。

    public class Employee 
    {
        private Department department;
        private Address address;
        private Education education;
        //So on...
    }
    
    

    因此,如果您想获取某个员工的信息,则可以从Employee对象中询问该信息 – 就像您在现实生活中一样,请询问此人本身。

  2. 控制抽象

    通过将复杂任务的动作序列隐藏在一个简单的方法调用中,可以实现控制抽象,因此可以从客户端隐藏执行任务的逻辑,并且将来可以更改该逻辑而不会影响客户端代码。

    public class EmployeeManager
    {
        public Address getPrefferedAddress(Employee e)
        {
            //Get all addresses from database 
            //Apply logic to determine which address is preferred
            //Return address
        }
    }
    
    

    在上面的示例中,明天如果您要更改逻辑,以使每次本地地址始终都是首选地址,则将更改getPrefferedAddress()方法内部的逻辑,并且客户端将不受影响。

阅读更多:了解 Java 中的抽象

2. 封装

将类中的数据和方法与实现隐藏(通过访问控制)结合起来通常称为 OOP 中的封装。 结果是具有特征和行为的数据类型。 封装本质上既具有信息隐藏又具有实现隐藏。

不管有什么变化,都将其封装” – 著名的设计原理

信息隐藏是通过使用访问控制修饰符(公共,私有,受保护的)完成的,而实现隐藏是通过创建类的接口来实现的。

实现隐藏使设计人员可以自由修改对象如何履行职责。 这在设计(甚至需求)可能会发生变化的点上特别有价值。

让我们以一个例子来使它更清楚。

2.1 信息隐藏

class InformationHiding 
{
    //Restrict direct access to inward data
    private ArrayList items = new ArrayList();

    //Provide a way to access data - internal logic can safely be changed in future
    public ArrayList getItems(){
        return items;
    }
}

2.2 实现隐藏

interface ImplemenatationHiding {
    Integer sumAllItems(ArrayList items);
}
class InformationHiding implements ImplemenatationHiding
{
    //Restrict direct access to inward data
    private ArrayList items = new ArrayList();

    //Provide a way to access data - internal logic can safely be changed in future
    public ArrayList getItems(){
        return items;
    }

    public Integer sumAllItems(ArrayList items) {
        //Here you may do N number of things in any sequence
        //Which you do not want your clients to know
        //You can change the sequence or even whole logic
        //without affecting the client
    }
}

阅读更多:Java 中的封装

3. 继承

继承是面向对象编程中的另一个重要概念。 Java 中的继承是一种机制,通过该机制,一个对象可以获取父对象的属性和行为。 本质上是在类之间创建父子关系。 在 Java 中,主要将继承用于代码的可重用性和可维护性。

关键字extends用于继承 Java 中的类。 关键字extends表示您正在制作一个新类,该新类是从现有类中派生的。 在 Java 术语中,被继承的类称为超类。 新类称为子类

子类从其超类继承所有非私有成员(字段,方法和嵌套类)。 构造器不是成员,因此它们不会被子类继承,但是可以从子类调用超类的构造器

例如

class Employee 
{
    private Department department;
    private Address address;
    private Education education;
    //So on...
}
class Manager extends Employee {
    private List<Employee> reportees;
}

在上面的示例中,ManagerEmployee的专用版本,可重用Employee类别中的部门,地址和教育,并定义其自己的报告人列表。

4. 多态

多态是一种能力,通过这种能力,我们可以创建在不同程序环境下表现不同的函数或参考变量。

在 Java 语言中,多态本质上被认为是两个版本:

  • 编译时多态(静态绑定或方法重载
  • 运行时多态(动态绑定或方法覆盖

阅读更多:Java 中的多态

上面是四个 Java OOP 概念,我建议您对每个概念都有一个很好的了解。

学习愉快!

参考文献:

https://docs.oracle.com/javase/tutorial/java/concepts/

http://c2.com/cgi/wiki?InformationHiding

Java 访问修饰符

原文: https://howtodoinjava.com/oops/java-access-modifiers/

Java 访问修饰符 – 公共,受保护,私有和默认

Java 提供四个访问修饰符来设置类,变量,方法和构造器的访问级别,即公共私有,和默认。 这些访问级别修饰符确定其他类是否可以使用特定字段或调用特定方法。

1. Java 访问修饰符

简而言之,让我们快速比较这些访问修饰符。

  1. 公开 – 随处可见
  2. 受保护 – 可在同一包和子类中访问
  3. 默认 – 仅在同一包中可访问
  4. 私有 – 仅可在同一类访问

可以严格按以下顺序将访问说明符排序为

公共>受保护的>包私有(或默认)>私有

1.1 公共

公共可以从任何地方访问。可以从任何其他类访问声明为public的类,方法,构造器,接口等。

但是,如果我们尝试访问的公共类位于不同的包中,则仍然需要导入公共类。

public class HelloWorld 
{
   private String format;

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

在上面的示例中,getFormat()setFormat()方法是公共的,因此可以在任何地方访问它们。

1.2 受保护

受保护的对象可以由同一包的类以及任何包中的子类访问。 受保护的访问使子类有机会使用辅助方法或变量,同时防止了不相关的类尝试使用它。

public class HelloWorld 
{
   private String format;

   protected String getFormat() {
      return this.format;
   }
   protected void setFormat(String format) {
      this.format = format;
   }
}

在上述HelloWorld的示例中,变量format被声明为protected,因此可以由存在HelloWorld.java的同一包中存在的所有类以及存在于其他包中的子类来访问它。

1.3 默认(包私有)

默认访问修饰符意味着我们没有明确声明类,字段,方法等的访问修饰符。默认可以被同一包的类访问

请注意,接口中的字段隐式为public static final,而接口中的方法默认为public

public class HelloWorld 
{
   String format;

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

在上述HelloWorld的示例中,变量format被声明为default,因此可以由存在HelloWorld.java的同一包中的所有类访问。

1.4 私有

私有访问修饰符是最严格的访问级别。 (最高级)类和接口不能是私有的。 私有成员仅可在同一类中访问。声明为私有的方法,变量和构造器只能在声明的类本身内访问。

在上述HelloWorld的示例中,变量format被声明为私有,因此没有类可以直接访问它。 必须通过公共方法getFormat()setFormat()访问它。

访问级别以两种方式影响您。 首先,当您使用其他来源的类时,访问级别确定您自己的类可以使用那些类的成员。 其次,编写类时,需要确定类中每个成员变量和每个方法应具有的访问级别。

局部变量和形式参数不能使用访问说明符。 由于根据作用域规则,它们本质上是外部无法访问的,因此它们实际上是私有的。

如果其他程序员使用您的类,则要确保不会发生由于滥用而引起的错误。 访问级别可以帮助您做到这一点。

2. 访问控制级别

两个级别的访问控制

  1. 类级别 - 允许的修饰符是public或包私有(默认)。
  2. 方法级别 - 允许的修饰符为publicprivateprotected或包私有(默认)。

可以使用修饰符public声明一个类,在这种情况下,该类对于所有地方的所有类都是可见的。 如果一个类没有修饰符(默认,也称为包私有),则仅在其自己的包中可见。

对于成员,还有两个附加的访问修饰符:privateprotectedprivate修饰符指定只能在其自己的类中访问该成员。

protected修饰符指定成员只能在其自己的包中(与包私有一起)访问,并且只能由其在另一个包中的类的子类访问。

私有的和受保护的都可以(并且经常)应用于嵌套的类和接口,而绝不能应用于顶级类和接口。

学习愉快!

参考: https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html

Java 构造器

原文: https://howtodoinjava.com/oops/java-constructors/

Java 构造器是特殊的方法(无返回类型),可让您在对象状态可以由应用程序内的其他类使用之前完全初始化。 使用new关键字调用 Java 中的构造器。

让我们更深入地了解构造器。

Table of Contents

What is Java Constructor
Types of Constructors
	i) Default Constructor (no-arg constructor)
	ii) Parameterized Constructor
Constructor Rules
Constructor Chaining
Private Constructor

Java 中的构造器是什么

构造器是一种特殊的方法,类似于(没有确切的方法)构造,它可以帮助程序员编写对象初始化代码,然后该对象可用于应用程序中的其他对象。

每当应用程序需要任何类的新实例时,JVM 就会在堆内部分配一个内存区域。 然后,JVM 执行调用的构造器(类可以具有多个构造器)并初始化对象状态。 在构造器内部,您可以访问所有对象属性并将其分配给它们的默认值或任何所需的值。

阅读更多: Java 内存模型

构造器的类型

  1. 默认构造器(无参构造器)

    如果程序员在类定义中不提供任何构造器 – JVM 在运行时为该类提供默认构造器。

    程序员还可以覆盖类中的默认构造器。 让我们看一下语法。

    public class Employee 
    {	
    	public Employee() {
    
    	}
    }
    
    

    在默认构造器中,构造器的名称必须与类名称匹配,并且不应具有任何参数。

  2. 带有构造函数重载的参数化构造器

    如上所述,一个类中可以有多个构造器。 这可以通过重载构造器来实现。 在构造器重载中,您可以根据要求传递参数列表,即,可以使用几种方法初始化类。

    public class Employee {
    	private String firstName;
    	private String lastName;
    
    	public Employee() {	//constructor 1
    
    	}
    
    	public Employee(String firstName) { //constructor 2
    
    	}
    
    	public Employee(String firstName, String lastName) { //constructor 3
    
    	}
    }
    
    

    在上面的类中,我们定义了 3 个构造器来处理 3 种情况 – 应用程序可能需要如何创建员工对象,即不使用名称,仅使用名字以及同时使用名字和姓氏。

    Employee employee1 = new Employee();
    Employee employee2 = new Employee("Lokesh");
    Employee employee3 = new Employee("Lokesh", "Gupta");
    
    

构造规则

在 Java 中创建构造器的强制性规则很少。

  1. 构造器名称必须与类的名称相同。
  2. 构造器定义中不能有任何返回类型。
  3. 构造器中不能有任何return语句。
  4. 构造器可以由不同的参数重载。
  5. 如果要使用super(),即超类构造器,则它必须是构造器中的第一条语句。

构造器链接

在 Java 中,可以在构造器中调用其他构造器。 就像方法调用一样,但是没有任何引用变量(显然,实例已完全初始化)。

现在,我们可以调用相同类或父类的构造器。 两者使用不同的语法。

调用同一个类的构造器

要从同一类调用其他构造器,请使用this关键字。 例如:

public Employee() {	

}

public Employee(String firstName) { 
	this();		//calling default constructor
}

public Employee(String firstName, String lastName) {
	this(firstName);	//calling constructor with single argument of String type
}

调用超类构造器

要从父类或父类调用构造器,请使用super关键字。 super关键字的用法类似于this关键字 – 唯一的区别是super引用超类,this引用当前实例。

public Employee() {	
	//refer to Object class constructor 
	//as it is parent class for every class
	super();	
}

私有构造器

有时您想保护构造器以免被其他类调用。 总之,您希望没有人能够创建该类的新实例。

为什么有人要那个? 好吧,这对于单例模式是必要的。 在单例中,应用程序希望只有任何一个类的一个实例。

常见的单例类定义如下所示:

public class DemoSingleton implements Serializable 
{
    private static final long serialVersionUID = 1L;

    private DemoSingleton() {
        // private constructor
    }

    private static class DemoSingletonHolder {
        public static final DemoSingleton INSTANCE = new DemoSingleton();
    }

    public static DemoSingleton getInstance() {
        return DemoSingletonHolder.INSTANCE;
    }

    protected Object readResolve() {
        return getInstance();
    }
}

这就是 Java 中的构造器。 将我的问题放在评论部分。

学习愉快!

Java 实例初始化器块

原文: https://howtodoinjava.com/oops/java-instance-initializer/

Java 实例初始化器是在执行构造器代码之前执行的代码块。 每当我们创建一个新对象时,这些初始化器就会运行。

1. 实例初始化语法

使用大括号创建实例初始化器块。 对象初始化语句写在括号内。

public class DemoClass {

    //This is initializer block 1
    {
      //statements
    }

    //This is initializer block 2
    {
      //statements
    }
}

2. Java 实例初始化器特性

实例初始化器具有以下特性。

  • 我们可以在一个类中定义多个初始化器
  • 所有初始化器将按顺序执行,以使其出现在类主体中。
  • 初始化器在调用父类构造器之后,在执行子类构造器之前运行。 请注意,如果我们未在子类的构造器中显式提供第一条语句,则 Java 会插入父类super()的默认构造器。
  • 在完成所有初始化器后,将执行构造器的语句。
  • 我们可以在初始化器中使用此类和父类的构造器的调用。

3. Java 实例初始化序列流程

基于以上给出的特性,让我们概述一下对象的实例初始化如何进行。

  1. 子类构造器被调用。
  2. 子类构造器的第一个语句为super()(或提供的显式构造器),因此将调用父类构造器。
  3. 父类的初始化器按其出现顺序执行。
  4. 父类构造器语句被执行。
  5. 子类的初始化器按其出现顺序执行。
  6. 子类构造器语句被执行。

4. Java 实例初始化器示例

让我们快速看一个示例,以演示以上理论。

public class ParentClass 
{ 
  public ParentClass() 
  {
    System.out.println("In ParentClass Constructor");
  }

  //Instance Initializer
  {
    System.out.println("In ParentClass Instance Initializer");
  }
}

public class ChildClass extends ParentClass 
{
  public ChildClass() 
  {
    super();  //If not provided, JVM will insert it
    System.out.println("In ChildClass Constructor");
  }

  //Instance Initializer 1
  { 
    System.out.println("In ChildClass Instance Initializer 1");
  }

  //Instance Initializer 2
  {
    System.out.println("In ChildClass Instance Initializer 2");
  }
}

public class Main 
{
  public static void main(String[] args) 
  {
    ChildClass childObj = new ChildClass();
  }
}

程序输出。

In ParentClass Instance Initializer
In ParentClass Constructor
In ChildClass Instance Initializer 1
In ChildClass Instance Initializer 2
In ChildClass Constructor

学习愉快!

Java 中的抽象示例

原文: https://howtodoinjava.com/oops/understanding-abstraction-in-java/

用最简单的话来说,您可以将抽象定义为仅捕获与当前视角相关的 Java 对象的那些细节。

例如,HashMap存储键值对。 它为您提供了两种方法get()put()方法,用于从映射存储和检索键值对。 实际上,这是您想要在应用程序中使用映射时所需的唯一信息。 它是如何工作的,不需要使用它就知道。 这是 Java 中非常抽象的示例。

再看一个现实生活中的抽象示例,它可以是电视遥控器。 您知道,当您按遥控器上的任何按钮时,电视上都会应用某些功能,例如更改频道,更改音量级别等。不需要了解远程内部的工作原理,即可正确使用它。 这是一个抽象的例子。

Table of Contents

1\. What is abstraction in oops?
2\. Types of abstraction
3\. How to use abstraction in java
4\. Encapsulation vs Abstraction

1. OOP 中的抽象是什么?

面向对象程序设计理论中,抽象涉及定义对象的能力,该对象代表可以执行工作,报告和更改其状态以及与系统中其他对象“通信”的抽象“角色”。

在计算机科学中,抽象是这样的过程,在该过程中,数据和程序以与其含义(语义学)形式相似的表示形式进行定义,同时隐藏了实现细节。 – WikiPedia

任何编程语言中的抽象都可以通过多种方式工作。 从创建子例程到定义用于进行低级语言调用的接口可以看出。

一些抽象试图通过完全隐藏它们依次建立在其上的抽象来限制程序员所需概念的广度,也就是设计模式

2. 抽象类型

通常,可以通过两种方式查看抽象:

  1. 数据抽象

    数据抽象是创建复杂数据类型并仅公开有意义的操作以与数据类型进行交互的方式,而将所有实现细节都隐藏在外部工作中。

    这种方法的好处在于可以随着时间的流逝改善实现的能力,例如解决性能问题无所不包。 想法是这样的更改不应该对客户端代码产生任何影响,因为它们在抽象行为上没有任何区别。

  2. 控制抽象

    软件本质上是用任何编程语言编写的众多语句的集合。 在大多数情况下,语句是相似的,并且多次重复出现。

    控制抽象是识别所有此类语句并将其作为工作单元公开的过程。 我们通常在创建函数来执行任何工作时使用此特性。

3. 如何用 Java 实现抽象?

由于抽象是面向对象编程实践的核心原则之一,而 Java 遵循所有 OOP 原则,因此抽象是 Java 语言的主要构建模块之一。

在 Java 中,抽象是通过接口和抽象类实现的。接口允许您完全抽象化实现,而抽象类也允许部分抽象。

数据抽象从创建简单数据对象到复杂的集合实现,例如HashMapHashSet

同样,从定义简单的函数调用到完整的开源框架,可以看出控制抽象。 控制抽象是结构化编程背后的主要力量。

3.1 Java 抽象示例

我们再来看一个 Java 使用接口进行抽象的示例。 在此示例中,我将创建各种报告,这些报告可以在应用程序生存期内随时按需运行。 作为报告的使用者,类不必知道报告的内部run(),只需要执行此方法即可执行报告。

import java.util.List;

public interface Report 
{
    List<Object> run(ReportContext reportContext);
}

public class ReportContext {
	//fields
}

import java.util.List;

public class EmployeeReport implements Report 
{
    @Override
    public List<Object> run(ReportContext reportContext) {
        //Custom Logic
        System.out.println("Executing employee report");
        return null;
    }
}

import java.util.List;

public class SalaryReport implements Report 
{
    @Override
    public List<Object> run(ReportContext reportContext) {
        //Custom logic
        System.out.println("Executing salary report");
        return null;
    }
}

现在使用run()方法执行报告。

package com.howtodoinjava.abstraction;

public class Main {
    public static void main(String[] args) {

        ReportContext reportContext = new ReportContext();
        //Populate context

        Report eReport = new EmployeeReport();
        eReport.run(reportContext);

        Report sReport = new EmployeeReport();
        sReport.run(reportContext);
    }
}

程序输出。

Executing employee report
Executing employee report

4. 封装与抽象

Encapsulation是您所需的abstraction的实现。

抽象更多地是关于隐藏实现细节。 在 Java 中,抽象是通过抽象类和接口实现的。

封装是将实现(代码)及其操作的数据(变量)包装在同一类中。 Java 类是封装类的一个示例,其中所有实例变量都是私有的,并且只有该类中的方法才能操纵这些变量。

如果您想了解有关 Java 中抽象类和接口的更多信息,请阅读我的下一篇文章探索 Java 中的接口和抽象类。

学习愉快!

Java 封装与抽象

原文: https://howtodoinjava.com/oops/encapsulation-in-java-and-its-relation-with-abstraction/

你们中的大多数人都同意封装和抽象在一起会带来很多混乱。 大多数博客只会进一步增加混乱。 让我们解决这个难题。

在上一篇文章“了解 Java 中的抽象”之后,我开始撰写此文章。 我的目标是了解 Java 中的封装及其与抽象的关系。 一开始,我就开始陷入前所未有的混乱之中。 在浏览了多个小时并阅读了一些写得很好以及令人困惑的博客条目之后,我能够做出一些清晰的理解。 跟随我的足迹

Table of Contents

Encapsulation in simple words
Encapsulation in Detail
Encapsulation vs Abstraction

简单的封装

将类中的数据和方法与实现隐藏(通过访问控制)结合起来通常称为封装。 结果是具有特征和行为的数据类型。 封装实质上具有信息隐藏和实现隐藏两者。

详细封装

我在某处读过它:“进行任何更改,将其封装”。 它被引用为著名的设计原则。 因此,在任何类中,运行时中的数据都可能发生更改,将来的发行版中可能会更改实现。 因此,封装既适用于数据也适用于实现。

访问控制或实现隐藏将边界置于数据类型或类中,这有两个重要原因。 首先是确定客户端程序员可以使用和不能使用的东西。 这直接导致了第二个原因,那就是将接口与实现分开。

如果您确定客户端程序员除了将消息发送到公共接口之外什么也不能做,那么您可以随意更改不公开的任何内容(例如,包访问,受保护或私有),而不会破坏客户端代码。 封装可以帮助您实现这一保证。

封装与抽象

如果您读完了上一篇,您将看到抽象本质上是一个主意,这有助于设置指导原则。 封装是我们实现所需抽象的机制。

简而言之,从 OOAD 的角度来看:

  • 抽象更多地涉及“类可以做什么”。(想法)
  • 封装更多地涉及“如何”来实现该功能。(实现)

我在很多博客上都看到了与此理论相矛盾的地方。 因此,如果您也不同意这一点,请多多包涵。 另外,我将要求您对与主题相关的想法发表评论。 我会很乐意尝试联系或否定。

展望未来,我将以我们众所周知的类HashMap为例。 此类负责存储键值对,基于键进行搜索并执行更多操作。 从外部,客户端代码仅知道方法名称及其行为。 它调用了这些方法,并快乐地生活着。 这实际上是抽象准则。 抽象表示,客户端代码应调用添加键值对的方法,基于键检索值的方法等。 应该怎么做? 不是抽象业务。

当您开始编写实际代码时,封装就到了。 您编写HashMap.Entry类,并创建类型为Entry[]的变量。 然后,您将所有此类内容声明为私有,并仅允许put()get()方法等公共访问。这实际上是封装。 您所需的抽象的实现。

希望您更加清楚 Java 封装,以及与抽象的区别。

学习愉快!

Java 继承

原文: https://howtodoinjava.com/oops/java-inheritance/

Java 中的继承(IS-A 关系) 是指子对象继承或从父对象获取所有属性和行为的能力。 在面向对象的编程中,继承用于提升代码的可重用性。

在本 Java 教程中,我们将学习 Java 支持的继承类型和 Java 应用程序中如何实现继承

Table of Contents

1\. What is inheritance
2\. Types of Inheritance in Java
    - 2.1\. Single Inheritance
    - 2.2\. Multilevel Inheritance
    - 2.3\. Hierarchical Inheritance
    - 2.4\. Multiple inheritance
3\. Accessing Inherited Super Class Members
    - 3.1\. Constructors
    - 3.2\. Fields
    - 3.3\. Methods
4\. Summary

1. Java 中的继承是什么

如前所述,继承就是通过派生类(子类或子类)继承父类(超类)的公共状态和行为。 默认情况下,子类可以继承超类的所有非私有成员

在 Java 中,关键字extends用于类之间的继承。 我们来看一个快速继承的例子。

1.1 Java 继承示例

假设我们有Employee类。 雇员类具有组织中所有雇员必须具有的所有公共属性和方法。 也可以有其他专业员工,例如 Manager。 经理是组织的正式员工,但与其他员工相比,他们的属性却很少。 他们有报告人或下属。

让我们设计以上类。

public class Employee 
{   
    private Long id;
    private String firstName;
    private String lastName;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    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 "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
    }
}

import java.util.List;

public class Manager extends Employee 
{
    private List<Employee> subordinates;

    public List<Employee> getSubordinates() {
        return subordinates;
    }

    public void setSubordinates(List<Employee> subordinates) {
        this.subordinates = subordinates;
    }

    @Override
    public String toString() {
        return "Manager [subordinates=" + subordinates + ", details=" + super.toString() + "]";
    }
}

在上述实现中,员工具有idfirstNamelastName之类的公共属性; 而经理仅具有专门的subordinates属性。 要从Employee类继承所有非私有成员(在本例中为获取器和设置器方法),请使用Manager extends Employee

让我们看看它是如何工作的?

public class Main 
{
    public static void main(String[] args) 
    {
        Manager mgr = new Manager();

        mgr.setId(1L);
        mgr.setFirstName("Lokesh");
        mgr.setLastName("Gupta");

        System.out.println(mgr);
    }
}

程序输出。

Manager [subordinates=null, details=Employee [id=1, firstName=Lokesh, lastName=Gupta]]

显然,Manager类可以使用Employee类的成员。 这种行为称为继承。 很简单,不是吗?

现在考虑是否不使用继承。 然后,我们将在两个类中都定义idfirstNamelastName。 这会导致代码重复,这总是在代码维护中造成问题。

2. Java 中的继承类型

在 Java 中,继承可以是四种类型之一 - 取决于类层次结构。 让我们了解所有四种继承。

2.1 单继承

这很简单。 有一个父类和一个子类。 一个子类扩展了一个父类。 它是单一继承。 上面的示例代码(员工和管理者)是单继承的示例。

Java Single Inheritance

Java 单继承

2.2 多级继承

在多级继承中,将在三个以上的类之间进行继承,使得子类将充当另一个子类的父类。 让我们用一个图表来理解。

Multilevel Inheritance

多级继承

在上面的示例中,类B扩展了类A,因此类B是类A的子类。 但是C扩展了B,因此BC的父类。 因此B既是父类,也是子类。

2.3 分层继承

在分层继承中,有一个超类,并且有多个子类扩展了超类。

Hierarchical Inheritance

分层继承

这些子类BCD将共享从A继承的公共成员,但是它们彼此之间不会相互意识到。

2.4 多重继承

在多重继承中,一个类也可以从多个父类中继承行为。 让我们来了解一下图表。

Multiple inheritance

多重继承

在图中,D扩展了AB类。 这样,D可以继承两个类的非私有成员。

但是,在 Java 中,不能将extends关键字用于两个类。 那么,多重继承将如何工作?

直到 JDK 1.7,在 Java 中都无法进行多重继承。 但是从 JDK 1.8 开始,可以通过使用具有默认方法的接口来实现多重继承

3. 访问被继承的父类成员

现在我们知道,使用四种类型的继承机制,我们可以访问父类的非私有成员。 让我们看看如何访问单个成员。

3.1 父类的构造器

可以通过super关键字来调用超类的构造器。 只有两个规则:

  1. super()调用必须从子类构造器进行。
  2. super()调用必须是构造器中的第一条语句。
public class Manager extends Employee 
{
    public Manager() 
    {
        //This must be first statement inside constructor
        super();

        //Other code after super class
    }
}

3.2 父类字段

在 Java 中,非私有成员字段可以在子类中继承。 您可以使用点运算符来访问它们,例如 manager.id。 这里id属性是从父类Employee继承的。

在父类和子类中处理具有相同名称的字段时,需要小心。 请记住, java 字段不能被覆盖。 具有相同名称的字段会在通过子类访问时对父类隐藏该字段。

在这种情况下,将基于引用类型的类来确定访问的属性。

ReferenceClass variable = new ActualClass();

在上述情况下,将从ReferenceClass访问成员字段。 例如

//Parent class
public class Employee 
{   
    public Long id = 10L;
}

//Child class
public class Manager extends Employee 
{
    public Long id = 20L;   //same name field
}

public class Main {
    public static void main(String[] args) 
    {
        Employee manager = new Manager();
        System.out.println(manager.id);     //Reference of type Employee

        Manager mgr = new Manager();
        System.out.println(mgr.id);     //Reference of type Manager
    }
}

Output:

10
20

3.3 父类方法

与字段访问相反,方法访问使用在运行时创建的实际对象的类型。

 ReferenceClass variable = new ActualClass();

在上述情况下,将从ActualClass访问成员方法。 例如

public class Employee 
{   
    private Long id = 10L;

    public Long getId() {
        return id;
    }
}

public class Manager extends Employee 
{
    private Long id = 20L;

    public Long getId() {
        return id;
    }
}

public class Main 
{
    public static void main(String[] args) 
    {
        Employee employee = new Employee();     //Actual object is Employee Type
        System.out.println(employee.getId());

        Employee manager = new Manager();       //Actual object is Manager Type
        System.out.println(manager.getId());

        Manager mgr = new Manager();       //Actual object is Manager Type
        System.out.println(mgr.getId());
    }
}

Output:

10
20
20

4. 总结

让我们总结一下我们对 java 继承的了解:

  • 继承也称为 IS-A 关系。
  • 它为子类提供了继承父类的非私有成员的能力。
  • 在 Java 中,继承是通过extends关键字实现的。
  • Java 8 开始,您可以使用具有默认方法的接口来实现多重继承。
  • 成员字段从引用类型类访问。
  • 成员方法从实际实例类型访问。

如果有任何问题,请在评论部分中向我发送。

学习愉快!

Java 多态示例

原文: https://howtodoinjava.com/oops/what-is-polymorphism-in-java/

简而言之,多态是一种能力,通过这种能力,我们可以创建在不同程序环境下表现不同的函数或参考变量

多态与继承,抽象和封装一样,是面向对象编程的主要组成部分之一。

“子类型多态在面向对象编程的上下文中通常简称为多态,是一种创建具有多种形式的变量,函数或对象的能力。” – 维基百科

推荐读物: Java 抽象与封装

Java 中的多态

多态的一个例子是使用超类的引用变量引用子类的实例。 例如

Object o = new Object(); //o can hold the reference of any subtype
Object o = new String();
Object o = new Integer();

在此,StringObject类别的子类别。 这是多态的基本示例。

在 Java 语言中,多态本质上被认为是两个版本。

  1. 编译时多态(静态绑定或方法重载)
  2. 运行时多态(动态绑定或方法覆盖)

编译时多态(静态绑定或方法重载)

由于含义是隐式的,因此它用于编写程序,以使控制流由编译时间本身决定。 使用方法重载实现。

在方法重载中,一个对象可以具有两个或更多个具有相同名称的方法,而它们的方法参数不同。 这些参数可能在两个基础上有所不同:

参数类型

方法参数的类型可以不同。 例如java.util.Math.max()函数具有以下版本:

public static double Math.max(double a, double b){..}
public static float Math.max(float a, float b){..}
public static int Math.max(int a, int b){..}
public static long Math.max(long a, long b){..}

实际调用的方法是根据传递给程序中函数的参数在编译时决定的。

参数计数

接受不同数量参数的函数。 例如在员工管理应用程序中,工厂可以采用以下方法:

EmployeeFactory.create(String firstName, String lastName){...}
EmployeeFactory.create(Integer id, String firstName, String lastName){...}

两种方法都具有相同的名称create,但是实际调用的方法将基于程序中传递的参数。

运行时多态(动态绑定或方法覆盖)

运行时多态基本上被称为方法覆盖 方法覆盖是在程序中实现继承时获得的特性。

一个简单的例子可以来自现实世界,例如动物。 应用程序可以具有Animal类,以及其专门的子类,例如CatDog。 这些子类将覆盖Animal类提供的默认行为及其某些特定行为。

public class Animal {
	public void makeNoise()
	{
		System.out.println("Some sound");
	}
}

class Dog extends Animal{
	public void makeNoise()
	{
		System.out.println("Bark");
	}
}

class Cat extends Animal{
	public void makeNoise()
	{
		System.out.println("Meawoo");
	}
}

现在将调用哪个makeNoise()方法,具体取决于在运行时创建的实际实例的类型,例如

public class Demo
{
	public static void main(String[] args) {
		Animal a1 = new Cat();
		a1.makeNoise(); //Prints Meowoo

		Animal a2 = new Dog();
		a2.makeNoise(); //Prints Bark
	}
}

在这里,重要的是要了解这些划分是特定于 java 的。 在软件工程的上下文中,还有其他形式的多态性也适用于不同的语言,但是对于 Java,主要考虑这些形式。

重要事项

  1. 多态是创建具有多种形式的变量,函数或对象的能力。
  2. 在 Java 中,多态分为两部分:方法重载和方法重载。
  3. 有人可能会说方法重载不是多态。 那么,编译时“多态”一词是什么意思呢?
  4. 还有另一个术语运算符重载,例如+运算符可用于添加两个整数以及连接两个子字符串。 好吧,这是 Java 中对运算符重载的唯一可用支持,并且您不能在 Java 中拥有自己的自定义定义的运算符重载。

学习愉快!

Java 方法重载与方法重载

原文: https://howtodoinjava.com/oops/method-overloading-and-overriding-rules-in-java/

方法重载和覆盖(换句话说,Java 中的多态)既不是一个非常困难的概念,也不是一个非常未知的主题。 但是,我将在本文中介绍这个主题,因为同时使用多个代码示例在 Java 面试中测试此类概念时,很容易犯错误。 我在这里没有给出任何新概念,但是我打算修改您有关 Java 中方法重载和覆盖规则的现有知识。

方法重载规则

重载 Java 中的任何方法时,请牢记以下规则:

1)Java 中重载方法的第一个重要规则是更改方法签名。 方法签名由变量数量,变量类型和变量顺序(如果它们是不同类型)组成。

public class DemoClass {
	// Overloaded method
	public Integer sum(Integer a, Integer b) {
		return a + b;
	}

	// Overloading method
	public Integer sum(Float a, Integer b) {  //Valid
		return null;
	}
}

2)方法的返回类型从来都不是方法签名的一部分,因此仅更改方法的返回类型并不等于方法重载

public class DemoClass {
	// Overloaded method
	public Integer sum(Integer a, Integer b) {
		return a + b;
	}

	// Overloading method
	public Float sum(Integer a, Integer b) {     //Not valid; Compile time error
		return null;
	}
}

3)重载方法时,也不会考虑方法引发的异常。 因此,您的重载方法会抛出相同的异常,不同的异常,或者它根本不会引发任何异常。 对方法加载完全没有影响。

public class DemoClass {
	// Overloaded method
	public Integer sum(Integer a, Integer b) throws NullPointerException{
		return a + b;
	}

	// Overloading method
	public Integer sum(Integer a, Integer b) throws Exception{ 	//Not valid; Compile time error
		return null;
	}
}

阅读更多:什么是 Java 中的多态

方法覆盖规则

我们在上面阅读了方法重载的规则,现在是时候列出在覆盖 Java 中的方法时应记住的规则了。

1)覆盖方法和被覆盖方法中的参数列表必须完全相同。如果它们不匹配,则最终将得到重载方法。

2)覆盖方法的返回类型可以是在被覆盖方法中声明的返回类型的子类。

public class SuperClass {
	//Overriden method
	public Number sum(Integer a, Integer b) {
		return a + b;
	}
}

class SubClass extends SuperClass {
	//Overriding method
	@Override
	public Integer sum(Integer a, Integer b) {  	//Integer extends Number; so it's valid
		return a + b;
	}
}

3)在所有规则中,不能以任何方式在 Java 中覆盖私有,静态和最终方法。 就如此容易 !!

public class SuperClass {
	private Integer sum(Integer a, Integer b) {   //private method; overriding not possible
		return a + b;
	}
}

class SubClass extends SuperClass {
	//Overriding method
	public Integer sum(Integer a, Integer b) {   
		return a + b;
	}
}

4)覆盖方法无法引发层次结构中比覆盖方法所引发的异常高的受检的异常。 例如,假设父类中的覆盖方法抛出FileNotFoundException,子类中的覆盖方法抛出FileNotFoundException; 但不允许抛出IOExceptionException,因为IOExceptionException的层次结构较高,即FileNotFoundException的超类。

此外,您可以从覆盖方法中省略异常声明。 允许且完全有效。 同样,无论覆盖方法是否声明该异常,覆盖方法都可以引发任何非受检的(运行时)异常。

public class SuperClass {
	//Overriden method
	public Integer sum(Integer a, Integer b) throws FileNotFoundException {
		return a + b;
	}
}

class SubClass extends SuperClass {
	//Overriding method
	public Integer sum(Integer a, Integer b) throws IOException {   	//Not valid; Compile time error
		return a + b;
	}
	//Exception IOException is not compatible with throws clause in SuperClass.sum(Integer, Integer)
	public Integer sum(Integer a, Integer b)  {						//It's valid; Don't declare the exception at all is permitted.
		return a + b;
	}
}

5)另请注意,覆盖方法不能缩小被覆盖方法的访问范围。 简而言之,如果父类中的被覆盖方法受到保护,则子类中的覆盖方法不能为私有。 它必须是受保护的(相同的访问权限)或公共的(更广泛的访问权限)。

public class SuperClass {
	//Overriden method
	protected Integer sum(Integer a, Integer b) {
		return a + b;
	}
}

class SubClass extends SuperClass {
	//Overriding method
	//Not valid; Compile time error &quot;Cannot reduce the visibility of the inherited method from SuperClass&quot;
	private Integer sum(Integer a, Integer b)  {	
		return a + b;
	}
}

在父类和子类的术语上进行讨论时,不再重复方法覆盖是合法的。 它不会在同一个类中发生。

要验证您是否正确覆盖了方法,只需在覆盖方法上使用注解@Override。 它将为您验证所有方法替代规则。 如果有任何问题,将导致编译时错误。

阅读更多: Java 面试问题

这就是这个简单而重要的概念的全部内容,可以帮助您熟练掌握 Java 和面向对象编程的基础知识。

祝您学习愉快!

参考: Oracle 博客

Java 中的原始数据类型

原文: https://howtodoinjava.com/java/basics/primitive-data-types-in-java/

了解 Java 中所有八种基本数据类型,它们的内存大小,默认值以及最大值和最小值范围。

原始数据类型由语言预先定义,并由保留关键字命名。 让我们在下面的图片中查看每种原始数据类型。

Primitive data types in java

Java 中的原始数据类型

1. 整数数据类型

整数数据类型是数值数据类型,其值为整数。 Java 提供五个整数数据类型byteshortintlongchar

1.1 int数据类型

  • int数据类型是 32 位带符号的 Java 基本数据类型。 int数据类型的变量占用 32 位内存
  • 其有效范围是-2,147,483,6482,147,483,647-2 ^ 31 2 ^ 31 – 1)。
  • 此范围内的所有整数都称为整数字面值(或整数常量)。 例如,10,-200、0、30、19 等是int的整数字面值。

可以将整数字面值分配给int变量,例如counter,如下所示:

int counter = 21;

1.1.1 Integer包装器类

Java 有一个名为Integer的包装器类,该包装器类定义了两个常量来表示int数据类型Integer.MAX_VALUEInteger.MIN_VALUE的最大值和最小值。 它是 int 值的对象表示。

int max = Integer.MAX_VALUE; // Assigns maximum int value to max
int min = Integer.MIN_VALUE; // Assigns minimum int value to min

1.2 long数据类型

  • long数据类型是 64 位带符号的 Java 基本数据类型。
  • 当对整数的计算结果可能超出int数据类型的范围时,将使用它。
  • 其范围是-2 ^ 632 ** 63 – 1
  • long范围内的所有整数都称为 long 类型的整数字面值

long 类型的整数字面值始终以L(或小写的l)结尾。

long num1 = 0L;
long num2 = 401L;
long mum3 = -3556L;

1.2.1 类型转换

即使long变量中存储的值恰好在int数据类型的范围内,也不允许从longint的赋值,而无需显式类型转换, 如下例所示:

int num1 = 5;
long num2 = 25L;

// A compile-time error. Even if num2's value 25 which is within the range of int.
num1 = num2;

如果要将long变量的值分配给int变量,则必须在代码中明确提及这一事实,以便 Java 确保您知道可能存在数据溢出。 您可以使用 Java 中的“类型转换”来执行此操作,如下所示:

long num1 = (int) num2; // Now it is fine because of the "(int)" cast

通过编写(int)num2,您将指示 Java 将num2中存储的值视为int。 在运行时,Java 将仅使用num2的 32 个最低有效位,并将存储在这 32 位中的值分配给num1。 如果num2的值超出 int 数据类型的范围,则num1中不会获得相同的值,并且会导致数据丢失

1.2.2 long包装类

Java 具有类Long(请注意Long中的大写L),该类定义了两个常量来表示长数据类型的最大值和最小值Long.MAX_VALUELong.MIN_VALUE

long max = Long.MAX_VALUE;
long min = Long.MIN_VALUE;

1.3 byte数据类型

  • byte数据类型是 8 位带符号的 Java 基本整数数据类型。
  • 其范围是 -128 至 127(-2 ^ 72 ^ 7 – 1)。 这是 Java 中可用的最小整数数据类型
  • intlong字面值不同,没有字节字面值。
  • 但是,您可以将属于byte范围的任何int字面值分配给byte变量。
byte b1 = 125;
byte b2 = -11;

1.3.1 类型转换

如果将int字面值分配给byte变量,并且该值超出字节数据类型的范围,则 Java 会生成编译器错误。 以下代码段将产生编译器错误:

// An error. 150 is an int literal outside -128 to 127
byte b3 = 150;

Java 不允许您将范围较大的数据类型的变量的值分配给范围较小的数据类型的变量,因为在进行这种分配时可能会失去精度。 要进行从intbyte的赋值,必须像在longint赋值时那样使用强制转换。

b1 = (byte)num1; // Ok

1.3.2 Byte包装器类

Java 具有类Byte(请注意,字节中的大写B),该类定义了两个常量来表示字节数据类型的最大值和最小值Byte.MAX_VALUEByte.MIN_VALUE

byte max = Byte.MAX_VALUE;
byte min = Byte.MIN_VALUE;

1.4 short数据类型

  • short数据类型是 16 位带符号的 Java 基本整数数据类型。 其范围是 -32768 至 32767(或-2 ^ 152 ^ 15 – 1)。与intlong字面值不同,没有short字面值。但是,您可以将任何在short范围内的int字面值(-32768 到 32767)分配给short变量。
short s1 = 12905;   // ok
short s2 = -11890;  // ok

字节变量的值始终可以分配给short变量,因为byte数据类型的范围在short数据类型的范围内。 将值从intlong变量分配给short变量的所有其他规则与字节变量的规则相同。

1.4.1 Short包装类

Java 有一个名为Short的类(在Short中注意大写字母S),它定义了两个常量来表示short数据类型的最大值和最小值Short.MAX_VALUEShort.MIN_VALUE

short max = Short.MAX_VALUE;
short min = Short.MIN_VALUE;

1.5 char数据类型

  • char数据类型是 16 位无符号 Java 基本数据类型。
  • 它表示 Unicode 字符
  • 请注意,char是无符号数据类型。 因此,char变量不能具有负值。
  • char数据类型的范围是 0 到 65535,与 Unicode 集的范围相同。
  • 字符字面值表示char数据类型的值。
char c1 = 'A';
char c2 = 'L';
char c3 = '5';
char c4 = '/';

1.5.1 字符转义序列

字符字面值也可以表示为字符转义序列。 字符转义序列以反斜杠开头,后跟一个字符,并且两者都用单引号引起来。

共有八个预定义的字符转义序列,如下所示:

字符转义序列 描述
\n 换行
\r 回车
\f 换页
\b 退格键
\t 标签
\\ 反斜杠
\" 双引号
\' 单引号

这些只是 Java 中的八个字符转义序列。 您不能定义自己的字符转义序列。

1.5.2 Unicode 转义序列

字符字面值也可以表示为'\uxxxx'形式的 Unicode 转义序列,此处\u(反斜杠紧跟小写的u)表示 Unicode 转义序列的开始,并且xxxx恰好代表四个十六进制数字。

char c1 = 'A';
char c2 = '\u0041';  // Same as c2 = 'A'

2. 浮点数据类型

包含小数部分的浮点数被称为实数,例如 3.25、0.49,-9.19 等。

数字如何存储在内存中

将实数转换为二进制表示形式时,计算机还必须存储数字中小数点的位置。 有两种策略可以将实数存储在计算机内存中。

  1. 定点数字格式 – 仅存储数字的二进制表示,并假定在该点之前和之后始终有固定数量的数字。 一个点在数字的十进制表示形式中称为小数点,在二进制表示形式中称为二进制点。 点的位置始终固定在数字中的表示类型称为“定点”数字格式。
  2. 浮点数格式 – 存储实数的二进制表示形式以及该点在实数中的位置。 由于在这种实数表示中,点之前和之后的位数可能会有所不同,因此我们说该点可以浮动。 这种表示形式称为“浮点”格式。

与定点表示相比,浮点表示的速度较慢,准确度较低。 但是,与定点表示相比,浮点表示可以在相同的计算机内存中处理更大范围的数字。

Java 支持“浮点”数字格式。

Floating-Point Numbers

IEEE-754 32 位单精度浮点数

Java 有两种浮点数字数据类型:floatdouble

2.1 float数据类型

float数据类型以 32 位 IEEE 754 标准格式存储浮点数(单精度浮点数)。 它的实数大小可小至1.4 x 10 ^ -45,大至3.4 x 10 ^ 38。 该范围仅包括幅度。 它可以是正面的或负面的。

所有以fF结尾的实数都称为浮点字面值

float f1 = 8F;
float f2 = 8.F;
float f3 = 8.0F;

2.1.1 正负无穷大

float数据类型定义两个无限性:正无穷大和负无穷大。 例如,将2.5F除以0.0F的结果是浮点正无穷大,而2.5F除以-0.0F的结果是浮点负无穷大。

2.1.2 NaN(非数字)

未对float上的某些操作结果进行定义。 这些结果由称为NaN(非数字)的float数据类型的特殊值表示。

Float类定义了三个常量,分别表示float数据类型的正无穷大,负无穷大和NaN。 还有两个常量,它们代表可以存储在float变量中的最大值和最小值(大于零)。

Float.POSITIVE_INFINITY - Positive infinity of type float.
Float.NEGATIVE_INFINITY - Negative infinity of type float.
Float.NaN - Not a Number of type float.
Float.MAX_VALUE - The largest positive value that can be represented in a float variable.
Float.MIN_VALUE - The smallest positive value greater than zero that can be represented in a float variable. 

请注意,可以将所有整数类型(intlongbyteshortchar)的值分配给float数据类型的变量,而无需使用显式强制转换,但必须先对float值进行强制转换,然后再将其赋给任何整数数据类型intlongbyteshortchar的变量。

2.2 double数据类型

double数据类型以 64 位“IEEE 754 标准格式”存储浮点数。 根据 IEEE 754 标准,以 64 位表示的浮点数也称为双精度浮点数

所有实数都称为double字面量。 双精度字面值可以选择以dD结尾,例如19.27d。 但是,后缀dD在双字面量中是可选的。 也就是说,19.27d19.27D都代表相同的双字面量。

double d1 = 8D
double d2 = 8.;
double d3 = 8.0;
double d4 = 8.D;

float数据类型一样,double数据类型定义了最大值,最小值,两个无限和一个NaN值。

Double.POSITIVE_INFINITY - Positive infinity of type double.
Double.NEGATIVE_INFINITY - Negative infinity of type double.
Double.NaN - Not a Number of type double.
Double.MAX_VALUE - The largest positive value that can be represented in a double variable.
Double.MIN_VALUE - The smallest positive value greater than zero that can be represented in a double variable. 

3. 布尔值

3.1 布尔数据类型

布尔数据类型只有两个有效值:truefalse。 这两个值称为布尔字面值。 您可以将布尔字面值用作

boolean done; // Declares a boolean variable named done
done = true;  // Assigns true to done

需要注意的重要一点是布尔变量不能转换为任何其他数据类型,反之亦然。 Java 没有指定布尔数据类型的大小。 它的大小由 JVM 实现决定。 通常,布尔数据类型的值在内部以字节存储。

这就是 Java 中可用的 8 种原始数据类型的全部内容。

学习愉快!

阅读更多:

Oracle Java 文档

数据表示

接口与 Java 中的抽象类

原文: https://howtodoinjava.com/oops/exploring-interfaces-and-abstract-classes-in-java/

抽象类和接口是大多数 Java API 的两个主要构建块。 在本文中,我们将介绍 Java 接口和抽象类之间最明显的区别。

Table of Contents

1\. Abstract classes in java
2\. Interfaces in java
3\. Abstract classes vs Interfaces
4\. When to use abstract class and when to use interfaces
5\. Java 8 default methods in interfaces

我将建议您先阅读抽象,因为它是抽象类和接口背后的主要力量。

1. Java 中的抽象类

用最简单的话来说,抽象类使用关键字abstract声明为抽象。 它可能包含也可能不包含任何抽象方法。 JVM 将抽象类标识为不完整类,该类尚未定义其完整行为。 声明一个类abstract仅能执行一件事:您无法创建该类的实例,仅此而已。

那么,为什么还要费心创建一个根本无法实例化的类呢? 答案在于解决某些关键设计问题的方法。 我们将在本文后面介绍这一部分。

1.1 抽象类的语法

abstract class TestAbstractClass
{
    public abstract void abstractMethod();
    public void normalMethod()
    {
        //method body
    }
}

这里,我们的TestAbstractClass有两种方法,一种是抽象方法,第二种是常规方法。 抽象方法。 在类中使用抽象方法将迫使您将类声明为抽象本身。

1.2 抽象方法

抽象方法是未就位实现的方法。 抽象方法给类增加了不完整性,因此编译器希望将整个类声明为抽象。

在应用程序中使用抽象类的唯一方法是扩展此类。 如果没有再次声明其abstract,则可以实例化其子类。 子类继承了超类的行为并且超类可以保留对子类的引用的特性在很多方面提高了抽象类的重要性。

2. Java 接口

接口是大多数 Java API 的另一个基本构建块。 您将其命名为集合,I/O 或 SWT,您可以在任何地方看到它们的运行情况。

接口定义契约,实现类需要遵守这些契约。

这些契约本质上是未实现的方法。 Java 已经有一个未实现方法的关键字,即 abstract。 Java 提供了任何类都可以实现任何接口的规定,因此接口中声明的所有方法只需公开。

2.1 接口语法

public interface TestInterface
{
    void implementMe();
}

对于上述接口,任何实现类都需要覆盖implementMe()方法。

2.2 抽象类实现接口

当实现一个接口并且不覆盖该方法时,只有一种情况,即声明实现类本身abstract

public abstract class TestMain implements TestInterface
{
    //No need to override implement Me
}

否则,必须在您的类中实现方法implementMe(),而没有任何其他异常。

public class TestMain implements TestInterface
{
    @Override
    public void implementMe() {
        // TODO Auto-generated method stub
    }
}

3)抽象类与接口

让我们记下抽象类和接口之间的区别以便快速查看:

  1. 接口的固有的所有方法是公共抽象的。 您不能通过尝试减少方法的可访问性来覆盖此行为。 您甚至不能声明静态方法。 仅公开和抽象。

    另一方面,抽象类可以灵活地声明方法。 您还可以定义受保护的可访问性的抽象方法。 此外,还可以定义静态方法,只要它们不是抽象的即可。 允许使用非抽象静态方法。

  2. 接口不能具有完全定义的方法。 根据定义,接口旨在提供唯一的契约。

    抽象类可以具有非抽象方法,没有任何限制。 您可以将任何关键字与非抽象方法一起使用,就像在其他任何类中一样。

  3. 任何想使用抽象类的类都可以使用关键字extends来扩展抽象类,而对于实现接口的关键字,则使用implements

    一个类只能扩展一个类,但可以实现任何数量的接口。 在 Java 中,此属性通常称为多继承的模拟。

  4. 接口绝对是abstract,不能实例化; Java 抽象类也无法实例化,但是可以在存在 main()的情况下调用。

接下来,如果我们既有抽象方法又有主类,可能会出现一个问题,我们可以尝试从main()调用抽象方法。 但是,此尝试将失败,因为main()方法始终是静态的,而抽象方法永远不能是静态的,因此您永远无法访问静态方法内的任何非静态方法。

4. 何时使用抽象类以及何时使用接口

始终记住,在接口或抽象类之间选择是二选一场景,在这种情况下选择未经适当分析的任何人都会产生相同的结果。 了解当前问题后,必须非常明智地做出选择。 让我们尝试在这里添加一些智慧。

4.1 抽象类的部分行为

抽象类使您可以定义一些行为。 它使它们成为应用程序框架内的优秀候选人。

让我们以HttpServlet为例。 如果要使用 Servlets 技术开发 Web 应用程序,则必须继承该类。 众所周知,每个 Servlet 都有明确的生命周期阶段,即初始化,服务和销毁。 如果我们创建了每个 servlet,我们必须一次又一次地编写关于初始化和销毁​​的同一段代码。 当然,这将是一个很大的痛苦。

JDK 设计人员通过制作HttpServlet抽象类来解决此问题。 它具有为初始化 Servlet 和销毁 Servlet 而编写的所有基本代码。 您只需要覆盖某些方法即可在其中编写与应用程序处理相关的代码。 有道理吧!

可以使用接口添加上述特性吗? 不,即使可以,对于大多数无辜的程序员来说,设计也将是一个地狱。

4.2 契约专用接口

现在,让我们看看接口的用法。 接口仅提供契约,实现类的责任是实现提供给它的每个契约。

如果您只想定义类的特征,并且要强制所有实现实体实现这些特征,则该接口是最合适的。

4.3 示例

我想以集合框架中的Map接口为例。 它仅提供规则,以及映射在实践中应如何表现。 例如它应该存储键值对,应该可以使用键等访问该值。这些规则在接口中采用抽象方法的形式。

所有实现类(例如HashMapHashTableTreeMapWeakHashMap)都不同地实现了所有方法,因此与其他方法相比具有不同的特性。

同样,接口可用于定义职责分离。 例如,HashMap实现 3 个接口:MapSerializableClonable。 每个接口定义了各自的职责,因此实现类选择要实现的对象,因此将提供有限的功能。

5. 接口中的 Java 8 默认方法

使用 Java 8,您现在可以在接口中定义方法。 这些称为默认方法。 默认方法使您可以向库的接口添加新功能,并确保与为这些接口的较早版本编写的代码二进制兼容。

顾名思义,Java 8 中的默认方法就是默认的。 如果不覆盖它们,则它们是调用者类将调用的方法。

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();		//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();		//I am running
    }
}

5.1)Java 8 中抽象类和接口之间的区别

如果您看到了,我们现在也可以提供带有接口的部分实现,就像抽象类一样。 因此,从本质上说,接口与抽象类之间的界线变得非常狭窄。 它们现在提供几乎相同的能力。

现在,只有一个大的区别是您不能扩展多个类,而可以实现多个接口。 除了这种区别之外,您还可以通过接口实现任何可能的功能,这些接口可以使抽象类成为可能,反之亦然。

希望您在 Java 中找到了有关接口和抽象类的足够信息

学习愉快!

Java extendsimplements关键字

原文: https://howtodoinjava.com/oops/extends-vs-implements/

在 Java 中,extends用于扩展类,implements用于实现接口。 这是扩展与工具之间的主要区别

1. extends关键字

在 Java 中,我们可以通过使用extend关键字对其进行扩展来继承类的字段和方法。 请注意,在 Java 中,一个类最多只能扩展一个类。

ArrayList类为例。 它扩展了AbstractList类,又扩展了AbstractCollection类。

因此,基本上ArrayList类具有其父类AbstractListAbstractCollection的方法和行为。

AbstractCollection提供了诸如contains(Object o)toArray()remove(Object o)等方法。而AbstractList类提供了add()indexOf()lastIndexOf()clear()等。同样一些方法被ArrayList覆盖。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
	//code
}

Java 扩展示例

让我们从头开始创建一些东西,以更好地了解使用extends关键字的 Java 继承的工作方式。 我创建了两个类 – ParentClassChildClass,其中ChildClass扩展ParentClass

public class ParentClass {

	public int dataVal = 100;

	public int getDataVal() {
		return this.dataVal;
	}
}

public class ChildClass extends ParentClass 
{

}

我没有向ChildClass添加任何字段或方法,只是为了表明即使我们不对其添加任何内容,它仍然具有其父类的行为。

public class Main 
{
	public static void main(String[] args) 
	{
		ChildClass child = new ChildClass();

		System.out.println( child.dataVal );
		System.out.println( child.getDataVal() );
	}
}

程序输出。

100
100

2. implements关键字

接口是在 Java 中强制执行契约的方法。 它们强制实现类提供某种行为。 要实现接口,类必须使用实现关键字

在 Java 中,我们可以实现多个接口。 在这种情况下,类必须实现所有接口的所有方法。 (或将其声明为抽象)。

再看一次ArrayList类声明。 它实现了 4 个接口,即ListRandomAccessCloneableSerializable。 它已经在给定的接口中实现了所有方法。

Java 实现示例

与前面的示例类似,让我们创建一些基本的知识来了解接口实现的外观。 我创建了两个接口MovableSwimmable。 两个接口都定义一种方法。

Human实现两个接口,因此它必须实现在两个接口中定义的方法。

public interface Movable {

	public void move();
}

public interface Swimmable
{
	public void swim();
}

public class Human implements Movable, Swimmable 
{
	@Override
	public void swim() {
		System.out.println("I am swimming");
	}

	@Override
	public void move() {
		System.out.println("I am moving");
	}
}

现在,我们将测试Human类及其强制行为。

public class Main 
{
	public static void main(String[] args) 
	{
		Human obj = new Human();

		obj.move();
		obj.swim();
	}
}

程序输出:

I am moving
I am swimming

显然,Human类实现了两个接口并定义了它们的行为。 这就是 Java 接口的全部目的。

3. 扩展与实现之间的差异

根据上述示例,让我们列出 Java 中extendsimplements关键字之间的区别

  1. extends关键字用于继承类; implements关键字用于继承接口。
  2. 一类只能扩展一个类。 但可以实现任意数量的接口。
  3. 扩展超类的子类可能会覆盖超类中的某些方法。 一个类必须实现接口中的所有方法。

学习愉快!

Java instanceof运算符

原文: https://howtodoinjava.com/oops/java-instanceof/

Java 实例运算符(也称为类型比较运算符)用于测试对象是否为指定类型(类,子类或接口)的实例。

它返回:

  • true - 如果变量是指定类的实例,则它是父类或实现指定接口或父接口
  • false - 如果变量不是类的实例或接口的实现; 或变量为空

1. Java instanceof语法

instanceof运算符将变量测试为指定的类型。 变量写在运算符的左侧,类型在运算符的右侧。

//<object-reference> instanceof TypeName

boolean value = var instanceof ClassType;

//or

if(var instanceof ClassType) {
	//perform some action
}

2. Java instanceof示例

让我们看一个示例,以充分了解instanceof运算符用于比较类型的用法。 在此示例中,我们使用ArrayList 类测试其类型信息。

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class Main 
{
	public static void main(String[] args) 
	{
		ArrayList<String> arrayList = new ArrayList<>();

		System.out.println(arrayList instanceof ArrayList);		//true

		System.out.println(arrayList instanceof AbstractList);	//true

		System.out.println(arrayList instanceof List);			//true

		System.out.println(arrayList instanceof Collection);	//true

		System.out.println(null instanceof ArrayList);			//false

		//System.out.println(arrayList instanceof LinkedList);	//Does not compile
	}
}

程序输出。

true
true
true
true
false

3. Java instanceof和数组

在 Java 中,数组也被视为对象,并具有与之关联的字段和方法。 因此,我们也可以将instanceof运算符与数组一起使用。

  • 基本数组Object和自身类型的实例。 例如int[]Objectint[]的类型。 两种比较均返回true
  • 对象数组是对象,对象数组,类类型数组,父类类型数组的类型。 例如Integer[]ObjectObject[]Integer[]Number []Integer extends Number)的类型。
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class Main 
{
	public static void main(String[] args) 
	{
		int[] intArr = new int[3];
		float[] floatArr = new float[3];

		Integer[] intObjArr = new Integer[3];
		Float[] floatObjArr = new Float[3];
		String[] stringArr = new String[3];

		System.out.println(intArr instanceof Object);		//true
		System.out.println(intArr instanceof int[]);		//true

		System.out.println(floatArr instanceof Object);		//true
		System.out.println(floatArr instanceof float[]);	//true

		System.out.println(intObjArr instanceof Object);	//true
		System.out.println(intObjArr instanceof Object[]);	//true
		System.out.println(intObjArr instanceof Integer[]);	//true
		System.out.println(intObjArr instanceof Number[]);	//true

		System.out.println(floatObjArr instanceof Float[]);	//true
		System.out.println(stringArr instanceof String[]);	//true
	}
}

程序输出:

true
true
true
true
true
true
true
true
true
true

4. 使用instanceof正确进行类型转换

一个使用instanceof运算符的现实示例可以将变量类型转换为另一种类型。instanceof运算符有助于在运行时避免ClassCastException

考虑以下示例,其中我们尝试将列表类型转换为LinkedList类,其中原始变量的类型为ArrayList。 它将抛出ClassCastException

List<String> list = new ArrayList<>();

LinkedList<String> linkedList = (LinkedList<String>) list;

为了正确地转换变量,我们可以使用instanceof运算符。 它不会导致ClassCastException

List<String> list = new ArrayList<>();

if(list instanceof LinkedList) 
{
	LinkedList<String> linkedList = (LinkedList<String>) list;

	//application code
}

向我提供有关用于类型比较的 Java instanceof运算符的问题。

学习愉快!

Java 中的多重继承

原文: https://howtodoinjava.com/oops/multiple-inheritance-in-java/

正如我们早已了解到的那样,Java 中不直接支持多重继承,而直到 Java 7 才支持。 Java 8 可以通过不带有菱形问题默认方法实现多继承的概念。

让我们看看如何?

Table of Contents

1\. What are default methods?
2\. How multiple inheritance is achieved via default methods?
3\. Possible conflicts and their resolutions

1. 什么是默认方法?

如果您有足够长的时间从事 Java 编程,您可能会意识到在现有接口中添加新方法可能会很痛苦。 您将需要在实现该接口的 java 类中实现该新方法。 这真的很难。 好吧,Java 8 带来了默认方法,可以在完全相同的情况下为您提供帮助。

默认方法使您可以向接口添加新功能,并确保向后兼容实现该接口的现有类。

顾名思义,接口中的默认方法是默认情况下将调用的方法 - 如果在实现类时未覆盖。 让我们看一个例子。

Moveable接口是一些现有接口,并且想要添加新方法moveFast()。 如果它使用旧技术添加了moveFast()方法,则实现Moveable的所有类也将被更改。 因此,我们将moveFast()方法添加为默认方法。

public interface Moveable 
{
    default void moveFast()
    {
        System.out.println("I am moving fast, buddy !!");
    }
}

如果所有实现Moveable接口的类都不需要自己更改(直到某些类专门想要覆盖moveFast()方法以添加自定义逻辑)。 所有类都可以直接调用instance.moveFast()方法。

public class Animal implements Moveable
{
    public static void main(String[] args)
    {
        Animal tiger = new Animal();

        //Call default method using instance reference
        tiger.moveFast();
    }
}

2. 如何通过默认方法实现多重继承?

多重继承是某些面向对象的计算机编程语言的特性,其中对象或类可以从多个父对象或父类继承特征和行为。

我们知道在 Java 中(直到 jdk 7),extends关键字支持 Java 中的继承,该关键字用于从父类创建子类。 您不能从两个类扩展。

在 Java 7 之前,接口仅用于声明必须实现哪些实现类的协定(除了abstract本身之外的实现类)。 因此,类可以继承的接口没有附加特定的行为。 因此,即使在一个类能够实现所需数量的接口之后,将其称为多重继承也是不合适的。

但是由于 Java 8 是默认方法,所以接口也具有行为。 因此,现在如果一个类实现了两个接口并且都定义了默认方法,那么它实质上是从两个父级继承行为,即多重继承

例如,在下面的代码Animal中,类未定义其自身的行为; 而是从父接口继承行为。 那是多重继承。

package com.howtodoinjava.examples;

interface Moveable
{
    default void moveFast(){
        System.out.println("I am moving fast, buddy !!");
    }
}

interface Crawlable
{
    default void crawl(){
        System.out.println("I am crawling !!");
    }
}

public class Animal implements Moveable, Crawlable 
{
    public static void main(String[] args) 
    {
        Animal self = new Animal();

        self.moveFast();
        self.crawl();
    }
}

3. 多重继承期间可能发生的冲突

在上面的示例中,我们有两个不同的接口和两个不同的方法 – 因此没有冲突。 如果两个接口都决定定义一个具有相同名称的新方法,该怎么办? 好了,他们可以毫无问题地进行定义。 但是,当Animal实例调用其名称时,将调用哪种方法。

那是矛盾的情况。

package com.howtodoinjava.examples;

interface Moveable
{
    default void run(){
        System.out.println("I am running, kid !!");
    }
}

interface Crawlable
{
    default void run(){
        System.out.println("I am running, daddy !!");
    }
}

public class Animal implements Moveable, Crawlable 
{
    public static void main(String[] args) 
    {
        Animal self = new Animal();

        //What will happen when below statement will execute
        //self.run();
    }
}

因此,要解决上述冲突,调用者类必须确定要调用的run()方法,然后使用如下所示的接口引用来调用。

Moveable.super.run();   //Call Moveable's run() method

//or 

Crawlable.super.run();  //Call Crawlable's run() method

这就是使用默认方法获得的 Java 8 多重继承特性的全部知识。

学习愉快!

关联,聚合和组合

原文: https://howtodoinjava.com/oops/association-aggregation-composition/

关联,聚合和组合是类在面向对象编程中可以具有的三种关系。 让我们了解它们之间的区别。

Table of Contents

1\. Association
2\. Aggregation
3\. Composition
4\. Summary

1. Java 关联

我们称其为关联的关系,这些关系的对象具有独立的生命周期,而在对象之间没有所有权

让我们以老师和学生为例。 多个学生可以与一个老师联系,一个学生可以与多个老师联系,但是两者都有自己的生命周期(可以独立创建和删除); 因此,当教师离开学校时,我们不需要删除任何学生,而当学生离开学校时,我们不需要删除任何老师。

association

2. Java 聚合

我们将那些对象具有独立生命周期但有所有权且子对象不能属于另一个父对象的关系称为聚合

让我们以手机和手机电池为例。 一块电池可以属于一部电话,但是如果该电话停止工作,并且我们从数据库中删除了该电池,则由于该电池仍可以工作,因此不会删除该电池。 因此,总的来说,尽管拥有所有权,但是对象具有自己的生命周期。

aggregation

3. Java 组合

我们使用术语组合来指代其对象没有独立生命周期的关系,而如果删除了父对象,则所有子对象也将被删除

让我们以问题和答案之间的关系为例。 单个问题可以有多个答案,并且答案不能属于多个问题。 如果我们删除问题,答案将自动删除。

composition

4. 总结

有时,决定我们应该使用关联,聚合还是组合可能是一个复杂的过程。 造成此困难的部分原因是聚合和组合是关联的子集,这意味着它们是关联的特定情况。

Association, Aggregation and Composition Relationship

关联,聚合和构成关系

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

学习愉快!

Java 并发指南

Java 并发教程

原文: https://howtodoinjava.com/java-concurrency-tutorial/

简而言之,并发是并行运行多个程序或程序的多个部分的能力。 并发使程序可以通过利用底层操作系统和机器硬件的未开发能力来实现高性能和吞吐量。 例如现代计算机在一个 CPU 中具有多个 CPU 或多个内核,程序可以将所有内核用于处理的某些部分; 因此,与顺序处理相比,可以更早地完成任务。

Java 并发的主干是线程。 线程是一个轻量级进程,它具有自己的调用栈,但可以访问同一进程中其他线程的共享数据。 Java 应用程序默认在一个进程中运行。 在 Java 应用程序中,您可以使用许多线程来实现并行处理或并发。

是什么使 Java 应用程序并发?

第一个类是java.lang.Thread类,您需要使一个 Java 类并发。 此类是 Java 中所有并发概念的基础。 然后,您具有java.lang.Runnable接口,可以从线程类中抽象出线程行为。

您可以在 Java 1.5 中添加的java.util.concurrent包中找到构建高级应用程序所需的其他类。

阅读更多: Java 并发演化

Java 并发真的那么简单吗?

上面的描述给人的印象是并发确实是一个好概念,并且很容易实现。 好吧,不是。 它需要对基本概念有足够的了解,并且需要对应用程序目标有清晰的了解。

与单线程应用程序相比,并发应用程序通常具有更复杂的设计。 由多个线程访问共享数据执行的代码需要特别注意。 错误线程同步引起的错误很难检测,重现和修复。 它们通常在较高的环境(例如生产环境)中显示,有时在较低的环境中不可能复制错误。

除了复杂的缺陷外,并发还需要更多资源来运行应用程序。 因此,请确保您的小猫咪有足够的资源。

Java 并发教程

在单个帖子中涵盖整个 Java 并发几乎是不可能的。 因此,我在下面的 Java 并发教程中编写了一篇文章,讨论了一个单独的概念。 浏览这些教程,如果您有任何问题或建议,请告诉我。

Java 并发基础

并发演化

什么是线程安全性?

对象级锁定和类级锁定

比较和交换(CAS)算法

wait()notify()notifyAll()方法

之间的区别

Runnable实现”和“扩展线程”之间的区别

锁和监视器之间的区别

yield()join()之间的区别 [

sleep()wait()之间的区别?

执行器框架

执行器框架教程

ScheduledThreadPoolExecutor示例

FixedSizeThreadPoolExecutor示例

ThreadPoolExecutor示例

Runnable+Future的示例

使用ThreadPoolExecutorSemaphore限制任务提交率

BlockingQueue示例

UncaughtExceptionHandler示例

并发

ForkJoinPool示例

CountDownLatch示例

使用信号量

BinarySemaphore

Java.util.concurrent.locks.Lock

java.util.concurrent.ThreadFactory

线程局部变量

线程间通信

并发集合

ConcurrentHashMap示例

ConcurrentLinkedDeque示例

杂项

创建和解决死锁

学习愉快!

Java 多线程的发展和主题

原文: https://howtodoinjava.com/java/multi-threading/java-multi-threading-evolution-and-topics/

我们的一位读者 Anant 提出了一个非常好的问题,详细阐述/列出了我们应该了解的有关多线程的所有相关主题,包括在 Java 8 中所做的更改(从入门级到高级)。 他想知道的就是 Java 中的多线程框架从简单的Runnable接口到 Java 8 的最新特性的演变。让我们解决他的查询。

我花了大量时间收集以下所有信息。 因此,如果您在任何时候有其他疑问,请随时建议在以下信息中进行编辑/更新。

每个 JDK 发行版的多线程概念

根据 JDK 1.x 发行版,在此初始发行版中仅存在很少的类。 具体来说,这些类/接口是:

  • java.lang.Thread
  • java.lang.ThreadGroup
  • java.lang.Runnable
  • java.lang.Process
  • java.lang.ThreadDeath
  • 和一些异常类

例如

  1. java.lang.IllegalMonitorStateException
  2. java.lang.IllegalStateException
  3. java.lang.IllegalThreadStateException

它还有很少的同步集合,例如 java.util.Hashtable

JDK 1.2JDK 1.3 没有与多线程相关的明显变化。 (如果我错过任何事情,请纠正我)。

JDK 1.4 ,几乎没有 JVM 级别更改,可以通过一次调用挂起/恢复多个线程。 但是没有出现重大的 API 更改。

JDK 1.5 是 JDK 1.x 之后的第一个大版本; 它包括多个并发工具。 Executorsemaphoremutexbarrierlatchesconcurrent collectionsblocking queues; 所有内容均包含在此版本本身中。 Java 多线程应用程序云的最大变化发生在此版本中。

阅读此链接中的全部更改: http://docs.oracle.com/javase/1.5.0/docs/guide/concurrency/overview.html

JDK 1.6 更多的是平台修复,而不是 API 升级。 因此,JDK 1.6 中出现了新变化。

JDK 1.7 添加了对ForkJoinPool的支持,该支持实现了工作窃听技术以使吞吐量最大化。 还添加了Phaser类。

JDK 1.8 因 Lambda 更改而广为人知,但并发更改也很少。 在java.util.concurrent包中添加了两个新接口和四个新类。 CompletableFutureCompletionException

集合框架在 Java 8 中进行了重大修订,以基于新添加的流工具lambda 表达式添加聚合操作; 导致在几乎所有Collection类中以及在并发集合中添加了大量方法。

阅读此链接中的全部更改: http://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/changes8.html

参考文献:

我希望上面的清单可以帮助您理解多线程特性(每个 JDK 版本)。

学习愉快!

Java 并发性 – 线程安全性?

原文: https://howtodoinjava.com/java/multi-threading/what-is-thread-safety/

定义线程安全性非常棘手。 快速的 Google 搜索会发现许多类似的“定义”:

  1. 线程安全代码是即使许多线程同时执行也可以运行的代码。
  2. 如果一段代码仅以保证多个线程同时安全执行的方式操作共享数据结构,则它是线程安全的。

并且还有更多类似的定义。

您难道不认为上述定义实际上并没有传达任何有意义的信息,甚至还会增加一些混乱。 尽管不能完全排除这些定义,因为它们没有错。 但是事实是他们没有提供任何实际的帮助或观点。 我们如何在线程安全类和不安全类之间区分?“安全”甚至意味着什么?

什么是线程安全的正确性?

线程安全性的任何合理定义的核心是正确性的概念。 因此,在了解线程安全性之前,我们应该首先了解“正确性”。

正确性是指一个类符合其规范。

您将同意,良好的类规范将在任何给定的时间获取有关类状态的所有信息,并且如果对类状态进行了某些操作,则它是后置条件。 由于我们经常没有为我们的类编写足够的规范,我们怎么可能知道它们是正确的? 我们不能,但是一旦我们说服自己“ 该代码有效”,这仍然不会阻止我们使用它们。 这个“代码置信度”与我们许多人接近正确性的程度差不多。

将“正确性”乐观地定义为可以识别的东西之后,我们现在可以以一种不太循环的方式定义线程安全性:当从多个线程中访问时,如果类继续正确运行,则该类是线程安全的。

如果一个类在从多个线程访问时能正确运行,则无论该线程在运行时环境中对这些线程的执行进行调度或交织,并且在调用代码部分没有任何其他同步或其他协调的情况下,如果该类行为正确,则该线程是线程安全的。

如果此处对“正确性”的宽松使用使您感到困扰,则您可能更喜欢认为线程安全类在并发环境中比在单线程环境中不会被破坏。 线程安全类封装了所有需要的同步,因此客户端不需要提供自己的同步。

示例:无状态 Servlet

线程安全类的一个很好的例子是 java servlet,它没有字段和引用,也没有其他类的字段等。它们是无状态

public class StatelessFactorizer implements Servlet 
{
    public void service(ServletRequest req, ServletResponse resp) 
	{
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        encodeIntoResponse(resp, factors);
    }
}

特定计算的暂态仅存在于局部变量中,这些局部变量存储在线程的栈中,并且只有执行线程才能访问。 一个线程访问StatelessFactorizer不会影响另一线程访问同一StatelessFactorizer的结果; 因为两个线程不共享状态,所以好像它们正在访问不同的实例。 由于线程访问无状态对象的动作不会影响其他线程中操作的正确性,因此无状态对象是线程安全的。

这就是围绕“什么是线程安全?”这个小而重要的概念的全部内容。

祝您学习愉快!

并发与并行

原文: https://howtodoinjava.com/java/multi-threading/concurrency-vs-parallelism/

并发表示多个任务,这些任务在重叠的时间段内以无特定顺序启动,运行和完成。 并行性是指多个任务或唯一任务的多个部分在字面上同时运行的情况,例如在多核处理器上。 请记住,并发和并行性不是一回事。

让我们更详细地了解当我说并发与并行时的意思。

并发

当我们谈论至少两个或更多任务时,并发本质上是适用的。 当一个应用程序实际上可以同时执行两个任务时,我们将其称为并发应用程序。 尽管这里的任务看起来像是同时运行的,但实际上它们可能不一样。 它们利用操作系统的 CPU 时间分段特性,其中每个任务运行其任务的一部分,然后进入等待状态。 当第一个任务处于等待状态时,会将 CPU 分配给第二个任务以完成其一部分任务。

因此,操作系统根据任务的优先级分配 CPU 和其他计算资源,例如记忆; 依次处理所有任务,并给他们完成任务的机会。 对于最终用户,似乎所有任务都是并行运行的。 这称为并发。

并行

并行不需要两个任务存在。 通过使用 CPU 的多核基础结构,通过为每个任务或子任务分配一个内核,它实际上可以同时运行部分任务或多个任务。

并行性本质上要求具有多个处理单元的硬件。 在单核 CPU 中,您可能会获得并发性,但不能并行化。

并发与并行之间的差异

现在,让我们列出并发和并行之间的显着差异。

并发是两个任务可以在重叠的时间段内启动,运行和完成的时间。 并行是指任务实际上在同一时间运行,例如。 在多核处理器上。

并发是独立执行进程的组成,而并行是同时执行(可能相关)计算。

并发是关于一次处理很多事物的问题。 并行性是关于一次完成很多事情

一个应用程序可以是并发的,但不能是并行的,这意味着它可以同时处理多个任务,但是没有两个任务可以同时执行。

一个应用程序可以是并行的,但不能是并发的,这意味着它可以同时处理多核 CPU 中一个任务的多个子任务。

应用程序既不能是并行的,也不能是并发的,这意味着它一次顺序地处理所有任务。

一个应用程序可以是并行的,也可以是并发的,这意味着它可以同时在多核 CPU 中同时处理多个任务。

就是并发与并行,这是 Java 多线程概念中非常重要的概念。

学习愉快!

Java 包装器类 – 自动装箱,拆箱和转换示例

原文: https://howtodoinjava.com/java/basics/java-wrapper-classes/

了解 Java 包装器类的用法,它们在原始和对象之间的转换; 以及带有示例的自动装箱和拆箱。

1. Java 包装器类

Java 提供类型包装器,它们是封装对象内原始类型的类。 在 Java 中,我们有 8 种原始数据类型

包装器类将数据类型包装(封装)并赋予其对象外观。 在任何需要数据类型作为对象的地方,都可以使用此对象。 包装器类包括解包装对象并返回数据类型的方法。

类型包装器类是java.lang包的一部分。

每个基本类型都有一个对应的包装器类。

基本类型 包装类
double Double
float Float
long Long
int Integer
short Short
byte Byte
char Character
boolean Boolean

2. 何时使用包装器类

在场景中使用 Java 包装器类:

  • 当两个方法要引用基本类型的相同实例时,请将包装器类作为方法参数传递。
  • Java 中的泛型仅适用于对象,不支持原始类型。
  • Java 中的集合仅处理对象。 要将原始类型存储在这些类之一中,您需要将原始类型包装在一个类中。
  • 要从数据类型引用null时,需要对象。 原始类型不能将null作为值。

3. 转换

3.1 包装器类的原始类型

有两种方法可以将原始类型转换为相应包装类的对象:

  1. 使用构造器
  2. 使用静态工厂方法(例如valueOf())(字符除外)
// 1\. using constructor
Integer object = new Integer(10);

// 2\. using static factory method
Integer anotherObject = Integer.valueOf(10);

在上面的示例中, valueOf()方法返回一个表示指定的int值的实例。

同样,我们可以将其他基本类型转换,例如将boolean转换为Boolean,将char转换为Character,将short转换为Short等。

Java 包装器类使用内部缓存,该缓存返回缓存的值,从而使其高效且在内存上明智。

3.2 包装器类到原始类型

从包装器转换为原始类型很简单。 我们可以使用相应的方法来获取基本类型。 例如intValue()doubleValue()shortValue()等。

Integer object = new Integer(10);

int val = object.intValue();	//wrapper to primitive

4. 自动装箱和拆箱

从 JDK 5 开始,Java 添加了两个重要特性:自动装箱自动拆箱

4.1 自动装箱

自动装箱是将原始类型自动转换为其对应的对象包装器类的方法。 例如,将int转换为Integer,将char转换为Character,依此类推。

我们可以简单地将原始类型传递或分配给接受包装类对象的参数或引用。 例如

Character ch = 'a';		//char to Character

List<Integer> integerList = new ArrayList<>();

for (int i = 1; i < 10; i ++) 
{
    integerList.add(i);		//int to Integer
}

integerListInteger对象的列表,而不是原始类型int值的列表。 编译器会根据i自动创建一个Integer对象,并将该对象添加到integerList中。 因此,前面的代码在运行时变成了下面的代码:

List<Integer> integerList = new ArrayList<>();

for (int i = 1; i < 50; i += 2) 
{
    integerList.add(Integer.valueOf(i));		//autoboxing
}

4.2 拆箱

拆箱是在从包装器类转换为其对应的原始类型时发生的。 这意味着我们可以将包装对象传递或分配给接受原始类型的参数或引用。 例如

public static int sumOfEven(List<Integer> integerList) 
{
    int sum = 0;
    for (Integer i: integerList) {
    	if (i % 2 == 0)
            sum += i;			//Integer to int
    }
    return sum;
}

在上面的示例中,余数(%)和一元加(+=)运算符不适用于Integer对象。 编译器在运行时通过调用intValue()方法自动将Integer转换为int

通过自动装箱和拆箱,开发人员可以编写更干净的代码,从而更易于阅读。

学习愉快!

阅读更多:

SO 帖子

Java 比较和交换示例 – CAS 算法

原文: https://howtodoinjava.com/java/multi-threading/compare-and-swap-cas-algorithm/

Java 5 中最好的补充之一是AtomicIntegerAtomicLong等类中支持的原子操作。这些类可帮助您将复杂的(不必要的)多线程用于一些基本操作的代码,例如递增或递减在多个线程之间共享的值。 这些类在内部依赖于名为 CAS(比较和交换)的算法。 在本文中,我将详细讨论这个概念。

1. 乐观锁和悲观锁

传统的锁定机制,例如在 Java 中使用同步的关键字的被称为锁定或多线程的悲观技术。 它要求您首先保证在特定操作之间没有其他线程会干扰(即锁定对象),然后仅允许您访问任何实例/方法。

这就像说“请先关上门; 否则,其他骗子会进来重新整理您的东西。”

尽管上述方法是安全的并且确实有效,但是在性能上对您的应用程序造成了重大损失。 原因很简单,等待线程无法做任何事情,除非它们也有机会执行受保护的操作。

还有一种方法在性能上更有效,并且本质上是乐观的。 通过这种方法,您可以进行更新,希望可以完成更新而不会受到干扰。 此方法依靠冲突检测来确定在更新期间是否存在来自其他方的干扰,在这种情况下,操作将失败并且可以重试(或不重试)。

乐观的方法就像老话所说:“获得宽容比得到许可更容易”,这里的“轻松”意味着“更有效率”。

比较并交换是这种乐观方法的一个很好的例子,我们将在下面讨论。

2. 比较和交换算法

该算法将存储位置的内容与给定值进行比较,并且只有它们相同时,才会将该存储位置的内容修改为给定的新值。 这是作为单个原子操作完成的。 原子性保证了根据最新信息计算新值; 如果与此同时值已由另一个线程更新,则写入将失败。 操作的结果必须表明它是否执行了替换; 这可以通过简单的布尔响应(此变量通常称为“比较设置”)来完成,也可以通过返回从内存位置读取的值(而不是写入该值)来完成。

CAS 操作有 3 个参数:

  1. 必须替换值的存储位置V
  2. 线程上次读取的旧值A
  3. 应该写在V上的新值B

CAS 说:“我认为V应该具有值A; 如果可以,则将B放在此处,否则不要更改它,但要告诉我我错了。” CAS 是一种乐观技术,它希望成功进行更新,并且自从上次检查变量以来,如果另一个线程更新了该变量,则可以检测到失败。

3. Java 比较和交换示例

让我们通过一个例子来了解整个过程。 假设V是存储值“10”的存储位置。 有多个线程想要递增此值并将递增的值用于其他操作,这是一种非常实际的方案。 让我们分步介绍整个 CAS 操作:

1)线程 1 和 2 想要增加它,它们都读取值并将其增加到 11。

V = 10, A = 0, B = 0

2)现在线程 1 首先出现,并将V与最后读取的值进行比较:

V = 10, A = 10, B = 11

if     A = V
   V = B
 else
   operation failed
   return V

显然,V的值将被覆盖为 11,即操作成功。

3)线程 2 到来并尝试与线程 1 相同的操作

V = 11, A = 10, B = 11

if     A = V
   V = B
 else
   operation failed
   return V

4)在这种情况下,V不等于A,因此不替换值,并且返回V的当前值即 11。 现在线程 2,再次使用以下值重试此操作:

V = 11, A = 11, B = 12

而这一次,条件得到满足,线程 2 返回增量值 12。

总而言之,当多个线程尝试使用 CAS 同时更新同一变量时,一个将获胜并更新该变量的值,而其余则将丢失。 但是失败者并不会因为线程中断而受到惩罚。 他们可以自由地重试该操作,或者什么也不做。

这就是与 Java 支持的原子操作有关的这个简单但重要的概念的全部。

祝您学习愉快!

Java synchronized关键字

原文: https://howtodoinjava.com/java/multi-threading/java-synchronized/

Java synchronized关键字将块或方法标记为临界区。 临界区是一次仅执行一个线程的线程,该线程持有同步部分的锁。

synchronized关键字有助于编写应用程序的并发部分,以保护此块中的共享资源。

synchronized关键字可以用于:

  • 一个代码块
  • 一个方法

1. Java 同步块

1.1 语法

编写同步块的一般语法如下。 此处 lockObject是对对象的引用,该对象的锁与同步语句表示的监视器关联。

synchronized( lockObject ) 
{
   // synchronized statements
}

1.2 内部工作

当线程要在同步块内执行同步语句时,必须获得lockObject监视器的锁定。 一次,只有一个线程可以获取锁对象的监视器。 因此,所有其他线程必须等到当前已获得锁的该线程完成执行。

通过这种方式,synchronized关键字可确保一次仅一个线程将执行同步的块语句,从而防止多个线程破坏该块内部的共享数据。

请记住,如果一个线程进入睡眠状态(使用sleep()方法),则它不会释放锁。 在此休眠时间,没有线程将执行同步的块语句。

如果'synchronized (lock)'中使用的锁定对象为null,则 Java 同步将抛出NullPointerException

1.3 Java 同步块示例

Java 程序演示同步块的用法。 在给定的示例中,我们具有使用方法printNumbers()MathClass。 此方法将打印从 1 到参数 N 的数字。

请注意,printNumbers()方法中的代码在同步块内部。

public class MathClass 
{
    void printNumbers(int n) throws InterruptedException 
    {
        synchronized (this) 
        {
            for (int i = 1; i <= n; i++) 
            {
                System.out.println(Thread.currentThread().getName() + " :: "+  i);
                Thread.sleep(500);
            }
        }
    }
}

我创建了两个线程,它们开始完全同时执行printNumbers()方法。 由于块被同步,因此仅允许一个线程访问,而另一个线程必须等待直到第一个线程完成。

public class Main 
{
    public static void main(String args[]) 
    {
        final MathClass mathClass = new MathClass();

        //first thread
        Runnable r = new Runnable() 
        {
            public void run() 
            {
                try {
                    mathClass.printNumbers(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        new Thread(r, "ONE").start();
        new Thread(r, "TWO").start();
    }
}

程序输出。

ONE :: 1
ONE :: 2
ONE :: 3

TWO :: 1
TWO :: 2
TWO :: 3

2. Java 同步方法

2.1 语法

编写同步方法的一般语法如下。 此处lockObject是对对象的引用,该对象的锁与同步语句表示的监视器关联。

<access modifier> synchronized method( parameters ) 
{
    // synchronized code
}

2.2 内部工作

与同步块类似,线程必须使用同步方法来获取关联的监视对象上的锁。 如果采用同步方法,则锁定对象为:

  • .class对象 - 如果该方法是静态的。
  • this对象 - 如果方法不是静态的。 this是指在其中调用同步方法的当前对象的引用。

阅读更多: Java 中的对象级别锁与类级别锁

Java synchronized关键字本质上是重入符,这意味着如果同步方法调用了另一个需要相同锁定的同步方法,则持有锁定的当前线程可以进入该方法而无需获取锁定。

2.3 Java 同步方法示例

与同步块示例类似,我们可以在printNumber()方法上应用synchronized关键字,这将使该方法成为同步方法。 现在,如果再次运行示例,我们将获得类似的输出。

public class MathClass 
{
    synchronized void printNumbers(int n) throws InterruptedException 
    {
        for (int i = 1; i <= n; i++) 
        {
            System.out.println(Thread.currentThread().getName() + " :: "+  i);
            Thread.sleep(500);
        }
    }
}

程序输出:

ONE :: 1
ONE :: 2
ONE :: 3

TWO :: 1
TWO :: 2
TWO :: 3

在评论中把您的问题交给我。

学习愉快!

Java 中的对象级别锁与类级别锁

原文: https://howtodoinjava.com/java/multi-threading/object-vs-class-level-locking/

在 Java 中,同步的代码块一次只能由一个线程执行。 另外,java 支持同时执行多个线程。 这可能会导致两个或多个线程同时访问相同的字段或对象。

同步是使所有并发线程在执行中保持同步的过程。 同步避免了由于共享内存视图不一致而导致的内存一致性错误。 当方法声明为同步时,该线程持有该方法对象的监视器或锁定对象。 如果另一个线程正在执行同步的方法,则该线程将被阻塞,直到该线程释放监视器。

请注意,我们可以在类中的已定义方法或块上使用synchronized关键字。 synchronized关键字不能与类定义中的变量或属性一起使用。

1. Java 中的对象级别锁定

对象级别锁定是一种机制,当我们要同步非静态方法非静态代码块时,仅一个线程将能够执行给定类实例上的代码块。 应始终执行它,来使实例级数据线程安全。

对象级别锁定可以通过以下方式完成:

public class DemoClass
{
	public synchronized void demoMethod(){}
}

or

public class DemoClass
{
	public void demoMethod(){
		synchronized (this)
		{
			//other thread safe code
		}
	}
}

or

public class DemoClass
{
	private final Object lock = new Object();
	public void demoMethod(){
		synchronized (lock)
		{
			//other thread safe code
		}
	}
}

2. Java 中的类级别锁定

类级别锁防止在运行时该类的所有可用实例中的多个线程进入synchronized块。 这意味着,如果在运行时有DemoClass的 100 个实例,则一次只能在一个实例中的任何一个线程上执行demoMethod(),而所有其他实例将被其他线程锁定。

应该始终执行类级别锁定以使静态数据线程安全。 我们知道 静态 关键字将方法的数据关联到类级别,因此请使用对静态字段或方法的锁定使其在类级别上。

public class DemoClass
{
	//Method is static
	public synchronized static void demoMethod(){

	}
}

or

public class DemoClass
{
	public void demoMethod()
	{
		//Acquire lock on .class reference
		synchronized (DemoClass.class)
		{
			//other thread safe code
		}
	}
}

or

public class DemoClass
{
	private final static Object lock = new Object();

	public void demoMethod()
	{
		//Lock object is static
		synchronized (lock)
		{
			//other thread safe code
		}
	}
}

3. 对象级别锁与类级别锁 – 重要说明

  1. Java 中的同步保证了没有两个线程可以同时或并发执行需要相同锁的同步方法。
  2. synchronized关键字只能与方法和代码块一起使用。 这些方法或块都可以是静态非静态
  3. 每当线程进入 Java synchronized方法或块时,它都会获取一个锁,而当线程离开同步方法或块时,它将释放该锁。 即使线程在完成后或由于任何错误或异常而离开同步方法时,也会释放锁定。
  4. Java synchronized关键字本质上是重入者,这意味着如果同步方法调用了另一个需要相同锁的同步方法,则持有锁的当前线程可以进入该方法而无需获取锁。
  5. 如果在同步块中使用的对象为 null,则 Java 同步将抛出NullPointerException。 例如,在以上代码示例中,如果将锁初始化为 null,则“ synchronized (lock)”将抛出NullPointerException
  6. Java 中的同步方法使您的应用程序性能降低。 因此,在绝对需要时使用同步。 另外,请考虑使用同步的代码块仅同步代码的关键部分。
  7. 静态同步方法和非静态同步方法都可能同时或同时运行,因为它们锁定在不同的对象上。
  8. 根据 Java 语言规范,不能将synchronized关键字与构造器一起使用。 这是非法的,并导致编译错误。
  9. 不要在 Java 中的同步块上的非 final 字段上进行同步。 因为对非 final 字段的引用可能随时更改,然后不同的线程可能会在不同的对象上进行同步,即完全没有同步。
  10. 不要使用 String 字面值,因为它们可能在应用程序中的其他地方被引用,并且可能导致死锁。 使用new关键字创建的字符串对象可以安全使用。 但是,作为最佳实践,请在我们要保护的共享变量本身上创建一个新的私有范围内的Object实例 OR 锁。(感谢 Anu 在评论中指出这一点。)

让我知道有关 Java 中对象级别锁与类级别锁的想法和查询。

学习愉快!

Java 中RunnableThread之间的区别

原文: https://howtodoinjava.com/java/multi-threading/java-runnable-vs-thread/

众所周知,在 Java 语言中,有两种创建线程的方法。 一种使用Runnable接口,另一种通过扩展线程类。 让我们确定两种方式之间的区别,即扩展线程和实现Runnable

1. 使用Runnable接口 vs Thread类创建线程

让我们快速检查一下这两种技术的 Java 代码。

1.1 Runnable接口

通过实现Runnable接口来创建线程的 Java 程序。

public class DemoRunnable implements Runnable {
    public void run() {
    	//Code
    }
}

//start new thread with a "new Thread(new demoRunnable()).start()" call

1.2 线程类

通过扩展线程类来创建线程的 Java 程序。

public class DemoThread extends Thread {
    public DemoThread() {
    	super("DemoThread");
    }
    public void run() {
    	//Code
    }
}
//start new thread with a "new demoThread().start()" call

2. RunnableThread之间的区别

关于哪种更好的方法已经有很多争论。 好吧,我也试图找出答案,下面是我的学习内容。

  1. 实现Runnable是首选的方法。 在这里,您并没有真正专门化或修改线程的行为。 您只是给线程一些要运行的东西。 这意味着合成是更好的选择。

  2. Java 仅支持单一继承,因此您只能扩展一个类。

  3. 实例化一个接口可以使您的代码与线程实现更加清晰地分离。

  4. 实现Runnable使您的类更加灵活。 如果您扩展Thread,那么您执行的操作将始终处于线程状态。 但是,如果您实现了Runnable,则不必这样做。 您可以在线程中运行它,或将其传递给某种执行服务,或仅作为任务在单个线程应用程序中传递。

  5. 如果您使用的是 JDK 4 或更低版本,则存在错误:

    http://bugs.java.com/bugdatabase/view_bug.do;jsessionid=5869e03fee226ffffffffc40d4fa881a86e3:WuuT?bug_id=4533087

    它已在 Java 1.5 中修复,但 Sun 并不打算在 1.4 中修复它。

    问题在于,在构造时,Thread被添加到内部线程表中的引用列表中。 在start()方法完成之前,它不会从该列表中删除。 只要存在该引用,就不会收集垃圾。

这就是 Java 中Runnable接口和Thread类之间区别的全部。 如果您知道更多信息,请在评论部分中添加,然后将其包含在帖子内容中。

学习愉快!

如何在 Java 中使用wait()notify()notifyAll()

原文: https://howtodoinjava.com/java/multi-threading/wait-notify-and-notifyall-methods/

Java 并发是一个非常复杂的主题,在编写处理在任何给定时间访问一个/多个共享资源的多个线程的应用程序代码时,需要引起很多关注。 Java 5 引入了诸如BlockingQueueExecutors之类的类,它们通过提供易于使用的 API 消除了一些复杂性。

使用并发类的程序员会比使用wait()notify()notifyAll()方法调用直接处理同步内容的程序员更有信心。 我还将建议您自己通过同步使用这些较新的 API,但是由于种种原因,我们经常需要这样做,例如维护旧版代码。 掌握这些方法的丰富知识将在您到达这种情况时为您提供帮助。

在本教程中,我将讨论 Java 中wait() notify() notifyall()的用途。 我们将了解waitnotify之间的区别

阅读更多: Java 中的wait()sleep()之间的区别

1. 什么是wait()notify()notifyAll()方法?

Java 中的Object类具有三个最终方法,这些方法允许线程就资源的锁定状态进行通信。

  1. wait()

    它告诉调用线程放弃锁定并进入睡眠状态,直到其他线程进入同一监视器并调用notify()为止。 wait()方法在等待之前释放锁,并在从wait()方法返回之前重新获取锁。 实际上,wait()方法与同步锁紧密集成在一起,使用的特性无法直接从同步机制获得。

    换句话说,我们不可能仅在 Java 中实现wait()方法。 它是本地方法

    调用wait()方法的常规语法如下:

    synchronized( lockObject )
    { 
    	while( ! condition )
    	{ 
    		lockObject.wait();
    	}
    
    	//take the action here;
    }
    
    
  2. notify()

    它唤醒一个在同一对象上调用wait()的单个线程。 应该注意的是,调用notify()实际上并没有放弃对资源的锁定。 它告诉等待的线程该线程可以唤醒。 但是,直到通知者的同步块完成后才真正放弃锁定。

    因此,如果通知者在资源上调用notify(),但通知者仍需要在其同步块内对该资源执行 10 秒的操作,则一直在等待的线程将至少需要再等待通知者 10 秒,来释放对象上的锁定,即使调用了notify()

    调用notify()方法的常规语法如下:

    synchronized(lockObject) 
    {
    	//establish_the_condition;
    
    	lockObject.notify();
    
    	//any additional code if needed
    }
    
    
  3. notifyAll()

    它将唤醒在同一对象上调用wait()的所有线程。 尽管没有保证,但是在大多数情况下,优先级最高的线程将首先运行。 其他与上述notify()方法相同。

    调用notify()方法的一般语法如下:

    synchronized(lockObject) 
    {
    	establish_the_condition;
    
    	lockObject.notifyAll();
    }
    
    

通常,使用wait()方法的线程会确认条件不存在(通常通过检查变量),然后调用wait()方法。 当另一个线程建立条件(通常通过设置相同的变量)时,它将调用notify()方法。 等待通知机制未指定特定条件/变量值是什么。 开发人员可以在调用wait()notify()之前指定要检查的条件。

让我们写一个小程序来了解应该如何使用wait()notify()notifyall()方法来获得理想的结果。

2. 如何一起使用wait()notify()notifyAll()方法

在本练习中,我们将使用wait()notify()方法解决生产者消费者问题。 为了使程序简单并专注于wait()notify()方法的使用,我们将只涉及一个生产者和一个消费者线程。

该程序的其他特性包括:

  • 生产者线程每 1 秒钟产生一个新资源,并将其放入taskQueue中。
  • 使用者线程需要 1 秒钟来处理taskQueue中消耗的资源。
  • taskQueue的最大容量为 5,即在任何给定时间,taskQueue中最多可以存在 5 个资源。
  • 两个线程都无限运行。

2.1 生产者线程

以下是根据我们的要求生产者线程的代码:

class Producer implements Runnable
{
   private final List<Integer> taskQueue;
   private final int           MAX_CAPACITY;

   public Producer(List<Integer> sharedQueue, int size)
   {
      this.taskQueue = sharedQueue;
      this.MAX_CAPACITY = size;
   }

   @Override
   public void run()
   {
      int counter = 0;
      while (true)
      {
         try
         {
            produce(counter++);
         } 
		 catch (InterruptedException ex)
         {
            ex.printStackTrace();
         }
      }
   }

   private void produce(int i) throws InterruptedException
   {
      synchronized (taskQueue)
      {
         while (taskQueue.size() == MAX_CAPACITY)
         {
            System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
         }

         Thread.sleep(1000);
         taskQueue.add(i);
         System.out.println("Produced: " + i);
         taskQueue.notifyAll();
      }
   }
}

  • 此处,produce(counter++)代码已在无限循环内编写,以便生产者以规则的间隔保持生产元素。
  • 我们已经按照通用准则编写了produce()方法代码,以编写第一部分中提到的wait()方法。
  • wait()完成后,生产者在 taskQueue 中添加一个元素,并称为notifyAll()方法。 由于上次wait()方法是由使用者线程调用的(这就是生产者处于等待状态的原因),因此使用者可以获取通知。
  • 收到通知后的使用者线程,如果准备按照书面逻辑使用该元素。
  • 请注意,两个线程也都使用sleep()方法来模拟创建和使用元素时的时间延迟。

2.2 使用者线程

以下是根据我们的要求使用的消费者线程的代码:

class Consumer implements Runnable
{
   private final List<Integer> taskQueue;

   public Consumer(List<Integer> sharedQueue)
   {
      this.taskQueue = sharedQueue;
   }

   @Override
   public void run()
   {
      while (true)
      {
         try
         {
            consume();
         } catch (InterruptedException ex)
         {
            ex.printStackTrace();
         }
      }
   }

   private void consume() throws InterruptedException
   {
      synchronized (taskQueue)
      {
         while (taskQueue.isEmpty())
         {
            System.out.println("Queue is empty " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
         }
         Thread.sleep(1000);
         int i = (Integer) taskQueue.remove(0);
         System.out.println("Consumed: " + i);
         taskQueue.notifyAll();
      }
   }
}

  • 此处,consume()代码已在无限循环内编写,以便使用者在taskQueue中发现某些内容时便继续使用元素。
  • 一旦wait()完成,使用者将删除 taskQueue 中的一个元素,并调用notifyAll()方法。 由于生产者线程调用了上次的wait()方法(这就是为什么生产者处于等待状态的原因),所以生产者会收到通知。
  • 获取通知后的生产者线程(如果准备按照书面逻辑生产元素)。

2.3 测试生产者消费者示例

现在让测试生产者和使用者线程。

public class ProducerConsumerExampleWithWaitAndNotify
{
   public static void main(String[] args)
   {
      List<Integer> taskQueue = new ArrayList<Integer>();
      int MAX_CAPACITY = 5;
      Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
      Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
      tProducer.start();
      tConsumer.start();
   }
}

程序输出。

Produced: 0
Consumed: 0
Queue is empty Consumer is waiting , size: 0
Produced: 1
Produced: 2
Consumed: 1
Consumed: 2
Queue is empty Consumer is waiting , size: 0
Produced: 3
Produced: 4
Consumed: 3
Produced: 5
Consumed: 4
Produced: 6
Consumed: 5
Consumed: 6
Queue is empty Consumer is waiting , size: 0
Produced: 7
Consumed: 7
Queue is empty Consumer is waiting , size: 0

我建议您将生产者线程和使用者线程花费的时间更改为不同的时间,并检查不同情况下的不同输出。

3. 关于wait()notify()notifyAll()方法的面试问题

3.1 调用notify()并且没有线程正在等待时会发生什么?

通常,如果正确使用这些方法,则在大多数情况下并非如此。 尽管如果在没有其他线程等待时调用notify()方法,则notify()只会返回而通知将丢失。

由于等待和通知机制不知道它正在发送通知的条件,因此,假设没有线程正在等待,通知就不会被听到。 稍后执行wait()方法的线程必须等待另一个通知发生。

3.2 在wait()方法释放或重新获取锁的时间段内是否存在竞争条件?

wait()方法与锁定机制紧密集成。 在等待线程已经可以接收通知的状态之前,实际上不会释放对象锁。 这意味着仅当线程状态更改为能够接收通知时,才会保留锁定。 该系统可防止在此机制中发生任何竞赛情况。

同样,系统确保在将线程移出等待状态之前,对象应完全持有锁定。

3.3 如果线程收到通知,是否可以保证条件设置正确?

简单地说,不。 在调用wait()方法之前,线程应始终在保持同步锁的同时测试条件。 从wait()方法返回后,线程应始终重新测试条件以确定是否应该再次等待。 这是因为另一个线程也可以测试条件并确定不需要等待 - 处理通知线程设置的有效数据。

当通知中涉及多个线程时,这是一种常见情况。 更具体地说,正在将处理数据的线程视为使用者。 它们使用其他线程产生的数据。 无法保证当消费者收到通知时,该通知尚未被其他消费者处理。

这样,当消费者醒来时,它无法假定其等待的状态仍然有效。 它可能在过去是有效的,但是在调用notify()方法之后以及使用者线程唤醒之前,状态可能已经更改。 等待线程必须提供检查状态的选项,并在通知已被处理的情况下返回到等待状态。 这就是为什么我们总是在循环中放置对wait()方法的调用的原因。

3.4 当多个线程正在等待通知时会发生什么? 调用notify()方法时,哪个线程实际获得通知?

这取决于许多因素。Java 规范没有定义要通知哪个线程。 在运行时中,哪个线程实际接收到通知取决于几个因素,包括 Java 虚拟机的实现以及程序执行期间的调度和计时问题。

即使在单个处理器平台上,也无法确定多个线程中的哪个接收通知。

就像notify()方法一样,notifyAll()方法不允许我们决定哪个线程获取通知:它们都被通知了。 当所有线程都收到通知时,可以设计出一种机制,让线程在它们之间选择哪个线程应该继续,哪个线程应该再次调用wait()方法。

3.5 notifyAll()方法是否真的唤醒所有线程?

是的,没有。 所有等待的线程都被唤醒,但是它们仍然必须重新获取对象锁。 因此,线程不会并行运行:它们必须各自等待对象锁被释放。 因此,一次只能运行一个线程,并且只能在调用notifyAll()方法的线程释放其锁之后运行。

3.6 如果根本只执行一个线程,为什么要唤醒所有线程?

有几个原因。 例如,可能有多个条件要等待。 由于我们无法控制哪个线程获取通知,因此通知完全有可能唤醒正在等待完全不同条件的线程。

通过唤醒所有线程,我们可以设计程序,以便线程在它们之间决定下一步应执行哪个线程。 另一种选择是生产者生成的数据可以满足多个消费者的需求。 由于可能难以确定有多少消费者可以对该通知感到满意,因此可以选择全部通知他们,从而允许消费者在他们之间进行分类。

学习愉快!

Java 并发性 – yield()join()之间的区别

原文: https://howtodoinjava.com/java/multi-threading/difference-between-yield-and-join-in-threads-in-java/

多线程从很长时间以来一直是面试官中非常受欢迎的话题。 尽管我个人觉得很少有人真正有机会在复杂的多线程应用程序上工作(在过去的 7 年中只有一次机会),但仍然可以帮助您方便地掌握这些概念来仅仅增强您的信心。 之前,我讨论过一个类似的问题,关于wait()sleep()方法之间的区别,这一次,我正在讨论join()yield()方法之间的区别 。 坦白地说,我在实践中并未同时使用这两种方法,因此,如果您在任何时候都感到不满意,请提出意见。

Java 线程调度的一些背景知识

需要 Java 虚拟机在其各个线程之间实现可抢占,基于优先级的调度器。 这意味着为 Java 程序中的每个线程分配了一定的优先级,该优先级在明确定义的范围内。 开发人员可以更改此优先级。 Java 虚拟机永远不会更改线程的优先级,即使该线程已经运行了一段时间。

优先级值很重要,因为 Java 虚拟机和底层操作系统之间的约定是操作系统通常必须选择以最高优先级运行 Java 线程。 这就是说 Java 实现基于优先级的调度器时的意思。 此调度器以抢先方式实现,这意味着,当有一个优先级较低的线程正在运行时,当出现优先级较高的线程时,该线程中断(会抢占)。 但是,与操作系统的契约不是绝对的,这意味着操作系统有时可以选择运行优先级较低的线程。(我讨厌有关多线程的问题。)

还要注意, java 并没有要求对其线程进行时间分段,但大多数操作系统都这样做。 这里的术语经常会有一些混乱:抢占经常与时间片混淆。 实际上,抢占仅意味着运行优先级更高的线程而不是优先级较低的线程,但是当线程具有相同优先级时,它们不会相互抢占。 它们通常受时间限制,但这不是 Java 的要求。

了解线程优先级

了解线程优先级是学习多线程的下一个重要步骤,尤其是 yield()的工作方式

  1. 请记住,未指定优先级时,所有线程均具有正常优先级。
  2. 优先级可以在 1 到 10 之间指定。10 是最高优先级,1 是最低优先级,5 是正常优先级。
  3. 请记住,优先级最高的线程将在执行时被赋予优先级。 但是不能保证它一开始就处于运行状态。
  4. 与正在等待机会的池中的线程相比,当前正在执行的线程始终始终具有更高的优先级。
  5. 线程调度器决定应该执行哪个线程。
  6. t.setPriority()可用于设置线程的优先级。
  7. 请记住,应该在调用线程启动方法之前设置优先级。
  8. 您可以使用常量MIN_PRIORITYMAX_PRIORITYNORM_PRIORITY设置优先级。

现在,当我们对线程调度和线程优先级有了一些基本了解时,让我们进入主题。

yield()方法

从理论上讲,yield()是指放弃,屈服,投降。 一个让步线程告诉虚拟机,它愿意让其他线程在其位置进行调度。 这表明它并没有做太重要的事情。 请注意,虽然只是提示,但不能保证完全有效。

yield()Thread.java中定义如下。

/**
  *	A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore
  * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU. 
  * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect. 
  */

public static native void yield();

让我们从上面的定义中列出要点:

  • 产量也是静态方法,也是本机方法。
  • Yield告诉当前正在执行的线程给线程池中具有相同优先级的线程一个机会。
  • 无法保证Yield将使当前正在执行的线程立即变为可运行状态。
  • 它只能使线程从运行状态变为可运行状态,而不能处于等待或阻塞状态。

yield()方法示例用法

在下面的示例程序中,我没有特定的原因创建了两个名为生产者和消费者的线程。 生产者设置为最小优先级,而消费者设置为最大优先级。 我将在有/无注释行Thread.yield()的情况下运行以下代码。 如果没有yield(),尽管输出有时会更改,但是通常首先打印所有使用者行,然后才打印所有生产者行。

使用yield()方法,两者都一次打印一行,并且几乎总是将机会传递给另一线程。

package test.core.threads;

public class YieldExample
{
   public static void main(String[] args)
   {
      Thread producer = new Producer();
      Thread consumer = new Consumer();

      producer.setPriority(Thread.MIN_PRIORITY); //Min Priority
      consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority

      producer.start();
      consumer.start();
   }
}

class Producer extends Thread
{
   public void run()
   {
      for (int i = 0; i < 5; i++)
      {
         System.out.println("I am Producer : Produced Item " + i);
         Thread.yield();
      }
   }
}

class Consumer extends Thread
{
   public void run()
   {
      for (int i = 0; i < 5; i++)
      {
         System.out.println("I am Consumer : Consumed Item " + i);
         Thread.yield();
      }
   }
}

上面程序的输出,“没有”yield()方法

I am Consumer : Consumed Item 0
 I am Consumer : Consumed Item 1
 I am Consumer : Consumed Item 2
 I am Consumer : Consumed Item 3
 I am Consumer : Consumed Item 4
 I am Producer : Produced Item 0
 I am Producer : Produced Item 1
 I am Producer : Produced Item 2
 I am Producer : Produced Item 3
 I am Producer : Produced Item 4

添加了yield()后上述程序方法的输出

I am Producer : Produced Item 0
 I am Consumer : Consumed Item 0
 I am Producer : Produced Item 1
 I am Consumer : Consumed Item 1
 I am Producer : Produced Item 2
 I am Consumer : Consumed Item 2
 I am Producer : Produced Item 3
 I am Consumer : Consumed Item 3
 I am Producer : Produced Item 4
 I am Consumer : Consumed Item 4

join()方法

可以将Thread实例的join()方法用于将一个线程的执行开始“连接”到另一个线程的执行的结束,这样一个线程只有在另一个线程结束后才能开始运行。 如果在Thread实例上调用join(),则当前正在运行的线程将阻塞,直到Thread实例完成执行为止。

//Waits for this thread to die. 

public final void join() throws InterruptedException

join()中设置超时,将使特定超时后的join()效果无效。当达到超时时,主线程和taskThread都是执行任务的同等可能性。 但是,与睡眠一样,join的运行时间也取决于操作系统,因此,您不应假定join会完全按照您指定的时间等待。

像睡眠一样,join通过退出InterruptedException来响应中断。

join()方法示例用法

package test.core.threads;

public class JoinExample
{
   public static void main(String[] args) throws InterruptedException
   {
      Thread t = new Thread(new Runnable()
         {
            public void run()
            {
               System.out.println("First task started");
               System.out.println("Sleeping for 2 seconds");
               try
               {
                  Thread.sleep(2000);
               } catch (InterruptedException e)
               {
                  e.printStackTrace();
               }
               System.out.println("First task completed");
            }
         });
      Thread t1 = new Thread(new Runnable()
         {
            public void run()
            {
               System.out.println("Second task completed");
            }
         });
      t.start(); // Line 15
      t.join(); // Line 16
      t1.start();
   }
}

Output:

First task started
Sleeping for 2 seconds
First task completed
Second task completed

仅此一个很小但很重要的概念。 在评论部分让我知道您的想法。

祝您学习愉快!

Java 中 sleep()wait()之间的区别

原文: https://howtodoinjava.com/java/multi-threading/sleep-vs-wait/

了解 Java 中sleep()wait()方法之间的区别。 了解何时使用哪种方法以及 Java 并发带来什么效果。

1. Java sleep()wait() – 讨论

sleep()是一种用于暂停该过程几秒钟或我们想要的时间的方法。 但是,在使用wait()方法的情况下,线程进入等待状态,直到我们调用notify()notifyAll()时,线程才会自动返回。

主要区别在于,wait()会释放锁定或监视器,而sleep()不会在等待期间释放锁定或监视器。 通常,wait()用于线程间通信,而sleep()用于引入执行暂停。

Thread.sleep()将当前线程发送到“不可运行”状态一段时间。 该线程保留已获取的监视器 - 即,如果该线程当前在synchronized块或方法中,则没有其他线程可以进入该块或方法。 如果另一个线程调用t.interrupt()。 它将唤醒睡眠线程。

sleep()static方法,这意味着它始终会影响当前线程(正在执行sleep方法的线程)。 一个常见的错误是在t是不同线程的情况下调用t.sleep()。 即使这样,仍将休眠的是当前线程,而不是t线程。

阅读更多:使用wait()notify()

2. Java sleep()wait() – 示例

synchronized(LOCK) {   
    Thread.sleep(1000); // LOCK is held
}

synchronized(LOCK) 
{   
    LOCK.wait(); // LOCK is not held
}

阅读更多: yield()join()之间的区别

3. Java sleep()wait() – 总结

简而言之,让我们对以上所有要点进行分类以记住。

3.1 在...上调用方法

  • wait() – 调用对象; 当前线程必须在锁对象上同步。
  • sleep() – 调用线程; 始终当前正在执行的线程。

3.2 同步

  • wait() – 同步多个线程时,一个线程一个对象访问一个对象。
  • sleep() – 同步时,多个线程等待休眠线程的休眠。

3.3 锁定时间段

  • wait() – 释放其他对象有机会执行的锁定。
  • sleep() – 如果指定了超时或有人中断,请保持锁定至少 t 次。

3.4 唤醒条件

  • wait() – 直到从对象调用notify()notifyAll()
  • sleep() – 直到至少时间到期或调用interrupt()

3.5 用法

  • sleep() – 用于时间同步
  • wait() – 用于多线程同步。

希望以上信息将为您的知识库增加一些价值。

学习愉快!

Thread.sleep方法 Java 文档

Object.wait()方法 Java 文档

锁和监视器之间的区别 – Java 并发

原文: https://howtodoinjava.com/java/multi-threading/multithreading-difference-between-lock-and-monitor/

您可能在面试中遇到了这个问题,锁和监视器之间的有什么区别? 好吧,要回答这个问题,您必须对 Java 多线程如何在后台工作有足够的了解。

答案很简单,锁为实现监视器提供了必要的支持。 长答案在下面阅读。

锁是一种数据,在逻辑上是堆内存中对象标头的一部分。JVM 中的每个对象都具有此锁(或互斥锁),任何程序均可使用该锁来协调对该对象的多线程访问。 如果有任何线程想要访问该对象的实例变量; 那么线程必须“拥有”对象的锁(在锁存储区域中设置一些标志)。 尝试访问该对象变量的所有其他线程必须等待,直到拥有该线程的线程释放该对象的锁(取消设置标志)。

线程拥有锁后,它可以多次请求相同的锁,但是在将锁提供给其他线程之前,必须释放相同的次数。 例如,如果一个线程请求了三次锁定,则该线程将继续拥有该锁定,直到它“释放”了三次。

请注意,当线程明确要求锁时,它是由线程获得的。 在 Java 中,这是通过synced关键字或waitnotify完成的。

监视器

监视器是一个同步构造,它允许线程具有互斥(使用锁)和协作,即使线程能够等待某些条件成立的能力(使用wait-set) 。

换句话说,每个 Java 对象与实现锁的数据在逻辑上均与实现wait-set的数据相关联。 锁可以帮助线程在共享数据上独立工作而不会互相干扰,而等待集可以帮助线程相互协作以共同努力实现一个共同的目标,例如所有等待线程都将移至该等待集,一旦释放锁定,所有通知线程都将得到通知。 此等待集通过锁定(mutex)的附加帮助来帮助构建监视器

互斥

简单来说,监视器就像一栋建筑物,其中包含一个特殊的房间(对象实例),一次只能占用一个线程。 房间中通常包含一些数据,需要保护这些数据以防止并发访问。 从线程进入该房间的时间到它离开的时间,它可以独占访问该房间中的任何数据。 进入显示器大楼称为“进入显示器”。 进入建筑物内的特别房间称为“获取显示器”。 占领房间称为“拥有显示器”,离开房间称为“释放显示器”。 离开整个建筑物称为“退出监视器”。

当线程到达以访问受保护的数据(进入特殊房间)时,首先将其放入建筑物接收队列中(条目集)。 如果没有其他线程在等待(监视器拥有),则该线程获取锁并继续执行受保护的代码。 线程完成执行后,它将释放锁并退出建筑物(退出监视器)。

如果一个线程到达并且另一个线程已经拥有监视器,则它必须在接收队列中等待(条目集)。 当前所有者退出监视器时,新到达的线程必须与也在入口集中等待的任何其他线程竞争。 只有一个线程会赢得比赛并拥有锁。

没有等待设置特性。

合作

通常,互斥仅在多个线程共享数据或其他资源时才重要。 如果两个线程无法使用任何通用数据或资源,则它们通常不会互相干扰,也不必以互斥的方式执行。 互斥有助于防止线程在共享数据时相互干扰,而协作则可以帮助线程共同努力实现某个共同目标。

当一个线程需要某些数据处于特定状态而另一个线程负责使数据进入该状态时,合作非常重要。 生产者/消费者问题,其中读取线程需要缓冲区处于“非空”状态才能从缓冲区中读取任何数据。 如果读取线程发现缓冲区为空,则必须等待。 写线程负责用数据填充缓冲区。 一旦写入线程完成了更多写入操作,读取线程便可以进行更多读取操作。 有时也称为“等待并通知”或“信号并继续”监视器,因为它保留了监视器的所有权,并在需要时继续执行监视器区域(继续)。 在稍后的某个时间,通知线程释放监视器,并且等待线程恢复拥有该锁。

这种合作需要输入集和等待集。 下面给出的示意图将帮助您理解这种合作。

java-monitor

上图将监视器显示为三个矩形。 在中心,一个大矩形包含一个线程,即显示器的所有者。 在左侧,一个小矩形包含条目集。 在右侧,另一个小矩形包含等待集。

我希望以上讨论将有助于您获得更多见识。 免费免费问任何问题。

学习愉快!

Java Callable Future示例

原文: https://howtodoinjava.com/java/multi-threading/java-callable-future-example/

Java 执行器框架的好处之一是,我们可以运行并发任务,这些并发任务在处理任务后可以返回单个结果。 Java 并发 API 通过以下两个接口CallableFuture实现此目的。

1. Java CallableFuture接口

1.1 Callable

Callable接口具有call()方法。 在这种方法中,我们必须实现任务的逻辑。 Callable接口是一个参数化接口,这意味着我们必须指出call()方法将返回的数据类型。

2.2 Future

Future接口具有获取Callable对象生成的结果并管理其状态的方法。

2. Java Callable Future示例

在此示例中,我们正在创建类型为CallableFactorialCalculator。 这意味着我们将覆盖它的call()方法,计算后,我们将从call()方法返回结果。 以后可以从主程序保存的Future参考中检索此结果。

public class FactorialCalculator implements Callable<Integer>
{

	private Integer number;

	public FactorialCalculator(Integer number) {
		this.number = number;
	}

	@Override
	public Integer call() throws Exception {
		int result = 1;
		if ((number == 0) || (number == 1)) {
			result = 1;
		} else {
			for (int i = 2; i <= number; i++) {
				result *= i;
				TimeUnit.MILLISECONDS.sleep(20);
			}
		}
		System.out.println("Result for number - " + number + " -> " + result);
		return result;
	}
}

现在,我们使用两个线程和 4 个数字测试上述阶乘计算器。

package com.howtodoinjava.demo.multithreading;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CallableExample 
{
	  public static void main(String[] args) 
	  {
		  ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

		  List<Future<Integer>> resultList = new ArrayList<>();

		  Random random = new Random();

		  for (int i=0; i<4; i++)
		  {
		      Integer number = random.nextInt(10);
		      FactorialCalculator calculator  = new FactorialCalculator(number);
		      Future<Integer> result = executor.submit(calculator);
		      resultList.add(result);
		  }

		  for(Future<Integer> future : resultList)
		  {
	            try 
	            {
	                System.out.println("Future result is - " + " - " + future.get() + "; And Task done is " + future.isDone());
	            } 
	            catch (InterruptedException | ExecutionException e) 
	            {
	                e.printStackTrace();
	            }
	        }
	        //shut down the executor service now
	        executor.shutdown();
	  }
}

程序输出。

Result for number - 4 -> 24
Result for number - 6 -> 720
Future result is -  - 720; And Task done is true
Future result is -  - 24; And Task done is true
Result for number - 2 -> 2
Result for number - 6 -> 720
Future result is -  - 720; And Task done is true
Future result is -  - 2; And Task done is true

在这里,我们使用submit()方法发送了一个Callable对象,该对象将在执行器中执行。 此方法接收Callable对象作为参数,并返回一个Future对象,我们可以将其用于两个主要目标:

  1. 我们可以控制任务的状态 – 我们可以取消任务并检查任务是否完成。 为此,我们使用isDone()方法检查任务是否完成。

  2. 我们可以通过call()方法返回结果。为此,我们使用了get()方法。 该方法一直等到Callable对象完成对call()方法的执行并返回其结果。

    如果在get()方法等待结果时线程被中断,则它将引发InterruptedException异常。 如果call()方法引发异常,则此方法引发ExecutionException异常。

Future接口提供了get()方法的另一个版本,即get(longtimeout, TimeUnitunit)。 如果任务的结果不可用,则此版本的get方法将等待指定的时间。 如果经过指定的时间段且结果尚不可用,则该方法将返回null值。

学习愉快!

如何使用UncaughtExceptionHandler重新启动线程

原文: https://howtodoinjava.com/java/multi-threading/how-to-restart-thread-using-uncaughtexceptionhandler/

我们已经知道 Java 中有两种异常。 受检的异常和非受检的异常。 必须在方法的throws子句中指定受检的异常或将其捕获在其中。 无需指定或捕获非受检的异常。 当在Thread对象的run()方法内引发受检异常时,由于run()方法不接受throws子句,因此我们必须相应地对其进行处理。 但是,当在Thread对象的run()方法内引发非受检的异常时,默认行为是在控制台中写入栈跟踪(或将其记录在错误日志文件中)并退出程序。

幸运的是,Java 为我们提供了一种机制来捕获和处理Thread对象中抛出的非受检的异常,从而避免程序结束。 这可以使用UncaughtExceptionHandler完成。

让我们以UncaughtExceptionHandler用法为例。 在此示例中,我们创建了一个线程,该线程尝试解析一些应该为整数的字符串。 我们编写了run()方法,使其在执行过程中抛出“ java.lang.NumberFormatException”。 由于程序不会尝试捕获此异常,因此异常会在 JVM 级别浮动,并且线程被杀死。 这绝对是正常行为,但可能不是您期望的行为。

不使用UncaughtExceptionHandler

在现实生活中的应用程序中,即使几次失败,您也想尝试执行一次多次重要任务。 下面的示例演示了用例,首先不使用UncaughtExceptionHandler; 导致线程在失败后立即死亡。

Task.java

class Task implements Runnable
{
   @Override
   public void run()
   {
      System.out.println(Integer.parseInt("123"));
      System.out.println(Integer.parseInt("234"));
      System.out.println(Integer.parseInt("345"));
      System.out.println(Integer.parseInt("XYZ")); //This will cause NumberFormatException
      System.out.println(Integer.parseInt("456"));
   }
}

DemoThreadExample.java

public class DemoThreadExample
{
   public static void main(String[] args)
   {
      Task task = new Task();
      Thread thread = new Thread(task);
      thread.start();
   }
}

下面是运行线程时得到的输出:

123
234
345
Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "XYZ"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at examples.algorithms.sleepingbarber.Task.run(DemoThreadExample.java:24)
	at java.lang.Thread.run(Unknown Source)

使用UncaughtExceptionHandler之后

让我们添加一个UncaughtExceptionHandler实现,以在运行时捕获任何非受检的异常。

ExceptionHandler.java

class ExceptionHandler implements UncaughtExceptionHandler
{
   public void uncaughtException(Thread t, Throwable e)
   {
      System.out.printf("An exception has been captured\n");
      System.out.printf("Thread: %s\n", t.getId());
      System.out.printf("Exception: %s: %s\n", e.getClass().getName(), e.getMessage());
      System.out.printf("Stack Trace: \n");
      e.printStackTrace(System.out);
      System.out.printf("Thread status: %s\n", t.getState());
      new Thread(new Task()).start();
   }
}

现在,将此异常处理程序添加到线程中。

class Task implements Runnable
{
   @Override
   public void run()
   {
      Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler());
      System.out.println(Integer.parseInt("123"));
      System.out.println(Integer.parseInt("234"));
      System.out.println(Integer.parseInt("345"));
      System.out.println(Integer.parseInt("XYZ")); //This will cause NumberFormatException
      System.out.println(Integer.parseInt("456"));
   }
}

现在再次运行上面的示例。 这将连续运行。 在现实生活中,如果该任务能够完成任务,那么它将在不引发任何异常的情况下退出并完成其生命周期。

123
234
345
An exception has been captured
Thread: 1394
Exception: java.lang.NumberFormatException: For input string: "XYZ"
Stack Trace: 
java.lang.NumberFormatException: For input string: "XYZ"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at examples.algorithms.sleepingbarber.Task.run(DemoThreadExample.java:24)
	at java.lang.Thread.run(Unknown Source)
Thread status: RUNNABLE
123
234
345
An exception has been captured
Thread: 1395
Exception: java.lang.NumberFormatException: For input string: "XYZ"
Stack Trace: 
java.lang.NumberFormatException: For input string: "XYZ"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at examples.algorithms.sleepingbarber.Task.run(DemoThreadExample.java:24)
	at java.lang.Thread.run(Unknown Source)
Thread status: RUNNABLE
123
234
345

上面的实现可帮助您以某种方式运行线程,直到执行任务为止。 这也可以通过其他多线程概念来实现。

请注意,UncaughtExceptionHandler也可以用于使日志记录更加健壮,而无需重新启动线程,因为在线程执行失败时,默认日志通常无法提供足够的上下文信息。

祝您学习愉快!

Java 中的语句类型

原文: https://howtodoinjava.com/java/basics/types-of-statements-in-java/

一条语句指定 Java 程序中的一个动作,例如将xy的和分配给z,将消息打印到标准输出,将数据写入文件等。

Java 语句可以大致分为三类:

  • 声明
  • 表达式语句
  • 控制流声明

声明

声明语句用于声明变量。 例如,

int num;
int num2 = 100;
String str;

表达式语句

;结尾的表达式称为表达式语句。 例如,

/Increment and decrement expressions
num++;
++num;
num--;
--num;

//Assignment expressions
num = 100;
num *= 10;

//Method invocation expressions 
System.out.println("This is a statement");
someMethod(param1, param2);

控制流语句

默认情况下,Java 程序中的所有语句均按照它们在程序中出现的顺序执行。 有时,您可能希望多次或只要特定条件为真就重复执行一组语句。

使用控制流语句在 Java 中所有这些都是可能的。 If块,while循环和for循环语句是控制流语句的示例。

您可以在此博客的单独教程中了解有关这些步骤的更多信息。

祝您学习愉快!

使用ThreadPoolExecutorSemaphore限制任务提交率

原文: https://howtodoinjava.com/java/multi-threading/throttling-task-submission-rate-using-threadpoolexecutor-and-semaphore/

如果您知道在 Web 服务器中,则可以配置到服务器的最大并发连接数。 如果有更多连接超出此限制,则它们必须等待直到释放或关闭某些其他连接。 此限制可以视为节流。 节流是为输出速率比输入速率慢的系统调节输入速率的能力。 必须停止系统崩溃或资源耗尽。

在与BlockingQueueThreadPoolExecutor相关的上一篇文章中,我们了解了如何创建具有以下能力的CustomThreadPoolExecutor

1)提交到阻塞队列
的任务,2)一个执行器,从队列中拾取任务并执行它们,3)已在ExecuteGate之后覆盖了Execute()方法以执行一些必要的额外活动,4)附加了一个RejectedExecutionHandler,用于处理由于队列已满而被拒绝的任务

我们的方法已经足够好,并且能够处理大多数实际情况。 现在,我们再添加一个概念,在某些情况下可能会证明是有益的。 这个概念是围绕队列中任务提交的限制。

在此示例中,节流将有助于使队列中的任务数保持在限制范围内,从而使任何任务都不会被拒绝。 它从本质上也消除了RejectedExecutionHandler的必要性。

使用CustomThreadPoolExecutorRejectedExecutionHandler的先前解决方案

在此解决方案中,我们有以下类:

DemoTask.java

public class DemoTask implements Runnable
{
   private String name = null;

   public DemoTask(String name) {
      this.name = name;
   }

   public String getName() {
      return this.name;
   }

   @Override
   public void run(){
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e){
         e.printStackTrace();
      }
      System.out.println("Executing : " + name);
   }
}

CustomThreadPoolExecutor.java

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomThreadPoolExecutor extends ThreadPoolExecutor
{
   public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
									TimeUnit unit, BlockingQueue<Runnable> workQueue)
   {
      super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
   }

   @Override
   protected void beforeExecute(Thread t, Runnable r)
   {
      super.beforeExecute(t, r);
   }

   @Override
   protected void afterExecute(Runnable r, Throwable t)
   {
      super.afterExecute(r, t);
      if (t != null)
      {
         t.printStackTrace();
      }
   }
}

DemoExecutor.java

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoExecutor
{
   public static void main(String[] args)
   {
      Integer threadCounter = 0;
      BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(50);
      CustomThreadPoolExecutor executor = new CustomThreadPoolExecutor(10, 20, 5000, TimeUnit.MILLISECONDS, blockingQueue);
      executor.setRejectedExecutionHandler(new RejectedExecutionHandler()
         {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
            {
               System.out.println("DemoTask Rejected : " + ((DemoTask) r).getName());
               try
               {
                  Thread.sleep(1000);
               } catch (InterruptedException e)
               {
                  e.printStackTrace();
               }
               System.out.println("Lets add another time : " + ((DemoTask) r).getName());
               executor.execute(r);
            }
         });
      // Let start all core threads initially
      executor.prestartAllCoreThreads();
      while (true)
      {
         threadCounter++;
         // Adding threads one by one
         //System.out.println("Adding DemoTask : " + threadCounter);
         executor.execute(new DemoTask(threadCounter.toString()));
         if (threadCounter == 1000)
            break;
      }
   }
}

如果运行上述程序,则将获得输出,如下所示:

DemoTask Rejected : 71
Executing : 3
Executing : 5
...
...

将出现多次“DemoTask Rejected”。 在下一个解决方案中,我们将使用节流技术,以使任何任务都不会被拒绝。

使用ThreadPoolExecutorSemaphore限制任务的提交率

在此解决方案中,我们将创建一个Semaphore,其编号必须等于在任何给定时间点阻塞队列中的最大任务数。 因此该方法如下所示:

1)在执行任务之前,要求锁定信号量
2)如果获取了锁定,则执行正常。 否则,将重试直到获得锁
3)任务完成后; 锁被释放到信号量

我们启用节流的新BlockingThreadPoolExecutor如下所示:

package threadpoolDemo;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class BlockingThreadPoolExecutor extends ThreadPoolExecutor
{
   private final Semaphore semaphore;

   public BlockingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
   {
      super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
      semaphore = new Semaphore(corePoolSize + 50);
   }

   @Override
   protected void beforeExecute(Thread t, Runnable r)
   {
      super.beforeExecute(t, r);
   }

   @Override
   public void execute(final Runnable task)
   {
      boolean acquired = false;
      do
      {
         try
         {
            semaphore.acquire();
            acquired = true;
         } catch (final InterruptedException e)
         {
            //LOGGER.warn("InterruptedException whilst aquiring semaphore", e);
         }
      } while (!acquired);
      try
      {
         super.execute(task);
      } catch (final RejectedExecutionException e)
      {
         System.out.println("Task Rejected");
         semaphore.release();
         throw e;
      }
   }

   @Override
   protected void afterExecute(Runnable r, Throwable t)
   {
      super.afterExecute(r, t);
      if (t != null)
      {
         t.printStackTrace();
      }
      semaphore.release();
   }
}

现在,如下测试代码。

package threadpoolDemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoExecutor
{
   public static void main(String[] args)
   {
      Integer threadCounter = 0;
      BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(50);
      BlockingThreadPoolExecutor executor = new BlockingThreadPoolExecutor(10, 20, 5000, TimeUnit.MILLISECONDS, blockingQueue);
      executor.setRejectedExecutionHandler(new RejectedExecutionHandler()
         {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
            {
               System.out.println("DemoTask Rejected : " + ((DemoTask) r).getName());
               try
               {
                  Thread.sleep(1000);
               } catch (InterruptedException e)
               {
                  e.printStackTrace();
               }
               System.out.println("Lets add another time : " + ((DemoTask) r).getName());
               executor.execute(r);
            }
         });
      // Let start all core threads initially
      executor.prestartAllCoreThreads();
      while (true)
      {
         threadCounter++;
         // Adding threads one by one
         System.out.println("Adding DemoTask : " + threadCounter);
         executor.execute(new DemoTask(threadCounter.toString()));
         if (threadCounter == 1000)
            break;
      }
   }
}

当使用BlockingThreadPoolExecutor代替CustomThreadPoolExecutor运行DemoExecutor程序时,您不会看到任何任务被拒绝,并且所有任务都将成功执行。

您可以控制在任何时候通过Semaphore构造函数传递参数的任务数量。

这就是这篇文章的全部内容。 您应该阅读有关并发的更多信息,以提高信心。

学习愉快!

Java 执行器框架教程和最佳实践

原文: https://howtodoinjava.com/java/multi-threading/executor-framework-tutorial/

与 JDK 5 一起发布的 Java 执行器框架java.util.concurrent.Executor)用于运行Runnable对象,而无需每次都创建新线程,并且主要是重新使用已经创建的线程。

我们都知道有两种方法可以在 Java 中创建线程。 如果您想了解有关它们比较的更多信息,请阅读如何在 Java中创建线程。

在 Java 中创建线程是一个非常昂贵的过程,其中还包括内存开销。 因此,如果我们可以在创建后重新使用这些线程来运行将来的可运行对象,则是一个好主意。 在此执行器框架教程中,我将编写一些演示程序来演示Executor的用法,然后我们将讨论在设计下一个多线程应用程序时需要牢记的一些最佳实践。

1. Java 执行器框架示例

在演示应用程序中,我们有两个任务正在运行。 两者都不会终止,并且都应在应用程序的生命周期内运行。 我将尝试编写一个主包装器类,例如:

  • 如果有任何任务引发异常,则应用程序将捕获该异常并重新启动该任务。
  • 如果有任何任务运行完毕,应用程序将注意到并重新启动任务。

下面是上述所需应用程序的代码示例。

package com.howtodoinjava.multiThreading.executors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DemoExecutorUsage {

	private static ExecutorService executor = null;
	private static volatile Future taskOneResults = null;
	private static volatile Future taskTwoResults = null;

	public static void main(String[] args) {
		executor = Executors.newFixedThreadPool(2);
		while (true)
		{
			try
			{
				checkTasks();
				Thread.sleep(1000);
			} catch (Exception e) {
				System.err.println("Caught exception: " + e.getMessage());
			}
		}
	}

	private static void checkTasks() throws Exception {
		if (taskOneResults == null
				|| taskOneResults.isDone()
				|| taskOneResults.isCancelled())
		{
			taskOneResults = executor.submit(new TestOne());
		}

		if (taskTwoResults == null
				|| taskTwoResults.isDone()
				|| taskTwoResults.isCancelled())
		{
			taskTwoResults = executor.submit(new TestTwo());
		}
	}
}

class TestOne implements Runnable {
	public void run() {
		while (true)
		{
			System.out.println("Executing task one");
			try
			{
				Thread.sleep(1000);
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}

	}
}

class TestTwo implements Runnable {
	public void run() {
		while (true)
		{
			System.out.println("Executing task two");
			try
			{
				Thread.sleep(1000);
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
	}
}

请不要忘记在帖子结尾阅读最佳实践。

2. Java 执行器框架 – MultiRunnable

不必每个Runnable都在单独的线程中执行。 有时,我们需要在单个线程中执行多个作业,并且每个作业都是Runnable的实例。 要设计此类解决方案,应使用MultiRunnable。 这个MultiRunnable对象不过是需要执行的可运行对象的集合。 唯一的补充是,该MultiRunnable库本身也是Runnable

以下是需要在单个线程中执行的任务列表。

package com.howtodoinjava.multiThreading.executors;

public class TaskOne implements Runnable {
	@Override
	public void run() {
		System.out.println("Executing Task One");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

public class TaskTwo implements Runnable {
	@Override
	public void run() {
		System.out.println("Executing Task Two");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

public class TaskThree implements Runnable {
	@Override
	public void run() {
		System.out.println("Executing Task Three");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

让我们创建上述任务的MultiRunnable包装器。

package com.howtodoinjava.demo.multiThread;

import java.util.List;

public class MultiRunnable implements Runnable {

    private final List<Runnable> runnables;

    public MultiRunnable(List<Runnable> runnables) {
        this.runnables = runnables;
    }

    @Override
    public void run() {
        for (Runnable runnable : runnables) {
        	 new Thread(runnable).start();
        }
    }
}

现在可以在下面的程序中以这种方式执行上面的multi runnable

package com.howtodoinjava.demo.multiThread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MultiTaskExecutor {

    public static void main(String[] args) {

        BlockingQueue<Runnable> worksQueue = new ArrayBlockingQueue<Runnable>(10);
        RejectedExecutionHandler rejectionHandler = new RejectedExecutionHandelerImpl();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 10, TimeUnit.SECONDS, worksQueue, rejectionHandler);

        executor.prestartAllCoreThreads();

        List<Runnable> taskGroup = new ArrayList<Runnable>();
        taskGroup.add(new TestOne());
        taskGroup.add(new TestTwo());
        taskGroup.add(new TestThree());

        worksQueue.add(new MultiRunnable(taskGroup));
    }
}

class RejectedExecutionHandelerImpl implements RejectedExecutionHandler
{
    @Override
    public void rejectedExecution(Runnable runnable,
            ThreadPoolExecutor executor)
    {
        System.out.println(runnable.toString() + " : I've been rejected ! ");
    }
}

3. Java 执行器框架最佳实践

  1. 始终针对静态分析工具(例如 PMDFindBugs)运行 Java 代码,以查找更深层次的问题。 它们对于确定将来可能出现的丑陋情况非常有帮助。
  2. 始终与高级人员进行交叉检查并更好地计划代码审查,以在执行过程中检测并可能在代码中出现死锁或活锁。 在大多数情况下,在应用程序中添加运行状况监视器以检查正在运行的任务的状态是一个很好的选择。
  3. 在多线程程序中,也要养成捕获错误的习惯,而不仅仅是异常。 有时会发生意想不到的事情,除了异常之外,Java 还会向您抛出错误。
  4. 使用退避开关,因此,如果出现问题并且无法恢复,您就不会急于启动另一个循环来升级情况。 相反,您需要等到情况恢复正常后再重新开始。
  5. 请注意,执行器的全部目的是抽象出执行的细节,因此除非明确说明,否则不能保证顺序。

学习愉快!

Java 线程间通信 – PipedReaderPipedWriter

原文: https://howtodoinjava.com/java/multi-threading/inter-thread-communication-using-piped-streams-in-java/

Java 线程间通信在很长一段时间以来一直是热门的面试问题。 在 JDK 1.5 版本中,ExecutorServiceBlockingQueue带来了另一种更有效的实现方式,但管道流方法也值得了解,在某些情况下可能有用。

Table of contents

What are piped streams
PipedReader and PipedWriter
Java inter-thread communication example
Summary

什么是管道流

管道流就像真实的管道一样。 您可以使用某些方法将事物一端放入管道中。 然后,您可以使用其他方法从另一端的管道流中收到相同的信息。

它们以 FIFO 顺序的先入先出的方式出现,就像从实际的管道中一样。

PipedReaderPipedWriter

PipedReaderReader类的扩展,用于读取字符流。 它的read()方法读取连接的PipedWriter的流。 同样,PipedWriterWriter类的扩展,它执行Reader类所收缩的所有工作。

可以通过以下两种方法将作家连接到读者:

  1. 使用构造器PipedWriter(PipedReader pr)
  2. 使用connect(PipedReader pr)方法

通过上述任何一种方式连接后,任何线程都可以使用write(....)方法在流中写入数据,并且数据将可供读取器使用,并且可以使用read()方法进行读取。

Java 线程间通信示例

下面给出的 Java 程序创建两个线程。 一个线程负责写入流,第二个线程仅读取数据以在控制台中打印它们。

public class PipeReaderThread implements Runnable 
{
    PipedReader pr;
    String name = null;

	public PipeReaderThread(String name, PipedReader pr) 
	{
	    this.name = name;
	    this.pr = pr;
	}

	public void run() 
	{
	    try {
	        // continuously read data from stream and print it in console
	        while (true) {
	            char c = (char) pr.read(); // read a char
	            if (c != -1) { // check for -1 indicating end of file
	                System.out.print(c);
	            }
	        }
	    } catch (Exception e) {
	        System.out.println(" PipeThread Exception: " + e);
	    }
	}
}

public class PipeWriterThread implements Runnable 
{
    PipedWriter pw;
    String name = null;

	public PipeWriterThread(String name, PipedWriter pw) {
	    this.name = name;
	    this.pw = pw;
	}

	public void run() {
	    try {
	        while (true) {
	            // Write some data after every two seconds
	            pw.write("Testing data written...n");
	            pw.flush();
	            Thread.sleep(2000);
	        }
	    } catch (Exception e) {
	        System.out.println(" PipeThread Exception: " + e);
	    }
	}
}

package multiThread;

import java.io.*;

public class PipedCommunicationTest 
{
	public static void main(String[] args) 
	{
	    new PipedCommunicationTest();
	}

	public PipedCommunicationTest() 
	{
	    try 
	    {
	        // Create writer and reader instances
	        PipedReader pr = new PipedReader();
	        PipedWriter pw = new PipedWriter();

	        // Connect the writer with reader
	        pw.connect(pr);

	        // Create one writer thread and one reader thread
	        Thread thread1 = new Thread(new PipeReaderThread("ReaderThread", pr));

	        Thread thread2 = new Thread(new PipeWriterThread("WriterThread", pw));

	        // start both threads
	        thread1.start();
	        thread2.start();

	    } 
	    catch (Exception e) 
	    {
	        System.out.println("PipeThread Exception: " + e);
	    }
	}
}

程序输出:

Testing data written...
Testing data written...
Testing data written...

总结

  • 您必须先创建某种类型的读取器并将其连接,然后才能写入管道。 换句话说,两个端必须存在并且已经连接,以使写入端正常工作。
  • 完成写入管道后,您将无法切换到最初未连接管道的其他读取器。
  • 如果关闭阅读器,则无法从管道中读回。 但是,您可以成功关闭写入端,并且仍然可以从管道读取数据。
  • 如果写入管道的线程结束,则无法从管道读回。

学习愉快!

Java 死锁示例和解决方案

原文: https://howtodoinjava.com/java/multi-threading/writing-a-deadlock-and-resolving-in-java/

通过示例,以编程方式学习在 Java 中创建死锁。 还学习检测死锁以及如何解决源代码中的死锁情况。

在我以前的文章中,我写了关于当属性文件中发生任何更改时自动重载配置的信息,我讨论了有关使用 Java WatchService刷新应用程序配置的信息。 由于配置是共享资源,并且通过线程进行访问时,总是有机会编写不正确的代码,而这可能导致死锁。

1. 死锁

在 Java 中,死锁是这样的情况,其中至少有两个线程在某个不同的资源上持有锁,并且两个线程都在等待其他资源完成其任务。 而且,没有人能够锁定它所持有的资源。

deadlock scenario

死锁场景

在上述情况下,Thread-1具有A但需要B才能完成处理,类似地Thread-2具有资源B但首先需要A

package thread;

public class ResolveDeadLockTest {

	public static void main(String[] args) {
		ResolveDeadLockTest test = new ResolveDeadLockTest();

		final A a = test.new A();
		final B b = test.new B();

		// Thread-1
		Runnable block1 = new Runnable() {
			public void run() {
				synchronized (a) {
					try {
						// Adding delay so that both threads can start trying to
						// lock resources
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// Thread-1 have A but need B also
					synchronized (b) {
						System.out.println("In block 1");
					}
				}
			}
		};

		// Thread-2
		Runnable block2 = new Runnable() {
			public void run() {
				synchronized (b) {
					// Thread-2 have B but need A also
					synchronized (a) {
						System.out.println("In block 2");
					}
				}
			}
		};

		new Thread(block1).start();
		new Thread(block2).start();
	}

	// Resource A
	private class A {
		private int i = 10;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}

	// Resource B
	private class B {
		private int i = 20;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}
}

由于非常明显的原因,上面的代码运行将导致死锁(如上所述)。 现在我们必须解决这个问题。

2. 如何避免死锁

我相信,解决任何问题的方法都在于确定问题的根源。 在我们的情况下,这是访问资源AB的模式,这是主要问题。 因此,要解决此问题,我们将仅对代码访问共享资源的语句重新排序。

       // Thread-1
	Runnable block1 = new Runnable() {
		public void run() {
			synchronized (b) {
				try {
					// Adding delay so that both threads can start trying to
					// lock resources
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// Thread-1 have A but need B also
				synchronized (a) {
					System.out.println("In block 1");
				}
			}
		}
	};

	// Thread-2
	Runnable block2 = new Runnable() {
		public void run() {
			synchronized (b) {
				// Thread-2 have B but need A also
				synchronized (a) {
					System.out.println("In block 2");
				}
			}
		}
	};

在类之前再次运行,您将不会遇到任何死锁情况。 我希望,它将帮助您避免死锁,如果遇到死锁,也可以解决死锁。

学习愉快!

Java 集合

Java 中的集合

原文: https://howtodoinjava.com/java-collections/

顾名思义,集合是一组对象。 Java 集合框架由接口和类组成,这些接口和类有助于处理不同类型的集合,例如列表,集合,映射,栈和队列等。

这些现成的集合类解决了许多非常常见的问题,在这些问题中,我们需要处理一组同构对象和异类对象。 其中的常见操作涉及添加,删除,更新,排序,搜索和更复杂的算法。 这些集合类使用集合 API 为所有此类操作提供了非常透明的支持。

1. Java 集合层次结构

借助核心接口可以更好地理解集合框架。 集合类实现这些接口并提供具体功能。

Java Collections Hierarchy

Java 集合层次结构

1.1 集合

集合接口位于层次结构的根部。 集合接口提供所有集合类必须支持(或抛出UnsupportedOperationException)的所有通用方法。 它扩展了Iterable接口,该接口使用“for-each循环”语句添加了对集合元素进行迭代的支持。

所有其他集合接口和类(Map除外)都可以扩展或实现此接口。 例如,列表(已索引,有序)和集(已排序)接口实现了此集合。

1.2 列表

列表表示元素的有序集合。 使用列表,我们可以按元素的整数索引(列表中的位置)访问元素,并在列表中搜索元素。 索引以0开头,就像数组一样。

实现List接口的一些有用的类是 – ArrayListCopyOnWriteArrayListLinkedListStackVector

1.3 集

代表排序的元素的集合。 集合不允许重复的元素。Set接口不能保证以任何可预测的顺序返回元素。 尽管某些Set实现以其自然顺序存储元素并保证此顺序。

实现Set接口的一些有用的类是 – ConcurrentSkipListSetCopyOnWriteArraySetEnumSetHashSetLinkedHashSetTreeSet

1.4 映射

Map接口使我们能够将数据存储在键值对中(键应该是不可变的)。 映射不能包含重复的键; 每个键最多可以映射到一个值。

Map接口提供了三个集合视图,这些视图允许将映射的内容作为一组键,值的集合或一组键值映射来查看。 一些映射实现(例如TreeMap类)对其顺序做出特定的保证。 其他的(例如HashMap类)则没有。

实现Map接口的一些有用的类是 – ConcurrentHashMapConcurrentSkipListMapEnumMapHashMapHashTableIdentityHashMapLinkedHashMapPropertyTreeMapWeakHashMap

1.5 Stack

Java Stack接口表示经典的栈数据结构,其中的元素可以被推入对象的后进先出(LIFO)栈。 在栈中,我们将元素推到栈的顶部,然后再次从栈顶部弹出。

1.6 Queue

队列数据结构旨在在由使用者线程进行处理之前保存元素(由生产者线程放入)。 除了基本的“集合”操作外,队列还提供其他插入,提取和检查操作。

队列通常但不一定以 FIFO(先进先出)的方式对元素进行排序。 一种此类异常情况是优先级队列,该队列根据提供的比较器或元素的自然顺序对元素进行排序。

通常,队列不支持阻止插入或检索操作。 阻塞队列实现类实现了BlockingQueue接口。

实现Map接口的一些有用的类是 – ArrayBlockingQueueArrayDequeConcurrentLinkedDequeConcurrentLinkedQueueDelayQueueLinkedBlockingDequeLinkedBlockingQueueLinkedListLinkedTransferQueuePriorityBlockingQueuePriorityQueueSynchronousQueue

1.7 Deque

一个双端队列(发音为“DQ”),支持两端的元素插入和移除。 当双端队列用作队列时,将产生 FIFO(先进先出)行为。 当双端队列用作栈时,将产生 LIFO(后进先出)行为。

此接口应优先于旧版Stack类使用。 当双端队列用作栈时,元素从双端队列的开头被压入并弹出。

实现此接口的一些常见的已知类是ArrayDequeConcurrentLinkedDequeLinkedBlockingDequeLinkedList

2. Java 集合和泛型

有目的的泛型提供类型安全性。 它检测到不兼容的类型(在方法参数中),并在运行时防止ClassCastException。 同样在 Java 集合中,我们可以定义一个集合类以仅存储某种类型的对象。 所有其他类型均应禁止。 这是通过泛型完成的。

在给定的示例中,允许使用前两个add()方法。 第三者将无法编译并给出错误 - “类型为HashMap<Integer,String>put(Integer, String)方法不适用于参数(String, String)”。 它有助于及早发现不兼容的类型,以防止运行时发生不可预测的行为。

HashMap<Integer, String> map = new HashMap<>();

map.put(1, "A");	//allowed
map.put(2, "B");	//allowed

map.put("3", "C");	//NOT allowed - Key is string

3. equals()hashCode()方法

许多集合类提供特定的特征,例如排序的元素,没有重复的元素等。要实现此行为,添加的元素(对象)必须正确实现 equals()hashCode()方法

所有 Java 包装器类和String类均以其特定实现覆盖这些函数,因此它们在此类集合中的行为正确。 我们还需要确保在用户定义的自定义类中正确覆盖了这些函数。

SortedSet<Integer> sortedSet = new TreeSet<>();

sortedSet.add(2);

sortedSet.add(1);
sortedSet.add(1);

sortedSet.add(3);

System.out.println(sortedSet); 	//[1,2,3]

4. Java 8 集合

Java 8 是主要版本,它引入了 Java 编程中的 lambda 样式。 结果,集合类也得到了改善。 例如,我们可以单行遍历集合,并使用forEach语句对集合的所有元素执行操作。

ArrayList<Integer> list = new ArrayList<>();

list.add(1);
list.add(2);
list.add(3);

list.forEach(System.out::print);

5. Java 集合的好处

  • 一致且可重用的API – 这是任何框架所做的。 它提供了一组一致的类方法,这些方法可用于一遍又一遍地解决一组类似的问题,而不会得到无法预测的结果。 Java 集合框架还有助于以一致的方式解决与一组对象有关的常见问题。

    所有集合类都具有一致的实现,并提供一些常见的方法,例如addgetputremove等。无论您要处理哪种数据结构,这些方法都将根据基础实现工作并透明地执行操作。

  • 更少的开发时间 – 通用且可预测的框架总是会减少开发时间,并有助于快速编写应用程序。 Java 集合还有助于对对象和集合执行一些最重复的常见任务,从而改善时间因素。

  • 性能 – Java 集合 API 是由一些最杰出的行业人士编写的,它们的性能在大多数情况下都是一流的。 Oracle 和非常热心的 Java 开发人员社区正在进行的开发工作有助于使它变得更好。

  • 干净的代码 – 这些 API 都是使用所有良好的编码惯例编写的,并且记录得很好。 它们在整个 Java 集合框架中遵循特定的标准。 它使程序员的代码看起来干净整洁。

    由于一致的类和方法名称,因此代码也更易于阅读。

6. Java 集合示例

阅读更多:

Wikepedia 链接
Java 文档

Java 中的数组

原文: https://howtodoinjava.com/java-array/

数组是一个容器对象,在连续内存位置中保存单类型固定数量的值。 它是一种数据结构,用于存储有限数量的元素,并且所有元素必须具有相似的数据类型。

数组是基于索引的数据结构,因此它们允许对存储的元素进行随机访问。 索引以'0'开头。

1. 内存中的数组表示

在此示例中,我们创建了一个由 5 个元素组成的数组。 索引的范围是'0''4'

int[] a = new int[5];

a[0] = 1;
a[1] = 2;
a[2] = 4;
a[3] = 8;
a[4] = 16;

上面示例的图形表示可以如下。

Array in memory

内存数组

2. 数组特性

  • 数组也是 Java 中Object的子类型。
  • 数组是对象,因此我们可以使用'length'属性找到数组的长度。
  • Java 数组是类型。 我们可以声明数组类型的变量
  • 数组是有序的,并且每个元素的索引都从'0'开始。
  • 数组可以存储原始类型以及对象。 但是在一个数组实例中,所有都必须是单一类型。
  • 就像其他变量一样,数组也可以是staticfinal或用作方法参数。
  • 数组的大小必须由int值指定。
  • Java 数组是CloneableSerializable

3. Java 中的数组类型

数组可以是两种类型之一。

3.1 一维数组

仅存储原始类型或对象的数组称为一维数组。 一维数组声明的一般形式为:

type var-name[];
OR
type[] var-name;

//Examples

int[] numbers;

String names[];

3.2 多维数组

多维数组存储其他数组。 它是个数组的数组。 在多维数组中,数组的每个元素都包含其他数组的引用。 多维数组是通过在每个维上附加一组方括号([ ])来创建的。

type var-name[][];
OR
type[][] var-name;

//Examples

int[][] cordinates;

String nameSets[][];

4. 数组示例

如何检查数组是否包含元素

Java 数组克隆示例

Java 数组深层复制示例

将字符串转换为字符串数组

如何打印数组

Java 复制数组范围

如何复制数组

将字节数组转换为字符串

将字符串转换为字节数组

打印数组的内容

删除数组中的重复元素

学习愉快!

Java ArrayList指南

原文: https://howtodoinjava.com/java-arraylist/

Java 中的 ArrayList表示可调整大小的对象列表。 我们可以在此列表中添加,删除,查找,排序和替换元素。 ArrayList是 Java 的集合框架的一部分,并实现 Java 的List接口。

ArrayList的层次结构

Java ArrayList类扩展了实现List接口的AbstractList类。 List接口以分层顺序扩展了CollectionIterable接口。

ArrayList Hierarchy

ArrayList 层次结构

1. ArrayList特性

ArrayList具有以下特性:

  1. 有序ArrayList中的元素保留其顺序,默认情况下是其添加到列表的顺序。
  2. 基于索引 – 可以使用索引位置随机访问元素。 索引以'0'开头。
  3. 动态调整大小 – 当需要添加的元素数量超过当前大小时,ArrayList动态增长。
  4. 不同步 – 默认情况下,ArrayList不同步。 程序员需要适本地使用synchronized关键字,或简单地使用Vector类。
  5. 允许重复 – 我们可以在ArrayList中添加重复元素。 不能成组放置。

2. ArrayList的内部工作

ArrayList类是围绕后备数组实现的。 从arraylist添加或删除的元素实际上是在此后备数组中修改的。 所有arraylist方法都访问此数组并获取/设置数组中的元素。

ArrayList基本上可以看作是 Java 中可调整大小的数组实现。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, 
        		   Cloneable, java.io.Serializable
{
	transient Object[] elementData;		//backing array
	private int size;					//array or list size

	//more code
}

3. ArrayList示例

3.1 创建ArrayList

通常,我们将创建一个空列表并向其中添加元素。 或者,我们将使用另一个现有集合创建一个arraylist

//Empty arraylist
List<String> names = new ArrayList<>(); 

//Arraylist initialized with another collection
List<Integer> numbers = new ArrayList<>(Arrays.asList(1,2,3,4,5)); 

3.2 添加和删​​除元素

使用add()set()remove()方法添加或更新列表中的元素。

//Create arraylist
List<String> names = new ArrayList<>(); 

names.add("lokesh");    //[lokesh]
names.add("alex");      //[lokesh, alex]

names.set(1, "brian");  //[lokesh, brian]

names.remove(1);        //[lokesh]

3.2 迭代

使用iterator()listIterator()获取迭代器实例的引用。 该迭代器可用于迭代arraylist中的元素。

ArrayList<Integer> digits = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));

Iterator<Integer> iterator = digits.iterator();

while(iterator.hasNext()) {
	System.out.println(iterator.next());
}

程序输出。

1
2
3
4
5
6

4. ArrayList方法

ArrayList add()方法示例

ArrayList addAll()方法示例

ArrayList clear()方法示例

ArrayList clone() – 如何克隆ArrayList

ArrayList contains()方法示例

ArrayList sureCapacity()方法示例

ArrayList forEach()方法示例

ArrayList get()方法示例

ArrayList indexOf()方法示例

ArrayList lastIndexOf()方法示例

ArrayList listIterator()方法示例

ArrayList remove()方法示例

ArrayList removeAll()方法示例

ArrayList keepAll()方法示例

ArrayList replaceAll()方法示例

ArrayList removeIf()方法示例

ArrayList sort()方法示例

ArrayList spliterator()方法示例

ArrayList subList()方法示例

ArrayList toArray()方法示例

5. Java ArrayList示例

5.1 创建ArrayList

初始化ArrayList

迭代ArrayList

5.2 添加元素并删除元素

ArrayList的特定索引处添加元素

ArrayList中删除元素

将多个项目添加到ArrayList

5.3 排序ArrayList

ArrayList排序

使用比较器和可比较对象的ArrayList排序

ArrayList分组排序 - 多个比较器示例

使用Collections.sort())方法对ArrayList排序的对象

5.4 获取/搜索

获取ArrayList的子列表

查找ArrayList中元素的最后一个索引

获取ArrayList中元素的索引

ArrayList中获取元素

检查 ArrayList 中是否存在元素

6. Java ArrayList上的其他教程

比较两个ArrayList

同步ArrayList

交换ArrayList

序列化ArrayList

连接两个ArrayList

清空ArrayList

检查ArrayList是否为空

替换ArrayList中现有元素的值

删除ArrayList中的重复元素

7. 转换

LinkedList转换为ArrayList

将向量转换为ArrayList

ArrayList转换为字符串数组

将数组转换为ArrayList

HashSet转换为ArrayList

8. 差异

ArrayList与向量

ArrayListLinkedList

参考文献:

ArrayList Java 文档

Java LinkedList

原文: https://howtodoinjava.com/java/collections/java-linkedlist-class/

Java LinkedList类是ListDeque接口的双链列表实现。 它实现所有可选的列表操作,并允许所有元素(包括null)。

Table of Contents

1\. LinkedList Hierarchy
2\. LinkedList Features
3\. LinkedList Constructors
4\. LinkedList Methods
5\. LinkedList Example
6\. LinkedList Usecases
7\. LinkedList Performance
8\. ArrayList vs LinkedList
9\. Conclusion

1. LinkedList层次结构

LinkedList类扩展了AbstractSequentialList类,实现了ListDeque接口。 这里E是值链表存储的类型。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
	//implementation
}

LinkedList Hierarchy

LinkedList层次结构

2. LinkedList特性

  • 双链表实现,它实现ListDeque接口。 因此,它也可以用作队列,双端队列或栈。
  • 允许所有元素,包括重复和NULL
  • LinkedList维护元素的插入顺序
  • 未同步。 如果多个线程同时访问一个链表,并且至少一个线程在结构上修改了该链表,则它必须在外部进行同步。
  • 使用Collections.synchronizedList(new LinkedList())获取同步链表。
  • 此类返回的迭代器是快速失败的,并且可能抛出ConcurrentModificationException
  • 它没有实现RandomAccess接口。 因此,我们只能按顺序访问元素。 它不支持随机访问元素。
  • 我们可以使用ListIterator来迭代LinkedList元素。

3. LinkedList构造器

  1. LinkedList():初始化一个空的LinkedList实现。
  2. LinkedListExample(Collection c):初始化一个LinkedList,其中包含指定集合的​​元素,并按集合的迭代器返回它们的顺序。

4. LinkedList方法

  1. boolean add(Object o):将指定的元素附加到列表的末尾。
  2. void add(int index, Object element):将指定元素插入列表中指定位置的索引处。
  3. void addFirst(Object o):将给定元素插入列表的开头。
  4. void addLast(Object o):将给定元素附加到列表的末尾。
  5. int size():返回列表中的元素数
  6. boolean contains(Object o):如果列表包含指定元素,则返回true,否则返回false
  7. boolean remove(Object o):删除列表中指定元素的首次出现。
  8. Object getFirst():返回列表中的第一个元素。
  9. Object getLast():返回列表中的最后一个元素。
  10. int indexOf(Object o):返回指定元素首次出现的列表中的索引;如果列表不包含指定元素,则返回 -1。
  11. lastIndexOf(Object o):返回指定元素最后一次出现的列表中的索引;如果列表不包含指定元素,则返回 -1。
  12. Iterator iterator():按适当的顺序返回此列表中元素的迭代器。
  13. Object[] toArray():按正确顺序返回包含此列表中所有元素的数组。
  14. List subList(int fromIndex, int toIndex):返回此列表中指定的fromIndex(包括)和toIndex(不包括)之间的视图。

5. Java LinkedList示例

5.1 添加,删除,迭代

Java 程序演示链表类中基本方法的用法。

import java.util.LinkedList;
import java.util.ListIterator;

public class LinkedListExample 
{
    public static void main(String[] args) 
    {
        //Create linked list
        LinkedList<String> linkedList = new LinkedList<>();

        //Add elements
        linkedList.add("A");
        linkedList.add("B");
        linkedList.add("C");
        linkedList.add("D");

        System.out.println(linkedList);

        //Add elements at specified position
        linkedList.add(4, "A");
        linkedList.add(5, "A");

        System.out.println(linkedList);

        //Remove element
        linkedList.remove("A");		//removes A
        linkedList.remove(0);		//removes B

        System.out.println(linkedList);

        //Iterate
        ListIterator<String> itrator = linkedList.listIterator();

        while (itrator.hasNext()) {
            System.out.println(itrator.next());
        }
    }
}

程序输出。

[A, B, C, D]
[A, B, C, D, A, A]
[C, D, A, A]
C
D
A
A

5.2 在数组和LinkedList之间转换

Java 程序将LinkedList转换为数组,将数组转换为Linkedlist

LinkedList<String> linkedList = new LinkedList<>();

linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
linkedList.add("D");

//1\. LinkedList to Array
String array[] = new String[linkedList.size()];
linkedList.toArray(array);

System.out.println(Arrays.toString(array));

//2\. Array to LinkedList
LinkedList<String> linkedListNew = new LinkedList<>(Arrays.asList(array));

System.out.println(linkedListNew);

程序输出:

[A, B, C, D]
[A, B, C, D]

5.3 如何排序LinkedList

使用Collections.sort()方法对LinkedList排序的 Java 示例。 请注意,对于对象的自定义排序,我们可以使用Collections.sort(linkedList, comparator)方法。

LinkedList<String> linkedList = new LinkedList<>();

linkedList.add("A");
linkedList.add("C");
linkedList.add("B");
linkedList.add("D");

//Unsorted
System.out.println(linkedList);

//1\. Sort the list
Collections.sort(linkedList);

//Sorted
System.out.println(linkedList);

//2\. Custom sorting
Collections.sort(linkedList, Collections.reverseOrder());

//Custom sorted
System.out.println(linkedList);

程序输出:

[A, C, B, D]
[A, B, C, D]
[D, C, B, A]

6. LinkedList用例

在任何桌面应用程序中,动作都可以记录在链表中,并实现从上一次迭代的撤消和重做特性。

可以使用链表对浏览器的“下一个”和“上一个”按钮进行编程。

链表(与哈希表配对)对于 LRU 缓存非常有用。

7. LinkedList性能

在 Java LinkedList类中,由于不需要进行任何转换,因此处理速度很快。 因此,基本上,所有添加和删除方法都提供非常好的性能O(1)

  • add(E element)方法是O(1)
  • get(int index)add(int index, E element)方法的类型为O(n)
  • remove(int index)方法的值为O(n)
  • Iterator.remove()O(1)
  • ListIterator.add(E element)O(1)

应该首选LinkedList,因为没有大量的元素随机访问,而有大量的添加/删除操作。

8. ArrayListLinkedList

让我们列出arraylist和链表之间的一些值得注意的差异

  • ArrayList是使用动态可调整大小的数组的概念实现的。 而LinkedList 是双向链表实现。
  • ArrayList允许随机访问其元素,而LinkedList则不允许。
  • LinkedList还实现Queue接口,该接口添加了比 ArrayList 更多的方法,例如offer()peek()poll()等。
  • LinkedList相比,ArrayList 的添加和删除速度较慢,但​​获取速度较快,因为如果数组LinkedList中已满,则无需调整数组大小并将内容复制到新数组中 。
  • LinkedListArrayList具有更多的内存开销,因为在ArrayList中,每个索引仅保存实际对象,但是在LinkedList的情况下,每个节点都保存下一个和上一个节点的数据和地址。

9. 总结

在此 Java LinkedList教程中,我们学习了什么是LinkedListLinkedListArrayList之间的区别是什么,如何创建LinkedList,如何在 LinkedList中添加,删除和搜索元素,以及如何遍历LinkedList

让我知道您的问题。

学习愉快!

参考:

LinkedList Java 文档

Java HashMap指南

原文: https://howtodoinjava.com/java-hashmap/

Java 中的HashMap在实现Map接口的集合类中。 它用于存储键值对。 每个键都映射到映射中的单个值。

键是唯一的。 这意味着我们只能在映射中插入键K一次。 不允许重复的键。 虽然值V可以映射到多个键。

1. java.util.HashMap

1.1 HashMap类声明

HashMap已声明如下:

public class HashMap<K,V> extends AbstractMap<K,V> 
				implements Map<K,V>, Cloneable, Serializable  

1.2 HashMap类层次结构

如上所示,HashMap实现Map接口并扩展AbstractMap类。

HashMap Hierarchy

HashMap层次结构

2. Java HashMap特性

  • HashMap不能包含重复的键。
  • HashMap允许多个null值,但只允许一个null键。
  • HashMap无序集合。 它不保证元素的任何特定顺序。
  • HashMap不是线程安全的。 您必须显式同步对HashMap的并发修改。 或者,您可以使用Collections.synchronizedMap(hashMap)来获取HashMap的同步版本。
  • 只能使用关联的键来检索值。
  • HashMap仅存储对象引用。 因此,必须将原始类型与其对应的包装器类一起使用。 例如int将存储为Integer
  • HashMap实现了Clonableserializable接口。

3. HashMap内部实现

HashMap按照哈希原理工作。 在将任何公式/算法应用于其属性之后,哈希是一种为任何变量/对象分配唯一代码的方法。 Java 中的每个对象都有其哈希码,这样两个相等的对象必须一致地产生相同的哈希码。

3.1 HashMap.Entry

键值对存储为内部类HashMap.Entry的实例,该内部类将键和值映射存储为属性。 键已标记为final

static class Entry<K ,V> implements Map.Entry<K, V>
{
    final K key;
    V value;

    Entry<K ,V> next;
    final int hash;

    ...//More code goes here
}

3.2 内部工作

Entry类的所有实例都存储在声明为transient Entry[] table的数组中。 对于要存储在HashMap中的每个键值,使用键的哈希码计算哈希值。 该哈希值用于计算数组中用于存储Entry对象的索引

冲突的情况下,其中多个键映射到单个索引位置,形成的链表用来存储所有应该放在单个数组索引位置的所有键值对。

通过键检索值时,使用键的哈希码可以找到第一个索引位置。 然后,在链表中迭代所有元素,并使用equals()方法识别正确的键,从而找到正确的值对象。

4. Java HashMap示例

让我们快速浏览一些示例,以在 Java 中使用HashMap

4.1 添加键值 – HashMap.put()

import java.util.HashMap;

public class HashMapExample 
{
    public static void main(String[] args) throws CloneNotSupportedException 
    {
        HashMap<Integer, String> map = new HashMap<>();

        map.put(1,  "A");
        map.put(2,  "B");
        map.put(3,  "C");

        System.out.println(map);
    }
}

程序输出。

{1=A, 2=B, 3=C}

4.2 通过键获取值 – HashMap.get()

HashMap<Integer, String> map = new HashMap<>();

map.put(1,  "A");
map.put(2,  "B");
map.put(3,  "C");

String value = map.get(2);

System.out.println("The value is :: "+  value );

程序输出:

The value is :: B

4.3 通过键删除偶对 – HashMap.remove()

HashMap<Integer, String> map = new HashMap<>();

map.put(1, "A");
map.put(2, "B");
map.put(3, "C");

System.out.println(map);

map.remove(3);

System.out.println(map);

程序输出:

{1=A, 2=B, 3=C}
{1=A, 2=B}

4.4 迭代HashMap

请注意,此类的迭代器为快速失败,如果在创建迭代器后进行了任何结构修改,它将抛出ConcurrentModificationException

HashMap<Integer, String> map = new HashMap<>();

map.put(1, "A");
map.put(2, "B");
map.put(3, "C");

System.out.println("//Iterate over keys");

Iterator<Integer> itr = map.keySet().iterator();

while (itr.hasNext()) 
{
    Integer key = itr.next();
    String value = map.get(key);

    System.out.println("The key is :: " + key + ", and value is :: " + value );
}

System.out.println("//Iterate over entries set");

Iterator<Entry<Integer, String>> entryIterator = map.entrySet().iterator();

while (entryIterator.hasNext()) 
{
    Entry<Integer, String> entry = entryIterator.next();

    System.out.println("The key is :: " + entry.getKey() + ", and value is :: " + entry.getValue() );
}

程序输出:

//Iterate over keys
The key is :: 1, and value is :: A
The key is :: 2, and value is :: B
The key is :: 3, and value is :: C

//Iterate over entries set
The key is :: 1, and value is :: A
The key is :: 2, and value is :: B
The key is :: 3, and value is :: C

5. HashMap方法

HashMap 类中的方法列表及其简短描述。

  1. void clear():从HashMap中删除所有键值对。
  2. Object clone():返回指定HashMap的浅表副本。
  3. boolean containsKey(Key key):根据是否在映射中找到指定的键,返回truefalse
  4. boolean containsValue(Value Value):类似于containsKey()方法,它查找指定的值而不是键。
  5. Value get(Key key):返回HashMap中指定键的值。
  6. boolean isEmpty():检查映射是否为空。
  7. Set keySet():返回HashMap中存储的所有键的Set
  8. Object put(Key k, Value v):将键值对插入HashMap中。
  9. int size():返回映射的大小,该大小等于存储在HashMap中的键值对的数量。
  10. Collection values():返回映射中所有值的集合。
  11. Value remove(Key key):移除指定键的键值对。
  12. void putAll(Map m):将映射的所有元素复制到另一个指定的映射。

6. HashMap 教程和示例

学习愉快!

Java 控制流语句

原文: https://howtodoinjava.com/java/basics/control-flow-statements/

Java 应用程序代码通常按代码出现的顺序从上到下顺序执行。 要应用业务逻辑,我们可能需要有条件地执行代码。 控制流语句有助于有条件地执行代码块。

所有控制流语句都与业务条件相关联 – 当为真时,代码块将执行; 当为假时将被跳过。

Java 支持以下控制语句

1. If-else语句

If-else语句仅在特定测试求值为true时,才告诉程序执行代码的特定部分,否则执行else块。

我们可以嵌套if-else块。

public class JavaExample 
{
	public static void main(String[] args) 
	{
		boolean condition = true;

		if(condition) {
			System.out.println("Condition is true");
		} 
		else 
		{
			System.out.println("Condition is false");
		}
	}
}

程序输出。

Condition is true

阅读更多: Java if-else语句

2. switch语句

正如if-else语句告诉您的程序仅在特定测试的结果为truefalse时才执行代码的特定部分,所以switch语句可以具有多个执行路径

switch适用于byteshortcharint基本数据类型。 它还适用于枚举类型,String类以及一些包装某些基本类型的特殊类:CharacterByteShortInteger。 (在 Java 5 中添加了枚举,在 Java 7 中添加了String类)。

public class JavaExample 
{
	public static void main(String[] args) 
	{
		String value = "B";

		switch (value) 
		{
			case "A":
				System.out.println("Value is A");
				break;
			case "B": 
				System.out.println("Value is B");
				break;
			default:
				System.out.println("Value is neither A nor B");
				break;
		}
	}
}

程序输出:

Value is B

阅读更多: Java switch语句

3. While循环

while语句或循环在特定条件为true时连续执行语句块。while语句继续测试表达式并执行其块,直到表达式的计算结果为false

public class JavaExample 
{
    public static void main(String[] args)
    {
        int count = 1;
        while (count < 5) 
        {
            System.out.println("Count is: " + count);
            count++;
        }
    }
}

程序输出:

1
2
3
4
5

阅读更多: Java while循环语句

4. do-while循环

do-whilewhile之间的区别在于do-while在循环的底部而不是顶部求值其表达式。 因此,do块中的语句始终至少执行一次。

请注意,do-while语句以分号结尾。 条件表达式必须是布尔表达式。

int i = 1;
int sum = 0;

do 
{
    sum = sum + i;
    i++;
}
while (i <= 10);

System.out.println(sum);

程序输出:

55

阅读更多: Java do-while循环语句

5. for循环

for语句在一系列值上进行迭代。 它反复遍历值,直到满足特定条件为止。

for(int num = 1; num <= 5; num++)
{

     System.out.println(num);

}

程序输出:

1
2
3
4
5

阅读更多: Java for循环语句

6. 增强的foreach循环

Java 5 引入了foreach循环,称为增强的for-each循环。 它用于遍历数组和集合的元素。

int[] numList = {10, 20, 30, 40};

for(int num : numList) 
{
    System.out.println(num);
}

程序输出:

10
20
30
40

阅读更多: Java for-each语句

7. 标签语句

每当在程序执行过程中,遇到带标签的break语句,该控制都会立即退出封闭的带标签的块。 同样,带标签的continue将使控制重新开始。 就像在普通的breakcontinue语句中一样,为块赋予了其他名称。

public class JavaExample 
{
	public static void main(String[] args) 
	{
		loop: for(int i=0; i < 6; i++) 
		{
			if(i % 2 == 0) 
			{
				System.out.println("In if block :: " + i);
				continue loop;
			} 
			else
			{
				System.out.println("In else block :: " + i);
			}
		}
	}
}

程序输出:

In if block :: 0
In else block :: 1
In if block :: 2
In else block :: 3
In if block :: 4
In else block :: 5

阅读更多: Java 标签语句

学习愉快!

参考: Java 文档

Java Hashtable

原文: https://howtodoinjava.com/java/collections/hashtable-class/

Java Hashtable类是哈希表数据结构的实现。 它与 Java 中的HashMap非常相似,最显着的区别是Hashtable同步的HashMap不是。

在此哈希表教程中,我们将学习它的内部结构,构造器,方法,用例和其他要点。

Table of Contents

1\. How Hashtable Works?
2\. Hashtable Features
3\. Hashtable Constructors
4\. Hashtable Methods
5\. Hashtable Example
6\. Hashtable Performance
6\. Hashtable vs HashMap
8\. Conclusion

1. Hashtable如何工作?

Hashtable内部包含存储键/值对的存储桶。 哈希表使用键的哈希码确定键/值对应映射到哪个存储桶。

Java Hashtable

Java 哈希表

从键的哈希码中获取存储桶位置的函数称为哈希函数。 从理论上讲,哈希函数是一种函数,当给定键时,该函数会在表中生成地址。 哈希函数总是返回对象的数字。 两个相等的对象将始终具有相同的数字,而两个不相等的对象可能并不总是具有不同的数字。

当我们将对象放入哈希表时,不同的对象(通过equals()方法)可能具有相同的哈希码。 这称为冲突。 为了解决冲突,哈希表使用列表的数组。 映射到单个存储桶(数组索引)的对存储在列表中,列表引用存储在数组索引中。

Hashtable collision

哈希冲突

1.1 Hashtable声明

Hashtable类在 Java 中声明如下。 它扩展了字典类,并且实现MapCloneableSerializable接口。 'K'是键的类型,'V'是键的映射值的类型。

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable 
{
	//implementation
}

2. Hashtable特性

有关 Java Hashtable类的重要知识是:

  1. 它类似于HashMap,但是它是同步的,而HashMap没有同步
  2. 它不接受null键或值。
  3. 它不接受重复的键。
  4. 它将键值对存储在内部维护列表数组的哈希表数据结构中。 每个列表可以被称为桶。 如果发生冲突,则将对存储在此列表中。
  5. Hashtable中的枚举器不是快速失败的。

3. Hashtable构造器

Hashtable类具有四个构造器。

  • Hashtable():这是默认构造器。 它使用默认的初始容量(11)和负载因子(0.75)构造一个新的空哈希表。
  • Hashtable(int size):它构造一个具有指定初始大小的新的空哈希表。
  • Hashtable(int size, float fillRatio):构造一个具有指定初始大小和填充率的新的空哈希表。
  • Hashtable(Map m):构造一个哈希表,该哈希表使用指定映射中的键值对初始化。

请注意,初始容量是指哈希表中的存储桶数。 需要最佳数量的存储桶来存储具有最小冲突(以提高性能)和有效内存利用率的键值对。

填充率确定在增加哈希表的容量之前可以达到多少。 它的值介于 0.0 到 1.0 之间。

4. Hashtable方法

Hashtable类中的方法与HashMap非常相似。 看一看。

  • void clear():用于删除哈希表中的所有对。
  • boolean contains(Object value):如果哈希表中存在任何对,则返回true,否则返回false。 注意,该方法的特性与containsValue()特性相同。
  • boolean containsValue(Object value):如果哈希表中存在任何对,则返回true,否则返回false
  • boolean containsKey(Object key):如果哈希表中存在任何对,则返回true,否则返回false
  • boolean isEmpty():如果哈希表为空,则返回true;否则,返回 0。 如果它包含至少一个键,则返回false
  • void rehash():用于增加哈希表的大小并重新哈希其所有键。
  • Object get(对象键):它返回指定键所映射到的值。 如果找不到这样的键,则返回null
  • Object put(Object key, Object value):它将哈希表中指定的key映射到指定的value。 键和值都不能为null
  • Object remove(Object key):它从哈希表中删除键(及其对应的值)。
  • int size():它返回哈希表中的条目数。

5. Hashtable示例

我们来看一个有关如何在 Java 程序中使用哈希表的示例。

import java.util.Hashtable;
import java.util.Iterator;

public class HashtableExample 
{
    public static void main(String[] args) 
    {
        //1\. Create Hashtable
        Hashtable<Integer, String> hashtable = new Hashtable<>();

        //2\. Add mappings to hashtable 
        hashtable.put(1,  "A");
        hashtable.put(2,  "B" );
        hashtable.put(3,  "C");

        System.out.println(hashtable);

        //3\. Get a mapping by key
        String value = hashtable.get(1);        //A
        System.out.println(value);

        //4\. Remove a mapping
        hashtable.remove(3);            //3 is deleted

        //5\. Iterate over mappings
        Iterator<Integer> itr = hashtable.keySet().iterator();

        while(itr.hasNext()) 
        {
            Integer key = itr.next();
            String mappedValue = hashtable.get(key);

            System.out.println("Key: " + key + ", Value: " + mappedValue);
        }
    }
}

程序输出。

{3=C, 2=B, 1=A}
A
Key: 2, Value: B
Key: 1, Value: A

6. Hashtable性能

对于大多数常见操作(例如get()put()contains()等),与Hashtable中的O(n)相比,性能明智的HashMapO(log(n))中执行。

Hashtable中朴素的线程安全方法(“同步每个方法”)使线程应用程序变得更加糟糕。 我们最好从外部同步HashMap。 一个经过深思熟虑的设计将比Hashtable表现更好。

哈希表已过时。 最好是使用ConcurrentHashMap类,它们提供更高的并发度。

7. HashtableHashMap

让我们快速列出 Java 中hashmaphashtable之间的差异

  1. HashMap不同步。 哈希表已同步。
  2. HashMap允许一个空键和多个空值。 哈希表不允许使用任何null键或值。
  3. HashMap很快。 由于增加了同步,哈希表很慢。
  4. HashMapIterator遍历。 HashtableEnumeratorIterator遍历。
  5. HashMap中的迭代器是快速失败的。 Hashtable中的枚举器不是快速失败的。
  6. HashMap继承AbstractMap类。 Hashtable继承Dictionary类。

8. 总结

在本教程中,我们了解了 Java Hashtable类,其构造器,方法,现实用例,并比较了它们的性能。 我们还了解了hastable与 Java 中的hashmap有何不同。

不要在新应用程序中使用哈希表。 如果不需要并发,请使用HashMap。 在并发环境中,更喜欢使用ConcurrentHashMap

在评论中把您的问题交给我。

学习愉快!

参考:

Hashtable Java 文档

Java LinkedHashMap

原文: https://howtodoinjava.com/java/collections/linkedhashmap/

Java 中的LinkedHashMap用于存储非常类似于HashMap类的键值对。 区别在于,LinkedHashMap会在HashMap无序的情况下保持插入其中的元素的顺序。

在本 Java 集合教程中,我们将学习LinkedHashMap类,其方法,用例和其他重要细节。

Table of Contents

1\. LinkedHashMap Hierarchy
2\. LinkedHashMap Features
3\. LinkedHashMap Constructors
4\. LinkedHashMap Methods
5\. LinkedHashMap Usecases
6\. LinkedHashMap Performance
7\. Concurrency in LinkedHashMap
8\. Conclusion

1. LinkedHashMap层次结构

LinkedHashMap类在 Java 中声明如下。 它扩展了HashMap ,并且实现了Map 接口。 'K'是键的类型,'V'是键的映射值的类型。

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{
	//implementation
}

2. LinkedHashMap特性

有关 Java LinkedHashMap类的重要知识是:

  • 它存储类似于HashMap的键值对。
  • 它仅包含唯一键。 不允许重复的键。
  • 它可能具有一个null键和多个null值。
  • 通过将元素添加到内部管理的双链表中,它可以保持插入的KV对的顺序。

2.1 按插入顺序排序的LinkedHashMap

默认情况下,LinkedHashMap是插入顺序的。 将元素添加到元素时,它会保持其顺序。 在LinkedHashMap上进行迭代时,我们会按添加KV对的确切顺序获得它们。

LinkedHashMap<Integer, String> pairs = new LinkedHashMap<>();

pairs.put(1,  "A");
pairs.put(2,  "B");
pairs.put(3,  "C");
pairs.put(4,  "D");

pairs.forEach((key, value) -> {
    System.out.println("Key:"+ key + ", Value:" + value);
});

程序输出。

Key:1, Value:A
Key:2, Value:B
Key:3, Value:C
Key:4, Value:D

2.2 按访问顺序排序的LinkedHashMap

在访问顺序映射中,键是根据上次使用LinkedHashMap的任何方法对其进行访问时的访问顺序排序的。 调用putputIfAbsentgetgetOrDefaultcomputecomputeIfAbsentcomputeIfPresentmerge方法会导致对相应条目的访问。

键按从最近访问最少到最近访问的顺序排序,并建立 LRU 缓存。

要创建访问顺序图,LinkedHashMap有一个特殊的构造器参数。 设置为true时,LinkedHashMap维护访问顺序。

//3rd parameter set access order
LinkedHashMap<Integer, String> pairs = new LinkedHashMap<>(2, .75f, true);

pairs.put(1,  "A");
pairs.put(2,  "B");
pairs.put(3,  "C");
pairs.put(4,  "D");

//Access 3rd pair
pairs.get(3);

//Access 1st pair
pairs.getOrDefault(2, "oops");

pairs.forEach((key, value) -> {
    System.out.println("Key:"+ key + ", Value:" + value);
});     

程序输出:

Key:1, Value:A
Key:4, Value:D
Key:3, Value:C
Key:2, Value:B

注意输出,最近访问的条目如何到达序列末尾。

3. LinkedHashMap构造器

LinkedHashMap 具有五种构造器:

  1. LinkedHashMap():使用默认的初始容量(16)和负载因子(0.75)初始化默认的LinkedHashMap实现。
  2. LinkedHashMap(int capacity):使用指定的容量和负载因子(0.75)初始化LinkedHashMap
  3. LinkedHashMap(Map map):使用与指定映射相同的映射初始化LinkedHashMap
  4. LinkedHashMap(int capacity, float fillRatio):使用指定的初始容量和负载因子初始化LinkedHashMap
  5. LinkedHashMap(int Capacity, float fillRatio, boolean Order):初始化LinkedHashMap的容量和填充率以及是否维护插入顺序或访问顺序。
    • 'true'启用访问顺序。
    • 'false'启用插入顺序。 使用其他构造器时,这是默认值行为。

4. LinkedHashMap方法

我们应该学习有关LinkedHashMap的重要方法如下:

  1. void clear():它将从映射中删除所有键值对。
  2. void size():它返回此映射中存在的键/值对的数量。
  3. void isEmpty():如果此映射不包含键值映射,则返回true
  4. boolean containsKey(Object key):如果映射中存在指定的键,则返回'true'
  5. boolean containsValue(Object key):如果将指定值映射到映射中的至少一个键,则返回'true'
  6. Object get(Object key):检索由指定的key映射的value
  7. Object remove(Object key):如果存在,它将从映射中删除指定键的键值对。
  8. boolean removeEldestEntry(Map.Entry eldest):当映射从访问顺序映射中删除其最旧的条目时,它返回'true'

4.1 Java LinkedHashMap示例

Java 程序,用于演示linkedhashmap方法的用法。

import java.util.Iterator;
import java.util.LinkedHashMap;

public class LinkedHashMapExample 
{
    public static void main(String[] args) 
    {
        //3rd parameter set access order
        LinkedHashMap<Integer, String> pairs = new LinkedHashMap<>();

        pairs.put(1,  "A");
        pairs.put(2,  "B");
        pairs.put(3,  "C");

        String value = pairs.get(3);    //get method

        System.out.println(value);

        value = pairs.getOrDefault(5, "oops");  //getOrDefault method

        System.out.println(value);

        //Iteration example
        Iterator<Integer> iterator =  pairs.keySet().iterator();

        while(iterator.hasNext()) {
            Integer key = iterator.next();
            System.out.println("Key: " + key + ", Value: " + pairs.get(key));
        }

        //Remove example
        pairs.remove(3);
        System.out.println(pairs);

        System.out.println(pairs.containsKey(1));    //containsKey method   

        System.out.println(pairs.containsValue("B"));    //containsValue method   
    }
}

程序输出:

C
oops
Key: 1, Value: A
Key: 2, Value: B
Key: 3, Value: C
{1=A, 2=B}
true
true

5. LinkedHashMap用例

在几乎所有需要使用HashMap的情况下,我们都可以使用LinkedHashMap。 在特性方面,它可以非常透明地替换HashMap

另外,LinkedHashMap维护插入顺序,这在我们要维护添加到Map的对的顺序时非常有用。

有序访问权限的LinkedHashMap通过覆盖removeEldestEntry()方法来强加一个用于在将新映射添加到映射时自动删除陈旧的策略的方法,从而为创建 LRU 缓存特性提供了一个很好的起点。 这使您可以使用定义的某些条件使数据过期。

6. LinkedHashMap的性能

HashMapLinkedHashMap以恒定的时间执行添加,删除和包含的基本操作。 LinkedHashMap的性能比HashMap差一些,因为它必须维护一个双向链表,而HashMap仅维护链表。

另一方面,在LinkedHashMap情况下遍历Map的速度比HashMap略快,因为所需的时间仅与“大小”成比例。 对于HashMap,迭代性能与“大小+容量”成正比。

7. LinkedHashMap中的并发

HashMapLinkedHashMap也不是线程安全的,这意味着我们不能直接在多线程应用程序中使用它们以获得一致的结果。 我们应该使用Collections.synchronizedMap(Map map)方法显式地同步它们。

Map<Integer, Integer> numbers = Collections.synchronizedMap(new LinkedHashMap<>());

Map<Integer, Integer> numbers = Collections.synchronizedMap(new HashMap<>());

对于HashMap,更建议使用ConcurrentHashMap,因为它提供的并发程度更高。

8. 总结

基于以上所有信息,我们可以说,在大多数情况下,最好选择HashMap而不是LinkedHashMap。 仅当我们有某些要求或用例需要保持添加到映射的元素顺序时,我们才更喜欢LinkedHashMap

两者在大多数现实用例中都提供几乎相同的性能。 当我们拥有大量数据时,则仅应考虑它们之间的权衡取舍。

学习愉快!

参考:

LinkedHashMap Java 文档

Java TreeMap

原文: https://howtodoinjava.com/java/collections/treemap-class/

Java 中的TreeMap用于存储非常类似于HashMap类的键值对。 区别在于TreeMap提供了一种有效的方式来以排序的顺序存储键/值对。 这是基于红黑树NavigableMap实现。

在本 Java TreeMap教程中,我们将学习TreeMap类,其方法,用例和其他重要细节。

Table of Contents

1\. TreeMap Hierarchy
2\. TreeMap Features
3\. TreeMap Constructors
4\. TreeMap Methods
5\. TreeMap Example
6\. TreeMap Usecases
7\. TreeMap Performance
8\. Concurrency in TreeMap
9\. Conclusion

1. TreeMap层次结构

在 Java 中,TreeMap类的声明如下。 它扩展了AbstractMap类,并且实现了NavigableMap接口。 'K'是键的类型,'V'是键的映射值的类型。

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
	//implementation
}

2. TreeMap特性

关于 Java TreeMap类的要点是:

  • 它存储类似于HashMap的键/值对。
  • 它仅允许不同的键。 重复的密钥是不可能的。
  • 它不能具有null键,但是可以具有多个null值。
  • 它按排序顺序(自然顺序)或在映射创建时提供的Comparator存储键。
  • 它为containsKeygetputremove操作提供了有保证的log(n)时间成本。
  • 它不是同步的。 使用Collections.synchronizedSortedMap(new TreeMap())在并发环境中工作。
  • 通过iterator方法返回的迭代器是快速失败

3. TreeMap构造器

TreeMap具有五种构造器:

  1. TreeMap():使用其键的自然顺序创建一个新的空树映射。
  2. TreeMap(Comparator c):创建一个新的空树映射,根据给定的比较器排序。
  3. TreeMap(Map map):创建一个新的树映射,其中包含与给定映射相同的映射,并根据其键的自然顺序进行排序。
  4. TreeMap(SortedMap map):创建一个新的树映射,其中包含与指定的已排序映射相同的映射并使用相同的顺序。

4. TreeMap方法

我们应该了解的有关TreeMap的重要方法如下:

  1. void clear():它将从映射中删除所有键值对。
  2. void size():它返回此映射中存在的键/值对的数量。
  3. void isEmpty():如果此映射不包含键值映射,则返回true
  4. boolean containsKey(Object key):如果映射中存在指定的键,则返回'true'
  5. boolean containsValue(Object key):如果将指定值映射到映射中的至少一个键,则返回'true'
  6. Object get(Object key):它检索由指定的key映射的value,如果此映射不包含该键的映射,则返回null
  7. Object remove(Object key):如果存在,它将从映射中删除指定键的键值对。
  8. Comparator comparator():它返回用于对映射中的键进行排序的比较器;如果此映射使用其键的自然顺序,则返回 null。
  9. Object firstKey():返回树图中当前的第一个(最小)键。
  10. Object lastKey():返回树图中当前的最后一个(最大)键。
  11. Object ceilingKey(Object key):它返回大于或等于给定键的最小键;如果没有这样的键,则返回null
  12. Object HigherKey(Object key):返回严格大于指定键的最小键。
  13. NavigableMapDescendingMap():它返回此映射中包含的映射的逆序视图

5. Java TreeMap示例

5.1 自然排序的TreeMap示例

Java 程序,以自然顺序演示TreeMap方法的用法。

import java.util.Iterator;
import java.util.TreeMap;

public class LinkedHashMapExample 
{
    public static void main(String[] args) 
    {
        //Natual ordering by deafult
        TreeMap<Integer, String> pairs = new TreeMap<>();

        pairs.put(2,  "B");
        pairs.put(1,  "A");
        pairs.put(3,  "C");

        String value = pairs.get(3);    //get method

        System.out.println(value);

        value = pairs.getOrDefault(5, "oops");  //getOrDefault method

        System.out.println(value);

        //Iteration example
        Iterator<Integer> iterator =  pairs.keySet().iterator();

        while(iterator.hasNext()) {
            Integer key = iterator.next();
            System.out.println("Key: " + key + ", Value: " + pairs.get(key));
        }

        //Remove example
        pairs.remove(3);
        System.out.println(pairs);

        System.out.println(pairs.containsKey(1));    //containsKey method   

        System.out.println(pairs.containsValue("B"));    //containsValue method   

        System.out.println(pairs.ceilingKey(2));
    }
}

程序输出。

C
oops
Key: 1, Value: A
Key: 2, Value: B
Key: 3, Value: C
{1=A, 2=B}
true
true
2

5.2 使用比较器自定义顺序的TreeMap示例

import java.util.Iterator;
import java.util.TreeMap;

public class LinkedHashMapExample 
{
    public static void main(String[] args) 
    {
    	//Sort keys in reverse order
        TreeMap<Integer, String> pairs = new TreeMap<>(Collections.reverseOrder());

        pairs.put(2,  "B" );
        pairs.put(1,  "A");
        pairs.put(3,  "C");

        System.out.println(pairs);
    }
}

程序输出:

{3=C, 2=B, 1=A}

6. TreeMap用例

无论是使用默认排序还是使用比较器进行自定义排序,TreeMap都提供了一种有效的方法来以排序的方式存储和检索其中包含的信息。 这使其成为在需要按顺序显示信息的情况下使用的出色工具。 例如,在任何移动应用程序中,员工基于其年龄或电话号码的信息。

另一个有用的用例可以是字典,在字典中以分类的方式记录和显示信息。

实际上,它们在需要对信息进行分类并且需要快速随机访问的任何地方都非常有用。 如果不需要随机访问,则使用排序集或列表。

7. TreeMap的性能

TreeMap为大多数操作(如add()remove()contains())提供log(n)的性能。 HashMap对于相同的操作执行时性能为O(1)。 这样,HashMap的性能就比TreeMap好得多。

TreeMap在内存管理中具有更好的性能,因为它不在内部维护数组来存储键值对。 在HashMap中,数组大小是在初始化或调整大小时确定的,如果确定大小通常超过当时的需要。 它浪费了内存。 TreeMap没有这种问题。

8. TreeMap中的并发

MapHashMapTreeMap的两个版本均未同步,程序员需要管理对映射的并发访问。

我们可以使用Collections.synchronizedSortedMap(new TreeMap())明确获取树图的同步视图。

Map<Integer, String> syncTreeMap = Collections.synchronizedSortedMap(new TreeMap<Integer, String>());

syncTreeMap.put(1,  "A");
syncTreeMap.put(2,  "B" );
syncTreeMap.put(3,  "C");

9. 总结

在本教程中,我们了解了 Java TreeMap类及其内部。 我们看到了它如何以自然方式(默认)或某些自定义键顺序(使用提供的比较器)以排序方式存储键值对。

我们讨论了在实时应用程序中应如何以及何时使用TreeMap。 我们将TreeMapHashMap的性能进行了比较,以更好地了解何时使用哪个版本的Map

在注释部分中,将您有关在 Java 中使用TreeMap的问题提供给我。

学习愉快!

参考:

TreeMap Java 文档

Java HashSet

原文: https://howtodoinjava.com/java/collections/java-hashset/

Java HashSet类实现了Set接口,由哈希表(实际上是HashMap实例)支持。 如果不对迭代顺序提供任何保证,并允许null元素。

Table of Contents

1\. HashSet Hierarchy
2\. HashSet Features
3\. HashSet Constructors
4\. HashSet Methods
5\. HashSet Example
6\. HashSet Usecases
7\. HashSet Performance
8\. Conclusion

1. HashSet层次结构

HashSet类扩展了实现Set接口的AbstractSet类。 Set接口以层次结构顺序继承CollectionIterable接口。

public class HashSet<E> extends AbstractSet<E> 
				implements Set<E>, Cloneable, Serializable 
{
	//implementation
}

HashSet Hierarchy

哈希集合层次结构

2. HashSet特性

  • 它实现了Set接口。
  • HashSet中不允许重复的值。
  • HashSet中允许一个NULL元素。
  • 它是无序集合,并且不保证集合的迭代顺序。
  • 此类为基本操作(添加,删除,包含和调整大小)提供恒定的时间性能。
  • HashSet不同步。 如果多个线程同时访问哈希集合,并且至少有一个线程修改了哈希集合,则必须在外部对其进行同步。
  • 使用Collections.synchronizedSet(new HashSet())方法来获取同步的哈希集合。
  • 此类的迭代器方法返回的迭代器为快速失败,并且如果在创建迭代器后的任何时间修改了集合,则可能会抛出ConcurrentModificationException,除了通过迭代器自己的remove()方法之外 。
  • HashSet还实现了SearlizableCloneable接口。

2.1 初始容量

初始容量表示创建哈希集合时(在支持HashMap中)的存储桶数。 如果当前大小已满,则存储桶数将自动增加。

默认初始容量为 16 。 我们可以通过在构造器HashSet(int initialCapacity)中传递默认容量来覆盖此默认容量。

2.2 负载系数

负载因子是在自动增加 HashSet 容量之前允许其充满的度量。 默认负载系数为 0.75

这称为阈值,等于(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)。 当HashSet元素计数超过此阈值时,将调整HashSet的大小,并且新容量将是先前容量的两倍。

使用默认的HashSet时,内部容量为 16,负载系数为 0.75。 当表格中有 12 个元素时,存储桶数将自动增加。

3. HashSet构造器

HashSet具有四种类型的构造器:

  1. HashSet():使用默认的初始容量(16)和默认的负载因子(0.75)初始化默认的HashSet实例。
  2. HashSet(int capcity):初始化具有指定容量和默认加载因子(0.75)的HashSet
  3. HashSet(int capcity, float loadFactor):使用指定的初始容量和指定的负载系数初始化HashSet
  4. HashSet(Collection c):使用与指定集合相同的元素初始化HashSet

4. HashSet方法

  1. public boolean add(E e):如果指定的元素尚不存在,则将其添加到Set中。 此方法在内部使用equals()方法检查重复项。 如果元素重复,则元素被拒绝,并且不替换值。
  2. public void clear():从哈希集合中删除所有元素。
  3. public boolean contains(Object o):如果哈希集合包含指定的元素则返回true,否则为false
  4. public boolean isEmpty():如果哈希集合不包含任何元素,则返回true,否则返回false
  5. public int size():返回哈希集合中的元素数。
  6. public Iterator<E> iterator():在此哈希集合中的元素上返回迭代器。 从迭代器返回的元素没有特定的顺序。
  7. public boolean remove(Object o):从哈希集合中删除指定的元素(如果存在)并返回true,否则返回false
  8. public boolean removeAll(Collection <?> c):删除哈希集合中所有属于指定集合的​​元素。
  9. public Object clone():返回哈希集合的浅表副本。
  10. public Spliterator<E> spliterator():在此哈希集合中的元素上创建后绑定且快速失败的拆分器

5. Java HashSet示例

5.1 HashSet添加,删除,迭代器示例

//1\. Create HashSet
HashSet<String> hashSet = new HashSet<>();

//2\. Add elements to HashSet 
hashSet.add("A");
hashSet.add("B");
hashSet.add("C");
hashSet.add("D");
hashSet.add("E");

System.out.println(hashSet);

//3\. Check if element exists
boolean found = hashSet.contains("A");        //true
System.out.println(found);

//4\. Remove an element
hashSet.remove("D");

//5\. Iterate over values
Iterator<String> itr = hashSet.iterator();

while(itr.hasNext()) 
{
    String value = itr.next();

    System.out.println("Value: " + value);
}

程序输出。

[A, B, C, D, E]
true
Value: A
Value: B
Value: C
Value: E

5.2 将HashSet转换为数组示例

Java 示例,使用toArrray()方法将哈希集合转换为数组

HashSet<String> hashSet = new HashSet<>();

hashSet.add("A");
hashSet.add("B");
hashSet.add("C");
hashSet.add("D");
hashSet.add("E");

String[] values = new String[hashSet.size()];

hashSet.toArray(values);

System.out.println(Arrays.toString(values));

程序输出:

[A, B, C, D, E]

5.3 将HashSet转换为ArrayList示例

使用 Java 8 流 API 将哈希集合转换为ArrayList的 Java 示例。

HashSet<String> hashSet = new HashSet<>();

hashSet.add("A");
hashSet.add("B");
hashSet.add("C");
hashSet.add("D");
hashSet.add("E");

List<String> valuesList = hashSet.stream().collect(Collectors.toList());

System.out.println(valuesList);

程序输出:

[A, B, C, D, E]

6. HashSet用例

HashSet非常类似于ArrayList类。 此外,它还会限制重复值。 因此,当我们有一个只需要存储不同元素的需求时,我们可以选择HashSet

HashSet的真实用例可以存储流中的数据,其中流可能包含重复的记录,而我们仅对不同的记录感兴趣。

另一个用例是在给定的句子中找到不同的单词。

7. Java HashSet性能

  • HashSet类为基本操作(添加,删除,包含和大小)提供 O(1)恒定时间性能,假设哈希函数将元素正确分散在存储桶中。
  • 对此集合进行迭代需要的时间与HashSet实例的大小(元素的数量)加上后备HashMap实例的“容量”(存储桶的数量)之和成比例。 因此,如果迭代性能很重要,则不要将初始容量设置得过高(或负载因子过低),这一点非常重要。

8. 总结

从上面的讨论中可以明显看出,在我们要处理重复记录的情况下,HashSet是非常有用的集合类。 它为基本操作提供了可预测的性能。

在评论中向我发送有关 Java 中 HashSet的问题

学习愉快!

参考:

HashSet Java 文档

Java LinkedHashSet

原文: https://howtodoinjava.com/java/collections/java-linkedhashset/

Java LinkedHashSet类扩展了HashSet和实现了Set接口。 除了提供可预测的迭代顺序以外,它与HashSet类非常相似。

Table of Contents

1\. LinkedHashSet Hierarchy
2\. LinkedHashSet Features
3\. LinkedHashSet Constructors
4\. LinkedHashSet Methods
5\. LinkedHashSet Example
6\. LinkedHashSet Usecases
7\. LinkedHashSet Performance
8\. Conclusion

1. LinkedHashSet层次结构

LinkedHashSet类扩展了HashSet类并实现了Set接口。 Set接口以层次结构顺序继承CollectionIterable接口。

public class LinkedHashSet<E> extends HashSet<E> 
				implements Set<E>, Cloneable, Serializable 
{
	//implementation
}

LinkedHashSet Hierarchy

LinkedHashSet层次结构

2. LinkedHashSet特性

  • 它扩展了HashSet类,扩展了AbstractSet类。
  • 它实现了Set接口。
  • LinkedHashSet中不允许重复的值
  • LinkedHashSet中允许一个NULL元素。
  • 这是一个有序集合,这是元素插入到集合中的顺序(插入顺序)。
  • HashSet一样,此类为基本操作(添加,删除,包含和调整大小)提供恒定时间性能
  • LinkedHashSet未同步。 如果多个线程同时访问哈希集合,并且至少有一个线程修改了哈希集合,则必须在外部对其进行同步。
  • 使用Collections.synchronizedSet(new LinkedHashSet())方法来获取同步的LinkedHashSet
  • 此类的迭代器方法返回的迭代器为快速失败,并且如果在创建迭代器后的任何时间修改了集合,则可能会抛出ConcurrentModificationException,除了通过迭代器自己的remove()方法之外 。
  • LinkedHashSet还实现了SearlizableCloneable接口。

2.1 初始容量

初始容量是指创建LinkedHashSet时的存储桶数(在支持HashMap中)。 如果当前大小已满,则存储桶数将自动增加。

默认初始容量为 16 。 我们可以通过在构造器 LinkedHashSet(int initialCapacity)中传递默认容量来覆盖此默认容量。

2.2 负载系数

负载因子是在自动增加LinkedHashSet的容量之前允许其充满的度量。 默认负载系数为 0.75

这称为阈值,等于(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)。 当LinkedHashSet元素计数超过此阈值时,将调整LinkedHashSet的大小,并且新容量是先前容量的两倍。

使用默认的LinkedHashSet时,内部容量为 16,负载系数为 0.75。 当表格中有 12 个元素时,存储桶数将自动增加。

3. LinkedHashSet构造器

LinkedHashSet具有四种构造器:

  1. LinkedHashSet():使用默认的初始容量(16)和负载因子(0.75)初始化默认的LinkedHashSet实例。
  2. LinkedHashSet(int Capacity):使用指定的容量和负载因子(0.75)初始化LinkedHashSet
  3. LinkedHashSet(int Capacity, float loadFactor):使用指定的初始容量和负载因子初始化LinkedHashSet
  4. LinkedHashSet(Collection c):使用与指定集合相同的元素初始化LinkedHashSet

4. LinkedHashSet方法

  1. public boolean add(E e):如果指定的元素尚不存在,则将其添加到Set中。 此方法在内部使用 equals()方法检查重复项。 如果元素重复,则元素被拒绝,并且不替换值。
  2. public void clear():从LinkedHashSet中删除所有元素。
  3. public boolean contains(Object o):如果LinkedHashSet包含指定的元素则返回true,否则为false
  4. public boolean isEmpty():如果LinkedHashSet不包含任何元素,则返回true,否则返回false
  5. public int size():返回LinkedHashSet中的元素数。
  6. public Iterator<E> iterator():在此LinkedHashSet中的元素上返回迭代器。 从迭代器返回的元素没有特定的顺序。
  7. public boolean remove(Object o):从LinkedHashSet中删除指定的元素(如果存在)并返回true,否则返回false
  8. public boolean removeAll(Collection <?> c):删除LinkedHashSet中属于指定集合的​​所有元素。
  9. public Object clone():返回LinkedHashSet的浅表副本。
  10. public Spliterator<E> spliterator():在此LinkedHashSet中的元素上创建后绑定和快速失败的Spliterator。 它具有以下初始化属性Spliterator.DISTINCTSpliterator.ORDERED

5. LinkedHashSet示例

5.1 LinkedHashSet添加,删除,迭代器示例

//1\. Create LinkedHashSet
LinkedHashSet<String> LinkedHashSet = new LinkedHashSet<>();

//2\. Add elements to LinkedHashSet 
LinkedHashSet.add("A");
LinkedHashSet.add("B");
LinkedHashSet.add("C");
LinkedHashSet.add("D");
LinkedHashSet.add("E");

System.out.println(LinkedHashSet);

//3\. Check if element exists
boolean found = LinkedHashSet.contains("A");        //true
System.out.println(found);

//4\. Remove an element
LinkedHashSet.remove("D");

//5\. Iterate over values
Iterator<String> itr = LinkedHashSet.iterator();

while(itr.hasNext()) 
{
    String value = itr.next();

    System.out.println("Value: " + value);
}

程序输出。

[A, B, C, D, E]
true
Value: A
Value: B
Value: C
Value: E

5.2 将LinkedHashSet转换为数组示例

Java 示例,使用toArrray()方法将LinkedHashSet转换为数组。

LinkedHashSet<String> LinkedHashSet = new LinkedHashSet<>();

LinkedHashSet.add("A");
LinkedHashSet.add("B");
LinkedHashSet.add("C");
LinkedHashSet.add("D");
LinkedHashSet.add("E");

String[] values = new String[LinkedHashSet.size()];

LinkedHashSet.toArray(values);

System.out.println(Arrays.toString(values));

程序输出:

[A, B, C, D, E]

5.3 将LinkedHashSet转换为ArrayList示例

使用Java 8 流 APILinkedHashSet转换为arraylist的 Java 示例。

LinkedHashSet<String> LinkedHashSet = new LinkedHashSet<>();

LinkedHashSet.add("A");
LinkedHashSet.add("B");
LinkedHashSet.add("C");
LinkedHashSet.add("D");
LinkedHashSet.add("E");

List<String> valuesList = LinkedHashSet.stream().collect(Collectors.toList());

System.out.println(valuesList);

程序输出:

[A, B, C, D, E]

6. LinkedHashSet用例

LinkedHashSet非常类似于ArrayList(有序)和HashSet(唯一元素)。 此外,它还保证了元素的迭代顺序(按插入元素的顺序)。

LinkedHashSet的实际用例可以存储流中的数据,其中流可能包含按所需顺序的重复记录,我们只对不同的记录感兴趣,但顺序完全相同。

另一个用例是在给定的句子中找到不同的单词,并且单词的顺序应该固定,因为它们出现在句子中。

7. LinkedHashSet性能

  • LinkedHashSet类为基本操作(添加,删除,包含和大小)提供O(1)恒定时间性能,假设哈希函数将元素正确分散在存储桶中。

  • 由于维护链表(除了一个迭代例外)会增加开销,因此性能可能会略低于HashSet

    LinkedHashSet上进行迭代需要的时间与集合的大小成正比,而不管其容量如何。 在HashSet上进行迭代可能会更昂贵,需要的时间与其容量成正比。 因此,LinkedHashSet可以在迭代时提供比HashSet更好的性能。

8. 总结

从上面的讨论中可以明显看出,LinkedHashSet在我们要以某种固定顺序处理重复记录的情况下是非常有用的集合类。 它为基本操作提供了可预测的性能。

如果不需要元素的迭代顺序,则建议改用较轻量的HashSetHashMap

在评论中向我发送与 Java 中的 LinkedHashSet有关的问题。

学习愉快!

参考:

LinkedHashSet Java 文档

Java TreeSet

原文: https://howtodoinjava.com/java/collections/java-treeset-class/

Java TreeSet类扩展了 AbstractSet和实现了NavigableSet接口。 它与HashSet类非常相似,除了它以排序顺序存储元素。

排序顺序可以是自然顺序,也可以是树集合创建时提供的比较器的排序顺序,具体取决于所使用的构造器

Table of Contents

1\. TreeSet Hierarchy
2\. TreeSet Features
3\. TreeSet Constructors
4\. TreeSet Methods
5\. TreeSet Example
6\. TreeSet Usecases
7\. TreeSet Performance
8\. Conclusion

1. TreeSet层次结构

TreeSet类扩展了AbstractSet类并实现了NavigableSet接口。 NavigableSet接口以层次结构顺序扩展SortedSet

class TreeSet<E> extends AbstractSet<E> 
				implements NavigableSet<E>, Cloneable, Serializable 
{
	//implementation
}

TreeSet Hierarchy

树集合层次结构

2. TreeSet 特性

  • 它扩展了AbstractSet类,扩展了AbstractCollection类。
  • 它实现了NavigableSet接口,该接口扩展了SortedSet接口。
  • TreeSet中不允许重复的值。
  • TreeSet中不允许NULL
  • 这是一个有序集合,它按排序顺序存储元素。
  • HashSet一样,此类为基本操作(添加,删除,包含和调整大小)提供恒定的时间性能。
  • TreeSet不允许插入异构对象,因为它必须比较对象以确定排序顺序。
  • TreeSet没有同步。 如果多个线程同时访问哈希集合,并且至少有一个线程修改了哈希集合,则必须在外部对其进行同步。
  • 使用Collections.synchronizedSortedSet(new TreeSet())方法来获取同步的TreeSet
  • 此类的迭代器方法返回的迭代器为快速失败,并且如果在创建迭代器后的任何时间修改了集合,则可能会抛出ConcurrentModificationException,除了通过迭代器自己的remove()方法之外 。
  • TreeSet还实现了SearlizableCloneable接口。

3. TreeSet构造器

TreeSet具有四个可能的构造器:

  1. TreeSet():创建一个新的空树集合,并根据其元素的自然顺序对其进行排序。
  2. TreeSet(Comparator c):创建一个新的空树集合,该树集合根据指定的比较器进行排序。
  3. TreeSet(SortedSet s):创建一个新树集合,其中包含与指定排序集相同的元素并使用相同的顺序。
  4. TreeSet(Collection c):创建一个新树集合,其中包含指定集合中的元素,并根据其元素的自然顺序对其进行排序。

4. TreeSet方法

  1. boolean add(E e):如果指定的元素尚不存在,则将其添加到Set中。
  2. Comparator comparator():返回用于对该集合中的元素进行排序的比较器;如果此集合使用其元素的自然排序,则返回null
  3. Object first():返回此集合中当前的第一个(最小)元素。
  4. Object last():返回当前在此集合中的最后一个(最大)元素。
  5. void clear():从TreeSet中删除所有元素。
  6. boolean contains(Object o):如果TreeSet包含指定的元素,则返回true,否则返回false
  7. boolean isEmpty():如果TreeSet不包含任何元素,则返回true,否则返回false
  8. int size():返回TreeSet中的元素数。
  9. Iterator<E> iterator():以升序返回此集合中元素的迭代器。
  10. Iterator<E> endingIterator():以降序返回此集合中元素的迭代器。
  11. NavigableSet<E> endingSet():返回此集合中包含的元素的逆序视图。
  12. boolean remove(Object o):从TreeSet中删除指定的元素(如果存在)并返回true,否则返回false
  13. Object clone():返回TreeSet的浅表副本。
  14. Spliterator<E> spliterator():在此TreeSet中的元素上创建后绑定和快速失败的分隔符。 它与树集合提供的顺序相同。

5. TreeSet示例

5.1 TreeSet添加,删除,迭代器示例

//1\. Create TreeSet
TreeSet<String> TreeSet = new TreeSet<>();

//2\. Add elements to TreeSet 
TreeSet.add("A");
TreeSet.add("B");
TreeSet.add("C");
TreeSet.add("D");
TreeSet.add("E");

System.out.println(TreeSet);

//3\. Check if element exists
boolean found = TreeSet.contains("A");        //true
System.out.println(found);

//4\. Remove an element
TreeSet.remove("D");

//5\. Iterate over values
Iterator<String> itr = TreeSet.iterator();

while(itr.hasNext()) 
{
    String value = itr.next();

    System.out.println("Value: " + value);
}

程序输出。

[A, B, C, D, E]
true
Value: A
Value: B
Value: C
Value: E

5.2 将TreeSet转换为数组示例

Java 示例,使用toArrray()方法将TreeSet转换为数组

TreeSet<String> TreeSet = new TreeSet<>();

TreeSet.add("A");
TreeSet.add("B");
TreeSet.add("C");
TreeSet.add("D");
TreeSet.add("E");

String[] values = new String[TreeSet.size()];

TreeSet.toArray(values);

System.out.println(Arrays.toString(values));

程序输出:

[A, B, C, D, E]

5.3 将TreeSet转换为ArrayList示例

使用 Java 8 流 API 将TreeSet转换为ArrayList的 Java 示例。

TreeSet<String> TreeSet = new TreeSet<>();

TreeSet.add("A");
TreeSet.add("B");
TreeSet.add("C");
TreeSet.add("D");
TreeSet.add("E");

List<String> valuesList = TreeSet.stream().collect(Collectors.toList());

System.out.println(valuesList);

程序输出:

[A, B, C, D, E]

6. TreeSet用例

TreeSet非常类似于HashSet(唯一元素),并提供可预测的迭代顺序(排序)。 排序顺序可以使用自定义比较器覆盖。

TreeSet在引擎盖下使用红黑树。 因此,该集合可以视为动态搜索树。 当您需要一个经常进行读写操作并应保持秩序的结构时,TreeSet是一个不错的选择。

如果您想使集合保持排序,并且主要是在追加元素,那么带ComparatorTreeSet是最好的选择。

7. TreeSet性能

  • TreeSet为基本操作(添加,删除和包含)提供了保证的log(n)时间成本。
  • 像对元素进行排序一样的迭代操作需要花费O(n)的时间。

8. 总结

从上面的讨论中可以明显看出,在我们要以排序方式处理重复记录的情况下,TreeSet是非常有用的集合类。 它还为基本操作提供了可预测的性能。

如果不需要元素的排序顺序,则建议改用较轻量的HashSetHashMap

在评论中向我发送有关 Java 中 TreeSet的问题

学习愉快!

参考:

TreeSet Java 文档

Java Comparable接口示例

原文: https://howtodoinjava.com/java/collections/java-comparable-interface/

Java Comparable接口,用于根据对象的自然顺序对对象的数组列表进行排序。 元素的自然排序是通过在对象中实现compareTo()方法来实现的。

1. Java Comparable接口

public interface Comparable<T> 
{
	public int compareTo(T o);
}

Java Comparable接口对实现它的每个类的对象强加了总体排序。 此排序称为类的自然排序,而该类的compareTo()方法也称为其自然比较方法。

使用Comparable接口,我们可以对以下元素进行排序:

  1. 字符串对象
  2. 包装器类对象,例如IntegerLong
  3. 用户定义的自定义对象

1.1 compareTo()方法

对于任何支持自然排序的类,它都应实现Comparable接口并覆盖其compareTo()方法。 它必须返回负整数,零或正整数,因为此对象小于,等于或大于指定的对象。

请注意,如果y.compareTo(x)引发异常,则方法必须引发异常。 关系也必须是可传递,即(x.compareTo(y) > 0 && y.compareTo(z) > 0)表示x.compareTo(z)> 0

例如,对于Employee类,自然排序可以基于其员工id

import java.time.LocalDate;

public class Employee implements Comparable<Employee> {

    private Long id;
    private String name;
    private LocalDate dob;

    @Override
    public int compareTo(Employee o) 
    {
        return this.getId().compareTo( o.getId() );
    }
}

所有包装器类和String类都实现Comparable接口。 包装器类通过它们的值进行比较,字符串在字典上进行比较。

1.2 Collections.sort()Arrays.sort()

  1. 使用Collections.sort()方法对对象的列表进行排序。
  2. 使用Arrays.sort()方法对对象的数组进行排序。

1.3 Collections.reverseOrder()

此工具方法返回一个Comparator,它在实现Comparable接口的对象集合上强加自然顺序的逆序。

这为排序(或维护)以反自然顺序实现Comparable接口的对象的集合(或数组)提供了一个简单的习惯用法。

2. Java Comparable示例

所有给定的示例都使用Collections.sort()方法对列表进行排序。 如果我们需要对相同对象的数组进行排序,只需将 Collections.sort()替换为Arrays.sort()

2.1 排序字符串列表

Java 程序使用Comparable接口对字符串列表进行排序。

ArrayList<String> list = new ArrayList<>();

list.add("E");
list.add("A");
list.add("C");
list.add("B");
list.add("D");

Collections.sort(list);

System.out.println(list);

程序输出。

[A, B, C, D, E]

2.2 以相反的顺序对字符串列表进行排序

Java 程序使用Comparable接口对字符串列表进行排序。

ArrayList<String> list = new ArrayList<>();

list.add("E");
list.add("A");
list.add("C");
list.add("B");
list.add("D");

//Sort in reverse natural order
Collections.sort(list, Collections.reverseOrder());

System.out.println(list);

程序输出:

[E, D, C, B, A]

2.3 排序整数列表

Java 程序使用Comparable接口对整数列表进行排序。

ArrayList<Integer> list = new ArrayList<>();

list.add(10);
list.add(300);
list.add(45);
list.add(2);
list.add(5);

//Natural order
Collections.sort(list);

System.out.println(list);

//Sort in reverse natural order
Collections.sort(list, Collections.reverseOrder());

System.out.println(list);

程序输出:

[2, 5, 10, 45, 300]
[300, 45, 10, 5, 2]

2.4 排序员工名单

Java 程序使用Comparable接口对自定义对象列表进行排序。 在此示例中,我们将按 ID 排序员工列表。

ArrayList<Employee> list = new ArrayList<>();

list.add(new Employee(22l, "Lokesh", LocalDate.now()));
list.add(new Employee(18l, "Alex", LocalDate.now()));
list.add(new Employee(30l, "Bob", LocalDate.now()));
list.add(new Employee(600l, "Charles", LocalDate.now()));
list.add(new Employee(5l, "David", LocalDate.now()));

//Natural order
Collections.sort(list);

System.out.println(list);

//Sort in reverse natural order
Collections.sort(list, Collections.reverseOrder());

System.out.println(list);

程序输出:

[
	Employee [id=5, name=David, dob=2018-10-29], 
	Employee [id=18, name=Alex, dob=2018-10-29], 
	Employee [id=22, name=Lokesh, dob=2018-10-29], 
	Employee [id=30, name=Bob, dob=2018-10-29], 
	Employee [id=600, name=Charles, dob=2018-10-29]
]

//Reverse sorted

[
	Employee [id=600, name=Charles, dob=2018-10-30], 
	Employee [id=30, name=Bob, dob=2018-10-30], 
	Employee [id=22, name=Lokesh, dob=2018-10-30], 
	Employee [id=18, name=Alex, dob=2018-10-30], 
	Employee [id=5, name=David, dob=2018-10-30]
]

3. 总结

在本教程中,我们学习了 Java 集合框架的Comparable接口。 它有助于通过简单的接口实现对对象施加自然顺序。

我们学习了对字符串列表,字符串数组,整数列表,整数数组进行排序,并学习了如何使用可比性对 Java 中的员工对象进行排序。

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

学习愉快!

参考文献:

Comparable接口 Java 文档

Java Comparator接口示例

原文: https://howtodoinjava.com/java/collections/java-comparator/

Java 比较器接口,用于根据自定义顺序对对象的数组列表进行排序。 通过在对象中实现Comparator.compare()方法来强加元素的自定义顺序。

1. Java Comparator接口

Java Comparator接口在可能没有自然顺序的对象上强加了自定义顺序

例如,对于Elpmoyees对象的列表,自然顺序可以是按员工 ID 排序的顺序。 但是在现实生活中,我们可能希望按照员工的名字,出生日期或其他任何类似标准对员工列表进行排序。 在这种情况下,我们需要使用Comparator接口。

在以下情况下,我们可以使用Comparator接口。

  1. 对对象的数组或列表进行排序,但不按自然顺序排序。
  2. 在无法修改对象源代码以实现Comparable接口的情况下,对对象的数组或列表进行排序。
  3. 在不同字段上对相同对象列表或对象数组进行排序。
  4. 在不同字段上对对象列表或数组进行分组排序。

1.1. Comparator.compare()

为了对对象启用总体排序,我们需要创建实现Comparator接口的类。 然后,我们需要覆盖它的compare(T o1, T o2)方法。

它比较两个参数的顺序。 当第一个参数小于,等于或大于第二个参数时,它将返回负整数,零或正整数。

实现者还必须确保该关系是可传递的compare(x, y)>0 && compare(y, z)>0隐含compare(x, z)>0

import java.time.LocalDate;

public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;
    private String name;
    private LocalDate dob;

    //Getters and Setters

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", dob=" + dob + "]";
    }
}

对于以上类别,可以通过创建如下的Comparator来强行按员工姓名排序。

import java.util.Comparator;

public class NameSorter implements Comparator<Employee>
{
    @Override
    public int compare(Employee e1, Employee e2) {
        return e1.getName().compareToIgnoreCase( e2.getName() );
    }
}

1.2 Collections.sort()Arrays.sort()

  1. 使用Collections.sort(list, Comparator)方法按提供的比较器实例施加的顺序对对象的列表进行排序。
  2. 使用Arrays.sort(array, Comparator)方法按提供的比较器实例施加的顺序对对象的数组进行排序。

1.3 Collections.comparing()

该工具方法接受一个为类提取排序键的函数。 本质上,这是一个将对类对象进行排序的字段。

//Order by name
Comparator.comparing(Employee::getName);

//Order by name in reverse order
Comparator.comparing(Employee::getName).reversed();

//Order by id field
Comparator.comparing(Employee::getId);

//Order by employee age
Comparator.comparing(Employee::getDate);

1.4 Collections.thenComparing()

该工具方法按类别分组。 使用此方法,我们可以链接多个比较器以对多个字段上的对象列表或对象数组进行排序。

它与SQL GROUP BY子句非常相似,可以对不同字段上的行进行排序。

//Order by name and then by age
Comparator.comparing(Employee::getName)
			.thenComparing(Employee::getDob);

//Order by name -> date of birth -> id 
Comparator.comparing(Employee::getName)
			.thenComparing(Employee::getDob)
			.thenComparing(Employee::getId);

使用以上语法,我们几乎可以创建任何排序逻辑。

1.5 Collections.reverseOrder()

此工具方法返回一个比较器,该比较器在实现Comparable接口的对象集合上强加自然排序自定义排序的逆向。

//Reverse of natural order as specified in 
//Comparable interface's compareTo() method 

Comparator.reversed();

//Reverse of order by name

Comparator.comparing(Employee::getName).reversed();

2. Java Comparator示例

2.1 排序对象列表

Java 示例使用比较器按名称对员工列表进行排序。

ArrayList<Employee> list = new ArrayList<>();

list.add(new Employee(22l, "Lokesh", LocalDate.now()));
list.add(new Employee(30l, "Bob", LocalDate.now()));
list.add(new Employee(18l, "Alex", LocalDate.now()));
list.add(new Employee(5l, "David", LocalDate.now()));
list.add(new Employee(600l, "Charles", LocalDate.now()));

//Sort in reverse natural order
Collections.sort(list, new NameSorter());

System.out.println(list);

程序输出。

[
	Employee [id=18, name=Alex, dob=2018-10-30], 
	Employee [id=30, name=Bob, dob=2018-10-30], 
	Employee [id=600, name=Charles, dob=2018-10-30], 
	Employee [id=5, name=David, dob=2018-10-30], 
	Employee [id=22, name=Lokesh, dob=2018-10-30]
]

2.2 以相反的顺序对对象列表进行排序

Java 示例使用反向顺序使用比较器按名称对员工列表进行排序。

ArrayList<Employee> list = new ArrayList<>();

list.add(new Employee(22l, "Lokesh", LocalDate.now()));
list.add(new Employee(30l, "Bob", LocalDate.now()));
list.add(new Employee(18l, "Alex", LocalDate.now()));
list.add(new Employee(5l, "David", LocalDate.now()));
list.add(new Employee(600l, "Charles", LocalDate.now()));

Collections.sort(list, Comparator.comparing( Employee::getName ).reversed());

System.out.println(list);

程序输出:

[
	Employee [id=22, name=Lokesh, dob=2018-10-30], 
	Employee [id=5, name=David, dob=2018-10-30], 
	Employee [id=600, name=Charles, dob=2018-10-30], 
	Employee [id=30, name=Bob, dob=2018-10-30], 
	Employee [id=18, name=Alex, dob=2018-10-30]
]

2.3 通过按照多个字段对对象列表进行分组排序

Java 示例,用于按多个字段对多个字段(即字段)上的雇员列表进行排序。

ArrayList<Employee> list = new ArrayList<>();

list.add(new Employee(22l, "Lokesh", LocalDate.now()));
list.add(new Employee(30l, "Lokesh", LocalDate.now()));
list.add(new Employee(18l, "Alex", LocalDate.now()));
list.add(new Employee(5l, "Lokesh", LocalDate.now()));
list.add(new Employee(600l, "Charles", LocalDate.now()));

Comparator<Employee> groupByComparator = Comparator.comparing(Employee::getName)
                                        .thenComparing(Employee::getDob)
                                        .thenComparing(Employee::getId);

Collections.sort(list, groupByComparator);

System.out.println(list);

程序输出:

[
	Employee [id=18, name=Alex, dob=2018-10-30], 
	Employee [id=600, name=Charles, dob=2018-10-30], 
	Employee [id=5, name=Lokesh, dob=2018-10-30], 
	Employee [id=22, name=Lokesh, dob=2018-10-30], 
	Employee [id=30, name=Lokesh, dob=2018-10-30]]

3. 总结

在本教程中,我们学习了 Java 集合框架的Comparator接口。 它有助于在不更改该类源代码的情况下,对对象施加总体顺序。

我们学会了对对象的列表和数组进行排序。 我们学习了如何使用Comparator接口在 Java 中对员工对象进行排序,即 Java 比较器的多个字段示例

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

学习愉快!

参考文献:

Comparator接口 Java 文档

Java Iterator接口示例

原文: https://howtodoinjava.com/java/collections/java-iterator/

Java 迭代器接口,用于迭代集合中的元素(列表,集合或映射)。 它有助于一个接一个地检索指定的收集元素,并对每个元素执行操作。

1. Java Iterator接口

所有 Java 集合类都提供iterator()方法,该方法返回迭代器的实例以遍历该集合中的元素。 例如,arraylistiterator()方法以适当的顺序返回此列表中元素的迭代器。

ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");

Iterator<String> iterator = list.iterator();

while(iterator.hasNext()) {
    System.out.println( iterator.next() );
}

程序输出。

A
B
C
D

2. Java Iterator方法

2.1 Iterator hasNext()

  • 如果迭代在集合中剩余更多元素,则此方法返回true
  • 如果迭代器遍历了所有元素,则此方法将返回false

2.2 Iterator next()

  • 此方法返回迭代中的下一个元素。
  • 如果迭代中没有更多元素,则抛出 NoSuchElementException

2.3 Iterator remove()

  • 它从基础集合中删除迭代器返回的最后一个元素(可选操作)。
  • 每次调用next()只能调用一次此方法。
  • 如果在迭代进行过程中以其他方式(而不是通过调用remove()方法)修改了基础集合,则迭代器将抛出ConcurrentModificationException
  • 执行此操作的迭代器称为快速失败迭代器,因为它们快速而干净地失败,而不是冒着在未来未定时间冒任意,不确定行为的风险。

2.4 Iterator forEachRemaining()

  • 此方法对其余的每个元素执行给定的操作,直到所有元素都已处理或该操作引发异常为止。
  • 如果指定了顺序,则按照迭代顺序执行操作。
  • 如果指定的操作为null,则抛出NullPointerException

3. Java Iterator示例

3.1 ArrayList迭代示例

遍历ArrayList元素的 Java 示例。

ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");

System.out.println(list);

//Get iterator
Iterator<String> iterator = list.iterator();

//Iterate over all elements
while(iterator.hasNext()) 
{
    //Get current element
    String value = iterator.next();

    System.out.println( value );

    //Remove element
    if(value.equals("B")) {
        iterator.remove();
    }
}

System.out.println(list);

程序输出:

[A, B, C, D]
A
B
C
D
[A, C, D]

3.2 HashSet迭代示例

遍历HashSet与遍历列表非常相似。 无明显差异。

HashSet<String> hashSet = new HashSet<>();

hashSet.add("A");
hashSet.add("B");
hashSet.add("C");
hashSet.add("D");

System.out.println(hashSet);

//Get iterator
Iterator<String> iterator = hashSet.iterator();

//Iterate over all elements
while(iterator.hasNext()) 
{
    //Get current element
    String value = iterator.next();

    System.out.println( value );

    //Remove element
    if(value.equals("B")) {
        iterator.remove();
    }
}

System.out.println(list);

程序输出:

[A, B, C, D]
A
B
C
D
[A, C, D]

3.3 HashMap键迭代器示例

迭代HashMap的键的 Java 示例。

HashMap<Integer, String> map = new HashMap<>();

map.put(1, "A");
map.put(2, "B");
map.put(3, "C");
map.put(4, "D");

System.out.println(map);

//Get iterator
Iterator<String> iterator = map.keys().iterator();

//Iterate over all keys
while(iterator.hasNext()) 
{
    String key = iterator.next();
    System.out.println( "Key : " + key + ", Value : " + map.get(key) );
}

程序输出:

{1=A, 2=B, 3=C, 4=D}
Key : 1, Value : A
Key : 2, Value : B
Key : 3, Value : C
Key : 4, Value : D

3.4 HashMap值迭代器示例

遍历HashMap值的 Java 示例。

HashMap<Integer, String> map = new HashMap<>();

map.put(1, "A");
map.put(2, "B");
map.put(3, "C");
map.put(4, "D");

System.out.println(map);

//Get iterator
Iterator<String> iterator = map.values().iterator();

//Iterate over all values
while(iterator.hasNext()) 
{
    System.out.println( "Value : " + iterator.next() );
}

程序输出:

{1=A, 2=B, 3=C, 4=D}
Value : A
Value : B
Value : C
Value : D

3.5 Iterator forEachRemaining()示例

Java 示例,它遍历ArrayList元素并对它们执行操作。

ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");

list.iterator().forEachRemaining( System.out::println );

程序输出:

A
B
C
D

4. 将迭代器转换为流

首先将迭代器转换为Spliterator,然后使用StreamSupportSpliterator获取流,从而将迭代器转换为流。

import java.util.Arrays;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class IteratorToStream 
{
	public static void main(String[] args) 
	{
		// Iterator
		Iterator<String> iterator = Arrays.asList("a", "b", "c")
											.listIterator();
		//Extra step to get Spliterator
		Spliterator<String> splitItr = Spliterators
				.spliteratorUnknownSize(iterator, Spliterator.ORDERED);

		// Iterator -> Stream
		Stream<String> stream = StreamSupport.stream(splitItr, false);

		// Apply stream operations
		stream.forEach(System.out::println);
	}
}

程序输出:

a
b
c

5. 总结

在本教程中,我们学习了 Java Iterator接口。 我们学习了迭代器方法和简单示例来迭代ListSetMap之类的不同集合。

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

学习愉快!

参考文献:

Iterator接口 Java 文档

Java ListIterator接口

原文: https://howtodoinjava.com/java/collections/java-listiterator/

Java ListIterator接口是双向迭代器,用于在向前向后的任一方向上对列表的元素进行迭代。

我们可以使用list.listIterator()方法调用来获取任何给定列表的列表迭代器的引用。 遵循给定的ListIterator语法。

ListIterator<T> listIterator = list.listIterator();

1. Java ListIterator特性

以下是 Java 中ListIterator提供的特性列表。

  1. 从 Java 1.2 开始,可以使用ListIterator
  2. ListIterator扩展了Iterator接口。
  3. ListIterator仅适用于List实现的类。
  4. Iterator不同,ListIterator支持在元素列表上的所有 CRUD 操作(创建,读取,更新和删除啊)。
  5. Iterator不同,ListIterator双向。 它支持正向和反向迭代。
  6. 它没有当前元素; 它的光标位置始终位于通过调用previous()返回的元素和通过调用next()返回的元素之间。

2. Java ListIterator示例

让我们从一个简单的ListIterator Java 示例开始,遍历ArrayList的元素。 请注意,数组索引以'0'开头。

ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");

ListIterator<String> listIterator = list.listIterator();

System.out.println("Forward iteration");

//Forward iterator
while(listIterator.hasNext()) {
    System.out.print(listIterator.next() + ",");
}

System.out.println("Backward iteration");

//Backward iterator
while(listIterator.hasPrevious()) {
    System.out.print(listIterator.previous() + ",");
}

System.out.println("Iteration from specified position");

//Start iterating from index 2
listIterator = list.listIterator(2);

while(listIterator.hasNext()) {
    System.out.print(listIterator.next() + ",");
}

程序输出。

Forward iteration
A,B,C,D,E,F,

Backward iteration
F,E,D,C,B,A,

Iteration from specified position
C,D,E,F,

3. Java ListIterator方法

  1. void add(Object o):将指定的元素插入列表(可选操作)。
  2. boolean hasNext():如果在向前遍历列表时此列表迭代器包含更多元素,则返回true
  3. boolean hasPrevious():如果在反向遍历列表时此列表迭代器包含更多元素,则返回true
  4. Object next():返回列表中的下一个元素并前进光标位置。
  5. int nextIndex():返回元素的索引,该元素的索引将由对next()的后续调用返回。
  6. Object previous():返回列表中的上一个元素,并将光标位置向后移动。
  7. int previousIndex():返回元素的索引,该元素的索引将由对next()的后续调用返回。
  8. void remove():从列表中移除next()previous()返回的最后一个元素(可选操作)。
  9. void set(Object o):将next()previous()返回的最后一个元素替换为指定的元素(可选操作)。

4。总结

在本教程中,我们学习了 Java ListIterator接口。 我们学习了ListIterator方法和简单示例,以向前和向后迭代列表元素。

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

学习愉快!

参考文献:

ListIterator接口 Java 文档

Java 中的标签语句

原文: https://howtodoinjava.com/java/basics/labeled-statements-in-java/

Java 标签块在逻辑上类似于 C/C++ 中的goto语句。

标签是任何有效的 Java 标识符,后跟冒号。 例如external:inner:inner123:inner_:

1. String类中的标签语句

多少次我们被告知goto语句是邪恶的。 我本人已经通过我们这个时代许多受人尊敬的作者读到了这种所谓的邪恶。 但是,如果您查看String.java的源代码,并阅public String toLowerCase(Locale locale)方法的源代码,则您将遇到类似这样的情况 :

scan :
    for (firstUpper = 0 ; firstUpper &lt; count; ) 
    {
        char c = value[offset+firstUpper];
        if ((c >= Character.MIN_HIGH_SURROGATE) &amp;&amp;
            (c <= Character.MAX_HIGH_SURROGATE)) {
            int supplChar = codePointAt(firstUpper);
            if (supplChar != Character.toLowerCase(supplChar)) {
                break scan;
            }
            firstUpper += Character.charCount(supplChar);
        } else {
            if (c != Character.toLowerCase(c)) {
                break scan;
            }
            firstUpper++;
        }
    }
    return this;
}

这是scan:。 这是我们今天将要学习的标签块。 好吧,他们总是告诉我们不要使用它们,并在 JDK 分发中最常用的类中使用了它们。

2. 带有breakcontinue关键字的标签语句

在 Java 中,我们都知道关键字breakcontinue的存在目的。 基本上,语句breakcontinue会更改复合语句的常规控制流。

2.1 带有和不带有标签语句的break关键字

while (Some condition) 
{
  if ( a specific condition ) 
  		break;        //Default usage
  else
  		normal business goes here..
}

另一种方法是将break与标签语句一起使用。

hackit:
while (Some condition) 
{
  if ( a specific condition ) 
  		break hackit;       //Usage with label
  else
  		normal business goes here..
}

每当在程序执行过程中,遇到带标签的break语句,该控制都会立即退出封闭的带标签的块。

同样,带标签的continue将使控制重新开始。 就像在普通的breakcontinue语句中一样,为块赋予了其他名称。

2.2 更多例子

让我们来看更多示例用法:

outer: for (int i = 0; i &lt; 10; i++) {
  inner: for (int j = 10; j > 0; j--) {
    if (i != j) {
      System.out.println(i);
      break outer;
    }else{
      System.out.println("-->>" + i);
      continue inner;
    }
  }
}

或者

int a = 10;
int b = 12;

block1: {
    if (a &lt; 0) {
      break block1;
    }
    if (b &lt; 0) {
      break block1;
    }
    System.out.println( a + b );
  }
}

3. 总结

  • Java 没有通用的goto语句。
  • Java 中的breakcontinue语句更改了复合语句的常规控制流。 他们可以使用带有冒号的有效的 Java 标识符作为标签。
  • 带标签的块只能与breakcontinue语句一起使用。
  • 必须在其范围内调用它们。 您不能引用它们标签的块的范围。
  • break语句立即跳到相应复合语句的末尾(或跳出)。
  • Continue语句立即跳转到相应循环的下一个迭代(如果有)。
  • Continue语句不适用于switch语句或块语句,仅适用于循环的forwhiledo-while循环的复合语句。

学习愉快!

Java Spliterator接口

原文: https://howtodoinjava.com/java/collections/java-spliterator/

Java Spliterator接口是一个内部迭代器,可将分解为较小的部分。 这些较小的零件可以并行处理。

在实际编程中,我们可能永远不需要直接使用分离器。 在正常操作下,它的行为将与 Java 迭代器完全相同。

Spliterator<T> spliterator = list.spliterator();

Java 集合类提供默认的stream()parallelStream()方法,这些方法在内部通过调用splitter()来使用Spliterator()。 它有助于并行处理收集数据。

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

1. Java Spliterator特性

以下是 Java 中Spliterator提供的特性列表。

  1. Java 8 中引入了分离器。
  2. 它为任何集合的元素流的并行处理提供支持。
  3. 它提供tryAdvance()方法来分别迭代不同线程中的元素。 它有助于并行处理。
  4. 要在单个线程中顺序迭代元素,请使用 forEachRemaining()方法。
  5. 如果可能,使用trySplit()方法对分割器进行分区。
  6. 它有助于将hasNext()next()操作组合为一种方法。

2. Java Spliterator方法

  1. int features():返回分离器的特性列表。 它可以是ORDEREDDISTINCTSORTEDSIZEDNONNULLIMMUTABLECONCURRENTSUBSIZED中的任何一个。
  2. long EstimateSize():返回forEachRemaining()遍历将遇到的元素数量的估计值;如果无限,未知或计算成本太高,则返回Long.MAX_VALUE
  3. default void forEachRemaining(Consumer action):依次对当前线程中的每个剩余元素执行给定的操作,直到所有元素都已处理或该动作引发异常。
  4. default comparator getComparator():如果分离器的源由比较器排序,则返回该比较器。
  5. default long getExactSizeIfKnown():如果此分离器为SIZED,则返回estimateSize(),否则为 -1。
  6. default boolean hasCharacteristics(int features):如果spliteratorfeatures()包含所有给定的特征,则返回true
  7. boolean tryAdvance(Consumer action):如果存在剩余元素,则对其执行给定操作,并返回true; 否则返回false
  8. Spliterator trySplit():如果可以对分离器进行分区,则返回一个覆盖元素的Spliterator,从该方法返回后,该分离器将不覆盖该元素。

3. Java Spliterator示例

3.1 Spliterator特性示例

验证从ArrayList获得的Spliterator特性的 Java 示例。

ArrayList<String> list = new ArrayList<>();

Spliterator<String> spliterator = list.spliterator();

int expected = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;

System.out.println(spliterator.characteristics() == expected);	//true

if (spliterator.hasCharacteristics(Spliterator.ORDERED)) {
    System.out.println("ORDERED");
}

if (spliterator.hasCharacteristics(Spliterator.DISTINCT)) {
    System.out.println("DISTINCT");
}

if (spliterator.hasCharacteristics(Spliterator.SORTED)) {
    System.out.println("SORTED");
}

if (spliterator.hasCharacteristics(Spliterator.SIZED)) {
    System.out.println("SIZED");
}

if (spliterator.hasCharacteristics(Spliterator.CONCURRENT)) {
    System.out.println("CONCURRENT");
}

if (spliterator.hasCharacteristics(Spliterator.IMMUTABLE)) {
    System.out.println("IMMUTABLE");
}

if (spliterator.hasCharacteristics(Spliterator.NONNULL)) {
    System.out.println("NONNULL");
}

if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
    System.out.println("SUBSIZED");
}

程序输出。

true

ORDERED
SIZED
SUBSIZED

3.2 Spliterator estimateSize()getExactSizeIfKnown()示例

Java 示例,用于获取支持集合的大小,即由 spliterator 迭代的元素数量。

ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");

Spliterator<String> spliterator = list.spliterator();

System.out.println(spliterator.estimateSize());
System.out.println(spliterator.getExactSizeIfKnown());

程序输出:

4
4

3.3 Spliterator getComparator()示例

查找分配器使用的比较器的 Java 示例。

SortedSet<String> set = new TreeSet<>( Collections.reverseOrder() );

set.add("A");
set.add("D");
set.add("C");
set.add("B");

System.out.println(set);

System.out.println(set.spliterator().getComparator());

程序输出:

[D, C, B, A]
java.util.Collections$ReverseComparator@7852e922

3.4 Spliterator trySplit()示例

Java 示例,将元素分为两组并独立进行迭代。

ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");

Spliterator<String> spliterator1 = list.spliterator();
Spliterator<String> spliterator2 = spliterator1.trySplit();

spliterator1.forEachRemaining(System.out::println);

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

spliterator2.forEachRemaining(System.out::println);

程序输出:

D
E
F
========
A
B
C

3.5 Spliterator forEachRemaining()示例

使用forEachRemaining()方法在单个语句中执行hasNext()next()操作的 Java 示例。

ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");

Spliterator<String> spliterator = list.spliterator();

spliterator.forEachRemaining(System.out::println);

程序输出:

A
B
C
D

4。总结

在本教程中,我们学习了 Java Spliterator接口。 我们学习了Spliterator方法和一些简单示例,以遍历集合元素和流,这与Spliterator中的其他有用方法分开。

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

学习愉快!

参考文献:

Spliterator接口 Java 文档

Java PriorityQueue

原文: https://howtodoinjava.com/java/collections/java-priorityqueue/

Java PriorityQueue类是一种队列数据结构实现,其中,根据对象的优先级处理对象。 它与遵循 FIFO (先进先出)算法的标准队列不同。

在优先级队列中,添加的对象根据其优先级。 默认情况下,优先级由对象的自然顺序决定。 队列构建时提供的比较器可以覆盖默认优先级。

Priority Queue

优先队列

1. PriorityQueue特性

让我们记下PriorityQueue的几个要点。

  • PriorityQueue是一个无界队列,并且会动态增长。 默认初始容量为'11',可以在适当的构造器中使用initialCapacity参数来覆盖。
  • 它不允许使用NULL对象。
  • 添加到PriorityQueue的对象必须是可比较的。
  • 默认情况下,优先级队列的对象以自然顺序排序。
  • 比较器可用于队列中对象的自定义排序。
  • 优先级队列的是基于自然排序或基于比较器排序的最小元素。 当我们轮询队列时,它从队列中返回头对象。
  • 如果存在多个具有相同优先级的对象,则它可以随机轮询其中的任何一个。
  • PriorityQueue不是线程安全的。 在并发环境中使用PriorityBlockingQueue
  • 它为添加和轮询方法提供了O(log(n))时间。

2. Java PriorityQueue示例

让我们看看对象的排序如何影响PriorityQueue中的添加和删除操作。 在给定的示例中,对象的类型为EmployeeEmployee类实现 Comparable接口,默认情况下,该接口使对象可通过员工'id'字段进行比较。

public class Employee implements Comparable<Employee> {

    private Long id;
    private String name;
    private LocalDate dob;

    public Employee(Long id, String name, LocalDate dob) {
        super();
        this.id = id;
        this.name = name;
        this.dob = dob;
    }

    @Override
    public int compareTo(Employee emp) {
        return this.getId().compareTo(emp.getId());
    }

    //Getters and setters

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", dob=" + dob + "]";
    }
}

2.1 自然排序

Java PriorityQueue示例,用于添加和轮询根据其自然顺序进行比较的元素。

PriorityQueue<Employee> priorityQueue = new PriorityQueue<>();

priorityQueue.add(new Employee(1l, "AAA", LocalDate.now()));
priorityQueue.add(new Employee(4l, "CCC", LocalDate.now()));
priorityQueue.add(new Employee(5l, "BBB", LocalDate.now()));
priorityQueue.add(new Employee(2l, "FFF", LocalDate.now()));
priorityQueue.add(new Employee(3l, "DDD", LocalDate.now()));
priorityQueue.add(new Employee(6l, "EEE", LocalDate.now()));

while(true) 
{
    Employee e = priorityQueue.poll();
    System.out.println(e);

    if(e == null) break;
}

程序输出。

Employee [id=1, name=AAA, dob=2018-10-31]
Employee [id=2, name=FFF, dob=2018-10-31]
Employee [id=5, name=BBB, dob=2018-10-31]
Employee [id=4, name=CCC, dob=2018-10-31]
Employee [id=3, name=DDD, dob=2018-10-31]
Employee [id=6, name=EEE, dob=2018-10-31]

2.2 使用比较器自定义顺序

让我们使用基于 Java 8 lambda 的比较器语法重新定义自定义顺序,并验证结果。

//Comparator for name field
Comparator<Employee> nameSorter = Comparator.comparing(Employee::getName);

PriorityQueue<Employee> priorityQueue = new PriorityQueue<>( nameSorter );

priorityQueue.add(new Employee(1l, "AAA", LocalDate.now()));
priorityQueue.add(new Employee(4l, "CCC", LocalDate.now()));
priorityQueue.add(new Employee(5l, "BBB", LocalDate.now()));
priorityQueue.add(new Employee(2l, "FFF", LocalDate.now()));
priorityQueue.add(new Employee(3l, "DDD", LocalDate.now()));
priorityQueue.add(new Employee(6l, "EEE", LocalDate.now()));

while(true) 
{
    Employee e = priorityQueue.poll();
    System.out.println(e);

    if(e == null) break;
}

程序输出:

Employee [id=1, name=AAA, dob=2018-10-31]
Employee [id=5, name=BBB, dob=2018-10-31]
Employee [id=4, name=CCC, dob=2018-10-31]
Employee [id=3, name=DDD, dob=2018-10-31]
Employee [id=6, name=EEE, dob=2018-10-31]
Employee [id=2, name=FFF, dob=2018-10-31]

3. Java PriorityQueue构造器

PriorityQueue类提供了 6 种不同的方法来用 Java 构造优先级队列。

  • PriorityQueue():构造具有默认初始容量(11)的空队列,该初始容量根据其元素的自然顺序对其进行排序。
  • PriorityQueue(Collection c):构造一个空队列,其中包含指定集合中的元素。
  • PriorityQueue(int initialCapacity):构造具有指定初始容量的空队列,该队列根据其自然顺序对其元素进行排序。
  • PriorityQueue(int initialCapacity, Comparator comparator):构造具有指定初始容量的空队列,该队列根据指定的比较器对其元素进行排序。
  • PriorityQueue(PriorityQueue c):构造一个空队列,其中包含指定优先级队列中的元素。
  • PriorityQueue(SortedSet c):构造一个空队列,其中包含指定排序集中的元素。

4. Java PriorityQueue方法

您应该知道PriorityQueue类下面给出了重要的方法。

  • boolean add(object):将指定的元素插入此优先级队列。
  • boolean offer(Object):将指定的元素插入此优先级队列。
  • boolean remove(object):从此队列中移除指定元素的单个实例(如果存在)。
  • Object poll():检索并删除此队列的头部,如果此队列为空,则返回null
  • Object element():检索但不删除此队列的头,如果此队列为空,则抛出NoSuchElementException
  • Object peek():检索但不删除此队列的头,如果此队列为空,则返回null
  • void clear():从此优先级队列中删除所有元素。
  • Comparator comparator():返回用于对此队列中的元素进行排序的比较器;如果此队列是根据其元素的自然顺序排序的,则返回null
  • boolean contains(Object o):如果此队列包含指定的元素,则返回true
  • Iterator iterator():在此队列中的元素上返回一个迭代器。
  • int size():返回此队列中的元素数。
  • Object[] toArray():返回包含此队列中所有元素的数组。

5. 总结

在此 Java 队列教程中,我们学习了使用PriorityQueue类,该类能够按默认的自然顺序或指定比较器的自定义顺序存储元素。

我们还了解了PriorityQueue类的一些重要方法和构造器

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

学习愉快!

参考文献:

PriorityQueue类 Java 文档

Java PriorityBlockingQueue

原文: https://howtodoinjava.com/java/collections/java-priorityblockingqueue/

Java PriorityBlockingQueue类是并发阻塞队列数据结构实现,其中根据对象的优先级处理对象。 添加名称的“阻塞”部分意味着线程将阻塞等待,直到队列上有可用的项目为止。

PriorityBlockingQueue中,添加的对象根据其优先级进行排序。 默认情况下,优先级由对象的自然顺序决定。 队列构建时提供的比较器可以覆盖默认优先级。

Priority Blocking Queue

优先阻塞队列

1. PriorityBlockingQueue特性

让我们记下PriorityBlockingQueue上的几个要点。

  • PriorityBlockingQueue是一个无界队列,并且会动态增长。 默认初始容量为'11',可以在适当的构造器中使用initialCapacity参数来覆盖。
  • 它提供了阻塞检索操作。
  • 它不允许使用NULL对象。
  • 添加到PriorityBlockingQueue的对象必须具有可比性,否则会抛出ClassCastException
  • 默认情况下,优先级队列的对象以自然顺序排序。
  • 比较器可用于队列中对象的自定义排序。
  • 优先级队列的是基于自然排序或基于比较器排序的最小元素。 当我们轮询队列时,它从队列中返回头对象。
  • 如果存在多个具有相同优先级的对象,则它可以随机轮询其中的任何一个。
  • PriorityBlockingQueue线程安全的
  • 方法iterator()中提供的Iterator不能保证以任何特定顺序遍历PriorityBlockingQueue的元素。 如果需要有序遍历,请考虑使用Arrays.sort(pbq.toArray())
  • rainToTo()可用于按优先级顺序删除部分或全部元素,并将它们放置在另一个集合中。

2. Java PriorityBlockingQueue示例

让我们看看对象的排序如何影响PriorityBlockingQueue中的添加和删除操作。 在给定的示例中,对象的类型为EmployeeEmployee类实现Comparable接口,默认情况下,该接口使对象可与员工'id'字段进行比较。

public class Employee implements Comparable<Employee> {

    private Long id;
    private String name;
    private LocalDate dob;

    public Employee(Long id, String name, LocalDate dob) {
        super();
        this.id = id;
        this.name = name;
        this.dob = dob;
    }

    @Override
    public int compareTo(Employee emp) {
        return this.getId().compareTo(emp.getId());
    }

    //Getters and setters

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", dob=" + dob + "]";
    }
}

2.1 自然排序

Java PriorityBlockingQueue示例,用于添加和轮询根据自然顺序进行比较的元素。

PriorityBlockingQueue<Employee> PriorityBlockingQueue = new PriorityBlockingQueue<>();

PriorityBlockingQueue.add(new Employee(1l, "AAA", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(4l, "CCC", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(5l, "BBB", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(2l, "FFF", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(3l, "DDD", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(6l, "EEE", LocalDate.now()));

while(true) 
{
    Employee e = PriorityBlockingQueue.poll();
    System.out.println(e);

    if(e == null) break;
}

程序输出。

Employee [id=1, name=AAA, dob=2018-10-31]
Employee [id=2, name=FFF, dob=2018-10-31]
Employee [id=5, name=BBB, dob=2018-10-31]
Employee [id=4, name=CCC, dob=2018-10-31]
Employee [id=3, name=DDD, dob=2018-10-31]
Employee [id=6, name=EEE, dob=2018-10-31]

2.2 PriorityBlockingQueue比较器示例

让我们使用基于 Java 8 lambda 的比较器语法重新定义自定义顺序,并验证结果。 我们正在使用构造器PriorityBlockingQueue(int initialCapacity, Comparator comparator)

//Comparator for name field
Comparator<Employee> nameSorter = Comparator.comparing(Employee::getName);

PriorityBlockingQueue<Employee> PriorityBlockingQueue = new PriorityBlockingQueue<>( 11, nameSorter );

PriorityBlockingQueue.add(new Employee(1l, "AAA", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(4l, "CCC", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(5l, "BBB", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(2l, "FFF", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(3l, "DDD", LocalDate.now()));
PriorityBlockingQueue.add(new Employee(6l, "EEE", LocalDate.now()));

while(true) 
{
    Employee e = PriorityBlockingQueue.poll();
    System.out.println(e);

    if(e == null) break;
}

程序输出:

Employee [id=1, name=AAA, dob=2018-10-31]
Employee [id=5, name=BBB, dob=2018-10-31]
Employee [id=4, name=CCC, dob=2018-10-31]
Employee [id=3, name=DDD, dob=2018-10-31]
Employee [id=6, name=EEE, dob=2018-10-31]
Employee [id=2, name=FFF, dob=2018-10-31]

2.3 PriorityBlockingQueuerainTo()示例

Java 示例,使用drianTo()方法在一条语句中从队列中获取多个元素。

PriorityBlockingQueue<Integer> priorityBlockingQueue = new PriorityBlockingQueue<>();

priorityBlockingQueue.add(1);
priorityBlockingQueue.add(3);
priorityBlockingQueue.add(2);
priorityBlockingQueue.add(6);
priorityBlockingQueue.add(4);
priorityBlockingQueue.add(5);

ArrayList<Integer> list = new ArrayList<>();

//Drain first 3 elements
priorityBlockingQueue.drainTo(list, 3);

System.out.println(list);

//Drain all elements
priorityBlockingQueue.drainTo(list);

System.out.println(list);

程序输出:

[1, 2, 3]
[1, 2, 3, 4, 5, 6]

2.4 PriorityBlockingQueue阻塞检索示例

Java 示例,它使用阻塞检索从PriorityBlockingQueue中获取元素。 线程将等待,直到队列中存在某个元素。

在给定的示例中,线程正在使用take()方法以无限循环在队列中等待。 等待 1 秒钟,然后再次检查。 一旦我们将元素添加到队列中,它就会轮询该项目并打印到控制台。

import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;

public class PriorityQueueExample 
{
    public static void main(String[] args) throws InterruptedException 
    {
        PriorityBlockingQueue<Integer> priorityBlockingQueue = new PriorityBlockingQueue<>();

        new Thread(() -> 
        {
          System.out.println("Waiting to poll ...");

          try 
          {
              while(true) 
              {
                  Integer poll = priorityBlockingQueue.take();
                  System.out.println("Polled : " + poll);

                  Thread.sleep(TimeUnit.SECONDS.toMillis(1));
              }

          } catch (InterruptedException e) {
              e.printStackTrace();
          }

        }).start();

        Thread.sleep(TimeUnit.SECONDS.toMillis(2));
        priorityBlockingQueue.add(1);

        Thread.sleep(TimeUnit.SECONDS.toMillis(2));
        priorityBlockingQueue.add(2);

        Thread.sleep(TimeUnit.SECONDS.toMillis(2));
        priorityBlockingQueue.add(3);
    }
}

程序输出:

Waiting to poll ...
Polled : 1
Polled : 2
Polled : 3

3. Java PriorityBlockingQueue构造器

PriorityBlockingQueue类提供了 4 种不同的方法来用 Java 构造优先级队列。

  • PriorityBlockingQueue():构造一个空队列,其默认初始容量为(11),该初始容量根据其元素的自然顺序对其进行排序。
  • PriorityBlockingQueue(Collection c):构造一个空队列,其中包含指定集合中的元素。
  • PriorityBlockingQueue(int initialCapacity):构造具有指定初始容量的空队列,该队列根据其自然顺序对其元素进行排序。
  • PriorityBlockingQueue(int initialCapacity, Comparator comparator):构造具有指定初始容量的空队列,该队列根据指定的比较器对其元素进行排序。

4. Java PriorityBlockingQueue方法

您应该知道PriorityBlockingQueue类下面提供了重要的方法。

  • boolean add(object):将指定的元素插入此优先级队列。
  • boolean offer(object):将指定的元素插入此优先级队列。
  • boolean remove(object):从此队列中移除指定元素的单个实例(如果存在)。
  • Object poll():检索并删除此队列的头,如果有必要使元素可用,则等待指定的等待时间。
  • Object poll(timeout, timeUnit):检索并删除此队列的开头,并在必要时等待指定的等待时间以使元素可用。
  • Object take():检索并删除此队列的头,如有必要,请等待直到元素可用。
  • void put(Object o):将指定的元素插入此优先级队列。
  • void clear():从此优先级队列中删除所有元素。
  • Comparator comparator():返回用于对此队列中的元素进行排序的比较器;如果此队列是根据其元素的自然顺序排序的,则返回null
  • boolean contains(Object o):如果此队列包含指定的元素,则返回true
  • Iterator iterator():在此队列中的元素上返回一个迭代器。
  • int size():返回此队列中的元素数。
  • int drainTo(Collection c):从此队列中删除所有可用元素,并将它们添加到给定的集合中。
  • int drainTo(Collection c, int maxElements):从此队列中最多移除给定数量的可用元素,并将其添加到给定的集合中。
  • int remainingCapacity():由于PriorityBlockingQueue不受容量限制,因此始终返回Integer.MAX_VALUE
  • Object[] toArray():返回包含此队列中所有元素的数组。

请注意take()poll()方法之间的区别poll()检索并删除此队列的头部,如果此队列为空,则返回null。 它不阻止操作。

take()检索并删除此队列的头部,如有必要,请等待直到元素可用。 它正在阻止操作。

5. 总结

在此 JavaPriorityBlockingQueue教程中,我们学习了使用PriorityBlockingQueue类的特性,该类可以按默认的自然顺序或自定义顺序(比较器)存储元素。

我们还了解了PriorityBlockingQueue类的一些重要方法和构造器

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

学习愉快!

参考文献:

PriorityBlockingQueue类 Java 文档

Java ArrayBlockingQueue

原文: https://howtodoinjava.com/java/collections/java-arrayblockingqueue/

ArrayBlockingQueue类是由数组支持的 Java 并发有界阻塞队列实现。 它对元素 FIFO(先进先出)进行排序。

ArrayBlockingQueue头部是已在队列中最长时间的元素。 ArrayBlockingQueue尾部是已在队列中最短时间的元素。 将新的元素插入到队列的末尾,并且队列检索操作将在队列的头获得元素。

1. ArrayBlockingQueue特性

让我们记下ArrayBlockingQueue类的几个要点。

  • ArrayBlockingQueue是由数组支持的固定大小的有界队列。
  • 它对元素 FIFO(先进先出)进行排序。
  • 元素插入到尾部,并从队列的开头检索。
  • 创建后,队列的容量无法更改。
  • 它提供阻塞插入和检索操作
  • 它不允许使用NULL对象。
  • ArrayBlockingQueue线程安全的
  • 方法iterator()中提供的Iterator从第一个(头)到最后一个(尾)顺序遍历元素。
  • ArrayBlockingQueue支持可选的公平性策略,用于顺序等待的生产者线程和使用者线程。 公平设置为true时,队列以 FIFO 顺序授予线程访问权限。

2. Java ArrayBlockingQueue示例

2.1 ArrayBlockingQueue阻塞插入和检索示例

使用阻塞插入和检索从ArrayBlockingQueue中放入和取出元素的 Java 示例。

  • 当队列已满时,生产者线程将等待。 一旦从队列中取出一个元素,它就会将该元素添加到队列中。
  • 如果队列为空,使用者线程将等待。 队列中只有一个元素时,它将取出该元素。

Java 数组阻塞队列生产者使用者示例。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class ArrayBlockingQueueExample 
{
    public static void main(String[] args) throws InterruptedException 
    {
        ArrayBlockingQueue<Integer> priorityBlockingQueue = new ArrayBlockingQueue<>(5);

        //Producer thread
        new Thread(() -> 
        {
            int i = 0;
            try 
            {
                while (true) 
                {
                    priorityBlockingQueue.put(++i);
                    System.out.println("Added : " + i);

                    Thread.sleep(TimeUnit.SECONDS.toMillis(1));
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        //Consumer thread
        new Thread(() -> 
        {
            try 
            {
                while (true) 
                {
                    Integer poll = priorityBlockingQueue.take();
                    System.out.println("Polled : " + poll);

                    Thread.sleep(TimeUnit.SECONDS.toMillis(2));
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();
    }
}

程序输出。

Added : 1
Polled : 1
Added : 2
Polled : 2
Added : 3
Added : 4
Polled : 3
Added : 5
Added : 6
Polled : 4
Added : 7
Added : 8
Polled : 5
Added : 9

3. Java ArrayBlockingQueue构造器

ArrayBlockingQueue类提供了 3 种不同的方法来用 Java 构造队列。

  • ArrayBlockingQueue(int Capacity):构造具有给定(固定)容量和默认访问策略的空队列。
  • ArrayBlockingQueue(int capacity, boolean fair):构造具有给定(固定)容量和指定访问策略的空队列。 如果公允值为true,则按 FIFO 顺序处理对在插入或拔出时阻塞的线程的队列访问; 如果为false,则未指定访问顺序。
  • ArrayBlockingQueue(int capacity, boolean fair, Collection c):构造一个具有给定(固定)容量,指定访问策略并最初包含给定集合元素的队列,并按集合的遍历顺序添加迭代器。

4. Java ArrayBlockingQueue方法

您应该知道ArrayBlockingQueue类具有以下给定的重要方法。

  • void put(Object o):将指定的元素插入此队列的尾部,如果队列已满,则等待空间变为可用。
  • boolean add(object):如果可以在不超出队列容量的情况下立即执行此操作,则在此队列的末尾插入指定的元素,如果队列已满,则返回true,并抛出IllegalStateException
  • boolean offer(object):如果可以在不超过队列容量的情况下立即执行此操作,则在此队列的末尾插入指定的元素,如果队列已满,则返回true,并抛出IllegalStateException
  • boolean remove(object):从此队列中移除指定元素的单个实例(如果存在)。
  • Object peek():检索但不删除此队列的头,如果此队列为空,则返回null
  • Object poll():检索并删除此队列的头部,如果此队列为空,则返回null
  • Object poll(timeout, timeUnit):检索并删除此队列的开头,并在必要时等待指定的等待时间以使元素可用。
  • Object take():检索并删除此队列的头,如有必要,请等待直到元素可用。
  • void clear():从此队列中删除所有元素。
  • boolean contains(Object o):如果此队列包含指定的元素,则返回true
  • Iterator iterator():按适当的顺序返回此队列中元素的迭代器。
  • int size():返回此队列中的元素数。
  • intrainToTo(Collection c):从此队列中删除所有可用元素,并将它们添加到给定的集合中。
  • intrainTo(Collection c, int maxElements):从此队列中最多移除给定数量的可用元素,并将其添加到给定的集合中。
  • int remainingCapacity():返回此队列理想情况下(在没有内存或资源约束的情况下)可以接受而不阻塞的其他元素的数量。
  • Object[] toArray():按正确的顺序返回包含此队列中所有元素的数组。

5. 总结

在此 Java ArrayBlockingQueue教程中,我们学习了使用ArrayBlockingQueue类,该类能够将元素存储在固定大小的并发阻塞队列中

我们还学习了ArrayBlockingQueue类的一些重要方法和构造器

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

学习愉快!

参考文献:

ArrayBlockingQueue类 Java 文档

Java TransferQueue – Java LinkedTransferQueue

原文: https://howtodoinjava.com/java/collections/transferqueue-linkedtransferqueue/

Java TransferQueue是并发阻塞队列的实现,在这种实现中,生产者可以等待消费者收到消息。LinkedTransferQueue类是 Java 中TransferQueue的实现。

例如,TransferQueue可能在消息传递应用程序中很有用,在该应用程序中,生产者有时(使用方法transfer())通过消费者调用takepoll来等待元素的接收,而在其他时候则排队元素(通过方法put())而无需等待收货。

当生产者到达TransferQueue传输消息并且有消费者在等待接收消息时,生产者直接将消息传输给消费者。

如果没有消费者等待,那么生产者将不会直接放置消息并返回,而是将等待任何消费者可以使用该消息。

1. LinkedTransferQueue特性

让我们记下 Java 中LinkedTransferQueue的几个要点。

  • LinkedTransferQueue是链接节点上的无限制队列。
  • 此队列针对任何给定的生产者对元素 FIFO(先进先出)进行排序。
  • 元素插入到尾部,并从队列的开头检索。
  • 它提供阻塞插入和检索操作
  • 它不允许使用NULL对象。
  • LinkedTransferQueue线程安全的
  • 由于异步性质,size()方法不是固定时间操作,因此,如果在遍历期间修改此集合,则可能会报告不正确的结果。
  • 不保证批量操作addAllremoveAllretainAllcontainsAllequalstoArray是原子执行的。 例如,与addAll操作并发操作的迭代器可能仅查看某些添加的元素。

2. Java LinkedTransferQueue示例

2.1 LinkedTransferQueue示例

一个非常简单的示例,用于从LinkedTransferQueue添加和轮询消息。

LinkedTransferQueue<Integer> linkedTransferQueue = new LinkedTransferQueue<>();

linkedTransferQueue.put(1);

System.out.println("Added Message = 1");

Integer message = linkedTransferQueue.poll();

System.out.println("Recieved Message = " + message);

程序输出。

Added Message = 1
Recieved Message = 1

2.2 LinkedTransferQueue阻塞插入和检索示例

使用阻塞插入和检索从LinkedTransferQueue放入和取出元素的 Java 示例。

  • 生产者线程将等待,直到有消费者准备从队列中取出项目为止。
  • 如果队列为空,使用者线程将等待。 队列中只有一个元素时,它将取出该元素。 只有在消费者接受了消息之后,生产者才可以再发送一条消息。
import java.util.Random;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;

public class LinkedTransferQueueExample 
{
    public static void main(String[] args) throws InterruptedException 
    {
        LinkedTransferQueue<Integer> linkedTransferQueue = new LinkedTransferQueue<>();

        new Thread(() -> 
        {
            Random random = new Random(1);
            try 
            {
                while (true) 
                {
                    System.out.println("Producer is waiting to transfer message...");

                    Integer message = random.nextInt();
                    boolean added = linkedTransferQueue.tryTransfer(message);
                    if(added) {
                        System.out.println("Producer added the message - " + message);
                    }
                    Thread.sleep(TimeUnit.SECONDS.toMillis(3));
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        new Thread(() -> 
        {
            try 
            {
                while (true) 
                {
                    System.out.println("Consumer is waiting to take message...");

                    Integer message = linkedTransferQueue.take();

                    System.out.println("Consumer recieved the message - " + message);

                    Thread.sleep(TimeUnit.SECONDS.toMillis(3));
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();
    }
}

程序输出:

Producer is waiting to transfer message...
Consumer is waiting to take message...
Producer is waiting to transfer message...
Producer added the message - 431529176
Consumer recieved the message - 431529176
Consumer is waiting to take message...
Producer is waiting to transfer message...
Producer added the message - 1761283695
Consumer recieved the message - 1761283695
Consumer is waiting to take message...
Producer is waiting to transfer message...
Producer added the message - 1749940626
Consumer recieved the message - 1749940626
Consumer is waiting to take message...
Producer is waiting to transfer message...
Producer added the message - 892128508
Consumer recieved the message - 892128508
Consumer is waiting to take message...
Producer is waiting to transfer message...
Producer added the message - 155629808
Consumer recieved the message - 155629808

请注意,控制台中可能有一些打印语句,似乎消费者甚至在生产者生成消息之前就已经使用了该消息。 不要混淆,这是因为示例的并发性。 实际上,它可以按预期工作。

3. Java LinkedTransferQueue构造器

LinkedTransferQueue类提供了 3 种不同的方法来用 Java 构造队列。

  • LinkedTransferQueue():构造一个最初为空的LinkedTransferQueue
  • LinkedTransferQueue(Collection c):构造一个LinkedTransferQueue,最初包含给定集合的元素,并以集合迭代器的遍历顺序添加。

4. Java LinkedTransferQueue方法

您应该知道LinkedTransferQueue类在下面提供了重要的方法。

  • Object take():检索并删除此队列的头,如有必要,请等待直到元素可用。
  • void transfer(Object o):将元素传输到使用者,如有必要,请等待。
  • boolean tryTransfer(Object o):如果可能,立即将元素传输到等待的使用者。
  • boolean tryTransfer(Object o, long timeout, TimeUnit unit):如果有可能,则在超时之前将元素传送给使用者。
  • int getWaitingConsumerCount():返回等待通过BlockingQueue.take()或定时轮询接收元素的使用者数量的估计值。
  • boolean hasWaitingConsumer():如果至少有一个使用者正在等待通过BlockingQueue.take()或定时轮询接收元素,则返回true
  • void put(Object o):将指定的元素插入此队列的尾部。
  • boolean add(object):将指定的元素插入此队列的末尾。
  • boolean offer(object):将指定的元素插入此队列的末尾。
  • boolean remove(object):从此队列中移除指定元素的单个实例(如果存在)。
  • Object peek():检索但不删除此队列的头,如果此队列为空,则返回 null。
  • Object poll():检索并删除此队列的头部,如果此队列为空,则返回 null。
  • Object poll(timeout, timeUnit):检索并删除此队列的开头,并在必要时等待指定的等待时间以使元素可用。
  • void clear():从此队列中删除所有元素。
  • boolean contains(Object o):如果此队列包含指定的元素,则返回true
  • Iterator iterator():按适当的顺序返回此队列中元素的迭代器。
  • int size():返回此队列中的元素数。
  • int drainTo(Collection c):从此队列中删除所有可用元素,并将它们添加到给定的集合中。
  • int drainTo(Collection c, int maxElements):从此队列中最多移除给定数量的可用元素,并将其添加到给定的集合中。
  • int remainingCapacity():返回此队列理想情况下(在没有内存或资源约束的情况下)可以接受而不阻塞的其他元素的数量。
  • Object[] toArray():按正确的顺序返回包含此队列中所有元素的数组。

5. Java TransferQueue总结

在此 Java LinkedTransferQueue教程中,我们学习了使用LinkedTransferQueue类,这是一个并发阻塞队列实现,生产者可以在其中等待使用者接收消息。

我们还了解了LinkedTransferQueue类的一些重要方法和构造器

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

学习愉快!

参考文献:

TransferQueue接口 Java 文档

LinkedTransferQueue类 Java 文档

Java CopyOnWriteArrayList

原文: https://howtodoinjava.com/java/collections/java-copyonwritearraylist/

Java CopyOnWriteArrayListArrayList线程安全变体,其中的所有可变操作(添加,设置等)均通过对基础数组进行全新复制来实现。

它的不可变快照式迭代器方法在创建迭代器时使用了对数组状态的引用。 这在遍历操作远远超过列表更新操作且我们不想同步遍历并且在更新列表时仍希望线程安全的用例中很有用。

Table of Contents

1\. CopyOnWriteArrayList Hierarchy
2\. CopyOnWriteArrayList Features
3\. CopyOnWriteArrayList Example
4\. CopyOnWriteArrayList Constructors
5\. CopyOnWriteArrayList Methods
6\. CopyOnWriteArrayList Usecases
7\. CopyOnWriteArrayList Performance
8\. Conclusion

1. CopyOnWriteArrayList层次结构

CopyOnWriteArrayList类实现以下接口 – ListRandomAccessCloneableserializable

public class CopyOnWriteArrayList<E>
    implements 	List<E>, 
    			RandomAccess, 
    			Cloneable, 
    			Serializable 

{
	private transient volatile Object[] array;

	//implementation
}

2. CopyOnWriteArrayList特性

有关 Java CopyOnWriteArrayList类的重要知识是:

  • CopyOnWriteArrayList类实现ListRandomAccess接口,因此提供ArrayList类中可用的所有特性。
  • 使用CopyOnWriteArrayList进行更新操作的成本很高,因为每个突变都会创建基础数组的克隆副本,并为其添加/更新元素。
  • 它是ArrayList的线程安全版本。 每个访问列表的线程在初始化此列表的迭代器时都会看到自己创建的后备数组快照版本。
  • 因为它在创建迭代器时获取基础数组的快照,所以它不会抛出ConcurrentModificationException
  • 不支持对迭代器的删除操作(删除,设置和添加)。 这些方法抛出UnsupportedOperationException
  • CopyOnWriteArrayList同步列表的并发替代,当迭代次数超过突变次数时,它提供了更好的并发性。
  • 它允许重复的元素和异构对象(使用泛型来获取编译时错误)。
  • 因为它每次创建迭代器时都会创建一个数组的新副本,所以性能比ArrayList

3. CopyOnWriteArrayList示例

显示在不同时间创建的迭代器如何查看CopyOnWriteArrayListlist的快照版本的 Java 程序。 在给定的示例中,当列表具有元素(1,2,3)时,我们首先创建了listitr1

然后,我们在列表中添加了另一个元素,并再次创建了一个迭代器itr2

最后,我们验证了两个迭代器中的元素。

CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[] {1,2,3});

System.out.println(list);	//[1, 2, 3]

//Get iterator 1
Iterator<Integer> itr1 = list.iterator();

//Add one element and verify list is updated
list.add(4);

System.out.println(list);	//[1, 2, 3, 4]

//Get iterator 2
Iterator<Integer> itr2 = list.iterator();

System.out.println("====Verify Iterator 1 content====");

itr1.forEachRemaining(System.out :: println);	//1,2,3

System.out.println("====Verify Iterator 2 content====");

itr2.forEachRemaining(System.out :: println);	//1,2,3,4

程序输出。

[1, 2, 3]
[1, 2, 3, 4]
====Verify Iterator 1 content====
1
2
3
====Verify Iterator 2 content====
1
2
3
4

4. CopyOnWriteArrayList构造器

  • CopyOnWriteArrayList():创建一个空列表。
  • CopyOnWriteArrayList(Collection c):创建一个列表,其中包含指定集合的​​元素,并按集合的迭代器返回的顺序排列。
  • CopyOnWriteArrayList(object[] array):创建一个保存给定数组副本的列表。

5. CopyOnWriteArrayList方法

CopyOnWriteArrayList类支持ArrayList类支持的所有方法。 该行为仅在迭代器(快照迭代器)和列表中发生突变时创建的新支持数组的情况下有所不同。

此外,它提供了一些此类之外的方法。

  • boolean addIfAbsent(Object o):如果不存在,则追加元素。
  • int addAllAbsent(Collection c):按指定集合的​​迭代器返回的顺序,将指定集合中尚未包含在此列表中的所有元素追加到此列表的末尾 。

对于所有受支持的其他方法,请访问ArrayList方法部分。

6. Java CopyOnWriteArrayList用例

在以下情况下,我们更喜欢使用CopyOnWriteArrayList而不是常规ArrayList

  1. 当在并发环境中使用列表时。
  2. 迭代次数超过了变异操作的次数。
  3. 迭代器在创建时必须具有列表的快照版本。
  4. 我们不想以编程方式同步线程访问

7. Java CopyOnWriteArrayList性能

由于每次更新列表时都要增加创建新支持数组的步骤,因此其性能比ArrayList差。
读取操作没有性能开销,并且两个类执行相同的操作。

8. 总结

在此 Java 集合教程中,我们学习了使用CopyOnWriteArrayList类,它的构造器,方法和用例。 我们学习了CopyOnWriteArrayList内部在 Java 中的工作原理以及CopyOnWriteArrayList与同步ArrayList的工作原理。

我们通过 Java CopyOnWriteArrayList示例程序演示了快照迭代器的工作方式。

在评论中把您的问题交给我。

学习愉快!

参考:

CopyOnWriteArrayList Java 文档

Java CopyOnWriteArraySet

原文: https://howtodoinjava.com/java/collections/java-copyonwritearrayset/

Java CopyOnWriteArraySetHashSet线程安全变体,其所有操作均使用基础CopyOnWriteArrayList

CopyOnWriteArrayList类似,它的不可变快照式迭代器方法在创建迭代器时使用对数组状态(在后备列表内)的引用。 这在遍历操作远远超过集合更新操作且我们不想同步遍历并且在更新集合时仍希望线程安全的用例中很有用。

Table of Contents

1\. CopyOnWriteArraySet Hierarchy
2\. CopyOnWriteArraySet Features
3\. CopyOnWriteArraySet Example
4\. CopyOnWriteArraySet Constructors
5\. CopyOnWriteArraySet Methods
6\. CopyOnWriteArraySet Usecases
7\. CopyOnWriteArraySet Performance
8\. Conclusion

1. CopyOnWriteArraySet层次结构

CopyOnWriteArraySet类扩展了AbstractSet类并实现了Serializable接口。

public class CopyOnWriteArraySet<E>
		extends AbstractSet<E>
		implements Serializable

{
	private final CopyOnWriteArrayList<E> al;

	//implementation
}

2. CopyOnWriteArraySet特性

有关 Java CopyOnWriteArraySet类的重要知识是:

  • 作为正常设置的数据结构,它不允许重复。
  • CopyOnWriteArraySet类实现Serializable接口并扩展AbstractSet类。
  • 使用CopyOnWriteArraySet进行更新操作成本很高,因为每个突变都会创建基础数组的克隆副本并向其添加/更新元素。
  • 它是HashSet的线程安全版本。 每个访问该集合的线程在初始化此集合的迭代器时都会看到自己创建的后备数组快照版本。
  • 因为它在创建迭代器时获取基础数组的快照,所以它不会抛出ConcurrentModificationException
  • 不支持迭代器上的变异操作。 这些方法抛出UnsupportedOperationException
  • CopyOnWriteArraySet同步集的并发替代,并且在迭代次数超过突变次数时提供了更好的并发性。
  • 它允许重复的元素和异构对象(使用泛型来获取编译时错误)。
  • 因为它每次创建迭代器时都会创建基础数组的新副本,所以性能比HashSet

3. Java CopyOnWriteArraySet示例

Java 程序,用于显示在不同时间创建的迭代器如何查看CopyOnWriteArraySet中集合的快照版本。 在给定的示例中,当列表具有元素(1,2,3)时,我们首先创建了listitr1

然后,我们在列表中添加了另一个元素,并再次创建了一个迭代器itr2

最后,我们验证了两个迭代器中的元素。

CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>(Arrays.asList(1,2,3));

System.out.println(set);	//[1, 2, 3]

//Get iterator 1
Iterator<Integer> itr1 = set.iterator();

//Add one element and verify set is updated
set.add(4);
System.out.println(set);	//[1, 2, 3, 4]

//Get iterator 2
Iterator<Integer> itr2 = set.iterator();

System.out.println("====Verify Iterator 1 content====");

itr1.forEachRemaining(System.out :: println);	//1,2,3

System.out.println("====Verify Iterator 2 content====");

itr2.forEachRemaining(System.out :: println);	//1,2,3,4

程序输出。

[1, 2, 3]
[1, 2, 3, 4]
====Verify Iterator 1 content====
1
2
3
====Verify Iterator 2 content====
1
2
3
4

4. CopyOnWriteArraySet构造器

  • CopyOnWriteArraySet():创建一个空集。
  • CopyOnWriteArraySet(Collection c):创建一个包含指定集合元素的集合,其顺序由集合的迭代器返回。

5. CopyOnWriteArraySet方法

  • boolean add(object o):如果指定的元素尚不存在,则将其添加到此集合中。
  • boolean addAll(collection c):将指定集合中的所有元素(如果尚不存在)添加到此集合中。
  • void clear():从该集合中删除所有元素。
  • boolean contains(Object o):如果此集合包含指定的元素,则返回 true。
  • boolean isEmpty():如果此集合不包含任何元素,则返回 true。
  • Iterator iterator():以添加这些元素的顺序在此集合中包含的元素上返回一个迭代器。
  • boolean remove(Object o):如果存在指定元素,则从此集合中删除该元素。
  • int size():返回此集合中的元素数。

6. Java CopyOnWriteArraySet用例

在集大小通常保持较小的应用程序中使用CopyOnWriteArraySet,只读操作大大超过了可变操作,并且您需要防止遍历期间线程之间的干扰。

CopyOnWriteArraySet有助于最小化程序员控制的同步步骤,并将控制权移交给经过良好测试的内置 API。

7. Java CopyOnWriteArraySet性能

由于每次更新集时都会增加创建新支持数组的步骤,因此其性能比HashSet差。
读取操作没有性能开销,并且两个类执行相同的操作。

8. 总结

在本 Java 集合教程中,我们学习了使用CopyOnWriteArraySet类,其构造器,方法和用例。

我们了解了在 Java 中CopyOnWriteArraySet的内部工作原理,以及CopyOnWriteArraySetCopyOnWriteArrayList的工作原理。

我们通过 Java CopyOnWriteArraySet示例程序演示了快照迭代器的工作方式。

在评论中把您的问题交给我。

学习愉快!

参考:

CopyOnWriteArraySet Java 文档

如何在 Java 中对数组,列表,映射和集合进行排序

原文: https://howtodoinjava.com/java-sorting-guide/

学习使用 Java 对数组或集合进行排序。 我们将学习对包含原始类型和自定义对象的集合,列表和映射进行排序。 我们还将学习升序排序。

1. 排序数组

1.1 升序排序数组

Java 程序使用Arrays.sort()方法以升序对整数数组进行排序。

import java.util.Arrays;

public class JavaSortExample 
{    
    public static void main(String[] args) 
    {
        //Unsorted array
        Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 520, 1123, 366, 420 };

        //Sort the array
        Arrays.sort(numbers);

        //Print array to confirm
        System.out.println(Arrays.toString(numbers));
    }
}

程序输出。

[9, 11, 15, 18, 47, 55, 366, 420, 520, 1123]

1.2 降序排序数组

Java 提供Collections.reverseOrder()比较器,以在一行中反转默认的排序行为。 使用此命令以降序对数组进行排序。

//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 520, 1123, 366, 420 };

//Sort the array in reverse order
Arrays.sort(numbers, Collections.reverseOrder());

//Print array to confirm
System.out.println(Arrays.toString(numbers));

程序输出:

[1123, 520, 420, 366, 55, 47, 18, 15, 11, 9]

1.3 排序数组范围

Arrays.sort()方法是重载方法,它采用两个附加参数,即fromIndex(包括)和toIndex(排除)。 提供时,数组将在从位置fromIndex到位置toIndex的指定范围内排序。

给定示例对元素 9 到 18 之间的数组进行排序,即{9,55,47,18}将被排序,其余元素将不会被触摸。

//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 1123, 520, 366, 420 };

//Sort the array
Arrays.sort(numbers, 2, 6);

//Print array to confirm
System.out.println(Arrays.toString(numbers));

程序输出:

[15, 11, 9, 18, 47, 55, 1123, 520, 366, 420]

1.4 Java 8 并行排序

Java 8 引入了许多用于并行处理数据集和流的新 API。 一种此类 API 是Arrays.parallelSort()。 它将数组分为不同的子数组,并且每个子数组在不同线程中以Arrays.sort()进行排序。 最终,所有排序的子数组将合并为一个数组。

两个 API 的parallelSort()sort()的输出最终将相同。 这只是利用多线程的问题。

Arrays.parallelSort(numbers);

Arrays.parallelSort(numbers, 2, 6);

Arrays.parallelSort(numbers, Collections.reverseOrder());

2. 排序列表

可以使用Collections.sort() API 在 Java 中对列表进行排序。 它使用修改后的归并排序,并提供有保证的nlog(n)性能。

2.1 升序排序列表

//Unsorted list
Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 1123, 520, 366, 420 };
List<Integer> numbersList = Arrays.asList(numbers);

//Sort the list
Collections.sort(numbersList);

//Print list to confirm
System.out.println(numbersList);

程序输出:

[9, 11, 15, 18, 47, 55, 366, 420, 520, 1123]

2.2 降序排序列表

与数组类似,使用Collections.reverseOrder()可以反转默认的排序行为。

//Unsorted list
Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 1123, 520, 366, 420 };
List<Integer> numbersList = Arrays.asList(numbers);

//Sort the list
Collections.sort(numbersList, Collections.reverseOrder());

//Print list to confirm
System.out.println(numbersList);

程序输出:

[1123, 520, 420, 366, 55, 47, 18, 15, 11, 9]

3. 排序集合

不直接支持对 Java 中的集合进行排序。 要对集合进行排序,请按照下列步骤操作:

  1. 将集转换为列表。
  2. 使用Collections.sort() API 排序列表。
  3. 将列表转换回集合。
//Unsorted list
HashSet<Integer> numbersSet = new LinkedHashSet<>( 
        Arrays.asList(15, 11, 9, 55, 47, 18, 1123, 520, 366, 420) );

List<Integer> numbersList = new ArrayList<Integer>(numbersSet) ;        //set -> list

//Sort the list
Collections.sort(numbersList);

numbersSet = new LinkedHashSet<>(numbersList);          //list -> set

//Print set to confirm
System.out.println(numbersSet);

程序输出:

[9, 11, 15, 18, 47, 55, 366, 420, 520, 1123]

4. 排序映射

映射是键值对的集合。 因此,映射可以通过两种方式进行排序,即按键排序或按值排序。

4.1 按键排序映射

按键对映射进行排序的最好,最有效的方法是在TreeMap对象中添加所有映射项。 TreeMap按键对条目集进行排序。

HashMap<Integer, String> map = new HashMap<>();

map.put(50, "Alex");
map.put(20, "Charles");
map.put(60, "Brian");
map.put(70, "Edwin");
map.put(120, "George");
map.put(10, "David");

TreeMap<Integer, String> treeMap = new TreeMap<>(map);

System.out.println(treeMap);

程序输出:

{10=David, 20=Charles, 50=Alex, 60=Brian, 70=Edwin, 120=George}

4.2 按值排序映射

在 Java 8 中,Map.Entry类具有静态方法compareByValue(),可帮助您按值排序。 此方法返回一个比较器,该比较器以自然顺序比较值上的Map.Entry

HashMap<Integer, String> unSortedMap = new HashMap<>();

unSortedMap.put(50, "Alex");
unSortedMap.put(20, "Charles");
unSortedMap.put(60, "Brian");
unSortedMap.put(70, "Edwin");
unSortedMap.put(120, "George");
unSortedMap.put(10, "David");

//LinkedHashMap preserve the ordering of elements in which they are inserted
LinkedHashMap<Integer, String> sortedMap = new LinkedHashMap<>();

unSortedMap.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByValue())
    .forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));

System.out.println(sortedMap);

程序输出:

{50=Alex, 60=Brian, 20=Charles, 10=David, 70=Edwin, 120=George}

5. 对自定义对象进行排序

自定义对象是用户定义的类,它们保存域数据,例如 EmployeeDepartmentAccount等。

为了对自定义对象列表进行排序,我们有两种流行的方法,即ComparableComparator。 在给定的示例中,我们将对Employee类的实例的集合进行排序。


import java.time.LocalDate;

public class Employee implements Comparable<Employee> {

    private Long id;
    private String name;
    private LocalDate dob;

    public Employee(Long id, String name, LocalDate dob) {
        super();
        this.id = id;
        this.name = name;
        this.dob = dob;
    }

    @Override
    public int compareTo(Employee o) {
        return this.getId().compareTo(o.getId());
    }

    //Getters and Setters

    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + ", dob=" + dob + "]";
    }
}

5.1 Comparable

Comparable接口启用其实现的类的自然排序。 它使类与其实例具有可比性。

实现Comparable接口的类必须覆盖compareTo()方法,在该方法中,它可以指定同一类的两个实例之间的比较逻辑。

可以通过Collections.sort()Arrays.sort()自动对实现此接口的对象的列表(和数组)进行排序。 当将实现此接口的对象放入已排序的映射(作为键)或已排序的集合(作为元素)时,将自动对其进行排序。

强烈建议(尽管不是必需的)自然顺序应与equals一致。 实际上,所有实现Comparable的 Java 核心类都具有与equals一致的自然顺序。

ArrayList<Employee> list = new ArrayList<>();

list.add(new Employee(1l, "Alex", LocalDate.of(2018, Month.APRIL, 21)));
list.add(new Employee(4l, "Brian", LocalDate.of(2018, Month.APRIL, 22)));
list.add(new Employee(3l, "Piyush", LocalDate.of(2018, Month.APRIL, 25)));
list.add(new Employee(5l, "Charles", LocalDate.of(2018, Month.APRIL, 23)));
list.add(new Employee(2l, "Pawan", LocalDate.of(2018, Month.APRIL, 24)));

Collections.sort(list);

System.out.println(list);

程序输出:

[Employee [id=1, name=Alex, dob=2018-04-21], ]
Employee [id=2, name=Pawan, dob=2018-04-24], 
Employee [id=3, name=Piyush, dob=2018-04-25], 
Employee [id=4, name=Brian, dob=2018-04-22], 
Employee [id=5, name=Charles, dob=2018-04-23]]

5.2 Comparator

很多时候,我们会遇到这样的情况,即由于遗留代码问题,我们不会寻求自然排序或类文件而无法进行编辑。 在这种情况下,我们可以利用比较器接口。

比较器不需要修改该类的源代码。 我们可以在单独的类中创建比较逻辑,以实现Comparator接口并覆盖其compare()方法。 然后将此比较器以及自定义对象列表传递给sort()方法。

例如,在比较器下面,按员工列表的名称对其排序。

import java.util.Comparator;

public class NameSorter implements Comparator<Employee>
{
    @Override
    public int compare(Employee e1, Employee e2) 
    {
        return e1.getName().compareToIgnoreCase( e2.getName() );
    }
}

Java 程序使用Comparator接口实现对列表进行排序。 注意在sort()方法中使用NameSorter作为第二个参数。

ArrayList<Employee> list = new ArrayList<>();

list.add(new Employee(1l, "Alex", LocalDate.of(2018, Month.APRIL, 21)));
list.add(new Employee(4l, "Brian", LocalDate.of(2018, Month.APRIL, 22)));
list.add(new Employee(3l, "Piyush", LocalDate.of(2018, Month.APRIL, 25)));
list.add(new Employee(5l, "Charles", LocalDate.of(2018, Month.APRIL, 23)));
list.add(new Employee(2l, "Pawan", LocalDate.of(2018, Month.APRIL, 24)));

Collections.sort(list, new NameSorter());

System.out.println(list);

程序输出:

[Employee [id=1, name=Alex, dob=2018-04-21], 
Employee [id=4, name=Brian, dob=2018-04-22], 
Employee [id=5, name=Charles, dob=2018-04-23], 
Employee [id=2, name=Pawan, dob=2018-04-24], 
Employee [id=3, name=Piyush, dob=2018-04-25]]

5.3 用 Java 8 Lambda 排序

Java 8 Lambda 表达式有助于即时编写Comparator实现。 我们不需要创建单独的类来提供一次性比较逻辑。

Comparator<Employee> nameSorter = (a, b) -> a.getName().compareToIgnoreCase(b.getName());

Collections.sort(list, nameSorter);

5.4 分组排序

要使用链中的多个比较器对不同字段上的对象集合进行分组排序。 可以使用Comparator.comparing()Comparator.thenComparing()方法创建比较器的链接。

例如,我们按姓名对员工列表进行排序,然后再按其年龄进行排序。

ArrayList<Employee> list = new ArrayList<>();

list.add(new Employee(1l, "Alex", LocalDate.of(2018, Month.APRIL, 21)));
list.add(new Employee(4l, "Brian", LocalDate.of(2018, Month.APRIL, 01)));
list.add(new Employee(3l, "Alex", LocalDate.of(2018, Month.APRIL, 25)));
list.add(new Employee(5l, "Charles", LocalDate.of(2018, Month.APRIL, 23)));
list.add(new Employee(2l, "Alex", LocalDate.of(2018, Month.APRIL, 30)));

Collections.sort(list, Comparator
                        .comparing(Employee::getName)
                        .thenComparing(Employee::getDob));

System.out.println(list);

程序输出:

[Employee [id=1, name=Alex, dob=2018-04-21], 
Employee [id=3, name=Alex, dob=2018-04-25], 
Employee [id=2, name=Alex, dob=2018-04-30], 
Employee [id=4, name=Brian, dob=2018-04-01], 
Employee [id=5, name=Charles, dob=2018-04-23]]

6. 总结

在上面给出的示例中,我们学习了对数组,列表,映射和集合进行排序。 我们看到了用于初始化和使用Comparator接口(包括 lambda 表达式)的不同方法。

欢迎分享您对 Java 排序的看法。

学习愉快!

Java 面试的 40 个热门问答集

原文: https://howtodoinjava.com/interview-questions/useful-java-collection-interview-questions/

毫无疑问,java 集合是最重要的领域之一,无论您是初级还是高级,您都可以在任何位置对其进行测试。 范围如此之广,几乎不可能涵盖所有问题。 但是,根据我以前的面试,我尝试提出您必须知道的尽可能多的良好的 java 集合面试问题

我的目标是初学者和高级问题,所以如果您发现一些基本问题,请多多包涵,因为它们对某些初级开发人员可能有用。

Java collection interview questions

General questions

1) What is the Java Collections API? List down its advantages?
2) Explain Collections hierarchy?
3) Why Collection interface does not extend Cloneable and Serializable interface?
4) Why Map interface does not extend Collection interface?

List interface related

5) Why we use List interface? What are main classes implementing List interface?
6) How to convert an array of String to ArrayList?
7) How to reverse the list?

Set interface related

8) Why we use Set interface? What are main classes implementing Set interface?
9) How HashSet store elements?
10) Can a null element added to a TreeSet or HashSet?

Map interface related

11) Why we use Map interface? What are main classes implementing Map interface?
12) What are IdentityHashMap and WeakHashMap?
13) Explain ConcurrentHashMap? How it works?
14) How hashmap works?
15) How to design a good key for hashmap?
16) What are different Collection views provided by Map interface?
17) When to use HashMap or TreeMap?

Tell the difference questions

18) Difference between Set and List?
19) Difference between List and Map?
20) Difference between HashMap and HashTable?
21) Difference between Vector and ArrayList?
22) Difference between Iterator and Enumeration?
23) Difference between HashMap and HashSet?
24) Difference between Iterator and ListIterator?
25) Difference between TreeSet and SortedSet?
26) Difference between ArrayList and LinkedList?

More questions

27) How to make a collection read only?
28) How to make a collection thread safe?
29) Why there is not method like Iterator.add() to add elements to the collection?
30) What are different ways to iterate over a list?
31) What do you understand by iterator fail-fast property?
32) What is difference between fail-fast and fail-safe?
33) How to avoid ConcurrentModificationException while iterating a collection?
34) What is UnsupportedOperationException?
35) Which collection classes provide random access of it’s elements?
36) What is BlockingQueue?
37) What is Queue and Stack, list their differences?
38) What is Comparable and Comparator interface?
39) What are Collections and Arrays class?
40) Recommended resources

不要浪费时间,让我们深入研究 Java 集合的概念。

Java 集合面试一般问题

1)什么是 Java 集合框架? 列出其优势?

根据定义,集合是代表一组对象的对象。 像集合论一样,集合是一组元素。 很简单!

在 JDK 1.2 之前,JDK 具有一些工具类,例如VectorHashTable,但是没有集合框架的概念。 从 JDK 1.2 以后,JDK 感到需要对可重用数据结构提供一致的支持。 最后,集合框架主要由 Joshua Bloch 设计和开发,并在 JDK 1.2 中引入了。

Java 集合的最明显的优点可以列出为:

  • 随时可用的代码,减少了编程工作
  • 由于数据结构和算法的高性能实现而提高了性能
  • 通过建立公共语言来回传递集合,从而在不相关的 API 之间提供互操作性
  • 通过仅学习一些顶级接口和受支持的操作,易于学习的 API

2)解释集合的层次结构?

Java Collection Hierarchy

Java 集合层次结构

如上图所示,集合框架顶部有一个接口,即收集。 通过设置,列表和队列接口对其进行了扩展。 然后在这 3 个分支中还有其他类别的负载,我们将在以下问题中学习。

记住Collection接口的签名。 它会在很多问题上帮助您。

public interface Collection extends Iterable {
//method definitions
}

框架还包含Map接口,它是集合框架的一部分。 但它不会扩展Collection接口。 我们将在此问题库中的第四个问题中看到原因。

3)为什么Collection接口没有扩展CloneableSerializable接口?

好吧,最简单的答案是“不需要这样做”。 扩展接口仅表示您正在创建接口的子类型,换句话说,不希望使用更专门的行为和Collection接口来实现CloneableSerializable接口。

另一个原因是并非每个人都有理由拥有Cloneable集合,因为如果它具有非常大的数据,那么每个不必要的克隆操作都将消耗大量内存。 初学者可能在不知道后果的情况下使用它。

另一个原因是CloneableSerializable是非常专门的行为,因此仅在需要时才应实现。 例如,集合中的许多具体类都实现了这些接口。 因此,如果您想要此特性。 使用这些集合类,否则使用其替代类。

4)为什么Map接口没有扩展Collection接口?

这个面试问题的一个很好的答案是“因为它们不兼容”。 集合具有方法add(Object o)Map无法使用这种方法,因为它需要键值对。 还有其他原因,例如Map支持keySetvalueSet等。Collection类没有此类视图。

由于存在如此大的差异,因此在Map接口中未使用Collection接口,而是在单独的层次结构中构建。

Java 集合面试 – 列出接口问题

5)为什么要使用List接口? 什么是实现List接口的主要类?

Java 列表是元素的“有序”集合。 该排序是基于零的索引。 它不关心重复项。 除了在Collection接口中定义的方法外,它确实有自己的方法,它们在很大程度上也要根据元素的索引位置来操作集合。 这些方法可以分为搜索,获取,迭代和范围视图。 以上所有操作均支持索引位置。

实现List接口的主要类为:StackVectorArrayListLinkedList。 在 Java 文档中阅读有关它们的更多信息。

6)如何将String数组转换为arraylist

这更多是一个程序性问题,在初学者水平上可以看到。 目的是检查收集工具类中申请人的知识。 现在,让我们了解集合框架中有两个工具类,它们大多数在面试中看到,即CollectionsArrays

集合类提供了一些静态函数来对集合类型执行特定操作。 数组提供了要在数组类型上执行的工具函数。

//String array
String[] words = {"ace", "boom", "crew", "dog", "eon"};
//Use Arrays utility class
List wordList = Arrays.asList(words);
//Now you can iterate over the list

请注意,此函数并非特定于String类,它将返回数组属于任何类型的元素的List。 例如

//String array
Integer[] nums = {1,2,3,4};
//Use Arrays utility class
List numsList = Arrays.asList(nums);

7)如何反转列表?

这个问题就像上面的测试您对Collections工具类的了解。 使用它reverse()方法可以反转列表。

   Collections.reverse(list);

Java 集合面试 – 设置接口问题

8)为什么要使用Set接口? 什么是实现Set接口的主要类?

对集合论中的数学集合进行建模。 Set接口类似于List接口,但有一些区别。 首先,它是未排序的集合。 因此,添加或删除元素时不会保留任何顺序。 它提供的主要特性是“元素的唯一性”。 它不支持重复元素。

Set还对equalshashCode操作的行为增加了更强的约定,从而即使它们的实现类型不同,也可以有意义地比较Set实例。 如果两个Set实例包含相同的元素,则它们相等。

基于上述原因,它没有基于列表的元素索引的操作。 它只有由Collection接口继承的方法。

实现Set接口的主要类为: EnumSetHashSetLinkedHashSetTreeSet。 阅读更多有关 Java 文档的信息。

9)HashSet如何存储元素?

您必须知道HashMap存储具有一个条件的键值对,即键将是唯一的。 HashSet使用映射的此特性来确保元素的唯一性。 在HashSet类中,映射声明如下:

private transient HashMap<E,Object> map;

//This is added as value for each key
private static final Object PRESENT = new Object();

因此,将元素存储在HashSet中时,会将元素存储为map中的键,将PRESENT对象存储为值。 (请参见上面的声明)。

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

我强烈建议您阅读这篇文章: HashMap如何在 Java 中工作?这篇文章将帮助您轻松地回答所有与 HashMap 相关的问题。

10)是否可以将null元素添加到TreeSetHashSet中?

如您所见,上一个问题的add()方法中没有null检查。 并且HashMap还允许一个null键,因此在HashSet中允许一个null

TreeSet使用与HashSet相同的概念进行内部逻辑,但是使用NavigableMap来存储元素。

private transient NavigableMap<E,Object> m;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

NavigableMapSortedMap的子类型,不允许使用null键。 因此,本质上,TreeSet 也不支持空键。 如果您尝试在TreeSet中添加null元素,它将抛出NullPointerException

Java 集合面试 – Map接口问题

11)为什么要使用Map接口? 什么是实现Map接口的主要类?

Map接口是一种特殊的集合类型,它用于存储键值对。 因此,它不会扩展Collection接口。 该接口提供了在映射的各种视图上添加,删除,搜索或迭代的方法。

实现 Map 接口的主要类有:HashMapHashtableEnumMapIdentityHashMapLinkedHashMapProperties

12)什么是IdentityHashMapWeakHashMap

IdentityHashMapHashMap相似,不同之处在于在比较元素时使用引用相等性。 IdentityHashMap类不是一种广泛使用的Map实现。 尽管此类实现了Map接口,但它有意违反Map的一般协定,该协定要求在比较对象时必须使用equals()方法。IdentityHashMap设计为仅在少数情况下使用,其中需要引用相等语义。

WeakHashMapMap接口的实现,该接口仅存储对其键的弱引用。 当不再在WeakHashMap外部引用键值对时,仅存储弱引用将允许对键值对进行垃圾回收。 该类主要用于与equals方法使用==运算符测试对象标识的键对象一起使用。 一旦丢弃了这样的键,就永远无法重新创建它,因此以后不可能在WeakHashMap中对该键进行查找,并且会惊讶地发现它的条目已被删除。

13)解释ConcurrentHashMap吗? 怎么运行的?

来自 Java 文档:

支持检索的完全并发和用于更新的可调整预期并发的哈希表。 此类遵循与Hashtable相同的特性规范,并且包括与Hashtable的每个方法相对应的方法的版本。 但是,即使所有操作都是线程安全的,检索操作也不需要进行锁定,并且不支持以阻止所有访问的方式锁定整个表。 在依赖于其线程安全性但不依赖于其同步详细信息的程序中,此类可与Hashtable完全互操作。

阅读有关ConcurrentHashMap面试问题的更多信息。

14)HashMap如何工作?

最重要的问题在每个工作面试中最有可能出现。 您必须在这个主题上非常清楚。不仅因为它是最常被问到的问题,而且会打开您对集合 API 相关的其他问题的思路。

这个问题的答案非常大,您应该阅读我的文章:HashMap如何工作?现在,让我们记住HashMap在哈希原理上工作。 根据定义,映射是:“将键映射到值的对象”。 为了存储这种结构,使用了内部类Entry

static class Entry implements Map.Entry
{
final K key;
V value;
Entry next;
final int hash;
...//More code goes here
}

此处,键和值变量用于存储键值对。 整个条目对象存储在数组中。

/**
* The table, re-sized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;

数组的索引是根据Key对象的哈希码计算的。 阅读更多链接主题。

15)如何为哈希表设计一个好的键?

在回答HashMap如何工作后,通常会跟进另一个好问题。 好吧,最重要的约束是,您将来必须能够取回值对象。 否则,没有使用这种数据结构。 如果您了解hashmap的工作原理,将会发现它很大程度上取决于Key对象的hashCode()equals()方法。

因此,好的键对象必须一次又一次提供相同的hashCode(),无论它被获取了多少次。 同样,与equals()方法比较时,相同的键必须返回true,而不同的键必须返回false

因此,不变类被认为是HashMap的最佳候选者。

阅读更多: 如何为HashMap设计一个好的键?

16)Map接口提供哪些不同的Collection视图?

Map 接口提供了 3 个存储在其中的键值对的视图:

  • 键集视图
  • 值集视图
  • 条目集视图

可以使用迭代器浏览所有视图。

17)什么时候使用HashMapTreeMap

HashMap是众所周知的类,我们所有人都知道。 因此,我将离开这部分,说它用于存储键值对,并允许对这样的对集合执行许多操作。

TreeMapHashMap的特殊形式。 它维护HashMap类中缺少的键的顺序。 默认情况下,此排序为“自然排序”。 通过提供Comparator类的实例,可以覆盖默认顺序,该类的compare方法将用于维护键的顺序。

请注意,所有插入映射的键都必须实现Comparable接口(这是确定顺序的必要条件)。 此外,所有这些键必须相互可比较:k1.compareTo(k2)不得为映射中的任何键k1k2抛出ClassCastException。 如果用户尝试将键放入违反此约束的映射中(例如,用户尝试将字符串键放入其键为整数的映射中),则put(Object key, Object value)调用将引发ClassCastException

Java 集合面试 – 讲述差异问题

18)SetList之间的区别?

最明显的区别是:

  • Set是无序集合,其中List是基于零索引的有序集合。
  • 列表允许重复元素,但Set不允许重复。
  • List不会阻止插入空元素(随您喜欢),但是Set将只允许一个空元素。

19)ListMap之间的区别?

也许是最简单的问题。 列表是元素的集合,而map是键值对的集合。 实际上,有很多差异源自第一个语句。 它们具有单独的顶层接口,单独的一组通用方法,不同的受支持方法和不同的集合视图

我会花很多时间来回答这个问题,仅作为第一个区别就足够了。

20)HashMapHashTable之间的区别?

Java 中的HashMapHashtable之间有一些区别:

  • Hashtable是同步的,而HashMap不是同步的。
  • 哈希表不允许使用空键或空值。 HashMap允许一个空键和任意数量的空值。
  • HashMapHashtable之间的第三个重要区别是HashMap中的Iterator是快速失​​败的迭代器,而Hashtable的枚举器则不是。

21)VectorArrayList之间的区别?

让我们记下差异:

  • Vector的所有方法都是同步的。 但是,ArrayList的方法不同步。
  • Vector是在 JDK 的第一个版本中添加的旧类。 当在 Java 中引入集合框架时,ArrayList是 JDK 1.2 的一部分。
  • 默认情况下,Vector在内部调整大小时会将其数组的大小加倍。 但是,重新调整大小时,ArrayList的大小增加一半。

22)迭代器和枚举器之间的区别?

迭代器与枚举器在以下三个方面有所不同:

  • 迭代器允许调用方使用其remove()方法在迭代过程中从基础集合中删除元素。 使用枚举器时,不能从集合中添加/删除元素。
  • 枚举器在旧类(例如Vector/Stack等)中可用,而Iterator在所有现代集合类中可用。
  • 另一个小的区别是Iterator改进了方法名称,例如Enumeration.hasMoreElement())变为Iterator.hasNext()Enumeration.nextElement()变为Iterator.next()等。

23)HashMapHashSet之间的区别?

HashMap是键值对的集合,而HashSet是唯一元素的无序集合。 而已。 无需进一步描述。

24)IteratorListIterator之间的区别?

有三个区别:

  • 我们可以使用Iterator遍历SetList以及MapObject类型。 但是列表迭代器可用于遍历列表类型的对象,但不能遍历对象的集合类型。
  • 通过使用Iterator,我们只能从正向检索Collection对象中的元素,而List Iterator则允许您使用hasPrevious()previous()方法在任一方向上遍历。
  • ListIterator允许您使用add() remove()方法修改列表。 使用Iterator不能添加,只能删除元素。

25)TreeSetSortedSet之间的区别?

SortedSetTreeSet实现的接口。 就是这样!

26)ArrayListLinkedList之间的区别?

  • LinkedList将元素存储在双链列表数据结构中。ArrayList将元素存储在动态调整大小的数组中。
  • LinkedList允许进行固定时间的插入或删除,但只允许顺序访问元素。 换句话说,您可以向前或向后浏览列表,但是在中间抓取一个元素所花费的时间与列表的大小成正比。 另一方面,ArrayList允许随机访问,因此您可以在固定时间内抓取任何元素。 但是,从末端开始的任何地方添加或删除,都需要将后面的所有元素移开,以形成开口或填补空白。
  • LinkedListArrayList具有更多的内存开销,因为在ArrayList中,每个索引仅保存实际的对象(数据),但是在LinkedList的情况下,每个节点都保存下一个和上一个节点的数据以及地址。

更多面试面试问题

27)如何使集合只读?

使用以下方法:

  • Collections.unmodifiableList(list)
  • Collections.unmodifiableSet(set)
  • Collections.unmodifiableMap(map)

这些方法采用集合参数,并返回一个具有与原始集合中相同的元素的新的只读collection

28)如何使集合线程安全?

使用以下方法:

  • Collections.synchronizedList(list)
  • Collections.synchronizedSet(set)
  • Collections.synchronizedMap(map)

上面的方法将集合作为参数并返回相同类型的集合,这些类型是同步的并且是线程安全的。

29)为什么没有像Iterator.add()这样的方法将元素添加到集合中?

迭代器的唯一目的是通过集合进行枚举。 所有集合都包含 add()方法以实现您的目的。 Iterator的添加毫无意义,因为集合可能有序,也可能没有排序。 而且add()方法对于有序和无序集合不能具有相同的实现。

30)有哪些不同的方法可以遍历列表?

您可以使用以下方式遍历列表:

  • 迭代器循环
  • for循环
  • for循环(高级)
  • While循环

阅读更多: http://www.mkyong.com/java/how-do-loop-iterate-a-list-in-java/

31)通过迭代器快速失败属性您了解什么?

快速失败迭代器一旦意识到自迭代开始以来Collection的结构已更改,便会失败。 结构更改意味着在一个线程迭代该集合时,从集合中添加,删除或更新任何元素。

通过保留修改计数来实现快速失败行为,如果迭代线程实现了修改计数的更改,则会引发ConcurrentModificationException

32)快速失败和故障安全之间有什么区别?

您已经在上一个问题中理解了快速失败。 故障安全迭代器与快速失败相反。 如果您修改要在其上进行迭代的基础集合,它们将永远不会失败,因为它们在Collection的克隆而不是原始集合上起作用,这就是为什么将它们称为故障保护迭代器。

CopyOnWriteArrayList的迭代器是故障安全迭代器的示例,而且ConcurrentHashMap keySet编写的迭代器也是故障安全迭代器,并且永远不会抛出ConcurrentModificationException

33)如何在迭代集合时避免ConcurrentModificationException

您应该首先尝试查找故障安全的另一个替代迭代器。 例如,如果您正在使用List,则可以使用ListIterator。 如果它是旧式集合,则可以使用枚举。

如果上述选项不可行,则可以使用以下三种更改之一:

  • 如果使用的是 JDK1.5 或更高版本,则可以使用ConcurrentHashMapCopyOnWriteArrayList类。 这是推荐的方法。
  • 您可以将列表转换为数组,然后在数组上进行迭代。
  • 您可以通过将列表放在同步块中来在迭代时锁定列表。

请注意,最后两种方法会导致性能下降。

34)什么是UnsupportedOperationException

实际的集合类型不支持的被调用方法抛出的异常

例如,如果您使用Collections.unmodifiableList(list)创建一个只读列表列表,然后调用add()remove()方法,那将会发生什么。 它应该明确抛出UnsupportedOperationException

35)哪些集合类别可随机访问其元素?

ArrayListHashMapTreeMapHashtable类提供对其元素的随机访问。

36)什么是BlockingQueue

一个队列,它另外支持以下操作:在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用。

BlockingQueue方法有四种形式:一种抛出异常,第二种返回特殊值(根据操作的不同,返回nullfalse),第三种无限期地阻塞当前线程,直到操作成功为止;第四种在放弃之前尽在给定的最大时间限制内阻塞。

阅读文章中的阻塞队列示例用法: 如何使用阻塞队列?

37)什么是队列和栈,列出它们之间的差异?

设计用于在处理之前保存元素的集合。 除了基本的集合操作之外,队列还提供其他插入,提取和检查操作。
通常但不一定以 FIFO(先进先出)的方式对元素进行排序。

栈也是队列的一种形式,但有一个区别,那就是 LIFO(后进先出)。

无论使用哪种顺序,队列的开头都是该元素,可以通过调用remove()poll()将其删除。 另请注意,栈和向量都已同步。

用法:如果要按接收顺序处理传入流,请使用队列。适用于工作列表和处理请求。
如果只想从栈顶部推动并弹出,请使用栈。 适用于递归算法。

38)什么是ComparableComparator接口?

在 Java 中。 所有具有自动排序特性的集合都使用比较方法来确保元素的正确排序。 例如,使用排序的类为TreeSetTreeMap等。

为了对一个类的数据元素进行排序,需要实现ComparatorComparable接口。 这就是所有包装器类(例如IntegerDoubleString类)都实现Comparable接口的原因。

Comparable帮助保留默认的自然排序,而Comparator帮助以某些特殊的必需排序模式对元素进行排序。 比较器的实例,通常在支持集合时作为集合的构造器参数传递。

39)什么是CollectionsArrays类?

CollectionsArrays类是支持集合框架核心类的特殊工具类。 它们提供工具函数以获取只读/同步集合,以各种方式对集合进行排序等。

Arrays还帮助对象数组转换为集合对象。 Arrays还具有一些特性,有助于复制或处理部分数组对象。

40)推荐资源

好吧,这不是面试的问题.. 😃。 这只是为了好玩。 但是您应该真正阅读我的博客,以获取有关集合框架知识的更多帖子。

希望这些 Java 集合面试问题对您的下一次面试有所帮助。 此外,除了本文之外,我建议您阅读更多有关上述问题的信息。 更多的知识只会帮助您。

学习愉快!

Java IO 教程

Java 字符串类指南

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

Java 字符串表示不可变的字符序列,并且一旦创建就无法更改。 字符串的类型为java.lang.String类。 在此页面中,学习有关使用字符串字面值和构造器,字符串方法以及与字符串转换和格式设置有关的各种字符串示例创建字符串的信息。

1. 用 Java 创建字符串

用 Java 创建字符串的方法有两种。

  1. 字符串字面量

    字符串字面值最简单,建议使用在 Java 中创建字符串。 这样,只需将双引号中的字符分配给java.lang.String类型的变量。

    String blogName = "howtodoinjava.com";
    
    String welcomeMessage = "Hello World !!";
    
    

    字符串字面值存储在字符串池中,字符串池是由 JVM 创建的特殊内存区域。 一个String只能有一个实例。 具有相同字符序列的任何第二个String都将第一个字符串的引用存储在字符串池中。 它可以高效地使用字符串,并在运行时节省大量物理内存。

    String blogName1 = "howtodoinjava.com";
    String blogName2 = "howtodoinjava.com";
    String blogName3 = "howtodoinjava.com";
    String blogName4 = "howtodoinjava.com";
    String blogName5 = "howtodoinjava.com";
    
    

    在上面的示例中,我们创建了 5 个具有相同char序列的字符串字面值。 在 JVM 内部,字符串池中只有一个String实例。 所有其余的 4 个实例将共享为第一个字面值创建的字符串字面值的引用。

  2. 字符串对象

    有时,我们可能希望为内存中的每个单独的字符串创建单独的实例。 我们可以使用新的关键字为每个字符串值创建一个字符串对象。

    使用new关键字创建的字符串对象 – 存储在堆内存中。

    String blogName1 = new String("howtodoinjava.com");
    String blogName2 = new String("howtodoinjava.com");
    String blogName3 = new String("howtodoinjava.com");
    
    

    在上面的示例中,堆内存中将有 3 个具有相同值的String实例。

2. Java 字符串方法

  1. char charAt(int index) – 返回指定索引处的字符。 指定的索引值应介于0length() -1之间(包括两个端点)。 如果索引无效/超出范围,则抛出IndexOutOfBoundsException

    String blogName = "howtodoinjava.com";
    
    char c = blogName.charAt(5);	//'d'
    
    
  2. boolean equals(Object obj) – 将字符串与指定的字符串进行比较,如果两者均匹配,则返回true,否则返回false

    String blogName = "howtodoinjava.com";
    
    blogName.equals( "howtodoinjava.com" );		//true
    blogName.equals( "example.com" );			//false
    
  3. boolean equalsIgnoreCase(String str) – 与equals方法相同,但不区分大小写。

    String blogName = "howtodoinjava.com";
    
    blogName.equalsIgnoreCase( "howtodoinjava.com" );		//true
    blogName.equalsIgnoreCase( "HowToDoInJava.com" );		//true
    
  4. int compareTo(String string) – 根据字符串中每个字符的 Unicode 值按字典顺序比较两个字符串。 您可以考虑基于字典的比较。

    如果参数字符串等于此字符串,则返回值为 0;否则,返回值为 0。 如果此字符串在字典上小于字符串参数,则小于 0 的值; 如果该字符串在字典上大于字符串参数,则该值大于 0。

    String blogName = "howtodoinjava.com";
    
    blogName.compareTo( "HowToDoInJava.com" );		//32
    blogName.compareTo( "example.com" );			//3
    
    
  5. int compareToIgnoreCase(String str) – 与CompareTo方法相同,但是在比较期间忽略大小写。

    String blogName = "howtodoinjava.com";
    
    blogName.compareToIgnoreCase( "HowToDoInJava.com" );		//0
    blogName.compareToIgnoreCase( "example.com" );				//3
    
    
  6. boolean startsWith(String prefix, int offset) – 从指定的偏移量索引开始,检查String是否具有指定的前缀。

    String blogName = "howtodoinjava.com";
    
    blogName.startsWith( "d", 5 );		//true
    blogName.startsWith( "e", 5 );		//false
    
    
  7. boolean startsWith(String prefix) – 测试字符串是否已指定prefix,如果是,则返回true,否则返回false。 在此重载方法中,偏移索引值为 0。

    String blogName = "howtodoinjava.com";
    
    blogName.startsWith( "h" );		//true
    blogName.startsWith( "e" );		//false
    
    
  8. boolean endsWith(String subfix) – 查字符串是否以指定的后缀结尾。

    String blogName = "howtodoinjava.com";
    
    blogName.endsWith( "com" );			//true
    blogName.endsWith( "java" );		//false
    
    
  9. int hashCode() – 返回字符串的哈希码。

    String blogName = "howtodoinjava.com";
    
    blogName.hashCode();			//1894145264
    
    
  10. int indexOf(int ch) – 返回指定字符参数在字符串中首次出现的索引。

    String blogName = "howtodoinjava.com";
    
    blogName.indexOf( 'o' );			//1
    
    
  11. int indexOf(int ch, int fromIndex)indexOf(char ch)方法的重载版本,但是它开始从指定的fromIndex中搜索字符串。

    String blogName = "howtodoinjava.com";
    
    blogName.indexOf( 'o', 5 );		//6
    
    
  12. int indexOf(String str) – 返回指定子字符串str首次出现的索引。

    String blogName = "howtodoinjava.com";
    
    blogName.indexOf( "java" );			//9
    
    
  13. int indexOf(String str, int fromIndex) - indexOf(String str)方法的重载版本,但是它开始从指定的fromIndex中搜索字符串。

    String blogName = "howtodoinjava.com";
    
    blogName.indexOf( "java" , 5);			//9
    
    
  14. int lastIndexOf(int ch) – 返回字符串中字符'ch'的最后一次出现。

    String blogName = "howtodoinjava.com";
    
    blogName.lastIndexOf( 'o' );			//15
    
    
  15. int lastIndexOf(int ch,int fromIndex)lastIndexOf(int ch)方法的重载版本。 从fromIndex开始向后搜索。

    String blogName = "howtodoinjava.com";
    
    blogName.lastIndexOf( 'o', 5 );			//4
    
    
  16. int lastIndexOf(String str) – 返回最后一次出现的字符串str的索引。 与lastIndexOf(int ch)相似。

    String blogName = "howtodoinjava.com";
    
    blogName.lastIndexOf( "java" );			//9
    
    
  17. int lastIndexOf(String str, int fromIndex)lastIndexOf(String str)方法的重载版本。 从fromIndex开始向后搜索。

    String blogName = "howtodoinjava.com";
    
    blogName.lastIndexOf( "java", 6 );			//9
    
    
  18. String substring(int beginIndex) – 返回字符串的子字符串。 子字符串以指定索引处的字符开头。

    String blogName = "howtodoinjava.com";
    
    blogName.substring( 7 );		//injava.com
    
    
  19. String substring(int beginIndex, int endIndex) – 返回子字符串。 子字符串以beginIndex处的字符开头,以endIndex处的字符结尾。

    String blogName = "howtodoinjava.com";
    
    blogName.substring( 7, 9 );		//in
    
    
  20. String concat(String str) – 在字符串的末尾连接指定的字符串参数。

    String blogName = "howtodoinjava.com";
    
    blogName.concat( " Hello Visitor !!" );		//howtodoinjava.com Hello Visitor !!
    
    
  21. String replace(char oldChar, char newChar) – 使用newChar参数更改所有出现的oldChar之后,返回新的更新字符串。

    String blogName = "howtodoinjava.com";
    
    blogName.replace( 'o', 'O' );		//hOwtOdOinjava.cOm
    
    
  22. public String replace(CharSequence target, CharSequence replacement) – 使用replacement参数更改所有出现的target后,返回新的更新字符串。

    String blogName = "howtodoinjava.com";
    
    blogName.replace( "com", "COM" );		//howtodoinjava.COM
    
    
  23. String replaceFirst(String regex, String replacement) – 用指定的替换字符串替换与给定正则表达式参数匹配的子字符串的第一个匹配项。

    String blogName = "howtodoinjava.com";
    
    blogName.replaceFirst("how", "HOW");		//HOWtodoinjava.com
    
    
  24. String.replaceAll(String regex, String replacement) – 用替换字符串替换所有出现的与正则表达式参数匹配的子字符串。

  25. String[] split(String regex, int limit) – 拆分字符串并返回与给定正则表达式匹配的子字符串数组。 limit是数组中元素的最大数量。

    String blogName = "howtodoinjava.com";
    
    blogName.split("o", 3);		//[h, wt, doinjava.com]
    
    
  26. String[] split(String regex) – 先前方法的重载,没有任何阈值限制。

  27. boolean contains(CharSequence s) – 检查字符串是否包含指定的char值序列。 如果是,则返回true,否则返回false。 如果参数为null,则抛出 NullPointerException

    String blogName = "howtodoinjava.com";
    
    blogName.contains( "java" );		//true
    blogName.contains( "python" );		//false
    
    
  28. public String toUpperCase(Locale locale) – 使用指定语言环境定义的规则将字符串转换为大写字符串。

    String blogName = "howtodoinjava.com";
    
    blogName.toUpperCase( Locale.getDefault() );		//HOWTODOINJAVA.COM
    
    
  29. String.toUpperCase() – 先前的toUpperCase()方法的重载版本,带有默认语言环境。

  30. String toLowerCase(Locale locale) – 使用给定语言环境定义的规则将字符串转换为小写字符串。

  31. String.toLowerCase() – 具有默认语言环境的先前方法的重载版本。

  32. String.intern() – 在内存池中搜索指定的字符串,如果找到,则返回它的引用。 否则,此方法将在字符串池中分配创建字符串字面值并返回引用。

  33. boolean isEmpty() – 如果给定的字符串长度为 0,则返回true,否则返回false

    String blogName = "howtodoinjava.com";
    
    blogName.isEmpty();		//false
    "".isEmpty();			//true
    
    
  34. static String join() - 使用指定的分隔符连接给定的字符串,并返回连接的 Java String字面值。

    String.join("-", "how","to", "do", "in", "java")		//how-to-do-in-java
    
    
  35. static String format() – 返回格式化的字符串。

  36. String.trim() - 从 Java 字符串中删除开头和结尾的空格。

  37. char[] toCharArray() – 将字符串转换为字符数组。

  38. static String copyValueOf(char[] data) – 返回一个字符串,其中包含指定字符数组的字符。

    char[] chars = new char[] {'h','o','w'};
    
    String.copyValueOf(chars);		//how
    
    
  39. byte[] getBytes(String charsetName) – 使用指定的字符集编码将字符串转换为字节序列。

  40. byte [] getBytes() – 先前方法的重载版本。 它使用默认字符集编码。

  41. int length() – 返回字符串的长度。

  42. boolean match(String regex) – 验证字符串是否与指定的正则表达式参数匹配。

  43. int codePointAt(int index) – 与charAt()方法相似。 它返回指定索引的 Unicode 代码点值,而不是字符本身。

  44. static String copyValueOf(char[] data, int offset, int count) – 先前方法的重载版本,带有两个额外的参数 – 子数组的初始偏移量和子数组的长度。 它根据额外的参数从数组中选择字符,然后创建字符串。

  45. getChars(int srcBegin, int srcEnd, char [] dest, int destBegin) – 将src数组的字符复制到dest数组。 仅将指定范围复制(从srcBeginsrcEnd)到dest子数组(从destBegin开始)。

  46. static String valueOf() – 返回所传递参数的字符串表示形式,例如intlongfloatdoublecharchar数组。

  47. boolean contentEquals(StringBuffer sb) – 将字符串与指定的字符串缓冲区进行比较。

  48. boolean regionMatches(int srcoffset, String dest, int destoffset, int len) – 将输入的子字符串与指定字符串的子字符串进行比较。

  49. boolean regionMatches(boolean ignoreCase, int srcoffset, String dest, int destoffset, int len)regionMatches方法的另一个变体,带有额外的布尔值参数,用于指定比较是区分大小写还是不区分大小写。

3. 字符串转换示例

  1. 将 Java 字符串转换为int
  2. 在 Java 中将int转换为字符串
  3. 将字符串转换为长
  4. 在 Java 中将Long转换为字符串
  5. 将字符串转换为日期
  6. 将日期转换为字符串
  7. 将字符串转换为String[]示例
  8. Java 8 – 连接字符串数组 – 将数组转换为字符串
  9. 将字符串转换为InputStream示例
  10. InputStream转换为字符串示例
  11. Java 拆分 CSV 字符串 – 将字符串转换为列表示例
  12. 将 CSV 连接到字符串
  13. 将 HTML 转义为字符串示例
  14. 转义 HTML – 将字符串编码为 HTML 示例
  15. 将字节数组转换为字符串
  16. StackTrace到字符串的转换
  17. 将浮点数转换为字符串 – 格式转换为 N 个小数点

4. 有用的字符串示例

  1. 使用递归反转 Java 中的字符串
  2. 删除单词之间的多余空格
  3. 仅删除字符串的前导空格
  4. 仅删除字符串的结尾空格
  5. 如何在 Java 中反转字符串
  6. 用 Java 反转字符串中的单词
  7. Java 中使用递归的反向字符串
  8. 如何在字符串中查找重复的单词
  9. 如何在字符串中查找重复的字符
  10. Java 按字母顺序对字符串字符进行排序
  11. 将字符串转换为标题大小写
  12. 分割字符串的 4 种方法
  13. 左,右或居中对齐字符串
  14. 读取文件为字符串
  15. Java 8 StringJoiner示例
  16. 用空格或零左移字符串
  17. 用空格或零右填充字符串
  18. 获取字符串的前 4 个字符
  19. 获取字符串的后 4 个字符
  20. 将字符串格式设置为(123)456-7890模式

5. 常见问题

  1. 始终使用length()而不是equals()来检查空字符串
  2. 为什么字符串是不可变的
  3. Java 字符串面试问题

6. 参考

String Java 文档

Java IO 教程和示例

原文: https://howtodoinjava.com/java-io-tutorial/

Java IO 是类和接口的集合,您可以使用它们来通过 Java 应用程序执行几乎所有可能的 IO 操作。 该 Java IO 教程列出了各种场景下 IO 操作的示例,以供快速参考。

Java IO 基础

目录操作

文件操作

临时文件操作

从 X 转换为 Y

杂项用途

祝您学习愉快!

参考https://docs.oracle.com/javase/tutorial/essential/io/

Java I/O 如何在较低级别上内部工作?

原文: https://howtodoinjava.com/java/io/how-java-io-works-internally-at-lower-level/

这篇博客文章主要讨论与 I/O 相关的事物在较低级别的工作方式。 这篇文章供那些想知道如何在机器级别映射 Java I/O 操作的读者使用; 以及您的应用程序在运行时,硬件在所有时间内的所有工作。 我假设您熟悉基本的 IO 操作,例如读取文件,通过 Java I/O API 写入文件; 因为那超出了这篇文章的范围。

Table of Contents

Buffer Handling and Kernel vs User Space
Virtual Memory
Memory Paging
File/Block Oriented I/O
File Locking
Stream Oriented I/O

缓冲区处理和内核与用户空间

缓冲区以及如何处理缓冲区是所有 I/O 的基础。 术语“输入/输出”仅意味着将数据移入和移出缓冲区。 始终要记住这一点。 通常,进程通过请求操作系统从缓冲区中清空数据(写入操作)或向缓冲区中填充数据(读取操作)来执行 I/O。 以上是 I/O 概念的全部总结。 操作系统内部执行这些传输的机制可能非常复杂,但是从概念上讲,它非常简单,我们将在本文中讨论其中的一小部分。

data buffering at os level

上图显示了块数据如何从外部源(例如硬盘)移动到正在运行的进程(例如 RAM)内部的存储区的简化“逻辑”图。 首先,该进程通过进行read()系统调用来请求填充其缓冲区。 此调用导致内核向磁盘控制器硬件发出命令以从磁盘获取数据。 磁盘控制器通过 DMA 将数据直接写入内核内存缓冲区,而无需主 CPU 的进一步协助。 磁盘控制器完成缓冲区填充后,内核将数据从内核空间中的临时缓冲区复制到进程指定的缓冲区中; 当它请求read()操作时。

需要注意的一件事是内核尝试缓存和/或预取数据,因此进程请求的数据可能已经在内核空间中可用。 如果是这样,则将过程所请求的数据复制出来。 如果数据不可用,则该过程将暂停,而内核将数据带入内存。

虚拟内存

您必须已经多次听说虚拟内存。 让我对它进行一些思考。

所有现代操作系统都使用虚拟内存。 虚拟内存意味着使用人工或虚拟地址代替物理(硬件 RAM)内存地址。 虚拟内存具有两个重要优点:

1)多个虚拟地址可以引用相同的物理内存位置。
2)虚拟内存空间可能大于可用的实际硬件内存。

在上一节中,从内核空间复制到最终用户缓冲区必须看起来像是额外的工作。 为什么不告诉磁盘控制器将其直接发送到用户空间中的缓冲区? 嗯,这是通过使用虚拟内存来完成的,它的优势是上面的 1。

通过将内核空间地址映射到与用户空间中虚拟地址相同的物理地址,DMA 硬件(只能访问物理内存地址)可以填充一个缓冲区,该缓冲区同时对内核和用户空间进程可见。

virtual memory

这消除了内核空间和用户空间之间的副本,但是需要内核空间和用户缓冲区共享相同的页面对齐方式。 缓冲区还必须是磁盘控制器使用的块大小的倍数(通常为 512 字节磁盘扇区)。 操作系统将其内存地址空间划分为页面,页面是固定大小的字节组。 这些内存页始终是磁盘块大小的倍数,通常为 2 的幂(这简化了寻址)。 典型的内存页面大小为 1,024、2,048 和 4,096 字节。 虚拟和物理内存页面大小始终相同。

内存分页

为了支持虚拟内存的第二个优势(可寻址空间大于物理内存),有必要进行虚拟内存分页(通常称为交换)。 这是一种方案,通过该方案可以将虚拟内存空间的页面持久保存到外部磁盘存储中,从而在物理内存中为其他虚拟页面腾出空间。 本质上,物理内存充当页面调度区域的缓存,这是磁盘上的空间,当磁盘空间被迫退出物理内存时,该空间用于存储内存页面的内容。

将内存页面大小调整为磁盘块大小的倍数,可使内核向磁盘控制器硬件发出直接命令,以将内存页面写入磁盘或在需要时重新加载它们。 事实证明,所有磁盘 I/O 都是在页面级别完成的。 在现代的分页操作系统中,这是数据在磁盘和物理内存之间移动的唯一方式。

现代 CPU 包含一个称为内存管理单元(MMU)的子系统。 该设备在逻辑上位于 CPU 和物理内存之间。 它包含将虚拟地址转换为物理内存地址所需的映射信息。 当 CPU 引用一个内存位置时,MMU 会确定该位置所在的页面(通常通过移动或屏蔽地址值的位)并将该虚拟页面号转换为物理页面号(此操作在硬件中完成,并且非常复杂)和快速)。

面向文件/块的 I/O

文件 I/O 始终在文件系统的上下文中发生。 文件系统与磁盘完全不同。 磁盘将数据存储在扇区中,通常每个扇区 512 字节。 它们是对文件语义一无所知的硬件设备。 它们只是提供了许多可以存储数据的插槽。 在这方面,磁盘的扇区类似于内存页面; 它们都具有统一的大小,并且可以作为一个大数组寻址。

另一方面,文件系统是更高级别的抽象。 文件系统是整理和解释存储在磁盘(或某些其他随机访问的,面向块的设备)上的数据的特殊方法。 您编写的代码几乎总是与文件系统交互,而不是直接与磁盘交互。 它是定义文件名,路径,文件,文件属性等的抽象的文件系统。

文件系统(在硬盘中)组织一系列大小一致的数据块。 一些块存储元信息,例如空闲块,目录,索引的映射等。其他块包含实际文件数据。 有关单个文件的元信息描述了哪些块包含文件数据,数据结束于何处,数据的最后更新时间等。当用户进程发出读取文件数据的请求时,文件系统实现将确定数据在磁盘上的确切位置。 然后,它将采取措施将这些磁盘扇区放入内存。

文件系统还具有页面的概念,其大小可以与基本内存页面相同,也可以是其倍数。 典型的文件系统页面大小为 2,048 至 8,192 字节,并且始终是基本内存页面大小的倍数。

分页文件系统如何执行 I/O 归结为以下逻辑步骤:

  1. 确定请求跨越哪个文件系统页面(磁盘扇区组)。 磁盘上的文件内容和/或元数据可能分布在多个文件系统页面上,并且这些页面可能是不连续的。
  2. 在内核空间中分配足够的内存页面以容纳已标识的文件系统页面。
  3. 在这些内存页面和磁盘上的文件系统页面之间建立映射。
  4. 为每个内存页面生成页面错误。
  5. 虚拟内存系统会捕获页面错误,并调度 pageins 通过从磁盘读取其内容来验证这些页面。
  6. 一旦 pageins 完成,文件系统将分解原始数据以提取请求的文件内容或属性信息。

请注意,此文件系统数据将像其他内存页面一样被缓存。 在后续的 I/O 请求中,部分或全部文件数据可能仍存在于物理内存中,并且可以重新使用而无需从磁盘重新读取。

文件锁定

文件锁定是一种方案,通过该方案,一个进程可以阻止其他进程访问文件或限制其他进程访问该文件的方式。 虽然“文件锁定”一词的含义是锁定整个文件(并且通常这样做),但锁定通常可以在更细粒度的级别上进行。 文件区域通常是锁定的,粒度低至字节级别。 锁与特定文件相关联,该锁从该文件中的特定字节位置开始,并在特定字节范围内运行。 这很重要,因为它允许许多进程协调对文件特定区域的访问,而不会妨碍其他进程在文件中的其他位置工作。

文件锁有两种形式:共享独占。 多个共享锁可能对同一文件区域同时生效。 另一方面,排他锁要求对请求的区域没有其他锁生效。

流 I/O

并非所有的 I/O 都是面向块的。 还有流 I/O,它是在管道上建模的。 I/O 流的字节必须顺序访问。 TTY(控制台)设备,打印机端口和网络连接是流的常见示例。

流通常(但不一定)比块设备慢,并且通常是间歇输入的来源。 大多数操作系统允许将流置于非阻塞模式,这允许进程检查输入是否在流上可用,而不会卡住当前是否可用。 这种特性允许进程在输入到达时处理输入,但在输入流空闲时执行其他函数。

超越非阻塞模式的一步是进行准备就绪选择的能力。 这类似于非阻塞模式(并且通常在非阻塞模式的基础上构建),但将检查流是否已准备就绪的检查分担给操作系统。 可以告诉操作系统观看流的集合,并向这些流中的哪些流准备就绪的过程返回指示。 此特性允许进程利用操作系统返回的就绪信息,使用通用代码和单个线程多路复用许多活动流。 这在网络服务器中被广泛使用以处理大量的网络连接。 准备就绪选择对于大批量缩放至关重要。

这就是这个非常复杂的主题,包含大量技术词汇

给我评论您的想法和疑问。

祝您学习愉快!

Java 标准 IO 与 Java NIO

原文: https://howtodoinjava.com/java/io/difference-between-standard-io-and-nio/

JDK 1.4 引入了新输入/输出(NIO)库。 从原始 I/O 遗留的地方接起,NIO 用标准 Java 代码提供了高速,面向块的 I/O。 通过定义用于保存数据的类,并通过按块处理该数据,NIO 可以利用低级优化的优势,而无需使用本机代码,原始 I/O 包就无法实现。

在本教程中,我将专注于确定最明显的区别,在决定在下一个项目中使用哪个区别之前,您必须知道这些区别。

回顾旧的 IO 机制

I/O(输入/输出) 是指计算机与世界其他地方之间的接口,或单个程序与计算机其余部分之间的接口。 单个程序通常会为他们完成大部分工作。 在 Java 编程中,直到最近才使用流隐喻来执行 I/O。 所有 I/O 都被视为单个字节通过一个称为流的对象一次移动。 流 I/O 用于联系外界。 它还在内部使用,用于将对象转换为字节,然后再转换为对象。

NIO 介绍

NIO 的创建是为了使 Java 程序员无需编写自定义本机代码即可实现高速 I/O。 NIO 将最耗时的 I/O 活动(即填充和清空缓冲区)移回到操作系统中,从而大大提高了速度。

如果以上介绍让您感到口渴,请不要担心,在我们前进的过程中您是否会感到更好。 让我们从发现差异开始。

识别 IO 和 NIO 之间的差异

1)IO 流与 NIO 块

原始 I/O 库(可在java.io.*中找到)和 NIO 之间最重要的区别与数据的打包和传输方式有关。 如前所述,原始 I/O 处理流中的数据,而 NIO 处理块中的数据。

面向流的 I/O 系统一次处理一个或多个字节的数据。 输入流产生一个字节的数据,而输出流消耗一个字节的数据。 为流数据创建过滤器非常容易。 将几个过滤器链接在一起也是相对简单的,这样每个过滤器都能发挥自己的作用,相当于一个单一的复杂处理机制。 重要的是字节不会在任何地方缓存。 此外,您不能在流中的数据中来回移动。 如果需要来回移动从流中读取的数据,则必须先将其缓存在缓冲区中。

面向块的 I/O 系统按块处理数据。 每个操作一步就产生或消耗一个数据块。 通过块处理数据可能比通过(流式传输)字节处理数据快得多。 您可以根据需要在缓冲区中来回移动。 这使您在处理过程中更具灵活性。 但是,您还需要检查缓冲区是否包含您需要的所有数据,以便对其进行完全处理。 并且,您需要确保在将更多数据读入缓冲区时,不要覆盖尚未处理的缓冲区中的数据。 但是面向块的 I/O 缺少面向流的 I/O 的一些优雅和简单性。

阅读更多: 3 种使用 Java NIO 读取文件的方法

2)同步与异步 IO

Java IO 的各种流正在阻塞或同步。 这意味着,当线程调用read()write()时,该线程将被阻塞,直到有一些数据要读取或数据被完全写入为止。 在此期间,线程将处于阻塞状态。 这被认为是在现代语言中引入多线程的一个很好的坚实理由。

在异步 IO 中,线程可以请求将某些数据写入通道,但不等待将其完全写入。 然后线程可以继续运行,同时执行其他操作。 通常,这些线程将空闲时间花费在未阻塞 IO 调用上的时间,通常同时在其他通道上执行 IO。 也就是说,单个线程现在可以管理输入和输出的多个通道。

同步程序通常不得不诉诸于轮询或创建许多线程来处理大量连接。 使用异步 I/O,您可以在任意数量的通道上侦听 I/O 事件,而无需轮询且无需额外的线程。

异步 I/O 中的中心对象称为选择器。 选择器是您对各种 I/O 事件感兴趣的地方,它是告诉您何时发生这些事件的对象。 因此,我们需要做的第一件事是创建一个选择器:

Selector selector = Selector.open();

稍后,我们将在各种通道对象上调用 register()方法,以使我们对那些对象内发生的 I/O 事件感兴趣。 register()的第一个参数始终是选择器。

阅读更多:如何在 Java NIO 中定义路径

3)IO 与 NIO API

猜测使用 NIO 时 API 调用与使用 IO 时看起来不同的猜测是没有根据的。 在 NIO 中,而不是从例如如果是InputStream,则必须首先将数据读入缓冲区,然后再对其进行处理。

使用标准 IO 的示例代码

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();
            }
        }
    }
}

使用 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();
    }
}

总结

NIO 允许您仅使用一个(或更少)线程来管理多个通道,但是代价是解析数据可能比使用标准 IO 从阻塞流中读取数据时要复杂得多。

如果您需要同时管理数千个打开的连接(每个连接仅发送少量数据),例如聊天服务器,则在 NIO 中实现该服务器可能是一个优势。 同样,如果您需要保持与其他计算机的大量开放连接,例如在 P2P 网络中,使用单个线程来管理所有出站连接可能是一个优势。

如果您只有很少的连接且带宽很高,那么一次发送大量数据,则应该选择标准 IO 服务器实现。

祝您学习愉快!

如何在 Java 中复制目录

原文: https://howtodoinjava.com/java/io/how-to-copy-directories-in-java/

要将目录及其包含的所有子文件夹和文件从一个位置复制到另一个位置,请使用以下代码,该代码使用递归遍历目录结构,然后使用Files.copy()函数复制文件。

目录复制示例源代码

在此示例中,我将c:\temp下的所有子目录和文件复制到新位置c:\tempNew

package com.howtodoinjava.examples.io;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

public class DirectoryCopyExample 
{
	public static void main(String[] args) throws IOException 
	{
		//Source directory which you want to copy to new location
		File sourceFolder = new File("c:\\temp");

		//Target directory where files should be copied
		File destinationFolder = new File("c:\\tempNew");

		//Call Copy function
		copyFolder(sourceFolder, destinationFolder);
	}
	/**
	 * This function recursively copy all the sub folder and files from sourceFolder to destinationFolder
	 * */
	private static void copyFolder(File sourceFolder, File destinationFolder) throws IOException
	{
		//Check if sourceFolder is a directory or file
		//If sourceFolder is file; then copy the file directly to new location
		if (sourceFolder.isDirectory()) 
		{
			//Verify if destinationFolder is already present; If not then create it
			if (!destinationFolder.exists()) 
			{
				destinationFolder.mkdir();
				System.out.println("Directory created :: " + destinationFolder);
			}

			//Get all files from source directory
			String files[] = sourceFolder.list();

			//Iterate over all files and copy them to destinationFolder one by one
			for (String file : files) 
			{
				File srcFile = new File(sourceFolder, file);
				File destFile = new File(destinationFolder, file);

				//Recursive function call
				copyFolder(srcFile, destFile);
			}
		}
		else 
		{
			//Copy the file content from one place to another 
			Files.copy(sourceFolder.toPath(), destinationFolder.toPath(), StandardCopyOption.REPLACE_EXISTING);
			System.out.println("File copied :: " + destinationFolder);
		}
	}
}

Output:

Directory created :: c:\tempNew
File copied :: c:\tempNew\testcopied.txt
File copied :: c:\tempNew\testoriginal.txt
File copied :: c:\tempNew\testOut.txt

使用Files.copy()方法,可以复制目录。 但是,目录内的文件不会被复制,因此即使原始目录包含文件,新目录也为空。

另外,如果目标文件存在,则复制将失败,除非指定了REPLACE_EXISTING选项。

验证是否正确复制了文件。 随意修改代码并以自己喜欢的方式使用它。

祝您学习愉快!

用 Java 递归删除目录

原文: https://howtodoinjava.com/java/io/delete-a-directory-with-all-files-inside-it/

如果您正在使用 Java swing 应用程序/桌面应用程序,则有时可能需要从文件系统中删除其中包含所有内部目录和文件的目录。 您可以在下面的代码示例中直接删除目录以及目录中所有包含文件的目录。

它分两步递归工作:

  1. 首先删除目录中的所有文件。 然后
  2. 它删除目录本身

使用 java.nio.file.Files(Java 7)

下面的代码示例使用 Files.walkFileTree() 方法和 SimpleFileVisitor 执行删除操作。

package example;

import java.io.IOException;
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.attribute.BasicFileAttributes;

public class DeleteDirectoryNIO 
{
	public static void main(String[] args) 
	{
		Path dir = Paths.get("c:/temp/innerDir");
		try 
		{
			Files.walkFileTree(dir, new SimpleFileVisitor<Path>() 
			{
			      @Override
			      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
			    		  throws IOException 
			      {
			          System.out.println("Deleting file: " + file);
			          Files.delete(file);
			          return FileVisitResult.CONTINUE;
			      }

			      @Override
			      public FileVisitResult postVisitDirectory(Path dir,
			              IOException exc) throws IOException 
			      {
			          System.out.println("Deleting dir: " + dir);
			          if (exc == null) {
			              Files.delete(dir);
			              return FileVisitResult.CONTINUE;
			          } else {
			              throw exc;
			          }
				   }

				});
		} 
		catch (IOException e) 
		{
		  e.printStackTrace();
		}
	}
}

Output:

Deleting file: c:\temp\innerDir\data.txt
Deleting file: c:\temp\innerDir\logging.log
Deleting file: c:\temp\innerDir\test.png
Deleting file: c:\temp\innerDir\test.txt
Deleting dir: c:\temp\innerDir

使用 java.nio.file.Files(Java 8)

从 Java 8 开始,您可以将 Java NIO 操作与 Java 流结合使用,上述方法变得非常简洁。

public class DeleteDirectoryNIOWithStream 
{
	public static void main(String[] args) 
	{
		Path dir = Paths.get("c:/temp/innerDir");

		Files.walk(dir)
      		.sorted(Comparator.reverseOrder())
      		.map(Path::toFile)
      		.forEach(File::delete);
	}
}

在这里,Files.walk()返回表示目录本身之前目录(即文件)内容的路径流。 此后,它将Path映射到File并删除每个文件。 现在,您可以使用delete()方法删除文件本身。

使用 commons-io

Apache commons IO 模块具有类FileUtils。 可以使用deleteDirectory(file)方法删除目录以及其中的所有子目录和文件。

import org.apache.commons.io.FileUtils;
import java.io.File;

public class DeleteDirectoryCommonsIO 
{
	public static void main(String[] args) 
	{
		File file = FileUtils.getFile("c:/temp/innerDir");

		FileUtils.deleteDirectory( file );
	}
}

每当您要删除目录和其中的所有文件时,请使用以上方便的代码示例。

学习愉快!

Java – 创建新文件

原文: https://howtodoinjava.com/java/io/how-to-create-a-new-file-in-java/

用 Java 创建一个新文件是非常容易的任务。 我们大多数人都知道这一点。 让我们列出三种为初学者创建新文件的方法。 如果您知道,请在评论部分添加更多方法。 我将在帖子中包含它们。

阅读更多:创建只读文件

1. 使用java.io.File类创建文件

使用File.createNewFile()方法创建新文件。 此方法返回布尔值:

  • true如果文件创建成功。
  • false如果文件已经存在或由于某种原因操作失败。

请注意,此方法将仅创建文件,但不会向其中写入任何内容

File file = new File("c://temp//testFile1.txt");

//Create the file
if (file.createNewFile())
{
    System.out.println("File is created!");
} else {
    System.out.println("File already exists.");
}

//Write Content
FileWriter writer = new FileWriter(file);
writer.write("Test data");
writer.close();

2)使用java.io.FileOutputStream类创建文件

FileOutputStream.write()方法自动创建一个新文件并向其中写入内容

String data = "Test data";

FileOutputStream out = new FileOutputStream("c://temp//testFile2.txt");

out.write(data.getBytes());
out.close();

3)使用java.nio.file.Files创建文件 – Java NIO

Files.write()创建文件的最佳方法,如果您尚未使用它,则应该是将来的首选方法。

此方法将文本行写入文件中。 每行都是一个char序列,并按顺序写入文件,每行由平台的行分隔符终止。

语法

/**
* @param   path 	- the path to the file
* @param   lines 	- an object to iterate over the char sequences
* @param   cs 		- the charset to use for encoding
* @param   options 	- options specifying how the file is opened
*
* @return  the path of new created file
*/

public static Path write(Path path,
     Iterable<? extends CharSequence> lines,
     Charset cs,
     OpenOption... options) throws IOException

Files.write()示例

String data = "Test data";
Files.write(Paths.get("c://temp//testFile3.txt"), data.getBytes());

//or

List<String> lines = Arrays.asList("1st line", "2nd line");

Files.write(Paths.get("file6.txt"), 
				lines, 
				StandardCharsets.UTF_8,
				StandardOpenOption.CREATE, 
				StandardOpenOption.APPEND);

使用 Java 创建新文件的所有示例

package com.howtodoinjava.examples.io;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;

public class CreateNewFile 
{
    public static void main(String[] args) throws IOException 
    {
        createFileUsingFileClass();
        createFileUsingFileOutputStreamClass();
        createFileIn_NIO();
    }

    private static void createFileUsingFileClass() throws IOException 
    { 
          File file = new File("c://temp//testFile1.txt");

          //Create the file
          if (file.createNewFile()){
            System.out.println("File is created!");
          }else{
            System.out.println("File already exists.");
          }

          //Write Content
          FileWriter writer = new FileWriter(file);
          writer.write("Test data");
          writer.close();
    }

    private static void createFileUsingFileOutputStreamClass() throws IOException 
    {
        String data = "Test data";
        FileOutputStream out = new FileOutputStream("c://temp//testFile2.txt");
        out.write(data.getBytes());
        out.close();
    }

    private static void createFileIn_NIO()  throws IOException 
    {
        String data = "Test data";
        Files.write(Paths.get("c://temp//testFile3.txt"), data.getBytes());

        //or

        List<String> lines = Arrays.asList("1st line", "2nd line");

        Files.write(Paths.get("file6.txt"), 
                    lines, 
                    StandardCharsets.UTF_8,
                    StandardOpenOption.CREATE, 
                    StandardOpenOption.APPEND);
    }
}

学习愉快!

Java – 写入文件

原文: https://howtodoinjava.com/java/io/java-write-to-file/

在企业应用程序上工作时,有时需要用 Java 写文件。 在文件系统中编写报告。 尽管有多种方法可以做到这一点,但让我们快速浏览其中的几种方法以在需要时快速参考。

Table of Contents

Write File using BufferedWritter
Write File using FileWriter/PrintWriter
Write File using FileOutputStream
Write File using DataOutputStream
Write File using FileChannel
Write File using Java 7 Path

使用BufferedWritter写入文件

BufferedWritter将内容写入文件的最简单方法。 它将文本写入字符输出流,缓冲字符,以便有效地写入单个字符,数组和字符串。

除非需要快速输出,否则建议将BufferedWriter包裹在其write()操作可能成本很高的Writer周围,例如FileWriterOutputStreamWriter

由于它在写入之前进行缓冲,因此它减少了的 IO 操作,因此提高了性能。

public static void usingBufferedWritter() throws IOException 
{
	String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";

	BufferedWriter writer = new BufferedWriter(new FileWriter("c:/temp/samplefile1.txt"));
	writer.write(fileContent);
	writer.close();
}

使用FileWriter/PrintWriter写入文件

FileWriter最干净的文件写入方式。 语法是自我解释的,易于阅读和理解。 FileWriter直接写入文件(性能较差),并且仅在写入次数较少时才应使用。

public static void usingFileWriter() throws IOException 
{
	String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";

	FileWriter fileWriter = new FileWriter("c:/temp/samplefile2.txt");
    fileWriter.write(fileContent);
    fileWriter.close();
}

使用PrintWriter将格式化的文本写入文件。 此类实现PrintStream中提供的所有打印方法,因此您可以使用System.out.println()语句使用的所有格式。

public static void usingPrintWriter() throws IOException 
{
	String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";

	FileWriter fileWriter = new FileWriter("c:/temp/samplefile3.txt");
    PrintWriter printWriter = new PrintWriter(fileWriter);
    printWriter.print(fileContent);
    printWriter.printf("Blog name is %s", "howtodoinjava.com");
    printWriter.close();
}

使用FileOutputStream写入文件

使用FileOutputStream将二进制数据写入文件FileOutputStream用于写入原始字节流,例如图像数据。 要编写字符流,请考虑使用FileWriter

public static void usingFileOutputStream() throws IOException 
{
	String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";

	FileOutputStream outputStream = new FileOutputStream("c:/temp/samplefile4.txt");
    byte[] strToBytes = fileContent.getBytes();
    outputStream.write(strToBytes);

    outputStream.close();
}

使用DataOutputStream写入文件

DataOutputStream允许应用程序以可移植的方式将原始 Java 数据类型写入输出流。 然后,应用程序可以使用数据输入流来读回数据。

public static void usingDataOutputStream() throws IOException 
{
	String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";

	FileOutputStream outputStream = new FileOutputStream("c:/temp/samplefile5.txt");
	DataOutputStream dataOutStream = new DataOutputStream(new BufferedOutputStream(outputStream));
	dataOutStream.writeUTF(fileContent);

	dataOutStream.close();
}

使用FileChannel写入文件

FileChannel可用于读取,写入,映射和操作文件。 如果要处理大文件,FileChannel可能比标准 IO 更快。

文件通道可以安全地供多个并发线程使用。

public static void usingFileChannel() throws IOException 
{
	String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";

	RandomAccessFile stream = new RandomAccessFile("c:/temp/samplefile6.txt", "rw");
    FileChannel channel = stream.getChannel();
    byte[] strBytes = fileContent.getBytes();
    ByteBuffer buffer = ByteBuffer.allocate(strBytes.length);
    buffer.put(strBytes);
    buffer.flip();
    channel.write(buffer);
    stream.close();
    channel.close();
}

使用 Java 7 路径写入文件

Java 7 引入了Files工具类,我们可以使用其write函数来编写文件,而在内部,它是使用OutputStream将字节数组写入文件中的。

public static void usingPath() throws IOException 
{
	String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";

	Path path = Paths.get("c:/temp/samplefile7.txt");

    Files.write(path, fileContent.getBytes());
}

总结

  1. 如果我们尝试写入不存在的文件,则将首先创建该文件,并且不会引发任何异常(使用Path方法除外)。
  2. 写入文件内容以释放所有资源后,请始终关闭输出流。 它还将有助于避免损坏文件。
  3. 用途PrintWriter用于写入格式化的文本。
  4. FileOutputStream写入二进制数据。
  5. 使用DataOutputStream写入原始数据类型。
  6. 使用FileChannel写入较大的文件。

学习愉快!

Java – 附加到文件

原文: https://howtodoinjava.com/java/io/java-append-to-file/

使用BufferedWritterPrintWriterFileOutputStreamFiles类学习将内容追加到 Java 中的文件。 在所有示例中,在打开要写入的文件时,您都传递了第二个参数true,该参数表示以附加模式打开了文件。

Table of Contents

Append to File using BufferedWritter
Append to File using PrintWriter
Append to File using FileOutputStream
Append to File using Files class

使用BufferedWritter附加到文件

BufferedWritter会在写入之前进行缓冲,因此会减少的 IO 操作的数量,从而提高性能。

要将内容附加到现有文件,请通过将第二个参数传递为true,以附加模式打开文件编写器。

public static void usingBufferedWritter() throws IOException 
{
	String textToAppend = "Happy Learning !!";

	BufferedWriter writer = new BufferedWriter(
								new FileWriter("c:/temp/samplefile.txt", true)	//Set true for append mode
							);	
	writer.newLine();	//Add new line
	writer.write(textToAppend);
	writer.close();
}

使用PrintWriter附加到文件

使用PrintWriter将格式化的文本写入文件。 此类实现PrintStream中提供的所有打印方法,因此您可以使用System.out.println()语句使用的所有格式。

要将内容追加到现有文件中,请通过将第二个参数传递为true来以追加模式打开文件编写器。

public static void usingPrintWriter() throws IOException 
{
	String textToAppend = "Happy Learning !!";

	FileWriter fileWriter = new FileWriter("c:/temp/samplefile.txt", true);	//Set true for append mode
    PrintWriter printWriter = new PrintWriter(fileWriter);
    printWriter.println(textToAppend);	//New line
    printWriter.close();
}

使用FileOutputStream附加到文件

使用FileOutputStream将二进制数据写入文件FileOutputStream用于写入原始字节流,例如图像数据。 要编写字符流,请考虑使用FileWriter

要将内容追加到现有文件中,请通过将第二个参数作为true传递,以追加模式打开FileOutputStream

public static void usingFileOutputStream() throws IOException 
{
	String textToAppend = "\r\n Happy Learning !!";	//new line in content

	FileOutputStream outputStream = new FileOutputStream("c:/temp/samplefile.txt", true);
    byte[] strToBytes = textToAppend.getBytes();
    outputStream.write(strToBytes);

    outputStream.close();
}

使用Files类附加到文件

使用Files类,我们可以使用write函数编写文件,在内部使用OutputStream将字节数组写入文件。

要将内容追加到现有文件中,请在写入内容时使用StandardOpenOption.APPEND

public static void usingPath() throws IOException 
{
	String textToAppend = "\r\n Happy Learning !!";	//new line in content

	Path path = Paths.get("c:/temp/samplefile.txt");

    Files.write(path, textToAppend.getBytes(), StandardOpenOption.APPEND);	//Append mode
}

学习愉快!

Java 创建只读文件示例

原文: https://howtodoinjava.com/java/io/how-to-make-a-file-read-only-in-java/

要使文件在 Java 中只读,可以使用以下方法之一。 使用Runtime.getRuntime().exec()的第三种方法是特定于平台的,因为您会将命令作为参数传递给它。 其余两种方法在大多数情况下都可以正常工作。

如果您想更改 Linux/unix 特定的文件属性,例如 使用chmod 775,则 java 不提供任何方法。 您应使用Runtime.getRuntime().exec()来使用第三种方法。

1)使用java.io.File.setReadOnly()方法

private static void readOnlyFileUsingNativeCommand() throws IOException 
{
	File readOnlyFile = new File("c:/temp/testReadOnly.txt");

	//Mark it read only
	readOnlyFile.setReadOnly();

	if (readOnlyFile.exists()) 
	{
		readOnlyFile.delete();
	}
	readOnlyFile.createNewFile();
}

2)使用java.io.File.setWritable(false)方法

private static void readOnlyFileUsingSetWritable() throws IOException 
{
	File readOnlyFile = new File("c:/temp/testReadOnly.txt");
	if (readOnlyFile.exists()) 
	{
		readOnlyFile.delete();
	}
	readOnlyFile.createNewFile();

	//Mark it read only
	readOnlyFile.setWritable(false);
}

3)执行本机命令(取决于平台)

private static void readOnlyFileUsingSetReadOnly() throws IOException 
{
	File readOnlyFile = new File("c:/temp/testReadOnly.txt");
	//Mark it read only in windows
	Runtime.getRuntime().exec("attrib " + "" + readOnlyFile.getAbsolutePath() + "" + " +R");
}

祝您学习愉快!

Java 将文件读取为字符串(已针对 Java 8 更新)

原文: https://howtodoinjava.com/java/io/java-read-file-to-string-examples/

学习将文件读取为 Java 中的字符串。 给定的示例使用Files.readAllBytes()Files.lines()(以逐行读取)和FileReaderBufferedReader将文本文件读取为字符串

将文件读取为字符串的 3 个 Java 示例

用于将文件读入字符串的所有 3 个示例的源代码。

//Java 8 - Read file line by line - Files.lines(Path path, Charset cs)

private static String readLineByLineJava8(String filePath) 
{
	StringBuilder contentBuilder = new StringBuilder();
	try (Stream<String> stream = Files.lines( Paths.get(filePath), StandardCharsets.UTF_8)) 
	{
		stream.forEach(s -> contentBuilder.append(s).append("\n"));
	}
	catch (IOException e) 
	{
		e.printStackTrace();
	}
	return contentBuilder.toString();
}

//Files.readAllBytes(Path path) - Java 7 and above

private static String readAllBytesJava7(String filePath) 
{
	String content = "";
	try 
	{
		content = new String ( Files.readAllBytes( Paths.get(filePath) ) );
	} 
	catch (IOException e) 
	{
		e.printStackTrace();
	}
	return content;
}

//Using BufferedReader and FileReader - Below Java 7

private static String usingBufferedReader(String filePath) 
{
	StringBuilder contentBuilder = new StringBuilder();
	try (BufferedReader br = new BufferedReader(new FileReader(filePath))) 
	{

		String sCurrentLine;
		while ((sCurrentLine = br.readLine()) != null) 
		{
			contentBuilder.append(sCurrentLine).append("\n");
		}
	} 
	catch (IOException e) 
	{
		e.printStackTrace();
	}
	return contentBuilder.toString();
}

我已将文件data.txt放置在位置c:/temp。 我将在所有 3 个示例中阅读此文件。

Welcome to howtodoinjava.com blog. 
Learn to grow.

Files.lines() – 在 Java 8 中将文件读取为字符串

lines()方法从文件中读取所有行以流,并随着被消耗而懒惰地填充。 使用指定的字符集将文件中的字节解码为字符。

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class ReadFileToString 
{
	public static void main(String[] args) 
	{
		String filePath = "c:/temp/data.txt";

		System.out.println( readLineByLineJava8( filePath ) );
	}

	//Read file content into string with - Files.lines(Path path, Charset cs)

	private static String readLineByLineJava8(String filePath) 
	{
		StringBuilder contentBuilder = new StringBuilder();

		try (Stream<String> stream = Files.lines( Paths.get(filePath), StandardCharsets.UTF_8)) 
		{
			stream.forEach(s -> contentBuilder.append(s).append("\n"));
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}

		return contentBuilder.toString();
	}
}

输出:

Welcome to howtodoinjava.com blog. 
Learn to grow.

将整个文件读到字符串 – Files.readAllBytes()(≥ Java 7)

readAllBytes()方法从文件读取所有字节。 该方法可确保在读取所有字节或引发 I/O 错误或其他运行时异常时关闭文件。

读取所有字节后,我们将这些字节传递给String类构造器以创建一个字符串。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class ReadFileToString 
{
	public static void main(String[] args) 
	{
		String filePath = "c:/temp/data.txt";

		System.out.println( readAllBytesJava7( filePath ) );
	}

	//Read file content into string with - Files.readAllBytes(Path path)

	private static String readAllBytesJava7(String filePath) 
	{
		String content = "";

		try 
		{
			content = new String ( Files.readAllBytes( Paths.get(filePath) ) );
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}

		return content;
	}
}

输出:

Welcome to howtodoinjava.com blog. 
Learn to grow.

逐行读取文件 – BufferedReader(< Java 7)

如果仍不使用 Java 7 或更高版本,则使用BufferedReader类。 readLine()方法一次读取一行文件,然后返回内容。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ReadFileToString 
{
	public static void main(String[] args) 
	{
		String filePath = "c:/temp/data.txt";

		System.out.println( usingBufferedReader( filePath ) );
	}

	//Read file content into string with - using BufferedReader and FileReader
	//You can use this if you are still not using Java 8

	private static String usingBufferedReader(String filePath) 
	{
		StringBuilder contentBuilder = new StringBuilder();
		try (BufferedReader br = new BufferedReader(new FileReader(filePath))) 
		{

			String sCurrentLine;
			while ((sCurrentLine = br.readLine()) != null) 
			{
				contentBuilder.append(sCurrentLine).append("\n");
			}
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
		return contentBuilder.toString();
	}
}

输出:

Welcome to howtodoinjava.com blog. 
Learn to grow.

使用上述任何一种方法,都可以使用 Java 将文件读入字符串

学习愉快!

Java 创建类 – 如何创建对象?

原文: https://howtodoinjava.com/java/basics/how-to-create-a-class-in-java/

面向对象范式中,类是编程的基本单位。 在本教程中,学习编写 Java 类如何在 Java 中创建对象

1. 类与对象

在 Java 中,对象是具有数据结构的容器,它们具有状态和行为。 对象代表系统或应用程序中的参与者。 例如,在 HR 应用程序中,参与者是雇员,经理,部门或报告。 对象是类的实例。

是描述其对象的状态和行为的模板。

2. 如何声明一个类

在 Java 中声明类的一般语法为:

<<modifiers>> class <<class name>> {

        // fields and members of the class
}

  • 类声明可能具有零个或多个修饰符
  • 关键字class用于声明一个类。
  • <<class name>>是该类的用户定义名称,应为有效标识符。
  • 每个类都有一个主体,该主体在一对大括号({…})中指定。
  • 类的主体包含其不同的组件,例如字段,方法等。

例如,

public class Main 
{
	// Empty body for now; Write we own
}

2.1 类的类型

在 Java 中,可以有两种类型的类。

  1. 抽象类 - 这些类是abstract。 这些是不完整的类。 这意味着您无法创建此类的实例。 您只能扩展这些类以完成其规范。
  2. 非抽象类 – 这些类定义其完整状态和行为。 他们是完整的类。 您可以创建此类的对象。

3. Java 类的渐进性

在 Java 中,类用作模板来创建对象。 Java 中的类可能包含五个主要组件。 即:

  1. 字段
  2. 方法
  3. 构造器
  4. 静态初始化器
  5. 实例初始化器

字段方法也被称为类成员构造器和两个初始化器都用于在类初始化期间使用,即使用类模板创建对象。

构造器用于创建类的对象。 我们必须为类至少具有构造器(如果我们明确声明,则 JVM 为我们注入默认构造器)。

初始化器用于初始化类的字段。 我们可以有零个或多个静态或实例类型的初始化器。

3.1 字段

类的字段表示该类对象的属性(也称为状态属性)。 这些字段在类的主体内声明。

在类中声明字段的一般语法为:

public class Main 

        // A field declaration
        <<modifiers>> <<data type>> <<field name>> = <<initial value>>;
}

假设Human类的每个对象都有两个属性:名称和性别。 Human类应该包括两个字段的声明:一个代表姓名,另一个代表性别。

public class Human {

        String name;
        String gender;
}

Human类在此处声明两个字段:名称和性别。 这两个字段均为String类型。Human类的每个实例(或对象)都将具有这两个字段的副本。

3.2 方法

Java 方法是语句的集合,这些语句被组合在一起以执行操作。 方法通常用于修改类字段的状态。 通过调用其他对象中的方法,方法也可以用于委派任务。

在 Java 中,方法可以:

  • 接受零个或多个参数
  • 返回void或单个值
  • 重载 – 意味着我们可以定义多个具有相同名称但语法不同的方法
  • 被覆盖 – 意味着我们可以在父类和子类中使用相同的语法定义方法
public class Human {

        String name;
        String gender;

        public void eat() {

        	System.out.println("I am eating");
        }
}

3.3 构造器

构造器是一个命名的代码块,用于在创建对象后立即初始化该类的对象。 构造器声明的一般语法为:

<<Modifiers>> <<Constructor Name>>(<<parameters list>>) throws <<Exceptions list>> {

        // Body of constructor goes here
}

  • 构造器可以将其访问修饰符设为publicprivateprotected或包级别(无修饰符)。
  • 构造器名称与类的简单名称相同。
  • 构造器名称后跟一对左括号和右括号,其中可能包含参数。
  • 可选地,在右括号后面可以加上关键字throws,而关键字throws之后是逗号分隔的异常列表。
  • 与方法不同,构造器没有返回类型。
  • 我们甚至不能将void指定为构造器的返回类型。 如果有任何返回类型,则为方法。
  • 请记住,如果构造的名称与类的简单名称相同,则它可以是方法或构造器。 如果指定返回类型,则为方法。 如果未指定返回类型,则为构造器。

3.4 实例初始化块

我们看到,构造器用于初始化类的实例。 实例初始化块(也称为实例初始化器)也用于初始化类的对象。

实例初始值设定项只是类体内的代码块,但不包括任何方法或构造器。 实例初始化器没有名称。 它的代码只是放在开括号和闭括号内。

请注意,实例初始化器在实例上下文中执行,并且关键字this在实例初始化器中可用。

public class Main 
{
	{
		//instance initializer block
	}
}

  • 一个类可以有多个实例初始化器。
  • 对于我们创建的每个对象,所有初始化器都将按文本顺序自动执行。
  • 所有实例初始化器的代码都在任何构造器之前执行。
  • 实例初始化器不能具有return语句
  • 除非所有已声明的构造器在其throws子句中列出那些受检的异常,否则它不会抛出受检的异常。
public class Main {

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

    //constructor
    public Main()       
    {
        System.out.println("Inside constructor");
    }

    public static void main(String[] args) {
        new Main();
    }
}

Output:

Inside instance initializer
Inside constructor

3.5 静态初始化块

  • 静态初始化块也称为静态初始化器
  • 它类似于实例初始化块,但用于初始化类。
  • 实例初始化器对每个对象执行一次,而静态初始化器仅在将类定义加载到 JVM 中时对一个类执行一次。
  • 为了区别于实例初始化器,我们需要在其声明的开头使用static关键字。
  • 我们可以在一个类中有多个静态初始化器。
  • 所有静态初始值设定项均按其出现的文本顺序执行,并在任何实例初始值设定项之前执行。

静态初始化器无法引发受检异常。 它不能有一个return语句。

public class Main {

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

    //constructor
    public Main()       
    {
        System.out.println("Inside constructor");
    }

    public static void main(String[] args) {
        new Main();
    }
}

Output:

Inside static initializer
Inside constructor

4. 如何创建对象?

在 Java 中,要通过类创建对象,请使用new关键字及其构造器之一。

<<Class>> <<variable>> = new <<Call to Class Constructor>>;

//e.g.

Human human = new Human();

请记住,当我们不向类添加构造器时,Java 编译器会为我们添加一个构造器。 Java 编译器添加的构造器称为默认构造器。 默认构造器不接受任何参数。 类的构造器的名称与类的名称相同。

new运算符之后,将调用正在创建其实例的类的构造器。new运算符通过在堆中分配内存来创建类的实例。

5. null引用类型

Java 具有称为null类型的特殊引用类型。 它没有名字。 因此,我们不能定义空引用类型的变量。 空引用类型只有 Java 定义的一个值,即空字面值。 它只是null

空引用类型是与任何其他引用类型兼容的分配。 也就是说,我们可以将空值分配给任何引用类型的变量。 实际上,存储在引用类型变量中的空值表示该引用变量没有引用任何对象。

// Assign null value to john

Human john = null;  	// john is not referring to any object
john = new Human(); 	// Now, john is referring to a valid Human object

请注意,nullnull类型的字面值。 我们无法为基本类型变量分配null,因此 Java 编译器不允许我们将基本值与null值进行比较。

这是有关在 Java 中创建类的非常基础的教程的全部内容。

学习愉快!

了解更多信息: Oracle Java Doc

Java 将文件读取到byte[]数组

原文: https://howtodoinjava.com/java/io/how-to-read-file-content-into-byte-array-in-java/

在 Java 中,在各种情况下都可能需要将文件读取到字节数组。 该字节数组可用于通过网络以及其他程序 API 进行进一步处理。 让我们了解几种在 Java 中将文件中的数据读取到字节数组中的方法。

1. 使用 NIO 将文件读取到byte[]数组(Java 7 及更高版本)

如果使用的是 Java 7,则Files.readAllBytes()是最好的方法。否则,您将需要下面列出的其他 3 个选项中的任何方法。

public class ContentToByteArrayExample
{
   public static void main(String[] args)
   {
      Path path = Paths.get("C:/temp/test.txt");
      byte[] data = Files.readAllBytes(path);
   }
}

在使用 Java 8 的情况下,也可以使用此方法。

阅读更多:使用 Java NIO 读取文件的 3 种方法

2. 使用FileInputStream将文件读取到byte[]数组(直到 Java 6)

我正在使用java.io.FileInputStream读取文件的内容,然后将读取的内容转换为字节。

import java.io.File;
import java.io.FileInputStream;

public class ContentToByteArrayExample
{
   public static void main(String[] args)
   {

      File file = new File("C:/temp/test.txt");

      readContentIntoByteArray(file);
   }

   private static byte[] readContentIntoByteArray(File file)
   {
      FileInputStream fileInputStream = null;
      byte[] bFile = new byte[(int) file.length()];
      try
      {
         //convert file into array of bytes
         fileInputStream = new FileInputStream(file);
         fileInputStream.read(bFile);
         fileInputStream.close();
         for (int i = 0; i < bFile.length; i++)
         {
            System.out.print((char) bFile[i]);
         }
      }
      catch (Exception e)
      {
         e.printStackTrace();
      }
      return bFile;
   }
}

3. 使用FileUtilsIOUtils将文件读取到字节数组

将数据读入字节数组的另一种好方法是在 Apache Commons IO 库中(如果允许在项目中使用它)。

//Using FileUtils.readFileToByteArray()
byte[] org.apache.commons.io.FileUtils.readFileToByteArray(File file)

//Using IOUtils.toByteArray
byte[] org.apache.commons.io.IOUtils.toByteArray(InputStream input) 

4. 使用 Google Guava 将文件读入字节数组

将数据读入字节数组的另一种好方法是 Google Guava 库(如果允许在您的项目中使用它)。

//Using Files.toByteArray()
byte[] com.google.common.io.Files.toByteArray(File file)

//Using ByteStreams.toByteArray
byte[] com.google.common.io.ByteStreams.toByteArray(InputStream is)

学习愉快!

Java – 逐行读取文件 – LineNumberReader

原文: https://howtodoinjava.com/java/io/java-io-linenumberreader-example-to-read-file-line-by-line/

如果您只想按特定要求逐行读取文件的内容,则LineNumberReader可能是您的理想选择。 LineNumberReaderBufferedReader类的子类,可让您跟踪当前正在处理的行。 行号从 0 开始。每当LineNumberReader在包装的Reader返回的字符中遇到行终止符时,行号都会递增。

您可以通过调用getLineNumber()方法来获取当前行号。 LineNumberReader还使您可以通过调用setLineNumber()方法将当前行号重置为另一个号码(为什么要这样做?)。

如果您要分析可能包含错误的文本文件,则行号可能会很方便。 向用户报告错误时,如果您的错误消息中包含遇到错误的行号,则更容易纠正错误。

让我们建立一个简单的示例来展示LineNumberReader的特性。

在下面的示例中,这是我将使用LineNumberReader读取的文件内容。

firstName=Lokesh
lastName=Gupta
blog=howtodoinjava
technology=java

LineNumberReader示例

这是使用LineNumberReader逐行读取以上文件内容的代码。

import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;

public class LineNumberReaderExample
{
   public static void main(String[] args)
   {
      readFromFile("app.properties");
   }

   private static void readFromFile(String filename)
   {
      LineNumberReader lineNumberReader = null;
      try
      {
         //Construct the LineNumberReader object
         lineNumberReader = new LineNumberReader(new FileReader(filename));

         //Print initial line number 
         System.out.println("Line " + lineNumberReader.getLineNumber());

         //Setting initial line number
         lineNumberReader.setLineNumber(5);

         //Get current line number
         System.out.println("Line " + lineNumberReader.getLineNumber());

         //Read all lines now; Every read increase the line number by 1
         String line = null;
         while ((line = lineNumberReader.readLine()) != null)
         {
            System.out.println("Line " + lineNumberReader.getLineNumber() + ": " + line);
         }
      } 
      catch (Exception ex)
      {
         ex.printStackTrace();
      } finally
      {
         //Close the LineNumberReader
         try {
            if (lineNumberReader != null){
               lineNumberReader.close();
            }
         } catch (IOException ex){
            ex.printStackTrace();
         }
      }
   }
}

Output:

Line 0
Line 5
Line 6: firstName=Lokesh
Line 7: lastName=Gupta
Line 8: blog=howtodoinjava
Line 9: technology=java

这就是将这个有用的课程向你介绍的知识的全部。

祝您学习愉快!

Java BufferedReader示例

原文: https://howtodoinjava.com/java/io/how-to-read-file-in-java-bufferedreader-example/

如果要使用BufferedReader在 Java 中读取文件,请使用以下代码作为模板,并按自己的方式重复使用它。

BufferedReader从字符输入流中读取文本,缓冲字符,以便有效读取字符,数组和行。

1)使用不带try-with-resourcesBufferedReader(在 Java 7 之前)

package com.howtodoinjava.examples.io;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderExample 
{
	public static void main(String[] args)
	{
		BufferedReader bufferedReader = null;

		try 
		{

			String currLine;

			bufferedReader = new BufferedReader(new FileReader("C:\\temp\\testOut.txt"));

			while ((currLine = bufferedReader.readLine()) != null)
			{
				System.out.println(currLine);
			}

		}
		catch (IOException e) 
		{
			e.printStackTrace();
		} 
		finally 
		{
			try 
			{
				if (bufferedReader != null)
					bufferedReader.close();
			} 
			catch (IOException ex) 
			{
				ex.printStackTrace();
			}
		}
	}
}

2)将BufferedReadertry-with-resources一起使用(Java 7 及更高版本)

package com.howtodoinjava.examples.io;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderExample 
{
	public static void main(String[] args)
	{
		try (BufferedReader bufferedReader = new BufferedReader(new FileReader("C:\\temp\\testOut.txt")))
		{

			String currLine;

			while ((currLine = bufferedReader.readLine()) != null) 
			{
				System.out.println(currLine);
			}

		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

祝您学习愉快!

Java – BufferedWriter

原文: https://howtodoinjava.com/java/io/how-to-write-to-file-in-java-bufferedwriter-example/

如果您想使用BufferedWriter将某些内容写入 Java 中的文件中,请使用以下代码作为模板,并按自己的方式重复使用它。

1)使用不带try-with-resourcesBufferedWriter(在 Java 7 之前)

package com.howtodoinjava.examples.io;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterExample 
{
	public static void main(String[] args)
	{
		try {

			String content = "Hello Learner !! Welcome to howtodoinjava.com.";

			File file = new File("c:/temp/samplefile.txt");

			FileWriter fw = new FileWriter(file);
			BufferedWriter bw = new BufferedWriter(fw);

			bw.write(content);
			bw.close();

		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

2)将BufferedWritertry-with-resources结合使用(Java 7 及更高版本)

package com.howtodoinjava.examples.io;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterExample 
{
	public static void main(String[] args)
	{
		try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("C:\\temp\\testOut.txt")))
		{

			String content = "Hello Learner !! Welcome to howtodoinjava.com.";

			bufferedWriter.write(content);

		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

祝您学习愉快!

Java 读写属性文件示例

原文: https://howtodoinjava.com/java/io/java-loadreadwrite-properties-file-examples/

如今,任何复杂的应用程序都需要某种配置。 有时我们需要将此配置为只读(通常在应用程序启动时读取),有时(或很少)我们需要写回或更新这些属性配置文件上的内容。

在这个简单易用的教程中,学习使用Properties.load()方法读取 Java 中的属性文件。 然后,我们将使用Properties.setProperty()方法将的新属性写入文件中。

用于例如app.properties的属性文件

下面是一个示例属性文件,我们将在示例应用程序中使用它。

firstName=Lokesh
lastName=Gupta
blog=howtodoinjava
technology=java

读取属性文件示例

在大多数应用程序中,属性文件在应用程序启动期间仅读取一次,然后进行缓存。 每当需要通过键来获取属性值时,您都将查询属性缓存并从中获取值。 该高速缓存通常是一个单例实例,因此应用程序不会多次读取属性文件;因此,应用程序无法读取属性文件。 因为对于当今世界上时间紧迫的应用程序,再次读取 IO 的成本非常高。

在下面的示例中,我给出了属性缓存单例实例的此类示例。 随时根据您的需要进行修改/建议。

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;

public class PropertiesCache
{
   private final Properties configProp = new Properties();

   private PropertiesCache()
   {
      //Private constructor to restrict new instances
      InputStream in = this.getClass().getClassLoader().getResourceAsStream("app.properties");
      System.out.println("Read all properties from file");
      try {
          configProp.load(in);
      } catch (IOException e) {
          e.printStackTrace();
      }
   }

   //Bill Pugh Solution for singleton pattern
   private static class LazyHolder
   {
      private static final PropertiesCache INSTANCE = new PropertiesCache();
   }

   public static PropertiesCache getInstance()
   {
      return LazyHolder.INSTANCE;
   }

   public String getProperty(String key){
      return configProp.getProperty(key);
   }

   public Set<String> getAllPropertyNames(){
      return configProp.stringPropertyNames();
   }

   public boolean containsKey(String key){
      return configProp.containsKey(key);
   }
}

在上面的代码中,我使用了 Bill Pugh 技术创建单例实例。

阅读更多: Java 中的单例设计模式

让我们测试上面为属性缓存创建的代码。

public static void main(String[] args)
{
  //Get individual properties
  System.out.println(PropertiesCache.getInstance().getProperty("firstName"));
  System.out.println(PropertiesCache.getInstance().getProperty("lastName"));

  //All property names
  System.out.println(PropertiesCache.getInstance().getAllPropertyNames());
}

Output:

Read all properties from file
Lokesh
Gupta
[lastName, technology, firstName, blog]

写入属性文件示例

就个人而言,我找不到从应用程序代码修改属性文件的任何充分理由。 仅当您准备将数据导出到仅需要此格式的数据的第三方供应商/或应用程序时,这才有意义。

因此,如果您面临类似的情况,请在PropertiesCache.java中创建另一个方法,如下所示:

public void setProperty(String key, String value){
  configProp.setProperty(key, value);
}

并以这种方式使用上述方法将写入新属性到属性文件中。

PropertiesCache cache = PropertiesCache.getInstance();
if(cache.containsKey("country") == false){
 cache.setProperty("country", "INDIA");
}
//Verify property
System.out.println(cache.getProperty("country"));  

Output:

Read all properties from file
INDIA 

这是与使用 Java 读写属性文件相关的这个简单易懂的教程的全部内容

给我留言是不清楚或您有任何疑问。

祝您学习愉快!

参考:Properties Java 文档

从资源文件夹读取文件 – Spring 示例

原文: https://howtodoinjava.com/java/io/read-file-from-resources-folder/

的两个 Java 示例从简单 Java 应用程序或任何 spring mvc / spring boot 应用程序中的资源文件夹读取文件。

Table of Contents

Project Structure
Read file using ClassLoader.getResource().toURI()
Read file using Spring's ResourceUtils.getFile()

项目结构

下图描述了此示例中使用的文件夹结构。 注意资源文件夹中的文件sample.txt

Read file from resources folder

从资源文件夹读取文件

使用ClassLoader.getResource().toURI()从资源文件夹中读取文件

我们可以使用类实例的ClassLoader引用从应用程序的资源包中读取文件。

package com.howtodoinjava.demo;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class ReadResourceFileDemo 
{
	public static void main(String[] args) throws IOException 
	{
		String fileName = "config/sample.txt";
		ClassLoader classLoader = new ReadResourceFileDemo().getClass().getClassLoader();

		File file = new File(classLoader.getResource(fileName).getFile());

		//File is found
		System.out.println("File Found : " + file.exists());

		//Read File Content
		String content = new String(Files.readAllBytes(file.toPath()));
		System.out.println(content);
	}
}

如果我们使用系统类加载器实例,则可以避免创建不必要的类实例,如下所示:

String fileName = "config/sample.txt";

ClassLoader classLoader = ClassLoader.getSystemClassLoader();

File file = new File(classLoader.getResource(fileName).getFile());

//File is found
System.out.println("File Found : " + file.exists());

//Read File Content
String content = new String(Files.readAllBytes(file.toPath()));
System.out.println(content);

程序输出如下所示。

Output:

File Found : true
Test Content

在 Spring 读取文件 – ResourceUtils.getFile()

如果您的应用程序是基于 springspring boot 的应用程序,则您可以直接利用ResourceUtils类。

File file = ResourceUtils.getFile("classpath:config/sample.txt")

//File is found
System.out.println("File Found : " + file.exists());

//Read File Content
String content = new String(Files.readAllBytes(file.toPath()));
System.out.println(content);

程序输出如下所示。

Output:

File Found : true
Test Content

学习愉快!

参考文献:

Spring ResourceUtils
ClassLoader.getResource()

Java – 读写 UTF-8 编码数据

原文: https://howtodoinjava.com/java/io/how-to-read-write-utf-8-encoded-data-in-java/

很多时候,我们需要在应用程序中处理 UTF-8 编码的数据。 这可能是由于本地化需求或仅仅是出于某些要求而处理用户输入。 甚至数据源也只能以这种格式提供数据。 在本教程中,我将提供两个非常简单的读取和写入操作示例。

如何写入 UTF-8 编码数据

这是演示如何从 Java 文件中写入“ UTF-8”编码数据的示例

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

public class WriteUTF8Data
{
   public static void main(String[] args)
   {
      try
      {
         File fileDir = new File("c:\\temp\\test.txt");
         Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileDir), "UTF8"));
         out.append("Howtodoinjava.com").append("\r\n");
         out.append("UTF-8 Demo").append("\r\n");
         out.append("क्षेत्रफल = लंबाई * चौड़ाई").append("\r\n");
         out.flush();
         out.close();
      } catch (UnsupportedEncodingException e)
      {
         System.out.println(e.getMessage());
      } catch (IOException e)
      {
         System.out.println(e.getMessage());
      } catch (Exception e)
      {
         System.out.println(e.getMessage());
      }
   }
}

您可能希望启用 Eclipse IDE 以支持 UTF-8 字符集。 默认情况下,它是禁用的。 如果您希望在 eclipse 中启用 UTF-8 支持,您将获得我以前的文章的必要帮助:

如何编译和运行以另一种语言编写的 Java 程序

如何读取 UTF-8 编码数据

这是演示如何从 Java 文件中读取“ UTF-8”编码数据的示例

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

public class ReadUTF8Data
{
   public static void main(String[] args)
   {
      try {
         File fileDir = new File("c:\\temp\\test.txt");

         BufferedReader in = new BufferedReader(
            new InputStreamReader(
                       new FileInputStream(fileDir), "UTF8"));

         String str;

         while ((str = in.readLine()) != null) {
             System.out.println(str);
         }

                 in.close();
         } 
         catch (UnsupportedEncodingException e) 
         {
             System.out.println(e.getMessage());
         } 
         catch (IOException e) 
         {
             System.out.println(e.getMessage());
         }
         catch (Exception e)
         {
             System.out.println(e.getMessage());
         }
   }
}

Output:

Howtodoinjava.com
UTF-8 Demo
क्षेत्रफल = लंबाई * चौड़ाई

祝您学习愉快!

Java 中如何检查文件是否存在

原文: https://howtodoinjava.com/java/io/how-to-check-if-file-exists-in-java/

在本教程中,我们将学习如何测试以检查文件是否存在或 Java 中给定路径中是​​否存在目录。

1. 使用File.exists()方法检查文件是否存在

要测试是否存在文件或目录,请使用 Java java.io.File类的exists()方法,如下所示:

File tempFile = new File("c:/temp/temp.txt");
boolean exists = tempFile.exists();

如果上述方法返回true,则文件或目录确实存在,否则不存在。

import java.io.File;
import java.io.IOException;

public class TemporaryFileExample
{
   public static void main(String[] args)
   {
      File temp;
      try
      {
         temp = File.createTempFile("myTempFile", ".txt");

         boolean exists = temp.exists();

         System.out.println("Temp file exists : " + exists);
      } catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}

程序输出。

Temp file exists : true

2. Files.exists()Files.notExists()方法

Java NIO 还提供了测试文件是否存在的好方法。 为此使用Files.exists()方法或Files.notExists()方法。

final Path path = Files.createTempFile("testFile", ".txt");

Files.exists(path);     //true

//OR

Files.notExists(path);  //false

3. 检查文件是否可读,可写或可执行

要验证程序是否可以根据需要访问文件,可以使用isReadable(Path)isWritable(Path)isExecutable(Path)方法 。

用于测试文件是否可读,可写和可执行的 Java 程序。

final Path path = ...;

Files.isReadable(path);

//OR

Files.isWritable(path);

//OR

Files.isExecutable(path);

这就是与检查 Java 中是否存在文件或目录相关的快速提示。 通过检查程序的可写属性来测试是否允许程序向其添加内容。

学习愉快!

参考:

Java 文档

Java 文件复制 – 用 Java 复制文件的 4 种方法

原文: https://howtodoinjava.com/java/io/4-ways-to-copy-files-in-java/

在 Java 中将文件从一个位置复制到另一个位置是您在应用程序中需要执行的常见任务。 在此示例中,我列出了 4 种不同的方式来复制 Java 中的文件。 使用最适合您需要的一种。

1)使用 apache commons IO 复制文件

要使用此方法,您需要下载 apache 通用 IO 的 jar 文件,并将其作为依赖项包含在您的项目中。

private static void fileCopyUsingApacheCommons() throws IOException 
{
	File fileToCopy = new File("c:/temp/testoriginal.txt");
	File newFile = new File("c:/temp/testcopied.txt");

	FileUtils.copyFile(fileToCopy, newFile);

	// OR

	IOUtils.copy(new FileInputStream(fileToCopy), new FileOutputStream(newFile));
}

2)使用java.nio.file.Files.copy()复制文件

这种方法非常快速且易于编写。

private static void fileCopyUsingNIOFilesClass() throws IOException 
{
	Path source = Paths.get("c:/temp/testoriginal.txt");
	Path destination = Paths.get("c:/temp/testcopied.txt");

	Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
}

3)使用java.nio.channels.FileChannel.transferTo()复制文件

如果您喜欢通道类的出色表现,请使用此方法。

private static void fileCopyUsingNIOChannelClass() throws IOException 
{
	File fileToCopy = new File("c:/temp/testoriginal.txt");
	FileInputStream inputStream = new FileInputStream(fileToCopy);
	FileChannel inChannel = inputStream.getChannel();

	File newFile = new File("c:/temp/testcopied.txt");
	FileOutputStream outputStream = new FileOutputStream(newFile);
	FileChannel outChannel = outputStream.getChannel();

	inChannel.transferTo(0, fileToCopy.length(), outChannel);

	inputStream.close();
	outputStream.close();
}

4)使用FileStreams复制文件

如果您对较旧的 Java 版本感到震惊,那么这个适合您。

private static void fileCopyUsingFileStreams() throws IOException
{
	File fileToCopy = new File("c:/temp/testoriginal.txt");
	FileInputStream input = new FileInputStream(fileToCopy);

	File newFile = new File("c:/temp/testcopied.txt");
	FileOutputStream output = new FileOutputStream(newFile);

	byte[] buf = new byte[1024];
	int bytesRead;

	while ((bytesRead = input.read(buf)) > 0) 
	{
		output.write(buf, 0, bytesRead);
	}

	input.close();
	output.close();
}

完整的示例代码

package com.howtodoinjava.examples.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

public class FileCopyExamples 
{
	public static void main(String[] args) throws IOException 
	{
		fileCopyUsingApacheCommons();
		fileCopyUsingNIOFilesClass();
		fileCopyUsingNIOChannelClass();
		fileCopyUsingFileStreams();
	}

	private static void fileCopyUsingFileStreams() throws IOException
	{
		File fileToCopy = new File("c:/temp/testoriginal.txt");
		FileInputStream input = new FileInputStream(fileToCopy);

		File newFile = new File("c:/temp/testcopied.txt");
		FileOutputStream output = new FileOutputStream(newFile);

		byte[] buf = new byte[1024];
		int bytesRead;

		while ((bytesRead = input.read(buf)) > 0) 
		{
			output.write(buf, 0, bytesRead);
		}

		input.close();
		output.close();
	}

	private static void fileCopyUsingNIOChannelClass() throws IOException 
	{
		File fileToCopy = new File("c:/temp/testoriginal.txt");
		FileInputStream inputStream = new FileInputStream(fileToCopy);
		FileChannel inChannel = inputStream.getChannel();

		File newFile = new File("c:/temp/testcopied.txt");
		FileOutputStream outputStream = new FileOutputStream(newFile);
		FileChannel outChannel = outputStream.getChannel();

		inChannel.transferTo(0, fileToCopy.length(), outChannel);

		inputStream.close();
		outputStream.close();
	}

	private static void fileCopyUsingApacheCommons() throws IOException 
	{
		File fileToCopy = new File("c:/temp/testoriginal.txt");
		File newFile = new File("c:/temp/testcopied.txt");

		FileUtils.copyFile(fileToCopy, newFile);

		// OR

		IOUtils.copy(new FileInputStream(fileToCopy), 
						new FileOutputStream(newFile));
	}

	private static void fileCopyUsingNIOFilesClass() throws IOException 
	{
		Path source = Paths.get("c:/temp/testoriginal.txt");
		Path destination = Paths.get("c:/temp/testcopied.txt");

		Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
	}
}

祝您学习愉快!

Java FilenameFilter示例 – 查找/删除某些扩展名的文件

原文: https://howtodoinjava.com/java/io/how-to-find-delete-files-of-certain-extension-filenamefilter-example/

很多时候,我们需要遍历并查找具有特定扩展名的所有文件,而对它们进行某些操作(例如删除它们)就只能进行。 如果您希望在一定时间后使用应用程序从日志文件夹中删除所有日志文件(如果存在此要求),则通常需要执行此操作。

在 Java 中,您可以使用FilenameFilter并覆盖它的accept(File targetDirectoty, String fileName)方法,以对参数目录内的所有文件执行文件过滤。

在此示例中,我将使用FilenameFilter列出文件夹“c:\\temp"中所有以.log扩展名结尾的文件,然后删除所有日志文件。

package com.howtodoinjava.examples.io;

import java.io.File;
import java.io.FilenameFilter;

public class FilenameFilterExample 
{
	public static void main(String[] args) 
	{
		//Delete all files from this directory
		String targetDirectory = "c:\\temp";
		File dir = new File(targetDirectory);

		//Filter out all log files
		String[] logFiles = dir.list(new LogFilterFilter());

		//If no log file found; no need to go further
		if (logFiles.length == 0)
			return;

		//This code will delete all log files one by one
		for (String log : logFiles) 
		{
			String tempLogFile = new StringBuffer(targetDirectory).append(File.separator).append(log).toString();
			File fileDelete = new File(tempLogFile);
			boolean isdeleted = fileDelete.delete();
			System.out.println("file : " + tempLogFile + " is deleted : " + isdeleted);
		}
	}
}

//This filter will be used to check for all files if a file is log file
class LogFilterFilter implements FilenameFilter 
{
	@Override
	public boolean accept(File dir, String fileName) 
	{
		return (fileName.endsWith(".log"));
	}
}

祝您学习愉快!

如何在 Java 中创建不可变的类

原文: https://howtodoinjava.com/java/basics/how-to-make-a-java-class-immutable/

不可变类是其状态一旦创建便无法更改的类。 有某些准则可以创建 Java 中不可变的类。

在这篇文章中,我们将重新审视这些准则。

Table of Contents

1\. Rules to create immutable classes
2\. Java immutable class example
3\. Benefits of making a class immutable
5\. Summary

1. 创建不可变类的规则

Java 文档本身具有一些准则,此链接中的这些准则可以确定编写不可变类。 通过使用Date字段创建具有可变对象的不可变类,我们将理解这些准则的实际含义。

  1. 不要提供“设置器”方法 - 修改字段或字段引用的对象的方法。

    该原则表明,对于您的类中的所有可变属性,请勿提供设置器方法。 设置器方法用于更改对象的状态,这是我们在此要避免的。

  2. 将所有字段定为最终和私有的

    这是增加不变性的另一种方法。 声明为private的字段将无法在类之外访问,并且将它们设置为最终字段将确保即使您无意中也无法更改它们。

  3. 不允许子类覆盖方法

    最简单的方法是将该类声明为final。 Java 中的final类无法扩展。

  4. 具有可变实例变量时要特别注意

    始终记住,实例变量将是可变的不可变的。 标识它们并返回具有所有可变对象复制内容的新对象。 不可变的变量可以安全地返回而无需额外的努力。

    一种更复杂的方法是使构造器private,并在工厂方法中构造实例。

2. Java 不可变类示例

让我们将以上所有规则应用于不可变类,并为 Java 中的不可变类实现具体的类实现


import java.util.Date;

/**
* Always remember that your instance variables will be either mutable or immutable.
* Identify them and return new objects with copied content for all mutable objects.
* Immutable variables can be returned safely without extra effort.
* */
public final class ImmutableClass
{

	/**
	* Integer class is immutable as it does not provide any setter to change its content
	* */
	private final Integer immutableField1;

	/**
	* String class is immutable as it also does not provide setter to change its content
	* */
	private final String immutableField2;

	/**
	* Date class is mutable as it provide setters to change various date/time parts
	* */
	private final Date mutableField;

	//Default private constructor will ensure no unplanned construction of class
	private ImmutableClass(Integer fld1, String fld2, Date date)
	{
		this.immutableField1 = fld1;
		this.immutableField2 = fld2;
		this.mutableField = new Date(date.getTime());
	}

	//Factory method to store object creation logic in single place
	public static ImmutableClass createNewInstance(Integer fld1, String fld2, Date date)
	{
		return new ImmutableClass(fld1, fld2, date);
	}

	//Provide no setter methods

	/**
	* Integer class is immutable so we can return the instance variable as it is
	* */
	public Integer getImmutableField1() {
		return immutableField1;
	}

	/**
	* String class is also immutable so we can return the instance variable as it is
	* */
	public String getImmutableField2() {
		return immutableField2;
	}

	/**
	* Date class is mutable so we need a little care here.
	* We should not return the reference of original instance variable.
	* Instead a new Date object, with content copied to it, should be returned.
	* */
	public Date getMutableField() {
		return new Date(mutableField.getTime());
	}

	@Override
	public String toString() {
		return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
	}
}

现在该测试我们的类了:


class TestMain
{
	public static void main(String[] args)
	{
		ImmutableClass im = ImmutableClass.createNewInstance(100,"test", new Date());
		System.out.println(im);
		tryModification(im.getImmutableField1(),im.getImmutableField2(),im.getMutableField());
		System.out.println(im);
	}

	private static void tryModification(Integer immutableField1, String immutableField2, Date mutableField)
	{
		immutableField1 = 10000;
		immutableField2 = "test changed";
		mutableField.setDate(10);
	}
}

程序输出:

100 - test - Tue Oct 30 21:34:08 IST 2012
100 - test - Tue Oct 30 21:34:08 IST 2012

可以看出,即使使用其引用更改实例变量也不会更改其值,因此该类是不可变的。

JDK 中的不可变类

除了您编写的类,JDK 本身还有很多不可变的类。 给出了这样的 Java 不可变类的列表。

  1. String
  2. 包装器类,例如IntegerLongDouble等。
  3. 不可变的集合类,例如Collections.singletonMap()等。
  4. java.lang.StackTraceElement
  5. Java 枚举(理想情况下应该如此)
  6. java.util.Locale
  7. java.util.UUID

3. 使类不可变的好处

首先让我们确定不可变类的优势。 在 Java 中,不可变类:

  1. 易于构建,测试和使用
  2. 自动是线程安全的,并且没有同步问题
  3. 不需要复制构造器
  4. 不需要克隆的实现
  5. 允许 hashCode() 使用延迟初始化,并缓存其返回值
  6. 用作字段时不需要防御性地复制
  7. 作为良好的映射的键和集合元素(在集合中这些对象不得更改状态)
  8. 在构造时就建立了其类不变式,因此无需再次检查
  9. 始终具有“失败原子性”(约书亚·布洛赫(Joshua Bloch)使用的术语):如果不可变对象引发异常,则它永远不会处于不希望或不确定的状态

4. 总结

在本教程中,我们学习了使用可变对象和不可变字段创建不可变的 Java 类。 我们还看到了不可变类在应用程序中带来的好处。

作为最佳设计实践,始终旨在使您的应用程序 Java 类不可变。 这样,您始终可以减少程序中与并发相关的缺陷的担心。

如何编写一个不可变的类? 这也可能是面试问题

学习愉快!

阅读更多:

为什么字符串类在 Java 中是不可变的?

Java FileFilter示例

原文: https://howtodoinjava.com/java/io/java-io-filefilter-example-tutorial/

有时我们会遇到这样的情况:必须在目录中处理特定类型的文件。 IO 包中提供java.io.FileFilter类仅用于这些目的。 让我们看一个简单的例子。 没什么复杂的。

让我们看一下FileFilter接口的源代码。 它仅包含一个方法,该方法将File实例作为参数,并根据文件满足所需条件返回true/false。

public interface FileFilter
{
   public abstract boolean accept(File file);
}

在此示例中,我正在构建FileFilter的实例,该实例可用于从指定目录中过滤掉.log 文件。 让我们看一下FileFilter实例的实现代码。

//create a FileFilter and override its accept-method
FileFilter logFilefilter 
				= new FileFilter() {
				  //Override accept method
				  public boolean accept(File file) {
					 //if the file extension is .log return true, else false
					 if (file.getName().endsWith(".log")) {
						return true;
					 }
					 return false;
				  }
			   };

上面的accept方法检查文件扩展名; 如果是.log,则将其视为日志文件,并且方法返回true;否则,方法返回false

现在来看FileFilter的工作示例。

import java.io.File;
import java.io.FileFilter;

public class IOUtils
{
   public void getFiles(String dir)
   {
      File directory = new File(dir);
      //Verify if it is a valid file name
      if (!directory.exists())
      {
         System.out.println(String.format("Directory %s does not exist", dir));
         return;
      }
      //Verify if it is a directory and not a file path
      if (!directory.isDirectory())
      {
         System.out.println(String.format("Provided value %s is not a directory", dir));
         return;
      }
      File[] files = directory.listFiles(logFilefilter);
      //Let's list out the filtered files
      for (File f : files)
      {
         System.out.println(f.getName());
      }
   }

   //create a FileFilter and override its accept-method
   FileFilter logFilefilter = new FileFilter() {
                                  //Override accept method
                                  public boolean accept(File file) {
                                     //if the file extension is .log return true, else false
                                     if (file.getName().endsWith(".log")) {
                                        return true;
                                     }
                                     return false;
                                  }
                               };

   //Test the file filtering                         
   public static void main(String[] args)
   {
      IOUtils ioUtils = new IOUtils();
      ioUtils.getFiles("C:\\temp");
   }
}

Output:

test.log
test1.log

上面的程序将列出“c:/temp”文件夹中的所有日志文件。

您可以构建自己的文件过滤器实例并实现自己的规则。 其余逻辑保持不变。

祝您学习愉快!

Java – 创建临时文件

原文: https://howtodoinjava.com/java/io/create-a-temporary-file-in-java/

在许多情况下都可能需要在 Java 中创建一个临时文件,但是大多数情况下,它会发生在您不想存储结果的单元测试中。 一旦测试用例完成,您就不再关心文件内容。

使用java.io.File.createTempFile()创建临时文件

此示例是最简单,最推荐的解决方案。

public class TemporaryFileExample
{
   public static void main(String[] args)
   {
      File temp;
      try
      {
         temp = File.createTempFile("myTempFile", ".txt");

         System.out.println("Temp file created : " + temp.getAbsolutePath());
      } catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}

Output:

Temp file created : C:\Users\lokesh\AppData\Local\Temp\myTempFile3492283537103788196.txt

使用 NIO 创建临时文件

public class TemporaryFileExample
{
   public static void main(String[] args)
   {
      try
      {
         final Path path = Files.createTempFile("myTempFile", ".txt");
         System.out.println("Temp file : " + path);

         //Delete file on exit
         path.toFile().deleteOnExit();

      } catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}

实际上,在使用 Junit 进行单元测试的情况下,您也可以使用TemporaryFolderTemporaryFolder规则允许创建保证在测试方法完成时(无论通过还是失败)都将被删除的文件和文件夹。

祝您学习愉快!

Java – 写入临时文件

原文: https://howtodoinjava.com/java/io/write-data-to-temporary-file-in-java/

在某些情况下,可能需要将数据写入 Java 中的临时文件。 因此,让我们快速写下一些示例代码。

使用java.io.BufferedWriter将数据写入临时文件

此示例是最简单的解决方案,并使用BufferedWriter编写内容。

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class TemporaryFileExample
{
   public static void main(String[] args)
   {
      File temp;
      try
      {
         temp = File.createTempFile("myTempFile", ".txt");

         //write data on temporary file
         BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
         bw.write("This is the temporary data written to temp file");
         bw.close();

         System.out.println("Written to Temp file : " + temp.getAbsolutePath());
      } catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}

使用 NIO 将数据写入临时文件

如果要使用 Java NIO 库,则可以使用Files.write()方法。

public class TemporaryFileExample
{
   public static void main(String[] args)
   {
      try
      {
         final Path path = Files.createTempFile("myTempFile", ".txt");
         System.out.println("Temp file : " + path);

		 //Writing data here
		 byte[] buf = "some data".getBytes();
		 Files.write(path, buf);

         //Delete file on exit
         path.toFile().deleteOnExit();

      } catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}

祝您学习愉快!

Java – 删除临时文件

原文: https://howtodoinjava.com/java/io/how-to-delete-temporary-file-in-java/

如果您的应用程序需要为某些应用程序逻辑或单元测试创​​建临时文件,那么您要确保在不需要这些临时文件时将其删除。 让我们学习如何在 Java 中删除临时文件。

使用 File.deleteOnExit()File.delete()删除临时文件

要在存在复制或完成复制时删除文件,可以使用:

File temp = File.createTempFile("myTempFile", ".txt"); 
temp.deleteOnExit();

要删除文件,可以立即使用delete()方法,而无需等待应用程序退出。

File temp = File.createTempFile("myTempFile", ".txt"); 
temp.delete();

删除临时文件的示例代码

import java.io.File;
import java.io.IOException;

public class TemporaryFileExample
{
   public static void main(String[] args)
   {
      File temp;
      try
      {
         temp = File.createTempFile("myTempFile", ".txt");

         System.out.println("Temp file created : " + temp.getAbsolutePath());

         //temp.delete(); //For deleting immediately

         temp.deleteOnExit(); //Delete on runtime exit

         System.out.println("Temp file exists : " + temp.exists());
      } catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}

使用 NIO 将数据写入临时文件

如果要使用 Java NIO 库,则可以使用Files.delete()Files.deleteIfExists()方法。

public class TemporaryFileExample
{
   public static void main(String[] args)
   {
      try
      {
         final Path path = Files.createTempFile("myTempFile", ".txt");
         System.out.println("Temp file : " + path);

         //Delete file on exit
         Files.deleteIfExists(path);

		 //Delete file immediately
		 Files.delete(path);	

      } catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}

祝您学习愉快!

Java – 读取控制台输入

原文: https://howtodoinjava.com/java/io/java-io-how-to-read-input-from-console/

这是一个快速参考代码示例,可使用 Java IO 从控制台读取输入。 这主要是为我的初学者准备的,因此,如果您已经知道它,请随时跳过。

Table of contents

1) Read console input using java.io.Console instance
2) Read console input using BufferedReader instance
3) Read console input using Scanner instance

让我们直接跳至示例代码。

1)使用java.io.Console实例读取控制台输入

private static void usingConsoleReader()
{
  Console console = null;
  String inputString = null;
  try
  {
	 // creates a console object
	 console = System.console();
	 // if console is not null
	 if (console != null)
	 {
		// read line from the user input
		inputString = console.readLine("Name: ");
		// prints
		System.out.println("Name entered : " + inputString);
	 }
  } catch (Exception ex)
  {
	 ex.printStackTrace();
  }
}

2)使用BufferedReader实例读取控制台输入

private static void usingBufferedReader()
{
  System.out.println("Name: ");
  try{
	 BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
	 String inputString = bufferRead.readLine();

	 System.out.println("Name entered : " + inputString);
 }
 catch(IOException ex)
 {
	ex.printStackTrace();
 }
}	

3)使用扫描器实例读取控制台输入

private static void usingScanner()
{
  System.out.println("Name: ");

  Scanner scanIn = new Scanner(System.in);
  String inputString = scanIn.nextLine();

  scanIn.close();            
  System.out.println("Name entered : " + inputString);
}

所有 3 个示例的完整代码

import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;

public class ConsoleInputExamples
{
   public static void main(String[] args)
   {
      usingConsoleReader();
      usingBufferedReader();
      usingScanner();
   }

   private static void usingConsoleReader()
   {
      Console console = null;
      String inputString = null;
      try
      {
         // creates a console object
         console = System.console();
         // if console is not null
         if (console != null)
         {
            // read line from the user input
            inputString = console.readLine("Name: ");
            // prints
            System.out.println("Name entered : " + inputString);
         }
      } catch (Exception ex)
      {
         ex.printStackTrace();
      }
   }

   private static void usingBufferedReader()
   {
      System.out.println("Name: ");
      try{
         BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
         String inputString = bufferRead.readLine();

         System.out.println("Name entered : " + inputString);
     }
     catch(IOException ex)
     {
        ex.printStackTrace();
     }
   }

   private static void usingScanner()
   {
      System.out.println("Name: ");

      Scanner scanIn = new Scanner(System.in);
      String inputString = scanIn.nextLine();

      scanIn.close();            
      System.out.println("Name entered : " + inputString);
   }

}

最重要的是,这三种技术同样有效,但是我个人喜欢java.io.Console方式。 它只是使代码更具可读性。 您有什么选择?

祝您学习愉快!

Java – 使用Scanner类读取类型安全输入

原文: https://howtodoinjava.com/java/io/read-typesafe-input-scanner-class/

学习在任何交互式 Java 应用程序中从控制台读取类型安全用户输入,即提示用户直到用户以正确的格式/数据类型输入值。

Table of Contents

Type unsafe way of reading input [Not recommended]
Typesafe way of reading input from console using Scanner [Recommended]

读取输入内容的不安全方式

是否曾尝试使用Scanner类在基于交互式控制台的程序中读取用户的输入? 这是非常简单的代码。 您问用户一个问题,用户输入值,然后按Enter。 然后,您可以使用Scanner.nextXYZ()方法读取该值。

我们来看一个使用Scanner类从控制台读取输入的示例。

private static void typeUnsafeReadExample() {

	Scanner scanner = new Scanner(System.in);

	System.out.print("Enter your age as an integer > ");

	int age = scanner.nextInt();
	System.out.println("Your age is " + age);

	scanner.close();
}

//Output - 1
Enter your age as an integer > 10
Your age is 10

//Output - 2
Enter your age as an integer > ten
Exception in thread "main" java.util.InputMismatchException
	at java.util.Scanner.throwFor(Scanner.java:864)
	at java.util.Scanner.next(Scanner.java:1485)
	at java.util.Scanner.nextInt(Scanner.java:2117)
	at java.util.Scanner.nextInt(Scanner.java:2076)
	at com.howtodoinjava.examples.TypeSafeInputExample.typeUnsafeReadExample(TypeSafeInputExample.java:19)
	at com.howtodoinjava.examples.TypeSafeInputExample.main(TypeSafeInputExample.java:9)

用户根据自己的理解键入正确的输入时,由于InputMismatchException异常而使应用程序崩溃。

让我们解决这个用例。

使用扫描器从控制台读取输入的类型安全方式

从理论上讲,我们可以在阅读之前检查下一个标记是否与我们的预期输入匹配,从而使程序更强大。 Scanner.hasNextXYZ()方法可以执行确切的操作,这是我们需要的。 如果可以将下一个标记读取为请求的数据类型,则返回true

例如,如果我们期望整数值,则仅当扫描器中的下一个可用标记可以解析为整数值时,调用Scanner.hasNextInt()才会返回true。 否则它将返回false,我们可以通知用户输入的值无效,并重新提示输入新值。

让我们来看看以上使用代码的解决方案。

private static void typeSafeReadExample() 
{
	Scanner scanner = new Scanner(System.in);

	System.out.print("Enter your age > ");

	while (!scanner.hasNextInt()) 
	{
		scanner.nextLine();	//clear the invalid input before prompting again
		System.out.print("Please enter your age in natural positive number > ");
	}

	int age = scanner.nextInt();
	System.out.println("Your age is " + age);

	scanner.close();
}

//Output:

Enter your age > ten
Please enter your age in natural positive number > 10.5
Please enter your age in natural positive number > 10
Your age is 10

使用Scanner.hasNextXYZ()Scanner.nextXYZ()方法,我们可以编写任何基于控制台的交互式 Java 应用程序,在其中可以强制用户仅输入有效输入-而不会导致程序崩溃。

学习愉快!

在 Java 中将字符串转换为InputStream

原文: https://howtodoinjava.com/java/io/convert-string-to-inputstream-in-java/

Java 示例使用ByteArrayInputStreamIOUtils类将字符串转换为InputStream。将字符串写入InputSteam在 Java 中是一项常见的工作,并且拥有一些良好的快捷方式在将来会变得更好。

1)使用ByteArrayInputStreamString转换为InputStream

使用ByteArrayInputStream是获取给定字符串输入流的最简单方法,并且不需要任何外部依赖关系。

]
import java.io.ByteArrayInputStream;
import java.io.InputStream;

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

	  //Here converting string to input stream
      InputStream stream = new ByteArrayInputStream(sampleString.getBytes());
   }
}

2)来自 Apache Commons 的IOUtils

IOUtils对于 Java 中的 IO 操作非常有用。 该解决方案也非常好,因为在任何项目中,apache commons 都是一个“大部分包含”的 jar。

它使代码更具可读性

import java.io.InputStream;
import org.apache.commons.io.IOUtils;

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

	  //Here converting string to input stream
      InputStream stream = IOUtils.toInputStream(sampleString);
   }
}

在链接的文章中学习InputStream转换为字符串

学习愉快!

在 Java 中将InputStream转换为字符串

原文: https://howtodoinjava.com/java/io/how-to-read-data-from-inputstream-into-string-in-java/

学习使用BufferedReaderScannerIOUtilsInputStream转换为字符串。 从InputStream读取字符串在几种类型的应用程序中是非常普遍的要求,在这些应用程序中,您需要从网络流或文件系统中读取数据并对其进行一些操作。

如果使用java.nio包读取文件,则可以使用 Java NIO 中的 3 种读取文件的有效方法。 但是,如果您仍在使用旧的 Java IO 类,或者愿意使用将文件读取为字符串的任何新有效方式,那么您来对地方了。

Table of Contents

1\. InputStream to String using Guava
2\. BufferedReader
3\. IOUtils
4\. java.util.Scanner

io-read-into-string

1. 使用 Google Guava IO 将InputStream转换为String

Guava 库具有一些非常有用的类和方法来执行 IO 操作。 这些类隐藏了所有复杂性,否则就会暴露出来。

1.1 Maven

Google Guava 的 Maven 依赖关系。

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>26.0-jre</version>
</dependency>

1.2 字节源

Java 程序使用 Google Guava 库中的ByteSource类将InputStream读取为String

package com.howtodoinjava.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import com.google.common.base.Charsets;
import com.google.common.io.ByteSource;

public class Main 
{
    public static void main(String[] args) throws Exception
    {
        InputStream inputStream = new FileInputStream(new File("C:/temp/test.txt"));

        ByteSource byteSource = new ByteSource() {
            @Override
            public InputStream openStream() throws IOException {
                return inputStream;
            }
        };

        String text = byteSource.asCharSource(Charsets.UTF_8).read();

        System.out.println(text);
    }
}

1.2 字符流

Java 程序,用于使用 Google Guava 库中的CharStreams类将InputStream转换为String

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import com.google.common.io.CharStreams;

public class Main 
{
    public static void main(String[] args) throws Exception
    {
        InputStream inputStream = new FileInputStream(new File("C:/temp/test.txt"));

        String text = null;

        try (final Reader reader = new InputStreamReader(inputStream)) {
            text = CharStreams.toString(reader);
        }

        System.out.println(text);
    }
}

2. 使用BufferedReaderInputStream转换为String

使用BufferedReader将文件读入字符串的最简单和流行的方法。 它有助于逐行读取输入流。

package com.howtodoinjava.demo.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ReadStreamIntoStringUsingReader
{
	public static void main(String[] args) throws FileNotFoundException, IOException 
	{
		InputStream in = new FileInputStream(new File("C:/temp/test.txt"));
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));
	    StringBuilder out = new StringBuilder();
	    String line;
	    while ((line = reader.readLine()) != null) {
	        out.append(line);
	    }
	    System.out.println(out.toString());   //Prints the string content read from input stream
	    reader.close();
	}
}

3. Apache Commons IOUtils(最易读)

Apache commons 有一个非常有用的类IOUtils将文件内容读入String。 它使代码更清晰,并且易于阅读。 它也是快速的。

使用两种方法之一:

  1. IOUtils.copy()
  2. IOUtils.toString()
package com.howtodoinjava.demo.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;

import org.apache.commons.io.IOUtils;

public class ReadStreamIntoStringUsingIOUtils
{
	public static void main(String[] args) throws FileNotFoundException, IOException 
	{
		//Method 1 IOUtils.copy()

		StringWriter writer = new StringWriter();
		IOUtils.copy(new FileInputStream(new File("C:/temp/test.txt")), writer, "UTF-8");
		String theString = writer.toString();
		System.out.println(theString);

		//Method 2 IOUtils.toString()

		String theString2 = IOUtils.toString(new FileInputStream(new File("C:/temp/test.txt")), "UTF-8");
		System.out.println(theString2);
	}
}

4. 使用扫描器将 Java InputStream转换为String

使用扫描器类不是​​很流行,但是可以使用。 它起作用的原因是因为Scanner对流中的标记进行迭代,并且在此过程中,我们可以使用“输入边界的起点”(A)来分隔标记,因此仅给我们一个流的整个内容的标记。

package com.howtodoinjava.demo.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ReadStreamIntoStringUsingScanner
{
	@SuppressWarnings("resource")
	public static void main(String[] args) throws FileNotFoundException, IOException 
	{
		FileInputStream fin = new FileInputStream(new File("C:/temp/test.txt"));
		java.util.Scanner scanner = new java.util.Scanner(fin,"UTF-8").useDelimiter("\A");
		String theString = scanner.hasNext() ? scanner.next() : "";
		System.out.println(theString);
		scanner.close();
	}
}

就这样。 这篇文章的目的是为特定目的提供快速链接,即将输入流读取到字符串中。

下载源码

学习愉快!

Java – 创建受密码保护的 Zip 文件

原文: https://howtodoinjava.com/java/io/how-to-create-password-protected-zip-files-in-java/

本教程介绍使用非常有用的库 Zip4j 创建受密码保护的 zip 文件。 Java 默认情况下不提供文件密码保护的任何支持。 尽管它对创建/提取 zip 文件具有很好的 API 支持。 还有一些其他有用的库,它们同样好,有时甚至比 zip4j 更好,但是它们也使用一些本机代码,这使得它们的使用平台在某种程度上依赖于。 Zip4j 完全使用 Java 代码,没有任何本机代码的支持,因此它更适合我。

Zip4j 提供以下特性:

  • 创建,添加,提取,更新,从 Zip 文件中删除文件
  • 读/写受密码保护的 Zip 文件
  • 支持 AES 128/256 加密
  • 支持标准 Zip 加密
  • 支持 Zip64 格式
  • 支持存储(无压缩)和压缩压缩方法
  • 从分卷 Zip 文件创建或提取文件(例如:z01,z02,…zip)
  • 支持 Unicode 文件名
  • 进度监视器

1)下载 Zip4j 库并将其包含到项目中

要下载 Zip4j 库,请转到其 zip4j 下载页面并下载其最新版本(直到今天的zip4j_1.3.2.jar)。

zip4j.jar文件添加到您项目的类路径中。

2)创建受密码保护的 zip 文件的示例

现在,您可以使用以下代码创建受密码保护的存档。

您将需要使用 Java 6 来运行示例。

import java.io.File;
import java.util.ArrayList;

import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;

public class CreatePasswordProtectedZipExample
{
	public static void main(String[] args) 
	{
		try {
			//This is name and path of zip file to be created
			ZipFile zipFile = new ZipFile("C:/temp/test.zip");

			//Add files to be archived into zip file
			ArrayList<File> filesToAdd = new ArrayList<File>();
			filesToAdd.add(new File("C:/temp/test1.txt"));
			filesToAdd.add(new File("C:/temp/test2.txt"));

			//Initiate Zip Parameters which define various properties
			ZipParameters parameters = new ZipParameters();
			parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE); // set compression method to deflate compression

			//DEFLATE_LEVEL_FASTEST 	- Lowest compression level but higher speed of compression
			//DEFLATE_LEVEL_FAST 		- Low compression level but higher speed of compression
			//DEFLATE_LEVEL_NORMAL 	- Optimal balance between compression level/speed
			//DEFLATE_LEVEL_MAXIMUM 	- High compression level with a compromise of speed
			//DEFLATE_LEVEL_ULTRA 		- Highest compression level but low speed
			parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL); 

			//Set the encryption flag to true
			parameters.setEncryptFiles(true);

			//Set the encryption method to AES Zip Encryption
			parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);

			//AES_STRENGTH_128 - For both encryption and decryption
			//AES_STRENGTH_192 - For decryption only
			//AES_STRENGTH_256 - For both encryption and decryption
			//Key strength 192 cannot be used for encryption. But if a zip file already has a
			//file encrypted with key strength of 192, then Zip4j can decrypt this file
			parameters.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);

			//Set password
			parameters.setPassword("howtodoinjava");

			//Now add files to the zip file
			zipFile.addFiles(filesToAdd, parameters);
		} 
		catch (ZipException e) 
		{
			e.printStackTrace();
		}
	}
}

还有其他有用的用例,您也可以查看其源码发行版zip4j_examples_eclipse_1.3.2.zip。 签出这个非常有用的库。

祝您学习愉快!

参考: http://www.lingala.net/zip4j/

Java – 解压缩带有子目录的文件

原文: https://howtodoinjava.com/java/io/unzip-file-with-subdirectories/

Java 代码示例,它使用java.util.zip包从压缩的 zip 文件中解压缩并提取文件。 在此示例中,代码打开一个 zip 文件并以与目录浏览类似的方式开始遍历这些文件。 如果找到目录条目,则创建目录。 如果找到文件条目,则写入解压缩的文件。

解压缩文件示例

import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class Main 
{
	public static void main(String[] args) 
	{
		//Open the file 
		try(ZipFile file = new ZipFile("files.zip"))
		{
			FileSystem fileSystem = FileSystems.getDefault();
			//Get file entries
			Enumeration<? extends ZipEntry> entries = file.entries();

			//We will unzip files in this folder
			String uncompressedDirectory = "uncompressed/";
			Files.createDirectory(fileSystem.getPath(uncompressedDirectory));

			//Iterate over entries
			while (entries.hasMoreElements()) 
			{
				ZipEntry entry = entries.nextElement();
				//If directory then create a new directory in uncompressed folder
				if (entry.isDirectory()) 
				{
					System.out.println("Creating Directory:" + uncompressedDirectory + entry.getName());
					Files.createDirectories(fileSystem.getPath(uncompressedDirectory + entry.getName()));
				} 
				//Else create the file
				else 
				{
					InputStream is = file.getInputStream(entry);
					BufferedInputStream bis = new BufferedInputStream(is);
					String uncompressedFileName = uncompressedDirectory	+ entry.getName();
					Path uncompressedFilePath = fileSystem.getPath(uncompressedFileName);
					Files.createFile(uncompressedFilePath);
					FileOutputStream fileOutput = new FileOutputStream(uncompressedFileName);
					while (bis.available() > 0) 
					{
						fileOutput.write(bis.read());
					}
					fileOutput.close();
					System.out.println("Written :" + entry.getName());
				}
			}
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
	}
}

示例如何运作

下面列出的事情在上面的示例中依次发生。

  1. ZipFile对象表示.zip文件,并用于访问其信息。
  2. ZipEntry类表示 zip 文件中的条目-文件或目录。
  3. 每个ZipEntry实例都具有压缩和未压缩的大小信息,名称以及未压缩字节的输入流。
  4. 使用InputStreamBufferedInputStream,我们将未压缩的字节读入字节缓冲区,然后使用FileOutputStream将其写入文件。
  5. 继续这样做,直到处理完整个文件。

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

学习愉快!

Java main()方法

原文: https://howtodoinjava.com/java/basics/main-method/

您是否曾经尝试过说明 Java main()方法publicstaticvoid的原因? 为什么叫main? 当您调用main()方法时,JVM 内部会发生什么? main()方法的目的是什么? 让我们找出答案。

Table of Contents

1\. Java main method syntax
2\. Why main method is public?
3\. Why main method is static?
4\. Why main method is void?
5\. Why the name is main?
6\. What happens internally when you invoke main method?
7\. main() method native code in java.c
8\. Do we always need main method to run java program?
9\. Summary

1. Java main()方法语法

首先提醒一下 Java 中main方法的语法

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

2. 为什么 Java main()方法是公开的?

这个大问题也许也是最困难的。 我尽力在我能获得的所有良好学习材料中找到找到此问题的一个很好的理由,但没有任何证据被证明是足够的。 因此,我的分析(与许多其他方法一样)说:main()方法是公共的,因此可以在任何地方以及可能希望使用它启动应用程序的每个对象中访问。 在这里,我并不是说 JDK / JRE 具有类似的原因,因为java.exejavaw.exe(对于 Windows)使用 Java 本机接口(JNI)对invoke方法的调用,因此无论使用哪种访问修饰符,它们都可以被调用。

回答这个问题的第二种方法是另一个问题,为什么不公开? Java 中的所有方法和构造器都有一些访问修饰符。 main()方法也需要一个。 没有理由不应该将其设置为public,而应使用任何其他修饰符(默认/受保护/私有)。

请注意,如果不使用main()方法public,则不会发生编译错误。 您将出现运行时错误,因为不存在匹配的main()方法。 请记住,整个语法应该匹配才能执行main()方法。

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

程序输出:

Error: Main method not found in class Main, please define the main method as:
   public static void main(String[] args)

3. 为什么 Java main()方法是静态的?

另一个大问题。 为了理解这一点,假设我们没有main方法为static。 现在,要调用任何方法,您需要它的一个实例。是吧?

众所周知,Java 可以具有重载的构造器。 现在,应该使用哪一个,重载的构造器的参数将从何处来。 这些只是更困难的问题,这些问题帮助 Java 设计人员下定了决心,并将main()方法设为static

请注意,如果不使用main()方法static,则不会发生编译错误。 您将运行时错误

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

程序输出:

Error: Main method is not static in class main, please define the main method as:
   public static void main(String[] args)

4. 为什么 Java main()方法是void

为什么不应该无效? 您是否从代码中调用过此方法? 没有。 因此,没有任何将值返回给实际调用此方法的 JVM 的用途。 它根本不需要任何返回值。

应用程序唯一想与调用过程进行通信的是正常终止还是异常终止。 使用System.exit(int)已经可以做到这一点。 非零值表示异常终止,否则一切正常。

5. 为什么名字叫main

没有坚如磐石的理由。 假设因为它已经与 C 和 C++ 语言一起使用。 因此,大多数开发人员已经对这个名称感到满意。 否则,没有其他充分的理由。

6. 调用main方法在内部会发生什么?

Java 中main()方法的用途是程序执行的起点

当您运行java.exe,然后运行时,有几个 Java 本机接口(JNI)调用。 这些调用会加载真正是 JVM 的 DLL(是的-java.exe不是 JVM)。 JNI 是我们在虚拟机世界和 C,C++ 等世界之间架起桥梁时所使用的工具。反之亦然。 如果不使用 JNI,就不可能真正使 JVM 运行。

基本上,java.exe是一个超级简单的 C 应用程序,解析命令行,在 JVM 中创建一个新的String数组来保存这些参数,解析出您指定来包含main()的类名,使用 JNI 调用来查找main()方法本身,然后调用main()方法,传入新创建的字符串数组作为参数。

这非常类似于使用 Java 反射时的操作,只是使用了令人困惑的命名本机函数调用。

对于您来说,编写自己的java.exe版本(源代码随 JDK 分发)并执行完全不同的操作是完全合法的。

7. java.c中的main()方法本机代码

下载并解压缩源 jar 并签出../launcher/java.c。 它是这样的:


/*
* Get the application's main class.
*/
if (jarfile != 0) {
mainClassName = GetMainClassName(env, jarfile);
... ...

mainClass = LoadClass(env, classname);
if(mainClass == NULL) { /* exception occured */
... ...

/* Get the application's main method */
mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");
... ...

{/* Make sure the main method is public */
jint mods;
jmethodID mid;
jobject obj = (*env)->ToReflectedMethod(env, mainClass, mainID, JNI_TRUE);
... ...

/* Build argument array */
mainArgs = NewPlatformStringArray(env, argv, argc);
if (mainArgs == NULL) {
ReportExceptionDescription(env);
goto leave;
}

/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

因此,在这里您可以看到main方法调用 Java 程序时会发生什么。

8. 我们是否总是需要main方法来运行 Java 程序?

我相信不是。 我们有 applet,在这里我们没有编写main方法。 我仍然需要检查它们是如何执行的。 如果您已经知道,请与我分享。

9. 总结

所有开发人员都使用 Java 的main方法,每个人都知道编写它的基本语法。 但是,很少有人完全理解正确的推理及其工作方式。 我仍在努力寻求更多的理解,如果发现更多有趣的事实,将更新这篇文章。

如果您有什么要分享的内容,请在评论部分添加或给我发送邮件。 在这篇文章中,我将包含在您的知识中。

学习愉快!

参考: StackOverflow

使用 Java 在 Linux 中管理不超过 N GB 的系统日志文件

原文: https://howtodoinjava.com/linux/manage-system-log-files-in-linux-using-java/

如今,大多数应用程序都需要管理自己的日志文件,包括在达到特定大小限制时将其删除。 在本文中,我将尝试为这种日志文件管理提出一种解决方案。 我还将建议捕获 linux 进程的输出并将其记录到一些单独的日志文件中的方法。

本文是我上一篇文章的延续:自动重载配置,其中我讨论了只要有人在文件系统中更改配置文件,应用程序如何无需重新启动应用程序即可完成其配置重载的工作 。

这篇文章中的部分:

  • 确定方法
  • 编写 bash 脚本
  • 编写脚本执行器
  • 添加食流者
  • 查看全部

确定方法

我相信,要把工作做好,我们肯定需要一个 bash 脚本。 Linux 有一些非常有用的内置命令,通过命令窗口执行时,这些命令可以立即执行此工作。 bash 脚本的优势包括将实际应用程序的文件处理程序代码解耦,如果需要任何特定于平台的更改,可以对其进行修改。

编写 bash 脚本

我已经编写了一个演示脚本。 您可以根据需要使用它:

#!/bin/bash
###############################################################################
#
# discCleanUp.sh
#
################################################################################

DIR_PATH=$1
NUM_OF_DAYS=$2
DIR_SIZE=$3
SIZE=1024

DIR_SIZE=$(($DIR_SIZE * $SIZE * $SIZE))

# Command to find the File older then NUM_OF_DAYS and removing them from directory DIR_PATH
find $DIR_PATH -mtime +$NUM_OF_DAYS -exec rm {} ;

#Go to specified directory
cd $DIR_PATH

# Command to get the current directory size.
CURR_DIR_SIZE=`du -sk | cut -f1`

while [[ $CURR_DIR_SIZE -gt DIR_SIZE ]];

do

echo $CURR_DIR_SIZE

FILE=`ls -1tra | head -1`

rm -rf $FILE

# Command to get the current directory size.
CURR_DIR_SIZE=`du -sk | cut -f1`

done

exit 0

在上面的脚本中,DIR_PATHNUM_OF_DAYSDIR_SIZE是命令行参数。 而SIZE是用于计算目的的常数。 在上面的脚本中,行号 16 将删除 n 天之前的日志文件。

行号 22 中,脚本使用du命令检查目录的当前大小。 如果大小超过DIR_SIZE(以 GB 为单位),脚本将开始使用ls -1tra | head -1命令并开始将它们一个一个地删除。 它将继续直到目录大小未达到所需的限制。

编写脚本执行器

至于本文,核心组件将保留在 bash 脚本之上,但仍然需要一种从应用程序运行.sh文件的方法。 最好的方法是使用线程(如果需要,可以使用执行器)。 该线程将以可配置的速率定期执行以上 bash 脚本。 我不会写那部分代码。 如果执行代码时遇到任何问题,请写下注释。

让我给你一个示例代码::

package corejava.dischandler;

import java.io.IOException;

/**
* Title: DiskFileManager.java
* Description :- This class provides API to check file size and file time threshold and
* if threshold is crossed then deletes file to come within threshold.
*/
public final class DiskFileManager
{

	private static DiskFileManager diskFileManager=new DiskFileManager();
	private DiskFileManager()
	{
	}

	/**
	* <pre>
	* <b>checkAndDeleteFiles</b>
	* <b>Description:This method is called to clean the files from the file system.</b>
	* </pre>
	* @throws InterruptedException
	* @throws IOException
	* @throws InstantiationException
	*/
	public   void checkAndDeleteFiles() throws InterruptedException, IOException, InstantiationException
	{
		try
		{
			StringBuffer outStr = new StringBuffer();
			StringBuffer errStr = new StringBuffer();
			String scriptFileName = "./discfilehandler.sh";
			String command = scriptFileName + "/logs 1 1";

			System.out.println("Command to be executed  :- " + command);

			Process output = Runtime.getRuntime().exec(command);
			StreamEater errorEater = new StreamEater(output.getErrorStream(),errStr);
			StreamEater outputEater = new StreamEater(output.getInputStream(),outStr);
			errorEater.start();
			outputEater.start();
			try
			{
				int ret = output.waitFor();
				errorEater.join();
				outputEater.join();

				System.out.println();

				//logger.info("execute(): Error Stream:" + errStr + " ; execute(): Output Stream:" + outStr + " ; execute(): ExitValue:" + ret);

			} catch (InterruptedException e)
			{
				throw e;
			}
		} catch (IOException e)
		{
			throw e;
		}
	}
	/**
	* <pre>
	* <b>getInstance</b>
	* <b>Description:This method is used to get the instance of Disk File Manager.</b>
	* </pre>
	* @return
	*/
	public static DiskFileManager getInstance()
	{
		return diskFileManager;
	}
}

上面的代码将在类路径中找到脚本文件,并传递所需的参数。 在我们的例子中,/logs 1 1是 3 个参数,这意味着

  • 要监视的目录是/logs
  • 删除一天的日志文件,然后
  • 请勿在任何时候存储超过 1 GB 的日志文件。

添加StreamEater

因此,我们已经添加了一个 bash 脚本和一个用于运行脚本的执行器代码。 您必须编写自己的线程才能在执行器上方定期运行。

现在,我们将研究StreamEater,它实际上收集命令输出并提供给您的应用程序代码,因此应用程序可以记录它或执行所需的任何操作。

我们已经在上面的执行器中使用过StreamEater,现在我给出其代码:

package corejava.dischandler;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class StreamEater extends Thread
{

	/**
	* Stream Eater thread to eat up every thing that is the output to the
	* execute command
	*/
	private InputStream  iStream;

	public InputStream getiStream() {
		return iStream;
	}

	public void setiStream(InputStream iStream) {
		this.iStream = iStream;
	}

	private StringBuffer stringBuffer;

	public StringBuffer getStringBuffer() {
		return stringBuffer;
	}

	public void setStringBuffer(StringBuffer stringBuffer) {
		this.stringBuffer = stringBuffer;
	}

	/**
	* This is the constructor.
	*
	* @param is - It is the input stream
	* @param type - type of input stream
	* @param redirect - string buffer to contain the output.
	* @throws InstantiationException
	*/

	public StreamEater(InputStream is, StringBuffer redirect) throws InstantiationException
	{
		this.iStream = is;
		if (redirect == null)
		{
			throw new InstantiationException("String Buffer Reference , second param can not be null");
		}
		this.stringBuffer = redirect;
	}

	/**
	* This is method called when the thread is started. It captures the stream
	* output and and store it into the buffer.
	*/
	public void run()
	{
		InputStreamReader isr = null;
		BufferedReader br = null;
		try
		{

			isr = new InputStreamReader(iStream);
			br = new BufferedReader(isr);
			String line;
			while ((line = br.readLine()) != null)
			{
				stringBuffer.append(line).append(System.getProperty("line.separator"));
			}

		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
		finally
		{
		if (isr != null)
		{
			try
			{
				isr.close();
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
		if (br != null)
		{
			try
			{
				br.close();
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
		}
	}

}

如您所见,这些都是等待收集进程输出的小线程。

查看全部

使用上述类和给定的 bash 脚本,您可以轻松开发一些应用程序组件,以确保日志文件的大小不超过某些预定义的数目。 当您的应用程序负责删除实际的日志文件时,可以在任何级别对​​其进行自定义。

希望您在这篇文章中得到一些有用的信息。 如果需要任何帮助或有任何疑问,请给我留言。

学习愉快!

在 Java 中生成 SHA 或 MD5 文件校验和哈希

原文: https://howtodoinjava.com/java/io/how-to-generate-sha-or-md5-file-checksum-hash-in-java/

校验和哈希是对用户提供的内容应用某些算法和操作后获得的加密字符序列。 在本文中,我们将学习为文件生成校验和哈希。

1. 为什么我们可能要为文件生成校验和哈希?

任何严肃的文件提供者都提供一种机制,以使可下载文件具有校验和。 校验和是一种机制的形式,可确保正确下载我们下载的文件。 校验和就像文件有效性的证明一样,因此如果文件损坏,则该校验和将更改,因此,我们可以得知该文件不是同一文件,或者由于任何原因在传输之间文件都已损坏。

您还可以创建文件的校验和,以检测第三方对文件的任何可能更改,例如许可证文件。 您向客户端提供许可证,这些许可证可以上传到您的服务器。 您可以交叉验证文件的校验和以验证许可证文件在创建后未被修改。

阅读更多: MD5,SHA,PBKDF2,BCrypt 示例

2. 如何为文件生成校验和哈希

要为文件创建校验和,您将需要逐字节读取文件的内容。 然后使用以下方式为其生成哈希。

此函数有两个参数:

  1. 邮件摘要算法的实现
  2. 需要为其生成校验和的文件
private static String getFileChecksum(MessageDigest digest, File file) throws IOException
{
	//Get file input stream for reading the file content
	FileInputStream fis = new FileInputStream(file);

	//Create byte array to read data in chunks
	byte[] byteArray = new byte[1024];
	int bytesCount = 0; 

	//Read file data and update in message digest
	while ((bytesCount = fis.read(byteArray)) != -1) {
		digest.update(byteArray, 0, bytesCount);
	};

	//close the stream; We don't need it now.
	fis.close();

	//Get the hash's bytes
	byte[] bytes = digest.digest();

	//This bytes[] has bytes in decimal format;
	//Convert it to hexadecimal format
	StringBuilder sb = new StringBuilder();
	for(int i=0; i< bytes.length ;i++)
	{
		sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
	}

	//return complete hash
   return sb.toString();
}

您可以使用以下代码来生成 MD5 文件校验和

//Create checksum for this file
File file = new File("c:/temp/testOut.txt");

//Use MD5 algorithm
MessageDigest md5Digest = MessageDigest.getInstance("MD5");

//Get the checksum
String checksum = getFileChecksum(md5Digest, file);

//see checksum
System.out.println(checksum);

生成 SHA 文件校验和,请使用以下代码:

//Use SHA-1 algorithm
MessageDigest shaDigest = MessageDigest.getInstance("SHA-1");

//SHA-1 checksum 
String shaChecksum = getFileChecksum(shaDigest, file);

如果需要更多说明,请给我评论。

学习愉快!

Java 日期时间教程

Java – 日期和时间 API

原文: https://howtodoinjava.com/java-date-and-time-apis/

Java 主要使用两个包java.timejava.util支持日期和时间特性。 包java.time是在 Java 8 中添加的( JSR-310 ),新添加的类旨在解决传统java.util.Datejava.util.Calendar类的缺点。

1. 旧版日期时间 API

1.1 类

Java 8 发行之前的主要类是:

  • System.currentTimeMillis():表示自 1970 年 1 月 1 日起的当前日期和时间(以毫秒为单位)。
  • java.util.Date:表示特定的时间瞬间,精度为毫秒。
  • java.util.Calendar:一个抽象类,提供用于在实例之间进行转换并以不同的方式操纵日历字段的方法。
  • java.text.SimpleDateFormat:一个具体的类,用于以对语言环境敏感的方式以及任何预定义以及任何用户定义的模式来格式化和解析日期。
  • java.util.TimeZone:代表时区偏移量,并且还计算出夏令时。

1.2 挑战

尽管这些 API 非常适合简单的用例,但 Java 社区仍在不断抱怨有效使用这些类的问题。 因此,其他许多第三方库(例如 Joda-TimeApache Commons 中的类)也更加受欢迎。

很少有挑战:

  • Date类应表示日期,但也表示具有小时,分钟和秒的实例。
  • 但是Date没有任何关联的时区。 它会自动选择默认时区。 您不能将日期表示为其他时区。
  • 类是可变的。 因此,这给开发人员在传递可以更改日期的函数之前克隆日期增加了负担。
  • 日期格式化类也不是线程安全的。 如果没有其他同步,则无法使用格式化器实例,否则代码可能会中断。
  • 出于某种原因,还有另一种java.sql.Date,其中包含时区信息。
  • 使用其他时区创建日期非常棘手,通常会导致错误的结果。
  • 它的类使用零索引数月,这是多年来应用程序中许多错误的原因。

2. Java 8 中的新日期时间 API

新的日期 api 尝试解决旧类的上述问题。 它主要包含以下类:

  • java.time.LocalDate:表示 ISO 日历中的年-月-日,对于表示没有时间的日期很有用。 它可用于表示仅日期的信息,例如出生日期或结婚日期。
  • java.time.LocalTime:仅及时处理。 这对于表示基于人的时间(例如电影时间或本映射书馆的开放和关闭时间)很有用。
  • java.time.LocalDateTime:处理日期和时间,没有时区。 它是LocalDateLocalTime的组合。
  • java.time.ZonedDateTime:将LocalDateTime类与ZoneId类中给出的区域信息组合在一起。 它代表完整的日期时间戳以及时区信息。
  • java.time.OffsetTime:处理时间与格林威治/ UTC 有相应时区偏移的时间,没有时区 ID。
  • java.time.OffsetDateTime:处理具有与格林威治/ UTC 相对应的时区偏移的日期和时间,没有时区 ID。
  • java.time.Clock:提供对任何给定时区中的当前时刻,日期和时间的访问。 尽管使用Clock类是可选的,但是此特性使我们可以测试其他时区的代码,也可以使用时间不可变的固定时钟来测试您的代码。
  • java.time.Instant:表示时间轴上的纳秒的开始(自纪元开始),对于生成表示机器时间的时间戳很有用。 在纪元之前发生的瞬间具有负值,在纪元之后发生的瞬间具有正值。
  • java.time.Duration:两个瞬间之间的差异,以秒或纳秒为单位,并且不使用基于日期的构造,例如年,月和日,尽管该类提供了转换为天,小时和分钟的方法。
  • java.time.Period:以基于日期的值(年,月,日)定义日期之间的差异。
  • java.time.ZoneId:指定时区标识符,并提供用于在InstantLocalDateTime之间进行转换的规则。
  • java.time.ZoneOffset:指定与格林威治/ UTC 时间的时区偏移。
  • java.time.format.DateTimeFormatter:提供许多预定义的格式化器,或者我们可以定义自己的格式化器。 它提供parse()format()方法来解析和格式化日期时间值。

3. 常见用例

这些示例使用 Java 8 日期时间 API 中引入的新类。

3.1 获取当前日期和时间

所有日期时间类都有一个工厂方法now(),这是在 Java 8 中获取当前日期和时间的首选方法。

LocalTime currentTime = LocalTime.now();				//13:33:43.557

LocalDate currentDate = LocalDate.now();				//2020-05-03

LocalDateTime currentDateTime = LocalDateTime.now();	//2020-05-03T13:33:43.557

3.2 解析日期和时间

日期时间类中的DateTimeFormatter类和parse()方法帮助完成日期解析。

String dateString = "2020-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime parsedDateTime = LocalDateTime.parse(dateString, formatter);

System.out.println(parsedDateTime);		//2020-04-08T12:30

3.3 格式化日期和时间

日期格式通过日期时间类中的DateTimeFormatter类和format()方法来完成。

//Format a date
LocalDateTime myDateObj = LocalDateTime.now();
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");

String formattedDate = myDateObj.format(myFormatObj);
System.out.println(formattedDate);	//	03-05-2020 13:46

4. 更多例子

Java – 获取两个日期之间的所有日期

Java 8 - 获取下一个和上一个日期

Java 8 - 获取当前日期时间

Java 8 – 获取当前时间戳

Java 8 – 日期比较

Java 8 - 转换为 EST 时区

Java 8 – 测量经过时间

Java 8 – 向日期添加天数

Java 8 – 两个日期之间的差异

Java 8 – 两个日期之间的差异

Java 8 – 加或减 N 个工作日

Java 8 – 计算两个日期之间的工作日

Java 8 – 计算两个日期之间的天数

Java – 获取当前用户区域设置

Java – 检查给定的年份是否为闰年?

Java – 将字符串解析为日期

Java – 将XMLGregorianCalendar格式设置为MM/dd/yyyy hh:mm:ss模式

Java – 将XMLGregorianCalendar格式化为字符串

Java – 以 12 小时格式格式化日期/时间戳

Java – 时区转换

Java – 严格日期验证

学习愉快!

下载源码

Java – 日期验证

原文: https://howtodoinjava.com/java/date-time/date-validation/

学习验证给定的字符串是否包含日期值。 我们将学习 Java 7, Java 8 及更高版本中可用的各种日期验证技术。

1. DateTimeFormatter – Java 8

  • 最好的机会是您的应用程序已经在使用 Java 8 或更高版本。 在这种情况下,用 Java 表示日期的最佳方法是使用LocalDate类。
  • 默认情况下,LocalDate.parse()方法使用ISO_LOCAL_DATE模式(yyyy-MM-dd)解析日期。 格式包括:
    • 年份的四位数或更多,其中 0000 到 9999 的范围将被补零,以确保四位数。 超出该范围的年份将带有前缀正号或负号。
    • 一年中的两位数字,并用零预填充以确保两位数字。
    • 每月中的两位数字,并用零预填充以确保两位数字。
  • 如果我们有自己的自定义日期格式,则可以使用DateTimeFormatter.ofPattern()方法创建。 默认情况下,ResolverStyle.SMART用于每个日期字段使用明智的默认值。 例如,“天”字段的值大于 31 将是有效值,并将其视为月份的最后一天。
  • 很多时候,这种智能解决方案无法满足业务需求,如果遇到此类无效值,我们希望引发解析异常。 使用ResolverStyle.STRICT严格解析日期和时间。 使用严格的分辨率将确保所有解析的值都在该字段的有效值的外部范围内。
  • 使用LocalDateparse(dateString, dateTimeFormatter)将字符串解析为Localdate实例。
  • DateTimeFormatter实例是线程安全且不可变的,因此我们只能为每个模式/应用程序创建一个实例,并在其他函数之间共享它。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;

public class JavaDateValidations 
{
	public static void main(String[] args) 
	{
		String dateFormat = "MM-dd-yyyy";
		String dateString = "05-26-2020";

		DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(dateFormat)
											.withResolverStyle(ResolverStyle.STRICT);

		LocalDate parsedLocalDate = validateAndParseDateJava8(dateString, dateFormatter);

		System.out.println(parsedLocalDate);
	}

	//Java 8 - Use DateTimeFormatter (thread-safe)
	public static LocalDate validateAndParseDateJava8(String dateStr, DateTimeFormatter dateFormatter) 
	{
		LocalDate date = null;
        try {
        	date = LocalDate.parse(dateStr, dateFormatter);
        } catch (DateTimeParseException e) {
        	e.printStackTrace();
        }
        return date;
    }
}

2. SimpleDateFormat – Java 7

  • 如果您仍然对 Java 7 感到震惊,并且由于某些旧应用程序的依赖性而无法升级,则可以使用SimpleDateFormat进行日期验证。
  • 尽管SimpleDateFormat不是线程安全的或不可变的,但它仍然可以很好地达到目的。 不要在具有同步功能的多线程环境中使用此类。
  • 不要忘记使用setLenient()方法来指定宽大因素。 使用宽大的解析,解析器可能会使用启发式方法来解释与该对象的格式不完全匹配的输入。 在严格分析的情况下,输入内容必须与此对象的格式匹配。
  • 然后,使用SimpleDateFormatparse(dateString)方法,该方法将引发受检的异常ParseException,该异常表示将日期字符串解析为java.util.Date对象时发生了一些错误。
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class JavaDateValidations 
{
	public static void main(String[] args) 
	{
		String dateFormat = "MM-dd-yyyy";
		String dateString = "05-26-2020";

		Date parsedDate = validateAndParseDateJava7(dateString, dateFormat);

		System.out.println(parsedDate);
	}

	//Java 7 - Use SimpleDateFormat (not thread-safe)
	public static Date validateAndParseDateJava7(String dateString, String dateFormat) {
		Date date = null;
        DateFormat sdf = new SimpleDateFormat(dateFormat);
        sdf.setLenient(false);
        try {
            date = sdf.parse(dateString);
        } catch (ParseException e) {
        	e.printStackTrace();
        }
        return date;
    }
}

3. 日期验证的最佳做法

以下是一些在 Java 中验证日期期间可以遵循的最佳实践。

  1. 尽管在 99% 的情况下不会有任何区别,但仍然可以考虑使用'uuuu'代替'yyyy'。 有关更多信息,请参考此 SO 帖子
  2. 使用相关方法(即sdf.setLenient(false)dtf.withResolverStyle(ResolverStyle.STRICT))使用严格的解析。
  3. 尽管严格的日期解析解决了大多数问题,但仍应考虑使用额外的检查 -- 例如,有效的解析日期必须在预定义的日期范围内。 批量分析对日期敏感的记录时,这可能非常有用。 例如,我们可以从大型 Excel 工作表中导入财务记录时使用这种类型的验证,因为手工工作出错的可能性很高。
  4. 如果您有将 Java 7 应用程序升级到 Java 8 的自由,请优先执行。 就性能而言,DateTimeFormatter的线程安全性和不变性是对SimpleDateFormat的巨大胜利。

在评论中给我您有关在 Java 中将字符串解析为日期对象的问题和建议。

学习愉快!

下载源码

Java – 日期格式

原文: https://howtodoinjava.com/java/date-time/java-date-formatting/

学习在 Java 8 中将日期格式化为字符串。我们将学习 Java 7 中的DateTimeFormatter自定义模式SimpleDateFormat内置模式

1. DateTimeFormatter – Java 8

在 Java 8 中,我们可以将DateTimeFormatter用于所有类型的日期和时间相关的格式化任务。 它是线程安全的不可变的 ,因此可以在并发环境中使用而没有风险。

1.1 日期格式示例

Java 8 示例,以所需的字符串模式格式化LocalDateTimeLocalDate实例。

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateFormatting 
{
	public static void main(String[] args) 
	{
		String dateTimeString = formatLocalDateTime(LocalDateTime.now()); 
		System.out.println(dateTimeString);  //2020-05-08 23:17:22 PM

		String dateString = formatLocalDate(LocalDate.now()); 
		System.out.println(dateString);  //2020-05-08
	}

	//Format LocalDateTime to String

	public static final String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss a"; 
	public static final DateTimeFormatter LDT_FOMATTER 
                      = DateTimeFormatter.ofPattern(TIMESTAMP_PATTERN);

	private static String formatLocalDateTime(LocalDateTime ldt)
	{
		return LDT_FOMATTER.format(ldt);
	}

	//Format LocalDate to String

	public static final String DATE_PATTERN = "yyyy-MM-dd"; 
	public static final DateTimeFormatter LD_FOMATTER 
                      = DateTimeFormatter.ofPattern(DATE_PATTERN);

	private static String formatLocalDate(LocalDate ld)
	{
		return LD_FOMATTER.format(ld);
	}
}

1.2 模式串

DateTimeFormatter提供了两种方法来定义模式:

  • 将近 15 种内置的预定义模式,例如 ISO_LOCAL_DATE2011-12-03)或ISO_OFFSET_DATE_TIME2011-12-03T10:15:30+01:00
  • 使用DateTimeFormatter.ofPattern(pattern)的任何自定义样式

定制模式串可以具有任意数量的具有自己含义的预定义字母和符号。 最常用的符号是:Y, M, D, h, m, s

另请注意,模式中字母的重复次数也具有不同的含义。 例如,MMM给出Jan,而MMMM给出January

让我们看看这些符号以供快速参考。

符号 含义 类型 示例
G 时代 String ADAnno Domini
y 时代中的一年 Year 2004 或 04
u 时代中的一年 Year y类似,但返回年份。
D 一年中的一天 Number 235
M / L 一年中的一月 Number/String 7 或 07; JJulJuly
d 一月中的一天 Number 21
Q / q 一年中的季度 Number / String 3 或 03;Q33rd quarter
Y 基于周的年份 Year 1996 或 96
w 一年中的一周 Number 32
W 一月中的一周 Number 3
e / c 本地化一周中的一天 Number/String 2 或 02;TTueTuesday
E 一周中的一天 String TTueTuesday
F 一月中的一周 Number 3
a 一天的上午/下午 String PM
h 上午/下午的小时(1-12) Number 12
K 上午/下午的小时(0-11) Number 0
k 一天中的小时(1-24) Number 15
H 一天中的小时(0-23) Number 15
m 小时中的分钟 Number 30
s 分钟中的秒钟 Number 55
S 秒钟的小数 Fraction 978
A 一天中的毫秒 Number 1234
n 秒钟中的纳秒 Number 987654321
N 一天中的纳秒 Number 1234560000
V 时区编号 Zone-id America/Los_AngelesZ–08:30
z 时区名称 Zone-name Pacific Standard TimePST
X 区域偏移(Z代表零) Offset-X Z–08–0830–08:30–083015–08:30:15
x 区域偏移 Offset-x +0000–08–0830–08:30–083015–08:30:15
Z 区域偏移 Offset-Z +0000–0800–08:00
O 本地区域偏移 Offset-O GMT+8GMT+08:00UTC–08:00
p 填充 填充修饰符 1

1.3 UnsupportedTemporalTypeException

如果我们尝试将DateTimeFormatter与日期时间实例不支持的模式一起使用,则其format()将引发此异常。

例如,如果我们尝试使用包含小时和分钟的模式格式化LocalDate,则将引发此异常,因为LocalDate不支持任何时间信息。

public static final String TIMESTAMP_PATTERN 
						= "yyyy-MM-dd HH:mm:ss a"; 
public static final DateTimeFormatter FOMATTER 
						= DateTimeFormatter.ofPattern(TIMESTAMP_PATTERN);

String formmatedString = FOMATTER.format( LocalDate.now() );

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay
	at java.base/java.time.LocalDate.get0(LocalDate.java:709)
	at java.base/java.time.LocalDate.getLong(LocalDate.java:688)
	at java.base/java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:308)
	at java.base/java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2704)
	at java.base/java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2343)
	at java.base/java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1847)
	at java.base/java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1821)
	at com.howtodoinjava.core.datetime.DateFormatting.formatLocalDate(DateFormatting.java:33)
	at com.howtodoinjava.core.datetime.DateFormatting.main(DateFormatting.java:21)

2. SimpleDateFormat – Java 7

如果您仍然停留在 Java 7 上并且由于某些旧版应用程序的依赖性而无法升级,则可以使用SimpleDateFormat进行日期格式化。

尽管SimpleDateFormat不是线程安全的或不可变的,但它仍然可以很好地达到目的。 不要在具有同步功能的多线程环境中使用此类。

import java.text.SimpleDateFormat;
import java.util.Date;

public class JavaDateValidations 
{
	public static final String TIMESTAMP_PATTERN 
							= "yyyy-MM-dd HH:mm:ss a"; 

	public static void main(String[] args) 
	{
		SimpleDateFormat sdf = new SimpleDateFormat(TIMESTAMP_PATTERN);

		Date date = new Date();

		String formattedDate = sdf.format(date);
		System.out.println(formattedDate);		//2020-05-09 00:32:28 AM
	}
}

3. 总结

如果您有将 Java 7 应用程序升级到 Java 8 的自由,请优先执行。 就性能而言,DateTimeFormatter的线程安全性和不变性是对SimpleDateFormat的巨大胜利。

这两个类都提供了format()示例,用于将日期对象格式化为字符串。

在评论中给我您有关 Java 日期格式化器示例的问题和建议。

学习愉快!

下载源码

Java LocalDate

原文: https://howtodoinjava.com/java/date-time/java-time-localdate-class/

Java 8 中引入java.time.LocalDate类,表示没有时区和时间的本地日期,例如2019-03-27。 它具有从一天中时刻开始的时间部分,即分钟和秒被视为零。

我们可以使用LocalDate实例来表示一天中没有一天中特定时间的日期,例如生日,假期或员工休假。

java.util.Datejava.time.LocalDate之间的主要区别是LocalDate实例是不可变的线程安全

1. 类声明

public final class LocalDate
	extends Object
	implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable
{
	//class body
}

2. 如何创建LocalDate

通常,我们将在两种情况下创建本地日期实例,即获取当前日期或为指定日期创建本地日期。

2.1 获取当前的本地日期

使用以下方法获取当前的本地日期。

LocalDate today = LocalDate.now();	//1 - Recommended

LocalDate today = LocalDate.ofInstant(Instant.now(), ZoneId.systemDefault());	//

2.2 创建指定的本地日期

要创建具有特定日期,月份和年份的本地日期,请使用以下方法。

LocalDate dateInstance = LocalDate.of(2019, 3, 27);

LocalDate dateInstance = LocalDate.parse("2019-03-27");

3. 如何将字符串解析为LocalDate

LocalDate类具有两个重载的parse()方法,用于将字符串日期转换为本地日期实例。

parse(CharSequence text)	//1

parse(CharSequence text, DateTimeFormatter formatter)	//2

  • 如果字符串包含ISO_LOCAL_DATE模式中的日期,即yyyy-MM-dd,请使用第一种方法。 这是 Java 中本地日期的默认模式
  • 对于其他任何日期模式,我们都需要使用第二种方法传递日期字符串以及表示该日期字符串的格式化器。
//1 - default date pattern
String date = "2019-03-23";
LocalDate localDate = LocalDate.parse(date);

//2 - specific date pattern
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MMM-yyyy");
String date = "23-Mar-2019";
LocalDate localDate = LocalDate.parse(date, formatter);

4. 如何将LocalDate格式化为字符串

使用LocalDate.format(DateTimeFormatter)方法将本地日期格式化为所需的字符串表示形式。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MMM-yyyy");

LocalDate today = LocalDate.now();

String dateString = today.format(formatter);	//23-Mar-2019

5. 如何修改本地日期

LocalDate提供以下方法,这些方法可用于获取相对于可用本地日期对象的新本地日期实例。

  • plusDays()
  • plusWeeks()
  • plusMonths()
  • plusYears()
  • minusDays()
  • minusWeeks()
  • minusMonths()
  • minusYears()
LocalDate today = LocalDate.now();

//Same date 3 years later
LocalDate localDate1 = today.plusYears(3);	

//local date before 3 months
LocalDate localDate2 = today.minusMonths(3);

6. Java LocalDate示例

通过一些示例,让我们更好地了解LocalDate类。

学习愉快!

参考: LocalDate Java 文档

Java LocalTime

原文: https://howtodoinjava.com/java/date-time/java-localtime/

Java 8 中引入java.time.LocalTime类,将没有日期或时区信息的本地时间对象表示为“小时-分-秒”成分。 它表示达到纳秒精度的时间,例如 09:25:59.123456789

我们可以使用LocalTime实例来表示时间,而无需使用如墙上时钟所示的日期或时区参考。 例如,我们可以使用LocalTime表示LocalTime类,以捕获员工何时进入办公大楼以及何时离开。

注意,LocalTime实例是不可变的线程安全

1. LocalTime类声明

public final class LocalTime
	extends Object
	implements Temporal, TemporalAdjuster, Comparable<LocalTime>, Serializable
{
	//class body
}

2. 如何在 Java 中创建LocalTime

通常,我们将在两种情况下创建本地时间实例,即获取当前时间或为指定时间戳创建本地时间。

2.1 获取当前本地时间

使用以下方法获取当前本地时间。

LocalTime now = LocalTime.now();

2.2 创建指定的本地时间

要创建具有特定小时,分钟和秒的本地时间,请使用以下方法。

LocalTime ltObject1 = LocalTime.of(08, 20, 45);	

LocalTime ltObject2 = LocalTime.of(08, 20, 45, 123456789);

LocalTime ltObject3 = LocalTime.parse("08:20");

LocalTime ltObject4 = LocalTime.parse("08:20:45.123456789");

3. 如何将字符串解析为LocalTime

LocalTime类具有两个重载的parse()方法,用于将字符串中的时间转换为本地时间实例。

parse(CharSequence text)	//1

parse(CharSequence text, DateTimeFormatter formatter)	//2

  • 如果字符串包含ISO_LOCAL_TIME模式中的时间,即HH:mm:ss,请使用第一种方法。 这是 Java 中本地时间的默认模式。
  • 对于任何其他时间模式,我们都需要使用第二种方法,在该方法中,我们将时间作为字符串传递,并使用表示该时间字符串的模式的格式化器。
//1 - default time pattern
String time = "08:20:45.123456789";
LocalTime localTimeObj = LocalTime.parse(time);

//2 - specific time pattern
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH.mm.ss.nnn");
String time = "08.20.45.123456789";
LocalTime localTimeObj = LocalTime.parse(time, formatter);

4. 如何将LocalTime格式化为字符串

使用LocalTime.format(DateTimeFormatter)方法将本地时间格式化为所需的字符串表示形式。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH.mm");

LocalTime today = LocalTime.now();

String timeString = today.format(formatter);	//12.38

5. 如何修改本地时间

LocalTime提供以下方法,可用于相对于可用本地时间实例获取新的本地时间实例。

  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()
LocalTime now = LocalTime.now();

//3 hours later
LocalTime localTime1 = now.plusHours(3);	

//3 minutes earliar
LocalTime localTime2 = now.minusMinutes(3);

6. 更多例子

LocalTimejava.sql.Time之间进行转换

学习愉快!

参考: LocalTime Java 文档

Java LocalDateTime

原文: https://howtodoinjava.com/java/date-time/java-localdatetime-class/

Java 8 中引入java.time.LocalDateTime类,表示没有时区信息的本地日期时间对象。 它表示达到纳秒精度的时间,例如 2007-12-03T10:15:30:55.123456789

我们可以使用LocalDateTime实例来表示时间,而无需任何时区引用。 例如,我们可以使用LocalDateTime在任何应用程序中触发批处理作业。 作业将在服务器所在的时区中的特定时间执行。

实际上,为了使其更有用,我们应该将LocalDateTime与其他信息(例如偏移或时区)一起使用。

注意,LocalDateTime实例是不可变的线程安全

1. LocalDateTime类声明

public final class LocalDateTime
	extends Object
	implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable
{
	//class body
}

2. 如何创建LocalDateTime

通常,我们将在两种情况下创建LocalDateTime实例,即获取当前时间或为指定时间戳创建本地日期时间。

2.1 获取当前时间

使用以下工厂方法获取当前本地日期时间。

LocalDateTime now = LocalDateTime.now();

2.2 创建指定时间

要创建具有特定日期和时间信息的本地时间,请使用以下方法。

//Milliseconds precision
LocalDateTime localDateTime1 = 
		LocalDateTime.of(2019, 03, 28, 14, 33, 48, 123456789);

//Month enum
LocalDateTime localDateTime2 = 
		LocalDateTime.of(2019, Month.MARCH, 28, 14, 33, 48, 123456789);

//seconds precision
LocalDateTime localDateTime3 = 
		LocalDateTime.of(2019, Month.MARCH, 28, 14, 33, 48);

//minutes precision
LocalDateTime localDateTime4 = 
		LocalDateTime.of(2019, Month.MARCH, 28, 14, 33);

//local date + local time
LocalDate date = LocalDate.of(2109, 03, 28);
LocalTime time = LocalTime.of(10, 34);	

LocalDateTime localDateTime5 = LocalDateTime.of(date, time);	

3. 如何将字符串解析为LocalDateTime

LocalDateTime类具有两个重载的parse()方法,用于将字符串中的时间转换为本地时间实例。

parse(CharSequence text)	//1

parse(CharSequence text, DateTimeFormatter formatter)	//2

  • 如果字符串包含ISO_LOCAL_DATE_TIME模式中的时间,即2019-03-27T10:15:30,请使用第一种方法。 这是 Java 中本地时间的默认模式。
  • 对于其他任何日期时间模式,我们都需要使用第二种方法,在该方法中,我们将时间作为字符串传递,并且使用格式化器来表示该日期时间字符串的模式。
//1 - default time pattern
String time = "2019-03-27T10:15:30";
LocalDateTime localTimeObj = LocalDateTime.parse(time);

//2 - specific date time pattern
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss a");
String time1 = "2019-03-27 10:15:30 AM";
LocalDateTime localTimeObj1 = LocalDateTime.parse(time1, formatter);

4. 如何将LocalDateTime格式化为字符串

使用LocalDateTime.format(DateTimeFormatter)方法将本地时间格式化为所需的字符串表示形式。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss a");

LocalDateTime now = LocalDateTime.now();

String dateTimeString = now.format(formatter);	//2019-03-28 14:47:33 PM

5. 如何修改本地时间

LocalDateTime提供以下方法,可用于相对于可用的localdatetime实例获取新的localdatetime实例。

  • plusYears()
  • plusMonths()
  • plusDays()
  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusYears()
  • minusMonths()
  • minusDays()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()
LocalDateTime now = LocalDateTime.now();

//3 hours later
LocalDateTime localDateTime1 = now.plusHours(3);	

//3 minutes earliar
LocalDateTime localDateTime2 = now.minusMinutes(3);

//Next year same time
LocalDateTime localDateTime2 = now.plusYears(1);

//Last year same time
LocalDateTime localDateTime2 = now.minusYears(1);

6. LocalDateTime示例

比较LocalDateTime实例

LocalDateTime和日期之间转换

LocalDateTimeZonedDateTime之间转换

LocalDateTime格式转换为字符串

将字符串解析为LocalDateTime

在 Java 中将LocalDate转换为LocalDateTime

在评论中向我发送有关 Java 8 LocalDateTime类的问题。

学习愉快!

参考: LocalDateTime Java 文档

Java ZonedDateTime

原文: https://howtodoinjava.com/java/date-time/zoneddatetime-class/

Java 8 中引入java.time.ZonedDateTime类表示 ISO-8601 日历系统中具有时区信息的日期和时间。 此类存储所有日期和时间字段,精度为纳秒。

我们可以使用ZonedDateTime实例来代表全球分布的用户的时间。 例如,我们可以使用ZonedDateTime传达会议日期,参加者将根据他们的本地日期和时间在线连接。

ZonedDateTime的状态等于三个单独的对象:LocalDateTimeZoneId和已解析的ZoneOffset

注意,ZonedDateTime实例是不可变的线程安全

1. ZonedDateTime类声明

public final class ZonedDateTime
	extends Object
	implements Temporal, ChronoZonedDateTime<LocalDate>, Serializable
{
	//class body
}

2. 如何创建ZonedDateTime

通常,我们将在两种情况下创建ZonedDateTime实例,即获取具有区域信息的当前时间戳或创建代表特定时区的时间戳。

2.1 获取当前的ZonedDateTime

使用以下工厂方法获取当前时间戳。

ZonedDateTime now = ZonedDateTime.now();

ZonedDateTime now = ZonedDateTime.now( ZoneId.of("GMT+05:30") ); //Time in IST

2.2 创建指定的ZonedDateTime

要创建带有特定日期,时间和区域信息的时间戳,请使用以下方法。

//1 - All date parts are inplace
ZoneId zoneId = ZoneId.of("UTC+1");

ZonedDateTime zonedDateTime1 =
    ZonedDateTime.of(2015, 11, 30, 23, 45, 59, 1234, zoneId);

//=========

//2 - Using existing local date and time values 
LocalDate localDate = LocalDate.of(2019, 03, 12);
LocalTime localTime = LocalTime.of(12,  44);
ZoneId zoneId = ZoneId.of("GMT+05:30");

ZonedDateTime timeStamp = ZonedDateTime.of( localDate, localTime, zoneId );

3. 如何将字符串解析为ZonedDateTime

ZonedDateTime类具有两个重载的parse()方法,用于将字符串中的时间转换为本地时间实例。

parse(CharSequence text)	//1

parse(CharSequence text, DateTimeFormatter formatter)	//2

  • 如果字符串以ISO_ZONED_DATE_TIME模式包含时间,即2019-03-28T10:15:30+01:00[Europe/Paris]则使用第一种方法。 这是默认模式。
  • 对于任何其他日期时间模式,我们都需要使用第二种方法,在该方法中我们将日期时间作为字符串传递,并使用格式化器来表示该日期时间字符串的模式。
//1 - default pattern
String timeStamp = "2019-03-27T10:15:30";
ZonedDateTime localTimeObj = ZonedDateTime.parse(time);

//2 - specified pattern
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss a");
String timeStamp1 = "2019-03-27 10:15:30 AM";
ZonedDateTime localTimeObj1 = ZonedDateTime.parse(timeStamp1, formatter);

4. 如何将ZonedDateTime格式化为字符串

使用ZonedDateTime.format(DateTimeFormatter)方法将本地时间格式化为所需的字符串表示形式。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss a");

ZonedDateTime now = ZonedDateTime.now();

String dateTimeString = now.format(formatter);	//2019-03-28 14:47:33 PM

5. 如何修改ZonedDateTime

ZonedDateTime提供以下修改方法。 所有方法都返回ZonedDateTime的新实例,因为现有实例始终是不可变的。

  • plusYears()
  • plusMonths()
  • plusDays()
  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusYears()
  • minusMonths()
  • minusDays()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()
ZonedDateTime now = ZonedDateTime.now();

//3 hours later
ZonedDateTime zonedDateTime1 = now.plusHours(3);	

//3 minutes earliar
ZonedDateTime zonedDateTime2 = now.minusMinutes(3);

//Next year same time
ZonedDateTime zonedDateTime2 = now.plusYears(1);

//Last year same time
ZonedDateTime zonedDateTime2 = now.minusYears(1);

6. 更多例子

ZonedDateTime格式化为字符串

将字符串解析为ZonedDateTime

在 Java 中将LocalDate转换为ZonedDateTime

ZonedDateTime时区转换示例

比较ZonedDateTime实例

Java 8 – 将字符串解析为 UTC 中的日期时间

在评论中向我发送有关 Java 8 ZonedDateTime类的问题。

学习愉快!

参考: ZonedDateTime Java 文档

核心 Java 教程

Java 注释

原文: https://howtodoinjava.com/java/basics/java-comments/

了解有关 Java 注释Java 注释的类型Javadoc 工具,注释的性能影响和需要遵循的最佳做法的所有信息。

1. 为什么要编写 Java 注释?

顾名思义, Java 注释是出于各种原因在程序之间编写的注释。 例如,您可以将注释写给:

  • 编写有关变量,方法,类或任何语句的信息或说明。
  • 编写可在 Java 文档中使用的文本。
  • 隐藏特定时间的程序代码,等等。

1.1 Java 注释示例

给定的代码是 Java 注释的示例,以及如何使用它们。

/**
 * Contains method to greet users by their name and location.
 * 
 * @author Lokesh Gupta
 */
public class Main {

	/**
	 * Launches the application
	 * 
	 * @param args - Application startup arguments
	 */
	public static void main(String[] args) {
		getMessage("Lokesh", "India");
	}

	/**
	 * Returns welcome message for a customer by customer name and location
	 * 
	 * @param name - Name of the visitor
	 * @param region - Location
	 * @return - Welcome message
	 */
	public static String getMessage (String name, String region) 
	{
		StringBuilder builder = new StringBuilder();
		builder.append("Hello ");
		builder.append(name);
		builder.append(", Welcome to ");
		builder.append(region);
		builder.append(" !!");
		return builder.toString();
	}
}

2. Java 注释的类型

Java 中有 3 种类型的注释

  1. 单行注释

    当注释只能写在一行中时,请使用单行注释。 这些注释是通过 Java 语句编写的,以阐明它们在做什么。

    //Initialize the counter variable to 0
    int counter = 0;
    
    
  2. 多行注释

    当您需要在源代码中添加超过一行的信息时,请使用多行注释。 多行注释通常用于代码块上方,这些代码块具有无法单行编写的复杂逻辑。

    /*
     * This function returns a variable which shall be used as a counter for any loop.
     * Counter variable is of integer type and should not be reset during execution.
     */
    public int getCounter() {
    	//
    }
    
    
  3. 文档注释

    当您想公开要由javadoc工具获取的信息时,将使用文档注释。 这是您在使用自动完成特性时在编辑器(例如,日食)中看到的信息。 这些注释位于类,接口和方法定义之上。

    文档注释以/**开头,以*/结尾。

    您可以在这些注释中使用 javadoc 注解,例如 @param@return

    /**
      * This function returns a variable which shall be used as a counter for any loop.
      * Counter variable is of integer type and should not be reset during execution.
      *
      * @param seed - initial value of the counter
      * @return counter value
      */
    public int getCounter(int seed) {
    	//
    }
    
    

    文档注释是编程不可或缺的一部分,不应跳过。

3. 注释快捷方式

在 Eclipse IDE 中,只需在公共方法或类之前键入/**[Enter],它将自动在所有必要的@param@author@return属性中生成。

Java Comment shortcut in eclipse

Eclipse 中的 Java 注释快捷方式

4. javadoc工具

javadoc 工具与 JDK 发行版捆绑在一起。 它将它们转换为标准化,格式清晰,易于阅读的网页。 它从文档注释生成 API 文档

4.1 从命令提示符运行javadoc

首先,确保javadoc工具位于路径中。 如果没有,则将 JDK /bin文件夹添加到PATH变量。

$ set PATH=.;C:\BAML\DFCCUI\installs\jdk1.8.0_31\bin

要生成 Java 文档,请执行带有两个参数的工具。 首先是生成的 Java 文档的位置,其次是 Java 源文件。 在我们的情况下,我从Main.java所在的位置执行了此命令。

$ javadoc -d C:/temp Main.java

它生成了这些 Java 文档 HTML 文件。

Generated Java docs - 2

生成的 Java 文档

4.2 从 Eclipse 运行javadoc

您也可以从 Eclipse IDE 生成 Java 文档。 遵循以下简单步骤:

  1. 包浏览器中,右键单击所需的项目/包。

  2. 选择Export.../Javadoc并点击Next.

    Export Java Doc Option

    导出 Java 文档选项

  3. 默认情况下,将选择整个源代码。 验证并更改您的选择。

    Java Doc Options in Eclipse

    Eclipse 中的 Java Doc 选项

  4. 您可以选择“ Private”来生成可见性级别。 这将生成所有可能的 Javadocs,即使是私有方法也是如此。

  5. 选择“ standard doclet”,它是文档的目标文件夹。

  6. 点击Next

  7. 输入一个有意义的Document title并单击Finish

如果正确执行上述所有步骤,则将生成与我们在命令提示符选项中看到的类似的 Java 文档文件。

5. Java 注释对性能的影响

Java 代码中的实现注释仅供人们阅读。 Java 注释是编译器未编译的语句,因此它们不包含在已编译的字节码(.class文件)中。

这就是 Java 注释对应用程序性能也没有影响的原因。

6. Java 注释最佳实践

请遵循这些最佳做法在您的应用程序源代码中包含适当的注释。

  1. 不要在源代码中使用不必要的注释。 如果您的代码需要比正常解释更多的内容,则可以重构您的代码。
  2. 保持注释缩进一致并匹配以实现最佳可读性。
  3. 注释是针对人类的,因此请使用简单的语言进行解释。
  4. 始终在源代码中添加文档注释。

学习愉快!

阅读更多:

Oracle 针对 Javadoc 的建议

Javadoc 标记参考

Java 8 – Period

原文: https://howtodoinjava.com/java/date-time/java8-period/

ISO-8601 日历系统中,使用 Java 8 Period类,在基于日期的值(例如天,月,年,周或年)中学习查找两个日期之间的差异

1. Period

Period类用于使用 ISO-8601 时间段格式PnYnMnDPnW中基于日期的值来表示时间量。 例如,P20Y2M25D字符串代表 20 年,2 个月和 25 天。

此时间段可以通过不同方式获得。

1.1 Period.between()

通常,Period用于表示两个日期之间的时间段(例如,两个LocalDate实例之间的时间段)。

LocalDate startLocalDate = LocalDate.of(2020, 3, 12);
LocalDate endLocalDate = LocalDate.of(2020, 7, 20);

Period periodBetween = Period.between(startLocalDate, endLocalDate);
System.out.println(periodBetween);	// P4M8D - 4 months and 8 days

System.out.println(periodBetween.getDays());		//8
System.out.println(periodBetween.getMonths());		//4
System.out.println(periodBetween.getYears());		//0

System.out.println(periodBetween.get(ChronoUnit.DAYS));	//8

1.2 Period.ofDays()

Period分类以下方法,以不同单位表示时间段:

  • ofDays(int days) – 表示天数的时间段。
  • ofMonths(int months) – 表示月数的时间段。
  • ofWeeks(int weeks) – 表示周数的时间段。
  • ofYears(int years) – 表示年数的时间段。
Period fromDays = Period.ofDays(150);	// 150 days
Period fromMonths = Period.ofMonths(4);	// 4 months
Period fromYears = Period.ofYears(10);	// 10 years
Period fromWeeks = Period.ofWeeks(15);	// 15 weeks

1.3 Period.of()

使用of(int years, int months, int days),我们可以获得基于年,月和日的实例。

//20 years, 3 months and 20 days
Period periodFromUnits = Period.of(20, 3, 20);

1.4 Period.parse()

可以从包含 ISO-8601 时间段格式的String中获得Period

//20 years, 3 months and 20 days
Period periodFromString1 = Period.parse("P20Y3M20D");

//365 Days
Period periodFromString2 = Period.parse("P365D");

//52 Weeks
Period periodFromString3 = Period.parse("P52W");

2. 获取Period

时间段值可以通过获取器方法获得:

  • Period.getDays() - 获取此时间段的天数。
  • Period.getMonths() - 获取此时间段的月数。
  • Period.getYears() - 获取此时间段的年数。
  • Period.get(TemporalUnit unit) - 获取所请求单位的值。 请注意,支持的单位是YEARS, MONTHS, DAYS。 所有其他单元都抛出UnsupportedTemporalTypeException
LocalDate startLocalDate = LocalDate.of(2020, 3, 12);
LocalDate endLocalDate = LocalDate.of(2020, 7, 20);

Period periodBetween = Period.between(startLocalDate, endLocalDate);

System.out.println(periodBetween.getDays());		//8
System.out.println(periodBetween.getMonths());		//4
System.out.println(periodBetween.getYears());		//0

System.out.println(periodBetween.get(ChronoUnit.DAYS));	//8

//Throws UnsupportedTemporalTypeException
System.out.println(periodBetween.get(ChronoUnit.WEEKS));	

3. 修改Period

我们可以从给定的Period对象中添加或减去一段时间。 支持加减法的方法有:

  • add(Period) – 返回给定时间段的副本,其中添加了指定的时间段。
  • plusYears() – 返回给定时间段的副本,其中添加了指定的年份。
  • plusMonths() – 返回给定时间段的副本,其中添加了指定的月份。
  • plusDays() – 返回给定时间段的副本,其中添加了指定的日期。
  • minus(period)-返回给定时间段的副本,减去指定时间段。
  • minusYears()-返回给定时间段的副本,其中减去指定的年份。
  • minusMonths() – 返回给定时间段的副本,其中减去指定的月份。
  • minusDays() – 返回给定时间段的副本,其中减去指定天数。
  • multipliedBy(scalar) – 返回一个新实例,该实例中的每个元素都乘以指定的标量。
Period period = Period.ofDays(5);

Period periodDaysAdded = period.plus(5);
Period periodPlus1Year = period.plusYears(1L);

在评论中向我发送有关将 Java 8 时间段用于日期差异的问题和建议。

学习愉快!

下载源码

Java 8 DateTimeFormatter

原文: https://howtodoinjava.com/java/date-time/java8-datetimeformatter-example/

Java 示例,可使用DateTimeFormatterZonedDateTimeLocalDateTimeLocalDateLocalTime格式化为具有预定义和自定义模式的字符串。

1. 创建DateTimeFormatter

您可以通过两种方式创建DateTimeFormatter

  1. 使用内置常量
  2. 使用ofPattern()方法创建自己的模式
//Use inbuilt pattern constants
DateTimeFormatter inBuiltFormatter1 = DateTimeFormatter.ISO_DATE_TIME;

DateTimeFormatter inBuiltFormatter2 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

//Define your own custom patterns
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy 'at' hh:mma z");

2. 格式化ZonedDateTime

Java 示例,将ZonedDateTime格式化为带有DateTimeFormatter的字符串。

//Create formatter
DateTimeFormatter FOMATTER = DateTimeFormatter.ofPattern("MM/dd/yyyy 'at' hh:mm a z");

//Zoned datetime instance
ZonedDateTime zdt = ZonedDateTime.now();

//Get formatted String
String zdtString = FOMATTER.format(zdt);

System.out.println(zdtString);		// 07/15/2018 at 02:51 PM IST

3. 格式化LocalDateTime

LocalDate没有时区部分。 因此,相应地创建模式。

//Create formatter
DateTimeFormatter FOMATTER = DateTimeFormatter.ofPattern("MM/dd/yyyy 'at' hh:mm a");

//Local date time instance
LocalDateTime localDateTime = LocalDateTime.now();

//Get formatted String
String ldtString = FOMATTER.format(localDateTime);

System.out.println(ldtString);		// 07/15/2018 at 02:49 PM

4. 格式化LocalDate

LocalDate没有时间和时区部分。 因此,相应地创建模式。

//Create formatter
DateTimeFormatter FOMATTER = DateTimeFormatter.ofPattern("MM/dd/yyyy");

//Local date instance
LocalDate localDate = LocalDate.now();

//Get formatted String
String dateString = FOMATTER.format(localDate);

System.out.println(dateString);		//07/15/2018

5. 格式化LocalTime

LocalTime没有日期和时区部分。 因此,相应地创建模式。

//Create formatter
DateTimeFormatter FOMATTER = DateTimeFormatter.ofPattern("hh:mm a");

//Local time instance
LocalTime localTime = LocalTime.now();

//Get formatted String
String localTimeString = FOMATTER.format(localTime);

System.out.println(localTimeString);		// 02:53 PM

6. 有用的格式化模式

模式 示例
yyyy-MM-ddISO 2018-07-14
dd-MMM-yyyy 14-Jul-2018
dd/MM/yyyy 14/07/2018
E, MMM dd yyyy Sat, Jul 14 2018
h:mm a 12:08 PM
EEEE, MMM dd, yyyy HH:mm:ss a Saturday, Jul 14, 2018 14:31:06 PM
yyyy-MM-dd'T'HH:mm:ssZ 2018-07-14T14:31:30+0530
hh 'o''clock' a, zzzz 12 o’clock PM, Pacific Daylight Time
K:mm a, z 0:08 PM, PDT

7. 更多例子

  1. Java 8 ResolverStyle.STRICT – 严格的日期验证和解析

学习愉快!

参考文献:

  1. DateTimeFormatter Javadoc
  2. ZonedDateTime Javadoc
  3. LocalDateTime Javadoc
  4. LocalDate Javadoc
  5. LocalTime Javadoc

Java 8 – TemporalAdjusters

原文: https://howtodoinjava.com/java/date-time/java8-temporal-adjusters/

学习使用 Java 8 TemporalAdjusters,我们要在其中处理重复日期,例如处理每周报告,发送每月自动报告, 等等

1. TemporalAdjuster接口

TemporalAdjuster接口和TemporalAdjusters工厂类提供了许多有用的内置调节器,用于处理重复事件。 其中大多数名称直接告诉您它们的作用。

如果提供的调整器无法解决任何特定的业务需求,我们可以构建自己的自定义TemporalAdjuster

然后,我们可以使用该日期/时间对象的with()方法将其应用于任何时间对象。

例如,在团队中,总是每个星期一都有每周一次的会议。 我们想获取接下来的 5 次会议的日期列表。

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;

public class TemporalAdjusterExamples 
{
	public static void main(String[] args) 
	{
		LocalDate localDate = LocalDate.of(2020, 5, 9);

		List<LocalDate> meetingDates = getWeeklyMeetingDates(localDate, 5);
		System.out.println(meetingDates);
	}

	private static List<LocalDate> 
			getWeeklyMeetingDates(LocalDate localDate, int count) 
	{
		List<LocalDate> dates = new ArrayList<>();
		for(int i = 0; i < count; i++)
		{
			localDate = localDate
				.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
			dates.add(localDate);
		}
	    return dates;
	}
}

程序输出。

[2020-05-11, 
2020-05-18, 
2020-05-25, 
2020-06-01, 
2020-06-08]

2. 内置调节器

这是方便使用的默认提供的调节器的列表。 访问官方 Java 文档以获取详细信息。

调节器 描述
firstDayOfMonth() 返回一个新日期,将其设置为当前月份的第一天。
lastDayOfMonth() 返回一个新日期,将其设置为当前月份的最后一天。
firstDayOfNextMonth() 返回一个新日期,将其设置为下个月的第一天。
firstDayOfYear() 返回一个新日期,将其设置为当年的第一天。
lastDayOfYear() 返回一个新日期,将其设置为当年的最后一天。
firstDayOfNextYear() 返回一个新日期,将其设置为明年的第一天。
firstInMonth() 返回同月的第一个匹配日期的新日期。 例如“ 5 月的第一个星期三”。
lastInMonth() 返回与上一个匹配的星期几相同的月份中的新日期。
dayOfWeekInMonth() 返回与月份中的星期几相同的月份中的新日期。
next() 将日期返回到调整日期后指定的星期几的第一个匹配项。
previous() 将日期返回到调整日期之前指定的星期几的第一个匹配项。

3. 创建自定义的TemporalAdjuster

创建一个自定义调整器,该调整器可用于将重复日期调整为某些业务逻辑。 它可以通过两种方式完成:

  • 实现TemporalAdjuster接口

  • 内联 Lambda 表达式

    //Custom temporal adjuster with lambda
    TemporalAdjuster temporalAdjuster = t -> t.plus(Period.ofDays(7));
    
    //Custom temporal adjuster with TemporalAdjuster interface
    class NextBirthDay implements TemporalAdjuster 
    {
        @Override
        public Temporal adjustInto(Temporal temporal) 
        {
            return temporal.with(ChronoField.MONTH_OF_YEAR, 11)
            				.with(ChronoField.DAY_OF_MONTH, 22);
        }
    }
    
    

    在评论中向我发送有关将 Java 8 TemporalAdjuster用于日期操作的问题和建议。

    学习愉快!

    下载源码

Java 8 – TemporalQuery

原文: https://howtodoinjava.com/java/date-time/temporalquery/

TemporalQuery是查询临时对象以制定更好的业务决策的标准方法。 在 Java 8 中,所有主要的日期时间类都实现了TemporalTemporalAccessor接口,因此TemporalQuery可以针对所有这些 Java 类运行。

1. TemporalQuery接口

TemporalQuery是一个函数式接口,因此可以用作 lambda 表达式方法参考的分配目标。 方法queryForm()使用时间对象查询并返回查询的值。

该实现定义查询的逻辑,并负责记录该逻辑。 它可以使用TemporalAccessor上的任何方法来确定结果。

输入对象不得更改。

@FunctionalInterface
public interface TemporalQuery<R> 
{
	R queryFrom(TemporalAccessor temporal);
}

2. TemporalQuery示例

让我们看一些示例,以更好地了解此接口。

2.1 给定的时间在营业时间内?

我们可以使用TemporalQuery确定任何给定时间是否在一定范围内。 例如。 时间是否介于工作时间之间。

LocalTime now = LocalTime.now();

System.out.println("Currently Working :: " + now.query(WorkingHoursQuery));

private static final TemporalQuery<Boolean> WorkingHoursQuery = temporal -> {
	LocalTime t = LocalTime.from(temporal);
	return t.compareTo(LocalTime.of(9, 0)) >= 0 
			&& t.compareTo(LocalTime.of(17, 0)) < 0;
};

程序输出:

Currently Working :: false

2.2 获取当前财务季度

我们还可以使用TemporalQuery确定该年度的当前财务季度。 在下面的示例中,第一个财政季度被认为是从一月到三月。 更改方法实现以实现所需的行为。

LocalDate today = LocalDate.now();

System.out.println("Current Financial Quarter :: " + today.query(CurrentQuarterQuery));

private static final TemporalQuery<Integer> CurrentQuarterQuery = temporal -> {
	LocalDate date = LocalDate.from(temporal);
	return (date.getMonthValue() / 3) + 1;
};

程序输出:

Current Financial Quarter :: 2

请向我发送在 Java 8 中使用带有日期的TemporalQuery的有关问题。

学习愉快!

下载源码

Java 8 – DayOfWeek

原文: https://howtodoinjava.com/java/date-time/find-dayofweek/

Java 示例,用于确定星期几是给定日期。 工作日为星期日,星期一至星期六。

1. DayOfWeek枚举

DayOfWeek是代表一周中七天的枚举 - 星期一,星期二,星期三,星期四,星期五,星期六和星期日。

由于它是一个枚举,因此具有与每天相关的序数值。 从 1(星期一)到 7(星期日)。 某些语言环境还为日期分配了不同的数值,从而声明星期日为 1。但是,此类不支持此特性。

要获得数字表示,建议使用getValue()。 这是一个不可变的线程安全枚举。

2. 给定LocalDateDayOfWeek

LocalDate类具有方法getDayOfWeek(),该方法返回表示星期几的枚举值。

LocalDate today = LocalDate.now();

System.out.println( today.getDayOfWeek() );				// SUNDAY
System.out.println( today.getDayOfWeek().getValue() );	// 7

类似于LocalDate,其他时间类别也提供此方法。

3. 特定于语言环境的值

使用getDisplayName(TextStyle, Locale)以特定于语言环境的方式获取星期几的值。

public static void main(String[] args) 
{	
	String textValue = getDayString(today, Locale.getDefault());

	System.out.println(textValue);			// Sunday

	textValue = getDayString(today, Locale.GERMAN);

	System.out.println(textValue);			// Sonntag
}

public static String getDayString(LocalDate date, Locale locale) 
{
    DayOfWeek day = date.getDayOfWeek();
    return day.getDisplayName(TextStyle.FULL, locale);
}

将有关 Java 8 中星期几的的问题交给我

学习愉快!

下载源码

Java 日期 – 解析,格式和转换

原文: https://howtodoinjava.com/java/date-time/java-date-examples/

学习创建新日期获取当前日期将日期解析为字符串或格式化Date对象,使用java.util.Date类。 这些用例是经常需要的,将它们放在一个地方将有助于节省我们许多人的时间。

java date

Table of Contents

Format Date to string
Parse string to Date
Get current date
Get current time
Convert Calendar to Date
Get Date to Calendar
Compare between two dates
Get date parts (year, month, day of month)

将日期格式化为字符串

将日期格式化为字符串表示形式的示例。

SimpleDateFormat sdf = new SimpleDateFormat("dd/M/yyyy");
String date = sdf.format(new Date()); 
System.out.println(date); //Prints 26/10/2015

有关详细的日期和时间模式,请参考SimpleDateFormat JavaDoc。 以下是您可以使用的最常见的模式字母的列表。

y   = year   (yy or yyyy)
M   = month  (MM)
d   = day in month (dd)
h   = hour (0-12)  (hh)
H   = hour (0-23)  (HH)
m   = minute in hour (mm)
s   = seconds (ss)
S   = milliseconds (SSS)
z   = time zone  text        (e.g. Pacific Standard Time...)
Z   = time zone, time offset (e.g. -0800)

例如,

yyyy-MM-dd           	(2015-12-31)
dd-MM-YYYY           		(31-10-2015)
yyyy-MM-dd HH:mm:ss  		(2015-12-31 23:59:59)
HH:mm:ss.SSS         		(23:59.59.999)
yyyy-MM-dd HH:mm:ss.SSS   	(2015-12-31 23:59:59.999)
yyyy-MM-dd HH:mm:ss.SSS Z   (2015-12-31 23:59:59.999 +0100)

将字符串解析为日期

将字符串解析为日期对象的示例。

SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
String dateInString = "15-10-2015 10:20:56";
Date date = sdf.parse(dateInString);
System.out.println(date); //Prints Tue Oct 15 10:20:56 SGT 2015

获取当前日期

在 Java 中获取当前日期的示例

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
System.out.println(dateFormat.format(date)); //2015/10/26 12:10:39

在 Java 8 中,可以使用LocalDate类。

LocalDate today = LocalDate.now();
System.out.println("Today's Local date : " + today);

获取当前时间

在 Java 中获取当前时间的示例。

LocalTime time = LocalTime.now();
System.out.println("local time now : " + time);

将日历转换为日期

Calendar calendar = Calendar.getInstance();
Date date =  calendar.getTime();

将日期转换为日历

SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
String dateInString = "27-04-2016 10:22:56";
Date date = sdf.parse(dateInString);

Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

比较两个日期

比较两个日期实例的示例。

Date date1 = new Date();
Date date2 = new Date();

int comparison = date1.compareTo(date2);

日期之间的比较遵循Comparable接口的规则,这意味着compareTo()方法返回:

  1. 如果调用该方法的日期晚于作为参数给出的日期,则大于 0 的int
  2. 如果日期相等,则int值为 0。
  3. 如果调用该方法的日期早于作为参数给出的日期,则int值小于 0。

从 Java 8 开始,LocalDate类覆盖了equal方法,以提供日期相等性。

LocalDate today = LocalDate.now();
LocalDate date1 = LocalDate.of(2015, 10, 26);
if(date1.equals(today)){
    System.out.printf("Today %s and date1 %s are same date %n", today, date1);
}

获取日期部分(年,月,日)

分别获取日期部分(例如年,月等)的示例。

不建议使用获取年,月,日,小时等的方法。 如果需要获取或设置年,月,日等,请改用java.util.Calendar

Calendar calendar = new GregorianCalendar();

int year       = calendar.get(Calendar.YEAR);
int month      = calendar.get(Calendar.MONTH); 
int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); // Jan = 0, not 1
int dayOfWeek  = calendar.get(Calendar.DAY_OF_WEEK);
int weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
int weekOfMonth= calendar.get(Calendar.WEEK_OF_MONTH);

int hour       = calendar.get(Calendar.HOUR);        // 12 hour clock
int hourOfDay  = calendar.get(Calendar.HOUR_OF_DAY); // 24 hour clock
int minute     = calendar.get(Calendar.MINUTE);
int second     = calendar.get(Calendar.SECOND);
int millisecond= calendar.get(Calendar.MILLISECOND);

仅供参考,在 Java 8 中,您可以按以下方式获得不同的时间单位。

LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();

这些 Java Date示例就是这些。

学习愉快!

Java 语言环境 – 创建和设置默认语言环境

原文: https://howtodoinjava.com/java/date-time/java-locale-api-examples/

如果要以符合用户语言和文化期望的用户友好方式显示数字,日期和时间,则需要使用 Java 语言环境 api。 在 Java 中,java.util.Locale类表示世界上特定的语言和地区。

如果一个类根据语言环境改变其行为,则称其为对语言环境敏感的。 例如,NumberFormatDateFormat类是对语言环境敏感的。 数字和日期的格式,它的返回取决于语言环境

Table of Contents

Create Locale Instance
Set Default Locale

创建语言环境实例

您可以通过以下方式创建 Java 语言环境实例:

  1. 静态语言环境对象

    这是最简单的方法,并使用Locale类中的预定义常量。

    Locale usLocale = Locale.US;
    
    long number = 123456789L;
    NumberFormat nf = NumberFormat.getInstance(usLocale);
    System.out.println( nf.format(number) );     //123,456,789
    
    Date now = new Date();
    DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, usLocale);
    System.out.println( df.format(now) );    //July 19, 2016 12:43:12 PM IST
    
    

    请注意,以这种方式构建语言环境时,Locale的区域部分未定义。 因此,以下两个语句本质上是相等的:

    //Region is missing in both cases
    Locale usLocale = Locale.US;       //1
    Locale usLocale = new Locale.Builder().setLanguage("en").build();        //2
    
    
  2. 语言环境构造器

    Locale类中提供了三个构造器:

  • Locale(String language)

  • Locale(String language, String country)

  • Locale(String language, String country, String variant)

    Locale usLocale = new Locale("en");
    //Locale usLocale = new Locale("en", "US");
    
    long number = 123456789L;
    NumberFormat nf = NumberFormat.getInstance(usLocale);
    System.out.println( nf.format(number) );    //123,456,789
    
    Date now = new Date();
    DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, usLocale);
    System.out.println( df.format(now) );    //July 19, 2016 12:43:12 PM IST
    
    
  1. Locale.Builder

    Locale.Builder工具类可用于构造符合 IETF BCP(最佳通用实践)47 语法的Locale对象。

    Locale usLocale = new Locale.Builder().setLanguage("en").setRegion("US").build();
    
    long number = 123456789L;
    NumberFormat nf = NumberFormat.getInstance(usLocale);
    System.out.println( nf.format(number) );    //123,456,789
    
    Date now = new Date();
    DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, usLocale);
    System.out.println( df.format(now) );    //July 19, 2016 12:43:12 PM IST
    
    

    如果其参数不是BCP 47标准的格式正确的元素,则此方法将返回java.util.IllformedLocaleException错误。

  2. Locale.forLanguageTag()方法

    如果您具有符合 IETF BCP 47 标准的语言标签字符串,则可以使用forLanguageTag(String)工厂方法。

    Locale usLocale = Locale.forLanguageTag("en-US");
    
    long number = 123456789L;
    NumberFormat nf = NumberFormat.getInstance(usLocale);
    System.out.println( nf.format(number) );    //123,456,789
    
    Date now = new Date();
    DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, usLocale);
    System.out.println( df.format(now) );    //July 19, 2016 12:43:12 PM IST
    
    

设置默认语言环境

尽管可以在运行时在所有对语言环境敏感的类中设置Locale,但是如果我们可以在启动时为每个用户请求设置默认语言环境(或特定于应用程序的语言环境),则无需为每个应用程序代码中的语言环境敏感的对象设置语言环境,因此我们可以避免很多代码行,并且也很少出现缺陷。

使用Locale.setDefault()方法设置默认情况下将使用所有对语言环境敏感的类的Locale实例。

Locale.setDefault(Locale.FRANCE);

Locale类还允许您为两个不同的类别设置默认的Locale

语言环境类别

Locale.Category枚举表示两个区域设置类别:

  1. Locale.Category.DISPLAY – 适用于应用程序的用户界面,例如资源束消息。
  2. Locale.Category.FORMAT – 用于日期和数字格式,具体取决于特定的区域信息
Locale.setDefault(Locale.Category.DISPLAY, Locale.US);
Locale.setDefault(Locale.Category.FORMAT, Locale.FR);

让我们来看一个例子。

//Set them at application startup or where request cycle begin
Locale.setDefault(Locale.Category.DISPLAY, Locale.US);
Locale.setDefault(Locale.Category.FORMAT, Locale.FRANCE);

ResourceBundle resourceBundle =  ResourceBundle.getBundle("bundleName", Locale.getDefault(Locale.Category.DISPLAY));
String message = resourceBundle.getString("message.key");

Date now = new Date();
DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
String date = df.format(now);

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

学习愉快!

资源:

ISO 国家/地区代码

ISO 语言代码

Java 枚举教程

Java 枚举

原文: https://howtodoinjava.com/java/enum/enum-tutorial/

Java 枚举,也称为 Java 枚举类型,是一种其字段由固定的常数集组成的类型。 枚举的主要目的是加强编译时类型的安全性enum关键字是 Java 中的保留关键字。

当我们知道在编译时或设计时变量的所有可能值时,我们应该使用枚举,尽管将来在标识变量时可以添加更多值。 在此 Java 枚举教程中,我们将学习什么是枚举以及它们解决了哪些问题?

Table of Contents

1\. What is enum in Java
2\. enum constructors
3\. enum methods
4\. enum inheritance
5\. Compare enums
6\. EnumMap and EnumSet
7\. Summary

1. Java 中的枚举

枚举(通常)通常是一组相关的常数。 从一开始,它们就已经使用其他编程语言,例如 C++。 在 JDK 1.4 之后,Java 设计人员决定也用 Java 支持它,并且在 JDK 1.5 版本中正式发布

关键字enum支持 Java 中的枚举。 枚举是一种特殊类型的类,始终扩展 java.lang.Enum

1.1 枚举是保留关键字

Java 中的enum是保留关键字。 这意味着您不能定义名称为enum的变量。 例如这将导致编译时错误"invalid VariableDeclaratorId"

enum is reserved keyword

枚举是保留关键字

1.2 Java 枚举声明

创建枚举的简单示例。 众所周知,通常我们在日常生活中要处理四个方向。 它们的名称,角度和其他属性是固定的。 因此,在程序中,我们可以为它们创建枚举。 创建枚举的语法如下:

public enum Direction 
{
   EAST, WEST, NORTH, SOUTH;
}

从逻辑上讲,每个枚举都是枚举类型本身的一个实例。 因此给定的枚举可以看作是下面的声明。 JVM 在内部向此类添加序数和值方法,我们可以在使用枚举时调用它。

final class Direction extends Enum<Direction> 
{
    public final static Direction EAST = new Direction();
    public final static Direction WEST = new Direction();
    public final static Direction NORTH = new Direction();
    public final static Direction SOUTH = new Direction();
}

1.3 Java 枚举示例

我们可以像使用final static类字段一样使用枚举。

public class EnumExample 
{
    public static void main(String[] args) 
    {
        Direction north = Direction.NORTH;

        System.out.println(north);        //Prints NORTH
    }
}

1.4 枚举ordinal()

ordinal()方法返回枚举实例的顺序。 它表示枚举声明中的序数,其中初始常量分配为'0'的序数。 它非常类似于数组索引。

它设计用于供基于复杂枚举的数据结构使用,例如EnumSetEnumMap

Direction.EAST.ordinal();     //0

Direction.NORTH.ordinal();    //2

1.5 枚举values()valueOf()

枚举values()方法返回枚举数组中的所有枚举值。

Direction[] directions = Direction.values();

for (Direction d : directions) {
    System.out.println(d);
}

//Output:

EAST
WEST
NORTH
SOUTH

枚举valueOf()方法有助于将字符串转换为枚举实例。

Direction east = Direction.valueOf("EAST");

System.out.println(east);

//Output:

EAST

1.6 枚举命名约定

按照惯例,枚举是常量。 在 Java 中,常量在所有UPPER_CASE字母中定义。 这也是枚举。

  • 枚举名称应为标题大小写(与类名相同)。
  • 枚举字段应全部大写(与静态最终常量相同)。

2. 枚举构造器

默认情况下,枚举不需要构造器定义,并且它们的默认值始终是声明中使用的字符串。 不过,您可以定义自己的构造器来初始化枚举类型的状态。

例如,我们可以在方向上添加angle属性。 所有方向都有一定角度。 因此,让我们添加它们。

public enum Direction 
{
    // enum fields
    EAST(0), WEST(180), NORTH(90), SOUTH(270);

    // constructor
    private Direction(final int angle) {
        this.angle = angle;
    }

    // internal state
    private int angle;

    public int getAngle() {
        return angle;
    }
}

如果要访问任何方向的角度,可以在枚举字段引用中进行简单的方法调用。

Direction north = Direction.NORTH;

System.out.println( north );                      //NORTH

System.out.println( north.getAngle() );           //90

System.out.println( Direction.NORTH.getAngle() ); //90

3. 枚举方法

请记住,枚举基本上是一种特殊的类类型,并且可以像其他任何类一样具有方法和字段。 您可以添加抽象以及具体的方法。 枚举中允许两种方法。

3.1 枚举的具体方法

在枚举中添加具体方法类似于在其他任何类中添加相同方法。 您可以使用任何访问说明符,例如 publicprivateprotected。 您可以从枚举方法返回值,也可以简单地使用它们执行内部逻辑。

public enum Direction 
{
    // enum fields
    EAST, WEST, NORTH, SOUTH;

    protected String printDirection() 
    {
        String message = "You are moving in " + this + " direction";
        System.out.println( message );
        return message;
    }
}

您可以像对枚举实例进行简单方法调用一样,调用printDirection()方法。

Direction.NORTH.printDirection(); //You are moving in NORTH direction

Direction.EAST.printDirection();  //You are moving in EAST direction

3.2 枚举中的抽象方法

我们可以在枚举中添加抽象方法。 在这种情况下,我们必须在每个枚举字段上单独实现抽象方法。

public enum Direction 
{
    // enum fields
    EAST {
        @Override
        public String printDirection() {
            String message = "You are moving in east. You will face sun in morning time.";
            return message;
        }
    },
    WEST {
        @Override
        public String printDirection() {
            String message = "You are moving in west. You will face sun in evening time.";
            return message;
        }
    },
    NORTH {
        @Override
        public String printDirection() {
            String message = "You are moving in north. You will face head in daytime.";
            return message;
        }
    },
    SOUTH {
        @Override
        public String printDirection() {
            String message = "You are moving in south. Sea ahead.";
            return message;
        }
    };

    public abstract String printDirection();
}

重新运行上面的示例。

Direction.NORTH.printDirection(); //You are moving in north. You will face head in daytime.

Direction.EAST.printDirection();  //You are moving in east. You will face sun in morning time.

您可以为以这种方式创建的所有枚举强制执行契约。 它可以用作创建枚举的模板

例如,如果我们希望Direction的每个枚举类型都应能够在需要时打印带有自定义消息的路线名称。 这可以通过在Direction中定义abstract方法来完成,每个枚举都必须覆盖该方法。 将来,将在更多方向上添加(真的吗?),那么我们还必须添加自定义消息。

4. 枚举继承

如前所述,枚举扩展了Enum类。 java.lang.Enum是一个抽象类。 这是所有 Java 枚举类型的通用基类。

public abstract class Enum<E extends Enum<E>> 
					extends Object 
					implements Comparable<E>, Serializable {

}

这意味着所有枚举都是隐式可比可序列化的。 此外,Java 中的所有枚举类型默认为单例

如前所述,所有枚举都扩展了java.lang.Enum,因此枚举不能扩展任何其他类,因为 Java 不支持 多继承。 但是枚举可以实现任何数量的接口。

5. 比较枚举

默认情况下,所有枚举都是可比较的,也是单例。 这意味着即使使用"=="运算符,也可以使用equals()方法进行比较。

Direction east = Direction.EAST;
Direction eastNew = Direction.valueOf("EAST");

System.out.println( east == eastNew );           //true
System.out.println( east.equals( eastNew ) );    //true

您可以使用'=='运算符或equals()方法比较枚举类型,因为默认情况下枚举是单例且可比较。

6. 枚举集合 – EnumSetEnumMap

java.util包中添加了两个类以支持枚举 – EnumSet(用于枚举的高性能Set实现;枚举集的所有成员必须具有相同的枚举类型)和EnumMap(与枚举键配合使用的高性能映射实现)。

6.1 java.util.EnumSet

EnumSet类的定义如下:

public abstract class EnumSet<E extends Enum<E>> 
						extends AbstractSet<E> 
						implements Cloneable, Serializable {

}

一种专门用于枚举类型的Set实现。 枚举集中的所有元素都必须来自创建集时明确或隐式指定的单个枚举类型。

6.1.1 EnumSet示例

public class Test 
{
   public static void main(String[] args) 
   {
     Set enumSet = EnumSet.of(  Direction.EAST,
                                Direction.WEST,
                                Direction.NORTH,
                                Direction.SOUTH
                              );
   }
 }

像大多数集合实现一样,EnumSet不同步。 如果多个线程同时访问一个枚举集,并且至少有一个线程修改了该枚举集,则应在外部对其进行同步。

不允许null元素。 同样,这些集合可确保基于声明枚举常量中元素的顺序来对元素进行排序。 与常规集合实现相比,性能和内存优势非常高。

6.2 java.util.EnumMap

EnumMap声明为:

public class EnumMap<K extends Enum<K>,V> extends AbstractMap<K,V> implements Serializable, Cloneable {

}

一种专门用于枚举类型键的Map实现。 同样,枚举映射中的所有键都必须来自创建映射时显式或隐式指定的单个枚举类型。

EnumSet一样,不允许null键,并且也不同步

6.2.1 EnumMap示例

public class Test 
{
  public static void main(String[] args)
  {
    //Keys can be only of type Direction
    Map enumMap = new EnumMap(Direction.class);

    //Populate the Map
    enumMap.put(Direction.EAST, Direction.EAST.getAngle());
    enumMap.put(Direction.WEST, Direction.WEST.getAngle());
    enumMap.put(Direction.NORTH, Direction.NORTH.getAngle());
    enumMap.put(Direction.SOUTH, Direction.SOUTH.getAngle());
  }
}

7. 总结

  1. 枚举是java.lang.Enum类的最终子类
  2. 如果枚举是类的成员,则隐式地static
  3. new关键字即使在枚举类型本身内也不能用于初始化枚举
  4. name()valueOf()方法仅使用枚举常量的文本,而toString()方法可能会被覆盖以提供任何内容(如果需要)
  5. 对于枚举常量equals()"=="得出相同的结果,可以互换使用
  6. 枚举常量隐式public static final
  7. 枚举常量列表的出现顺序称为“自然顺序”,并且还定义了其他项使用的顺序:compareTo()方法,值的迭代顺序 EnumSetEnumSet.range()
  8. 枚举构造器应声明为private。 编译器允许非私有构造器,但这对读者来说似乎是一种误导,因为 new 永远不能与枚举类型一起使用。
  9. 由于这些枚举实例都是有效的单例,因此可以使用标识("==")比较它们的相等性。
  10. 您可以在switch语句中使用枚举,例如intchar原始数据类型

在本文中,我们从语言基础到更高级,更有趣的实际用例,探讨了 Java 枚举

学习愉快!

参考文献:

SO 帖子

枚举 Java 文档

Java 1.5 枚举

带有字符串值的 Java 枚举

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

在本本使用字符串值进行 Java 枚举的指南中,学习使用字符串创建枚举,遍历所有枚举值,获取枚举值以及对进行反向查找,以通过字符串参数查找枚举。

具有固定值的固定属性集时,应始终创建枚举。 它们本质上是单例,提供了更好的性能。

1. 使用字符串值创建 Java 枚举

Java 程序用字符串创建枚举。 该枚举包含部署环境及其各自的 URL。 将每个枚举常量的 URL 传递给枚举构造器

public enum Environment 
{
	PROD("https://prod.domain.com:1088/"), 
	SIT("https://sit.domain.com:2019/"), 
	CIT("https://cit.domain.com:8080/"), 
	DEV("https://dev.domain.com:21323/");

	private String url;

	Environment(String envUrl) {
		this.url = envUrl;
	}

	public String getUrl() {
		return url;
	}
}

2. 迭代枚举常量

要遍历枚举列表,请对枚举类型使用values()方法,该方法返回数组中的所有枚举常量。

//Get all enums
for(Environment env : Environment.values())
{
	System.out.println(env.name() + " :: "+ env.getUrl());
}

输出:

PROD :: https://prod.domain.com:1088/
SIT :: https://sit.domain.com:2019/
CIT :: https://cit.domain.com:8080/
DEV :: https://dev.domain.com:21323/

3. Java 枚举转换为String

要获取单个枚举值(例如,从枚举常量获取产品网址),请使用您创建的值方法。

//Using enum constant reference
String prodUrl = Environment.PROD.getUrl();

System.out.println(prodUrl);

输出:

https://prod.domain.com:1088/

4. 按名称获取枚举 – 枚举字符串参数

如果您想使用它的名称获取枚举常量,请使用valueOf()方法。

//First get enum constant reference from string
Environment sitUrl = Environment.valueOf("SIT");

System.out.println(sitUrl.getUrl());

输出:

https://sit.domain.com:2019/

5. 反向查找 – 从值中获取枚举名称

很多时候,我们将拥有枚举的值,并且我们将需要通过字符串值获取枚举名称。 这可以通过反向查找来实现。

在反向查找中,枚举在内部维护值到枚举的映射作为键值对。 搜索了此映射,应用程序需要查找与其关联的字符串值有关的枚举。

import java.util.HashMap;
import java.util.Map;

public enum Environment 
{
	PROD("https://prod.domain.com:1088/"), 
	SIT("https://sit.domain.com:2019/"), 
	CIT("https://cit.domain.com:8080/"), 
	DEV("https://dev.domain.com:21323/");

	private String url;

	Environment(String envUrl) {
		this.url = envUrl;
	}

	public String getUrl() {
		return url;
	}

	//****** Reverse Lookup Implementation************//

	//Lookup table
	private static final Map<String, Environment> lookup = new HashMap<>();

	//Populate the lookup table on loading time
	static 
	{
		for(Environment env : Environment.values())
		{
			lookup.put(env.getUrl(), env);
		}
	}

	//This method can be used for reverse lookup purpose
	public static Environment get(String url) 
	{
		return lookup.get(url);
	}
}

要使用此反向查找,请在应用程序代码中使用enum.get()方法。

//Get enum constant by string - Reverse Lookup
String url = "https://sit.domain.com:2019/";

Environment env = Environment.get(url);

System.out.println(env);

Output:

SIT

阅读更多:完整的 Java 枚举指南

在评论部分,将我对 Java 8 枚举的问题留给我。

学习愉快!

阅读更多:

Java 枚举教程

Java 按值传递与按引用传递

原文: https://howtodoinjava.com/java/basics/java-is-pass-by-value-lets-see-how/

关于“java 是按值传递还是按引用传递?”的争论很多。 好吧,让我们最后一次总结一下, Java 是按值传递而不是引用传递。 如果已经按引用传递了代码,那么我们应该能够像对象交换那样使用 C 语言,但是我们不能在 Java 中做到这一点。 我们已经知道了吧?

当您将实例传递给方法时,其内存地址会一点一点地复制到新的引用变量中,因此它们都指向同一实例。 但是,如果您在方法内部更改了引用,原始引用将不会更改。 如果它按引用传递,那么它也将被更改。

为了证明这一点,让我们看一下内存分配在运行时如何发生。 如果有任何疑问,它应该可以解决。 我正在使用以下程序演示该概念。

public class Foo
{
	private String attribute;

	public Foo (String a){
		this.attribute = a;
	}
	public String getAttribute() {
		return attribute;
	}
	public void setAttribute(String attribute) {
		this.attribute = attribute;
	}
}

public class Main
{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will change the object that the reference variable "f" refers to!
     }
     public static void changeReference(Foo a) {
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c) {
          c.setAttribute("c");
     }
}

让我们逐步查看运行时发生的情况:

1)Foo f = new Foo("f");

该语句将创建Foo类的实例,并将attribute初始化为f。 对该创建实例的引用分配给变量f

innstance creation

2)public static void changeReference(Foo a)

执行此操作后,将声明名称为a的类型为Foo的引用,并将其最初分配为null

Null reference

3)changeReference(f);

调用方法changeReference时,会将引用a分配给作为参数传递的对象。

reference assignment

4)Foo b = new Foo("b");内部第一种方法

这将与第一步完全相同,并创建一个新的Foo实例,并将其分配给b;

new instance

5)a = b;

这是重点。 在这里,我们有三个参考变量,当执行语句时,ab将指向在该方法内部创建的同一实例。 注意:f不变,它一直指向实例,它原来是指向的。没变 !!

assignment

6)ModifyReference(Foo c);

现在,当该语句执行引用时,将创建c并将其分配给具有属性f的对象。

new reference

7)c.setAttribute("f");

这将更改引用c指向的对象的属性,以及引用f指向的同一对象。

modify reference

我希望这个解释足够清楚,以使您更好地理解(如果尚未理解)。

学习愉快!

枚举真的是最好的单例吗?

原文: https://howtodoinjava.com/java/enum/is-enum-really-best-for-singletons/

您必须多次听到枚举始终是在 Java 中实现单例设计模式的最佳选择。 他们真的最好吗? 如果可以的话,它比其他可用技术更好吗? 让我们找出答案。

编写单例实现总是很棘手。 我在博客文章中已经讨论了几种方法(包括我最喜欢的方法)。 我在那里写清楚,枚举为线程安全提供了隐式支持,并且只保证了一个实例。 这也是以最小的努力获得单例的好方法。

枚举作为单例的问题

话虽这么说,就像宇宙中的任何其他事物一样,这种方法确实有其缺点,您在做出任何决定之前需要考虑这些缺点。

  1. 默认情况下,枚举不支持延迟加载
  2. 虽然这种情况非常罕见,但是如果您改变主意并现在想将单例转换为多例,枚举将不允许

如果以上两种情况对任何人都没有问题,则枚举可能是最佳选择。

无论如何,顺便说一句,编译后,java 枚举只能通过其他方法(例如 values()valueOf()…等)转换为类。

基于枚举的单例示例

一旦您决定编写基于枚举的单例,编写它就非常容易,例如

enum Singleton
{
	INSTANCE;
	// instance vars, constructor

	private final Connection connection;

	Singleton()
	{
		// Initialize the connection
		connection = DB.getConnection();
	}

	// Static getter
	public static Singleton getInstance()
	{
		return INSTANCE;
	}

	public Connection getConnection()
	{
		return connection;
	}
}

现在您可以使用final Singleton s = Singleton.getInstance()了。 请记住,由于这是一个枚举,因此您也始终可以通过Singleton.INSTANCE进行访问。

学习愉快!

枚举器和迭代器之间的区别?

原文: https://howtodoinjava.com/java/collections/difference-between-enumerator-and-iterator/

在任何 java 面试中都可以询问EnumeratorIterator之间的区别。 在这篇文章中,我列出了您在回答问题时可能会列举的一些差异。

枚举器和迭代器之间的区别

首先,枚举仅适用于遗留类,例如HashtableVector枚举是初始 Java 版本 JDK1.0 的一部分。 尽管迭代器与集合框架一起包含在 JDK 1.2 中,该框架也仅在 JDK 1.2 中添加。

很明显,迭代器被设计为仅专注于集合框架。 如果您阅读了Iterator的 Java 文档,则会清楚说明其用途。 从甲骨文官方网站引用:

集合上的迭代器。 在 Java 集合框架中,迭代器代替了枚举。 迭代器与枚举有以下两种不同:

  1. 迭代器允许调用者在迭代过程中使用定义明确的语义从基础集合中删除元素。
  2. 方法名称已得到改进。

该接口是 Java 集合框架的成员。

最重要的是,EnumerationIterator都将给出连续的元素,但是Iterator以这种方式进行了改进,因此方法名称更短,并且具有附加的remove()方法。

这是一个并排比较:

枚举器 迭代器
hasMoreElement() hasNext()
nextElement() next()
不适用 remove()

Java API 规范建议,对于较新的程序,应首选迭代器而不是枚举,因为“迭代器在 Java 集合框架中取代了枚举”。

这就是这个简单而重要的主题。

祝您学习愉快!

Java 异常

Java try-finally

原文: https://howtodoinjava.com/java/exception-handling/try-catch-finally/

Java trycatchfinally块有助于编写可能在运行时引发异常的应用程序代码,并为我们提供了通过执行替代应用程序逻辑或优雅地处理异常,以向用户报告。 它有助于防止丑陋的应用程序崩溃。

1. Java trycatchfinally

1.1 try

try块包含应在正常条件下运行的应用程序代码。 例如,读取文件,写入数据库或执行复杂的业务操作。

try块写为try关键字,后跟花括号。

try {
    //application code
}

1.2 catch

可选的catch块位于try块之后,并且必须处理try块引发的受检异常以及任何可能的非受检异常。

try {
    //code
}
catch(Exception e) {
    //handle exception
}

应用程序可能会以 N 种不同方式出错。 这就是为什么我们可以将多个catch与单个try块相关联的原因。 在每个catch块中,我们可以以一种独特的方式处理一个或多个特定的异常。

当一个catch块处理异常时,不执行下一个catch块。 控制直接从已执行的catch块转移到执行器的其余部分,包括finally块。

try {
    //code
}
catch(NullPointerException e) {
    //handle exception
}
catch(NumberFormatException e) {
    //handle exception
}
catch(Exception e) {
    //handle exception
}

1.3 finally

可选的finally块使我们有机会在每次try-catch块完成时运行我们要执行的代码 - 有错误或无错误。

即使我们未能在catch块中成功处理异常,也可以保证finally块语句的执行。

try {
	//open file
    //read file
}
catch(Exception e) {
    //handle exception while reading the file
}
finally {
	//close the file
}

1.4 仅try块是强制性的

请注意,只有try块是必需的,而catchfinally块是可选的。 使用try块,我们可以根据需要使用catch块或finally块。

下面可能用 Java 给出两种组合。 这两个版本均有效

try {

}
catch(Exception e) {

}

try {

}
finally {

}

2. Java 异常处理如何工作?

在正常情况下,当运行时发生异常时,JVM 将错误信息包装在Throwable子类型的实例中。 此异常对象类似于其他 Java 对象,并且具有字段和方法。

唯一的区别是 JVM 检查它们的存在并将控制权传递给catch块,该块可以处理异常类型或其父类类型

try catch finally flow

try catch finally

当在应用程序中找不到异常的catch块时,未捕获的异常由 JVM 级别的默认异常处理程序处理。 它向用户报告异常并终止应用程序。

3. 带有trycatchfinally块的不同执行流程

我们来看一些示例,以了解在不同情况下执行流程的流程。

3.1 trycatchfinally块 – 没有发生异常

如果没有发生异常,那么 JVM 将仅执行finally块。 catch块将被跳过。

try 
{
    System.out.println("try block");
} 
catch (Exception e) 
{
    System.out.println("catch block");
} 
finally 
{
    System.out.println("finally block");
}

程序输出。

try block
finally block

3.2 trycatchfinally块 – 发生异常

如果try块中发生异常,则 JVM 将首先执行catch块,然后执行finally

try 
{
    System.out.println("try block");

    throw new NullPointerException("Null occurred");
} 
catch (Exception e) 
{
    System.out.println("catch block");
} 
finally 
{
    System.out.println("finally block");
}

程序输出:

try block
catch block
finally block

3.3 tryfinally块 – 未处理异常

如果提供的catch块未处理异常,则 JVM 默认异常处理程序将处理该异常。 在这种情况下,将执行finally块,然后执行默认的异常处理机制。

try 
{
    System.out.println("try block");

    throw new NullPointerException("Null occurred");
} 
finally 
{
    System.out.println("finally block");
}

程序输出:

try block
finally block

Exception in thread "main" 
java.lang.NullPointerException: Null occurred
	at com.howtodoinjava.Main.main(Main.java:12)

3.4 trycatchfinally块 – 多个catch

如果有多个catch块与try块相关联,则异常由顺序处理的第一个catch块处理,该异常可以处理异常类型或其父类型。

例如,处理IOExceptioncatch块可以处理FileNotFoundException类型的异常,这也是因为FileNotFoundException extends IOException

try 
{
    System.out.println("try block");

    throw new NullPointerException("null occurred");
} 
catch (NumberFormatException e) 
{
    System.out.println("catch block 1");
}
catch (NullPointerException e) 
{
    System.out.println("catch block 2");
}
catch (Exception e) 
{
    System.out.println("catch block 3");
} 
finally 
{
    System.out.println("finally block");
}

程序输出:

try block
catch block 2
finally block

3.5 trycatchfinally块 – catch块引发异常

catch块中处理另一个异常时,有时可能会出现异常。 它将如何处理?

如果catch块中有异常,则将执行转移到与各个catch块相关联的finally块(如果有)。 然后,将该异常传播到方法调用栈中,以找到可以处理此异常的catch块。

如果找到了此类catch块,则处理异常,否则 JVM 默认异常处理程序将处理异常并终止应用程序。

try 
{
    System.out.println("try block");

    throw new NullPointerException("NullPointerException occured");
} 
catch (NullPointerException e) 
{
    System.out.println("catch block 1");

    throw new NumberFormatException("NumberFormatException occurred");
}
catch (Exception e) 
{
    System.out.println("catch block 2");
} 
finally 
{
    System.out.println("finally block");
}

程序输出:

try block
catch block 1
finally block

Exception in thread "main" 
java.lang.NumberFormatException: NumberFormatException occurred
	at com.howtodoinjava.Main.main(Main.java:18)

4. try-with-resources

对于可关闭的资源(例如流),Java SE 7 引入了try-with-resources语句,这是在上述情况下处理异常的推荐方法。 在这种方法中,我们不需要关闭流,而 JVM 会为我们完成它。 它消除了finally块的需要。

try-with-resources中,资源在小括号内的try块中打开,finally块完全消失了。

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();
}

阅读更多信息: Java 7 try-with-resource

学习愉快!

Java throw关键字

原文: https://howtodoinjava.com/java/exception-handling/throw-vs-throws/

在 Java 异常处理中,throw关键字用于显式抛出方法或构造器中的异常。throws`关键字**用于声明该方法或构造器可能抛出的异常列表。

1. Java throw关键字

1.1 语法

要从方法或构造器引发异常,请使用throw关键字以及异常类的实例。

public void method() 
{
   //throwing an exception
   throw new SomeException("message");
}

1.2 处理受检与非受检异常

如果我们从方法中抛出非受检的异常,则处理该异常或在throws子句中声明不是强制性的。 例如,NullPointerException是非受检的异常。

public class JavaExample 
{
	public static void main(String[] args) 
	{
		method();
	}

	public static void method( ) {
		throw new NullPointerException();
	}
}

但是,如果我们使用throw语句抛出受检异常,则必须在catch块**中处理该异常,或者必须使用throws声明显式地声明该方法。 例如,FileNotFoundException是受检异常。

public class JavaExample 
{
	public static void main(String[] args) 
	{
		try 
		{
			method();
		} 
		catch (FileNotFoundException e) 
		{
			e.printStackTrace();
		}
	}

	public static void method( ) throws FileNotFoundException 
	{
		throw new FileNotFoundException();
	}
}

在 Java 中,ErrorRuntimeException的每个子类都是非受检的异常。 一个受检的异常是Throwable类下的所有其他内容。

1.3 异常传播

在调用栈中,异常会在方法与方法之间传播,直到被捕获为止。 因此,如果a()调用b(),然后调用c(),又调用d(),并且如果d()抛出异常,则该异常将从d传播到cb传播到a,除非这些方法之一捕获异常。

如上所述,对异常处理程序的搜索从发生错误的方法开始,然后以与调用方法相反的顺序在调用栈中进行。

找到合适的处理程序(catch)后,运行时系统会将异常传递给处理程序。 如果未找到异常处理程序,则异常到达 JVM 的默认异常处理程序,该程序将异常详细信息打印到日志并终止应用程序。

2. Java throws关键字

Java throws关键字用于声明方法执行期间可能发生的异常列表。

2.1 语法

要声明异常列表,请使用throws关键字以及异常类名称。

public static void method( ) throws FileNotFoundException, ConnectionException {
	//code
}

2.2 可以抛出受检和非受检的异常

我们可以使用throws子句声明两种类型的异常,即受检和非受检的异常。 但是调用给定方法必须仅处理受检异常。 非受检异常的处理是可选的。

public class JavaExample 
{
	public static void main(String[] args) 
	{
		try 
		{
			//Can skip handling of NullPointerException (unchecked exception)
			method();	
		} 
		catch (FileNotFoundException e) 
		{
			e.printStackTrace();
		}
	}

	public static void method( ) throws NullPointerException, FileNotFoundException {
		//code
	}
}

3. Java 中throwthrows之间的区别

  1. throw关键字用于从任何方法或构造器中显式抛出单个异常,而throws关键字用于方法和构造器声明中,表示该方法可能引发该异常。
  2. throw后跟异常类的实例,而throws后跟异常类的名称。
  3. throw用于方法和构造器,其中throws与方法和构造器签名一起使用。
  4. 我们可以使用throw仅抛出单个异常,但是可以使用throws声明多个异常,其中之一可以通过方法抛出也可以不通过方法抛出。
  5. 受检的异常将传播到调用方方法,而非受检的异常将不会传播,因此可能不需要显式的异常处理。
  6. 使用throw关键字,我们还可以中断switch语句或循环,而无需使用break关键字,使用throws无法执行它。

学习愉快!

Java 受检与非受检的异常

原文: https://howtodoinjava.com/java/exception-handling/checked-vs-unchecked-exceptions-in-java/

在此 Java 异常教程中,了解 Java 中的异常是什么,什么是受检的异常以及它与非受检的异常有何不同。 我们还将学习有关 Java 受检异常的一些最佳实践。

Table of Contents

1\. What is an exception in Java?
2\. Checked vs unchecked exceptions in Java
3\. Java exception handling best practices

1. Java 中有什么异常?

“异常是在程序执行期间发生的意外事件,它破坏了正常的指令流。”

在 Java 中,所有错误和异常都用Throwable类表示。 当方法中发生错误时,该方法将创建一个对象(Throwable的任何子类型)并将其交给运行时系统。 该对象称为异常对象。

异常对象包含有关错误的信息,包括错误的类型和发生错误时程序的状态。 创建异常对象并将其交给运行时系统称为引发异常

1.1 异常处理

在应用程序中创建异常对象时,我们有两种选择。

  • 我们要么在方法中处理它
  • 或者我们可以将其传递给调用方方法以使其处理。

在设置方法责任时,这是非常重要的决定。 一种方法应该清楚地表明它将处理所有异常情况,而不会处理哪些异常情况。 它是使用throws子句在方法语法中定义的。

要处理异常,我们必须在try-catch块的catch部分中捕获异常。

如果在应用程序中未处理异常,则该异常将传播到 JVM,并且 JVM 通常将终止程序本身。

2. Java 中的受检与非受检异常

2.1 异常层次

在 Java 中,异常大致分为两部分:受检异常和非受检的异常

ExceptionHierarchyJava

2.2 受检异常

Java 迫使您在应用程序代码中以某种方式处理这些错误情况。 一旦您开始编译程序,它们将立即出现在您的脸上。 您绝对可以忽略它们,而让它们传递给 JVM,但这是一个坏习惯。 理想情况下,您必须在应用程序内部的适当级别上处理这些异常,以便可以通知用户有关失败的信息并要求他重试/稍后再来。

通常,受检异常表示程序无法直接控制的错误情况。 它们通常发生在与外部资源/网络资源例如数据库问题,网络连接错误,文件丢失等

受检异常是Exception类的子类。

受检异常的示例是:ClassNotFoundExceptionIOExceptionSQLException等。

受检异常示例

FileNotFoundException是 Java 中的一个受检异常。 任何时候,我们都想从文件系统中读取文件,Java 会强制我们处理文件可能不存在的错误情况。

public static void main(String[] args) 
{
    FileReader file = new FileReader("somefile.txt");
}

在上述情况下,您将获得消息 – Unhandled exception type FileNotFoundException的编译时错误。

为了使程序能够编译,您必须在try-catch块中处理此错误情况。 下面给出的代码将完全可以编译。

public static void main(String[] args) 
{
    try 
    {
		FileReader file = new FileReader("somefile.txt");
	} 
    catch (FileNotFoundException e) 
    {
    	//Alternate logic
		e.printStackTrace();
	}
}

2.3 非受检的异常

Java 还提供了UncheckedExceptions,编译器不会检查其出现。 一旦执行了任何错误代码,它们就会生效/出现在您的程序中。

编译器不强制方法声明其实现引发的非受检的异常。 通常,此类方法几乎总是也不会声明它们。

非受检的异常是RuntimeException的子类。 非受检的异常的示例是:ArithmeticExceptionArrayStoreExceptionClassCastException等。

“奇怪的是,RuntimeException本身是Exception的子类,即所有非受检异常类都应该隐式是受检异常,但不是。”

非受检的异常示例

在下面检查给定的代码。 上面的代码没有给出任何编译时错误。 但是当您举这个例子时,它抛出NullPointerExceptionNullPointerException是 Java 中非受检的异常。

public static void main(String[] args) 
{
    try 
    {
		FileReader file = new FileReader("pom.xml");

		file = null;

		file.read();
	} 
    catch (IOException e) 
    {
    	//Alternate logic
		e.printStackTrace();
	}
}

请记住,受检的异常和非受检的异常之间最大的区别在于,受检的异常是由编译器强制执行的,用于指示不受程序控制的异常情况(例如,I/O 错误),而非受检的异常在运行时发生,用于指示编程错误(例如,空指针)。

3. Java 异常处理最佳实践

  1. 当方法无法执行其名称所说明的功能时,可以使用受检异常。 例如预先准备好配置文件并使用配置文件进行配置的名为prepareSystem()的方法可以声明抛出FileNotFoundException,这意味着该方法使用了文件系统中的配置文件。

  2. 理想情况下,绝对不应将受检异常用于编程错误,而在此类情况下,绝对不应将资源错误用于流控制。

  3. 仅抛出方法无法以任何方式处理的那些异常。 方法应首先尝试在遇到它时立即对其进行处理。 仅当无法处理内部方法时才引发异常。

  4. 定义方法签名的一种好方法是在方法名称附近声明异常。 如果您的方法名为openFile,则应抛出FileNotFoundException”?。 如果您的方法名为findProvider,则应抛出NoSuchProviderException

    同样,应将这些类型的异常设置为受检异常,因为它会强制调用者处理方法语义所固有的问题。

  5. 规则是,如果可以合理地期望客户端从异常中恢复,请将其设置为受检的异常。 如果客户端无法采取任何措施来从异常中恢复,请将其设置为非受检的异常。

    实际上,大多数应用程序必须从几乎所有异常中恢复,包括NullPointerExceptionIllegalArgumentExceptions和许多其他非受检的异常。 失败的操作/事务将被中止,但应用程序必须保持活动状态并准备为下一个操作/事务提供服务。

    通常只有在启动期间关闭应用程序才是合法的。 例如,如果缺少配置文件,并且没有该配置文件,应用程序将无法执行任何明智的操作,则关闭该应用程序是合法的。

4. 总结

在本文中,我们了解了 Java 中受检异常与非受检异常之间的区别,以及如何处理非受检异常(带有示例的 Java 中的异常层次结构)。

随时在评论中提问。

学习愉快!

阅读更多:

Java 同步和异步异常

原文: https://howtodoinjava.com/java/exception-handling/asynchronous-and-synchronous-exceptions-in-java/

在本 Java 教程中,了解 Java 中的异步和同步异常。 了解受检和非受检的异常的不同之处。

1. 异步和同步异常

通常,Java 在发现时,会根据“定时”将异常分为两类。 这些类别是受检和非受检的异常。

类似地,基于出现位置,Java 异常可以进一步分为两类。

  1. 同步异常
  2. 异步异常

2. 同步异常

同步异常发生在特定的程序语句上,无论我们在类似的执行环境中运行一个程序多少次。

同步异常的例子是我们在作为开发人员的日常生活中所关心的东西,例如NullPointerExceptionArrayIndexOutOfBoundsException等。

例如,我们以相同的输入运行 Java 程序“N”次。 如果NullPointerException出现在行号“M”,则它们每次都将出现在同一行号。 这是 Java 中同步异常的示例。

3. 异步异常

异步异常实际上可以引发任何地方。 因此,编译器不需要异步异常处理。 它们也很难编程。

自然异步事件的示例包括按Ctrl-C中断程序,或从另一个执行线程接收信号,例如“停止”或“挂起”。 例如,如果您在应用程序执行过程中按了CTRL + C N 次,则没人能保证应用程序将在其上结束的行号。

我希望有关 Java 同步和异步异常的讨论将有助于您进行编程活动以及 Java 面试

学习愉快!

阅读更多: Oracle 文档

Java NullPointerException - 如何在 Java 中有效处理空指针

原文: https://howtodoinjava.com/java/exception-handling/how-to-effectively-handle-nullpointerexception-in-java/

Java NullPointerException是非受检的异常,并且扩展了RuntimeExceptionNullPointerException不会强迫我们使用catch块来处理它。 对于大多数 Java 开发人员社区而言,此异常非常像一场噩梦。 当我们最不期望它们时,它们通常会弹出。

在寻找解决此类问题的原因和最佳方法时,我也花费了大量宝贵的时间。 我将在这里编写一些最佳实践,这些最佳实践遵循了行业惯例,一些专家讲座以及我自己的经验。

Table of Contents

1\. Why NullPointerException occur in the code
2\. Common places where NullPointerException usually occur
3\. Best ways to avoid NullPointerException
4\. Available NullPointerException safe operations
5\. What if you must allow NullPointerException in some places

1. 为什么在代码中出现NullPointerException

NullPointerException是代码中您尝试访问/修改尚未初始化的对象的情况。 从本质上讲,这意味着对象引用变量未指向任何位置,并且未引用任何内容或引用了null。 一个抛出空指针异常的示例 Java 程序。

package com.howtodoinjava.demo.npe;

public class SampleNPE 
{
   public static void main(String[] args) 
   {
      String s = null;
      System.out.println( s.toString() );   // 's' is un-initialized and is null
   }
}

2. Java NullPointerException通常发生的常见位置

好吧,由于各种原因,NullPointerException可能会出现在代码中的任何位置,但我根据自己的经验准备了最常用的地点清单。

  1. 在未初始化的对象上调用方法
  2. 方法中传递的参数为null
  3. null的对象上调用toString()方法
  4. 在不检查null相等性的情况下比较if块中的对象属性
  5. 对于像 Spring 这样的依赖注入的框架来说,配置不正确
  6. null的对象上使用synchronized
  7. 链接语句,即单个语句中的多个方法调用

这不是详尽的清单。 还有其他几个地方和原因。 如果您还可以回忆起其他任何内容,请发表评论。 它也会帮助其他人(初学者)。

3. 避免 Java NullPointerException的最佳方法

3.1 三元运算符

如果运算符不为null,则结果为左侧的值,否则求值右侧。 它具有如下语法:

boolean expression ? value1 : value2;

如果expression计算为true,则整个表达式将返回value1,否则返回value2。 它更像if-else构造,但是更有效和更具表现力。 为了防止NullPointerException(NPE),请像下面的代码一样使用此运算符:

String str = (param == null) ? "NA" : param;

3.2 使用 Apache Commons StringUtils进行字符串操作

Apache Commons lang 是几个工具类的集合,用于各种操作之王。 其中之一是StringUtils.java。 使用StringUtils.isNotEmpty()验证作为参数传递的字符串是否为空或空字符串。 如果不为null或为空; 然后进一步使用它。

其他类似方法是StringUtils.IsEmpty()StringUtils.equals()。他们在自己的 javadocs 中声称,如果StringUtils.isNotBlank()抛出 NPE,则 API 中存在错误。

if (StringUtils.isNotEmpty(obj.getvalue())){
    String s = obj.getvalue();
    ....
}

3.3 尽早检查方法参数是否为空

您应该始终将输入验证置于方法的开头,以使其余代码不必处理输入错误的可能性。 因此,如果有人传递空值,那么事情将在栈的早期破裂,而不是在更难以识别根本问题的更深的位置破裂。

在大多​​数情况下,针对快速失败行为的选择是一个不错的选择。

3.4 考虑原始类型而不是对象

当对象引用指向无内容时,将发生空问题。 因此,尽可能多地使用原始类型始终是安全的,因为它们不会受到空引用的影响。 所有原始类型都必须附加一些默认值,因此请当心。

3.5 仔细考虑链接方法调用

虽然链式语句很容易在代码中查看,但它们不是 NPE 友好的。 一条分散在多行中的语句将为您提供栈跟踪中第一行的行号,无论它出现在何处。

ref.method1().method2().method3().methods4();

这些链接的语句将仅打印“NullPointerException在行号xyz中发生”。确实很难调试此类代码。 避免这样的调用。

3.6 使用String.valueOf()而不是toString()

如果您必须打印任何对象的字符串表示形式,请不要使用object.toString()。 这是 NPE 的非常软的目标。 而是使用String.valueOf(object)
即使object在第二种方法中为null,它也不会给出异常,并且将输出“null”到输出流。

3.7 避免从方法中返回null

避免 NPE 的一个很棒的技巧是返回空字符串或空集合,而不是返回null。 在您的应用程序中一致地执行此操作。 您会注意到,如果这样做,则不需要进行大量的空检查。

例如:

List<string> data = null;

@SuppressWarnings("unchecked")
public List getDataDemo()
{
   if(data == null)
      return Collections.EMPTY_LIST; //Returns unmodifiable list
   return data;
}

使用上述方法的用户即使错过了空检查,也不会看到难看的 NPE。

3.8 不鼓励传递空参数

我已经看到一些方法声明,其中方法需要两个或多个参数。 如果参数之一作为null传递,则方法以某种不同的方式起作用。 避免这种情况。

相反,您应该定义两种方法; 一个带有单个参数,第二个带有两个参数。 强制传递参数。 当您在方法内部编写应用程序逻辑时,这很有帮助,因为您可以确保方法参数不会为null。 因此您不会提出不必要的假设和主张。

3.9 在“安全”的非空字符串上调用String.equals(String)

代替在下面的代码中编写的字符串比较

public class SampleNPE {
   public void demoEqualData(String param) {
      if (param.equals("check me")) {
         // some code
      }
   }
}

像这样写上面的代码。 即使参数作为null传递,这也不会引起 NPE。

public class SampleNPE {
   public void demoEqualData(String param) {
      if ("check me".equals(param)) // Do like this
      {
         // some code
      }
   }
}

4. 可用的NullPointerException安全操作

4.1 实例运算符

instanceof运算符是 NPE 安全的。 因此,instanceof null总是返回false。 它不会导致NullPointerException。 如果您记住这一事实,则可以消除混乱的条件代码。

// Unnecessary code
if (data != null &amp;&amp; data instanceof InterestingData) {
}

// Less code. Better!!
if (data instanceof InterestingData) {
}

4.2 访问类的静态成员

如果您要处理静态变量或静态方法,则即使您的引用变量指向null,也不会获得null指针异常,因为静态变量和方法调用是在编译时根据类名绑定的,并且与对象无关

MyObject obj = null;
String attrib = obj.staticAttribute; //no NullPointerException because staticAttribute is static variable defined in class MyObject

如果您知道更多这样的语言构造,当遇到null时也不会失败,请告诉我。

5. 如果必须在某些地方允许NullPointerException怎么办

《Effective Java》中的 Joshua bloch 说:“可以说,所有错误的方法调用都可以归结为非法参数或非法状态,但是其他异常通常用于某些类型的非法参数和状态。 如果调用者在某个参数中传递了null,该参数禁止使用null值,则约定将抛出NullPointerException而不是IllegalArgumentException。”

因此,如果必须在代码中的某些位置允许NullPointerException,则请确保使其具有更多信息,然后通常是这样。 看下面的例子:

package com.howtodoinjava.demo.npe;

public class SampleNPE {
   public static void main(String[] args) {
      // call one method at a time
      doSomething(null);
      doSomethingElse(null);
   }

   private static String doSomething(final String param) {
      System.out.println(param.toString());
      return "I am done !!";
   }

   private static String doSomethingElse(final String param) {
      if (param == null) {
         throw new NullPointerException(
               " :: Parameter 'param' was null inside method 'doSomething'.");
      }
      System.out.println(param.toString());
      return "I am done !!";
   }
}

这两个方法调用的输出是这样的:

Exception in thread "main" java.lang.NullPointerException
 at com.howtodoinjava.demo.npe.SampleNPE.doSomething(SampleNPE.java:14)
 at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)

Exception in thread "main" java.lang.NullPointerException:  :: Parameter 'param' was null inside method 'doSomething'.
 at com.howtodoinjava.demo.npe.SampleNPE.doSomethingElse(SampleNPE.java:21)
 at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)

显然,第二个栈跟踪更有用,并且使调试容易。 将来使用。

到目前为止,我对NullPointerException的了解已完成。 如果您知道有关该主题的其他观点,请与我们所有人分享!

学习愉快!

Java 自定义异常 – 最佳实践

原文: https://howtodoinjava.com/java/exception-handling/best-practices-for-for-exception-handling/

我们已经在几乎所有行业标准应用程序的代码中使用了 java 自定义异常处理。 通常的方法是创建一些自定义的异常类,以扩展基本的异常处理最佳实践,这可能会使您更有意义。

1)Java 自定义异常处理 - 新方法

1.1 传统异常处理

我们的新方法针对每个新的异常情况使用静态内部类

传统上,我们通过扩展Exception类来创建DBException类。 现在,每次遇到需要抛出与数据库相关的异常的情况时,我们通常会创建DBException的实例,以消息的形式放置一些信息并将其抛出。

现在,让我们考虑以下几种情况,我们发现需要抛出DBException

  1. SQL 执行错误
  2. 我们预计至少需要一行数据,就没有数据
  3. 存在多行,我们希望只有一行
  4. 无效的参数错误
  5. 还有更多这样的情况

上述方法的问题在于,在应处理这些异常的catch块或应用程序代码中,DBException没有提供足够的信息来唯一地处理每个居留权列出的用例。

1.2 内部类的新异常处理

让我们使用内部类解决上述问题,我们将为每个用例创建一个类,然后将其分组到DBException类中。

让我们从创建为abstractBaseException类开始,它将是我们所有异常类的超类。

// Make this class abstract so that developers are forced to create
// suitable exception types only
public abstract class BaseException extends Exception{
	//Each exception message will be held here
	private String message;

	public BaseException(String msg)
	{
		this.message = msg;
	}
	//Message can be retrieved using this accessor method
	public String getMessage() {
		return message;
	}
}

不是时候创建新的Exception内部类。

public class DBExeption
{
	//SQL execution error
	public static class BadExecution extends BaseException
	{
		private static final long serialVersionUID = 3555714415375055302L;
		public BadExecution(String msg) {
			super(msg);
		}
	}

	//No data exist where we expect at least one row
	public static class NoData extends BaseException
	{
		private static final long serialVersionUID = 8777415230393628334L;
		public NoData(String msg) {
			super(msg);
		}
	}

	//Multiple rows exist where we expect only single row
	public static class MoreData extends BaseException
	{
		private static final long serialVersionUID = -3987707665150073980L;
		public MoreData(String msg) {
			super(msg);
		}
	}

	//Invalid parameters error
	public static class InvalidParam extends BaseException
	{
		private static final long serialVersionUID = 4235225697094262603L;
		public InvalidParam(String msg) {
			super(msg);
		}
	}
}

在这里,我们为开始时确定的每种可能的错误情况创建了一个内部类。 可能还有更多其他功能。 仅取决于您确定和添加更多类。

1.3 如何使用自定义异常

现在,要了解它的用处,让我们创建一个异常并将其抛出。 然后,我们将在日志中看到错误消息。

public class TestExceptions {
	public static void main(String[] args)
	{
		try
		{
			throw new DBExeption.NoData("No row found for id : x");
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

程序输出:

com.exception.DBExeption$NoData: No row found for id : x
at com.test.TestExceptions.main(TestExceptions.java:7)

如您所见,在异常栈跟踪中的日志消息,它已变得更具信息性。 它清楚地说明了什么是错误。 同样在应用程序代码中,您可以检查自定义异常的实例并进行相应的处理。

2. 使用内部类作为自定义异常的优点

  1. 最重要的好处是,如果您的开发人员编写了一些可疑的消息文本,那么您也可以清楚地观察到实际出了什么问题。
  2. 您可以在处理不同异常情况的不同情况下使用实例比较。
  3. 您无需针对大量特殊情况发送单个异常。
  4. 对于知道期望确切的异常类的否定情况,它很容易编写单元测试用例。
  5. 日志记录更加有意义和有用。

我希望有关 Java 自定义异常的帖子对您​​有所帮助。 如果您有任何建议,请给我留言。

学习愉快!

构造器可以声明初始化器块中引发的受检异常

原文: https://howtodoinjava.com/java/exception-handling/checked-exceptions-thrown-in-initializer-blocks-can-be-declared-by-the-constructors/

这篇文章是对 Java 鲜为人知的特性的继续。 在上一篇文章中,我介绍了“相同类的实例可以访问彼此的私有成员”,以及一些非常令人惊讶的sun.misc.Unsafe类的用法。 在本文中,我将讨论一个关于初始化块的鲜为人知的特性。

初始化块是括号之间的代码块,该代码块在创建类实例之前甚至在调用构造器之前执行。 完全没有必要将它们包括在您的类中。

初始化器可以通过两种方式使用:

1)非静态初始化器块

它取决于对象,并且为创建的类的每个对象执行初始化块。 它可以初始化类的实例成员变量。

2)静态初始化器块

它使用关键字static定义,并且在加载类时执行一次,并且有限制,它只能初始化类的静态数据成员。

这就是我们所知道的。 现在进入我们很多人以前都不知道的部分。

有时,在初始化器块中,您可能需要编写一些代码,这些代码可能引发受检异常。 受检异常是那些在编译时受检异常,编译器会强制您在代码中处理它们。 让我们举个例子:

	public class CheckedExceptionsFromConstrctor {
		Document doc = null;
		{
			doc = new SAXBuilder(false).build(new StringReader(new String("<users/>")));
		}
	}

上面的代码引发了两个受检的异常IOExceptionJDOMException。您可以使用try-catch处理它们。 例如:

	{
		try {
			doc = new SAXBuilder(false).build(new StringReader(new String("<users/>")));
		} catch (JDOMException | IOException e) {
			e.printStackTrace();
		}
	}

如果您不希望在初始化器中处理异常并尝试抛出该异常,则编译器将不允许您这样做。

	{
		try {
			doc = new SAXBuilder(false).build(new StringReader(new String("<users/>")));
		} catch (JDOMException | IOException e) {
			throw e;								//Not allowed
		}
	}

解决方案:在所有构造器中为所有受检异常添加throws子句

在构造器中添加throws子句,您将可以从初始化器中抛出受检异常。 例如

public class CheckedExceptionsFromConstrctor
{
	Document doc = null;
	public CheckedExceptionsFromConstrctor() throws IOException, JDOMException
	{
		//Some other code
	}
	public CheckedExceptionsFromConstrctor(String s) throws IOException, JDOMException
	{
		//Some other code
	}
	{
		doc = new SAXBuilder(false).build(new StringReader(new String("<users/>")));
	}
}

或者:

public class CheckedExceptionsFromConstrctor
{
	Document doc = null;
	public CheckedExceptionsFromConstrctor() throws IOException, JDOMException
	{
		//Some other code
	}
	public CheckedExceptionsFromConstrctor(String s) throws IOException, JDOMException
	{
		//Some other code
	}
	{
		try {
			doc = new SAXBuilder(false).build(new StringReader(new String("<users/>")));
		} catch (JDOMException | IOException e) {
			throw e;
		}
	}
}

以上两种解决方案均有效,编译器将允许您执行此操作。

静态初始化器无法引发受检异常

上面的推理是针对非静态初始化器的。 如果您的类中有静态初始化器,则必须处理受检异常。 您不得以任何可能的方式扔它们。

public class CheckedExceptionsFromConstrctor
{
	static Document doc = null;
	public CheckedExceptionsFromConstrctor() 
	{
		//Some other code
	}
	static {
		try {
			doc = new SAXBuilder(false).build(new StringReader(new String("<users/>")));
		} catch (JDOMException | IOException e) {
			e.printStackTrace();				//You must handle the exception here
		}
	}
}

就是这个话题。 希望你喜欢它。 如果您在评论部分有任何想法,我们将不胜感激。 而且,不要忘了通过电子邮件订阅,以获取诸如此类鲜为人知的新特性。

祝您学习愉快!

Java 系统属性

原文: https://howtodoinjava.com/java/basics/java-system-properties/

Java 为其操作维护了一组系统属性。 每个 java 系统属性是一个键值(字符串-字符串)对,例如java.version = "1.7.0_09"。 您可以通过System.getProperties()检索所有系统属性,也可以通过System.getProperty(key)检索单个属性。

请注意,Java 安全管理器和策略文件可能会限制对系统属性的访问。 默认情况下,Java 程序可以不受限制地访问所有系统属性。

Table of Contents

Important Java System Properties
Get System Property
Set System Property

重要的 Java 系统属性

  1. JRE 相关的系统属性

    java.home JRE 主目录,例如“ C:\Program Files\Java\jdk1.7.0_09\jre ”。
    java.library.path 用于搜索本机库的 JRE 库搜索路径。 通常但不一定来自环境变量PATH
    java.class.path JRE 类路径,例如'.' (点,用于当前工作目录)。
    java.ext.dirs JRE 扩展库路径,例如“ C:\Program Files\Java\jdk1.7.0_09\jre\lib\ext;C:\Windows\Sun\Java\lib\ext ”。
    java.version JDK 版本,例如 1.7.0_09
    java.runtime.version JRE 版本,例如 1.7.0_09-b05
  2. 文件相关的系统属性

    file.separator 文件目录分隔符的符号,例如'd:\test\test.java'。 对于 Windows,默认值为 '\',对于 Unix / Mac,默认值为'/'
    path.separator 用于分隔路径条目的符号,例如PATHCLASSPATH中的符号。 对于 Windows,默认值为';',对于 Unix / Mac,默认值为':'
    line.separator 行尾(或换行)的符号。 对于 Windows,默认值为"\r\n",对于 Unix / Mac OS X,默认值为"\n"
  3. 与用户相关的系统属性

    user.name 用户名。
    user.home 用户的主目录。
    user.dir 用户的当前工作目录。
  4. 与操作系统相关的系统属性

    os.name 操作系统的名称,例如“Windows 7”。
    os.version 操作系统的版本,例如“6.1”。
    os.arch 操作系统的架构,例如“x86”。

获取系统属性

如前所述,您可以通过System.getProperties()获取所有系统属性,也可以通过System.getProperty(key)检索单个属性。

import java.util.Properties;
public class PrintSystemProperties 
{
   public static void main(String[] a) 
   {
      // List all System properties
      Properties pros = System.getProperties();
      pros.list(System.out);

      // Get a particular System property given its key
      // Return the property value or null
      System.out.println(System.getProperty("java.home"));
      System.out.println(System.getProperty("java.library.path"));
      System.out.println(System.getProperty("java.ext.dirs"));
      System.out.println(System.getProperty("java.class.path"));
   }
}

设置系统属性

在 Java 中,可以从命令工具或从 Java 代码本身设置自定义系统属性。

  1. 从命令行设置系统属性(-D选项)

    java -Dcustom_key="custom_value" application_launcher_class
    
  2. 使用System.setProperty()方法从代码设置系统属性

    System.setProperty("custom_key", "custom_value");
    

这就是本基本教程的全部内容,内容涉及用 Java 读写系统属性。

学习愉快!

Java 泛型教程

完整的 Java 泛型教程

原文: https://howtodoinjava.com/java/generics/complete-java-generics-tutorial/

Java 中的泛型作为 JDK 5 的特性之一引入。就个人而言,我发现泛型中使用的尖括号<>非常吸引人,它总是使我不得不重新思考我在哪里使用它,或查看它是否以其他人的代码编写。 坦率地说,很长一段时间以来我一直在使用泛型,但我仍然不完全有信心盲目使用它。 在本教程中,我将介绍对 java 泛型有用的所有内容以及与它们有关的内容。 如果您认为我可以在教程的任何部分使用更精确的词,或者可以添加示例,或者您不同意我的观点; 给我留言。 我很高兴知道您的观点。

Table of content

1) Why Generics?
2) How Generics works in Java
3) Types of Generics?
   i)  Generic Type Class or Interface
   ii) Generic Type Method or Constructor
4) Generic Type Arrays
5) Generics with Wildcards
    i)  Unbounded Wildcards
    ii)  Bounded Wildcards
        a)  Upper Bounded Wildcards
        b)  Lower Bounded Wildcards
6) What is not allowed to do with Generics?

Java 泛型”是一个技术术语,表示与泛型类型和方法的定义和使用有关的一组语言特性。 在 Java 中,泛型类型或方法与常规类型和方法的不同之处在于它们具有类型参数。

“Java 泛型是一种语言特性,允许定义和使用泛型类型和方法。”

通过提供替代正式类型参数的实际类型参数,实例化泛型类型以形成参数化类型。 像LinkedList<E>这样的类是泛型,其类型参数为 E。 诸如LinkedList<Integer>LinkedList<String>之类的实例称为参数化类型,而StringInteger是各自的实际类型参数。

1)为什么要泛型?

如果仔细观察 Java 集合框架类,则您会发现大多数类都采用Object类型的参数/参数,并从方法中返回Object作为值。 现在,以这种形式,他们可以将任何 Java 类型用作参数并返回相同的值。 它们本质上是异构的,即不是特定的相似类型。

像我们这样的程序员经常想指定一个集合只包含某种类型的元素,例如 IntegerStringEmployee。 在原始的集合框架中,如果在代码中添加一些检查之前没有添加额外的检查,就不可能拥有同类收集。 引入泛型来消除此限制是非常具体的。 他们会在编译时自动在代码中添加这种类型的参数检查。 这可以节省我们编写大量不必要的代码的时间,如果编写正确的话,这些代码实际上不会在运行时添加任何值。

“用通俗易懂的术语来说,泛型使用 Java 语言强制类型安全。”

没有这种类型的安全性,您的代码可能会感染各种错误,而这些错误只会在运行时才被发现。 使用泛型,使它们在编译时本身突出显示,甚至在获得 Java 源代码文件的字节码之前,也使代码健壮。

“泛型通过在编译时检测到更多错误来增加代码的稳定性。”

因此,现在我们有了一个合理的想法,为什么泛型首先出现在 java 中。 下一步是了解有关它们如何在 Java 中工作的知识。 在源代码中使用泛型时实际发生的情况。

2)泛型在 Java 中的工作方式

泛型的核心是“类型安全性”。 类型安全到底是什么? 编译器只是保证,如果在正确的位置使用正确的类型,则运行时不应有任何ClassCastException。 用例可以是Integer的列表,即List<Integer>。 如果您在 Java 中声明List<Integer>之类的列表,则 Java 保证它将检测并报告您将任何非整数类型插入上述列表的任何尝试。

Java 泛型中的另一个重要术语是“类型擦除”。 从本质上讲,这意味着使用泛型添加到源代码中的所有额外信息将从其生成的字节码中删除。 在字节码内部,它将是旧的 Java 语法,如果您根本不使用泛型,则会得到该语法。 当未在语言中添加泛型时,这必然有助于生成和执行在 Java 5 之前编写的代码。

让我们看一个例子。

List<Integer> list = new ArrayList<Integer>();

list.add(1000);     //works fine

list.add("lokesh"); //compile time error; 

当您编写上述代码并进行编译时,会出现以下错误:“类型为List<Integer>add(Integer)方法不适用于参数(String)”。 编译器警告您。 这正是泛型的唯一目的,即类型安全。

第二部分是从上述示例中删除第二行后获得字节码。 如果将上面的示例的字节码与泛型进行比较,则没有任何区别。 显然,编译器删除了所有泛型信息。 因此,上面的代码与没有泛型的下面的代码非常相似。

List list = new ArrayList();

list.add(1000);     

“准确地说,Java 中的泛型不过是类型安全代码的语法糖,所有此类类型信息都将由编译器的类型擦除特性擦除。”

3)泛型的类型?

现在我们对泛型的意义有了一些了解。 现在开始探索围绕泛型的其他重要概念。 我将从确定各种方法开始,泛型可以应用于源代码。

泛型类或接口

如果一个类声明一个或多个类型变量,则它是泛型的。 这些类型变量称为类的类型参数。 让我们看一个例子。

DemoClass是简单的 Java 类,具有一个属性t(也可以不止一个); 属性的类型是Object

class DemoClass {
   private Object t;

   public void set(Object t) { this.t = t; }

   public Object get() { return t; }
}

在这里,我们希望一旦使用某种类型初始化了该类,则该类应仅与该特定类型一起使用。 例如如果我们要让一个类的实例保存类型为“String”的值t,那么程序员应该设置并获取唯一的String类型。 由于我们已将属性类型声明为Object,因此无法强制执行此限制。 程序员可以设置任何对象。 由于所有 Java 类型都是Object类的子类型,因此可以从get方法获得任何返回值类型。

要强制执行此类型限制,我们可以使用以下泛型:

class DemoClass<T> {
   //T stands for "Type"
   private T t;

   public void set(T t) { this.t = t; }

   public T get() { return t; }
}

现在我们可以放心,不会将类误用于错误的类型。 DemoClass的用法示例如下所示:

DemoClass<String> instance = new DemoClass<String>();
instance.set("lokesh");   //Correct usage
instance.set(1);        //This will raise compile time error

上面的类比也适用于接口。 让我们快速看一个示例,以了解如何在 Java 接口中使用泛型类型信息。

//Generic interface definition
interface DemoInterface<T1, T2> 
{
   T2 doSomeOperation(T1 t);
   T1 doReverseOperation(T2 t);
}

//A class implementing generic interface
class DemoClass implements DemoInterface<String, Integer>
{
   public Integer doSomeOperation(String t)
   {
      //some code
   }
   public String doReverseOperation(Integer t)
   {
      //some code
   }
}

我希望我足够清楚地介绍泛型类和接口。 现在该看一下泛型方法和构造器了。

泛型方法或构造器

泛型方法与泛型类非常相似。 它们仅在一个方面不同,即类型信息的范围仅在方法(或构造器)内部。 泛型方法是引入自己的类型参数的方法。

让我们通过一个例子来理解这一点。 下面是一种泛型方法的代码示例,该方法可用于在该类型的变量列表中查找所有出现的类型参数。

public static <T> int countAllOccurrences(T[] list, T item) {
   int count = 0;
   if (item == null) {
      for ( T listItem : list )
         if (listItem == null)
            count++;
   }
   else {
      for ( T listItem : list )
         if (item.equals(listItem))
            count++;
   }
   return count;
}   

如果在此方法中传递String的列表和另一个字符串进行搜索,它将可以正常工作。 但是,如果尝试在String列表中找到Number,则会出现编译时错误。

与上述相同可以作为泛型构造器的示例。 让我们也为泛型构造器提供一个单独的示例。

class Dimension<T>
{
   private T length;
   private T width;
   private T height;

   //Generic constructor
   public Dimension(T length, T width, T height)
   {
      super();
      this.length = length;
      this.width = width;
      this.height = height;
   }
}

在此示例中,Dimension类的构造器也具有类型信息。 因此,您可以仅具有单一类型的所有属性的维度实例。

4)泛型数组

任何语言中的数组都具有相同的含义,即数组是元素类型相似的集合。 在 Java 中,在运行时将任何不兼容的类型推入数组将抛出ArrayStoreException。 这意味着数组在运行时保留其类型信息,而泛型使用类型擦除或在运行时删除任何类型信息。 由于上述冲突,不允许在 Java 中实例化泛型数组。

public class GenericArray<T> {
    // this one is fine
    public T[] notYetInstantiatedArray;

    // causes compiler error; Cannot create a generic array of T
    public T[] array = new T[5];
}

与上述泛型类型类和方法相同,我们可以在 java 中拥有泛型数组。 如我们所知,数组是元素类型相似的集合,并且推送任何不兼容的类型都会在运行时抛出ArrayStoreExceptionCollection类不是这种情况。

Object[] array = new String[10];
array[0] = "lokesh";
array[1] = 10;      //This will throw ArrayStoreException

犯上述错误并不是很难。 它可以随时发生。 因此,最好也将类型信息提供给数组,以便在编译时就捕获错误。

数组不支持泛型的另一个原因是数组是协变的,这意味着超类型引用的数组是子类型引用的数组的超类型。 也就是说,Object[]String[]的超类型,并且可以通过类型Object[]的引用变量访问字符串数组。

Object[] objArr = new String[10];  // fine
objArr[0] = new String(); 

5)带有通配符的泛型

在泛型代码中,称为通配符的问号(?)表示未知类型。 通配符参数化类型是泛型类型的实例,其中至少一个类型参数是通配符。 通配符参数化类型的示例是Collection<?>List<? extends Number>Comparator<? super String>Pair<String, ?>。 通配符可以在多种情况下使用:作为参数,字段或局部变量的类型; 有时作为返回类型(尽管更具体的做法是更好的编程习惯)。 通配符从不用作泛型方法调用,泛型类实例创建或超类型的类型参数。

在不同的地方使用通配符也具有不同的含义。 例如

  • Collection<?>表示Collection接口的所有实例化,与类型参数无关。
  • List<? extends Number>表示所有列表类型,其中元素类型是Number的子类型。
  • Comparator<? super String>表示Comparator接口的所有实例化,这些实例化类型是String的超类型。

通配符参数化类型不是可能出现在新表达式中的具体类型。 它只是暗示了 Java 泛型所执行的规则,即在使用通配符的任何特定情况下,哪种类型均有效。

例如,以下是涉及通配符的有效声明:

Collection<?> coll = new ArrayList<String>(); 
//OR
List<? extends Number> list = new ArrayList<Long>(); 
//OR
Pair<String,?> pair = new Pair<String,Integer>();

以下是通配符的无效用法,它们将给出编译时错误。

List<? extends Number> list = new ArrayList<String>();  //String is not subclass of Number; so error
//OR
Comparator<? super String> cmp = new RuleBasedCollator(new Integer(100)); //Integer is not superclass of String

泛型中的通配符可以是无界的,也可以是有界的。 让我们找出不同方面的差异。

无界通配符参数化类型

一种泛型类型,其中所有类型参数都是无界通配符?,对类型变量没有任何限制。 例如

ArrayList<?>  list = new ArrayList<Long>();  
//or
ArrayList<?>  list = new ArrayList<String>();  
//or
ArrayList<?>  list = new ArrayList<Employee>();  

有界通配符参数化类型

有界通配符对可能的类型施加了一些限制,您可以用来实例化参数化类型。 使用关键字superextends强制执行此限制。 为了更清楚地区分,我们将它们分为上限通配符和下限通配符。

上限通配符

例如,假设您要编写一种适用于List<String>List<Integer>List<Double>的方法,则可以使用上限通配符来实现,例如您将指定List<? extends Number>。 这里的IntegerDoubleNumber类的子类型。 用通俗易懂的话来说,如果您希望泛型表达式接受特定类型的所有子类,则可以使用extends关键字来使用上限通配符。

public class GenericsExample<T>
{
   public static void main(String[] args)
   {
      //List of Integers
      List<Integer> ints = Arrays.asList(1,2,3,4,5);
      System.out.println(sum(ints));

      //List of Doubles
      List<Double> doubles = Arrays.asList(1.5d,2d,3d);
      System.out.println(sum(doubles));

      List<String> strings = Arrays.asList("1","2");
      //This will give compilation error as :: The method sum(List<? extends Number>) in the 
      //type GenericsExample<T> is not applicable for the arguments (List<String>)
      System.out.println(sum(strings));

   }

   //Method will accept 
   private static Number sum (List<? extends Number> numbers){
      double s = 0.0;
      for (Number n : numbers)
         s += n.doubleValue();
      return s;
   }
}

下界通配符

如果您希望泛型表达式接受所有类型,即特定类型的“超”类型或特定类的父类,则可以使用super关键字使用下界通配符。

在下面给出的示例中,我创建了三个类,即SuperClassChildClassGrandChildClass。 下面的代码显示了这种关系。 现在,我们必须创建一个以某种方式(例如从 DB)获取GrandChildClass信息并创建其实例的方法。 我们希望将此新的GrandChildClass存储在GrandChildClasses的现有列表中。

这里的问题是GrandChildClassChildClassSuperClass的子类型。 因此,任何SuperClassesChildClasses的泛型列表都可以容纳GrandChildClasses。 在这里,我们必须使用super关键字来使用下界通配符。

package test.core;

import java.util.ArrayList;
import java.util.List;

public class GenericsExample<T>
{
   public static void main(String[] args)
   {
      //List of grand children
      List<GrandChildClass> grandChildren = new ArrayList<GrandChildClass>();
      grandChildren.add(new GrandChildClass());
      addGrandChildren(grandChildren);

      //List of grand childs
      List<ChildClass> childs = new ArrayList<ChildClass>();
      childs.add(new GrandChildClass());
      addGrandChildren(childs);

      //List of grand supers
      List<SuperClass> supers = new ArrayList<SuperClass>();
      supers.add(new GrandChildClass());
      addGrandChildren(supers);
   }

   public static void addGrandChildren(List<? super GrandChildClass> grandChildren) 
   {
      grandChildren.add(new GrandChildClass());
      System.out.println(grandChildren);
   }
}

class SuperClass{

}
class ChildClass extends SuperClass{

}
class GrandChildClass extends ChildClass{

}

6)不允许使用泛型?

到目前为止,我们已经了解了许多可使用 Java 中的泛型来避免应用程序中许多ClassCastException实例的方法。 我们还看到了通配符的用法。 现在是时候确定一些 Java 泛型中不允许执行的任务。

a)您不能使用类型的静态字段

您不能在类中定义静态的泛型参数化成员。 这样做的任何尝试都会产生编译时错误:无法静态引用非静态类型T

public class GenericsExample<T>
{
   private static T member; //This is not allowed
}

b)您不能创建T的实例

任何创建T实例的尝试都会失败,并显示以下错误:无法实例化类型T

public class GenericsExample<T>
{
   public GenericsExample(){
      new T();
   }
}

c)泛型与声明中的原始类型不兼容

对,是真的。 您不能声明泛型表达式,例如ListMap <String,double>。 绝对可以使用包装器类代替原始类型,然后在传递实际值时使用原始类型。 通过使用自动装箱将原始类型转换为相应的包装器类,可以接受这些值原始类型。

final List<int> ids = new ArrayList<>();    //Not allowed

final List<Integer> ids = new ArrayList<>(); //Allowed

d)您无法创建泛型异常类

有时,程序员可能需要传递泛型类型的实例以及引发异常。 在 Java 中这是不可能的。

// causes compiler error
public class GenericException<T> extends Exception {}

当您尝试创建这样的异常时,您将得到如下消息:泛型类GenericException可能不是子类java.lang.Throwable

到此为止,这一切都结束了,这次是关于 java 泛型的讨论。 在以后的文章中,我将提出与泛型相关的更多有趣的事实和特性。

如果有不清楚的地方,请给我评论/或者您还有其他问题。

学习愉快!

Java 泛型 PECS - 生产者extends消费者super

原文: https://howtodoinjava.com/java/generics/java-generics-what-is-pecs-producer-extends-consumer-super/

昨天,我正在研究一些 Java 集合 API,并且发现了两种主要用于将元素添加到集合中的方法。 他们俩都使用泛型语法来获取方法参数。 但是,第一种方法是使用<? super T>,而第二种方法是使用<? extends E>。 为什么?

首先,让我们看一下这两种方法的完整语法。

此方法负责将集合c的所有成员添加到另一个调用此方法的集合中。

boolean addAll(Collection<? extends E> c);

调用此方法可将“元素”添加到集合c

public static <T> boolean addAll(Collection<? super T> c, T... elements);

两者似乎都在做简单的事情,所以为什么它们都有不同的语法。 我们许多人可能会纳闷。 在这篇文章中,我试图揭开围绕它的概念的神秘性,该概念主要被称为 PECS (最早由 Joshua Bloch 在他的著作《Effective Java》中创造的术语)。

为什么要使用泛型通配符?

在我与 java 泛型 相关的上一篇文章中,我们了解到,泛型本质上用于类型安全性和不变式。 用例可以是 Integer 的列表,即List<Integer>。 如果您在 Java 中声明List<Integer>之类的列表,则 Java 保证它将检测并报告您将任何非整数类型插入上述列表的任何尝试。

但是很多时候,我们面临这样的情况,为了特定的目的,我们必须在方法中传递类的子类型或超类型作为参数。 在这些情况下,我们必须使用协变(缩小引用)和逆变(扩大引用)之类的概念。

了解<? extends T>

这是 PECS 的第一部分,即 PE(生产者extends。 为了将其与现实生活中的术语联系起来,让我们使用一篮子水果(即水果的集合)的类比。 当我们从篮子里摘水果时,我们要确保只取出水果而没有其他东西。 这样我们就可以编写如下泛型代码:

Fruit get = fruits.get(0);

在上述情况下,我们需要将水果的集合声明为List<? extends Fruit>。 例如:

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}

class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}

public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());

      //We can assign a list of apples to a basket of fruits;
      //because apple is subtype of fruit 
      List<? extends Fruit> basket = apples;

      //Here we know that in basket there is nothing but fruit only
      for (Fruit fruit : basket)
      {
         System.out.println(fruit);
      }

      //basket.add(new Apple()); //Compile time error
      //basket.add(new Fruit()); //Compile time error
   }
}

查看上面的for循环。 它确保了从篮子里出来的任何东西都肯定会结出果实; 因此,您可以遍历它,然后简单地将其浇铸成水果。 现在,在最后两行中,我尝试在购物篮中添加AppleFruit,但是编译器不允许我添加。 为什么?

如果我们考虑一下,原因很简单。 <? extends Fruit>通配符告诉编译器我们正在处理水果类型的子类型,但是我们无法知道哪个水果,因为可能存在多个子类型。 由于没有办法说出来,而且我们需要保证类型安全(不变性),因此您不能在此类结构内放置任何内容。

另一方面,由于我们知道它可能是Fruit的子类型,因此可以从结构中获取数据,并保证它是Fruit

在上面的示例中,我们从集合List<? extends Fruit> basket中取出元素。所以这个篮子实际上是在生产水果。 简而言之,当您只想从集合中检索元素时,请将其视为生产者并使用<? extends T>语法。 “生产者extends”现在对您来说更有意义。

了解<? super T>

现在以不同的方式查看上述用例。 假设我们正在定义一个方法,在此方法中,我们只会在此购物篮中添加不同的水果。 就像我们在帖子“ addAll(Collection<? super T> c, T... elements)”开头看到的方法一样。 在这种情况下,篮子用于存储元素,因此应称为元素的使用者。

现在看下面的代码示例:

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}

class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}

class AsianApple extends Apple {
   @Override
   public String toString() {
      return "I am an AsianApple !!";
   }
}

public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());

      //We can assign a list of apples to a basket of apples
      List<? super Apple> basket = apples;

      basket.add(new Apple()); 		//Successful
      basket.add(new AsianApple()); //Successful
      basket.add(new Fruit()); 		//Compile time error
   }
}

我们可以在篮子内添加苹果,甚至是亚洲苹果,但不能在篮子中添加Fruit(苹果的超类型)。 为什么?

原因是购物篮是对Apple的超类商品列表的引用。 同样,我们不知道它是是哪个超类型,但是我们知道可以将Apple及其任何子类型(它们是Fruit的子类型)添加为没有问题(您可以随时在超类的集合中添加一个子类)。 因此,现在我们可以在购物篮中添加任何类型的Apple

如何从这种类型的数据中获取数据呢? 事实证明,您唯一可以使用的是Object实例:由于我们无法知道它是哪个超类型,因此编译器只能保证它将是对Object的引用,因为Object是任何 Java 类型的超类型。

在上面的示例中,我们将元素放入集合List<? super Apple> basket; 所以在这里,这个篮子实际上是在消耗苹果等元素。 简而言之,当您只想在集合中添加元素时,请将其视为使用者并使用<? super T>语法。 现在,“消费者super”对您也应该更有意义。

总结

基于上述推理和示例,让我们总结要点。

  1. 如果需要从集合中检索类型T的对象,请使用<? extends T>通配符。
  2. 如果需要将T类型的对象放入集合中,请使用<? super T>通配符。
  3. 如果您需要同时满足这两个条件,请不要使用任何通配符。 就这么简单。
  4. 简而言之,请记住术语 PECS。 生产者extends消费者super。 真的很容易记住。

这就是 Java 中泛型中简单而又复杂的概念的全部。 通过评论让我知道您的想法。

祝您学习愉快!

Java 垃圾回收

Java 垃圾收集算法(直到 Java 9)

原文: https://howtodoinjava.com/java/garbage-collection/all-garbage-collection-algorithms/

垃圾收集(GC)一直是 Java 受欢迎的重要特征之一。 垃圾回收是 Java 中用来释放未使用的内存的机制。 本质上,它是跟踪所有仍在使用的对象,并将其余对象标记为垃圾。 Java 的垃圾回收被认为是一种自动内存管理架构,因为程序员不必将对象指定为可以被重新分配的对象。 垃圾回收在低优先级线程上运行。

在本教程中,我们将介绍与内存分配/解除分配,在后台运行的算法以及自定义此行为所需的选项有关的各种概念。

Table of Contents

Object Life Cycle
Garbage collection algorithms
	Mark and sweep
	Concurrent mark sweep (CMS) garbage collection
	Serial garbage collection
	Parallel garbage collection
	G1 garbage collection
Customization Options
Summary

对象生命周期

一个 Java 对象的生命周期可以分为三个阶段:

  1. 对象创建

    为了创建对象,通常我们使用new关键字。 例如

    Object obj = new Object();
    

    创建对象时,将分配特定数量的内存来存储该对象。 分配的内存量可能会根据架构和 JVM 而有所不同。

  2. 对象使用

    到那时为止,对象被应用程序的其他对象使用(其他活动对象具有指向它的引用)。 在使用过程中,对象驻留在内存中,并且可能包含对其他对象的引用。

  3. 对象销毁

    垃圾收集系统监视对象,并在可行的情况下计算对每个对象的引用数。 如果没有对对象的引用,则无法使用当前正在运行的代码来访问它,因此取消分配关联的内存是很有意义的。

垃圾收集算法

对象创建由您编写的代码完成; 以及用于使用其提供的特性的框架。 作为 Java 开发人员,我们不需要取消分配内存或取消引用对象。 垃圾收集器会在 JVM 级别自动完成此操作。 自 Java 诞生以来,在算法上进行了大量更新,这些算法在后台运行以释放内存。 让我们看看它们如何工作?

标记清除

它是初始且非常基本的算法,分为两个阶段运行:

  1. 标记活动对象 – 找出所有仍然存在的对象。
  2. 删除无法访问的对象 – 摆脱所有其他东西 – 所谓的已死和未使用的对象。

首先,GC 将某些特定对象定义为垃圾收集根。 例如当前执行方法的局部变量和输入参数,活动线程,已加载类的静态字段和 JNI 引用。 现在,GC 遍历了内存中的整个对象图,从这些根开始,然后是从根到其他对象的引用。 GC 访问的每个对象都被标记为活动对象。

需要停止应用程序线程以进行标记,因为如果它不断变化,它将无法真正遍历图。 它被称为 Stop The World 暂停。

第二阶段是清除未使用的对象以释放内存。 这可以通过多种方式来完成,例如

  • 正常删除 – 正常删除会将未引用的对象删除以释放空间并保留引用的对象和指针。 内存分配器(某种哈希表)保存对可分配新对象的可用空间块的引用。

    通常被称为mark-sweep算法。

    Normal Deletion - Mark and Sweep

    正常删除 - 标记清除

  • 带有压缩的删除 – 仅删除未使用的对象效率不高,因为可用内存块分散在整个存储区域中,并导致OutOfMemoryError,如果创建的对象足够大并且找不到足够大的内存块。

    为了解决此问题,删除未引用的对象后,将对其余的引用对象进行压缩。 这里的压缩指的是将参考对象一起移动的过程。 这使得新的内存分配变得更加容易和快捷。

    通常被称为mark-sweep-compact算法。

    Deletion with compacting

    带有压缩的删除

  • 带有复制的删除 – 与标记和补图方法非常相似,因为它们也会重新放置所有活动对象。 重要的区别是重定位的目标是不同的存储区域。

    通常被称为mark-copy算法。

    Deletion with copying - Mark and Sweep

    带有复制的删除 – 标记清除

在进一步阅读之前,我将真诚地建议您首先阅读 java 内存管理。 它详细讨论了新生代,老年代和永久代。

并发标记清除(CMS)垃圾收集

CMS 垃圾回收实质上是一种升级的标记和清除方法。 它使用多个线程扫描堆内存。 对其进行了修改,以利用更快的系统并增强了性能。

它尝试通过与应用程序线程同时执行大多数垃圾收集工作来最大程度地减少由于垃圾收集而造成的暂停。 它在新生代中使用并行的标记复制算法,在老一代中使用大多数并发的标记清除算法。

要使用 CMS GC,请使用以下 JVM 参数:

-XX:+UseConcMarkSweepGC
CMS GC 优化选项
标志 描述
-XX:+UseCMSInitiating\OccupancyOnly 表示您只想使用占用率作为启动 CMS 收集操作的条件。
-XX:CMSInitiating\OccupancyFraction=7 设置 CMS 生成占用率以启动 CMS 收集周期。
-XX:CMSTriggerRatio=70 这是在 CMS 周期开始之前分配的 CMS 生成中MinHeapFreeRatio的百分比。
-XX:CMSTriggerPermRatio=90 设置在开始 CMS 收集周期之前已分配的 CMS 永久代中MinHeapFreeRatio的百分比。
-XX:CMSWaitDuration=2000 使用参数指定允许 CMS 等待年轻集合的时间。
-XX:+UseParNewGC 选择使用并行算法收集年轻空间。
-XX:+CMSConcurrentMTEnabled 允许在并发阶段使用多个线程。
-XX:ConcGCThreads=2 设置用于并发阶段的并行线程数。
-XX:ParallelGCThreads=2 设置要用于 STW 阶段的并行线程数。
-XX:+CMSIncrementalMode 启用增量 CMS(iCMS)模式。
-XX:+CMSClassUnloadingEnabled 如果未启用此特性,CMS 将不会清除永久空间。
-XX:+ExplicitGCInvokes\Concurrent 这允许System.gc()触发并发收集,而不是整个垃圾收集周期。

串行垃圾收集

该算法为新生代使用标记复制,为老一代使用标记扫描压缩。 它在单个线程上工作。 执行时,它将冻结所有其他线程,直到垃圾回收操作结束。

由于串行垃圾回收具有线程冻结特性,因此仅适用于非常小的程序。

要使用串行 GC,请使用以下 JVM 参数:

-XX:+UseSerialGC

并行垃圾收集

是串行 GC 类似,在新生代中使用mark-copy,在老一代中使用mark-sweep-compact。 多个并发线程用于标记和复制/压缩阶段。 您可以使用-XX:ParallelGCThreads=N选项配置线程数。

如果您的主要目标是通过有效利用现有系统资源来提高吞吐量,那么并行垃圾收集器将适用于多核计算机。 使用这种方法,可以大大减少 GC 循环时间。

直到 Java 8,我们都将并行 GC 视为默认的垃圾收集器。 从 Java 9 始,G1 是 32 位和 64 位服务器配置上的默认垃圾收集器。– JEP 248

要使用并行 GC,请使用以下 JVM 参数:

-XX:+UseParallelGC

G1 垃圾收集

G1(垃圾优先)垃圾收集器已在 Java 7 中提供,旨在长期替代 CMS 收集器。 G1 收集器是并行的,并发的,渐进压缩的低暂停垃圾收集器。

此方法涉及将内存堆分段为多个小区域(通常为 2048)。 每个区域都被标记为新生代(进一步划分为伊甸园地区或幸存者地区)或老一代。 这样,GC 可以避免立即收集整个堆,而可以逐步解决问题。 这意味着一次只考虑区域的一个子集。

Memory regions marked - G1

标记为 – G1 的内存区域

G1 跟踪每个区域包含的实时数据量。 此信息用于确定包含最多垃圾的区域。 因此它们是首先收集的。 这就是为什么将其命名为垃圾优先集合的原因。

与其他算法一样,不幸的是,压缩操作是使用 Stop the World 方法进行的。 但是,根据设计目标,您可以为其设置特定的性能目标。 您可以配置暂停持续时间,例如在任何给定的秒内不超过 10 毫秒。 垃圾优先 GC 将尽最大可能(但不能确定,由于 OS 级线程管理,这很难实时实现)来尽力实现该目标。

如果要在 Java 7 或 Java 8 计算机中使用,请使用 JVM 参数,如下所示:

-XX:+UseG1GC
G1 优化选项
标志 描述
-XX:G1HeapRegionSize=16m 堆区域的大小。 该值为 2 的幂,范围为 1MB 至 32MB。 目标是根据最小 Java 堆大小具有大约 2048 个区域。
-XX:MaxGCPauseMillis=200 为所需的最大暂停时间设置目标值。 默认值为 200 毫秒。 指定的值不适合您的堆大小。
-XX:G1ReservePercent=5 这确定堆中的最小保留量。
-XX:G1ConfidencePercent=75 这就是置信系数暂停预测启发式算法。
-XX:GCPauseIntervalMillis=200 这是每个 MMU 的暂停间隔时间片,以毫秒为单位。

GC 自定义选项

GC 配置标志

标志 描述
-Xms2048m -Xmx3g 设置初始和最大堆大小(年轻空间加上租用空间)。
-XX:+DisableExplicitGC 这将导致 JVM 忽略应用程序对System.gc()方法的任何调用。
-XX:+UseGCOverheadLimit 这是用于限制在抛出OutOfMemory错误之前在垃圾回收上花费的时间的使用策略。
-XX:GCTimeLimit=95 这限制了在引发OutOfMemory错误之前在垃圾回收上花费的时间比例。 与GCHeapFreeLimit一起使用。
-XX:GCHeapFreeLimit=5 这设置了在抛出OutOfMemory错误之前,在进行完全垃圾回收之后的最小可用空间百分比。 与GCTimeLimit一起使用。
-XX:InitialHeapSize=3g 设置初始堆大小(年轻空间加上租用空间)。
-XX:MaxHeapSize=3g 设置最大堆大小(年轻空间加租用空间)。
-XX:NewSize=128m 设置年轻空间的初始大小。
-XX:MaxNewSize=128m 设置年轻空间的最大大小。
-XX:SurvivorRatio=15 将单个幸存者空间的大小设置为 Eden 空间大小的一部分。
-XX:PermSize=512m 设置永久空间的初始大小。
-XX:MaxPermSize=512m 设置永久空间的最大大小。
-Xss512k 设置每个线程专用的栈区域的大小(以字节为单位)。

GC 记录标志

标志 描述
-verbose:gc or -XX:+PrintGC 这将打印基本的垃圾收集信息。
-XX:+PrintGCDetails 这将打印更详细的垃圾收集信息。
-XX:+PrintGCTimeStamps 您可以为每个垃圾收集事件打印时间戳。 这些秒是连续的,并且从 JVM 启动时间开始。
-XX:+PrintGCDateStamps 您可以为每个垃圾收集事件打印日期戳。
-Xloggc: 使用此方法,您可以将垃圾回收输出重定向到文件而不是控制台。
-XX:+Print\TenuringDistribution 您可以在每个收集周期之后打印有关年轻空间的详细信息。
-XX:+PrintTLAB 您可以使用此标志来打印 TLAB 分配统计信息。
-XX:+PrintReferenceGC 使用此标志,您可以打印世界暂停期间进行参考处理的时间(即弱,弱等)。
-XX:+HeapDump\OnOutOfMemoryError 这将在内存不足的情况下创建堆转储文件。

总结

因此,在此 Java 垃圾回收教程中,我们学习了以下内容:

  1. 对象生命周期分为三个阶段,即对象创建,对象使用和对象销毁。
  2. mark-sweepmark-sweep-compactmark-copy机制如何运作。
  3. 不同的单线程和并发 GC 算法。
  4. 直到 Java 8,并行 GC 才是默认算法。
  5. 从 Java 9 开始,将 G1 设置为默认 GC 算法。
  6. 此外,还有各种标志来控制垃圾收集算法的行为并记录任何应用程序的有用信息。

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

学习愉快!

posted @ 2024-10-24 18:13  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报