宋红康《30天搞定Java》学习笔记(B站视频)

编程语言

软件,即一系列按照特定顺序组织的计算机数据和指令的集合,有系统软件和应用软件之分 。
人机交互方式: 图形化界面(Graphical User Interface GUI) 、 命令行方式(Command Line Interface CLI) 。

Window操作系统常用DOS命令:

  1. dir : 列出当前目录下的文件以及文件夹
  2. md : 创建目录
  3. rd : 删除目录
  4. cd : 进入指定目录 ;cd .. : 退回到上一级目录;cd \ : 退回到根目录
  5. del : 删除文件
  6. exit : 退出 dos 命令行
  7. (重定向):echo “Hello DOS”>test.txt

编程语言:机器语言、汇编语言、高级语言

Java技术体系平台:

  • Java SE(Java Standard Edition)标准版: 支持面向桌面级应用的Java平台,提供了完整的Java核心API
  • Java EE(Java Enterprise Edition)企业版: 是为开发企业环境下的应用程序提供的一套解决方案
  • Java ME(Java Micro Editon)小型版: 支持Java程序运行在移动终端(手机、PDA)上的平台

Java语言概念

Java发展中的一些重要概念:

  1. Java是一个纯粹的面向对象的程序设计语言,1995年由SUN公司推出,Java之父是James Gosling
  2. Java主要应用领域:企业应用后端、大数据开发、Android平台开发
  3. Java语言特点:面向对象、跨平台性(JVM)、健壮性(取消指针、垃圾收集)
  4. Java 提供一种系统级线程跟踪存储空间的分配情况。在JVM空闲时,检查并释放可被释放的存储空间
  5. Java虚拟机,JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。 对于不同的平台有不同的虚拟机,Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”
  6. JDK(Java Development Kit Java开发工具包) ; JRE(Java Runtime Environment Java运行环境) 。JDK包含了JRE和开发工具集,JRE包含了JVM和Java SE标准类库

Java、Python、C/C++的执行过程:C/C++是一种编译型语言,它更接近底层。Python是一种解释型语言。而Java这是一种先编译后解释的语言。C/C++的执行过程是编译、连接库文件、运行,Java的执行过程是编译、交给虚拟机解释、运行,而Python则是解释(可生成.py字节码文件复用加速执行过程)、运行。
Java编译时可以指定编码,如javac -encoding utf-8 java源文件
Snipaste_2022-04-08_12-09-26.png

JDK安装与环境配置:

  1. 可以在Oracle官网下载,也可下载第三方网站提供的JDK包如JDK下载网址
  2. 配置系统环境变量PATH(可执行程序的搜索路径),最好新增变量JAVA_HOME,再添加路径%JAVA_HOME%\bin到PATH中

Java三种注释模式:

  1. 单行注释://注释文字
  2. 多行注释:/注释文字/(多行注释不可嵌套使用)
  3. 文档注释:/*注释文字(@author指定作者 @version指定版本)/,利用javadoc命令可以导出网页文件形式呈现的说明文档,命令为javadoc -d 目录 -author 作者 -version 版本 java文件

Java程序的特点:

  1. Java源文件以“.java”为扩展名,源文件基本组成部分是类(class),在一个java源文件中可以声明多个class,但最多有一个类声明为public,且要求声明为public的类的类名必须与源文件名相同。
  2. Java应用程序执行入口是main方法,固定书写格式是public static void main(String[] args){...}
  3. Java的每条语句以“;”结束。
  4. 源文件编译后会生成一个或多个字节码文件,字节码文件名和java源文件中的类名相同。
  5. Java运行过程:编写、编译(javac)、运行(java)

    Java基本语法

    变量与运算符

    Java关键字(keyword): 被Java语言赋予了特殊含义,用做专门用途的字符串,关键字中所有字母都为小写
    Java保留字(reserved word):goto、const
    标识符: 对各种变量、方法和类等要素命名时使用的字符序列

    合法标识符规则:

    1. 由26个英文字母大小写,0-9 ,_或 $ 组成
    2. 数字不可以开头
    3. 不可以使用关键字和保留字,但能包含关键字和保留字
    4. Java中严格区分大小写,长度无限制
    5. 标识符不能包含空格

Java中的名称命名规范:

  1. 包名:多单词组成时所有字母都小写:xxxyyyzzz
  2. 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
  3. 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
  4. 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ

Java变量:变量是内存中的一块存储区域,它是程序的基本存储单元,包含变量类型、变量名、存储的值。Java是一种强类型语言,Java中的变量必须先声明赋值后才能使用,变量有其作用域,同一作用域内不能定义重名的变量。

变量分类:

  1. 基本数据类型:数值型(整型byte、short、int、long;浮点型float、double)、字符型(char)、布尔型(boolean)
  2. 引用数据类型:类(class)、接口(interface)、数组([])

变量使用的一些注意细节:

  1. Java的整型常量默认为 int 型,声明long型常量须后加‘l’或‘L’
  2. Java 的浮点型常量默认为double型,声明float型常量,须后加‘f’或‘F’
  3. char 型数据用来表示通常意义上“字符”(2字节), char类型是可以进行运算的。因为它都对应有Unicode码
  4. boolean类型数据只允许取值true和false,无null

Snipaste_2022-04-09_11-41-13.png
Snipaste_2022-04-09_11-41-35.pngJava基础数据类型转换:自动类型转换,即把容量小的类型自动转换为容量大的数据类型;强制类型转换, 使用时要加上强制转换符:(类型),但可能造成精度降低或溢出。
Snipaste_2022-04-10_23-36-26.png

类型转换的一些注意事项:

  1. byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型
  2. 当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类型的值将自动转化为字符串(String)类型
  3. boolean类型不能与其它数据类型运算,且不可以转换为其它的数据类型

Java运算符: 用以表示数据的运算、赋值和比较等,包括算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符、三元运算符

运算符的一些注意事项:

  1. 取模运算可以把负号忽略,最后的结果再赋予和被取模数一样的符号
  2. 整数之间做除法(/)时,只保留整数部分而舍弃小数部分
  3. 赋值时,两侧数据类型不一致时,可以使用自动类型转换或强制类型转换进行处理
  4. 逻辑与&、短路与&&的区别:使用逻辑与(&)无论左边真假,右边都进行运算;使用短路与(&&) 如果左边为真,右边参与运算,如果左边为假,那么右边不参与运算
  5. 逻辑或|、短路或||的区别:使用逻辑或(|)无论左边真假,右边都进行运算;使用短路或(||)如果左边为真,右边不参与运算,如果左边为假,那么右边参与运算
  6. 位运算是直接对整数的二进制进行的运算
  7. 自增、自减、带运算赋值的运算符不会改变变量本身的数据类型
  8. 自增、自减运算符的一个注意点:i=i++这条语句先赋值,把i原来的值重新赋值给i,不变,然后i自增,但是这个自增后的值没有放回变量i的位置,因此i的值还是原来的值

Snipaste_2022-04-09_22-31-45.png
Snipaste_2022-04-09_22-32-11.png
Snipaste_2022-04-09_22-32-30.png
Snipaste_2022-04-09_22-32-51.png
Snipaste_2022-04-09_22-33-13.png

三目运算符的类型转换:

  1. 三目运算符的两个结果应该是同一数据类型
  2. 三目运算符的两个结果表达式存在变量时,优先都转换为变量中容量大的数据类型,若无法转变再以常量的数据类型进行转换
  3. 三目运算符的两个结果表达式都是常量时,如果一个是char,如果另一个是[0~65535]之间的整数按char处理;如果一个是char,另一个是其他,按照自动类型转换规则处理成一致的类型
  1. char x = 'x';//'x'的ASCII码为120
  2. int i = 97;
  3. System.out.println(x);//x
  4. System.out.println(false? x : 97);//a
  5. System.out.println(true? x : 65536);//120
  6. System.out.println(true? x : i);//120
  7. System.out.println(true? 'x' : 97);//x
  8. System.out.println(false? 'x' : 97);//a
  9. System.out.println(false? 'x' : 65536);//65536
  10. System.out.println(true? 'x' : 65536);//120
  11. System.out.println(true? 'x' : i);//120

流程控制

流程控制语句是用来控制程序中各语句执行顺序的语句, 三种基本流程结构包括顺序、分支、循环结构。

分支结构(if-else)使用的注意点:

  1. 条件表达式必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量
  2. 语句块只有一条执行语句时,一对{}可以省略,但建议保留
  3. if-else语句结构,根据需要可以嵌套使用
  4. 当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略
  5. 当多个条件是“互斥”关系时,条件判断语句及执行语句间顺序无所谓;当多个条件是“包含”关系时,“小上大下 / 子上父下”
  6. if-else语句结构按就近原则进行匹配
  1. if(条件表达式1){
  2. 执行代码块1;
  3. }else if(条件表达式2){
  4. 执行代码块2;
  5. }else{
  6. 执行代码块n;
  7. }

分支结构(switch-case)使用的注意点:

  1. switch(表达式)中表达式的值必须是下述几种类型之一:byte,short,char,int,枚举 (jdk 5.0),String (jdk 7.0)
  2. case子句中的值必须是常量,不能是变量名或不确定的表达式值
  3. break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有break,程序会顺序执行到switch结尾
  4. default子句是可任选的。同时,位置也是灵活的
  1. switch(表达式){
  2. case 常量1:
  3. 语句1;
  4. // break;
  5. case 常量2:
  6. 语句2;
  7. // break;
  8. case 常量N:
  9. 语句N;
  10. // break;
  11. default:
  12. 语句;
  13. // break;
  14. }

循环语句: 在某些条件满足的情况下,反复执行特定代码的功能。循环语句包括四个组成部分: 初始化部分(init_statement) 、循环条件部分(test_exp)、循环体部分(body_statement)、迭代部分(alter_statement)。
“无限” 循环格式:while(true) , for(;;),无限循环存在的原因是并不知道循环多少次,需要根据循环体内部某些条件,来控制循环的结束。

循环结构(for)使用的注意点:

  1. 循环条件部分为boolean类型表达式,当值为false时,退出循环
  2. 初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔
  3. 可以有多个变量更新,用逗号分隔
  1. for (初始化部分;循环条件部分;迭代部分){
  2. 循环体部分;
  1. 初始化部分
  2. while(循环条件部分){
  3. 循环体部分;
  4. 迭代部分;
  5. }
  1. 初始化部分;
  2. do{
  3. 循环体部分
  4. 迭代部分
  5. }while(循环条件部分);

break和continue的使用:

  1. break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块,标号语句必须紧接在循环的头部
  2. break只能用于switch语句和循环语句中,continue只能用于循环语句中;二者功能类似,但continue是终止本次循环,break是终止本层循环

数组

数组(Array),是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,通过编号的方式对这些数据进行统一管理。与数组相关的概念包括:数组名、元素、角标(下标、索引)、数组长度。数组可按维数分为一维数组和多维数组,按照数组元素的类型有:基本数据类型元素的数组、引用数据类型元素的数组。

数组的特点:

  1. 数组是有序排列的
  2. 数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型
  3. 创建数组对象会在内存中开辟一整块连续的空间
  4. 数组的长度一旦确定,就不能修改

数组使用注意点:

  1. 数组声明与初始化:

静态初始化:数组的初始化和数组元素的赋值操作同时进行,如int[] ids = new int[]{1,2,3,4};
动态初始化:数组的初始化和数组元素的赋值操作分开进行,如int[] ids = int[4];ids = new int[]{1,2,3,4};也可以写为类型推断的方式:ids = {1,2,3,4};

  1. 数组的引用:通过角标的方式调用
  2. 数组的属性:数组长度,如ids.length
  3. 数组的遍历:for(int i = 0;i < ids.length;i++){System.out.println(ids[i]);}
  4. 数组的默认初始化值
  5. 数组的内存解析:Java中使用关键字new来创建数组,栈内存存放数组名和对应的对象地址,堆内存存放创建的对象数据

Snipaste_2022-04-13_15-08-10.png

数组常见操作:

  1. 数组元素的赋值(杨辉三角、回形数等)
  2. 求数值型数组中元素的最大值、最小值、平均数、总和等
  3. 数组的复制、反转、查找(线性查找、二分法查找)
  4. 数组元素的排序算法

Arrays工具类的使用:操作数组的工具类java.util.Arrays,常见的方法如下,其余详见API文档
Snipaste_2022-04-13_22-13-03.png
数组使用中的常见异常:

  1. 数组脚标越界异常(ArrayIndexOutOfBoundsException)
  2. 空指针异常(NullPointerException)

    排序算法

    算法的五大特征:输入、输出、有穷性、确定性、可行性。 满足确定性的算法也称为:确定性算法。现在人们也关注更广泛的概念,例如考虑各种非确定性的算法,如并行算法、概率算法等。
    Snipaste_2022-04-13_21-31-22.png

    衡量排序算法的优劣:

    1. 时间复杂度:分析关键字的比较次数和记录的移动次数
    2. 空间复杂度:分析排序算法中需要多少辅助内存
    3. 稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保持不变,则称这种排序算法是稳定的

排序算法可分为内部排序和外部排序,内部排序的所有操作都在内存中完成;外部排序一般是数据量大,无法将整个排序过程放在内存中完成,常见的是多路归并排序。

各种排序方法的优劣比较:

  1. 从平均时间而言:快速排序最佳。但在最坏情况下时间性能不如堆排序和归并排序
  2. 从算法简单性看:由于选择排序、插入排序和冒泡排序的算法比较简单,将其认为是简单算法。对于Shell排序、堆排序、快速排序和归并排序算法,其算法比较复杂,认为是复杂排序
  3. 从稳定性看:插入排序、冒泡排序和归并排序时稳定的;而选择排序、快速排序、Shell排序和堆排序是不稳定排序
  4. 从待排序的记录数n的大小看,n较小时,宜采用简单排序;而n较大时宜采用改进排序

Snipaste_2022-04-13_22-01-07.png

面向对象

面向过程&面向对象

面向过程(POP):Procedure Oriented Programming;面向对象(OOP):Object Oriented Programming。 面向过程强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。 类(Class)和对象(Object)是面向对象的核心概念。类是对一类事物的描述,是抽象的、概念上的定义; 对象是实际存在的该类事物的每个个体,因而也称为实例(instance) 。

面向对象三大特征:

  1. 封装 (Encapsulation)
  2. 继承 (Inheritance)
  3. 多态 (Polymorphism)

一个比较完整的Java类可能包含:属性(field,成员变量)、构造器、方法(method)、代码块、内部类。

  1. 修饰符 class 类名 {
  2. 属性声明;//考虑修饰符、属性类型、属性名、初始化值
  3. 方法声明;//考虑修饰符、返回值类型、方法名、形参等
  4. }

对象创建与使用:使用类名 对象名 = new 类名();来创建对象,使用对象名.对象成员来访问对象。
类的访问机制: 在一个类中的方法可以直接访问类中的成员变量(例外:static方法访问非static,编译不通过)。在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。
匿名对象:不定义对象的句柄,而直接调用这个对象的方法,如new TestClass().test();

类的成员对象:属性

属性声明格式为修饰符 数据类型 属性名 = 初始化值 ; 常用修饰符有private、缺省、protected、public,其它修饰符有static、final。在方法体外,类体内声明的变量称为成员变量;在方法体内部声明的变量称为局部变量,主要区别为内存加载位置及作用域不同。

局部变量前不能放置任何访问修饰符 (private,public和protected)

Snipaste_2022-04-17_08-36-16.png
Snipaste_2022-04-17_08-39-04.png

属性赋值过程:

  1. 默认初始化
  2. 显式初始化、代码块依次执行(显式初始化和代码块属于同级别,按位置的先后顺序执行)
  3. 构造器中初始化
  4. 通过“对象.属性“或“对象.方法”的方式赋值

类的成员对象:方法

方法是类或对象行为特征的抽象,用来完成某个功能操作。方法中只能调用方法或属性,不可以在方法内部定义方法。

  1. 修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2, ….){
  2. 方法体程序代码
  3. return 返回值;

方法的一些使用:

  1. 方法的重载(overload):在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可,注意与返回值类型无关,只与形参列表中参数个数、类型、顺序相关。
  2. 可变个数的形参:在JDK 5.0以前:采用数组形参来传入多个同一类型变量,如public static void test(int a ,String[] books); ,在JDK5.0及之后:采用可变个数形参来定义方法,传入多个同一类型变量,如public static void test(int a ,String…books);
  3. 方法参数的值传递机制:Java里方法的参数传递方式只有值传递一种,即将实际参数值的副本传入方法内,而参数本身不受影响,但Java中的变量有基本和引用数据类型,基本数据类型变量将“数据值”传递给形参;引用数据类型变量将“地址值”传递给形参。
  4. 递归方法 (recursion) :一个方法体内调用它自身。

Java的值传递机制:即使传入引用数据类型,也只能修改指向地址的数据,不能重新指定地址

  1. class Value{
  2. int i = 15;
  3. }
  4. class Test{
  5. public static void main(String argv[]) {
  6. Test t = new Test();
  7. t.first();
  8. }
  9. public void first() {
  10. int i = 5;
  11. Value v = new Value();
  12. v.i = 25;
  13. second(v, i);
  14. System.out.println(v.i);
  15. }
  16. public void second(Value v, int i) {
  17. i = 0;
  18. v.i = 20;
  19. Value val = new Value();
  20. v = val;
  21. System.out.print(v.i + " " + i + " ");
  22. }
  23. }
  24. //输出为15 0 20

可变参数形参的声明格式为方法名(参数的类型名...参数名),可变参数要放在形参列表最后部分,且最多只能声明一个可变个数形参。参数个数是可变多个的(0个,1个或多个)。 可变个数形参的方法与同名的方法之间,彼此构成重载,但和对应的同类型数组形参会造成冲突。

类的成员对象:构造器

构造器特征:

  1. 具有与类相同的名称
  2. 不声明返回值类型 (与声明为void不同)
  3. 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值

根据参数不同,构造器可以分为如下两类:一是隐式无参构造器(系统默认提供);二是显式定义一个或多个构造器(无参、有参)

构造器使用注意点:

  1. Java语言中,每个类都至少有一个构造器,因为系统默认会提供一个隐式无参构造器
  2. 默认构造器的修饰符与所属类的修饰符一致
  3. 一旦显式定义了构造器,则系统不再提供默认构造器,但一般会定义一个无参构造器用于反射使用
  4. 一个类可以创建多个重载的构造器
  5. 父类的构造器不可被子类继承
  1. 修饰符 类名 (形参列表) {
  2. 初始化语句;
  3. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Base b1 = new Base();
  4. Base b2 = new Sub();
  5. }
  6. }
  7. class Base{
  8. Base(){
  9. method(100);
  10. }
  11. public void method(int i){
  12. System.out.println("base : " + i);
  13. }
  14. }
  15. class Sub extends Base{
  16. Sub(){
  17. super.method(70);
  18. }
  19. public void method(int j){
  20. System.out.println("sub : " + j);
  21. }
  22. }
  23. /*输出结果:
  24. base : 100
  25. sub : 100
  26. base : 70
  27. */

类的成员对象:代码块

代码块的主要作用是对Java类或对象进行初始化。一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块 (static block),没有使用static修饰的,为非静态代码块。

静态代码块(即用static修饰的代码块):

  1. 可以有输出语句
  2. 可以对类的属性、类的声明进行初始化操作
  3. 不可以对非静态的属性初始化。即不可以调用非静态的属性和方法
  4. 若有多个静态的代码块,那么按照从上到下的顺序依次执行
  5. 静态代码块的执行要先于非静态代码块
  6. 静态代码块随着类的加载而加载,且只执行一次

非静态代码块(没有static修饰的代码块):

  1. 可以有输出语句
  2. 可以对类的属性、类的声明进行初始化操作
  3. 除了调用非静态的结构外,还可以调用静态的变量或方法
  4. 若有多个非静态的代码块,那么按照从上到下的顺序依次执行
  5. 每次创建对象的时候,都会执行一次。且先于构造器执行

类使用的一些关键字

this关键字:可用来区分属性和同名的局部变量。在方法内部使用,表示这个方法所属对象的引用;在构造器内部使用,表示该构造器正在初始化的对象。可以在类的构造器中使用”this(形参列表)”的方式,调用本类中重载的其他的构造器 ,但不能调用自身,且不能成环,”this(形参列表)”必须声明在类的构造器的首行,且最多只能有一个。
package关键字:作为Java源文件的第一条语句,指明该文件中定义的类所在的包,格式为package 顶层包名.子包名 ; 包对应文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次。包可以帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式 。
Snipaste_2022-04-17_09-52-07.png

JDK中主要使用的包:

  1. java.lang——包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能
  2. java.net——包含执行与网络相关的操作的类和接口
  3. java.io ——包含能提供多种输入/输出功能的类
  4. java.util——包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数
  5. java.text——包含了一些java格式化相关的类
  6. java.sql——包含了java进行JDBC数据库编程的相关类/接口

import关键字:使用语法为import 包名.类名;

import使用的注意点:

  1. 在源文件中使用import显式的导入指定包下的类或接口
  2. import声明在包的声明和类的声明之间
  3. 如果需要导入多个类或接口,那么就并列显式使用多个import语句即可,也可以使用类似java.util.*的方式,一次性导入包下所有的类或接口
  4. 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句
  5. 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类
  6. 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类,仍然需要单独导入
  7. import static组合的使用:调用指定类或接口下的静态的属性或方法

super关键字:调用父类中的指定操作,如访问父类定义的属性、成员方法、构造器。

super关键字与调用父类构造器的注意点:

  1. 子类中所有的构造器默认都会访问父类中的空参构造器
  2. 当父类中没有空参构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能“二选一”,且必须放在构造器的首行
  3. 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参构造器,则编译出错
  4. 按以上特征来看,所有的类都有一个共同的父类,即Object类

Snipaste_2022-04-19_16-01-17.png
instanceof操作符:x instanceof A,检验x是否为类A的对象,返回值为boolean型。如果x属于类A的子类B,该表达式的值也为true。 x所属的类与类A必须是子类和父类的关系,否则编译错误。
static关键字:编写一个类时,其实就是在描述其对象的属性和行为,并没有产生实质上的对象,只有通过new关键字才会产生对象,这时系统才会分配内存空间给对象, 其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份。

类属性:作为该类各个对象之间共享的变量 类方法:不需要创建对象就可以调用,可以用类名.方法名()的形式访问由static修饰的类方法

static使用注意点:

  1. 可用static修饰属性、方法、代码块、内部类
  2. 被static修饰后的成员随着类的加载而加载,优先于对象存在,并被所有对象所共享
  3. 类变量(静态变量)内存解析位置在JVM的方法区中,方法区加载了类的信息、静态域、常量池;而普通成员变量(非静态变量)内存解析位置在堆中,局部变量解析在栈里
  4. 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构,因为生命周期不同
  5. 不需要实例就可以访问static方法,因此static方法内部不能有this、super关键字
  6. static修饰的方法不能被重写

final关键字:在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。

final关键字使用注意:

  1. final标记的类不能被继承,如String类、System类
  2. final标记的方法不能被子类重写,如Object类中的getClass()方法
  3. final标记的变量(成员变量或局部变量)即称为常量。名称一般大写,且只能被赋值一次,如Math.PI

面向对象的特征

封装:

  1. Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对属性的操作。
  2. Java权限修饰符public、protected、(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。对于class的权限修饰只可以用public和default(缺省),public类可以在任意地方被访问,default类只可以被同一个包内部的类访问。

Snipaste_2022-04-17_10-16-25.png
继承:
多个类存在相同属性和方法时,将这些共性抽取到单独一个类中,每个类就无需再定义这些属性和方法,只要继承那个类即可。子类(派生类)继承父类(基类或超类) ,语法class Subclass extends SuperClass{ }

继承的作用:

  1. 减少代码冗余,提高代码的复用性
  2. 有利于功能的扩展
  3. 让类与类之间产生了关系,提供了多态的前提

Java中继承使用的一些注意点:

  1. 注意子类不是父类的子集,子类可以对父类的属性、方法进行“扩展”
  2. 子类不能直接访问父类中私有的(private)成员变量和方法,但这些属性仍存在子类实例化对象中,可利用父类公共的(public)方法进行访问
  3. Java只支持单继承和多层继承,不允许多重继承
  4. 子类可以用自己的方式实现父类的方法 (override/overwrite)

多态:
父类的引用指向子类的对象,又称为向上转型(upcasting)。Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。若编译时类型和运行时类型不一致,就出现了对象的多态性。多态提高了代码的通用性,常称作接口重用。

一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法

虚拟方法调用(Virtual Method Invocation):子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的,方法的调用是在运行时确定的,称为动态绑定。
对象类型转换 (Casting ) : 从子类到父类的类型转换可以自动进行(就是父类的引用指向子类的对象,多态),从父类到子类的类型转换必须通过造型(强制类型转换)实现。无继承关系的引用类型间的转换是非法的,操作前可以使用instanceof操作符测试一个对象的类型

方法的重写(override/overwrite)

子类可以根据需要对从父类中继承过来的方法进行实现。在程序执行时,子类的方法将覆盖父类的方法。

方法重写注意点:

  1. 子类重写的方法必须和父类被重写的方法具有相同的方法名称、形参列表
  2. 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限(特别的,子类不能重写父类中声明为private权限的方法)
  3. 子类方法抛出的异常不能大于父类被重写方法的异常
  4. 返回值类型方面:父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void;父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类;父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
  1. class Demo{
  2. int show(int a,int b){return 0;}
  3. }
  4. //下面那些函数可以存在于Demo的子类中:
  5. public int show(int a,int b){return 0;}//可以,覆盖
  6. private int show(int a,int b){return 0;}//不可以,权限不够
  7. private int show(int a,long b){return 0;}//可以,和父类不是一个函数。没有覆盖,相当于重载
  8. public short show(int a,int b){return 0;}//不可以,返回类型和父类方法返回类型不兼容
  9. static int show(int a,int b){return 0;}//不可以,静态只能覆盖静态

Object类

Object类是所有Java类的根父类。如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
Snipaste_2022-04-19_17-26-12.png

==操作符的使用【两边数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错】 基本类型比较值:只要两个变量的值相等,即为true 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,才返回true

equals()方法的使用 所有类都继承了Object类,也就获得了equals()方法,查看Object类的源码可以发现,equals()方法只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。但对于类File、String、Date及包装类 (Wrapper Class)来说,是比较类型及内容而不考虑引用是否是同一个对象,因为其对equals()方法进行了重写

toString()方法的使用 toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。 String与其它类型数据的连接操作时,自动调用toString()方法。 可以根据需要在用户自定义类型中重写toString()方法。 基本类型数据转换为String类型时,调用了对应包装类的toString()方法。

包装类(Wrapper)

针对八种基本数据类型定义相应的引用类型,称为包装类(封装类)。
Snipaste_2022-04-19_17-49-13.png
基本数据类型包装成包装类:
通过包装类的构造器实现:int i = 500; Integer t = new Integer(i);
通过字符串参数构造包装类对象:Float f = new Float("4.56");
JDK5.0之后可自动装箱,无需显式包装对象
获取包装类中的基本数据类型:
调用包装类的方法:boolean b = bObj.booleanValue();
JDK5.0之后可自动拆箱
字符串转换成基本数据类型:
通过包装类的构造器并由自动拆箱实现:int i = new Integer("12");
通过包装类的静态方法:float f = Float.parseFloat("12.1");
基本数据类型转换成字符串:
调用字符串重载的valueOf()方法:String fstr = String.valueOf(2.34f);
使用拼接的方式:String intStr = 5 + "";

Integer包装类使用的注意点:Interger源码中IntegerCache类定义了-128至127的缓存数组,因此其自动装箱声明的变量,若值的范围在-128至127间,引用的地址相同 Integer和int类型比较时,只要数值相同结果即为true,比较原理是调用Integer对象的intValue方法拆箱转换为基本数据类型,再进行比较

  1. public void method() {
  2. Integer i = new Integer(1);
  3. Integer j = new Integer(1);
  4. System.out.println(i == j);//false
  5. Integer m = 1;
  6. Integer n = 1;
  7. System.out.println(m == n);//true
  8. Integer x = 128;
  9. Integer y = 128;
  10. System.out.println(x == y);//false
  11. int z = 128;
  12. System.out.println(x == z);//true
  13. }

内部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。内部类可分为成员内部类(static成员内部类和非static成员内部类)、局部内部类(不含修饰符)、匿名内部类。

成员内部类的特征:

  1. 从作为类的成员角色来看:和外部类不同,Inner class可以声明为private或protected;可以调用外部类的结构;Inner class可以声明为static的,但此时就不能再使用外部类的非static的成员变量
  2. 从作为类的角色来看:可以在内部定义属性、方法、构造器等结构;可以声明为abstract类 ,因此可以被其它的内部类继承;可以声明为final的;编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

成员内部类使用注意点:

  1. 非static的成员内部类中的成员不能声明为static,只有外部类或static的成员内部类中才可声明static成员
  2. 外部类访问成员内部类的成员,需要内部类.成员内部类对象.成员的方式
  3. 成员内部类可以直接使用外部类的所有成员,包括私有的数据
  4. 当想要在外部类的静态成员部分使用内部类时,可以考虑将内部类声明为静态的

非static的成员内部类中的成员不能声明为static: 每一个非静态内部类必须维持对一个外部类实例的应用,表明其作用域为实例级别;static指定的属性、方法等为类级别;二者作用域不同,自然编译无法通过。可在变量前添加final,使其变为全局变量。

局部内部类的特征:

  1. 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号
  2. 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类;但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
  3. 局部内部类可以使用外部类的成员,包括私有的结构

局部内部类使用注意点:

  1. 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
  2. 局部内部类不能使用static修饰,因此也不能包含静态成员
  3. 局部内部类可以使用外部方法的局部变量,但是必须是final的

为什么局部内部类和匿名内部类只能访问 final 的局部变量? 内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁,但是内部类对象可能还存在(只有没有再引用它时,才会销毁)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量销毁后,内部类仍可以访问它,实际访问的是局部变量的副本。但这样又会出现问题:将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变。因此就将局部变量设置为final,对它初始化后,不能再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协

匿名内部类:一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个抽象类。

  1. //匿名类
  2. new 父类构造器(实参列表){抽象方法实现;}
  3. //接口的匿名实现
  4. new 实现接口(){抽象方法实现;}

抽象类

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

  1. 用abstract关键字来修饰一个类,这个类叫做抽象类
  2. 用abstract来修饰一个方法,该方法叫做抽象方法,抽象方法只有方法的声明,没有方法的实现,例如public abstract void method();,含有抽象方法的类必须被声明为抽象类
  3. 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类
  4. 不能用abstract修饰变量、代码块、构造器
  5. 不能用abstract修饰私有方法(private)、静态方法(static)、final的方法、final的类

Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。按语法来看,一个抽象类也可以不提供抽象方法,但这样无法体现抽象的优势

接口(interface)

有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承;或者说从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已,因此就有接口的需求。

接口(interface)是抽象方法和常量值定义的集合

  1. 用关键字interface来定义
  2. 接口中的所有成员变量都默认是由public static final修饰的,即使不显式指明,也默认赋予此类权限
  3. 接口中的所有抽象方法都默认是由public abstract修饰的,即使不显式指明,也默认赋予此类权限
  4. 接口中没有构造器
  5. 接口采用多继承机制(一个接口可以继承自多个接口)

JDK8.0后关于接口的新特性:可以为接口添加静态方法和默认方法。静态方法使用static关键字修饰,可以通过接口直接调用静态方法;默认方法使用 default 关键字修饰,可以通过实现类对象来调用。

  1. 定义Java类的语法格式:先写extends,后写implements,如class SubClass extends SuperClass implements InterfaceA,InterfaceB{ }
  2. 一个类可以实现多个接口,接口也可以继承其它接口
  3. 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化,否则仍为抽象类
  4. 接口的主要用途就是被实现类实现(面向接口编程),与继承关系类似,接口与实现类之间存在多态性
  5. 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义 (JDK7.0及之前),而没有变量和方法的实现

Snipaste_2022-04-21_10-55-22.png
接口使用中的冲突问题:

  1. 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现接口冲突。解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突
  2. 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题,此时遵守类优先原则
  3. 在实现了接口的实现类中使用接口的同名方法可以使用接口名.super.静态方法();来调用
  4. 若接口和类都有同名变量,编译会报存在二义性的问题(The field xxx is ambiguous)

main方法的语法

Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,数组中保存执行Java命令 时传递给所运行的类的参数。
因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
可以在命令行中传递参数,语法为java 类名 参数1 参数2 ...

设计模式

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。

单例模式 (Singleton)

单例模式解决的问题:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
单例模式实现:如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象, 静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

  1. class Singleton {
  2. // 1.私有化构造器
  3. private Singleton() { }
  4. // 2.内部提供一个当前类的实例,此实例也必须静态化
  5. private static Singleton single = new Singleton();
  6. // 3.提供公共的静态的方法,返回当前类的对象
  7. public static Singleton getInstance() {
  8. return single;
  9. }
  10. }
  1. class Singleton {
  2. // 1.私有化构造器
  3. private Singleton() { }
  4. // 2.内部提供一个当前类的实例,此实例也必须静态化
  5. private static Singleton single;
  6. // 3.提供公共的静态的方法,返回当前类的对象
  7. public static Singleton getInstance() {
  8. if (single == null) {//后续判断无须同步,提高效率
  9. synchronized (Singleton.class) {//同步代码块包含了存在线程安全的代码
  10. if (single == null) {//存在线程安全的判断语句
  11. single = new Singleton();
  12. }
  13. }
  14. }
  15. return single;
  16. }
  17. }

单例模式优点:减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式应用场景:网站计数器、数据库连接池

模板方法设计模式 (TemplateMethod)

模板方法模式解决的问题:在软件开发中实现一个算法时,整体步骤很固定、通用,但是某些部分易变。
模板方法模式实现:当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。不确定部分可以定义为抽象方法,我们称为钩子方法。

  1. abstract class Template {
  2. public final void getTime() {
  3. long start = System.currentTimeMillis();
  4. code();
  5. long end = System.currentTimeMillis();
  6. System.out.println("执行时间是:" + (end - start));
  7. }
  8. public abstract void code();
  9. }
  10. //抽象类(模板方法)的使用
  11. class SubTemplate extends Template {
  12. public void code() {
  13. for (int i = 0; i < 10000; i++) {
  14. System.out.println(i);
  15. }
  16. }
  17. }

代理模式(Proxy)

代理模式解决的问题:代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
代理模式应用场景:安全代理、延迟加载。
代理模式实现:创建代理类来替原对象进行一些操作。

  1. interface Network {
  2. public void browse();
  3. }
  4. // 被代理类
  5. class RealServer implements Network {
  6. @Override
  7. public void browse() {
  8. System.out.println("真实服务器上网浏览信息");
  9. }
  10. }
  11. //代理类
  12. class ProxyServer implements Network {
  13. private Network network;
  14. public ProxyServer(Network network) {
  15. this.network = network;
  16. }
  17. public void check() {
  18. System.out.println("检查网络连接、地址转换、安全等操作");
  19. }
  20. public void browse() {
  21. check();
  22. network.browse();
  23. }
  24. }
  25. //实际使用
  26. public class ProxyDemo {
  27. public static void main(String[] args) {
  28. Network net = new ProxyServer(new RealServer());
  29. net.browse();
  30. }
  31. }

工厂模式(Factory Method 工厂方法/Abstract Factory 抽象工厂)

工厂模式解决的问题:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

工厂模式分类:

  1. 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
  2. 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)【简单工厂模式只有一个(对于一个项目或者一个独立的模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类】
  3. 抽象工厂模式:用来生产不同产品族的全部产品。(不能增加新的产品;支持增加产品族)

异常处理

异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。

Java程序在执行过程中所发生的异常事件可分为两类:

  1. Error:运行过程中出现JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError(栈溢出)和OutOfMemoryError(OOM,堆内存耗尽)
  2. Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理

运行时异常:是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
编译时异常:是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。

常见异常类型及其体系结构:

  • java.lang.Throwable
  • |——-java.lang.Error:一般不编写针对性的代码进行处理
  • |——-java.lang.Exception:可以进行异常的处理
  • |———编译时异常(checked)
  • |——-IOException
  • |——-FileNotFoundException
  • |——-ClassNotFoundException
  • |———运行时异常(unchecked,RuntimeException)
  • |——-NullPointerException
  • |——-ArrayIndexOutOfBoundsException
  • |——-ClassCastException
  • |——-NumberFormatException
  • |——-InputMismatchException
  • |——-ArithmeticException

异常处理机制:Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时的系统,这个过程称为抛出 (throw)异常。

异常对象常用的方法有:

  1. getMessage()获取异常信息,返回字符串
  2. printStackTrace() 打印异常类名和异常信息,以及异常出现在程序中的位置

异常处理的两种方式:一是通过try-catch-finally语句实现;二是向上传递throws。

  1. try{
  2. ...... //可能产生异常的代码
  3. }
  4. catch( ExceptionName1 e ){
  5. ...... //当产生ExceptionName1型异常时的处置措施
  6. }
  7. catch( ExceptionName2 e ){
  8. ...... //当产生ExceptionName2型异常时的处置措施
  9. }
  10. [ finally{
  11. ...... //无论是否发生异常,都无条件执行的语句,优先于其它位置的return语句执行
  12. } ]

向上传递异常(throws):如果一个方法执行中可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理, 而由方法的调用者负责处理。

  1. public void method() throws ExceptionName1,ExceptionName2{
  2. //方法体
  3. throw e;//可手动抛出异常
  4. }

用户自定义异常类:用户自己的异常类必须继承现有的异常类,提供serialVersionUID,并编写重载的构造器。自定义的异常需手动throw抛出。

  1. public class RuntimeException extends Exception {
  2. //提供serialVersionUID
  3. @java.io.Serial
  4. static final long serialVersionUID = -7034897190745766939L;
  5. //提供空参构造器
  6. public RuntimeException() {
  7. super();
  8. }
  9. //提供带异常信息的参数构造器
  10. public RuntimeException(String message) {
  11. super(message);
  12. }
  13. }

其它

JavaBean

JavaBean是一种Java语言写成的可重用组件,具体是指符合如下标准的Java类:

  1. 类的访问权限是公共的
  2. 提供无参的公共构造器
  3. 具有属性,并提供相应的get和set方法

UML

UML是统一建模语言,是一种可视化的面向对象建模语言,用来对真实物理世界进行建模的标准标记,用图形方式表现典型的面向对象系统的整个结构。

具体类在类图中用矩形框表示,矩形框分为三层:第一层是类名字;第二层是类的成员变量;第三层是类的方法。成员变量以及方法前的访问修饰符用符号来表示:

  • “+”表示 public
  • “-”表示 private
  • “#”表示 protected
  • 不带符号表示 default

字符编码

常见的编码表 ASCII:美国标准信息交换码,用一个字节的7位可以表示 GB2312:中国的中文编码表,最多两个字节编码所有字符 GBK:中国的中文编码表升级,融合了更多的中文文字符号。变长的编码方式,最多两个字节编码,用最高位是 1 或 0 表示使用两个字节或一个字节 Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的文字都用两个字节来表示 UTF-8:变长的编码方式,可用1-4个字节来表示一个字符

Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。常用的Unicode编码方案有UTF-16和UTF-8

Snipaste_2022-05-05_17-11-32.png
如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内,因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。

关于Java中的string.length的说明 返回字符串的长度,这一长度等于字符串中的Unicode代码单元的数目。Java中有内码和外码这一区分,简单来说内码是char或String等在内存里使用的编码方式,除了内码都可以认为是“外码”(包括class文件的编码)而java内码使用的是UTF-16。UTF-16的16指的就是最小为16位一个单元,也即两字节为一个单元,UTF-16可以包含一个单元和两个单元,对应即是两个字节和四个字节。以下音符字符的代码点是U+1D11E,但它的代理单元是U+D834和U+DD1E,令字符str = “uD834uDD1E”,那么机器会识别它是2个代码单元代理,但是是1个代码点,故而,length的结果是代码单元数量2,而codePointCount()的结果是代码点数量1

  1. String str= "hello𝄞";
  2. System.out.println(str.codePointCount(0, str.length()));//6
  3. System.out.println(str.length());//7
  4. char[] chars = str.toCharArray();
  5. for(int i = 0;i<chars.length;i++){
  6. System.out.print(chars[i]+"-");
  7. }//h-e-l-l-o-?-?-

对象序列化

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流(字节数据),从而可以把这种字节数据持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。任何实现了Serializable接口的对象都可以转化为字节数据,使其在保存和传输时可被还原。

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID ,该序列化版本标识符用来表明类的不同版本间的兼容性。在进行反序列化时,JVM会把字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常