Java 基础语法学习总结
一、异常
错误与异常
java中对于程序出现的异常情况分为两种:
- 错误(Error)
- 异常(Exception)
Error:错误通常是系统级别的问题,比如说JVM内存溢出(*Error),JVM系统错误等,这些问题是程序员无法修复的问题,程序运行时出现的无法被程序员从业务上解决的问题,这些问题一般是系统级别的。错误不是我们关注的范畴
Exception:异常通常是程序再运行期间,或者编译期间由编译器抛出的一些,可以被程序员处理的代码上的问题,比如(NullPointerExcepotion/ArrayIndexOutOfBoundsException),异常是程序员开发中需要解决的问题
Throwable
Throwable是Java中错误和异常的*父类,以下是Throwable和Error,Exception之间的关系
java中的所有错误从Error类继承,并且绝大多数类名称后缀以Error结尾
Java中的所有异常从Exception类继承,都是以Exception作为后缀结尾
异常概述
Exception:异常,一般在程序运行期间,或者编译期间由编译器抛出的异常信息,这些异常情况可以由程序员进行处理(抛出,捕获);java中的异常根据类型划分又分为两种类型:
- 运行时异常(RuntimeException)
- 检查异常(一般异常)
运行时异常
运行时异常一般在程序运行期间,出现了对应异常情况之后由JVM抛出,并且将异常的堆栈信息输出到控制台(或日志文件),java中的所有运行时异常都是从java.lang.RuntimeException继承而来。常见的运行时异常:
异常类型 | 说明 |
---|---|
java.long.ArithmeticException | 算数异常(比如被零除) |
java.lang.NullPonterException | 空指针异常(调用方法,属性的对象位null时) |
java.lang.ArrayIndexOutOfBoundsException | 数组索引越界 |
java.lang.ClassCastException | 类型转换异常 |
java.util.InputMismatchException | 输入的数据类型不匹配读取的异常 |
运行时异常即程序运行时才会产生的异常
检查异常
检查异常也称之为一般异常,或者编译期异常,这种类型异常通常在编译期间由编译器提示需要进行显式的处理:
常见的检查异常:
异常类型 | 说明 |
---|---|
java.lang.ClassNotFoundException | 未找到异常 |
java.io.FileNotFoundException | 文件未找到异常 |
java.io.IOException | IO异常 |
java.sql.SQLException | 访问数据库的异常 |
java.text.ParseException | 解析异常 |
检查异常是在程序编译时产生的
异常处理
异常既然产生则有必要进行合理的处理,Java中对于异常的处理分为两种方式:
- 异常抛出(throw/throws)
- 异常捕获(try/catch/finally)
Java程序中一旦出现异常,则出现异常问题的所在代码行之后的代码无法再执行
异常抛出
异常的抛出指的是将有可能出现的异常通过方法的结构向外抛出,交给下一级的调用者处理
/**
* 抛出异常
* @throws ClassNotFoundException
*/
public static void e1() throws ClassNotFoundException{
Class.forName("java.lang.Strin");
}
抛出异常常见的关键字:
- throws:用于方法的声明中,抛出有可能出现的异常
- throw:用于语句块中,抛出指定类型的异常对象,throw一旦执行,则一定会出现该类型异常
语法区别:
- throws
【修饰符】 返回值类型 方法名(【参数列表】) throws 异常类型名称{
//方法体
}
public static void e1() throws ClassNotFoundException{
Class.forName("java.lang.String");
}
- throw
方法体{
throw 异常类型对象
}
public static void main(String[] args) throws IOException {
int i = 0;
if(i == 0) {
//抛出异常对象
throw new IOException();
}
System.out.println("hello");
}
对于存在继承关系的异常抛出问题
父类的结构:
public class Animal {
public void eat(){
System.out.println("吃东西");
}
}
子类的结构:
public class Dog extends Animal{
//编译错误
public void eat() throws ClassNotFoundException{
Class.forName("");
}
}
对于以上程序:
子类Dog对父类Animal中的方法eat()方法进行了重写,但是由于父类方法没有抛出任何的异常,此时子类无法进行任何检查的抛出,否则会不兼容父类方法定义,因此以上程序在子类中会出现编译错误
解决方案由两种:
- 子类方法中对于异常捕获
- 在父类方法的声明上加上对应的异常类型抛出定义:throws ClassNotFoundException
父类方法可以抛出比子类方法抛出的范围更大的异常,比如直接throws Exception
注意事项:
父类方法未抛出任何异常情况下,子类只能抛出运行时异常。
游离快和静态语句块中不能抛出任何异常,因为外界无法直接调用这两种语句块
异常捕获
异常的捕获即,将有可能出现异常的代码片段使用try语句块进行包裹,然后使用catch语句块将有可能产生的异常类型进行捕获,并作出处理。
异常捕获常见的关键字:
- try
- catch
- finally
语法结构:
try{
//有可能出现异常的代码片段
}catch(异常类型 变量名){
//处理异常
}finally{
//不论是否出现异常,始终执行
}
try {
m1();
PrintStream ps = new PrintStream("a/test/details.log");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
System.out.println("fileNotFound");
e.printStackTrace();
} catch (IOException e) {
System.out.println("IO");
e.printStackTrace();
}
程序执行到try语句块,在执行期间如果出现了对应catch的异常类型,则直接进入catch语句块,如果catch语句块中没有合适的异常解决方案,则由JVM进行统一处理(打印异常的堆栈信息)
finally
finally一般用于异常捕获之后执行最终的处理工作,比如,清理资源,关闭流,关闭连接;finally中的代码无论是否出现异常,始终会执行。
try {
//打开资源
System.out.println("打开文件");
System.out.println(10/2);
}catch(Exception e) {
e.printStackTrace();
}finally {
//无论是否异常始终执行
System.out.println("关闭文件");
}
try,catch,finally的组织方式可以有多种:
//方法一:
try {
}finally {
}
//方法二:
try {
}catch(Exception e) {
}
//方法三:
try {
}catch(RuntimeException re) {
}catch(Exception e) {
}
//方法四:
try {
}catch(Exception e) {
}finally {
}
关于异常的常见面试题
1. 请你说出 final、finalize和finally的区别?
final是一个关键字用于修饰类,属性,方法
finalize是Object类中提供的一个方法,用于在jvm对对象清理时,对于当前对象执行一些最终的处理工作的
2. java中是否会存在内存溢出的问题?(指针)
理论上java不会存在内存泄漏问题,因为jvm提供了GC(垃圾回收:garbage collection)机制,会在适当的时候自动回收内存空间,不需要由程序员手动处理;但是如果使用第三方资源(比如:打开一个文件,打开了网络通道,打开数据库连接等)并且未及时的清理以及回收,将会导致内存泄漏。
3. 异常处理中finally和return的结合使用?
如果try语句块中有使用return,并且try语句块中没有任何异常时,程序首先会执行finally然后再执行return;但是对于基本类型的数据,finally的赋值是不会生效的,但是finally中操作引用类型的属性可以生效
//程序正常执行,返回 20;finally中的赋值无效
public static int m2() {
int i = 10;
try {
i = 20;
return i;
}catch(Exception e){
e.printStackTrace();
}finally {
i = 30;
System.out.println("finally");
}
return i;
}
//程序正常执行,返回对象中的name属性值被修改为“李四”;finally中的赋值生效
public static User m3() {
User u = new User();
try {
u.name = "张三";
return u;
}catch(Exception e) {
e.printStackTrace();
} finally {
u.name = "李四";
}
return u;
}
异常定位
自定义异常
概述
以上我们已经熟悉了java中的异常分类以及处理方式,其中异常分类主要包含检查异常和运行时异常,但是以上所有异常都是有JDK预定义好的异常类型,比如:空指针,索引越界,类型转换失败等代码语法方面的异常,并没有与实际项目相关一些业务方面的异常,比如:订单创建失败,用户权限不足,余额不足等异常情况;
因此,针对以上的需求,当预定的异常无法满足所有需要时,我们可以通过对JDK的异常进行扩展,自定义异常,以满足实际项目的需求。
自定义异常的使用
java中创建自定义异常十分简单,只需要对现有的异常类型,扩展即可,比如常见的方式为:继承Exception,声明一个无参的以及一个包含字符串类型参数的构造器即可。异常的定义通常用于标记程序运行时的异常情况,并不需要在异常中进行任何的业务逻辑处理,因此自定义异常中也无需定义任何的方法。
案例:
public class MyException extends Exception{
public MyException() {
super();
}
public MyException(String msg) {
super(msg);
}
}
自定义异常综合案例:
一有个银行账户A和账户B,现在需要从账户A转账到账户B,转账需要检查账户的余额是否足够,如果余额不足,则抛出一个 MoneyLessException,请实现!
账户类(Account.java)
public class Account {
private int id;
private String name;
private double money;
//构造器(略)
//setter/getter(略)
//toString(略)
}
账户管理类(AccountManager.java)
public class AccountManager {
/**
* 将指定账户中的余额转移指定数目到另一个账户中
* @param a1 账户A
* @param a2 账户B
* @param money 需要转账的金额
* @throws MoneyLessException
*/
public void transfer(Account a1,Account a2,double money) throws MoneyLessException {
if(a1.getMoney() < money) {
//余额不足
throw new MoneyLessException("余额不足:"+(a1.getMoney() - money));
}
a1.setMoney(a1.getMoney() - money);
a2.setMoney(a2.getMoney() + money);
System.out.println(a1);
System.out.println(a2);
}
}
自定义异常类(MoneyLessException.java)
/**
* 余额不足异常
* @author mrchai
*
*/
public class MoneyLessException extends Exception {
public MoneyLessException() {
super();
}
public MoneyLessException(String msg) {
super(msg);
}
}
测试类(Test.java)
public class Test {
public static void main(String[] args) throws MoneyLessException {
Account a1 = new Account(1, "A", 500);
Account a2 = new Account(2, "B", 100);
AccountManager am = new AccountManager();
am.transfer(a1, a2, 100);
}
}
常用类之BigDecimal与DecimalFormat
BigDecimal
java.math.BigDecimal类从java.math.Number类继承而来,用于表示精度较高的数值类型的封装类型,一般用于精度要求较高的程序中,比如银行账户的金额属性,
常见构造器:
- java.math.BigDecimal(String s)
- java.math.BigDecimal(double d)
- java.math.BigDecimal(long l)
- java.math.BigDecimal(int i)
常见方法:
- add(BigDecimal b):与另一个BigDecimal执行相加运算
- subtract(BigDecimal b):与另一个BigDecimal执行相减运算
- multiply(BigDecimal b):与另一个BigDecimal执行相乘运算
- divide(BigDecimal b):与另一个BigDecimal执行相除运算
double d1 = 0.1;
double d2 = 0.2;
// System.out.println(d1 + d2);
//在涉及到一些敏感的浮点数运算时,不适合直接使用float和double,精度丢失
BigDecimal b1 = new BigDecimal("0.1");
BigDecimal b2 = new BigDecimal("0.2");
//相加 a + b => a.add(b)
System.out.println(b1.add(b2));
System.out.println(b1.add(b2,MathContext.DECIMAL128));
//相减 a - b => a.subtract(b)
System.out.println(b1.subtract(b2));
//相乘 a * b => a.multiply(b)
System.out.println(b1.multiply(b2));
//相除 a / b => a.divide(b)
System.out.println(b1.divide(b2));
对于相除运算的使用:
BigDecimal d1 = new BigDecimal("10");
BigDecimal d2 = new BigDecimal("3");
//当两个数值相除为无限循环数时会出现算术异常
//向上取整
System.out.println(d1.divide(d2,RoundingMode.CEILING));
//向下取整
System.out.println(d1.divide(d2,RoundingMode.FLOOR));
//向上保留指定位小数点
System.out.println(d1.divide(d2, 2, BigDecimal.ROUND_CEILING));
//向下保留指定位小数点
System.out.println(d1.divide(d2, 2, BigDecimal.ROUND_FLOOR));
NumberFormat & DecimalFormat
java.text.NumberFormat 和java.text.DecimalFormat是用于进行数值格式化的类,可以对数值的显示位数进行格式化处理,以及对浮点数进行金额或者百分比的格式表现;DecimalFormat是NumberFormat的子类。
java.text.NumberFormat是一个抽象类,内部提供了几个静态方法用于直接获取NumberFormat对象,而这些静态方法的实现通过java.text.DecimalFormat实现。
NumberFormat常见方法:
- format(double d):将一个double值按指定的格式转换为String
- parse(String s):将一个String类型的数值解析为double类型
double d = 150500.491987;
//获取用于进行货币格式化(本地环境)的数值格式化对象
NumberFormat fmt = NumberFormat.getCurrencyInstance();
//将浮点数转换为字符串类型的固定格式
String s = fmt.format(d);
System.out.println(s);//¥150,500.49
//获取整数类型的格式化对象
fmt = NumberFormat.getIntegerInstance();
System.out.println(fmt.format(d));//150,500
//获取标准的数值类型格式化对象(对于小数点最大保留后三位)
fmt = NumberFormat.getNumberInstance();
System.out.println(fmt.format(d));//150,500.492
//获取进行百分比格式化的对象
fmt = NumberFormat.getPercentInstance();
System.out.println(fmt.format(0.456789));//46%
由于NumberFormat提供的方法可能无法满足一些个性化的格式需求,比如需要将double值转换为百分比,并保留小数点后两位,NumberFormat中提供的格式化方法getPersentInstence()只能保留整数位;因此,我们需要能够自定格式的工具,所以java中提供的java.text.DecimalFormat就能够满足需求了:
//创建一个数值格式化对象
DecimalFormat fmt = new DecimalFormat("##.##%");
System.out.println(fmt.format(0.456789)); // 45.68%
fmt = new DecimalFormat("\u00A4##,###,###.#");
System.out.println(fmt.format(5423423467890.345)); //¥5,423,423,467,890.3
//NumberFormat&DecimalFormat不仅能够将double值按指定的格式格式化为String,
//同时,也能将String类型表示的数字解析为需要的数值类型
String s = "50.67%";
//将以上字符串转换为double值 0.5067
fmt = new DecimalFormat("##.##%");
Number num = fmt.parse(s);
System.out.println(num.doubleValue());
二、常用类
Objects类
Object类是所有java类的顶层父类(祖宗类),但是Objects是从Java7开始新增的一个对于java对象进行空指针安全操作的工具类
- Objects类是一个工具类
- Objects对外不提供构造器,因此无法创建对象
- Objects类中的所有方法都是static
- 提供的是对于对象的空指针安全操作的方法
User u1 = new User(1, "softeem", "123456");
User u2 = null;
// if(u2 != null) {
// System.out.println(u2.equals(u1));
// }
//空指针安全的对象比较
System.out.println(Objects.equals(u1, u2));
//空指针安全的toString
System.out.println(Objects.toString(u2)); // 对象若为null则输出“null”字符串
System.out.println(Objects.toString(u2,"0")); //对象若为null则输出“0”
//空指针安全的hashCode
System.out.println(Objects.hashCode(u2));
//为空判断
System.out.println(Objects.isNull(u2));
//不为空判断
System.out.println(Objects.nonNull(u2));
//检查对象不为空,若为空则抛出NullPointerException
System.out.println(Objects.requireNonNull(u2));
Random类
Math类中提供了一个random()的方法,用于随机一个从0.0~1.0之间的浮点数,当实际需求有个性化的要求时,只能通过计算获取其他类型的随机数,实际操作相对比较麻烦;因此,Java中还提供了另一个专门用于生成各种需求的随机数类型,比如,随机整数,随机浮点数,随机布尔值。
构造器:
- Random()
常见方法:
- nextBoolean():随机一个布尔值
- nextDouble():随机一个0.0~1.0之间的double值
- nextInt():随机一个int范围内的整数值
- nextInt(int bounds):随机一个从0~bounds-1位的整数值
public class RandomDemo {
private static final class RandomGeneratorHolder{
static final Random randomNumberGenerator = new Random();
}
private static final String SOURCE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
//随机指定长度位验证码
public static String genCode(int count) {
String code = "";
for(int i = 0;i < count;i++) {
int index = RandomGeneratorHolder.randomNumberGenerator.nextInt(SOURCE.length());
code += SOURCE.charAt(index);
}
return code;
}
public static void main(String[] args) {
Random r = new Random();
//随机布尔值
boolean f = r.nextBoolean();
System.out.println(f);
//随机一个从0.0~1.0之间的浮点数
double d = r.nextDouble();
System.out.println(d);
int i = r.nextInt();
System.out.println(i);
//随机一个5以内的整数
i = r.nextInt(5);
System.out.println(i);
//随机一个5(含)~10(含)之间的整数
i = r.nextInt(6) + 5;
System.out.println(i);
//随机生成指定长度的验证码,包含0-9A-Za-z之间的字符
String code = genCode(6);
System.out.println(code);
}
}
File类
File类是来自于java.io包中的一个用于处理本机操作系统中的文件,文件可以是目录也可以是一个标准文件;File类是用于在Java中实现跟本机文件系统进行关联的操作类。其中包含一些常见的文件操作:创建文件/目录,查看文件的状态,删除文件等。
常见属性
常量 | 解释 |
---|---|
pathSeparator | 获取与本机系统相关的路径分隔符(windows是";"符号,Linux是”:“符号) |
separator | 获取与本机系统相关的目录分隔符(windows是”\“,Linux是”/“) |
构造器
File(File parent, String child) 使用父目录所表示的File对象,结合子文件名构建新的File对象 |
---|
File(String pathname) 根据提供的文件路径构建一个File对象 |
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。 |
File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。 |
常用方法
- createNewFile() 创建新文件(标准文件)
- exists() 判断File对象是否存在
- delete() 删除File对象所表示的文件或者目录(空目录)
- deleteOnExit() 当JVM结束时删除
- getName() 获取File对象所表示的文件名称
- getParent() 获取File所指的文件或者目录的父路径
- isFile() 判断当前File所指的是否是标准文件
- isDirectory() 判断当前File所指的是否是目录
- isHidden() 判断当前File对象是否是隐藏目录
- lastModified() 获取最后修改时间
- length() 获取File所表示文件大小(目录为4096)
- list() 获取File所表示目录下所有子文件的名称数组
- listFiles() 获取File对象所表示目录下的所有子File数组
- listFiles(FileFilter filter) 通过文件过滤器将File所表示目录中符合条件的File对象过滤出来
- listRoots() 获取当前电脑可用的磁盘根
- mkdir() 创建当前File所表示的目录(一级)
- mkdirs() 创建当前File所表示的目录(多级)
- renameTo(File file) 重命名文件
测试一:
public class FileDemo1 {
public static void main(String[] args) throws IOException {
//获取与本机系统相关的路径分割符
System.out.println(File.pathSeparator); // windows是; linux是:
//获取与本机系统相关的名称(目录)分割符
System.out.println(File.separator); //windows是 \ linux是 /
// System.out.println(File.separatorChar);
//通过java程序操作本地文件,在java中就必须存在文件对象(逻辑)
//根据提供的文件的绝对路径(abstract path)获取文件对象
File file = new File("D:/music_db.sql");
//根据提供的文件相对路径(relative path)获取文件对象
File file2 = new File("test/a.txt");
//输出File对象所表示文件的绝对路径
System.out.println(file.getAbsolutePath());
System.out.println(file2.getAbsolutePath());
//输出File对象所有表示文件的相对路径
System.out.println(file.getPath());
System.out.println(file2.getPath());
//判断文件的可用性:可执行,可读,可写
System.out.println(file2.canExecute());
System.out.println(file2.canRead());
System.out.println(file2.canWrite());
//判断文件是否存在
System.out.println("文件(或目录)是否存在-->"+file2.exists());
file2 = new File("test/com");
//创建新文件(标准文件)前提:父目录必须存在,否则IO异常
boolean b = file2.createNewFile();
System.out.println("文件创建结果--->"+b);
file2 = new File("D:\\带班资料\\2020\\j2009\\code\\part1-javabase\\lesson17\\test\\org\\softeem\\demo");
//创建目录(创建一级目录)
System.out.println("一级目录创建结果--->"+file2.mkdir());
System.out.println("创建多级目录--->"+file2.mkdirs());
// Random r = new Random();
// for (int i = 0; i < 1000; i++) {
// File f3 = new File(file2, r.nextInt(10000)+""+i);
// if(f3.mkdirs()) {
// System.out.println(f3.getAbsolutePath()+"创建成功!");
// }
// }
}
}
测试二:
public class FileDemo2 {
public static void main(String[] args) throws IOException, InterruptedException {
// 创建临时文件
// File f = File.createTempFile("softeem", ".log",new File("test"));
// System.out.println(f.getAbsolutePath());
// if(f.exists()) {
// 立即删除文件
// boolean b = f.delete();
// System.out.println("删除结果:"+b);
// }
File f2 = new File("test/a.txt");
// 删除目录的前提是目录下没有任何子文件或者子目录
// System.out.println("删除目录-->"+f2.delete());
// 当jvm结束才删除
// f2.deleteOnExit();
// System.out.println(5/0);
// 休眠5秒
// Thread.sleep(10000);
// 返回当前文件所在磁盘的剩余空间(字节)
long size = f2.getFreeSpace();
System.out.println(size / (1024 * 1024 * 1024) + "GB");
//获取当前文件所在磁盘的总空间大小
size = f2.getTotalSpace();
System.out.println(size / (1024 * 1024 * 1024) + "GB");
//获取file对象所表示的文件名称
System.out.println(f2.getName());
//获取File对象所表示文件所在的父目录名称(String)
System.out.println(f2.getParent());
//获取File对象所表示文件所在的父目录File对象(File)
System.out.println(f2.getParentFile());
// 实现一个程序:要求能清理指定目录下的所有字节码文件(清理缓存)包括多余的空目录
// 1. 如何获取一个目录下的所有子文件或者子目录(递归)
// 2. 如何获取所有的字节码文件(以 .class 结尾的文件,String类)
}
}
文件过滤
File类中提供了几个用于列出指定目录下所有子文件的方法:
- list()
- listFiles()
以上两个方法不会对文件进行任何的限制和过滤,直接全部遍历,但是以上两个方法还提供了对应的重载方法
- list(FilenameFilter filter)
- listFiles(FileFilter filter)
- listFiles(FilenameFilter filter)
过滤方式一
以listFiles(FileFilter filter)为例,使用方法如下:
File f = new File("D:\\素材\\音乐\\music素材");
//创建过滤器对象
FileFilter filter = new FileFilter() {
//回调函数
@Override
public boolean accept(File f) {
return f.getName().endsWith(".mp3");
}
};
File[] files = f.listFiles(filter);
for (File file : files) {
System.out.println(file.getName());
}
以上程序执行之后会获取所有的mp3文件,实现原理如下:
以上程序的实现,使用了匿名内部类,回调机制
过滤方式二:
创建文件过滤器:
/**
* 文件过滤器
* @author mrchai
*/
public class MyFileFilter implements FileFilter {
private String suffix;
public MyFileFilter(String suffix) {
this.suffix = suffix;
}
/**
* 实现过滤规则
*/
@Override
public boolean accept(File f) {
return f.getName().endsWith(suffix);
}
}
测试类:
public class FileDemo5 {
public static void main(String[] args) {
File f = new File("D:\\素材\\音乐\\music素材");
//创建过滤器对象
FileFilter filter = new MyFileFilter(".jpg");
//使用过滤器进行文件过滤
File[] files = f.listFiles(filter);
for (File file : files) {
System.out.println(file.getName());
}
}
}
文件递归遍历
在对系统文件加遍历时往往会遇到一种情况:需要将一个目录下的所有子文件全部获取,但是该目录下可能还存在子目录,以及多个子目录嵌套的情况;而具体的嵌套层次是未知的,因此无法使用传统的循环语句进行遍历,此时,最好的方式可以通过递归实现遍历:
public class FileDemo6 {
/**
* 读取指定目录中的所有子文件
* @param dir 源目录
*/
public static void readDir(File dir) {
//获取目录下所有的文件对象(数组)
File[] files = dir.listFiles();
//判断数组对象是否为null
if(Objects.nonNull(files)) {
//遍历所有的File对象
for(File file:files) {
//判断当前File对象所表示的是否一个目录
if(file.isDirectory()) {
//如果是目录,则递归遍历
readDir(file);
}
//输出文件(或者目录)名称
System.out.println(file.getName());
}
}
}
public static void main(String[] args) {
//源目录
File dir = new File("D:\\java");
//读取
readDir(dir);
}
}
三、常用类(如期类&正则表达式)
日期处理类
在程序开发中日期的处理十分常见,比如:订单的创建时间,用户注册时间,某项具体操作的触发时间等;因此java中也提供了丰富用于日期时间处理的工具类:
- java.util.Date(jdk1.0)
- java.util.Calendar(jdk1.1)
- java.time.LocalDate(jdk8)
- java.time.LocalTime(jdk8)
- java.time.LocalDateTime(jdk8)
Date类
java.util.Date是一个传统的java用于处理日期时间的类,由于版本更新,内部有很多构造器包括方法均已标记为过时,取而代之的是java.util.Calendar类;
常用构造器
- Date():获取当前系统时间所表示的日期对象
- Date(long time):根据提供的时间毫秒数,构建一个日期对象(从1970年1月1日 00:00:00开始)
Date d = new Date();
// d = new Date(120,11,1);
d = new Date(System.currentTimeMillis());
System.out.println(d);
//获取十分钟之后的日期对象 1sec = 1000
d = new Date(System.currentTimeMillis() + 600000);
System.out.println(d);
常用方法
- after(Date d)
- before(Date d)
- compareTo(Date d)
- getTime()
//判断当前日期对象是否是参数日期对象之后的日期
System.out.println(d1.after(d2));
System.out.println(d1.before(d2));
//实现一个小程序,判断两个学生的年龄,输出较大学生的信息?
//学生:学号,姓名,生日(1999/11/11)
Date d3 = new Date();
System.out.println(d3.equals(d1));
//获取当前日期对象所表示的时间毫秒数
System.out.println(d3.getTime());
System.out.println(d1.getTime());
// System.out.println(d3.getYear());
日期格式化(DateFormat & SimpleDateFormat)
DateFormat类来自java.text包下的用于进行日期时间格式化处理的类,是一个抽象类,内部提供了一系列用于获取格式化对象的静态方法:
- getDateInstance()
- getTimeInstance()
- getDateTimeInstance()
- getInstance()
SimpleDateFormat
DateFormat中提供的静态方法可以用户获取各种丰富的格式化对象,但是针对一些需求DateFormat并不能完全满足,比如需要如下格式日期:
- 20201120
- 2020年11月20日 09时48分50秒
- 10:11:35
通过观察DateFormat中getInstance()源码得知,内部使用了一个子类SimpleDateFormat实现,SimpleDateFormat可以通过定制化的匹配模式来匹配不同的日期时间输出格式:
实例参考:
使用方法
Date d = new Date();
//20201120
DateFormat fmt = new SimpleDateFormat("yyyyMMdd");
time = fmt.format(d);
System.out.println(time);
//2020年11月20日 09时48分50秒
fmt = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒");
time = fmt.format(d);
System.out.println(time);
//[10:05:55]
fmt = new SimpleDateFormat("[HH:mm:ss]");
System.out.println(fmt.format(d));
/********将String类型的日期时间转换为Date类型对象********/
String dateTime = "1999/11/11 09:10:11";
fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
//将字符串类型的时间根据指定的格式解析为Date对象
Date date = fmt.parse(dateTime);
System.out.println(date);
Calendar
从JDK1.1开始java.util.Date中的很多方法和构造器标记为过时,同时jdk新增了对应的替代方案:java.util.Calendar,是一个抽象类,有一个直接子类:java.util.GregorianCalendar对其继承,并且实现其中的抽象方法,常见的Calendar实例获取方式为直接使用内部提供的静态方法getInstance()
具体使用如下:
//以当前系统时间为基础获取日历实例 +8
Calendar c = Calendar.getInstance();
System.out.println(c);
常见字段
- YEAR
- MONTH
- DAY_OF_MONTH
- HOUR
- MINITE
- SECOND
- DAY_OF_WEEK
- …
常见方法
- get(int field)
- set(int field,int value)
- getActualMaximum(int field)
具体用法
//以当前系统时间为基础获取日历实例 +8
Calendar c = Calendar.getInstance();
System.out.println(c);
//设置日历到2019年11月20日?
// c.set(2019, 10, 20);
//设置日历对象中指定字段的值
c.set(Calendar.YEAR, 2019);
System.out.println("获取日历表示的年份:"+c.get(Calendar.YEAR));
System.out.println("获取日历表示的月份(月份从0开始):"+c.get(Calendar.MONTH));
System.out.println("获取日历表示的日期"+c.get(Calendar.DAY_OF_MONTH));
System.out.println("获取日历表示的小时"+c.get(Calendar.HOUR_OF_DAY));
System.out.println("获取日历表示的分钟"+c.get(Calendar.MINUTE));
System.out.println("获取日历表示的秒钟"+c.get(Calendar.SECOND));
System.out.println("获取日历表示的毫秒"+c.get(Calendar.MILLISECOND));
System.out.println("获取当前日历表示的日期是今年的第多少天:"+c.get(Calendar.DAY_OF_YEAR));
System.out.println("获取日历所有表示的时间毫秒数"+c.getTimeInMillis());
//设置月份为12月
c.set(Calendar.MONTH, 11);
//获取当前日期所表示字段的可能最大值
int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH);
System.out.println(maxDay); //31
int maxMonth = c.getActualMaximum(Calendar.MONTH);
System.out.println(maxMonth);//11 (0~11)
//获取小时(24小时制)的最大值
int maxHour = c.getActualMaximum(Calendar.HOUR_OF_DAY);
System.out.println(maxHour); //23 (0~23)
//获取小时(12小时制)的最大值
maxHour = c.getActualMaximum(Calendar.HOUR);
System.out.println(maxHour);
//计算从你出生到现在一共生活了多少天?(生命倒计时) 1000 * 60 * 60 * 24
c.set(1999, 10, 11);
long start = c.getTimeInMillis();
long now = System.currentTimeMillis();
long days = (now - start) / (1000 * 60 * 60 * 24);
System.out.println(days);
//将日历的日期设置为1号(设置为这个月的第一天)
c.set(Calendar.DAY_OF_MONTH, 1);
System.out.println(c.get(Calendar.DAY_OF_WEEK));
GregorianCalendar gc = (GregorianCalendar)c;
//判断指定年份是否是闰年(该方法为GregorianCalendar特有,因此需要将Calendar强制转换)
boolean leapYear = gc.isLeapYear(2020);
System.out.println(leapYear);
//将Calendar转换为java.util.Date
Date date = c.getTime();
System.out.println(date);
正则表达式
正则表达式( Regular expression)是一组由字母和符号组成的特殊文本, 它可以用来从文本中找出满足你想要的格式的句子。正则表达式最早源自于perl语言(脚本语言);正则表达式的功能十分强大,可以用于进行文本的匹配,检索,替换;常见于一些网络爬虫。
java中对于正则表达式的处理主要由一下三个类实现:
- java.lang.String
- java.util.regex.Pattern
- java.util.regex.Macher
简单案例:
String a = "13567845635";
//判断两个字符串是否完全一致
System.out.println(a.equals("13567845634"));
//判断当前String对象是否匹配给定的正则表达式( 匹配手机号格式字符串)
System.out.println(a.matches("^1\\d{10}$"));
匹配元字符
元字符 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
//.用于匹配出回车换行之外的其他任意单个字符(数字,字母,符号)
System.out.println("!".matches(".")); //true
//匹配所有的数字,字母,不支持符号
System.out.println("a".matches("\\w")); //true
//匹配除了数字,字母外的其他符号
System.out.println("\t".matches("\\W"));//true
//匹配空格
System.out.println(" ".matches("\\s"));//true
//匹配非空格
System.out.println(" ".matches("\\S"));//false
//匹配数字
System.out.println("0".matches("\\d"));//true
//匹配非数字
System.out.println("a".matches("\\D"));//true
//边界匹配,匹配是否以指定字符开头
System.out.println("abc".matches("\\babc")); //true
System.out.println("a".matches("^a")); //true
//边界匹配,匹配是否以指定的字符结尾
System.out.println("d".matches("d$")); //false
匹配长度
语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
//匹配输入长度为4的数字要求必须以1开头
System.out.println("1234".matches("^1\\d{3}"));
//验证输入的是否是qq号 5-11位长度数字,不能以0开始
System.out.println("12334352324".matches("^[1-9]\\d{4,10}"));
// \\w
System.out.println("a".matches("[0-9a-zA-Z]"));
//匹配不能少于5位长度的数字
System.out.println("12387912".matches("\\d{5,}"));
//匹配不能超过6位长度的数字
System.out.println("12346".matches("\\d{0,6}"));
//匹配字母0个或多个
System.out.println("".matches("[a-zA-Z]*"));
//匹配字母1个或多个
System.out.println("a".matches("[a-zA-Z]+"));
//匹配字母0个或1个
System.out.println("ac".matches("[a-zA-Z]?"));
Pattern&Matcher
String content = "adfasdffdiu1353457839417812341023u901223423418712312317823sdfsd";
//手机号正则表达式
String regex = "1\\d{10}";
//编译正则表达式获取匹配模式对象
Pattern p = Pattern.compile(regex);
//对指定的输入内容进行匹配并且获取匹配器
Matcher m = p.matcher(content);
//直接匹配
//System.out.println(m.matches());
//搜索是否存在匹配的组
while(m.find()) {
//取出当前匹配到的组
String s = m.group();
System.out.println(s);
正则表达式在线测试网站
- https://regexr.com/
- https://regex101.com/
四、常用类(综合实践)
1、微信红包(拼手气红包)
基于BigDecimal类实现微信红包算法的功能,比如设置红包总金额,然后设置需要生成的红包个数,为每个红包随机指定金额,最低不能低于0.01元,要求:
- 每个红包金额随机指定
- 每个红包金额不能低于0.01元
- 要求每个红包的金额之和恰好等于总金额
- 如果平均每个红包的金额不足0.01元时抛出一个RedPacketException,提示每个红包金额不能少于0.01元
红包类
/**
* 红包类
* @author mrchai
*
*/
public class RedPacket {
/**红包ID*/
private int id;
/**红包金额*/
private BigDecimal money;
public RedPacket() {
}
public RedPacket(int id, BigDecimal money) {
super();
this.id = id;
this.money = money;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public BigDecimal getMoney() {
return money;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
@Override
public String toString() {
return id+"号用户获得"+money+"元";
}
}
红包异常类
/**
* 红包异常
* @author mrchai
*/
public class RedpacketException extends Exception{
public RedpacketException() {
// TODO Auto-generated constructor stub
}
public RedpacketException(String msg) {
super(msg);
}
}
红包管理
public class RedPacketManage {
/** 设置每个红包最小金额 */
static final BigDecimal MIN = new BigDecimal("0.01");
/*
* @double total 总金额
* @int count 红包个数
* @return 返回生成的所有红包金额集合
*/
public static ArrayList<RedPacket> genRedPacket(double total, int count) throws RedpacketException {
// 声明临时变量用于存储所有随机的红包对象
ArrayList<RedPacket> packets = new ArrayList<RedPacket>();
// 计算每个红包分配最低金额一共需要多少钱
double min = MIN.multiply(new BigDecimal(count)).setScale(2, BigDecimal.ROUND_HALF_EVEN).doubleValue();
if (min > total) {
// 红包金额不够分配时,抛出异常
throw new RedpacketException("每个红包金额不能少于0.01元");
} else if (min == total) {
// 红包金额恰好每人只够分配0.01元,则平均分配
for (int i = 0; i < count; i++) {
// 创建红包对象
RedPacket item = new RedPacket(i + 1, new BigDecimal("0.01"));
// 将红包加入集合
packets.add(item);
}
} else {
// 当总金额大于每人最少金额之和时,随机分配
// 将总金额包装为BigDecimal
BigDecimal totalMoney = new BigDecimal(total);
//声明临时变量统计当前分配的金额总数
BigDecimal now = new BigDecimal(0);
// 获取每个红包的比例
double[] scale = randomScale(count);
// 为前count-1个红包分配金额
for (int i = 0; i < count - 1; i++) {
// 获取当前比例红包需要分配的金额
BigDecimal item = totalMoney.multiply(new BigDecimal(scale[i]))
.setScale(2, BigDecimal.ROUND_HALF_EVEN);
packets.add(new RedPacket(i + 1, item));
//累计已分配金额总数
now = now.add(item);
}
// 剩余的金额给最后一个
//获取剩余的金额
BigDecimal last = totalMoney.subtract(now);
packets.add(new RedPacket(count, last));
}
return packets;
}
/**
* 随机红包金额比例
* @param count 红包的份数
* @return 每份红包的比例数组
*/
private static double[] randomScale(int count) {
// 临时数组存储所有红包的金额比例
double[] scale = new double[count];
Random r = new Random();
double total = 0.0;
for (int i = 0; i < count; i++) {
// 为每一个元素设置一个1-100随机数
scale[i] = r.nextInt(100) + 1;
// 累计所有随机的数值
total += scale[i];
}
// 循环计算每个红包的金额比例
for (int i = 0; i < count; i++) {
scale[i] = scale[i] / total;
}
return scale;
}
}
测试类
p ublic static void main(String[] args) throws RedpacketException {
ArrayList<RedPacket> list = genRedPacket(5, 10);
BigDecimal t = new BigDecimal(0);
for (RedPacket rp : list) {
System.out.println(rp);
t= t.add(rp.getMoney());
}
System.out.println(t);
}
运行结果
1号用户获得0.70元
2号用户获得0.13元
3号用户获得0.46元
4号用户获得0.50元
5号用户获得0.59元
6号用户获得0.92元
7号用户获得0.02元
8号用户获得0.11元
9号用户获得0.65元
10号用户获得0.92元
5.00
2、斗地主发牌
参考斗地主的游戏规则,完成一个发牌的功能(54张牌,考虑点数,花色;三名玩家,其中地主比其他玩家多3张牌)
牌类
/**
* 3-10 J Q K A 2 Queen King 牌类
*
* @author mrchai
*/
public class Card {
/** 牌面值 */
private String name;
/** 花色 */
private String flower;
/** 大小点数 */
private int num;
public Card() {
}
public Card(String name, String flower, int num) {
super();
this.name = name;
this.flower = flower;
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFlower() {
return flower;
}
public void setFlower(String flower) {
this.flower = flower;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
if (Objects.nonNull(flower)) {
return name + "-" + num + "-" + flower;
}
return name + "-" + num;
}
}
玩家类
/**
* 玩家类
* @author mrchai
*/
public class Player {
/**玩家id*/
private int id;
/**玩家姓名*/
private String name;
/**是否地主*/
private boolean boss;
/**牌集合*/
private ArrayList<Card> cards=new ArrayList<Card>();
public Player() {
}
public Player(int id, String name, boolean boss, ArrayList<Card> cards) {
super();
this.id = id;
this.name = name;
this.boss = boss;
this.cards = cards;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isBoss() {
return boss;
}
public void setBoss(boolean boss) {
this.boss = boss;
}
public ArrayList<Card> getCards() {
return cards;
}
public void setCards(ArrayList<Card> cards) {
this.cards = cards;
}
@Override
public String toString() {
return name+(boss?"(地主)":"")+":"+cards;
}
}
游戏管理类
public class GameManage {
/**声明所有牌的集合*/
private static ArrayList<Card> all = new ArrayList<>();
/**用于生成牌的牌面值*/
private static String[] names = {"3","4","5","6","7","8","9","10","J","Q","K","A","2","Queen","King"};
/**用于生成牌的花色*/
private static String[] flowers = {"红桃","方块","梅花","黑桃"};
/**所有玩家集合*/
private ArrayList<Player> players = new ArrayList<Player>();
/**声明随机数生成器*/
private static Random randomGen = new Random();
static {
/******初始化所有牌******/
//笛卡尔积
int i = 0;
for (; i < names.length-2; i++) {
for (int j = 0; j < flowers.length; j++) {
Card c = new Card(names[i], flowers[j], i);
all.add(c);
}
}
//将大小王加入
all.add(new Card(names[names.length-2],null,i++));
all.add(new Card(names[names.length-1],null,i++));
}
/**
* 添加玩家
*/
public void addPlayer() {
Scanner sc = new Scanner(System.in);
System.out.println("请输入玩家1名称:");
String name1 = sc.nextLine();
System.out.println("请输入玩家2名称:");
String name2 = sc.nextLine();
System.out.println("请输入玩家3名称:");
String name3 = sc.nextLine();
Player p1 = new Player();
p1.setId(1);
p1.setName(name1);
Player p2 = new Player();
p2.setId(2);
p2.setName(name2);
Player p3 = new Player();
p3.setId(3);
p3.setName(name3);
//将三名玩家加入集合
players.add(p1);
players.add(p2);
players.add(p3);
}
/**
* 随机地主
*/
public void randomBoss() {
//添加玩家
addPlayer();
//随机地主索引
int i = randomGen.nextInt(players.size());
//设置指定位置的玩家为地主
players.get(i).setBoss(true);
}
/**
* 发牌
*/
public ArrayList<Player> sendCard() {
//随机地主
randomBoss();
//对每一名玩家遍历
for (Player p : players) {
//先为每一位玩家随机发17张牌
for (int i = 0; i < 17; i++) {
//随机一张牌的索引
int cardIndex = randomGen.nextInt(all.size());
//获取随机索引位置的牌
Card card = all.get(cardIndex);
//将随机的牌加入当前遍历玩家的集合
p.getCards().add(card);
//从源集合中移除这张牌
all.remove(card);
}
}
//最后三张牌给地主
for (Player p : players) {
if(p.isBoss()) {
//将all集合中的所有元素加入地主的集合
p.getCards().addAll(all);
}
}
return players;
}
}
测试类
public static void main(String[] args) {
ArrayList<Player> players = new GameManage().sendCard();
for (Player p : players) {
System.out.println(p);
}
}
运行结果
请输入玩家1名称:
张三
请输入玩家2名称:
李四
请输入玩家3名称:
王五
张三:[A-11-梅花, 4-1-梅花, 9-6-红桃, 8-5-梅花, A-11-方块, 8-5-黑桃, 6-3-红桃, 6-3-方块, K-10-方块, Queen-13, 10-7-梅花, K-10-梅花, Q-9-梅花, 2-12-红桃, Q-9-红桃, 6-3-梅花, 10-7-黑桃]
李四:[5-2-黑桃, 9-6-黑桃, A-11-黑桃, 4-1-红桃, 5-2-红桃, 3-0-红桃, 5-2-方块, A-11-红桃, 6-3-黑桃, 5-2-梅花, 7-4-红桃, K-10-黑桃, 8-5-方块, 4-1-黑桃, 3-0-方块, Q-9-方块, 8-5-红桃]
王五(地主):[10-7-方块, 2-12-黑桃, 7-4-梅花, 2-12-方块, 2-12-梅花, 4-1-方块, 7-4-黑桃, Q-9-黑桃, J-8-梅花, 7-4-方块, 3-0-梅花, J-8-方块, 3-0-黑桃, J-8-黑桃, 10-7-红桃, J-8-红桃, King-14, 9-6-方块, 9-6-梅花, K-10-红桃]
本文地址:https://blog.csdn.net/qq_44298719/article/details/109895912
上一篇: 我常用java 8 的特性
下一篇: 维持友谊的方式