12
返回列表 发新帖
楼主: Sky-Tiger

First Steps to Scala

[复制链接]
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
11#
 楼主| 发表于 2009-3-22 21:30 | 只看该作者
Step 10. Use Sets and Maps

Because Scala aims to help you take advantage of both functional and imperative styles, its collections libraries make a point to differentiate between mutable and immutable collection classes. For example, Arrays are always mutable, whereas Lists are always immutable. When it comes to Sets and Maps, Scala also provides mutable and immutable alternatives, but in a different way. For Sets and Maps, Scala models mutability in the class hierarchy.

For example, the Scala API contains a base trait for Sets, where a trait is similar to a Java interface. (You'll find out more about traits in Step 12.) Scala then provides two subtraits, one for mutable Sets, and another for immutable Sets. As you can see in Figure 2, these three traits all share the same simple name, Set. Their fully qualified names differ, however, because they each reside in a different package. Concrete Set classes in the Scala API, such as the HashSet classes shown in Figure 2, extend either the mutable or immutable Set trait. (Although in Java you implement interfaces, in Scala you “extend” traits.) Thus, if you want to use a HashSet, you can choose between mutable and immutable varieties depending upon your needs.



Figure 2. Class hierarchy for Scala Sets.

To try out Scala Sets, type the following code into a file named jetset.scala:

import scala.collection.mutable.HashSet

val jetSet = new HashSet[String]
jetSet += "Lear"
jetSet += ("Boeing", "Airbus")
println(jetSet.contains("Cessna"))

The first line of jetSet.scala imports the mutable HashSet. As with Java, the import allows you to use the simple name of the class, HashSet, in this source file. After a blank line, the third line initializes jetSet with a new HashSet that will contain only Strings. Note that just as with Lists and Arrays, when you create a Set, you need to parameterize it with a type (in this case, String), since every object in a Set must share the same type. The subsequent two lines add three objects to the mutable Set via the += method. As with most other symbols you've seen that look like operators in Scala, += is actually a method defined on class HashSet. Had you wanted to, instead of writing jetSet += "Lear", you could have written jetSet.+=("Lear"). Because the += method takes a variable number of arguments, you can pass one or more objects at a time to it. For example, jetSet += "Lear" adds one String to the HashSet, but jetSet += ("Boeing", "Airbus") adds two Strings to the set. Finally, the last line prints out whether or not the Set contains a particular String. (As you'd expect, it prints false.)

Another useful collection class in Scala is Maps. As with Sets, Scala provides mutable and immutable versions of Map, using a class hierarchy. As you can see in Figure 3, the class hierarchy for Maps looks a lot like the one for Sets. There's a base Map trait in package scala.collection, and two subtrait Maps: a mutable Map in scala.collection.mutable and an immutable one in scala.collection.immutable.



Figure 3. Class hierarchy for Scala Maps.

Implementations of Map, such as the HashMaps shown in the class hierarchy in Figure 3, implement either the mutable or immutable trait. To see a Map in action, type the following code into a file named treasure.scala.

// In treasure.scala

import scala.collection.mutable.HashMap

val treasureMap = new HashMap[Int, String]
treasureMap += 1 -> "Go to island."
treasureMap += 2 -> "Find big X on ground."
treasureMap += 3 -> "Dig."
println(treasureMap(2))

On the first line of treasure.scala, you import the mutable form of HashMap. After a blank line, you define a val named treasureMap and initialize it with a new mutable HashMap whose keys will be Ints and values Strings. On the next three lines you add key/value pairs to the HashMap using the -> method. As illustrated in previous examples, the Scala compiler transforms an binary operation expression like 1 -> "Go to island." into 1.->("Go to island."). Thus, when you say 1 -> "Go to island.", you are actually calling a method named -> on an Int with the value 1, and passing in a String with the value "Go to island." This -> method, which you can invoke on any object in a Scala program3, returns a two-element tuple containing the key and value. You then pass this tuple to the += method of the HashMap object to which treasureMap refers. Finally, the last line prints the value that corresponds to the key 2 in the treasureMap. If you run this code, it will print:

Find big X on ground.

Because maps are such a useful programming construct, Scala provides a factory method for Maps that is similar in spirit to the factory method shown in Step 9 that allows you to create Lists without using the new keyword. To try out this more concise way of constructing maps, type the following code into a file called numerals.scala:

// In numerals.scala
val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V")
println(romanNumeral(4))

In numerals.scala you take advantage of the fact that the the immutable Map trait is automatically imported into any Scala source file. Thus when you say Map in the first line of code, the Scala interpreter knows you mean scala.collection.immutable.Map. In this line, you call a factory method on the immutable Map's companion object5, passing in five key/value tuples as parameters. This factory method returns an instance of the immutable HashMap containing the passed key/value pairs. The name of the factory method is actually apply, but as mentioned in Step 8, if you say Map(...) it will be transformed by the compiler to Map.apply(...). If you run the numerals.scala script, it will print IV.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
12#
 楼主| 发表于 2009-3-22 21:30 | 只看该作者
Step 11. Understand classes and singleton objects

Up to this point you've written Scala scripts to try out the concepts presented in this article. For all but the simplest projects, however, you will likely want to partition your application code into classes. To give this a try, type the following code into a file called greetSimply.scala:

// In greetSimply.scala

class SimpleGreeter {
  val greeting = "Hello, world!"
  def greet() = println(greeting)
}

val g = new SimpleGreeter
g.greet()

greetSimply.scala is actually a Scala script, but one that contains a class definition. This first, example, however, illustrates that as in Java, classes in Scala encapsulate fields and methods. Fields are defined with either val or var. Methods are defined with def. For example, in class SimpleGreeter, greeting is a field and greet is a method. To use the class, you initialize a val named g with a new instance of SimpleGreeter. You then invoke the greet instance method on g. If you run this script with scala greetSimply.scala, you will be dazzled with yet another Hello, world!.

Although classes in Scala are in many ways similar to Java, in several ways they are quite different. One difference between Java and Scala involves constructors. In Java, classes have constructors, which can take parameters, whereas in Scala, classes can take parameters directly. The Scala notation is more concise—class parameters can be used directly in the body of the class; there’s no need to define fields and write assignments that copy constructor parameters into fields. This can yield substantial savings in boilerplate code; especially for small classes. To see this in action, type the following code into a file named greetFancily.scala:

// In greetFancily.scala

class FancyGreeter(greeting: String) {
  def greet() = println(greeting)
}

val g = new FancyGreeter("Salutations, world")
g.greet

Instead of defining a constructor that takes a String, as you would do in Java, in greetFancily.scala you placed the greeting parameter of that constructor in parentheses placed directly after the name of the class itself, before the open curly brace of the body of class FancyGreeter. When defined in this way, greeting essentially becomes a value (not a variable—it can't be reassigned) field that's available anywhere inside the body. In fact, you pass it to println in the body of the greet method. If you run this script with the command scala greetFancily.scala, it will inspire you with:

Salutations, world!

This is cool and concise, but what if you wanted to check the String passed to FancyGreeter's primary constructor for null, and throw NullPointerException to abort the construction of the new instance? Fortunately, you can. Any code sitting inside the curly braces surrounding the class definition, but which isn't part of a method definition, is compiled into the body of the primary constructor. In essence, the primary constructor will first initialize what is essentially a final field for each parameter in parentheses following the class name. It will then execute any top-level code contained in the class's body. For example, to check a passed parameter for null, type in the following code into a file named greetCarefully.scala:

// In greetCarefully.scala
class CarefulGreeter(greeting: String) {

  if (greeting == null) {
    throw new NullPointerException("greeting was null")
  }

  def greet() = println(greeting)
}

new CarefulGreeter(null)

In greetCarefully.scala, an if statement is sitting smack in the middle of the class body, something that wouldn't compile in Java. The Scala compiler places this if statement into the body of the primary constructor, just after code that initializes what is essentially a final field named greeting with the passed value. Thus, if you pass in null to the primary constructor, as you do in the last line of the greetCarefully.scala script, the primary constructor will first initialize the greeting field to null. Then, it will execute the if statement that checks whether the greeting field is equal to null, and since it is, it will throw a NullPointerException. If you run greetCarefully.scala, you will see a NullPointerException stack trace.

In Java, you sometimes give classes multiple constructors with overloaded parameter lists. You can do that in Scala as well, however you must pick one of them to be the primary constructor, and place those constructor parameters directly after the class name. You then place any additional auxiliary constructors in the body of the class as methods named this. To try this out, type the following code into a file named greetRepeatedly.scala:

// In greetRepeatedly.scala
class RepeatGreeter(greeting: String, count: Int) {

  def this(greeting: String) = this(greeting, 1)

  def greet() = {
    for (i <- 1 to count)
      println(greeting)
  }
}

val g1 = new RepeatGreeter("Hello, world", 3)
g1.greet()
val g2 = new RepeatGreeter("Hi there!")
g2.greet()

RepeatGreeter's primary constructor takes not only a String greeting parameter, but also an Int count of the number of times to print the greeting. However, RepeatGreeter also contains a definition of an auxiliary constructor, the this method that takes a single String greeting parameter. The body of this constructor consists of a single statement: an invocation of the primary constructor parameterized with the passed greeting and a count of 1. In the final four lines of the greetRepeatedly.scala script, you create two RepeatGreeters instances, one using each constructor, and call greet on each. If you run greetRepeatedly.scala, it will print:

Hello, world
Hello, world
Hello, world
Hi there!

Another area in which Scala departs from Java is that you can't have any static fields or methods in a Scala class. Instead, Scala allows you to create singleton objects using the keyword object. A singleton object cannot, and need not, be instantiated with new. It is essentially automatically instantiated the first time it is used, and as the “singleton” in its name implies, there is ever only one instance. A singleton object can share the same name with a class, and when it does, the singleton is called the class's companion object. The Scala compiler transforms the fields and methods of a singleton object to static fields and methods of the resulting binary Java class. To give this a try, type the following code into a file named WorldlyGreeter.scala:

// In WorldlyGreeter.scala

// The WorldlyGreeter class
class WorldlyGreeter(greeting: String) {
  def greet() = {
    val worldlyGreeting = WorldlyGreeter.worldify(greeting)
    println(worldlyGreeting)
  }
}

// The WorldlyGreeter companion object
object WorldlyGreeter {
  def worldify(s: String) = s + ", world!"
}

In this file, you define both a class, with the class keyword, and a companion object, with the object keyword. Both types are named WorldlyGreeter. One way to think about this if you are coming from a Java programming perspective is that any static methods that you would have placed in class WorldlyGreeter in Java, you'd put in singleton object WorldlyGreeter in Scala. In fact, when the Scala compiler generates bytecodes for this file, it will create a Java class named WorldlyGreeter that has an instance method named greet (defined in the WorldlyGreeter class in the Scala source) and a static method named worldify (defined in the WorldlyGreeter companion object in Scala source). Note also that in the first line of the greet method in class WorldlyGreeter, you invoke the singleton object's worldify method using a syntax similar to the way you invoke static methods in Java: the singleton object name, a dot, and the method name:

// Invoking a method on a singleton object from class WorldlyGreeter
// ...
val worldlyGreeting = WorldlyGreeter.worldify(greeting)
// ...

To run this code, you'll need to create an application. Type the following code into a file named WorldlyApp.scala:

// In WorldlyApp.scala
// A singleton object with a main method that allows
// this singleton object to be run as an application
object WorldlyApp {
  def main(args: Array[String]) {
    val wg = new WorldlyGreeter("Hello")
    wg.greet()
  }
}

Because there's no class named WorldlyApp, this singleton object is not a companion object. It is instead called a stand-alone. object. Thus, a singleton object is either a companion or a stand-alone object. The distinction is important because companion objects get a few special privileges, such as access to private members of the like-named class.

One difference between Scala and Java is that whereas Java requires you to put a public class in a file named after the class—for example, you'd put class SpeedRacer in file SpeedRacer.java—in Scala, you can name .scala files anything you want, no matter what Scala classes or code you put in them. In general in the case of non-scripts, however, it is recommended style to name files after the classes they contain as is done in Java, so that programmers can more easily locate classes by looking at file names. This is the approach we've taken with the two files in this example, WorldlyGreeter.scala and WorldlyApp.scala.

Neither WorldlyGreeter.scala nor WorldlyApp.scala are scripts, because they end in a definition. A script, by contrast, must end in a result expression. Thus if you try to run either of these files as a script, for example by typing:

scala WorldlyGreeter.scala # This won't work!

The Scala interpreter will complain that WorldlyGreeter.scala does not end in a result expression. Instead, you'll need to actually compile these files with the Scala compiler, then run the resulting class files. One way to do this is to use scalac, which is the basic Scala compiler. Simply type:

scalac WorldlyApp.scala WorldlyGreeter.scala

Given that the scalac compiler starts up a new JVM instance each time it is invoked, and that the JVM often has a perceptible start-up delay, the Scala distribution also includes a Scala compiler daemon called fsc (for fast Scala compiler). You use it like this:

fsc WorldlyApp.scala WorldlyGreeter.scala

The first time you run fsc, it will create a local server daemon attached to a port on your computer. It will then send the list of files to compile to the daemon via the port, and the daemon will compile the files. The next time you run fsc, the daemon will already be running, so fsc will simply send the file list to the daemon, which will immediately compile the files. Using fsc, you only need to wait for the the JVM to startup the first time. If you ever want to stop the fsc daemon, you can do so with fsc -shutdown.

Running either of these scalac or fsc commands will produce Java class files that you can then run via the scala command, the same command you used to invoke the interpreter in previous examples. However, instead of giving it a filename with a .scala extension containing Scala code to interpret6 as you did in every previous example, in this case you'll give it the name of a class containing a main method. Similar to Java, any Scala class with a main method that takes a single parameter of type Array[String] and returns Unit7 can serve as the entry point to an application. In this example, WorldlyApp has a main method with the proper signature, so you can run this example by typing:

scala WorldlyApp

At which point you should see:

Hello, world!

You may recall seeing this output previously, but this time it was generated in this interesting manner:

    * The scala program fires up a JVM with the WorldlyApp's main method as the entry point.
    * WordlyApp's main method creates a new WordlyGreeter instance via new, passing in the string "Hello" as a parameter.
    * Class WorldlyGreeter's primary constructor essentially initializes a final field named greeting with the passed value, "Hello" (this initialization code is automatically generated by the Scala compiler).
    * WordlyApp's main method initializes a local \@val@ named wg with the new WorldlyGreeter instance.
    * WordlyApp's main method then invokes greet on the WorldlyGreeter instance to which wg refers.
    * Class WordlyGreeter's greet method invokes worldify on singleton object WorldlyGreeter, passing along the value of the final field greeting, "Hello".
    * Companion object WorldlyGreeter's worldify method returns a String consisting of the value of a concatenation of the s parameter, which is "Hello", and the literal String ", world!".
    * Class WorldlyGreeter's greet method then initializes a \@val@ named worldlyGreetingwithplaces the "Hello, world!" String returned from the worldify method.
    * Class WorldlyGreeter's greet method passes the "Hello, world!" String to which worldlyGreeting refers to println, which sends the cheerful greeting, via the standard output stream, to you.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
13#
 楼主| 发表于 2009-3-22 21:31 | 只看该作者
Step 12. Understand traits and mixins

As first mentioned in Step 10, Scala includes a construct called a trait, which is similar in spirit to Java's interface. One main difference between Java interfaces and Scala's traits are that whereas all methods in Java interfaces are by definition abstract, you can give methods real bodies with real code in Scala traits. Here's an example:

trait Friendly {
  def greet() = "Hi"
}

In this example, the greet method returns the String "Hi". If you are coming from Java, this greet method may look a little funny to you, as if greet() is somehow a field being initialized to the String value "Hi". What is actually going on is that lacking an explicit return statement, Scala methods will return the value of the last expression. In this case, the value of the last expression is "Hi", so that is returned. A more verbose way to say the same thing would be:

trait Friendly {
  def greet(): String = {
    return "Hi"
  }
}

Regardless of how your write the methods, however, the key point is that Scala traits can actually contain non-abstract methods. Another difference between Java interfaces and Scala traits is that whereas you implement Java interfaces, you extend Scala traits. Other than this implements/extends difference, however, inheritance when you are defining a new type works in Scala similarly to Java. In both Java and Scala, a class can extend one (and only one) other class. In Java, an interface can extend zero to many interfaces. Similarly in Scala, a trait can extend zero to many traits. In Java, a class can implement zero to many interfaces. Similarly in Scala, a class can extend zero to many traits. implements is not a keyword in Scala.

Here's an example:

class Dog extends Friendly {
  override def greet() = "Woof"
}

In this example, class Dog extends trait Friendly. This inheritance relationship implies much the same thing as interface implementation does in Java. You can assign a Dog instance to a variable of type Friendly. For example:

var pet: Friendly = new Dog
println(pet.greet())

When you invoke the greet method on the Friendly pet variable, it will use dynamic binding, as in Java, to determine which implementation of the method to call. In this case, class Dog overrides the greet method, so Dog's implementation of greet will be invoked. Were you to execute the above code, you would get Woof (Dog's implementation of greet), not Hi (Friendly's implementation of greet). Note that one difference with Java is that to override a method in Scala, you must precede the method's def with override. If you attempt to override a method without specifying override, your Scala code won't compile.

Finally, one quite significant difference between Java's interfaces and Scala's traits is that in Scala, you can mix in traits at instantiation time. For example, consider the following trait:

trait ExclamatoryGreeter extends Friendly {
  override def greet() = super.greet() + "!"
}

Trait ExclamatoryGreeter extends trait Friendly and overrides the greet method. ExclamatoryGreeter's greet method first invokes the superclass's greet method, appends an exclamation point to whatever the superclass’s greet method returns, and returns the resulting String. With this trait, you can mix in its behavior at instantiation time using the with keyword. Here's an example:

val pup: Friendly = new Dog with ExclamatoryGreeter
println(pup.greet())

Given the initial line of code, the Scala compiler will create a synthetic8 type that extends class Dog and trait ExclamatoryGreeter and instantiate it. When you invoke a method on the synthetic type, it will cause the correct implementation to be invoked. When you run this code, the pup variable will first be initialized with the new instance of the synthetic type, then when greet is invoked on pup, you'll see "Woof!". Note that had pup not been explicitly defined to be of type Friendly, the Scala compiler would have inferred the type of pup to be Dog with ExclamatoryGreeter.

To give all these concepts a try, type the following code into a file named friendly.scala:

trait Friendly {
  def greet() = "Hi"
}

class Dog extends Friendly {
  override def greet() = "Woof"
}

class HungryCat extends Friendly {
  override def greet() = "Meow"
}

class HungryDog extends Dog {
  override def greet() = "I'd like to eat my own dog food"
}

trait ExclamatoryGreeter extends Friendly {
  override def greet() = super.greet() + "!"
}

var pet: Friendly = new Dog
println(pet.greet())

pet = new HungryCat
println(pet.greet())

pet = new HungryDog
println(pet.greet())

pet = new Dog with ExclamatoryGreeter
println(pet.greet())

pet = new HungryCat with ExclamatoryGreeter
println(pet.greet())

pet = new HungryDog with ExclamatoryGreeter
println(pet.greet())

When you run the friendly.scala script, it will print:

Woof
Meow
I'd like to eat my own dog food
Woof!
Meow!
I'd like to eat my own dog food!

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
14#
 楼主| 发表于 2009-3-22 21:31 | 只看该作者
Conclusion

As you may have glimpsed by reading this article, the promise of Scala is that you can get more productivity while leveraging existing investments. Scala's basic conciseness of syntax and support for the functional programming style promise increased programmer productivity compared to the Java langauge, while enabling you to continue to take advantage of all the great things about the Java platform. You can complement Java code with Scala code and continue to leverage your existing Java code and APIs, the many APIs available for the Java platform, the runtime performance offered by JVMs, and your own knowledge of the Java platform.

With the knowledge you've gained in this article, you should already be able to get started using Scala for small tasks, especially scripts. In future articles, we will dive into more detail in these topics, and introduce other topics that weren't even hinted at here.
Share your opinion

Have a question or opinion about Scala? Discuss this article in the Articles Forum topic, First Steps to Scala.

使用道具 举报

回复
求职 : Java研发
论坛徽章:
36
2009新春纪念徽章
日期:2009-01-04 14:52:28蛋疼蛋
日期:2012-03-07 10:09:01复活蛋
日期:2012-03-07 10:09:01生肖徽章2007版:马
日期:2012-03-07 10:13:26茶鸡蛋
日期:2012-03-22 18:44:00蛋疼蛋
日期:2012-04-06 10:44:15蛋疼蛋
日期:2012-05-11 13:33:36鲜花蛋
日期:2012-06-04 13:48:57茶鸡蛋
日期:2012-06-05 08:04:53复活蛋
日期:2012-06-06 11:57:42
15#
发表于 2009-8-7 16:26 | 只看该作者
Good posts.

使用道具 举报

回复
论坛徽章:
8
生肖徽章2007版:龙
日期:2009-09-10 11:23:34ITPUB8周年纪念徽章
日期:2009-09-27 10:21:20祖国60周年纪念徽章
日期:2009-10-09 08:28:002010新春纪念徽章
日期:2010-01-04 08:33:082010新春纪念徽章
日期:2010-03-01 11:04:58ITPUB9周年纪念徽章
日期:2010-10-08 09:28:51ITPUB十周年纪念徽章
日期:2011-11-01 16:20:282012新春纪念徽章
日期:2012-01-04 11:49:54
16#
发表于 2009-8-25 03:42 | 只看该作者
难得有人对scala感兴趣啊,呵呵,不错

使用道具 举报

回复
论坛徽章:
0
17#
发表于 2009-9-12 16:50 | 只看该作者
学习了

使用道具 举报

回复
论坛徽章:
1
ITPUB新首页上线纪念徽章
日期:2007-10-20 08:38:44
18#
发表于 2009-10-29 09:40 | 只看该作者
有电子的没?

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表