java
扫描器(键入)
public class input {
//1.引入Scanner类
//2.创建scanner对象
public static void main(String[] args) {
// scanner 就是Scanner类的对象
Scanner scanner = new Scanner(System.in);
System.out.println("请输入名字");
// 接受用户的输入
String name = scanner.next(); // 接收字符串
System.out.println("请输入年龄");
int age = scanner.nextInt(); // 接受年龄
System.out.println("请输入薪水");
double sal = scanner.nextDouble(); // 接受薪水
System.out.println(name + age + sal);
}
}进制
介绍
1、二进制 0,1 满2进1 以0b或者0B 开头
2、十进制0-9 满10进1
3、八进制 0-7 满8进1 以0开头
4、十六进制 0-9 A(10)-F(15)满16进1 以0x或者0X开头 a-f不区分大小写
进制转换
2 8 16 转 10进制
2=====》10
规则: 从最低位(右边)开始,将每个位上的数提取出来乘2的位数-1次方,然后求和
0b1011=12的(1-1)次方 + 1 2的(2-1)次方+02的(3-1)次方+12的(4-1)次方 = 1+2+0+8 =11
8======》10
规则:从最低位(右边)开始,将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和
0234 = 48^0 + 38^1 +2*8^2 = 4+24+128 = 156
16======》10
规则:从最低位(右边)开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和
0x23A=1016^0 + 316^1 + 2*16^2 = 10+48 +512 = 570
练习题:
0b110001100 转10进制
02456转10进制
0xA45转10进制
10转2 8 16
10=====》2
规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
34=0b100010(0b00100010)

10=====》8
规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的8进制
131=0203

10=====》16
规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的16进制
237=0xED

练习:
123转成2进制
678转成八进制
8912转成16进制
2转8
规则:从低位开始,将二进制数每三位一组,转成八进制数
0b11(3)010(2)101(5)=0325
2转16
规则:从低位开始,将二进制数每4位一组,转成对应的16进制数
0b1101(D)0101(5) = 0xD5
练习:
0b11100101转成8进制
0b1110010110转成16进制
8转成2
规则:将八进制每一位转成对应的一个3位的二进制的数
02(010)3(011)7(111) = 0b010011111(0b10011111)
16进制转2
规则:将16进制每位转成对应的4位二进制数
0x2(0010)3(0011)B(1011) =0b001000111011
练习:
01230转成二
0xAB29转成2
数组
介绍:
数组可以存放多个同一类型的数据,数组也是一种数据类型,是引用类型
数组的三种使用方式
方式1-动态初始化
数据类型 数组名[] = new 数据类型[大小]
数据类型[] 数组名 = new 数据类型[大小]
public static void main(String[] args) {
double[] hens = new double[5];
Scanner scanner = new Scanner(System.in);
for (int i=0;i< hens.length;i++){
System.out.println("请输入第"+(i+1)+"个元素的值");
hens[i] = scanner.nextDouble();
}
for (int j=0; j <hens.length; j++){
System.out.println("第"+(j+1)+"个元素的值为"+hens[j]);
}
}方式2-动态初始化
先声明
数据类型 数组名[]
数据类型[] 数组名
再创建数组
数组名 = new 数据类型[大小]
方式3-静态初始化
数据类型 数组名[] = {元素值,元素值...}
数组的注意事项
1、数组是多个相同类型数据的组合,实现对这些数据的统一管理(声明int不能存放double,声明double可以存放int)
2、数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用
3、数据创建后,如果没有赋值,则有初始值
| 数据类型 | 初始值 |
|---|---|
| byte,short,int,long | 0 |
| float,double | 0.0 |
| char | \u0000 |
| boolean | false |
| String | null |
4、定义long时,数字后面需要加L,float ,数字后面需要加F
5、数组属于引用类型,数组型数据是对象
6、使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值 3 使用数组
二维数组
快速入门
/*
1 2 3 4 5 6
8 7 5 4 3 3
0 1 4 6 3 2
*/
int[][] arr = {{1, 2, 3, 4, 5, 6},
{ 8, 7, 5, 4, 3, 3},
{0, 1, 4, 6, 3, 2}};
for( int i=0;i<arr.length;i++){
for(int j=0;j<arr[i].length;j++){
System.out.print(arr[i][j]+" ");
}
System.out.println();
}使用
使用方式1:动态初始化
类型 数组名 = new 类型大小
易错
String strs[] = new String[]{"a","b","c"}; 正确
面向对象
从类到对象有几种说法:
1、创建一个对象
2、实例化一个对象
3、把类实例化
public class Array {
//编写一个 main 方法
public static void main(String[] args) {
/*
张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。
还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户输入小猫的名字时,
就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,
则显示 张老太没有这只猫猫。
第 183页
韩顺平循序渐进学 Java 零基础
第 184页
*/
//单独变量来解决 => 不利于数据的管理(你把一只猫的信息拆解)
//第 1 只猫信息
// String cat1Name = "小白";
// int cat1Age = 3;
// String cat1Color = "白色";
// //第 2 只猫信息
// String cat2Name = "小花";
// int cat2Age = 100;
// String cat2Color = "花色";
//数组 ===>(1)数据类型体现不出来(2) 只能通过[下标]获取信息,造成变量名字和内容
// 的对应关系不明确(3) 不能体现猫的行为
//第 1 只猫信息
// String[] cat1 = {"小白", "3", "白色"};
// String[] cat2 = {"小花", "100", "花色"};
//使用 OOP 面向对象解决
//实例化一只猫[创建一只猫对象]
//老韩解读
//1. new Cat() 创建一只猫(猫对象)
//2. Cat cat1 = new Cat(); 把创建的猫赋给 cat1
//3. cat1 就是一个对象
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
cat1.weight = 10;
//创建了第二只猫,并赋给 cat2
//cat2 也是一个对象(猫对象)
Cat cat2 = new Cat();
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
cat2.weight = 20;
//怎么访问对象的属性呢
System.out.println("第 1 只猫信息" + cat1.name
+ " " + cat1.age + " " + cat1.color + " " + cat1.weight);
System.out.println("第 2 只猫信息" + cat2.name
+ " " + cat2.age + " " + cat2.color + " " + cat2.weight);
}
}
//使用面向对象的方式来解决养猫问题
//
//定义一个猫类 Cat -> 自定义的数据类型
class Cat {
//属性/成员变量
String name; //名字
int age; //年龄
String color; //颜色
double weight; //体重
//行为
}属性/成员变量
基本介绍:
成员变量=属性=field(字段)
属性的定义:
访问修饰符 属性类型 属性名;
4种访问修饰符 public,proctected,private,默认
属性的定义类型可以是任何类型
属性如果不赋值,则会有默认值
类和对象的内存分配机制
1、栈:一般存放基本数据类型(局部变量)
2、堆:存放对象(Cat cat,数组等)
3、方法区:常量池(常量,比如字符串),类加载信息(只会加载一次)
Person p = new Person();
p.name = "jack";
p.age 10步骤:
1、先加载类信息(属性和方法(只会加载一次))
2、在堆中分配空间,进行默认初始化,
3、然后把地址赋给p,p就指向对象
4、进行指定初始化

成员方法
快速入门
public class Method01 {
//编写一个 main 方法
public static void main(String[] args) {
//方法使用
//1. 方法写好后,如果不去调用(使用),不会输出
//2. 先创建对象 ,然后调用方法即可
Person p1 = new Person();
p1.speak(); //调用方法
}
}
class Person {
String name;
int age;
//方法(成员方法)
//添加 speak 成员方法,输出 “我是一个好人”
//老韩解读
//1. public 表示方法是公开
//2. void : 表示方法没有返回值
//3. speak() : speak 是方法名, () 形参列表
//4. {} 方法体,可以写我们要执行的代码
//5. System.out.println("我是一个好人"); 表示我们的方法就是输出一句话
public void speak() {
System.out.println("我是一个好人");
}
}快速入门2
public class Method01 {
//编写一个 main 方法
public static void main(String[] args) {
//方法使用
//1. 方法写好后,如果不去调用(使用),不会输出
//2. 先创建对象 ,然后调用方法即可
Person p1 = new Person();
p1.speak(); //调用方法
p1.cal01(); //调用 cal01 方法
p1.cal02(5); //调用 cal02 方法,同时给 n = 5
p1.cal02(10); //调用 cal02 方法,同时给 n = 10
//调用 getSum 方法,同时 num1=10, num2=20
//把 方法 getSum 返回的值,赋给 变量 returnRes
int returnRes = p1.getSum(10, 20);
System.out.println("getSum 方法返回的值=" + returnRes);
}
}
class Person {
String name;
int age;
//方法(成员方法)
//添加 speak 成员方法,输出 “我是一个好人”
//老韩解读
//1. public 表示方法是公开
//2. void : 表示方法没有返回值
//3. speak() : speak 是方法名, () 形参列表
//4. {} 方法体,可以写我们要执行的代码
//5. System.out.println("我是一个好人"); 表示我们的方法就是输出一句话
public void speak() {
System.out.println("我是一个好人");
}
//添加 cal01 成员方法,可以计算从 1+..+1000 的结果
public void cal01() {
//循环完成
int res = 0;
for(int i = 1; i <= 1000; i++) {
res += i;
}
System.out.println("cal01 方法 计算结果=" + res);
}
//添加 cal02 成员方法,该方法可以接收一个数 n,计算从 1+..+n 的结果
//老韩解读
//1. (int n) 形参列表, 表示当前有一个形参 n, 可以接收用户输入
public void cal02(int n) {
//循环完成
int res = 0;
for(int i = 1; i <= n; i++) {
res += i;
}
System.out.println("cal02 方法 计算结果=" + res);
}
//添加 getSum 成员方法,可以计算两个数的和
//老韩解读
//1. public 表示方法是公开的
//2. int :表示方法执行后,返回一个 int 值
//3. getSum 方法名
//4. (int num1, int num2) 形参列表,2 个形参,可以接收用户传入的两个数
//5. return res; 表示把 res 的值, 返回
public int getSum(int num1, int num2) {
int res = num1 + num2;
return res;
}
}内存机制
成员方法的定义
public 返回的数据类型 方法名 (形参列表...){
// 方法体;
return 返回值;
}细节详解
1、访问修饰符
作用:控制方法的使用范围
可选,不选为默认,
四种:public、protected 、private、 默认
2、返回数据类型
- 一个方法最多有一个返回值,返回多个值可以使用数组
public int[] getSumAndSub(int n1,int n2){
int[] resArr = new int[2];
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}- 返回类型可以为任意类型,包含基本类型或者引用类型(数组,对象)
- 如果方法要求有返回类型,则方法体中最后的执行语句必须为return值,而且要求返回值类型必须和return的值类型一致或者兼容
- 如果方法是void,则方法体中可以没有return语句,或者只写return。
- 方法名:建议使用驼峰命名 例如:
getSum
3、形参列表
- 个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如
getSum(int n1,int n2) - 参数类型可以为任意类型,包含基本类型或引用类型,比如
printArr(int map) - 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型 的参数
![getSum]
4、方法体
方法不能嵌套定义
5、方法调用
- 同一个类中的方法可以直接调用,否则需要创建对象
- 和访问修饰符有关
方法的重载
介绍
java同一个类中,多个同名方法的存在,但要求形参列表不同
规则
方法名必须相同
形参列表必须不同(形参类型,个数,顺序,至少有一个不一样的,参数名不要求)
返回类型不要求
可变参数
public int sum(int... nums) {
//System.out.println("接收的参数个数=" + nums.length);
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}作用域
全局变量有默认值可以不赋值 局部变量没有默认值,不赋值不能用
注意:
1、属性和局部变量可以重名,访问时遵循就近原则
2、属性生命周期长,伴随着对象的创建而创建,伴随着对象的销毁而销毁,局部变量生命周期短,伴随着代码块的执行er创建,伴随着代码块的ji结束而销毁
3、修饰不同 全局变量可以加修饰符,局部变量不能加
构造方法/构造器
作用:
完成对新对象的初始化
(创建对象时系统自动调用该类的构造器对对象的初始化)
语法
[修饰符] 方法名(形参列表){
方法体;
}
说明:
1、修饰符可以是默认的,也可以是public、private、protected
2、构造器没有返回值
3、方法名必须和类名相同
4、参数列表和成员方法一样
5、构造器的调用是由系统完成的
入门案例
public class Constructor01 {
//编写一个 main 方法
public static void main(String[] args) {
//当我们 new 一个对象时,直接通过构造器指定名字和年龄
Person p1 = new Person("smith", 80);
System.out.println("p1 的信息如下");
System.out.println("p1 对象 name=" + p1.name);//smith
System.out.println("p1 对象 age=" + p1.age);//80
}
}
//在创建人类的对象时,就直接指定这个对象的年龄和姓名
//
class Person {
String name;
int age;
//构造器
//老韩解读
//1. 构造器没有返回值, 也不能写 void
//2. 构造器的名称和类 Person 一样
//3. (String pName, int pAge) 是构造器形参列表,规则和成员方法一样
public Person(String pName, int pAge) {
System.out.println("构造器被调用~~ 完成对象的属性初始化");
name = pName;
age = pAge;
}
}细节
1、一个类可以定义多个不同的构造器,及构造器重载
2、构造器名和类名相同
3、构造器没有返回值
4、构造器是完成对象的初始化,并不是创建对象
5、在创建对象时,系统自动的调用该类的构造方法
6、如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器
7、一旦定义了自己的构造器,默认的构造器就会覆盖,就不能在使用无参构造器,除非显式的定义一下。
this关键字
是什么
代表当前对象
哪个对象调用,就代表哪个对象

细节
1、this关键字可以用来访问本类的属性、方法、构造器。
2、this用于区分当前类的属性和局部变量
3、访问成员方法的语法:this.方法名(参数列表)
4、访问构造器语法:this(参数列表)注意只能在构造器中使用(即只能在构造器中访问另一个构造器,必须放在第一条语句)
5、this不能再类定义的外部使用,只能在类定义的方法中使用
案例
public class TestPerson {
//编写一个 main 方法
public static void main(String[] args) {
Person p1 = new Person("mary", 20);
Person p2 = new Person("mary", 20);
System.out.println("p1 和 p2 比较的结果=" + p1.compareTo(p2));
}
}
/*
定义 Person 类,里面有 name、age 属性,并提供 compareTo 比较方法,
用于判断是否和另一个人相等,提供测试类 TestPerson 用于测试, 名字和年龄完全一样,就返回 true, 否则返回 false
韩顺平循序渐进学 Java 零基础
*/
class Person {
String name;
int age;
//构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//compareTo 比较方法
public boolean compareTo(Person p) {
//名字和年龄完全一样
// if(this.name.equals(p.name) && this.age == p.age) {
// return true;
// } else {
// return false;
// }
return this.name.equals(p.name) && this.age == p.age;
}
}访问修饰符

面向对象编程的三大特征
封装、继承、多态
封装
封装:就是把抽象出的数据【属性】和对数据的操作【方法】封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作【方法】,才能进行操作
封装的步骤
1、将属性进行私有化(private)不能直接修改属性
2、提供一个公共的(public)set方法,用于对属性进行判断和赋值
public void setXxx(类型 参数名){
// Xxx表示属性名
属性=参数名
}3、提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxx(){
// 权限判断
return xx;
}快速入门
package com.ybs.test;
public class Encapsulation01 {
public static void main(String[] args) {
// 如果要使用快捷键 alt+r, 需要先配置主类
// 第一次,我们使用鼠标点击形式运行程序,后面就可以用
Person person = new Person();
person.setName("韩顺平");
person.setAge(30);
person.setSalary(30000);
System.out.println(person.info());
System.out.println(person.getSalary());
// 如果我们自己使用构造器指定属性
Person smith = new Person("smith", 80, 50000);
System.out.println("====smith 的信息======");
System.out.println(smith.info());
}
}
/*
* 那么在 java 中如何实现这种类似的控制呢? 请大家看一个小程序(com.hspedu.encap: Encapsulation01.java), 不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认
* 年龄, 必须在 1-120, 年龄, 工资不能直接查看 , name 的长度在 2-6 字符 之间
*/
class Person {
public String name; // 名字公开
private int age; // age 私有化
private double salary; // ..
// 构造器 alt+insert
public Person() {
}
// 有三个属性的构造器
public Person(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
// 我们可以将 set 方法写在构造器中,这样仍然可以验证
setName(name);
setAge(age);
setSalary(salary);
}
// 自己写 setXxx 和 getXxx 太慢,我们使用快捷键
// 然后根据要求来完善我们的代码.
public String getName() {
return name;
}
public void setName(String name) {
// 加入对数据的校验,相当于增加了业务逻辑
if (name.length() >= 2 && name.length() <= 6) {
this.name = name;
} else {
System.out.println("名字的长度不对,需要(2-6)个字符,默认名字");
this.name = "无名人";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 判断
if (age >= 1 && age <= 120) {// 如果是合理范围
this.age = age;
} else {
System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄 18 ");
this.age = 18; // 给一个默认年龄
}
}
public double getSalary() {
// 可以这里增加对当前对象的权限判断
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
// 写一个方法,返回属性信息
public String info() {
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}继承
作用 : 提高代码的复用性
细节
子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
子类必须调用父类的构造器, 完成父类的初始化
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作
如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
java 所有类都是 Object 类的子类, Object 是所有类的基类.
父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
继承本质详解

练习
练习一
class A {
A() {
System.out.println("a");
}
A(String name) {
System.out.println("a name");
}
}
class B extends A {
B() {
this("abc");
System.out.println("b");
}
B(String name) {
System.out.println("b name");
}
}
main中 B b = new B(); 会输出什么super关键字
作用
super代表父类的引用,用于访问父类的属性、方法、构造器
基本语法
super.属性
super.方法名(参数列表)
访问构造器 super(参数列表)
只能放在构造器的第一句,只能出现一次
| 区别点 | this | super |
|---|---|---|
| 访问属性 | 访问本类的属性,如果没有则从父类中查找 | 从父类开始查找属性 |
| 调用方法 | 访问本类的方法,如果没有则从父类中查找 | 从父类开始查找方法 |
| 调用构造器 | 调用本类构造器,必须放到构造器首行 | 调用父类构造器,必须放在子类构造器首行 |
| 特殊 | 表示当前对象 | 子类中访问父类对象 |
细节
1、当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super,如果没有重名,使用super、this、直接访问是一样的效果
2、super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类中的成员,如果多个基类中都有同名的成员,使用super访问遵循就近原则,也需要遵守访问权限的相关规则
3、直接方法名调用 等价于this点方法名,从本类开始逐级向上查找 ,super点方法名从父类开始逐级向上查找
方法重写
基本介绍
方法重写就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,就说子类的这个方法覆盖了父类的方法
细节
1、子类的方法的形参列表、方法名称,要和父类方法的形参列表,方法名称完全一样
2、子类方法的返回类型和父类方法的返回类型一样,或者是父类放回类型的子类
例如
public Object getInfo(){}
public Object getInfo(){}3、子类方法不能缩小父类方法的访问权限
| 名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
|---|---|---|---|---|---|
| 重载(overload) | 本类 | 必须一样 | 类型,个数或者顺序至少有一个不同 | 无要求 | 无要求 |
| 重写(override) | 父子类 | 必须一样 | 相同 | 子类重写的方法,返回类型和父类返回的类型一致,或者是其子类 | 子类方法不能缩小父类方法的访问范围 |
多态(多种状态)
基本介绍
方法或对象具有多种形态,多态是建立在继承和封装的基础之上的
多态的具体体现
方法的多态
1、重写和重载体现多态
对象的多态(核心)
注意:
1、一个对象的编译类型和运行类型可以不一致
2、编译类型在定义对象式就确定了,不能改变
3、运行类型是可以变化的
4、编译类型看等号的的左边,运行类型看等号的右边
Animal animal = new Dog();
animal = new Cat();快速入门
package com.wz.poly_;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// public void feed(Dog dog ,Food bone){
// System.out.println("主人 " + name + " 给 " + dog.getName() + " 吃 " + bone.getName());
// }
public void feed(Animal animal,Food food){
System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
}
public static void main(String[] args) {
Master master = new Master("Tom");
Dog dog = new Dog("大黄");
Bone bone = new Bone("骨头");
master.feed(dog,bone);
Cat cat = new Cat("小花");
Fish fish = new Fish("鱼");
master.feed(cat,fish);
}
}注意事项和细节讨论
1、多态的前提是:两个对象(类)存在继承关系
2、多态的向上转型
- 本质:父类的引用指向了子类的对象
- 语法:父类类型 引用名 = new 子类类型();
Animal animal = new Cat();
- 特点:编译类型看左边,运行类型看右边。可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员(编译阶段,能调用哪些成员是由变编译类型决定的),最终运行效果看子类的具体实现(先从本类查找)!
3、多态的向下转型
- 语法:子类类型 引用名 = (子类类型)父类引用
Cat cat = (Cat) animal;
- 只能墙砖父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型中所有的成员
4、属性没有重写之说,属性的值看编译类型
5、instanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或者XX类型的子类型
练习
public static void main(String[]args){
double d=13.4; // 正确
long l=(long)d; // 正确
System.out.println(13); // 正确
int in=5; // 正确
boolean b= (boolean) in; // 错误
Object obj="Hello"; // 正确
String objstr=(String)obj; // 正确
Object obiPri=new Integer(5); // 正确,但不推荐使用Integer构造器
String str=(String)obiPri; // 错误
Integer str1=(Integer)obiPri; // 正确
}double d=13.4;正确。直接给一个double类型变量赋值。long l=(long)d;正确。将double类型强制转换为long类型。这是合法的,但可能会导致精度损失。System.out.println(13);正确。直接打印整数13到控制台。int in=5;正确。给一个int类型变量赋值。boolean b= (boolean) in;错误。这是因为Java中不允许将整型直接强制转换为布尔类型。Java中的布尔类型不能像某些其他语言那样用数字表示。Object obj="Hello";正确。字符串字面值"Hello"是一个String对象,而String类是Object类的子类,所以可以将其赋值给一个Object类型的变量。String objstr=(String)obj;正确。因为obj实际上是一个指向String对象的引用,所以可以将其强制类型转换回String。Object obiPri=new Integer(5);正确,但自从Java 5以来,自动装箱特性使得直接使用Integer.valueOf(5)更加推荐,且直接使用new Integer(5)被视为过时(deprecated),因为它创建了不必要的Integer对象实例。String str=(String)obiPri;错误。obiPri是一个指向Integer对象的引用,而Integer和String在类型层次结构中是平行的,它们之间不能进行直接的强制类型转换。Integer str1=(Integer)obiPri;正确。因为obiPri实际上是一个指向Integer对象的引用,所以可以将其强制类型转换回Integer。
练习二

java动态绑定机制(非常重要)
1、当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2、当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用

多态数组
是什么
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
public static void main(String[] args) {
//应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
// 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
for(int i = 0; i < persons.length;i++){
System.out.println(persons[i].say());
}升级
for (int i = 0; i < persons.length; i++) {
//老师提示: person[i] 编译类型是 Person ,运行类型是是根据实际情况有 JVM 来判断
System.out.println(persons[i].say());//动态绑定机制
//这里大家聪明. 使用 类型判断 + 向下转型.
if(persons[i] instanceof Student) {//判断 person[i] 的运行类型是不是 Student
Student student = (Student)persons[i];//向下转型
student.study();
//小伙伴也可以使用一条语句 ((Student)persons[i]).study();
} else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
} else if(persons[i] instanceof Person){
} else {
System.out.println("你的类型有误, 请自己检查...");
}多态参数
package com.wz.poly_.polyparameter_;
public class PloyParameter {
public void showEmpAnnual(Employee employee){
System.out.println(employee.getAnnual());
}
public static void main(String[] args) {
PloyParameter ployParameter = new PloyParameter();
Works tom = new Works("Tom", 2000.0);
ployParameter.showEmpAnnual(tom);
ployParameter.testWork(tom);
}
public void testWork(Employee employee){
if(employee instanceof Works){
((Works) employee).work();
}else if (employee instanceof Manage){
((Manage) employee).manage();
}else {
}
}
}Object 类详解
==
==是一个比较运算符
1、既可以判断基本类型,也可以判断引用类型
2、如果判断基本类型,是判断的值是否相等,
3、判断引用类型,是判断地址是否相等,即判断是不是同一个对象
equals
equals是object的一个方法,只能判断引用类型
默认判断的是地址是否相等,子类往往重写该方法,用于判断内容是否相等
课堂练习
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("jack", 10, '男');
Person person2 = new Person("jack", 20, '男');
System.out.println(person1.equals(person2));//假
}
}
//判断两个 Person 对象的内容是否相等,
//如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false
class Person{ //extends Object
private String name;
private int age;
private char gender;
//重写 Object 的 equals 方法
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回 true
if(this == obj) {
return true;
}
//类型判断
if(obj instanceof Person) {//是 Person,我们才比较
//进行 向下转型, 因为我需要得到 obj 的 各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
//如果不是 Person ,则直接返回 false
return false;
}课堂练习二
public class EqualsExercise02 {
public static void main(String[] args) {
Person_ p1 = new Person_();
p1.name = "hspedu";
Person_ p2 = new Person_();
p2.name = "hspedu";
System.out.println(p1==p2); //False
System.out.println(p1.name .equals( p2.name));//T
System.out.println(p1.equals(p2));//False
String s1 = new String("asdf");
String s2 = new String("asdf");
System.out.println(s1.equals(s2));//T
System.out.println(s1==s2); //F
}
}
class Person_{//类
public String name;
}课堂练习三
int it = 65;
float fl = 65.0f;
System.out.println(“65 和 65.0f 是否相等?” + (it == fl));//T
char ch1 = ‘A’; char ch2 = 12;
// 在 Java 中,char 类型在涉及到数值操作时会被自动转换为对应的整数值
System.out.println(“65 和‘A’是否相等?” + (it == ch1));//T
System.out.println(“12 和 ch2 是否相等?” + (12 == ch2));//T
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1 和 str2 是否相等?"+ (str1 == str2)); //F
System.out.println(“str1 是否 equals str2?”+(str1.equals(str2)));//T
System.out.println(“hello” == new java.sql.Date()); //编译错误hashCode 方法
返回该对象的哈希码值。支持此方法是为了提高哈希表的性能
实际上,由Object类定义的hashCode方法确实会针对不同的对象返回不同的整数(这一般是通过该对象的内部地址转换成一个整数来实现的)
小结
1、提高具有哈希结构的容器的效率
2、两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
3、两个引用,如果指向的是不同的对象,则哈希值是不一样的
4、哈希值只要根据地址号来的,不能完全将哈希值等价于地址
toString 方法
基本介绍
1、默认返回:全类名(包名加类名)+@+哈希值的十六进制

2、子类往往重写toString方法,用于返回对象的属性信息
3、重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式.
4、当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用monster.toString()
finalize 方法
1、当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写该方法,做一些释放资源的操作
2、什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。
3、垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制
老韩提示: 我们在实际开发中,几乎不会运用 finalize , 所以更多就是为了应付面试
断点调试
快捷键
F7(跳入) F8(跳过) shift+F8(跳出) F9(resume,执行到下一个断点)
F7:跳入方法内 F8: 逐行执行代码.
shift+F8:跳出方法

面向对象编程(高级部分)
类变量内存布局

不管static对象在哪 共识
1、static变量是同一个类所有对象共享
2、static类变量,在类加载时生成
类变量
什么是类变量
类变量也叫做静态属性/静态变量,是该类所有对象共享的变量,任何一个该类对象去访问它时,取到的都是相同的值,任何一个对象是修改它时,修改的也是同一个变量
定义
访问修饰符 static 数据类型 变量名 (推荐)
static 访问修饰符 数据类型 变量名
如何访问
类名.类变量名 (推荐)
对象名.类变量名
细节
1、什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student(name,staticfee)
2、类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
3、加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
4、类变量可以通过 类名,类变量名 或者 对象名.类变量名 来访问,但java设计者推荐我们使用 类名.类变量名方式访问。【前提是 满足访问修饰符的访问权限和范围】
5、实例变量不能通过 类名,类变量名 方式访问.。
6、类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了就可以使用类变量了。
7、类变量的生命周期是随类的加载开始随着类消亡而销毁。
类方法(静态方法)
定义
访问修饰符 static 数据返回类型 方法名(){}
调用
类名.类方法名
对象名.类方法名
应用场景
不创建实例就可以使用方法
注意
1、类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数
普通方法中隐含着this的参数
2、类方法可以通过类名调用,也可以通过对象名调用。
3、普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用。
4、类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以
5、类方法(静态方法)中 只能访问 静态变量 或静态方法。
6、普通成员方法,既可以访问 非静态成员,也可以访问静态成员。
小结: 静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员
(必须遵守访问权限)
练习题

理解 main 方法语法
深入了解main方法
public static void main(String[] args){}
1、main方法时虚拟机调用
2、java虚拟机需要调用类的main()方法,所有该方法的访问权限必须是public
3、java虚拟机在执行main方法时不必创建对象,所以该方法必须是static
4、该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
5、java 执行的程序 参数1 参数2 参数3
特别说明
1、在main()方法中,我们可以直接调用main方法所在类的静态方法和静态属性
2、不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态的成员。
代码块
基本介绍
代码块又称为初始化块,属于类中的成员,类似方法,将逻辑语句封装在方法体中,通过{}包围
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或者类显式调用,而是加载类时,或者创建对象时隐式调用。
语法
[修饰符] {};
注意
1、修饰符 可选,要写的话,也只能是static
2、代码块分为两类,使用static修饰的称为静态代码块,没有static修饰的称为普通代码块或者非静态代码块
3、逻辑语句可以为任何逻辑语句(输出,输入,方法调用,循环,判断等)
4、;可写可不写
好处
1、相当于另一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2、场景:如果多个构造器中都有重复的语句,可以抽取到初始化代码中,提高代码的复用性
3、代码块调用的顺序优先于构造器
注意事项
1、static代码块也叫做静态代码块,作用是对类进行初始化,而且随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象,就执行。
2、类什么时候被加载
- 创建对象实例时
- 创建子类对象实例时,父类也会被加载
- 使用类的静态成员时
3、普通的代码块,在创建对象实例时,会被隐式调用,被创建一次,就会调用一次,如果只是使用类的静态成员时,普通代码块并不会执行
4、创建一个对象时,在一个类调用顺序是:
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
- 调用构造方法
5、构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时就执行完毕了,因此是优先于构造器和普通代码块执行的
class A{
public A(){// 构造器
// 这里隐藏的执行要求
//1、super()
//2、调用普通代码块
System.out.println("ok")
}
}6、在继承关系中,静态代码块,静态属性初始化,普通代码初始化,普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类构造方法
7、静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
练习题
题 1:下面的代码输出什么?1min
class Person {
public static int total;//静态变量
static {//静态代码块
total = 100;
System.out.println("in static block!");//(1)
}
}
public class Test {
public static void main(String[] args) {
System.out.println("total = " + Person.total); //100
System.out.println("total = " + Person.total); //100
}
}
in static block!
total=100
total=100单例设计模式
什么是设计模式
1、静态方法和属性的经典使用
2、设计模式是大量的实践中总结和理论化之后优选的代码结构,编程风格,以及解决问题的思考方式,设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再去思考和摸索。
什么是单例模式
单例(单个的实例)
1、所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取的其对象实例的方法
2、单例模式有两种方式 饿汉式 懒汉式
饿汉式实现
步骤
1、构造器私有化 防止直接new
2、类的内部创建对象
3、向外暴露一个静态的公共方法。返回 对象
public class SingleTon01 {
public static void main(String[] args) {
// GirlFriend xh = new GirlFriend("小红");
// GirlFriend xb = new GirlFriend("小白");
//通过方法可以获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
//System.out.println(GirlFriend.n1);
//... }
}
class GirlFriend {
private String name;
//public static int n1 = 100;
//为了能够在静态方法中,返回 gf 对象,需要将其修饰为 static
//對象,通常是重量級的對象, 餓漢式可能造成創建了對象,但是沒有使用.
private static GirlFriend gf = new GirlFriend("小红红");
//如何保障我们只能创建一个 GirlFriend 对象
//步骤[单例模式-饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是 static)
//3. 提供一个公共的 static 方法,返回 gf 对象
private GirlFriend(String name) {
System.out.println("構造器被調用.");
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}懒汉式
public class SingleTon02 {
public static void main(String[] args) {
//new Cat("大黃");
//System.out.println(Cat.n1);
Cat instance = Cat.getInstance();
System.out.println(instance);
//再次調用 getInstance
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
}
}
//希望在程序運行過程中,只能創建一個 Cat 對象
//使用單例模式
class Cat {
private String name;
public static int n1 = 999;
private static Cat cat; //默認是 null
//步驟
//1.仍然構造器私有化
//2.定義一個 static 靜態屬性對象
//3.提供一個 public 的 static 方法,可以返回一個 Cat 對象
//4.懶漢式,只有當用戶使用 getInstance 時,才返回 cat 對象, 後面再次調用時,會返回上次創建的 cat 對象
// 從而保證了單例
private Cat(String name) {
System.out.println("構造器調用...");
this.name = name;
}
public static Cat getInstance() {
if (cat == null) {//如果還沒有創建 cat 對象
cat = new Cat("小可愛");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}饿汉式 VS 懒汉式
- 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程后,会完善一把)
- 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
- 在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式
final 关键字
final 中文意思:最后的,最终的.
final 可以修饰类、属性、方法和局部变量.
在某些情况下,程序员可能有以下需求,就会使用到final:
1)当不希望类被继承时,可以用final修饰.
2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。: 访问修饰符 final 返回类型 方法名
3)当不希望类的的某个属性的值被修改,可以用final修饰. public final double TAX_RATE=0.08
4)当不希望某个局部变量被修改,可以使用final修饰 final double TAX_RATE=0.08
final 使用注意事项和细节讨论
1)final修饰的属性又叫常量,一般 用 XX_XX_XX 来命名
2final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
① 定义时: 如 public final double TAX RATE=0.08:
② 在构造器中
③ 在代码块中
3)如果final修饰的属性是静态的,则初始化的位置只能是
① 定义时
② 在静态代码块 不能在构造器中赋值。
4final类不能继承,但是可以实例化对象。
5)如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
6)一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
7)final不能修饰构造方法(即构造器)
8final 和 static 往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。
9)包装类(Integer,Double,Float,Boolean等都是final),String也是final类
抽象类
介绍
- 当父类的方法需要声明,但又不确定怎么实现的时候,可以将其生命为抽象方法,那么这个类就是抽象类,
- 所谓抽象方法就是没有实现的方法。
- 所谓没有实现的方法就是没有方法体
- 当一个类中有抽象方法时,需要将这个类声明为抽象类
- 抽象类会被继承,由子类来实现抽象方法。
- 用abstract关键字来修饰一个类时,这个类就叫做抽象类
- 访问修饰符 abstract 类名{}
- 用abstract 关键字来修饰一个方法时,这个方法就是抽象方法
- 访问修饰符 abstract 返回类型 方法名(参数列表); // 没有方法体
- 抽象类的价值更多的在于设计,是设计者设计好后,让子类继承并实现抽象类
抽象类使用的注意事项和细节讨论
- 抽象类不能被实例化。
- 抽象类不一定包含abstract方法,也就是说抽象类可以没有abstract 方法
- 一旦类包含抽象方法,则这个类必须声明为抽象类
- abstract 只能修饰类和方法,不能修饰属性和其他的
- 抽象类可以有任意成员【抽象类本质还是类,可以包含类的所有 成员】
- 抽象方法不能有主题,即不能实现。
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
- 抽象方法不能使用private,final,static来修饰,因为这些关键字都是和重写相违背的
接口
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
interface 接口名{
// 属性
// 抽象方法
}
class 类名 implements 接口{
自己的属性
自己的方法
必须实现的接口的抽象方法
}
小结
接口是更加抽象的抽象类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体,jdk7.0 以前
接口体现了程序设计的多态和高内聚低耦合的设计思想
特别说明:jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
默认方法
default public void ok(){
System.out.println("ok....)
}静态方法
public static void ok(){
System.out.println("ok....)
}应用场景
package com.hspedu.interface_;
public interface DBInterface
{ //项目经理
public void connect(); //连接方法
public void close(); //关闭连接
}
package com.hspedu.interface_;
//A 程序
public class MysqlDB implements DBInterface
{
@Override
public void connect()
{
System.out.println("连接 mysql");
}
@Override
public void close()
{
System.out.println("关闭 mysql");
}
}
package com.hspedu.interface_;
//B 程序员连接 Oracle
public class OracleDB implements DBInterface
{
@Override
public void connect()
{
System.out.println("连接 oracle");
}
@Override
public void close()
{
System.out.println("关闭 oracle");
}
}
package com.hspedu.interface_;
public class Interface03
{
public static void main(String[] args)
{
MysqlDB mysqlDB = new MysqlDB();
t(mysqlDB);
OracleDB oracleDB = new OracleDB();
t(oracleDB);
}
public static void t(DBInterface db)
{
db.connect();
db.close();
}
}注意事项和细节
- 接口不能被实例化
- 接口所有的方法是public 方法,接口中抽象方法,可以不用abstract修饰
- 一个普通类实现接口,就必须将该接口中的所有方法都实现
- 抽象类实现接口,可以不用实现接口的方法
- 一个类同时可以实现多个接口
- 接口中的属性,只能是final的,而且是public static final 修饰符,比如 int a =1 实际上是
public static final int a = 1(必须初始化) - 接口中属性的访问形式 接口名.属性名
- 接口不能继承其他的类。但是可以继承多个别的接口 interface A extends B,C{}
- 接口的修饰符 只能是public 和默认, 这点和类的修饰符是一样的
练习
interface A
{
int a = 23; //等价 public static final int a= 23:
}
class B implements A
{ //正确
main中: B b = new B(): //ok
System.out.println(b.a);//23
System.out.printIn(A.a); //23
System.out.println(B.a); //23
}实现接口 vs 继承类
当子类继承了父类,就自动的拥有父类的功能
如果子类需要扩展功能,可以通过实现接口的方式扩展.
可以理解 实现接口 是 对 java 单继承机制的一种补充.
小结
接口和继承解决的问题不同
继承的价值主要在于:解决代码复用性和可维护性
接口的价值主要在于:设计,设计各种规范(方法),让其它类去实现这些方法。即更加的灵活
接口比继承更加灵活
接口比继承更加灵活,继承是满足is-a的关系,而接口只是满足like-a的关系
接口在一定程度上实现代码解耦【即:接口规范性+动态绑定机制】
接口的多态特性
package com.hspedu.interface_;
public class InterfacePolyArr
{
public static void main(String[] args)
{
//多态数组 -> 接口类型数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
/*
给 Usb 数组中,存放 Phone 和 相机对象,Phone 类还有一个特有的方法 call(),
请遍历 Usb 数组,如果是 Phone 对象,除了调用 Usb 接口定义的方法外,
还需要调用 Phone 特有方法 call
*/
for(int i = 0; i < usbs.length; i++)
{
usbs[i].work(); //动态绑定.. //和前面一样,我们仍然需要进行类型的向下转型
if(usbs[i] instanceof Phone_)
{ //判断他的运行类型是 Phone_
((Phone_) usbs[i]).call();
}
}
}四种内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员【思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系,注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.
基本语法
class outer{// 外部类
class inner{ // 内部类
}
}
class other{ // 外部其他类
}
内部类的分类
定义在外部类局部位置(方法中/代码块) :
(1) 局部内部类 (有类名)
(2) 匿名内部类(没有类名)
定义在外部类成员位置
(1) 成员内部类 (没用static修饰)
(2) 静态内部类(用static修饰)
局部内部类的使用
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
1.可以直接访问外部类的所有成员,包含私有的
2.不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
3.作用域:仅仅在定义它的方法或代码块中。
4.局部内部类---访问---->外部类的成员[访问方式:直接访问】
5.外部类---访问---->局部内部类的成员访问方式:创建对象,再访问(注意:必须在作用域内)
6.外部其他类---不能访问----->局部内部类(因为 局部内部类地位是一个局部变量)
7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
记住:
(1)局部内部类定义在方法中/代码块
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类
匿名内部类的使用(重要!!!!!!!)
(1)本质是类
(2)内部类
(3)该类没有名字
(4)同时还是一个对象
说明:
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
1.匿名内部类的基本语法
new 类或接口(参数列表){
类体
};
基于接口的匿名内部类
tiger 的编译类型 ? IA
tiger 的运行类型 ? 就是匿名内部类 Outer04$1
jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址
返回给 tiger
匿名内部类使用一次,就不能再使用
细节
2.匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
调用的两种方式
p.hi();//动态绑定, 运行类型是 Outer05$1
//也可以直接调用, 匿名内部类本身也是返回对象
// class 匿名内部类 extends Person {}
// new Person(){
// @Override
// public void hi() {
// System.out.println("匿名内部类重写了 hi 方法,哈哈...");
// }
// @Override
// public void ok(String str) {
// super.ok(str);
// }
// }.ok("jack");3.可以直接访问外部类的所有成员,包含私有的
4.不能添加访问修饰符,因为它的地位就是一个局部变量。
5.作用域:仅仅在定义它的方法或代码块中。
6.匿名内部类---访问---->外部类成员 [访问方式:直接访问]
7.外部其他类---不能访问--->匿名内部类(因为 匿名内部类地位是一个局部变量)
8.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
实践
public class InnerClassExercise01 {
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new IL() {
@Override
public void show() {
System.out.println("这是一副名画~~...");
}
});
//传统方法
f1(new Picture());
}
//静态方法,形参是接口类型
public static void f1(IL il) {
il.show();
}
}
//接口
interface IL {
void show();
}
//类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画 XX...");
}
}public class InnerClassExercise02 {
public static void main(String[] args) {
/*
1.有一个铃声接口 Bell,里面有个 ring 方法。(右图)
2.有一个手机类 Cellphone,具有闹钟功能 alarmClock,参数是 Bell 类型(右图)
3.测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
4.再传入另一个匿名内部类(对象),打印:小伙伴上课了
*/
CellPhone cellPhone = new CellPhone();
//老韩解读
//1. 传递的是实现了 Bell 接口的匿名内部类 InnerClassExercise02$1
//2. 重写了 ring
//3. Bell bell = new Bell() {
// @Override
// public void ring() {
// System.out.println("懒猪起床了");
// }
// }
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell { //接口
void ring();//方法
}
class CellPhone {//类
public void alarmClock(Bell bell) {//形参是 Bell 接口类型
System.out.println(bell.getClass());
bell.ring();//动态绑定
}
}成员内部类的使用
说明:成员内部类是定义在外部类的成员位置,并且没有static修饰
1.可以直接访问外部类的所有成员,包含私有的

2.可以添加任意访问修饰符(public 、protected、默认、private)因为他的地位是一个成员
3.作用域和外部类的其他成员一样,为整个类体比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法.
4.成员内部类---访问---->外部类成员(比如属性)访问方式:直接访问
5.外部类---访问------>成员内部类(说明)访问方式: 创建对象,再访问
6.外部其他类---访问---->成员内部类

7.如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
静态内部类
说明:静态内部类是定义在外部类的成员位置,并且有static修饰
1.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
3.作用域 :同其他的成员,为整个类体
4.静态内部类---访问---->外部类(比如:静态属性)[访问方式:直接访问所有静
5.外部类---访问------>静态内部类 访问方式:创建对象,再访问态成员]
6.外部其他类---访问----->静态内部类


7.如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近 原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
面向对象已完结
枚举和注解
创建 Season 对象有如下特点
季节的值是有限的几个值(spring, summer, autumn, winter)
只读,不需要修改。
解决方案-枚举
枚举对应英文(enumeration, 简写 enum)
枚举是一组常量的集合。
可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
枚举的二种实现方式
自定义类实现枚举
使用 enum 关键字实现枚举
第一种:自定义类实现枚举-应用案例
1.不需要提供setXxx 方法,因为枚举对象值通常为只读
2.对枚举对象/属性使用 final + static 共同修饰,实现底层优化
3.枚举对象名通常使用全部大写,常量的命名规范
4.枚举对象根据需要,也可以有多个属性
public class Enumeration02 {
public static void main(String[] args) {
System.out.println(Season.AUTUMN);
System.out.println(Season.SPRING);
}
}
//演示字定义枚举实现
class Season {//类
private String name;
private String desc;//描述
//定义了四个对象, 固定.
public static final Season SPRING = new Season("春天", "温暖");
public static final Season WINTER = new Season("冬天", "寒冷");
public static final Season AUTUMN = new Season("秋天", "凉爽");
public static final Season SUMMER = new Season("夏天", "炎热");
//1. 将构造器私有化,目的防止 直接 new
//2. 去掉 setXxx 方法, 防止属性被修改
//3. 在 Season 内部,直接创建固定的对象
//4. 优化,可以加入 final 修饰符
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}第一种:自定义类实现枚举-小结
构造器私有化
本类内部创建一组对象[四个 春夏秋冬]
对外暴露对象(通过为对象添加 public final static 修饰符)
可以提供 get 方法,但是不要提供 set
第二种:enum 关键字实现枚举
public class Enumeration03 {
public static void main(String[] args) {
System.out.println(Season2.AUTUMN);
System.out.println(Season2.SUMMER);
}
}
//演示使用 enum 关键字来实现枚举类
enum Season2 {//类
//定义了四个对象, 固定. // public static final Season SPRING = new Season("春天", "温暖");
// public static final Season WINTER = new Season("冬天", "寒冷");
// public static final Season AUTUMN = new Season("秋天", "凉爽");
// public static final Season SUMMER = new Season("夏天", "炎热");
//如果使用了 enum 来实现枚举类
//1. 使用关键字 enum 替代 class
//2. public static final Season SPRING = new Season("春天", "温暖") 直接使用
// SPRING("春天", "温暖") 解读 常量名(实参列表)
//3. 如果有多个常量(对象), 使用 ,号间隔即可
//4. 如果使用 enum 来实现枚举,要求将定义常量对象,写在前面
//5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
SPRING("春天","温暖"),WINTER("冬天","寒冷"),
AUTUMN("秋天","凉爽"),SUMMER("夏天","炎热")/*, What()*/;
private String name;
private String desc;//描述
private Season2() {//无参构造器
}
private Season2(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}enum 关键字实现枚举注意事项
当我们使用 enum 关键字开发一个枚举类时,默认会继承 Enum 类, 而且是一个final 类[如何证明],老师使用 javap 工具来演示
传统的 public static final Season2 SPRING = new Season2("春天", "温暖"); 简化成SPRING("春天", "温暖"), 这里必须知道,它调用的是哪个构造器.
如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
当有多个枚举对象时,使用,间隔,最后有一个分号结尾
枚举对象必须放在枚举类的行首
enum 常用方法说明
说明:使用关键字 enum 时,会隐式继承 Enum 类, 这样我们就可以使用 Enum 类相关的方法。
- toString:Enum 类已经重写过了,返回的是当前对象
名,子类可以重写该方法,用于返回对象的属性信息
name:返回当前对象名(常量名),子类中不能重写
ordinal:返回当前对象的位置号,默认从 0 开始
values:返回当前枚举类中所有的常量
valueOf:将字符串转换成枚举对象,要求字符串必须
为已有的常量名,否则报异常!
- compareTo:比较两个枚举常量,比较的就是编号!
public class EnumMethod {
public static void main(String[] args) {
//使用 Season2 枚举类,来演示各种方法
Season2 autumn = Season2.AUTUMN;
//输出枚举对象的名字
System.out.println(autumn.name());
//ordinal() 输出的是该枚举对象的次序/编号,从 0 开始编号
//AUTUMN 枚举对象是第三个,因此输出 2
System.out.println(autumn.ordinal());
//从反编译可以看出 values 方法,返回 Season2[]
//含有定义的所有枚举对象
Season2[] values = Season2.values();
System.out.println("===遍历取出枚举对象(增强 for)====");
for (Season2 season : values) {//增强 for 循环
System.out.println(season);
}
//valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
//执行流程
//1. 根据你输入的 "AUTUMN" 到 Season2 的枚举对象去查找
//2. 如果找到了,就返回,如果没有找到,就报错
Season2 autumn1 = Season2.valueOf("AUTUMN");
System.out.println("autumn1=" + autumn1);
System.out.println(autumn == autumn1);
//compareTo:比较两个枚举常量,比较的就是编号
//老韩解读
//1. 就是把 Season2.AUTUMN 枚举对象的编号 和 Season2.SUMMER 枚举对象的编号比较
//2. 看看结果
/*
public final int compareTo(E o) {
return self.ordinal - other.ordinal;
}
Season2.AUTUMN 的编号[2] - Season2.SUMMER 的编号[3]
*/
System.out.println(Season2.AUTUMN.compareTo(Season2.SUMMER));
}
}enum 实现接口
使用 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum,而 Java 是单继承机制。
枚举类和普通类一样,可以实现接口,如下形式。
enum 类名 implements 接口 1,接口 2{}
注解
注解的理解
注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。
和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE 中注解占据了更重要的角
色,例如用来配置应用程序的任何切面,代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。
基本的 Annotation 介绍
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
三个基本的 Annotation:
@Override: 限定某个方法,是重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
@SuppressWarnings: 抑制编译器警告
基本的 Annotation 应用案例
@Override
//老韩解读
//1. @Override 注解放在 fly 方法上,表示子类的 fly 方法时重写了父类的 fly
//2. 这里如果没有写 @Override 还是重写了父类 fly
//3. 如果你写了@Override 注解,编译器就会去检查该方法是否真的重写了父类的
// 方法,如果的确重写了,则编译通过,如果没有构成重写,则编译错误
//4. 看看 @Override 的定义
// 解读: 如果发现 @interface 表示一个 注解类
/*
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
*/使用说明
1 @Override 表示指定重写父类的方法(从编译层面验证),如果父类没有fly方法,则会报错
2 如果不写@Override 注解,而父类如果有fly 则仍然可以构成重写
3 @Override 只能修饰方法,不能修饰其他类,包,属性等等
4 查看@Override 注解源码 为 @Target(ElementType.METHOD)说明只能修饰方法
5 @Target 是修饰注解的注解,成为元注解
@Deprecated
//老韩解读
//1. @Deprecated 修饰某个元素, 表示该元素已经过时
//2. 即不在推荐使用,但是仍然可以使用
//3. 查看 @Deprecated 注解类的源码
//4. 可以修饰方法,类,字段, 包, 参数 等等
//5. @Deprecated 可以做版本升级过渡使用
/*
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
*/使用说明
1 用于表示某个程序元素(类,方法等)已过时
2 可以修饰方法,类,字段,包,参数,等等
3 @Deprecated 的作用可以做到新旧版本的兼容和过度
@SuppressWarnings
//老韩解读
//1. 当我们不希望看到这些警告的时候,可以使用 SuppressWarnings 注解来抑制警告信息
//2. 在{""} 中,可以写入你希望抑制(不显示)警告信息
//3. 可以指定的警告类型有
// all,抑制所有警告
// boxing,抑制与封装/拆装作业相关的警告
// //cast,抑制与强制转型作业相关的警告
// //dep-ann,抑制与淘汰注释相关的警告
// //deprecation,抑制与淘汰的相关警告
// //fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告
// //finally,抑制与未传回 finally 区块相关的警告
// //hiding,抑制与隐藏变数的区域变数相关的警告
// //incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告
// //javadoc,抑制与 javadoc 相关的警告
// //nls,抑制与非 nls 字串文字相关的警告
// //null,抑制与空值分析相关的警告
// //rawtypes,抑制与使用 raw 类型相关的警告
// //resource,抑制与使用 Closeable 类型的资源相关的警告
// //restriction,抑制与使用不建议或禁止参照相关的警告
// //serial,抑制与可序列化的类别遗漏 serialVersionUID 栏位相关的警告
// //static-access,抑制与静态存取不正确相关的警告
// //static-method,抑制与可能宣告为 static 的方法相关的警告
// //super,抑制与置换方法相关但不含 super 呼叫的警告
// //synthetic-access,抑制与内部类别的存取未最佳化相关的警告
// //sync-override,抑制因为置换同步方法而遗漏同步化的警告
// //unchecked,抑制与未检查的作业相关的警告
// //unqualified-field-access,抑制与栏位存取不合格相关的警告
// //unused,抑制与未用的程式码及停用的程式码相关的警告
//4. 关于 SuppressWarnings 作用范围是和你放置的位置相关
// 比如 @SuppressWarnings 放置在 main 方法,那么抑制警告的范围就是 main
// 通常我们可以放置具体的语句, 方法, 类.
//5. 看看 @SuppressWarnings 源码
//(1) 放置的位置就是 TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE
//(2) 该注解类有数组 String[] values() 设置一个数组比如 {"rawtypes", "unchecked", "unused"}
/*
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
*/JDK 的元 Annotation
JDK 的元 Annotation 用于修饰其他 Annotation
元注解的种类
Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
Target // 指定注解可以在哪些地方使用
Documented //指定该注解是否会在 javadoc 体现
Inherited //子类会继承父类注解
@Retention 注解
说明
只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间, @Rentention 包含一个 RetentionPolicy类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
@Retention 的三种值
RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释
RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
RetentionPolicy.RUNTIME:编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注解
@Inherited
被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解
异常-Exception
异常介绍
基本概念
Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
执行过程中所发生的异常事件可分为两大类
1)Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out ofmemory),Error 是严重错误,程序会崩溃。
2)Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception 分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。
异常体系图一览

异常体系图的小结
1.异常分为两大类,运行时异常和编译时异常.
2.运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常
3.对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
4.编译时异常,是编译器要求必须处置的异常。
常见的运行时异常
NullPointerException 空指针异常
ArithmeticException 数学运算异常
ArrayIndexOutOfBoundsException 数组下标越界异常
ClassCastException 类型转换异常
NumberFormatException 数字格式不正确异常[]
常见的运行时异常举例
- NullPointerException 空指针异常 NullPointerException_.java
当应用程序试图在需要对象的地方使用 null 时,抛出该异常,
public class NullPointerException_ {
public static void main(String[] args) {
String name = null;
System.out.println(name.length());
}
}- ArithmeticException 数学运算异常 ArithmeticException_.java
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例,
- ArrayIndexOutOfBoundsException 数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
public class ArrayIndexOutOfBoundsException_ {
public static void main(String[] args) {
int[] arr = {1,2,4};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
}
}- ClassCastException 类型转换异常
当试图将对象强制转换为不是实例的子类时,抛出该异常。例如,以下代码将生成一个 ClassCastException
public static void main(String[] args) {
A b = new B(); //向上转型
B b2 = (B)b;//向下转型,这里是 OK
C c2 = (C)b;//这里抛出 ClassCastException
}
}
class A {}
class B extends A {}
class C extends A {}- NumberFormatException 数字格式不正确异常
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常 => 使用异常我们
可以确保输入是满足条件数字.
public static void main(String[] args) {
String name = "韩顺平教育";
//将 String 转成 int
int num = Integer.parseInt(name);//抛出 NumberFormatException
System.out.println(num);//1234
}编译异常
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
常见的编译异常
SQLException //操作数据库时,查询表可能发生异常
IOException //操作文件时,发生的异常
FileNotFoundException //当操作一个不存在的文件时,发生异常
ClassNotFoundException //加载类,而该类不存在时,异常
EOFException //操作文件,到文件末尾,发生异常
IllegalArguementException //参数异常
异常处理
基本介绍
异常处理就是当异常发生时,对异常处理的方式
异常处理的方式
1)try-catch-finally
程序员在代码中捕获发生的异常,自行处理
2)throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
示意图


try-catch 异常处理
try-catch 方式处理异常说明
1)Java提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用 于处理try块中发生的异常。可以根据需要在程序中有多个try...catch块。
2)基本语法
try {
//可疑代码
//将异常生成对应的异常对象,传递给catch块
}catch(异常){
//对异常的处理
}
//如果没有finally, 语法是可以通过try-catch 方式处理异常-注意事项
1)如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块
2)如果异常没有发生,则顺序执行try的代码块,不会进入到catch.
3)如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用如下代码-finally{}
4)可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception 在后,NulPointerException 在前),如果发生异常,只会匹配一个catch
5)可以进行 try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。应用场景,就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑
异常处理课堂练习



try-catch-finally 执行顺序小结
1)如果没有出现异常,则执行try块中所有语句,不执行catch块中语句,如果有finally,最后还需要执行finally里面的语句
2)如果出现异常,则try块中异常发生后,try块剩下的语句不再执行。将执行catch块中的语句,如果有finally,最后还需要执行finally里面的语句
课后练习
如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
import java.util.Scanner;
public class TryCatchExercise04 {
public static void main(String[] args) {
//如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
//思路
//1. 创建 Scanner 对象
//2. 使用无限循环,去接收一个输入
//3. 然后将该输入的值,转成一个 int
//4. 如果在转换时,抛出异常,说明输入的内容不是一个可以转成 int 的内容
//5. 如果没有抛出异常,则 break 该循环
Scanner scanner = new Scanner(System.in);
int num = 0;
String inputStr = "";
while (true) {
System.out.println("请输入一个整数:"); //
inputStr = scanner.next();
try {
num = Integer.parseInt(inputStr); //这里是可能抛出异常
break;
} catch (NumberFormatException e) {
System.out.println("你输入的不是一个整数:");
}
}
System.out.println("你输入的值是=" + num);
}
}throws 异常处理
基本介绍
1)如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何表明该方法将不对这些处理这种异常,则此方法应显示地声明抛出异常,异常进行处理,而由该方法的调用者负责处理。
2)在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
注意事项和使用细节
1)对于编译异常,程序中必须处理,比如 try-catch 或者 throws
2)对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
3)子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
4)在throws 过程中,如果有方法 try-catch,就相当于处理异常,就可以不必throws
自定义异常
基本概念
当程序中出现了某些“错误”但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
自定义异常步骤
1)定义类:自定义异常类名(程序员自己写) 继承Exception或RuntimeException
2)如果继承Exception,属于编译异常
3)如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)
自定义异常的应用实例
public class CustomException {
public static void main(String[] args) /*throws AgeException*/ {
int age = 180;
//要求范围在 18 – 120 之间,否则抛出一个自定义异常
if (!(age >= 18 && age <= 120)) {
//这里我们可以通过构造器,设置信息
throw new AgeException("年龄需要在 18~120 之间");
}
System.out.println("你的年龄范围正确.");
}
}
//自定义一个异常
//老韩解读
//1. 一般情况下,我们自定义异常是继承 RuntimeException
//2. 即把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制
//3. 即比较方便
class AgeException extends RuntimeException {
public AgeException(String message) {//构造器
super(message);
}
}throw 和 throws 的区别

测试题

常用类
包装类
1 针对八种基本数据类型相应的引用类型——包装类
2 有了类的特点,就可以调用类中的方法
| 基本数据类型 | 包装类 |
|---|---|
| boolean | Boolean |
| char | Character |
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| double | Double |
| float | Float |


包装类和基本数据的转换
演示 包装类 和 基本数据类型的相互转换,这里以int 和 Integer演示:
1)jdk5 前的手动装箱和拆箱方式,装箱: 基本类型->包装类型, 反之,拆箱
2)jdk5 以后(含jdk5)的自动装箱和拆箱方式
3)自动装箱底层调用的是valueOf方法,比如Integer.valueOf()
4)其它包装类的用法类似,不一-举例
装箱和拆箱
///演示 int <--> Integer 的装箱和拆箱
//jdk5 前是手动装箱和拆箱
//手动装箱 int->Integer
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//手动拆箱
//Integer -> int
int i = integer.intValue();
//jdk5 后,就可以自动装箱和自动拆箱
int n2 = 200;
//自动装箱 int->Integer
Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
//自动拆箱 Integer->int
int n3 = integer2; //底层仍然使用的是 intValue()方法练习

包装类型和 String 类型的相互转换
//包装类(Integer)->String
Integer i = 100;//自动装箱
//方式 1
String str1 = i + "";
//方式 2
String str2 = i.toString();
//方式 3
String str3 = String.valueOf(i);
//String -> 包装类(Integer)
String str4 = "12345";
Integer i2 = Integer.parseInt(str4);//使用到自动装箱
Integer i3 = new Integer(str4);//构造器Integer 类和 Character 类的常用方法
public class WrapperMethod {
public static void main(String[] args) {
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 类面试题 1
public class WrapperExercise02 {
public static void main(String[] args) {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //False
//所以,这里主要是看范围 -128 ~ 127 就是直接返回
/*
老韩解读
//1. 如果 i 在 IntegerCache.low(-128)~IntegerCache.high(127),就直接从数组返回
//2. 如果不在 -128~127,就直接 new Integer(i)
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
*/
Integer m = 1; //底层 Integer.valueOf(1); -> 阅读源码
Integer n = 1;//底层 Integer.valueOf(1);
System.out.println(m == n); //T
//所以,这里主要是看范围 -128 ~ 127 就是直接返回
//,否则,就 new Integer(xx);
Integer x = 128;//底层 Integer.valueOf(1);
Integer y = 128;//底层 Integer.valueOf(1);
System.out.println(x == y);//False
}
}Intege 类面试题总结
public class WrapperExercise03 {
public static void main(String[] args) {
//示例一
Integer i1 = new Integer(127);
Integer i2 = new Integer(127);
System.out.println(i1 == i2);//F
//示例二
Integer i3 = new Integer(128);
Integer i4 = new Integer(128);
System.out.println(i3 == i4);//F
//示例三
Integer i5 = 127;//底层 Integer.valueOf(127)
Integer i6 = 127;//-128~127
System.out.println(i5 == i6); //T
//示例四
Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8);//F
//示例五
Integer i9 = 127; //Integer.valueOf(127)
Integer i10 = new Integer(127);
System.out.println(i9 == i10);//F
//示例六
Integer i11 = 127;
int i12 = 127;
//只要有基本数据类型,判断的是值是否相同
System.out.println(i11 == i12); //T
//示例七
Integer i13 = 128;
int i14 = 128;
System.out.println(i13 == i14);//T
}
}String类
String类的理解和创建对象
1)String 对象用于保存字符串,也就是一组字符序列
2)字符串常量对象是用双引号括起的字符序列。例如:"你好"、"12.97"、"boy"等
3)字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字占两个字节
4)String类较常用构造器(其它看手册):
String s1 = new String(): //
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startindex,int count)
说明:
String实现了Serializable,说明String可以串行化,也就是可以进行网络传输
String实现了Comparable接口,说明String对象可以比较
public class String01 {
public static void main(String[] args) {
//1.String 对象用于保存字符串,也就是一组字符序列
//2. "jack" 字符串常量, 双引号括起的字符序列
//3. 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
//4. String 类有很多构造器,构造器的重载
// 常用的有 String s1 = new String(); //
//String s2 = new String(String original);
//String s3 = new String(char[] a);
//String s4 = new String(char[] a,int startIndex,int count)
//String s5 = new String(byte[] b)
//5. String 类实现了接口 Serializable【String 可以串行化:可以在网络传输】
// 接口 Comparable [String 对象可以比较大小]
//6. String 是 final 类,不能被其他的类继承
//7. String 有属性 private final char value[]; 用于存放字符串内容
//8. 一定要注意:value 是一个 final 类型, 不可以修改(需要功力):即 value 不能指向
// 新的地址,但是单个字符内容是可以变化
String name = "jack";
name = "tom";
final char[] value = {'a', 'b', 'c'};
char[] v2 = {'t', 'o', 'm'};
value[0] = 'H';
//value = v2; 不可以修改 value 地址
}
}创建 String 对象的两种方式
1)方式一:直接赋值 String s="hspedu"
2)方式二:调用构造器 String s= new String("hspedu");
两种创建 String 对象的区别
方式一:直接赋值 String s="hsp":
方式二:调用构造器 String s2 = new String("hsp”);
- 方式一:先从常量 池查看是否有"hsp"数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s最终指向的是常量池的空间地址
- 方式二:先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间。如果常量池没有"hsp",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。
- 画出两种方式的内存分布图

练习
第一题
String a = "abc";
String b ="abc"
System.out.printIn(a.equals(b));//T
System.out.printIn(a==b);//T 指向同一个对象
第二题
String a=“hsp"; //a 指向 常量池的“hsp”
String b =new String("hsp");//b 指向堆中对象System.out.printIn(a.equals(b));//T
System.out.println(a==b); //F
System.out.println(a==b.intern()); //T System.out.println(b==b.intern()); //F
知识点:当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用equals(objcct) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用
老韩解读;(1)b.intern()方法最终返回的是常量池的地址(对象)
第三题
String s1 ="hspedu”;//指向常量池”hspedu'
String s2 ="java";//指向常量池”java'
String s4 ="java";//指向常量池”java'
String s3 = new String("java”);//指向堆中对象
System.out.println(s2 == s3);//F
System.out.println(s2 ==s4)://T System.out.println(s2.equals(s3));//T
System.out.println(s1 ==s2);//F
第四题
Person p1 = new Person();
p1.name ="hspedu";
Person p2 = new Person();
p2.name = "hspedu";
System.out.println(p1.name.equals(p2.name));//比较内容: True
System.out.printin(p1.name ==p2.name);//T
System.out.println(p1.name =="hspedu"); //T
String s1 = new String("bcde");
String s2 = new String("bcde"");
System.out.println(s1==s2)://False
请画出内存布局图
字符串的特性
1)String是一个final类,代表不可变的字符序列
2)字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的
1.以下语句创建了几个对象?画出内存布局图,
String s1 ="hello"
s1="haha"; //创建了2个对象。
面试题
1)题1 String a = "hello"+"abc"
创建了几个对象?只有1个对象.
//老韩解读:String a="hello"+"abc";//==>优化等价 String a="helloabc”
分析1.编译器不傻,做一个优化, 判断创建的常量池对象,是否有引用指向
2.String a ="hello"+"abc";=》 String a= "helloabc";
2)题2
String a="hello"; //创建 a对象
String b ="abc";//创建 b对象
String c=a+b; 创建了几个对象?画出内存图?
//关键就是要分析 Stringc=a+b; 到底是如何执行的//一共有3对象,
老韩小结: 底层是 StringBuilder sb =new StringBuilder();sb.append(a);sb.append(b);sb是在堆中,并且append是在原来字符串的基础上追加的.
重要规则, String c1 =“ab"+“cd";常量相加,看的是池。 String c1=a+b:变量相加,是在堆中
3)题3 下面代码输出什么,并说明原因
String s1 ="hspedu";//s1 指向池中的“hspedu”
String s2 ="java"; // s2 指向池中的“java"
String s5 ="hspedujava";//s5 指向池中的“hspedujava"
String s6 =(s1 + s2).intern();//s6 指向池中的 “hspedujava"
System.out.println(s5== s6);//T
System.out.println(s5.equals(s6));//T

String 类的常见方法
说明
String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因 此java设计者还提供了StringBuilder 和 StringBuffer 来增强String的功能,并提高效率。
String 类的常见方法一览
- equals // 区分大小写,判断内容是否相等
- equalslgnoreCase //忽略大小写的判断内容是否相等
- length // 获取字符的个数,字符串的长度
- indexOf //获取字符在字符串中第1次出现的索引,索引从0开始,如果找不到,返回-1
- lastlndexOf //获取字符在字符串中最后1次出现的索引,索引从0开始,如找不到,返回-1
- substring //截取指定范围的子串
- trim //去前后空格
- charAt:获取某索引处的字符,注意不能使用Str[index] 这种方式,
public class StringMethod01 {
public static void main(String[] args) {
//1. equals 前面已经讲过了. 比较内容是否相同,区分大小写
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//
// 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
String username = "johN";
if ("john".equalsIgnoreCase(username)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
// 3.length 获取字符的个数,字符串的长度
System.out.println("韩顺平".length());
// 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从 0 开始,如果找不到,返回-1
String s1 = "wer@terwe@g";
int index = s1.indexOf('@');
System.out.println(index);// 3
System.out.println("weIndex=" + s1.indexOf("we"));//0
// 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从 0 开始,如果找不到,返回-1
s1 = "wer@terwe@g@";
index = s1.lastIndexOf('@');
System.out.println(index);//11
System.out.println("ter 的位置=" + s1.lastIndexOf("ter"));//4
// 6.substring 截取指定范围的子串
String name = "hello,张三";
//下面 name.substring(6) 从索引 6 开始截取后面所有的内容
System.out.println(name.substring(6));//截取后面的字符
//name.substring(0,5)表示从索引 0 开始截取,截取到索引 5-1=4 位置
System.out.println(name.substring(2, 5));//llo
}
}看老师的案例演示第二组String相关的方法:
toUpperCase
toLowerCase
concat
replace 替换字符串中的字符
split 分割字符串,对于某些分割字符,我们需要 转义比如|\\等
案例: String poem ="锄禾曰当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦”;和 文件路径,
compareTo //比较两个字符串的大小
toCharArray //转换成字符数组
format //格式字符串,%s 字符串 %c 字符 %d 整型 %.2f 浮点型
public class StringMethod02 { public static void main(String[] args) { // 1.toUpperCase 转换成大写 String s = "heLLo"; System.out.println(s.toUpperCase());//HELLO // 2.toLowerCase System.out.println(s.toLowerCase());//hello // 3.concat 拼接字符串 String s1 = "宝玉"; s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together"); System.out.println(s1);//宝玉林黛玉薛宝钗 together // 4.replace 替换字符串中的字符 s1 = "宝玉 and 林黛玉 林黛玉 林黛玉"; //在 s1 中,将 所有的 林黛玉 替换成薛宝钗 // 老韩解读: s1.replace() 方法执行后,返回的结果才是替换过的. // 注意对 s1 没有任何影响 String s11 = s1.replace("宝玉", "jack"); System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉 System.out.println(s11);//jack and 林黛玉 林黛玉 林黛玉 // 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等 String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦"; //老韩解读: // 1. 以 , 为标准对 poem 进行分割 , 返回一个数组 // 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \ String[] split = poem.split(","); poem = "E:\\aaa\\bbb"; split = poem.split("\\\\"); System.out.println("==分割后内容==="); for (int i = 0; i < split.length; i++) { System.out.println(split[i]); } // 6.toCharArray 转换成字符数组 s = "happy"; char[] chs = s.toCharArray(); for (int i = 0; i < chs.length; i++) { System.out.println(chs[i]); } // 7.compareTo 比较两个字符串的大小,如果前者大, // 则返回正数,后者大,则返回负数,如果相等,返回 0 // 老韩解读 // (1) 如果长度相同,并且每个字符也相同,就返回 0 // (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小 // 就返回 if (c1 != c2) { // return c1 - c2; // } // (3) 如果前面的部分都相同,就返回 str1.len - str2.len String a = "jac";// len = 3 String b = "jack";// len = 4 System.out.println(a.compareTo(b)); // 返回值是 a.len-b.len=-1 // 8.format 格式字符串 /* 占位符有: * %s 字符串 %c 字符 %d 整型 %.2f 浮点型 * */ String name = "john"; int age = 10; double score = 56.857; char gender = '男'; //将所有的信息都拼接在一个字符串. String info = "我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我! "; System.out.println(info); //老韩解读 //1. %s , %d , %.2f %c 称为占位符 //2. 这些占位符由后面变量来替换 //3. %s 表示后面由 字符串来替换 //4. %d 是整数来替换 //5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理 //6. %c 使用 char 类型来替换 String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!"; String info2 = String.format(formatStr, name, age, score, gender); System.out.println("info2=" + info2); } }
StringBuffer 类
java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删。很多方法与String相同,但StringBuffer是可变长度的。StringBuffer是一个容器

public class StringBuffer01 {
public static void main(String[] args) {
//老韩解读
//1. StringBuffer 的直接父类 是 AbstractStringBuilder
//2. StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
//3. 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final
// 该 value 数组存放 字符串内容,引出存放在堆中的
//4. StringBuffer 是一个 final 类,不能被继承
//5. 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)
// 不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
StringBuffer stringBuffer = new StringBuffer("hello");
}
}String VS StringBuffer
1)String保存的是字符串常量,里面的值不能更改,每次String类的更上就是更改地址,效率较低 //private final char valuel];
2)StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率高
//char[] value; // 这个放在堆.
StringBuffer的构造器

String 和 StringBuffer 相互转换
public class StringAndStringBuffer {
public static void main(String[] args) {
//看 String——>StringBuffer
String str = "hello tom";
//方式 1 使用构造器
//注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式 2 使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//看看 StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式 1 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
//方式 2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
}
}StringBuffer 类常见方法
public class StringBufferMethod {
public static void main(String[] args) {
StringBuffer s = new StringBuffer("hello");
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏 100true10.5"
System.out.println(s);//"hello,张三丰赵敏 100true10.5"
//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除 11~14 的字符 [11, 14)
*/
s.delete(11, 14);
System.out.println(s);//"hello,张三丰赵敏 true10.5"
//改
//老韩解读,使用 周芷若 替换 索引 9-11 的字符 [9,11)
s.replace(9, 11, "周芷若");
System.out.println(s);//"hello,张三丰周芷若 true10.5"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);//6
//插
//老韩解读,在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
s.insert(9, "赵敏");
System.out.println(s);//"hello,张三丰赵敏周芷若 true10.5"
//长度
System.out.println(s.length());//22
System.out.println(s);
}
}StringBuffer 类课堂测试题 1
public class StringBufferExercise01 {
public static void main(String[] args) {
String str = null;// ok
StringBuffer sb = new StringBuffer(); //ok
sb.append(str);//需要看源码 , 底层调用的是 AbstractStringBuilder 的 appendNull
System.out.println(sb.length());//4
System.out.println(sb);//null
//下面的构造器,会抛出 NullpointerException
StringBuffer sb1 = new StringBuffer(str);//看底层源码 super(str.length() + 16);
System.out.println(sb1);
}
}StringBuffer 类课堂测试题2
public class StringBufferExercise02 {
public static void main(String[] args) {
/*
输入商品名称和商品价格,要求打印效果示例, 使用前面学习的方法完成:
商品名 商品价格
手机 123,564.59 //比如 价格 3,456,789.88
要求:价格的小数点前面每三位用逗号隔开, 在输出。
思路分析
1. 定义一个 Scanner 对象,接收用户输入的 价格(String)
2. 希望使用到 StringBuffer 的 insert ,需要将 String 转成 StringBuffer
3. 然后使用相关方法进行字符串的处理
代码实现
*/
//new Scanner(System.in)
String price = "8123564.59";
StringBuffer sb = new StringBuffer(price);
//先完成一个最简单的实现 123,564.59
//找到小数点的索引,然后在该位置的前 3 位,插入,即可
// int i = sb.lastIndexOf(".");
// sb = sb.insert(i - 3, ",");
//上面的两步需要做一个循环处理,才是正确的
for (int i = sb.lastIndexOf(".") - 3; i > 0; i -= 3) {
sb = sb.insert(i, ",");
}
System.out.println(sb);//8,123,564.59
}
}StringBuilder 类
1)一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步(StringBuilder 不是线程安全)。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类因为在大多数实现中,它比 StringBuffer 要快 。
2)在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。
public class StringBuilder01 {
public static void main(String[] args) {
//老韩解读
//1.stringBuilder 继承 AbstractStringBuilder 类
//2.实现了 Serializable 说明stringBuilder对象是可以串行化(对象可以网络传输,可以保存到文件)
// 3.StringBuilder 是final类,不能被继承
//4.StringBuilder 对象字符序列仍然是存放在其父类 AbstractStringBuilder的 char[] value;
//因此,字符序列是堆中
//5.StringBuilder 的方法,有做互斥的处理,即没有synchronized 关键字,因此在单线程的情况下使用
// StringBuilder
StringBuilder stringBuilder = new StringBuilder();
}
}String、StringBuffer 和 StringBuilder 的比较
1)StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且方法 也一样
2)String:不可变字符序列,效率低,但是复用率高。
3)StringBuffer:可变字符序列效率较高(增删)、线程安全,看源码
4)StringBuilder:可变字符序、效率最高、线程不安全
5)String使用注意说明:
string s="a"; //创建了一个字符串
s+="b"😕/实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符 串s+="b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到盾环中,会极大影响程序的性能 => 结论:如果我们对string 做大量修改,不要使用String
String、StringBuffer 和 StringBuilder 的效率测试
public class StringVsStringBufferVsStringBuilder {
public static void main(String[] args) {
long startTime = 0L;
long endTime = 0L;
StringBuffer buffer = new StringBuffer("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuffer 拼接 20000 次
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer 的执行时间:" + (endTime - startTime));
StringBuilder builder = new StringBuilder("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuilder 拼接 20000 次
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder 的执行时间:" + (endTime - startTime));
String text = "";
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//String 拼接 20000
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String 的执行时间:" + (endTime - startTime));
}
}String、StringBuffer 和 StringBuilder 的选择
使用的原则,结论:
1.如果字符串存在大量的修改操作,一般使用 StringBuffer 或StringBuilder
2如果字符串存在大量的修改操作,并在单线程的情况,使用 StringBuilder
3如果字符串存在大量的修改操作,并在多线程的情况,吏用 StringBuffer
4.如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
StringBuilder 的方法使用和StringBuffer 一样,不再说
Math 类
Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。
public class MathMethod {
public static void main(String[] args) {
//看看 Math 常用的方法(静态方法)
//1.abs 绝对值
int abs = Math.abs(-9);
System.out.println(abs);//9
//2.pow 求幂
double pow = Math.pow(2, 4);//2 的 4 次方
System.out.println(pow);//16
//3.ceil 向上取整,返回>=该参数的最小整数(转成 double);
double ceil = Math.ceil(3.9);
System.out.println(ceil);//4.0
//4.floor 向下取整,返回<=该参数的最大整数(转成 double)
double floor = Math.floor(4.001);
System.out.println(floor);//4.0
//5.round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);
System.out.println(round);//6
//6.sqrt 求开方
double sqrt = Math.sqrt(9.0);
System.out.println(sqrt);//3.0
//7.random 求随机数
// random 返回的是 0 <= x < 1 之间的一个随机小数
// 思考:请写出获取 a-b 之间的一个随机整数,a,b 均为整数 ,比如 a = 2, b=7
// 即返回一个数 x 2 <= x <= 7
// 老韩解读 Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
// (1) (int)(a) <= x <= (int)(a + Math.random() * (b-a +1) )
// (2) 使用具体的数给小伙伴介绍 a = 2 b = 7
// (int)(a + Math.random() * (b-a +1) ) = (int)( 2 + Math.random()*6)
// Math.random()*6 返回的是 0 <= x < 6 小数
// 2 + Math.random()*6 返回的就是 2<= x < 8 小数
// (int)(2 + Math.random()*6) = 2 <= x <= 7
// (3) 公式就是 (int)(a + Math.random() * (b-a +1) )
for (int i = 0; i < 100; i++) {
System.out.println((int) (2 + Math.random() * (7 - 2 + 1)));
}
//max , min 返回最大值和最小值
int min = Math.min(1, 9);
int max = Math.max(45, 90);
System.out.println("min=" + min);
System.out.println("max=" + max);
}
}Arrays 类
Arrays 类常见方法应用案例
Arrays里面包含了一系列静态方法,用于管理或操作数组(比如排序和搜索)
1)toString 返回数组的字符串形式 Arrays.toString(arr)
2)sort 排序(自然排序和定制排序)Integer arr[]= {1,-1,7,0,89};
3)binarySearch 通过二分搜索法进行查找,要求必须排好序
int index = Arrays.binarySearch(arr, 3):
4)copyOf 数组元素的复制
Integer[] newArr = Arrays.copyOf(arr, arr.length);
5)fill 数组元素的填充
Integer[] num = new Integer[]{9,3,2};
Arrays.fill(num, 99);
6)equals 比较两个数组元素内容是否完全一致
boolean equals = Arrays.equals(arr, arr2);
7)asList 将一组值,转换成list
List<Integer> asList = Arrays.asList(2,3,4,5,6,1);
System.out.println("asList=" + asList);
import java.util.Arrays;
import java.util.Comparator;
public class ArraysMethod01 {
public static void main(String[] args) {
Integer[] integers = {1, 20, 90};
//遍历数组
// for(int i = 0; i < integers.length; i++) {
// System.out.println(integers[i]);
// }
//直接使用 Arrays.toString 方法,显示数组
// System.out.println(Arrays.toString(integers));//
//演示 sort 方法的使用
Integer arr[] = {1, -1, 7, 0, 89};
//进行排序
//老韩解读
//1. 可以直接使用冒泡排序 , 也可以直接使用 Arrays 提供的 sort 方法排序
//2. 因为数组是引用类型,所以通过 sort 排序后,会直接影响到 实参 arr
//3. sort 重载的,也可以通过传入一个接口 Comparator 实现定制排序
//4. 调用 定制排序 时,传入两个参数 (1) 排序的数组 arr
// (2) 实现了 Comparator 接口的匿名内部类 , 要求实现 compare 方法
//5. 先演示效果,再解释
//6. 这里体现了接口编程的方式 , 看看源码,就明白
// 源码分析
//(1) Arrays.sort(arr, new Comparator()
//(2) 最终到 TimSort 类的 private static <T> void binarySort(T[] a, int lo, int hi, int start, // Comparator<? super T> c)()
//(3) 执行到 binarySort 方法的代码, 会根据动态绑定机制 c.compare()执行我们传入的
// 匿名内部类的 compare ()
// while (left < right) {
// int mid = (left + right) >>> 1;
// if (c.compare(pivot, a[mid]) < 0)
// right = mid;
// else
// left = mid + 1;
// }
//(4) new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// Integer i1 = (Integer) o1;
// Integer i2 = (Integer) o2;
// return i2 - i1;
// }
// }
//(5) public int compare(Object o1, Object o2) 返回的值>0 还是 <0
// 会影响整个排序结果, 这就充分体现了 接口编程+动态绑定+匿名内部类的综合使用
// 将来的底层框架和源码的使用方式,会非常常见
//Arrays.sort(arr); // 默认排序方法
//定制排序
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return i2 - i1;
}
});
System.out.println("===排序后===");
System.out.println(Arrays.toString(arr));//
}
}import java.util.Arrays;
import java.util.Comparator;
public class ArraysSortCustom {
public static void main(String[] args) {
int[] arr = {1, -1, 8, 0, 20};
//bubble01(arr);
bubble02(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
int i1 = (Integer) o1;
int i2 = (Integer) o2;
return i2 - i1;// return i2 - i1;
}
});
System.out.println("==定制排序后的情况==");
System.out.println(Arrays.toString(arr));
}
//使用冒泡完成排序
public static void bubble01(int[] arr) {
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
//从小到大
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
//结合冒泡 + 定制
public static void bubble02(int[] arr, Comparator c) {
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
//数组排序由 c.compare(arr[j], arr[j + 1])返回的值决定
if (c.compare(arr[j], arr[j + 1]) > 0) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}import java.util.Arrays;
import java.util.List;
public class ArraysMethod02 {
public static void main(String[] args) {
Integer[] arr = {1, 2, 90, 123, 567};
// binarySearch 通过二分搜索法进行查找,要求必须排好
// 老韩解读
//1. 使用 binarySearch 二叉查找
//2. 要求该数组是有序的. 如果该数组是无序的,不能使用 binarySearch
//3. 如果数组中不存在该元素,就返回 return -(low + 1); // key not found.
int index = Arrays.binarySearch(arr, 567);
System.out.println("index=" + index);
//copyOf 数组元素的复制
// 老韩解读
//1. 从 arr 数组中,拷贝 arr.length 个元素到 newArr 数组中
//2. 如果拷贝的长度 > arr.length 就在新数组的后面 增加 null
//3. 如果拷贝长度 < 0 就抛出异常 NegativeArraySizeException
//4. 该方法的底层使用的是 System.arraycopy()
Integer[] newArr = Arrays.copyOf(arr, arr.length);
System.out.println("==拷贝执行完毕后==");
System.out.println(Arrays.toString(newArr));
//ill 数组元素的填充
Integer[] num = new Integer[]{9, 3, 2};
//老韩解读
//1. 使用 99 去填充 num 数组,可以理解成是替换原理的元素
Arrays.fill(num, 99);
System.out.println("==num 数组填充后==");
System.out.println(Arrays.toString(num));
//equals 比较两个数组元素内容是否完全一致
Integer[] arr2 = {1, 2, 90, 123};
//老韩解读
//1. 如果 arr 和 arr2 数组的元素一样,则方法 true;
//2. 如果不是完全一样,就返回 false
boolean equals = Arrays.equals(arr, arr2);
System.out.println("equals=" + equals);
//asList 将一组值,转换成 list
//老韩解读
//1. asList 方法,会将 (2,3,4,5,6,1)数据转成一个 List 集合
//2. 返回的 asList 编译类型 List(接口)
//3. asList 运行类型 java.util.Arrays#ArrayList, 是 Arrays 类的
// 静态内部类 private static class ArrayList<E> extends AbstractList<E>
// implements RandomAccess, java.io.Serializable
List asList = Arrays.asList(2, 3, 4, 5, 6, 1);
System.out.println("asList=" + asList);
System.out.println("asList 的运行类型" + asList.getClass());
}
}练习
import java.util.Arrays;
import java.util.Comparator;
public class ArrayExercise {
public static void main(String[] args) {
/*
案例:自定义 Book 类,里面包含 name 和 price,按 price 排序(从大到小)。
要求使用两种方式排序 , 有一个 Book[] books = 4 本书对象. 使用前面学习过的传递 实现 Comparator 接口匿名内部类,也称为定制排序。
[同学们完成这个即可 10min ], 可以按照 price (1)从大到小 (2)从小到大 (3) 按照书名长度从大到小
*/
Book[] books = new Book[4];
books[0] = new Book("红楼梦", 100);
books[1] = new Book("金瓶梅新", 90);
books[2] = new Book("青年文摘 20 年", 5);
books[3] = new Book("java 从入门到放弃~", 300);
//(1)price 从大到小
// Arrays.sort(books, new Comparator() {
// //这里是对 Book 数组排序,因此 o1 和 o2 就是 Book 对象
// @Override
// public int compare(Object o1, Object o2) {
// Book book1 = (Book) o1;
// Book book2 = (Book) o2;
// double priceVal = book2.getPrice() - book1.getPrice();
// //这里老师进行了一个转换
// //如果发现返回结果和我们输出的不一致,就修改一下返回的 1 和 -1
// if(priceVal > 0) {
// return 1;
// } else if(priceVal < 0) {
// return -1;
// } else {
// return 0;
// }
// }
// });
//(2)price 从小到大
// Arrays.sort(books, new Comparator() {
// //这里是对 Book 数组排序,因此 o1 和 o2 就是 Book 对象
// @Override
// public int compare(Object o1, Object o2) {
// Book book1 = (Book) o1;
// Book book2 = (Book) o2;
// double priceVal = book2.getPrice() - book1.getPrice();
// //这里老师进行了一个转换
// //如果发现返回结果和我们输出的不一致,就修改一下返回的 1 和 -1
// if(priceVal > 0) {
// return -1;
// } else if(priceVal < 0) {
// return 1;
// } else {
// return 0;
// }
// }
// });
//(3)按照书名长度从大到小
Arrays.sort(books, new Comparator() {
//这里是对 Book 数组排序,因此 o1 和 o2 就是 Book 对象
@Override
public int compare(Object o1, Object o2) {
Book book1 = (Book) o1;
Book book2 = (Book) o2;
//要求按照书名的长度来进行排序
return book2.getName().length() - book1.getName().length();
}
});
System.out.println(Arrays.toString(books));
}
}
class Book {
private String name;
private double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}System 类
System 类常见方法和案例
1)exit 退出当前程序
2)arraycopy:复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组
int[] src={1,2,3};
int[] dest = new int[3];
System.arraycopy(src, 0, dest, 0, 3);
3)currentTimeMillens:返回当前时间距离1970-1-1 的毫秒数
4)gc:运行垃圾回收机制 System.gc();
import java.util.Arrays;
public class System_ {
public static void main(String[] args) {
//exit 退出当前程序
// System.out.println("ok1");
// //老韩解读
// //1. exit(0) 表示程序退出
// //2. 0 表示一个状态 , 正常的状态
// System.exit(0);//
// System.out.println("ok2");
//arraycopy :复制数组元素,比较适合底层调用,
// 一般使用 Arrays.copyOf 完成复制数组
int[] src = {1, 2, 3};
int[] dest = new int[3];// dest 当前是 {0,0,0}
//老韩解读
//1. 主要是搞清楚这五个参数的含义
//2. // 源数组
// * @param src the source array. // srcPos: 从源数组的哪个索引位置开始拷贝
// * @param srcPos starting position in the source array. // dest : 目标数组,即把源数组的数据拷贝到哪个数组
// * @param dest the destination array. // destPos: 把源数组的数据拷贝到 目标数组的哪个索引
// * @param destPos starting position in the destination data. // length: 从源数组拷贝多少个数据到目标数组
// * @param length the number of array elements to be copied. System.arraycopy(src, 0, dest, 0, src.length);
// int[] src={1,2,3};
System.out.println("dest=" + Arrays.toString(dest));//[1, 2, 3]
//currentTimeMillens:返回当前时间距离 1970-1-1 的毫秒数
// 老韩解读:
System.out.println(System.currentTimeMillis());
}
}BigInteger 和 BigDecimal 类
应用场景:
1)BigInteger适合保存比较大的整型
2)BigDecimal适合保存精度更高的浮点型(小数)
BigInteger 和 BigDecimal 常见方法
1)add 加
2)subtract减
3)multiply乘
4)divide除
import java.math.BigInteger;
public class BigInteger_ {
public static void main(String[] args) {
//当我们编程中,需要处理很大的整数,long 不够用
//可以使用 BigInteger 的类来搞定
// long l = 23788888899999999999999999999l;
// System.out.println("l=" + l);
BigInteger bigInteger = new BigInteger("23788888899999999999999999999");
BigInteger bigInteger2 = new BigInteger("10099999999999999999999999999999999999999999999999999999999999999999999999999999999");
System.out.println(bigInteger);
//老韩解读
//1. 在对 BigInteger 进行加减乘除的时候,需要使用对应的方法,不能直接进行 + - * /
//2. 可以创建一个 要操作的 BigInteger 然后进行相应操作
BigInteger add = bigInteger.add(bigInteger2);
System.out.println(add);//
BigInteger subtract = bigInteger.subtract(bigInteger2);
System.out.println(subtract);//减
BigInteger multiply = bigInteger.multiply(bigInteger2);
System.out.println(multiply);//乘
BigInteger divide = bigInteger.divide(bigInteger2);
System.out.println(divide);//除
}
}import java.math.BigDecimal;
public class BigDecimal_ {
public static void main(String[] args) {
//当我们需要保存一个精度很高的数时,double 不够用
//可以是 BigDecimal
// double d = 1999.11111111111999999999999977788d;
// System.out.println(d);
BigDecimal bigDecimal = new BigDecimal("1999.11");
BigDecimal bigDecimal2 = new BigDecimal("3");
System.out.println(bigDecimal);
//老韩解读
//1. 如果对 BigDecimal 进行运算,比如加减乘除,需要使用对应的方法
//2. 创建一个需要操作的 BigDecimal 然后调用相应的方法即可
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal.subtract(bigDecimal2));
System.out.println(bigDecimal.multiply(bigDecimal2));
//System.out.println(bigDecimal.divide(bigDecimal2));//可能抛出异常 ArithmeticException
//在调用 divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
//如果有无限循环小数,就会保留 分子 的精度
System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
}
}日期类
第一代日期类
1)Date:精确到毫秒,代表特定的瞬间
2)SimpleDateFormat:格式和解析日期的类SimpleDateFormat 格式化和解析日期的具体类。它允许进行格式化(日期->文本)解析(文本 ->日期)和规范化

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Date01 {
public static void main(String[] args) throws ParseException {
//老韩解读
//1. 获取当前系统时间
//2. 这里的 Date 类是在 java.util 包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1);
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
System.out.println("d2=" + d2); //获取某个时间对应的毫秒数
//
//老韩解读
//1. 创建 SimpleDateFormat 对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);
//老韩解读
//1. 可以把一个格式化的 String 转成对应的 Date
//2. 得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常
String s = "1996 年 01 月 01 日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));
}
}第二代日期类

import java.util.Calendar;
public class Calendar_ {
public static void main(String[] args) {
//老韩解读
//1. Calendar 是一个抽象类, 并且构造器是 private
//2. 可以通过 getInstance() 来获取实例
//3. 提供大量的方法和字段提供给程序员
//4. Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
//5. 如果我们需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为 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));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
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));
}
}第三代日期类
前面两代日期类的不足分析 JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar也存在问题是:
1)可变性:像日期和时间这样的类应该是不可变的。
2)偏移性:Date中的年份是从1900开始的,而月份都从0开始
3)格式化:格式化只对Date有用,Calendar则不行。
4)此外,它们也不是线程安全的;不能处理闰秒等(每隔2天,多出1s)
1)LocalDate(日期/年月曰)、LocalTime(时间/时分秒)、LocalDateTime(日期时 间/年月日时分秒)JDK8加入
LocalDate只包含日期,,可以获取日期字段
LocalTime只包含时间,可以获取时间字段
LocalDateTime包含日期+时间,可以获取日期和时间字段
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
public class LocalDate_ {
public static void main(String[] args) {
//第三代日期
//老韩解读
//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
DateTimeFormat dtf= DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);
Instant 时间戳
类似于Date
提供了一系列和Date类转换的方式
Instant-->Date:
Date date = Date.from(instant);
Date-->Instant:
Instant instant = date.tolnstant(),
import java.time.Instant;
import java.util.Date;
public class Instant_ {
public static void main(String[] args) {
//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();
}
}第三代日期类更多方法
LocalDateTime类
MonthDay类:检查重复事件
是否是闰年
增加日期的某个部分
使用plus方法测试增加时间的某个部分
使用minus方法测试查看一年前和一年后的日期
集合
数组

集合
1 可以动态的保存任意多个对象,使用方便
2 提供了一系列方法的操作对象的方法, add remove set get 等
3 使用了集合添加删除新元素的代码 ,简洁
集合框架体系图


集合主要是两组(单列集合 , 双列集合)
Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
Map 接口的实现子类 是双列集合,存放的 K-V
Collection 接口和常用方法
1 collection 实现子类可以存放多个元素,每个元素可以是Object
2 有些collection的实现类,可以存放重复的元素,有些不可以
3 有些collection 的实现类, 有些是有序的(List),有些不是有序的(Set)
4 Collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的
import java.util.ArrayList;
import java.util.List;
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
// 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 实现类来演示. }
}
}Collection 接口遍历元素方式 1-使用 Iterator(迭代器)
1)lterator对象称为迭代器,主要用于遍历 Collection 集合中的元素。
2)所有实现了Collection接口的集合类都有一个iterator()方法,用以返回-个实现了lterator接口的对象,即可以返回一个选代器。
3)lterator 仅用于遍历集合,Iterator 本身井不存放对象
迭代器的执行原理
Iterator iterator = coll.iterator();//得到-个集合的选代器
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next()作用:1.下移 2.将下移以后集合位置上的元素返回
System.out.printIn(iterator:next());}
老韩提示:在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
//System.out.println("col=" + col);
//现在老师希望能够遍历 col 集合
//1. 先得到 col 对应的 迭代器
Iterator iterator = col.iterator();
//2. 使用 while 循环遍历
// while (iterator.hasNext()) {//判断是否还有数据
// //返回下一个元素,类型是 Object
// Object obj = iterator.next();
// System.out.println("obj=" + obj);
// }
//老师教大家一个快捷键,快速生成 while => itit
//显示所有的快捷键的的快捷键 ctrl + j
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
// iterator.next();//NoSuchElementException
//4. 如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}Collection 接口遍历对象方式 2-for 循环增强
增强for循环,可以代替iterator选代器,特点:增强for就是简化版的iterator本质一样。只能用于遍历集合或数组。
基本语法
for(元素类型 元素名:集合名或数组名)
for (Object object : col) {System.out.printin(object);}
课堂练习
请编写程序 CollectionExercise,java
1.创建 3个 Dog {name, age}对象,放入到 ArrayList 中,赋给 List 引用
2.用选代器和增强for循环两种方式来遍历
3.重写Dog 的toString方法,输出name和age
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionExercise {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Dog("小黑", 3));
list.add(new Dog("大黄", 100));
list.add(new Dog("大壮", 8));
//先使用 for 增强
for (Object dog : list) {
System.out.println("dog=" + dog);
}
//使用迭代器
System.out.println("===使用迭代器来遍历===");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object dog = iterator.next();
System.out.println("dog=" + dog);
}
}
}
/**
* 创建 3 个 Dog {name, age} 对象,放入到 ArrayList 中,赋给 List 引用
* 用迭代器和增强 for 循环两种方式来遍历
* 重写 Dog 的 toString 方法, 输出 name 和 age
*/
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}List 接口和常用方法
List 接口基本介绍
List 接口是 Collection 接囗的子接口
1)List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
2)List集合中的每个元素都有其对应的顺序索引,即支持索引。
3)List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
import java.util.ArrayList;
import java.util.List;
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("hsp");
list.add("tom");
System.out.println("list=" + list);
//2. List 集合中的每个元素都有其对应的顺序索引,即支持索引
// 索引是从 0 开始的
System.out.println(list.get(3));//hsp
//3. }
}
}List 接口的常用方法
import java.util.ArrayList;
import java.util.List;
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
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 = new ArrayList();
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);
}
}List 接口课堂练习
添加10个以上的元素(比如String "helo"),在2号位插入一个元素"韩顺平教育",获得第5个元素,删除第6个元素,修改第7个元素,在使用选代器遍历集合,要求:使用List的实现类ArrayList完成。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListExercise {
@SuppressWarnings({"all"})
public static void main(String[] args) {
/*
添加 10 个以上的元素(比如 String "hello" ),在 2 号位插入一个元素"韩顺平教育",
获得第 5 个元素,删除第 6 个元素,修改第 7 个元素,在使用迭代器遍历集合,韩顺平循序渐进学 Java 零基础
第 618页
要求:使用 List 的实现类 ArrayList 完成。
*/
List list = new ArrayList();
for (int i = 0; i < 12; i++) {
list.add("hello" + i);
}
System.out.println("list=" + list);
//在 2 号位插入一个元素"韩顺平教育"
list.add(1, "韩顺平教育");
System.out.println("list=" + list);
//获得第 5 个元素
System.out.println("第五个元素=" + list.get(4));
//删除第 6 个元素
list.remove(5);
System.out.println("list=" + list);
//修改第 7 个元素
list.set(6, "三国演义");
System.out.println("list=" + list);
//在使用迭代器遍历集合
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
}
}List 的三种遍历方式 [ArrayList, LinkedList,Vector]
1)方式一:使用iterator
lterator iter = col.iterator();
while(iter.hasNext()){Object o = iter.next();
2)方式二:使用增强forfor(Object o:col){}
3)方式三:使用普通for
fon(int i=0;i<list.size();i+ +)
说明:使用LinkedList完成 使用方式和ArrayList 一样
import java.util.*;
public class ListFor {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//List 接口的实现子类 Vector LinkedList
//List list = new ArrayList();
//List list = new Vector();
List list = new LinkedList();
list.add("jack");
list.add("tom");
list.add("鱼香肉丝");
list.add("北京烤鸭子");
//遍历
//1. 迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
System.out.println("=====增强 for=====");
//2. 增强 for
for (Object o : list) {
System.out.println("o=" + o);
}
System.out.println("=====普通 for====");
//3. 使用普通 for
for (int i = 0; i < list.size(); i++) {
System.out.println("对象=" + list.get(i));
}
}
}ArrayList 底层结构和源码分析
ArrayList 的注意事项
1)permits all elements, including null ,ArrayList 可以加入null,并且多个
2)ArrayList 是由数组来实现数据存储的
3)ArrayList 基本等同于Vector,除了 ArrayList是线程不安全(执行效率高) 看源码。在多线程情况下,不建议使用ArrayList
ArrayList 的底层操作机制源码分析(重点,难点.)
1)ArrayList中维护了一个Object类型的数组elementData.[debug 看源码]
transient Object[] elementData; //transient 表示瞬间,短暂的,表示该属性不会被序列化
2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
3)如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容则直接扩容elementData为1.5倍。
老师建议:自己去debug 一把我们的ArrayList的创建和扩容的流程
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class ArrayListSource {
public static void main(String[] args) {
//老韩解读源码
//注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据
//需要做设置. //使用无参构造器创建 ArrayList 对象
ArrayList list = new ArrayList();
// ArrayList list = new ArrayList(8);
//使用 for 给 list 集合添加 1-10 数据
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//使用 for 给 list 集合添加 11-15 数据
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
}
}
Vector底层结构和源码剖析
Vector的基本介绍
1)Vector底层也是一个对象数组,protected Object[]elementData;
2)Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized
3)在开发中,需要线程同步安全时,考虑使用Vector
import java.util.Vector;
@SuppressWarnings({"all"})
public class Vector_ {
public static void main(String[] args) {
//无参构造器
//有参数的构造
Vector vector = new Vector(8);
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector=" + vector);
//老韩解读源码
//1. new Vector() 底层
/*
public Vector() {
this(10);
}
补充:如果是 Vector vector = new Vector(8);
走的方法:
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
2. vector.add(i)
2.1 //下面这个方法就添加数据到 vector 集合
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
//newCapacity = oldCapacity + ((capacityIncrement > 0) ?
// capacityIncrement : oldCapacity);
//就是扩容两倍. private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
*/
}
}Vector 和 ArrayList 的比较

LinkedList 底层结构
LinkedList 的全面说明
1)LinkedList底层实现了双向链表和双端队列特点
2)可以添加任意元素(元素可以重复),包括null
3)线程不安全,没有实现同步
LinkedList 的底层操作机制

public class LinkedList01 {
public static void main(String[] args) {
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node hsp = new Node("老韩");
//连接三个结点,形成双向链表
//jack -> tom -> hsp
jack.next = tom;
tom.next = hsp;
//hsp -> tom -> jack
hsp.pre = tom;
tom.pre = jack;
Node first = jack;//让 first 引用指向 jack,就是双向链表的头结点
Node last = hsp; //让 last 引用指向 hsp,就是双向链表的尾结点
//演示,从头到尾进行遍历
System.out.println("===从头到尾进行遍历===");
while (true) {
if (first == null) {
break;
}
//输出 first 信息
System.out.println(first);
first = first.next;
}
//演示,从尾到头的遍历
System.out.println("====从尾到头的遍历====");
while (true) {
if (last == null) {
break;
}
//输出 last 信息
System.out.println(last);
last = last.pre;
}
//演示链表的添加对象/数据,是多么的方便
//要求,是在 tom --------- 老韩直接,插入一个对象 smith
//1. 先创建一个 Node 结点,name 就是 smith
Node smith = new Node("smith");
//下面就把 smith 加入到双向链表了
smith.next = hsp;
smith.pre = tom;
hsp.pre = smith;
tom.next = smith;
//让 first 再次指向 jack
first = jack;//让 first 引用指向 jack,就是双向链表的头结点
System.out.println("===从头到尾进行遍历===");
while (true) {
if (first == null) {
break;
}
//输出 first 信息
System.out.println(first);
first = first.next;
}
last = hsp; //让 last 重新指向最后一个结点
//演示,从尾到头的遍历
System.out.println("====从尾到头的遍历====");
while (true) {
if (last == null) {
break;
}
//输出 last 信息
System.out.println(last);
last = last.pre;
}
}
}
//定义一个 Node 类,Node 对象 表示双向链表的一个结点
class Node {
public Object item; //真正存放数据
public Node next; //指向后一个结点
public Node pre; //指向前一个结点
public Node(Object name) {
this.item = name;
}
public String toString() {
return "Node name=" + item;
}
}LinkedList 的增删改查案例
import java.util.Iterator;
import java.util.LinkedList;
@SuppressWarnings({"all"})
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList=" + linkedList);
//演示一个删除结点的
linkedList.remove(); // 这里默认删除的是第一个结点
//linkedList.remove(2);
System.out.println("linkedList=" + linkedList);
//修改某个结点对象
linkedList.set(1, 999);
System.out.println("linkedList=" + linkedList);
//得到某个结点对象
//get(1) 是得到双向链表的第二个对象
Object o = linkedList.get(1);
System.out.println(o);//999
//因为 LinkedList 是 实现了 List 接口, 遍历方式
System.out.println("===LinkeList 遍历迭代器====");
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
System.out.println("===LinkeList 遍历增强 for====");
for (Object o1 : linkedList) {
System.out.println("o1=" + o1);
}
System.out.println("===LinkeList 遍历普通 for====");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
//老韩源码阅读.
/* 1. LinkedList linkedList = new LinkedList();
public LinkedList() {
}
2. 这时 linkeList 的属性 first = null last = null
3. 执行 添加
public boolean add (E e){
linkLast(e);
return true;
}
4. 将新的结点,加入到双向链表的最后
void linkLast (E e){
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
*/
/*
老韩读源码 linkedList.remove(); // 这里默认删除的是第一个结点
1. 执行 removeFirst
public E remove() {
return removeFirst();
}韩顺平循序渐进学 Java 零基础
第 638页
2. 执行
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
*/
}
}ArrayList 和 LinkedList 比较

Set 接口和常用方法
Set 接口基本介绍
1无序(添加和取出的顺序不一致),没有索引
2)不允许重复元素,所以最多包含一个null
Set 接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Colection接口的子接口,
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取
Set 接口的常用方法举例
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@SuppressWarnings({"all"})
public class SetMethod {
public static void main(String[] args) {
//老韩解读
//1. 以 Set 接口的实现类 HashSet 来讲解 Set 接口的方法
//2. set 接口的实现类的对象(Set 接口对象), 不能存放重复的元素, 可以添加一个 null
//3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
//4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定.
Set set = new HashSet();
set.add("john");
set.add("lucy");
set.add("john");//重复
set.add("jack");
set.add("hsp");
set.add("mary");
set.add(null);//
set.add(null);//再次添加 null
for (int i = 0; i < 10; i++) {
System.out.println("set=" + set);
}
//遍历
//方式 1: 使用迭代器
System.out.println( "=====使用迭代器====");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
set.remove(null);
//方式 2: 增强 for
System.out.println("=====增强 for====");
for (Object o : set) {
System.out.println("o=" + o);
}
//set 接口对象,不能通过索引来获取
}
}Set 接口实现类-HashSet
HashSet 的全面说明
1)Hashset实现了Set接口
2)HashSet实际上是HashMap,看下源码. public Hashset(){map = new HashMap<>();}
3)可以存放null值,但是只能有一个null
4)HashSet不保证元素是有序的,取决于hash后,再确定索引的结果。(即,不保证存放元素的顺序和取出顺序一致)
5)不能有重复元素/对象.在前面Set 接口使用已经讲过
import java.util.HashSet;
import java.util.Set;
@SuppressWarnings({"all"})
public class HashSet_ {
public static void main(String[] args) {
//老韩解读
//1. 构造器走的源码
/*
public HashSet() {
map = new HashMap<>();
}
2. HashSet 可以存放 null ,但是只能有一个 null,即元素不能重复
*/
Set hashSet = new HashSet();
hashSet.add(null);
hashSet.add(null);
System.out.println("hashSet=" + hashSet);
}
}HashSet 案例说明
import java.util.HashSet;
@SuppressWarnings({"all"})
public class HashSet01 {
public static void main(String[] args) {
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 底层机制说明
分析HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
分析Hashset的添加元素底层是如何实现(hash()+equals())

import java.util.HashSet;
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
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 比较后相同
//就不能加入韩顺平循序渐进学 Java 零基础
第 650页
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;
}
*/
}
}
import java.util.HashSet;
@SuppressWarnings({"all"})
public class HashSetIncrement {
public static void main(String[] args) {
/*
HashSet 底层是 HashMap, 第一次添加时,table 数组扩容到 16,
临界值(threshold)是 16*加载因子(loadFactor)是 0.75 = 12
如果 table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,韩顺平循序渐进学 Java 零基础
第 653页
新的临界值就是 32*0.75 = 24, 依次类推
*/
HashSet hashSet = new HashSet();
// for(int i = 1; i <= 100; i++) {
// hashSet.add(i);//1,2,3,4,5...100
// }
/*
在 Java8 中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64),就会进行树化(红黑树), 否则仍然采用数组扩容机制
*/
// for(int i = 1; i <= 12; i++) {
// hashSet.add(new A(i));//
// }
/*
当我们向 hashset 增加一个元素,-> Node -> 加入 table , 就算是增加了一个 size++
*/
for (int i = 1; i <= 7; i++) {//在 table 的某一条链表上添加了 7 个 A 对象
hashSet.add(new A(i));
}
for (int i = 1; i <= 7; i++) {//在 table 的另外一条链表上添加了 7 个 B 对象
hashSet.add(new B(i));//
}
}
}
class B {
private int n;
public B(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 200;
}
}
class A {
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 100;
}
}Set 接口实现类-LinkedHashSet
LinkedHashSet 的全面说明
1)LinkedHashSet是HashSet 的子类
- LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个 数组+ 双向链表
3)LinkedHashSet 根据元素的 hashcode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的
4)LinkedHashset 不允许添重复元素

Map 接口和常用方法
Map 接口实现类的特点 [很实用]
1)Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
2)Map 中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
3)Map 中的 key 不允许重复,原因和HashSet 一样,前面分析过源码
4)Map 中的 value 可以重复
5)Map 的key 可以为 null, value 也可以为null ,注意 key 为null, 只能有一个,value 为null ,可以多个.
6)常用String类作为Map的 key
- key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings({"all"})
public class Map_ {
public static void main(String[] args) {
//老韩解读 Map 接口实现类的特点, 使用实现类 HashMap
//1. Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
//2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
//3. Map 中的 key 不允许重复,原因和 HashSet 一样,前面分析过源码. //4. Map 中的 value 可以重复
//5. Map 的 key 可以为 null, value 也可以为 null ,注意 key 为 null,
// 只能有一个,value 为 null ,可以多个
//6. 常用 String 类作为 Map 的 key
//7. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put("no1", "张三丰");//当有相同的 k , 就等价于替换. map.put("no3", "张三丰");//k-v
map.put(null, null); //k-v
map.put(null, "abc"); //等价替换
map.put("no4", null); //k-v
map.put("no5", null); //k-v
map.put(1, "赵敏");//k-v
map.put(new Object(), "金毛狮王");//k-v
// 通过 get 方法,传入 key ,会返回对应的 value
System.out.println(map.get("no2"));//张无忌
System.out.println("map=" + map);
}
}
Map 接口常用方法
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings({"all"})
public class MapMethod {
public static void main(String[] args) {
//演示 map 接口常用方法
Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");//替换-> 一会分析源码
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put("鹿晗", "关晓彤");//OK
map.put("hsp", "hsp 的老婆");
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val=" + val);
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty());//F
// clear:清除 k-v
//map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("hsp"));//T
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}Map 接口遍历方法
containsKey:查找键是否存在
keySet:获取所有的键
entrySet:获取所有关系k-v
values:获取所有的值
import java.util.*;
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();
//(1) 增强 for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}Map 接口课堂练习
使用HashMap添加3个员工对象,要求
键:员工id
值:员工对象
并遍历显示工资>18000的员工(遍历方式最少两种
员工类:姓名、工资、员工id
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class MapExercise {
public static void main(String[] args) {
//完成代码
Map hashMap = new HashMap();
//添加对象
hashMap.put(1, new Emp("jack", 300000, 1));
hashMap.put(2, new Emp("tom", 21000, 2));
hashMap.put(3, new Emp("milan", 12000, 3));
//遍历 2 种方式
//并遍历显示工资>18000 的员工(遍历方式最少两种)
//1. 使用 keySet -> 增强 for
Set keySet = hashMap.keySet();
System.out.println("====第一种遍历方式====");
for (Object key : keySet) {
//先获取 value
Emp emp = (Emp) hashMap.get(key);
if (emp.getSal() > 18000) {
System.out.println(emp);
}
}
//2. 使用 EntrySet -> 迭代器
// 体现比较难的知识点
// 慢慢品,越品越有味道.
Set entrySet = hashMap.entrySet();
System.out.println("======迭代器======");
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
//通过 entry 取得 key 和 value
Emp emp = (Emp) entry.getValue();
if (emp.getSal() > 18000) {
System.out.println(emp);
}
}
}
}
/**
* 使用 HashMap 添加 3 个员工对象,要求
* 键:员工 id
* 值:员工对象
* <p>
* 并遍历显示工资>18000 的员工(遍历方式最少两种)
* 员工类:姓名、工资、员工 id
*/
class Emp {
private String name;
private double sal;
private int id;
public Emp(String name, double sal, int id) {
this.name = name;
this.sal = sal;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", sal=" + sal +
", id=" + id +
'}';
}
}Map 接口实现类-HashMap
HashMap 小结
1)Map接口的常用实现类:HashMap、Hashtable和Properties。
2)HashMap是 Map 接口使用频率最高的实现类。
3)HashMap 是以 key-val 对的方式来存储数据(HashMap$Node类型)[案例 Entry]
4)key 不能重复,但是值可以重复,允许使用null键和null值。
5)如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key不会替换,val会替换)
6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.(idk8的hashMap 底层 数组+链表+红黑树)
7)HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
HashMap 底层机制及源码剖析

扩容机制[和HashSet相同]
1)HashMap底层维护了Node类型的数组table,默认为null
2)当创建对象时,将加载因子(loadfactor)初始化为0.75.
3)当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素.如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的key相是否等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
4)第1次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)
5)以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,依次类推
6)在Java8中,如果一条链表的元素个数超过 TREEIFY THRESHOLD(默认是8),并且table的大小>= MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)
import java.util.HashMap;
@SuppressWarnings({"all"})
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);//ok
map.put("php", 10);//ok
map.put("java", 20);//替换 value
System.out.println("map=" + map);//
/*老韩解读 HashMap 的源码+图解
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行 put 调用 hash 方法,计算 key 的 hash 值 (h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3. 执行 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 数组为 null, 或者 length =0 , 就扩容到 16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v
//, 创建成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;//辅助变量
// 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,
// 并 满足(table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真)
// 就认为不能加入新的 k-v
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处
理
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果找到的结点,后面是链表,就循环比较
for (int binCount = 0; ; ++binCount) {//死循环
if ((e = p.next) == null) {//如果整个链表,没有和他相同,就加到该链表的最后
p.next = newNode(hash, key, value, null);
//加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后
//就调用 treeifyBin 方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && //如果在循环比较过程中,发现有相同,就 break,就只是替换 value
((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; //替换,key 对应 value
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个 Node ,就 size++
if (++size > threshold[12-24-48])//如 size > 临界值,就扩容
resize();
afterNodeInsertion(evict);
return null;
}
5. 关于树化(转成红黑树)
//如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容. //否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
*/
}
}Map 接口实现类-Hashtable
HashTable 的基本介绍
1)存放的元素是键值对: 即 K-V
2)hashtable的键和值都不能为null,否则会抛出NulPointerException
3)hashTable 使用方法基本上和HashMap一样
4)hashTable 是线程安全的(synchronized),hashMap 是线程不安全的
简单说一下Hashtable的底层
1 底层有数粗Hashtable$Entry[] 初始化大小为11
2临界值 threshold 8 = 11 * 0.75
3 扩容 按照自己的扩容机制进行扩容
4.执行 方法 addEntry(hash,key,value,index);添加K-V 封装到Entry
5.当 if(count >= threshold)满足时,就进行扩容
6.按照 int newcapacity=(oldcapacity<< 1)+1;的大小扩容
Hashtable 和 HashMap 对比

Map 接口实现类-Properties
1.Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据。
2.他的使用特点和Hashtable类似
3.Properties 还可以用于 从 xxx.properties 文件中,加载数据到Properties类对象并进行读取和修改
4.说明: 工作后 xxx.properties 文件通常作为配置文件
import java.util.Properties;
@SuppressWarnings({"all"})
public class Properties_ {
public static void main(String[] args) {
//老韩解读
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
//增加
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
//通过 k 获取对应值
System.out.println(properties.get("lic"));//88
//删除
properties.remove("lic");
System.out.println("properties=" + properties);
//修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);
}
}总结-开发中如何选择集合实现类(记住)

泛型
传统方法的问题分析
1 不等你对加入到集合ArrayList中的数据类型进行约束
2 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
泛型快速体验
ArrayList<Dog> arrayList = new ArrayList<Dog>();泛型的理解和好处
1 编译时, 检查添加元素的类型,提高了安全性
2 减少了类型转换的次数,提高了效率
泛型介绍
1 泛型又称为参数化类型,是jdk5出来的性特性,解决数据类型的安全性问题
2 在类声明或者实例化时只要指定好需要的具体的类型即可
3 java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常,同时代码更加简洁
4 泛型的作用是:可以在类声明时通过一个表示类中 某个属性的类型,或者某个方法的返回值类型,或者是参数类型。
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
class Person<E> {
E s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
public Person(E s) {//E 也可以是参数类型
this.s = s;
}
public E f() {//返回类型使用 E
return s;
}
public void show() {
System.out.println(s.getClass());//显示 s 的运行类型
}
Person<Integer> person2 = new Person<Integer>(100);
person2.show();//Integer泛型的语法
泛型声明
interface 接口 <T> {} class类 <K,V>{}说明
1 KTV不代表值,而是表示类型
2 任意字母都可以,常用T
泛型的实例化
List <String> str = new ArrayList<String>();泛型使用的注意事项和细节
1 T E只能是引用类型
2 在给泛型指定具体类型后,可以传入该类型或者其子类类型
3 泛型使用形式
List <Integer> list1 = new ArrayList<Integer>();
List <Integer> list1 = new ArrayList<>(); 推荐4 不指定泛型 默认为Object
List list3 = new ArrayList();
等价于
List<Object> list4 = new ArrayList<>();练习
题目
定义Employee类
1)该类包含:private成员变量name,sal,birthday,其中 birthday 为 MyDate 类的对象
2)为每一个属性定义 getter; setter 方法;
3)重写 toString 方法输出 name, sal, birthday
4)MyDate类包含: private成员变量month,day,year;并为每一个属性定义 gettersetter 方法;
5)创建该类的 3 个对象,并把这些对象放入 ArrayList 集合中(ArrayList 需使用泛型来定义),对集合中的元素进行排序,并遍历输出: 排序方式: 调用ArrayList 的 sort 方法,传入 Comparator对象[使用泛型],先按照name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】
package com.yb.exec;
import java.util.ArrayList;
import java.util.Comparator;
public class main {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("wangwu",2000,new MyData(40,4,2024)));
employees.add(new Employee("lisi",2000,new MyData(2,1,2022)));
employees.add(new Employee("zhangsan",2000,new MyData(23,4,2032)));
System.out.println(employees);
System.out.println("排序");
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee emp1, Employee emp2) {
int i = emp1.getName().compareTo(emp2.getName());
if (i!=0){
return i;
}
return emp1.getBirthday().compareTo(emp2.getBirthday());
}
});
System.out.println(employees);
}
} package com.yb.exec;
public class MyData implements Comparable<MyData> {
private int day;
private int month;
private int year;
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public MyData(int day, int month, int year) {
this.day = day;
this.month = month;
this.year = year;
}
@Override
public String toString() {
return "MyData{" +
"day=" + day +
", month=" + month +
", year=" + year +
'}';
}
@Override
public int compareTo(MyData o) {
int year1 = year - o.getYear();
if (year1!=0){
return year1;
}
int month1 = month - o.getMonth();
if (month1!=0){
return month1;
}
return day - o.getDay();
}
}package com.yb.exec;
import javax.xml.crypto.Data;
public class Employee {
private String name;
private double sal;
private MyData birthday;
public Employee(String name, double sal, MyData birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyData getBirthday() {
return birthday;
}
public void setBirthday(MyData birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "\nEmployee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}自定义泛型类
基本语法
class 类名 <T,R...> { // ... 表示可以有多个泛型
成员
}注意细节
1 普通成员可以使用泛型(属性,方法)
2 使用泛型的数组,不能初始化(不能确定类型,就不能开辟空间)
3 静态方法中不能使用类的泛型
因为静态方法是和类的加载相关的,在类的加载时,对象还没有创建,所以,如果静态方法或者静态属性使用了泛型,jvm无法进行初始化
4 泛型类的类型,是在创建对象时确定的,(因为创建对象时,需要指定确定类型)
5 如果在创建对象时,没有指定类型,默认为Object
基本语法
interface 接口名 <T,R...>{}
注意细节
1 接口中,静态成员也不能使用泛型
2 泛型接口类型,在继承接口或者实现接口时确定
3 没有指定类型,默认为Object
自定义泛型方法
基础语法
修饰符<T,R...> 返回类型 方法名(参数列表){}
注意细节
1 泛型方法,可以定义在普通类中,也可以定义在泛型类中
2 当泛型方法被调用时,类型会确定
3 public void eat( E e){}修饰符后没有<T,R...> eat方法不是泛型方法,而是使用了泛型
泛型的继承和通配符
说明
1 泛型不具备继承性
List <Object> list = new ArrayList<String> () ; 错误
2 <?>:支持任意泛型类型
3 <? extends A> : 支持A类以及A类的子类,规定了泛型的上限
4 <? super A > : 支持A 类以及A类的父类,不限于直接A类,规定了泛型的下限坦克大战【1】
多线程基础
线程相关概念
程序(program)
是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码
进程
1.进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
2.进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
什么是线程
1.线程由进程创建的,是进程的一个实体
2.一个进程可以拥有多个线程
其他相关概念
1.单线程:同一个时刻,只允许执行一个线程
2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件

线程基本使用
创建线程的两种方式

线程应用案例 1-继承 Thread 类

public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建 Cat 对象,可以当做线程使用
Cat cat = new Cat();
//老韩读源码
/*
(1)
public synchronized void start() {
start0();
}
(2)
//start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
//真正实现多线程的效果, 是 start0(), 而不是 run
private native void start0();
*/
cat.start();//启动线程-> 最终会执行 cat 的 run 方法
//cat.run();//run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行
//说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
//这时 主线程和子线程是交替执行..
System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字 main
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
//让主线程休眠
Thread.sleep(1000);
}
}
}
//老韩说明
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run 方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的 run 方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread {
int times = 0;
@Override
public void run() {//重写 run 方法,写上自己的业务逻辑
while (true) {
//该线程每隔 1 秒。在控制台输出 “喵喵, 我是小猫咪”
System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
//让该线程休眠 1 秒 ctrl+alt+t
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break;//当 times 到 80, 退出 while, 这时线程也就退出.. }
}
}
}
}
线程应用案例 2-实现 Runnable 接口
说明.java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
请编写程序,该程序可以每隔1秒。在控制台输出“hi!”,当输出10次后,自动退出
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start(); 这里不能调用 start
//创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
Thread thread = new Thread(dog);
thread.start();
// Tiger tiger = new Tiger();//实现了 Runnable
// ThreadProxy threadProxy = new ThreadProxy(tiger);
// threadProxy.start();
}
}
class Animal {
}
class Tiger extends Animal implements Runnable {
@Override
public void run() {
System.out.println("老虎嗷嗷叫....");
}
}
//线程代理类 , 模拟了一个极简的 Thread 类
class ThreadProxy implements Runnable {//你可以把 Proxy 类当做 ThreadProxy
private Runnable target = null;//属性,类型是 Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定(运行类型 Tiger)
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//这个方法时真正实现多线程方法
}
public void start0() {
run();
}
}
class Dog implements Runnable { //通过实现 Runnable 接口,开发线程
int count = 0;
@Override
public void run() { //普通方法
while (true) {
System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
//休眠 1 秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}线程使用应用案例-多线程执行
请编写一个程序,创建两个线程,一个线程每隔1秒输出“hello,world”,输出10次,退出,一个线程每隔1秒输出“hi”,输出 5次退出.
package thread;
public class Thread03 {
public static void main(String[] args) {
T11 t1 = new T11();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();//启动第 1 个线程
thread2.start();//启动第 2 个线程
}
}
class T11 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
//每隔 1 秒输出 “hello,world”,输出 10 次
System.out.println("hello,world " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 60) {
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
//每隔 1 秒输出 “hi”,输出 5 次
while (true) {
System.out.println("hi " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 50) {
break;
}
}
}
}继承 Thread vs 实现 Runnable 的区别
1 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
2 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable
3 [售票系统],编程模拟三个售票窗口售票1003.,分别使用 继承 Thread 和实现 Runnable方式,并分析有什么问题?
package thread;
/**
* @author 韩顺平
* @version 1.0
* 使用多线程,模拟三个窗口同时售票 100 张
*/
public class SellTicket {
public static void main(String[] args) {
//测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //这里我们会出现超卖.. // sellTicket01.start();//启动售票线程
// sellTicket02.start();//启动售票线程
// sellTicket03.start();//启动售票线程
System.out.println("===使用实现接口方式来售票=====");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第 1 个线程-窗口
new Thread(sellTicket02).start();//第 2 个线程-窗口
new Thread(sellTicket02).start();//第 3 个线程-窗口
}
}
//使用 Thread 方式
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠 50 毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
}
}
}
//实现接口方式
class SellTicket02 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠 50 毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}线程终止
基本说明
1.当线程完成任务后,会自动退出
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
应用案例

线程常用方法
常用方法第一组
1.setName //设置线程名称,使之与参数 name 相同
2.getName //返回该线程的名称
3.start //使该线程开始执行:Java 虚拟机底层调用该线程的 start0 方法
4.run //调用线程对象 run 方法
5.setPriority //更改线程的优先级
6.getPriority //获取线程的优先级
sleep//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt//中断线程
注意事项和细节
1.start 底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程
2 线程优先级的范围
3interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
- sleep:线程的静态方法,使当前线程休眠
应用案例

常用方法第二组
1.yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
2.join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
案例:main线程创建一个子线程,每隔1s 输出 hello,输出 20次,主线程每隔1秒,输出 hi,输出 20次.要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续,


课堂练习
1.主线程每隔1s,输出 hi,一共 10次
2.当输出到 hi 5 时,启动一个子线程(要求实现Runnable),每隔1s输出 hello,等该线程输出10次 hello后,退出
3.主线程继续输出 hi,直到主线程退出,
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
Thread t3 = new Thread(new T3());//创建子线程
for (int i = 1; i <= 10; i++) {
System.out.println("hi " + i);
if (i == 5) {//说明主线程输出了 5 次 hi
t3.start();//启动子线程 输出 hello... t3.join();//立即将 t3 子线程,插入到 main 线程,让 t3 先执行
}
Thread.sleep(1000);//输出一次 hi, 让 main 线程也休眠 1s
}
}
}
class T3 implements Runnable {
private int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hello " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}用户线程和守护线程
1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3.常见的守护线程:垃圾回收机制

线程的生命周期

线程状态转换图

public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() + " 状态 " + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) {
System.out.println(t.getName() + " 状态 " + t.getState());
Thread.sleep(500);
}
System.out.println(t.getName() + " 状态 " + t.getState());
}
}
class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}线程的同步
Synchronized线程同步机制
1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
同步具体方法-Synchronized

public class SellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean loop = true;//控制run方法变量
//老韩说明
//1. public synchronized void sell() {} 就是一个同步方法
//2. 这时锁在 this对象
//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
public synchronized void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
if (ticketNum <= 0) {
System.out.println("窗口 " + Thread.currentThread().getName() + " 卖完了");
loop = false;
return;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一共同步方法
}
}
}互斥锁
基本介绍
1.Java语言中,引入了对象互斥锁的概念,,来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3.关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态的)的锁为当前类本身。
使用互斥锁来解决售票问题
/**
* @author 韩顺平
* @version 1.0
* 使用多线程,模拟三个窗口同时售票100张
*/
public class SellTicket {
public static void main(String[] args) {
//测试一把
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean loop = true;//控制run方法变量
Object object = new Object();
//同步方法(静态的)的锁为当前类本身
//老韩解读
//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
//2. 如果在静态方法中,实现一个同步代码块.
/*
synchronized (SellTicket03.class) {
System.out.println("m2");
}
*/
public synchronized static void m1() {
}
public static void m2() {
synchronized (SellTicket03.class) {
System.out.println("m2");
}
}
//老韩说明
//1. public synchronized void sell() {} 就是一个同步方法
//2. 这时锁在 this对象
//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
synchronized (/*this*/ object) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一共同步方法
}
}
}
//使用Thread方式
// new SellTicket01().start()
// new SellTicket01().start();
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享 ticketNum
// public void m1() {
// synchronized (this) {
// System.out.println("hello");
// }
// }
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
}注意事项和细节
1.同步方法如果没有使用static修饰:默认锁对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤:
需要先分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可!
线程的死锁
基本介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.
package thread;
/**
* @author 韩顺平
* @version 1.0
* 模拟线程死锁
*/
public class DeadLock_ {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
//线程
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程B 得不到 o1 对象锁,就会Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}释放锁
下面操作会释放锁
1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到break、return。
经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
下面操作不会释放锁
1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起该线程不会释放锁。
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
线程池介绍
将线程对象交给线程池维护可以降低系统成本 从而提升程序的性能
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互
当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程,就会严重浪费系统资源

使用 JDK 提供的线程池
Executors 中提供静态方法来创建线程池
| 方法 | 介绍 |
|---|---|
| static ExecutorService newCachedThreadPool () | 创建一个默认的线程池 |
| static newFixedThreadPool (int nThreads) | 创建一个指定最多线程数量的线程池 |
自定义线程池
ThreadPoolExecutor 类

- 参数1核心线程数量 (正式员工) 不能小于0
- 参数2最大线程数量 (正式员工 + 临时工) 不能小于等于0, 最大数量 >= 核心线程数量
- 参数3空闲时间 不能小于0
- 参数4时间单位
- 参数5任务队列 (指定排队人数) 不能为null
- 参数6线程对象工厂 不能为null
- 参数7拒绝策略 不能为null
| 策略选项 | 说明 |
|---|---|
| ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常 (默认) |
| ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
| ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
| ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法, 绕过线程池直接执行 |
临时线程什么时候创建?
线程任务数 > 核心线程数 + 任务队列的数量
什么时候会开启拒绝策略
线程任务数 > 最大线程数 + 任务队列的数量
IO流
什么是文件
文件,对我们并不陌生,文件是保存数据的地方,比如大家经常使用的word文档,txt文件,excel文件...都是文件。它既可以保存一张图片,也可以保持视频,声音...
文件流

常用文件操作
创建文件对象相关构造器和方法
相关方法
①new File(String pathname) // 根据路径构建一个File对象
②new File(File parent,String child) // 根据父目录文件+子路径构建
③new File(String parent,String child) // 根据父目录+子路径构建
createNewFile 创建新文件
// ①`new File(String pathname) ` // 根据路径构建一个File对象
@Test
public void create01(){
String filePath = "e:\\test.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ②`new File(File parent,String child)` // 根据父目录文件+子路径构建
@Test
public void create02(){
File parentFile = new File("e:\\");
String filepath = "test02.txt";
File file = new File(parentFile, filepath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ③`new File(String parent,String child)` // 根据父目录+子路径构建
@Test
public void create03(){
String parentPath="e:\\";
String filePath="test03.txt";
File file = new File(parentPath, filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}获取文件的相关信息
getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
//获取文件的信息
@Test
public void info() {
//先创建文件对象
File file = new File("e:\\news1.txt");
//调用相应的方法,得到对应信息
System.out.println("文件名字=" + file.getName());
//getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
System.out.println("文件绝对路径=" + file.getAbsolutePath());
System.out.println("文件父级目录=" + file.getParent());
System.out.println("文件大小(字节)=" + file.length());
System.out.println("文件是否存在=" + file.exists());//T
System.out.println("是不是一个文件=" + file.isFile());//T
System.out.println("是不是一个目录=" + file.isDirectory());//F
}目录的操作和文件删除
mkdir创建一级目录、mkdirs创建多级目录、delete删除空目录或文件
案例
1)判断 d:\news1.txt 是否存在,如果存在就删除
判断 D:\demo02 是否存在,存在就删除,否则提示不在。
判断D:\demo\a\b\c 目录是否存在,如果存在就提示已经存在,否则就创建
// 1)判断 d:\\news1.txt 是否存在,如果存在就删除
@Test
public void m1(){
String filePath = "e:\\test.txt";
File file = new File(filePath);
if (file.exists()){
if (file.delete()){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}else {
System.out.println("该文件不存在");
}
}
// 2) 判断 D:\demo02 是否存在,存在就删除,否则提示不在。
@Test
public void m2(){
String filePath = "D:\\demo02";
File file = new File(filePath);
if (file.exists()){
if (file.delete()){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}else {
System.out.println("该目录不存在");
}
}
// 3) 判断D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
@Test
public void m3(){
String filePath = "D:\\a\\b\\c\\d";
File file = new File(filePath);
if (file.exists()){
System.out.println("存在");
}else {
if (file.mkdirs()){
System.out.println("创建成功");
}else {
System.out.println("创建失败");
}
}
}IO 流原理及流的分类
Java IO 流原理
1.I/0是Input/Output的缩写,I/0技术是非常实用的技术,用于处理数据传输。如读/写文件,网络通讯。
2.Java程序中,对于数据的输入/输出操作以”流(stream)”的方式进行。
3.java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
4.输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
5.输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
流的分类

IO 流体系图-常用的类
io流体系图

InputStream:字节输入流
InputStream抽象类是所有类字节输入流的超类
InputStream常用的子类
1.FileInputStream :文件输入流
2.BufferedInputStream:缓冲字节输入流
3.ObjectInputStream:对象字节输入流
FileInputStream
案例
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStream_ {
public static void main(String[] args) {
}
/**
* 演示读取文件... * 单个字节的读取,效率比较低
* -> 使用 read(byte[] b)
*/
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
//如果返回-1 , 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData);//转成 char 显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用 read(byte[] b) 读取文件,提高效率
*/
@Test
public void readFile02() {
String filePath = "e:\\hello.txt";
//字节数组
byte[] buf = new byte[8]; //一次读取 8 个字节.
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多 b.length 字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
//如果返回-1 , 表示读取完毕
//如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));//显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}FileOutputStream
案例
要求: 请使用 FileOutputStream 在 a.txt 文件,中写入 “hello,world”. [老师代码演示], 如果文件不存在,会创建文件(注意:前提是目录已经存在.)
import org.junit.jupiter.api.Test;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream01 {
public static void main(String[] args) {
}
/**
* 演示使用 FileOutputStream 将数据写到文件中, * 如果该文件不存在,则创建该文件
*/
@Test
public void writeFile() {
//创建 FileOutputStream 对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//得到 FileOutputStream 对象 对象
//老师说明
//1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
//2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
// 1 fileOutputStream.write('H');
//写入字符串
String str = "hsp,world!";
//str.getBytes() 可以把 字符串-> 字节数组
// 2 fileOutputStream.write(str.getBytes());
/* 3 write(byte[] b, int off, int len) 将 len 字节从位于偏移量 off 的指定字节数组写入此文件输出流*/
fileOutputStream.write(str.getBytes(), 0, 3);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}文件拷贝
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
//完成 文件拷贝,将 e:\\Koala.jpg 拷贝 c:\\
//思路分析
//1. 创建文件的输入流 , 将文件读入到程序
//2. 创建文件的输出流, 将读取到的文件数据,写入到指定的文件.
String srcFilePath = "e:\\Koala.jpg";
String destFilePath = "e:\\Koala3.jpg";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(srcFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
//定义一个字节数组,提高读取效果
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = fileInputStream.read(buf)) != -1) {
//读取到后,就写入到文件 通过 fileOutputStream
//即,是一边读,一边写
fileOutputStream.write(buf, 0, readLen);//一定要使用这个方法
}
System.out.println("拷贝 ok~");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭输入流和输出流,释放资源
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}FileReader 和 FileWriter 介绍(字符)
FileReader 相关方法:
1)new FileReader(File/String)
2)read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
3)read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-相关API:
4)new String(char[]):将char[]转换成String
5)new String(char[],off,len):将char[]的指定部分转换成String
FileWriter 常用方法
1)new FileWriter(File/String):覆盖模式,相当于流的指针在首端
2)new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
3)write(int):写入单个字符
4)write(char[]):写入指定数组
5)write(char[],off,len):写入指定数组的指定部分
6)write(string):写入整个字符串
7)write(string,off,len):写入字符串的指定部分
相关APl:String类: toCharArray:将String转换成char[]
注意:
FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!
读取案例
import org.junit.Test;
import java.io.FileReader;
import java.io.IOException;
public class FileReader_ {
public static void main(String[] args) {
}
/**
* 单个字符读取文件
*/
@Test
public void readFile01() {
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int data = 0;
//1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用 read, 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符数组读取文件
*/
@Test
public void readFile02() {
System.out.println("~~~readFile02 ~~~");
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
//1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用 read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}使用 FileWriter 将 “风雨之后,定见彩虹” 写入到 note.txt 文件中, 注意细节
import java.io.FileWriter;
import java.io.IOException;
public class FileWriter_ {
public static void main(String[] args) {
String filePath = "e:\\note.txt";
//创建 FileWriter 对象
FileWriter fileWriter = null;
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath);//默认是覆盖写入
// 3) write(int):写入单个字符
fileWriter.write('H');
// 4) write(char[]):写入指定数组
fileWriter.write(chars);
// 5) write(char[],off,len):写入指定数组的指定部分
fileWriter.write("韩顺平教育".toCharArray(), 0, 3);
// 6) write(string):写入整个字符串
fileWriter.write(" 你好北京~");
fileWriter.write("风雨之后,定见彩虹");
// 7) write(string,off,len):写入字符串的指定部分
fileWriter.write("上海天津", 0, 2);
//在数据量大的情况下,可以使用循环操作.
} catch (IOException e) {
e.printStackTrace();
} finally {
//对应 FileWriter , 一定要关闭流,或者 flush 才能真正的把数据写入到文件
//老韩看源码就知道原因.
try {
//fileWriter.flush();
//关闭文件流,等价 flush() + 关闭
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("程序结束...");
}
}节点流和处理流

节点流和处理流一览图

节点流和处理流的区别和联系
1、节点流是底层流/低级流,直接跟数据源相接。
2、处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
3、处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
处理流的功能主要体现在以下两个方面:
1、性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
2、操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
处理流-BufferedReader 和 BufferedWriter
1、BufferedReader 和 BufferedWriter 属于字符流,是按照字符来读取数据的
2、关闭时处理流,只需要关团外层流即可
BufferedReader
使用BufferedReader 读取文本文件,并显示在控制台
public class BufferedReader_ {
public static void main(String[] args) throws Exception {
String filePath = "e:\\a.java";
//创建 bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line; //按行读取, 效率高
//说明
//1. bufferedReader.readLine() 是按行读取文件
//2. 当返回 null 时,表示文件读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭 节点流
//FileReader。
/*
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();//in 就是我们传入的 new FileReader(filePath), 关闭了. } finally {
in = null;
cb = null;
}
}
}
*/
bufferedReader.close();
}
}BufferedWriter
使用BufferedWriter 将”hello,韩顺平教育”,写入到文件中
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
//创建 BufferedWriter
//说明:
//1. new FileWriter(filePath, true) 表示以追加的方式写入
//2. new FileWriter(filePath) , 表示以覆盖的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello, 韩顺平教育!");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("hello2, 韩顺平教育!");
bufferedWriter.newLine();
bufferedWriter.write("hello3, 韩顺平教育!");
bufferedWriter.newLine();
//说明:关闭外层流即可 , 传入的 new FileWriter(filePath) ,会在底层关闭
bufferedWriter.close();
}
}文件拷贝
import java.io.*;
public class BufferedCopy_ {
public static void main(String[] args) {
//老韩说明
//1. BufferedReader 和 BufferedWriter 是安装字符操作
//2. 不要去操作 二进制文件[声音,视频,doc, pdf ], 可能造成文件损坏
//BufferedInputStream
//BufferedOutputStream
String srcFilePath = "e:\\a.java";
String destFilePath = "e:\\a2.java";
BufferedReader br = null;
BufferedWriter bw = null;
String line;
try {
br = new BufferedReader(new FileReader(srcFilePath));
bw = new BufferedWriter(new FileWriter(destFilePath));
//说明: readLine 读取一行内容,但是没有换行
while ((line = br.readLine()) != null) {
//每读取一行,就写入
bw.write(line);
//插入一个换行
bw.newLine();
}
System.out.println("拷贝完毕...");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流
try {
if (br != null) {
br.close();
}
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}处理流-BufferedInputStream 和 BufferedOutputStream
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
* 演示使用 BufferedOutputStream 和 BufferedInputStream 使用
* 使用他们,可以完成二进制文件拷贝. * 思考:字节流可以操作二进制文件,可以操作文本文件吗?当然可以
*/
public class BufferedCopy02 {
public static void main(String[] args) {
// String srcFilePath = "e:\\Koala.jpg";
// String destFilePath = "e:\\hsp.jpg";
// String srcFilePath = "e:\\0245_韩顺平零基础学 Java_引出 this.avi";
// String destFilePath = "e:\\hsp.avi";
String srcFilePath = "e:\\a.java";
String destFilePath = "e:\\a3.java";
//创建 BufferedOutputStream 对象 BufferedInputStream 对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//因为 FileInputStream 是 InputStream 子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环的读取文件,并写入到 destFilePath
byte[] buff = new byte[1024];
int readLen = 0;
//当返回 -1 时,就表示文件读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}
System.out.println("文件拷贝完毕~~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}对象流-ObjectInputStream 和 ObjectOutputStream
看一个需求
1、将int num = 100 这个 int 数据保存到文件中,注意不是 100 数字,而是 int 100,并且,能够从文件中直接恢复 int 100
2、将 Dog dog = new Dog(“小黄”,3) 这个 dog对象 保存到 文件中,并且能够从文件恢复
3、上面的要求,就是 能够将 基本数据类型 或者 对象 进行 序列化 和 反序列化操作
序列化和反席列化
1、序列化就是在保存数据时,保存数据的值和数据类型
2、反序列化就是在恢复数据时,恢复数据的值和数据类型
3、需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable //这是一个标记接口,没有方法>
Externalizable // 该接口有方法需要实现,因此我们一般实现上面的 Serializable接口
对象流介绍
功能:提供了对基本类型或对象类型的序列化和反序列化的方法
ObjectOutputStream 提供 序列化功能
ObjectInputStream 提供 反序列化功能
应用案例 1.一个 Dog对象(name, age),并使用ObjectOutputStream 序列化 基本数据类型和保存到 data.dat 文件中 ObjectOutStream .java
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class ObjectOutStream_ {
public static void main(String[] args) throws Exception {
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("韩顺平教育");//String
//保存一个 dog 对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}2.使用ObjectlnputStream 读取 data.dat 并反序列化恢复数据
// 1.创建流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\data.dat"));
// 2.读取, 注意顺序
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject());
System.out.println(ois.readObject());
System.out.println(ois.readObject());
// 3.关闭
ois.close();
System.out.println("以反序列化的方式读取(恢复)ok~");注意事项和细节说明
1、读写顺序要一致
2、要求序列化或反序列化对象,需要实现 Serializable
3、序列化的类中建议添加SerialVersionUlD,为了提高版本的兼容性
//serialVersionUID 序列化的版本号,可以提高兼容性
private static final long serialVersionUID = 1L;
4、序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
5、序列化对象时,要求里面属性的类型也需要实现序列化接口
6、序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
标准输入输出流
类型 默认设备
System.in 标准输入 InputStream 键盘
System.out 标准输出 PrintStream 显示器
public class InputAndOutput {
public static void main(String[] args) {
//System类的public final static InputStream in = null;
//System.in 编译类型InputStream
//System.in 运行类型BufferedInputStream
//表示的是标准输入 键盘
System.out.println(System.in.getClass());
//老韩解读
//1.System.out public final static PrintStream out = null;
//2,编译类型 PrintStream
//3,运行类型 Printstream
//4,表示标准输出 显示器
System.out.println(System.out.getClass());
}
}转换流-InputStreamReader 和 OutputStreamWriter
介绍
1.InputStreamReader:Reader的子类,可以将lnputStream(字节流)包装成(转换)Reader(字符流)
2.OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
3.当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
4.可以在使用时指定编码格式(比如 utf-8,gbk,gb2312,ISO8859-1 等)
应用案例编程将 字节流FilelnputStream 包装成(转换成)字符流InputStreamReader, 对文件进行读取(按照 utf-8/gbk 格式),进而在包装成 BufferedReaderInputStreamReader .java
/**
* @author 韩顺平
* @version 1.0
* 演示使用 InputStreamReader 转换流解决中文乱码问题
* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
*/
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
//解读
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把 InputStreamReader 传入 BufferedReader
//BufferedReader br = new BufferedReader(isr);
//将 2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(filePath), "gbk"));
//4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
//5. 关闭外层流
br.close();
}
}应用案例
编程将 字节流 FileOutputStream 包装成(转换成) 字符流OutputStreamWriter 对文件进行写入(按照gbk格式,可以指定其他,比如utf-8)
// 1.创建流对象
OutputStreamWriter osw =
new OutputStreamWriter(new FileOutputStream("d:\\a.txt"), "gbk");
// 2.写入
osw.write("hello,韩顺平教育~");
// 3.关闭
osw.close();
System.out.println("保存成功~");打印流-PrintStream 和 PrintWriter
/**
* @author 韩顺平
* 第 858页
* 韩顺平循序渐进学 Java 零基础
* @version 1.0
* 演示 PrintWriter 使用方式
*/
public class PrintWriter_ {
public static void main(String[] args) throws IOException {
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
/**
* @author 韩顺平
* @version 1.0
* 演示 PrintStream (字节打印流/输出流)
*/
public class PrintStream_ {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
/*
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
*/
out.print("john, hello");
//因为 print 底层使用的是 write , 所以我们可以直接调用 write 进行打印/输出
out.write("韩顺平,你好".getBytes());
out.close();
//我们可以去修改打印流输出的位置/设备
//1. 输出修改成到 "e:\\f1.txt"
//2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
//3. public static void setOut(PrintStream out) {
// checkIO();
// setOut0(out); // native 方法,修改了 out
// }
System.setOut(new PrintStream("e:\\f1.txt"));
System.out.println("hello, 韩顺平教育~");
}
}
printWriter.print("hi, 北京你好~~~~");
printWriter.close();//flush + 关闭流, 才会将数据写入到文件..
}
}Properties 类

public class Properties01 {
public static void main(String[] args) throws IOException {
//读取 mysql.properties 文件,并得到 ip, user 和 pwd
BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) { //循环读取
String[] split = line.split("=");
//如果我们要求指定的 ip 值
if ("ip".equals(split[0])) {
System.out.println(split[0] + "值是: " + split[1]);
}
}
br.close();
}
}基本介绍
1)专门用于读写配置文件的集合类
配置文件的格式:
键=值
键=值
2)注意:键值对不需要有空格,值不需要用引号一起来。默认类型是String
3)Properties的常见方法
- load:加载配置文件的键值对到Properties对象
- Iist:将数据显示到指定设备
- getProperty(key):根据键获取值
- setProperty(key,value):设置键值对到Properties对象
- store:将Properties中的键值对存储到配置文件,在idea 中,保存信息到配置文件,如果含有中文,会存储为unicode码
http://tool.chinaz.com/tools/unicode.aspxunicode码查询工具
应用案例
1.使用Properties类完成对 mysql.properties 的读取,看老师代码演示
2.使用Properties类添加key-val到新文件mysql2.properties 中
3.使用Properties类完成对 mysql2.properties 的读取,并修改某个key-val
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用 Properties 类来读取 mysql.properties 文件
//1. 创建 Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3. 把 k-v 显示控制台
properties.list(System.out);
//4. 根据 key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
}
}public class Properties03 {
public static void main(String[] args) throws IOException {
//使用 Properties 类来创建 配置文件, 修改配置文件内容
Properties properties = new Properties();
//创建
//1.如果该文件没有 key 就是创建
//2.如果该文件有 key ,就是修改
/*
Properties 父类是 Hashtable , 底层就是 Hashtable 核心方法
*/
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode 码值
properties.setProperty("pwd", "888888");
//将 k-v 存储文件中即可
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
System.out.println("保存配置文件成功~");
}
}网络编程
InetAddress 类
相关方法
1.获取本机InetAddress对象 getLocalHost
2.根据指定主机名/域名获取ip地址对象 getByName
3.获取lnetAddress对象的主机名 getHostName
4.获取InetAddress对象的地址 getHostAddress
public static void main(String[] args) throws UnknownHostException {
//获取本机 InetAddress 对象 getLocalHost
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
//根据指定主机名/域名获取 ip 地址对象 getByName
InetAddress host2 = InetAddress.getByName("LAPTOP-UOIMN48V");
System.out.println(host2);
InetAddress host3 = InetAddress.getByName("zx520.top");
System.out.println(host3);
//获取 InetAddress 对象的主机名 getHostName
String host3Name = host3.getHostName();
System.out.println(host3Name);
//获取 InetAddress 对象的地址 getHostAddress
String host3Address = host3.getHostAddress();
System.out.println(host3Address);
}Socket
1、套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准。
2、通信的两端都要有Socket,是两台机器间通信的端点
3、网络通信其实就是Socket间的通信。
4.Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
5.一般主动发起通信的应用程序属客户端,等待通信请求的为服务端
TCP 网络通信编程
基本介绍
1、基于客户端一服务端的网络通信
2、底层使用的是TCP/IP协议
3、应用场景举例:客户端发送数据,服务端接受并显示控制台
4、基于Socket的TCP编程

应用案例 1(使用字节流)
1、编写一个服务器端, 和一个客户端
2、服务器端在 9999端口监听
3、客户端连接到服务器端,发送"helo, server",然后退出
4、服务器端接收到 客户端发送的 信息,输出,并退出

package socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 韩顺平
* @version 1.0
* 服务端
*/
public class SocketTCP01Server {
public static void main(String[] args) throws IOException, IOException {
//思路
//1. 在本机 的 9999 端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听 9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在 9999 端口监听,等待连接..");
//2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO 读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根据读取到的实际长度,显示内容. }
//5.关闭流和 socket
inputStream.close();
socket.close();
serverSocket.close();//关闭
}
}
}package socket;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @author 韩顺平
* @version 1.0
* 客户端,发送 "hello, server" 给服务端
*/
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999 端口, 如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket 返回=" + socket.getClass());
//2. 连接上后,生成 Socket, 通过 socket.getOutputStream()
// 得到 和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道
outputStream.write("hello, server".getBytes());
//4. 关闭流对象和 socket, 必须关闭
outputStream.close();
socket.close();
System.out.println("客户端退出.....");
}
}应用案例 2(使用字节流)
1.编写一个服务端,和一个客户端
2.服务器端在 9999端口监听
3.客户端连接到服务端,发送"hello,server",并接收服务器端回发的"hello,client",再退出
4.服务器端接收到 客户端发送的 信息,输出,并发送"hello, client”,,再退出

package socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 韩顺平
* @version 1.0
* 服务端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的 9999 端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听 9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在 9999 端口监听,等待连接..");
//2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO 读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根据读取到的实际长度,显示内容. }
//5. 获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, client".getBytes());
// 设置结束标记
socket.shutdownOutput();
//6.关闭流和 socket
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();//关闭
}
}
}package socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @author 韩顺平
* @version 1.0
* 客户端,发送 "hello, server" 给服务端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999 端口, 如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket 返回=" + socket.getClass());
//2. 连接上后,生成 Socket, 通过 socket.getOutputStream()
// 得到 和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道
outputStream.write("hello, server".getBytes());
// 设置结束标记
socket.shutdownOutput();
//4. 获取和 socket 关联的输入流. 读取数据(字节),并显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
//5. 关闭流对象和 socket, 必须关闭
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客户端退出.....");
}
}应用案例 3(使用字符流)
1.编写一个服务端,和一个客户端
2.服务端在 9999端口监听
3.客户端连接到服务端,发送"hello,server”,并接收服务端回发的"hello,client”,再退出
4.服务端接收到 客户端发送的 信息,输出,并发送"hello, client",再退出

package socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 韩顺平
* @version 1.0
* 服务端, 使用字符流方式读写
*/
@SuppressWarnings({"all"})
public class SocketTCP03Server {
public static void main(String[] args) throws IOException, IOException {
//思路
//1. 在本机 的 9999 端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听 9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个 Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在 9999 端口监听,等待连接..");
//2. 当没有客户端连接 9999 端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过 socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO 读取, 使用字符流, 老师使用 InputStreamReader 将 inputStream 转成字符流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);//输出
//5. 获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
// 使用字符输出流的方式回复信息
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello client 字符流");
bufferedWriter.newLine();// 插入一个换行符,表示回复内容的结束
bufferedWriter.flush();//注意需要手动的 flush
//6.关闭流和 socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.close();//关闭
}
}package socket;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @author 韩顺平
* @version 1.0
* 客户端,发送 "hello, server" 给服务端, 使用字符流
*/
@SuppressWarnings({"all"})
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999 端口, 如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket 返回=" + socket.getClass());
//2. 连接上后,生成 Socket, 通过 socket.getOutputStream()
// 得到 和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道, 使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello, server 字符流");
bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束, 注意,要求对方使用 readLine()!!!!
bufferedWriter.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道
//4. 获取和 socket 关联的输入流. 读取数据(字符),并显示
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
//5. 关闭流对象和 socket, 必须关闭
bufferedReader.close();//关闭外层流
bufferedWriter.close();
socket.close();
System.out.println("客户端退出.....");
}
}注意:关闭的时候是后打开的先关闭
应用案例 4(上传文件)
1.编写一个服务端, 和一个客户端服务器端在 8888端口监听客户端连接到服务端,发送 一张图片 e:\gie.png3.服务器端接收到 客户端发送的 图片,保存到 src下,发送"收到图片"再退出5.客户端接收到服务端发送的"收到图片", 再退出该程序要求使用 StreamUtils.java,我们直接使用
public class SocketTCP04Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器启动,等待接收数据");
Socket socket = serverSocket.accept();
FileOutputStream fileOutputStream = new FileOutputStream("src/com.ybs/java/socketMy/1.jpg");
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int dataLen = 0;
while ((dataLen=inputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0,dataLen);
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write("服务器接收成功".getBytes());
socket.shutdownOutput();
outputStream.close();
fileOutputStream.close();
inputStream.close();
serverSocket.close();
socket.close();
}
}public class SocketTCP04Client {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("d:\\123.jpg");
byte[] bytes = new byte[1024];
int dataLen = 0;
// 创建客户端Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
while ((dataLen=fileInputStream.read(bytes))!=-1){
OutputStream outputStream = socket.getOutputStream();
outputStream.write(bytes,0,dataLen);
}
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] bytes1 = new byte[1024];
int dataLen1 = 0;
while ((dataLen1=inputStream.read(bytes1))!=-1){
System.out.println(new String(bytes1,0,dataLen1));
}
inputStream.close();
fileInputStream.close();
socket.close();
}
}netstat
1、netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
2、netstat -an|more 可以分页显示
TCP网络通信编程

当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP 来分配的,是不确定的,是随机的.
UDP 网络通信编程

基本介绍
1.类 DatagramSocket 和 DatagramPacket[数据包/数据报] 实现了基于 UDP协议网络程序。
2.UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
3.DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
4.UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接
基本流程
1.核心的两个类/对象 DatagramSocket与DatagramPacket
2.建立发送端,接收端(没有服务端和客户端概念)
3.发送数据前,建立数据包/报 DatagramPacket对象
4.调用DatagramSocket的发送、接收方法
5.关闭DatagramSocket
应用案例
1.编写一个接收端A,和一个发送端B
2.接收端A在 9999端口等待接收数据(receive)
3.发送端B向接收端A 发送 数据"hello,明天吃火锅~
接收端A接收到 发送端B发送的数据, 回复"好的,明天见”,再退出发送端接收 回复的数据,再退出 A.
package udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 创建一个 DatagramSocket 对象,准备在 9999 接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 创建一个 byte 数组,用于接收数据
byte[] buf = new byte[1024];
//3. 创建一个 DatagramPacket 对象,用于接收数据
DatagramPacket packet = new DatagramPacket(buf, buf.length);
System.out.println("等待数据");
// 等待接收数据
socket.receive(packet);
int length = packet.getLength();
byte[] data = packet.getData();
String s = new String(data, 0, length);
System.out.println(s);
socket.close();
}
}package udp;
import java.io.IOException;
import java.net.*;
public class UDPSenderB {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9998);
// 发送数据
byte[] data = "hello".getBytes();
DatagramPacket datagramPacket =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.1.1"), 9999);
socket.send(datagramPacket);
socket.close();
}
}import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 创建一个 DatagramSocket 对象,准备在 9999 接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解 UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet 对象
//老师提示: 当有数据包发送到 本机的 9999 端口时,就会接收到数据
// 如果没有数据包发送到 本机的 9999 端口, 就会阻塞等待. System.out.println("接收端 A 等待接收数据..");
socket.receive(packet);
//4. 可以把 packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
byte[] data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);
//===回复信息给 B 端
//将需要发送的数据,封装到 DatagramPacket 对象
data = "好的, 明天见".getBytes();
//说明: 封装的 DatagramPacket 对象 data 内容字节数组 , data.length , 主机(IP) , 端口
packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.1.1"), 9998);
socket.send(packet);//发送
//5. 关闭资源
socket.close();
System.out.println("A 端退出...");
}
}import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
@SuppressWarnings({"all"})
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//1.创建 DatagramSocket 对象,准备在 9998 端口 接收数据
DatagramSocket socket = new DatagramSocket(9998);
//2. 将需要发送的数据,封装到 DatagramPacket 对象
byte[] data = "hello 明天吃火锅~".getBytes(); //
//说明: 封装的 DatagramPacket 对象 data 内容字节数组 , data.length , 主机(IP) , 端口
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9999);
socket.send(packet);
//3.=== 接收从 A 端回复的信息
//(1) 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解 UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
//(2) 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet 对象
//老师提示: 当有数据包发送到 本机的 9998 端口时,就会接收到数据
// 如果没有数据包发送到 本机的 9998 端口, 就会阻塞等待. socket.receive(packet);
//(3) 可以把 packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);
//关闭资源
socket.close();
System.out.println("B 端退出");
}
}反射
需求
1 根据配置文件re.properties指定信息,创建Cat对象并调用方法hi
2 这样的需求即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式,ocp原则(开闭原则:不修改源码,扩展功能)
快速入门
1 创建Cat类
package com.yb.reflection.question;
public class Cat {
public String name = "小花";
public void hi(){
System.out.println("hi"+name);
}
}2 创建配置信息
classfullpath=com.yb.reflection.question.Cat
method=hi3 创建 ReflectionQuestion
package com.yb.reflection.question;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
@SuppressWarnings({"all"})
public class ReflectionQuestion {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//根据配置文件 re.properties 指定信息, 创建 Cat 对象并调用方法 hi
//老韩回忆
//传统的方式 new 对象 -》 调用方法
// Cat cat = new Cat();
// cat.hi(); ===> cat.cry() 修改源码. //我们尝试做一做 -> 明白反射
//1. 使用 Properties 类, 可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/resources/re.properties"));
String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"
String methodName = properties.get("method").toString();//"hi"
System.out.println("classfullpath=" + classfullpath);
System.out.println("method=" + methodName);
//2. 创建对象 , 传统的方法,行不通 =》 反射机制
//new classfullpath();
//3. 使用反射机制解决
//(1) 加载类, 返回 Class 类型的对象 cls
Class cls = Class.forName(classfullpath);
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o 的运行类型=" + o.getClass()); //运行类型
//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4) 通过 method1 调用方法: 即通过方法对象来实现调用方法
System.out.println("=============================");
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
}
}Java 反射机制原理示意图!!!

Java 反射机制可以完成
1 在运行时判断任意一个对象所属的类
2 在运行时构造任意一个类的对象
3 在运行时得到任意一个类所具有的成员变量和方法
4 在运行时调用任意一个对象的成员变量和方法
5 生成动态代理
反射相关的主要类
1 java.lang.Class :代表一个类,Class对象表示某个类加载后在堆中的对象
2 java.lang.reflect.Method :代表类的方法,Method对象表示某个类的方法
3 java.lang.reflect.Field : 代表类的成员变量,Filed 对象表示某个类的成员变量
4 java.lang.reflect.Constructor 代表类的构造方法,Constructor 对象表示构造器
使用
@SuppressWarnings({"all"})
public class Reflection01 {
public static void main(String[] args) throws Exception {
//1. 使用 Properties 类, 可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/resources/re.properties"));
String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"
String methodName = properties.get("method").toString();//"hi"
//2. 使用反射机制解决
//(1) 加载类, 返回 Class 类型的对象 cls
Class cls = Class.forName(classfullpath);
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o 的运行类型=" + o.getClass()); //运行类型
//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4) 通过 method1 调用方法: 即通过方法对象来实现调用方法
System.out.println("=============================");
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
//java.lang.reflect.Field: 代表类的成员变量, Field 对象表示某个类的成员变量
//得到 name 字段
//getField 不能得到私有的属性
Field nameField = cls.getField("age"); //
System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象)
//java.lang.reflect.Constructor: 代表类的构造方法, Constructor 对象表示构造器
Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
System.out.println(constructor);//Cat()
Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是 String 类的Class 对象
System.out.println(constructor2);//Cat(String name)
}
}反射优点和缺点
1 优点 : 可以动态 的创建和使用对象(也是框架底层的核心),使用灵活,没有反射机制,框架技术就失去底层支撑
2 缺点:使用反射基本是解释执行,对执行速度有影响
优化
Method和Field、Constructor对象都有setAccessible()方法.setAccessible作用是启动和禁用访问安全检查的开关参数值为true表示 反射的对象在使用时取消访问检查,为false则表示反射的对象执行访问检查 提高反射的效率。参数值
Class类
基本介绍

1 Class也是类,因此也是继承Object类
2 Class类对象不是new出来的,而是系统创建的
3 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
4 每个类的实例都会记得自己是由哪个Class实例生成的
5 通过Class对象可以完整的得到一个类的完整结构,通过一系列的API
6 Class对象是存放在堆的
7 类的字节码二进制数据是放在方法区的,有的地方成为类的元数据(包括 方法代码,变量名,方法名,访问权限等等。)
Class 类的常用方法
package com.yb.reflection.question;
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
String classAllPath = "com.hspedu.Car";
//1 . 获取到 Car 类 对应的 Class 对象
//<?> 表示不确定的 Java 类型
Class<?> cls = Class.forName(classAllPath);
//2. 输出 cls
System.out.println(cls); //显示 cls 对象, 是哪个类的 Class 对象 com.hspedu.Car
System.out.println(cls.getClass());//输出 cls 运行类型 java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName());//包名
//4. 得到全类名
System.out.println(cls.getName());
//5. 通过 cls 创建对象实例
Car car = (Car) cls.newInstance();
System.out.println(car);//car.toString()
//6. 通过反射获取属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));//奔驰
//8 我希望大家可以得到所有的属性(字段)
System.out.println("=======所有的字段属性====");
Field[] fields = cls.getFields();
for (Field f : fields) {
System.out.println(f.getName());//名称
}
}
}获取 Class 类对象

public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.forName
String classAllPath = "com.hspedu.Car"; //通过读取配置文件获取
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);
//2. 类名.class , 应用场景: 用于参数传递
Class cls2 = Car.class;
System.out.println(cls2);
//3. 对象.getClass(), 应用场景,有对象实例
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);
//4. 通过类加载器【4 种】来获取到类的 Class 对象
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到 Class 对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
//cls1 , cls2 , cls3 , cls4 其实是同一个对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
//5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到 Class 类对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);//int
//6. 基本数据类型对应的包装类,可以通过 .TYPE 得到 Class 类对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE; //其它包装类 BOOLEAN, DOUBLE, LONG,BYTE 等待
System.out.println(type1);
System.out.println(integerClass.hashCode());//?
System.out.println(type1.hashCode());//?
}
}
哪些类型有 Class 对象
1.外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2.interface:接口 3.数组
4.enum:枚举
5.annotation:注解
6.基本数据类型
7.void
public class AllTypeClass {
public static void main(String[] args) {
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
//枚举
Class<Thread.State> cls6 = Thread.State.class;
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void 数据类型
Class<Class> cls9 = Class.class;//
System.out.println(cls1);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);
System.out.println(cls5);
System.out.println(cls6);
System.out.println(cls7);
System.out.println(cls8);
System.out.println(cls9);
}
}类加载
基本说明
反射机制是 java实现动态语言的关键,也就是通过反射实现类动态加载。
1、静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
2、动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性
类加载时机
1.当创建对象时(new) //静态加载
2.当子类被加载时,父类也加载 //静态加载
3.调用类中的静态成员时 //静态加载
4.通过反射 //动态加载
Class.forName("com.test.Cat");
类的加载过程图

类加载各阶段完成任务

加载阶段

连接阶段-验证
1、目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2、包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
3、可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段-准备
1.JVM 会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。这些变量所使用的内存都将在方法区中进行分配
class A {
//属性-成员变量-字段
//老韩分析类加载的链接阶段-准备 属性是如何处理
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是 20
//3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}连接阶段-解析

Initialization(初始化)
1、到初始化阶段,才真正开始执行类中定义的Java 程序代码,此阶段是执行<clinit>()方法的过程。
2、<clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
3、虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕(正因为有这个机制,才能保证某个类在内存中, 只有一份 Class 对象)通过反射获取类的结构信息
第一组: java.lang.Class 类

第二组: java.lang.reflect.Field 类

第三组: java.lang.reflect.Method 类

第四组: java.lang.reflect.Constructor 类

package com.yb.reflection.question;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionUtils {
public static void main(String[] args) {
}
@Test
public void api_02() throws ClassNotFoundException, NoSuchMethodException {
//得到 Class 对象
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//getDeclaredFields:获取本类中所有属性
//规定 说明: 默认修饰符 是 0 , public 是 1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName()
+ " 该属性的修饰符值=" + declaredField.getModifiers()
+ " 该属性的类型=" + declaredField.getType());
}
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName()
+ " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
+ " 该方法返回类型" + declaredMethod.getReturnType());
//输出当前这个方法的形参数组情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型=" + parameterType);
}
}
//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("====================");
System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参类型=" + parameterType);
}
}
}
//第一组方法 API
@Test
public void api_01() throws ClassNotFoundException, NoSuchMethodException {
//得到 Class 对象
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//getName:获取全类名
System.out.println(personCls.getName());//com.hspedu.reflection.Person
//getSimpleName:获取简单类名
System.out.println(personCls.getSimpleName());//Person
//getFields:获取所有 public 修饰的属性,包含本类以及父类的
Field[] fields = personCls.getFields();
for (Field field : fields) {//增强 for
System.out.println("本类以及父类的属性=" + field.getName());
}
//getDeclaredFields:获取本类中所有属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName());
}
//getMethods:获取所有 public 修饰的方法,包含本类以及父类的
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println("本类以及父类的方法=" + method.getName());
}
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName());
}
//getConstructors: 获取所有 public 修饰的构造器,包含本类
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类的构造器=" + constructor.getName());
}
//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
}
//getPackage:以 Package 形式返回 包信息
System.out.println(personCls.getPackage());//com.hspedu.reflection
//getSuperClass:以 Class 形式返回父类信息
Class<?> superclass = personCls.getSuperclass();
System.out.println("父类的 class 对象=" + superclass);//
//getInterfaces:以 Class[]形式返回接口信息
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息=" + anInterface);
}
//getAnnotations:以 Annotation[] 形式返回注解信息
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息=" + annotation);//注解
}
}
}
class A {
public String hobby;
public void hi() {
}
public A() {
}
public A(String name) {
}
}
interface IA {
}
interface IB {
}
@Deprecated
class Person extends A implements IA, IB {
//属性
public String name;
protected static int age; // 4 + 8 = 12
String job;
private double sal;
//构造器
public Person() {
}
public Person(String name) {
}
//私有的
private Person(String name, int age) {
}
//方法
public void m1(String name, int age, double sal) {
}
protected String m2() {
return null;
}
void m3() {
}
private void m4() {
}
}通过反射创建对象
1.方式一:调用类中的public修饰的无参构造器
2.方式二:调用类中的指定构造器
3.Class类相关方法
- newInstance :调用类中的无参构造器,获取对应类的对象
- getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor(Class...cazz):根据参数列表,获取对应的所有构造器对象
4.Constructor类相关方法
- setAccessible:暴破
- newlnstance(Object...obj):调用构造器
案例
测试 1:通过反射创建某类的对象,要求该类中必须有 public 的无参构造
测试 2:通过调用某个特定构造器的方式,实现创建某类的对象
package com.yb.reflection.question;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1. 先获取到 User 类的 Class 对象
Class<?> userClass = Class.forName("com.hspedu.reflection.User");
//2. 通过 public 的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//3. 通过 public 的有参构造器创建实例
/*
constructor 对象就是
public User(String name) {//public 的有参构造器
this.name = name;
}
*/
//3.1 先得到对应构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
//3.2 创建实例,并传入实参
Object hsp = constructor.newInstance("hsp");
System.out.println("hsp=" + hsp);
//4. 通过非 public 的有参构造器创建实例
//4.1 得到 private 的构造器对象
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例
//暴破【暴力破解】 , 使用反射可以访问 private 构造器/方法/属性, 反射面前,都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(100, "张三丰");
System.out.println("user2=" + user2);
}
}
class User { //User 类
private int age = 10;
private String name = "韩顺平教育";
public User() {//无参 public
}
public User(String name) {//public 的有参构造器
this.name = name;
}
private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}通过反射访问类中的成员
访问属性
1.根据属性名获取Field对象
Field f= clazz对象.getDeclaredField(属性名);
2.暴破: f.setAccessible(true);//f是Field
3.访问f.set(o,值); //o表示对象
syso(f.get(o));//o 表示对象
4.注意:如果是静态属性,则set和get中的参数o,可以写成null
package com.yb.reflection.question;
import java.lang.reflect.Field;
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//1. 得到 Student 类对应的 Class 对象
Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
//2. 创建对象
Object o = stuClass.newInstance();//o 的运行类型就是 Student
System.out.println(o.getClass());//Student
//3. 使用反射得到 age 属性对象
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来操作属性
System.out.println(o);//
System.out.println(age.get(o));//返回 age 属性的值
//4. 使用反射操作 name 属性
Field name = stuClass.getDeclaredField("name");
//对 name 进行暴破, 可以操作 private 属性
name.setAccessible(true);
//name.set(o, "老韩");
name.set(null, "老韩~");//因为 name 是 static 属性,因此 o 也可以写出 null
System.out.println(o);
System.out.println(name.get(o)); //获取属性值
System.out.println(name.get(null));//获取属性值, 要求 name 是 static
}
}
class Student {//类
public int age;
private static String name;
public Student() {//构造器
}
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}访问方法
1、根据方法名和参数列表获取Method方法对象:Method m = clazz.getDeclaredMethod(方法名,XX.class);//得到本类的所有方法
2、获取对象:Object o=clazz.newlnstance();
3、暴破:m.setAccessible(true);
4、访问:Object returnValue = m.invoke(o,实参列表);//o 就是对象
5、注意:如果是静态方法,则invoke的参数o,可以写成null!
package com.yb.reflection.question;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, InvocationTargetException {
//1. 得到 Boss 类对应的 Class 对象
Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");
//2. 创建对象
Object o = bossCls.newInstance();
//3. 调用 public 的 hi 方法
//Method hi = bossCls.getMethod("hi", String.class);//OK
//3.1 得到 hi 方法对象
Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
//3.2 调用
hi.invoke(o, "韩顺平教育~");
//4. 调用 private static 方法
//4.1 得到 say 方法对象
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//4.2 因为 say 方法是 private, 所以需要暴破,原理和前面讲的构造器和属性一样
say.setAccessible(true);
System.out.println(say.invoke(o, 100, "张三", '男'));
//4.3 因为 say 方法是 static 的,还可以这样调用 ,可以传入 null
System.out.println(say.invoke(null, 200, "李四", '女'));
//5. 在反射中,如果方法有返回值,统一返回 Object , 但是他运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, "王五", '男');
System.out.println("reVal 的运行类型=" + reVal.getClass());//String
//在演示一个返回的案例
Method m1 = bossCls.getDeclaredMethod("m1");
Object reVal2 = m1.invoke(o);
System.out.println("reVal2 的运行类型=" + reVal2.getClass());//Monster
}
}
class Monster {
}
class Boss {//类
public int age;
private static String name;
public Boss() {//构造器
}
public Monster m1() {
return new Monster();
}
private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}
public void hi(String s) {//普通 public 方法
System.out.println("hi " + s);
}
}JDBC 和数据库连接池
基本介绍
1.JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
2.Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。

JDBC 程序编写步骤
1.注册驱动-加载Driver 类
2.获取连接-得到Connection
3.执行增删改查-发送SQL给mysql执行
4.释放资源-关闭相关连接
JDBC 第一个程序
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 韩顺平
* @version 1.0
* 这是第一个 Jdbc 程序,完成简单的操作
*/
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
//前置工作: 在项目下创建一个文件夹比如 libs
// 将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
//1. 注册驱动
Driver driver = new Driver(); //创建 driver 对象
//2. 得到连接
// 老师解读
//(1) jdbc:mysql:// 规定好表示协议,通过 jdbc 的方式连接 mysql
//(2) localhost 主机,可以是 ip 地址
//(3) 3306 表示 mysql 监听的端口
//(4) hsp_db02 连接到 mysql dbms 的哪个数据库
//(5) mysql 的连接本质就是前面学过的 socket 连接
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hsp"); //密码
Connection connect = driver.connect(url, properties);
//3. 执行 sql
//String sql = "insert into actor values(null, '刘德华', '男', '1970-11-11', '110')";
//String sql = "update actor set name='周星驰' where id = 1";
String sql = "delete from actor where id = 1";
//statement 用于执行静态 SQL 语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql); // 如果是 dml 语句,返回的就是影响行数
System.out.println(rows > 0 ? "成功" : "失败");
//4. 关闭连接资源
statement.close();
connect.close();
}
}获取数据库连接 5 种方式
package com.hspedu.jdbc;
import com.mysql.jdbc.Driver;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author 韩顺平
* @version 1.0
* 分析java 连接mysql的5中方式
*/
public class JdbcConn {
//方式1
@Test
public void connect01() throws SQLException {
Driver driver = new Driver(); //创建driver对象
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hsp"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
//方式2
@Test
public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hsp"); //密码
Connection connect = driver.connect(url, properties);
System.out.println("方式2=" + connect);
}
//方式3 使用DriverManager 替代 driver 进行统一管理
@Test
public void connect03() throws IllegalAccessException, InstantiationException, ClassNotFoundException, SQLException {
//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
DriverManager.registerDriver(driver);//注册Driver驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第三种方式=" + connection);
}
//方式4: 使用Class.forName 自动完成注册驱动,简化代码
//这种方式获取连接是使用的最多,推荐使用
@Test
public void connect04() throws ClassNotFoundException, SQLException {
//使用反射加载了 Driver类
//在加载 Driver类时,完成注册
/*
源码: 1. 静态代码块,在类加载时,会执行一次.
2. DriverManager.registerDriver(new Driver());
3. 因此注册driver的工作已经完成
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
Class.forName("com.mysql.jdbc.Driver");
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第4种方式~ " + connection);
}
//方式5 , 在方式4的基础上改进,增加配置文件,让连接mysql更加灵活
@Test
public void connect05() throws IOException, ClassNotFoundException, SQLException {
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式5 " + connection);
}
}ResultSet[结果集]
1、表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
2、ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
3、next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false ,因此可以在while循环中使用循环来遍历结果集

package com.hspedu.jdbc.resultset_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* @author 韩顺平
* @version 1.0
* 演示select 语句返回 ResultSet ,并取出结果
*/
@SuppressWarnings({"all"})
public class ResultSet_ {
public static void main(String[] args) throws Exception {
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1. 注册驱动
Class.forName(driver);//建议写上
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
//3. 得到Statement
Statement statement = connection.createStatement();
//4. 组织SqL
String sql = "select id, name , sex, borndate from actor";
//执行给定的SQL语句,该语句返回单个 ResultSet对象
/*
+----+-----------+-----+---------------------+
| id | name | sex | borndate |
+----+-----------+-----+---------------------+-------+
| 4 | 刘德华 | 男 | 1970-12-12 00:00:00 |
| 5 | jack | 男 | 1990-11-11 00:00:00 |
+----+-----------+-----+---------------------+-------+
*/
/*
老韩阅读debug 代码 resultSet 对象的结构
*/
ResultSet resultSet = statement.executeQuery(sql);
//5. 使用while取出数据
while (resultSet.next()) { // 让光标向后移动,如果没有更多行,则返回false
int id = resultSet.getInt(1); //获取该行的第1列
//int id1 = resultSet.getInt("id"); 通过列名来获取值, 推荐
String name = resultSet.getString(2);//获取该行的第2列
String sex = resultSet.getString(3);
Date date = resultSet.getDate(4);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}
//6. 关闭连接
resultSet.close();
statement.close();
connection.close();
}
}Statement

-- 演示 sql 注入
-- 创建一张表
CREATE TABLE admin ( -- 管理员表
NAME VARCHAR(32) NOT NULL UNIQUE, pwd VARCHAR(32) NOT NULL DEFAULT '') CHARACTER SET utf8; -- 添加数据
INSERT INTO admin VALUES('tom', '123'); -- 查找某个管理是否存在
SELECT *
FROM admin
WHERE NAME = 'tom' AND pwd = '123'
-- SQL
-- 输入用户名 为 1' or
-- 输入万能密码 为 or '1'= '1
SELECT *
FROM admin
WHERE NAME = '1' OR' AND pwd = 'OR '1'= '1' ;
SELECT * FROM adminPreparedStatement

好处
1.不再使用+ 拼接sql语句,减少语法错误
2.有效的解决了sql注入问题!
3.大大减少了编译次数,效率较高
package com.hspedu.jdbc.preparedstatement_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
* @author 韩顺平
* @version 1.0
* 演示PreparedStatement使用
*/
@SuppressWarnings({"all"})
public class PreparedStatement_ {
public static void main(String[] args) throws Exception {
//看 PreparedStatement类图
Scanner scanner = new Scanner(System.in);
//让用户输入管理员名和密码
System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束
String admin_name = scanner.nextLine(); // 老师说明,如果希望看到SQL注入,这里需要用nextLine
System.out.print("请输入管理员的密码: ");
String admin_pwd = scanner.nextLine();
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//1. 注册驱动
Class.forName(driver);//建议写上
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
//3. 得到PreparedStatement
//3.1 组织SqL , Sql 语句的 ? 就相当于占位符
String sql = "select name , pwd from admin where name =? and pwd = ?";
//3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//3.3 给 ? 赋值
preparedStatement.setString(1, admin_name);
preparedStatement.setString(2, admin_pwd);
//4. 执行 select 语句使用 executeQuery
// 如果执行的是 dml(update, insert ,delete) executeUpdate()
// 这里执行 executeQuery ,不要在写 sql
ResultSet resultSet = preparedStatement.executeQuery(sql);
if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
System.out.println("恭喜, 登录成功");
} else {
System.out.println("对不起,登录失败");
}
//关闭连接
resultSet.close();
preparedStatement.close();
connection.close();
}
}