注意事项
代码规范!
使用GBK是因为老师要授课,要在DOS系统中显示中文,所以用GBK。一般编码都是用UTF-8
DOS系统
…\ 是从当前目录返回到上一级目录
cd /D c: 中,/D 可切换根目录。
JRE JVM JDK 的关系
JDK = JRE + Java开发工具
JRE = JVM + 核心类库
环境变量path配置及其作用
- 环境变量的作用是为了在dos的任意目录可以使用java和javac的命令
- 先配置JAVA_HOME = 指向jdk安装的主目录
- 编辑path环境变量,增加%JAVA_HOME%\bin+++
Java中判断字符串相等,不能用==,假设String a = “12454”;
判断a是否和字符串12454相等的方法:
a.euqals("12454")
使用Java从键盘输入数据
import java.util.Scanner;
public class Input {
public static void main(String[] args){
/*
* 1. Scanner类表示简单文本扫描器,在java.util包
* 2. 导入Scanner类所在的包
* 3. 创建Scanner对象,new一个,
*/
Scanner scanner = new Scanner(System.in);
String name = scanner.next(); //接收用户的string
int age = scanner.nextInt(); //接受用户的年龄 int
double salary = scanner.nextDouble(); //接受用户的薪水 salary
System.out.println("name="+ name +" age=" + age + " salary=" + salary);
scanner.close();
}
}
使用Acwing编写java
import java.util.Scanner;
public class Main{
public static void main(String[] args){
int a,b;
Scanner scanner = new Scanner(System.in);
a = scanner.nextInt();
b = scanner.nextInt();
System.out.println(a+b);
}
}
使用java随机数
import java.util.Random;
public class RANDOM{
public static void main(String[] args){
int[] a = new int[10];
Random random = new Random();
for(int i=0;i<a.length;i++)
a[i] = random.nextInt(100);
for(int i=0;i<a.length;i++)
System.out.print(a[i] + " ");
}
}
使用java实现冒泡排序
import java.util.Random;
public class BubbleSort {
public static void main(String[] args){
int[] a = new int[10];
Random random = new Random();
for(int i=0;i<a.length;i++)
a[i] = random.nextInt(100);
for(int i=0;i<a.length;i++)
System.out.print(a[i] + " ");
int n = a.length;
for(int i=1;i<n;i++)
for(int j=0;j<n-i;j++)
if(a[j] > a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
System.out.println();
for(int i=0;i<a.length;i++)
System.out.print(a[i] + " ");
}
}
- 可变参数的使用,
int... nums
表示接受任意多个int。可以理解为nums是个数组 - 可变参数可以和其余形参放在一起,但可变参数在最后
正则表达式
原理
目标:匹配所有四个数字
- \d表示任何一个数字
- 创建模式对象[即正则表达式对象]
- 创建匹配器
//说明:创建匹配器matcher,按照正则表达式的规则pattern 去匹配content字符串 - 开始匹配
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReGTheory {
public static void main(String[] args){
String content = "Java 的历史要追溯到 1991 年,由 Sun公1998司带领的开2456发小组,"+
"想设计一种小型的计算机语言,主要用于有线电视转换盒这类的消费设备。"+
"由于这类设备的处理能力和内存都非常有限,所以语言就必须设计的非常小且能够生成紧凑的代码。"+
"另外,由于不同的厂商会选择不同的 CPU, 因此很重要的一点就是这种语言不应该与任何特定的体系结构绑定。这个项目被命名为 “Green”。1996 年"+
", Sun 发布了第一个 Java 1.0 版本。 ";
//1.生成正则表达式
String regStr = "(\\d\\d)(\\d\\d)";
//2.创建模式对象[即正则表达式对象]
Pattern pattern = Pattern.compile(regStr);
//3.创建匹配器
//说明:创建匹配器matcher,按照正则表达式pattern的规则去匹配content字符串
Matcher matcher = pattern.matcher(content);
//4.开始匹配
while(matcher.find()){
System.out.println("找到"+matcher.group(0));
}
}
}
matcher.find()完成的任务
- 根据指定的规则,定位满足规则的子字符串
- 找到后,将该子字符串的开始的索引记录到matcher对象的属性int[] groups中.
以1991为例,此时groups[0] = 13,把该子字符串的结束的索引+1的值记录到groups[1]=17 - 同时记录oldLast的值为子字符串的结束的索引值+1,即4.即下次执行find时,从4开始匹配。
matcher.group(0)分析
源码:
public String group(int group) {
if (first < 0)
throw new IllegalStateException("No match found");
if (group < 0 || group > groupCount())
throw new IndexOutOfBoundsException("No group " + group);
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
return null;
return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
}
根据groups[0]=13 和groups[1]= 17的记录的位置,从content开始截取子字符串(从groups[0]~groups[1])返回。
如果再次使用find方法,仍然按照上面分析来执行。后面再用时,groups[0]和groups[1]的值就变成下一个匹配的子字符串的位置。
分组为什么是group(0),有什么含义?
groups[0] groups[1]分别表示整个匹配到的子字符串的起止位置。
groups[2]和groups[3]分别表示第一个分组的起止位置,第1组即group(1)
groups[4]和groups[5]分别表示第二个分组的起止位置,第2组即group(2)
groups[6]和groups[7]分别表示第三个分组的起止位置,第3组即group(3)
所以上面为什么是get-Sequence(groups[group * 2], groups[group * 2 + 1])呢
整个是0*2=0,0*2+1=1,所以groups[0] groups[1]表示整个的子字符串
第一组group(1):1*2=2,1*2+1=3
第二组group(2): 2*2=4, 2*2+1=5
第三组group(3):3*2=6,3*2+1=7.
小结:
如果正则表达式有() 即分组:
取出匹配的字符串规则如下:
group(0)表示匹配到的整个子字符串
group(1)表示匹配到的子字符串的第1组字符串
group(2)表示匹配到的子字符串的第2组字符串
group(3)表示匹配到的子字符串的第3组字符串
下标注意不能越界。分组数不能越界。
正则转义符中,Java中两个反斜杠才代表一个反斜杠。\( 才能匹配到(。
(?i)abc 表示不区分大小写abc。
在编译正则表达式时,直接就选择忽略大小写:
Pattern pattern = Pattern.compile(regStr,Pattern.CASE_INSENSITIVE);
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if(matcher.find()){
System.out.println("匹配成功");
}
else System.out.println("匹配失败");
//验证url
String content = "https://www.bilibili.com/video/BV1Eq4y1E79W/?p=17&spm_id_from=pageDriver&vd_source=4283306641c7c0c69e8d362d4f1b4e97";
String regStr = "^((http|https)://)([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&#/%.]*)?$";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if(matcher.find()){
System.out.println("匹配成功");
}
else System.out.println("匹配失败");
Pattern类的matches方法
不使用先定义pattern,再使用matcher,直接使用matches
这里如果是判断一个字符串是否可以和一个正则表达式整体匹配,。只能判断整体 全部匹配
上面的只是找到了就可以。matcher.find()
Pattern.matches(regStr, content); //先写正则表达式串,再写字符串内容
使用pattern将字符串替换为别的字符串
// 使用patcher实现正则表达式的替换。此替换是返回新的字符串,原内容不变。
String regStr = "lgq";
String content = "agfkjisagjwlgqfggwseafgserffjiqaflgqglgqdajoifglgq";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
String newContent = matcher.replaceAll("刘广琦"); //replaceAll中参数为替换后的字符串
System.out.println("替换后的新字符串为" + newContent);
**反向引用 **
反向引用:
内部反向引用用 \\分组号
,外部反向引用用$分组号
String content = "(\\d)(\\d)\\2{2}\\1{1}" //匹配类似122211 155511即abbbaa
结巴去重案例
String content = "我...我要.....学学学学.......编程Java!";
String regStr = "\\.+";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
//1. 去掉所有的.
content = matcher.replaceAll("");
System.out.println(content);
//2. 去掉重复的字 使用(.)\\1+ 这样就是匹配至少出现2次的重复的字
//注意:由于正则表达式发生变化,所以需要重置matcher
pattern = Pattern.compile("(.)\\1+"); //分组的捕获内容记录到$1
matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println(matcher.group(0));
}
System.out.println("去掉.的字符:" + content);
//3. 使用反向引用$1来替换匹配到的内容
content = matcher.replaceAll("$1");
System.out.println("最后的字符为:" + content);
// 使用一条语法,去掉重复的字,
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
System.out.println("使用一条语法去掉重复的字为:" + content);.
//使用字符串的replaceAll直接进行正则表达式替换 将JDK1.4 JDK1.3替换为JDK
String content = "1s5a3fg1564 JDK1.3g srgJDK1.4";
content = content.replaceAll("JDK1(\\.3|\\.4)","JDK");
System.out.println(content);
//验证一个手机号,必须以138 139开头
// 直接使用字符串的matches方法,也是使用正则方法。
content = "13588889999";
System.out.println(content.matches("13(8|9)\\d{8}"));
//字符串的spilt方法
//要求按照# - ~ 数字来分割
content = "hello#abc-jack12smith~北京";
String[] spilt = content.split("#|-|~|\\d+");
for(String s:spilt)
System.out.println(s);
//判断邮箱是否OK合格
String content = "";
boolean ans = content.matches("[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+");
// 判断是否为整数或小数。
boolean ans = content.matches("^[-+]?([1-9]\\d*|0)(\\.\\d+)?$")
对象和类
引用类型的放在方法区中的常量池
成员变量 = 属性 = field(字段)
访问修饰符: 控制属性的访问范围,共4种:public, protected, 默认,private
Person p1 = new Person();
// p1是对象名,也称对象引用
// new Person()创建的对象空间(包括数据)才是真正的对象
// 对象的默认属性值和数组一样,char是\u0000 String是null,其他的都是0
//创建对象的两种方式
//1.先声明再创建
Cat cat; //此时并没有创建空间。
cat = new Cat();
// 2. 直接创建
Person p1 = new Person(); //在堆中开辟一个空间
此时堆中有一个空间,内有两个 空间是age和name,都是初始值0和null
赋值之后小明在常量区创建一个小明的引用类型
然后将p2指向p1。此时p1和p2指向堆中同一片空间
内存分配机制:堆栈
方法的使用原理
** 编写类AA 有一个方法,判断一个数是奇数odd还是偶数even。返回boolean**
//
import java.util.Scanner;
public class MethodExercise01 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
AA a = new AA();
System.out.println(num + "是否是?" + a.isEven(num));
scanner.close();
}
}
class AA {
public boolean isEven(int n){
if(n%2==0)
return true;
return false;
}
}
要写测试类,就是写main函数的类!!!!
IDEA快捷键
- 删除当前行:
删除所选行:Ctrl + d
- 复制当前行:
克隆行:Ctrl + Alt + 向下光标
- 补全代码:
Alt + /
- 添加注释和取消注释:
Ctrl + /
第一次是添加注释,第二次是取消注释 - 自动导入包:
Alt + Enter
- 重新格式化代码:
Ctrl + Alt + L
- 生成构造器:
Alt + insert
选择构造函数 - 查看一个类的层级关系:
Ctrl + h
- 迭代器for遍历:
I
- 迭代器itertor遍历:
itit
new Scanner(System.in).var => Scanner scanner = new Scanner(System.in);
- 提示快捷键:
Ctrl + Alt + T
try-catch什么的。 Ctrl+Shift+加号(回退左边的), 减号
: 展开或折叠方法体
import java.util.Arrays;
Arrays.sort(a); // 库函数对数组直接排序
访问修饰符 modifer:
类只有public和默认可以修饰
封装步骤:
- 将属性私有化private,保证除了本类,其他类无法直接访问修改该属性
- 设置set方法用于修改属性;设置get方法用于获取属性
继承细节:
- 子类必须调用父类的构造器来实现父类的初始化。(在子类的构造器中会默认调用父类的无参构造器(如果不显示调用父类的构造器的话))
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会调用父类的构造器。如果父类没有提供无参构造器,则不惜在子类的构造器中用super()去指定使用父类的那个构造器完成对父类的初始化工作。否则编译不会通过
- 如果希望指定调用父类的某个构造器,则显式的调用一下:super()
- super在使用时,需要放在构造器的第一行
- super()和this()都只能够放在构造器的第一行,因此这两个方法不能共存于一个构造器
- 访问父类的属性方法(private除外):super.属性名
- 调用子类的方法:
this.cal()
(这种方法等价于cal()
).调用父类的方法:super.cal()
this()
方法从从本类开始一直往父类找,并不是只找本类。this方法也可以访问到父类的变量和方法,子类没有就往父类找- this()从本类开始往父类找一直找到没有父类为止,super() 从父类开始往上找,一直找到没有父类为止
class father{
String name = "jack";
father(String s){
this.name = name;
}
}
class son extends father{
String name = "Rose";
son(String s){
super(s);
}
}
main中:
son("John");
这个方法是将John赋值给father类中的name而不是son类中的name。
class B{
B(){ // 无访问修饰符的变量。
this("ABC"); //调用B(String str)方法
System.out.println("b");
}
B(String str){
System.out.println("b name" + str);
}
}
方法重写Override
- 就是子类和父类的方法名都一样,就叫重写了。
- 子类的方法的参数,方法名称 要和父类方法的参数,方法名称完全一致。
- 子类方法的返回类型和父类方法返回类型一样,或者子类方法的返回类型是父类返回类型的子类。(父类是Object,子类可以是String)
- 子类方法不能缩小父类方法的访问权限。(保证先访问到子类)(public > protected > default > private)
如何继承父类的属性? 在子类中使用?
public Student(String name, int age, int id, int score){
super(name, age); //继承父类的name, age。 这里会调用父类构造器 父类要有这样的构造器
this.id = id;
this.score = score;
}
子类中如何调用父类的属性或方法,直接使用?
如果父类的属性是private的,只能使用get方法。如果要重写父类的方法,可在子类里直接调用父类的方法:super.方法()
多态
- 一个对象的编译类型和运行类型可以不一致。编译类型为=的左边,运行类型为等好的右边。
- 编译类型在定义对象时就确定了,不能改变。
- 运行类型是可以变化的
- 父类的对象引用可以指向子类对象
Animal animal = new Dog()
Animal animal = new Dog(); // 编译类型是animal,运行类型是Dog
animal = new Cat(); // 将运行类型改为Cat
- 多态的前提是:两个对象(类)存在继承关系
1.多态的向上转型(父类的引用指向子类的对象)
父类类型 引用名 = new 子类类型();
- 可以调用父类中的所有成员+方法(须遵守访问权限),不能调用子类中特有成员+方法,可以调用子类重写父类的方法
- 运行方法或属性时,从当前子类到父类依次找方法或属性
2.多态的向下转型
把指向子类对象的父类引用,转化为指向子类对象的子类引用。
将父类的对象引用转为子类的对象引用。如果这个引用不是父类类型或其子类的,则无法向下转型。向下转型时要判断引用是否为父类类型或其子类的
- 语法:
子类类型 引用名 = (子类类型) 父类引用
- 当向下转型后,可以调用子类类型中的所有成员。
Cat cat = (Cat) animal; // 将父类的引用转换为子类的
此时animal引用是指向cat,定义新的引用cat也是指向cat。此时有两个引用都指向Cat类。区别在于animal引用不能调用子类特有成员,cat引用可以调用子类所有成员。 - 只能强转父类的引用,不能强转父类的对象(对象是什么不可改变,但是引用是可以转为子类的)
- 要求父类的引用必须指向的是当前目标类型的对象。
// 错误示例
Animal animal = new Cat();
Cat cat = (Cat) animal; // 向下转型,此时cat和animal都指向Cat类
Dog dog = (Dog) animal // 报错。animal指向Cat类,不能转为Dog类。
Person person = new Student(); //向上转型,
Student student = (Student) person; //向下转型
//下面是错误示例:
Person person = new Person();
Student student = (Student) person; //向下转型 错误!
// person本来是指向父类对象的父类引用而不是指向子类对象的父类引用,所以无法向下转型。
属性没有重写之说,属性的值看的编译类型 即看= 左边的
instanceOf 用于判断对象的运行类型是否为XX类型或XX类型的子类型.
可以直接用getClass()方法查看对象的运行类型
属性只看编译类型,方法要看运行类型。从子类到父类依次去找。
Java的动态绑定机制★★★★★
- 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定。(从子类调用方法,调用到父类方法时,又嵌套了子类父类都有的方法时,此时会调用子类方法而不是父类方法。)
- 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用。
多态的应用
多态数组
数组的定义为父类类型,里面保存的元素类型为子类类型
父类类型的引用可以指向子类类型的对象
// 要调用子类中不同的方法的实现:向下转型
// 如果仅调用子类中相同方法,直接调用就可。向上转型在调用方法和属性时从子类开始找。
Person[] persons = new Person[3];
persons[0] = new Person("龙岗区", 22);
persons[1] = new Student("我需要", 22, 20, 100);
persons[2] = new Teacher("和视频", 40, 10000000);
for(int i=0; i< persons.length; i++){
if(persons[i] instanceof Student){
Student student = (Student) persons[i]; // 向下转型 本来是向上转型的,无法使用子类特有成员方法。 所以要向下转型来使用子类特有的成员方法。
System.out.println(student.learn());
} else if(persons[i] instanceof Teacher){
Teacher teacher = (Teacher) persons[i];
System.out.println(teacher.teach());
}else if(persons[i] instanceof Person){
System.out.println(persons[i].say());
}
}
多态参数
向下转型并访问子类特有方法:
Employee e = new Employee();
// 1. 先转型,在访问
Executive manager = (Executive) e;
return manager.manage();
// 2. 转型和访问一起实现
((CEmployee) e).work(); //(CEmployee) e 是向下转型
Object类中常用方法
equals()
hashCode() // 返回该对象的哈希码值
getClass()
equals
==和equals的区别:
- ==:既可以判断基本类型,也可以判断引用类型。如果判断基本类型,判断的是值是否相等。10 和10.0是相等的;如果判断引用类型,判断的是地址是否相等,即判断是否为同一个对象
- equals:只能判断引用类型。默认判断地址是否相等。子类中往往重写该方法,用于判断值是否相等。如Integer,String。
练习1:
// 重写Person类继承的Object类中的方法:
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if(obj instanceof Person){
Person p = (Person) obj; //将Object类向下转型到Person类
return p.name.equals(this.name)&&p.age==this.age;
}
return false;
}
hashCode
- 提高具有哈希结构容器的效率
- 两个引用,如果指向的是同一个对象,则哈希值完全一样
- 两个引用,如果指向的是不同对象,则哈希值是不一样的。
- 哈希值主要根据地址号来的,但不完全等价。
- 后面在集合中,如果用到hashCode,则会重写。
toString
- 返回该对象的字符串表示。默认返回全类名+@+哈希值的十六进制。 全类名=包名+类名
- 直接输出一个对象,toString会被默认调用。sout(Person) <->sout(Person.toString())
将int, double转为toString
int a = 5;
double b = 0.5;
sout(Integer.toString(a));
sout(Double.toString(b));
断点调试
java对象数组在使用时,要每个重新赋值。
houses[idx] = new House(); // 每一个数组的元素都要重新设置。
类变量
类变量,也叫静态变量,用static修饰。
该变量最大的特点就是会被该类所有的对象实例共享
类变量可以通过类名.类变量名
或对象名.类变量名
来访问
静态方法(类方法)
当方法中不涉及任何与对象相关的成员时,则可以将方法设计成静态方法,提高开发效率。
- 类方法中不允许使用和对象有关的关键字,如this和super。普通成员方法可以。
- 静态方法中只能访问 静态变量 或 静态方法。
- 普通成员方法中,即可访问静态成员,也可访问非静态成员。
- 静态方法只能访问静态的成员,非静态的方法可以访问静态成员+非静态成员
代码块
代码块又称初始化块,属于类中的成员,类似于方法,封装于方法体内,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,在加载类或创建对象时隐式调用。
// 语法:
[修饰符]
{
}; //;可以省略
//eg:
{
System.out.println("电脑屏幕打开");
System.out.println("广告开始");
System.out.println("电影正式开始");
}
注意:
- 修饰符可选,要写也只能写static。
- 可以写可以省略
- 代码块分为两类:静态代码块(使用static修饰),普通代码块(不使用static修饰)
优点:
- 相当于另一种形式的构造器(对构造器的补充机制),可以做初始化。
- 如果多个构造器中都有重复语句,可以抽取到初始化块中,提高重写性。
细节:
- static代码块作用就是对类进行初始化,而且它随着类的加载而执行,且只会执行一次;如果是普通代码块(无static修饰)则每创建一个对象,就执行。
- 静态代码块只能调用静态成员(属性和方法),普通代码块可以调用任意成员。
类加载时做的事情:
- 调用构造器
- 调用静态变量,静态方法。
- 调用代码块(相当于构造器的一部分)
- 类什么时候被加载:
- 创建对象实例时(new)
- 创建子类对象实例时,父类也会被加载
- 使用类的静态成员时(静态属性/静态方法)
- 普通的代码块在创建对象实例时,会被隐式的调用。被创建一次则调用一次。
小结:
- static代码块在类加载时执行,只会执行一次。
- 普通代码块在创建对象时调用,创建一次调用一次
创建一个对象时,在一个类中的调用顺序是:(重点,难点)
① 调用静态代码块和静态属性初始化。(多个静态的按定义的顺序调用)
② 调用普通代码块和普通属性的初始化。(多个普通的按定义的顺序调用)
③ 调用构造方法。
注: 上面的静态方法和普通方法在类中调用该方法时才会使用。
父类的初始化
构造器的最前面其实隐含了super()和调用普通代码块。
class A{
public A(){
// 这里有隐藏的执行要求
// 1.super()
// 2.调用普通初始代码块
// 3. 构造器的内容。
}
}
创建一个子类对象(继承关系时),其静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法调用顺序:
① 父类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
② 子类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
③ 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
④ 父类的构造方法
⑤ 子类的的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
⑥ 子类的构造方法
单例设计模式
单例就是在整个运行过程中,某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例设计模式有两种方式:1. 饿汉式 2.懒汉
步骤【饿汉式】:
- 构造器私有化(防止直接new)
- 类的内部创建静态对象
- 向外暴露一个静态的公共方法。
注:因为要在不创建对象实例的前提下得到一个对象实例,所以必须new静态对象,提供静态方法。
class Cat {
// 饿汉式
private String name;
private static Cat cat = new Cat("Tom"); //在类中创建静态private对象
// 构造器私有化
private Cat(String name) {
this.name = name;
}
public static Cat getInstance(){ // 静态方法返回对象
return cat;
}
}
class Cat {
// 懒汉式
private String name;
private static Cat cat; //创建对象引用,但不创建
private Cat(String name) {
this.name = name;
}
public static Cat getInstance(){
if(cat == null)
cat = new Cat("Tom"); //在需要得到对象时才去引用
return Cat;
}
}
懒汉式VS饿汉式
- 两者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建。
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
- 饿汉式存在良妃资源的可能。因为如果没有使用对象实例,则饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存这个问题。
final关键字
可以修饰类,属性,方法和局部变量。
- 不希望类被继承时,可以用final修饰
- 不希望父类的方法被子类覆盖/重写(override)时,可以用final关键字修饰。
- 当不希望类的属性的值被修改,可以用final修饰。
- 当不希望某个局部变量被修改,可以使用final修饰。
final使用注意事项
- final修饰爹属性又称常量,一般用XX_XX_XX命名
- final修饰的属性在定义时,必须赋初值,且不可再更改。赋值可在以下位置:
- 定义时:
public final double MAX_RATE = 0.08
- 在构造器中
- 在代码块中
- 定义时:
- 如果final修饰的属性是静态的,则初始化的位置只能是:(不可在构造器中。)
- 定义时
- 在静态代码块中。(静态代码块就是在{}前面加上static)
- final类不能继承,但可实例化对象
- 如果类不是final类但含有final方法,则该方法虽然不能重写,但是可以被继承。
- 如果类是final类,则没必要将方法修饰成final方法。
- final不能修饰构造方法
- final和static往往搭配使用,效率更高。不会导致类加载。
抽象类
当父类的某些方法需要声明但有不确定如何实现时,可以将其声明为抽象方法,则这个类就是抽象类。
// 抽象类示例:
abstract public void eat();
-
用abstract关键字来修饰一个类时,这个类就叫抽象类。
访问修饰符 abstract 类名{ }
-
用abstract关键字来修饰一个方法时,这个方法就是抽象方法。
// 抽象方法没有方法体 访问修饰符 abstract 返回类型 方法名(参数列表);
-
抽象类的价值更多在于设计,是设计者设计好后,让子类继承并实现抽象类。
-
抽象类不能被实例化。
-
抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法。
-
一旦类包含了abstract方法,则这个类必须声明为abstract。
-
abstract只能修饰类和方法,不能修饰其他的。
-
如果一个类继承了抽象类,则他必须实现抽象类的所有抽象方法,除非他自己也声明为abstract类。
-
抽象方法不能用private,final,static修饰。
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把方法写出来。
interface 接口名{
// 属性
// 抽象方法
}
class 类名 implements 接口{
// 自己属性
// 自己方法
// 必须实现的接口的抽象方法
}
小结: 接口是更抽象的抽象的类。抽象类里的方法可以有方法体,接口里的所有方法都没有方法体。接口体现了程序设计的多态和高内聚低耦合的思想。
- 接口不能被实例化。
- 接口中所有的方法是public方法,接口中抽象方法可以不用abstract修饰。
- 一个普通类实现接口,必须将该接口的所有方法都实现。
- 抽象类实现接口,可以不用实现接口的方法。
- 一个类可以同时实现多个接口
- 接口中的属性只能是final,而且是
public static final
修饰符。 - 接口中属性的访问形式:
接口名.属性名
- 接口不能继承其他的类,但是可以继承多个别的接口。
- 接口的修饰符只能是public 和 默认,这点于类的修饰符是一致的。
- 接口类型可以指向实现该接口的类。
Phone phone = new UsbInterface()
(多态)
接口vs继承
- 子类继承了父类,就自动拥有了父类的功能。
- 如果子类需要拓展功能,可以通过实现接口方式扩展。
- 可以理解,实现接口 是 对java 单继承机制 的一种补充。
- 继承的价值在于:解决代码的复用性和可维护性。接口的价值在于:设计好各种规范(方法),让其他类去实现这些方法。
- 接口比继承更灵活。
内部类
一个类的的内部又完整的嵌套了另一个类结构被嵌套的类被称为内部类inner class,乞讨其他类的类称为外部类outer class。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
如果定义在局部位置(方法中/代码块):1. 局部内部类 2. 匿名内部类。
定义在成员位置:1. 成员内部类 2. 静态内部类。
// 基本语法:
class Outer{
class Inner{
}
}
class Other{
}
内部类的分类:
定义在外部类局部位置上(通常在方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名)
定义在外部类的成员位置上:
- 成员内部类(没有static修饰)
- 静态内部类(使用static修饰)
局部内部类(有类名)
- 定义在外部类的局部位置,比如方法或代码块中,且有类名。
- 不能添加访问修饰符,只可以用final修饰。地位相当于局部变量。
- 作用域:仅仅在定义它的方法或代码块中
- 局部内部类-访问-外部类的成员:【直接访问】
- 外部类-访问-局部内部类的成员:【在主类体内先创建局部内部类对象在调用局部内部类方法(非常扯淡)】
- 外部其他类不能访问局部内部类。
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想要访问外部类的成员,可使用【外部类名.this.成员】
class A{
public void fun(){
class B{
private final String name = "刘广琦";
public void show(){
System.out.println("局部内部类B的name=" + name);
System.out.println("外部类A的name=" + A.this.name);
}
}
B b = new B(); // 在主类体内先创建局部内部类对象
b.show(); // 调用局部内部类方法
}
}
匿名内部类(无类名)★★
匿名内部类和接口:通过创建一个实现接口的匿名类的实例,从而间接地使用接口
- 本质是类 2. 内部类 3. 该类没有名字 4. 同时还是一个对象
匿名内部类通常用于重写已有的类或接口的方法。
new 类或接口(参数列表) {
};
- 可以调用匿名内部类的方法。
- 可以直接访问外部类的所有成员,包括私有的。匿名内部类访问外部类成员:【直接访问】
- 外部其他类不能访问匿名内部类【因为匿名内部类地位是一个局部变量】
- 不能添加访问修饰符。因为他的地位就是局部变量。
- 作用域:仅仅在定义他的方法或代码块中。
- 如果外部类和匿名内部类的成员重名时,访问遵循就近原则。如果想访问外部类的成员,可以使用【外部类名.this.成员】
匿名内部类当作参数
// 例1
// 示匿名内部类
public class AnonymousClass {
public static void main(String[] args) {
// 直接使用匿名内部类。
// 匿名内部类可以当作实参直接传递,简介高效
f1(new IL(){
@Override
public void show() {
System.out.println("这是一副名画~~~~");
}
});
// 传统方法 先写一个类实现IL接口,再用这个类
f1(new Picture());
}
//静态方法 形参是接口类型
public static void f1(IL il){
il.show();
}
}
interface IL {
void show();
}
//不使用匿名内部类:类->实现 IL => 编程领域 (硬编码:专门写一个类来实现某个仅使用一次的功能)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画 XX...");
}
}
// 例2
public class Telephone {
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
/*
* 传递的是实现了Bell接口的匿名内部类
* 重写了ring方法,即接口的ring方法
* alarmclock的参数Bell bell的编译类型是最初的Bell接口
* 运行类型是新实现的匿名内部类
*/
cellphone.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
}
}
public class Cellphone{
public void alarmclock(Bell bell){
bell.ring();
}
}
interface Bell{
public void ring();
// 例3
package enum_;
public class homework04{
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
cellphone.testWork(new Calculator() { //重写Calculator()接口,变成类
@Override
public double work(double a, double b) {
return a + b;
}
}, 10, 20);
}
}
interface Calculator {
public double work(double a, double b);
}
class Cellphone{
public void testWork(Calculator calculator, double a, double b){
double res = calculator.work(a, b);
System.out.println("计算后的结果是" + res);
}
}
成员内部类
成员内部类是定义在外部类的成员位置,且没有static修饰。
-
可以直接访问外部类的所有成员,包含私有的。
-
可以添加任意访问修饰符(public, protected, 默认, private),因为它的地位就是一个成员
-
作用域:整个类。
-
成员内部类访问外部类成员:【直接访问】
-
外部类访问成员内部类:【先创建对象,在访问】
-
外部其他类访问成员内部类:【2种方式】
//外部其他类使用成员内部类的方法 // 法一 先创建父类,再dot 子类创建子类 Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.hi(); // 调用hi方法 // 法二 在外部类中编写一个方法返回内部类对象 Outer.Inner inner1 = outer.getInstance(); inner1.hi(); // 调用hi方法 class Outer{ class Inner{ // 成员内部类 public void hi(){ System.out.println("hi()方法..."); } } //内部类 public Inner getInstance(){ return new Inner(); }
package enum_;
public class homework07 {
public static void main(String[] args) {
Car car = new Car(39);
Car car1 = new Car(41);
Car car2 = new Car(-14);
Car.Air air = car.new Air(); // 直接创建子类对象
Car.Air air1 = car1.new Air();
car.getAir().flow(); //使用方法返回一个内部类Air对象
car1.getAir().flow();
car2.getAir().flow();
}
}
class Car{
private int temperature;
public Car(int temperature) {
this.temperature = temperature;
}
public int getTemperature() {
return temperature;
}
public void setTemperature(int temperature) {
this.temperature = temperature;
}
class Air{
public void flow(){
if(temperature>40){
System.out.println("吹冷气");
}
else if(temperature < 0){
System.out.println("吹暖气");
}
else{
System.out.println("关闭空调");
}
}
}
// 提供一个方法,返回Air对象
public Air getAir(){
return new Air();
}
}
- 如果外部类和成员内部类的成员重名时,内部类访问遵循就近原则,如果想访问外部类的成员,可以使用【外部类名.this.成员名】
静态内部类
静态内部类是定义在外部类的成员位置,且有static修饰。
-
可以直接访问外部类的所有静态成员,包括私有的。但不能访问非静态成员。
-
可以添加任意访问修饰符(public,protected,默认,private),因为其地位就是一个成员
-
作用域:整个类
-
静态内部类访问外部类(只能访问静态):【直接访问】
-
外部类访问静态内部类:【先创建对象,在访问】
-
外部其他类访问静态内部类:
// 方法一 因为静态内部类,所以可以通过类名直接访问,不用先创建外部类实例 Outer.Inner inner = new Outer.Inner(); inner.say(); //调用静态内部类say方法 // 方法2 编写一个方法,返回静态内部类的对象实例。 Outer outer = new Outer(); //创建外部类对象实例 Outer.Inner inner = outer.getInner(); class Outer{ static class Inner{ //... } public Inner getInner() {return new Inner();} }
-
如果外部类和静态内部类的成员重名时,静态内部类访问时,遵循就近原则,如果想访问外部类的成员,使用【外部类名.成员去访问】
枚举类型的使用
多个枚举类对象用逗号分隔,且要将枚举类对象放在最前面
enum常用方法应用实例:
- toString:Enum 类已经重写过了,返回的是当前对象
名,子类可以重写该方法,用于返回对象的属性信息 - name:返回当前对象名(常量名),子类中不能重写
- ordinal:返回当前对象的位置号,默认从 0 开始
- values:返回当前枚举类中所有的常量
- valueOf:将字符串转换成枚举对象,要求字符串必须
为已有的常量名,否则报异常! - compareTo:比较两个枚举常量,比较的就是编号!
// 示例
enum Season{
SPRING("春天","温暖"), //等价于public static final Season SPRING = new Season("春天", "温暖")
SUMMER("夏天", "炎热"),
AUTUMN("秋天","凉爽"),
WINTER("冬天","寒冷");
private String name;
private String desc;
private Season(){}
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
//举例2
public class Enumeration01 {
public static void main(String[] args) {
Week[] weeks = Week.values();
for(Week week : weeks){ // 增强for循环
System.out.println(week); //这里输出的实际为toString方法中的返回值
}
}
}
enum Week{
MONDAY("星期一"), TUESDAY("星期二"),
WEDNESDAY("星期三"), THURSDAY("星期四"),
FRIDAY("星期五"), SATURDAY("星期六"), SUNDAY("星期日");
private String name;
private Week(String name){
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
// 使用switch匹配枚举类对象
package enum_;
public class homework08 {
public static void main(String[] args) {
//枚举值在switch中的使用
Color green = Color.GREEN;
green.show();
switch (green){
case RED:
System.out.println("匹配到红色");
break;
case BLUE:
System.out.println("匹配到蓝色");
// ...
}
}
}
enum Color implements Interface{
RED(255,0,0),
BLUE(0,0,255),
BLACK(0,0,0),
YELLOW(255,255,0),
GREEN(0,255,0);
private int redValue;
private int greenValue;
private int blueValue;
Color(int redValue, int greenValue, int blueValue) {
this.redValue = redValue;
this.greenValue = greenValue;
this.blueValue = blueValue;
}
@Override
public void show() {
System.out.println("redValue=" + redValue);
System.out.println("greenValue=" + greenValue);
System.out.println("blueValue=" + blueValue);
}
}
interface Interface {
public void show();
}
Annotation注解
@Override 重写
@interface 表示一个注解类
@Target 修饰注解的注解,称为元注解。
@Deprecated 用于表示某个程序元素已过时。修饰某个元素
@SuppressWarnings: 抑制编译器警告
JDK 的元 Annotation 元注解
- Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
只能用于修饰一个Annotation定义,用于指定该Annotation可以保留多长时间。 - Target // 指定注解可以在哪些地方使用
用于指定被修饰的Annotation能用于修饰哪些程序元素。 - Documented //指定该注解是否会在 javadoc 体现
用于指定被该元Annotation修饰的Annotation类被javadoc工区提取成文档。即在生成文档时,可以看到该注解。
说明:定义为Documented的注解必须设置Retention值为RUNTIME. - Inherited //子类会继承父类注解(使用较少)
被他习俗hi的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。
异常
异常的第一个案例
package exception_;
public class Exception01 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
try {
int res = num1 / num2;
} catch (Exception e) {
System.out.println("出现异常的原因=" + e.getMessage());
}
System.out.println("程序继续运行");
}
}
Integer.parseInt(String s) 将字符串s转为int类型
try-catch
public static void main(String[] args) {
String str = "132456l";
try {
int a = Integer.parseInt(str);
System.out.println(a);
} catch (Exception e) {
e.printStackTrace(); //打印异常的堆栈跟踪信息
} finally {
System.out.println("不管是否发生异常,都会执行的代码");
}
}
// 使用多个catch
// 可以有多个catch。要求子类异常在前,父类异常在后
try {
Person person = new Person();
person = null;
System.out.println(person.getName());
int n1 = 10, n2 = 0;
int res = n1 / n2;
} catch (NullPointerException e){
System.out.println("空指针异常" + e.getMessage());
} catch (ArithmeticException e){
System.out.println("算术异常" + e.getMessage());
} catch (Exception e){
System.out.println(e.getMessage());
}
// 让用户反复输入,直到输入一个整数为止
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while(true){
String str;
str = scanner.next();
try {
int num = Integer.parseInt(str); // 将string转为int
break;
}
catch (Exception e){
System.out.println("您输入的不是数字,请重新输入");
continue;
}
}
}
throws
第一个throws的应用
public class Throws01 {
public static void readFile(String file) throws FileNotFoundException{
FileInputStream fis = new FileInputStream(file);
}
}
自定义异常
一般自定义异常做成 运行时异常,好处是,我们可以使用默认的处理机制,直接调用super显示错误信息
package exception_;
import java.util.Scanner;
// 自定义异常的应用实例
public class CustomException {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while(true){
int age = scanner.nextInt();
if(!(age >= 18 && age <= 120)){
throw new AgeException("年龄需在18-120之间");
}
System.out.println("你的年龄范围正确");
}
}
}
class AgeException extends RuntimeException {
// 构造器
public AgeException(String message) {
super(message);
}
}
throw 和 throws 的区别
思考下面代码的输出:
package exception_;
public class ThrowException {
public static void main(String[] args) {
try {
ReturnExceptionDemo.methodA();
} catch (Exception e){
System.out.println(e.getMessage()); //捕获到异常则输出异常的信息
}
ReturnExceptionDemo.methodB();
}
}
class ReturnExceptionDemo {
static void methodA(){
try {
System.out.println("进入方法A");
throw new RuntimeException("制造异常");
} finally {
System.out.println("用A方法的finally");
}
}
static void methodB(){
try{
System.out.println("进入方法B");
return;
} finally {
System.out.println("调用B方法的finally");
}
}
}
进入方法A
用A方法的finally
制造异常
进入方法B
调用B方法的finally
KP: 命令行就是main函数中String[] args中的args。
通过点击编辑配置手动输入。
package exception_;
public class Homework01 {
public static void main(String[] args) {
try {
if (args.length != 2) {
throw new ArrayIndexOutOfBoundsException("参数个数不对");
}
int n1 = Integer.parseInt(args[0]);
int n2 = Integer.parseInt(args[1]);
double res = cal(n1, n2);
System.out.println("res =" + res);
} catch (NumberFormatException e) {
System.out.println("数据格式不对");
} catch (ArithmeticException e) {
System.out.println("不能除以0");
}
}
public static double cal(int n1, int n2){
return 1.0*n1/n2;
}
}
十三章 包装类
// 非常经典面试题
// 三元运算符要看做一个整体,取精度最高的。
Object obj1 = true? new Integer(1) : new Double(2.0);
System.out.println(obj1.getClass()); // class java.lang.Double
System.out.println(obj1); // 1.0
Inter & Character类的常用方法
System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.MAX_VALUE);
System.out.println(Character.isDigit('a')); // 判断是否为数字
System.out.println(Character.isLetter('a')); // 判断是否为字母
System.out.println(Character.isUpperCase('a')); // 判断是否为大写字母
System.out.println(Character.isLowerCase('a')); // 判断是否为小写字母
System.out.println(Character.isWhitespace('a')); // 判断是否为空格
System.out.println(Character.toUpperCase('a')); // 转为大写字母
System.out.println(Character.toLowerCase('a')); // 转为小写字母
KP:
intern()是Java中的一个方法,它可以用于在运行时将字符串对象添加到常量池中,并返回常量池中对应的引用。
当调用字符串对象的intern()方法时,如果常量池中已经存在一个等于该字符串内容的字符串,则直接返回常量池中的引用;否则,将该字符串对象添加到常量池中,并返回常量池中的引用。
// 判断输出值
package Chapter13;
public class Test1 {
String str = new String("hsp");
final char[] ch = {'j','a','v','a'};
public void change(String str, char ch[]){
str = "java";
ch[0] = 'h';
}
public static void main(String[] args) {
Test1 ex = new Test1();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.println(ex.ch);
//输出结果: hsp and hava
}
}
String 类的常用方法
String.subString(a, b+1) 才是得到[a, b]的字符串
StringBuffer 类
//看 String——>StringBuffer
String str = "hello tom";
//方式 1 使用构造器
//注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式 2 使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//看看 StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式 1 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
//方式 2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
// StringBuffer的常用方法
StringBuffer s= new StringBuffer("123456789");
//增
s.append("aaaa"); //123456789aaaa
//删除
s.delete(2,4); // 删除[2, 3]的内容 1256789
// 改
s.replace(2,4, "bbbb"); //12bbbb789
// 查找指定字串在字符串第一次出现的索引。
int indexOf = s.indexOf("b");
System.out.println(indexOf); //2
System.out.println(s); //12bbbb789aaaa
// 插入
//在索引为i的位置插入 "赵敏",原来索引为 i 的内容自动后移
s.insert(5, "cccc");
System.out.println(s); // 12bbbccccb789aaaa
// 求长度
System.out.println(s.length());
Scanner scanner = new Scanner(System.in);
for(int i=0;i<3;i++){ //输入n个商品
String name = scanner.next();
String price_ = scanner.next();
StringBuffer price = new StringBuffer(price_);
int k = price.lastIndexOf("."); // 定位到最后一个. 然后再-3就是第一个要插入,的位置
for(k-=3;k>0;k-=3)
price.insert(k, ",");
System.out.println(name + "\t" + price);
}
StringBuilder 类
String & StringBuilder & StringBuffer的区别
Math 类
//1.abs 绝对值
int abs = Math.abs(-9);
//2.pow 求幂
double pow = Math.pow(2, 4);//2 的 4 次方
//3.ceil 向上取整,返回>=该参数的最小整数(转成 double);
double ceil = Math.ceil(3.9); // 4.0
//4.floor 向下取整,返回<=该参数的最大整数(转成 double)
double floor = Math.floor(4.001); //4.0
//5.round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);
//6.sqrt 求开方
double sqrt = Math.sqrt(9.0);
//7.random 求随机数
// random 返回的是 0 <= x < 1 之间的一个随机小数
// 思考:请写出获取 a-b 之间的一个随机整数,a,b 均为整数 ,比如 a = 2, b=7
// 即返回一个数 x 2 <= x <= 7
// 老韩解读 Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
// Math.random()*6 返回的是 0 <= x < 6 小数
// 2 + Math.random()*6 返回的就是 2<= x < 8 小数
Arrays 类
Arrays.sort(a); // 对数组进行排序
System类
BigInteger 和 BigDecimal 类
// 必须创建大数对象
BigInteger bigInteger = new BigInteger("23788888899999999999999999999");
//1. 在对 BigInteger 进行加减乘除的时候,需要使用对应的方法,不能直接进行+ - * /
// 高精度的数
BigDecimal bigDecimal = new BigDecimal("1999.11");
日期类
第一代日期类
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1); // 当前日期=Fri Sep 08 22:36:46 CST 2023
//1. 创建 SimpleDateFormat 对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(d1); // 将日期转换为指定格式的字符串。
System.out.println("当前日期=" + format); // 当前日期=2023年09月08日 10:40:32 星期五
第二代日期类
//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) );
第三代日期类
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
2023-9-8 22:46:21
2023-09-08T22:46:21.388
// 使用now()返回表示当前日期时间的对象
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
// 使用DataTimeFormatter对象来格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format1= dateTimeFormatter.format(ldt);
System.out.println("格式化的日期" + format1); // 格式化的日期2023-09-08 22:48:56
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒
/提供 plus 和 minus 方法可以对当前时间进行加或者减
//看看 890 天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime));
//看看在 3456 分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
DateTimeFormatter 格式日期类
Instant 时间戳
Date和Instant的相互转化
//1.通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//2. 通过 from 可以把 Instant 转成 Date
Date date = Date.from(now);
System.out.println(date);
//3. 通过 date 的 toInstant() 可以把 date 转成 Instant 对象
Instant instant = date.toInstant();
14章 集合
集合的特点
Java 的集合类很多,主要分为两大类,如图 :[背下来]
Collection包括List和Set,都是单列集合
Map包括HashMap,TreeMap,Hashtable,都是双列集合,也就是都有key和item
Collection和Map区别:
Collection包含的都是单列集合、Map包含的都是双列集合,有key和value。
Collection 接口和常用方法
Collection 接口实现类的特点
// add:添加单个元素
// remove:删除指定元素
// contains:查找元素是否存在
// size:获取元素个数
// isEmpty:判断是否为空
// clear:清空
// addAll:添加多个元素
// containsAll:查找多个元素是否都存在
// removeAll:删除多个元素
// 子类 ArrayList的常用方法
List list = new ArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);
list.add(true);
System.out.println(list);
// remove:删除指定元素
list.remove(0); //删除0号索引的元素
list.remove(true); //删除指定元素
System.out.println(list);
// cotains: 查找元素是否存在
System.out.println(list.contains("jack"));
// size: 获取元素个数
System.out.println(list.size());
// isEmpty: 判断是否为空
System.out.println(list.isEmpty());
// clear:清空
list.clear();
System.out.println(list);
// addAll: 一次添加多个元素
ArrayList list1 = new ArrayList();
list1.add("红梅楼");
list1.add("三国演义");
list.addAll(list1);
System.out.println(list);
// containsAll: 查找多个元素是否都存在
System.out.println(list.containsAll(list1));
// removeAll: 删除多个元素
list.removeAll(list1);
System.out.println(list);
Collection 接口遍历元素方式 1-使用 Iterator(迭代器)
package src.com.hspedu.collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator_ {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
// 通过迭代器遍历col
// 1. 先得到col对应的迭代器
Iterator iterator = col.iterator();
// 2.使用while循环遍历(快捷键itit)
//显示所有的快捷键的的快捷键 ctrl + j
while (iterator.hasNext()) {
Object o = iterator.next();
System.out.println("o=" + o);
}
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
Collection 接口遍历对象方式 2-for 循环增强
List list = new ArrayList();
list.add(new Dog("小黑", 3));
list.add(new Dog("大黄", 100));
list.add(new Dog("大壮", 8));
// 使用增强for循环遍历
for(Object dog : list){
System.out.println("dog=" + dog);
}
}
List 接口和常用方法
List 接口基本介绍
Vector、ArrayList、LinkList都是有序的
list常用方法
// list的常用方法
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
// void add(int index, Object ele):在 index 位置插入 ele 元素
list.add(2, true);
System.out.println(list);
// boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list1 = new ArrayList();
list1.add("张三");
list1.add("王五");
list.addAll(list1);
System.out.println(list);
// Object get(int index):获取指定 index 位置的元素
System.out.println(list.get(3));
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf(true));
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位
list.add("张三丰");
System.out.println(list);
System.out.println(list.lastIndexOf("张三丰"));
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
Object o = list.remove(5);
System.out.println(o);
// Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换.
list.set(2, "王者荣耀");
System.out.println(list);
// List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
List sublist = list.subList(1,3);
System.out.println(sublist);
//1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("hsp");
list.get(3);
ArrayList常用方法
list课堂练习:
package src.list_;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.*;
public class ListExercise02 {
public static void main(String[] args) {
// ArrayList, Vector, LinkedList
List list = new LinkedList();
list.add(new Book("三国演义", 211, "罗贯中"));
list.add(new Book("红楼梦", 34, "曹雪芹"));
list.add(new Book("西游记", 11, "吴承恩"));
System.out.println(list);
// 对list调用排序
list.sort(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
o1 = (Book) o1;
o2 = (Book) o2;
return Double.compare(((Book) o1).getPrice(), ((Book) o2).getPrice());
}
});
System.out.println(list);
}
}
class Book{
private String name;
private double price;
private String author;
public Book(String name, double price, String author) {
this.name = name;
this.price = price;
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
", author='" + author + '\'' +
'}';
}
}
ArrayList注意事项:
synchronized是确保线程安全的
ArrayList底层源码
Vector底层源码
集合Set
Set是个接口,有很多实现的类:AbstractSet , ConcurrentHashMap.KeySetView , ConcurrentSkipListSet , CopyOnWriteArraySet , EnumSet , HashSet , JobStateReasons , LinkedHashSet , TreeSet
HashSet详细讲解
HashSet的底层结构
package src.set_;
public class HashSetStructure {
public static void main(String[] args) {
Node[] nodes = new Node[16];
nodes[2] = new Node("John");
Node r = nodes[2];
Node jack = new Node("jack");
r.next = jack;
System.out.println(nodes);
}
}
class Node{
Object item;
Node next;
public Node(Object item) {
this.item = item;
}
@Override
public String toString() {
return "Node{" +
"item=" + item +
", next=" + next +
'}';
}
}
HashSet源码分析:
package chapter14.src.set_;
import java.util.HashSet;
import java.util.Objects;
public class HashSetExercise02 {
/*
定义一个Employee类,该类包含:private成员属性name,sal,birthday(MyDate类型),其中birthday为MyDate类型(属性包括:year,month,day) 要求:
创建3个Employee 对象放入HashSet中。
当name和birthday的值相同时,认为是相同员工,不能添加到HashSet集合中。
思路:重写Employee类中的equals方法和MyDate类中的equals方法。
这样在比较时会逐层的去比较。
*/
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new EMployee("刘广琦", 10000, new MyDate(1998, 4, 25)));
hashSet.add(new EMployee("刘广琦", 10000, new MyDate(1968, 4, 25)));
hashSet.add(new EMployee("刘广琦", 10000, new MyDate(1998, 4, 25)));
System.out.println(hashSet);
}
}
class EMployee {
private String name;
private double sal;
private MyDate birthday;
public EMployee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EMployee)) return false;
EMployee employee = (chapter14.src.set_.EMployee) o;
return Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, birthday);
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
}
class MyDate{
int year, month, day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MyDate)) return false;
MyDate myDate = (MyDate) o;
return year == myDate.year && month == myDate.month && day == myDate.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
}
Set接口的实现类LinkedHashSet
LinkedHashMap举例:
package chapter14.src.set_;
import java.util.LinkedHashSet;
import java.util.Objects;
public class LinkedHashSetExercise {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("Freeari", 199999));
linkedHashSet.add(new Car("奥托", 10000));
linkedHashSet.add(new Car("Audi", 100000));
linkedHashSet.add(new Car("Freeari", 199999));
for(Object o:linkedHashSet){
System.out.println(o);
}
}
}
class Car{
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Car)) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
Map接口
Map接口的实现类有:AbstractMap , Attributes , AuthProvider , ConcurrentHashMap , ConcurrentSkipListMap , EnumMap , HashMap , Hashtable , IdentityHashMap , LinkedHashMap , PrinterStateReasons , Properties , Provider , RenderingHints , SimpleBindings , TabularDataSupport , TreeMap , UIDefaults , WeakHashMap
Map接口初使用
Map map = new HashMap(); // Map是接口 HashMap实现了Map接口
//放入map中键值对:map.put()
map.put("no1", "韩顺平");
map.put("no2", "刘广琦");
map.put("no1", "张三丰");//当有相同的 k , 就等价于替换.
map.put(null, null); //k-v
map.put(null, "abc"); //等价替换
System.out.println(map);
// {no2=刘广琦, null=abc, no1=张三丰}
Map接口的实现类的常用方法
HashMap
//演示 map 接口常用方法
// remove:根据键删除映射关系
// get:根据键获取值
// size:获取元素个数
// isEmpty:判断个数是否为 0
// clear:清除 k-v
// containsKey:查找键是否存在
Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");// 会替换之前有的key
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put("鹿晗", "关晓彤");//OK
map.put("hsp", "hsp 的老婆");
// remove:根据键删除映射关系
System.out.println(map); // {邓超=孙俪, 宋喆=马蓉, 刘令博=null, null=刘亦菲, hsp=hsp 的老婆, 王宝强=马蓉, 鹿晗=关晓彤}
map.remove(null);
System.out.println(map); // {邓超=孙俪, 宋喆=马蓉, 刘令博=null, hsp=hsp 的老婆, 王宝强=马蓉, 鹿晗=关晓彤}
//get:根据键获取值
System.out.println(map.get("王宝强")); //马蓉
// size:获取元素个数
System.out.println(map.size()); // 6
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty()); //false
// clear:清除 k-v
map.clear();
System.out.println(map); // {}
// containsKey:查找键是否存在
System.out.println(map.containsKey("王宝强")); //false
Map遍历方式
- containsKey: 查找键是否存在
- keySet: 查找所有的键
- entrySet: 获取所有关系k-v
- values: 获取所有的值
//Map的遍历方式
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//1. 取出所有的key,再根据key取value
Set keySet = map.keySet(); //Map的key可以理解为Set
// 1.1使用增强for循环
for(Object key:keySet)
System.out.println(key + "=" + map.get(key));
System.out.println("=============使用迭代器============");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
System.out.println(o + "=" + map.get(o));
}
//2. 遍历所有的values值
Collection values = map.values(); //map中的values是Collection类的
System.out.println("=====取出所有的values通过增强for=========");
for(Object val:values)
System.out.println(val);
System.out.println("=====取出所有的values通过迭代器=========");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object o = iterator1.next();
System.out.println(o);
}
// 3.通过 EntrySet 来获取 k-v
// 这个方法都需要用到向下转型。因为要调用子类特有的getKey和getValue方法
Set entrySet = map.entrySet(); //map的entrySet是Set类型的
System.out.println("=====使用EntrySet通过增强for=========");
for(Object entry:entrySet){
Map.Entry m = (Map.Entry) entry; // 向下转型, 将Object类向下转型为Map.Entry类,才能调用getKey和getValue
System.out.println(m.getKey() + ":" + m.getValue());
}
System.out.println("=====使用EntrySet通过迭代器=========");
Iterator iterator2 = entrySet.iterator();
while(iterator2.hasNext()){
Object entry = iterator2.next();
Map.Entry m = (Map.Entry) entry; //向下转型, 从Object类向下转型为Map.Entry类。
System.out.println(m.getKey() + ":" + m.getValue());
}
课堂练习:
package chapter14.src.map_;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapExercise {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put(31, new Employee("刘广琦", 1000, 31));
hashMap.put(11, new Employee("王雪莹", 21000, 11));
hashMap.put(34, new Employee("王春", 2400, 34));
// 遍历显示工资>18000的员工
// 1.使用keySet通过增强for方法
Set keys = hashMap.keySet();
for(Object o:keys){
o = hashMap.get(o);
Employee employee = (Employee) o; //将Object类转为Employee类的
if(employee.getSal() > 18000)
System.out.println(employee);
}
// 2. 使用EntrySet通过迭代器方法
Set entryset = hashMap.entrySet();
Iterator iterator = entryset.iterator();
while(iterator.hasNext()){
Object o = iterator.next(); // 先通过迭代器得到Object对象
Map.Entry entry = (Map.Entry) o; //再将Object对象转为Map.Entry对象
Employee employee = (Employee) entry.getValue(); //在通过getValue()方法得到value对象
if(employee.getSal() > 18000)
System.out.println(employee);
}
}
}
class Employee{
private String name;
private double sal;
private int id;
public Employee(String name, double sal, int id) {
this.name = name;
this.sal = sal;
this.id = id;
}
public double getSal() {
return sal;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", id=" + id +
'}';
}
}
Map小结:
HashMap源码图解(大体理解、需要再细看)
/**/ 老韩解读 HashMap的源码+图解
1. 执行构造器 new HashMap(),
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2.
执行 put调用hash方法,计算 key的hash 值(h =key.hashCode())^(h >>>16)
public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;//辅助变量
//如果底层的 table 数组为 null, 或者 length =0 , 就扩容到 16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v
//, 创建成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K, V> e;
K k;//辅助变量
// 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,
// 并 满足(table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真)
// 就认为不能加入新的 k-v
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处理
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {
//如果找到的结点,后面是链表,就循环比较
for (int binCount = 0; ; ++binCount) {//死循环
if ((e = p.next) == null) {//如果整个链表,没有和他相同,就加到该链表的最后
p.next = newNode(hash, key, value, null);
//加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后
//就调用 treeifyBin 方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) //如果在循环比较过程中,发现有相同,就 break,就只是替换 value
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //替换,key 对应 value
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个 Node ,就 size++
if (++size > threshold[12 - 24 - 48])//如 size > 临界值,就扩容
resize();
afterNodeInsertion(evict);
return null;
}
5.关于树化(转成红黑树)
//如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容. //否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K, V>[] tab, int hash) {
int n, index;
Node<K, V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
Hashtable
Hashtable介绍
Hashtable和HashMap对比
Properties
Properties也是Map接口的实现类
Properties类介绍
Properties类的方法简单使用
Properties properties = new Properties();
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
// 通过key获取对应值
System.out.println(properties.get("lic"));
// 删除
properties.remove("lic");
System.out.println(properties);
// 修改
properties.put("lic", "理财");
System.out.println(properties);
补充:TreeMap的使用
可以自动根据key的值进行排序。
TreeMap<Integer, Character> treeMap = new TreeMap<>();
treeMap.put(16, 'a');
treeMap.put(1, 'b');
treeMap.put(4, 'c');
treeMap.put(3, 'd');
treeMap.put(8, 'e');
// 遍历
System.out.println("默认排序:");
treeMap.forEach((key, value)->{
System.out.println(key+":"+value);
});
/*
默认排序:
1:b
3:d
4:c
8:e
16:a
*/
// 构造方法传入比较器
Map<Integer, String> tree2Map = new TreeMap<>((o1, o2) -> o2 - o1); // (o1, o2) -> o2 - o1就是倒叙排序。(o1, o2) -> o1 - o2就是正序排序
tree2Map.put(16, "a");
tree2Map.put(1, "b");
tree2Map.put(4, "c");
tree2Map.put(3, "d");
tree2Map.put(8, "e");
// 遍历
System.out.println("倒序排序:");
tree2Map.forEach((key, value) -> {
System.out.println("key: " + key + ", value: " + value);
});
/*
倒序排序:
key: 1, value: b
key: 3, value: d
key: 4, value: c
key: 8, value: e
key: 16, value: a
*/
// 重匿名内部类重写compare方法
TreeMap<String, Character> treeMap = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length(); // 按照key的length从小到大排序
//return ((String) o2).compareTo((String) o1); //按照传入的 k(String) 的大小进行排序
}
});
treeMap.put("eg", 'a');
treeMap.put("sgasag", 'b');
treeMap.put("gfagrgth", 'c');
treeMap.put("thjrjbk", 'd');
treeMap.put("e", 'e');
/*
e:e
eg:a
sgasag:b
thjrjbk:d
gfagrgth:c
* */
补充:TreeSet的使用
TreeSet内部就是使用TreeMap实现的。其实就是只取了TreeMap中的键key。
public void test1() {
NavigableSet<Integer> set = new TreeSet<>();
set.add(5);
set.add(4);
set.add(5);
set.add(3);
set.add(1);
set.add(9);
//正顺序遍历
System.out.print("正序遍历:" );
set.forEach(item -> {
System.out.print(item + " ");
});
System.out.println();
//逆序遍历
System.out.print("逆序遍历:" );
set.descendingIterator().forEachRemaining(item -> {
System.out.print(item + " ");
});
}
开发中如何选择集合实现类
Collections 工具类
排序方法:(均为static方法)
Collections的常用方法
Collections.sort(list);
Collections.sort(list); // sort: 根据元素的自然顺序对指定List集合元素按升序排序
根据Comparator指定的顺序,返回最大元素
Object max(Collection, Comparator)
Collections.swap(list, 1, 2); // 将指定list集合中的元素交换
Collections.frequency(list, "tom") //返回元素的出现次数
void copy(dest, src): 将src复制到dest
replaceALl(list, oldVal, newVal)
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
System.out.println(list);
// 翻转list
// Collections.reverse(list);
// System.out.println(list);
//sort: 根据元素的自然顺序对指定List集合元素按升序排序
// Collections.sort(list);
// System.out.println(list);
// 根据指定的Comparator排序
// Collections.sort(list, new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// //按照字符串的长度从小到大排序
// return ((String) o1).length() - ((String) o2).length();
// }
// });
// System.out.println(list);
// 将指定list集合中的元素交换
Collections.swap(list, 1, 2);
System.out.println(list);
// 根据元素的自然顺序,返回最大值
System.out.println(Collections.max(list));
// 根据Comparator指定的顺序,返回最大元素
// Object max(Collection, Comparator)
// Object maxObj = Collections.max(list, new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// // eg:返回长度最长的元素
// return ((String) o1).length() - ((String) o2).length();
// }
// });
// System.out.println(maxObj);
//还有返回最小值,和max操作一样
//int frequency(Collection, Object): 返回元素的出现次数
System.out.println("tom出现的次数" + Collections.frequency(list, "tom"));
//void copy(dest, src): 将src复制到dest
ArrayList arrayList = new ArrayList();
//直接copy会报错。要先给dest赋值,大小和list.size()一样
for (int i= 0; i < list.size(); i++) {
arrayList.add("");
}
Collections.copy(arrayList, list);
System.out.println("复制后的list为" + arrayList);
// boolean replaceALl(list, oldVal, newVal): 使用新值替代旧值
Collections.replaceAll(list, "tom", "汤姆");
System.out.println(list);
分析HashSet和TreeSet如何实现去重的
注:如果没有传入comparator匿名对象,则要以你添加的这个对象(这个对象是实现了Comparable接口的类的对象)实现的Compareable接口的compareTo去重。
分析:这段代码会报错:ClassCastException。
加入Person类时,因为Person类并没有实现Compareable接口,所以它往TreeSet中加入时,会找不到compareTo方法从而无法比较加入的对象是否相同。所以需要Person类实现Compareable接口,并重写compareTo方法来比较。
15章 泛型
泛型是可以表示数据类型(String, Integer)的一种数据类型
在使用增强for循环时for (Object o : arrayList)
,遍历时只能用Object对象,所以还需要向下转型成猫。但是如果列表中有一只狗就会出现ClassCastException。而在添加狗时不会报错。
arrayList.add(new Cat("招财猫", 8));
for (Object o : arrayList) {
//向下转型 Object ->Dog
Dog dog = (Dog) o;
System.out.println(dog.getName() + "-" + dog.getAge());
}
所以需要使用泛型将list中添加的元素只能为Dog:ArrayList<Dog> arrayList = new ArrayList<Dog>();
使用泛型的好处
泛型的语法
泛型的实例化
public class Generic03 {
public static void main(String[] args) {
Person<String> p = new Person<>("韩顺平");
System.out.println(p.f());
p.show();
}
}
class Person<E>{ // 自定义泛型
E s;
public Person(E s) {
this.s = s;
}
public E f(){// 返回类型使用E
return s;
}
public void show(){
System.out.println(s.getClass()); // 显示s的运行类型
}
}
GenericExercise
// 使用泛型放3个学生对象到HashSet中
HashSet<Student> studentHashSet = new HashSet<Student>(); //定义泛型HashSet
studentHashSet.add(new Student("jack", 18));
System.out.println(studentHashSet);
// 使用泛型放3个学生对象到HashMap中
HashMap<String, Student> StudentHashMap = new HashMap<>();
StudentHashMap.put("tom", new Student("tom", 24));
System.out.println(StudentHashMap);
// HashSet的迭代器
Iterator<Student> iterator = studentHashSet.iterator();
// HashMap的迭代器
// entrySet(这里的entrySet保存的是所有的键值对)
Set<Map.Entry<String, Student>> entries = StudentHashMap.entrySet();
//迭代器遍历entrySet
Iterator<Map.Entry<String, Student>> iterator1 = entries.iterator();
泛型使用细节
2的具体举例:
泛型课堂练习
分析:由于需要比较MyDate中的年月日,所以应该设置MyDate类是可比较的,也就是implements Comparable接口,然后需要实现compareTo方法,在该方法中按照年月日比较。
package Chapter15.generic;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Objects;
public class GenericExercise02 {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("刘广琦", 1000, new MyDate(1999, 1, 1)));
employees.add(new Employee("王雪莹", 2000, new MyDate(1989, 2, 22)));
employees.add(new Employee("刘广琦", 3000, new MyDate(1979, 3, 3)));
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
if (o1.getName() != o2.getName()) return o1.getName().compareTo(o2.getName());
else {
return o1.getBirthday().compareTo(o2.getBirthday());
}
}
});
System.out.println(employees);
}
}
class Employee {
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return Objects.equals(getBirthday(), employee.getBirthday());
}
@Override
public int hashCode() {
return Objects.hash(getBirthday());
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" + "name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}';
}
}
class MyDate implements Comparable<MyDate> { //实现接口,表示这个类是可比较的。
private int year, month, day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public int compareTo(MyDate o) {
int yearDiff = year - o.year;
if (yearDiff != 0) return yearDiff;
int monthDiff = month - o.getMonth();
if (monthDiff != 0) return monthDiff;
int dayDiff = day - o.getDay();
return dayDiff;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MyDate)) return false;
MyDate myDate = (MyDate) o;
return getYear() == myDate.getYear() && getMonth() == myDate.getMonth() && getDay() == myDate.getDay();
}
@Override
public int hashCode() {
return Objects.hash(getYear(), getMonth(), getDay());
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public String toString() {
return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}';
}
}
自定义泛型类
泛型参数在创建对象时才具体化,而静态方法不依赖于实例。所以静态方法中不能使用泛型
// 使用泛型方法
public R getR() {
return r;
class Apple<T, R, M> {
public <E> void fly(E e){
System.out.println(e.getClass().getSimpleName());
}
public<U> void eat(U u){} //对,泛型方法中指定了U泛型
// public void run(M m){} // 不对,没有指定M泛型
}
}
自定义泛型接口
泛型的继承和通配符说明
Junit使用教程
在测试代码过程中,如果要来回切换代码、添加注释、比较麻烦。所以引用JUnit可以很方便的测试某个方法或者调试。
具体方法为在每个方法前面加上@Test,然后下载JUnit5.8.1,然后在方法体内右击就可以单独跑这一个代码
重点看JUnit的使用和(4)返回所有的T对象
public class Homework01 {
// 使用JUnit单元测试
@Test
public void test() {
DAO<User> dao = new DAO<>();
dao.save("Jack", new User(1, 18, "Jack"));
dao.save("Tom", new User(2, 22, "Tom"));
System.out.println(dao.list());
dao.update("Amy", new User(3, 22, "Amy"));
dao.delete("Tom");
System.out.println(dao.list());
}
}
class DAO<T> {
Map<String, T> map = new HashMap<>();
@Test
public void save(String id, T entity) {
map.put(id, entity);
}
@Test
public T get(String id) {
return map.get(id);
}
@Test
public void update(String id, T entity) {
if (map.get(id) == null) {
System.out.println("没找到您要更新的值" + id);
return;
}
save(id, entity);
}
@Test
public List<T> list() {
Collection<T> values = map.values();
List<T> list = new ArrayList();
for (T t : values)
list.add(t);
return list;
}
@Test
public void delete(String id) {
map.remove(id);
}
}
class User {
private int id, age;
private String name;
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}';
}
}
16章 多线程基础
创建线程的两种方式
- 继承 Thread 类
- 实现 Runnable 接口
- 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行。这是,主线程和子线程是交替执行…
- 当所有的线程都结束时,整个进程才结束。并不是当Main线程结束,整个进程就结束。
start0才是真正实现run方法的。
public class thread01 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
}
}
class Cat extends Thread{
@Override
public void run() {
int i = 1;
while(true){
System.out.println(i++);
try {
Thread.sleep(1000); //里面是毫秒。
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(i >= 5)
break;
}
}
}
Runnable接口的实现
public class thread02 implements Runnable{
public static void main(String[] args) {
thread02 thread02 = new thread02();
Thread thread = new Thread(thread02); //使用Runnable接口时必须创建Thread对象,然后传进去重写Runnable的类。
thread.start();
}
int times = 0;
@Override
public void run() {
while(true){
if(times <= 5){
System.out.println("Hi" + times);
this.times ++;
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
else{
break;
}
}
}
}
使用代理模式
public class thread03 {
// 使用代理模式(自己没有,通过线程代理实现start方法来调用run方法,实际上还是自己用的run方法)
public static void main(String[] args) {
Tiger tiger = new Tiger();
ThreadProxy threadProxy = new ThreadProxy(tiger); // tiger表示的是target
threadProxy.start();
}
}
class Animal{}
class ThreadProxy implements Runnable{
private Runnable target = null;
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0();
}
private void start0() {
run();
}
@Override
public void run() {
target.run(); // 动态绑定机制,实际上是调用的target的运行类型的(Tiger)的方法
}
}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫");
}
}
线程的通知退出
public class ThreadExit {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
Thread.sleep( 2 * 1000);
// 通知线程退出
t.setLoop(false);
System.out.println("运行结束");
}
}
/*
当前线程正在运行:Thread-0 i=647560
运行结束
*/
class T extends Thread{
int i=0;
boolean loop=true;
@Override
public void run() {
while(loop){
System.out.println("当前线程正在运行:" + Thread.currentThread().getName() + " i=" + i++);
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程常用方法
// 例题
package Chapter17.method;
/*
主线程输出hello,子线程输出hi。让两个线程同时进行。当输出5次后,让子线程进行完,然后再让主线程进行。
* */
public class method01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
for(int i=0;i<20;i++){
System.out.println("主线程输出hello" + (i+1));
Thread.sleep(1000);
if(i==4)
t.join();
}
}
}
class T extends Thread{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println("子线程输出hello" + (i+1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package Chapter17.method;
public class method02 {
public static void main(String[] args) throws InterruptedException {
Th t = new Th();
Thread thread = new Thread(t);
for(int i=1;i<=10;i++){
System.out.println("主进程hi" + i);
Thread.sleep(1000);
if(i==5){
thread.start(); //开启后立即join,可以保证开启后执行完毕在执行别的。
thread.join();
}
}
}
}
class Th implements Runnable{
@Override
public void run() {
for(int i=1;i<=10;i++){
System.out.println("子进程输出hello" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
用户线程和守护线程
package src.com.hspedu.method;
public class DaemonThread_ {
// 要求实现守护线程.守护线程是所有的线程结束后会自动结束.
// 将MyDaemonThread线程设置为守护线程. 当主线程结束后, MyDaemonThread线程也结束.
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
// 设置为守护线程
myDaemonThread.setDaemon(true); // 将这个线程设置为守护线程
myDaemonThread.start();
for(int i=1;i<=10;i++){
System.out.println("主线程正在运行" + i + "次");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{
@Override
public void run() {
int i=1;
while(true){
System.out.println("子线程正在运行第" + i++ + "次");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
Synchronized关键字
Synchronized如何使用
// 使用Synchronized锁实现多线程卖票
// 这种多个线程共享同一资源时必须用Runnable接口实现,继承Thread是不可以的.
package Chapter17.ticket;
public class SellTicket2_0 {
public static void main(String[] args) {
SellTickets sellTickets = new SellTickets();
new Thread(sellTickets).start();
new Thread(sellTickets).start();
new Thread(sellTickets).start();
}
}
class SellTickets implements Runnable{
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (this){ // 锁住当前对象,保证此时进入到下面代码块的只有一个线程
if(this.tickets > 0){ //当一个线程修改了this.tick后,另一个线程进来买票之前一定要再次判断是否还有票
this.tickets --;
System.out.println("当前的线程为" + Thread.currentThread().getName() + "当前剩余的票为" + this.tickets);
}
}
}
}
}
线程的死锁和释放锁
// 死锁
package Chapter17.DeadLock;
// 死锁案例.
public class DeadLock_ {
public static void main(String[] args) {
DedaLockDemo dedaLockDemo = new DedaLockDemo(true);
DedaLockDemo dedaLockDemo1 = new DedaLockDemo(false);
dedaLockDemo1.start();
dedaLockDemo.start();
}
}
class DedaLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
private boolean loop = false;
public DedaLockDemo(boolean loop){
this.loop = loop;
}
@Override
public void run() {
while(loop){
synchronized(o1){
System.out.println("当前进程" + Thread.currentThread().getName() + "获取o1");
synchronized(o2){
System.out.println("当前进程" + Thread.currentThread().getName() + "获取o2");
}
}
}
while(!loop){
synchronized(o2){
System.out.println("当前进程" + Thread.currentThread().getName() + "获取o2");
synchronized(o1){
System.out.println("当前进程" + Thread.currentThread().getName() + "获取o1");
}
}
}
}
}
homework
/*
在main方法中启动两个线程
第一个线程驯化随机打印100以内的正数
直到第二个线程从键盘中读取了Q命令.
提示: 使用通知方式解决.
*/
public class homework01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);
a.start();
b.start();
}
}
class A extends Thread{
private boolean loop = true;
@Override
public void run() {
while(loop){
System.out.println((int)(Math.random()*100 )+ 1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
class B extends Thread{
private A a = null;
private Scanner scanner = new Scanner(System.in);
public B(A a){
this.a = a;
}
@Override
public void run() {
while (true){
System.out.println("请输入指令:Q退出");
char c = scanner.next().toUpperCase().charAt(0); // 从字符串中获取字符的方法charAt(idx)
if(c == 'Q'){
a.setLoop(false);
System.out.println("A线程退出");
break;
}
}
System.out.println("B线程退出");
}
}
如果在遍历列表时需要增加或者删除列表中的元素, 此时使用增强for循环会出现问题. 但是使用普通for循环就不会出问题.
十七章 IO流
文件的创建
// 文件创建的三种方式 @Test
public void create01() throws IOException {
// 方法1 new File(String filename)
File file = new File("E://news1.txt"); // 只是在内存中有了一个对象,但是还没有和硬盘发生交互。
if(file.createNewFile()){ // 在硬盘中创建新文件。
System.out.println("创建成功");
}
else{
System.out.println("创建失败");
}
}
@Test
public void create02(){
// 方法2 new File(File parentFile, String filename)
File parentFile = new File("E://");
String filename = "news2.txt";
File file = new File(parentFile, filename);
try {
file.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void create03(){
// 方法3 new File(String parent, String child) 根据父目录 + 字路径构建
String parentFile = "E://";
String filename = "news3.txt";
File file = new File(parentFile, filename);
try {
file.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
文件的常用方法
在Java中, 目录也被当作文件对待.
length是求的文件的大小
IO流
InputStream
// 使用FileInputStream
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("E:\\JavaCode\\基础\\Chapter18\\inputstream_\\hello.txt");
byte[] buf = new byte[8]; // 字节缓冲区
int readlen;
while((readlen = fileInputStream.read(buf)) != -1){ // readlen 表示读取的字节的长度, 可能buf缓冲区最后一次读取并没有读取满.
System.out.print(new String(buf, 0, readlen)); // 这里的长度一定是0 ~ readlen
}
}
// 使用FileOutputStream
public static void main(String[] args) throws IOException {
String filepath = "E:\\JavaCode\\基础\\Chapter18\\inputstream_\\output.txt";
FileOutputStream fileOutputStream = new FileOutputStream(filepath); //以覆盖方式写入
//FileOutputStream fileOutputStream1 = new FileOutputStream(filepath, true); //以追加方式写入. append=true表示以追加方式写入
// FileOutputStream是以字节方式写入的,当想写入字符串时, 可以调用字符串的getBytes()方法
String content = "hello my java";
fileOutputStream.write(content.getBytes());
// fileOutputStream.write(content.getBytes(), 0, 5); // 从0开始, 指定长度为5
}
// 使用FileInputStream和FileOutputStream去做文件的拷贝
public static void main(String[] args) throws IOException {
String path = "E:\\min.jpg";
FileInputStream fileInputStream = new FileInputStream(path);
FileOutputStream fileOutputStream = new FileOutputStream("E:\\JavaCode\\基础\\Chapter18\\FileStream_\\miner.jpg");
int readlen;
byte[] buf = new byte[1024];
while((readlen = fileInputStream.read(buf)) != -1){ //一边读
fileOutputStream.write(buf, 0, readlen); // 一边写
}
fileInputStream.close();
fileOutputStream.close();
}
FileReader和FileWriter
都是字符流
注意: 一定要关闭或刷新FileWriter
节点流和处理流
节点流只能处理单一的特定的, 不够灵活.
处理流可以处理文件, 二进位制文件, 字符… 使用起来比较灵活.
处理流的构造方法中都有一个抽象基类, 可以用其任意节点流子类当作运行类型. 例如: BufferedReader中有一个Reader对象, 这个对象可以是FilreReader, PipeReader等
BufferedReader & BufferedWriter
这两个都是字符处理流.
BUfferedReader有一个一次读取一行的方法,叫做 BufferedReader.readLine()
// BufferedReader的使用
public class BufferedReader_ {
public static void main(String[] args) throws Exception {
// 使用BufferedReader读取文件中的内容
BufferedReader bufferedReader = new BufferedReader(new FileReader("E:\\JavaCode\\基础\\Chapter18\\reader\\FileReader_.java"));
String line;
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
bufferedReader.close(); // 关闭外层的BufferedReader后, 内层的FileReader也会关闭.
}
}
// BufferedWriter的使用
public class BufferedWriter_ {
// 演示BufferWriter的使用
// 将hello, 韩顺平教育写入到文件中
public static void main(String[] args) throws IOException {
BufferedWriter bufferedReader = new BufferedWriter(new FileWriter("E:\\JavaCode\\基础\\Chapter18\\writer_\\text.txt"));
bufferedReader.write("hello, 韩顺平教育");
bufferedReader.close();
}
}
// 使用BufferedReader和BufferedWriter实现字符文件的拷贝
public class BufferedCopy {
public static void main(String[] args) throws Exception {
String srcFilePath = "E:\\JavaCode\\基础\\Chapter18\\writer_\\note.txt";
String desFilePath = "E:\\note.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(srcFilePath));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(desFilePath));
String line;
while((line = bufferedReader.readLine()) != null){
bufferedWriter.write(line);
}
bufferedReader.close();
bufferedWriter.close();
}
}
BufferedInputStream和BufferedOutputStream
这两个都是字节处理流, 处理的是二进制文件. 同时也可以处理字符文件.
因为处理字符文件底层还是处理的字节. 字节是根本
// 使用BufferedInputStream和BufferedOutputStream实现二进制文件的拷贝
public class BufferedCopy_ {
public static void main(String[] args) throws IOException {
// 使用BufferedInputStream 和BufferedOutputStream实现二进制文件(图片, 音频的拷贝)
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("E:\\min.jpg"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("E:\\JavaCode\\基础\\Chapter18\\stream_\\miner.jpg"));
int readlen;
byte[] buf = new byte[1024]; // 设置一个缓冲区
while((readlen = bufferedInputStream.read(buf)) != -1){
bufferedOutputStream.write(buf, 0, readlen);
}
bufferedInputStream.close();
bufferedOutputStream.close();
}
}
ObjectInputStream和ObjectOutputStream
// ObjectOutputStream的使用public class ObjectOutputStream_ {
public static void main(String[] args) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:\\JavaCode\\基础\\Chapter18\\stream_\\data.dat"));
objectOutputStream.writeInt(123);
objectOutputStream.writeChar('a');
objectOutputStream.writeUTF("helloworld"); // 写字符串用的是UTF
objectOutputStream.writeBoolean(false);
objectOutputStream.writeObject(new Dog("Tom", 13));
// 关闭对象流
objectOutputStream.close();
}
}
//ObjectInputStream的使用
public class ObjectInputStream_ {
// 从文件中读取对象, 然后打印出来
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E:\\JavaCode\\基础\\Chapter18\\stream_\\data.dat"));
int a = objectInputStream.readInt();
char c = objectInputStream.readChar();
String str = objectInputStream.readUTF();
boolean b = objectInputStream.readBoolean();
Object dog = objectInputStream.readObject();
System.out.println(a);
System.out.println(c);
System.out.println(str);
System.out.println(b);
// System.out.println("dog的运行类型为" + dog.getClass());
System.out.println(dog);
// 运行结束后关闭对象流
objectInputStream.close();
}
}
// dog类
public class Dog implements Serializable {
String name;
int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
转换流 InputStreamReader & OutputStreamWriter
转换流可以将字节流转为字符流. 直接用字符流可能字符的编码方式不同,会出现乱码. 而字节流可以指定编码方式, 然后再转为字符流就不会出现乱码.
public class InputStreamReader_ {
// 演示使用InputStreamReader转换流 解决中文乱码问题.
public static void main(String[] args) throws Exception {
// // 出现乱码
// BufferedReader bufferedReader = new BufferedReader(new FileReader("E:\\JavaCode\\基础\\Chapter18\\transformation\\text.txt")); //该文件保存是gbk编码. 读取文件默认是按照utf-8编码
// String line;
// while((line = bufferedReader.readLine()) != null){
// System.out.println(line);
// }
// bufferedReader.close(); // 关闭流
FileInputStream fileInputStream = new FileInputStream("E:\\JavaCode\\基础\\Chapter18\\transformation\\text.txt"); // 字节文件输入流
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "gbk"); //转化为转换流
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
bufferedReader.close(); // 关闭流
}
}
public class OutputStreamWriter_ {
public static void main(String[] args) throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream("E:\\JavaCode\\基础\\Chapter18\\transformation\\text.txt"); // 文件字节流
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk"); // 转换流
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); // 处理流
bufferedWriter.write("我是你的爹爹");
bufferedWriter.newLine();
bufferedWriter.write("father, you know?");
bufferedWriter.close(); // 关闭外层流
}
}
字节打印流/ 输出流
// 演示 PrintStream (字节打印流/输出流)
public class PrintStream_ {
public static void main(String[] args) throws IOException {
//PrintInputStream
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 = new PrintWriter(System.out);
printWriter.print("hi, 北京你好~~~~");
// 写入文件
PrintWriter printWriter1 = new PrintWriter(new FileWriter("E:\\JavaCode\\基础\\Chapter18\\transformation\\text.txt"));
printWriter1.close(); // close才是真正写入数据的步骤.
printWriter.close();
}
}
properties类
序列化: 就是将各种数据以二进制形式保存到文件,数据库或通过网络传输。
反序列化: 反序列化是将序列化的字节流还原为对象的过程,以便从文件、数据库或网络接收的数据中恢复对象。
properties使用
public class properties01 {
public static void main(String[] args) throws IOException {
// 使用Properties 类读取mysql.properties文件
// 1. 创建Properties对象
Properties properties = new Properties();
// 2. 读取指定的配置文件
properties.load(new FileReader("E:\\JavaCode\\基础\\Chapter18\\properties_\\mysql.properties"));
// 3. 把k-v显示在控制台
properties.list(System.out);
// 4. 根据key获取对应的value
String ip = properties.getProperty("ip");
String user1 = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println(ip);
System.out.println(user1);
System.out.println(pwd);
// 5. 创建新的k-v
properties.setProperty("charset", "utf-8");
properties.setProperty("user", "韩顺平"); // 如果之前有键值对, 则直接覆盖
properties.setProperty("pwd", "abc");
// 6. 修改后保存到文件内
properties.store(new FileOutputStream("E:\\JavaCode\\基础\\Chapter18\\properties_\\mysql.properties"), null); //comments表示文件的注释
}
}
homework
public class homework01 {
public static void main(String[] args) throws IOException {
// 创建文件夹, 在文件夹下创建文件, 并写入数据.
String parentPath = "F://mytemp";
File file = new File(parentPath);
if(!file.exists()) // 如果文件不存在
{
if(file.mkdirs()){
System.out.println("创建文件夹成功");
}
else{
System.out.println("创建文件夹失败");
}
}
else{
System.out.println("文件夹已存在.");
}
File file1 = new File(parentPath, "hello.txt");
if(!file1.exists()){
file1.createNewFile();
}
else{
System.out.println("文件已存在");
}
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file1));
bufferedWriter.write("hello, world");
bufferedWriter.close();
}
}
public class homework02 {
public static void main(String[] args) throws Exception {
/*
使用BufferedReader读取一个文本文件, 为每行加上行号,再联通内容一起输出到屏幕上.
*/
BufferedReader bufferedReader = new BufferedReader(new FileReader("E:\\JavaCode\\基础\\Chapter18\\homework\\text.txt"));
String line;
String content = "";
int i = 1;
while((line=bufferedReader.readLine())!=null){
System.out.println("当前读取到的内容为" + line);
content += Integer.toString(i) + " " + line + "\n";
i++;
System.out.println(content);
}
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("E:\\JavaCode\\基础\\Chapter18\\homework\\text.txt"));
bufferedWriter.write( content);
System.out.println("content为" + content);
bufferedWriter.close();
bufferedReader.close();
}
}
package Chapter18.homework;
import java.io.*;
import java.util.Properties;
public class homework03 {
/*
编写一个dog.properities, 要求name=tom, age=5, color=read
编写Dog类, 创建一个dog对象, 读取dog.properties 用响应的内容完成属性初始化,并输出.
将创建的Dog对象, 序列化到文件dog.dat中.
*/
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("E:\\JavaCode\\基础\\Chapter18\\homework\\dog.properties"));
String name = properties.getProperty("name");
int age = Integer.parseInt(properties.getProperty("age")); // Integer.parseInt() 将String转为int
String color = properties.getProperty("color");
Dog dog = new Dog(name, age, color);
System.out.println("新创建的dog为" + dog);
// 将创建的dog对象, 序列化到dog.dat文件中.
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:\\JavaCode\\基础\\Chapter18\\homework\\dog.dat"));
objectOutputStream.writeObject(dog);
objectOutputStream.close();
}
}
class Dog implements Serializable {
String name;
int age;
String color;
public Dog(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
}
// dog.properties:
name=tom
age=5
color=red
十八章 网络编程
- 在使用端口时,0-1024都不要使用.
InetAddress 类
相关方法
public class API_ {
// InetAddress类相关方法
public static void main(String[] args) throws UnknownHostException {
// 获取本机InetAddress对象: getLocalHost()
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost); // LAPTOP-NQ8MQL3C/192.168.217.1
// 根据指定主机名/域名获取IP地址对象: getByName
InetAddress byName = InetAddress.getByName("LAPTOP-NQ8MQL3C");
System.out.println(byName); // LAPTOP-NQ8MQL3C/192.168.217.1
InetAddress baidu = InetAddress.getByName("www.baidu.com");
System.out.println(baidu); // www.baidu.com/39.156.66.14
// 获取InetAddress对象的主机名getHostName
String hostName = baidu.getHostName();
System.out.println(hostName); // www.baidu.com
// 获取InetAddress对象的地址 getHostAddress
String hostAddress = baidu.getHostAddress();
System.out.println(hostAddress); // 39.156.66.14
}
}
Socket编程
// TCP字节流编程 01
// 服务端
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
// 1. 在本机的9999端口监听, 等待连接.(要求在本机没有其他服务在监听9999端口)
// ServerSocket和Socket的区别在于ServerSocket可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端socket正在监听9999端口, 等待连接...");
//2. 当没有客户端连接9999端口时, 程序会阻塞,等待连接.
// 如果有客户端连接, 则会返回Socket对象, 程序继续.
Socket socket = serverSocket.accept();
System.out.println("服务端socket = " + socket);
// 3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
// 使用IO读取
int readLen = 0;
byte[] buf = new byte[1024];
while((readLen = inputStream.read(buf)) != -1){
System.out.println(new String(buf, 0, readLen));
}
System.out.println("服务端退出...");
// 关闭流和socket
socket.close();
inputStream.close();
serverSocket.close();
}
}
// 客户端
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
// 1. 连接服务器(通过连接指定IP的指定端口号去连接服务器)
// 如果连接成功, 则返回Socket对象.
Socket socket = new Socket(InetAddress.getLocalHost(), 9999); // 返回Socket对象.
System.out.println("客户端socket返回" + socket.getClass());
// 2. 连接上后, 生成Socket对象, 通过socket.getOutputStream()得到和socket对象关联的输出流对象.
OutputStream outputStream = socket.getOutputStream();
// 3. 通过输出流, 将数据写入到数据通道.
outputStream.write("hello, server".getBytes());
// 4. 关闭流和socket.
outputStream.close();
socket.close();
System.out.println("客户端退出了...");
socket.close();
outputStream.close();
}
}
// 2.0版本: 互相都说话, 加上结束标记.
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
int readLen = 0;
byte[] bytes = new byte[1024];
while ((readLen=inputStream.read(bytes))!=-1){
System.out.println(new String(bytes, 0, readLen));
}
outputStream.write("hello client!".getBytes());
// 设置结束标记!
socket.shutdownOutput();
serverSocket.close();
socket.close();
inputStream.close();
outputStream.close();
}
}
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
// ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, server".getBytes());
System.out.println("1 客户端发送消息完毕...");
// 设置结束标记.
socket.shutdownOutput(); // 这样之后才能保证我当前确保说完了!
InputStream inputStream = socket.getInputStream();
int readLen = 0;
byte[] bytes = new byte[1024];
System.out.println("2 客户端接收到服务端发送的消息...");
while((readLen=inputStream.read(bytes))!=-1){
System.out.println(new String(bytes, 0, readLen));
}
outputStream.close();
socket.close();
inputStream.close();
}
}
字符流
// 使用字符流去做网络编程
public class SocketTCP03Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("当前服务端正在等待接受客户端发来的信息");
Socket socket = serverSocket.accept();
System.out.println("当前服务端接收到客户端发来的信息。。。");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String line = null;
while((line = bufferedReader.readLine())!=null){
System.out.println(line);
}
bufferedWriter.write("hello, client!");
// bufferedWriter.newLine();
bufferedWriter.flush(); // 使用BufferedOutputStream和BufferedWriter, 需要手动刷新, 否则数据不会写入数据通道
socket.shutdownOutput();
bufferedReader.close();
socket.close();
serverSocket.close();
}
}
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
bufferedWriter.write("hello, server");
// bufferedWriter.newLine(); //设置写入结束标记。
bufferedWriter.flush(); // 如果使用的是字符流, 需要手动刷新, 否则数据不会写入数据通道
socket.shutdownOutput(); // 输出结束。
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
bufferedReader.close();
socket.close();
bufferedWriter.close();
}
}
//StreamUtils
public class StreamUtils {
/**
*
* @param is 输入流
* @return bytes[]
*/
public static byte[] stream2byteArray(InputStream is) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 实现将数据写入字节数组.
byte[] bytes = new byte[1024];
int readLen = 0;
while((readLen=is.read(bytes))!=-1){
byteArrayOutputStream.write(bytes, 0, readLen); // 将InputStream输入的数据写入到byteArrayOutputStream
}
byte[] byteArray = byteArrayOutputStream.toByteArray();
return byteArray;
}
/**
*
* @param is 输入流, 应该是向上转型了
* @return
* @throws IOException
*/
public static String stream2String(InputStream is) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
StringBuilder str = new StringBuilder();
String line = null;
while((line = bufferedReader.readLine())!=null){
str.append(line + "\n");
}
return str.toString();
}
}
//TCPFileCopyClient
public class TCPFileCopyClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
String srcPath = "E:\\JavaCode\\基础\\Chapter19\\bankCard.jpg";
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcPath)); // 从本地读取照片到输入流, 然后将流中的数据转为byte数组
byte[] bytes = StreamUtils.stream2byteArray(bufferedInputStream); // 将图片数据转为byte数组
// 然后将数据再发送给Server
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); // 用于将读取到的数据写入管道.
bufferedOutputStream.write(bytes);
socket.shutdownOutput(); //我当前发送完了, 我说一声.
System.out.println("Client发送数据成功");
BufferedInputStream bufferedInputStream1 = new BufferedInputStream(socket.getInputStream()); // 用于从管道中读取数据.
String s = StreamUtils.stream2String(bufferedInputStream1);
System.out.println("Client收到的Server的数据为" + s);
bufferedOutputStream.close();
bufferedInputStream1.close();
bufferedInputStream.close();
}
}
//TCPFileCopyServer
public class TCPFileCopyServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("当前Server正在等待...");
Socket socket = serverSocket.accept();
String receivePath = "E:\\JavaCode\\基础\\Chapter19\\receive.jpg";
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); //用于从管道中读取数据
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(receivePath)); // 用于将读取到的数据写入到本地.
BufferedOutputStream bufferedOutputStream1 = new BufferedOutputStream(socket.getOutputStream()); // 用于将数据写入管道
// 使用管道从bufferedInputStream读取数据, 通过bufferedOutputStream写入到本地.
byte[] bytes = new byte[1024];
int readLen = 0;
while((readLen = bufferedInputStream.read(bytes))!=-1){
bufferedOutputStream.write(bytes, 0, readLen);
}
bufferedOutputStream1.write("接收完毕".getBytes());
// 关闭.
socket.shutdownOutput();
bufferedInputStream.close();
bufferedOutputStream1.close();
bufferedOutputStream.close();
}
}
netstat
betstat -anb | more 以分页方式,查看外部地址以及使用的程序
TCP小知识点
当客户端连接到服务端后, 实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP来分配的。
就是服务端是一个ServerSocket, 客户端是连接到某个IP的某个端口,客户端实现这一操作也是通过在一个端口上实现的。
UDP网络编程
基本介绍
基本流程
DatagramSocket在发送数据时要指明发送的IP和端口号, 但是在接收数据时不需要指定这个。
//Receiver
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 创建一个 DatagramSocket 对象,准备在9999接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length); // 接收数据时不需要写IP和端口号
//3. 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet对象
//老师提示: 当有数据包发送到 本机的9999端口时,就会接收到数据
// 如果没有数据包发送到 本机的9999端口, 就会阻塞等待.
System.out.println("接收端A 等待接收数据..");
socket.receive(packet);
//4. 可以把packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
byte[] data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);
//===回复信息给B端
//将需要发送的数据,封装到 DatagramPacket对象
data = "好的, 明天见".getBytes();
//说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);
socket.send(packet);//发送
//5. 关闭资源
socket.close();
System.out.println("A端退出...");
}
}
// Sender
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//1.创建 DatagramSocket 对象,准备在9998端口 接收数据
DatagramSocket socket = new DatagramSocket(9998);
//2. 将需要发送的数据,封装到 DatagramPacket对象
byte[] data = "hello 明天吃火锅~".getBytes(); //
//说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9999);
socket.send(packet);
//3.=== 接收从A端回复的信息
//(1) 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
//(2) 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet对象
//老师提示: 当有数据包发送到 本机的9998端口时,就会接收到数据
// 如果没有数据包发送到 本机的9998端口, 就会阻塞等待.
socket.receive(packet);
//(3) 可以把packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);
//关闭资源
socket.close();
System.out.println("B端退出");
}
}
小坑: 使用BufferedOutputStream和BufferedWriter, 需要手动刷新, 否则数据不会写入数据通道
public class homework03Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待下载文件");
Socket socket = serverSocket.accept();
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); // 从网络中读取数据.
int readLen = 0;
byte[] buf = new byte[1024];
String music = "";
while((readLen=bufferedInputStream.read(buf))!=-1){
music += new String(buf, 0, readLen); // 将读取的byte[]转为String
System.out.println("music=" + music + " readlen=" + readLen);
}
//System.out.println("music=" + music + " readlen=" + readLen);
System.out.println("用户想要接受的输入为" + music);
FileInputStream fileInputStream = null;
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); // 用来通过网络传播数据
if(music.equals("高山流水")){
System.out.println("开始传送高山流水文件");
fileInputStream = new FileInputStream("./基础/Chapter19/homework/高山流水.mp3");
byte[] bytes = homework03StreamUtils.stream2byteArray(fileInputStream); // 要传输的数据
bufferedOutputStream.write(bytes);
}
else{
System.out.println("开始传送默认文件");
fileInputStream = new FileInputStream("./基础/Chapter19/homework/无名.mp3");
byte[] bytes = homework03StreamUtils.stream2byteArray(fileInputStream);
bufferedOutputStream.write(bytes);
}
serverSocket.close();
socket.close();
fileInputStream.close();
bufferedOutputStream.close();
}
}
public class homework03Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getByName("219.217.63.93"), 9999);
OutputStream outputStream = socket.getOutputStream();// 网络发送需要的歌曲
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); // 通过网络写入
String s = "高山流水";
bufferedOutputStream.write(s.getBytes());
bufferedOutputStream.flush(); //使用BufferedOutputStream和BufferedWriter, 需要手动刷新!!! 否则数据不会写入数据通道
socket.shutdownOutput(); //发送完后说一下发送完了, TCP要告诉对方我说完了.
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); // 网络接受发送端的数据
byte[] bytes = homework03StreamUtils.stream2byteArray(bufferedInputStream); // 得到数据的字节数组.
BufferedOutputStream bufferedOutputStream1 = new BufferedOutputStream(new FileOutputStream("./基础/Chapter19/homework/receive.mp3")); // 存储接收的数据. Java中的.表示当前项目路径.
bufferedOutputStream1.write(bytes);
System.out.println("收到文件了");
bufferedInputStream.close();
bufferedOutputStream1.close();
}
}
public class homework03StreamUtils {
public static byte[] stream2byteArray(InputStream is) throws IOException {
// 将输入流转为byte数组
// 这个需要用到ByteArrayOutputStream流
byte[] buf = new byte[1024];
int readLen = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while((readLen=is.read(buf))!=-1){
byteArrayOutputStream.write(buf,0, readLen);
}
byte[] byteArray = byteArrayOutputStream.toByteArray();
return byteArray;
}
public static String stream2String(InputStream is) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); // 转换流, 将字节流转为字符流.
String line=null;
StringBuilder res = null; // 使用StringBuilder来扩充字符串
while((line=bufferedReader.readLine())!=null){
res.append( line + "\n");
}
return res.toString();
}
}
项目开发流程
拉取在线用户列表逻辑:
1.客户端通过一个message发送请求,在message中设置请求在线用户列表
2. 服务端在线程run方法中收到对应请求后,通过一个方法得到所有的在线用户列表(以空格分开), 然后将此信息通过Message传回客户端。
3. 客户端在线程的run方法中收到对应类型的message后,打印出所有的在线用户列表
无异常退出
群聊实现逻辑
1.客户端输入发送的内容,构建好message对象发送给服务器。
1. 客户端如果选择了2, 表示我想群发,调用服务类中的sendToAll方法。
case 2:{
userClientService.sendToAll();
break;
}
2. sendToAll():
public void sendToAll() throws IOException {
System.out.print("请输入您要群发的消息:");
String content = scanner.next();
Message message = new Message();
message.setContent(content);
message.setSender(user.getUserId());
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message);
}
2.服务器收到后,首先得到所有的用户列表,然后将传来的message对象发送给所有的用户。
case MessageType.MESSAGE_TO_ALL_MES:{
// 接收客户端发来的消息message
String sender = message.getSender();
String content = message.getContent();
System.out.println("用户" + sender + " 向大家群发了消息, 消息是" + content);
HashMap<String, ServerConnectClientThread> hashMap = ManageServerConnectClientThread.getHashMap(); // 得到哈希树
Iterator<String> iterator = hashMap.keySet().iterator(); // 得到哈希树key的迭代器, 里面保存的用户Id
while (iterator.hasNext()) {
String userId = iterator.next().toString();
// 下面这行代码尤其注意, 发送给的线程是服务端所有线程而不是本类的socket.本类的socket只发给自己.
//ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); //不对, socket只是自己的socket并不是大家全部各自的socket
ObjectOutputStream objectOutputStream = new ObjectOutputStream(ManageServerConnectClientThread.getThread(userId).getSocket().getOutputStream()); // 对. 各个线程的socket.
objectOutputStream.writeObject(message);
}
break;
}
3.服务端向大家发送了这个群消息后, 客户端的线程也需要接收这个消息.
客户端的线程的while loop:
case MessageType.MESSAGE_TO_ALL_MES:{
System.out.println("\n【您收到一条群发消息】:用户" + message.getSender() + "对大家所有人说" +
message.getContent());
break;
}
发送文件逻辑梳理
一句话概括: 发送方指定源文件路径, 目标文件路径, 源文件的byte数组形式打包成message对象发送给服务器, 服务器将此message对象转发给要转发用户所在线程的socket对象. 此对象(同样是在客户端) 在线程中监听, 当得到从服务器端发来的文件类型message后, 将message对象的byte数组写入到目标路径.
1. 发送方指定... 打包message对象.
case 4:{
userClientService.sendFile();
break;
}
=> public void sendFile()
public void sendFile() throws IOException {
Message message = new Message();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
message.setSender(user.getUserId());
System.out.print("请输入您要发送的用户");
message.setGetter(scanner.next());
System.out.print("请输入源文件地址:");
message.setSrc(scanner.next());
System.out.print("请输入对方的目的地址:");
message.setDest(scanner.next());
// 得到源文件数据流
FileInputStream fileInputStream = new FileInputStream(new File(message.getSrc())); //获取源文件
// 再将源文件数据流转换为bytes数组
byte[] buf = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 使用byteArrayOutputStream将读取的byte数据导入到byte数组中
int readLen = 0;
while((readLen = fileInputStream.read(buf))!=-1){
byteArrayOutputStream.write(buf, 0, readLen);
}
byte[] data = byteArrayOutputStream.toByteArray(); //将流中的数据导入变成data的byte数组
message.setFileBytes(data);
message.setMesType(MessageType.MESSAGE_FILE_MES);
objectOutputStream.writeObject(message);
}
2. 服务器将此message对象转发给对应用户
case MessageType.MESSAGE_FILE_MES:{
// 服务器只做一个中转的作用,将message对象再传递给要发送的用户
// 先得到线程对象
ServerConnectClientThread thread = ManageServerConnectClientThread.getThread(message.getGetter());
if(thread==null){
System.out.println("您要发送文件的用户不在线");
break;
}
ObjectOutputStream objectOutputStream = new ObjectOutputStream(thread.getSocket().getOutputStream());
objectOutputStream.writeObject(message);
break;
}
3. 目标用户得到message对象, 然后将此message对象中的数据写入到目标路径中.
在客户端的线程的while循环中, 如果监听到了来自服务器的文件消息, 则将此消息写入本地
case MessageType.MESSAGE_FILE_MES:{
System.out.println("\n用户" + message.getSender() + " 将源文件" + message.getSrc()
+ "发送到目标用户" + message.getGetter() + "的"+message.getDest());
FileOutputStream fileOutputStream = new FileOutputStream(new File(message.getDest()));
fileOutputStream.write(message.getFileBytes());
fileOutputStream.close(); // 写完之后关闭文件
System.out.println("文件写入成功");
break;
}
实现离线留言和离线发文件.
反射
// 反射快速入门
public class ReflectionQuestion {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Properties properties = new Properties();
properties.load(new FileReader(".//src//re.properties"));
String method = properties.get("method").toString();
String classpath = properties.get("classpath").toString();
// 使用反射机制
// 1.加载类, 返回Class类型的对象cls
Class aClass = Class.forName(classpath);
// 2. 通过aClass得到你加载的类的对象实例
Object o = aClass.newInstance();
// 3. 通过aClass得到你加载的类中的方法的对象
// 即: 在方法中, 可以把方法视为对象
Method method1 = aClass.getMethod(method);
// 反射机制调用方法: 方法.invoke(类)]
method1.invoke(o);
}
}
反射原理
Class类
获取Class对象
public static void main(String[] args) throws ClassNotFoundException {
String classFullPath = "Class_.Car";
//1. Class.forName()
Class cls1 = Class.forName(classFullPath);
System.out.println(cls1);
//2. 类.class
Class<Car> cls2 = Car.class;
System.out.println(cls2);
// 3.对象.getClass() 应用于有对象实例的情况
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);
// 4. 通过类加载器[4种]来获取到类的Class对象
// (1) 先得到类加载器car
ClassLoader classLoader = car.getClass().getClassLoader();
// (2) 通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classFullPath);
System.out.println(cls4);
System.out.println(cls1.hashCode()); // 都是一个,因为Car的Class类对象只有一个.
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
}
静态加载和动态加载
常用方法
通过反射用构造器创建对象
通过反射访问类中的成员
访问属性
访问方法
注意下面的1根据获取方法名获取方法对象里的XX.class 中的XX表示参数的类型(String name的话, XX就是String)
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1. 得到 Boss 类对应的 Class 对象
Class<?> bossCls = Class.forName("Reflection.Boss");
//2. 创建对象
Object o = bossCls.getDeclaredConstructor().newInstance();
//3. 调用 public 的 hi 方法
Method hi = bossCls.getMethod("hi", String.class);//这里的String.class表示hi的参数
//3.2 调用
hi.invoke(o, "韩顺平教育~");
//4. 调用private static 方法
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);
System.out.println(say.invoke(null, 200, "李四", '女')); // 调用say方法,后面是参数
// 5.在反射中, 如果方法有返回值, 统一返回Object, 但他的运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 100, "王五", '男');
say.setAccessible(true);
System.out.println("reVal的运行类型为" + reVal.getClass()); //reVal的运行类型为class java.lang.String
}
}
public class Boss {//类
public int age;
private static String name;
public Boss() {//构造器
}
public Monster m1() {
return new Monster();
}
private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}
public void hi(String s) {//普通 public 方法
System.out.println("hi " + s);
}
}
class Monster{}
作业
public class homework01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
// 1. 获取Class对象
Class cls = Class.forName("homework.PrivateTest");
// 2. 获取对象实例
Object o = cls.getDeclaredConstructor().newInstance();
// 3. 获取方法
Method clsMethod = cls.getMethod("getName");
System.out.println("name=" + clsMethod.invoke(o));
// 4.修改name
Field name = cls.getDeclaredField("name");
name.setAccessible(true);
name.set(o, "儿子");
System.out.println("name=" + clsMethod.invoke(o));
}
}
public class PrivateTest {
private String name = "hellokitty";
public String getName() {
return name;
}
}
public class homework02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class cls = Class.forName("java.io.File");
// 获取File的所有构造器
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.toString());
}
// 通过newInstance方法创建File对象, 并创建文件
// 1. 得到我想要的构造器
Constructor declaredConstructor = cls.getDeclaredConstructor(String.class);
// 创建File对象
Object file = declaredConstructor.newInstance("E:\\JavaCode\\基础\\Chapter20\\src\\homework\\aa.txt"); // 里面传的是参数具体内容
Method createNewFile = cls.getMethod("createNewFile");
createNewFile.invoke(file);
}
}