ammayjxf

           

          泛型

          編者注:在從Java in a Nutshell,5th edith中摘錄的兩部部分中的第一部分,David Flanagan描述了如何使用泛型。這部分,David Flanagan將具體告訴你如何創(chuàng)建自己的泛型和泛型方法,并且以Java核心API很多重要的泛型作為結(jié)束總結(jié)。

          創(chuàng)建泛型和泛型方法
          創(chuàng)建一個(gè)簡(jiǎn)單的泛型是非常容易的。首先,在一對(duì)尖括號(hào)(< >)中聲明類型變量,以逗號(hào)間隔變量名列表。在類的實(shí)例變量和方法中,可以在任何類型的地方使用那些類型變量。切記,類型變量?jī)H在編譯時(shí)存在,所以不能使用instanceof和new這類運(yùn)行時(shí)操作符來操作類型變量。

          讓我們以一個(gè)簡(jiǎn)單的例子來開始這部分的學(xué)習(xí),而后將精簡(jiǎn)這個(gè)例子。這段代碼定義了一個(gè)樹形數(shù)據(jù)結(jié)構(gòu),使用類型變量V代表存儲(chǔ)在各個(gè)樹結(jié)點(diǎn)中的值。

          import java.util.*;
          /**
          * A tree is a data structure that holds values of type V.
          * Each tree has a single value of type V and can have any number of
          * branches, each of which is itself a Tree.
          */
          public class Tree {
          // The value of the tree is of type V.
          V value;

          // A Tree can have branches, each of which is also a Tree

          List<tree branches = new ArrayList<tree();

          // Here's the constructor. Note the use of the type variable V.
          public Tree(V value) { this.value = value; }

          // These are instance methods for manipulating the node value and branches.
          // Note the use of the type variable V in the arguments or return types.
          V getValue() { return value; }
          void setValue(V value) { this.value = value; }
          int getNumBranches() { return branches.size(); }
          Tree getBranch(int n) { return branches.get(n); }
          void addBranch(Tree branch) { branches.add(branch); }
          }


          正如你所看到的,命名一個(gè)類型變量習(xí)慣于一個(gè)大寫字母。使用一個(gè)字母可以同現(xiàn)實(shí)中那些具有描述性的,長(zhǎng)的實(shí)際變量名有所區(qū)別。使用大寫字母要同變量命名規(guī)則一致,并且要區(qū)別于局部變量,方法參數(shù),成員變量,而這些變量常常使用一個(gè)小寫字母。集合類中,比如java.util中常常使用類型變量E代表“Element type”。T和S常常用來表示范型變量名(好像使用i和j作為循環(huán)變量一樣)。

          注意到,當(dāng)一個(gè)變量被聲明為泛型時(shí),只能被實(shí)例變量和方法調(diào)用(還有內(nèi)嵌類型)而不能被靜態(tài)變量和方法調(diào)用。原因很簡(jiǎn)單,參數(shù)化的泛型是一些實(shí)例。靜態(tài)成員是被類的實(shí)例和參數(shù)化的類所共享的,所以靜態(tài)成員不應(yīng)該有類型參數(shù)和他們關(guān)聯(lián)。方法,包括靜態(tài)方法,可以聲明和使用他們自己的類型參數(shù),但是,調(diào)用這樣一個(gè)方法,可以被不同地參數(shù)化。這些內(nèi)容將在本章后面談到。

          類型變量綁定
          上面例子中的Tree中的類型變量V是不受約束的:Tree可以被參數(shù)化為任何類型。以前我們常常會(huì)設(shè)置一些約束條件在需要使用的類型上:也許我們需要強(qiáng)制一個(gè)類型參數(shù)實(shí)現(xiàn)一個(gè)或多個(gè)接口,或是一個(gè)特定類的子類。這可以通過指明類型綁定來完成。我們已經(jīng)看到了統(tǒng)配符的上界,而且使用簡(jiǎn)單的語(yǔ)法可以指定一般類型變量的上界。后面的代碼,還是使用Tree這個(gè)例子,并且通過實(shí)現(xiàn)Serializable和Comparable來重寫。為了做到這點(diǎn),例子中使用類型變量綁定來確保值類型的Serializable和Comparable。

          import java.io.Serializable;
          import java.util.*;

          public class Tree>
          implements Serializable, Comparable<tree

          {
          V value;
          List<tree branches = new ArrayList<tree();

          public Tree(V value) { this.value = value; }

          // Instance methods
          V getValue() { return value; }
          void setValue(V value) { this.value = value; }
          int getNumBranches() { return branches.size(); }
          Tree getBranch(int n) { return branches.get(n); }
          void addBranch(Tree branch) { branches.add(branch); }

          // This method is a nonrecursive implementation of Comparable<tree

          // It only compares the value of this node and ignores branches.
          public int compareTo(Tree that) {
          if (this.value == null && that.value == null) return 0;
          if (this.value == null) return -1;
          if (that.value == null) return 1;
          return this.value.compareTo(that.value);
          }

          // javac -Xlint warns us if we omit this field in a Serializable class
          private static final long serialVersionUID = 833546143621133467L;
          }


          一個(gè)類型變量的綁定是通過extends后的名字和一個(gè)類型列表(這可以是參數(shù)化的,就像Comparable一樣)表達(dá)的。注意當(dāng)有不止一個(gè)綁定時(shí),就像上面例子中的,綁定的類型要用&作為分隔符,而不是使用逗號(hào)。都后用來分隔類型變量,如果用來分隔類型變量綁定,就會(huì)模棱兩可。一個(gè)類型變量可以有任何數(shù)量的綁定,包括任何數(shù)量的借口和至多一個(gè)類。

          范型中的通配符
          上一章的例子中我們看到了通配符和控制參數(shù)化類型的通配符綁定。這些在范型中同樣非常有用。當(dāng)前設(shè)計(jì)的Tree要求每個(gè)節(jié)點(diǎn)有相同類型的值,V。也許這樣太嚴(yán)格了,也許我們應(yīng)該讓Tree的branches能夠存放V的子類而不全是V。這個(gè)版本的Tree(刪除了Comparable和Serializable接口的實(shí)現(xiàn))這樣做會(huì)更靈活。

          public class Tree {
          // These fields hold the value and the branches
          V value;
          List<tree<? extends V>> branches = new ArrayList<tree<? extends V>>();

          // Here's a constructor
          public Tree(V value) { this.value = value; }

          // These are instance methods for manipulating value and branches
          V getValue() { return value; }
          void setValue(V value) { this.value = value; }
          int getNumBranches() { return branches.size(); }
          Tree<? extends V> getBranch(int n) { return branches.get(n); }
          void addBranch(Tree<? extends V> branch) { branches.add(branch); }
          }


          通配符綁定允許我們?cè)谥?jié)點(diǎn)上增加一個(gè)Tree,比如,一個(gè)樹枝Tree
          Tree t = new Tree(0); // Note autoboxing
          t.addBranch(new Tree(1)); // int 1 autoboxed to Integer


          通過getBranch()查詢樹枝,而樹枝的返回類型不知道,所以必須使用統(tǒng)配符來表達(dá)。接下來的兩個(gè)是合法的,但第三個(gè)不是:
          Tree<? extends Number> b = t.getBranch(0);
          Tree<?> b2 = t.getBranch(0);
          Tree b3 = t.getBranch(0); // compilation error


          當(dāng)我們這樣來查詢一個(gè)樹枝時(shí),不能精確確定它的返回類型,但是存在類型的上限,所以,我們可以這樣做:
          Tree<? extends Number> b = t.getBranch(0);
          Number value = b.getValue();


          那我們不能做什么呢?設(shè)定樹枝的值,或者在原有的樹枝上添加新的樹枝。早前章節(jié)解釋的,上界的存在不會(huì)改變返回值的類型不可知,編譯器沒有足夠的信息讓我們安全的給setValue()或者一個(gè)樹枝(包括值類型)的addBranch()傳遞一個(gè)值。下面的兩行代碼都是非法的:
          b.setValue(3.0); // Illegal, value type is unknown
          b.addBranch(new Tree(Math.PI));


          這個(gè)例子在設(shè)計(jì)時(shí)找到了一個(gè)平衡點(diǎn):使用綁定通配符使得數(shù)據(jù)結(jié)構(gòu)更加靈活,但是減少了安全使用其中方法的可能。這個(gè)設(shè)計(jì)是好是壞就要根據(jù)上下文聯(lián)系了。通常,好的范型設(shè)計(jì)是非常困難的。幸運(yùn)的是,大多我們要使用的已經(jīng)在java.util包中設(shè)計(jì)好了,而不用我們自己再去設(shè)計(jì)。

          范型方法
            正如前面說的,范型只能被實(shí)例成員調(diào)用,而不是靜態(tài)成員。同實(shí)例方法一樣,靜態(tài)方法也可以使用通配符。盡管靜態(tài)方法不能使用包含他們的類中的類型變量,但是他們可以聲明自己的類型變量。當(dāng)一個(gè)方法聲明了自己的類型變量,就叫做范型方法。

          這里有一個(gè)要添加到Tree中的靜態(tài)方法。他不是一個(gè)范型方法,但是使用了綁定的通配符,就好像先前我們看到的sumList()一樣:
          /** Recursively compute the sum of the values of all nodes on the tree */
          public static double sum(Tree<? extends Number> t) {
          double total = t.value.doubleValue();
          for(Tree<? extends Number> b : t.branches) total += sum(b);
          return total;
          }


            通過通配符的上界綁定,聲明自己的類型變量來重寫這個(gè)方法:
          public static  double sum(Tree t) {
          N value = t.value;
          double total = value.doubleValue();
          for(Tree<? extends N> b : t.branches) total += sum(b);
          return total;
          }


            范型的sum()不比通配符版本的簡(jiǎn)單,而且聲明變量并沒有讓我們獲得什么。這種情況下,通配符方案要比范型方法更有效,當(dāng)一個(gè)類型變量用來表達(dá)兩個(gè)參數(shù)之間或者參數(shù)和返回值之間的關(guān)系時(shí),范型方法才是需要的。請(qǐng)看下面的例子:
          // This method returns the largest of two trees, where tree size
          // is computed by the sum() method. The type variable ensures that
          // both trees have the same value type and that both can be passed to sum().
          public static Tree max(Tree t, Tree u) {
          double ts = sum(t);
          double us = sum(u);
          if (ts > us) return t;
          else return u;
          }
            

          這個(gè)方法使用類型變量N來約束參數(shù)和返回值有相同類型,并且參數(shù)是Number或者他的子類。

            使得參數(shù)具有相同類型也許是有爭(zhēng)議的,應(yīng)該讓我們能調(diào)用max()不論是Tree或者Tree。一種方法是使用兩個(gè)不相干的類型變量來表示兩個(gè)不相干的值類型。注意,我們不能在方法的返回時(shí)使用變量而必須使用通配符:
          public static 
          Tree<? extends Number> max(Tree t, Tree u) {...}


            既然兩個(gè)類型變量N和M沒有任何聯(lián)系,而且每個(gè)僅在簽名的時(shí)候使用,他們沒有提供比通配符更多的好處,這種方法最好這樣寫:
          public static Tree<? extends Number> max(Tree<? extends Number> t,
          Tree<? extends Number> u) {...}


          所有在這里的范型方法都是靜態(tài)的,這并不是必須的,實(shí)例方法也可以聲明自己的類型變量。

          調(diào)用范型方法
          當(dāng)你使用范型時(shí),必須指定實(shí)際類型參數(shù)來代替相應(yīng)的類型變量。但這些對(duì)范型方法有些不同:編譯器總是能計(jì)算出基于你所傳遞的參數(shù)的相應(yīng)范型方法參數(shù)。考慮一下上面定義的max(),作為例子:
          public static  Tree max(Tree t, Tree u) {...}


          當(dāng)你調(diào)用這個(gè)方法時(shí),不需要指明N,因?yàn)镹是隱含地由t和u指明。在后面的代碼中,編譯器決定N為Integer:

          Tree x = new Tree(1);
          Tree y = new Tree(2);
          Tree z = Tree.max(x, y);


          編譯器判斷范型方法的參數(shù)類型稱為類型推斷。類型推斷是相對(duì)于知覺推斷的。而實(shí)際編譯器的實(shí)現(xiàn)方法是一種非常復(fù)雜的過程,超過了這本書的討論范圍。更多的細(xì)節(jié)在The Java Language Specification, Third Edition的第十五章。
          讓我們看一個(gè)更加復(fù)雜的類型推斷,考慮一下這個(gè)方法:

          public class Util {
          /** Set all elements of a to the value v; return a. */
          public static T[] fill(T[] a, T v) {
          for(int i = 0; i < a.length; i++) a[i] = v;
          return a;
          }
          }


          這里有兩個(gè)該方法的調(diào)用:

          Boolean[] booleans = Util.fill(new Boolean[100], Boolean.TRUE);
          Object o = Util.fill(new Number[5], new Integer(42));


          在第一個(gè)例子中,編譯器可以輕松的推斷出T是Boolean類型,第二個(gè)例子中,編譯器判斷T是Number。
          在非常罕見的情況下,你可能會(huì)顯示的指明范型方法的參數(shù)類型。有時(shí)候這是必要的,比如,當(dāng)范型方法不需要參數(shù)時(shí)。考慮一下java.util.Collections.emptySet():返回一個(gè)空集合,但是不同于Collections.singleton()(可以在參考部分察看),他不帶任何參數(shù),但需要指明返回類型。通過在方法名前的<>中,可以顯示的指明參數(shù)類型:

          Set empty = Collections.emptySet();


            類型參數(shù)不能同沒有限制的方法名結(jié)合使用:他們必須跟隨在一個(gè).后或者在關(guān)鍵字new后,或者在關(guān)鍵字this前,或者構(gòu)造函數(shù)的super前。
          可以證明,如果如果你將Collections.emptySet()的返回值賦給一個(gè)變量,就像我們上邊通過類型推斷機(jī)制推斷基于變量類型的參數(shù)類型。盡管顯示的類型說明可以更加清楚,但這不是必要的,可以像下面一樣重寫:

          Set empty = Collections.emptySet();


            在方法調(diào)用表達(dá)式中,顯示的說明emptySet()的返回值類型是必要的。比如,假設(shè)你要調(diào)用一個(gè)名為printWords()的方法,該方法僅需一個(gè)Set的參數(shù),如果你想傳遞一個(gè)空的集合給該方法,就要像下面一樣寫:
          printWords(Collections.emptySet());


          這種情況下,顯示的類型說明是必要的。

          范型方法和數(shù)組
          早先我們看到,編譯器不允許創(chuàng)建一個(gè)類型參數(shù)化的數(shù)組。但是對(duì)于范型的使用會(huì)是不同的。考慮一下前面定義的Util.fill(),它得以第一個(gè)參數(shù)和返回值類型都是T[]。而方法體內(nèi)不必創(chuàng)建任何參數(shù)為T的數(shù)組,所以這個(gè)方法是合法的。

          如果你創(chuàng)建一個(gè)方法使用varargs(參見第二章的2.6.4)和類型變量,記住調(diào)用varargs隱含創(chuàng)建一個(gè)數(shù)組,請(qǐng)看下面的例子:

          /** Return the largest of the specified values or null if there are none */
          public static > T max(T... values) { ... }


          你可以使用一個(gè)Integer類型來調(diào)用這個(gè)方法,因?yàn)榫幾g器會(huì)在調(diào)用的時(shí)候插入必要的數(shù)組創(chuàng)建代碼。但是你不能將參數(shù)轉(zhuǎn)換為Comparable來調(diào)用這個(gè)方法,因?yàn)閯?chuàng)建一個(gè)Comparable[]是不合法的。

          參數(shù)化異常
          異常是在運(yùn)行時(shí)拋出和捕獲的。沒有辦法讓編譯器完成類型檢查,來保證在catch塊中拋出的未知的類型匹配異常。由于這個(gè)原因,catch塊很可能不包含類型變量和通配符。既然不可能保證在運(yùn)行時(shí)捕獲一個(gè)編譯器時(shí)類型參數(shù)完整性異常,所以不允許創(chuàng)建任何Throwable類型的子類。參數(shù)化異常是不允許的。
          但是你可以使用類型變量在throw塊里的方法簽名中。看看下面的例子:

          public interface Command {
          public void doit(String arg) throws X;
          }


          這個(gè)接口描述了一個(gè)“command”:一塊代碼只有一個(gè)String類型的參數(shù),沒有返回值。代碼可能拋出一個(gè)類型為X的異常。這里有一個(gè)例子使用這個(gè)接口:

          Command save = new Command() {
          public void doit(String filename) throws IOException {
          PrintWriter out = new PrintWriter(new FileWriter(filename));
          out.println("hello world");
          out.close();
          }
          };

          try { save.doit("/tmp/foo"); }
          catch(IOException e) { System.out.println(e); }



          范型個(gè)案研究:比較和枚舉
          Java1.5引入的范型新特性,在1.5的API中有使用,特別多的是在java.util包中,但是在java.lang,java.lang.reflect和java.util.concurrent中也有。這些API都是經(jīng)過仔細(xì)的斟酌創(chuàng)建的,通過學(xué)習(xí)這些API我們可以學(xué)到很多好的設(shè)計(jì)方法。

          java.util中的范形是比較簡(jiǎn)單的:因?yàn)榇蠖喽际羌项悾愋妥兞恳彩谴砑现械脑亍ava.lang中的幾個(gè)重要范型是比較難以理解的,他們不是集合,而且第一眼很不容易理解為什么設(shè)計(jì)成范型。學(xué)習(xí)這些范型可以讓我們更深層次的理解范形的工作機(jī)制,并且介紹一些我們沒有提到的概念。特別的,我們要檢查Comparable接口和Enum類(枚舉類型的超類,后面一張講解)并且學(xué)習(xí)一些重要但是很少使用的范型特性,比如通配符下界。

          在java1.5中,Comparable接口被修改為范型的。大多數(shù)的類都實(shí)現(xiàn)了這個(gè)接口,考慮一下Integer:

          public final class Integer extends Number implements Comparable


          原先的Comparable接口在類型安全方面是有問題的。兩個(gè)繼承了Comparable接口的對(duì)象可能不能相互比較。JDK5.0前,非范形的Comparable接口是非常有用但是不安全的,而現(xiàn)在的接口,捕獲了我們需要的信息:他告訴我們一個(gè)對(duì)象是可比較的,并且可以同什么比較。

          現(xiàn)在,考慮一下comparable類的子類。Integer是final的,所以不能有子類,那么讓我們看看java.math.BigInteger:

          public class BigInteger extends Number implements Comparable


          如果我們實(shí)現(xiàn)一個(gè)BiggerInteger類是BigInteger的子類,他從父類那里繼承了Comparable接口,但是注意繼承的是Comparable而不是Comparable。這意味著BigInteger和BiggerInteger是可以相互比較的,這是非常好的。BiggerInteger可以重載compareTo(),但是不允許實(shí)現(xiàn)一個(gè)不同的參數(shù)化的Comparable。這就是說BiggerInteger不能同時(shí)繼承BigInteger和實(shí)現(xiàn)Comparable

          當(dāng)你使用可比較的對(duì)象時(shí)(當(dāng)寫排序算法的時(shí)候)記住兩點(diǎn)。首先,使用原生類型是不夠充分的:考慮到類型安全,必須指明同什么比較。接下來,類型是不允許同自己比較的:有時(shí)候他會(huì)同他的祖先比較。為了具體說明,考慮java.util.Collections.max():

          這是一個(gè)冗長(zhǎng)而且復(fù)雜的方法標(biāo)簽,我們來一步步考慮:
          方法中包含一個(gè)類型變量T,并且有復(fù)雜的綁定,稍后我們返回來討論。
          方法的返回值類型是T。
          方法名是max()。

          方法的參數(shù)是一個(gè)集合。元素的類型指定為綁定的通配符。我們并不知道元素的確切類型,但直到有一個(gè)上限T。所以我們知道元素的類型要么為T,要么是T的子類。集合的任何元素都可以作為返回值使用。

          這些是比較簡(jiǎn)單的,本章我們已經(jīng)看到了通配符上界,我們?cè)賮砜纯磎ax()中的類型變量聲明:

          >


          要說明的第一點(diǎn),T必須實(shí)現(xiàn)了Comparable接口。(范型的語(yǔ)法使用關(guān)鍵字extends來代表類型綁定,不論是類或接口)這是期望的,因?yàn)檫@個(gè)方法是找到集合中最大的元素。但是觀察這個(gè)參數(shù)化的Comparable接口,這是一個(gè)通配符,但是這個(gè)通過關(guān)鍵字super來綁定,而不是extends。這是下界綁定。? extends T是我們熟悉的上界綁定:這意味著T或者其子類。? super T比較少用:這意味著T或者他的超類。

          總結(jié)一下,類型變量聲明表明:“T是一個(gè)實(shí)現(xiàn)了Comparable接口或者他的父類實(shí)現(xiàn)了該接口的類型。”Collections.min()和Collections.binarySearch()有著相同的聲明。
          對(duì)其他的下界通配符(對(duì)于Comparable接口沒有作用)的例子,Collections中的addAll(),copy(),和fill()。觀察addAll()的聲明:

          public static  boolean addAll(Collection<? super T> c, T... a)


          這是一個(gè)varargs方法,接受任意數(shù)量的參數(shù),并且傳遞給他們一個(gè)T[],命名為a。他將a中的所有元素都賦給集合c。集合的元素類型雖然不知道,但是有一個(gè)下界:元素均為T或者T的超類。不論類型是什么,我們可以確定數(shù)組的元素都是類型的實(shí)例,所以將數(shù)組的元素添加到集合中是合法的。

          返回到我們先前討論的上界通配符,如果有一個(gè)集合的元素是上界通配符,那么都是只讀的。考慮List<? extends Serializable>。我們知道,所有的元素都是Serializable,所以像get()這樣的方法返回一個(gè)Serializable類型的返回值。編譯器不允許我們調(diào)用add()這樣的方法,因?yàn)閷?shí)際的元素類型是不可知的。不能夠添加絕對(duì)的Serializable對(duì)象到list中,因?yàn)閷?shí)現(xiàn)他們的類可能不是正確的類型。

          既然上界統(tǒng)配符的結(jié)果是只讀的,所以你可能會(huì)期望下界通配符來實(shí)現(xiàn)只寫的集合。實(shí)際并不是這樣,假設(shè)這里有一個(gè)List<? extends Integer>。元素的實(shí)際類型是不知道的,但是可能性是Integer或者他的祖先類Number和Object。無(wú)論實(shí)際類型是什么,將Integer類型(而不是Number和Object對(duì)象)的元素添加到list中是安全的。無(wú)論實(shí)際類型是什么,list中所有元素都是Object對(duì)象的實(shí)例,所以list中像get()一樣的方法返回Object。

          最后,讓我們把注意力放到j(luò)ava.lang.Enum類。Enum是所有枚舉類型的父類,它實(shí)現(xiàn)了Comparable接口,但是有一個(gè)讓人迷惑的范型聲明方法:

          public class Enum> implements Comparable, Serializable


          第一眼,類型變量E的聲明在一個(gè)循環(huán)中。再仔細(xì)的看一看:聲明真正說明了,Enum必須是一個(gè)本身就是Enum類型的類型。這種表面上的循環(huán)是很顯然的,如果我們看到了implements子句。正如我們看到的,Comparable類通常被定義為可以同自己比較的。而且他們的子類也可以同他們的父類比較。從另一個(gè)方面將,Enum實(shí)現(xiàn)了Comparable接口不是為了他本身,而是為了他的子類E。


          資源:
          ·Onjava.com:Onjava.com
          ·Matrix-Java開發(fā)者社區(qū):http://www.matrix.org.cn/


          Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd
          Java中的泛型 第二部分

          posted on 2009-12-14 22:55 ammay 閱讀(249) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 定结县| 聂拉木县| 砀山县| 武城县| 孟州市| 桂平市| 菏泽市| 临潭县| 宣威市| 达拉特旗| 彰武县| 三原县| 太原市| 安徽省| 若尔盖县| 和平区| 偏关县| 岳阳县| 惠来县| 连州市| 开平市| 大田县| 安义县| 阜新市| 基隆市| 钦州市| 大余县| 邯郸县| 仁布县| 沈丘县| 崇礼县| 南部县| 赤城县| 万盛区| 巨野县| 米易县| 尼玛县| 开原市| 唐河县| 类乌齐县| 苏尼特右旗|