Java巢狀類(內部類)總結
巢狀類(nested class)是指被定義在另一個類的內部的類。巢狀類的目的應該只是為它的外圍類(enclosing class)提供服務。如果巢狀類將來可能會用於其他某個環境中,它就應該是頂層類,而不是被設計為巢狀類。巢狀類分如下四種:
靜態成員類(static member class)、非靜態成員類(nostatic member class)、匿名類(anonymous class)和區域性類(local class)。
除了靜態成員類之外,其他三種都被稱為內部類(Inner Class)。靜態成員類和非靜態成員類稱為成員類,後兩個稱為非成員類。
靜態成員類
靜態成員類是最簡單的一種巢狀類,最好把它看作是普通的類,只是碰巧被宣告在另一個類的內部而已,它可以訪問外圍類的所有成員(靜態),包括那些宣告為私有的成員。靜態成員類是外圍類的一個靜態成員,與其他的靜態成員一樣,也遵循同樣的可訪問性規則。如果它被宣告為私有的,它就只能在外圍類的內部才能被訪問。
靜態成員類的一種常見用法是作為公有的輔助類,僅當它與外部類一起使用時才有意義。
例如String類的CaseInsensitiveComparator比較器,該靜態成員類提供了String型別忽略字元大小寫的比較方法。
例項:public final class String implements java.io.Serializable, Comparable<String>, CharSequence { public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } } ...... }
輸出:public class StaticMemberClassTest { public static void main(String[] args) { String[] strs={"ab","Ab","CC","abc"}; Arrays.sort(strs);//預設排序規則(大小寫敏感) for(String s:strs){ System.out.print(s+" "); } System.out.println(); Arrays.sort(strs,String.CASE_INSENSITIVE_ORDER);//大小寫不敏感的排序規則 for(String s:strs){ System.out.print(s+" "); } } }
Ab CC ab abc
Ab ab abc CC
再比如HashMap中的靜態成員類Entry,該類代表雜湊對映表的鍵值對,與他的外圍類一起使用。
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}
....
}
例項:
public class EntryStaticMemberClass {
public static void main(String[] args) {
int[] nums={1,3,3,2,1,5,4,3,8,2};
Map<Integer,Integer> countMap=new HashMap<Integer,Integer>();
for(int i:nums){
if(countMap.get(i)==null){
countMap.put(i, 1);
}else{
countMap.put(i, countMap.get(i)+1);
}
}
for(Entry<Integer, Integer> entry:countMap.entrySet()){
System.out.println("元素:"+entry.getKey()+" 出現了:"+entry.getValue()+"次");
}
}
}
輸出:
元素:1 出現了:2次
元素:2 出現了:2次
元素:3 出現了:3次
元素:4 出現了:1次
元素:5 出現了:1次
元素:8 出現了:1次
非靜態成員類
從語法上將,靜態成員類和非靜態成員類之間唯一的區別是,靜態成員類的宣告中包含修飾符static。儘管語法上非常相似,但是這兩種巢狀類有很大的不同。非靜態成員類的每個例項都隱含著與外圍類的一個例項相關聯。在非靜態成員類的例項方法內部,可以呼叫外圍例項上的方法,或者獲取外圍例項的引用。
如果巢狀的的例項可以在外圍類的例項之外獨立存在,即不需要與外圍類的特定例項關聯,這個巢狀類應該宣告為靜態成員類。
在沒有外圍例項的情況下,要想建立非靜態成員類的例項是不可能的。當非靜態成員類的例項被建立的時候,它和外圍例項之間的關聯關係就隨之建立起來了。而且這種關聯關係以後不會被修改。通常情況下,在外圍類的某個例項方法的內部呼叫非靜態成員類的構造器時,這種關聯關係就建立起來了。
非靜態成員類的一種常見的用法是定義一個Adapter(介面卡),提供類的各種檢視。例如,Map介面的實現往往使用非靜態成員類來實現它們的集合檢視(Collection View),這些集合檢視由Map的keySet、entrySet和values方法返回。
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
// Views
transient volatile Set<K> keySet = null;//父類AbstractMap中
transient volatile Collection<V> values = null;//父類AbstractMap中
private transient Set<Map.Entry<K,V>> entrySet = null;
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
}
private final class Values extends AbstractCollection<V> {
public Iterator<V> iterator() {
return newValueIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
HashMap.this.clear();
}
}
public Set<Map.Entry<K,V>> entrySet() {
return entrySet0();
}
private Set<Map.Entry<K,V>> entrySet0() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> e = (Map.Entry<K,V>) o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
HashMap.this.clear();
}
}
....
}
我們可以看到,每個非靜態成員類,都可以直接訪問外圍類的例項屬性(keySet、values和entrySet)。public class NoStaticMemberClassTest {
public static void main(String[] args) {
int[] nums={1,3,3,2,1,5,4,3,8,2};
Map<Integer,Integer> countMap=new HashMap<Integer,Integer>();
for(int i:nums){
if(countMap.get(i)==null){
countMap.put(i, 1);
}else{
countMap.put(i, countMap.get(i)+1);
}
}
System.out.print("鍵集合:");
for(Integer i:countMap.keySet()){
System.out.print(i+" ");
}
System.out.println();
System.out.print("值集合:");
for(Integer i:countMap.values()){
System.out.print(i+" ");
}
System.out.println();
System.out.println("建值對集合:");
for(Entry<Integer, Integer> entry:countMap.entrySet()){
System.out.println("建:"+entry.getKey()+" 值:"+entry.getValue());
}
}
}
輸出:
鍵集合:1 2 3 4 5 8
值集合:2 2 3 1 1 1
建值對集合:
建:1 值:2
建:2 值:2
建:3 值:3
建:4 值:1
建:5 值:1
建:8 值:1
此外,Set和List這種集合介面的實現往往也使用非靜態成員類來實現它們的迭代器(Iterator)。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned = null;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
......
}
例項:
public class IteratorTest {
public static void main(String[] args) {
LinkedList<Integer> arr=
new LinkedList<Integer>(Arrays.asList(1,2,3,4,5,6));
Iterator iter = arr.iterator();
while(iter.hasNext()){
System.out.print((Integer) iter.next()+" ");
}
System.out.println();
iter = arr.descendingIterator();
while(iter.hasNext()){
System.out.print((Integer) iter.next()+" ");
}
}
}
輸出:
1 2 3 4 5 6
6 5 4 3 2 1
私有靜態成員類
私有靜態成員類的一種常見用法就是來代表外圍類的元件。例如,Map中的Entry,Entry代表鍵值對(key-value)。雖然每個Entry都與一個Map關聯,但是entry上的方法並不需要訪問該Map。因此,使用非靜態成員來表示Entry是非常浪費的,而私有的靜態成員類是最佳的選擇。如果不小心漏掉了Entry宣告中的static修飾符,該map仍然可以工作,但是每個Entry中將會包含一個指向該Map的引用,這樣就浪費了空間和時間。因此,如果成員類不需要訪問外圍類例項,我們應該適用靜態成員類,而不是非靜態成員類。
匿名類
匿名類沒有名字,匿名類在使用的同時被宣告和初始化,匿名類可以出現在任何允許存在表示式的地方。匿名類的一種常見用法是動態的建立函式物件。
例如,利用匿名的Comparator例項,根據字串長度對字串陣列進行排序。
public class AnonymousClassTest {
public static void main(String[] args) {
String[] strs={"ab","aaaa","CC","abc"};
Arrays.sort(strs,new Comparator<String>(){
public int compare(String s1,String s2){
return s1.length()-s2.length();
}
});
for(String s:strs){
System.out.print(s+" ");
}
}
}
輸出:
ab CC abc aaaa
另一種常見用法是建立過程物件,比如Runnable、Thread例項等。
public class AnonymousClassTest {
static class Anonymous implements Runnable{
@Override
public void run(){
System.out.println("do something only once");
}
}
public static void main(String[] args) {
//常規方式(定義一個靜態成員類,也可以單獨定義)
new Thread(new Anonymous()).start();
//實現介面的匿名類
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("do something only once");
}
}).start();
//繼承基類的匿名類
new Thread(){
@Override
public void run(){
System.out.println("do something only once");
}
}.start();
}
}
第三種常見用法就是靜態工廠方法的內部。
public class ListFactory {
//將int[]轉換成的List<Integer>的靜態工廠方法
static List<Integer> intArrayAsList(final int[] a ){
if(a==null){
throw new NullPointerException();
}
return new AbstractList<Integer>(){
@Override
public Integer get(int i){
return a[i];
}
@Override
public Integer set(int i,Integer value){
int oldValue=a[i];
a[i]=value;
return oldValue;
}
@Override
public int size(){
return a.length;
}
} ;
}
public static void main(String[] args) {
int[] a={1,2,3,45,2,1};
List<Integer> list=ListFactory.intArrayAsList(a);
Collections.sort(list);
for(int i:list){
System.out.print(i+" ");
}
}
}
從上面三個例子,不難看出匿名類僅限於只例項化一次的內部類。這樣可以防止產生很多短小且複用率不高的類。
匿名類的適用性受到諸多的限制:
1、你無法宣告一個匿名類來同時擴充套件基類和實現介面。匿名類要麼實現一個介面要麼繼承一個父類。
2、由於匿名類出現在表示式中,它們必須保持簡短,否則會影響程式的可讀性。
3、除了宣告它們的地方,你無法再次對它們例項化。
區域性類
區域性類是四種巢狀類中用得最少的。在任何“可以宣告區域性變數”的地方,都可以宣告區域性類,並且區域性類也遵循同樣的作用域規則,同樣要求必須簡短。
巢狀類的選擇
四種不同的巢狀類,都有各自的適用場景。
如果一個巢狀類需要在單個方法之外仍然是可見的,或者它太長,不適合放在方法內部,就應該適用成員類(靜態/非靜態)。
如果成員類的每個例項都需要一個指向其外圍例項的引用,就要設計為非靜態成員類。否則,設計為靜態成員類。
假設,這個巢狀類屬於單個方法的內部,如果你只需要在一個地方建立例項,並且已經有了一個預知的型別(類或介面)可以說明這個類的特徵,就要把它設計為匿名類。否則,就做成區域性類。
為什麼需要巢狀類
咋一看,巢狀類就像是一種程式碼隱藏機制,我們將巢狀類置於外圍類的內部,從而允許你把一些邏輯相關的類組織在一起,並控制巢狀類的可見性。但是,巢狀類遠非如此。
1、恰當的使用匿名類和區域性類,可以讓程式碼更加清晰而優雅。
2、我們可以利用私有靜態成員類,將外圍類的具體實現以及內部結構封裝在內部,增加內聚度。(Map的Entry)
3、非靜態成員類,每個非靜態成員類的例項可以直接操作相關的外圍類例項,不同的非靜態成員類可以提供外圍類的不同視窗(Iterator)。
4、每個非靜態成員類都能獨立的繼承父類和實現介面,使得在單繼承體系的Java語言中實現“多繼承”變得可能。
參考:
《java程式設計思想》
《Effective Java》