Collections
集合,是将许多元素组合成一个单一单元的容器对象
集合,可用于存储/检索/操作/传输/聚合数据
集合框架,是用于表示和操作集合的体系结构,集合框架包含
接口(Interfaces)
实现(Implementations)
算法(Algorithms)
即,Java提供了一套包含,多种集合类型,多种数据结构实现,以及操作处理算法的集合框架,供开发人员直接使用
Iterable接口
实现此接口的类支持ForEach循环语句.Iterable接口不属于Java集合框架
Collection(接口)
表示一组被称为元素的对象,Collection接口继承自Iterable接口。即,所有集合类型均支持foreach循环语句
<E> ,泛型。集合并不关心元素的具体类型,因此设计使用泛型
List(接口)
java.util.List<E>集合。有序的,允许包含重复元素的集合。除从Collection继承的方法外,提供基于位置索引的操作方法
void add(int index, E element),将指定位置元素后移,
添加 E set(int index, E element),替换 E get(int index),
获取 E remove(int index),移除
List集合接口基本实现类,即不同的数据结构
java.util.ArrayList<E>类,基于对象数组数据结构的实现
java.util.LinkedList<E>类,基于双向链表数据结构的实现
List集合的声明与创建
声明List集合类型变量 <>括号中声明集合中元素的类型必须为引用类型 基于对象数组存储结构ArrayList实现类 创建集合对象
Subtyping(子类型)
赋值支持子类型关系而非子类关系。即,等号右侧必须是左侧的子类型
继承关系的类型是子类型的一种。A继承自B,A是B的子类也是子类型。按JVM规范,元素间拥有继承关系的数组也是子类型。即,A[]虽然不是B[]的子类,但确是子类型 (基本数据类型int是long的子类型,因此可以实现赋值)
public class User {
public String name;
public User(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
import java.util.ArrayList;
import java.util.List;
public class Main {
private static final List<User> USERS = create();
private static List<User> create() {
User user = new User("BO");
User user2 = new User ("SUN");
User user3 = new User("SUN");
List<User> users = new ArrayList<>();
users.add(user2);
users.add(user3);
return users;
}
public static void main(String[] args) {
System.out.println(USERS.isEmpty());
System.out.println(USERS.size());
for (User u : USERS) {
System.out.println(u.getName());
}
}
}
User user = USERS.get(0);
User user2 = USERS.get(1);
System.out.println("Before: " + user.getName());
System.out.println("Before: " + user2.getName());
for(User u : USERS) {
u.setName("ZHANG");
}
System.out.println("Before: " + user.getName());
System.out.println("Before: " + user2.getName());
因此,集合为逻辑上的容器,容器中仅保存元素对象的引用地址;操作集合中的元素时,实际操作的是元素引用的对象
ArrayList
ArrayList构造函数
ArrayList(),创建空List集合。默认创建0个元素的对象数组(OpenJDK)
ArrayList(int initialCapacity),基于指定长度创建List集合。长度仅初始化集合时使用,后期添加/移除自动更改容量
ArrayList(Collection<? extends E> c),基于指定集合创建List集合
可快速基于索引访问元素对象;其底层使用Arrays.copyOf()方法实现对象数组的增删,性能损失较小
LinkedList
List集合,基于LinkedList双向链表数据结构的实现
为每个元素创建2个节点对象,保存前/后元素的地址
当需要极其频繁的在集合头部添加元素时,效率较高。但需要为每一个元素创建两个节点对象,基于索引位置的访问需要线性时间(Positional access requires linear-time),整体性能开销较大
多数情况下使用ArrayList或不可变集合(后期讨论)即可
The Map Interface
Map接口没有继承Collection接口 Collection接口与Map接口 是平行并列的
java.util.Map<K,V>
Map,用于存放键值对(key-value)
Map不是集合
通过key值 ,保存其对应的value值,通过Key值获取对其对应的value值操作。
Map中key必须是唯一的,且每个key只能对应一个value
但不同key,可以对应同一个value
添加key-value时,如果key已经存在,则后一个覆盖前一个
基本实现类 java.util.HashMap<K, V>,查询效率与内存占用最平衡,非线程安全 java.util.TreeMap <K, V>/HashTable<K, V>
常用操作方法 V put(K key, V value),
保存键值对 V get(K key),基于key获取对应的value,如果value不存在,返回null
default V getOrDefault(Object key, V defaultValue),获取对应的value,没有则使用默认值。但不会自动存入
V remove(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
putAll()/clear()
Map没有,基于index索引的操作
Map没有,继承Iterable接口,不支持foreach语句遍历*
测试用List集合 2城4人
import java.util.ArrayList;
import java.util.List;
public class User {
public static final String HAERBIN = "哈尔滨";
public static final String BEIJING = "北京";
private int id;
private String name;
private String city;
private static final List<User> Users = create();
public User(int id, String name,String city) {
this.id = id;
this.name = name;
this.city = city;
}
private static List<User> create() {
User u = new User(1,"BO",User.HAERBIN);
User u1 = new User(2,"SUN",User.BEIJING);
User u2 = new User(3,"ZHANG",User.BEIJING);
User u3 = new User(4,"LIU",User.HAERBIN);
List<User> users = new ArrayList<>();
users.add(u);users.add(u1);
users.add(u2);users.add(u3);
return users;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class User {
public static final String HAERBIN = "哈尔滨";
public static final String BEIJING = "北京";
private int id;
private String name;
private String city;
private static final List<User> Users = create();
public User(int id, String name,String city) {
this.id = id;
this.name = name;
this.city = city;
}
private static List<User> create() {
User u = new User(1,"BO",User.HAERBIN);
User u1 = new User(2,"SUN",User.BEIJING);
User u2 = new User(3,"ZHANG",User.BEIJING);
User u3 = new User(4,"LIU",User.HAERBIN);
List<User> users = new ArrayList<>();
users.add(u);users.add(u1);
users.add(u2);users.add(u3);
return users;
}
public int getId() {
return id;
}
public void setId() {
this.id = id;
}
public String getName() {
return name;
}
public void setName() {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity() {
this.city = city;
}
public static void main(String[] args) {
List<User> USERS = create();
Map<Integer,User> uMap = new HashMap<>();
for (User u : USERS) {
uMap.put(u.getId(),u);
}
System.out.println(uMap.size());
User u = uMap.get(1);
System.out.println(u.getCity());
}
}
需求:以居民ID为key,居民本身为value
需求:以城市名为key,以对应的居民为value,分组置于Map
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class User {
public static final String HAERBIN = "哈尔滨";
public static final String BEIJING = "北京";
private int id;
private String name;
private String city;
private static final List<User> Users = create();
public User(int id, String name,String city) {
this.id = id;
this.name = name;
this.city = city;
}
private static List<User> create() {
User u = new User(1,"BO",User.HAERBIN);
User u1 = new User(2,"SUN",User.BEIJING);
User u2 = new User(3,"ZHANG",User.BEIJING);
User u3 = new User(4,"LIU",User.HAERBIN);
List<User> users = new ArrayList<>();
users.add(u);users.add(u1);
users.add(u2);users.add(u3);
return users;
}
public int getId() {
return id;
}
public void setId() {
this.id = id;
}
public String getName() {
return name;
}
public void setName() {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity() {
this.city = city;
}
public static void main(String[] args) {
List<User> USERS = create();
List<User> hList = new ArrayList<>();
hList.add(USERS.get(0));
hList.add(USERS.get(3));
List<User> bList = new ArrayList<>();
bList.add(USERS.get(1));
bList.add(USERS.get(2));
Map<String,List<User>> uMap = new HashMap<>();
uMap.put(User.HAERBIN,hList);
uMap.put(User.BEIJING,bList);
System.out.println(uMap.size());
for (User u : uMap.get(User.BEIJING)) {
System.out.println(u.getName());
}
}
}
HashMap
基于hashCode()方法获计算key的hash值
创建Node数组,基于加载因子扩容,平衡内存占用与执行效率
创建Node对象,基于key的hash值+算法,计算Node对象在数组中的索引。Node对象中,封装Key/value对象,相同位置以及存在Node对象
减少数量小于等于6个,基于单向链表保存
增加数量大于等于8个,基于红黑树保存
数量改变时,转换数据结构
获取时,基于key的hash值+算法,直接在Node数组获取对应的Node对象,基于具体数据结构(单向链表/红黑树)进一步获取value对象
The Set Interface
java.util.Set<E>
Set集合,不包含重复元素(数学中集合的抽象)
Set接口,只包含继承自Collection方法,并包含重复元素的校验
基本实现类
java.util.HashSet<E>,元素无序(底层基于HashMap确定元素是否重复) java.util.LinkedHashSet<E>,元素有序
java.util.TreeSet <E>,元素有序
无论使用有序/无序实现,均无基于索引的操作方法
无序 则无基于索引的获取元素方法
遍历时元素无序输出
Iterators
java.util.Iterator<E>
Iterator接口。迭代器,允许遍历集合,并根据需求选择性地从集合中移除元素
不同集合类型,的不同数据结构的实现类,有不同的迭代器实现,但仅需面向Iterator接口完成遍历与移除
Iterator<E> iterator()方法,Collection接口方法,获取集合对象的迭代器
Immutable Collection
如果一个对象的状态在构造后不能改变,则该对象被认为是不可变的(immutable)
不可变集合是线程安全
因不可变集合的结构不变,构造时速度更快,消耗的内存空间更小
即,不可变集合的结构不可变(长度不可变),一旦创建即不可添加/移除元素,如需改变结构必须创建新的集合(类似数组)
但,不可变集合中元素可以替换,元素对象属性值可以改变
List.of()/Set.of()/Map.of() ,返回空集合对象
List.of(elements…)/Set.of(elements…)/Map.of(K, V, ….),类似直接创建指定元素的数组,直接基于集合元素创建相应集合对象
集合长度不可变 但元素对象中封装的数据可变
null 是所有引用类型的默认值
但不可变类型集合不允许包含null
Functional Programming
函数式编程,是一种构建程序结构的编程范式。是一种与面向对象程序设计,完全不同的应用程序设计思想
在函数式编程中,函数的输出应且仅应依赖于函数的本身。即,函数的执行,不应依赖于函数外部数据的状态(闭包)
函数式编程与面向对象编程是不同场景下,分析设计应用的思考方式,无优劣之分
Lambda Expressions
通过函数式接口,声明一个函数(一个包含约束函数参数/返回类型的抽象方法的接口)(后期讨论)
通过Lambda表达式,实现/描述一个函数(函数式接口的实现)
函数有自己的参数列表,函数体以及返回值。且具有
闭包。独立于类
匿名。实现时无需声明修饰符/返回类型/名称(Think More, Write Less)
传递。可以像引用类型变量一样声明,像对象一样传递
Lambda表达式语法
(arg1, arg2) -> expression
(arg1, arg2) -> {body}
参数列表,当参数为空时,需声明空括号;当只有一个参数时,可省略括号;参数类型可省略,编译器自动完成类型推导
Lambda 表达式的函数体,可包含0或多条语句
函数体,只有一条语句的表达式,可省略{}号;包含一条以上语句,必须包含在括号中(代码块);返回类型必须匹配;没有可不声明返回值
有参数无返回值的函数 基于语句,也可省略{}
有2参数有返回值的函数 多条语句,必须使用{} 必须声明返回结果
Processing Data with Streams
java.util.stream.Stream<T>接口
鉴于Java对集合操作的复杂性,Java8中引入Stream API,用于操作处理集合中的元素
集合是存储元素对象的容器
而Stream(集合流),并不是存储元素的数据结构,而是操作集合元素的管道
商品(元素)从仓库(集合)取出,经商品包装流水线(集合流)操作,达到出厂销售的结果
Stream操作的是集合中的元素,而非集合本身。因此,将创建新集合聚合Stream操作的结果,而不影响源集合结构
Stream仅会对流过的元素操作一次(流走了)(与Iterator的游标相似)
因此,必须生成一个新Stream才能继续操作
Stream上的操作会被延迟处理,即针对一个集合的多次操作会被优化后执行,从而提高执行效率
通过Collection接口中stream()方法获取当前集合的Stream对象
Terminal Operations
Terminal Operations。终止操作,终止stream操作处理,消费stream操作产生的结果
collect():聚合在stream中间操作的结果
forEach():迭代stream的每个元素
Collectors(java.util.stream.Collectors)类,用于操作聚合结果的工具类
groupingBy()/mapping()
toList()/toSet()/toMap()
单条语句 可进一步简化省略{}
Intermediate Operations
Intermediate Operations。中间操作,对集合中元素的执行的具体操作
Stream filter():基于参数选择stream中的元素,过滤
Stream map():基于stream操作映射为新的类型,映射
Stream sorted():排序stream中的元素,排序
Long count():获取stream中元素个数,计数
中间操作执行后,将结果置入一个新Stream,从而允许基于新Stream实现后续操作,形成基于Stream的操作链
import java.util.List;
import java.util.stream.*;
public class Apple {
public enum Color {
RED,GREEN
}
private int id;
private Color color;
private int weight;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Apple(int id, Color color, int weight) {
this.id = id;
this.color = color;
this.weight = weight;
}
private static final List<Apple> APPLES = create();
private static final List<Apple> create() {
Apple a = new Apple(1, Color.RED, 200);
Apple a2 = new Apple(2, Color.GREEN, 250);
Apple a3 = new Apple(3, Color.RED, 260);
Apple a4 = new Apple(4, Color.GREEN, 230);
return List.of(a,a2,a3,a4);
}
private static List<Apple> filter(Apple.Color c) {
return APPLES.stream()
.filter(a->a.getColor() == c)
.toList();
}
}
Stream<T> map()。映射Stream中元素,基于条件将元素映射为新类型元素
private static void filter(Apple.Color c) {
APPLES.stream()
.map(a -> a.getWeight())
.collect(Collectors.toList())
.forEach(i -> System.out.println(i));
}
只要2个集合中有匹配属性 即符合过滤条件
Apple a1 = new Apple(1, Color.RED, 200);
Apple a2 = new Apple(5, Color.GREEN, 240);
List<Apple> newApples = List.of(a1,a2);
List<Apple> oldApples = APPLES;
List<Apple> apples2 = newApples.stream()
.filter(a -> oldApples.stream()
.anyMatch((oa -> oa.getId() == a.getId())))
.toList();
需求:将所有苹果的颜色映射为新集合
聚合为Set集合 过滤相同颜色
groupingBy(),基于给定数据以Map<K, List<T>>分组集合
需求:基于颜色分组苹果
toMap(K, V),基于给定键值,以Map<K, V>分组集合
需求:基于ID分组苹果
等价
支持Map forEach()方法,可直接获取每次遍历元素的键与值
removeIf
Collection接口中定义。移除符合函数表达式的元素。底层依然基于Iterator迭代器实现
极简洁优雅的实现了元素的移除 使代码关注于对集合的业务操作本身 而非游标/位置/移动等非业务逻辑操作
Functional Interfaces
函数式接口,能且只能包含1个抽象方法的接口
函数式接口中,仅声明定义函数的参数/参数类型/返回类型,使用时具体实现
@FunctionalInterface(java.lang.FunctionalInterface)注解,声明该接口为函数式接口,只要是只包含一个抽象方法的函数式接口,可以省略;当接口中定义多于1个抽象方法时,无法编译 Java定义了多个函数接口,供集合Stream等使用
Stream仅操作源集合中的元素,基于操作产生新集合,不改变源集合结构。即,源集合/新集合,均持有该元素对象的引用
filter(),
过滤 map(),
映射 sorted(),
排序 collect(),
聚合
操作均返回Stream,因此可以链接形成一条单向的管道 支持多线程并发处理,而无需显式创建线程
Optional
java.util.Optional<T> 为解决空引用异常引入的,用于封装单值元素的容器
即,基于Optional提供的一系列方法,操作封装在Optional容器中的,可能引起空引用的元素对象
创建容器 ofNullable() / of()
执行操作,基于容器是否为空,执行操作,无返回值 ifPresent() / ifPresentOrElse()
中间操作,将操作结果,置于新Optional容器以执行后续操作,结果为空,也会返回相同类型的空容器 filter() / map() / or()(需手动创建容器注入)
获取操作,获取容器中对象 orElse() / orElseGet() / get()
判断方法,判断当前容器是否为空 isEmpty() / isPresent()
Optional<T> Optional.of(T value),基于必不为空对象,创建optional容器,注入为空元素将抛出NullPointerException异常
Optional<T> Optional.ofNullable(T value),基于可能为空的对象,创建optional容器
public class Soundcard {
private USB usb;
public Soundcard(USB usb) {
this.usb = usb;
}
public USB getUsb() {
return usb;
}
public void setUsb(USB usb) {
this.usb = usb;
}
}
public class USB {
public USB(String version) {
this.version = version;
}
private String version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
import java.util.Optional;
public class Computer {
private Soundcard soundcard;
public Soundcard getSoundcard() {
return soundcard;
}
public Computer(Soundcard soundcard) {
this.soundcard = soundcard;
}
public void setSoundcard(Soundcard soundcard) {
this.soundcard = soundcard;
}
public static String getVersion0(Computer com) {
String version = com.getSoundcard().getUsb().getVersion();
return version == null ? "UNKNOW" : version;
}
private static void getIFPresent(USB usb) {
Optional.ofNullable(usb)
.ifPresent(u -> {
System.out.println(u.getVersion());
});
Optional.ofNullable(usb)
.ifPresentOrElse(u -> {
System.out.println(u.getVersion());
}, () -> {
System.out.println("usb为空");
});
}
private static void filter(USB usb) {
Optional.ofNullable(usb)
.filter(u -> "3.0".equals(u.getVersion()))
.ifPresent(u -> {
System.out.println(u.getVersion());
});
Optional.ofNullable(usb)
.filter(u -> !"UNKNOW".equals(u.getVersion()))
.or(() -> Optional.of(new USB("1.1")))
.map(USB::getVersion)
.ifPresent(System.out::println);
String v2 = Optional.ofNullable(usb)
.map(USB::getVersion)
.orElse("UNKNOW");
System.out.println(v2);
}
}