精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

從Java走進(jìn)Scala:Scitter庫(kù)的增強(qiáng)

開發(fā) 后端
Scitter是用于訪問(wèn)Twitter的一個(gè)Scala庫(kù)。本文進(jìn)一步講述Scitter庫(kù)一組更有趣也更有用的特性。對(duì)于想在實(shí)踐中學(xué)習(xí)Scala的開發(fā)者而言,Scitter是絕佳的學(xué)習(xí)對(duì)象。

現(xiàn)在對(duì)于Scala而言,Twitter是一個(gè)很好的學(xué)習(xí)對(duì)象。在之前一篇文章中,Ted已經(jīng)談到了 Twitter,這個(gè)微博客站點(diǎn)目前正引起社會(huì)性網(wǎng)絡(luò)的極大興趣,我們還談到它的基于 XML-/REST 的 API 如何使它成為開發(fā)人員進(jìn)行研究和探索的一個(gè)有趣平臺(tái)。為此,我們首先充實(shí)了“Scitter” 的基本結(jié)構(gòu),Scitter 是用于訪問(wèn) Twitter 的一個(gè) Scala 庫(kù)。更多來(lái)自IBMDW的Scala文章,請(qǐng)參考從Java走進(jìn)Scala(Scala經(jīng)典讀物)

我們對(duì)于 Scitter 有幾個(gè)目標(biāo):

  1. 簡(jiǎn)化 Twitter 訪問(wèn),比過(guò)去打開 HTTP 連接然后 “手動(dòng)” 執(zhí)行操作更容易。
  2. 可以從 Java 客戶機(jī)輕松訪問(wèn)它。
  3. 輕松模擬以便進(jìn)行測(cè)試。

在這一期,我們不必完成整個(gè) Twitter API,但是我們將完成一些核心部分,目的是讓這個(gè)庫(kù)達(dá)到公共源代碼控制庫(kù)的程度,便于其他人來(lái)完成這項(xiàng)工作。

到目前為止:Scitter 0.1

首先我們簡(jiǎn)單回顧一下到目前為止我們所處的階段:

清單 1. Scitter v0.1

				
package com.tedneward.scitter
{
  import org.apache.commons.httpclient._, auth._, methods._, params._
  import scala.xml._

  /**
   * Status message type. This will typically be the most common message type
   * sent back from Twitter (usually in some kind of collection form). Note
   * that all optional elements in the Status type are represented by the
   * Scala Option[T] type, since that's what it's there for.
   */
  abstract class Status
  {
    /**
     * Nested User type. This could be combined with the top-level User type,
     * if we decide later that it's OK for this to have a boatload of optional
     * elements, including the most-recently-posted status update (which is a
     * tad circular).
     */
    abstract class User
    {
      val id : Long
      val name : String
      val screenName : String
      val description : String
      val location : String
      val profileImageUrl : String
      val url : String
      val protectedUpdates : Boolean
      val followersCount : Int
    }
    /**
     * Object wrapper for transforming (format) into User instances.
     */
    object User
    {
      /*
      def fromAtom(node : Node) : Status =
      {
      
      }
      */
      /*
      def fromRss(node : Node) : Status =
      {
      
      }
      */
      def fromXml(node : Node) : User =
      {
        new User {
          val id = (node \ "id").text.toLong
          val name = (node \ "name").text
          val screenName = (node \ "screen_name").text
          val description = (node \ "description").text
          val location = (node \ "location").text
          val profileImageUrl = (node \ "profile_image_url").text
          val url = (node \ "url").text
          val protectedUpdates = (node \ "protected").text.toBoolean
          val followersCount = (node \ "followers_count").text.toInt
        }
      }
    }
  
    val createdAt : String
    val id : Long
    val text : String
    val source : String
    val truncated : Boolean
    val inReplyToStatusId : Option[Long]
    val inReplyToUserId : Option[Long]
    val favorited : Boolean
    val user : User
  }
  /**
   * Object wrapper for transforming (format) into Status instances.
   */
  object Status
  {
    /*
    def fromAtom(node : Node) : Status =
    {
    
    }
    */
    /*
    def fromRss(node : Node) : Status =
    {
    
    }
    */
    def fromXml(node : Node) : Status =
    {
      new Status {
        val createdAt = (node \ "created_at").text
        val id = (node \ "id").text.toLong
        val text = (node \ "text").text
        val source = (node \ "source").text
        val truncated = (node \ "truncated").text.toBoolean
        val inReplyToStatusId =
          if ((node \ "in_reply_to_status_id").text != "")
            Some((node \"in_reply_to_status_id").text.toLong)
          else
            None
        val inReplyToUserId = 
          if ((node \ "in_reply_to_user_id").text != "")
            Some((node \"in_reply_to_user_id").text.toLong)
          else
            None
        val favorited = (node \ "favorited").text.toBoolean
        val user = User.fromXml((node \ "user")(0))
      }
    }
  }


  /**
   * Object for consuming "non-specific" Twitter feeds, such as the public timeline.
   * Use this to do non-authenticated requests of Twitter feeds.
   */
  object Scitter
  {
    /**
     * Ping the server to see if it's up and running.
     *
     * Twitter docs say:
     * test
     * Returns the string "ok" in the requested format with a 200 OK HTTP status code.
     * URL: http://twitter.com/help/test.format
     * Formats: xml, json
     * Method(s): GET
     */
    def test : Boolean =
    {
      val client = new HttpClient()

      val method = new GetMethod("http://twitter.com/help/test.xml")

      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))

      client.executeMethod(method)
      
      val statusLine = method.getStatusLine()
      statusLine.getStatusCode() == 200
    }
    /**
     * Query the public timeline for the most recent statuses.
     *
     * Twitter docs say:
     * public_timeline
     * Returns the 20 most recent statuses from non-protected users who have set
     * a custom user icon.  Does not require authentication.  Note that the
     * public timeline is cached for 60 seconds so requesting it more often than
     * that is a waste of resources.
     * URL: http://twitter.com/statuses/public_timeline.format
     * Formats: xml, json, rss, atom
     * Method(s): GET
     * API limit: Not applicable
     * Returns: list of status elements     
     */
    def publicTimeline : List[Status] =
    {
      import scala.collection.mutable.ListBuffer
    
      val client = new HttpClient()

      val method = new GetMethod("http://twitter.com/statuses/public_timeline.xml")

      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))

      client.executeMethod(method)
      
      val statusLine = method.getStatusLine()
      if (statusLine.getStatusCode() == 200)
      {
        val responseXML =
          XML.loadString(method.getResponseBodyAsString())

        val statusListBuffer = new ListBuffer[Status]

        for (n <- (responseXML \\ "status").elements)
          statusListBuffer += (Status.fromXml(n))
        
        statusListBuffer.toList
      }
      else
      {
        Nil
      }
    }
  }
  /**
   * Class for consuming "authenticated user" Twitter APIs. Each instance is
   * thus "tied" to a particular authenticated user on Twitter, and will
   * behave accordingly (according to the Twitter API documentation).
   */
  class Scitter(username : String, password : String)
  {
    /**
     * Verify the user credentials against Twitter.
     *
     * Twitter docs say:
     * verify_credentials
     * Returns an HTTP 200 OK response code and a representation of the
     * requesting user if authentication was successful; returns a 401 status
     * code and an error message if not.  Use this method to test if supplied
     * user credentials are valid.
     * URL: http://twitter.com/account/verify_credentials.format
     * Formats: xml, json
     * Method(s): GET
     */
    def verifyCredentials : Boolean =
    {
      val client = new HttpClient()

      val method = new GetMethod("http://twitter.com/help/test.xml")

      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
      client.getParams().setAuthenticationPreemptive(true)
      val creds = new UsernamePasswordCredentials(username, password)
      client.getState().setCredentials(
        new AuthScope("twitter.com", 80, AuthScope.ANY_REALM), creds)

      client.executeMethod(method)
      
      val statusLine = method.getStatusLine()
      statusLine.getStatusCode() == 200
    }
  }
}

代碼有點(diǎn)長(zhǎng),但是很容易分為幾個(gè)基本部分:

  1. case 類 UserStatus,表示 Twitter 在對(duì) API 調(diào)用的響應(yīng)中發(fā)回給客戶機(jī)的基本類型,包括用于構(gòu)造或提取 XML 的一些方法。
  2. 一個(gè) Scitter 獨(dú)立對(duì)象,處理那些不需要對(duì)用戶進(jìn)行驗(yàn)證的操作。
  3. 一個(gè) Scitter 實(shí)例(用 username 和 password 參數(shù)化),用于那些需要對(duì)用戶執(zhí)行驗(yàn)證的操作。

到目前為止,對(duì)于這兩種 Scitter 類型,我們只談到了測(cè)試、verifyCredentials 和 public_timeline API。雖然這些有助于確定 HTTP 訪問(wèn)的基礎(chǔ)(使用 Apache HttpClient 庫(kù))可以工作,并且我們將 XML 響應(yīng)轉(zhuǎn)換成 Status 對(duì)象的基本方式也是可行的,但是現(xiàn)在我們甚至不能進(jìn)行基本的 “我的朋友在說(shuō)什么” 的公共時(shí)間線查詢,也沒(méi)有采取過(guò)基本的措施來(lái)防止代碼庫(kù)中出現(xiàn) “重復(fù)” 問(wèn)題,更不用說(shuō)尋找一些方法來(lái)模擬用于測(cè)試的網(wǎng)絡(luò)訪問(wèn)代碼。

顯然,在這一期我們有很多事情要做。

#p#

連接

對(duì)于代碼,***件讓我煩惱的事就是,我在 Scitter 對(duì)象和類的每個(gè)方法中都重復(fù)這樣的操作序列:創(chuàng)建 HttpClient 實(shí)例,對(duì)它進(jìn)行初始化,用必要的驗(yàn)證參數(shù)對(duì)它進(jìn)行參數(shù)化,等等。當(dāng)它們只有 3 個(gè)方法時(shí),可以進(jìn)行管理,但是顯然不易于伸縮,而且以后還會(huì)增加很多方法。此外,以后重新在那些方法中引入模擬和/或本地/離線測(cè)試功能將十分困難。所以我們要解決這個(gè)問(wèn)題。

實(shí)際上,我們這里介紹的并不是 Scala 本身,而是不要重復(fù)自己(Don't-Repeat-Yourself)的思想。因此,我將從基本的面向?qū)ο蠓椒ㄩ_始:創(chuàng)建一個(gè) helper 方法,用于做實(shí)際工作:

清單 2. 對(duì)代碼庫(kù)執(zhí)行 DRY 原則

				
package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    // ...
    private[scitter] def exec ute(url : String) =
    {
      val client = new HttpClient()
      val method = new GetMethod(url)
      
      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
      client.executeMethod(method)
      
      (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
    }
  }
}

注意兩點(diǎn):首先,我從 execute() 方法返回一個(gè)元組,其中包含狀態(tài)碼和響應(yīng)主體。這正是讓元組成為語(yǔ)言中固有的一部分的一個(gè)強(qiáng)大之處,因?yàn)閷?shí)際上很容易從一個(gè)方法調(diào)用返回多個(gè)返回值。當(dāng)然,在 Java 代碼中,也可以通過(guò)創(chuàng)建包含元組元素的***或嵌套類來(lái)實(shí)現(xiàn)這一點(diǎn),但是這需要一整套專用于這一個(gè)方法的代碼。此外,本來(lái)也可以返回一個(gè)包含 String 鍵和 Object 值的 Map,但是那樣就在很大程度上喪失了類型安全性。元組并不是一個(gè)非常具有變革性的特性,它只不過(guò)是又一個(gè)使 Scala 成為強(qiáng)大語(yǔ)言的優(yōu)秀特性。

由于使用元組,我需要使用 Scala 的另一個(gè)特色語(yǔ)法將兩個(gè)結(jié)果都捕捉到本地變量中,就像下面這個(gè)重寫后的 Scitter.test 那樣:

清單 3. 這符合 DRY 原則嗎?

				
package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    /**
     * Ping the server to see if it's up and running.
     *
     * Twitter docs say:
     * test
     * Returns the string "ok" in the requested format with a 200 OK HTTP status code.
     * URL: http://twitter.com/help/test.format
     * Formats: xml, json
     * Method(s): GET
     */
    def test : Boolean =
    {
      val (statusCode, statusBody) =
        execute("http://twitter.com/statuses/public_timeline.xml")
      statusCode == 200
    }
  }
}

實(shí)際上,我可以輕松地將 statusBody 全部去掉,并用 _ 替代它,因?yàn)槲覜](méi)有用過(guò)第二個(gè)參數(shù)(test 沒(méi)有返回 statusBody),但是對(duì)于其他調(diào)用將需要這個(gè) statusBody,所以出于演示的目的,我保留了該參數(shù)。

注意,execute() 沒(méi)有泄露任何與實(shí)際 HTTP 通信相關(guān)的細(xì)節(jié) — 這是 Encapsulation 101。這樣便于以后用其他實(shí)現(xiàn)替換 execute()(以后的確要這么做),或者便于通過(guò)重用 HttpClient 對(duì)象來(lái)優(yōu)化代碼,而不是每次重新實(shí)例化新的對(duì)象。

接下來(lái),注意到 execute() 方法在 Scitter 對(duì)象上嗎?這意味著我將可以從不同的 Scitter 實(shí)例中使用它(至少現(xiàn)在可以這樣做,如果以后在 execute() 內(nèi)部執(zhí)行的操作不允許這樣做,則另當(dāng)別論)— 這就是我將 execute() 標(biāo)記為 private[scitter] 的原因,這意味著 com.tedneward.scitter 包中的所有內(nèi)容都可以看到它。

(順便說(shuō)一句,如果還沒(méi)有運(yùn)行測(cè)試的話,那么請(qǐng)運(yùn)行測(cè)試,確保一切運(yùn)行良好。我將假設(shè)我們?cè)谟懻摯a時(shí)您會(huì)運(yùn)行測(cè)試,所以如果我忘了提醒您,并不意味著您也忘記這么做。)

順便說(shuō)一句,對(duì)于經(jīng)過(guò)驗(yàn)證的訪問(wèn),為了支持 Scitter 類,需要一個(gè)用戶名和密碼,所以我將創(chuàng)建一個(gè)重載的 execute() 方法,該方法新增兩個(gè) String 參數(shù):

清單 4. 更加 DRY 化的版本

				
package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    // ...
    private[scitter] def execute(url : String, username : String, password : String) =
    {
      val client = new HttpClient()
      val method = new GetMethod(url)
      
      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
	  client.getParams().setAuthenticationPreemptive(true)
	  client.getState().setCredentials(
		new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
		  new UsernamePasswordCredentials(username, password))
      
      client.executeMethod(method)
      
      (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
    }
  }
}

實(shí)際上,除了驗(yàn)證部分,這兩個(gè) execute() 基本上是做相同的事情,我們可以按照第二個(gè)版本完全重寫***個(gè) execute(),但是要注意,Scala 要求顯式表明重載的 execute() 的返回類型:

清單 5. 放棄 DRY

				
package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    // ...
    private[scitter] def execute(url : String) : (Int, String) =
	  execute(url, "", "")
    private[scitter] def execute(url : String, username : String, password : String) =
    {
      val client = new HttpClient()
      val method = new GetMethod(url)
      
      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
      if ((username != "") && (password != ""))
      {
        client.getParams().setAuthenticationPreemptive(true)
        client.getState().setCredentials(
          new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
            new UsernamePasswordCredentials(username, password))
      }
      
      client.executeMethod(method)
      
      (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
    }
  }
}

到目前為止,一切良好。我們對(duì) Scitter 的通信部分進(jìn)行了 DRY 化處理,接下來(lái)我們轉(zhuǎn)移到下一件事情:獲得朋友的 tweet 的列表。

#p#

連接(到朋友)

Twitter API 表明,friends_timeline API 調(diào)用 “返回認(rèn)證用戶和該用戶的朋友發(fā)表的最近 20 條狀態(tài)”。(它還指出,對(duì)于直接從 Twitter Web 站點(diǎn)使用 Twitter 的用戶,“這相當(dāng)于 Web 上的 ‘/home’”。)對(duì)于任何 Twitter API 來(lái)說(shuō),這是非常基本的要求,所以讓我們將它添加到 Scitter 類中。之所以將它添加到類而不是對(duì)象中,是因?yàn)檎缥臋n中指出的那樣,這是以驗(yàn)證用戶的身份做的事情,而我已經(jīng)決定歸入 Scitter 類,而不是 Scitter 對(duì)象。

但是,這里我們碰到一塊絆腳石:friends_timeline 調(diào)用接受一組 “可選參數(shù)”,包括 since_idmax_idcountpage,以控制返回的結(jié)果。這是一項(xiàng)比較復(fù)雜的操作,因?yàn)?Scala 不像其他語(yǔ)言(例如 Groovy、JRuby 或 JavaScript)那樣原生地支持 “可選參數(shù)” 的思想,但是我們首先來(lái)處理簡(jiǎn)單的東西 — 我們來(lái)創(chuàng)建一個(gè) friendsTimeline 方法,該方法只執(zhí)行一般的、非參數(shù)化的調(diào)用:

清單 6.“告訴我你身邊的朋友是怎樣的...”

				
package com.tedneward.scitter
{
  class Scitter
  {
    def friendsTimeline : List[Status] =
    {
      val (statusCode, statusBody) =
       Scitter.execute("http://twitter.com/statuses/friends_timeline.xml",
                        username, password)

      if (statusCode == 200)
      {
        val responseXML = XML.loadString(statusBody)

        val statusListBuffer = new ListBuffer[Status]

        for (n <- (responseXML \\ "status").elements)
          statusListBuffer += (Status.fromXml(n))
        
        statusListBuffer.toList
      }
      else
      {
        Nil
      }
    }
  }
}

到目前為止,一切良好。用于測(cè)試的相應(yīng)方法看上去如下所示:

清單 7. “我能判斷您是怎樣的人 ”(Miguel de Cervantes)

				
package com.tedneward.scitter.test
{
  class ScitterTests
  {
    // ...
	
    @Test def scitterFriendsTimeline =
    {
      val scitter = new Scitter(testUser, testPassword)
      val result = scitter.friendsTimeline
      assertTrue(result.length > 0)
    }
  }
}

好極了。看上去就像 Scitter 對(duì)象中的 publicTimeline() 方法,并且行為也幾乎完全相同。

對(duì)于我們來(lái)說(shuō),那些可選參數(shù)仍然有問(wèn)題。因?yàn)?Scala 并沒(méi)有可選參數(shù)這樣的語(yǔ)言特性,乍一看來(lái),惟一的選擇就是完整地創(chuàng)建重載的 friendsTimeline() 方法,讓該方法帶有一定數(shù)量的參數(shù)。

幸運(yùn)的是,還有一種更好的方式,即通過(guò)一種有趣的方式將 Scala 的兩個(gè)語(yǔ)言特性(有一個(gè)特性我還沒(méi)有提到過(guò)) — case 類和 “重復(fù)參數(shù)” 結(jié)合起來(lái)(見清單 8):

清單 8. “我有多愛你?……”

				
package com.tedneward.scitter
{
  // ...
  
  abstract class OptionalParam
  case class Id(id : String) extends OptionalParam
  case class UserId(id : Long) extends OptionalParam
  case class Since(since_id : Long) extends OptionalParam
  case class Max(max_id : Long) extends OptionalParam
  case class Count(count : Int) extends OptionalParam
  case class Page(page : Int) extends OptionalParam
  
  class Scitter(username : String, password : String)
  {
    // ...
	
    def friendsTimeline(options : OptionalParam*) : List[Status] =
    {
      val optionsStr =
        new StringBuffer("http://twitter.com/statuses/friends_timeline.xml?")
      for (option <- options)
      {
        option match
        {
          case Since(since_id) =>
            optionsStr.append("since_id=" + since_id.toString() + "&")
          case Max(max_id) =>
            optionsStr.append("max_id=" + max_id.toString() + "&")
          case Count(count) =>
            optionsStr.append("count=" + count.toString() + "&")
          case Page(page) =>
            optionsStr.append("page=" + page.toString() + "&")
        }
      }
      
      val (statusCode, statusBody) =
        Scitter.execute(optionsStr.toString(), username, password)
      if (statusCode == 200)
      {
        val responseXML = XML.loadString(statusBody)

        val statusListBuffer = new ListBuffer[Status]

        for (n <- (responseXML \\ "status").elements)
          statusListBuffer += (Status.fromXml(n))
        
        statusListBuffer.toList
      }
      else
      {
        Nil
      }
    }
  }
}

看到標(biāo)在選項(xiàng)參數(shù)后面的 * 嗎?這表明該參數(shù)實(shí)際上是一個(gè)參數(shù)序列,這類似于 Java 5 中的 varargs 結(jié)構(gòu)。和 varargs 一樣,傳遞的參數(shù)數(shù)量可以像前面那樣為 0(不過(guò),我們將需要回到測(cè)試代碼,向 friendsTimeline 調(diào)用增加一對(duì)括號(hào),否則編譯器無(wú)法作出判斷:是調(diào)用不帶參數(shù)的方法,還是出于部分應(yīng)用之類的目的而調(diào)用該方法);我們還可以開始傳遞那些 case 類型,如下面的清單所示:

清單 9. “……聽我細(xì)細(xì)說(shuō)”(William Shakespeare)

				
package com.tedneward.scitter.test
{
  class ScitterTests
  {
    // ...
	
    @Test def scitterFriendsTimelineWithCount =
    {
      val scitter = new Scitter(testUser, testPassword)
      val result = scitter.friendsTimeline(Count(5))
      assertTrue(result.length == 5)
    }
  }
}

當(dāng)然,總是存在這樣的可能性:客戶機(jī)傳入古怪的參數(shù)序列,例如 friendsTimeline(Count(5), Count(6), Count(7)),但是在這里,我們只是將列表傳遞給 Twitter(希望它們的錯(cuò)誤處理足夠強(qiáng)大,能夠只采用指定的***那個(gè)參數(shù))。當(dāng)然,如果真的擔(dān)心這一點(diǎn),也很容易在構(gòu)造發(fā)送到 Twitter 的 URL 之前,從頭至尾檢查重復(fù)參數(shù)列表,并采用指定的每種參數(shù)的***一個(gè)參數(shù)。不過(guò),后果自負(fù)

#p#

兼容性

但是,這又產(chǎn)生一個(gè)有趣的問(wèn)題:從 Java 代碼調(diào)用這個(gè)方法有多容易?畢竟,如果這個(gè)庫(kù)的主要目標(biāo)之一是維護(hù)與 Java 代碼的兼容性,那么我們需要確保 Java 代碼在使用它時(shí)不至于太麻煩。

我們首先通過(guò)我們的好朋友 javap 檢驗(yàn)一下 Scitter 類:

清單 10. 哦,沒(méi)錯(cuò),Java 代碼……我現(xiàn)在想起來(lái)了……

				
C:\>javap -classpath classes com.tedneward.scitter.Scitter
Compiled from "scitter.scala"
public class com.tedneward.scitter.Scitter extends java.lang.Object implements s
cala.ScalaObject{
    public com.tedneward.scitter.Scitter(java.lang.String, java.lang.String);
    public scala.List friendsTimeline(scala.Seq);
    public boolean verifyCredentials();
    public int $tag()       throws java.rmi.RemoteException;
}

這時(shí)我心中有兩點(diǎn)擔(dān)心。首先,friendsTimeline() 帶有一個(gè) scala.Seq 參數(shù)(這是我們剛才用過(guò)的重復(fù)參數(shù)特性)。其次,friendsTimeline() 方法和 Scitter 對(duì)象中的 publicTimeline() 方法一樣(如果不信,可以運(yùn)行 javap 查證),返回一個(gè)元素列表 scala.List。這兩種類型在 Java 代碼中有多好用?

最簡(jiǎn)單的方法是用 Java 代碼而不是 Scala 編寫一組小型的 JUnit 測(cè)試,所以接下來(lái)我們就這樣做。雖然可以測(cè)試 Scitter 實(shí)例的構(gòu)造,并調(diào)用它的 verifyCredentials() 方法,但這些并不是特別有用 — 記住,我們不是要驗(yàn)證 Scitter 類的正確性,而是要看看從 Java 代碼中使用它有多容易。為此,我們直接編寫一個(gè)測(cè)試,該測(cè)試將獲取 “friends timeline” — 換句話說(shuō),我們要實(shí)例化一個(gè) Scitter 實(shí)例,并且不使用任何參數(shù)來(lái)調(diào)用它的 friendsTimeline() 方法。

這有點(diǎn)復(fù)雜,因?yàn)樾枰獋魅搿?CODE>scala.Seq 參數(shù) — scala.Seq 是一個(gè) Scala 特性,它將映射到底層 JVM 中的一個(gè)接口,所以不能直接實(shí)例化。我們可以嘗試典型的 Java null 參數(shù),但是這樣做會(huì)在運(yùn)行時(shí)拋出異常。我們需要的是一個(gè) scala.Seq 類,以便從 Java 代碼中輕松地實(shí)例化這個(gè)類。

最終,我們還是在 mutable.ListBuffer 類型中找到一個(gè)這樣的類,這正是在 Scitter 實(shí)現(xiàn)本身中使用的類型:

清單 11. 現(xiàn)在我明白了自己為什么喜歡 Scala……

				
package com.tedneward.scitter.test;

import org.junit.*;
import com.tedneward.scitter.*;

public class JavaScitterTests
{
  public static final String testUser = "TESTUSER";
  public static final String testPassword = "TESTPASSWORD";
  
  @Test public void getFriendsStatuses()
  {
    Scitter scitter = new Scitter(testUser, testPassword);
    if (scitter.verifyCredentials())
    {
      scala.List statuses =
        scitter.friendsTimeline(new scala.collection.mutable.ListBuffer());
      Assert.assertTrue(statuses.length() > 0);
    }
    else
      Assert.assertTrue(false);
  }
}

使用返回的 scala.List 不是問(wèn)題,因?yàn)槲覀兛梢韵駥?duì)待其他 Collection 類一樣對(duì)待它(不過(guò)我們的確懷念 Collection 的一些優(yōu)點(diǎn),因?yàn)?List 上基于 Scala 的方法都假設(shè)您將從 Scala 中與它們交互),所以,遍歷結(jié)果并不難,只要用上一點(diǎn) “舊式” Java 代碼(大約 1995 年時(shí)候的風(fēng)格):

清單 12. 重回 1995,又見 Vector……

				
package com.tedneward.scitter.test;

import org.junit.*;
import com.tedneward.scitter.*;

public class JavaScitterTests
{
  public static final String testUser = "TESTUSER";
  public static final String testPassword = "TESTPASSWORD";

  @Test public void getFriendsStatuses()
  {
    Scitter scitter = new Scitter(testUser, testPassword);
    if (scitter.verifyCredentials())
    {
      scala.List statuses =
        scitter.friendsTimeline(new scala.collection.mutable.ListBuffer());
      Assert.assertTrue(statuses.length() > 0);
      
      for (int i=0; i

這將我們引向另一個(gè)部分,即將參數(shù)傳遞到 friendsTimeline() 方法。不幸的是,ListBuffer 類型不是將一個(gè)集合作為構(gòu)造函數(shù)參數(shù),所以我們必須構(gòu)造參數(shù)列表,然后將集合傳遞到方法調(diào)用。這樣有些單調(diào)乏味,但還可以承受:

清單 13. 現(xiàn)在可以回到 Scala 嗎?

				
package com.tedneward.scitter.test;

import org.junit.*;
import com.tedneward.scitter.*;

public class JavaScitterTests
{
  public static final String testUser = "TESTUSER";
  public static final String testPassword = "TESTPASSWORD";
  
  // ...

  @Test public void getFriendsStatusesWithCount()
  {
    Scitter scitter = new Scitter(testUser, testPassword);
    if (scitter.verifyCredentials())
    {
      scala.collection.mutable.ListBuffer params =
        new scala.collection.mutable.ListBuffer();
      params.$plus$eq(new Count(5));
      
      scala.List statuses = scitter.friendsTimeline(params);

      Assert.assertTrue(statuses.length() > 0);
      Assert.assertTrue(statuses.length() == 5);
      
      for (int i=0; i

所以,雖然 Java 版本比對(duì)應(yīng)的 Scala 版本要冗長(zhǎng)一點(diǎn),但是到目前為止,從任何要使用 Scitter 庫(kù)的 Java 客戶機(jī)中調(diào)用該庫(kù)仍然非常簡(jiǎn)單。好極了。

結(jié)束語(yǔ)

顯然,對(duì)于 Scitter 還有很多事情要做,但是它已經(jīng)逐漸豐滿起來(lái),感覺(jué)不錯(cuò)。我們?cè)O(shè)法對(duì) Scitter 庫(kù)的通信部分進(jìn)行了 DRY 化處理,并且為 Twitter 提供的很多不同的 API 調(diào)用合并了所需的可選參數(shù) — 到目前為止,Java 客戶機(jī)基本上沒(méi)有受到我們公布的 API 的拖累。即使 API 沒(méi)有 Scala 所使用的那些 API 那樣干凈,但是如果 Java 開發(fā)人員要使用 Scitter 庫(kù),也不需要費(fèi)太多力氣。

Scitter 庫(kù)仍然帶有對(duì)象的意味,不過(guò)我們也開始看到,一些有實(shí)用意義的 Scala 特性正在出現(xiàn)。隨著我們繼續(xù)構(gòu)建這個(gè)庫(kù),只要有助于使代碼更簡(jiǎn)潔、更清晰,越來(lái)越多這樣的特性將添加進(jìn)來(lái)。本應(yīng)如此。

是時(shí)候說(shuō)再見了,我要短暫離開一下。等我回來(lái)時(shí),我將為這個(gè)庫(kù)增加對(duì)離線測(cè)試的支持,并增加更新用戶狀態(tài)的功能。到那時(shí),Scala 迷們,請(qǐng)記住:功能正常總比功能失常好。(對(duì)不起,我只是太喜歡開玩笑了。)

【相關(guān)閱讀】

  1. Scala編程語(yǔ)言專題
  2. 從Java走進(jìn)Scala(Scala經(jīng)典讀物)
  3. Scala代碼實(shí)例之Kestrel:文章匯總
  4. Java開發(fā)人員指南:Scala + Twitter = Scitter
  5. 從Java走進(jìn)Scala:一步步教你使用Scala Actor
責(zé)任編輯:yangsai 來(lái)源: IBMDW
相關(guān)推薦

2009-08-21 16:17:25

ScalaTwitter API

2009-09-28 11:01:39

從Java走進(jìn)Scal

2009-12-09 09:15:47

從Java走進(jìn)ScalTwitter API

2009-06-17 11:44:22

Scala控制結(jié)構(gòu)

2009-06-16 17:54:38

Scala類語(yǔ)法語(yǔ)義

2009-07-15 10:14:25

Scala并發(fā)性

2009-02-04 17:32:03

ibmdwJavaScala

2009-06-16 17:09:17

Scala面向?qū)ο?/a>函數(shù)編程

2009-08-27 12:00:40

ibmdwJava

2009-06-17 13:57:25

Scala元組數(shù)組

2009-06-17 13:26:06

scala繼承模型

2009-06-19 10:51:39

Scalapackage訪問(wèn)修飾符

2009-08-14 11:35:01

Scala Actor

2009-06-19 11:13:47

Scalacase類模式匹配

2009-06-19 11:42:09

Scala計(jì)算器解析

2009-06-19 13:16:36

Scala計(jì)算器解析器組合子

2009-08-20 09:56:05

2009-12-09 10:04:20

ibmdwJavaTwitter

2012-07-25 13:51:06

2010-08-26 09:01:27

Infobright
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

91精品国产自产观看在线| 青青草av免费在线观看| 午夜激情久久| 日韩一区二区在线观看视频播放| 好吊色欧美一区二区三区 | 里番在线观看网站| 国产精品99久久久久久似苏梦涵| 中文字幕精品在线视频| 免费黄频在线观看| 成av人电影在线观看| 国产一区欧美一区| 2023亚洲男人天堂| 91制片厂在线| 久久久精品国产**网站| 欧美日韩www| 久在线观看视频| 五月天婷婷社区| 久久国产99| 精品中文字幕在线观看| 一级性生活毛片| 亚洲天堂av资源在线观看| 色欧美片视频在线观看在线视频| 久久青青草原| 国产日韩免费视频| 日韩电影一二三区| zzjj国产精品一区二区| a视频免费观看| 精品3atv在线视频| 亚洲午夜电影在线观看| 中文字幕欧美日韩一区二区| 青青草手机在线| 国产99一区视频免费| 国产精品久久久久77777| 1级黄色大片儿| 久久av网址| 精品国产91久久久久久久妲己| 国产肉体ⅹxxx137大胆| 香蕉视频国产在线观看| 久久久三级国产网站| 成人资源视频网站免费| 国产又粗又大又爽| 日本系列欧美系列| 欧洲亚洲免费视频| 日韩免费在线视频观看| 亚洲精品一区二区妖精| 国产一区二区三区久久精品| 欧美大片免费播放器| 99久久香蕉| 精品国产一区a| 亚洲欧美激情一区二区三区| 四虎影视国产精品| 欧美三级韩国三级日本一级| 国产天堂在线播放| av大大超碰在线| 中文字幕制服丝袜成人av | 国产一级淫片a| 中文字幕免费精品| 欧美成人激情在线| 免费国产羞羞网站美图| 婷婷久久综合| 久久精品亚洲精品| 欧美一区二区三区爽爽爽| 国产精品成人一区二区不卡| 日韩亚洲欧美成人| 国产极品美女在线| 中文字幕亚洲精品乱码| 精品视频—区二区三区免费| 亚洲av无码一区二区三区观看| 99精品美女视频在线观看热舞| 性做久久久久久久免费看| 日韩精品手机在线观看| 美女日批视频在线观看| 亚洲国产cao| 国产特级黄色大片| 性欧美18一19sex性欧美| 欧美日韩在线三区| 欧洲美女亚洲激情| 日韩中文在线| 日韩经典中文字幕| 久久久男人的天堂| 欧美三级午夜理伦三级在线观看 | 国产精品美女久久久久久久久| 国产精品一区在线播放| 爽爽视频在线观看| 欧美激情中文不卡| www.男人天堂网| 黄频免费在线观看| 欧美性生活大片视频| 亚洲小视频网站| 国偷自产视频一区二区久| 精品亚洲一区二区三区| 青青青手机在线视频| 正在播放日韩欧美一页| 97在线免费观看视频| www.日韩一区| 国产一区二区在线影院| 国产日韩精品在线| 免费观看的毛片| 国产欧美1区2区3区| 日韩妆和欧美的一区二区| 日日夜夜精品一区| 香蕉成人啪国产精品视频综合网| 小泽玛利亚av在线| 在线女人免费视频| 337p亚洲精品色噜噜| 大尺度做爰床戏呻吟舒畅| av中文一区| 欧美精品videosex牲欧美| 欧美成人免费看| 亚洲在线久久| 日本一区二区在线免费播放| 国产suv一区二区| 久久夜色精品国产欧美乱极品| 久久国产精品亚洲va麻豆| 午夜在线视频观看| 中文字幕中文乱码欧美一区二区| 99久久久无码国产精品性色戒| 麻豆视频在线免费观看| 欧美日韩性视频在线| 91小视频在线播放| 日韩精品视频中文字幕| 怡红院精品视频| 国产午夜福利片| 99视频在线精品国自产拍免费观看| 97香蕉久久夜色精品国产| 国产免费黄色网址| 国产午夜精品一区二区三区嫩草 | 亚洲一二三区视频| 这里只有视频精品| 久久久免费高清视频| 国产乱码精品1区2区3区| www久久99| 欧美猛烈性xbxbxbxb| 色噜噜狠狠色综合中国| 国产在线不卡av| 欧美喷水视频| 91精品综合视频| 69久久久久| 欧美性感一区二区三区| 国产精品1000部啪视频| 亚洲午夜电影| 不卡一区二区三区视频| 男人天堂综合| 五月婷婷综合激情| www.欧美com| 综合一区av| 欧美综合在线第二页| 亚洲综合精品国产一区二区三区| 国产成人免费高清| 一二三四中文字幕| 91蝌蚪精品视频| 欧美成人国产va精品日本一级| 天堂中文在线网| 99国产欧美久久久精品| 成熟丰满熟妇高潮xxxxx视频| 精品久久毛片| 视频在线一区二区| 国产又粗又大又黄| 亚洲欧美一区二区三区久本道91 | 日本va欧美va欧美va精品| 蜜桃欧美视频| 欧美电影网站| 正在播放国产一区| 国产又黄又爽视频| 亚洲欧美日韩国产手机在线| 黄色三级视频在线播放| 国产精品jizz在线观看美国| 国产二区不卡| 竹内纱里奈兽皇系列在线观看| 欧美老人xxxx18| 亚洲av无一区二区三区| 国产一区二区视频在线播放| 日本不卡高清视频一区| 精品欧美日韩精品| 久久久999精品视频| 久久久久久久亚洲| 中文字幕亚洲精品在线观看| 黄色片子免费看| 98精品久久久久久久| 99中文视频在线| 蜜桃视频在线观看播放| 亚洲人av在线影院| 一级片视频播放| 亚洲午夜免费电影| 亚洲综合中文网| 午夜在线精品| 在线不卡日本| 亚洲狼人综合| 久久久久久久久久久网站| 欧美成熟毛茸茸| 91麻豆精品国产自产在线观看一区| 国产精品成人在线视频| 国产福利视频一区二区三区| 99视频在线免费播放| 激情小说一区| 国产精品午夜国产小视频| 青青草原国产在线| 亚洲欧美色婷婷| 国产三级小视频| 亚洲精品一二三| 日韩av一二区| 国产在线精品一区二区夜色| 美国av在线播放| 国产aa精品| 欧美在线精品免播放器视频| 黄黄的网站在线观看| 国产偷亚洲偷欧美偷精品| 国产精品欧美综合亚洲| 精品国产91久久久久久| www.av免费| 久久久久综合网| 天天影视综合色| 日产精品一区二区| 精品一区二区三区日本| 99热这里有精品| 欧美另类极品videosbest最新版本 | 秋霞成人午夜鲁丝一区二区三区| 亚洲欧洲精品视频| 91麻豆精品国产91久久久| 欧美日韩综合一区二区三区| 亚洲精品水蜜桃| 一级片黄色录像| 91蜜桃在线观看| 中文字幕乱码在线人视频| 琪琪一区二区三区| 欧美亚洲国产成人| 激情综合电影网| 91精品一区二区三区四区| 色无极亚洲影院| 欧美一区二区三区在线免费观看| 日韩专区视频| 国产成人午夜视频网址| 国产福利片在线观看| 欧美多人爱爱视频网站| 毛片免费不卡| 神马久久桃色视频| 二区在线观看| 亚洲人成亚洲人成在线观看| 深爱五月激情五月| 精品国产乱子伦一区| 国产高清在线免费| 日本高清不卡在线观看| 久热这里只有精品6| 亚洲成a人v欧美综合天堂| 麻豆视频在线观看| 夜色激情一区二区| 国产sm调教视频| 久久久久久久电影| 国产成人精品无码免费看夜聊软件| 久久91精品国产91久久小草| 男人天堂av片| 伊人成人网在线看| 欧美视频在线观看视频| 成人激情视频| 亚洲精品一区二区三区蜜桃久| 懂色av一区二区| 国产精品白丝jk白祙| 国产一区二区三区视频在线| 91久久综合亚洲鲁鲁五月天| 国产精欧美一区二区三区蓝颜男同| 久久精品美女视频网站| 欧美日韩在线资源| 国产视频精品xxxx| a天堂在线视频| 精品美女被调教视频大全网站| 亚洲视频在线免费播放| 在线不卡免费av| 精品欧美一区二区精品少妇| 精品美女一区二区| 日本福利在线观看| 日韩精品中文字幕在线不卡尤物| 中国一区二区视频| 欧美精品一卡二卡| 天堂av免费在线观看| 欧美视频一区在线观看| 97精品人妻一区二区三区香蕉| 91久久精品网| 天堂а√在线中文在线新版| 欧洲日韩一区二区三区| 国产女人高潮毛片| 亚洲精品一区在线观看| 色综合888| 日韩网站免费观看| a视频在线免费看| 韩国三级电影久久久久久| 美女福利一区二区三区| 国产美女精品视频| heyzo欧美激情| 日本成人黄色| 欧美日韩亚洲一区二区三区在线| 樱花www成人免费视频| 欧美久久视频| 天堂中文视频在线| 国产美女视频91| 亚洲国产欧美91| 久久一区二区三区四区| 小泽玛利亚一区二区免费| 中文字幕永久在线不卡| 久久久久无码精品国产| 色综合久久综合网97色综合 | 98精品国产自产在线观看| 日韩成人亚洲| 国产精品久久久久久久午夜| 精品123区| 国产欧美一区二区三区另类精品| 超碰成人福利| 日韩欧美一区二区三区久久婷婷| 精品少妇av| 日韩精品视频在线观看视频| 美国三级日本三级久久99| 欧美xxxxx精品| 亚洲婷婷综合色高清在线| 国产伦精品一区二区三区视频我| 日本精品一区二区三区高清 | 欧美一区二区三区免费视频| 欧美另类自拍| 欧美精品激情在线| 99国内精品久久久久| 欧美一区2区三区4区公司二百| 成人精品视频| 椎名由奈jux491在线播放| 国产亚洲福利| 国产亚洲精品成人a| a亚洲天堂av| 久久久久99精品成人片毛片| 欧美日韩国产综合草草| 日本韩国一区| 欧美一级在线播放| 粉嫩av一区二区| 免费看黄色a级片| 久久国产精品一区二区| 久久久久久久毛片| 欧美性猛交xxxx免费看漫画 | 在线观看二区| 日本欧美在线视频| 国产95亚洲| 影音先锋欧美在线| 麻豆久久久久久久| 一级特黄曰皮片视频| 欧美日韩一区二区免费在线观看 | 欧美高清视频一区二区三区在线观看| 色综合中文网| 无码精品a∨在线观看中文| 白白色 亚洲乱淫| 伊人365影院| 亚洲精品在线观| 久草在线视频福利| 成人高清在线观看| 欧美日本久久| 女同性αv亚洲女同志| 一区二区三区在线视频观看58| 天天干天天操天天爱| 亚洲精品视频网上网址在线观看| 成人免费在线| 成人精品久久一区二区三区| 久久免费精品视频在这里| 粉色视频免费看| 亚洲视频在线一区二区| 999久久久久久| 欧美激情成人在线视频| 哺乳一区二区三区中文视频| 2018日日夜夜| 91视频www| 波多野结衣二区三区| 中文字幕久热精品视频在线| 欧美激情三区| 欧美日韩午夜爽爽| 精品亚洲aⅴ乱码一区二区三区| aa片在线观看视频在线播放| 欧美色播在线播放| 成人av毛片| 亚洲一区二区三区xxx视频| 欧美日韩四区| 亚洲av片不卡无码久久| 欧美系列亚洲系列| 日韩专区av| 欧美精品七区| 久久国产精品第一页| 美国美女黄色片| 日韩欧美一卡二卡| 黄色软件视频在线观看| 国产欧美亚洲日本| 久久中文在线| 亚洲天堂一级片| 亚洲黄在线观看| 日韩av电影资源网| 欧美另类videosbestsex日本| 国产中文字幕一区| 日韩精品视频免费看| 一区二区三区 在线观看视| 9999在线精品视频| 最近看过的日韩成人| 成人免费观看av| 久久国产乱子伦精品| 久久99亚洲精品| 奇米色欧美一区二区三区| 国产精品久久久久野外| 色悠悠久久综合| 自拍视频在线免费观看| 高清av免费一区中文字幕|