Java笔记1.0

java编程笔记1.0

一、概述

软件开发

软件,即一系列按照特定顺序组织的计算机数据和指令的集合。有系统软件和应用软件之分。

图形化界面(Graphical User Interface GUI)这种方式简单直观,使用 者易于接受,容易上手操作。

命令行方式(Command Line Interface CLI):需要有一个控制台,输 入特定的指令,让计算机完成一些操作。较为麻烦,需要记录住一些 命令。

常用的DOS命令

dir : 列出当前目录下的文件以及文件夹

md : 创建目录 rd : 删除目录

cd : 进入指定目录

cd… : 退回到上一级目录

cd: 退回到根目录

del : 删除文件

exit : 退出 dos 命令行

补充:echo javase>1.doc

计算机语言

人与计算机交流的方式

计算机语言的历史

第一代语言 : 机器语言。指令以二进制代码形式存在。

第二代语言 :汇编语言。使用助记符表示一条机器指令。

第三代语言:高级语言

​ C、Pascal、Fortran面向过程的语言

​ C++面向过程/面向对象

​ Java跨平台的纯面向对象的语言

​ .NET跨语言的平台

​ Python、Scala…

JAVA语言概述

出生:是SUN(Stanford University Network,斯坦福大学网络公司 ) 1995年推出的一 门高级编程语言。

java是一种面向Internet的编程语言。Java一开始富有吸引力是因为Java程序可以 在Web浏览器中运行。这些Java程序被称为Java小程序(applet)。applet使 用现代的图形用户界面与Web用户进行交互。 applet内嵌在HTML代码中。

随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。

后台开发:

Java、PHP、Python、Go、Node.js

Java技术体系平台
Java SE(Java Standard Edition)标准版

支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核 心API,此版本以前称为J2SE

Java EE(Java Enterprise Edition)企业版

是为开发企业环境下的应用程序提供的一套解决方案。该技术体系中包含的技术如 :Servlet 、Jsp等,主要针对于Web应用程序开发。版本以前称为J2EE

java ME(Java Micro Edition)小型版

支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,并加 入了针对移动终端的支持,此版本以前称为J2ME

Java Card

支持一些Java小程序(Applets)运行在小内存设备(如智能卡)上的平台

Java在各领域的应用

从Java的应用领域来分,Java语言的应用方向主要表现在以下几个方面:

• 企业级应用:

主要指复杂的大企业的软件系统、各种类型的网站。Java的安全机制以及 它的跨平台的优势,使它在分布式系统领域开发中有广泛应用。应用领域包括金融、电 信、交通、电子商务等。

• Android平台应用:

Android应用程序使用Java语言编写。Android开发水平的高低 很大程度上取决于Java语言核心能力是否扎实。

• 大数据平台开发:

各类框架有Hadoop,spark,storm,flink等,就这类技术生态 圈来讲,还有各种中间件如flume,kafka,sqoop等等 ,这些框架以及工具大多数 是用Java编写而成,但提供诸如Java,scala,Python,R等各种语言API供编程。

• 移动领域应用:

主要表现在消费和嵌入式领域,是指在各种小型设备上的应用,包括手 机、PDA、机顶盒、汽车通信设备等。

主要特性
• Java语言是安全的。

Java通常被用在网络环境中,为此,Java提供了一个安全机 制以防恶意代码的攻击。如:安全防范机制(类ClassLoader),如分配不同的 名字空间以防替代本地的同名类、字节代码检查。

• Java语言是体系结构中立的。

Java程序(后缀为java的文件)在Java平台上被 编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个 Java平台的任何系统中运行。

• Java语言是解释型的。

如前所述,Java程序在Java平台上被编译为字节码格式, 然后可以在实现这个Java平台的任何系统的解释器中运行。

• Java是性能略高的。

与那些解释型的高级脚本语言相比,Java的性能还是较优的。

• Java语言是原生支持多线程的。

在Java语言中,线程是一种特殊的对象,它必须 由Thread类或其子(孙)类来创建。

Java语言运行机制及运行过程
java的特点

特点一:面向对象

​ 两个基本概念:类、对象

​ 三大特性:封装、继承、多态

特点二:健壮性

​ 吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与 释放等),提供了一个相对安全的内存管理和访问机制

特点三:跨平台性

​ 跨平台性:通过Java语言编写的应用程序在不同的系统平台上都可以运行。“Write once , Run Anywhere”

原理:

只要在需要运行 java 应用程序的操作系统上,先安装一个Java虚拟机 (JVM Java Virtual Machine) 即可。由JVM来负责Java程序在该系统中的运行。

Java语言运行机制及运行过程
Java两种核心机制
Java虚拟机 (Java Virtal Machine)

​ JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指 令,管理数据、内存、寄存器。

​ 对于不同的平台,有不同的虚拟机。

​ 只有某平台提供了对应的java虚拟机,java程序才可在此平台运行

Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”

java语言的运行机制
image-20210716170936945
垃圾收集机制 (Garbage Collection)
不再使用的内存空间应回收—— 垃圾回收。

​ 在C/C++等语言中,由程序员负责回收无用内存。

​ Java 语言消除了程序员回收无用内存空间的责任:它提供一种系统级线程跟踪存储空 间的分配情况。并在JVM空闲时,检查并释放那些可被释放的存储空间。

​ 垃圾回收在Java程序运行过程中自动进行,程序员无法精确控制和干预。

Java程序还会出现内存泄漏和内存溢出的问题

Java语言的环境搭建
JDK(Java Development Kit Java开发工具包)

​ JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了 JRE。所以安装了JDK,就不用在单独安装JRE了。

​ 其中的开发工具:编译工具(javac.exe) 打包工具(jar.exe)等

JRE(Java Runtime Environment Java运行环境)

​ 包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等, 如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

即:使用JDK的开发工具完成的java程序,交给JRE去运行。

JDK,JRE,JVM之间的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vhqGO2Q-1631502259878)(image-20210716171006849.png)]

image-20210716171027747
第一个程序“HelloWorld”
编写Test.java
public class Test{
    public static void main(String[] args) {
        System.out.println(Hello World!);
    }
}
编译javac Test.java

​ 有了.java源文件,通过编译器将其编译成JVM可以识别的字节码文件。

​ 在该源文件目录下,通过javac编译工具对Test.java文件进行编译。

​ 如果程序没有错误,没有任何提示,但在当前目录下会出现一个Test.class文 件,该文件称为字节码文件,也是可以执行的java的程序。

运行

​ 有了可执行的java程序(Test.class字节码文件)

​ 通过运行工具java.exe对字节码文件进行执行。

注 释(comment)
单行注释

格式: //注释文字

多行注释

/ 注释文字 /

对于单行和多行注释,被注释的文字,不会被JVM(java虚拟机)解释执行。

文档注释 (java特有)

格式:

/**

​ @author 指定java程序的作者

​ @version 指定源文件的版本

*/

注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形 式体现的该程序的说明文档。

文档注释编译格式如下

javadoc -d mydoc -author -version Test.java

第一个程序总结

Java源文件以“java”为扩展名。源文件的基本组成部分是类(class),

**Java应用程序的执行入口是main()方法。它有固定的书写格式: **

public static void main(String[] args) {…}

Java语言严格区分大小写。

Java方法由一条条语句构成,每个语句以“;”结束。

大括号都是成对出现的,缺一不可。

一个源文件中最多只能有一个public类。其它类的个数不限,如果源文件包含 一个public类,则文件名必须按该类名命名。

Java API的文档
API (Application Programming Interface,应用程序编程接口)

​ 是 Java 提供 的基本编程接口

Java语言提供了大量的基础类,

​ 因此 Oracle 也为这些基础类提供了相应的 API文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。

良好的编程风格

正确的注释和注释风格

​ 使用文档注释来注释整个类或整个方法。

​ 如果注释方法中的某一个步骤,使用单行或多行注释。

正确的缩进和空白

​ 使用一次tab操作,实现缩进

​ 运算符两边习惯性各加一个空格。比如:2 + 4 * 5。

块的风格

​ Java API 源代码选择了行尾风格

二、变量与运算符

image-20210716171426474
关键字的定义和特点

定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)

特点:关键字中所有字母都为小写

官方地址: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html

关键字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMVSGGvc-1631502259879)(image-20210716171451720.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0eFJ5VnV-1631502259882)(image-20210716171510485.png)]

保留字(reserved word)

Java保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使 用。自己命名标识符时要避免使用这些保留字 goto 、const

标识符(Identifier)

​ Java 对各种变量、方法和类等要素命名时使用的字符序列称为标识符

​ 技巧:凡是自己可以起名字的地方都叫标识符。

定义合法标识符规则:

​ 由26个英文字母大小写,0-9 ,_或 $ 组成

​ 数字不可以开头。

​ 不可以使用关键字和保留字,但能包含关键字和保留字。

​ Java中严格区分大小写,长度无限制。

​ 标识符不能包含空格。

Java中的名称命名规范
包名:多单词组成时所有字母都小写:xxxyyyzzz
类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个 单词首字母大写:xxxYyyZzz
常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
注意1:在起名字时,为了提高阅读性,要尽量有意义,“见名知意”。
注意2:java采用unicode字符集,因此标识符也可以使用汉字声明,但是不建议使用。

变量

变量的概念:

​ 内存中的一个存储区域

​ 该区域的数据可以在同一类型范围内不断变化

​ 变量是程序中最基本的存储单元。包含变量类型、变量名和存储的值

变量的作用:

​ 用于在内存中保存数据

使用变量注意:

Java中每个变量必须先声明,后使用

​ 使用变量名来访问这块区域的数据

变量的作用域:其定义所在的一对{ }内

​ 变量只有在其作用域内才有效

​ 同一个作用域内,不能定义重名的变量

声明变量

​ 语法:<数据类型> <变量名称>

​ 例如:int var;

变量的赋值

​ 语法:<变量名称> = <值>
​ 例如:var = 10;

声明和赋值变量

​ <数据类型> <变量名> = <初始化值>

​ 例如:int var = 10;

变量的分类-按数据类型

​ 对于每一种数据都定义了明确的具体数据类型(强类型语言),在内存中分 配了不同大小的内存空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C0eTvs2I-1631502259883)(image-20210716172036550.png)]

变量的分类-按声明的位置的不同

在方法体外,类体内声明的变量称为成员变量。

在方法体内部声明的变量称为局部变量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZiT3qEG3-1631502259884)(image-20210716172122571.png)]

整数类型:byte、short、int、long

Java各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保 证java程序的可移植性。

java的整型常量默认为 int 型,声明long型常量须后加‘l’或‘L’

java程序中变量通常声明为int型,除非不足以表示较大的数,才使用long

image-20210716172432610

**500MB 1MB = 1024KB 1KB= 1024B B= byte ? bit? **

bit: 计算机中的最小存储单位。byte:计算机中基本存储单元。

浮点类型:float、double

与整数类型类似,Java 浮点类型也有固定的表数范围和字段长度,不受具体操作 系统的影响。

浮点型常量有两种表示形式:

​ 十进制数形式:如:5.12 512.0f .512 (必须有小数点)

​ 科学计数法形式:如:5.12e2 512E2 100E-2

float:单精度,尾数可以精确到7位有效数字。很多情况下,精度很难满足需求。

double:双精度,精度是float的两倍。通常采用此类型。

Java 的浮点型常量默认为double型,声明float型常量,须后加‘f’或‘F’。

字符类型:char

char 型数据用来表示通常意义上“字符”(2字节)

Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字 母,一个汉字,或其他书面语的一个字符。

字符型变量的三种表现形式:

字符常量是用单引号(‘ ’)括起来的单个字符。

​ 例如:char c1 = ‘a’; char c2 = ‘中’; char c3 = ‘9’;

Java中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量

​ 例如:char c3 = ‘\n’; // '\n’表示换行符

直接使用 Unicode 值来表示字符型常量

​ ‘\uXXXX’。其中,XXXX代表 一个十六进制整数。如:\u000a 表示 \n。

char类型是可以进行运算的。因为它都对应有Unicode码。

布尔类型:boolean

boolean 类型用来判断逻辑条件,一般用于程序流程控制:

​ if条件控制语句;

​ while循环控制语句;

​ do-while循环控制语句;

​ for循环控制语句;

boolean类型数据只允许取值true和false,无null。

​ 不可以使用0或非 0 的整数替代false和true,这点和C语言不同。

​ Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达所操作的 boolean值,在编译之后都使用java虚拟机中的int数据类型来代替:

true用1表示,false 用0表示。———《java虚拟机规范 8版》

ASCII 码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VIBhwn47-1631502259885)(image-20210716175910041.png)]

缺点:

 不能表示所有字符。  相同的编码表示的字符不一样:比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gime(ג) 。

Unicode 编码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G6PNWemm-1631502259886)(image-20210716180136604.png)]

UTF-8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iRGuQtGH-1631502259888)(image-20210716180209364.png)]

基本数据类型转换

自动类型转换:

容量小的类型自动转换为容量大的数据类型。数据类型按容 量大小排序为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nmOAQgE9-1631502259889)(image-20210716181106972.png)]

有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。

byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型。

boolean类型不能与其它数据类型运算。

当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类 型的值将自动转化为字符串(String)类型。

强制类型转换

自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符:(),但可能造成精度降低或溢出,格外要注意。

通常,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。

如: String a = “43”; int i = Integer.parseInt(a);

boolean类型不可以转换为其它的数据类型。

字符串类型:String

String不是基本数据类型,属于引用数据类型

使用方式与基本数据类型一致。例如:String str = “abcd”;

	**一个字符串可以串接另一个字符串,也可以直接串接其他类型的数据。**

例如: str = str + “xyz” ; int n = 100; str = str + n;

进 制

所有数字在计算机底层都以二进制形式存在。

对于整数,有四种表示方式:

二进制(binary):0,1 ,满2进1.以0b或0B开头。

十进制(decimal):0-9 ,满10进1。

八进制(octal):0-7 ,满8进1. 以数字0开头表示。

十六进制(hex):0-9及A-F,满16进1. 以0x或0X开头表示。此处的A-F不区分大小写。 如:0x21AF +1= 0X21B0

image-20210716183754435 image-20210716183821275
二进制

Java整数常量默认是int类型,当用二进制定义整数时,其第32位是符号位; 当是long类型时,二进制默认占64位,第64位是符号位

二进制的整数有如下三种形式:

​ 原码:直接将一个数值换成二进制数。最高位是符号位

​ 负数的反码:是对原码按位取反,只是最高位(符号位)确定为1。

​ 负数的补码:其反码加1。

计算机以二进制补码的形式保存所有的整数。

​ 正数的原码、反码、补码都相同

​ 负数的补码是其反码+1

image-20210716184400835
二进制到十进制
image-20210716184841262

计算机底层都以补码的方式来存储数据!

128 64 32 16 8 4 2 1

2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0

运算符

**运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。 **

**算术运算符 **
image-20210716191252684

如果对负数取模,可以把模数负号忽略不记

​ 如:5%-2=1。 但被模数是 负数则不可忽略。此外,取模运算的结果不一定总是整数。

对于除号“/”,它的整数除和小数除是有区别的

​ 整数之间做除法时,只 保留整数部分而舍弃小数部分。 例如:int x=3510;x=x/1000*1000; x的 结果是?

“+”除字符串相加功能外,还能把非字符串转换成字符串.

​ 例如: System.out.println(“5+5=”+5+5); //打印结果是? 5+5=55 ?

**赋值运算符 **

符号:=

当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制 类型转换原则进行处理。

​ 支持连续赋值。

*扩展赋值运算符: +=, -=, =, /=, %=

**比较运算符(关系运算符) **

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5LEwpwR4-1631502259890)(image-20210716191839702.png)]

比较运算符的结果都是boolean型,也就是要么是true,要么是false。

比较运算符“==”不能误写成“=” 。

**逻辑运算符 **
image-20210716191930236 image-20210716191953253

逻辑运算符用于连接布尔型表达式,在Java中不可以写成33 & x<6 。

“&”和“&&”的区别:

​ 单&时,左边无论真假,右边都进行运算;

​ 双&时,如果左边为真,右边参与运算,如果左边为假,那么右边不参与运算。

“|”和“||”的区别同理,||表示:当左边为真,右边不参与运算。

异或( ^ )与或( | )的不同之处是:当左右都为true时,结果为false。

理解:异或,追求的是“异”!

**位运算符 **
image-20210716192404714

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkpSBCx8-1631502259891)(image-20210716192517071.png)]

位运算是直接对整数的二进制进行的运算

位运算的例题
image-20210716192704772 image-20210716192735092 image-20210716192757767 image-20210716192821296 image-20210716192856589
三元运算符

格式

(条件表达式)?表达式1:表达式2;

​ 表达式为true,运算后的结果是表达式1;

​ 表达式为false,运算后的结果是表达式2;

表达式1和表达式2为同种类型

三元运算符与if-else的联系与区别:

​ 1)三元运算符可简化if-else语句

​ 2)三元运算符要求必须返回一个结果。

​ 3)if后的代码块可有多个语句

运算符的优先级
image-20210716193244972

程序流程控制

• 流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组 合成能完成一定功能的小逻辑模块。

其流程控制方式采用结构化程序设计中规定的三种基本流程结构,即:

顺序结构

程序从上到下逐行地执行,中间没有任何判断和跳转

image-20210716193751761
分支结构

​ **根据条件,选择性地执行某段代码。 **

有if…else和switch-case两种分支语句。

if…else
image-20210716193956947 image-20210716194031932

**条件表达式必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量 **

**语句块只有一条执行语句时,一对{}可以省略,但建议保留 **

**if-else语句结构,根据需要可以嵌套使用  当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略 **

当多个条件是“互斥”关系时,条件判断语句及执行语句间顺序无所谓 当多个条件是“包含”关系时,“小上大下 / 子上父下”

switch-case
image-20210716194328703 image-20210716194413529
switch语句有关规则

switch(表达式)中表达式的值必须是下述几种类型之一:

byte,short, char,int,枚举 (jdk 5.0),String (jdk 7.0);

case子句中的值必须是常量,不能是变量名或不确定的表达式值;

同一个switch语句,所有case子句中的常量值互不相同;

break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有break,程序会顺序执行到switch结尾

default子句是可任选的。同时,位置也是灵活的。当没有匹配的case时, 执行default

循环结构

**根据循环条件,重复性的执行某段代码。 **

​ **有while、do…while、for三种循环语句。 **

注:JDK1.5提供了foreach循环,方便的遍历集合、数组元素。

image-20210716194943941
for 循环
语法格式
for (①初始化部分; ②循环条件部分; ④迭代部分){
    	③循环体部分;
执行过程

①-②-③-④-②-③-④-②-③-④-…-②

②循环条件部分为boolean类型表达式,当值为false时,退出循环

①初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔

④可以有多个变量更新,用逗号分隔

image-20210716195347215
while 循环
语法格式
①初始化部分
while(②循环条件部分){
	  ③循环体部分;
	  ④迭代部分;
}
执行过程:

①-②-③-④-②-③-④-②-③-④-…-②

注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。

for循环和while循环可以相互转换

应用
image-20210716195814803
do-while 循环
语法格式
①初始化部分;
do{
	③循环体部分
	④迭代部分
}while(②循环条件部分);
执行过程:

①-③-④-②-③-④-②-③-④-…②

do-while循环至少执行一次循环体。

应用案例
image-20210716200054212

最简单“无限” 循环格式:while(true) , for(;😉,无限循环存在的原因是并不 知道循环多少次,需要根据循环体内部某些条件,来控制循环的结束。

class PositiveNegative {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int positiveNumber = 0;//统计正数的个数
        int negativeNumber = 0;//统计负数的个数
        for(;;){ //while(true){
            System.out.println("请输入一个整数:");
            int z = scanner.nextInt();
            if(z>0)
                positiveNumber++;
            else if(z<0)
                negativeNumber++;
            else
        break;
}
     System.out.println("正数的个数为:"+ positiveNumber);
     System.out.println("负数的个数为:"+ negativeNumber); 
 } 
}
嵌套循环(多重循环)

​ **将一个循环放在另一个循环体内,就形成了嵌套循环。其中, for ,while ,do…while均可以作为外层循环或内层循环。 **

​ **实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的 循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开 始下一次的循环。 **

设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。

特殊关键字的使用:
break 语句

break语句用于终止某个语句块的执行

break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是 哪一层语句块

image-20210716200655682
continue的使用

continue只能使用在循环结构中

​ **continue语句用于跳过其所在循环语句块的一次执行,继续下一次循环 **

continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环

continue语句举例
public class ContinueTest {
    public static void main(String args[]){
        for (int i = 0; i < 100; i++) {
            if (i%10==0)
                continue;
            System.out.println(i);
        }
    }
}
return的使用

return:并非专门用于结束循环的,它的功能是结束一个方法。 当一个方法执行到一个return语句时,这个方法将被结束。

与break和continue不同的是,return直接结束整个方法,不管 这个return处于多少层循环之内

特殊流程控制语句说明

break只能用于switch语句和循环语句中。

continue 只能用于循环语句中。

二者功能类似,但continue是终止本次循环,break是终止本层循环。

break、continue之后不能有其他的语句,因为程序永远不会执行其后的语句。

标号语句必须紧接在循环的头部。标号语句不能用在非循环语句的前面。

很多语言都有goto语句,goto语句可以随意将控制转移到程序中的任意一条 语句上,然后执行它。但使程序容易出错。Java中的break和continue是不同 于goto的。

数组

数组的概述

数组(Array),是多个相同类型数据按一定顺序排列 的集合,并使用一个名字命名,并通过编号的方式 对这些数据进行统一管理。

数组的常见概念

数组名,下标(或索引),元素,数组的长度

数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括 基本数据类型和引用数据类型。

创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是 这块连续空间的首地址。

数组的长度一旦确定,就不能修改。

我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。

数组的分类:

按照维度:一维数组、二维数组、三维数组、…

按照元素的数据类型分:

基本数据类型元素的数组

引用数据类型元素的数组(即对象数组)

一维数组的使用
声明方式:

type var[]

type[] var;

int a[];
int[] a1;
double b[];
String[] c; //引用类型变量数组

Java语言中声明数组时不能指定其长度(数组中元素的数)

一维数组的使用:初始化

动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行

int[] arr = new int[3];
arr[0] = 3;
arr[1] = 9;
arr[2] = 8;

System.out.println("++++++++++++++++++++++");

String names[];
names = new String[3];
names[0] = "钱学森";
names[1] = "邓稼先";
names[2] = "袁隆平";

静态初始化:在定义数组的同时就为数组元素分配空间并赋值。

int arr[] = new int[]{ 3, 9, 8};
int[] arr = {3,9,8};
System.out.println("+++++++++++++++");
String names[] = {
“李四光”,“茅以升”,“华罗庚”
}
数组元素的引用

定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;

数组元素的引用方式:数组名[数组元素下标]

​ **数组元素下标可以是整型常量或整型表达式。**如a[3] , b[i] , c[6*i];

数组元素下标从0开始;长度为n的数组合法下标取值范围:

​ 0 —>n-1;如int a[]=new int[3]; 可引用的数组元素为a[0]、a[1]、a[2]

每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长 度(元素个数)

数组一旦初始化,其长度是不可变的

数组元素的默认初始化值

数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。

public class Test {
    public static void main(String argv[]){
        int a[]= new int[5];
        System.out.println(a[3]); //a[3]的默认值为0
    }
}

**对于基本数据类型而言,默认初始化值各有不同 **

对于引用数据类型而言,默认初始化值为null(注意与0不同!)

数组元素的默认初始化值
image-20210716203632719
基本数据类型数组

Java中使用关键字new来创建数组

案例一

public class Test{
    public static void main(String args[]){
        int[] s;
        s = new int[10];
        for ( int i=0; i<10; i++ ) {
            s[i] =2*i+1;
            System.out.println(s[i]);
        }
    }
}
image-20210716203803665

案例二

public class Test{
    public static void main(String args[]){
        int[] s;
        s = new int[10];
        //int[] s=new int[10];
        //基本数据类型数组在显式赋值之前,
        //Java会自动给他们赋默认值。
        for ( int i=0; i<10; i++ ) {
            s[i] =2*i+1;
            System.out.println(s[i]);
        }
    }
}
image-20210716204019079

案例三

public class Test{
    public static void main(String args[]){
        int[] s;
        s = new int[10];
        for ( int i=0; i<10; i++ ) {
            s[i] =2*i+1;
            System.out.println(s[i]);
        }
    }
}
image-20210716204243724 image-20210716204332105
二维数组[][]:数组中的数组
初始化
image-20210716204924392 image-20210716205001327
二维数组的内存解析
image-20210716205057952
二分法查找算法
//二分法查找:要求此数组必须是有序的。
int[] arr3 = new int[]{-99,-54,-2,0,2,33,43,256,999};
boolean isFlag = true;
int number = 256;
//int number = 25;
int head = 0;//首索引位置
int end = arr3.length - 1;//尾索引位置
while(head <= end){
    int middle = (head + end) / 2;
    if(arr3[middle] == number){
        System.out.println("找到指定的元素,索引为:" + middle);
        isFlag = false;
        break;
    }else if(arr3[middle] > number){
        end = middle - 1;
    }else{//arr3[middle] < number
        head = middle + 1;
    }
}
if(isFlag){
    System.out.println("未找打指定的元素");
}
衡量排序算法的优劣:

1.时间复杂度:分析关键字的比较次数和记录的移动次数

2.空间复杂度:分析排序算法中需要多少辅助内存

3.稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保 持不变,则称这种排序算法是稳定的。

算法的5大特征
image-20210716205511770

说明:满足确定性的算法也称为:确定性算法。现在人们也关注更广泛的概念,例如考虑各种非确定性的算法,如并行算法、概率算法等。另外人们也关注并不要求终止的计算描述,这种描述有时被称为过程(procedure)。

各种内部排序方法性能比较

**1.从平均时间而言:**快速排序最佳。但在最坏情况下时间性能不如堆排序和归 并排序。

**2.从算法简单性看:**由于直接选择排序、直接插入排序和冒泡排序的算法比较 简单,将其认为是简单算法。对于Shell排序、堆排序、快速排序和归并排序 算法,其算法比较复杂,认为是复杂排序。

**3.从稳定性看:**直接插入排序、冒泡排序和归并排序时稳定的;而直接选择排 序、快速排序、 Shell排序和堆排序是不稳定排序

4.从待排序的记录数n的大小看,n较小时,宜采用简单排序;而n较大时宜采 用改进排序。

image-20210716205735225
Arrays工具类的使用
image-20210716205811570

面向对象

image-20210716212603149 image-20210716212618684
面向过程与面向对象

二者都是一种思想,面向对象是相对于面向过程而言的。

面向过程,强调的 是功能行为,以函数为最小单位,考虑怎么做。

面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

面向对象的三大特征

封装 (Encapsulation)

继承 (Inheritance)

多态 (Polymorphism)

类(Class)和对象(Object)是面向对象的核心概念。

​ **类是对一类事物的描述,是抽象的、概念上的定义 **

对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。

类 = 抽象概念的人;对象 = 实实在在的某个人

面向对象程序设计的重点是类的设计

类的设计,其实就是类的成员的设计

java 的类以及类的成员

修饰符 class 类名 {
    属性声明;
    方法声明;
}
说明:修饰符public:类可以被任意访问
类的正文要用{ }括起来
属性

Field = 属性 = 成员变量,

对应类中的成员变量

格式

​ **修饰符 数据类型 属性名 = 初始化值 ; **

说明1: 修饰符

常用的权限修饰符有:private、缺省、protected、public

其他修饰符:static、final (暂不考虑)

说明2:数据类型

任何基本数据类型(如int、Boolean) 或 任何引用数据类型。

说明3:属性名

属于标识符,符合命名规则和规范即可

分类
image-20210718152524316

注意:二者在初始化值方面的异同:

同:都有生命周期

异:局部变量除形参外,均需显式初始化。

成员变量

在方法体外,类体内声明的变量称为成员变量。

局部变量

在方法体内部声明的变量称为局部变量。

image-20210718152625269
对象属性的默认初始化赋值

当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型。

image-20210718152952865
方法

Method = (成员)方法 = 函数

对应类中的成员方法

方法(method、函数)

​ **方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中 也称为函数或过程。 **

​ **将功能封装为方法的目的是,可以实现代码重用,简化代码 **

Java里的方法不能独立存在,所有的方法必须定义在类里。

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

修饰符:public,缺省,private, protected等

返回值类型:

没有返回值:void

有返回值,声明出返回值的类型。与方法体中“return 返回值”搭配使用

**方法名 :**属于标识符,命名时遵循标识符命名规则和规范,“见名知意”

**形参列表 :**可以包含零个,一个或多个参数。多个参数时,中间用“,”隔开

**返回值 :**方法在执行完毕后返还给调用它的程序的数据。

方法的分类
按照是否有形参及返回值
image-20210718153617579
方法的调用

方法通过方法名被调用,且只有被调用才会执行。

调用过程
image-20210718153719426

**方法被调用一次,就会执行一次 **

没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可 以不必使用return语句。如果使用,仅用来结束方法。

定义方法时,方法的结果应该返回给调用者,交由调用者处理。

方法中只能调用方法或属性,不可以在方法内部定义方法。

对象数组内存解析
image-20210718154122813
方法的重载(overload)
概念

在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数 类型不同即可。

重载的特点:

与返回值类型无关,**只看参数列表,且参数列表必须不同。(参数个数,或参数类型也包括参数类型的顺序)。**调用时,根据方法参数列表的不同来区别。

重载示例:
//返回两个整数的和
int add(int x,int y){
    return x+y;
}

//返回三个整数的和
int add(int x,int y,int z){
    return x+y+z;
}

//返回两个小数的和
double add(double x,double y){
    return x+y;
}



public class PrintStream {
    public static void print(int i) {……}
    public static void print(float f) {……}
    public static void print(String s) {……}
    public static void main(String[] args) {
        print(3);
        print(1.2f);
        print("hello!");
    }
}
可变个数的形参

Varargs机制

//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);



public void test(String[] msg){
    System.out.println(“含字符串数组参数的test方法 ");
} 
public void test1(String book){
    System.out.println(****与可变形参方法构成重载的test1方法****");
}
public void test1(String ... books){
    System.out.println("****形参长度可变的test1方法****");
}
                       
public static void main(String[] args){
    TestOverload to = new TestOverload();
       //下面两次调用将执行第二个test方法
    to.test1();
    to.test1("aa" , "bb");
    //下面将执行第一个test方法
    to.test(new String[]{"aa"});
}

**1. 声明格式:方法名(参数的类型名 …参数名) **

**2. 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个 **

**3. 可变个数形参的方法与同名的方法之间,彼此构成重载 **

**4. 可变参数方法的使用与方法参数部分使用数组是一致的 **

**5. 方法的参数部分有可变形参,需要放在形参声明的最后 **

6. 在一个方法的形参位置,最多只能声明一个可变个数形参

方法参数的值传递机制

方法,必须由其所在类或对象调用才有意义。若方法含有参数:

​ 形参:方法声明时的参数

​ 实参:方法调用时实际传给形参的参数值

Java的实参值传入方法

Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本 (复制品)传入方法内,而参数本身不受影响。

​ 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参

​ 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

匿名对象

格式

new Person().shout(); 

使用情况

​ 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。

​ 我们经常将匿名对象作为实参传递给一个方法调用

基本数据类型的参数传递

public static void main(String[] args) {
    int x = 5;
    System.out.println("修改之前x = " + x);// 5
    // x是实参
    change(x);
    System.out.println("修改之后x = " + x);// 5
}

public static void change(int x) {
    System.out.println("change:修改之前x = " + x);
    x = 3;
    System.out.println("change:修改之后x = " + x);
}
image-20210718160715138

引用数据类型的参数传递

类的访问机制:

**在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。 **

(例外:static方法访问非static,编译不通过。)

在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中 定义的成员

案例一

public static void main(String[] args) {
    Person obj = new Person();
    obj.age = 5;
    System.out.println("修改之前age = " + obj.age);// 5
    // x是实参
    change(obj);
    System.out.println("修改之后age = " + obj.age);// 3
}
public static void change(Person obj) {
    System.out.println("change:修改之前age = " + obj.age);
    obj.age = 3;
    System.out.println("change:修改之后age = " + obj.age);
}
//其中Person类定义为:
class Person{
    int age;
}
image-20210718161128725

案例二

public static void main(String[] args) {
    Person obj = new Person();
    obj.age = 5;
    System.out.println("修改之前age = " + obj.age);// 5
    // x是实参
    change(obj);
    System.out.println("修改之后age = " + obj.age);// 5
}
public static void change(Person obj) {
    obj = new Person();							//类的内部重新new了一个Person
    System.out.println("change:修改之前age = " + obj.age);
    obj.age = 3;
    System.out.println("change:修改之后age = " + obj.age);
}
//其中Person类定义为:
class Person{
    int age;
}
image-20210718161958662

案例三

class DataSwap {
	public int a;
	public int b;
}

public class TransferTest2 {
	public static void swap(DataSwap ds) {
		int temp = ds.a;		//temp = 5
		ds.a = ds.b;			//ds.b = 10
		ds.b = temp;			//ds.a = a
		System.out.println("swap方法里,a Field的值是" + ds.a + ";b Field的值是" + ds.b);
		}
	
	public static void main(String[] args) {
		DataSwap ds = new DataSwap();
		ds.a = 5;
		ds.b = 10;
		swap(ds);
		System.out.println("交换结束后,a Field的值是" + ds.a + ";b Field的值是" + ds.b);
		}
}
image-20210718164530077
实例化类
格式
对象

java类的实例化,即创建类的对象

类名 对象名 = new 类名();

使用“对象名.对象成员”的方式访问对象成员(包括属性和方法)

public class Zoo{
    public static void main(String args[]){        
        Animal xb=new Animal();		//创建对象
        xb.legs=4;					//访问属性
        System.out.println(xb.legs);//输出属性值
        xb.eat();					//访问方法
        xb.move();					//访问方法
    }
}

/**
        定义一个Animal类
        定义int legs属性
        和eat(),move()方法
*/


public class Animal {			//定义一个Animal类
    public int legs;			//定义int legs属性
    public void eat(){			//定义eat()方法
        System.out.println(Eating.);
    }
    public viod move(){			//定义move()方法
        System.out.println(Move.);
    }
}
对象的内存解析

​ **堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。 **

​ **栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。 局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、 char 、 short 、 int 、 float 、 long 、 double)、对象引用(reference类型, 它不等同于对象本身,是对象在堆内 存的首地址)。 方法执行完,自动释 放。 **

方法区(Method Area),用于存储已 被虚拟机加载的类信息、常量、静态 变量、即时编译器编译后的代码等数据。

image-20210718151610638
class PersonTest{
    public static void main(String[] args) { //程序运行的内存布局如下图
        Person p1 = new Person();
        Person p2 =new Person();
        p1.age = -30;
        p1.shout();
        p2.shout();
    }
}
image-20210718151222864
递归(recursion)方法

递归方法:一个方法体内调用它自身。

方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执 行无须循环控制。

递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死 循环。

//计算1-100之间所有自然数的和
public int sum(int num){
    if(num == 1){
        return 1;
    }else{
        return num + sum(num - 1);
    }
}
封装与隐藏

我们程序设计追求“高内聚,低耦合”。

高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;

低耦合 :仅对外暴露少量的方法用于使用。

​ 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。

信息的封装和隐藏

​ Java中通过**将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,**以实现下述目的:

​ 隐藏一个类中不需要对外提供的实现细节;

​ 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑, 限制对属性的不合理操作;

​ 便于修改,增强代码的可维护性;

class Animal {
    private int legs;// 将属性legs定义为private,只能被Animal类内部访问
    public void setLegs(int i) { // 在这里定义方法 eat() 和 move()
        if (i != 0 && i != 2 && i != 4) {
            System.out.println("Wrong number of legs!");
            return;
        }
        legs = i;
    }
    public int getLegs() {
        return legs;
    }
}
public class Zoo {
    public static void main(String args[]) {
        Animal xb = new Animal();
        xb.setLegs(4); // xb.setLegs(-1000);
        //xb.legs = -1000; // 非法
        System.out.println(xb.getLegs());
    }
}
四种访问权限修饰符

Java权限修饰符public、protected、(缺省)、private置于类的成员定义前, 用来限定对象对该类成员的访问权限。

image-20210718165855075

**对于class的权限修饰只可以用public和default(缺省)。 **

​ **public类可以在任意地方被访问。 **

default类只可以被同一个包内部的类访问。

image-20210718170009741
构造器(或构造方法)

构造器的特征

它具有与类相同的名称

​ **它不声明返回值类型。(与声明为void不同) **

不能被static、final、synchronized、abstract、native修饰,不能有 return语句返回值

构造器的作用:

创建对象;给对象进行初始化

语法格式:
修饰符 类名 (参数列表) { 
    初始化语句; 
} 


public class Animal {
    private int legs;
    // 构造器
    public Animal() {
        legs = 4;
    }
    public void setLegs(int i) {
        legs = i;
    }
    public int getLegs() {
        return legs;
    }
}
构造器分类

根据参数不同,构造器可以分为如下两类:

隐式无参构造器(系统默认提供)

显式定义一个或多个构造器(无参、有参)

​ **Java语言中,每个类都至少有一个构造器 **

​ **默认构造器的修饰符与所属类的修饰符一致 **

​ **一旦显式定义了构造器,则系统不再提供默认构造器 **

​ **一个类可以创建多个重载的构造器 **

父类的构造器不可被子类继承

构造器重载

构造器重载,参数列表必须不同

构造器一般用来创建对象的同时初始化对象。

class Person{
    String name;
    int age;
    public Person(String n , int a){ 
        name=n; age=a;
    }
}

构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。

public class Person{
    public Person(){}
    public Person(String name, int age, Date d) {this(name,age);}
    public Person(String name, int age) {}
    public Person(String name, Date d) {}
}

//构造器重载举例
public class Person {
    private String name;
    private int age;
    private Date birthDate;
    
    public Person(String n, int a, Date d) {
        name = n;
        age = a;
        birthDate = d;
    }
    public Person(String n, int a) {
        name = n;
        age = a;
    }
    public Person(String n, Date d) {
        name = n;
        birthDate = d;
    }
    public Person(String n) {
        name = n;
        age = 30;
    }
}
属性赋值过程
赋值的位置:

① 默认初始化

​ **② 显式初始化 **

​ **③ 构造器中初始化 **

④ 通过“对象.属性“或“对象.方法”的方式赋值

赋值的先后顺序:① - ② - ③ - ④

JavaBean

JavaBean是一种Java语言写成的可重用组件。

所谓javaBean,是指符合如下标准的Java类:

​ **类是公共的 **

​ **有一个无参的公共的构造器 **

有属性,且有对应的get、set方法

用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以 用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP 页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用 户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关 心任何改变。

案例

public class JavaBean {
    private String name; // 属性一般定义为private
    private int age;
    public JavaBean() {
        
    }
    
    public int getAge() {
        return age;
    }
    public void setAge(int a) {
        age = a;
    }
    public String getName() {
        return name;
    }
    public void setName(String n) {
        name = n;
    }
}
UML类图
image-20210718184308258
this的使用

**它在方法内部使用,即这个方法所属对象的引用; **

它在构造器内部使用,表示该构造器正在初始化的对象。

this 可以调用类的属性、方法和构造器

当在方法内需要用到调用该方法的对象时,就用this。 具体的:我们可以用this来区分属性和局部变量。

this调用属性方法

**1. 在任意方法或构造器内,如 果使用当前类的成员变量或成 员方法可以在其前面添加this, 增强程序的阅读性。不过,通 常我们都习惯省略this。 **

**2. 当形参与成员变量同名时, 如果在方法内或构造器内需要 使用成员变量,必须添加this来 表明该变量是类的成员变量 **

3.使用this访问属性和方法时, 如果在本类中未找到,会从父 类中查找

class Person{ // 定义Person类
    private String name ;
    private int age ;
    public Person(String name,int age){
        this.name = name ;
        this.age = age ; 
    }
    public void getInfo(){
        System.out.println("姓名:" + name) ;
        this.speak();
    }
    public void speak(){
        System.out.println(“年龄:” + this.age);
    }
}
使用this调用本类的构造器

this可以作为一个类中 构造器相互调用的特殊 格式

class Person{ // 定义Person类
    private String name ;
    private int age ;
    public Person(){ // 无参构造器
        System.out.println("新对象实例化") ;
    }
    public Person(String name){
        this(); // 调用本类中的无参构造器
        this.name = name ;
    }
    public Person(String name,int age){
        this(name) ; // 调用有一个参数的构造器
        this.age = age;
    }
    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age ;
    }
}

**可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器! **

**明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器 **

**如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了 “this(形参列表)” **

**"this(形参列表)"必须声明在类的构造器的首行! **

在类的一个构造器中,最多只能声明一个"this(形参列表)"

关键字:package

package语句作为Java源文件的第一条语句,指明该文件中定义的类所在 的包。(若缺省该语句,则指定为无名包)。

它的格式为: package 顶层包名.子包名 ;

pack1\pack2\PackageTest.java
    package pack1.pack2; //指定类PackageTest属于包pack1.pack2
public class PackageTest{
    public void display(){
        System.out.println("in method display()");
    }
}

包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;

包通常用小写单词标识。通常使用所在公司域名的倒置:com.atguigu.xxx

**包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式 **

**包可以包含类和子包,划分项目层次,便于管理 **

**解决类命名冲突的问题 **

控制访问权限

MVC设计模式
image-20210718190643832
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数据库编程的相关类/接口

  7. java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这 些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S

关键字:import

为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类 或全部类(.*)。import语句告诉编译器到哪里去寻找类。

语法格式: import 包名. 类名;

注意

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

继承性(inheritance)

为什么要有继承?

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中, 那么多个类无需再定义这些属性和行为,只要继承那个类即可。

此处的多个类称为子类(派生类),单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类”

类继承语法规则:

class Subclass extends SuperClass{ }

**作用: **

​ **继承的出现减少了代码冗余,提高了代码的复用性。 **

继承的出现,更有利于功能的扩展。

继承的出现让类与类之间产生了关系,提供了多态的前提。

不要仅为了获取其他类中某个功能而去继承

子类继承了父类,就继承了父类的方法和属性。

**在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和 方法。 **

在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集, 而是对父类的“扩展”

继承的规则:

子类不能直接访问父类中私有的(private)的成员变量和方法。

Java只支持单继承和多层继承,不允许多重继承

**一个子类只能有一个父类 **

**一个父类可以派生出多个子类 **

**class SubDemo extends Demo{ } //ok **

class SubDemo extends Demo1,Demo2…//error

方法的重写

定义:

在子类中可以根据需要对从父类中继承来的方法进行改造,也称 为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。

要求:
  1. 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表

  2. 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型

  3. 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限

    子类不能重写父类中声明为private权限的方法

  4. 子类方法抛出的异常不能大于父类被重写方法的异常

    注意:

    子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),

    同时声明为 static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

案例

public class Person {
    public String name;
    public int age;
    public String getInfo() {
        return "Name: "+ name + "\n" +"age: "+ age;
    }
}
public class Student extends Person {
    public String school;
    public String getInfo() { //重写方法
        return "Name: "+ name + "\nage: "+ age
            + "\nschool: "+ school;
    }
    public static void main(String args[]){
        Student s1=new Student();
        s1.name="Bob";
        s1.age=20;
        s1.school="school2";
        System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
    }
}

案例二

class Parent {
    public void method1() {
        
    }
}
class Child extends Parent {
    //非法,子类中的method1()的访问权限private比被覆盖方法的访问权限public小
    private void method1() {
        
    }
}
public class UseBoth {
    public static void main(String[] args) {
        Parent p1 = new Parent();
        Child c1 = new Child();
        p1.method1();
        c1.method1();
    }
}
关键字:super

在Java类中使用super来调用父类中的指定操作:

super可用于访问父类中定义的属性

super可用于调用父类中定义的成员方法

super可用于在子类构造器中调用父类的构造器

注意:

尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员

super的追溯不仅限于直接父类

super和this的用法相像,this代表本类对象的引用,super代表父类的内存 空间的标识

案例

class Person {
    protected String name = "张三"; 
    protected int age;
    public String getInfo() {
        return "Name: " + name + "\nage: " + age;
    }
}
class Student extends Person {
    protected String name = "李四";
    private String school = "New Oriental";
    public String getSchool() {
        return school;
    }
    public String getInfo() {
        return super.getInfo() + "\nschool: " + school;
    }
}
public class StudentTest {
    public static void main(String[] args) {
        Student st = new Student();
        System.out.println(st.getInfo());
    }
}
调用父类的构造器

**子类中所有的构造器默认都会访问父类中空参数的构造器 **

**当父类中没有空参数的构造器时,子类的构造器必须通过this(参 数列表)或者super(参数列表)语句指定调用本类或者父类中相应的 构造器。同时,只能”二选一” ,且必须放在构造器的首行 **

如果子类构造器中既未显式调用父类或本类的构造器,且父类中又 没有无参的构造器,则编译出错

案例

public class Person {
    private String name;
    private int age;
    private Date birthDate;
    public Person(String name, int age, Date d) {
        this.name = name;
        this.age = age;
        this.birthDate = d;
    }
    public Person(String name, int age) {
        this(name, age, null);
    }
    public Person(String name, Date d) {
        this(name, 30, d);
    }
    public Person(String name) {
        this(name, 30);
    }
}
this和super的区别
image-20210718193428854
子类对象实例化过程
image-20210718193519847
多态性

**多态性,是面向对象中最重要的概念,在Java中的体现: **

​ **对象的多态性:父类的引用指向子类的对象 **

​ **可以直接应用在抽象类和接口上 **

Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明 该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。

**简 称:编译时,看左边;运行时,看右边。 **

**若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism) **

多态情况下,

**“看左边” :看的是父类的引用(父类中不具备子类特有的方法) **

“看右边” :看的是子类的对象(实际运行的是子类重写父类的方法)

**对象的多态 —在Java中,子类的对象可以替代父类的对象使用 **

**一个变量只能有一种确定的数据类型 **

**一个引用类型变量可能指向(引用)多种不同类型的对象 **

Person p = new Student(); 

Object o = new Person();	//Object类型的变量o,指向Person类型的对象 

o = new Student(); 			//Object类型的变量o,指向Student类型的对象 

子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向 上转型(upcasting)。

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

属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编 译错误。

案例

public class Test {
    public void method(Person e) {
        // ……
        e.getInfo();
    }
    public static void main(Stirng args[]) {
        Test t = new Test();
        Student m = new Student();
        t.method(m); // 子类的对象m传送给父类类型的参数e
    }
}
虚拟方法调用(Virtual Method Invocation)

正常的方法调用

Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();

虚拟方法调用(多态情况下)

子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父 类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法 确定的。

Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法

编译时类型和运行时类型

编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类 的getInfo()方法。——动态绑定

instanceof 操作符

**x instanceof A:检验x是否为类A的对象,返回值为boolean型。 **

**要求x所属的类与类A必须是子类和父类的关系,否则编译错误。 **

如果x属于类A的子类B,x instanceof A值也为true。

public class Person extends Object {}
public class Student extends Person {}
public class Graduate extends Person {}
-------------------------------------------------------------------
public void method1(Person e) {
    if (e instanceof Person)
        // 处理Person类及其子类对象
        if (e instanceof Student)
            //处理Student类及其子类对象
            if (e instanceof Graduate)
                //处理Graduate类及其子类对象
            }

对象类型转换 (Casting )

基本数据类型的Casting:

自动类型转换:

**小的数据类型可以自动转换成大的数据类型 **

**long g=20; **

**double d=12.0f **

强制类型转换:

**可以把大的数据类型强制转换(casting)成小的数据类型 **

**float f=(float)12.0; **

int a=(int)1200L

**对Java对象的强制类型转换称为造型 **

​ **从子类到父类的类型转换可以自动进行 **

​ **从父类到子类的类型转换必须通过造型(强制类型转换)实现 **

​ **无继承关系的引用类型间的转换是非法的 **

在造型前可以使用instanceof操作符测试一个对象的类型

案例一

public class ConversionTest {
    public static void main(String[] args) {
        double d = 13.4;
        long l = (long) d;
        System.out.println(l);
        int in = 5;
        // boolean b = (boolean)in;
        Object obj = "Hello";
        String objStr = (String) obj;
        System.out.println(objStr);
        Object objPri = new Integer(5);
        // 所以下面代码运行时引发ClassCastException异常
        String str = (String) objPri;
    }
}

案例二

public class Test {
    public void method(Person e) { // 设Person类中没有getschool() 方法
        // System.out.pritnln(e.getschool()); //非法,编译时错误
        if (e instanceof Student) {
            Student me = (Student) e; // 将e强制转换为Student类型
            System.out.pritnln(me.getschool());
        }
    }
    public static void main(String[] args){
        Test t = new Test();
        Student m = new Student();
        t.method(m);
    }
}
image-20210718194937727
子类继承父类

若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的 同名方法,系统将不可能把父类里的方法转移到子类中。

对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的 实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量

Object类的使用

**Object类是所有Java类的根父类 **

如果在类的声明中未使用extends关键字指明其父类,则默认父类 为java.lang.Object类

image-20210718195222809
equals方法

equals():所有类都继承了Object,也就获得了equals()方法。还可以重写。

只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。

格式:obj1.equals(obj2)

特例:当用equals()方法进行比较时,对类File、String、Date及包装类 (Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对 象;

原因:在这些类中重写了Object类的equals()方法。

当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都 相等

重写equals()方法的原则

对称性:如果x.equals(y)返回是“true” ,那么y.equals(x)也应该返回是 “true”。

自反性:x.equals(x)必须返回是“true”。

传递性:如果x.equals(y)返回是“true” ,而且y.equals(z)返回是“true” , 那么z.equals(x)也应该返回是“true”。

一致性:如果x.equals(y)返回是“true” ,只要x和y内容一直不变,不管你 重复x.equals(y)多少次,返回都是“true”。

任何情况下,x.equals(null),永远返回是“false” ; x.equals(和x不同类型的对象)永远返回是“false”。

==和equals的区别

**1 == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型 就是比较内存地址 **

**2 equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也 是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中 用的比较多,久而久之,形成了equals是比较值的错误观点。 **

**3 具体要看自定义类里有没有重写Object的equals方法来判断。 **

4 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。

toString() 方法

**toString()方法在Object类中定义,其返回值是String类型,返回类名和它 的引用地址。 **

**在进行String与其它类型数据的连接操作时,自动调用toString()方法 **

**Date now=new Date(); **

**System.out.println(“now=”+now); **

**相当于 **

**System.out.println(“now=”+now.toString()); **

**可以根据需要在用户自定义类型中重写toString()方法 如String 类重写了toString()方法,返回字符串的值。 **

**s1=“hello”; System.out.println(s1);//相当于System.out.println(s1.toString()); **

**基本类型数据转换为String类型时,调用了对应包装类的toString()方法 **

int a=10; System.out.println(“a=”+a);

包装类(Wrapper)

**针对八种基本数据类型定义相应的引用类型—包装类(封装类) **

有了类的特点,就可以调用类中的方法,Java才是真正的面向对象

image-20210718195740120
装箱

基本数据类型包装成包装类的实例

通过包装类的构造器实现:

int i = 500; Integer t = new Integer(i);

通过字符串参数构造包装类对象:

Float f = new Float(4.56);
Long l = new Long(“asdf”); //NumberFormatException
拆箱

获得包装类对象中包装的基本类型变量

调用包装类的.xxxValue()方法

boolean b = bObj.booleanValue();
字符串转换成基本数据类型

通过包装类的构造器实现:

int i = new Integer(12);

通过包装类的parseXxx(String s)静态方法:

Float f = Float.parseFloat(12.1);
基本数据类型转换成字符串

调用字符串重载的valueOf()方法:

String fstr = String.valueOf(2.34f);

直接的方式:

String intStr = 5 + “”
基本类型、包装类与String类间的转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQNheYHW-1631502259894)(image-20210718200459445.png)]

包装类用法举例

装箱:包装类使得一个基本数据类型的数据变成了类。 有了类的特点,可以调用类中的方法。

int i = 500;
Integer t = new Integer(i);

String s = t.toString(); // s = “500“,t是类,有toString方法
String s1 = Integer.toString(314); // s1= “314“ 将数字转换成字符串。
String s2=4.56;
double ds=Double.parseDouble(s2); //将字符串转换成数字

拆箱:将数字包装类中内容变为基本数据类型。

包装类在实际开发中用的最多的在于字符串变为基本数据类型。

int j = t.intValue(); // j = 500,intValue取出包装类中的数据
String str1 = "30" ;
String str2 = "30.3" ;
int x = Integer.parseInt(str1) ; // 将字符串变为int型
float f = Float.parseFloat(str2) ; // 将字符串变为int型

关键字:static

泛型

前言

相信大家日常开发中,经常看到Java对象“implements Serializable”。那么,它到底有什么用呢?本文从以下几个角度来解析序列这一块知识点~

  • 什么是Java序列化?
  • 为什么需要序列化?
  • 序列化用途
  • Java序列化常用API
  • 序列化的使用
  • 序列化底层
  • 日常开发序列化的注意点
  • 序列化常见面试题

一、什么是Java序列化?

  • 序列化:把Java对象转换为字节序列的过程
  • 反序列:把字节序列恢复为Java对象的过程

二、为什么需要序列化?

Java对象是运行在JVM的堆内存中的,如果JVM停止后,它的生命也就戛然而止。


如果想在JVM停止后,把这些对象保存到磁盘或者通过网络传输到另一远程机器,怎么办呢?磁盘这些硬件可不认识Java对象,它们只认识二进制这些机器语言,所以我们就要把这些对象转化为字节数组,这个过程就是序列化啦~

打个比喻,作为大城市漂泊的码农,搬家是常态。当我们搬书桌时,桌子太大了就通不过比较小的门,因此我们需要把它拆开再搬过去,这个拆桌子的过程就是序列化。 而我们把书桌复原回来(安装)的过程就是反序列化啦。

三、序列化用途

序列化使得对象可以脱离程序运行而独立存在,它主要有两种用途:

  • 1) 序列化机制可以让对象地保存到硬盘上,减轻内存压力的同时,也起了持久化的作用;

比如 Web服务器中的Session对象,当有 10+万用户并发访问的,就有可能出现10万个Session对象,内存可能消化不良,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

  • 2) 序列化机制让Java对象在网络传输不再是天方夜谭。

我们在使用Dubbo远程调用服务框架时,需要把传输的Java对象实现Serializable接口,即让Java对象序列化,因为这样才能让对象在网络上传输。

四、Java序列化常用API

java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.Serializable
java.io.Externalizable
Serializable 接口

Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。

public interface Serializable {
}
Externalizable 接口

Externalizable继承了Serializable接口,还定义了两个抽象方法:writeExternal()和readExternal(),如果开发人员使用Externalizable来实现序列化和反序列化,需要重写writeExternal()和readExternal()方法

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
java.io.ObjectOutputStream类

表示对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行序列化,再把得到的字节序列写到一个目标输出流中。

java.io.ObjectInputStream

表示对象输入流,
它的readObject()方法,从输入流中读取到字节序列,反序列化成为一个对象,最后将其返回。

五、序列化的使用

序列化如何使用?来看一下,序列化的使用的几个关键点吧:

  • 声明一个实体类,实现Serializable接口
  • 使用ObjectOutputStream类的writeObject方法,实现序列化
  • 使用ObjectInputStream类的readObject方法,实现反序列化
声明一个Student类,实现Serializable
public class Student implements Serializable {

    private Integer age;
    private String name;

    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
使用ObjectOutputStream类的writeObject方法,对Student对象实现序列化

把Student对象设置值后,写入一个文件,即序列化,哈哈~

ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("D:\\text.out"));
Student student = new Student();
student.setAge(25);
student.setName("jayWei");
objectOutputStream.writeObject(student);

objectOutputStream.flush();
objectOutputStream.close();

看看序列化的可爱模样吧,test.out文件内容如下(使用UltraEdit打开):

使用ObjectInputStream类的readObject方法,实现反序列化,重新生成student对象

再把test.out文件读取出来,反序列化为Student对象

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student = (Student) objectInputStream.readObject();
System.out.println("name="+student.getName());

六、序列化底层

Serializable底层

Serializable接口,只是一个空的接口,没有方法或字段,为什么这么神奇,实现了它就可以让对象序列化了?

public interface Serializable {
}

为了验证Serializable的作用,把以上demo的Student对象,去掉实现Serializable接口,看序列化过程怎样吧~

序列化过程中抛出异常啦,堆栈信息如下:

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Student
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:13)

顺着堆栈信息看一下,原来有重大发现,如下~

原来底层是这样:
ObjectOutputStream 在序列化的时候,会判断被序列化的Object是哪一种类型,String?array?enum?还是 Serializable,如果都不是的话,抛出 NotSerializableException异常。所以呀,Serializable真的只是一个标志,一个序列化标志~

writeObject(Object)

序列化的方法就是writeObject,基于以上的demo,我们来分析一波它的核心方法调用链吧~(建议大家也去debug看一下这个方法,感兴趣的话)

writeObject直接调用的就是writeObject0()方法,

public final void writeObject(Object obj) throws IOException {    ......    writeObject0(obj, false);    ......}

writeObject0 主要实现是对象的不同类型,调用不同的方法写入序列化数据,这里面如果对象实现了Serializable接口,就调用writeOrdinaryObject()方法~

private void writeObject0(Object obj, boolean unshared)        throws IOException    {    ......   //String类型    if (obj instanceof String) {        writeString((String) obj, unshared);   //数组类型    } else if (cl.isArray()) {        writeArray(obj, desc, unshared);   //枚举类型    } else if (obj instanceof Enum) {        writeEnum((Enum<?>) obj, desc, unshared);   //Serializable实现序列化接口    } else if (obj instanceof Serializable) {        writeOrdinaryObject(obj, desc, unshared);    } else{        //其他情况会抛异常~        if (extendedDebugInfo) {            throw new NotSerializableException(                cl.getName() + "\n" + debugInfoStack.toString());        } else {            throw new NotSerializableException(cl.getName());        }    }    ......

writeOrdinaryObject()会先调用writeClassDesc(desc),写入该类的生成信息,然后调用writeSerialData方法,写入序列化数据

    private void writeOrdinaryObject(Object obj,                                     ObjectStreamClass desc,                                     boolean unshared)        throws IOException    {            ......            //调用ObjectStreamClass的写入方法            writeClassDesc(desc, false);            // 判断是否实现了Externalizable接口            if (desc.isExternalizable() && !desc.isProxy()) {                writeExternalData((Externalizable) obj);            } else {                //写入序列化数据                writeSerialData(obj, desc);            }            .....    }

writeSerialData()实现的就是写入被序列化对象的字段数据

  private void writeSerialData(Object obj, ObjectStreamClass desc)        throws IOException    {        for (int i = 0; i < slots.length; i++) {            if (slotDesc.hasWriteObjectMethod()) {                   //如果被序列化的对象自定义实现了writeObject()方法,则执行这个代码块                    slotDesc.invokeWriteObject(obj, this);            } else {                // 调用默认的方法写入实例数据                defaultWriteFields(obj, slotDesc);            }        }    }

defaultWriteFields()方法,获取类的基本数据类型数据,直接写入底层字节容器;获取类的obj类型数据,循环递归调用writeObject0()方法,写入数据~

   private void defaultWriteFields(Object obj, ObjectStreamClass desc)        throws IOException    {           // 获取类的基本数据类型数据,保存到primVals字节数组        desc.getPrimFieldValues(obj, primVals);        //primVals的基本类型数据写到底层字节容器        bout.write(primVals, 0, primDataSize, false);        // 获取对应类的所有字段对象        ObjectStreamField[] fields = desc.getFields(false);        Object[] objVals = new Object[desc.getNumObjFields()];        int numPrimFields = fields.length - objVals.length;        // 获取类的obj类型数据,保存到objVals字节数组        desc.getObjFieldValues(obj, objVals);        //对所有Object类型的字段,循环        for (int i = 0; i < objVals.length; i++) {            ......              //递归调用writeObject0()方法,写入对应的数据            writeObject0(objVals[i],                             fields[numPrimFields + i].isUnshared());            ......        }    }

七、日常开发序列化的一些注意点

  • static静态变量和transient 修饰的字段是不会被序列化的
  • serialVersionUID问题
  • 如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化
  • 子类实现了序列化,父类没有实现序列化,父类中的字段丢失问题
static静态变量和transient 修饰的字段是不会被序列化的

static静态变量和transient 修饰的字段是不会被序列化的,我们来看例子分析一波~ Student类加了一个类变量gender和一个transient修饰的字段specialty

public class Student implements Serializable {    private Integer age;    private String name;    public static String gender = "男";    transient  String specialty = "计算机专业";    public String getSpecialty() {        return specialty;    }    public void setSpecialty(String specialty) {        this.specialty = specialty;    }    @Override    public String toString() {        return "Student{" +"age=" + age + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", specialty='" + specialty + '\'' +                '}';    }    ......

打印学生对象,序列化到文件,接着修改静态变量的值,再反序列化,输出反序列化后的对象~

运行结果:

序列化前Student{age=25, name='jayWei', gender='男', specialty='计算机专业'}序列化后Student{age=25, name='jayWei', gender='女', specialty='null'}

对比结果可以发现:

  • 1)序列化前的静态变量性别明明是‘男’,序列化后再在程序中修改,反序列化后却变成‘女’了,what?显然这个静态属性并没有进行序列化。其实,静态(static)成员变量是属于类级别的,而序列化是针对对象的~所以不能序列化哦
  • 2)经过序列化和反序列化过程后,specialty字段变量值由’计算机专业’变为空了,为什么呢?其实是因为transient关键字,它可以阻止修饰的字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如int型的值会被设置为 0,对象型初始值会被设置为null。
serialVersionUID问题

serialVersionUID 表面意思就是序列化版本号ID,其实每一个实现Serializable接口的类,都有一个表示序列化版本标识符的静态变量,或者默认等于1L,或者等于对象的哈希码。

private static final long serialVersionUID = -6384871967268653799L;

serialVersionUID有什么用?

JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。

接下来,我们来验证一下吧,修改一下Student类,再反序列化操作

Exception in thread "main" java.io.InvalidClassException: com.example.demo.Student;local class incompatible: stream classdesc serialVersionUID = 3096644667492403394,local class serialVersionUID = 4429793331949928814	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1876)	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1745)	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2033)	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)	at com.example.demo.Test.main(Test.java:20)

从日志堆栈异常信息可以看到,文件流中的class和当前类路径中的class不同了,它们的serialVersionUID不相同,所以反序列化抛出InvalidClassException异常。那么,如果确实需要修改Student类,又想反序列化成功,怎么办呢?可以手动指定serialVersionUID的值,一般可以设置为1L或者,或者让我们的编辑器IDE生成

private static final long serialVersionUID = -6564022808907262054L;

实际上,阿里开发手册,强制要求序列化类新增属性时,不能修改serialVersionUID字段~

如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化

给Student类添加一个Teacher类型的成员变量,其中Teacher是没有实现序列化接口的

public class Student implements Serializable {        private Integer age;    private String name;    private Teacher teacher;    ...}//Teacher 没有实现public class Teacher  {......}

序列化运行,就报NotSerializableException异常啦

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Teacher	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)	at com.example.demo.Test.main(Test.java:16)

其实这个可以在上小节的底层源码分析找到答案,一个对象序列化过程,会循环调用它的Object类型字段,递归调用序列化的,也就是说,序列化Student类的时候,会对Teacher类进行序列化,但是对Teacher没有实现序列化接口,因此抛出NotSerializableException异常。所以如果某个实例化类的成员变量是对象类型,则该对象类型的类必须实现序列化

子类实现了Serializable,父类没有实现Serializable接口的话,父类不会被序列化。

子类Student实现了Serializable接口,父类User没有实现Serializable接口

//父类实现了Serializable接口public class Student  extends User implements Serializable {    private Integer age;    private String name;}//父类没有实现Serializable接口public class User {    String userId;}Student student = new Student();student.setAge(25);student.setName("jayWei");student.setUserId("1");ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\text.out"));objectOutputStream.writeObject(student);objectOutputStream.flush();objectOutputStream.close();//反序列化结果ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));Student student1 = (Student) objectInputStream.readObject();System.out.println(student1.getUserId());//output/**  * null */

从反序列化结果,可以发现,父类属性值丢失了。因此子类实现了Serializable接口,父类没有实现Serializable接口的话,父类不会被序列化。

八、序列化常见面试题

  • 序列化的底层是怎么实现的?
  • 序列化时,如何让某些成员不要序列化?
  • 在 Java 中,Serializable 和 Externalizable 有什么区别
  • serialVersionUID有什么用?
  • 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
  • 在 Java 序列化期间,哪些变量未序列化?
1.序列化的底层是怎么实现的?

本文第六小节可以回答这个问题,如回答Serializable关键字作用,序列化标志啦,源码中,它的作用啦还有,可以回答writeObject几个核心方法,如直接写入基本类型,获取obj类型数据,循环递归写入,哈哈

2.序列化时,如何让某些成员不要序列化?

可以用transient关键字修饰,它可以阻止修饰的字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如int型的值会被设置为 0,对象型初始值会被设置为null。

3.在 Java 中,Serializable 和 Externalizable 有什么区别

Externalizable继承了Serializable,给我们提供 writeExternal() 和 readExternal() 方法, 让我们可以控制 Java的序列化机制, 不依赖于Java的默认序列化。正确实现 Externalizable 接口可以显著提高应用程序的性能。

4.serialVersionUID有什么用?

可以看回本文第七小节哈,JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。

5.是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?

可以的。我们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。同时,可以声明这些方法为私有方法,以避免被继承、重写或重载。

6.在 Java 序列化期间,哪些变量未序列化?

static静态变量和transient 修饰的字段是不会被序列化的。静态(static)成员变量是属于类级别的,而序列化是针对对象的。transient关键字修字段饰,可以阻止该字段被序列化到文件中。

型的类必须实现序列化

给Student类添加一个Teacher类型的成员变量,其中Teacher是没有实现序列化接口的

public class Student implements Serializable {        private Integer age;    private String name;    private Teacher teacher;    ...}//Teacher 没有实现public class Teacher  {......}

序列化运行,就报NotSerializableException异常啦

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Teacher	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)	at com.example.demo.Test.main(Test.java:16)

其实这个可以在上小节的底层源码分析找到答案,一个对象序列化过程,会循环调用它的Object类型字段,递归调用序列化的,也就是说,序列化Student类的时候,会对Teacher类进行序列化,但是对Teacher没有实现序列化接口,因此抛出NotSerializableException异常。所以如果某个实例化类的成员变量是对象类型,则该对象类型的类必须实现序列化
[外链图片转存中…(img-ErIraCK0-1631502259911)]

子类实现了Serializable,父类没有实现Serializable接口的话,父类不会被序列化。

子类Student实现了Serializable接口,父类User没有实现Serializable接口

//父类实现了Serializable接口public class Student  extends User implements Serializable {    private Integer age;    private String name;}//父类没有实现Serializable接口public class User {    String userId;}Student student = new Student();student.setAge(25);student.setName("jayWei");student.setUserId("1");ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\text.out"));objectOutputStream.writeObject(student);objectOutputStream.flush();objectOutputStream.close();//反序列化结果ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));Student student1 = (Student) objectInputStream.readObject();System.out.println(student1.getUserId());//output/**  * null */

从反序列化结果,可以发现,父类属性值丢失了。因此子类实现了Serializable接口,父类没有实现Serializable接口的话,父类不会被序列化。

八、序列化常见面试题

  • 序列化的底层是怎么实现的?
  • 序列化时,如何让某些成员不要序列化?
  • 在 Java 中,Serializable 和 Externalizable 有什么区别
  • serialVersionUID有什么用?
  • 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
  • 在 Java 序列化期间,哪些变量未序列化?
1.序列化的底层是怎么实现的?

本文第六小节可以回答这个问题,如回答Serializable关键字作用,序列化标志啦,源码中,它的作用啦还有,可以回答writeObject几个核心方法,如直接写入基本类型,获取obj类型数据,循环递归写入,哈哈

2.序列化时,如何让某些成员不要序列化?

可以用transient关键字修饰,它可以阻止修饰的字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如int型的值会被设置为 0,对象型初始值会被设置为null。

3.在 Java 中,Serializable 和 Externalizable 有什么区别

Externalizable继承了Serializable,给我们提供 writeExternal() 和 readExternal() 方法, 让我们可以控制 Java的序列化机制, 不依赖于Java的默认序列化。正确实现 Externalizable 接口可以显著提高应用程序的性能。

4.serialVersionUID有什么用?

可以看回本文第七小节哈,JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。

5.是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?

可以的。我们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。同时,可以声明这些方法为私有方法,以避免被继承、重写或重载。

6.在 Java 序列化期间,哪些变量未序列化?

static静态变量和transient 修饰的字段是不会被序列化的。静态(static)成员变量是属于类级别的,而序列化是针对对象的。transient关键字修字段饰,可以阻止该字段被序列化到文件中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值