黑马程序员——集合框架5:集合工具类

来源:互联网 发布:arm linux gcc 是什么 编辑:程序博客网 时间:2024/06/10 17:36
------- android培训、java培训、期待与您交流! ----------

       在前面的内容中,我们已经将《集合框架1:体系概述》中的常见成员全部介绍完了,下面我们再补充介绍两个同样包含于集合框架中的工具类——Collections和Arrays。

1.     Collections工具类

       Collections本身并不是集合类,它是用来对集合对象进行各种操作的工具。那么工具类的最大特点就是——它的方法都是静态的,也就是说,由于该类不需要存储任何特有数据,因而不用创建Collections类的对象,只需要对传入的集合对象进行操作即可。比如,Collections就对外提供了对集合中元素进行排序的方法——sort,因为除了TreeSet和TreeMap以外,其他集合类是不能自动对其内的元素进行排序的。下面我们就从sort方法开始,介绍几个Collections类常用的几个静态方法。

1.1  sort——排序

       从Collections类的API文档可知,sort有两个重载方法,如下所示:

(1)    public static <T extends Comparable<? super T>> voidsort(List<T> list):根据元素的自然顺序对指定列表按升序进行排列。

方法说明:由于Collections类是工具类,因此无法事先确定将要操作的List集合中存储的元素类型,因此该方法定义为了泛型方法。泛型表达式中的T代表操作的集合中的元素类型,extends Comparable表示,T类必须是Comparable接口的子类(实际上通常是实现类),这样才能具备比较性,从而按照compareTo方法对元素进行排序。<? super T>表示,定义T类时实现的Comparable接口的泛型类型可以是T的父类,这样能够进一步提高扩展性。大家需要注意的是,sort方法只能用于对List集合进行排序,其他集合类是不适用的。

方法演示:

代码1:

import java.util.*; class CollectionsDemo{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("WaterMelon");list.add("Pear");//梨list.add("Mango");list.add("Pomelo");//柚子list.add("WaterMelon");//存储重复元素list.add("Orange");list.add("Pineapple");System.out.println(list);//打印排序前的集合 Collections.sort(list);//排序 System.out.println(list);//打印排序后的集合}}
执行结果为:

[Apple, WaterMelon, Pear, Mango, Pomelo,WaterMelon, Orange, Pineapple]

[Apple, Mango, Orange, Pear, Pineapple,Pomelo, WaterMelon, WaterMelon]

结果显示,sort方法将集合中的字符串对象按照字母顺序进行了排序。

(2)    public static <T> void sort(List<T>list,Comparator<?super T> c):根据指定比较器产生的顺序对指定列表进行排序。

方法说明:该方法同样为泛型方法,在传递集合的同时,还需要传递一个比较器对象。

方法演示:

代码2:

import java.util.*; //定义比较器,长度为主要条件,字母顺序为次要条件class StrLenComparator implementsComparator<String>{public int compare(String str1, String str2){int value =new Integer(new Integer(str1.length())).compareTo(new Integer(str2.length())); if(value== 0)return str1.compareTo(str2);return value;}}class CollectionsDemo2{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("WaterMelon");list.add("Pear");list.add("Mango");list.add("Pomelo");//柚子list.add("WaterMelon");//存储重复元素list.add("Orange");list.add("Pineapple"); System.out.println(list);//打印排序前的集合 Collections.sort(list,newStrLenComparator());//使用比较器排序 System.out.println(list);//打印排序后的集合}}
执行结果为:

[Apple, WaterMelon, Pear, Mango, Pomelo,WaterMelon, Orange, Pineapple]

[Pear, Apple, Mango, Orange, Pomelo, Pineapple,WaterMelon, WaterMelon]

结果表明,通过比较器将字符串按照长度进行了排序,相同长度的字符串又按照字母顺序进行了排序。

1.2  max与min——获取最值

       这里指的最值,并不是指的最大数值(最小数值),而是按照某种排序方式对集合中的元素进行排序以后,在该条件下的最大元素(最小元素)。比如,按照长度对字符串对象排序,那么最大(小)值就是,长度最大(小)的那个字符串。这里我们以max方法为例进行介绍,min方法类似。

       max方法同样包含两个重载方法,如下所示:

(1)    public static <T extends Object & Comparable<? superT>> T max(Collection<? extends T> coll):根据元素的自然顺序,返回给定collection的最大元素。

方法说明:该方法定义的泛型与sort类似,首先T必须是Object类的子类(自然而然的),同时也得是Comparable接口的子类(实现类),同样为了提高扩展性,Comparable接口的泛型可以是T的父类。max方法与sort的不同之处在于,作为参数传递的集合,其泛型可以是T及其子类。这样就可以以父类T引用接收子类元素对象了,同样可以提高后期的扩展性。

方法演示:

代码3:

import java.util.*; class CollectionsDemo3{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); System.out.println("Max="+Collections.max(list));//获取字母顺序最靠后的元素}}
执行结果为:

Max=Zoo

(2)    public static <T> max(Collection<? extends T>coll,Comparator<? super T> comp):根据制定比较器产生的顺序,返回给定collection的最小元素。

方法说明:与另一个重载方法类似,只是需要再传递一个比较器对象。

方法演示:

代码4:

import java.util.*; //定义比较器,以字符串长度为主要条件,以字母顺序为次要条件class StrLengthComparator implementsComparator<String>{public int compare(String str1, String str2){int value = new Integer(str1.length()).compareTo(new Integer(str2.length())); if(value== 0)return str1.compareTo(str2);return value;}}class CollectionsDemo4{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); //获取长度最大的字符串元素System.out.println("Max="+Collections.max(list,newStrLengthComparator()));}}
执行结果为:

Max=Restaurant

1.3  binarySearch——二分查找

       该方法的主要作用就是通过二分搜索法搜索指定列表,以获得指定对象在集合中的脚标值。由于应用二分查需要使用元素对应的角标,因此该方法只能用于List集合。而且,实现二分查找的另一个前提条件是——该集合必须是有序集合,换句话说,其中的元素必须已经按照某种条件进行了排序。

       该方法同样有两个重载方法,如下所示,

(1)      public static <T> int binarySearch(List<? extendsComparable<? super T>> list,T key)方法说明:由二分查找的原理可知,需要将中间位置元素与被查找元素进行比较,因此集合中的元素必须是Comparable接口的实现类,通过compareTo方法的返回值来比较大小。如果该方法中的T表示自定义类,那么必须手动令其实现Comparable接口,并复写compareTo方法,否则编译失败。

方法演示:

代码5:

import java.util.*; class CollectionsDemo5{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); //二分查找前,先进行排序Collections.sort(list);//打印排序后的集合System.out.println(list); int index = Collections.binarySearch(list,"Mango");System.out.println("index= "+index);}}
执行结果为:

[Apple, Cat, Mango, Restaurant, Tomorrow,Zoo]

index = 2

       如果我们查找的元素并不存在于集合中时,比如查找"Fly",那么输出值为-3。API文档中规定,当被搜索的元素不包含在列表中时,返回(-(插入点)-1),也就是说按照字母顺序"Fly"应插入到"Cat"后,"Mango"前,那么插入点就是2,则-2-1得-3。这样就保证了当且仅当被查找元素被找到时,返回值才大于等于0。

 

小知识点1:

       我们按照二分查找的原理,自定义一个binarySearch方法,通过这种方式来复习并扩展二分查找方法,

代码6:

import java.util.*; class MyCollections{public static <T> int binarySearch(List<? extends Comparable<? superT>> list,T key){int max,min,mid;max= list.size()-1;//定义尾脚标值min= 0;//定义头脚标值 while(min<=max){mid= (min + max) >> 1;//计算中间脚标值                    Comparable<?super T> midValue = list.get(mid);//获取中间脚标对应的元素int value = midValue.compareTo(key);//对中间脚标元素与被查找对象进行比较 if(value> 0)max= mid - 1;elseif(value < 0)min= mid + 1;elsereturn mid;}return -min-1;//min即为插入点脚标}}class CollectionsDemo6{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); //二分查找前,先进行排序Collections.sort(list);//打印排序后的集合System.out.println(list); //通过自定义二分查找方法,获取指定元素脚标int index = MyCollections.binarySearch(list,"Mango");System.out.println("index= "+index);}}
执行结果为:

[Apple, Cat, Mango, Restaurant, Tomorrow,Zoo]

index = 2

        通过上述自定义的二分查找方法,同样实现了获取指定元素在集合中脚标的功能。二分查找的原理较为简单,这里不再赘述,主要来说说我自己犯的一个错误——对于泛型下限的理解。自定义binarySearch方法的参数List定义的泛型为,

List<? extends Comparable<? superT>>

它的意思是,该方法并不确定接收的List集合中的元素类型具体是什么,但必须是Comparable接口的实现类。需要注意的是,这个元素类型不是T,T在该方法的作用只是用来指代另一个参数key的类型,并参与到Comparable接口泛型的定义。因此,应该使用Comparable<? super T>类型变量来接收从参数list中获取元素,而不是T,这样才能成功调用compareTo方法进行大小的比较。


(2)    public static <T> binarySearch(List<? extends T> list,Tkey,Comparator<? super T> c)

方法说明:当参数list中的元素自身并不具备比较性时,应调用该binarySearch重载方法实现二分查找。该方法基本原理与思想和前述binarySearch方法是基本相同的。

方法演示:

代码7:

import java.util.*; class StrLenComparator implements Comparator<String>{public int compare(String str1, String str2){int value =new Integer(new Integer(str1.length())).compareTo(new Integer(str2.length())); if(value== 0)return str1.compareTo(str2);return value;}}class CollectionsDemo7{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); //二分查找前,先进行排序,这里需要传递比较器对象Collections.sort(list,newStrLenComparator());//打印排序后的集合System.out.println(list); //通过带比较器的二分查找,查找指定元素int index = Collections.binarySearch(list,"Mango",newStrLenComparator());System.out.println("index= "+index);}}
执行结果为:

[Cat, Zoo, Apple, Mango, Tomorrow,Restaurant]

index = 3

大家也可以自行尝试查找集合中不存在元素对象,返回值同样也是-(插入点)-1。

 

小知识点2:

       我们同样通过自定义使用比较器的binarySearch方法来,了解带比较器的二分查找原理,

代码8:

import java.util.*; //比较器类与代码7相同 class MyCollections{public static <T> int binarySearch(List<? extends T> list,Tkey,Comparator<? super T> c){int max,min,mid;max= list.size()-1;min= 0; while(min <= max){mid= (min + max) >> 1; //相比较于代码6,仅下两行代码发生了变化T midValue = list.get(mid);int value = c.compare(midValue,key); if(value> 0)max= mid - 1;elseif(value < 0)min= mid + 1;elsereturn mid;}return -min-1;//min即为插入点脚标}}class CollectionsDemo8{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); //二分查找前,先进行排序Collections.sort(list,newStrLenComparator());//打印排序后的集合System.out.println(list); //通过自定义二分查找方法,获取指定元素脚标int index = MyCollections.binarySearch(list,"Mango",newStrLenComparator());System.out.println("index= "+index);}}
执行结果与代码7是一致的:

[Cat, Zoo, Apple, Mango, Tomorrow,Restaurant]

index = 3

        上述代码中binarySearch方法的List参数,其泛型定义为了<?extends T>,因此上述代码中应用过T类型变量来接收从list集合中获取的元素对象,这是与代码6有所区别的地方,并且比较中间元素与被查找元素时,调用的是比较器对象的compare方法,因此同时传递了midValue与key。

1.4  fill——替换全部元素

public static<T> void fill(List<? super T> list,T obj):使用指定元素替换列表中的所有元素。

方法演示

代码9:

import java.util.*; class CollectionsDemo9{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); System.out.println(list);Collections.fill(list,"AllTheSame");//将集合中的元素全部替换成"AllTheSame"System.out.println(list);}}
[Apple, Cat, Mango, Tomorrow, Restaurant,Zoo]

[AllTheSame, AllTheSame, AllTheSame,AllTheSame, AllTheSame, AllTheSame]

方法练习:

需求:定义一个方法,将list集合中的部分元素替换成指定元素。

分析:只需在java.util.Collections类fill方法的基础上,再分别传递起始角标和末尾角标,从起始角标开始for循环遍历集合,并通过set方法把指定角标上的元素替换为指定对象即可。

代码10:

import java.util.*; class MyCollections{public static <T> void fillPart(List<? super T> list,T obj,int start,intend){for(int x=start; x<=end-1; x++){list.set(x,obj);}}}class CollectionsDemo10{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Zoo"); System.out.println(list);//将集合中的元素全部替换成"AllTheSame"MyCollections.fillPart(list,"AllTheSame",1,5);System.out.println(list);}}
执行结果为:

[Apple, Cat, Mango, Tomorrow, Restaurant,Zoo]

[Apple, AllTheSame, AllTheSame, AllTheSame,AllTheSame, Zoo]

实现了将List集合中部分元素替换成指定元素的功能。

1.5  replaceAll——替换指定元素

public static<T> boolean replaceAll(List<T> list,T oldVal,T newVal):将集合中所有的指定元素替换成新的指定值。该方法的功能也是替换,但是具有具体的指向。

方法演示:

代码11:

import java.util.*; class CollectionsDemo11{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Apple");list.add("Mango");list.add("Cat");list.add("Mango");list.add("Tomorrow");list.add("Restaurant");list.add("Mango");list.add("Zoo"); System.out.println(list);//将集合中的所有"Mango"全部替换成"***"Collections.replaceAll(list,"Mango","***");System.out.println(list);}}
执行结果为:

[Apple, Mango, Cat, Mango, Tomorrow,Restaurant, Mango, Zoo]

[Apple, ***, Cat, ***, Tomorrow, Restaurant,***, Zoo]

该方法的原理是获取到被替换的元素在集合中的所有脚标后,通过set方法,将这一脚标上的元素替换为指定对象。

1.6  reverse——反转集合

public static voidreverse(List<? list>):反转指定列表中元素的顺序。

方法演示:

代码12:

import java.util.*; class CollectionsDemo12{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Mango");list.add("Apple");list.add("Cat");list.add("Zoo");list.add("Tomorrow");list.add("Restaurant"); System.out.println(list);//对集合中元素进行排序Collections.sort(list);System.out.println(list);//反转集合Collections.reverse(list);System.out.println(list);}}
执行结果为:

[Mango, Apple, Cat, Zoo, Tomorrow,Restaurant]

[Apple, Cat, Mango, Restaurant, Tomorrow,Zoo]

[Zoo, Tomorrow, Restaurant, Mango, Cat,Apple]

1.7  reverseOrder——反转集合

       该方法的功能与reverse相同,也是用于反转集合中的元素,但是原理不同。reverse方法仅仅是将集合中的前后相对的元素相互交换,而reverseOrder方法是返回一个比较器对象,该比较器强行逆转实现了Comparable接口的对象collection的自然顺序,或者是指定的比较器的顺序。获取到逆转后的比较器,就可以通过Collections的sort方法将List集合中元素顺序进行反转,或者也可以用于TreeSet集合元素顺序的反转。

       该方法也有两个重载方法,一个是返回逆转自然顺序的比较器;另一个是逆转指定比较器的比较器,如下所示,

(1)    public static <T> Comparator<T> reverseOrder():返回一个比较器。它强行逆转实现了Comparable接口对象的collection的自然顺序。

(2)    public static <T>Comparator<T>reverseOrder(Comparator<T> cmp):返回一个比较器,它强行逆转指定比较器的顺序。

方法演示:

代码13:

import java.util.*; //定义一个比较器,将字符串从短到长进行排序class StrLenComparator implementsComparator<String>{public int compare(String str1,String str2){int value = new Integer(str1.length()).compareTo(new Integer(str2.length())); if(value== 0)return str1.compareTo(str2);return value;}}class CollectionsDemo13{public static void main(String[] args){//强行逆转String的自然顺序Set<String> set1 = new TreeSet<String>(Collections.reverseOrder()); set1.add("Mango");set1.add("Apple");set1.add("Cat");set1.add("Zoo");set1.add("Tomorrow");set1.add("Restaurant"); System.out.println(set1);System.out.println("=========================================="); //强行逆转按照字符串长度排序的比较器顺序Set<String> set2 =new TreeSet<String>(Collections.reverseOrder(new StrLenComparator())); set2.add("Mango");set2.add("Apple");set2.add("Cat");set2.add("Zoo");set2.add("Tomorrow");set2.add("Restaurant"); System.out.println(set2);}}
执行顺序为:

[Zoo, Tomorrow, Restaurant, Mango, Cat,Apple]

==============================

[Restaurant, Tomorrow, Mango, Apple, Zoo,Cat]

         上述两方法最大的作用就在于,免去了手动编写逆转比较器的类的麻烦,用一个静态方法即可自动完成,提高了代码的开发效率。实际上两方法的原理主要就是调换compareTo方法,或者compare方法两比较对象的顺序,即可返回相反的比较结果值,从而实现元素顺序的逆转。

1.8  synchronizedList——线程安全

         我们可以将存储了大量对象的集合看做是一个“资源”,在实际开发过程中,可能会出现多个线程同时访问一个集合的情况,此时就会出现线程安全问题。根据前面介绍的多线程相关内容,我们应该将所有操作一个集合资源的方法封装到一个同步代码块中,并且要保证所有涉及同一个资源的同步代码块均设置同一个锁对象,当某个线程在操作资源时,不允许其他线程操作,以保证线程安全。为了免去将操作集合的代码封装到同步代码块中的麻烦,Collections类对外提供了将线程不安全集合转为线程安全集合的方法。

下面我们以synchronizedList方法为例,介绍这一类方法的基本实现原理,下面是该方法的源代码,

代码14:(部分代码)

public static <T> List<T> synchronizedList(List<T> list) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list) :new SynchronizedList<>(list));}static class SynchronizedList<E>extends SynchronizedCollection<E>implements List<E> {private static final long serialVersionUID = -7754090372962971524L; final List<E> list; SynchronizedList(List<E> list) {super(list);this.list = list;}SynchronizedList(List<E> list, Object mutex) {super(list, mutex);this.list = list;} public boolean equals(Object o) {if (this == o)return true;synchronized (mutex) {return list.equals(o);}}public int hashCode() {synchronized (mutex) {return list.hashCode();}} public E get(int index) {synchronized (mutex) {returnlist.get(index);}}public E set(int index, E element) {synchronized (mutex) {return list.set(index, element);}}public void add(int index, E element) {synchronized (mutex) {list.add(index, element);}}public E remove(int index) {synchronized (mutex) {return list.remove(index);}} public int indexOf(Object o) {synchronized (mutex) {return list.indexOf(o);}}public int lastIndexOf(Object o) {synchronized (mutex) {return list.lastIndexOf(o);}} public boolean addAll(int index, Collection<? extends E> c) {synchronized (mutex) {return list.addAll(index, c);}} public ListIterator<E> listIterator() {return list.listIterator(); // Must be manually synched by user} public ListIterator<E> listIterator(int index) {return list.listIterator(index); // Must be manually synched by user} public List<E> subList(int fromIndex, int toIndex) {synchronized (mutex) {return newSynchronizedList<>(list.subList(fromIndex, toIndex),mutex);}}private Object readResolve() {return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : this);}}
        该方法首先对传入到synchronizedList方法的list对象进行了类型判断,然后返回了一个SynchronizedList对象。向下看代码,即可发现SynchronizedList是一个静态内部类,并且实现了List接口,那么必然它需要复写List接口的所有方法。从上面的代码我们可以看出,SynchronizedList静态内部类在覆盖List接口的大多数方法时,都是采用在调用参数list对象的同名方法的同时,将这一部分代码用静态代码块封装起来的方式,并且所有方法的静态代码块都设置为了同一个锁对象。其中,有一小部分方法,并没有使用任何同步代码块封装起来,而是在方法体后面进行了注释,注释内容为:Must be manually synched by user,表明这些方法只能手动同步,保证其线程安全。

1.9  swap——交换集合中两元素的位置

顾名思义,该方法是用于交换集合中两元素的位置。而前述的reverse方法在对集合中元素顺序进行逆转时,所使用的方法就是swap。

public static voidswap(List<?> list,int i,int j):在指定列表(List集合)的指定位置交换元素。

方法演示:

代码15:

import java.util.*; class CollectionsDemo14{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Mango");list.add("Apple");list.add("Cat");list.add("Zoo");list.add("Tomorrow");list.add("Restaurant"); Collections.sort(list);System.out.println(list); Collections.swap(list,3,5); System.out.println(list);}}
执行结果为:

[Apple, Cat, Mango, Restaurant, Tomorrow,Zoo]

[Apple, Cat, Mango, Zoo, Tomorrow,Restaurant]

1.10             shuffle——随机排列List集合中的元素

该方法的主要功能是将List集合中的元素随机打乱顺序,并且每次打乱的顺序是不相同的。该方法也有两个重载方法,如下所示,

public static voidshuffle(List<?> list):使用默认随机源对指定列表进行置换。

public staiic voidshuffle(List<?> list,Random rnd):使用指定的随机源对指定列表进行置换。

方法说明:上述方法描述中的随机源指的是Random对象,该对象用于产生伪随机数。而这两个重载方法的区别在于,后者可以指定一个手动初始化了一个种子的Random对象,并通过该Random对象随机打乱集合中的元素。

方法演示:

代码16:

import java.util.*; class CollectionsDemo15{public static void main(String[] args){List<String> list = new ArrayList<String>(); list.add("Mango");list.add("Apple");list.add("Cat");list.add("Zoo");list.add("Tomorrow");list.add("Restaurant"); Collections.sort(list);System.out.println(list); Collections.shuffle(list);//打乱集合中元素顺序System.out.println(list);}}
执行结果为:

[Apple, Cat, Mango, Restaurant, Tomorrow,Zoo]

[Cat, Apple, Restaurant, Zoo, Tomorrow,Mango]

多次执行上述代码,元素的排列顺序每次都是不一样的。有兴趣的同学也可以尝试使用指定随机源的重载方法,来打乱集合中元素的顺序。

2.     Arrays工具类

        相对于Collections工具类用于操作集合,Arrays工具类顾名思义就是专门用于操作数组的工具类。Arrays工具类存在的意义与Collections工具类是相同的,就是免去了程序员自定义一些常用方法的麻烦,并且同样由于不需要存储特有数据,因此该类的所有方法均为静态方法。下面我们就简单介绍Arrays工具类的一些常用方法。

2.1  binarySearch——二分查找

        Arrays工具类API文档的方法摘要一栏中,可以看到大片的binarySearch重载方法,这些binarySearch重载方法可以对除boolean以外的所有七种基本数据类型数组和引用数据类型数组进行二分查找。对基本数据类型数组和引用数据类型数组的二分查找原理我们已经在前面的内容中介绍过了,而其使用方法与Collections工具类是相同的,因此这里不再赘述。

2.2  copyOf——复制数组

        该方法的作用是用于将原数组中的元素复制到指定长度的新数组中,如果新数组长度大于原数组,那么剩余部分将用数组所属数据类型的初始值填充,例如,如果是boolean类型的数组,剩余部分将全部使用false填充。我们以操作int型数组的方法进行演示,

public static int[]copyOf(int[] original,int newLength):复制指定的数组,截取(当新数组长度小于原数组),或用0填充(当新数组长于原数组),以使新数组具有指定的长度。参数newLength表示的就是指定的新数组的长度。

方法演示:

代码17:

import java.util.*; class ArraysDemo{public static void main(String[] args){//通过伪随机数生成器,生成5个在0-99之间的整数,并存储到一个数组中Random rand = new Random();int[] source = new int[5];for(intx=0; x<source.length; x++){source[x]= rand.nextInt(100);} //复制数组int[] newArray1 = Arrays.copyOf(source,3);//新数组比原数组短int[] newArray2 = Arrays.copyOf(source,5);//新数组长度与原数组相同int[] newArray3 = Arrays.copyOf(source,7);//新数组比原数组长 //打印原数组与新数组,进行比较System.out.println("source="+Arrays.toString(source));System.out.println("short="+Arrays.toString(newArray1));System.out.println("equals="+Arrays.toString(newArray2));System.out.println("long="+Arrays.toString(newArray3));}}
执行结果为:

source=[48, 32, 72, 91, 59]

short=[48, 32, 72]

equals=[48, 32, 72, 91, 59]

long=[48, 32, 72, 91, 59, 0, 0]

多次执行该程序的结果将不完全相同,这是因为每次产生的随机数是不同的。

2.3  copyOfRange——复制部分数组

        该方法也是用于复制数组,但是不同之处在于copyOfRange的主要功能是将原数组的指定部分复制到新数组中,并且该方法相较于copyOf方法可以操作boolean型数组。需要注意的是,在指定复制范围时,同样遵循包含头不包含尾的原则。我们同样以操作int型数组的重载方法为例进行演示,

pubic static int[]copyOfRange(int[] original,int from,int to):将知识数组的指定范围部分复制到新数组。

方法演示:

代码18:

import java.util.*; class ArraysDemo2{public static void main(String[] args){Random rand = new Random();int[] source = new int[5];for(intx=0; x<source.length; x++){source[x]= rand.nextInt(100);}             int[] newArray = Arrays.copyOfRange(source,1,4);//复制范围包含头,不包含尾System.out.println(Arrays.toString(source));System.out.println(Arrays.toString(newArray));}}
执行结果为:

[41, 36, 45, 30, 55]

[36, 45, 30]

每次执行的结果将会有所不同,因此原数组中的元素是随机产生的。

2.4  equals与deepEquals——比较两数组是否相等

(1)    equals方法

equals方法用于比较两数组中的元素是否完全相同,可以用来比较基本数据类型数组,也可以用于比较引用数据类型数组。以判断int型数组的方法为例,

public staticboolean equals(int[] a,int[] a2):如果两个数组以相同的元素排列顺序包含相同个数、相同值的元素,则两个两数组是相同的,返回true。当比较两引用数据类型数组时,会调用元素的equals方法来比较两元素是否相同,若元素对象的所属类复写了equals方法,那么比较的将是两元素的内容,而不仅是地址。

由于该方法较为简单,不再进行演示。

(2)    deepEquals方法

当两引用数据类型数组存储的元素为若干集合(或数组)时,deepEquals方法将进一步比较两数组每一个集合中每一个元素是否相等,此即为深层相等比较。

public staticboolean deepEquals(Object[] a1,Object[] a2):如果两个指定数组是深层相等的,则返回true,该方法与上述equals(Object[],Object[])方法不同,此方法适用于任意深度的嵌套数组。

代码19:

import java.util.*; class Student{private String name;private int age;Student(Stringname,int age){this.name= name;this.age= age;}public String getName(){return name;}public int getAge(){return age;}public boolean equals(Object obj){if(!(objinstanceof Student))throw new IllegalArgumentException("参数类型异常!");             Student stu = (Student)obj; return name.equals(stu.getName()) && age == stu.getAge();}}class ArraysDemo3{public static void main(String[] args){//创建Student对象Student stu1 = new Student("Jack",23);Student stu2 = new Student("Kate",23);Student stu3 = new Student("Peter",23); //创建存储有以上Student对象的集合List<Student> list1 = new ArrayList<Student>();list1.add(stu1);list1.add(stu2); List<Student> list2 = new ArrayList<Student>();list2.add(stu1);list2.add(stu3); List<Student> list3 = new ArrayList<Student>();list3.add(stu2);list3.add(stu3); //创建存有以上List集合的数组List[] stus1 = {list1,list2};List[] stus2 = {list1,list2};List[] stus3 = {list1,list3}; System.out.println("stu1deepEquals stu2:"+Arrays.deepEquals(stus1,stus2));System.out.println("stu1deepEquals stu3:"+Arrays.deepEquals(stus1,stus3));}}
执行结果为:

stu1 deepEquals stu2:true

stu1 deepEquals stu3:false

2.5  fill——替换元素

        Arrays类中也定义了大片的fill重载方法,可以替换8种基本数据类型数组以及引用数据类型数组中的元素,并且可以指定替换元素的脚标范围,由于该方法的使用方法与Collections工具类相同,因此不再赘述。

2.6  sort——排序

        可对除boolean型数据以外的7中基本数据类型数组,以及引用数据类型数组进行排序,并且可以指定排序脚标范围,也就是可以实现局部排序。对于应用数据类型数组,同样可以按照自然顺序排序,也可以指定比较器排序。该方法的使用方法与Collections相同,因此不再赘述。

2.7  toString——将数组转换为字符串

        返回指定数组内容的字符串表示形式。字符串表示形式由数组的元素列表组成,括在方括号("[]")中。相邻元素用字符", "(逗号加空格)分隔。数组中的元素通过 String.valueOf()方法转换为字符串。该方法较为简单,不再进行演示。

2.8  asList——将数组转换为List集合

public static <T>List<T> asList(T… a):返回一个受指定数组支持的固定大小的列表。向该方法中传递一个数组,该方法将返回一个包含数组中全部元素的List集合。相对于数组而言,我们可以使用集合的思想和方法来更为方便的操作集合中的元素,比如若想要判断集合中是否存在指定元素,不必自定义判断方法,直接调用List集合方法即可,而这也就是asList方法的重要意义之一。

代码20:

import java.util.*; class ArraysDemo4{public static void main(String[] args){Integer[] arr = {34,12,77}; //将Integer数组转换为Integer集合List<Integer> list = Arrays.asList(arr);//直接打印Integer集合的内容System.out.println(list); //判断集合中是否存在某个元素System.out.println(list.contains(77)); //以下代码将发生运行时错误//list.add(100);}}
执行结果为:

[34, 12, 77]

true

        将原数组中的内容按照集合的格式打印到了控制台,并通过List集合的contains方法判断了集合中是否存在封装了数字77的Integer对象。但如果尝试执行被注释的代码将发生UnsupportedOperationException异常,因此需要大家注意的是,将一个数组通过Arrays的静态方法asList转换之后得到的集合,不可以使用增删方法,因为数组的长度是固定的,因此相对应的集合的长度也是固定。但除增删方法以外的其他List方法都是可以使用的。

        我们就asList方法再举一个特殊情况,阅读下面的代码

代码21:

import java.util.*; class ArraysDemo6{public static void main(String[] args){int[] arr = {34,12,77}; List list = Arrays.asList(arr);System.out.println(list);}}
执行结果为:

[[I@1d2fc36]

        打印结果并不是原数组中的元素,而是arr所指向的int型数组的哈希值。而上述代码与代码20之间的唯一区别就是,数组类型不是基本数据类型int,而是与之对应的包装类Integer。那么出现这一现象的原因是:当数组中的元素是引用数据类型时,将该数组转换为集合后,数组中的元素将直接转换为集合中的元素;而如果数组中的元素都是基本数据类型,就会将该数组作为唯一的元素存储到新集合中并返回。因此,上述代码中List集合的泛型应定义为<int[]>。

 

3.      集合转数组

        本节内容本不属于这一篇博客,但是在上一节内容中介绍了数组转集合的方法,因此我们在这里补充介绍Collections集合的共性方法toArray——集合转数组,便于将asList和toArray两方法结合起来进行记忆。查阅Collection接口的API文档可知,toArray有两个重载方法,如下所示,

Object[] toArray():返回包含此collection中所有元素的数组。该方法由于不涉及泛型,因此无论原集合为哪种数据类型,返回的数组类型均提升为了Object。

<T> T[] toArray(T[] a):返回包含此collection中所有元素的数组,返回数组的运行时类型与指定数组的运行时类型相同。相比于前一个重载方法,该方法由于传入了一个指定类型的数组,并使用了泛型,因此将数组转为集合时不再需要类型提升。

方法演示:

代码22:

import java.util.*; class CollectionToArray{public static void main(String[] args){ArrayList<Integer> al = new ArrayList<Integer>(); al.add(37);al.add(19);al.add(90); //将al集合中的元素存储到指定数组中Integer[] arr = al.toArray(new Integer[0]);//指定数组的长度为0System.out.println(Arrays.toString(arr));}}
执行结果为:

[37, 19, 90]

结合上述的执行结果和代码22的内容,我们进行两点说明:

        第一点是关于指定数组的长度。虽然从结果看,toArray方法将原集合中的元素成功存储到了指定的数组中并返回,但问题是我们指定的数组长度为0,按理应该一个元素也存储不了。这是因为,当指定的数组长度小于原集合的长度,那么该方法内部就会创建一个新的数组,其长度等于原集合的长度,这也就是为什么指定数组长度为0时依然可以用于存储元素的原因;相反,如果指定数组长度大于集合长度时,剩余的数组空间就用数组所属类型的默认初始化值填充。

由以上的特性可知,调用toArray方法时,传递一个与原集合长度相等的数组是最优的,既不必重新创建一个新的数组,也不必因为填不满指定数组,而浪费内存空间。

        第二点需要说明的是,既然集合可以更为方便的操作数组,那么为什么还要费劲的将集合转换为数组呢?它的主要目的其实是为了限定对元素的操作,可以理解为保证数组中元素的“安全”。如果将一些不希望被修改的元素存储到集合中并返回,那么使用该方法的程序员就可以对该集合进行增删操作,但如果返回的是数组就可以避免这一情况的发生。举个例子,比如一个集合中存储了一些Student对象,每个Student对象中存储着每个学生的个人信息,而一个集合就表示一个班级。当我们需要去查询某个班中所有学生的信息时,如果直接返回原集合,虽然可以实现查询功能,但是这种方式具有一定的安全隐患,因为外界可以任意对该集合进行增删修改,而一个班级中的学生成员是固定的,不能随意修改,因此最好的办法是将原集合中的元素存储到一个数组中返回,避免了上述的安全问题。


0 0