目录

Java开发笔记

目录

Oracle Java 下载JDK账号

目前在官网下载低于jdk1.8的Java jdk的时候需要登陆,这边分享一个账号,方便下载

账号:2696671285@qq.com

密码:Oracle123

Java获取系统时间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.util.Date;
import java.text.SimpleDateFormat;

public class NowString {
    public static void main(String[] args) { 
          SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
          System.out.println(df.format(new Date()));// new Date()为获取当前系统时间
        //---------------------
          Date date = new Date();       
		 Timestamp nousedate = new Timestamp(date.getTime());
    }
}

整理一

这里的一片文章,我个人认为讲解的很详细,有对 java.sql.Date的使用还有困惑的请看。

java.sql.Date 只存储日期数据不存储时间数据

// 会丢失时间数据

1
preparedStatement.setDate(1, new java.sql.Date(date.getTime())); 

//可以这样来处理

1
preparedStatement.setTimestamp(1, new java.sql.Timestamp(new java.util.Date().getTime())); 

//想要得到完整的数据,包括日期和时间,可以这样

1
java.util.Date d = resultSet.getTimestamp(1); 

//这样处理更合适一些,可以避免一些潜在Timestamp 问题

1
java.util.Date d = new java.util.Date(resultSet.getTimestamp(1).getTime()); 

自己补的话:

往数据库存储的时候可以接收 java.util.Date类型 再用getTime()方法得到代表那个Date对象的long值,再以这个long值 构造一个Timestamp对象 存进数据库中。

从存数据库里取的时候,可以先得到Timestamp用他的getTime()方法得到long值,再以这个long值构造一个 java.util.Date对象,这样就可以对这个Date对象操作了。不如说 new SimpleTimeFormat(“yyyyy-MM-dd HH:mm:ss”).format()等等

整理二

用Timestamp来记录日期时间还是很方便的,但有时候显示的时候是不需要小数位后面的毫秒的,这样就需要在转换为String时重新定义格式。

1
2
3
4
5
6
Timestamp转化为String: SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//定义格式,不显示毫秒
Timestamp now = new Timestamp(System.currentTimeMillis());
//获取系统当前时间 String str = df.format(now);
String转化为Timestamp: SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = df.format(new Date()); Timestamp ts = Timestamp.valueOf(time); 

整理三

在 ResultSet中我们经常使用的setDate或getDate的数据类型是java.sql.Date,而在平时java程序中我们一般习惯使用 java.util.Date。因此在DAO层我们经常会碰到这俩种数据类型的相互转换,经过了一个下午的折腾,鄙人对两者的转换方法做出了小小总结,希望大家不吝指教。 两者的关系

1
2
3
4
5
java.lang.Object
  |
  +---java.util.Date
      |
      +----java.sql.Date 

从这个图中我们可以知道java.sql.Date是从java.util.Date继承过来的。

相互转换

  1. 使用getTime()函数 这两个类都提供了getTime()函数,用于返回对应的毫秒数(long类型)。利用这个函数可以实现转换:

    1
    2
    
    java.util.Date utilDate = new java.util.Date(sqlDate.getTime());  // sql -> util
    java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());  // util -> sql
    
  2. 使用SimpleDateFormat类实现转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类 它允许格式化 (date -> text)语法分析和标准化
    (text -> date)
    SimpleDateFormat dateFormat = new SimpleDateFormate("yyyy-MM-dd HH:mm:ss");
    java.util.Date utilDate = dateFormat.parse(sqlDate.toString());
    (date -> text)
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
    //Date指定格式:yyyy-MM-dd HH:mm:ss:SSS
    Date date = new Date();//创建一个date对象保存当前时间
    String dateStr = simpleDateFormat.format(date);//format()方法将Date转换成指定格式的String
    
  3. 直接转换 由于java.sql.Date是从java.util.Date中继承过来的,所以可以直接用:

    1
    
    utilDate = sqlDate;
    
  4. 另类获得日期的方法:

    1
    2
    3
    4
    5
    6
    
    SimpleDateFormat sy=new SimpleDateFormat("yyyy");
    SimpleDateFormat sm=new SimpleDateFormat("MM");
    SimpleDateFormat sd=new SimpleDateFormat("dd");
    String syear=sy.format(date);
    String smon=sm.format(date);
    String sday=sd.format(date);
    

ps: java.util.Date类中的getYear()要加上1900才可得到实际值,getMonth()则要加上1

java获取当前时间戳的方法

获取当前时间戳

1
2
3
4
5
6
//方法 一
System.currentTimeMillis();
//方法 二
Calendar.getInstance().getTimeInMillis();
//方法 三
new Date().getTime();

获取当前时间

1
2
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
String date = df.format(new Date());// new Date()为获取当前系统时间,也可使用当前时间戳

获取时间戳三种方法执行效率比较:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.Calendar;
import java.util.Date;
 
public class TimeTest {
    private static long _TEN_THOUSAND=10000;
    public static void main(String[] args) {
        long times=1000*_TEN_THOUSAND;
        long t1=System.currentTimeMillis();
        testSystem(times);
        long t2=System.currentTimeMillis();
        System.out.println(t2-t1);
 
        testCalander(times);
        long t3=System.currentTimeMillis();
        System.out.println(t3-t2);
 
        testDate(times);
        long t4=System.currentTimeMillis();
        System.out.println(t4-t3);
    }
 
    public static void testSystem(long times){//use 188
        for(int i=0;i<times;i++){
            long currentTime=System.currentTimeMillis();
        }
    }
 
    public static void testCalander(long times){//use 6299
        for(int i=0;i<times;i++){
            long currentTime=Calendar.getInstance().getTimeInMillis();
        }
    }
 
    public static void testDate(long times){
        for(int i=0;i<times;i++){
            long currentTime=new Date().getTime();
        }
    }
 
}
1
2
3
4
执行结果:
133
2372
137

Calendar.getInstance().getTimeInMillis() 这种方式速度最慢,这是因为Canlendar要处理时区问题会耗费较多的时间。

Java中的<<>>>>> 详细分析

<<表示左移移,不分正负数,低位补0; 

注:以下数据类型默认为byte-8位

左移时不管正负,低位补0

正数:r = 20 << 2

  20的二进制补码:0001 0100

  向左移动两位后:0101 0000

       结果:r = 80

负数:r = -20 << 2

  -20 的二进制原码 :1001 0100

  -20 的二进制原码 :1110 1011

  -20 的二进制补码 :1110 1100

  左移两位后的补码:1011 0000

        反码:1010 1111

        原码:1101 0000

        结果:r = -80

>>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;

注:以下数据类型默认为byte-8位

正数:r = 20 >> 2

  20的二进制补码:0001 0100

  向右移动两位后:0000 0101

       结果:r = 5

负数:r = -20 >> 2

  -20 的二进制原码 :1001 0100

  -20 的二进制反码 :1110 1011

  -20 的二进制补码 :1110 1100

  右移两位后的补码:1111 1011

        反码:1111 1010

        原码:1000 0101

        结果:r = -5

>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0

正数: r = 20 >>> 2

    的结果与 r = 20 >> 2 相同;

负数: r = -20 >>> 2

注:以下数据类型默认为int 32位

  -20:源码:10000000 00000000 00000000 00010100

    反码:11111111 11111111 11111111 11101011

    补码:11111111 11111111 11111111 11101100

    右移:00111111 11111111 11111111 11111011

    结果:r = 1073741819

操作系统控制台中查看Java进程

一、Linux篇

方法一

1
ps -ef|grep java

方法二

1
2
jps -l (显示java进程的Id和软件名称)
jps -lmv(显示java进程的Id和软件名称;显示启动main输入参数;虚拟机参数)

二、Windows篇

1
2
jps
jps -l(显示java进程的Id和软件路径及名称)

Java this()与super()使用详解

这几天看到类在继承时会用到thissuper,这里就做了一点总结,与各位共同交流,有错误请各位指正~

this

this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

this的用法在java中大体可以分为3种:

1.普通的直接引用

这种就不用讲了,this相当于是指向当前对象本身。

2.形参与成员名字重名,用this来区分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Person {
    private int age =10;
    public Person(){
    System.out.println("初始化年龄:"+age);
}
    public int GetAge(int age){
        this.age = age;
        return this.age;
    }
}
 
public class test1 {
    public static void main(String[] args) {
        Person Harry =new Person();
        System.out.println("Harry's age is "+Harry.GetAge(12));
    }
}     

运行结果:

初始化年龄:10 Harry’s age is 12

可以看到,这里age是GetAge成员方法的形参,this.age是Person类的成员变量。

3.引用构造函数

这个和super放在一起讲,见下面。

super

super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

super也有三种用法:

1.普通的直接引用

与this类似,super相当于是指向当前对象的父类,这样就可以用super.xxx来引用父类的成员。

2.子类中的成员变量或方法与父类中的成员变量或方法同名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Country {
    String name;
    void value() {
       name ="China";
    }
}
  
class Cityextends Country {
    String name;
    void value() {
    name ="Shanghai";
    super.value();     //调用父类的方法
    System.out.println(name);
    System.out.println(super.name);
    }
  
    public static void main(String[] args) {
       City c=new City();
       c.value();
       }
}

运行结果:

Shanghai China

可以看到,这里既调用了父类的方法,也调用了父类的变量。若不调用父类方法value(),只调用父类变量name的话,则父类name值为默认值null。

3.引用构造函数

super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。

this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Person { 
    public static void prt(String s) { 
       System.out.println(s); 
    } 
   
    Person() { 
       prt("父类·无参数构造方法: "+"A Person."); 
    }//构造方法(1) 
    
    Person(String name) { 
       prt("父类·含一个参数的构造方法: "+"A person's name is " + name); 
    }//构造方法(2) 
} 
    
public class Chineseextends Person { 
    Chinese() { 
       super();// 调用父类构造方法(1) 
       prt("子类·调用父类”无参数构造方法“: "+"A chinese coder."); 
    } 
    
    Chinese(String name) { 
       super(name);// 调用父类具有相同形参的构造方法(2) 
       prt("子类·调用父类”含一个参数的构造方法“: "+"his name is " + name); 
    } 
    
    Chinese(String name,int age) { 
       this(name);// 调用具有相同形参的构造方法(3) 
       prt("子类:调用子类具有相同形参的构造方法:his age is " + age); 
    } 
    
    public static void main(String[] args) { 
       Chinese cn =new Chinese(); 
       cn =new Chinese("codersai"); 
       cn =new Chinese("codersai",18); 
    } 
}

运行结果:

父类·无参数构造方法: A Person. 子类·调用父类”无参数构造方法“: A chinese coder. 父类·含一个参数的构造方法: A person’s name is codersai 子类·调用父类”含一个参数的构造方法“: his name is codersai 父类·含一个参数的构造方法: A person’s name is codersai 子类·调用父类”含一个参数的构造方法“: his name is codersai 子类:调用子类具有相同形参的构造方法:his age is 18

从本例可以看到,可以用super和this分别调用父类的构造方法和本类中其他形式的构造方法。

例子中Chinese类第三种构造方法调用的是本类中第二种构造方法,而第二种构造方法是调用父类的,因此也要先调用父类的构造方法,再调用本类中第二种,最后是重写第三种构造方法。

super和this的异同:

  • super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
  • this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)
  • super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
  • this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
  • 调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
  • super()和this()类似,区别是,super()从子类中调用父类的构造方法,this()在同一类内调用其它方法。
  • super()和this()均需放在构造方法内第一行。
  • 尽管可以用this调用一个构造器,但却不能调用两个。
  • this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
  • this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
  • 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
1
2
3
4
5
6
7
        BigDecimal b = new BigDecimal(9.655 );
        //double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();

        System.out.println("f1=" + f1);//f1=9.65
        BigDecimal mData = new BigDecimal("9.655").setScale(2, BigDecimal.ROUND_HALF_UP);
        System.out.println("mData=" + mData);//mData=9.66

注:

  1. 此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
  2. 另一方面,String 构造方法是完全可预知的:写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好 等于预期的 0.1。因此,比较而言,通常建议优先使用String` 构造方法。
  3. double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double)方法,然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法。

Java中native关键字的详解

这几天看java的源代码时多次见到方法前有个native关键字,而且凡是有这个关键字修饰的方法都没有方法体,只是一个方法的声明。很是疑惑,于是上网查了一下,现总结如下。

NO.1

​ native用在类的method前面,表示这个method不是用java实现的。

*NO.*2

java语言是运行在虚拟机上的, java又是不允许直接访问硬件的,(也就是java安全性的体现)

而java想要做一些例如绘图、画线之类的要去操作硬件的事情的话, 必然要用到底层一些的调用。 这就引出了native的关键字!

native是一个用来修饰方法的关键字! 用它修饰的方法,在虚拟机里都有一个与这个java方法同名的函数, 去做java想要做的事情! 也就是说: 调用到native修饰的方法, 其实就是调用虚拟机里的一个同名方法去做要做的事! 这也就是为什么native方法都没有方法体的原因! 例如Graphics类里有一个drawRect的方法。 它的定义就是: public native void drawRect(int x, int y, int width, int height); 调用到它的时候,就是告诉虚拟机, 我要画一个根据x,y,width,height这些参数定义的矩形! 而实际起到作用去画这个矩形的, 是虚拟机里的一个名为XXXX_XXXX_drawRect的C函数 XXX使这个函数的打包路径,和import中的XXX.XXX差不多!

简单说: 其实java根本什么也没做, 做的只是一些面向对象的定义(但这些定义也够复杂) 而“干实事”的都一些虚拟机里的那些用C写成的函数!

NO.3

使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。 这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。

NO.4

native的意思就是通知操作系统, 这个函数你必须给我实现,因为我要使用。 所以native关键字的函数都是操作系统实现的, java只能调用。

NO.5

java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了

try…catch(多个异常) 多个异常采取同样的解决措施

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Demo {
    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            int a = 1 / 0;// 除以0
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
            // 多个异常见用 | 隔开
            // 多个异常必须是平级关系
            System.out.println("发生了ArithmeticException 或者 ArrayIndexOutOfBoundsException 异常");
        }

        try {
            int[] a = {1, 2};
            System.out.println(a[3]); // 越界
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
            // 出现多个异常,采取同样的处理措施
            // 多个异常见用 | 隔开
            // 多个异常必须是平级关系
            System.out.println("发生了ArithmeticException 或者 ArrayIndexOutOfBoundsException 异常");
        }
    }
}

result

1
2
发生了ArithmeticException 或者 ArrayIndexOutOfBoundsException 异常
发生了ArithmeticException 或者 ArrayIndexOutOfBoundsException 异常

java-jar 带环境变量(参数)启动

需求

java工程我们可以编译成jar也可以翻译成war,一般地,war包我会丢到tomcat容器里,启动tomcat来访问服务,端口、SSL证书、日志等等,都托给tomcat。

如果打的是jar包,我通常会用nohup启动,比如生产环境的一些db、redis、第三方secret等不会配置到项目里,今天要记录的便是用nohup java -jar 启动jar包时如何加载环境变量配置的问题。

java语言开发的jar包启动时可以按照如下方式加启动参数。

方式一:-DpropName=propValue

这种方式应该很快都能找到

1
-DpropName=propValue

比如:

1
java -jar -DdatabaseUrl="mysql://localhost:3306/pdb?user=root&password=root"  -Dapp.key="123" -Dapp.secret="xxx"  demo.jar

多个参数也可以。

方式二:参数直接跟在命令后面,多个参数之间用空格隔开

1
java -jar demo.jar JOURNAL_TREENODE_DATA-20190404174502.txt processType=1

这种方式参数就是jar包里主启动类中main方法的args参数,按顺序来

方式三:使用springboot的方式,–propName=propValue方式

1
java -jar demo.jar  --spring.profiles.active=dev  --server.port=8181

注意: 运行jar包时指定端口:java -jar xxx.jar –server.port=8088 若命令行传入的server.port没有作用,服务仍然使用8081端口启动,原因是spring-cloud-config会覆盖命令行传入的参数,这是有意为之,

办法是在web-prod.yml中做点小改动,让“配置”变得“可配置”:加一对花括符

1
server.port={port:8081}

用clojure开发的jar里有惊喜

在clojure上面的配置就变得诡异了,猜猜下面的配置能不能生效呢?

1
java -jar -Ddatabase-url="mysql://localhost:3306/pdb?user=root&password=root"  -Dapp.key="123" -Dapp.secret="xxx"  demo.jar

如果你用cider-conect通过nrepl的端口连接上你的服务,你会发现,这个配置导致database-url的值确实已经改了,但是后面两个没有。

究其原因,我们java从classpath里获取参数使用的是properties形式的,也就是json的格式。这不难理解,spring有它的办法,clojure当然也有自己的方式。

1
2
3
4
"app": {
    "key": "123",
    "secret":"xxx"
}

虽然json和我们的edn里map是很像的,但是毕竟是不同,于是要分析下现在的edn里的配置信息他是怎么读取的呢?

代码里的env

config这个namespace里找到了env

1
2
3
4
5
6
7
(defstate env
  :start
  (load-config
   :merge
   [(args)
    (source/from-system-props)
    (source/from-env)]))

cprop加载配置

cprop.source这个文件就是用来加载edn文件的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(defn- env->path [k]
  (k->path k "_" #"__"))

(defn read-system-env
  ([]
   (read-system-env {}))
  ([opts]
   (->> (System/getenv)
        (map (fn [[k v]] [(env->path k)
                          (str->value v opts)]))
        (into {}))))

从获取的过程看,应该是会把_做为单元节点断开,因此需要改成这样

1
java -jar -Ddatabase-url="mysql://localhost:3306/pdb?user=root&password=root"  -Dapp_key="123" -Dapp_secret="xxx"  demo.jar

对应到edn里应该是

1
2
3
{:app
    {:key "123"
     :secret "xxx"}}

如果仔细看看cprop这个库就不难理解了。 cprop加载配置文件的顺序是 :

By default cprop will merge all configurations it can find in the following order: classpath resource config file on a file system (pointed by a conf system property or by (load-config :file )) custom configurations, maps from various sources, etc. System properties ENV variables

对于ENV的加载也有明确说明

ENV variables lack structure. The only way to mimic the structure is via use of an underscore character. The _ is converted to - by cprop, so instead, to identify nesting, two underscores can be used.

了解更多详情,请阅读cprop介绍

Java -Djava.ext.dirs启动的坑

java以jar包形式启动启动

-Djava.ext.dirs=lib的作用

以bat形式启动项目,linux环境需要改变语法


1
java -Djava.ext.dirs=d:\libs -classpath classes com.xxxclass

或者 设置环境变量

1
2
set lib = d:\libs
java -Djava.ext.dirs=%lib% -classpath classes com.xxxclass

java命令引入jar时可以-cp参数,但-cp只能指定一个固定jar包,不能用通配符(多个jar时要一个个写,不能*.jar),通常情况jar都在同一目录,且多于1个。可以使用-Djava.ext.dirs。


下面来说说-Djava.ext.dirs的坑

-Djava.ext.dirs是通过设置系统属性的方式也加载jar包的,这个级别就有点高,和-classpath的区别在于-Djava.ext.dirs会覆盖Java本身的ext设置,java.ext.dirs指定的目录由ExtClassLoader加载器加载,如果您的程序没有指定该系统属性(-Djava.ext.dirs=d:\libs)那么该加载器默认加载$JAVA_HOME/lib/ext目录下的所有jar文件。但如果你手动指定系统属性且忘了把$JAVA_HOME/lib/ext路径给加上,那么ExtClassLoader不会去加载$JAVA_HOME/lib/ext下面的jar文件,这意味着你将失去一些功能,例如java自带的加解密算法实现。

具体说是jre的ext目录:C:\Program Files\Java\jdk1.8.0_45。一般情况下普通程序运行并无差异,因为可以看到ext目录下也就几个jar,但是如果使用java.security相关类时,也就是使用加密解决相关的算法,就会发现-Djava.ext.dirs会导致library找不到相关类而报错。如报错:

NoSuchAlgorithmException: Cannot find any provider supporting RSA

这个错误当然也有可能是security\java.security文件中配置错误引起 。

当然也有解决方法:

1、将ext下相关jar包复制到新的ext director。

2、在-D.java.ext.dirs中配置多个目录。可以使用冒号分隔(windows下使用分号)。

比如:-Djava.ext.dirs=-Djava.ext.dirs=“libs;%JAVA_HOME%\jre\lib\ext”。

Java8函数作为参数

创建Function有多种方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FunctionParams {
    public static void main(String[] args) {
        Function<String,String> f01=(password)-> Base64.getEncoder().encodeToString(password.getBytes());
// 输出加密后的字符串
        System.out.println(f01.apply("123456"));
//--------------------------------------------------------
        System.out.println(addApi(1,FunctionParams::add1));
        System.out.println(addApi(1,FunctionParams::add2));
    }

    private static String add1(int n) {
        return String.valueOf(n + 1);
    }

    private static String add2(int n) {
        return String.valueOf(n + 2);
    }

    public static String addApi(int n, Function<Integer, String> add) {
        return add.apply(n);
    }
}

@FunctionalInterface的使用

一.使用规则

1.必须注解在接口上

2.被注解的接口有且只有一个抽象方法

3.被注解的接口可以有默认方法/静态方法,或者重写Object的方法

二.使用方法

除了可以和普通接口一样写Impl之外,还可通过Lambda表达式进行构造,而不用写Impl class。

./15.jpg

./16.jpg

输出结果:

./17.jpg

三.使用场景

用于某一特定的场景,但是有不同的处理逻辑的时候,比如导入数据,导入是共性,但是处理逻辑都不同。然鹅感觉用处不是很大。

Java中反斜杠转义问题

1.符串中反斜杠

反斜杠(\)在JAVA中有特别的意义;它和紧跟着它的那个字符构成转义字符,如"\n"(表示换行)" \’ “(表示字符[ ’ ])等,所以在字符串中要表示反斜杠 \ 要用” \ “来表示。例如这样定义一个字符串String s = “name\sex"是错误的,要这样定义String s = “name\sex”

如下定义一个非法的字符串,会报错

1
2
String regx = "women\halou";
System.out.print(regx);

输出:

1
Error:(17, 30) java: 非法转义符

如下定义一个正确的字符串

1
2
String regx = "women\\halou";
System.out.print(regx);

输出:

1
2
women\halou
Process finished with exit code 0

2. 正则表达式中反斜杠

在正则表达式中的“\”和后面紧跟着的那个字符构成一个转义字符,代表着特殊的意义;所以如果你要在正则表达式中表示一个反斜杠\,应当写成“\\”。

下面匹配A\

1
2
3
4
5
6
String regx = "\u0041\\\\";
Pattern p = Pattern.compile(regx);
Matcher m = p.matcher("A\\\\b");
while(m.find()){
 	System.out.println(m.group());
        }

输出:

1
2
3
D:\ProgramFiles\eclipse\eclipse\JDK\bin\java.exe...
A\
Process finished with exit code 0

下面匹配电话号码

1
2
3
4
5
6
String regx = "\\d\\d\\d\\d-\\d\\d\\d\\d\\d\\d\\d";
Pattern p = Pattern.compile(regx);
Matcher m = p.matcher("小明的电话号码为:0563-4667722,小方的电话为:0555-7777777");
  while(m.find()){
    System.out.println(m.group());
        }

输出:

1
2
3
0563-4667722
0555-7777777
Process finished with exit code 0

时间格式统一

需求

最近接了一个需求,解析别人从Kafka获取的信息,其中的时间格式乱七八糟,无论怎么改后台都各种报错

他的格式中部分格式使用T作为日期和时间的分隔符,有的用空格;日期部分有的用斜杠作为分隔符,有的用横杠作为分隔符,这其实两句replace就解决了,最致命的是,他的日期时间一位数,没有补0,比如2021年1月1日1点1分1秒为2021-1-1T1:1:1,这就会导致格式化日期信息报错。于是乎,就有了下面的解决办法。

知识介绍

我使用的是org.apache.commons.lang3StringUtils

方法如下:

1
2
3
public static String leftPadTime(Integer time){
    return StringUtils.leftPad(String.valueOf(time), 2, "0");
}

左侧填充: leftPad()

1
2
StringUtils.leftPad(String str,int size); 
StringUtils.leftPad(String str,int size,String padStr);

右侧填充: rightPad()

1
2
StringUtils.rightPad(String str,int size); 
StringUtils.rightPad(String str,int size,String padStr);

中间填充: center()

1
2
StringUtils.center(String str, int size); 
StringUtils.center(String str, int size, String padString);

例:

1
StringUtils.center("abcdef", 20) 

返回结果(左右各7个空格):abcdef

1
StringUtils.center("abcdef", 20,"*_"); 

返回结果:*_*_*_abcdef*_*_*_*_

1
StringUtils.leftPad("abc", 10, "*"); 

返回结果:*******abc

我的工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    /**
     * 给带T的日期补0,如2021-1-2T1:1:2->2021-01-02T01:01:02
     * @param timeS
     * @return
     */
    public static String dataStrAdd0(String timeS){
        StringBuffer res = new StringBuffer();
        String[] ts = timeS.split("T");
        //日期
        List<String> data = Arrays.asList(ts[0].split("-"));
        //时间
        List<String> time = Arrays.asList(ts[1].split(":"));
        data.forEach(t->{
            res.append(StringUtils.leftPad(t,2,"0"));
            res.append("-");
        });
        res.deleteCharAt(res.length()-1);
        res.append("T");
        time.forEach(t->{
            res.append(StringUtils.leftPad(t,2,"0"));
            res.append(":");
        });
        res.deleteCharAt(res.length()-1);
        return res.toString();
    }

Windows 10 配置Java 环境变量

下载 JDK

下载地址

点击下载按钮:

./1.png

./2.jpg

开始安装JDK:

./3.jpg

可以设置为你想安装的路径。


环境变量配置

1. 打开 环境变量窗口

右键 This PC(此电脑) -> Properties(属性) -> Advanced system settings(高级系统设置) -> Environment Variables(环境变量)…

./4.png

./5.png

./6.png

./7.png

2. 新建JAVA_HOME 变量 点击 New(新建)… 按钮

./8.png

输入:

1
2
变量名JAVA_HOME
变量值电脑上JDK安装的绝对路径

输入完毕后点击 OK。

./9.png

JDK 路径下必须能够看到如下的文件。

./10.png

3. 新建/修改 CLASSPATH 变量

如果存在 CLASSPATH 变量,选中点击 Edit(编辑)

如果没有,点击 New(新建)… 新建。

输入/在已有的变量值后面添加:

1
2
变量名:CLASSPATH
变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

点击 OK 保存。

./11.png

4. 修改Path 变量

由于 win10 的不同,当选中 Path 变量的时候,系统会很方便的把所有不同路径都分开了,不会像 win7 或者 win8 那样连在一起。

./12.png

新建两条路径:

1
2
%JAVA_HOME%\bin
%JAVA_HOME%\jre\bin

./13.png

4. 检查 打开 cmd,输入 java,出现一连串的指令提示,说明配置成功了:

./14.png

Scanner扫描器的 next* 方法行为

有的时候会发现 Scanner 的 next* 方法有 “bug”:还没等用户输入,系统就给了一个空白字符的默认值。

next* 指的是 next()、nextInt()、nextDouble() 这些。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    System.out.print("输入1:");
    String next = input.next();
    System.out.println("next():" + next);
 
    System.out.print("输入2:");
    String nextLine = input.nextLine();
    System.out.println("nextLine():" + nextLine);
 
    input.close();
}

例如上面的代码,会发现 nextLine 方法自动获取到了回车Enter

1
2
3
4
5
输入1hello
next()hello
输入2nextLine()

进程已结束,退出代码0

这是因为 nextLine() 方法和其他 next* 方法获取的内容不一样。

Scanner 阻塞程序的运行,等待并把用户的输入写入缓冲区,Scanner 从缓冲区读取内容,next* 方法会以空白字符作为结束标识,截取并返回结束标识前的内容,但会把结束标识留在缓冲区中

空白字符有:空格、tab键、回车键等

nextLine() 方法顾名思义,用来获取一行内容,理所当然以 Enter 作为结束标识。跟其他 next* 方法不一样,它会把结束标识前的内容联通结束标识一同截取,但返回的时候不带结束标识,相当于丢弃了。

上面的例子就可以理解了:

  • 输入字符串:hello,回车后 next() 方法以回车符作为结束标识,并把回车留在了缓冲区
  • 第二次 nextLine() 方法来获取,发现缓冲区有 next() 方法 “残留” 的回车符,获取并丢弃回车符,第二次默认就是空了

这是个不小心会容易踩坑的点。

如何解决?

也容易。next* 方法读取后残留的空白符,它自己读取没有问题,因为空白符在开头会被忽略(所以 next* 不适合读取空字符串);nextLine() 这个老实人就惨了,它会把空白符老老实实读给自己。但我们又可以利用它读取空白符并丢弃的特点,额外使用一个 nextLine() 方法把空白符过滤掉。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("输入1:");
        String next = input.next();
        System.out.println("next():" + next);
 
        input.nextLine();  // 额外代码用来过滤Enter
        System.out.print("输入2:");
        String nextLine = input.nextLine();
        System.out.println("nextLine():" + nextLine);
 
        input.close();
}

现在可以正常输入

1
2
3
4
5
6
输入1:hello
next():hello
输入2:world
nextLine():world

进程已结束,退出代码0

另外一个我个人认为容易歧义的点就是,next* 方法是获取结束标识前的内容,但并不是说结束标识后的内容就丢了。上面的例子就说明了这点,否则也不会出现第二次 “默认输入空白” 的情况。

遍历取所有值的 hasNext() 方法就更能说明问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("输入:");
        String s1 = input.next();
        System.out.println(s1);
 
        System.out.println("==================");
 
        while (!input.hasNext("#")) {
            String s2 = input.next();
            System.out.println(s2);
        }
 
        input.close();
}

输出结果:

1
2
3
4
5
6
7
8
输入:hello world good morning place holding
hello
==================
world
good
morning
place
holding

很明显的感觉到,第一次 next() 取走了 “hello”,后面留在缓冲区,第二次 next() 读取就不需要用户的输入,而是把剩下的内容依次读取出来。

End