搜文章
推荐 原创 视频 Java开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发
Lambda在线 > 我没有三颗心脏 > Java转Ruby【快速入门】

Java转Ruby【快速入门】

我没有三颗心脏 2019-05-15

最近参加实习了,公司的技术栈中需要用到 Ruby 以及 Rails 框架,所以算是开始了踩坑之旅吧..

Ruby 简介

网上的简介要搜都能搜到,具体涉及的包括历史啦之类这里不再赘述,提几个关键词吧:

  • 1993 年由日本的松本行弘创建

  • 纯粹面相对象编程/ 脚本语言/ 解释型/ 动态类型

对于准备迈入 Ruby 的 Java 程序员来说,有几个地方需要特别的去了解一下。

  • 纯粹面相对象
    其实经过论证,Java 同 Ruby 一样都是纯粹的面相对象的语言,这也就意味着包含所有的数字等在内都是对象,注意所有的都是。

  • 脚本语言
    这意味着你写的程序不用编译就能运行,甚至实时生效。

  • 解释型
    同 Java 一样,Ruby 有自己的虚拟机,运行需要一定的环境,也就是 Ruby 解释器,它会负责把 Ruby 翻译成及其能够执行的代码。

  • 动态类型
    Ruby 中的数据更像是一种符号,在使用的时候不检查类型,而是在运行时动态的检查。

为什么是 Ruby ?

  • 原因很简单:高效/ 灵活/ 优雅/ 简单

如果你再稍微花一些心思搜索一下 Ruby on Rails 这个 Web 开发框架,并且打开一些详细说明了体验之后的文章或者是多年经验开发者的分享,你可能会对它产生一些兴趣,这一Part就留着之后介绍了,这也是为以后学习 RoR 框架做准备的。

总之我们要明确我们目的:知道最基本的语法,理解其中的一些关键概念即可。

Ruby 初体验

Mac OX 中有默认的 Ruby 环境,我们可以来一个最短的 "Hello World" 程序,首先在控制台中输入 irb 命令,然后输入 puts "Hello World!" 命令:

irb
irb(main):001:0> puts "Hello World!"
Hello World!
=> nil

你就能看到紧跟着你的输入会有一个 Hello World! 的输出以及一个 nil (对应 Java 中的 null)的返回。

再来一个更加复杂的例子,我们这一次来创建一个数组然后循环输出它:

irb(main):002:0> properties = ['name','age','sex']
=> ["name""age""sex"]
irb(main):003:0> properties
=> ["name""age""sex"]
irb(main):005:0> properties.each {|property| puts "This is #{property}."}
This is name.
This is age.
This is sex.
=> ["name""age""sex"]

不知道感觉如何?至少我们可以直观的感受到:

  • 不用生命变量,直接 = 就好

  • 每条 Ruby 代码都会返回某个值

从 Java 到 Ruby

Java 非常成熟,并且通过  Spring 的加持得到了许多企业的青睐,但是不知道大家有没有感受到一点:它或许有些啰嗦…(我乱说的啊,我也不知道,别问我啊..)从 Java 到 Ruby 据说可以预见性的将代码的规模量大大缩小,因此也能使用更少的时间来输出产品原型。

相似点

Ruby 与 Java 有一些相似的地方…

  • 垃圾回收器帮你管理内存。

  • 强类型对象。

  • 有 public、 private 和 protected 方法。

  • 拥有嵌入式文档工具(Ruby 的工具叫 rdoc)。rdoc 生成的文档与 javadoc 非常相似。

不同点

Ruby 与 Java 不同的地方…

  • 你不需要编译你的代码。你只需要直接运行它。

  • 有几个不同的流行的第三方GUI工具包。Ruby 用户可以尝试 WxRuby、 FXRuby、 Ruby-GNOME2、 Qt 或 Ruby 内置的 Tk。

  • 定义像类这样的东西时,可以使用 end 关键字,而不使用花括号包裹代码块。

  • 使用 require 代替 import

  • 所有成员变量为私有。在外部,使用方法获取所有你需要的一切。

  • 方法调用的括号通常是可选的,经常被省略。

  • 一切皆对象,包括像 2 和 3.14159 这样的数字。

  • 没有静态类型检查。

  • 变量名只是标签。它们没有相应的类型。

  • 没有类型声明。按需分配变量名,及时可用(如:a = [1,2,3] 而不是 int[] a = {1,2,3};)。

  • 没有显式转换。只需要调用方法。代码运行之前,单元测试应该告诉你出现异常。

  • 使用 foo = Foo.new("hi") 创建新对象,而非 Foo foo = new Foo("hi")

  • 构造器总是命名为“initialize” 而不是类名称。

  • 作为接口的替代,你将获得“混入(mixins)”。

  • 相比 XML,倾向于使用 YAML。

  • nil 替代 null

  • Ruby 对 ==equals() 的处理方式与 Java 不一样。测试相等性使用 ==(Java 中是 equals())。测试是否为同一对象使用 equals?()(Java 中是 ==)。

以上的相同与不同来自:https://www.ruby-lang.org/zh_cn/documentation/ruby-from-other-languages/to-ruby-from-java/
延伸阅读:https://gquintana.github.io/2017/01/08/From-Java-to-Ruby.html


Ruby 基础

在大致了解了 Ruby 一些基础信息之后,我们开始 Ruby 基础语法的学习,虽然面对一门新的语言,语法啊特性啊之类的了解很有必要,但还是想在了解之前看一看 Ruby 的一些代码规范,好让自己能快速了解 Ruby 的基础上还能养成一个良好的编码习惯。

学习之前必备 - 代码规范

或许有些语句还不能理解,没关系,有一个基础印象就好。

  • 一般来讲,Ruby 中的变量名和方法名使用下划线命名法(小写字母 + _),类名和模块名使用 Java 类似的驼峰命名法

  • 每个缩进级别使用两个 space(又名软 tabs),不要使用硬 tabs

# bad - four spaces
def some_method
    do_something
end

# good
def some_method
  do_something
end
  • 不用使用 ; 来分割语句和表达式。以此推论 - 一行使用一个表达式

# bad
puts 'foobar'# superfluous semicolon

puts 'foo'; puts 'bar' # two expression on the same line

# good
puts 'foobar'

puts 'foo'
puts 'bar'

puts 'foo''bar' # this applies to puts in particular
  • 避免单行方法。即便还是会受到一些人的欢迎,这里还是会有一些古怪的语法用起来很容易犯错. 无论如何 - 应该一行不超过一个单行方法.

# bad
def too_much; something; something_else; end

# okish - notice that the first ; is required
def no_braces_method; body end

# okish - notice that the second ; is optional
def no_braces_method; body; end

# okish - valid syntax, but no ; make it kind of hard to read
def some_method() body end

# good
def some_method
  body
end

空方法是这个规则的例外。

# good
def no_op; end
  • 当赋值一个条件表达式的结果给一个变量时,保持分支的缩排在同一层。

# bad - pretty convoluted
kind = case year
when 1850..1889 then 'Blues'
when 1890..1909 then 'Ragtime'
when 1910..1929 then 'New Orleans Jazz'
when 1930..1939 then 'Swing'
when 1940..1950 then 'Bebop'
else 'Jazz'
end

result = if some_cond
  calc_something
else
  calc_something_else
end

# good - it's apparent what's going on
kind = case year
       when 1850..1889 then 'Blues'
       when 1890..1909 then 'Ragtime'
       when 1910..1929 then 'New Orleans Jazz'
       when 1930..1939 then 'Swing'
       when 1940..1950 then 'Bebop'
       else 'Jazz'
       end

result = if some_cond
           calc_something
         else
           calc_something_else
         end

# good (and a bit more width efficient)
kind =
  case year
  when 1850..1889 then 'Blues'
  when 1890..1909 then 'Ragtime'
  when 1910..1929 then 'New Orleans Jazz'
  when 1930..1939 then 'Swing'
  when 1940..1950 then 'Bebop'
  else 'Jazz'
  end

result =
  if some_cond
    calc_something
  else
    calc_something_else
  end
  • 在方法定义之间使用空行并且一个方法根据逻辑段来隔开。

def some_method
  data = initialize(options)

  data.manipulate!

  data.result
end

def some_methods
  result
end
  • 不要使用区块注释。它们不能由空白引导(=begin 必须顶头开始),并且不如普通注释容易辨认。

# bad
== begin
comment line
another comment line
== end

# good
# comment line
# another comment line
  • 使用括号将def的参数括起来。当方法不接收任何参数的时候忽略括号。

# bad
def some_method()
  # body omitted
end

# good
def some_method
  # body omitted
end

# bad
def some_method_with_arguments arg1arg2
  # body omitted
end

# good
def some_method_with_arguments(arg1, arg2)
  # body omitted
end
  • 从来不要使用 for, 除非你知道使用它的准确原因。大多数时候迭代器都可以用来替 forfor 是由一组 each 实现的 (因此你正间接添加了一级),但是有一个小道道 - for 并不包含一个新的 scope (不像 each)并且在它的块中定义的变量在外面也是可以访问的。(这里有些像 Java 中不用 for-each 语句类似,感兴趣的也可以去搜一搜)

arr = [123]

# bad
for elem in arr do
  puts elem
end

# note that elem is accessible outside of the for loop
elem #=> 3

# good
arr.each { |elem| puts elem }

# elem is not accessible outside each's block
elem #=> NameError: undefined local variable or method `elem'
  • 利用 if and case 是表达式这样的事实它们返回一个结果。

# bad
if condition
  result = x
else
  result = y
end

# good
result =
  if condition
    x
  else
    y
  end
  • 布尔表达式使用&&/||, and/or用于控制流程。(经验Rule:如果你必须使用额外的括号(表达逻辑),那么你正在使用错误的的操作符。)

# boolean expression
if some_condition && some_other_condition
  do_something
end

# control flow
document.save? or document.save!
  • 永远不要使用 unlesselse 组合。将它们改写成肯定条件。

# bad
unless success?
  puts 'failure'
else
  puts 'success'
end

# good
if success?
  puts 'success'
else
  puts 'failure'
end
  • 不用使用括号包含 if/unless/while 的条件。

# bad
if (x > 10)
  # body omitted
end

# good
if x > 10
  # body omitted
end
  • 倾向使用 module,而不是只有类方法的 class。类别应该只在创建实例是合理的时候使用。

# bad
class SomeClass
  def self.some_method
    # body omitted
  end

  def self.some_other_method
  end
end

# good
module SomeClass
  module_function

  def some_method
    # body omitted
  end

  def some_other_method
  end
end
  • 当你希望将模块的实例方法变成 class 方法时,偏爱使用 module_function 胜过 extend self

module Utilities
  extend self

  def parse_something(string)
    # do stuff here
  end

  def other_utility_method(number, string)
    # do some more stuff
  end
end

# good
module Utilities
  module_function

  def parse_something(string)
    # do stuff here
  end

  def other_utility_method(number, string)
    # do some more stuff
  end
end
  • 总是为你自己的类提供 to_s 方法, 用来表现这个类(实例)对象包含的对象.

class Person
  attr_reader :first_name:last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def to_s
    "#@first_name #@last_name"
  end
end
  • 考虑使用 Struct.new, 它可以定义一些琐碎的 accessors, constructor(构造函数) 和 comparison(比较) 操作。

# good
class Person
  attr_reader :first_name:last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
end

# better
class Person < Struct.new(:first_name, :last_name)
end
  • 考虑使用 Struct.new,它替你定义了那些琐碎的存取器(accessors),构造器(constructor)以及比较操作符(comparison operators)。

# good
class Person
  attr_accessor :first_name:last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
end

# better
Person = Struct.new(:first_name:last_namedo
end
  • 不要去 extend 一个 Struct.new - 它已经是一个新的 class。扩展它会产生一个多余的 class 层级 并且可能会产生怪异的错误如果文件被加载多次。

  • 鸭子类型(duck-typing)优于继承。

# bad
class Animal
  # abstract method
  def speak
  end
end

# extend superclass
class Duck < Animal
  def speak
    puts 'Quack! Quack'
  end
end

# extend superclass
class Dog < Animal
  def speak
    puts 'Bau! Bau!'
  end
end

# good
class Duck
  def speak
    puts 'Quack! Quack'
  end
end

class Dog
  def speak
    puts 'Bau! Bau!'
  end
end
  • 当访问一个数组的第一个或者最后一个元素,倾向使用 first 或 last 而不是 [0] 或 [-1]。

  • 优先使用 字符串插值 来代替 字符串串联

# bad
email_with_name = user.name + ' <' + user.email + '>'

# good
email_with_name = "#{user.name} <#{user.email}>"

# good
email_with_name = format('%s <%s>', user.name, user.email)
  • 在对象插值的时候不要使用 Object#to_s,它将会被自动调用。

  • 操作较大的字符串时, 避免使用 String#+ 做为替代使用 String#&lt;&lt;。就地级联字符串块总是比 String#+ 更快,它创建了多个字符串对象。

# good and also fast
html = ''
html << '<h1>Page title</h1>'

paragraphs.each do |paragraph|
  html << "<p>#{paragraph}</p>"
end
  • RuboCop
    RuboCop 是一个基于本指南的 Ruby 代码风格检查工具。RuboCop 涵盖了本指南相当大的部分,支持 MRI 1.9 和 MRI 2.0,而且与 Emacs 整合良好。

  • RubyMine
    RubyMine 的代码检查是 部分基于 本指南的。

基于下面链接简化了大部分,因为有一些编码风格能从 Java 自然的切换过来,而且我们也可以使用相应的工具来检查我们的代码,更多可以看这里:https://ruby-china.org/wiki/coding-style

Ruby 语言基础

其实通过上面的语法规范,我们或多或少能感知到一些 Ruby 的语法,有一些细节的地方可以自行去菜鸟教程or其他网站能看到,下面我们就着一些重要的东西快速的学习一遍吧..

数据类型

Ruby 中有以下几种不同的数据类型:

  • 数字/ 字符串/ 符号/ 哈希/ 数组/ 布尔
    比较在意的是 Ruby 并没有 Java 中的枚举类型,可能是出于安全方面的考虑吧..(我也不知道…)

  • 符号就像字符串。一个符号之前是冒号(:)。例如:

:abcd

它们不包含空格。含有多个单词的符号用(_)写成。字符串和符号之间的一个区别是,如果文本是一个数据,那么它是一个字符串,但如果它是一个代码,它是一个符号。

符号是唯一的标识符,表示静态值,而字符串表示更改的值。

示例:

irb(main):011:0> "string".object_id
=> 26922000
irb(main):012:0> "string".object_id
=> 29115220
irb(main):013:0> :symbol.object_id
=> 788188
irb(main):014:0> :symbol.object_id
=> 788188
  • 哈希将其值分配给其键。它们可以用键关联指定值。键的值由 =&gt; 符号分配。键/值对之间用逗号分隔,所有对都用大括号括起来。例如:

{"key1" => "value1""key2" => "Chemistry""key3" => "Maths"}

示例:

data = {"key1" => "Physics""key2" => "Chemistry""key3" => "Maths"}   
puts data["key1"]   
puts data["key2"]   
puts data["key3"]

执行上述代码,得到以下结果:

Physics
Chemistry
Maths

变量

Ruby 有四种类型的变量,变量的命名方式决定了变量的种类:

  • 局部变量
    以英文小写字母或者 _ 开头,作用域等同于 Java 局部变量。

  • 全局变量
    $ 开头,作用域等同于 Java 全局变量。只要全局变量的名称相同,不管变量在程序的哪个部分使用,程序都认为是它们是同一个变量。未初始化的全局变量的值会被初始化为:nil。建议不要使用全局变量,因为它们使程序变得秘密和复杂。

示例:

$global_var = "GLOBAL"   
class One   
  def display   
     puts "Global variable in One is #$global_var"   
  end   
end   
class Two   
  def display   
     puts "Global variable in Two is #$global_var"   
  end   
end   

oneobj = One.new   
oneobj.display   
twoobj = Two.new   
twoobj.display

执行代码输出结果如下:

Total number of states written: 4
Total number of states written: 4
Total number of states written: 4
Total number of states written: 4
  • 实例变量
    @ 开头,在同一个实例中,程序可以超越方法定义,任意引用、修改实例变量。它属于类的一个实例,可以从方法中的类的任何实例访问。它们只能访问一个特定的类的实例。它们不需要初始化,未初始化的实例变量的值是:nil

示例:

class States   
   def initialize(name)   
      @states_name=name   
   end   
   def display()   
      puts "States name #@states_name"   
    end   
end   

# Create Objects   
first=States.new("Hainan")   
second=States.new("GuangDong")   
third=States.new("Beijing")   
fourth=States.new("ShangDong")   

# Call Methods   
first.display()   
second.display()   
third.display()   
fourth.display()

执行代码输出结果如下:

States name GuangDong
States name Beijing
States name ShangDong
  • 类变量
    @@ 开头,作用域为该类的所有实例。需要在使用前进行初始化,由类的所有后代共享,未初始化的变量将导致错误。

示例:

class States   
   @@no_of_states=0   
   def initialize(name)   
      @states_name=name   
      @@no_of_states += 1   
   end   
   def display()   
     puts "State name #@state_name"   
    end   
    def total_no_of_states()   
       puts "Total number of states written: #@@no_of_states"   
    end   
end   

# Create Objects   
first=States.new("Assam")   
second=States.new("Meghalaya")   
third=States.new("Maharashtra")   
fourth=States.new("Pondicherry")   

# Call Methods   
first.total_no_of_states()   
second.total_no_of_states()   
third.total_no_of_states()   
fourth.total_no_of_states()

执行上面代码输出结果如下:

Total number of states written: 4
Total number of states written: 4
Total number of states written: 4
Total number of states written: 4

类和对象

Object 类是所有 Ruby 对象的默认根。Ruby 对象继承自 BasicObject(它是Ruby中所有类的父类)类,允许创建替代对象层次结构。

首先与 Java 很不同的是创建对象:

Object newObject = new Object(); // Java 中新建对象

对比 Ruby:

objectName = className.new

每个 Ruby 类都是 Class 类的一个实例。通常对于类名,使用驼峰命名规则,类的名称始终以大写字母开头。定义类是用 end 关键字完成的。

语法

class ClassName  
    codes...  
end

我们使用上面学习过的语法规范来创建一个 Person 类(写上 to_s 方法):

class Person
  attr_reader :first_name:last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def to_s
    "#@first_name #@last_name"
  end
end

注意这里的 attr_reader 对应在 Java 中相当于为 first_namelast_name 定义了 getter ,在 Ruby 中,从对象外部不能直接访问实例变量或对实例变量赋值,需要通过方法来访问对象的内部,如果像 Java 那样一遍一遍为每一个变量写 getter/setter 就有些冗杂了,Ruby 为我们提供了一些方便的 存取器。

定义 意义
attr_reader :name 只读(定义 name 方法)
attr_writer :name 只写(定义 name= 方法)
attr_accessor :name 读写(定义以上两个方法)

另外一点上面有一个非常有趣的规范是使用 Struct.new 来简化代码,我觉得很酷也想把它应用在上述 Person 类的创建中,但是发现失败了(不能在其中定义其他功能性的代码),所以可能结论是:这样的简化只适用于一些实体类保存数据的类吧。

方法

Ruby 方法使用 def 关键字开始,最后还需要使用 end 关键字来表示方法定义结束。

语法:

def methodName  
    code...  
end

示例:

def method_defining(a1 = "Ruby", a2 = "Python")
   puts "The programming language is #{a1}"
   puts "The programming language is #{a2}"
end
method_defining "C""C++"
method_defining

运行上面的代码执行结果如下:

The programming language is C
The programming language is C++
The programming language is Ruby
The programming language is Python

方法返回值:

在初探 Ruby 的时候我们就感受到,貌似每一条指令都会返回一个返回值,方法也是这样,在 Ruby 中每个方法都有一个返回值,这个返回的值将是最后一个语句的值。例如:

def my_method
   i = 100
   j = 10
   k = 1
end

上面代码中,最后方法的返回值是 1

Ruby return 语句

Ruby 中的 return 语句用于从 Ruby 方法中返回一个或多个值

示例:

def method
   i = 100
   j = 200
   k = 300
return i, j, k
end
var = method
puts var

上面代码结果如下:

100
200
300

可变参数:

假设声明一个方法需要两个参数,每当调用这个方法时,需要传递两个参数。但是,Ruby允许您声明使用可变数量参数的方法。让我们来看一下这个示例:

def sample (*test)
   puts "The number of parameters is #{test.length}"
   for i in 0...test.length
      puts "The parameters are #{test[i]}"
   end
end
sample "Maxsu""6""F"
sample "Mac""38""M""MCA"

执行上面代码,得到如下结果:

The number of parameters is 3
The parameters are Maxsu
The parameters are 6
The parameters are F
The number of parameters is 4
The parameters are Mac
The parameters are 38
The parameters are M
The parameters are MCA

类方法:

当方法在类定义之外定义时,默认情况下该方法被标记为 private。另一方面,默认情况下,类定义中定义的方法被标记为 public。模块的默认可见性和 private 标记可以通过模块的 publicprivate 更改。

Ruby 给出一种不用实例化一个类就可以访问一个方法。下面来看看看如何声明和访问类方法 -

class Accounts
   def reading_charge
   end
   def Accounts.return_date
   end
end

访问类方法 -

Accounts.return_date

模板

Ruby 模块是方法和常量的集合。暂时你可简单的理解为一个不能实例化的类,这样做的好处是一来可以提供一个命名空间避免名字冲突,另一个是实现了 mixin 的功能。

不知道您有没有发现,Ruby 没有提供多重继承的功能,但 Ruby 的模板几乎消除了多重继承的需要,提供了一种名为 mixin 的装置。

示例:

module A
  def a1
  end
  def a2
  end
end
module B
  def b1
  end
  def b2
  end
end

class Sample
  include A
  include B
  def s1
  end
end

samp=Sample.new
samp.a1
samp.a2
samp.b1
samp.b2
samp.s1

Ruby 代码在其他编程语言中被称为闭包。它由一组代码组成,它们始终用大括号括起来,或者在 do..end 之间书写。大括号语法总是具有比 do..end 语法更高的优先级。也就是说大括号优先级高,do..end 优先级低。

语法:

block_name{
   statement1
   statement2
   ..........
}

yield语句:

def test
   puts "在 test 方法内"
   yield
   puts "你又回到了 test 方法内"
   yield
end
test {puts "你在块内"}

上面代码运行结果如下:

在 test 方法内
你在块内
你又回到了 test 方法内
你在块内

块和方法:

def test
  yield
end
test{ puts "Hello world"}

本实例是实现块的最简单的方式。您使用 yield 语句调用 test 块。

但是如果方法的最后一个参数前带有 &,那么您可以向该方法传递一个块,且这个块可被赋给最后一个参数。如果 *& 同时出现在参数列表中,& 应放在后面。

def test(&block)
   block.call
end
test { puts "Hello World!"}

上述代码运行结果如下:

Hello World!

这一部分建议再去看一下慕课网上的教程,看关于第三章的内容即可:Ruby语言快速入门:https://www.imooc.com/learn/765


总结

经过以上简单的学习,我们对 Ruby 有了一个大致的了解,算是简单入了个门(有一些简单的例如循环啊,判断啊,运算符之类的简单我就没有写了),更多的东西需要自己平时的编码中去总结学习(肯定有一些坑需要自己去填的)。

参考文章:
1.Ruby 教程 - 菜鸟驿站 - http://www.runoob.com/ruby/ruby-intro.html
2.Ruby 教程 - 易百教程 - https://www.yiibai.com/ruby
3.20分钟体验 Ruby - https://www.ruby-lang.org/zh_cn/documentation/quickstart/
4.笨方法学 Ruby - https://lrthw.github.io/intro/
5.Ruby 快速入门 - https://wdxtub.com/2016/03/30/ruby-first-step/


按照惯例黏一个尾巴:


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《Java转Ruby【快速入门】》的版权归原作者「我没有三颗心脏」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注我没有三颗心脏微信公众号

我没有三颗心脏微信公众号:wmyskxz

我没有三颗心脏

手机扫描上方二维码即可关注我没有三颗心脏微信公众号

我没有三颗心脏最新文章

精品公众号随机推荐

上一篇 >>

初探Rsocket