JAVA学习

Java小知识之代码优化:

代码优化的目标是

  • 减小代码的体积
  • 提高代码运行的效率

1、尽量重用对象:

​ 1、特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBu ffer代替。由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。

2、循环内不要不断创建对象引用
1
for (int i = 1; i <= count; i++) {Object obj = new Object(); }

​ 这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:

1
Object obj = null;for (int i = 0; i <= count; i++) { obj = new Object(); }

​ 内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了

​ 3、 尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用 同步机制而导致了性能开销

​ 4、实现RandomAccess接口的集合比如ArrayList,应当使用最普通的for循环而不是foreach循环来遍历

这是JDK推荐给用户的。JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现RandomAccess接口的类实例,假如是随机访问的,使用普通for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高。可以使用类似如下的代码作判断:``

1
2
3
4
5
if (list instanceof RandomAccess)
{ for (int i = 0; i < list.size(); i++){}
}else{
Iterator<?> iterator = list.iterable(); while (iterator.hasNext()){iterator.next()}
}

foreach循环的底层实现原理就是迭代器Iterator,

参见Java语法糖1:可变长度参数以及foreach循环原理。所以后半句反过来,如果是顺序访问的,则使用Iterator会效率更高”的意思就是顺序访问的那些类实例,使用foreach循环去遍历。


Java判断字符串是否为空

一.空字符串与null区别

1、类型

null表示的是一个对象的值,而并不是一个字符串。例如声明一个对象的引用,String a = null ;
“”表示的是一个空字符串,也就是说它的长度为0。例如声明一个字符串String str = “” ;

2、内存分配

String str = null ; 表示声明一个字符串对象的引用,但指向为null,也就是说还没有指向任何的内存空间;
String str = “”; 表示声明一个字符串类型的引用,其值为””空字符串,这个str引用指向的是空字符串的内存空间;
在java中变量和引用变量是存在栈中(stack),而对象(new产生的)都是存放在堆中(heap):

就如下:

String str = new String(“abc”) ;

ps:=左边的是存放在栈中(stack),=右边是存放在堆中(heap)。

3、示例程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class String_Demo01 {
public static void main(String[] args) {
String str1 = new String() ;
String str2 = null ;
String str3 = "" ;
System.out.println(str1==str2); //内存地址的比较,返回false
System.out.println(str1.equals(str2)); //值的比较,返回false
System.out.println(str2==str3); //内存地址的比较,返回false
System.out.println(str3.equals(str2)); //值的比较,返回false
System.out.println(str1==str3); //内存地址的比较,返回false
System.out.println(str1.equals(str3)); //值的比较,返回true
}

}

通过如上的程序可以得出如下结论:

字符串对象与null的值不相等,且内存地址也不相等;
空字符串对象与null的值不相等,且内存地址也不相等;
new String()创建一个字符串对象的默认值为”” (String类型成员变量的初始值为null)

4.Java判断字符串常方法

方法一: 最多人使用的一个方法, 直观, 方便, 但效率很低:

                 if(s == null || s.equals(""));

方法二: 比较字符串长度, 效率高, 是我知道的最好一个方法:

                 if(s == null || s.length() == 0);

方法三: Java SE 6.0 才开始提供的方法, 效率和方法二几乎相等, 但出于兼容性考虑, 推荐使用方法二.

                 if(s == null || s.isEmpty());

方法四: 这是一种比较直观,简便的方法,而且效率也非常的高,与方法二、三的效率差不多:

                 if (s == null || s == "");

注意:s == null 是有必要存在的.

  如果 String 类型为null, 而去进行 equals(String) 或 length() 等操作会抛出java.lang.NullPointerException.

  并且s==null 的顺序必须出现在前面,不然同样会抛出java.lang.NullPointerException.

  如下Java代码:

1
2
3
4
String str = null;
  if(str.equals("") || str == null){//会抛出异常
   System.out.println("success");
  }
五、StringUtils的isBlank与isEmply
1.public static boolean isEmpty(String str)

  判断某字符串是否为空,为空的标准是 str==null 或 str.length()==0

  下面是 StringUtils 判断是否为空的示例:

1
2
3
4
5
6
7
8
9
10
11
  StringUtils.isEmpty(null) = true

  StringUtils.isEmpty("") = true

  StringUtils.isEmpty(" ") = false //注意在 StringUtils 中空格作非空处理

  StringUtils.isEmpty(" ") = false

  StringUtils.isEmpty("bob") = false

  StringUtils.isEmpty(" bob ") = false
2.public static boolean isNotEmpty(String str)

  判断某字符串是否非空,等于 !isEmpty(String str)

  下面是示例:

1
2
3
4
5
6
7
8
9
10
11
  StringUtils.isNotEmpty(null) = false

  StringUtils.isNotEmpty("") = false

  StringUtils.isNotEmpty(" ") = true

  StringUtils.isNotEmpty(" ") = true

  StringUtils.isNotEmpty("bob") = true

  StringUtils.isNotEmpty(" bob ") = true
3.pblic static boolean isBlank(String str)

  判断某字符串是否为空或长度为0或由空白符(whitespace) 构成

  下面是示例:

1
  StringUtils.isBlank(null) = true  StringUtils.isBlank("") = true  StringUtils.isBlank(" ") = true  StringUtils.isBlank("        ") = true  StringUtils.isBlank("\t \n \f \r") = true   //对于制表符、换行符、换页符和回车符  StringUtils.isBlank()  //均识为空白符  StringUtils.isBlank("\b") = false   //"\b"为单词边界符  StringUtils.isBlank("bob") = false  StringUtils.isBlank(" bob ") = false
4.public static boolean isNotBlank(String str)

  判断某字符串是否不为空且长度不为0且不由空白符(whitespace) 构成,等于 !isBlank(String str)

  下面是示例:

1
  StringUtils.isNotBlank(null) = false  StringUtils.isNotBlank("") = false  StringUtils.isNotBlank(" ") = false  StringUtils.isNotBlank("         ") = false  StringUtils.isNotBlank("\t \n \f \r") = false  StringUtils.isNotBlank("\b") = true  StringUtils.isNotBlank("bob") = true  StringUtils.isNotBlank(" bob ") = true

java list如何进行判断空

1、常用表达方式

1
if(null == list || list.size() ==0 ){  //为空的情况}else{  //不为空的情况}
1
if(list!=null && !list.isEmpty()){   //不为空的情况}else{   //为空的情况}

2、遇到问题

1、 list.isEmpty() 和 list.size()==0

  答案:没有区别 。isEmpty()判断有没有元素,而size()返回有几个元素, 如果判断一个集合有无元素 建议用isEmpty()方法.比较符合逻辑用法。

2、 list!=null 跟 ! list.isEmpty()

  这就相当与,你要要到商店买东西

    list!=null 首先判断是否有商店

    !list.isEmpty() 没有判断商店是否存在,而是判断商店是否有东西

Java局部变量、成员变量、类变量

1、实例变量

也叫对象变量、类成员变量:从属于类,并由类生生成对象是,才分配存储空间, 能通过对象访问实例变量

1
private String nameString;	public int age;	protected int priority;	//实例方法	public String getNameString(){		return this.nameString;	}

类变量:

也叫静态变量,是一种比较特殊的实例变量,用static关键字修饰;一个类的静态变量,所有由这类生成的对象都共用这个类变量,类装载时就分配存储空间。一个对象修改了变量,则所以对象中这个变量的值都会发生改变。

1
//类变量(静态变量)	public static int a = 0;	//实例变量   private String nameString;

局部变量:

方法中或者某局部中声明定义的变量方法或方法的参数 ,他们只存在创建他们的block里无法再block外面进行任何操作,在Java的多线程中,每个线程都会复制一份局部的变量,可以防止某些同步问题的发生

1
public static int a = 0;	//实例变量	private String nameString;		public void test(){		//局部变量		int temp = 1;

image-20210723003109260

四、他们的区别

成员变量和局部变量的区别
成员变量:

①成员变量定义在类中,在整个类中都可以被访问。

②成员变量随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的堆内存中。

③成员变量有默认初始化值。

局部变量:

①局部变量只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。

②局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放。

③局部变量没有默认初始化值

在使用变量时需要遵循的原则为:就近原则

首先在局部范围找,有就使用;接着在成员位置找

b 成员变量和静态变量的区别
1、两个变量的生命周期不同

​ 成员变量随着对象的创建而存在,随着对象被回收而释放。

​ 静态变量随着类的加载而存在,随着类的消失而消失。****

2、调用方式不同

​ 成员变量只能被对象调用。

​ 静态变量可以被对象调用,还可以被类名调用。

3、别名不同

​ 成员变量也称为实例变量。

​ 静态变量也称为类变量。

4、数据存储位置不同

​ 成员变量存储在堆内存的对象中,所以也叫对象的特有数据。

静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。

[java 静态变量生命周期(类生命周期) - HF_Cherish - 博客园 (cnblogs.com)](https://www.cnblogs.com/hf-cherish/p/4970267.html#:~:text=因为静态变量生命周期虽然长(就是类的生命周期),但是当程序执行完,也就是该类的所有对象都已经被回收,或者加载类的ClassLoader已经被回收,那么该类就会从jvm的方法区卸载,即生命期终止。 更进一步来说,static变量终究是存在jvm的内存中的,jvm下次重新运行时,肯定会清空里边上次运行的内容,包括方法区、常量区的内容。,要实现某些变量在程序多次运行时都可以读取,那么必须要将变量存下来,即存到本地文件中。 常用的数据存取格式:XML、JSON、Propertities类(类似map的键值对)等)


java序列化:

一、序列化的含义、意义及使用场景:

  • 序列化:将对象写入到IO流中

  • 反序列化:从IO流中恢复对象

序列化目的:

方便数据的传递以及存储到磁盘上(把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要将对象转换成字节流才能进行网络传输。对于这种通用的操作,就出现了序列化来统一这些格式)

​ 简单来说序列化就是一种用来处理对象流的机制。将对象转化成字节序列后可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在

就是说你想要你要传输对象,那么就必须转换字节码传输,传输之后想要还原到原来的样子,就必须是要进行

使用场景:

  • 所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错
  • 所有需要保存到磁盘的java对象都必须是可序列化的, Redis 将对象当做字符串存储的时候,如果对象实现了序列化,则只需要将对象直接存储即可(java会自动将对象转换成序列化后的字节流)

二、为什么要序列化和反序列化

我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。

  • 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里)
  • 利用序列化实现远程通信,即在网络上传送对象的字节序列。

三、涉及到的javaAPI

​ java.io.ObjectOutputStream表示对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

​ java.io.ObjectInputStream表示对象输入流,它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。

​ 只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。

转载:

java序列化,看这篇就够了 - 9龙 - 博客园 (cnblogs.com)

(66条消息) java序列化和反序列化以及序列化ID的作用分析_Java仗剑走天涯的博客-CSDN博客_序列化id

通常建议:程序创建的每个JavaBean类都实现Serializeable接口

String类型

关于String 不可以变:

什么是不可变?

​ 说String不可以变,那么到底是什么不可以变呢? 我们可以这样认为,当创建一个对象,不改变他的状态,就是说不能改变他的成员变量,包括基本类型的值不能变,引用类型的不能指向别的对象,它的状态也不能够被改变

例如一下状态:

1
2
3
4
String s = "ABCabc";
System.out.println("s = " + s);//ABCabc
s = "123456";
System.out.println("s = " + s);//123456

不是说不可变了吗,为什么还是变了?

这个就要注意一下, s只是String对象的一个引用,并不是指对象本身,。对象在内存中是一个内存区,成员变量越多,内存就越大。引用只是一个四字节的地址,通过该地址可以访问到对象。所以说 s只是一个引用,它指向了对象所处的地址。代码中,s是同一引用,但是,值在不同的地址中,开始时,引用s 是“ABC”,调用同一个引用,又将s指向的地址改变成了“123456”的值

image-20210819220635970


在Java中,Java并不能直接操控对象本身,所有的对象都是一个应用的指向,必须通过引用才能访问对象的本身,包括成员变量等,改变对象的成员变量,调用指针的方法等,引用和指针类似,都是存放的对象在内存中的地址,只是不能够进行指针运算

image-20210819225848264

所以说,一旦创建就不能够修改,而是重新创建一个地址,然后让该引用指向该地址的值,原来的数据并没有变化。而是重新创建了一个新的地址,改变了引用的指向

image-20210820080154918

上面的结果是true,因为string在创建对象是,在常量池里边,创建一个对象,会自动到常量池里面去找,如果有改变该引用的指向,否则,重新创建一个对象

==比较的是值是不是相同,这个值分为地址值和数据的值,但它是基本数据类型时(int等 但是并没有对他进行实例化),因为那么就是比较的值,如果两个引用变量,指向同一个地址,采用== 就是ture,

equals 没有重写之前,就是和==一样

打个方,拿string做个比较就是,它重写了equals方法,两个string引用变量去比较,首先比较采用== ,比较地址值,你的地址值指向一样,那值肯定就是相等的

如果在不同的地址,那么引用地址的引用类型就需要重写了,equals来比较是否连个对象中的值相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public boolean equals(Object anObject) {
//两个引用指向同一个地址,那么他们的值一定相等
if (this == anObject) {
return true;
}
//如果在不同的地址之下,就要比较hash是否相同,如果hash值相同,就是说值相等,
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
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
41
42
43
44
45
    @org.junit.Test
public void IntTest(){
String integer =new String("7") ;
change(integer);
User user = new User("jin");
user.setUserId(288);
String kk = user.getUserName();
// 只要是new的对象就在方法区,就不会去常量池中寻找
String name3 =new String("jin");
String name= "jin";
// 引用地址值不一样,所指向的值一样,但指向不同的方法区
System.out.println("name3+name ="+(name3==name));//false
// == 进行的是值比较,在同一个常量池中,指向的地址一样
System.out.println("kk==name ="+(kk==name));//true
// 同上,set方法中对象在常量池中
System.out.println("name==user.getUserName()="+(name==user.getUserName()));//true
User user1 = new User();
// user方法中初始话使用了new String
// System.out.println("user1.getName1()==name="+(user1.getName1()==name));//false
// user1.setName1("jin");
//// 再次用set方法赋值时,进入常量池中
// System.out.println("user1.getName1()==name="+(user1.getName1()==name));//ture

// 基本数据类型会进行值比较
// 基本数据的引用类型 Integer 超过了127,就会自动装箱,创建内存地址,此时就是地址不同了
// 值比较
int id=user.getUserId();
// 大于127进入堆中,地址值不一样
Integer integer1 =288;
Integer integer2 = user.getUserId();
System.out.println("integer1==integer2 ="+(integer1==integer2));//false
System.out.println(integer1 == user.getUserId());
// 超过范围自动装箱变引用,比较的就是一个地址值
System.out.println(id==user.getUserId());
System.out.println( user.getUserName() == name);
if (user.getUserName().equals(name)){
System.out.println("ooooooo");
}
System.out.println(integer);

System.out.println("--------------未有重写---------------------");
System.out.println("1"+new User().hashCode());
System.out.println("2"+new User().hashCode());
System.out.println("3"+new User().hashCode());
System.out.println("4"+new User().hashCode());

如何理解 String 类型值的不可变? - 知乎 (zhihu.com)


关于hashcode ():

什么是hash :

image-20210820123558804

  • 不同关键字经过散列算法变换后可能得到同一个散列地址,这种现象称为碰撞
  • 如果两个Hash值不同(前提是同一Hash算法),那么这两个Hash值对应的原始输入必定不同
HashCode:

1、HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的

2、如果两个对象equals相等,那么这两个对象的HashCode一定也相同

3、如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写

4、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

HashCode有什么用

回到最关键的问题,HashCode有什么用?不妨举个例子:

1、假设内存中有0 1 2 3 4 5 6 7 8这9个位置,如果我有个字段叫做ID,那么我要把这个字段存放在以上9个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到8个位置中去挨个查找

2、使用HashCode则效率会快很多,把ID的HashCode % 9,然后把ID存放在取得余数的那个位置,然后每次查找该类的时候都可以通过ID的HashCode % 9求余数直接找到存放的位置了

3、如果ID的HashCode % 9算出来的位置上本身已经有数据了怎么办?这就取决于算法的实现了,比如ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据;HashMap的做法就是通过链式结构连起来。反正,只要保证放的时候和取的时候的算法一致就行了。

4、如果ID的HashCode % 9相等怎么办(这种对应的是第三点说的链式结构的场景)?

这时候就需要定义equals了。先通过HashCode%9来判断类在哪一个位置,再通过equals来在这个位置上寻找需要的类。对比两个类的时候也差不多,先通过HashCode比较,假如HashCode相等再判断equals。如果两个类的HashCode都不相同,那么这两个类必定是不同的

举个实际的例子Set:

我们知道Set里面的元素是不可以重复的,那么如何做到?

Set是根据equals()方法来判断两个元素是否相等的。比方说Set里面已经有1000个元素了,那么第1001个元素进来的时候,最多可能调用1000次equals方法,如果equals方法写得复杂,对比的东西特别多,那么效率会大大降低。使用HashCode就不一样了,比方说HashSet,底层是基于HashMap实现的,先通过HashCode取一个模,这样一下子就固定到某个位置了,如果这个位置上没有元素,那么就可以肯定HashSet中必定没有和新添加的元素equals的元素,就可以直接存放了,都不需要比较;如果这个位置上有元素了,逐一比较,比较的时候先比较HashCode,HashCode都不同接下去都不用比了,肯定不一样,HashCode相等,再equals比较,没有相同的元素就存,有相同的元素就不存。如果原来的Set里面有相同的元素,只要HashCode的生成方式定义得好(不重复),不管Set里面原来有多少元素,只需要执行一次的equals就可以了。这样一来,实际调用equals方法的次数大大降低,提高了效率。

为什么要重写hashcode

[讲讲HashCode的作用 - 五月的仓颉 - 博客园 (cnblogs.com)](https://www.cnblogs.com/xrq730/p/4842028.html#:~:text=HashCode. 然后讲下什么是HashCode,总结几个关键点:.,1、HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的. 2、如果两个对象equals相等,那么这两个对象的HashCode一定也相同. 3、如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写.)

(6条消息) 为什么重写equals()方法时,必须要求重写hashCode()方法?_IT无知君的博客-CSDN博客

保证成员变量都相等的情况下,他们的hashcode都相等的,重写equals方法必须尽量要重写hashCode方法的原因

1.equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。

2.hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。

所有对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equals()去再对比了),如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!

1
2
3
4
5
6
7
8
9
10
11
public int hashCode() {    
int h = hash;//默认是0
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
} return h;
}

java关键字

1、instanceof

1、instanceof用法详解:

instanceof严格上来说是一个双目运算符,用来测试一个类是否是一个类的实例

1
boolean result = obj instanceof Class
  • 类的实例包含本身的实例,以及所有直接或间接子类的实例
  • instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于同一个继承树,否则会编译错误

2、instanceof用法:

  • 左边的实例对象不能为基本数据类型

image-20210811170323344

  • 左边的实例与右边的类不在同一个继承树上

image-20210811170424784

  • obj 为 null ,跟任何类型比较时都是false

image-20210811171114568

image-20210811171124133

  • obj 为 class 接口的实现类

    1
    public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

    instanceof 运算符判断 某个对象是否是 List 接口的实现类,如果是返回 true,否则返回 false

    image-20210811171507254

    obj 为 class 类的直接或间接子类

image-20210811171217018

3、原理使用伪代码表示为:

image-20210811172228543

表达式必须是null或者是引用类型,否则会返回false

如果 obj 不为 null 并且 (T) obj 不抛 ClassCastException 异常则该表达式值为 true ,否则值为 false 。

4、应用场景:

​ 对象类型强制转换:

image-20210811172552838

image-20210811172603353

final关键字:

[浅析Java中的final关键字 - Matrix海子 - 博客园 (cnblogs.com)](https://www.cnblogs.com/dolphin0520/p/3736238.html#:~:text=一.final关键字的基本用法. 在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。. 下面就从这三个方面来了解一下final关键字的基本用法。. 当用final修饰一个类时,表明这个类不能被继承。.,也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。. final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。. 在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。. 下面这段话摘自《Java编程思想》第四版第143页:.)

static 关键字:

目的:

  • ​ 当只想用一个内存区去保存一个特定的数据(无论创建多少个对象,甚至根本不用去创建对象)

  • ​ 与该类没有任何的对象的关联,即使没有创建对象,也能够调用方法

特点:

  • static修饰的代码块、变量、方法内部类在类加载的期间就已经完成了初始化,存储在堆中的静态存储区,static优于对象而存在

  • static修饰的(变量和成员方法)被所有的对象所共享,也叫静态变量或静态方法

使用场景:

static 是一个修饰符,常常用来修饰变量和方法,如在开发过程中常常static 来进行修饰,如 工具类的方法,如DateUtils,StringUtils这类的工具方法会用到static来进行修饰

static 变量

static 方法

static 代码块

static 内部类

static 包内导入

static变量:

随着类的加载而存在,随着类的消失而消失,当类被加载的时候,就会在Java heap中分配内存空间,静态变量被所有的 对象 进行共享,一个值改动,就会改变另一个对象的取值。

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
public class Person {
private String name;

private static int eyeNum;

public static int legNum = 2;

public String getName() {
return name;
}

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

public static int getEyeNum() {
return eyeNum;
}

public static void setEyeNum(int eyeNum) {
Person.eyeNum = eyeNum;
}
}
public static void main(String[] args) {
Person person = new Person();
person.setEyeNum(25);

Person person1 = new Person();
person1.setEyeNum(28);
System.out.println(person.getEyeNum());//28
System.out.println(person1.getEyeNum());//28

int legNum = person.legNum;
System.out.println(legNum);//2
}
什么时候使用static变量

作为共享变量去使用,通过常和final关键字使用:

1
private static final String GENERAL_MAN = "man";

减少对象的创建,比如在类开头的部分,定义Logger方法,用于异常日志采集:

1
private static Logger LOGGER = LogFactory.getLoggger(MyClass.class);
static方法:
  • 静态方法只能访问静态成员或静态方法

  • 非静态方法既可以访问静态方法也可以访问非静态方

什么时候使用static方法:

static方法一般用于与当前对象无法的工厂方法、工具方法。如Math.sqrt(),Arrays.sort(),StringUtils.isEmpty()等。

static 代码块:

static代码块在加载一个类的时候最先执行,且只执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
public static Map<String, String> timeTypes;
static {
timeTypes = new HashMap<>();
timeTypes.put("year", "年");
timeTypes.put("quarter", "季");
timeTypes.put("month", "月");
timeTypes.put("day", "日");
System.out.println("初始化1");
}
public static void main(String[] args) {
System.out.println("初始化2");
}

JAVA集合:

image-20220424085316058

image-20210817083243280

image-20210818210531421

集合框架底层数据结构总结

List

Arraylist : Object[] 数组

Vector : Object[] 数组

LinkedList : 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

Set

HashSet (⽆序,唯⼀): 基于 HashMap 实现的,底层采⽤ HashMap 来保存元素

LinkedHashSet : LinkedHashSet 是 HashSet 的⼦类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现⼀样,不过还是有⼀点点区别的

TreeSet (有序,唯⼀): 红⿊树(⾃平衡的排序⼆叉树) 再来看看 Map 接⼝下⾯的集合。

Map

HashMap : JDK1.7 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以 减少搜索时间

LinkedHashMap : LinkedHashMap 继承⾃ HashMap ,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红⿊树组成。另外, LinkedHashMap 在上⾯结构的基础上,增加了 ⼀条双向链表,使得上⾯的结构可以保持键值对的插⼊顺序。同时通过对链表进⾏相应的操作, 实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》

Hashtable : 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突 ⽽存在的

TreeMap : 红⿊树(⾃平衡的排序⼆叉树)

如何选⽤集合?

主要根据集合的特点来选⽤

根据键值获取到元素值时就选⽤ Map 接⼝下的集合

  • 需要排序时选择 TreeMap
  • 不需要排序时就选择 HashMap
  • 需要保证线程安全就选⽤ ConcurrentHashMap 。

只需要存放元素值: 选择实现 Collection 接⼝的集合

保证元素唯⼀: Set 接⼝的集合⽐如 TreeSet 或 HashSet ,不需要就选择实现 List 接⼝的⽐如 ArrayList 或 LinkedList

有哪些集合是线程不安全的?怎么解决呢?

我们常⽤的 Arraylist , LinkedList , Hashmap , HashSet , TreeSet , TreeMap , PriorityQueue 都不是线程安全的。

解决办法很简单,可以使⽤线程安全的集合来代替。 如果你要使⽤线程安全的集合的话,

java.util.concurrent 包中提供了很多并发容器供你使⽤:

  1. ConcurrentHashMap : 可以看作是线程安全的
  2. CopyOnWriteArrayList :可以看作是线程安全的 ArrayList ,在读多写少的场合性能⾮常好,远远好于 Vector
  3. ConcurrentLinkedQueue :⾼效的并发队列,使⽤链表实现。可以看做⼀个线程安全的 LinkedList ,这是⼀个⾮阻塞队列。
  4. BlockingQueue : 这是⼀个接⼝,JDK 内部通过链表、数组等⽅式实现了这个接⼝。表示阻塞队列,⾮常适合⽤于作为数据共享的通道。
  5. ConcurrentSkipListMap :跳表的实现。这是⼀个 Map ,使⽤跳表的数据结构进⾏快速查找。

Collection 接口

  • Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法 既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
  • JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List) 实现。
  • 在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都 当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。

Collection 接口方法

1、添加

  • add(Object obj)
  • addAll(Collection coll)

2、获取有效元素的个数

  • int size()

3、清空集合

  • void clear()

4、是否是空集合

  • boolean isEmpty()

5、是否包含某个元素

  • boolean contains(Object obj):是通过元素的equals方法来判断是否 是同一个对象
  • boolean containsAll(Collection c):也是调用元素的equals方法来比 较的。拿两个集合的元素挨个比较。

6、删除

  • boolean remove(Object obj) :通过元素的equals方法判断是否是 要删除的那个元素。只会删除找到的第一个元
  • 素 boolean removeAll(Collection coll):取当前集合的差集

7、取两个集合的交集

  • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c

8、集合是否相等

  • boolean equals(Object obj)

9、转成对象数组

  • Object[] toArray()

10、获取集合对象的哈希值 hashCode()

11、遍历

iterator():返回迭代器对象,用于集合遍历

Iterator迭代器接口

使用 Iterator 接口遍历集合元素

image-20220423081923313

Iterator接口的方法

image-20220423081954296

image-20220423082054071

在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且 下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。

Iterator接口remove()方法

1
2
3
4
5
6
7
Iterator iter = coll.iterator();//回到起点
while(iter.hasNext()){
Object obj = iter.next();
if(obj.equals("Tom")){
iter.remove();
}
}
1
2
3
4
5
6
7
8
9
10
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "C++");
map.put(3, "PHP");
Iterator<Map.Entry<Integer, Stringef iterator =
map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey() + entry.getValue());

  • Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。

  • 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用remove都会报IllegalStateException。

foreach 循环遍历集合元素:

image-20220423082356837

1.2. Collection 子接口

List接口:

1.2.1: 什么是List:

List: 元素是有序的(元素存取是有序)、可重复. 有序的 collection,可以对列表中每个元素的插入位置进行精确地控制。可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。可存放重复元素,元素存取是有序的。

常用实现类:

ps**:dancer: ArrayList、LinkedList和Vector。**

  • ArrayList

线程不安全的,效率高;底层使用Object[]

  • LinkedList

对于频繁的插入、删除操作,使用此类效率比ArrayList高;双链表

  • Vector

作为List接口的古老实现类;线程安全的,效率低;底层使用object[]

1.2.2: List接口中常用类:

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来 操作集合元素的方法。 

  • void add(int index, Object ele):在index位置插入ele元素
  • boolean addAll(int index, Collection eles):从index位置开始将eles中 的所有元素添加进来
  • Object get(int index):获取指定index位置的元素
  • int indexOf(Object obj):返回obj在集合中首次出现的位置
  • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
  • Object remove(int index):移除指定index位置的元素,并返回此元素
  • Object set(int index, Object ele):设置指定index位置的元素为ele
  • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合

1.2.3:ArrayList

简介:

底层数据: 底层是数组。与数组相比较,容量能够动态增长。在添加大量数组前,ensureCapacity操作数组的容量。可以减少递增式的分类

插入和删除:线性表顺序存储,插入元素的时间复杂度为O(n),移动到指定元素再插入,取第i元素时间复杂度为O(1)

是否支持快速随机访问:实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接 口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。

ArayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。 ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。

线程安全:操作不是线程安全的

建议在单线程中才使用 ArrayList, 而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

为什么扩容:

ArrayList的扩容机制提高了性能,如果每次只扩充一个, 那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。

image-20210818082036011

  • jdk7中的ArrayList的对象创建的类似于单例的饿汉模式,开始创建的时候就分配内存地址
  • jdk8中的ArrayList的对象中的类似于单例模式的懒汉式,延迟了数组的创建,需要的时候在进行创建
源码分析:
JDK1.7:

ArrayList 提供了三种方式的构造器,可以构造一个默认初始容量为 10 的空列表、构造 一个指定初始容量的空列表以及构造一个包含指定 collection 的元素的列表,这些元素按照 该 collection 的迭代器返回它们的顺序排列的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public ArrayList() {
2. this(10);
3. }
4.
5. public ArrayList(int initialCapacity) {
6. super();
7. if (initialCapacity < 0)
8. throw new IllegalArgumentException("Illegal Capacity: "+ initialCapaci
ty);
9. this.elementData = new Object[initialCapacity];
10. }
11.
12. public ArrayList(Collection<? extends E> c) {
13. elementData = c.toArray();
14. size = elementData.length;
15. // c.toArray might (incorrectly) not return Object[] (see 6260652)
16. if (elementData.getClass() != Object[].class)
17. elementData = Arrays.copyOf(elementData, size, Object[].class);
18. }

数组扩容:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void ensureCapacity(int minCapacity) {
2. modCount++;
3. int oldCapacity = elementData.length;
4. if (minCapacity > oldCapacity) {
5. Object oldData[] = elementData;
//数据进行扩容
6. int newCapacity = (oldCapacity * 3)/2 + 1;
7. if (newCapacity < minCapacity)
newCapacity = minCapacity;
9. // minCapacity is usually close to size, so this is a win:
10. elementData = Arrays.copyOf(elementData, newCapacity);
11. }
12. }

数组进行扩容时,会将老数组中的元素重新拷贝一份到新的 数组中,每次数组容量的增长大约是其原容量的 1.5 倍。这种操作的代价是很高的,因此在 实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时, 要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求, 通过调用 ensureCapacity 方法来手动增加 ArrayList 实例的容量。

JDK 1.8

思路:

JDK 1.8扩容的机制为,ArrayList在初始化时没有进行初始化默认赋值,而是赋予空的数组,无参构造时使用的空数组,有参创建一个该范围的数组,在添加函数中,如果开始时,小于10.初始化为10,但最小的minCapacity 大于了 数组长度,就需要进行扩容,扩容机制为原来的1.5 倍,如果扩容之后还是比原来的数据小,那就是原来的数据 ,超过则指定为Integer的最大值,否则指定为限制容量大小,之后复制元素

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
    /**
* Default initial capacity.
*/
//在添加过程个中,如果是空参构造默认赋值
private static final int DEFAULT_CAPACITY = 10
/**
* Shared empty array instance used for empty instances.
*/
//空实例时的共享数据默认初始
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
//用于区分实例化时默认指定值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
//存储数据的列表
transient Object[] elementData; // non-private to simplify nested class access

/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
1
2
3
4
5
6
7
8
9
    
//将元素添加至数组末尾
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// 给数组赋值
elementData[size++] = e;
return true;
}

得到最小的扩容量

1
2
3
4
5
6
7
8
9
10

private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取默认的容量和传入参数的较大值,初始化值为10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

判断是否需要进行扩容(如果初始值为10,那么直到添加到11 个元素才需要进行扩容):

1
2
3
4
5
6
7
8
9
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;

//如果添加元素之后,比较元素大小,判断是否需要进行扩容
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容,调用此方法代表已经开始扩容了
grow(minCapacity);
}

扩容函数grow():

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
/**
* ArrayList扩容的核心方法。
*/
private void grow(int minCapacity) {
//elementData为保存ArrayList数据的数组
///elementData.length求数组长度elementData.size是求数组中的元素个数
// oldCapacity为旧容量,newCapacity为新容量
int oldCapacity = elementData.length;
//将oldCapacity 右移一位,其效果相当于oldCapacity /2,
//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5
倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作
数组的新容量,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//再检查新容量是否超出了ArrayList所定义的最大容量,
//若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新
容量大小则为 MAX_ARRAY_SIZE。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
移位运算符

简介:

移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种: <<(左移)、>>(带符号右移)和>>>(无符号右移)。

作用:

对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而 已,不去计算,这样提高了效率,节省了资源

比如这里:int newCapacity = oldCapacity + (oldCapacity >> 1);

右移一位相当于除2,右移n位相当于除以 2 的 n 次方。

这里 oldCapacity 明显右移了1位所以相当于 oldCapacity /2。

ps:jack_o_lantern:另外需要注意的是:

java 中的length 属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.

java 中的length()方法是针对字 符串String说的,如果想看这个字符串的长度则用到 length()这个 方法. java 中的size()方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看

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
package list;
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListDemo {
public static void main(String[] srgs){
ArrayList<Integer> arrayList = new ArrayList<Integer>();
System.out.printf("Before add:arrayList.size() =
%d\n",arrayList.size());
arrayList.add(1);
arrayList.add(3);
arrayList.add(5);
arrayList.add(7);
arrayList.add(9);
System.out.printf("After add:arrayList.size() =
%d\n",arrayList.size());
System.out.println("Printing elements of arrayList");
// 三种遍历方式打印元素
// 第一种:通过迭代器遍历
System.out.print("通过迭代器遍历:");
Iterator<Integer> it = arrayList.iterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}
System.out.println();
// 第二种:通过索引值遍历
System.out.print("通过索引值遍历:");
for(int i = 0; i < arrayList.size(); i++){
System.out.print(arrayList.get(i) + " ");
}
System.out.println();
// 第三种:for循环遍历
System.out.print("for循环遍历:");
for(Integer number : arrayList){
System.out.print(number + " ");
}
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
// toArray用法
// 第一种方式(最常用)
Integer[] integer = arrayList.toArray(new Integer[0]);
// 第二种方式(容易理解)
Integer[] integer1 = new Integer[arrayList.size()];
arrayList.toArray(integer1);
// 抛出异常,java不支持向下转型
//Integer[] integer2 = new Integer[arrayList.size()];
//integer2 = arrayList.toArray();
System.out.println();
// 在指定位置添加元素
arrayList.add(2,2);
// 删除指定位置上的元素
arrayList.remove(2);
// 删除指定元素
arrayList.remove((Object)3);
// 判断arrayList是否包含5
System.out.println("ArrayList contains 5 is: " +
arrayList.contains(5));
// 清空ArrayList
arrayList.clear();
// 判断ArrayList是否为空
System.out.println("ArrayList is empty: " + arrayList.isEmpty());
}
}

LinkedList(双向链表):

简介:

底层双向链表:****实现了List接口和Deque接口的双端链表

高效删除插入:LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得 LinkedList类也具有队列的特性;

LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的 synchronizedList方法:

1
List list=Collections.synchronizedList(new LinkedList())

内部结构:

LinkedList类中的一个内部私有类Node

1
2
3
4
5
6
7
8
9
10
private static class Node<E> {
E item;//节点值
Node<E> next;//后继节点
Node<E> prev;//前驱节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

Arraylist 与 LinkedList 区别?

是否保证线程安全

ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

底层数据结构:

  • Arraylist 底层使⽤的是 Object 数组
  • LinkedList 底层使⽤的是双向链表数据结构
  • (JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下⾯有介绍到!)

插⼊和删除是否受元素位置的影响:

① ArrayList 采⽤数组存储,所以插⼊和删除元素的 时间复杂度受元素位置的影响。 ⽐如:执⾏ add(E e) ⽅法的时候, ArrayList 会默认在 将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。

但是如果要在指定位置 i 插⼊和删除元素的话( add(int index, E element) )

**时间复杂度就为 O(n-i)**。

因为在进⾏上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执⾏向后位/向前移⼀位的操作。

② LinkedList 采⽤链表存储,所以对于 add(E e) ⽅法的插⼊,删除元素时间复杂度不受元素位置的影响,近似 O(1),

如果是要在指定位置 i 插⼊和删除元素的话( (add(int index, E element) ) 时间复杂度近似为 o(n)) 因为需要先移动到指定位置再插⼊。

是否⽀持快速随机访问:

LinkedList 不⽀持⾼效的随机元素访问,⽽ ArrayList ⽀持。 快速随机访问就是通过元素的序号快速获取元素对象(对应于 get(int index) ⽅法)。

内存空间占⽤:

ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空间,⽽ LinkedList 的空间花费则体现在它的每⼀个元素都需要消耗⽐ ArrayList 更多的空间 (因为要存放直接后继和直接前驱以及数据)。

双向链表:

image-20220424084724419

双向循环链表: 最后⼀个节点的 next 指向 head,⽽ head 的 prev 指向最后⼀个节点,构成⼀个 环。

image-20220424084744519

vector:

ArrayList和Vector的区别

Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢

Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

Set接口:

什么是set

存储无序的、不可重复的对象:

  • HashSet 是set接口的典型的实例,大多数使用Set集合都是
  • 按hash算法来存储集合中的元素,具有很好的存储、删除、查找、删除的性能
⽆序性和不可重复性的含义是什么
  • 1、什么是⽆序性?

⽆序性不等于随机性 ,⽆序性是指存储的数据在底层数组中并⾮按照数组索引的顺序添加 ,⽽是根据数据的哈希值决定的。

  • 2、什么是不可重复性?

不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()⽅法和 HashCode()⽅法。

特点

  • 不能够保证元素的排列的顺序
  • HashSet不是线程安全的
  • 集合的元素可以为空值

判断两个元素相等的标准,通过hashcode()方法比较,并且两个对象的quals()方法也相同

对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

⽐较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

  • HashSet 是 Set 接⼝的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值;
  • LinkedHashSet 是 HashSet 的⼦类,能够按照添加的顺序遍历;
  • TreeSet 底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排序

HashSet:

线程不安全,内部是基于散列函数实现,存取速度快。它是如何保证元素唯一性的呢?依赖的是元素的hashCode方法和euqals方法。

底层是HashMap

image-20210818215627841

先进行hash值比较,在进行equals比较

image-20210818215733459

HashCode:

image-20210818221115967

image-20210818221238854

1
2
3
4
5
6
7
8
9
10
11
12
  
public static int hashCode(Object a[]) {
if (a == null)
return 0;

int result = 1;

for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());

return result;
}

TreeSet:

基本简介

​ 按照对象的不同属性进行排序

​ 两种排序:自然排序,定制排序

重写排序之后,如果你直接添加元素,会进行自动排序,下面是二级排序,如果有一个属性相同,就不会插入,因为是一个树形结构

image-20210818221954348

image-20210818222237587

comparable 和 Comparator 的区别
  • comparable 接⼝实际上是出⾃ java.lang 包 它有⼀个 compareTo(Object obj) ⽅法⽤来排序()
  • comparator 接⼝实际上是出⾃ java.util 包它有⼀个 compare(Object obj1, Object obj2) ⽅法⽤来排序

当我们需要对元素进行排序的时候,我们 使用 comparator 并重写compare()方法,实现定制,Collections.sort()

当对对象时:⽐如⼀个 song 对象中的歌名和歌⼿名分别采⽤⼀种排序 ⽅法的话,我们可以重写 compareTo() ⽅法和使⽤⾃制的 Comparator ⽅法或者以两个 Comparator 来实现歌名排序和歌星名排序,第⼆种代表我们只能使⽤两个参数版的 Collections.sort()

Comparator 定制排序
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
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
Output:
重写 compareTo ⽅法实现按年龄来排序
System.out.println("原始数组:");
System.out.println(arrayList);
// void reverse(List list):反转
Collections.reverse(arrayList);
System.out.println("Collections.reverse(arrayList):");
System.out.println(arrayList);
// void sort(List list),按⾃然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
System.out.println(arrayList);
// 定制排序的⽤法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序后:");
System.out.println(arrayList);
1
2
3
4
5
6
7
8
原始数组:
[-1, 3, 3, -5, 7, 4, -9, -7]
Collections.reverse(arrayList):
[-7, -9, 4, 7, -5, 3, 3, -1]
Collections.sort(arrayList):
[-9, -7, -5, -1, 3, 3, 4, 7]
定制排序后:
[7, 4, 3, 3, -1, -5, -7, -9

重写 compareTo ⽅法实现按年龄来排序

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
// person对象没有实现Comparable接⼝,所以必须实现,这样才不会出错,才可
以使treemap中的数据按顺序排列
// 前⾯⼀个例⼦的String类已经默认实现了Comparable接⼝,详细可以查看
String类的API⽂档,另外其他
// 像Integer类等都已经实现了Comparable接⼝,所以不需要另外实现了
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* T重写compareTo⽅法实现按年龄来排序
*/
@Override
public int compareTo(Person o) {
if (this.age > o.getAge()) {
return 1;
}
if (this.age < o.getAge()) {
return -1;
}
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
TreeMap<Person, String> pdata = new TreeMap<Person, String>();
pdata.put(new Person("张三", 30), "zhangsan");
pdata.put(new Person("李四", 20), "lisi");
pdata.put(new Person("王五", 10), "wangwu");
pdata.put(new Person("⼩红", 5), "xiaohong");
// 得到key的值的同时得到key所对应的值
Set<Person> keys = pdata.keySet();
for (Person key : keys) {
System.out.println(key.getAge() + "-" + key.getName());
}
}

5-⼩红

10-王五

20-李四

30-张

Map接口:

Map接口继承树

image-20220425065845510

|———HashMap:

Map的主要实现类,线程不安全,效率高

​ |—————LinkedHashMap :保证遍历Map元素时,可以按照添加的顺序实现遍历

​ 在原有的HashMap底层数据结构上,添加一对指针,指向前一个和后一个,对于频繁的遍历操作,执行效率高

|————TreeMap: 保证添加的Key-value 进行排序(按key进行排序),实现排序遍历,考虑Key的自然排序和定制排序,底层使用红黑树

|————HashTable: 线程安全,效率底;不能够存储null·的key和value

HashMap:

JDK1.8之前:

HashMap的底层是数组加链表,结合就是链表散列。HashMap通过key的hashCode经过扰动函数处理后得到hash值,通过(n-1)&hash 判断当前元素存放的位置(这里的n指的是数组的长度),如果数组

image-20220506204318278

要保证低位是1,

工作原理

jdk7

调用hashcode()计算key1的hash 值,此哈希值相同经过算法计算以后,

HashMap 在map。entry静态内部类实现中存储key-value,时,HashMap使用哈希算法,当我们使用key时候,通过调用hashcode()和hash算法,先找出索引,如果不一样,添加成功,否者,找到同一个桶之后,使用quals检验值是否存在,如果存在,会覆盖value,如果不存在,会创建一个新的entry(包括属性有;key ,value,next) 属性。默认扩容为原来的两倍.

Jdk8相较于Jdk7在底层实现的方面的不同:

1、new HashMap():底层没有创建一个长度为16 的数组

2、jdk 8 底层是 Node[] 不是entry

3、首次调用put 方法时,底层创建长度为16的数组

4、jdk7底层结构数组链表,jdk8 中的底层:数组+链表+红黑树

为了避免链表过长,查询速度变为了线性, 当数组上的某一索引元素以链表的形式存在的数据个数大于>8 ,且当前的长度>64 此时索引的位置上的说有数据改为红黑树

image-20210819090458603

对HashMap中put/get方法的认识?

HashMap

image-20210820200517223

jdk8:

底层存储数据使用的是node:

1
//    static class Node<K,V> implements Map.Entry<K,V> {        final int hash;        final K key;        V value;        Node<K,V> next;        Node(int hash, K key, V value, Node<K,V> next) {            this.hash = hash;            this.key = key;            this.value = value;            this.next = next;        }

put方法:

1
public V put(K key, V value) {        return putVal(hash(key), key, value, false, true);    }
1
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                   boolean evict) {        Node<K,V>[] tab; Node<K,V> p; int n, i;        if ((tab = table) == null || (n = tab.length) == 0)            //首次初始化数组            n = (tab = resize()).length;             //找到当前数组的元素值        if ((p = tab[i = (n - 1) & hash]) == null)            tab[i] = newNode(hash, key, value, null);        else {            Node<K,V> e; K k;            if (p.hash == hash &&                ((k = p.key) == key || (key != null && key.equals(k))))                e = p;            else if (p instanceof TreeNode)                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);            else {                for (int binCount = 0; ; ++binCount) {                    if ((e = p.next) == null) {                        p.next = newNode(hash, key, value, null);                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                            treeifyBin(tab, hash);                        break;                    }                    if (e.hash == hash &&                        ((k = e.key) == key || (key != null && key.equals(k))))                        break;                    p = e;                }            }            if (e != null) { // existing mapping for key                V oldValue = e.value;                if (!onlyIfAbsent || oldValue == null)                    e.value = value;                afterNodeAccess(e);                return oldValue;            }        }        ++modCount;        if (++size > threshold)            resize();        afterNodeInsertion(evict);        return null;    }

resize:

1
final Node<K,V>[] resize() {        Node<K,V>[] oldTab = table;        int oldCap = (oldTab == null) ? 0 : oldTab.length;        int oldThr = threshold;        int newCap, newThr = 0;        if (oldCap > 0) {            if (oldCap >= MAXIMUM_CAPACITY) {                threshold = Integer.MAX_VALUE;                return oldTab;            }            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                     oldCap >= DEFAULT_INITIAL_CAPACITY)                newThr = oldThr << 1; // double threshold        }        else if (oldThr > 0) // initial capacity was placed in threshold            newCap = oldThr;        else {               // zero initial threshold signifies using defaults            newCap = DEFAULT_INITIAL_CAPACITY;            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);        }        if (newThr == 0) {            float ft = (float)newCap * loadFactor;            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?                      (int)ft : Integer.MAX_VALUE);        }        threshold = newThr;        @SuppressWarnings({"rawtypes","unchecked"})        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];        table = newTab;        if (oldTab != null) {            for (int j = 0; j < oldCap; ++j) {                Node<K,V> e;                if ((e = oldTab[j]) != null) {                    oldTab[j] = null;                    if (e.next == null)                        newTab[e.hash & (newCap - 1)] = e;                    else if (e instanceof TreeNode)                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);                    else { // preserve order                        Node<K,V> loHead = null, loTail = null;                        Node<K,V> hiHead = null, hiTail = null;                        Node<K,V> next;                        do {                            next = e.next;                            if ((e.hash & oldCap) == 0) {                                if (loTail == null)                                    loHead = e;                                else                                    loTail.next = e;                                loTail = e;                            }                            else {                                if (hiTail == null)                                    hiHead = e;                                else                                    hiTail.next = e;                                hiTail = e;                            }                        } while ((e = next) != null);                        if (loTail != null) {                            loTail.next = null;                            newTab[j] = loHead;                        }                        if (hiTail != null) {                            hiTail.next = null;                            newTab[j + oldCap] = hiHead;                        }                    }                }            }        }        return newTab;    }

LinkedHashMap的底层原理(待完成):

map常用方法:

image-20210820203510349

1.1.3. 集合框架底层数据结构总结

1.1.3.1. List

  • ArraylistObject[] 数组 线程不安全,查询速度快。底层数据结构是数组结构 底层使用Object[ ] 存储
  • VectorObject[] 数组 线程安全,但速度慢,已被ArrayList替代
  • LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环,线程不安全。增删速度快。底层数据结构是链表结构

1.1.3.2. Set

  • HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashSet: LinkedHashSetHashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

1.1.3.3 Queue

  • PriorityQueue: Object[] 数组来实现二叉堆
  • ArrayQueue: Object[] 数组 + 双指针

再来看看 Map 接口下面的集合。

1.1.3.4. Map

  • HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间

  • LinkedHashMapLinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》

  • Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的

  • TreeMap: 红黑树(自平衡的排序二叉树)

1.1.2. 说说 List, Set, Queue, Map 四者的区别?

  • List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
  • Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。
  • Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
  • Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),”x” 代表 key,”y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。

1.2.1. Arraylist 和 Vector 的区别?

  • ArrayListList 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,效率高 线程不安全 ,;
  • VectorList 的古老实现类,底层使用Object[ ] 存储,使用synchronized效率低线程安全的。

1.2.2. Arraylist 与 LinkedList 区别?

  1. 是否保证线程安全: ArrayListLinkedList 都是不同步的,也就是不保证线程安全;
  2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
  3. 插入和删除是否受元素位置的影响:
    • ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
    • LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响(add(E e)addFirst(E e)addLast(E e)removeFirst()removeLast()),近似 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element)remove(Object o)) 时间复杂度近似为 O(n) ,因为需要先移动到指定位置再插入。
  4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  5. 内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。

1.2.2. 负载因子值的大小,对HashMap有什么影响?

​ 负载因子的大小决定了HashMap的数据密度

​ 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数也越小,性能会更高,但是会浪费一定的内容空间。而且经常扩容会影响性能建议初始化预设大一点的空间

​ 按照其他语言的参考及研究,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数

1.2.3⽐较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

  • HashSet 是 Set 接⼝的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值;
  • LinkedHashSet 是 HashSet 的⼦类,能够按照添加的顺序遍历;
  • TreeSet 底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排序

Collections常用方法:

Collections 工具类常用方法:

  1. 排序
  2. 查找,替换操作
  3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)

查找,替换操作

void reverse(List):

反转List中元素的顺序

void shuffle(List):

对List集合元素进行随机排序

void sort(List):

根据元素的自然顺序对指定List集合元素按升序排序

void sort(List,Comparator):

根据指定的Comparator产生的顺序对List集合元素进行排序

void swap(List,int,int):

将指定list集合中的i处元素和j处元素进行交换

查找替换操作:

1
2
3
4
5
6
7
8
9
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比
int min(Collection coll)int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比
int min(Collection coll, Comparator c)void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比
int lastIndexOfSubList(List source, list target)
boolean replaceAll(List list, Object oldVal, Object newVal)//用新元素替换旧元素
void copy(List dest,Lsit src):将src的内容复制到dest中

同步控制:

Collections 提供了多个synchronizedXxx()方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections 提供了多个静态方法可以把他们包装成线程同步的集合。

  • synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)
  • collection.synchronizedList(List list)//返回指定列表支持的同步(线程安全的)
  • List.synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步(线程安全的)
  • Map。synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。

枚举类和注解:

枚举

  • 类的对象只有有限个,确定的

  • 当需要进行定义一组常量时,强烈建议使用枚举类:

如何使用关键字enum定义枚举类;

使用关键字:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//类的对象只有有限个,确定的
//当需要进行定义一组常量时,强烈建议使用枚举类//如果枚举类中只有一个对象,可以作为单例模式的实现方式
enum TestByEnum {
SYS_CONFIG("系统配置","sys_config"),
ADMIN_LOGIN("后台登录","admin_login"),
ADMIN_UI("后台样式","admin_ui"),
WECHAT_DEV("微信开发","wechat_dev");
private String text; private String value;// 定义你的常量的类型
private TestByEnum(String text,String value) {
this.text=text; this.value=value; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
@Override public String toString() {
return "TestByEnum{" + "text='" + text + '\'' + ", value='" + value + '\'' + '}'; }
test:
1
2
3
4
5
@Test    public void  TestEnum(){        
System.out.println(Enum.Spring.toString());
TestByEnum[] values = TestByEnum.values();
for (int i = 0; i < values.length; i++) {
System.out.println(values[i].getValue()); } }// 使用关键字来进行定义
使用自定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package wz.TestEnum;
public class Enum {
// 方式一: jdk5.0 自定义枚举类
// 方式二,可以使用enum关键字定义枚举类
// 自定义枚举类
private final String seasonName;
private final String seasonDesc;
public Enum(String seasonName,String seasonDesc) {
this.seasonName = seasonName; this.seasonDesc = seasonDesc; }
// 提供当前枚举类的多个对象
public static final Enum Spring = new Enum("春天","sss");
public String getSeasonName() { return seasonName; }
public String getSeasonDesc() { return seasonDesc; }
@Override public String toString() {
return "Enum{" + "seasonName='" + seasonName + '\'' + ", seasonDesc='" + seasonDesc + '\'' + '}'; }}

注解:

●注解(Annotation)概述·

​ jdk5.0 开始

Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

Annotation可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的“name=value”对中。

框架 =注解+ 反射+设计模式

●自定义AnnotationJDK中的元注解

  • 自定义注解通常都会指明两个元注解:retention、target
  • 利用反射获取注解信息(在反射部分涉及)

●JDK 8中注解的新特性

元数据

@retention:

指定所修饰的annotation的生命周期:source\class,只有声明为runtime 生命周期的注解,才能通过反射

@target:

用于指定被修饰的Annotation能用于修饰哪些程序元素

image-20210821200035996

1
public enum ElementType {    /** Class, interface (including annotation type), or enum declaration */    TYPE,    /** Field declaration (includes enum constants) */   //属性    FIELD,    /** Method declaration */   //方法    METHOD,    /** Formal parameter declaration */    //私有的    PARAMETER,    /** Constructor declaration */    //构造函数    CONSTRUCTOR,    /** Local variable declaration */    //本地变量    LOCAL_VARIABLE,    /** Annotation type declaration */    //注释类型    ANNOTATION_TYPE,    /** Package declaration */    //包注解    PACKAGE,    /**     * Type parameter declaration     *     * @since 1.8     */    //类型注解,表示该注解能够写在类型变量的声明语句中(如:泛型的声明    TYPE_PARAMETER,    /**     * Use of a type     *     * @since 1.8     */    //该注解能够写在使用类型的任何语句之中    TYPE_USE}

image-20210822094532788

@Documented:

用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。>定义为Documented的注解必须设置Retention值为RUNTIME。

@inherited

修饰的Annotation将具有继承性,如果某个类使用了被该注解修饰的Annotion,那么其子类将自动具有该注解

比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解

利用反射获取信息:

当一个Annotation类型被定义为运行时annotation后,该注解才是运行时可见,当class文件被载入时保存在class 文件中Annotation 才会被虚拟机读取

程序可以调用annotateElement对象来访问Annotation的信息

jdk8中的新注解:

可重复注解:

1、在MyAnnotation上面声明@Repeatable,与成员值进行绑定 MyAnnotation.class

类型注解:

image-20210822093246408

注解样例:

1
2
3
4
5
6
7
8
9
package com.jfinal.plugin.ehcache;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * CacheName can configure CacheInterceptor's cache name or EvictInterceptor's evict cache name. * <p> * The order of CacheInterceptor searching for CacheName annotation:<br> * 1: Action method of current invocation<br> * 2: Controller of the current invocation<br> * CacheInterceptor will use the actionKey as the cache name If the CacheName annotation not found. * CacheName可以配置CacheInterceptor的缓存名或victinterceptor的evict缓存名。* < p >* CacheInterceptor搜索CacheName注释的顺序:<br>* 1:当前调用的动作方法<br>2:当前调用的控制器<br>*如果没有找到CacheName注释,CacheInterceptor会使用actionKey作为缓存名。 */@Inherited //被修饰的注解将具备继承性@Documented //表示修饰的注解将被Javadoc解析时保留下来@Retention(RetentionPolicy.RUNTIME)//指定修饰的生命周期@Target({ElementType.TYPE, ElementType.METHOD})//指定修饰的类型,适合哪个
public @interface CacheName { String value();}

泛型:

1.1为什么要有泛型(Generic):

​ 在jdk1.5之前只能把元素类型设计为Object,把元素的类型设计成一个参数,从而达到能够对不同的对象进行重用

所谓泛型:就是允许在定义类中、接口时通过一个标识符表示某个属性的类型或者某个方法的返回值以及参数类型 。这个类型将在使用时确定(例如: 继承或者实习这个接口用这个声明变量、创建对象)确定(传入实际的参数,也称为类型的参数)

image-20210822113013534

image-20210822113221451

如果没有使用泛型在取出时就会有问题

image-20210822113354241

image-20210822113645205

使用的时候只需要指定你要的什么类型,然后在进行使用

image-20210822171725315

泛型嵌套:

image-20210823090328643

通配符的使用:

1、使用类型通配符:类型变量的限定 ,能够对指定的进行限制

List<? extends Object> 其父类必须是Object

对于List<?>不能够向其内部添加数据

//获取(读取):允许读取数据,读取的数据为Object 类型


image-20210823081616708

通配符指定上限:

上限extends:

使用时指定的类型必须继承某个类,或者实现某个接口

通配符指定下限:

下限super:s

使用时指定的类型不能小于操作的类

(无穷小,Number) ​ 只允许泛型为Number及Number子类的引用 ​ 只允许泛型为Number及Number父类的引用 ​ 只允许泛型为实现Comparaable接口的实现类的引用 ​ 便于提取出 泛型的数据: ​ 首先人的信息进行封装 ,能够给指定的人显示所需要显示的信息 ![image-20210823090544107](http://qiniu.littlekim.top/image/202201222323881.png) ![image-20210823090552698](http://qiniu.littlekim.top/image/202201222323338.png) ![1573457377345671.png](http://qiniu.littlekim.top/image/202201222323921.png)
1
2
3
4
5
6
7
   /**     * 这个只能传递T类型的数据     * 返回值 就是Demo<T> 实例化传递的对象类型     * @param data     * @return     */    private T getListFisrt(List<T> data) {       
if (data == null || data.size() == 0) { return null; }
return data.get(0); }} /** * 这个<T> T 可以传入任何类型的List * 参数T * 第一个 表示是泛型 * 第二个 表示返回的是T类型的数据 * 第三个 限制参数类型为T * @param data * @return */
private <T> T getListFisrt(List<T> data) {
if (data == null || data.size() == 0) {
return null; }
return data.get(0); }}
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//    将对象转换为字符串  Object-->String<T> 声明泛型 返回值为string 传入值为 T型      
public static <T> String obj2String(T src){
if (src == null){
return null;
}
try {// 判断src是否为String,如果是,强转为string 如果不是调用jackson方法转换
return src instanceof String ? (String) src:
objectMapper.writeValueAsString(src);
}catch (Exception e){
return null;
}
}// String --> Object
public static <T> T string2Obj(String src , TypeReference<T> typeReference){// 将string转换为为我们所需要的对象
if (src == null|| typeReference == null){
return null;
}
try {
return (T)(typeReference.getType().equals(String.class)? src:objectMapper.readValue(src,typeReference)); }catch (Exception exception){
exception.printStackTrace();
return null;
} }

# 设计模式: - 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式 - 结构型模式: 适配器模式、装饰器模式、代理模式、外观模式、组合模式、享乐 - 行为模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式状态模式、访问者模式、中介者模式、解释器模式 #### 单例模式: > 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。 > 让天不漫奇心学的技太 ##### 懒汉式和饿汉式 懒汉式: - 延迟去创建对象 - 线程不安全 饿汉式: - 坏处:对象的加载时间过长,一开始就加载 - 好处:线程是安全的 应用: - 网站的计数器; - 应用程序的日志文件
1
2
3
4
5
6
7
8
9
10
11
		Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();		
slf4jLogFilter.setConnectionLogEnabled(false);
slf4jLogFilter.setResultSetLogEnabled(false);
slf4jLogFilter.setStatementParameterSetLogEnabled(false);
slf4jLogFilter.setConnectionLogEnabled(false);
slf4jLogFilter.setResultSetCloseAfterLogEnabled(false); slf4jLogFilter.setConnectionCloseAfterLogEnabled(false); slf4jLogFilter.setStatementParameterClearLogEnable(false); slf4jLogFilter.setStatementPrepareAfterLogEnabled(false); slf4jLogFilter.setStatementPrepareCallAfterLogEnabled(false); slf4jLogFilter.setStatementCreateAfterLogEnabled(false); slf4jLogFilter.setStatementCloseAfterLogEnabled(false); //设置输出执行后的日志 带执行耗时等信息
slf4jLogFilter.setStatementExecuteAfterLogEnabled(false); //设置批量操作执行后的日志 带执行耗时等信息
slf4jLogFilter.setStatementExecuteBatchAfterLogEnabled(false); //设置查询操作执行后的日志 带执行耗时等信息
slf4jLogFilter.setStatementExecuteQueryAfterLogEnabled(false); //设置更新 插入 删除 操作执行后的日志 带执行耗时等信息
slf4jLogFilter.setStatementExecuteUpdateAfterLogEnabled(false); //输出完整的SQL 将值替换掉问号,这个仅在开发模式下有效
slf4jLogFilter.setStatementExecutableSqlLogEnable(DEV_MODE);
- 数据库连接池(典型使用)
1
2
3
4
5
6
7
8
9
10
11
12
    static DruidPlugin druidplugin;    
static{
druidplugin= MainConfig.createSQLserverDruidPlugin();
druidplugin.start(); }
public static Connection getConnection() {
try {
Connection connection =druidplugin.getDataSource().getConnection();
return connection;
} catch (SQLException e) {
e.printStackTrace();
return null;
} }
# 反射机制: ### 反射概念: > reflection (反射)是被视为动态语言的关键,反射机制允许程序在执行期,借助于refelction 取得任何类的内部信息,并能够直接操作任意对象的内部属性及方法 > 加载完类之后,在堆内存的方法区就产生了一个class类型的对象(一个类中只有一个class的对象),这个对象就包含了完整的类的结构。 ![image-20210823100910743](http://qiniu.littlekim.top/image/202201222323327.png) ### Java反射机制提供的功能 - 在运行时判断任意一个对象所属的类 - 在运行时构造任意一个类的对象 - 在运行时判断任意一个类所具有的成员变量和方法 - 在运行时获取泛型信息 - 在运行时判断任意一个对象所具有的成员变量和方法 - 在运行是处理注解 - 生成动态代理 ### 反射主要的API: java.lang.Class:代表一个类 java.lang.reflect.Method:代表类的方法 java.lang.reflect.Field:代表类的成员变量 java.lang.reflect.Constructor:代表类的构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test    public void test2() {        Class cla = User.class;        try {//              通过反射获取对象的构造方法            
Constructor constructor = cla.getConstructor(String.class);
User user = (User) constructor.newInstance("jjjjj");
System.out.println("user = " + user);
// 2、通过反射,调用对象指定的属性
Field userName = cla.getDeclaredField("userName");
userName.set(user, "22222");
System.out.println(user.toString());// 调用指定的方法
Method show = cla.getDeclaredMethod("show");
show.invoke(user);// 通过反射,可以调用Person类的私有的结构,比如私有的构造器、方法、属性
Constructor declaredConstructor = cla.getDeclaredConstructor(String.class,String.class); declaredConstructor.setAccessible(true);
User user1 = (User) declaredConstructor.newInstance("jjj","uuuuu");
System.out.println(user1);// 通过反射,直接获取私有的属性
Field name = cla.getDeclaredField("name");
name.setAccessible(true);
name.set(user,"66666");
System.out.println("user = " + user);
} catch (Exception e) {
e.printStackTrace(); } }
### 哪些类型可以有class对象? - 外部类,成员(成员内部类,静态内部类), - 局部内部类,匿名内部类 - interface:接口 - []:数组 - enum:枚举 - annotation:注解@interface(6) - primitive type:基本数据类型 ![image-20210824202620811](http://qiniu.littlekim.top/image/202201222323271.png) **关于class.lang.class类的理解** 1、类的加载文件 程序在经过javac.exe命令以后,会生成一个或者多个字节码文件.class文件, 接着使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中 这个就是**类的加载**,加载到内存中的类,我们就称为**运行时类** ,这时候运行时类,就是一个class的实**例(相当于类本身也是个对象)** 2、class的实例就是一个运行时的对应的运行时类 3、加载到了内存中的运行时类,会缓存一定的的时间,在此时间类,我们可以通过不同的方式来获取运行时类
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的实例的方式    
@Test public void test3(){ //
try {
// 方式一:调用运行时类的属性:.class
Class class1= User.class; System.out.println("class1 = " + class1);
// 方式二: 通过运行时类的对象,调用getclass()
User p1 = new User();
Class class2= p1.getClass();
System.out.println(class2);
// 方式三:通过调用class的静态方法
Class class3 =Class.forName("wz.Reflection.User");
System.out.println(class3);
// 加载到内存中的运行时类,会缓存一段时间,我们可以通过不同的的方式来获取运行时类
System.out.println(class1 == class2);
System.out.println(class1 == class3);
// 使用类加载机制: classLoader
// 先加载类,然后在使用类
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class class4 = classLoader.loadClass("wz.Reflection.User");
// 使用类加载器:ClassLoader获取对象
System.out.println("class4 = " + class4);
}catch (Exception e){
e.printStackTrace();
} }
### 类的加载与ClassLoader的理解: #### 类的加载过程 当程序主动使用某个类时,如果该类还没有被加载到内存中,则系统会通过一下三个步骤对该类进行初始化 ![image-20210824203819378](http://qiniu.littlekim.top/image/202201222323077.png) #### 类加载器的作用: ![image-20210824204248064](http://qiniu.littlekim.top/image/202201222323771.png) ##### 类加载器的作用: > 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的Java.lang.class对象,作为方法区中类数据的访问入口 ###### 类缓存: ##### 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JⅣM垃圾回收机制可以回收这些Class对象。 ##### ![image-20210824204910174](http://qiniu.littlekim.top/image/202201222323423.png) ### 创建运行时类的对象: 通过newInstance()创建运行时类的对象
1
Class<User> personClass = User.class;        /**         * newInstance() 调用此方法,创建对应的运行时对象,内部调用了运行时类的空参构造         *要想此方法正常的创建运行时类的对象         * 1 运行时类必须要提供空参的构造器         * 空参的构造器的访问权限,通常设置为public         * 在Javabean 中要求提供一个public的空参构造器,原因         * 1 便于通过反射,创建运行时类的对象         * 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器         *         */        User obj =personClass.newInstance();        System.out.println("obj = " + obj);
### java反射机制概述:
1
package wz.Reflection.TestField;import org.junit.Test;import wz.Reflection.Person;import java.lang.annotation.Annotation;import java.lang.reflect.*;import java.time.Period;public class TestField {    @Test    public void test1() {        Class cl = Person.class;//      获取属性结构        //拿取public 修饰的属性值//     getFields(): 获取当前运行时类及其父类中的属性,该属性必须为public 修饰权限的属性值        Field[] fields = cl.getFields();        for (Field f : fields        ) {            System.out.println(f);        }        System.out.println("------------");//        getDeclaredFields():获取当前运行时类中声明的所有属性,(不能包含父类中的属,当前类中使用public 修饰的值)        Field[] declareFields = cl.getDeclaredFields();        for (Field f : declareFields        ) {            System.out.println(f);        }    }    //    权限修饰符 数据类型 变量名    @Test    public void test() {        Class clazz = Person.class;        Field[] declareFields = clazz.getDeclaredFields();        for (Field f : declareFields        ) {//            1.权限修饰符,获取权限修饰符            int modifiers = f.getModifiers();            System.out.print(Modifier.toString(modifiers) + "\t");//            2.获取数据类型值            Class type = f.getType();            System.out.print(type + "\t");//            3.获取数据变量名            /**             * private	class java.lang.String	name             * public	int	age             * public	int	id             */            String name = f.getName();            System.out.print(name);            System.out.println();        }    }    /**     * 获取运行时类的构造器     */    @Test    public void Test() {        Class clazz = Person.class;//       getConstructors()获取当前运行时类中声明为public 的构造器        Constructor[] constructors = clazz.getConstructors();        for (Constructor c :                constructors) {            System.out.println(c);            /**             * public wz.Reflection.Person()             * public wz.Reflection.Person(java.lang.String)             */        }//  获取当前运行是类的所有构造器        Constructor[] constructor = clazz.getDeclaredConstructors();        for (Constructor c :                constructor) {            System.out.println(c);        }    }    /**     * 获取运行时类的父类及父类的泛型     */    @Test    public void TestT() {//        supec = class java.lang.Class        Class c = Person.class;        Class supec = c.getClass();        System.out.println("supec = " + supec);    }    /**     * 获取运行时类带泛型的父类的泛型     */    @Test    public void testSuperClass() {//        genericSuperclass = wz.Reflection.Creature<java.lang.String>        Class<Person> personClass = Person.class;        Type genericSuperclass = personClass.getGenericSuperclass();        System.out.println("genericSuperclass = " + genericSuperclass);    }    /**     * 获运行时类的带泛型的父类的泛型     * actualTypeArgument[0] = class java.lang.String     */    @Test    public void TestSuperClass() {        Class clazz = Person.class;        Type genericSupperClass = clazz.getGenericSuperclass();        ParameterizedType parameterizedType = (ParameterizedType) genericSupperClass;//        获取泛型的数据        Type[] actualTypeArgument = parameterizedType.getActualTypeArguments();        System.out.println("actualTypeArgument[0] = " + (Class) actualTypeArgument[0]);    }    /**     * 获取运行时类的接口     */    @Test    public void TestInterface() {        /*        interface java.lang.Comparable        interface wz.Reflection.MyInterface         */        Class clazz = Person.class;        Class[] interfaces = clazz.getInterfaces();        for (Class c :                interfaces) {            System.out.println(c);        }// 获取运行时类的父类的接口,先得到反射的类的对象。在使用对象进行进行后一步的操作        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();        for (Class c :                interfaces) {            System.out.println(c);        }    }    /**     * 获取运行时类所在的包     */    @Test    public void Testpackage() {//        pack = package wz.Reflection        Class cla = Person.class;        Package pack = cla.getPackage();        System.out.println("pack = " + pack);    }    /**     * 获取运行时类的声明的注解     */    @Test    public void TestAnnotation(){        Class clazz = Person.class;        Annotation[] annotations = clazz.getAnnotations();        for (Annotation a:             annotations) {            System.out.println("a = " + a);        }    }}
##### 反射获取对象实例:
1
package wz.Reflection.TestField;import org.junit.Test;import wz.Reflection.Person;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * 调用运行时类的中的指定的接口,属性、方法、 构造器 */public class TestReflection {    @Test    public void TestField() throws NoSuchFieldException, InstantiationException, IllegalAccessException {        Class clazz = Person.class;//        创建运行时类的对象          Person p = (Person) clazz.newInstance();//        获取指定的反射类型 ,指定泛型的属性,这里就是指获取反射对象的属性值        Field id = clazz.getField("id");//       属性--》动态获取的参数,指明哪个对象的属性值 + 参数2:参数值设置为多少        id.set(p,100);        System.out.println("id = " + id);        /**         * 获取当前属性的值         */        int pid =(int) id.get(p);        System.out.println("pid = " + pid);    }    /**     *     * @throws InstantiationException     * @throws IllegalAccessException     * @throws NoSuchFieldException     */    @Test    public  void testField() throws InstantiationException, IllegalAccessException, NoSuchFieldException {        Class clazz = Person.class;//        创建运行时类的对象        Person p = (Person) clazz.newInstance();//      获取运行时类的对象        Field name = clazz.getDeclaredField("name");//        保证当前属性值是可以访问的        name.setAccessible(true);//        获取设置指定对象的此属性值        name.set(p,"Tom");        System.out.println("name = " + name);    }   @Test    public  void testMethod() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {        Class clazz = Person.class;//     1   创建运行时类的对象       Person p =(Person) clazz.newInstance();       /**        * 获取指定的某个方法        * 2获取某个指定的某个方法 ,指明获取的名称 参数2: 指明获取的方法的形参列表        */       Method show = clazz.getDeclaredMethod("show", String.class);       /**        *3 invoke(): 参数1 方法的调用者 参数2 给方法赋值的形参        * invoke() 的返回值即为对应类中调用的方法的返回值        */       Object chn = show.invoke(p, "chn");   }    /**     * 调用指定的构造器     */   @Test    public  void TestConstructor() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {       Class cla  = Person.class;       Constructor declaredConstructor = cla.getDeclaredConstructor(String.class);//        2、b保证构造器是可以进行访问的//       3、调用此构造器创建运行时类的对象       declaredConstructor.setAccessible(true);//       3、调用此构造器创建运行时类的对象       Person tom = (Person)declaredConstructor.newInstance("tom");//       输出的是地址值       System.out.println("tom = " + tom);   }}
### 1.写出获取Class实例的三种常见方式 Class clazz =String.Class Class clazz =person.getClass();//sout(person) Class.forname(String classPath)//体现反射的动态性 ### 2.谈谈你对Class类的理解 Class 实例对应着加载到内存中的一个运行时类 ## 反射的应用:动态代理 ### 代理设计模式的原理: > 使用代理将对象包装起来然后用该对象取代原始的对象。任何对原始的对象都要通过代理。代理对象**决定是否以何时将方法调用转到原始对象上** > 在编译期间就将代理类和目标确定下来了,不利于程序的扩展。同时,**每个代理只能为一个接口服务**。这样就会导致开发过程中产生很多的代理。最好可以通过一个代理类完成全部的代理的功能 **主要区别就是在编译期间确定对象 还是在运行期间进行动态的创建** > 动态代理是指客户通过代理类来调用其他对象的方法,并且在运行时根据需要创建目标类的代理对象 使用场合: ​ 调试 ​ 远程方法的调用 #### 动态代理相比于静态代理的优点: 抽象角色(接口中)声明的对象的方法都被转移到调用处理器一个集中的方法中去处理 这样可以更加灵活的处理更多的方法 #### 动态代理样例:
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
41
42
43
44
45
46
47
package wz.Reflection.Proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;
import java.lang.reflect.Proxy;interface
Human { String getBelief(); void eat(String food);}
//被代理
class SuperMan implements Human {
@Override
public String getBelief() {
return "I believe I can fly !"; }
@Override
public void eat(String food) {
System.out.println("小" + food); }}
/** * 要想实现动态代理,需要解决的问题?
* 问题一: 如何解决根据加载到内存中的被代理类,动态的创建一个代理类及其对象
* 问题二: 通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法 */
public class ProxyFactory {
//调用此方法 返回一个代理类的对象
public static Object getProxyInstance(Object obj) {
//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
// 先进行对象的实例化
handler.bind(obj);
// 获取类加载器,创建代理类的对象,需要同代理类使用同样的接口,
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler); }}
class MyInvocationHandler implements InvocationHandler {
// 当我们通过代理类的对象,调用方法 a 时,就是自动的调用如下的方法:
invoke()
// 将被代理类要执行的方法a的功能就声明在invoke()中
private Object object;
//赋值时,也需要使用被代理类的对象进赋值
public void bind(Object o) {
this.object = o;
}
// proxy调用者的对象,调用的方法,
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method 代理类对象的调用的方法,此方法也就作为被代理类对象的方法// obj : 被代理类的对象
Object invoke = method.invoke(object, args);
// 上述方法的返回值就作为当前类中的invoke()返回值
return invoke; }
public class ProxyTest{
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
// proxyInstance; 代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan); proxyInstance.getBelief();
proxyInstance.eat("麻辣烫");
}
}
}
#### 动态代理与AOP: ![image-20210901105249112](http://qiniu.littlekim.top/image/202201222324606.png) ​ ![image-20210901105209703](http://qiniu.littlekim.top/image/202201222324838.png) 以硬编码的方式就是在程序中将代码在编译期间就已经写死了, ![image-20210901105603807](http://qiniu.littlekim.top/image/202201222324621.png) # 常用类: ### 时间日期APL: jdk-8之前的日期APL: ![image-20210901120406801](http://qiniu.littlekim.top/image/202201222324841.png) #### java.lang.System ​ ![image-20210901120353495](http://qiniu.littlekim.top/image/202201222324592.png)
1
@Test    public void TestCurrent(){//        代表1970年1月1日到当前时间的时间差,常用于时间戳,精确到了毫秒        long time = System.currentTimeMillis();//        时间戳        System.out.println(time);//        1630469278535    }
#### 2、java.util.Date ![image-20210901123245640](http://qiniu.littlekim.top/image/202201222324208.png) #### 3、java.text.SimpleDateformat ![image-20210903182720331](http://qiniu.littlekim.top/image/202201222324773.png) ### java.util.[Calendar](https://so.csdn.net/so/search?q=Calendar&spm=1001.2101.3001.7020)(日历)类 这是一个抽象(abstract)类,主用用于完成日期字段之间相互操作的功能。 实例化: ①使用`Calendar.getInstance()`该静态方法 ②调用它的子类GregorianCalendar的构造器 常用方法:
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
import java.util.Calendar;
import java.util.Date;

public class CalendarTest {
public static void main(String[] args) {
Calendar instance = Calendar.getInstance();

//get():int get(int field)
int ofMouth = instance.get(Calendar.DAY_OF_MONTH);
System.out.println(ofMouth);
int ofYear = instance.get(Calendar.DAY_OF_YEAR);
System.out.println(ofYear);

//set():void set(int field,int value)
instance.set(Calendar.DAY_OF_MONTH,9);
System.out.println(instance.get(Calendar.DAY_OF_MONTH));

//add:void add(int field,int amount)
instance.add(Calendar.DAY_OF_MONTH,-2);
System.out.println(instance.get(Calendar.DAY_OF_MONTH));

//getTime:final Date getTime() 将日历类型转换为Date类型
Date time = instance.getTime();
System.out.println(time);

//setTime:final void setTime(Date date) 将Date类型转换为日历类型
instance.setTime(new Date());
System.out.println("********" + instance.getTime());

}
}

![img](http://qiniu.littlekim.top/image/9e1c1e714c95493a83cb10a2e0773f3a.png) > 一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND。 ## java.time类 `LocalDate、LocalTime、LocalDateTime 类`是其中较重要的几个子类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。 ![img](http://qiniu.littlekim.top/image/0770d658794f4b61886e75b68fd6e0f7.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class TimeTest {
public static void main(String[] args) {
//now() 获取当前日期,时间,日期+时间
LocalDate now1 = LocalDate.now();
LocalTime now2 = LocalTime.now();
LocalDateTime now3 = LocalDateTime.now();
System.out.println(now1);
System.out.println(now2);
System.out.println(now3);

//of() 设置指定的年月日时分秒,没有偏移量
LocalDateTime of = LocalDateTime.of(2021, 9, 9, 15, 36, 40);
System.out.println(of);
}
}

![img](http://qiniu.littlekim.top/image/97fbce2b62f2447c932cc269c4ccb82f.png) ![在这里插入图片描述](http://qiniu.littlekim.top/image/75f38e06d04a4940998fdc1a3faaf1cf.png) ### java.time.Instant类 > Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。
1
2
3
4
5
6
7
8
9
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

public class InstantTest {
public static void main(String[] args) {
//获取本初子午线当前的标准时间
Instant now = Instant.now();
System.out.println(now);
1
2
3
4
5
6
7
8
9
10
11
12
    //结合时区,添加当地时间偏移量
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);

//返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳
long l = now.toEpochMilli();
System.out.println(l);

//返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象
Instant instant = Instant.ofEpochMilli(l);
System.out.println(instant);
}
### java.time.format.DateTimeFormatter 类 > 该类提供了三种格式化方法: > ①预定义的标准格式,如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME > ②本地化相关的格式,如:ofLocalizedDateTime(FormatStyle.LONG) > ③自定义的格式,如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
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
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.TemporalAccessor;

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


//方式一:预定义的标准格式
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化
String format1 = isoDateTime.format(LocalDateTime.now());
System.out.println(format1);
//解析
TemporalAccessor parse1 = isoDateTime.parse(format1);
System.out.println(parse1);

//方式二:本地化相关的格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String format2 = dateTimeFormatter.format(LocalDateTime.now());
System.out.println(format2);
//解析
TemporalAccessor parse2 = dateTimeFormatter.parse(format2);
System.out.println(parse2);

//方式三:自定义格式
DateTimeFormatter dateTimeFormatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String format3 = dateTimeFormatter3.format(LocalDateTime.now());
System.out.println(format3);
//解析
TemporalAccessor parse3 = dateTimeFormatter3.parse(format3);
System.out.println(parse3);
}
}

![img](http://qiniu.littlekim.top/image/79f599e363604b1d8a415641e1b9965a.png) 其它API ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris ZonedDateTime:一个在ISO-8601日历系统时区的日期时间, 如 2007-12-03T10:15:30+01:00 Europe/Paris 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如: Asia/Shanghai等 Clock:使用时区提供对当前即时、日期和时间的访问的时钟 持续时间:Duration,用于计算两个“时间”间隔 日期间隔:Period,用于计算两个“日期”间隔 TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。 TemporalAdjusters : 该类通过静态方法(firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用TemporalAdjuster 的实现 ![img](http://qiniu.littlekim.top/image/b9d00acda5ba473a8d9ccc8e21b88562.png) ![img](http://qiniu.littlekim.top/image/ac5dec0a7e8047c291e176c09af4a5ec.png) ![img](http://qiniu.littlekim.top/image/be357ad1860b499487a8cb79e4e4669b.png) ![img](http://qiniu.littlekim.top/image/614538cb7014484e8234cf9ea8084272.png) ## LocalDateTime用法 - ### 获取当前日期、时间
1
2
3
4
5
6
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
![img](http://qiniu.littlekim.top/image/16466161-18bc14a7df6482ea.png) - ### 指定时间日期创建对应的对象
1
2
3
4
5
6
LocalDate localDate = LocalDate.of(2018, 1, 13);
LocalTime localTime = LocalTime.of(9, 43, 20);
LocalDateTime localDateTime = LocalDateTime.of(2018, 1, 13, 9, 43, 20);
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
![img](http://qiniu.littlekim.top/image/16466161-b9f522a1343866a0.png) - ### 计算两个时间点的间隔
1

- ### 时间、日期相加减 > 对于LocalDate,只有精度大于或等于日的加减,如年、月、日; > 对于LocalTime,只有精度小于或等于时的加减,如时、分、秒、纳秒; > 对于LocalDateTime,则可以进行任意精度的时间相加减; > >
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
LocalDateTime localDateTime = LocalDateTime.now();
//以下方法的参数都是long型,返回值都是LocalDateTime
LocalDateTime plusYearsResult = localDateTime.plusYears(2L);
LocalDateTime plusMonthsResult = localDateTime.plusMonths(3L);
LocalDateTime plusDaysResult = localDateTime.plusDays(7L);
LocalDateTime plusHoursResult = localDateTime.plusHours(2L);
LocalDateTime plusMinutesResult = localDateTime.plusMinutes(10L);
LocalDateTime plusSecondsResult = localDateTime.plusSeconds(10L);

System.out.println("当前时间是 : " + localDateTime + "\n"
+ "当前时间加2年后为 : " + plusYearsResult + "\n"
+ "当前时间加3个月后为 : " + plusMonthsResult + "\n"
+ "当前时间加7日后为 : " + plusDaysResult + "\n"
+ "当前时间加2小时后为 : " + plusHoursResult + "\n"
+ "当前时间加10分钟后为 : " + plusMinutesResult + "\n"
+ "当前时间加10秒后为 : " + plusSecondsResult + "\n"
);

//也可以以另一种方式来相加减日期,即plus(long amountToAdd, TemporalUnit unit)
// 参数1 : 相加的数量, 参数2 : 相加的单位
LocalDateTime nextMonth = localDateTime.plus(1, ChronoUnit.MONTHS);
LocalDateTime nextYear = localDateTime.plus(1, ChronoUnit.YEARS);
LocalDateTime nextWeek = localDateTime.plus(1, ChronoUnit.WEEKS);

System.out.println("now : " + localDateTime + "\n"
+ "nextYear : " + nextYear + "\n"
+ "nextMonth : " + nextMonth + "\n"
+ "nextWeek :" + nextWeek + "\n"
);

//日期的减法用法一样,在此不再举例
![img](http://qiniu.littlekim.top/image/16466161-e67ba546033ee72c.png) **获取给定时间点的年份、月份、周、星期等**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LocalDate localDate = LocalDate.now();
//当前时间基础上,指定本年当中的第几天,取值范围为1-365,366
LocalDate withDayOfYearResult = localDate.withDayOfYear(200);
//当前时间基础上,指定本月当中的第几天,取值范围为1-29,30,31
LocalDate withDayOfMonthResult = localDate.withDayOfMonth(5);
//当前时间基础上,直接指定年份
LocalDate withYearResult = localDate.withYear(2017);
//当前时间基础上,直接指定月份
LocalDate withMonthResult = localDate.withMonth(5);
System.out.println("当前时间是 : " + localDate + "\n"
+ "指定本年当中的第200天 : " + withDayOfYearResult + "\n"
+ "指定本月当中的第5天 : " + withDayOfMonthResult + "\n"
+ "直接指定年份为2017 : " + withYearResult + "\n"
+ "直接指定月份为5月 : " + withMonthResult + "\n"
);
- ### 时间日期前后的比较与判断
1
2
3
4
5
6
//判断两个时间点的前后
LocalDate localDate1 = LocalDate.of(2017, 8, 8);
LocalDate localDate2 = LocalDate.of(2018, 8, 8);
boolean date1IsBeforeDate2 = localDate1.isBefore(localDate2);
System.out.println("date1IsBeforeDate2 : " + date1IsBeforeDate2);
// date1IsBeforeDate2 == true
- ### 判断是否为闰年
1
2
LocalDate now = LocalDate.now();
System.out.println("now : " + now + ", is leap year ? " + );
- ### java8时钟 : clock()
1
2
3
4
//返回当前时间,根据系统时间和UTC
Clock clock = Clock.systemUTC();
// 运行结果: SystemClock[Z]
System.out.println(clock);
- ### 计算时间、日期间隔 - [x] Duration:用于计算两个“时间”间隔 - [x] Period:用于计算两个“日期”间隔
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
//计算两个日期的日期间隔-年月日
LocalDate date1 = LocalDate.of(2018, 2, 13);
LocalDate date2 = LocalDate.of(2017, 3, 12);
//内部是用date2-date1,所以得到的结果是负数
Period period = Period.between(date1, date2);
System.out.println("相差年数 : " + period.getYears());
System.out.println("相差月数 : " + period.getMonths());
System.out.println("相差日数 : " + period.getDays());
//还可以这样获取相差的年月日
System.out.println("-------------------------------");
long years = period.get(ChronoUnit.YEARS);
long months = period.get(ChronoUnit.MONTHS);
long days = period.get(ChronoUnit.DAYS);
System.out.println("相差的年月日分别为 : " + years + "," + months + "," + days);
//注意,当获取两个日期的间隔时,并不是单纯的年月日对应的数字相加减,而是会先算出具体差多少天,在折算成相差几年几月几日的

//计算两个时间的间隔
System.out.println("-------------------------------");
LocalDateTime date3 = LocalDateTime.now();
LocalDateTime date4 = LocalDateTime.of(2018, 1, 13, 22, 30, 10);
Duration duration = Duration.between(date3, date4);
System.out.println(date3 + " 与 " + date4 + " 间隔 " + "\n"
+ " 天 :" + duration.toDays() + "\n"
+ " 时 :" + duration.toHours() + "\n"
+ " 分 :" + duration.toMinutes() + "\n"
+ " 毫秒 :" + duration.toMillis() + "\n"
+ " 纳秒 :" + duration.toNanos() + "\n"
);
//注意,并没有获得秒差的,但既然可以获得毫秒,秒就可以自行获取了
![img](http://qiniu.littlekim.top/image/16466161-27973ec99debce86.png) [LocalDateTime用法 - 简书 (jianshu.com)](https://www.jianshu.com/p/048ee8580639)