为什么使用集合?
假设,一个班级有30个人,我们需要存储学员的信息,我们可以用数组就解决了。 那换一个问题,一个网站每天要存储的新闻信息,我们知道新闻是可以实时发布的,我们并不知道需要 多大的空间去存储,如果去设置一个很大的数组,要是没有存满,或者不够用,都会影响我们,前者 浪费的空间,后者影响了业务!如果并不知道程序运行时会需要多少对象,或者需要更复杂的方式存储对象,那我们就可以使用Java的 集合!
- 数组的长度是一旦确定就不能改动,数组元素类型必需一致
- 集合长度不定,集合元素类型不要求一样,只能放引用类型
集合类中只能存放对象,而不能存放原始数据类型的元素,所以当有原始数据类型需要存放时,只能将其转换成相应的包装类对象。
集合分类
Collection 存放单值
-
List 有序可以重复
ArrayList 底层是数组(一块连续的内存地址),查找和修改比较快,新增(指定索引位置插入)和删除比较 慢;
LinkedList 底层是双向链表,查找和修改相对较慢,新增和删除相对比较快;
Vector,底层也是数组,和ArrayList一致;内部方法加了线程同步,性能较低;
-
Set 无序不可重复
HashSet:底层是HashMap 在进行比较两个对象是否相等的时候,使用equals进行比较,还会计 算对象的hash值
TreeSet:底层用到了红黑树,集合元素会进行排序;要求插入的类型实现Comparable接口,或者 提供一个Comparator的实现类(向TreeSet中添加的数据,要求是相同类的对象。)
LinkedHashSet:是HashSet子类,底层是LinkedHashMap;元素的插入顺序和遍历顺序一致;
向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
list中常用方法:
- 增:add(Object obj)
- 删:remove(int index) / remove(Object obj)
- 改:set(int index, Object ele)
- 查:get(int index)
- 插:add(int index, Object ele)
- 长度:size()
- 遍历:
① Iterator迭代器方式
② 增强for循环
③ 普通的循环
public class CollectionDemo {
public static void main(String[] args) {
List arrayList = new ArrayList();
//isEmpty 判断list为空吗
System.out.println("是否为空:" + arrayList.isEmpty());
//add 追加
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
System.out.println(arrayList);
//add 指定索引位置添加元素,index不能超过集合元素个数
arrayList.add(3, "4");
//把集合元素直接打印出来
System.out.println(arrayList);
//获取list大小
int size = arrayList.size();
System.out.println(size);
//contains 是否包含指定的集合元素
System.out.println("是否包含:" + arrayList.contains("123"));
System.out.println("是否为空:" + arrayList.isEmpty());
//toArray 把list转成数组
/*Object[] objects = arrayList.toArray();
System.out.println(Arrays.toString(objects));
String[] strings = (String[]) arrayList.toArray(new String[4]);
System.out.println(Arrays.toString(strings));*/
//remove移除集合元素,指定索引删除或者指定元素删除
arrayList.remove(3);
arrayList.remove("4");
System.out.println(arrayList);
}
}
public static void main(String[] args) {
List arrayList1 = new ArrayList();
arrayList1.add("1");
arrayList1.add("2");
arrayList1.add("3");
List arrayList2 = new ArrayList();
arrayList2.add("1");
arrayList2.add("2");
//集合是否包含另一个集合(集合中每个元素都有包含在内)
boolean b = arrayList1.containsAll(arrayList2);
System.out.println(b);
//添加集合(每个集合元素都添加进去)
arrayList1.addAll(arrayList2);
System.out.println(arrayList1);
//按指定的集合进行删除
// arrayList1.removeAll(arrayList2);
System.out.println(arrayList1);
//清空集合元素
//arrayList1.clear();
System.out.println(arrayList1);
//get() 括号中代表索引 从0开始
System.out.println(arrayList1.get(2));
//制定索引修改集合元素
arrayList1.set(2, "abc");
System.out.println(arrayList1);
System.out.println("指定元素首次出现所在的索引位置:" + arrayList1.indexOf("1"));
System.out.println("指定元素最后出现所在的索引位置:" + arrayList1.lastIndexOf("1"));
//截取子集合,起始索引包含,尾部索引不包含
System.out.println(arrayList1.subList(2, arrayList1.size()));
}
set常用方法
public class HashSetDemo01 {
public static void main(String[] args) {
HashSet<String> strings = new HashSet<>();
String str1 = new String("abc");
String str2 = new String("abc");
//abc 只会插入一次
strings.add(str1);
strings.add(str2);
System.out.println(strings);
//获取集合大小
System.out.println(strings.size());
//set集合转数组
Object[] objects = strings.toArray();
System.out.println(Arrays.toString(objects));
}
}
list的4种遍历方式:
public class ArrayListTraverse {
public static void main(String[] args) {
ArrayList<String> strArr = new ArrayList<>();
strArr.add("aa");
strArr.add("bb");
strArr.add("cc");
//1.普通for循环
for (int i = 0; i < strArr.size(); i++) {
System.out.println(strArr.get(i));
}
System.out.println("==========");
//2.增强for循环
for (String s : strArr) {
System.out.println(s);
}
System.out.println("==========");
//3.使用迭代器遍历
Iterator<String> iterator = strArr.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("===========");
//4.使用forEach遍历
strArr.forEach((s) -> {
System.out.println(s);
});
}
}
set的3种遍历方式:
public class HashSetTraverse {
public static void main(String[] args) {
HashSet<String> strings = new HashSet<>();
strings.add("a");
strings.add("b");
strings.add("c");
//System.out.println(strings);
//1.增强for循环
for (String string : strings) {
System.out.println(string);
}
System.out.println("==========");
//2.使用迭代器进行遍历
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("==========");
//3.使用forEach遍历
strings.forEach(s -> {
System.out.println(s);
});
}
}
Map
存放的一对值 (key-value);其中的key值不能重复
HashMap
- HashMap结合了数组和链表的优势
- 底层是哈希表(hash表/映射表),jdk8的底层是 数组和单向链表+红黑树
- 哈希表是一种数据结构,用到了hash算法
- hash算法,将无限的数据映射到有限的数据范围内;抽屉原理 9个,20 ,2个苹果会放到一个抽屉中;如果能做到结果尽可能分散,算法较优秀,如果得到的hash值一样会发生hash冲突,
- HashMap没有做任何的线程同步,在多线程情况下不安全;
map常用方法
public class HashMapDemo01 {
public static void main(String[] args) {
Map<String, String> stringMap = new HashMap<>();
stringMap.put("1","a");
stringMap.put("2","b");
stringMap.put("1","java");
//{1=java, 2=b}
System.out.println(stringMap);
//get() 根据key值取出对应的value值
System.out.println(stringMap.get("1"));
//size() 获取键值对个数
System.out.println(stringMap.size());
//containsKey() 是否包含某个key值
System.out.println(stringMap.containsKey("2"));
//containsValue() 是否包含某个value值
System.out.println(stringMap.containsValue("b"));
//remove() 根据key进行删除
/*stringMap.remove("1");
System.out.println(stringMap);*/
//clear() 清空集合
/*stringMap.clear();
System.out.println(stringMap);*/
//map的遍历
stringMap.forEach((key,value)->
{
System.out.println(key);
System.out.println(value);
});
}
}
源码解析:
1,put方法
两个key对象不同,hashcode值不同hash值也不同,但是取模之后得到的索引值是一样的,会冲突
两个key对象不同,但是hashcode值相同,取模之后得到的索引值是一样的,会冲突
2,get方法
3,resize()方法,首次扩容,默认值是16;否则的话会进行元素移动(旧的数组索引位对应的元素会移动到高位或者地位)
key值的选择:
key的数据类型需要选择不可变的数据类型,比如String,Integer这些
//map中的key是不能重复的。String是不可变的非常适合作为key
HashMap<String, Integer> map = new HashMap<>();
String str = "a";
map.put(str,123);
str+="ab";
map.put(str,234);
System.out.println(map);//{a=123, aab=234}
//StringBuilder是可变的,不能作为key
HashMap<StringBuilder, Integer> map1 = new HashMap<>();
StringBuilder stringBuilder = new StringBuilder("a");
map1.put(stringBuilder,123);
stringBuilder.append("ab");
map1.put(stringBuilder,234);
System.out.println(map1);//{aab=234}
LinkedHashMap
底层使用双向链表结构保持集合元素的插入顺序,使访问顺序与插入顺序一致
System.out.println("--------------");
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("123", "abc");
linkedHashMap.put("456", "abc");
linkedHashMap.put("678", "abc");
linkedHashMap.put("12", "abc");
linkedHashMap.put("1", "abc");
linkedHashMap.forEach((k, v) -> {
System.out.print(k);
System.out.println(":" + v);
});
遍历结果:
TreeMap
- 对插入的key值进行自然排序,底层是红黑树;
- TreeMap的key 要么实现Comparable接口,要么是new TreeMap实例的时候传入一个Comparator实现类(匿名内部类或者lambda表达式)
TreeMap常用的方法:
TreeMap<Integer, String> trM = new TreeMap<>();
trM.put(45,"abc");
trM.put(12,"aaa");
trM.put(1,"ccc");
//获取最小的key值
System.out.println(trM.firstKey());
//获取最大的key值
System.out.println(trM.lastKey());
//根据key值取出value
System.out.println(trM.get(12));
System.out.println(trM.firstEntry());//1=ccc
Bike实现Comparable接口:
public class Bike implements Comparable{
private String brand;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int compareTo(Object o) {
return 0;
}
}
//Bike实现Comparable接口
/*TreeMap<Bike, String> treeMap = new TreeMap<>();
treeMap.put(new Bike(),"aa");*/
//lambda 表达式
/*TreeMap<Bike, String> treeMap = new TreeMap<>((bike1,bike2)->{
return bike1.getBrand().compareTo(bike2.getBrand());
});*/
//匿名内部类
/*TreeMap<Bike, String> treeMap = new TreeMap<>(new Comparator<Bike>() {
@Override
public int compare(Bike o1, Bike o2) {
return o1.getBrand().compareTo(o2.getBrand());
}
});*/
HashTable
- 是线程安全的map,内部做了方法同步,效率比较低;锁的是整个hash表
- ConcurrentHashMap是高并发情况下使用的一个map,上锁是分段锁(jdk7),性能比HashTable优秀
map的几种遍历
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "aa");
hashMap.put(12, "aa");
hashMap.put(23, "aa");
hashMap.put(34, "aa");
//1.for循环遍历
Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("============");
//2.使用forEach遍历
hashMap.forEach((k, v) -> {
System.out.println(k + ":" + v);
});
//3.先获取key的集合
System.out.println("=======");
Set<Integer> integers = hashMap.keySet();
for (Integer integer : integers) {
System.out.println(integer + ":" + hashMap.get(integer));
}
//获取map中value的集合
System.out.println("============");
Collection<String> values = hashMap.values();
System.out.println(values);//[aa, aa, aa, aa]
集合遍历需要注意的地方
遍历过程中不要直接使用集合删除元素或者改动集合modCount的操作,如果要改动必须使用迭代器(iterator)进行操作。
通过Iterator对象遍历集合的模式称为迭代器。
- 使用迭代器的好处在于,调用方总是以统一的方式遍历各种集合类型,而不必关系它们内部的存储结构。
list集合演示:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
//list遍历的时候删除元素,必须使用iterator
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (next == 1) {
iterator.remove();
}
}
System.out.println(arrayList);//[2, 3]
map集合演示:
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "a");
hashMap.put(2, "a");
hashMap.put(3, "a");
//map遍历的时候删除元素
Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
Iterator<Map.Entry<Integer, String>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> next = iterator.next();
if (next.getKey() == 1) {
iterator.remove();
}
}
System.out.println(hashMap);//{2=a, 3=a}
Collections类
Collections是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合。
常用方法:
- addAll() 可以给一个Collection类型的集合添加若干元素。因为方法签名是Collection,所以我们可以传入List,Set等各种集合类型。
- sort()
- max/min
- indexOfSubList/lastIndexOfSubList
- fill
ArrayList<String> strings = new ArrayList<>();
strings.add("a");
//Collections.addAll 将所有指定的元素添加到指定集合。 可变长参数是可以传0个或者多个
Collections.addAll(strings, "1", "a","hello", "Abc");
System.out.println(strings);
//根据其元素的自然顺序对指定来列表进行排序
Collections.sort(strings);
System.out.println("排序后:" + strings);
System.out.println("最大值:" + Collections.max(strings));
System.out.println("最小值:" + Collections.min(strings));
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
//返回指定源列表中指定目标列表的第一次出现的起始位置,如果没有此列表返回-1
int i = Collections.indexOfSubList(strings, arrayList);
System.out.println("子list第一次出现的起始索引:" + i);
System.out.println("子list最后出现的起始索引:" + Collections.lastIndexOfSubList(strings, arrayList));
List<Object> objects = Collections.emptyList();
//fill() 使用指定元素代替指定列表中的所有元素
Collections.fill(strings,"world");
System.out.println(strings);
//自定义集合中的泛型要实现Comparable接口,才能进行排序
ArrayList<Bike> bikes = new ArrayList<>();
Collections.sort(bikes);
Stream流操作(Java8)
Steam 是Java8 提出的一个新概念,不是输入输出的 Stream 流,而是一种用函数式编程方式在集合类上进行复杂操作的工具。简而言之,是以内部迭代的方式处理集合数据的操作,内部迭代可以将更多的控制权交给集合类。Stream 和 Iterator 的功能类似,只是 Iterator 是以外部迭代的形式处理集合数据的操作。
- 在Java8以前,对集合的操作需要写出处理的过程,如在集合中筛选出满足条件的数据,需要一 一遍历集合中的每个元素,再把每个元素逐一判断是否满足条件,最后将满足条件的元素保存返回。而Stream 对集合筛选的操作提供了一种更为便捷的操作,只需将实现函数接口的筛选条件作为参数传递进来,Stream会自行操作并将合适的元素同样以stream 的方式返回,最后进行接收即可。
Stream流操作分两种
- 中间操作(intermediate operation):方法调用后返回值类型是Stream,其实就是返回的流,一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性化的
- 终止操作(terminal operation):只能出现一个而且必需在最后出现,最终会从Stream中得到值。一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
常用的一些操作
中间操作:
- map():对流中的元素进行相同的操作后得到新的流元素
- sorted():对流中的元素进行排序
- filter():根据指定的规则对流中的元素进行过滤
- limit (long maxSize):按指定长度截断流
终止操作:
- count() :得到流中的元素个数
- collect() :收集流元素,可以转换为一个list
- max():得到最大值
- min():得到最小值
- reduce()流元素进行合并(+,-,*,/)
代码演示:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(10);
arrayList.add(9);
arrayList.add(32);
arrayList.add(18);
arrayList.add(6);
System.out.println(arrayList);
- map()/sorted()/collect() :元素乘2之后再进行排序遍历
Stream<Integer> stream = arrayList.stream();
List<Integer> collect = stream.map(a -> {
return a * 2;
}).sorted().collect(Collectors.toList());
System.out.println(collect);
结果:
- count():得到流元素个数
Stream<Integer> stream = arrayList.stream();
long count = stream.map(a -> {
return a * 2;
}).count();
System.out.println(count);
结果:
- filter():根据指定的规则对流中的元素进行过
Stream<Integer> stream = arrayList.stream();
long count = stream.map(a -> {
return a * 2;
}).filter(e -> {
if (e > 15) {
//返回为true,表示留下的,否则就是过滤掉的
return true;
}
return false;
}).count();
System.out.println(count);
结果:
- filter() / collect() :a*2大于15进行遍历
Stream<Integer> stream = arrayList.stream();
List<Integer> collect = stream.map(a -> {
return a * 2;
}).filter(e -> {
if (e > 15) {
return true;
}
return false;
}).collect(Collectors.toList());
System.out.println(collect);
结果:
- limit()
Stream<Integer> stream = arrayList.stream();
List<Integer> collect = stream.map(a -> {
return a * 1;
}).filter(e -> {
if (e > 1) {
return true;
}
return false;
}).limit(3).collect(Collectors.toList());
System.out.println(collect);
结果:
- max()
Stream<Integer> stream = arrayList.stream();
Optional<Integer> max = stream.map(a -> {
return a * 1;
}).filter(e -> {
if (e > 1) {
return true;
}
return false;
}).limit(3).max((o1, o2) -> {
return o1 - o2;
});
System.out.println(max);
System.out.println(max.get());
结果:
- min()
Stream<Integer> stream = arrayList.stream();
Optional<Integer> min = stream.map(a -> {
return a * 1;
}).filter(e -> {
if (e > 1) {
return true;
}
return false;
}).limit(3).min((o1, o2) -> {
return o1 - o2;
});
System.out.println(min);
System.out.println(min.get());
结果:
- reduce() : 流元素进行合并
Stream<Integer> stream = arrayList.stream();
Optional<Integer> op = stream.map(a -> {
return a * 1;
}).filter(e -> {
if (e > 1) {
return true;
}
return false;
}).limit(3).reduce((a, b) -> {
return a + b;
});
System.out.println(op.get());
结果:
- toArray()
Stream<Integer> stream = arrayList.stream();
Object[] objects = stream.map(a -> {
return a * 2;
}).filter(e -> {
if (e > 20) {
return true;
}
return false;
}).limit(3).toArray();
System.out.println(Arrays.toString(objects));
结果:
几种生成流的方式:
String[] strings = new String[10];
Stream<String> stream1 = Arrays.stream(strings);
Stream<Integer> strings1 = Stream.of(1, 2, 3, 4);
Stream<Integer> stream = arrayList.stream();
Java可变长参数理解
Java从JDK1.5以后,允许定义形参长度可变的参数从而允许为方法指定数量不确定的形参。如果在定义方法时在最后一个形参类型后增加3个点即(…);则表明该形参可以接受多个参数值,多个参数值会被当做数组传入。
使用过程中要注意的几点:
-
调用时,如果同时能匹配固定参数和可变长参数的方法,会优先匹配固定参数方法。
-
如果能同时和2个包含可变参数的方法想匹配,则编译会报错,因为编译器不知道该调用哪个方法。
-
一个方法只能有一个可变参数,且可变参数应为最后一个参数。