java 快速入门
public class Main {
public static void main(String[] args) {
System.out.println("Hello and welcome!");
}
}
- Public class Main 表示一个公开的类
- Public static void main (String[] args) 表示程序的入口
- Main { } 表示类的开始与结束
- Main{} 方法的开始与结束
System.out.println("Hello and welcome!");
输出一条语句
java 开发细节
- Java 源文件以. Java 为扩展名。源文件的基本组成部分是类,如本类中的 Main 类
- Java 应用程序的执行入口是 main ()方法。他有固定的书写格式
public static void main(String[] args)
- Java 语言严格区分大小写
- 大括号都是成对出现的,缺一不可。
- 一个源文件最只能有一个 public 类。其他类的个数不限。
- 如果源文件包含一个 public 类,则源文件名必需与该类类名一致
- 一个源文件中最多只能有一个 public 类。其他类的个数不限,每个类都可以有自己的 main 方法。
转义字符
\t
制表符\n
换行\\
一个\\'
一个单引号\"
一个双引号\r
回车
相对路径和绝对路径
- 相对路径:从当前目录开始定位,形成的一个路径
- 绝对路径:从顶层目录开始定位,形成的一个路径
变量
- 变量是程序的基本组成单位
- 变量三要素 ^f56ef2
- 类型
- 名称
- 值
- 注意事项
- 变量表示内存中的一个存储区域(不同变量占字节数不同)
- 该区域有自己的名称和类型
- 变量必须先声明在使用
- 该区域的数据在同一类型范围内不断变化
- 变量在同一个作用于内不能重名
- 变量= 类型+名称+值
- +号的使用
- 当左右两边都是数值时,做加法运算
- 左右两边有一边为字符串时,做拼接
- 运算顺序是从左到右
- 数据类型
- 基本数据类型
- 数值型
- 整数型
byte(1) short(2) int(4) long(8)
- 使用细节
- 声明
long
类型需要在数字后加 l 或 L
- 声明
- 浮点型
float(4) double(8)
- 使用细节
- 声明 float 需要在小数后加 f 或 F
- 有两种表示形式:十进制和科学计数法
- 注意精度问题
- 8.1/3 并不等于 2.7
- 整数型
- 字符型
char(2)
存放一个字符- 使用细节
- Char 的值是单引号括起来的单·个字符
- 本质是一个整数,对照 unicode 码表
- 可以直接赋值为一个整数范围在(0~255)
- 可以直接进行运算
- 布尔型
boolean(1)
存放 true 和 false- 使用细节
- 只能存放 true 和 false,无 null
- 数值型
- 引用数据类型
- 类(class)
- 数组(Array)
- 接口(interface)
- 枚举(enum)
- 记录
- String
- 数据类型转换
- 基本数据类型转换
- 自动类型转换
char->int->long->float->double
byte->short->int->long->float->double
- 细节
- 当有多种数据类型同时进行运算时,会将所有数据类型全部转为最大的数据类型进行计算
- 当精度大的数据类型转为精度小的数据类型时会报错,反之为自动类型转换
- (byte 和 short)与 char 之间不会进行自动数据类型转换
byte、short、char
三者之间可以计算,在计算时自动转为 int 类型- Boolean 类型不参与转换
- 自动提升原则:表达式结果的类型自动提升为表达式中最高的类型
- 强制类型转换
- 将大容量的数据类型转为小容量的数据类型时需要加上强制类型转换符
()
,但可能会造成精度丢失 - 细节
- 强转符号只针对于最近的操作数有效
char
类型只能保存int
的常量值不能保存 int 的变量,需要强转
- 将大容量的数据类型转为小容量的数据类型时需要加上强制类型转换符
- 基本数据类型与 string 类型相互转换
- 基本数据类型转为 string 直接+“”
- String 转为基本数据类型可以调用对应数据类型的包装类的方法
- 例如
public static int parseInt
- 例如
- 字符串转为字符
- 可以通过
charAt
方法提取相应位置的字符
- 可以通过
- 注意事项
- 将 string 转为基本数据类型时要确保 string 类型可以转成有效的数据比如"123"可以转为 123 但是“hello”无法转为数值
- 自动类型转换
- 基本数据类型转换
- 基本数据类型
运算符
算术运算符
- 细节说明
- 对于/,整数除和小数除是有区别的,整数除是取整,直接舍去小数部分,只保留整数部分
- 当对一个数取模时,可以等价于
a%b=a-a/b*b
。 - 当自增做一个独立语言使用时,不管是前置加还是后置加都是本身自增一。
- 当自增作为一个表达式使用时
j=++i等价于i=i+1;j=i
- 当自增作为一个表达式使用时
j=i++等价于j=i;i=i+1
- 注意
关系运算符
- 细节说明
- 值都为 boolean 型
- == 不能误写为=
逻辑运算符
- 用于连接多个关系表达式,结果为布尔值
- 与 (仅左右语句都为真时为真)包括:逻辑与& 和短路与&&
- 或 (左右语句有一则或超过一则为真时为真)包括:逻辑或| 和短路或||
- 非 (取反,假时为真,真时为假)包括:逻辑非!
- 异或 (左右相异时为真,左右相同时为假)包括:逻辑异或^
赋值运算符
- 特点
- 顺序为从右往左
- 赋值运算符的左边只能是变量,右边可以是变量也可以是表达式,常量值。
- 复合赋值运算符会进行类型转换。
byte i=1; i+=1;
三元运算符
- 条件表达式 ? 表达式 1:表达式 2;
- 规则
- 条件表达式为 true 返回表达式 1 反之返回表达式 2
运算符优先级
标识符
- 规范
- 由字母、数字、下划线、$组成
- 数字不能开头
- 不能使用关键字和保留字
- 严格区分大小写
- 不能包含空格
进制
位运算符
流程控制
If else
Switch
- Switch 判断的类型必须为
byte short int char string 枚举
循环控制
- For
- While
- Do while
数组
一维数组
//数组初始化
int [] arr=new int[5];//初始化时规定大小
int [] arr;//先定义变量
arr=new int[5];//再进行赋值
//静态初始化
int [] arr=new int[]{1,2,3,4,5};
- 数值型数组和字符型数组默认值为 0,boolean 默认值为 false String 型默认值为 null
二维数组
//初始化
int[][] arr=new int[2][3];
//对于列数不相同的数组可以先省略列数
int[][] arr=new int[2][];
//静态初始化
int[][] arr=new int[][]{{1,2},{2,3}};
数组使用注意事项
- 数组是多个相同类型数据的组合,实现对这些数据的统一管理
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用
- 数组索引是从零开始
- 数组下表需要再指定范围内使用
- 数组属于引用类型,数组型数据是对象
面向对象基础
类与对象
- 类是抽象的,概念的,代表一类事物
- 对象是实体,具体的,代表一个具体的事物
- 类是对象的模板,对象是类的具体表现形式
对象在内存中的形式
属性/成员/字段
- 概念上这三者相同。
- 属性是类的组成,一般是基本数据类型,也可以是引用数据类型。
注意事项
- 属性的定义语法同变量,与变量不同的是属性有四种权限修饰符
- 属性可以定义为任意类型
- 属性如果不赋值有默认值,规则和数组一致
类和对象的内存分配机制
- 栈:一般存放基本数据类型
- 堆:存放对象(数组)等
- 方法区:常量池,类加载信息
方法的调用机制原理
成员方法的传参机制
基本数据类型的传参机制
- 传递的是值,形参的任何变化都不会影响实参
引用数据类型的传参机制
- 传的是地址值,形参的变化会引起实参的变化
形参置为 null 时不会影响到实参,只是将形参保存的值消去。
递归的规则
- 执行一个方法时,就创建一个新的受保护的独立空间。
- 方法的局部变量是独立的,不会相互影响。
- 如果方法中使用的是引用类型变量,会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无线递归
- 当一个方法执行完毕,或者遇到 return,就会返回
方法重载
- 方法名:必须相同
- 形参列表:必须不同(形参类型或者个数或者顺序,至少有一样不同,参数名无要求)
- 返回类型:无要求
可变参数
- 基本语法
public void sum(int... arg){}
- 注意事项
- 可变参数的实参可以为 0 个或任意多个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变参数可以喝普通类型的参数一起放在形参列表,但必须保证可变参数在最后
- 一个形参列表中只能出现一个可变参数
public class SwitchDetail {
public static void main(String[] args) {
VariableParameter variableParameter = new VariableParameter();
variableParameter.sum(1);
variableParameter.sum(1, 2);
int[] arr = new int[]{1, 2, 3};
variableParameter.sum(arr);
}
}
class VariableParameter {
public void sum(int... arg) {
for (int i = 0; i < arg.length; i++) {
System.out.println(arg[i]);
}
}
public void sum(String name, int... arg) {
for (int i = 0; i < arg.length; i++) {
System.out.println(arg[i]);
}
}
}
作用域
- 属性和局部变量可以重名
- 在同一个作用域中,变量不能重名
- 属性生命周期较长,与对象相关。局部变量生命周期较短,与代码块相关
- 范围不同,全局变量可以在本类或者其他类中使用,局部变量只能在本类中使用
- 全局变量可以加修饰符,局部变量无法加修饰符
构造器
- 构造器的修饰符可以是默认,也可以是
public private protected
- 构造器没有返回值
- 名字和类名一致
- 参数列表和成员方法一样
- 构造器的调用由系统完成
注意事项
- 构造器也可以重载
- 构造器只完成对象初始化并不是创建对象
- 创建对象时系统自动调用构造方法
- 如果系统没有定义构造器,系统会自动给类生成一个默认的无参构造器
- 一旦定义了机子的构造器,默认的构造器就覆盖了,就不能在使用默认的无参构造器,除非显示定义
对象创建流程
This 关键字
- 代表当前类的对象
- This 关键字可以用来访问本类属性、方法、构造器
- This 用于区分当前类的属性和局部变量
- 访问成员方法,可以直接
this.变量/方法()
- 访问构造器
this(参数列表)
==只能在构造函数中访问另一个构造函数 - This 只在当前类中,代表本类
面向对象中级
包
包的三大作用
- 区分相同名字的类
- 当类很多时可以很好的管理
- 控制访问范围
包的基本语法
package com.cui.main;
package
表示包com.cui.main;
表示包名
包的本质分析
包的命名
- 只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是保留字或关键字
常用的包
java.lang
基本包,默认引入java.util
系统提供的工具包java.net
网络包java.awt
java 界面开发 GUI
包的注意事项
- 需要放在类的最上面
访问修饰符
基本介绍
public
对外公开protected
对本包下以及本类的子类公开- 默认,对本包公开
private
对本类公开
访问范围
注意事项
- 修饰符可以用来修饰类中的属性,成员方法以及类
- 只有默认的和 public 才能修饰类
- 成员方法的访问规则和属性完全一样
面向对象的三大特征
封装
- 把抽象出的数据和对数据才做的方法封装在一起,数据被保护在内部,程序的其他部分只有通过特定的端口,才能对数据进行操作
封装的理解和好处
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
封装步骤
//私有化属性
private String name;
//提供用于访问属性的get方法
public String getName() {
return name;
}
//提供用于修改属性的set方法
public void setName(String name) {
this.name = name;
}
继承
集成作用
- 提高代码复用
继承示意图
基本语法
class 子类 extends 父类{}
1.子类自动拥有父类定义的属性和方法
2.父类又叫超类,基类
3.子类又叫派生类
注意事项
- 子类继承了所有的属性和方法,但不包括私有的,在子类中访问父类私有的属性和方法也需要通过父类提供的端口
- 子类必须调用父类的构造器,完成父类的初始化
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化,否则编译失败
- Super 可以显示指定调用父类的哪个构造器super 只能在构造器中使用
- Super 在使用时必须放在子类构造器第一行
- Super 和 this 都只能放在构造器的第一行,因此这两个方法不能共存于同一个构造器
- Java 所有类都是 Object 的子类,object 是所有类的基类
- 父类构造器的调用不限于直接父类!将一直追溯到 object 类
- 子类做多只能继承一个父类单继承
继承的本质
super 关键字
基本介绍
- 代表父类的引用,用于访问父类的属性、方法、构造器
基本语法
- 访问父类属性,但不能访问父类的私有属性
super.属性名
- 访问父类方法,不能访问父类的私有方法
super.方法名
- 访问父类的构造器
Super 和 this 比较
方法重写
- 子类的一个方法与父类方法相同(名称、返回类型、参数相同)
注意事项
- 子类不能缩小父类的访问权限
多态
基本介绍
- 方法或对象具有多种状态,多态是建立在封装和继承的基础上的
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看=左边运行类型看=右边
注意事项
- 多态的前提是两个对象存在继承关系
向上转型
- 本质:父类引用只想子类对象
- 语法
父类类型 名称 =new 子类类型()
- 特点:编译类型看左边,运行类型看右边,可以调用父类中所有成员,不能调用子类中私有成员,最终运行效果看子类的具体实现
向下转型
- 语法:子类类型引用名=(子类类型)父类引用
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型中所有成员
动态绑定
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
Object 类
Equals
== 与 equals 对比
- == :既可以判断基本类型,又可以判断引用类型
- == :如果判断基本类型,判断的是值是否相等
- == :如果判断引用类型,判断的是地址是否相等,及判定是不是同一个对象
- Equals 是 object 类中的方法,只能判断引用类型,
- 默认判断的是地址是否相等,子类一般会重写该方法,用于判断内容是否相等。
零钱通项目案例
package com.cui.main;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/**
* @projectName: javaSeCode
* @package: com.cui.main
* @className: SmallChanAgeSysApp
* @author: 崔
* @description: 韩顺平javaSe零钱通项目 面向过程版
* @date: 2023/8/18 21:59
* @version: 1.0
*/
public class SmallChanAgeSysApp {
public static void main(String[] args) {
boolean flag = true;
Scanner scanner = new Scanner(System.in);
int opt;
String detail = "";
double money;
Date date;
//用于格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
double totalAmount = 0;
do {
System.out.println("==========零钱通菜单========");
System.out.println("\t\t\t\t1. 零钱通明细");
System.out.println("\t\t\t\t2. 收益入账");
System.out.println("\t\t\t\t3. 消费");
System.out.println("\t\t\t\t4. 退出");
System.out.println("请输入选项");
opt = scanner.nextInt();
switch (opt) {
case 1:
System.out.println("---------零钱通明细--------");
System.out.println(detail);
break;
case 2:
System.out.print("请输入入账金额");
money = scanner.nextDouble();
if (money > 0 && money < totalAmount) {
System.out.println("---------收益入账--------");
totalAmount += money;
date = new Date();
detail += "\n 收益入账\t+" + money + "\t" + sdf.format(date) + "\t" + totalAmount;
System.out.println(detail);
}
break;
case 3:
System.out.println("请输入消费金额");
money = scanner.nextDouble();
if (money > 0 && money < totalAmount) {
totalAmount -= money;
date = new Date();
detail += "\n 消费金额\t-" + money + "\t" + sdf.format(date) + "\t" + totalAmount;
System.out.println("---------消费--------");
System.out.println(detail);
} else {
System.out.println("金额输入错误");
}
break;
case 4:
System.out.println("你确认退出吗?y/其他");
String next = scanner.next();
if ("y".equals(next)) { //使用常量调用equals方法可以避免空指针
flag = false;
}
break;
default:
System.out.println("请重新输入选项");
break;//当default位于最后时break可以省略
}
} while (flag);
}
}
房屋出租项目代码
- 修改功能未写(懒)
package com.cui.main.house.view;
import com.cui.main.house.domain.House;
import com.cui.main.house.service.HouseService;
import com.cui.main.house.utils.Utils;
/**
* @projectName: javaSeCode
* @package: com.cui.main.house.view
* @className: HouseView
* @author: 崔
* @description: 视图层
* @date: 2023/8/20 14:30
* @version: 1.0
*/
public class HouseView {
private boolean flag = true;
private static HouseService service = new HouseService();
public void menu() {
do {
System.out.println("--------系统菜单--------");
System.out.println("\t\t\t\t1. 新增房源");
System.out.println("\t\t\t\t2. 查找房屋信息");
System.out.println("\t\t\t\t3. 删除房屋信息");
System.out.println("\t\t\t\t4. 修改房屋信息");
System.out.println("\t\t\t\t5. 显示房屋信息");
System.out.println("\t\t\t\t6. 退出系统");
System.out.print("请输入你的选择:");
char c = Utils.readChar();
switch (c) {
case '1':
System.out.print("请输入姓名:");
String name = Utils.readString(4);
System.out.print("请输入手机号:");
String phone = Utils.readString(11);
System.out.print("请输入地址:");
String address = Utils.readString(32);
System.out.print("请输入状态:");
String state = Utils.readString(4);
System.out.print("请输入租金:");
double money = Utils.readDouble();
House house = new House(name, phone, state, address, money);
service.addHouse(house);
break;
case '2':
System.out.print("请输入需要查找的房屋id:");
int id = Utils.readInt();
service.selectByIndex(id);
break;
case '3':
System.out.println("删除房屋信息");
System.out.print("请输入需要删除的id:");
int index = Utils.readInt();
boolean b = service.delHouse(index);
System.out.println(b ? "删除成功" : "删除失败");
break;
case '4':
System.out.println("修改房屋信息");
break;
case '5':
service.list();
break;
case '6':
System.out.println("退出系统");
flag = false;
break;
}
} while (flag);
}
}
package com.cui.main.house.utils;
import java.util.Scanner;
/**
* @projectName: javaSeCode
* @package: com.cui.main.house.utils
* @className: utils
* @author: 崔
* @description: 工具类
* @date: 2023/8/20 14:29
* @version: 1.0
*/
public class Utils {
private static Scanner scanner = new Scanner(System.in);
public static char readChar() {
return scanner.next().charAt(0);
}
public static String readString(int index) {
String string;
do {
string = scanner.next();
int length = string.length();
if (length > 0 && length <= index) {
break;
}
System.out.println("请输入1~" + index + "之间的字符串");
} while (true);
return string;
}
public static double readDouble() {
return scanner.nextDouble();
}
public static int readInt() {
return scanner.nextInt();
}
}
package com.cui.main.house.service;
import com.cui.main.house.domain.House;
/**
* @projectName: javaSeCode
* @package: com.cui.main.house.service
* @className: HouseService
* @author: 崔
* @description: 房屋系统业务类
* @date: 2023/8/20 14:41
* @version: 1.0
*/
public class HouseService {
private House[] houses;
private int housesSize;
private int houseId = 1;
public HouseService() {
houses = new House[0];
}
public void addHouse(House house) {
house.setId(houseId++);
House[] newHouse = new House[houses.length + 1];
if (houses.length > 0) {
for (int i = 0; i < houses.length; i++) {
newHouse[i] = houses[i];
}
}
newHouse[houses.length] = house;
houses = newHouse;
}
public void list() {
System.out.println("--------房源列表--------");
System.out.println("id\t\tname\t\tphone\t\taddress\t\tmoney\t\tstate");
if (houses.length > 0) {
for (House house : houses) {
System.out.println(house.getId() + "\t\t" + house.getName() + "\t\t" + house.getPhone() + "\t\t"
+ house.getAddress() + "\t\t" + house.getMoney() + "\t\t" + house.getState());
}
}
}
public boolean delHouse(int index) {
int id = selectHouse(index);
if (id == -1) {
return false;
}
House[] newHouse = new House[houses.length-1];
for (int i = 0; i < id; i++) {
newHouse[i] = houses[i];
}
id++;
for (int i = id; i < houses.length; i++) {
newHouse[i - 1] = houses[i];
}
houses = newHouse;
return true;
}
public int selectHouse(int index) {
int r = houses.length;
int l = 0;
int mid = houses.length / 2;
while (l <= r) {
if (index > houses[mid].getId()) {
l = mid + 1;
}
if (index < houses[mid].getId()) {
r = mid - 1;
}
if (index == houses[mid].getId()) {
return mid;
}
mid = (l + r) / 2;
}
return -1;
}
public void selectByIndex(int index) {
System.out.println("--------查询房屋信息--------");
int i = selectHouse(index);
if (i == -1) {
System.out.println("未查询到该房屋信息");
return;
}
House house = houses[i];
System.out.println(house.getId() + "\t\t" + house.getName() + "\t\t" + house.getPhone() + "\t\t"
+ house.getAddress() + "\t\t" + house.getMoney() + "\t\t" + house.getState());
}
public House[] getHouses() {
return houses;
}
public void setHouses(House[] houses) {
this.houses = houses;
}
public int getHousesSize() {
return housesSize;
}
public void setHousesSize(int housesSize) {
this.housesSize = housesSize;
}
}
package com.cui.main.house.domain;
/**
* @projectName: javaSeCode
* @package: com.cui.main.house.domain
* @className: House
* @author: 崔
* @description: 房屋类
* @date: 2023/8/20 14:27
* @version: 1.0
*/
public class House {
private int id;
private String name;
private String phone;
private String state;
private String address;
private double money;
public House() {
}
public House(String name, String phone, String state, String address, double money) {
this.name = name;
this.phone = phone;
this.state = state;
this.address = address;
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "House{" +
"id=" + id +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
", state='" + state + '\'' +
", address='" + address + '\'' +
'}';
}
}
面向对象高级
类变量
类变量定义
- 类变量也叫静态变量,是属于类的,所有对象实例共有的属性。
类变量定义方式
访问修饰符 static 变量类型 变量名= 初始值
变量的内存布局
类变量访问方式
类名.变量名
类变量使用细节
- 类变量与实例变量区别
- 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
- 加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
- 类变量可以通过类名调用或者对象名调用,推荐使用类名调用
- 实例变量不能通过类名调用
- 类变量是在类加载时初始化,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量
- 类变量的声明周期是随类的加载开始,随类消亡而销毁。
类方法
类方法的基本介绍
- 类方法也叫静态方法,可以通过类名嗲用
类方法的定义
访问修饰符 static 返回值类型 方法名 (){}
使用场景
- 一般将一些通用的方法定义为静态的,方便使用
类方法注意事项
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区,类方法中不能使用 this,普通方法中可以使用 this
- 类方法可以通过类名调用
- 普通方法只能通过对象名调用
- 类方法不允许使用 this 与 supe
- 类方法只能访问静态变量或静态方法
- 普通成员方法,既可以访问非静态成员也可以访问静态成员
Main 方法
Main 方法的形式
public static void main(String[] args) {}
- main 方法时虚拟机调用所以该方法的权限是公开的
- Java 虚拟机在执行 main 的时候不会创建对象所以是 static
- 该方法接受 string 类型数组,该数组中保存执行 java 命令时传递给所运行的类的参数
代码块
基本介绍
- 代码块又称为初始化块,属于类中的成员,类似于方法,讲逻辑语句封装在方法体中,通过{}包围起来。
- 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不同通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。
定义方式
[修饰符]{};
- 修饰符是可选的,只能写 static
- 代码块分为静态代码块和普通代码块
- 逻辑语句可以为任何逻辑语句
- 分号可以写也可以省略
代码块作用
- 可以将多个构造器中重复的操作写在代码块中,提高复用性
注意事项
- 静态代码块随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象就会执行一次。
- 类什么时候加载
- 创建队形实例时
- 创建子类对象实例,父类也会加载
- 使用类的静态成员时
创建对象时类的调用顺序
- 调用静态代码块和静态属性初始化(如果有多个按定义的顺序调用)
- 普通代码块和普通属性的初始化(如果有多个按定义的顺序加载)
- 调用构造方法
创建子类对象时调用顺序
- 父类的静态代码块和静态属性初始化(按定义顺序)
- 子类的静态代码块和静态属性(按定义顺序)
- 父类的普通代码块和普通属性初始化
- 父类的构造方法
- 子类的普通代码块和普通属性初始化
- 子类的构造方法
- 静态代码块只能调用静态成员
package com.cui.main;
/**
* @projectName: javaSeCode
* @package: com.cui.main
* @className: SwitchDetail
* @author: 崔
* @description:
* @date: 2023/8/10 14:26
* @version: 1.0
*/
public class SwitchDetail {
public static void main(String[] args) {
Test02 test02 = new Test02();
}
}
class Test01 {
{
System.out.println("父类普通代码块");
}
public String name;
static {
System.out.println("父类静态代码块");
}
public Test01() {
System.out.println("父类构造函数");
}
public Test01(String name) {
this.name = name;
}
}
class Test02 extends Test01{
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类普通代码块");
}
public Test02() {
//这里隐藏了super和普通代码块
//super()
//调用普通代码块
System.out.println("子类构造方法");
}
}
单例设计模式
基本介绍
- 采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种
- 饿汉式
- 懒汉式
饿汉式
package com.cui.main;
/**
* @projectName: javaSeCode
* @package: com.cui.main
* @className: SwitchDetail
* @author: 崔
* @description:
* @date: 2023/8/10 14:26
* @version: 1.0
*/
public class SwitchDetail {
public static void main(String[] args) {
HungryHanStyle hungryHanStyle = HungryHanStyle.getHungryHanStyle();
}
}
class HungryHanStyle {
/**
* 1. 使构造函数私有化(无法从外部创建对象)
* 2. 创建一个私有的静态对象
* 3. 提供一个获取静态对象的方法
*/
private HungryHanStyle() {
}
private static HungryHanStyle hungryHanStyle = new HungryHanStyle();
public static HungryHanStyle getHungryHanStyle() {
return hungryHanStyle;
}
}
懒汉式
package com.cui.main;
/**
* @projectName: javaSeCode
* @package: com.cui.main
* @className: SwitchDetail
* @author: 崔
* @description:
* @date: 2023/8/10 14:26
* @version: 1.0
*/
public class SwitchDetail {
public static void main(String[] args) {
HungryHanStyle hungryHanStyle = HungryHanStyle.getHungryHanStyle();
}
}
class HungryHanStyle {
/**
* 1. 使构造函数私有化(无法从外部创建对象)
* 2. 定义一个私有的对象引用
* 3. 提供一个获取静态对象的方法,在调用方法时判断对象有无创建,并返回该对象。
*/
private HungryHanStyle() {
}
private static HungryHanStyle hungryHanStyle;
public static HungryHanStyle getHungryHanStyle() {
if (hungryHanStyle == null) {
hungryHanStyle = new HungryHanStyle();
}
return hungryHanStyle;
}
}
饿汉式与懒汉式对比
- 创建时机不同
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(完善方式写在线程那部分)
- 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那饿汉式创建的对象就浪费了,懒汉式是使用时创建,不存在这个问题。
Final 关键字
基本介绍
- Final 可以修饰类,属性,方法和局部变量
- Final 修饰的类不被继承,修饰的方法不被重写,修饰的属性和局部变量值不变
注意事项
- Final 修饰的属性又叫常量
- Final 的属性在定义时必须复制,并且以后不能再修改,赋值可以在如下位置之一
- 在定义时
- 在构造器中
- 在代码块中
- 如果 final 修饰的属性是静态的,则初始化位置只能在
- 定义时
- 静态代码块中
- Final 类不能被继承但是可以实例化对象
- Final 修饰的方法不能被重写但是可以被继承
- Final 不能修饰构造器
- Final 一般和 static 搭配使用,不会导致类加载,底层做了优化
抽象类
基本介绍
- 用 abstract 关键字修饰一个类时,这个类就称为抽象类
- 定义形式
访问修饰符 abstract 类名{}
- 用 abstract 修饰方法时,该方法为抽象方法,特点没有方法体
注意事项
- 抽象类不能实例化
- 抽象类不一定包含抽象方法
- 一个类如果包含抽象方法,则必须声明为抽象类
- Abstract 只能等修饰类和方法
- 抽象类可以有任何成员(本质就是类)
- 抽象方法不能有方法体
- 如果一个类继承了抽象类则必须实现所有抽象方法,除非这个类也声明为抽象类
- 抽象方法不能使用
private static final
修饰
模板设计模式
接口
基本介绍
- 接口是更加抽象的抽象类,抽象类里的方法可以有方法体,接口里只有静态方法、默认方法才可以有方法体
注意事项
- 接口不能被实例化
- 接口所有方法都是 public 方法接口中抽象方法,可以不同 abstract 修饰
- 一个普通类实现接口,就必须将该接口的所有方法都实现
- 抽象类实现接口,可以不用实现接口的方法
- 一个类可以实现多个接口
- 接口中的属性只能是 final 的,而且是
public static final
修饰。 - 接口中属性的访问形式
接口名.属性名
- 接口不能继承其他类,但是可以继承多个别的接口
- 接口的修饰符只能是 public 和默认。
接口与继承对比
- 继承的价值在于解决代码的复用性和可维护性
- 接口的价值在于设计规范
- 接口在一定程度上实现代码解耦
接口的多态性
- 多态参数
- 多态数组
- 多态传递
内部类
基本介绍
- 如果类定义在方法或代码块中
- 局部内部类
- 匿名内部类
- 定义在成员位置
- 成员内部类
- 静态内部类
- 一个类被定义在另一个类中称为内部类
基本语法
class InnerClass {
//成员内部类
class Test {
}
//静态内部类
static class Test03 {
}
public void test() {
//局部内部类
class Test02 {
}
//匿名内部类
new Test04() {
};
}
}
interface Test04 {
}
局部内部类的使用
- 可以直接访问外部类的所有成员
- 不能添加访问修饰符,但是可以使用 final 修饰
- 作用域:仅仅在定义它的方法或代码块中
- 外部类访问局部内部类需要创建对象
- 本质仍然是一个类
- 外部其他类不能访问局部内部类
- 如果外部类和局部类成员重名,默认尊许就近原则,访问外部类成员可以用
外部类名.this 去访问
匿名内部类
- 没有类名并且是一个对象
- 其他同上
成员内部类
- 可以添加访问修饰符
- 作用域为整个类
- 其他同上
静态内部类
- 只能访问静态成员
- 可以添加访问年休师傅
- 作用域为整个类
- 其他同上
枚举和注解
枚举
- 默认继承
Enum
类,而且是一个 final 类 - 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
- 当有多个枚举对象时,使用,间隔,最后有一个分号结尾
- 枚举对象必须放在枚举类的行首
- 枚举类可以实现接口
注解
异常
/**
* 异常基本概念:程序执行中发生的不正常情况称为异常
* 异常分类:
* 1. Error(错误):java虚拟机无法解决的严重问题。如jvm系统内部错误、资源耗尽等严重情况。
* 2. Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以是用针对性的代码进行处理。例如空指针访问,试图读取
* 不存在的文件。网络连接中断等等。Exception分为两大类:运行时异常和编译异常
* 对于运行时异常可以不做处理
*
*/
异常结构图
异常处理
try { 可疑代码}catch (异常){异常处理}finally {无论有无异常都会执行} //对异常进行补货并处理
//如果出现异常则之后的代码不在执行,直接进入catch在处理结束后返回前执行finally
//catch语句可疑有多个捕获不同的异常
throws //抛出异常交给调用者处理,抛出的异常类型可以是方法中产生的类型也可以是他的父类
//子类重写父类抛出异常的方法时,需要与父类抛出异常类型一致或者是父类抛出异常类型的子类
常用类
包装类
包装类的分类
基本数据类型 | 包装类 |
---|---|
int | Integer |
char | Character |
byte | Byte |
short | Short |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
包装类和基本数据类型转换
/*
包装类与基本类型互相转换可以自动拆箱和装箱,也可以手动拆箱和装箱
*/
//手动装箱
int n = 100;
Integer integer = new Integer(n);
Integer integer1 = Integer.valueOf(n);
//手动拆箱
int i = integer.intValue();
//自动装箱
Integer s = 100; //自动装箱底层使用的是Integer.valueOf
//自动拆箱
int s1 = s;//自动拆箱底层使用的是intValue
包装类与 String 类型相互转换
//包装类转String
Integer n = 100;
String nS = n + "";
String s = String.valueOf(n);
String string = n.toString();
//string转包装类
String str = "123";
int i = Integer.parseInt(str);
Integer integer = new Integer(str);
Integer 与 Character 常用方法
System.out.println(Integer.MIN_VALUE); //返回最小值
System.out.println(Integer.MAX_VALUE);//返回最大值
System.out.println(Character.isDigit('a'));//判断是不是数字
System.out.println(Character.isLetter('a'));//判断是不是字母
System.out.println(Character.isUpperCase('a'));//判断是不是大写
System.out.println(Character.isLowerCase('a'));//判断是不是小写
System.out.println(Character.isWhitespace('a'));//判断是不是空格
System.out.println(Character.toUpperCase('a'));//转成大写
System.out.println(Character.toLowerCase('A'));//转成小写
注意事项
- 如果创建的 Integer 值在-128 到 127 之间直接返回这个值而不会在堆中创建对象
- Integer 与基本数据类型比较判断的是值
String 类
基本介绍
- String 保存字符串底层使用不可变的字符数组保存
private final char value[];
- 不区分字母和汉字一个字符占两个字节
Serializable, Comparable
string 实现了这俩接口代表可以在网络上传输和比较
创建 string 的两种方式
String n = "";
String string = new String("");
- 方式 1 先查看常量池中是否已经存在该值,如果有直接指向,如果没有在常量池中创建然后再指向
- 方式 2 先在堆中创建一个空间,然后使用 value 属性指向常量池中该值的地址
public String intern()
返回的是常量池中的地址
字符串的特性
- 字符串是一个 final 类,代表不可变序列,一旦分配内存空间,其内容不可变,修改变量的值会重新在常量池中创建一个对象
String n1="123";
n1="asd";//会在常量池中重新创建一个对象
- 两个字符串相加只会创建一个对象
String n1="asd"+"asd"
- 两个字符串变量相加会使用 StringBuider 拼接后转为 String
String a="1";String b="2";String n= a+b
最后对象是存在堆空间中
String 类常见方法
StringBuffer 类
- Stringbuffer 是一个可变序列,一部分方法与 string 相同,可以对字符串内容进行增删
- Stringbuffer 在每次修改时不会更换地址,只是更换值
string 与 stringbuffer 相互转换
//string转stringbuffer
String n = "123";
StringBuffer stringBuffer = new StringBuffer(n);
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(n);
//stringbuffer转string
StringBuffer stringBuffer2 = new StringBuffer("asd");
String string = stringBuffer2.toString();
String s = new String(stringBuffer2);
Stringbuffer 常见方法
append()//追加一个字符串在结尾
insert()//在指定位置添加一个字符串
delete()//删除子串
replace()//替换一个子串
注意
- Stringbuffer 可以追加一个 null 但是不可以使用构造器创建 stringbuffer 对象时传入一个 null
Stringbuilder
- Stringbuilder 与 stringbuffer 类似,只不过 stringbuilder 不是线程安全的,在单线程情况下要比 stringbuffer 快
Math
Arrays
System
BigInteger 和 BigDecimal 类
- BigInteger 保存较大的整数
- BigDecimal 保存较大的浮点数
- 二者都提供了加减乘除方法
日期类
第一代日期类 Date
- 精确到毫秒,代表特定的瞬间
- SimpleDateFormat 格式化日期
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
第二代日期类 calendar
//Calendar 没有提供对应的格式化的类
//如果需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance();
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
//Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" +
c.get(Calendar.DAY_OF_MONTH) +
" " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );
分析
- 第一代日期类,随着第二代的引入,大多数方法已经弃用了
- 第二代日期类
- 可变性:像日期和时间这样的类应该是不可变的
- 偏移性:Date 中的年份是从 1900 年开始的,而月份从 0 开始
- 格式化:格式化只对 Date 有用,Calendar
- 第一代日期与第二代日期都不是线程安全的,不能处理润秒(每隔两天,多出一秒)
第三代日期
- LocalDate:只包含日期
- LocalTime: 只包含时间
- LocalDateTime:包含日期和时间
//1. 使用 now() 返回表示当前日期时间的 对象
LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
System.out.println(ldt);
//2. 使用 DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter 对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒
//提供 plus 和 minus 方法可以对当前时间进行加或者减
//看看 890 天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime));
//看看在 3456 分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
DateTimeFormatter
类似于 SimpleDateFormat
时间戳
//1.通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//2. 通过 from 可以把 Instant 转成 Date
Date date = Date.from(now);
//3. 通过 date 的 toInstant() 可以把 date 转成 Instant 对象
Instant instant = date.toInstant();
第三代日期方法
集合
Collection 特点
- 实现子类可以存放多个元素,每个元素可以是 Object
- 有些 Collection 的实现类,可以存放重复的元素,有些不可以
- 有些 Collection 的实现类,有些是有序的 list,有些不是有序 set
- Collection 接口没有直接的实现子类,是通过它的子接口 set 和 list 来实现的
Collection 常用方法
List list = newArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);
// remove:删除指定元素
//list.remove(0);//删除第一个元素
list.remove(true);//指定删除某个元素
System.out.println("list=" + list);
// contains:查找元素是否存在
System.out.println(list.contains("jack"));//T
// size:获取元素个数
System.out.println(list.size());//2
// isEmpty:判断是否为空
System.out.println(list.isEmpty());//F
// clear:清空
list.clear();
System.out.println("list=" + list);
// addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//T
// removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);//[聊斋]
// 说明:以 ArrayList 实现类来演示.
迭代器
package com.cui.main;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @projectName: javaSeCode
* @package: com.cui.main
* @className: ExplanationIterators
* @author: 崔
* @description: TODO
* @date: 2023/8/27 10:30
* @version: 1.0
*/
public class ExplanationIterators {
public static void main(String[] args) {
List<Dog> dogList = new ArrayList<>();
dogList.add(new Dog("asd", "12", "123"));
dogList.add(new Dog("aad", "12", "1223"));
dogList.add(new Dog("a3d", "12", "1234"));
//获取迭代器
Iterator<Dog> iterator = dogList.iterator();
//hasNext方法判断下一行是否有元素
while (iterator.hasNext()) {
//next方法使指针后移
Dog next = iterator.next();
System.out.println(next);
}
//如果像重新遍历,需要重置迭代器,因为此时迭代器指向了最后一个元素。
}
}
class Dog {
private String name;
private String age;
private String price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", price='" + price + '\'' +
'}';
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public Dog(String name, String age, String price) {
this.name = name;
this.age = age;
this.price = price;
}
}
增强 for
- 底层使用的是迭代器
for (Dog dog : dogList) {
System.out.println(dog);
}
List 接口
- 有序,可重复
- 支持索引
常用方法
List list = newArrayList();
list.add("张三丰");
list.add("贾宝玉");
// void add(int index, Object ele):在 index 位置插入 ele 元素
//在 index = 1 的位置插入一个对象
list.add(1, "韩顺平");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list2 = newArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定 index 位置的元素
//说过
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("tom"));//2
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add("韩顺平");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("韩顺平"));
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换.
list.set(1, "玛丽");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);
三种遍历方式
- 迭代器
- 增强 for
- For 循环
ArrayList
注意事项
- 可以加入 null
- 有数组实现存储
- 基本等同于 Vector,
- 线程不安全
- 改查快
底层源码分析
- ArrayList 中维护了一个 object 数组
transient Object[] elementData;
transient 表示该数组不会被序列化 - 当创建 arraylist 对象时,如果使用的是无参构造器,则初始 elementData 容量为 0,第一次扩容 elementData 值为 10,再次扩容为 elementData 1.5 倍
- 当使用的是制定大小的构造器,则初始 elementData 为指定大小,如果需要扩容,则直接扩容 elementData 1.5 倍
Vector
- 与 arraylist 基本一致,线程安全
- 默认扩容两倍可以指定扩容大小
- 默认初始化 10
LinkedList
- 底层实现了双向链表和双端队列特点
- 可以添加任意元素,包括 null
- 线程不安全
- 增删块
操作机制
- 底层维护了一个双向链表
- Linkedlist 维护了两个属性 first 和 last 分别指向首尾节点
- 每个节点,又维护了 preve、next、item 属性,preve 指向前一个节点,next 指向后一个节点
Set 接口
基本介绍
- 无序,无索引
- 不可重复
遍历方式
- 迭代器
- 增强 for
Hashset 说明
- 底层使用的是 hashmap
- 可以存放 null
- 不能保证顺序,因为索引是由 hash 确定的
- 不能有重复元素
案例说明
HashSet set = new HashSet();
/说明
//1. 在执行 add 方法后,会返回一个 boolean 值
//2. 如果添加成功,返回 true, 否则返回 false
//3. 可以通过 remove 指定删除哪个对象
System.out.println(set.add("john"));//T
System.out.println(set.add("lucy"));//T
System.out.println(set.add("john"));//F
System.out.println(set.add("jack"));//T
System.out.println(set.add("Rose"));//T
set.remove("john");
System.out.println("set=" + set);//3 个
//
set = new HashSet();
System.out.println("set=" + set);//0
//4 Hashset 不能添加相同的元素/数据?
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok
System.out.println("set=" + set);
//在加深一下. 非常经典的面试题.
//看源码,做分析, 先给小伙伴留一个坑,以后讲完源码,你就了然
//去看他的源码,即 add 到底发生了什么?=> 底层机制.
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了.
System.out.println("set=" + set);
class Dog { //定义了 Dog 类
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
Hashset
- 底层是红黑树加链表加数组
- 现获取元素的哈希值
- 对哈希值进行运算,得出一个索引值即为要存在哈希表中的位置号
- 如果该位置上没有其他元素则直接存放,如果该位置已经有其他元素,则需要进行 equals 判断,如果相等,则不添加,如果不相等则以链表形式添加
- 在 java 8 中如果一条链表长度达到 8 ,并且 table 大小达到 64 时,会进行树化,否则仍然采用数组扩容机制
HashSet hashSet = new HashSet();
hashSet.add("java");//到此位置,第 1 次 add 分析完毕.
hashSet.add("php");//到此位置,第 2 次 add 分析完毕
hashSet.add("java");
System.out.println("set=" + hashSet);
/*
老韩对 HashSet 的源码解读
1. 执行 HashSet()
public HashSet() {
map = new HashMap<>();
}
2. 执行 add()
public boolean add(E e) {//e = "java"
return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
}
3.执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值 算法 h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//key = "java" value = PRESENT 共享
return putVal(hash(key), key, value, false, true);
}
4.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
//table 就是 HashMap 的一个数组,类型是 Node[]
//if 语句表示如果当前 table 是 null, 或者 大小=0
//就是第一次扩容,到 16 个空间.
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据 key,得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置
//并把这个位置的对象,赋给 p
//(2)判断 p 是否为 null
//(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
Node<K,V> e; K k; //
//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
//并且满足 下面两个条件之一:
//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树,
//如果是一颗红黑树,就调用 putTreeVal , 来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点
// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先 table 扩容.
// 只有上面条件不成立时,才进行转成红黑树
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//size 就是我们每加入一个结点 Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
*/
分析扩容机制
- Hashset 底层是 hashmap,第一次添加时 table 扩容到 16,临界值是 16*加载因子 0.75=12
- 如果 table 数组使用到了临界值(添加次数达到 12),就会扩容到 16_2=32,新的临界值就是 32_0.75=24,以此类推
linkedHashset
- hashset 的一个子类
- 底层维护了一个数组+双向链表
- linkedhashset根据元素的hashcode值来决定元素的存储位置,同时使用链表维护元素的次序(图),使元素看起来向是以插入顺序保存的
- linkedhashset不允许添加重复元素
map接口和常用方法
接口实现类特点
- map与collection并列存在。用于保存具有映射关系的数据
- map中的key不允许重复
- map中的value可以重复
- map的key可以为null,注意key为null,只能有一个,value为null可以为多个
- 常用的string类作为map的key
- key和value之间存在单向一对一对应关系,即通过指定的key总能找到对应的value
常用方法
package com.cui.main;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @projectName: javaSeCode
* @package: com.cui.main
* @className: ExplanationIterators
* @author: 崔
* @description: TODO
* @date: 2023/8/27 10:30
* @version: 1.0
*/
public class ExplanationIterators {
public static void main(String[] args) {
Map<String, Dog> dogMap = new HashMap();
//添加元素
dogMap.put("123", new Dog("123"));
dogMap.put("456", new Dog("456"));
dogMap.put("789", new Dog("789"));
//通过迭代器遍历
Iterator<Map.Entry<String, Dog>> iterator = dogMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Dog> next = iterator.next();
System.out.println(next);
}
//获取元素
System.out.println(dogMap.get("123"));
//判断是否含有key
System.out.println(dogMap.containsKey("123"));
//删除元素
dogMap.remove("123");
System.out.println(dogMap);
//获取元素个数
System.out.println(dogMap.size());
//判断集合是否为空
System.out.println(dogMap.isEmpty());
//清空集合
dogMap.clear();
System.out.println(dogMap);
}
}
class Dog { //定义了 Dog 类
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
map遍历方式
- containKey:查找键是否存在
- keySet:获取所有键
- entrySet:获取所有关系k-v
- values:获取所有的值
hashMap
- hashmap是map接口使用频率最高的实现类
- hashmap是以键值对方式来存储数据
- key不能重复,但是值可以重复,允许使用null键和null值
- 如果添加相同的key,则会覆盖原来的key-val,等同于修改。
- 与hashset一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
- hashmap没有实现同步,因此线程不安全,方法没有做同步互斥的操作,没有
synchronized
hashmap底层机制
hashtable
基本介绍
- 存放的是键值对
- hashtable的键和值都不能为null,否则会抛出空指针异常
- hashtable使用方法基本和hashmap一样
- hashtable是线程安全的hashmap是线程不安全的
properties
基本介绍
- 继承自hashtable并实现了map接口,也是键值对形式的数据
- 特点与hashtable类似
- properties还可以用于从配置文件中加载数据到properties对象中
如何选择集合
- 先判断存储类型
- 一组对象(单例):collection接口
- 允许重复:list
- 增删多:linkedlist(底层是一个双向链表)
- 改查多:arraylist(底层是一个object数组)
- 不允许重复 set
- 无序:hashset(底层是hashmap,维护了一个哈希表(数组+链表+红黑树))
- 排序:treeset
- 插入和取出顺序一致:linkedhashset,(维护数组+双向链表)
- 允许重复:list
- 一组键值对
- 键无序:hashmap(底层是哈希表)
- 键排序:treemap
- 键插入和取出顺序一致:linkedhashmap
- 读取文件properties
- 一组对象(单例):collection接口
collections工具类
看api
泛型
基本介绍
- 泛型又称参数化类型,用来解决数据类型的安全性问题
- 在类声明或实例化时只要制定好需要的具体的类型即可
- java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生类型转换异常
- 可以在类声明时通过一个标识标识类中某个属性的类型,或者是某个方法的返回值类型,或者参数类型
泛型声明
class T1<T>{}
interface T2<K>{}
// T和K不代表值,代表任意类型,在使用时确定
//泛型实例化
List<String> strings = new ArrayList<>();
注意事项和细节
List<String> strings = new ArrayList<>();
//尖括号里只能是引用类型
//给泛型制定类型后只能传入该类型或者该类型的子类型
//如果不制定类型默认是object类型
自定义泛型类
基本语法
class T1<T,U....>{}//泛型可以有多个
细节
- 普通成员可以使用泛型
- 使用泛型数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型的类型,实在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时没有指定类型,默认为object
自定义泛型接口
基本语法
interface 接口名<K......>{}
细节
- 接口中,静态成员也不能使用泛型
- 泛型接口的类型,在继承接口或者实现接口时确定
- 没有指定类型默认为obejct
自定义泛型方法
基本语法
修饰符 <T.....> 返回类型 方法名(){}
注意细节
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,需确定类型
泛型的继承和通配符
基本说明
<?> 表示支持任意泛型类型
<? extends A> 支持A类及A的子类,规定了泛型的上限
<? super A> 支持A类及A的父类,不限于直接父类,规定了泛型的下限
多线程
相关概念
程序
就是我们写的代码
进程
- 进程是指运行中的程序,比如我们使用qq就启动一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有自身的产生、存在和消亡的过程。
线程
- 线程由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
其他相关概念
单线程
同一个时刻,只允许执行一个线程
多线程
同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口。
并发
同一个时刻,多个任务交替执行,造成一种貌似同时的错觉,(单核cpu实现多任务就是并发)
并行
同一个时刻,多个任务同时执行。(多核cpu可以实现并行)
线程的基本使用
继承Thread类
class T extends Thread {
@Override
public void run() {
System.out.println("继承thread实现多线程");
}
}
public class MyThread {
@Test
public void test() {
T t = new T();
//调用start程序并不会马上执行,只是进入可执行状态,具体什么时候执行由cpu决定
t.start();
}
}
实现Runnable
class K implements Runnable{
@Override
public void run() {
System.out.println("使用实现runnable接口实现多线程");
}
}
public class MyThread {
@Test
public void test() {
new Thread(new K()).start();
}
}
使用案例
- 编写两个线程,其中一个输出helloword,输出100次结束,另一个输出hi输出50次结束.
public class MyThread {
@Test
public void test() {
new Thread(new K()).start();
new Thread(new T()).start();
}
}
class T implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("helloword");
}
}
}
class K implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("hi");
}
}
}
继承与实现方式的区别
- 从java设计角度看,通过继承或者实现接口来创建线程本质没有区别。
- 实现接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用runnable
售票系统模拟,并分析问题
class K implements Runnable {
private static int tickNum = 100;
@Override
public void run() {
while (true) {
if (tickNum <= 0) {
System.out.println("售票结束");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "售出一张票" + "还剩" + (--tickNum));
}
}
}
class T extends Thread {
private static int tickNum = 100;
@Override
public void run() {
while (true) {
if (tickNum <= 0) {
System.out.println("售票结束");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "售出一张票" + "还剩" + (--tickNum));
}
}
}
- 存在超卖以及重复买票问题
线程终止
基本说明
- 当线程完成任务后,自动退出
- 还可以通过控制变量来控制run方法退出的方式停止线程,即通知方式
控制变量方式案例
public class MyThread {
public static void main(String[] args) {
T t1 = new T();
t1.start();
System.out.println("停止t1线程");
t1.setFlag(false);
}
}
class T extends Thread {
private boolean flag = true;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while (flag) {
System.out.println("hello");
}
}
}
线程常用方法
常用方法第一组
public class MyThread {
public static void main(String[] args) {
T t1 = new T();
//设置线程名称
t1.setName("设置线程名称");
//返回线程名称
t1.getName();
//使线程开始执行
t1.start();
t1.setFlag(false);
//调用线程对象的run方法
/*
更改线程优先级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
*/
t1.setPriority(Thread.MIN_PRIORITY);
//获取线程的优先级
t1.getPriority();
//线程睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//终端线程 一般用于终端正在睡眠的线程
t1.interrupt();
}
}
注意事项和细节
- start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程
- 线程优先级范围
- interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
- sleep 线程的静态方法,使当前线程休眠
常用方法第二组
- yield线程的礼让。让出cpu,让其他线程执行,但礼让时间不确定
- join:线程插队,线程的插队一旦插队成功,则肯定先执行完插入的线程所有的任务,由需要插队的线程调用
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
线程的声明周期
线程状态转换图
线程的同步
线程同步机制
- 在多线程编程一些敏感数据不允许被多个线程同时访问,此时使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
- 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步具体方法
1. synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步代码
}
2. synchronized //还可以放在方法声明中,表示这个方法-为同步方法
互斥锁
基本介绍
- java引入了对象互斥锁,来保证共享数据操作的完整性
- 每个对象都对应于一个可称为互斥锁的标记,这个标记用来保证在任意时刻只有一个线程访问该对象
- 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态)的锁为当前类本省
.class
对象
解决卖票问题
class K extends Thread {
private static int tickNum = 100;
final String str = "";//定义锁对象,只要保证锁对象不变即可
public synchronized void m1() {
}
@Override
public void run() {
synchronized (str) {
while (true) {
if (tickNum <= 0) {
System.out.println("售票结束");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "售出一张票" + "还剩" + (--tickNum));
}
}
}
}
线程的死锁
基本介绍
- 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
模拟线程死锁
class K extends Thread {
private static int tickNum = 100;
static Object o1 = new Object();
static Object o2 = new Object();
final String str = "";
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
private boolean flag = true;
public synchronized void m1() {
}
@Override
public void run() {
if (flag) {
synchronized (o1) {
System.out.println("01");
synchronized (o2) {
System.out.println("o2");
}
}
} else {
synchronized (o2) {
System.out.println("02");
synchronized (o1) {
System.out.println("o2");
}
}
}
}
}
释放锁
释放锁情况
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的error或exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait方法、当前线程暂停,并释放锁
不会释放锁情况
- 调用sleep、yield方法
- 其他线程使用了该线程的suspend方法将该项成挂起不会释放锁
IO流
文件
常用的文件操作
public static void main(String[] args) {
String filePath = "d:/a.text";
//通过文件路径创建文件对象
File file = new File(filePath);
//通过父目录文件对象加子路径创建文件对象
String parentPath = "d:/file";
File file1 = new File(parentPath);
File file2 = new File(file1, "a.text");
//根据父目录路径与子路径创建文件对象
File file3 = new File(parentPath, filePath);
try {
file.createNewFile();
file2.createNewFile();
file3.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
获取文件相关信息
//获取文件名字
String name = file.getName();
//获取绝对路径
String absolutePath = file.getAbsolutePath();
//获取父目录,如果没有父目录则返回null
String parent = file.getParent();
//获取文件长度,如果该文件为目录则返回值未指定
long length = file.length();
//判断文件是否存在
boolean exists = file.exists();
//判断是否是文件
boolean file4 = file.isFile();
//判断是否是目录
boolean directory = file.isDirectory();
目录的操作和文件删除
//创建一级目录
file.mkdir();
//创建多级目录
file.mkdirs();
//删除空目录或者文件
file.delete();
IO流的分类
- 按操作数据单位不同:
- 字节流(8 bit)二进制文件,字符流(按字符)文本文件
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
- java的io总共设计40多个类,都从上表四个抽象基类派生出来的
- 由这四个派生出来的子类名称都是以其父类名作为子类名后缀
IO流常用类
IO体系图
FileInputStream
/**
* 单个字节读取文件,速度较慢
*/
@Test
public void test() {
String pathName = "e:/hello.txt";
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(pathName);
int readData = 0;
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用字节数组读取文件,效率较高
*/
@Test
public void test02() {
String pathName = "e:/hello.txt";
FileInputStream fileInputStream = null;
byte[] bytes = new byte[1024];
try {
fileInputStream = new FileInputStream(pathName);
int readData = 0;
while ((readData = fileInputStream.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, readData));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileOutputStream
@Test
public void test03() {
String outName = "e:/hello01.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(outName);
//开启追加模式
fileOutputStream = new FileOutputStream(outName, true);
//写入字符
fileOutputStream.write('h');
String message = "asdsad";
//写入字符串
fileOutputStream.write(message.getBytes());
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader和FileWriter
fileReader相关方法
new FileReader(File/String);
read();//每次读取单个字符,返回该字符,如果是文件末尾则返回-1
read(char[])//批量读取多个字符到数组
fileWriter相关方法
new FileWriter(File/String)
覆盖模式new FileWriter(File/String) 追加模式
write(int)
写入字符- == FileWrite 使用后必须关闭或刷新,否则写入不到指定的文件
/**
* 单个字符读取
*/
@Test
public void test04() {
String pathName = "e:/hello.txt";
FileReader fileReader = null;
try {
fileReader = new FileReader(pathName);
int readData = 0;
while ((readData = fileReader.read()) != -1) {
System.out.print((char) readData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符数组读取文件
*/
@Test
public void test05() {
String pathName = "e:/hello.txt";
FileReader fileReader = null;
char[] chars = new char[1024];
try {
fileReader = new FileReader(pathName);
int readData = 0;
while ((readData = fileReader.read(chars)) != -1) {
System.out.print(new String(chars,0,readData));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
节点流和处理流
基本介绍
- 节点流可以从一个特定的数据源读写数据
- 处理流是连接在已存在的流智商,为程序提供更为强大的读写功能。
节点流和处理流的区别和联系
- 节点流是底层流,直接跟数据源相接
- 处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出
- 处理流对接点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
处理流的功能体现
- 性能:主要以增加缓冲的方式提高输入输出效率
- 操作:处理流提供了一系列便捷方法来一次输入输出大批量的数据,使用更加灵活方便
BufferedReader 和 BufferedWriter
不能操作二进制文件会造成损坏
BufferedInputStream 和 BufferedOutputStream
ObjectInputStream 和 ObjectOutputStream
- 对数据类型和对象进行序列化
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 序列化条件
- 实现
Serializable
接口或者实现Externalizable
接口
- 实现
注意事项
- 读写顺序要一致
- 序列化的类中创建SerialversionUID可以提高版本兼容性
- 序列化对象时,默认将里面所有属性都序列化,除了static或transient修饰的成员
- 序列化对象时要求里面属性的类型实现了序列化接口
- 序列化具备可继承性
标准输入输出流
system.in和system.out
InputStreamReader 和 OutputStreamWriter
inputStreamReader
:Reader
的子类,可以将inputStream
(字节流)包装成reader
(字符流)outputStreamWriter
:Writer
的子类,实现将outputSteram
(字节流)包装成writer
(字符流)- 可以解决中文问题,提升效率
- 可以在使用时指定编码格式
PrintStream 和 PrintWriter
Properties 类
- 用来读取配置文件
- 键值对不需要有空格,值不需要用引号引起来。默认值是字符串
- load:加载配置文件
- list:输出数据
- getProperty(key)
- setProperty(key,value)
- store:对象中的键值对存储在配置文件中,如果含有中文idea会显示unicode码,可以再设置中修改为显示中文
网络编程
TCP和UDP
TCP
- 使用tcp协议前,需要先建立连接,形成传输数据通道
- 传输前,采用三次握手方式,是可靠的
- tcp协议进行通信的两个应用进程:客户端、服务端
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据包大小限制在64K内,不适合传输大量数据
- 因无序连接,所以不可靠
- 发送数据结束时无序释放资源(因为不是面向连接的)速度快
InetAddress
相关方法
//获取本机InetAddress对象
InetAddress localHost1 = InetAddress.getLocalHost();
System.out.println(localHost1);
//根据指定主机名/域名获取ip地址对象
InetAddress byName = InetAddress.getByName("192.168.99.88");
System.out.println(byName);
//获取对象的主机名
System.out.println(localHost1.getHostName());
//获取对象的地址
System.out.println(localHost1.getHostAddress());
运行结果
Socket
- 套接字(socket)开发网络应用程序被广泛采用,以至于称为事实上的标准
- 通信的两端都要有socket,是两台机器通信的断电
- 网络通信其实就是socket间的通信
- socket允许程序把网络连接当成一个流,数据在两个socket间通过IO传输
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端
示意图
基于socket的tcp编程实例
字节流案例
package com.cui.main.network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @projectName: javaStudyCode
* @package: com.cui.main.network
* @className: SocketTCP
* @author: 崔
* @description: 服务器
* @date: 2023/9/12 15:38
* @version: 1.0
*/
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
/*
1. 需要当前主机9999端口没被占用
2. serverSocket可以通过accept返回多个socket(多个客户端连接)
*/
ServerSocket serverSocket = new ServerSocket(9999);
/*
1. 当调用accept方法时若没有客户端连接,程序会阻塞在这里
*/
System.out.println("服务器等待中......");
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
byte[] bytes = new byte[1024];
int read = 0;
while ((read = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, read));
}
OutputStream outputStream = accept.getOutputStream();
String message = "hello,client";
outputStream.write(message.getBytes());
accept.shutdownOutput();
outputStream.close();
inputStream.close();
accept.close();
serverSocket.close();
}
}
package com.cui.main.network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @projectName: javaStudyCode
* @package: com.cui.main.network
* @className: SocketTcpClient
* @author: 崔
* @description: 客户端
* @date: 2023/9/12 15:45
* @version: 1.0
*/
public class SocketTcpClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream outputStream = socket.getOutputStream();
String message = "hello,word";
outputStream.write(message.getBytes());
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int read = 0;
while ((read = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, read));
}
inputStream.close();
outputStream.close();
socket.close();
}
}
字符流案例
package com.cui.main.network.characterstream;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @projectName: javaStudyCode
* @package: com.cui.main.network.characterstream
* @className: SocketServer
* @author: 崔
* @description: TODO
* @date: 2023/9/12 15:59
* @version: 1.0
*/
public class SocketTcpServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String message = bufferedReader.readLine();
System.out.println(message);
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello,client");
//添加一个新行表示结束,但是要求读取时使用readLine读取
bufferedWriter.newLine();
bufferedWriter.flush();
//使用缓存方式写入一定要刷新,
// bufferedWriter.flush();
// socket.shutdownOutput();
bufferedReader.close();
bufferedWriter.close();
socket.close();
serverSocket.close();
}
}
package com.cui.main.network.characterstream;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @projectName: javaStudyCode
* @package: com.cui.main.network.characterstream
* @className: SocketTcpClient
* @author: 崔
* @description: TODO
* @date: 2023/9/12 16:06
* @version: 1.0
*/
public class SocketTcpClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("hello,server");
bufferedWriter.newLine();
bufferedWriter.flush();
// bufferedWriter.flush();
// socket.shutdownOutput();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message = bufferedReader.readLine();
System.out.println(message);
bufferedWriter.close();
bufferedReader.close();
socket.close();
}
}
传输图片案例
package com.cui.main.network;
import com.cui.main.utils.StreamUtils;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @projectName: javaStudyCode
* @package: com.cui.main.network
* @className: SocketServerImg
* @author: 崔
* @description: TODO
* @date: 2023/9/12 16:21
* @version: 1.0
*/
public class SocketServerImg {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
byte[] bytes = StreamUtils.streamToArray(inputStream);
String destPath = "e:/test2.jpg";
FileOutputStream fileOutputStream = new FileOutputStream(destPath);
fileOutputStream.write(bytes);
fileOutputStream.close();
accept.close();
serverSocket.close();
}
}
package com.cui.main.network;
import com.cui.main.utils.StreamUtils;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @projectName: javaStudyCode
* @package: com.cui.main.network
* @className: SocketClientImg
* @author: 崔
* @description: TODO
* @date: 2023/9/12 16:29
* @version: 1.0
*/
public class SocketClientImg {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream outputStream = socket.getOutputStream();
String filePath = "e:/test.jpg";
FileInputStream fileInputStream = new FileInputStream(filePath);
byte[] bytes = StreamUtils.streamToArray(fileInputStream);
outputStream.write(bytes);
socket.shutdownOutput();
outputStream.close();
socket.close();
}
}
netstat指令
- netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
- netstat -an|more 可以分页显示
- 要求在dos控制台下执行
- listening 表示某个端口在监听
- 如果有一个外部程序(客户端)连接到该端口,就会显示一条连接信息
tcp注意事项
客户端与服务器通信也是通过一个端口进行的,这个端口是随机的
udp编程
反射
Reflection
- 反射机制允许程序在执行期间借助于 reflection api 取得任何类的内部信息,并能操作对象属性及方法。反射在设计模式和框架底层都会用到
- 加载完类之后,在堆中就产生了一个 class 类型的对象(一个类只有一个 class 对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个 class 对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射
反射原理示意图
反射优缺点
- 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
- 缺点:使用反射基本是解释执行,对执行速度有影响。
Class 类
- Class 也是类,因此也继承 object 类
- Class 类对象不是 new 出来的,而是系统创建的
- 类的 class 对象在内存中只有一个,因为类只加载一次
- 每个类的实力都会记得自己是由哪个 class 实力所生成的
- 通过 class 对象可以得到一个类的完整结构
- Class 对象是存在堆中
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限)
Class 类的常用方法
package com.cui.main.reflect;
import java.lang.reflect.Field;
/**
* @projectName: javaStudyCode
* @package: com.cui.main.reflect
* @className: ReflectClass
* @author: 崔
* @description: TODO
* @date: 2023/9/14 21:15
* @version: 1.0
*/
public class ReflectClass {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classPath = "com.cui.main.reflect.Cat";
//根据类路径获取类对象
Class<?> aClass = Class.forName(classPath);
System.out.println(aClass);
//输出类对象的运行类型
System.out.println(aClass.getClass());
//获取包名
System.out.println(aClass.getPackage().getName());
//获取全类名
System.out.println(aClass.getName());
//创建对象实例
Object o = aClass.newInstance();
System.out.println(o);
//获取对象属性
Field age = aClass.getField("age");
System.out.println(age.get(o));
//给获取到的属性赋值
age.set(o, 123);
System.out.println(age.get(o));
Field[] fields = aClass.getFields();
for (Field f : fields) {
System.out.println(f.get(o));
}
}
}
获取 class 类对象
- 前提:已知一个类的全类名,且该类在类路径下,可通过 class 类的静态方法
forName()
获取,可能抛出classNotFoundException
- 若已知具体类可以通过类. Class 获取
- 已知某个类实例可以通过
getClass()
方法实现 - 其他方式通过类加载器获取 class 对象
- 基本数据通过.
class
得到 - 基本类型对应的包装类通过
.TYPE
得到
哪些类有 class 对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- Interface:接口
- 数组
- Enum:枚举
- Annotation;注解
- 基本数据类型
- Void
类加载
基本说明
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类,及时不存在该类,则不报错,降低了依赖性
类加载时机
- 静态加载
- 当创建对象时(new)
- 当子类被加载时,父类也加载
- 调用类中静态成员时
- 动态加载
- 通过反射
类加载过程图
类加载各阶段任务
加载阶段
Jvm 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 class 对象
连接阶段-验证
- 目的是为了确保 class 文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机的自身安全
- 文件格式验证、元数据验证、字节码验证、和符号引用验证
- 可以考虑使用
-Xverify:none
参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段-准备
- Jvm 会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值)。这些变量所使用的内存都将在方法区中进行分配
连接阶段-解析
虚拟机将常量池的符号引用替换为直接的引用的过程
初始化
- 到初始化阶段,才真正开始执行类中定义的 java 程序代码,此阶段是执行client()方法的过程
- client()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块的语句,并进行合并。
- 虚拟机会保证一个类的client()方法在多线程环境中被正确地枷锁,同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的client()方法,其他线程都需要阻塞等待,直到活动线程执行client()方法完毕
通过反射获取类结构信息
Class 类
String classPath = "com.cui.main.reflect.Cat";
Class<?> aClass = Class.forName(classPath);
//获取全类名
System.out.println(aClass.getName());
//获取简单类名
System.out.println(aClass.getSimpleName());
//获取所有public修饰的属性,包括本类及父类
System.out.println(Arrays.toString(aClass.getFields()));
//获取本类中所有属性
System.out.println(Arrays.toString(aClass.getDeclaredFields()));
//获取本类中所有公开方法及本类以及父类的方法
System.out.println(Arrays.toString(aClass.getDeclaredMethods()));
//获取所有公开的构造器
System.out.println(Arrays.toString(aClass.getConstructors()));
//获取本类所有构造器
System.out.println(Arrays.toString(aClass.getDeclaredConstructors()));
//以packaged返回包信息
System.out.println(aClass.getPackage());
//以class形式返回父类信息
System.out.println(aClass.getSuperclass());
//以class[]数组形式返回接口信息
System.out.println(Arrays.toString(aClass.getInterfaces()));
//以注解数组形式返回注释信息
System.out.println(Arrays.toString(aClass.getAnnotations()));
Filed
String classPath = "com.cui.main.reflect.Cat";
Class<?> aClass = Class.forName(classPath);
Field age = aClass.getField("age");
//以int形式返回修饰符 默认是0、public是1、private是2、protected是4、static是8、final是16
System.out.println(age.getModifiers());
System.out.println(age.getType());//以class形式返回类型
System.out.println(age.getName());//返回属性名
Method
String classPath = "com.cui.main.reflect.Cat";
Class<?> aClass = Class.forName(classPath);
Method hi = aClass.getMethod("hi");
System.out.println(hi.getModifiers());//以int形式返回修饰符
System.out.println(hi.getReturnType());//以class形式返回类型
System.out.println(hi.getName());//返回方法名
System.out.println(Arrays.toString(hi.getParameterTypes()));//返回参数类型数组
Constructor
String classPath = "com.cui.main.reflect.Cat";
Class<?> aClass = Class.forName(classPath);
Constructor<?> constructor = aClass.getConstructor();
System.out.println(constructor.getModifiers());
System.out.println(Arrays.toString(constructor.getParameterTypes()));
System.out.println(constructor.getName());
通过反射创建对象
通过反射获取类中的成员
- 如果是静态属性和静态方法参数对象可以换成 null
正则表达式
正则表达式底层实现
String content = "1998 年 12 月 8 日,第二代 Java 平台的企业版 J2EE 发布。1999 年 6 月,Sun 公司发布了"
+
"第二代 Java 平台(简称为 Java2)的 3 个版本:J2ME(Java2 Micro Edition,Java2 平台的微型" +
"版),应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition,Java 2 平台的" +
"标准版),应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2 平台的企业版),应" +
"用 3443 于基于 Java 的应用服务器。Java 2 平台的发布,是 Java 发展过程中最重要的一个" +
"里程碑,标志着 Java 的应用开始普及 9889 ";
//目标:匹配所有四个数字
//说明
//1. \\d 表示一个任意的数字
String regStr = "(\\d\\d)(\\d\\d)";
//2. 创建模式对象[即正则表达式对象]
Pattern pattern = Pattern.compile(regStr);
//3. 创建匹配器
//说明:创建匹配器 matcher, 按照 正则表达式的规则 去匹配 content 字符串
Matcher matcher = pattern.matcher(content);
//4.开始匹配
/**
*
* matcher.find() 完成的任务 (考虑分组
* 什么是分组,比如 (\d\d)(\d\d) ,正则表达式中有() 表示分组,第 1 个()表示第 1 组,第 2 个()表示第 2 组...
* 1. 根据指定的规则 ,定位满足规则的子字符串(比如(19)(98))
* 2. 找到后,将 子字符串的开始的索引记录到 matcher 对象的属性 int[] groups;
* 2.1 groups[0] = 0 , 把该子字符串的结束的索引+1 的值记录到 groups[1] = 4
* 2.2 记录 1 组()匹配到的字符串 groups[2] = 0 groups[3] = 2
* 2.3 记录 2 组()匹配到的字符串 groups[4] = 2 groups[5] = 4
* 2.4.如果有更多的分组.....
* 3. 同时记录 oldLast 的值为 子字符串的结束的 索引+1 的值即 35, 即下次执行 find 时,就从 35 开始匹
配
*
* matcher.group(0) 分析
*
* 源码:
* public String group(int group) {
* if (first < 0)
* throw new IllegalStateException("No match found");
* if (group < 0 || group > groupCount())
* throw new IndexOutOfBoundsException("No group " + group);
* if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
* return null;
* return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
* }
* 1. 根据 groups[0]=31 和 groups[1]=35 的记录的位置,从 content 开始截取子字符串返回
* 就是 [31,35) 包含 31 但是不包含索引为 35 的位置
*
* 如果再次指向 find 方法.仍然安上面分析来执行
*/
while (matcher.find()) {
//小结
//1. 如果正则表达式有() 即分组
//2. 取出匹配的字符串规则如下
//3. group(0) 表示匹配到的子字符串
//4. group(1) 表示匹配到的子字符串的第一组字串
//5. group(2) 表示匹配到的子字符串的第 2 组字串
//6. ... 但是分组的数不能越界.
System.out.println("找到: " + matcher.group(0));
System.out.println("第 1 组()匹配到的值=" + matcher.group(1));
System.out.println("第 2 组()匹配到的值=" + matcher.group(2));}
正则表达式语法
基本介绍
- 限定符
- 选择匹配符
- 分组组合和反向引用符
- 特殊字符
- 字符匹配符
- 定位符
元字符-转义号
\符号说明:在我们使用正则表达式去检索某些特殊字符时,需要用转义字符,否则检索不到结果,甚至报错
字符匹配符
符号 | 介绍 | 示例 | 解释 |
---|---|---|---|
[] | 可接受的字符列表 | [efgh] | efgh 中的任意一个字符 |
[^] | 不接受的字符列表 | [^abc] | 除 abc 以外的其他字符包括数字和特殊符号 |
- | 连字符 | A-Z | 任意单个大写字母 |
. | 匹配\n 以外的任何字符 | a…b | 以 a 开头,b 结尾,中间包括两个任意字符长度为 4 的字符串 |
\\d | 匹配单个数字 | \\d{3}(\\d)? | 包括 3 个或 4 个数字的字符串 |
\\D | 匹配单个非数字字符 | \\D (\\d)* | 一单个非数字开头后接任意个数字字符串 |
\\w | 匹配单个数字、大小写字母 | \\d{3}\\w{4} | 以三个数字开头长度为 7 的数字字母字符串 |
\\W | 匹配单个非数字、大小写字母 | \\W+\\d{2} | 以至少 1 个非数字字母字符开头,2 个数字字符结尾的字符串 |
\\s | 匹配任何空白字符 | ||
\\S | 匹配任何非空白字符 | ||
//. | 匹配除\n 之外的所有字符,如果要匹配. 则使用\\. |
选择匹配符
|
相当于或
限定符
符号 | 含义 | 示例 | 说明 |
---|---|---|---|
* | 指定字符重复 0 或 n 次 | (abc)* | 包含任意个 abc 的字符串, 等效于\\w* |
+ | 指定字符重复 1 次或 n 次 | m+(abc)* | 以至少一个 m 开头,后接任意个 abc 的字符串 |
? | 指定字符重复 0 次或 1 次 | m+abc? | 以至少 1 个 m 开头,后接 ab 或 abc 的字符 |
{n} | 只能输入 n 个字符 | [abcd]{3} | 由 abcd 中字母组成的任意长度为 3 的字符串 |
{n,} | 指定至少 n 个 | [abcd]{3,} | 由 abcd 字母组成的任意长度不小于 3 的字符串 |
{n, m} | 指定至少 n 个但不多于 m 个 | [abcd]{3,5} | 由 abcd 字母组成长度不少于 3 个,不大于 5 的字符串 |
定位符
符号 | 含义 | 示例 | 说明 |
---|---|---|---|
^ | 指定起始字符 | ||
$ | 指定结束字符 | ||
\\b | 匹配目标字符串的边界 | 这里的边界指的是目标子串间有空格或者结束位置 | |
\\B | 匹配目标字符串的非边界 | 匹配的是开始位置, 包括空格之后的起始 |
分组
常用分组构造形式 | 说明 |
---|---|
pattern | 非命名捕获。捕获匹配的子字符串。编号为 0 的第一个捕获是由整个正则表达式模式匹配的文本,其他捕获结果则根据左括号的顺序从 1 开始自动编号 |
(?pattern) | 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于 name 的字符串不能包括任何标点符号,并且不能以数字开头。可以使用单引号替代接括号 (?‘name’) |
(?:pattern) | 匹配 pattern 但不捕获该匹配的子表达式,即他是一个非捕获匹配,不存储供以后使用的匹配。例如 industr(?: y|ies) 比 industry|industries 更经济的表达式 |
(?=pattern) | 他是一个非匹配捕获例如 windows (?=95|98|NT|2000)匹配 windows 2000 中的 windows, 但不匹配 windows 3.1 中的 windows |
(?!Pattern) |
反向引用
- 正则表达式内引用 \组号
- 正则表达式外引用 $组号
- 引用内容必须是已匹配到的