1、概述
数组元素既可以是int等基本数据类型,也可以是对象引用,集合中元素只能是对象引用,集合类位于java.util包下。java集合主要包含List、Set、Queue、Map四种体系。
Set、List、Queue是由Collection接口派生而来的子接口,Collection包含add()、remove()、size()等元素操作方法。List、Set、Queue又派生出了ArrayList、ArrayDeque、LinkedList、HashSet、TreeSet等实现类。HashMap、TreeMap等实现类的父接口是Map,Map不是继承自Collection接口,Map也有put()、remove()、size()等方法。
Collection的方法removeIf()可以删除符合指定条件的元素,其参数是一个Predicate类型的函数式接口,可以传入一个Lambda表达式给它。Predicate是一个比较常用的谓词对象,它包含的抽象函数为boolean test(T t),重写它来实现条件判断逻辑。
Iterator是Java集合框架的顶级接口,实现此接口使集合可以通过迭代器遍历自身元素。Iterable中有一个返回Iterator的iterator()抽象方法,List、Set等都重写了该方法返回对应类型的迭代器对象,通过这个迭代器也可以遍历集合。
Collection的父接口Iterable中有一个默认方法forEach(Consumer action),可以调用它来遍历集合。forEach()的参数是Consumer类型,它是一个函数式接口类型,包含的虚函数为void accept(T t)。当使用forEach()遍历集合元素时,会将元素传给accept()方法。因此可以给forEach传入Lambda表达式,在该表达式中对元素进行访问处理:
Collection的forEach()方的使用和源码:
Collection c = new ArrayList();
c.add("java");
c.add("c#");
c.add("c++");
c.forEach(item->System.out.println(item)/*System.out::println*/);
default void forEach(Consumer<? super Type> action)
{
......
for(Type ele : this)
{
action.accept(ele);
}
}
使用容器的remove()、add()方法删除、添加元素后会导致原来的迭代器失效,这样再使用迭代器进行迭代容器的话会引发java.util.ConcurrentModificationException异常,如下所示的两个示例。如果我们需要在迭代遍历集合中删除集合中的元素的话,可以使用迭代器的remove()方法, 它删除上次next()获得的集合元素,不会抛出异常。
Collection c = new ArrayList();
c.add("java");
c.add("c#");
c.add("c++");
Iterator it = c.iterator();
c.add("php");
while(it.hasNext())
{
String s = (String)it.next(); //此处会引发异常
System.out.println(s);
}
ArrayList<Integer> aryList = new ArrayList();
aryList.add(1);
aryList.add(2);
aryList.add(3);
var iter = aryList.listIterator(2);
if(iter.hasNext())
{
Integer i = iter.next(); // 3
//iter.add(4); // 1, 2, 3, 4
iter.set(4); // 1, 2, 4
}
System.out.println(aryList);
Collection c = new ArrayList();
c.add("java");
c.add("c#");
c.add("c++");
//使用foreach循环遍历集合元素
for(Object item : c)
{
String s = (String)item;
System.out.println(s);
if(s == "c++")
c.remove(item); //error:foreach循环中不能改变集合,否则会引发异常!
}
//使用迭代器循环遍历集合元素
Iterator it = c.iterator();
while(it.hasNext())
{
String s = (String)it.next(); //next()返回的是Object类型,所以需要强转
System.out.println(s); //输出元素
if(s == "c++")
it.remove(); //在迭代循环中可以通过迭代器的remove来删除元素
}
迭代器有一个forEachRemaining()方法,它类似于容器的forEach方法,可以配合一个Lambda表达式来遍历当前和剩余的元素。
Conllection不知道收集的对象的具体类型,其都是以Object来参考被收集的对象的,所以Conllection的话可以收集不同类型的对象。如果在定义集合的时候不是使用的Collection而是具体的集合类型的话应该使用泛型来指定集合只能存储指定元素类型,如ArrayList<Integer> al = new ArrayList()。
package xu;
import java.util.*;
import java.lang.*;
public class Test
{
public static void main(String[] args)
{
Collection c = new ArrayList();
c.add("java");
c.add("c#");
c.add("c++");
Integer inte = 0;
c.add(inte);
c.add(10); //对于基本数据类型会自动装箱
System.out.println(c); //输出[c#, c++, java]
boolean b = c.contains(10); //true
if(b)
c.remove(10);
c.removeIf(item -> ((String)item).length() < 3); //删除长度小于3的元素
String str = c.toString();
System.out.println(c); //输出集合:[java, c++]
//使用foreach循环遍历集合元素
for(Object item : c)
{
String s = (String)item;
System.out.println(s);
//c.remove("c++"); //error:foreach循环中不能改变集合,否则会引发异常!
}
//使用Lambda表达式遍历集合
c.forEach(item->System.out.println(item)); //输出集合中每个元素
//使用迭代器遍历集合
Iterator it = c.iterator();
while(it.hasNext())
{
String s = (String)it.next(); //next()返回的是Object类型,所以需要强转
System.out.println(s); //输出元素
if(s == "c++")
it.remove(); //在迭代循环中可以通过迭代器的remove来删除元素
}
//使用迭代器配合Lambda表达式遍历集合
it = c.iterator();
it.forEachRemaining(System.out.println);
}
}
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception
{
String[] ary = {"a", "b", "c"};
List<String> l1 = List.of(ary);
l1.forEach(item->System.out.println(item));
List<String> l2 = List.of(new String("1"), new String("2"), new String("3"));
l2.forEach(System.out::println);
}
}
2、流式操作
Stream、IntStream、LongStream、DoubleStream接口提供了对对象的流式操作,集合通过Stream可以很方便的实现很多操作,如对集合过滤、转换等,集合的stream()方法可以获得对应的Stream对象,Stream的collect()可以获得流对应的集合。Stream的map()、filter()等方法可以通过指定的操作来获得一个新的Stream(即根据指定条件过滤),Stream中还有许多实用方法,如sum()、max()、average()等。使用Stream的collect()还可以将不同的集合类型进行互转。Strem中的很多方法都只能调用一个,在同一个Stream上调用多个的话会引发异常,这点需要注意:
import java.util.stream.*;
import java.util.*;
public class Test
{
public static void main(String[] args)
{
/*************** 构造一个Stream ********************/
List<String> l = List.of(new String("abc"), new String("def"), new String("cdn"));
Stream is = l.stream();
is.forEach(System.out::println);
/*其它构造方法*/
IntStream is = IntStream.builder().add(1).add(2).add(3).add(4).build();
IntStream is = IntStream.of(1, 2, 3, 4);
//构造无限循环的流
Stream<Object> s = Stream.generate(...);
Stream<Object> s = Stream.iterate(...);
//构造有限的流
IntStream s = IntStream.iterate(5/*初始值*/, n->n<10/*有限条件*/, n->n+1/*值生成条件*/); //s中元素为5、6、7、8、9
/*************** map()可以映射成一个新的Stream ******************/
IntStream newIs = is.map(item->item * 2); //新的Stream中每个元素值是原Stream的两倍
newIs.forEach(System.out::println);
DoubleStream dbs = is.mapToDouble(num->num * 1.0); //新的Stream元素类型为double
Stream nis = people.stream().map(Person::getName); //people是一个Person类型的集合,新的Stream元素为Person的name
/*************** filter()可以过滤成一个新的Stream ***************/
IntStream newIs = is.filter(item->item >= 0); //过滤掉小于0的元素
newIs.forEach(System.out::println);
//求包含"java"字符串的元素的个数
Collection books = new HashSet();
books.add(new String("java编程"));
books.add(new String("java测试"));
books.add(new String("c++编程"));
books.add(new String("c++测试"));
long count = books.stream().filter(item->((String)item).contains("java")).count();
/***************** takeWhile()和dropWhile() ********************/
//takeWhile()遇到第一个不符合条件的元素就终止
Stream<Integer> s = Stream.of(1, 2, 3, 2, 1).takeWhile(x->x < 3); //1, 2
//dropWhile()会舍弃符合条件的元素,遇到不符合的元素就终止
Stream.of(1, 2, 3, 2, 1).dropWhile(x->x < 3).forEach(System.out::println); //3, 2, 1
/*************** collect()可以将Stream转换为一个集合,使用它可以将集合互转**************/
//<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
ArrayList<Integer> li = IntStream.of(1, 2, 3, 4).filter(num->num < 10).collect( //转ArrayList
()->new ArrayList<>(), /*定义怎样建立一个集合,这里为建立一个ArrayList*/
(list, item)->list.add(item), /*定义集合元素收集方式, 这里为直接添加流中元素*/
(listTotal, listSub)->listTotal.addAll(listSub) /*针对具有并行处理能力的Stream,如何合并分而治之的小任务*/);
ArrayList<Integer> li = IntStream.of(1, 2, 3, 4).filter(num->num < 10).collect(
ArrayList::new, ArrayList::add, ArrayList::addAll); //直接使用ArrayList的相关方法
HashMap<Integer, Integer> hm = list.stream().collect( //转HashMap
()->new HashMap<Integer, Integer>(),
(map, item)->map.put(item, item * 2),
(mapTotal, mapSub)->mapTotal.putAll(mapSub));
//<R,A> R collect(Collector<? super T,A,R> collector)
TreeSet<String> set = people.stream().map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new)); //转TreeSet,TreeSet中元素是people集合元素的name\
List<Integer> numbers1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> biggerThan5Numbers = numbers1.stream()
.filter(n->n > 5) //过滤掉小于等于5的元素
.collect(Collectors.toList()); //转List
Map<Integer, String> map = numbers1.stream()
.collect(Collectors.toMap(n->n, n->String.valueOf(n * 10))); //转Map,Map中元素key为原来流中元素,value为原元素*10后的String
//使用collect()进行字符串的连接: 将Person类型的集合people中每个元素的姓联合起来拼成字符串,如"wang, zhang, li"
String str = people.stream().map(Person::getFirstName).collect(Collectors.joining(", "));
/******************** 使用collect()进行分组,分组的结果放到一个map里 **************************/
//<R,A> R collect(Collector<? super T,A,R> collector)
//Collectors.groupingBy()返回一个Collector对象
//static <T,K,A,D> Collector<T,?,Map<K,D>> groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)
//static <T,K> Collector<T,?,Map<K,List<T>>> groupingBy(Function<? super T,? extends K> classifier)
//employees是Employee类型的集合,下面按职位position对其进行分组,分别获得不同职位员工的年龄之和
Map<String/*职位*/, Integer/*各员工总年龄*/> map = employees.stream().collect(
Collectors.groupingBy(Employee::getPosition, Collectors.reducing(0, Employee::getAge, Integer::sum)));
//employees是Employee类型的集合,下面按职位position对其进行分组,分别获得不同职位员工的平均年龄
Map<String/*职位*/, Double/*平均年龄*/> map2 = employees.stream().collect(
Collectors.groupingBy(Employee::getPosition, Collectors.averagingInt(Employee::getAge)));
//根据职位分组,将相同职位的雇员的名称保存到一个List中
Map<String/*职位*/, List<String>/*雇员名称*/> map = employees.stream().collect(Collectors.groupingBy(Employee::getPosition,
Collectors.mapping(Employee::getName, Collectors.toList())));
//将Employee类型的集合employees根据职位position分组,将相同职位的雇员保存到一个List中
Map<String/*职位*/, List<Employee>/*雇员*/> map = employees.stream().collect(Collectors.groupingBy(Employee::getPosition));
/*************** reduce()对流中元素迭代运算获得一个Optional类型的结果,它接受一个函数式接口BinaryOperator,该接口有两个参数,第一个参数为前次运算的结果,第二个参数为目前流中元素,通过这两个参数来迭代运算获得最终结果 ********************/
Optional<Integer> sum1 = Stream.of(1, 2, 3, 4).reduce((n1, n2)->n1 + n2);
System.out.println(sum1.get()); //输出为 10(1 + 2 + 3 + 4)
int sum2 = Stream.of(1, 2, 3, 4).reduce(10/*基础值*/, (n1, n2)->n1 + n2);
System.out.println(sum2); //输出为20 (10 + 1 + 2 + 3 + 4)
String str = Stream.of("A", "is", "a", "dog").reduce("test: "/*基础值*/, (s1, s2)->s1 + s2);
System.out.println(str); //输出为"test: Aisadog"
/************** toArray()获得对应的数组 ****************************/
Object[] oAry = Stream.of(1, 2, 3, 4).toArray(); //无参数版本的toArray()返回Object类型的数组
System.out.println(Arrays.toString(oAry)); //输出为 [1, 2, 3, 4]
Integer[] iAry = Stream.of(1, 2, 3, 4).toArray(size->new Integer[size]);//带参数的toArray()可以获得指定类型的数组,传入的lambda用来生成指定类型的数组
System.out.println(Arrays.toString(iAry)); //输出为 [1, 2, 3, 4]
Integer[] iAry = Stream.of(1, 2, 3, 4).toArray(Integer[]::new); //toArray的参数类型IntFunction接口与数组的构造函数一致,而且数组的构造函数即为生成一个指定类型的数组
System.out.println(Arrays.toString(iAry)); //输出为 [1, 2, 3, 4]
/************** 一些其它的中间方法 *********************/
IntStream st = is.peek(action); //依次对元素执行一些操作,但返回的流的元素值不变,主要用于调试
IntStream st = is.distinct(); //去除重复的元素,元素重复标准使用的是equals()
IntStream st = is.sorted(); //排序
IntStream st = is.limit(100); //指定最大元素数量
/************** 一些其它的末端方法 *********************/
is.forEach(System.out::println); //遍历操作
int max = is.max().getAsInt(); //最大值
int sum = is.sum(); //元素之和
double average = is.average().getAsDouble(); //平均值
is.anyMatch(item->item * item > 100); //是否有一个元素大于100
is.allMatch(item->item * item > 100); //所有元素的平方是否都大于100
OptionalInt op = is.findFirst(); //获得流中第一个元素
}
}
上面代码使用的Optional 类是一个可以为null的容器对象(Java中的容器指可以包装容纳其它对象的意思,比如容纳一个普通对象、一个集合对象等),使用它不用显式进行空值检测:
import java.util.Optional;
public class Test
{
public static void main(String[] args)
{
Integer value1 = null;
Optional<Integer> a = Optional.ofNullable(value1); //Optional.ofNullable()允许传递非null值和null到Optional
if(a.isPresent()) //Optional.isPresent()如果Optional中值为null则返回true,否则返回 false
{
Integer i = a.get(); //Optional.get()获得Optional中的值, 如果值为null会抛出NoSuchElementException异常
System.out.println(i);
}
else
{
System.out.println("is null");
}
Integer value2 = 10;
Optional<Integer> b = Optional.of(value2); //Optional.of()仅允许传递非null值到Optional,如果传入nulll的话会抛出异常 NullPointerException
Integer val = b.orElse(0); //Optional.orElse() 如果值不为null,返回它,否则返回指定的默认值
int iVal = b.orElseThrow().intValue(); //如果值不为null,返回它后调用intValue()方法,否则抛出NoSuchElementException异常
Optional<Integer> op = Optional.empty(); // Optional.empty()返回一个值为null的Optional
String ss = new String("abc");
Optional<String> os = Optional.ofNullable(ss);
os.ifPresent(str->System.out.println(str)); //如果os不为null的话会调用传入的lambda表达式
os.ifPresentOrElse(str->System.out.println(str), ()->System.out.println("none")); //如果os不为null的话调用第一个lambda,否则调用第二个lambda
Optional<String> os2 = os.or(()->Optional.of("none")); //如果os为空的话os2的值为"none"
}
}
如下所示的func1,当我们需要判断对象是否为空才能进行下一步操作的时候有时会显得代码很繁琐,我们可以将getSpecilPerson()、getAddress()、getStreet()修改为返回Optional类型,然后使用Optional的flatMap()方法(返回类型为Optional),如下的func2()方法。如果我们无法修改上面方法的返回类型的话也可以使用Optional的map()方法(返回类型为Optional),如下的func3()方法:
String func1()
{
Person p = persons.getSpecilPerson();
if(p != null)
{
Address addr = p.getAddress();
if(addr != null)
{
String street = addr.getStreet();
if(street != null)
return street;
}
}
return "unknown";
}
Optional<String> func2()
{
return persons.getSpecilPerson()
.flatMap(Person::getAddress) //如果getSpecilPerson()返回不为null,执行flatMap():通过getAddress()获得新的Optional
.flatMap(Address::getStreet) //如果getAddress()返回不为null,执行flatMap():通过getStreet()获得新的Optional
.orElse("unknown"); //getSpecilPerson()或getAddress()或getStreet()返回null,执行orElst返回一个指定的Optional
}
Optional<String> func3()
{
return Optional.ofNullable(persons)
.map(Persons::getSpecilPerson) //如果persons不为null,执行map():通过getSpecilPerson()方法获得新的Optional
.map(Person::getAddress) //如果getSpecilPerson()返回不为null,执行map():通过getAddress()获得新的Optional
.map(Address::getStreet) //如果getAddress()返回不为null,执行map():通过getStreet()获得新的Optional
.orElse("unknown"); //以上有一个返回为null,指定orElse返回一个指定的Optional
}
Stream中也有一个flatMap()方法,它返回一个新的Stream(原Stream如果是空的话返回的也是一个空的Stream),新Stream的元素通过参数lambda表达式来设置,lambda表达式输入原Stream的元素,返回一个Stream,flatMap()会将返回的这个Stream中的元素添加到新的Stream中,而且如果lambda返回的Stream是空的话就不会被添加。其它的还有flatMapToInt()/ flatMapToLong()/ flatMapToDouble(),他们的返回类型是IntStream/ LongStream/ DoubleStream。如下所示flatMap()的参数为Function类型,其输入参数为原Stream的元素,输出为Stream类型的元素。Optional的stream()用来获得对象的Stream类型,如果调用flatMap()的Stream的元素类型不是一个List而是一个普通对象的Optional,那么可以在lambda中通过该方法来获得元素对应的Stream来返回。
如下的两句语句是等效的效果:
Stream<String> emailList = Optional.ofNullable(order.getCustomer()) //order对象的getCustomer()返回一个Customer类型对象,可能为null
.map(customer->customer.geEmailList().stream()) //order.getCustomer()返回不为null,为一个Customer对象的话,执行map():通过指定的lambda的返回值获得一个新的Optional
.orElst(Stream.empty()); //order.getCustomer()返回为null或customer.geEmailList().stream()为一个空的Stream
Stream<String> emailList = Stream.ofNullable(order.getCustomer()) //Stream.ofNullable()接受一个null或非null对象,来生成一个空的Stream或有值的Stream
.flatMap(customer->customer.getEmailList().Stream()); //如果Stream不为空的话
如下的getNickName1()获得用户名的昵称到一个Stream,如果昵称为空的话就不统计到Stream,也可以使用更为简洁的getNickName2()来代替getNickName1():
import java.util.stream.*;
import java.util.*;
public class Test
{
public static void main(String[] args)
{
}
public Stream<String> getNickName1(List<String> listUserName)
{
return listUserName.stream() //转为包含用户名的流,流中元素类型为String
.map(userName->getNickName(userName)) //转为包含昵称的流,流中元素类型为Optional<String>
.filter(opt->opt.isPresent()) //过滤掉昵称为空的值
.map(opt->opt.get()); //转为元素类型为String的流
}
public Stream<String> getNickName2(List<String> listUserName)
{
return listUserName.stream() //转为包含用户名的流,流中元素类型为String
.map(this::getNickName) //转为包含昵称的流,流中元素类型为Optional<String>
.flatMap(Optional::stream); //转为元素类型为String的流,不包含空值
}
public Optional<String> getNickName(String userName)
{
return Optional.ofNullable(m_mapNickName.get(userName));
}
HashMap<String, String> m_mapNickName;
}
如下的一个元素类型为List的List,想要搜集所有的根元素,可以使用Stream的flatMap()方法,也可以使用Collectors的flatMapping()方法,它返回一个Collector:
List<List<String>> list = List.of(List.of("c++", "java", "c#"),
List.of("visual studio", "eclipse"),
List.of("windows", "linux"));
List<String> content = list.stream().flatMap(subList->subList.stream()).collect(Collectors.toList());
System.out.println(content); //输出为:[c++, java, c#, visual studio, eclipse, windows, linux]
List<String> content = list.stream().collect(Collectors.flatMapping(subList->subList.stream(), Collectors.toList()));
System.out.println(content); //输出为:[c++, java, c#, visual studio, eclipse, windows, linux]
Stream的其它方法:
//原来的循环:
for(int i = 0; i < 100; ++i)
{
System.out.println(i);
}
//新的循环:
IntStream.range(0, 100).forEach(/*IntConsumer*/System.out::println);
String s = "123abc";
s.chars().forEach(System.out::println); //chars()返回一个IntStream,输出为49、50、51、97、98、99
ThreadLocalRandom rand = ThreadLocalRandom.current();
IntStream is = rand.ints(5, 0, 10); //获得5个随机数,在[0,10)之间
3、平行化处理
Stream具有并行处理的能力,如下的代码将stream()修改为parallelStream()后Stream就会在可能的情况下进行并行化处理,可能的情况是指处理过程能够分而治之然后再合并,比如下面例子的filter()方法和collect()方法都符合这种情况,所以会进行并行化处理:
List<Person> males = persons.stream()
.filter(person->person.getGender() = Person.Gender.MALE)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
还需要留意并行处理的顺序,如下代码,程序的输出并不是按照原来的顺序,如果需要按照原来的顺序来显示的话可以使用forEachOrdered()来代替forEach()。使用forEachOrdered()可能会失去并行化的优势,比如中间有filter()操作会并行化处理,最后调用forEachOrdered()的话会等待所有并行化操作完成后才能进行。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream().forEach(System.out::println);
Stream的reduce()在并行处理的时候基本上是按照来源顺序,collect()方法则视给予的Collector而定,下面的两个collect()都会按照源顺序来处理。collect()使用Collectors.groupingByConcurrent()方法返回的Collector为参数也会按照源顺序来处理:
List<Person> males = persons.parallelStream()
.filter(person->person.getGender() = Person.Gender.MALE)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
List<Person> males = persons.parallelStream()
.filter(person->person.getGender() = Person.Gender.MALE)
.collect(Collectors.toList());
List<Person> males = persons.parallelStream()
.filter(person->person.getGender() = Person.Gender.MALE)
.collect(Collectors.groupingByConcurrent(...));
在使用流处理的时候应该一次只做一件事,避免在一个流中对另一个集合进行操作,这样可以避免混乱,也有利于并行化计算,如下所示:
//不使用流操作的代码
List<Integer> numbers1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> numbers2 = new ArrayList<>();
for(Integer n : numbers1)
{
if(n > 5)
{
numbers2.add(n + 10);
System.out.println(n);
}
}
//上面代码使用流的话应该改成这样
List<Integer> numbers1 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> biggerThan5Numbers = numbers1.stream()/*parallelStream()*/
.filter(n->n > 5)
.collect(Collectors.toList());
biggerThan5Numbers.forEach(System.out::println);
List<Integer> numbers2 = biggerThan5Numbers.stream()/*parallelStream()*/
.map(n->n + 10)
.collect(Collectors.toList());
4、其它
不能将子类元素类型的集合赋值给父类元素类型的集合:
import java.util.*;
class Foo
{
}
class Bar extends Foo
{
}
public class Test
{
static void func(List<Foo> li){}
public static void main(String[] args)
{
List<Foo> li = null;
li.add(new Foo());
func(li);
List<Bar> lb = null;
lb.add(new Bar());
func(lb); //error !
}
}