常用API(一):Objects类、toString、equals方法,对象克隆clone
什么是API?
- API(Application Programming interface) :应用程序编程接口
- 就是Java帮我们已经写好一些程序,如:类、方法等,我们直接拿过来用就可以解决一些问题。
为什么要写别人写好的程序?
开发效率高!
常用API
Object类的作用:
Object类是Java中所有类的祖宗类,因此,Java中所有类的对象都可以直接使用Object类中提供的一些方法。
Object类的常见方法
package com.liu.d13_api_object;
import java.util.Objects;
public class Student extends Object{ //extends Object{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//重写equals方法,比较两个对象的内容一样返回true
//比较者:s2 == this
//被比较者:s1 == o
@Override
public boolean equals(Object o) {
//1、判断两个对象是否地址一样,一样直接返回true
if (this == o) return true;
//2、判断o是null直接返回false,或者比较者的类型与被比较者的类型不一样,返回false
// Student.class != Pig.class
if (o == null || getClass() != o.getClass()) return false;
//3、o不是null,且o一定是学生类型的对象,开始比较内容了!
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
toString存在的意义:
toString()方法存在的意义就是为了被子类重写,以便返回对象具体的内容。
equals存在的意义:
直接比较两个对象的地址是否相同完全可以用“==”替代equals,equals存在的意义就是为了被子类重写,以便子类自己来定制比较规则(比如比较对象内容)。
Object类提供的对象克隆方法
package com.liu.d13_api_object;
//Cloneable是一个标记接口
//规则
public class User implements Cloneable{
private int id;//编号
private String username; //用户名
private String password; //密码
private double[] score; //分数
public User() {
}
public User(int id, String username, String password, double[] score) {
this.id = id;
this.username = username;
this.password = password;
this.score = score;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//super 去调用父类Object中的clone方法
return super.clone();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public double[] getScore() {
return score;
}
public void setScore(double[] score) {
this.score = score;
}
}
package com.liu.d13_api_object;
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
//目标:掌握Object类提供的对象克隆的方法
//1、protected Object clone(),对象克隆
User u1 = new User(1,"张三",
"500",new double[]{99.0,99.5});
User u2 = (User) u1.clone();
System.out.println(u1.getId());
System.out.println(u1.getUsername());
System.out.println(u1.getPassword());
System.out.println(u1.getScore());
System.out.println(u2.getId());
System.out.println(u2.getUsername());
System.out.println(u2.getPassword());
System.out.println(u2.getScore());
}
}
浅克隆
拷贝出的新对象,与原对象中的数据一模一样(引用类型拷贝的只是地址)
深克隆
- 对象中基本类型的数据直接拷贝。
- 对象中的字符串数据拷贝的还是地址。
- 对象中还包含的其他对象,不会拷贝地址,会创建新对象。
package com.liu.d13_api_object;
//Cloneable是一个标记接口
//规则
public class User implements Cloneable{
private int id;//编号
private String username; //用户名
private String password; //密码
private double[] score; //分数
public User() {
}
public User(int id, String username, String password, double[] score) {
this.id = id;
this.username = username;
this.password = password;
this.score = score;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//super 去调用父类Object中的clone方法
User u2 = (User) super.clone();
u2.score = u2.score.clone();
return u2;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public double[] getScore() {
return score;
}
public void setScore(double[] score) {
this.score = score;
}
}
package com.liu.d13_api_object;
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
//目标:掌握Object类提供的对象克隆的方法
//1、protected Object clone(),对象克隆
User u1 = new User(1,"张三",
"500",new double[]{99.0,99.5});
User u2 = (User) u1.clone();
System.out.println(u1.getId());
System.out.println(u1.getUsername());
System.out.println(u1.getPassword());
System.out.println(u1.getScore());
System.out.println(u2.getId());
System.out.println(u2.getUsername());
System.out.println(u2.getPassword());
System.out.println(u2.getScore());
}
}
什么是对象克隆?
复制一个一模一样的新对象出来。
常用API(一):Objects类、包装类
Objects
Objects是一个工具类,提供了很多操作对象的静态方法给我们使用。
Objects类的常见方法
package com.liu.d14_api_objects;
import java.util.Objects;
public class Test {
public static void main(String[] args) {
//目标:掌握Objects类提供的常用方法
String s1 = null;
String s2 = "java";
//System.out.println(s1.equals(s2)); 执行出来会报错
System.out.println(Objects.equals(s1, s2)); //false //更安全、更好
System.out.println(Objects.isNull(s1)); //true
System.out.println(s1 == null); //true
System.out.println(Objects.isNull(s2)); //false
System.out.println(s2 == null);//false
System.out.println(Objects.nonNull(s2)); //true
System.out.println(Objects.nonNull(s1)); //false
}
}
为什么要使用Objects类提供的equals方法来比较两个对象?
更安全。
为什么要有包装类?
包装类
包装类就是把基本类型的数据包装成对象。
自动装箱:
基本数据类型可以自动转换为包装类型。
自动拆箱:
包装类型可以自动转换为基本数据类型。
package com.liu.d15_integer;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
//目标:掌握包装类的使用
// Integer a1 = new Integer(12);
Integer a2 = Integer.valueOf(12);
System.out.println(a2);
//自动装箱;可以自动把基本类型的数据转换成对象
Integer a3 = 12;
//自动拆箱:可以自动把包装类型的对象转换成对应的基本数据类型
int a4 = a3;
//泛型和集合不支持基本数据类型,支持引用数据类型
//ArrayList<int> list = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<>();
list.add(12);//自动装箱
list.add(13);//自动装箱
int rs = list.get(1); //自动装箱
}
}
包装类的其他常见操作
- 可以把基本类型的数据转换成字符串类型。
- 可以把字符串类型的数值转换成数值本身对应的数据类型。
package com.liu.d15_integer;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
//目标:掌握包装类的使用
// Integer a1 = new Integer(12);
Integer a2 = Integer.valueOf(12);
System.out.println(a2);
//自动装箱;可以自动把基本类型的数据转换成对象
Integer a3 = 12;
//自动拆箱:可以自动把包装类型的对象转换成对应的基本数据类型
int a4 = a3;
//泛型和集合不支持基本数据类型,支持引用数据类型
//ArrayList<int> list = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<>();
list.add(12);//自动装箱
list.add(13);//自动装箱
int rs = list.get(1); //自动装箱
System.out.println("------------------------------");
//1、 把基本类型的数据转换成字符串
Integer a = 23;
String rs1 = Integer.toString(a); // "23"
System.out.println(rs1+1); //231
String rs2 = a.toString();// "23"
System.out.println(rs2+1);
String rs3 = a + "";
System.out.println(rs3+1);
//2、把字符串类型的数值转换成对应的基本类型
String ageStr = "29";
//int ageI = Integer.parseInt(ageStr); // 29
int ageI = Integer.valueOf(ageStr); //29
System.out.println(ageI + 1); // 30
String scoreStr = "99.5";
//double score = Double.parseDouble(scoreStr); //99.5
double score = Double.valueOf(scoreStr); //99.5
System.out.println(score + 0.5);
}
}
常用API(一):StringBuilder、StringBuffer、StringJoiner
StringBuilder
- StringBuilder代表可变字符串对象,相当于是一个容器,它里面装的字符串是可以改变的,就是用来操作字符串的。
- 好处:StringBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁。
package com.liu.d16_stringBuilder;
public class Test1 {
public static void main(String[] args) {
//目标:搞清楚StringBuilder的用法和使用
// StringBuilder s = new StringBuilder(); // s ""
StringBuilder s = new StringBuilder("懒羊羊"); // s "懒羊羊"
//1、拼接内容
s.append(12);
s.append("小懒");
s.append(true);
//支持链式编程
s.append(666).append("小王子").append(500);
System.out.println(s); //懒羊羊12小懒true666小王子500
//2、反转操作
s.reverse();
System.out.println(s); //005子王小666eurt懒小21羊羊懒
//3、返回字符串的长度
System.out.println(s.length()); //20
//4、 把StringBuilder对象又转换成String类型
String rs = s.toString();
System.out.println(rs); //005子王小666eurt懒小21羊羊懒
}
}
为啥操作字符串建议使用StringBuilder,而不用原来学过的String??
- 对于字符串相关的操作,如频繁的拼接、修改等,建议用StringBuidler,效率更高!
- 注意:如果操作字符串较少,或者不需要操作,以及定义字符串变量,还是建议用String。
package com.liu.d16_stringBuilder;
public class Test2 {
public static void main(String[] args) {
//目标:掌握StringBuilder的好处
//需求:拼接100万次abc
//先用String测试看看性能
// String rs = "";
// for (int i = 0; i <1000000 ; i++) {
// rs = rs + "abc";
// }
// System.out.println(rs);
//使用StringBuilder演示
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
sb.append("abc");
}
System.out.println(sb);
}
}
StringBuffer与StringBuilder 注意:
- StringBuffer的用法与StringBuilder是一模一样的
- 但 StringBuilder是线程不安全的 StringBuffer是线程安全的
案例:返回任意整数型数组的内容
分析:
- 方法是否需要接收数据?需要接收整型数组
- 方法是否需要返回数据?需要返回拼接后的结果
- 方法内部:遍历数组的数据,把遍历到的数据都拼接起来,此时使用StringBuilder来完成拼接
package com.liu.d16_stringBuilder;
public class Test3 {
public static void main(String[] args) {
//目标:完成遍历数组的内容,并拼接成指定格式的案例
System.out.println(getArrayDay(new int[]{11, 22, 33}));
}
public static String getArrayDay(int[] arr) {
//1、判断arr是否为null
if (arr == null) {
return null;
}
//2、arr数组对象存在 arr = 【11,22,33】
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
sb.append(arr[i]);
} else {
sb.append(arr[i]).append(",");
}
}
sb.append("]");
return sb.toString();
}
}
为什么学StringJoiner?
StringJoiner
- JDK8开始才有的,跟StringBuilder一样,也是用来操作字符串的,也可以看成是一个容器,创建之后里面的内容是可变的。
- 好处:不仅能提高字符串的操作效率,并且在有些场景下使用它操作字符串,代码会更简洁
package com.liu.d17_stringjoiner;
import java.util.StringJoiner;
public class Test {
public static void main(String[] args) {
//目标:掌握StringJoiner的使用
// StringJoiner s1 = new StringJoiner(","); //间隔符
StringJoiner s = new StringJoiner(",","[","]"); //间隔符
s.add("java1");
s.add("java2");
s.add("java3");
// System.out.println(s1); //java1,java2,java3
System.out.println(s); //[java1,java2,java3]
System.out.println(getArrayDay(new int[]{12,22,33}));
}
public static String getArrayDay(int[] arr) {
//1、判断arr是否为null
if (arr == null) {
return null;
}
//2、arr数组对象存在 arr = 【11,22,33】
StringJoiner s = new StringJoiner(",","[","]");
for (int i = 0; i < arr.length; i++) {
s.add(arr[i] + "");
}
return s.toString();
}
}
常用API(二):Math、System、Runtime
Math
- 代表数学,是一个工具类,里面提供的都是对数据进行操作的一些静态方法。
Math类提供的常见方法
package com.liu.math;
public class MathTest {
public static void main(String[] args) {
//目标:了解Math类提供的常见方法
//1、abs 取绝对值
System.out.println(Math.abs(-12));//12
System.out.println(Math.abs(123));//123
System.out.println(Math.abs(-3.14));//3.14
//2、ceil向上取整
System.out.println(Math.ceil(4.00000001)); //5.0
System.out.println(Math.ceil(4.0)); //4.0
//3、floor 向下取整
System.out.println(Math.floor(4.99999999)); //4.0
System.out.println(Math.floor(4.0)); //4.0
//4、round 四舍五入
System.out.println(Math.round(3.49999)); //3
System.out.println(Math.round(3.5001)); //4
//5、max:取最大值 min:取最小值
System.out.println(Math.max(10, 20)); //20
System.out.println(Math.min(10, 20)); //10
//6、pow 取次方
System.out.println(Math.pow(2, 3));//2的3次方 8.0
System.out.println(Math.pow(3, 2));//3的2次方 9.0
//7、random 取随机数
System.out.println(Math.random()); //取值数在【0.0 , 1.0)
}
}
System
- System代表程序所在的系统,也是一个工具类。
System类提供的常见方法
package com.liu.math;
public class SystemTest {
public static void main(String[] args) {
//目标:了解System类的常见方法
//1、exit
//终止当前运行的Java虚拟机
//该参数用作状态代码;按照惯例,非零状态代码表示异常终止
//System.exit(0); //人为的终止虚拟机(不要使用)
System.out.println("_____________________________________--");
//2、currentTimeMillis()
//获取当前系统的时间
//返回的是long类型的时间毫秒值:指的是从1970——1——1 0:0:0开始走到此刻的总的毫秒值,1s = 100ms
long time = System.currentTimeMillis();
System.out.println(time);
for (int i = 0; i < 1000000; i++) {
System.out.println("输出了:" + i);
}
long time2 = System.currentTimeMillis();
System.out.println((time2-time)/1000.0 + "s");
}
}
时间毫秒值
- 指的是从1970年1月1日 00:00:00 走到此刻的总的毫秒数,应该是很大的。 1s = 1000ms。
为啥选择“1970年1月1日 00:00:00” 作为时间的起点?
- 1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编译语言在老旧的PDP-7机器上开发出了Unix的一个版本。随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNIX。
- 1970年1月1日 算C语言的生日
Runtime
- 代表程序所在的运行环境。
- Runtime是一个单例类。
Runtime类提供的常见方法
package com.liu.math;
import java.io.IOException;
public class RuntimeTest {
public static void main(String[] args) throws IOException, InterruptedException {
//目标:了解Runtime的几个常见方法
//1、public static Runtime getRuntime()
//返回与当前Java应用程序关联的运行时对象
Runtime r = Runtime.getRuntime();
//2、public void exit (int status)
//终止当前运行的虚拟机
//r.exit(0);
//3、public int availableProcessors()
//返回Java虚拟机可用的处理器数。
System.out.println(r.availableProcessors());
//4、public long totalMemory()
//返回Java虚拟机中的内存总量
System.out.println(r.totalMemory()/1024.0/1024.0 + "MB"); //1024 = 1k 1024* 1024 = 1M
//5、public long freeMemory()
//返回Java虚拟机中的可用内存
System.out.println(r.freeMemory() / 1024.0 / 1024.0 + "MB");
//6、public Process exec(String command)
//启动某个程序,并返回代表该程序的对象
r.exec("\u202AC:\\Users\\biscuit\\Desktop\\网易云音乐.lnk");
//直接启动qq程序,前提qq启动命令已经配置在环境变量里
Process p = r.exec("QQ");
Thread.sleep(5000); //让程序在这里暂停5秒后继续往下走!!!
p.destroy(); //销毁!关闭程序!
}
}
常用API(二):BigDecimal
BigDecimal
- 用于解决浮点型运算时,出现结果失真的问题。
BigDecimal的常见构造器、常用方法
package com.liu.d2_bigDecimal;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BigDecimalDemo1 {
public static void main(String[] args) {
//目标:掌握DigDecimal的使用,解决小数运算失真的问题
double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println(c); //0.30000000000000004
System.out.println("----------------------------------");
//1、把它们变成字符串封装成BigDecimal对象来运算
// BigDecimal a1 = new BigDecimal(Double.toString(a));
// BigDecimal b1 = new BigDecimal(Double.toString(b));
//推荐用以下方式:把小数转换成字符串再得到BigDecimal对象来使用(更简洁)
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);
// BigDecimal c1 = a1.add(b1); //加法
// BigDecimal c1 = a1.subtract(b1); //减法
// BigDecimal c1 = a1.multiply(b1); //乘法
BigDecimal c1 = a1.divide(b1); //除法
System.out.println(c1);
BigDecimal i = BigDecimal.valueOf(0.1);
BigDecimal j = BigDecimal.valueOf(0.3);
BigDecimal k = i.divide(j,2, RoundingMode.HALF_UP); //除法
System.out.println(k);
//把BigDecimal对象转换成double类型的数据
double rs = k.doubleValue();
System.out.println(rs);
}
}
应该如何把浮点型转换成BigDecimal的对象?
- BigDecimal b1 = BigDecimal.valueOf(0.1)
常用API(二):传统时间:Date日期类、SimpleDateFormat
Date
- 代表的是日期和时间。
package com.liu.d3_time;
import java.util.Date;
public class Test1Date {
public static void main(String[] args) {
//目标:掌握Date日期类的使用
//1、创建一个Date的对象,代表系统当前时间信息的
Date d = new Date();
System.out.println(d);
//2、拿到时间毫秒值
long time = d.getTime();
System.out.println(time);
//3、把时间毫秒值转换成日期对象:2s之后的时间是多少
time += 2 * 1000;
Date d2 = new Date(time);
System.out.println(d2);
//4、直接把日期对象的时间通过setTime方法进行修改
Date d3 = new Date();
d3.setTime(time);
System.out.println(d3);
}
}
为什么要用SimpleDateFormat?
SimpleDateFormat
- 代表简单日期格式化,可以用来把日期对象、时间毫秒值格式化成我们想要的形式。
SimpleDateFormat
- 代表简单日期格式化,可以用来把日期对象、时间毫秒值格式化成我们想要的形式。
package com.liu.d3_time;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test2SimpleDateFormat {
public static void main(String[] args) {
//目标:掌握SimpleDateFormat的使用
//1、准备一些时间
Date d = new Date();
System.out.println(d);
long time = d.getTime();
System.out.println(time);
//2、格式化日期对象、时间毫秒值
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
String rs = sdf.format(d);
String rs2 = sdf.format(time);
System.out.println(rs);
System.out.println(rs2);
}
}
时间格式的常见符号:
SimpleDateFormat解析字符串时间成为日期对象
package com.liu.d3_time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test2SimpleDateFormat {
public static void main(String[] args) throws ParseException {
//目标:掌握SimpleDateFormat的使用
//1、准备一些时间
Date d = new Date();
System.out.println(d);
long time = d.getTime();
System.out.println(time);
//2、格式化日期对象、时间毫秒值
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
String rs = sdf.format(d);
String rs2 = sdf.format(time);
System.out.println(rs);
System.out.println(rs2);
System.out.println("-----------------------------------------------------");
//目标:掌握SimpleDateFormate解析字符串时间,或为日期对象
String dateStr = "2003-07-02 12:12:11";
//1、创建简单日期格式化对象,指定的时间格式必须与被解析的时间格式一模一样否则程序会出bug
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d2 = sdf2.parse(dateStr);
System.out.println(d2);
}
}
练习 秒杀活动
需求
- 小贾下单并付款的时间为:2023年11月11日 0:01:18
- 小皮下单并付款的时间为:2023年11月11日 0:10:51
- 请用代码说明这两位同学有没有参加上秒杀活动?
package com.liu.d3_time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test3 {
public static void main(String[] args) throws ParseException {
//目标:完成秒杀案例
//1、把开始时间、结束时间、小贾下单时间、小皮下单时间拿到程序中来
String start = "2023年11月11日 0:0:0";
String end = "2023年11月11日 0:10:0";
String xj = "2023年11月11日 0:01:18";
String xp = "2023年11月11日 0:10:57";
//2、把字符串的时间解析成日期或日期对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date startDt = sdf.parse(start);
Date endDt = sdf.parse(end);
Date xjDt = sdf.parse(xj);
Date xpDt = sdf.parse(xp);
//3、开始判断小皮和小贾是否秒杀成功了
//把日期对象转换成时间毫秒值来判断
long stratTime = startDt.getTime();
long endTime = endDt.getTime();
long xjTime = xjDt.getTime();
long xpTime = xpDt.getTime();
if (xjTime >= stratTime && xjTime <= endTime){
System.out.println("小贾您秒杀成功了!");
}else {
System.out.println("小贾您秒杀失败了!");
}
if (xpTime >= stratTime && xpTime <= endTime){
System.out.println("小皮您秒杀成功了!");
}else {
System.out.println("小皮您秒杀失败了!");
}
}
}
为什么要学Calendar?
需求:将2023年09月10日 增加一个月
- 定义字符串记住: 2023年09月10日
- 把字符串时间解析成Date日期对象
- 通过Date日期对象获取2023年9月10日的毫秒值
- 增加一个月的毫秒值:1000 * 60 * 60 * 24 * 30
- 再格式化时间毫秒值得到结果:2023年10月10日
Calendar
- 代表的是系统此刻时间对应的日历。
- 通过它可以单独获取、修改时间中的年、月、日、时、分、秒等。
Calendar
- 代表的是系统此刻时间对应的日历,通过它可以单独获取、修改时间中的年、月、日、时、分、秒等
Calendar日历类的常见方法
- 注意:calendar是可变对象,一旦修改后其对象本身表示的时间将产生变化。
package com.liu.d3_time;
import java.util.Calendar;
import java.util.Date;
public class Test4Calendar {
public static void main(String[] args) {
//目标:掌握Calendar的使用和特点
//1、得到系统此刻时间对应的日历对象
Calendar now = Calendar.getInstance();
System.out.println(now);
//2、获取日历中的某个信息
int year = now.get(Calendar.YEAR);
System.out.println(year);
int days = now.get(Calendar.DAY_OF_YEAR);
System.out.println(days);
//3、日历中记录的日期对象
Date d = now.getTime();
System.out.println(d);
//4、拿到时间毫秒值
long time = now.getTimeInMillis();
System.out.println(time);
//5、修改日历中的某个信息
now.set(Calendar.MONTH,9); //修改月份成为10月份
now.set(Calendar.DAY_OF_YEAR,125); //修改成一年中的125天
System.out.println(now);
//6、为某个信息增加或减少多少
now.add(Calendar.DAY_OF_YEAR,100);
now.add(Calendar.DAY_OF_YEAR,-10);
now.add(Calendar.DAY_OF_MONTH,6);
now.add(Calendar.HOUR,12);
System.out.println(now);
}
}
Calendar的对象,我们是如何获取的?
常用API(二):为啥要学习JDK8新增的时间
为什么要学JDK 8新增的时间?
package com.liu.d4_jdk8_time;
import java.util.Calendar;
import java.util.Date;
public class Test {
public static void main(String[] args) {
//目标:搞清楚为什么要用JDK 8 开始新增的时间类
//传统时间类
//1、设计不合理,使用不方便,很多都被淘汰了。
Date d = new Date();
// System.out.println(d.getYear() +1900);
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
System.out.println(year);
//2、都是可变对象,修改后会丢失最开始的时间信息。
//3、线程不安全。
//4、不能精确到纳秒,只能精确到毫秒。
//1秒 = 1000 毫秒
//1毫秒 = 1000微妙
//1微秒 = 1000纳秒
}
}
JDK8新增的时间
LocalDate:年、月、日
LocalTime:时、分、秒
LocalDateTime:年、月、日 时、分、秒
ZoneId:时区
ZonedDateTime:带时区的时间
Instant:时间戳/时间线
DateTimeFormatter:用于时间的格式化和解析
Duration:时间间隔(时、分、秒,纳秒)
Period:时间间隔(年,月,日)
常用API(二):为啥要学习JDK8新增的时间
LocalDate:
- 代表本地日期(年、月、日、星期)
LocalTime:
- 代表本地时间(时、分、秒、纳秒)
LocalDateTime:
- 代表本地日期、时间(年、月、日、星期、时、分、秒、纳秒)
它们获取对象的方案
LocalDate的常用API(都是处理年、月、日、星期相关的)。
package com.liu.d4_jdk8_time;
import java.time.LocalDate;
public class Test1_LocalDate {
public static void main(String[] args) {
//目标:获取本地日期对象
LocalDate ld = LocalDate.now(); //年 月 日
System.out.println(ld);
//1、获取日期对象中的信息
int year = ld.getYear(); //年
int month = ld.getMonthValue(); //月(1-12)
int day = ld.getDayOfMonth(); // 日
int dayOfYear = ld.getDayOfYear(); //一年中的第几天
int dayOfWeek = ld.getDayOfWeek().getValue(); //星期几
System.out.println(year);
System.out.println(day);
System.out.println(dayOfWeek);
//2、直接修改某个信息,返回新日期对象
//withYear、withMonth、withDayOfMonth、withDayOfYear
LocalDate ld2 = ld.withYear(2099);
System.out.println(ld2);
System.out.println(ld);
//3、把某个信息加多少,返回新日期对象
//plusYears、plusMonths、plusDays、plusWeeks
LocalDate ld4 = ld.plusYears(2);
LocalDate ld5 = ld.plusMonths(2);
//4、把某个信息减多少,返回新日期对象
//minusYears、minusMonths、minusDays,minusWeeks
LocalDate ld6 = ld.minusYears(5);
LocalDate ld7 = ld.minusYears(5);
//5、获取指定日期的LocalDate对象:
//public static LocalDate of(int year,int month,int dayOfMonth)
LocalDate ld8 = LocalDate.of(2011,12,12);
LocalDate ld9 = LocalDate.of(2099,12,12);
//6、判断两个日期对象,是否相等,在前还是在后
//minusYears、minusMonths、minusDays,minusWeeks equals isBefore isAfter
System.out.println(ld8.equals(ld9)); //false
System.out.println(ld8.isAfter(ld)); //false
System.out.println(ld8.isBefore(ld)); //true
}
}
LocalTime的常用API (都是处理时、分、秒、纳秒相关的)。
package com.liu.d4_jdk8_time;
import java.time.LocalTime;
public class Test2_LocalTime {
public static void main(String[] args) {
//目标:获取本地时间对象
LocalTime lt = LocalTime.now(); //时 分 秒
System.out.println(lt);
//1、获取时间对象中的信息
int hour = lt.getHour();//时
int minute = lt.getMinute();//分
int second = lt.getSecond();//秒
int nano = lt.getNano();//时秒
//2、修改时间
//withHour、withMinute、withSecond、withNano
LocalTime lt3 = lt.withHour(10);
LocalTime lt4 = lt.withMinute(10);
LocalTime lt5 = lt.withSecond(10);
LocalTime lt6 = lt.withNano(10);
//3、加多少
//plusHours、plusMinutes、plusSeconds、plusNanos
LocalTime lt7 = lt.plusHours(10);
LocalTime lt8 = lt.plusMinutes(10);
LocalTime lt9 = lt.plusSeconds(10);
LocalTime lt10 = lt.plusNanos(10);
//4、减多少
//minusHours、minusMinutes、minusSeconds、minusNanos
LocalTime lt11 = lt.minusHours(10);
LocalTime lt12 = lt.minusMinutes(10);
LocalTime lt13 = lt.minusSeconds(10);
LocalTime lt14 = lt.minusNanos(10);
//5、获取指定日期的LocalDate对象:
//public static LocalTime of(int hour,int minute,int second)
LocalTime lt15 = LocalTime.of(12,12,12);
LocalTime lt16 = LocalTime.of(12,12,12);
//6、判断两个日期对象,是否相等,在前还是在后
//minusYears、minusMonths、minusDays,minusWeeks equals isBefore isAfter
System.out.println(lt15.equals(lt16)); //true
System.out.println(lt15.isAfter(lt)); //true
System.out.println(lt15.isBefore(lt)); //false
}
}
LocalDateTime的常用API(可以处理年、月、日、星期、时、分、秒、纳秒等信息)
转换相关的API
LocalDateTime的转换成LocalDate、LocalTime
package com.liu.d4_jdk8_time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Test3_LocalDateTime {
public static void main(String[] args) {
//目标:获取本地日期和时间对象
LocalDateTime ldt = LocalDateTime.now();//年 月 日 时 分 秒
System.out.println(ldt);
//1、可以获取日期和时间的全部信息
int year = ldt.getYear();//年
int month = ldt.getMonthValue();//月
int day = ldt.getDayOfMonth();//日
int dayOfYear = ldt.getDayOfYear();//一年中的第几天
int dayOfWeek = ldt.getDayOfWeek().getValue();//获取是周几
int hour = ldt.getHour();//时
int minute = ldt.getMinute();//分
int second = ldt.getNano();//时秒
//2、修改时间信息
//withYear、withMonth、withdayOfMonth、withDayOfYear、withHour、withminute、withSecond、withNano
LocalDateTime ldt2 = ldt.withYear(2029);
LocalDateTime ldt3 = ldt.withMinute(50);
//3、加多少
//pulsYears、plusMonths、plusDays、plusWeeks、plusHours、plusMinutes、plusSeconds、plusManos
LocalDateTime ldt4 = ldt.plusYears(2);
LocalDateTime ldt5 = ldt.plusMinutes(3);
//4、减多少
//minusDays、minusYears、minusMonths、minusWeeks、minusHours、minusMinutes、minusSeconds、minusNanos
LocalDateTime ldt6 = ldt.minusYears(2);
LocalDateTime ldt7 = ldt.minusMinutes(3);
// 5、获取指定日期和时间的LocalDateTime对象:
// public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour,
// int minute, int second, int nanoOfSecond)
LocalDateTime ldt8 = LocalDateTime.of(2029, 12, 12, 12, 12, 12, 1222);
LocalDateTime ldt9 = LocalDateTime.of(2029, 12, 12, 12, 12, 12, 1222);
// 6、 判断2个日期、时间对象,是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(ldt9.equals(ldt8)); //true
System.out.println(ldt9.isAfter(ldt)); //true
System.out.println(ldt9.isBefore(ldt)); //false
// 7、可以把LocalDateTime转换成LocalDate和LocalTime
// public LocalDate toLocalDate()
// public LocalTime toLocalTime()
// public static LocalDateTime of(LocalDate date, LocalTime time)
LocalDate ld = ldt.toLocalDate(); //年 月 日
LocalTime lt = ldt.toLocalTime();//时 分 秒
LocalDateTime ldt10 = LocalDateTime.of(ld, lt);
}
}
JDK8新增的日期、时间类
什么是时区?
- 由于世界各个国家与地区的经度不同,各地区的时间也有所不同,因此会划分为不同的时区。
ZoneId 时区的常见方法
package com.liu.d4_jdk8_time;
import java.time.Clock;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Test4_ZoneId_ZonedDateTime {
public static void main(String[] args) {
// 目标:了解时区和带时区的时间。
// 1、ZoneId的常见方法:
// public static ZoneId systemDefault(): 获取系统默认的时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId.getId()); //Asia/Shanghai
System.out.println(zoneId); //Asia/Shanghai
// public static Set<String> getAvailableZoneIds(): 获取Java支持的全部时区Id
System.out.println(ZoneId.getAvailableZoneIds());
//Ctrl + F 可以搜索
// public static ZoneId of(String zoneId) : 把某个时区id封装成ZoneId对象。
ZoneId zoneId1 = ZoneId.of("America/New_York");//获取时区Id
// 2、ZonedDateTime:带时区的时间。
// public static ZonedDateTime now(ZoneId zone): 获取某个时区的ZonedDateTime对象。
ZonedDateTime now = ZonedDateTime.now(zoneId1);
System.out.println(now); //2024-09-06T21:25:21.564-04:00[America/New_York]
// 世界标准时间了
ZonedDateTime now1 = ZonedDateTime.now(Clock.systemUTC()); //世界标准时间
System.out.println(now1); //2024-09-07T01:25:21.568Z
// public static ZonedDateTime now():获取系统默认时区的ZonedDateTime对象
ZonedDateTime now2 = ZonedDateTime.now(); //获取系统默认时间
System.out.println(now2); //2024-09-07T09:25:21.568+08:00[Asia/Shanghai]
// Calendar instance = Calendar.getInstance(TimeZone.getTimeZone(zoneId1));
}
}
ZonedDateTime 带时区时间的常见方法
JDK8新增的日期、时间类
Instant 时间线上的某个时刻/时间戳
- 通过获取Instant的对象可以拿到此刻的时间,该时间由两部分组成:从1970-01-01 00:00:00 开始走到此刻的总秒数 + 不够1秒的纳秒数
- 作用:可以用来记录代码的执行时间,或用于记录用户操作某个事件的时间点。
- 传统的Date类,只能精确到毫秒,并且是可变对象;
- 新增的Instant类,可以精确到纳秒,并且是不可变对象,推荐用Instant代替Date。
package com.liu.d4_jdk8_time;
import java.time.Instant;
import java.time.LocalDateTime;
public class Test5_Instant {
public static void main(String[] args) {
// 1、创建Instant的对象,获取此刻时间信息
Instant now = Instant.now(); // 不可变对象
// 2、获取总秒数
long second = now.getEpochSecond();
System.out.println(second); //1725673504
// 3、不够1秒的纳秒数
int nano = now.getNano();
System.out.println(nano); //491000000
//491 不够一秒的一秒数
System.out.println(now); //2024-09-07T01:45:04.491Z
//加111纳秒
Instant instant = now.plusNanos(111);
//搞清楚 Instant对象的作用!
// Instant对象的作用:做代码的性能分析,或者记录用户的操作时间点
Instant now1 = Instant.now();
// 代码执行。。。。
Instant now2 = Instant.now();
LocalDateTime l = LocalDateTime.now();
}
}
常用API(二):JDK8新时间:DateTimeFormatter、Period、Duration
JDK8新增的日期、时间类
DateTimeFormatter
LocalDateTime提供的格式化、解析时间的方法
package com.liu.d4_jdk8_time;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Test6_DateTimeFormatter {
public static void main(String[] args) {
/**
* 目标:掌握JDK 8新增的DateTimeFormatter格式化器的用法。
*/
// 1、创建一个日期时间格式化器对象出来。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
// 2、对时间进行格式化
LocalDateTime now = LocalDateTime.now();
System.out.println(now); //2024-09-08T08:40:46.294
String rs = formatter.format(now); // 正向格式化
System.out.println(rs); //2024年09月08日 08:40:46
// 3、格式化时间,其实还有一种方案。
String rs2 = now.format(formatter); // 反向格式化
System.out.println(rs2); //2024年09月08日 08:40:46
// 4、解析时间:解析时间一般使用LocalDateTime提供的解析方法来解析。
String dateStr = "2029年12月12日 12:12:11";
LocalDateTime ldt = LocalDateTime.parse(dateStr, formatter);
System.out.println(ldt); //2029-12-12T12:12:11
}
}
JDK8新增的日期、时间类
Period(一段时期)
可以用于计算两个 LocalDate对象 相差的年数、月数、天数。
package com.liu.d4_jdk8_time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
public class Test7_Period {
public static void main(String[] args) {
//目标:掌握Period的作用:计算两个日期相差的年数、月数、天数
LocalDate start = LocalDate.of(2003, 8, 2);
LocalDate end = LocalDate.of(2029, 12, 12);
//1、创建period对象,封装两个日期对象
Period period = Period.between(start, end);
//2、通过period对象获取两个日期对象相差的信息
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
}
}
Duration(持续时间)
可以用于计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数;支持LocalTime、LocalDateTime、Instant等时间。
package com.liu.d4_jdk8_time;
import java.time.Duration;
import java.time.LocalDateTime;
public class Test8_Duration {
public static void main(String[] args) {
LocalDateTime start = LocalDateTime.of(2025, 11, 11, 11, 11);
LocalDateTime end = LocalDateTime.of(2025, 11, 12, 12, 12);
//1、得到Duration对象
Duration duration = Duration.between(start, end);
//2、获取两个时间对象间隔的信息
System.out.println(duration.toDays()); //间隔多少天
System.out.println(duration.toHours());//间隔多少小时
System.out.println(duration.toMinutes());//间隔多少分
System.out.println(duration.getSeconds());//间隔多少秒
System.out.println(duration.toMillis());//间隔多少好毫秒
System.out.println(duration.toNanos());//间隔多少纳秒
}
}
JDK8新增的日期、时间类
常用API(二):JDK8时间 DateTimeFormatter、Period、Duration
Arrays
用来操作数组的一个工具类。
Arrays类提供的的常见方法
对数组中的数据进行排序
package com.liu.d5_arrays;
import java.util.Arrays;
import java.util.function.IntToDoubleFunction;
public class ArraysTest1 {
public static void main(String[] args) {
//目标:掌握Arrays类的常用方法
// 1、public static String toString(类型[] arr): 返回数组的内容
int[] arr = {10,20,30,40,50,60};
System.out.println(Arrays.toString(arr));
// 2、public static 类型[] copyOfRange(类型[] arr, 起始索引, 结束索引) :拷贝数组(指定范围,包前不包后)
int[] arr2 = Arrays.copyOfRange(arr, 1, 4);
System.out.println(Arrays.toString(arr2));
// 3、public static copyOf(类型[] arr, int newLength):拷贝数组,可以指定新数组的长度。
int[] arr3 = Arrays.copyOf(arr, 10);
System.out.println(Arrays.toString(arr3));
// 4、public static setAll(double[] array, IntToDoubleFunction generator):把数组中的原数据改为新数据又存进去。
double[] prices = {99.8,128,100};
//把所有的数据都大八折,然后又存进去
Arrays.setAll(prices, new IntToDoubleFunction() {
@Override
public double applyAsDouble(int value) {
// value = 0 1 2 3
return prices[value] * 0.8;
}
});
System.out.println(Arrays.toString(prices));
// 5、public static void sort(类型[] arr):对数组进行排序(默认是升序排序)
Arrays.sort(prices);
System.out.println(Arrays.toString(prices));
}
}
如果数组中存储的是对象,如何排序?
自定义排序规则时,需要遵循的官方约定如下:
package com.liu.d5_arrays;
public class Student implements Comparable<Student>{
private String name;
private double height;
private int age;
// 指定比较规则
// this o
@Override
public int compareTo(Student o) {
// 约定1:认为左边对象 大于 右边对象 请您返回正整数
// 约定2:认为左边对象 小于 右边对象 请您返回负整数
// 约定3:认为左边对象 等于 右边对象 请您一定返回0
// 按照年龄升序排序。
// if(this.age > o.age){
// return 1;
// }else if(this.age < o.age){
// return -1;
// }
// return 0;
// return this.age - o.age; // 升序 简化写法
return o.age - this.age; // 降序 简化写法
}
public Student() {
}
public Student(String name, double height, int age) {
this.name = name;
this.height = height;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", height=" + height +
", age=" + age +
'}';
}
}
package com.liu.d5_arrays;
import java.util.Arrays;
import java.util.Comparator;
public class ArraysTest2 {
public static void main(String[] args) {
// 目标:掌握如何对数组中的对象进行排序。
Student[] students = new Student[4];
students[0] = new Student("蜘蛛精", 169.5, 23);
students[1] = new Student("紫霞", 163.8, 26);
students[2] = new Student("紫霞", 163.8, 26);
students[3] = new Student("至尊宝", 167.5, 24);
// 1、public static void sort(类型[] arr):对数组进行排序。
// Arrays.sort(students);
// System.out.println(Arrays.toString(students));
// 2、public static <T> void sort(T[] arr, Comparator<? super T> c)
// 参数一:需要排序的数组
// 参数二:Comparator比较器对象(用来制定对象的比较规则)
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 制定比较规则了:左边对象 o1 右边对象 o2
// 约定1:认为左边对象 大于 右边对象 请您返回正整数
// 约定2:认为左边对象 小于 右边对象 请您返回负整数
// 约定3:认为左边对象 等于 右边对象 请您一定返回0
// if(o1.getHeight() > o2.getHeight()){
// return 1;
// }else if(o1.getHeight() < o2.getHeight()){
// return -1;
// }
// return 0; // 升序
return Double.compare(o1.getHeight(), o2.getHeight()); // 升序 简化代码
// return Double.compare(o2.getHeight(), o1.getHeight()); // 降序 简化代码
}
});
System.out.println(Arrays.toString(students));
}
}
JDK8 新特性:Lambda表达式、Lambda表达式的省略规则
Lambda表达式
- Lambda表达式是JDK 8开始新增的一种语法形式; 作用:用于简化匿名内部类的代码写法。
格式:
package com.liu.d6_lambda;
public class LamdbaTest1 {
public static void main(String[] args) {
//目标:认识Lambda表达式
// Animal a = new Animal() {
// @Override
// public void run() {
// System.out.println("狗跑的快");
// }
// };
// a.run();
//注意:Lambda表达式并不是说能简化全部匿名内部类的方法,只能简化函数式接口的匿名内部类
//错误的代码!
// Animal a = () -> {
// System.out.println("狗跑的快");
// };
// a.run();
// Swimming s = new Swimming() {
// @Override
// public void swim() {
// System.out.println("学生快乐的游泳!");
// }
// };
// s.swim();
Swimming s = () -> {
System.out.println("学生快乐的游泳!");
};
s.swim();
}
interface Swimming {
void swim();
}
abstract class Animal {
public abstract void run();
}
}
注意 :
- Lambda表达式只能简化函数式接口的匿名内部类!!!
什么是函数式接口?
- 有且仅有一个抽象方法的接口。
- 注意:将来我们见到的大部分函数式接口,上面都可能会有一个@FunctionalInterface的注解,有该注解的接口就必定是函数式接口。
Lambda表达式简化setAll方法中匿名内部类
Lambda表达式简化Comparator接口的匿名形式
package com.liu.d6_lambda;
import com.liu.d5_arrays.Student;
import java.util.Arrays;
public class LambdaTest2 {
public static void main(String[] args) {
// 目标:使用Lambda简化函数式接口。
double[] prices = {99.8, 128, 100};
// Arrays.setAll(prices, new IntToDoubleFunction() {
// @Override
// public double applyAsDouble(int value) {
// // value = 0 1 2
// return prices[value] * 0.8;
// }
// });
//简化代码
Arrays.setAll(prices, (int value) -> {
return prices[value] * 0.8;
});
System.out.println(Arrays.toString(prices));
System.out.println("----------------------------------------------------------");
Student[] students = new Student[4];
students[0] = new Student("蜘蛛精", 169.5, 23);
students[1] = new Student("紫霞", 163.8, 26);
students[2] = new Student("紫霞", 163.8, 26);
students[3] = new Student("至尊宝", 167.5, 24);
// Arrays.sort(students, new Comparator<Student>{
// @Override
// public int compare(Student o1, Student o2) {
// return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
// }
// });
//简化代码
Arrays.sort(students, (Student o1, Student o2) -> {
return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
});
System.out.println(Arrays.toString(students));
}
}
Lambda表达式的省略写法(进一步简化Lambda表达式的写法)
- 参数类型可以省略不写。
- 如果只有一个参数,参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写。
package com.liu.d6_lambda;
import com.liu.d5_arrays.Student;
import java.util.Arrays;
public class LambdaTest2 {
public static void main(String[] args) {
// 目标:使用Lambda简化函数式接口。
double[] prices = {99.8, 128, 100};
// Arrays.setAll(prices, new IntToDoubleFunction() {
// @Override
// public double applyAsDouble(int value) {
// // value = 0 1 2
// return prices[value] * 0.8;
// }
// });
//简化代码
// Arrays.setAll(prices, (int value) -> {
// return prices[value] * 0.8;
// });
//Lambda表达式的省略写法(进一步简化Lambda表达式的写法)
// Arrays.setAll(prices, (value) -> {
// return prices[value] * 0.8;
// });
//简化
// Arrays.setAll(prices, value -> {
// return prices[value] * 0.8;
// });
//再简化
//如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写。
Arrays.setAll(prices, value -> prices[value] * 0.8 );
System.out.println(Arrays.toString(prices));
System.out.println("----------------------------------------------------------");
Student[] students = new Student[4];
students[0] = new Student("蜘蛛精", 169.5, 23);
students[1] = new Student("紫霞", 163.8, 26);
students[2] = new Student("紫霞", 163.8, 26);
students[3] = new Student("至尊宝", 167.5, 24);
// Arrays.sort(students, new Comparator<Student>{
// @Override
// public int compare(Student o1, Student o2) {
// return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
// }
// });
//简化代码
// Arrays.sort(students, (Student o1, Student o2) -> {
// return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
// });
//Lambda表达式的省略写法(进一步简化Lambda表达式的写法)
// Arrays.sort(students, ( o1, o2 ) -> {
// return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
// });
//再简化
//如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写。
Arrays.sort(students, ( o1, o2 ) -> Double.compare(o1.getHeight(), o2.getHeight()));
System.out.println(Arrays.toString(students));
}
}
JDK8新特性:方法引用、特定类型方法引用、构造器引用
静态方法的引用
- 类名::静态方法。
使用场景
- 如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用。
package com.liu.d7_method_references;
import com.liu.d5_arrays.Student;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) {
/**
* 目标:了解静态方法引用、实例方法引用,简化Lambda表达式。
*/
Student[] students = new Student[4];
students[0] = new Student("蜘蛛精", 169.5, 23);
students[1] = new Student("紫霞", 163.8, 26);
students[2] = new Student("紫霞", 163.8, 26);
students[3] = new Student("至尊宝", 167.5, 24);
// 原始写法:对数组中的学生对象,按照年龄升序排序
// Arrays.sort(students, new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// return o1.getAge() - o2.getAge(); // 按照年龄升序排序
// }
// });
// 使用Lambda简化后的形式
// Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());
// Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2));
// 静态方法引用
Arrays.sort(students, CompareByData::compareByAge);
System.out.println(Arrays.toString(students));
}
}
package com.liu.d7_method_references;
import com.liu.d5_arrays.Student;
public class CompareByData {
public static int compareByAge(Student o1, Student o2){
return o1.getAge() - o2.getAge(); // 升序排序的规则
}
}
实例方法的引用
- 对象名: : 实例方法。
使用场景
- 如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用。
package com.liu.d7_method_references;
import com.liu.d5_arrays.Student;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) {
/**
* 目标:了解静态方法引用、实例方法引用,简化Lambda表达式。
*/
Student[] students = new Student[4];
students[0] = new Student("蜘蛛精", 169.5, 23);
students[1] = new Student("紫霞", 163.8, 26);
students[2] = new Student("紫霞", 163.8, 26);
students[3] = new Student("至尊宝", 167.5, 24);
// 原始写法:对数组中的学生对象,按照年龄升序排序
// Arrays.sort(students, new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// return o1.getAge() - o2.getAge(); // 按照年龄升序排序
// }
// });
// 使用Lambda简化后的形式
// Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());
// Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2));
// 静态方法引用
// Arrays.sort(students, CompareByData::compareByAge);
// Arrays.sort(students, (o1, o2) -> o2.getAge() - o1.getAge()); // 降序
CompareByData compare = new CompareByData();
// Arrays.sort(students, (o1, o2) -> compare.compareByAgeDesc(o1, o2)); // 降序
// 实例方法引用
Arrays.sort(students, compare::compareByAgeDesc); // 降序
System.out.println(Arrays.toString(students));
}
}
package com.liu.d7_method_references;
import com.liu.d5_arrays.Student;
public class CompareByData {
public static int compareByAge(Student o1, Student o2){
return o1.getAge() - o2.getAge(); // 升序排序的规则
}
public int compareByAgeDesc(Student o1, Student o2) {
return o2.getAge() - o1.getAge(); // 降序排序的规则
}
}
特定类型的方法引用
- 类型: : 方法。
使用场景
- 如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用。
package com.liu.d7_method_references;
import java.util.Arrays;
public class Test2 {
public static void main(String[] args) {
/**
* 目标:掌握特定类型的方法引用。
*/
String[] names = {"boby", "angela", "Andy" ,"dlei", "caocao", "Babo", "jack", "Cici"};
// 进行排序(默认是按照字符串的首字符编号进行升序排序的)
// Arrays.sort(names);
// 要求忽略首字符大小写进行排序。
// Arrays.sort(names, new Comparator<String>(){
// @Override
// public int compare(String o1, String o2) {
// // 制定比较规则。o1 = "Andy" o2 = "angela"
// return o1.compareToIgnoreCase(o2);
// }
// });
//简化代码
// Arrays.sort(names, ( o1, o2) -> o1.compareToIgnoreCase(o2) );
// 特定类型的方法引用!
Arrays.sort(names, String::compareToIgnoreCase);
System.out.println(Arrays.toString(names));
}
}
构造器引用
- 类名: : new。
使用场景
- 如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用。
package com.liu.d7_method_references;
public class Test3 {
public static void main(String[] args) {
/**
* 目标:构造器引用(理解语法)
*/
// 1、创建这个接口的匿名内部类对象。
// CreateCar cc = new CreateCar(){
// @Override
// public Car create(String name, double price) {
// return new Car(name, price);
// }
// };
//简化代码
// CreateCar cc = ( name, price) -> new Car(name, price);
// 构造器引用
CreateCar cc = Car::new;
Car c = cc.create("奔驰", 49.9);
System.out.println(c);
}
}
interface CreateCar{
Car create(String name, double price);
}
package com.liu.d7_method_references;
public class Car {
private String name;
private double price;
public Car() {
}
public Car(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
阶段项目拓展:医院挂号系统
算法:认识、冒泡排序、选择排序及优化
什么是算法?
- 解决某个实际问题的过程和方法!
为什么要学习算法?
- 编程思维
排序算法
对一堆数据进行排序
排序算法
- 冒泡排序
- 选择排序
- ......
学习算法的 技巧
1、先搞清楚算法的流程
2、直接去推敲如何写代码
冒泡排序法
- 每次从数组中找出最大值放在数组的后面去。
实现冒泡排序的关键步骤分析
- 确定总共需要做几轮: 数组的长度-1.
- 每轮比较几次:
- 当前位置大于后一个位置则交换数据
package com.liu.d1_algorithm;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
/**
* 目标:掌握冒泡排序的编写。
*/
// 1、准备一个数组
int[] arr = {5, 2, 3, 1};
// 2、定义一个循环控制排几轮
for (int i = 0; i < arr.length - 1; i++) {
// i = 0 1 2 【5, 2, 3, 1】 次数
// i = 0 第一轮 0 1 2 3
// i = 1 第二轮 0 1 2
// i = 2 第三轮 0 1
// 3、定义一个循环控制每轮比较几次。
for (int j = 0; j < arr.length - i - 1; j++) {
// 判断当前位置的元素值,是否大于后一个位置处的元素值,如果大则交换。
if(arr[j] > arr[j+1]){
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
选择排序
- 每轮选择当前位置,开始找出后面的较小值与该位置交换
选择排序的关键
- 确定总共需要选择几轮: 数组的长度-1.
- 控制每轮从以前位置为基准,与后面元素选择几次。
方法一
package com.liu.d1_algorithm;
import java.util.Arrays;
public class Test2 {
public static void main(String[] args) {
/**
* 目标:掌握选择排序。
*/
// 1、准备好一个数组
int[] arr = {5, 1, 3, 2};
// 0 1 2 3
// 2、控制选择几轮
for (int i = 0; i < arr.length - 1; i++) {
// i = 0 第一轮 j = 1 2 3
// i = 1 第二轮 j = 2 3
// i = 2 第三轮 j = 3
// 3、控制每轮选择几次。
for (int j = i + 1; j < arr.length; j++) {
// 判断当前位置是否大于后面位置处的元素值,若大于则交换。
if(arr[i] > arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
方法二
package com.liu.d1_algorithm;
import java.util.Arrays;
public class Test2 {
public static void main(String[] args) {
/**
* 目标:掌握选择排序。
*/
// 1、准备好一个数组
int[] arr = {5, 1, 3, 2};
// 0 1 2 3
// 2、控制选择几轮
for (int i = 0; i < arr.length - 1; i++) {
// i = 0 第一轮 j = 1 2 3
// i = 1 第二轮 j = 2 3
// i = 2 第三轮 j = 3
int minIndex = i;
// 3、控制每轮选择几次。
for (int j = i + 1; j < arr.length; j++) {
// 判断当前位置是否大于后面位置处的元素值,若大于则交换。
if(arr[minIndex] > arr[j]){
minIndex = j;
}
}
//决定是否交换
if (i != minIndex){
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
System.out.println(Arrays.toString(arr));
}
}
算法:二分查找算法
基本查找/顺序查找
注意:在数据量特别大的时候,基本查找这种从前往后挨个找的形式,性能是很差的!
二分查找(折半查找)
- 前提条件:数组中的数据必须是有序的
- 核心思想:每次排除一半的数据,查询数据的性能明显提高极多!
1、
2、
结论:
二分查找正常的折半条件应该是开始位置left <= 结束位置right
package com.liu.d1_algorithm;
import java.util.Arrays;
public class Test3 {
public static void main(String[] args) {
/**
* 目标:掌握二分查找算法。
*/
// 1、准备好一个数组。
int[] arr = {7, 23, 79, 81, 103, 127, 131, 147};
System.out.println(binarySearch(arr, 150));
System.out.println(Arrays.binarySearch(arr, 81));
}
public static int binarySearch(int[] arr, int data){ //返回类型为int 用于返回索引
// 1、定义两个变量,一个站在左边位置,一个站在右边位置
int left = 0;
int right = arr.length - 1;
// 2、定义一个循环控制折半。
while (left <= right){
// 3、每次折半,都算出中间位置处的索引
int middle = (left + right) / 2;
// 4、判断当前要找的元素值,与中间位置处的元素值的大小情况。
if(data < arr[middle]){
// 往左边找,截止位置(右边位置) = 中间位置 - 1
right = middle - 1;
}else if(data > arr[middle]){
// 往右边找,起始位置(左边位置) = 中间位置 + 1
left = middle + 1;
}else {
// 中间位置处的元素值,正好等于我们要找的元素值
return middle;
}
}
return -1; // -1特殊结果,就代表没有找到数据!数组中不存在该数据!
}
}
二分查找的实现步骤是什么样的?
- 定义变量记录左边和右边位置。
- 使用while循环控制二分查询(条件是左边位置<=右边位置)
- 循环内部获取中间元素索引
- 判断当前要找的元素如果大于中间元素,左边位置=中间索引+1
- 判断当前要找的元素如果小于中间元素,右边位置=中间索引-1
- 判断当前要找的元素如果等于中间元素,返回当前中间元素索引。
正则表达式:概述、初体验、匹配规则
正则表达式
- 就是由一些特定的字符组成,代表的是一个规则。
作用一:用来校验数据格式是否合法
- 更简单!更便捷!
package com.liu.d2_regex;
public class RefexTest1 {
public static void main(String[] args) {
/**
* 目标:体验一下使用正则表达式来校验数据格式的合法性。
* 需求:校验QQ号码是否正确,要求全部是数字,长度是(6-20)之间,不能以0开头。
*/
System.out.println(checkQQ(null));
System.out.println(checkQQ("251425876"));
System.out.println(checkQQ("2514a8d76"));
System.out.println("--------------------------------------------------");
System.out.println(checkQQ1(null));
System.out.println(checkQQ1("251425876"));
System.out.println(checkQQ1("2514a8d76"));
}
//
public static boolean checkQQ1(String qq){
return qq != null && qq.matches("[1-9]\\d{5,19}");
}
public static boolean checkQQ(String qq){
// 1、判断qq号码是否为null
if(qq == null || qq.startsWith("0") || qq.length() < 6 || qq.length() > 20){
return false;
}
// 2、qq至少是不是null,不是以0开头的,满足6-20之间的长度。
// 判断qq号码中是否都是数字。
// qq = 2514ghd234
for (int i = 0; i < qq.length(); i++) {
// 根据索引提取当前位置处的字符。
char ch = qq.charAt(i);
// 判断ch记住的字符,如果不是数字,qq号码不合法。
if(ch < '0' || ch > '9'){
return false;
}
}
// 3、说明qq号码肯定是合法
return true;
}
}
作用二:在一段文中查找满足要求的内容
String提供了一个匹配正则表达式的方法
正则表达式的书写规则
package com.liu.d2_regex;
public class RefexTest2 {
public static void main(String[] args) {
/**
* 目标:掌握正则表达式的书写规则
*/
// 1、字符类(只能匹配单个字符)
System.out.println("a".matches("[abc]")); // [abc]只能匹配a、b、c
System.out.println("e".matches("[abcd]")); // false
System.out.println("d".matches("[^abc]")); // [^abc] 不能是abc
System.out.println("a".matches("[^abc]")); // false
System.out.println("b".matches("[a-zA-Z]")); // [a-zA-Z] 只能是a-z A-Z的字符
System.out.println("2".matches("[a-zA-Z]")); // false
System.out.println("k".matches("[a-z&&[^bc]]")); // : a到z,除了b和c
System.out.println("b".matches("[a-z&&[^bc]]")); // false
System.out.println("ab".matches("[a-zA-Z0-9]")); // false 注意:以上带 [内容] 的规则都只能用于匹配单个字符
// 2、预定义字符(只能匹配单个字符) . \d \D \s \S \w \W
System.out.println("徐".matches(".")); // .可以匹配任意字符
System.out.println("徐徐".matches(".")); // false
// \转义 在Java中,\是有特殊用途的,例如特殊字符 \n、\t
System.out.println("\"");
// \n \t
System.out.println("3".matches("\\d")); // true \d: 0-9
System.out.println("a".matches("\\d")); //false
System.out.println(" ".matches("\\s")); // \s: 代表一个空白字符
// System.out.println("a".matches("\s")); // false java8不支持此版本
System.out.println("a".matches("\\S")); // \S: 代表一个非空白字符
System.out.println(" ".matches("\\S")); // false
System.out.println("a".matches("\\w")); // \w: [a-zA-Z_0-9]
System.out.println("_".matches("\\w")); // true
System.out.println("徐".matches("\\w")); // false
System.out.println("徐".matches("\\W")); // [^\w]不能是a-zA-Z_0-9
System.out.println("a".matches("\\W")); // false
System.out.println("23232".matches("\\d")); // false 注意:以上预定义字符都只能匹配单个字符。
// 3、数量词: ? * + {n} {n, } {n, m}
System.out.println("a".matches("\\w?")); // ? 代表0次或1次
System.out.println("".matches("\\w?")); // true
System.out.println("abc".matches("\\w?")); // false
System.out.println("abc12".matches("\\w*")); // * 代表0次或多次
System.out.println("".matches("\\w*")); // true
System.out.println("abc12张".matches("\\w*")); // false
System.out.println("abc12".matches("\\w+")); // + 代表1次或多次
System.out.println("".matches("\\w+")); // false
System.out.println("abc12张".matches("\\w+")); // false 不能带 张
System.out.println("a3c".matches("\\w{3}")); // {3} 代表要正好是n次
System.out.println("abcd".matches("\\w{3}")); // false
System.out.println("abcd".matches("\\w{3,}")); // {3,} 代表是>=3次
System.out.println("ab".matches("\\w{3,}")); // false
System.out.println("abcde徐".matches("\\w{3,}")); // false
System.out.println("abc232d".matches("\\w{3,9}")); // {3, 9} 代表是 大于等于3次,小于等于9次
// 4、其他几个常用的符号:(?i)忽略大小写 、 或:| 、 分组:()
System.out.println("abc".matches("(?i)abc")); // true
System.out.println("ABC".matches("(?i)abc")); // true
System.out.println("aBc".matches("a((?i)b)c")); // true
System.out.println("ABc".matches("a((?i)b)c")); // false 只对b忽略大小写
// 需求1:要求要么是3个小写字母,要么是3个数字。
System.out.println("abc".matches("[a-z]{3}|\\d{3}")); // true
System.out.println("ABC".matches("[a-z]{3}|\\d{3}")); // false
System.out.println("123".matches("[a-z]{3}|\\d{3}")); // true
System.out.println("A12".matches("[a-z]{3}|\\d{3}")); // false
// 需求2:必须是”我爱“开头,中间可以是至少一个”编程“,最后至少是1个”666“
System.out.println("我爱编程编程666666".matches("我爱(编程)+(666)+")); //三个6出现
System.out.println("我爱编程编程66666".matches("我爱(编程)+(666)+"));
}
}
小结
1、String类提供了哪个方法与正则表达式进行匹配?
2、正则表达式的书写规则有哪些?
正则表达式:应用案例、爬取信息、搜索替换
案例:请使用正则表达式完成如下需求
需求:校验用户输入的电话、邮箱、时间是否合适
package com.liu.d2_regex;
import java.util.Scanner;
public class RefexTest3 {
public static void main(String[] args) {
/**
* 目标:校验用户输入的电话、邮箱、时间是否合法。
*/
// checkPhone();
checkEmail();
}
public static void checkPhone(){
while (true) {
System.out.println("请您输入您的电话号码(手机|座机): ");
Scanner sc = new Scanner(System.in);
String phone = sc.nextLine();
// 18676769999 010-3424242424 0104644535
if(phone.matches("(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})")){ //1开头 包含3—9
System.out.println("您输入的号码格式正确~~~");
break;
}else {
System.out.println("您输入的号码格式不正确~~~");
}
}
}
public static void checkEmail(){
while (true) {
System.out.println("请您输入您的邮箱: ");
Scanner sc = new Scanner(System.in);
String email = sc.nextLine();
/**
* 1651552720@qq.com
* liujing@itcast.com.cn
*/
if(email.matches("\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2}")){
System.out.println("您输入的邮箱格式正确~~~");
break;
}else {
System.out.println("您输入的邮箱格式不正确~~~");
}
}
}
}
案例:使用正则表达式查找一段文本中的内容(正则表达式作用二)
需求:请下面文本中的电话,邮箱,座机号码,热线都爬取出来
需求2:只需要把每个邮箱中的用户名爬取出来。
需求3:某系统的日志文件记录了当天进入系统的全部用户信息,需要把这些用户的名字爬取出来另作他用
案例一:
package com.liu.d2_regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RefexTest4 {
public static void main(String[] args) {
/**
* 目标:掌握使用正则表达式查找内容。
*/
method1();
}
// 需求1:从以下内容中爬取出,手机,邮箱,座机、400电话等信息。
public static void method1(){
String data = " 来黑马程序员学习Java,\n" +
" 电话:1866668888,18699997777\n" +
" 或者联系邮箱:boniu@itcast.cn,\n" +
" 座机电话:01036517895,010-98951256\n" +
" 邮箱:bozai@itcast.cn,\n" +
" 邮箱:dlei0009@163.com,\n" +
" 热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";
// 1、定义爬取规则
String regex = "(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})|(\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2})"
+ "|(400-?\\d{3,7}-?\\d{3,7})";
// 2、把正则表达式封装成一个Pattern对象
Pattern pattern = Pattern.compile(regex);
// 3、通过pattern对象去获取查找内容的匹配器对象。
Matcher matcher = pattern.matcher(data);
// 4、定义一个循环开始爬取信息
while (matcher.find()){
String rs = matcher.group(); // 获取到了找到的内容了。
System.out.println(rs);
}
}
}
案例二:
package com.liu.d2_regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RefexTest5 {
public static void main(String[] args) {
method2();
}
// 需求2:把全部邮箱的账户名找出来。
public static void method1(){
String data = " 来黑马程序员学习Java,\n" +
" 电话:1866668888,18699997777\n" +
" 或者联系邮箱:boniu@itcast.cn,\n" +
" 座机电话:01036517895,010-98951256\n" +
" 邮箱:bozai@itcast.cn,\n" +
" 邮箱:dlei0009@163.com,\n" +
" 热线电话:400-618-9090 ,heima, 400-618-4000,4006184000,4006189090";
// 1、定义爬取规则
String regex = "(\\w{2,})@(\\w{2,20})(\\.\\w{2,10}){1,2}";
// 2、把正则表达式封装成一个Pattern对象
Pattern pattern = Pattern.compile(regex);
// 3、通过pattern对象去获取查找内容的匹配器对象。
Matcher matcher = pattern.matcher(data);
// 4、定义一个循环开始爬取信息
while (matcher.find()){
System.out.println(matcher.group());
System.out.println(matcher.group(1)); // 指定获取正则表达式匹配后的第一组内容
System.out.println(matcher.group(2)); // 指定获取正则表达式匹配后的第二组内容
}
}
// 需求3:某系统的日志文件记录了当天进入系统的全部用户信息,需要把这些用户的名字爬取出来另作他用。
public static void method2(){
String data = "欢迎张全蛋光临本系统!他删库并跑路,欢迎李二狗子光临本系统!" +
"欢迎马六子光临本系统!它浏览了很多好看的照片!欢迎夏洛光临本系统!他在六点钟购买了一台拖拉机!";
// 1、定义爬取规则
// String regex = "欢迎(.+)光临"; // 贪婪匹配
String regex = "欢迎(.+?)光临"; // +? 非贪婪匹配
// 2、把正则表达式封装成一个Pattern对象
Pattern pattern = Pattern.compile(regex);
// 3、通过pattern对象去获取查找内容的匹配器对象。
Matcher matcher = pattern.matcher(data);
// 4、定义一个循环开始爬取信息
while (matcher.find()){
System.out.println(matcher.group());
System.out.println(matcher.group(1));
}
}
}
正则表达式用于搜索替换、分割内容,需要结合String提供的如下方法完成:
package com.liu.d2_regex;
import java.util.Arrays;
public class RefexTest6 {
public static void main(String[] args) {
/**
* 目标:掌握使用正则表达式做搜索替换,内容分割。
*/
// 1、public String replaceAll(String regex , String newStr):按照正则表达式匹配的内容进行替换
// 需求1:请把 古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴,中间的非中文字符替换成 “-”
String s1 = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
System.out.println(s1.replaceAll("\\w+", "-"));
// 需求2(拓展):某语音系统,收到一个口吃的人说的“我我我喜欢编编编编编编编编编编编编程程程!”,需要优化成“我喜欢编程!”。
String s2 = "我我我喜欢编编编编编编编编编编编编程程程";
/**
* (.)一组:.匹配任意字符的。
* \\1 :为这个组声明一个组号:1号
* +:声明必须是重复的字
* $1可以去取到第1组代表的那个重复的字
*/
System.out.println(s2.replaceAll("(.)\\1+", "$1"));
// 2、public String[] split(String regex):按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
// 需求1:请把 古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴,中的人名获取出来。
String s3 = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
String[] names = s3.split("\\w+");
System.out.println(Arrays.toString(names));
}
}
异常:认识异常、自定义异常
什么异常?
异常就是代表程序出现的问题
1、
2、
3、
读取一个不存在的文件
4、
读取网络数据
......................
异常的体系444
Error:
- 代表的系统级别错误(属于严重问题),也就是说系统一旦出现问题,sun公司会把这些问题封装成Error对象给出来, 说白了,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它。
Exception:
- 叫异常,它代表的才是我们程序可能出现的问题,所以,我们程序员通常会用Exception以及它的孩子来封装程序出现的问题。
运行时异常:
- RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常)
编译时异常:
编译阶段就会出现错误提醒的。(如:日期解析异常)
package com.liu.d3_exception;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionTest1 {
//解决编译时异常方法1
public static void main(String[] args) throws ParseException{
/**
* 目标:认识异常。
*/
// 1、运行时异常
// 运行时异常 在写代码时不干扰我们 执行会错误提醒
// Integer.valueOf("abc"); //报错
//1、运行时异常
// int[] arr = {11, 22, 33};
// System.out.println(arr[5]);
//解决编译时异常方法1 CTRL+alt+T键 加入try
// try {
// 2、编译时异常
//写代码的时候就会报错,强制提醒错误
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24");
System.out.println(d);
// } catch (ParseException e) {
// e.printStackTrace();
// }
}
}
抛出异常(throws)
- 在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理。
捕获异常(try…catch)
- 直接捕获程序出现的异常。
自定义异常
- Java无法为这个世界上全部的问题都提供异常类来代表, 如果企业自己的某种问题,想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了。
自定义异常
- Java无法为这个世界上全部的问题都提供异常类来代表, 如果企业自己的某种问题,想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了。
自定义异常的种类
package com.liu.d3_exception;
public class ExceptionTest2 {
public static void main(String[] args) {
/**
* 目标:掌握自定义异常,以及异常的作用。
*/
// 需求:保存一个合法的年龄
//自定义运行时异常
// try {
// saveAge(223);
// System.out.println("底层执行成功的!");
// } catch (Exception e) {
// e.printStackTrace();
// System.out.println("底层出现了bug!");
// }
//自定义编译时异常
try {
saveAge2(225);
System.out.println("saveAge2底层执行是成功的!");
} catch (AgeIllegalException e) {
e.printStackTrace();
System.out.println("saveAge2底层执行是出现bug的!");
}
}
//自定义编译时异常
public static void saveAge2(int age) throws AgeIllegalException{
if(age > 0 && age < 150){
System.out.println("年龄被成功保存: " + age);
}else {
// 用一个异常对象封装这个问题
// throw 抛出去这个异常对象
// throws 用在方法上,抛出方法内部的异常
throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age);
}
}
//自定义运行时异常
public static void saveAge(int age){
if(age > 0 && age < 150){
System.out.println("年龄被成功保存: " + age);
}else {
// 用一个异常对象封装这个问题
// throw 抛出去这个异常对象
throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age);
}
}
}
package com.liu.d3_exception;
// 1、必须让这个类继承自RuntimeException,才能成为一个运行时异常类。
public class AgeIllegalRuntimeException extends RuntimeException{
//右键 找到Generate菜单
public AgeIllegalRuntimeException() {
}
public AgeIllegalRuntimeException(String message) {
super(message);
}
}
package com.liu.d3_exception;
// 1、必须让这个类继承自Exception,才能成为一个编译时异常类。
public class AgeIllegalException extends Exception{
public AgeIllegalException() {
}
public AgeIllegalException(String message) {
super(message);
}
}
异常有什么作用?
1、异常是用来查询系统Bug的关键参考信息!
2、异常可以作为方法内部的一种特殊返回值,以便通知上层调用者底层的执行情况!
开发中对于异常的常见处理方式
1、捕获异常,记录异常并响应合适的信息给用户
2、捕获异常,尝试重新修复
抛出异常(throws)
- 在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理。
package com.liu.d3_exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 目标:异常的处理
*/
public class ExceptionTest3 {
public static void main(String[] args) {
try {
test1();
} catch (FileNotFoundException e) {
System.out.println("您要找的文件不存在!!");
e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
} catch (ParseException e) {
System.out.println("您要解析的时间有问题了!");
e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
}
}
public static void test1() throws FileNotFoundException, ParseException, ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24:11");
System.out.println(d);
test2();
}
public static void test2() throws FileNotFoundException {
// 读取文件的。
InputStream is = new FileInputStream("D:/meinv.png");
}
}
简化代码
package com.itheima.d3_exception;
import java.io.FileInputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 目标:异常的处理
*/
public class ExceptionTest3_2 {
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("您当前操作有问题");
e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
}
}
public static void test1() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24:11");
System.out.println(d);
test2();
}
public static void test2() throws Exception {
// 读取文件的。
InputStream is = new FileInputStream("D:/meinv.png");
}
}
捕获异常(try…catch)
- 直接捕获程序出现的异常。
package com.liu.d3_exception;
import java.util.Scanner;
/**
* 目标:掌握异常的处理方式:捕获异常,尝试修复。
*/
public class ExceptionTest4 {
public static void main(String[] args) {
// 需求:调用一个方法,让用户输入一个合适的价格返回为止。
// 尝试修复
while (true) {
try {
System.out.println(getMoney());
break;
} catch (Exception e) {
System.out.println("请您输入合法的数字!!");
}
}
}
public static double getMoney(){
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请您输入合适的价格:");
double money = sc.nextDouble();
if(money >= 0){
return money;
}else {
System.out.println("您输入的价格是不合适的!");
}
}
}
}
异常处理的总结?
在开发中异常的常见处理方式是:底层的异常抛出去给最外层,最外层集中捕获处理。
集合框架(一):概述、Collection集合的常用方法
集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用。
集合体系结构
Collection
Collection代表单列集合,每个元素(数据)只包含一个值。
Map
Map代表双列合,每个元素包含两个值(键值对)。
Collecton集合体系
Collection集合特点
- List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinekdList :有序、可重复、有索引。
- Set系列集合:添加的元素是无序、不重复、无索引。
- HashSet: 无序、不重复、无索引;
- LinkedHashSet: 有序、不重复、无索引。
- TreeSet:按照大小默认升序排序、不重复、无索引。
package d1_collection;
import java.util.ArrayList;
import java.util.HashSet;
/**
* 目标:认识Collection体系的特点。
*/
public class CollectionTest1 {
public static void main(String[] args) {
// 简单确认一下Collection集合的特点。
ArrayList<String> list = new ArrayList <>(); // 有序 可重复 有索引
list.add("java1");
list.add("java2");
list.add("java1");
list.add("java2");
System.out.println(list);
HashSet<String> set = new HashSet <>(); // 无序,不重复,无索引
set.add("java1");
set.add("java2");
set.add("java1");
set.add("java2");
set.add("java3");
System.out.println(set);
}
}
为啥要先学Collection的常用方法?
- Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。
为啥要先学Collection的常用方法?
- Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。
Collection的常见方法如下:
package d1_collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
目标:掌握Collection集合的常用API.
Collection是集合的祖宗类,它的功能是全部集合都可以继承使用的,所以要学习它。
Collection API如下:
- public boolean add(E e): 把给定的对象添加到当前集合中 。
- public void clear() :清空集合中所有的元素。
- public boolean remove(E e): 把给定的对象在当前集合中删除。
- public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
- public boolean isEmpty(): 判断当前集合是否为空。
- public int size(): 返回集合中元素的个数。
- public Object[] toArray(): 把集合中的元素,存储到数组中。
*/
public class CollectionTest2API {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>(); // 多态写法
// 1.public boolean add(E e):添加元素, 添加成功返回true。
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java2");
c.add("java3");
System.out.println(c);
// 2.public void clear():清空集合的元素。
//c.clear();
//System.out.println(c);
// 3.public boolean isEmpty():判断集合是否为空 是空返回true,反之。
System.out.println(c.isEmpty()); // false
// 4.public int size():获取集合的大小。
System.out.println(c.size());
// 5.public boolean contains(Object obj):判断集合中是否包含某个元素。
System.out.println(c.contains("java1")); // true
System.out.println(c.contains("Java1")); // false
// 6.public boolean remove(E e):删除某个元素:如果有多个重复元素默认删除前面的第一个!
System.out.println(c.remove("java1"));
System.out.println(c);
// 7.public Object[] toArray():把集合转换成数组
Object[] arr = c.toArray();
System.out.println(Arrays.toString(arr));
String[] arr2 = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(arr2));
System.out.println("--------------------------------------------");
// 把一个集合的全部数据倒入到另一个集合中去。
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection<String> c2 = new ArrayList<>();
c2.add("java3");
c2.add("java4");
c1.addAll(c2); // 就是把c2集合的全部数据倒入到c1集合中去。
System.out.println(c1);
System.out.println(c2);
}
}
集合框架(一):Collection集合的遍历方式-迭代器、增强for循环、Lambda、案例
迭代器概述
- 迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。
Collection集合获取迭代器的方法
Iterator迭代器中的常用方法
package d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
目标:Collection集合的遍历方式一:使迭代器Iterator遍历
*/
public class CollectionDemo01 {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
// c.add("灭绝");
System.out.println(c);
// c = [赵敏, 小昭, 素素]
// it
// 使用迭代器遍历集合
// 1、从集合对象中获取迭代器对象。
Iterator<String> it = c.iterator();
// System.out.println(it.next()); //赵敏
// System.out.println(it.next()); //小昭
// System.out.println(it.next()); //素素
// System.out.println(it.next()); //灭绝
// System.out.println(it.next()); // 出现异常的
// 2、我们应该使用循环结合迭代器遍历集合。
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
}
迭代器执行流程
增强for循环
- 增强for可以用来遍历集合或者数组。
- 增强for遍历集合,本质就是迭代器遍历集合的简化写法。
package d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
/**
目标:Collection集合的遍历方式二:增强for
*/
public class CollectionDemo02 {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c);
// c = [赵敏, 小昭, 素素, 灭绝]
// ele
// 使用增强for遍历集合或者数组。
for (String ele : c) {
System.out.println(ele);
}
String[] names = {"迪丽热巴", "古力娜扎", "稀奇哈哈"};
for (String name : names) {
System.out.println(name);
}
}
}
增强for修改变量值会出现什么问题
修改增强for中的变量值不会影响到集合中的元素。
Lambda表达式遍历集合
- 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合。
需要使用Collection的如下方法来完成
package d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
/**
目标:Collection集合的遍历方式三:JDK8开始新增的Lambda表达式。
*/
public class CollectionDemo03 {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("殷素素");
c.add("周芷若");
System.out.println(c);
// [赵敏, 小昭, 殷素素, 周芷若]
// s
// default void forEach(Consumer<? super T> action): 结合Lambda表达式遍历集合:
// c.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
//
//代码简化
// c.forEach((String s) -> {
// System.out.println(s);
// });
//
//代码简化
// c.forEach(s -> {
// System.out.println(s);
// });
//
//代码简化
// c.forEach(s -> System.out.println(s) );
c.forEach(System.out::println );
}
}
案例:遍历集合中的自定义对象
需求
- 展示多部电影信息。
目的:
分析:
1、每部电影都是一个对象,多部电影要使用集合装起来。
2、遍历集合中的3个电影对象,输出每部电影的详情信息。
package d2_collection_traverse;
public class Movie {
private String name;
private double score;
private String actor;
public Movie() {
}
public Movie(String name, double score, String actor) {
this.name = name;
this.score = score;
this.actor = actor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
@Override
public String toString() {
return "Movie{" +
"name='" + name + '\'' +
", score=" + score +
", actor='" + actor + '\'' +
'}';
}
}
package d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
/**
* 目标:完成电影信息的展示。
* new Movie("《肖生克的救赎》" , 9.7 , "罗宾斯")
* new Movie("《霸王别姬》" , 9.6 , "张国荣、张丰毅")
* new Movie("《阿甘正传》" , 9.5 , "汤姆.汉克斯")
*/
public class CollectionTest04 {
public static void main(String[] args) {
// 1、创建一个集合容器负责存储多部电影对象。
Collection<Movie> movies = new ArrayList<>();
movies.add( new Movie("《肖生克的救赎》" , 9.7 , "罗宾斯"));
movies.add( new Movie("《霸王别姬》" , 9.6 , "张国荣、张丰毅"));
movies.add( new Movie("《阿甘正传》" , 9.5 , "汤姆.汉克斯"));
System.out.println(movies);
System.out.println("-------------------------------------------------");
for (Movie movie : movies) {
System.out.println("电影名:" + movie.getName());
System.out.println("评分:" + movie.getScore());
System.out.println("主演:" + movie.getActor());
System.out.println("---------------------------------------------");
}
}
}
集合存储对象原理
集合框架(一):List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理
集合中存储的是元素的什么信息?
- 集合中存储的是元素对象的地址。
Collection集合体系
List系列集合特点: 有序,可重复,有索引
- ArrayList:有序,可重复,有索引。
- LinkedList:有序,可重复,有索引。
底层实现不同!适合的场景不同
List集合的特有方法
- List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了。
package d3_collection_list;
import java.util.ArrayList;
import java.util.List;
/**
目标:掌握List系列集合的特点,以及其提供的特有方法。
*/
public class ListTest1 {
public static void main(String[] args) {
// 1.创建一个ArrayList集合对象(有序、可重复、有索引)
List<String> list = new ArrayList <>(); // 一行经典代码
list.add("蜘蛛精");
list.add("至尊宝");
list.add("至尊宝");
list.add("牛夫人");
System.out.println(list); // [蜘蛛精, 至尊宝, 至尊宝, 牛夫人]
// 2.public void add(int index, E element): 在某个索引位置插入元素。
list.add(2, "紫霞仙子");
System.out.println(list); //[蜘蛛精, 至尊宝, 紫霞仙子, 至尊宝, 牛夫人]
// 3.public E remove(int index): 根据索引删除元素,返回被删除元素
System.out.println(list.remove(2)); //紫霞仙子
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛夫人]
// 4.public E get(int index): 返回集合中指定位置的元素。
System.out.println(list.get(3)); //牛夫人
// 5.public E set(int index, E element): 修改索引位置处的元素,修改成功后,会返回原来的数据
System.out.println(list.set(3, "牛魔王")); //牛夫人
System.out.println(list); //[蜘蛛精, 至尊宝, 至尊宝, 牛魔王]
}
}
List集合支持的遍历方式
1、for循环(因为List集合有索引)
2、迭代器
3、增强for循环
4、Lambda表达式
package d3_collection_list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
拓展:List系列集合的遍历方式.
List遍历方式:
(1)for循环。(独有的,因为List有索引)。
(2)迭代器。
(3)foreach。
(4)JDK 1.8新技术。
*/
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("糖宝宝");
list.add("蜘蛛精");
list.add("至尊宝");
//(1)for循环
for (int i = 0; i< list.size(); i++) {
// i = 0 1 2
String s = list.get(i);
System.out.println(s);
}
//(2)迭代器。
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
//(3)增强for循环(foreach遍历)
for (String s : list) {
System.out.println(s);
}
//(4)JDK 1.8开始之后的Lambda表达式
list.forEach(s -> {
System.out.println(s);
});
}
}
Collection集合体系
List系列集合特点: 有序,可重复,有索引
- ArrayList:有序,可重复,有索引。
- LinkedList:有序,可重复,有索引。
二者( ArrayList 、LinkedList) 区别:
底层采用的数据结构不同,应用场景不同
ArrayList集合的底层原理
- 基于数组实现的
数组的特点
- 查询快、增删慢
- 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定义,查询任意数据耗时相同
- 删除效率低:可能需要把后面很多的数据进行前移。
- 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。
ArrayList集合的底层原理实现
1、利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
2、添加第一个元素时,底层会创建一个新的长度为10的数组
3、存满时,会扩容1.5倍
4、如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
2、ArrayList集合适合的应用场景
1、ArrayList适合:根据索引查询数据,比如根据随机索引取数据(高效)!或者数据量不是很大时!
2、ArrayList不适合:数据量大:数据量大的同时,又要频繁的进行增删操作!
集合框架(一):Set集合的特点、底层原理、哈希表、去重复原理
LinkedList集合的底层原理
- 基于双链表实现的。
什么是链表?
- 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
单向链表
添加
链表有啥特点?
- 链表的特点1:查询慢,无论查询哪个数据都要从头开始找。
- 链表的特点2:链表增删相对快
添加
删除
LinkedList集合的底层原理
- 基于双链表实现的。
双向链表特点:
- 相对于数组来说,查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。
LinkedList集合的底层原理
- 基于双链表实现的。
- 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。
LinkedList新增了:很多首尾操作的特有方法。
LinkedList的应用场景之一:可以用来设计队列
只是在首尾增删元素,用 LinkedList 来实现很合适!
kage d3_collection_list;
import java.util.LinkedList;
/**
* 目标:掌握LinkedList集合的使用。
*/
public class ListTest3 {
public static void main(String[] args) {
// 1、创建一个队列。
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue); //[第1号人, 第2号人, 第3号人, 第4号人]
// 出队
System.out.println(queue.removeFirst()); //第1号人
System.out.println(queue.removeFirst()); //第2号人
System.out.println(queue.removeFirst()); //第3号人
System.out.println(queue); //[第4号人]
}
}
LinkedList的应用场景之一:可以用来设计栈
数据进入栈模型的过程称为:
压 / 进栈(push)
数据离开栈模型的过程称为:
弹 / 出栈(pop)
d3_collection_list;
import java.util.LinkedList;
/**
* 目标:掌握LinkedList集合的使用。
*/
public class ListTest3 {
public static void main(String[] args) {
// 2、创建一个栈对象。
LinkedList<String> stack = new LinkedList<>();
// 压栈(push)
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack); //[第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
// 出栈(pop)
System.out.println(stack.pop()); //第4颗子弹
System.out.println(stack.pop()); //第3颗子弹
System.out.println(stack); //[第2颗子弹, 第1颗子弹]
}
}
集合(一):Set集合的特点、底层原理、哈希表、去重复原理
Collection集合体系
Set系列集合特点:
- 无序:添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引;
- HashSet : 无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
注意:
Set要用到的常用方法,基本上就是Collection提供的!!
自己几乎没有额外新增一些常用功能!
注意:在正式了解HashSet集合的底层原理前,我们需要先搞清楚一个前置知识:哈希值!
哈希值
- 就是一个int类型的数值,Java中每个对象都有一个哈希值。
- Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
对象哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
- 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
package d4_collection_set;
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
// this o
@Override
public int compareTo(Student o) {
// 如果认为左边对象大于右边对象返回正整数
// 如果认为左边对象小于右边对象返回负整数
// 如果认为左边对象等于右边对象返回0
// 需求:按照年龄升序排序、
return this.age - o.age;
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
// 只要两个对象内容一样就返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
// 只要两个对象内容一样,返回的哈希值就是一样的。
@Override
public int hashCode() {
// 姓名 年龄 身高计算哈希值的
return Objects.hash(name, age, height);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package d4_collection_set;
/**
* 目标:了解一下哈希值。
* Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
* public int hashCode(): 返回对象的哈希值。
* 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
* 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
*/
public class SetTest2 {
public static void main(String[] args) {
Student s1 = new Student("蜘蛛精",25, 169.5);
Student s2 = new Student("紫霞",22, 166.5);
System.out.println(s1.hashCode()); //-281121023
System.out.println(s1.hashCode()); //-281121023
System.out.println(s2.hashCode()); //2072149404
//一般情况下不同,但也有可能会相同(哈希碰撞)。
String str1 = new String("abc");
String str2 = new String("acD");
System.out.println(str1.hashCode()); //96354
System.out.println(str2.hashCode()); //96354
}
}
HashSet集合的底层原理
- 基于哈希表实现。
- 哈希表是一种增删改查数据,性能都较好的数据结构。
哈希表
- JDK8之前,哈希表 = 数组+链表
- JDK8开始,哈希表 = 数组+链表+红黑树
JDK8之前HashSet集合的底层原理,基于哈希表:数组+链表
1、创建一个默认长度16的数组,默认加载因子为0.75,数组名table
2、使用元素的哈希值对数组的长度求余计算出应存入的位置
3、判断当前位置是否为null,如果是null直接存入
4、如果不为null,表示有元素,则调用equals方法比较
相等,则不存;不相等,则存入数组
- JDK 8之前,新元素存入数组,占老元素位置,老元素挂下面
1、
2、
3、
哈希表是一种增删改查数据性能都较好的结构。
如果数组沾满了,会出现什么问题?怎么办?
- 链表会过长,导致查询性能降低
- 扩容
JDK8开始,当链表长度超过8,且数组长度 >= 64时,自动将链表转换成红黑树
- JDK 8开始之后,新元素直接挂在老元素下面
了解一下数据结构(数) 二叉树
度:
每一个节点的子节点数量 (二叉树中,任意节点的度<=2)
树高:
树的总层数
根节点:
最顶层的节点
左子节点
右子节点
左子树
右子树
二叉查找树存在的问题:
平衡二叉树
- 在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。
- 红黑树,就是可以自平衡的二叉树
- 红黑树是一种增删改查数据性能相对都较好的结构。
哈希表的详细流程
1、创建一个默认长度16,默认加载因为0.75的数组,数组名table
2、根据元素的哈希值跟数组的长度计算出应存入的位置
3、判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素, 则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
4、当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
深入理解HashSet集合去重复的机制。
- HashSet集合默认不能对内容一样的两个不同对象去重复!
- 比如内容一样的两个学生对象存入到HashSet集合中去 , HashSet集合是不能去重复的!
package d4_collection_set;
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
// this o
@Override
public int compareTo(Student o) {
// 如果认为左边对象大于右边对象返回正整数
// 如果认为左边对象小于右边对象返回负整数
// 如果认为左边对象等于右边对象返回0
// 需求:按照年龄升序排序、
return this.age - o.age;
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
// 只要两个对象内容一样就返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
// 只要两个对象内容一样,返回的哈希值就是一样的。
@Override
public int hashCode() {
// 姓名 年龄 身高计算哈希值的
return Objects.hash(name, age, height);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package d4_collection_set;
import java.util.HashSet;
import java.util.Set;
/**
* 目标:自定义的类型的对象,比如两个内容一样的学生对象,如果让HashSet集合能够去重复!
*/
public class SetTest3 {
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
Student s1 = new Student("至尊宝", 28, 169.6);
Student s2 = new Student("蜘蛛精", 23, 169.6);
Student s3 = new Student("蜘蛛精", 23, 169.6);
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
Student s4 = new Student("牛魔王", 48, 169.6);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students);
}
}
package d4_collection_set;
import java.util.HashSet;
import java.util.Set;
/**
* 目标:自定义的类型的对象,比如两个内容一样的学生对象,如果让HashSet集合能够去重复!
*/
public class SetTest3 {
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
Student s1 = new Student("至尊宝", 28, 169.6);
Student s2 = new Student("蜘蛛精", 23, 169.6);
Student s3 = new Student("蜘蛛精", 23, 169.6);
System.out.println(s2.hashCode()); //573521603
System.out.println(s3.hashCode()); //573521603
Student s4 = new Student("牛魔王", 48, 169.6);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students);
}
}
HashSet集合去重复详解
集合框架(一):Linked0HashSet的底层原理、TreeSet集合
Collection集合体系
Set系列集合特点:
无序:添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引;
- HashSet : 无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
package d4_collection_set;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 目标:整体了解一下Set系列集合的特点。
*/
public class SetTest1 {
public static void main(String[] args) {
// 1、创建一个Set集合的对象
//Set<Integer> set = new HashSet<>(); // 创建了一个HashSet的集合对象 一行经典代码 HashSet: 无序 不重复 无索引
Set<Integer> set = new LinkedHashSet<>(); // 有序 不重复 无索引
// Set<Integer> set = new TreeSet<>(); // 可排序(升序) 不重复 无索引
set.add(666);
set.add(555);
set.add(555);
set.add(888);
set.add(888);
set.add(777);
set.add(777);
System.out.println(set);
}
}
LinkedHashSet底层原理
- 依然是基于哈希表(数组、链表、红黑树)实现的。
- 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。
TreeSet
- 特点:不重复、无索引、可排序(默认升序排序 ,按照元素的大小,由小到大排序)
- 底层是基于红黑树实现的排序。
package d4_collection_set;
import java.util.Set;
import java.util.TreeSet;
/**
* 目标:掌握TreeSet集合的使用。
*/
public class SetTest4 {
public static void main(String[] args) {
Set<Integer> set1 = new TreeSet<>();
set1.add(6);
set1.add(5);
set1.add(5);
set1.add(7);
System.out.println(set1); //[5, 6, 7]
}
}
注意:
- 对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,TreeSet默认是无法直接排序的。
自定义排序规则
- TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。
方式一
- 让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。
方式二
- 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则。
package d4_collection_set;
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
// this o
@Override
public int compareTo(Student o) {
// 如果认为左边对象大于右边对象返回正整数
// 如果认为左边对象小于右边对象返回负整数
// 如果认为左边对象等于右边对象返回0
// 需求:按照年龄升序排序、
return this.age - o.age;
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
// 只要两个对象内容一样就返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
// 只要两个对象内容一样,返回的哈希值就是一样的。
@Override
public int hashCode() {
// 姓名 年龄 身高计算哈希值的
return Objects.hash(name, age, height);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package d4_collection_set;
import java.util.Set;
import java.util.TreeSet;
/**
* 目标:掌握TreeSet集合的使用。
*/
public class SetTest4 {
public static void main(String[] args) {
Set<Integer> set1 = new TreeSet<>();
set1.add(6);
set1.add(5);
set1.add(5);
set1.add(7);
System.out.println(set1); //[5, 6, 7]
// TreeSet就近选择自己自带的比较器对象进行排序
// Set<Student> students = new TreeSet<>(new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// // 需求:按照身高升序排序
// return Double.compare(o1.getHeight() , o2.getHeight());
// }
// });
//
Set<Student> students = new TreeSet<>(( o1, o2) -> Double.compare(o1.getHeight() , o2.getHeight()));
students.add(new Student("蜘蛛精",23, 169.7));
students.add(new Student("紫霞",22, 169.8));
students.add(new Student("至尊宝",26, 165.5));
students.add(new Student("牛魔王",22, 183.5));
System.out.println(students);
}
}
package d4_collection_set;
import java.util.Set;
import java.util.TreeSet;
/**
* 目标:掌握TreeSet集合的使用。
*/
public class SetTest4 {
public static void main(String[] args) {
Set<Integer> set1 = new TreeSet<>();
set1.add(6);
set1.add(5);
set1.add(5);
set1.add(7);
System.out.println(set1); //[5, 6, 7]
// TreeSet就近选择自己自带的比较器对象进行排序
//方式二
// Set<Student> students = new TreeSet<>(new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// // 需求:按照身高升序排序
// return Double.compare(o1.getHeight() , o2.getHeight());
// }
// });
//方式一
Set<Student> students = new TreeSet<>(( o1, o2) -> Double.compare(o1.getHeight() , o2.getHeight()));
students.add(new Student("蜘蛛精",23, 169.7));
students.add(new Student("紫霞",22, 169.8));
students.add(new Student("至尊宝",26, 165.5));
students.add(new Student("牛魔王",22, 183.5));
System.out.println(students);
}
}
两种方式中,关于返回值的规则:
- 如果认为第一个元素 > 第二个元素 返回正整数即可。
- 如果认为第一个元素 < 第二个元素返回负整数即可。
- 如果认为第一个元素 = 第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:
- 如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。
集合框架(一):集合的并发修改异常问题
1、如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
- 用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
2、如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
- 用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
3. 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
- 用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)
4.如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
- 用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。
5. 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
- 用TreeSet集合,基于红黑树实现。
集合的并发修改异常
- 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
- 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误。
怎么保证遍历集合同时删除数据时不出bug?
- 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
- 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。
package d5_collection_exception;
import java.util.ArrayList;
import java.util.List;
/**
* 目标:理解集合的并发修改异常问题,并解决。
*/
public class CollectionTest1 {
public static void main(String[] args) {
List<String> list = new ArrayList<> ();
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");
System.out.println(list);
// [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
// 需求:找出集合中全部带“李”的名字,并从集合中删除。
// Iterator<String> it = list.iterator();
// while (it.hasNext()){
// String name = it.next();
// if(name.contains("李")){
// list.remove(name);
// }
// }
// System.out.println(list); //报错
// 使用for循环遍历集合并删除集合中带李字的名字
// [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
// [王麻子, 李爱花, 张全蛋, 李玉刚]
// i
// for (int i = 0; i < list.size(); i++) {
// String name = list.get(i);
// if(name.contains("李")){
// list.remove(name);
// }
// }
// System.out.println(list); //出现bug // [王麻子, 李爱花, 张全蛋, 李玉刚]
System.out.println("---------------------------------------------------------");
// 怎么解决呢?
// 使用for循环遍历集合并删除集合中带李字的名字
// [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
// [王麻子, 张全蛋]
// i
// for (int i = 0; i < list.size(); i++) {
// String name = list.get(i);
// if(name.contains("李")){
// list.remove(name);
// i--; //做 i-- 操作
// }
// }
// System.out.println(list);
// 倒着去删除也是可以的。
// 需求:找出集合中全部带“李”的名字,并从集合中删除。
// Iterator<String> it = list.iterator();
// while (it.hasNext()){
// String name = it.next();
// if(name.contains("李")){
// // list.remove(name); // 并发修改异常的错误。
// it.remove(); // 删除迭代器当前遍历到的数据,每删除一个数据后,相当于也在底层做了i--
// }
// }
// System.out.println(list);
// 使用增强for循环遍历集合并删除数据,没有办法解决bug.
// for (String name : list) {
// if(name.contains("李")){
// list.remove(name);
// }
// }
// System.out.println(list);
// list.forEach(name -> {
// if(name.contains("李")){
// list.remove(name);
// }
// });
// System.out.println(list); //报错
}
}
集合框架(二):前置知识——可变参数、Collection工具类
可变参数
- 就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型...参数名称;
可变参数的特点和好处
- 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
- 好处:常常用来灵活的接收数据。
可变参数的注意事项:
- 可变参数在方法内部就是一个数组。
- 一个形参列表中可变参数只能有一个
- 可变参数必须放在形参列表的最后面
package d1_parameter;
import java.util.Arrays;
/**
* 目标:认识可变参数,掌握其作用。
*/
public class ParamTest {
public static void main(String[] args) {
// 特点:
test(); // 不传数据
test(10); // 传输一个数据给它
test(10, 20, 30); // 传输多个数据给它
test(new int[]{10, 20, 30, 40}); // 传输一个数组给可变参数
}
// 注意事项1:一个形参列表中,只能有一个可变参数。
// 注意事项2:可变参数必须放在形参列表的最后面
public static void test(int...nums){
// 可变参数在方法内部,本质就是一个数组。
System.out.println(nums.length);
System.out.println(Arrays.toString(nums));
System.out.println("-----------------------------------------");
}
}
假如需要定义一个方法求和,该方法可以灵活的完成如下需求:
- 计算1个数据的和。
- 计算2个数据的和。
- 计算3个数据的和。
- 计算n个数据的和,甚至可以支持不接收参数进行调用。
Collections
- 是一个用来操作集合的工具类
Collections提供的常用静态方法
package d4_collection_set;
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
// this o
@Override
public int compareTo(Student o) {
// 如果认为左边对象大于右边对象返回正整数
// 如果认为左边对象小于右边对象返回负整数
// 如果认为左边对象等于右边对象返回0
// 需求:按照年龄升序排序、
return this.age - o.age;
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
// 只要两个对象内容一样就返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
// 只要两个对象内容一样,返回的哈希值就是一样的。
@Override
public int hashCode() {
// 姓名 年龄 身高计算哈希值的
return Objects.hash(name, age, height);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package d2_collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* 目标:掌握Collections集合工具类的使用。
*/
public class CollectionsTest1 {
public static void main(String[] args) {
// 1、public static <T> boolean addAll(Collection<? super > c, T...elements):为集合批量添加数据
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三", "王五", "李四", "张麻子");
System.out.println(names); //[张三, 王五, 李四, 张麻子]
// 2、public static void shuffle(List<?> list):打乱List集合中的元素顺序。
Collections.shuffle(names);
System.out.println(names); //[李四, 张三, 张麻子, 王五]
// 3、 public static <T> void sort(List<> list):对List集合中的元素进行升序排序。
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(5);
list.add(2);
Collections.sort(list);
System.out.println(list); //[2, 3, 5]
List<Student> students = new ArrayList<>();
students.add(new Student("蜘蛛精",23, 169.7));
students.add(new Student("紫霞",22, 169.8));
students.add(new Student("紫霞",22, 169.8));
students.add(new Student("至尊宝",26, 165.5));
// Collections.sort(students);
// System.out.println(students); // 不能直接排序,会报错 因为不知道按照什么规则排序 在学生类添加东西即可规定
// 4、public static <T> void sort(List<T> list, Comparator<? super T> c): 对List集合中元素,按照比较器对象指定的规则进行排序
//按照身高 升序排序
Collections.sort(students, new Comparator <Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
});
System.out.println(students);
}
}
Collections只能支持对List集合进行排序
排序方式1:
注意:本方法可以直接对自定义类型的List集合排序,但自定义类型必须实现了Comparable接口,指定了比较规则才可以。
排序方式2:
案例:斗地主游戏
分析业务需求
- 总共有54张牌
- 点数: "3","4","5","6","7","8","9","10","J","Q","K","A","2“
- 花色: "♠", "♥", "♣", "♦“
- 大小王: "👲" , "🃏“
- 斗地主:发出51张牌,剩下3张做为底牌。
分析实现
- 在启动游戏房间的时候,应该提前准备好54张牌
- 接着,需要完成洗牌、发牌、对牌排序、看牌
package d3_collection_test;
public class Card {
private String number; // 点数
private String color; // 花色
private int size; // 每张牌的大小
public Card() {}
public Card(String number, String color, int size) {
this.number = number;
this.color = color;
this.size = size;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public String toString() {
return color + number;
}
}
package d3_collection_test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Room {
private List<Card> allCards = new ArrayList<>();
public Room() {
// 创建54张牌
String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
String[] colors = {"♠", "♥", "♣", "♦"};
int size = 0; // 表示每张牌的大小
for (String number : numbers) {
size++;
for (String color : colors) {
Card c = new Card(number, color, size);
allCards.add(c); // 存入了牌
}
}
// 单独存入大小王
Card c1 = new Card("", "🃏", ++size); // 小王
Card c2 = new Card("", "👲", ++size); // 大王
Collections.addAll(allCards, c1, c2);
System.out.println("新牌:" + allCards);
}
public void start() {
// 洗牌
Collections.shuffle(allCards);
System.out.println("洗牌后:" + allCards);
// 创建玩家列表
List<Card> linHuChong = new ArrayList<>();
List<Card> jiuMoZhi = new ArrayList<>();
List<Card> renYingYing = new ArrayList<>();
// 发牌
for (int i = 0; i < allCards.size() - 3; i++) {
Card c = allCards.get(i);
if (i % 3 == 0) linHuChong.add(c);
else if (i % 3 == 1) jiuMoZhi.add(c);
else if (i % 3 == 2) renYingYing.add(c);
}
// 排序
sortCards(linHuChong);
sortCards(jiuMoZhi);
sortCards(renYingYing);
// 输出结果
System.out.println("啊冲:" + linHuChong);
System.out.println("啊鸠:" + jiuMoZhi);
System.out.println("盈盈:" + renYingYing);
// 底牌
List<Card> lastThreeCards = allCards.subList(allCards.size() - 3, allCards.size());
System.out.println("底牌:" + lastThreeCards);
// 啊鸠抢到地主后
jiuMoZhi.addAll(lastThreeCards);
sortCards(jiuMoZhi);
System.out.println("啊鸠抢到地主后:" + jiuMoZhi);
}
private void sortCards(List<Card> cards) {
Collections.sort(cards, Comparator.comparingInt(Card::getSize));
}
}
package d3_collection_test;
/**
目标:斗地主游戏的案例开发。
业务需求分析:
业务: 总共有54张牌。
点数: "3","4","5","6","7","8","9","10","J","Q","K","A","2"
花色: "♠", "♥", "♣", "♦"
大小王: "👲" , "🃏"
点数分别要组合4种花色,大小王各一张。
斗地主:发出51张牌,剩下3张作为底牌。
*/
public class GameDemo {
public static void main(String[] args) {
// 1、牌类 Card
// 2、房间 Room
Room m = new Room();
// 3、启动游戏
m.start();
}
}
集合框架(二):Map系列集合:概述、常用方法
认识Map集合
- Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , ...}, 一次需要存一对数据做为一个元素.
- Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对集合”
- Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
Map集合在什么业务场景下使用
{商品1=2 , 商品2=3 , 商品3 = 2 , 商品4= 3}
划重点
需要存储一一对应的数据时,就可以考虑使用Map集合来做
Map集合体系
Map集合体系的特点
注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
- HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
- LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
- TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引。
package d4_map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* 目标:掌握Map集合的特点。
*/
public class MapTest1 {
public static void main(String[] args) {
//1、HashMap 按照键 无序,不重复,无索引
// Map<String, Integer>map = new HashMap<>(); // 一行经典代码。 按照键 无序,不重复,无索引。
//2、LinkedHashMap 有序,不重复,无索引
Map<String, Integer> map = new LinkedHashMap<>(); // 有序,不重复,无索引。
map.put("手表", 100);
map.put("手表", 220); // 后面重复的数据会覆盖前面的数据(键)
map.put("手机", 2);
map.put("Java", 2);
map.put(null, null);
System.out.println(map);
//3、TreeMap 可排序,不重复,无索引
Map<Integer, String> map1 = new TreeMap<>(); // 可排序,不重复,无索引
map1.put(23, "Java");
map1.put(23, "MySQL");
map1.put(19, "李四");
map1.put(20, "王五");
System.out.println(map1); //{19=李四, 20=王五, 23=MySQL}
}
}
为什么要先学习Map的常用方法 ?
- Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的。
Map的常用方法如下:
package d4_map;
/**
* 目标:掌握Map集合的常用方法
*/
import java.util.*;
/**
* 目标:掌握Map集合的常用方法(重点)
*/
public class MapTest2 {
public static void main(String[] args) {
// 1.添加元素: 无序,不重复,无索引。
Map<String, Integer> map = new HashMap<>();
map.put("手表", 100);
map.put("手表", 220);
map.put("手机", 2);
map.put("Java", 2);
map.put(null, null);
System.out.println(map);
// map = {null=null, 手表=220, Java=2, 手机=2}
// 2.public int size():获取集合的大小
System.out.println(map.size()); //4
// 3、public void clear():清空集合
//map.clear();
//System.out.println(map);
// 4.public boolean isEmpty(): 判断集合是否为空,为空返回true ,反之!
System.out.println(map.isEmpty());
// 5.public V get(Object key):根据键获取对应值
int v1 = map.get("手表");
System.out.println(v1); //220
System.out.println(map.get("手机")); // 2
System.out.println(map.get("张三")); // null
// 6. public V remove(Object key):根据键删除整个元素(删除键会返回键的值)
System.out.println(map.remove("手表")); //220
System.out.println(map); //{null=null, Java=2, 手机=2}
// 7.public boolean containsKey(Object key): 判断是否包含某个键 ,包含返回true ,反之
System.out.println(map.containsKey("手表")); // false
System.out.println(map.containsKey("手机")); // true
System.out.println(map.containsKey("java")); // false
System.out.println(map.containsKey("Java")); // true
// 8.public boolean containsValue(Object value): 判断是否包含某个值。
System.out.println(map.containsValue(2)); // true
System.out.println(map.containsValue("2")); // false
// 9.public Set<K> keySet(): 获取Map集合的全部键。
Set<String> keys = map.keySet();
System.out.println(keys); //[null, Java, 手机]
// 10.public Collection<V> values(); 获取Map集合的全部值。
Collection<Integer> values = map.values();
System.out.println(values); //[null, 2, 2]
// 11.把其他Map集合的数据倒入到自己集合中来。(拓展)
Map<String, Integer> map1 = new HashMap<>();
map1.put("java1", 10);
map1.put("java2", 20);
Map<String, Integer> map2 = new HashMap<>();
map2.put("java3", 10);
map2.put("java2", 222);
map1.putAll(map2); // putAll:把map2集合中的元素全部倒入一份到map1集合中去。
System.out.println(map1); //{java3=10, java2=222, java1=10}
System.out.println(map2); //{java3=10, java2=222}
}
}
集合框架(二):Map集合的遍历方式
Map集合的遍历方式
Map集合的遍历方式一:键找值
需要用到Map的如下方法:
package d5_map_traverse;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 目标:掌握Map集合的遍历方式1:键找值
*/
public class MapTest1 {
public static void main(String[] args) {
// 准备一个Map集合。
Map<String, Double> map = new HashMap<>();
map.put("蜘蛛精", 162.5);
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map);
// map = {蜘蛛精=169.8, 牛魔王=183.6, 至尊宝=169.5, 紫霞=165.8}
// 1、获取Map集合的全部键
Set<String> keys = map.keySet();
// System.out.println(keys); // [蜘蛛精, 牛魔王, 至尊宝, 紫霞]
// [蜘蛛精, 牛魔王, 至尊宝, 紫霞] 存keySet中
// 初始值key=蜘蛛精
// 2、遍历全部的键,根据键获取其对应的值
for (String key : keys) {
// 根据键获取对应的值
double value = map.get(key);
System.out.println(key + "=====>" + value);
}
}
}
遍历Map集合方式一:键找值流程
Map集合的遍历方式二:键值对
流程
需要用到Map的如下方法
package d5_map_traverse;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 目标:掌握Map集合的第二种遍历方式:键值对。
*/
public class MapTest2 {
public static void main(String[] args) {
Map<String, Double> map = new HashMap<> ();
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map);
// map = {蜘蛛精=169.8, 牛魔王=183.6, 至尊宝=169.5, 紫霞=165.8}
// entries = [(蜘蛛精=169.8), (牛魔王=183.6), (至尊宝=169.5), (紫霞=165.8)]
// entry
// 1、调用Map集合提供entrySet方法,把Map集合转换成键值对类型的Set集合
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) { //键为entry
String key = entry.getKey();
double value = entry.getValue();
System.out.println(key + "---->" + value);
}
}
}
遍历Map集合方式二:键值对流程
Map集合的遍历方式三:Lambda
需要用到Map的如下方法
流程
package d5_map_traverse;
import java.util.HashMap;
import java.util.Map;
/**
* 目标:掌握Map集合的第二种遍历方式:键值对。
*/
public class MapTest3 {
public static void main(String[] args) {
Map<String, Double> map = new HashMap<> ();
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map);
// map = {蜘蛛精=169.8, 牛魔王=183.6, 至尊宝=169.5, 紫霞=165.8}
// map.forEach((k, v) -> { //k v 为取键和取值的变量
// System.out.println(k + "--->;" + v);
// });
//实现遍历
// map.forEach(new BiConsumer<String, Double>() {
// @Override
// public void accept(String k, Double v) { //k代表接收 键,v代表接收值
// System.out.println(k + "---->" + v);
// }
// });
//代码简化
map.forEach(( k, v) -> {
System.out.println(k + "---->" + v);
});
}
}
案例——Map集合的案例-统计投票人数
需求
- 某个班级80名学生,现在需要组织秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
分析
- 将80个学生选择的数据拿到程序中去,[A, A, B , A, B, C, D, ...]
- 准备一个Map集合用于存储统计的结果,Map<String,Integer>,键是景点,值代表投票数量。
- 遍历80个学生选择的景点,每遍历一个景点,就看Map集合中是否存在该景点,不存在存入“景点=1“,存在则其对应值+1
package d5_map_traverse;
import java.util.*;
/**
* 目标:完成Map集合的案例:统计投票人数。
*/
public class MapDemo4 {
public static void main(String[] args) {
// 1、把80个学生选择的景点数据拿到程序中来。
//集合:存入数据
List<String> data = new ArrayList<>();
//定义一个数组,代表能够选择的数组信息
String[] selects = {"A", "B", "C", "D"};
Random r = new Random();
for (int i = 1; i <= 80; i++) {
// 每次模拟一个学生选择一个景点,存入到集合中去。
//随机一个索引,代表一个景点
int index = r.nextInt(4); // 0 1 2 3
//选取索引放入data里去
data.add(selects[index]);
}
System.out.println(data);
// 2、开始统计每个景点的投票人数
// 准备一个Map集合用于统计最终的结果
//String代表存存景点 Integer代表投票的人数取整数
Map<String, Integer> result = new HashMap<>();
// 3、开始遍历80个景点数据
for (String s : data) { //s会依次遍历80个景点中的每一个景点
//每遍历一个景点,问问Map集合中是否存在该景点
if(result.containsKey(s)){
//如果包含
// 说明这个景点之前统计过。其值+1. 存入到Map集合中去
result.put(s, result.get(s) + 1);
}else {
// 说明这个景点是第一次统计,存入"景点=1"
result.put(s, 1);
}
}
System.out.println(result); //打印map集合
}
}
总结
- 需要存储一一对应的数据时,就可以考虑使用Map集合来做
集合框架(二):Map系列集合:HashMap、LinkedHashMAp、TreeMap原理
HashMap(由键决定特点):
- 无序、不重复、无索引; (用的最多)
实际上:
- 原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
哈希表
- JDK8之前,哈希表 = 数组+链表
- JDK8开始,哈希表 = 数组+链表+红黑树
- 哈希表是一种增删改查数据,性能都较好的数据结构。
HashMap的底层原理
HashMap底层是基于哈希表实现的
- HashMap集合是一种增删改查数据,性能都较好的集合
- 但是它是无序,不能重复,没有索引支持的(由键决定特点)
- HashMap的键依赖hashCode方法和equals方法保证键的唯一
- 如果键存储的是自定义类型的对象,可以通过重写hashCode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。
package d6_map_impl;
import java.util.Objects;
public class Student implements Comparable<Student> {
private String name;
private int age;
private double height;
// this o
@Override
public int compareTo(Student o) {
return this.age - o.age; // 年龄升序排序
}
//重写hashCode和equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package d6_map_impl;
import java.util.HashMap;
import java.util.Map;
/**
* 目标:掌握Map集合下的实现类:HashMap集合的底层原理。
*/
public class Test1HashMap {
public static void main(String[] args) {
Map<Student, String> map = new HashMap<>();
map.put(new Student("蜘蛛精", 25, 168.5), "盘丝洞");
map.put(new Student("蜘蛛精", 25, 168.5), "水帘洞");
map.put(new Student("至尊宝", 23, 163.5), "水帘洞");
map.put(new Student("牛魔王", 28, 183.5), "牛头山");
System.out.println(map);
}
}
LinkedHashMap (由键决定特点):
- 有序、不重复、无索引。
package d6_map_impl;
import java.util.LinkedHashMap;
/**
* 目标:掌握LinkedHashMap的底层原理。
*/
public class Test2LinkedHashMap {
public static void main(String[] args) {
// Map<String, Integer> map = new HashMap<>(); // 按照键 无序,不重复,无索引。
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(); // 按照键 有序,不重复,无索引。
map.put("手表", 100);
map.put("手表", 220);
map.put("手机", 2);
map.put("Java", 2);
map.put(null, null);
System.out.println(map);
}
}
LinkedHashMap集合的原理
- 底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)。
实际上:
- 原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap。
TreeMap (由键决定特点):
- 按照键的大小默认升序排序、不重复、无索引。
TreeMap特点:
- 不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)
TreeMap原理:
- TreeMap跟TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序。
package d6_map_impl;
import java.util.Objects;
public class Student implements Comparable<Student> {
private String name;
private int age;
private double height;
// this o
@Override
public int compareTo(Student o) {
return this.age - o.age; // 年龄升序排序
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package d6_map_impl;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
/**
* 目标:掌握TreeMap集合的使用。
*/
public class Test3TreeMap {
public static void main(String[] args) {
Map<Student, String> map = new TreeMap<> (new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//按照身高实现降序排序
return Double.compare(o2.getHeight(), o1.getHeight());
}
});
//简化代码
// Map<Student, String> map = new TreeMap<>(( o1, o2) ->Double.compare(o2.getHeight(), o1.getHeight()));
map.put(new Student("蜘蛛精", 25, 168.5), "盘丝洞");
map.put(new Student("蜘蛛精", 25, 168.5), "水帘洞");
map.put(new Student("至尊宝", 23, 163.5), "水帘洞");
map.put(new Student("牛魔王", 28, 183.5), "牛头山");
System.out.println(map);
}
}
TreeMap集合同样也支持两种方式来指定排序规则
- 让类实现Comparable接口,重写比较规则。
- TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。
集合框架(二):补充知识:集合的嵌套
集合的嵌套
- 指的是集合中的元素又是一个集合
案例:Map集合案例-省和市
需求
- 要求在程序中记住如下省份和其对应的城市信息,记录成功后,要求可以查询出湖北省的城市信息。
分析
- 定义一个Map集合,键用表示省份名称,值表示城市名称,注意:城市会有多个。
- 根据“湖北省”这个键获取对应的值展示即可。
package d7_collection_nesting;
import java.util.*;
/**
* 目标:理解集合的嵌套。
* 江苏省 = "南京市","扬州市","苏州市“,"无锡市","常州市"
* 湖北省 = "武汉市","孝感市","十堰市","宜昌市","鄂州市"
* 河北省 = "石家庄市","唐山市", "邢台市", "保定市", "张家口市"
*/
public class Test {
public static void main(String[] args) {
// 1、定义一个Map集合存储全部的省份信息,和其对应的城市信息。
Map<String, List<String>> map = new HashMap<>();
List<String> cities1 = new ArrayList<>();
Collections.addAll(cities1, "南京市","扬州市","苏州市" ,"无锡市","常州市");
map.put("江苏省", cities1);
List<String> cities2 = new ArrayList<>();
Collections.addAll(cities2, "武汉市","孝感市","十堰市","宜昌市","鄂州市");
map.put("湖北省", cities2);
List<String> cities3 = new ArrayList<>();
Collections.addAll(cities3, "石家庄市","唐山市", "邢台市", "保定市", "张家口市");
map.put("河北省", cities3);
System.out.println(map);
//{江苏省=[南京市, 扬州市, 苏州市, 无锡市, 常州市], 湖北省=[武汉市, 孝感市, 十堰市, 宜昌市, 鄂州市], 河北省=[石家庄市, 唐山市, 邢台市, 保定市, 张家口市]}
List<String> cities = map.get("湖北省");
for (String city : cities) {
System.out.println(city);
//武汉市
//孝感市
//十堰市
//宜昌市
//鄂州市
}
map.forEach((p, c) -> {
System.out.println(p + "----->" + c);
//江苏省----->[南京市, 扬州市, 苏州市, 无锡市, 常州市]
//湖北省----->[武汉市, 孝感市, 十堰市, 宜昌市, 鄂州市]
//河北省----->[石家庄市, 唐山市, 邢台市, 保定市, 张家口市]
});
}
}