java进阶篇

接口

接口注意事项!

  1. 接口不能实例化,但可以使用匿名内部类来实现方法的方式来实现接口实例化

  2. 接口中默认都是抽象方法

  3. 实现接口必须实现方法,抽象类可以不实现

  4. 使用default修饰的接口方法无需实现

  5. 一个类可以实现多个接口 例: class S implements A , B , C{ }

  6. 接口中的属性,只能是final的,public static final 修饰。必须初始化。

  7. 如果想要访问接口的变量,可以使用接口对象或接口对象的实例化 + 变量名来访问

  8. 接口不能继承类,可以继承多个接口 例:interface A extends B , C{ }

  9. 接口的只能修饰为 public 和 默认

    java中的接口能够被实例化吗?_wolf小狼崽的博客-CSDN博客_interface可以实例化吗


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
package com.zero02;
@SuppressWarnings("all")
/**
* @author Zero02
* @date 2022-08-29上午 09:13
* @description 接口的实例化操作
* @package com.zero02
*/
public class P {
public static void main(String[] args) {
//接口实例化的操作,匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
A a1 = new A() {};
a1.a();
System.out.println(a1.i + A.i);//输出接口类的变量,使用接口名或接口对象 + 变量名
}
}
interface A {
//接口中的属性必须初始化,默认为 public static final 修饰
int i = 1;
//使用default修饰的方法可以无需实现
default void a() {
System.out.println("aaa");
}
}

接口小练习

1
2
3
4
5
6
7
8
9
10
interface A{
int a = 23;
}
class B implements A{}
class main{
B b = new B();
System.out.println(b.a);
System.out.println(A.a);
System.out.println(B.a);
}

接口Vs继承类

当子类继承了父类,就自动的拥有父类的功能

如果子类需要扩展功能,可以通过实现接口的方式扩展.

可以理解 实现接口 是 对 java 单继承机制的一种补充

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
class main {
public static void main(String[] args) {
WuKong wuKong = new WuKong();
wuKong.Climb();
wuKong.swim();
wuKong.fly();
}
}

class WuKong extends Monkey implements fish,bird{
@Override
public void swim(){
System.out.println("学习了可以像🐟一样swim");
};
@Override
public void fly(){
System.out.println("学习了可以像鸟一样fly");
}
}

class Monkey{
public void Climb(){
System.out.println("🐒会爬树");
}
}
interface fish{
void swim();
}
interface bird{
void fly();
}

接口可以更好的解决代码的复用性和可维护性,接口更加适合设计各种规范。可以自行实现,使其更加灵活

接口的多态参数!!

可以让接口类型的对象指向被这个接口实现的类

1
2
3
4
5
6
7
8
9
10
11
class main {    
public static void main(String[] args) {
//可以将B接口类型的对象指向实现B接口的类,体现接口的多态参数
B b = new A();
//C对象也是如此
B b1 = new C();
}
}
class A implements B {}
class C implements B {}
interface B {}

接口的多态数组!!

接口类的实例指向一个接口类的数组,数组中的元素是实现接口的类的对象或实例,体现多态的接口类数组

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
class main {    
public static void main(String[] args) {
B[] b = new B[2];
A a = new A();
C c = new C();
b[0] = a;
b[1] = c;
for (int i = 0; i < b.length; i++) {
b[i].w();//动态绑定,会根据数组元素的类自动指定对应w方法
//类型筛选
if(b[i] instanceof A){
((A) b[i]).s();//向下转型A的匿名对象并运行A类的s方法
}
}
}
}class A implements B {
@Override
public void w() {
System.out.println("A的w方法");
}
//A类的独有方法
public void s(){
System.out.println("A的s方法");
}
}
class C implements B {
@Override public void w() {
System.out.println("C的w方法");
}
}
interface B {
void w();
}

接口的多态传递!!

当一个接口继承了另一个接口,实现该接口的类即要重写该接口的方法,也要重写该接口继承接口中的方法

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 main {    
public static void main(String[] args) {
//接口的多态传递,C实现了B接口就相当于实现了A接口,因为B接口继承了A接口
A a = new C();
//两个接口的实例均可指向这个类
B b = new C();
}
}
interface A {
void a();
}
class C implements B {
@Override
public void a() {
System.out.println("这是A的a方法");
}
@Override
public void b() {
System.out.println("这是B类的b方法");
}
}
interface B extends A {
void b();
}

内部类

局部内部类

  1. 可以访问外部类的所有成员,包括私有
  2. 不能添加修饰符,他相当于一个局部变量,不过可以使用final
  3. 局部内部类的作用域,在定义他的方法或代码块中
  4. 直接访问外部类的成员,创建对象再访问(必须在作用域中)
  5. 局部内部类两个特点: 作用域在方法体内或代码块中,本质也是一个类
  6. 外部其他类不能访问局部内部类
  7. 如果外部类和局部内部类重名时,按照就近原则访问成员,如果想要访问外部类的成员,则可以使用 外部类名 + 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
class main {    
public static void main(String[] args) {
A a = new A();
a.b();
System.out.println(a);
}
}
class A {
private int n1 = 100;
private void m1() {
System.out.println("A类的m1方法");
}
public void b() {
//这个代码块中为他的作用域,如果定义在方法中,那么整个方法内部就是他的作用域
//定义在b方法的局部位置中,相当于局部变量
//可以使用final修饰
final class B {//局部内部类,本质还是一个类
private int n1 = 10;
public void m2() {
System.out.println("B类的m2方法");
//可以直接访问外部类的成员
m1();
}
//访问重名的外部成员
public void An1() {
System.out.println("A类的n1:" + A.this.n1); System.out.println("B类的n1:" + this.n1);
}
}
//如果想要使用局部内部类的方法,就需要定义该类的对象或实例,在这个方法体内访问该类的方法
B b = new B();
b.An1();
}
}

匿名内部类!!!

  1. 本质上还是类,他也是个内部类,但是没有名字
  2. 匿名内部类的语法独特,该类既是一个类的定义,同时也是一个对象,既有定义类的特征,也有创建实例的特征。
  3. 可以直接访问外部类的所有成员,包括私有
  4. 不能添加访问修饰符,他相当于一个局部变量
  5. 作用域在定义他的代码块和方法体中
  6. 外部其他类不能访问匿名内部类
  7. 如果外部类和匿名内部类重名时,按照就近原则访问成员,如果想要访问外部类的成员,则可以使用 外部类名 + 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
class main {    
public static void main(String[] args) {
A a = new A();
a.method();
}
}
class A {
private int n1 = 10;//成员属性
/**底层实现 会分配给匿名内部类一个名字
* class A$1 implements IA{
* //@Override
* public void cry(){
* System.out.println("老虎在喊");
* }
* }
*/
//jdk底层会创建一个匿名内部类A$1,并且把实现好方法的IA对象返回给tiger
//匿名内部类只能使用一次
public void method() {
IA tiger = new IA() {
@Override
public void shout() {
System.out.println("老虎在喊");
}
};
//当前在方法可以多次调用,只是实现好的IA对象只有一个
tiger.shout();
tiger.shout();
}
}
interface IA {
void shout();
}

匿名内部类的实践

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
package com.hspedu.innerclass;

import com.hspedu.abstract_.AA;

public class InnerClassExercise01 {
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new IL() {
@Override
public void show() {
System.out.println("这是一副名画~~...");
}
});
//传统方法
f1(new Picture());
}

//静态方法,形参是接口类型
public static void f1(IL il) {
il.show();
}
}
//接口
interface IL {
void show();
}
//类->实现IL => 编程领域 (硬编码)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画XX...");
}
}

成员内部类

  1. 成员内部类是定义在外部类的成员位置,并且没有static修饰
  2. 可以直接访问外部类的所有成员,包括私有
  3. 可以添加任何访问修饰符public protected 无 private 因为他相当于一个成员
  4. 成员内部类的作用域为整个类体,和其他成员的作用域相同。在外部类成员方法中建立该类对象,可以调用该类的方法
  5. 成员内部类访问外部类成员,直接访问 。外部类访问成员内部类需要创建对象,再访问。外部其他类访问成员内部类,直接访问
  6. 如果外部类和成员内部类重名时,在成员内部类访问的话,按照就近原则访问成员,如果想要访问外部类的成员,则可以使用 外部类名 + 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
37
38
39
class main {
public static void main(String[] args) {
A a = new A();
a.B();
//通过get方法来使用成员B类的B方法
a.getB().B();
//另一种实例成员内部类的方式
A.B b = a.new B();
b.B();
//比上一个复杂点,这种适用于指向使用一次的成员内部类的B方法
new A().new B().B();
}
}

class A {
private int n1 = 10;
private String name = "张三";

public void B() {
System.out.println("A的B方法");
}

class B {
private int n1 = 100;

public void B() {
//这里指定A类的this.n1对象,就能访问A类的n1成员变量
//this.n1就是访问当前类中的n1变量
System.out.println("A类的n1:" + A.this.n1 + " B类的n1:" + this.n1);
//这里按照就近原则,找到A类的name成员变量
System.out.println("name:" + name);
}
}

//返回一个B的实例
public B getB() {
return new B();
}
}

静态内部类

  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
  2. 可以添加任意访问修饰符 public protected 无 private ,因为它的地位就是一个成员
  3. 静态内部类的作用域为整个类体,和其他成员一样
  4. 静态内部类访问外部类,直接访问所有静态成员。 非静态无法访问。
  5. 外部类访问静态内部类,创建对象,再访问。 外部其他类访问静态内部类直接访问
  6. 如果外部类和静态内部类的成员重名时,静态内部类的方法访问成员,默认按照就近原则,如果想要访问外部类的成员,则可以使用外部类名 + 成员去访问
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
class main {
public static void main(String[] args) {
A a = new A();
a.BB();//通过方法来访问
//通过返回B对象来访问B方法
A.B b = a.getB();
b.B();
//通过一次性创建A.B静态内部类的实例
A.B b1 = new A.B();
b1.B();
}
}

class A {
private int n1 = 10;
private static String name = "李四";
private static void cry() {}

//B就是静态内部类
static class B {
private static String name = "王五";
public void B() {
//访问变量默认遵循就近原则
System.out.println("B类的name:" + name);
System.out.println("A类的name:" + A.name);
cry();
//非静态变量无法访问
//A.n1
}
}

//可以创建一个方法来访问该B类的B方法
public void BB() {
B b = new B();
b.B();
}

//通过返回B来调用方法
public B getB() {
return new B();
}
}

枚举

枚举的特点

枚举作为一个可读取的对象,存放很多常量,他们可用于随时调用。不需要修改,还可以指定一个枚举对象的常量所拥有的属性。所有常量使用static 和 final 修饰。通常对象名都大写,体现出是一个不可更改的常量。

枚举的注意事项

  1. 当我们使用enum 关键字开发一个枚举类时,默认会继承Enum类, 而且是一个final 类[如何证明],使用 javap 工具来演示
  2. 如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
  3. 当有多个枚举对象时,使用,间隔,最后有一个分号结尾
  4. 枚举对象必须放在枚举类的行首.
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
class main {
public static void main(String[] args) {
Color black = Color.BLACK;
System.out.println(black);
}
}

enum Color {

//已经定义好的枚举类 所有枚举对象 public static final 类型
RED(255, 0, 0), BLUE(0, 0, 255), Green(0, 255, 0), BLACK(255, 255, 255), WHITE(0, 0, 0);
private final int r, g, b;

//构造器默认是私有的
Color(int r, int g, int b) {
this.r = r;
this.g = g;
this.b = b;
}

@Override
public String toString() {
return "Color{" +
"r=" + r +
", g=" + g +
", b=" + b +
'}';
}
}

枚举类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
valueOf()  传递枚举类型的Class对象和枚举常量名称给静态方法valueOf,会得到于参数匹配的枚举常量

toString() 得到当前枚举常量的名称。你可以通过重写这个方法来使得到的结果更易读

equals() 在枚举类型中可以直接使用"=="来比较两个枚举是否相等,Enum提供的这个equals方法,也是直接使用"=="来实现的。它的存在是为了在set、list和map中使用。注意,equals()是不可变的

hashCode() Enum实现了hashCode()来和equals()保持一致,它也是不可变的

getDeclaringClass() 得到枚举常量所属枚举类型的Class对象,可以用它来判断两个枚举常量是否属于同一个枚举类型

name() 得到当前枚举常量的名称,建议优先使用toString()

ordinal() 得到当前枚举常量的次序。

compareTo() 枚举类型实现了Comparable接口,这样可以比较两个枚举常量的大小(按照声明的次序排列)

clone() 枚举对象不能被克隆,为了防止子类克隆的方法,Enum实现了一个仅抛出

CloneNotSupportedException 异常不变的Clone()

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
class main {
public static void main(String[] args) {
Color r = Color.GREEN;
System.out.println(r.name());//输出枚举类的名字
System.out.println(r.ordinal());//输出对象的定义序列位置
Color[] v = Color.values();//获取所有枚举对象
for(Color c : v){//遍历所有对象
System.out.println(c);
}
//valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
Color c1 = Color.valueOf("BLACK");
System.out.println("Black"+c1);
//compareTo()比较两个枚举常量,比较的就是编号 就是5-2
System.out.println(Color.WHITE.compareTo(Color.BLUE));
}
}

enum Color {

//已经定义好的枚举类 所有枚举对象 public static final 类型
RED(255, 0, 0), BLUE(0, 0, 255), GREEN(0, 255, 0), BLACK(255, 255, 255), WHITE(0, 0, 0);
private final int r, g, b;

//构造器默认是私有的
Color(int r, int g, int b) {
this.r = r;
this.g = g;
this.b = b;
}

@Override
public String toString() {
return "Color{" +
"r=" + r +
", g=" + g +
", b=" + b +
'}';
}
}

注解

注解的解释

  1. 注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息
  2. 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
  3. 在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 java EE 旧版中所遗留的繁冗代码和XML 配置等。

基本的 Annotation 介绍

使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元
素 三个基本的 Annotation:

  1. @Override: 限定某个方法,是重写父类方法, 该注解只能用于方法
  2. @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
  3. @SuppressWarnings: 抑制编译器警告

抑制的警告类型

  • all,抑制所有警告
  • boxing,抑制与封装/拆装作业相关的警告
  • cast,抑制与强制转型作业相关的警告
  • dep-ann,抑制与淘汰注释相关的警告
  • deprecation,抑制与淘汰的相关警告
  • fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告
  • finally,抑制与未传回 finally 区块相关的警告
  • hiding,抑制与隐藏变数的区域变数相关的警告
  • incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告
  • javadoc,抑制与 javadoc 相关的警告

元注解的基本介绍

JDK 的元 Annotation 用于修饰其他 Annotation 元注解: 本身作用不大,讲这个原因希望同学们,看源码时,可以知道他是干什么.

元注解的种类 (使用不多,了解, 不用深入研究)

  • Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
  • Target // 指定注解可以在哪些地方使用
  • Documented //指定该注解是否会在 javadoc 体现
  • Inherited //子类会继承父类注解

异常

异常基本说明

java语言中,将程序中发生的不正常情况称为”异常”,语法错误和逻辑错误不是异常。

异常分为两种,一种是严重错误异常Error,一种是运行编译时异常Exception。

Error指无法解决的严重问题,如StackOverFloor栈溢出和OOM(out of memory)内存溢出,程序会崩溃。

Exception指其他一些偶然或外在因素产生的一般性问题,可以更改代码解决问题,如空指针访问,读取不存在的文件,网络连接中断等等,Exception分为两个,运行时异常和编译时异常

异常的总体系图

Snipaste_2022-07-22_13-24-05

编译时异常,是在编写程序时编译器要求必须处理的异常

运行时异常,编译器检测不出来,对于这类异常,可以不做处理,因为这种异常很普遍。

常见的运行时异常

NullPointerException空指针异常

ArithmeticException算术异常

ArrayIndexOutOfBoundsException数组下标越界异常

ClassCastException类转换异常

NumberFormatException数字格式错误异常

常见的编译时异常

SQLException操作数据库时,查询表可能发生异常

IOException操作文件时,发生的异常

FileNotFoundException当操作一个不存在的文件时,发生异常

ClassNotFoundException加载类,而该类不存在时,异常

EOFException操作文件,到文件末尾,发生异常

IllegalArguementException非法的参数异常

异常处理的方式

try - catch - finally 程序员在代码中捕获发生的异常,自行处理

throws将发生的异常抛出,交给调用者来处理 (JVM或调用者)

try - finally 不会捕获异常,执行代码后,不管是否发生异常,都必须执行所有代码

1
2
3
4
5
6
7
8
try{
//可疑代码,将异常生成的异常对象,传入到catch块
}catch(异常类型){
//对异常的处理
}finally{//finally不是必须的,一般用于不管是否异常都要实行的操作
//必须运行的代码,例如关闭流,释放资源等
}

1
2
3
4
5
6
class A{
public void A1() throws 异常{//让异常抛给他的调用者,可能是一个方法或JVM,如果是主方法调用也会抛给JVM
//可疑代码
}
}

自定义异常

当程序中出现了某些错误,但该信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。

通常是自定义一个异常类名作为一个类,继承Exception或RuntimeException,分别代表编译异常和运行异常

一般来说继承RuntimeException作为自定义异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CustomException {
public static void main(String[] args) { /*throws AgeException*/
int age = 180;
//要求范围在 18 – 120 之间,否则抛出一个自定义异常
if (!(age >= 18 && age <= 120)) {
//这里我们可以通过构造器,设置信息
throw new AgeException("年龄需要在 18~120 之间");
} System.out.println("你的年龄范围正确.");
}
}

class AgeException extends RuntimeException {
public AgeException(String message) {//构造器
super(message);
}
}

集合

集合与数组的区别

长度区别:集合长度可变,数组长度不可变

内容区别:集合可存储不同类型的元素,数组存储只能存储单一类型元素

元素区别:集合只能存储引用类型元素,数组可存储引用类型,也可存储基本类型

集合体系框架图

1667917129847

注:蓝色的是接口,黑色的是实现类

接口的特点

Collection接口被list接口和set接口继承

list接口有三个实现类:ArrayList,LinkedList,Vector

Set接口被HashSet实现,被SortedSet接口继承,同时TreeSet类实现SortedSet接口,LinkedHashSet类继承HashSet类

Map接口有两个实现类,HashMap,Hashtable,同时Properties类继承Hashtable

Map接口被SortedMap接口继承,同时TreeMap类实现了SortedMap接口

Set:元素无序,不可重复的集合

List:元素有序,可重复的集合

Map:双列集合:具有映射关系的”key-value”Entry键值对的集合

Collection接口(单列集合)

collection是单列集合的最顶层接口,定义了一些通用的方法

  • Collection.add()添加单个元素

  • Collection.remove()删除指定元素,还可以指定元素索引

  • Collection.contains()判断指定元素是否存在与list列表中

  • Collection.size()获取集合中元素的个数

  • Collection.clear()清空所有集合中的元素

  • Collection.addAll()添加多个元素,可以给定一个要添加的另一个list,将两个list合并为一个

  • Collection.containsAll()判断多个元素是否都存在,可以给定一个要判断的另一个list,判断当前list是否存在这个list所有的元素

  • Collection.removeAll()删除多个元素,可以给定一个要删除的另一个list,将当前list中去除和另一个list的相同的元素

  • Collection.toArray()可以将collection类的集合转为数组

  • Collection.isEmpty()判断集合是否为空

Iterator接口遍历集合使用

Iterator是Collection的父接口,所以只要实现了Collection接口的类,都可以使用Iterator进行遍历数组

Iterator iterator = 集合对象.iterator();

Iterator接口的方法 idea快捷键:itit

Iterator.hasNext(),往往和while组合使用

Iterator.Next(),在while循环体中取出元素

for增强遍历集合格式 idea快捷键: iter

For(数据类型 引用名: 集合){

输出语句(引用名);

}

List接口的特点

有索引,精确操作元素

list集合类中元素有序(添加和取出顺序一致,且可重复)

list集合中的每个元素都有对应的顺序索引,即支持索引

list容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据需要存取容器中的元素

list接口是collection的子接口,可以使用collection和iterator的接口方法

List接口的实现类的方法

  • List.add(int index,Object obj)在index位置插入obj

  • List.addAll(int index,Collection obj)从index位置开始将obj中的所有元素添加至list中

  • List.get(Int index)获取指定index索引的元素

  • List.indexof(Object obj)返回obj在集合中第一次出现的位置

  • List.lastIndexOf(Object obj)返回obj在集合中最后一次出现的位置

  • List.remove(int index)指定index位置的元素将其移除,并返回元素

  • List.set(int index,Object obj)设置指定index位置的obj,相当于替换

  • List.subList(int fromindex,int toindex)返回一个从fromindex到toindex的子集合,[ }

ArrayList特点

查询快,增删慢,主要用于查询遍历数据,为最常用集合之一

arraylist允许null值,并且允许多个

arraylist是用数组来存储的

arraylist是线程不安全的,在多线程中使用vector

ArrayList底层

arraylist中维护了一个object类型的数组elementdata

Transient Object[] elementdata

trasient瞬间,短暂的 transient的属性不会被序列化

创建arraylist对象时,如果使用无参构造器,初始elementdata容量为0,第一次添加,则扩容

elementdata为10,如果再次扩容,则扩容为上次的1.5倍

如果指定了大小的构造器,则初始化elementdata为指定大小,如果再次扩容,则扩容为上次的1.5倍

数据结构是有序的元素序列,在内存中开辟一段连续的空键,在空键中存放元素,每个空键都有编号,通过编号可以快速找到相应元素,因此查询快,数组初始化时长度时固定的,想要增删元素,必须创建一个新数组,把源数组的元素复制进来,随后源数组销毁,耗时长,因此增删慢。

LinkedList的特点

linkedlist底层实现了双向链表和数段队列特点

查询慢,增删快

有特有的属性:

  • First 指向链表中的第一个元素

  • Last 指向链表中的最后一个元素

  • Next 指向元素的下一个元素

  • Prev 指向元素的前一个元素

删除元素,只需要改变链表的next和prev指向,就可以实现,所以效率高

可以添加任意元素(元素可以重复),包括null

linkedList是线程不安全的

LinkedList的特有方法

  • getFirst()返回第一个元素

  • getLast()返回最后一个元素

  • pop() 此方法用于删除或返回或弹出位于链表所表示的堆栈顶部(初始或第一位置)的对象。

  • push(E e) 这类似于LinkedList的addFirst()方法,只是将元素插入到链表的第一个位置或顶部。

  • addFirst(E e)添加元素到开头,头插

  • addLast(E e)添加元素到结尾,尾插


Vector类的特点

查询快,增删慢

vector底层和ArrayList一样,使用数组来存储的

Protected Object[] elementData;

vector是线程同步的,vector类的操作方法都带有synchrionized

如果需要线程安全需要使用vector

vector的效率比arraylist要低

在java编程思想中提到他又一些遗留的缺点,因此不建议使用

Vector的底层

如果使用的是无参构造器,默认的底层数组大小是10,当存储满后,按2倍扩容

如果指定大小,则每次直接按2倍扩大

ArrayList和LinkedList的比较

Arraylist是一个可变数组,根据指定流程来创建集合大小和扩容,增删效率低,因为是数组扩容,改查效率较高

LinkedList是一个双向链表,根据指针来组合整个链表中的前后关系,增删的效率较高,底层通过gc回收没有指向的元素,修改指针即可增加删除,改查效率较低

相对而言,查询较多,大部分会选择ArrayList

Set接口的特点

set是无序的,即存放和取出的顺序不一致,取出的顺序是固定的,但是和存放的顺序不一致。没有索引

不允许添加重复元素,也不允许有多个null值

set接口和list接口一样,都是collection接口的子接口,都可以使用collection接口和iterator接口的方法

Set也可以使用迭代器,增强for,但是不能通过索引遍历的方式来获取元素

HashSet的特点

查询快,元素无序,元素不可重复,没有索引

hashset底层是hashmap

JDK1.8之前:哈希表(数组+单向链表);JDK1.8之后:哈希表(数组+单向链表+红黑树)

通过获取一个元素的hash值来决定存放在数组的哪个位置上。当有两个或多个元素的hash值决定的下标相同时,会在该数组下表形成一个单向链表,存放多个元素添加的元素如果有相同值,首先调用该类的equals方法判断元素是否相同,相同会放弃添加,不同就加入如果任意一条链表的元素数量达到8并且数组的大小(table)为64,就会进化为红黑树

哈希表底层用数组+单向链表实现,即使用链表处理冲突,同一Hash值的元素都存储在一个链表里,但是当位于一个链表中的元素较多,即Hash值相等的元素较多,通过key值依次查找的效率降低。JDK1.8之后,哈希表底层采用数据+单向链表+红黑树实现,当链表长度超过阈值(8)时,链表将转换为红黑树,极大缩短查询时间。

ps:哈希值是一个十进制的整数,是对象的地址值,是一个逻辑地址,不是实际存储的物理地址,由系统随机给出。Object类的int hashCode()方法,可以获取对象的哈希值。

HashSet底层添加数据的过程

第一次添加

使用的是HashSet.put()方法

使用元素的 hashcode ^ hashcode结果的无符号右移16位的结果 获取该元素应该存放的索引下标值

底层会首先判断table是否为空,如果为空说明是第一次添加数据

如果table为null,或者大小为0,就将其扩容到16个空间

然后定义一个添加元素的临界值,是当前空间的4/3大小,如果已存在的元素大于临界值,会再一次扩容2倍

然后将元素添加进去

第二次添加

首先得到元素应该存放的索引下标值

判断此下标值是否为空,如果为空,就将元素添加进去

判断是否大于临界值,大于扩容,否则会跳过

第三次添加

首先得到元素应该存放的索引下标值

假如我们添加的和第一次添加的值相同

就进入非下标值为空的分支

首先有三种情况

如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样

并且满足准备加入的key和要添加位置的元素是同一个对象;

判断当前索引是否是一个红黑树,如果是一颗红黑数,就调用putTreeVal,来进行添加,在转成红黑树如果前table大于64并且单条链表为8个元素,才会进行树化;

否则就比较这个table当前索引的链表,遍历比较是否有相等的元素,如果没有就放在改链表的最后面

LinkedHashSet的特点

是HashSet的子类,LinkedHashSet的底层是LinkedHashMap(是hashMap的子类),

维护了一个数组 + 双向链表 + 红黑数

查询快,元素有序,元素不可重复,没有索引

LinkedHashSet根据元素的hashCode值来决定元素的存储位置

同时使用指针维护元素的次序,使得元素看起来是以插入顺序保存的

拥有before和after属性,first和last属性

添加一个元素时,先求hash值,在求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表,原理和hashset类似

linkedHashSet的底层机制

添加第一次时,直接将数组table扩容到16,存放的节点类型时LinkedHashmap$Entry

table的类型时LinkedHashMap$Node, Entry继承了Node,继承关系是在内部类完成

linkedHashset的底层机制和hashset类似,但是不需要扩容,因为是双向链表指针

TreeSet的特点

数据结构:红黑树

查询快,元素有序,元素不可重复,没有索引

TreeSet实现了继承于Set接口的SortedSet接口 ,它支持两种排序方法,自然排序和定制排序,自然排序的意思就是放入元素“a”,“b”,a会自然地排在b前面,其中还有几个特有方法。

可以给添加的数据排序,默认按照字母排序

可以通过构造器传入一个comparator,自定义排序方法

Map接口的特点

元素包含两个值(key,value)即键值对,key值不允许重复,value可以重复,key和value时对应的,元素无序

Map与Collection并列存在,存储具有映射关系的键值对Entry<K,V>

Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中

Map中的key不允许重复,原因和hashset一样

Map中的value可以重复

key和value可以为null,但是key不能有多个null,value可以有多个null

常用String类作为map的key

key和value存在一对一关系,通过key来获取value

Map的方法

  • Map.put()添加一个元素

  • Map.remove()根据键值删除映射关系

  • Map.get()根据键获取值

  • Map.size()获取元素个数

  • Map.isEmpty()Map集合是否为null

  • Map.clear()清除所有元素

  • Map.containsKey()查找键是否存在

ps:Map集合必须保证保证key唯一,作为key,必须重写hashCode方法和equals方法,以保证key唯一。

Map集合的遍历

  • Set keySet = Map.keySet();//可以使用增强for,和while循环iterator
  • Collection values = map.values();//可以使用增强for,和while循环iterator
  • EntrySet<Entry<K,V>> entrySet = Map.entrySet()//可以使用增强for,和while循环iterator
  • Entry.getKey()或者Entry.getValue()

HashMap 的 7 种遍历方式与性能分析!(强烈推荐) - 腾讯云开发者社区-腾讯云 (tencent.com)

HashMap的特点底层机制

JDK1.8之前:哈希表(数组+单向链表);JDK1.8之后:哈希表(数组+单向链表+红黑树)

查询快,元素无序,key不允许重复但可以为null,value可以重复

当添加一个和之前key一样但是value不一样的值时,会把指向的value替换

K - V 最后是 HashMap$Node node = newNode(hash,key,value,null)

K - V 为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型Entry,Entry存放的就是key和value

即:transient Set<Map.Entry<K,V>> entrySet

可以通过Map.setEntry()生成Set进行遍历

然后可以通过entry对象调用getKey()或getValue()或Values()

LinkedHashMap实现类特点

JDK1.8之前:哈希表(数组+单向链表);JDK1.8之后:哈希表(数组+单向链表+红黑树)

查询快,元素有序,key不允许重复但可以为null,value可以重复

底层和LinkedHashset底层相同

Hashtable的特点

查询快,元素无序,key不允许重复并且不可以为null,value可以重复

存放的元素是键值对,即k-v

hasttable的键和值都不能为null

hashtable是线程安全的,hashMap是线程不安全的

继承了Dictionary类,实现了map接口

hashtable的键和值都不能为null,否则会抛出nullpointerException

hashtable使用方法基本上和hashMap一样

HashTable和Vector一样是古老的集合,有遗留缺陷,在JDK1.2之后 被更先进的集合取代了;HashTable是线程安全的,速度慢,HashMap是线程不安全的,速度快;

ps:Hashtable的子类Properties现在依然活跃,Properties集合是一个唯一和IO流结合的集合。

Properties

properties继承了hashtable类并且实现了map接口,也是使用一种键值对的形式来保存数据

properties的使用特点和hashtable类似

properties可以用于从xxx.properties文件中,加载数据到properties类对象。

properties也不允许空键和空值

如果添加了相同的key数据,会替换原key中的value

TreeMap的特点

查询快,元素有序,key不允许重复并且不可以为null,value可以重复。

可以给所有的node节点进行排序,默认也是按照字母排序

给构造器添加一个comparator,可以自定义排序规则

和TreeSet底层相类似

ConcurrentHashMap的特点

1669287471681

ConcurrentHashMap是线程安全的,和hashMap的底层是一样的,是数组+单项链表。

ConcurrentHashMap的性能要比HashMap更强,并且可以有多个线程同时执行修改ConcurrentHashMap.

ConcurrentHashMap有多个Segment组合而成,Segment本身就相当于一个HashMap对象。同HashMap一样,Segment包含一个HashEntry数组,数组中的每一个HashEntry既是一个键值对,也是一个链表的头节点.

Segment在ConcurrrentHashMap的数量是2的n次方个,共同保存在一个名为segments的数组当中。

segment是一个内部类,大致是这样的

1
2
3
4
5
6
7
8
9
static class Segment<K, V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
final float loadFactor;

Segment(float var1) {
this.loadFactor = var1;
}
}

1
2
ConcurrentHashMap要分为两种情况下来进行分析。一个是在jdk1.7,然后是在jdk1.8,他们之间的差别是非常大的。在jdk1.7它的底层是用数组加链表实现的。然后使用了一种分段锁来保证现场安全,它是将数组分成了16段,也就是给每个segment来配一把锁,然后再读每个segment的话就要获取对应的锁,他最多能有16个线程并发去操作。到了jdk1.8之后,他和HashMap一样也引入了红黑树这样的一种结构,同时在并发处理的方面不再使用分段锁的方式,而是采用cas加synchronize的关键字的这种方式来实现一种更加细粒度的锁,相当于是把这个锁的控制在了这种更加细微的hash桶的级别。在写入键值对的时候。这个可以锁住Hash桶的这种链表的头节点。就不会影响到其他的Hash桶的写入。

面试被问CAS和Synchronized的区别

深入浅出ConcurrentHashMap

开发使用总结

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:

1.先判断存储的类型(一组对象[单列]或一组键值对[双列])

2.一组对象:collection接口

允许重复 : list

增删多:LinkedList

改查多:ArrayList

不允许重复: set

无序:HashSet【底层是hashmap,维护了一个哈希表即数组+链表+红黑树】

排序:TreeSet

插入和取出顺序一致:linkedHashSet,维护数组+双向链表

3 一组键值对 map

键无序:hashMap【底层是 jdk7:数组+链表 jdk8:数组+链表+红黑树】

键排序:TreeMap

键插入和取出顺序一致:LinkedHashMap

读取文件 Properties

Java集合总结,详细且易懂

Collection工具类的使用

是一个操作set,list,map等集合的工具类

collection中提供了一系列静态的方法对集合元素进行排序,查询和修改等操作

排序操作均为static方法

  • Reverse(list) 反转list中元素的顺序

  • Shuffle(list) 对list集合元素进行随机排序

  • Sort(list) 根据元素的自然排序对指定list集合元素按升序排序

  • Sort(list,comparator)根据元素的自定义排序对只当list集合元素按升序排序

  • Swap(list,int,int)将指定list集合中的i处元素和j处元素进行交换

  • Max(collection)根据元素的自然顺序,返回给定集合中最大的元素

  • Max(collection,comparator)根据comparator指定的排序,返回给定集合中的最大元素

  • Min(collection)根据元素的自然顺序,返回给定集合中最小的元素

  • Min(Collection)根据comparator指定的排序,返回给定集合中最小元素

  • Int frequency(collection,object)返回指定集合中指定元素的出现次数

  • Void copy(list dest,list src) 将src中的内容复制到dest中

  • Boolean replaceAll(list list,object oldval,object newval):使用新值替换list对象的虽有旧值

泛型

泛型的介绍

泛型又称为参数化类型,是jdk5.0出现的新特性,解决数据类型的安全性问题

在类声明或实例化时只要指定好需要的具体类型即可

java泛型可以保证如果在编译时没有发出警告,运行时就不会产生ClassCastException异常,同时,代码更加简洁,健壮

泛型的作用是:可以在类声明时通过一个表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型

泛型的语法

  • 泛型的声明

    interface接口{} 和 Class类<K,V>{}

    说明:1.其中,T,K,V不代表值,而是表示任意一种类型 2.任意字母都可以,常用T表示,是Type的缩写

    • 泛型的实例化

      要在类名后面指定类型参数的值(类型)如:

      1
      2
      3
      List<String> strList = new ArrayList<String>();
      Iterator<Customer> iterator = customer.iterator();

泛型的注意事项和细节

1
2
3
4
5
6
7
8
9
10
11
12
13
interface List<T>{}
public class HashSet<E>{} //..等等
//说明T,E只能是引用类型
//看看下面语句是否正确
List<Integer> list = new ArrayList<Integer>();//对的
List<int> list2 = new ArrayList<int>();//不对
//在只当泛型的具体类型后,可以传入该类型或者其子类类型
//泛型使用格式
List<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<>();//类型推断
ArrayList arrayList = new ArrayList();
ArrayList<Object> arrayList = new ArrayList<>();//等价于

自定义泛型类

1
2
3
4
class 类名 <T,R..>{
成员
}

  1. 普通成员可以使用泛型(属性,方法)
  2. 使用泛型的数组,不能初始化
  3. 静态方法中不能使用类的泛型
  4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  5. 如果在创建对象时,没有指定类型,默认为object
1
2
3
4
5
6
7
8
9
10
11
12
13
class Tiger<T,R,M> {
String name;
R r;//属性使用泛型
M m;
T t;
//因为数组在new 不能确定T的类型,就无法在内存中开辟空间
T[] ts = new T[8];//错误
}
//因为静态和类相关的,在类加载时,对象还没有创建
//所以,如果静态方法和静态属性使用了泛型,jvm就无法初始化
static R r2;//报错
public static void m1(M m){}//报错

自定义泛型接口

1
2
3
4
iterface 接口名<T,R...>{

}

  1. 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)

  2. 泛型接口的类型,在继承接口或者实现接口时确定

  3. 没有指定类型,默认为object

    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
    iterface IUsb <U,R> {
    int n = 10;
    U name = "hsp";//不能这样使用
    //普通方法中,可以使用接口泛型
    R get(U u);
    void hi(R r);
    void run(R r1,R r2,U u1,U u2);
    //在jdk8中,可以在接口中,使用默认方法,也可以使用泛型
    default R method(U u){
    return null;
    }
    }
    //在继承接口指定泛型的接口类型
    iterface IA extends IUsb<String,Double>{

    }
    class AA implements IA{
    //需要实现IA和IA的父接口的所有方法
    }
    //实现接口时,直接只当泛型接口的类型
    Class BB implements IUsb<Integer,Float>{
    //给U指定了Integer,给R指定了FLoat
    }
    //没有指定类型,默认为Object
    class CC implements IUsb{//等价 class CC implements IUsb<Object,Object>{}

    }

自定义泛型方法

1
2
修饰符 <T,R...> 返回类型 方法名(参数列表){
}
  1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中

  2. 当泛型方法被调用时,类型会确定

  3. 当修饰符后面没有<T,R..>,那么方法不是泛型方法,而是使用了泛型

    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
    class Car{//普通类
    public void run(){//普通方法

    }
    //<T,R>就是泛型,提供给fly方法使用的
    public <T,R> void fly(T t,R r){//泛型方法
    System.out.println(t.getClass());
    System.out.println(r.getClass());
    }
    }
    class Fish<T,R>{//泛型类
    public void run(){//普通方法

    }
    public<U,M> void eat(U u,M m){//泛型方法

    }
    //hi方法不是泛型方法,而是方法使用了类定义声明的泛型
    public void hi(T t){

    }
    //泛型方法,可以使用类声明的泛型,也可以使用自己声明的泛型
    public<K> void hell(R r,K k){
    System.out.println(r.getClass());
    System.out.println(k.getClass());
    }
    }

    Car car = new Car();
    car.fly("baoma",100);//当调用方法时,传入参数,编译器就会确定类型
    //T -> String R -> ArrayList
    Fish<String,ArrayList> fish = new Fish<>();

    泛型的继承和通配符

    泛型不具备继承性

    :支持任意泛型类型 :支持A类以及A类的子类,规定了泛型的上限 :支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
    1
    2
    3
    Object o = new String("xx");
    //这样写会报错
    List<Object> list = new ArrayList<String>();