[Java]知识点

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

1、知识点扩展

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

2、多态

2.1 继承

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

  1. 子类拥有父类所有,只是无法使用private修饰部分;父类拥有子类所有,只是无法使用子类扩展部分。故经过上溯或下溯转型,都可使用相应资源;
  2. 只能先进行上溯转型,再进行下溯转型。由于上溯或下溯转型后,很难判断引用所指向的对象到底属于哪个类,故可用instanceOf判断;
  3. 若子类未重写父类方法,尽管子类拥有父类所有,但那些方法在根本上还是属于父类,因此子类调用的还是父类方法;
  4. super并不代表父类引用,只是用于调用父类成员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:为什么JVM会默认为类创建无参构造方法?

因为实例初始化时需要。

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

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

3:为什么super()必须在构造方法的第一行?

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

3.2 创建对象方法

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

1、new 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:为什么非静态内部类的成员不能声明为}$static

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

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

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

4.2 final

1:赋初值。

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

2:常将常量声明为static的原因。

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

3:常量不可变吗?

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

大家是否还记得:

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

我们来回顾一下。

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

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

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

4.3 abstract

参考笔记一,P32.8。

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

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

5、类加载

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

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

5.1 什么是“类加载”?

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

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

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

下述对此流程进行详述。

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. 类加载仅一次,类初始化仅一次;而实例初始化可多次。只要子类实例初始化,父类实例也会初始化,即生成新的子类和父类,因此,即使有多个子类,也不会出现并发性问题。
  7. JVM 方法区用于存放静态资源和类信息,多线程共享(线程安全)。当多线程同时使用一个类时,若此类未加载,则只有一个线程去加载类,其他线程等待。

6、断言

参考笔记二,P7.5。

6.1 什么是“断言”?

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

1、assert a;
2、assert a: b;
3、assert(a);

a 是一个boolean表达式或boolean值,b 是一个基本数据类型变量或对象。

语句表达的意思是:如果 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
    }
}

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

特点:

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

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

特点:

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

3:“编译”与“解释”的运用

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

最后

本文中的例子是为了方便大家理解和阐述知识点而简单举出的,旨在阐明知识点,并不一定有实用性,仅是抛砖引玉。

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

本文持续更新中。。。

posted @ 2023-03-23 23:50  进步·于辰  阅读(3)  评论(0编辑  收藏  举报  来源