三大特殊类

前言

在Java中有三大特殊类:String类,Object类,包装类.针对于这三个特殊类做了相对应的总结

一.String类

1.String类的两种实例化方式

首先我们应该知道String是一个类,而不是一个简单类型(int,float…),作为一个类,它具有类的所有特性,自然也和其他类一样能够通过new来进行实例化,同时他也和数组一样,在实例化时有更为简洁的方法

1
2
3
//String类的两种实例化方式
String str = "hello world"; //最为简洁的方式
String str1 = new String("hello world"); //通过关键字new来实例化对象,这只是String类构造方法的一种

2.字符串相等比较

1
2
3
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");

如上有三个字符串,他们的内容全都一样,现在想要比较一下他们是否相等,有人可能会问,内容都一样了,怎么会不相等?

1
2
System.out.println(str1 == str2);  //true
System.out.println(str2 == str3); //false

答案如注释所示,为什么会出现这种情况?
在JAVA中,==比较的是两个对象的引用.
str1,str2没有通过new来创建对象,其实他们的内容是在一个叫做字符常量池的空间中存放着,只要有新的没有用new产生的String变量生成就会到常量池中去寻找看是否有这个字符串,如果有就将引用指向这个字符串,所以str1和str2他们引用的指向都是一个地方,故相等.即==一般用于数值比较,在对象之间他比较的是两个对象的引用(即内容所处的地址)
而str3是通过类的实例化得来的,所以他的abc是保存在堆上的,并且由一个在栈中的引用指向他,这个引用指向的位置自然和str1与str2不同,故不等
equals方法
想要比较两个字符串的内容是否相等,就要用String类提供的equals()方法.他比较的是两个字符串的内容,如果内容一致,就是true,否则相反

1
2
System.out.println(str.equals(str1));  //true
System.out.println(str.equals(str2)); //true

在日后的开发过程之中,如果要判断用户输入的字符串是否等同于特定字符串,一定要将特定字符串写在前面,以此来避免空指针异常的错误

3.String类两种实例化方式的区别

1.直接赋值法

String类的设计采用共享模式,使用直接赋值法时,
在JVM底层实际上会自动维护一个对象池(字符串对象池),如果现在采用了直接赋值的模式进行String类的对象
实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中。如果下次继续使用直接赋值的模式
声明String类对象,此时对象池之中如若有指定内容,将直接进行引用;如若没有,则开辟新的字符串对象而后将
其保存在对象池之中以供下次使用
所谓的对象池就是一个对象数组(目的就是减少开销)

2.类的实例化方式

1
String str = new String("abc");

使用此种方式实际上JVM会前后开辟两块空间,当开始执行此条语句的时候,会在new之前先在堆上开创一片空间,将”abc”放入,然后在使用new时又会创建一片空间将相同的内容放入,然后引用指向这片空间,之前那片空间便成为了垃圾空间 .并且由这种方式创建出来的字符串是不能共享的,但是String提供了手工入池的方法intern().将字符串入池之后就和其他直接赋值创建的字符串一样了
总结区别:
方式1.只会开辟一块空间,并且该字符串对象可以自动保存在对象池中供下次使用
方式2.会开辟两块内存空间,并且第一块会变为垃圾空间,该字符串不能自动保存在对象池中,但是可以手工入池(手工入池要注意一点,一定要在对象创建好的时候就手工入池,不能之后 通过引用来调用入池操作)

1
2
3
4
5
//正确手工入池操作
String str = new String("abc").intern();

//错误方式
str.intern();

3.字符串一旦定义就不能修改

字符串底层实现都是字符数组,数组的最大缺陷就是一旦定义好了变不能够修改大小,所以字符串一但定义好就不能再修改.
但是字符串可以使用”+”号进行拼接,例如

1
return str1+str2

4.字符串和字符数组的相互转换

  • 字符串转换为字符数组
    使用String类中提供的toCharArray()方法,使得一个字符串能够转换为一个字符数组

    1
    2
    String str = "abdef";
    str.toCharArray();
  • 字符数组转变为一个字符串
    在String类中,有一个构造方法,他需要一个参数便是一个字符数组.所以将要转变的字符数组当作一个参数传递给一个String的构造方法,便可以达到转变的目的

    1
    2
    char[] arr = {a,b,c,s,a};
    String str = new String(arr); //转换完成

获得指定索引处字符的方法 charAt();

5.字节数组和字符串之间的相互转化

  • 字节数组转变为字符串

    1
    2
    char[] arr = {'a','b','c','d'};
    String str = new String(arr);
  • 字符串转变为字符数组
    直接调用getBytes()方法就可进行转换
    需要注意的是字节数组并不适合处理中文,只有字符适合处理中文,因为一个字符默认的是两个字节的大小.字节只适合处理二进制数据

    6.equals,equalsIgnoreCase和compareTo

    前两个都为比较方法,他们的区别在于第一个比较是区分大小的比较,第二个比较是不区分大小写的.
    compareTo()方法可以比较两个字符串的大小,他的结果会返回一个整型,如果字符串大于被比较的字符串则返回一个大于零的数,等于就返回零,小于就返回一个小于零的数

    7.字符串查找

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    String str = "abcdefghi";
    String str1 = "cde";

    System.out.println(str.contains("abc")); //str中包含str1
    System.out.println(str.indexOf("hi")); //在一个字符串中从头开始查找指定字符串出现的位置并返回索引
    System.out.println(str.indexOf("hi",2)); //从指定索引处开始查找,并返回查找到的字符串的开头索引位置
    System.out.println(str.lastIndexOf("ef")); //从后向前查找指定字符串,返回指定字符串在原字符串中的索引
    System.out.println(str.lastIndexOf("ef",str.length())); //从后往前,指定起始下标位置,返回索引(同上)
    System.out.println(str.startsWith("abc")); //判断字符串是否已某个指定字符串开头
    System.out.println(str.startsWith("ab",0)); //判断指定索引处是否以某个字符串开头
    System.out.println(str.endsWith("i")); //判断字符串是否以某个字符串结尾

8.字符串替换

1
2
3
//字符串替换
System.out.println(str.replaceAll("k","o")); //将所有的字符全部替换
System.out.println(str.replaceFirst("k","o")); //只替换指定字符第一次出现的位置

9.字符串拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//字符串拆分
//1.全部拆分
String s = "hello world hello bit haha";
String[] arrStr = s.split(" ");
for(String i: arrStr){
System.out.println(i);
}

//2.部分拆分
String[] arrStr2 = s.split(" ",4);
for(String i: arrStr2){
System.out.println(i);
}

//拆分一个ip地址
String str = "192.168.1.1" ;
String[] ipArr = str.split("\\.");
for(String i: ipArr){
System.out.println(i);
}

//多次拆分
String strr = "yuisama:27|yui:25" ;
String[] first = strr.split("\\|");
for(String i:first){
String[] second = i.split("\\:");
System.out.println(second[0]+" "+second[1]);
}

10.字符串截取

1
2
3
4
//字符串截取
String aa = "abcdefrg";
System.out.println(aa.substring(1,5)); //从指定索引处开始截取到指定索引处 注意截取范围: [start,end)
System.out.println(aa.substring(2)); //从制定索引出截取到末尾 [start,str.length]

11.其他方法

1
2
3
4
5
6
7
8
9
10
//去掉一个字符串左右两边的空格,保留中间的空格
String s = " a b c d e ";
System.out.println("["+s+"]");
System.out.println("["+s.trim()+"]");

//字符串变大写
System.out.println(s.toUpperCase());
//字符串变小写
System.out.println(s.toUpperCase().toLowerCase());
//需要注意的是此方法只会转变字母

12.StringBuffer类

String类只要定义了一个字符串,那么这个字符串就不能够进行更改,为了方便的对一个字符串进行修改操作,便有了StringBuffer类,这个类提供了许多字符串修改的操作

1.连接操作

String中连接两个字符串使用+就好了.在StringBuffer中需要使用append方法

1
2
3
4
5
6
7
8
9
10
11
12
public class TestStringBuffer {

public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
sb.append("Hello").append("World");
fun(sb);
System.out.println(sb);
}
public static void fun(StringBuffer temp) {
temp.append("\n").append("www.bit.com.cn");
}
}

注意上面这段代码,StringBuffer和数组一样,在一个方法中是会被修改的.
StringBuffer和String的最大区别就是他可以对字符串进行修改

2.String和StringBuffer互相转换

  • String–>StringBuffer: 将String作为参数传给StringBuffer的一个构造方法

    1
    2
    StringBuffer sb1 = new StringBuffer(s);
    System.out.println(sb1);
  • StringBuffer–>String
    使用StringBuffer中的toString方法

    1
    2
    3
    StringBuffer sb = new StringBuffer();
    sb.append("Hello").append("World");
    System.out.println(sb.toString());

3.StringBuffer中的其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
StringBuffer sb = new StringBuffer("abcdef");
//字符串反转
sb.reverse();

//删除指定范围的内容
sb.delete(2,4);
System.out.println(sb);
//删除指定下标处的内容
sb.deleteCharAt(3);

//插入操作
sb.insert(1,"hahahah");
System.out.println(sb);

4.StringBuild

与StringBuffer相同的还有个StringBuild,StringBuild和StringBuffer的区别在于前者是线程不安全的,他适合于在单线程中工作,效率比较高;后者是线程安全的,但是相对效率会低于前者.常用于多线程中

二.Object类

Object是Java默认提供的一个类,他是所有类的父类.即所有类的对象都可以使用Object类来接收,所有的类也都具备Object类的特性(包括方法,属性等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestObject {

//使用Object类来接收对象
public static void fun(Object oj){
System.out.println(oj);
}

public static void main(String[] args){
fun(new Person());
fun(new Student());
}
}

class Person{ }

class Student{ }

执行结果(后面会解释):

1
2
io.github.Object.Person@1540e19d
io.github.Object.Student@677327b6

在开发中,Object是形参的最高同一类型.

Object类提供的方法

Object中提供了很多方法,在这里总结两个很关键的方法.
equals和toString方法

1.toString:获取对象信息

在一开始的代码中,我们使用fun()方法来打印了每一个对象的信息,然后发现结果是:io.github.Object.Person@1540e19d, 这是什么东西呢? —>
首先需要知道,当打印一个对象信息的时候便默认调用了这个对象类中的toString方法,这个方法便是用来打印对象信息的.
Object中的toString方法:

1
2
3
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

所以,在使用对象直接输出的时候,默认输出的是一个地址编码.
为什么String类的对象在使用对象直接输出的时候不是编码呢?
.Object是所有引用类型的父类,在Object中有一个方法是toString,他返回的结果是对象的地址编码.String类是继承自Object类的,但他覆写了Object类中的toString方法. 在实例化一个类的对象时,如果子类覆写了父类的某个方法,那么在调用此方法时如果没有明确指定是调用父类中的方法就会默认调用覆写过后的方法,所以在String类实例化对象后打印信息时会自动调用String中覆写过的toString方法
String类中覆写过的toString方法

1
2
3
public String toString() {
return this;
}

可以看到String类中的toString方法是直接返回String中的内容的,所以不是我们看到的地址编码
启示:我们所写的每一个类也都是Object的子类,如果不想在打印对象信息的时候出现的结果是地址编码,那么我们就需要按照自己的需求也将toString方法进行覆写
改写第一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestObject {

public static void fun(Object oj){
System.out.println(oj);
}

public static void main(String[] args){
fun(new Person());
fun(new Student());
}
}

class Person{
private String name = "Alice";
private int age = 19;

@Override
public String toString() {
return "name: "+name+" \nage: "+age;
}
}

class Student{ }

此时输出内容:

1
2
3
name: Alice 
age: 19
io.github.Object.Student@1540e19d

覆写过后的toString方法就能够按照我们意愿打印对象信息了!

2.equals:对象比较

在Object中提供了equals方法用来比较两个对象

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

但是他仅仅是简单的做了一下对象引用的比较.所以在其他子类中一般都需要重新覆写该方法.例如String类就重新覆写了equals方法.
我们也可以在自己定义的类中重新覆写该方法,按照我们的意愿来进行两个对象的比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class TestObject {

public static void fun(Object oj){
System.out.println(oj);
}

public static void main(String[] args){
Person per1 = new Person("Alice",21);
Person per2 = new Person("Jack",22);
System.out.println(per1.equals(per2));

}
}

class Person{
private String name ;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "name: "+name+" \nage: "+age;
}

@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}

if(this == obj){
return true;
}

if(!(obj instanceof Person)){
return false;
}

Person per = (Person)obj; //向下转型,比较属性值
return this.age==per.age && this.name.equals(per.name);

}
}

这样就覆写了Object中的equals方法

4.总结

在Java中,Object是所有类的父类,所有的类都继承自Object类,都拥有Object所提供的开放方法及属性.为了达到准确编程的目的我们在必要的时候需要覆写Object中的方法.Object由于是所有类的父类,所以他可以接收所有的引用类型,包括数组,接口,类!!

三.包装类型

Object可以接受所有的引用类型,那么基本类型怎么办?这里就要使用到包装类型了

基本原理

包装类就是将基本数据类型封装到类中
自己定义一个int类型的包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {


public static void main(String[] args) {
Object intDome = new IntDome(45);
IntDome intDome1 = (IntDome)intDome;
System.out.println(intDome1.getInt());
}
}


//自己定义一个int类型的包装类
class IntDome{

private int num;
public IntDome(int num){
this.num = num;
}

public int getInt(){
return this.num;
}
}

这样我们便自己定义好了一个int类型的包装类,他可以使用Object来接收并且能够和int一样定义我们需要的数据 ,我们定义的这个类就是一个基本的包装类,他将一个基本数据类型包装成了一个可以使用Object接收的引用数据类型
结论:将基本数据类型包装为一个类对象的本质就是使用Object进行接收处理。
但是,基本数据类型有8种,每一种都在自己编程的时候进行包装未免太过于麻烦,代码重复度也会增加,所以,JDK为我们直接提供了包装类的使用,而对于包装类的使用,提供了两种类型:

  • 对象型(Object的直接子类):Boolean、Character(char);
  • 数值型(Number的直接子类): Byte、Double、Short、Long、Integer(int)、Float
    这些包装类型直接当作引用类型使用就可以了

    装箱拆箱

    在包装类与基本数据类型处理之中存在有两个概念:
    装箱:将基本数据类型变为包装类对象,利用每一个包装类提供的构造方法实现装箱处理
    拆箱:将包装类中包装的基本数据类型取出.利用Number类中提供的方法
    int类型装箱拆箱实例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class TestInt {

    public static void main(String[] args) {
    int a = 55;
    Integer integer = new Integer(a); //装箱

    int b = integer.intValue(); //拆箱
    }
    }

以上示范采用的是手工的装箱拆箱操作,在JDK1.5之后,提供了自动装箱拆箱处理,自此可以使用包装类对象直接进行数值上的逻辑运算

1
2
Integer a = 55;//自动装箱
System.out.println(++a); //逻辑运算时,自动拆箱

包装类型的一个大坑

观察问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
Integer n1 =  11;
Integer n2 = 11;
Integer n3 = new Integer(11);
Integer n4 = -2;
Integer n5 = 129;
Integer n6 = 129;
Integer n7 = new Integer(129);

System.out.println(n4);
System.out.println(n1 == n3); //false
System.out.println(n1==n2); //true
System.out.println(n5==n6); //false
System.out.println(n5==n7); //false

我们知道包装类型和引用类型一样,使用等号比较的时候,比较的还是引用,所以n1!=n3,n1=n2,n5!=n7
但是为什么n1==n2,n5!=n6呢?
原因是这样的: 当产生一个Integer的对象,如果这个值是在-127–+128之间,那么这个值会在IntegerCache.cache 产生,并且会复用已有对象,这个区间的值可以直接使用”\==”来判断,但是这个区间之外的值,会在堆上产生,并且不会复用已有对象,只有用equals来比较才能得到我们想要的结果
阿里编码规范:

  • 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较
  • 【强制】RPC 方法(远程方法调用)的返回值和参数必须使用包装数据类型。
  • 【推荐】所有的局部变量使用基本数据类型。

    字符串与基本数据类型的转换

    在开发中,我们从外部接收的数据都是字符串类型的,那么如何把这些字符串类型的内容转变为我们需要的基本类型呢,这时候就要使用我们的包装类型了

  • String变为int 类型(Integer类):public static int parseInt(String s) throws
    NumberFormatException

  • double parseDouble(String s) throws
    NumberFormatException
  • String变为Boolean类型(Boolean类):public static boolean parseBoolean(String s)
    具体使用:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //字符串转int
    String str = "123456";
    int rstr = Integer.parseInt(str);

    //字符串转double
    double rstr1 = Double.parseDouble(str);

    //字符串转boolean
    Boolean.parseBoolean(str);

及每一种包装类型都有自己的parse方法能够将String类型转变为包装类型在自动拆箱成基本类型
问题: 字符串转数字时一定要注意给定的字符串中不能出现非数字,否则会出现(NumberFormatException)
基本类型如何变为字符串类型?

  • 任何数据类型只要使用+连接空白字符串就会变为字符串类型
    举个例子:
    1
    2
    3
    4
    5
    6
    int a = 1;
    int b= 2;
    int c = 3;

    System.out.println(a+b+c);
    System.out.println(a+""+b+c);

运行结果:
6
123
上面一行直接用+号连接,求出来的就是三个值的和
下面一行使用+连接空符串(什么字符串都可以完成转换),在空包字符串后面的内容就自动被转换成了字符串

  • 使用String类提供的valueOf方法也可以进行基本类型到字符串的转换
    1
    String str = String.valueOf(100);