- 浏览: 10452 次
- 性别:
- 来自: 北京
最新评论
-
copoplar:
发给贴好难啊!过关一样!
谢谢了!虽然没有domain。
《Grails权威指南》中的bookmarks工程代码 -
vdgame:
好像不对啊,domain文件都到哪去了?
《Grails权威指南》中的bookmarks工程代码 -
loong_lang:
原书的代码也只有3-8章的源码,可以用第7章的源码,代替第8章 ...
《Grails入门指南》各章节的工程代码 -
ourfirebird:
感谢楼主,还少第8-11章的内容!
《Grails入门指南》各章节的工程代码
Ted Neward , 主管, ThoughtWorks
2009 年 8 月 27 日
抽象地谈论 Scala 的确有趣,然而一旦将其付诸实践,就会发现将它作为 “玩具” 与在工作中使用它的区别。Scala 狂热者 Ted Neward 撰写了一篇 对 Scitter 的介绍 ,Scitter 是一个用于访问 Twitter 的 Scala 库,本文是其后续篇,在本文中,Ted Neward 为这个客户机库提供了一组更有趣也更有用的特性。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->
欢迎回来,Scala 迷们。上个月 , 我们谈到了 Twitter,这个微博客站点目前正引起社会性网络的极大兴趣,我们还谈到它的基于 XML-/REST 的 API 如何使它成为开发人员进行研究和探索的一个有趣平台。为此,我们首先充实了 “Scitter” 的基本结构,Scitter 是用于访问 Twitter 的一个 Scala 库。
|
我们对于 Scitter 有几个目标:
- 简化 Twitter 访问,比过去打开 HTTP 连接然后 “手动” 执行操作更容易。
- 可以从 Java 客户机轻松访问它。
- 轻松模拟以便进行测试。
在这一期,我们不必完成整个 Twitter API,但是我们将完成一些核心部分,目的是让这个库达到公共源代码控制库的程度,便于其他人来完成这项工作。
首先我们简单回顾一下到目前为止我们所处的阶段:
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 } } } |
代码有点长,但是很容易分为几个基本部分:
- case 类
User
和Status
,表示 Twitter 在对 API 调用的响应中发回给客户机的基本类型,包括用于构造或提取 XML 的一些方法。
- 一个 Scitter 独立对象,处理那些不需要对用户进行验证的操作。
- 一个 Scitter 实例(用 username 和 password 参数化),用于那些需要对用户执行验证的操作。
|
到目前为止,对于这两种
Scitter 类型,我们只谈到了测试、verifyCredentials 和 public_timeline API。虽然这些有助于确定
HTTP 访问的基础(使用 Apache HttpClient 库)可以工作,并且我们将 XML 响应转换成 Status
对象的基本方式也是可行的,但是现在我们甚至不能进行基本的 “我的朋友在说什么” 的公共时间线查询,也没有采取过基本的措施来防止代码库中出现 “重复” 问题,更不用说寻找一些方法来模拟用于测试的网络访问代码。
显然,在这一期我们有很多事情要做。
对 于代码,第一件让我烦恼的事就是,我在 Scitter 对象和类的每个方法中都重复这样的操作序列:创建 HttpClient 实例,对它进行初始化,用必要的验证参数对它进行参数化,等等。当它们只有 3 个方法时,可以进行管理,但是显然不易于伸缩,而且以后还会增加很多方法。此外,以后重新在那些方法中引入模拟和/或本地/离线测试功能将十分困难。所以 我们要解决这个问题。
实际上,我们这里介绍的并不是 Scala 本身,而是不要重复自己(Don't-Repeat-Yourself)的思想。因此,我将从基本的面向对象方法开始:创建一个 helper 方法,用于做实际工作:
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()) } } } |
|
注意两点:首先,我从 execute()
方法返回一个元组,其中包含状态码和响应主体。这正是让元组成为语言中固有的一部分的一个强大之处,因为实际上很容易从一个方法调用返回多个返回值。当
然,在 Java
代码中,也可以通过创建包含元组元素的顶级或嵌套类来实现这一点,但是这需要一整套专用于这一个方法的代码。此外,本来也可以返回一个包含 String
键和 Object
值的 Map
,但是那样就在很大程度上丧失了类型安全性。元组并不是一个非常具有变革性的特性,它只不过是又一个使 Scala 成为强大语言的优秀特性。
由于使用元组,我需要使用 Scala 的另一个特色语法将两个结果都捕捉到本地变量中,就像下面这个重写后的 Scitter.test 那样:
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 } } } |
实际上,我可以轻松地将 statusBody
全部去掉,并用
_
替代它,因为我没有用过第二个参数(test
没有返回 statusBody),但是对于其他调用将需要这个 statusBody,所以出于演示的目的,我保留了该参数。
注意,execute()
没有泄露任何与实际 HTTP 通信相关的细节 — 这是 Encapsulation 101。这样便于以后用其他实现替换 execute()
(以后的确要这么做),或者便于通过重用 HttpClient
对象来优化代码,而不是每次重新实例化新的对象。
|
接下来,注意到 execute()
方法在 Scitter
对象上吗?这意味着我将可以从不同的 Scitter 实例中使用它(至少现在可以这样做,如果以后在 execute()
内部执行的操作不允许这样做,则另当别论)— 这就是我将 execute()
标记为 private[scitter]
的原因,这意味着
com.tedneward.scitter
包中的所有内容都可以看到它。
(顺便说一句,如果还没有运行测试的话,那么请运行测试,确保一切运行良好。我将假设我们在讨论代码时您会运行测试,所以如果我忘了提醒您,并不意味着您也忘记这么做。)
顺便说一句,对于经过验证的访问,为了支持 Scitter
类,需要一个用户名和密码,所以我将创建一个重载的
execute()
方法,该方法新增两个 String
参数:
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()) } } } |
实际上,除了验证部分,这两个 execute()
基本上是做相同的事情,我们可以按照第二个版本完全重写第一个 execute()
,但是要注意,Scala 要求显式表明重载的 execute()
的返回类型:
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()) } } } |
到目前为止,一切良好。我们对 Scitter 的通信部分进行了 DRY 化处理,接下来我们转移到下一件事情:获得朋友的 tweet 的列表。
Twitter API 表明,friends_timeline
API 调用 “返回认证用户和该用户的朋友发表的最近 20 条状态”。(它还指出,对于直接从 Twitter Web 站点使用 Twitter
的用户,“这相当于 Web 上的 ‘/home’”。)对于任何 Twitter API 来说,这是非常基本的要求,所以让我们将它添加到 Scitter
类中。之所以将它添加到类而不是对象中,是因为正如文档中指出的那样,这是以验证用户的身份做的事情,而我已经决定归入
Scitter
类,而不是 Scitter
对象。
但是,这里我们碰到一块绊脚石:friends_timeline
调用接受一组 “可选参数”,包括 since_id
、max_id
、count
和 page
,以控制返回的结果。这是一项比较复杂的操作,因为
Scala 不像其他语言(例如 Groovy、JRuby 或 JavaScript)那样原生地支持 “可选参数” 的思想,但是我们首先来处理简单的东西 — 我们来创建一个 friendsTimeline
方法,该方法只执行一般的、非参数化的调用:
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 } } } } |
到目前为止,一切良好。用于测试的相应方法看上去如下所示:
清单 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
对象中的 publicTimeline()
方法,并且行为也几乎完全相同。
|
对于我们来说,那些可选参数仍然有问题。因为 Scala 并没有可选参数这样的语言特性,乍一看来,惟一的选择就是完整地创建重载的 friendsTimeline()
方法,让该方法带有一定数量的参数。
幸运的是,还有一种更好的方式,即通过一种有趣的方式将 Scala 的两个语言特性(有一个特性我还没有提到过) — case 类和 “重复参数” 结合起来(见清单 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 } } } } |
看到标在选项参数后面的 *
吗?这表明该参数实际上是一个参数序列,这类似于 Java 5 中的
varargs
结构。和 varargs
一样,传递的参数数量可以像前面那样为 0(不过,我们将需要回到测试代码,向 friendsTimeline
调用增加一对括号,否则编译器无法作出判断:是调用不带参数的方法,还是出于部分应用之类的目的而调用该方法);我们还可以开始传递那些 case 类型,如下面的清单所示:
清单 9. “……听我细细说”(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) } } } |
当然,总是存在这样的可能性:客户机传入古怪的参数序列,例如
friendsTimeline(Count(5), Count(6), Count(7))
,但是在这里,我们只是将列表
传递给 Twitter(希望它们的错误处理足够强大,能够只采用指定的最后那个参数)。当然,如果真的担心这一点,也很容易在构造发送到
Twitter 的 URL 之前,从头至尾检查重复参数列表,并采用指定的每种参数的最后一个参数。不过,后果自负
。
但是,这又产生一个有趣的问题:从 Java 代码调用这个方法有多容易?毕竟,如果这个库的主要目标之一是维护与 Java 代码的兼容性,那么我们需要确保 Java 代码在使用它时不至于太麻烦。
我们首先通过我们的好朋友 javap 检验一下 Scitter
类:
清单 10. 哦,没错,Java 代码……我现在想起来了……
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; } |
这时我心中有两点担心。首先,friendsTimeline()
带有一个
scala.Seq
参数(这是我们刚才用过的重复参数特性)。其次,friendsTimeline()
方法和 Scitter
对象中的
publicTimeline()
方法一样(如果不信,可以运行 javap 查证),返回一个元素列表 scala.List
。这两种类型在 Java 代码中有多好用?
最简单的方法是用 Java 代码而不是 Scala 编写一组小型的 JUnit 测试,所以接下来我们就这样做。虽然可以测试 Scitter 实例的构造,并调用它的 verifyCredentials()
方法,但这些并不是特别有用 — 记住,我们不是要验证 Scitter
类的正确性,而是要看看从 Java 代码中使用它有多容易。为此,我们直接编写一个测试,该测试将获取 “friends timeline” — 换句话说,我们要实例化一个
Scitter 实例,并且不使用任何参数来调用它的 friendsTimeline()
方法。
这有点复杂,因为需要传入 scala.Seq
参数
—
scala.Seq
是一个 Scala 特性,它将映射到底层 JVM 中的一个接口,所以不能直接实例化。我们可以尝试典型的 Java
null
参数,但是这样做会在运行时抛出异常。我们需要的是一个 scala.Seq
类,以便从 Java 代码中轻松地实例化这个类。
最终,我们还是在 mutable.ListBuffer
类型中找到一个这样的类,这正是在 Scitter 实现本身中使用的类型:
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
不是问题,因为我们可以像对待其他 Collection
类一样对待它(不过我们的确怀念 Collection 的一些优点,因为 List 上基于 Scala 的方法都假设您将从 Scala 中与它们交互),所以,遍历结果并不难,只要用上一点 “旧式” Java 代码(大约 1995 年时候的风格):
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<statuses.length(); i++) { Status stat = (Status)statuses.apply(i); System.out.println(stat.user().screenName() + " said " + stat.text()); } } else Assert.assertTrue(false); } } |
这将我们引向另一个部分,即将参数传递到
friendsTimeline()
方法。不幸的是,ListBuffer
类型不是将一个集合作为构造函数参数,所以我们必须构造参数列表,然后将集合传递到方法调用。这样有些单调乏味,但还可以承受:
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<statuses.length(); i++) { Status stat = (Status)statuses.apply(i); System.out.println(stat.user().screenName() + " said " + stat.text()); } } else Assert.assertTrue(false); } } |
所以,虽然 Java 版本比对应的 Scala 版本要冗长一点,但是到目前为止,从任何要使用 Scitter 库的 Java 客户机中调用该库仍然非常简单。好极了。
显 然,对于 Scitter 还有很多事情要做,但是它已经逐渐丰满起来,感觉不错。我们设法对 Scitter 库的通信部分进行了 DRY 化处理,并且为 Twitter 提供的很多不同的 API 调用合并了所需的可选参数 — 到目前为止,Java 客户机基本上没有受到我们公布的 API 的拖累。即使 API 没有 Scala 所使用的那些 API 那样干净,但是如果 Java 开发人员要使用 Scitter 库,也不需要费太多力气。
Scitter 库仍然带有对象的意味,不过我们也开始看到,一些有实用意义的 Scala 特性正在出现。随着我们继续构建这个库,只要有助于使代码更简洁、更清晰,越来越多这样的特性将添加进来。本应如此。
是时候说再见了,我要短暂离开一下。等我回来时,我将为这个库增加对离线测试的支持,并增加更新用户状态的功能。到那时,Scala 迷们,请记住:功能正常总比功能失常好。(对不起,我只是太喜欢开玩笑了。)
文章转载于:http://www.ibm.com/developerworks/cn/java/j-scala06029.html
相关推荐
《面向Java开发人员的Scala指南》是一本专为熟悉Java编程语言的开发者设计的书籍,旨在帮助他们理解和掌握Scala这门强大的多范式编程语言。Scala结合了面向对象和函数式编程的特点,提供了更高的代码抽象能力和性能...
Scala是一种强大的多范式编程语言,它融合了面向对象和函数式编程的特性,为Java开发者提供了一种更高效、更简洁的编程选择。在Java开发领域,Servlet是用于构建Web应用程序的标准API,广泛应用于服务器端编程。本...
《IBM:面向Java开发人员的Scala指南》是一本旨在帮助Java开发者过渡到Scala语言的实用教程。Scala是一种多范式编程语言,它结合了面向对象和函数式编程的特性,设计目的是提高代码的表达力和效率。这本书的第178页...
通过学习这个面向 Java 开发人员的 Scala 指南系列,你可以深入了解 Scala 的语法特性、编程模式以及如何将这些知识应用到实际项目中,从而拓宽你的编程视野和提高编程技能。无论你是希望在现有 Java 项目中引入 ...
此文摘自developerWorks中国的Javatechnology系列中国 [选择]使用条款文档选项打印本页将此页作为电子邮件发送英文原文关于本系列Ted
通过这个面向Java开发者的Scala教程,你可以逐步了解并掌握Scala的基本语法和特性,从而扩展你的编程技能,适应现代软件开发的需求。继续学习,深入探索Scala的世界,你会发现更多有趣且实用的编程理念和工具。
面向对象编程之类第13讲-Scala编程:面向对象编程之对象第14讲-Scala编程:面向对象编程之继承第15讲-Scala编程:面向对象编程之Trait第16讲-Scala编程:函数式编程第17讲-Scala编程:函数式编程之集合操作第18讲-...
### Scala中文教程:Java平台开发语言Scala简单教程 #### Scala:一种融合面向对象与函数式编程的创新语言 Scala作为一种新兴的编程语言,由Martin Odersky创立,并在其后续的发展中逐渐成为一种兼具面向对象与...
Scala编程详解:数组操作之数组转换 共5页第11讲-Scala编程详解:Map与Tuple 共8页第12讲-Scala编程详解:面向对象编程之类 共12页第13讲-Scala编程详解:面向对象编程之对象 共9页第14讲-Scala编程详解:面向对象...
标题中的“Scala Java相关开发工具”表明我们讨论的是与Java和Scala编程语言相关的开发环境和工具。Java和Scala都是在JVM(Java虚拟机)上运行的语言,它们有着紧密的联系,但各自拥有独特的特性和用途。 首先,...
赠送jar包:scala-parser-combinators_2.11-1.0.4.jar; 赠送原API文档:scala-parser-combinators_2.11-1.0.4-javadoc.jar; 赠送源代码:scala-parser-combinators_2.11-1.0.4-sources.jar; 包含翻译后的API...
标签:11、xml_2、scala、lang、modules、jar包、java、中文文档; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准...
Scala编程详解:数组操作之数组转换 共5页第11讲-Scala编程详解:Map与Tuple 共8页第12讲-Scala编程详解:面向对象编程之类 共12页第13讲-Scala编程详解:面向对象编程之对象 共9页第14讲-Scala编程详解:面向对象...
Scala编程详解:数组操作之数组转换 共5页第11讲-Scala编程详解:Map与Tuple 共8页第12讲-Scala编程详解:面向对象编程之类 共12页第13讲-Scala编程详解:面向对象编程之对象 共9页第14讲-Scala编程详解:面向对象...
Scala编程详解:数组操作之数组转换 共5页第11讲-Scala编程详解:Map与Tuple 共8页第12讲-Scala编程详解:面向对象编程之类 共12页第13讲-Scala编程详解:面向对象编程之对象 共9页第14讲-Scala编程详解:面向对象...
赠送jar包:scala-java8-compat_2.11-0.7.0.jar; 赠送原API文档:scala-java8-compat_2.11-0.7.0-javadoc.jar; 赠送源代码:scala-java8-compat_2.11-0.7.0-sources.jar; 赠送Maven依赖信息文件:scala-java8-...
Scala编程详解:数组操作之数组转换 共5页第11讲-Scala编程详解:Map与Tuple 共8页第12讲-Scala编程详解:面向对象编程之类 共12页第13讲-Scala编程详解:面向对象编程之对象 共9页第14讲-Scala编程详解:面向对象...