Java-数组/集合工具类

序言

java.util.Arrays类能方便地操作数组,它提供的方法都是静态的。整个Arrays工具类的实现有3000+行,但是归纳总结一下可知它有以下功能(9个)。

1、Arrays工具类

(1) asList

定义:

@SafeVarargs
    public static  List asList(T... a) {
        return new ArrayList<>(a);
    }

功能:

将一个数组(变长参数的语法糖实现就是数组)转变成一个List(确切的来说是ArrayList),注意这个List是定长的,企图添加或者删除数据都会报错

譬如案例1-1:

List<Integer> list = Arrays.asList(3,4,2,1,5,7,6);
System.out.println(list);

//输出结果:
[3, 4, 2, 1, 5, 7, 6] 

但是,对于基础类型(比如byte,int,float等)千万不要想着这么实现(案例1-2,勿效仿):

int a[] = new int[]{1,2,5,4,6,8,7,9};
List list = Arrays.asList(a);

因为List list = Arrays.asList(a);会变成List

    for(int[] arr:list)
        {
            for(int i:arr)
            {
                System.out.println(i);
            }
        }
这样操作就显得非常的烦琐。因为预想List是List形式的,
没想到是List<int[]>形式的。使用的时候要特别的注意一下。

那为什么要转换成集合呢?

要判断某个元素是否在数组中,就不用去遍历数据了,直接使用集合的Contains方法。是不是方便很多;

数组变集合还有一个比较重要的是:基本的数据类型不能直接使用:看代码:
Java-数组/集合工具类_第1张图片
像这个数组,就不能再用 int ,只能用 Integer。

(2) sort

对数组进行排序。适合byte,char,double,float,int,long,short等基本类型,还有Object类型(实现了Comparable接口),如果提供了比较器Comparator也可以适用于泛型。

案例2-1(基础类型,输出:[1, 1, 4, 4, 5, 6, 7, 9]):

    int a[] = new int[]{1,9,5,4,6,4,7,1};
    Arrays.sort(a);
    System.out.println(Arrays.toString(a));

案例2-2(String类型(Object),实现了Comparable接口,输出:[s1, s2, s3, s4]):

    String str[] = {"s2","s4","s1","s3"};
    Arrays.sort(str);
    System.out.println(Arrays.toString(str));

案例2-3 (自定义类型,实现了Comparable接口,输出:[jj:17, zzh:18, qq:19]):

     Person1 persons[] = new Person1[]{
          new Person1("zzh",18),new Person1("jj",17),new Person1("qq",19)
     };
     Arrays.sort(persons);
     System.out.println(Arrays.toString(persons));

案例2-4(泛型,如果类型没有实现Comparable接口,可以通过Comparator实现排序。输出:[jj:17, zzh:18, qq:19] ):

        Person2 persons2[] = new Person2[]{
                new Person2("zzh",18),new Person2("jj",17),new Person2("qq",19)
        };
        //使用了匿名内部类
        Arrays.sort(persons2,new Comparator(){

            @Override
            public int compare(Person2 o1, Person2 o2)
            {
                if(o1 == null || o2 == null)
                    return 0;
                return o1.getAge()-o2.getAge();
            }

        });
        System.out.println(Arrays.toString(persons2));

(3) binarySearch

通过二分查找法对已排序(譬如经过Arrays.sort排序,且按照升序进行排序。如果数组没有经过排序,那么检索结果未知)的数组进行查找

适合byte,char,double,float,int,long,short等基本类型,还有Object类型和泛型(参考sort那段)

案例3-1:

String str[] = {"s2","s4","s1","s3"};
Arrays.sort(str);
System.out.println(Arrays.toString(str));
int ans = Arrays.binarySearch(str, "s1");
System.out.println(ans);

输出:

[s1, s2, s3, s4]
0   //返回的值为查询的目标在数组中的下标

(4) copyOf

数组拷贝,底层采用System.arrayCopy(native方法)实现。

案例4-1:

String str[] = {"s2","s4","s1","s3"};
String str2[] = Arrays.copyOf(str, str.length);
System.out.println(Arrays.toString(str2));

//输出:[s2, s4, s1, s3]

(5) copyOfRange

数组拷贝指定一定的范围,譬如(public static T[] copyOfRange(T[] original, int from, int to))。底层采用System.arrayCopy(native方法)实现。

案例5-1:

String str[] = {"s2","s4","s1","s3"};
String str2[] = Arrays.copyOfRange(str,1,3);
System.out.println(Arrays.toString(str2));

//输出:[s4, s1]

(6) fill

给数组赋值。填充数组之用。

案例7-1:

   String str[] = {"s2","s4","s1","s3",null};
   System.out.println(Arrays.toString(str));
   Arrays.fill(str, "s5");
   System.out.println(Arrays.toString(str));

   //输出
   [s2, s4, s1, s3, null]
   [s5, s5, s5, s5, s5]

(7) equals和deepEquals

equals:判断两个数组的每一个对应的元素是否相等。

案例6-1:

String str1[] = {"s2","s4","s1","s3",null};
String str2[] = Arrays.copyOf(str1, str1.length);
System.out.println(Arrays.equals(str1, str2));

//输出:true

deepEquals:主要针对一个数组中的元素还是数组的情况,类似deepToString, deepHashCode如下:

案例6-2:

   int a1[] = new int[]{1,2,3};
   int a2[] = new int[]{1,3,3};
   int a3[] = new int[]{4,3,2,1};
   int a4[] = new int[]{1,2,3};
   int a5[] = new int[]{1,3,3};
   int a6[] = new int[]{4,3,2,1};
   int[] a [] = new int[][]{a1,a2,a3};
   int[] b [] = new int[][]{a4,a5,a6};

   System.out.println(Arrays.equals(a, b));
   System.out.println(Arrays.deepEquals(a, b));

   //输出结果:
   false
   true

(8) toString和deepToString

toString:对于一个数组int a[] = new int[]{1,9,5,4,6,4,7,1};如果按照System.out.println(a);打印企图可以打印出[1,9,5,4,6,4,7,1],实际上只会打印出[I@3e2de41d这种。在打印数组的时候需要写成Arrays.toString(a)的形式。可参考sort的详解。

deepToString:当数组中又包含数组,即二维数组,那么就不能单存的利用Arrays.toString()了,请看例子。

案例8-1:

  int a1[] = new int[]{1,2,3};
  int a2[] = new int[]{1,3,3};
  int a3[] = new int[]{4,3,2,1};
  int[] a [] = new int[][]{a1,a2,a3};
  System.out.println(Arrays.toString(a));
  System.out.println(Arrays.deepToString(a));

输出结果:

[[I@1b6b7f83, [I@2e807f85, [I@76340c9c]
[[1, 2, 3], [1, 3, 3], [4, 3, 2, 1]]

(9) hashCode和deepHashCode

hashCode:计算一个数组的hashCode.对于一个数组Object[], hashCode方法返回的值取决于:数组中每个元素的元素oi.hashCode()的值初级计算result = 31 * result + (oi== null ? 0 : oi.hashCode());

deepHashCode: 对于一个数组Object[], deepHashCode取决于:数组中每个元素oi,如果oi还是一个数组,那么就继续深入的去获取hashCode,这段比较绕,来个例子比较形象。

案例9-1:

  int a1[] = new int[]{1,2,3};
  int a2[] = new int[]{1,3,3};
  int a3[] = new int[]{4,3,2,1};
  int[] a [] = new int[][]{a1,a2,a3};
  System.out.println(Arrays.hashCode(a));
  System.out.println(Arrays.deepHashCode(a));

运行结果:

-1683374023
31646847

这样可以看到hashCode与deepHashCode的区别。对于数组而言hashCode只调用到它第一层元素,deepHashCode会一直调用直至不能再拆分成数组的元素。


2、Collections工具类

既然集合很常用,那么肯定就会有相应的工具类,可以直接使用,就不用那么累了,对吧,哈哈。集合也有自己的工具类,那就是:Collections。看清楚,这是带有s的,不是Collection接口。

既然是工具类,那么就来看看这个工具类里面有什么方法是我们经常用到的?
Java-数组/集合工具类_第2张图片

(1) 排序sort

Java-数组/集合工具类_第3张图片
这是最基本的排序方式,也就是自然排序,我们也可以传入一个自定义的Comparator 对其进行排序。
Java-数组/集合工具类_第4张图片
这个和上面那个没什么区别,只是sort方法中多传递了一个比较器对其进行排序,排序的依据是根据字符串的长度进行排序,短的排前面,长的排后面。
Java-数组/集合工具类_第5张图片
这个就是刚才的比较器。

(2) 交换swap

Java-数组/集合工具类_第6张图片
看结果,红色框中的两个元素的位置是不是互换了。

(3) 取最大max

Java-数组/集合工具类_第7张图片
有max肯定就还有min,min就不再演示了。

(4) 二分查找binarysearch

Java-数组/集合工具类_第8张图片
有一点需要说明的是:二分查找必须要先对其进行排序,否则是没法找出来或者找出来的是不正确的。

第一行打印的是原集合,第二行打印的是排序后的集合,第三行打印的是该元素的序号,第几位。最后一行打印出来的是-6,这个要特别说明一下的,当打印出来的是负数的时候,说明这个元素在这个集合中不存在

(5) 填充fill

Java-数组/集合工具类_第9张图片
这个方法也很简单,不用过多的解释了,就是将所有的元素替换成指定的元素。

(6) 反转 reverseOrder

反转其实是将比较器进行反转,例如前面的例子中的按照字符串的长度的升序排序的,现在要变成降序了,我们就不用再去写多一个比较器了,可以直接用升序的这个比较器去反转,然后传入反转的这个比较器就可以了。
Java-数组/集合工具类_第10张图片
看打印出来的,第一行是原集合,第二行是根据我写的按照字符串长度排序的比较器对其进行比较的结果,第三行打印的是我们利用反转回来的比较器对其进行比较的结果。

(7) 随机shuffle

Java-数组/集合工具类_第11张图片

3、浅析Comparable与Comparator

Java中Comparable和Comparator接口都是用来比较大小的。

首先来看一下Comparable的定义:

package java.lang;
import java.util.*;
public interface Comparable {
    public int compareTo(T o);
}

Comparator的定义如下:

package java.util;
public interface Comparator {
    int compare(T o1, T o2);
    boolean equals(Object obj);
}

在《Effective Java》一书中,作者Joshua Bloch推荐大家在编写自定义类的时候尽可能的考虑实现一下Comparable接口,一旦实现了Comparable接口,它就可以跟许多泛型算法以及依赖于改接口的集合实现进行协作。你付出很小的努力就可以获得非常强大的功能。

  事实上,Java平台类库中的所有值类都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按年代顺序,那你就应该坚决考虑实现这个接口。
  
  compareTo方法不但允许进行简单的等同性进行比较,而且语序执行顺序比较,除此之外,它与Object的equals方法具有相似的特征,它还是一个泛型。类实现了Comparable接口,就表明它的实例具有内在的排序关系,为已经实现Comparable接口的对象数组进行排序就这么简单: Arrays.sort(a)

(1) Comparable

Comparable对实现它的每个类的对象进行整体排序。这个接口需要类本身去实现(这句话没看懂?没关系,接下来看个例子就明白了)。

若一个类实现了Comparable 接口,实现 Comparable 接口的类的对象的 List 列表 ( 或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。此外,实现 Comparable 接口的类的对象 可以用作 “有序映射 ( 如 TreeMap)” 中的键或 “有序集合 (TreeSet)” 中的元素,而不需要指定比较器。

  举例(类Person1实现了Comparable接口)

public class Person1 implements Comparable<Person1>
{
    private int age;
    private String name;

    public Person1(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    @Override
    public int compareTo(Person1 o)
    {
        return this.age-o.age;
    }
    @Override 
    public String toString()
    {
        return name+":"+age;
    }
}

可以看到Person1实现了Comparable接口中的compareTo方法。实现Comparable接口必须修改自身的类,即在自身类中实现接口中相应的方法。

测试代码:

Person1 person1 = new Person1("zzh",18);
Person1 person2 = new Person1("jj",17);
Person1 person3 = new Person1("qq",19);

List list = new ArrayList<>();
list.add(person1);
list.add(person2);
list.add(person3);

System.out.println(list);
Collections.sort(list);
System.out.println(list);

 输出结果:

[zzh:18, jj:17, qq:19]
[jj:17, zzh:18, qq:19]

(2) Comparator

如果我们的这个类无法修改,譬如String,我们又要对其进行排序,当然String中已经实现了Comparable接口,如果单纯的用String举例就不太形象。对类自身无法修改这就用到了Comparator这个接口(策略模式)。

public final class Person2
{
    private int age;
    private String name;

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

    @Override 
    public String toString()
    {
        return name+":"+age;
    }

    //getter and setter方法省略....
}

 如类Person2,这个类已经固定,无法进行对其类自身的修改,也修饰词final了,你也别想继承再implements Comparable,那么此时怎么办呢?在类的外部使用Comparator的接口。如下测试代码:

        Person2 p1 = new Person2("zzh",18);
        Person2 p2 = new Person2("jj",17);
        Person2 p3 = new Person2("qq",19);
        List list2 = new ArrayList();
        list2.add(p1);
        list2.add(p2);
        list2.add(p3);
        System.out.println(list2);

        //下面使用了匿名内部类创建的对象作为参数
        Collections.sort(list2,new Comparator(){
            @Override
            public int compare(Person2 o1, Person2 o2)
            {
                if(o1 == null || o2 == null)
                    return 0;
                return o1.getAge()-o2.getAge();
            }
        });
        System.out.println(list2);

输出结果:

[zzh:18, jj:17, qq:19]
[jj:17, zzh:18, qq:19] 

  再譬如博主遇到的真实案例中,需要对String进行排序,且不区分大小写,我们知道String中的排序是字典排序,譬如:A a D排序之后为A D a,这样显然不对,那么该怎么办呢?同上(下面代码中的list是一个String的List集合):

   Collections.sort(list, new Comparator()
        {
            @Override
            public int compare(String o1, String o2)
            {
                if(o1 == null || o2 == null)
                    return 0;
                return o1.toUpperCase().compareTo(o2.toUpperCase());
            }
        });

这样就可以实现不区分大小进行排序String的集合了,是不是很方便~

  细心的同学可能会有疑问,明明在Comparator接口中定义了两个方法,为什么继承的时候只实现了一个方法,难道要颠覆我对Java接口常识的理解了嚒?
  
  实际上,我们知道当一个类没有显式继承父类的时候,会有一个默认的父类,即java.lang.Object,在Object类中有一个方法即为equals方法,所以这里并不强制要求实现Comparator接口的类要实现equals方法,直接调用父类的即可,虽然你显式的实现了equals()方法 will be a better choice~

(3) 比较

1、Comparable 是排序接口;若一个类实现了 Comparable 接口,就意味着 “该类支持排序”。
2、 而 Comparator是比较器;我们若需要控制某个类的次序,可以建立一个 “该类的比较器” 来进行排序。

  前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。可以说前者属于 “静态绑定”,而后者可以 “动态绑定”。
  
  我们不难发现:Comparable 相当于 “内部比较器”,而 Comparator 相当于 “外部比较器”。


你可能感兴趣的:(编程开发)