一、包装类
出现原因: java为纯面向对象语言,但是8种基本数据类型不能new对象,破坏了java为纯面向对象语言的特征,所以java为8种基本数据类型分别匹配了对应的类,这种类叫做包装类/封装类
Java中的基本数据类型没有方法和属性,而包装类就是为了让这些拥有方法和属性,实现对象化交互。
1. 基本数据类型的包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
2. 层次结构
数值型包装类都继承至Number,而字符型和布尔型继承至Object。
3. 基本数据和包装类之间的转换
应用场景:
集合只能存引用数据类型,如果要想存基本数据类型,可以把基本数据类型转换为对应包装类的对象
自jdk1.5开始,Java增加了对基本数据类型的自动装箱和自动拆箱操作。
装箱:基本数据类型转换为包装类。
手动装箱:
//利用构造函数
Integer integer1 = new Integer(100);
//利用包装类中的静态方法
Integer integer2 = Integer.valueOf(100);
自动装箱:
//直接把一个基本数据类型赋值给包装类
Integer integer1 = 100;
//底层实现:Integer integer1 = Integer.valueOf(100);
拆箱:包装类转换为基本数据类型。
手动拆箱:
//返回包装类对象integer1对应的基本数据
int int1= integer1.intValue();
自动拆箱:
//直接把一个包装类对象,赋值给基本类型
int int2 = new Integer(100);
//底层实现:int int2 = new Integer(100).intValue();
通过包装类Integer.toString()将整型转换为字符串;
String str1 = Integer.toString(100);
通过Integer.parseInt()将字符串转换为int类型;
int int1 = Integer.parseInt("100");
通过valueOf()方法把字符串转换为包装类然后自动拆箱;
int int2 = Integer.valueOf("110");
需求:将字符串数组转换为int数组
String[] ss = {"1","2","3","4","5","6"};
int[] is = new int[ss.length];
for (int i = 0; i < ss.length; i++) {
String str = ss[i];
//将字符串转换为int
int num = Integer.parseInt(str);
is[i] = num;
}
4. 深入包装类
整数缓冲区
Java为了提高拆装箱效率,在执行过程中提供了一个缓存区(对象池)(类似于常量数组)。整型对象通过使用相同的对象引用实现了缓存和复用。
如果传入的参数是在整数值区间-128 至 +127,会直接去缓存查找数据,如果有就对已创建的对象进行复用,如果没有就隐式调用new方法创建
只适用于自动装箱。使用构造函数创建对象不适用。
//利用构造方法
//one和对two是两个不同的对象
Integer one = new Integer(100);
Integer two = new Integer(100);
// == 在比较对象时比较的是内存地址,one和two是两个是不同的空间,放的值相同
System.out.println(one == two);//false
//自动装箱
//这时缓存区没有,就会构造一个对象
Integer three = 100;//底层实现:Integer three=Integer.valueOf(100);
//在创建对象之前先从IntegerCache.cache中寻找。如果没找到才使用new新建对象。
// 这时缓存区有,就会直接取来复用
Integer four = 100;//底层实现:Integer four=Integer.valueOf(100);
System.out.println(three == four);//true
//这里为200,超出了缓存区数组 [-128, 127],所以都需要新建
Integer five = 200;
Integer six = 200;
System.out.println(five == six);//false
二、String 类
String是不可变类,即一旦一个String对象被创建,包含在这个对象中的字符序列是不可改变的,直至该对象被销毁。String类是final类,不能有子类。
String为什么设计成不可变类?
- 符合Java字符串池的设计方式
String str1="abc";
String str2="abc";
Java通过字符串池的设计方式节省内存空间,如上面一段代码只会生成一个对象放在常量池当中。str1和str2都指向这个对象,如果String类可变,通过str1这个引用就可以修改这个对象,那其他引用就会受影响。
- 安全性
JDK提供的众多API当中,大多的参数都是String类型,如类加载函数,数据库的连接,Sql语句,Socket的参数等。如果String类可以被修改就会造成安全漏洞。而且多线程情况下,String类数据也可以保护数据不被其他线程修改。
String怎么实现不可变?
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
...
从String类的源码中可以看到,String是通过char value[]保存字符的。而且声明为private final,并且不提供我们写value的接口。所以String类不能够修改。
1. String类的使用
String str = "123abc";
//在目标字符串末尾追加新的字符串,并返回
System.out.println(str.concat("DEF123"));//123abcDEF123
//从开始下标处截取到字符串末尾,并返回
System.out.println(str.substring(2));//3abc
//从开始下标处(包含)截取到结束下标处(不包含),并返回
System.out.println(str.substring(1, 3));//23
//字母转小写,并返回
System.out.println(str.toLowerCase());//123abc
//字母转大写,并返回
System.out.println(str.toUpperCase());//123ABC
String str = " 123 abc DEF 123 ";
//去除首尾空格
System.out.println(str.trim());//123 abc DEF 123
//替换字符,并返回
System.out.println(str.replace('2', 'x'));// 1x3 abc DEF 1x3
//替换第一个出现的字符串,并返回
System.out.println(str.replaceFirst("1", "哈"));// 哈23 abc DEF 123
//替换字符串,并返回
System.out.println(str.replaceAll(" ", ""));//123abcDEF123
String str = "哈x3abcDEF1x3";
//判断两个字符串是否相等(区分大小写)
System.out.println(str.equals("哈x3ABCdef1x3"));//false
//判断两个字符串是否相等(不区分大小写)
System.out.println(str.equalsIgnoreCase("哈x3ABCdef1x3"));//true
//判断目标字符串是否以某个字符串开头
System.out.println(str.startsWith("哈"));//true
//判断目标字符串是否以某个字符串结尾
System.out.println(str.endsWith("DEF1x3"));//true
//获取指定下标上的字符
System.out.println(str.charAt(3));//a
//查询字符第一次出现的下标
System.out.println(str.indexOf("3"));//2
//查询字符最后一次出现的下标
System.out.println(str.lastIndexOf("3"));//11
//获取字符个数
System.out.println(str.length());//12
面试题:以下代码创建了几个String对象
//创建了1个String对象(“abc”在常量池中是唯一个)
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);//true
//创建了3个String对象("abc"算一个,然后又new了两个)
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2);//false:判断的是内存地址
System.out.println(str1.equals(str2));//true:判断的是内容
2. StringBuilder类
StringBuilder称为字符串缓冲区,代表可变的字符序列。
工作原理:预先申请一块内存,存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列。StringBuilder是可变对象,这个是和String最大的不同。
创建StringBuilder对象
//创建StringBuilder的对象(默认字符串缓冲区,初始容量16个字符)
StringBuilder sb1 = new StringBuilder();
//创建StringBuilder的对象(指定字符串缓冲区,初始容量20个字符)
StringBuilder sb2 = new StringBuilder(20);
//创建StringBuilder的对象(指定字符串缓冲区,初始容量16 + "123abc".length()个字符)
StringBuilder sb3 = new StringBuilder("123abc");
StringBuilder常用方法
StringBuilder sb = new StringBuilder("123abc");
//追加字符串,并返回
System.out.println(sb.append("DEF123"));//123abcDEF123
//在指定下标处插入字符串,并返回
System.out.println(sb.insert(6, "哈哈哈"));//123abc哈哈哈DEF123
//替换指定下标上的字符,无返回值
sb.setCharAt(7, '啊');
System.out.println(sb);//123abc哈啊哈DEF123
//从开始下标处(包含)替换到结束下标处(不包含),并返回
System.out.println(sb.replace(6, 9, "嘿嘿嘿"));//123abc嘿嘿嘿DEF123
//删除指定下标处的字符,并返回
System.out.println(sb.deleteCharAt(2));//12abc嘿嘿嘿DEF123
//从开始下标处(包含)删除到结束下标处(不包含),并返回
System.out.println(sb.delete(5, 8));//12abcDEF123
//反转字符串,并返回
System.out.println(sb.reverse());//321FEDcba21
//获取字符长度
System.out.println(sb.length());//11
3. StringBuffer类
StringBuffer与StringBuilder的用法完全一致,StringBuffer和StringBuilder类的区别也在于StringBuffer是线程安全的,很多方法都有synchronized关键字。所以StringBuilder的性能要比StringBuffer要好。多数情况下建议使用 StringBuilder 类。
//StringBuffer类的源码
public synchronized void ensureCapacity(int minimumCapacity) {
...
}
public synchronized void trimToSize() {
super.trimToSize();
}
...
单线程推荐使用StringBuilder,多线程使用StringBuffer。
4. String创建对象的问题
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);//true
//"ab"和"c"都是常量,编译时直接拼接
String str3 = "ab" + "c";//编译时代码直接是String str3 = "abc";
System.out.println(str3 == str1);//true
final String s1 = "ab";
final String s2 = "c";
//s1和s2都是常量,编译时直接拼接
String str4 = s1+s2;
System.out.println(str4 == str1);//true
String s3 = "ab";
String s4 = "c";
//两个 变量 底层会new StringBuilder
String str5 = s3+s4;//底层:new StringBuilder(s3).append(s4).toString();
System.out.println(str5 == str1);//false
频繁的拼接String请使用StringBuilder或StringBuffer
//获取1970年1月1日0:0:0到现在的毫秒值
long startTime = System.currentTimeMillis();
String str = "椎名真白";
for (int i = 0; i < 10001; i++) {
str = str + "小可爱,皇冠给你带";
//new了10001次对象,非常的耗时
//底层:str = new StringBuilder(str).append("小可爱,皇冠给你带").toString();
}
//获取1970年1月1日0:0:0到现在的毫秒值
long endTime = System.currentTimeMillis();
System.out.println("运行时长:" + (endTime-startTime));//610ms
//获取1970年1月1日0:0:0到现在的毫秒值
long startTime = System.currentTimeMillis();
StringBuilder sb = new StringBuilder("樱泽墨");
for (int i = 0; i < 10001; i++) {
sb.append("小可爱,皇冠给你带");
}
//获取1970年1月1日0:0:0到现在的毫秒值
long endTime = System.currentTimeMillis();
System.out.println("运行时长:" + (endTime-startTime));//2ms
扩展:
面试题1: String为什么不可变?
String类源代码中有一个char数组,并且这个char数组是被final修饰的。因为数组一旦创建长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可在指向其它对象,所以String是不可变的!
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
面试题2: StringBuffer和StringBuilder为什么是可变的?
StringBuffer和StringBuilder内部实际上是一个char[ ]数组,这个char[ ]数组没有被final修饰,StringBuffer和StringBulider的初始化容量为16,当存满之后会进行扩容,底层调用了数组拷贝的方法:System.arraycopy()…扩容的,所以StringBuffer和StringBuilder适用于字符串的频繁拼接操作,并且StringBuffer是线程安全的,StringBuilder是非线程安全的。
父类 AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
...
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
...
子类 StringBuilder.java
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
...
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
...