第八单元 数组与集合
Posted 誉尚学教育
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第八单元 数组与集合相关的知识,希望对你有一定的参考价值。
1. 数组(Array)
数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。
声明数组变量并不是声明 number0、number1、...、number99 一个个单独的变量,而是声明一个就像 numbers 这样的变量,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来表示一个个单独的变量。数组中某个指定的元素是通过索引来访问的。
所有的数组都是由连续的内存位置组成的。最低的地址对应第一个元素,最高的地址对应最后一个元素。
声明数组
在 C# 中声明一个数组,您可以使用下面的语法:
datatype[] arrayName;
其中,
-
datatype 用于指定被存储在数组中的元素的类型。
-
[ ] 指定数组的秩(维度)。秩指定数组的大小。
-
arrayName 指定数组的名称。
例如:
double[] balance;
初始化数组
声明一个数组不会在内存中初始化数组。当初始化数组变量时,您可以赋值给数组。
数组是一个引用类型,所以您需要使用 new 关键字来创建数组的实例。
例如:
double[] balance = new double[10];
赋值给数组
您可以通过使用索引号赋值给一个单独的数组元素,比如:
double[] balance = new double[10]; balance[0] = 4500.0;
您可以在声明数组的同时给数组赋值,比如:
double[] balance = 2340.0, 4523.69, 3421.0;
您也可以创建并初始化一个数组,比如:
int [] marks = new int[5] 99, 98, 92, 97, 95;
在上述情况下,你也可以省略数组的大小,比如:
int [] marks = new int[] 99, 98, 92, 97, 95;
您也可以赋值一个数组变量到另一个目标数组变量中。在这种情况下,目标和源会指向相同的内存位置:
int [] marks = new int[] 99, 98, 92, 97, 95; int[] score = marks;
当您创建一个数组时,C# 编译器会根据数组类型隐式初始化每个数组元素为一个默认值。例如,int 数组的所有元素都会被初始化为 0。
访问数组元素
元素是通过带索引的数组名称来访问的。这是通过把元素的索引放置在数组名称后的方括号中来实现的。例如:
double salary = balance[9];
下面是一个实例,使用上面提到的三个概念,即声明、赋值、访问数组:
实例
static void Main(string[] args) int [] n = new int[10]; /* n 是一个带有 10 个整数的数组 */ int i,j; /* 初始化数组 n 中的元素 */ for ( i = 0; i < 10; i++ ) n[ i ] = i + 100; /* 输出每个数组元素的值 */ for (j = 0; j < 10; j++ ) Console.WriteLine("Element[0] = 1", j, n[j]); Console.ReadKey();
当上面的代码被编译和执行时,它会产生下列结果:
Element[0] = 100 Element[1] = 101 Element[2] = 102 Element[3] = 103 Element[4] = 104 Element[5] = 105 Element[6] = 106 Element[7] = 107 Element[8] = 108 Element[9] = 109
2. 集合
集合(Collection)类是专门用于数据存储和检索的类。这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类实现了相同的接口。
集合(Collection)类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等。这些类创建 Object 类的对象的集合。在 C# 中,Object 类是所有数据类型的基类。
各种集合类和它们的用法
下面是各种常用的 System.Collection 命名空间的类。点击下面的链接查看细节。
类 | 描述和用法 |
---|---|
动态数组(ArrayList) | 它代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。 |
哈希表(Hashtable) | 它使用键来访问集合中的元素。当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值。哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。 |
排序列表(SortedList) | 它可以使用键和索引来访问列表中的项。排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果您使用索引访问各项,则它是一个动态数组(ArrayList),如果您使用键访问各项,则它是一个哈希表(Hashtable)。集合中的各项总是按键值排序。 |
堆栈(Stack) | 它代表了一个后进先出的对象集合。当您需要对各项进行后进先出的访问时,则使用堆栈。当您在列表中添加一项,称为推入元素,当您从列表中移除一项时,称为弹出元素。 |
队列(Queue) | 它代表了一个先进先出的对象集合。当您需要对各项进行先进先出的访问时,则使用队列。当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队。 |
点阵列(BitArray) | 它代表了一个使用值 1 和 0 来表示的二进制数组。当您需要存储位,但是事先不知道位数时,则使用点阵列。您可以使用整型索引从点阵列集合中访问各项,索引从零开始。 |
1. 动态数组(ArrayList)
动态数组(ArrayList)代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。
ArrayList 类的方法和属性
下表列出了 ArrayList 类的一些常用的 属性:
属性 | 描述 |
---|---|
Capacity | 获取或设置 ArrayList 可以包含的元素个数。 |
Count | 获取 ArrayList 中实际包含的元素个数。 |
IsFixedSize | 获取一个值,表示 ArrayList 是否具有固定大小。 |
IsReadOnly | 获取一个值,表示 ArrayList 是否只读。 |
IsSynchronized | 获取一个值,表示访问 ArrayList 是否同步(线程安全)。 |
Item[Int32] | 获取或设置指定索引处的元素。 |
SyncRoot | 获取一个对象用于同步访问 ArrayList。 |
下表列出了 ArrayList 类的一些常用的 方法:
序号 | 方法名 & 描述 |
---|---|
1 | public virtual int Add( object value ); 在 ArrayList 的末尾添加一个对象。 |
2 | public virtual void AddRange( ICollection c ); 在 ArrayList 的末尾添加 ICollection 的元素。 |
3 | public virtual void Clear(); 从 ArrayList 中移除所有的元素。 |
4 | public virtual bool Contains( object item ); 判断某个元素是否在 ArrayList 中。 |
5 | public virtual ArrayList GetRange( int index, int count ); 返回一个 ArrayList,表示源 ArrayList 中元素的子集。 |
6 | public virtual int IndexOf(object); 返回某个值在 ArrayList 中第一次出现的索引,索引从零开始。 |
7 | public virtual void Insert( int index, object value ); 在 ArrayList 的指定索引处,插入一个元素。 |
8 | public virtual void InsertRange( int index, ICollection c ); 在 ArrayList 的指定索引处,插入某个集合的元素。 |
9 | public virtual void Remove( object obj ); 从 ArrayList 中移除第一次出现的指定对象。 |
10 | public virtual void RemoveAt( int index ); 移除 ArrayList 的指定索引处的元素。 |
11 | public virtual void RemoveRange( int index, int count ); 从 ArrayList 中移除某个范围的元素。 |
12 | public virtual void Reverse(); 逆转 ArrayList 中元素的顺序。 |
13 | public virtual void SetRange( int index, ICollection c ); 复制某个集合的元素到 ArrayList 中某个范围的元素上。 |
14 | public virtual void Sort(); 对 ArrayList 中的元素进行排序。 |
15 | public virtual void TrimToSize(); 设置容量为 ArrayList 中元素的实际个数。 |
static void Main(string[] args) ArrayList al = new ArrayList(); Console.WriteLine("Adding some numbers:"); al.Add(45); al.Add(78); al.Add(33); al.Add(56); al.Add(12); al.Add(23); al.Add(9); Console.WriteLine("Capacity: 0 ", al.Capacity); Console.WriteLine("Count: 0", al.Count); Console.Write("Content: "); foreach (int i in al) Console.Write(i + " "); Console.WriteLine(); Console.Write("Sorted Content: "); al.Sort(); foreach (int i in al) Console.Write(i + " "); Console.WriteLine(); Console.ReadKey();
当上面的代码被编译和执行时,它会产生下列结果:
Adding some numbers: Capacity: 8 Count: 7 Content: 45 78 33 56 12 23 9 Sorted Content: 9 12 23 33 45 56 78
3. 使用 foreach 循环
在前面的实例中,我们使用一个 for 循环来访问每个数组元素。您也可以使用一个 foreach 语句来遍历数组。
实例
static void Main(string[] args) int [] n = new int[10]; /* n 是一个带有 10 个整数的数组 */ /* 初始化数组 n 中的元素 */ for ( int i = 0; i < 10; i++ ) n[i] = i + 100; /* 输出每个数组元素的值 */ foreach (int j in n ) int i = j-100; Console.WriteLine("Element[0] = 1", i, j); Console.ReadKey();
当上面的代码被编译和执行时,它会产生下列结果:
Element[0] = 100 Element[1] = 101 Element[2] = 102 Element[3] = 103 Element[4] = 104 Element[5] = 105 Element[6] = 106 Element[7] = 107 Element[8] = 108 Element[9] = 109
4. C# 数组细节
在 C# 中,数组是非常重要的,且需要了解更多的细节。下面列出了 C# 程序员必须清楚的一些与数组相关的重要概念:
概念 | 描述 |
---|---|
多维数组 | C# 支持多维数组。多维数组最简单的形式是二维数组。 |
传递数组给函数 | 您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。 |
参数数组 | 这通常用于传递未知数量的参数给函数。 |
1. 多维数组
C# 支持多维数组。多维数组又称为矩形数组。
您可以声明一个 string 变量的二维数组,如下:
string [,] names;
或者,您可以声明一个 int 变量的三维数组,如下:
int [ , , ] m;
二维数组
多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。
一个二维数组可以被认为是一个带有 x 行和 y 列的表格。下面是一个二维数组,包含 3 行和 4 列:
初始化二维数组
多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组。
int [,] a = new int [3,4] 0, 1, 2, 3 , /* 初始化索引号为 0 的行 */ 4, 5, 6, 7 , /* 初始化索引号为 1 的行 */ 8, 9, 10, 11 /* 初始化索引号为 2 的行 */ ;
访问二维数组元素
二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。例如:
int val = a[2,3];
上面的语句将获取数组中第 3 行第 4 个元素。您可以通过上面的示意图来进行验证。让我们来看看下面的程序,我们将使用嵌套循环来处理二维数组:
static void Main(string[] args) /* 一个带有 5 行 2 列的数组 */ int[,] a = new int[5, 2] 0,0, 1,2, 2,4, 3,6, 4,8 ; int i, j; /* 输出数组中每个元素的值 */ for (i = 0; i < 5; i++) for (j = 0; j < 2; j++) Console.WriteLine("a[0,1] = 2", i, j, a[i,j]); Console.ReadKey();
当上面的代码被编译和执行时,它会产生下列结果:
a[0,0]: 0 a[0,1]: 0 a[1,0]: 1 a[1,1]: 2 a[2,0]: 2 a[2,1]: 4 a[3,0]: 3 a[3,1]: 6 a[4,0]: 4 a[4,1]: 8
2. 传递数组给函数
在 C# 中,您可以传递数组作为函数的参数。您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
public static double getAverage(int[] arr, int size) int i; double avg; int sum = 0; for (i = 0; i < size; ++i) sum += arr[i]; avg = (double)sum / size; return avg; static void Main(string[] args) MyArray app = new MyArray(); /* 一个带有 5 个元素的 int 数组 */ int [] balance = new int[]1000, 2, 3, 17, 50; double avg; /* 传递数组的指针作为参数 */ avg = getAverage(balance, 5 ) ; /* 输出返回值 */ Console.WriteLine( "平均值是: 0 ", avg ); Console.ReadKey();
当上面的代码被编译和执行时,它会产生下列结果:
平均值是: 214.4
3. 参数数组
有时,当声明一个方法时,您不能确定要传递给函数作为参数的参数数目。C# 参数数组解决了这个问题,参数数组通常用于传递未知数量的参数给函数。
params 关键字
在使用数组作为形参时,C# 提供了 params 关键字,使调用数组为形参的方法时,既可以传递数组实参,也可以传递一组数组元素。params 的使用格式为:
public 返回类型 方法名称( params 类型名称[] 数组名称 )
public static int AddElements(params int[] arr) int sum = 0; foreach (int i in arr) sum += i; return sum; static void Main(string[] args) ParamArray app = new ParamArray(); int sum = app.AddElements(512, 720, 250, 567, 889); Console.WriteLine("总和是: 0", sum); Console.ReadKey();
当上面的代码被编译和执行时,它会产生下列结果:
总和是: 2938
5. 作业
-
求两个二维数组的和
1 3 5 2 4 6
7 9 2 + 8 9 7 = ?
4 6 8 3 5 1
数组A 数组B 数组C -
假设一个数组共有20个元素,其中前5位元素是:1,1,2,3,5..... 打印出数组的第20位元素的值。
-
创建一个
ArrayList
集合对象,循环10次向集合中添加10个元素。 -
将第三题的集合按从大到小的顺序进行输出(可直接调用现成的集合封装好的方法)。
视频教程:
誉尚学教育_誉尚学教育腾讯课堂官网 (qq.com)
或者:C# 最强入门编程(.Net 学习系列开山巨作)_哔哩哔哩_bilibili
第八章.Java集合
Java集合类是一种特别有用的工具类,可用于存储数量不等的对象。Java集合大致可分为Set、List、Queue和Map四种体系
Set代表无序、不可重复的集合
List代表有序、重复的集合
Map代表具有映射关系的集合
Java5又增加了Queue代表一种队列集合
java集合概述:
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),java提供了集合类。
集合类主要负责保存、盛装其他数据,因此,集合类也被称为容器类。所有的集合类都在java.util包下,后来为了处理多线程环境下的并发安全问题,Java5在java.util.concurrent
包下提供了一些多线程支持的集合类。
集合和数组不一样,数组元素可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上只是保存对象的引用,但通常习惯上认
为集合里保存的是对象)
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
Set和List接口是Collection接口派生的两个子接口,分别表示了无序集合和有序集合;Queue是Java提供的队列实现。
对于Set、List、Queue、Map四种集合,最常用的实现类是:HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList和HashMap、TreeMap。
Collection接口 和 Iterator接口:
Collection接口是List、Set和Queue接口的父接口
Collection接口定义了以下操作集合元素的方法:
1.boolean add(Object o):该方法用于向集合中添加一个元素,若集合对象被添加操作改变了,则返回true。
2.boolean addAll(Collection c):该方法把集合c里的所有元素添加到指定集合里。若集合对象被添加操作改变了,则返回true。
3.void clear():清除集合里的所有元素,将集合长度变为0。
4.boolean contains(Object o):返回集合里是否包含指定元素。
5.boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素
6.boolean isEmpty():返回集合是否为空。当集合长度为0时,返回true,否则返回false。
7.Iterator iterator():返回一个Iterator对象,用于遍历集合里的元素
8.boolean remove(Object o):删除集合中的指定元素0,当集合中包含了一个或多个元素o时,该方法只删除第一个符合条件的元素,该方法将返回true。
9.boolean removeAll(Collection c):从集合中删除集合c中包含的所有元素(相当于把调用该方法的集合减集合c),若删除了一个或一个以上的元素,则该方法返回true
10.boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(相当于把调用该方法的集合编程该集合和集合c的交集),若该操作改变了调用该方法的集合,则
该方法返回true。
11.int size():该方法返回集合里元素的个数
12.Object[] toArray():该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素。
上面的方法无需硬性记忆,对于集合的操作无非是:添加对象、删除对象、清空集合、判断集合是否为空等。
1 import java.util.Collection; 2 import java.util.ArrayList; 3 import java.util.HashSet; 4 5 public class CollectionTest{ 6 public static void main(String[] args){ 7 Collection c = new ArrayList(); 8 //添加元素 9 c.add("孙悟空"); 10 //虽然集合里不能放基本类型的值,但Java支持自动装箱 11 c.add(6); 12 System.out.println("c集合的元素个数为:" + c.size());//输出2 13 //删除指定元素 14 c.remove(6); 15 System.out.println("c集合的元素个数为:" + c.size());//输出1 16 //判断是否包含指定字符串 17 System.out.println("c集合是否包含\\"孙悟空\\"字符串:" + c.contains("孙悟空"));//输出true 18 c.add("轻量级Java EE企业应用实战"); 19 System.out.println("c集合的元素:" + c); 20 Collection books = new HashSet(); 21 books.add("轻量级Java EE企业应用实战"); 22 books.add("疯狂Java讲义"); 23 System.out.println("c集合是否完全包含books集合?" + c.containsAll(books));//输出false 24 //用c集合减去books集合里的元素 25 c.removeAll(books); 26 System.out.println("c集合的元素:" + c); 27 //删除c集合里所有的元素 28 c.clear(); 29 System.out.println("c集合的元素:" + c); 30 //控制books集合里只剩下c集合里也包含的元素 31 books.retainAll(c); 32 System.out.println("books集合的元素:" + books); 33 } 34 }
使用System.out.println()方法来输出集合对象时,将输出[ele1, ele2, ...]的形式,因为所有Collection实现类都重写了toString()方法,该方法可以一次性的输出集合中的所
有元素。
使用Lambda表达式遍历集合:
Java8位Iterable接口新增了一个forEach(Consumer action)默认方法,该方法所需参数的类型是一个函数式接口,而Iterable接口是Collection接口的父接口因此Collection集合也
可直接调用该方法。
当程序调用Iterable的forEach(Consumer action)遍历集合时,程序会依次将集合元素传给Consumer的accept(T t)方法(该接口中唯一的抽象方法)。
1 import java.util.Collection; 2 import java.util.HashSet; 3 4 public class CollectionEach{ 5 public static void main(String[] args){ 6 //创建一个集合 7 Collection books = new HashSet(); 8 books.add("轻量级Java EE企业应用实战"); 9 books.add("疯狂Java讲义"); 10 books.add("疯狂Android讲义"); 11 //调用forEach方法遍历集合 12 books.forEach(obj -> System.out.println("迭代集合元素:" + obj)); 13 } 14 }
使用Java8增强的Iterator遍历集合元素:
Iterator主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器
Iterator接口定义的4个方法:
1.boolean hasNext():若被迭代的集合元素还没有被遍历完,则返回true
2.Object next():返回集合中的下一个元素
3.void remove():删除集合中上一次next方法返回的元素。
4.void forEachRemainIng(Consumer action):这是Java8为Iterator新增的默认方法,该方法可使用Lambda表达式来遍历集合元素
1 import java.util.HashSet; 2 import java.util.Collection; 3 import java.util.Iterator; 4 5 public class IteratorTest{ 6 public static void main(String[] args){ 7 //创建集合、添加元素的代码与前一个程序相同 8 Collection books = new HashSet(); 9 books.add("轻量级Java EE企业应用实战"); 10 books.add("疯狂Java讲义"); 11 books.add("疯狂Android讲义"); 12 //获取books集合对应的迭代器 13 Iterator it = books.iterator(); 14 while(it.hasNext()){ 15 //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换 16 String book = (String) it.next(); 17 System.out.println(book); 18 if(book.equals("疯狂Java讲义")){ 19 //从集合中删除上一次next()方法返回的元素 20 it.remove(); 21 } 22 //对book变量赋值,不会改变集合元素本身 23 book = "测试字符串"; 24 } 25 System.out.println(books); 26 } 27 }
从上面代码可以看出,Iterator仅用于遍历集合,Iterator本身并不提供盛装对象的能力。若需要创建Iterator对象,则必须有一个被迭代的集合。
Iterator必须依附于Collection对象,若有一个Iterator对象,则必然有一个与之关联的Collection对象。
使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iteratorde remove()方法删除上一次next()方法返回的集合元素才可以;否则将会引发
java.util.ConcurrentModificationException异常:
1 import java.util.HashSet; 2 import java.util.Collection; 3 import java.util.Iterator; 4 5 public class IteratorErrorTest{ 6 public static void main(String[] args){ 7 //创建集合、添加元素的代码与前一个程序相同 8 Collection books = new HashSet(); 9 books.add("轻量级Java EE企业应用实战"); 10 books.add("疯狂Java讲义"); 11 books.add("疯狂Android讲义"); 12 //获取books集合对应的迭代器 13 Iterator it = books.iterator(); 14 while(it.hasNext()){ 15 //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换 16 String book = (String) it.next(); 17 System.out.println(book); 18 if(book.equals("疯狂Java讲义")){ 19 //使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常 20 books.remove(book); 21 } 22 } 23 System.out.println(books); 24 } 25 }
没有报错,不知道什么原因。
使用Lambda表达式遍历Iterator:
Java8为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需Consumer参数同样也是函数式接口:
1 import java.util.HashSet; 2 import java.util.Collection; 3 import java.util.Iterator; 4 5 public class IteratorEach{ 6 public static void main(String[] args){ 7 //创建集合、添加元素的代码与前一个程序相同 8 Collection books = new HashSet(); 9 books.add("轻量级Java EE企业应用实战"); 10 books.add("疯狂Java讲义"); 11 books.add("疯狂Android讲义"); 12 //获取books集合对应的迭代器 13 Iterator it = books.iterator(); 14 it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj)); 15 } 16 }
使用foreach循环遍历集合元素:
Java5提供了foreach循环迭代访问集合元素:
1 import java.util.HashSet; 2 import java.util.Collection; 3 import java.util.Iterator; 4 5 public class ForeachTest{ 6 public static void main(String[] args){ 7 //创建集合、添加元素的代码与前一个程序相同 8 Collection books = new HashSet(); 9 books.add("轻量级Java EE企业应用实战"); 10 books.add("疯狂Java讲义"); 11 books.add("疯狂Android讲义"); 12 for(Object obj : books){ 13 //此处的book变量也不是集合元素本身 14 String book = (String) obj; 15 System.out.println(book); 16 if(book.equals("疯狂Java讲义")){ 17 //下面代码引发异常 18 books.remove(book); 19 } 20 } 21 System.out.println(books); 22 } 23 }
没有报错,不知道为什么?
使用Java8新增的Predicate操作集合:
Java8位Collection集合新增了一个removeIf(Predicate filter)方法,该方法会批量删除符合filter条件的所有元素。Predicate也是函数式接口。
1 import java.util.Collection; 2 import java.util.HashSet; 3 4 public class PredicateTest{ 5 public static void main(String[] args){ 6 //创建books集合、为books集合添加元素的代码 7 Collection books = new HashSet(); 8 books.add(new String("轻量级Java EE企业应用实战")); 9 books.add(new String("疯狂Java讲义")); 10 books.add(new String("疯狂IOS讲义")); 11 books.add(new String("疯狂Ajax讲义")); 12 books.add(new String("疯狂Android讲义")); 13 14 //使用Lambda表达式(目标类型是Predicate)过滤集合 15 //所有长度小于10的字符串元素都会被删除 16 books.removeIf(ele -> ((String) ele).length() < 10); 17 System.out.println(books); 18 } 19 }
对上面的集合提出三个统计要求:
1.统计书名中出现“疯狂”字符串的图书数量
2.统计书命中出现“Java”字符串的图书数量
3.统计书名长度大于10的图书数量
1 import java.util.Collection; 2 import java.util.HashSet; 3 import java.util.function.Predicate; 4 5 public class PredicateTest{ 6 public static void main(String[] args){ 7 //创建books集合、为books集合添加元素的代码 8 Collection books = new HashSet(); 9 books.add(new String("轻量级Java EE企业应用实战")); 10 books.add(new String("疯狂Java讲义")); 11 books.add(new String("疯狂IOS讲义")); 12 books.add(new String("疯狂Ajax讲义")); 13 books.add(new String("疯狂Android讲义")); 14 15 //统计书名中包含“疯狂”子串的图书数量 16 System.out.println(calAll(books, ele -> ((String) ele).contains("疯狂"))); 17 //统计书名中包含“Java”子串的图书数量 18 System.out.println(calAll(books, ele -> ((String) ele).contains("Java"))); 19 //统计书名长度大于10的图书数量 20 System.out.println(calAll(books, ele -> ((String) ele).length() > 10)); 21 } 22 23 public static int calAll(Collection books, Predicate p){ 24 int total = 0; 25 for(Object obj : books){ 26 //使用Predicate的test()方法判断该对象是否满足指定的条件 27 if(p.test(obj)){ 28 total ++; 29 } 30 } 31 return total; 32 } 33 }
若采用传统方式完成上面3个需求,需要执行3次循环,但是采用Predicate只需要一个方法即可。
使用Java8新增的Stream操作集合:
Java8新增了Stream、IntStream、LongStream、DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素。
上面四个接口中Stream是通用的流接口,IntStream、LongStream、DoubleStream则代表元素类型为int、long、double的流。
Java8还为上面每个流式API提供了对应的Builder,如Stream.Builder、IntStream.Builder、LongStream.Builder、DoubleStream.Builder
独立使用Stream的步骤如下:
1.使用Stream或XxxStream的builder()类方法创建该Stream对应的Builder
2.重复调用Builder的add()方法向该流中添加多个元素
3.调用Builder的build()方法获取对应的Stream
4.调用Stream的聚集方法
1 import java.util.stream.IntStream; 2 3 public class IntStreamTest{ 4 public static void main(String[] args){ 5 IntStream is = IntStream.builder() 6 .add(20) 7 .add(13) 8 .add(-2) 9 .add(18) 10 .build(); 11 //下面调用聚集方法的代码每次只能执行一行 12 System.out.println("is所有元素的最大值:" + is.max().getAsInt()); 13 System.out.println("is所有元素的最小值:" + is.min().getAsInt()); 14 System.out.println("is所有元素的总和:" + is.sum()); 15 System.out.println("is所有元素的总数:" + is.count()); 16 System.out.println("is所有元素的平均值:" + is.average()); 17 System.out.println("is所有元素的平方是否都大于10:" + is.allMatch(ele -> ele * ele > 20)); 18 System.out.println("is是否包含任何元素的平方大于20:" + is.anyMatch(ele -> ele * ele > 20)); 19 //将is映射成一个新Stream,新Stream的每个元素时原Stream元素的2倍 + 1 20 IntStream newIs = is.map(ele -> ele * 2 + 1); 21 //使用方法引用的方式来遍历集合元素 22 newIs.forEach(System.out::println);//输出41 27 -3 37 23 } 24 }
上面System.out.println()方法,每次只能执行一行,其余的要注释掉,这就是报错的原因。
1 import java.util.stream.IntStream; 2 3 public class IntStreamTest{ 4 public static void main(String[] args){ 5 IntStream is = IntStream.builder() 6 .add(20) 7 .add(13) 8 .add(-2) 9 .add(18) 10 .build(); 11 //下面调用聚集方法的代码每次只能执行一行 12 //System.out.println("is所有元素的最大值:" + is.max().getAsInt()); 13 //System.out.println("is所有元素的最小值:" + is.min().getAsInt()); 14 //System.out.println("is所有元素的总和:" + is.sum()); 15 //System.out.println("is所有元素的总数:" + is.count()); 16 System.out.println("is所有元素的平均值:" + is.average()); 17 //System.out.println("is所有元素的平方是否都大于10:" + is.allMatch(ele -> ele * ele > 20)); 18 //System.out.println("is是否包含任何元素的平方大于20:" + is.anyMatch(ele -> ele * ele > 20)); 19 //将is映射成一个新Stream,新Stream的每个元素时原Stream元素的2倍 + 1 20 //IntStream newIs = is.map(ele -> ele * 2 + 1); 21 //使用方法引用的方式来遍历集合元素 22 //newIs.forEach(System.out::println);//输出41 27 -3 37 23 } 24 }
注释掉所有聚集操作,只保留一条后,就不会报错了。
Stream提供了大量的方法进行聚集操作,这些方法有“中间的(intermediate)”,也有“末端的(terminal)”。
中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法。上面map()方法就是中间方法。中间方法的返回值是另外一个流
末端方法:末端方法是对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用。
除此之外,流的方法还有如下两个特征:
1.有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理。有状态的方法往往需要更大的性能
开销
2.短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素。
介绍一些Stream常用的中间方法:
1.filter(Predicate predicate):过滤Stream中所有不符合predicate的元素
2.mapToXxx(ToXxxFunction mapper):使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素
3.peek(Consumer action):依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试。
4.distinct():该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true)。这是一个有状态的方法。
5.sorted():该方法用于保证流中的元素在后续访问中处于有序状态。这是一个有状态的方法。
6.limit(long maxSize):该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态、短路方法。
介绍一些Stream常用的末端方法:
1.forEach(Consumer action):遍历流中所有元素,对每个元素执行action
2.toArray():将流中所有元素转换为一个数组
3.reduce():该方法有三个重载的版本,都用于通过某种操作来合并流中的元素
4.min():返回流中所有元素的最小值
5.max():返回流中所有元素的最大值
6.count():返回流中所有元素的数量
7.anyMatch(Predicate predicate):判断流中是否至少包含一个元素符合Predicate条件
8.AllMatch(Predicate predicate):判断流中是否每个元素都符合Predicate条件
9.noneMatch(Predicate predicate):判断流中是否所有元素都不符合Predicate条件
10.findFirst():返回流中的第一个元素
11.findAny():返回流中任意一个元素
java8允许使用流式API来操作集合,Collection接口提供了一个stream()默认方法,该默认方法可返回该集合对应的流,接下来即可通过流式API来操作集合元素。
1 import java.util.Collection; 2 import java.util.HashSet; 3 import java.util.stream.Stream; 4 5 public class CollectionStream{ 6 public static void main(String[] args){ 7 //创建books集合、为books集合添加元素的代码 8 Collection books = new HashSet(); 9 books.add(new String("轻量级Java EE企业应用实战")); 10 books.add(new String("疯狂Java讲义")); 11 books.add(new String("疯狂IOS讲义")); 12 books.add(new String("疯狂Ajax讲义")); 13 books.add(new String("疯狂Android讲义")); 14 //统计书名中包含“疯狂”子串的图书数量 15 System.out.println(books.stream().filter(ele -> ((String) ele).contains("疯狂")).count()); 16 //统计书名中包含“Java”子串的图书数量 17 System.out.println(books.stream().filter(ele -> ((String) ele).contains("Java")).count()); 18 //统计书名长度大于10的图书数量 19 System.out.println(books.stream().filter(ele -> ((String) ele).length() > 10).count()); 20 //先调用Collection对象的stream()方法将集合转换为Stream 21 //再调用Stream的mapToInt()方法获取原有的Stream对应的IntStream 22 books.stream().mapToInt(ele -> ((String) ele).length()).forEach(System.out::println);//输出8 11 16 7 8 23 } 24 }
Set集合:
Set集合与Collection基本相同,没有提供任何额外的方法。实际上Set就是Collection,只是行为略有不同(Set不允许包含重复元素)。
Set集合不允许包含相同的元素,若试图把两个相同元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。
上面介绍的是Set集合通用的知识,因此完全适合HashSet、TreeSet、EnumSet三个实现类。
HashSet类:
HashSet按Hash算法来存储集合中的元素,因此具有良好的存取和查找性能
HashSet有如下特点:
1.不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化
2.HashSet不是同步的,若多个线程访问一个HashSet,假设有两个或两个以上的线程同时修改HashSet集合时,则必须通过代码来保证其同步
3.集合元素值可以是null
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置
。若有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。