Fork me on GitHub

十七.String类,Arrays工具类,折半查找,包装类

一.String类

1.String不可变的原理

image

String类里面的value是final修饰的

2.StringBuffer和StringBuilder

StringBuffer和StringBuilder非常类似,均代表可变的字符序列。 这两个类都是抽象类AbstractStringBuilder的子类,方法几乎一模一样。

image

image

image

不可变和可变字符序列使用陷阱

String一经初始化后,就不会再改变其内容了。对String字符串的操作实际上是对其副本(原始拷贝)的操作,原来的字符串一点都没有改变。比如:

String s =”a”; 创建了一个字符串

s = s+”b”; 实际上原来的”a”字符串对象已经丢弃了,现在又产生了另一个字符串s+”b”(也就是”ab”)。 如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的时间和空间性能,甚至会造成服务器的崩溃。

相反,StringBuilder和StringBuffer类是对原字符串本身操作的,可以对字符串进行修改而不产生副本拷贝或者产生少量的副本。因此可以在循环中使用。

3.Arrays工具类的使用

JDK提供的java.util.Arrays类,包含了常用的数组操作,方便我们日常开发。Arrays类包含了:排序、查找、填充、打印内容等常见的操作。

打印数组

1
2
3
4
5
6
7
8
import java.util.Arrays;
public class Test {
public static void main(String args[]) {
int[] a = { 1, 2 };
System.out.println(a); // 打印数组引用的值;
System.out.println(Arrays.toString(a)); // 打印数组元素的值;
}
}
注意:
此处的Arrays.toString()方法是Arrays类的静态方法,不是前面讲的Object的toString()方法。
探究:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @author RickYinPeng
* @ClassName Test_07_overload
* @Description 测试方法重载
* @date 2019/1/20/8:49
*/
public class Test_07_overload {

public void show(int a) {
System.out.println(a);
}

/**
* 重载:
* 方法重载返回值不同不能重载
*
* @param a
*/
/*public String show(){}*/
public void show(String a) {
System.out.println(a);

/**
* 1.刚刚我试图去new一个Arrays类,但发现new不出来,我点进源码发现它的构造函数是私有的
* 2.在 Java 中,区别一个方法和另一个方法的关键是“方法签名”。
* 3.方法签名不是单一元素,而是一个组合。方法签名由 方法名+参数列表 构成(返回值不同,Java编程思想中说过)
*
*/
}
/**
* 重写:协变返回
* 1.父类的返回值类型是 Object 而子类的返回值类型是 String 等任意一个,这种情况是没问题的,而且好像有一个很高大上的术语叫“协变返回”
* 2.如果子类的返回值类型是父类的返回值类型的子类,比如例子中 Object 和 String 或者其它自定义类但是具有继承关系的情况。
* 3.注意,假如父类中是 Object 而子类中是 int 不构成协变返回,int 是基本数据类型,和 Object 不构成继承关系。如果是Integer 没问题
* 重写中还需要注意以下两点
* 1.子类重写的方法的访问控制符不能比父类的可见性小可以一样,也可以比父类的大,一般都保持一致\
* 2.在抛出异常方面,子类重写的方法抛出的异常的检查范围不能比父类的大。
*/
}


> 注意新名词:协变返回

数组元素的排序

1
2
3
4
5
6
7
8
9
import java.util.Arrays;
public class Test {
public static void main(String args[]) {
int[] a = {1,2,323,23,543,12,59};
System.out.println(Arrays.toString(a));
Arrays.sort(a);
System.out.println(Arrays.toString(a));
}
}

数组元素是引用类型的排序(Comparable接口的应用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
Man[] msMans = { new Man(3, "a"), new Man(60, "b"), new Man(2, "c") };
Arrays.sort(msMans);
System.out.println(Arrays.toString(msMans));
}
}

class Man implements Comparable {
int age;
int id;
String name;

public Man(int age, String name) {
super();
this.age = age;
this.name = name;
}

public String toString() {
return this.name;
}

public int compareTo(Object o) {
Man man = (Man) o;
if (this.age < man.age) {
return -1;
}
if (this.age > man.age) {
return 1;
}
return 0;
}
}

二分法查找

1
2
3
4
5
6
7
8
9
10
11
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] a = {1,2,323,23,543,12,59};
System.out.println(Arrays.toString(a));
Arrays.sort(a); //使用二分法查找,必须先对数组进行排序;
System.out.println(Arrays.toString(a));
//返回排序后新的索引位置,若未找到返回负数。
System.out.println("该元素的索引:"+Arrays.binarySearch(a, 12));
}
}

二.冒泡排序,优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @author RickYinPeng
* @ClassName Test_08_冒泡排序
* @Description 优化冒泡排序
* @date 2019/1/20/10:01
*/
public class Test_08_冒泡排序 {

public static void main(String[] args) {
int[] values = {3,4,2,4,5,7,1,2,8,9,10};

for(int i = 0;i<values.length;i++){
boolean flag = true;
for(int j = 0;j<values.length-1-i;j++){
if(values[j]>values[j+1]){
int temp = values[j];
values[j] = values[j+1];
values[j+1] = temp;
flag = false;
}
System.out.println(Arrays.toString(values));
}
System.out.println("---------------------------");
if(flag == true){
break;
}
}
}
}
注意的几个点:
1. 外层循环控制总循环次数
2. 内层循环控制两两比较的次数
3. 内层循环之前写错过,来看看,里面有个 j<values.length-1-i (这个i是外层循环的控制变量),为什么要减i呢?
因为你每次两两比较完之后最后一个元素就是最大的,这个元素就不再比较了

1
2
3
4
5
6
7
8
9
10
for(int i = 0;i<values.length;i++){
for(int j = 0;j<values.length-1-i;j++){
if(values[j]>values[j+1]){
int temp = values[j];
values[j] = values[j+1];
values[j+1] = temp;
flag = false;
}
}
}


## 三.折半查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @author RickYinPeng
* @ClassName Test_09_二分查找
* @Description 二分查找
* @date 2019/1/20/11:39
*/
public class Test_09_二分查找 {

public static void main(String[] args) {
int[] arry = {30,20,50,10,80,9,7,12,100,40,8};
Arrays.sort(arry);

int value = 10;

System.out.println(Arrays.toString(arry));
int i = myBinarySearch(arry, value);
System.out.println(i);

}

public static int myBinarySearch(int[] arr,int value){
int low = 0;
int high = arr.length-1;
while(low<=high){
int mid = (low+high)/2;
if(value==arr[mid]){
return mid;
}
if(value>arr[mid]){
low = mid+1;
}

if(value<arr[mid]){
high = mid-1;
}
}
return -1;
}
}


## 四.包装类

Java是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。比如:将基本数据类型存储到Object[]数组或集合中的操作等等。

为了解决这个不足,Java在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。

包装类均位于java.lang包,八种包装类和基本数据类型的对应关系如下

image

在这八个类名中,除了Integer和Character类以外,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写而已。

在这八个类中,除了Character和Boolean以外,其他的都是“数字型”,“数字型”都是java.lang.Number的子类。Number类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数字型”包装类都可以互相转型。

image

image

image

4.1 包装类的用途

  1. 作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如Object[]、集合等的操作
  2. 包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)。

4.2 包装类的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
/** 测试Integer的用法,其他包装类与Integer类似 */
void testInteger() {
// 基本类型转化成Integer对象
Integer int1 = new Integer(10);
Integer int2 = Integer.valueOf(20); // 官方推荐这种写法
// Integer对象转化成int
int a = int1.intValue();
// 字符串转化成Integer对象
Integer int3 = Integer.parseInt("334");
Integer int4 = new Integer("999");
// Integer对象转化成字符串
String str1 = int3.toString();
// 一些常见int类型相关的常量
System.out.println("int能表示的最大整数:" + Integer.MAX_VALUE);
}
public static void main(String[] args) {
Test test = new Test();
test.testInteger();
}
}

4.3 包装类的的缓存问题

整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。

缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。

Integer类相关源码如下:

1
2
3
4
public static Integer getInteger(String nm, int val) {
Integer result = getInteger(nm, null);
return (result == null) ? Integer.valueOf(val) : result;
}

image
这段代码中我们需要解释下面几个问题:

  1. IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。
  2. 一般情况下 IntegerCache.low为-128,IntegerCache.high为127,IntegerCache.cache为内部类的一个静态属性

IntegerCache类相关源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}

由上面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程会在类加载时完成。

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
public class Test3 {
public static void main(String[] args) {
Integer in1 = -128;
Integer in2 = -128;
System.out.println(in1 == in2);//true 因为123在缓存范围内
System.out.println(in1.equals(in2));//true
Integer in3 = 1234;
Integer in4 = 1234;
System.out.println(in3 == in4);//false 因为1234不在缓存范围内
System.out.println(in3.equals(in4));//true
}
}

五.自动装箱和拆箱

其实呢,所谓的自动都是骗人的,只不过是编译器在运行的时候帮我做了,看起来是自动的一样,其实是骗人的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @author RickYinPeng
* @ClassName Test_11_AutoBox
* @Description
* @date 2019/1/20/15:40
*/
public class Test_11_AutoBox {

public static void main(String[] args) {
/**
* 其实编译器帮我执行的是:
* Integer a = Integer.valueof(234);
*/
Integer a = 234;

/**
* 其实编译器帮我执行的是:
* int b = a.intValue();
*/
int b = a;
}
}

注意:

1
2
3
4
Integer c = null;  ----->   Integer c = Integer.valueof(null);

//自动拆箱,报错,空指针异常
int d = c; -----> int d = c.intValue();

上面这两句代码会产生异常,空指针异常

因为我们在调用 int d = c.intValue(); 来进行自动拆箱时c是一个null值,所以报了空指针异常

控制箱异常是怎么报的呢?就是当前你这个对象是null,你还用这个null去调用它的方法,这个时候就会包空指针异常