1、Collections工具类
Collections的特点:
集合框架的工具类。
Collections:此类完全由在 collection上进行操作或返回 collection的静态方法组成,它是集合框架的工具类,里面定义的都是静态方法。
Collections和Collection有什么区别?
Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。
它有两个常用的子接口:
List:对元素都有定义索引,有序的,可以重复元素。
Set:不可以重复元素,无序。
Collections是集合框架中的一个工具类,该类中的方法都是静态的
提供的方法中有可以对list集合进行排序,二分查找等方法。(List没有直接排序的方式,TreeSet才有)
通常常用的集合都是线程不安全的,因为要提高效率。
如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。
Collections的常用方法示例第一部分
package pack;
import java.util.*;
class CollectionsDemo
{
public static void main(String[] args)
{
// CollectionsDemo.sortDemo();
// CollectionsDemo.maxDemo();
CollectionsDemo.binarySearchDemo();
}
//首先是sort()方法:public static <T extends Comparable<? super T>> void sort(List<T> list)
//首先,比较的List中的对象T要有比较性,继承Comparable,而Comparable也要限定其比较的类型为T,
//那么Comparable的参数可以是T以及T的父类(见视频17-10,3分钟处解析)
//sort不能给Set排序!
public static void sortDemo()
{
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("aaa");
list.add("z");
list.add("kkkkk");
list.add("qq");
list.add("z");//重复也可以排序
System.out.println("排序前:"+list);
//用Collections的sort方法排序——自然顺序
Collections.sort(list);//按照自然顺序排序(如果该类有自然顺序的话)
System.out.println("自然顺序排序后:"+list);
//用Collections的sort方法排序——指定比较器,我们这里按字符串长度来排序
Collections.sort(list,new strLenComparator());
System.out.println("比较器排序后:"+list);
//在指定列表的指定位置处交换元素。
Collections.swap(list,1,2);
System.out.println("交换元素后后:"+list);
}
//寻找Collections中的最大元素
public static void maxDemo()
{
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
Collections.sort(list);
System.out.println(list);
String max1 = Collections.max(list);
System.out.println("自然顺序下最大元素:"+max1);
String max2 = Collections.max(list,new strLenComparator());//按字符串长度来寻找最大值
System.out.println("比较器下最大元素:"+max2);
}
//二分查找:二分查找只能对有序集合进行操作
public static void binarySearchDemo()
{
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
Collections.sort(list);//二分查找必须先排序
System.out.println("排序结果:"+list);
//二分查找
int index1 = Collections.binarySearch(list,"aaa");
System.out.println("index1:"+index1);
//查找一个不存在的值
int index2 = Collections.binarySearch(list,"aaaa");
System.out.println("不存在的aaaa的角标index2:"+index2);//-2(见17-12,1.35处解析)
//对于不存在的值,我们返回该值在集合中的 (-(插入点) - 1)。“aaaa”插入点为1,返回-1-1=-2
//binarySearch原理
int index3 = CollectionsDemo.halfSearch(list, "aaa");
System.out.println("二分查找原理——自然顺序:"+index3);
Collections.sort(list,new strLenComparator());//想按长度进行二分查找,必须先按长度排序
System.out.println("按长度排序:"+list);
int index4 = CollectionsDemo.halfSearch2(list, "aaa", new strLenComparator());
System.out.println("二分查找原理——比较器自定义:"+index4);
}
//二分查找原理方法1——自然顺序
public static int halfSearch(List<String> list , String key)
{
//首先定义上下限以及中间值的表示
int mid,max,min;
max = list.size()-1;
min = 0;
//当min<=max的时候,我们持续二分查找(原理可以在本子上分析)
while(min<=max)
{
//首先,取得mid值
mid = (min+max)>>1;
//我们再取得list集合在mid位置上的字符串
String str = list.get(mid);
//再用字符串的自然顺序比较,将要查找的key与str比较,获得一个int值
int num = str.compareTo(key);//String的compareTo方法很好用!!!
if(num>0)
max = mid - 1;//更新max
else if(num<0)
min = mid + 1;//更新min
else
return mid;//如果num=0,说明mid就是我们要查找的key
}
//循环结束,集合没有符合条件的值,这个值插入点为此时的min
return -min-1;
}
//二分查找原理方法2——Comparator比较器自定义顺序——当我们要比较的元素不具备比较性或者具备的比较性不是我们需要的的时候
//这里传入Comparator接口的一个对象,调用的时候传入实现Comparator的子类对象,即可使用该子类重写的compare方法(多态实现传入不同子类比较方式不同)
public static int halfSearch2(List<String> list , String key , Comparator<String> cmp)
{
//首先定义上下限以及中间值的表示
int mid,max,min;
max = list.size()-1;
min = 0;
//当min<=max的时候,我们持续二分查找(原理可以在本子上分析)
while(min<=max)
{
//首先,取得mid值
mid = (min+max)>>1;//前面这里错写为>>2的时候,debug发现程序会一直在while中死循环,因此不会打印!
//我们再取得list集合在mid位置上的字符串
String str = list.get(mid);
//再用字符串的自然顺序比较,将要查找的key与str比较,获得一个int值
int num = cmp.compare(str,key);
if(num>0)
max = mid-1;//更新max
else if(num<0)
min = mid+1;//更新min
else
return mid;//如果num=0,说明mid就是我们要查找的key
}
//循环结束,集合没有符合条件的值,这个值插入点为此时的min
return -min-1;
}
}
class strLenComparator implements Comparator<String>
{
public int compare(String s1,String s2)
{
//先比较字符串长度
if(s1.length() > s2.length())
return 1;
if(s1.length() < s2.length())
return -1;
//字符串长度相等再用compareTo比较自然顺序
return s1.compareTo(s2);
}
}
Collections的常用方法示例第二部分
package pack;
import java.util.*;
class CollectionsDemo
{
public static void main(String[] args)
{
// fillDemo();
// showPartFill();
// replaceAllDemo();
// reverseOrderDemo();
shuffleDemo();
}
//替换。fill方法:使用指定元素替换指定列表中的所有元素。
public static void fillDemo()
{
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
System.out.println("原集合:"+list);//原集合:[abcd, aaa, zz, kkkkk, qq, z]
Collections.fill(list, "haha");
System.out.println("替换之后的集合:"+list);//替换之后的集合:[haha, haha, haha, haha, haha, haha]
}
/*
练习。fill方法可以将list集合中所有元素替换成指定元素,我们想要将list集合中部分元素替换成指定元素。
*/
public static void showPartFill()
{
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
Collections.sort(list);
System.out.println("原集合:"+list);
partFillDemo(list,1,4);
System.out.println("替换之后的集合:"+list);
}
public static void partFillDemo(List<String> list , int start , int end)
{
for(int x = start; x<end ;x++)
{
list.remove(x);
list.add(x, "haha");
}
}
//替换所有:replaceAll(List<T> list, T oldVal, T newVal):使用另一个值替换列表中出现的所有某一指定值。
public static void replaceAllDemo()
{
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("aaa");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
System.out.println("原集合:"+list);
Collections.replaceAll(list, "aaa" , "haha");
System.out.println("替换后集合:"+list);
//反转
Collections.reverse(list);
System.out.println("反转后集合:"+list);
/*
结果:
原集合:[abcd, aaa, aaa, zz, kkkkk]
替换后集合:[abcd, haha, haha, zz, kkkkk]
反转后集合:[kkkkk, zz, haha, haha, abcd]
*/
}
//reverseOrder(Comparator<T> cmp):返回一个比较器,它强行逆转指定比较器的顺序。
public static void reverseOrderDemo()
{
//首先我们创建一个TreeSet集合实现比较器
TreeSet<String> ts1 = new TreeSet<String>(new strLenComparator());
ts1.add("abcde");
ts1.add("aaa");
ts1.add("aac");
ts1.add("k");
ts1.add("cc");
//遍历
Iterator<String> it = ts1.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
/*
* 结果:安装字符串长度顺序自动排序
k
cc
aaa
aac
abcde
*/
//再创建一个TreeSet集合实现反转比较器
//有了这个逆转比较器的方法,我们想反转的时候就不需要去修改比较器的代码而是直接调用方法将其反转就可以!
TreeSet<String> ts2 = new TreeSet<String>(Collections.reverseOrder(new strLenComparator()));
ts2.add("abcde");
ts2.add("aaa");
ts2.add("aac");
ts2.add("k");
ts2.add("cc");
//遍历
Iterator<String> it1 = ts2.iterator();
while(it1.hasNext())
{
System.out.println(it1.next());
}
/*
* 结果:安装字符串长度按照反转顺序自动排序
abcde
aac
aaa
cc
k
*/
}
//注意视频17-15——集合的多线程同步方法!——注意源码的解析!
//shuffle(List<?> list) 使用默认随机源对指定列表进行置换。
public static void shuffleDemo()
{
List<String> list = new ArrayList<String>();
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
System.out.println(list);
Collections.shuffle(list);
System.out.println(list);
/*
* 结果:随机置换成功,每次置换的结果都不同
[abcd, aaa, zz, kkkkk, qq, z]
[kkkkk, qq, zz, z, abcd, aaa]
*/
}
}
class strLenComparator implements Comparator<String>
{
public int compare(String s1,String s2)
{
//先比较字符串长度
if(s1.length() > s2.length())
return 1;
if(s1.length() < s2.length())
return -1;
//字符串长度相等再用compareTo比较自然顺序
return s1.compareTo(s2);
}
}
2、Arrays工具类(主要是数组变集合的方法)
Arrays:用于操作数组的工具类。里面都是静态方法。下面是代码示例
package pack;
import java.util.*;
class ArraysDemo
{
public static void main(String[] args)
{
//将数组转换为字符串
int[] arr = {1,6,3,5};
System.out.println(Arrays.toString(arr));
/*
* asList:public static <T> List<T> asList(T... a):返回一个受指定数组支持的固定大小的列表。
* asList:将数组变成list集合
*/
String[] arr1 = {"abc","cc","kkkk"};
List<String> list = Arrays.asList(arr1);
System.out.println(list);
/*把数组变成list集合有什么好处?
可以使用集合的思想和方法来操作数组中的元素。(数组的方法较少而集合的方法较多)
(如想知道数组是否包含某个元素,数组没有专门的方法,只能遍历,而转换为集合就可以使用contains判断)
注意:将数组变成集合,不可以使用集合的增删方法,因为数组的长度是固定。
下面是可以使用的方法:
contains、get、indexOf、subList();
如果你增删。那么会反生UnsupportedOperationException,
*/
// list.add("qq");//报错:java.lang.UnsupportedOperationException
//特殊:基本数据类型数组转换为List集合
int[] arr2 = {1,6,3,5};
List li = Arrays.asList(arr2);//这里写泛型应该写作List<int[]> li
System.out.println(li);//打印:[[I@7852e922],也就是说集合li中只有一个元素arr2
Integer[] arr3 = {1,6,3,5};
List<Integer> li2 = Arrays.asList(arr3);
System.out.println(li2);//[1, 6, 3, 5]:因为现在Integer创建的数组里面的数字都是Integer对象
/*
如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集合中的元素。
如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
*/
}
//如果不将数组转换为集合,想判断数组中是否包含某个元素,就必须用循环遍历数组,用equals判断每一个元素是否相等
public static boolean myContains(String[] arr,String key)
{
for(int x=0;x<arr.length; x++)
{
if(arr[x].equals(key))
return true;
}
return false;
}
}
3、集合变数组的方法
集合变数组的方法如下:
package pack;
import java.util.*;
class ArraysDemo
{
public static void main(String[] args)
{
ArrayList<String> al = new ArrayList<String>();
al.add("abc1");
al.add("abc2");
al.add("abc3");
//toArray(T[] a):返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同。
String[] arr = al.toArray(new String[al.size()]);//返回一个数组,并且toArray方法必须指定数组类型及长度
System.out.println(Arrays.toString(arr));
//如果想直接将数组转换为字符串,必须用Arrays类的toString(数组)方法。直接打印arr.toString()打印的是String类型的数组对象arr,而不是数组内容
/*
* 结果分析:
* al.toArray(new String[4/5/6/....]):[abc1, abc2, abc3, null, null, null,...];
* al.toArray(new String[0/1/2/3]):[abc1, abc2, abc3]
1、指定类型的数组到底要定义多长呢?
当指定类型的数组长度小于了集合的size,那么该方法内部会创建一个新的数组,长度为集合的size;
当指定类型的数组长度大于了集合的size,就不会新创建了数组,而是使用传递进来的数组,多余的部分补null;
所以创建一个刚刚好的数组最优:al.size()。
2、为什么要将集合变数组?
为了限定对元素的操作,不需要进行增删了。(数组没办法增删,只能修改查询等)
*/
}
}
4、增强for循环——foreach
简介:
高级for循环
格式:
for(数据类型 变量名 : 被遍历的集合(Collection)或者数组)
{
}
特点:
foreach只能对集合进行遍历,获取集合元素,而不能对集合进行操作;
迭代器除了遍历,还可以进行remove集合中元素的动作。如果是用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。
传统for和高级for有什么区别呢?
高级for有一个局限性,必须有被遍历的目标;(17-18,12)
建议在遍历数组的时候,还是希望是用传统for,因为传统for可以定义脚标。
代码示例
package pack;
import java.util.*;
class ForEachDemo
{
public static void main(String[] args)
{
ArrayList<String> al = new ArrayList<String>();
al.add("abc1");
al.add("abc2");
al.add("abc3");
//List集合取出元素——迭代器或者循环,Set集合只能使用迭代器,因为其美元顺序
Iterator<String> it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
System.out.println();
//原理:(17-18,5.20)
for(String str:al)
{
str = "haha";
System.out.println(str);
}
System.out.println(al);
/*
* 结果:str遍历al的时候,第一次指向“abc1”,接着又将str指向“haha”,但是al此时的内容没有改变!
* 也就是说:foreach有局限性,只能对集合的元素做取出动作,而不能做修改动作。
haha
haha
haha
[abc1, abc2, abc3]
*/
System.out.println();
int[] arr = {1,5,2};
for(int i:arr)
{
System.out.println("i:"+i);
}
for(int x=0; x<arr.length; x++)
{
System.out.println(arr[x]);
}
System.out.println();
//对HashMap进行取元素——用foreach
HashMap<Integer,String> hm = new HashMap<Integer,String>();
hm.put(1,"a");
hm.put(2,"b");
hm.put(3,"c");
Set<Integer> keySet = hm.keySet();
//先使用迭代器
Iterator<Integer> it1 = keySet.iterator();
while(it1.hasNext())
{
System.out.println(hm.get(it1.next()));
}
//使用foreach——这里省略了将键放入迭代器的一步,所以使用foreach对HashMap进行值遍历要更加简单!
for(Integer i:keySet)//遍历keySet的Set集合
{
System.out.println(hm.get(i));
}
// Set<Map.Entry<Integer, String>> entrySet = hm.entrySet();//一个Map.Entry<>对象表示一对键和值
// for(Map.Entry<Integer, String> me : entrySet)
//以上2句可以缩写
for(Map.Entry<Integer, String> me : hm.entrySet())
{
System.out.println(me.getValue());
}
}
}
5、方法的可变参数
可变参数的介绍如下:
/*
JDK1.5版本出现的新特性。
方法的可变参数:在使用时注意,可变参数一定要定义在参数列表最后面。
可变参数: 其实就是上一种数组参数的简写形式,不用每一次都手动的建立数组对象。
只要将要操作的元素作为参数传递即可,隐式将这些参数封装成了数组。
*/
package pack;
import java.util.*;
class ForEachDemo
{
public static void main(String[] args)
{
// show(1,2);
//虽然少定义了多个方法,但是每次都要定义一个数组,作为实际参数。
// int[] arr = {3,4};//使用数组
// show(arr);
show("haha",2,3,4);
}
//先传统方法
public static void show(int a ,int b)
{
System.out.println(a+":"+b);
}
//如果我们想打印3个值,就必须重载show方法——比较麻烦
// public static void show(int a ,int b,int c)
// public static void show(int[] arr)//干脆定义成为数组方法,在主函数内打印数组即可
//但是这样我们想打印数字就的每次new数组,很麻烦
//下面我们利用新特性
public static void show(String str ,int...arr)
{
System.out.println(arr);//[I@7852e922:说明arr代表数组
System.out.println(arr.length);//打印传入的数据的长度(数字个数)
//当然,在方法中想获取传入的可变参数,任然需要对可变参数数组进行操作
System.out.println(Arrays.toString(arr));
}
}
6、静态导入
静态导入的示例如下:
/*
StaticImport 静态导入。
当类名重名时,需要指定具体的包名。
当方法重名是,指定具备所属的对象或者类。(17-20,7.20处解析)
举例:比如,现在2个包下有同名的类
packa/Demo.class
packb/Demo.class
我们在使用的时候只导入2个包,那么我们new Demo()的时候不知道new的是哪一个Demo
import packa.*;
import packb.*;
因此,在创建对象的时候,应该加上包名:new packa.Demo()
*/
package pack;
import java.util.*;
import static java.util.Arrays.*;//导入的是Arrays这个类中的所有静态成员。
import static java.lang.System.*;//导入了System类中所有静态成员。
//注意导入所有静态成员,需要在导入的时候加上static
class StaticImportDemo
{
public static void main(String[] args)
{
int[] arr = {2,3,4,5};
Arrays.sort(arr);
int index = Arrays.binarySearch(arr, 2);
System.out.println("index:"+index);
System.out.println(Arrays.toString(arr));
//上面我们发现我们每次想使用Arrays里面的方法(都是静态)的时候,每次都要通过类名调用,很麻烦!
//我们可以通过静态导入,直接将Arrays中的所有静态成员导入,这样我们在使用Arrays的静态方法的时候便可以直接使用!
sort(arr);
int index1 = binarySearch(arr, 2);
System.out.println("index:"+index);
System.out.println(Arrays.toString(arr));
/*
这里toString前面的Arrays不能省略,因为StaticImportDemo本身继承了Object类,而Object中有toString方法
Object先进入内存,系统认为我们使用的是Object的toString方法。因此我们这里不能省略类名Arrays
*/
//关于输出:System类中有out的静态属性,那么我们将System中的所有静态成员导入,我们不需要加System类名,就可以直接使用out字段
out.print("haha");
}
}
3、集合类就业班补充
补充1:各类集合之间比较元素是否相同时底层所调用的方法。
各类集合之间比较元素是否相同时底层所引用的方法
- ArrayList、LinkedList:equals()方法;
- HashSet:equals()方法与hashCode()方法;
- TreeSet:compareTo方法return 0
另一方面,存储在ArrayList、LinkedList的元素(没有HashSet,因为其是无序的),如果这个类型的元素没有自然顺序进行排序,需要使得这个类实现Comparable接口并重写compareTo()方法,来设置自己的自然顺序。(或者自然顺序不满足要求,也可以通过实现Comparator接口并重写compare方法自定义顺序)。
比如,对于List集合,Collections也有List排序的方法sort,如果想对指定的类进行排序,这个类也必须实现Comparable接口并重写compareTo()方法,这样我们才有排序的依据。当然,List集合,我们也可以像TreeSet集合一样,传入一个Comparator比较器实现类对象,通过实现类的方法对List对象进行排序(自己设定的顺序)。——参考就业班——集合工具类Collections方法——2,3的解析。(注意此处2中Person使用的是Comparable接口的compareTo进行比较,而3中Person使用的是Comparator中的compare进行比较)