[Java]知识点

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/m0_69908381/article/details/129740905
出自【进步*于辰的博客

1、知识点扩展

  1. 细节与使用经验
  2. 静态代理与动态代理
  3. 基本数据类型和引用类型赋值时的底层分析
  4. Socket套接字
  5. 二进制相关概念、运算与应用
  6. 浮点数二进制
  7. 23种设计模式
  8. 自定义注解
  9. 泛型
  10. Lambda表达式
  11. 反射
  12. 线程生命周期与线程通信
  13. 线程池生命周期。(见【生命周期状态】一栏)
  14. @CallerSensitive注解
  15. Java_API解析——java.lang.Class

2、多态

2.1 继承

参考笔记一,P33.7、P34.1、P35.2/3;笔记二,P26.5。

  1. 子类拥有父类所有,只是无法使用private修饰部分;父类拥有子类所有,只是无法使用子类扩展部分。故经过上溯或下溯转型,都可使用相应资源;
  2. 只能先进行上溯转型,再进行下溯转型。由于上溯或下溯转型后,很难判断引用所指向的对象到底属于哪个类,故可用instanceOf判断;
  3. 若子类未重写父类方法,尽管子类拥有父类所有,但那些方法在根本上还是属于父类,因此子类调用的还是父类方法;
  4. super 并不代表父类引用,只是用于调用父类成员 \color{green}{并不代表父类引用,只是用于调用父类成员} 并不代表父类引用,只是用于调用父类成员getClass()是 Object 类的 final 方法(不可重写),故super.getClass()等同于this.getClass(),返回的是当前对象所属类的 Class 对象。

2.2 重载

参考笔记二,P26.6、P29.4。

参数列表不同、或返回值类型不同、或都不同的同名方法称之为“重载”。与访问权限无关,其作用是增加代码可读性。注意:

  1. main()也可重载,默认情况下执行参数类型为String[]main()
  2. 若方法名、参数列表都相同,仅返回值类型不同,则不是“重载”,且不允许

2.3 重写

参考笔记二,P29.3。

当子类方法与父类参数列表和返回值类型都相同的同名方法称之为“重写”。注意:

  1. 重写方法的访问权限不能低于被重写方法;
  2. 重写方法的异常范围不能大于被重写方法;
  3. 若子类某方法与父类某方法方法名、参数列表都相同,仅返回值类型不同,这不是“重写”,且不允许。

3、实例化相关

3.1 构造方法说明

参考笔记一,P34.3。

1 :为什么 J V M 会默认为类创建无参构造方法? \color{grey}{1:为什么JVM会默认为类创建无参构造方法?} 1:为什么JVM会默认为类创建无参构造方法?

因为实例初始化时需要。

2 :为什么子类会默认调用父类无参构造方法? \color{grey}{2:为什么子类会默认调用父类无参构造方法?} 2:为什么子类会默认调用父类无参构造方法?

因为子类拥有父类所有,这需要初始化父类,即调用构造方法。注意:无论子类或父类有没用自定义构造方法,子类都会隐式调用父类无参构造方法。

3 :为什么 ‘ s u p e r ( ) ‘ 必须在构造方法的第一行? \color{grey}{3:为什么`super()`必须在构造方法的第一行?} 3:为什么super()必须在构造方法的第一行?

从第2点可知,子类初始化时会同时初始化父类,但并不能知道super()在第一行的原因。因为父类的初始化数据存储于子类内存空间(具体指堆),即便后续调用(先初始化子类,后初始化父类)也可访问。因此,super()在第一行的原因是:子类要为父类分配内存空间,自然要先知道父类占多少空间,即先初始化父类。

3.2 创建对象方法

参考笔记二,P39.7、P45.2。

1new xx()
2、反射
3、xx.clone()
4、反序列化

其中,clone()属Object类方法,使用clone()的类必须实现Cloneable接口和重写clone()。其中,克隆的对象是一个新对象,克隆分为浅克隆深克隆 两种,两者的区别是当克隆的类中引用其他类时,深克隆会同时克隆引用类,而浅克隆不会,仍是同一个。深克隆的实现原理是在相应clone()内克隆引用类

3.3 注意

  1. 若仅实例化子类,由于this代表的是当前实例,故当在父类中使用this时,this代表的是子类实例,而非父类实例。
  2. 实例化时,会先从方法区中检查构造方法是否相符(相同),再初始化成员变量和成员方法。

4、关键字

4.1 static

参考笔记一,P16.1、P29.14。

1 :为什么非静态内部类的成员不能声明为 \color{red}{1:为什么非静态内部类的成员不能声明为} 1:为什么非静态内部类的成员不能声明为static

因为由static声明或定义的变量为所有拥有它的对象共享,而内部类属于外部类,故内部类的static变量为所有外部类的对象共享,这会导致内部类被提升为外部类、则内部类中的局部变量将无意义。除非是静态内部类

2 :为什么不能在成员方法和类方法中定义类变量? \color{grey}{2:为什么不能在成员方法和类方法中定义类变量?} 2:为什么不能在成员方法和类方法中定义类变量?

因为为类变量分配内存空间是在类加载第二过程的准备阶段,而成员方法和类方法是在类加载完成后再进行初始化,此时已不能再为类变量分配内存空间。

4.2 final

1 :赋初值。 \color{green}{1:赋初值。} 1:赋初值。:

常量仅能在声明static int a = 10或构造器(包括:代码块、构造方法)内赋初值。

2 :常将常量声明为 ‘ s t a t i c ‘ 的原因。 \color{purple}{2:常将常量声明为`static`的原因。} 2:常将常量声明为static的原因。

若没有static,则属于对象成员,则在每个对象实例化时都会创建一个副本,占据堆空间;而声明为static,则会在类加载时被加载进方法区的全局数据访问区中,即只有一个副本,节省空间。并且,声明为static,还可以在静态代码块中赋初值。

3 :常量不可变吗? \color{grey}{3:常量不可变吗?} 3:常量不可变吗?

常量不可变,这可谓是定理,相信大家也一直是这么学习和理解的。可实际上可变,例如:通过反射,

大家是否还记得:

  1. 反射可以跳过泛型检查。(详述可查阅博文《[Java]反射》中的【反射运用:跳过泛型检查】一栏)
  2. 在泛型内有一个概念叫做“泛型擦除”。(详述可查阅博文《[Java]泛型》中的【泛型通配符】一栏)

我们来回顾一下。

  1. “泛型检查”是指泛型的限制作用作用于编译阶段,若传入数据的类型与泛型的类型实参不符,则编译不通过。而反射作用于方法区,且不经过编译阶段,故可以跳过泛型检查。
  2. “泛型擦除”是指在编译完成后,JVM会将泛型的类型实参“擦除”,并上转为其父类(Object)。

我的猜想:反射可以跳过“泛型检查”,反射也可以修改常量(final)。那么,我们所知的“由final修饰的常量不可变”是不是如“泛型擦除”一般,仅是作用于编译阶段,用于限制变量的修改(仅能赋值一次,也就是初始化)。

PS:我暂且未查阅到相关文章,以上出于我的理解。可能不全对,但目前,这有利于我的学习理解。

4.3 abstract

参考笔记一,P32.8。

1 :为什么抽象类的子类(非抽象类)必须实现抽象类所有抽象方法? \color{grey}{1:为什么抽象类的子类(非抽象类)必须实现抽象类所有抽象方法?} 1:为什么抽象类的子类(非抽象类)必须实现抽象类所有抽象方法?

因为若子类不全部实现抽象类的所有抽象方法,则必须是抽象类,而抽象类不能实例化。

5、类加载

参考笔记一,P31.5、P32.2~5;笔记三,P15.2。

下文仅是大致阐述了类加载的执行过程,对于类加载时各过程执行的详细情况并未细述。如果大家有学习意向,可查阅博文《面试官问我:自己写String类,包名也是java.lang,这个类能编译成功吗,能运行成功吗》(转发)。

5.1 什么是“类加载”?

大家先看个图:
在这里插入图片描述

这张是反射的过程图,我曾在博文《[Java]反射》中使用过。那篇文章中阐述:

图中的 B → D,通过类加载器 ClassLoader 将 class 字节码文件加载进 J V M 方法区 \color{green}{JVM方法区} JVM方法区、生成 class 信息、进而创建 Class 对象,这个过程就是 类加载 \color{red}{类加载} 类加载。(注:只有对类的主动使用才会触发类加载,例如:反射实例化

下述对此流程进行详述。

5.2 类加载过程

  1. 过程一:加载,将磁盘中 class 字节码文件 加载进 JVM方法区
  2. 过程二:连接,第1步:验证,验证加载进内存的类的正确性;第2步:准备,为类变量分配内存,并赋默认值;第3步:解析,将常量池中的 符号引用 替换成 直接引用 (即内存地址,存于栈);
    注:如Person p1中的p1初始就是符号引用,经过解析转为内存地址,常说的“引用就是内存地址”就是这个意思。
  3. 过程三:初始化,为类变量赋初始值,如:执行静态代码块和static int a = 10(在静态代码块之外)。

5.3 子类实例化时语句执行顺序

(前提:存在父类)
在这里插入图片描述
前2项在类初始化时执行,后4项在实例化时执行。

5.4 注意

  1. 只有对类的主动使用(如:反射实例化)才会触发类初始化,并且,初始化时数据从JVM方法区的全局数据访问区获取;
  2. 类初始化指类加载的第三过程,实例化指创建对象;
  3. 一载三化类加载类初始化(类加载的第三过程)、实例初始化(实例化前执行)、实例化
  4. 实例化过程:编译 → 类加载 → 实例初始化 → 实例化
  5. 因为继承,故父类的类加载在子类之前;因为子类拥有父类所有成员,故父类的实例初始化在子类之前。因此,在JVM中,父类的初始化数据存储于子类的存储空间中
  6. 类加载仅一次 \color{red}{类加载仅一次} 类加载仅一次,类初始化仅一次;而实例初始化可多次。只要子类实例初始化,父类实例也会初始化,即生成新的子类和父类,因此,即使有多个子类,也不会出现并发性问题。
  7. JVM 方法区用于存放静态资源和类信息,多线程共享(线程安全)。当多线程同时使用一个类时,若此类未加载,则只有一个线程去加载类,其他线程等待。

6、断言

参考笔记二,P7.5。

6.1 什么是“断言”?

断言是类似异常处理的一种特殊语句,使用时的表现是一条语句。一般有两种形式:

1assert a;
2assert a: b;

a 是一个 boolean 表达式,b 是一个基本数据类型参数(如:int、float、String、char、boolean)或一个对象(Integer、Float、String、Character、Boolean),

语句表达的意思是:如果 a 为false,抛出异常(这是固定的),程序终止,否则打印 b。

断言的作用是保证程序的正确性(是否可执行成功),常用于开发和测试阶段(实际上一般很少用)。

与异常处理不同的是:断言是先验条件(先执行 assert,后运行程序;而异常处理是后验条件(先运行程序,后处理异常),比如文件操作里比较常见的,是先运行程序,然后发现文件不存在,抛出 FileNotFoundException。

6.2 为什么assert无效?

由于断言会影响程序性能,故“断言校验”是默认关闭的。因此使用断言需要先打开断言:
在这里插入图片描述
输入:

-enableassertions
或
-ea

7、VM 参数

推荐一篇博文《深入理解 Java 虚拟机(第二弹) - 常用 vm 参数分析》(转发)。

目前我暂未作研究,大家可以查阅这篇博文。

9、序列化

参考笔记一,P1、P2.3。

9.1 什么是“序列化”?

“序列化”指将对象转为字节序列、实现将对象持久化(永久保存)到磁盘、使对象能在网络进程间 传递的过程。
(注:实现序列化的类必须实现Serializable接口;若此类引用了其他类,则相应类也必须实现此接口)

特点:

  1. 持久性
    将对象转为字节序列存于磁盘,即使JVM死机,对象仍然存于磁盘。当JVM宕机时,可以通过反序列化还原对象,并且,序列化的对象占据更小的磁盘空间;

  2. 序列化的对象在网络上传输会更快捷、更安全;

  3. 可实现在进程间传递对象
    “进程间”是什么意思?因为无论程序复杂或简单,在启动时,JVM都只会创建一个进程,而进程间无法直接传输对象。

9.2 序列化ID

示例:

private static final long serialVersionUID = -5809782578272943999L;

这就是序列化ID,其在类加载时生成,用于作为类在JVM内存中的唯一标识。其作用是通过比较字节序列中的序列化ID和实体类中的序列化ID是否一致来判断是否为同一个类,即是否可反序列化,从而还原对象。

9.3 测试

“序列化”与“反序列化*”是一种实现对象持久化的概念,具体实现方法任意。如下示例中使用ObjectOutputStream实现序列化,使用ObjectInputStream实现反序列化是其中一种实现方法。

1、序列化:将对象 Person 序列化到 Person.txt 文件中。
示例:

@AllArgsConstructor
class Person implements Serializable {
    private static final long serialVersionUID = -5809782578272943999L;

    private Integer id;
    private String name;
}
class TestSerializable {
    public static void main(String[] args) throws Exception {
        Person person = new Person(1001, "yuchen");
        System.out.println(person);// 打印:Person(id=1001, name=yuchen)

        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("C:\\Users\\于辰\\Downloads\\新建文件夹\\Person.txt")));
        oos.writeObject(person);
    }
}

测试结果:
字节序列文件:
在这里插入图片描述
字节序列:
在这里插入图片描述
2、反序列化:将 Person.txt 文件中的字节序列通过反序列化还原成 Person 对象。
示例:

class TestSerializable {
    public static void main(String[] args) throws Exception {
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("C:\\Users\\于辰\\Downloads\\新建文件夹\\Person.txt")));
        Person person = (Person) ois.readObject();
        System.out.println(person);// 打印:com.neusoft.boot.Person@69d0a921
    }
}

11、数据类型转换

下述阐述需要计算不同位二进制的表示范围,引用博文《[MySQL]知识点》的第1项中的“求数据类型取值范围的计算通式”。虽然Java与Mysql属不同体系,但是数据类型的取值范围是相同的。因此,大家可以此博文作为参考。
参考笔记二,P65.1。

两个结论: \color{green}{两个结论:} 两个结论:

  1. 数据类型转换分为显式转换和隐式转换两种方式,显式转换需要强转。具体何种方式取决于转换前后类型所占字节数的大小
  2. int类型采用有符号二进制存储,char类型采用无符号二进制存储。

11.1 int → char

引用那篇博文中关于数据类型数值范围的计算通式:
在这里插入图片描述

int类型占4个字节,char类型占2个字节,先计算一下这两种类型的数值范围:

  1. int类型数值范围:-2{8*4-1} ~ 2{8*4-1} - 1 = -2.147483648E9 ~ 2.147483647E9;(有符号二进制)
  2. char类型取值范围:-2{8*2-1} ~ 2{8*2-1} - 1 = -32768 ~ 32767;(有符号二进制)
    注:这个取值范围是按照字节长度计算的。实际上,由于char类型对应 A S C L L 码 \color{blue}{ASCLL码} ASCLL,ASCLL码是正数,故不能按照有符号二进制的方式进行计算。
    char类型由16位无符号二进制表示,故char类型数值范围是:0 ~ 2{8*2} - 1 = 0 ~ 65535

字节长度:int > char,故int类型转为char类型属显式转换。但有一个 特例 \color{red}{特例} 特例

当转为char类型的整型数据是常量,且数值在char类型的数值范围之内时,可隐式转换。

说明示例:
1、将数值在char类型取值–数值范围内的int常量转为char类型。

char c1 = -1;//----------A
char c2 = 65536//;-------B
char c3 = 0;//-----------C
char c4 = 65535;//-------D

char类型的数值范围是: 0 ~ 65535。故A、B编译报错,而C、D转换成功。

2、将int常量转为char类型。

int a1 = 100;
final int a2 = 100;
char c5 = a1;// 编译报错
char c6 = a2;// 转换成功

尽管100在char类型的取值范围内,但a1是变量,故编译报错;而a2以及举例1中的都是常量,故转换成功。

11.2 float与int

float类型与int类型都占4个字节,且都采用有符号二进制进行存储,

int类型的最大和最小值分别是:

Integer.MAX_VALUE;// 2147483647
Integer.MIN_VALUE;// -2147483648

2147483647等于 231 - 1,-2147483648等于 -231

float类型的最大和最小值分别是:

Float.MAX_VALUE;// 3.4028235E38
Float.MIN_VALUE;// 1.4E-45

这两个值是如何计算出来的?这涉及到浮点数二进制的计算方法,大家可查阅博文《浮点数(小数)在计算机中如何用二进制存储?》。具体如何计算,我暂无头绪。

对于float与int间转换,只能强转,那有没有限制呢?做个测试:

(int) Float.MAX_VALUE;// 2147483647
(int) Float.MIN_VALUE;// 0

可见,无限制。

12、ORM

全称Object Relational Mapping(对象关系映射),指实体类与数据表一一对应,属性与字段一一对应,而实例/对象对应记录,即将对象“存入”数据表中。常用的ORM框架:mybatis、mybatis-plus、hibernate。

优点:

  1. 提高开发效率;
  2. 解耦合。如:客户需求变更,需要增删字段来实现功能,使用ORM框架,不用 sql 直接编码,能够像操作对象一样从数据库读取数据。

缺点:降低程序性能

  1. 一般ORM框架系统都是多层的,系统层次多会降低程序性能。且ORM框架是完全面向对象,也会降低程序性能;
  2. ORM框架生成的 sql 语句一般很难写出高效的算法,只能先将对象提取到内存对象中(指select到程序中),再进行过滤和加工,这会降低程序性能;
  3. 在对象持久化时,ORM框架一般会持久化对象的所有属性,有时这是不需要的,这会降低程序性能。
    注:“持久化”指将数据存储进数据库。

14、IPv4

参考笔记一,P75.2。

IPv4由4部分组成,包含网络地址主机地址,每部分解释为一个字节。故都是0 ~ 255的整数。因此,ipv4 占4个字节(32位)。

分类:(注:a指网络地址,b指主机地址)

  1. A类:a 占前8位,b 占后24位,a 的最高位必须是0。因此,范围是:0.0.0.0 ~ 127.255.255.255
  2. B类:a 占前16位,b 占后16位,a 的最高位必须是10。因此,范围是:128.0.0.0 ~ 191.255.255.255
  3. C类:a 占前24位,b 占后8位,a 的最高位必须是110。因此,范围是:192.0.0.0 ~ 223.255.255.255
  4. D类:暂未知,a 的最高位必须是1110。因此,范围是:224.0.0.0 ~ .239.255.255.255
  5. E类:暂未知,a 的最高位必须是1111。因此,范围是:240.0.0.0 ~ 255.255.255.255

17、编译与解释

参考笔记二,P20。

1 :什么是“编译”? \color{grey}{1:什么是“编译”?} 1:什么是编译

“编译”是指将源代码一次性转换成目标程序的过程,编译只执行一次,故编译的着重点不是编译速度,而是目标程序的运行速度。因此,编译器一般都会尽可能多地集成优化技术,使生成的目标程序具有更高的执行速度。

特点:

  1. 对于相同的源代码,目标程序执行速度更快;
  2. 目标程序运行不需要编译器支持,在同类操作系统上使用灵活。

2 :什么是“解释”? \color{grey}{2:什么是“解释”?} 2:什么是解释

“解释”是指将源代码逐条转换并同时运行的过程,不会生成目标程序,故解释的着重点是解释速度。因此,解释在每次程序运行时都需要解释器和源代码,也不能集成太多的优化技术,这样会降低程序的运行速度。

特点:

  1. 由于解释不会生成目标程序,因此解释执行需要保留源代码,这样也便于程序的纠错和维护;
  2. 只要有解释器,源代码在任何操作系统皆可运行,可移植性强。

3 :“编译”与“解释”的运用 \color{green}{3:“编译”与“解释”的运用} 3编译解释的运用

高级语言按照执行方式分为 静态语言 \color{blue}{静态语言} 静态语言 脚本语言 \color{purple}{脚本语言} 脚本语言。静态语言使用编译,如:C/C++、java;脚本语言使用解释,如:python、js、php。在实际运用中,会将两者混合使用,以实现在保证程序逐条运行的同时保留目标程序,因为程序逐条运行能大大提升程序运行速度,保留目标程序便于程序纠错和维护。

19、异常处理

参考笔记一,P35.4/5。

19.1 介绍

相信大家对try...catch...finally都很熟悉了,在此我提一点使用细节。

大家先看这段代码:

try {
    return 0;
} catch (Exception e) {
} finally {
    return 1;
}

请问编译通过吗?如果通过,返回值是哪个?

要回答这两个问题,需要先对异常处理的性质有一个较为全面的了解。如下:

  1. 异常处理不会终止try...catch...finally语句块外代码的执行。
  2. 无论是否异常,try...catch中有无returnfinally必会执行,且优先于try...catch...finally语句块外代码执行。
  3. try...catchfinally中都有return,前者会被后者覆盖。因为在此情况下,前者仅用于保存数据,存于栈,故会被后者覆盖。

因此,编译通过,返回值为1

补充一点 \color{red}{补充一点} 补充一点try中尽量不要放置太多代码,因为异常处理是线程,“检查性异常”之所以可以实时检查,是因为此线程在自动循环检查。如果代码太多,会占用太多内存。

19.2 JDK1.7中异常处理新特性

常规写法示例:

try {
    Class clazz = Class.forName("");
    FileReader fr = new FileReader("");
    FileWriter fw = new FileWriter("");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

新写法:

try(FileReader fr = new FileReader("");
    FileWriter fw = new FileWriter("")) {// ------------A
    Class clazz = Class.forName("");
} catch (ClassNotFoundException | IOException e) {// ---B
    e.printStackTrace();
}

说明:

  1. 能够置于A的类必须实现了Closeable接口。
  2. B中的异常列表中,每一类异常只能出现一个。如示例中FileNotFoundException与IOException都属于IO异常,故B中只能是IOException。

最后

本文中的例子是为了方便大家理解、以及阐述相关知识点而简单举出的,不一定有实用性,仅是抛砖引玉。

推荐一个Java学习平台 → HOW2J.CN

本文持续更新中。。。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进步·于辰

谢谢打赏!!很高兴可以帮到你!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值