1.包装类
1.1 包装类简介
java语言是面向对象的语言,但是其中的八大基本数据类型不符合面向对象的特征。
因此java为了弥补这样的缺点,为这八种基本数据类型专门设计了八种符合面向对象特征的的类型,这八种具有面向对象特征的类型,统称为包装类,英文单词:wrapper class。
包装类,就是在类的内部,维护了一个基本数据类型的成员变量,以及其他方法,常量等。
比如int对应的包装类Integer的部分源码如下:
public final class Integer extends Number implements Comparable<Integer> {
public static final int MIN_VALUE = 0x80000000;
public static final int MAX_VALUE = 0x7fffffff;
private final int value;
public Integer(int value) {this.value = value;}
public Integer(String s) throws NumberFormatException {this.value = parseInt(s, 10);}
public static String toHexString(int i) {}
public static String toOctalString(int i) {}
public static String toBinaryString(int i) {}
public static int parseInt(String s) throws NumberFormatException {}
public static Integer valueOf(String s) throws NumberFormatException {}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int compareTo(Integer anotherInteger) {}
public static int max(int a, int b) {
return Math.max(a, b);
}
public static int min(int a, int b) {
return Math.min(a, b);
}
//.....
}
所有的基本数据类型和包装类型的对比,参考如下:
基本数据类型 | 包装类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
只有int和char类型的包装类特殊。
1.2 包装类的学习(拆箱装箱)
包装类的学习:
1.因为基本数据类型,没有面相对象的特征,因此java专门为这八个类型,设计了具有面向对象特征的类。
实际上就是对基本数据类型做的类的封装,即里面有一个基本数据类型的成员变量+各种方法,常量等。
2. 除了int,char。剩下的六个基本数据类型的包装类名,都是基本数据类型名的首字母大写形式
int-->Integer
char-->Character
3. 装箱和拆箱
装箱: 调用包装类的构造器或者value0f(),获取一个包装类对象。
拆箱: 调用包装类里的xxxValue(),返回一个基本数据类型。
代码测试:
public class WrapperDemo01 {
public static void main(String[] args) {
//装箱,获取一个包装类对象。思考:num里存储的是什么? 存储的是对象的地址, int a = 1; a存的是值。
Integer num = new Integer(1);
Integer num2 = Integer.valueOf(1);
System.out.println(num==num2);//false 是两个不同的对象,只不过对象的成员变量的值一样。
System.out.println(num.equals(num2));//true 值相等
//拆箱:其他类型的包装类,想要拆箱,就调用自己的xxxValue()方法。
int i =num.intValue();
// 形参是包装类,因此可以赋值为null,编译器不会报错。运行时报错,空指针异常。
//Integer result = calcualte(null,num2);
//System.out.println("result="+result);
}
//定义方法,形参是包装类型
public static Integer calcualte(Integer num,Integer num2) {
return num +num2;
}
}
1.3 自动拆箱装箱
包装类的自动拆装箱:
1.从jdk1.5开始,引入了这两个操作
2.自动装箱:
将基本数据类型的字面值或变量赋值给包装类的变量。
本质:编译器在编译期间调用了类的value0f方法进行了包装
3.自动拆箱:
将包装类对象的地址赋值给基本数据类型的变量。
本质:编译器在编译期间调用了类的xxxValue方法进行了拆箱。
案例代码:
public class WrapperDemo02 {
public static void main(String[] args) {
Integer num = 1;//发生了自动装箱,底层调用了Integer.valueOf(1)方法进行包装。
int a = 10;
Integer b = a;//发生了自动装箱
int x = num;//自动拆箱,底层隐含了num.intValue();
int y = new Integer(100);
}
}
1.4 包装的常量池
对于装箱操作后的包装类的对象,jvm在堆中,维护了一个常量池,该常量池适用于调用了valueOf()方法产生的包装类对象,以及自动装箱的包装类对象(默认调用的valueOf方法)。不适用于new关键字创建的包装类对象。
各类型常量池的范围:
Byte, Short, Integer, Long 这 4 种包装类默认创建了数值 [-128, 127] 的相应类型的缓存数据,
Character 创建了数值在 [0, 127] 范围的缓存数据。
Boolean 直接返回 true 或 false。两种浮点数类型的包装类 Float, Double 并没有实现常量池技术。没有常量池。
代码案例:
public class WrapperDemo03 {
public static void main(String[] args) {
//检验常量池
Integer n1 = 10;
Integer n2 = 10;
System.out.println(n1==n2);//true
Integer n3 = Integer.valueOf(10);
System.out.println(n3==n2);//true
Integer n4 = new Integer(10);
System.out.println(n4==n2);//false
//范围
Integer n5 = 128;
Integer n6 = 128;
System.out.println(n5==n6);//false;超出了常量池的范围,所以是两个新对象。
Boolean b1 = true;
Boolean b2 = Boolean.valueOf(true);//true
Boolean b3 = Boolean.TRUE;//true
Boolean b4 = new Boolean(true);//false
System.out.println(b1==b2);//true
System.out.println(b1==b3);//true
System.out.println(b1==b4);//false
Character c1 = 'a';
Character c2 = 'a';
System.out.println(c1==c2);
}
}
1.5 包装类的其他方法或常量
public class WrapperDemo04 {
public static void main(String[] args) {
System.out.println(Short.MIN_VALUE);
System.out.println(Integer.MAX_VALUE);
//找出两个数中的较大值
System.out.println(Integer.max(10,20));
System.out.println(Integer.min(10,20));
/**
* static int parseInt(String str) throws NumberFormatException
* 1.静态方法
* 2.作用:将字符串类型的值转成int类型的值
* 3.该方法声明了一个运行时异常,数字格式异常类型,当传入的值不是纯数字型字符串。
*/
String str ="10";
int i = Integer.parseInt(str);
System.out.println(i);
int n2 = 10;
System.out.println(Integer.toBinaryString(n2));
System.out.println(Integer.toHexString(n2));
System.out.println(Integer.toOctalString(n2));
}
}
上述代码中会产生一个新的异常类型:
数字格式异常(NumberFormatException): 即将字符串变成数字时,字符串中有非数字字符。
Integer.parseInt(); 转成int 类型
Integer.toBinaryString(); 转成二进制
Integer.toHexString(); 转成16进制
Integer.toOctalString() 转成8进制
public static int parseInt(String str):
public static Integer valueOf(String str):
上述两个方法都用Integer类名调用,
作用相似:都是将字符串转成整形,一个是转成int类型,一个是转成Integer类型
2. BigDecimal
2.1 BigDecimal
因为使用八大基本数据类型做运算时,尤其是浮点数的运算时,容易精度不准确,所以java语言提供了BigDecimal这个类来完善这类运算,可以非常精确,可以精确到小数点后无数位。BigDecimal 通常支持任意位数的小数部分,用来对超过16位有效位的数进行精确的运算,(float可以精确到6-7左右,double是15,6位左右)。
当运算时,尤其是对浮点数类型的数据做超过15,6位的精确运算时,建议使用BigDecimal类型
1.常用的构造器
BigDecimal(int i)
BigDecimal(double i) 该构造器不推荐使用,因为double类型在传入时,就有可能发生了精度损失问题。
BigDecimal(long i)
BigDecimal(String i): 如果想要使用浮点数,建议使用形参时字符串的构造器
2.加减乘除运算
除数不能为0,对象也不能为null。还要注意除不尽的情况。
- add 加法
- subtract 减法
- multiple 乘法
- divide 除法
- abs() 将BigDecimal对象中的值转换成绝对值
- doubleValue()
- floatValue()
- longValue()
- intValue() 上述四个方法 将BigDecimal对象中的值转换成相应的基本数据类型
- compareTo(BigDecimal val) 比较大小,返回int类型。0(相等) 1(大于) -1(小于)
- toString() 有必要时使用科学计数法。
代码测试:
public class bigDecimalDemo01 {
public static void main(String[] args) {
BigDecimal b1 = new BigDecimal(3);
BigDecimal b2 = new BigDecimal(1.5);
BigDecimal r1 = b1.add(b2);
BigDecimal r2 = b1.subtract(b2);
BigDecimal r3 = b1.multiply(b2);
BigDecimal r4 = b1.divide(b2);
System.out.println("r1 = " + r1);
System.out.println("r2 = " + r2);
System.out.println("r3 = " + r3);
System.out.println("r4 = " + r4);
}
}
代码测试2:
import java.math.BigDecimal;
public class BigeDecimalDemoTest1 {
public static void main(String[] args) {
BigDecimal bg1 = new BigDecimal("2563");
BigDecimal bg2 = new BigDecimal("25");
BigDecimal bg3 = bg1.add(bg2);
BigDecimal bg4 = bg1.subtract(bg2);
BigDecimal bg5 = bg1.multiply(bg2);
BigDecimal bg6 = bg1.divide(bg2, 2, 2);
int a = bg1.intValue();
int b = bg1.compareTo(bg3);
System.out.println(bg3.toString());
}
}
代码测试3:
public class BigDecimalDemo02 {
public static void main(String[] args) {
BigDecimal bg1 = new BigDecimal("3");
BigDecimal bg2 = new BigDecimal("2.9");
System.out.println(bg1.subtract(bg2));
try{
System.out.println(bg1.divide(bg2));
}catch (Exception e){
e.printStackTrace();
}
System.out.println(bg1.divide(bg2, 20, BigDecimal.ROUND_HALF_UP));
}
}
注意,在做除法运算时,除不尽就是会发生异常。算术异常(ArithmeticException),运行时异常。因此,一般在做除法运算时,都会进行try-catch处理,以免影响程序的运行。可以在后面添加精度和舍入模式。让程序输出固定小数位的结果。
2.2 舍入模式
- BigDecimal.setScale()方法用于格式化小数点
- setScale(1)表示保留一位小数,默认用四舍五入方式
- setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3
- setScale(1,BigDecimal.ROUND_UP)进位处理,2.33变成2.4
- setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4
- setScaler(1,BigDecimal.ROUND_HALF_DOWN)五舍六入,2.35变成2.3,如果是5则向下舍。
- setScaler(1,BigDecimal.ROUND_CEILING) 接近正无穷大的舍入
- setScaler(1,BigDecimal.ROUND_FLOOR)接近负无穷大的舍入,数字>0和ROUND_DOWN作用一样,数字<0和ROUND_UP作用一样。
- setScaler(1,BigDecimal.ROUND_HALF_EVEN)向最接近的数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
ROUND_HALF_EVEN,向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。
注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。
此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。如果前一位为奇数,则入位,否则舍去。以下例子为保留小数点1位,那么这种舍入方式下的结果。
eg. 1.15->1.2, 1.25->1.2.
2.3 BigDecimal常见问题
1) 坑一:创建 BigDecimal精度丢失的坑
在BigDecimal 中提供了多种创建方式,可以通过new 直接创建,也可以通过 BigDecimal.valueOf 创建。如果这两种方式使用不当,也会导致精度问题。如下:
public static void main(String[] args) throws Exception {
BigDecimal b1= new BigDecimal(0.1);
System.out.println(b1);
BigDecimal b2= BigDecimal.valueOf(0.1);
System.out.println(b2);
BigDecimal b3= BigDecimal.valueOf(0.111111111111111111111111111234);
System.out.println(b3);
}
执行结果
0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111
上面示例中两个方法都传入了double类型的参数0.1但是 b1 还是出现了精度的问题。造成这种问题的原因是 0.1 这个数字计算机是无法精确表示的,送给 BigDecimal 的时候就已经丢精度了,而 BigDecimal.valueOf 的实现却完全不同。如下源码所示,BigDecimal.valueOf 中是把浮点数转换成了字符串来构造的BigDecimal,因此避免了问题。
public static BigDecimal valueOf(double val) {
return new BigDecimal(Double.toString(val));
}
结论:
第一,在使用BigDecimal构造函数时,尽量传递字符串而非浮点类型; 第二,如果无法满足第一条,则可采用BigDecimal.valueOf方法来构造初始化值。但是valueOf受double类型精度影响,当传入参数小数点后的位数超过double允许的16位精度还是可能会出现问题的。
2)坑二:等值比较的坑
一般在比较两个值是否相等时,都是用equals 方法,但是,在BigDecimal 中使用equals可能会导致结果错误,BigDecimal 中提供了 compareTo 方法,在很多时候需要使用compareTo 比较两个值。如下所示:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("1.00");
System.out.println(b1.equals(b2));
System.out.println(b1.compareTo(b2));
}
执行结果:
false
0
出现此种结果的原因是,equals不仅比较了值是否相等,还比较了精度是否相同。示例中,由于两个值的精度不同,所有结果也就不相同。而 compareTo 是只比较值的大小。返回的值为-1(小于),0(等于),1(大于)。
3) 坑三:无限精度的坑
BigDecimal 并不代表无限精度,当在两个数除不尽的时候,就会出现无限精度的坑,如下所示:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
b1.divide(b2);
}
异常:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1693)
at com.demo.controller.Test.main(Test.java:29)
大致意思就是,如果在除法(divide)运算过程中,如果商是一个无限小数(如 0.333…),而操作的结果预期是一个精确的数字,那么将会抛出ArithmeticException异常。
此种情况,只需要在使用 divide方法时指定结果的精度即可:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
System.out.println(b1.divide(b2,2, RoundingMode.HALF_UP));//0.33
}
3. Date日期类型与SimpleDateFormat类型
3.1 简介
是一个用来表示时间、日期的类,这个类在 java.util 包中,在使用 Date的时候,一定要注意 : 在 java.sql 包中,也有一个Date,千万不要导错包了。
java中的时间使用标准类库的Date类型来表示,是用距离某一个固定时间点的毫秒数表达的一个特定的时间点:
固定时间点:1970年1月1日00:00:00
UTC(Universal Time Coordinated世界调整时间)与GMT(Greenwich Mean Time格林威治时间)一样,是一种具有实际目的的科学标准时间。
Date类的大多数用于进行时间分量计算的方法已经被Calendar取代。
3.2 Date的学习
Date类型的学习:
1.java.util包下的类
2.用于日期、时间的描述。
3.实际上是距离一个固定时间点1970年1月1日00:00:00的毫秒数
4.我们常用的是格林威治时间:GMT。 UTC:世界调整时间
5.固定时间点:说的其实是本初子午线的时间。因此北京时间:1970年1月1日8:00:00
构造器:
Date(): 获取的是当前系统的时间点
Date(long time) : 获取距离纪元时间点毫秒数的时间点
方法:
long getTime():获取日期时间对象的毫秒数,距离纪元。
void setTime(long time):设置距离纪元的毫秒数
public class DateDemo01 {
public static void main(String[] args) {
/**
* 常用构造器
* 1.Date(): 获取的是当前系统的时间点
* 2.Date(long time) : 获取距离纪元时间点毫秒数的时间点
*
* CST:中国标准时间:China Standard Time UT+8:00
* 可以理解为东八区的时间
*/
Date now = new Date();
//Fri Aug 02 15:53:06 CST 2024
//星期 月份 日 时 分 秒 标准时间 年
System.out.println(now);
//格林威治时间是凌晨零点时,北京时间是早八点
Date t1 = new Date(1000*60*60*24);
//打印的是北京时间
System.out.println(t1);
/**
* long getTime():获取日期时间对象的毫秒数,距离纪元。
* void setTime(long time):设置距离纪元的毫秒数
*/
long time = now.getTime();
System.out.println("当前时间距离纪元的毫秒数"+time);
now.setTime(1000*60*60*12);
System.out.println(now);
}
}
3.3 SimpleDateFormat的学习
构造器:
SimpleDateFormat()
SimpleDateFormat(String pattern):指定一个日期格式符号来构造对象
方法:
Date的toString方法转成的字符串不是我们想要的格式。因此需要使用SimpleDateFormat类型来自定义格式
--format(Date date) : 格式化成我们需要的字符串形式
另外一个功能就是可以将一个字符串按照指定格式转成我们需要的日期类型。
--parse(String str) : 解析转变。将字符串类型转成日期类型
代码:
public class DateDemo02 {
public static void main(String[] args) throws ParseException {
Date now = new Date();
//使用SimpleDateFormat来定义你需要的格式
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy MM dd HH mm ss E a ");
//调用格式化方法,转成字符串
String format = sdf.format(now);
System.out.println(format);
/**
* 练习: 将字符串装成日期。
* 将自己的出生年月日转成对应的日期类型。,然后获得距离纪元的北京早八点的毫秒数据
*/
String birthday = "2002-10-11 22:11:30";
// 注意,一旦字符串的格式确定了,那么构造器的时间格式必须和字符串一致。否则报异常
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date birth = sdf.parse(birthday);
System.out.println("birthday:" + birth);
//获取距离纪元时间点北京早八点的毫秒
long t1 = birth.getTime();
System.out.println(t1);
}
}
SimpleDateFormat格式:
字符 | 含义 | 案例 |
---|---|---|
y | 年 | yyyy年—2018年;yy-18年 |
M | 月 | MM月—05月;M月—5月 |
d | 日 | dd日—06日;d日—6日 |
E | 星期 | E-星期日(Sun) |
a | 上下午(AM、PM) | a—下午(PM) |
H | 24小时制 | a h时--------下午 10时HH:mm:ss------12:21:34hh(a):mm:ss------12(PM):21:34 |
h | 12小时制 | |
m | 分钟 | |
s | 秒 |
4. Calendar 类型
4.1 简介
Calendar: 日历,是用来表示时间、日期的类。在Date类中,有很多的方法都已经废弃了,使用 Caneldar中的某些方法进行实现。
java.util.Calendar类用于封装日历信息,其主要作用在于其方法可以对时间分量进行运算。
Calendar是抽象类型,其具体子类是针对不同国家的日历系统,其中应用最广泛的是GregorianCalendar(格里高利历,即通用的阳历),对应世界上绝大多数国家/地区使用的标准日历系统。
4.2 Calendar的学习
Calendar: 日历类型,封装了日历信息,以及提供了各个时间分量的方法和常量。
1.是一个抽象类,提供了工厂方法 getInstance() 获取最常用的子类格力高日历对象
2. 常量:
static int YEAR指定年份的字段数字
static int MONTH指定月份的字段数字,0为一月
static int DATE 指示一个月份中的第几天
static int DAY_0F_WEEK 指定一个星期中的某天,1为星期日
static int WEEK_OF_MONTH 指定一个月中的第几周
static int WEEK OF YEAR 指定一年中的第几周
3. get/set方法配合常量,就可以对时间分量进行作4. Date getTime(Calendar cal) ;
5.getActualMaximum方法
int getActualMaximum(int field)
作用:指定一个时间常量,返回指定日历分量可能拥有的最大值。
6.add方法
void add(int field , int mount)
作用:指定一个时间常量,在此时间分量上增加指定的数值,若为负值,则是减去指定的数值。
代码测试:
public class CalendarDemo01 {
public static void main(String[] args) {
//获取日历对象
Calendar cal = Calendar.getInstance();
System.out.println(cal);
/**
* 调用get方法,获取时间日期分量
*
*/
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH)+1;//日历的月份是从0~11,因此我们使用月份的时候,别忘记+1操作。
int day = cal.get(Calendar.DAY_OF_MONTH);
// int day2 = cal.get(Calendar.DATE);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
System.out.println("Year: " + year);
System.out.println("Month: " + month);
System.out.println("Day: " + day);
// System.out.println("Day 2: " + day2);
System.out.println("Hour: " + hour);
System.out.println("Minute: " + minute);
System.out.println("Second: " + second);
// System.out.println("上下午: "+cal.get(Calendar.AM_PM));
// 日历默认是 星期日为1 因此在使用时要-1;
System.out.println("星期: + "+(cal.get(Calendar.DAY_OF_WEEK)));
System.out.println("今天是今年的多少天: "+cal.get(Calendar.DAY_OF_YEAR)+"天");
/**
* set方法的测试:也要配合常量
*/
Calendar cal2 = Calendar.getInstance();
cal2.set(Calendar.YEAR, 2088);
//因为月份是从0~11,所以在设置值时,要进行-1操作。
cal2.set(Calendar.MONTH, 8);
cal2.set(Calendar.DAY_OF_MONTH, 10);
cal2.set(Calendar.HOUR_OF_DAY, 11);
cal2.set(Calendar.MINUTE, 8);
cal2.set(Calendar.SECOND, 8);
System.out.println(cal2);
Date date =cal2.getTime();//转换成日期类型
System.out.println(date);
}
}
练习:获取2024年每月的天数
public class CalendarDemo02 {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
for (int i = 0; i < 12; i++) {
//将日历对象设置成相对的月份
cal.set(Calendar.MONTH,i);
//获取月份对应的最大天数。
int days=cal.getActualMaximum(Calendar.DAY_OF_MONTH);
System.out.println((i+1)+"月有"+days+"天");
}
}
}