ch2 Java概述
JDK:
- JDK 的全称(Java Development Kit Java 开发工具包)
JDK = JRE + java 的开发工具 [java, javac,javadoc,javap 等] - JDK 是提供给 Java 开发人员使用的, 其中包含了 java 的开发工具, 也包括了 JRE。 所以安装了 JDK, 就不用在单独安装 JRE 了。
JRE:
- JRE(Java Runtime Environment Java 运行环境)
JRE = JVM + Java 的核心类库[类] - 包括 Java 虚拟机(JVM Java Virtual Machine)和 Java 程序所需的核心类库等, 如果想要运行一个开发好的 Java 程序,
计算机中只需要安装 JRE 即可
JDK、JRE、JVM关系
- **JDK = JRE + 开发工具集(**例如 Javac,java 编译工具等)
- JRE = JVM + Java SE 标准类库(java 核心类库)
- 如果只想运行开发好的 .class 文件 只需要 JRE
ch7 面向对象编程(基础)
现有的技术解决问题:
张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。 还有一只叫小花,今年 100 岁,花色。 请编写一个程序, 当用户
输入小猫的名字时, 就显示该猫的名字, 年龄, 颜色。 如果用户输入的小猫名错误, 则显示 张老太没有这只猫猫。
缺点:
- 不利于数据管理
- 效率低
- 不能满足新的需求
数组 ===>
(1)数据类型体现不出来
(2) 只能通过[下标]获取信息,造成变量名字和内容 的对应关系不明确
(3) 不能体现猫的行为
对象在内存中存在形式
![image-20230926102500754](https://img-blog.csdnimg.cn/44f81abf34ea4fbead0742bc32bbd1a2.jpeg)
Java 内存的结构分析
- 栈: 一般存放基本数据类型(局部变量)
- 堆: 存放对象(Cat cat , 数组等)
- 方法区: 常量池(常量, 比如字符串), 类加载信息
![image-20230926105408475](https://img-blog.csdnimg.cn/7cb00f5fe0ab415082284625330f1a92.jpeg)
成员方法的好处:
- 提高代码复用性
- 可以将实现的代码细节封装起来,供其他用户来调用即可
汉诺塔问题:
package ch07;
import java.util.Scanner;
public class HanoiTower {
public static void main(String[] args) {
Tower tower = new Tower();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入汉诺塔牌的个数");
int num = scanner.nextInt();
tower.move(num, 'A', 'B', 'C');
}
}
class Tower {
// num表示移动的个数,a,b,c 分别表示 A,B,C塔。将a移动到c,借助b
public void move(int num, char a, char b, char c) {
if(num == 1) {
System.out.println(a + "->" + c);
}else {
/*如果有多个盘,可以看成两个,最下面和上面的所有盘(num - 1)*/
/*(1)先移动上面的所有盘到b, 借助c*/
move(num - 1, a, c, b);
/*(2)把最下面的盘,移动到c*/
System.out.println(a + "->" + c);
/*(3)再把b中所有盘,移动到c,借助a*/
move(num - 1, b, a, c);
}
}
}
变量的作用域
![image-20230926215702104](https://img-blog.csdnimg.cn/5d00e62d514948b398dafcb8c5687abf.jpeg)
构造器
![image-20230926220429625](https://img-blog.csdnimg.cn/019712198649439e9fa85f48c640e613.jpeg)
对象创建以及构造器赋值的流程分析
![image-20230926221430169](https://img-blog.csdnimg.cn/21e6a1b1e0574c5fbd55f6d28fc9f523.jpeg)
![image-20230926221452644](https://img-blog.csdnimg.cn/de3e7d5ae8fe43049ee42a7ad95c0c9c.jpeg)
this 的注意事项和使用细节
-
this 关键字可以用来访问本类的属性、 方法、 构造器
-
this 用于区分当前类的属性和局部变量
-
访问成员方法的语法: this.方法名(参数列表);
-
访问构造器语法: this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一
条语句) -
this 不能在类定义的外部使用, 只能在类定义的方法中使用。
ch8 面向对象编程(中级)
Idea 常用快捷键
ctrl + D 删除当前行(自己设定的)
Ctrl+Alt+L 快速格式化代码
ctrl + alt+ ⬇ 复制到下一行 (自个设定的)
ctrl + alt + t 生成for、while、try catch等关键字
ctrl + j 显示所有的快捷键
查看一个类的层级关系 ctrl + H [学习继承后, 非常有用]
将光标放在一个方法上, 输入 ctrl + B , 可以定位到方法 [学继承后, 非常有用]
shift + Enter 直接换到下一行
ctrl + W 选中代码区域
ctrl + alt + ⬅/➡ 回到上次查看代码位置
ctrl+q 查看方法的文档注释
包的命名
访问修饰符范围
注意事项
封装
![image-20230927204012202](https://img-blog.csdnimg.cn/045a32cae3624426bd06ebf0dbf64d73.jpeg)
![image-20230927204030935](https://img-blog.csdnimg.cn/af5ce801720640ba8e24cd48f820517f.jpeg)
继承
继承带来的便利:
- 代码的复用性提高了
- 代码的扩展性和维护性提高
细节问题:
- 子类继承了所有的属性和方法, 非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访
问, 要通过父类提供公共的方法去访问 - 子类必须用父类的构造器, 完成父类的初始化 。(即先调用父类的构造器,再调用子类的构造器)
- 当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器[ 在子类构造器中有隐藏的super(),即调用父类的构造器 ], 如果父类没有提供无参构造器, 则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作, 否则, 编译不会通过。
- 如果希望指定去调用父类的某个构造器, 则显式的调用一下 : super(参数列表)
- super 在使用时, 必须放在构造器第一行(super 只能在构造器中使用)
- super() 和 this() 都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
- java 所有类都是 Object 类的子类, Object 是所有类的基类.
- 父类构造器的调用不限于直接父类! 将一直往上追溯直到 Object 类(顶级父类)
- 子类最多只能继承一个父类(指直接继承), 即 java 中是单继承机制。
- 不能滥用继承, 子类和父类之间必须满足 is-a 的逻辑关系 比如:Cat extends Animal 猫是一只动物(√) Person extends Music 人是一首音乐(×)
继承的本质分析
当子类继承父类,创建子类对象时,内存中的发生情况:建立其查找的关系
当子类访问属性信息时:
(1) 首先看子类是否有该属性
(2) 如果子类有这个属性, 并且可以访问, 则返回信息
(3) 如果子类没有这个属性, 就看父类有没有这个属性(如果父类有该属性, 并且可以访问, 就返回信息…)
(4) 如果父类没有就按照(3)的规则, 继续找上级父类, 直到 Object…
如果当父类的对象某属性为private修饰的时候,子类不可以访问该属性,只能通过public方法来返回。如 pulibc getAge()
课堂练习
案例1
main方法中:B b = new B(); 会输出什么
在B(String name)方法中有隐藏的super()。
因为B( )方法中有this()方法了,就不能再有super了。
输出 a,b name, b
案例2
main方法中:C c = new C(); 会输出什么
![image-20231009174057408](https://img-blog.csdnimg.cn/ad67a02f70bb444e9d13e13135945329.jpeg)
输出:
我是A类
hahah我是B类的有参构造
hahah我是c类的有参构造
我是c类的无参构造
super关键字
super 代表父类的引用, 用于访问父类的属性、 方法、 构造器
![image-20231009200441046](https://img-blog.csdnimg.cn/62e047773b184b6bb68d3f348744b55f.jpeg)
super给编程带来的便利
![image-20231009203449013](https://img-blog.csdnimg.cn/6703646307c343ec9ca44e64db1d2e8a.jpeg)
super和this的比较
![image-20231009203744107](https://img-blog.csdnimg.cn/1022a1f5a8ba4c828ca4de6763eabd56.jpeg)
重写/覆盖(override)
![image-20231009205624527](https://img-blog.csdnimg.cn/65f2b5e6b056448fade2eacac9b4547a.jpeg)
![image-20231009205643297](https://img-blog.csdnimg.cn/f5e1643a1df343f0b68bd3a6ae228ab8.jpeg)
重写和重载的比较
![image-20231009205913602](https://img-blog.csdnimg.cn/7f7ea1f11fe748eca9ddf3204aa87f79.jpeg)
多态
![image-20231009212724555](https://img-blog.csdnimg.cn/fe3f5c224a5e44b5bf316f833427eae1.jpeg)
代码复用性不高,不利于代码维护。
多态就是多种状态,在重载和重写中可以体现。但最重要的还是对象的多态。
多态建立在封装和继承之上。
对象的多态
![image-20231009215825644](https://img-blog.csdnimg.cn/9903c00649ff402dab4a960f6404ceea.jpeg)
使用多态的机制来解决主人喂食物的问题
/**
Dog extends Animal
Cat extends Animal
Bone extends Food
Fish extends Food
*/
Dog dog = new Dog("大黄");
Bone bone = new Bone("大棒骨");
tom.feed(dog, bone); /*传子类对象类型*/
Cat cat = new Cat("小猫");
Fish fish = new Fish("小黄鱼");
tom.feed(cat, fish);
public void feed(Animal animal, Food food) { //只需定义 animal food 即可,避免写多个feed方法对应不同类型的对象
System.out.println(animal.getName() + "吃" + food.getName());
}
多态向上转型
前提:两个对象(类)存在继承关系
![image-20231009222748446](https://img-blog.csdnimg.cn/02b285b440b948aab327e9221135ac79.jpeg)
多态向下转型
![image-20231010141944523](https://img-blog.csdnimg.cn/471f65dfea04424e8314dcfe2543f806.jpeg)
接上面代码
Amimal animal = new Cat(); // 向上转型
Cat cat = (Cat)animal; // 向下转型
![image-20231010142154907](https://img-blog.csdnimg.cn/9dd3840ead2547edb72828d09618775c.jpeg)
注意:
- 第二点,如上图,只能强转引用,而不能改变原有的对象。
- 第三点,只能将animal转换成Cat,而不能转换成Dog。
多态的细节
属性没有重写之说! 属性的值看编译类型。
instanceOf 比较操作符, 用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
AA aa = new AA();
System.out.println(aa instanceof AA); // true
AA aa2 = new BB();
System.out.println(aa2 instanceof BB); // true
课堂练习
![image-20231010144046010](https://img-blog.csdnimg.cn/f390181ce5c94f96844f0270b877598d.jpeg)
多态的动态绑定机制
-
当调用方法的时候,该方法会和该对象的内存地址/运行类型绑定。(先看运行类型)
举例:设Base base = new Sub(); Sub为子类,Base为父类。 当调用方法1时,子类不存在,则查找父类方法,父类方法有方法1,但方法1中调用了方法2,该方法2子类和父类都有。由于动态绑定,则方法2是调用子类,即运行类型。
-
当调用**对象属性的时,没有动态绑定机制,**哪里声明,哪里使用。
举例:假如调用的方法使用到了某属性,该属性在该类有声明,则直接使用该声明的属性值。因为对象属性没有动态绑定机制。
多态的应用
多态数组
public static void main(String[] args) {
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());
if(persons[i] instanceof Teacher) {
((Teacher) persons[i]).teach();
}else if(persons[i] instanceof Student) {
((Student) persons[i]).study();
}else {
System.out.println("有误");
}
}
}
多态参数
方法定义的形参为父类,实参类型(即传递的真实的参数)允许为子类。
Oject类详解
equals方法
![image-20231010164431350](https://img-blog.csdnimg.cn/2d5842d867b841cebd17e1035494271a.jpeg)
把光标放在equals方法,按ctrl+b,即可查看源码。
// Object的equals方法源码
public boolean equals(Object anObject) {
if (this == anObject) {//如果是同一个对象,即指向的地址相同
return true;//返回 true
}
// String类的重写equals方法,变成比较两个字符串的值是否相同
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
// 重写equals方法,判断值相等
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();韩顺平循序渐进学 Java 零基础
}
return false;
}
![image-20231010165306698](https://img-blog.csdnimg.cn/44274ca9d5f64af198517e7ac84e3051.jpeg)
hashcode方法
hashcode()返回该对象的哈希码值,支持此方法是为了提高哈希表的性能。
小结:
- 提高具有哈希结构的容器的效率!
- 两个引用, 如果指向的是同一个对象, 则哈希值肯定是一样的!
- 两个引用, 如果指向的是不同对象, 则哈希值是不一样的
- 哈希值主要根据地址号来的! 不能完全将哈希值等价于地址。
- 后面在集合中 hashCode 如果需要的话, 也会重写, 在讲解集合时, 老韩在说如何重写 hashCode()
toString方法
- 默认返回: 全类名+@+哈希值的十六进制
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// 例如返回com.czk.object@0x23fb32
- 重写 toString 方法, 打印对象或拼接对象时, 都会自动调用该对象的 toString 形式。
@Override
public String toString() {
return "Person1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
- 当直接输出一个对象时, toString 方法会被默认的调用, 比如 System.out.println(person1);
Finalize方法
package ch08;
public class Finalize_ {
public static void main(String[] args) {
Car bmw = new Car("宝马");
bmw = null;
//这时 car 对象就是一个垃圾,垃圾回收器就会回收(销毁)对象, 在销毁对象前, 会调用该对象的 finalize 方法
//程序员就可以在 finalize 中, 写自己的业务逻辑代码(比如释放资源: 数据库连接,或者打开文件..)
//如果程序员不重写 finalize,那么就会调用 Object 类的 finalize, 即默认处理
//但是由于gc算法不会立马释放
System.gc(); //这里调用gc方法主动释放
System.out.println("程序退出了...");
}
}
class Car {
private String name;
public Car(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("销毁汽车对象" + name);
System.out.println("释放某些资源");
}
}
- 当对象被回收时, 系统自动调用该对象的 finalize 方法。 子类可以重写该方法
- 什么时候被回收: 当某个对象没有任何引用时, 则 jvm 就认为这个对象是一个垃圾对象, 就会使用垃圾回收机制来销毁该对象, 在销毁该对象前, 会先调用 finalize 方法
- 垃圾回收机制的调用, 是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制
断点调试
重要提示:在断点调试过程中 ,是运行状态,是以对象的运行类型来执行的。
断点调试快捷键
- F8:跳过,逐行执行代码
- F7: 跳入(跳入方法内) alt+shift+f7 强行跳入
- shift + F8:跳出
- F9:resume,执行到下一个断点
![image-20231011204522172](https://img-blog.csdnimg.cn/ec2f6141175b4f09b20b3d04462e0e0f.jpeg)
注意:java默认不允许跳入源码,按一下设置就可以了。
![image-20231011210341436](https://img-blog.csdnimg.cn/bf5be9da826e4a66b947001dec5a7471.jpeg)
细节
断点可以在 debug 过程中, 动态的下断点
ch9 房屋出租系统
设计框架:分析从上往下,实现从下往上。
代码地址:D:\file\Java学习求职之路\1 java编程基础\韩顺平java教程\Mycode\src\ch08\houserent
ch10 面向对象编程(高级)
类变量
提出问题:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?, 编写程序解决。
![image-20231017113722205](https://img-blog.csdnimg.cn/63fd471e78914eb5915a93b010d8c429.jpeg)
静态变量是p1和p2所指向的实例对象共享的
![image-20231017113916508](https://img-blog.csdnimg.cn/6e7f217c9e9a4f139dd8fa75066a1c21.jpeg)
代码
public static void main(String[] args) {
Student student1 = new Student("a");
student1.count ++;
Student student2 = new Student("b");
student2.count ++;
Student student3 = new Student("c");
student3.count ++;
System.out.println(Student.count); // 3
}
class Student {
private String name;
public static int count = 0;
}
static变量保存在哪有两种说法:1. 堆中 2.方法区的静态域中。 和jdk版本不同而不同
不管static变量在哪里:
- static类变量是同一个类所有对象共享
- static类变量在类加载的时候就生成了
什么是类变量?
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
如何定义类变量
![image-20231017141926423](https://img-blog.csdnimg.cn/10a74066b5c940b39c53c16d0241d0d7.jpeg)
如何访问类变量
![image-20231017142000668](https://img-blog.csdnimg.cn/be834cac44804c14ac8b3e32c405a004.jpeg)
类变量使用细节
![image-20231017142952533](https://img-blog.csdnimg.cn/1327a23e5f524434b36ff2ebdb679872.jpeg)
类方法
类方法也叫静态方法。
形式如下:
- 访问修饰符 static 数据返回类型 方法名(){} [推荐]
- static 访问修饰符 数据返回类型 方法名(){}
使用方式:类名.类方法名 或者 对象名.类方法名 [前提:满足访问修饰符的访问权限和范围]
class Student {
public String name;
public static double totalFee = 0.0;
public Student(String name) {
this.name = name;
}
public static void payFee(double fee) {
Stu.totalFee += fee;
}
public void showFee() {
System.ou .println("总学费为...." + Student.totalFee)
}
}
使用场景
![image-20231017145049695](https://img-blog.csdnimg.cn/1a25cf3b3f4e4bf3bff2fcd97d3f52fe.jpeg)
注意和使用细节
![image-20231017145532101](https://img-blog.csdnimg.cn/17ec15142b314ef3a3f135fa455767ec.jpeg)
重点:1. 静态方法,只能访问静态成员(静态变量和静态方法)。2非静态方法,可以访问所有的成员。 3.在编写代码时,仍然要遵守访问权限规则
main方法
![image-20231017152907081](https://img-blog.csdnimg.cn/b2be91d5011c4ae9b8978643fe576f8e.jpeg)
第五点 参数可通过命令行传进去 :
public class Hello {
public static void main(String[] args) {
for(int i = 0; i < args.length; i ++) {
System.out.println("第"+i+"个参数=" + args[i]);
}
}
}
![image-20231017153037259](https://img-blog.csdnimg.cn/ed86cae66c7e4504a23d11cd048458ec.jpeg)
特别提示
-
在main方法中,可以直接调用main方法所在类的静态方法和静态属性
-
但是,不能直接访问该类中的非静态变量,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
public class Main01 { private int n1 = 10000; public static void main(String[] args) { Main01 main01 = new Main01(); System.out.printlin(main01.n1); } }
代码块
代码块又称初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。
![image-20231017165015967](https://img-blog.csdnimg.cn/9a6eb5e671e7411a8553e541876a759a.jpeg)
代码块的好处
![image-20231017165035995](https://img-blog.csdnimg.cn/9e5aa8226af541e4ad5cece711a21448.jpeg)
下面构造器中都有相同的语句,这样代码看起来比较冗余 ,这时我们可以把相同的语句, 放入到一个代码块中, 即可 。
这样当我们不管调用哪个构造器, 创建对象, 都会先调用代码块的内容。
代码块调用的顺序优先于构造器 。
public class Movie {
private String name;
private double price;
private String director;
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始....");
System.out.println("电影正式开始....");
}
public Movie(String name) {
//System.out.println("电影屏幕打开...");
//System.out.println("广告开始....");
//System.out.println("电影正式开始....");
this.name = name;
}
public Movie(String name, double price) {
//System.out.println("电影屏幕打开...");
//System.out.println("广告开始....");
//System.out.println("电影正式开始....");
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
this.name = name;
this.price = price;
this.director = director;
}
}
当调用Movie类的构造器的时候,首先会执行代码块中的内容。
代码块的使用细节
![image-20231017172517561](https://img-blog.csdnimg.cn/21b52f03c81f420cbcdd6e719bd3784d.jpeg)
当创建一个对象时,记住一个类的调用顺序
![image-20231017213951692](https://img-blog.csdnimg.cn/67fe023d4cd4420cab8d474d52310a3a.jpeg)
举例代码如下:
package ch10.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();
return;
}
}
class A {
private int n2 = getN2(); // 普通变量初始化
private static int n1 = getN1(); // 静态变量初始化
{
System.out.println("A类普通代码块");
}
static {
System.out.println("A类静态代码块");
}
public static int getN1(){
System.out.println("getN1 被调用");
return 100;
}
public int getN2() {
System.out.println("getN2 被调用");
return 200;
}
public A() {
System.out.println("调用构造方法");
}
}
/*
输出:
getN1 被调用
A类静态代码块
getN2 被调用
A类普通代码块
调用构造方法
*/
![image-20231017215255362](https://img-blog.csdnimg.cn/b513ea37ef944fc987f0608f88e5593f.jpeg)
构造器前面包含了super()和调用普通代码块,而静态相关代码块和属性初始化在类加载时就执行完毕了。
举例代码如下:
package ch10.codeblock_;
public class CodeBlockDetail03 {
public static void main(String[] args) {
BB bb = new BB();
}
}
class AA {
{
System.out.println("AA类普通代码块");
}
public AA() {
// 1.super()
// 2.调用本类普通代码块
System.out.println("AA类构造器");
}
}
class BB extends AA{
{
System.out.println("BB的普通代码块");
}
public BB() {
// 1.super()
// 2.调用本类普通代码块
System.out.println("BB构造器");
}
}
/*
AA类普通代码块
AA类构造器
BB的普通代码块
BB构造器
*/
第6点是综合。(第1.2点应该是静态属性初始化)
![image-20231017220142702](https://img-blog.csdnimg.cn/ec6099f911184639992ae895a5b50bb3.jpeg)
单例设计模式
![image-20231017222027574](https://img-blog.csdnimg.cn/55d35840841748dea37c4a4f01432c3b.jpeg)
什么是单例模式
单例(单个的实现)
- 所谓的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方式:1)饿汉式 2)懒汉式
步骤:
![image-20231017223227535](https://img-blog.csdnimg.cn/42bc57a4446e471caf80825d9da830a5.jpeg)
饿汉式单例
为什么是饿汉式:可能你还没又去使用实例,但是它还是会直接先创建一个实例。
//步骤[单例模式-饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是 static)
//3. 提供一个公共的 static 方法, 返回 gf 对象
package ch10.single_;
public class SingleTon01 {
public static void main(String[] args) {
GirlFriend instance1 = GirlFriend.getInstance();
System.out.println(instance1);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
}
class GirlFriend {
private String name;
/*私有化构造器,防止new*/
private GirlFriend(String name) {
this.name = name;
}
/*为了能够在静态方法中, 返回 gf 对象, 需要将其修饰为 static*/
private static GirlFriend gf = new GirlFriend("lqy");
/*这里static是因为,如果不是静态方法的话,还是必须new再调用该方法,那就没意义了*/
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
/**
GirlFriend{name='lqy'}
GirlFriend{name='lqy'}
true
/
单例对象,通常是重量级别的对象,饿汉式可能造成了创建了对象,但是没有使用。
懒汉式单例
package ch10.single_;
public class SingleTon02 {
public static void main(String[] args) {
Cat instance1 = Cat.getInstance();
System.out.println(instance1);
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
}
class Cat {
private String name;
private static Cat cat;
private Cat(String name) {
System.out.println("构造器被调用");
this.name = name;
}
public static Cat getInstance() {
if(cat == null) {
cat = new Cat("小花猫");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
/**
构造器被调用
Cat{name='小花猫'}
Cat{name='小花猫'}
true
*/
懒汉式,只有当用户使用getInstance时,才能创建并返回Cat对象。
总结
![image-20231018115305218](https://img-blog.csdnimg.cn/1c69c8f95f2f437285e0b1a13e45e861.jpeg)
final关键字
![image-20231018115926005](https://img-blog.csdnimg.cn/d5c50be4f2d04aaa89e96178c738f119.jpeg)
使用细节
![image-20231018191859526](https://img-blog.csdnimg.cn/43aad7e8c02a4efab8a4d9ef8437c17d.jpeg)
![image-20231018192517068](https://img-blog.csdnimg.cn/3367a5eb8b7845769dda17be6b605eea.jpeg)
第7点代码举例:
package ch10.final_;
public class FinalDetail02 {
public static void main(String[] args) {
System.out.println(AA.num);
}
}
class AA {
public final static int num = 1000; // 搭配使用不会导致类加载,底层编译器做了优化处理。
static {
System.out.println("静态代码块被执行..");
}
}
/*
输出:1000
没有进行类加载
*/
抽象类
abstra关键字
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法, 那么这个类就是抽象类。
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法, 这个方法就是抽象方法,用abstract来修饰该类就是抽象类。
![image-20231018195238933](https://img-blog.csdnimg.cn/f1f95fcd5f184c3d9eeab11223e441df.jpeg)
使用细节
![image-20231018195308167](https://img-blog.csdnimg.cn/934ae88bcb014b4b834b8176e78f4e46.jpeg)
![image-20231018195604093](https://img-blog.csdnimg.cn/94a6dec34de94218b9a9383f136894ff.jpeg)
第8点示例代码
package ch10.abstract_;
public class AbstractDetail02 {
}
abstract class C {
abstract public void say();
}
abstract class D extends C{
}
class E extends C {
@Override
public void say() {
System.out.println("你好");
}
}
![image-20231018201154455](https://img-blog.csdnimg.cn/5d572cf5e5fd466dacdec6d4d50bc321.jpeg)
练习
![image-20231018202002556](https://img-blog.csdnimg.cn/8127d455d8ad42ffb641116c0ac69740.jpeg)
抽象类的实践-模板设计模式
![image-20231018202519098](https://img-blog.csdnimg.cn/abb97b9fef504be69ccabd83ea535a62.jpeg)
![image-20231018202533113](https://img-blog.csdnimg.cn/5953b6f852634b81bcc23368926bda8b.jpeg)
实践
![image-20231018204225825](https://img-blog.csdnimg.cn/1415252c583849338819db29fbacb84a.jpeg)
![image-20231018204240429](https://img-blog.csdnimg.cn/7ac3de20373f40f8b21e27f0792d15fb.jpeg)
代码
package ch10.abstract_;
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
abstract class Template {
public abstract void job(); //抽象方法
public void calculateTime() {
long start = System.currentTimeMillis();
job(); //动态绑定机制
long end = System.currentTimeMillis();
System.out.println("任务执行时间" + (end - start));
}
}
class AA extends Template{
@Override
public void job() {
long num = 0;
for (int i = 0; i <= 800000; i++) {
num += i;
}
}
}
class BB extends Template {
@Override
public void job() {
long num = 0;
for (int i = 0; i <= 800000; i++) {
num *= i;
}
}
}
注意:子类AA和BB调用方法父类的calculateTime方法时,会调用job()方法。此时根据动态绑定机制,由于运行类型是子类,则调用子类的job方法。
接口
![image-20231018212044065](https://img-blog.csdnimg.cn/266a9f70a44a49dbac2e654537882b6b.jpeg)
使用
![image-20231018213911876](https://img-blog.csdnimg.cn/a5b84ce91c114e7984655371c7b42a47.jpeg)
如果项目经理要3个程序员写mySQL、Oracle、DB2的数据库连接和关闭方法。则可能会出现3个程序员不同的命名规范。
使用接口则可以更加规范,并且使用起来可以统一管理。
// DBInterface.java
package ch10.interface_;
public interface DBInterface { //项目经理
public void connect();
public void close();
}
//MysqlDB.java
package ch10.interface_;
public class MysqlDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接mysql");
}
@Override
public void close() {
System.out.println("关闭mysql");
}
}
//OracleDB.java
package ch10.interface_;
public class OracleDB implements DBInterface {
@Override
public void connect() {
System.out.println("连接Orcal");
}
@Override
public void close() {
System.out.println("关闭Orcal");
}
}
//Interface03.java
package ch10.interface_;
public class Interface03 {
public static void main(String[] args) {
MysqlDB mysqlDB = new MysqlDB();
t(mysqlDB);
OracleDB oracleDB = new OracleDB();
t(oracleDB);
}
/*这里静态只是为了不创建Interface03对象*/
public static void t(DBInterface db) {
db.connect();
db.close();
}
}
/**
输出:
连接mysql
关闭mysql
连接Orcal
关闭Orcal
*/
但是接口的妙用远不止这些。
细节和注意事项
![image-20231018215503290](https://img-blog.csdnimg.cn/0ecbbabb040e4e43b16025b7265f72b5.jpeg)
![image-20231018220129770](https://img-blog.csdnimg.cn/8eb0292a71824571aa410939d649b04a.jpeg)
实现接口vs继承类
![image-20231018221109954](https://img-blog.csdnimg.cn/f9dbfb19dac74666bc94ad6375861de6.jpeg)
代码:
package ch10.interface_;
public class ExtendvsInterface {
public static void main(String[] args) {
LittleMonkey littleMonkey = new LittleMonkey("小猴子");
littleMonkey.climb();
littleMonkey.swimming();
littleMonkey.flying();
}
}
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public void climb() {
System.out.println(name + "会爬树");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
interface Fishable {
void swimming();
}
interface Birdable {
void flying();
}
class LittleMonkey extends Monkey implements Fishable, Birdable{
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + "通过学习,可以像鱼儿一样游泳");
}
@Override
public void flying() {
System.out.println(getName() + "通过学习,可以像鸟儿一样飞翔");
}
}
小结:
- 当子类继承了父类, 就自动的拥有父类的功能。
- 如果子类需要扩展功能, 可以通过实现接口的方式扩展。
- 可以理解 实现接口 是 对 java 单继承机制的一种补充。
![image-20231018221735783](https://img-blog.csdnimg.cn/845b47d34d0d46f19d68b7525af27325.jpeg)
接口的多态
![image-20231018222328300](https://img-blog.csdnimg.cn/d02f9476570e487285581eea6f4622e4.jpeg)
第一点代码示例:
// 多态1:形参是接口类型,实际参数是实现接口的类的对象
public static void t(DBInterface db) {
db.connect();
db.close();
}
// 多态2:引用指向实现接口的类的对象
Interface01 interface01 = new AAA();
第二点代码:
/*
call()是phone中特定的方法
Phone类和Camea类都重写了work方法
*/
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
for(int i = 0; i < usbs.length; i++) {
usbs[i].work();//动态绑定..
//和前面一样, 我们仍然需要进行类型的向下转型
if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_
((Phone_) usbs[i]).call();
}
}
interface Usb{
void work();
}
第三点代码:
public static void main(String[] args) {
//接口类型的变量可以指向, 实现了该接口的类的对象实例
IG ig = new Teacher();
//如果 IG 继承了 IH 接口, 而 Teacher 类实现了 IG 接口
//那么, 实际上就相当于 Teacher 类也实现了 IH 接口.
//这就是所谓的 接口多态传递现象.
IH ih = new Teacher();
}
interface IH{}
interface IG extends IH{}
class Teacher implement IG{}
内部类
如果定义类在局部位置(方法中/代码块) :(1) 局部内部类 (2) 匿名内部类
定义在成员位置 (1) 成员内部类 (2) 静态内部类
![image-20231020121629206](https://img-blog.csdnimg.cn/9e617d9db7664b1484fb7eb7f1651443.jpeg)
![image-20231020121643723](https://img-blog.csdnimg.cn/48866e01248649b98b505a4f3dba26ac.jpeg)
内部类的分类
![image-20231020121707545](https://img-blog.csdnimg.cn/07c382aeb198479cbe70e107675975db.jpeg)
局部内部类的使用
![image-20231020140325653](https://img-blog.csdnimg.cn/25056f37f678482f947f240a2ac9b7c9.jpeg)
![image-20231020141605040](https://img-blog.csdnimg.cn/494e32d565e24b2d92217f9495fdb9cb.jpeg)
代码示例:
package ch10.innerclass_;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
public class InnerClass01 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.m1();
}
}
class Outer {
private int n1 = 10;
public void m2() {
System.out.println("调用外部类的m2");
}
public void m1(){
class inner01 {
private int n1 = 50;
public void f1() {
/*局部内部类可以直接访问外部类的成员, 比如m2()*/
/*但是如果外部类和局部内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 使用 (外部类名.this.成员) 去访问*/
/* Outer02.this 本质就是外部类的对象, 即哪个对象调用了 m1, Outer02.this 就是哪个对象*/
System.out.println("n1: " + n1 + " " + "外部类的n1: " + Outer.this.n1);
m2();
}
}
/*外部类要访问内部类的成员,需要在方法中,创建 Inner02 对象, 然后访问*/
inner01 inner01 = new inner01();
inner01.f1();
}
}
-
局部内部类可以直接访问外部类的成员,如果外部类和局部内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 使用 (外部类名.this.成员) 去访问。
-
外部类要访问内部类的成员,需要在方法中,创建 Inner02 对象, 然后访问。
-
Outer02.this 本质就是外部类的对象。
匿名内部类
匿名内部类的使用
![image-20231020150120300](https://img-blog.csdnimg.cn/88f09601c7ad432ca83c8972abb278a6.jpeg)
代码
package ch10.innerclass_;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 {
private int n1 = 10;
public void method() {
/*基于接口的匿名内部类*/
/*
1. 需求: 想使用 IA 接口,并创建对象
2. 传统方式, 是写一个类, 实现该接口, 并创建对象
3. 老韩需求是 Tiger 类只是使用一次, 后面再不使用
4. 可以使用匿名内部类来简化开发
5. tiger 的编译类型 ? IA
6. tiger 的运行类型 ? 就是匿名内部类 Outer04$1
7. jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址返回tiger
8. 匿名内部类使用一次, 就不能再使用(是类不能再使用,不是对象)
*/
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫唤.....");
}
};
//底层会分配类名Outer04$1,相当于
// class Outer04$1 implements IA {
// @Override
// public void cry() {
//
// }
// }
System.out.println("tiger的运行类型是" + tiger.getClass());
tiger.cry();
tiger.cry();
/*基于类的匿名内部类*/
//1. father 编译类型 Father
//2. father 运行类型 Outer04$2
//3. 底层会创建匿名内部类
//4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
//5. 注意("jack") 参数列表会传递给 构造器
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了 test 方法");
}
}
*/
Father father = new Father("jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
};
System.out.println("fater的运行类型是" + father.getClass());
father.test();
}
}
interface IA {
void cry();
}
//class Tiger implements IA {
//
// @Override
// public void cry() {
//
// }
//}
class Father {
public Father(String name) { //构造器
}
public void test(){}
}
/*
tiger的运行类型是class ch10.innerclass_.Outer04$1
老虎叫唤.....
老虎叫唤.....
fater的运行类型是class ch10.innerclass_.Outer04$2
匿名内部类重写了test方法
*/
匿名内部类注意事项和细节
![image-20231020155439639](https://img-blog.csdnimg.cn/daee6315c83e4db9a79abb30524851c3.jpeg)
![image-20231020155515631](https://img-blog.csdnimg.cn/c55838e97b7045808b120cd1241d2509.jpeg)
匿名内部类的最佳实践
将匿名内部类当作实参直接传递,简洁高效。
![image-20231020164617721](https://img-blog.csdnimg.cn/c27df77fd8d3440f84184d97860012ab.jpeg)
![image-20231020164748290](https://img-blog.csdnimg.cn/5e70f12f15124a048c0538ffc3753046.jpeg)
package ch10.innerclass_;
public class InnerClassExercise02 {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
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.ring();
}
}
匿名内部类包含的特点:1. 继承 2.多态 3.动态绑定 4.内部类
成员内部类
![image-20231020165833777](https://img-blog.csdnimg.cn/5fccb1c3d28c41b1a8d064d074be78c1.jpeg)
![image-20231020172154422](https://img-blog.csdnimg.cn/6c04f6d7b022406980c83aef526b74d6.jpeg)
![image-20231020172203717](https://img-blog.csdnimg.cn/23b55107ea0b491b8cba0a727be5e565.jpeg)
代码
package com.hspedu.innerclass;
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的三种方式
//老韩解读
// 第一种方式
// outer08.new Inner08(); 相当于把 new Inner08()当做是outer08成员
// 这就是一个语法,不要特别的纠结.
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部类中,编写一个方法,可以返回 Inner08对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 { //外部类
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
//1.注意: 成员内部类,是定义在外部内的成员位置上
//2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
public class Inner08 {//成员内部类
private double sal = 99.8;
private int n1 = 66;
public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
//,可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("n1 = " + n1 + " name = " + name + " 外部类的n1=" + Outer08.this.n1);
hi();
}
}
//方法,返回一个Inner08实例
public Inner08 getInner08Instance(){
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
静态内部类
![image-20231020175412259](https://img-blog.csdnimg.cn/b1b02ae447304474a9fb4518be0c1bec.jpeg)
main {
//外部其他类 使用静态内部类
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
}
class Outer10 {
private static String name = "张三";
//Inner10就是静态内部类
//1. 放在外部类的成员位置
//2. 使用static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
//5. 作用域 :同其他的成员,为整个类体
static class Inner10 {
private static String name = "xxx";
public void say() {
System.out.println(name + " 外部类name= " + Outer10.name);
}
}
}
ch11 枚举和注解
枚举
枚举是一组常量的集合。
可以这里理解: 枚举属于一种特殊的类, 里面只包含一组有限的特定的对象。
枚举的两种实现方式
- 自定义类实现枚举
2) 使用 enum 关键字实现枚举
自定义实现枚举
![image-20231024142146918](https://img-blog.csdnimg.cn/a0578e99bb7743b89b35172855164022.jpeg)
代码
class Season {
private String name;
private String desc;
public Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public static final Season SPRING = new Season("春天", "温暖");
public static final Season SUMMER = new Season("夏天", "炎热");
public static final Season AUTUMN = new Season("秋天", "凉爽");
public static final Season WINTER = new Season("冬天", "寒冷");
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
- 构造器私有化
2) 本类内部创建一组对象[四个 春夏秋冬]
3) 对外暴露对象(通过为对象添加 public final static 修饰符)
4) 可以提供 get 方法, 但是不要提供 set
enum关键字实现枚举
package ch11.enum_;
/**
* @author czk
* @date: 2023/10/24
* @version: 1.0
*/
public class Enumeration02 {
}
enum Season {
SUMMER("夏天", "炎热"), WINTER("冬天", "寒冷"), SPRINTG();
private String name;
private String desc;
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
private Season(){}
}
- 当我们使用 enum 关键字开发一个枚举类时, 默认会继承 Enum 类, 而且是一个 final 类
- 传统的 public static final Season2 SPRING = new Season2(“春天”, “温暖”); 简化成 SPRING(“春天”, “温暖”), 这里必须知道, 它调用的是哪个构造器.
- 如果使用无参构造器 创建 枚举对象, 则实参列表和小括号都可以省略
- 当有多个枚举对象时, 使用,间隔, 最后有一个分号结尾
- 枚举对象必须放在枚举类的行首.
enum常用的方法
- toString:Enum 类已经重写过了, 返回的是当前对象名,子类可以重写该方法, 用于返回对象的属性信息
- name: 返回当前对象名(常量名) , 子类中不能重写
- ordinal: 返回当前对象的位置号, 默认从 0 开始
- values: 返回当前枚举类中所有的常量
- valueOf: 将字符串转换成枚举对象, 要求字符串必须为已有的常量名, 否则报异常!
- compareTo: 比较两个枚举常量, 比较的就是编号! 返回编号相减的数。
enum实现接口
- 使用 enum 关键字后, 就不能再继承其它类了, 因为 enum 会隐式继承 Enum, 而 Java 是单继承机制。
- 枚举类和普通类一样, 可以实现接口, 如下形式。
enum 类名 implements 接口 1, 接口 2{}
注解
- 注解(Annotation)也被称为元数据(Metadata), 用于修饰解释 包、 类、 方法、 属性、 构造器、 局部变量等数据信息。
- 和注释一样, 注解不影响程序逻辑, 但注解可以被编译或运行, 相当于嵌入在代码中的补充信息。
- 在 JavaSE 中, 注解的使用目的比较简单, 例如标记过时的功能, 忽略警告等。 在 JavaEE 中注解占据了更重要的角色, 例如用来配置应用程序的任何切面, 代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。
三个基本的 Annotation:
- @Override: 限定某个方法, 是重写父类方法, 该注解只能用于方法
- @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
- @SuppressWarnings: 抑制编译器警告
Annotation注解
Deprecated注解
SuppressWarnings注解
-
当我们不希望看到这些警告的时候, 可以使用 SuppressWarnings 注解来抑制警告信息
-
在{“”} 中, 可以写入你希望抑制(不显示)警告信息
SuppressWarnings 作用范围是和你放置的位置相关。
源码的@Target为该注解在哪些地方使用:@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
![image-20231024162654373](https://img-blog.csdnimg.cn/2d54eac8ff00492fb7c672abc233469c.jpeg)
第十章作业
第一题
![image-20231024212728441](https://img-blog.csdnimg.cn/bcb1e8bdbae848688940216f7da83ca2.jpeg)
注意: 当进行Car c1 = new Car(100);的时候,static String color = "white"不会再执行,c1与c共享color。
ch12 异常
介绍
![image-20231026165347136](https://img-blog.csdnimg.cn/730b5eb4faa4436388bb9e47cabf477e.jpeg)
异常体系图
![image-20231026170522274](https://img-blog.csdnimg.cn/1cb31cf0847b4d51b3aafaaf7bacbfdb.jpeg)
![image-20231026171137272](https://img-blog.csdnimg.cn/56d68ec19bdd4c3c804aa4b22820fa86.jpeg)
常见运行时异常
- NullPointerException 空指针异常
2) ArithmeticException 数学运算异常
3) ArrayIndexOutOfBoundsException 数组下标越界异常
4) ClassCastException 类型转换异常
5) NumberFormatException 数字格式不正确异常[]
常见编译异常
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
![image-20231026173650037](https://img-blog.csdnimg.cn/1ad6830120aa431ab3010032741ab894.jpeg)
异常处理
![image-20231026222408281](https://img-blog.csdnimg.cn/33d68377deaf4b7a8d5676e872ad5000.jpeg)
![image-20231026222425428](https://img-blog.csdnimg.cn/d31ec02b99db449fa0cea53fad0655a5.jpeg)
![image-20231026222439584](https://img-blog.csdnimg.cn/fe18b41d6e324fb19a174858f9e84c93.jpeg)
try catch使用细节
![image-20231026222520997](https://img-blog.csdnimg.cn/ab007490bfa343988e1ff94c3b2af964.jpeg)
![image-20231026222533495](https://img-blog.csdnimg.cn/8e3bea61a899431d922be425e0518860.jpeg)
![image-20231026223325158](https://img-blog.csdnimg.cn/75f3214fedda46799db05ae85dca0b84.jpeg)
![image-20231026223348997](https://img-blog.csdnimg.cn/279bcab69c574591b3edca75ea6ee979.jpeg)
try-catch执行顺序小结
![image-20231028141810597](https://img-blog.csdnimg.cn/e60857dbbdf34c2aa5f5f019a1e55c85.jpeg)
throws异常处理
![image-20231028144138086](https://img-blog.csdnimg.cn/96d9faa8660349369b69a1d330320857.jpeg)
![image-20231028144155294](https://img-blog.csdnimg.cn/4d04f44007bc4c849b459dee4e99373e.jpeg)
throws与throw的区别
![image-20231028145150793](https://img-blog.csdnimg.cn/b58f6255670f45d0b36f6e366088fe6e.jpeg)
/*自定义异常*/
class AgeException extends RuntimeException {
public AgeException(String message){
super(message);
}
}
// main方法中
if(!(age >= 18 && age <= 120)) {
throw new AgeException("年龄需要在 18~120 之间");
}
例题
![image-20231028145132966](https://img-blog.csdnimg.cn/b72544b0b16643cc860de96e52ded55a.jpeg)
注意:finally必须执行。即使捕获到了异常有return语句或抛出异常等语句,也要先执行finally语句,再执行return或抛出异常等。
ch13 常用类
包装类
- 针对八种基本数据类型相应的引用类型—包装类
2) 有了类的特点, 就可以调用类中的方法
![image-20231028161608443](https://img-blog.csdnimg.cn/0f51e553ce274a28bb402fd81af4f5fb.jpeg)
包装类的装箱拆箱
![image-20231028164442723](https://img-blog.csdnimg.cn/e6c2a573edba4fbea10c5caec0621545.jpeg)
// 手动装箱
int n1 = 10;
Integer integer1 = new Integer(n1);
// 手动拆箱
int i = Integer.valueOf(n1);
//jdk5后自动装拆
int n2 = 50;
Integer integer2 = n2;//底层使用的是 Integer.valueOf(n2)
int n3 = integer2; //底层仍然使用的是 intValue()方法
课堂测试题
![image-20231028192642691](https://img-blog.csdnimg.cn/61cd541210b4446c9e8e47d7b4906e86.jpeg)
这是经典面试题: 三元运算符是一个整体,整数1应该转为浮点数1。
Integer面试题
![image-20231028194919238](https://img-blog.csdnimg.cn/547c1dffa86242d3a0d41bf2fa2e9e4c.jpeg)
当int数值在-128~127 ,不new,直接返回。已经new好了-128~127的Integer对象在cache数组中。
String类
![image-20231028210647606](https://img-blog.csdnimg.cn/b34dad55b34c4aef86b08bf40879493c.jpeg)
创建String对象的两种方式
![image-20231028210719566](https://img-blog.csdnimg.cn/885d9f98a5dc4ec1b3283eff4d7b6210.jpeg)
![image-20231028210731782](https://img-blog.csdnimg.cn/9f48d3e3b7564ddf986812bcc3910244.jpeg)
字符串的特性
- String是一个final类,代表不可变的字符序列。
- 字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的。
题1:
![image-20231029114841669](https://img-blog.csdnimg.cn/5ce6d7184244436b889e70d2f3692f14.jpeg)
注意是创建了两个对象在常量池中。
题2:
![image-20231029115013333](https://img-blog.csdnimg.cn/36ad27c8ef6e4f02b93037181e0240b8.jpeg)
String c = a + b 源码过程:
- StringBuilder sb = new StringBuilder();
- sb.append(a); sb.append(b);
- sb.toString(){return new String(value)}
c是新new出来的对象。
String c = aString a = "hello";
String c = a + b;
System.out.println(c == "helloabc"); //false
String d = "hello" + "abc";
System.out.println(d == "helloabc"); //true 直接看池 d指向常量池
题3:
![image-20231029122744394](https://img-blog.csdnimg.cn/e543d1b21a63439caf73f4ba58789727.jpeg)
注意:
- change方法中,传进去的是str参数,它指向的是value,而不是指向value所指向的hsp。所以局部变量str变为指向新new变量池中的java。而ex.str仍然指向str开辟的value对象空间,不影响它的值。
- ch数组不是在常量池中,是在堆中。ex.ch传入change方法,由于局部变量ch也指向java,则它的修改也会导致ex.ch的修改。
输出:hsp and hava
String的常用方法
![image-20231029150937191](https://img-blog.csdnimg.cn/af469762df904c49ba82589fd49eb9bf.jpeg)
![image-20231029151007904](https://img-blog.csdnimg.cn/6057bbb6d80642dc98145e277c55f171.jpeg)
StringBuffer类
![image-20231029222925538](https://img-blog.csdnimg.cn/1d26c75625954570861ef082cc3021f7.jpeg)
创建一个StringBuffer对象的源码
// StringBuffer构造器
public StringBuffer() {
super(16);
}
// 父类
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
- StringBuffer 的直接父类 是 AbstractStringBuilder
- StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
- 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final
- StringBuffer 是一个 final 类, 不能被继承
- 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
![image-20231029223037765](https://img-blog.csdnimg.cn/330ddccd456c46d296153abacb6c1efa.jpeg)
StringBuffer常用方法
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏 100true10.5"
//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除 11~14 的字符 [11, 14)
*/
s.delete(11, 14);
//改
//老韩解读, 使用 周芷若 替换 索引 9-11 的字符 [9,11)
s.replace(9, 11, "周芷若");
//插
//老韩解读, 在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
s.insert(9, "赵敏");
StringBuffer与String相互转换
//看 String——>StringBuffe
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);
例题:
![image-20231029223947514](https://img-blog.csdnimg.cn/03b25bba25ef40248bd8e6a572ab3821.jpeg)
sb指向"null"字符串
StringBuilder类
![image-20231030103217776](https://img-blog.csdnimg.cn/da08715a93d645df9a593920f75fb775.jpeg)
String、StringBuffer、StringBuilder比较
![image-20231030103302216](https://img-blog.csdnimg.cn/7cbe02fb30ea48959a0f83853b82a5b3.jpeg)
三者选择
![image-20231030103348648](https://img-blog.csdnimg.cn/d3d97230edc54370847f60f77c906443.jpeg)
Arrays类
- Arrays.toString(arr) 返回数组的字符串格式
- Arrays.sort
- Arrays.binarySearch
- Arrays.copyof 数组元素复制
- Arrays.fill(num, 99) 使用 99 去填充 num 数组, 可以理解成是替换原理的元素
- Arrays.asList(2,3,4,5,6,1) 返回的 asList 编译类型 List(接口) asList 运行类型 java.util.Arrays#ArrayList, 是 Arrays 类的 静态内部类
System类
-
exit退出当前程序
-
arraycopy:复制数组元素,比较适合底层调用。一般使用Arrays.copyOf完成复制数组
int[] src = {1, 2, 3}; int[] des = new int[3]; /* 五个参数 原数组、复制原数组起始位置、目标数组、复制到目标位置起始位置、复制长度 */ System.arraycopy(src, 0, des, 0, 3); for(int x : des) { System.out.println(x); }
-
currentTimeMillens:返回当时时间距离1970-1-1的毫秒数
-
gc: 运行垃圾回收机制 System.gc();
BigInteger、BigDecimal
// 大数
BigInteger a = new BigInteger("233243244234234214214"); //注意是用字符串传参
BigInteger b = new BigInteger("2314"); //注意是用字符串传参
a.add(b);
a.subtract(b);
a.multiply(b);
a.divide(b);
// 大精度
BigDecimal c = new BigDecimal("1.11111111111244");
Date类(第一代)
//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));
Calendar类(第二代)
//老韩解读
//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) );
前两代的不足:
![image-20231030221349553](https://img-blog.csdnimg.cn/310a6c3c77d64115bf4f7f4c05cfc18d.jpeg)
LocalDate类(第三代)
![image-20231030221445091](https://img-blog.csdnimg.cn/66e48c4a3b994515b9131cc70c5a001c.jpeg)
//第三代日期
//老韩解读
//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));
本章作业
![image-20231030223250095](https://img-blog.csdnimg.cn/2f6893034e0647e38068b86a1fdfc7c0.jpeg)
ch14 集合
集合的框架体系
![image-20231031215943746](https://img-blog.csdnimg.cn/cfccc83538724e5590ff3c6f90d952d4.jpeg)
![image-20231031215955534](https://img-blog.csdnimg.cn/964ff07d39ab4021a50f9ad0fb51acf9.jpeg)
Collection接口和常用方法
![image-20231031220232458](https://img-blog.csdnimg.cn/695488bd559544269909cdb2a0407025.jpeg)
Collection 接口常用方法,以实现子类 ArrayList 来演示.
![image-20231031220902913](https://img-blog.csdnimg.cn/fc3bc97c64bb4e51af5bbc6182c1c394.jpeg)
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接口遍历(Iterator(迭代器) )
![image-20231031221526968](https://img-blog.csdnimg.cn/445b2a3b2ed441a7975639767165d4db.jpeg)
![image-20231031221540877](https://img-blog.csdnimg.cn/8dcf377f3af947fcb375ee7aef38dd72.jpeg)
Collection col = new ArrayList();
col.add("fwef");
col.add(234);
Iterator iterator = col.iterator();
// 快捷生成 itit
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();
Collection接口遍历对象方式for 循环增强
// for (Object book : col) {
// System.out.println("book=" + book);
// }
for (Object o : col) {
System.out.println("book=" + o);
}
- 使用增强for, 在Collection集合
- 增强for, 底层仍然是迭代器
- 增强for可以理解成就是简化版本的 迭代器遍历
- 快捷键方式 大写的I
List接口和常用方法
![image-20231031223758306](https://img-blog.csdnimg.cn/52d325955b694af2857ed8314661ecde.jpeg)
List遍历方式
ArrayList
注意事项
![image-20231101215158863](https://img-blog.csdnimg.cn/32d71ccc32014cab8a36115ccf4773d8.jpeg)
底层操作机制和源码
![image-20231101215807128](https://img-blog.csdnimg.cn/54fe90161f28434e9c5712095438e1d8.jpeg)
transient 表示瞬间、短暂的,表示该属性不会被序列化。
ArrayList list = new ArrayList();
for (int i = 1; i <= 10; i++) {
list.add(i);
}
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
注意:Arrays.copyOf()不会覆盖原来的数据,上图用法可以延长列表长度。
Vector
![image-20231102215255490](https://img-blog.csdnimg.cn/edf3555df91b4ca28d54d3867b4a97dc.jpeg)
![image-20231102215307202](https://img-blog.csdnimg.cn/3d939be7357548338ade059f35eaec0b.jpeg)
LinkList
介绍
![image-20231102221605714](https://img-blog.csdnimg.cn/6761d0da595d48b3802449b31c5de185.jpeg)
![image-20231102221616595](https://img-blog.csdnimg.cn/44bda5ca43a147a88f2aca1150321539.jpeg)
源码
LinkedList list = new LinkedList();
list.add(1)
源码步骤:
-
public LinkedList() { }
-
public boolean add(E e) { linkLast(e); return true; }
-
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
/*
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
*/
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
```java
list.remove();
源码步骤:
默认删除首结点
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return 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比较
![image-20231102221810672](https://img-blog.csdnimg.cn/2ba5f510d52c4e5bb073c93ac6a165bf.jpeg)
Set接口和常用方法
![image-20231102222558022](https://img-blog.csdnimg.cn/1156f670a9ed4156ae582bd6efb05d70.jpeg)
常用方法和 List 接口一样, Set 接口也是 Collection 的子接口, 因此, 常用方法和 Collection 接口一样.
遍历方式:
![image-20231102222629318](https://img-blog.csdnimg.cn/e53e7ccab05b4d7faf5f41a97fc687fe.jpeg)
HashSet
![image-20231102222927516](https://img-blog.csdnimg.cn/17bc3aba6a3e4df9ae829bbe3afc3ce5.jpeg)
案例
HashSet set = new HashSet();
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok 因为是两个不同的对象
// 经典面试题
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了.为什么?
底层机制说明
HashSet hashSet = new HashSet();
hashSet.add("java");
hashSet.add("hsp");
hashSet.add("java");
源码分析:
//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)
public V put(K key, V value) { // key为e, value为PRESENT
return putVal(hash(key), key, value, false, true);
}
// 4.得到 key 对应的 hash 值, >>>为无符号右移
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//5.执行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
// 就执行resize()方法第一次扩容,到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key得到的hash去计算该 key 应该存放到 table 表的哪个索引位置并把这个位置的对象, 赋给 p
//(2)判断 p 是否为 null
//如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT) 就放在该位置 tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); // (hash值,key,value,next)
else {
//一个开发技巧提示: 在需要局部变量(辅助变量)时候,再创建
Node<K,V> e; K k;
//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
//并且满足 下面两个条件之一:
//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同。这个equals比较方法遵循动态绑定,可以由程序员制定
//就不能加入
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).pu tTreeVal(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 - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 和上面一样,比较hash值,并判断是否存在相同key或满足key.equal。
// 若为真,则break,不加入该节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 将相同key不同value的对象,进行value替换。
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
![image-20231103175031031](https://img-blog.csdnimg.cn/f098c69d72664d3aa3ca96ea8fc7c138.jpeg)
LinkHashSet
![image-20231103202201952](https://img-blog.csdnimg.cn/8129842afb6f467e9d2f6679ed81de47.jpeg)
![image-20231103202215949](https://img-blog.csdnimg.cn/a503f495f63a4ed38d969c7862aaef30.jpeg)
Map接口和常用方法
![image-20231103211351452](https://img-blog.csdnimg.cn/239c7f6aa9684f118391e60f3fb4608f.jpeg)
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
// 通过 get 方法, 传入 key ,会返回对应的 value
System.out.println(map.get("no2"));//张无忌
![image-20231103214649777](https://img-blog.csdnimg.cn/3726539ef379489baef8836f0748ce4c.jpeg)
注意:KeySet里面的key 和 Values的value始终指向HashMap$Node里面的key和value
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put(new Car(), new Person());//k-v
//老韩解读
//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
//2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry对象就有k,v
// EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
// 这时因为 static class Node<K,V> implements Map.Entry<K,V>
//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法
// K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set.getClass());// HashMap$EntrySet
for (Object obj : set) { //接受的是Object编译类型
//System.out.println(obj.getClass()); //HashMap$Node
//为了从 HashMap$Node 取出k-v
Map.Entry entry = (Map.Entry) obj; //向下转型 使之能使用Entry接口的方法
System.out.println(entry.getClass()); //HashMap$Node 运行类型始终是HashMap$Node
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
Set set1 = map.keySet(); // 里面保存所有key
System.out.println(set1.getClass());
Collection values = map.values(); // 里面保存所有value
System.out.println(values.getClass());
常用方法
![image-20231103223611399](https://img-blog.csdnimg.cn/03a1b808551f4bf984f54c5798b510a0.jpeg)
遍历方法
//第一组: 先取出 所有的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) {
//将编译类型为Object的entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用EntrySet 的 迭代器(第4种)----");
Itera tor 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.Entry的实现内部类Node呢?
因为HashMap.Node是默认类型,而接口是public类型。
HashMap
![image-20231104114546965](https://img-blog.csdnimg.cn/19832100e22544c1b5a8a1d0de7a546e.jpeg)
![image-20231104120103423](https://img-blog.csdnimg.cn/404d12f75e29415a9a72b941e9c0665a.jpeg)
![image-20231104120112479](https://img-blog.csdnimg.cn/6bf6497963094615b45429c5edac17bf.jpeg)
扩容、加入元素、创建Map等源码和之前的HashSet相同。
HashTable
![image-20231104123418965](https://img-blog.csdnimg.cn/8c98d2aea8ee4a18a460b16fa42071a7.jpeg)
简单说明一下Hashtable的底层
- 底层有数组 Hashtable$Entry[] 初始化大小为 11
- 临界值 threshold 8 = 11 * 0.75
- 扩容: 按照自己的扩容机制来进行即可.
- 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
- 当 if (count >= threshold) 满足时,就进行扩容 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.
![image-20231104123429595](https://img-blog.csdnimg.cn/67e576fa81d944c4bbcd08f8b02d3de2.jpeg)
Properties
![image-20231104203101995](https://img-blog.csdnimg.cn/78e52a6b6f6e4ec5ae2313bfdf496aa3.jpeg)
总结
![image-20231104203603242](https://img-blog.csdnimg.cn/f45b1d27b1234f02999fc908862a69d7.jpeg)
TreeSet
1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的
2. 师希望添加的元素,按照字符串大小来排序
3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类),并指定排序规则
//4. 简单看看源码
//老韩解读
/*
1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 在 调用 treeSet.add("tom"), 在底层会执行到
if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回0,这个Key就没有加入
return t.setValue(value);
} while (t != null);
}
*/
// TreeSet treeSet = new TreeSet();
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//下面 调用String的 compareTo方法进行字符串大小比较
//如果老韩要求加入的元素,按照长度大小排序
//return ((String) o2).compareTo((String) o1);
return ((String) o1).length() - ((String) o2).length();
}
});
//添加数据.
treeSet.add("jack");
treeSet.add("tom");//3
treeSet.add("sp");
treeSet.add("a");
treeSet.add("abc");//3
System.out.println("treeSet=" + treeSet);
TreeMap
使用默认的构造器,创建TreeMap, 是无序的(也没有排序)
/*
老韩要求:按照传入的 k(String) 的大小进行排序
*/
// TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照传入的 k(String) 的大小进行排序
//按照K(String) 的长度大小排序
//return ((String) o2).compareTo((String) o1);
return ((String) o2).length() - ((String) o1).length();
}
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("hsp", "韩顺平");//如果构造器中的比较接口是比较长度,则加入不了
System.out.println("treemap=" + treeMap);
/*
老韩解读源码:
1. 构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 调用put方法
2.1 第一次添加, 把k-v 封装到 Entry对象,放入root
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
2.2 以后添加
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do { //遍历所有的key , 给当前key找到适当位置
parent = t;
cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加
return t.setValue(value);
} while (t != null);
}
*/
Collections工具类
![image-20231104212602716](https://img-blog.csdnimg.cn/7459461ccfe24f4a915f5df69d9d79f5.jpeg)
![image-20231104212611086](https://img-blog.csdnimg.cn/75f32e16787947d98f871a776d9d5eff.jpeg)
Collections.reverse(list);
Collections.sort(list);
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//可以加入校验代码.
return ((String) o2).length() - ((String) o1).length();
}
});
Collections.swap(list, 0, 1);
![image-20231104214900375](https://img-blog.csdnimg.cn/091629f887b948a6aad09824132e806e.jpeg)
本章作业
![image-20231104220452536](https://img-blog.csdnimg.cn/3c86295c71fd4710bf4c5dc4dd7b50b0.jpeg)
5:会抛出异常。因为传入Person类,在源码中需要进行向上转型成Comparable接口类型。可是Person并没有实现该接口。因此会抛出类型转换异常。
解决:
class Person implements Comparable {
@Override
public int compareTo(Object o) {
// 规则
}
}
![image-20231104221845991](https://img-blog.csdnimg.cn/5fe61988e5c74de085d4e7a977c05a90.jpeg)
解释:set.remove(p1)是失败的。因为p1在1号位已经被修改了,调用remove方法的参数为修改后的p1,无法找到原来的p1所hash的位置。
set.add(new Person(1001, “AA”));添加在p1后面。是因为hash值是一样的,但经过比较它们的值并不相同(p1的name修改了),则可以添加在p1的后面。
![image-20231104222735313](https://img-blog.csdnimg.cn/b856628c53fa4a539cbfffe59d108c6b.jpeg)
ch15 泛型
泛型的理解和好处
以前存在的问题:
ArrayList<Dog> arraylist = new ArrayListL<Dog>();
好处:
![image-20231105105021120](https://img-blog.csdnimg.cn/df7ff7312a7441a9b38bd6e909a1c5ae.jpeg)
介绍
![image-20231105110117469](https://img-blog.csdnimg.cn/bc55fea9c38144c69ff48559fc5f03be.jpeg)
//注意,特别强调: E具体的数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
Person<String> person = new Person<String>("韩顺平教育");
person.show(); //String
/*
你可以这样理解,上面的Person类
class Person {
String s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
public Person(String s) {//E也可以是参数类型
this.s = s;
}
public String f() {//返回类型使用E
return s;
}
}
*/
Person<Integer> person2 = new Person<Integer>(100);
person2.show();//Integer
/*
class Person {
Integer s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
public Person(Integer s) {//E也可以是参数类型
this.s = s;
}
public Integer f() {//返回类型使用E
return s;
}
}
*/
}
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
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的运行类型
}
}
泛型的语法
![image-20231105110430246](https://img-blog.csdnimg.cn/8b74efb09a04416784ba1d170b8baf8d.jpeg)
泛型的细节
![image-20231105111900350](https://img-blog.csdnimg.cn/fe0e1ecb9edc4d27b67d5516e1a23f97.jpeg)
//1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>(); //OK
//List<int> list2 = new ArrayList<int>();//错误
//2. 说明
//因为 E 指定了 A 类型, 构造器传入了 new A()
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f(); //变量e的运行类型为A
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.f(); //变量e的运行类型为B
//3. 泛型的使用形式
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
//在实际开发中,我们往往简写
//编译器会进行类型推断, 老师推荐使用下面写法
ArrayList<Integer> list3 = new ArrayList<>();
List<Integer> list4 = new ArrayList<>();
ArrayList<Pig> pigs = new ArrayList<>();
// 如果不指定泛型,默认泛型为Object
ArrayList arrayList = new ArrayList(); //等价于ArrayList<Object> arrayList = nwe ArrayList<>();
class A {}
class B extends A {}
class Pig<E> {//
E e;
public Pig(E e) {
this.e = e;
}
public void f() {
System.out.println(e.getClass()); //运行类型
}
}
自定义泛型类
![image-20231105115821994](https://img-blog.csdnimg.cn/66b1bfa35f3f4b22bb074f3ecfb7b71a.jpeg)
自定义泛型接口
![image-20231105120855873](https://img-blog.csdnimg.cn/0d2115d14b19429980dc33649f8cb091.jpeg)
/**
* 泛型接口使用的说明
* 1. 接口中,静态成员也不能使用泛型
* 2. 泛型接口的类型, 在继承接口或者实现接口时确定
* 3. 没有指定类型,默认为Object
*/
//实现接口时,直接指定泛型接口的类型
//给U 指定Integer 给 R 指定了 Float
//所以,当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
//当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double
//,在实现IUsu接口的方法时,使用String替换U, 是Double替换R
class AA implements IA {
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {
}
interface IUsb<U, R> {
int n = 10;
//U name; 不能这样使用
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
//在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
default R method(U u) {
return null;
}
}
自定义泛型方法
![image-20231105121932679](https://img-blog.csdnimg.cn/2e3facb12d044caebda63450bca437c8.jpeg)
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型
car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型
//测试
//T->String, R-> ArrayList
Fish<String, ArrayList> fish = new Fish<>();
fish.hello(new ArrayList(), 11.3f);
}
}
//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类
public void run() {//普通方法
}
//说明 泛型方法
//1. <T,R> 就是泛型
//2. 是提供给 fly使用的
public <T, R> void fly(T t, R r) {//泛型方法
System.out.println(t.getClass());//String
System.out.println(r.getClass());//Integer
}
}
class Fish<T, R> {//泛型类
public void run() {//普通方法
}
public<U,M> void eat(U u, M m) {//泛型方法
}
//1. 下面hi方法不是泛型方法
//2. 是hi方法使用了类声明的 泛型
public void hi(T t) {
}
//泛型方法,既可以使用类声明的泛型,也可以使用自己声明泛型
public<K> void hello(R r, K k) {
System.out.println(r.getClass());//ArrayList
System.out.println(k.getClass());//Float
}
}
泛型的继承和通配符
//? extends AA 表示 上限, 可以接受 AA 或者 AA 子类
public static void printCollection2(List<? extends AA> c) {}
//? spper AA 表示 下限, 可以接受 AA 或者 AA 父类,不限于直接父类
public static void printCollection3(List<? super AA> c) {}
ch16 坦克大战1
画图
![image-20231105174251901](https://img-blog.csdnimg.cn/f82c1e0cdc184844a96240804f7efc63.jpeg)
![image-20231105174300490](https://img-blog.csdnimg.cn/3f09917797b44cb2ab172e952d91dc5c.jpeg)
![image-20231105223726961](https://img-blog.csdnimg.cn/cb9d6ef5095846a59bfc7ce040d1c6a6.jpeg)
![image-20231106102924218](https://img-blog.csdnimg.cn/bdbb4e778144480eb94245f0b2278226.jpeg)
事件处理机制
![image-20231106120750069](https://img-blog.csdnimg.cn/c1edd761d3e4416dadc00e7fb7784b1f.jpeg)
![image-20231106120802221](https://img-blog.csdnimg.cn/9013e33c0ad1454694b91e067ba74c9a.jpeg)
![image-20231106120818121](https://img-blog.csdnimg.cn/a6c777129a524e40b00de68a5d4cb32a.jpeg)
![image-20231106120858279](https://img-blog.csdnimg.cn/b116752d881a4ac2a4e9a2e4050bd4d3.jpeg)
![image-20231106120907656](https://img-blog.csdnimg.cn/19740699d5414171ac182bc92f0925f8.jpeg)
ch17 多线程基础
线程的应用1-继承Thread
![image-20231106161551706](https://img-blog.csdnimg.cn/375f6d69bdf743bfb79b1d6aa579b8f1.jpeg)
![image-20231106163924440](https://img-blog.csdnimg.cn/479744911ec34e03b0b07077fbe46691.jpeg)
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, 这时线程也就退出..
}
}
}
}
![image-20231106165735906](https://img-blog.csdnimg.cn/85a97b05f3204c1ab07f02f061071e4f.jpeg)
线程的应用2-实现runnable接口
![image-20231106220052247](https://img-blog.csdnimg.cn/a899d3a78e644160a1c11d31ad5c7e8f.jpeg)
![image-20231106220030322](https://img-blog.csdnimg.cn/ca60e57707a8457d82bd66af94d3cf86.jpeg)
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;
}
}
}
}
案例:
package ch17;
/**
* @author czk
* @date: 2023/11/6
* @version: 1.0
*/
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
while(true) {
System.out.println("hello, world " + (++ count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count == 10) {
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hi " + (++ count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count == 5) {
break;
}
}
}
}
理解
![image-20231106222757122](https://img-blog.csdnimg.cn/aa9f77e427ce464fabaf7003828db76a.jpeg)
![image-20231106222824144](https://img-blog.csdnimg.cn/de513f8a182b4feaaa1adcb4acd36591.jpeg)
继承Thread和Runnable的接口对比
线程终止
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
t1.start();
//如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
//让t1 退出run方法,从而终止 t1线程 -> 通知方式
//让主线程休眠 10 秒,再通知 t1线程退出
System.out.println("main线程休眠10s...");
Thread.sleep(10 * 1000);
t1.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
//设置一个控制变量
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);// 让当前线程休眠50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 运行中...." + (++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程方法
![image-20231107222341828](https://img-blog.csdnimg.cn/8bccf4d0ad1547ca8cc17c034d992310.jpeg)
![image-20231107222413007](https://img-blog.csdnimg.cn/197d9395e6454d6dbb5c492d6dbf31ca.jpeg)
package ch17;
/**
* @author czk
* @date: 2023/11/7
* @version: 1.0
*/
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("老韩"); //设置线程名称
t.setPriority(Thread.MIN_PRIORITY); //设置优先级
t.start();
//主线程打印5hi,然后就中断子线程的休眠
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi " + i);
}
System.out.println(t.getName() + " 线程优先级 = " + t.getPriority());
t.interrupt();
}
}
class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "运行中...");
}
System.out.println(Thread.currentThread().getName() + "休眠中...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
//InterruptedException 是捕获到一个中断异常.
System.out.println(Thread.currentThread().getName() + "被 interrupt了");
}
}
}
}
T2 t2 = new T2();
t2.start();
for(int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程(小弟) 吃了 " + i + " 包子");
if(i == 5) {
System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
//join, 线程插队
//t2.join();// 这里相当于让t2 线程先执行完毕
Thread.yield();//礼让,不一定成功..
System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");
}
}
用户线程和守护线程
MyDemoThread dt = new MyDemoThread();
// 将dt设置为守护线程,当所有线程结束后,dt也就自动结束
dt.setDaemon(true);
dt.start();
线程生命周期
![image-20231108091856588](https://img-blog.csdnimg.cn/6c11116ba4fa408dbaa7b246aafe6528.jpeg)
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());
线程同步Synchronized
![image-20231108102715498](https://img-blog.csdnimg.cn/979c12c93ac948a5b6140b3062cf1af5.jpeg)
![image-20231108102724757](https://img-blog.csdnimg.cn/3dd8e89ec2c448728898270bd29b3ec8.jpeg)
互斥锁
![image-20231108104618053](https://img-blog.csdnimg.cn/6daeb06d6c6c4dddaf3d2e76658284b9.jpeg)
- 方法加锁
- 代码块加锁
//同步方法(静态的) 的锁为当前类本身
//老韩解读
//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
//2. 如果在静态方法中, 实现一个同步代码块.
/*
synchronized (SellTicket03.class) {
System.out.println("m2");
}
*/
Oject object = new Objcet();
//1. public synchronized void sell() {} 就是一个同步方法
//2. 这时锁在 this 对象
//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在 this 对象
public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行 sell 方法
synchronized (/*this*/ object) { //this或object相当于一个互斥锁,则下面代码是同步代码块,this或同一个对象。 this也必须是指的同一个对象,不能是多个new出来的this,就没用了
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
}
public synchronized static void m1() { //静态同步方法
}
public static void m2() {
synchronized (SellTicket03.class) { //互斥锁,下面为静态方法中的同步代码块 synchronized(类.class)
System.out.println("m2");
}
}
注意事项:
![image-20231108105338570](https://img-blog.csdnimg.cn/f2c12b78abbc43a587408d5b737c8193.jpeg)
死锁
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Overrid
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) {
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}
释放锁
![image-20231108111526704](https://img-blog.csdnimg.cn/13c4dda583f34bdf94c1360b5aea7c02.jpeg)
作业
public class Homework02 {
public static void main(String[] args) {
T t = new T();
Thread thread1 = new Thread(t);
thread1.setName("t1");
Thread thread2 = new Thread(t);
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
//编程取款的线程
//1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
//2. 每次取出 1000
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {
//解读
//1. 这里使用 synchronized 实现了线程同步
//2. 当多个线程执行到这里时,就会去争夺 this对象锁
//3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁
//4. 争夺不到this对象锁,就blocked ,准备继续争夺
//5. this对象锁是非公平锁.
synchronized (this) {//
//判断余额是否够
if (money < 1000) {
System.out.println("余额不足");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
}
//休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ch19 IO流
文件流的概念:
![image-20231115120220289](https://img-blog.csdnimg.cn/d9460be5e5ae48f8af1e553507ad50b6.jpeg)
常用文件相关操作
创建文件
![image-20231115190952168](https://img-blog.csdnimg.cn/5748a5920e26408aa7383396b5a0ef8f.jpeg)
@Test
public void create1() {
String filePath = "D:\\new1.txt";
// 创建文件对象
File file = new File(filePath);
try {
file.createNewFile(); // 创建新的文件
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void create2 (){
File parentFile = new File("d:\\");
String filename = "news2.txt";
File file = new File(parentFile, filename);
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void create3() {
String parentPath = "d:\\";
String filename = "news3.txt";
File file = new File(parentPath, filename);
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
获取文件相关信息
![image-20231115192728782](https://img-blog.csdnimg.cn/2f772a68972e49a39acdfdbe2b188428.jpeg)
IO流体系
![image-20231115194131647](https://img-blog.csdnimg.cn/b81bbf8a2be2496999f7d5b83027b7c1.jpeg)
![image-20231115194141788](https://img-blog.csdnimg.cn/3f0f2c5243e14ce383bf1a4ad1945859.jpeg)
![image-20231115194153104](https://img-blog.csdnimg.cn/23c40b835a8041e7b4ffce7f529bd5ed.jpeg)
![image-20231115194610631](https://img-blog.csdnimg.cn/015d2769ccaa4120acc379e75cabe8ef.jpeg)
FileInputStream
![image-20231115194342617](https://img-blog.csdnimg.cn/22b7bc2218c8473ba2e89058423220dd.jpeg)
String filePath = "D:\\new1.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(filePath);
// 从该输入流读取一个字节的数据。
// 如果返回-1,表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭文件流,释放资源
try {
fileInputStream.close();
} catch (IOException e ) {
e.printStackTrace();
}
}
// 一次性发读取多个字节
byte[] buf = new byte[4];
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
FileOutputStream
- new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
- new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
//创建 FileOutputStream对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//得到 FileOutputStream对象 对象
//老师说明
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
//fileOutputStream.write('H');//
//写入字符串
String str = "hsp,world!";
//str.getBytes() 可以把 字符串-> 字节数组
//fileOutputStream.write(str.getBytes());
/*
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();
}
}
FileReader、FileWriter
![image-20231115205939888](https://img-blog.csdnimg.cn/b98ac12ffb574c67961e26722d832c5b.jpeg)
![image-20231115205947811](https://img-blog.csdnimg.cn/66df7837a1b140088e65f27d21133e84.jpeg)
![image-20231115210056760](https://img-blog.csdnimg.cn/2e604691a03947369ed2e92b713fcf92.jpeg)
关闭或刷新之前,所写的内容还在内存里面,只有关闭和刷新才能写到指定的文件中去。
节点流和处理流
![image-20231116104831963](https://img-blog.csdnimg.cn/af8b33868dae4750b1f01a73383db297.jpeg)
![image-20231116104844268](https://img-blog.csdnimg.cn/a3ffb0e2d87a4b749820e9c99ca87839.jpeg)
节点流:针对特定数据源,灵活性较差。
![image-20231116105258479](https://img-blog.csdnimg.cn/f7514cc6651c48a09bf46fe8c465b478.jpeg)
处理流(包装流):可以封装一个节点流,灵活性高。操作时真正调用的还是节点流,只是封装了它。
![image-20231116105212113](https://img-blog.csdnimg.cn/dfdb0548caf44fe882949af75e994918.jpeg)
节点流和处理流的区别和联系
![image-20231116114728027](https://img-blog.csdnimg.cn/07adfeb4ff47442cb5412241b7d851c5.jpeg)
![image-20231116114745167](https://img-blog.csdnimg.cn/dc8fdd14de6248c19f676621e716ed98.jpeg)
BufferedReader
public static void main(String[] args) throws Exception {
String filePath = "d:\\new1.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
String line;
while((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
}
对象流
也是处理流的一种。
![image-20231116223746040](https://img-blog.csdnimg.cn/a057bd6d70b047bfbc9f0c71f619c8ac.jpeg)
介绍:
功能: 提供了对基本类型或对象类型的序列化和反序列化的方法
ObjectOutputStream 提供 序列化功能
ObjectInputStream 提供 反序列化功能
ObjectOutputStream\
// 来自某个包的对象,必须实现序列化接口,才能进行序列化和反序列
class Dog implements Serializable {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
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("数据保存完毕(序列化形式)");
ObjectInputStream
//指定反序列化的文件
String filePath = "e:\\data.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//1. 读取(反序列化)的顺序需要和你保存数据(序列化)的 顺序 一致
//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());
//dog 的编译类型是 Object , dog 的运行类型是 Dog
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object -> Dog
//这里是特别重要的细节:
//1. 如果我们希望调用Dog的方法, 需要向下转型
//2. 需要我们将Dog类的定义,放在到可以引用的位置
Dog dog2 = (Dog)dog;
System.out.println(dog2.getName()); //旺财..
//关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
ois.close();
细节:
标准输入流和标准输出流
//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());]
System.out.println("hello, 韩顺平教育~");
Scanner scanner = new Scanner(System.in);
System.out.println("输入内容");
String next = scanner.next();
System.out.println("next=" + next);
转换流
InputStreamReader和OutputStreamWriter
![image-20231117161011808](https://img-blog.csdnimg.cn/c9428f51cf6348b197fd1d8494dfe40d.jpeg)
![image-20231117161055324](https://img-blog.csdnimg.cn/e25bfc9b1b8442f98626b43db5cc1a7b.jpeg)
![image-20231117160738137](https://img-blog.csdnimg.cn/0b968680b1cd4df4b386b3866be06c66.jpeg)
InputStreamReader示例代码:
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();
打印流
打印流只有输出流,没有输入流。
![image-20231117162946115](https://img-blog.csdnimg.cn/0cb4dda051864f3abb56dd415975c189.jpeg)
PrintStream
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
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
printWriter.print("hi, 北京你好~~~~");
printWriter.close();//flush + 关闭流, 才会将数据写入到文件..
Properties类
String filePath = "\\src\\ch19\\mysql.properties";
Properties properties = new Properties();
properties.load(new FileReader("src/ch19/mysql.properties"));
// 打印到显示器
properties.list(System.out);
String user = properties.getProperty("user");
System.out.println(user);
String pwd = properties.getProperty("pwd");
System.out.println(pwd);
properties.setProperties("charset", "utf-8");
properties.setProperties("user", "汤姆");
// null参数是注释内容,会保存到文件最上方,用//注释
properties.store(new FileOutputStream(new FileOutputStream("\\src\\ch19\\mysql.properties"), null))
ch21 网络编程
概念
![image-20231118171621967](https://img-blog.csdnimg.cn/af6972fcc859468b88e26f6fb83fabc1.jpeg)
![image-20231118171634246](https://img-blog.csdnimg.cn/aed0d141904c4a4898190c6bed5e7267.jpeg)
![image-20231118171643516](https://img-blog.csdnimg.cn/3bf5892de1114b24b12c86de7eec2afe.jpeg)
![image-20231118171706298](https://img-blog.csdnimg.cn/b80be168e7eb4460a370f67adcb8c970.jpeg)
![image-20231118171713393](https://img-blog.csdnimg.cn/947fca8e32a042eaa06107d3052c99bb.jpeg)
![image-20231118171721493](https://img-blog.csdnimg.cn/a1bec9fa397a4a8b9d7b189caaa6bba7.jpeg)
![image-20231118190122621](https://img-blog.csdnimg.cn/58791e53fc4d4477a490bf23dae82b53.jpeg)
![image-20231118190229905](https://img-blog.csdnimg.cn/f2b63091f4a04f5d9261dc7bcc8fec8e.jpeg)
TCP和UDP
![image-20231118190218479](https://img-blog.csdnimg.cn/b0afb957eb2847d38a63e4de999f611a.jpeg)
InetAddress类
![image-20231118194126377](https://img-blog.csdnimg.cn/87f2358bf74141f5b63bfa26331969a0.jpeg)
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
InetAddress host1 = InetAddress.getByName("DESKTOP-AU9M7GV");
System.out.println(host1);
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2);
String hostAddress = host2.getHostAddress();
System.out.println(hostAddress);
System.out.println(host2.getHostName());
/*
DESKTOP-AU9M7GV/10.163.166.121
DESKTOP-AU9M7GV/10.163.166.121
www.baidu.com/36.155.132.55
36.155.132.55
www.baidu.com
*/
Socket
![image-20231118195106920](https://img-blog.csdnimg.cn/0fc4d159a5644a64a209cbb72ba4de48.jpeg)
![image-20231118195209496](https://img-blog.csdnimg.cn/e173af1c152e40c39dc0b7293a321122.jpeg)
TCP网络通信编程
![image-20231119145903297](https://img-blog.csdnimg.cn/2684366196904019a2c8221001e4d13a.jpeg)
应用案例
![image-20231118201014679](https://img-blog.csdnimg.cn/8dd7afcea5664d6c8029bcc3560c094d.jpeg)
![image-20231118213231860](https://img-blog.csdnimg.cn/b4c7962224c74ed1b17e749bfd28bcf6.jpeg)
思路分析:
![image-20231118214046744](https://img-blog.csdnimg.cn/1eda6112869f430495dc236f55839e11.jpeg)
//SocketTCP01Server.java
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
// 细节:这个ServerSocket 可以通过 accept() 返回多个Socket (多个客户端连接服务器的并发)
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接");
Socket socket = serverSocket.accept();
System.out.println("服务端 socket = " + socket.getClass());
InputStream is = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = is.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
OutputStream os = socket.getOutputStream();
os.write("hello client".getBytes());
socket.shutdownOutput(); // 设发送数据结束标志
is.close();
socket.close();
serverSocket.close();
}
}
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
// 连接本机的 9999 端口
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端socket = " + socket.getClass());
OutputStream os = socket.getOutputStream();
os.write("hello Server!".getBytes());
socket.shutdownOutput();// 设置结束标志
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int readLen;
while((readLen = is.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, readLen));
}
os.close();
socket.close();
System.out.println("客户端退出了");
}
}
netstat指令
![image-20231119150002399](https://img-blog.csdnimg.cn/794313d2742641dba1ebefb2a177cc67.jpeg)
UDP网络通信编程
![image-20231119153823973](https://img-blog.csdnimg.cn/b778b76d0e6c4015ac4cef21422256a4.jpeg)
![image-20231119153835480](https://img-blog.csdnimg.cn/59a017e692a7485794577a86455e4ac8.jpeg)
应用案例
![image-20231119154148362](https://img-blog.csdnimg.cn/44b26a91deca49ddb40a5cf3368a3a5a.jpeg)
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9999);
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
System.out.println("接收端A等待接受数据");
socket.receive(packet);
int length = packet.getLength();
byte[] data = packet.getData();
String s = new String(data, 0, length);
System.out.println(s);
data = "好的,明天见".getBytes();
packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.163.166.121"), 9998);
socket.send(packet);
socket.close();
System.out.println("A端退出");
}
}
public class UDPSenderB {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9998);
byte[] data = "明天整火锅".getBytes();
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("10.163.166.121"), 9999);
socket.send(packet);
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
int len = packet.getLength();
data = packet.getData();
String s = new String(data, 0, len);
System.out.println(s);
socket.close();
System.out.println("B端退出");
}
}
ch23 反射
引出需求
![image-20231119212015196](https://img-blog.csdnimg.cn/b9f1ebb0ab4e4bcda77e75321da54499.jpeg)
//re.reporterties 配置文件
classfullpath=ch23.Cat
method=hi
//根据配置文件 re.properties 指定信息, 创建 Cat 对象并调用方法 hi
Properties properties = new Properties();
properties.load(new FileInputStream("src/ch23/re.reporterties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
System.out.println(classfullpath);
System.out.println(methodName);
//创建对象 , 传统的方法, 行不通 =》 反射机制
//new classfullpath();
//使用反射机制解决
//加载类, 返回 Class 类型的对象 cls
Class cls = Class.forName(classfullpath);
//通过 cls 得到你加载的类 ch23.CatCat 的对象实例
Object o = cls.newInstance();
//可以通过向下转型调用方法
//Cat cat = (Cat) o;
//cat.say();
//通过 cls 得到你加载的类 ch23.Cat 的 methodName"hi" 的方法对象
// 即: 在反射中, 可以把方法视为对象(万物皆对象)
Method method = cls.getMethod(methodName);
// 通过 method1 调用方法: 即通过方法对象来实现调用方法
method.invoke(o); // 传统方法 对象.方法() , 反射机制 方法.invoke(对象)
反射机制
![image-20231119223416878](https://img-blog.csdnimg.cn/direct/0f88c092616b49d58830419ab8c0d7c0.png)
![image-20231119223428185](https://img-blog.csdnimg.cn/6bbd48a1e7cd4b988d78f3bde6dfdfa6.jpeg)
Field nameField = cls.getField("name");
System.out.println(nameField.get(o));
Constructor constructor1 = cls.getConstructor();
System.out.println(constructor1);
Constructor constructor2 = cls.getConstructor(String.class);
System.out.println(constructor2);
/*
tom
public ch23.Cat()
public ch23.Cat(java.lang.String)
*/
反射优缺点
![image-20231120111321181](https://img-blog.csdnimg.cn/804372e734214865a59813ec183a6193.jpeg)
Class类
![image-20231120112950476](https://img-blog.csdnimg.cn/55e1cb8fdefd4bbe811696a9030f1d2f.jpeg)
![image-20231120113000535](https://img-blog.csdnimg.cn/c9fbcb47f1a64d89b27981ed1f78316d.jpeg)
Class类常用方法:
![image-20231120115926161](https://img-blog.csdnimg.cn/19a2eb5a8bcd49cda7221c007e596f0e.jpeg)
//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类对象
![image-20231120121940794](https://img-blog.csdnimg.cn/5e3ded3bc021473fab48439d1fbdb086.jpeg)
![image-20231120121948859](https://img-blog.csdnimg.cn/1054de6491c94dfca773e4d675d5ab8e.jpeg)
![image-20231120122001025](https://img-blog.csdnimg.cn/26d86ea65b7a47f0a33cebe30a4b6f07.jpeg)
哪些类型有Class对象
![image-20231120122434752](https://img-blog.csdnimg.cn/c635a3d3bedb41368933b634b71251fc.jpeg)
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;//
/*
class java.lang.String
interface java.io.Serializable
class [Ljava.lang.Integer;
class [[F
interface java.lang.Deprecated
class java.lang.Thread$State
long
void
class java.lang.Class
*/
类加载
![image-20231120154713580](https://img-blog.csdnimg.cn/b2088e2f3a304e7aad6879d930b4c9b4.jpeg)
类加载时机:
![image-20231120154734044](https://img-blog.csdnimg.cn/d56faeda7122488dad2d9d0a367029f4.jpeg)
类加载过程图:
![image-20231120162700284](https://img-blog.csdnimg.cn/41fe5c364fb44e458e26fbfb0a020ae2.jpeg)
![image-20231120162722556](https://img-blog.csdnimg.cn/92e566c041c0419c90926ba9c38027a5.jpeg)
加载阶段:
![image-20231120162747317](https://img-blog.csdnimg.cn/1ed0e1b828c94d4e99ac0a11f35c2f59.jpeg)
连接阶段:
- 验证
![image-20231120162811815](https://img-blog.csdnimg.cn/ca1b8bad2e6945b2862b36352d8e1687.jpeg)
-
准备:
//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;
-
解析:
初始化阶段
![image-20231120164650575](https://img-blog.csdnimg.cn/7afa8c95caa6443a8e5c9e079687780b.jpeg)
public static void main(String[] args) throws ClassNotFoundException {
//1. 加载B类,并生成 B的class对象
//2. 链接 num = 0
//3. 初始化阶段
// 依次按顺序自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
/*
clinit() { //按顺序收集
System.out.println("B 静态代码块被执行");
//num = 300; //被合并了
num = 100;
}
合并: num = 100
*/
//new B();//类加载
//System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载
//加载类的时候,是有同步机制控制
/*
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象
synchronized (getClassLoadingLock(name)) {
//....
}
}
*/
B b = new B();
}
class B {
static {
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100;
public B() {//构造器
System.out.println("B() 构造器被执行");
}
}
通过反射获取类的结构信息
Class类
![image-20231120171546553](https://img-blog.csdnimg.cn/f1346f059f594b848a350f6891865602.jpeg)
Filed类
![image-20231120172818082](https://img-blog.csdnimg.cn/80eafba3a7dc4b61a43bb2bae318c1ee.jpeg)
Method类
![image-20231120172914057](https://img-blog.csdnimg.cn/4071b8fbea364cab9b3709c38b053f7e.jpeg)
Constructor 类
![image-20231120173204620](https://img-blog.csdnimg.cn/5c4996ee4609411997c1700fa2ac7f86.jpeg)
通过反射创建对象
![image-20231120174612021](https://img-blog.csdnimg.cn/a6fc3b6ba7a54f42bd6c50d4974ce4e4.jpeg)
//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 + "]";
}
}
通过反射访问类中的成员
![image-20231120215900392](https://img-blog.csdnimg.cn/64bc700f3ad34e0cacc19907e66be00c.jpeg)
勘误:(clazz -> class)
//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;
}
![image-20231120221112184](https://img-blog.csdnimg.cn/a0c2b8a493354b33b1318ff7e3798504.jpeg)
在反射中, 如果方法有返回值, 统一返回 Object , 但是他运行类型和方法定义的返回类型一致。
作业
![image-20231120222239899](https://img-blog.csdnimg.cn/ddccdb1de26641c28e50fd4b5e0b592e.jpeg)
/**
* 利用Class类的forName方法得到File类的class 对象
* 在控制台打印File类的所有构造器
* 通过newInstance的方法创建File对象,并创建E:\mynew.txt文件
*/
//1. Class类的forName方法得到File类的class 对象
Class<?> fileCls = Class.forName("java.io.File");
//2. 得到所有的构造器
Constructor<?>[] declaredConstructors = fileCls.getDeclaredConstructors();
//遍历输出
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("File构造器=" + declaredConstructor);
}
//3. 指定的得到 public java.io.File(java.lang.String)
Constructor<?> declaredConstructor = fileCls.getDeclaredConstructor(String.class);
String fileAllPath = "e:\\mynew.txt";
Object file = declaredConstructor.newInstance(fileAllPath);//创建File对象
//4. 得到createNewFile 的方法对象
Method createNewFile = fileCls.getMethod("createNewFile");
createNewFile.invoke(file);//创建文件,调用的是 createNewFile
//file的运行类型就是File
System.out.println(file.getClass());
System.out.println("创建文件成功" + fileAllPath);