day12
包装类
Java号称纯面向对象语言,除了基本8种基数据类型,其他的都是类。每一个种类型都有对应的包装类型。
byte -> Byte
short -> Short
int -> Integer
long -> Long
float -> Float
double -> Double
char -> Character
boolean -> Boolean
装箱和拆箱
包装类型封装了一些常用操作,比如从字符串解析到对应的类型可以通过包装类.parse类型
进行转换。
int i = Integer.parseInt("123");
byte b = Byte.parseByte("1");
long l = Long.parseLong("222");
....
还可以通过包装类.ValueOf
来创建一个包装类型对象。
Integer i = Integer.valueOf(123);
Float f = Float.valueOf(123.00);
从JDK1.5开始支持简写,上面这种写法,简写之后为:
Integer i = 123;
Float f = 2.00;
Java会自动做valueOf
来转换为对应的对象,这个过程称之为装箱
;反之通过包装类型转换为基本类型称为拆箱
。
Integer i = 123;
int i1 = i.intValue();
从包装类转换为基本数据类型可以通过数据类型Value()
来实现,同样的,上面这种写法也可以简写。
Integer i = 123;
int i1 = i;
Integer类
Integer
类内部有一个缓存器,当通过常量
或者valueOf()
赋值给一个Integer
对象时候,这时候会现在缓存器中查找常量
或者valueOf()
传递的参数是否存在于缓存中(缓存的范围是-128-127即byte的长度),如果存在的话将直接返回。否则通过new Integer()
返回新的引用。
Integer integer2 = new Integer(3);
Integer integer3 = new Integer(3);
// false
System.out.println(integer2 == integer3);
// true
System.out.println(integer2.equals(integer3));
// 缓存器 -128-127 如果存在返回引用
// 否则返回new Integer(value)
Integer integer = 1;
Integer integer1 = 1;
Integer integer4 = 128;
Integer integer5 = 128;
// true
System.out.println(integer == integer1);
// true
System.out.println(integer.equals(integer1));
// false
System.out.println(integer4 == integer5);
// false
System.out.println(integer4.equals(integer5));
String
String
类型所有常量都是字符串对象,因此可以通过字符串常量调用方法。对于等值比较推荐将字符串常量放在equals
左边,而未确定值的变量作为参数传递。如果是未确定值的变量调用equals
方法,在变量的值为null
的情况下,会抛出NullPointerException
。
在内存中方法区中有一个常量池
,所有的字符串常量都存放在其中,类似于一个缓存器。当有新的字符串常量的时候,如果常量池存存在与新字符串常量的内容相等的情况下,将直接返回常量池存在的内容引用,而不会创建新的字符串常量。
// 由于常量池的原因,两个对象指向同一个常量池引用
String s = "abc";
String s1 = "abc";
// 在堆中创建一个对象,对象的属性存放"abc"由于常量池的存在 三个对象共享同一个引用
String s2 = new String("abc");
// true
System.out.println(s == s1);
// false
System.out.println(s.equals(s1));
// false
System.out.println(s1 == s2);
// true
System.out.println(s1.equals(s2));
上述代码String s2 = new Stirng("abc");
首先在常量池存放内容”abc”,接着在堆中开辟一块空间存放String
对象;最后在栈中通过s2
指向堆中String
对象,总共创建了3个对象,如果有new String("abc")
的情况下,那么会开辟两个对象,因为常量池已经存在字符常量”abc”。
比较判断
在实际业务过程中,我们经常要用到比较判断,对于基本数据类型==
判断的是值,而对于引用数据类型==
判断的是引用地址。Object
类有一个方法equals
用于判断两个引用类型的数据是否相等。
public boolean equals(Object obj){
return (this == obj);
}
Objectequals
方法默认实现是判断两个引用类型的引用是否相等。而有有些场景需要判断引用类型的某个属性相等的情况下返回true
。而String
类重写了Object的equals
实现了属性value
的比较,如果两个String
类型的属性values
值相等,那么会返回true
。
@Override
public boolean equals(Object anObject){
if(this == anObject){
return true;
}
if(anObject instanceof String){
String anotherString = (String)anObject;
int n = value.length;
if(n == anotherString.value.length){
char[] v1 = value;
char[] v2 = anotherString.value;
int i = 0;
while(n-- != 0){
if(v1[i] != v2[i]){
return false;
}
i++;
}
return true;
}
return false;
}
return false;
}
上述代码中,首先判断当前类和anObject
是指向同一个引用,如果指向同一个引用那么属性值必然相等,所以返回true
。然后判断anObject
是不是String
类的实例,如果不是的话就可以返回false
,然后判断当前类的内容与anotherString
的内容长度是否一致,如果不一致那么返回false
。接着循环比较每一个值如果其中一个值不相等那么返回false
,最后在while
语句返回true
。
根据String
重写equals
的源码,我们可以分析出,重写equals
进行属性值比较有以下几个步骤:
- 判断是否为同一个引用
- 判断是否为当前对象的类的实例
- 进行强转
- 进行属性比较
假如有以下类,在所有属性值相等的情况下返回true
,请重写该equals
方法。
public class Student {
private int age;
private String name;
private double score;
private String[] hobby;
@Override
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof Student) {
Student anotherStudent = (Student) anObject;
if (anotherStudent.age != this.age) {
return false;
}
if (!anotherStudent.name.equals(this.name)) {
return false;
}
if (anotherStudent.score != this.score) {
return false;
}
int hobbyArrayLength = this.hobby.length;
if (anotherStudent.hobby.length != hobbyArrayLength) {
return false;
}
String[] currentStudentHobbyArray = this.hobby;
String[] anotherStudentHobbyArray = anotherStudent.hobby;
int i = 0;
while (hobbyArrayLength-- != 0) {
if (!currentStudentHobbyArray[i].equals(anotherStudentHobbyArray[i])) {
return false;
}
i++;
}
}
return false;
}
}
==和equals的区别
==
是比较运算符对于基本数据类型比较的是值,对于引用数据类型比较的是内存地址。而equals
是Object类的方法,默认实现是比较内存地址,像数据类型的类equals重写了equals
,比较的是值。
字符序列
可变与不可变
String
是不可变字符序列,内部value
使用final
修饰。不可变不是指每次修改字符串内容会报错,而是会指向一个新的引用,特别是对于字符串常量拼接,开销很大。
System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
String s = "";
for (int i = 0; i < 100000; i++){
s += "a";
}
System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
System.out.println("-------");
System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++){
sb.append("a");
}
System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
// 2020-12-01 20:02:15
// 2020-12-01 20:02:21
// -------
// 2020-12-01 20:02:21
// 2020-12-01 20:02:21
对于频繁使用字符串拼接且不用考虑线程问题可以使用StringBuilder
,而需要考虑线程问题可以使用StringBuffer
。
由于String
的不可变性,导致很多修改内容的方法如:拼接,裁剪(concat、subString)等都是返回一个新的字符串。而对于可变字符串很多修改内容不会返回新的字符串而是在本身的内容上修改。
String,StringBuffer,StringBuilder的区别
String
是不可变字符序列,StringBuffer
和StringBuilder
是不可变字符序列StringBuffer
是线程安全的,StringBuilder
是非线程安全的
常用类
Date SimpleDateFormat
Date表示日期,有如下几个public修饰的构造方法
public Date(){
this(System.currentTimeMillis);
}
public Date(long date){
fastTime = date;
}
这两个构造方法有一个重要参数:date
表示从标志时间到现在的毫秒数,会加上时区。而默认的构造方法会返回当前的日期。
如果日期格式看着不习惯可以使用SimpleDateFormat
。
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
Date now = new Date();
System.out.println(sdf.format(now));
具体差异请查看API。
BigDecimal
由于double
运算精度的问题,可以使用BigDecimal
,创建两个对象,然后调用方法进行运算。BigDecimal
只支持字符串类型。
Objects
Arrays
正则表达式
自己百度