JavaSE重学

文章目录

JavaSE

第一章

java文件的运行

首先现有一个文件,java文件作为基础,通过java编译器和JVM进行转换,转为class文件,class文件交给JVM进行转化,转为机器码,并执行(这里的Java文件是源文件,class文件是字节码文件)

JVM

JVM是什么

JVM是一个虚拟机,java语言自带的虚拟计算机,面向编译器提供编程接口

JVM运行

JVM运行基于栈来行动,每个线程都有独立的栈用于保存运行的状态信息

Java虚拟机还支持多线程并发执行,这需要保证线程之间的同步和互斥

JVM的几个步骤
1、装载

把源文件转化为字节码文件

2、链接

就是把字节码文件中的符号引用(比如类、方法、变量等)转换为直接引用(即内存地址)

3、初始化

对类进行初始化,目的是给类的静态变量赋值,以及执行静态代码块的代码

4、执行

执行程序的主体部分

5、垃圾回收
有什么用

垃圾回收机制是一种动态存储管理技术,可以及时回收无用的内存,减轻编程压力,

怎么运行

垃圾回收机制可以准确的标记存活对象,同时定位对象之间的关系,这样就可以知道无用对象是哪些,对其进行清除

有哪些

有标记-清除算法、复制算法、标记-整理算法和分代回收算法。其中,复制算法和标记-整理算法比较常用。

第二章

理解面向对象

结构化设计
如何实现

结构化设计方法是自顶向下进行的,逐步分析,这一种设计方法是最小单位是函数,每个函数都对应一个功能,每个函数也都有输入和输出,输入是函数形参、全局变量等,输出就是传参,整个系统就此形成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UYS6kiQb-1683524877275)(C:\Users\卯末\AppData\Roaming\Typora\typora-user-images\image-20230429175326894.png)]

缺点

1、需要把每个功能都分解,与人思维不相同

2、可扩展力差,自顶向下的设计注定他有功能需要添加时,都要修改现有的实现方式,修改成本太高了

程序的三种基本结构
1、顺序结构

按照代码在源代码排列顺序依次执行

2、选择结构

有单选择、双选择、多选择三种,也就是if、if else、if elseif else结构

3、循环结构

有当型结构(while)、直到结构(do while),分别时先判断再执行、先执行再判断

什么是面向对象

面向对象是一种编程思想,将东西抽象为程序之中的类和对象,面向对象软件系统由多个类组成,(可以适当的解释三大特征),还有优点的说明

面向对象的几个特点
基本思想

使用类、对象、封装、继承、多态、抽象进行编程

基本特征

封装、继承、多态三大基本特征,抽象虽然在面向对象中十分重要,但它并不是基本特征之一

1、对象是面向对象的基本概念

2、类是具有共同属性、共同方法的一类事物,类是对象的抽象对象是类的实例

3、继承分为单继承和多继承,但是多继承在java中并不支持

4、多态是基于继承的,它使得同一个方法在不同的对象上可以表现出不同的行为

UML
UML是什么

UML是一种统一建模语言,在一定程度上,可以说是OOA、OOD、OOP组成

(OOA/D/P:面向对象分析、设计、编程)

UML主要包括的是图形符号和模型,也就是各种图

第三章

标识符和关键字

Java 语言的标识符必须是字母、下画线(_)、美元符($)开头

数据类型分类

分为基本类型和引用类型

基本类型

一共有8种,分别是(这里都是以 占据字节数|二进制位 来进行注解)

字节数 * 8 = 二进制位数

boolean(t1 / f0)、byte(1字节|8位)、short(2字节|16位)、int(4字节|32位)、long(8字节|64位)、float(4字节|32位)、double(8字节|64位)、char(2字节|16位Unicode字符)

引用类型

引用类型指的是对象的引用,用于指向对象在堆中的内存地址,而不是对象本身的值。Java中的引用类型包括类、接口、数组和枚举等。

运算符

分为:算数、赋值、比较、洛基、位、类型相关运算符

位运算符

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jlbn0nYc-1683524877276)(C:\Users\卯末\AppData\Roaming\Typora\typora-user-images\image-20230430105731312.png)]

左右移运算符

就是在原有的二进制码的基础上进行移动,左移就是去除前面,后面补0,右移相反

所以左移是乘以 2 的移动次方,右移是除

(注意:>>是补1,>>>是补0)

注意点

1、左右移动时,对于int以下的类型,总是需要自动转换成int再移位

2、当移位超过32时,需要对32进行求余再进行计算,所以会出现 a>>33 == a>>1的情况,而 a>>32 == a

按位或(|) 和 或(||)的区别

| 和 || 的区别,& 和 && 的区别:

前者总会计算两个操作数,而后者会先计算前面的数(此时如果符合直接结束),再查看后面的数

int a = 10;
int b = 10;
if (a > 9 | b++ > 10) {
    // 结果:10 + 11
    System.out.println(a + " + " + b);
}
int c = 10;
int d = 10;
if (c > 9 || d++ > 10) {
    // 结果:10 + 10
    System.out.println(c + " + " + d);
}

运算符的执行顺序

所有的数学运算都是从左到右,java大部分都是如此

例外的有:单目运算符(++,–)、三目运算符(?)、赋值运算符,这些都是从右到左

第四章

循环结构

循环结构控制
break 和 break outer

break只能退出当前循环,而break outer可以连外层也退出

continue 和 continue outer

continue outer可以跳出当前循环并继续执行外层循环

outer的使用

需要在外层声明一个(outer:),这样continue 和 break就可以对其进行操作

outer:
for (int i = 0; i < 10; i++) {
    for (int j = 10; j < 20; j++) {
        System.out.println(i + " + " + j);
        if (i == 0) {
            break outer;
        }
        System.out.println(i + " + " + j);
    }
}

数组

动态初始化
int len = 10;
int[] ints = new int[len];

动态初始化一个数组之后,会根据类型指定的长度给每一个数组元素分配内存空间,系统会给这些数组辅以初始值

而这个初始值是什么,则是根据数组元素类型

初始值

整数型:0

浮点型:0.0

字符型:‘\u0000’

布尔型:false

引用型:null

实际数组使用

实际使用数组对象,会先在堆内存中创建,之后方法使用到这个数组时(此时他作为一个局部变量),会调用它的内存地址,放在栈中

代码讲解
public static void main(String[] args) {
    int[] ints = new int[5];
}

最开始,根据顺序结构,从左到右

int[] ints 是先创建一个空引用,放在栈内存之中,此时堆中并没有任何的数据

new int[5] 开始动态初始化,这才是创建对象放在堆内存之中

image-20230430215740804
关于多维数组

其实可以看作是一维数组,只是这个一维数组之中,存放的是引用类型,也就是另一个一维数组

即多维数组其实就是在一维数组的基础上,添加了多个指向其他一维数组的指针

image-20230430220258806

堆和栈

堆和栈的不同特点

堆和栈都是用于存储内存中的数据结构,但两个的使用方式和存储对象不同

堆特点

堆内存用于存储Java对象,创建一个新的对象之后,jvm会在堆中分配一段连续的内存空间,并且返回这个对象在堆中的地址

栈特点

栈内存用于存储方法调用使用的数据和临时变量,每调用一个方法,就会创建一个栈帧,将方法的各种参数和变量放在里面,随着方法的执行结束,栈帧销毁,栈内存也就随之释放

第五章

面向对象(上)

修饰符
static

static可修饰方法、成员变量等

当修饰成员时,说明这个成员属于这个类本身,而不是这个类的单个实例

所以称这些成员是:类变量、类方法

static和this的冲突

。。。

方法的传参
代码
public static void swap(DataWrap dw) {
    int tmp = dw.a;
    dw.a = dw.b;
    dw.b = tmp;
    System.out.println(dw.a + " + " + dw.b); // 9 + 6
}

// 这里是共同指向这个对象
public static void main(String[] args) {
    DataWrap dw = new DataWrap();
    dw.a = 6;
    dw.b = 9;
    swap(dw);
    System.out.println(dw.a + " + " + dw.b); // 9 + 6
}

这里dw在堆中开辟了一个新的内存空间,有两个栈帧使用这个dw,出现两个引用

这两个引用对应同一个对象,进行修改时,会一起修改,即使是在不同栈帧中

参数
实参和形参的区别

实参:是在方法或函数调用中传递给方法或函数的值或对象的引用,可以传递到方法的形参中

// 这里只是普通局部变量
DataWrap dw = new DataWrap();
dw.a = 6;
dw.b = 9;
// 这里swap(dw)中dw才是实参
swap(dw);

形参:是指在方法或函数声明中用来接收传递进来的参数值的变量

// 传进来的dw就是形参
public static void swap(DataWrap dw) {
    int tmp = dw.a;
    dw.a = dw.b;
    dw.b = tmp;
    System.out.println(dw.a + " + " + dw.b);
}
方法重载
重载和重写的区别

重载是同一个类中的两个名字相同的方法之间进行

重写则是不同类之中方法

成员变量和局部变量

在这里插入图片描述

成员变量

会随着调用而改变

static int i = 0;

public static void main(String[] args) {
    a();
    System.out.println(i); // 1
}

public static void a() {
    i = 1;
    System.out.println("123: " + i); // 123: 1
}
实例变量和类变量

实例变量和类变量的生存时间不同,实例变量是从这个类的实例建立开始存在,而类变量则是这个类的准备阶段就有了

两者的结束时间也不同,实例变量是到这个实例完全销毁,而类变量是到系统完全销毁

public class Car {
    
    // 类变量,表示汽车的总数
    public static int count = 0;
    // 实例变量,表示汽车的速度
    public int speed;

    // 构造函数,每次创建新对象时将汽车总数加1
    public Car(int speed) {
        this.speed = speed;
        count++;
    }

    // 加速方法,修改实例变量speed的值
    public void accelerate(int increment) {
        this.speed += increment;
    }

    // 打印汽车状态,包括总数和速度
    public void printStatus() {
        System.out.println("汽车总数:" + count);
        System.out.println("当前速度:" + speed);
    }
}

上面这段代码里,类变量从类加载就准备好了,而实例变量还得通过赋值才能体现(需要在对象被创建后才会分配内存并进行初始化)

实例共享类变量

类变量是与类相关联的变量,所有类的实例共享同一个类变量,而实例变量则是与类的实例相关联的变量,每个类的实例都有自己的实例变量。

下面代码可以说明两个对象共享一个count

这里输出不了MyClass.number

public class MyClass {
    public static int count = 0;
    public int number;

    public MyClass(int number) {
        this.number = number;
        count++;
    }
}

// 在另一个类中使用 MyClass 类
public class Main {
    public static void main(String[] args) {
        MyClass obj1 = new MyClass(1);
        MyClass obj2 = new MyClass(2);

        System.out.println("obj1.number = " + obj1.number); // 输出 obj1.number = 1
        System.out.println("obj2.number = " + obj2.number); // 输出 obj2.number = 2

        System.out.println("MyClass.count = " + obj1.count); // 输出 obj1.count = 2
        System.out.println("MyClass.count = " + obj2.count); // 输出 obj2.count = 2
        
        System.out.println("MyClass.count = " + MyClass.count); // 输出 MyClass.count = 2
    }
}
隐藏和封装
封装

把对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,必须通过这个类提供的方法进行访问

访问控制符

3大访问控制符:private、protected、public

4个访问控制级别:

在这里插入图片描述

default:是包访问权限,当不适用任何的访问控制符,即默认此级别

访问控制界别表

在这里插入图片描述

继承

通过extends关键字实现

Java的继承机制

Java中每个子类都只能有一个直接父类,单继承模式

但是子类可以有多层父类,也就是间接父类,多层继承模式

重写机制

java继承之后的方法重写,需要遵循 “两同两小一大”

两同:方法名相同,形参列表相同

两小:子类方法返回值类型应该比父类方法返回值类型更小或者相等,抛出的异常也应该是更小或相同(这里的小,是指 animal 和 dog这种对象之间的小,而不是 int 和 byte 之间的)

一大:子类访问权限应该比父类大或者相等,子类方法的访问控制符必须比父类方法更松散,父类(protected)子类(protected || public)

(注意:两个方法要么都是类方法、要么都是实例方法,不能说不同)

权限不同的继承(或者说不是继承)

下面父类方法权限是private,而子类的是public,子类访问不了父类的这个方法,所以不是继承,加上static也没事

class Father {
    private void test() {...}
}
class Son extends Father {
    public static void test() {}
}
构造方法

子类的构造方法一定会调用父类的构造方法,这是因为子类继承了父类的所有属性和方法,需要先初始化父类的成员变量和方法,才能保证子类的正确初始化。

子类的构造方法中,隐藏了一个super(),所以会调用到

继承和组合的区别

继承是静态的,编译的时候就确定最终形态; 组合是动态的,可以在运行时改变所包含的对象

继承耦合度太高,子类和父类紧密关联; 组合耦合度小得多

组合例子:(组合指的是将一个类的对象作为另一个类的成员变量)

public class Car {
    private Engine engine;

    public Car() { 
        // 这就是组合
        engine = new Engine();
    }

    public void start() {
        engine.start();
    }
}

public class Engine {
    public void start() {
        System.out.println("Engine started");
    }
}
多态
多态类型

多态实际上是有两个类型,编译类型和运行类型

这里编译类型是Father,运行类型是Son

Father father = new Son();
多态性

这里的输出:

Father
Son
Son's test
0

因为方法有多态性,而成员变量没有

所以方法会执行对象的方法,而成员变量则是引用的值

(注:引用在编译阶段智能调用编译时类型所具有的方法,调用方法只能是编译类型的方法)

public static void main(String[] args) {
    // 向上转型
    Father father = new Son();
    father.test();
    System.out.println(father.i);
    // 这里son的test1方法执行不了
    // father.test1();
}

static class Father {
    int i = 0;
    public Father() {
        System.out.println("Father");
    }

    public void test() {
        System.out.println("Father's test");
    }
}

static class Son extends Father {
    int i = 1;
    public Son() {
        System.out.println("Son");
    }

    public void test() {
        System.out.println("Son's test");
    }
    
    public void test1() {
        System.out.println("Son's test1");
    }
}
强制类型转换
向上转型和向下转型的判断

以对象为考虑点,查看引用是比对象大还是小,大就是向上,反之向下

// 向上转型,因为long范围比int大
int d = 0;
long l = (long)d;
// 向下转型,虽然long和double都是8字节,但是double有小数
double d = 0.0;
long l = (long)d;

第六章

处理对象

== 和 equals 方法

== 当两个基础类型对比时,对比的是值,如果是引用类型,对比的为:是否指向同一个对象(即是否引用同一块内存地址)

equals比较的是对象逻辑相等性(不是内存地址,而是对象内容)

// s1 + s2 会创建一个新的字符串对象,值为 ”12“
// ”1“ + ”2“ 所创建的是另一个值为 ”12“ 的对象,所以会有所不同
public static void main(String[] args) {
    String s1 = "1";
    String s2 = "2";

    String s3 = "1" + "2";
    String s4 = "12";
    System.out.println(s3 == s4); // true
    System.out.println(s3 == (s1 + s2)); // false
}
equals 和 hashCode 方法

重写 equals 方法的同时,应该重写 hashCode 方法

为什么?

因为如果重写了 equals,但没重写 hashCode,那么相等的对象有可能会产生不同的哈希码,这将导致对象无法正确存储或查找

类成员

类成员种类

成员变量、方法、构造器、初始化块、内部类(包括接口、枚举)

单例类

单例类是指只允许创建一个对象实例的类

单例类的要求

1、私有化的构造器,只有做到构造器私有化,才不会给别人随便创建对象,想要创建对象必须根据唯一public入口创建

2、成员变量私有化,保证唯一性

3、唯一公有化的入口

public class Singleton {
    private Singleton() {}
    
    private static Singleton instance;

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

final修饰符

final修饰符注意事项

初始化时必须附上初始值,这里可以是在代码块里完成,也可以在构造函数里完成,但一旦初始化就必须附上初始值

(**问:**final修饰的变量不能被赋值,这句话对吗?

错误,严格来说,是final修饰的变量不能被改变,不能被重新赋值)

代码块

代码块分为静态代码块和非静态代码块

静态代码块:在类加载的时候执行

​ (类加载不等于初始化,类加载的过程由 JVM 的类加载器完成,初始化则是类加载的最后一个阶段)

非静态代码块:在构建对象时执行

final成员变量的初始化

Java不允许直接访问没赋值的final修饰的成员变量,但间接是没问题的

public class ContinueTest {
    final int age;
    {
        System.out.println(age); // 没赋值就直接访问了,报错
        printAge(); // 没赋值,但是间接访问,可以
        age = 8;
        System.out.println(age);
    }
    public void printAge() {
        System.out.println(age);
    }
    public static void main(String[] args) {
        new ContinueTest();
    }
}
final的作用目标

这里因为final的作用目标是引用,而不是对象,所以改变有效

static class Person {
    int age;
    public Person(int age) {
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

public static void main(String[] args) {
    final Person person = new Person(1);
    System.out.println(person.getAge()); // 1

    person.setAge(10);
    System.out.println(person.getAge()); // 10
}
final的使用场所

final经常作为 “宏变量” 使用,比如一个类的内部类中经常要使用到 “hhx” 作为名字,但是每个类都什么一个局部变量有点麻烦,此时就可以声明一个宏变量a,用来储存 ’‘hhx“ ,不能够在内部类中更改,随时可以使用、更改

final类无法被继承

当一个类由final标签修饰时,说明它的实现是最终的,无法给其他类修改,如果允许继承的话,就是允许修改这个类的行为,是错误的

抽象类

抽象类的特性

1、抽象类由abstract修饰

2、不能实例化,即使没有抽象方法也不能够实例化

3、可以含有成员变量、构造器、初始化块、内部类

4、可以有抽象方法(直接定义;继承抽象父类;实现接口但没完全实现)

抽象方法和final的区别

当使用 abstract 修饰类时,表明这个类只能被继承;当使用 abstract 修饰方法时,表明这个方法必须由子类提供实现(即重写)

final 修饰的类不能被继承 ,也不能被重写, 因此 final 和 abstract 永远不能同时使用

抽象方法和static

static 和 abstract不能同时修饰某一个方法,因为static必须在当前类实现,而abstract则是传给子类去实现

模板模式

抽象类经常使用

在模板模式中,一个抽象类公开定义了一个算法的模板(即一组固定的步骤和操作),具体步骤的实现推迟到子类中完成。

修饰符

修饰符可以是public 、private、protected 和 default

接口

接口修饰符

接口的修饰符可以是public 和 default,不能使用private 和 protected

接口中的变量

此变量默认是public、 static 和 final修饰

接口方法

接口方法只能是抽象方法、类方法、默认方法和私有方法,

如果不是类方法、默认方法和私有方法,系统会自动给普通方法添加 abstract 修饰符(普通方法只能是 public)

实现

普通方法不能实现

类方法、默认方法、私有方法必须有方法实现

代码演示
public interface TestInterface {
    
    int i = 0; // 默认加上 public static final 

    public static void a() {
        System.out.println("类方法 a 实现");
    }

    default void b() {
        System.out.println("默认方法 b 实现");
    }

    private void c() { // 在java9之后是可以有私有方法的
        System.out.println("私有方法 c 实现");
    }

    void d(); // 默认变成抽象方法,不能实现
}
接口继承
继承种类

接口继承和类继承不一样,接口继承可以有多继承

即:一个接口继承多个父类接口,同样可以有多个子类接口

继承了什么

可以获取父类接口的常量(成员变量)、方法(抽象方法和默认方法)

接口实现
问:一个类可以实现多个接口吗

答案是可以

实现类从接口获取到什么

和继承一样,可以获取父类接口的常量(成员变量)、方法(抽象方法和默认方法)

接口实现的奇特使用方法
Animal animal = new Cat();
animal.eat();
System.out.println(animal.i == Animal.i && Animal.i == Cat.i); // true

接口和抽象类的区别

相同点

1、都不能被实例化

2、都可以有抽象方法

不同点

1、接口只能包含抽象方法、静态方法、默认方法和私有方法,不能给普通方法提供实现,但抽象类是可以的

2、接口只能定义静态常量,不能定义普通成员变量;抽象类则是都可以

3、接口没有构造器;抽象类有构造器(这个构造器不是用来创建对象的,而是让子类调用这些构造器来实现抽象类的初始化工作)

4、接口不能有初始化块,而抽象类可以有

5、继承机制不同

内部类

内部类可以使用内部类外面的成员变量

内部可以访问外部的private

但是反过来就不行了

普通类静态内部类

静态内部类只能使用外部类的静态成员,即使是使用静态内部类实例方法也是不行的

public class test {
    static int aaa = 0;
    int bbb = 0;
    static class a {
        public void in() {
            System.out.println(aaa);
            System.out.println(bbb); // 报错
            System.out.println(test.bbb); // 报错
            System.out.println(new test().bbb);
        }
    }
}https://free.churchless.tech/v1/chat/completions 
接口中的内部接口

接口内部也是可以有内部接口的,默认是public 和 static,因为它们只能是静态的

局部内部类

如果是在方法里面创建内部类,那么就是局部内部类

局部内部类的成员不能使用static,因为它们的上一级就是方法,使用static并没有意义

枚举类

组成

有自己的成员变量、方法、可以实现一个或者多个接口、有自己的构造器

在类中的限制

一个java源文件只能有一个public访问权限的枚举类,而且源文件和枚举类的类名必须相同

枚举类和普通类的区别

1、枚举类默认继承 java .lang .Enum类,而普通类默认继承的 java .lang .Object类

2、使用enum定义、非抽象的枚举类默认使用final修饰,因此不能派生出子类

3、构造器只能是使用private来修饰

4、所有的实例都应该在第一行显式列出,会自动添加public static final

5、枚举类本身并不能被实例化,一般使用枚举常量代表枚举类实例

6、成员变量通常被设置为 private final

枚举类使用示例
public enum EnumTest {
    MonDay("xqy", 11),
    TuesDay("xqe", 22);

    private final String day;
    private final int i;

    EnumTest(String day, int i) {
        this.day = day;
        this.i = i;
    }
}


public class test1 {
    public static void main(String[] args) {
        test.EnumTest enumTest = test.EnumTest.valueOf("MonDay");
        System.out.println(enumTest.day); // xqy
        System.out.println(enumTest.i); // 11
        
        test.EnumTest enumTest1 = test.EnumTest.valueOf("TuesDay");
        System.out.println(enumTest1.day); // xqe
        System.out.println(enumTest1.i); // 22
        
        System.out.println(EnumTest.EnumTest1.MonDay.day); // xqy
    }
}
枚举类实现接口

枚举类可以像普通类一样实现接口,但有所不同

枚举类可以根据不同的枚举值,提供不同的实现方式,下面是例子:

public interface EnumInterface {
    public void info();
}

public enum EnumTest1 implements EnumInterface {

    // 不同的枚举类实现方式不同
    MonDay("xqy", 11) {
        public void info() {
            System.out.println("xqyInfo");
        }
    },
    TuesDay("xqe", 22) {
        public void info() {
            System.out.println("xqeInfo");
        }
    };

    String day;
    int i;

    EnumTest1(String day, int i) {
        this.day = day;
        this.i = i;
    }

    public String getDay() {
        return day;
    }

    public int getI() {
        return i;
    }
}

对象和垃圾回收

对象在垃圾回收的状态
1、可达状态

当对象创建之后,只要有一个引用变量引用它

2、可恢复状态

对象不再有任何变量引用它,这时候是可恢复状态

系统在这种情况下,会调用 finalize() 方法进行资源清理,如果该方法使得这个对象重新被引用,那么就会变成可达状态,反之则为不可达状态

3、不可达状态

在可恢复状态下,调用 finalize() 方法之后仍然没有变成可达状态,那么此时就真正是不可达状态,系统将会回收它所占的资源

强制回收机制
System.gc
Runtime.getRuntime().gc();

一般用途不大,在大多数情况下,使用 System.gc() 并不能显著提高应用程序的性能,甚至可能会降低性能

jvm的自适应垃圾回收机制一般都能解决问题

finalize方法

用于在对象被垃圾回收之前进行一些清理工作,例如关闭文件、释放资源等。

当系统觉得需要更多的额外空间时,才会调用垃圾回收机制,没有使用回收机制,那么finalize方法自然也就不会被调用

关于引用

java的引用分为四种:

1、强引用(StrongReference)

平常使用的就是强引用,只有当一个对象没有任何强引用指向它时,才会被判定为垃圾并被回收。

Object o = new Object();
2、软引用(SoftReference)

只有软引用时,当系统内存充足,不会去回收它,但是如果不足,就可能会回收

Object o = new Object();
SoftReference softReference = new SoftReference(o);
3、弱引用(WeakReference)

和软引用差不多,但是优先级会更低,不管内存是否充足,系统都可能会回收它

Object o = new Object();
WeakReference weakReference = new WeakReference(o);
4、虚引用(PhantomReference)

类似于没有引用,对象甚至时感受不到虚引用的存在,当一个对象只有虚引用存在时,无法通过该引用获取到对象实例

虚引用需要结合队列进行使用

第七章

Java基础库

主函数为什么是psvm
public static void main(String[] args) {}
1、public

使用public是为了让JVm更自由的调用这个main() 方法,让这个方法暴露出来

2、static

JVM调用这个主方法时,不会先创建主类的对象,而是直接通过这个类来调用主方法

3、void

因为是给JVm调用的,返回值没有任何意义

4、main

通用的主方法名字罢了

5、String[] args

JVM调用这个方法,那么JVM就应该传入形参

args里面存储了java命令行参数,使用Java命令来启动Java虚拟机并加载指定的类

比如有这么一个java命令:java Test value1 value2,此时 args[0] 存储了命令,之后都是参数

系统相关
System类

提供了许多和系统相关的方法,包括标准输入输出、错误输出、获取系统属性、系统环境变量、获取当前时间等等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rJe8TaNC-1683524877279)(C:\Users\卯末\AppData\Roaming\Typora\typora-user-images\image-20230504215803593.png)]

Runtime类

表示当前应用程序的运行时环境

public static void main(String[] args) throws IOException {
    Runtime runtime = Runtime.getRuntime();
    System.out.println(runtime.availableProcessors()); // 返回当前系统可用的处理器数目
    System.out.println(runtime.totalMemory()); // 返回Java虚拟机中的内存总量
    System.out.println(runtime.freeMemory()); // 返回Java虚拟机中的空闲内存量
    String comand = "ls";
    Process process = runtime.exec(comand); // 执行系统命令
}
常用类
Object类
hashCode()

每个对象都会有默认的hashCode方法,会返回一个哈希码(int)

(哈希码是根据对象的内存地址或其他特定的属性计算得出的一个整数值,用于快速确定对象在哈希表中的索引)

哈希码可以用于判断两个对象(在同一个哈希表)是否相同,哈希表是根据哈希码来决定对象的存储位置的

String、StringBuffer、StringBuilder类区别

1、String是不可变类,每次修改都会产生新对象,

​ 而其他两个是可变量,在原有的对象基础上进行修改,StringBuilder 性能优于 StringBuffer

2、在多线程环境下,StringBuffer 是线程安全,而StringBuilder 是非线程安全(不能保证对象在被多个线程同时访问时的正确性),性 能会高一点

3、String是final类,不可被继承,而 StringBuffer 和 StringBuilder 都是可以被继承的

所以我们创建一个内容可变的字符串时(单线程),一般使用 StringBulider,多线程则是 StringBuffer

ThreadLocalRandom 和 Random区别

1、ThreadLocalRandom 线程会比较安全,可多个线程并发使用

2、运行方法不同(XORShift 算法 和 线性同余算法)

3、ThreadLocalRandom 生成随机数更加高效、快速

(注:两个都是左闭右开)

public static void main(String[] args) throws IOException {
    ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
    System.out.println(threadLocalRandom.nextInt(1,10)); // 输入开头和结尾

    Random random = new Random();
    System.out.println(random.nextInt(9) + 1); // 只需要输入结尾,开头默认是 0
}
日期类
Date类

历史很悠久的类,很多方法都过时了

Calendar类

能够弥补Date类的缺陷,但它是一个抽象类,是所有日历类的模板

add

向前或者先后推日期都可以,而且自适应年月

public static void main(String[] args) throws IOException {
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime()); // Thu May 04 23:17:26 CST 2023
    calendar.add(Calendar.MONTH,1);
    System.out.println(calendar.getTime()); // Sun Jun 04 23:17:26 CST 2023
}
roll

和add不同,不会自动处理年月的增长或者减少,需要手动改

public static void main(String[] args) throws IOException {
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime()); // Thu May 04 23:23:59 CST 2023

    calendar.roll(Calendar.DATE, 32);
    System.out.println(calendar.getTime()); // Fri May 05 23:23:59 CST 2023
}
set

延迟修改的特性,尽管设置了之后字段立刻修改,但是Calendar 所代表的时间会在下次get() 、add()、roll()…的时候才改动

public static void main(String[] args) throws IOException {
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime()); // Thu May 04 23:26:23 CST 2023

    calendar.set(2023, 6, 10);
    System.out.println(calendar.getTime()); // Mon Jul 10 23:26:23 CST 2023
}
工具类
Format类

是个抽象类,提供了一些方法来将不同类型的数据格式化成字符串,或将字符串解析为对应的数据类型

DateFormat类

抽象类,常用子类有:(下面两个都不是抽象类)

1、SimpleDateFormat

格式的转化,Date 和 String 之间

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = format.format(new Date()); // 2023-05-04 23:49:06

String dateString = "2022-05-05 14:30:00";
Date date = format.parse(dateString);
2、DateTimeFormatter

也同样是转化,但比 SimpleDateFormat 更加方便

// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();

// 创建一个 DateTimeFormatter 实例,指定日期时间格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// 将当前日期时间格式化为指定格式的字符串
String formattedDateTime = now.format(formatter);

// 输出格式化后的日期时间字符串
System.out.println(formattedDateTime); // 2023-05-04 23:52:48
NumberFormat类

格式化数字

// 创建一个 NumberFormat 实例,指定数字格式
NumberFormat formatter = NumberFormat.getInstance();

// 将数字格式化为指定格式的字符串
String formattedNumber = formatter.format(12345.67);

// 输出格式化后的字符串
System.out.println(formattedNumber); // 12,345.67
NumberFormat类 和 DateFormat类的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uyCshf5F-1683524877280)(C:\Users\卯末\AppData\Roaming\Typora\typora-user-images\image-20230504233610171.png)]

MessageFormat类

可以生成动态文本

public static void main(String[] args) throws IOException {
    String pattern = "Hello, {0}! Today is {1}.";

    String name = "Alice";
    Date date = new Date();
    String message = MessageFormat.format(pattern, name, date);

    System.out.println(message); // Hello, Alice! Today is 23-5-4 下午11:41.
}

第八章

集合

Java的集合类主要是由Collection 和 Map派生出来的

Java集合可以分为三大类:Set、Map、List

Collection接口下的接口

Set、List、Queue

Map接口下的类

HashMap、Hashtable、TreeMap

Iterator接口

主要是用于遍历,依赖于实现 Iterable 接口的集合类(List、Set、Queue等)

每个实现了 Collection 接口的类都会有一个 iterator() 方法,会返回一个 Iterator 对象

Collection collection = new ArrayList();
collection.add("apple");
collection.add("banana");
collection.add("orange");

Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    System.out.println(fruit);
}
Stream类 和 stream() 方法

Stream是一个流式API(一组接口的集合,用于实现特定的功能或提供特定的服务)

而 stream() 方法是 Collection 接口定义的一个方法,把一个集合转化为 Stream 对象,之后使用 Stream 类的方法进行流式操作

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
    List<Integer> evenNumbers = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());
}

这里 filter 是为了过滤掉不符合 n % 2 == 0 的数字,之后使用 collect 收集成为 list

Set集合
HashSet

不能保证排列的顺序

value可以是null

HashSet不是同步的,如果多个线程访问一个hashset,需要通过代码来保证同步

HashSet的存储逻辑

会调用对象的 hashCode() 方法,获取 hashCode值,根据这个值来决定对象在 HashSet 的存储位置

HashSet 集合判断两个元素是否相等的标准是:equals() 方法相等、hashCode() 返回值也相等

LinkedHashSet

和 HashSet 大概相同,但他使用了链表哈希表,可以保证元素的次序(插入顺序),性能会略低于 HashSet

TreeSet

是 SortedSet 接口的实现类,也可以保证排序状态,但这个排序是根据元素实际值的大小来排序的,使用的是红黑树来存储集合元素

相对的,红黑树维护成本较高,所以速度会慢于前面两个

TreeSet有两种排序方式,一种是自然排序(默认),另外一种是定制排序

自然排序

通过compareTo(Object obj) 方法来比较元素大小关系,将集合元素升序排列

这个方法使用的时候,对比的两者应该是类型相同的对象

obj1.compareTo(obj2) 的值,根据前后两者的大小来决定,如果1>2,返回的是正整数,反之是负整数

定制排序

通过我们自己的需求,定义元素之间的比较规则

EnumSet
特性

其中所有的元素都是指定枚举类型的枚举值,且对于每个枚举类型值都只会存储一次

其中的集合元素也是有序的,以Enum类中定义的顺序来决定元素顺序

通过位向量来实现,所以EnumSet对象占用小、效率高

位向量

位向量(bit vector)是一种非常紧凑的数据结构,用于表示一个由二进制位组成的向量,其中每个二进制位只能是 0 或 1

位向量可以用来表示一个集合,其中向量的每个位置表示集合中的一个元素,如果该位置为 1,则表示集合中包含该元素,否则表示集合中不包含该元素。

使用

EnumSet没有暴露任何的构造器来创建该类的实例,我们只能通过类方法来创建它的对象

public class ColorExample {
    public enum Color { RED, GREEN, BLUE, YELLOW, BLACK, WHITE };

    public static void main(String[] args) {
        // 创建一个包含所有颜色的 EnumSet
        EnumSet<Color> allColors = EnumSet.allOf(Color.class);
        System.out.println("All colors: " + allColors);

        // 创建一个空的 EnumSet
        EnumSet<Color> noColors = EnumSet.noneOf(Color.class);
        System.out.println("No colors: " + noColors);

        // 创建一个只包含 RED 和 BLUE 的 EnumSet
        EnumSet<Color> someColors = EnumSet.of(Color.RED, Color.BLUE);
        System.out.println("Some colors: " + someColors);

        // 创建一个包含 RED、GREEN 和 BLUE 的 EnumSet
        EnumSet<Color> rangeColors = EnumSet.range(Color.RED, Color.BLUE);
        System.out.println("Range colors: " + rangeColors);
    }
}

结果:
All colors: [RED, GREEN, BLUE, YELLOW, BLACK, WHITE]
No colors: []
Some colors: [RED, BLUE]
Range colors: [RED, GREEN, BLUE]
各个Set实现类的性能分析
性能

HashSet 和 TreeSet是两种典型,HashSet性能会比TreeSet高,红黑树的原因

LinkedHashSet则是略慢于HashSet,多了一个链表来整理顺序

EnumSet是性能最好的

有顺序

除了HashSet,L是插入顺序、T是大小顺序、E是枚举顺序

线程

H、T、E都是线程不安全,L安全

可以使用 synchronizedSortedSet(Collections类中)包装这个Set类,保证线程安全

List集合
listIterator() 方法

和 set 集合只提供一个 Iterator 方法不同,list 额外提供了该接口,提供了专门操作 List 的方法

调用这个方法,会返回一个 listIterator 对象

与 Iterator 的不同

该方法不仅仅能对后迭代,还能对前迭代

能够提供 add 方法,而 Iterator 只能是删除元素

ArrayList 和 Vector 实现类

是 List 类的典型实现,Vector 是一个比较古老的集合,最开始并不在 List 类下,后面改过来的,所以和 List 会有一些重复功能

实际上,Vector 的缺点很多,尽量少用

ArrayList 是基于动态数组实现的,当数组容量不够的时候,就会重新分配一个数组来存储

Vector 也是基于数组

线程

ArrayList 是线程不安全的

Vector 是线程安全的,因为线程是安全的,所以性能会低一点

(其实后面会提供一个 Collections 工具类,可以使得 ArrayList 变得安全,所以还是尽量不使用 Vector)

LinkedList实现类

它不仅仅实现了 List 接口,还实现了 Deque 接口,作为双端队列使用

它和 ArrayList、ArrayDeque 的底层不一样,它们用的是数组,而 LinkedList 是使用链表

ArrayList 和 LinkedList

线程都是不安全的

ArrayList 的性能比 LinkedList 性能会好一点

List 集合建议

1、对于 ArrayList、Vector,随机访问方法(get)来遍历会好一点,而 LinkedList 则是采用 Iterator 来遍历好一点

2、如果经常使用插入元素、删除元素来改变 List 集合的大小,使用 LinkedList 集合好一点,A 和 V需要经常重新分配数组大小

3、多个线程访问 List 集合时,开发可以使用 Collections 包装保证线程安全

Queue集合
PriorityQueue实现类

PriorityQueue元素的顺序不是按加入队列的顺序,而是按照队列元素大小进行排序

当使用 peek 、poll时,弹出来的是最小的元素

PriorityQueue 不允许插入 null 元素

非线程安全

排序

有两种排序,分别位自然排序、定制排序

这一点和 TreeSet 差不多

Deque接口

Queue接口的子接口,代表了一个双端队列,允许从两端都进行插入或者弹出

ArrayDeque实现类

基于数组实现的双端队列,和ArrayList一样,底层是基于数组完成

既可以作为栈来使用,也可以是作为队列来使用

线程不安全

Map集合
Map特性

key 不允许重复,key 集和 Set 的存储形式完全相同

所有的 Map 都重写了 toString()

HashMap 和 Hashtable

他们的关系类似于ArrayList 和 Vector,Hashtable 同样时古老的类,线程安全

值得注意的是:HashMap 允许 null 作为 key 和 value,而 Hashtable 不行,因为 key 不能重复,所以 HashMap 中只有一个 key 为 null

他们的存储逻辑和 HashSet 差不多,也不能保证数据,也必须要实现 equals 和 hasCode

LinkedHashMap

有顺序,使用双向链表来维持元素的插入顺序,所以性能略低

线程安全

Properties读写属性文件

是Hashtable 的子类,就是平常使用的配置文件,同样是key 和 value结构

SortedMap接口和TreeMap类

和TreeSet类似,同样有红黑树

元素值大小排序(自然排序)、定制排序

(注意:这个排序是按照 key 来进行排序)

WeakHashMap

他和HashMap 基本相同,但是 HashMap 使用的是强引用,而 WeakHashMap 则是弱引用

这意味着,HashMap不被摧毁,那么这个 HashMap 的所有 key 所引用的对象就不会给回收

反之,WeakHashMap 随时可能被回收

IdentityHashMap

和HashMap也基本相同,不同的是,如何处理key值是否相同

I:key1 == key2,这代表必须严格相同

H:equals 和 hashCode 是否同时为 true

EnumMap

这和 EnumSet差不多,这里是key 必须为枚举值,value没有要求
ector)

LinkedList实现类

它不仅仅实现了 List 接口,还实现了 Deque 接口,作为双端队列使用

它和 ArrayList、ArrayDeque 的底层不一样,它们用的是数组,而 LinkedList 是使用链表

ArrayList 和 LinkedList

线程都是不安全的

ArrayList 的性能比 LinkedList 性能会好一点

List 集合建议

1、对于 ArrayList、Vector,随机访问方法(get)来遍历会好一点,而 LinkedList 则是采用 Iterator 来遍历好一点

2、如果经常使用插入元素、删除元素来改变 List 集合的大小,使用 LinkedList 集合好一点,A 和 V需要经常重新分配数组大小

3、多个线程访问 List 集合时,开发可以使用 Collections 包装保证线程安全

Queue集合
PriorityQueue实现类

PriorityQueue元素的顺序不是按加入队列的顺序,而是按照队列元素大小进行排序

当使用 peek 、poll时,弹出来的是最小的元素

PriorityQueue 不允许插入 null 元素

非线程安全

排序

有两种排序,分别位自然排序、定制排序

这一点和 TreeSet 差不多

Deque接口

Queue接口的子接口,代表了一个双端队列,允许从两端都进行插入或者弹出

ArrayDeque实现类

基于数组实现的双端队列,和ArrayList一样,底层是基于数组完成

既可以作为栈来使用,也可以是作为队列来使用

线程不安全

Map集合
Map特性

key 不允许重复,key 集和 Set 的存储形式完全相同

所有的 Map 都重写了 toString()

HashMap 和 Hashtable

他们的关系类似于ArrayList 和 Vector,Hashtable 同样时古老的类,线程安全

值得注意的是:HashMap 允许 null 作为 key 和 value,而 Hashtable 不行,因为 key 不能重复,所以 HashMap 中只有一个 key 为 null

他们的存储逻辑和 HashSet 差不多,也不能保证数据,也必须要实现 equals 和 hasCode

LinkedHashMap

有顺序,使用双向链表来维持元素的插入顺序,所以性能略低

线程安全

Properties读写属性文件

是Hashtable 的子类,就是平常使用的配置文件,同样是key 和 value结构

SortedMap接口和TreeMap类

和TreeSet类似,同样有红黑树

元素值大小排序(自然排序)、定制排序

(注意:这个排序是按照 key 来进行排序)

WeakHashMap

他和HashMap 基本相同,但是 HashMap 使用的是强引用,而 WeakHashMap 则是弱引用

这意味着,HashMap不被摧毁,那么这个 HashMap 的所有 key 所引用的对象就不会给回收

反之,WeakHashMap 随时可能被回收

IdentityHashMap

和HashMap也基本相同,不同的是,如何处理key值是否相同

I:key1 == key2,这代表必须严格相同

H:equals 和 hashCode 是否同时为 true

EnumMap

这和 EnumSet差不多,这里是key 必须为枚举值,value没有要求

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值