Java三大特殊类型

本文主要讲述了Java三大特殊类,String、StringBuffer/StringBuilder、Object和包装类。

String类(上)

String类的两种实例化方式

1.1.直接赋值(常用)

String str = "Hello Bit";//str是一个对象,那么“Hello Bit”就应该保存在堆内存中
System.out.println(str);

1.2.传统方法:String本身是一个类,既然是类,就存在构造方法,String类其中一种构造方法如下

public String(String str);//带参构造

//使用new关键字进行对象实例化
String str = new String("Hello Bit");
System.out.println(str);

2.字符串相等比较

2.1.用"==“比较

2.1.1.基本数据类型比较

int x = 10;
int y = 10;
System.out.println(x==y);//true

2.1.2.String类对象上使用”=="

String str1 = "Hello";
String str2 = new String("Hello");
System.out.println(str1 == str2);//false

分析:两个字符串内容相同,而使用“==”得到的结果却是错误,为什么呢?内存分析图如下

“==”本身是进行数值比较,那么比较的是两个对象所保存内容地址的比较,并没有比较对象的内容,想要比较内容应该使用String类的equals方法。

请解释String类“==”和equals的区别

3.字符串常量是String类的匿名对象

在任何语言的底层,都不会提供直接的字符串类型,现在所谓的字符串只不过是高级语言提供给开发者方便开发的支持而已。在Java中,本身也没有直接提供字符串常量的概念,所以使用“”定义的内容本质上来讲都是String的匿名对象。

所以String str = "Hello"本质上就是将一个匿名的String类对象设置有名字,而且匿名对象一定保存在堆内存。

注:在开发中,如果要判断用户输入的字符是否等同于特定字符串,尽量将特定字符串放在前面(方法二),防止输入为空,出现空指针(NullPointException)问题,如下:

String str = null;//假设由用户输入
System.out.println(str.equals("Hello"));//方法1
System.out.println("Hello".equals(str));//方法2

另外如上也说明"Hello"字符串常量是匿名对象,因为equals是对象方法。

4.String类两种实例化的区别

4.1.直接赋值

String str1 = "Hello";
String str2 = "Hello";
String str3 = "Hello";
System.out.println(str1 == str2);//true
System.out.println(str1 == str3);//true
System.out.println(str2 == str3);//true

为什么现在没有开辟新的堆内存空间?

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

4.2.构造方法(标准方法)

String str = new String("Hello");

使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为将成为垃圾空间,除此之外,也会对字符串共享产生影响:该字符串常量并没有保存在对象池之中

String str1 = new String("hello");
String str2 = "hello";
System.out.println(str1 == str2);//false

解决办法:public String intern(); //String类提供方法入池

String str1 = new String("hello").intern();
String str2 = "hello";
System.out.println(str1 == str2);//true

请解释String类中两种实例化对象方法的区别

综上,虽然构造方法是实例化对象的标准方法,但是一般会采用第一种方式即直接赋值。

5.字符串常量不可变更

注意:字符串常量一旦定义不可变更

所有语言对于字符串底层的实现都是字符数组,数组的最大缺陷就是长度固定,在定义字符串常量时,它的内容不可变更,如下:

String str = "hello";
str = str + "world";
str += "!!!";
System.out.println(str);//hello world!!!

以上字符串的变更是字符串对象的变更而非字符串常量,字符串对象的引用一直在改变,而且会形成大量的垃圾空间,因为这个特点,如下代码不该出现在开发中:

String str = "hello";
for(int i = 0; i < 100; i++){
	str += i;
}
Ststem.out.println(str);

当很多用户都使用如上操作,那么产生的垃圾空间将相当可观

原则:字符串使用就采用直接赋值、字符串比较就使用equals()实现、字符串别改变太多(防止产生大量垃圾空间)

6.字符与字符串

字符串就是一个字符数组,所以在String类里面支持有字符数组转换为字符串以及字符串变为字符的操作方法。

方法名称类型描述
public String(char value[])构造将字符数组中的所有内容变为字符串
public String(char value[],int offset,int count)构造将部分字符数组中的内容变为字符串
public char charAt(int index)普通取得指定索引位置的字符(索引从0开始)
public char[] toChaArray()普通将字符串变为字符返回
6.1.charAt()方法
String str = "hello";
System.out.println(str.charAt(0));//h
System.out.println(syr.charAt(10));//索引超出字符串长度:StringIndexOutOfBoundsException

6.2.字符串与字符数组的转换

String str = "helloworld";
//字符串变为字符数组
char[] data = str.toCharArray();
for(int i = 0; i < data.length; i++){
	data[i] -= 32;//小写转大写
	System.out.println(data[i] + "、")
}
//字符数组变为字符串
System.out.println(new String(data));//全部转换
System.out.println(new String(data,5,5));//部分转换

判断一个字符串是否由数字组成

7.字节与字符串

字节常用于数据传输以及编码转换处理中,在String中提供有对字节的支持

方法名称类型描述
public String(byte bytes[])构造将字节数组变为字符串
public String(byte bytes[],int offset,int length)构造将部分字节数组中的内容变为字符串
public byte[] getBytes()普通将字符串以字节数组的形式返回
public byte[] getBytes(String charsetName)throws UnsupportedEncodingException普通编码转换处理
字符串与字节数组的转换处理
String str = "helloworld";
byte[] data = str.getBytes();
for(int i = 0; i < data.length; i++){
	data[i] -= 32;//转大写的字节数组
	System.out.println(data[i] + "、")
}
System.out.println(new String(data));

通过程序可以发现,字节并不适合处理中文,只有字符适合处理中文,按照程序的概念来讲,一个字符等于两个字节,字节只适合处理二进制数据。

8.字符串比较

前面使用String类提供的equals()方法,该方法本身是可以进行区分大小写的相等判断,除了这个方法,String类还提供如下比较操作:

方法名称类型描述
public boolean equals(Object anObject)普通区分大小写的比较
public boolean equalsIanoreCase(String anotherString)普通不区分大小写的比较
public int compareTo(String anotherString)普通比较两个字符串大小关系
不区分大小写比较:
//不区分大小写的比较
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//false
System.out.println(str1.equalsIgnoreCase(str2));//true

在String类中compareTo()方法是一个非常重要的方法,它区分大小写,该方法返回一个类型,该数据会根据大小关系返回三类内容: 相等,返回0 小于,返回内容小于0 大于,返回内容大于0

System.out.println("A".compareTo("a"));//-32
System.out.println("a".compareTo("A"));//32
System.out.println("A".compareTo("A"));//0
System.out.println("AB".compareTo("AC"))//-1
System.out.println("刘".compareTo("杨"))

String类(下)

1.字符串查找

从一个完整的字符串之中可以判断指定内容是否存在,查找方法如下:

方法名称类型描述
public boolean contains(CharSequence s)普通判断一个子字符串是否存在
public int indexOf(String str)普通从头开始查找指定字符串位置,查到了返回开始位置索引,如果查不到返回-1
public int indexOf(String str,int fromIndex)普通从指定位置开始查找指定字符串位置
public int lastIndexOf(String str)普通由后向前查找子字符串位置
public int lastIndexOf(String str,int fromIndex)普通从指定位置向后查找指定字符串出现位置
public boolean startsWith(String prefix)普通判断是否以指定字符串开头
public boolean startsWith(String prefix),int toffset)普通从指定位置开始判断是否以指定字符串开头
public boolean endsWith(String suffix)普通判断是否以指定字符串结尾
字符串查找最方便的是contains(),它是JDK1.5之后的新特性,在此前要实现必须借助indexOf()方法实现(常用)
String str = "helloworld";
System.out.println(str.contains("world"));//true

使用indexOf()方法进行位置

String str = "helloworld";
System.out.println(str.indexOf("world"));//true
System.out.println(str.indexOf("lili"));//-1
if(str.indexOf("hello") != -1){
    System.out.println("可以查到指定字符串");//可以查到指定字符串
}

//注:使用indexOf()需要注意的是,如果出现内容重复它只返回查找到的第一个位置
String str = "helloworld";
System.out.println(str.indexOf("l"));//2
System.out.println(str.indexOf("l",5));//8
System.out.println(str.lastIndexOf("l"));//8

在查找字符串时往往会判断开头或结尾

String str = "**@@helloworld!!";
System.out.println(str.startsWith("**"));//true
System.out.println(str.startsWith("@@"));//false
System.out.println(str.startsWith("@@",2));//true
System.out.println(str.endsWith("!!"));//true
System.out.println(str.endsWith("d"));//false

2.字符串替换

使用一个新的字符串替换掉已有的字符串数据,可用的方法如下:

方法名称类型描述
public String replaceAll(String regex,String replacement)普通替换所有指定内容
public String replaceFirst(String regex,String replacement)普通替换首个指定内容
字符串替换处理还与正则表达式有关
//字符串的替换处理
String str = "helloworld";
System.out.println(str.replaceAll("l","-"));//he--owor-d
System.out.println(str.replaceFirst("l","-"));// he-loworld

3.字符串拆分

将一个完整的字符串按照指定的分隔符划分为若干个子字符串,方法如下:

方法名称类型描述
public String[] split(String regex)普通将字符串全部拆分
public String[] split(String regex,int limit)普通将字符串部分拆分,该数组长度就是limit极限
实现字符串拆分处理
String str = "hello world hello bit";
String[] result = str.split(" ");//按照空格拆分
for(String s : result){
    System.out.println(s);
}
//输出
//hello
//world
//hello
//bit

将字符串的部分拆分

String str = "hello world hello bit";
String[] result = str.split(" ",2);
for(String s : result){
    System.out.println(s);
}
//输出:
//hello
//world hello bit

有些内容无法拆分就需要使用""转义

//eg:拆分IP地址
String str = "192.168.1.1";
String[] result = str.split("\\.");
for(String s : result){
    System.out.println(s);
}
//输出
//191
//168
//1
//1

比较常用的是多次拆分

String str = "XXX:18|YYY:81";
String[] result = str.split("\\|");
for(int i = 0; i < result.length; i ++){
    String[] tmp = result[i].split(":");
    System.out.println(temp[0] + "=" +temp[1]);
}
//输出
//XXX=18
//YYY=81

4.字符串截取

从一个完整的字符串中取出部分内容

方法名称类型描述
public String substring(int beginIndex)普通从指定索引截取到结尾
public String substring(int beginIndex,int endIndex)普通截取部分内容
观察字符串截取
String str = "helloworld";
System.out.println(str.substring(5));//world
System.out.println(str.substring(3,5));//lo

注:第二种截取方式从beginIndex索引开始截取,beginIndex对应的位置要截取上,到endIndex结束,endIndex对应的位置不截取。

5.字符串其他操作方法

方法名称类型描述
public String trim()普通去掉字符串中的左右空格,保留之间空格
public String toUpperCase()普通字符串转大写
public String toLowerCase()普通字符串转小写
public native String intern()普通字符串入池操作
public String concat(String str)普通字符串拼接,等同于"+",不入池
public int length()普通取得字符串长度
public boolean isEmpty()普通判断是否为空字符串,但不是null,而是长度0
trim()方法
String str = "    hello  world";
System.out.println("[" + str + "]");//[    hello  world]
System.out.println("[" + str.trim() + "]");//[hello  world]

大小写转换(这两个方法只转换字母)

String str = "   hello%$$&%%&*WORLD  zoujierchibaba ";
System.out.println(str.toUpperCase());//   HELLO%$$&%%&*WORLD  ZOUJIERCHIBABA 
System.out.println(str.toLowerCase());//   hello%$$&%%&*world  zoujierchibaba 

数组长度使用数组名称.length是属性,而String中的length()是方法

String str = "hello" ;
System.out.println(str.length());//5

isEmpty()方法

System.out.println("hello".isEmpty());//false
System.out.println("".isEmpty());//true
System.out.println(new String().isEmpty());//true

6.首字母大写操作

String类中没有提供首字母大写操作,需要自己实现

public static void main(String[] args) {
   //首字母大写
   System.out.println(fistUpper("xxx"));//Xxx
   System.out.println(fistUpper(""));//
   System.out.println(fistUpper("z"));//Z
}
public static String fistUpper(String str) {
   //字符串为null或者长度为0时
   if ("".equals(str)||str==null) {
      return str ;
   }
   //字符串长度大于1时
   if (str.length()>1) {
	  //取出第一个字符转大写再拼接第一个字母之后的内容
      return str.substring(0, 1).toUpperCase()+str.substring(1) ;
   }
   //字符串只要一个字符时
   return str.toUpperCase() ;
}

StringBuffer类

1.引入

任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的只不过是其引用的指向而已由于String的不可改性,为了方便字符串修改,提供了StringBuffer类,在String中使用"+“来进行字符串连接,但是这个操作在StringBuffer中需要改为append()方法

public synchronized StringBuffer append(各种数据类型 b)

2.基本使用

StringBuffer和String最大的区别在于:String的内容无法修改,而StringBuffer的内容可以修改。如果需要频繁需改字符串,那么建议使用StringBuffer。如下

public class C{
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append("Hello").append("XXX");
        fun(sb);
        System.out.println(sb);
    }

    public static void fun(StringBuffer temp){
        temp.append("\n").append("www.YYY.cn");
    }
}
//输出
//HelloXXX
//www.YYY.cn

3.String和StringBuffer继承结构

String类StringBuffer类
public final class String implements java.io.Serializable,Comparable,CharSequencepublic final class StringBuffer extends AbstractStringBuider implements java.io.Serializable,CharSequence
可以看出两个类都是"CharSequence"接口的子类,这个借口描述的是一系列字符集。所以字符串时字符集的子类,以后见到CharSequence最简单的联想就是字符串。

4.String和StringBuffer的转换

String和StringBuffer类不能直接转换,想要互相转换需要采用以下原则

  • String变为StringBuffer:利用StringBuffer的构造方法append()方法
  • StringBuffer变为String:调用toString()方法

5.除append()外StringBuffer类中一些String类没有的方法

5.1.字符串反转

public synchronized StringBuffer reverse()

使用示例:

StringBuffer s = new StringBuffer("abc");
System.out.println(s.reverse());//cba

5.2.删除指定范围数据

如果删除区间的后半部分超过最字符串范围,默认删除到最后结束

public synchronized StringBuffer delete(int start,int end)

范例

StringBuffer s = new StringBuffer("hellozoujier");
System.out.println(s.delete(5,7));//hellojier

5.3.插入数据

会从offset当前对应的索引位置插入,而不是从offset位置后面开始插入

public synchronized StringBuffer insert(int offset,各种数据类型 b)

范例

StringBuffer s = new StringBuffer("hellojier");
System.out.println(s.delete(5,10).insert(0,"你好"));//你好hello

6.String和StringBuffer、StringBuilder的区别

  • String内容不可修改,StringBuffer和StringBuilder内容可以修改
  • StringBuffer采用同步处理,属于线程安全操作,StringBuilder采用异步处理,属于线程不安全操作。

Object

1.Object简述

Object是Java默认提供的一个类。Java里除了Object类,**所有的类都是存在继承关系的,默认会继承Object父类,也就是说,所有类的对象都可以用Object来接收。如下

class Person{};
class Student{};
public class P1 {
    public static void main(String[] args) {
        fun(new Person());
        fun(new Student());
    }
    public static void fun(Object obj){
        System.out.println(obj);
    }
}
//输出
//Person@1b6d3586
//Student@4554617c

所以在开发中,Object类是最高的参数统一化,同时Object类中存在一些定义好的方法,如下

方法名称类型描述
public Object()构造无参构造为子类服务
public String toString()普通取得对象信息
public boolean equals(Object obj)普通对象比较

2.取得对象信息

在使用对象直接输出的时候,默认输出的是一个地址编码,如果现在使用的是String类,该类对象直接输出的是内容,如下:

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

public class test{
    public static void main(String[] args) {
        fun(new Person("xiaokeai",18));//Person@1b6d3586
        fun("Hello");//Hello
    }
    public static void fun(Object obj){
        System.out.println(obj.toString());//默认输出对象调用的就是toString()方法
    }
}

通过以上代码我们可以发现,默认Object类提供的toString()方法只能得到一个对象的地址(而这是所有对象共同具备的特征)。觉得默认给出的toString()方法功能不足,就要在子类上覆写toString()方法,如下

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 "名字:" + this.name + "  年龄:" + this.age;
    }
}

public class test{
    public static void main(String[] args) {
        fun(new Person("xiaokeai",18));//名字:xiaokeai  年龄:18
        fun("Hello");//Hello
    }
    public static void fun(Object obj){
        System.out.println(obj.toString());//默认输出对象调用的就是toString()方法
    }
}

toString()方法的核心目的在于取得对象信息。而String作为信息输出的重要数据类型,在Java中所有的数据类型只要遇见String并执行了“+”,那么都要求将其变为字符串后连接,而所有对象想要变成字符串就要使用toString()方法

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 "名字:" + this.name + "  年龄:" + this.age;
    }
}

public class test{
    public static void main(String[] args) {
        String msg = "Hello" + new Person("xiaokeai",18);
        System.out.println(msg);//Hello名字:xiaokeai  年龄:18
    }
}

3.对象比较

String类对象的比较使用的是equals()方法,实际上String类的equals()方法就是覆写Object类中的equals()方法。

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 "名字:" + this.name + "  年龄:" + this.age;
    }
    @Override
    public boolean equals(Object obj){
        //对象判空
        if(obj == null){
            return false;
        }
        //判断是不是当前对象
        if(this == obj){
            return true;
        }
        //判断是不是Person类对象
        if(!(obj instanceof Person)){
            return false;
        }
        //是当前类对象
        //向下转型比较属性值
        Person person = (Person)obj;
        return this.name.equals(person.name) && this.age == person.age;
    }
}

public class test{
    public static void main(String[] args) {
        Person per1 = new Person("zoujier",18);
        Person per2 = new Person("zoujier",18);
        System.out.println(per1.equals(per2));
    }
}

4.接收引用数据类型

Object是所有类的父类,它的特点不仅限于此,他可以接收所有数据类型,包括:类、数组、接口。

//使用Object来接收数组对象
public class test{
    public static void main(String[] args) {
        //object接收数组对象,向上转型
        Object obj = new int[]{1,2,3,4,5,6};
        //向下转型,需要强转
        int[] data = (int[])obj;
        for(int i = 0;i < data.length; i ++){
            System.out.println(data[i]);
        }
    }
}
//输出结果
//1
//2
//3
//4
//5
//6

Object接收接口是Java中的强制要求,因为接口本身不继承任何类。

//使用Object接收接口对象
interface IMessage{
    public void getMessage();
}

class MessageImpl implements IMessage{
    @Override
    public String toString(){
        return "i am father";
    }
    public void getMessage(){
        System.out.println("我是大帅哥");
    }
}

public class test{
    public static void main(String[] args) {
        //子类向父接口转型
        IMessage msg = new MessageImpl();
        //接口向Object转型
        Object obj = msg;
        System.out.println(obj);
        //强制转换类型
        IMessage temp = (IMessage) obj;
        temp.getMessage();
    }
}
//输出
//i am father
//我是大帅哥

Object真正达到了参数统一,如果一个类希望接收所有的数据类型就是用Object完成。

包装类

1.包装类基本原理

包装类就是将基本数据类型封装到类中。

//这里的IntDemo实际上就是int数据类型的包装类
class IntDemo{
    private int num;
    public IntDemo(int num){
        this.num = num;
    }
    public int intValue(){
        return this.num;
    }
}

public class test{
    public static void main(String[] args) {
        //子类对象向上转型
        Object obj = new IntDemo(55);
        //向下转型
        IntDemo temp = (IntDemo)obj;
        //取出里面的基本数据类型操作
        System.out.println(temp.intValue());
    }
}
//输出
//55

综上,将基本数据类型包装为一个类对象的本质就是使用Object进行接收处理

Java中有8种数据类型,如果每种都按照以上方式编写存在以下问题:

  • 开发中代码重复
  • 在进行数学计算的时候,必须利用明确的方法将包装的数据取出后才能进行运算

为了便于开发,专门提供了包装类的使用,而关于包装类的使用又提供了两种类型:

  • 对象型(Object的直接子类):Boolean、Character(char)
  • 数值型(Number的直接子类):Byte、Double、Short、Long、Integer(int)、Float

说明:关于Number类

  • Number类的定义:public abstract Number implements java.io.Serializable
  • 在Number类里面实际定义有六种重要方法

2.装箱与拆箱

装箱:将基本数据类型变为包装类对象,利用每个包装类提供的构造方法实现装箱处理 拆箱:将包装类中包装的基本数据类型取出(利用Number类中提供的六种方法)。

public class test{
   public static void main(String[] args) {
       Integer num = new Integer(55);//装箱
       int data = num.intValue();//拆箱
       System.out.println(data);
   }
}
//输出
//55

如上操作是手工的装箱和拆箱。在JDK1.5之后提供了自动拆装箱机制,由于此机制的存在,可以直接利用包装类进行各种数学计算,如下

public class test{
    public static void main(String[] args) {
        //自动装箱
        Integer x = 55;
        //可以直接利用包装类对象操作
        System.out.println(++x);
    }
}
//输出
//56

注意:这里存在一个“==”和“equals”问题,在阿里编码规范中,所有相同类型的包装类对象之间的值比较全部使用equals方法

public class test{
    public static void main(String[] args) {
        Integer num1 = new Integer(10);
        Integer num2 = new Integer(10);
        System.out.println(num1 == num2);//false
        System.out.println(num1 == new Integer(10));//false
        System.out.println(num1.equals(num2));//true
        System.out.println(num1.equals(new Integer(10)));//true
    }
}

说明:对于Integer var = ?在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==判断,但是这个区间外的所有数据都会在堆上产生,并不会复用已有对象,所有推荐使用equals进行比较。

3.使用基本数据类型还是包装类

根据阿里编码规范:

  • 【强制】所有的POJO类属性必须使用包装数据类型
  • 【强制】RPC方法的返回值和参数必须使用包装数据类型
  • 【推荐】所有的局部变量使用基本数据类型

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

以后进行各种数据的输入,一定都是字符串类型接收。所以在开发中,如何将字符串变为各个数据类型就需要包装类的支持。

String变int类型(Integer类)

public static int parsrInt(String s)throws NumberFormatException

String str = "123";//String类型
int num = Integer.parseInt(str);
System.out.println(num);//123

String变double类型(Double类)

public static int parseDouble(String s)throws NumberFormatException

String str = "123" ; // String类型
double num = Double.parseDouble(str) ;
System.out.println(num);//123.0

需要注意的是,将字符串转为数字的时候,字符串的组成有非数字,那么转换就会出现错误(NumberFormatException)

String str = "123aassa" ; // String类型
double num = Double.parseDouble(str) ;
System.out.println(num);
//Exception in thread "main" java.lang.NumberFormatException: For input string: "123aassa"

String变boolean类型(Boolean类)

public static boolean parseBoolean(String s)

String str = "346" ; // String类型
boolean num = Boolean.parseBoolean(str) ;
System.out.println(num);//false

如果现在要把基本数据类型变为字符串:

  • 任何数据类型使用了“+”连接空白字符串就变为了字符串类型
  • 使用String类中提供的valueOf()方法,此方法不产生垃圾
String str = String.valueOf(100) ;
System.out.println(str);//100