JavaSE笔记
- 一、JAVA基础编程
- 二、第一阶段--JAVA基本语法
- 第06章 面向对象编程(基础)
- 第1阶段:Java基本语法
- 第2阶段:Java面向对象编程
- 第3阶段:Java高级应用
一、JAVA基础编程
1.程序
“编写”:代码保存在“.java”
后缀文件中
“编译”:用"javac 源文件名.java"
编译,生成源文件中所有类的"类名.class"
字节码文件
“运行”:用"java 类名"运行生成的字节码文件
2.一个java源文件可以声明多个类,但只能有一个public类,且public类类名必须和源文件名一致
二、第一阶段--JAVA基本语法
2.1 关键字与保留字
-
关键字:被JAVA赋予特殊含义,用作专门用途
-
保留字:后续可能会添加进关键字(goto、const)
-
权限修饰符:private/public/缺省/protected
//使用四种权限修饰符来修饰类和类的内部成员
类:只能使用"public"、"缺省"修饰
类的内部成员:可以使用四种修饰符
- 四种权限修饰符的作用域
- 四种权限修饰符通过定义类及内部成员的可见性,体现了良好的封装特性
2.2 标识符
命名规则:
-
由26个英文字母大小写,0-9 ,_或 $ 组成
-
数字不可以开头。
-
不可以使用关键字和保留字,但能包含关键字和保留字。
-
Java中严格区分大小写,长度无限制。
-
标识符不能包含空格。
命名规范:
- 包名:多单词组成时所有字母都小写:xxxyyyzzz
- 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
- 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个 单词首字母大写:xxxYyyZzz
- 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
2.3 变量
JAVA变量:强类型
注意事项:
- Java中每个变量必须先声明,后使用
- 使用变量名来访问这块区域的数据
- 变量的作用域:其定义所在的一对{ }内
- 变量只有在其作用域内才有效
- 同一个作用域内,不能定义重名的变量
2.3.2 数据类型
基本数据类型:
类型 | 占用存储空间 | 表示范围 |
---|---|---|
byte | 1B | -128 ~ 127 |
short | 2B | $-2^{15} $ ~ $2^{15}-1$ |
int | 4B | $-2^{31}$ ~ $2^{31}-1$ |
long(声明时必须以"l/L"结尾) | 8B | $-2^{63}$ ~ $2^{63}-1$ |
float(F或f结尾) | 4B | |
double | 8B | |
char | 2B | Unicode编码 |
boolean | 1bit |
引用类型:
类、数组、接口、枚举、注解
2.3.3 数据类型转换
- 隐式转换(
由低到高
)
byte -> short -> int -> long -> float -> double
- 强制转换(
可能造成精度丢失
)
double b = 10;
int i = (int)b;
// 不能通过强制转换将String --> 基本数据类型,可以同过包装类方法转换
2.3.4 进制
二进制:0B或0b开头
八进制:0开头
十六进制:0x或0X开头
2.4 运算符
2.41 算术运算符
2.42 赋值运算符
= += -= /= %= ++ -- ,其中+=不会改变变量本身的类型
推荐 +=和++
2.43 比较运算符
①比较运算符的结果位boolean类型
② > < >= <= :只能用在数值类型的数据之间
③ == 和 != : 不仅可以用在数值型数据之间,也能用于引用类型,比较是否是同一对象
2.44 逻辑运算符
& | ^ ! && ||
1、&与&&的异同点
相同点:运算结果相同
不同点:&无论前面的结果如何都会执行后面的,&&如果前面的结果为false则不会执行后面的
2.45 三元运算符
表达式 ? 结果true : 结果false
ps:可嵌套使用
2.46 移位运算符
<<:左移运算符,空位补0
<<:右移运算符,空位补符号位
>>>:无符号右移,空位补0
2.5 从键盘获取输入 Scanner类
1.导包:import java.util.Scanner
- 实例化 Scanner scan = new Scanner(System.in)
- 使用 调用Scanner类的方法 next()或nextXxx() 接受不同的输入
2.6 流程控制结构
- if-else结构
if-else 结构可嵌套
else配对按照就近原则,且else可省略
- switch-case
①根据switch表达式中的值,依次匹配case中的常量,匹配成功则进入该case,直到遇到break或执行结束
②switch表达式支持的类型:byte、short、int、char、枚举类型、String
且case中只能放常量,执行break后跳出switch-case结构
③当switch-case和if-else均适用时,优先选择switch-case,执行效率稍高
2.7 循环结构
分类:
-
for循环
不能用for(;;)表示死循环,for(;true;)可以
for( ; ; ) {} — — 初始条件和迭代部分可以有多个条件,用 ; 分隔
循环条件必须是boolean类型
-
while循环
①初始化部分
while(②循环条件部分){
③循环体部分;
④迭代部分;
}
- do while循环
至少执行一次循环体
①初始化部分;
do{
③循环体部分
④迭代部分
}while(②循环条件部分);
不限制次数的结构:for(;;)和while(true)
-
break和continue
使用范围 作用 相同点 break switch-case、循环 跳出循环结构 若break或continue执行成功, continue 循环 跳过本次循环 则它们之后的语句不会被执行 PS:java中break可以使用标签跳出外层循环
番外篇--软件开发流程
需求分析->项目设计->开发实现->功能测试->部署实施->(项目运营、项目维护)
番外篇--IDEA使用经验
IDEA项目结构
1.层级关系:
project(工程) - 多个module(模块) - 多个package(包) - 多个class(类)
2.Project和Module
在IDEA中Project是最顶级的结构单元,然后就是Module,一个项目通常由多个Module构成,一般按功能划分,模块之间彼此可以相互依赖。
· 当为单Module项目时,这个Module就是一个Project
· 当为多Module项目时,多个模块处于同一个Project中,可具有相互依赖关系
· 当多个模块没有建立依赖关系的话,也可以作为单独一个“小项目”运行
2.8 一维数组
多个相同类型元素按一定顺序排列构成的集合
数组的特点
- 数组本身是
引用数据类型
,而数组中的元素可以是任何数据类型
,包括基本数据类型和引用数据类型。 - 创建数组对象会在内存中开辟一整块
连续的空间
。占据的空间的大小,取决于数组的长度和数组中元素的类型。 - 数组中的元素在内存中是依次紧密排列的,有序的。
- 数组,一旦初始化完成,其长度就是确定的。数组的
长度一旦确定,就不能修改
。 - 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
- 数组名中引用的是这块连续空间的首地址。
2.8.1声明与初始化
2.8.1.1 静态初始化
- 如果数组变量的初始化和数组元素的赋值操作同时进行,那就称为静态初始化
- 静态初始化,本质是用静态数据(编译时已知)为数组初始化。此时数组的长度由静态数据的个数决定
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,...}; //可缩写为int[] a = {1,2,3,...}; //类型匹配
//或
数据类型[] 数组名;
数组名 = new 数据类型[]{元素1,元素2,元素3,...}; //关键字new:堆中开辟一块新的空间
//错误格式1
int[] arr;
arr = {1,2,3};
//错误格式2,静态初始化时不能指定长度
int[] arr = new int[3]{1,2,3};
2.8.1.2 动态初始化
- 数组变量的初始化和数组元素的赋值操作分开进行,即为动态初始化
- 动态初始化中,只确定了数组的长度,并未给元素赋值,此时元素为默认值
- 数组长度一旦确定,便不可更改
//格式2
int[] arr = new int[3];
//格式1
int[] arr1;
arr1 = new int[3];
2.8.2 数组元素调用
- 通过下标调用,数组长度为array.length
- 下标从0开始,可访问[0~n-1]
2.8.3 数组元素的默认值
数据类型 | 默认值 |
---|---|
整型、浮点型 | 0和0.0 |
char型 | \u0000或0 |
boolean型 | false |
引用型 | null |
2.8.4 一维数组的内存解析
- Java中的内存结构是如何划分的?(主要关心JVM的运行时环境)
将内存区域划分为5个部分:程序计数器、虚拟机栈、本地方法栈、堆、方法区
-
与目前数组相关的内存结构:比如 int[] arr = new int []{1,2,3};
虚拟机栈:用于存放方法中声明的变量。如arr
堆:用于存放具体的数组元素(即数组中的所有元素)
数组名存放数组元素实际存放位置的首地址,下标实际为偏移量
2.9 二维数组
本质上并没有二维数组,可把array [i] [j]中[i]看做外层元素,[i] [j]看做内层元素
2.91 初始化
1、静态初始化
int[][] array = new int[][]{{1},{1,2},{1,2,3}}; //右边new int[][]可省略
//等价于
int[][] array = {{1},{1,2},{1,2,3}};
2、动态初始化--1
int[][] array = new int[3][4];
3、动态初始化 -- 可变长二维数组
int[][] array = new int[3][]; //列数可以后续要使用时再分配空间,但必须声明行数。
array[0] = new int[1]; //构建不规则数组
array[1]= new int[2];
2.92 数组元素调用
调用:array[i] [j]
外层元素array[0]、array[1]其实存放的是其内层元素的首地址
System.out.println(array[0]);
二维数组变量名其实存放的是外层元素的首地址
System.out.println(array);
2.93 数组长度
array.length ---- 外层元素个数
array[0].length ---- 外层元素array[0]指向的内层元素个数
2.94 默认值
动态初始化--1
int arr[][] = new int[3][4];
//外层元素默认值:地址
//内层元素默认值:与数组类型相同元素的默认值
动态初始化--2
int[][] arr = new int[3][]; //此时还没有给内层元素分配空间
//外层元素默认值:null
//内层元素无法访问,否则报‘空指针错误'
2.95 二维数组内存解析
- 外层元素其实是引用数据类型
- 内层元素可静态初始化,也可动态初始化
- 外层元素存放的是其指向的内层元素的首地址
- 数组名存放的是外层元素的首地址
int[] arr1 = new int[3];
int[][] arr2 = new int[3][4];
//引用数据类型:arr1、arr2、arr2[k]
//基本数据类型:arr1[k]、arr2[i][j]
内存结构图:
二维数组课后练习
声明:int[] x,y[]; 在给x,y变量赋值以后,以下选项允许通过编译的是:
a) x[0] = y; --no
b) y[0] = x; --yes
c) y[0][0] = x; --no
d) x[0][0] = y; --no
e) y[0][0] = x[0]; --yes
f) x = y; --no
//首先,等号要成立,必须两边满足:
1.两边类型一致 (强制/隐式类型转换,数组是//引用类型)
2.两边维数相等 (y指向二维,而y[0]才是指向一维
2.96 数组排序
下面是一些常用的排序算法及其Java语言实现示例:
- 冒泡排序(Bubble Sort):比较相邻元素,若逆序则交换,依次冒泡最大元素至末尾。
public void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换位置
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
- 选择排序(Selection Sort):找到未排序部分的最小元素,将其与未排序部分的第一个元素交换。
public void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换位置
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
- 插入排序(Insertion Sort):将未排序元素逐个插入已排序部分的正确位置。
public void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
- 快速排序(Quick Sort):选择一个基准元素,将小于基准的元素移到左边,大于基准的元素移到右边,然后递归对左右两部分进行排序。
public void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
private int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
// 交换位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 交换位置
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
第06章 面向对象编程(基础)
1. 面向对象编程思想概述
- 面向过程的程序设计思想(Process-Oriented Programming)**,简称
POP
- 关注的焦点是
过程
:过程就是操作数据的步骤。如果某个过程的实现代码重复出现,那么就可以把这个过程抽取为一个函数
。这样就可以大大简化冗余代码,便于维护。 - 典型的语言:C语言
- 代码结构:以
函数
为组织单位。 - 是一种“
执行者思维
”,适合解决简单问题。扩展能力差、后期维护难度较大。
2. 面向对象的程序设计思想( Object Oriented Programming),简称OOP
- 关注的焦点是
类
:在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。 - 典型的语言:Java、C#、C++、Python、Ruby和PHP等
- 代码结构:以
类
为组织单位。每种事物都具备自己的属性
和行为/功能
。 - 是一种“
设计者思维
”,适合解决复杂问题。代码扩展性强、可维护性高。
2. 基本元素--类和对象
2.1 类和对象概述
类(Class)和
对象(Object)`是面向对象的核心概念。
1、什么是类
类:具有相同特征的事物的抽象描述,是抽象的
、概念上的定义。
2、什么是对象
对象:实际存在的该类事物的每个个体
,是具体的
,因而也称为实例(instance)
。
2.2 类的成员概述
面向对象程序设计的重点是
类的设计
类的设计,其实就是
类的成员的设计
Java中用类class来描述事物也是如此。类,是一组相关属性
和行为
的集合,这也是类最基本的两个成员。
- 属性:该类事物的状态信息。对应类中的
成员变量
- 成员变量 <=> 属性 <=> Field
- 行为:该类事物要做什么操作,或者基于事物的状态能做什么。对应类中的
成员方法
- (成员)方法 <=> 函数 <=> Method
2.3 面向对象完成功能的三步骤(重要)
步骤一:类的定义
类的定义使用关键字:class。格式如下:
[修饰符] class 类名{
属性1;
属性2;
方法1();
方法2();
}
举例1:
public class Person{
//声明属性age
int age ;
//声明方法showAge()
public void eat() {
System.out.println("人吃饭");
}
}
步骤2:创建对象
- 创建对象即类的实例化
- 实例化使用关键词----new
//方式一
Person p1 = new Person();
//方式二:匿名对象
new Person()
步骤3:通过对象调用方法和属性
-
对象是类的一个实例,必然具备该类事物的属性和行为(即方法)。
-
使用"
对象名.属性
" 或 "对象名.方法
"的方式访问对象成员(包括属性和方法)
Person p1 = new Person();
p1.age = 18;
p1.eat() //输出:人吃饭
java中类的结构:
2.4 匿名对像(anonymous object)
-
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
- 如:new Person().shout();
-
使用情况
- 如果一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
3. 对象的内存解析
3.1 JVM内存结构划分
HotSpot Java虚拟机的架构图如下。主要关心的是运行时数据区部分(Runtime Data Area)。
堆(Heap)
:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
栈(Stack)
:是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
方法区(Method Area)
:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
说明:
- 堆:凡是new出来的结构(对象、数组)都放在堆空间中。
- 对象的属性存放在堆空间中。
- 创建一个类的多个对象(比如p1、p2),则每个对象都拥有当前类的一套"副本"(即属性)。当通过一个对象修改其属性时,不会影响其它对象此属性的值。
- 当声明一个新的变量使用现有的对象进行赋值时(比如p3 = p1),此时并没有在堆空间中创建新的对象。而是两个变量共同指向了堆空间中同一个对象。当通过一个对象修改属性时,会影响另外一个对象对此属性的调用。
直接打印对象名和数组名都是显示“类型@对象的hashCode值"
面试题:对象名中存储的是什么呢?
答:对象实例化在堆中的首地址
4. 类的成员之一:成员变量
4.1 如何声明成员变量
[修饰符1] class 类名{
[修饰符2] 数据类型 成员变量名 [= 初始化值];
}
说明:
- 位置要求:必须在类中,方法外
- 修饰符
- 常用的权限修饰符有:private、缺省、protected、public
- 其他修饰符:static、final
- 数据类型
- 任何基本数据类型(如int、Boolean) 或 任何引用数据类型。
- 成员变量名
- 属于标识符,符合命名规则和规范即可。
- 初始化值
- 根据情况,可以显式赋值;也可以不赋值,使用默认值
4.2 成员变量vs局部变量
1. 变量分类
- 成员变量:在方法体外,类内声明
- 局部变量:在方法体内声明
2.成员变量与局部变量的异同
-
同
略
-
不同点
1、声明位置和方式
(1)实例变量:在类中方法外
(2)局部变量:在方法体{}中或方法的形参列表、代码块中
2、在内存中存储的位置不同
(1)实例变量:堆
(2)局部变量:栈
3、生命周期
(1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
而且每一个对象的实例变量是独立的。
(2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
而且每一次方法调用都是独立。
4、作用域
(1)实例变量:通过对象就可以使用,本类中直接调用,其他类中“对象.实例变量”
(2)局部变量:出了作用域就不能使用
5、修饰符
(1)实例变量:public,protected,private,final,volatile,transient等
(2)局部变量:final
6、默认值
(1)实例变量:有默认值
(2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
5. 类的成员之二:方法
5.1 方法的声明
1. 声明方法的语法格式
[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]{
方法体的功能代码
}
2. 方法声明的组成
- 修饰符(可选):public、protected、private、static、abstract、native、final、synchronized
- 返回类型:
- 无返回值:void
- 有返回值:返回类型 方法名()
- 方法名、形参列表、throws异常列表
- return语句的使用
- 1.结束方法的执行,当方法返回类型为void时,可以用此结束方法的执行
- 2.返回方法的结果
- return后不能声明执行语句,否则报
Unreachable code
异常
5.2 方法的内存解析
- 方法
没有被调用
的时候,都在方法区
中的字节码文件(.class)中存储。 - 方法
被调用
的时候,需要进入到栈内存
中运行。方法每调用一次就会在栈中有一个入栈
动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值。 - 当方法执行结束后,会释放该内存,称为
出栈
,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
5.3 方法特性1——重载(Overload)
方法签名: 方法名、参数列表(参数个数和参数类型)
方法重载: 在一个类中声明几个同名而参数列表不同的的方法,称为重载
一个类中不能声明签名相同的方法,要么不同名,要么重载
面试题:
int[] arr = new int[]{1,2,3};
System.out.println(arr);//地址值
char[] arr1 = new char[]{'a','b','c'};
System.out.println(arr1);//abc
//原因:println()存在一个重载方法,参数列表为char[],调用时直接输出数组
boolean[] arr2 = new boolean[]{false,true,true};
System.out.println(arr2);//地址值
5.4 方法特性2——可变个数形参(jdk 5.0新增)
1. 使用场景
在调用方法时,可能会出现方法形参的类型是确定的,但是参数的个数不确定。此时,我们就可以使用可变个数形参的方法
2. 格式:(参数类型 ... 参数名)
public void method1(int ... parameter){}
public void method2(int i,int ... parameter){}
//错误格式
public void method2(int ... parameter,int i){}
public void method1(int[] array){} 不能与method1(int ... parameter)构成重载
3. 说明:
① 可变个数形参的方法在调用时,针对于可变的形参赋的实参的个数可以为:0个、1个或多个
② 可变个数形参的方法与同一个类中,同名的多个方法之间可以构成重载
③ 特例:可变个数形参的方法与同一个类中方法名相同,且与可变个数形参的类型相同的数组参数不构成重载。(*)
④ 可变个数的形参必须声明在形参列表的最后
⑤ 可变个数的形参最多在一个方法的形参列表中出现一次
5.5 值传递机制(重点)
1. 万变不离其宗:
> 如果是基本数据类型的变量,则将此变量保存的数据值传递出去。
> 如果是引用数据类型的变量,则将此变量保存的地址值传递出去。
2.形参和实参
形参和实参分属于不同方法的栈空间,实则是两组变量,靠值传递机制
传递值
- 如果形参是基本数据类型,则直接传递一个
数据值
- 如果形参是引用数据类型,则传递引用类型变量保存的
地址值
类{
int m = 10;
method1(m); //实参
print(m); //m=10
}
public void method1(int m){ //形参
m++;
}
5.6 方法特性3——递归
- 直接递归:方法直接调用自己
- 间接递归:A()方法调用B()方法,B()方法调用C()方法,C()方法调用A()方法。
6. 类的成员之三:构造器
- 作用
用于生成类的实例,可接受传入的参数用于初始化对象相关属性,可重载。
如果类中没有显式构造器,则使用默认构造器,一旦类中显式声明了构造器,则系统不再
提供默认构造器。
- 在字节码文件中以
()方法的形式体现
7. 对象数组
数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用类型中的类时,我们称为对象数组。
7.1 对象数组的内存解析
Student[] stus = new Student[5];
只是在堆
中开辟了存放对象地址的空间,且初始时stus[i]都是null,而stus[0] = new Student();
才正式在堆
中开辟一个存放一个Student对象实例的空间,并将首地址
存放在stus[0]中。
即:
对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
8. 关键字:package、import
8.1 package(包)
package,称为包,用于指明该文件中定义的类、接口等结构所在的包。
package 顶层包名.子包名 ;
说明:
- 一个源文件只能有一个声明包的package语句
- package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。
- 包名,属于标识符,满足标识符命名的规则和规范(全部小写)、见名知意
- 包通常使用所在公司域名的倒置:com.atguigu.xxx。
- 大家取包名时不要使用"
java.xx
"包
- 包对应于文件系统的目录,package语句中用 “.” 来指明包(目录)的层次,每”.“一次就表示一层文件目录。
- 同一个包下可以声明多个结构(类、接口),但是不能定义同名的结构(类、接口)。不同的包下可以定义同名的结构(类、接口)
8.1.1 包的作用
- 包可以包含类和子包,划分
项目层次
,便于管理 - 帮助
管理大型软件
系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式 - 解决
类命名冲突
的问题 - 控制
访问权限
MVC设计模式:
MVC是一种软件构件模式,目的是为了降低程序开发中代码业务的耦合度。
MVC设计模式将整个程序分为三个层次:视图模型(Viewer)层
,控制器(Controller)层
,与数据模型(Model)层
。
8.1.2 JDK中主要的包及其说明
java.lang
----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
java.net
----包含执行与网络相关的操作的类和接口。
java.io
----包含能提供多种输入/输出功能的类。
java.util
----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
java.text
----包含了一些java格式化相关的类
java.sql
----包含了java进行JDBC数据库编程的相关类/接口
java.awt
----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 (不常用)
8.2 import(导入)
为了使用定义在其它包中的Java类,需用import语句来显式引入指定包下所需要的类。相当于import语句告诉编译器到哪里去寻找这个类
。
import 包名.类名;
-
如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
-
如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入。
-
如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。
-
(了解)
import static
组合的使用:调用指定类或接口下的静态的属性或方法
第1阶段:Java基本语法
01~20:第01章_Java语言概述
21~40:第02章_变量与运算符
41~58:第03章_流程控制语句
59~61:第04章_IDEA的安装与使用
62~72:第05章_数组
第2阶段:Java面向对象编程
73~89:第06章_面向对象编程(基础)
- 对象属性赋值
(1) 默认
(2) 显式初始化
(3) 构造器中初始化
(4) object.method(...)或object.field
**PS: ** 以上赋值方式的执行先后顺序自上而下
- JavaBean
JavaBean是指符合以下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
90~104:第07章_面向对象编程(进阶)
1. this关键字
- 场景1:this调用属性
当成员方法或构造器中形参名与属性
重名
时,需要用this.
的方式来区分
- 场景2:this调用构造器
格式:"this(形参列表)"
用法:在当前构造器中调用其他构造器
注意事项:
this作为调用构造器使用时必须放在当前构造器的
首行
最多使用一个
如果一个类中声明了n个构造器,则最多可以有n-1个this调用构造器,避免形成闭环
2.继承
定义:子类继承父类,从而拥有父类的所有属性和方法,但受权限控制可能某些无法直接调用
意义:
- 减少了代码的冗余,提高了代码的复用
- 增加了代码的可扩展性
- 为多态提供了基础
关键字:
extends
java中声明的类,如果没有显式声明其父类,默认的父类都是
java.lang.object
单继承:java中每个类都可以有多个子类,但只能有一个直接父类
构造器:但创建一个子类对象时,会先使用父类的构造器初始化,然后再使用子类的构造器
2.1 方法的重写(override)
定义:子类对从父类继承过来的方法进行重新实现,在子类中覆盖父类的方法,则称为重写
PS: 属性同名也不存在重写
- 规则
(1) 重写的方法必须与父类方法的函数签名(函数名、形参列表)相同
(2) 子类重写的方法的权限修饰符必须不小于父类方法,但无法重写父类中声明为private
的方法
(3) 关于返回值类型
父类方法返回值为void,则子类重写的方法返回值必须是void
父类方法返回值为基本数据类型,则子类重写的方法返回值必须与父类方法相同
父类方法返回值为引用类型,则子类重写的方法返回值必须与父类方法相同或者是被重写的方法返回值类型的子类(当返回值类型为类时)
子类重写的方法抛出的异常类型或与父类方法抛出的异常类型相同,或是父类方法抛出的异常的子类
(4) 扩展知识
在子类中调用方法时,编译器默认的查找顺序是由下到上的,也就是先在子类中查找,然后再到父类中,逐层往上查找
2.2 super关键字
super关键字意为父类的,可以使用super.[属性] [方法]或super(形参列表)的方式来调用父类的属性、方法和构造器
-
在不影响封装性的前提下,子类可以在构造器或方法中使用
super
来显式调用父类的属性和方法,即使子类与父类存在属性同名(不建议)或者方法重写,super
都指向的父类的成员 -
super调用构造器
(1) 子类继承父类时不会继承父类的构造器,只能通过
super(形参)
的方式调用父类的构造器(2) 如果要在子类构造器中显式调用父类构造器,则必须将super语句放在首行
(3) 在使用子类构造器构造对象时,一定会直接或间接地调用父类的构造器
(4) 当子类构造器没有显式调用父类构造器时,相当于系统在首行默认调用父类的空参构造器super()
面试题
当子类与父类存在同名属性时,对于属性的访问存在就近原则。
如果属性可直接在外部通过子类对象访问
object.属性
,则优先访问子类中自定义的属性;如果是通过方法访问,两种情况:
(1)当方法没被子类重写时,此时调用的方法仍是在父类中定义的,因此优先访问父类中的属性
(2)当方法被子类重写,此时访问的就是子类中的属性,除非使用
super
来显式访问
3. 多态性
3.1 多态性的体现
多态性在Java中体现在对象的多态性中,即父类的引用指向子类的对象
3.2 多态的理解
Java引用变量有两个类型:
编译时类型
和运行时类型
。编译时类型由声明
该变量时使用的类型决定,运行时类型由实际赋给该变量的对象
决定。简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
多态使用的前提条件:(1) 类的继承关系 (2) 方法的重写
3.3 多态的作用
当我们声明了一个父类的引用指向子类对象时,编译器
编译时
会认为这是一个父类对象,但实际运行时
则是子类对象在起作用,因此通过这个父类引用调用被子类重写的方法时,运行时实际调用的是子类重写的方法,属性无法重写也就没有多态性。
- 多态的好处
当需要对多个对象进行处理时,可以利用多态性实现动态绑定,从而根据实际被引用的对象来执行相应的方法。代码更加灵活,可维护性和扩展性更好了,符合开闭原则
,减少了大量的重载操作
- 多态的坏处
如果使用一个执行子类对象的父类引用,则该引用无法访问子类独有的属性和方法,只能通过向下转型将该父类引用转为子类引用后才能访问
扩展:
虚方法调用:编译阶段不能确定方法的调用入口地址,在运行阶段才能确定的方法叫虚方法。
在多态情况下,虚方法就是父类被重写的方法,根据赋给的子类对象不同,运行时执行不同的子类方法
3.4 向上转型和向下转型
- 向上转型(多态)
向上转型就是多态,将子类对象赋给父类的引用
- 向下转型
由于多态时,父类引用无法访问子类独有的方法和属性,因此需要向下转型将父类引用转换为子类类型,此时就可以访问子类独有的属性和方法。向下转型语法就是强制类型转换。
扩展:
在向下转型时,可以使用instanceof关键字来判断父类引用实际指向的子类对象类型,避免出现类型转换错误
instanceof语法:
引用变量 intanceof 类类型
,相同返回true
4. Object类
java.lang.Object
类是所有类的超类,每个实现的类都会直接或间接的继承于Object类该类实现了equals、toString、clone等方法
4.1 equals方法
作用:比较引用类型的两个对象是否地址或内容(重写后)相同
-
默认情况下是使用的Object类实现的
equals
方法,比较的是引用的地址 -
像String、File、Date、包装类等重写了
equals
方法的类,比较的就是对象的内容。
注意事项:如果当前类重写了equals方法,并且当前类属性包含自定义类,那么此类也要重写equals方法
4.2 toString方法
自定义的类,在没有重写toString方法时,打印的是对象的
地址值(哈希值)
常用的String/File/Date/包装类等打印的则是对应的内容
调用
println
来打印对象引用变量实际上就是默认调用的toString
方法
105~123:第08章_面向对象编程(高级)
1. static关键字
使用static修饰的属性或方法称为静态变量和静态方法,也称为类变量和类方法
调用方式:类.静态属性
或类.静态方法
,对象也可以直接调用静态结构,但不推荐
PS:当使用
null
的对象来调用静态结构时,不会出现问题
静态变量与实例变量的区别
1.1 内存空间的区别
静态变量:在内存中只有一份,被多个对象共享
实例变量:类的每个实例都保存一份自己的变量
1.2 生命周期的区别
静态变量:静态变量随类的加载而加载,随类的卸载而消亡
实例变量:实例变量随对象的创建而创建,随对象的消亡而消亡
1.3 方法访问权限
静态方法可以被继承但不能被重写,也不能在静态方法中使用this或super关键字
访问权限 | 静态方法或属性 | 实例方法或属性 |
---|---|---|
静态方法 | 可以 | 不可以 |
实例方法 | 可以 | 可以 |
2. 单例设计模式
2.1 概述
设计模式**是在大量的实践中总结
和理论化
之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。"套路"
经典的设计模式共有23种。每个设计模式均是特定环境下特定问题的处理方法。
2.2 单例模式实现
- 饿汉式
写法简单,由于内存中较早加载,使用更方便、更快,线程安全。缺点是内存中占用时间较长。
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
- 懒汉式
在需要时进行创建,节省内存空间。缺点是线程不安全。
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } //线程安全版 public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
3. 类的成员之四——代码块
//静态代码块
static {
}
//非静态代码块
{
}
3.1 作用
用于初始化类和成员,最多只能用static修饰
3.2 分类
- 静态代码块(static修饰)
- 非静态代码块
3.3 二者的区别
静态代码块:
(1) 随着类的加载而执行
(2) 由于类的加载只会执行一次,因此静态代码块也只会执行一次
(3) 静态代码块只能调用静态的属性和方法
非静态代码块:
(1) 随着对象的创建而执行
(2) 每创建一个对象,都会执行一次非静态代码块
(3) 内部可以调用非静态和静态结构
3.4 类属性赋值的先后顺序
类属性的初始化顺序是由父及子
静态代码块 > 非静态代码块 > 构造器
4. final关键字
final主要用来修饰类、方法和变量
- final修饰类表示类不能被继承
- final修饰方法表示方法不能被重写
- final修饰变量表示变量只能被赋值一次,不容许二次赋值,且无默认值
- 成员变量:仅允许显式赋值、构造器或代码块赋值
- 局部变量、形参:调用前必须先赋值,且赋值后不可更改
- 引用类型:final修饰的引用类型变量不可更改其指向的地址,但引用的地址上的
内容
可以更改- final+static可以创建一个全局常量
5.抽象类与抽象方法(abstract)
使用
abstract
来修饰的类或方法叫抽象类或抽象方法
5.1 使用规则
(1)抽象类
- 抽象类不能实例化
- 抽象类可以不包含抽象方法
- 抽象类中也要提供构造方法,供子类实例化
(2)抽象方法
- 抽象方法不能被调用,只有方法声明,没有方法体
- 只有被子类实现过的抽象方法才能被调用
- 包含抽象方法的类必须声明为抽象类
- 抽象类的子类必须重写父类所有的抽象方法,否则仍为抽象类
5.2 注意事项
- abstract关键字只能修饰类和方法
- abstract关键字不能修饰
私有方法
和静态方法
,也不能与final关键字
共用
6. 接口(interface)
6.1 接口的概念
接口本质是一个标准、规范,然后类去实现接口,接口不能实例化
接口主要是常量和抽象方法的集合
6.2 接口的使用
- 使用方法
使用interface声明接口,类使用implements实现接口,一个类可以实现多个接口
- 接口内能声明的结构:
属性:默认为public static final的属性,声明时可以省略
方法:
jdk8之前:只能声明public abstract类型的方法
jdk8:抽象方法、静态方法(有方法体),默认方法default
(有方法体)
public interface USB3{
//静态常量,省略了public static final
long MAX_SPEED = 500*1024*1024;//500MB/s
//抽象方法,省略了public abstract
void in();
void out();
//默认方法,省略了public
default void start(){
System.out.println("开始");
}
default void stop(){
System.out.println("结束");
}
//静态方法,省略了public
static void show(){
System.out.println("USB 3.0可以同步全速地进行读写操作");
}
}
(1) 接口中声明的静态方法只能用该接口名进行调用,不能被继承和重写
(2) 接口中使用
default
修饰的默认方法可以用实现类的对象调用,可继承可重写(3) 接口冲突:两个接口定义了同名的默认函数,实现类同时实现两个接口时会冲突。此时要求实现类必须对该方法进行重写
(4) 类优先原则:父类和接口定义了同名方法(接口是默认方法),在子类没有重写该方法前,默认调用的是父类的方法
(5) 如何指定调用实现于某个接口的同名函数:
接口名.super.方法名
jdk9:抽象方法、静态方法、私有方法(有方法体)
-
实现接口的类必须实现接口内所有的抽象方法
-
接口也具有多态性,接口与实现类构成多态
-
接口没有构造器,没有初始化块
-
继承与实现
类与类之间是继承关系,接口与类之间是实现关系,接口与接口之间是多继承 extends关系
当一个类声明中同时出现
extends
和implements
时,extends
在前面
6.3 接口匿名实现类实例化写法
//假设Car是一个接口,并包含一个抽象方法run()
return new Car(){
//对run()方法的重写实现
...
};
//这样就返回了一个Car接口的匿名实现类实例
6.4 接口与抽象类的区别
7. 类的成员之五——内部类
7.1 概念
在类内部再定义一个类,这个新定义的类称为内部类
使用场景:当一个类A内部还需要一个完整的类B来进行描述,而这个类B又只为A服务,那么就可以将类B定义为内部类,这样的设计遵循高内聚,低耦合的原则。
内部类的分类:
7.2 成员内部类的实例化
假设类A内部定义了一个内部类B
成员内部类的使用特征,概括来讲有如下两种角色:
- 成员内部类作为
类的成员的角色
:
- 和外部类不同,Inner class还可以声明为private或protected;
- 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
- 成员内部类作为
类的角色
:
- 可以在内部定义属性、方法、构造器等结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的,表示不能被继承
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意点:
外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
成员内部类可以直接使用外部类的所有成员,包括私有的数据
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
//静态成员内部类
A.B b = new A.B();
//非静态成员内部类
A a = new A();
A.B b = a.new B();
7.3 匿名内部类实例化
//类与类
new 父类([实参列表]){
重写方法...
}
//类与接口
new 父接口(){
重写方法...
}
8. 枚举类、包装类和注解
8.1 枚举类
本质上也是一个类,通常用于描述有限的几个状态
实现:
//(1)一般实现
enum 类名 {
obj1,
obj2,
...;
}
//(2)这种实现方式,可以直接用"对象(形参)"的方式初始化对象
enum 类名 {
obj1(a,b),
obj2(c,d),
...;
[private] 类名(type v1, type v2){ //构造方法,默认是private类型
...
}
}
/**
枚举类也可以实现接口:
方式一:以类为单位提供一种实现接口的方法
方式二:以对象为单位对接口方法提供多种实现
/
8.2 注解(Annotation)
在jdk5.0引入,注解相当于给编译器或其他程序看的注释,是一种自检和显式注释机制
格式:@注解名
- 如何自定义注解?:以
@SuppressWarnings
作参考
- Annotation 的成员在 Annotation 定义中以
无参数有返回值
的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组 - 可以使用 default 关键字为抽象方法指定默认返回值
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value(); //
//String columnName();
//String columnType();
}
- 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以
省略“value=”
,所以如果注解只有一个抽象方法成员,建议使用方法名value
@Table("t_stu")
public class Student {...}
- 框架 = 注解 + 反射 + 设计模式
- 元注解:
@Target:描述注解的使用范围
@Retation:描述注解的生命周期
@Documented:表明这个注解应该被 javadoc工具记录
@Inherited:允许子类继承父类中的注解
8.3 包装类
- 基本数据类型==>包装类
int a = 10;
Integer a = Integer.valueOf(10);
- 包装类==>基本数据类型
Integer a = Integer.valueOf(10);
int b = a.intValue();
- jdk5.0新特性——自动装箱、自动拆箱
//直接赋值
Integer <=> int
String、基本数据类型、包装类之间的转换
第3阶段:Java高级应用
124~131:第09章_异常处理
异常(Exception)分为:
编译时异常
运行时异常
常见的异常:
编译时异常:
ClassNotFoundException
FileNotFoundException
IOException
运行时异常:
ArrayIndex0ut0fBoundsException
NullPointerException
ClassCastException
NumberFormatException
InputMismatchException
ArithmeticException
异常处理方式一
try{
//可能会抛出异常的代码
}catch(异常类型 e){
//对捕获的异常的处理
}catch(异常类型 e){
}...
finally{
//退出try-catch前的最后操作
}
异常处理方式二
在方法的声明处使用"
方法名 throws 异常类型1, 异常类型2, ...
{方法体}"
可以主动的使用throw + 异常对象
在方法内部抛出异常对象,并且throw
后的代码不能被执行,编译不通过
自定义异常类
步骤:
(1) 继承于现有的异常体系。通常继承于RuntimeException \ Exception
(2) 通常提供几个重载的构造器
(3) 提供一个全局常量,声明为:
static final long serialVersionUID;
(4) 自定义异常只能自己手动抛出
132~141:第10章_多线程
进程:正在内存中运行的应用程序,是操作系统调度和分配资源的最小单位
线程:是进程的一部分,一个线程是程序内部的一条执行路径
线程创建方式一
(1) 创建一个继承于Thread类的子类
(2) 重写Thread类的run()
方法:将此线程要执行的操作放在此方法中
(3) 创建当前Thread子类的对象
(4) 通过对象调用start()
方法
//自定义线程类
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
//测试类:
public class TestMyThread {
public static void main(String[] args) {
//创建自定义线程对象1
MyThread mt1 = new MyThread("子线程1");
//开启子线程1
mt1.start();
//创建自定义线程对象2
MyThread mt2 = new MyThread("子线程2");
//开启子线程2
mt2.start();
//在主方法中执行for循环
for (int i = 0; i < 10; i++) {
System.out.println("main线程!"+i);
}
}
}
线程创建方式二
(1) 创建一个类实现Runnable
接口
(2) 实现run()
方法
(3) 创建当前实现类的对象,将当前对象作为参数
传递到Thread类的构造器中,创建Thread类的实例
(4) 用Thread类的实例调用start()
方法
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
//测试类:
public class TestMyRunnable {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t = new Thread(mr, "长江");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("黄河 " + i);
}
}
}
Java线程常用方法
线程状态相关:
线程优先级相关:
线程.getPriority()
线程.setPriority()
线程生命周期
JDK5.0之后:
线程安全问题及解决
当使用多线程访问同一资源时,可能会出现线程安全问题,主要是多线程写操作
- 解决方法1--同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
/**
说明:
- 需要被同步的代码:操作共享数据的代码
- 共享数据:多个线程都需要操作的数据
- 需要被同步的代码在被synchronized包含之后,就使得一个线程在操作这些代码的过程中,其他线程必须等待
- 同步监视器:俗称"锁",可以使用任何一个类的对象充当,但同步监视器必须是唯一的
/
- 解决方法2--同步方法
public synchronized void method(){
//需要被同步的代码
}
/**
默认的"同步监视器":
- 非静态的同步方法:'this'
- 静态的同步方法:'当前类.class'
/
- 解决方法3--Lock
Lock是JUC包中的一个接口,基于JDK5.0
使用Lock解决线程安全步骤:
Lock实现类 lock = new Lock实现类(); //1.创建一个Lock实例,需要确保多个线程公用同一个Lock实例,可以声明为static final
lock.lock(); //2.调用lock()方法,加锁
//对共享数据的操作
lock.unlock(); //3.调用ublock()方法,解锁
- 死锁问题
死锁形成的条件:
1、互斥条件
2、占用且等待
3、不可抢占
4、循环等待
针对以上四个条件,可以通过破坏条件来避免死锁:
1、互斥条件基本无法破坏
2、考虑一次性申请所有所需的资源,这样就不存在等待问题
3、占用部分资源的线程在进一步申请其他资源时,如果申请不到,则主动释放所拥有的资源
4、可以将资源改为线性顺序,申请资源时,每个线程都需按线性来申请,避免循环等待问题
- 注意事项
1、sleep()和wait()方法的区别
sleep()方法执行后不会释放同步监视器,而执行wait()方法会释放同步监视器
2、线程间的通信
wait()方法:阻塞当前线程,释放同步监视器,只能在同步代码块/同步方法 中使用
notify()方法:一旦执行此方法,会随机唤醒被wait()的线程中优先级最高的那一个;如果优先级相同则随机唤醒一个。被唤醒的线程从当初被wait()的位置继续执行。
notifyAll()方法:唤醒所有wait()的线程
注意:三个方法的使用必须在
同步代码块
或同步方法
中,必须由同步监视器来调用(可默认不写),这三个方法声明在Object类中
142~152:第11章_常用类和基础API
String
特性:
1、字符串常量池:两个内容相同的String常量指向的地址相同
2、不可变性:一个String类型变量一旦赋值,就不可以再更改,除非新开辟一段空间给它
3、两个字符串常量拼接的字符串常量仍然放在字符串常量池中
4、intern()方法返回的是字符串常量池中的字面量地址
String StringBuffer StringBuilder的区别
不可变的字符序列:String
可变的字符序列:
常用API:增删改查
插入
长度
(1)StringBuffer
jdk1.0声明,线程安全,效率低
当给sb添加一个null的引用字符对象时,append方法会默认转成"null"字符串形式,而构造器初始化则会报空指针异常
(2)StringBuilder
jdk5.0声明,线程不安全,效率高
StringBuilder、StringBuffer的API是完全一致的,并且很多方法与String相同。
1、常用API
(1)StringBuffer append(xx):提供了很多的append()方法,用于进行字符串追加的方式拼接
(2)StringBuffer delete(int start, int end):删除[start,end)之间字符
(3)StringBuffer deleteCharAt(int index):删除[index]位置字符
(4)StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列为str
(5)void setCharAt(int index, char c):替换[index]位置字符
(6)char charAt(int index):查找指定index位置上的字符
(7)StringBuffer insert(int index, xx):在[index]位置插入xx
(8)int length():返回存储的字符数据的长度
(9)StringBuffer reverse():反转
执行效率比较
StringBuilder > StringBuffer > String
- String:不可变的字符序列; 底层使用char[]数组存储(JDK8.0中)
- StringBuffer:可变的字符序列;线程安全(方法有synchronized修饰),效率低;底层使用char[]数组存储 (JDK8.0中)
- StringBuilder:可变的字符序列; jdk1.5引入,线程不安全的,效率高;底层使用char[]数组存储(JDK8.0中)
153~163:第12章_集合框架
Java 集合可分为 Collection 和 Map 两大体系:
-
Collection接口:用于存储一个一个的数据,也称
单列数据集合
。- List子接口:用来存储有序的、可以重复的数据(主要用来替换数组,"动态"数组)
- 实现类:ArrayList(主要实现类)、LinkedList、Vector
- Set子接口:用来存储无序的、不可重复的数据(类似于高中讲的"集合")
- 实现类:HashSet(主要实现类)、LinkedHashSet、TreeSet
- List子接口:用来存储有序的、可以重复的数据(主要用来替换数组,"动态"数组)
-
Map接口:用于存储具有映射关系“key-value对”的集合,即一对一对的数据,也称
双列数据集合
。(类似于高中的函数、映射。(x1,y1),(x2,y2) ---> y = f(x) )- HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties
-
JDK提供的集合API位于java.util包内
-
图示:集合框架全图
Collection的常用方法
Collection coll = new ArrayList();
coll.add()/addAll()/isEmpty()/size()/clear()/remove()/removeAll()/contains()/iterator()
- 使用迭代器遍历集合元素
迭代器(Iterator)是专门用来遍历集合元素的,原理是通过指针来遍历访问集合的元素
//(1)获取迭代器对象
Iterator iterator = coll.iterator(); //每次调用iterator()方法都会返回一个新的iterator对象
//(2)使用`hasNext()/next()`方法遍历集合
while(iterator.hasNext()){
iterator.next();
}
- foreach循环遍历集合
for(:){}
List接口
存储数据的特点:用于存储有序的、可重复的数据
常用方法
增:
add(Object b)
addAll(Collection c)
删:
remove(Object b)
remove(int index) //删除指定下标处元素
改:
set(int index, Object b)
查:
get(int index)
插:
add(int index, Object b)
addAll(int index, Collection c)
长度:
size()
遍历:
//1. 迭代器
//2. for/for-each
List常见实现类对比
- ArrayList——主要实现类
线程不安全、效率高、底层使用Object[]数组存储
<> 起始容量是10
<> 每次扩容1.5倍
- Vector——老实现类
线程安全、效率低、底层使用Object[]数组存储
- LinkedList
底层使用双向链表的方式进行存储
Set接口
要求添加到HashSet和LinkedHashSet中的元素的类要重写
equals
和hashCode
两个方法
两个性质
- 无序性
哈希算法
- 不可重复性
添加到Set中的元素不能是相同的
比较标准: 需要判断hashCode()
得到的哈希值以及equals()
得到的布尔型结果
常见实现类
- HashSet——主要实现类
底层使用hashmap,即"数组+单链表+红黑树(jdk8后)"
- LinkedHashSet
HashSet的子类,底层在HashSet的基础上,加入了双向链表,用于记录添加元素的先后顺序,可按添加顺序遍历
- TreeSet
TreeSet添加的元素必须是同一个类型的对象,否则会报ClassCastException
TreeSet底层基于红黑树,可按照元素的某个属性来进行排序,需要考虑自定义"排序方法"
排序方法:
(1)自然排序——实现comparable接口
(2)定制排序——实现comparator类,更灵活
Map接口
存储键值对
常用方法
- 增
put(Object key, Object value)
putAll(Map m)
- 删
Object remove(Object key)
- 改
put(Object key, Object value) //键一致,值不一致,直接覆盖
- 查
Object get(Object key)
- 遍历
遍历key集:Set keySet()
遍历value集:Collection values()
遍历entry集:Set entrySet()
实现类及其区别
- HashMap——主要实现类
特点:线程不安全,效率高,可以添加null的key和value值;
底层数据结构:数组+单向链表+红黑树
键值对特点:
a. 存放key的是Set集合,无序且不可重复,要添加的元素需重写equals和HashCode方法(因为是Set)
b. 存放value的是无序但可重复的
c. 一对key-value称为一个Entry,entry之间不可重复,无序,所有的entry构成了一个集合
-
- LinkedHashMap——HashMap的子类
特点:线程安全,效率低,不可以添加null的键值对;
底层在HashMap的基础上加入了 双向链表 ,可按添加顺序遍历
- TreeMap
> 特点:可以按照添加的key-value中的key元素的指定属性的大小顺序进行遍历。需要考虑使用 a.自然排序 b.定制排序
底层数据结构:红黑树
> 要求:添加的key必须是同一类型的
- Hashtable
特点:老实现类;线程安全,效率低;可以添加null键值对;
底层数据结构: 数组+单向链表
-
- Properties——Hashtable的子类
特点:key和value都是String类型。常用来处理文件
164~167:第13章_泛型_
所谓泛型,就是允许在定义类、接口时通过一个
标识<T>
表示类中某个属性的类型
或者是某个方法的返回值或参数的类型
。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数
,也称为类型实参)。
自定义泛型结构
泛型类或泛型接口
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 接口们】{
}
-
泛型类实例化时,泛型的类型如果不用
<>
指定,默认按照Object
类型处理 -
使用泛型类或泛型方法时只能使用
引用数据类型
-
可同时声明多个泛型类型:
<T1,T2,...>
-
不可以在静态方法中使用
类的泛型
,但泛型方法
可以声明为static -
异常类不能带有泛型
-
子类/子接口可以选择
确定/不确定
父类/父接口的泛型,也可以自己添加新的泛型参数
泛型方法
- 泛型方法与类是否泛型无关
- 泛型类型通常在方法被调用时确定
public [static] <E> 返回值类型 method(E e){
}
//在方法调用时指明具体泛型类型
//泛型方法与类是否泛型无关系
泛型通配符
当无法确定这个泛型类或泛型接口的类型变量
<T>
的具体类型,此时我们考虑使用类型通配符<?>
,通常用在
- 通配符不能用在泛型类、泛型方法的声明上
- 创建对象
=new Animal<类型>
时不能用通配符?
- 将任意元素加入到
<?>型引用
不是类型安全的,会产生编译错误,唯一可插入的值是null
- 读取List<?>的对象list中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是Object
public class TestWildcard { public static void m4(Collection<?> coll){ for (Object o : coll) { System.out.println(o); } } }
有限制的通配符
-
<?>
- 允许所有泛型的引用调用
-
通配符指定上限:
<? extends 类/接口 >
- 使用时指定的类型必须是继承某个类,或者实现某个接口,包括当该类或接口
-
通配符指定下限:
<? super 类/接口 >
- 使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,包括当该类或接口
-
说明:
<? extends Number> //(无穷小 , Number] //只允许泛型为Number及Number子类的引用调用 <? super Number> //[Number , 无穷大) //只允许泛型为Number及Number父类的引用调用 <? extends Comparable> //只允许泛型为实现Comparable接口的实现类的引用调用
168~174:第14章_数据结构与集合源码
175~182:第15章_File类与IO流
1、File类
//定义
- 可创建一个File类实例,用于指向某一文件路径
- 注意路径分隔符,可使用
File.seperator
替代/
或\\
以达到一次编写,多次运行
的效果
- 常用方法
返回值 | 函数名 | 用途 |
---|---|---|
String | getAbsoluteFile() | 返回绝对路径 |
String | getPath() | 获取new File("path")中的path |
boolean | createNewFile() | 创建文件 |
boolean | mkdirs() | 创建文件夹 |
String[] | list() | 遍历该路径下的一级文件夹和文件 |
File[] | listFiles() | 同上 |
int | length() | 获取文件的字节数 |
2、IO流
将数据保存到文件中,实现永久保存
- 按照数据的流向可分为"输入/输出流"
- 按照数据的单位可分为"字符/字节流"
- 按照流的角色不同可分为"节点/处理流"
(抽象基类) | 输入流 | 输出流 |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
读写文件步骤:
创建File类对象,指定文件
创建节点流对象
读写文件
关闭流资源
java.io.Reader
抽象类是表示用于读取字符流的所有类的父类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public int read(char[] cbuf,int off,int len)
:从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public void close()
:关闭此流并释放与此流相关联的任何系统资源。
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int c)
:写出单个字符。public void write(char[] cbuf)
:写出字符数组。public void write(char[] cbuf, int off, int len)
:写出字符数组的某一部分。off:数组的开始索引;len:写出的字符个数。public void write(String str)
:写出字符串。public void write(String str, int off, int len)
:写出字符串的某一部分。off:字符串的开始索引;len:写出的字符个数。public void flush()
:刷新该流的缓冲。public void close()
:关闭此流。
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。public int read(byte[] b,int off,int len)
:从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int b)
:将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。
四个节点流(文件流)
- FileReader/FileWriter
java.io.FileReader
类用于读取字符文件,构造时使用系统默认的字符编码和默认字节缓冲区。
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
java.io.FileWriter
类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区。
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。FileWriter(File file,boolean append)
: 创建一个新的 FileWriter,指明是否在现有文件末尾追加内容。
关于flush:
因为内置缓冲区的原因,如果FileWriter不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要
flush()
方法了。
flush()
:刷新缓冲区,流对象可以继续使用。close()
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
- FileInputStream/FileOutputStream(以字节为单位)
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
public FileOutputStream(File file)
:创建文件输出流,写出由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流,指定的名称为写出文件。public FileOutputStream(File file, boolean append)
: 创建文件输出流,指明是否在现有文件末尾追加内容。
3、处理流1之缓冲流
缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用
8192个字节(8Kb)
的缓冲区),通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
位置关系:处理流(
节点流(
File对象))
- 处理非文本文件的字节流 —— BufferedInputStream和BufferedOutputStream
public BufferedInputStream(InputStream in)
:创建一个 新的字节型的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的字节型的缓冲输出流。
- 处理文本文件的字符流 —— BufferedReader和BufferedWriter
public BufferedReader(Reader in)
:创建一个 新的字符型的缓冲输入流。public BufferedWriter(Writer out)
: 创建一个新的字符型的缓冲输出流。字符缓冲流特有的常用方法:
- BufferedReader:
public String readLine()
: 读一行文字。- BufferedWriter:
public void newLine()
: 写一行行分隔符,由系统属性定义符号。
流的关闭:
关闭流时先关闭外层的缓冲流,但是默认关闭外层流时自动关闭内层流。
4、处理流2之转换流
字符编码、字符解码
- 作用
实现字节流与字符流的转换,避免字符乱码等问题,转换流是字节与字符之间的桥梁
- API
InputStreamReader: 将一个输入型的字节流
转换为一个输入型的字符流
OutputStreamWriter: 将一个输出型的字符流
转换为一个输出型的字节流
构造器:
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in,String charsetName)
: 创建一个指定字符集的字符流。
PS:解码使用的字符集必须要与当初编码时使用的字符集一致
将gbk编码转为utf-8:
//将GBK编码文件以GBK编码读入
InputStreamReader isr = new InputStreamReader(fis, "gbk");
//将文件以UTF-8编码写出
OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
5、处理流3之对象流
- 数据流
- 对象流及其作用
API:
ObjectInputStream
ObjectOutputStream
作用:
可以读写基本数据类型
、引用类型
变量
- 对象的序列化机制
对象序列化机制允许把内存中的Java对象转换为与平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上;
或通过网络将这种二进制流传输到另一个网络节点,并恢复为完整的对象;
- 对象流
-
序列化:使用
ObjectOutputStream
流实现, 将内存中的Java对象保存在文件中或通过网络传输出去 -
反序列化:使用
ObjectInputStream
流实现, 将文件中的数据或网络传输过来的数据还原为内存中的Java对象
- 自定义类要实现序列化机制,需满足:
①自定义类需要实现接口:Serializable
②要求自定义类声明一个全局常量:static final long serialVersionUID = xxxL;
用来唯一的标识当前类
③要求自定义类的各个属性也必须是可序列化的:
基本数据类型默认可序列化
引用数据类型要求实现Serializable接口
- 注意点
①如果不声明全局变量serialVersionUID ,系统会自动生成一个针对于当前类的UID。
但如果修改此类,会导致UID发生变化,进而导致反序列化时出现InvalidClassException
异常
② 类中的属性如果声明为transient
或static
,则不参加序列机制
183~186:第16章_网络编程
IP,端口, 协议, 模型
- IP
InetAddress
类的一个实例就代表一个IP
- 实例化方法
静态方法:
getByName(string host) //host可以是域名或具体IP地址
getLocalHost()
常用方法:
getHostName() //获取主机名
getHostAddress() //获取主机IP
187~193:第17章_反射机制
- 可以通过反射调用类中
private
属性、方法或构造器
- 获取Class实例的四种方法
//1. 类名.class
Class c = User.class
//2. 调用运行时类的实例的方法getClass()
Class c = p1.getClass()
//3. 调用静态方法forName()
Class c = Class.forName("全类名")
//4. 使用类加载器(了解)
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass("类的全类名");
//四种获取的Class都是同一个在内存中被类加载器装载的类
- Class实例可以指向的结构?
类的加载过程
- 分类
BootstrapClassLoader:引导类加载器,使用c/c++编写,负责加载Java的核心类库rt.jar
继承于ClassLoader的类加载器:使用java编写
- 自定义类加载器
使用自定义类加载器,可以实现应用的隔离、数据的加密
- 通过ClassLoader读取配置文件
Properties pros = new Properties();
//通过系统类加载器读取文件,默认路径为当前module的src下
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("info.properties");
pros.load(in);
反射的应用
- 创建运行时类的对象
jdk9.0之前通过Class的实例调用newInstance(),之后通过Constructor()
- 获取运行时类的所有内部结构信息
- 调用指定的结构
- 调用指定的属性
- 调用指定的方法
- 调用指定构造器
194~200:第18章_JDK8-17新特性