[java复习] java常用类(含面试题讲解)

375 阅读11分钟

本文章是复习Java时整理而成,部分内容不全面。

1. 包装类

包装类(wrapper)是针对八种基本数据类型定义的相应引用类型。从而使得基本数据类型有了类的特点,可以调用类中的方法。对应关系如下:

序号基本数据类型包装类
1booleanBoolean
2charCharacter
3byteByte
4shortShort
5intInteger
6longLong
7floatFloat
8doubleDouble

1.1 Boolean、Character

Boolean包装类和Character包装类的层级结构图如下:

image-20220325211730290.png

1.2 Byte、Short、Integer、Long、Float、Double

Byte、Short、Integer、Long、Float、Double这几个包装类都是整形类型的包装类,其结构层次图如下:

image-20220325211544697.png

1.3 装箱和拆箱

1.3.1 装箱

所谓的装箱就是将基本数据类型转为对应的包装类。装箱又分为手动装箱自动装箱(JDK5+) 。以下案例使用int基本类型和Integer包装类进行讲解。其它类型都类似。

手动装箱就是通过Xxx.valueOf(value)方法来由技术人员手动将基本类型value转为包装类。也可以通过new的方式创建一个包装类对象。

 //手动装箱
 int a = 12;
 Integer value1 = new Integer(12);
 Integer value2 = Integer.valueOf(a);

在JDK5及其以后新增了自动装箱的功能:

 // 自动装箱,调试运行
 int i1 = 2;
 Integer integer = i1;

自动装箱的本质还是在底层调用了Xxx.valueOf(value)方法,这部分可以通过调试运行查看。

1.3.2 拆箱

拆箱是装箱的逆过程,即将包装类转为基本数据类型。同样,也有手动拆箱主动拆箱

手动拆箱是通过使用调用xxx.Value()方法来实现。主动装箱的底层同样是调用了Xxx.value()方法。

 //手动拆箱
 int v1 = value1.intValue();
 int v2 = value2.intValue();
 ​
 // 自动拆箱
 Integer integer =3;
 int vv = integer;

1.4 面试题

  1. 阅读下列代码,写出输出结果?
 public static void question(){
     Integer i = new Integer(1);
     Integer j = new Integer(1);
     System.out.println(i==j); // false,
 ​
     Integer m =1;
     Integer n =1;
     System.out.println(n==m); // true
 ​
     Integer x = 128;
     Integer y = 128;
     System.out.println(x==y); // fasle
     
     Integer i11 = 127;
     int i12 = 127;
     System.out.println(i11==i12) // true
 }

输出结果为:

 false
 true
 false
 true

解释如下:

  1. 第一个输出false,因为是通过new关键字 创建的两个对象,因此i和j的内存地址不同,输出false。
  2. Integer n = 1的执行本质上是调用了Integer.valueOf(int value)方法,查看valueOf的源码可知:当value的值在128-127之间时,总会缓存,当再次调用valueOf(int value)方法且value的值在-128~127之间时,直接返回缓存中对应数据的地址,而不是创建新的内存地址。
  3. 同理,value的值大于127,则会new一个对象。
  4. ==作用于基本数据类型时,总是比较值是否相等。

2. String类型

在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。String类是final类,其保存的是常量。因此String类是一个不可变类,每次更新都需要重新开辟新的空间。

2.1 创建String对象的两种方式

2.1.1 直接赋值法

 String str = "qwe";

使用这种形式创建字符串对象时,先从常量池中查找是否含有“qwe”数据空间,若有,则直接指向该数据空间;若没有,则创建该数据空间,然后指向该数据空间。使用这种方式创建的好处是:避免了相同值的字符串重复创建,节约了内存。 因此,该String对象指向的是“qwe”的数据空间。

2.1.2 调用构造器

 String str  = new String("qwe");

先在堆中创建空间,里面维护了value属性,然后value属性指向了常量池中的“qwe”空间。若常量池中没有“qwe”空间,,则创建然后指向。因此,该String对象最终指向的时该堆的空间地址

2.2 String面试题

面试题1:

阅读以下代码,写出输出结果:

 public static void test01() {
 ​
     String str1 = "qwe";
     String str2 = "qwe";
     System.out.println(str1 == str2); // true
     System.out.println(str1.equals(str2)); // true
 ​
     String s1 = new String("qwe");
     String s2 = new String("qwe");
     System.out.println(s1 == s2); // fasle
     System.out.println(s1.equals(s2));  // true
 ​
     System.out.println(str1 == s1); // false
     System.out.println(str1.equals(s1)); //true
 ​
     System.out.println(str1 == s1.intern()); // true
     System.out.println(s1 == s1.intern()); // false
     System.out.println(str1==str1.intern()); // true
 ​
 }

原因如下:

  1. true:两者都是指向的常量池中的“qwe”对象。

  2. true:String对象重写了equals方法,用于比较值是否相同。

  3. false:是两个对象,因此地址不同。

  4. true:String对象重写了equals方法,用于比较值是否相同。

  5. false:两者对象地址不同

  6. true:同4。比较值。

    String.intern()方法方法返回的是该String对象在常量池中对应的String对象的地址。先查找,若没有,则创建。

  7. ture: str1指向的是在常量池中对应的String对象的地址,s1.intern()方法能够在池中查找到”qwe“对象,因此返回的是该string对象的地址。两者相同。

  8. false: s1指向的是在堆中该String对象的地址,而s1.intern()方法是返回的在池中对应的”qwe“对象的地址。两者不同。

  9. true: 同7

面试题2:

阅读下面代码,写出运行结果:

 public static void test02() {
 ​
     Person p1 = new Person();
     p1.name = "xcy";
     Person p2 = new Person();
     p2.name = "xcy";
 ​
     System.out.println("p1.name.equals(p2.name):" + p1.name.equals(p2.name)); // true
     System.out.println("p1.name ==p2.name:" + (p1.name == p2.name)); // true
     System.out.println("p1.name == "xcy":"+(p1.name == "xcy")); // true
 ​
 }

原因如下:

  1. true: String对象重写了equals方法,用于比较值是否相同。
  2. true:两者的name属性都是通过直接赋值的方式创建的,因此指向的都是常量池中的”xcy“数空间。若将其中一个对象的属性改为使用构造器创建,则输出结果为false
  3. true:两者都是指向的常量池中的xcy数据空间。

2.3 String类的特性

查看String类的源码可知:

 public final class String
     implements java.io.Serializable, Comparable<String>, CharSequence {……}

String类是一个final类,其value属性也是final属性,代表不可变的字符串序列。一个字符串对象一旦被分配,则其内容是不能改变的。在修改对象时则会创建一个的对象,并将原有对象的引用指向新对象的地址

 String s1 = "xcy";
 s1 = "coder xcy";

以上代码,执行的过程,创建了两个String对象。

我们再来看几个代码示例:

面试题一: 以下代码执行的过程中,创建了几个String对象?

 String a = "hello" + "abc";

按照上面我们分析的逻辑,首先程序需要在常量池中创建hello对象和abc对象,然后创建helloabc对象,最后将该对象的地址引用给a。因此创建了3个对象。这是错误的!

对于编译器来说,它会优化此类型的执行过程:即String a = "hello"+"abc"被优化为了String a = "helloabc"因此该执行过程只创建了一个对象

面试题二: 以下代码执行的过程中,创建了几个String对象?画出内存图。

 String a = "hello";
 String b = "abc";
 Stirng c = a+b;

结论:创建了三个对象。

原理(调试运行):

  1. 在常量池中创建hello对象
  2. 在常量池中创建abc对象
  3. 创建StringBuilder对象:StringBuilder sb = new StringBuilder()
  4. hello字符串append到sb对象中:sb.append("hello")
  5. abc字符串append到sb对象中:sb.append("abc")
  6. 调用sb.toString()方法:String c = sb.toString()
  7. 即c指向的是堆中的一个字符串对象,其value属性指向常量池中helloabc数据空间。
  8. 因此,整个过程都创建了helloabchelloabc三个String对象。

面试题三: 阅读下面代码,写出输出结果,并解释原因。

 public static void test01() {
     String a = "hello";
     String b = "abc";
     String c = "helloabc";
     String d = (a + b).intern();
 ​
     System.out.println(c == d); // true
     System.out.println(c.equals(d)); // true
 }

输出结果为:

 true
 true

String.intern()方法的原理可知,返回值为常量池中的对应对象。因此,c和d的地址相同。后者是比较的值,因此也相同。

2.4 String 类型的传参方式

面试题四: 下列程序的运行结果是什么?尝试画出内存布局图

 class Test02{
     String str = new String("xcy");
     final char[] ch = {'j','a','v','a'};
 ​
     public void change(String str,char[] ch){
         str = "java";
         ch[0] = 'h';
     }
 ​
     public static void main(String[] args) {
         Test02 ex = new Test02();
         ex.change(ex.str,ex.ch);
         System.out.println(ex.str+"and"); // xcyand 
         System.out.println(ex.ch); // hava
     }
 }

输出结果如下:

 xcyand
 hava

原因如下:

在java中,引用数据类型的传参方式是传地址。即在change()方法中,str参数保存的是变量ex.str的引用(地址);ch参数保存的是ex.ch的引用(地址)。而语句str="java"的执行结果则是将str的值(引用地址)替换为"java"字符串的空间地址,但实际上ex.str的引用并未发生改变(因为String类型是不可变类)。因此,ex.str指向的仍是xcy的对象地址。语句ch[0]='h'的执行则是根据ch的引用,将该地址上的0位置的数据替换为了hch的引用并未发生改变。

可以简单理解为:

java传递参数都是,只不过对象传递的是“地址”的值,就是将引用的值复制一份给方法当参数。如果是根据引用把堆里的对象修改了,那么对象真被修改了(如问题代码中的ch[0]='h' ,不过不是被创建赋值给的那个引用修改的,是方法里的一个复制的引用副本给修改的。如果是将该参数的值(即引用)给覆盖了(如问题代码中的str="java"),那么再怎么修改该参数,原有的实参都不会发生改变。

2.5 String类的常用方法

序号方法描述
1equals()判断内容是否相等,区分大小写
2equalsIgnoreCase()判断内容是否相等,不区分大小写
3length()获取字符串的长度
4indexOf()获取字符(串)在当前字符串中第一次出现的索引,没有则返回-1
5lastIndexOf()获取字符(串)在当前字符串中最后一次出现的索引,没有则返回-1
6subString()截取指定范围内的字符串,参数为起始索引和介素索引,遵循前闭后开原则
7trim()去除前后空格
8charAt()获取某个索引位置的字符。
9toUpperCase()将字符全部转为大写
10toLowerCase()将字符全部转为小写
11replace()替换字符串中的指定字符
12split()以指定的字符(串)作为分隔符,将字符串进行分割
13compareTo()比较两个字符串的大小。逐个比较字符的ASIIC码,返回第一个不同码的差。若一个字符串的开头为另外一个字符串,则返回长度的插值。
14toCharArray()将当前字符串转换成字符数组
15fotmat()格式化字符串。%s 字符串;%c字符;%d整型;%.2f浮点型(四舍五入)

3. StringBuffer类

java.lang.StringBuffer类代表的是可变字符串,可以对字符串内容进行增删改。StringBuffer对象的长度是可变的。通过源码可知:StringBuffer继承自StringBuilder类,其value属性不再是final类型,因此是可变的(地址不变)。

是线程安全的。

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;
    ……
}

image-20220327145150145.png

3.1 StringBuffer构造器

3.1.1 StringBuffer()

构造一个空的、初始容量为16的字符串缓冲区对象。

 // 源码
 public StringBuffer() {
     super(16);
 }

3.1.2 StringBuffer(CharSequence seq)

构建一个字符串缓冲区对象,它包含与seq相同 的字符。

3.1.3 StringBuffer(int capacity)

构建一个指定容量的字符串缓冲区对象。

 public StringBuffer(int capacity) {
     super(capacity);
 }

3.1.4 StringBuffer(String str)

通过给定的字符串,创建一个字符串缓冲区对象,它包含该字符串。默认长度为,该字符串的长度+16。

 public StringBuffer(String str) {
     super(str.length() + 16);
     append(str);
 }

3.2 String类型和StringBuffer类型的相互转换

  • String类型转StringBuffer类型
 StringBuffer sBuffer1 = new StringBuffer(str);
 StringBuffer sBuffer2 = new StringBuffer();
 sBuffer2.append(str);
  • StringBuffer类型转为String类型使用toString()方法即可。

3.3 StringBuffer常用方法

序号方法描述
1append()在当前字符串后追加字符串
2delete(startIndex,endIndex)删除指定下标位置的字符串(前闭后开)
3replace()替换字符串
4indexOf()获取字符(串)在当前字符串中第一次出现的索引,没有则返回-1
5insert()在指定下表位置插入字符(串)
6length()获取字串的长度

4. StringBuilder类

java.lang.StringBuilder类是一个可变的字符串序列对象。它提供了和StringBuffer类兼容的API。但它不是线程安全的,效率高于StringBuffer。

String、StringBuffer、StringBuilder三者比较

  • String不可变字符串,效率较低,但是复用性强。
  • StringBuffer:可变字符串,效率较高,是线程安全的。
  • StringBuilder: 可变字符串,效率最高,但不是线程安全的。

5.Math类

在 Java 中 Math 类封装了常用的数学运算,提供了基本的数学操作,如指数、对数、平方根和三角函数等。Math 类位于 java.lang 包,它的构造方法是 private 的,因此无法创建 Math 类的对象,并且 Math 类中的所有方法都是类方法,可以直接通过类名来调用它们。

Math当作工具类使用即可,即查即用,没必要记住其方法。

Java8在线API文档