一、配置IDEA环境
I、JDK的安装
1. 下载链接
JDK版本:JDK17
最新版本下载链接:Java Downloads | Oracle
JDK17下载链接:Java 存档下载 - Java SE 17
2. 安装
安装位置修改为D盘
ctrl+shift+鼠标右键会在鼠标右键菜单栏中添加PowerShell窗口
校验安装是否成功,打开powershell窗口,输入java -version,出现 java 版本信息即为安装成功
3. 配置环境变量
- 右键
此电脑->高级系统设置->环境变量 - 点击
系统变量中的Path->编辑 - 将
C:\Program Files\Common Files\Oracle\Java\javapath选项修改为我们实际安装路径->D:\Program Files\Java\jdk17_dir\jdk-17\bin - 点击
确定->确定->确定保存
重新校验配置是否正确,重新打开powershell窗口,输入java -version,出现 java 版本信息即为配置成功
4. Hello World示例
- 编写代码
HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
- 编译
打开命令行powershell窗口,运行命令javac HelloWorld.java,生成一个HelloWorld.class文件即为成功 - 运行
java HelloWorld
// 控制台输出:Hello World!
II、IDEA的安装和使用
1. 下载链接
[IntelliJ IDEA – the Leading Java and Kotlin IDE] (www.jetbrains.com/idea/) 下载完之后根据自己需要勾选选项安装
2. 新建空项目
①. 新建项目
new project->Empty Project
name(项目名称)->HelloWorld
Location(项目路径)->E:\tempFile\java\_code
确定
②. 新建模块
右键HelloWorld项目->New->Module...
name->Mod01
其他默认
create
③. 创建包名
鼠标右键HelloWorld/Mod01/src->New->Package
包名:一般都是公司域名倒过来写,com.kanxue
④. 创建类
鼠标右键HelloWorld/Mod01/src/com.kanxue->New->Java Class->输入类名HelloWorld02
package com.kanxue;
public class HelloWorld02 {
public static void main(String[] args) {
System.out.println("你好 Java!");
}
}
IDEA编辑器中输入
sout,然后Tab,代码会自动补全为System.out.println();
⑤. 关闭项目
File->Close Project
3. 创建Java命令行项目
①. 新建项目
New Project
Name->TestModule
其他默认->create
②. 新建模块1
右键TestModule项目->New->Module...
name->Mod01
其他默认
create
③. 新建模块2
Mod02
④. 模块的删除
右键Mod02->Remove Module->Remove
这种方式并没有将选中模块文件删除,只是在项目中的引用关系移除了,项目中依然存在该文件
⑤. 重新引入删除的模块
选中TestModule根目录 → File → Project Structure...
左侧第一栏选中Modules → 第二栏点击+号 → 选择Import Module → 展开TestModule项目目录 → 选中将要引用的Mod02模块(刚才删除的) → OK → Next → Next → Next → Next → OverWrite → Create → Apply → OK
III. 常用的快捷键和主题
1. 快捷键
- 格式化代码:
Ctrl+Alt+L - 注释、取消注释:
Ctrl+/ - 快速生成方法:
Alt+Insert
2. 设置主题
File → Settings... → Appearance → Theme
二、什么是面向对象程序设计
三、什么是类和对象
1. 类的定义
src/Student.java
public class Student {
private String name;
private int age;
public void talk() {
System.out.println("My name is :" + name + ", age is :" + age);
}
public void setName(String pName) {
name = pName;
}
public void setAge(int pAge) {
age = pAge;
}
}
2. 对象的创建
- 代码 src/Main.java
public class Main {
public static void main(String[] args) {
Student student = new Student();
student.setAge(15);
student.setName("John");
student.talk();
Student student2 = new Student();
student2.setAge(20);
student2.setName("Jane");
student2.talk();
}
}
3. 计算机实例
src/Calc.java
public class Calc {
public double oper(double a, String operator, double b) {
switch (operator) {
case "+":
return add(a, b);
case "-":
return sub(a, b);
case "*":
return mul(a, b);
case "/":
return div(a, b);
default:
return 0;
}
}
public double add(double a, double b) {
return a + b;
}
public double sub(double a, double b) {
return a - b;
}
public double mul(double a, double b) {
return a * b;
}
public double div(double a, double b) {
return a / b;
}
}
src/Main.java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
System.out.println("请输入第1个数:");
Scanner num1 = new Scanner(System.in);
System.out.println("请输入操作符:");
Scanner op1 = new Scanner(System.in);
System.out.println("请输入第2个数:");
Scanner num2 = new Scanner(System.in);
Calc calc = new Calc();
double out1 = calc.oper(num1.nextDouble(), op1.next(), num2.nextDouble());
System.out.println("结果:" + out1);
double out2 = calc.oper(num1.nextDouble(), op1.next(), num2.nextDouble());
System.out.println("结果:" + out2);
}
}
四、统一建模语言UML
1. 什么是UML
是一种用于对软件密集系统进行可视化建模的标准语言
2. PowerDesigner工具介绍
五、空白注释及语句关键字标识符
1. 空白
-
空白
\r \n \t 空格 -
编译器针对空格的处理
忽略多余的空白
2. 注释
-
单行注释
ctrl+/ -
多行注释
ctrl+shift+/
常用于:说明方法作用或一段代码功能 -
文档注释
/**回车
常用于:类定义或共有方法定义的前面
3. 语句
- Java语言最小执行单位,分号“;”结束
- 块语句
4. 关键字
有特殊用途的单词
5. 标识符
-
标识符 就是我们给各种方法函数、变量、类名起名字
-
命名规则
前提:不能用关键字
开头:字母、下划线、$
整体:字母、下划线、$、数字 -
约定俗成
见名知意:类名、方法名、变量名、常量名
六、变量与常量
1. 变量
-
什么是变量
程序运行期间,内存中可以被操作的一块空间 -
基本要素
名字、数据类型、作用域 -
使用规范
声明、初始化、使用
2. 常量
-
定义
内容不能更改的特殊变量 -
使用规范
final修饰
声明的时候必须初始化
3. 程序执行过程
-
一般程序执行过程
C/C++生成exe在windows系统上执行 -
JVM程序执行过程
Java生成.class后跨平台执行
七、数据类型
1. 逻辑类型
-
定义关键字
boolean -
取值范围
只能是truefalse -
注意,在java中以下不是逻辑类型
0、非0、null
2. 整数类型
-
包括
byteshortintlong -
取值范围
byte:8bit,-128~127,1个字节( ~ )
short:16bit,-32768~32767,2个字节( ~ )
int:32bit,-2147483648~2147483647,4个字节( ~ )
long:64bit,-9223372036854774808~9223372036854774808,8个字节( ~ ) -
进制表示
二进制、八进制、十进制、十六进制
3. 字符类型
-
关键字
char -
取值范围
0~65536,占用2个字节 -
编码
Ascii、Unicode、其他
4. 浮点类型
-
关键字
floatdouble -
有效范围
float:32bit,占用4个字节,有效8位
double:64bit,占用8个字节,有效16位 -
表示方法
小数表示法、科学计数法
八、运算符
1. 算术运算符
-
基本的四则运算
加减乘除模(+-*/%) -
增量运算符
-=+=*=%= -
自增自减
++-- -
说明
放前边先自增或自减后使用,反之则先使用后自增或自减
2. 关系运算符
-
包含
==!=<><=>= -
计算结果
true或false
3. 逻辑运算符
-
逻辑与
&& -
逻辑或
|| -
逻辑非
!
4. 位运算符
位运算符是一种用于对二进制位进行操作的运算符
-
按位与
&:如果两个位都为1,则结果为1,否则为0 -
按位或
|:如果两个位中至少有一个为1,则结果为1,否则为0 -
按位取反
~:将每个位0变为1,1变为0 -
按位异或
^:如果两个位相同,为0,否则为1
5. 移位运算符
-
算数左移
<<:符号位不变,右补0 -
算数右移
>>:符号位不变,左补符号位 -
逻辑右移
>>>:左补0
6. 其他运算符
-
三元运算符
?: -
instanceof
判断对象属不属于某个类Student s1 = new Student(); if (s1 instanceof Student) { System.out.println("s1是Student类的实例"); } else { System.out.println("s1不是Student类的实例"); }
九、类型转换
1. 情景
- 复制给变量的值与变量类型不同
- 类型等级-低到高
byteshortcharintlongfloatdouble
2. 自动类型转换
等级低的值或变量赋值给高等级变量
int i1 = 123;
int i2 = 456;
double d1 = (i1 + i2) * 1.2; // 1.2默认为double类型
3. 强制类型转换
等级高的值或者变量赋值给低等级变量需要强制类型转换
目标数据类型 变量名 = (目标数据类型)值或者变量
int i1 = 123;
int i2 = 456;
float f1 = (float)((i1 + i2) * 1.2); // 1.2默认为double类型
十、表达式语句、块、分支语句
1. 表达式语句
int a = 10, b = 20;
int c = (a + b) * 4;
2. 块
{}
3. 分支语句
if else if else switch
十一、循环语句
1. For循环语句
int result = 0;
int a = 1;
for (int i = 1; i <= 10; i++) {
if (i % 3 == 0) {
System.out.println("i=" + i + ", i是3的倍数");
}
}
2. While循环语句
int b = 1, sum = 0;
while (b <= 10) {
System.out.println(b);
sum += b;
b++;
}
System.out.println(sum);
3. do...while循环语句
int b = 1, sum = 0;
do {
System.out.println(b);
sum += b;
b++;
} while (b <= 10);
System.out.println(sum);
4. break和continue
break:结束循环
continue:结束本次循环,继续循环
十二、类
举例:台球碰撞 src/MyFrame.java
import java.awt.*;
public class MyFrame {
private int selfWidth = 500, selfHeight = 300;
private int x = 200, y = 300;
private int screenWidth = 0, screenHeight = 0;
private int loopCount = 0;
private boolean flagX = true, flagY = true;
private Frame frame;
public void createWindow() {
// 创建窗口
frame = new Frame("台球窗口例子");
// 窗口的可见性
frame.setVisible(true);
// 窗口大小
frame.setSize(selfWidth, selfHeight);
// 窗口背景色
frame.setBackground(new Color(255, 0, 0));
// 窗口的默认位置
frame.setLocation(x, y);
// 窗口大小是否可以改变
frame.setResizable(false);
// 获取屏幕大小
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
screenWidth = screenSize.width;
screenHeight = screenSize.height;
}
public void walk() {
while (true) {
loopCount += 1;
// 延时
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 根据变化方向,改变x和y的值 及其坐标
if (flagX)
x += 1;
else
x -= 1;
if (flagY)
y += 1;
else
y -= 1;
// 判断窗口是否跑出屏幕外
if (x < 0 || x + selfWidth > screenWidth)
flagX = !flagX;
if (y < 0 || y + selfHeight > screenHeight)
flagY = !flagY;
frame.setLocation(x, y);
}
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
MyFrame myFrame = new MyFrame();
myFrame.createWindow();
myFrame.walk();
}
}
十三、对象的创建和初始化
src/MyFrame.java
import java.awt.*;
public class MyFrame {
private int selfWidth = 500, selfHeight = 300;
private int x = 200, y = 300;
private int screenWidth = 0, screenHeight = 0;
private int loopCount = 0;
private boolean flagX = true, flagY = true;
private int sleepStep = 1;
private Frame frame;
public void setWindowSize(int pWindowWidth, int pWindowHeight) {
selfWidth = pWindowWidth;
selfHeight = pWindowHeight;
}
public void setPos(int pX, int pY) {
x = pX;
y = pY;
}
public void setFlag(boolean pFlagX, boolean pFlagY) {
flagX = pFlagX;
flagY = pFlagY;
}
public void setSleepStep(int pSleepStep) {
sleepStep = pSleepStep;
}
public void createWindow() {
// 创建窗口
frame = new Frame("台球窗口例子");
// 窗口的可见性
frame.setVisible(true);
// 窗口大小
frame.setSize(selfWidth, selfHeight);
// 窗口背景色
frame.setBackground(new Color(255, 0, 0));
// 窗口的默认位置
frame.setLocation(x, y);
// 窗口大小是否可以改变
frame.setResizable(false);
// 获取屏幕大小
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
screenWidth = screenSize.width;
screenHeight = screenSize.height;
}
public void walk() {
while (true) {
loopCount += 1;
// 延时
try {
Thread.sleep(sleepStep);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 根据变化方向,改变x和y的值 及其坐标
if (flagX)
x += 1;
else
x -= 1;
if (flagY)
y += 1;
else
y -= 1;
// 判断窗口是否跑出屏幕外
if (x < 0 || x + selfWidth > screenWidth)
flagX = !flagX;
if (y < 0 || y + selfHeight > screenHeight)
flagY = !flagY;
frame.setLocation(x, y);
}
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
MyFrame myFrame = new MyFrame();
myFrame.setWindowSize(50, 60);
myFrame.setFlag(false,true);
myFrame.setSleepStep(50);
myFrame.createWindow();
myFrame.walk();
}
}
十四、构造方法及其重载
1. 构造方法
-
定义
用来创建对象的方法(实例化对象的方法) -
作用
创建对象过程中对成员变量进行初始化 -
分类 五参数构造方法 & 有参数构造方法
-
格式
public class 类名 {修饰符 类名([参数列表] {方法体})}private String name; // 构造方法 public Person(String pName) { name = pName; System.out.println(name); }
2. 方法重载
-
定义
在同一类中,一组方法名相同,参数列表不同,就构成方法重载 -
特点
- 在同一个类中
- 方法名相同
- 参数列表不同(参数个数,参数顺序,参数类型)
- 与访问权限修饰符和返回值无关
-
优点
方法重载其实是对原有方法的一种升级,可以根据参数的不同,采用不同的实现方法,而且不需要编写多个名称,简化了类调用方法的代码 -
案例
public class Person { private String name = "张三"; private int age; // 构造方法 public Person(String pName) { name = pName; System.out.println(name); } // 构造方法重载 public Person(int pAge) { age = pAge; System.out.println(this.age); } }
十五、类的继承
1. 定义
继承是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为
2. 目的
代码重用,类的重用
名词:父类(基类、超类),子类(派生类)
关键词:extends
3. 格式
class 父类{}
class 子类 extends 父类{}
4. super关键字
我们可以通过super关键字实现对父类成员的访问,用来引用当前对象的父类
5. this关键字
指向自己的引用
6. final关键字
- 声明类:
final class 类名{类体} - 声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){方法体}
7. 访问权限控制
private:只能在类内部访问(权限要求最高,能被访问的范围越小)default:(啥也不写,默认的),包级权限,可以被同包的其他类访问protected:可以被子类访问,也可以被同包的其他类访问,protected在被同包的类访问的基础上,还能被其他包的子类访问public:可以在类外部访问(权限要求最低)
十六、多态与转换对象
1. 多态性
多态性是一种允许使用一个界面来访问一类动作的特性,特定的动作可以由不同的具体情况而定(传入不同的参数)
多态的必要条件:
① 有继承,父类定义方法,子类重写方法
② 父类的引用指向子类的重写
③ 可以使用参数传递多态,也可以直接创建对象时多态
多态的分类:
① 编译时多态:方法的重载
② 运行时多态:Java运行时系统根据调用该方法的实例的类型来决定选择调用哪个方法则被称为运行时多态
2. 对象转换类型
向上转型:
① 即将子类对象当作父类类型使用,调用父类中的定义,子类中实现的方法
② 格式:父类 对象名1 = new 子类()
向下转型:
① 若想调用子类中特有的方法,则需将父类类型的对象强制转换为子类类型
② 格式:子类 对象名2 = (子类)对象名1
十七、理解关键字
1. static关键字
static关键字表示静态,可以修饰成员变量、方法和代码块
static修饰成员变量:
① 被static修饰的属性所有对象共享同一个值
② 被static修饰的属性值会被保存在内存的方法区中
③ 当该类的所有对象共享一个资源时,将这个资源设置为静态能够节省内存
④ 静态属性使用类名调用
⑤ 静态属性也称为类属性
src/Cat.java
public class Cat {
public static String name;
public static void eat(String food) {
System.out.println(name + "吃" + food);
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
Cat cat1 = new Cat();
Cat cat2 = new Cat();
cat1.name = "小黄";
cat1.eat("秒爆");
Cat.eat("鱼");
cat2.eat("土豆");
Cat.name = "小白";
cat1.eat("秒爆");
Cat.eat("鱼");
cat2.eat("土豆");
cat2.name = "小黑";
cat1.eat("秒爆");
Cat.eat("鱼");
cat2.eat("土豆");
}
}
static修饰方法:
① 被static修饰的方法被称为静态方法,类方法
② 静态方法可以直接使用类名调用,无需实例化对象
③ 静态方法中无法调用普通(实例)属性和方法,因为静态方法中没有this
④ 如果一个不需要访问当前类中的实例成员就可以定义成静态方法
⑤ 静态方法不能被重写
static修饰代码块量:
此时该代码块称为静态代码块,属于类本身,该代码块也是在类加载的期间执行一次,也就是说,如果加载过这个代码块,就不会再加载了。可以为未初始化的静态常量赋值
2. abstract关键字
abstract关键字表示抽象的,可以修饰方法和类
抽象方法和抽象类:
① 由abstract修饰的方法为抽象方法,抽象方法只有方法的定义,没有方法体的实现,用一个分号结尾(一个普通方法中{}表示方法体,抽象方法中不能有{})
② 一个类中如果包含了抽象方法,该类应该用abstract关键字声明为抽象类
③ 如果一个类继承了抽象类,必须重写其抽象方法(除非该类也声明为抽象类)
④ 抽象类不可以实例化。即使一个类中没有抽象方法,也可以将其定义为抽象类,同样,该类不可以实例化。但是有抽象方法的类必须是抽象类
⑤ abstract和final关键字不可以同时用于修饰一个类,因为final关键字使得类不可以继承,而abstract修饰的类如果不可以集成将没有任何意义
public abstract class Animal {
public abstract void eat();
}
十八、方法重写规则和包装类型
1. 重写方法综述
重写方法是Java中一项重要特性,它允许在子类中重写父类中的方法,以在不改变父类结构的情况下提供特定于子类的实现。
2. 重写方法规则
① 方法名和形式参数必须具有完全相同的签名,返回类型也必须一致。方法可以改变返回类型,但子类的返回类型必须是父类返回类型的子类型。例如,如果父类的方法返回一个Object,子类的重写方法可以返回一个String,但重写方法不能返回Integer,因为String不是Integer的子类型
② 子类重写的方法可以改变其访问权限,比如将父类中的protected方法重写为public方法
③ 子类重写的方法不能抛出比父类中被重写的方法更广泛的异常
④ 如果一个类声明了一个方法带有final关键字,标明此方法为不可变的,不能被重写
⑤ 子类可以重写父类中被声明为static的方法,但必须保持方法的签名完全一致
⑥ 子类重写的方法可以调用父类中被重写的方法,而且可以使用super关键字在子类中直接调用父类中被重写的方法
3. 包装类型
为什么要包装基本数据类型?
基本数据类型没有对象的属性和方法,为了让基本类型具有对象的属性和方法,所以把基本数据包装成类
包装后的数据类型类
基本数据类型和包装类型的区别
① 基本数据类型没有对象的属性和方法
② 存储位置不一样,基本数据类型在栈中,包装类型在堆里面
③ 默认初始值不一样
十九、接口的定义和实现
1. 接口的关键字:interface
在定义一个接口时,我们需要把public class xxx中的class改成interface,即public interface xxx,这就是定义一个接口
2. 接口中定义的方法和常量
-
在定义方法时,例如我们定义一个
run方法与go方法,我们可以写成public abstract void run();和public abstract void go();,但是因为在接口的定义中都是abstract,因此我们只需要写:void run();和void go();即可。即直接写:方法类型+方法名(参数 类型);即可 -
在定义常量时,因为接口中所有的常量都是
public static void final,而final类是无法被继承的,所以我们不经常在接口中定义常量。
3. 接口及其实现类,implements关键字
-
由于接口仅仅能给我们定义一个方法,无法让我们去实例化(即实现它),因此我们需要再写一个实现类来实现它,而这个实现类的名字就是
接口名+Impl -
Impl就是implements,而想要实现接口中的类,就需要重写接口中的所有方法
4. 接口与继承的不同
接口为多继承,而extend是单继承,例如我们这里再定义一个TimeService的接口,我们可以在UserService实现类中再继承一个TimeService,只要重写TimeService中的方法即可
public class UserServiceImpl implements UserService, TimeService {
}
5. 接口的作用
- 接口是一种约束
- 接口可以定义一些方法让不同的人实现,(都是
public abstract) - 接口中的常量都是
public static final,final关键字无法被继承 - 接口不能被实例化,因为接口中没有构造方法
implements可以实现多个接口- 接口必须要重写里面的所有方法
6. 案例
src/Animal.java
public interface Animal {
void sleep();
}
src/Hunter.java
public interface Hunter {
void hunt();
}
src/Pet.java
public interface Pet {
void playWithOwner();
}
src/Cat.java
public class Cat implements Animal, Hunter, Pet {
@Override
public void sleep() {
System.out.println("趴着睡觉");
}
@Override
public void hunt() {
System.out.println("捕捉老鼠");
}
@Override
public void playWithOwner() {
System.out.println("陪主人互动,玩线团");
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.sleep();
cat.hunt();
cat.playWithOwner();
}
}
二十、内部类与匿名类
1. 内部类
-
Java中的类有两种重要的成员,即成员变量和方法。其实Java还允许类有一种成员,即在一个类中声明另一个类,这就是内部类,而包含内部类的类叫做外嵌类
-
内部类和外嵌类之间的关系:
① 内部类可以使用其外嵌类的成员变量和方法
② 内部类中不可以声明其外嵌类变量和类方法
③ 外嵌类可以使用内部类声明对象作为外嵌类的成员
④ 内部类仅提供它的外嵌类使用,其他类不可以用某个类的内部类声明对象 -
内部类可以被修饰为static内部类,类是一种数据类型,那么static内部类就是外嵌类中的一种静态数据类型,这样其他类就可以使用static内部类来声明创建对象了。注意:static内部类不能操作外嵌类中的实例成员变量
src/Farm.java
public class Farm {
public String farmName;
public Cow cow = new Cow("小白",5);
Farm(String pFarmName) {
this.farmName = pFarmName;
}
class Cow{
private String cowName;
int age;
Cow(String pCowName, int pAge){
this.cowName = pCowName;
this.age = pAge;
}
void speak() {
System.out.println("我是" + cowName + ",年龄" + age + ",我在农场:"+farmName);
}
}
static class Pig{
String pigName;
int age;
Pig(String pPigName, int pAge){
this.pigName = pPigName;
this.age = pAge;
}
void speak() {
System.out.println("我是" + pigName + ",年龄" + age);
}
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
Farm farm = new Farm("QQ农场");
farm.cow.speak();
Farm.Pig pig = new Farm.Pig("小黑", 3); // 使用内部类
pig.speak();
}
}
2. 和类有关的匿名类
-
我们可以直接使用一个类的子类的类体创建一个对象。在创建子类对象时,使用的是父类的构造方法和类体,这个类体被认为是子类去掉类声明之后的类体,称作匿名类
-
匿名类主要是为了实现,要执行的任务或者方法需要一个对象,但是又不值得创建一个全新的对象
-
匿名类可以继承,重写父类的方法,使用匿名类的时候,必然是在某个类当中直接使用匿名类创建对象,因此,匿名类一定是内部类
-
由于匿名类是一个子类,但是没有类名,所以在使用匿名类创建对象时必须使用父类的构造方法
-
匿名对象的引用可以传递给一个匹配的参数,匿名类常用的方式是向方法的参数传值
src/Speak.java
abstract class Speak {
Speak(){
System.out.println("父类构造方法");
}
public abstract void speakHello();
}
src/A.java
public class A {
void f(Speak sp) {
sp.speakHello();
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
// 第一种方式
Speak speak = new Speak(){
@Override
public void speakHello() {
System.out.println("Hello World");
}
};
speak.speakHello();
// 第二种方式
new Speak(){
@Override
public void speakHello() {
System.out.println("Hello World1");
}
}.speakHello();
// 第三种方式
A a = new A();
a.f(new Speak() {
@Override
public void speakHello() {
System.out.println("Hello World2");
}
});
}
}
3. 和接口有关的匿名类
-
我们可以直接使用接口名和一个类体创建一个匿名对象,这个类体被称为是实现了这个接口的类去掉类声明后的类体,称为匿名类
-
如果某个方法的参数是接口类型,那么可以使用接口名和类体组合创建一个匿名对象传递给这个方法的参数,但类体必须重写接口中的所有方法
src/Cubic.java
public interface Cubic {
double getCubic(double x);
}
src/B.java
public class B {
void g(Cubic cu) {
double ret = cu.getCubic(5);
System.out.println("result = " + ret);
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
// 第一种方式
Cubic cubic = new Cubic() {
@Override
public double getCubic(double x) {
return x * x * x;
}
};
System.out.println(cubic.getCubic(2));
// 第二种方式
System.out.println(new Cubic() {
@Override
public double getCubic(double x) {
return x * x * x;
}
}.getCubic(2));
// 第三种方式
B b = new B();
b.g(new Cubic() {
@Override
public double getCubic(double x) {
return x * x * x;
}
});
}
}
二一、数组初始化与使用
1. 数组声明与定义区别
- 对于变量来说,java定义就是声明
例如:int[] a;我们可以说它是定义也可以说它是声明
2. 初始化方法
-
初始化或者直接声明时完成,或者用
{}括起来,没初始化时,编译器只会提示identifier expected -
Java语言中数组必须先初始化,然后才可以使用
初始化就是为数组的数组元素分配内存空间,并为每个元素的赋初始值 -
注意:数组完成初始化后,内存空间中针对该数组的各个元素就有一个默认值
- 基本数据类型的整数类型(
byteshortintlong)默认值是0 - 基本数据类型的浮点类型(
floatdouble)默认值是0.0 - 基本数据类型的字符类型(
char)默认值是'\u0000' - 基本数据类型的布尔类型(
boolean)默认值是false - 类型的引用类型(类、数组、接口、String)默认值是
null
- 基本数据类型的整数类型(
3. 数组初始化方式
-
静态初始化
初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组的长度-
Type arrayName = new Type[]{element1, element2, element3...}
int[] arr2; arr2 = new int[]{1, 2, 3}; -
简化的静态初始化方式
type[] arrName = {element1, element2, element3...}
int[] arr1 = {1, 2, 3};
-
-
动态初始化
初始化时由程序员指定数组的长度,由系统初始化每个数组元素的默认值
arrayName = new type[length];
int[] arr3 = new int[3]; -
注意
不要同时使用静态初始化和动态初始化,也就是说,不要在进行数组初始化时,既指定数组的长度,也为每个数组元素分配初始值
一旦数组完成初始化,数组在内存中所占的空间将被固定下来,所以数组的长度将不可改变
4. 数组的使用
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// 数组定义
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = new int[4];
int[] arr3 = new int[]{1, 2, 3};
// 访问元素
for (int i = 0; i < arr1.length; i++) {
System.out.println("下标" + i + ":" + arr1[i]);
}
// 数组赋值
arr1[0] = 66;
// 数组的赋值(拷贝)
// 浅拷贝
int[] arr4 = arr1; // 两个数组是同一个内存地址
arr4[1] = 6;
System.out.println(arr1[1]); // 6
// 深拷贝
int[] arr5 = new int[arr1.length]; // 为arr5开辟新的内存地址
for (int i = 0; i < arr1.length; i++) {
arr5[i] = arr1[i];
}
arr5[3] = 33;
System.out.println(arr1[3]); // 4
int[] arr6 = {1,1,1,1,1,1,1};
System.arraycopy(arr3, 0, arr6, 0, 3);
for (int i = 0; i < arr6.length; i++) {
System.out.println("下标" + i + ":" + arr6[i]);
}
// 数组排序
Arrays.sort(arr6);
for (int i = 0; i < arr6.length; i++) {
System.out.println("下标" + i + ":" + arr6[i]);
}
// 不规则数组
int[][] arr7 = new int[4][];
arr7[0] = new int[3];
arr7[1] = new int[]{6, 5, 4};
arr7[2] = new int[]{3, 2, 1, 6, 8};
arr7[3] = new int[]{3, 3};
for (int i = 0; i < arr7.length; i++) {
System.out.println("");
for (int j = 0; j < arr7[i].length; j++) {
System.out.print(arr7[i][j] + ",");
}
}
}
}
二二、容器和字符串
I. 字符串
-
java.lang.String是 Java 的字符串类。String 是一个不可变对象,所有对 String 修改的操作都需要构造新的 String 实例 -
java.lang.StringBuffer与java.lang.StringBuilder是可变的字符串对象,StringBuilder 较快但是线程不安全的,在对线程安全没有要求时我们通常使用 StringBuilder
线程安全: 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问某一个类的某个数据时进行保护,其他线程不能进行访问直到该线程读取完其他线程才可使用,并且不会出现数据不一致或者数据污染的情况。
非线程安全: 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据,从而导致数据不一致,造成所得到的数据是脏数据
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
char[] data = {'a', 'b', 'c', 'd'};
String str1 = new String(data);
String str2 = new String("efg");
String str3 = "hijk";
String str4 = "lmn";
String str5 = "";
StringBuffer sb = new StringBuffer(str3);
for (int i = 0; i < 10000; i++) {
str5 = str3 + i; // 每次都会在内存中开辟新空间,造成内存浪费,如果垃圾回收机制不回收则内存占用会越来越多
sb.append(i); // 在固定内存中,会自动扩展内存空间
System.out.println(str5);
System.out.println(sb.toString());
}
// 字符串比较
// 此时的str6和str7是两个不同的内存地址,值相同但是内存地址不同
String str6 = new String("aaaa");
String str7 = new String("aaaa");
if (str6 == str7) {
System.out.println("str6 == str7 true");
} else {
System.out.println("str6 == str7 false"); // 运行结果
}
if (str6.equals(str7)) {
System.out.println("str6 == str7 true"); // 运行结果
} else {
System.out.println("str6 == str7 false");
}
// 此时的两个"aaaa"是同一块内存地址,所以str8==str9为true
String str8 = "aaaa";
String str9 = "aaaa";
if (str8 == str9) {
System.out.println("str8 == str9 true"); // 运行结果
} else {
System.out.println("str8 == str9 false");
}
}
}
II. 容器
1. 集合与数组
List Set Map
2. 容器的分类
- Collection:List、Set
- Map:HashMap、HashTable、TreeMap、ConcurrentHashMap
3. List
有序集合,允许重复的元素
4. Set
无序集合,不允许重复的元素
5. HashMap
采用散列算法来实现,底层用哈希表来存储数据,因此要求键不能重复。线程不安全,HashMap 在查找、删除、修改方面效率都非常高。允许 key 或 value 为 null
6. Map比较
- HashTable:与 HashMap 类似,只是其中的方法添加了 synchronized 关键字以确保线程同步检查,线程安全,但效率低。不允许 key 或 value 为 null
- TreeMap:红黑树的典型实现。TreeMap 和 HashMap 实现了同样的接口 Map。在需要 Map 中 Key 按照自然排序时才选用 TreeMap
import java.util.*;
public class Main {
public static void main(String[] args) {
// List
List myList = new ArrayList();
myList.add("A");
myList.add("B");
myList.add("C");
myList.add("D");
// 访问List
System.out.println(myList.get(0));
// 遍历List
// 方式1
for (int i = 0; i < myList.size(); i++) {
System.out.println(myList.get(i));
}
// 第二种方式,用迭代器
Iterator it = myList.iterator();
while (it.hasNext()) {
Object o = it.next();
System.out.println(o);
}
// List其他定义方式
List<String> aList = new ArrayList<>();
aList.add("A");
// HashMap
Map<String, String> map = new HashMap<>();
map.put("张三", "北京市xxx小区");
map.put("李四", "海南省xxx小区");
map.put("王五", "海南省zzz小区");
// 遍历
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println("key:"+key+",value:"+value);
}
// 用迭代器遍历
Iterator<Map.Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, String> entry = entries.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println("key:"+key+",value:"+value);
}
// 只遍历key
for (String oneKey : map.keySet()) {
}
// 只遍历值
for (String oneKey : map.values()) {
}
}
}
二三、公共异常
1. 异常简介
-
Java 中的异常又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。为了能够及时有效处理程序中的运行错误,必须使用异常类,这可以让程序具有极好的容错性且更加健壮。
-
在 Java 中一个异常的产生,主要有如下三种原因
① Java 内部错误发生异常,Java 虚拟机产生的异常
② 编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等
③ 通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息 -
我们把生成异常对象,并把它提交给运行时系统的过程称为抛出(throw)异常。运行时系统在方法的调用栈中查找,直到找到能够处理该类异常的对象,这一过程称为捕获(catch)异常
2. 异常类型
-
为了能够及时有效地处理程序中的运行错误,Java 专门引入了异常类。在 Java 中所有异常类型都是内置类
java.lang.Throwable类的子类,即Throwable位于异常类层次结构的顶层。Throwable类下有两个异常分支Exception和Error -
Throwable类是所有异常和错误的超类,下面有Error和Exception两个子类分别表示错误和异常。其中异常类Exception又分为运行时异常和非运行时异常,这两种异常有很大区别,也称为不检查异常(Unchecked Exception)和检查异常(Checked Exception)
3. 举例
import java.io.IOException;
public class Main {
public static void test() {
int m = Integer.parseInt("3333saf");
System.out.println("m:"+m);
}
public static void main(String[] args) {
try {
// test();
throw new IOException("IO出问题了");
} catch (IOException e) {
System.out.println(e.getMessage());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
二四、自定义异常
1. 自定义异常简介
-
如果 Java 提供的内置异常类型不能满足程序设计的需求,这时我们可以自己设计 Java 类库或框架,其中包括异常类型。实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类
-
语法形式:
<class><自定义异常名><extends><Exception> -
编码规范:一般将自定义异常类的类名命名为
XXXException,其中XXX用来代表该异常的作用
2. 自定义异常类
- 自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法
3. 实例
src/MyException.java
public class MyException extends Exception{
String msg;
public MyException(String message) {
// super(message);
msg = message;
}
@Override
public String getMessage() {
// return super.getMessage();
return msg;
}
}
src/Main.java
public class Main {
static int div(int a, int b) throws MyException {
if (b == 0)
throw new MyException("分母不能为0");
return a / b;
}
public static void main(String[] args) throws MyException {
try {
System.out.println(div(8,0));
return;
} catch (MyException e) {
System.out.println(e.getMessage());
throw new MyException("catch里面抛出异常");
} finally {
// 都会执行finally块,为了关闭某些打开状态(如数据库、文件)的操作
System.out.println("进入finally块");
}
}
}
二五、输入数据流与输出数据流
1. 什么是IO
-
Java 中 I/O 操作主要是指使用 Java 进行输入,输出操作。Java 所有的 I/O 机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java 的 I/O 流提供了读写数据的标准方法。任何 Java 中表示数据源的对象都会提供以数据流的方式读写它数据的方法
-
Java.io 是大多数面向数据流的输入/输出类的主要软件包。此外,Java 也对块传输提供支持,在核心库 java.nio 中采用的便是块IO
-
流 IO 的好处就是简单易用,缺点是效率较低。块 IO 效率很高,但编程比较复杂
2. 数据流的基本概念
-
数据流
一组有序,有起点和终点的字节的数据序列。包括输入流和输出流 -
输入流(Input Stream)
程序从输入流读取数据源。数据源包括外界(键盘、文件、网络...),即是将数据源读入到程序的通信通道 -
输出流
程序想输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络...)的通信通道 -
数据流分类
① 字节流:数据流中最小的数据单元是字节
② 字符流:数据流中最小的数据单元是字符,Java中的字符是 Unicode 编码,一个字符占用两个字节
3. 标准I/O
命令行参数
标准输入、输出数据流:
-
标准输入流
System.out
System.out向标准输出设备输出数据,其数据类型为PrintStream
方法:Void print(参数)Void println(参数) -
标准输入流
System.in
System.in读取标准输入设备数据(从标准输入获取数据,一般是键盘),其数据类型为InputStream
方法:int read()(返回 ASCII 码,若返回值=-1,说明没有读取到任何字节读取工作结束)。
int read(byte[] b)(读入多个字节到缓冲区 b 中,返回值是读入的字节数) -
标准错误流
System.err输出标准错误,其数据类型为PrintStream。可查阅 API 获得详细说明。可以参考代码
4. 案例
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
// 打印命令行参数
for (int i = 0; i < args.length; i++) {
System.out.println("参数" + i + ": " + args[i]);
}
// 标准io System.in System.out
int userInput;
System.out.println("请输入一个字符:");
try {
while ((userInput = System.in.read()) != 'q') {
System.out.println((char) userInput);
}
} catch (IOException e) {
System.out.println(e.toString());
}
// 按行输入
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = "";
try {
// 获取用户输入的一行字符串
while (true) {
s = br.readLine();
if (s.equals("exit"))
break;
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
二六、文件数据流
1. 文件数据流
-
流的原理:
输入流(input):将外部的文件通过流读取到内存中
输出流(output):将内存中的文件通过流输出到硬盘等存储设备中 -
分类:
按照操作单位分为:字节流,字符流
按照流向不同分为:输入流,输出流
按照角色不同分文:节点流,处理流
2. FileInputStream
- FileInputStream 的构造方法
①FileInputStream(File file):通过打开一个到实际文件的连接来创建一个FileInputStream对象,该文件通过文件系统中的 File 对象 file 指定
②FileInputStream(String name):通过打开一个到实际文件的连接来创建一个FileInputStream对象,该文件通过文件系统中的路径名 name 来指定
3. FileOutStream
- FileOutputStream 构造方法
①FileOutputStream(File file[, boolean addFlag]):创建一个向指定 File 对象表示的文件中写入数据的文件输出流
②FileOutputStream(FileDescriptor fdObj):创建一个向指定文件描述符处写入数据的输出文件流,该文件描述表示一个文件系统中的某个实际文件的现有连接
③FileOutputStream(String name[, boolean addFlag]):创建一个向具有指定 name 的文件中写入数据的输出文件流
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
// 字节流操作文件
try {
// 输入数据
byte buff[] = new byte[1024];
int cnt = System.in.read(buff);
// 将输入数据输出到read.txt文件
FileOutputStream fileOutputStream = new FileOutputStream("read.txt", true);
fileOutputStream.write(buff, 0, cnt);
fileOutputStream.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
// 读取文件
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("read.txt");
byte buffer[] = new byte[1024];
int readLen = -1;
while ((readLen = fileInputStream.read(buffer, 0, buffer.length)) != -1 && readLen > 0) {
byte tem[] = new byte[readLen];
System.arraycopy(buffer, 0, tem, 0, readLen);
System.out.print(new String(tem));
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
4. FileReader
-
IO 体系中的 FileReader
FileReader 是针对字符型文件(后缀.txt)的流,根据定义的不同也可称其为字符流、节点流、输入流 -
FileReader 的使用
① 实例化一个 File 类用于操作的对象
② 实例化一个 FileReader 类并且将 File 对象作为参数传入它的构造器中
③ 用 FileReader 的方法将数据读入
FileReader 读入数据的方法是read()方法
read()的空参构造器,返回读入的一个字符并继续往下读,如果读到最后一个元素返回 -1
read(char cbuf[])一次读入 cbuf 中字符个元素的个数,如果到达文件末尾,返回 -1
④ 关闭流close(),方法流不会自动关闭,必须手动关闭,不然会浪费资源
5. FileWriter
-
IO 体系中的 FileWriter
FileWriter 是针对字符类型文件(后缀.txt)的流,根据定义的不同也可以称其为字符流、节点流、输出流 -
FileWriter 的使用:(FileWriter其实使用步骤和FileReader是一样的)
① 实例化一个 File 类
② 实例化一个 FileWriter 类并且将 File 的实例作为参数传入它的构造器中
new FileWriter(file, false), 默认情况是 false,修改原有文件的内容改为添加的数据
new FileWriter(file, true), 在原有文件的基础上添加写入的数据
③ 使用 write 方法写入数据到指定文件夹中
如果实例化的 File 类的 file 文件不存在会自动添加
public void write(char cbuf[]),一次写入 cbuf 指定个数的数据
public void wirte(char cbuf[], int off, int len),在传入的 cbuf 数组中从 off 开始写入,一次写入 len 个数据
④ 关闭 FileWriter 流,close()方法
import java.io.*;
public class Main {
public static void main(String[] args) {
// 字符文件操作
// 写文件
try {
File file = new File("a.txt");
FileWriter fw = new FileWriter(file, true);
fw.write("这是文件内容");
fw.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
// 读文件
try {
File file = new File("a.txt");
FileReader fr = new FileReader(file);
int ct = -1;
while ((ct = fr.read()) != -1) {
System.out.print((char) ct);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
// 字符流字节流2种转换方式
try {
FileInputStream input = new FileInputStream("read.txt"); // 创建输入字节流
InputStreamReader isr = new InputStreamReader(input); // 转为输入字符流
FileOutputStream output = new FileOutputStream("b.txt"); // 创建输出字节流
OutputStreamWriter osw = new OutputStreamWriter(output); // 转为输出字符流
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
二七、字符流与字节流
1. 字节流 — 过滤流
- 缓冲区数据流
缓冲区数据流有BufferedInputStream和BufferedOutputStream
网上错误说法:在关闭一个缓冲区输出流之前,要使用flush()方法,强制输出剩余数据,确保缓冲区里的所有数据全部写入输出流,错误说法,其实在调用close()方法关闭的时候会自动执行flush()
import java.io.*;
public class Main {
public static void main(String[] args) {
// 缓冲区对象流
try {
FileInputStream in = new FileInputStream("input.bin");
FileOutputStream out = new FileOutputStream("output.bin");
BufferedInputStream bin = new BufferedInputStream(in);
BufferedOutputStream bout = new BufferedOutputStream(out);
int data;
while ((data = bin.read()) != -1) {
bout.write(data);
}
bin.close();
bout.close();
in.close();
out.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
- 数据数据流
之前说的数据流中,处理的数据不是字节就是字节数组,但是有很多时候,不止是只有两种数据,所以就要用专门的过滤流数据流来处理,这里给出DataInputStream、DataOutputStream,他们允许对 Java 基本类型进行处理
import java.io.*;
public class Main {
public static void main(String[] args) {
// 数据流对象
try {
FileOutputStream out = new FileOutputStream("data.bin");
DataOutputStream dout = new DataOutputStream(out);
// 写入
dout.writeUTF("Hello World");
dout.writeInt(122);
dout.writeChar('a');
dout.writeBoolean(true);
dout.close();
out.close();
// 读取
FileInputStream in = new FileInputStream("data.bin");
DataInputStream din = new DataInputStream(in);
String str = din.readUTF();
int num = din.readInt();
char ch = din.readChar();
boolean b = din.readBoolean();
System.out.println(str+num+ch+b);
din.close();
in.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
2. 字节流 — 可持久化
-
可持久化就是对象通过描述自己的状态的数值来记录自己的过程
-
当一个类实现
Serializable接口时,表明该类加入了对象串行化协议 -
需要注意的是,要使一个 Java 对象可序列化,必须实现
Serializable接口。这个接口没有任何方法,只是一个标记接口,用于告诉 Java 虚拟机这个类可以被序列化 -
transient关键字用于标记一个成员变量不需要被序列化
3. 字节流 — 管道数据流、对象流
-
管道数据流:
PipedOutputStream和PipedInputStream,管道的两端建立连接后就可以通信 -
对象流:
ObjectOutputStream和ObjectInputStream,将一个对象示例写入文件
import java.io.*;
// 定义Person类实现Serializable接口
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class Main {
public static void main(String[] args) {
// 序列化
Person person01 = new Person("John Smith", 30);
try {
// 将序列化对象示例保存至文件
FileOutputStream fos = new FileOutputStream("personOut.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(person01);
oos.close();
fos.close();
// 读取序列化文件
FileInputStream fis = new FileInputStream("personOut.bin");
ObjectInputStream ois = new ObjectInputStream(fis);
Person person02 = (Person)ois.readObject();
ois.close();
fis.close();
System.out.println(person02.getName());
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
// 管道通信流
try {
// 创建管道
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
// 线程1
Thread writer = new Thread(() -> {
try {
pos.write("hello world".getBytes());
pos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
});
// 线程2
Thread reader = new Thread(() -> {
try {
int data;
while ((data = pis.read()) != -1) {
System.out.println((char)data);
}
pis.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
});
writer.start();
reader.start();
writer.join();
reader.join();
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
4. 字符流 — 缓冲区读者、写者
-
缓冲区的出现,提高了对数据的读写效率,缓冲区要结合流才可以使用,缓冲区是在流的基础上对流的功能进行增强
-
BufferedReader
-
BufferedWriter
import java.io.*;
public class Main {
public static void main(String[] args) {
// 字符流 缓冲区
try {
FileWriter fw = new FileWriter("readme.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("This is a readme.txt");
bw.newLine();
bw.write("This is a readme.txt");
bw.close();
fw.close();
FileReader fr = new FileReader("readme.txt");
BufferedReader br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
fr.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
二八、文件的处理
1. File 类的概述
-
java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关 -
File 类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用 IO 流来完成
-
想要在 Java 程序中表示一个真实存在的文件或目录,那么必须有一个 File 对象,但是 Java 程序中的一个 File 对象,可能没有一个真实存在的文件或目录
-
File 对象可以作为参数传递给流的构造器,指明读取或写入的“终点”
2. File 类的使用
-
绝对路径 vs 相对路径
① 绝对路径:是一个固定的路径,从盘符开始
② 相对路径:是相对于某个位置开始
main()方法中的相对路径是相对于当前工程
单元测试方法中的相对路径是相对于当前模块 -
路径分隔符
① 路径中的每级目录之间用一个路径分隔符隔开
② 路径分隔符和系统有关:windows 和 DOS 系统默认使用“\”来表示,UNIX 和 URL 使用“/”来表示
③ Java 程序支持跨平台运行,因此路径分隔符要慎用。
为了解决这个隐患,File 类提供了一个常量:
public static final String separator,根据操作系统,动态的提供分隔符
| File 类构造方法 | 功能描述 |
|---|---|
public File(String filename) | 创建 File 对象,filename 表示文件或目录的路径 |
public File(String parent, String child) | 创建 File 对象,parent 表示上级目录,child 表示指定的子目录或文件名 |
public File(File obj, String child) | 设置 File 对象,obj 表示 File 对象, child 表示指定的子目录或文件名 |
3. 随机访问文件
-
使用随机访问文件,我们可以从文件读取以及写入文件。使用文件输入和输出流的读取和写入是顺序过程
-
使用随机访问文件,可以在文件中的任何位置读取或写入
-
RandomAccessFile 类的一个对象可以进行随机文件访问。可以读/写字节和所有原始类型的值到一个文件
-
RandomAccessFile 可以使用其
readUTF()和writeUTF()方法处理字符串 -
RandomAccessFile 类不在 InputStream 和 OutputStream 类的类层次结构中
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
public static void main(String[] args) throws IOException {
// File 类的使用
File file = new File("src/a.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.getParent());
System.out.println(file.getPath());
System.out.println(file.getCanonicalFile());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("最后修改时间:" + sdf.format(new Date(file.lastModified())));
System.out.println(File.separator); // 系统分隔符
// 随机读写文件
String fileName = "randomAccessFile.txt";
File fo = new File(fileName);
if (!fo.exists()) {
// fo.createNewFile(); //常规创建
RandomAccessFile raf = new RandomAccessFile(fo.getPath(), "rw");
raf.writeInt(1234);
raf.writeDouble(2.35);
raf.writeUTF("Hello World");
raf.close();
}
RandomAccessFile raf = new RandomAccessFile(fo.getPath(), "rw");
int intVal = raf.readInt();
double doubleVal = raf.readDouble();
String stringVal = raf.readUTF();
System.out.println("int:" + intVal + " double:" + doubleVal + " string:" + stringVal);
long cur = raf.getFilePointer(); // 文件的指针
raf.seek(4); // 将文件的指针设置为4,4之后是double开始
raf.writeDouble(8.99);
raf.seek(cur); // 恢复指针
raf.close();
}
}
二九、线程的概念与结构
1. 线程的概念
线程是指在一个进程中执行的独立的、可调度的执行单元,在 Java 中,线程是 Thread 类的实例, 可以通过继承 Thread 类或实现 Runnable 接口来创建线程。每个线程都有自己的执行路径和执行状态,可以共享进程的资源,例如内存、文件句柄等,因此线程之间的通信和协作比进程之间更加高效
2. 线程的结构
在 Java 中,线程的结构通常包括线程ID、程序计数器、寄存器集合、堆栈和状态等。线程ID是一个唯一标识符,用于区分不同的线程。程序计数器用于当前线程执行的位置,寄存器集合用于保存线程的上下文信息,堆栈用于保存线程的局部变量和方法用栈,状态用于记录线程的执行状态,例如就绪、运行、阻塞等。
3. 线程的创建和启动
在 Java 中,线程的创建和启动可以通过继承 Thread 类或实现 Runnable 接口来实现。例如,可以创建一个继承 Thread 类的子类,并重写 run() 方法来定义线程的执行逻辑,然后通过调用 start() 方法来启动线程。也可以创建一个实现 Runnable 接口的类,并实现 run() 方法来定义线程的执行逻辑,然后通过创建 Thread 对象并传入 Runnable 对象来启动线程
4. 线程的同步和协作
在 Java 中,线程的并发执行可能会导致一些问题,例如竞态条件、死锁、饥饿等。为了避免这些问题,需要使用同步机制,例如锁、信号量、条件变量等,来保证线程之间的互斥和协作
5. 结论
-
线程的优点
提高了系统吞吐率
提高 IO 效率
充分利用多核资源 -
线程的缺点
安全问题
资源竞争
上下文切换
可靠性
三十、继承Thread类
1. 继承 Thread 类创建线程
在 Java 中,可以通过继承 Thread 类来创建线程。例如,可以创建一个继承 Thread 类的子类,并重写 run() 方法来定义线程的执行逻辑,然后通过调用 start() 方法来启动线程
src/TestThread01.java
public class TestThread01 extends Thread{
private int id;
private int loopCount;
public TestThread01(int pId, int pIoopCout, String pName) {
this.id = pId;
this.loopCount = pIoopCout;
this.setName(pName);
}
@Override
public void run() {
for (int i = 0; i < loopCount; i++) {
System.out.println("Thread name: " + Thread.currentThread().getName() + ", loop count: " + (i + 1));
}
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
TestThread01 testThread01_1 = new TestThread01(1, 4, "张三");
TestThread01 testThread01_2 = new TestThread01(1, 2, "李四");
testThread01_1.start();
testThread01_2.start();
System.out.println("main Thread finished");
}
}
2. 线程的生命周期
在 Java 中,线程的生命周期通常包括新建、就绪、运行、阻塞和终止等状态。新建状态是指线程对象被创建但还没有启动的状态;就绪状态是指线程已经准备好执行但还没获得 CPU 资源的状态;运行是指线程正在执行的状态;阻塞状态是指线程因为某些原因暂时停止执行的状态;终止状态是指线程完毕或者因为异常等原因被终止的状态
src/TestThread02.java
public class TestThread02 extends Thread {
private int id;
private int loopCount;
public TestThread02(int pId, int pIoopCout, String pName) {
this.id = pId;
this.loopCount = pIoopCout;
this.setName(pName);
}
@Override
public void run() {
for (int i = 0; i < loopCount; i++) {
System.out.println("Thread name: " + Thread.currentThread().getName() + ", loop count: " + (i + 1));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
TestThread02 testThread02_1 = new TestThread02(1, 4, "张三");
System.out.println("线程状态:" + testThread02_1.getState());
testThread02_1.start();
System.out.println("线程状态:" + testThread02_1.getState());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程状态:" + testThread02_1.getState());
try {
testThread02_1.join(); // 等待线程结束-阻塞状态
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程状态:" + testThread02_1.getState());
System.out.println("main Thread finished");
}
}
3. 线程的优先级
在 Java 中,每个线程都有一个优先级,用于指定线程在竞争 CPU 资源时的优先级。线程的优先级可以通过 setPriority() 方法来设置,取值范围为 1~10,其中1为最低优先级,10为最高优先级, 默认值为5。线程的优先级并不是绝对的,只是一个先对的概念,具体的调度顺序还受到操作系统和 CPU 的影响
4. 守护线程
src/TestThread03.java
public class TestThread03 extends Thread{
@Override
public void run() {
System.out.println("我是守护进程吗? " + this.isDaemon());
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
TestThread03 testThread03 = new TestThread03();
// 设置testThread03 为守护进程,如果有用户线程(默认是用户线程)则程序会一直运行,如果只有守护进程,主程序运行完,程序结束
testThread03.setDaemon(true); // 不加这个为用户进程,会一直执行
testThread03.start();
System.out.println("main Thread finished");
}
}
三一、实现 Runnable 接口
1. Runnable 接口简介
- Runnable 接口位于
java.lang包中 - Runnable 接口描述了一个可以作为线程运行的类
- 只包含一个方法:
void run()
2. 为什么使用 Runnable 接口
- 适用于多线程编程
- 实现了 Runnable 接口的类可以被多个线程执行
- 避免 Java 单继承限制
3. 如何实现 Runnable 接口
- 创建一个类,实现 Runnable 接口
- 重写
run()方法,定义线程要执行的任务 - 创建 Thread 对象,将 Runnable 实现类的实例作为参数传递
- 调用 Thread 对象的
start()方法启动线程
4. Runnable 接口与 Thread 类的关系
- Runnable 接口通常与 Thread 类一起使用
- Thread 类实现了 Runnable 接口
- Thread 类提供了更多线程管理功能,如设置优先级、线程状态查询等
src/MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Hello i: " + i);
}
}
}
src/Main.java
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread01 = new Thread(myRunnable, "thread01");
thread01.start();
Thread thread02 = new Thread(myRunnable, "thread02");
thread02.start();
}
}
三二、线程的启动、调度与挂起
1. 线程的启动
2. 线程的调度
-
线程调度由操作系统管理
-
Java 提供了线程优先级设置
-
优先级范围:1(最低)到10(最高)
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("a");
t2.setName("b");
t3.setName("c");
t1.setPriority(1);
t2.setPriority(5);
t3.setPriority(10);
t1.start();
t2.start();
t3.start();
}
}
3. 线程的挂起(5种其中的3种)
-
使用
Thread.sleep方法让线程暂停 -
使用
TimeUnit类让线程暂停 -
使用
LockSupport让线程变相暂停
src/MyThreadSleep.java
public class MyThreadSleep extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + ":" + i);
}
}
}
src/MyThreadTimeUnit.java
import java.util.concurrent.TimeUnit;
public class MyThreadTimeUnit extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1); // TimeUnit是封装的Sleep
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + ":" + i);
}
}
}
src/MyThreadLockSupport.java
import java.util.concurrent.locks.LockSupport;
public class MyThreadLockSupport extends Thread{
@Override
public void run() {
System.out.println("in run");
LockSupport.park(); // 休眠
for(int i=0; i<10; i++){
System.out.println(getName() + ":" + i);
}
}
}
src/Main.java
import java.util.concurrent.locks.LockSupport;
public class Main {
public static void main(String[] args) {
MyThreadSleep myThreadSleep = new MyThreadSleep();
myThreadSleep.setName("sleep");
myThreadSleep.start();
MyThreadTimeUnit myThreadTimeUnit = new MyThreadTimeUnit();
myThreadTimeUnit.setName("unit");
myThreadTimeUnit.start();
MyThreadLockSupport myThreadLockSupport = new MyThreadLockSupport();
myThreadLockSupport.setName("lockSupport");
myThreadLockSupport.start();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(myThreadLockSupport); // 停止休眠,继续执行线程
}
}
三三、线程间的通信
1. Object wait()、notify()、notifyAll()方法
-
wait()来自 Object -
sleep不会释放锁,需要捕捉异常;wait会释放锁 -
只能配合
synchronized使用,不需要捕捉异常
2. 使用synchronized和volatile关键字
volatile修饰变量,多个角色操作同一个变量
多个线程同时操作一个变量的时候,防止数据不同步出现的错误sychronized修饰代码块,可以是块,也可以是方法
具有原子性,必须将代码块执行完
3. Condition await()、signal()和signalAll()方法
-
使用方法跟
wait()雷同,升级版 -
优势1:可多个condition条件
-
优势2:不担心唤醒和等待的顺序问题
4. 示例
src/Supermarket.java
public class Supermarket {
private volatile int curGoodCount = 0;
private int maxGoodCount = 10;
// 入库
public synchronized void add() {
if (curGoodCount < maxGoodCount) {
curGoodCount ++;
System.out.println(Thread.currentThread().getName() + " 在加紧生产,货架现有" + curGoodCount + "个商品");
notify(); // 唤醒
} else {
// 如果货架货物满了就等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 出库
public synchronized void sub() {
if (curGoodCount > 0) {
curGoodCount --;
System.out.println(Thread.currentThread().getName() + "又来购物了,还剩" + curGoodCount + "个商品");
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
src/Producter.java
public class Producter implements Runnable{
Supermarket supermarket;
public Producter(Supermarket su) {
supermarket = su;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
supermarket.add();
}
}
}
src/Consumer.java
public class Consumer implements Runnable{
Supermarket supermarket;
public Consumer(Supermarket su) {
supermarket = su;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
supermarket.sub();
}
}
}
src/Main.java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
// wait示例
public static void waitDemo() {
// 创建超市
Supermarket sm = new Supermarket();
// 创建生产者
Thread[] producters = new Thread[3];
for (int i = 0; i < producters.length; i++) {
producters[i] = new Thread(new Producter(sm), new String(i + "号生产者"));
producters[i].start();
}
// 创建消费者
Thread[] consumers = new Thread[6];
for (int i = 0; i < consumers.length; i++) {
consumers[i] = new Thread(new Consumer(sm), new String(i + "号消费者"));
consumers[i].start();
}
}
// condition示例
public static void conditionDemo() throws InterruptedException {
final Lock lock = new ReentrantLock();
final Condition condition01 = lock.newCondition();
final Condition condition02 = lock.newCondition();
final Condition condition03 = lock.newCondition();
// 子线程
new Thread(()->{
System.out.println("线程执行第一步");
lock.lock();
try {
condition01.await();
System.out.println("condition01 wake"); // 提示子线程已唤醒
Thread.sleep(3000);
condition02.signal();
condition03.await();
System.out.println("condition03 wake");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
Thread.sleep(1000); // 主线程等待1秒
lock.lock();
try {
condition01.signal(); // 发信号唤醒子线程
condition02.await(); // 02 休眠
System.out.println("condition02 wake");
Thread.sleep(5000);
condition03.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// waitDemo();
conditionDemo();
}
}
三四、网络相关概念
1. 网络编程基础
-
定义网络编程
-
网络编程的重要性
-
网络编程的基本元素(如 IP 地址,端口号,协议等)
2. Java 中的网络编程
-
Java 对网络编程的支持
-
Java 网络编程的优势
3. Java 网络编程的关键概念
- 套接字编程
- 定义套接字和套接字编程
- Java 中的 Socket 和 ServerSocket 类
- URL 处理
- 定义 URL 和 Java 中的 URL 类
- 如何在 Java 中使用 URL 类读取网页内容的示例
- 网络接口
- 定义网络接口和 Java 中的 NetworkInterface 类
- 如何在 Java 中使用 NetworkInterface 类获取网络接口信息的示例
4. 应用举例
- Java 网络编程在实际应用中的例子
Web 服务器,聊天应用,手机 app 等
三五、使用 InetAddress
1. InetAddress 类概述
-
用途
InetAddress 类用于网络编程,它提供了 IP 地址和主机名(域名)之间的映射 -
特点
InetAddress 类没有公共构造函数,我们只能通过类方法来创建其对象
2. InetAddress 类的使用
- 构造
getByname(String host),此方法接受一个主机名作为参数,返回该主机名的 InetAddress 对象 - 使用
getHostAddress(),此方法返回 InetAddress 对象的 IP 地址字符串getHostName(),此方法返回 InetAddress 对象的主机名。如果对象是通过 IP 地址创建的,那么它将返回 IP 地址作为主机名getLocalHost(),此方法返回一个 InetAddress 对象,该对象表示本地主机。它是获取本地主机名和 IP 地址的一种快捷方式
- 其他
配合 NetworkInterface 使用
3. 示例
import java.net.*;
import java.util.Enumeration;
import java.util.List;
public class Main {
// 将二进制网卡转为十六进制的字符串
public static String macBytes2String(byte[] buf) {
if (buf == null) return null;
StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
if (i != 0) sb.append("-");
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) hex = "0" + hex;
sb.append(hex.toUpperCase());
}
return sb.toString().toUpperCase();
}
public static void main(String[] args) throws UnknownHostException, SocketException {
// InetAddress
InetAddress inet01 = InetAddress.getByName("www.baidu.com");
System.out.println("hostname: " + inet01.getHostName());
System.out.println("IP Address: " + inet01.getHostAddress());
InetAddress inet02 = InetAddress.getLocalHost();
System.out.println("hostname: " + inet02.getHostName());
System.out.println("IP Address: " + inet02.getHostAddress());
System.out.println("-----------");
// 获取本机网卡信息
Enumeration<NetworkInterface> netWorkInterface = NetworkInterface.getNetworkInterfaces();
while (netWorkInterface.hasMoreElements()) {
NetworkInterface nif = netWorkInterface.nextElement();
Enumeration<InetAddress> addresses = nif.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
List<InterfaceAddress> interfaceAddresses = nif.getInterfaceAddresses();
System.out.println(interfaceAddresses);
System.out.println(nif.getDisplayName()); // 网络接口名字
System.out.println(nif.getName());
System.out.println(address.getHostName());
System.out.println(address.getHostAddress());
byte[] mac = nif.getHardwareAddress(); // mac地址
System.out.println(macBytes2String(mac));
System.out.println("++++++++++++++++++");
}
}
}
}
三六、统一资源定位器概念与 URL 创建
1. URL 的概念
URL (统一资源定位器)是互联网上用于描述信息位置的一种方式
它是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它
结构:
blog.csdn.net/xinzi123/ar…
协议://域名:端口/目录/目录?参数1=参数1值&参数2=参数2值
2. URL 的创建
在 Java 中,我们可以使用java.net.URL类来创建 URL 对象
3. 同步请求
同步请求是指客户端发出请求后,必须等待服务器回应才能进行下一步操作
在此期间,客户端不能做其他操作,这种方式的好处就是变成模型简单,缺点是等待服务器响应的时间可能会很长
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
public static void get() {
try {
URL url = new URL("https://www.baidu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept-Charset", "UTF-8");
conn.setRequestProperty("Accept", "text/html");
conn.connect(); // 发送请求数据
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 初始化输入流
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
Stream<String> stringStream = new BufferedReader(inputStreamReader).lines();
String streamToString = stringStream.collect(Collectors.joining());
System.out.println(streamToString);
} else {
System.out.println("Error Code: " + responseCode);
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void post() {
try {
URL url = new URL("https://www.baidu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept-Charset", "UTF-8");
conn.setRequestProperty("Accept", "text/html");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.connect(); // 发送请求数据
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), "UTF-8"));
writer.write("aaadsfds=dfs");
writer.close();
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 初始化输入流
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
Stream<String> stringStream = new BufferedReader(inputStreamReader).lines();
String streamToString = stringStream.collect(Collectors.joining());
System.out.println(streamToString);
} else {
System.out.println("Error Code: " + responseCode);
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void downloadFile() {
try {
URL url = new URL("https://www.baidu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept-Charset", "UTF-8");
conn.setRequestProperty("Accept", "text/html");
conn.connect(); // 发送请求数据
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 初始化输入流
InputStream inputStream = conn.getInputStream();
File dir = new File("downloadFileDir");
// 创建目录
if (!dir.exists()) {
dir.mkdirs();
}
// 创建文件
File file = new File(dir, "fname.html");
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int length = -1;
while ((length = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
fos.close();
inputStream.close();
} else {
System.out.println("Error Code: " + responseCode);
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// 同步get请求
get();
// 同步post请求
post();
// 下载文件
downloadFile();
}
}
三七、异步请求
1. Okhttp3 出现的背景
网络访问的高效性要求,可以说是为高效而生
2. OKhttp3 优点
提供了对 HTTP/2 和 SPDY 的支持,这使得对同一主机发出的所有请求都可以共享相同的套接字连接
如果 HTTP/2 和 SPDY 不可用,OkHttp 会使用连接池来复用连接以提高效率
提供了对 GZIP 的默认支持来降低传输内容的大小
提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求
当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址
3. Okhttp3 使用教程
-
GRADLE 引入包
-
创建 OkHttpClient 实例
简单来说,通过 OkHttpClient 可以发送一个 Http 请求,并读取该 Http 请求的响应,它是一个生产 Call 的工厂。此外,受益于一个共享的相应缓存/线程池/复用的连接等因素,绝大多数应用使用一个 OkHttpClient 实例,便可以满足整个应用的 Http 请求 -
GET 和 POST
-
HTTP 头部的设置和读取
HTTP 头的数据结构是Map<String, List<String>>类型。也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。 -
安装依赖
File→Project Structure...→Modules→选择项目→Dependencies→+→Library...→From Maven...
搜索框输入com.squareup.okhttp3->点击搜索→选择低版本的com.squareup.okhttp3:okhttp:4.4.1→选中Download to和Transitive dependencies→OK→OK→Apply→OK
src/MyNet.java
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import javax.net.ssl.*;
import java.io.IOException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class MyNet {
private OkHttpClient okHttpClient;
public MyNet() {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.readTimeout(20, TimeUnit.SECONDS); // 读取超时
clientBuilder.connectTimeout(5, TimeUnit.SECONDS); // 链接超时
clientBuilder.writeTimeout(60, TimeUnit.SECONDS); // 写入超时
// ssl 支持设置
clientBuilder.sslSocketFactory(mySSLSocketFactory(), new TrustAllCerts());
// 域名 hostName 校验
clientBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
// 生成client
okHttpClient = clientBuilder.build();
}
class TrustAllCerts implements X509TrustManager {
// 客户端证书校验
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
// 服务端证书校验
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
private SSLSocketFactory mySSLSocketFactory() {
SSLSocketFactory sslSocketFactory = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
// sslContext.init(null, new TrustManager[]{new TrustAllCerts()}, new SecureRandom());
sslContext.init(null, null, new SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return sslSocketFactory;
}
public interface MyCallBack {
void success(Call call, Response response) throws IOException;
void failure(Call call, IOException e);
}
// get 请求
public void get(String url, MyCallBack myCallBack) {
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
myCallBack.failure(call, e);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
myCallBack.success(call, response);
}
});
}
// post 请求
public void post(String url, Map<String, String> bodyMap, MyCallBack myCallBack) {
FormBody.Builder builder = new FormBody.Builder();
Iterator<String> iterator = bodyMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
builder.add(key, bodyMap.get(key));
}
RequestBody requestBody = builder.build();
Request request = new Request.Builder().url(url).post(requestBody).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
myCallBack.failure(call, e);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
myCallBack.success(call, response);
}
});
}
}
src/Main.java
import okhttp3.Call;
import okhttp3.Response;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
MyNet myNet = new MyNet();
myNet.get("https://www.baidu.com", new MyNet.MyCallBack() {
@Override
public void success(Call call, Response response) throws IOException {
System.out.println(response.body().string());
System.out.println("====================================");
}
@Override
public void failure(Call call, IOException e) {
e.printStackTrace();
}
});
Map<String, String> reqBody = new HashMap<>();
reqBody.put("num", "123456");
reqBody.put("key", "fasdfsa");
myNet.post("https://www.baidu.com", reqBody, new MyNet.MyCallBack() {
@Override
public void success(Call call, Response response) throws IOException {
System.out.println(response.body().string());
System.out.println("====================================");
}
@Override
public void failure(Call call, IOException e) {
e.printStackTrace();
}
});
}
}
三八、socket 通信基本步骤
1. Socket 通信简介
-
定义 Socket 通信
-
描述 Socket 通信的重要性和应用
2. Socket 通信基本步骤
-
创建 Socket
-
绑定 Socket 到地址和端口
-
监听连接
-
接受连接
-
发送和接收数据
-
关闭 Socket
3. 问题和解决方案
4. 实例
src/Student.java
public class Student {
private int sId;
private String sName;
public Student(String sName, int sId) {
this.sId = sId;
this.sName = sName;
}
@Override
public String toString() {
return "Student{" +
"sId=" + sId +
", sName='" + sName + ''' +
'}';
}
}
src/Service.java
import java.io.*;
import java.net.Socket;
public class Service implements Runnable{
private InputStream input = null;
private OutputStream output = null;
private Socket s = null;
private ObjectOutputStream oos = null;
private PrintStream pw = null;
public Service(Socket s) {
this.s = s;
}
@Override
public void run() {
// 实现输入输出方法
try {
input = s.getInputStream();
output = s.getOutputStream();
oos = new ObjectOutputStream(output);
// 发送
oos.writeInt(1314);
oos.writeDouble(1.23);
Student st = new Student("张三", 165);
oos.writeObject(st);
oos.flush(); // 刷新
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
src/Clientv1.java
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Clientv1 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 7890);
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 数据源可以是命令行窗口的用户输入,也可以是一个文件里面的内容,也可以是其他数据
String send1 = "hello server";
outputStream.write(send1.getBytes());
System.out.println("向服务器发送数据:" + send1);
String send2 = "exit";
outputStream.write(send2.getBytes());
System.out.println("向服务器发送数据:" + send2);
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
String recv = new String(buffer, 0, bytesRead);
System.out.println("接收到服务器返回数据:" + recv);
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
src/Main.java
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Main {
public static void main(String[] args) {
if (false) {
try {
ServerSocket serverSocket = new ServerSocket(7890);
System.out.println("Server started");
Socket socket = serverSocket.accept(); // 阻塞
System.out.println("客户端已连接:" + socket.getInetAddress().getHostAddress());
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 接收一条数据
byte[] buffer = new byte[1024];
while (true) {
int bytesRead = inputStream.read(buffer);
String requestData = new String(buffer, 0, bytesRead);
System.out.println("接收到客户端发送的数据:" + requestData);
// 根据不同数据,不同格式,走不同的数据处理分支
if (requestData.equals("exit")) {
System.out.println("break");
break;
}
}
// 发送一条数据
String responseData = "hello client";
outputStream.write(responseData.getBytes());
System.out.println("向客户端发送相应数据:" + responseData);
inputStream.close();
outputStream.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(7890);
System.out.println("Server started");
while (true) {
// 有几个 client 就会有几个 socket
socket = serverSocket.accept(); // 阻塞
System.out.println("客户端已连接:" + socket.getRemoteSocketAddress() + ", 端口" + socket.getPort());
// 多线程处理数据流
new Thread(new Service(socket)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}