JavaSE基础——常用类1
一、Object类
java.lang.Object
类是Java语言中的根类,即所有类的父类。它当中描述的所有方法子类都可以使用。
如果在类的声明中没有显式地使用extends
来指明父类,则该类默认的父类就是java.lang.Object
。
根据API文档,Object类中包含的方法有11个:
-
toString()方法、equals()方法
:详细说明 -
hashCode()方法
:返回对象的哈希码值。(集合) -
clone()方法
:创建并返回对象的副本。 -
getclass()方法
:返回对象所在的类。
- ``getclass().getSuperclass()方法`:返回对象所在的父类
-
finalize()方法
:当垃圾收集确定不再有对该对象的引用时,垃圾收集器在对象上调用该方法。- 垃圾回收机制只回收
JVM堆内存
里的对象空间。对其他类如数据库连接、输入输出流、Socket连接等无能为力。 - 垃圾回收具有不可预知性,程序无法精准控制垃圾回收机制的执行。程序员可以通过
System.gc()
或者Runtime.getRuntime.gc()
来通知系统进行垃圾回收,但系统什么时候进行不得而知 - 垃圾回收机制回收任何对象前,总会让对象调用自身的
finalize()方法
。 - 可以将对象的引用变量设为null,来表明需要垃圾回收机制回收该对象。
- 垃圾回收机制只回收
-
notify()方法、 notifyAll()方法、 wait()方法(3个):
(线程)
下面学习几个主要的方法:
1. equals方法
1.1 ==运算符回顾
==
是运算符,可以使用在基本数据类型和引用类型的比较- 如果比较的是基本数据类型,则比较这两个变量保存的值是否相等,此时与数据类型无关。比如:
char类型
的10和int类型
的10是相等的,因为他们的值相等。 - 如果比较的是引用类型,则比较的是引用的地址值是否相等。即比较两个引用是否指向同一个对象
1.2 equals方法
equals()
是一个方法,只能被对象调用,因此只能用于引用类型的比较- Object类中的
equals()方法
和==运算符
的作用是相同的,即都是比较引用的地址值是否相同,判断是否指向同一个对象 - String类、包装类、Data类、IO流等都重写了Object类中的equals方法,重写后都是比较两个对象的内容是否相同。
// == 与 equals方法
public class Demo01 {
public static void main(String[] args) {
// == 运算符用于数值类型的比较时,比较两个对象存储的值是否相等
//注意不能比较boolean值
int a = 65;
double b= 65.0;
char c = 65;
char d = 'A';
boolean e = true;
int f = 1;
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a == d);
System.out.println(c == d);
//即使数据类型不一致,但由于变量中存储的值时相同的,因此结果都是true
//System.out.println(e == f); 直接报错,== 不能比较boolean类型和其他数据类型
System.out.println("************ == 用于引用类型的比较***********");
// == 用于引用类型的比较时,比较两个引用的地址是否一样,即比较两个引用是否指向同一个对象;
String s = new String("automan");
String s1 = new String("automan");
System.out.println(s == s1);
Man man = new Man("张三",20);
Man man1 = new Man("张三",20);
Man man2 = man;
System.out.println(man == man1);
System.out.println(man == man2);
System.out.println("************ equals方法用于引用类型的比较***********");
//Object类中的equals方法
System.out.println("Object类中的equals:" + man.equals(man1));
//String类中的equals方法
System.out.println("String类中的equals:" + s.equals(s1));
}
}
class Man {
String name;
int age;
public Man() {
}
public Man(String name, int age) {
this.name = name;
this.age = age;
}
}
---------------------------------------------------
运行结果:
true
true
true
true
*********** == 用于引用类型的比较**********
false
false
true
********** equals方法用于引用类型的比较*********
Object类中的equals:false
String类中的equals:true
1.3 equals方法的重写
一般对于自定义的类,也想用equals方法
比较两个对象的内容是否一致,这时就需要重写equals方法
。
快捷键:Alt + insert
生成equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Man man = (Man) o;
return age == man.age &&
Objects.equals(name, man.name);
}
2. toString方法
-
toString方法
默认打印的是对象的虚拟地址值。-
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
-
String类、包装类、Data类、IO流等都重写了Object类中的toString方法,使得调用此类对象的toString方法,返回对象的内容。
-
一般对于自定义的类,也想用
toString方法
比较两个对象的内容是否一致,这时就需要重写toString方法
。可以用快捷键:Alt + insert
生成。一般是返回对象的属性等。
import java.util.Date;
public class Demo01 {
public static void main(String[] args) {
Man man = new Man("张三",20);
System.out.println(man);
System.out.println(man.toString());
System.out.println("***************");
String s = "automan";
System.out.println(s);
System.out.println(s.toString());
System.out.println("***************");
Date date = new Date(456487985695L);
System.out.println(date);
System.out.println(date.toString());
}
}
class Man {
String name;
int age;
public Man() {
}
public Man(String name, int age) {
this.name = name;
this.age = age;
}
}
--------------------------------------------------
运行结果:
com.CommomMethod.www.Man@14ae5a5
com.CommomMethod.www.Man@14ae5a5
***************
automan
automan
***************
Tue Jun 19 18:13:05 CST 1984
Tue Jun 19 18:13:05 CST 1984
//可以看到打印对象名,实际上也就是在调用对象的toString方法。
toString()方法默认返回对象的字符串表示形式,也就是对象的地址,通过重写toString()方法可以让其返回对象的属性等值。
二、 包装类
1. 包装类简介
一般地,当需要使用数字的时候,我们通常使用内置数据类型,如:byte、int、long、double 等。但编程中经常会遇到需要使用对象,而不是内置数据类型的情形。
为了解决这个问题,Java 语言为每一个内置数据类型提供了对应的包装类。这样基本数据类型就也具有了类的特征。所有的包装类**(Integer、Long、Byte、Double、Float、Short)**都是抽象类 Number 的子类。
包装类 | 基本数据类型 |
---|---|
Boolean | boolean |
Byte | byte |
Short | short |
Integer | int |
Long | long |
Character | char |
Float | float |
Double | double |
public class Demo01 {
public static void main(String[] args) {
// 此时自动调用:Integer.valueOf(),若数字大于-128且小于127,直接返回值,否则新建一个对象
Integer integer1 = 1000;
Integer integer2 = 1000;
int i1 = 1000;
int i2 = 1000;
// 因为两个变量不是同一个对象因此结果为false
System.out.println(integer1 == integer2);
System.out.println(integer1.equals(integer2));// true
System.out.println(i1 == i2); // true
System.out.println(i1 == integer2); // true
}
}
---------------------------------------------------
运行结果:
false
true
true
true
//包装类后也具有类的特征了
2. 包装类、基本数据类型、String类的转换
2.1 基本数据类型转为包装类
调用包装类的构造器
public class Demo01 {
public static void main(String[] args) {
//基本数据类型转换为包装类:利用包装类的构造器
int num1 = 10;
//System.out.println(num1.toString()); 报错,因为num1是基本数据类型,不能调用类方法
Integer num2 = new Integer(num1); //将num1的值作为包装类Integer构造方法的参数,转为包装类对象num2
System.out.println(num2.toString()); //包装类中的toString方法也是重写过的,输出对象的内容
Integer num3 = new Integer("123");
System.out.println(num3.toString());
//Integer num4 = new Integer("123abc");
//System.out.println(num4.toString()); 由于数字与字母混用出现格式错误
Float f1 = new Float(12.3f);
System.out.println(f1);
Float f2 = new Float("12.3f");
System.out.println(f2);
//Float f3 = new Float("12.3abc");
//System.out.println(f3); 由于数字与字母混用出现格式错误
Num num = new Num();
System.out.println(num.b1); //当变量是boolean型时,默认值是false
System.out.println(num.b2); //当转换为包装类Boolean时,默认值是null
}
}
class Num {
boolean b1;
Boolean b2;
}
-----------------------------------------
运行结果:
10
123
12.3
12.3
false
null
2.2 包装类转换为基本类型
调用包装类方法Value()
public class Demo01 {
public static void main(String[] args) {
Integer num1 = new Integer(12);
double num2 = num1.doubleValue();
System.out.println(num2 + 1);
}
}
---------------------------------------------------
运行结果:
13.0
13
2.3 自动装箱与自动拆箱
上面学习了包装类与基本数据类型之间的转换,但是在JDK1.5
之后加入了自动装箱与自动拆箱的新特性。
一般情况下,包装类的对象是不能进行数值运算的,但由于自动拆箱的存在,包装类在书写上可以和基本数据类型一样加减乘除。
一般情况下,基本数据类型是不可能转换为一个包装类类型的,要借助包装类的构造方法。但是由于自动装箱的存在,在书写上,也可以直接将一个数值赋给一个对象类型的。
因此,下面的书写也是没有问题的:
public class Demo01 {
public static void main(String[] args) {
int num1 = 10;
Integer num2 = new Integer(20);
Integer num3 = new Integer(30);
//自动拆箱,包装类对象自动转为基本数据类型进行加减乘除
System.out.println(num2 + num3);
//自动封箱,基本数据类型可以赋给包装类对象
Integer num4 = num1;
System.out.println(num4.toString());
}
}
------------------------------------------------
运行结果:
50
10
由于自动装箱与自动拆箱的存在,基本数据类型与包装类的区别就不是那么明显了,书写上可以看作是一个类型。
2.4 基本数据类型、包装类转换为String类
由于自动装箱与自动拆箱的存在,基本数据类型与包装类的区别就不是那么明显了,这里将其看作一个整体实现向String类型
的转换。
使用String类里的valueOf方法
;
2.5 String类转换为基本数据类型、包装类
使用包装类的Paras方法
public class Demo01 {
public static void main(String[] args) {
//基本数据类型、包装类转为String类:valueOf方法
float f1 = 12.3f;
String s1 = String.valueOf(f1);
System.out.println(s1);
Double d1 = new Double(12.5);
String s2 = String.valueOf(d1);
System.out.println(s2);
Boolean b1 = new Boolean(true);
String s3 = String.valueOf(b1);
System.out.println(s3);
System.out.println("*****String类转为基本数据类型、包装类*****");
//String类转换为基本数据类型、包装类
String str1 = "123";
String str2 = "123.5";
//int num1 = (int)str1; 类对象不能强制转换为基本类型
//Integer num2 = (Integer)str1; 类对象要想实现强制转换,转换前后的类必须有父子关系
Integer num1 = Integer.parseInt(str1);
Double num2 = Double.parseDouble(str2);
System.out.println(num1);
System.out.println(num2);
String str3 = "true";
Boolean num3 = Boolean.parseBoolean(str3);
System.out.println(num3);
//对于boolean包装类,只要不是true,都转换为false
String str4 = "true100";
Boolean num4 = Boolean.parseBoolean(str4);
System.out.println(num4);
}
}
--------------------------------------------------
运行结果:
12.3
12.5
true
*****String类转为基本数据类型、包装类*****
123
123.5
true
false
3. 一个例子
public class Demo01 {
public static void main(String[] args) {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);
//当数值在-128~127范围内,自动封装的引用指向的都是同一个对象(IntegerCache中的Integer[]数组中的值)
Integer a = 20;
Integer b = 20;
System.out.println(a == b);
//当超出范围,自动封装就是new一个新的对象引用,这时两次封装指向的就是不同的对象
Integer x = 128;
Integer y = 128;
System.out.println(x == y);
}
}
-----------------------------------------
运行结果:
false
true
false
所有的包装类内部都有一个数组用于存储所指示范围内的数据,因为比较常用,为了提高效率就用了这么一个数组,当自动封装的数据为该范围内的数值时,就直接指向数组内对应的值。
当超出数组范围的数据时,自动封装相当于使用new关键字调用包装类的构造方法,创建一个新对象的引用,这时两次自动封装所指向的对象就不一样了。
三、String类
字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
1. String类简介
- String类是一个final类,不可被继承
- String类实现了Serializable接口:表示字符串是支持序列化的(IO流)
- String类实现了Comparable接口:表示String可以比较大小
- String类内部定义了
final char[] value
数组用于存储字符串的数据。也就是说字符串本质是一个字符数组,数组内的每一个元素都是char类型 - String代表不可变的字符序列。具有不可变性。字符串的值在创建之后不能更改。
2. 创建字符串
创建字符串有两种方式:
- 直接创建
String str = "automan";
- 利用构造方法创建
String str2 = new String("automan")
这两种方法的作用效果是不一样的。字符串常量是存储在方法区的常量池中的。直接创建时,String类的引用在栈中,直接指向常量池中的字符串。利用new创建时,String类引用指向堆中的String类对象,String类对象中存储常量池中的字符串。
public class Demo01 {
public static void main(String[] args) {
String str1 = "automan";
String str2 = "automan";
System.out.println(str1 == str2);
String str3 = new String("automan");
String str4 = new String("automan");
System.out.println(str3 == str4);
System.out.println(str1 == str3);
System.out.println(str2 == str4);
}
}
----------------------------
运行结果:
true
false
false
false
由上图可见,str1
和str2
引用都是指向常量池中的字符串常量,地址一样,因此==运算符
结果为true
,而str3
存储的时堆中String对象
的地址,str4
中存储的是堆中String对象
的地址,地址不一样,因此==运算
结果为false。这也是两种String类实例化方式的区别。
同样的,对于下面程序有:
public class Demo01 {
public static void main(String[] args) {
Person p1 = new Person("automan", 20);
Person p2 = new Person("automan",20);
Person p3 = new Person(new String("automan"), 20);
System.out.println(p1.name == p2.name);
System.out.println(p1.name == p3.name);
}
}
class Person {
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
-----------------------------------------
运行结果:
true
false
3. 连接字符串
String 类提供了连接两个字符串的方法:
string1.concat(string2);
:返回 string2 连接 string1 的新字符串。也可以对字符串常量使用 concat() 方法- 更常用的是使用
'+'操作符
来连接字符串
不同拼接操作的比较:
public class Demo01 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = "hello" + "world";
String s5 = "hello" + s2;
String s6 = s1 + s2;
String s7 = (s1 + s2).intern();
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
System.out.println(s4 == s5);
System.out.println(s3 == s7);
//有final修饰的是常量,常量与常量拼接结果在常量池中。
final String s8 = "world";
String s9 = "hello" + s8;
System.out.println(s3 == s9);
}
}
------------------------------------
运行结果:
true
false
false
false
true
true
- 常量与常量的拼接,结果也在常量池中,且常量池中不会村次昂相同内容的常量;
- 只要有一个是变量,结果就在堆中
- 如果拼接结果调用intern()方法,则返回值是常量池中的结果。
4. 一个例子
public class Demo01 {
String str = new String("good");
char[] ch = {'t', 'e', 's', 't'};
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
Demo01 test = new Demo01();
test.change(test.str, test.ch);
System.out.println(test.str);
System.out.println(test.ch);
}
}
------------------------------
运行结果:
good
best
对于原来的存储可以看作是这样的,引用类型作为形参,传递的是地址,因此局部变量和原有变量指向的是同一个对象:
当执行change方法后:
change
内的操作使得局部变量str
指向的对象改变,但原来str
指向的没有改变。change
内的操作使得堆里面char[]
数组的的第一个元素指向改变,因此原来ch
指向的char[]
数组内容发生了变化。
这也是String字符串不可变性的体现
5.String类中的常用方法
常用方法 | 作用 |
---|---|
length() | 返回字符串长度 |
boolean isEmpty() | 判断字符串是否为空。 |
toLowerCase() | 使用默认语言环境,将String中所有字符转为小写 |
toUpperCase() | 使用默认语言环境,将String中所有字符转为大写 |
concat(String str) | 将指定的字符串连接到该字符串的末尾 |
equals(Object obj) | 将此字符串与指定对象进行比较 |
equalsIgnoreCase(Object obj) | 将此字符串与指定对象进行比较,忽略大小写 |
endsWith(String suffix) | 测试字符串是否以指定后缀结束 |
startsWith(String prefix) | 测试字符串是否以指定前缀开始 |
startsWith(String prefix,int toffset) | 测试该字符串从指定索引开始的子字符串是否以指定前缀开始 |
indexOf(String str) | 返回指定子字符串在该字符串中第一次出现时的索引 |
indexOf(String str, int fromIndex) | 从指定索引开始,返回指定子字符串在该字符串中第一次出现时的索引 |
lastIndexOf(String str) | 返回指定子字符串在该字符串中最后一次出现时的索引 |
lastIndexOf(String str, int fromIndex) | 从指定索引开始反向搜索,返回指定子字符串在该字符串中最后一次出现时的索引 |
contains(CharSequence s) | 当且仅当字符串中包含指定的char值序列时,返回true |
6. String与字符数组的转换
- String转换为字符数组
- 使用String类中的toCharArray方法
- 字符数组转为String
- 调用String的构造器
//将一个字符串进行反转。将字符串中指定部分进行反转。比如将“abcdefg”反转为”abfedcg”
public class Demo01 {
public static void main(String[] args) {
String str = "a21cb3";
//此处给定的值是字符数组的索引值
System.out.println(reverse(str,1,4));
}
public static String reverse(String str, int begin, int end) {
if (str != null) {
char[] ch = str.toCharArray();
for (int i = begin, j = end; i < j; i++, j--) {
char temp = ch[i];
ch[i] = ch[j];
ch [j] = temp;
}
return new String(ch);
}
return null;
}
}
--------------------------------------
运行结果:
abc123
7. String与字节数组的转换
- String转换为字符数组
- 使用String类中的
getBytes
方法
- 使用String类中的
- 字符数组转为String
- 调用String的构造器
public class Demo01 {
public static void main(String[] args) {
String str1 = "abc123中国";
//使用默认的字符集进行转换,这里默认的是UTF-8编码集
byte[] b = str1.getBytes();
System.out.println(Arrays.toString(b));
String s = new String(b);
System.out.println(s);
}
}
------------------------------------------
运行结果:
[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
abc123中国
//对于汉字,UTF-8编码用三个字节表示一个汉字
String与字节数组的转换经过了编码与解码的过程,编码与解码时的字符集要一致。
四、StringBuffer 和 StringBuilder 类
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
1. String类、StringBuffer 和 StringBuilder 类比较
- String类: 不可变的字符序列
- StringBuffer类:可变的字符序列,线程安全,效率低
- StringBuilder类:可变的字符序列,线程不安全,效率高
- 三个类都是关于字符串的类,底层都是使用
char[] 数组
存储字符串内容。
关于String类的不可变:
String字符串是不可变的字符列,有如下特点:
String str1 = "hello"; //相当于在常量池中有一个"hello",将str1指向的该字符串常量;
str1 = "hello" + "world"; //这就会在常量池中新建一个"helloworld"常量,将str1引用指向这个新的字符串常量,原来的"hello"依然存在。
str1 = "mm"; //这就会在常量池中新建一个"mm"常量,将str1引用指向这个新的字符串常量,原来的"hello"和"helloworld"依然存在。
//这些留下来的字符常量就交给垃圾回收,当没有引用指向它们时,JVM认为它们无用了就会回收释放空间。
但是 StringBuffer 和 StringBuilder 类不一样,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuffer sb1 = "hello"; //相当于在常量池中有一个"hello",将sb1指向的该字符串常量;
sb1 = "hello" + "world"; //还是常量池中原有的那个"hello",会在其底层的char[]数组后面加上"world",还是将sb1指向常量池中的这个字符串,但字符串内容变了。不会产生新的字符串常量。
sb1 = "mm"; //还是在常量池中的那个字符串常量,将其底层的char[]数组内的元素变为mm,不产生新的字符串常量。
由此可已看出StringBuffer类的字符串长度似乎是“可变”的。
分析其源码可以知道,StringBuffer类和StringBuilder 类当创建一个新的空对象时,其底层的char[]数组默认长度是16,也就是说相当于默认创建的是一个长度为16的数组,可以存放16个字符。
当存储的字符串长度超过16时,就需要扩容,根据其源码的设计,默认会新建一个长度为2 * 当前数组长度 + 2
的新的字符数组,并将现有字符数组里的内容赋值给新数组。
一般情况下,应该尽量避免扩容(创建新数组并复制元素,当数据量较大时,速度较慢),因此应该预判会用到多长的数组,在创建新对象时,使用有参的构造方法确定字符串长度。此时底层的数组的长度为设定的字符串长度+16
public class Demo01 {
public static void main(String[] args) {
String str = new String(); //底层char[] value = new char[0];
String str1 = new String("abc");//底层char[] value = new char[0]{a, b, c};
StringBuffer sb1 = new StringBuffer(); //底层char[] value = new char[16];底层创建一个长度为16的数组
sb1.append('a'); //底层:value[0] = 'a'; 相当于把字符依次赋给长度为16的字符数组
sb1.append('b'); //底层:value[1] = 'b'; 相当于把字符依次赋给长度为16的字符数组
//但此时返回字符串的长度,返回的是往数组内填充的元素的个数,而不是全部的字符数组长度
System.out.println(sb1.length()); //字符串的长度为2
StringBuffer sb2 = new StringBuffer("abc");//底层char[] value = new char["abc".length + 16];
}
}
一般情况下,对于需要修改的字符串,优先选用StringBuffer 和 StringBuilder 类,而不是String类。然后再根据线程安全问题选择是StringBuffer 还是 StringBuilder 类,并预判所需的字符串长度,调用有参的构造方法创建可变的StringBuffer对象或者 StringBuilder 对象。
2. SrtingBuffer类的常用方法
方法 | 作用 |
---|---|
append(xxx) | 类似于字符串拼接,再已有字符串后面填充xxx |
delete(int start, int end) | 删除指定区域内的字符 |
replace(int start, int end, String str) | 替代指定范围内的字符为str |
insert(int offset, xxx) | 再指定位置插入xxx |
reverse() | 把当前字符序列逆转 |
indexOf(String str) | 返回str再当前字符串中首次出现的位置 |
substring(int start,int end) | 返回一个start到end的左闭右开的子字符串(切片) |
length() | 返回字符串长度 |
charAt(int n ) | 返回指定索引处的字符 |
setCharAt(int n ,char ch) | 将指定位置的字符改为ch |
- 当使用append方法和insert方法时,若原有value长度不够,可扩容。
- 上述方法都支持方法链操作
总结:
- 增:
append(xxx)
- 删:
delete(int start, int end)
- 改:
setCharAt(int n ,char ch)
和replace(int start, int end, String str)
- 查:`charAt(int n )
- 插:
insert(int offset, xxx)
- 长度:
length()
- 遍历:for循环 +
charAt(int n )
3. StringBuilder类
StringBuilder
和StringBuffer
非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样
4. 三者效率对比
public class Demo01 {
public static void main(String[] args) {
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
}
}
----------------------
运行结果;
StringBuffer的执行时间:5
StringBuilder的执行时间:3
String的执行时间:1493