Java_4_Lambda_泛型_集合
文章目录
- Lambda表达式
- Lambda语法
- 函数式接口
- 方法引用
- 泛型
- 泛型介绍
- 泛型注意事项
- 自定义泛型
- 泛型继承与通配符
- 集合的使用
- 集合体系图
- Collection接口概述
- Iterator迭代器
- List接口及其实现类
- ArrayList类使用与解析
- Vector类使用与解析
- LinkedList类使用与解析
- Set接口及其实现类
- HashSet类使用与解析
- LinkedHashSet类使用与解析
- TreeSet类
- Map接口及其实现类
- HashMap类使用与解析
- Properties类使用与解析
- TreeMap类使用与解析
- Collections工具类与接口方法
- 各接口集合选型
- 不可变集合
Lambda表达式
Lambda语法
概念:Lambda表达式是生成函数式接口的匿名实现类实例对象的语法糖,可理解为将一段可传输的代码。 Lambda表达式可以看作是一个匿名函数,也可称为闭包。Lambda表达式可以写出更简洁、更灵活的代码,而其作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
表达式成员:lambda形参列表、箭头、lambda方法体
语法:
无参,无返回值
Runnable runnable = () -> System.out.println("Runnable");
一参,无返回值
Consumer<String> c = x -> System.out.println("Consumer");
两参,多语句
Comparator<Integer> c = (x,y) -> { System.out.println("Comparator"); return Integer.compare(x,y); }//形参列表数据类型可不写,JVM可自动根据上下文推断
两参,仅有一条返回值语句
Comparator<Integer> c = (x,y) -> Integer.compare(x,y);
函数式接口
概念:仅有一个抽象方法的接口,称之为函数式接口,可通过Lambda表达式语法快速生成函数式接口的匿名内部实现类的实例(即Lambda本质是函数式接口的实例)。函数式接口可用==@FunctionalInterface==注解以此来检查是否定义为函数式接口,并在Javadoc中声明其为函数式接口。java.util.function提供丰富的函数式接口
四大核心接口:
其他接口:
方法引用
原理:当需要传递给Lambda体的操作,已经有实现的方法了,可使用方法引用。方法引用可视为Lambda深层次的表达,方法引用就是Lambda表达式,是函数式接口的实例,通过方法名字来指向一个方法,可以认为Lambda的语法糖
要求:实现接口的抽象方法的参数列表和返回值类型,与方法引用的方法参数列表与返回值类型一致
语法:
- 通过对象
对象名::实例方法名
- 通过类静态方法
类名::静态方法名
- 通过类成员方法
类名::实例方法
实例:
//1.对象::实例方法名 //Consumer中的void accept(T t) //ps对象的void println(T t) PrintStream ps = System.out; Consumer<String> con1 = ps::prinltn; con1.accept("lxl");//控制台输出lxl //2,类::静态方法名 //Comparator中的int compare(T t1,T t2) Interger中的static int compare(T t1,T t2) Comparator<Integer> com1 = Integer::compare; com1.compare(12,13);//返回-1 //3.类::实例方法名 //Comparator中的int compare(T t1,T t2) //String中的int compareTo(T t) //String的compareTo方法用t1调用,t2为参数,该情况可使用方法引用 Comparator<String> com2 = String::compareTo; com2.compare("abc","abd");//返回-1 //Function中的R apply(T t) //Employee类中的String getName() //Employee的getName方法用t调用,该情况可使用方法引用 Employee emp = new Employee("lxl"); Function<Employee,String> fn = Employee::getName; fn.apply(emp);//返回lxl //构造器引用 //Employee中的public Employee()构造器 //Supplier<T>中的T get()返回获取Employee对象 Supplier<Employee> sup = new Supplier<Employee>(){//原始写法 @Override public Employee get(){ return new Employee(); } } Supplier<Employee> sup = () -> new Employee();//Lambda表达式写法 Supplier<Employee> sup = Employee::new;//构造器引用写法 //Employee中的public Employee(String name)构造器 //Function<T,R>中的R apply(T t)返回获取Employee对象 Function<String,Employee> fn = Employee::new; //此时fn的Employee apply方法参数列表为String t,对应Employee(String name)构造器 //Employee中的public Employee(int id,String name)构造器 //BiFunction<T,U,R>中的R apply(T t,U u)返回获取Employee对象 BiFunction<Integer,String,Employee> fn = Employee::new; //此时fn的Employee apply方法参数列表为Integer t,String u //对应Employee(int id,String name)构造器 //Lambda表达式写法 Function<Integer,String[]> fn1 = length -> new String[length]; //数组引用写法 Function<Integer,String[]> fn2 = Stirng[] :: new; //即将数组视为一个特殊的类
泛型
泛型介绍
- 泛型又称参数化类型,是
JDK5.0
出现的新特性,解决数据类型安全性问题- 在类声明和实例化时只需指定好具体类型即可
- Java泛型若保证编译时没有发出警告,运行时就不会产生
- 泛型的作用是:可在类声明时通过一个标识指定类中某个属性的类型,或是某个方法的返回值类型,或是参数类型
- 泛型表示的数据类型,是在类对象定义时指定(即是编译期指定)
- 泛型仅仅只是针对编译阶段有效!在运行时期是会被擦除的(类型擦除)
泛型注意事项
1.给泛型指定数据类型,必须是引用类型,不可为基本数据类型
2.给泛型指定具体类型后,可以传入该类型或其子类类型
3.泛型的使用形式:
(1)ArrayList<Integer> list1 = new ArrayList<Integer>();
(2)List<Integer> list2 = new ArrayList<Integer>();
(3)ArrayList<Integer> list1 = new ArrayList<>(); 推荐使用简写
4.若定义类时使用了泛型,但是创建类对象时未传入类型,则泛型的数据类型默认为Object
自定义泛型
自定义泛型类
基本语法:
class 类名<T>{}
注意事项:
1.普通成员可以使用泛型(属性、方法)
2.使用泛型的数组,不能初始化
3.静态方法中不能使用类的泛型
4.泛型类的类型,在创建对象时确定(new 类名<泛型类型>)
5.若泛型无指定类型,默认为Object
-------------------------------------------------------------------------
自定义泛型接口
基本语法:
interface 接口名<T,R>{
public T get(){};
}
注意事项:
1.接口中静态成员也不能使用泛型
2.泛型接口的类型,可在继承接口或实现接口时确定
3.始终不确定泛型的类型,可直到创建对象时确定泛型的类型
-------------------------------------------------------------------------
自定义泛型方法
基本语法:
修饰符 <T,R>返回类型 方法名(T t,R r){}
注意细节:
1.泛型方法可以定义在普通类中,也可定义在泛型类中
2.当泛型方法被调用时,类型会确定(编译器根据传入参数确定泛型)
3.public void eat(E e){},不是泛型方法,只是使用了泛型
4.泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型
泛型继承与通配符
- 泛型不具有继承性
- 泛型
<?>
支持任意的泛型类型<? extends A>
支持A类及其子类,规定泛型上限<? super A
支持A类及其父类,不限于直接父类,规定泛型下限
集合的使用
集合体系图
Collection接口概述
Iterator迭代器
List接口及其实现类
List集合选型:
集合 底层结构 增删效率 查询效率 ArrayList 可变数组 低 高 LinkedList 双向链表 高 低
- 改查多选ArrayList,增删多选LinkedList
- 一般开发情况查询较多,大部分选择ArrayList
- 根据业务灵活选择
ArrayList类使用与解析
常用方法:
public boolean add(E e); // 直接在集合的末尾添加元素数据e public void add(int index,E e); // 在集合指定index索引位置添加元素数据e public E get(int index); // 获得集合总指定index索引位置的元素数据 public E remove(int index); // 删除集合中指定index索引位置的元素数据 public boolean remove(Object obj); // 删除集合中指定的元素数据obj public E set(int index,E e); // 将集合index索引位置的元素数据修改为e public int size(); // 获得集合里面元素数据的个数! public int indexOf(Object o); //返回o在集合中第一次出现的索引,使用equals比较
集合构造器源码:
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; //private static final Object[] EMPTY_ELEMENTDATA = {}; } else { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //private static final Object[] EMPTY_ELEMENTDATA = {}; }
集合扩容源码:
public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//calculateCapacity中判断是否是第一次扩容 } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //DEFAULTCAPACITY_EMPTY_ELEMENTDATA为fianl空数组 return Math.max(DEFAULT_CAPACITY, minCapacity); //DEFAULT_CAPACITY为10,判断是否是第一次扩容 } return minCapacity;//返回添加后的集合容量 } private void ensureExplicitCapacity(int minCapacity) { modCount++;//记录集合(elementData)修改次数 if (minCapacity - elementData.length > 0) //判断是否需要扩容 grow(minCapacity); } } private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容算法 if (newCapacity - minCapacity < 0) //判断扩容后容量是否满足需求 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) //判断扩容后容量是否大于数组类型最大长度 newCapacity = hugeCapacity(minCapacity); //实际上的数组扩容 elementData = Arrays.copyOf(elementData, newCapacity); }
Vector类使用与解析
概述:
- Vector底层是一个Object数组,
protected Object[] elementData
;- Vector是线程同步(线程安全),Vector类操作方法带有
synchronized
- 开发中考虑线程安全时,可使用Vector
- 无参构造器生成的Vector对象,默认容量为
10
;有参构造器生成的Vector对象容量为指定大小;之后每次扩容后的大小为原来的2倍集合扩容源码:
public synchronized boolean add(E e){ modCount++;//记录该Vector对象修改次数 ensureCapacityHelper(elementCount+1); //elementCount为数组当前使用的容量 elementData[elementCount++]=e; return true; } private void ensureCapacityHelper(int minCapacity){ if(minCapacity - elementDate.length > 0){ //minCapacity为增加后的数组容量 grow(minCapacity);//如果需要容量小于数组长度则扩容 } } public void grow(int minCapacity){ int oldCapacity = elementData.length;//记录原来数组长度 int newCapacity = oldCapacity+((capacityIncrement>0)?capacityIncrement:oldCapacity);//capacityIncrement为0, if(newCapacity-minCapacity<0){//判断新容量是否小于需要容量 newCapacity = minCapacity; } if(newCapacity - MAX_ARRAY_SIZE>0){ //判断新容量是否大于数组类型最大长度 newCapacity = hugeCapacity(minCapacity); } elementData = Arrays.copyOf(elementData,newCapacity); //使用Arrays.copyOf实现扩容 }
LinkedList类使用与解析
集合添加与删除源码:
public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { final Node<E> l = last;//保存last引用 final Node<E> newNode = new Node<>(l, e, null); //将e对象为参数生成Node类型对象 last = newNode;//将新Node对象引用给last变量 if (l == null) //如果最初last为null,新Node对象即为第一个数据 first = newNode; else //若非第一个,则将上个Node的next属性指向新Node对象 l.next = newNode; size++;//LinkedList大小+1 modCount++;//记录该LinkedList对象修改次数 }
public E remove() { return removeFirst(); } public E removeFirst() { final Node<E> f = first;//f指向first指向的第一个结点 if (f == null)//若第一个结点为null,抛出异常 throw new NoSuchElementException(); return unlinkFirst(f);//第一结点作为参数,调用unlinkFirst } private E unlinkFirst(Node<E> f) { final E element = f.item; final Node<E> next = f.next;//next指向第二结点 f.item = null; f.next = null; first = next; //first指向第一结点的next属性,即第二结点 if (next == null)//若第二结点为null,则将last也置空 last = null;//此时集合为空 else next.prev = null;//第二结点的prev属性置空 size--;//LinkedList大小-1 modCount++; return element;//返回被删除Node的item,即被删的数据 }
Set接口及其实现类
Set集合选型:
集合 是否有序 去重规则 HashSet 无序 hasCode+equals LinkedHashSet 有序(存取一致) 同上 TreeSet 默认排序/自定义排序 元素compareTo/比较器compare
HashSet类使用与解析
源码解析:
public HashSet(){ map = new HashMap<>();//底层给map属性赋值一个新HashMap对象 } public boolean add(E e) { return map.put(e, PRESENT)==null; //PRESENT为静态属性,值为Object对象,用以占HashMap中k-v中v位置 } public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //获得key的哈希值并处理,算法尽量避免出现相同的哈希值 } final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i;//定义辅助变量 if ((tab = table) == null || (n = tab.length) == 0) //table是HashMap中存储结点链表的数组 //该if语句判断table是否为空,为空则进行第一次扩容(16长度) n = (tab = resize()).length; //resize方法对table进行扩容 if ((p = tab[i = (n - 1) & hash]) == null) //1.根据key的hash值,计算该key在table表格中的索引 //2.判断索引的位置是否为空 tab[i] = newNode(hash, key, value, null); //为空则将key作为参数创建Node结点,并将Node结点存储到table中的相应索引的位置 else { Node<K,V> e; K k; if (p.hash == hash &&((k == p.key) == key || (key != null && key.equals(k)))) //判断p指向结点中数据是否与key相同(两个条件): //1.p指向结点中hash与被添加的key的hash相同,key与结点中的key属性为同一对象 //2.key不为空,并调用key的equals与p结点的key比较结果为true e = p; else if (p instanceof TreeNode) //判断 p 是否是红黑树 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//当p指向的数组位置已经是一个链表时进入 for (int binCount = 0; ; ++binCount) { //循环遍历链表 if ((e = p.next) == null) {//第一循环e指向链表第二元素 //发现p指向的下一位置为空时,key作为参数创建Node结点,并存储到该位置 p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) //添加新元素后,判断该链表长度是否大于8 treeifyBin(tab, hash); //treeifyBin判断是否满足条件,满足时先table扩容,再将链表转为红黑树 break; } if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) //发现被添加元素与链表中元素相同时,退出循环 break; p = e;//将p指向后一个Node结点,准备下次循环 } } if (e != null) { //e不为null,就说明元素没有添加成功 V oldValue = e.value;//e.value是默认的空对象PRESENT if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); HashMap中该方法为空,由HashMap子类实现并进行业务逻辑 return oldValue; } } ++modCount; //修改次数+1 if (++size > threshold) //HashMap的大小+1,并将新大小与临界值进行比较 resize(); //若增加元素后的HashMap大小比临界值大,则进行table扩容 afterNodeInsertion(evict); //HashMap中该方法为空,由HashMap子类实现并进行业务逻辑 return null; }
LinkedHashSet类使用与解析
源码解读:
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after;//用来存储上个结点,下个结点的地址 Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } public LinkedHashSet() {//无参构造器,初始化 super(16, .75f, true); } //上层调用父类的构造器 HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
TreeSet类
原理概述:
- TreeSet底层是TreeMap,以红黑树的结构对数据进行存储,查询较快
- 若使用无参构造器创建TreeSet,是默认调用元素的compareTo方法进行排序
- 使用匿名内部类形式
new Comparator(){}
作为参数创建TreeSet集合后,可以通过比较器的compare方法中定义排序规则- 不能添加null值
源码解读:
//1.TreeSet构造器将传入的Comparator对象,赋给底层TreeMap的comparator属性 public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } //2.调用add时,底层会执行 if (cpr != null) { do { parent = t;//t为树的根节点 cmp = cpr.compare(key, t.key);//用传入的比较器进行比较 if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else//若出现相同元素,退出并setValue替换值 return t.setValue(value); } while (t != null); }
Map接口及其实现类
Map集合遍历方法:
Map map = new HashMap(); //第一组:先取出所有的key,通过key得到value Set keyset = map.keySet(); //第一种方法:增强for for (Object key : keyset) { System.out.println(map.get(key)); } //第二种:迭代器 Iterator iterator1 = keyset.iterator(); while (iterator1.hasNext()) { Object key = iterator1.next(); System.out.println(map.get(key)); } //第二组:直接把所有的values取出 Collection values = map.values(); //第一种:增强for for (Object value : values) { System.out.println(value); } //第二种:迭代器 Iterator iterator2 = values.iterator(); while (iterator2.hasNext()) { Object value = iterator2.next(); System.out.println(value); } //第三组:通过EntrySet获取K-V Set entryset = map.entrySet(); //第一种:增强for for (Object entry : entryset) { //将Object类型的entry转成Map.Entry类型 //entry实际运行类型是HashMap$Node Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey()+"-"+m.getValue()); } //第二种:迭代器 Iterator iterator3 = entryset.iterator(); while (iterator3.hasNext()) { Map.Entry m = (Map.Entry)iterator3.next(); //iterator3.next()返回的是HashMap$Node类型 System.out.println(m.getKey()+"-"+m.getValue()); } //猜想:不可以使用HashMap.Node的原因是:Node静态成员内部类权限修饰符为默认,而Map.Entry为Map接口中内部接口,接口中的方法都是默认public abstract修饰且必须为public;接口中的属性都是默认public static final修饰
HashMap类使用与解析
注意事项:
- HashMap是Map接口使用频率最高的实现类,K-V对的方式存储数据
- Key可为且仅有一个null,不能复用;但Value可重复,允许null键值
- 若添加相同key,会覆盖原来的value,等同修改
- 与HashSet一样,不保证映射顺序,因其底层为hash表实现存储(数组+链表+红黑树)
- 无实现同步,非线程安全,需要线程安全的情况下可以使用ConcurrentHashMap
- 扩容机制与HashSet一致,因为其底层就是HashMap,源码可参照HashSet部分
Hashtable类使用与解析
源码分析:
//1.底层由数组Hashtable$Entry[],初始化为11 private transient Entry<?,?>[] table; public Hashtable() { this(11, 0.75f);//无参初始化table长度为11 } //2.临界值threshold = initialCapacity * 0.75 //initialCapacity为扩容时新数组长度 private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; //3.扩容:当Hashtable元素个数大于等于临界值会触发扩容,扩容大小为2n+1 if (count >= threshold) {//count为table数组中元素总数 rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; //4.将key,value封装成Hashtable$Entry类型的结点放入table中 tab[index] = new Entry<>(hash, key, value, e); count++; } protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; int newCapacity = (oldCapacity << 1) + 1;//扩容大小为2n+1 if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++;//table表被修改次数+1 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//生成新的临界值 table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
Properties类使用与解析
基本介绍:
- Properties类继承于Hashtable并实现了Map接口,也使用一种键值对形式保存数据,使用特点与Hashtable类似
- Properties还可从properites文件中,加载数据到Properties类对象并进行读取和修改
- 工作中常作为配置文件读取后存储的数据集合
使用方法:
getProperty(String key)
用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。load(InputStream inStream)
从输入流中读取属性列表(键和元素对),通过对指定的文件(比如说上面的test.properties 文件)进行装载setProperty(String key,String value)
调用Hashtable的方法 put,他通过调用基类的put方法来设置键值对。store(OutputStream out,String comments)
以与写入相同的格式,将此Properties表中的属性列表(键值对)写入到指定的文件中去。clear()
清除此Properties表所有装载的键值对。
TreeMap类使用与解析
原理概述:
- TreeMap底层以红黑树的结构对数据进行存储,查询较快
- 若使用无参构造器创建TreeSet,是默认调用元素的compareTo方法进行排序
- 使用匿名内部类形式
new Comparator(){}
作为参数创建TreeSet集合后,可以通过比较器的compare方法中定义排序规则- key不可为null
源码分析:
//1.将传入的实现Comparator接口的匿名内部类,传给TreeMap类中的comparator public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } //2.第一次使用put方法添加 if (t == null) { compare(key, key); //检测key是否为null root = new Entry<>(key, value, null); size = 1; modCount++; return null; }
Collections工具类与接口方法
各接口集合选型
判断存储的类型:(1)一组对象 (2)一组键值对
一组对象/单列:Collection接口
允许重复:List接口
增删多:LinkedList(双向链表)
改查多:ArrayList(Object类型可变数组),Vector(线程安全)
不允许重复:Set
无序:HashSet(底层HashMap)
排序:TreeSet
插入与取出顺序一致:LinkedHashSet(底层LinkedHashMap)一组键值对:Map
键无序:HashMap(哈希表(数组+链表+红黑树))
键排序:TreeMap
键插入与取出顺序一致:LinkedHashMap(数组+双向链表)
读取文件:Properties(继承Hashtable)
不可变集合
概述:不可变集合,就是不可被修改的集合。集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。JDK9开始支持!!!
引用场景:如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。或者当集合对象被不可信的库调用时,不可变形式是安全的。
使用方法:
在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。
这个集合不能添加,不能删除,不能修改。
方法名称 说明 static List of(E…elements) 创建一个具有指定元素的List集合对象 static Set of(E…elements) 创建一个具有指定元素的Set集合对象 static <K , V> Map<K,V> of(E…elements) 创建一个具有指定元素的Map集合对象
CSDN-Ada助手: Java 技能树或许可以帮到你:https://edu.csdn.net/skill/java?utm_source=AI_act_java