1.包装类
- 概念:针对八种基本数据类型相应的引用类型为包装类(Wrapper),具有了类的特点
基本数据类型 包装类 boolean Boolean char Character byte Byte short Short int Integer long Long float Float double Double
装箱和拆箱:
- 装箱:基本类型->包装类型
- 拆箱:包装类型->基本类型
- 手动装箱/拆箱:JDK5之前为手动装箱/拆箱,如下代码:
int i = 10; // 手动装箱的两种方式 Integer i1 = new Integer(i); Integer i2 = Integer.valueOf(i); // 手动拆箱 int j = i1.intValue();
- 自动装箱/拆箱:JDK5后为自动装箱/拆箱,自动装箱底层调用valueOf()方法,自动拆箱底层调用intValue()方法,如下代码:
int i = 10; // 自动装箱 Integer i1 = i; // 自动拆箱 int j = i1; // 包装类->String Integer i2 = 100; String str1 = i2 + ""; // 方式1 String str2 = i2.toString() // 方式2 String str3 = String.valueOf(i) // 方式3 // String->包装类 String s = "123"; Integer i3 = Integer.parseInt(s); Integer i4 = new Integer(s);
tips:
- 除了Boolean和Character类,其他都是继承Number类
- 三元运算符是一个整体:
Object o1 = true ? new Integer(1) : new Double(2.0); System.out.println(o1); // 输出1.0而不是1,因为三元运算符是一个整体,所以遇到new Double进行了类型提升
Integer.valueOf
源码涉及到面试题:Integer i1 = new Integer(100); Integer i2 = new Integer(100); Integer m = 1; // 自动装箱,底层运行Integer.valueOf(),在源码中没有new Integer(1),直接从数组返回 Integer n = 1; // 自动装箱,底层运行Integer.valueOf(),在源码中没有new Integer(1),直接从数组返回 Integer x = 128; // 自动装箱,底层运行Integer.valueOf(),在源码中运行new Integer(128) Integer y = 128; // 自动装箱,底层运行Integer.valueOf(),在源码中运行new Integer(128) Integer i3 = 128; int i4 = 128; System.out.println(i3 == i4); // true,只要有基本数据类型,则==判断的是值是否相等 System.out.println(i1 == i2); // false System.out.println(m == n); // true System.out.println(x == y); // false // ---------valueOf源码--------- public static Integer valueOf(int i) { // low=-128,high=127 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);
2.String类
创建方式:
- 直接赋值:
String s1 = "psj";
。先从常量池看是否有“psj”
数据空间,有就直接指向,没有就重新创建然后指向(对象s1的地址为0x222)- 使用构造器:
String s2 = new String("psj");
。先在堆中创建空间,里面维护了value属性且该属性指向常量池的“psj”
数据空间(对象s2的地址为0x111不是0x222)
tips:
- 字符串的字符使用Unicode编码,即一个字符(无论字母还是汉字)占两个字节
- String类实现了
Serializable
接口,说明可以串行化,即可以在网络传输- String类是final类,不能被继承,同时代表不可变的字符序列,即一个字符串对象一旦被分配,内容不可变:
String s1 = "hello"; s1 = "haha"; // 上面代码并不是将hello所在地址的内容改为haha,而是在常量池中创建了haha,s1再指向haha所在地址(创建了两个对象) String s2 = "hello" + "world"; // 上面代码只在常量池中创建了helloworld(假设地址为0x222),并不会额外创建hello和world(编译器做了优化,如果额外创建了那两个对象会造成浪费) String a = "hello"; String b = "world"; String c = a + b; // 上面代码中String c = a + b实际执行了: // 1. 创建StringBuilder对象sb // 2. 执行sb.append("hello") // 3. 执行sb.append("world") // 4. String c = sb.toString(),toString方法中执行new String(...)(创建的String对象假设地址为0x333,但是里面的value属性还是指向0x222) // 最终还是创建了三个对象:常量池中的hello、world以及堆中的0x333 System.out.println(s2 == c); // false,c是指向堆0x333而s2指向常量池0x222
- String类有属性
private final byte[] value
,用于存放字符串内容(这里为final类型表示value在堆中的地址无法被修改,但是指向常量池的地址可以被修改):final int a = 1; a = 2; // 报错 final char[] value = {'a'}; final char[] value2 = {'b'}; value[0] = 'b'; // 不会报错 value = value2; // 报错
- String类的直接赋值和包装类的直接赋值是有区别的:
String a = "psj"; String b = "psj"; // 指向常量池的同一个地址,所以为true。如果为包装类的直接赋值,实际上是自动装箱,涉及到创建对象 System.out.println(a == b); // true
- String类的replace、concat等方法执行后对于原来的字符串是没有影响的
- 相同的字符串调用hashCode方法,得到的值是一样的,与内存地址、进程、机器无关:
String s1 = new String("psj"); String s2 = new String("psj"); System.out.println(s1 == s2); // false // 两个字符串对象的hashcode值相同 System.out.println(s1.hashCode()); System.out.println(s2.hashCode());
3.StringBuffer类
String
vsStringBuffer
:
string
保存的是字符串常量(即value指向的内容在常量池),里面的值不能更改,每次更新实际上是更改指向常量池的地址(实际上是在常量池中创建了新的对象),效率较低StringBuffer
保存的是字符串变量(即value指向的内容在堆中),里面的值可以更改,每次更新实际上是更新内容,不用每次更新地址(创建新对象),效率较高
String
和StringBuffer
之间的转换:
String
->StringBuffer
:
- 使用构造器:
String s = new String("psj"); StringBuffer sb = new StringBuffer(s);
- 使用append方法:
String s = new String("psj"); StringBuffer sb = new StringBuffer(); sb.append(s);
StringBuffer
->String
:
- 使用
toString
方法:StringBuffer sb = new StringBuffer("psj"); String s = sb.toString();
- 使用构造器:
StringBuffer sb = new StringBuffer("psj"); String s = new String(sb);
特点:
StringBuffer
类的父类是AbstractStringBuilder
类。该父类中有属性byte[] value
,用于存放字符串内容,且由于没有final修饰,所以字符串存放在堆中不是常量池中(即value指向的是堆,不再是常量池)
StringBuffer
类实现了Serializable
接口,说明可以串行化,即可以在网络传输
StringBuffer
类是final类,不能被继承tips:
StringBuffer()
方法构造不带字符的字符串缓冲区时,初始容量为16个字符。StringBuffer("psj")
方法构造的字符串缓冲区容量为字符串的长度加上16- 当字符串为空时使用
StringBuffer
的append
方法不会报错,但是直接在构造器中使用会出错:String s = null; StringBuffer sb = new StringBuffer(); sb.append(s); System.out.println(sb); // 打印"null",因为底层调用的是AbstractStringBuilder类的appendNull方法 StringBuffer sb1 = new StringBuffer(s); // 报错,底层源码为super(str.length() + 16),执行str.length()会出现空指针异常
4.StringBuilder类
- 特点:
- 可变的字符序列
- 方法没有做互斥处理(即不是线程安全),用在字符串缓冲区被单个线程使用。单线程时优先采用该类,它比
StringBuffer
类要快StringBuilder
类是final类,不能被继承StringBuilder
类的父类是AbstractStringBuilder
类,和StringBuffer
类一样字符串存放在堆中不是常量池中StringBuilder
类实现了Serializable
接口,说明可以串行化,即可以在网络传输String
vsStringBuffer
vsStringBuilder
:*
String
是不可变字符序列,效率低,但是复用率高(比如有多个字符串对象赋值psj
,指向的都是常量池的同一个对象)StringBuffer
是可变字符序列,线程安全,效率较高StringBuilder
是可变字符序列,线程不安全,效率最高- 对字符串做大量修改时不要使用
String
;字符串有大量修改操作且单线程情况下使用StringBuilder
,多线程情况下就使用StringBuffer
,比如执行String s = "a";s+="b";
,实际上原来的a
字符串对象被丢弃,并且产生一个字符串ab
,多次执行会产生大量的副本留着内存降低效率。
5.其他类
tips:
- 返回[a,b]之间的随机整数:
(int)(Math.random()*(b-a+1)+a)
,因为Math.random()
返回的是[0,1),右边是开区间,所以在代码中需要改为b-a+1
Arrays.sort()
定制排序(只针对包装类):Integer[] integers = {1, 2, 5, 0}; Arrays.sort(integers, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); System.out.println(Arrays.toString(integers)); // [5, 2, 1, 0]
上述方法底层调用的是
binarySort
方法,如果要底层实现排序的方法为冒泡排序代码如下:int[] arr = {1, 5, -1, 9}; bubble(arr, new Comparator() { @Override public int compare(Object o1, Object o2) { int i1 = (Integer)o1; // 涉及到自动拆箱 int i2 = (Integer)o2; return i1 - i2; } }); // bubble方法实现 public static void bubble(int[] arr, Comparator c) { int temp = 0; for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { // 数组排序由c.compare()返回值决定 if (c.compare(arr[j], arr[j + 1]) > 0) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
System.arraycopy(...)
比较适合底层调用,一般使用Arrays.copyOf(...)
完成复制数组(底层代码调用前者)- 处理很大的整数时,如果
long
类型不够用,可以使用BigInteger
类:BigInteger b1 = new BigInteger("151549494949494848488"); BigInteger b2 = new BigInteger("1518181151"); BigInteger b3 = b1.add(b2); // 不能直接使用加号等符号进行运算 System.out.println(b3);
- 需要保存一个精度很高的数时,
double
类型的精度不够使用,可以使用BigDecimal
类:BigDecimal b1 = new BigDecimal("121.15151881811818181"); BigDecimal b2 = new BigDecimal("12.81811818181"); BigDecimal b3 = b1.add(b2); // 不能直接使用加号等符号进行运算 System.out.println(b3); BigDecimal b1 = new BigDecimal("19.15151881811818181"); BigDecimal b2 = new BigDecimal("1.1"); // 使用除法时可以会报错(无限循环等),此时指定精度即可,结果会保留分子的精度 BigDecimal b3 = b1.divide(b2, BigDecimal.ROUND_CEILING);
Date
类默认输出的是国外的日期格式,需要使用SimpleDateFormat
类进行格式转换:Date date = new Date(); // 获取系统当前时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E"); // 指定格式 String format = sdf.format(date); System.out.println(format); // 2022年02月09日 10:21:07 周三 // 可以把格式化的字符串转换成对应的Date(字符串的格式需要和SimpleDateFormat中指定的格式一致) Date parse = sdf.parse("2022年02月09日 10:21:07 周三"); System.out.println(parse); // Wed Feb 09 10:21:07 CST 2022
- 日期类
Canlendar
是一个抽象类,且构造器由protected
修饰,需要通过getInstance
方法获取实例,不能直接new:Calendar calendar = Calendar.getInstance(); // Calender类没有专门的格式化方法,需要自己组合 System.out.println(calendar.get(Calendar.YEAR) + "年");