HowToDoInJava-其它教程-2-一-

龙哥盟 / 2024-11-20 / 原文

HowToDoInJava 其它教程 2(一)

原文:HowToDoInJava

协议:CC BY-NC-SA 4.0

Gson 教程

抽象工厂模式解释

原文: https://howtodoinjava.com/design-patterns/creational/abstract-factory-pattern-in-java/

抽象工厂模式是又一个创造型设计模式,并且被视为工厂模式的另一抽象层。 在本教程中,我们将扩展工厂模式中讨论的汽车工厂问题的范围。 我们将通过扩大汽车工厂的范围来学习何时使用工厂模式,然后通过抽象工厂模式解决扩展的范围。

Table of Contents

1\. Design global car factory using abstract factory pattern
2\. Abstract factory pattern based solution
3\. Abstract factory pattern implementation
4\. Summary

1.使用抽象工厂模式设计全局汽车工厂

在“ 工厂设计模式”中,我们讨论了如何针对各种汽车模型类型以及其包含在汽车制造过程中的附加逻辑来抽象汽车制造过程。 现在,想象一下我们的汽车制造商是否决定走向全局。

为了支持全局运营,我们将需要增强系统以支持不同国家/地区的不同汽车制造风格。 例如,在某些国家/地区中,方向盘位于左侧,而在某些国家/地区中,其方向盘位于右侧。 在汽车的不同部分及其制造过程中可能还会存在更多此类差异。

为了描述抽象工厂模式,我们将考虑三种制造方式 - 所有其他国家/地区的美国亚洲默认。 支持多个位置将需要进行关键的设计更改。

首先,我们需要在问题说明中指定的每个位置中都有汽车工厂。 即USACarFactoryAsiaCarFactoryDefaultCarFactory。 现在,我们的应用应该足够智能,可以识别使用的位置,因此我们应该能够使用适当的汽车工厂,甚至不知道内部将使用哪个汽车工厂实现。 这也可以避免我们为某个特定位置称呼错误的工厂。

因此,基本上,我们需要另一层抽象,它将识别位置并在内部使用正确的汽车工厂实现方式,甚至不会向用户提供任何提示。 这正是使用抽象工厂模式来解决的问题。

2.基于抽象工厂模式的解决方案

2.1 包图

使用抽象工厂模式参与全局汽车工厂设计的类的类图。

Abstract factory design pattern package diagram

2.2 时序图

此图显示了CarFactory工厂类背后的类与抽象之间的交互。

abstract factory design pattern sequence diagram

请注意,我已设计解决方案以完全隐藏最终用户的位置详细信息。 因此,我没有直接暴露任何特定于位置的工厂。

在替代解决方案中,我们可以首先基于location参数获取特定于位置的工厂,然后在抽象引用上使用其buildCar()方法来构建实际的汽车实例。

3.抽象工厂模式实现

Java 类为全局汽车工厂实现抽象工厂模式。

首先,我们必须为不同的位置编写所有独立的汽车工厂。 为了支持特定于位置的功能,请首先使用另一个属性location修改Car.java类。

public abstract class Car {

  public Car(CarType model, Location location){
    this.model = model;
    this.location = location;
  }

  protected abstract void construct();

  private CarType model = null;
  private Location location = null;

  //getters and setters

  @Override
  public String toString() {
    return "Model- "+model + " built in "+location;
  }
}

这增加了创建另一个用于存储不同位置的枚举的工作。

public enum Location {
  DEFAULT, USA, ASIA
}

所有汽车类型还将具有附加的location属性。 我们只为豪华车而写。 小轿车和轿车也是如此。


public class LuxuryCar extends Car
{
  public LuxuryCar(Location location)
  {
    super(CarType.LUXURY, location);
    construct();
  }

  @Override
  protected void construct() {
    System.out.println("Building luxury car");
    //add accessories
  }
}

到目前为止,我们已经创建了基本类。 现在让我们拥有不同的汽车工厂,这是抽象工厂模式背后的核心思想。

public class AsiaCarFactory
{
  public static Car buildCar(CarType model)
  {
    Car car = null;
    switch (model)
    {
      case SMALL:
      car = new SmallCar(Location.ASIA);
      break;

      case SEDAN:
      car = new SedanCar(Location.ASIA);
      break;

      case LUXURY:
      car = new LuxuryCar(Location.ASIA);
      break;

      default:
      //throw some exception
      break;
    }
    return car;
  }
}

public class DefaultCarFactory
{
  public static Car buildCar(CarType model)
  {
    Car car = null;
    switch (model)
    {
      case SMALL:
      car = new SmallCar(Location.DEFAULT);
      break;

      case SEDAN:
      car = new SedanCar(Location.DEFAULT);
      break;

      case LUXURY:
      car = new LuxuryCar(Location.DEFAULT);
      break;

      default:
      //throw some exception
      break;
    }
    return car;
  }
}

public class USACarFactory
{
  public static Car buildCar(CarType model)
  {
    Car car = null;
    switch (model)
    {
      case SMALL:
      car = new SmallCar(Location.USA);
      break;

      case SEDAN:
      car = new SedanCar(Location.USA);
      break;

      case LUXURY:
      car = new LuxuryCar(Location.USA);
      break;

      default:
      //throw some exception
      break;
    }
  return car;
  }
}

好吧,现在我们所有 3 个不同的汽车工厂。 现在,我们必须抽象化访问这些工厂的方式。

public class CarFactory
{
  private CarFactory() {
    //Prevent instantiation
  }

  public static Car buildCar(CarType type)
  {
    Car car = null;
    Location location = Location.ASIA; //Read location property somewhere from configuration
    //Use location specific car factory
    switch(location)
    {
      case USA:
      	car = USACarFactory.buildCar(type);
      	break;
      case ASIA:
      	car = AsiaCarFactory.buildCar(type);
      	break;
      default:
      	car = DefaultCarFactory.buildCar(type);
    }
  return car;
  }
}

我们已经完成编写代码。 现在,让我们测试一下工厂和汽车。

public class TestFactoryPattern
{
  public static void main(String[] args)
  {
    System.out.println(CarFactory.buildCar(CarType.SMALL));
    System.out.println(CarFactory.buildCar(CarType.SEDAN));
    System.out.println(CarFactory.buildCar(CarType.LUXURY));
  }
}

程序输出:

Output: (Default location is Asia)

Building small car
Model- SMALL built in ASIA

Building sedan car
Model- SEDAN built in ASIA

Building luxury car
Model- LUXURY built in ASIA

4.总结

我们已经看到了工厂模式的用例场景,因此,每当需要一组工厂上的另一个抽象级别时,都应考虑使用抽象工厂模式。 工厂模式与抽象工厂模式之间可能只是这些区别

您已经可以更深入地研究 JDK 分发中的抽象工厂的不同实时示例

  • DocumentBuilderFactory#newInstance()
  • TransformerFactory#newInstance()

还有其他类似的示例,但是需要对抽象工厂设计模式有所了解,这是您到目前为止必须已经掌握的。

学习愉快!

参考文献:

抽象工厂 – 维基百科

Java 中的原型设计模式

原文: https://howtodoinjava.com/design-patterns/creational/prototype-design-pattern-in-java/

在构建实际对象之前, 原型是任何对象的模板。 同样在 Java 中,它具有相同的含义。 原型设计模式用于应用需要创建多个类实例的情况,这些实例的状态几乎相同或相差很小。

在这种设计模式中,在启动时会创建一个实际对象的实例(即原型),此后每当需要一个新实例时,都会将该原型克隆为另一个实例。 此模式的主要优势是具有最少的实例创建过程,该过程比克隆过程要昂贵得多。

Table of Contents

Design participants
Problem statement
Implementation

请确保您要对原型进行深克隆或浅克隆,因为两者在运行时的行为不同。 如果需要深度复制,则可以使用在内存序列化中使用此处给出的良好技术。

原型模式 – 参与者

  • 原型:这是如上所述的实际对象的原型。
  • 原型注册表:用作注册表服务,使所有原型都可以使用简单的字符串参数进行访问。
  • 客户端:客户端将负责使用注册表服务访问原型实例。

问题陈述

让我们通过一个例子来了解这种模式。 我正在创建一个娱乐应用,该应用将非常频繁地需要MovieAlbumShow类的实例。 我不想每次都创建它们的实例,因为这很昂贵。 因此,我将创建他们的原型实例,每当我需要一个新实例时,我都将克隆该原型。

原型模式示例 – 实现

让我们从创建类图开始。

prototype-pattern-class-diagram

上面的类图说明了必要的类及其之间的关系。

解决方案中仅增加了一个接口“PrototypeCapable”。 使用此接口的原因是Cloneable接口的损坏行为。 该接口有助于实现以下目标:

  • 能够克隆原型而不知道它们的实际类型
  • 提供要在注册表中使用的类型引用

他们的工作流程将如下所示:

Prototype pattren sequence diagram

让我们敲键盘并编写这些类。

PrototypeCapable.java

package com.howtodoinjava.prototypeDemo.contract;

public interface PrototypeCapable extends Cloneable
{
	public PrototypeCapable clone() throws CloneNotSupportedException;
}

Movie.javaAlbum.javaShow.java

package com.howtodoinjava.prototypeDemo.model;

import com.howtodoinjava.prototypeDemo.contract.PrototypeCapable;

public class Movie implements PrototypeCapable
{
	private String name = null;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public Movie clone() throws CloneNotSupportedException {
		System.out.println("Cloning Movie object..");
		return (Movie) super.clone();
	}
	@Override
	public String toString() {
		return "Movie";
	}
}

public class Album implements PrototypeCapable
{
	private String name = null;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public Album clone() throws CloneNotSupportedException {
		System.out.println("Cloning Album object..");
		return (Album) super.clone();
	}
	@Override
	public String toString() {
		return "Album";
	}
}

public class Show implements PrototypeCapable
{
	private String name = null;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public Show clone() throws CloneNotSupportedException {
		System.out.println("Cloning Show object..");
		return (Show) super.clone();
	}
	@Override
	public String toString() {
		return "Show";
	}
}

PrototypeFactory.java

package com.howtodoinjava.prototypeDemo.factory;

import com.howtodoinjava.prototypeDemo.contract.PrototypeCapable;
import com.howtodoinjava.prototypeDemo.model.Album;
import com.howtodoinjava.prototypeDemo.model.Movie;
import com.howtodoinjava.prototypeDemo.model.Show;

public class PrototypeFactory
{
	public static class ModelType
	{
		public static final String MOVIE = "movie";
		public static final String ALBUM = "album";
		public static final String SHOW = "show";
	}
	private static java.util.Map<String , PrototypeCapable> prototypes = new java.util.HashMap<String , PrototypeCapable>();

	static
	{
		prototypes.put(ModelType.MOVIE, new Movie());
		prototypes.put(ModelType.ALBUM, new Album());
		prototypes.put(ModelType.SHOW, new Show());
	}

	public static PrototypeCapable getInstance(final String s) throws CloneNotSupportedException {
		return ((PrototypeCapable) prototypes.get(s)).clone();
	}
}

TestPrototypePattern

package com.howtodoinjava.prototypeDemo.client;

import com.howtodoinjava.prototypeDemo.factory.PrototypeFactory;
import com.howtodoinjava.prototypeDemo.factory.PrototypeFactory.ModelType;

public class TestPrototypePattern
{
	public static void main(String[] args)
	{
		try
		{
			String moviePrototype  = PrototypeFactory.getInstance(ModelType.MOVIE).toString();
			System.out.println(moviePrototype);

			String albumPrototype  = PrototypeFactory.getInstance(ModelType.ALBUM).toString();
			System.out.println(albumPrototype);

			String showPrototype  = PrototypeFactory.getInstance(ModelType.SHOW).toString();
			System.out.println(showPrototype);

		}
		catch (CloneNotSupportedException e)
		{
			e.printStackTrace();
		}
	}
}

运行客户端代码时,输​​出如下。

Cloning Movie object..
Movie
Cloning Album object..
Album
Cloning Show object..
Show

希望您喜欢 Java 原型模式示例上的这篇文章。 如有任何疑问,请发表评论。

下载源码

学习愉快!

行为型设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/

在软件工程中,行为型设计模式是一种设计模式,用于标识对象之间的通用通信模式并实现这些模式。 通过这样做,这些模式增加了执行此通信的灵活性。

行为型模式与对象之间的责任分配有关,或者与将行为封装在对象中并委托给它有关。

与创建和结构型模式不同,创建和结构型模式处理实例化过程以及对象和类的蓝图,此处的中心思想是集中于对象的互连方式。 总之,我们可以这样说:如果创建型是关于实例化的,而结构型是蓝图,那么行为型是对象之间关系的模式。

责任链设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/chain-of-responsibility-design-pattern/

责任链被称为行为型模式。 这种模式的主要目的是避免将请求的发送方耦合到接收方,从而为多个对象提供了处理请求的机会。 GoF 定义的核心逻辑是:

"Gives more than one object an opportunity to handle a request by linking receiving objects together."

责任链允许许多类尝试处理请求,而与链上的任何其他对象无关。 处理完请求后,它就完成了整个流程。

可以在链中添加或删除额外的处理器,而无需修改任何具体处理器内部的逻辑。

Sections in this post:

Suggested usage
Participants in the solution
Sample problem to be solved
Proposed solution
Class diagram of participants
Sourcecode of participants
Test the application
Download sourecode link
Reference implementations in JDK

建议用法

当多个对象可以处理一个请求并且处理器不必是特定的对象时,建议使用此模式。 另外,处理器是在运行时确定的。 请注意,任何处理器都不处理的请求是有效的用例。

例如,Windows OS 中的事件处理机制,可以从鼠标,键盘或某些自动生成的事件生成事件。 所有这些事件都可以由多个处理器处理,并且在运行时可以找到正确的处理器。

更一般的例子可以是对呼叫中心的服务请求。 可以在前台,主管或任何更高级别处理此请求。 仅当在各个级别上遍历请求时,才在运行时知道正确的请求处理器。 我们将在这篇文章中解决这种情况。

解决方案中的参与者

1)Handler:这可以是一个主要接收请求并将请求分派到处理器链的接口。 它仅引用链中的第一个处理器,而对其余处理器一无所知。

2)ConcreteHandler:这些是按某些顺序链接的请求的实际处理器。

3)Client:请求的始发者,它将访问处理器来处理它。

Participants in chain of responsibility

责任链中的参与者

要解决的示例问题

问题陈述是为支持服务系统设计一个系统,该系统由前台,主管,经理和主管组成。 任何客户都可以致电前台,并寻求解决方案。 如果前台能够解决问题,它将解决; 否则将转交给主管。 同样,主管将尝试解决该问题,如果他能够解决,则将解决; 否则传给经理。 同样,经理将解决问题或转给董事。 导演将解决该问题或拒绝它。

建议的解决方案

以上问题是使用责任链模式的良好人选。 我们可以在每个级别上定义处理器,即支持台,主管,经理和主管。 然后,我们可以定义一条链来处理支持请求。 该链必须遵循以下顺序:

Support desk > supervisor > manager > director

上面的链也可以使用 Java 中的编程解决方案进行管理,但是在本教程中,我将使用 spring 注入依赖项,从而形成该链。 同样,系统将首先将请求仅分配给前台。

参与者的类图

我已经绘制了解决方案中涉及的所有实体的结构,如下所示。

Chain of responsibility class diagram

支持服务系统:类图

参与者的源代码

以下是使用责任链设计模式实现支持服务的所有参与者的源代码:

ServiceLevel.java

package com.howtodoinjava;

public enum ServiceLevel
{
	LEVEL_ONE, LEVEL_TWO, LEVEL_THREE, LEVEL_FOUR, INVALID_REQUEST
}

ServiceRequest.java

package com.howtodoinjava.data;

import com.howtodoinjava.ServiceLevel;

public class ServiceRequest {

	private ServiceLevel type;
	private String conclusion = null;

	public ServiceLevel getType() {
		return type;
	}
	public void setType(ServiceLevel type) {
		this.type = type;
	}
	public String getConclusion() {
		return conclusion;
	}
	public void setConclusion(String conclusion) {
		this.conclusion = conclusion;
	}
}

SupportServiceItf.java

package com.howtodoinjava.handler;

import com.howtodoinjava.data.ServiceRequest;

public interface SupportServiceItf
{
	public void handleRequest(ServiceRequest request);
}

SupportService.java

package com.howtodoinjava.handler;

import com.howtodoinjava.data.ServiceRequest;

public class SupportService implements SupportServiceItf {

	private SupportServiceItf handler = null;

	public SupportServiceItf getHandler() {
		return handler;
	}

	public void setHandler(SupportServiceItf handler) {
		this.handler = handler;
	}

	@Override
	public void handleRequest(ServiceRequest request) {
		handler.handleRequest(request);
	}
}

FrontDeskSupport.java

package com.howtodoinjava.handler;

import com.howtodoinjava.ServiceLevel;
import com.howtodoinjava.data.ServiceRequest;

public class FrontDeskSupport implements SupportServiceItf {

	private SupportServiceItf next = null;
	public SupportServiceItf getNext() {
		return next;
	}
	public void setNext(SupportServiceItf next) {
		this.next = next;
	}

	@Override
	public void handleRequest(ServiceRequest service) {
		if(service.getType() == ServiceLevel.LEVEL_ONE)
		{
			service.setConclusion("Front desk solved level one reuqest !!");
		}
		else
		{
			if(next != null){
				next.handleRequest(service);
			}
			else
			{
				throw new IllegalArgumentException("No handler found for :: " + service.getType());
			}
		}
	}
}

SupervisorSupport.java

package com.howtodoinjava.handler;

import com.howtodoinjava.ServiceLevel;
import com.howtodoinjava.data.ServiceRequest;

public class SupervisorSupport implements SupportServiceItf {

	private SupportServiceItf next = null;
	public SupportServiceItf getNext() {
		return next;
	}
	public void setNext(SupportServiceItf next) {
		this.next = next;
	}

	@Override
	public void handleRequest(ServiceRequest request) {
		if(request.getType() == ServiceLevel.LEVEL_TWO)
		{
			request.setConclusion("Supervisor solved level two reuqest !!");
		}
		else
		{
			if(next != null){
				next.handleRequest(request);
			}
			else
			{
				throw new IllegalArgumentException("No handler found for :: " + request.getType());
			}
		}
	}
}

ManagerSupport.java

package com.howtodoinjava.handler;

import com.howtodoinjava.ServiceLevel;
import com.howtodoinjava.data.ServiceRequest;

public class ManagerSupport implements SupportServiceItf {

	private SupportServiceItf next = null;
	public SupportServiceItf getNext() {
		return next;
	}
	public void setNext(SupportServiceItf next) {
		this.next = next;
	}

	@Override
	public void handleRequest(ServiceRequest request) {
		if(request.getType() == ServiceLevel.LEVEL_THREE)
		{
			request.setConclusion("Manager solved level three reuqest !!");
		}
		else
		{
			if(next != null){
				next.handleRequest(request);
			}
			else
			{
				throw new IllegalArgumentException("No handler found for :: " + request.getType());
			}
		}
	}
}

DirectorSupport.java

package com.howtodoinjava.handler;

import com.howtodoinjava.ServiceLevel;
import com.howtodoinjava.data.ServiceRequest;

public class DirectorSupport implements SupportServiceItf {

	private SupportServiceItf next = null;
	public SupportServiceItf getNext() {
		return next;
	}
	public void setNext(SupportServiceItf next) {
		this.next = next;
	}

	@Override
	public void handleRequest(ServiceRequest request) {
		if(request.getType() == ServiceLevel.LEVEL_FOUR)
		{
			request.setConclusion("Director solved level four reuqest !!");
		}
		else
		{
			if(next != null){
				next.handleRequest(request);
			}
			else
			{
				request.setConclusion("You problem is none of our business");
				throw new IllegalArgumentException("You problem is none of our business :: " + request.getType());
			}
		}
	}
}

applicationConfig.xml

<?xml  version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
        http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="supportService" class="com.howtodoinjava.handler.SupportService">
        <property name="handler" ref="frontDeskSupport"></property>
    </bean>

    <bean id="frontDeskSupport" class="com.howtodoinjava.handler.FrontDeskSupport">
        <property name="next" ref="supervisorSupport"></property>
    </bean>
    <bean id="supervisorSupport" class="com.howtodoinjava.handler.SupervisorSupport">
        <property name="next" ref="managerSupport"></property>
    </bean>
    <bean id="managerSupport" class="com.howtodoinjava.handler.ManagerSupport">
        <property name="next" ref="directorSupport"></property>
    </bean>
    <bean id="directorSupport" class="com.howtodoinjava.handler.DirectorSupport"></bean>

</beans>

测试应用

我将在链下传递各种级别的支持请求,这些请求将由正确的级别处理。 任何无效的请求将按计划被拒绝。

package com.howtodoinjava;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.howtodoinjava.data.ServiceRequest;
import com.howtodoinjava.handler.SupportService;

public class TestChainOfResponsibility {
	public static void main(String[] args)
	{
		ApplicationContext context = new ClassPathXmlApplicationContext("application-config.xml");
		SupportService supportService = (SupportService) context.getBean("supportService");

		ServiceRequest request = new ServiceRequest();
		request.setType(ServiceLevel.LEVEL_ONE);
		supportService.handleRequest(request);
		System.out.println(request.getConclusion());

		request = new ServiceRequest();
		request.setType(ServiceLevel.LEVEL_THREE);
		supportService.handleRequest(request);
		System.out.println(request.getConclusion());

		request = new ServiceRequest();
		request.setType(ServiceLevel.INVALID_REQUEST);
		supportService.handleRequest(request);
		System.out.println(request.getConclusion());
	}
}

<strong>Output:</strong>

Front desk solved level one reuqest !!
Manager solved level three reuqest !!
Exception in thread "main" java.lang.IllegalArgumentException: You problem is none of our business :: INVALID_REQUEST

要下载上述示例应用的源代码,请单击以下链接。

JDK 中的参考实现

  • javax.servlet.Filter#doFilter()

每当客户端对链末端的资源提出请求时,每次通过链传递请求/响应对时,容器都会调用FilterdoFilter方法。 传入此方法的FilterChain允许Filter将请求和响应传递给链中的下一个实体。

  • java.util.logging.Logger#log

如果当前为给定消息级别启用了记录器,则将给定消息转发到所有已注册的输出Handler对象。

我希望这篇文章能为您对责任链模式的理解增加一些知识。 如有任何疑问,请发表评论。

祝您学习愉快!

命令设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/command-pattern/

命令模式是一种行为型设计模式,可用于将业务逻辑抽象为离散的动作,我们将其称为命令。 此命令对象有助于松散耦合两个类之间的关系,其中一个类(调用者)应调用另一类(接收者)上的方法来执行业务操作。

让我们学习命令如何帮助将调用者与接收者解耦。

Table of Contents
Introduction
Design Participants
Problem Statement
Command Pattern Implementation
Demo
When to Use Command Pattern
Popular Implementations
Summary

介绍

在面向对象的编程中,命令模式是一种行为型设计模式,其中一个对象用于封装执行动作,业务操作或触发事件所需的所有信息。 方法名称,接收方对象引用和方法参数值(如果有)。 该对象称为命令

类似的方法也适用于责任链模式。 唯一的区别是命令中只有一个请求处理器,而在责任链中单个请求对象可以有许多处理器。

设计参与者

命令设计模式的参与者包括:

  • 命令接口 – 用于声明操作。
  • 具体命令类 – 扩展了Command接口,并具有用于在接收方上调用业务操作方法的执行方法。 它在内部具有命令接收者的参考。
  • 调用者 – 被赋予执行操作的命令对象。
  • 接收器 – 执行操作。

在命令模式中,调用者与接收者执行的动作分离。 调用者不知道接收者。 调用者调用一个命令,然后该命令执行接收方的相应操作。 因此,调用者可以在不知道要执行的动作的细节的情况下调用命令。 此外,这种脱钩意味着对接收者动作的更改不会直接影响动作的调用。

问题陈述

假设我们需要为家庭自动化系统构建一个遥控器,该遥控器应控制房屋的不同照明/电气单元。 遥控器中的单个按钮可能能够在类似设备上执行相同的操作,例如,电视开/关按钮可用于打开/关闭不同房间中的不同电视机。

在这里,此遥控器将是一个可编程的遥控器,它将用于打开和关闭各种灯/风扇等。

首先,让我们看看如何使用任何设计方法解决问题。 她的遥控器代码可能看起来像:

If(buttonName.equals(“Light”))
{
	//Logic to turn on that light
}
else If(buttonName.equals(“Fan”))
{
	//Logic to turn on that Fan
}

但是上述解决方案显然存在许多明显的问题,例如:

  • 任何新项目(例如TubeLight)都需要更改遥控器的代码。 您将需要添加更多的if-else
  • 如果我们想为其他目的更改按钮,那么我们也需要更改代码。
  • 最重要的是,如果家里有很多项目,代码的复杂性和可维护性将会增加。
  • 最后,代码不干净且紧密耦合,我们未遵循为接口编码等最佳实践。

命令模式实现

让我们用命令设计模式解决上述家庭自动化问题,并一次设计一个组件。

  • ICommand接口是命令接口
  • Light接收器组件之一。 它可以接受与Light有关的多个命令,例如打开和关闭
  • Fan也是接收器组件的另一种类型。 它可以接受与风扇相关的多个命令,例如打开和关闭
  • HomeAutomationRemote调用者对象,它要求命令执行请求。 这里风扇开/关,灯开/关。
  • StartFanCommandStopFanCommandTurnOffLightCommandTurnOnLightCommand等是命令实现的不同类型。

类图

Class Diagram

命令模式类图

让我们看一下每个类和接口的 java 源代码。

ICommand.java

package com.howtodoinjava.designpattern.command.homeautomation;

/**
 * Command Interface which will be implemented by the exact commands.
 *
 */
@FunctionalInterface
public interface ICommand {
	public void execute();
}

Light.java

package com.howtodoinjava.designpattern.command.homeautomation.light;

/**
 * Light is a Receiver component in command pattern terminology.
 *
 */
public class Light {
	public  void turnOn() {
		System.out.println("Light is on");
	}

	public void turnOff() {
		System.out.println("Light is off");
	}
}

Fan.java

package com.howtodoinjava.designpattern.command.homeautomation.fan;

/**
 * Fan class is a Receiver component in command pattern terminology.
 *
 */
public class Fan {
	void start() {
		System.out.println("Fan Started..");

	}

	 void stop() {
		System.out.println("Fan stopped..");

	}
}

TurnOffLightCommand.java

package com.howtodoinjava.designpattern.command.homeautomation.light;

import com.howtodoinjava.designpattern.command.homeautomation.ICommand;
/**
 * Light Start Command where we are encapsulating both Object[light] and the
 * operation[turnOn] together as command. This is the essence of the command.
 *
 */
public class TurnOffLightCommand implements ICommand {

	Light light;

	public TurnOffLightCommand(Light light) {
		super();
		this.light = light;
	}

	public void execute() {
		System.out.println("Turning off light.");
		light.turnOff();
	}
}

TurnOnLightCommand.java

package com.howtodoinjava.designpattern.command.homeautomation.light;

import com.howtodoinjava.designpattern.command.homeautomation.ICommand;

/**
 * Light stop Command where we are encapsulating both Object[light] and the
 * operation[turnOff] together as command. This is the essence of the command.
 *
 */
public class TurnOnLightCommand implements ICommand {

	Light light;

	public TurnOnLightCommand(Light light) {
		super();
		this.light = light;
	}

	public void execute() {
		System.out.println("Turning on light.");
		light.turnOn();
	}
}

StartFanCommand.java

package com.howtodoinjava.designpattern.command.homeautomation.fan;

import com.howtodoinjava.designpattern.command.homeautomation.ICommand;

/**
 * Fan Start Command where we are encapsulating both Object[fan] and the
 * operation[start] together as command. This is the essence of the command.
 *
 */
public class StartFanCommand implements ICommand {

	Fan fan;

	public StartFanCommand(Fan fan) {
		super();
		this.fan = fan;
	}

	public void execute() {
		System.out.println("starting Fan.");
		fan.start();
	}
}

StopFanCommand.java

package com.howtodoinjava.designpattern.command.homeautomation.fan;

import com.howtodoinjava.designpattern.command.homeautomation.ICommand;
/**
 * Fan stop Command where we are encapsulating both Object[fan] and the
 * operation[stop] together as command. This is the essence of the command.
 *
 */
public class StopFanCommand implements ICommand {

	Fan fan;

	public StopFanCommand(Fan fan) {
		super();
		this.fan = fan;
	}

	public void execute() {
		System.out.println("stopping Fan.");
		fan.stop();
	}
}

HomeAutomationRemote.java

package com.howtodoinjava.designpattern.command.homeautomation;

/**
 * Remote Control for Home automation where it will accept the command and
 * execute. This is the invoker in terms of command pattern terminology
 */
public class HomeAutomationRemote {

	//Command Holder
	ICommand command;

	//Set the command in runtime to trigger.
	public void setCommand(ICommand command) {
		this.command = command;
	}

	//Will call the command interface method so that particular command can be invoked.
	public void buttonPressed() {
		command.execute();
	}
}

演示

让我们编写代码并执行客户端代码,以查看命令的执行方式。

package com.howtodoinjava.designpattern.command.homeautomation;

import com.howtodoinjava.designpattern.command.homeautomation.fan.Fan;
import com.howtodoinjava.designpattern.command.homeautomation.fan.StartFanCommand;
import com.howtodoinjava.designpattern.command.homeautomation.fan.StopFanCommand;
import com.howtodoinjava.designpattern.command.homeautomation.light.Light;
import com.howtodoinjava.designpattern.command.homeautomation.light.TurnOnLightCommand;

/**
 * Demo class for HomeAutomation
 *
 */
public class Demo 	//client
{
	public static void main(String[] args) 
	{
		Light livingRoomLight = new Light();	//receiver 1

		Fan livingRoomFan = new Fan();	//receiver 2

		Light bedRoomLight = new Light();	//receiver 3

		Fan bedRoomFan = new Fan();		//receiver 4

		HomeAutomationRemote remote = new HomeAutomationRemote();	//Invoker

		remote.setCommand(new TurnOnLightCommand( livingRoomLight ));
		remote.buttonPressed();

		remote.setCommand(new TurnOnLightCommand( bedRoomLight ));
		remote.buttonPressed();

		remote.setCommand(new StartFanCommand( livingRoomFan ));
		remote.buttonPressed();

		remote.setCommand(new StopFanCommand( livingRoomFan ));
		remote.buttonPressed();

		remote.setCommand(new StartFanCommand( bedRoomFan ));
		remote.buttonPressed();

		remote.setCommand(new StopFanCommand( bedRoomFan ));
		remote.buttonPressed();
	}
}

输出:

Turning on light.
Light is on

Turning on light.
Light is on

starting Fan.
Fan Started..

stopping Fan.
Fan stopped..

starting Fan.
Fan Started..

stopping Fan.
Fan stopped..

何时使用命令模式

您可以使用命令模式来解决许多设计问题,例如:

  • 处理 Java 菜单项和按钮的动作。
  • 提供对宏的支持(宏的记录和播放)。
  • 提供“撤消”支持。
  • 进度条实现。
  • 创建多步骤向导。

流行的命令模式实现

这些是命令模式实现的一些实际示例:

  • 可运行接口(java.lang.Runnable
  • Swing Actionjavax.swing.Action)使用命令模式
  • ActionServlet调用 Struts Action类在内部使用命令模式。

总结

  1. 命令模式是一种行为型设计模式。
  2. 在命令模式中,对象用于封装在任何时间执行操作或触发事件所需的所有信息,从而使开发人员能够分散业务逻辑。 它称为命令。
  3. 客户端与调用者对话并传递命令对象。
  4. 每个命令对象都有对其接收者的引用。
  5. Commandexecute()方法调用接收器中定义的实际业务操作。
  6. 接收者执行业务操作。

这是此模式的一些利弊。 它可以帮助您就使用命令模式做出正确的决定。

优点

  • 使我们的代码具有可伸缩性,因为我们可以添加新命令而无需更改现有代码。
  • 使用命令对象增加调用者和接收者之间的松散耦合。

缺点

  • 在不同的视图中,增加每个单独命令的类数。 在某些特定情况下,它可能不是首选。

这就是命令设计模式的全部。 将我的问题放在评论部分。

下载源码

学习愉快!

迭代器设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/iterator-design-pattern/

根据 GoF 定义,迭代器模式提供了一种顺序访问聚合对象元素的方法,而无需暴露其基础表示。 这是行为设计模式。

顾名思义,迭代器有助于以定义的方式遍历对象的集合,这对客户端应用很有用。 在迭代期间,客户端程序可以根据需要对元素执行各种其他操作。

1.何时使用迭代器设计模式

每种编程语言都支持一些数据结构,例如列表或映射,用于存储一组相关对象。 在 Java 中,我们具有ListMapSet接口及其实现,例如ArrayListHashMap

集合仅在提供一种在不暴露其内部结构的情况下访问其元素的方式时才有用。 迭代器承担此责任。

因此,任何时候,我们都有对象集合,并且客户端需要一种以某种适当顺序迭代每个集合元素的方法,我们必须使用迭代器模式来设计解决方案。

迭代器模式使我们能够以以下方式设计集合迭代器:

  • 我们能够访问集合的元素而无需暴露元素的内部结构或集合本身。
  • 迭代器支持从开始到结束以向前,向后或双向双向遍历一个集合。
  • 迭代器提供了一个统一的接口,用于透明地遍历不同的集合类型。

关键思想是从聚合对象中承担访问和遍历的责任,并将其放入定义标准遍历协议的Iterator对象中。

2.迭代器模式的真实示例

  • 在 Java 中,我们具有java.util.Iterator接口及其特定的实现,例如ListIterator。 所有 Java 集合都提供Iterator接口的一些内部实现,这些接口用于遍历集合元素。

    	List<String> names = Arrays.asList("alex", "brian", "charles");
    
    	Iterator<String> namesIterator = names.iterator();
    
    	while (namesIterator.hasNext()) 
    	{
    		String currentName = namesIterator.next();
    
    		System.out.println(currentName);
    }
    
    
  • 在媒体播放器中,我们列出了歌曲列表,我们可以通过遍历歌曲列表并选择所需的歌曲来播放歌曲。 这也是一个迭代器示例。

3.迭代器设计模式

3.1 架构

Iterator Pattern Class Diagram

迭代器模式类图

3.2 设计参与者

迭代器模式的参与者如下:

  • Iterator:访问或遍历元素集合的接口。 提供具体的迭代器必须实现的方法。
  • ConcreteIterator:实现Iterator接口方法。 它还可以在遍历聚合集合时跟踪当前位置。
  • Aggregate:通常是一个集合接口,它定义可以创建Iterator对象的方法。
  • ConcreteAggregate:它实现Aggregate接口,其特定方法返回ConcreteIterator的实例。

4.迭代器设计模式示例

在此迭代器模式示例中,我们正在创建一个集合,该集合可以保存Token类的实例,并将提供一个迭代器来迭代序列中的标记集合。

public class Topic 
{
	private String name;

	public Topic(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

public interface Iterator<E> 
{
	void reset();	// reset to the first element

	E next();	// To get the next element

	E currentItem();	// To retrieve the current element

	boolean hasNext();	// To check whether there is any next element or not.
}

public class TopicIterator implements Iterator<Topic> {

	private Topic[] topics;
    private int position;

    public TopicIterator(Topic[] topics)
    {
        this.topics = topics;
        position = 0;
    }

	@Override
	public void reset() {
		position = 0;
	}

	@Override
	public Topic next() { 
		return topics[position++];
	}

	@Override
	public Topic currentItem() {
		return topics[position];
	}

	@Override
	public boolean hasNext() {
		if(position >= topics.length)
            return false;
        return true;
	}
}

public interface List<E>
{
	Iterator<E> iterator();
}

public class TopicList implements List<Topic>
{
	private Topic[] topics;

    public TopicList(Topic[] topics)
    {
        this.topics = topics;
    }

	@Override
	public Iterator<Topic> iterator() {
		return new TopicIterator(topics);
	}
}

使用迭代器的客户端代码将像这样。

public class Main 
{
	public static void main(String[] args) 
	{
		Topic[] topics = new Topic[5];
		topics[0] = new Topic("topic1");
		topics[1] = new Topic("topic2");
		topics[2] = new Topic("topic3");
		topics[3] = new Topic("topic4");
		topics[4] = new Topic("topic5");

		List<Topic> list = new TopicList(topics);

		Iterator<Topic> iterator = list.iterator();

		while(iterator.hasNext()) {
			Topic currentTopic = iterator.next();
			System.out.println(currentTopic.getName());
		}
	}
}

程序输出。

topic1
topic2
topic3
topic4
topic5

在评论中向我发送有关迭代器模式的问题。

学习愉快!

参考:维基百科

中介者设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/mediator-pattern/

根据 GoF 定义,中介者模式定义了一个对象,封装了一组对象如何交互。 中介者通过防止对象之间显式地相互引用来促进松散耦合,并且它使我们可以独立地更改其交互。

中介者是行为型设计模式,也是 GoF 讨论的其他 23 个模式之一。

1.何时使用中介者设计模式

中介者有助于在对象之间建立松耦合通信,并有助于减少彼此之间的直接引用。 这有助于最大程度地减少依赖管理和参与对象之间的通信的复杂性。

中介者以对象不知道其他对象存在的方式帮助促进对象之间的交互。 对象仅依赖于单个中介者类,而不耦合到许多其他对象。

在设计问题的解决方案期间,如果遇到多个对象需要相互交互以处理请求,但直接通信可能会创建复杂系统的情况,则可以考虑使用中介者模式。

该模式使您可以将类之间的所有关系提取到单独的类中,从而将对特定组件的任何更改与其余组件隔离。

2.中介者模式的真实示例

  • 现实世界中很好的中介者模式示例是机场的“交通管制室”。 如果所有航班都必须进行交互以查找下一个要降落的航班,则会造成很大的混乱。

    相反,航班只会将其状态发送给塔楼。 这些塔依次发送信号,以确认哪架飞机可以起飞或着陆。 我们必须注意,这些塔无法控制整个飞行。 它们仅在终端区域实现约束。

  • 中介者模式的另一个很好的例子是聊天应用。 在聊天应用中,我们可以有多个参与者。 将每个参与者连接到所有其他参与者不是一个好主意,因为连接数量确实很高。 最好的解决方案是建立一个所有参与者都可以连接的集线器。 该中心只是中介者类。

  • 在 Java 编程中,java.util.concurrent.Executor接口内部的execute()方法遵循此模式。 java.util.Timer类的各种schedule()方法的不同重载版本也可以视为遵循此模式。

3.中介者设计模式

此模式定义了一个单独的(中介)对象,该对象封装了一组对象之间的交互,并且这些对象将其交互委托给中介对象,而不是直接彼此交互。

3.1 架构

Mediator design pattern

中介者设计模式

图片来源 – Wikipedia

3.2 设计参与者

  • Mediator – 定义Colleague对象之间的通信接口
  • ConcreteMediator – 实现Mediator接口并协调Colleague对象之间的通信。 它了解所有同事及其在互通方面的目的
  • Colleague – 定义了通过Mediator与其他同事进行交流的接口
  • ConcreteColleague – 实现Colleague接口并通过其Mediator与其他同事进行通信

4.中介者设计模式示例

在此 Java 中介者模式示例中,我们模拟了聊天应用,用户可以在其中以一对一的方式向其他用户发送消息。 必须向所有聊天应用注册所有用户才能发送或接收消息。

中介者接口

public interface IChatRoom 
{
    public void sendMessage(String msg, String userId);

    void addUser(User user);
}

具体中介者

import java.util.HashMap;
import java.util.Map;

public class ChatRoom implements IChatRoom {

    private Map<String, User> usersMap = new HashMap<>();

    @Override
    public void sendMessage(String msg, String userId) 
    {
        User u = usersMap.get(userId);
        u.receive(msg);
    }

    @Override
    public void addUser(User user) {
        this.usersMap.put(user.getId(), user);
    }
}

同事类

public abstract class User
{
    private IChatRoom mediator;

    private String id;
    private String name;

    public User(IChatRoom room, String id, String name){
        this.mediator = room;
        this.name = name;
        this.id = id;
    }

    public abstract void send(String msg, String userId);
    public abstract void receive(String msg);

    public IChatRoom getMediator() {
        return mediator;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

具体同事

public class ChatUser extends User {

    public ChatUser(IChatRoom room, String id, String name) {
        super(room, id, name);
    }

    @Override
    public void send(String msg, String userId) {
        System.out.println(this.getName() + " :: Sending Message : " + msg);
        getMediator().sendMessage(msg, userId);
    }

    @Override
    public void receive(String msg) {
        System.out.println(this.getName() + " :: Received Message : " + msg);
    }

}

测试中介者模式。

public class Main 
{
    public static void main(String[] args) 
    {
        IChatRoom chatroom = new ChatRoom();

        User user1 = new ChatUser(chatroom,"1", "Alex");
        User user2 = new ChatUser(chatroom,"2", "Brian");
        User user3 = new ChatUser(chatroom,"3", "Charles");
        User user4 = new ChatUser(chatroom,"4", "David");

        chatroom.addUser(user1);
        chatroom.addUser(user2);
        chatroom.addUser(user3);
        chatroom.addUser(user4);

        user1.send("Hello brian", "2");
        user2.send("Hey buddy", "1");
    }
}

程序输出。

Alex :: Sending Message : Hello brian
Brian :: Received Message : Hello brian

Brian :: Sending Message : Hey buddy
Alex :: Received Message : Hey buddy

5.常见问题

  • 中介者模式的优势

    使用中介者模式,我们可以减少系统中对象之间的通信复杂性。 它促进了松耦合,并减少了系统中子类的数量。

    中介者帮助将“多对多”关系替换为“一对多”关系,因此更易于阅读和理解。 而且由于通信的集中控制,维护变得容易。

  • 中介者模式的缺陷

    如果您在中介对象中添加太多逻辑,其架构可能会变得复杂。 不适当使用中介者模式可能会导致“上帝类”反模式。

  • 中介者模式 VS 外观模式

    中介者模式可以看作是复用的外观模式。 在中介者中,不是在单个对象的接口上工作,而是在多个对象之间创建多路复用的接口以提供平滑的过渡。

学习愉快!

备忘录设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/memento-design-pattern/

备忘录设计模式是行为型模式,是 GOF 讨论的 23 种设计模式之一。 备忘录模式用于将对象的状态恢复到先前的状态。 也称为快照模式

备忘录就像对象生命周期中的还原点,客户端应用可使用该还原点将对象状态还原为其状态。 从概念上讲,这很像我们为操作系统创建还原点,并在发生故障或系统崩溃时用于还原系统。

备忘录模式的目的是在不破坏封装的情况下捕获对象的内部状态,从而提供一种在需要时将对象恢复为初始状态的手段。

1.何时使用备忘录设计模式

在对象状态不断变化的任何应用中都应使用备忘录模式,并且该应用的用户可以在任何时候决定回滚或撤消所做的更改。

备忘录也可用于必须从其上一个已知工作状态或草稿重新启动的应用中。 这样的一个示例可以是 IDE,它可以在关闭 IDE 之前由用户更改后重新启动。

2.备忘录模式的真实示例

  • 在 GUI 编辑器(例如 MS 画图)中,我们可以继续对图形进行更改,并且可以使用CTRL + Z之类的简单命令来回滚更改。
  • 在代码编辑器中,我们可以使用简单的命令将撤消和重做还原或应用任何代码更改。
  • 在计算器应用中,我们只需按一下按钮就可以重新访问内存中的所有计算。
  • 在编程中,可以使用备忘录在数据库事务期间创建检查点。 如果任何操作失败,我们只需将所有内容回滚到最后一个已知的稳定数据库状态。
  • javax.swing.text.JTextComponent类提供了撤消支持机制。 javax.swing.undo.UndoManager可以充当看守,javax.swing.undo.UndoableEdit的实现可以像备忘录,而javax.swing.text.Document的实现可以像发起者。

3.备忘录设计模式

3.1 架构

Memento Design Pattern

备忘录设计模式

图片提供 - 维基百科

3.2 设计参与者

备忘录模式有三个参与者。

  1. Originator – 是知道如何创建和保存其状态以备将来使用的对象。 它提供了方法createMemento()restore(memento)
  2. Caretaker – 在发起者上执行操作,同时可能回滚。 它跟踪多种备忘录。 看守者类是指发起者类,用于保存和恢复发起者的内部状态。
  3. Memento – 由发起者编写和读取,并由看守者管理的锁盒。 原则上,备忘录必须在不可变的对象中,这样一旦创建便没有人可以更改其状态。

4. 备忘录设计模式示例

在此示例中,我们为Article对象创建备忘录,该对象具有三个基本属性 – idtitlecontentArticleMemento类用作Article对象的备忘录。

public class Article 
{
	private long id;
	private String title;
	private String content;

	public Article(long id, String title) {
		super();
		this.id = id;
		this.title = title;
	}

	//Setters and getters

	public ArticleMemento createMemento() 
	{
		ArticleMemento m = new ArticleMemento(id, title, content);
		return m;
	}

	public void restore(ArticleMemento m) {
		this.id = m.getId();
		this.title = m.getTitle();
		this.content = m.getContent();
	}

	@Override
	public String toString() {
		return "Article [id=" + id + ", title=" + title + ", content=" + content + "]";
	}
}

public final class ArticleMemento 
{
	private final long id;
	private final String title;
	private final String content;

	public ArticleMemento(long id, String title, String content) {
		super();
		this.id = id;
		this.title = title;
		this.content = content;
	}

	public long getId() {
		return id;
	}

	public String getTitle() {
		return title;
	}

	public String getContent() {
		return content;
	}
}

Main类充当Caretaker,它创建并恢复memento对象。

public class Main 
{
	public static void main(String[] args) 
	{
		Article article = new Article(1, "My Article");
		article.setContent("ABC");		//original content
		System.out.println(article);

		ArticleMemento memento = article.createMemento();	//created immutable memento

		article.setContent("123");		//changed content
		System.out.println(article);

		article.restore(memento);		//UNDO change
		System.out.println(article);	//original content
	}
}

程序输出。

Article [id=1, title=My Article, content=ABC]
Article [id=1, title=My Article, content=123]
Article [id=1, title=My Article, content=ABC]

5.常见问题

5.1 备忘录是内部类吗?

可以用许多不同的方式来实现备忘录设计模式,例如内部类,包专用可见性或序列化等。实现备忘录模式没有固定的准则。

5.2 备忘录模式的好处

  • 最大的优点是,您始终可以丢弃不需要的更改,并将其还原到预期或稳定的状态。
  • 您不会破坏与参与此模型的关键对象关联的封装。
  • 保持高凝聚力。
  • 提供一种简单的恢复技术。

5.3 备忘录模式的挑战

  • 大量备忘录需要更多存储空间。 同时,他们给看守人增加了负担。
  • 同时还增加了维护成本,因为还需要付出代码努力来管理备忘录类。
  • 保存状态的额外时间降低了系统的整体性能。

在评论中向我发送有关备忘录模式的问题。

学习愉快!

观察者设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/observer-design-pattern/

根据 GoF 定义,观察者模式定义了对象之间的一对多依赖关系,因此当一个对象更改状态时,将自动通知并更新其所有依赖关系。 它也称为发布 - 订阅模式

在观察者模式中,有许多观察者(订阅者对象)正在观察特定的主题(发布者对象)。 观察者将自己注册到某个主题时,可以在该主题内部进行更改时得到通知。

观察者对象可以在任何时间点从主体注册或注销。 它有助于使对象与对象松散耦合。

1.何时使用观察者设计模式

如上所述,当您设计一个系统时,多个实体对某个特定的第二个实体对象的任何可能更新感兴趣,我们可以使用观察者模式。

Observer Pattern

观察者模式

该流程非常简单易懂。 应用创建具体的主题对象。 所有具体的观察者都会进行注册,以便随时了解主题状态的任何更新。

一旦主题状态改变,主题就会通知所有注册的观察者,并且观察者可以访问更新后的状态并采取相应的行动。

Observer Pattern Sequence Diagram

观察者模式时序图

2.观察者模式的真实示例

  • 观察者模式的真实示例可以是任何社交媒体平台,例如 Facebook 或 Twitter。 当某人更新其状态时,所有关注者都会收到通知。

    关注者可以在任何时间跟随或取消关注另一个人。 一旦取消关注,该人将来就不会收到该主题的通知。

  • 在编程中,观察者模式是面向消息的应用的基础。 应用更新后,其状态会通知订阅者有关更新的信息。 像 HornetQ , JMS 之类的框架都在此模式下工作。

  • 同样,基于 Java UI 的编程,所有键盘和鼠标事件均由其监听器对象和指定的函数处理。 当用户单击鼠标时,预订鼠标单击事件的函数将作为方法参数传递给它的所有上下文数据被调用。

3.观察者设计模式

3.1 架构

Observer Pattern Architecture

观察者模式架构

3.2 设计参与者

观察者模式有四个参与者。

  • Subject – 接口或抽象类定义了将观察者附加到主题或从主题上拆下的操作。
  • ConcreteSubject - 具体的Subject类。 它维护对象的状态,并在状态发生变化时通知附加的观察者。
  • Observer – 定义用于通知此对象的操作的接口或抽象类。
  • ConcreteObserver – 具体的Observer实现。

4.观察者设计模式示例

在下面的示例中,我将创建类型为Subject的消息发布者和类型为Observer的三个订阅者。 发布者将定期将消息发布给所有订阅或附加的观察者,他们会将更新后的消息打印到控制台。

主题和具体主题

public interface Subject 
{
    public void attach(Observer o);
    public void detach(Observer o);
    public void notifyUpdate(Message m);
}

import java.util.ArrayList;
import java.util.List;

public class MessagePublisher implements Subject {

    private List<Observer> observers = new ArrayList<>();

    @Override
    public void attach(Observer o) {
        observers.add(o);
    }

    @Override
    public void detach(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyUpdate(Message m) {
        for(Observer o: observers) {
            o.update(m);
        }
    }
}

观察者和具体观察者

public interface Observer 
{
    public void update(Message m);
}

public class MessageSubscriberOne implements Observer 
{
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriberOne :: " + m.getMessageContent());
    }
}

public class MessageSubscriberTwo implements Observer 
{
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriberTwo :: " + m.getMessageContent());
    }
}

public class MessageSubscriberThree implements Observer 
{
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriberThree :: " + m.getMessageContent());
    }
}

状态对象

该对象必须是不变的对象,这样任何类都不能错误地修改其内容。

public class Message 
{
    final String messageContent;

    public Message (String m) {
        this.messageContent = m;
    }

    public String getMessageContent() {
        return messageContent;
    }
}

现在测试发布者和订阅者之间的通信。

public class Main 
{
    public static void main(String[] args) 
    {
        MessageSubscriberOne s1 = new MessageSubscriberOne();
        MessageSubscriberTwo s2 = new MessageSubscriberTwo();
        MessageSubscriberThree s3 = new MessageSubscriberThree();

        MessagePublisher p = new MessagePublisher();

        p.attach(s1);
        p.attach(s2);

        p.notifyUpdate(new Message("First Message"));   //s1 and s2 will receive the update

        p.detach(s1);
        p.attach(s3);

        p.notifyUpdate(new Message("Second Message")); //s2 and s3 will receive the update
    }
}

程序输出。

MessageSubscriberOne :: First Message
MessageSubscriberTwo :: First Message

MessageSubscriberTwo :: Second Message
MessageSubscriberThree :: Second Message

5.常见问题

  • 不同类型的观察者可以注册一个主题吗?

    观察者的性质和功能可以有所不同,但它们都必须实现一个通用的Observer接口,主题支持该接口注册和注销。

  • 我可以在运行时添加或删除观察者吗?

    是。 我们可以在任何时间添加或删除观察者。

  • 观察者模式和责任链模式之间的区别?

    在观察者模式中,所有已注册的处理器对象都同时获得通知,并且它们同时处理更新。

    但是在责任链模式中,链中的处理器对象是一个接一个地通知的,此过程一直持续到一个对象完全处理了通知为止。

  • 观察者模式的好处?

    主题和观察者构成一个松散耦合的系统。 他们不需要明确地彼此了解。 我们可以随时独立添加或删除观察者。

相关的 Java 类:

Observer Java 文档

Observable Java 文档

状态设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/state-design-pattern/

状态模式行为型设计模式。 根据 GoF 定义,状态允许对象在其内部状态更改时更改其行为。 该对象似乎将更改其类。

从上面的定义可以得出,对象的每个可能状态应有一个单独的具体类。 每个具体的状态对象都有逻辑,可以根据其当前状态和作为方法参数传递给它的上下文信息来接受或拒绝状态转换请求。

1.何时使用状态模式

在任何应用中,当我们处理对象时,该对象在其生命周期期间可能处于不同的状态,以及如何根据其当前状态处理传入的请求(或进行状态转换)– 我们可以使用状态模式。

如果在这种情况下不使用状态模式,则最终将有很多if-else语句,这些语句会使代码库变得丑陋,不必要地复杂且难以维护。 状态模式允许对象根据当前状态表现不同,并且我们可以在不同类中定义特定于状态的行为。

状态模式解决了对象在其内部状态更改时应更改其行为的问题。 同样,添加新状态不应影响现有状态的行为。

2.状态模式的真实例子

  • 为简单起见,让我们想象一下通过遥控器操作的“电视盒”。 我们可以通过按遥控器上的按钮来更改电视的状态。 但是电视的状态是否会改变,取决于电视的当前状态。 如果电视已打开,我们可以将其关闭,静音或更改显示方式和来源。 但是,如果电视关闭,当我们按遥控器按钮时将什么也不会发生。

    对于关闭的电视。 只有可能的下一个状态才能打开。

  • 状态模式用于在复杂的应用中实现状态机的实现。

  • 另一个示例可以是 Java 线程状态。 线程可以是其生命周期中的五个状态之一。 只有在获得当前状态后才能确定其下一个状态。 例如,我们无法启动已停止的线程,也无法让线程等待,直到线程开始运行。

3.状态设计模式的实现

定义单独的(状态)对象,这些对象封装每个状态的特定于状态的行为。 即,定义用于执行特定于状态的行为的接口(状态),并定义实现每个状态的接口的类。

状态模式未指定状态转换的定义位置。 选择有两个:“上下文”对象,或每个单独的状态派生类。

3.1 架构

State design pattern

状态设计模式

3.2 设计参与者

  • State – 接口定义每个状态必须处理的操作。
  • ConcreteState – 包含状态特定行为的类。
  • Context – 定义与客户端进行交互的接口。 它维护对具体状态对象的引用,这些引用可用于定义对象的当前状态。 它将特定于状态的行为委派给不同的State对象。

4.状态模式示例

在此示例中,我们模拟了快递递送系统,其中包裹在转换过程中可能处于不同状态。

public interface PackageState 
{
    public void updateState(DeliveryContext ctx);
}

public class Acknowledged implements PackageState 
{
    //Singleton
    private static Acknowledged instance = new Acknowledged();

    private Acknowledged() {}

    public static Acknowledged instance() {
        return instance;
    }

    //Business logic and state transition
    @Override
    public void updateState(DeliveryContext ctx) 
    {
        System.out.println("Package is acknowledged !!");
        ctx.setCurrentState(Shipped.instance());
    }
}

public class Shipped implements PackageState 
{
    //Singleton
    private static Shipped instance = new Shipped();

    private Shipped() {}

    public static Shipped instance() {
        return instance;
    }

    //Business logic and state transition
    @Override
    public void updateState(DeliveryContext ctx) 
    {
        System.out.println("Package is shipped !!");
        ctx.setCurrentState(InTransition.instance());
    }
}

public class InTransition implements PackageState 
{
    //Singleton
    private static InTransition instance = new InTransition();

    private InTransition() {}

    public static InTransition instance() {
        return instance;
    }

    //Business logic and state transition
    @Override
    public void updateState(DeliveryContext ctx) 
    {
        System.out.println("Package is in transition !!");
        ctx.setCurrentState(OutForDelivery.instance());
    }
}

public class OutForDelivery implements PackageState 
{
    //Singleton
    private static OutForDelivery instance = new OutForDelivery();

    private OutForDelivery() {}

    public static OutForDelivery instance() {
        return instance;
    }

    //Business logic and state transition
    @Override
    public void updateState(DeliveryContext ctx) 
    {
        System.out.println("Package is out of delivery !!");
        ctx.setCurrentState(Delivered.instance());
    }
}

public class Delivered implements PackageState 
{
    //Singleton
    private static Deliveredinstance = new Delivered();

    private Delivered() {}

    public static Deliveredinstance() {
        return instance;
    }

    //Business logic
    @Override
    public void updateState(DeliveryContext ctx) 
    {
        System.out.println("Package is delivered!!");
    }
}

上下文

public class DeliveryContext {

    private PackageState currentState;
    private String packageId;

    public DeliveryContext(PackageState currentState, String packageId) 
    {
        super();
        this.currentState = currentState;
        this.packageId = packageId;

        if(currentState == null) {
            this.currentState = Acknowledged.instance();
        }
    }

    public PackageState getCurrentState() {
        return currentState;
    }

    public void setCurrentState(PackageState currentState) {
        this.currentState = currentState;
    }

    public String getPackageId() {
        return packageId;
    }

    public void setPackageId(String packageId) {
        this.packageId = packageId;
    }

    public void update() {
        currentState.updateState(this);
    }
}

现在测试代码。

public class Main 
{
    public static void main(String[] args) 
    {
        DeliveryContext ctx = new DeliveryContext(null, "Test123");

        ctx.update();
        ctx.update();
        ctx.update();
        ctx.update();
        ctx.update();
    }
}

程序输出。

Package is acknowledged !!
Package is shipped !!
Package is in transition !!
Package is out of delivery !!
Package is delivered !!

5.常见问题

5.1 状态模式与策略模式

两种模式的结构相似,但目的不同。 策略模式为子类提供了更好的选择,而在状态模式下 – 行为封装在单独的类中。

然而,两者都促进了继承的组合和委托。

5.2 状态对象应该是单例吗?

是。 始终尝试使状态对象单例。

5.3 状态设计模式的优势?

我们可以轻松地在应用中添加新状态和新行为,而不会影响其他组件。

它还通过减少对if-else语句或switch/case条件逻辑的使用来帮助降低复杂度。

5.4 状态设计模式的缺点?

状态模式也称为状态的对象。 因此,我们可以假设更多的状态需要更多的代码,并且明显的副作用对我们来说很难维护。

学习愉快!

阅读更多:

维基百科

Gson 教程

原文: https://howtodoinjava.com/learningpaths/gson/

通常,所有现代应用都从远程服务(例如 REST 或 SOAP)中获取数据。 数据大部分为 XML 或 JSON 格式。 Gson 自动(在必要时使用简单的toJson()fromJson()方法手动或手动)帮助应用进行 Java-JSON 序列化和反序列化

Gson 可以处理任意 Java 对象,包括我们没有源代码的现有对象。

Gson 的最佳好处是,在我们为某些成员字段进行非常特定的操作之前,不必强制将注解添加到 Java 类中。

请注意,Gson实例在调用 Json 操作时不会保持任何状态。 因此,您可以自由地将同一对象重用于多个 Json 序列化和反序列化操作。

在本 Gson 教程中,我们将学习在我们的应用中有效地使用它。

1. Gson 基础知识

在第一部分中,我们将了解 Google Gson 库及其基本映射功能。

  • 安装

    学习使用诸如 maven,gradle 或简单的 jar 文件之类的构建工具在 Java 应用中包括 gson 依赖项。

  • 简单序列化和反序列化

    学习如何使用 GSON 将简单的 Java 对象序列化为 JSON 表示,并将 JSON 字符串反序列化为等效的 Java 对象。

  • JSON 输出格式紧凑型 VS 精美打印

    Gson 提供的默认 JSON 输出是紧凑的 JSON 格式。 使用精美打印功能来格式化 JSON 以供读取。

  • 数组和对象列表的映射

    了解如何使用 Google GSON 库将包含 json 数组的 JSON 反序列化或解析为 Java 数组或 Java 列表对象。

  • 集的映射

    了解如何使用 Google GSON 库反序列化或解析 JSON 以在 Java 中进行设置(例如HashSet)。

  • 映射的映射

    学习使用 Google Gson 库序列化HashMap。 还应学习使用 Gson 将 JSON 字符串反序列化为包含自定义对象的HashMap,以便将字段值复制到适当的通用类型中。

2.高级用法

第一部分中的基本示例足以应付默认用例。 我们可能需要自定义 Gson 的行为以支持特定要求。 了解如何执行此操作。

  • 使用GsonBuilder自定义 Gson 对象

    对于简单的用例,使用'Gson gson = new Gson();'就足够了。 学习使用 GsonBuilder 通过自定义配置创建新的 Gson 实例。

  • 序列化空值

    默认情况下,Gson 忽略空值。 学习在序列化和反序列化期间包括空值。

  • 版本支持

    可以使用@Since注解维护同一对象的多个版本。 Gson 将忽略任何大于配置的版本号的字段/对象。

  • 使用@SerializedName的自定义字段名称

    了解如何使用 Google GSON 库将 Java 对象序列化为其 JSON 表示形式,以及将 JSON 字符串反序列化为等效的 Java 对象。

  • 从序列化和反序列化中排除字段

    Gson 支持多种机制来排除顶级类,字段和字段类型。 了解如何使用它们。

  • JsonReader – 流式 JSON 解析器

    了解如何读取 JSON 字符串或文件作为 JSON 令牌流。

  • JsonParserJsonElementJsonObject

    学习将 JSON 字符串解析或流化为 Java 对象的树形结构并转换为JsonElement

  • 自定义序列化和反序列化

    学习将 JSON 字符串解析或流化为 Java 对象的树形结构并转换为JsonElement

  • Gson 教程 - 回顾

    让我们来回顾一下到目前为止我们已经学过的最重要的主题。

请把关于 gson 教程的问题交给我。

学习愉快!

参考文献:

Github 上的 Google Gson

策略设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/strategy-design-pattern/

策略设计模式是行为设计​​模式,在该行为型设计模式中,我们选择算法或任务在运行时的特定实现 - 从同一任务的多个其他实现中选择。

重要的一点是,这些实现是可互换的 – 根据任务可以选择一种实现而不会干扰应用工作流程。

Table of Contents
Introduction
Design Participants
Problem Statement
Solution with strategy design pattern
Code Implementation
Demo
Popular Implementations
Summary

介绍

策略模式涉及从其宿主类中删除算法并将其放在单独的类中,以便在同一编程上下文中可能存在不同的算法(即策略),可以在运行时选择它们。

策略模式使客户端代码可以从一系列相关但不同的算法中进行选择,并提供了一种简单的方法来根据客户端上下文在运行时选择任何算法。

由开/关原则驱动

该模式基于开闭原则。 我们不需要修改上下文(已关闭以进行修改),但是可以选择并添加任何实现(可扩展用于开放)。

例如,在Collections.sort()中 - 我们无需更改排序方法即可获得不同的排序结果。 我们可以在运行时提供不同的比较器。

阅读更多:比较器示例

设计参与者

在策略模式中,我们首先创建算法的抽象。 这是具有抽象操作的接口。 然后,我们创建此抽象的实现,这些实现称为策略。

客户端将始终调用抽象,并将传递上下文对象。 该上下文对象将决定使用哪种策略。

Strategy Pattern Participants

策略模式参与者

问题陈述

让我们解决一个设计问题,以更详细地了解策略模式。

我想设计一个社交媒体应用,使我可以在四个社交平台(例如,Facebook,Google Plus,Twitter 和 Orkut)上与朋友联系。 现在,我希望该客户端能够告诉朋友的名称和所需的平台 - 然后我的应用应该透明地连接到他。

更重要的是,如果我想在应用中添加更多社交平台,则应用代码应在不破坏设计的情况下容纳它。

使用策略设计模式的解决方案

在上述问题中,我们有一个可以以多种方式(连接到朋友)完成的操作,用户可以在运行时选择所需的方式。 因此,它非常适合用于策略设计模式。

为了实现该解决方案,让我们一次设计一个参与者。

  • ISocialMediaStrategy - 抽象操作的接口。
  • SocialMediaContext – 确定实现的上下文。
  • 实现ISocialMediaStrategy的各种实现。 例如。 FacebookStrategyGooglePlusStrategyTwitterStrategyOrkutStrategy

类图

Strategy Pattern Class Diagram

策略模式类图

代码实现

现在,让我们在设计参与者之上进行编码。

ISocialMediaStrategy.java

package com.howtodoinjava.designpattern.strategy;

public interface ISocialMediaStrategy 
{
	public void connectTo(String friendName);
}

SocialMediaContext.java

package com.howtodoinjava.designpattern.strategy;

public class SocialMediaContext 
{
	ISocialMediaStrategy smStrategy;

	public void setSocialmediaStrategy(ISocialMediaStrategy smStrategy) 
	{
		this.smStrategy = smStrategy;
	}

	public void connect(String name) 
	{
		smStrategy.connectTo(name);
	}
}

FacebookStrategy.java

package com.howtodoinjava.designpattern.strategy.impl;

import com.howtodoinjava.designpattern.strategy.ISocialMediaStrategy;

public class FacebookStrategy implements ISocialMediaStrategy {

	public void connectTo(String friendName) 
	{
		System.out.println("Connecting with " + friendName + " through Facebook");
	}
}

GooglePlusStrategy.java

package com.howtodoinjava.designpattern.strategy.impl;

import com.howtodoinjava.designpattern.strategy.ISocialMediaStrategy;

public class GooglePlusStrategy implements ISocialMediaStrategy {

	public void connectTo(String friendName) 
	{
		System.out.println("Connecting with " + friendName + " through GooglePlus");
	}
}

TwitterStrategy.java

package com.howtodoinjava.designpattern.strategy.impl;

import com.howtodoinjava.designpattern.strategy.ISocialMediaStrategy;

public class TwitterStrategy implements ISocialMediaStrategy {

	public void connectTo(String friendName) 
	{
		System.out.println("Connecting with " + friendName + " through Twitter");
	}
}

OrkutStrategy.java

package com.howtodoinjava.designpattern.strategy.impl;

import com.howtodoinjava.designpattern.strategy.ISocialMediaStrategy;

public class OrkutStrategy implements ISocialMediaStrategy {

	public void connectTo(String friendName) 
	{
		System.out.println("Connecting with " + friendName + " through Orkut [not possible though :)]");
	}
}

演示

现在看看如何在运行时中使用这些策略。

package com.howtodoinjava.designpattern.strategy.demo;

import com.howtodoinjava.designpattern.strategy.SocialMediaContext;
import com.howtodoinjava.designpattern.strategy.impl.FacebookStrategy;
import com.howtodoinjava.designpattern.strategy.impl.GooglePlusStrategy;
import com.howtodoinjava.designpattern.strategy.impl.OrkutStrategy;
import com.howtodoinjava.designpattern.strategy.impl.TwitterStrategy;

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

		// Creating social Media Connect Object for connecting with friend by
		// any social media strategy.
		SocialMediaContext context = new SocialMediaContext();

		// Setting Facebook strategy.
		context.setSocialmediaStrategy(new FacebookStrategy());
		context.connect("Lokesh");

		System.out.println("====================");

		// Setting Twitter strategy.
		context.setSocialmediaStrategy(new TwitterStrategy());
		context.connect("Lokesh");

		System.out.println("====================");

		// Setting GooglePlus strategy.
		context.setSocialmediaStrategy(new GooglePlusStrategy());
		context.connect("Lokesh");

		System.out.println("====================");

		// Setting Orkut strategy.
		context.setSocialmediaStrategy(new OrkutStrategy());
		context.connect("Lokesh");
	}
}

输出:

Connecting with Lokesh through Facebook
====================
Connecting with Lokesh through Twitter
====================
Connecting with Lokesh through GooglePlus
====================
Connecting with Lokesh through Orkut [not possible though :)]

流行实现

  1. Java Collections.sort(list, comparator)方法,客户端根据运行时的要求实际将适当的比较器传递给该方法,并且该方法通用以接受任何比较器类型。 根据传递的比较器,可以对同一集合进行不同的排序。
  2. Log4j 中的附加器,布局和过滤器。
  3. UI 工具箱中的布局管理器。

总结

  • 此模式定义了一组相关算法,并将它们封装在单独的类中,并允许客户端在运行时选择任何算法。
  • 它允许添加新算法而无需修改使用算法或策略的现有算法或上下文类
  • 策略是“GOF”列表中的一种行为型模式。
  • 策略模式基于 SOLID 主体的开放式封闭设计原则。
  • Collections.sort()Comparator接口的组合是策略模式的可靠示例。

这就是有关策略设计模式的全部内容。 将我的问题放在评论部分。

下载源码

学习愉快!

模板方法设计模式

原文: https://howtodoinjava.com/design-patterns/behavioral/template-method-pattern/

模板方法设计模式是广泛接受的行为型设计模式,用于在编程的上下文中实现某种算法(固定的步骤集)。

它定义了执行多步算法的顺序步骤,并且还可以选择提供默认实现(根据要求)。

Table of Contents

Introduction
Problem Statement
Solution
Code Implementation
Key Design Points
Popular implementations of template method pattern
Summary

介绍

您是否在编程中遇到过任何情况,每次都需要以固定顺序定义一组特定步骤,以便可以由实现类强制执行? 如果是这样,那么模板方法模式是适合您的解决方案,可用于有序执行这些步骤(算法)。

模板方法模式的适用性

  • 当我们有预定义的步骤来实现一些算法时。
  • 当我们要避免重复代码时,请在基类中移动通用实现和步骤。

现在,让我们解决一个设计问题以详细了解它。

问题陈述

可以说,我们需要建造通常有某些步骤的任何房屋。 有些步骤具有默认实现,而某些步骤是针对没有默认实现的实现者的。

默认实现的步骤

  • 基础建设
  • 屋顶施工

默认实现为“否”的步骤

  • 墙的建造
  • 门窗施工
  • 绘画
  • 室内装修

我们希望通过应用中的所有实现类依次执行这些步骤

解决方案

在上述问题中,我们按照固定顺序采取了某些步骤,所有建筑类别都必须遵循这些步骤。 因此,我们可以使用模板方法设计模式来解决此问题。

让我们定义一个将所有步骤作为方法的基类House,将一个将依次调用中间步骤的模板方法 buildhouse()定义为基类。

我们还将根据房屋的类型(例如GlassWallHouseConcreteWallHouse)创建派生类。

类图

template method design pattern class diagram

使用模板方法设计模式的类图

代码实现

首先,我们需要创建一个名为House的抽象类,它将定义名为buildHouse()的模板方法。

House.java

package com.howtodoinjava.designpattern.template;

/**
 * abstract class House containing template method buildHouse and implementation
 * of steps which is same for all types of houses. Those implementations have
 * been marked as final.
 */

public abstract class House {
	/**
	 * This is the template method we are discussing. This method should be
	 * final so that other class can't re-implement and change the order of the
	 * steps.
	 */
	public final void buildhouse() {
		constructBase();
		constructRoof();
		constructWalls();
		constructWindows();
		constructDoors();
		paintHouse();
		decorateHouse();
	}

	public abstract void decorateHouse();

	public abstract void paintHouse();

	public abstract void constructDoors();

	public abstract void constructWindows();

	public abstract void constructWalls();

	/**
	 * final implementation of constructing roof - final as all type of house
	 * Should build roof in same manner.
	 */
	private final void constructRoof() {
		System.out.println("Roof has been constructed.");
	}

	/**
	 * final implementation of constructing base - final as all type of house
	 * Should build base/foundation in same manner.
	 */
	private final void constructBase() {
		System.out.println("Base has been constructed.");
	}
}

现在,我们将创建该类的两个实现,可用于建造具体房屋和玻璃房屋。

ConcreteWallHouse.java

package com.howtodoinjava.designpattern.template;

public class ConcreteWallHouse extends House {
      @Override
      public void decorateHouse() {
            System.out.println(“Decorating Concrete Wall House”);
      }
      @Override
      public void paintHouse() {
            System.out.println(“Painting Concrete Wall House”);
      }
      @Override
      public void constructDoors() {
            System.out.println(“Constructing Doors for Concrete Wall House”);
      }
      @Override
      public void constructWindows() {
            System.out.println(“Constructing Windows for Concrete Wall House”);
      }
      @Override
      public void constructWalls() {
            System.out.println(“Constructing Concrete Wall for my House”);
      }
}

GlassWallHouse.java

package com.howtodoinjava.designpattern.template;

public class GlassWallHouse extends House {
	@Override
	public void decorateHouse() {
		System.out.println("Decorating Glass Wall House");
	}

	@Override
	public void paintHouse() {
		System.out.println("Painting Glass Wall House");
	}

	@Override
	public void constructDoors() {
		System.out.println("Constructing Doors for Glass Wall House");
	}

	@Override
	public void constructWindows() {
		System.out.println("Constructing Windows for Glass Wall House");
	}

	@Override
	public void constructWalls() {
		System.out.println("Constructing Glass Wall for my House");
	}
}

演示

让我们尝试建造两种房屋。

package com.howtodoinjava.designpattern.template;

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

            System.out.println(“Going to build Concrete Wall House”);

            House house = new ConcreteWallHouse();
            house.buildhouse();

            System.out.println(“Concrete Wall House constructed successfully”);

            System.out.println(“********************”);

            System.out.println(“Going to build Glass Wall House”);

            house = new GlassWallHouse();
            house.buildhouse();

            System.out.println(“Glass Wall House constructed successfully”);
      }
}

输出:

Going to build Concrete Wall House
Base has been constructed.
Roof has been constructed.
Constructing Concrete Wall for my House
Constructing Windows for Concrete Wall House
Constructing Doors for Concrete Wall House
Painting Concrete Wall House
Decorating Concrete Wall House
Concrete Wall House constructed successfully

********************

Going to build Glass Wall House
Base has been constructed.
Roof has been constructed.
Constructing Glass Wall for my House
Constructing Windows for Glass Wall House
Constructing Doors for Glass Wall House
Painting Glass Wall House
Decorating Glass Wall House
Glass Wall House constructed successfully

关键设计要点

  1. 将模板方法标记为最终的,以便实现类无法覆盖和更改步骤的顺序。
  2. 在基类中,使用默认实现实现所有方法,因此派生类无需定义它们。
  3. 标记所有必须抽象实现派生类的方法

模板方法模式的流行实现

这些是模板方法设计模式的几种流行的现有实现。

  • Java IO 中InputStreamOutputStreamReaderWriter的非抽象方法。
  • 一些抽象集合类的非抽象方法,例如AbstractListAbstractSetAbstractMap等。

总结

模板模式是一种非常简单的设计模式,用于定义和实现编程示例中的某些顺序算法步骤。 它有助于定义算法的框架,该框架不得在子类中被覆盖。

因此,展望未来,当您需要实现上述业务场景时,请考虑一下模板模式 – 它非常易于实现并且也非常易于维护。

学习愉快!

下载源码

访问者设计模式示例

原文: https://howtodoinjava.com/design-patterns/behavioral/visitor-design-pattern-example-tutorial/

设计模式用于解决模式中出现的问题,我们都知道,对吧? 我们也知道行为型设计模式是识别对象之间常见通信模式的设计模式。 这种行为型模式之一是访问者模式,我们将在本文中学习。

如果您一直在处理管理大量产品的应用,那么您可以轻松地解决此问题:

“您需要在类的层次结构中添加一个新方法,但是添加该方法可能会给设计带来痛苦或破坏。”

显然,您希望对象的层次结构可以修改其行为,而不需要修改其源代码。 怎么做? 为了解决这个问题,访问者模式成为现实。

Sections in this post:

Visitor pattern introduction
Design participants/components
Sample problem to solve
Proposed solution using Visitor pattern
Implementation code
How to use visitors in application code
源码下载

访问者模式介绍

根据维基百科所述, 访问者设计模式是一种将算法与操作对象的结构分离的方法。 这种分离的实际结果是能够在不修改那些对象结构的情况下向现有对象结构添加新操作。 这是遵循开闭原则SOLID 设计原则 之一)的一种方法。

上面的设计灵活性允许将方法添加到任何对象层次结构,而无需修改为层次结构编写的代码。 而是使用双调度机制来实现此功能。 双调度是一种特殊的机制,可以根据调用中涉及的两个对象的运行时类型,将函数调用分派给不同的具体函数。

设计参与者/组件

此模式中的参与者类是:

Visitor – 这是用于声明所有可访问类类型的访问操作的接口或抽象类。

ConcreteVisitor – 对于每种类型的访问者,必须实现在抽象访问者中声明的所有访问方法。 每个访问者将负责不同的操作。

Visitable – 是一个声明接受操作的接口。 这是使访问者对象可以“访问”对象的入口点。

ConcreteVisitable – 这些类实现了 Visitable 接口或类并定义了接受操作。 使用 accept 操作将访问者对象传递给该对象。

要解决的示例问题

一个例子总是比冗长的理论更好。 因此,让我们在这里也有一个访问者设计模式。

假设我们有一个在不同环境中管理路由的应用。 路由应该能够从其他节点发送和接收字符数据,应用应该能够在不同的环境中配置路由。

本质上,设计应该足够灵活,以支持路由将来可以在其他环境中进行配置的方式,而无需对源代码进行大量修改。

Existing routers in application

应用中的现有路由

我们有以上 3 种类型的路由,我们需要为它们编写代码。 解决此问题的一种方法是在Router.java接口中定义诸如configureForWinodws()configureForLinux()之类的方法,并在不同的产品中实现它们,因为每种方法都有自己的配置设置和过程。

但是上述方法的问题在于,每次引入新环境时,都必须重新编译整个路由的层次结构。 不可接受的解决方案。 那么什么可以预防这种情况呢?

使用访问者模式的建议解决方案

访问者模式非常适合这些类型的问题,在这些类型的问题中,您希望对对象的层次结构引入新的操作,而无需更改其结构或对其进行修改。 在此解决方案中,我们将通过引入两种方法(即accept()visit()方法)来实现双调度技术。 方法accept(),将在路由的层次结构中定义,访问方法将在访问者级别。

每当需要添加新环境时,都会将新的访问者添加到访问者层次结构中,这需要为所有可用的路由实现visit()方法,仅此而已。

Solution using visitor pattern

使用访问者模式的解决方案

在上面的类图中,我们为 Mac 和 Linux 操作系统配置了路由。 如果我们还需要添加 Windows 功能,那么我不需要更改任何类,只需定义一个新的访问者WindowsConfigurator并实现RouterVisitor接口中定义的visit()方法。 它将提供所需的功能,而无需任何进一步的修改。

实现代码

让我们看一下上面讨论的问题和解决方案所涉及的不同文件的源代码。

Router.java

public interface Router 
{
	public void sendData(char[] data);
	public void acceptData(char[] data);

	public void accept(RouterVisitor v);
}

DLinkRouter.java

public class DLinkRouter implements Router{

	@Override
	public void sendData(char[] data) {
	}

	@Override
	public void acceptData(char[] data) {
	}

	@Override
	public void accept(RouterVisitor v) {
		v.visit(this);
	}
}

LinkSysRouter.java

public class LinkSysRouter implements Router{

	@Override
	public void sendData(char[] data) {
	}

	@Override
	public void acceptData(char[] data) {
	}

	@Override
	public void accept(RouterVisitor v) {
		v.visit(this);
	}
}

TPLinkRouter.java

public class TPLinkRouter implements Router{

	@Override
	public void sendData(char[] data) {
	}

	@Override
	public void acceptData(char[] data) {
	}

	@Override
	public void accept(RouterVisitor v) {
		v.visit(this);
	}
}

RouterVisitor.java

public interface RouterVisitor {
	public void visit(DLinkRouter router);
	public void visit(TPLinkRouter router);
	public void visit(LinkSysRouter router);
}

LinuxConfigurator.java

public class LinuxConfigurator implements RouterVisitor{

	@Override
	public void visit(DLinkRouter router) {
		System.out.println("DLinkRouter Configuration for Linux complete !!");
	}

	@Override
	public void visit(TPLinkRouter router) {
		System.out.println("TPLinkRouter Configuration for Linux complete !!");
	}

	@Override
	public void visit(LinkSysRouter router) {
		System.out.println("LinkSysRouter Configuration for Linux complete !!");
	}
}

MacConfigurator.java

public class MacConfigurator implements RouterVisitor{

	@Override
	public void visit(DLinkRouter router) {
		System.out.println("DLinkRouter Configuration for Mac complete !!");
	}

	@Override
	public void visit(TPLinkRouter router) {
		System.out.println("TPLinkRouter Configuration for Mac complete !!");
	}

	@Override
	public void visit(LinkSysRouter router) {
		System.out.println("LinkSysRouter Configuration for Mac complete !!");
	}
}

如何在应用代码中使用访问者

要使用上述设计,请按以下给定方式使用访问者。 我已经以 JUNIT 测试用例的形式使用了代码,您可以以适合您的情况的方式更改代码。

TestVisitorPattern.java

public class TestVisitorPattern extends TestCase
{
	private MacConfigurator macConfigurator;
	private LinuxConfigurator linuxConfigurator;
	private DLinkRouter dlink;
	private TPLinkRouter tplink;
	private LinkSysRouter linksys;

	public void setUp()
	{
		macConfigurator = new MacConfigurator();
		linuxConfigurator = new LinuxConfigurator();

		dlink = new DLinkRouter();
		tplink = new TPLinkRouter();
		linksys = new LinkSysRouter();
	}

	public void testDlink()
	{
		dlink.accept(macConfigurator);
		dlink.accept(linuxConfigurator);
	}

	public void testTPLink()
	{
		tplink.accept(macConfigurator);
		tplink.accept(linuxConfigurator);
	}

	public void testLinkSys()
	{
		linksys.accept(macConfigurator);
		linksys.accept(linuxConfigurator);
	}
}

Output:

DLinkRouter Configuration for Mac complete !!
DLinkRouter Configuration for Linux complete !!
LinkSysRouter Configuration for Mac complete !!
LinkSysRouter Configuration for Linux complete !!
TPLinkRouter Configuration for Mac complete !!
TPLinkRouter Configuration for Linux complete !!

源代码下载

要下载上述应用的源代码,请遵循给定的链接。

下载源码

祝您学习愉快!

结构型设计模式

原文: https://howtodoinjava.com/design-patterns/structural/

结构型设计模式向您展示了如何以灵活和可扩展的方式将系统的不同部分粘合在一起。 它们可帮助您确保当其中一部分发生更改时,整个结构都不需要更改。

这些模式关注以下方面:类如何彼此继承以及如何从其他类组成。 结构型模式使用继承来构成接口或实现。 结构对象模式描述了组成对象以实现新功能的方式

Java 中的适配器设计模式

原文: https://howtodoinjava.com/design-patterns/structural/adapter-design-pattern-in-java/

曾经尝试在笔记本电脑中使用相机存储卡。 您不能直接使用它,因为笔记本电脑中没有接受它的端口。 您必须使用兼容的读卡器。 您将存储卡放入读卡器,然后将读卡器注入笔记本电脑。 该读卡器可以称为适配器。

类似的示例是移动充电器笔记本电脑充电器,它们可以与任何电源一起使用,而不必担心不同位置的电源变化。 这也称为电源“适配器”。

同样在编程中,适配器模式也用于类似目的。 它使两个不兼容的接口能够彼此平滑地工作。 按照定义:

适配器设计模式是[结构型设计模式](// howtodoinjava.com/category/design-patterns/structural/)之一,它的使用是为了使两个不相关的接口可以一起工作。 连接这些不相关接口的对象称为适配器。

关于设计模式的 GOF 中提供的适配器的定义指出:

“将类的接口转换为客户期望的另一个接口。 适配器使类可以协同工作,因为接口不兼容,否则无法一起工作。”

适配器模式也称为包装模式。 当某些其他现有组件必须在不修改源代码的情况下被现有系统采用时,适配器设计对于系统集成非常有用。

典型的交互是这样的:

adapter sequence diagram

在哪里使用适配器设计模式?

这种模式的主要用途是当您需要使用的类不符合接口的要求时。 例如,如果要通过 java 中的命令提示符读取系统输入,则下面的代码是执行此操作的常用方法:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter String");
String s = br.readLine();
System.out.print("Enter input: " + s);

现在,请仔细观察上面的代码。

1)System.inInputStreamstatic实例,声明为

	public final static InputStream in = null;

此输入流以字节流的形式从控制台本地读取数据。

2)如 Java 文档所定义的 BufferedReader,读取字符流。

//Reads text from a character-input stream, buffering characters so as to 
//provide for the efficient reading of characters, arrays, and lines. 

public class BufferedReader extends Reader{..}

现在这是问题所在。 System.in提供字节流,其中BufferedReader需要字符流。 他们将如何一起工作?

这是在两个不兼容的接口之间放置适配器的理想情况InputStreamReader正是这样做并且在System.inBufferedReader之间工作适配器。

/** An InputStreamReader is a bridge from byte streams to character streams: 
  * It reads bytes and decodes them into characters using a specified charset. 
  * The charset that it uses may be specified by name or may be given explicitly, 
  * or the platform's default charset may be accepted. 
  */

public class InputStreamReader extends Reader {...}

我希望上述用例对大家都有意义。 现在,下一个问题是适配器应该做多少工作才能使两个不兼容的接口一起工作?

适配器模式应该执行多少工作?

答案真的很简单,它应该做很多工作,以便两个不兼容的接口都能相互适应。 例如,在我们上面的案例研究中,一个InputStreamReader只是包装了InputStream而没有其他内容。 然后,BufferedReader能够使用基础的Reader读取流中的字符。

/**
 * Creates an InputStreamReader that uses the default charset.
 * @param  in   An InputStream
 */
public InputStreamReader(InputStream in) {
	super(in);
	try {
		sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
	} catch (UnsupportedEncodingException e) {
		// The default encoding should always be available
		throw new Error(e);
	}
}

还要注意,如果目标服务器与被适配者相似,则适配器只需将来自目标的请求委托给被适配者。 如果目标和被适配者不相似,则适配器可能必须在它们之间转换数据结构并实现目标所要求的但被适配者未实现的操作。

现在,当我们对适配器的外观有了很好的了解后,让我们确定适配器设计模式中使用的参与者:

适配器设计模式的参与者

下表列出了参与此模式的类和/或对象:

  • TargetBufferedReader):它定义客户端直接使用的特定于应用的接口。
  • AdapterInputStreamReader):它将接口Adaptee适配到Target接口。 是中间人
  • AdapteeSystem.in):它定义了现有的不兼容接口,需要在应用中使用之前进行修改。
  • Client:它是与Target接口一起使用的应用。

适配器设计模式的其他示例实现

其他值得注意的例子如下:

1) java.util.Arrays#asList()

此方法接受多个字符串并返回输入字符串的列表。 虽然这是非常基本的用法,但是它是适配器的功能,对不对?

2) java.io.OutputStreamWriter(OutputStream)

这类似于我们在本文中讨论的上述用例:

Writer writer = new OutputStreamWriter(new FileOutputStream("c:\\data\\output.txt"));
writer.write("Hello World");

3) javax.xml.bind.annotation.adapters.XmlAdapter#marshal()#unmarshal()

使 Java 类型适应自定义编组处理。 将绑定类型转换为值类型。

这就是这个简单主题的全部内容。

学习愉快!

桥接设计模式

原文: https://howtodoinjava.com/design-patterns/structural/bridge-design-pattern/

桥接设计模式用于将类分解为两个部分(抽象及其实现),以便将来可以相互进化而不会相互影响。 它增加了类抽象与其实现之间的松散耦合。

Table of Contents

Design participants of bridge design pattern
When we need bridge design pattern
Sample problem statement
Solution using bridge design pattern
Final notes

将抽象与其实现分离,以便二者可以独立变化。

桥是“手柄或身体”成语的同义词。 这是一种将实现类封装在接口类内部的设计机制。 前者是身体,后者是手柄。 用户将手柄视为实际类,但工作是在体内完成的。

通过在从抽象到实现的方法调用之间添加一个重定向,可以实现这种分离。

桥接设计模式的设计参与者

Bridge pattern participants

桥接模式参与者

以下参与者构成桥接设计模式。

  1. Abstraction(抽象类)

    它定义了抽象接口,即行为部分。 它还维护实现者参考。

  2. RefinedAbstraction(普通类)

    它扩展了Abstraction定义的接口。

  3. Implementer(接口)

    它定义了实现类的接口。 该接口不需要直接对应于抽象接口,并且可以有很大的不同。 抽象imp提供了一个由Implementer接口提供的操作的实现。

  4. ConcreteImplementor(普通类)

    它实现了Implementer接口。

当我们需要桥接设计模式时

桥接模式是旧建议的应用,“与继承相比更偏向组合”。 当您必须以彼此正交的方式将不同的时间子类化时,它将变得很方便。

例如,假设您要创建具有不同颜色的各种 GUI 形状。 一种解决方案可能是:

Without bridge pattern

不使用桥接模式

但是上述解决方案存在问题。 如果要更改Rectange类别,则可能最终也会更改BlueRectangleRedRectangle – 即使更改是特定于颜色的,也可能需要更改Circle类别。

您可以通过以下方式解耦ShapeColor接口来解决上述问题。

With bridge pattern

使用桥接模式

现在,当您更改任何形状时,颜色将保持不变。 同样,反之亦然。

问题陈述

网桥设计模式最适用于需要提供平台独立性的应用。

假设我们正在设计应用,该应用可以在任何操作系统上下载和存储文件。 我想以这种方式设计系统,将来我应该能够以最小的变化添加更多的平台支持。 此外,如果我想在下载程序类中添加更多支持(例如,仅在 Windows 中删除下载内容),那么它应该不会影响客户端代码以及 linux 下载程序。

使用桥接设计模式的解决方案

由于此问题是与经典平台独立性相关的问题,因此我将使用桥接模式来解决此问题。 我将把下载器组件分为抽象部分和实现部分。

我在这里创建两个接口,FileDownloaderAbstraction表示客户端将与之交互的抽象; FileDownloadImplementor代表实现。 这样,两个层次结构可以独立发展而不会互相影响。

FileDownloaderAbstraction.java

public interface FileDownloaderAbstraction 
{
    public Object download(String path);

    public boolean store(Object object);
}

FileDownloaderAbstractionImpl.java

public class FileDownloaderAbstractionImpl implements FileDownloaderAbstraction {

    private FileDownloadImplementor provider = null;

    public FileDownloaderAbstractionImpl(FileDownloadImplementor provider) {
        super();
        this.provider = provider;
    }

    @Override
    public Object download(String path) 
    {
        return provider.downloadFile(path);
    }

    @Override
    public boolean store(Object object) 
    {
        return provider.storeFile(object);
    }
}

FileDownloadImplementor.java

public interface FileDownloadImplementor
{
    public Object downloadFile(String path);

    public boolean storeFile(Object object);
}

LinuxFileDownloadImplementor.java

public class LinuxFileDownloadImplementor implements FileDownloadImplementor 
{
    @Override
    public Object downloadFile(String path) {
        return new Object();
    }

    @Override
    public boolean storeFile(Object object) {
        System.out.println("File downloaded successfully in LINUX !!");
        return true;
    }
}

WindowsFileDownloadImplementor.java

public class WindowsFileDownloadImplementor implements FileDownloadImplementor 
{
    @Override
    public Object downloadFile(String path) {
        return new Object();
    }

    @Override
    public boolean storeFile(Object object) {
        System.out.println("File downloaded successfully in WINDOWS !!");
        return true;
    }
}

Client.java

public class Client 
{
    public static void main(String[] args) 
    {
        String os = "linux";
        FileDownloaderAbstraction downloader = null;

        switch (os) 
        {
            case "windows":
                downloader = new FileDownloaderAbstractionImpl( new WindowsFileDownloadImplementor() );
                break;

            case "linux":
                downloader = new FileDownloaderAbstractionImpl( new LinuxFileDownloadImplementor() );
                break;

            default:
                System.out.println("OS not supported !!");
        }

        Object fileContent = downloader.download("some path");
        downloader.store(fileContent);
    }
}

Output:

File downloaded successfully in LINUX !!

抽象的更改不会影响实现

现在,假设您要在抽象层添加另一项功能(即删除)。 它也不能强迫现有的实现者和客户进行改变。

FileDownloaderAbstraction.java

public interface FileDownloaderAbstraction 
{
    public Object download(String path);

    public boolean store(Object object);

    public boolean delete(String object);
}

FileDownloaderAbstractionImpl.java

public class FileDownloaderAbstractionImpl implements FileDownloaderAbstraction {

    private FileDownloadImplementor provider = null;

    public FileDownloaderAbstractionImpl(FileDownloadImplementor provider) {
        super();
        this.provider = provider;
    }

    @Override
    public Object download(String path) 
    {
        return provider.downloadFile(path);
    }

    @Override
    public boolean store(Object object) 
    {
        return provider.storeFile(object);
    }

    @Override
    public boolean delete(String object) {
        return false;
    }
}

上述更改并不强迫您对实现器类/接口进行任何更改。

实现中的更改不会影响抽象

假设您要在实现层为客户端不知道的所有下载程序添加删除功能(内部功能)。

FileDownloadImplementor.java

public interface FileDownloadImplementor
{
    public Object downloadFile(String path);

    public boolean storeFile(Object object);

    public boolean delete(String object);
}

LinuxFileDownloadImplementor.java

public class LinuxFileDownloadImplementor implements FileDownloadImplementor 
{
    @Override
    public Object downloadFile(String path) {
        return new Object();
    }

    @Override
    public boolean storeFile(Object object) {
        System.out.println("File downloaded successfully in LINUX !!");
        return true;
    }

    @Override
    public boolean delete(String object) {
        return false;
    }
}

WindowsFileDownloadImplementor.java

public class WindowsFileDownloadImplementor implements FileDownloadImplementor 
{
    @Override
    public Object downloadFile(String path) {
        return new Object();
    }

    @Override
    public boolean storeFile(Object object) {
        System.out.println("File downloaded successfully in LINUX !!");
        return true;
    }

    @Override
    public boolean delete(String object) {
        return false;
    }
}

上面的更改不会影响抽象层,因此客户端完全不会受到影响。

最后的笔记

  1. 桥接模式将抽象与其实现分离,以便两者可以独立变化。
  2. 它主要用于实现平台独立性功能。
  3. 它添加了另一种方法级别重定向以实现该目标。
  4. 将抽象接口发布在单独的继承层次结构中,并将实现放入其自己的继承层次结构中。
  5. 使用桥接模式来实现执行时的绑定。
  6. 使用桥模式来映射正交类层次结构
  7. 桥是预先设计的,以使抽象和实现独立变化。

祝您学习愉快!

组合设计模式

原文: https://howtodoinjava.com/design-patterns/structural/composite-design-pattern/

组合设计模式是修改对象结构的结构型模式。 此模式最适合需要使用对象的情况,这些对象形成树状结构。 在该树中,每个节点/对象(根节点除外)都是复合节点或叶节点。 实现组合模式可使客户统一对待单个对象和构图。

Table of Contents

When to use composite design pattern
Participants in composite design pattern
Sample problem to solve
Solution using composite design pattern
Final notes

何时使用组合设计模式

组合设计模式将对象组合成树状结构,以表示整个部分的层次结构。 复合可以使客户统一对待单个对象和对象组成。

  • 当应用具有分层结构并且需要跨结构的通用功能时。
  • 当应用需要跨层次结构聚合数据时。
  • 当应用要统一对待复合对象和单个对象时。

组合设计模式的真实示例用法可能是:

  1. 建立银行中客户帐户的综合视图(即客户的投资组合)
  2. 建立总账
  3. 计算机/网络监控应用
  4. 零售和库存申请
  5. 文件系统实现中的目录结构
  6. GUI 屏幕中的菜单项

组合设计模式的参与者

以下是任何基于组合模式的解决方案的参与者。

Composite design pattern

组合设计模式

参与此模式的类和对象:

  1. component

    • 声明组合中对象的接口。
    • 根据需要为所有类通用的接口实现默认行为。
    • 声明用于访问和管理其子组件的接口。
  2. Leaf

    • 表示组合中的叶对象。 叶子没有孩子。
    • 定义组合中原始对象的行为。
  3. composite

    • 定义具有子级的组件的行为。
    • 存储子组件。
    • Component接口中实现与子相关的操作。
  4. Client

    • 通过Component接口操作组合中的对象。

在上图中,客户端使用Component接口与复合层次结构中的对象进行交互。 在层次结构内部,如果对象是复合对象,则它将请求传递给叶节点。 如果对象是叶节点,则立即处理请求。

复合叶子还可以选择在叶子节点处理请求之前或之后修改请求/响应。

要解决的示例问题

假设我们正在构建财务应用。 我们的客户拥有多个银行帐户。 我们被要求准备一个设计,该设计可用于生成客户的合并帐户视图,该视图能够在合并所有帐户对帐单后显示客户的总帐户余额以及合并对帐单。 因此,应用应该能够生成:

1)客户来自所有帐户的总帐户余额
2)合并帐户对帐单

使用组合设计模式的解决方案

在这里,我们正在处理形成树状结构的帐户对象,在该结构中,我们将遍历帐户对象并仅对帐户对象执​​行一些操作,因此可以应用组合设计模式。

让我们来看看参与的类:

Component.java

import java.util.ArrayList;
import java.util.List;

public abstract class Component 
{
    AccountStatement accStatement;

    protected List<Component> list = new ArrayList<>();

    public abstract float getBalance();

    public abstract AccountStatement getStatement();

    public void add(Component g) {
        list.add(g);
    }

    public void remove(Component g) {
        list.remove(g);
    }

    public Component getChild(int i) {
        return (Component) list.get(i);
    }
}

CompositeAccount.java

public class CompositeAccount extends Component 
{
    private float totalBalance;
    private AccountStatement compositeStmt, individualStmt;

    public float getBalance() {
        totalBalance = 0;
        for (Component f : list) {
            totalBalance = totalBalance + f.getBalance();
        }
        return totalBalance;
    }

    public AccountStatement getStatement() {
        for (Component f : list) {
            individualStmt = f.getStatement();
            compositeStmt.merge(individualStmt);
        }
        return compositeStmt;
    }
}

AccountStatement.java

public class AccountStatement 
{
    public void merge(AccountStatement g) 
    {
        //Use this function to merge all account statements
    }
}

DepositAccount.java

public class DepositAccount extends Component 
{
    private String accountNo;
    private float accountBalance;

    private AccountStatement currentStmt;

    public DepositAccount(String accountNo, float accountBalance) {
        super();
        this.accountNo = accountNo;
        this.accountBalance = accountBalance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public float getBalance() {
        return accountBalance;
    }

    public AccountStatement getStatement() {
        return currentStmt;
    }
}

SavingsAccount.java

public class SavingsAccount extends Component 
{
    private String accountNo;
    private float accountBalance;

    private AccountStatement currentStmt;

    public SavingsAccount(String accountNo, float accountBalance) {
        super();
        this.accountNo = accountNo;
        this.accountBalance = accountBalance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public float getBalance() {
        return accountBalance;
    }

    public AccountStatement getStatement() {
        return currentStmt;
    }
}

Client.java

public class Client 
{
    public static void main(String[] args) 
    {
        // Creating a component tree
        Component component = new CompositeAccount();

        // Adding all accounts of a customer to component
        component.add(new DepositAccount("DA001", 100));
        component.add(new DepositAccount("DA002", 150));
        component.add(new SavingsAccount("SA001", 200));

        // getting composite balance for the customer
        float totalBalance = component.getBalance();
        System.out.println("Total Balance : " + totalBalance);

        AccountStatement mergedStatement = component.getStatement();
        //System.out.println("Merged Statement : " + mergedStatement);
    }
}

Output:

Total Balance : 450.0

最后的笔记

  1. 组合模式定义了由各个对象和复合对象组成的类层次结构。
  2. 客户端通过组件接口统一对待原始对象和组合对象,这使客户端代码变得简单。
  3. 添加新组件很容易,并且由于客户端通过组件接口处理新组件,因此不需要更改客户端代码。
  4. 可以使用迭代器设计模式遍历复合层次结构。
  5. 访问者设计模式可以对组合应用操作。
  6. 享元设计模式通常与组合结合以实现共享的叶节点。

祝您学习愉快!

Java 中的装饰器设计模式

原文: https://howtodoinjava.com/design-patterns/structural/decorator-design-pattern/

在软件工程中,装饰器设计模式用于向类的特定实例添加其他功能或行为,而不会修改同一类的其他实例。 装饰器提供了子类别的灵活替代方案,以扩展功能。 请注意,以上描述暗示装饰对象会更改其行为,但不会更改其接口。

理解这种模式非常重要,因为一旦您了解了装饰技术,就可以赋予您(或其他人)对象新的职责,而无需对基础类进行任何代码更改。 有趣,不是吗? 这种模式也非常有用,并且经常会遇到 Java 面试问题关于设计模式的问题。

在这篇文章中,我们将讨论以下几点:

  • 设计参与者
  • 问题陈述
  • 使用装饰器设计模式的建议解决方案
  • 常见面试问题
  • 一些实际用法

设计参与者

装饰器模式的典型示意图如下所示。

decorator design pattern participants

装饰设计模式参与者

以下是“装饰器设计”模式的参与者:

  • Component – 这是包装器,在运行时可以具有与其相关的其他职责。
  • ConcreteComponent – 是在程序中添加了其他职责的原始对象。
  • Decorator - 这是一个抽象类,其中包含对组件对象的引用,并且还实现了组件接口。
  • ConcreteDecorator - 它们扩展了装饰器,并在Component类的顶部构建了附加功能。

如果您仔细阅读这两行内容,那么您将了解它的工作方式如下:

您有一个实例,并将另一个实例放入其中。 它们都支持相同(或相似)的接口。 外面的一个是“装饰器”。 您在外面用一个。 它可以掩盖,更改或通过其内部实例的方法。

我总是喜欢以身作则。 因此,让我们解决一个问题。

问题陈述

让我们有一个常见的用例,我们必须将所有用户创建的报告显示给管理员。 现在,这些报告可以属于这些类别。

  • 客户报告
  • 支持报告

这两个报告都必须具有第一列作为到原始报告的链接,并且它们应具有不同的颜色。 可能应在不同弹出窗口大小中打开它们。 这些只是很少的事情,现实中可以实现很多。

一种可能的方法是扩展Report对象,然后具有两个单独的类ClientReportSupportReport。 在这两种方法中,定义私有方法,例如makeAnchorLink()designPopup()applyColor()等。 现在,在第一列数据单元格的某些获取器方法中调用这些方法。

SubClassing

如果您在此处停止并且不再修改系统,则它将起作用。 但是,假设几天后,您被告知为两种报告应用不同的背景色。 现在,您只有一种方法可以在两个类中定义方法colorBackground(),并进行适当的调用。 将来系统中有更多报告时,问题将变得更加严重。

上面的解决方案明显违反了 SMART 类别设计中提到的开闭原则

使用装饰器设计模式的建议解决方案

上面的问题是装饰模式的理想选择。 请记住,当我们有一个需要扩展但设计不当的对象时,请进行装饰。

通过下面的类图可以轻松解决以上问题。 在这里,我仅用于支持报告。 也可以为客户报告构建类似的类层次结构。

Decorator Pattern Solution

装饰器模式解决方案

如果我们已经实现了这样的解决方案,那么将来任何时候我们都可以添加其他修饰,而无需修改现有的类层次结构。 那是我们开始这篇文章的最终目标,对吧?

源代码列表

让我们看一看类的源代码,以了解其实际外观。

Report.java

public interface Report {
	public Object[][] getReportData(final String reportId);
	public String getFirstColumnData();
}

SupportReport.java

public class SupportReport implements Report {

	@Override
	public Object[][] getReportData(String reportId) {
		return null;
	}

	@Override
	public String getFirstColumnData() {
		return "Support data";
	}

}

ColumDecorator.java

public abstract class ColumDecorator implements Report 
{
	private Report decoratedReport;

	public ColumDecorator(Report report){
		this.decoratedReport = report;
	}

	public String getFirstColumnData() {
        return decoratedReport.getFirstColumnData(); 
    }

	@Override
	public Object[][] getReportData(String reportId) {
		return decoratedReport.getReportData(reportId);
	}
}

SupportLinkDecorator.java

public class SupportLinkDecorator extends ColumDecorator{

	public SupportLinkDecorator(Report report) {
		super(report);
	}

	public String getFirstColumnData() {
		return addMoreInfo (super.getFirstColumnData()) ;
	}

	private String addMoreInfo(String data){
		return data  + " - support link - ";
	}
}

SupportPopupDecorator.java

public class SupportPopupDecorator extends ColumDecorator{

	public SupportPopupDecorator(Report report) {
		super(report);
	}

	public String getFirstColumnData() {
		return addPopup (super.getFirstColumnData()) ;
	}

	private String addPopup(String data){
		return data  + " - support popup - ";
	}
}

DecoratorPatternTest.java

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

		//ClientPopupDecorator popupDecoratored = new ClientPopupDecorator(new ClientLinkDecorator(new ClientReport()));
		//System.out.println(popupDecoratored.getFirstColumnData());

		SupportPopupDecorator supportPopupDecoratored = new SupportPopupDecorator(new SupportLinkDecorator(new SupportReport()));
		System.out.println(supportPopupDecoratored.getFirstColumnData());
	}
}

Output:

Support data - support link -  - support popup - 

要下载完整的源代码和 UML 图,请遵循文章结尾处的下载链接。

关于装饰模式的面试问题

A)如何确定何时使用装饰器模式?

如果我们深入了解该概念,则会发现装饰器设计模式具有多个需求指标以表明这是潜在的解决方案,例如:

  • 我们有一个需要扩展的对象。 例如一个需要其他“可选”功能(例如滚动条,标题栏和状态栏)的窗口控件。
  • 几个通过“装饰”支持扩展的对象。 通常,这些对象共享一个公共接口,特征或超类,有时还具有其他中间超类。
  • 装饰的对象(类或原型实例化)和装饰器对象具有一个或几个共同的特征。 为了确保该功能,装饰器&的装饰器具有公共接口,特征或类继承。

B)装饰器模式和适配器模式之间的区别

不可以。适配器模式用于将对象的接口转换为其他内容。 装饰器模式用于扩展对象的功能,同时保持其接口。 由于它们都“包裹”了一个对象,因此两者有时有时也被称为“包装模式”。

C)装饰器模式与子类别之间的差异

装饰器模式和子类之间的区别在于,在子类中,您可以“使用单个类”修饰实现接口的任何类。 假设我想给自己一个java.util.Map,每当我添加或删除键时,它都会打印一条消息。 如果我只实际使用过java.util.HashMap,那么我可以创建PrintingMap吗? 作为HashMap的子类并覆盖&删除。 但是,如果要创建TreeMap的打印版本,则可以创建PrintingTreeMap? (哪些代码与PrintingMap具有几乎相同的代码?

装饰器模式的常用用法:

1)Java IO 库类,例如BufferedInputStream bs = new BufferedInputStream(new FileInputStream(new File("File1.txt")));

2)在显示标签 jsp 库中的装饰器列中,例如:

<display:table name="reportsViewResultTable" class="demoClass" id="reportsQueryViewResultTable">
	<display:column title="Report Id" sortable="true" property="reportDisplayId" decorator="com.comp.FirstColumnDataDecorator"></display:column>
</display:table>

3)在 sitemesh 中使用装饰器,以提供一致的 UI 体验。

源码下载

祝您学习愉快!

外观设计模式

原文: https://howtodoinjava.com/design-patterns/structural/facade-design-pattern/

外观设计模式为子系统中的一组接口提供了统一的接口。 外观定义了更高级别的接口,使子系统更易于使用。

1.何时使用外观模式

外观模式是结构型设计模式中的一种,另外还有四个设计模式。 当我们有一个复杂的系统要以简化的方式向客户公开时,外观模式是合适的。 其目的是将内部复杂性隐藏在从外部看起来很简单的单个接口后面。

外观还可以将使用系统的代码与子系统的细节分离开来,从而使以后修改系统变得更加容易。

2.现实世界中的外观示例

为了了解外观,让我们举一个非常简单的台式计算机示例。 当我们必须启动计算机时,我们要做的就是按下开始按钮。 我们真的不在乎计算机硬件和软件中包含什么。 这是外观模式的一个示例。

在 Java 编程中,我们必须已连接到数据库以获取一些数据。 我们只是调用方法dataSource.getConnection()来获取连接,但是在内部发生了很多事情,例如加载驱动程序,创建连接或从池中获取连接,更新统计信息,然后将连接引用返回给调用者方法。 这是编程世界中外观模式的另一个示例。

同样,我们可以找到更多的示例,这些示例隐藏了许多内部复杂性,并为程序员提供了使用简单的接口来与系统一起工作。 所有这些都是外观示例。

3.外观设计模式示例

让我们为演示目的编写自己的外观实现。 在此示例中,我们将创建一个报告生成器,该报告生成器具有多个步骤来创建任何报告。 例如,它将首先创建报告的页眉,页脚,添加数据行,格式化报告,然后以所需的格式(pdf,html 等)编写报告。

使用ReportGeneratorFacade,我们将隐藏所有这些步骤并公开易于使用的方法。

public class Report {

	private ReportHeader header;
	private ReportData data;
	private ReportFooter footer;

	public ReportHeader getHeader() {
		return header;
	}
	public void setHeader(ReportHeader header) {
		System.out.println("Setting report header");
		this.header = header;
	}
	public ReportData getData() {
		return data;
	}
	public void setData(ReportData data) {
		System.out.println("Setting report data");
		this.data = data;
	}
	public ReportFooter getFooter() {
		return footer;
	}
	public void setFooter(ReportFooter footer) {
		System.out.println("Setting report footer");
		this.footer = footer;
	}
}

public class ReportHeader {

}

public class ReportFooter {

}

public class ReportData {

}

public enum ReportType 
{
	PDF, HTML
}

public class ReportWriter {

	public void writeHtmlReport(Report report, String location) {
		System.out.println("HTML Report written");

		//implementation
	}

	public void writePdfReport(Report report, String location) {
		System.out.println("Pdf Report written");

		//implementation
	}
}

import javax.activation.DataSource;

public class ReportGeneratorFacade 
{
	public static void generateReport(ReportType type, DataSource dataSource, String location) 
	{
		if(type == null || dataSource == null) 
		{
			//throw some exception
		}
		//Create report
		Report report = new Report();

		report.setHeader(new ReportHeader());
		report.setFooter(new ReportFooter());

		//Get data from dataSource and set to ReportData object

		report.setData(new ReportData());

		//Write report
		ReportWriter writer = new ReportWriter();
		switch(type) 
		{
			case HTML:
				writer.writeHtmlReport(report, location);
				break;

			case PDF:
				writer.writePdfReport(report, location);
				break;
		}
	}
}

让我们测试一下外观实现。

import com.howtodoinjava.facade.ReportGeneratorFacade;
import com.howtodoinjava.facade.ReportType;

public class Main 
{
	public static void main(String[] args) throws Exception
	{
		ReportGeneratorFacade reportGeneratorFacade = new ReportGeneratorFacade();

		reportGeneratorFacade.generateReport(ReportType.HTML, null, null);

		reportGeneratorFacade.generateReport(ReportType.PDF, null, null);
	}
}

程序输出。

Setting report header
Setting report footer
Setting report data
HTML Report written

Setting report header
Setting report footer
Setting report data
Pdf Report written

4.常见问题

4.1 外观模式的优点

请记住,外观不会降低复杂性。 它只对外部系统和客户端隐藏它。 因此,外观模式的主要受益者仅是客户端应用和其他系统。

它为客户提供了一个简单的接口,即,我们没有为客户展示一个复杂的子系统,而是为客户提供了一个简化的接口。 它还可以帮助我们减少客户端需要处理的对象数量。

4.2 外观不限制对子系统的访问

外观不封装子系统类或接口。 它只是提供了一个简单的接口(或图层),使我们的生活更轻松。 我们可以自由公开子系统或整个子系统本身的任何功能。

它只会使代码看起来难看,否则它将起作用。

4.3 外观样式与适配器样式

在适配器模式中,我们尝试更改接口,以便客户端可以使用系统。 否则,该系统将很难被客户端使用(甚至无法使用)。

外观模式简化了接口。 它为客户端提供了一个与之交互的简单接口(而不是复杂的子系统)。

4.4 外观模式与中介者模式

在中介者模式实现中,子系统知道中介者。 他们互相交谈。

但是在外观中,子系统不了解外观,并且从外观到子系统之间提供了单向通信。

4.5 一个复杂子系统只有一个外观?

一点也不。 我们可以为特定的复杂子系统创建任意数量的外观。 这样做的目的是使系统更易于使用。 它需要创建 N 个外观,然后进行制作。

4.6 外观设计模式的挑战

  • 子系统与外观层连接。 因此,您需要注意额外的编码层。
  • 当子系统的内部结构发生变化时,您还需要将这些变化合并到外观层中。

学习愉快!

享元设计模式

原文: https://howtodoinjava.com/design-patterns/structural/flyweight-design-pattern/

根据 GoF 定义,享元设计模式可以使用对象共享来有效地支持大量细粒度对象。 权重是共享对象,可以同时在多个上下文中使用。 享元在每种情况下都充当独立对象。

1.何时使用享元设计模式

我们可以在以下情况下使用享元模式:

  • 当我们需要大量类似的对象时,这些对象只有几个参数是唯一的,而大多数东西通常都是通用的。
  • 我们需要通过创建更少的对象并将它们共享来控制大量对象的内存消耗。

2.外部和固有属性

享元物体本质上具有两种属性 - 内部属性和外部属性。

固有状态属性存储/共享在享元对象中,并且与享元的上下文无关。 作为最佳实践,我们应该使固有状态不可变

外部状态会随着重量级环境的变化而变化,这就是为什么它们无法共享的原因。 客户端对象保持外部状态,它们需要在对象创建过程中将其传递给享元对象。

Flyweight Pattern

享元模式

3.享元模式的真实示例

  • 假设我们有一个,可以加/不加笔芯。 笔芯可以是任何颜色,因此可以使用钢笔创建具有 N 种颜色的图形。

    这里Pen可以是具有refill作为外部属性的享元对象。 所有其他属性(例如笔身,指针等)可以是所有笔所共有的固有属性。 一支笔只能通过其笔芯颜色来区分,没有别的。

    所有需要访问红笔的应用模块 - 都可以使用红笔的同一实例(共享对象)。 仅当需要使用不同颜色的笔时,应用模块才会从享元工厂要求另一只笔。

  • 在编程中,我们可以将java.lang.String常量视为享元对象。 所有字符串都存储在字符串池中,如果我们需要带有某些内容的字符串,那么运行时将返回对该池中已存在的字符串常量的引用(如果有)。

  • 在浏览器中,我们可以在网页的多个位置使用图片。 浏览器将仅加载一次图像,而其他浏览器将重用缓存中的图像。 现在图像是相同的,但已在多个地方使用。 URL 是固有属性,因为它是固定且可共享的。 图像的位置坐标,高度和宽度是外部属性,它们根据必须渲染的位置(上下文)而变化。

4.享元设计模式示例

在给定的示例中,我们正在构建一个笔刷应用,客户可以在这三种类型上使用画笔 - THICKTHINMEDIUM。 所有粗(细或中等)画笔将以完全相似的方式绘制内容 - 仅内容颜色会有所不同。

public interface Pen 
{	
	public void setColor(String color);
	public void draw(String content); 
}

public enum BrushSize {
	THIN, MEDIUM, THICK
}

public class ThickPen implements Pen {

	final BrushSize brushSize = BrushSize.THICK;	//intrinsic state - shareable
	private String color = null; 					//extrinsic state - supplied by client

	public void setColor(String color) {
		this.color = color;
	}

	@Override
	public void draw(String content) {
		System.out.println("Drawing THICK content in color : " + color);
	}
}

public class ThinPen implements Pen {

	final BrushSize brushSize = BrushSize.THIN;
	private String color = null; 

	public void setColor(String color) {
		this.color = color;
	}

	@Override
	public void draw(String content) {
		System.out.println("Drawing THIN content in color : " + color);
	}
}

public class MediumPen implements Pen {

	final BrushSize brushSize = BrushSize.MEDIUM;
	private String color = null; 

	public void setColor(String color) {
		this.color = color;
	}

	@Override
	public void draw(String content) {
		System.out.println("Drawing MEDIUM content in color : " + color);
	}
}

这里笔刷color是外部属性,将由客户端提供,否则Pen的所有内容都将保持不变。 因此,从本质上讲,仅当颜色不同时,我们才会创建一定大小的笔。 一旦其他客户或环境需要该笔的大小和颜色,我们将重新使用它。

import java.util.HashMap;

public class PenFactory 
{
	private static final HashMap<String, Pen> pensMap = new HashMap<>();

	public static Pen getThickPen(String color) 
	{
		String key = color + "-THICK";

		Pen pen = pensMap.get(key);

		if(pen != null) {
			return pen;
		} else {
			pen = new ThickPen();
			pen.setColor(color);
			pensMap.put(key, pen);
		}

		return pen;
	}

	public static Pen getThinPen(String color) 
	{
		String key = color + "-THIN";

		Pen pen = pensMap.get(key);

		if(pen != null) {
			return pen;
		} else {
			pen = new ThinPen();
			pen.setColor(color);
			pensMap.put(key, pen);
		}

		return pen;
	}

	public static Pen getMediumPen(String color) 
	{
		String key = color + "-MEDIUM";

		Pen pen = pensMap.get(key);

		if(pen != null) {
			return pen;
		} else {
			pen = new MediumPen();
			pen.setColor(color);
			pensMap.put(key, pen);
		}

		return pen;
	}
}

让我们使用客户端来测试重量轻的笔对象。 客户端在这里创建了三支THIN笔,但在运行时它们只是一个瘦型的笔对象,并且与所有三个调用共享。

public class PaintBrushClient 
{
	public static void main(String[] args) 
	{
		Pen yellowThinPen1 = PenFactory.getThickPen("YELLOW");	//created new pen
		yellowThinPen1.draw("Hello World !!");

		Pen yellowThinPen2 = PenFactory.getThickPen("YELLOW");	//pen is shared
		yellowThinPen2.draw("Hello World !!");

		Pen blueThinPen = PenFactory.getThickPen("BLUE");		//created new pen
		blueThinPen.draw("Hello World !!");

		System.out.println(yellowThinPen1.hashCode());
		System.out.println(yellowThinPen2.hashCode());

		System.out.println(blueThinPen.hashCode());
	}
}

程序输出。

Drawing THICK content in color : YELLOW
Drawing THICK content in color : YELLOW
Drawing THICK content in color : BLUE

2018699554		//same object
2018699554		//same object
1311053135

5.常见问题

5.1 单例模式和享元模式之间的区别

单例模式有助于我们仅在系统中维护一个对象。 换句话说,一旦创建了所需的对象,我们就无法创建更多对象。 我们需要在应用的所有部分中重用现有对象。

当我们必须根据客户端提供的外部属性创建大量不同的相似对象时,将使用享元模式。

5.2 并发对享元的影响

类似于单例模式,如果我们在并发环境中创建享元对象,则最终可能会遇到同一个享元对象的多个实例,这是不希望的。

要解决此问题,我们需要在创建享元时使用单例模式中使用的双检锁

5.3 享元设计模式的好处

使用享元装置,我们可以:

  • 减少可以相同控制的重物的内存消耗。
  • 减少系统中“完整但相似的对象”的总数。
  • 提供了集中机制来控制许多“虚拟”对象的状态。

5.4 内部和外部数据可以共享吗?

内部数据是可共享的,因为它是所有上下文通用的。 不共享外部数据。 客户需要将信息(状态)传递给飞锤,这是其上下文所特有的。

5.5 享元模式的挑战

  • 我们需要花一些时间来配置这些享元。 最初,设计时间和技能可能是开销。
  • 为了创建重量级,我们从现有对象中提取一个通用的模板类。 附加的编程层可能很棘手,有时很难调试和维护。
  • 享元模式通常与单例工厂实现结合使用,并且为了保护奇点,需要额外的成本。

在评论中向我发送有关享元模式的问题。

学习愉快!

Gson 安装

原文: https://howtodoinjava.com/gson/gson-installation-maven-gradle-jar/

学习使用诸如 maven,gradle 或简单 jar 文件之类的构建工具在 Java 应用中包括 gson 依赖项

1. 在 Gradle / Android 上安装 Gson

通过添加以下依赖项,将 gson 包含在应用或 android 项目中:

dependencies {
    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
}

2. 用 Maven 安装 Gson

通过添加以下依赖项,在 Maven 中心中下载最新的 Gson 版本或所需版本:

<dependencies>
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.5</version>
    </dependency>
</dependencies>

3. 将 Gson 安装为 Jar 文件

可以从此位置下载 jar 文件。

学习愉快!

代理设计模式

原文: https://howtodoinjava.com/design-patterns/structural/proxy-design-pattern/

根据代理设计模式的 GoF 定义,代理对象为另一对象提供代理或占位符来控制对其的访问。 代理基本上可以替代我们由于许多原因而创建的预期对象,例如,与创建完全初始化的原始对象相关的安全性原因或成本。

1.何时使用代理设计模式

代理对象隐藏原始对象并控制对其的访问。 当我们可能想要使用可以充当其他接口的类时,可以使用代理。

代理大量用于实现与延迟加载相关的用例,在这种情况下,我们直到真正需要时才创建完整的对象。

代理也可以用于在原始对象周围添加额外的安全层。

2.代理模式的真实示例

  • 在 hibernate 中,我们编写了从数据库中获取实体的代码。 Hibernate 返回一个对象,该对象由代理(通过扩展域类由 Hibernate 动态构造)到基础实体类。 客户端代码能够读取数据,而无论需要使用代理读取什么内容。

    这些代理实体类有助于实现延迟加载方案,在这种方案中,只有在明确请求关联实体时,才提取它们。 它有助于提高 DAO 操作的性能。

  • 在公司网络中,互联网访问位于网络代理后面。 所有网络请求都通过代理,该代理首先检查对允许的网站和发布到网络的数据的请求。 如果请求看起来可疑,则代理将阻止该请求,否则请求将通过。

  • 在面向切面的编程(AOP)中,由 AOP 框架创建的一个对象,用于实现切面协定(建议方法执行等)。 例如,在 Spring AOP 中,AOP 代理将是 JDK 动态代理或 CGLIB 代理。

3.代理设计模式

3.1 架构

Proxy design pattern

代理设计模式

3.2 设计参与者

  • Subject – 是一个接口,它公开了可供客户端使用的功能。
  • RealSubject – 是实现Subject的类,并且是具体的实现,需要隐藏在代理后面。
  • Proxy – 通过扩展它来隐藏真实对象,并且客户端通过此代理对象与真实对象进行通信。 通常,当客户端请求真实对象时,框架会创建此代理对象。

4.代理设计模式示例

在给定的示例中,我们有一个RealObject,客户端需要访问它才能执行某项操作。 它将要求框架提供RealObject的实例。 但是由于需要保护对该对象的访问,因此框架将对RealObjectProxy的引用返回。

对代理对象的任何调用都用于其他要求,并且该调用将传递给实际对象。

public interface RealObject 
{
	public void doSomething();
}

public class RealObjectImpl implements RealObject {

	@Override
	public void doSomething() {
		System.out.println("Performing work in real object");
	}

}

public class RealObjectProxy extends RealObjectImpl 
{
	@Override
	public void doSomething() 
	{
		//Perform additional logic and security
		//Even we can block the operation execution
		System.out.println("Delegating work on real object");
		super.doSomething();
	}
}

public class Client 
{
	public static void main(String[] args) 
	{
		RealObject proxy = new RealObjectProxy();
		proxy.doSomething();
	}
}

程序输出。

Delegating work on real object
Performing work in real object

5.常见问题

5.1 有哪些不同类型的代理

代理通常分为四种类型:

  1. 远程代理 – 表示远程授乳的对象。 要与远程对象对话,客户端需要在网络通信方面做其他工作。 代理对象代表原始对象进行此通信,而客户端则专注于实际交谈。
  2. 虚拟代理 – 延迟昂贵对象的创建和初始化,直到需要时才按需创建对象。 Hibernate 创建的代理实体是虚拟代理的示例。
  3. 保护代理 – 帮助实现对原始对象的安全性。 他们可以在方法调用之前检查访问权限,然后根据结论允许或拒绝访问。
  4. 智能代理 – 当客户端访问对象时,执行其他内部管理工作。 一个示例可以是在访问真实对象之前检查它是否已锁定,以确保没有其他对象可以更改它。

5.2 代理模式与装饰器模式

两种模式之间的主要区别是它们承担的责任。 装饰器专注于添加职责,但是代理专注于控制对对象的访问。

在评论中向我发送有关代理模式的问题。

学习愉快!

参考文献:

图片来源 – 维基百科

设计原则

Java 中的 SOLID 原则(含示例)

原文: https://howtodoinjava.com/best-practices/5-class-design-principles-solid-in-java/

类是任何 Java 应用的构建块。 如果这些块不牢固,则建筑物(即应用)将来将面临艰难时期。 这实质上意味着,当应用范围扩大或应用在生产或维护中面临某些设计问题时,编写得不够好会导致非常困难的情况。

另一方面,精心设计和编写的类集可以极大地加快编码过程,同时减少比较中的错误数量。

在本教程中,我们将通过示例讨论 Java 中的 SOLID 原则,这是 5 种最推荐的设计原则,在编写类时应牢记。 它们还构成设计应用类时要遵循的最佳实践

Table Of Contents

1\. Single Responsibility Principle
2\. Open Closed Principle
3\. Liskov's Substitution Principle
4\. Interface Segregation Principle
5\. Dependency Inversion Principle

5 java class design principles

5 个 Java 类设计原则

让我们一一向下钻取所有对象。

1.单一责任原则

原则的名称说明了一切:

“一个类应该只有一个责任”

换句话说,我们应该仅出于一个目的编写,更改和维护类。 如果是模型类,则它应严格只代表一个参与者/实体。 这将使我们能够灵活地在将来进行更改,而不必担心更改对另一个实体的影响。

同样,如果我们正在编写服务/管理器类,则它应该只包含方法调用的那一部分,而不包含其他任何内容。 甚至没有与模块相关的工具全局函数。 最好将它们分开在另一个可全局访问的类文件中。 这将有助于维护用于该特定目的的类,并且我们可以决定该类仅对特定模块的可见性。

1.1 单一责任原则示例

在所有流行的 Java 库中,我们都可以找到很多遵循单一责任原则的类。 例如,在 log4j 中,我们具有使用日志记录方法的不同类,不同的类是日志记录级别,依此类推。

在我们的应用级代码中,我们定义了模型类来表示实时实体,例如人,雇员,帐户等。这些类中的大多数是 SRP 原则的示例,因为当我们需要更改人的状态时,才需要修改 一个人的类。 等等。

在给定的示例中,我们有两个类PersonAccount。 两者都负有存储其特定信息的单一责任。 如果要更改Person的状态,则无需修改类Account,反之亦然。

public class Person 
{
	private Long personId;
	private String firstName;
	private String lastName;
	private String age;
	private List<Account> accounts;
}

public class Account 
{
	private Long guid;
	private String accountNumber;
	private String accountName;
	private String status;
	private String type;
}

2.开闭原则

这是设计应用时应牢记的第二个重要规则。 开闭原则指出:

“软件组件应为扩展开放,但应为修改封闭”

这是什么意思?? 这意味着我们的类应该这样设计:每当开发人员想要改变应用中特定条件下的控制流时,他们都需要扩展我们的类并覆盖某些函数,仅此而已。

如果其他开发人员由于类的约束而无法设计所需的行为,则应重新考虑更改类。 在这里我并不是说任何人都可以改变我们类的整体逻辑,但是他/她应该能够以软件允许的无害方式覆盖软件提供的选项。

2.1 开闭原则示例

如果我们研究诸如 struts 或 spring 之类的任何良好框架,我们将看到我们无法更改其核心逻辑和请求处理,但仅通过扩展某些类并将其插入配置文件中即可修改所需的应用流。

例如,spring 框架具有类DispatcherServlet。 此类充当基于 String 的 Web 应用的前端控制器。 要使用此类,我们不需要修改此类。 我们所需要做的就是传递初始化参数,然后我们可以按需要扩展其功能。

请注意,除了在应用启动期间传递初始化参数之外,我们还可以覆盖方法,以通过扩展类来修改目标类的行为。 例如,struts Action类被扩展以覆盖请求处理逻辑。

public class HelloWorldAction extends Action 
{
	@Override
	public ActionForward execute(ActionMapping mapping, 
								ActionForm form, 
								HttpServletRequest request, 
								HttpServletResponse response) 
								throws Exception 
	{

	    //Process the request

    }
}

3. 里氏替代原则

该原则是先前讨论的打开 - 闭合原则的变体。 它说:

“派生类型必须完全可以替代其基本类型”

这意味着通过扩展我们的类而创建的同级开发人员应该能够适合应用而不会失败。 这要求子类的对象的行为与父类的对象相同。 这通常在我们进行运行时类型识别然后将其转换为适当的引用类型的地方看到。

3.1 里氏替代原则示例

LSP 的一个示例可以是 Spring 框架中的自定义属性编辑器。 Spring 提供了属性编辑器以与对象本身不同的方式表示属性,例如,从 HTTP 请求参数解析人类可读的输入或在视图层中显示纯 Java 对象的人类可读的值,例如 CurrencyURL

Spring 可以为一种数据类型注册一个属性编辑器,并且需要遵循基类 PropertyEditorSupport强制的约束。 所以是任何扩展PropertyEditorSupport类的类,那么它可以被需要基类的任何地方替换。

例如,每本书都有一个 ISBN 号,它始终是固定的显示格式。 您可以在数据库和 UI 中使用独立的 ISBN 表示形式。 为此,我们可以用以下方式编写属性编辑器:

import java.beans.PropertyEditorSupport;
import org.springframework.util.StringUtils;
import com.howtodoinjava.app.model.Isbn;

public class IsbnEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (StringUtils.hasText(text)) {
            setValue(new Isbn(text.trim()));
        } else {
            setValue(null);
        }
    }

    @Override
    public String getAsText() {
        Isbn isbn = (Isbn) getValue();
        if (isbn != null) {
            return isbn.getIsbn();
        } else {
            return "";
        }
    }
}

4.接口隔离原则

这是我最喜欢的原则。 它适用于接口,因为单一责任原则适用于类。 ISP 说:

“不应强迫客户实现不必要的方法”

举个例子。 开发人员 Alex 创建了一个接口Reportable并添加了两种方法generateExcel()generatedPdf()。 现在,客户“ A”希望使用此接口,但他打算仅使用 PDF 格式的报告,而不使用 excel。 他将能够轻松使用该功能吗?

没有。 他将必须实现这两种方法,其中一种是软件设计人员要给他增加的负担。 他或者实现另一种方法,或者将其留空。 这不是一个好的设计。

那么解决方案是什么? 解决方案是通过破坏现有接口来创建两个接口。 它们应该像PdfReportableExcelReportable。 这将为用户提供灵活性,使其仅使用必需的功能。

4.1 接口隔离原则示例

查找 IPS 示例的最佳地方是 Java AWT 事件处理器,用于处理从键盘和鼠标触发的 GUI 事件。 对于每种事件,它都有不同的监听器类。 我们只需要编写希望处理的事件处理器即可。 没有什么是强制性的。

一些监听器是:

  • FocusListener
  • KeyListener
  • MouseMotionListener
  • MouseWheelListener
  • TextListener
  • WindowFocusListener

任何时候,我们希望处理任何事件,只需找出相应的监听器并实现它即可。

public class MouseMotionListenerImpl implements MouseMotionListener 
{
	@Override
	public void mouseDragged(MouseEvent e) {
		//handler code
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		//handler code
	}
}

5.依赖倒置原则

我们大多数人已经熟悉原则名称中使用的词语。 DI 原则说:

“依赖抽象而不是具体”

换一种说法。 我们应该以这样一种方式设计我们的软件,即可以使用抽象层将各个模块彼此绑定在一起,从而将各个模块彼此分离。

5.1 依赖倒置原则示例

Spring 框架中 Bean 配置原则的经典用法。

在 spring 框架中,所有模块都作为单独的组件提供,可以通过简单地将依赖注入到其他模块中来一起工作。 此依赖关系在 XML 文件中从外部进行管理。

这些独立的组件在边界上封闭得非常好,以至于我们可以轻松地在 Spring 之外的其他软件模块中使用它们。 这已经通过依赖倒置和开闭原则来实现。 所有模块仅公开抽象,这对于扩展功能或另一个模块中的插件很有用。

这些是五类设计原则,也称为 SOLID 原则,这是设计应用类别时应遵循的最佳实践。

学习愉快!

开闭原则

原文: https://howtodoinjava.com/design-patterns/open-closed-principle/

开闭原则(OCP)指出,模块应该可以扩展,但可以关闭以进行修改。 它是著名的 5 条实体原则和非常重要的面向对象设计原则之一。

1.开闭原则的定义

有两种流行的定义来描述这一原则:

1.1 Mayer 的定义

伯特兰·梅耶(Bertrand Mayer)在其 1988 年的著作面向对象的软件构造(Prentice Hall)中,定义了开闭原则(OCP)如下:

软件实体应为扩展而开放,但应为修改而封闭。

1.2 Martin 的定义

Robert C. Martin 在他的书《敏捷软件开发:原则,模式和实践》(Prentice Hall,2003 年)中定义了 OCP,如下所示:

开放扩展 – 这意味着可以扩展模块的行为。 随着应用需求的变化,我们能够通过满足这些变化的新行为来扩展模块。 换句话说,我们能够更改模块的功能。

封闭修改 – 扩展模块的行为不会导致模块的源代码或二进制代码更改。 模块的二进制可执行版本,无论是在可链接库,DLL 还是 Java .jar 中,都保持不变。

2.讨论

根据上面引用的定义,对于要开放扩展的代码,开发人员必须能够响应不断变化的需求并支持新功能。 尽管模块无法修改,但仍必须做到这一点。 开发人员必须支持新功能,而无需编辑源代码或现有模块的已编译程序集。

2.1 例外情况

请注意,在少数情况下,绝对有必要修改代码,并且无法避免。

  • 这样的例子之一就是模块中存在的缺陷。 在修复缺陷的情况下,允许更改模块代码及其相应的测试用例。

    我们可以使用 TDD 方法来解决代码中的这些问题。 修正错误后,您必须确保没有其他测试会因副作用而失败。

  • 另一个允许的例外情况是,允许对现有代码进行任何更改,只要它也不需要更改该代码的任何客户端即可。 这允许使用新的语言功能升级模块版本。 例如,Spring 5 支持并使用 Java8 lambda 语法,但要使用它,我们不需要更改我们的客户端应用代码。

    一个模块,其中的类是松散耦合的,在不强迫其他类更改的情况下更改了类,这就是鼓励松散耦合的原因。 如果您允许对现有代码进行修改而不会强制对客户端进行进一步更改,则保持松散的耦合将限制 OCP 的影响。

2.2 如何设计开闭原则

为了实现模块的扩展,我们可以采用两种(通常使用的)机制中的任何一种。

2.2.1 实现继承

实现继承使用抽象类和方法。 您可以将扩展点定义为抽象方法。

该抽象类可以由几个具有大多数常见场景的预定义实现的类扩展。 对于特定于客户的场景,开发人员必须扩展抽象类并提供特定的实现逻辑。 这将有助于保留 OCP。

模板方法模式非常适合这些用例。 在这种模式下,由于可以委托抽象方法,因此可以自定义一般步骤。 实际上,基类将流程的各个步骤委托给子类。

2.2.2 接口继承

在接口继承中,客户端对类的依赖关系被替换为接口。 与抽象方法相比,这实际上是首选方法。 这呼应了这样的建议,即首选组合而不是继承,并保持继承层次结构较浅,并且子分类层很少。

设计继承或禁止继承。 – Effective Java(Addison-Wesley,2008 年),约书亚·布洛赫(Joshua Bloch)

3.开闭原则示例

如果要查看开闭原则的真实示例,只需看一下 Spring 框架即可。 Spring 的设计和实现非常精美,因此您可以扩展其功能的任何部分,并立即将自定义实现注入。 它经过了很好的时间测试,并且像今天一样完美无缺。

3.1 没有 OCP 的计算器程序

假设我们正在创建一个简单的计算器模块,其中仅包含两个加减运算。 该模块的代码如下。

public interface IOperation {
}

public class Addition implements IOperation 
{
	private double firstOperand;
	private double secondOperand;
	private double result = 0.0;

	public Addition(double firstOperand, double secondOperand) {
		this.firstOperand = firstOperand;
		this.secondOperand = secondOperand;
	}

	//Setters and getters
}

public class Substraction implements IOperation 
{
	private double firstOperand;
	private double secondOperand;
	private double result = 0.0;

	public Substraction(double firstOperand, double secondOperand) {
		this.firstOperand = firstOperand;
		this.secondOperand = secondOperand;
	}

	//Setters and getters
}

public interface ICalculator {
	void calculate(IOperation operation);
}

public class SimpleCalculator implements ICalculator 
{
	@Override
	public void calculate(IOperation operation) 
	{
		if(operation == null) {
			throw new InvalidParameterException("Some message");
		}

		if(operation instanceof Addition) {
			Addition obj = (Addition) operation;
			obj.setResult(obj.getFirstOperand() + obj.getSecondOperand());
		} else if(operation instanceof Substraction) {
			Addition obj = (Addition) operation;
			obj.setResult(obj.getFirstOperand() - obj.getSecondOperand());
		} 
	}
}

上面的模块代码看起来不错并达到目的。 但是,在客户端应用中时,开发人员想要增加乘法功能 – 除了更改方法calculate()中的SimpleCalculator类代码外,他别无选择。 此代码不符合 OCP。

3.2 符合 OCP 的代码

请记住,抽象功能是应用中的变化。 在此计算器程序中,calculate方法中的代码将随每个传入的新操作支持请求而变化。 因此,我们需要在此方法中添加抽象。

解决方案是委派在操作本身内部提供计算逻辑的责任。 每个操作必须具有自己的逻辑才能获取结果和操作数。 现在查看修改后的代码。

public interface IOperation {
	void performOperation();
}

public class Addition implements IOperation 
{
	private double firstOperand;
	private double secondOperand;
	private double result = 0.0;

	public Addition(double firstOperand, double secondOperand) {
		this.firstOperand = firstOperand;
		this.secondOperand = secondOperand;
	}

	//Setters and getters

	@Override
	public void performOperation() {
		result = firstOperand + secondOperand;
	}
}

public class Substraction implements IOperation 
{
	private double firstOperand;
	private double secondOperand;
	private double result = 0.0;

	public Substraction(double firstOperand, double secondOperand) {
		this.firstOperand = firstOperand;
		this.secondOperand = secondOperand;
	}

	//Setters and getters

	@Override
	public void performOperation() {
		result = firstOperand - secondOperand;
	}
}

public interface ICalculator {
	void calculate(IOperation operation);
}

public class SimpleCalculator implements ICalculator 
{
	@Override
	public void calculate(IOperation operation) 
	{
		if(operation == null) {
			throw new InvalidParameterException("Some message");
		}

		operation.performOperation();
	}
}

现在,我们可以根据需要添加任意数量的操作,而无需更改原始模块代码。 任何新的操作都将轻松实现。 例如,乘法运算将这样写,并且可以正常工作。

public class Multiplication implements IOperation 
{
	private double firstOperand;
	private double secondOperand;
	private double result = 0.0;

	public Multiplication(double firstOperand, double secondOperand) {
		this.firstOperand = firstOperand;
		this.secondOperand = secondOperand;
	}

	//Setters and getters

	@Override
	public void performOperation() {
		result = firstOperand * secondOperand;
	}
}

4. 结论

开闭原则是类和接口的总体设计以及开发人员如何构建允许随时间变化的代码的指南。

现在,当大多数组织都在采用敏捷实践时,每次经过 sprint 时,新的需求都是不可避免的,应该被接受。 如果您生成的代码不是为了进行更改而构建的,则更改将很困难,耗时,容易出错且成本很高。

通过确保代码可以扩展但不能修改,可以有效地禁止将来对现有类和程序集进行更改,这迫使程序员创建可以插入扩展点的新类。

建议的方法是确定需求中可能更改的部分或难以实现的部分,并将这些部分排除在扩展点之后。

学习愉快!

阅读更多:

维基百科

单一责任原则

原文: https://howtodoinjava.com/design-patterns/single-responsibility-principle/

单一责任原则(SRP)指出,软件组件(通常为类)必须仅具有一个职责。 类负有全部责任这一事实意味着,类只负责一件具体的事情,因此,我们可以得出结论,类只有一个改变的理由。 它是 5 个著名的 SOLID 原则之一。

一个类只有一个改变的理由。

还有另一种看待这一原则的方法。 如果在查看一个类时,我们发现相互排斥且彼此不相关的方法,则它们是必须分解为较小类的不同职责。 请记住,较小的类总是更好。

1.动机

如果出于各种原因必须对类进行修改,则意味着抽象不正确,并且该类承担了太多责任。 基本上,我们希望在所有情况下都避免具有多个职责的对象(通常称为神对象,因为它们知道的太多或超出了他们应有的知识)。 这些对象将不同的行为(大多数是不相关的)组合在一起,从而使它们难以维护。

2.单一责任原则示例

要了解单责任原则的真实示例,我们可以看一下 JDK 代码和其他流行的库,例如 Log4J,Spring 框架等。Log4J 代码具有使用日志记录方法的不同类,不同的类是日志记录级别等等。 在 Spring 框架中,类实际上很小,通常只执行一两个相关操作。

让我们再举一个自己的例子,以更好地理解什么是单一责任原则。

2.1 问题

为了理解 SRP 原则,我们假设我们正在开发涉及与员工一起工作的应用。 我们有一个接口IEmployeeStore,它的实现EmployeeStore具有以下方法。

public interface IEmployeeStore 
{
	public Employee getEmployeeById(Long id);

	public void addEmployee(Employee employee);

	public void sendEmail(Employee employee, String content);
}

public class EmployeeStore implements IEmployeeStore 
{
	@Override
	public Employee getEmployeeById(Long id) {
		return null;
	}

	@Override
	public void addEmployee(Employee employee) {

	}

	@Override
	public void sendEmail(Employee employee, String content) {		
	}
}

在任何普通应用上,上述类似乎都不错。 使用EmployeeStore,可以获取/添加员工并向他们发送电子邮件。

现在假设产品发布后,我们要求电子邮件内容可以是两种类型,即 HTML 和文本。 该类支持仅文本内容。 你会做什么?

解决此问题的一种方法是创建另一种方法sendHtmlEmail(),但是当我们被要求支持为两种内容类型发送电子邮件的不同协议时会发生什么。 整体类看起来非常丑陋,难以阅读和维护。

而且总是有机会在修改期间,某些开发人员可以更改用于共享员工方法的获取/添加员工方法的逻辑。

2.2 SRP 原则的解决方案

要解决此问题,我们必须退出电子邮件功能,以分离专门处理电子邮件相关功能的接口和类。 这样,我们可以确定其他功能不会受到影响。

基于我们的假设,我们可以抽象出两个接口IEmailSenderIEmailContent。 第一个接口负责电子邮件的发送过程,第二个接口负责传递电子邮件的内容及其类型。

重构的类在下面给出。

public interface IEmployeeStore 
{	
	public Employee getEmployeeById(Long id);

	public void addEmployee(Employee employee);
}

public class EmployeeStore implements IEmployeeStore 
{
	//inject in runtime
	private IEmailSender emailSender;

	@Override
	public Employee getEmployeeById(Long id) {
		return null;
	}

	@Override
	public void addEmployee(Employee employee) {
	}
}

public interface IEmailSender 
{
	public void sendEmail(Employee employee, IEmailContent content);
}

public class EmailSender implements IEmailSender
{
	@Override
	public void sendEmail(Employee employee, IEmailContent content) {		
		//logic
	}
}

public interface IEmailContent {

}

public class EmailContent implements IEmailContent 
{
	private String type;
	private String content;
}

现在,如果要更改电子邮件功能,我们将仅更改EmailSender类。 员工 CRUD 操作的任何更改将仅在EmployeeStore中发生。 一种能力的任何改变都不会错误地改变另一种能力。 它们也不太容易阅读和维护。

3.好处

3.1 易于理解和维护

当类仅做“一件事情”时,其接口通常具有较少数量的方法(和成员变量),这些方法很容易解释。 它使代码更易于阅读和理解。

当需要更改应用行为时,与类职责相关的更改是相当隔离的。 它减少了破坏软件其他无关区域的机会。 它使代码更易于维护。

3.2 改善的可用性

如果一个类有多个职责,并且为了某个职责而需要在应用的其他部分中使用它,则可能不利地暴露其他职责,而这是不希望的。 它可能导致应用中不良行为,例如,安全和数据隐私问题。

如果该类严格遵循 SRP 原则,则不会暴露出不必要的功能,这将使该类更加可用,而不必担心会产生不利影响。

4. 结论

单责任原则设计模式对代码的适应性具有巨大的积极影响。 与不遵循该原则的等效代码相比,符合 SRP 的代码导致产生了更多的类,这些类更小且指向范围更广。

SRP 的实现主要是通过抽象接口后的代码并将不相关功能的职责委托给运行时恰好在接口后的实现来实现的。

阅读更多:

关注点分离

Java 最佳实践

Java 最佳实践指南

原文: https://howtodoinjava.com/java-best-practices/

一个好的程序员就是写程序(不仅仅是代码)的人。 这意味着编写足够独立的代码单元,以便以多种方式重复使用,并且仍然保持健壮性。

因此,让我们开始识别和学习一些 Java 最佳实践,这些最佳实践可以将任何代码转换为程序。

1. Java 设计最佳实践

遵循 Java 最佳实践主要集中在系统设计时间方面。 它们主要对高级开发人员有用。 最终,如果不是今天,您也将成为高级。 为什么不更好地准备呢?

8 个不良单元测试用例的迹象

糟糕的单元测试是现实,每个执行代码审查的人都偶尔(可能是定期)面对它。 那么,什么构成不良的测试案例呢? 如何识别不良测试案例?

Java 中的 5 个类设计原则(S.O.L.I.D.)

在编写类时,请牢记 5 条最推荐的设计原则。 简而言之,这些设计原则称为 SOLID。

单元测试最佳实践

编写糟糕的单元测试非常容易,这会给项目增加很少的价值,同时又会天文数字地增加代码更改的成本。 了解如何正确编写它们。

异常处理的新方法

一种新方法针对每个新的异常情况使用静态内部类。 值得将来的软件设计师阅读。

使用内部类处理 Java 异常

涵盖了一些众所周知的和鲜为人知的实践,您在处理下一个 Java 编程任务中的异常时必须考虑这些实践。

Java 执行器框架最佳实践

设计下一个多线程应用时需要牢记的一些最佳实践。

您应考虑迁移旧版系统的 5 个原因

从旧的旧系统迁移到新的高级系统有多个原因。 这是其中的 5 个。

编写 Spring 配置文件的 13 个最佳实践

编写高度可维护的 spring XML 配置的 13 个最佳实践。

2. Java 编码最佳实践

遵循 Java 最佳实践的重点是在您敲键盘并开始键入实际程序时要考虑的事项。 它们主要对所有级别的开发人员/程序员有用。

在生产中生成安全密码哈希(MD5 / SHA)

用户提供的密码通常很弱并且很容易猜到。 Java 中有许多哈希算法,这些哈希算法可以证明对您的应用和用户的密码安全非常有效。

Serializable接口指南

我们都知道Serializable接口的作用。 但是我们真的知道,您的类中有哪些更改会破坏您的设计?

对象初始化最佳实践

创建新对象始终是一个昂贵的过程。 让我们学习一下 Java 如何利用对象池解决此问题。

如何使 Java 类不可变

不变类是一种一旦创建便无法更改其状态的类。 有一些创建不可变类的准则。 让我们学习它们。

考虑在运行时重新加载 log4j 级别

了解如何使用WatchService在运行时重新加载日志记录配置。 也可以使用它们来重新加载其他配置。

使用ToStringBuilder有效覆盖toString()

ToStringBuilder是 apache commons lang 库提供的工具类。 学习使用它编写高度自定义的toString()方法。

在任何循环内使用数组代替Vector.elementAt()

从多个线程访问时,Vector不需要额外的同步,但是由于相同的原因,它会降低其性能。 评估其影响。

在 Java 中始终使用length()而不是equals()来检查空字符串

检查字符串是否为空的最好方法是使用length()方法而不是equals()。 为什么? 让我们做一些推理。

3. Java 性能最佳实践

遵循 Java 最佳实践可以提高 Java 应用的性能并将其提升到一个新的水平。 努力学习它们并时刻牢记。

不同方式迭代HashMap的性能比较

让我们看看迭代HashMap的不同方法,看看它们可以带来的性能差异。

Java 中不同for循环的性能比较

让我们比较一下不同for循环的效果。 它们在任何程序中无处不在。

改善 JDBC 性能的最佳实践

数据库访问是任何应用的主要部分。 如果您使用的是简单的 JDBC,那么这个适合您。

更多……

学习愉快!

编写好的单元测试的 FIRST 原则

原文: https://howtodoinjava.com/best-practices/first-principles-for-good-tests/

在解决现实问题的任何应用中,单元测试中都存在问题 – 是最不希望的事情。 好的书面测试是资产,而不好的书面测试是您应用的负担。 在本教程中,我们将学习单元测试 FIRST 原则,它可以使您的测试脱颖而出,并确保其收益超过成本。

良好单元测试的首要原则

首字母缩写词 FIRST 表示以下测试功能:

  • [F]快速
  • [I]隔离
  • [R]可重复
  • [S]自我验证
  • [T]及时

如果您在编写单元测试时遵循这五个原则,那么您将拥有更健壮的单元测试,从而使应用代码更稳定。

让我们详细了解这些 FIRST 原则。

快速

单元测试应该快速,否则它们会减慢您的开发/部署时间,并且需要更长的时间才能通过或失败。 通常,在足够大的系统上,将有数千个单元测试 - 假设有 2000 个单元测试。 如果平均单元测试需要 200 毫秒才能运行(应该被认为是快速的),那么运行完整套件将需要 6.5 分钟。

在此阶段,6.5 分钟的时间似乎并不长,但请想象一下,如果您一天在开发计算机上多次运行它们,将会消耗大量的生产时间。 想象一下,当向应用添加新功能时,这些测试的数量增加时,它将进一步增加测试执行时间。

您的单元测试套件的价值会降低,因为它们提供有关系统运行状况的连续,全面和快速反馈的能力也会降低。

缓慢测试的主要原因之一是依赖关系,必须处理外部邪恶的必需品,例如数据库,文件和网络调用。 他们花费数千毫秒。 因此,要使套件快速运行,必须避免通过使用模拟测试创建这些依赖项。

隔离

永远不要编写依赖于其他测试用例的测试。 无论您如何精心设计它们,总会有误报的可能性。 更糟的是,您可能最终会花费更多的时间来确定链中的哪个测试导致了失败。

在最佳情况下,您应该可以在任何时间以任何顺序运行任何人的测试。

通过进行独立的测试,可以轻松地使测试仅关注少量行为。 如果该测试失败,您将确切知道出了什么问题以及出了什么地方。 无需调试代码本身。

SOLID 类设计原则的单一责任原则(SRP)指出,类应该小而单一。 这也可以应用于您的测试。 如果您的一种测试方法可能由于多种原因而失败,请考虑将其拆分为单独的测试。

可重复

可重复测试每次运行都会产生相同的结果。 要完成可重复的测试,必须将它们与外部环境中的任何东西隔离开,而不是直接控制。 在这些情况下,请随意使用模拟对象。 它们就是为此目的而设计的。

有时,您需要直接与外部环境影响进行交互,例如数据库。 您需要设置一个私有沙箱,以避免与测试同时更改数据库的其他开发人员发生冲突。 在这种情况下,您可以使用内存数据库。

如果测试不可重复,那么您肯定会得到一些虚假的测试结果,并且您不能浪费时间去追逐幻象问题。

自我验证

测试必须是自我验证的手段 – 每个测试都必须能够确定预期的输出与否。 它必须确定它失败或通过。 必须没有人工解释结果

手动验证测试结果是一个耗时的过程,也可能带来更多风险。

确保您不会做任何愚蠢的事情,例如设计测试以要求在运行前需要手动安排步骤。 您必须自动执行测试所需的任何设置 – 甚至不依赖于数据库和预煮数据的存在。

创建一个内存数据库,创建架构并放入虚拟数据,然后测试代码。 这样,您可以运行该测试 N 次,而不必担心会影响测试执行及其结果的任何外部因素。

及时

实际上,您可以随时编写单元测试。 您可以等待代码准备好投入生产,或者最好集中精力及时编写单元测试。

作为建议,您应该对单元测试有指导原则或严格的规则。 您可以使用审查过程甚至自动化工具来拒绝代码,而无需进行充分的测试。

您进行的单元测试越多,发现在进行相应的单元测试之前编写较小的代码块所付出的代价就越大。 首先,编写测试更加容易,其次,当您充实周围代码中的其余行为时,测试将立即获得回报。

额外提示

如果使用 Eclipse 或 IntelliJ IDEA,请考虑合并 Infinitest 之类的工具。 在对系统进行更改时,Infinitest 会识别并运行(在后台)任何可能受到影响的测试。

在更大范围内,您可以使用持续集成(CI)工具,例如 Jenkins 或 TeamCity。 CI 工具会监视您的源仓库,并在识别到更改后启动构建/测试过程。

在评论部分中,将您与 FIRST 原则相关的查询发送给我。

学习愉快!

参考:本文引用并使用了一些示例,例如 Andy Hunt 的“使用 JUnit 进行 Java8 中的实用单元测试”中给出的示例; 杰夫·兰格,戴夫·托马斯

您应该如何对 DAO 层进行单元测试

原文: https://howtodoinjava.com/best-practices/how-you-should-unit-test-dao-layer/

如果您正在基于 SpringHibernateJPA 构建的项目中 ,并且您想对其数据访问层(DAO)进行单元测试,那么本教程中提供的信息可能会对您有所帮助。 当我们要测试 DAO 层时,我们也需要访问数据库。 但是由于某些原因,可能会损坏测试数据(主要是为集成测试准备的),或者由于某些其他团队成员也需要访问该数据,所以可能由于某些原因而不允许您使用任何现有数据库。 为了解决此问题,我正在使用内存数据库 。 IM(内存中)数据库是个不错的选择,因为它不会留下任何回溯,并且您可以确保在每次测试之前都会得到空表(通常是一个好习惯)。

Unit-test-dao-layer

一个好的单元测试应该保持数据库状态与测试用例执行之前的状态相同。 它应该删除所有添加的数据; 并回滚所有更新。

Table of Contents

1) Always create unit test specific configuration file
2) Writing unit tests for DAO layer
3) Package Structure

1)始终创建单元测试特定的配置文件

这可能是为 DAO 层创建单元测试的第一步。 理想情况下,测试应使用与应用相同的配置。 但是可能会有一些更改,这些更改仅针对单元测试。 要解决此问题,您应该创建另一个测试特定的配置文件,并添加/覆盖测试特定的配置更改

例如。 在主应用中,如果配置文件为application-context.xml,则应创建另一个文件application-context-test.xml,然后将原始配置导入到该文件的顶部。 然后覆盖您可能需要的 Bean 定义(例如,使用内存数据库而不是常规数据库)。

application-context-test.xml


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/tx/ http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

   <import resource="application-context.xml"/>

   <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
      <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
      <property name="url" value="jdbc:hsqldb:mem:howtodoinjava" />
      <property name="username" value="sa" />
      <property name="password" value="" />
   </bean>

</beans>

application-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/tx/ http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

    <context:component-scan base-package="com.howtodoinjava.jpa.demo" />

    <bean id="entityManagerFactoryBean" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="dataSource" ref="dataSource" />

      <property name="packagesToScan" value="com.howtodoinjava.jpa.demo.entity" />

      <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
      </property>
      <property name="jpaProperties">
         <props>
            <prop key="hibernate.archive.autodetection">class,hbm</prop>
            <prop key="hibernate.hbm2ddl.auto">create</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
         </props>
      </property>
   </bean>

  <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver" />
      <property name="url" value="jdbc:mysql://localhost:3306/test" />
      <property name="username" value="root" />
      <property name="password" value="password" />
   </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="entityManagerFactory" ref="entityManagerFactoryBean" />
   </bean>

   <tx:annotation-driven />

</beans>

在上面的示例中,我使用内存中数据源实现覆盖了常规数据源。

2)编写 DAO 层的单元测试

下一部分将编写 junit(或任何其他框架)测试用例。 我正在使用 Spring 测试模块。 您可以按照以下方式编写测试用例。

package com.jpa.demo.test;

import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import com.howtodoinjava.jpa.demo.dao.DepartmentDAO;
import com.howtodoinjava.jpa.demo.dao.EmployeeDAO;
import com.howtodoinjava.jpa.demo.entity.DepartmentEntity;
import com.howtodoinjava.jpa.demo.entity.EmployeeEntity;

@ContextConfiguration(locations = "classpath:application-context-test.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestEmployeeDAO 
{

	@Autowired
	private EmployeeDAO employeeDAO;

	@Autowired
	private DepartmentDAO departmentDAO;

	@Test
	@Transactional
	@Rollback(true)
	public void testAddDepartment()
	{
		DepartmentEntity department = new DepartmentEntity("Information Technology");
		departmentDAO.addDepartment(department);

		List<DepartmentEntity> departments = departmentDAO.getAllDepartments();
		Assert.assertEquals(department.getName(), departments.get(0).getName());
	}

	@Test
	@Transactional
	@Rollback(true)
	public void testAddEmployee()
	{
		DepartmentEntity department = new DepartmentEntity("Human Resource");
		departmentDAO.addDepartment(department);

		EmployeeEntity employee = new EmployeeEntity();
		employee.setFirstName("Lokesh");
		employee.setLastName("Gupta");
		employee.setEmail("howtodoinjava@gmail.com");
		employee.setDepartment(department);

		employeeDAO.addEmployee(employee);

		List<DepartmentEntity> departments = departmentDAO.getAllDepartments();
		List<EmployeeEntity> employees = employeeDAO.getAllEmployees();

		Assert.assertEquals(1, departments.size());
		Assert.assertEquals(1, employees.size());

		Assert.assertEquals(department.getName(), departments.get(0).getName());
		Assert.assertEquals(employee.getEmail(), employees.get(0).getEmail());
	}
}

现在注意这里的几件事。

  1. 在启动测试套件之前,请加载测试配置文件。

    @ContextConfiguration(locations = "classpath:application-context-test.xml");
    
  2. 加载完主要配置后,您将可以轻松地将 DAO 引用直接注入到测试用例中。

    @Autowired
    private EmployeeDAO employeeDAO;
    
    @Autowired
    private DepartmentDAO departmentDAO;
    
    
  3. 使用@Rollback(true)注解可恢复原始数据库状态。

    @Test
    @Transactional
    @Rollback(true)
    public void testAddDepartment()
    {
    	//other code
    }
    
    
  4. 始终在测试用例中创建一些数据,并在同一测试用例中验证该数据。 切勿在另一个测试用例上使用一个测试用例。 您可能需要阅读以下文章中的一些单元测试最佳实践和准则。

    阅读更多:单元测试最佳实践

3)包结构

最后,看一下此示例中使用的项目结构。 注意,application-context-test.xml位于test/resources文件夹中。

Package Structure

包结构

现在查看我为本教程编写的其他文件。

log4j.properties

log4j.logger.org.hibernate=INFO, hb
log4j.logger.org.hibernate.type=TRACE
log4j.appender.hb=org.apache.log4j.ConsoleAppender
log4j.appender.hb.layout=org.apache.log4j.PatternLayout

DepartmentEntity.java

package com.howtodoinjava.jpa.demo.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity(name="DepartmentEntity")
@Table (name="department")
public class DepartmentEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private Integer id;
    private String name;

    public DepartmentEntity(){
    }

    public DepartmentEntity(String name) {
        super();
        this.name = name;
    }

    @OneToMany(mappedBy="department",cascade=CascadeType.PERSIST)
    private List<EmployeeEntity> employees = new ArrayList<EmployeeEntity>();

    //Setters and Getters

	@Override
	public String toString() {
		return "DepartmentVO [id=" + id + ", name=" + name + "]";
	}
}

EmployeeEntity.java

package com.howtodoinjava.jpa.demo.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity(name="EmployeeEntity")
@Table (name="employee")
public class EmployeeEntity implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private Integer id;

    private String firstName;
    private String lastName;
    private String email;

    @ManyToOne
    private DepartmentEntity department;

    public EmployeeEntity() {}

    public EmployeeEntity(String name, DepartmentEntity department) {
        this.firstName = name;
        this.department = department;
    }

    public EmployeeEntity(String name) {
        this.firstName = name;
    }

    //Setters and Getters

    @Override
    public String toString() {
        return "EmployeeVO [id=" + id + ", firstName=" + firstName
                + ", lastName=" + lastName + ", email=" + email
                + ", department=" + department + "]";
    }
}

DepartmentDAO.java

package com.howtodoinjava.jpa.demo.dao;

import java.util.List;
import com.howtodoinjava.jpa.demo.entity.DepartmentEntity;

public interface DepartmentDAO 
{
	public List<DepartmentEntity> getAllDepartments();
	public DepartmentEntity getDepartmentById(Integer id);
	public boolean addDepartment(DepartmentEntity dept);
	public boolean removeDepartment(DepartmentEntity dept);
	public boolean removeAllDepartments();
}

DepartmentDAOImpl.java

package com.howtodoinjava.jpa.demo.dao;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.howtodoinjava.jpa.demo.entity.DepartmentEntity;

@Repository
@Transactional
public class DepartmentDAOImpl implements DepartmentDAO {

	@PersistenceContext
    private EntityManager manager;

	@Override
	public List<DepartmentEntity> getAllDepartments() {
		List<DepartmentEntity> depts = manager.createQuery("Select a From DepartmentEntity a", DepartmentEntity.class).getResultList();
        return depts;
	}

	@Override
	public DepartmentEntity getDepartmentById(Integer id) {
		return manager.find(DepartmentEntity.class, id);
	}

	@Override
	public boolean addDepartment(DepartmentEntity dept) {
		try{
			manager.persist(dept);
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public boolean removeDepartment(DepartmentEntity dept) {
		try{
			manager.remove(dept);
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public boolean removeAllDepartments() {
		try{
			Query query = manager.createNativeQuery("DELETE FROM DEPARTMENT");
			query.executeUpdate();
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}
}

EmployeeDAO.java

package com.howtodoinjava.jpa.demo.dao;

import java.util.List;
import com.howtodoinjava.jpa.demo.entity.EmployeeEntity;

public interface EmployeeDAO 
{
	public List<EmployeeEntity> getAllEmployees();
	public List<EmployeeEntity> getAllEmployeesByDeptId(Integer id);
	public EmployeeEntity getEmployeeById(Integer id);
	public boolean addEmployee(EmployeeEntity employee);
	public boolean removeEmployee(EmployeeEntity employee);
	public boolean removeAllEmployees();
}

EmployeeDAOImpl.java

package com.howtodoinjava.jpa.demo.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.howtodoinjava.jpa.demo.entity.EmployeeEntity;

@Repository
@Transactional
public class EmployeeDAOImpl implements EmployeeDAO {

	@PersistenceContext
    private EntityManager manager;

	@Override
	public List<EmployeeEntity> getAllEmployees() {
		List<EmployeeEntity> employees = manager.createQuery("Select a From EmployeeEntity a", EmployeeEntity.class).getResultList();
        return employees;
	}

	@Override
	public List<EmployeeEntity> getAllEmployeesByDeptId(Integer id) {
		List<EmployeeEntity> employees = manager.createQuery("Select a From EmployeeEntity a", EmployeeEntity.class).getResultList();
        return employees;
	}

	@Override
	public EmployeeEntity getEmployeeById(Integer id) {
		return manager.find(EmployeeEntity.class, id);
	}

	@Override
	public boolean addEmployee(EmployeeEntity employee) {
		try{
			manager.persist(employee);
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public boolean removeEmployee(EmployeeEntity employee) {
		try{
			manager.remove(employee);
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public boolean removeAllEmployees() {
		try{
			Query query = manager.createNativeQuery("DELETE FROM EMPLOYEE");
			query.executeUpdate();
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}
}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.howtodoinjava.jpa.demo</groupId>
	<artifactId>JPAExamples</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

		<!-- Spring Support -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>4.1.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.1.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>4.1.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.1.4.RELEASE</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>4.2.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.common</groupId>
			<artifactId>hibernate-commons-annotations</artifactId>
			<version>4.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.javax.persistence</groupId>
			<artifactId>hibernate-jpa-2.0-api</artifactId>
			<version>1.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>4.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.0.0.GA</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.5</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.5.6</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.logging</groupId>
			<artifactId>jboss-logging</artifactId>
			<version>3.1.0.CR2</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.10</version>
		</dependency>

		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.2.8</version>
		</dependency>
		<dependency>
			<groupId>antlr</groupId>
			<artifactId>antlr</artifactId>
			<version>2.7.6</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.1</version>
		</dependency>
		<dependency>
			<groupId>dom4j</groupId>
			<artifactId>dom4j</artifactId>
			<version>1.6.1</version>
		</dependency>
		<dependency>
			<groupId>javassist</groupId>
			<artifactId>javassist</artifactId>
			<version>3.4.GA</version>
		</dependency>
		<dependency>
			<groupId>javax.transaction</groupId>
			<artifactId>jta</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.5.6</version>
		</dependency>

	</dependencies>
</project>

JPA 示例

随时给我您的疑问和建议。

祝您学习愉快!

JUnit 最佳实践指南

原文: https://howtodoinjava.com/best-practices/unit-testing-best-practices-junit-reference-guide/

我假设您了解 JUnit 的基础知识。 如果您没有基础知识,请首先阅读(已针对 JUnit 5 更新)。 现在,我们将介绍在编写测试用例时必须考虑的 junit 最佳实践

编写糟糕的单元测试非常容易,这会给项目增加很少的价值,同时又会天文数字地增加代码更改的成本。

Table of Contents

Unit testing is not about finding bugs
Tips for writing great unit tests
     Test only one code unit at a time
     Don’t make unnecessary assertions
     Make each test independent to all the others
     Mock out all external services and state
     Don’t unit-test configuration settings
     Name your unit tests clearly and consistently
     Write tests for methods that have the fewest dependencies first, and work your way up
     All methods, regardless of visibility, should have appropriate unit tests
     Aim for each unit test method to perform exactly one assertion
     Create unit tests that target exceptions
     Use the most appropriate assertion methods.
     Put assertion parameters in the proper order
     Ensure that test code is separated from production code
     Do not print anything out in unit tests
     Do not use static members in a test class
     Do not write your own catch blocks that exist only to fail a test
     Do not rely on indirect testing
     Integrate Testcases with build script
     Do not skip unit tests
     Capture results using the XML formatter
Summary

在编程中,“单元测试是一种测试源代码的各个单元以确定它们是否适合使用的方法。” 现在,这个源代码单元可以在不同的情况下使用。

例如:在过程编程中,一个单元可以是一个完整的模块,但更常见的是一个单独的函数或过程。 在面向对象编程中,一个单元通常是一个完整的接口,例如一个类,但可以是一个单独的方法。 直观上,应该将一个单元视为应用中最小的可测试部分。

单元测试不是寻找回归错误

好吧,了解单元测试的动机很重要。 单元测试不是查找应用范围的错误或检测回归缺陷的有效方法。 根据定义,单元测试将分别检查代码的每个单元。 但是,当您的应用实际运行时,所有这些单元都必须协同工作,并且整个过程比独立测试部分的总和更加复杂和微妙。 证明 X 和 Y 组件都可以独立工作,并不表示它们相互兼容或配置正确。

因此,如果您要查找回归错误,则实际上一起运行整个应用会更有效,因为它将在生产环境中运行,就像手动测试 。 如果您将这种测试自动化以检测将来发生的破损,则称为集成测试,通常使用与单元测试不同的技术。

“从本质上讲,单元测试应该被视为设计过程的一部分,因为它是 TDD(测试驱动开发)中的一部分。” 这应该用于支持设计过程,以便设计人员可以识别系统中的每个最小模块并分别进行测试。

编写出色的单元测试的提示

1.一次仅测试一个代码单元

首先,也许是最重要的。 当我们尝试测试代码单元时,该单元可能有多个用例。 我们应该始终在单独的测试用例中测试每个用例。 例如,如果我们为一个函数编写测试用例,该函数应该具有两个参数,并且在进行一些处理后应返回一个值,那么不同的用例可能是:

  1. 第一个参数可以为空。 它应该抛出无效的参数异常。
  2. 第二个参数可以为空。 它应该抛出无效的参数异常。
  3. 两者都可以为空。 它应该抛出无效的参数异常。
  4. 最后,测试功能的有效输出。 它应该返回有效的预定输出。

当您进行了一些代码更改或重构,然后测试功能没有损坏时,这将很有帮助,运行测试用例就足够了。 同样,如果您更改任何行为,则需要更改单个或最少数量的测试用例。

2.不要做出不必要的断言

请记住,单元测试是某种行为应该如何工作的设计规范,而不是对代码碰巧所做的所有事情的观察列表。

不要试图断言所有事情都只专注于要测试的内容,否则,由于一个原因,您最终将导致多个测试用例失败,这无助于实现任何目标。

3.使每个测试独立于其他所有测试

不要制作单元测试用例链。 这将阻止您确定测试用例失败的根本原因,并且您将不得不调试代码。 同样,它会创建依赖关系,这意味着如果您必须更改一个测试用例,那么您就不必要在多个测试用例中进行更改。

尝试对所有测试用例使用@Before@After方法。 如果您需要多种支持@Before@After中的不同测试用例的方法,请考虑创建新的Test类。

4.模拟所有外部服务和状态

否则,这些外部服务中的行为会与多个测试重叠,并且状态数据意味着不同的单元测试会影响彼此的结果。 如果您必须按照特定的顺序运行测试,或者只有在数据库或网络连接处于活动状态时才能运行,则您肯定走错了路。

另外,这很重要,因为您不希望调试由于某些外部系统中的错误而实际失败的测试用例。

(顺便说一句,有时您的架构可能意味着您的代码在单元测试期间会触及静态变量。如果可以,请避免这样做,但如果不能这样做,请至少确保每个测试在运行之前将相关的静态操作重置为已知状态。 )

5.不进行单元测试配置设置

根据定义,您的配置设置不是任何代码单元的一部分(这就是为什么您将设置提取到某些属性文件中的原因)。 即使您可以编写用于检查配置的单元测试,也可以只编写一个或两个测试用例,以验证配置加载代码是否正常工作,仅此而已。

在每个单独的测试用例中测试所有配置设置仅证明了一件事:“ 您知道如何复制和粘贴。”

6.清晰一致地命名您的单元测试

好吧,这可能是最重要的一点,要记住并保持关注。 您必须根据测试案例的实际用途和测试来命名它们。 使用类名和方法名作为测试用例名称的测试用例命名约定从来都不是一个好主意。 每次更改方法名称或类名称时,您最终也会更新很多测试用例。

但是,如果您的测试用例名称是逻辑的,即基于操作,则几乎不需要修改,因为大多数应用逻辑将保持不变。

例如。 测试用例名称应类似于:

1) TestCreateEmployee_NullId_ShouldThrowException
2) TestCreateEmployee_NegativeId_ShouldThrowException
3) TestCreateEmployee_DuplicateId_ShouldThrowException
4) TestCreateEmployee_ValidId_ShouldPass

7.首先对依赖关系最少的方法编写测试,然后逐步进行

该原则表明,如果要测试Employee模块,则应该首先测试Employee模块的创建,因为它对外部测试用例的依赖项最小。 一旦完成,就开始编写Employee修改的测试用例,因为它们需要一些雇员在数据库中。

要在数据库中拥有一些员工,您的创建员工测试用例必须先通过,然后才能继续。 这样,如果员工创建逻辑中存在一些错误,则可以更早地发现它。

8.所有方法,无论是否可见,都应进行适当的单元测试

好吧,这确实是有争议的。 您需要查找代码中最关键的部分,并且应该对其进行测试,而不必担心它们是否是私有的。 这些方法可以具有从一到两个类调用的某些关键算法,但是它们起着重要的作用。 您想确保它们按预期工作。

9.针对每种单元测试方法,精确执行一个断言

即使这不是经验法则,您也应该尝试在一个测试用例中仅测试一件事。 不要在单个测试用例中使用断言来测试多个事物。 这样,如果某个测试用例失败,则可以确切地知道出了什么问题。

10.创建针对异常的单元测试

如果某些测试用例希望从应用中抛出异常,请使用“Expected”属性。 尝试避免在catch块中捕获异常,并使用failasset方法结束测试。

@Test(expected=SomeDomainSpecificException.SubException.class) 

11.使用最合适的断言方法

每个测试用例都可以使用许多断言方法。 运用最适当的理由和思想。 他们在那里是有目的的。 尊敬他们。

12.以正确的顺序放置断言参数

断言方法通常采用两个参数。 一个是期望值,第二个是原始值。 根据需要依次传递它们。 如果出现问题,这将有助于正确的消息解析。

13.确保测试代码与生产代码分开

在您的构建脚本中,确保测试代码未与实际源代码一起部署。 这浪费了资源。

14.不要在单元测试中打印任何内容

如果您正确地遵循了所有准则,那么您将不需要在测试用例中添加任何打印语句。 如果您想拥有一个,请重新访问您的测试用例,您做错了什么。

15.不要在测试类中使用静态成员。 如果您已经使用过,则针对每个测试用例重新初始化

我们已经说过,每个测试用例都应该彼此独立,因此永远不需要静态数据成员。 但是,如果您在紧急情况下需要任何帮助,请记住在执行每个测试用例之前将其重新初始化为初始值。

16.不要写自己的只能使测试失败的catch

如果测试代码中的任何方法引发某些异常,则不要编写catch块只是为了捕获异常并使测试用例失败。 而是在测试用例声明本身中使用throws Exception语句。 我将建议使用Exception类,并且不要使用Exception的特定子类。 这也将增加测试范围。

17.不要依赖间接测试

不要假定特定的测试用例也会测试另一种情况。 这增加了歧义。 而是为每种情况编写另一个测试用例。

18.将测试用例与构建脚本集成

最好将测试用例与构建脚本集成在一起,以使其在生产环境中自动执行。 这提高了应用以及测试设置的可靠性。

19.不要跳过单元测试

如果某些测试用例现在无效,则将其从源代码中删除。 不要使用@Ignoresvn.test.skip来跳过它们的执行。 在源代码中包含无效的测试用例不会帮助任何人。

20.使用 XML 格式器捕获结果

这是感觉良好的因素。 它绝对不会带来直接的好处,但是可以使运行单元测试变得有趣和有趣。 您可以将 JUnit 与 ant 构建脚本集成,并生成测试用例,并使用一些颜色编码以 XML 格式运行报告。 遵循也是一种很好的做法。

总结

毫无疑问,单元测试可以显着提高项目质量。 我们这个行业中的许多学者声称,任何单元测试总比没有好,但是我不同意:测试套件可以成为一项重要资产,但是不良套件可以成为负担不起的同样巨大的负担。 这取决于这些测试的质量,这似乎取决于其开发人员对单元测试的目标和原理的理解程度。

如果您理解上述准则,并尝试在下一组测试用例中实现其中的大多数准则,那么您一定会感到与众不同。

请让我知道您的想法。

学习愉快!

GSON – 序列化和反序列化 JSON

原文: https://howtodoinjava.com/gson/gson-serialize-deserialize-json/

了解如何使用 Google GSON 库将 Java 对象序列化为其 JSON 表示,并将 JSON 字符串反序列化为等效的 Java 对象。 GSON 提供了简单的toJson()fromJson()方法,将 Java 对象转换为 JSON,反之亦然。

使用GsonBuilder创建具有自定义配置的Gson对象,例如精美打印

//Gson gson = new Gson();
Gson gson = new GsonBuilder().setPrettyPrinting().create();

Employee emp = new Employee(1001, "Lokesh", "Gupta", "howtodoinjava@gmail.com");

String jsonString = gson.toJson(emp);

Employee empObject = gson.fromJson(jsonString, Employee.class);

1.依赖

Maven 依赖项。 访问 maven 仓库以获取最新版本。

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.5</version>
</dependency>

Gradle 依赖项。

<dependency>
dependencies {
  implementation 'com.google.code.gson:gson:2.8.5'
}

2.序列化 – 使用 Gson 编写 JSON

在 Gson 上下文中的序列化意味着将 Java 对象转换为其 JSON 表示形式。 为了进行序列化,我们需要一个 Gson 对象,该对象可以处理转换。 接下来,我们需要调用函数toJson(),并传递Employee对象。

Employee emp = new Employee(1001, "Lokesh", "Gupta", "howtodoinjava@gmail.com");

Gson gson = new Gson();

String jsonString = gson.toJson(emp);

System.out.println(jsonString);

程序输出。

{
   "id":1001,
   "firstName":"Lokesh",
   "lastName":"Gupta",
   "email":"howtodoinjava@gmail.com"
}

2.反序列化 – 使用 Gson 读取 JSON

在 Gson 上下文中,反序列化意味着将 JSON 字符串转换为等效的 Java 对象。 为了进行反序列化,我们需要一个 Gson 对象,并从Json()调用函数,并在解析完成后传递两个参数,即 JSON 字符串和所需的 Java 类型。

String jsonString = "{'id':1001, 'firstName':'Lokesh', 'lastName':'Gupta', 'email':'howtodoinjava@gmail.com'}";

Gson gson = new Gson();

Employee empObject = gson.fromJson(jsonString, Employee.class);

System.out.println(empObject);

程序输出:

Employee [id=1001, firstName=Lokesh, lastName=Gupta, email=howtodoinjava@gmail.com]

向我提供有关Gson对象及其toJson()fromJson()方法的问题。

学习愉快!

参考文献:

GSON Github

不良单元测试用例的 8 个迹象

原文: https://howtodoinjava.com/best-practices/8-signs-of-bad-unit-test-cases/

如果您从事软件开发已有很长时间,那么您可以轻松地与单元测试的重要性联系起来。 专家说,如果我们遵循这些最佳实践来编写单元测试,那么大多数错误都可以在单元测试阶段本身捕获,并最终传递给质量团队。

“编写糟糕的单元测试极其容易,这给项目增加了很少的价值,同时又天文数字地增加了代码更改的成本。”

虽然,糟糕的单元测试是现实,并且每个进行代码审查的人都偶尔(可能是定期)面对它。 那么,什么构成不良的测试案例呢? 如何识别不良测试案例?

在这篇文章中,我试图确定 8 个这样的迹象,这些迹象将为您提供微妙的提示,即一个特定的测试用例不好,需要进行更新。

Table of Contents

Test passes but not testing the actual feature
Testing irrelevant things
Testing multiple things in assertions
Test accessing the testee using reflection
Tests swallowing exceptions
Test which depends on excessive setup
Test compatible to only developer's machine
Test filling log files with load of texts

bad unit test cases

8 个不良单元测试用例的迹象

让我们一一看一下这些指针的含义:

1)测试通过但未测试实际功能

相信我,我在以前的项目中看到过这样的测试用例,它们似乎在代码中做了很多工作,但实际上它们什么也没做。 他们正在向服务器发送请求,无论什么服务器响应,它们都在传递。 恐怖!

通过严格的代码审查,当心将此类测试用例放在您的代码仓库中。 一旦它们进入您的代码库,它们将成为您承担责任,每次携带,构建和运行它们,但不增加任何价值。

2)测试无关的东西

这是测试用例不好的另一个标志。 我已经看到开发人员检查了多个不相关的事情,以便代码可以执行某些操作,但不一定是正确的事情。 最好的方法是遵循单一责任原则,即一个单元测试用例只能测试一件事,仅此而已。

3)测试断言中的多个事物

这个似乎与上面的迹象相似,但事实并非如此。 在以前的符号中,测试正在测试不相关的事物,而在此符号中,此处是对一个正确的事物进行单元测试,但是在一个测试用例中测试了许多这样的事物。 这再次违反了单一责任原则。

好吧,请注意,我不鼓励在每个测试用例中使用单个断言。 要测试单个实体,您可能需要使用多个断言,并根据需要执行。

例如,一个 API 在帖子主体中采用一些参数并创建Employee对象并作为响应返回。 此Employee对象可以具有多个字段,例如名字,姓氏,地址等。编写一个测试用例以仅验证名字,然后编写一个用于验证姓氏和另一个用于地址的测试用例是代码的重复性,而没有任何值。 不要做

而是编写一个用于创建员工的肯定测试用例,并验证该单元测试中的所有字段。 在这种情况下,负测试用例应单独编写,仅做一件事和一个断言。 例如,一个测试用例用于空白的名字,一个测试用例用于无效的名字,依此类推。 可以使用单个断言来验证所有此类否定测试用例,这些断言可以期望响应中出现异常

4)使用反射测试被测者的访问

这真是太糟糕了。 尝试将测试对象更改为需要的用户。 在测试对象上进行代码重构时会发生什么。 测试用例会爆炸。 请勿使用或允许使用。

5)测试隐藏了异常

我得到了这些测试用例的应有份额。 在测试用例结束时,他们以小小的catch块静默地吞下了异常。 更糟糕的是,它们也不会发出失败的警报。 异常是您的应用抛出的信号,以表明发生了某些不良情况,您必须对其进行调查。 您不应允许测试用例忽略这些信号。

每当您看到意外的异常时,请立即使测试用例失败。

6)测试取决于过多的设置

我不喜欢测试用例,它需要在开始测试实际事物之前发生很多事情。 这样的场景可能像:促进在线会议的应用。 现在测试特定类型的用户是否可以加入会议,以下是测试执行的步骤:

  • 创建用户
  • 设置用户权限
  • 创建会议
  • 设置会议属性
  • 发布会议参加信息
  • [测试]用户参加会议
  • 过关失败

现在,在上述情况下,在验证实际逻辑之前必须设置 5 个复杂的步骤。 对我来说,这不是一个很好的测试案例。

正确的测试系统至少要为此活动在系统中存在一个现有用户。 在测试用例中,它将至少减少两个步骤。 如果可以的话,他可以召开一些已经创建的会议,也可以使这个测试用例真正成为重点。

另一种使其正确的方法是使用模拟对象。 他们在那里是为了这个目的。 是吗?

7)测试仅兼容开发者的机器

这不是很广为人知,但当新生写的时候有时可见。 他们使用依赖于系统的文件路径,环境变量或属性来代替使用通用属性/路径或类似的东西。 提防他们。

8)使用文本加载测试日志文件的填充

他们似乎并没有在快乐的日子里制造问题。 但是,在下雨天的时候,它们通过在日志文件中放置没有任何信息的不必要的文本来使人为难,并为试图查找那些日志文件中隐藏的内容的调试器带来了麻烦。

测试不是为了调试应用,因此不要放置调试日志种类语句。 一个通过/失败的日志语句就足够了。 相信我。

这些是我根据最近几年的学习而得出的想法。 我不希望您在上述所有方面都同意我的观点。 但是可能还有其他一些很酷的观点。 但是我肯定会想讨论您对主题的看法。

祝您学习愉快!

20 个 Java 异常处理最佳实践

原文: https://howtodoinjava.com/best-practices/java-exception-handling-best-practices/

这篇博文是此博客中最佳实践系列的又一篇文章。 在这篇文章中,我将介绍一些众所周知的和鲜为人知的实践,您在处理下一个 Java 编程任务中的异常时必须考虑这些实践。 单击此链接以阅读有关 Java 中异常处理的更多信息。

Table of Contents

Type of exceptions
User defined custom exceptions

Best practices you must consider and follow

Never swallow the exception in catch block
Declare the specific checked exceptions that your method can throw
Do not catch the Exception class rather catch specific sub classes
Never catch Throwable class
Always correctly wrap the exceptions in custom exceptions so that stack trace is not lost
Either log the exception or throw it but never do the both
Never throw any exception from finally block
Always catch only those exceptions that you can actually handle
Don't use printStackTrace() statement or similar methods
Use finally blocks instead of catch blocks if you are not going to handle exception
Remember "Throw early catch late" principle
Always clean up after handling the exception
Throw only relevant exception from a method
Never use exceptions for flow control in your program
Validate user input to catch adverse conditions very early in request processing
Always include all information about an exception in single log message
Pass all relevant information to exceptions to make them informative as much as possible
Always terminate the thread which it is interrupted
Use template methods for repeated try-catch
Document all exceptions in your application in javadoc

在深入探讨异常处理最佳实践的深入概念之前,让我们从最重要的概念之一入手,这是要理解 Java 中存在三种可抛出类的常规类型:受检的异常,非受检的异常和错误。

1. Java 中的异常类型

Exception Hierarchy in java

Java 中的异常层次结构

受检异常

这些是必须在方法的throws子句中声明的异常。 它们扩展了Exception,旨在成为“面对您”的异常类型。 Java 希望您处理它们,因为它们某种程度上取决于程序之外的外部因素。 受检的异常指示正常系统运行期间可能发生的预期问题。 通常,当您尝试通过网络或文件系统使用外部系统时,会发生这些异常。 通常,对检查到的异常的正确响应应该是稍后重试,或者提示用户修改其输入。

非受检异常

这些是不需要在throws子句中声明的异常。 JVM 不会强迫您处理它们,因为它们大多数是由于程序错误在运行时生成的。 它们扩展了RuntimeException。 最常见的示例是NullPointerException(很吓人。不是吗?)。 非受检的异常可能不应该重试,正确的操作通常应该是什么也不做,然后让它从方法中出来并通过执行栈。 在高级别执行时,应记录此类异常。

错误

是严重的运行时环境问题,几乎可以肯定无法解决。 例如OutOfMemoryErrorLinkageErrorStackOverflowError。 它们通常会使您的程序或程序的一部分崩溃。 只有良好的日志记录做法才能帮助您确定错误的确切原因。

2.用户定义的自定义异常

每当用户出于某种原因而感觉到要使用其自己的应用特定异常时,他都可以创建一个扩展了适当超类(主要是Exception)的新类,并在适当的地方开始使用它。 这些用户定义的异常可以两种方式使用:

  1. 当应用出现问题时,直接抛出自定义异常

    throw new DaoObjectNotFoundException("Couldn't find dao with id " + id);
    
  2. 或将原始异常包装在自定义异常中,然后将其抛出

    catch (NoSuchMethodException e) {
      throw new DaoObjectNotFoundException("Couldn't find dao with id " + id, e);
    }
    

包装异常可以通过添加您自己的消息/上下文信息来向用户提供额外的信息,同时仍保留原始异常的栈跟踪和消息。 它还允许您隐藏代码的实现细节,这是包装异常的最重要原因。

现在让我们开始探索在异常处理方面明智的最佳实践。

3.您必须考虑并遵循的 Java 异常处理最佳实践

3.1 永远不要在catch块中吞下异常

catch (NoSuchMethodException e) {
   return null;
}

这样做不仅返回“null”,而不是处理或重新引发异常,它完全吞没了异常,永远失去了错误原因。 而当您不知道失败的原因时,将来如何预防呢? 永远不要这样做!

3.2 声明您的方法可以抛出的特定受检异常

public void foo() throws Exception { //Incorrect way
}

始终避免像上面的代码示例中那样进行操作。 它根本无法达到受检异常的全部目的。 声明您的方法可以抛出的特定受检异常。 如果此类受检异常太多,则可能应将它们包装在您自己的异常中,并在异常消息中添加信息。 如果可能,您还可以考虑代码重构。

public void foo() throws SpecificException1, SpecificException2 { //Correct way
}

3.3 不捕获Exception类,而是捕获特定的子类

try {
   someMethod();
} catch (Exception e) {
   LOGGER.error("method has failed", e);
}

捕获Exception的问题是,如果您稍后调用的方法在其方法签名中添加了一个新的受检的异常,则开发人员的意图是您应该处理特定的新异常。 如果您的代码只是捕获到Exception(或Throwable),那么您将永远不会知道更改以及您的代码现在是错误的并且可能在运行时的任何时间中断的事实。

3.4 永不捕获Throwable

好吧,这是更严重的麻烦了。 因为 Java 错误也是Throwable的子类。 错误是不可逆的条件,JVM 本身无法处理。 对于某些 JVM 实现,JVM 甚至可能实际上不会在Error上调用您的catch子句。

3.5 始终正确地将异常包装在自定义异常中,以便不会丢失栈跟踪

catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " + e.getMessage());  //Incorrect way
}

这破坏了原始异常的栈跟踪,并且总是错误的。 正确的方法是:

catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " , e);  //Correct way
}

3.6 记录异常还是抛出异常,但不要两者都做

catch (NoSuchMethodException e) {
   LOGGER.error("Some information", e);
   throw e;
}

与上面的示例代码一样,由于代码中的单个问题,日志记录和抛出将导致日志文件中出现多条日志消息,并使试图挖掘日志的工程师费尽心思。

3.7 永远不要从finally块中抛出任何异常

try {
  someMethod();  //Throws exceptionOne
} finally {
  cleanUp();    //If finally also threw any exception the exceptionOne will be lost forever
}

只要cleanUp()永远不会抛出任何异常,就可以了。 在上面的示例中,如果someMethod()引发异常,并且在finally块中,cleanUp()引发异常,则第二个异常将从方法中消失,原始的第一个异常(正确原因)将永远消失。 如果您在finally块中调用的代码可能会引发异常,请确保处理该异常或将其记录下来。 永远不要让它脱离最后的障碍。

3.8 始终仅捕获您可以实际处理的异常

catch (NoSuchMethodException e) {
   throw e; //Avoid this as it doesn't help anything
}

好吧,这是最重要的概念。 不要仅仅为了捕获异常就捕获任何异常。 仅在您要处理任何异常或要在该异常中提供其他上下文信息时才捕获任何异常。 如果您无法在捕获区中处理它,那么最好的建议就是不要仅将其重新抛出就捕获它。

3.9 不要使用printStackTrace()语句或类似方法

完成代码后,切勿离开printStackTrace()。 很有可能是您的同事之一,最终将获得这些栈跟踪中的一个,并且对如何处理它的知识完全为零,因为它不会附加任何上下文信息。

3.10 如果您不打算处理异常,请使用finally块而不是catch

try {
  someMethod();  //Method 2
} finally {
  cleanUp();    //do cleanup here
}

这也是一个好习惯。 如果在您的方法内部访问某个方法 2,并且方法 2 抛出一些您不想在方法 1 中处理的异常,但是仍然希望进行一些清除以防万一发生异常,那么请在finally块中进行此清除。 不要使用挡块。

3.11 记住“早抛出晚捕获”的原则

这可能是有关异常处理的最著名的原理。 它基本上说您应该尽快抛出异常,并尽可能晚地捕获它。 您应该等待,直到掌握了正确处理所有信息为止。

该原则隐含地表明,您将更有可能将其扔到低级方法中,在这种方法中,您将检查单个值是否为null或不合适。 并且您将使异常爬升到栈跟踪相当多个级别,直到达到足够的抽象级别以能够解决问题为止。

3.12 处理异常后请务必清理

如果您正在使用数据库连接或网络连接之类的资源,请确保清理它们。 如果要调用的 API 仅使用非受检的异常,则仍应在使用后使用try–finally块清理资源。 在try块内部访问资源,最后在内部关闭资源。 即使在访问资源时发生任何异常,资源也将正常关闭。

3.13 仅抛出方法中的相关异常

相关性对于保持应用清洁很重要。 一种尝试读取文件的方法; 如果抛出NullPointerException,则不会向用户提供任何相关信息。 相反,如果将此类异常包装在自定义异常中会更好,例如NoSuchFileFoundException,那么它将对该方法的用户更加有用。

3.14 切勿在程序中使用异常进行流控制

我们已经阅读了很多次,但是有时我们会在项目中不断看到代码,在这些代码中,开发人员试图将异常用于应用逻辑。 绝对不要那样做。 它使代码难以阅读,理解和难看。

3.15 在请求处理的早期就验证用户输入以捕获不利条件

始终在很早的阶段就验证用户输入,甚至在输入到实际控制器之前。 这将帮助您最大程度地减少核心应用逻辑中的异常处理代码。 如果用户输入中存在一些错误,它还可以帮助您使应用保持一致。

例如:如果在用户注册应用中,您将遵循以下逻辑:

1)验证用户
2)插入用户
3)验证地址
4)插入地址
5)如果有问题,请回滚所有内容

这是非常不正确的方法。 在各种情况下,它会使数据库处于不一致状态。 而是首先验证所有内容,然后在 dao 层中获取用户数据并进行数据库更新。 正确的方法是:

1)验证用户
2)验证地址
3)插入用户
4)插入地址
5)如果有问题,请回滚所有内容

3.16 始终在单个日志消息中包含有关异常的所有信息

LOGGER.debug("Using cache sector A");
LOGGER.debug("Using retry sector B");

不要这样

在测试用例中,将多行日志消息与多次调用LOGGER.debug()一起使用可能看起来不错,但是当它显示在并行运行 400 个线程的应用服务器的日志文件中时,所有信息都将转储到同一线程 日志文件中,即使两条日志消息出现在代码的后续行中,它们最终也可能在日志文件中以 1000 行隔开。

像这样做:

LOGGER.debug("Using cache sector A, using retry sector B");

3.17 将所有相关信息传递给异常,以使它们尽可能多地提供信息

这对于使异常消息和栈跟踪有用和提供信息也非常重要。 如果您无法从中确定任何内容,则日志的用途是什么。 这些类型的日志仅存在于您的代码中用于装饰。

3.18 总是终止被中断的线程

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {} //Don't do this
  doSomethingCool();
}

InterruptedException是您的代码的线索,它应该停止正在执行的任何操作。 线程被中断的一些常见用例是活动事务超时或线程池被关闭。 您的代码应该尽最大努力完成正在执行的工作,并结束当前的执行线程,而不是忽略InterruptedException。 因此,请更正上面的示例:

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {
    break;
  }
}
doSomethingCool();

3.19 使用模板方法重复try-catch

在代码的 100 个地方没有使用相似的catch块是没有用的。 它增加了代码重复性,无济于事。 在这种情况下,请使用模板方法。

例如,下面的代码尝试关闭数据库连接。

class DBUtil{
    public static void closeConnection(Connection conn){
        try{
            conn.close();
        } catch(Exception ex){
            //Log Exception - Cannot close connection
        }
    }
}

这种类型的方法将在应用中的数千个位置中使用。 不要将整个代码放在每个地方,而要定义上述方法,并在如下所示的任何地方使用它:

public void dataAccessCode() {
    Connection conn = null;
    try{
        conn = getConnection();
		....
    } finally{
        DBUtil.closeConnection(conn);
    }
}

3.20 使用 javadoc 记录应用中的所有异常

练习 Javadoc 一段代码在运行时可能抛出的所有异常。 还要尝试包括可能的措施,如果出现这些异常,用户应遵循。

我现在所想到的就是与 Java 异常处理最佳实践相关的所有内容。 如果您发现任何缺失或与我的观点无关,请给我评论。 我很乐意讨论。

学习愉快!

13 个编写 Spring 配置文件的最佳实践

原文: https://howtodoinjava.com/best-practices/13-best-practices-for-writing-spring-configuration-files/

Spring 是一个功能强大的框架,它使用多种配置选项。 它的最佳功能是为称为 bean 的旧式 Java 对象(PlainOld​​JavaObject)提供企业服务。 Spring 使用依赖项注入(DI)来简化并提高可测试性。

Spring Bean,依赖项和 Bean 所需的服务在 xml 配置文件或注释中指定。 但是,XML 配置文件是冗长且更干净的。 如果未正确计划和编写,在大型项目中将变得非常难以管理。

在本文中,我将向您展示 13 个最佳实践,用于编写 spring XML 配置。 其中一些似乎是最佳实践而不是最佳实践,但我将它们包含在此处是因为它们与该主题高度相关。

注意:其他一些因素(例如应用设计)可能会影响 XML 配置决策,但是我主要关注 XML 配置的可读性和可维护性。

Table of Contents

1) Add a header comment to each configuration file
2) Use consistent naming conventions
3) No version numbers in schema references
4) Prefer setter injection over constructor injection
5) Prefer type over index for constructor argument matching
6) Use shortcut forms over expanded forms
7) Reuse bean definitions as much as possible
8) Always use ids as bean identifiers
9) Try to avoid autowiring
10) Always use classpath prefix
11) Always externalize properties
12) Use dependency-check at the development phase
13) Do not abuse/overuse dependency injection

让我们详细讨论上面的每一个,以使其更有意义。

1)在每个配置文件中添加标题注释

我总是更加强调代码注释。 配置文件也是如此。 添加配置文件标头总是非常有帮助的,该标头总结了配置文件中定义的 bean /属性。

在 Spring 配置中,您可以像添加 XML 注释一样添加注释,也可以使用description元素。 例如:

<beans>
	<description>
		This configuration file will have all beans 
		which may be used for controlling transactions.
	</description>
	...
</beans>

使用description标签的一个可能的优点是,某些工具可能会从此元素中获取描述,以在其他地方为您提供帮助。

2)使用一致的命名约定

在所有配置文件中使用相同的命名是非常重要的。 在整个项目中使用清晰,描述性和一致的名称约定,可以提高配置文件的可读性,并使其他开发人员可以轻松避免一些偶然的错误。

例如,对于 bean ID,可以遵循 Java 类字段名称约定。 EmployeeUpdateDAO实例的 bean ID 将是employeeUpdateDAO。 对于大型项目,可以将包名称添加为 Bean ID 的前缀。 例如 finance.employeeUpdateDAO

3)模式引用中没有版本号

我在之前的文章中也指出了此功能。 我再次将其包括在内,因为从长远来看,它对于改善可维护性至关重要且有益。 要刷新您的内存,根本不需要在 bean 配置文件中为引用的模式指定版本号,您可以忽略它。 如果确实如此,您应该一直忽略它。

Spring 自动从项目依赖项(jar)中选择可用的最高版本。 另外,随着项目的发展和 Spring 版本的更新,我们不必维护所有 XML 配置文件即可看到新功能。

阅读更多: Spring 无版本的架构引用

一个示例示例将如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context/
  http://www.springframework.org/schema/context/spring-context.xsd">

  <!-- Other bean definitions-->

</beans>

4)优先使用设置器注入而不是构造方法注入

Spring 提供了三种类型的依赖项注入:构造器注入,设置器注入和方法注入。 通常,我们都只使用前两种类型。

<!-- Constructor injection -->
<bean id="employeeDAO"	class="com.howtodoinjava.dao.EmployeeDAO">
	<constructor-arg ref="datasource"/>
</bean>

<!-- Setter injection -->
<bean id="employeeDAO" class="com.howtodoinjava.dao.EmployeeDAO">
	<property name="datasource"	ref="datasource">
</bean>

构造器注入可以提供最便宜的线程安全,即不可变对象。 此外,它还可以确保没有完成初始化就不会将对象移交给其他 bean。

设置器注入提供了许多理想的功能,即灵活性或可维护性。 如果在 bean 中要设置多个属性,那么为构造器创建一长串参数不是一个好主意。 同样,如果可能的话,某些属性可能是可选的。

更偏向灵活性。 为了保持不变或线程安全,请遵循其他编程规则。

阅读更多:如何使 Java 类不可变

5)在构造器注入中优先使用类型而不是索引以进行构造器参数匹配

最好避免使用构造器注入,而更偏向使用设置器注入进行依赖项注入。 但是,如果绝对需要使用构造器注入,则总是更偏向基于类型而不是索引的参数匹配。

<!-- Index based constructor injection -->
<bean id="employeeDAO" class="com.howtodoinjava.EmployeeDAO">
	<constructor-arg index="0" value="rest"/>
	<constructor-arg index="1" value="8080"/>
</bean>

<!-- Type based constructor injection -->
<bean id="employeeDAO" class="com.howtodoinjava.EmployeeDAO">
	<constructor-arg type="java.lang.String" value="rest"/>
	<constructor-arg type="int" value="8080"/>
</bean>

如您所见,基于类型的参数传递更具可读性,并且不易出错。 但是,只要基于类型的参数传递有任何歧义,就可以毫不犹豫地转到基于索引的参数传递。

6)使用快捷形式而不是扩展形式

Spring bean 配置语义允许两种形式来指定属性值和其他 bean 引用。 一种是扩展形式,另一种是较短形式。 最好使用较短的版本。

<!-- Expanded version -->
<bean id="employeeDAO" class="com.howtodoinjava.dao.EmployeeDAO">
	<property name="datasource">
		<ref bean="datasource"></ref>
		<value>datasource</value>
	 </property>
</bean>

<!-- Shorter/shortcut version -->
<bean id="employeeDAO" class="com.howtodoinjava.dao.EmployeeDAO">
	<property name="datasource"	ref="datasource" value="datasource">
</bean>

7)尽可能重用 bean 定义

Spring 提供了非常有用的功能,您应该在项目中广泛使用它,即 bean 定义可重用性。 在这里,我不是在谈论用于设置器注入的 bean 参考。 而是我指出了在构造其他 bean 时重用 bean 定义。

以数据源定义的示例为例:

<bean id="abstractDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
	destroy-method="close"
	p:driverClassName="${jdbc.driverClassName}"
	p:username="${jdbc.username}"
	p:password="${jdbc.password}" />

<bean id="concreteDataSourceOne"
	parent="abstractDataSource"
	p:url="${jdbc.databaseurlOne}"/>

<bean id="concreteDataSourceTwo"
	parent="abstractDataSource"
	p:url="${jdbc.databaseurlTwo}"/>

阅读更多: Spring AbstractRoutingDataSource示例

8)始终使用“id”作为 bean 标识符

Spring 允许 bean 使用两种类型的标识符。 使用属性“id”或“name”。 您应该始终选择属性 ID 而不是名称。 通常,它既不会增加可读性,也不会增加任何性能。 这是行业标准的做法,所有其他开发人员都在全世界甚至在您的团队中遵循。

只是不要在这里是个奇怪的人。

9)尽量避免自动装配

如果可以长期管理,自动装配是一个很棒的功能。 通常,如果您的项目只有很少的 bean,并且几乎可以在内存中记住它们,那将是有益的。

一旦项目变大,自动装配就开始在确定要使用的正确依赖项方面造成麻烦。 我发现主要的缺点是无法将整个系统绑定在一起。 这是 spring 配置文件获胜的地方。 他们可以在几分钟之内将整个系统呈现给任何新手。

另外,当您开始调试一些复杂的问题时,所有在配置文件中的所有信息实际上都会有很大帮助。 自动装配使调试更加困难。

了解更多:Spring 自动装配

10)始终使用classpath前缀

导入资源,XML 配置,属性等时,请始终使用classpath:classpath*:前缀。 这提供了资源位置的一致性和清晰度。 并非 Spring 的每个功能都具有相同的行为,类路径保证一致性

类路径由构建工具和 IDE 确定。 对于 Java 代码,通常为src/main/java,对于非 Java 依赖项和测试通常为src/main/resources,对于 Java 代码为src/test/java,对于非 Java 资源为src/test/resources

<!-- Always use classpath: prefix-->
<import resource="classpath:/META-INF/spring/applicationContext-security.xml"/>

11)总是外部化属性

通常,存在与应用运行时间相关的多个配置参数。 它们被传递到 bean 配置上下文文件中的 bean 定义。 不要将它们硬编码在配置文件中。 而是将它们外部化到某些属性文件。

最好根据它们的用法或模块将它们分组在单独的文件中,即jdbc.properties文件中所有与 JDBC 数据源相关的属性。

<bean id="abstractDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:username="${jdbc.username}"
        p:password="${jdbc.password}" />

和属性文件是:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=password

12)在开发阶段使用“依赖项检查”

您应该将 Bean 定义上的dependency-check属性设置为simpleobjectsall(默认值为none,即不检查),以便容器可以为您执行显式依赖项验证。 当必须显式地或通过自动装配来设置 Bean 的所有属性(或某些类别的属性)时,这很有用。

<bean id="abstractDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
	destroy-method="close"
	p:driverClassName="${jdbc.driverClassName}"
	p:username="${jdbc.username}"
	p:password="${jdbc.password}" 
	dependency-check="all" />

在上面给出的示例中,容器将确保在应用初始化时间本身中设置数据源的所有属性/参数。

13)不要滥用依赖注入

最后,请不要滥用引入依赖注入的动机。

Java 提供“new”关键字来创建新对象。 在不需要 DI 的地方使用此精彩的关键字,例如 DTO 对象。 不要试图玩得更聪明。 只需遵循基础知识即可。

如果您对以上几点有异议,请给我评论。

学习愉快!

Java Web 应用性能改进技巧

原文: https://howtodoinjava.com/best-practices/improving-web-application-performance/

Web 应用的性能对于任何公司和任何客户的项目成功都是至关重要的。 您应该对可能的瓶颈及其解决方案有一个很好的思考过程。

最近,博客读者之一斯里曼(Sriman)向我提出了这个问题。 好吧,我认为这个问题的答案可能会对更多人有用,所以我在这里发布答案。

性能技巧

你们中的许多人都认为性能本身是一个相对术语。 性能卓越的应用可能会让其他人或您的客户感到失望。 因此,与任何其他相对测量一样,您也必须在此处遵循基本要求。

  1. 定义(出色的)性能基准:

    您需要做的第一件事就是决定什么对您来说是好的表现。 就时间单位或资源利用率而言,这应该是一个可衡量的目标。

  2. 确定问题领域:

    定义基准数字后,请使用工具来确定应用落后于基准数字的区域。 深入探讨代码中的语句级别。 确定哪些代码段导致性能下降。 列出它们中的每一个,并在整个过程中跟踪每个零件。 注意:请记住,过度优化通常会带来更多危害。 因此,明智地选择。

  3. 解决每个问题并衡量差异:

    与您的团队成员就每个问题进行头脑风暴,并找到解决该问题的最佳方法。 一次一个。 比较每个问题的每个解决方案,并记录其结果。

  4. 衡量整体表现:

    最后,衡量整体应用性能的提高并与基准进行比较。 仍然您落后,然后重复所有步骤。

尽管上述步骤已经完成,但实际上需要完成哪些工作 – 以可衡量的方式提高性能,但您需要记住以下几点:

  • 开箱即用。 例如,如果数据库查询花费时间,则不要过度使用 SQL 语句,而应考虑使用缓存。
  • 永远不要假设/猜测任何东西。 始终进行建议的更改并记录其效果。
  • 寻求自动化的帮助。 每次更改后,使用工具来捕获性能。 这将使生活更加轻松。
  • 避免过度优化。 首选代码的简单性和可维护性。
  • 也考虑使用高级的,功能更强大的硬件/网络解决方案。 它们有时会产生很大的影响。

永远记住,性能是相对的经验。 向您的客户寻求反馈。 如果他们满意,则不要尝试修复应用中的任何内容。

我希望上述过程可以帮助您在优化任何应用性能时克服这一复杂情况。

祝您学习愉快!

Java 算法

Java 算法和实现

原文: https://howtodoinjava.com/java-algorithms-implementations/

此页面列出了本博客中讨论的所有 Java 算法和实现,以提供快速链接。 随意建议您可能想学习的更多算法。

Java 排序算法

  1. 快速排序

    快速排序是一种分而治之的算法,这意味着将原始数组分为两个数组,分别对每个数组进行排序,然后将排序后的输出合并以生成排序后的数组。

  2. 归并排序

    当数据结构不支持随机访问时使用归并排序,因为它可以与纯顺序访问(正向迭代器,而不是随机访问迭代器)一起使用。 它也广泛用于外部排序,与顺序访问相比,随机访问的费用非常高。

  3. 冒泡排序

    冒泡排序是一种简单而缓慢的排序算法,它会反复遍历集合,比较每对相邻元素,如果顺序错误则交换它们。

  4. 选择排序

    选择排序是一种简单而缓慢的排序算法,可从未排序部分中反复选择最低或最高元素,然后将其移至已排序部分的末尾。

  5. 插入排序

    插入排序是一种简单而缓慢的排序算法,它反复从未排序部分中取出下一个元素,并将其插入到正确位置的已排序部分中。

更多 Java 算法

以下是博客中提供的更多 Java 算法。

  1. 使用 Soundex 算法进行语音搜索

    您是否曾经想过,在任何单词编辑器中,拼写检查器会如何在您遇到任何拼写错误时建议您列出其他可能的单词? 这是通过语音搜索完成的。 Soundex 是一种语音算法,用于通过声音索引名称(英语发音)。

  2. 比较和交换(CAS)算法

    该算法将存储位置的内容与给定值进行比较,并且只有它们相同时,才会将该存储位置的内容修改为给定的新值。 这是作为单个原子操作完成的。

  3. 使用 MD5,SHA,PBKDF2,BCrypt 等进行密码加密。

    密码哈希是在对用户提供的密码应用某些算法和操作后获得的加密的字符序列,这些密码和密码通常很弱并且很容易猜到。 让我们探讨几种生成这些哈希的算法。

  4. 如何检测链表中的无限循环

    询问您是否有一个只能在一个方向上移动的链表,并且如果该链表中有一个循环,您将如何检测到它? 让我们解决问题。

  5. AES(高级加密标准)算法示例

    高级加密标准,它是一种对称加密算法。 美国使用 AES 加密来保护敏感但未分类的资料,因此可以说它足够安全。

祝您学习愉快!

冒泡排序 Java 示例

原文: https://howtodoinjava.com/algorithm/bubble-sort-java-example/

冒泡排序是一种简单而缓慢的排序算法,它会反复浏览整个集合,比较每对相邻元素,并以错误的顺序交换它们。 在排序算法中,如果您观察元素以较高顺序(即较大的值)移动,它们就像水中的气泡,从底部到顶部(从数组的一侧/中间到另一侧)缓慢漂浮。

您可以想象在每一步上都有大气泡漂浮并停留在表面。 在该步骤中,当没有气泡移动时,排序停止。

冒泡排序算法

冒泡排序算法的基本思想可以描述为以下步骤:

  1. 数据元素分为两部分:已排序部分和未排序部分。
  2. 遍历未排序部分中的每个元素,并与相邻元素重新排列其位置,以将具有较高顺序的元素放在较高位置。 最后,顺序最高的元素将位于未排序部分的顶部,并移至已排序部分的底部。
  3. 重复步骤 2,直到未排序的部分中没有剩余的元素。

Bubble sort algorithm

冒泡排序算法

冒泡排序 Java 示例

public class BubbleSortExample 
{
	public static void main(String[] args) 
	{
		// This is unsorted array
		Integer[] array = new Integer[] { 12, 13, 24, 10, 3, 6, 90, 70 };

		// Let's sort using bubble sort
		bubbleSort(array, 0, array.length);

		// Verify sorted array
		System.out.println(Arrays.toString(array));
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void bubbleSort(Object[] array, int fromIndex, int toIndex) 
	{
		Object d;
		for (int i = toIndex - 1; i > fromIndex; i--) 
		{
			boolean isSorted = true;
			for (int j = fromIndex; j < i; j++) 
			{
				//If elements in wrong order then swap them
				if (((Comparable) array[j]).compareTo(array[j + 1]) > 0) 
				{
					isSorted = false;
					d = array[j + 1];
					array[j + 1] = array[j];
					array[j] = d;
				}
			}
			//If no swapping then array is already sorted
			if (isSorted)
				break;
		}
	}
}

Output: [3, 6, 10, 12, 13, 24, 70, 90]

冒泡排序性能和复杂度

  1. 冒泡排序属于 O(n^2)排序算法,这使得排序大型数据量效率很低。
  2. 冒泡排序既是稳定的,又是自适应的
  3. 对于几乎排序的数据,冒泡排序需要O(n)时间,但至少需要 2 次通过数据。
  4. 如果输入通常是按排序顺序,但偶尔可能有一些乱序的元素几乎在适当的位置,则这是可行的。
  5. 在大型集合的情况下,应避免冒泡排序。
  6. 在逆序集合的情况下,效率不高。

学习愉快!

插入排序 Java 示例

原文: https://howtodoinjava.com/algorithm/insertion-sort-java-example/

插入排序是一种简单而缓慢的排序算法,它反复从未排序部分中提取下一个元素,然后将其插入正确位置的已排序部分中。

插入排序的想法来自我们的日常生活经验。 例如,当您与朋友一起玩纸牌时,会将您挑选的下一张纸牌插入手中的已排序纸牌中。

插入排序算法

插入排序算法的基本思想可以描述为以下步骤:

  1. 数据元素分为两部分:已排序部分和未排序部分。
  2. 从未排序的部分中获取一个元素。
  3. 根据可比属性,将元素插入排序部分的正确位置。
  4. 重复步骤 2 和 3,直到未排序的部分中没有剩余的元素。

插入排序优势

尽管插入排序显然比归并排序等其他排序算法要慢,但是在某些情况下它具有一些良好的优点:

  • 对小型数据输入集有效
  • 它是自适应,即对于已基本排序的数据集有效
  • 稳定; 即不使用相同的键更改元素的相对顺序
  • 在线; 即可以对列表进行排序

插入排序 Java 实现

public class InsertionSortExample 
{
	public static void main(String[] args) 
	{
		//This is unsorted array
		Integer[] array = new Integer[] {12,13,24,10,3,6,90,70};

		//Let's sort using insertion sort
		insertionSort(array, 0, array.length);

		//Verify sorted array
		System.out.println(Arrays.toString(array));
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void insertionSort(Object[] a, int fromIndex, int toIndex) 
	{
		Object d;
		for (int i = fromIndex + 1; i < toIndex; i++) 
		{
			d = a[i];
			int j = i;
			while (j > fromIndex && ((Comparable) a[j - 1]).compareTo(d) > 0) 
			{
				a[j] = a[j - 1];
				j--;
			}
			a[j] = d;
		}
	}
}

Output: [3, 6, 10, 12, 13, 24, 70, 90]

插入排序性能改进

如果查看以前的实现,则可以轻松地确定遍历数组以在已排序的数组中找到正确的位置,这似乎是大多数时间的任务,可以使用更快速的搜索算法轻松地对其进行改进。

正如我们看到的那样,数组已经排序,因此我们可以采用二分搜索算法,该算法对于已排序的数组更有效。 因此,我们的改进的插入排序算法将变为:

public class InsertionSortExample 
{
	public static void main(String[] args) 
	{
		// This is unsorted array
		Integer[] array = new Integer[] { 12, 13, 24, 10, 3, 6, 90, 70 };

		// Let's sort using insertion sort
		insertionSortImproved(array, 0, array.length);

		// Verify sorted array
		System.out.println(Arrays.toString(array));
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void insertionSortImproved(Object[] a, int fromIndex, int toIndex) 
	{
		Object d;
		for (int i = fromIndex + 1; i < toIndex; i++)
		{
			d = a[i];
			int jLeft = fromIndex;
			int jRight = i - 1;
			//Check if its current position is it's suitable position
			if (((Comparable) a[jRight]).compareTo(d) > 0) 
			{
				//Perform binary search
				while (jRight - jLeft >= 2) 
				{
					int jMiddle = (jRight - jLeft) / 2 + jLeft - 1;
					if (((Comparable) a[jMiddle]).compareTo(d) > 0) {
						jRight = jMiddle;
					} else {
						jLeft = jMiddle + 1;
					}
				}
				if (jRight - jLeft == 1) 
				{
					int jMiddle = jLeft;
					if (((Comparable) a[jMiddle]).compareTo(d) > 0) {
						jRight = jMiddle;
					} else {
						jLeft = jMiddle + 1;
					}
				}
				//Place the element
				int j = i;
				for (j = i; j > jLeft; j--) 
				{
					a[j] = a[j - 1];
				}
				a[j] = d;
			}
		}
	}
}

Output: [3, 6, 10, 12, 13, 24, 70, 90]

当然,它具有更多的代码行,但是肯定会提高整体排序时间的性能。

学习愉快!

参考: https://en.wikipedia.org/wiki/Insertion_sort

归并排序 Java 示例

原文: https://howtodoinjava.com/algorithm/merge-sort-java-example/

在计算机科学中,归并排序(也通常称为拼写归并排序)是一种基于O(n log n)比较的排序算法。 大多数实现都会产生稳定排序,这意味着该实现会在排序后的输出中保留相等元素的输入顺序。 归并排序是一种分而治之的算法。 分而治之算法将原始数据分为较小的数据集以解决该问题。

在归并排序过程中,集合中的对象分为两个集合。 要拆分集合,归并排序将采用集合的中间部分并将其拆分为左侧和右侧。 通过归并排序算法将生成的集合再次递归拆分,直到将其分解为每个集合中的单个元素为止。

分割每个集合后,归并排序算法开始组合通过上述过程获得的所有集合。 为了合并两个集合,归并排序从每个集合的开头开始。 它选择较小的对象,然后将该对象插入新集合中。 对于此集合,它现在通过一次比较每个集合中的一个元素,来选择下一个元素,并从两个集合中选择较小的元素。

此过程将创建一个已排序元素的集合(所有需要排序的元素的子集)。 对于在第一步中获得的所有可用集合,即对集合进行拆分,将以递归方式完成此过程。

将两个集合中的所有元素都插入新集合后,归并排序已成功对集合进行了排序。

为了避免创建太多集合,通常只创建一个新集合,而将新集合和现有集合视为不同的集合。

为了更好地理解,请看下面遵循上述方法的图表。

Merge sort algorithm

归并排序算法

从概念上讲,归并排序以递归方式进行如下工作:

  1. 将未排序的列表分为两个大小约为一半的子列表
  2. 对两个子列表中的每个列表进行排序
  3. 将两个已排序的子列表合并回一个已排序的列表

归并排序示例

在下面的示例中,我们以表达方式实现了归并排序算法,以使其更易于理解。 在给定归并排序代码的情况下,遵循下面每个步骤/语句上方写的注释。

import java.util.*;

public class MergerSort 
{
	public static void main(String[] args) 
	{
		//Unsorted array
		Integer[] a = { 2, 6, 3, 5, 1 };

		//Call merge sort
		mergeSort(a);

		//Check the output which is sorted array
		System.out.println(Arrays.toString(a));
	}

	@SuppressWarnings("rawtypes") 
	public static Comparable[] mergeSort(Comparable[] list) 
	{
		//If list is empty; no need to do anything
        if (list.length <= 1) {
            return list;
        }

        //Split the array in half in two parts
        Comparable[] first = new Comparable[list.length / 2];
        Comparable[] second = new Comparable[list.length - first.length];
        System.arraycopy(list, 0, first, 0, first.length);
        System.arraycopy(list, first.length, second, 0, second.length);

        //Sort each half recursively
        mergeSort(first);
        mergeSort(second);

        //Merge both halves together, overwriting to original array
        merge(first, second, list);
        return list;
    }

	@SuppressWarnings({ "rawtypes", "unchecked" }) 
    private static void merge(Comparable[] first, Comparable[] second, Comparable[] result) 
	{
        //Index Position in first array - starting with first element
        int iFirst = 0;

        //Index Position in second array - starting with first element
        int iSecond = 0;

        //Index Position in merged array - starting with first position
        int iMerged = 0;

        //Compare elements at iFirst and iSecond, 
        //and move smaller element at iMerged
        while (iFirst < first.length && iSecond < second.length) 
        {
            if (first[iFirst].compareTo(second[iSecond]) < 0) 
            {
                result[iMerged] = first[iFirst];
                iFirst++;
            } 
            else 
            {
                result[iMerged] = second[iSecond];
                iSecond++;
            }
            iMerged++;
        }
        //copy remaining elements from both halves - each half will have already sorted elements
        System.arraycopy(first, iFirst, result, iMerged, first.length - iFirst);
        System.arraycopy(second, iSecond, result, iMerged, second.length - iSecond);
    }
}

Output:

Input Array :  [ 2, 6, 3, 5, 1, 1, 8 ]
Output Array : [ 1, 1, 2, 3, 5, 6, 8 ]

Input Array :  [ 12, 16, 333, 50, 1000, 5, 897, 1, 3, 66, 13 ]
Output Array : [ 1, 3, 5, 12, 13, 16, 50, 66, 333, 897, 1000 ]

何时使用归并排序

  1. 当数据结构不支持随机访问时使用归并排序,因为它可以与纯顺序访问(正向迭代器,而不是随机访问迭代器)一起使用。 它也广泛用于外部排序,与顺序访问相比,随机访问的费用非常高。

    例如,当对不适合内存的文件进行排序时,您可以将其分成适合内存的块,单独使用对它们进行排序,将每个数据写入文件,然后合并对生成的文件进行排序。

  2. 另外,当您需要稳定排序时,可以使用归并排序。 这是归并排序的非常重要的功能。

  3. 当处理链接列表时,归并排序更快。 这是因为合并列表时可以轻松更改指针。 它只需要遍历列表一次(O(n))。

  4. 如果发生大量并行化,则归并排序并行化要比其他排序算法简单。

这就是关于归并排序 Java 教程的全部内容。 在下面的评论部分中将您的问题/疑问交给我。

祝您学习愉快!

快速排序 Java 示例

原文: https://howtodoinjava.com/algorithm/quicksort-java-example/

快速排序算法是最常用的排序算法之一,尤其是对大型列表/数组进行排序。 快速排序是分而治之算法,这意味着将原始数组分为两个数组,每个数组分别进行排序,然后将排序后的输出合并以生成排序后的数组。 平均而言,具有O(n log n)复杂度,这使得快速排序适合于对大数据量进行排序。

用更标准的词来说,快速排序算法通过与枢轴元素进行比较,将未排序的部分重复地分为低阶子部分和高阶子部分。 递归结束时,我们得到了排序数组。 请注意,可以将快速排序实现为“就地”排序。 这意味着排序在数组中进行,不需要创建其他数组。

快速排序算法

快速排序算法的基本思想可以描述为以下步骤:

如果数组仅包含一个元素或零个元素,则对数组进行排序。 如果数组包含多个元素,则:

  1. 通常从中间选择一个元素作为枢轴元素,但这不是必需的。
  2. 数据元素分为两部分:一个元素的顺序比枢轴元素低,一个元素的顺序比枢轴元素高。
  3. 重复步骤 1 和 2,分别对这两部分进行排序。

快速排序 Java 示例

以下是示例快速排序 Java 实现

public class QuickSortExample 
{
	public static void main(String[] args) 
	{
		// This is unsorted array
		Integer[] array = new Integer[] { 12, 13, 24, 10, 3, 6, 90, 70 };

		// Let's sort using quick sort
		quickSort( array, 0, array.length - 1 );

		// Verify sorted array
		System.out.println(Arrays.toString(array));
	}

	public static void quickSort(Integer[] arr, int low, int high) 
	{
		//check for empty or null array
		if (arr == null || arr.length == 0){
			return;
		}

		if (low >= high){
			return;
		}

		//Get the pivot element from the middle of the list
		int middle = low + (high - low) / 2;
		int pivot = arr[middle];

		// make left < pivot and right > pivot
		int i = low, j = high;
		while (i <= j) 
		{
			//Check until all values on left side array are lower than pivot
			while (arr[i] < pivot) 
			{
				i++;
			}
			//Check until all values on left side array are greater than pivot
			while (arr[j] > pivot) 
			{
				j--;
			}
			//Now compare values from both side of lists to see if they need swapping 
			//After swapping move the iterator on both lists
			if (i <= j) 
			{
				swap (arr, i, j);
				i++;
				j--;
			}
		}
		//Do same operation as above recursively to sort two sub arrays
		if (low < j){
			quickSort(arr, low, j);
		}
		if (high > i){
			quickSort(arr, i, high);
		}
	}

	public static void swap (Integer array[], int x, int y)
    {
		int temp = array[x];
		array[x] = array[y];
		array[y] = temp;
    }
}

Output: [3, 6, 10, 12, 13, 24, 70, 90]

在 Java 中, Arrays.sort()方法使用快速排序算法使用双枢轴元素对原始类型数组进行排序。 双枢轴使该算法更快。 检查出。

学习愉快!

Gson – JSON 输出的精美打印

原文: https://howtodoinjava.com/gson/pretty-print-json-output/

默认情况下,Gson 以紧凑格式打印 JSON,即字段名称及其值,对象字段以及 JSON 输出中数组内的对象等之间将没有空格。

例如:

{"id":1,"firstName":"Lokesh","lastName":"Gupta", "emailId":"howtogoinjava@gmail.com"}

要启用 Gson 精美打印功能,我们必须使用GsonBuilder配置Gson实例。

默认情况下,Gson 格式化输出 JSON,其默认行长为 80 个字符,2 个字符的缩进和 4 个字符的右边距。

1.精美打印 JSON 的语法

Gson gson = new GsonBuilder()
				.setPrettyPrinting()
				.create();

String jsonOutput = gson.toJson(someObject);

2. Gson 精美打印示例

序列化Employee对象并漂亮地打印 JSON 输出的 Java 程序。

public class Employee 
{
	private Integer id;
    private String firstName;
    private String lastName;
    private String email;

    //Constructors
    //Getters and setters
}

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Main 
{
	public static void main(String[] args) 
    {
		Employee employeeObj = new Employee(1, "Lokesh", "Gupta", "howtogoinjava@gmail.com");

		Gson gson = new GsonBuilder()
				.setPrettyPrinting()
				.create(); 

		System.out.println(gson.toJson(employeeObj));
    }
}

程序输出。

{
  "id": 1,
  "firstName": "Lokesh",
  "lastName": "Gupta",
  "emailId": "howtogoinjava@gmail.com"
}

向我提供有关在 Gson 中实现精美打印的问题。

学习愉快!

参考:

Gson 用户指南

选择排序 Java 示例

原文: https://howtodoinjava.com/algorithm/selection-sort-java-example/

选择排序是一种简单且缓慢的排序算法,反复从未排序部分中选择最低或最高元素,然后将其移至已排序部分的末尾。 通常,从性能角度来看,它甚至比插入排序还要慢。 它不会以任何方式适应数据,因此其运行时间始终是二次的。

但是,您不应得出结论,永远不要使用选择排序。 好东西是选择排序具有的属性,可将每次迭代的交换次数减至最少。 在交换项目成本很高的应用中,选择排序可能是很好的选择算法。

选择排序算法

以下是逻辑代码结构或通常的选择排序的伪代码。

for i = 1:n,
k = i
for j = i+1:n, if a[j] < a[k], k = j
//-> invariant: a[k] smallest of a[i..n]
swap a[i,k]
//-> invariant: a[1..i] in final position
end

用简单的英语来说,会发生以下情况:

  1. 数据元素分为两部分:一个已排序的部分(最初为空)和一个未排序的部分(最初为完整数组)。
  2. 假定排序顺序是从低到高,请从未排序部分中找到可比较顺序最低的元素。
  3. 将找到的元素放在已排序部分的末尾。
  4. 重复步骤 2 和 3,直到未排序的部分中没有剩余的元素。

选择排序 Java 源代码

以下是 Java 中的示例选择排序实现

public class SelectionSortExample 
{
	public static void main(String[] args) {
		// This is unsorted array
		Integer[] array = new Integer[] { 12, 13, 24, 10, 3, 6, 90, 70 };

		// Let's sort using selection sort
		selectionSort(array, 0, array.length);

		// Verify sorted array
		System.out.println(Arrays.toString(array));
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void selectionSort(Object[] array, int fromIndex, int toIndex) 
	{
		Object d;
		for (int currentIndex = fromIndex; currentIndex < toIndex; currentIndex++) 
		{
			int indexToMove = currentIndex;
			for (int tempIndexInLoop = currentIndex + 1; tempIndexInLoop < toIndex; tempIndexInLoop++) 
			{
				if (((Comparable) array[indexToMove]).compareTo(array[tempIndexInLoop]) > 0) 
				{
					//Swapping
					indexToMove = tempIndexInLoop;
				}
			}
			d = array[currentIndex];
			array[currentIndex] = array[indexToMove];
			array[indexToMove] = d;
		}
	}
}

Output: [3, 6, 10, 12, 13, 24, 70, 90]

如果你们知道有什么方法可以提高选择排序的性能,请分享。 我无能为力。

学习愉快!

Java AES 加密解密示例

原文: https://howtodoinjava.com/security/java-aes-encryption-example/

Java 支持许多安全的加密算法,但是其中一些功能较弱,无法在安全性要求很高的应用中使用。 例如,数据加密标准(DES)加密算法被认为是高度不安全的。 使用电子前沿基金会(EFF)的 Deep Crack 之类的机器,在一天之内用 DES 加密了使用 DES 加密的邮件。

一种更安全的加密算法是 AES – 高级加密标准,它是一种对称加密算法。 美国使用 AES 加密来保护敏感但未分类的资料,因此可以说它足够安全。

阅读更多: Java AES 256 加密解密示例

1. AES 加密和解密

我们来看一个在 Java 程序中使用 AES 加密的示例。

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AES {

	private static SecretKeySpec secretKey;
	private static byte[] key;

	public static void setKey(String myKey) 
	{
		MessageDigest sha = null;
		try {
			key = myKey.getBytes("UTF-8");
			sha = MessageDigest.getInstance("SHA-1");
			key = sha.digest(key);
			key = Arrays.copyOf(key, 16); 
			secretKey = new SecretKeySpec(key, "AES");
		} 
		catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} 
		catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	public static String encrypt(String strToEncrypt, String secret) 
	{
		try 
		{
			setKey(secret);
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			cipher.init(Cipher.ENCRYPT_MODE, secretKey);
			return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8")));
		} 
		catch (Exception e) 
		{
			System.out.println("Error while encrypting: " + e.toString());
		}
		return null;
	}

	public static String decrypt(String strToDecrypt, String secret) 
	{
		try 
		{
			setKey(secret);
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
			cipher.init(Cipher.DECRYPT_MODE, secretKey);
			return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
		} 
		catch (Exception e) 
		{
			System.out.println("Error while decrypting: " + e.toString());
		}
		return null;
	}
}

2.加密和解密示例

让我们测试一下是否能够从加密字符串中取回解密后的字符串。

public static void main(String[] args) 
{
	final String secretKey = "ssshhhhhhhhhhh!!!!";

	String originalString = "howtodoinjava.com";
	String encryptedString = AES.encrypt(originalString, secretKey) ;
	String decryptedString = AES.decrypt(encryptedString, secretKey) ;

	System.out.println(originalString);
	System.out.println(encryptedString);
	System.out.println(decryptedString);
}

Output:

howtodoinjava.com
Tg2Nn7wUZOQ6Xc+1lenkZTQ9ZDf9a2/RBRiqJBCIX6o=
howtodoinjava.com

在下面将您的问题和评论丢给我。

学习愉快!

使用 Soundex 算法实现语音搜索

原文: https://howtodoinjava.com/algorithm/implement-phonetic-search-using-soundex-algorithm/

您是否曾经想过,在任何单词编辑器中,拼写检查器会如何在您遇到任何拼写错误时建议您列出其他可能的单词? 这是通过语音搜索完成的。 Soundex 是一种语音算法,用于通过声音索引名称(英语发音)。 我们的目标是将同音异义词(发音与另一个单词相同,但含义不同,并且拼写可能有所不同)被编码为相同的表示形式,以便尽管拼写稍有不同(例如, bear - beerNelson - Neilson - Neelson

现在,它已成为流行的数据库软件(例如 DB2,PostgreSQL,MySQL,Ingres,MS SQL Server 和 Oracle)以及一些主要单词编辑器的标准功能。

Soundex 算法

该算法由罗伯特·罗素(Robert Russell)在 1910 年针对英语单词开发。 该算法的主要原理是,辅音根据序数进行分组,最后被编码为与其他辅音匹配的值。 它旨在通过上述过程为每个单词找到一个代码,称为 soundex 代码。

The Soundex code for a name consists of a letter followed by three numerical digits: the letter is the first letter of the name, and the digits encode the remaining consonants.

查找 soundex 代码的完整算法如下:

  1. 保留名称的第一个字母,并删除所有其他出现的a, e, i, o, u, y, h, w

  2. 如下将辅音替换为数字(在第一个字母之后):

    b, f, p, v → 1

    c, g, j, k, q, s, x, z → 2

    d, t → 3

    l → 4

    m, n→5

    r → 6

  3. 如果两个或两个以上具有相同编号的字母在原始名称中相邻(在步骤 1 之前),则仅保留第一个字母;否则,将保留第一个字母。 同样,两个以“h”或“w”分隔的相同数字的字母也被编码为一个数字,而以元音分隔的此类字母被编码两次。 此规则也适用于首字母。

  4. 重复上一步,直到有一个字母和三个数字。 如果您的单词字母太少而无法分配三个数字,请在后面加上零,直到三个数字为止。 如果您的字母超过 3 个,则只需保留前 3 个数字即可。

Java 的 Soundex 实现

Soundex 算法的一种实现如下:

package com.howtodoinjava.examples;

public class Soundex 
{
	public static String getGode(String s) 
	{
		char[] x = s.toUpperCase().toCharArray();

		char firstLetter = x[0];

		//RULE [ 2 ]
		//Convert letters to numeric code
		for (int i = 0; i < x.length; i++) {
			switch (x[i]) {
			case 'B':
			case 'F':
			case 'P':
			case 'V': {
				x[i] = '1';
				break;
			}

			case 'C':
			case 'G':
			case 'J':
			case 'K':
			case 'Q':
			case 'S':
			case 'X':
			case 'Z': {
				x[i] = '2';
				break;
			}

			case 'D':
			case 'T': {
				x[i] = '3';
				break;
			}

			case 'L': {
				x[i] = '4';
				break;
			}

			case 'M':
			case 'N': {
				x[i] = '5';
				break;
			}

			case 'R': {
				x[i] = '6';
				break;
			}

			default: {
				x[i] = '0';
				break;
			}
			}
		}

		//Remove duplicates
		//RULE [ 1 ]
		String output = "" + firstLetter;

		//RULE [ 3 ]
		for (int i = 1; i < x.length; i++)
			if (x[i] != x[i - 1] && x[i] != '0')
				output += x[i];

		//RULE [ 4 ]
		//Pad with 0's or truncate
		output = output + "0000";
		return output.substring(0, 4);
	}
}

让我们看看如何使用上述算法。

class Main {
	public static void main(String[] args) 
	{
		String name1 = "beer";
		String name2 = "bear";
		String name3 = "bearer";

		System.out.println(Soundex.getGode(name1));
		System.out.println(Soundex.getGode(name2));
		System.out.println(Soundex.getGode(name3));
	}
}

Output:

B600
B600
B660

从上面的输出中很明显,单词“bear”和“beer”具有相同的代码; 所以他们是语音的 “bearer”一词具有不同的代码,因为它的语音不同。

您可以查看一些可用的 soundex 算法实现,例如 Apache Commons Soundex 实现

参考文献

https://zh.wikipedia.org/wiki/Soundex

http://introcs.cs.princeton.edu/java/31datatype/Soundex.java.html

祝您学习愉快!

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

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

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

1.乐观锁和悲观锁

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

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

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

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

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

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

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

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

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

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

祝您学习愉快!

Python 教程

Python 教程

原文: https://howtodoinjava.com/python-tutorial/

Python 是一种流行的编程语言,由 Guido van Rossum 创建,并于 1991 年发布。Python 被认为是最流行的编程语言中最热门的技能之一。

它是开源的,即我们甚至可以出于商业目的自由安装,使用和分发。

在本教程中,我们将学习 python 基础知识和一些高级概念。

1. Python 是解释语言

编程语言通常分为两类 - 解释语言编译语言

编译语言是指使用编译器事先将源代码编译为可执行指令的语言(例如 Java)。 以后,这些符合条件的指令可以由运行时环境执行。

解释语言是不应用中间编译步骤并且可以将源代码直接提供给运行时环境的语言。 在此,源代码到机器代码的转换在执行器的同时发生。

意味着,任何用 python 编写的源代码都可以直接执行而无需编译。

2. Python 很简单

Python 主要是为了强调代码的可读性而开发的,它的语法允许程序员用更少的代码行来表达概念。

根据语言中可用关键字的简单性粗略衡量,Python3 有 33 个关键字,Python 2 有 31 个关键字。相比之下,C++ 有 62 个关键字,Java 有 53 个关键字。

Python 语法提供了一种易于学习和易于阅读的简洁结构。

3.与其他语言比较

  • Python 使用新行来完成语句。 在其他编程语言中,我们经常使用分号或括号。
  • Python 依靠缩进(使用空格)来定义范围,例如,循环,函数和类。 为此,其他编程语言通常使用花括号。

4.用途和好处

Python 可用于快速原型制作或可用于生产环境的软件开发。 以下列表列出了 python 的一些流行用法。

  • Python 具有强大的标准库和许多有用的模块,可用于开发应用。 这些模块可以帮助我们添加所需的功能,而无需编写更多代码。
  • 由于 python 是一种解释型高级编程语言,它使我们无需修改即可在多个平台上运行相同的代码。
  • Python 可用于以过程风格,面向对象风格或函数风格编写应用。
  • Python 具有分析数据和可视化等功能,有助于为大数据分析机器学习人工智能创建自定义解决方案。
  • Python 还用于机器人技术,网页抓取,脚本编写,人脸检测,颜色检测和 3D 应用中。 我们可以使用 python 构建基于控制台的应用,基于音频的应用,基于视频的应用,企业应用等。

5.安装 Python

如今,大多数计算机和操作系统均已安装了 python。 要检查机器中是否已经存在 python,请执行以下命令。

$ python --version

#prints

Python3.8.0

如果机器没有安装 python,那么我们可以从以下网站免费下载它:https://www.python.org/。

6.编写并执行 python 代码

6.1 Python 文件

如前所述,python 是一种解释型语言,因此我们可以在扩展名为(.py)的文件中编写源代码,并使用'python'命令执行该文件。

让我们在任何文本编辑器中编写第一个 Python 文件helloworld.py

print("Hello, World!")

保存文件并在命令提示符或控制台中执行它。

$ python helloworld.py

#prints

Hello, World!

6.2 内联代码

Python 代码可以直接在命令行中运行,通常对于测试少量代码很有用。

要获取 python 控制台,请在 OS 控制台中键入命令'python'

$ python

Python3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:21:23) [MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.

>>> print("Hello, World!")

Hello, World!

7.学习 Python

7.1 语言基础

Python – 在 Sublime 编辑器中安装

Python – 注释

Python – 变量

Python – 数据类型

Python – 关键字

Python – 关键字

Python – 整数

Python – 字符串

Python – 列表

Python – 元组

7.2 字符串

Python – 字符串split()示例

Python – 字符串startswith()示例

Python – 字符串endswith()示例

7.3 集合

Python – 查找列表或数组中的最大值和最小值

Python – 查找最大 N 个(前 N 个)或最小 N 个项

Python- 将元组解包为变量

Python – 元组比较

Python – 列表与元组

Python – 解包元组 – 太多值无法解包

Python – Multidict示例

Python – OrderedDict示例

Python – 字典交集

Python – 优先级队列示例

7.4 杂项

Python – 读写 CSV 文件

Python – httplib2

在评论中向我发送有关 python 的问题。

学习愉快!

如何在 Sublime 编辑器中安装 Python 包

原文: https://howtodoinjava.com/python/install-python-sublime-editor/

了解在 Sublime 编辑器中安装 python 包的功能,例如自动完成功能,以及在 Sublime 编辑器本身中运行构建。

Table of Contents

Install Sublime Package Control
Install Package Python3
Verify Python Autocomplete and Build

安装 Sublime 包控制

首先下载用于 sublime 编辑器的包控制。

  1. 转到 URL: https://packagecontrol.io/installation#st3

    Sublime package control

    Sublime 包控制

  2. 现在记下 sublime 编辑器中安装包的文件夹的位置。 您可以通过单击“首选项 -> 浏览软件包”来找到位置。

    Browse Packages

    浏览包

  3. 单击“Package Control -> sublime-package”链接,保存从包控制网站下载的文件,并将其放在第二步的文件夹中。

  4. 现在,通过关闭 Sublime 编辑器来重新启动,然后再次将其打开。

  5. 要验证是否正确安装了包控制,请单击“首选项 -> 程序包控件”菜单项。 它应该打开包控制窗口。

    Package Control Menu

    包控制菜单

安装 Python3 包

  1. 现在要安装任何包支持,包括 Python 包,请转到“首选项 -> 包控制”,然后选择“安装包”

    Install Package Window

    安装包窗口

  2. 在打开的窗口中,键入“python”以过滤仅与 python 相关的包列表

    Select Python Package to Install

    选择 Python 包以安装

等待几秒钟,Python 包将安装到编辑器中。

验证 Python 自动完成和构建

要验证 python 支持,再次重新启动 IDE 。 创建一个名称为demo.py的文件。 键入一些简单的命令,例如print应该打开自动完成窗口。

现在,键入简单的 helloworld 代码,然后在键盘中输入CTRL + B。 它将在底部窗格中打开输出输出窗口,并且将在demo.py文件中显示命令的构建输出。

Sublime Build Output

Sublime 构建输出

现在,您可以使用 sublime 编辑器创建和构建 python 程序了。

学习愉快!

Python – 注释

原文: https://howtodoinjava.com/python/python-comments/

在 Python(或任何其他编程语言)中,注释用于解释源代码。 注释描述了代码,这有助于将来维护应用。

# prints 4
print(2 + 2)

print(2 + 3)	# prints 5

"""
prints the sum of
two numbers which are 2 and 2
"""
print(2 + 2)

Python 中的注释类型

Python 支持编写简单的单行注释以及多行注释。

单行注释

一个简单的单行注释将以哈希(#)字符开头。 Python 运行时会忽略在#字符之后编写的任何文本,并将其视为注释。

# This is a simple comment
print(2 + 2)

print(2 + 3)	# prints 5

多行注释

Python 没有什么特别的东西可以写多行注释。 要编写它,我们可以编写多个单行注释

我推荐这种形式的注释。

# This statement
# prints the sum of
# two numbers which are 2 and 2
print(2 + 2)

我们可以利用未分配给变量的多行字符串字面值(使用三引号)。 Python 会忽略未分配给任何变量的字符串字面值,因此不会影响程序执行。

"""
This statement
prints the sum of
two numbers which are 2 and 2
"""
print(2 + 2)

将有关在 python 中编写注释的问题交给我。

学习愉快!

Python – 变量

原文: https://howtodoinjava.com/python/python-variables/

在 python 中了解变量,以声明局部变量和全局变量。 另外,了解 python 函数内部使用的全局关键字。

1.创建变量

1.1 简单赋值

Python 语言没有没有关键字来声明变量。 当我们首先为变量赋值时,会立即在适当位置创建一个变量。

i = 20
blogName = "howtodoinjava"

print(i)			# prints 20
print(blogName)		# prints howtodoinjava

可以使用单引号和双引号来创建字符串类型的变量。

author = 'Lokesh'
blogName = "howtodoinjava"

print(author)	# prints Lokesh
print(blogName)	# prints howtodoinjava

1.2 链式赋值

Python 还允许使用链式赋值,这使得可以将相同的值同时赋值给多个变量。

i = j = k = 20

print(i)			# prints 20
print(j)			# prints 20
print(k)			# prints 20

1.3 单行中的多个赋值

Python 允许您在一行中将值赋值给多个变量。

x, y, z = "A", "B", 100

print(x)	# prints A
print(y)	# prints B
print(z)	# prints 100

1.2 变量重新声明

由于变量不需要数据类型信息,因此我们可以毫无问题地重新赋值任何类型的新值。 在 Python 中,可以为变量赋值一种类型的值,然后在以后重新赋值其他类型的值。

index = 10
index = 20
index = "NA"

print(index)	# prints NA

2.命名约定

在 Python 中创建变量的规则是:

  • 变量名必须以字母或下划线字符开头。
  • 变量名不能以数字开头。
  • 变量名只能包含字母数字字符和下划线 (A-z, 0-9, and _ )
  • 变量名称区分大小写。 例如,名称,名称和名称是三个不同的变量。

注意: Python3 具有完全的 Unicode 支持,它也允许在变量名中使用 Unicode 字符。

3.局部变量与全局变量

3.1 创建局部变量和全局变量

在函数内部创建的变量称为局部变量

在函数外部创建的变量是全局变量。 全局变量可以被函数内部和外部的每个人使用。

x = 10		# global variable

def myfunc():
  y = 10	# local variable
  print("Sum of x and y = " + str(x + y))	# prints Sum of x and y = 20

myfunc()

print("Sum of x and y = " + str(x + y))		# NameError: name 'y' is not defined

3.2 局部变量限制在函数范围内

如果在函数内部创建具有相同名称的变量,则该变量将是局部变量,并且只能在函数内部使用。 具有相同名称的全局变量将保留原样,并具有原始值。

x = 10		# global variable

def myfunc():
  x = 20	# local variable
  print("x is " + str(x))		# prints x is 20

myfunc()

print("x is " + str(x))			# prints x is 10

3.3 global关键字

要在函数内部创建全局变量,我们可以使用global关键字。

x = 10		# global variable

def myfunc():
  global y 
  y = 10	# global variable created inside function
  print("Sum of x and y = " + str(x + y))	# prints Sum of x and y = 20

myfunc()

print("Sum of x and y = " + str(x + y))		# prints Sum of x and y = 20

将您与 python 变量有关的问题交给我。

学习愉快!

Python – 数据类型

原文: https://howtodoinjava.com/python/python-data-types/

数据类型定义变量的类型。 由于所有内容都是 Python 中的对象,因此数据类型实际上是类; 变量是类的实例。

在任何编程语言中,都可以对不同类型的数据类型执行不同的操作,其中某些数据类型与其他数据类型相同,而某些数据类型可能非常特定于该特定数据类型。

1. Python 中的内置数据类型

Python 默认具有以下内置数据类型。

类别 数据类型/类名
文本/字符串 str
数值 intfloatcomplex
列表 listtuplerange
映射 dict
集合 setfrozenset
布尔值 bool
二进制 bytesbytearraymemoryview

2.详细的数据类型

2.1 字符串

字符串可以定义为用单引号,双引号或三引号引起来的字符序列。 三引号(""")可用于编写多行字符串。

x = 'A'
y = "B"
z = """
	C
	"""

print(x)	# prints A
print(y)	# prints B
print(z)	# prints C

print(x + y)	# prints AB	- concatenation

print(x*2)		# prints AA - repeatition operator

name = str('john')	# Constructor

sumOfItems = str(100)	# type conversion from int to string

2.2 整数,浮点数,复数

这些是数字类型。 它们是在将数字分配给变量时创建的。

  • int保留不限长度的有符号整数。
  • float保留浮点精度数字,它们的精度最高为 15 个小数位。
  • complex – 复数包含实部和虚部。
x = 2					# int
x = int(2)				# int	

x = 2.5					# float
x = float(2.5)			# float	

x = 100+3j				# complex
x = complex(100+3j) 	# complex

2.3 列表,元组,范围

在 Python 中,列表是某些数据的有序序列,使用方括号([ ])和逗号(,)编写。 列表可以包含不同类型的数据。

切片运算符[:]可用于访问列表中的数据。

连接运算符(+)和重复运算符(*)的工作方式类似于str数据类型。

使用切片运算符可以将范围视为sublist,从list中取出。

元组list类似,但tuple是只读数据结构,我们无法修改元组项的大小和值。 另外,项目用括号(, )括起来。

randomList = [1, "one", 2, "two"]
print (randomList);  				# prints [1, 'one', 2, 'two']
print (randomList + randomList);  	# prints [1, 'one', 2, 'two', 1, 'one', 2, 'two']
print (randomList * 2);  			# prints [1, 'one', 2, 'two', 1, 'one', 2, 'two']

alphabets  = ["a", "b", "c", "d", "e", "f", "g", "h"]  

print (alphabets[3:]);  			# range - prints ['d', 'e', 'f', 'g', 'h']
print (alphabets[0:2]);  			# range - prints ['a', 'b']

randomTuple = (1, "one", 2, "two")
print (randomTuple[0:2]);  			# range - prints (1, 'one')

randomTuple[0] = 0		# TypeError: 'tuple' object does not support item assignment

2.4 字典

字典或字典是项的键值对的有序集合。 键可以保存任何原始数据类型,而值是任意的 Python 对象。

字典中的项目用逗号分隔并括在花括号{, }中。

charsMap = {1:'a', 2:'b', 3:'c', 4:'d'};   

print (charsMap); 		# prints {1: 'a', 2: 'b', 3: 'c', 4: 'd'}
print("1st entry is " + charsMap[1]);  # prints 1st entry is a
print (charsMap.keys());  		# prints dict_keys([1, 2, 3, 4])
print (charsMap.values());   	# prints dict_values(['a', 'b', 'c', 'd'])

2.5 集,frozenset

可以将 python 中的定义为大括号{, }内各种项目的无序集

的元素不能重复。 python 集的元素必须是不可变的

list不同,集合元素没有index。 这意味着我们只能遍历set的元素。

冻结集是正常集的不变形式。 这意味着我们无法删除任何项目或将其添加到冻结集中。

digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}   

print(digits)  			# prints {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

print(type(digits))  	# prints <class 'set'>

print("looping through the set elements ... ")  
for i in digits:  
    print(i)  			# prints 0 1 2 3 4 5 6 7 8 9 in new lines

digits.remove(0)	# allowed in normal set

print(digits)		# {1, 2, 3, 4, 5, 6, 7, 8, 9}

frozenSetOfDigits = frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})   

frozenSetOfDigits.remove(0)	# AttributeError: 'frozenset' object has no attribute 'remove'

2.6 布尔值

布尔值是两个常量对象FalseTrue。 它们用于表示真值。 在数字上下文中,它们的行为分别类似于整数 0 和 1。

x = True
y = False

print(x)	#True
print(y)	#False

print(bool(1))	#True
print(bool(0))	#False

2.7 字节,字节数组,内存视图

字节字节数组用于处理二进制数据。 内存视图使用缓冲区协议访问其他二进制对象的内存,而无需进行复制。

字节对象是单个字节的不变序列。 仅在处理与 ASCII 兼容的数据时,才应使用它们。

bytes字面值的语法与string字面值相同,只是添加了'b'前缀。

始终通过调用构造器bytearray()来创建bytearray对象。 这些是可变的对象。

x = b'char_data'
x = b"char_data"

y = bytearray(5)

z = memoryview(bytes(5))

print(x)	# b'char_data'
print(y)	# bytearray(b'\x00\x00\x00\x00\x00')
print(z)	# <memory at 0x014CE328>

3. type()函数

type()函数可用于获取任何对象的数据类型。

x = 5
print(type(x))			# <class 'int'>

y = 'howtodoinjava.com'
print(type(y))			# <class 'str'>

将您的问题留在我的评论中。

学习愉快!

参考: Python 文档

GSON – 将 JSON 数组解析为 Java 数组或列表

原文: https://howtodoinjava.com/gson/gson-parse-json-array/

了解如何使用 Google GSON 库将包含 json 数组的 JSON 反序列化或解析为 Java 数组或 Java 列表对象。

值得一提的是,JSON 仅具有数组数据类型。 Java 同时具有 – 数组和列表。

1.将 JSON 数组解析为根对象

[
    {
      "name": "Alex",
      "id": 1
    },
    {
      "name": "Brian",
      "id": 2
    },
    {
      "name": "Charles",
      "id": 3
    }
]

public class User 
{
	private long id;
	private String name;

	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + "]";
	}
}

1.1 对象数组

Java 程序将 json 数组作为根反序列化 – 为 Java 对象数组。

String userJson = "[{'name': 'Alex','id': 1}, "
				+ "{'name': 'Brian','id':2}, "
				+ "{'name': 'Charles','id': 3}]";

Gson gson = new Gson(); 

User[] userArray = gson.fromJson(userJson, User[].class);  

for(User user : userArray) {
	System.out.println(user);
}

程序输出。

User [id=1, name=Alex]
User [id=2, name=Brian]
User [id=3, name=Charles]

1.2 对象列表

Java 程序将 json 数组作为根反序列化 – 为 Java 对象列表。

import java.lang.reflect.Type;
import java.util.ArrayList;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

String userJson = "[{'name': 'Alex','id': 1}, "
				+ "{'name': 'Brian','id':2}, "
				+ "{'name': 'Charles','id': 3}]";

Gson gson = new Gson(); 

Type userListType = new TypeToken<ArrayList<User>>(){}.getType();

ArrayList<User> userArray = gson.fromJson(userJson, userListType);  

for(User user : userArray) {
	System.out.println(user);
}

程序输出:

User [id=1, name=Alex]
User [id=2, name=Brian]
User [id=3, name=Charles]

2.解析 JSON 数组作为成员

如果它们是非根对象,则 Gson 可以轻松地将 JSON 数组解析为成员。 我们可以按常规方式使用fromJson()方法,它将正确地将 json 数组解析为所需的 Java 数组或列表。

{
	"id" 	: 1,
	"name"	: "HR",
	"users" : [
	    {
	      "name": "Alex",
	      "id": 1
	    },
	    {
	      "name": "Brian",
	      "id": 2
	    },
	    {
	      "name": "Charles",
	      "id": 3
	    }
	]

2.1 成员数组

Java 程序将反序列化 json 数组作为成员对象 – 转换为 Java 对象数组作为成员字段。

public class Department 
{
	private long id;
	private String name;
	private User[] users;

	//Getters and Setters

	@Override
	public String toString() {
		return "Department [id=" + id + ", name=" + name + ", users=" + Arrays.toString(users) + "]";
	}
}

String departmentJson = "{'id' : 1, "
		+ "'name': 'HR',"
		+ "'users' : ["
			+ "{'name': 'Alex','id': 1}, "
			+ "{'name': 'Brian','id':2}, "
			+ "{'name': 'Charles','id': 3}]}";

Gson gson = new Gson(); 

Department department = gson.fromJson(departmentJson, Department.class);  

System.out.println(department);

程序输出:

Department [id=1, name=HR, 
	users=[User [id=1, name=Alex], 
		User [id=2, name=Brian], 
		User [id=3, name=Charles]]]

2.2 成员列表

Java 程序将 json 数组反序列化为成员对象 – 到 Java 对象列表的成员字段。

public class Department 
{
	private long id;
	private String name;
	private List<User> users;

	//Getters and Setters

	@Override
	public String toString() {
		return "Department [id=" + id + ", name=" + name + ", users=" + users + "]";
	}
}

String departmentJson = "{'id' : 1, "
		+ "'name': 'HR',"
		+ "'users' : ["
			+ "{'name': 'Alex','id': 1}, "
			+ "{'name': 'Brian','id':2}, "
			+ "{'name': 'Charles','id': 3}]}";

Gson gson = new Gson(); 

Department department = gson.fromJson(departmentJson, Department.class);  

System.out.println(department);

程序输出:

Department [id=1, name=HR, 
	users=[User [id=1, name=Alex], 
		User [id=2, name=Brian], 
		User [id=3, name=Charles]]]

向我提供有关 gson 将 JSON 数组解析为 Java 列表和数组的问题。

学习愉快!

参考文献:

GSON Github

Python – 关键字

原文: https://howtodoinjava.com/python/python-keywords/

Python 关键字是 python 编程语言的保留字。 这些关键字不能用于其他目的。

Python 中有 35 个关键字 - 下面列出了它们的用法。

and

A logical AND operator. Return True if both statements are True.

			x = (5 > 3 and 5 < 10)
			print(x)	# True

or

逻辑或运算符。 如果两个语句中的任何一个为True,则返回True。 如果两个语句都为假,则返回False

			x = (5 > 3 or 5 > 10)
			print(x)	# True

as

它用于创建别名。

			import calendar as c
			print(c.month_name[1])	#January

assert

它可以用于调试代码。 它会测试条件并返回True,否则产生AssertionError

		x = "hello"

		assert x == "goodbye", "x should be 'hello'"  # AssertionError

async

它用来声明一个函数为协程,就像@asyncio.coroutine装饰器所做的一样。

async def ping_server(ip):

await

它用于调用async协程。

		async def ping_local():
    		return await ping_server('192.168.1.1')

class

它用于创建一个类。

		class User:
		  name = "John"
		  age = 36

def

它用于创建或定义函数。

		def my_function():
		  print("Hello world !!")

		my_function()

del

它用于删除对象。 在 Python 中,所有事物都是对象,因此del关键字也可以用于删除变量,列表或列表的一部分,等等。

		x = "hello"

		del x

if

它用于创建条件语句,该条件语句仅在条件为True时才允许我们执行代码块。

		x = 5

		if x > 3:
		  print("it is true")

elif

它用于条件语句中,是else if的缩写。

		i = 5

		if i > 0:
	        print("Positive")
	    elif i == 0:
	        print("ZERO")
	    else:
	        print("Negative")

else

它决定如果if..else语句中的条件为False时该怎么办。

		i = 5

		if i > 0:
	        print("Positive")
	    else:
	        print("Negative")

也可以在try...except块中使用。

		x = 5

		try:
		    x > 10
		except:
		    print("Something went wrong")
		else:
		    print("Normally execute the code")

try

如果它包含任何错误,它将定义一个测试代码块。

except

如果try块引发错误,它将定义要运行的代码块。

		try:
		    x > 3
		except:
		    print("Something went wrong")

finally

它定义了一个代码块,无论try块是否引发错误,该代码块都将执行。

		try:
		    x > 3
		except:
		    print("Something went wrong")
		finally:
			 print("I will always get executed")

raise

它用于手动引发异常。

		x = "hello"

		if not type(x) is int:
		  	raise TypeError("Only integers are allowed")

False

它是一个布尔值,与 0 相同。

True

它是一个布尔值,与 1 相同。

for

它用于创建for循环。 for循环可用于遍历序列(如列表,元组等)。

		for x in range(1, 9):
  			print(x)

while

它用于创建while循环。 循环继续进行,直到条件语句为假。

		x = 0

		while x < 9:
		  	print(x)
		  	x = x + 1

break

它用于中断for循环或while循环。

		i = 1 

		while i < 9:
		  	print(i)
		  	if i == 3:
		    	break
		  	i += 1

continue

它用于在for循环(或while循环)中结束当前迭代,并继续进行下一个迭代。

		for i in range(9):
		  	if i == 3:
		    	continue
		  	print(i)

import

它用于导入模块。

import datetime

from

它仅用于从模块中导入指定的节。

from datetime import time

global

它用于从非全局范围创建全局变量,例如在函数内部。

		def myfunction():
		  	global x
		  	x = "hello"

in

1. 它用于检查序列(列表,范围,字符串等)中是否存在值。

2. 它也用于在for循环中遍历序列。

		fruits = ["apple", "banana", "cherry"]

		if "banana" in fruits:
		  	print("yes")

		for x in fruits:
  			print(x)

is

它用于测试两个变量是否引用同一对象。

		a = ["apple", "banana", "cherry"]
		b = ["apple", "banana", "cherry"]
		c = a

		print(a is b)	# False
		print(a is c)	# True

lambda

它用于创建小的匿名函数。 它们可以接受任意数量的参数,但只能有一个表达式。

		x = lambda a, b, c : a + b + c

		print(x(5, 6, 2))

None

它用于定义一个“空”值,或者根本没有值。 None与 0,False或空字符串不同。

None是它自己的数据类型(NoneType),并且只有None可以是None

		x = None

		if x:
		  print("Do you think None is True")
		else:
		  print("None is not True...")		# Prints this statement

nonlocal

它用于声明变量不是局部变量。 它用于在嵌套函数内部使用变量,其中变量不应属于内部函数。

		def myfunc1():
		  	x = "John"
		  	def myfunc2():
		    	nonlocal x
		    	x = "hello"
		  	myfunc2()
		  	return x

		print(myfunc1())

not

它是一个逻辑运算符,并反转TrueFalse的值。

		x = False

		print(not x)	# True

pass

它用作将来代码的占位符。 当执行pass语句时,什么也不会发生,但是当不允许使用空代码时,可以避免出现错误。循环,函数定义,类定义或if语句中不允许使用空代码。

for x in [0, 1, 2]:
  			pass

return

它用于退出一个函数并返回一个值。

def myfunction():
  			return 3+3

as

用于简化异常处理

yield

用于结束一个函数,返回一个生成器

学习愉快!

参考: W3School

Python – 字符串

原文: https://howtodoinjava.com/python/python-strings/

在 Python 中,string字面值是:

  • 代表 Unicode 字符的字节数组
  • 用单引号或双引号引起来
  • 无限长度
str = 'hello world'

str = "hello world"

使用三个单引号或三个双引号创建多行字符串

str = '''Say hello
		to python
		programming'''

str = """Say hello
		to python
		programming"""

Python 没有字符数据类型,单个字符就是长度为 1 的字符串。

2.子串或切片

通过使用切片语法,我们可以获得一系列字符。 索引从零开始。

str[m:n]从位置 2(包括)到 5(不包括)返回字符串。

str = 'hello world'

print(str[2:5])	# llo

负切片从末尾返回子字符串。

str = 'hello world'

print(str[-5:-2])	# wor

str[-m:-n]将字符串从位置 -5(不包括)返回到 -2(包括)。

3.字符串作为数组

在 python 中,字符串表现为数组。 方括号可用于访问字符串的元素。

str = 'hello world'

print(str[0])	# h
print(str[1])	# e
print(str[2])	# l

print(str[20])	# IndexError: string index out of range

4.字符串长度

len()函数返回字符串的长度:

str = 'hello world'

print(len(str))	# 11

5.字符串格式化

要在 python 中格式化字符串,请在所需位置在字符串中使用占位符{ }。 将参数传递给format()函数以使用值格式化字符串。

我们可以在占位符中传递参数位置(从零开始)。

age = 36
name = 'Lokesh'

txt = "My name is {} and my age is {}"

print(txt.format(name, age))	# My name is Lokesh and my age is 36

txt = "My age is {1} and the name is {0}"

print(txt.format(name, age))	# My age is 36 and the name is Lokesh

6.字符串方法

6.1 capitalize()

它返回一个字符串,其中给定字符串的第一个字符被转换为大写。 当第一个字符为非字母时,它将返回相同的字符串。

name = 'lokesh gupta'

print( name.capitalize() )	# Lokesh gupta

txt = '38 yrs old lokesh gupta'

print( txt.capitalize() )	# 38 yrs old lokesh gupta

6.2 casefold()

它返回一个字符串,其中所有字符均为给定字符串的小写字母。

txt = 'My Name is Lokesh Gupta'

print( txt.casefold() )	# my name is lokesh gupta

6.3 center()

使用指定的字符(默认为空格)作为填充字符,使字符串居中对齐。

在给定的示例中,输出总共需要 20 个字符,而“ hello world”位于其中。

txt = "hello world"

x = txt.center(20)

print(x)	# '    hello world     '

6.4 count()

它返回指定值出现在字符串中的次数。 它有两种形式:

count(value) - 要在字符串中搜索的value

count(value, start, end) - 要在字符串中搜索的value,其中搜索起始于start位置,直到end位置。

txt = "hello world"

print( txt.count("o") )			# 2

print( txt.count("o", 4, 7) )	# 1

6.5 encode()

它使用指定的编码对字符串进行编码。 如果未指定编码,将使用UTF-8

txt = "My name is åmber"

x = txt.encode()

print(x)	# b'My name is \xc3\xa5mber'

6.6 endswith()

如果字符串以指定值结尾,则返回True,否则返回False

txt = "hello world"

print( txt.endswith("world") )		# True

print( txt.endswith("planet") )		# False

6.7 expandtabs()

它将制表符大小设置为指定的空格数。

txt = "hello\tworld"

print( txt.expandtabs(2) )		# 'hello world'

print( txt.expandtabs(4) )		# 'hello   world'

print( txt.expandtabs(16) )		# 'hello           world'

6.8 find()

它查找指定值的第一次出现。 如果指定的值不在字符串中,则返回-1

find()index()方法相同,唯一的区别是,如果找不到该值,则index()方法会引发异常。

txt = "My name is Lokesh Gupta"

x = txt.find("e")

print(x)		# 6

6.9 format()

它格式化指定的字符串,并在字符串的占位符内插入参数值。

age = 36
name = 'Lokesh'

txt = "My name is {} and my age is {}"

print( txt.format(name, age) )	# My name is Lokesh and my age is 36

6.10 format_map()

它用于返回字典键的值,以格式化带有命名占位符的字符串。

params = {'name':'Lokesh Gupta', 'age':'38'} 

txt = "My name is {name} and age is {age}"

x = txt.format_map(params)

print(x)		# My name is Lokesh Gupta and age is 38

6.11 index()

  • 它在给定的字符串中查找指定值的第一次出现。
  • 如果找不到要搜索的值,则会引发异常。
txt = "My name is Lokesh Gupta"

x = txt.index("e")

print(x)		# 6

x = txt.index("z")	# ValueError: substring not found

6.12 isalnum()

它检查字母数字字符串。 如果所有字符均为字母数字,表示字母(a-zA-Z)和数字(0-9),则返回True

print("LokeshGupta".isalnum())		# True

print("Lokesh Gupta".isalnum())		# False - Contains space

6.13 isalpha()

如果所有字符都是字母,则返回True,表示字母(a-zA-Z)

print("LokeshGupta".isalpha())			# True

print("Lokesh Gupta".isalpha())			# False - Contains space

print("LokeshGupta38".isalpha())		# False - Contains numbers

6.14 isdecimal()

如果所有字符均为十进制(0-9),则返回代码。 否则返回False

print("LokeshGupta".isdecimal())	# False

print("12345".isdecimal())			# True

print("123.45".isdecimal())			# False - Contains 'point'

print("1234 5678".isdecimal())		# False - Contains space

6.15 isdigit()

如果所有字符都是数字,则返回True,否则返回False。 指数也被认为是数字。

print("LokeshGupta".isdigit())		# False

print("12345".isdigit())			# True

print("123.45".isdigit())			# False - contains decimal point

print("1234\u00B2".isdigit())		# True - unicode for square 2

6.16 isidentifier()

如果字符串是有效的标识符,则返回True,否则返回False

有效的标识符仅包含字母数字字母(a-z)(0-9)或下划线( _ )。 它不能以数字开头或包含任何空格。

print( "Lokesh_Gupta_38".isidentifier() )		# True

print( "38_Lokesh_Gupta".isidentifier() )		# False - Start with number

print( "_Lokesh_Gupta".isidentifier() )			# True

print( "Lokesh Gupta 38".isidentifier() )		# False - Contain spaces

6.17 islower()

如果所有字符均小写,则返回True,否则返回False。 不检查数字,符号和空格,仅检查字母字符。

print( "LokeshGupta".islower() )		# False

print( "lokeshgupta".islower() )		# True

print( "lokesh_gupta".islower() )		# True

print( "lokesh_gupta_38".islower() )	# True

6.18 isnumeric()

如果所有字符都是数字(0-9),则此方法返回True,否则返回False。 指数也被认为是数值。

print("LokeshGupta".isnumeric())	# False

print("12345".isnumeric())			# True

print("123.45".isnumeric())			# False - contains decimal point

print("1234\u00B2".isnumeric())		# True - unicode for square 2

6.19 isprintable()

如果所有字符均可打印,则返回True,否则返回 False。 不可打印字符用于指示某些格式化操作,例如:

  • 空格(被视为不可见的图形)
  • 回车
  • 制表
  • 换行
  • 分页符
  • 空字符
print("LokeshGupta".isprintable())		# True

print("Lokesh Gupta".isprintable())		# True

print("Lokesh\tGupta".isprintable())	# False

6.20 isspace()

如果字符串中的所有字符都是空格,则返回True,否则返回False

6.21 istitle()

如果文本中的所有单词均以大写字母开头,而其余单词均为小写字母(即标题大小写),则返回True。 否则False

print("Lokesh Gupta".istitle())		# True

print("Lokesh gupta".istitle())		# False

6.22 isupper()

如果所有字符均大写,则返回True,否则返回False。 不检查数字,符号和空格,仅检查字母字符。

print("LOKESHGUPTA".isupper())		# True

print("LOKESH GUPTA".isupper())		# True

print("Lokesh Gupta".isupper())		# False

6.23 join()

它以可迭代方式获取所有项目,并使用强制性指定的分隔符将它们连接为一个字符串。

myTuple = ("Lokesh", "Gupta", "38")

x = "#".join(myTuple)

print(x)	# Lokesh#Gupta#38

6.24 ljust()

此方法将使用指定的字符(默认为空格)作为填充字符使字符串左对齐。

txt = "lokesh"

x = txt.ljust(20, "-")

print(x)	# lokesh--------------

6.25 lower()

它返回一个字符串,其中所有字符均为小写。 符号和数字将被忽略。

txt = "Lokesh Gupta"

x = txt.lower()

print(x)	# lokesh gupta

6.26 lstrip()

它删除所有前导字符(默认为空格)。

txt = "#Lokesh Gupta"

x = txt.lstrip("#_,.")

print(x)	# Lokesh Gupta

6.27 maketrans()

它创建一个字符到其转换/替换的一对一映射。 当在translate()方法中使用时,此转换映射用于将字符替换为其映射的字符。

dict = {"a": "123", "b": "456", "c": "789"}

string = "abc"

print(string.maketrans(dict))	# {97: '123', 98: '456', 99: '789'}

6.28 partition()

它在给定的文本中搜索指定的字符串,并将该字符串拆分为包含三个元素的元组:

  • 第一个元素包含指定字符串之前的部分。
  • 第二个元素包含指定的字符串。
  • 第三个元素包含字符串后面的部分。
txt = "my name is lokesh gupta"

x = txt.partition("lokesh")

print(x)	# ('my name is ', 'lokesh', ' gupta')

print(x[0])	# my name is 
print(x[1])	# lokesh
print(x[2])	#  gupta

6.29 replace()

它将指定的短语替换为另一个指定的短语。 它有两种形式:

  • string.replace(oldvalue, newvalue)
  • string.replace(oldvalue, newvalue, count)count指定要替换的匹配次数。 默认为所有事件。
txt = "A A A A A"

x = txt.replace("A", "B")

print(x)	# B B B B B

x = txt.replace("A", "B", 2)

print(x)	# B B A A A

6.30 rfind()

它查找指定值的最后一次出现。 如果在给定的文本中找不到该值,则返回-1

txt = "my name is lokesh gupta"

x = txt.rfind("lokesh")		

print(x)		# 11

x = txt.rfind("amit")		

print(x)		# -1

6.31 rindex()

它查找指定值的最后一次出现,如果找不到该值,则会引发异常。

txt = "my name is lokesh gupta"

x = txt.rindex("lokesh")		

print(x)				# 11

x = txt.rindex("amit")	# ValueError: substring not found

6.32 rjust()

它将使用指定的字符(默认为空格)作为填充字符来右对齐字符串。

txt = "lokesh"

x = txt.rjust(20,"#")

print(x, "is my name")	# ##############lokesh is my name

6.33 rpartition()

它搜索指定字符串的最后一次出现,并将该字符串拆分为包含三个元素的元组。

  • 第一个元素包含指定字符串之前的部分。
  • 第二个元素包含指定的字符串。
  • 第三个元素包含字符串后面的部分。
txt = "my name is lokesh gupta"

x = txt.rpartition("lokesh")

print(x)	# ('my name is ', 'lokesh', ' gupta')

print(x[0])	# my name is 
print(x[1])	# lokesh
print(x[2])	#  gupta

6.34 rsplit()

它将字符串从右开始拆分为列表。

txt = "apple, banana, cherry"

x = txt.rsplit(", ")

print(x)	# ['apple', 'banana', 'cherry']

6.35 rstrip()

它删除所有结尾字符(字符串末尾的字符),空格是默认的结尾字符。

txt = "     lokesh     "

x = txt.rstrip()

print(x)	# '     lokesh'

6.36 split()

它将字符串拆分为列表。 您可以指定分隔符。 默认分隔符为空格。

txt = "my name is lokesh"

x = txt.split()

print(x)	# ['my', 'name', 'is', 'lokesh']

6.37 splitlines()

通过在换行符处进行拆分,它将字符串拆分为列表。

txt = "my name\nis lokesh"

x = txt.splitlines()

print(x)	# ['my name', 'is lokesh']

6.38 startswith()

如果字符串以指定值开头,则返回True,否则返回False。 字符串比较区分大小写。

txt = "my name is lokesh"

print( txt.startswith("my") )	# True

print( txt.startswith("My") )	# False

6.39 strip()

它将删除所有前导(开头的空格)和结尾(结尾的空格)字符(默认为空格)。

txt = "   my name is lokesh   "

print( txt.strip() )	# 'my name is lokesh'

6.40 swapcase()

它返回一个字符串,其中所有大写字母均为小写字母,反之亦然。

txt = "My Name Is Lokesh Gupta"

print( txt.swapcase() )	# mY nAME iS lOKESH gUPTA

6.41 title()

它返回一个字符串,其中每个单词的第一个字符均为大写。 如果单词开头包含数字或符号,则其后的第一个字母将转换为大写字母。

print( "lokesh gupta".title() )	# Lokesh Gupta

print( "38lokesh gupta".title() )	# 38Lokesh Gupta

print( "1\. lokesh gupta".title() )	# Lokesh Gupta

6.42 translate()

它需要转换表根据映射表替换/转换给定字符串中的字符。

translation = {97: None, 98: None, 99: 105}

string = "abcdef"	

print( string.translate(translation) )	# idef

6.43 upper()

它返回一个字符串,其中所有字符均大写。 符号和数字将被忽略。

txt = "lokesh gupta"

print( txt.upper() )	# LOKESH GUPTA

6.44 zfill()

它在字符串的开头添加零(0),直到达到指定的长度。

txt = "100"

x = txt.zfill(10)

print( 0000000100 )	# 0000000100

学习愉快!

Python – 列表

原文: https://howtodoinjava.com/python/python-lists/

在 Python 中,列表为:

  • 有序
  • 具有索引(索引从 0 开始)
  • 可变
  • 异构的(列表中的项目不必是同一类型)
  • 写为方括号之间的逗号分隔值列表
listOfSubjects = ['physics', 'chemistry', "mathematics"]

listOfIds = [0, 1, 2, 3, 4]

miscList = [0, 'one', 2, 'three']

1.访问列表项

要访问列表中的值,请使用切片语法或数组索引形式的方括号来获取单个项目或项目范围。

传递的索引值可以是正数或负数。 负索引值表示从列表末尾开始计数。

list [m : n]表示子列表从索引m(包括)开始,到索引n(不包括)结束。

  • 如果未提供m,则假定其值为零。
  • 如果未提供n,则选择范围直到列表的最后。
ids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print( ids[0] )			# 0

print( ids[1:5] )		# [1, 2, 3, 4]

print( ids[ : 3] )		# [0, 1, 2]

print( ids[7 : ] )		# [7, 8, 9]

print( ids[-8:-5] )		# [2, 3, 4]

2.列表

要更改列表中的特定项目,请使用其索引进行引用并分配一个新值。

charList =  ["a", "b", "c"]

charList [2] = "d"

print (charList)	# ['a', 'b', 'd']

3.迭代列表

我们可以使用for循环遍历列表项。

charList =  ["a", "b", "c"]

for x in charList:
	print(x)

# a
# b
# c

4.检查列表中是否存在项目

使用'in'关键字确定列表中是否存在指定的项目。

charList =  ["a", "b", "c"]

if "a" in charList:
	print("a is present")	# a is present

if "d" in charList:
	print("d is present")
else:
	print("d is NOT present")	# d is NOT present

5.查找列表的长度

使用len()函数查找给定列表的长度。

charList =  ["a", "b", "c"]

x = len (charList)

print (x)	# 3

6.添加项目

  • 要将项目添加到列表的末尾,请使用append(item)方法。
  • 要在特定索引位置添加项目,请使用insert(index, item)方法。 如果index大于索引长度,则将项目添加到列表的末尾。
charList =  ["a", "b", "c"]

charList.append("d")	
charList.append("e")

print (charList)		# ['a', 'b', 'c', 'd', 'e']

charList.insert(5, "f")

print (charList)		# ['a', 'b', 'c', 'd', 'e', 'f']

charList.insert(10, "h")	# No error 

print (charList)	# ['a', 'b', 'c', 'd', 'e', 'f', 'h']

7.移除项目

要从列表中删除一项,请使用以下四种方式之一,即remove()pop()clear()del关键字。

7.1 remove()

它会通过其值删除指定的项目。

charList =  ["a", "b", "c"]

charList.remove("c")	

print (charList)		# ['a', 'b']

7.2 pop()

它将通过索引删除指定的项目。 如果未提供index,它将从列表中删除最后一项。

charList =  ["a", "b", "c", "d"]

charList.pop()			# removes 'd' - last item

print (charList)		# ['a', 'b', 'c']

charList.pop(1)			# removes 'b'

print (charList)		# ['a', 'c']

7.3 clear()

它清空列表。

charList =  ["a", "b", "c", "d"]

charList.clear()	

print (charList)		# []

7.4 del关键字

它可以用于通过索引从列表中删除某项。 我们也可以使用它来删除整个列表

charList =  ["a", "b", "c", "d"]

del charList[0]	

print (charList)		# ['b', 'c', 'd']

del charList

print (charList)		# NameError: name 'charList' is not defined

8.连接两个列表

我们可以使用"+"运算符或extend()函数在 Python 中连接两个给定列表。

charList = ["a", "b", "c"]
numList	= [1, 2, 3]

list1 = charList + numList

print (list1)	# ['a', 'b', 'c', 1, 2, 3]

charList.extend(numList)

print (charList)	# ['a', 'b', 'c', 1, 2, 3]

9. Python 列表方法

9.1 append()

在列表的末尾添加一个元素。

charList =  ["a", "b", "c"]

charList.append("d")

print (charList)	# ["a", "b", "c", "d"]

9.2 clear()

从列表中删除所有元素。

charList =  ["a", "b", "c"]

charList.clear()

print (charList)	# []

9.3 copy()

返回列表的副本。

charList =  ["a", "b", "c"]

newList = charList.copy()

print (newList)	# ["a", "b", "c"]

9.4 count()

返回具有指定值的元素数。

charList =  ["a", "b", "c"]

x = charList.count('a')

print (x)	# 1

9.5 extend()

将列表的元素添加到当前列表的末尾。

charList = ["a", "b", "c"]
numList	= [1, 2, 3]

charList.extend(numList)

print (charList)	# ['a', 'b', 'c', 1, 2, 3]

9.6 index()

返回具有指定值的第一个元素的索引。

charList =  ["a", "b", "c"]

x = charList.index('a')

print (x)	# 0

9.7 insert()

在指定位置添加元素。

charList =  ["a", "b", "c"]

charList.insert(3, 'd')

print (charList)	# ['a', 'b', 'c', 'd']

9.8 pop()

删除指定位置或列表末尾的元素。

charList =  ["a", "b", "c", "d"]

charList.pop()			# removes 'd' - last item

print (charList)		# ['a', 'b', 'c']

charList.pop(1)			# removes 'b'

print (charList)		# ['a', 'c']

9.9 remove()

删除具有指定值的项目。

charList =  ["a", "b", "c", "d"]

charList.remove('d')

print (charList)		# ['a', 'b', 'c']

9.10 reverse()

反转列表中项目的顺序。

charList =  ["a", "b", "c", "d"]

charList.reverse()

print (charList)		# ['d', 'c', 'b', 'a']

9.11 sort()

默认情况下,以升序对给定列表进行排序。

charList =  ["a", "c", "b", "d"]

charList.sort()

print (charList)		# ["a", "b", "c", "d"]

学习愉快!

阅读更多:

Python – 列表与元组

Python – 元组

原文: https://howtodoinjava.com/python/python-tuples/

在 Pyhton 中,元组list相似,但它是不可变的,并用可选的圆括号编写。

元组是:

  • 不变的
  • 有序
  • 异构
  • 带有索引(从零开始)
  • 带圆括号(可选,但建议)
  • 在迭代过程中更快,因为它是不可变的

元组对于创建对象很有用,该对象通常包含相关信息,例如: 员工信息。 换句话说,元组可以让我们将相关信息“块”在一起,并将其用作单个事物。

1.创建一个元组

元组中的元素用圆括号括起来,并用逗号分隔。 元组可以包含任意数量的不同类型的项。

Tuple = (item1, item2, item3)
tuple1 = ()		# empty tuple

tuple2 = (1, "2", 3.0)

tuple3 = 1, "2", 3.0

1.1 一个元素的元组

如果元组仅包含一个元素,则不将其视为元组。 它应该以逗号结尾,以指定解释器为元组。

tupleWithOneElement = ("hello", )	# Notice trailing comma

1.2 嵌套元组

一个包含另一个元组作为元素的元组,称为嵌套元组。

nestedTuple = ("hello", ("python", "world"))

2.访问元组项

我们可以使用方括号内的索引访问元组项。

  • 正索引从元组的开始开始计数。
  • 负索引从元组的末尾开始计数。
  • 索引的范围将创建带有指定项目的新元组(称为切片)。
  • 范围[m:n]表示从位置m包括)到位置n排除)。
  • 使用双索引访问嵌套元组的元素。
Tuple = ("a", "b", "c", "d", "e", "f")

print(Tuple[0])		# a
print(Tuple[1])		# b

print(Tuple[-1])	# f
print(Tuple[-2])	# e

print(Tuple[0:3])	# ('a', 'b', 'c')
print(Tuple[-3:-1])	# ('d', 'e')

Tuple = ("a", "b", "c", ("d", "e", "f"))

print(Tuple[3])			# ('d', 'e', 'f')
print(Tuple[3][0])		# d
print(Tuple[3][0:2])	# ('d', 'e')

3.遍历元组

使用for循环,以遍历元组项。

Tuple = ("a", "b", "c")

for x in Tuple:
  print(x)

4.检查项目是否存在于元组中

要检查元组是否包含给定元素,可以使用'in'关键字和'not in'关键字。

Tuple = ("a", "b", "c", "d", "e", "f")

if "a" in Tuple:
  print("Yes, 'a' is present")		# Yes, 'a' is present

if "p" not in Tuple:
  print("No, 'p' is not present")	# No, 'p' is not present

5.对元组进行排序

使用内置的sorted()语言方法对元组中的元素进行排序。

Tuple = ("a", "c", "b", "d", "f", "e")

sortedTuple = sorted(Tuple)

print (sortedTuple)	# ("a", "b", "c", "d", "e", "f")

6.元组的重复和连接

要重复一个元组的所有元素,请将其乘以所需因子N

Tuple = ("a", "b") 

repeatedTuple = Tuple * 3

print (repeatedTuple)	# ('a', 'b', 'a', 'b', 'a', 'b')

要连接/连接两个或多个元组,我们可以使用+运算符。

Tuple1 = ("a", "b", "c") 
Tuple2 = ("d", "e", "f")

joinedTuple = Tuple1 + Tuple2

print (joinedTuple)	# ("a", "b", "c", "d", "e", "f")

7.打包和解包元组

打包是指我们将一组值分配给变量的操作。 在打包时,元组中的所有项目都分配给一个元组对象。

在下面的示例中,所有三个值都分配给变量Tuple

Tuple = ("a", "b", "c")

解包称为将元组变量分配给另一个元组,并将元组中的各个项目分配给各个变量的操作。

在给定的示例中,将元组解包为新的元组,并将值a, b, c – 分配给变量x, y, z

Tuple = ("a", "b", "c")		# Packing

(x, y, z) = Tuple

print (x)	# a
print (y)	# b
print (z)	# c

在解包期间,分配左侧的元组中的元素数必须等于右侧的数量。

Tuple = ("a", "b", "c")		# Packing

(x, y, z) = Tuple		# ValueError: too many values to unpack (expected 2)

(x, y, z, i) =	Tuple 	# ValueError: not enough values to unpack (expected 4, got 3)

8.命名元组

Python 提供了一种称为namedtuple()特殊类型的函数,该函数来自collection模块。

命名元组类似于字典,但是支持从值和键访问,其中字典仅支持按键访问。

import collections

Record = collections.namedtuple('Record', ['id', 'name', 'date'])

R1 = Record('1', 'My Record', '12/12/2020')

#Accessing using index
print("Record id is:", R1[0])		# Record id is: 1

# Accessing using key	
print("Record name is:", R1.name)	# Record name is: My Record

9. Python 元组方法

9.1 any

如果该元组中至少存在一个元素,则返回True;如果该元组为空,则返回False

print( any( () ) )		# Empty tuple - False
print( any( (1,) ) )	# One element tuple - True
print( any( (1, 2) ) )	# Regular tuple - True

9.2 min()

返回元组的最小元素(整数)。

Tuple = (4, 1, 2, 6, 9)

print( min( Tuple ) )	# 1

9.3 max()

返回元组的最大元素(整数)。

Tuple = (4, 1, 2, 6, 9)

print( max( Tuple ) )	# 9

9.4 len()

返回元组的长度。

Tuple = (4, 1, 2, 6, 9)

print( len( Tuple ) )	# 5

9.5 sum()

返回元组的所有元素(整数)的总和。

Tuple = (4, 1, 2, 6, 9)

print( sum( Tuple ) )	# 22

10.总结

如上所述,元组不可变,有序和索引的异构元素集合。 它写有或没有圆括号。

元组对于创建对象类型和实例非常有用。

元组支持类似于list类型的操作,只有我们不能更改元组元素。

学习愉快!

阅读更多 :

Python – 元组比较
Python – 列表与元组

Python max()min()– 在列表或数组中查找最大值和最小值

原文: https://howtodoinjava.com/python/max-min/

Python 示例使用max()min()方法在可比较元素的集合(例如列表,集合或数组)中找到最大(或最小)的项目。

1. Python max()函数

max()函数用于:

  1. 计算在其参数中传递的最大值。
  2. 如果字符串作为参数传递,则在字典上的最大值。

1.1 查找数组中的最大整数

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

>>> max( nums )

42		#Max value in array

1.2 查找数组中的最大字符串

>>> blogName = ["how","to","do","in","java"]

>>> max( blogName )

'to'		#Largest value in array

1.3 查找最大键或值

有点复杂的结构。

>>> prices = {
   'how': 45.23,
   'to': 612.78,
   'do': 205.55,
   'in': 37.20,
   'java': 10.75
}

>>> max( prices.values() )
612.78

>>> max( prices.keys() ) 	#or max( prices ). Default is keys().
'to'

2. Python min()函数

此函数用于:

  1. 计算在其参数中传递的最小值。
  2. 如果字符串作为参数传递,则在字典上的最小值。

2.1 查找数组中的最小整数

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

>>> min( nums )

-4		#Min value in array

2.2 查找数组中的最小字符串

>>> blogName = ["how","to","do","in","java"]

>>> min( blogName )

'do'		#Smallest value in array

2.3 查找最小键或值

A little complex structure.

>>> prices = {
   'how': 45.23,
   'to': 612.78,
   'do': 205.55,
   'in': 37.20,
   'java': 10.75
}

>>> min( prices.values() )
10.75

>>> min( prices.keys() ) 	#or min( prices ). Default is keys().
'do'

学习愉快!

Python 找到 N 个最大的或最小的项目

原文: https://howtodoinjava.com/python/find-largest-smallest-items/

Python 示例使用heapq库中的nlargest()nsmallest()函数从元素集合中找到最大(或最小)的 N 个元素。

1.使用heapq模块的nlargest()和 nsmallest()

Python heapq模块可用于从集合中找到 N 个最大或最小的项目。 它具有两个辅助函数:

  1. nlargest()
  2. nsmallest()

1.1 在简单的可迭代对象中查找项目

>>> import heapq

>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

print(heapq.nlargest(3, nums))  
>>> [42, 37, 23]

print(heapq.nsmallest(3, nums)) 
>>> [-4, 1, 2]

1.2 查找复杂的可迭代项

>>> portfolio = 
[
   {'name': 'IBM', 'shares': 100, 'price': 91.1},
   {'name': 'AAPL', 'shares': 50, 'price': 543.22},
   {'name': 'FB', 'shares': 200, 'price': 21.09},
   {'name': 'HPQ', 'shares': 35, 'price': 31.75},
   {'name': 'YHOO', 'shares': 45, 'price': 16.35},
   {'name': 'ACME', 'shares': 75, 'price': 115.65}
]

>>> cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
>> cheap
>>> [
		{'price': 16.35, 'name': 'YHOO', 'shares': 45}, 
		{'price': 21.09, 'name': 'FB', 'shares': 200}, 
		{'price': 31.75, 'name': 'HPQ', 'shares': 35}
	]

>>> expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
>>> expensive
>>> [
		{'price': 543.22, 'name': 'AAPL', 'shares': 50}, 
		{'price': 115.65, 'name': 'ACME', 'shares': 75}, 
		{'price': 91.1, 'name': 'IBM', 'shares': 100}
	]

如果您只是想查找单个最小或最大项目(N = 1),则使用min()max()函数更快。

学习愉快!

Python 读写 CSV 文件

原文: https://howtodoinjava.com/python/python-read-write-csv-files/

学习在 Python 中使用 CSV 文件。 CSV(逗号分隔值)格式是在电子表格和数据库中使用的非常流行的导入和导出格式。 Python 语言包含csv模块,该模块具有可以读写 CSV 格式的类。

Table of Contents

Reading CSV file with csv.reader()
Reading CSV file with csv.DictReader
Writing CSV file using csv.writer()
Quoting
CSV Dialects
Custom CSV Dialects

使用csv.reader()读取 CSV 文件

csv.reader()方法返回一个读取器对象,该对象将遍历给定 CSV 文件中的行。

假设我们有以下包含数字的numbers.csv文件:

6,5,3,9,8,6,7

以下 python 脚本从此 CSV 文件读取数据。

#!/usr/bin/python3

import csv

f = open('numbers.csv', 'r')

with f:

    reader = csv.reader(f)
    for row in reader:
        print(row)

在上面的代码示例中,我们打开了numbers.csv进行读取,并使用csv.reader()方法加载了数据。

现在,假设 CSV 文件将使用不同的定界符。 (严格来说,这不是 CSV 文件,但是这种做法很常见。)例如,我们有以下items.csv文件,其中的元素由竖线字符(|)分隔:

pen|table|keyboard

以下脚本从items.csv文件读取数据。

#!/usr/bin/python3

import csv

f = open('items.csv', 'r')

with f:

    reader = csv.reader(f, delimiter="|")
    for row in reader:
        for e in row:
            print(e)

我们在csv.reader()方法中使用delimiter参数指定新的分隔字符。

使用csv.DictReader读取 CSV 文件

csv.DictReader类的操作类似于常规阅读器,但是将读取的信息映射到字典中。

字典的键可以使用fieldnames参数传入,也可以从 CSV 文件的第一行推断出来。

我们具有以下values.csv文件:

min, avg, max
1, 5.5, 10

第一行代表字典的键,第二行代表值。

#!/usr/bin/python3

import csv

f = open('values.csv', 'r')

with f:

    reader = csv.DictReader(f)
    for row in reader:
        print(row)

上面的 python 脚本使用csv.DictReadervalues.csv文件读取值。

这是示例的输出。

$ ./read_csv3.py 
{' max': ' 10', 'min': '1', ' avg': ' 5.5'}

使用csv.writer()写入 CSV 文件

csv.writer()方法返回一个写入器对象,该对象负责将用户数据转换为给定文件状对象上的定界字符串。

#!/usr/bin/python3

import csv

nms = [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]

f = open('numbers2.csv', 'w')

with f:

    writer = csv.writer(f)
    for row in nms:
        writer.writerow(row)

该脚本将数字写入numbers2.csv文件。 writerow()方法将一行数据写入指定的文件。

该脚本生成以下文件(numbers2.csv):

1,2,3,4,5,6
7,8,9,10,11,12

一次写入所有数据是可能的。 writerows()方法将所有给定的行写入 CSV 文件。

下一个代码示例将 Python 列表写入numbers3.csv文件。 该脚本将三行数字写入文件。

#!/usr/bin/python3

import csv

nms = [[1, 2, 3], [7, 8, 9], [10, 11, 12]]

f = open('numbers3.csv', 'w')

with f:

    writer = csv.writer(f)
    writer.writerows(nms)

运行上述程序时,以下输出将写入numbers3.csv文件:

1,2,3
7,8,9
10,11,12

引用

可以在 CSV 文件中引用单词。 Python CSV 模块中有四种不同的引用模式

  • QUOTE_ALL — 引用所有字段
  • QUOTE_MINIMAL - 仅引用包含特殊字符的字段
  • QUOTE_NONNUMERIC — 引用所有非数字字段
  • QUOTE_NONE — 不引用字段

在下一个示例中,我们将三行写入items2.csv文件。 所有非数字字段均用引号引起来。

#!/usr/bin/python3

import csv

f = open('items2.csv', 'w')

with f:

    writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)
    writer.writerows((["coins", 3], ["pens", 2], ["bottles", 7]))

该程序将创建以下items2.csv文件。 引用项目名称,不引用数字表示的数量。

"coins",3
"pens",2
"bottles",7

CSV 方言

尽管 CSV 格式是一种非常简单的格式,但还是有许多差异,例如不同的定界符,换行或引号字符。 因此,有不同的 CSV 方言可用。

下一个代码示例将打印可用的方言及其特征。

#!/usr/bin/python3

import csv

names = csv.list_dialects()

for name in names:

    print(name)

    dialect = csv.get_dialect(name)

    print(repr(dialect.delimiter), end=" ")
    print(dialect.doublequote, end=" ")
    print(dialect.escapechar, end=" ")
    print(repr(dialect.lineterminator), end=" ")
    print(dialect.quotechar, end=" ")
    print(dialect.quoting, end=" ")
    print(dialect.skipinitialspace, end=" ")
    print(dialect.strict)

csv.list_dialects()返回方言名称的列表,csv.get_dialect()方法返回与方言名称关联的方言。

$ ./dialects.py 
excel
',' 1 None '\r\n' " 0 0 0
excel-tab
'\t' 1 None '\r\n' " 0 0 0
unix
',' 1 None '\n' " 1 0 0

程序将打印此输出。 内置三种方言:excelexcel-tabunix

自定义 CSV 方言

在本教程的最后一个示例中,我们将创建一个自定义方言。 使用csv.register_dialect()方法创建自定义方言。

#!/usr/bin/python3

import csv

csv.register_dialect("hashes", delimiter="#")

f = open('items3.csv', 'w')

with f:

    writer = csv.writer(f, dialect="hashes")
    writer.writerow(("pencils", 2)) 
    writer.writerow(("plates", 1))
    writer.writerow(("books", 4))

该程序使用(#)字符作为分隔符。 使用csv.writer()方法中的dialect选项指定方言。

程序生成以下文件(items3.csv):

pencils#2
plates#1
books#4

在本教程中,我们探索了 Python csv模块,并通过了一些在 python 中读写 CSV 文件的示例。

学习愉快!

该教程由运行 zetcode.com 的 Jan Bodnar 编写。

Python httplib2 – HTTP GET 和 POST 示例

原文: https://howtodoinjava.com/python/httplib2-http-get-post-requests/

学习使用 Python httplib2模块。 超文本传输​​协议(HTTP)是用于分布式协作超媒体信息系统的应用协议。 HTTP 是万维网数据通信的基础。

Python httplib2模块提供了用于通过 HTTP 访问 Web 资源的方法。 它支持许多功能,例如 HTTP 和 HTTPS,认证,缓存,重定向和压缩。

$ service nginx status
 * nginx is running

我们在本地主机上运行 nginx Web 服务器。 我们的一些示例将连接到本地运行的 nginx 服务器上的 PHP 脚本。

Table of Contents

Check httplib2 Library Version
Use httplib2 to Read Web Page
Send HTTP HEAD Request
Send HTTP GET Request
Send HTTP POST Request
Send User Agent Information
Add Username/Password to Request

检查httplib2库版本

第一个程序打印库的版本,其版权和文档字符串。

#!/usr/bin/python3

import httplib2

print(httplib2.__version__)
print(httplib2.__copyright__)
print(httplib2.__doc__)

httplib2.__version__给出httplib2库的版本,httplib2.__copyright__给出其版权,httplib2.__doc__给出其文档字符串。

$ ./version.py 
0.8
Copyright 2006, Joe Gregorio

httplib2

A caching http interface that supports ETags and gzip
to conserve bandwidth.

Requires Python3.0 or later

Changelog:
2009-05-28, Pilgrim: ported to Python3
2007-08-18, Rick: Modified so it's able to use a socks proxy if needed.

这是示例的示例输出。

使用httplib2读取网页

在下面的示例中,我们显示了如何从名为 www.something.com 的网站上获取 HTML 内容。

#!/usr/bin/python3

import httplib2

http = httplib2.Http()
content = http.request("http://www.something.com")[1]

print(content.decode())

使用httplib2.HTTP()创建一个 HTTP 客户端。 使用request()方法创建一个新的 HTTP 请求; 默认情况下,它是一个 GET 请求。 返回值是响应和内容的元组。

$ ./get_content.py 
<html><head><title>Something.</title></head>
<body>Something.</body>
</html>

这是示例的输出。

剥离 HTML 标签

以下程序获取一个小型网页,并剥离其 HTML 标签。

#!/usr/bin/python3

import httplib2
import re

http = httplib2.Http()
content = http.request("http://www.something.com")[1]

stripped = re.sub('<[^<]+?>', '', content.decode())
print(stripped)

一个简单的正则表达式用于剥离 HTML 标记。 请注意,我们正在剥离数据,我们没有对其进行清理。 (这是两件事。)

$ ./strip_tags.py 
Something.
Something.

该脚本会打印网页的标题和内容。

检查响应状态

响应对象包含status属性,该属性提供响应的状态代码。

#!/usr/bin/python3

import httplib2

http = httplib2.Http()

resp = http.request("http://www.something.com")[0]
print(resp.status)

resp = http.request("http://www.something.com/news/")[0]
print(resp.status)

我们使用request()方法执行两个 HTTP 请求,并检查返回的状态。

$ ./get_status.py 
200
404

200 是成功 HTTP 请求的标准响应,而 404 则表明找不到所请求的资源。

发送 HTTP HEAD 请求

HTTP HEAD 方法检索文档标题。 标头由字段组成,包括日期,服务器,内容类型或上次修改时间。

#!/usr/bin/python3

import httplib2

http = httplib2.Http()

resp = http.request("http://www.something.com", "HEAD")[0]

print("Server: " + resp['server'])
print("Last modified: " + resp['last-modified'])
print("Content type: " + resp['content-type'])
print("Content length: " + resp['content-length'])

该示例打印服务器,www.something.com网页的上次修改时间,内容类型和内容长度。

$ ./do_head.py 
Server: Apache/2.4.12 (FreeBSD) OpenSSL/1.0.1l-freebsd mod_fastcgi/mod_fastcgi-SNAP-0910052141
Last modified: Mon, 25 Oct 1999 15:36:02 GMT
Content type: text/html
Content length: 72

这是程序的输出。 从输出中,我们可以看到该网页是由 FreeBSD 托管的 Apache Web 服务器交付的。 该文档的最后修改时间是 1999 年。网页是 HTML 文档,其长度为 72 个字节。

发送 HTTP GET 请求

HTTP GET 方法请求指定资源的表示形式。 对于此示例,我们还将使用greet.php脚本:

<?php

echo "Hello " . htmlspecialchars($_GET['name']);

?>

/usr/share/nginx/html/目录中,有此greet.php文件。 该脚本返回name变量的值,该值是从客户端检索到的。

htmlspecialchars()函数将特殊字符转换为 HTML 实体; 例如&&amp

#!/usr/bin/python3

import httplib2

http = httplib2.Http()
content = http.request("http://localhost/greet.php?name=Peter", 
                       method="GET")[1]

print(content.decode())

该脚本将带有值的变量发送到服务器上的 PHP 脚本。 该变量直接在 URL 中指定。

$ ./mget.py 
Hello Peter

这是示例的输出。

$ tail -1 /var/log/nginx/access.log
127.0.0.1 - - [21/Aug/2016:17:32:31 +0200] "GET /greet.php?name=Peter HTTP/1.1" 200 42 "-" 
"Python-httplib2/0.8 (gzip)"

我们检查了 nginx 访问日志。

发送 HTTP POST 请求

POST 请求方法请求 Web 服务器接受并存储请求消息正文中包含的数据。 上载文件或提交完整的 Web 表单时经常使用它。

<?php

echo "Hello " . htmlspecialchars($_POST['name']);

?>

在本地 Web 服务器上,我们有此target.php文件。 它只是将过帐的值打印回客户。

#!/usr/bin/python3

import httplib2
import urllib

http = httplib2.Http()

body = {'name': 'Peter'}

content = http.request("http://localhost/target.php", 
                       method="POST", 
                       headers={'Content-type': 'application/x-www-form-urlencoded'},
                       body=urllib.parse.urlencode(body) )[1]

print(content.decode())

脚本使用具有Peter值的name键发送请求。 数据使用urllib.parse.urlencode()方法进行编码,并在请求的正文中发送。

$ ./mpost.py 
Hello Peter

这是mpost.py脚本的输出。

$ tail -1 /var/log/nginx/access.log
127.0.0.1 - - [23/Aug/2016:12:21:07 +0200] "POST /target.php HTTP/1.1" 
    200 37 "-" "Python-httplib2/0.8 (gzip)"

使用 POST 方法时,不会在请求 URL 中发送该值。

发送用户代理信息

在本节中,我们指定用户代理的名称。

<?php 

echo $_SERVER['HTTP_USER_AGENT'];

?>

在 nginx 文档根目录中,我们有agent.php文件。 它返回用户代理的名称。

#!/usr/bin/python3

import httplib2

http = httplib2.Http()
content = http.request("http://localhost/agent.php", method="GET", 
                  headers={'user-agent': 'Python script'})[1]

print(content.decode())

该脚本为agent.php脚本创建一个简单的 GET 请求。 在headers词典中,我们指定用户代理。 这可以通过 PHP 脚本读取,并返回给客户端。

$ ./user_agent.py 
Python script

服务器使用我们随请求发送的代理名称进行了响应。

将用户名/密码添加到请求

客户端的add_credentials()方法设置用于领域的名称和密码。 安全领域是一种用于保护 Web 应用资源的机制。

$ sudo apt-get install apache2-utils
$ sudo htpasswd -c /etc/nginx/.htpasswd user7
New password: 
Re-type new password: 
Adding password for user user7

我们使用htpasswd工具创建用于基本 HTTP 认证的用户名和密码。

location /secure {

        auth_basic "Restricted Area";
        auth_basic_user_file /etc/nginx/.htpasswd;
}

在 nginx /etc/nginx/sites-available/default配置文件中,我们创建一个安全页面。 领域的名称是“禁区”。

<!DOCTYPE html>
<html lang="en">
<head>
<title>Secure page</title>
</head>

<body>

<p>
This is a secure page.
</p>

</body>

</html>

/usr/share/nginx/html/secure目录中,我们具有上面的 HTML 文件。

#!/usr/bin/python3

import httplib2

user = 'user7'
passwd = '7user'

http = httplib2.Http()
http.add_credentials(user, passwd)
content = http.request("http://localhost/secure/")[1]

print(content.decode())

该脚本连接到安全网页; 它提供访问该页面所需的用户名和密码。

$ ./credentials.py 
<!DOCTYPE html>
<html lang="en">
<head>
<title>Secure page</title>
</head>

<body>

<p>
This is a secure page.
</p>
</body>

</html>

使用正确的凭据,脚本将返回受保护的页面。

在本教程中,我们探索了 Python httplib2模块。

该教程由 Jan Bodnar 编写,他经营着 zetcode.com,后者专门研究编程教程。

Python 将元组解包为变量或参数

原文: https://howtodoinjava.com/python/unpack-tuple-sequence/

Python 示例将 N 元素元组或序列解包为 N 个变量的集合。 Python 示例将元组解包为变量

1. Python 解包元组示例

可以使用简单的赋值操作将任何序列(或可迭代)解包为变量。 唯一的要求是变量和结构的数量与序列匹配。

1.1 解包示例 – 1

>>> data = (1, 2, 3)
>>> x, y, z = data
>>> x
1
>>> y
2
>>> z
3

1.2 解包示例 – 2

>>> data = [ 'Lokesh', 37, 73.5, (1981, 1, 1) ]

>>> name, age, weight, dob = data

>>> name
'Lokesh'
>>> dob
(1981, 1, 1)

# Another Variation

>>> name, age, weight, (year, mon, day) = data

>>> name
'Lokesh'
>>> year
1981
>>> mon
1
>>> day
1

1.3 解包示例 – 3

>>> greeting = 'Hello'

>>> a, b, c, d, e = greeting

>>> a
'H'	
>>> b
'e'
>>> c
'o'

2.解包时可能出现的错误

如果元素的数量不匹配,则会出现错误。

>>> p = (4, 5)

>>> x, y, z = p

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 2 values to unpack

学习愉快!

Python 解包元组 – 太多值无法解包

原文: https://howtodoinjava.com/python/unpack-variable-length-tuple/

Python 示例,用于解包元组或序列或可迭代,以便该元组可能长于 N 个元素,从而导致“值太多,无法解包”异常。

1.解包任意长度的元组

Python“星表达式”可用于解包任意长度的元组。

>>> employee = ('Lokesh', 'email@example.com', '111-222-333', '444-555-666')

>>> name, email, *phone_numbers = employee

>>> name
'Lokesh'
>>> email
'email@example.com'
>>> phone_numbers
['111-222-333', '444-555-666']

>>> *elements, end = [1,2,3,4,5,6,7,8]

>>> elements
[1,2,3,4,5,6,7]

>>> end
8

2.解包元组并丢弃不需要的值

有时,您可能想拆开值并将其丢弃。 解包时,您不仅可以指定裸露的*,还可以使用通用的一次性变量名,例如“_”或忽略。

>>> record = ('Lokesh', 37, 72.45, (1, 1, 1981))

>>> name, *_, (*_, year) = record	#Only read name and year

>>> name
'Lokesh'

>>> year
1981

学习愉快!

GSON – 序列化和反序列化 JSON 为集

原文: https://howtodoinjava.com/gson/gson-serialize-deserialize-set/

了解如何在 Java 中使用 Google GSON 库反序列化或解析 JSON 为集(例如HashSet)。 另外,学习序列化集为 JSON

值得一提的是,仅当Set是根元素时,我们才会做出额外的努力。 Gson 作为成员(在根下)可以很好地处理这些集。

1.序列化集为 JSON

Java 程序使用Gson.toJson()方法将HashSet序列化为 JSON。

Set<String> userSet = new HashSet<>();
userSet.add("Alex");
userSet.add("Brian");
userSet.add("Charles");

Gson gson = new Gson(); 

String jsonString= gson.toJson(userSet);  

System.out.println(jsonString);

程序输出。

["Alex","Brian","Charles"]

2.反序列化 JSON 为集

Java 程序,使用Gson.fromJson()方法和TypeToken将 JSON 反序列化为HashSet

import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

String jsonString = "['Alex','Brian','Charles','Alex']";

Gson gson = new Gson(); 

Type setType = new TypeToken<HashSet<String>>(){}.getType();

Set<String> userSet = gson.fromJson(jsonString, setType);  

System.out.println(userSet);

程序输出:

["Alex","Brian","Charles"]

向我提供有关在 Java 中解析和反序列化 json 为集的问题。

学习愉快!

参考文献:

GSON Github

Python 多重字典示例 – 将单个键映射到字典中的多个值

原文: https://howtodoinjava.com/python/multidict-key-to-multiple-values/

1.什么是多重字典

在 python 中,“多重字典”一词用于指代字典,在其中可以将单键映射到多个值。 例如:

multidictWithList = {
   'key1' : [1, 2, 3],
   'key2' : [4, 5]
}

multidictWithSet = {
   'key1' : {1, 2, 3},
   'key2' : {4, 5}
}

1. 如果您想保留项目的插入顺序,请使用List

2. 如果您想消除重复项(并且不关心顺序),请使用Set

2. 多重字典示例

要轻松构造此类词典,可以在collections模块中使用defaultdictdefaultdict的功能是它会自动初始化第一个值,因此您只需专注于添加项目即可。

from collections import defaultdict

d1 = defaultdict(list)	#list backed multidict

d1['key1'].append(1)
d1['key1'].append(2)
d1['key1'].append(3)
d1['key2'].append(4)
d1['key2'].append(5)

d2 = defaultdict(set) 	#set backed multidict

d2['key1'].add(1)
d2['key1'].add(2)
d2['key1'].add(3)
d2['key2'].add(4)
d2['key2'].add(5)

>>> d1
defaultdict(<type 'list'>, {'key2': [4, 5], 'key1': [1, 2, 3]})

>>> d1['key1']
[1, 2, 3]

>>> d2
defaultdict(<type 'set'>, {'key2': set([4, 5]), 'key1': set([1, 2, 3])})

>>> d2['key1']
set([1, 2, 3])

学习愉快!

Python OrderedDict – 有序字典

原文: https://howtodoinjava.com/python/ordereddict-ordered-dictionary/

OrderedDict维护添加到字典中的项目的插入顺序。 项目的顺序在迭代或序列化时也会保留。

1. Python OrderedDict示例

OrderedDict是 python 集合模块的一部分。

要轻松构造OrderedDict,可以在collections模块中使用OrderedDict

from collections import OrderedDict

d = OrderedDict()

d['how'] = 1
d['to'] = 2
d['do'] = 3
d['in'] = 4
d['java'] = 5

for key in d:
    print(key, d[key])

('how', 1)
('to', 2)
('do', 3)
('in', 4)
('java', 5)

2.将OrderedDict转换为 JSON

项目的顺序在序列化为 JSON 时也会保留。

from collections import OrderedDict
import json

d = OrderedDict()

d['how'] = 1
d['to'] = 2
d['do'] = 3
d['in'] = 4
d['java'] = 5

json.dumps(d)

'{"how": 1, "to": 2, "do": 3, "in": 4, "java": 5}'

学习愉快!

Python 字典交集 – 比较两个字典

原文: https://howtodoinjava.com/python/dictionary-intersection/

Python 示例,用于查找 2 个或更多词典之间的常见项目,即字典相交项目。

1.使用“&”运算符的字典交集

最简单的方法是查找键,值或项的交集,即使用两个字典之间的 & 运算符。

a = { 'x' : 1, 'y' : 2, 'z' : 3 }
b = { 'u' : 1, 'v' : 2, 'w' : 3, 'x'  : 1, 'y': 2 }

set( a.keys() ) & set( b.keys() )   	# Output set(['y', 'x'])

set( a.items() ) & set( b.items() )   	# Output set([('y', 2), ('x', 1)])

2.Set.intersection()方法

Set.intersection()方法返回一个集合,该集合包含集合a和集合b中都存在的项。

a = { 'x' : 1, 'y' : 2, 'z' : 3 }
b = { 'u' : 1, 'v' : 2, 'w' : 3, 'x'  : 1, 'y': 2 }

setA = set( a )
setB = set( b )

setA.intersection( setB )  

# Output 
# set(['y', 'x'])

for item in setA.intersection(setB):
    print item

# Output 
#x
#y

请问有关检查两个字典在 python 中是否具有相同的键或值的问题。

学习愉快!

Python 优先级队列示例

原文: https://howtodoinjava.com/python/priority-queue/

1.什么是优先队列

  • 优先级队列是一种抽象数据类型,类似于常规队列或栈数据结构,但每个元素还具有与之关联的“优先级”。
  • 在优先级队列中,优先级高的元素先于优先级低的元素提供。
  • 如果两个元素具有相同的优先级,则将根据它们在队列中的顺序为其提供服务。

2. Python 中的优先级队列实现

以下 python 程序使用heapq模块来实现一个简单的优先级队列:

import heapq

class PriorityQueue:
    def __init__(self):
        self._queue = []
        self._index = 0

    def push(self, item, priority):
        heapq.heappush(self._queue, (-priority, self._index, item))
        self._index += 1

    def pop(self):
        return heapq.heappop(self._queue)[-1]

3. Python 优先级队列示例

让我们看一个如何使用上面创建的优先级队列的示例。

class Item:
	def __init__(self, name):
		self.name = name
	def __repr__(self):
		return 'Item({!r})'.format(self.name)

>>> q = PriorityQueue()

>>> q.push(Item('how'), 1)
>>> q.push(Item('to'), 5)
>>> q.push(Item('do'), 4)
>>> q.push(Item('in'), 2)
>>> q.push(Item('java'), 1)

>>> q.pop()
Item('to')		#5

>>> q.pop()
Item('do')		#4

>>> q.pop()
Item('in')		#2

>>> q.pop()
Item('how')		#1

>>> q.pop()
Item('java')	#1

学习愉快!

RxJava 教程

原文: https://howtodoinjava.com/library/rxjava-tutorial/

RxJava 2.0 是 Java 的开源扩展,用于 NetFlix 的异步编程。 如 Java8 lambda 表达式中所示,它与函数式编程更加接近。 反应代码的基本构建块是可观察对象订阅者Observable发出项目; Subscriber消耗这些项目。

RxJava 看起来也像观察者设计模式,但有所不同 – 可观察对象通常不会开始发布项目,除非有人明确订阅它们。

Table of Contents

1\. What is RxJava and Reactive Programming
2\. RxJava2 Dependency
3\. Transformation
4\. Conclusion

1.什么是 RxJava 和反应式编程

反应式编程是一个通用的编程术语,着重于对诸如数据值或事件之类的变化做出反应。 回调是命令式完成反应式编程的一种方法。

例如,如果您有一个数据源(生产者)和一个数据目标(消费者); 然后,在将消费者连接到订阅者之后,反应式编程框架负责将生产者生成的数据推送到消费者。 请注意,一个可观察对象可以有任意数量的订阅者。

让我们看一个非常基本的 RxJava helloworld 示例

package com.howtodoinjava.app;

import io.reactivex.Observable;
import io.reactivex.functions.Consumer;

public class RxJava2Example 
{
      public static void main(String[] args) 
      {     
            //producer
            Observable<String> observable = Observable.just("how", "to", "do", "in", "java");

            //consumer
            Consumer<? super String> consumer = System.out::println;

            //Attaching producer to consumer
            observable.subscribe(consumer);
      }
}

在上面的示例中,"how", "to", "do", "in", "java"可被视为事件流。 为这些事件创建了observable。 然后,我们创建一个可以对这些单词起作用的consumer - 在这种情况下,它只是将它们打印出来以供控制台使用。 该消费者不过是subscriber

最后,我们使用subscribe()将订阅者连接到消费者。 一旦我们将两者连接起来,单词/事件就开始流动,订阅者开始在控制台中打印它们。

在代码内部,当从可观察对象发出新单词时,将在每个订阅者上调用onNext()方法。 当可观察的单词成功完成或出现错误时,将在订阅者上调用onComplete()onError()方法。

2. RxJava2 依赖项

要将 RxJava 2.0 包含到项目运行时中,可以在给定的 maven 配置,gradle 配置或 jar 文件添加到类路径中进行选择。

2.1 RxJava 2.0 Maven 依赖关系

<!-- https://mvnrepository.com/artifact/io.reactivex.rxjava2/rxjava -->
<dependency>
      <groupId>io.reactivex.rxjava2</groupId>
      <artifactId>rxjava</artifactId>
      <version>2.1.0</version>
</dependency>

2.2 RxJava 2.0 Gradle 依赖关系

compile group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.1.0'

2.3 RxJava 2.0 Jar 依赖关系

下载 RxJava 2.0 Jar 文件链接。

3. RxJava 中的转换

在 RxJava 中,订阅者从可观察到的事件类型与订阅者发出的事件类型不需要相同。 它们可以包含的数据,数据类型或接收到的事件和发出的事件之间的任何其他主要差异是不同的。

这是为源和目标之间的事件的中间转换提供支持的必要条件,以便两者都能按其设计的方式工作并且仍然兼容。 与适配器设计模式非常相似。

让我们举个例子。 在我们的 helloworld 示例中,我们要以大写形式打印单词。 这是一个简单的转换,但是您会明白的。

Observable<String> observable = Observable.just("how", "to", "do", "in", "java");
Consumer<? super String> consumer = System.out::println;

//Transformation using map() method
observable.map(w -> w.toUpperCase()).subscribe(consumer);

在上面的示例中,我们在订阅可观察对象之前添加了一种中间方法map()。 因此,每个单词都首先通过map()方法,然后再传递给订阅者以进行进一步处理。 这称为转换。

如前所述,您还可以在转换过程中更改事件的数据类型。 例如:

Observable<String> observable = Observable.just("how", "to", "do", "in", "java");
Consumer<? super Integer> consumer = System.out::println;

observable.map(w -> w.toUpperCase().hashCode()).subscribe(consumer); 

在此程序中,我们遍历单词,然后在转换中获得单词的哈希码并将其传递给订阅者,后者在控制台中打印该哈希码。 因此,在这里,我们将连接可观察对象(它发出字符串)和订阅者(它接受整数)。

4. 结论

可观察的方式和订阅者之间的松散连接,为开发人员带来了很大的优势。 他们不需要考虑整个并发示例,对于我们许多人来说,这已经是艰巨的任务。 您只需连接制作人和订阅者 - 一切都将完美无缺地进行。

另外,您无需同时考虑可观察者和订阅者。 您可以使用最佳设计选择独立地开发它们,然后使用转换概念将它们连接起来。 太好了!

RxJava 教程是 RxJava 的简介。 我将在即将到来的 rxjava 初学者教程中更深入地介绍与反应式编程教程相关的重要概念。

学习愉快!

完整的 Java Servlet 教程

原文: https://howtodoinjava.com/servlets/complete-java-servlets-tutorial/

Servlet 是符合 Java Servlet API 的 Java 类,该 Java Servlet API 允许 Java 类响应请求。 尽管 Servlet 可以响应任何类型的请求,但最常见的是将其编写为响应基于 Web 的请求。 必须将 servlet 部署到 Java servlet 容器中才能使用。 尽管许多开发人员都使用诸如 Java Server Pages(JSP)Java Server Faces(JSF)之类的 servlet 框架,但这两种技术通过 servlet 容器将页面编译为后台的 Java servlet。 也就是说,Java servlet 技术的基础知识对于任何 Java Web 开发人员都可能非常有用。

在本教程中,我们将涵盖以下主题,以全面了解 Java Servlet 技术。

Table of Contents

Writing your first Servlet
Servlet Life Cycle Methods
Develop Servlet with @WebServlet Annotation
Packaging and Deploying Servlet into Tomcat Server
Writing dynamic content in Servlet response
Handling Servlet Request and Response
Listening for Servlet Container Events
Passing Servlet Initialization Parameters
Adding Servlet Filters for Specific URL Requests
Downloading a binary file using Servlet
Forward request to another servlet using RequestDispatcher.forward()
Redirect request to another servlet using HttpServletResponse.sendRedirect()
Writing and Reading Cookie using Servlets

让我们开始逐步了解 servlet。

编写您的第一个 Servlet

我们的第一个 servlet 是非常简单的 servlet,它具有很少的代码,因此您只能专注于重要的事情。

package com.howtodoinjava.servlets;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyFirstServlet extends HttpServlet {

	private static final long serialVersionUID = -1915463532411657451L;

	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException 
	{
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		try {
			// Write some content
			out.println("<html>");
			out.println("<head>");
			out.println("<title>MyFirstServlet</title>");
			out.println("</head>");
			out.println("<body>");
			out.println("<h2>Servlet MyFirstServlet at " + request.getContextPath() + "</h2>");
			out.println("</body>");
			out.println("</html>");
		} finally {
			out.close();
		}
	}

	@Override
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		//Do some other work
	}

	@Override
	public String getServletInfo() {
		return "MyFirstServlet";
	}
}

要在 Web 容器上方注册上述 Servlet,您将为您的应用创建一个入口web.xml文件。

<?xml version="1.0"?>
<web-app 	xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
			http://xmlns.jcp.org/xml/ns/javaee/web-app_3_0.xsd"
			version="3.0">

	<welcome-file-list>
		<welcome-file>/MyFirstServlet</welcome-file>
	</welcome-file-list>

	<servlet>
		<servlet-name>MyFirstServlet</servlet-name>
		<servlet-class>com.howtodoinjava.servlets.MyFirstServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>MyFirstServlet</servlet-name>
		<url-pattern>/MyFirstServlet</url-pattern>
	</servlet-mapping>

</web-app>

Servlet 上面没有做一些重要的事情,您可能需要学习。

  1. MyFirstServlet扩展了HttpServlet。 这是强制性的,因为所有 servlet 必须是扩展javax.servlet.GenericServlet的通用 servlet 或扩展javax.servlet.http.HttpServlet的 HTTP servlet。
  2. 覆盖doGet()doPost()方法。 这些方法在HttpServlet类中定义。 每当 GET 或 POST 请求到来时,都会将其映射到其相应的方法,例如,如果您对此 Servlet 发出 HTTP GET 请求,然后调用doGet()方法。
  3. 还有一些其他有用的方法,您可以覆盖它们以在运行时控制应用,例如getServletInfo()
  4. HttpServletRequestHttpServletResponse是所有doXXX()方法的默认参数。 我们将在后面的部分中了解有关这些对象的更多信息。

这就是您应该知道的简单 servlet 的全部内容。

Servlet 生命周期方法

无论何时在您的应用中,都会加载并使用一个 servlet。 在该 Servlet 的初始化和销毁​​过程中发生了一系列事件。 这些被称为 servlet 的生命周期事件(或方法)。 让我们详细了解它们。

三种方法对于 Servlet 的生命周期至关重要。 它们是init()service()destroy()。 它们由每个 servlet 实现,并在运行时在特定时间调用。

1)在 Servlet 生命周期的初始化阶段, Web 容器通过调用init()方法并传递实现javax.servlet.ServletConfig接口的对象来初始化 Servlet 实例。 此配置对象允许 Servlet 访问 Web 应用的web.xml文件中定义的名称 - 值初始化参数。 它在该 Servlet 实例的生存期内仅被调用一次。

初始化方法的定义如下所示:

public void  init() throws ServletException {
	//custom initialization code
}

2)初始化后,Servlet 实例可以为客户端请求提供服务。 Web 容器针对每个请求调用 servlet 的service()方法。 service()方法确定发出的请求的类型,并将其分派给适当的方法以处理该请求。 Servlet 的开发人员必须提供这些方法的实现。 如果对不是由 Servlet 实现的方法提出了请求,则将调用父类的方法,通常会导致将错误返回给请求者。

几乎在所有情况下都无需覆盖此方法。

protected void service(HttpServletRequest req, HttpServletResponse resp)
	throws ServletException, IOException
{
String method = req.getMethod();

if (method.equals(METHOD_GET)) {
	long lastModified = getLastModified(req);
	if (lastModified == -1) {
	// servlet doesn't support if-modified-since, no reason
	// to go through further expensive logic
	doGet(req, resp);
	} else {
	long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
	if (ifModifiedSince < (lastModified / 1000 * 1000)) {
		// If the servlet mod time is later, call doGet()
				// Round down to the nearest second for a proper compare
				// A ifModifiedSince of -1 will always be less
		maybeSetLastModified(resp, lastModified);
		doGet(req, resp);
	} else {
		resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
	}
	}

} else if (method.equals(METHOD_HEAD)) {
	long lastModified = getLastModified(req);
	maybeSetLastModified(resp, lastModified);
	doHead(req, resp);

} else if (method.equals(METHOD_POST)) {
	doPost(req, resp);

} else if (method.equals(METHOD_PUT)) {
	doPut(req, resp);	

} else if (method.equals(METHOD_DELETE)) {
	doDelete(req, resp);

} else if (method.equals(METHOD_OPTIONS)) {
	doOptions(req,resp);

} else if (method.equals(METHOD_TRACE)) {
	doTrace(req,resp);

} else {
	//
	// Note that this means NO servlet supports whatever
	// method was requested, anywhere on this server.
	//

	String errMsg = lStrings.getString("http.method_not_implemented");
	Object[] errArgs = new Object[1];
	errArgs[0] = method;
	errMsg = MessageFormat.format(errMsg, errArgs);

	resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}

3)最后, Web 容器调用destroy()方法,该方法使 Servlet 退出服务。 如果要在 Servlet 超出范围之前关闭或销毁某些文件系统或网络资源,则应调用此方法。 像init()一样,destroy()方法在 Servlet 的生命周期中仅被调用一次。

public void destroy() {
	//
}

通常,在大多数情况下,您不需要在 servlet 中覆盖它们中的任何一个。

阅读更多: Web 服务器如何工作?

使用@WebServlet注解开发 Servlet

如果您不太喜欢 xml 配置,而是特别喜欢注解,那么 Servlets API 也可以。 您可以使用@WebServlet注解,如下例所示,然后您无需在web.xml中进行任何输入。 容器将自动将您的 servlet 注册到运行时,并像往常一样处理它

package com.howtodoinjava.servlets;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "MyFirstServlet", urlPatterns = {"/MyFirstServlet"})
public class MyFirstServlet extends HttpServlet {

	private static final long serialVersionUID = -1915463532411657451L;

	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException 
	{
		//Do some work
	}

	@Override
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		//Do some other work
	}
}

将 Servlet 打包和部署到 Tomcat 服务器

如果您使用的是任何 IDE(例如 eclipse),则打包和部署应用只是一个步骤。 Right click on project > Run As > Run As Server。 如果尚未配置服务器,则准备好进行滚动。

如果您不使用任何 IDE,那么您需要做一些额外的工作,例如从命令提示符下编译应用,使用 ANT 创建 WAR 文件等。但是我非常相信当今的每个人都使用一些 IDE 进行开发,因此我将 不要在本节中浪费更多时间。

当您在 tomcat 中部署我们的第一个 servlet 并在浏览器中访问 URL“http://localhost:8080/servletexamples/MyFirstServlet”时,您将获得以下响应。

servlet example

在 Servlet 响应中编写动态内容

Java servlet 之所以如此有用的原因之一是因为它们允许将动态内容显示在网页上。 内容可以取自服务器本身,数据库,另一个网站或许多其他可从 Web 访问的资源。 Servlet 不是静态网页。 他们充满活力,可以说是他们最大的优势。

让我们以一个 servlet 为例,该 servlet 负责向用户显示当前日期和时间,以及其名称和一些自定义消息。 让我们为其编写代码。

package com.howtodoinjava.servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "CalendarServlet", urlPatterns = {"/CalendarServlet"})
public class CalendarServlet extends HttpServlet {

	private static final long serialVersionUID = -1915463532411657451L;

	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException 
	{

		Map<String,String> data = getData();

		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		try {
			// Write some content
			out.println("<html>");
			out.println("<head>");
			out.println("<title>CalendarServlet</title>");
			out.println("</head>");
			out.println("<body>");
			out.println("<h2>Hello " + data.get("username") + ", " + data.get("message") + "</h2>");
			out.println("<h2>The time right now is : " + new Date() + "</h2>");
			out.println("</body>");
			out.println("</html>");
		} finally {
			out.close();
		}
	}

	//This method will access some external system as database to get user name, and his personalized message
	private Map<String, String> getData() 
	{
		Map<String, String> data = new HashMap<String, String>();
		data.put("username", "Guest");
		data.put("message",  "Welcome to my world !!");
		return data;
	}
}

当您在 tomcat 中的 servlet 上方运行并在浏览器中访问 URL“http://localhost:8080/servletexamples/CalendarServlet”时,将得到以下响应。

dynamic content in servlet

处理 Servlet 请求和响应

Servlet 使创建符合请求和响应生命周期的 Web 应用变得容易。 它们具有提供 HTTP 响应的能力,并且还可以在同一代码体内处理业务逻辑。 处理业务逻辑的能力使 servlet 比标准 HTML 代码强大得多。

在实际的应用中,HTML Web 表单包含发送到 Servlet 的参数。 然后,该 Servlet 以某种方式处理这些参数,并发布客户端可以看到的响应。 对于HttpServlet对象,客户端是 Web 浏览器,响应是 Web 页面。 <form>动作属性指出应使用该属性来处理表单中包含的值。

要获取请求参数,请调用HttpServletRequest对象的getParameter()方法,并传递您要获取的输入参数的 ID。

String value1 = req.getParameter("param1");
String value1 = req.getParameter("param2");

一旦获得值,就可以根据需要对其进行处理。 然后,如上节所述,为客户准备响应。 使用HttpServletResponse对象将该响应发送回客户端。

请求和响应处理的基本用法可以这样完成:

@Override
protected void doGet(HttpServletRequest request,
		HttpServletResponse response) throws ServletException, IOException 
{

	response.setContentType("text/html;charset=UTF-8");
	PrintWriter out = response.getWriter();

	String username = request.getParameter("username");
	String password = request.getParameter("password");

	boolean success = validateUser(username, password);

	try {
		// Write some content
		out.println("<html>");
		out.println("<head>");
		out.println("<title>LoginServlet</title>");
		out.println("</head>");
		out.println("<body>");

		if(success) {
			out.println("<h2>Welcome Friend</h2>");
		}else{
			out.println("<h2>Validate your self again.</h2>");
		}

		out.println("</body>");
		out.println("</html>");
	} finally {
		out.close();
	}
}

为了发送内容,您将必须使用从HttpServletResponse获得的PrintWriter对象。 写入其中的任何内容都将写入输出流,并且数据将发送回客户端。

监听 Servlet 容器事件

有时了解某些事件在应用服务器容器中何时发生很有用。 此概念在许多不同的情况下都可能有用,但是最常用于启动时初始化应用或关闭后清除应用。 Servlet 监听器可以向应用注册,以指示何时启动或关闭它。 因此,通过监听此类事件,servlet 可以有机会在它们发生时执行某些操作。

要创建一个基于容器事件执行操作的监听器,必须开发一个实现ServletContextListener接口的类。 需要实现的方法是contextInitialized()contextDestroyed()。 这两种方法都接受ServletContextEvent作为参数,并且分别在每次初始化或关闭 servlet 容器时自动调用它们。

若要向容器注册监听器,可以使用以下技术之一:

1)使用@WebListener注解。

2)在web.xml应用部署描述符中注册监听器。

3)使用在ServletContext上定义的addListener()方法。

请注意,ServletContextListener不是 servlet API 中的唯一列表器。 还有更多例如:

  • javax.servlet.ServletRequestListener
  • javax.servlet.ServletRequestAttrbiteListener
  • javax.servlet.ServletContextListener
  • javax.servlet.ServletContextAttributeListener
  • javax.servlet.HttpSessionListener
  • javax.servlet.HttpSessionAttributeListener

可以由您的列表器类根据您想听所有事件的选择来实现它们; 例如,每当创建或销毁新的用户会话时,都会通知HttpSessionListener

传递 Servlet 初始化参数

当今大多数应用都需要设置一些配置参数,您可以在应用/控制器启动时将其传递给他们。 Servlet 还可以接收初始化参数,在为第一个请求提供服务之前,它们可以用来完全构造它们。

显然,您可以在 servlet 本身中对配置值进行硬编码,但是更改其中的任何一个将需要您重新重新编译整个应用,而且没人愿意这样做。

<web-app>
    <servlet>
        <servlet-name>SimpleServlet</servlet-name>
        <servlet-class>com.howtodoinjava.servlets.SimpleServlet</servlet-class>

		<!-- Servlet init param -->
        <init-param>
            <param-name>name</param-name>
            <param-value>value</param-value>
        </init-param>

    </servlet>

</web-app>

设置后,可以通过调用getServletConfig().getInitializationParameter()并传递参数名称在代码内使用该参数,如以下代码行所示:

String value = getServletConfig().getInitParameter("name");

为特定的 URL 请求添加 Servlet 过滤器

Web 过滤器对于预处理请求和访问给定 URL 时调用某些功能很有用。 与其直接调用在给定 URL 上存在的 servlet,否则将在 servlet 之前调用任何包含相同 URL 模式的过滤器。 这在许多情况下可能是有用的,这对于执行日志记录,认证或在没有用户交互的情况下在后台进行的其他服务而言可能是最有用的。

过滤器必须实现javax.servlet.Filter接口。 此接口中包含的方法包括init()destroy()doFilter()。 容器调用init()destroy()方法。 doFilter()方法用于实现过滤器类的任务。 如果要链接过滤器,或者给定 URL 模式存在多个过滤器,则将按照在web.xml部署描述符中对其进行配置的顺序来调用它们。

要将web.xml文件配置为包含过滤器,请使用<filter><filter-mapping> XML 元素及其关联的子元素标签。

<filter>
    <filter-name>LoggingFilter</filter-name>
    <filter-class>LoggingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>LogingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

如果要使用注解为特定的 servlet 配置过滤器,则可以使用@WebFilter注解。

使用 Servlet 下载二进制文件

对于几乎所有 Web 应用来说,下载文件都是一项基本任务。 要下载文件,servlet 必须提供与要下载的文件相匹配的相同类型的响应。 它还必须在响应标头中指出要包含附件,如下所示。

String mimeType = context.getMimeType( fileToDownload );
response.setContentType( mimeType != null ? mimeType : "text/plain" );
response.setHeader( "Content-Disposition", "attachment; filename=\"" + fileToDownload + "\"" );

您可以通过调用ServletContext.getResourceAsStream()方法并传递文件路径来获取对要下载(存储在文件系统中)文件的引用。 这将返回一个InputStream对象,该对象可用于读取文件的内容。 然后创建一个字节缓冲区,该缓冲区将在读取文件时用于从文件中获取数据块。 最后的实际任务是读取文件内容并将其复制到输出流。 这是使用while循环完成的,该循环将继续从InputStream读取,直到处理完所有内容为止。 使用循环将数据块读入并写入输出流。 之后,将调用ServletOutputStream对象的flush方法以清除内容并释放资源。

让我们看一下示例代码

private void downloadFile(HttpServletRequest request, HttpServletResponse response, String fileToDownload) throws IOException
	{
		final int BYTES = 1024;
		int length = 0;

		ServletOutputStream outStream = response.getOutputStream();
		ServletContext context = getServletConfig().getServletContext();

		String mimeType = context.getMimeType( fileToDownload );
		response.setContentType( mimeType != null ? mimeType : "text/plain" );
		response.setHeader( "Content-Disposition", "attachment; filename=\"" + fileToDownload + "\"" );

		InputStream in = context.getResourceAsStream("/" + fileToDownload);

		byte[] bbuf = new byte[BYTES];

		while ((in != null) && ((length = in.read(bbuf)) != -1)) {
			outStream.write(bbuf, 0, length);
		}

		outStream.flush();
		outStream.close();
	}

使用RequestDispatcher.forward()将请求转发到另一个 servlet

有时,您的应用要求 servlet 应该将请求移交给其他 servlet,以完成需要完成的任务。 此外,应在不将客户端重定向到另一个 URL 的情况下移交请求,即浏览器中的 URL 不应更改。

这样做的功能直接内置在ServletContext中,因此一旦获得对ServletContext的引用,就可以简单地调用getRequestDispatcher()方法来获取一个RequestDispatcher对象,该对象可用于分派请求。 调用getRequestDispatcher()方法时,传递一个字符串,其中包含要将您的请求传递到的 servlet 的名称。 获得RequestDispatcher对象后,通过将HttpServletRequestHttpServletResponse对象传递给它来调用其前向方法。 转发方法执行移交请求的任务。

RequestDispatcher rd = servletContext.getRequestDispatcher("/NextServlet");
rd.forward(request, response);

使用HttpServletResponse.sendRedirect()将请求重定向到另一个 servlet

尽管在某些情况下,您不希望像上一节中所看到的那样通知用户 servlet 重定向已发生,但是在某些情况下,我们实际上希望发生这种情况。 当您访问应用中的特定 URL 时,您想将浏览器重定向到另一个 URL。

为此,您将需要调用HttpServletResponse对象的sendRedirect()方法。

httpServletResponse.sendRedirect("/anotherURL");

与 servlet 链接相反,这种简单的重定向不会将HttpRequest对象传递到目标地址。

许多应用都希望将用户浏览历史记录的当前状态存储在客户端计算机中,以便当用户再次返回到应用时,他从离开的地方开始。 通常为此要求使用 cookie。 您可以将 Cookie 视为存储在客户计算机上的基于键值对的数据。 当在浏览器中访问应用时,应用将能够读取或写入这些值。

要创建 cookie,只需实例化一个新的javax.servlet.http.Cookie对象并为其分配名称和值。 一旦实例化了 cookie,就可以设置将有助于配置 cookie 的属性。 在此食谱的示例中,调用了 cookie 的setMaxAge()setHttpOnly()方法,设置了 cookie 的生存时间,并确保防止客户端脚本编写。

从 Servlet 3.0 API 开始,将 cookie 标记为仅 HTTP 的功能已变得可用。 这样可以保护 cookie 免受客户端脚本攻击,从而使 cookie 更加安全。

Cookie cookie = new Cookie("sessionId","123456789");
cookie.setHttpOnly(true);
cookie.setMaxAge(-30);
response.addCookie(cookie);

这里的响应是传递给doXXX()方法的HttpServletResponse的实例。

要回读服务器父项上的 cookie 信息,请使用以下代码:

Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies)
{
	//cookie.getName();
	//cookie.getValue()
}

以上就是有关 Servlet 技术的本教程。 随意删除评论/反馈。

祝您学习愉快!

vaadin 教程

使用 Maven 的 vaadin HelloWorld Web 应用

原文: https://howtodoinjava.com/vaadin/hello-world-web-application-maven/

在此示例中,我们将学习使用 maven 创建 vaadin helloworld 应用,然后在内置Jetty服务器中运行部署该应用。

使用 Maven 原型创建 vaadin 项目

在工作区中使用下面的 maven 命令创建最简单的 vaadin Web 应用。

$ mvn archetype:generate 
	-DarchetypeGroupId=com.vaadin 
	-DarchetypeArtifactId=vaadin-archetype-application 
	-DarchetypeVersion=LATEST 
	-DgroupId=com.howtodoinjava.vaadin.demo 
	-DartifactId=VaadinExample 
	-Dversion=1.0 
	-Dpackaging=war

请根据需要更新-DgroupId-DartifactId

生成的项目结构

现在,将生成的项目作为现有的 maven 项目导入到您的 IDE(在我的情况下为 eclipse)中。

Vaadin HelloWorld Application Project Structure

Vaadin HelloWorld 应用项目结构

生成的项目包含任何 Maven Web 应用的标准文件夹结构。

生成的文件

连同文件夹结构一起,我们将获得这些生成的文件以及 vaadin helloworld 项目

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd;
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.howtodoinjava.vaadin.demo</groupId>
	<artifactId>VaadinExample</artifactId>
	<packaging>war</packaging>
	<version>1.0</version>
	<name>VaadinExample</name>

	<prerequisites>
		<maven>3</maven>
	</prerequisites>

	<properties>
		<vaadin.version>7.7.0</vaadin.version>
		<vaadin.plugin.version>7.7.0</vaadin.plugin.version>
		<jetty.plugin.version>9.3.9.v20160517</jetty.plugin.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<!-- If there are no local customisations, this can also be "fetch" or "cdn" -->
		<vaadin.widgetset.mode>local</vaadin.widgetset.mode>
	</properties>

	<repositories>
		<repository>
			<id>vaadin-addons</id>
			<url>http://maven.vaadin.com/vaadin-addons</url>
		</repository>
		<repository>
			<id>vaadin-snapshots</id>
			<url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
			<releases>
				<enabled>false</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>com.vaadin</groupId>
				<artifactId>vaadin-bom</artifactId>
				<version>${vaadin.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.0.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.vaadin</groupId>
			<artifactId>vaadin-server</artifactId>
		</dependency>
		<dependency>
			<groupId>com.vaadin</groupId>
			<artifactId>vaadin-push</artifactId>
		</dependency>
		<dependency>
			<groupId>com.vaadin</groupId>
			<artifactId>vaadin-client-compiled</artifactId>
		</dependency>
		<dependency>
			<groupId>com.vaadin</groupId>
			<artifactId>vaadin-themes</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
					<!-- Exclude an unnecessary file generated by the GWT compiler. -->
					<packagingExcludes>WEB-INF/classes/VAADIN/widgetsets/WEB-INF/**</packagingExcludes>
				</configuration>
			</plugin>
			<plugin>
				<groupId>com.vaadin</groupId>
				<artifactId>vaadin-maven-plugin</artifactId>
				<version>${vaadin.plugin.version}</version>
				<executions>
					<execution>
						<goals>
							<goal>update-theme</goal>
							<goal>update-widgetset</goal>
							<goal>compile</goal>
							<!-- Comment out compile-theme goal to use on-the-fly theme compilation -->
							<goal>compile-theme</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-clean-plugin</artifactId>
				<version>3.0.0</version>
				<!-- Clean up also any pre-compiled themes -->
				<configuration>
					<filesets>
						<fileset>
							<directory>src/main/webapp/VAADIN/themes</directory>
							<includes>
								<include>**/styles.css</include>
								<include>**/styles.scss.cache</include>
							</includes>
						</fileset>
					</filesets>
				</configuration>
			</plugin>

			<!-- The Jetty plugin allows us to easily test the development build by
				running jetty:run on the command line. -->
			<plugin>
				<groupId>org.eclipse.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>${jetty.plugin.version}</version>
				<configuration>
					<scanIntervalSeconds>2</scanIntervalSeconds>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<profiles>
		<profile>
			<!-- Vaadin pre-release repositories -->
			<id>vaadin-prerelease</id>
			<activation>
				<activeByDefault>false</activeByDefault>
			</activation>

			<repositories>
				<repository>
					<id>vaadin-prereleases</id>
					<url>http://maven.vaadin.com/vaadin-prereleases</url>
				</repository>
			</repositories>
			<pluginRepositories>
				<pluginRepository>
					<id>vaadin-prereleases</id>
					<url>http://maven.vaadin.com/vaadin-prereleases</url>
				</pluginRepository>
			</pluginRepositories>
		</profile>
	</profiles>

</project>

MyUI.java

package com.howtodoinjava.vaadin.demo;

import javax.servlet.annotation.WebServlet;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

/**
 * This UI is the application entry point. A UI may either represent a browser window 
 * (or tab) or some part of a html page where a Vaadin application is embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be 
 * overridden to add component to the user interface and initialize non-component functionality.
 */
@Theme("mytheme")
public class MyUI extends UI {

    @Override
    protected void init(VaadinRequest vaadinRequest) {
        final VerticalLayout layout = new VerticalLayout();

        final TextField name = new TextField();
        name.setCaption("Type your name here:");

        Button button = new Button("Click Me");
        button.addClickListener( e -> {
            layout.addComponent(new Label("Thanks " + name.getValue() 
                    + ", it works!"));
        });

        layout.addComponents(name, button);
        layout.setMargin(true);
        layout.setSpacing(true);

        setContent(layout);
    }

    @WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
    @VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
    public static class MyUIServlet extends VaadinServlet {
    }
}

如果要使用web.xml文件,因为您的服务器不支持 servlet 3.0 规范,则可以在web.xml文件中使用此配置。

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

	<servlet>
		<servlet-name>myservlet</servlet-name>
		<servlet-class>com.vaadin.server.VaadinServlet</servlet-class>
		<init-param>
			<param-name>UI</param-name>
			<param-value>com.howtodoinjava.vaadin.demo.MyUI</param-value>
		</init-param>
	</servlet>

	<servlet-mapping>
		<servlet-name>myservlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

如果您正在使用web.xml文件,请不要忘记删除@WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)

构建生成的项目

现在该构建项目,以便它可以下载所有依赖项并将其包含在项目的运行时中。

$ mvn clean install

上面的 maven 命令将下载依赖项(需要一些时间),并构建 war 文件VaadinExample-1.0.war

运行并测试 vaadin HelloWorld 应用

现在该运行该应用了。 在命令提示符下,运行以下命令以启动随附的 Jetty 服务器。

$mvn jetty:run

这将启动内置的 Jetty 服务器,您可以通过http://localhost:8080/访问该应用。

Vaadin HelloWorld Screen

Vaadin 你好世界屏幕

现在在文本框中填写您的姓名或任何字符串,然后单击按钮。 它将在按钮下方打印消息。

Vaadin HelloWorld Screen -2

Vaadin 你好世界屏幕 - 2

下载源码

学习愉快!

资源:

Vaadin 原型

Vaadin Maven 设置

Vaadin ComboBox示例

原文: https://howtodoinjava.com/vaadin/vaadin-combobox-examples/

在本教程中,我们将学习与 Vaadin ComboBox UI 组件一起使用,包括设置值,过滤,添加新值和事件处理。

Table of Contents

Set ComboBox Values List
Disable Empty/Null Selection
Pre-selecting value
Enable/Disable Values Filtering
Editable Vaadin ComboBox
Add Custom CSS Style
Complete Example

设置组合框值列表

要将项目添加到 vaadin 组合框组件,请使用ComboBox.addItem()ComboBox.addItems()方法。

Vaadin ComboBox Items

Vaadin ComboBox项目

ComboBox combobox = new ComboBox("Select your gender:");

//Add multiple items
combobox.addItems("Select", "Male", "Female", "Other");

layout.addComponent(combobox);

如果您想一次添加一项:

ComboBox combobox = new ComboBox("Select your gender:");

//Add one item at a time
combobox.addItem("Select");
combobox.addItem("Male");
combobox.addItem("Female");
combobox.addItem("Other");

layout.addComponent(combobox);

同样,您可以使用其他数据结构来填充组合框项目。 例如。 我已经使用枚举类型来填充 vaadin 组合框项目

enum Genders {
	MALE("Male"), FEMALE("Female"), OTHERS("Others");

	private String name;

	private Genders(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return name;
	}
}

ComboBox combobox = new ComboBox("Select your gender:");

//Add enum values
combobox.addItems("Select", Genders.MALE, Genders.FEMALE, Genders.OTHERS);

layout.addComponent(combobox);

禁用空选项

您在第一个屏幕截图中注意到第一个选项为空。 如果要删除此空选项,请使用combobox.setNullSelectionAllowed(false);方法。

Empty Option Removed from Vaadin Combobox

Vaadin 组合框中的空选项已删除

ComboBox combobox = new ComboBox("Select your gender:");
combobox.addItems("Select", "Male", "Female", "Other");

//Remove empty option
combobox.setNullSelectionAllowed(false);

layout.addComponent(combobox);

预选值

很多时候您想预选一些值,例如,如果您正在显示一个带有预先存储在数据库中的值的编辑表单。 在这种情况下,请使用setValue()方法。

Preselect value in vaadin combobox

vaadin 组合框中的预选值

ComboBox combobox = new ComboBox("Select your gender:");
combobox.addItems("Select", "Male", "Female", "Other");

//Remove empty option
combobox.setNullSelectionAllowed(false);

//Pre-set some value
combobox.setValue("Male");

layout.addComponent(combobox);

启用/禁用值过滤

默认情况下,vaadin 组合框组件支持过滤,即您可以通过键入文本框来过滤值。 如果要控制启用/禁用此功能,请使用combobox.setFilteringMode()方法。

Filtering in vaadin combobox

vaadin 组合框中的过滤

共有 3 种过滤模式:

  1. FilteringMode.CONTAINS

    与包含组件文本字段部分中给出的字符串的任何项目匹配。

  2. FilteringMode.OFF

    它可以过滤掉并始终显示所有项目。

  3. FilteringMode.STARTSWITH

    仅匹配以给定字符串开头的项目。 这是 vaadin7 中的默认模式。

ComboBox combobox = new ComboBox("Select your gender:");
combobox.addItems("Select", "Male", "Female", "Other");

combobox.setNullSelectionAllowed(false);

//Add Filtering
combobox.setFilteringMode(FilteringMode.CONTAINS);

layout.addComponent(combobox);

Vaadin 可编辑ComboBox

可编辑组合框中,您也可以从 UI 添加新项目。 要添加新项目,只需键入新项目标题,然后按Enter

添加一个NewItemHandler实例来处理新添加的项目。 例如。 添加后,我在组合框中选择它作为当前值。

Editable Combobox

可编辑ComboBox

Label label = new Label();

ComboBox combobox = new ComboBox("Select your gender:");
combobox.addItems("Select", "Male", "Female", "Other");
combobox.setNullSelectionAllowed(false);

//Allow new Items
combobox.setNewItemsAllowed(true);
combobox.setImmediate(true);

combobox.setNewItemHandler(new NewItemHandler(){

	private static final long serialVersionUID = 1L;

	@Override
	public void addNewItem(String newItemCaption) {
		combobox.addItem(newItemCaption);
		combobox.setValue(newItemCaption);
		label.setValue("You added : " + newItemCaption);
	}
});

layout.addComponent(label);
layout.addComponent(combobox);

添加自定义 CSS 样式

  • 整个组合框组件都包含在v-filterselect样式中。
  • 输入字段为v-filterselect-input样式。
  • 右端用于打开和关闭下拉结果列表的按钮为v-filterselect-button样式。
  • 下拉结果列表具有整体v-filterselect-suggestpopup样式。
  • 它包含v-filterselect-suggestmenu样式的建议列表。

修改以上 CSS 类以更改组合框的显示。

完整的例子

package com.howtodoinjava.vaadin.demo;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.shared.ui.combobox.FilteringMode;
import com.vaadin.ui.AbstractSelect.NewItemHandler;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

@Theme("mytheme")
public class MyUI extends UI {

	private static final long serialVersionUID = 1387172685749279538L;

	enum Genders {
		MALE("Male"), FEMALE("Female"), OTHERS("Others");

		private String name;

		private Genders(String name) {
			this.name = name;
		}

		@Override
		public String toString() {
			return name;
		}
	}

	@Override
	protected void init(VaadinRequest vaadinRequest) {
		final VerticalLayout layout = new VerticalLayout();

		addListItems(layout);
		removeEmptySelection(layout);
		preSelectValue(layout);
		addFiltering(layout);
		editableCombobox(layout);

		layout.setMargin(true);
		layout.setSpacing(true);

		setContent(layout);
	}

	private void addListItems(VerticalLayout layout) {
		ComboBox combobox = new ComboBox("Select your gender:");

		/*
		 * combobox.addItem("Select"); combobox.addItem("Male");
		 * combobox.addItem("Female"); combobox.addItem("Other");
		 */

		// combobox.addItems("Select", "Male", "Female", "Other");

		combobox.addItems("Select", Genders.MALE, Genders.FEMALE, Genders.OTHERS);

		layout.addComponent(combobox);
	}

	private void removeEmptySelection(VerticalLayout layout) {
		ComboBox combobox = new ComboBox("Select your gender:");
		combobox.addItems("Select", "Male", "Female", "Other");

		//Remove empty option
		combobox.setNullSelectionAllowed(false);

		layout.addComponent(combobox);
	}

	private void preSelectValue(VerticalLayout layout) {
		ComboBox combobox = new ComboBox("Select your gender:");
		combobox.addItems("Select", "Male", "Female", "Other");

		//Remove empty option
		combobox.setNullSelectionAllowed(false);

		//Pre-set some value
		combobox.setValue("Male");

		layout.addComponent(combobox);
	}

	private void addFiltering(VerticalLayout layout) {
		ComboBox combobox = new ComboBox("Select your gender:");
		combobox.addItems("Select", "Male", "Female", "Other");

		combobox.setNullSelectionAllowed(false);

		//Add Filtering
		combobox.setFilteringMode(FilteringMode.CONTAINS);

		layout.addComponent(combobox);
	}

	private void editableCombobox(VerticalLayout layout) 
	{
		Label label = new Label();

		ComboBox combobox = new ComboBox("Select your gender:");
		combobox.addItems("Select", "Male", "Female", "Other");
		combobox.setNullSelectionAllowed(false);

		//Allow new Items
		combobox.setNewItemsAllowed(true);
		combobox.setImmediate(true);

		combobox.setNewItemHandler(new NewItemHandler(){

			private static final long serialVersionUID = 1L;

			@Override
			public void addNewItem(String newItemCaption) {
				combobox.addItem(newItemCaption);
				combobox.setValue(newItemCaption);
				label.setValue("You added : " + newItemCaption);
			}
		});

		layout.addComponent(label);
		layout.addComponent(combobox);
	}

	@VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
	public static class MyUIServlet extends VaadinServlet {
		private static final long serialVersionUID = -2718268186398688122L;
	}
}

源码下载

学习愉快!

参考文献:

查看ComboBox

查看图书示例

查看 Wiki

vaadin 文本字段示例

原文: https://howtodoinjava.com/vaadin/vaadin-text-field-examples/

学习这些简单易懂的代码示例,以设置,设置样式和验证 vaadin 文本字段控件。

Table of Contents

Vaadin text field label
Vaadin text field width and height
Vaadin text field size limit
Vaadin text field focus
Vaadin text field change event listener
Vaadin text field empty validation
Vaadin text field number validation
Vaadin text field CSS styling
Source Code Download Link

Vaadin 文本字段标签

Vaadin text field label

Vaadin 文本字段标签

您可以通过两种方式为文本字段设置标签。

  1. 将文本字段标签设置为构造器参数

    final TextField txtFldOne = new TextField("Type your name here:");
    
  2. 使用setCaption()方法设置文本字段标签

    final TextField txtFldOne = new TextField();
    
    //Set Text Field Label
    txtFldOne.setCaption("Type your name here:");
    
    

Vaadin 文本字段的宽度和高度

Vaadin text field size

Vaadin 文本字段大小

要设置文本字段的宽度和高度,请使用setXXX方法。

final TextField txtFldOne = new TextField();
txtFldOne.setCaption("Field size example 1");

//Set width and height
txtFldOne.setWidth(300, Unit.PIXELS);
txtFldOne.setHeight(50, Unit.PIXELS);

layout.addComponents(txtFldOne);

或者

final TextField txtFldTwo = new TextField();
txtFldTwo.setCaption("Field size example 2");

txtFldTwo.setWidth("300px");
txtFldTwo.setHeight("50px");

layout.addComponents(txtFldTwo);

Vaadin 文本字段大小限制

Vaadin text field limit

Vaadin 文本字段限制

要在文本字段中设置最大字符数限制,请使用setMaxLength(int length)方法。

final TextField txtFldOne = new TextField();
txtFldOne.setCaption("Field limit example");

txtFldOne.setMaxLength(10);

layout.addComponents(txtFldOne);

Vaadin 文本字段焦点

Vaadin text field focus

Vaadin 文本字段焦点

要使焦点集中在文本字段上,请使用focus()方法。

final TextField txtFldOne = new TextField();
txtFldOne.setCaption("Field focus example");

txtFldOne.focus();

layout.addComponents(txtFldOne);

Vaadin 文本字段更改事件监听器

Vaadin text field change events

Vaadin 文本字段更改事件

要处理文本字段中的任何值更改事件,您可以附加两个事件监听器。

  1. 值更改事件监听器

    当文本字段中发生值更改时,或在编辑后失去焦点时,将触发值更改事件。 使用ValueChangeListener处理值更改事件。

    final TextField txtFld = new TextField();
    txtFld.setCaption("Field value change listner example");
    
    txtFld.focus();
    txtFld.setInputPrompt("Enter any text");
    
    txtFld.addValueChangeListener(new Property.ValueChangeListener() 
    {
    	private static final long serialVersionUID = 5678485350989070188L;
    
    	public void valueChange(ValueChangeEvent event) 
    	{
            String value = (String) event.getProperty().getValue();
            // Do something with the value. I am showing a tray notification
            Notification.show("Value is: " + value, Type.TRAY_NOTIFICATION);
        }
    });
    //Fire value changes immediately when the field loses focus
    txtFld.setImmediate(true);
    
    layout.addComponents(txtFld);
    
    
  2. 文本更改事件监听器

    有时,您可能希望尽快处理文本字段中的每个文本更改事件。 您可以使用TextChangeListener处理这些文本更改。

    final TextField txtFld = new TextField();
    txtFld.setCaption("Field text change listner example");
    
    txtFld.focus();
    txtFld.setMaxLength(20);
    txtFld.setInputPrompt("Enter any text");
    
    final Label counter = new Label();
    counter.setValue(txtFld.getValue().length() + " of " + txtFld.getMaxLength());
    
    txtFld.addTextChangeListener(new TextChangeListener() {
    	private static final long serialVersionUID = -8043238443902618783L;
    
    	public void textChange(TextChangeEvent event) {
            counter.setValue(event.getText().length() + " of " + txtFld.getMaxLength());
        }
    });
    
    txtFld.setTextChangeEventMode(TextChangeEventMode.EAGER);
    
    layout.addComponents(txtFld);
    layout.addComponents(counter);
    
    

Vaadin 文本字段空验证

Vaadin text field empty validation

Vaadin 文本字段空验证

要在文本字段上添加空字段验证,请使用setRequired()setRequiredError()setValidationVisible()并添加StringLengthValidator

final TextField txtFld = new TextField();
txtFld.setCaption("Empty field validation example");

txtFld.setInputPrompt("Enter your name");

txtFld.setRequired(true);
txtFld.setRequiredError("Please enter your name to proceed !!");
txtFld.addValidator(new StringLengthValidator("Must be less than 20 chars", 1, 20, false));
txtFld.setValidationVisible(true);

txtFld.setTextChangeEventMode(TextChangeEventMode.LAZY);
txtFld.setNullRepresentation("");

txtFld.addTextChangeListener(new FieldEvents.TextChangeListener() {
	private static final long serialVersionUID = 1L;

	@Override
	public void textChange(FieldEvents.TextChangeEvent event) {
		try {
			txtFld.setValue(event.getText());

			txtFld.setCursorPosition(event.getCursorPosition());

			txtFld.validate();
		} catch (InvalidValueException e) {
			//Log error
		}
	}
});

layout.addComponents(txtFld);

在上面的示例中,我们在每个按键事件上验证文本字段。 如果您只想在对焦时验证该字段,请使用ValueChangeEventListner

Vaadin 文本字段数字验证

Vaadin text field number min-max validation

Vaadin 文本字段数字最大最小验证

Vaadin text field number format validation

Vaadin 文本字段数字格式验证

如果要强制文本字段仅接受整数值,则可以使用IntegerRangeValidator类并针对文本更改事件进行验证。

final TextField txtFld = new TextField("Please enter only integer value");
layout.addComponent(txtFld);

//To convert string value to integer before validation
txtFld.setConverter(new StringToIntegerConverter());

//Add error message and min-max values allowed in field. If no limit than use null
txtFld.addValidator(new IntegerRangeValidator("The value has to be an integer", null, null));

//What if text field is empty - integer will be null in that case, so show blank when null
txtFld.setNullRepresentation("");

//Add validation hints in UI??
txtFld.setValidationVisible(true);

txtFld.setTextChangeEventMode(TextChangeEventMode.LAZY);

txtFld.addTextChangeListener(new FieldEvents.TextChangeListener() {
	private static final long serialVersionUID = 1L;

	@Override
	public void textChange(FieldEvents.TextChangeEvent event) {
		try {
			txtFld.setCursorPosition(event.getCursorPosition());
			txtFld.validate();
		} catch (InvalidValueException e) {
			//Log error
		}
	}
});

Vaadin 文本字段 CSS 样式

所有文本字段均添加了样式名称v-textfield,因此,如果您希望将特定的 CSS 应用于所有文本字段,则将其添加到v-textfield类中。

.v-textfield {
	//style to apply on all text fields
}

如果要在单个文本字段上应用某种样式,则可以将类名称(例如darkBorder)添加到文本字段,如下所示:

final TextField txtFld = new TextField("Custom Styled Text Box");
layout.addComponent(txtFld);

layout.addStyleName("darkBorder");

现在,您可以在 css 文件中定义样式规则。 样式名称将为{.v-textfield+添加到字段的自定义样式类}

.v-textfield-darkBorder { 
	border: 5px solid gray;
}

完整的源代码

现在,让我们看看上面为本教程编写的所有示例。

package com.howtodoinjava.vaadin.demo;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.data.validator.IntegerRangeValidator;
import com.vaadin.data.validator.StringLengthValidator;
import com.vaadin.event.FieldEvents;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Notification.Type;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

@Theme("mytheme")
public class MyUI extends UI {

	private static final long serialVersionUID = 1387172685749279538L;

	@Override
	protected void init(VaadinRequest vaadinRequest) {
		final VerticalLayout layout = new VerticalLayout();

		fieldLabelExample(layout);
		fieldSizeExample(layout);
		fieldLimitExample(layout);
		fieldFocusExample(layout);
		fieldValueChangeListenerExample(layout);
		fieldTextChangeListenerExample(layout);
		fieldNullValidationExample(layout);
		fieldNumberValidationExample(layout);
		customCSSExample(layout);

		layout.setMargin(true);
		layout.setSpacing(true);

		setContent(layout);
	}

	private void customCSSExample(VerticalLayout layout) {
		final TextField txtFld = new TextField("Custom Styled Text Box");
		layout.addComponent(txtFld);

		layout.addStyleName("darkBorder");
	}

	private void fieldNumberValidationExample(VerticalLayout layout) {

		final TextField txtFld = new TextField("Please enter only integer value");
		layout.addComponent(txtFld);

		//To convert string value to integer before validation
		txtFld.setConverter(new StringToIntegerConverter());

		//Add error message and min-max values allowed in field. If no limit than use null
		txtFld.addValidator(new IntegerRangeValidator("The value must be between 0 and 100", 0, 100));

		//What if text field is empty - integer will be null in that case, so show blank when null
		txtFld.setNullRepresentation("");

		//Add validation hints in UI??
		txtFld.setValidationVisible(true);

		txtFld.setTextChangeEventMode(TextChangeEventMode.LAZY);

		txtFld.addTextChangeListener(new FieldEvents.TextChangeListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void textChange(FieldEvents.TextChangeEvent event) {
				try {
					txtFld.setCursorPosition(event.getCursorPosition());
					txtFld.validate();
				} catch (InvalidValueException e) {
					//Log error
				}
			}
		});
	}

	private void fieldNullValidationExample(VerticalLayout layout) {

		final TextField txtFld = new TextField();
		txtFld.setCaption("Empty field validation example");

		txtFld.setInputPrompt("Enter your name");

		txtFld.setRequired(true);
		txtFld.setRequiredError("Please enter your name to proceed !!");
		txtFld.addValidator(new StringLengthValidator("Must be less than 20 chars", 1, 20, false));
		txtFld.setValidationVisible(true);

		txtFld.setTextChangeEventMode(TextChangeEventMode.LAZY);
		txtFld.setNullRepresentation("");

		txtFld.addTextChangeListener(new FieldEvents.TextChangeListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void textChange(FieldEvents.TextChangeEvent event) {
				try {
					txtFld.setValue(event.getText());

					txtFld.setCursorPosition(event.getCursorPosition());

					txtFld.validate();
				} catch (InvalidValueException e) {
					//Log error
				}
			}
		});

		layout.addComponents(txtFld);
	}

	private void fieldTextChangeListenerExample(VerticalLayout layout) {
		final TextField txtFld = new TextField();
		txtFld.setCaption("Field text change listner example");

		txtFld.focus();
		txtFld.setMaxLength(20);
		txtFld.setInputPrompt("Enter any text");

		final Label counter = new Label();
		counter.setValue(txtFld.getValue().length() + " of " + txtFld.getMaxLength());

		txtFld.addTextChangeListener(new TextChangeListener() {
			private static final long serialVersionUID = -8043238443902618783L;

			public void textChange(TextChangeEvent event) {
				counter.setValue(event.getText().length() + " of "
						+ txtFld.getMaxLength());
			}
		});

		txtFld.setTextChangeEventMode(TextChangeEventMode.EAGER);

		layout.addComponents(txtFld);
		layout.addComponents(counter);
	}

	private void fieldValueChangeListenerExample(VerticalLayout layout) {
		final TextField txtFld = new TextField();
		txtFld.setCaption("Field value change listner example");

		txtFld.focus();
		txtFld.setInputPrompt("Enter any text");

		txtFld.addValueChangeListener(new Property.ValueChangeListener() {
			private static final long serialVersionUID = 5678485350989070188L;

			public void valueChange(ValueChangeEvent event) {
				String value = (String) event.getProperty().getValue();
				// Do something with the value
				Notification.show("Value is: " + value, Type.TRAY_NOTIFICATION);
			}
		});
		// Fire value changes immediately when the field loses focus
		txtFld.setImmediate(true);

		layout.addComponents(txtFld);
	}

	private void fieldFocusExample(VerticalLayout layout) {
		final TextField txtFld = new TextField();
		txtFld.setCaption("Field focus example");

		txtFld.focus();

		layout.addComponents(txtFld);
	}

	private void fieldLimitExample(VerticalLayout layout) {
		final TextField txtFld = new TextField();
		txtFld.setCaption("Field limit example");

		txtFld.setMaxLength(10);

		layout.addComponents(txtFld);
	}

	private void fieldSizeExample(VerticalLayout layout) {
		final TextField txtFldOne = new TextField();
		txtFldOne.setCaption("Field size example 1");

		txtFldOne.setWidth(300, Unit.PIXELS);
		txtFldOne.setHeight(50, Unit.PIXELS);

		final TextField txtFldTwo = new TextField();
		txtFldTwo.setCaption("Field size example 2");

		txtFldTwo.setWidth("300px");
		txtFldTwo.setHeight("50px");

		layout.addComponents(txtFldOne);
		layout.addComponents(txtFldTwo);
	}

	private void fieldLabelExample(VerticalLayout layout) {
		final TextField txtFld = new TextField();
		// Set Text Field Label
		txtFld.setCaption("Type your name here:");
		layout.addComponents(txtFld);
	}

	@VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
	public static class MyUIServlet extends VaadinServlet {
		private static final long serialVersionUID = -2718268186398688122L;
	}
}

源代码下载

单击下面的链接下载该项目的源代码。

下载源码

资源:

Vaadin 文本字段组件

TextChangeListener

ValueChangeListener

StackOverflow

Gson – 序列化和反序列化包含自定义对象的HashMap

原文: https://howtodoinjava.com/gson/gson-serialize-deserialize-hashmap/

了解使用 Google Gson 库对HashMap进行序列化。 另请学习使用 Gson 将 JSON 字符串反序列化为包含自定义对象的HashMap,以便将字段值复制到适当的通用类型中。

这些转换可用于创建HashMap的深层克隆。

1.将包含通用类型的HashMap序列化为 JSON

使用 Gson 将HashMap序列化为 JSON 很容易。 转换HashMap之后,只需使用gson.toJson()方法即可获取 JSON 字符串。

使用 Gson 将HashMap转换为 JSON 字符串的 Java 程序。

HashMap<Integer, Employee> employeeMap = new HashMap<>();

employeeMap.put(1, new Employee(1l, "Alex", LocalDate.of(1990, 01, 01)));
employeeMap.put(2, new Employee(2l, "Bob", LocalDate.of(1990, 02, 01)));

//Deep clone
Gson gson = new Gson();
String jsonString = gson.toJson(employeeMap);

{
  "1": {
    "id": 1,
    "name": "Alex",
    "dob": {
      "year": 1990,
      "month": 1,
      "day": 1
    }
  },
  "2": {
    "id": 2,
    "name": "Bob",
    "dob": {
      "year": 1990,
      "month": 2,
      "day": 1
    }
  }
}

此处的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 + "]";
    }
}

2.将 JSON 转换为包含自定义对象的HashMap

要将 JSON 字符串反序列化回HashMap涉及两个步骤:

  1. 创建代表通用类型的com.google.gson.reflect.TypeToken。 它强制客户端创建此类的子类,即使在运行时也可以检索类型信息。
  2. 使用gson.fromJson()方法从 JSON 字符串获取HashMap

Java 程序将 JSON 解析为包含通用类型的HashMap对象。

import java.lang.reflect.Type;
import java.time.LocalDate;
import java.util.HashMap;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class GsonExample 
{
    public static void main(String[] args) 
    {
        HashMap<Integer, Employee> employeeMap = new HashMap<>();

        employeeMap.put(1, new Employee(1l, "Alex", LocalDate.of(1990, 01, 01)));
        employeeMap.put(2, new Employee(2l, "Bob", LocalDate.of(1990, 02, 01)));

        //Deep clone
        Gson gson = new Gson();
        String jsonString = gson.toJson(employeeMap);

        Type type = new TypeToken<HashMap<Integer, Employee>>(){}.getType();
        HashMap<Integer, Employee> clonedMap = gson.fromJson(jsonString, type); 

        System.out.println(clonedMap);
    }
}

{1=Employee [id=1, name=Alex, dob=1990-01-01], 2=Employee [id=2, name=Bob, dob=1990-02-01]}

学习愉快!

Vaadin Spring Security BasicAuth 示例

原文: https://howtodoinjava.com/vaadin/vaadin-spring-security-basicauth-example/

在本教程中,我们将学习在 spring security 模块提供的基本认证安全性之后保护 vaadin 应用的安全。

我正在使用 Spring 安全配置更新 vaadin helloworld 应用源代码,因此,如果您已经有任何 vaadin 应用,则可以直接查看 Spring 安全性部分。

Table of Contents

Development environment
Spring Security BasicAuth Configuration
Vaadin UI Configuration
Maven Dependencies
Run the application

开发环境

本示例使用以下工具和框架来构建演示 vaadin 应用,该应用在 spring 的基本认证安全性的保护下得到保护。

  1. JDK 1.8
  2. Vaadin 7.7.0
  3. Spring Security 4.1.3.RELEASE
  4. Eclipse Luna
  5. Tomcat 7

Spring Security BasicAuth 配置

要配置 spring basicauth 安全性,您需要在类路径中添加applicationContext.xml文件(如果尚不存在),然后需要配置安全性设置,例如,安全的 URL 模式,哪些角色可以访问哪些 URL 等。

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-4.1.xsd">

	<http auto-config="true">
		<intercept-url pattern="/vaadinServlet/**" access="hasRole('ROLE_EDITOR')" />
		<intercept-url pattern="/vaadinServlet/*.*" access="hasRole('ROLE_EDITOR')" />
		<intercept-url pattern="/**" access="hasRole('ROLE_EDITOR')" />
		<http-basic />
		<csrf disabled="true"/>
	</http>

	<authentication-manager>
		<authentication-provider>
			<user-service>
				<user name="howtodoinjava" password="password" authorities="ROLE_EDITOR" />
			</user-service>
		</authentication-provider>
	</authentication-manager>

</beans:beans>

现在,您需要在web.xml文件中配置springSecurityFilterChain,以便将安全性添加到应用中。 另外,如果您添加了新的applicationContext.xml文件,则还需要注册ContextLoaderListener

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

//Other configuration will be added here

</web-app>

Spring 基本认证配置已完成。 现在,您可以根据应用的要求修改各个部分。 例如。 您可能要从数据库中获取用户名/密码详细信息,然后可以在applicationContext.xml文件的authentication-provider中使用jdbc-user-service

Vaadin UI 配置

正如我已经提到的,我正在修改 vaadin helloworld 应用,它具有非常基本的功能。 只需在web.xml文件和带有标签的主页屏幕中进行VaadinServlet配置,以在认证成功的情况下显示成功消息。

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
     http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    //Spring security configuration as mentioned in above section

	<context-param>
		<description>Vaadin production mode</description>
		<param-name>productionMode</param-name>
		<param-value>false</param-value>
	</context-param>

	<servlet>
		<servlet-name>vaadinServlet</servlet-name>
		<servlet-class>com.vaadin.server.VaadinServlet</servlet-class>
		<init-param>
			<param-name>UI</param-name>
			<param-value>com.howtodoinjava.vaadin.demo.AppUI</param-value>
		</init-param>
	</servlet>

	<servlet-mapping>
		<servlet-name>vaadinServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

AppUI.java

package com.howtodoinjava.vaadin.demo;

import com.vaadin.annotations.Theme;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

@Theme("mytheme")
public class AppUI extends UI {

	private static final long serialVersionUID = 1387172685749279538L;

	@Override
	protected void init(VaadinRequest vaadinRequest) {
		final VerticalLayout layout = new VerticalLayout();

		Label label = new Label("Welcome to BasicAuth Secured Vaadin Application");
		layout.addComponent(label);

		layout.setMargin(true);
		layout.setSpacing(true);

		setContent(layout);
	}
}

Maven 依赖

应用非常重要的一部分是收集和配置运行时依赖项。 当我们使用 maven 时,我已将以下依赖项添加到现有的pom.xml文件中。

pom.xml

<!-- Spring Security -->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-core</artifactId>
	<version>${org.springframework.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${org.springframework.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${org.springframework.version}</version>
</dependency>

<!-- Commons Logging is required with Spring4.x -->
<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>1.2</version>
</dependency>

运行应用

现在,该应用已配置完毕,可以进行测试了。 在浏览器中点击应用 URL。

  1. 点击网址http://localhost:8080/VaadinExample/

    您将在浏览器弹出窗口中输入您的用户名和密码。

    Vaadin Spring Security BasicAuth Window

    Vaadin Spring Security BasicAuth 窗口

  2. 填写不正确的凭据并提交

    弹出字段将被清除,并再次询问用户名/密码。

  3. 填写正确的凭据并提交

    应用的主页将显示成功消息。

    Vaadin Spring Security BasicAuth Successful

    Vaadin Spring Security BasicAuth 成功

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

源码下载

资源:

Spring 安全参考

Vaadin HelloWorld 应用

RFC-2617 BasicAuth

SQL 教程

SQL – 不使用临时表删除重复行

原文: https://howtodoinjava.com/sql/how-to-remove-duplicate-rows-in-mysql-without-using-temporary-table/

作为开发人员,我们经常遇到必须处理数据库相关内容的情况。 通常,当客户端以 excel 工作表的形式向您发送其数据,并且在经过一些 excel 操作后将数据推送到数据库表时,便完成了该操作。 我也做了很多次。

这种方法面临的一个非常普遍的问题是,有时可能会导致重复行,因为发送的数据主要来自人力资源和财务等部门,而人们对这些数据标准化技术的了解并不充分 😃。

我将使用Employee表,其中的列名称为 ID,名称,部门和电子邮件。

以下是用于生成测试数据的 SQL 脚本。

Create schema TestDB;

CREATE TABLE EMPLOYEE
(
    ID INT,
    NAME Varchar(100),
    DEPARTMENT INT,
    EMAIL Varchar(100)
);

INSERT INTO EMPLOYEE VALUES (1,'Anish',101,'anish@howtodoinjava.com');
INSERT INTO EMPLOYEE VALUES (2,'Lokesh',102,'lokesh@howtodoinjava.com');
INSERT INTO EMPLOYEE VALUES (3,'Rakesh',103,'rakesh@howtodoinjava.com');
INSERT INTO EMPLOYEE VALUES (4,'Yogesh',104,'yogesh@howtodoinjava.com');

--These are the duplicate rows

INSERT INTO EMPLOYEE VALUES (5,'Anish',101,'anish@howtodoinjava.com');
INSERT INTO EMPLOYEE VALUES (6,'Lokesh',102,'lokesh@howtodoinjava.com');

解决方案:

DELETE e1 FROM EMPLOYEE e1, EMPLOYEE e2 WHERE e1.name = e2.name AND e1.id > e2.id;

上面的 sql 查询将删除名称字段重复的行,并且仅保留名称唯一且 ID 字段最低的唯一行。 例如,ID 为 5 和 6 的行将被删除,而 ID 为 1 和 2 的行将被保留。

delete-duplicate-rows-in-mysql

如果要保留具有最新生成的 ID 值的行,则将where子句中的条件反转为e1.id < e2.id,如下所示:

DELETE e1 FROM EMPLOYEE e1, EMPLOYEE e2 WHERE e1.name = e2.name AND e1.id > e2.id;

如果要比较多个字段并添加适当的where子句。

注意:请始终首先对测试数据执行以上(或修改)查询,以确保其产生预期的输出。

学习愉快!

查找员工的第 N 高薪的 SQL 查询

原文: https://howtodoinjava.com/sql/sql-query-find-nth-highest-salary/

如果您想担任初级职位,要找到的第 n 高薪水是一个非常常见的面试问题。 大多数人逐个字符地学习它,但是很少有人了解此查询的工作方式。 它的性能好坏是多少? 在这篇文章中,我们将学习这些东西。 我正在使用 MySQL 数据库进行演示。

Table of Contents

1\. Create the schema and populate table
2\. Write the query and verify the result
3\. How the query works
4\. Performance analysis

1.创建数据库架构并填充表

让我们创建一个Employee表的简单示例。 我们将用 ID 和雇员的工资填充此表。 我们将编写查询以在此表上找到第 n 高薪水。

创建一个新的数据库架构。

Create schema TestSQL;

创建一个新表Employee_Test

CREATE TABLE Employee_Test
(
    Employee_ID INT,
    Employee_name Varchar(100),
    Employee_Salary Decimal (10,2)
);

用测试数据填充表。

INSERT INTO Employee_Test VALUES (1,'Anish',1000);
INSERT INTO Employee_Test VALUES (2,'Sushant',1200);
INSERT INTO Employee_Test VALUES (3,'Rakesh',1100);
INSERT INTO Employee_Test VALUES (4,'Manoj',1300);
INSERT INTO Employee_Test VALUES (5,'Amit',1400);
INSERT INTO Employee_Test VALUES (6,'Lokesh',1600);
INSERT INTO Employee_Test VALUES (7,'Maneneder',1400);
INSERT INTO Employee_Test VALUES (8,'Narendra',400);
INSERT INTO Employee_Test VALUES (9,'Kaushal',1900);
INSERT INTO Employee_Test VALUES (10,'Vikas',3400);
INSERT INTO Employee_Test VALUES (11,'Sudhir',800);

2. 找到第 n 高薪水的 SQL 查询

因此,正如我们大多数人所知,查询是这样写的,以查找第 N 高薪水。 在下面的查询中,我们看到如何在不使用max函数的情况下在 sql 中找到最高薪水。

SELECT *
FROM Employee_Test Emp1
WHERE ( n ) = (
                SELECT COUNT( DISTINCT ( Emp2.Employee_Salary ) )
                FROM Employee_Test Emp2
                WHERE Emp2.Employee_Salary >= Emp1.Employee_Salary
            )

在此,将n替换为任何数字。 例如,如果您必须找到第六高的薪水,则将 n 替换为 6。

SELECT *
FROM Employee_Test Emp1
WHERE (6) = (
SELECT COUNT( DISTINCT ( Emp2.Employee_Salary ) )
FROM Employee_Test Emp2
WHERE Emp2.Employee_Salary >= Emp1.Employee_Salary
)

上面的查询将产生下面的结果。

nth_highest_salary

3.查询的工作方式

如我们所见,该查询涉及内部查询的使用。 内部查询可以有两种类型。 相关的不相关的查询。 不相关查询是内部查询可以独立于外部查询运行的地方,而相关查询是内部查询与外部查询一起运行的地方。 我们的第 n 高薪是相关查询的示例。

最好先了解一下内部查询每次都会执行,处理外部查询中的一行。 内部查询基本上不会做任何非常秘密的工作,它只会返回比当前处理行的薪水列高的不同薪水的计数。 任何时候,它都会发现外部查询中当前行的薪水列的值等于内部查询中较高薪水的计数,并返回结果。

4.性能分析

正如我们从上面了解到的那样,每次执行内部查询都会处理一行外部查询,这会带来很多性能开销,特别是在行数太大的情况下。

为了避免这种情况,应该使用特定于数据库的关键字来更快地获得结果。 例如在 SQL Server 中,可以这样使用关键字TOP

如何在 SQL Server 中找到第 n 高薪水。

SELECT TOP 1 EMPLOYEE_SALARY
FROM
(
    SELECT DISTINCT TOP N EMPLOYEE_SALARY
    FROM EMPLOYEE_TEST
    ORDER BY EMPLOYEE_SALARY DESC
) A
ORDER BY EMPLOYEE_SALARY
WHERE N > 1

学习愉快!

SQLException:用户root@localhost的访问被拒绝

原文: https://howtodoinjava.com/sql/sqlexception-access-denied-for-user-rootlocalhost-after-re-installation-of-mysql-server/

重新安装 MySQL 服务器后,您是否曾经遇到过用户拒绝访问的问题或任何与登录有关的问题。 我遇到了这个问题,当我用 Google 搜索它时,我看到许多其他人也经常遭受这个问题的困扰。 在这篇文章中,我建议您尝试一下,它可以在 95% 的情况下解决此问题。

访问被拒绝错误的原因

用户“root”问题的访问被拒绝错误通常发生在您卸载并安装新版本甚至旧版本时。 问题在于由 mysql 卸载程序向导完成的不完整清理。 因此,解决方案也很简单,手动清除剩余的垃圾。

您的异常将如下所示:


Caused by: java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
 at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1055)
 at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)
 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)
 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:910)
 at com.mysql.jdbc.MysqlIO.secureAuth411(MysqlIO.java:3923)
 at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1273)
 at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2031)
 at com.mysql.jdbc.ConnectionImpl.init(ConnectionImpl.java:718)
 at com.mysql.jdbc.JDBC4Connection.init(JDBC4Connection.java:46)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
 at java.lang.reflect.Constructor.newInstance(Unknown Source)
 at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
 at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:302)
 at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:282)
 at java.sql.DriverManager.getConnection(Unknown Source)
 at java.sql.DriverManager.getConnection(Unknown Source)
 at org.hibernate.connection.DriverManagerConnectionProvider.getConnection(DriverManagerConnectionProvider.java:110)
 at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:417)
 ... 5 more

解决方案

解决访问被拒绝错误的方法是从以下位置删除旧数据文件夹。

C:/Documents and Settings/All Users/Application Data/MySQL(参见此处)

该目录包含临时文件和旧 DB 数据,包括您在先前安装中使用的最新登录凭据。 因此,要解决此问题:

  1. 卸载 MYSQL 服务器
  2. 从上方文件夹中删除所有内容
  3. 安装新的 MySQL 服务器

您应该可以在服务器实例中登录。

学习愉快!

Struts2 教程

Struts2 HelloWorld 示例

原文: https://howtodoinjava.com/struts2/struts-2-hello-world-example-application/

在我以前的文章中,我写了许多关于 JAX-RS RESTEasySpring3Hibernate 和其他 Java 框架,例如 Mavenjunit 的示例和教程。 我也有很多要求在 Struts2 上写一些东西。 好吧,这里我从世界示例开始。 在下一篇文章中,我将尝试涵盖涉及 Struts2 的最大领域和概念。因此,请继续关注。

Sections in this post:
Create maven web project
Struts2 dependencies
web.xml changes
Know struts.xml configuration file
Using struts.properties file
Writing first action class
Composing view files
Testing the application
源码下载

创建 Maven Web 项目

我在这里不会吃太多空间。 您可以阅读有关如何创建 Maven Eclipse Web 项目的更多详细信息。 简而言之,使用以下命令。

mvn archetype:generate -DgroupId=com.howtodoinjava.struts2.example -DartifactId=struts2example -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

cd struts2example

mvn eclipse:eclipse -Dwtpversion=2.0

Struts2 依赖项

我正在使用 maven 导入 Struts2 运行时依赖项。 这样做的好处是,您无需手动记住和寻找必需的依赖项,一举就能掌握所有内容。

	<dependency>
		<groupId>org.apache.struts</groupId>
		<artifactId>struts2-core</artifactId>
		<version>2.3.15.1</version>
	</dependency>

如果您想查看所有包含的内容,请查看下图:(获取最新版本的 jar 文件)

Struts2 jar files

Struts2 jar 文件

修改后的web.xml

需要以某种方式将 Struts 插入您的 Web 应用。 这意味着应将对应用的传入请求移交给 strut 进行处理。 这是通过在web.xml文件中添加过滤器定义来完成的。 此筛选器实质上将所有传入请求重定向到StrutsPrepareAndExecuteFilter,然后使用配置来处理该请求。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
  	<display-name>Archetype Created Web Application</display-name>

	<filter>
	    <filter-name>struts2</filter-name>
	    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>
	<filter-mapping>
	    <filter-name>struts2</filter-name>
	    <url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

了解struts.xml配置文件

因此,StrutsPrepareAndExecuteFilter有一个要处理的请求。 怎么办? 它将使用该配置来知道如何处理特定请求。 此配置在 struts.xml 文件中定义。 该文件将具有特定于应用工作流及其操作处理器的 url 映射。 它还定义了输入/成功/错误视图。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
	<include file="struts-default.xml"/>
	<package name="default" extends="struts-default">
	    <action name="">
            <result>/WEB-INF/jsp/index.jsp</result>
        </action>
	   <action name="testAction" class="com.howtodoinjava.struts2.example.web.TestAction">
	      <result name="input">/WEB-INF/jsp/index.jsp</result>
	      <result name="success">/WEB-INF/jsp/success.jsp</result>
	      <result name="error">/WEB-INF/jsp/index.jsp</result>
	   </action>
	</package>
</struts>

使用struts.properties文件

Struts 使用一些默认属性在运行时配置其行为。 您可以在stuts.properties文件中覆盖这些默认值。

#see http://struts.apache.org/2.0.14/docs/strutsproperties.html
struts.devMode=true
//message resource
struts.custom.i18n.resources=global

编写第一个动作类

这很重要,因为您将在此处编写实际的应用逻辑。 Struts2 动作通常扩展ActionSupport类,这些类提供了一些方法来覆盖和更改应用流,并在两者之间注入业务逻辑。

package com.howtodoinjava.struts2.example.web;

import java.util.Date;
import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("serial")
public class TestAction extends ActionSupport 
{
	private String name;
    private Date nowDate;

    @Override
    public void validate(){
        if (name==null || name.length()==0)
            addActionError(getText("error.enter.message"));
    }

    @Override
    public String execute() throws Exception {
        nowDate = new Date();
        return ActionSupport.SUCCESS;
    }
    public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Date getNowDate() {
        return nowDate;
    }
}

注意: Struts2 动作看起来像 POJO 类,因为它们必须充当动作形式,它们也是 Struts1 中的单独实体。

构建视图文件

这是一般步骤,涉及编写视图层,例如,在我们的例子中,我们正在编写 jsp 文件。 您可以使用消息资源从属性文件中获取消息,稍后在 i18n 中提供帮助。

index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <title>Struts2 hello world example</title>
        <s:head/>
    </head>

    <body>
        <h1><s:text name="welcome" /></h1>
        <s:if test="hasActionErrors()">
	        <div id="fieldErrors">
	            <s:actionerror/>
	        </div>
        </s:if>
        <s:form action="testAction" namespace="/" method="post" name="myForm" theme="xhtml">
            <s:textfield name="name" size="40" maxlength="40" key="your.message-label"/>
            <s:submit key="submit" />
        </s:form>
    </body>
</html>	

success.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <title>Success Screen !!</title>
    </head>
    <body>
        <h2>Thank you for running this demo on <s:property value="nowDate" /></h2>
        <p>
            Your name recorded was: <h3><s:property value="name" /></h3>
        </p>
    </body>
</html>

他们正在使用的消息资源文件是:

global.properties

submit=Submit
your.message-label=Your Name
welcome=Welcome to Struts2!
error.enter.message=Please enter your name !!

测试应用

现在好运行我们的 helloworld 应用。 让我们点击浏览器。

键入http://localhost:8080/struts2example/,然后按Enter

struts2-hello-world-1

按提交而不输入任何内容

struts2-hello-world-2

输入您的姓名,然后按提交

struts2-hello-world-3

这就是 Struts2 helloworld 应用的全部朋友。 如果要下载本教程的源代码,请遵循以下给定的下载链接。

源码下载

学习愉快!

Struts2 HelloWorld 注解示例

原文: https://howtodoinjava.com/struts2/struts-2-hello-world-with-annotations/

在我以前的文章中,我写了许多关于 JAX-RS RESTEasySpring3Hibernate 和其他 Java 框架,例如 Mavenjunit 的示例和教程。 我还写了与 Struts2 helloworld 和 xml 配置 相关的帖子。 在本文中,我将更新先前的示例,以使用注解来配置 Struts2 应用。

有关信息,struts 注解是 struts 常规插件的一部分。

Sections in this post: 
Create maven web project
Struts2 dependencies
web.xml changes
Writing first action class
Composing view files
Testing the application
源码下载

创建 Maven Web 项目

我在这里不会吃太多空间。 您可以阅读有关如何创建 Maven Eclipse Web 项目的更多详细信息。 简而言之,使用以下命令。

mvn archetype:generate -DgroupId=com.howtodoinjava.struts2.example -DartifactIad=struts2example
-DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

cd struts2example

mvn eclipse:eclipse -Dwtpversion=2.0

Struts2 依赖项

我正在使用 maven 导入 Struts2 运行时依赖项。 这样做的好处是,您无需手动记住和寻找必需的依赖项,一举就能掌握所有内容。

<dependency>
		<groupId>org.apache.struts</groupId>
		<artifactId>struts2-core</artifactId>
		<version>2.3.15.1</version>
	</dependency>
	<dependency>
        <groupId>org.apache.struts</groupId>
	  	<artifactId>struts2-convention-plugin</artifactId>
	  	<version>2.3.15.1</version>
    </dependency>
</dependencies>

web.xml的更改

需要以某种方式将 Struts 插入您的 Web 应用。 这意味着应将对应用的传入请求移交给 strut 进行处理。 这是通过在web.xml文件中添加过滤器定义来完成的。 此筛选器实质上将所有传入请求重定向到 StrutsPrepareAndExecuteFilter,然后使用配置来处理该请求。

另外,我传递了“actionPackages”初始化参数,以便可以扫描此包以查找必需的带注解的类。

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
  	<display-name>Archetype Created Web Application</display-name>

	<filter>
	    <filter-name>struts2</filter-name>
	    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	    <init-param>
			<param-name>actionPackages</param-name>
			<param-value>com.howtodoinjava.struts2.example.web</param-value>
		</init-param>
	</filter>
	<filter-mapping>
	    <filter-name>struts2</filter-name>
	    <url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

编写第一个类

这很重要,因为您将在此处编写实际的应用逻辑。 Struts2 操作通常扩展ActionSupport类,该类提供了一些方法来覆盖和更改应用流,并在两者之间注入业务逻辑。

@Namespace("/default")
@ResultPath(value="/")
@Results({
	   @Result(name="success", location="WEB-INF/jsp/success.jsp"),
	   @Result(name="input", location="WEB-INF/jsp/index.jsp")
})
public class TestAction extends ActionSupport
{
	private static final long serialVersionUID = 1L;

	private String name;

    private Date nowDate;

    @Override
    public void validate(){
        if (name==null || name.length()==0)
            addActionError(getText("error.enter.message"));
    }

    @Actions({
        @Action("/"),
        @Action("/test")
    })
    @Override
    public String execute() throws Exception {
        nowDate = new Date();
        return ActionSupport.SUCCESS;
    }

    public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Date getNowDate() {
        return nowDate;
    }
}

注意:Struts2 动作看起来像 POJO 类,因为它们也必须充当动作形式,它们也是 Struts1 中的单独实体。

构建视图文件

这是一般步骤,涉及编写视图层,例如,在我们的例子中,我们正在编写 jsp 文件。 您可以使用消息资源从属性文件中获取消息,稍后在 i18n 中提供帮助。

index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <title>Struts2 hello world example</title>
        <s:head/>
    </head>

    <body>
        <h1><s:text name="welcome" /></h1>
        <s:if test="hasActionErrors()">
	        <div id="fieldErrors">
	            <s:actionerror/>
	        </div>
        </s:if>
        <s:form action="test" namespace="/" method="post" name="myForm" theme="xhtml">
            <s:textfield name="name" size="40" maxlength="40" key="your.message-label"/>
            <s:submit key="submit" />
        </s:form>
    </body>
</html>

success.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
    <head>
        <title>Success Screen !!</title>
    </head>
    <body>
        <h2>Thank you for running this demo on <s:property value="nowDate" /></h2>
        <p>
            Your name recorded was: <h3><s:property value="name" /></h3>
        </p>
    </body>
</html>

我正在使用的消息资源文件是:

TestAction.properties

submit=Submit
your.message-label=Your Name
welcome=Welcome to Struts2!
error.enter.message=Please enter your name !!

测试应用

现在好运行我们的 helloworld 应用。 让我们点击浏览器。

键入http://localhost:8080/struts2example/,然后按Enter

struts2-hello-world-1

按提交而不输入任何内容

struts2-hello-world-2

输入您的姓名,然后按提交

struts2-hello-world-3

项目的文件/文件夹树

struts-folder-tree

就是这个带有注解的 Struts2 helloworld 应用的朋友。 如果要下载本教程的源代码,请按照以下给定的下载链接进行操作。

源码下载

学习愉快!

使用@InterceptorRef的 Struts2 自定义拦截器示例

原文: https://howtodoinjava.com/struts2/struts-2-custom-interceptor-with-interceptorref-example/

在以前的帖子中,我们了解了 helloworld 应用和设置 Struts2 应用的结果路径。 现在,在这篇文章中,我将举一个使用注解的自定义或用户定义的拦截器配置示例。

拦截器是一个类,其每次访问已配置的服务器资源时都会调用其预定义方法。 这可以在资源访问之前或之后访问。 请注意,此处的资源访问也意味着 HTTP 请求处理。

Steps summary:
1) Create custom interceptor class which implements com.opensymphony.xwork2.interceptor.Interceptor
2) Implement overridden methods
3) Give definition in struts.xml file
4) Apply on any Action class

重要:请注意,Struts2 随附了许多现成的拦截器实现,因此请确保您确实需要创建自己的拦截器,或者有什么适合您的。

1)& 2)创建自定义拦截器类并实现覆盖的方法

以下是自定义拦截器的示例代码,以及所有替代方法的实现。 请随时以适当的方法添加应用逻辑。

package com.howtodoinjava.struts2.example.web;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

public class DemoCustomInterceptor implements Interceptor
{
	private static final long serialVersionUID = 1L;

	@Override
	public void destroy() {
		System.out.println("DemoCustomInterceptor destroy() is called...");
	}

	@Override
	public void init() {
		System.out.println("DemoCustomInterceptor init() is called...");
	}

	@Override
	public String intercept(ActionInvocation invocation) throws Exception 
	{
		System.out.println("DemoCustomInterceptor intercept() is called...");
		System.out.println(invocation.getAction().getClass().getName());
		return invocation.invoke();
	}
}

3)在struts.xml文件中给出定义

这是非常重要的一步,因为在这里您可以在 struts 上下文和运行时中注册拦截器类。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
	<package name="default" namespace="/" extends="struts-default">
		<interceptors>	
			<interceptor name="demoCustomInterceptor" class="com.howtodoinjava.struts2.example.web.DemoCustomInterceptor" />
			<interceptor-stack name="customStack">
	     		<interceptor-ref name="demoCustomInterceptor"/>
				<interceptor-ref name="defaultStack" />
        	</interceptor-stack>
	    </interceptors>	    
	</package>

	<constant name="struts.convention.result.path" value="/WEB-INF/jsp/" />
	<constant name="struts.devMode" value="true" />
	<constant name="struts.action.extension" value="action," />
	<constant name="struts.custom.i18n.resources" value="test" />
	<constant name="struts.convention.default.parent.package" value="default"/>

</struts>

4)应用任何动作类

我在一个这样的TestAction类中应用了上面的拦截器。

package com.howtodoinjava.struts2.example.web;

import java.util.Date;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
import org.apache.struts2.convention.annotation.InterceptorRef;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

import com.opensymphony.xwork2.ActionSupport;

@InterceptorRef(value="customStack")
@Results({
	   @Result(name="success", location="success.jsp"),
	   @Result(name="input", location="index.jsp")
})
public class TestAction extends ActionSupport 
{
	private static final long serialVersionUID = 1L;

	private String name;
    private Date nowDate;

    @Override
    public void validate(){
        if (name==null || name.length()==0)
            addActionError(getText("error.enter.message"));
    }
    @Actions({
        @Action("/"),
        @Action("/test")
    })
    @Override
    public String execute() throws Exception {
        nowDate = new Date();
        return ActionSupport.SUCCESS;
    }
    public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Date getNowDate() {
        return nowDate;
    }
}

请注意,我已经使用@InterceptorRef(value="customStack")定义应用了拦截器。 它可以帮助您一次性应用多个拦截器。 如果仅只需要特定的拦截器,请使用其名称,如下所示:@InterceptorRef(value="demoCustomInterceptor")

测试应用

当我运行上面的应用时。 我观察到服务器在控制台中的日志如下:

INFO: Loading global messages from [test]

DemoCustomInterceptor init() is called...

//some logs
INFO: Server startup in 1146 ms

DemoCustomInterceptor intercept() is called...
com.howtodoinjava.struts2.example.web.TestAction

请点击下面的链接下载该项目的源代码。

祝您学习愉快!

Struts2 – 如何正确设置结果路径

原文: https://howtodoinjava.com/struts2/how-to-correctly-set-result-path-in-struts-2/

在这里,结果路径表示 Struts2 在执行 Action 类中的代码后将解析的 JSP 文件或其他视图文件的位置。 这些结果路径在Action类顶部的@Result注解的“位置”中提到。

一旦动作类完成执行,它将把控件传递给视图解析器。 视图解析器尝试查找需要渲染的视图文件的位置。 该解决方案主要可以通过两种方式提及:

1)将整个相对路径添加到“位置”属性

现在,在这里,一个简单的“/”可以改变整个含义,所以要小心。

A)在位置属性中带有“/”字符

@Namespace("/default")
@Results({
	   @Result(name="success", location="/WEB-INF/jsp/success.jsp"),
	   @Result(name="input", location="/WEB-INF/jsp/index.jsp")
})
public class TestAction extends ActionSupport 
{
	//More code...
} 

上面的定义会将 jsp 文件的位置解析为::

%PROJECT_PATH%/WEB-INF/jsp/success.jsp

B)位置属性中没有“/”字符

@Namespace("/default")
@Results({
	   @Result(name="success", location="WEB-INF/jsp/success.jsp"),
	   @Result(name="input", location="WEB-INF/jsp/index.jsp")
})
public class TestAction extends ActionSupport 
{
	//More code...
} 

上面的定义会将jsp文件的位置解析为:

%PROJECT_PATH%/WEB-INF/content/WEB-INF/jsp/index.jsp

原因:背后的原因是,开始时的“/”在项目根处解析,而其余路径在项目根目录下解析。 但是,当不在开始时使用“/”时,默认的根目录假定为“/WEB-INF/content”文件夹,其余的路径与此相对。 因此,当您下次注意时,请在@Result注解中指定location属性。

2)将资源根路径定义为常量“struts.convention.result.path

您可以在此常量中定义资源的固定部分,即 JSP 文件,然后只需指定 jsp 文件的名称即可,而不必担心其在项目中的位置。 另一个优点是,您以后可以将视图文件移动到其他文件夹中,甚至重命名文件夹名称,而不必担心其影响。 您只需要更改此常数中的路径。

可以在struts.xml中将该常量定义为:

<struts>
	<constant name="struts.convention.result.path" value="/WEB-INF/jsp/" />
</struts>

struts.properties文件中为:

	struts.convention.result.path=/WEB-INF/jsp/

指定此常量后,在Action类中,我们只需要指定 JSP 文件名即可。

@Namespace("/default")
@Results({
	   @Result(name="success", location="success.jsp"),
	   @Result(name="input", location="index.jsp")
})
public class TestAction extends ActionSupport 
{
	//More code...
}

推荐使用涉及常量“struts.convention.result.path”的第二种方法。

祝您学习愉快!

Gson – GsonBuilder配置示例

原文: https://howtodoinjava.com/gson/gson-gsonbuilder-configuration/

对于简单的用例,使用Gson gson = new Gson();及其标准配置就足够了。 但是,如果您打算自定义 Gson 的行为,则可以使用 GsonBuilder通过自定义配置创建新的 Gson 实例。

GsonBuilder类提供.create()方法,该方法返回Gson实例。

Gson gson = new GsonBuilder().create(); 

1. GsonBuilder.setPrettyPrinting() – 精美打印 JSON

默认情况下,Gson 将创建缩小的 JSON 字符串。 这对于减少通过网络传输的数据量非常重要。

但是,这种缩小的 JSON 对开发人员进行开发/调试应用时无济于事。 使用精美打印来格式化 JSON 输出。

Gson gson = new GsonBuilder()
		.setPrettyPrinting()
		.create(); 

2. FieldNamingPolicy

FieldNamingPolicy 枚举在序列化期间为 JSON 字段名称提供了几种标准命名约定。 它有助于Gson实例将 Java 字段名称正确转换为所需的 JSON 字段名称。

注意:请注意,以下任何命名约定均不会影响用@SerializedName注解的字段。

以下命名选项可用。 我们将验证使用User类的每个策略生成的名称。 为了示例目的,我们以不同的模式添加了字段名称,以便我们可以了解每种命名策略如何在不同策略下转换不同的名称。

public class User 
{
	private long id;
	private String first_Name;
	private String lastName;
	private String _email;
}

User user = new User(1, "Lokesh", "Gupta", "admin@howtodoinjava.com");

Gson gson = new GsonBuilder()
				.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
				.setPrettyPrinting().create(); 

System.out.println(gson.toJson(user));

2.1 FieldNamingPolicy.IDENTITY

与 Gson 一起使用此命名策略将确保字段名称不变。 这是默认行为。

{
  "id": 1,
  "first_Name": "Lokesh",
  "lastName": "Gupta",
  "_email": "admin@howtodoinjava.com"
}

2.2 FieldNamingPolicy.LOWER_CASE_WITH_DASHES

Gson 会将 Java 字段名称从其驼峰大小写形式修改为小写的字段名称,其中每个单词都用破折号(-)分隔。

{
  "id": 1,
  "first_-name": "Lokesh",
  "last-name": "Gupta",
  "_email": "admin@howtodoinjava.com"
}

2.3 FieldNamingPolicy.LOWER_CASE_WITH_DOTS

Gson 会将 Java 字段名称从其驼峰大小写形式修改为小写的字段名称,其中每个单词都用点(.)分隔。

{
  "id": 1,
  "first_.name": "Lokesh",
  "last.name": "Gupta",
  "_email": "admin@howtodoinjava.com"
}

2.4 FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES

Gson 会将 Java 字段名称从其驼峰大小写形式修改为小写的字段名称,其中每个单词都用下划线(_)分隔。

{
  "id": 1,
  "first__name": "Lokesh",
  "last_name": "Gupta",
  "_email": "admin@howtodoinjava.com"
}

2.5 FieldNamingPolicy.UPPER_CAMEL_CASE

Gson 将确保序列化为 JSON 格式的 Java 字段名称的第一个“字母”大写。

{
  "Id": 1,
  "First_Name": "Lokesh",
  "LastName": "Gupta",
  "_Email": "admin@howtodoinjava.com"
}

2.6 FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES

Gson 将确保在将 Java 字段名称的第一个“字母”序列化为 JSON 格式时将其大写,并且单词之间将使用空格分隔。

{
  "Id": 1,
  "First_ Name": "Lokesh",
  "Last Name": "Gupta",
  "_Email": "admin@howtodoinjava.com"
}

3. GsonBuilder.serializeNulls() – 空值的序列化

默认情况下,Gson 在序列化过程中会忽略null属性。 但是,有时我们想序列化具有空值的字段,以便它必须出现在 JSON 中。 为此,请使用serializeNulls()方法。

Employee employeeObj = new Employee(1, "Lokesh", "Gupta", null);

Gson gson = new GsonBuilder()
		.serializeNulls()
		.setPrettyPrinting().create(); 

System.out.println(gson.toJson(employeeObj));

程序输出。

{
  "id": 1,
  "firstName": "Lokesh",
  "lastName": "Gupta",
  "emailId": null
}

4. GsonBuilder.setExclusionStrategies()

ExclusionStrategy用于确定是否应序列化或反序列化字段或顶级类作为 JSON 输出/输入的一部分。

对于序列化,如果shouldSkipClass(Class)方法返回true,则该类或字段类型将不属于 JSON 输出。

对于反序列化,如果shouldSkipClass(Class)返回true,则不会将其设置为 Java 对象结构的一部分。

相同的规则应用于shouldSkipField(attribute)方法。

在下面的示例中,将从序列化和反序列化中排除带有@NPI注解和所有Account类实例的 Gson 成员字段。

Gson gson = new GsonBuilder()
	.setExclusionStrategies(new ExclusionStrategy() {
		@Override
		public boolean shouldSkipField(FieldAttributes f) {
			return f.getAnnotation(NPI.class) != null;
		}

		@Override
		public boolean shouldSkipClass(Class<?> clazz) {
			return clazz.getAnnotation(Account.class) != null;
		}
	})
	.setPrettyPrinting()
	.create(); 

5. GsonBuilder.setLenient() – 宽松的 JSON 语法规则

在反序列化期间,Gson 使用了一个不太宽大的JsonReader类。 这意味着它仅接受兼容的 JSON 输入。 如果 JSON 违反结构规则之一,它将抛出MalformedJsonException

如果我们将lenient设置为true,它将吞噬某些违规行为,并尝试最好甚至读取格式较差的 JSON。

Gson gson = new GsonBuilder()
		.setLenient()
		.setPrettyPrinting().create(); 

这些是一些最常用的GsonBuilder配置方法及其用法。 将您的问题留在我的评论中。

学习愉快!

Spring4 + Struts2 + Hibernate 集成教程

原文: https://howtodoinjava.com/struts2/spring-4-struts-2-hibernate-integration-tutorial/

以前,我已经介绍了 Spring3 + Hibernate 集成示例和 Struts2 helloworld 示例。 在本教程中,我将讨论将 spring 框架与 Struts 和 Hibernate 结合使用时要记住的所有重要点。 另外,请注意,本教程使用了其他次要但很重要的概念,例如日志记录,TLD 的使用和事​​务以及已集成到本教程中。 因此,请记住还要检查其详细信息。

在本教程中,我将使用简单的功能(例如全部获取,添加和删除)来构建员工管理屏幕。 它看起来像这样:

home screen for spring struts hibernate integration

我将按照以下步骤讨论集成过程:

1) Integration Overview 
2) Spring + Struts Integration
3) Spring + Hibernate Integration
4) Other Integrated Functionalities
    a) Log4j
    b) TLDs
    c) Transactions
5) Important Points to Keep Remember
6) Database Schema Used in Tutorial
7) 下载源码

1)概述

在进入集成细节之前,让我们先确定一下为什么我们需要此集成本身。 像 Struts 一样,Spring 也可以充当 MVC 实现。 两种框架都有其优缺点,仍然有很多人会同意 Spring 更好,并且提供了更广泛的功能。 对我来说,只有两种情况,您需要本教程中提供的信息:

i)您有一个用 Struts 编写的旧应用,并且想要使用 spring 来提高应用的功能很多倍。

ii)您确实想根据自己的原因来学习它。

否则,我不知道为什么有人会在 Spring 选择支柱。 如果您知道其他一些好的理由,请与我们所有人分享。 那挺棒的。

继续,在本教程中,我将委托从 Struts 到 Spring 进行动作管理。 进行委派的原因是,通过 Spring 上下文实例化Action类时,它可以使用 spring 在其自己的 MVC 实现中为其提供的Controller类的所有其他功能。 因此,您将获得所有 spring 功能以及 struts Action类,以具有包括ActionForm概念在内的控制器逻辑。

2)Spring + Struts 集成

这是核心逻辑,从在web.xml中注册ContextLoaderListenerStrutsPrepareAndExecuteFilter开始。 ContextLoaderListener带有初始化参数contextConfigLocation,并负责设置和启动 Spring WebApplicationContext。 现在,struts 将在与 Spring 相关的服务中特别是在依赖项注入中利用此上下文。

StrutsPrepareAndExecuteFilter在类路径中查找struts.xml文件,并配置 strut 的特定内容,例如动作映射,全局转发和其他在struts.xml文件中定义的内容。

web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://www.oracle.com/technetwork/java/index.html; id="WebApp_ID" version="2.5">

  <display-name>Spring+Struts+Hibernate Integration Example</display-name>
  	<welcome-file-list>
  		<welcome-file>/WEB-INF/index.jsp</welcome-file>
  	</welcome-file-list>

  	<!-- Specify the spring context information location;
  	Default location is applicationContext.xml file in classpath
  	 -->
  	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:beans.xml</param-value>
	</context-param>
	<!-- Bootstrap listener to start up and shut down Spring's root WebApplicationContext. -->
  	<listener>  
  		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
	</listener> 
	<!-- Handles both the preparation and execution phases of the Struts dispatching process. -->
	<filter>  
		<filter-name>struts2</filter-name>  
		<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>  
		<init-param>
			<param-name>debug</param-name>
			<param-value>0</param-value>
		</init-param>
		<init-param>
			<param-name>detail</param-name>
			<param-value>0</param-value>
		</init-param>
	</filter>  
	<filter-mapping>  
		<filter-name>struts2</filter-name>  
		<url-pattern>/*</url-pattern>  
	</filter-mapping>  
	<!-- Struts Tag Library Descriptors -->
	<jsp-config>
		<taglib>
			<taglib-uri>/tags/struts-bean</taglib-uri>
			<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
		</taglib>
		<taglib>
			<taglib-uri>/tags/struts-html</taglib-uri>
			<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
		</taglib>
		<taglib>
			<taglib-uri>/tags/struts-logic</taglib-uri>
			<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
		</taglib>
		<taglib>
			<taglib-uri>/tags/struts-nested</taglib-uri>
			<taglib-location>/WEB-INF/struts-nested.tld</taglib-location>
		</taglib>
	</jsp-config>
</web-app>

第二步,将在struts.xml文件中创建操作映射,如下所示:

struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">	

<struts>
    <!-- devMode is helpful when you want some extra logs for debugging -->
    <constant name="struts.devMode" value="false" />
    <!-- Global message resource; 
    	 Otherwise you will have seperate message resource for each Action 
    -->
    <constant name="struts.custom.i18n.resources" value="messages" /> 
    <!-- 
    	This is important if you are planning to have slashes in Action URLs
    	e.g. In this demo, employee is deleted using URL /delete/10
    	This this is set to false; then struts will try to find mapping for 
    	URL "/10" instaed of "/delete/10"
     -->
    <constant name="struts.enable.SlashesInActionNames" value="true"/>

    <!-- Normal Action mappings are defined here -->
	<package name="default" namespace="" extends="struts-default">
	    <!-- Two things to Notice: 
	    	 1) class is set to 'editEmployeeAction' which is bean defined by Spring context
	    	 2) We have given the method to be called here as well;
	   	-->
		<action name="list" class="editEmployeeAction" method="listEmployees">
			<result>/view/editEmployeeList.jsp</result>
		</action>
		<action name="add" class="editEmployeeAction" method="addEmployee">
			<result type="redirect">/list</result>
		</action>
		<action name="delete/*" class="editEmployeeAction" method="deleteEmployee">
		    <param name="employee.id">{1}</param>
			<result type="redirect">/list</result>
		</action>
		<action name="*" class="editEmployeeAction" method="listEmployees">
		  <result>/view/editEmployeeList.jsp</result>
		</action> 
	</package>

</struts>

在单独的 strut 应用程序中,我们将在“class”属性中具有完整的Action类及其包装信息。 在这里,我们将类名命名为editEmployeeAction。 它在哪里定义? 我们将要求 Spring 为我们查找。

Spring 上下文文件beans.xml是典型的 Spring 单独上下文文件,具有 Web 应用运行所需的所有内容,其中包括 struts 正在寻找的 bean 定义editEmployeeAction

beans.xml

<?xml  version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop/ http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jee/ http://www.springframework.org/schema/jee/spring-jee.xsd
        http://www.springframework.org/schema/lang/ http://www.springframework.org/schema/lang/spring-lang.xsd
        http://www.springframework.org/schema/tx/ http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/util/ http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- This bean has been referred fron struts.xml file; So type it correctly; -->
    <!-- Make scope prototype; This is really important. -->
    <bean name="editEmployeeAction" class="com.howtodoinjava.controller.EditEmployeeAction" scope="prototype">
	     <property name="employeeManager">
	        <ref bean="employeeManager"/>
	     </property>
    </bean>

    <!-- These beans are injected automatically by spring context -->
    <bean id="employeeDAO" class="com.howtodoinjava.dao.EmployeeDaoImpl">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="employeeManager" class="com.howtodoinjava.service.EmployeeManagerImpl">
        <property name="employeeDAO" ref="employeeDAO"/>
    </bean>

    <!-- Configure jdbc.properties -->
    <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
        p:location="/WEB-INF/jdbc.properties" />

    <!-- Data Source configuration -->
    <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.databaseurl}" p:username="${jdbc.username}"
        p:password="${jdbc.password}" />

	<!-- Configure hibernate session factory -->    
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation">
            <value>classpath:hibernate.cfg.xml</value>
        </property>
        <property name="configurationClass">
            <value>org.hibernate.cfg.AnnotationConfiguration</value>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${jdbc.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>
    <!-- Run SQL queries in transactions -->
    <tx:annotation-driven />
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

</beans>

这是我们要做的将 Struts 与 spring 框架集成在一起的所有步骤。 现在,您的动作类如下所示:

EditEmployeeAction.java

package com.howtodoinjava.controller;

import java.util.List;

import org.apache.log4j.Logger;

import com.howtodoinjava.entity.EmployeeEntity;
import com.howtodoinjava.service.EmployeeManager;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.Preparable;

public class EditEmployeeAction extends ActionSupport implements Preparable	
{
	private static final long serialVersionUID = 1L;

	//Logger configured using log4j
	private static final Logger logger = Logger.getLogger(EditEmployeeAction.class);

	//List of employees; Setter and Getter are below
	private List<EmployeeEntity> employees;

	//Employee object to be added; Setter and Getter are below
	private EmployeeEntity employee;

	//Employee manager injected by spring context; This is cool !!
	private EmployeeManager employeeManager;

	//This method return list of employees in database
	public String listEmployees() {
		logger.info("listEmployees method called");
		employees = employeeManager.getAllEmployees();
		return SUCCESS;
	}

	//This method will be called when a employee object is added
	public String addEmployee() {
		logger.info("addEmployee method called");
		employeeManager.addEmployee(employee);
		return SUCCESS;
	}

	//Deletes a employee by it's id passed in path parameter
	public String deleteEmployee() {
		logger.info("deleteEmployee method called");
		employeeManager.deleteEmployee(employee.getId());
		return SUCCESS;
	}

	//This method will be called before any of Action method is invoked;
	//So some pre-processing if required.
	@Override
	public void prepare() throws Exception {
		employee = null;
	}

	//Getters and Setters hidden
}

Spring 还将 DAO 引用注入到Manager类。

EmployeeManagerImpl.java

package com.howtodoinjava.service;

import java.util.List;

import org.springframework.transaction.annotation.Transactional;

import com.howtodoinjava.dao.EmployeeDAO;
import com.howtodoinjava.entity.EmployeeEntity;

public class EmployeeManagerImpl implements EmployeeManager 
{
	//Employee dao injected by Spring context
    private EmployeeDAO employeeDAO;

	//This method will be called when a employee object is added
	@Override
	@Transactional
	public void addEmployee(EmployeeEntity employee) {
		employeeDAO.addEmployee(employee);
	}

	//This method return list of employees in database
	@Override
	@Transactional
	public List<EmployeeEntity> getAllEmployees() {
		return employeeDAO.getAllEmployees();
	}
	//Deletes a employee by it's id
	@Override
	@Transactional
	public void deleteEmployee(Integer employeeId) {
		employeeDAO.deleteEmployee(employeeId);
	}

	//This setter will be used by Spring context to inject the dao's instance
	public void setEmployeeDAO(EmployeeDAO employeeDAO) {
		this.employeeDAO = employeeDAO;
	}
}

3)Spring + Hibernate 集成

现在我们必须将 Hibernate 集成到应用中。 最好的地方是利用 Spring 的强大功能进行集成,以充分利用依赖注入来与不同的 ORM 一起使用。

上面beans.xml文件中已经给出了 Spring 所需的 Hibernate 依赖关系。 您将需要的其他文件是:

hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <mapping class="com.howtodoinjava.entity.EmployeeEntity" />
    </session-factory>
</hibernate-configuration>

jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.dialect=org.hibernate.dialect.MySQLDialect
jdbc.databaseurl=jdbc:mysql://127.0.0.1:3306/test
jdbc.username=root
jdbc.password=password

EmployeeDaoImpl.java

package com.howtodoinjava.dao;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;

import com.howtodoinjava.entity.EmployeeEntity;

@Repository
public class EmployeeDaoImpl implements EmployeeDAO  
{
	//Session factory injected by spring context
    private SessionFactory sessionFactory;

    //This method will be called when a employee object is added
	@Override
	public void addEmployee(EmployeeEntity employee) {
		this.sessionFactory.getCurrentSession().save(employee);
	}

	//This method return list of employees in database
	@SuppressWarnings("unchecked")
	@Override
	public List<EmployeeEntity> getAllEmployees() {
		return this.sessionFactory.getCurrentSession().createQuery("from EmployeeEntity").list();
	}

	//Deletes a employee by it's id
	@Override
	public void deleteEmployee(Integer employeeId) {
		EmployeeEntity employee = (EmployeeEntity) sessionFactory.getCurrentSession()
										.load(EmployeeEntity.class, employeeId);
        if (null != employee) {
        	this.sessionFactory.getCurrentSession().delete(employee);
        }
	}

	//This setter will be used by Spring context to inject the sessionFactory instance
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
}

供您参考,EmployeeEntity类如下所示:

EmployeeEntity.java

package com.howtodoinjava.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="EMPLOYEE")
public class EmployeeEntity {

    @Id
    @Column(name="ID")
    @GeneratedValue
    private Integer id;

    @Column(name="FIRSTNAME")
    private String firstname;

    @Column(name="LASTNAME")
    private String lastname;

    @Column(name="EMAIL")
    private String email;

    @Column(name="TELEPHONE")
    private String telephone;

    //Setters and Getters
}

4)其他集成功能

除了 struts + spring + hibernate,我们还使用以下组件来构建应用。

a)Log4j

Spring 通过扫描类路径中的 log4j 自动配置日志记录。 我们使用pom.xml文件添加了 log4j 依赖项。 现在,您只需要在类路径中放置一个log4j.xmllog4j.properties文件。

log4j.properties

log4j.rootLogger=info, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=c:/log/demo.log
log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

b)顶级域

如果您查看web.xml文件,我们在其中包含了一些 TLD。 我们可以随时在视图层中使用它们,如下所示:

editEmployeeList.jsp

<%@ taglib prefix="s" uri="/struts-tags"%> 
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
    <title>Spring-4 + Struts-3 + Hibernate Integration Demo</title>
    <style>
	table.list
	{
		border-collapse:collapse;
		width: 40%;
	}
	table.list, table.list td, table.list th
	{
		border:1px solid gray;
		padding: 5px;
	}
	</style>
</head>
<body>

<h2>Spring-4 + Struts-3 + Hibernate Integration Demo</h2>

<s:form method="post" action="add">
    <table>
	    <tr>
	        <td><s:textfield key="label.firstname" name="employee.firstname"/></td> 
	    </tr>
	    <tr>
	        <td><s:textfield key="label.lastname" name="employee.lastname"/></td>
	    </tr>
	    <tr>
	        <td><s:textfield key="label.email" name="employee.email"/></td>
	    </tr>
	    <tr>
	        <td><s:textfield key="label.telephone" name="employee.telephone"/></td>
	    </tr>
	    <tr>
	        <td>
	        	<s:submit key="label.add"></s:submit>
	        </td>
	    </tr>
	</table> 
</s:form>

<h3>Employees</h3>
<c:if  test="${!empty employees}">
	<table class="list">
		<tr>
		    <th align="left">Name</th>
		    <th align="left">Email</th>
		    <th align="left">Telephone</th>
		    <th align="left">Actions</th>
		</tr>
		<c:forEach items="${employees}" var="emp">
		    <tr>
		        <td>${emp.lastname}, ${emp.firstname} </td>
		        <td>${emp.email}</td>
		        <td>${emp.telephone}</td>
		        <td><a href="delete/${emp.id}">delete</a></td>
		    </tr>
		</c:forEach>
	</table>
</c:if>

</body>
</html>

c)事务

EmployeeManagerImpl.java在诸如getAllEmployees()deleteEmployee()之类的方法中使用注解@Transactional。 这实际上是在单个事务中运行在此方法下执行的所有数据库查询。 像这样在beans.xml上下文文件中声明了事务依赖项。

<!-- Run SQL queries in transactions -->
<tx:annotation-driven />

<bean id="transactionManager"
	class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory" />
</bean>

5)要记住的要点

a)如果运行时无法找到lib文件夹中存在的类,则将来自项目依赖项的 jar 文件添加到项目部署程序集中。

b)使bean.xml中定义的Action类的作用域为“原型”。 Spring 提供的 bean 的默认范围是单例,并且必须为每个请求创建新的 Struts Action类,因为它包含特定于用户会话的数据。 为了容纳两者,请将Action类 bean 标记为原型。

c)在运行此应用之前,请不要忘记设置数据库。 如果安装不正确,将导致您出现一些异常。

d)另外,请在项目运行时依赖项中也包括struts2-spring-plugin

6)教程中使用的数据库架构

下表已在 MySQL 中的名为“test”的数据库中创建。

CREATE TABLE EMPLOYEE
(
    ID          INT PRIMARY KEY AUTO_INCREMENT,
    FIRSTNAME   VARCHAR(30),
    LASTNAME    VARCHAR(30),
    TELEPHONE   VARCHAR(15),
    EMAIL       VARCHAR(30),
    CREATED     TIMESTAMP DEFAULT NOW()
);

如果您打算自己构建应用,则在下面给定的直接在此应用中使用的结构将为您提供帮助。

directory structure for spring struts hibernate integration

Spring Struts Hibernate 集成的目录结构

此项目中使用的pom.xml文件具有所有项目相关性(有些额外),如下所示:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.howtodoinjava.app</groupId>
  <artifactId>Spring4Struts2HibernateIntegration</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Spring4Struts2HibernateIntegration Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <!-- JBoss repository for Hibernate -->
  <repositories>
	<repository>
		<id>JBoss repository</id>
		<url>http://repository.jboss.org/nexus/content/groups/public/</url>
	</repository>
  </repositories>
  <properties>
    <org.springframework.version>4.0.3.RELEASE</org.springframework.version>
  </properties>
  <dependencies>

	<dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

	<dependency>
		<groupId>org.apache.struts</groupId>
		<artifactId>struts2-core</artifactId>
		<version>2.3.16.2</version>
	</dependency>

   <dependency>
		<groupId>org.apache.struts</groupId>
		<artifactId>struts2-spring-plugin</artifactId>
		<version>2.3.16.2</version>
	</dependency>

     <!--
	    Core utilities used by other modules.
	    Define this if you use Spring Utility APIs (org.springframework.core.*/org.springframework.util.*)
	-->
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-core</artifactId>
	  <version>${org.springframework.version}</version>
	  <scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
		<version>${org.springframework.version}</version>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>${org.springframework.version}</version>
		<scope>runtime</scope>
	</dependency>

	<!--
	    Bean Factory and JavaBeans utilities (depends on spring-core)
	    Define this if you use Spring Bean APIs (org.springframework.beans.*)
	-->
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-beans</artifactId>
	  <version>${org.springframework.version}</version>
	</dependency>
	<!--
	    Application Context (depends on spring-core, spring-expression, spring-aop, spring-beans)
	    This is the central artifact for Spring's Dependency Injection Container and is generally always defined
	-->
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-context</artifactId>
	  <version>${org.springframework.version}</version>
	</dependency>
	<!--
	    Aspect Oriented Programming (AOP) Framework (depends on spring-core, spring-beans)
	    Define this if you use Spring AOP APIs (org.springframework.aop.*)
	-->
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-aop</artifactId>
	  <version>${org.springframework.version}</version>
	</dependency>
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjtools</artifactId>
		<version>1.6.2</version>
	</dependency>
	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
		<version>3.1</version>
	</dependency>

	<!--
	    Transaction Management Abstraction (depends on spring-core, spring-beans, spring-aop, spring-context)
	    Define this if you use Spring Transactions or DAO Exception Hierarchy
	    (org.springframework.transaction.*/org.springframework.dao.*)
	-->
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-tx</artifactId>
	  <version>${org.springframework.version}</version>
	</dependency>
	<!--
	    JDBC Data Access Library (depends on spring-core, spring-beans, spring-context, spring-tx)
	    Define this if you use Spring's JdbcTemplate API (org.springframework.jdbc.*)
	-->
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-jdbc</artifactId>
	  <version>${org.springframework.version}</version>
	</dependency>

	<!--
	    Object-to-Relation-Mapping (ORM) integration with Hibernate, JPA, and iBatis.
	    (depends on spring-core, spring-beans, spring-context, spring-tx)
	    Define this if you need ORM (org.springframework.orm.*)
	-->
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-orm</artifactId>
	  <version>${org.springframework.version}</version>
	</dependency>

	<dependency>  
       <groupId>log4j</groupId>  
       <artifactId>log4j</artifactId>  
       <version>1.2.15</version>  
       <exclusions>  
         <exclusion>  
           <groupId>javax.mail</groupId>  
           <artifactId>mail</artifactId>  
         </exclusion>  
         <exclusion>  
           <groupId>javax.jms</groupId>  
           <artifactId>jms</artifactId>  
         </exclusion>  
         <exclusion>  
           <groupId>com.sun.jdmk</groupId>  
           <artifactId>jmxtools</artifactId>  
         </exclusion>  
         <exclusion>  
           <groupId>com.sun.jmx</groupId>  
           <artifactId>jmxri</artifactId>  
         </exclusion>  
       </exclusions>  
       <scope>runtime</scope>  
     </dependency>  

     <dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>3.6.3.Final</version>
	</dependency>

	<dependency>
		<groupId>javassist</groupId>
		<artifactId>javassist</artifactId>
		<version>3.12.1.GA</version>
	</dependency>

 	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>jstl</artifactId>
		<version>1.2</version>
		<scope>runtime</scope>
	</dependency>

	<dependency>
		<groupId>taglibs</groupId>
		<artifactId>standard</artifactId>
		<version>1.1.2</version>
		<scope>runtime</scope>
	</dependency>

	<dependency>
	  <groupId>commons-dbcp</groupId>
	  <artifactId>commons-dbcp</artifactId>
	  <version>1.4</version>
	</dependency>

   <dependency>
	    <groupId>mysql</groupId>
	    <artifactId>mysql-connector-java</artifactId>
	    <version>5.1.9</version>
	</dependency>

  </dependencies>
  <build>
    <finalName>Spring3Struts2HibernateIntegration</finalName>
    <plugins>
		<plugin>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>2.3.2</version>
			<configuration>
				<source>1.6</source>
				<target>1.6</target>
			</configuration>
		</plugin>
	</plugins>
  </build>
</project>

7)下载源代码

下载以上示例的源代码或获取.war文件。

下载源码

下载.war文件

这是 Spring4 + Struts2 + hibernate 集成教程的全部内容。 让我知道您的想法和疑问。

祝您学习愉快!

[已解决] 无法找到ref-name引用的拦截器类

原文: https://howtodoinjava.com/struts2/solved-unable-to-find-interceptor-class-referenced-by-ref-name/

在为@InterceptorRef示例编写代码时,我才知道此功能。 我必须在struts.xml文件中声明拦截器定义,而我想通过注解使用拦截器。 第一次尝试时,失败并显示以下错误:

Unable to load configuration. - [unknown location]
	at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:70)
	at org.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:446)
	at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:490)
Caused by: Unable to find interceptor class referenced by ref-name customStack - [unknown location]
	at com.opensymphony.xwork2.config.providers.InterceptorBuilder.constructInterceptorReference(InterceptorBuilder.java:63)
	at org.apache.struts2.convention.DefaultInterceptorMapBuilder.buildInterceptorList(DefaultInterceptorMapBuilder.java:95)
	at org.apache.struts2.convention.DefaultInterceptorMapBuilder.build(DefaultInterceptorMapBuilder.java:86)
	at org.apache.struts2.convention.DefaultInterceptorMapBuilder.build(DefaultInterceptorMapBuilder.java:64)

原因:

默认情况下,Convention插件使用其自己的包Convention-default,其中不包含您在struts.xml中定义的包。 这意味着Convention将在其中放置您的操作的包不会扩展定义拦截器的包。

Random exceptions

随机异常

解决方案

要更改,您有两个选择:

  1. 使用@ParentPackage注解

  2. struts.xml中定义<constant name=”struts.convention.default.parent.package” value=”default”/ >

例如:

<struts>
	<package name="default" namespace="/" extends="struts-default">
		<interceptors>	
			<interceptor name="demoCustomInterceptor" class="com.howtodoinjava.struts2.example.web.DemoCustomInterceptor" />
			<interceptor-stack name="customStack">
	     		<interceptor-ref name="demoCustomInterceptor"/>
				<interceptor-ref name="defaultStack" />
        	</interceptor-stack>
	    </interceptors>	    
	</package>

	<constant name="struts.convention.result.path" value="/WEB-INF/jsp/" />
	<constant name="struts.devMode" value="true" />
	<constant name="struts.action.extension" value="action," />
	<constant name="struts.custom.i18n.resources" value="test" />

	<constant name="struts.convention.default.parent.package" value="default"/>

</struts>

这样可以解决问题。

祝您学习愉快!

[已解决]:找不到扩展名propertiesxml的结果类型

原文: https://howtodoinjava.com/struts2/solved-unable-to-find-a-result-type-for-extension-properties-or-xml/

如果您刚刚开始编写 Struts2 应用,并且正在配置它,那么您可能会遇到此异常。 完整的栈跟踪如下所示:

SEVERE: Dispatcher initialization failed
Unable to load configuration. - [unknown location]
at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:70)
at org.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:446)
at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:490)
at org.apache.struts2.dispatcher.ng.InitOperations.initDispatcher(InitOperations.java:74)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.init(StrutsPrepareAndExecuteFilter.java:57)
at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:277)
at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:258)
at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:382)
at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:103)
at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4650)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5306)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: Unable to find a result type for extension [properties] in location attribute [/struts.properties]. - [unknown location]
at org.apache.struts2.convention.DefaultResultMapBuilder$ResultInfo.determineType(DefaultResultMapBuilder.java:513)
at org.apache.struts2.convention.DefaultResultMapBuilder$ResultInfo.<init>(DefaultResultMapBuilder.java:476)
at org.apache.struts2.convention.DefaultResultMapBuilder.makeResults(DefaultResultMapBuilder.java:397)
at org.apache.struts2.convention.DefaultResultMapBuilder.createFromResources(DefaultResultMapBuilder.java:304)
at org.apache.struts2.convention.DefaultResultMapBuilder.build(DefaultResultMapBuilder.java:191)
at org.apache.struts2.convention.PackageBasedActionConfigBuilder.createActionConfig(PackageBasedActionConfigBuilder.java:935)
at org.apache.struts2.convention.PackageBasedActionConfigBuilder.buildConfiguration(PackageBasedActionConfigBuilder.java:718)
at org.apache.struts2.convention.PackageBasedActionConfigBuilder.buildActionConfigs(PackageBasedActionConfigBuilder.java:348)
at org.apache.struts2.convention.ClasspathPackageProvider.loadPackages(ClasspathPackageProvider.java:53)
at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:260)
at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:67)
... 18 more
Aug 14, 2013 12:24:55 AM org.apache.catalina.core.StandardContext filterStart
SEVERE: Exception starting filter struts2
Unable to load configuration. - [unknown location]
at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:502)
at org.apache.struts2.dispatcher.ng.InitOperations.initDispatcher(InitOperations.java:74)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.init(StrutsPrepareAndExecuteFilter.java:57)
at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:277)
at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:258)
at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:382)
at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:103)
at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4650)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5306)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: Unable to load configuration. - [unknown location]
at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:70)
at org.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:446)
at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:490)
... 16 more
Caused by: Unable to find a result type for extension [properties] in location attribute [/struts.properties]. - [unknown location]
at org.apache.struts2.convention.DefaultResultMapBuilder$ResultInfo.determineType(DefaultResultMapBuilder.java:513)
at org.apache.struts2.convention.DefaultResultMapBuilder$ResultInfo.<init>(DefaultResultMapBuilder.java:476)
at org.apache.struts2.convention.DefaultResultMapBuilder.makeResults(DefaultResultMapBuilder.java:397)
at org.apache.struts2.convention.DefaultResultMapBuilder.createFromResources(DefaultResultMapBuilder.java:304)
at org.apache.struts2.convention.DefaultResultMapBuilder.build(DefaultResultMapBuilder.java:191)
at org.apache.struts2.convention.PackageBasedActionConfigBuilder.createActionConfig(PackageBasedActionConfigBuilder.java:935)
at org.apache.struts2.convention.PackageBasedActionConfigBuilder.buildConfiguration(PackageBasedActionConfigBuilder.java:718)
at org.apache.struts2.convention.PackageBasedActionConfigBuilder.buildActionConfigs(PackageBasedActionConfigBuilder.java:348)
at org.apache.struts2.convention.ClasspathPackageProvider.loadPackages(ClasspathPackageProvider.java:53)
at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:260)
at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:67)
... 18 more

Random exceptions

解决方案

出现此异常是因为为属性“struts.convention.result.path” 配置了错误的路径,该路径通常如下所示:

<constant name="struts.convention.result.path" value="/" />

//or in properties file

struts.convention.result.path=/

好吧,你的路径不正确。 将其更正为 JSP 文件所在的任何有效位置。 例如:

<constant name="struts.convention.result.path" value="/WEB-INF/jsp/" />

//or in properties file

struts.convention.result.path=/WEB-INF/jsp/

它将解决此问题。

祝您学习愉快!

数据结构教程

使用数组的 Java 栈实现

原文: https://howtodoinjava.com/data-structure/java-stack-implementation-array/

本教程给出了使用数组实现栈数据结构的示例。 栈提供了将新对象放入栈(方法push())并从栈中获取对象(方法pop())。 栈根据后进先出(LIFO)返回对象。 请注意,JDK 提供了默认的 Java 栈实现作为类java.util.Stack

适用于所有栈实现的两个强制操作是:

  • push():将数据项放置在栈指针指向的位置。
  • pop():在其中删除数据项并从栈指针指向的位置返回该数据项。

某些严重依赖栈的环境可能会根据要求提供其他操作。 Stack 最常见的用法是表达式求值和语法解析。 您也可以在文本处理器的撤消/重做操作中找到它们。

Java 栈实现源代码

下面的程序是栈数据结构的示例实现。 可以根据需要随意修改源代码。

package com.howtodoinjava.datastructure;

import java.util.Arrays;

public class CustomStack <E> 
{
	private int size = 0;
	private static final int DEFAULT_CAPACITY = 10;
	private Object elements[];

	public CustomStack() {
		elements = new Object[DEFAULT_CAPACITY];
	}

	public void push(E e) {
		if (size == elements.length) {
			ensureCapacity();
		}
		elements[size++] = e;
	}

	@SuppressWarnings("unchecked")
	public E pop() {
		E e = (E) elements[--size];
		elements[size] = null;
		return e;
	}

	private void ensureCapacity() {
		int newSize = elements.length * 2;
		elements = Arrays.copyOf(elements, newSize);
	}

	@Override
    public String toString()
    {
         StringBuilder sb = new StringBuilder();
         sb.append('[');
         for(int i = 0; i < size ;i++) {
             sb.append(elements[i].toString());
             if(i < size-1){
                 sb.append(",");
             }
         }
         sb.append(']');
         return sb.toString();
    }
}

现在,通过从栈中推送和弹出一些项目来测试我们的栈实现。

public class Main
{
	public static void main(String[] args) 
	{
		CustomStack<Integer> stack = new CustomStack<>();

		stack.push(10);
		stack.push(20);
		stack.push(30);
		stack.push(40);

		System.out.println(stack);

		System.out.println( stack.pop() );
		System.out.println( stack.pop() );
		System.out.println( stack.pop() );

		System.out.println( stack );
	}
}

Output:

[10,20,30,40]
40
30
20
[10]

这就是有关 Java 栈实现示例的简单但重要的概念。

学习愉快!

Java 中的自定义列表实现示例

原文: https://howtodoinjava.com/data-structure/list-implementation-example-in-java/

在本教程中,我将给出 Java 中List的示例实现。 通过在DemoList类中添加或删除方法,可以随意自定义列表的行为。 如果您有一些想法可以改善此实现,请与我们分享。

Java 列表实现示例

在此类DemoList.java中,我们将创建具有以下功能的List实现:

  • 列表可能会从零增长到无限大小(至少在理论上是这样)。
  • 创建列表时,将使用最少 10 个元素初始化列表。
  • 列表将提供在生命周期中任何状态下获取,添加,删除和打印列表的方法。

列表实现的源代码

package com.howtodoinjava.datastructure;

import java.util.Arrays;

public class DataList<E> 
{
	//Size of list
	private int size = 0;

	//Default capacity of list is 10
	private static final int DEFAULT_CAPACITY = 10;

	//This array will store all elements added to list
	private Object elements[];

	//Default constructor
	public DataList() {
		elements = new Object[DEFAULT_CAPACITY];
	}

	//Add method
	public void add(E e) {
		if (size == elements.length) {
			ensureCapacity();
		}
		elements[size++] = e;
	}

	//Get method
	@SuppressWarnings("unchecked")
	public E get(int i) {
		if (i >= size || i < 0) {
			throw new IndexOutOfBoundsException("Index: " + i + ", Size " + i);
		}
		return (E) elements[i];
	}

	//Remove method
	@SuppressWarnings("unchecked")
	public E remove(int i) {
		if (i >= size || i < 0) {
			throw new IndexOutOfBoundsException("Index: " + i + ", Size " + i);
		}
		Object item = elements[i];
		int numElts = elements.length - ( i + 1 ) ;
		System.arraycopy( elements, i + 1, elements, i, numElts ) ;
		size--;
		return (E) item;
	}

	//Get Size of list
	public int size() {
		return size;
	}

	//Print method
	@Override
	public String toString() 
	{
		 StringBuilder sb = new StringBuilder();
		 sb.append('[');
		 for(int i = 0; i < size ;i++) {
			 sb.append(elements[i].toString());
			 if(i<size-1){
				 sb.append(",");
			 }
		 }
		 sb.append(']');
		 return sb.toString();
	}

	private void ensureCapacity() {
		int newSize = elements.length * 2;
		elements = Arrays.copyOf(elements, newSize);
	}
}

让我们快速测试一下List的实现。

package com.howtodoinjava.datastructure;

public class Main
{
	public static void main(String[] args) 
	{
		DataList<Integer> list = new DataList<>();

		//Add elements
		list.add(1);
		list.add(2);
		list.add(3);
		System.out.println(list);

		//Remove elements from index
		list.remove(2);
		System.out.println(list);

		//Get element with index
		System.out.println( list.get(0) );
		System.out.println( list.get(1) );

		//List Size
		System.out.println(list.size());
	}
}

Output:
[1,2,3,4,5]
[1,2,4,5]
1
2
4

如以上输出所示,我们的列表实现能够提供所有必要的功能。

学习愉快!

HTML5 教程

HTML5 – <section>标签示例

原文: https://howtodoinjava.com/html5/html5-section-tag/

HTML5 <section>标签定义了文档中的各个部分,例如章节,页眉,页脚或文档的任何其他部分。 以前(在 HTML5 之前),只能通过<div>标签提供这种分离的唯一方法。

section元素是语义元素。 这意味着它为用户代理和人员提供了有关所含内容(具体是文档的一部分)的含义。 它是一个通用语义元素,因此当其他语义容器元素(articleasidenav)都不适合时,应使用它。

<section>标签语法

在 HTML5 中创建<section>时,就像在 HTML 中使用<div>标签时一样,可以使用 ID属性。 每个 ID 必须是唯一的,如 HTML 中一样,并且可以在必要时多次使用。

您应该始终将标头元素(H1H6)作为section的一部分。 如果您无法提供该部分的标题,那么<div>元素可能更合适。 而且永远不要使用section标签仅用于放置样式。

假设您正在创建有关数据处理的文档。 以下是<section>元素的典型用法。

<section id="preparation" class="imaginaryClass">
     <h2>Prepare Data</h2>
     <p>Random text Random text Random text...</p>
</section>
<section id="processing">
     <h2>Process Prepared Data</h2>
     <p>Some More Random text Some More Random text Some More Random text ...</p>
</section>
<section id="display">
     <h2>Processed Data</h2>
     <p>Some More Random text Some More Random text Some More Random text ...</p>
</section>

总结

<section></section>元素的目的不是替换 HTML <div>标签。 如果要将文档划分为逻辑文档部分(以定义内容的语义上离散的部分),请使用<section></section>元素或结构元素之一。 但是,如果仅出于格式化目的分割文档,则适合使用<div>标签。

如果正确使用它们,则可以在有效的 HTML5 文档中同时使用DIVSECTION元素。

学习愉快!

HTML5 字符集 – 字符编码声明

原文: https://howtodoinjava.com/html5/html5-charset/

如今,针对不同地理位置和语言以及不同语言的 Web 主机应用使用不同的字符集或字符集。 要通知浏览器 HTML5 文档中使用的字符集,您需要使用标签,并将其命名为charset

我们来看UTF-8charset声明的示例。

<meta charset = "UTF-8" />

我建议尽可能使用 UTF-8,因为它可以在几乎 99.99% 的情况下表示任何字符。 大多数现代浏览器也都很好地支持它。

如果要使用旧方式提供字符集信息,则将使用以下格式。 在此提供此信息仅供参考,不建议使用。

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 

XHTML 文档具有第三种选择:通过 XML 声明来表示字符编码:

<?xml version="1.0" encoding="UTF-8"?>

  1. 尽管UTF-8在大多​​数情况下都可以使用,但是许多开发人员发现使用ISO-8859-1作为字符集可以提供更大的灵活性。
  2. 另一个字符集UTF-16有时会导致错误的字符,并且在某些情况下会导致应用无法正常运行。
  3. 大多数现代的网络浏览器都具有自动字符编码检测功能(如果未明确声明)。

学习愉快!

参考:

https://www.w3.org/International/articles/http-charset/index

https://en.wikipedia.org/wiki/Character_encodings_in_HTML

HTML5 DOCTYPE声明示例

原文: https://howtodoinjava.com/html5/html5-doctype/

从技术上讲,要正确解析和显示页面,浏览器需要知道 HTML 文档的DOCTYPE类型,该类型告诉浏览器在文档中应该使用哪个 HTML 版本。

在 HTML5 之前,DOCTYPE声明引用 DTD,因为它基于 SGML。 DTD 指定标记语言的规则,以便浏览器正确呈现内容。

HTML5 不是基于 SGML,因此不需要引用 DTD。 因此 HTML5 将DOCTYPE简化为:

//In HTML5
<!DOCTYPE html>

与 HTML4 之前使用的更复杂的声明相比:

//BEFORE HTML5, Till HTML4.0.1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

DOCTYPE 声明不是大小写敏感的。

请记住, 请勿DOCTYPE标记之前保留任何空格,否则即使是单个空格也可能导致浏览器呈现 HTML5 代码时出错。

学习愉快!

Gson - 序列化NULL

原文: https://howtodoinjava.com/gson/serialize-null-values/

Gson 中实现的默认行为是空对象字段被忽略。 例如,如果在Employee对象中未指定电子邮件(即电子邮件为null),则电子邮件将不会成为序列化 JSON 输出的一部分。

Gson 会忽略null字段,因为此行为允许使用更紧凑的 JSON 输出格式。

1.如何在序列化期间允许空值

要配置 Gson 实例以输出null,我们必须使用GsonBuilder对象的serializeNulls()

Gson gson = new GsonBuilder()
		.serializeNulls()
		.create();

2.演示

让我们看看启用或禁用空字段序列化时 Gson 的行为。

这是员工类,具有四个字段。 我们将电子邮件字段设置为'null'

public class Employee 
{
    private Integer id;
    private String firstName;
    private String lastName;
    private String email;
}

2.1 不要序列化空字段

默认 Gson 序列化,JSON 输出中不包含空值。

Employee employeeObj = new Employee(1, "Lokesh", "Gupta", null);

Gson gson = new GsonBuilder()
		.setPrettyPrinting()
		.create(); 

System.out.println(gson.toJson(employeeObj));

{
  "id": 1,
  "firstName": "Lokesh",
  "lastName": "Gupta"
}

2.2 序列化空字段

自定义 Gson 序列化,JSON 输出中包含空值。

Employee employeeObj = new Employee(1, "Lokesh", "Gupta", null);

Gson gson = new GsonBuilder()
		.setPrettyPrinting()
		.serializeNulls()
		.create(); 

System.out.println(gson.toJson(employeeObj));

{
  "id": 1,
  "firstName": "Lokesh",
  "lastName": "Gupta",
  "emailId": null
}

显然null字段已在 JSON 输出中序列化。 向我提供有关此 Gson 序列化NULL文章的问题。

学习愉快!

参考:

Gson 用户指南

Java 题目

Java 面试题目与答案

原文: https://howtodoinjava.com/java-interview-puzzles-answers/

这些 Java 题目及其答案将在您下一次 Java 编码面试中为您提供帮助。 提前了解他们的解决方案,以在您的下一次面试中留下更好的印象。

Java 题目

  • Java 中的无效代码和无法访问的代码
  • 如何在不使用new新关键字的情况下创建任何类的实例
  • 检查回文数字
  • 如何检测LinkedList 中的无限循环
  • TreeMap放置操作题目
  • 查找所有不同的重复元素
  • 好的字符串 - 坏的字符串
  • 检查字符串是否完整(包含所有字母)
  • 返回所有具有第 N 个最长长度的字符串
  • 如何反转字符串
  • 从系列中查找丢失的数字

如果被问到,别忘了分享更多这样的题目 – 并且您认为它们可以帮助其他人。

学习愉快!

Java 中的无效代码和无法访问的代码

原文: https://howtodoinjava.com/puzzles/dead-code-and-unreachable-code-in-java-puzzle/

学习识别 Java 中的无效代码和无法访问的代码。 在您的 Java 面试编码问题中可能会提出这样的谜题。

1.无效代码和无法访问的代码错误

为了理解无效代码,在这个题目中,我在下面给出了一段代码。 如果代码是在 Eclipse IDE 中编译的,请尝试确定代码中的问题。

public class IdentifyProblemsInCode {

    public void howToDoInJava_method1() {
        System.out.println("how to do");
        return;
        System.out.println("in java");
    }

    public void howToDoInJava_method2() {
        System.out.println("how to do");
        if (true) {
            return;
        }
        System.out.println("in java");
    }

    public void howToDoInJava_method3() {
        System.out.println("how to do");
        while (true) {
            return;
        }
        System.out.println("in java");
    }
}

我将在下一节中给出上述题目的答案,但我建议您先尝试一下。 它只是为了好玩。

2.解决方案 – 代码无效

我们所有人都必须面对与“无效代码”相关的编译错误,并且有些人可能已经注意到“无效代码警告”。 以上题目仅与他们有关。

在第一种方法howToDoInJava_method1()中,第二条打印语句无法访问,因此编译器会出于明显的原因而报错。

在第二种方法howToDoInJava_method2()中,第二条打印语句也无法访问,但是奇怪的编译器只会警告您。 稍后我们将尝试在这里获得逻辑。

同样在第三种方法howToDoInJava_method3()中,第二条打印语句不可访问,因此编译器将再次发出错误消息。

为什么!

3.什么是无效代码

方法 2 中无法访问的代码称为“无效代码”。 这是纯粹是 Eclipse 编译器报告的错误,如果您使用“javac”编译上述类,则 Java 内置编译器将仅报错其他两种方法(第一和第三)。

引用 Java 语言规范

“我们的想法是,从包含该语句的构造器,方法,实例初始化器或静态初始化器的开始,必须有一些可能的执行路径。 分析考虑了语句的结构。

除了对条件表达式具有恒定值truewhiledofor语句进行特殊处理外,在流量分析中不考虑其他表达式的值。

这意味着在确定不可访问的代码时不会考虑'if'块。 因为如果您通过'if'语句的路径之一,则可以到达第二个print语句。 一切都取决于编译器,它在编译期间确定了这一点。

在其他两个语句中,编译器确定了不可访问性,因此它报错错误。

如果我们再次像这样覆盖第二种方法。

public void howToDoInJava_method2() 
{
	System.out.println("how to do");

	if (true) 
	{
		return;
	}
	else
	{
		return;
	}

	System.out.println("in java");
}

现在,编译器确定它根本无法到达最后一个打印语句,因此'javac'再次报告了第二种方法的不可达代码。

如果您仍然对 Java 中的无效代码有疑问,请在评论部分写下。 我将尝试解决您的查询。

学习愉快!

Java 字符串回文 – Java 数字回文示例

原文: https://howtodoinjava.com/puzzles/java-string-palindrome-number-palindrome/

回文是一个单词,词组,数字或其他单元序列,可以在任一方向上以相同的方式读取,通常是否使用逗号,分隔符或其他单词分隔符不是必需的

类似地,回文数是指如果所有数字都颠倒了就代表相同数字的那些数字(下划线可以忽略较大的数字,例如1_00_00_001)。 数字文字中的下划线是 Java7 功能中的新增内容。

1. Java 回文字符串示例

要检查回文字符串,请反转字符串字符。 现在使用String.equals()方法来验证给定的字符串是否是回文。

1.1 使用 Apache Commons StringUtils检查字符串回文

Java 中用于字符串的简单回文程序。 它也是 Java 中使用反转方法的回文程序。

public class Main 
{
    public static void main(String[] args) 
    {
        System.out.println( isPalindromeString("howtodoinjava") );		//false
        System.out.println( isPalindromeString("abcba") );				//true
    }

    public static boolean isPalindromeString(String originalString) 
    {
        String reverse = StringUtils.reverse(originalString);
        return originalString.equals(reverse);
    }
}

1.2 使用StringBuilder检查字符串回文

同样的逻辑也可以应用于StringBuffer类。

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

    public static boolean isPalindromeString(String originalString) 
    {
        String reverse = new StringBuilder(originalString).reverse().toString();
        return originalString.equals(reverse);
    }
}

1.3 用for循环检查字符串回文

使用for循环和charAt()方法遍历最后一个索引的字符串字符来获取反转字符串,并创建新字符串。

仅当您在 Java 中检查字符串回文而不使用反转方法时,才使用此方法。

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

    public static boolean isPalindromeString(String originalString) 
    {
        String reverse = "";

        int length = originalString.length();

        for ( int i = length - 1; i >= 0; i-- )
            reverse = reverse + originalString.charAt(i);

        return originalString.equals(reverse);
    }
}

2. Java 回文数示例

为了验证给定数字是否为回文数是否为真,我们需要反转数字位数,如果两者相等或不相等,则与原始数字进行比较。

package com.howtodoinjava.puzzle;

public class PalindromeTest
{
    /**
     * Test the actual code if it works correctly
     * */
    public static void main(String[] args)
    {
        System.out.println(checkIntegerPalindrome( 100 )); 		//false
        System.out.println(checkIntegerPalindrome( 101 )); 		//true
        System.out.println(checkIntegerPalindrome( 500045 )); 	//false
        System.out.println(checkIntegerPalindrome( 50005 )); 	//true
    }

    /**
     * This function will test the equality if a number and its reverse.
     * @return true if number is palindrome else false
     * */
    public static boolean checkIntegerPalindrome(int number)
    {
        boolean isPalindrome = false;
        if(number == reverse(number))
        {
            isPalindrome = true;
        }
        return isPalindrome;
    }

    /**
     * This function will reverse a given number.
     * @return reverse number
     * */
    public static int reverse(int number)
    {
        int reverse = 0;
        int remainder = 0;
        do {
            remainder = number % 10;
            reverse = reverse * 10 + remainder;
            number = number / 10;

        } while (number > 0);
        return reverse;
    }
}

程序输出:

false
true
false
true

学习愉快!

检测LinkedList中的无限循环的示例

原文: https://howtodoinjava.com/puzzles/how-to-detect-infinite-loop-in-linkedlist-in-java-with-example/

这是一个非常常见的面试问题。 询问您是否有一个只能在一个方向上移动的链表,并且如果该链表中有一个循环,您将如何检测到它?

好吧,如果您不知道答案,那就不要灰心丧气。 我个人的观点是,这类问题无法评估候选人的逻辑思维,因为这样的问题具有非常具体的答案。 您或者知道,或者不知道。

对于这个特定问题,面试官寻找的最佳答案是“弗洛伊德循环发现算法”。 该算法提出了一种解决方案,建议您一次仅具有两个指针,而不是仅一个遍历列表的指针。 两个指针都将从链接列表的第一个节点开始,并使用next属性遍历。

不同之处在于它们在每个步骤中跳跃的节点数。 第一个节点每次跳到下一个节点,另一个节点一次跳两个节点。 第一个节点称为较慢的节点乌龟,第二个节点较快的节点被称为兔子

Tortoise_and_hare_algorithm

龟兔算法

这种遍历可确保如果链接链接中存在循环,则两个节点肯定会在其遍历路径中的某处相遇。 它具有O(n)复杂度。

让我们使用 Java 示例代码对此进行验证。

我已经写了一个最少可能的单链表代码,仅用于演示此示例。

package com.howtodoinjava.demo.core;

public class SinglyLinkedList {

	private Node start;

	public void add(Integer i)
	{
		Node node = new Node(i);
		if(start == null)
			start = node;
		else
		{
			Node temp = start;
			while(temp.next != null)
			{
				temp = temp.next;
			}
			temp.next = node;
		}
	}

	public Node getStart()
	{
		return start;
	}

	static class Node
	{
		Node(Integer i)
		{
			this.value = i;
		}

		private Integer value;
		private Node next;
		public Integer getValue() {
			return value;
		}
		public void setValue(Integer value) {
			this.value = value;
		}
		public Node getNext() {
			return next;
		}
		public void setNext(Node next) {
			this.next = next;
		}
	}
}

现在,让我们先在链表上进行循环测试,然后再进行循环测试。

package com.howtodoinjava.demo.core;

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

		FindLoopsInLinkedList finder = new FindLoopsInLinkedList();

		SinglyLinkedList sampleList = new SinglyLinkedList();
		// First Insert randomly ten elements in a linked list
		for (int i = 0; i < 10; i++) {
			sampleList.add(i);
		}

		System.out.println("Loop Existence : " + finder.doesLoopExist(sampleList));
		System.out.println("Loop Existence : " + finder.doesLoopExist(finder.createLoop(sampleList)));
	}

	public boolean doesLoopExist(SinglyLinkedList listToCheck) {
		SinglyLinkedList.Node tortoise = listToCheck.getStart();
		SinglyLinkedList.Node hare = listToCheck.getStart();

		try {
			while (true) {
				tortoise = tortoise.getNext();
				hare = hare.getNext().getNext();
				if (tortoise == hare) {
					return true;
				}
			}
		} catch (NullPointerException ne) {
			return false;
		}
	}

	private SinglyLinkedList createLoop(SinglyLinkedList sampleList) {
		sampleList.getStart().getNext().getNext().getNext().setNext(sampleList.getStart().getNext());
		return sampleList;
	}
}

在上面的程序中,我们创建了一个链接列表,并在该列表中插入了 10 个元素。 否,当我们检查行号中是否存在循环时。 15 这是错误的。

但是,当在第 167 行时,我们在链接列表内部创建了一个循环,结果如愿。

上面程序的输出是这样的:

Loop Existence : false            [Line 15]
Loop Existence : true             [Line 16]

如您所见,只要我们在行号中插入循环。 16,我们的算法实现能够检测到它。

学习愉快!

复合赋值运算符i += j与 Java 中的i = i + j不同

原文: https://howtodoinjava.com/puzzles/compound-assignment-operator-i-j-is-not-same-as-i-i-j-in-java/

在我们的日常编程中,我们都使用过i += ji = i + j之类的语法。 乍一看,它们看起来很相似。 实际上,在实际情况下,几乎所有情况下它们都将产生相同的输出。 但是,令您惊讶的是它们并不相似。 在运行时,当ij属于不同类型时,它们将得到不同对待。 让我们看下面的例子:

int i = 5;

double d1 = (double)i + 4.5; //necessary to satisfy compiler
i += 4.5;

System.out.println(i);
System.out.println(d1);

Output:

9
9.5

奇怪的。 是不是 两者都期望相同,因为操作相同。 为什么它们具有不同的值? 让我们找出答案。

原因

Java 语言规范指出:

形式为E1 op= E2的复合赋值表达式与E1 = (T)((E1) op (E2)),等效,其中TE1的类型,不同之处在于E1仅被求值一次。

因此,可以有效地将我们的原始示例代码覆盖如下:

int i = 5;

double d1 = (double) i + 4.5;    
i = (int)(i + 4.5); 		//Result converted to int

System.out.println(i);
System.out.println(d1);

Output:

9
9.5

因此,值 9 只是从双精度到整数转换时精度丢失的结果。

学过的知识

始终非常小心地使用复合赋值运算符i + = j。 仅在处理类似数据类型时才应使用。 在不同的数据类型中,结果可能不正确。

祝您学习愉快!

参考

http://stackoverflow.com/questions/8710619/java-operator

http://docs.oracle.com/javase/specs/jls/se5.0/html/expressions .html#15.26.2

Java 中的 HiLo 猜谜游戏

原文: https://howtodoinjava.com/puzzles/hilo-guessing-game-in-java/

你们中的许多人在您的童年时代一定玩过 HiLo 游戏。 即使不完全相同,游戏也可能与此类似。 很好玩吧? 那如果我们现在成年了怎么办? 让我们以自己的方式再次玩这个游戏。 让我们为此构建一个 Java 程序,然后开始玩这个精彩的游戏 HiLo。

用 Java 编写 HiLo 游戏

在下面的程序中,我尝试用 Java 语言模拟 HiLo 游戏。 我为此版本的游戏设定了两个简单的规则:

  1. 最多猜 6 次密码。
  2. 密码是 1 到 100(含)之间的整数。

每次您猜到一个低于密码的数字(只有 JRE 知道)时,都会打印“LO”。 同样,如果您猜到一个比密码高的数字,则会打印“HI”。 您必须调整下一个猜测,以便能够在六次尝试中猜测正确的数字。

package hilo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Random;

public class HiLo {

    private Random generator;
    private int generatedNumber;
    private int numberOfAttempts;
    BufferedReader reader = null;

    public HiLo() {
        generator = new Random();
        reader = new BufferedReader(new InputStreamReader(System.in));
    }

    public void start() throws IOException {

        boolean wantToPlay = false;
        boolean firstTime = true;

        do {
            System.out.println();
            System.out.println();
            System.out.println("Want to play the game of Hi and Lo??");
            if (wantToPlay = prompt()) {
                generatedNumber = generateSecretNumber();
                numberOfAttempts = 0;
                if (firstTime) {
                    describeRules();
                    firstTime = false;
                }
                playGame();
            }

        } while (wantToPlay);

        System.out.println();
        System.out.println("Thanks for playing the game. Hope you loved it !!");
        reader.close();
    }

    private void describeRules() {
        System.out.println();
        System.out.println("Only 2 Rules:");
        System.out.println("1) Guess the secret number in maximum 6 tries.");
        System.out.println("2) The secret number is an integer between 1 and 100, inclusive :-)");
        System.out.println();
        System.out.println();
    }

    private int generateSecretNumber() {
        return (generator.nextInt(100) + 1);
    }

    private void playGame() throws IOException {

        while (numberOfAttempts < 6) {

            int guess = getNextGuess();

            if (guess > generatedNumber) {
                System.out.println("HI");
            } else if (guess < generatedNumber) {
                System.out.println("LO");
            } else {
                System.out.println("Brave Soul, You guessed the right number!! Congratulations !!");
                return;
            }
            numberOfAttempts++;
        }

        System.out.println("Sorry, you didn't guess the right number in six attempts. In other two words, YOU LOST !!!!");
        System.out.println("The secret number was " + generatedNumber);

    }

    private boolean prompt() {

        boolean answer = false;

        try {

            boolean inputOk = false;
            while (!inputOk) {
                System.out.print("Y / N : ");
                String input = reader.readLine();
                if (input.equalsIgnoreCase("y")) {
                    inputOk = true;
                    answer = true;
                } else if (input.equalsIgnoreCase("n")) {
                    inputOk = true;
                    answer = false;
                } else {
                    System.out.println("Ohh come on. Even Mr. Bean knows where are 'y' and 'n' in the keyboard?? Please try again:");
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }

        return answer;
    }

    private int getNextGuess() throws IOException {

        boolean inputOk = false;
        int number = 0;
        String input = null;
        while (!inputOk) {
            try {

                System.out.print("Please guess the secret number: ");
                input = reader.readLine();
                number = Integer.parseInt(input);
                if (number >= 1 && number <= 100) {
                    inputOk = true;
                } else {
                    System.out.println("Really? You didn't read the rules boy. Your number is not between 1 and 100 (" + number + ").");
                }
            } catch (NumberFormatException e) {
                System.out.println("Invalid input (" + input + ")");
            }
        }

        return number;
    }
}

玩 HiLo 游戏

现在,游戏已准备就绪。 播放吧。

package hilo;

import java.io.IOException;

public class PlayGame
{
   public static void main(String[] args)
   {
      HiLo hiLo = new HiLo();
      try {
          hiLo.start();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

Output:

Want to play the game of Hi and Lo??
Y / N : y

Only 2 Rules:
1) Guess the secret number in maximum 6 tries.
2) The secret number is an integer between 1 and 100, inclusive.

Please guess the secret number: 40
LO
Please guess the secret number: 60
LO
Please guess the secret number: 80
HI
Please guess the secret number: 70
LO
Please guess the secret number: 75
LO
Please guess the secret number: 77
HI
Sorry, you didn't guess the right number in six attempts. In other two words, YOU LOST !!!!
The secret number was 76

希望您喜欢这个游戏。

祝您学习愉快!

Java 题目 – 查找所有重复的元素

原文: https://howtodoinjava.com/puzzles/find-all-duplicate-elements/

题目:给定 n 个正整数的输入数组,其中整数是随机顺序的。 该数组中的每个数字可以出现多次。 您需要找到所有不同的元素并将所有这些元素放在一个数组中,即output1。 如果输入中没有重复的数字,则输出应为{-1}

输入规格

输入 1:输入 2 中的元素数(n)

输入 2:n 个正整数的数组

输出规格:

输出:在input2中重复的不同元素的数组

Example 1: input : 6 input2 : {4,4,7,8,8,9} output : {4,8} 
Example 2: input : {2,3,6,8,90,58,58,60} output : {58} 
Example 3: input : {3,6,5,7,8,19,32} output : {-1}

解决方案

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

public class DuplicatesInArray
{
    public static void main(String[] args)
    {
        Integer[] array = {1,2,3,4,5,6,7,8};  //input 1
        int size = array.length;              //input 2

        Set<Integer> set = new HashSet<Integer>();
        Set<Integer> duplicates = new HashSet<Integer>();

        for(int i = 0; i < size ; i++)
        {
            if(set.add(array[i]) == false)
            {
                duplicates.add(array[i]);
            }
        }

        if(duplicates.size() == 0)
        {
            duplicates.add(-1);
        }

        System.out.println(duplicates);
    }
}

上面的程序将从数组中找到所有重复的元素,并将它们放入单独的集合中。 您可以在此处找到有关此逻辑的更多讨论:https://howtodoinjava.com/java/interviews-questions/find-duplicate-elements-in-an-array/

学习愉快!

Java 题目 – TreeMap的放置操作

原文: https://howtodoinjava.com/puzzles/java-puzzle-treemap-put-operation/

题目:我提供了以下映射,其中包含以下选项,

Map map = new TreeMap();
map.put("test key 1", "test value 1");
map.put("test key 2", "test value 2");
map.put("test key 3", "test value 3");

System.out.println(map.put("test key 3", "test value 3"));
System.out.println(map.put("test key 4", "test value 4"));

选项 A)System.out.println(map.put("test key 3", "test value 3"));

答案)将输出打印为"test value 3"

选项 B)System.out.println(map.put("test key 4", "test value 4"));

答案)这将输出打印为null

谁能解释为什么选项 B 赋予我们这种行为?

另外,当我在选项 B sysout语句后打印映射时,是否有测试键 4?

解决方案:如果查看Map.put()操作,则在映射中已经存在键的情况下,返回该值

添加键"test key 3"后,再次尝试添加键时,它将返回值"test value 3"

首次添加"test key 4"时,它在映射中不存在,因此映射返回值是null

下次存储"test key 4"时,该时间项目已经存在,因此将值返回为"test value 4"

祝您学习愉快!

题目 – 返回所有字符串中的第 N 长字符串

原文: https://howtodoinjava.com/puzzles/puzzle-return-all-the-strings-with-the-nth-longest-length/

算法:给定一个字符串列表,例如,返回列表中的第 N 个最长长度的字符串:list – Yuri, Ron, Interview, Longest, List, Containnth = 1将返回 只是Interview,而nth = 2将返回LongestContain

虽然“如何在O(n)中长度为 n 的未排序数组中找到第 k 个最大元素”的解决方案可以应用于字符串长度,如何转换回以打印所有长度为 n 的字符串?

解决方案

我编写了一个简单的 Java 程序,该程序可以从字符串列表中找到“所有 N 个最长的元素”。 程序如下:

package com.howtodoinjava.examples;

import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;

public class NthLogestStringAlgorithm
{
    public static void main(String[] args)
    {
        int n = 0;
        List<String> list = new ArrayList<String>();
        list.add("Yuri");
        list.add("Ron");
        list.add("Interview");
        list.add("Longest");
        list.add("List");
        list.add("Contain");

        System.out.println( findNthLongestElement(list, n) );
    }

    private static List<String> findNthLongestElement(List<String> list, int n)
    {
        if(n < 1) {
            return null; //Handle invalid case
        }

        TreeMap<Integer, List<String>> map = new TreeMap<>();

        for(String str : list)
        {
            Integer length = str.length();
            List<String> tempList = map.get(length) != null ? map.get(length) : new ArrayList<String>();
            tempList.add(str);
            map.put(length, tempList);
        }
        return map.get( map.descendingKeySet().toArray()[n-1] );
    }
}
========Output of program=======

n = 0 => null
n = 1 => [Interview]
n = 2 => [Longest, Contain]
n = 3 => [Yuri, List]
n = 4 => [Ron]

祝您学习愉快!

JMS 教程

Gson @Since – 版本支持

原文: https://howtodoinjava.com/gson/gson-since-version-support/

随着应用随时间变化,模型类也会随之变化。 有时这些变化可能会打破例如,添加/删除字段等

所有此类更改都可以使用@Since注解进行标记,以跟踪模型类,以便在这些系统使用反序列化 JSON 数据进行交谈时,与其他系统的应用集成不会中断。

1. Gson @Since注解

在 Gson 中,可以通过使用@Since注解来维护同一对象的多个版本。 可以在类,字段以及将来的方法中使用此注解。 它采用单个参数 – ignoreVersionsAfter

当我们用版本号'M.N'配置Gson实例时,所有标记有版本大于M.N的类字段将被忽略

例如,如果我们将 Gson 配置为版本号'1.2',则所有版本号更大的字段(例如 1.3、1.4…)都将被忽略。

@Since(1.2)
private String email;

2. Gson 版本支持

让我们创建并运行一个示例,以详细了解 Gson 中的版本控制支持。

2.1 如何使用@Since注解编写版本化的类

Employee类下面,我们对三个字段进行了版本控制,即firstNamelastNameemail

public class Employee 
{
	private Integer id;

	@Since(1.0)
    private String firstName;

    @Since(1.1)
    private String lastName;

    @Since(1.2)
    private String email;
}

2.2 创建具有版本支持的 Gson 实例

要创建将使用@Since注解的Gson实例,请使用GsonBuilder.setVersion()方法。

Gson gson = new GsonBuilder()
			.setVersion(1.1)
			.create();

3.演示

@Since注解会影响的序列化和反序列化,因此请务必谨慎使用。

3.1 具有版本支持的序列化

将版本号为1.1的上方Employee对象序列化。

Employee employeeObj = new Employee(1, "Lokesh", "Gupta", "howtogoinjava@gmail.com");

Gson gson = new GsonBuilder()
		.setVersion(1.1)
		.setPrettyPrinting()
		.create();

System.out.println(gson.toJson(employeeObj));

{
  "id": 1,
  "firstName": "Lokesh",
  "lastName": "Gupta"
}

观察输出。 版本号大于1.1,即1.2的字段将被忽略。 其他领域也都很好。

3.2 具有版本支持的反序列化

让我们将 JSON 字符串反序列化为版本号为1.1Employee对象。

String json = "{'id': 1001, "
				+ "'firstName': 'Lokesh',"
				+ "'lastName': 'Gupta',"
				+ "'email': 'howtodoinjava@gmail.com'}";

Gson gson = new GsonBuilder()
		.setVersion(1.1)
		.setPrettyPrinting()
		.create();

Employee employeeObj = gson.fromJson(json, Employee.class);

System.out.println(employeeObj);

Employee [id=1001, firstName=Lokesh, lastName=Gupta, email=null]

观察输出。 即使 JSON 字符串具有email字段,也没有反序列化。

请来回答有关使用@Since注解的 Gson 版本支持的问题

学习愉快!

Java 题目:好的字符串 – 坏的字符串

原文: https://howtodoinjava.com/puzzles/java-puzzle-good-string-bad-string/

题目: Chandu 非常喜欢字符串。 (或者他认为!)但是,他不喜欢具有相同连续字母的字符串。 没有人知道为什么会这样。 他称这些字符串为不良字符串。 因此,好的字符串是没有相同连续字母的字符串。 现在,问题很简单。 给定字符串 S,您需要将其转换为好字符串。

您只需要执行一项操作 - 如果有两个相同的连续字母,则删除其中一个。

解决方案

我相信使用正则表达式只能解决此问题。 我编写了一个示例程序来解决它。 请随时根据要求修改正则表达式。

public class GoodStringBadString
{
    public static void main(String[] args)
    {
        String input = "Good Oops, Bad Oops";
        String output = input.replaceAll("(?i)(\\p{L})\\1", "$1");
        System.out.println(output);
    }
}
Output: God Ops, Bad Ops

祝您学习愉快!

题目 – 检查字符串是否完整(包含所有字母)

原文: https://howtodoinjava.com/puzzles/puzzle-check-if-string-is-complete-contains-all-alphabets/

如果字符串包含从 a 到 z 的所有字符,则认为该字符串是完整的。 给定一个字符串,检查它是否完整。 例如:

示例输入

3
wyyga
qwertyuioplkjhgfdsazxcvbnm
ejuxggfsts

示例输出

NO
YES
NO

使用for循环和indexOf()的解决方案

我写了一个简单的函数,可以找到这个完整的字符串。

package com.howtodoinjava.examples;

public class CheckAllAlphabetsAlgorithms
{
    public static void main(String[] args)
    {
        System.out.println( checkAllChars( "qwertyuioplkjhgfdsAzxcvbnm" ) );
        System.out.println( checkAllChars( "123" ) );
        System.out.println( checkAllChars( "ejuxggfsts" ) );
        System.out.println( checkAllChars( "wyyga" ) );
    }

    private static String checkAllChars ( String input )
    {
        //If input length is less than 26 then it can never be complete
        if(input.length() < 26)
        {
            return "FALSE";
        }

        for (char ch = 'A'; ch <= 'Z'; ch++)
        {
            if (input.indexOf(ch) < 0 && input.indexOf((char) (ch + 32)) < 0)
            {
                return "FALSE";
            }
        }
        return "TRUE";
    }
}

输出

TRUE
FALSE
FALSE
FALSE

用于正则表达式的解决方案

这是一个使用正则表达式查找完整字符串的解决方案(很丑,因为我不喜欢长的正则表达式)。

package com.howtodoinjava.examples;

public class CheckAllAlphabetsAlgorithms
{
    public static void main(String[] args)
    {
        System.out.println( checkAllCharsUsingRegex( "qwertyuioplkjhgfdsAzxcvbnm" ) );
        System.out.println( checkAllCharsUsingRegex( "123" ) );
        System.out.println( checkAllCharsUsingRegex( "ejuxggfsts" ) );
        System.out.println( checkAllCharsUsingRegex( "wyyga" ) );
    }

    private static String checkAllCharsUsingRegex ( String input )
    {
        //If input length is less than 26 then it can never be complete
        if(input.length() < 26)
        {
            return "FALSE";
        }

        String regex = "(?i)(?=.*a)(?=.*b)(?=.*c)(?=.*d)(?=.*e)(?=.*f)"
                + "(?=.*g)(?=.*h)(?=.*i)(?=.*j)(?=.*k)(?=.*l)(?=.*m)(?=.*n)"
                + "(?=.*o)(?=.*p)(?=.*q)(?=.*r)(?=.*s)(?=.*t)(?=.*u)(?=.*v)"
                + "(?=.*w)(?=.*x)(?=.*y)(?=.*z).*";

        if(input.matches(regex)){
            return "TRUE";
        }
        return "FALSE";
    }
}

输出

TRUE
FALSE
FALSE
FALSE

祝您学习愉快!

Java 中的反转字符串 - 单词反转字符串

原文: https://howtodoinjava.com/puzzles/how-to-reverse-string-in-java/

学习编写 Java 程序以反转字符串。 我们将首先看到如何反转字符串,我们还将看到如何反转字符串

在 Java 面试中,这是初学者常见的题目。 让我们记住这些解决方案以便快速调用。

1. 反转字符串的 Java 程序

您可以使用StringBuilder.reverse()方法轻松地逐字符反转字符串。

String blogName = "HowToDoInJava.com";

String reverse = new StringBuilder(string).reverse();

System.out.println("Original String -> " + blogName);
System.out.println("Reverse String -> " + reverse);

Output:
Original String -> HowToDoInJava.com
Reverse String -> moc.avaJnIoDoTwoH

Java 程序通过单词反转字符串

通过单词反转字符串内容时,最自然的方法是使用StringTokenizerStack。 如您所知,Stack是一个实现易于使用的后进先出(LIFO)对象栈的类。

]
String description = "Java technology blog for smart java concepts and coding practices";

// reverse string builder
StringBuilder reverseString = new StringBuilder();

// Put words from String in Stack
Stack<String> myStack = new Stack<>();

StringTokenizer tokenizer = new StringTokenizer(description, " ");

while (tokenizer.hasMoreTokens()) {
	myStack.push(tokenizer.nextToken());
}

//Pop each word from stack and append in builder

while (!myStack.empty()) {
	reverseString.append(myStack.pop() + " ");
}

System.out.println(reverseString.toString());

Output:

practices coding and concepts java smart for blog technology Java 

学习愉快!

参考:

StringBuilder.reverse() Java 文档

StringTokenizer

用 Java 计算阶乘的 3 种方法

原文: https://howtodoinjava.com/puzzles/3-ways-to-calculate-factorial-in-java/

编写程序以用 Java 计算阶乘 – 可能是 Java 面试期间的编码练习。 最好了解如何构建这样的析因程序。 让我们经历以下三种方式:

1)使用迭代计算阶乘

简单而最基本的版本。 很高兴知道,但由于性能原因没有使用权。

public static long factorialIterative ( long n ) 
{
	long r = 1;
    for ( long i = 1; i <= n; i++ ) 
    {
        r*=i;
    }
    return r;
}

2)使用递归计算阶乘

如果您使用的是 Java7 或更低版​​本,那么这是您的最佳选择。 一个很好接受的答案。 它使用递归来计算阶乘。

public static long factorialRecursive( long n ) 
{
    return n == 1 ? 1 : n * factorialRecursive( n-1 );
}

3)使用流计算阶乘(Java8)

Java8 支持流,您可以使用流以最有效的方式计算阶乘,如下所示。

public static long factorialStreams( long n )
{
    return LongStream.rangeClosed( 1, n )
                     .reduce(1, ( long a, long b ) -> a * b);
}

  • 在这里,LongStream.rangeClosed(2, n)方法创建内容为[2, 3, ... , n]long流。
  • reduce (a, b) -> a * b表示每对ab – 将它们相乘并返回结果。 然后结果继续到下一轮。
  • 在简化方法中使用的值“1”在第一次迭代中用作变量a的起始值。

4)使用BigInteger计算大于 20 的数字的阶乘

如果您为大于 20 的数字运行以上示例中的任何一个; 由于long数据类型的限制,您将获得错误的输出。

System.out.println(getFactorial(20)); // 2432902008176640000
System.out.println(getFactorial(21)); // -4249290049419214848

值变得比long所能容纳的更大。 BigInteger类分配所需的内存,以容纳需要保存的所有数据位。 显然,如果系统中预设了那么多的内存,则只能这样。

现在,您将需要BigInteger来保存更大的值,并使用以下代码获取阶乘。

public static BigInteger getFactorial(int num) {
    BigInteger result = BigInteger.ONE;
    for (int i = 1; i <= num; i++)
        result = result.multiply(BigInteger.valueOf(i));
    return result;
}

现在,无论数量多大,您都可以得到阶乘。

System.out.println(getFactorial(22)); // 1124000727777607680000
System.out.println(getFactorial(32)); // 263130836933693530167218012160000000
System.out.println(getFactorial(132)); // Indeed a very long number is printed - Try yourself.

学习愉快!

Java 中的 FizzBu​​zz 解决方案

原文: https://howtodoinjava.com/puzzles/fizzbuzz-solution-java/

FizzBu​​zz 是一款有趣的游戏,主要在小学阶段玩。 规则很简单:轮到您时,请说出下一个号码。 但是,如果该数字是 5 的倍数,则应改为说“fizz”(最好带有法国口音)。 如果数字是 7 的倍数,则应说“buzz”。 而且,如果两者均是倍数,则应说“fizzbuzz”。 如果您搞砸了,就出局了,比赛在没有您的情况下继续进行。

请注意,可以使用不同的除数来代替 5 和 7,也可以使用不同的除数,并且不同的单词或手势可以代替“fizz”或“buzz”(参考)。

让我们学习编写一个程序来用 Java 模拟这个游戏。

在 Java8 中解决 FizzBu​​zz

使用最新的 Java 版本 8,让我们使用 Java8 构造设计解决方案。

IntStream.rangeClosed(1, 100)
	.mapToObj(i -> i % 5 == 0 ? (i % 7 == 0 ? "FizzBuzz" : "Fizz") : (i % 7 == 0 ? "Buzz" : i))
	.forEach(System.out::println);

在 Java8 之前解决 FizzBu​​zz

如果您仍未使用 Java8,则此 Fizzbuzz 解决方案将使用基本的for循环并遍历数字范围并确定要打印的内容。

for (int i = 1; i <= num; i++) 
{
	if (((i % 5) == 0) && ((i % 7) == 0)) // Is it a multiple of 5 & 7?
		System.out.println("fizzbuzz");
	else if ((i % 5) == 0) // Is it a multiple of 5?
		System.out.println("fizz");
	else if ((i % 7) == 0) // Is it a multiple of 7?
		System.out.println("buzz");
	else
		System.out.println(i); // Not a multiple of 5 or 7
}

完整的 FizzBu​​zz 示例代码

两种解决方案都非常容易并且易于说明。 下面给出的是 fizzbuzz 的工作示例源代码。 随意修改和使用代码。

import java.util.stream.IntStream;

public class FizzBuzz 
{
	public static void main(String[] args) 
	{
		fizzBuzzBeforeJava8(100);
		fizzBuzzInJava8(100);
	}

	private static void fizzBuzzBeforeJava8(int num) 
	{
		for (int i = 1; i <= num; i++) 
		{
			if (((i % 5) == 0) && ((i % 7) == 0)) // Is it a multiple of 5 & 7?
				System.out.println("fizzbuzz");
			else if ((i % 5) == 0) // Is it a multiple of 5?
				System.out.println("fizz");
			else if ((i % 7) == 0) // Is it a multiple of 7?
				System.out.println("buzz");
			else
				System.out.println(i); // Not a multiple of 5 or 7
		}
	}

	private static void fizzBuzzInJava8(int num) {
		IntStream.rangeClosed(1, 100)
				.mapToObj(i -> i % 5 == 0 ? (i % 7 == 0 ? "FizzBuzz" : "Fizz") : (i % 7 == 0 ? "Buzz" : i))
				.forEach(System.out::println);
	}
}

学习愉快!

从 Java 中的序列/数组中查找缺失的数字

原文: https://howtodoinjava.com/puzzles/find-missing-number-from-series/

在 Java 面试上,一个常见的题目是 – 从一系列数字或数组中找到缺失的数字。 这个题目已经在 Amazon 中问过。

在这个 Java 题目中,您有一系列数字开头(例如1…N),而该系列中恰好缺少一个数字。 您必须编写一个 Java 程序来查找系列中的缺失数字。

查找数字的解决方案

出乎意料的是,仅当您已经知道该题目的解决方案时,它才非常简单。

  1. 计算A = n (n+1)/2,其中n是序列1…N中的最大数。
  2. 计算B为给定序列中所有数字的总和
  3. 缺少号码等于A – B

让我们用代码编写解决方案。

public class FindMissingNumber {
	public static void main(String[] args) {
		//10 is missing
		int[] numbers = {1,2,3,4,5,6,7,8,9, 11,12};

		int N = 12;
		int idealSum = (N * (N + 1)) / 2;
		int sum = calculateSum(numbers);

		int missingNumber = idealSum - sum;
		System.out.println(missingNumber);
	}

	private static int calculateSum(int[] numbers) {
		int sum = 0;
		for (int n : numbers) {
			sum += n;
		}
		return sum;
	}
}

Output:

10

查找数字的解决方案 – Java8

上面的代码虽然很简单,但是可以使用新语言功能(例如 Java8 中的 lambda)减少许多行。 让我们看看如何?

import java.util.Arrays;

public class FindMissingNumber {
	public static void main(String[] args) {
		//10 is missing
		int[] numbers = {1,2,3,4,5,6,7,8,9, 11,12};

		int N = 12;
		int idealSum = (N * (N + 1)) / 2;
		int sum = Arrays.stream(numbers).sum();

		int missingNumber = idealSum - sum;
		System.out.println(missingNumber);
	}
}

Output:

10

这样的题目很容易解决,但是在任何面试中问清楚解决方案总是很有用的。 因此,准备在下一次面试中找到数组中缺少的数字。

学习愉快!

参考: SO 帖子

Java – 不使用“new”关键字创建对象

原文: https://howtodoinjava.com/puzzles/how-to-create-an-instance-of-any-class-without-using-new-keyword/

我们都知道如何创建任何类的对象。 在 Java 中创建对象的最简单方法是使用new关键字。 让我们探讨一下 Java 中无需使用new新关键字即可创建对象的其他方法。

Table of contents

Using Class.forName() and Class.newInstance()
ClassLoader loadClass()
Using Object.clone()
Deserialization
Using reflection

注意:在给定的示例中,我仅在编写伪代码。 要构建完整的,可以正常工作的示例代码,请阅读相关功能。

使用Class.newInstance()创建对象

Class ref = Class.forName("DemoClass");
DemoClass obj = (DemoClass) ref.newInstance();

Class.forName()将类加载到内存中。 要创建此类的实例,我们需要使用newInstance()

使用类加载器的loadClass()创建对象

就像上述方法一样,类加载器的loadClass()方法执行相同的操作。 它使用相同类的现有实例创建类的新实例。

instance.getClass().getClassLoader().loadClass("NewClass").newInstance();

使用Object.clone()创建对象

这也是拥有新的类的独立实例的方法。

NewClass obj = new NewClass();
NewClass obj2 = (NewClass) obj.clone();

使用序列化和反序列化创建新对象

如果您已经阅读了本文,则可以理解,序列化和反序列化也是在系统中拥有类的另一个实例的一种方式。

ObjectInputStream objStream = new ObjectInputStream(inputStream);
 NewClass obj = (NewClass ) inStream.readObject();

使用反射创建新对象

反射也是在大多数可用框架中创建新实例的流行方法。

builder.newInstance();class.newInstance();

如果您认为我没有其他可能的方法,请告诉我。

学习愉快!

面试问题

Java 面试问题

原文: https://howtodoinjava.com/java-interview-questions/

未来几天有计划的面试吗? 本指南包含 100 多个 Java 面试问题,将帮助您针对经验丰富的开发人员以及从小型初创公司到大型公司的大多数 Java 核心面试问题进行修订。

从这些基本的核心 Java 问题开始。 我写这些教程是为了帮助您回答您可能会遇到的一些题目。 我试图在每个答案后面尽可能多地推理。 尽管如此,仍不可能在本指南中包含所有可能的问题,因此我在本面试指南的末尾提供了一些很好的资源。

1.核心 Java 面试问题

1.1 核心 Java 面试问题系列

通常,每位访问者都将从 Java 核心概念入手,然后再进入更高级的主题。 原因很简单,他想放松您并使您感到舒适。

这些问题的范围可能从简单的面向对象原理到最常用的 Java 类,例如StringHashMap。 我之所以说这些简单问题,是因为它们测试了您的基础,基础知识以及对细节的好奇心。 尝试回答所有此类问题。 它们是低挂的水果,您不应该错过。

该系列分为 3 部分。 这些问题将帮助您在很短的时间内快速修订最常见的核心 Java 问题。 开始准备下一次面试的最佳方法。

  • 核心 Java 面试问题 – 第 1 部分
  • 核心 Java 面试问题 – 第 2 部分
  • 核心 Java 面试问题 – 第 3 部分

1.2 对象初始化最佳实践

在 Java 中,对象初始化被认为是一个繁重的过程,您应该了解每个新创建的对象如何影响内存和应用性能。 一个简单的例子是 Java 包装器类,它从外部看起来很简单,就像原始类型一样,但是它们并不像它们看起来那么简单。 了解 Java 如何在包装类(例如DoubleLongInteger)内对对象进行内部缓存。

1.3 HashMap在 Java 中的工作方式

如果您参加任何初级或中级面试,HashMap可能是讨论最多且最具争议的话题。 如果您知道hashmap在内部如何工作,您可以面对与HashMap相关的任何面试问题。 这篇文章将帮助您回答一些很好的问题,例如:

  • HashMap如何存储键值对?
  • HashMap如何解决冲突?
  • HashMap中如何使用hashCode()equals()方法?
  • 键的随机/固定hashCode()值的影响?
  • 在多线程环境中使用HashMap

1.4 为HashMap设计一个好的键

因此,您现在知道HashMap的工作原理吗? 现在了解有关为HashMap设计一个好的键的信息。 这是测试您是否正确理解HashMap内部工作的一种好方法。 这将帮助您回答以下问题:

  • 为什么StringHashMap的好钥匙?
  • 您将如何设计用作键的类?
  • 您将覆盖Key类中的hashCode()方法吗? 有什么影响?
  • 为可以作为HashMap关键对象的类编写语法?

1.5 关于ConcurrentHashMap的问题

HashMap不是线程安全的。 我们可以在并发应用中使用HashTable,但这会影响应用性能。 所以我们有ConcurrentHashMap。 它是HashMap的并发版本,具有与HashMap相同的性能,并且同时也是线程安全的。

您应该进一步了解ConcurrentHashMap。 仅此一个类就有很多概念。 这也是准备下一次面试时要学习的另一个好话题。

1.6 Java 集合面试问题

我是否应该建议您准备集合框架及其所有主要类别? 我想你已经足够聪明了。

如果没有任何与集合框架有关的问题,则任何面试(初中和中级)都将是不完整的。 它真正测试您的编程技能和对核心 Java API 的了解。 问题可能像集合层次结构一样简单,而困难的问题例如队列和栈。 这是此类常见集合面试问题的列表:

  • 解释集合层次?
  • SetList之间的区别?
  • VectorArrayList之间的差异?
  • HashMapHashTable之间的区别?
  • IteratorListIterator之间的区别?
  • 为什么Map接口没有扩展Collection接口?
  • 如何将String数组转换为ArrayList
  • 如何扭转列表?
  • HashSet如何存储元素?
  • 是否可以将空元素添加到TreeSetHashSet
  • 什么是IdentityHashMapWeakHashMap
  • 什么时候使用HashMapTreeMap
  • 如何使集合只读?
  • 如何使集合线程安全?
  • 故障快速故障安全有什么区别?
  • 什么是ComparatorComparable接口?
  • 什么是集合和数组类?
  • 什么是队列? 列出他们的差异?

1.7 Java 中的多态是什么?

简而言之,多态性是一种能力,通过它我们可以创建在不同程序环境下表现不同的函数或引用变量。 与继承,抽象和封装一样,多态是面向对象编程的主要构建块之一。

通过示例更详细地了解该概念。 这是如此重要。

1.8 Java 中的抽象是什么?

在上一个问题中,您学习了多态。 现在是时候通过理解抽象来扩展您的知识了。 任何 Java 面试的话题都非常复杂。

1.9 抽象与封装?

了解抽象和封装之间的区别是深入理解这两个概念的关键。 您不能孤立地学习两者。 它们采用 Java 编写,因此必须加以集体理解。

在这篇文章中,我已经解释了封装并通过抽象来区分它。 在继续之前,必须阅读 Java 面试问题。

1.10 接口和抽象类之间的区别?

自从 Java 语言诞生以来,在抽象类接口中就有非常清晰的分离。 但是自从 Java8 发行到市场以来,发生了很多变化。 它的核心概念之一是函数式接口。

函数式接口完全改变了我们查看 Java 语言的两个基本构建块的方式。 如果您的简历说您使用 Java8,就不能跳过这个问题。在链接的教程中,我将向您展示正确的场景,这将帮助您解决一些复杂的面试问题和案例研究。

1.11 枚举面试问题

枚举已成为核心构建块很长时间了。 在大多数流行的 Java 库中都可以看到它们。 它们帮助您以更加面向对象的方式管理常量。 它们看起来非常简单,但是如果您深入研究,它们会隐藏很多复杂性。 一些枚举问题可能是:

  • 枚举与枚举类之间的区别?
  • 枚举可以与String一起使用吗?
  • 我们可以扩展枚举吗?
  • 写枚举的语法?
  • 如何在枚举中实现反向查找?
  • 什么是EnumMapEnumSet

1.12 Java 序列化和Serializable接口

如果您准备对电信公司或在其应用流程中使用序列化的任何此类域进行 Java 面试,那么您将从本教程中受益匪浅。 用 Java 进行序列化的可做与不可做的一个很好的列表。 可能的问题可能包括:

  • 什么是serialVersionUID
  • 什么是readObjectwriteObject
  • 您将如何序列化和反序列化一个类?
  • 您将如何对类进行更改,以使序列化不中断?
  • 我们可以序列化静态字段吗?

1.13 Java main方法

有没有想过为什么main()是公开的,静态的和void? 在 Java 面试中,这不是一个经常问到的面试问题,但我仍然建议您阅读这篇文章来回答以下问题:

  • Java main方法语法?
  • 为什么main方法是公开的?
  • 为什么main方法是静态的?
  • 为什么main方法是void
  • 当您调用main方法时,内部会发生什么?

1.14 Java 对象克隆

Java 中的对象克隆并非易事。 我自己花了很长时间了解 Java 的克隆。 看起来真的很简单; 使用Cloneable接口并覆盖clone()方法。 可是等等; 在面试中还有很多要说的和要问的。 例如:

  • clone()方法如何工作?
  • Java 中的浅表复制是什么?
  • 什么是复制构造器?
  • Java 中的深层复制是什么?
  • 创建对象的深层副本的不同方法?

1.15 什么是 CountDownLatch

从 Java 5 开始,java.uti.concurrent包具有许多有用的但很复杂的类来在并发应用上工作。 CountDownLatch是在任何 Java 面试大型企业中都要求很高的类之一。 在本教程中,CountDownLatch附带示例和概念进行了解释。

1.16 为什么字符串是不可变的?

这个问题是初学者中非常受欢迎的面试问题。 基本上,面试官测试您对String类,字符串池,内存区域和对象创建的了解。

我分开写这篇文章是因为这个概念非常重要。 实际上,不变性本身是 Java 中非常重要的概念。 感受冰山一角。

1.17 如何使 Java 类不可变?

不变类是一种一旦创建便无法更改其状态的类。 在 Java 中创建不可变的类有某些准则,您必须知道它们才能正确回答此问题。

请注意,不变性在许多设计方面都很重要,并且是所有 Java 专家推荐的设计模式。 了解如何使 Java 类不可变,如何使 Java 类受益于应用设计,并准备在其上遇到更多软件设计面试问题。

2. Java 并发面试问题

2.1 什么是线程安全性?

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

在这个必读的 Java 教程中,清除您的疑问并准备回答一些流行的面试问题。 例如:

  • 线程安全的正确性是什么?
  • 举个线程安全类的例子吗?
  • 您将如何设计线程安全的 Java 类?
  • 不变类线程安全吗?

2.2 对象级别锁定与类级别锁定

并发的核心是对象锁定的概念。 锁定发生在实例级别以及类级别。

  • 当您要同步非静态方法或非静态代码块时,只有一个线程将能够在给定的类实例上执行代码块,因此对象级锁定是一种机制。 应该始终这样做以确保实例级数据线程安全。
  • 类级别锁定可防止多个线程在运行时在所有可用实例中的任何一个中进入同步块。 这意味着,如果在运行时有DemoClass的 100 个实例,则一次只有一个线程能够在任何一个实例中执行demoMethod(),而所有其他实例将被其他线程锁定。 为了确保静态数据线程的安全,应该始终这样做。

在本教程中详细了解整个概念。

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

这也是一个非常受欢迎的面试问题。 如果您的角色要求为并发应用创建设计,那么您必须知道此问题的正确答案。 这个问题的答案决定了您在两者之间选择哪种结构。

此外,它将帮助您回答一些基本问题,例如:

  • 线程和可运行之间的区别?
  • 编写 Java 代码以使用Runnable接口创建线程?
  • 两者之间应首选哪种方法?

2.4 比较和交换(CAS)算法

此问题针对中级或高级开发人员。 在回答这个问题之前,这需要对其他并发概念有深刻的理解。 因此,这是测试 Java 并发知识的好方法。

  • 什么是乐观锁定和悲观锁定?
  • 什么是比较和交换算法?
  • 什么是原子操作?
  • AtomicIntegerAtomicLong如何工作?

2.5 什么是 Fork/Join 框架?

自 Java8 发行以来,这不是一个新概念,但现在已以多种方式使用。Fork-Join 将手头的任务分解为多个微型任务,直到该微型任务足够简单,无需进一步分解即可解决。 这就像分而治之的算法。 在此框架中要注意的一个重要概念是,理想情况下,没有工作线程处于空闲状态。 他们实现了一种偷窃工作的算法,即闲置的工作器从忙碌的工作器那里窃取了工作。

学习这个精巧而高效的算法,以更好地准备下一次面试。

2.6 什么是ThreadPoolExecutor

在并发 Java 应用中,创建线程是一项昂贵的操作。 而且,如果您每次都开始创建新的线程实例来执行任务,则应用性能肯定会降低。 ThreadPoolExecutor解决了此问题。

ThreadPoolExecutor将任务创建和执行分开。 使用ThreadPoolExecutor,您仅需实现Runnable对象并将其发送给执行器。 它负责它们的执行,实例化以及使用必要的线程运行。

阅读ThreadPoolExecutor如何解决各种问题以及如何与BlockingQueue一起使用。

2.7 Java 执行器框架教程和最佳实践

您将在上一个链接中学习执行器,但是要想有效地使用这些执行器,您需要了解某些事情。

2.8 如何用 Java 编写并解决死锁

它可能以题目的形式出现。 最好为此做好准备。 面试官可以测试您的并发知识以及对wait()notify()方法调用的深刻理解。

指尖准备好一个死锁源代码示例。 您将需要它。

在出现任何 Java 面试之前,必须阅读以上给出的问题。 但是,它们仍无法提供完整的覆盖范围。 您需要了解的越来越多,才能在面试中表现更好。 我建议您按照给定的顺序阅读以下各节中的更多主题。

3.有经验的开发人员的 Java 面试问题

3.1 最佳实践指南

强烈推荐的最佳实践列表。 他们将打开您的思路,朝着不同的方向思考。 简而言之,他们将为您的下一次 Java 面试提供帮助。

3.2 解决一些题目

解决这些题目并在它们周围玩耍。 您永远不知道在糟糕的一天会遇到什么。

3.3 整理设计模式

为了在下一次 Java 面试中获得更高的职位,您必须知道这些设计模式可以处理一些复杂的应用设计问题,这些问题通常在当今的所有面试中都得到讨论。

3.4 随机浏览核心 Java 主题

仍然有实力阅读更多。 在此处浏览更多 Java 面试问题。

4.Spring 面试题

4.1 Spring 核心面试问题

我已经尝试收集一些 Spring 热门核心面试问题,您将在下次技术面试中面对这些问题,例如:

  • 什么是控制反转(IoC)和依赖注入(DI)?
  • BeanFactoryApplicationContext之间的区别?
  • 什么是基于 Spring Java 的配置?
  • 解释 Spring Bean 的生命周期?
  • Spring Bean 范围有哪些不同?
  • 在 Spring 框架中,单例 bean 线程安全吗?
  • 解释 Bean 自动装配的不同模式?
  • 用示例解释@Qualifier注解?
  • 构造器注入和设置器注入之间的区别?
  • 命名 Spring 框架中使用的一些设计模式?

4.2 Spring AOP 面试问题

Spring AOP (面向方面​​的编程)在某种意义上补充了 OOP,因为它也提供了模块化。 在 OOP 中,关键单位是“对象”,但在 AOP 中,关键单位是方面或横切关注点,例如日志记录和安全性。 AOP 提供了一种使用简单的可插拔配置在实际逻辑之前,之后或周围动态添加横切关注点的方法

通过这些最常见的 AOP 面试问题:

  • 关注点和跨领域关注点之间的区别?
  • 有哪些可用的 AOP 实现?
  • Spring AOP 中有哪些不同的建议类型?
  • 什么是 Spring AOP 代理?
  • 什么是连接点切入点
  • 什么是混合织入?

4.3 Spring MVC 面试问题

这些 Spring MVC 面试问题和答案已被编写,可帮助您为面试做准备,并快速地对总体概念进行修订。 如果有多余的时间,我强烈建议您更深入地研究每个概念。 一般来说,您应该能够回答以下问题:

  • 什么是 MVC 架构?
  • 什么是DispatcherServletContextLoaderListener
  • 如何使用基于 Java 的配置?
  • 我们如何使用 Spring 创建返回 JSON 响应的 Restful Web 服务?
  • <context:annotation-config><context:component-scan>之间的区别?
  • @Component@Controller@Repository@Service注解之间的区别?
  • Spring MVC 如何提供验证支持?
  • 什么是 Spring MVC 拦截器以及如何使用?
  • 如何在 Spring MVC 框架中处理异常?
  • 如何在 Spring MVC 应用中实现本地化?

5.测试您的知识

5.1 针对 Oracle 企业管理器项目的 Java 真实面试问题

到目前为止,您已经学习了 Java 中的所有不同概念,这些概念可以通过面试问题的形式出现在您面前。 现在该看看您是否准备好了。 在接受 Oracle 公司的面试时,请回答 Sreenath Ravva 提出的一些实际问题。

  • 您可以开始讲述自己和您的项目吗?
  • 什么是 Java 中的抽象和封装?
  • 方法重载规则?
  • Java 的扩大和缩小?
  • 我在代码中可以只有try块吗?
  • 线程:生产者和消费者的问题?
  • 为什么在Object类中定义了wait()notify()notifyAll()
  • 我们可以覆盖wait()notify()方法吗?
  • wait()sleep()yield()之间的区别?
  • 解释一下线程类中的join()方法?
  • 您是否遇到了内存不足错误? 如果是,您如何解决? 告诉不同的情况为什么会这样?
  • 数据库连接泄漏?
  • 编写程序以使用第三个变量交换两个数字?
  • 编写程序对数组进行排序并删除重复项?
  • 在单例上编写程序?
  • 写一个程序合并两个数组?
  • finalfinally关键字有什么用?
  • 我可以将类声明为静态还是私有的吗?
  • 为什么要更改公司?

5.2 针对中级开发人员的 Java 面试问题

Nikhil 在 Java/j2ee 开发人员方面有 6 年的经验,他打算改变公司。 我向他提出了这些中级面试问题。

他的面试很成功。 您也可以从中受益。

6.推荐书籍

6.1 Java 题目

每种编程语言都有其怪癖。 本书通过编程题目揭示了 Java 编程语言的奇特之处。

6.2 实践中的 Java 并发性

实践中的 Java 并发为您提供了为当今和未来的系统编写安全且可扩展的 Java 程序所需的概念和技术。

6.3 Joshua Bloch 撰写的《Effective Java》

第二版的《有效 Java》汇集了七十八个不可或缺的程序员经验法则:针对您每天遇到的编程挑战提供可行的最佳实践解决方案。

学习愉快!

Gson @SerializedName

原文: https://howtodoinjava.com/gson/gson-serializedname/

在此 Gson @SerializedName示例中,学习在序列化和反序列化过程中,如何更改 json 和 java 对象之间的字段名称。

1. @SerializedName

默认情况下,我们假设 Java 模型类和 JSON 将具有完全相同的字段名称。 但有时情况并非如此,某些名称有所不同。 现在我们必须将 json 中的someName映射到 Java 类中的someOtherName。 这是@SerializedName注解提供帮助的地方。

@SerializedName注解指示应将带注解的成员序列化为 JSON,并使用提供的名称值作为其字段名称。 此注解将覆盖可能已经使用GsonBuilder类的所有FieldNamingPolicy,包括默认字段命名策略。

请注意,您在此注解中指定的值必须是有效的 JSON 字段名称。

1.1 注解属性

它接受两个属性:

  • value – 序列化或反序列化时所需的字段名称。
  • alternate – 反序列化字段时的备用名称。 除了value属性以外,它还提供更多可能的名称。 如果有多个字段匹配一个属性,则 Gson 将使用最后处理的那个。

请记住,具有多个名称的alternate选项仅限于反序列化。 在序列化中,不会有任何影响。

2.在序列化期间更改字段名称

让我们以仅具有 4 个字段的Employee类为例。 我们要创建 JSON,其中"email"被写为字段名"emailId"

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

    @SerializedName(value = "emailId", alternate = "emailAddress")
    private String email;
}

让我们序列化一个员工记录并查看 JSON 输出。

Employee emp = new Employee(1001, "Lokesh", "Gupta", "howtodoinjava@gmail.com");

Gson gson = new GsonBuilder().setPrettyPrinting().create();  

System.out.println(gson.toJson(emp));

程序输出。

{
  "id": 1001,
  "firstName": "Lokesh",
  "lastName": "Gupta",
  "emailId": "howtodoinjava@gmail.com"
}

3.在反序列化期间更改字段名称

Java 程序,用于在将 JSON 反序列化为 Java 类的过程中映射不同的字段名称。

{
  "id": 1001,
  "firstName": "Lokesh",
  "lastName": "Gupta",
  "email": "howtodoinjava@gmail.com",
  "emailAddress": "admin@gmail.com"
}

String json = "{'id': 1001,"
		+ "'firstName': 'Lokesh',"
		+ "'lastName': 'Gupta',"
		+ "'email': 'howtodoinjava@gmail.com',"
		+ "'emailAddress': 'admin@gmail.com'}";

Gson gson = new GsonBuilder().setPrettyPrinting().create(); 

Employee emp = gson.fromJson(json, Employee.class);

System.out.println(emp);

程序输出:

Employee [id=1001, firstName=Lokesh, lastName=Gupta, email=admin@gmail.com]

注意程序输出。 我们在电子邮件字段(即emailemailAddress)中有两个匹配项。 最后一次出现是"emailAddress",因此它的值已填充到Employee对象中。

让我问一下有关使用 Gson @SerializedName注解将多个不同名称映射到 Java 类中的成员字段的问题。

学习愉快!

Java 字符串面试问题与答案

原文: https://howtodoinjava.com/interview-questions/interview-stuff-about-string-class-in-java/

我们所有人都必须经过与 Java 中的字符串类相关的面试问题。 这些字符串面试问题的范围从不变性到内存泄漏问题。 我将在这篇文章中尝试解决此类问题。

Frequently asked String Interview Questions

1\. Is String keyword in Java?
2\. Why are strings immutable?
3\. What is String constant pool?
4\. Keyword 'intern' usage
5\. Matching Regular expressions?
6\. String comparison with equals() and '=='?
7\. Memory leak issue in String class
8\. How does String work in Java?
9\. What are different ways to create String Object?
10\. How to check if String is Palindrome.
11\. How to remove or replace characters from String.
12\. How to make String upper case or lower case?
13\. How to compare two Strings in java program?
14\. Can we use String in the switch case?
15\. Write a program to print all permutations of String?
16\. Write a java program to reverse each word of a given string??
17\. How to Split String in java?
18\. Why is Char array preferred over String for storing password?
19\. Is String thread-safe in Java
20\. Why String is popular HashMap key in Java
21\. Difference between String, StringBuffer and StringBuilder?
22\. How to concatenate multiple strings.
23\. How many objects will be created with string initialization code?
24\. How do you count the number of occurrences of each character in a string?
25\. Write a java program to reverse a string?

1. Java 中的String是关键字吗?

没有。 String不是 Java 保留的关键字。 它是派生类型数据类型,即类。

public class StringExample 
{
    public static void main(String[] args) 
    {
        Integer String = 10;

        System.out.println(String);		//Prints 10
    }
}

2.为什么字符串是不可变的?

我们都知道 java 中的字符串是不可变的。 如果您想知道什么是不变性以及如何实现? 按照这篇文章:如何使 java 类不可变?

这里的问题是为什么? 为什么一成不变? 让我们分析一下。

  1. 我能想到的第一个原因是性能提升。 开发 Java 语言是为了加快应用程序开发,因为以前的语言并没有那么快。 JVM 设计人员必须足够聪明,才能识别出现实世界中的应用程序将主要由标签,消息,配置,输出等多种形式的字符串组成。

    看到这种过度使用,他们想像到不正确使用字符串会带来多大的危险。 因此,他们提出了字符串池的概念(下一部分)。 字符串池不过是一些大多数唯一字符串的集合。 字符串池背后的最基本思想是重新创建字符串。 这样,如果一个特定的字符串在代码中创建了 20 次,则应用最终将只有一个实例。

  2. 第二个原因是安全注意事项。 字符串是 Java 编程各个方面中最常用的参数类型。 无论是加载驱动程序还是打开 URL 连接,您都需要以字符串形式将信息作为参数传递。 如果字符串不是最终的,那么它们就打开了一个安全问题的潘多拉盒。我们所有人都必须通过与 Java 中的String类相关的面试问题。 这些问题的范围从不变性到内存泄漏问题。 我将在这篇文章中尝试解决此类问题。

除了上述两个原因外,我没有找到任何令人信服的答案。 如果您有任何吸引人的内容,请与我分享。

3.字符串池概念

字符串池是一个特殊的内存区域,与存储这些字符串常量的常规堆内存分开。 这些对象在应用的生命周期中称为字符串变量。

在 Java 中,可以通过多种方式创建String。 让我们了解它们:

1)字符串赋值


String str = "abc";

上面的代码使 JVM 验证是否已经有一个字符串"abc"(相同的字符序列)。 如果存在这样的字符串,则 JVM 仅将现有对象的引用分配给变量str,否则,将创建一个新的对象"abc",并将其引用分配给变量str

2)使用new新关键字


String str = new String("abc");

此版本最终在内存中创建两个对象。 字符串池中的一个对象具有char序列"abc",第二个对象在堆存储器中由变量str引用,并且具有与"abc"相同的char序列。

正如 java 文档所说的: 除非需要显式的原始副本,否则不需要使用此构造器,因为字符串是不可变的。

4.关键字“实习生”的用法

java 文档 最好地描述了这一点:

调用intern()方法时,如果池已经包含一个与equals(Object)方法确定的等于String对象的字符串,则返回池中的字符串。 否则,将此String对象添加到池中,并返回对此String对象的引用。

String str = new String("abc");

str.intern();

因此,对于st中的任何两个字符串,当且仅当s.equals(t)true时,s.intern() == t.intern()才为true。 意味着如果st都是不同的字符串对象,并且具有相同的字符序列,则在这两个变量上调用intern()将导致两个变量引用的单个字符串池文本。

5.匹配正则表达式

如果您还没有探索的话,它并不是那么秘密,但很有用。 您必须已经看到PatternMatcher用于正则表达式匹配的用法。 字符串类提供了自己的快捷方式。 直接使用。 此方法还使用函数定义内的Pattern.matches()


String str = new String("abc");

str.matches("<regex>");

6.使用equals()==进行字符串比较

面试中另一个最喜欢的领域。 通常有两种比较对象的方法

  • 使用==运算符
  • 使用equals()方法

==运算符比较对象引用,即内存地址相等性。 因此,如果两个字符串对象引用字符串池中的相同文本或堆中的相同字符串对象,则s==t将返回true,否则返回false

equals()方法在String类中被覆盖,它验证字符串对象持有的char序列。 如果它们存储相同的字符序列,则s.equals(t)将返回true,否则返回false

7.内存泄漏问题

到目前为止,我们已经完成了基本的工作。 现在发生了严重的事情。 您是否尝试过从字符串对象创建子字符串? 我敢打赌,是的。 您知道 Java 中子字符串的内部吗? 他们如何造成内存泄漏?

Java 中的子字符串是使用方法substring(int beginIndex)和此方法的其他一些重载形式创建的。 所有这些方法都会创建一个新的String对象,并更新在本文开头看到的offsetcount变量。

原始值数组不变。 因此,如果您创建一个包含 10000 个字符的字符串,并创建 100 个每个包含 5-10 个字符的子字符串,则所有 101 个对象将具有大小为 10000 个字符的相同字符数组。 毫无疑问,这是内存浪费。

让我们使用程序来看看:


import java.lang.reflect.Field;
import java.util.Arrays;

public class SubStringTest {
	public static void main(String[] args) throws Exception
	{
		//Our main String
		String mainString = "i_love_java";
		//Substring holds value 'java'
		String subString = mainString.substring(7);

		System.out.println(mainString);
		System.out.println(subString);

		//Lets see what's inside mainString
		Field innerCharArray = String.class.getDeclaredField("value");
		innerCharArray.setAccessible(true);
		char[] chars = (char[]) innerCharArray.get(mainString);
		System.out.println(Arrays.toString(chars));

		//Now peek inside subString
		chars = (char[]) innerCharArray.get(subString);
		System.out.println(Arrays.toString(chars));
	}
}

Output:

i_love_java
java
[i, _, l, o, v, e, _, j, a, v, a]
[i, _, l, o, v, e, _, j, a, v, a]

显然,两个对象都存储有相同的char数组,而subString只需要四个字符。

让我们使用自己的代码解决此问题:


import java.lang.reflect.Field;
import java.util.Arrays;

public class SubStringTest
{
	public static void main(String[] args) throws Exception
	{
		//Our main String
		String mainString = "i_love_java";
		//Substring holds value 'java'
		String subString = fancySubstring(7, mainString);

		System.out.println(mainString);
		System.out.println(subString);

		//Lets see what's inside mainString
		Field innerCharArray = String.class.getDeclaredField("value");
		innerCharArray.setAccessible(true);
		char[] chars = (char[]) innerCharArray.get(mainString);
		System.out.println(Arrays.toString(chars));

		//Now peek inside subString
		chars = (char[]) innerCharArray.get(subString);
		System.out.println(Arrays.toString(chars));
	}

	//Our new method prevents memory leakage
	public static String fancySubstring(int beginIndex, String original)
	{
		return new String(original.substring(beginIndex));
	}
}

Output:

i_love_java
java
[i, _, l, o, v, e, _, j, a, v, a]
[j, a, v, a]

现在,子字符串只有它需要的字符,并且可以无用地收集用于创建正确子字符串的中间字符串,从而不占用任何内存。

8. String如何在 Java 中工作?

Java 中的字符串类似于任何其他编程语言,都是字符序列。 这更像是用于该char序列的工具类。 此char序列在以下变量中维护:

/** The value is used for character storage. */
private final char value[];

要在不同的情况下访问此数组,请使用以下变量:

/** The offset is the first index of the storage that is used. */
private final int offset;

/** The count is the number of characters in the String. */
private final int count;

10.如何检查回文中的字符串?

如果字符串的值在反转时相同,则称其为回文。 要检查回文,只需反转字符串并检查原始字符串和受尊敬的字符串的内容。

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

        StringBuilder strBuilder = new StringBuilder(originalString);
        String reverseString = strBuilder.reverse().toString();

       boolean isPalindrame = originalString.equals(reverseString);

       System.out.println(isPalindrame);	//true
    }
}

11.如何从String中删除或替换字符?

要替换或删除字符,请使用String.replace()String.replaceAll()。 这些方法有两个参数。 第一个参数是要替换的字符,第二个参数是将放置在字符串中的新字符。

如果要删除字符,请在第二个参数中传递空白字符。

String originalString = "howtodoinjava";

//Replace one character
System.out.println( originalString.replace("h", "H") );         //Howtodoinjava

//Replace all matching characters
System.out.println( originalString.replaceAll("o", "O") );      //hOwtOdOinjava

//Remove one character
System.out.println( originalString.replace("h", "") );         //owtodoinjava

//Remove all matching characters
System.out.println( originalString.replace("o", "") );         //hwtdinjava

12.如何使String大写或小写?

使用String.toLowerCase()String.toUpperCase()方法将字符串转换为小写或大写。

String blogName = "HowToDoInJava.com";

System.out.println(blogName.toLowerCase());     //howtodoinjava.com

System.out.println(blogName.toUpperCase());     //HOWTODOINJAVA.COM

13.如何在 Java 程序中比较两个字符串?

始终使用equals()方法来验证字符串是否相等。 切勿使用"=="运算符。 双重等于运算符始终检查内存中的对象引用。 equals()方法检查字符串内容。

String blogName = "HowToDoInJava.com";

String anotherString = new String("HowToDoInJava.com");

System.out.println(blogName == anotherString);     //false

System.out.println(blogName.equals(anotherString));     //true

14.我们可以在开关盒中使用 String 吗?

是的,自 Java7 起,您可以在switch语句中使用String类。在 Java7 之前,这是不可能的,您必须使用if-else语句才能实现类似的行为。

String number = "1";

switch (number) 
{
case "1":
    System.out.println("One");	//Prints '1'
    break;
case "2":
    System.out.println("Two");
    break;
default:
    System.out.println("Other");
}

15.编写一个程序以打印String的所有排列?

排列是对字符的有序列表的元素进行重新排列,以使每个排列相对于其他排列都是唯一的。 例如,以下是字符串“ABC”的排列 – ABC ACB BAC BCA CBA CAB

长度为N的字符串具有N!N的阶乘)个排列。

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

public class StringExample 
{
    public static void main(String[] args) 
    {
        System.out.println(getPermutations("ABC"));	

        //Prints
        //[ACB, BCA, ABC, CBA, BAC, CAB]
    }

    public static Set<String> getPermutations(String string) 
    {
        //All permutations
        Set<String> permutationsSet = new HashSet<String>();

        // invalid strings
        if (string == null || string.length() == 0) 
        {
            permutationsSet.add("");
        } 
        else 
        {
            //First character in String
            char initial = string.charAt(0); 

            //Full string without first character
            String rem = string.substring(1); 

            //Recursive call
            Set<String> wordSet = getPermutations(rem);

            for (String word : wordSet) {
                for (int i = 0; i <= word.length(); i++) {
                    permutationsSet.add(charInsertAt(word, initial, i));
                }
            }
        }
        return permutationsSet;
    }

    public static String charInsertAt(String str, char c, int position) 
    {
        String begin = str.substring(0, position);
        String end = str.substring(position);
        return begin + c + end;
    }
}

16.编写一个 Java 程序来反转给定字符串的每个单词?

要分别反转每个单词,首先,对字符串进行标记,并在数组中将所有单词分开。 然后对每个单词应用反向单词逻辑,最后连接所有单词。

String blogName = "how to do in java dot com";

//spilt on white space
String[] tokens = blogName.split(" ");

//It will store reversed words 
StringBuffer finalString = new StringBuffer();

//Loop all words and reverse them
for (String token : tokens) {
    String reversed = new StringBuffer(token).reverse().toString();
    finalString.append(reversed);
    finalString.append(" ");
}

//Check final string
System.out.println(finalString.toString());     //woh ot od ni avaj tod moc

17.如何在 Java 中拆分字符串?

使用String.split()方法可在给定正则表达式的匹配项附近中断给定字符串。 也称为基于定界符的获取字符串令牌

split()方法返回字符串数组。 数组中的每个字符串都是单独的标记。

String numbers = "1,2,3,4,5,6,7";

String[] numArray = numbers.split(",");

System.out.println(Arrays.toString(numArray));	//[1, 2, 3, 4, 5, 6, 7]

18.为什么用Char数组而不是String来首选Char数组来存储密码?

我们知道字符串存储在 Java 的常量池中。 一旦在字符串池中创建了一个字符串,它将一直保留在该池中,直到收集到垃圾为止。 这时,任何恶意程序都可以访问物理内存位置中的内存位置,也可以访问字符串。

如果我们将密码存储为字符串,那么它也将存储在 spring 池中,并且在内存中的可用时间比要求的更长,因为垃圾收集周期是不可预测的。 这使得敏感的密码字符串容易遭到黑客攻击和数据盗窃

使用后可以将String留空吗? 不,我们不可以。 我们知道,一旦创建了字符串,我们将无法对其进行操作,例如,您不能更改其内容。 字符串是最终的且不可变的。

但是char数组是可变的,使用后它们的内容可以被覆盖。 因此,您的应用应使用char[]存储密码文本,并在使用密码后,将数组内容替换为空白。

String password = "123456";     //Do not use it

char[] passwordChars = new char[4];      //Get password from some system such as database

//use password

for(char c : passwordChars) {
    c = ' ';
}

19. Java 中的字符串线程安全吗?

是的,字符串是线程安全的。 它们是不可变的,默认情况下,java 中所有不可变的实例都是线程安全的。

20.为什么String是 Java 中流行的HashMap键?

在 Java 中,必须在Map中使用的键是不可变的,并且应遵循equals()hashCode()方法之间的约定。 String类满足这两个条件。

另外,String类提供了许多有用的方法来比较,排序,标记化或小写。 在Map上执行 CRUD 操作时可以使用这些方法。 这使它成为在Map中使用而不是创建自己的类的非常有用的类。

21. StringStringBufferStringBuilder之间的区别?

  • String类表示字符序列,并提供了使用字符的有用方法。 字符串类实例是不可变的。 因此,每次使用字符串类执行字符串连接时,都会使用连接的字符串创建一个新对象。

  • StringBuilder类用于以更节省内存的方式执行字符串连接操作。 它在内部维护一个char数组并仅操作该数组中的内容。

    执行完所有操作后需要获取完整的连接字符串时,它将创建一个具有字符数组内容的新String

  • StringBufferStringBuilder类非常相似。 唯一的区别是它是线程安全的。 所有方法都是synchronized

22.如何连接多个字符串?

根据您是否需要线程安全来使用StringBufferStringBuilder类。 在两个类中都使用append()方法来连接字符串。

StringBuffer buffer = new StringBuffer();

buffer.append("how")
        .append("to")
        .append("do")
        .append("in")
        .append("java")
        .append(".")
        .append("com");

String blogName = buffer.toString();

System.out.println(blogName); //howtodoinjava.com

23.使用字符串初始化代码将创建多少个对象?

String s1 = "howtodoinjava.com";
String s2 = "howtodoinjava.com";
String s3 = new String("howtodoinjava.com");

  1. 上面的代码将创建 2 个对象
  2. 第一个语句将在字符串池中创建第一个对象。
  3. 第二条语句不会创建任何新对象,并且s2将引用与s1相同的字符串常量。
  4. 第三条语句将在堆内存中创建一个新的字符串对象。

24.如何计算字符串中每个字符的出现次数?

为了找到给定字符串中每个字符的出现次数,我们使用了HashMap,并将该字符作为键,并将其出现作为值。 每次出现新的字符时,我们将增加该字符的值。

String blogName = "howtodoinjava.com";

HashMap<Character, Integer> occurancesMap = new HashMap<Character, Integer>();

char[] strArray = blogName.toCharArray();

for (char c : strArray)
{
    if(occurancesMap.containsKey(c))
    {
        occurancesMap.put(c, occurancesMap.get(c)+1);
    }
    else
    {
        occurancesMap.put(c, 1);
    }
}

System.out.println(occurancesMap);
//{a=2, c=1, d=1, h=1, i=1, j=1, m=1, n=1, .=1, o=4, t=1, v=1, w=1}

25.编写一个 Java 程序来不使用StringBuilderStringBuffer反转字符串?

反转字符串的最佳方法肯定是StringBuffer.reverse()StringBuilder.reverse()方法。 不过,面试官可能会要求您编写自己的程序,以检查您的技能水平。

使用下面给出的基于递归的示例来反转字符串。 该程序从字符串中获取第一个字符,并将其放置在字符串中的最后一个位置。 它将替换字符串中的所有字符,直到验证整个字符串为止。

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

        String reverseString = recursiveSwap(blogName);

        System.out.println(reverseString);
    }

    static String recursiveSwap(String str)
    {
         if ((null == str) || (str.length() <= 1))
         {
                return str;
         }
         return recursiveSwap(str.substring(1)) + str.charAt(0);
    }
}

我可以想到这些常见的String面试问题将为您的下一次面试提供帮助。 如果您还有其他关于String类的问题,请分享。

学习愉快!

Java 核心面试问题 – 第 1 部分

原文: https://howtodoinjava.com/interview-questions/core-java-interview-questions-series-part-1/

您打算学习核心 Java 吗? 还是计划在未来几天进行面试? 不用担心,请阅读下面给出的所有面试问题,以刷新您的概念,并可能在最佳 Java 列表中添加一些新内容。

Interview Questions List

How to create a immutable object in Java? Count all benefits?
Is Java Pass by Reference or Pass by Value?
What is the use of the finally block? Is finally block in Java guaranteed to be called? When finally block is NOT called?
Why there are two Date classes; one in java.util package and another in java.sql?
What is Marker interface?
Why main() in java is declared as public static void main?
What is the difference between creating String as new() and literal?
How does substring() inside String works?
Explain the working of HashMap.
Difference between interfaces and abstract classes?
When do you override hashCode and equals()?

如何用 Java 创建不可变的对象? 算上所有好处?

不变类是一种一旦创建便无法更改其状态的类。 在这里,对象状态本质上是指存储在类的实例变量中的值,无论它们是原始类型还是引用类型。

要使类不可变,需要遵循以下步骤:

  1. 不要提供设置器方法或修改字段或字段引用的对象的方法。 设置器方法旨在更改对象的状态,这是我们在此要避免的。
  2. 将所有字段设为finalprivate。 声明为private的字段将无法在该类之外访问,将其设置为final将确保即使您不小心也无法更改它们。
  3. 不允许子类覆盖方法。 最简单的方法是将类声明为final。 Java 中的最终类不能被覆盖。
  4. 永远记住,您的实例变量将是可变的或不可变的。 标识它们并返回具有所有可变对象(对象引用)复制内容的新对象。 不变的变量(原始类型)可以安全地返回,而无需付出额外的努力。

另外,您应该记住不变类的以下好处。 面试期间您可能需要它们。 不变的类:

  • 易于构建,测试和使用
  • 自动是线程安全的,并且没有同步问题
  • 不需要复制构造器
  • 不需要克隆的实现
  • 允许hashCode使用延迟初始化,并缓存其返回值
  • 用作字段时不需要防御性地复制
  • 制作好Map键和Set元素(这些对象在集合中时不得更改状态)
  • 在构造时就建立了其类不变式,因此无需再次检查
  • 始终具有“失败原子性”(Joshua Bloch 使用的术语):如果不可变对象抛出异常,则永远不会处于不希望或不确定的状态。

看一下这篇在 Java 中编写的示例

Java 是按引用传递还是按值传递?

Java 规范说 Java 都是按值传递。 在 Java 中,没有“引用传递”之类的东西。 这些术语与方法调用关联,并将变量作为方法参数传递。 好吧,原始类型总是按值传递而不会造成任何混淆。 但是,应该在复杂类型的方法参数的上下文中理解该概念。

在 Java 中,当我们将复杂类型的引用作为任何方法参数传递时,总是将内存地址一点一点地复制到新的引用变量中。 见下图:

pass-by-value

在上面的示例中,第一个实例的地址位被复制到另一个引用变量,因此导致两个引用都指向存储实际对象的单个存储位置。 请记住,再次引用null不会使第一次引用也为null。 但是,从任一引用变量更改状态也会对其他参考产生影响。

在此处详细阅读: Java 是值或引用传递?

finally块的用途是什么? Java 中的finally块是否可以保证被调用? 何时不调用finally块?

try块退出时,finally块总是执行。 这样可以确保即使发生意外异常也执行finally块。 但是finally不仅可以用于异常处理,还可以使清除代码意外地被returncontinuebreak绕过。 将清除代码放在finally块中始终是一个好习惯,即使没有异常的情况也是如此。

如果在执行trycatch代码时退出 JVM,则finally块可能不会执行。 同样,如果执行trycatch代码的线程被中断或杀死,则即使整个应用继续运行,finally块也可能不会执行。

为什么有两个Date类; 一个在java.util包中,另一个在java.sql中?

java.util.Date代表一天中的日期和时间,java.sql.Date仅代表日期。 java.sql.Date的补码为java.sql.Time,仅代表一天中的某个时间。
java.sql.Datejava.util.Date的子类(扩展)。 因此,java.sql.Date中发生了什么变化:

toString()生成不同的字符串表示形式:yyyy-mm-dd
– 一种static valueOf(String)方法,从具有上述表示形式的字符串中创建日期
– 获取器和设置器数小时 ,不建议使用分钟和秒

java.sql.Date类与 JDBC 一起使用,并且不应包含时间部分,即小时,分钟,秒和毫秒应为零…但是该类未强制执行。

解释标记接口?

标记接口模式是计算机科学中的一种设计模式,与提供有关对象的运行时类型信息的语言一起使用。 它提供了一种方法,可将元数据与该语言不明确支持此类的类相关联。 在 Java 中,它用作未指定方法的接口。

在 Java 中使用标记接口的一个很好的例子是Serializable接口。 一个类实现此接口,以指示可以将其非瞬态数据成员写入字节流或文件系统。

标记接口的主要问题是,接口定义用于实现类的协定,并且该协定被所有子类继承。 这意味着您不能“取消实现”标记。 在给定的示例中,如果创建不想序列化的子类(可能是因为它依赖于瞬态),则必须诉诸显式抛出NotSerializableException

为什么将 Java 中的main()声明为public static void

为什么公开? main方法是public,因此它可以在任何地方访问,并且对于可能希望使用它启动应用的每个对象都可以访问。 在这里,我并不是说 JDK / JRE 具有类似的原因,因为java.exejavaw.exe(对于 Windows)使用 Java 本机接口(JNI)调用来调用方法,因此,无论使用哪种访问修饰符,他们都可以通过任何一种方式来调用它。

为什么是静态? 假设我们没有像static那样的主要方法。 现在,要调用任何方法,您需要它的一个实例。 对? 众所周知,Java 可以有重载的构造器。 现在,应该使用哪一个,重载的构造器的参数从何而来。

为什么void 因此,没有任何将值返回给实际调用此方法的 JVM 的用途。 应用唯一想与调用过程进行通信的是:正常终止或异常终止。 使用System.exit(int)已经可以实现。 非零值表示异常终止,否则一切正常。

将 String 创建为new()和字面值之间有什么区别?

当我们使用new()创建String时,它会在堆中创建并添加到字符串池中,而使用字面值创建的String仅在字符串池中创建,而字符串池仅存在于堆的永久区域中。

那么,您真的需要非常深入地了解字符串池的概念,才能回答此问题或类似问题。 我的建议...“认真学习”字符串类和字符串池。

String中的substring()如何工作?

Java 中的String与其他任何编程语言一样,都是字符序列。 这更像是用于该char序列的工具类。 此char序列在以下变量中维护:

/** The value is used for character storage. */
private final char value[];

要在不同情况下访问此数组,请使用以下变量:

/** The offset is the first index of the storage that is used. */
private final int offset;

/** The count is the number of characters in the String. */
private final int count;

每当我们从任何现有的字符串实例创建子字符串时,substring()方法都只会设置offsetcount变量的新值。 内部char数组不变。 如果不小心使用substring()方法,这可能是内存泄漏的原因。 在此处了解更多信息。

解释HashMap的工作。 如何解决重复冲突?

你们大多数人都会同意,HashMap是当今面试中最喜欢讨论的话题。 如果有人要我描述“HashMap如何工作?”,我只是回答:“关于哈希的原理”。 就这么简单。

现在,以最简单的形式进行哈希处理是一种在对属性应用任何公式/算法之后为任何变量/对象分配唯一代码的方法。

定义的映射是:“将键映射到值的对象”。 很容易..对吗? 因此,HashMap有一个内部类Entry,它看起来像这样:

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
}

当某人尝试将键值对存储在HashMap中时,会发生以下情况:

  • 首先,检查键对象是否为空。 如果keynull,则值存储在table[0]位置。 因为null的哈希码始终为 0。

  • 然后,下一步,通过调用键的hashCode()方法,使用键的哈希码计算哈希值。 该哈希值用于计算数组中用于存储Entry对象的索引。 JDK 设计人员很好地假设可能存在一些编写不当的hashCode()函数,它们可能返回非常高或很低的哈希码值。 为解决此问题,他们引入了另一个hash()函数,并将对象的哈希码传递给此hash()函数,以将哈希值带入数组索引大小的范围内。

  • 现在,调用indexFor(hash, table.length)函数来计算用于存储Entry对象的精确索引位置。

  • 这是主要部分。 现在,我们知道两个不相等的对象可以具有相同的哈希码值,如何将两个不同的对象存储在相同的数组位置(称为存储桶)中。 答案是LinkedList。 如果您还记得的话,Entry类的属性为next。 此属性始终指向链中的下一个对象。 这正是LinkedList的行为。

    因此,在发生碰撞的情况下,Entry对象以LinkedList形式存储。 当Entry对象需要存储在特定索引中时,HashMap检查是否已经有一个项目? 如果尚无项目,则Entry对象存储在此位置。

    如果已经有一个对象位于计算索引上,则检查其next属性。 如果为null,则当前Entry对象成为LinkedList中的next节点。 如果next变量不为空,则遵循步骤直到next被求值为空。

    如果我们添加另一个具有与之前输入相同的键的值对象,该怎么办。 从逻辑上讲,它应该替换旧值。 怎么做的? 好吧,在确定Entry对象的index位置之后,在对计算索引上的LinkedList进行迭代时,HashMap为每个Entry对象调用关键对象上的equals()方法。 LinkedList中的所有这些Entry对象将具有相似的哈希码,但equals()方法将测试真实相等性。 如果key.equals(k)true,则两个键都被视为相同的键对象。 这将仅导致替换Entry对象中的value对象。

这样,HashMap确保键的唯一性。

接口和抽象类之间的区别?

如果您正在面试初级程序员,这是一个非常常见的问题。 好吧,最明显的区别如下:

  • 在 Java 接口中声明的变量默认为final。 抽象类可能包含非最终变量。
  • Java 接口是隐式abstract,不能具有实现。 Java 抽象类可以具有实现默认行为的实例方法。
  • 默认情况下,Java 接口的成员是公开的。 Java 抽象类可以具有类成员通常的风格,例如privateabstract等。
  • Java 接口应使用关键字“implements”实现; Java 抽象类应使用关键字“extends”来扩展。
  • Java 类可以实现多个接口,但只能扩展一个抽象类。
  • 接口是绝对抽象,不能实例化; Java 抽象类也无法实例化,但是可以在存在main()的情况下调用。 从 Java8 开始,您可以在接口中定义默认方法
  • 抽象类比接口要快一些,因为接口涉及在 Java 中调用任何覆盖方法之前进行的搜索。 在大多数情况下,这并不是显着的差异,但是如果您正在编写时间紧迫的应用,那么您可能不想无所事事。

何时覆盖hashCode()equals()

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

hashCode()方法用于获取给定对象的唯一整数。 当此对象需要存储在类似数据结构的HashTable中时,该整数用于确定存储桶位置。 默认情况下,对象的hashCode()方法返回并以整数形式表示存储对象的内存地址。 顾名思义,
equals()方法用于简单地验证两个对象的相等性。 默认实现只是检查两个对象的对象引用以验证它们的相等性。

请注意,通常有必要在每次覆盖此方法时都覆盖hashCode方法,以维护hashCode()方法的常规约定,该约定规定相等的对象必须具有相等的哈希码。

  • equals()必须定义一个相等关系(它必须是自反,对称和可传递)。 另外,它必须是一致的(如果未修改对象,则它必须保持返回相同的值)。 此外,o.equals(null)必须始终返回false
  • hashCode()也必须保持一致(如果未根据equals()修改对象,则它必须保持返回相同的值)。

两种方法之间的关系为:

每当a.equals(b)时,a.hashCode()必须与b.hashCode()相同。

祝您学习愉快!