代码整洁之道

ctrl + shift + f 格式化代码

一、String优化:

1、字符串判空使用str.length == 0 length是string的属性,调用属性性能非常高
2、将整形装换为String不要使用 1 + "" 使用 String.valueOf(1);
3、使用字符串拼接不使用 “str” + “ing” 而是使用src.concat("").concat(""); 或者使用StringBuilder或者StringBuffer,如果字符串不是成员变量,是局部变量的话,使用StringBuilder,性能很高。
4、在使用StringBuilder、StringBuffer的时候最好能给一个容量初始值
5、在创建String的时候最好使用 String str = ""; 而不使用 String str = new String("");
6、字符串的分割,split性能最佳

二、数组优化

1、初始化数组最佳 String[] strs = new String[10]; 不要使用String strs[] = new String[10];
2、存放基本类型数据集,有限考虑数组,对于javaBean使用集合、队列、栈

/**
* 输出结果为 1
* @param args
*/
public static void main(String[] args) {
    int[] as = new int[]{1,2,3,4,5};
    List<int[]> ints = Arrays.asList(as);
    //输出结果为1
    System.out.println(ints.size());
}
/**
 * 输出结果为 5
 * @param args
 */
public static void main(String[] args) {
    Integer[] as = new Integer[]{1,2,3,4,5};
    List<Integer> ints = Arrays.asList(as);
    System.out.println(ints.size());
}

3、数组的copy

package com.example.config;

import java.util.Arrays;

/**
 * @className TestArray
 * @program: spring-cloud
 * @author: Zyred
 * @create: 2019-10-24 17:15
 **/
public class TestArray {
    private static final byte[] buffer = new byte[1024 * 10];
    static {
        for (int i = 0, len = buffer.length; i < len; i++) {
            buffer[i] = (byte) (i);
        }
    }
    private static long startTime ;
    public static void main(String[] args) {
        startTime = System.nanoTime();
        byte[] newBuffer = new byte[1024 * 10];
        for (int i = 0, lens = newBuffer.length; i < lens; i++) {
            newBuffer[i] = buffer[i];
        }
        calcTime("forCopy");

        startTime = System.nanoTime();
        byte[] clone = buffer.clone();
        calcTime("cloneCopy");

        startTime = System.nanoTime();
        byte[] copyOf = Arrays.copyOf(buffer, buffer.length);
        calcTime("Arrays.copyOf");

        startTime = System.nanoTime();
        byte[] newBuffer1 = new byte[buffer.length];
        System.arraycopy(buffer, 0, newBuffer1,0, buffer.length);
        calcTime("System.arraycopy");
    }

    private static void calcTime(String str){
        System.out.println(str + "----->" + (System.nanoTime() - startTime) / 1000);
    }
}

结果:

forCopy----->293
cloneCopy----->13
Arrays.copyOf----->28
System.arraycopy----->8

4、数组的深层拷贝SerializationUtils.clone();

三、集合

1、CollectionUtils集合判空
2、空集合的优雅获取方式

public  List<String> getEmptyList(){
    //SET  Collections.EMPTY_SET
    //MAP  Collections.EMPTY_MAP
    return Collections.EMPTY_LIST;
    return Collections.EmptyList();
}

四、优雅的方法

1、方法的名字,和业务场景尽量相似 2、避免方法返回null值

public String getStr(int a){
    if(a > 0){
        return "yes";
    }
    //减少return null的方法
    return null;
}

3、避免方法的层级太深,最多不超过三层,如果有三层以上的,尽量优化到三层以下

public void test(int a){
    boolean flag = true;
    if(flag){
        if(flag){
            if(flag){
                if(flag){
                    System.out.println("---->" + "is empty");
                }
            }
        }
    }
}

4、如果方法的形参超过3个,可以考虑封装成实体类
5、子类继承父类,重写了父类的方法后,一定要加上@Override注解,避免下一个程序员操作的时候,改了重写后的方法名称,导致出错
6、在接口中最好不要采用方法的修饰符

public interface InterfaceTest(){
    //错误写法
    public void test();
    public String getIndex();
    //正确写法,因为在接口中定义的抽象方法,每一个方法都是public,
    void test();
    void getIndex();
}

7、final的使用,提升代码整体的性能。

public void test(final Mpa<String> map, final String id){
	//这样会报错的,形参中使用了final有效的杜绝了代码中的参数被改变
	id = "";
}

五、优雅的使用异常

1、禁止捕获非受检异常

public void getStr(){
    try{
        
    }catch(NullPointerException e){
        
    }
}

2、对异常的分类进行处理

try{
    
}catch(Exception e){
    //禁止不做任何操作
}

try{
    
}catch(FileNotFoundException e){
        logger.warn("文件找不到,请检查")
        e.printStackTrace();
}

3、根据业务场景,封装自己适合的异常 4、循环体中禁止放异常块

六、范型的使用

1、泛型的擦除

//书写的时候,泛型用于校验,规定传入的类型
public void test(){
	List<String> list = new ArrayList<>();
	list.add("Z");
	list.add("y");
}
//反编译的结果, 
public void test(){
    List list = new ArrayList<>();
    list.add("Z");
	list.add("y");
}

2、使用原生集合(不添加泛型),泛型默认为Object,如果后续取值的会出现ClassCastException异常
3、利用有限通配符来提升API的灵活性

<? extends T>   //类型必须继承T
<? super T>		//超类限定,类型必须是此类的超类(父类)
    
public class TestClass{
    class Fruit{}
    class Apple extends Fruit{}
    class RedApple extends Apple{}
    
    public void test(){
        List<Fruit> fruitList = new ArrayList<>();
        List<? super Apple> appleList = fruitList;
        //正确
        appleList.add(new Apple()); 
        appleList.add(new RedApple());
        //无法添加
        appleList.add(new Fruit());
        appleList.add(new Object());
    }
}

七、枚举

1、标准的枚举类

public enum ColorEnum{
    
    BULE(1, "黄色"),
    YELLOW(2, "黄色"),
    BLACK(3, "黑色");
    
    private final Integer code;
    
    private final String name;
    
    ColorEnum(Integer code, String name){
        this.code = code;
        this.name = name;
    }
    
    public String getName(){
        return this.name;
    }
    
    @Override
    public String toString(){
        return this.code;
    }
}

2、switch语句只支持int,char,改用枚举

enum Signal {
    GREEN, YELLOW, RED
}

public class test {

    public static void main(String[] args) {
        Signal color = Signal.RED;
        switch (color) {
            case RED:
                color = Signal.GREEN;
                break;
            case YELLOW:
                color = Signal.RED;
                break;
            case GREEN:
                color = Signal.YELLOW;
                break;
        }
    }
}

八、序列化和反序列化

1、及时关闭流、先开后关、后开先关

public void testStream(HttpServletResponse resp){
    OutputStream out = resp.getOutputStream();
    InputStream is = new InputStream();
    //先关闭后开启的流
    is.close();
    //后关闭先开启的流
    out.close();
}

2、文件中的分隔符使用类中常量

//windows中和linux系统中,文件的路径斜杠方向不同,可是可以使用File.pathSeparator或者File.separator
pubulic static void mian(String[] args){
    //在windows系统中输出内容是\ 而在linux中输出的内容是/
    System.out.println(File.pathSeparator);
}

3、尽量使用缓存流

在软件系统中,IO速度比内存速度慢,IO读写在很多情况下会是系统的瓶颈。在java标准IO操作中,InputStream和OutputStream提供基于流的IO操作,以字节为处理单位;Reader和Writer实现了Buffered缓存,以字符为处理单位。从Java1.4开始,增加NIO(New IO),增加缓存Buffer和通道Channel,以块为处理单位,是双向通道(可读可写,类似RandomAccessFile),支持锁和内存映射文件访问接口,大大提升了IO速度。


缓冲流分为字节和字符缓冲流

字节缓冲流为:

BufferedInputStream 字节输入缓冲流

BufferedOutputStream 字节输出缓冲流

字符缓冲流为: BufferedReader 字符输入缓冲流 BufferedWriter 字符输出缓冲流


4、流的四种使用

a、BufferedOutputStream 定义: BufferedOutputStream类实现缓冲的输出了,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每一个字节写入都调用底层系统

public static void main(String[] args) { 
    try { 
        //创建字节输出流实例 
        OutputStream out=new FileOutputStream("I:\\test.txt"); 
        //根据字节输出流构建字节缓冲流 
        BufferedOutputStream buf=new BufferedOutputStream(out); 
        String data="好好学习,天天向上"; 
        //写入缓冲区
        buf.write(data.getBytes());
        //写入缓冲区,刷新缓冲区,即把内容写入 
        buf.flush();
        //关闭流 关闭缓冲流时,也会刷新一次缓冲区 
        buf.close();
        out.close(); 
    } catch (IOException e) { 
        e.printStackTrace(); 
    } 
}

b、BufferedInputStream 定义: BufferedInputStream为别的输入流添加缓冲功能,在创建BufferedInputStream时会创建一个内部缓冲数组,用于缓冲数据,提高性能。

public static void main(String[] args) {
       try {
        //创建字节输入流实例
        InputStream in=new FileInputStream("L:\\test.txt");
        //根据字节输入流构建字节缓冲流
        BufferedInputStream buf=new BufferedInputStream(in);
        byte[]bytes=new byte[1024];
        //数据读取
        int len=-1;
        StringBuffer sb = new StringBuffer();
        while((len = buf.read(bytes)) != -1){
            sb.append(new String(bytes,0,len));
        }
        System.out.println("内容为:"+sb);
        //关闭流
        buf.close();
        in.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

c、BufferedWriter 定义: 将文本写入字符输出流,缓冲各个字符,从而提供高效的写入。可以指定缓冲区的大小,一般情况下,默认的缓冲区大小就足够了

public static void main(String[] args) {
    try {
        Writer w = new FileWriter("L:\\test.txt");
        //根据字符输出流创建字符缓冲流
        BufferedWriter buf = new BufferedWriter(w);
        //写入数据
        buf.write("只要功夫深铁杵磨成针");
        //刷新流
        buf.flush();
        //关闭流
        buf.close();
        w.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

d、BufferedReader 定义: 从字符输入流中读取信息,缓冲各个字符,从而实现高效读取。可以指定缓冲区的大小,一般情况下,默认的缓冲区大小就足够了

public static void main(String[] args) {
    try {
        Reader r = new FileReader("L:\\test.txt");
        //根据字符输入流创建字符缓冲流
        BufferedReader buf = new BufferedReader(r);
        char [] data = new char[512];
        //数据读取
        int len = -1;
        StringBuilder sb = new StringBuilder();
        while((len = buf.read(data)) != -1){
            sb.append(new String(data,0,len));
        }
        System.out.println("内容是: "+sb);  
        //关闭流
        buf.close();
        r.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

九、多线程

参考:

1、变量的线程安全分为成员变量局部变量,如果变量是成员变量,很有可能造成线程不安全的问题,如果是final修饰了的变量例外

2、方法锁,对象锁,类锁概念

方法锁--->synchronized 修饰方法时。 对象锁--->synchronized 修饰方法或代码块。 类锁------>synchronized 修饰静态的方法或代码块。

总结:synchronized 关键字加到 static 方法上是给 Class 上锁,加到非 static 方法上是给对象上锁。class 锁可以对类的所有实例起作用,即就是在任何时候,只能有一个线程访问该类的 static 方法。因为 static 方法就是类方法。

3、锁优化的思路

a、减少锁持有时间(尽量缩小锁的范围)
b、降低锁粒度
c、锁分离
d、锁粗化
e、锁消除

a、减少锁持有时间

public synchronized void synchronizedTest(){ getName(); //该方法满足线程安全 syncMethod(); getAge(); } *******************优化******************** public void synchronizedTest(){ getName(); getAge(); //将需要线程安全的方法使用synchronized关键字,不需要的,就让其他线程执行,提升系统的性能 synchronized(this){ syncMethod(); } }

b、降低锁粒度:降低锁的粒度,是为了降低线程对锁的竞争。

// TODO ConCurretnHashMap看了后再写此部分

c、锁分离

//TODO LinkedBlockingQueue看完再写

d、锁粗化

定义: 通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早地获得资源执行任务。但是,如果对同一个锁不停地进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。

为此,虚拟机在遇到一连串连读地对同一锁不断进行请求和释放的操作时,便会把所有的锁作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作叫做锁是粗化。比如代码段:

public void demoMethod() {     synchronized(lock) {         //do sth1     }     //做其他不需要的同步的工作,但能很快执行完毕     synchronized(lock) {         //do sth2     } }

粗锁后的代码:

public void demoMethod() { synchronized(lock) { //do sth1 //do sth2 } }

e、锁消除 定义:

锁消除是一种更彻底的锁优化,Java虚拟机在JIT编译时,通过对上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。

//例如,在一个不可能存在并发竞争的场合使用Vector,而Vector内部使用了Synchronized请求锁 public String[] createStrings() { //属于线程私有数据,不可能被其它线程访问 //JVM检测到无用的锁,会清除无用的锁     Vector<String> v = new Vector<String>();     for(int i=0; i<100; i++) {         v.add(Integer.toString(i));     }     return v.toArray(new String[]{}); }

4、优雅休眠线程

public static void main(String[] args) throws InterruptedException {
    int a = 1;
    //总结, <<= 是给参与运算的变量附上值,并且存在返回值
    int b = a <<= 2;
    //不改变参与运算的变量,只又运算后的返回值
    int c = a << 2;
    //a = 4
    System.out.println(a);
    //b = 4
    System.out.println(b);
    //c = 16
    System.out.println(c);

    //启动线程休眠
    TimeUnit.MINUTES.sleep(1);

    System.out.println("over----->");
}

十、优雅关闭I/O流

public static void main(String[] args) {
    byte[] buffer = new byte[1024];
    //在try中声明需要关闭流的对象
    try (FileInputStream fis = new FileInputStream(new File("E:\\test.md"))) {
        while (fis.read(buffer) > 0) {
            System.out.println(buffer.toString());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//如果是多个需要关闭的流,则可以这么写
public static void main(String[] args) {
    byte[] buffer = new byte[1024];
    try (FileInputStream fis = new FileInputStream(new File("E:\\input.txt"));
         FileOutputStream fos = new FileOutputStream(new File("E:\\output.txt"));
         OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
        fis.read(buffer);
        char[] c = getChars(buffer);
        osw.write(c);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

十一、stream如何优雅

1、理解终端操作和中间操作

定义:

终端操作:终端操作会关闭该流,因为Stream顶级接口继承了AutoCloseable类 中间操作:中间操作会创建一个新的流,即返回参数是一个流,例如:

//返回值类型则是一个Stream<T> Stream<T> filter(Predicate<? super T> predicate);测试:

实现:

public static void main(String[] srgs){ List<Integer> lists = new ArrayList<>(); lists.add(4); lists.add(3); lists.add(6); lists.add(1); lists.add(5); lists.add(2); lists.add(-2); //此时产生了一个流,可以被理解为中间操作 Stream<Integer> stream = lists.parallelStream(); //1 //调用了一个终端操作 Optional<Integer> min = stream.min(Integer::compareTo); //2 System.out.println("最小值:" +min.get()); //如果此时继续操作第1步则会出现关闭流无法操作的异常 //如果想正常操作,则将该步骤放入第一步和第二步中间即可 Optional<Integer> max = stream.max(Integer::compareTo); } //异常信息: Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

2、reduce()方法的执行逻辑

//a = 1 5 8 14 15 20 22
//b = 4 3 6  1  5  2  2
public static void main(String[] srgs){
    List<Integer> lists = new ArrayList<>();
    lists.add(4);
    lists.add(3);
    lists.add(6);
    lists.add(1);
    lists.add(5);
    lists.add(2);
    lists.add(2);
    Integer integerReduce = lists.stream().reduce(0, (a, b) -> {
        System.out.println("a = " + a);
        System.out.println("b = " + b);
        return a + b;
    });
    System.out.println(integerReduce);
}
//得到运行结果:
/*a = 0
b = 4
a = 0
b = 3
a = 7
b = 6
a = 13
b = 1
a = 14
b = 5
a = 19
b = 2
a = 21
b = 2
*/
//整理得到的结果:
//a = 0 4 7 13 14 19 21
//b = 4 3 6  1  5  2  2

notice:调用T reduce(T identity, BinaryOperator<T> accumulator);是这个方法;

结论:identity = 0, accumulator中a变量的初始值就是0, 此时b变量代表list中的每个值,第一个则是4,打印出结果 a = 0 b = 4,注意:执行到了return 语句了,return语句并不是代表结束当前调用,如果结束了当前调用,那么返回的结果则是4,显然是错误的,那么这个return起到什么作用呢?此时的return是在 BinaryOperator<T> accumulator中进行return 的,所以,只是结束了这个表达式,然后将return的值,重新赋值到了a变量,此时 a = 4,再重复一次则得到了a = 7, a = 13 ....., a = 21,当 a =21的时候,list集合中还存在一个值,就是最后一个 b = 2,最后一次循环return 21 + 2; 结果自然就是 23啦

总结:identity:只是一个a变量初始的大小,在第一次的时候这个初始值也会参与计算; a变量:第一次加载identity的值,之后存储每次和b变量相加的值; b变量:遍历得到list集合中每个元素;

十二、优雅如何落地

1、简单的if/esle判断

if("a".equals(str)){
	return "success";
}
return "error";

if(1 == sex){
    return "男";
}else if(0 == sex){
 	return "女";   
}else{
    return "其他";
}
//这么写 他不香嘛?
return "a".equals(str) ? "success" : "error";
return 1 == sex ? "男" : 0 == sex ? "女" : "其他";

2、大量的 || 和 && 条件判断

private final String name = "job"; 
if("what".equals(name) 
   || "is".equals(name) 
   || "the".equals(name) 
   || "fuck".equals(name) 
   || "code".equals(name) 
   || ",".equals(name) 
   || "stupid".equals(name)){
}
//创建方法
public static boolean paramCheck(String name){
    //扩展性强,阅读性高
    String[] paras = {"what", "is", "the", "fuck", "code", ",", "stupid"};
    for(int i = 0, len = paras.length; i < len; i++){
        //这么写,不香吗?
        if(paras[i].equals(name)){
            return true;
        }
    }
    return false;
}

3、代码的冗余

Map<Integer, Object>> map = new HashMap<>();

if (1 == flag) {
    map.put(i, value1);
} else {
    map.remove(i);
}
if (1 == flag) {
    map.put(i, value2);
} else {
    map.remove(i);
}
if (1 == flag) {
    map.put(i, value3);
} else {
    map.remove(i);
}
private void addNode(Map<Integer, Object>> map, int flag, int key, Object... objs) {
    //定义if条件为1,为满足XXX条件,目的是不要使用魔法变量
    int myFlag = 1;
    for(int i = 0, len = objs.length; i < len; i ++){
        if (myFlag == insertFlag) {
            retList.put(key, objs[i]);
        } else {
            retList.remove(key);
        }
    }
}

4、大佬曾经说过,一个方法操作30行代码,都是小白

public void xxx(Person p){
    /**数据验证逻辑,此处省略业务20行代码**/
    
    /**************分隔符**************/
    
    /** 数据添加逻辑,此处省略业务20行代码**/
    
    /**************分隔符**************/
    
    /**数据删除逻辑,此处省略业务20行代码**/
}
public void personCheckInfo(){
     /**数据验证逻辑,此处省略业务20行代码**/
}

public void addPersonInfo(){
     /** 数据添加逻辑,此处省略业务20行代码**/
}

public void delPersonInfo(){
    /**数据删除逻辑,此处省略业务20行代码**/
}

/**
 * 主方法, 分三个方法出来只是我个人习惯,其实实际开发中,这种场景没必要全部逻辑提出来分为方法
 * 这么做的目的是为了减少每一个栈帧中数据的大小
 */
public void xxx(Person p){
    personCheckInfo();
    addPersonInfo();
    delPersonInfo();
}

5、方法中减少随意定义变量

public void XXX(Person p){
	//实际上这儿就多了两个String对象
    String name = p.getName();
    Integer age = p.getAge();
    this.personMap.add(name, age);
}
//改进
public void XXX(Person p){
    this.personMap.add(p.getName(), p.getAge());
}

6、封装常用变量 Variable.java

public class Variable{
    /**
    * 例如微信OPEN_ID,一般是放入配置文件,这里只是做演示
    **/
    private final static String WECHAT_OPEN_ID = "";
    private final static Integer SEX_GIRL = 0;
    private final static Integer SEX_BOY = 1;
}

7、包装类型于包装类型做比较判断的时候使用equals()

if(Variable.SEX_BOY == User.getSex());
//建议改为equals(),阿里巴巴规范手册中明确指出
if(Variable.SEX_BOY.equels(User.getSex()));

8、Getter & Setter

//当属性过多的时候,添加get、set方法确实是不明智的选择
public class User{
	private String name;
	private Integer age;
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
//改进如下,pom文件中添加 lambok依赖,在idea中下载lombok插件即可
@Data
public class User{
	private String name;
	private Integer age;
}

9、减少代码层级

/**
* 1、层级过深
* 2、接口不要返回null,因为别人调用你接口的时候,不知道是哪儿出了问题
* 3、调用接口的人,并不知道错误原因
**/
public List<Student> getStudentByTeacherId(Long teacherId){
    if(Objects.nonNull(teacherId)){
        List<Student> listStudent = this.userReadMapper.getStudentByTeacherId(teacherId);
        if(CollectionUtils.isEmpty(listStudent)){
            return null;
        }
        return listStudent;
    }
    return null;
}

/************************分隔符*************************/
public ResponseEntity<List<Student>> getStudentByTeacherId(Long teacherId){
    if(Objects.nonNull(teacherId)){
        //不一定非要返回500,自己定义即可,写清楚接口文档
        return new ResponseEntity("主键不能为空", 500);
    }
    List<Student> listStudent = this.userReadMapper.getStudentByTeacherId(teacherId);
    if(CollectionUtils.isEmpty(listStudent)){
        return Collections.EMPTY_LIST;
    }
    return listStudent;
}

10、实体类中重写toString();

@Override
public String toString() {
    return "User{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
}

//如果对象的属性过于多的情况下,可以考虑使用ablibab的fastJson提供帮助
@Override
public String toString() {
    //这样看起来就相对清晰且不繁琐
    return JSONObject.toJSONString(this);
}

11、异常除日志的打印和查询失败的返回

//为什么返回一个静态的user空对象,而不是new一个对象返回回去
//如果此方法被并发10000访问,那么堆中会new 10000个user对象,而静态的只存在一个,
//如果占用内存过高,那么启动一次GC需要消耗大量的资源。
public static final User EMPTY_USER = new User();

public User getUserInfoByToken(HttpServletRequest req){
	try{
		User user = (User)redisUtils.getUser(req);
	}catch(ClassCastException e){
        log.warn("当前登录的用户不是***类型用户");
        e.printStackTrace();
        //切记不能返回null
        return EMPTY_USER;
    }
}

12、整形如何优雅toString()

int a = 0;
String str = a + "";
//对比结果,很明显第二种优雅吧
String src = String.valueOf(a);

13、如何优雅获取时间

//yyyy-mm-dd
String now = new SimpleDateFormat("yyyy-mm-dd").format(new Date());//no
String now = LocalDate.now();//yes

LocalTime.MAX;   		//获取当天最后时间  23:59:59.999999999
LocalTime.now(); 		//获取 HH:MM:ss:SSS

//获取yyyy-mm-dd HH:MM:ss
new SimpleDateFormat("yyyy-mm-dd HH:MM:ss").format(new Date());  //no
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); //yes
//原则上,Date对象是可变对象,不推荐使用Date对象
//其次是并没有使用new关键字,如果new关键词创建对象诞生在Eden区,那么第二种则没有new对象,节约了系统资源