java核心技术 基础知识<第8-9章>

java核心技术 基础知识<第8-9章>

泛型的经典应用即集合。

8 泛型程序设计

Java 泛型更加详尽的信息

Generic programming。

8.1 为什么要使用泛型程序设计

泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。和C++的模板很像。
类型参数的魅力在于:使得程序具有更好的可读性和安全性。

ArrayList<String> files = new ArrayList<>();

Java 语言的设计者发明了一个具有独创性的新概念, 通配符类型( wildcard type)。

凭经验来说, 那些原本涉及许多来自通用类型(如Object 或Comparable 接口)的强制类型转换的代码一定会因使用类型参数而受益

8.2 定义简单泛型类

默认命名方式:

<E> // element集合元素
<K, V>  // key value,键值 
<T, U, S> // 任意类型
public class Pair<T, U> { 
	private T first; // 使用T代替某种类型
}

C++ 注释: 从表面上看, Java 的泛型类类似于C++ 的模板类。唯一明显的不同是Java 没有专用的template 关键字。但是, 在本章中读者将会看到, 这两种机制有着本质的区别

8.3 泛型方法

注意,类型变量放在修饰符(这里是public static ) 的后面返回类型的前面
当调用一个泛型方法时,在方法名前的尖括号中放人具体的类型。

public static <T> T getMiddle(T... a)
{
	return a[a.length / 2];
}

String middle = ArrayAlg.<String>getMiddle("JohnM", "Q.n", "Public"); 

大多数情况,编译器可以推导出使用哪个T。

提示:如果想知道编译器对一个泛型方法调用最终推断出哪种类型, PetervonderAM 推荐了这样一个窍门: 有目的引入一个错误, 并研究所产生的错误消息。

C++注释:C++把类型参数放在方法名后面,可能有歧义。g(f<a, b>©)可能理解为bool值f<a和b>c调用g。所以java在方法名前面。

8.4 类型变量的限定

对类型加以约束。比如必须实现某个接口,必须派生于某个超类。

public static <T extends Comparable & Serializable> T min(T[] a){
	...
}

限定类型用“ &” 分隔,而逗号用来分隔类型变量。在Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用
一个类作为限定,它必须是限定列表中的第一个。

8.5 泛型代码和虚拟机

类型擦除:
泛型实现原理:虚拟机对类型进行了擦除,作为一个普通类。

无限定的类型用Object替换。有限定的类型用第一个限定的类型变量来替换,其余用类型转换。所以要把tagging接口放在最后一个。

C++注释:就这点而言, Java 泛型与C++ 模板有很大的区别。C++ 中每个模板的实例化产生不同的类型,这一现象称为**“ 模板代码膨胀”**。Java 不存在这个问题的困扰。

翻译泛型表达式:
调用泛型方法时,编译器插入强制类型转化。因为被擦除了,所以编译器要加上强制类型转化。

翻译泛型方法:
用桥接方式。
总之,需要记住有关Java 泛型转换的事实:
• 虚拟机中没有泛型, 只有普通的类和方法。
• 所有的类型参数都用它们的限定类型替换。
• 桥方法被合成来保持多态。
• 为保持类型安全性,必要时插人强制类型转换。

8.6 约束与局限性

  1. 不能用基本类型实例化类型参数
    没有Pair, 只有Pair。
  2. 运行时类型查询只适用于原始类型
    getClass总会返回原始类型。
if (a instanceof Pair<String>) // Error
if (a instanceof Pair<T>) // Error

Pair<String> stringPair = . .
Pair< Employee>employeePair = . .
if (stringPair.getClass() == employeePair.getClass()) // they are equal
  1. 不能创建参数化类型的数组
    不能实例化参数化类型的数组, 例如:
    Pair<String>[] table = new Pair<String>[10] ; // Error
  2. Varargs 警告
  3. 不能实例化类型变置
public Pair() { first = new T(); second = new T(); } // Error
  1. 不能构造泛型数组。类型擦除会让他永远构造Comparable[2]数组
    T[] mm = new T[2];
  2. 泛型类的静态上下文中类型变量无效。被擦除了。
  3. 不能抛出或捕获泛型类的实例

8.7 泛型类型的继承规则

无论S与T有什么联系,通常,Pair<S>Pair<T>没什么联系。

8.8 通配符类型

允许参数类型变化。例如:

Pair<? extends Employee>

类型Pair<Manager>Pair<? extends Employee> 的子类型。
? extends XX表示XX某一个具体的子类。
可以有 ? extends Employee getFirst()但是不能有 void setFirst(? extends Employee)

Pair<Manager> managerBuddies = new Pair<>(ceo, cfo) ;
Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK
wildcardBuddies.setFirst(lowlyEnployee) ; // compile-time error

这样将不可能调用setFirst 方法。编译器只知道需要某个Employee 的子类型, 但不知道具体是什么类型。它拒绝传递任何特定的类型。毕竟?不能用来匹配。!!wildcardBuddies可以接收任一子类,两个子类之间是不能转换的!!

将getFirst 的返回值赋给一个Employee 的引用完全合法。

通配符的超类型限定(superType bound):
? super Manager
限制为Manager的所有超类型。带有超类型限定的通配符的行为与8.8 节介绍的相反。可以为方法提供参数, 但不能使用返回值。例如, Pair<? super Manager> 有方法

void setFirst(<? super Manager>)
<? super Manager> getFirst()

编译器无法知道set的具体类型(任意Manager的超类,比如Object比如Employee)。因此只能传递Manager。
调用get,不能保证返回值类型,只能赋值给Object。
直观地讲,带有超类型限定的通配符(super)可以向泛型对象写入,带有子类型限定(extends)的通配符可以从泛型对象读取。

无限定通配符:
Pair<?>。返回值只能赋值给object,set方法不能调用。在有些简单的操作中比较有用,不需要实际类型的场景。

通配符捕获只有在有许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、确定的类型。

8.9 反射和泛型

泛型Class类,String.class实际上是Class类的对象(唯一对象)。
java.lang.Class中定义了很多对类型进行操作的函数。

使用Class参数进行类型匹配:

public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException
{
return new Pair<>(c.newInstance() , c.newInstance()) ;
}
// 如果调用
makePair(Employee.class)

makePair一定返回Pair因为Employee.class是Class的一个对象。

Java 泛型更加详尽的信息

9 集合

一堆用泛型实现的数据结构

9.1 Java集合框架

集合框架

将集合的接口与实现分离:
interface和implementation分离,例如Queue:

接口定义:

public interface Queue<E> // a simplified form of the interface in the standard library
{
	void add(E element) ;
	E remove();
	int size();
}

实现方式有两种(举例,实际并没有)

  1. CircularArrayQueue<E>循环队列。
  2. linkedListQueue<E> 链表

注:API文档中有一些AbstractXX的类,扩展这些类比实现Queue接口中的所有方法轻松。

Collection接口:
集合类的基本接口是Collection接口:

public interface Collection<E>{
	boolean add(E element);
	Iterator<E> interator();
	...
}

add 方法用于向集合中添加元素。如果添加元素确实改变了集合就返回true, 如果集合没有发生变化就返回false
iterator返回一个实现Iterator接口的对象,可以依次访问集合中的元素。

迭代器:
Iterator 接口包含4 个方法:

public interface Iterator<E>
{
	E next(); // 返回一个元素,迭代器向后
	boolean hasNext(); // 有其他元素
	void remove(); // 将会删除上次调用next 方法时返回的元素
	// 删除指定元素时,要越过这个元素
	default void forEachRemaining(Consumer<? super E> action) ;
}

forEachRemainging的用法:lambda表达式作用每个元素。

iterator.forEachRemaining(element -> do something with element) ;
for (String element : c) // 遍历所有元素
{
	do something with element
}

应该将Java 迭代器认为是位于两个元素之间。当调用next 时, 迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。
C++注释:与C++不同,C++的迭代器是元素本身。Java的查找与位置变更是紧密相连的。

泛型实用方法:
Collection接口声明了很多有用的方法

int size()
boolean isEmpty()
boolean contains(Object obj)
boolean containsAll (Collection<?> c)
boolean equals(Object other)
boolean addAll (Collection<? extends E> from)
boolean remove(Object obj)
boolean removeAll(Collection<?> c)
void clear()
boolean retainAll(Collection<?> c)
Object[] toArray()
<T> T[] toArray(T[] arrayToFill )

default boolean removelf (Predicate<? super E> filter) // 删除某种元素

Collection通过AbstractCollection实现了大部分通用的功能。是一种通过集成减少重复代码的手段。

集合框架中的接口:
集合框架的接口

Collection:
boolean add(E element)

Map:
V put(K key, V Value)
V get(K key)

List:
void add(int index, E element) // 增
void remove(int index) // 删
E set(int index, E element) // 改
E get(int index)  // 查

ListIterator extends Iterator:
void add(E element) // 在迭代器前增一个元素

RandomAccess: // 标记接口,测试是否支持高效随机访问
if (c instanceof RandomAccess){
	// 随机访问
}else{
	// 逐个访问
}

Set == Collection, 
// add不允许重复。
// equals意义不一样

NavigableSet, NavigableMap:
// 用于搜索和遍历有序集

9.2 具体的集合

Collection:
ArrayList 一种可以动态增长和缩减的索引序列
LinkedList 一种可以在任何位置进行高效地插人和删除操作的有序序列
ArrayDeque 一种用循环数组实现的双端队列
HashSet 一种没有重复元素的无序集合
TreeSet 一种有序集
EnumSet 一种包含枚举类型值的集
LinkedHashSet 一种可以记住元素插人次序的集
PriorityQueue 一种允许高效删除最小元素的集合

Map:
HashMap 一种存储键/ 值关联的数据结构
TreeMap 一种键值有序排列的映射表
EnumMap 一种键值属于枚举类型的映射表
LinkedHashMap 一种可以记住键/ 值项添加次序的映射表
WeakHashMap 一种其值无用武之地后可以被垃圾回收器回收的映射表
IdentityHashMap 一种用=而不是用equals 比较键值的映射表

链表:
双向链表。
是有序集合,每个对象位置很重要。
list可以produce ListIterator,可使用ListIterator的add向链表某个迭代器前add元素。
ListIterator可从两个方向遍历。previous(),hasPrevious()
list.listIterator(n)将返回索引为n的元素的前面的位置。
list.get(n) 效率极低。
add 方法只依赖于迭代器的位置, 而remove 方法依赖于迭代器的状态(两个方向指针行为不一样)。
链表结构被改后,其他迭代器会失效。
可使用toString和contains方法。

相关类,接口:

java.util.List<E> // 继承Collection
java.util.ListIterator<E> // 生成自List
java.util.LinkedList<E> // List的具体实现

数组列表:
实现List接口(有序,位置重要的集合)。
ArrayList(线程不安全,单线程快),Vector(线程安全, 单线程慢)。

散列集:
元素位置无所谓,可快速查找元素。
散列表(hash table)。hash取模,链表,平衡二叉树。
如果自定义类,就要负责实现这个类的hashCode方法。equals结果为true,则两个对象的hashCode必须相同。
Java SE 8中,散列表桶满之后会从链表变为平衡二叉树。如果选择散列函数不当,会产生很多冲突。

HashSet extends AbstractSet extends Set extends Collection

访问随机!contains效率高!

java.util.HashSet<E>
// 可构造自其它Collection,可指定容量和装填因子。

树集:
TreeSet是有序的。遍历是按照排序后顺序的。红黑树实现。
相比HashSet,增删查要慢一些.logn复杂度。
元素要实现Comparable接口或提供Comparator

TreeSet extends AbstractSet&SortedSet extends Set extends Collection
java.util.TreeSet<E>; // 一些构造函数
java.util.SortedSet<E>; // 获取比较器,最大最小元素
java.util.NavigableSet<E>; // 获取上下界,删除并返回等,递减顺序迭代器

注释: 从JavaSE 6 起, TreeSet 类实现了NavigableSet 接口。这个接口增加了几个便于定位元素以及反向遍历的方法。详细信息请参看API 注释。

队列和双端队列:

java.util.Queue<E>interface
boolean add(E);  // 增尾部
E remove(); // 删头部
E element(); // 返回头部(不删),以上异常抛出Exception

boolean offer(E); // 同add。满了返回false
E poll(); // 删头部。不存在返回null
E peek(); // 返回头部(不删)不存在返回null

java.util.Deque<E>interface
// add remove get + (First or Last)
// offer poll peek + (First or Last)

java.util.ArrayDeque<E>  是 一种实现
ArrayDeque( ); //  16初始容量
ArrayDeque(int initialCapacity); // 给定初始容量
// 无限双端队列。

优先队列:

java.lang.Object
	java.util.AbstractCollection<E>
		java.util.AbstractQueue<E>
			java.util.PriorityQueue<E>

priority queue中元素可以按照任意顺序插入,却总按照排序的顺序进行检索。
堆数据结构存储,不必排序,无论何时调用remove总会获得当前优先队列中最小的元素。

add,remove会把最小元素移动到根,而不必排序。
迭代器不是按照顺序的!与TreeSet不同

9.3 映射

存放键值对。提供键查找值。
HashMapTreeMap都实现了Map接口。

Map<String, Employee> staff = new HashMap<>();// HashMap implements Map
Employee harry = new Employee("Harry Hacker");
staff.put("987-98-9996", harry); // 插入,第二个会取代第一个。返回旧值

String id = "987-98-9996";
e = staff.get(id);// gets harry 获得
int score = scores,get(id, 0); // 没有的话返回指定默认值0

staff.remove("567-24-2546") ;  // 删除

scores.forEach((k, v) ->
	System.out.println("key=" + k + ", value:" + v)) ; // 迭代用forEach

映射的包介绍

java.util.Map<K,V>
V	get(Object key);
default V	getOrDefault(Object key, V defaultValue);
V	put(K key, V value);
void	putAll(Map<? extends K,? extends V> m);
boolean	containsKey(Object key);
boolean	containsValue(Object value);
default void	forEach(BiConsumer<? super K,? super V> action);

java.util.HashMap<K,V>:
java.lang.Object
	java.util.AbstractMap<K,V>
		java.util.HashMap<K,V>

java.util.TreeMap<K,V>:
java.lang.Object
	java.util.AbstractMap<K,V>
		java.util.TreeMap<K,V>

更新映射项:
三种合理的方式自增:

counts.put(word,  counts.getOrDefault(word, 0)+ 1);

counts.putlfAbsent(word, 0);
counts.put(word, counts.get(word)+ 1);

counts.merge(word, 1, Integer::sum);

有一些方法,比如merge, computeIfPresent, computeIfAbsent, replaceAll

映射视图:
Map不是一个Collection,不过可以得到映射的视图(view)——这是实现了Collection接口或某个子接口的对象。
有三种视图:
键集合,值集合,键值对集合。
需要说明的是, keySet 不是HashSet 或TreeSet

Set<K> keySet()
Collection<V> values()
Set<Map.Entry<K, V>> entrySet()

可以用来枚举键、值、键值对。

for (String key : map.keySet()){} //遍历键值
for (Map.Entry<String, Employee> entry : staff.entrySet()){} //遍历键值对
counts.forEach((k, v) -> { // 最高效的遍历方式
	do something with k, v
});
java.util.Map<K,V>:
Set<Map.Entry<K, V>> entrySet();
返回Map.Entry 对象(映射中的键/ 值对)的一个集视图。可以从这个集中删除元素,
它们将从映射中删除,但是不能增加任何元素。
Set<K> keySet();
返回映射中所有键的一个集视图。可以从这个集中删除元素,键和相关联的值将从映
射中删除,但是不能增加任何元素。
Collection<V> values();
返回映射中所有值的一个集合视图。可以从这个集合中删除元素, 所删除的值及相应
的键将从映射中删除,不过不能增加任何元素。

java.util.Map.Entry<K, V>
K getKey()
V getValue()
返回这一条目的键或值。
V setValue(V newValue)
将相关映射中的值改为新值, 并返回原来的值。

弱散列映射:
解决键不能被GC的问题。因为键在桶里,HashMap不删的话,就一直存在。
WeakHashMap:当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键/ 值对。

链接散列表与映射:
LinkedHashSet和LinkedHashMap会记录插入元素的顺序
链接散列映射将用访问顺序, 而不是插入顺序, 对映射条目进行迭代
每次调用get或put,受影响的条目将会被移动到链表尾部(只有条目在链表中的位置会受影响, 而散列表中的桶不会受影响。一个条目总位于与键散列码对应的桶中)。

访问顺序对于实现高速缓存的“ 最近最少使用” 原则十分重要。

枚举集与映射:
EmimSet 是一个枚举类型元素集的高效实现。位序列实现。bitmap?

enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
EnumSet<Weekday> always = EnumSet.allOf(Weekday.class);  // allOf方法
EnumSet<Weekday> never = EnumSet.noneOf(Weekday.class); // noneOf方法
EnumSet<Weekday> workday = EnumSet.range(Weekday.MONDAY, Weekday.FRIDAY); // range方法
EnumSet<Weekday> mwf = EnumSet.of(Weekday.MONDAY, Weekday.WEDNESDAY, Weekday.FRIDAY); // of 方法

EnumMap 是一个键类型枚举类型的映射。值数组实现。构造器指定键类型。

EnumMap<Weekday, Employee〉personlnCharge = new EnumMapo(Weekday.class);

标识散列映射:
键的散列值不由hashCode计算,而是由System.identifyHashCode计算,根据内存地址计算散列码用==比较,不用equals。
也就是说, 不同的键对象, 即使内容相同, 也被视为是不同的对象

9.4 视图与包装器

使用视图可以获得其他的实现了Collection接口和Map接口的对象(不一定是上图中的)。比如Map中的keySet方法所返回的Collection。这种可以对原类进行操作的集合称为视图。有很多有用的应用。

  • 轻量级集合包装器
  • 子范围
  • 不可修改视图
  • 同步视图
  • 受查视图
  1. 轻量级集合包装器
Card[] cardDeck = new Card[52];
List<Card> cardList = Arrays.asList(cardDeck):

List<String> names = Arrays.asList("Amy", "Bob", "Carl") ;
List<String> settings = Collections.nCopies(100, "DEFAULT") ;

Collections.singleton(anObject)//返回实现Set接口,不可修改的单元素集。
  1. 子范围
List group2 = staff.subList(10, 20) ;

// 对于有序集和映射
SortedSet<E> subSet(E from, E to)
SortedSet<E> headSet(E to)
SortedSet<E> tailSet(E from)

// 对于有序集和映射
SortedMap< K, V> subMap(K from, K to)
SortedMap<K, V> headMap(K to)
SortedMap<K, V> tailMap(K from)

// Java SE 6后
NavigableSet<E> subSet( E from , boolean fromlnclusive, E to, boolean tolnclusive)
NavigableSet<E> headSet(E to, boolean tolnclusive)
NavigableSet<E> tailSet(E from , boolean fromlnclusive)
  1. 不可修改的视图
    这些视图对现有集合增加了一个运行时的检查。如果发现试图对集合进行修改, 就抛出一个异常,同时这个集合将保持未修改的状态。
Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableNavigableSet
Collections.unmodifiableMap
Collections.unmodifiableSortedMap
Collections.unmodifiableNavigableMap

List<String> staff = new LinkedList<>() ;
lookAt(Collections.unmodifiableList(staff)) ;
  1. 同步视图
    类库的设计者使用视图机制来确保常规集合的线程安全, 而不是实现线程安全的集合类。
Map<String, Employee>map = Collections.synchronizedMap(new HashMap<String, Employee>())
  1. 受查视图
    受査” 视图用来对泛型类型发生问题时提供调试支持。
    一般情况下,add不报错,get时报错。用受查视图可在add时发现错误。
List<String> safestrings = Collections.checkedList(strings, String,class);
ArrayList rawList = safestrings;
rawList.add(new Date()); // checked list throws a ClassCastException

可选操作:
虽然视图是某种interface,但是不是所有操作都是可行的,有一些限制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值