Java Tips

150 阅读16分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

import lombok.EqualsAndHashCode;
/**
 * 存在继承关系的子类——加上注解:@EqualsAndHashCode(callSuper = true)
 * 作用: 将其父类属性也进行比较
 * */
@EqualsAndHashCode(callSuper = true)

小工具

List<String> 的快速初始化

注:这两种初始化无法对 list 对象进行元素的增删

List<String> list = Arrays.asList("txt", 12, true);
// 单个元素的 list
List<String> oneList = Collections.singletonList("dd");

此方法可进行元素控制

import com.google.common.collect.Lists;

Lists.newArrayList(12, 2);

List 打印元素(Java8)

list.forEach(System.out::println);

List 去重

public static Object duplicateRemove(List<Object> list) {
    Set<Object> set = new HashSet<>(list);
    return new ArrayList<>(set);
}

List a 去除 List b 中元素

aList.removeAll(bList);

List 两种遍历

    public void itRest() {
        List<String> strList = new ArrayList<>();

        // iterator 遍历方式
        Iterator it = strList.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        // for-in 遍历方式
        for (String str : strList) {
            System.out.println(str);
        }
    }

StringList 转 List

// idStr = "1,2,3,4";
Arrays.asList(idStr.split(","))

将 list 按照指定数量分成小 list

import com.google.common.collect.Lists; 

List<List<String>> subSets = Lists.partition(list, size);

List 与 JSONArray 互相转换(fastJson)

// List 转 JSONArray
List<T> list = new ArrayList<T>();
JSONArray array= JSONArray.parseArray(JSON.toJSONString(list));

// JSONArray转List
JSONArray array = new JSONArray();
List<EventColAttr> list = JSONObject.parseArray(array.toJSONString(), EventColAttr.class);

// 字符串转List
String str = "";
List<T> list = JSONObject.parseArray(str,T.class);

三目运算符

if(a<b)
    min=a;
else
    min=b;
// 三目
min = (a<b)?a:b;

获取 Map 的 Key or Value

/**
     * 获取 Map 的 key
     * 
     * @param map
     * @param isKey true/false
     * @return true 返回 keys,false 返回 values
     */
public static List<Object> mapKeys(Map<Object, Object> map, boolean isKey) {
    List<Object> keys = new ArrayList<>();
    List<Object> values = new ArrayList<>();

    for (Map.Entry<Object, Object> entry : map.entrySet()) {
        keys.add(entry.getKey());
        values.add(entry.getValue());
    }

    return isKey ? keys : values;
}

JSON & JavaBean

// Java 对象转 JSONObject
JSONObject jsonObj = (JSONObject) JSON.toJSON(javaBean);

// JSONObject 转 Java 对象
AlarmAddition addition = JSON.parseObject(detail.getAlarmDetail(), AlarmAddition.class);

数据库事务机制

@Transactional(rollbackFor = Exception.class)

深/浅拷贝

BeanUtils.copyProperties(dst, src);

注:List/Map 不能使用 BeanUtils


// 首先引入 spring 的工具包,使用工具 BeanUtils
import org.apache.commons.beanutils.BeanUtils;

    @SneakyThrows
    @Test
    public void useDeepCopy2() {
        WorkDay six = new WorkDay();
        six.setLocation(6);
        six.setName("Saturday");
        six.setWork(false);

        WorkDay five = new WorkDay();
        System.out.println(six);
        System.out.println(five);

        // arg1 目标 arg2 本源
        BeanUtils.copyProperties(five, six);

        System.out.println(five);
    }

WorkDay(location=6, name=Saturday, isWork=false)
WorkDay(location=0, name=null, isWork=false)
WorkDay(location=6, name=Saturday, isWork=false)

StringBuffer 和 StringBuilder

结论:使用 StringBuilder

原文地址 www.runoob.com/java/java-s…

String 输出 1 格式为 01

System.out.println(String.format("%02d", one));

zhidao.baidu.com/question/19…

这里使用了 String.format() 方法,作用就是格式化输出参数。%02d 是指定输出格式,%作先导标记,0 表示自动补 0, 2 的意思是最小长度为 2(如果用 4,则 1 输出 0001),d 表示整数。

HashMap 静态声明

private static Map<String, String> paramMap;
static {
    paramMap = new HashMap<>();
    paramMap.put("Device.Time.CurrentLocalTime", "dataTime");
}

数据类型转换

// String 转 Boolean  
String a = "true";
Boolean.getBoolean(a);

// Boolean 转 String
boolean b = true;
String s = String.valueOf(b);

int 和 Integer

1,无论如何,Integer 与 new Integer 不会相等。不会经历拆箱过程,new 出来的对象存放在堆,而非 new 的 Integer 常量则在常量池(在方法区),他们的内存地址不一样,所以为 false。

2,两个都是非 new 出来的 Integer,如果数在-128 到 127 之间,则是 true, 否则为 false。因为 java 在编译 Integer i2 = 128 的时候,被翻译成:Integer i2 = Integer.valueOf(128); 而 valueOf() 函数会对-128 到 127 之间的数进行缓存。

3,两个都是 new 出来的,都为 false。还是内存地址不一样。

4,int 和 Integer(无论 new 否)比,都为 true,因为会把 Integer 自动拆箱为 int 再去比。

————————————————

版权声明:本文为 CSDN 博主「login_sonata」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文地址 blog.csdn.net/login_sonat…

转义

import org.apache.commons.lang3.StringEscapeUtils;
StringEscapeUtils.unescapeJava(taskRs.getImgFeature().replace("\"",""));

编码技巧

SQL 中不要使用 where 1=1

原因:

  • 使用 where 1=1 虽可以很方便的解决问题,但是会造成非常大的性能损失。
  • 添加了 where 1=1 就无法使用索引等查询优化策略,数据库会被迫全表扫描,当表中数据量大的话,查询速度会非常慢。

Mybatis 中的正确使用:使用 标签

<!-- 根据条件查询产品 -->
<select id="selectByInfo" resultType="com.zte.vops.tmc.pojo.Product" resultMap="BaseResultMap">
    select
        <include refid="Base_Column_List" />
    from product
    where 1=1
        <if test="productName != null">
            and product_name = #{productName,jdbcType=VARCHAR},
        </if>
        <if test="bussType != null">
            and buss_type = #{bussType,jdbcType=INTEGER},
        </if>
    order by product_name
</select>
<!-- 根据条件查询产品 -->
<select id="selectByInfo" resultType="com.zte.vops.tmc.pojo.Product" resultMap="BaseResultMap">
    select
        <include refid="Base_Column_List" />
    from product
<!-- where标签可以自动添加where,同时处理sql语句中第一个and关键字 -->
    <where>
        <if test="productName != null">
            and product_name = #{productName,jdbcType=VARCHAR},
        </if>
        <if test="bussType != null">
            and buss_type = #{bussType,jdbcType=INTEGER},
        </if>
    </where>
    order by product_name
</select>

注解使用

@RestController

使用 @RestController 可以取代 @Controller + @RequestBody 注解,确保请求返回 json 格式

反例

@Controller
public class ReadController {
    @Resource
    private ReadService readService;

    @GetMapping("/pojo")
    @RequestBody
    public Ingredient readPojo() {
        return readService.readPojo();
    }
}

正例

@RestController
public class ReadController {
    @Resource
    private ReadService readService;
    
    @GetMapping("/pojo")
    public Ingredient readPojo() {
        return readService.readPojo();
    }
}

框架使用

Feign

配置特定接口(客户端)超时时间

在 yml 文件中修改配置。

feign:
  client:
    config:
      default:
        connectTimeout: 3000
        readTimeout: 40000
      terminal-service:
        connectTimeout: 3000
        readTimeout: 100000

如上 default 表示默认所有接口,terminal-service@FeignClient 注解中 name 为 terminal-service 的 api(客户端)下的接口。

若从 cpe 模块调用 protocol 模块,则应在 cpe 模块的配置文件中修改上述配置,即 在调用方配置

对抽象理解

在继承关系较高的类中,有一些不适合写具体方法,但是大家都会用到的方法声明

使用 abstract 关键词将该方法声明为抽象方法,注意: 包含一个或多个抽象方法的类本身必须被声明为抽象的,即 - 一个类如果有抽象方法,则这个类一定是抽象的 同时,一个类即使没有抽象方法也可以被声明为抽象类。

抽象类中可以包含抽象方法/具体方法/具体数据,极致的抽象类(即不包含具体方法的类)我们一般管他叫接口

一个类只能继承一个抽象类,而一个类却可以实现多个接口

抽象类不能被实例化,只能通过其具体的子类进行实例化。如 List

public void abTest(){
    List<String> list = new ArrayList<>();
}

Java 设计模式

单例模式

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

/**
 * 设计模式之单例模式
 * 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
 * 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,
 * 不需要实例化该类的对象。
 * 
 * 1. 单例模式只能有一个实例
 * 2. 单例必须自己创建自己的唯一实例
 * 3. 单例必须给其他所有对象提供这一实例
 * 
 * 主要解决:一个全局使用的类频繁地创建与销毁。
 * 
 * 何时使用:当您想控制实例数目,节省系统资源的时候。
 */
public class DesignPatternSingleton {
    public static void main(String[] args) {
        SlackerSingletonObject slackerSingletonObject = SlackerSingletonObject.getInstance();
        HungrySingletonObject hungrySingletonObject = HungrySingletonObject.getInstance();
        EnumSingletonObject enumSingletonObject = EnumSingletonObject.INSTANCE;
    }
}

/**
 * 懒汉模式-单例模式
 */
class SlackerSingletonObject{
    private static SlackerSingletonObject instance;
    private SlackerSingletonObject() {
        System.out.println("加载 SlackerSingletonObject");
    }
    public static synchronized SlackerSingletonObject getInstance() {
        if(instance==null) {
            instance = new SlackerSingletonObject();
        }
        return instance;
    }
    public static Map<String,Object> getProFile(){
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("jdbcDriver", "oracle");
        return map;
    }
}

/**
 * 饿汉模式-单例模式
 */
class HungrySingletonObject{
    private static HungrySingletonObject instance = new HungrySingletonObject();
    private String name ;
    private HungrySingletonObject() {
        System.out.println("加载 HungrySingletonObject");
    }
    public static HungrySingletonObject getInstance() {
        return instance;
    }
    public static Map<String,Object> getProFile(){
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("jdbcDriver", "oracle");
        return map;
    }
}

/**
 * 枚举--单例模式
 */
enum EnumSingletonObject{
    INSTANCE;
    public static Map<String,Object> getProFile(){
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("jdbcDriver", "oracle");
        return map;
    }
}

工厂模式

/**
 * 设计模式之工厂模式
 * 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
 * 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
 * 
 * 主要解决:主要解决接口选择的问题。
 * 何时使用:我们明确地计划不同条件下创建不同实例时。
 * 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
 * 关键代码:创建过程在其子类执行。
 */
public class DesignPatternFactory {
    public static void main(String[] args) {
        ShapeFactory shapeFactory = new ShapeFactory();
         
          //获取 Circle 的对象,并调用它的 draw 方法
          Shape shape1 = shapeFactory.getShape("CIRCLE");
     
          //调用 Circle 的 draw 方法
          shape1.draw();
     
          //获取 Rectangle 的对象,并调用它的 draw 方法
          Shape shape2 = shapeFactory.getShape("RECTANGLE");
     
          //调用 Rectangle 的 draw 方法
          shape2.draw();
     
          //获取 Square 的对象,并调用它的 draw 方法
          Shape shape3 = shapeFactory.getShape("SQUARE");
     
          //调用 Square 的 draw 方法
          shape3.draw();
    }
}

interface Shape{
    public void draw();
}

class Rectangle implements Shape{
    @Override
    public void draw() {
         System.out.println("Inside Square::draw() method.");
    }
    
}

class Circle implements Shape{
    @Override
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}

class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

class ShapeFactory {
    public Shape getShape(String shapeType) {
        if(shapeType==null) {
            return null;
        }
        if(shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        }else if(shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        }else {
            return new Square();
        }
    }
}

Java Interface 接口

作者:刘扬俊 链接:blog.csdn.net/qq_19782019… 来源:CSDN

接口的概念

官方解释:Java 接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

我的解释:接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决 Java 无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为 100%的抽象类,即接口中的方法必须全部是抽象方法。(JDK1.8 之前可以这样理解)

接口的特点

就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。 接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。 一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者 implement move() 方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。 如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类) 一个 JAVA 库中接口的例子是:Comparator 接口,这个接口代表了“能够进行比较”这种能力,任何类只要实现了这个 Comparator 接口的话,这个类也具备了“比较”这种能力,那么就可以用来进行排序操作了。

为什么要用接口

接口被用来描述一种抽象。 因为 Java 不像 C++一样支持多继承,所以 Java 可以通过实现接口来弥补这个局限。 接口也被用来实现解耦。 接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非 final 的变量,但是在接口中存在的变量一定是 final,public,static 的。

接口的进一步理解

我们知道,如果某个设备需要向电脑中读取或者写入某些东西,这些设备一般都是采用 USB 方式与电脑连接的,我们发现,只要带有 USB 功能的设备就可以插入电脑中使用了,那么我们可以认为 USB 就是一种功能,这种功能能够做出很多的事情(实现很多的方法),其实 USB 就可以看做是一种标准,一种接口,只要实现了 USB 标准的设备我就认为你已经拥有了 USB 这种功能。(因为你实现了我 USB 标准中规定的方法),下面是具体的例子:

先声明 USB 接口:其中规定了要实现 USB 接口就必须实现接口规定实现的 read( ) 和 write( ) 这两个方法。

interface USB(){
    void read();
    void write();
}

然后写一个 U 盘类和一个键盘类,这两个类都去实现 USB 接口。(实现其中的方法)

class UDisk implements USB{
    @Override
    public void read(){
        System.out.println("U 盘通过 USB 读取");
    }
    @Override
    public void write(){
        System.out.println("U 盘通过 USB 写入");
    }
}

class KeyBoard implements USB{
    @Override
    public void read(){
        System.out.println("键盘通过 USB 读取");
    }
    @Override
    public void write(){
        System.out.println("键盘通过 USB 写入");
    }
}

那么,现在 U 盘和键盘都实现了 USB 功能,也就是说 U 盘和键盘都能够调用 USB 接口中规定的方法,并且他们实现的方式都不一样。

我们在写一个测试,来看看具体的实现:

public class Main{
    public static void main(String[] args){
        // generate a UDisk Object
        UDisk uDisk = new UDisk();
        uDisk.read();
        uDisk.write();

        KeyBoard keyBoard = new KeyBoard();
        keyBoard.read();
        keyBoard.write();
    }
}

关于接口的几个重点

  1. 我们不能直接去实例化一个接口,因为接口中的方法都是抽象的,是没有方法体的。 这样怎么可能产生具体的实例呢? 我们可以使用接口类型的引用指向一个实现了该接口的对象,并且可以调用这个接口中的方法。因此,根据 Java 中多态的特性,前文的方法调用可以如下方式写:
public class Main{
    public static void main(String[] args){
        // 类似 List 和 Map 
        USB keyBoard = new KeyBoard();
        keyBoard.read();
        keyBoard.write();
    }
}
  1. 一个类可以实现不止一个接口。
  2. 一个接口可以继承于另一个接口,或者另一些接口,接口也可以继承,并且可以多继承。
  3. 一个类如果要实现某个接口的话,那么它必须要实现这个接口中的所有方法。
  4. 接口中所有的方法都是抽象的和 public 的,所有的属性都是 public,static,final 的。
  5. 接口用来弥补类无法实现多继承的局限。
  6. 接口也可以用来实现解耦。

接口的通俗理解

前面我们讲多态的时候用“空调”——“遥控器”的方式去理解多态,实际上在上面的的几个重点中的第一条讲的也是多态的实现,比如,我们可以把“节能”作为一种标准,或者说节能就是一个“接口”,这个接口中有一个方法,叫做变频方法,任何空调,如果要称得上叫做节能空调的话,那么必须实现“节能”这个接口,实现“节能”这个接口,也就必须实现“节能”接口中规定实现的“变频”方法,这样才算是真正的实现了“节能”这个接口,实现了“节能”这个功能。

当某个空调实现了“节能”接口后,这个空调就具备了节能的功能,那么我们也可以不用空调类的引用指向空调对象,我们可以直接使用一个“节能”接口类型引用的“遥控器”去指向“空调”,虽然这个“遥控器”上面只有一个按键,只有一个“变频”的方法,但是“遥控器”所指向的空调是实现了“节能”这个接口的,是有“变频”方法的实现的,我们用这个只有一个“变频”方法的遥控器去命令空调调用“变频”方法,也是行得通的,实在不清楚的同学可以去看我的另一篇文章:JAVA 之对象的多态性

接口的标识用法

虽然接口内部定义了一些抽象方法,但是并不是所有的接口内部都必须要有方法,比如 Seriallizable 接口,Seriallizable 接口的作用是使对象能够“序列化”,但是 Seriallizable 接口中却没有任何内容,也就是说,如果有一个类需要实现“序列化”的功能,则这个类必须去实现 Seriallizable 接口,但是却并不用实现方法(因为接口中没有方法),此时,这个 Serilizable 接口就仅仅是一个“标识”接口,是用来标志一个类的,标志这个类具有这个“序列化”功能。具体的实现请参考我的另一篇文章:JAVA 之 IO 流

Java 超时机制

public class InterfaceTimeOut{
    public static void main(String args[]){
        final ExecutorService exec = Executors.newSingleThreadExecutor();
        Callable<String>  call = new Callable<String>(){
            @Ovrride
            public String call() throws Exception{
                Thread.sleep(1000 * 5);
                return "线程执行完";
            }
        };
 
        Future<String> future = exec.submit(call);
        String obj = null;
        try{
            obj = future.get(1000 * 6,TimeUnit.MILLISECONDS);
            System.out.print("任务成功返回"+obj);
        }catch(InterruptedException e){
            e.printStackTrace();
        }catch(ExecutionException e){
            e.printStackTrace();
        }catch(TimeoutException e){
            e.printStackTrace();
        }
        exec.shutdown();
    }
}

数组初始化

// 静态初始化
array = new int[]{1,2,3,4,5};

int[] array = {1,2,3,4,5};

// 动态初始化
int[] array = new int[10];

public static native void arraycopy(Object src,int srcPos,Object dest, int destPos,int length);

// src: 源数组
// srcPos: 源数组要复制的起始位置
// dest: 目的数组
// destPos: 目的数组放置的起始位置
// length: 要复制的长度

为什么不建议使用 Date,而是使用 Java8 新的时间和日期 API

作者:武培轩 链接:www.jianshu.com/p/dbe781216… 来源:简书

Date api 存在的线程问题

java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且都不是线程安全的。

Date 如果不格式化,打印出的日期可读性差。

Thu Sep 12 13:47:34 CST 2019

可以使用 SimpleDateFormat 对时间进行格式化,但 SimpleDateFormat 是线程不安全的,SimpleDateFormat 的 format 方法源码如下:

private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
    // Convert input date to time field list
    calendar.setTime(date);

    boolean useDateFormatSymbols = useDateFormatSymbols();

    for (int i = 0; i < compiledPattern.length; ) {
        int tag = compiledPattern[i] >>> 8;
        int count = compiledPattern[i++] & 0xff;
        if (count == 255) {
            count = compiledPattern[i++] << 16;
            count |= compiledPattern[i++];
        }

        switch (tag) {
        case TAG_QUOTE_ASCII_CHAR:
            toAppendTo.append((char)count);
            break;

        case TAG_QUOTE_CHARS:
            toAppendTo.append(compiledPattern, i, count);
            i += count;
            break;

        default:
            subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
            break;
        }
    }
    return toAppendTo;
}

其中 calendar 是共享变量,并且这个共享变量没有做线程安全控制。当多个线程同时使用相同的 SimpleDateFormat 对象【如用 static 修饰的 SimpleDateFormat 】调用 format 方法时,多个线程会同时调用 calendar.setTime 方法,可能一个线程刚设置好 time 值另外的一个线程马上把设置的 time 值给修改了导致返回的格式化时间可能是错误的。

在多并发情况下使用 SimpleDateFormat 需注意。

SimpleDateFormat 除了 format 是线程不安全以外,parse 方法也是线程不安全的。parse 方法实际调用 alb.establish(calendar).getTime() 方法来解析,alb.establish(calendar) 方法里主要完成了

  • 重置日期对象 cal 的属性值
  • 使用 calb 中中属性设置 cal
  • 返回设置好的 cal 对象

但是这三步不是原子操作,导致解析出来的时间可能是错误的。

Date 对时间处理比较麻烦,比如想获取某年、某月、某星期,以及 n 天以后的时间,如果用 Date 来处理的话真是太难了,并且 Date 类的 getYear、getMonth 这些方法都被弃用了。

多线程并发如何保证线程安全

避免线程之间共享一个 SimpleDateFormat 对象,每个线程使用时都创建一次 SimpleDateFormat 对象 => 创建和销毁对象的开销大

对使用 format 和 parse 方法的地方进行加锁 => 线程阻塞性能差

使用 ThreadLocal 保证每个线程最多只创建一次 SimpleDateFormat 对象 => 较好的方法

Java 8 新的时间和日期 API

Java 8 的日期和时间类都在 java.time 包中,包括:

  • LocalDate
  • LocalTime、Instant
  • Duration
  • Period

常用使用方法:

// LocalDate 只会获取年月日
// 创建 LocalDate,获取当前年月日
LocalDate localDate = LocalDate.now();
// 构造指定的年月日
LocalDate localDate1 = LocalDate.of(2019, 9, 12);
// 获取年、月、日、星期几
int year = localDate.getYear();
int year1 = localDate.get(ChronoField.YEAR);
Month month = localDate.getMonth();
int month1 = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.getDayOfMonth();
int day1 = localDate.get(ChronoField.DAY_OF_MONTH);
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
int dayOfWeek1 = localDate.get(ChronoField.DAY_OF_WEEK);

// LocalTime 只会获取时分秒
// 创建 LocalTime
LocalTime localTime = LocalTime.of(14, 14, 14);
LocalTime localTime1 = LocalTime.now();
// 获取小时
int hour = localTime.getHour();
int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
// 获取分
int minute = localTime.getMinute();
int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
// 获取秒
int second = localTime.getMinute();
int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);
// 获取毫秒
int nanoscd = localTime.getNano();

// LocalDateTime 获取年月日时分秒,相当于 LocalDate + LocalTime
// 创建 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
LocalDateTime localDateTime2 = LocalDateTime.of(localDate, localTime);
LocalDateTime localDateTime3 = localDate.atTime(localTime);
LocalDateTime localDateTime4 = localTime.atDate(localDate);
// 获取 LocalDate
LocalDate localDate2 = localDateTime.toLocalDate();
// 获取 LocalTime
LocalTime localTime2 = localDateTime.toLocalTime();

// Instant 获取秒数,用于表示一个时间戳(精确到纳秒)
// 创建 Instant 对象
Instant instant = Instant.now();
// 获取秒数
long currentSecond = instant.getEpochSecond();
// 获取毫秒数
long currentMilli = instant.toEpochMilli();
// 直接获取系统时间戳
System.currentTimeMillis()

// Duration.between() 方法创建 Duration 对象
LocalDateTime from = LocalDateTime.of(2017, Month.JANUARY, 1, 00, 0, 0);    // 2017-01-01 00:00:00
LocalDateTime to = LocalDateTime.of(2019, Month.SEPTEMBER, 12, 14, 28, 0);     // 2019-09-15 14:28:00
Duration duration = Duration.between(from, to);     // 表示从 from 到 to 这段时间
long days = duration.toDays();              // 这段时间的总天数
long hours = duration.toHours();            // 这段时间的小时数
long minutes = duration.toMinutes();        // 这段时间的分钟数
long seconds = duration.getSeconds();       // 这段时间的秒数
long milliSeconds = duration.toMillis();    // 这段时间的毫秒数
long nanoSeconds = duration.toNanos();      // 这段时间的纳秒数

// 修改 LocalDate、LocalTime、LocalDateTime、Instant
// LocalDate、LocalTime、LocalDateTime、Instant 为不可变对象,修改这些对象对象会返回一个副本
LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 12, 14, 32, 0);
// 增加一年
localDateTime = localDateTime.plusYears(1);
localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
// 减少一个月
localDateTime = localDateTime.minusMonths(1);
localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);  
// 通过 with 修改某些值
// 修改年为 2020
localDateTime = localDateTime.withYear(2020);
localDateTime = localDateTime.with(ChronoField.YEAR, 2020);
// 时间计算
// 获取该年的第一天
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.with(firstDayOfYear());

TemporalAdjusters 包含许多静态方法,可以直接调用,以下列举一些:

方法名 描述 dayOfWeekInMonth 返回同一个月中每周的第几天 firstDayOfMonth 返回当月的第一天 firstDayOfNextMonth 返回下月的第一天 firstDayOfNextYear 返回下一年的第一天 firstDayOfYear 返回本年的第一天 firstInMonth 返回同一个月中第一个星期几 lastDayOfMonth 返回当月的最后一天 lastDayOfNextMonth 返回下月的最后一天 lastDayOfNextYear 返回下一年的最后一天 lastDayOfYear 返回本年的最后一天 lastInMonth 返回同一个月中最后一个星期几 next / previous 返回后一个/前一个给定的星期几 nextOrSame / previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回

格式化时间

LocalDate localDate = LocalDate.of(2019, 9, 12);
String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
// 自定义格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String s3 = localDate.format(dateTimeFormatter);

// 注:
// localDate.format 与 sdf.format 并不是同一个 format 方法

// 解析时间
LocalDate localDate1 = LocalDate.parse("20190912", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate localDate2 = LocalDate.parse("2019-09-12", DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate localDate2 = LocalDate.parse("2019-09-12", DateTimeFormatter.ofPattern("yyyy-MM-dd");

总结

和 SimpleDateFormat 相比,DateTimeFormatter 是线程安全的。

Instant 的精确度更高,可以精确到纳秒级。

Duration 可以便捷得到时间段内的天数、小时数等。

LocalDateTime 能够快速地获取年、月、日、下一月等。

TemporalAdjusters 类中包含许多常用的静态方法,避免自己编写工具类。

Java8 时间 API 使用

LocalTime(本地时间)

LocalTime 表示一天中的某个时间,例如 18:00:00。LocaTime 与 LocalDate 类似,他们也有相似的 API。

需要注意的是:LocalTime 本身不关心是 AM 还是 PM,而是格式化程序来负责这个事情。

LocalDateTime(本地日期时间)

LocalDateTime 表示一个日期和时间,它适合用来存储确定时区的某个时间点。不适合跨时区的问题。

若需要处理跨时区的时间,需要使用 ZonedDateTime.

ZonedDateTime(带时区的时间)

时区(Time Zone) 是地球上的区域使用同一个时间定义。1884 年在华盛顿召开国际经度会议时,为了克服时间上的混乱,规定将全球划分为 24 个时区。

由于实用上常常 1 个国家,或 1 个省份同时跨着 2 个或更多时区,为了照顾到行政上的方便,常将 1 个国家或 1 个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。