中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

從Java走進Scala如何使用元組、數組和列表

發布時間:2021-11-03 11:40:20 來源:億速云 閱讀:178 作者:小新 欄目:編程語言

這篇文章主要介紹了從Java走進Scala如何使用元組、數組和列表,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

使用 Option(s)

在什么情況下,“無” 并不代表 “什么也沒有”?當它為 0 的時候,與 null 有什么關系。

對于我們大多數人都非常熟悉的概念,要在軟件中表示為 “無” 是一件十分困難的事。例如,看看 C++ 社區中圍繞 NULL 和 0 進行的激烈討論,或是 SQL 社區圍繞 NULL 列值展開的爭論,便可知曉一二。 NULL 或 null 對于大多數程序員來說都表示 “無”,但是這在 Java 語言中引出了一些特殊問題。

考慮一個簡單操作,該操作可以從一些位于內存或磁盤的數據庫查找程序員的薪資:API 允許調用者傳入一個包含程序員名字的 String,這會返回什么呢?從建模角度來看,它應該返回一個 Int,表示程序員的年薪;但是這里有一個問題,如果程序員不在數據庫中(可能根本沒有雇用她,或者已經被解雇,要不就是輸錯了名字……),那么應該返回什么。如果返回類型是 Int,則不能返回 null,這個 “標志” 通常表示沒有在數據庫中找到該用戶(您可能認為應該拋出一個異常,但是大多數時候數據庫丟失值并不能視為異常,因此不應該在這里拋出異常)。

在 Java 代碼中,我們最終將方法標記為返回 java.lang.Integer,這迫使調用者知道方法可以返回 null。自然,我們可以依靠程序員來全面歸檔這個場景,還可以依賴程序員讀取 精心準備的文檔。這類似于:我們可以要求經理傾聽我們反對他們要求的不可能完成的項目期限,然后經理再進一步把我們的反對傳達給上司和用戶。

Scala 提供了一種普通的函數方法,打破了這一僵局。在某些方面,Option 類型或 Option[T],并不重視描述。它是一個具有兩個子類 Some[T] 和 None 的泛型類,用來表示 “無值” 的可能性,而不需要語言類型系統大費周折地支持這個概念。實際上,使用 Option[T] 類型可以使問題更加清晰(下一節將用到)。

在使用 Option[T] 時,關鍵的一點是認識到它實質上是一個大小為 “1” 的強類型集合,使用一個不同的值 None 表示 “nothing” 值的可能性。因此,在這里方法沒有返回 null 表示沒有找到數據,而是進行聲明以返回 Option[T],其中 T 是返回的原始類型。那么,對于沒有查找到數據的場景,只需返回 None,如下所示:

清單 1. 準備好踢足球了嗎?

@Test def simpleOptionTest =  {    val footballTeamsAFCEast =      Map("New England" -> "Patriots",          "New York" -> "Jets",          "Buffalo" -> "Bills",          "Miami" -> "Dolphins",          "Los Angeles" -> null)        assertEquals(footballTeamsAFCEast.get("Miami"), Some("Dolphins"))    assertEquals(footballTeamsAFCEast.get("Miami").get(), "Dolphins")    assertEquals(footballTeamsAFCEast.get("Los Angeles"), Some(null))    assertEquals(footballTeamsAFCEast.get("Sacramento"), None)  }

注意,Scala Map 中 get 的返回值實際上并不對應于傳遞的鍵。相反,它是一個 Option[T] 實例,可以是與某個值有關的 Some(),也可以是 None,因此可以很清晰地表示沒有在 map 中找到鍵。如果它可以表示 map 上存在某個鍵,但是有對應的 null 值,這一點特別重要了。比如清單 1 中 Los Angeles 鍵。

通常,當處理 Option[T] 時,程序員將使用模式匹配,這是一個非常函數化的概念,它允許有效地 “啟用” 類型和/或值,更不用說在定義中將值綁定到變量、在 Some() 和 None 之間切換,以及提取 Some 的值(而不需要調用麻煩的 get() 方法)。清單 2 展示了 Scala 的模式匹配:

清單 2. 巧妙的模式匹配

@Test def optionWithPM =  {    val footballTeamsAFCEast =      Map("New England" -> "Patriots",          "New York" -> "Jets",          "Buffalo" -> "Bills",          "Miami" -> "Dolphins")              def show(value : Option[String]) =    {      value match      {        case Some(x) => x        case None => "No team found"     }    }        assertEquals(show(footballTeamsAFCEast.get("Miami")), "Dolphins")  }

C# 2.0 可變為 null 值的類型

其他語言已試圖通過各種方法解決 “可 null 值化” 問題:C++ 一直都忽略了這個問題,直至最后確定 null 和 0 是不同的值。Java 語言仍然沒有徹底解決這個問題,而是依賴于自動裝箱(autobox)— 將原語類型自動轉換為它們的包裝器對象(在 1.1 以后引入)— 幫助 Java 程序員解決問題。一些模式愛好者建議每種類型都應該有一個對應的 “Null Object”,即將自己的所有方法重寫為不執行任何操作的類型(實際上是子類型)的實例 — 實踐證明這需要大量工作。C# 1.0 發布后,C# 設計者決定采取一種完全不同的方法解決 null 值化問題。

C# 2.0 引入了可變為 null 值的類型 的概念,重要的是添加了語法支持,認為任何特定值類型(基本指原語類型)都可以通過將 null 封裝到一個泛型/模板類 Nullable< T>,從而提供 null 支持。Nullable< T> 本身是在類型聲明中通過 ? 修飾符號引入。因此,int? 表示一個整數也可能為 null。

表面上看,這似乎很合理,但是事情很快就變得復雜起來。int 和 int? 是否應該被視為可兼容類型,如果是的話,什么時候將 int 提升為 int?,反之呢?當將 int 添加到 int? 會發生什么,結果會是 null 嗎?這類問題等等。隨后類型系統進行了一些重要的調整,可變為 null 值的類型隨后包含到了 2.0 中 — 而 C# 程序員幾乎完全忽略了它們。

回顧一下 Option 類型的函數方法,它使 Option[T] 和 Int 之間的界限變得很清晰,看上去要比其他方法更加簡單。在那些圍繞可變為 null 值類型的反直覺(counterintuitive)提升規則之間進行比較時,尤其如此。(函數領域對該問題近二十年的思考是值得的)。要使用 Option[T] 必須付出一些努力,但是總的來說,它產生了更清晰的代碼和期望。

元組和集合

在 C++ 中,我們將之稱為結構體。在 Java 編程中,我們稱之為數據傳輸對象或參數對象。在 Scala 中,我們稱為元組。實質上,它們是一些將其他數據類型收集到單個實例的類,并且不使用封裝或抽象 — 實際上,不 使用任何抽象常常更有用。

在 Scala 創建一個元組類型非常的簡單,這只是主體的一部分:如果首先將元素公開給外部,那么在類型內部創建描述這些元素的名稱就毫無價值。考慮清單 3:

清單 3. tuples.scala

// JUnit test suite  //  class TupleTest  {    import org.junit._, Assert._    import java.util.Date       @Test def simpleTuples() =    {      val tedsStartingDateWithScala = Date.parse("3/7/2006")       val tuple = ("Ted", "Scala", tedsStartingDateWithScala)            assertEquals(tuple._1, "Ted")      assertEquals(tuple._2, "Scala")      assertEquals(tuple._3, tedsStartingDateWithScala)    }  }

創建元組非常簡單,將值放入一組圓括號內,就好象調用一個方法調用一樣。提取這些值只需要調用 “_n” 方法,其中 n 表示相關的元組元素的位置參數:_1 表示第一位,_2 表示第二位,依此類推。傳統的 Java java.util.Map 實質上是一個分兩部分的元組集合。

元組可以輕松地實現使用單個實體移動多個值,這意味著元組可以提供在 Java 編程中非常重量級的操作:多個返回值。例如,某個方法可以計算 String 中字符的數量,并返回該 String 中出現次數最多的字符,但是如果程序員希望同時 返回最常出現的字符和 它出現的次數,那么程序設計就有點復雜了:或是創建一個包含字符及其出現次數的顯式類,或將值作為字段保存到對象中并在需要時返回字段值。無論使用哪種方法,與使用 Scala 相比,都需要編寫大量代碼;通過簡單地返回包含字符及其出現次數的元組,Scala 不僅可以輕松地使用 “_1”、“_2” 等訪問元組的各個值,還可以輕松地返回多個返回值。

如下節所示,Scala 頻繁地將 Option 和元組保存到集合(例如 Array[T] 或列表)中,從而通過一個比較簡單的結構提供了極大的靈活性和威力。

數組帶您走出陰霾

讓我們重新審視一個老朋友 — 數組 — 在 Scala 中是 Array[T]。和 Java 代碼中的數組一樣,Scala 的 Array[T] 是一組有序的元素序列,使用表示數組位置的數值進行索引,并且該值不可以超過數組的總大小,如清單 4 所示:

清單 4. array.scala

object ArrayExample1  {    def main(args : Array[String]) : Unit =    {      for (i <- 0 to args.length-1)      {        System.out.println(args(i))      }    }  }

盡管等同于 Java 代碼中的數組(畢竟后者是最終的編譯結果),Scala 中的數組使用了截然不同的定義。對于新手,Scala 中的數組實際上就是泛型類,沒有增加 “內置” 狀態(至少,不會比 Scala 庫附帶的其他類多)。例如,在 Scala 中,數組一般定義為 Array[T] 的實例,這個類定義了一些額外的有趣方法,包括常見的 “length” 方法,它將返回數組的長度。因此,在 Scala 中,可以按照傳統意義使用 Array,例如使用 Int 在 0 到 args.length - 1 間進行迭代,并獲取數組的第 i 個元素(使用圓括號而不是方括號來指定返回哪個元素,這是另一種名稱比較有趣的方法)。 

擴展數組

事實證明 Array 擁有大量方法,這些方法繼承自一個非常龐大的 parent 層次結構:Array 擴展 Array0,后者擴展 ArrayLike[A],ArrayLike[A] 擴展 Mutable[A],Mutable[A] 又擴展 RandomAccessSeq[A],RandomAccessSeq[A] 擴展了 Seq[A],等等。實際上,這種層次結構意味著 Array 可以執行很多操作,因此與 Java 編程相比,在 Scala 中可以更輕松地使用數組。

例如,如清單 4 所示,使用 foreach 方法遍歷數組更加簡單并且更貼近函數的方式,這些都繼承自 Iterable 特性:

清單 5. ArrayExample2

object   {    def main(args : Array[String]) : Unit =    {      args.foreach( (arg) => System.out.println(arg) )    }  }

看上去您沒有節省多少工作,但是,將一個函數(匿名或其他)傳入到另一個類中以便獲得在特定語義下(在本例中指遍歷數組)執行的能力,是函數編程的常見主題。以這種方式使用更高階函數并不局限于迭代;事實上,還得經常對數組內容執行一些過濾 操作去掉無用的內容,然后再處理結果。例如,在 Scala 中,可以輕松地使用 filter 方法進行過濾,然后獲取結果列表并使用 map 和另一個函數(類型為 (T) => U,其中 T 和 U 都是泛型類型),或 foreach 來處理每個元素。我在清單 6 中采取了后一種方法(注意 filter 使用了一個 (T) : Boolean 方法,意味著使用數組持有的任意類型的參數,并返回一個 Boolean)。

清單 6. 查找所有 Scala 程序員

class ArrayTest  {    import org.junit._, Assert._        @Test def testFilter =    {      val programmers = Array(          new Person("Ted", "Neward", 37, 50000,            Array("C++", "Java", "Scala", "Groovy", "C#", "F#", "Ruby")),          new Person("Amanda", "Laucher", 27, 45000,            Array("C#", "F#", "Java", "Scala")),          new Person("Luke", "Hoban", 32, 45000,            Array("C#", "Visual Basic", "F#")),    new Person("Scott", "Davis", 40, 50000,      Array("Java", "Groovy"))        )       // 查找所有Scala程序員 ...      val scalaProgs =        programmers.filter((p) => p.skills.contains("Scala") )            // 應該只有2      assertEquals(2, scalaProgs.length)            // ... now perform an operation on each programmer in the resulting      // array of Scala programmers (give them a raise, of course!)      //      scalaProgs.foreach((p) => p.salary += 5000)            // Should each be increased by 5000 ...      assertEquals(programmers(0).salary, 50000 + 5000)      assertEquals(programmers(1).salary, 45000 + 5000)            // ... except for our programmers who don't know Scala      assertEquals(programmers(2).salary, 45000)   assertEquals(programmers(3).salary, 50000)    }  }

創建一個新的 Array 時將用到 map 函數,保持原始的數組內容不變,實際上大多數函數性程序員都喜歡這種方式:

清單 7. Filter 和 map

@Test def testFilterAndMap =  {    val programmers = Array(        new Person("Ted", "Neward", 37, 50000,          Array("C++", "Java", "Scala", "C#", "F#", "Ruby")),        new Person("Amanda", "Laucher", 27, 45000,          Array("C#", "F#", "Java", "Scala")),        new Person("Luke", "Hoban", 32, 45000,          Array("C#", "Visual Basic", "F#"))  new Person("Scott", "Davis", 40, 50000,    Array("Java", "Groovy"))      )     // Find all the Scala programmers ...    val scalaProgs =      programmers.filter((p) => p.skills.contains("Scala") )        // Should only be 2    assertEquals(2, scalaProgs.length)        // ... now perform an operation on each programmer in the resulting    // array of Scala programmers (give them a raise, of course!)    //    def raiseTheScalaProgrammer(p : Person) =    {      new Person(p.firstName, p.lastName, p.age,        p.salary + 5000, p.skills)    }    val raisedScalaProgs =       scalaProgs.map(raiseTheScalaProgrammer)        assertEquals(2, raisedScalaProgs.length)    assertEquals(50000 + 5000, raisedScalaProgs(0).salary)    assertEquals(45000 + 5000, raisedScalaProgs(1).salary)  }

注意,在清單 7 中,Person 的 salary 成員可以標記為 “val”,表示不可修改,而不是像上文一樣為了修改不同程序員的薪資而標記為 “var”。

Scala 的 Array 提供了很多方法,在這里無法一一列出并演示。總的來說,在使用數組時,應該充分地利用 Array 提供的方法,而不是使用傳統的 for ... 模式遍歷數組并查找或執行需要的操作。最簡單的實現方法通常是編寫一個函數(如果有必要的話可以使用嵌套,如清單 7 中的 testFilterAndMap 示例所示),這個函數可以執行所需的操作,然后根據期望的結果將該函數傳遞給 Array 中的 map、filter、foreach 或其他方法之一。

函數性列表

函數編程多年來的一個核心特性就是列表,它和數組在對象領域中享有相同級別的 “內置” 性。列表對于構建函數性軟件非常關鍵,因此,您(作為一名剛起步的 Scala 程序員)必須能夠理解列表及其工作原理。即使列表從未形成新的設計,但是 Scala 代碼在其庫中廣泛使用了列表。因此學習列表是非常必要的。

在 Scala 中,列表類似于數組,因為它的核心定義是 Scala 庫中的標準類 List[T]。并且,和 Array[T] 相同,List[T] 繼承了很多基類和特性,首先使用 Seq[T] 作為直接上層基類。

基本上,列表是一些可以通過列表頭或列表尾提取的元素的集合。列表來自于 Lisp,后者是一種主要圍繞 “LISt 處理” 的語言,它通過 car 操作獲得列表的頭部,通過 cdr 操作獲得列表尾部(名稱淵源與歷史有關;第一個可以解釋它的人有獎勵)。

從很多方面來講,使用列表要比使用數組簡單,原因有二,首先函數語言過去一直為列表處理提供了良好的支持(而 Scala 繼承了這些支持),其次可以很好地構成和分解列表。例如,函數通常從列表中挑選內容。為此,它將選取列表的第一個元素 — 列表頭部 — 來對該元素執行處理,然后再遞歸式地將列表的其余部分傳遞給自身。這樣可以極大減少處理代碼內部具有相同共享狀態的可能性,并且,假如每個步驟只需處理一個元素,極有可能使代碼分布到多個線程(如果處理是比較好的)。

構成和分解列表非常簡單,如清單 8 所示:

清單 8. 使用列表

class ListTest  {    import org.junit._, Assert._        @Test def simpleList =    {      val myFirstList = List("Ted", "Amanda", "Luke")            assertEquals(myFirstList.isEmpty, false)      assertEquals(myFirstList.head, "Ted")      assertEquals(myFirstList.tail, List("Amanda", "Luke")      assertEquals(myFirstList.last, "Luke")    }  }

注意,構建列表與構建數組十分相似;都類似于構建一個普通對象,不同之處是這里不需要 “new”(這是 “case 類” 的功能,我們將在未來的文章中介紹到)。請進一步注意 tail 方法調用的結果 — 結果并不是列表的最后一個元素(通過 last 提供),而是除第一個元素以外的其余列表元素。

當然,列表的強大力量部分來自于遞歸處理列表元素的能力,這表示可以從列表提取頭部,直到列表為空,然后累積結果:

清單 9. 遞歸處理

@Test def recurseList =  {    val myVIPList = List("Ted", "Amanda", "Luke", "Don", "Martin")        def count(VIPs : List[String]) : Int =    {      if (VIPs.isEmpty)        0     else       count(VIPs.tail) + 1   }        assertEquals(count(myVIPList), myVIPList.length)  }

注意,如果不考慮返回類型 count,Scala 編譯器或解釋器將會出現點麻煩 — 因為這是一個尾遞歸(tail-recursive)調用,旨在減少在大量遞歸操作中創建的棧幀的數量,因此需要指定它的返回類型。即使是這樣,也可以輕松地使用 List 的 “length” 成員獲取列表項的數量,但關鍵是如何解釋列表處理強大的功能。清單 9 中的整個方法完全是線程安全的,因為列表處理中使用的整個中間狀態保存在參數的堆棧上。因此,根據定義,它不能被多個線程訪問。函數性方法的一個優點就是它實際上與程序功能截然不同,并且仍然創建共享的狀態。

列表 API

列表具有另外一些有趣的特性,例如構建列表的替代方法,使用 :: 方法(是的,這是一種方法。只不過名稱比較有趣)。因此,不必使用 “List” 構造函數語法構建列表,而是將它們 “拼接” 在一起(在調用 :: 方法時),如清單 10 所示:

清單 10. 是 :: == C++ 嗎?

@Test def recurseConsedList =  {    val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil        def count(VIPs : List[String]) : Int =    {      if (VIPs.isEmpty)        0     else       count(VIPs.tail) + 1   }        assertEquals(count(myVIPList), myVIPList.length)  }

在使用 :: 方法時要小心 — 它引入了一些很有趣的規則。它的語法在函數語言中非常常見,因此 Scala 的創建者選擇支持這種語法,但是要正確、普遍地使用這種語法,必須使用一種比較古怪的規則:任何以冒號結束的 “名稱古怪的方法” 都是右關聯(right-associative)的,這表示整個表達式從它的最右邊的 Nil 開始,它正好是一個 List。因此,可以將 :: 認定為一個全局的 :: 方法,與 String 的一個成員方法(本例中使用)相對;這又表示您可以對所有內容構建列表。在使用 :: 時,最右邊的元素必須是一個列表,否則將得到一個錯誤消息。

什么是右關聯?

要更好地理解 :: 方法,要記住 “冒號” 這類操作符僅僅是一些名稱比較有趣的方法。對于普通的左管理語法,左側的標記一般是我將要對其調用方法名(右側的標記)的對象。因此,通常來說,表達式 1 + 2 在編譯器看來等同于 1.+(2)。

但是對于列表而言,這些都不適合 — 系統中的每個類都需要對系統中的所有類型使用 :: 方法,而這嚴重違背了關注點分離原則。

Scala 的修復方法是:以冒號結束的任何具有奇怪名稱的方法(例如 :: 或 :::,甚至是我自己創建的方法,比如 foo:)都是右關聯的。因此,比方說,a :: b :: c :: Nil 轉換為 Nil.::(c.::(b.::(a))),后者正是我需要的:List 在首位,這樣每次調用 :: 都可以獲取對象參數并返回一個 List,并繼續執行下去。

最好為其他命名約定指定右關聯屬性,但是在撰寫本文之際,Scala 已將這條規則硬編碼到該語言中。就目前來說,冒號是惟一觸發右關聯行為的字符。

在 Scala 中,列表的一種最強大的用法是與模式匹配結合。由于列表不僅可以匹配類型和值,它還可以同時綁定變量。例如,我可以簡化清單 10 的列表代碼,方法是使用模式匹配區別一個至少具有一個元素的列表和一個空列表:

清單 11. 結合使用模式匹配和列表

@Test def recurseWithPM =  {    val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil        def count(VIPs : List[String]) : Int =    {      VIPs match      {        case h :: t => count(t) + 1       case Nil => 0     }    }        assertEquals(count(myVIPList), myVIPList.length)  }

在第一個 case 表達式中,將提取列表頭部并綁定到變量 h,而其余部分(尾部)則綁定到 t;在本例中,沒有對 h 執行任何操作(實際上,更好的方法是指明這個頭部永遠不會被使用,方法是使用一個通配符 _ 代替 h,這表明它是永遠不會使用到的變量的占位符)。但是 t 被遞歸地傳遞給 count,和前面的示例一樣。還要注意,Scala 中的每一個表達式將隱式返回一個值;在本例中,模式匹配表達式的結果是遞歸調用 count + 1,當達到列表結尾時,結果為 0。

考慮到相同的代碼量,使用模式匹配的價值體現在哪里?實際上,對于比較簡單的代碼,模式匹配的價值不很明顯。但是對于稍微復雜的代碼,例如擴展示例以匹配特定值,那么模式匹配非常有幫助。

清單 12. 模式匹配

@Test def recurseWithPMAndSayHi =  {    val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil        var foundAmanda = false   def count(VIPs : List[String]) : Int =    {      VIPs match      {        case "Amanda" :: t =>          System.out.println("Hey, Amanda!"); foundAmanda = true; count(t) + 1       case h :: t =>          count(t) + 1       case Nil =>          0     }    }        assertEquals(count(myVIPList), myVIPList.length)    assertTrue(foundAmanda)  }

示例很快會變得非常復雜,特別是正則表達式或 XML 節點,開始大量使用模式匹配方法。模式匹配的使用同樣不局限于列表;我們沒有理由不把它擴展到前面的數組示例中。事實上,以下是前面的 recurseWithPMAndSayHi 測試的數組示例:

清單 13. 將模式匹配擴展到數組

@Test def recurseWithPMAndSayHi =  {    val myVIPList = Array("Ted", "Amanda", "Luke", "Don", "Martin")     var foundAmanda = false       myVIPList.foreach((s) =>      s match      {        case "Amanda" =>          System.out.println("Hey, Amanda!")          foundAmanda = true       case _ =>          ; // Do nothing      }    )     assertTrue(foundAmanda)  }

如果希望進行實踐,那么嘗試構建清單 13 的遞歸版本,但這不用在 recurseWithPMAndSayHi 范圍內聲明一個可修改的 var。提示:需要使用多個模式匹配代碼塊

感謝你能夠認真閱讀完這篇文章,希望小編分享的“從Java走進Scala如何使用元組、數組和列表”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

榆林市| 靖江市| 怀宁县| 历史| 临漳县| 玉山县| 陆河县| 汕尾市| 丰顺县| 繁峙县| 宜宾市| 洪雅县| 渝北区| 阜新市| 理塘县| 扎鲁特旗| 和顺县| 礼泉县| 雅安市| 新竹县| 盱眙县| 凤庆县| 和林格尔县| 桑植县| 彝良县| 肇庆市| 昭平县| 盐池县| 定襄县| 榆社县| 丹凤县| 涪陵区| 荆门市| 北安市| 宣武区| 永登县| 黎城县| 慈利县| 万宁市| 辽阳市| 鄄城县|