黑马程序员—Java学习笔记之集合框架(三)以及1.5新特性

时间:2023-02-19 18:19:46

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


一 Collections

1.概述

Collections:操作集合的工具类。方法全为静态的,不需要创建对象。

在Collections工具类中大部分方法是用于对List集合进行操作的。例如排序,查找等。


2.常用方法:

Collections.sort(list);//list集合进行元素的自然顺序排序。

Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。

Collections.max(list); //返回list中字典顺序最大的元素。   

int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。

Collections.reverseOrder();//逆向反转排序。返回的是一个逆向比较器。

如果是使用指定的比较器那么这么写Collections.reverseOrder(比较器对象)

Collections.fill(List list,Object obj); //使用指定元素替换指定列表中的所有元素,无返回值。

Collections.swap(list,1,2);//将指定的List集合1处元素和2处元素进行交换,无返回值;

Collections.replaceAll(list,old,new); 使用另一个值替换列表中出现的所有某一指定值。

Collections.shuffle(list);//随机对list中的元素进行位置的置换。

List<T>synchronizedList(List<T> list);//返回支持的同步(线程安全的)List集合

Map<K,V>synchronizedList(Map<K,V> m);//返回支持的同步(线程安全的)Map集合

 

3.Collection 和 Collections的区别:

Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。

Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

Example:


import java.util.*;
class CollectionsListDemo
{
public static void main(String[] args)
{
ArrayList<Integer> al=new ArrayList<Integer>();//创建集合对象。
al.add(1);
al.add(5);
al.add(3);
al.add(10);
al.add(25);

Collections.sort(al,Collections.reverseOrder());//按照指定的比较器对集合进行排序。
System.out.println(Collections.max(al));//返回集合中字典顺序最大的元素。
System.out.println(Collections.binarySearch(al,5));//使用二分法查找5在集合中的位置。
System.out.println(Collections.replaceAll(al,5,8));
System.out.println(al);
}

}

二  Arrays

用于操作数组对象的工具类,里面都是静态方法,不需要创建对象。

public static  List asList(Object... a);//将数组转换成list集合。

String[] arr = {"abc","kk","qq"};

List<String> list = Arrays.asList(arr);//将arr数组转成list集合。

 

1.将数组转换成集合

有什么好处呢?

用aslist方法,将数组变成集合;

可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set; 

注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException);

如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。

如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。

 

2.集合变数组:用的是Collection接口中的方法:toArray();

 

如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。

如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。

所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。

将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可

3.常见方法:

static void sort(int[] a) 

          对指定的 int 型数组按数字升序进行排序。 

static void sort(int[] a, int fromIndex, int toIndex) 

          对指定 int 型数组的指定范围按数字升序进行排序。 

static int binarySearch(int[] a, int key) 

          使用二分搜索法来搜索指定的 int 型数组,以获得指定的值。 

static int binarySearch(int[] a, int fromIndex, int toIndex, int key) 

          使用二分搜索法来搜索指定的 int 型数组的范围,以获得指定的值。 

static void fill(int[] a, int val) 

          将指定的 int 值分配给指定 int 型数组的每个元素。 

static void fill(int[] a, int fromIndex, int toIndex, int val) 

          将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。 

static int[] copyOf(int[] original, int newLength) 

          复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。 

static int[] copyOfRange(int[] original, int from, int to) 

          将指定数组的指定范围复制到一个新数组。 

static int hashCode(int[] a) 

          基于指定数组的内容返回哈希码。 

static String toString(int[] a) 

          返回指定数组内容的字符串表示形式。 可以用来打印数组。

static String deepToString(Object[] a) 

          返回指定数组“深层内容”的字符串表示形式。 

 

 

static boolean equals(int[] a, int[] a2) 

          如果两个指定的 int 型数组彼此相等,则返回 true。 

如果两个指定的 int 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。此外,如果两个数组引用都为 null,则认为它们是相等的。


Example:


import java.util.*;
class ArraysDemo
{
public static void main(String[] args)
{
int[] arr={9,8,6,7,5,4,3,1,2};//创建一个一个数组。
Arrays.sort(arr);//对数组进行排序。
System.out.println(Arrays.binarySearch(arr,5));//使用二分法查找5在数组中的位置。
System.out.println(Arrays.toString(arr));
String[] str={"abc","def","ghi"};
List<String> li=Arrays.asList(str);//将数组转成List集合。
System.out.println(li);
System.out.println(li.contains("aa"));
}

}


集合变数组:

      方法:Collection接口中的toArray方法。

    <T> T[]toArray(T[] a);将集合变为指定类型的数组。

1、指定类型的数组到底要定义多长呢?

        当指定类型的数组长度小于了集合的size,那么该方法内部会创建一个新的数组。长度为集合的size。

         当指定类型的数组长度大于了集合的size,就不会新创建了数组。而是使用传递进来的数组。

         所以创建一个刚刚好的数组最优。

2、为什么要将集合变数组?

         为了限定对元素的操作。不需要进行增删了。


Example:将集合编程数组


import java.util.*;
class ListToArryDemo
{
public static void main(String[] args)
{
ArrayList<String> al=new ArrayList<String>();//创建集合。
al.add("abc");//添加元素。
al.add("gg");
al.add("dd");
String[]str=al.toArray(new String[al.size()]);//将集合变成数组。
System.out.println(Arrays.toString(str));
}

}


三 1.5新特性

1.增强for循环:

foreach语句,foreach简化了迭代器。

格式:// 增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器

for( 元素类型 变量名 : Collection集合 & 数组 ) {

}

高级for循环和传统for循环的区别:

高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合,在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。

如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。

 

高级for循环可以遍历map集合吗?不可以。但是可以将map转成set后再使用foreach语句。

 

1)、作用:对存储对象的容器进行迭代: 数组  collection   map

2)、增强for循环迭代数组:

String [] arr = {"a", "b", "c"};//数组的静态定义方式,只试用于数组首次定义的时候

for(String s : arr) {

System.out.println(s);

}

3)、单列集合 Collection:

List list = new ArrayList();

list.add("aaa");

// 增强for循环, 没有使用泛型的集合能不能使用增强for循环迭代?能

for(Object obj : list) {

String s = (String) obj;

System.out.println(s);

}

4)、双列集合 Map:

Map map = new HashMap();

map.put("a", "aaa");

// 传统方式:必须掌握这种方式

Set entrys = map.entrySet(); // 1.获得所有的键值对Entry对象

iter = entrys.iterator(); // 2.迭代出所有的entry

while(iter.hasNext()) {

Map.Entry entry = (Entry) iter.next();

String key = (String) entry.getKey(); // 分别获得key和value

String value = (String) entry.getValue();

System.out.println(key + "=" + value);

}

// 增强for循环迭代:原则上map集合是无法使用增强for循环来迭代的,因为增强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证一定有iterator方法,java有这样的限定是因为增强for循环内部还是用迭代器实现的,而实际上,我们可以通过某种方式来使用增强for循环。

for(Object obj : map.entrySet()) {

Map.Entry entry = (Entry) obj;  // obj 依次表示Entry

System.out.println(entry.getKey() + "=" + entry.getValue());

}

5)、集合迭代注意问题:在迭代集合的过程中,不能对集合进行增删操作(会报并发访问异常);可以用迭代器的方法进行操作(子类listIterator:有增删的方法)。

6)、增强for循环注意问题:在使用增强for循环时,不能对元素进行赋值;

int[] arr = {1,2,3};

for(int num : arr) {

num = 0; //不能改变数组的值

}

System.out.println(arr[1]);


Example:

import java.util.*;
class ForDemo
{
public static void main(String[] args)
{
int[] arr={9,8,7,6,3,5,4,1,2};
for(int a:arr)//用高级for遍历数组。
{
System.out.println(a);
}
ArrayList<String> al=new ArrayList<String>();
al.add("aaa");
al.add("abbb");
al.add("ccc");
for(String str:al)//用高级for遍历集合。
{
System.out.println(str);
}

}

}


2.可变参数(...):

用到函数的参数上,当要操作的同一个类型元素个数不确定的时候,可是用这个方式,这个参数可以接受任意个数的同一类型的数据。

 

和以前接收数组不一样的是:

以前定义数组类型,需要先创建一个数组对象,再将这个数组对象作为参数传递给函数。现在,直接将数组中的元素作为参数传递即可。底层其实是将这些元素进行数组的封装,而这个封装动作,是在底层完成的,被隐藏了。所以简化了用户的书写,少了调用者定义数组的动作。

如果在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,否则编译会失败。)。

如果要获取多个int数的和呢?可以使用将多个int数封装到数组中,直接对数组求和即可。


Example:


class KeBianDemo
{
public static void main(String[] args)
{
int sum=kebian(1,5,3,6,9,8);
System.out.println(sum);
}
//定义一个带可变参数的方法。
public static int kebian(int...a)
{
System.out.println(a.length);
int sum=0;
for(int x=0;x<a.length;x++)
{
sum+=a[x];//求和。

}
return sum;
}

}


3.泛型

jdk1.5版本以后出现的一个安全机制。表现格式:< >。

泛型定义:

java5开始出现的一种对Java语言类型的一种拓展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数类型时指定的类型占位符,就好比方法的形式参数是实际参数的占位符一样.

泛型能保证大型应用程序的类型安全和良好的维护性;

使用泛型的优势:

类型安全,使编译器对泛型定义的类型做判断限制.如保证TreeSet里的元素类型必须一致;消除强制类型的转换,如,使用Comparable比较时每次都需要类型强转。

好处:

1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。

2:避免了强制转换的麻烦。

 

只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。

泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。

为什么? 因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。

在运行时,如何知道获取的元素类型而不用强转呢?

泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。

什么时候用泛型类呢?

当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。

类型参数规范:推荐使用规范-常见的泛型,泛型只保存在源文件中,class文件中不存在;也就是说在编译阶段就会丢失,基本数据类型不能作为泛型类型;

K  键,比如映射的键  key的类型

V 值,比如Map的值 value类型

E 元素,比如Set<E>  Element表示元素,元素的类型

T  泛型,Type的意思


将泛型定义带类上:

1、若类实例对象中要使用到同一泛型参数,即这些地方引用类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型。

2、什么时候定义泛型类

       当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展。现在定义泛型来完成扩展。

3、泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所以要操作的类型就已经固定了。

Example:


import java.util.*;
public class FanxingDemo1<T>
{
private T t;
public void setT(T t)
{
this.t=t;
}
public T getT()
{
return t;
}
public static void main(String[] args)
{
FanxingDemo1<String> f=new FanxingDemo1<String>();
f.setT("zhangsan");
System.out.println(f.getT());
}

}


当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上。

public <W> void method(W w) {

System.out.println("method:"+w);

}

静态方法上的泛型:静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

public static <Q> void function(Q t) {

System.out.println("function:"+t);

泛型接口.

interface Inter<T> {

void show(T t);

}

class InterImpl<R> implements Inter<R> {

public void show(R r) {

System.out.println("show:"+r);

}

泛型中的通配符:可以解决当具体类型不确定的时候,这个通配符就是 ?  ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

 

泛型限定:

上限:?extends E:可以接收E类型或者E的子类型对象。

下限:?super E:可以接收E类型或者E的父类型对象。

 

上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。

 

下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。