String相关

1).字符型常量和字符串常量的区别

  1. 形式上:前者单引号,后者双引号
  2. 含义上:字符常量相当于一个整型值(ASCII值),可以参加表达式运算;字符串代表一个地址值(该字符串在内存中存放位置)
  3. 占内存上:字符常量只占一个字节,字符串常量占若干个字节(至少一个字符结束标志)

2).什么是字符串常量池?

  • 定义:位于堆内存,专门用于存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用

3).String 是最基本的数据类型吗

  • 不是,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符
  • String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用
  • Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean
  • 除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型

4).String有哪些特性

  • 不变性:String是只读字符串,是一个典型的immutable对象,对它进行任何操作,其实都是在创建一个新的对象,再把引用指向新对象。
  • 不变模式的作用:当对象被多线程共享并频繁访问时,可以保证数据一致性
  • 常量池优化:String对象创建后,会在字符串常量池中间缓存,如果下次创建相同的对象,直接返回缓存的引用
  • final:使用final定义String类,表示String类不能被继承,提高系统安全性

5).String为什么是不可变的吗?

简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:

/** The value is used for character storage. */
private final char value[];

6).String真的是不可变的吗?

1.不可变,但引用可变

  • 例子:
String str = "Hello";
str = str + " World";
System.out.println("str=" + str);
  • 结果:
str=Hello World
  • 分析:
    实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符

2.通过反射可以修改所谓的“不可变”对象

  • 例子:
// 创建字符串"Hello World", 并赋给引用s
String s = "Hello World";

System.out.println("s = " + s); // Hello World

// 获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");

// 改变value属性的访问权限
valueFieldOfString.setAccessible(true);

// 获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);

// 改变value所引用的数组中的第5个字符
value[5] = '_';

System.out.println("s = " + s); // Hello_World
  • 结果:
s = Hello World
s = Hello_World
  • 分析:
    用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。

7).是否可以继承 String 类

String 类是 final 类,不可以被继承

8).String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中

9).String s = new String(“xyz”);创建了几个字符串对象

  • 两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象

  • 例子:

String str1 = "hello"; //str1指向静态区
String str2 = new String("hello");  //str2指向堆上的对象
String str3 = "hello";
String str4 = new String("hello");
System.out.println(str1.equals(str2)); //true
System.out.println(str2.equals(str4)); //true
System.out.println(str1 == str3); //true
System.out.println(str1 == str2); //false
System.out.println(str2 == str4); //false
System.out.println(str2 == "hello"); //false
str2 = str1;
System.out.println(str2 == "hello"); //true

10).如何将字符串反转?

  • 使用 StringBuilder 或者 stringBuffer 的 reverse() 方法

  • 例子:

// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba

11).数组有没有 length()方法?String 有没有 length()方法

  • 数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆

12).String 类的常用方法都有那些?

  • length()
  • charAt()
  • indexOf():返回指定字符的索引
  • replace():字符串替换
  • trim():去除字符串两端空白
  • split():分割字符串,返回一个分割后的字符串数组
  • getBytes():返回字符串的 byte 类型数组
  • toLowerCase()
  • toUpperCase()
  • substring()
  • equals()

13).在使用 HashMap 的时候,用 String 做 key 有什么好处?

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快

14).String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的

  • 可变性:String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
  • 线程安全性:String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
  • 性能:每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
  • 对于三者的总结:
  1. 如果要操作少量的数据用 = String
  2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer

15).拼接字符串的几种方式

1、使用+拼接字符串

在Java中,拼接字符串最简单的方式就是直接使用符号+来拼接。

通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。那么也就是说,Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。

String wechat = "Hollis";  

String introduce = "每日更新Java相关技术文章";  

String hollis = wechat + "," + introduce;

2、concat

使用String类中的方法concat方法来拼接字符串。

concat方法:首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。

通过源码我们也可以看到,经过concat方法,其实是new了一个新的String。

String str1 = "aaa"; 
String str2 = "bbb"; 
String str = str1.concat(",").concat(str2);

3、StringBuffer 线程安全

可以用来定义字符串变量的StringBuffer类,它的对象是可以扩充和修改的。

使用StringBuffer可以方便的对字符串进行拼接。如:

StringBuffer sb1 = new StringBuffer("aaa"); 
String str1 = "bbb"; 
StringBuffer sb2 = sb1.append(",").append(str1);

4、StringBuilder  该类继承了AbstractStringBuilder类

还有一个类StringBuilder也可以使用,其用法和StringBuffer类似。如:

StringBuilder sb1 = new StringBuilder("aaa"); 
String str1 = "bbb"; 
StringBuilder sb2= sb1 .append(",").append(str1 );

结论:

1.使用StringBuilder的方式是效率最高的。

2.如果不是在循环体中进行字符串拼接的话,直接使用+就好了。

3.如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder。

16).为什么不在循环中使用+进行字符串拼接

17).String、new String()创建对象数

String name1 = "Tom";  	//创建了一个String类型的对象
String name2 = "Lu"+"cy";	//创建了一个String类型的对象(先拼接后创建对象,所以是一个)

String str = "Ja";
String name3 = str + "mes";		//这两行共创建了两个String类型的对象

String name4 = new String("Katty");//创建了两个String类型的对象(字符串"Katty"创建一个,new对象时又创建一个,共两个)

String letters = "a";
for(int i=1; i<=3; i++){
	letters = "a"+letters;
} 		//共创建了4个对象(刚开始创建一个对象,循环三次又创建了3个对象,因此一共创建了四个对象),
		//这样拼接的缺点就是不停地创建新对象,从而浪费内存

18).用StringBuffer创建对象进行拼接时就不会再创建新对象,也就不会浪费太多的内存

StringBuffer name = new StringBuffer("ha");
for(int i=1; i<=3; i++){
	name.append("ha");					//拼接
}
System.out.println(name.toString());	//输出:hahahaha
System.out.println(name.length());		//返回字符的个数,此处输出:8
System.out.println(name.reverse());		//反转,此处输出:ahahahah

这几行代码一共只创建了name一个对象

包装类相关

1).自动装箱与拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来
  • 拆箱:将包装类型转换为基本数据类型
  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

2).int 和 Integer 有什么区别

Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

Integer是int提供的封装类,从java5之后引入了自动装箱、拆箱机制,使得两者可以相互转换,而int是java基本数据类型
Integer默认值是null,而int默认值是0
Integer是对象,用一个引用指向这个对象,而int是基本类型,直接存储数据。
Integer提供了好多与整数相关的操作方法,例如:将一个字符串转换成整数等.
Integer会有缓存

3).Integer a= 127 与 Integer b = 127相等吗

  • 对于对象引用类型:== 比较的是对象的内存地址

  • 对于基本数据类型:==比较的是值

  • 如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false

  • 例子

public static void main(String[] args) {
    Integer a = new Integer(3);
    Integer b = 3;  // 将3自动装箱成Integer类型
    int c = 3;
    System.out.println(a == b); // false 两个引用没有引用同一对象
    System.out.println(a == c); // true a自动拆箱成int类型再和c比较
    System.out.println(b == c); // true

    Integer a1 = 128;
    Integer b1 = 128;
    System.out.println(a1 == b1); // false

    Integer a2 = 127;
    Integer b2 = 127;
    System.out.println(a2 == b2); // true
}

4).为什么在接口设计的时候推荐使用包装类型而不推荐使用基本数据类型?

① Java 的设计思想是万物既对象,包装类体现了面向对象的设计理念;

② 包装类包含了很多属性和方法,比基本数据类型功能多,比如提供的获取哈希值(hashCode)或获取类(getClass)的方法等。

我们知道Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这时就需要这些基本类型的包装器类了。