目录
一、概念
数组就是容器,同一类型,创建时指定容量,长度不变,在内存空间连续存储
不足:长度固定,不能改变
需求:程序在运行时,数据数量随时会发生变化,需要的存储结构也会有特殊要求(增删多,用链表结构,查询多,用数组结构)
二、集合体系
三、collection接口
collection(List:可重复(ArrayList、LinkedList)、Set:不可重复(HashSet、TreeSet))
collection(定义单列集合中共有的方法),Java中集合类默认使用泛型,如果没有定义集合中存储的数据类型,默认数据类型为object,建议使用泛型语法为集合指明数据类型,类型统一就不会出现转换问题
add(); 添加指定元素
clear(); 删除元素(清空)
isEmpty(); 是否为空
remove(); 删除指定元素,成功返回true,否则返回false
size(); 数组的长度
public class CollectionDemo1 {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("a");
c.add("b");
System.out.println(c);//[a, b]
/* c.clear();
System.out.println(c);//[]*/
System.out.println(c.contains("a"));//true
System.out.println(c.isEmpty());//false
System.out.println(c.remove("b"));//true
System.out.println(c);//[a]
System.out.println(c.size());//1
}
}
addAll(); 添加指定集合
containsAll(); 是否包含指定集合
removeAll(); 删除指定集合
retainAll(); 两个集合共有的元素,内容改变为true,内容未改变为false
public class CollectionDemo1 {
public static void main(String[] args) {
Collection<String> c1 = new ArrayList<String>();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("d");
Collection<String> c2 = new ArrayList<String>();
c2.add("d");
c2.add("e");
c2.add("f");
c1.addAll(c2);
/*System.out.println(c1);//[a, b, c, d, d, e, f]
System.out.println(c1.containsAll(c2));//true*/
// System.out.println(c1.removeAll(c2));//true
System.out.println(c1.retainAll(c2));//true
}
}
四、list接口及实现类
list:可以存储数组
ArrayList 数组列表,查询快,中间添加,删除,效率低
LinkedList 链表利润表 ,查询慢,从头或者结尾节点开始查询,中间删除,添加效率高
Vector 数组列表,线程安全
1、Arraylist
add(E e) 向末尾添加元素
添加前先判断元素添加进去后,数组是否能放的下,如果可以,直接添加进去,如果不可以,会新创建一个数组(数组扩容)
public class ArrayListDemo1 {
public static void main(String[] args) {
/*
ArrayList<String> alist = new ArrayList<String>(); 创建对象时不会创建数组,第一次添加时默认创建容量为10的数组
ArrayList<String> alist = new ArrayList<String>(20); 创建对象时,创建一个指定容量的数组
transient Object[] elementData; 底层存储数据的数组
//检测容量
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
//添加后的长度-数组长度>0,将进行数组扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity); //数组扩容
//数组扩容的方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);扩容为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) 最大上限为int最大值-8 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
*/
ArrayList<String> alist = new ArrayList<String>();
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("d");
alist.add("e");
alist.add("f");
alist.add(3,"P");
System.out.println(alist);//[a, b, c, P, d, e, f]
}
}
2、LinkedList
存储重复元素,按照顺序添加
add(E e) 向末尾添加元素
LinkedList中的方法:
public class LinkedListDemo1 {
public static void main(String[] args) {
/*
* public boolean add(E e) {
linkLast(e);
return true;
}
* private static class Node<E> { Node:节点
E item; 数据
Node<E> next; 后
Node<E> prev; 前
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
* public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
* Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { 索引小于数组的一半,从头节点向后找
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { 从尾节点向前找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
}
* */
LinkedList<String> llist = new LinkedList<>();
llist.add("a");
llist.add("b");
llist.add("c");
llist.add("d");
llist.add("a");
System.out.println(llist);
System.out.println(llist.get(3));
}
}
(3)Vector
底层也是数组实现,是线程安全的
public class VectorDome1 {
public static void main(String[] args) {
/*
* protected Object[] elementData;
* public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
*/
Vector<String> vector = new Vector<>();
vector.add("a");
vector.add("b");
vector.add("c");
vector.add("d");
vector.add("a");
System.out.println(vector);
}
}
五、list集合的迭代器
1、for循环
支持在遍历的过程中删除集合中的元素,应注意索引的变化
public class ForDemo {
public static void main(String[] args) {
ArrayList<String> alist = new ArrayList<>();
alist.add("a");
alist.add("a");
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("a");
for (int i = 0; i < alist.size(); i++) {
if (alist.get(i).equals("a")){
alist.remove(i);
}
}
System.out.println(alist);//[a, b, c]
}
}
2、增强for循环
不支持在遍历的过程中删除集合中的元素,如果删除,会抛出java.util.ConcurrentModificationException(并发修改)异常
public class ZengqiangDemo {
public static void main(String[] args) {
ArrayList<String> alist = new ArrayList<>();
alist.add("a");
alist.add("a");
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("a");
for (String item:alist){
if (item.equals("a")){
alist.remove(item);//删除时会报错,如果使用break结束循环,可以继续执行
break;
}
}
System.out.println(alist);//[a, a, b, c, a]
}
}
3、迭代器
调用iterator();返回一个迭代器对象
public class DiedaiqiDemo {
public static void main(String[] args) {
ArrayList<String> alist = new ArrayList<>();
alist.add("a");
alist.add("a");
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("a");
Iterator<String> it = alist.iterator();
while (it.hasNext()){
String item = it.next();
if (item.equals("a")){
it.remove();//删除当前指针指向的那个元素
}
}
System.out.println(alist);//[b, c]
}
}
ListIterator(集合长度),逆序输出集合中的元素,ListIterator只能遍历list接口下的集合
public class DiedaiqiDemo {
public static void main(String[] args) {
ArrayList<String> alist = new ArrayList<>();
alist.add("a");
alist.add("a");
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("a");
Iterator<String> it = alist.iterator();
ListIterator<String> it = alist.listIterator(alist.size());
while (it.hasPrevious()){
String item = it.previous();
System.out.println(item);
}
}
}
六、set接口
不能存储重复元素,元素无序(指的是不按添加顺序排列,List集合是按照添加顺序排放),元素没有索引
set集合只能使用增强for循环和迭代器遍历(迭代器中只能使用iterator)
1、HashSet
HashSet添加元素时,如何判断元素是否存在(也就是判断HashMap的键是否重复)
add(E)在添加元素时,在里面要判断元素是否存在,需要解决两个问题:1、效率高,2、安全
add添加时,先用内容调用hashcode()方法计算出一个hash值(int类型),用哈希值比较是否相同,效率高,但是,只用hash值比较是不安全的,这时会调用equals方法对每个字符进行比较
public class Person {
private int id;
private String Name;
public Person(int id, String name) {
this.id = id;
Name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", Name='" + Name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return getId() == person.getId() &&
getName().equals(person.getName());
}
@Override
public int hashCode() {
return Objects.hash(getId(), getName());
}
}
public class TestSetDemo {
public static void main(String[] args) {
HashSet<Person> hashSet = new HashSet<>();
/*new person()在内存中会创建一个对象,有一个内存地址,public native int hashCode();*/
Person p1 = new Person(1,"张三1");
Person p2 = new Person(2,"张三2");
Person p3 = new Person(3,"张三3");
Person p4 = new Person(1,"张三1");
Person p5 = new Person(4,"张三4");
hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
hashSet.add(p4);
hashSet.add(p5);
/* System.out.println(hashSet);
这块儿的结果是:[Person{id=3, Name='张三3'}, Person{id=4, Name='张三4'}, Person{id=2, Name='张三2'},
Person{id=1, Name='张三1'}, Person{id=1, Name='张三1'}]
此时Person类中没有重写hasnCode()和equals(),默认调用了object中的这两个方法,取出的是内存地址*/
System.out.println(hashSet);
/*这块儿结果:[Person{id=1, Name='张三1'}, Person{id=2, Name='张三2'},
Person{id=3, Name='张三3'}, Person{id=4, Name='张三4'}]
因为此时重写了自己的hasnCode()和equals(),调用的是自己的这两个方法
*/
}
}
一般的类中都重写object类中的hashcode(),重写后的计算公式都是根据对象中的包含内容的值来计算的,重写后添加时,调用自己的hashcode()
2、TreeSet
元素不重复,可以根据元素进行排列
添加时要对元素进行排序和去重,此时要实现Comparable接口
public class Car implements Comparable<Car>{
private int num;
private String name;
public Car(int num, String name) {
this.num = num;
this.name = name; }
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Car{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Car o) {
return this.num-o.num;
// return this.name.compareTo(o.name);
}
}
public class TestTreeSetDemo {
public static void main(String[] args) {
TreeSet<Car> treeSet = new TreeSet<>();
Car car1 = new Car(1,"宝马1");
Car car2 = new Car(2,"宝马2");
Car car3 = new Car(3,"宝马3");
Car car4 = new Car(4,"宝马4");
Car car5 = new Car(1,"宝马5");
treeSet.add(car1);
treeSet.add(car2);
treeSet.add(car3);
treeSet.add(car4);
treeSet.add(car5);
/*System.out.println(treeSet);
没有实现Comparable之前
会报错,ava.lang.ClassCastException(转型异常):day3.treesetdemo.Car cannot be cast to java.lang.Comparable
所以要实现Comparable接口
*/
System.out.println(treeSet);
/*
按num进行排序
public int compareTo(Car o) {
return this.num-o.num;
}
[Car{num=1, name='宝马1'}, Car{num=2, name='宝马2'}, Car{num=3, name='宝马3'}, Car{num=4, name='宝马4'}]
*/
/*
按name进行排序
public int compareTo(Car o) {
return this.name.compareTo(o.name);
}
[Car{num=1, name='宝马1'}, Car{num=2, name='宝马2'}, Car{num=3, name='宝马3'},
Car{num=4, name='宝马4'}, Car{num=1, name='宝马5'}]
*/
}
}
七、Map接口
双列存储,键---值,键不允许重复,值可以重复
所实现的类:HashMap TreeMap Hashtable
1、HashMap
public class MapDemo1 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("a","aaa"); //向map中添加元素
map.put("b","bbb");
map.put("c","ccc");
map.put("d","ddd");
System.out.println(map);//System.out.println(map);
/*containsKey() 是否包含指定的键*/
System.out.println(map.containsKey("b"));//true
/*containsValue 是否包含指定的值*/
System.out.println(map.containsValue("ccc"));//true
/*get() 根据指定的键找到对应的值*/
System.out.println(map.get("b"));///bbb
/*remove() 删除指定的键并返回对应的值*/
System.out.println(map.remove("d"));//ddd
/*size() 有几组键值对*/
System.out.println(map.size());//3
/*获取map中键的那一列,存放到一个set中*/
Set<String> set = map.keySet();
System.out.println(set);//[a, b, c]
/*获取map中值的那一列,存放到一个collection中*/
Collection<String> val = map.values();
System.out.println(val);//[aaa, bbb, ccc]
}
}
put(K,V)向HashMap中添加数据过程及底层结构:
putVal(hash(key),key,value,false,true)
哈希函数根据内容的哈希值计算在哈希表中的位置:(h = key.hashCode()) ^ (h >>> 16)
第一次添加元素时,会创建哈希表,将元素插入到对应的位置,后面如果有位置相同的元素就放在链表中
当链长度大于等于7时,就会将链表转为红黑树
哈希表默认长度是:DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
最大容量是:MAXIMUM_CAPACITY = 1 << 30
负载因子:DEFAULT_LOAD_FACTOR = 0.75f
当哈希容量到达整个数组的的0.75倍是,进行扩容,扩容为原来的2倍
2、Hashtable
不允许有为null的key,是线程安全的,添加了同步锁,锁住了整个put(),访问量较小时可以使用,访问很高时,效率太低,后面会有新的线程安全来代替
3、TreeMap
可以根据键的自然顺序排序(a,b,c.............;1,2,3,.........),key值所在的类必须实现comparable接口
4、Map中的遍历方式
a:遍历方式1
public class MapDemo3 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("c","ccc");
map.put("a","aaa");
map.put("b","bbb");
map.put("d","ddd");
Set<String> keyset = map.keySet();
for (String key:keyset){
System.out.println(map.get(key));
}
}
}
b:遍历方式2
.entrySet():把Map中的键值对封装在一个Entry对象中,将多个Entry对象装进Set集合中,Entry对象包含键值
public class MapDemo3 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("c","ccc");
map.put("a","aaa");
map.put("b","bbb");
map.put("d","ddd");
Set<Map.Entry<String,String>> entrySet = map.entrySet();
for (Map.Entry entry:entrySet){
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
八、Collections类
Collections是集合类的工具类
其中常用的方法:
addAl l(Col lection<? super T> c, T... elements);
binarySearch(List<? extends Comparable<? super T>> l ist, T key)
sort(List<T> l ist)
sort(List<T> l ist, Comparator<? super T> c)
swap(List<?> l ist, int i, int j)
copy(List<? super T> dest, List<? extends T> src) ; 注意 dest size需大于等于src.size
emptyList() 返回为空的集合,不能添加数据
fi l l(List<? super T> l ist, T obj)
max(Col lection<? extends T> col l)
min(Col lection<? extends T> col l)
replaceAl l(List<T> l ist, T oldVal, T newVal)
reverse(List<?> l ist)
public class CollectionsDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list,2,1,3,4,5);
System.out.println(list);//[2, 1, 3, 4, 5]
Collections.sort(list);
System.out.println(list);//[1, 2, 3, 4, 5]
System.out.println(Collections.binarySearch(list,4));//3
Collections.swap(list,1,3);
System.out.println(list);//[1, 4, 3, 2, 5]
/*emptyList(),返回一个空集合,此集合不能使用,避免在判断时出现空指针
* List<Integer> list1 = Collections.emptyList();
list1.add(1);
System.out.println(list1);
* */
// Collections.fill(list,8);
// System.out.println(list);//[8, 8, 8, 8, 8]
System.out.println(Collections.max(list));//5
System.out.println(Collections.min(list));//
Collections.replaceAll(list,2,6);
System.out.println(list);//[1, 4, 3, 6, 5]
Collections.reverse(list);
System.out.println(list);//[5, 6, 3, 4, 1]
List<Integer> list1 = new ArrayList<>();
list1.add(9);
list1.add(8);
list1.add(7);
Collections.copy(list,list1);
System.out.println(list);//[9, 8, 7, 4, 1]
}
}
T... elements,定义可变长度的参数,本质是数组,一个参数列表中只能有一个,并且放在参数列表的最后一位
九、泛型
把类型作为参数传进去,参数化类型或者类型参数化
不明确类型默认为object类型,向上转为object类型,在需要时向下转型,会出现转型异常,java中希望集合存储同一类型数据,使用泛型语法,在创建时,把类型当做参数传进去,这样集合中的类型就明确
子类和父类都是泛型类,创建子类对象时传入类型,父类类型与子类类型一致,如果子类不是泛型类,那么明确父类类型,因为子类如果不是泛型类,创建子类对象时不能传入类型,父类类型也就无法明确
类型通配符 ?
<? extends T> 泛型的类型上限,只能传入类型以及类型的子类
<? super T> 泛型的类型下限,只能传入类型以及类型的父类
public class DemoB<T> {
/*
?类型通配符(任意的), 用来定义表示实际参数的类型
*/
/* public void test(DemoB<?> db){
}
public static void main(String[] args) {
DemoB<Integer> di = new DemoB<>();
DemoB<Number> dn = new DemoB<>();
di.test(di);
dn.test(dn);
}*/
// <? extends 类型> 泛型类型上限 只能传入 类型以及类型的子类
/*public void test(DemoB<? extends Number> db){
}
public static void main(String[] args) {
DemoB<Integer> di = new DemoB<>();
DemoB<Number> dn = new DemoB<>();
di.test(di);
dn.test(dn);
}*/
//<? super Number> 类型下限 只能传入Number类型 以及 Number的父类
public void test(DemoB<? super Number> db){
}
public static void main(String[] args) {
DemoB<Object> di = new DemoB<>();
DemoB<Number> dn = new DemoB<>();
di.test(di);
dn.test(dn);
}
}
类型擦除:泛型是JDK1.5之后的语法,以前的版本还不支持,所以底层还是使用Object作为类型,泛型的主要作用是在编译期间对类型进行明确的
public class DemoC {
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
ArrayList<String> list = new ArrayList<>();
Class c = list.getClass();
Field f = c.getDeclaredField("elementData");
System.out.println(f.getName()+"::"+f.getType());
}
十、Lambda表达式
Lambda 表达式是一个匿名函数,我们可以把 lambda 表达式理解为一段可以传递的代码(将代码段像数据一样传递)。使用它可以写出更简洁, 更灵活的代码。作为一种更紧凑的代码风格,使 java 语言的表达式能力得到的提升。
Lambda 表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此可以使用更少的代码来实现同样的功能。
创建conparator接口的匿名内部类对象,在匿名内部类对象重写compare方法,进行比较,匿名内部类也是一种语法的简化,Lambda 表达式又对匿名内部类语法进行简化,直接将接口中的方法实现作为参数传递
Lambda 表达式又称为函数式编程
public class Demo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("b");
list.add("a");
list.add("c");
list.add("d");
/*list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return 0;
}
});*/
/*结构: (参数列表)->{方法体}*/
list.sort( (o1,o2)->{ return o1.compareTo(o2); } );
//(o1,o2)->{ return o1.compareTo(o2); } 匿名函数
}
}
无参数,无返回值,lambda 体中只有一行代码时,{}可以忽略 () -> System.out.println("Hello World"); 无参数,有返回值 () -> { return 3.1415 }; 有参数,无返回值 (String s) -> { System.out.println(s); } 有一个参数,无返回值 s -> { System.out.println(s); } 有多个参数,有返回值 (int a, int b) -> { return a + b; } 有多个参数,表达式参数类型可以不写,jvm 可以根据上下文进行类型推断 (a, b) -> { return a - b; }
在Lambda 表达式中 ,只能有一个抽象方法,如果有两个,编译会报错,使用@FunctionalInterface修饰的接口就是功能函数接口
@FunctionalInterface
public interface LambdaDemo {
//void test();
//void test1(int a,int b);
int test2(int a,int b);
}
public class TestLambda {
public static void main(String[] args) {
//LambdaDemo lam = ()->System.out.println("hello");
/* LambdaDemo lam = (int a,int b)->{ System.out.println(a+b); };
lam.test1(10,5);*/
LambdaDemo lam = (a,b)->{
System.out.println("");
return a+b;
};
int res = lam.test2(10,6);
System.out.println(res);
/* new Thread(new Runnable(){
@Override
public void run() {
}
});*/
/*new Thread(()->{
System.out.println("qqqqq");
});*/
/* JButton jb = new JButton("");
jb.addActionListener((e)->{
});*/
ArrayList<String> list = new ArrayList<>();
list.add("b");
list.add("a");
list.add("c");
list.add("d");
/*list.forEach(new Consumer<String>(){
@Override
public void accept(String o) {
System.out.println(o);
}
});*/
list.forEach( (e)->{ System.out.println(e);} );
}
}
十一、Stream
集合和数组中的操作有些简单,java8提供了Stream来丰富结合和数组操作,分为两步
(1)获取流
集合.stream() Arrays.stream(a); Stream.of(1,2,3,4,5,6,7); 字符流中reader.lines(); 可以将读入到程序中字符存入到Stream中
(2)流操作
中间操作 返回的还是Stream 过滤,排序,去重.... 终端操作 获取到你最终想要的数据 toList, toSet, max min
public static void main(String[] args) {
/* int [] a = {1,2,3,6,4,5};
Arrays.stream(a);*/
Stream.of(1,2,3,4,5,6,7);
ArrayList<Integer> alist = new ArrayList<>();
alist.add(2);
alist.add(1);
alist.add(5);
alist.add(3);
alist.add(6);
alist.add(4);
alist.add(6);
alist.add(7);
/*alist.stream()
.filter( (e)->{return e>=5;} )
.forEach((s)->{ System.out.println(s); });*/
/* alist.stream()
.distinct()
.forEach((s)->{ System.out.println(s); });
*/
alist.stream()
.distinct()
.sorted((a,b)->{return a-b;})
.skip(6)
.limit(2)
.forEach((s)->{ System.out.println(s); });
//1,2,3,4,5,6,7
}
Stream与IO流是完全不同的, Stream提供了对集合元素更加丰富的操作(中间操作,终端操作).
public class StreamDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("b");
list.add("a");
list.add("c");
list.add("d");
/*
要找出集合中c字符,遍历集合
for(String s : list){
if(s.equals("c")){
}
}
*/
// 把集合或数组中的元素添加到Stream对象中去.
Stream<String> stream = list.stream();
List<String> list1 = stream.filter( (e)->{ return e.equals("c"); }) //中间操作
.collect(Collectors.toList()); //终端操作
System.out.println(list1);//[c]
System.out.println(list);//[b, a, c, d] 流操作不影响原始集合数据
}
}