以java、kotlin和scala编写基本spring web应用的比较 -买球官网平台

引用
原文:
作者:
翻译:vincent

译者注:现在可以用来开发web应用的语言五花八门,每种语言都各有千秋,本文作者挑选了java、kotlin 、scala这三种语言,开发同一个基础的spring web应用,从而比对出他们之间的差别。以下为译文。

我一直在想,在jvm语言中选择一个(如scala和kotlin )用来实现同一个基础的spring boot应用程序是多么的困难,所以我决定试试。

源代码可以这个地址看到:

这款应用程序是非常基础的,因为它只包含以下元素:
  • 两个数据库实体
  • 两个repository注解
  • 两个controller控制器
  • 六个endpoint
  • 一个虚拟的静态的index页面
我将用三种语言来做代码比较:
  • java
  • kotlin
  • scala
实体

这个应用里面涉及到了两个实体:customer 和 pet

java
@entity
public class customer {
    @id
    @generatedvalue(strategy = generationtype.auto)
    private long id;
    private string firstname, lastname;
    @jsonignore
    @onetomany(mappedby = "owner")
    private list pets;
    protected customer() {
    }
    public customer(string firstname, string lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }
    // a whole lot of getters and setters here...
    // ommited for the sake of brevity
    @override
    public string tostring() {
        return firstname " " lastname;
    }
}

@entity
public class pet {
    @id
    @generatedvalue(strategy = generationtype.auto)
    private long id;
    private string name;
    @manytoone
    @joincolumn(name = "ownerid", nullable = false)
    private customer owner;
    protected pet() {
    }
    public pet(string name) {
        this.name = name;
    }
    // a whole lot of getters and setters here...
    // ommited for the sake of brevity
    @override
    public string tostring() {
        return name;
    }
}

这里无需多言——因为很显然java是很冗长的,即使去掉getter和setter方法之后,还是会有很多的代码。除了使用lombok可以帮助用户生成模板文件以外,或者类似的工具,我们也没有什么更好的办法。

kotlin

在kotlin语言中有好几种方法可以定义一个实体类,我已经试过两种了。尽管作用都是一样的,但是后者可能更受用户欢迎,因为前者只是简单地在做一些java里面也能做的事情。
// implementation using a regular class, mimicking regular java
@entity
class pet {
    constructor() {
    }
    constructor(name: string) {
        this.name = name
    }
    @id
    @generatedvalue(strategy = generationtype.auto)
    var id: long = 0
    var name: string = ""
    @manytoone
    @joincolumn(name = "ownerid", nullable = false)
    var owner: customer? = null
    override fun tostring(): string = "$name"
}

// implementation using a data class (preferred)
@entity
data class customer(
        @id @generatedvalue(strategy = generationtype.auto) 
        var id: long = 0,
        var firstname: string = "",
        var lastname: string = "",
        @jsonignore @onetomany(mappedby = "owner") 
        var pets: list? = null
) {
    override fun tostring(): string = "$firstname $lastname"
}

尽管第一眼看上去,它不像java代码那样比较直观,但是用数据类实现的话,代码量就要短得多,而且也不需要大量的模板文件。这里的大部分冗余代码都是因为需要做必要的注释。

注意,实体类需要一个默认的没有参数的构造函数——它在常规类的情况下显式提供,而数据类通过为单个构造函数中的每个参数定义 默认值 来提供的 - 包括一个默认值,而没有参数 ,它只是将默认值分配给每个变量。

由于需要将override关键字显示的定义出来,这样做代码更容易阅读,出现错误的概率也会降低,所以我挺喜欢这种做法的。

scala
@entity
class customer {
  // need to specify a parameterized constructor explicitly
  def this(firstname: string, lastname: string) {
    this()
    this.firstname = firstname
    this.lastname = lastname
  }
  // beanproperty needed to generate getters and setters
  @id
  @generatedvalue(strategy = generationtype.auto)
  @beanproperty
  var id: long = _
  @beanproperty
  var firstname: string = _
  @beanproperty
  var lastname: string = _
  @jsonignore
  @onetomany(mappedby = "owner")
  @beanproperty
  var pets: java.util.list[pet] = _
  override def tostring(): string = s"$firstname $lastname"
}

@entity
class pet {
  def this(name: string, owner: customer) {
    this()
    this.name = name
    this.owner = owner
  }
  @id
  @generatedvalue(strategy = generationtype.auto)
  @beanproperty
  var id: long = _
  @beanproperty
  var name: string = _
  @manytoone
  @joincolumn(name = "ownerid", nullable = false)
  @beanproperty
  var owner: customer = _
}

实际上仅针对这种情况,我对scala感到失望——它的实现几乎和java一样冗长,它们的区别就在于scala不需要显示的定义好getter和setter方法,它只需要使用额外的字段注释(@beanproperty)就可以了。

我试图使用一个来减少代码实现的行数,这在理论上是可以行的通的,但是我不能让它运行起来(也许这根本原因就是因为我使用scala不熟)。

至少它提供了字符串插值(string interpolation),允许在一行中使用大括号,并且需要显式的
override关键字,这与kotlin是一致的。

repositories

java

@repository
public interface customerrepository extends crudrepository {
    list findbylastname(string lastname);
}

@repository
public interface petrepository extends crudrepository {
}

注意,findbylastname函数实际上并没有在其它地方进行调用,我定义它只是用来提供一个示例的。

kotlin
@repository
interface customerrepository : crudrepository {
    fun findbylastname(name: string): list
}

`@repository
interface petrepository : crudrepository`

这里没有太大的区别,代码基本上是一样的。kotlin版本的代码稍微短一点,这是因为kotlin的默认修饰符是public的,而且有一个:符号而不是extends关键字。此外,也有可能是如果没有在body中定义任何内容的话,就有可能可能会忽略花括号。

scala
@repository
trait customerrepository extends crudrepository[customer, java.lang.long] {
  def findbylastname(lastname: string): list[customer]
}

@repository
trait petrepository extends crudrepository[pet, java.lang.long]


scala使用的是,而不是interfaces,但在大部分情况下它们都是相同的概念,或者至少针对我们这个简单的例子而言它们是一样的。

由于某些原因,需要将long类明确定义为java.lang.long以避免编译错误(我再次对scala感到失望)。

controllers控制器

java
@restcontroller
@requestmapping("/customers")
public class customercontroller {
    private customerrepository customerrepository;
    @autowired
    public customercontroller(customerrepository customerrepository) {
        this.customerrepository = customerrepository;
    }
    @getmapping(value = "/{id}", produces = "application/json")
    public customer getcustomer(@pathvariable("id") long id) {
        return customerrepository.findone(id);
    }
    @getmapping(produces = "application/json")
    public list getallcustomers() {
        return (list) customerrepository.findall();
    }
    @getmapping(value = "/formatted", produces = "application/json")
    public list getallcustomersformatted() {
        return ((list) customerrepository.findall())
                .stream()
                .map(
                    customer -> customer.getfirstname() " " customer.getlastname()
                )
                .collect(collectors.tolist());
    }
    @postmapping(produces = "application/json",
                 consumes = "application/json")
    public customer addcustomer(@requestbody customer customer) {
        return customerrepository.save(customer);
    }
}

@restcontroller
@requestmapping("/pets")
public class petcontroller {
    @autowired
    private petrepository petrepository;
    @getmapping(produces = "application/json")
    public list getallpets() {
        return (list) petrepository.findall();
    }
    @postmapping(produces = "application/json",
                 consumes = "application/json")
    public pet addpet(@requestbody pet pet) {
        return petrepository.save(pet);
    }
}

scala

@restcontroller
@requestmapping(array("/customers"))
class customercontroller (
  private val customerrepository: customerrepository
) {
  @getmapping(value = array("/{id}"),
              produces = array("application/json"))
  def getcustomer(@pathvariable("id") id: long) = customerrepository.findone(id)
  @getmapping(produces = array("application/json"))
  def getallcustomers() = customerrepository.findall()
  @getmapping(value = array("/formatted"),
              produces = array("application/json"))
  def getallcustomersformatted() = {
    customerrepository
      .findall()
      .asscala
      .map(_.tostring())
      .asjava
  }
  @postmapping(produces = array("application/json"),
               consumes = array("application/json"))
  def addcustomer(@requestbody customer: customer) = customerrepository.save(customer)
}

@restcontroller
@requestmapping(array("/pets"))
class petcontroller {
  @autowired
  var petrepository: petrepository = null
  @getmapping(produces = array("application/json"))
  def getallpets = petrepository.findall()
  @postmapping(produces = array("application/json"),
               consumes = array("application/json"))
  def addpet(@requestbody pet: pet) = petrepository.save(pet)
}

customercontroller是通过构造函数注入的,而petcontroller则是通过字段注入的,这么做是为了提供出两种不同的方式——kotlin和scala也是同样的处理逻辑。

同样,java的话,代码还是显得很冗长,尽管其中很大一部分来自于健壮的注释(使用@get/postmapping代替@requestmapping来减少注释的大小)。值得注意的是,java 8将会解决这个问题,因为由于缺少lambda函数,getallcustomersformatted()函数在java 7中会变得更加臃肿。

kotlin

@restcontroller
@requestmapping("/customers")
class customercontroller(val customerrepository: customerrepository) {
    @getmapping(value = "/{id}", produces = arrayof("application/json"))
    fun getcustomer(@pathvariable("id") id: long): customer? = 
            customerrepository.findone(id)
    @getmapping(value = "/formatted", produces = arrayof("application/json"))
    fun getallcustomersformatted() = 
            customerrepository.findall().map { it.tostring() }
    @getmapping(produces = arrayof("application/json"))
    fun getallcustomers() = customerrepository.findall()
    @postmapping(produces = arrayof("application/json"),
                 consumes = arrayof("application/json"))
    fun addcustomer(@requestbody customer: customer): customer? = 
            customerrepository.save(customer)
}

@restcontroller
@requestmapping("/pets")
class petcontroller {
    // when using autowired like this we need to make the variable lateinit
    @autowired
    lateinit var petrepository: petrepository
    @getmapping(produces = arrayof("application/json"))
    fun getallpets() = petrepository.findall()
    @postmapping(produces = arrayof("application/json"),
                 consumes = arrayof("application/json"))
    fun addpet(@requestbody pet: pet): pet? = petrepository.save(pet)
}

乍一看,这似乎和java一样冗长,这很让人吃惊,但我们必须注意到,这种冗长的代码大部分来自于所需的注释。除去这些,控制器的主体仅仅只有4行。

当然,如果我要将@requestmapping注释写在一行中,那么它就不会那么简单了,但是在博客文章中,可读性就会首先出现。

使用@get/postmapping注释可以让我们至少跳过方法参数,以减少注释的大小。理论上,我们可以去掉produces和consumes,但这也会使xml成为一个可行的选择——所以这些params并不是多余的。

需要指出的一件令人讨厌的事情是,如果需要使用多个参数(除了默认值以外),那么在注解中使用arrayif()是必要的。这将在。

我喜欢这个构造函数注入芬兰湾的科特林提供了(我们甚至不需要一个@ autowired注解出于某种原因[这是原因])虽然看起来令人困惑如果类更大,更依赖项注入,我想说这是一个机会,在这种情况下适当的格式。

我喜欢这个构造函数注入芬兰湾的科特林提供了(我们甚至不需要一个@ autowired注解出于某种原因[这是原因])虽然看起来令人困惑如果类更大,更依赖项注入,我想说这是一个机会,在这种情况下适当的格式。

scala

@restcontroller
@requestmapping(array("/customers"))
class customercontroller (
  private val customerrepository: customerrepository
) {
  @getmapping(value = array("/{id}"),
              produces = array("application/json"))
  def getcustomer(@pathvariable("id") id: long) = customerrepository.findone(id)
  @getmapping(produces = array("application/json"))
  def getallcustomers() = customerrepository.findall()
  @getmapping(value = array("/formatted"),
              produces = array("application/json"))
  def getallcustomersformatted() = {
    customerrepository
      .findall()
      .asscala
      .map(_.tostring())
      .asjava
  }
  @postmapping(produces = array("application/json"),
               consumes = array("application/json"))
  def addcustomer(@requestbody customer: customer) = customerrepository.save(customer)
}

@restcontroller
@requestmapping(array("/pets"))
class petcontroller {
  @autowired
  var petrepository: petrepository = null
  @getmapping(produces = array("application/json"))
  def getallpets = petrepository.findall()
  @postmapping(produces = array("application/json"),
               consumes = array("application/json"))
  def addpet(@requestbody pet: pet) = petrepository.save(pet)
}

scala还需要在提供参数时使用array关键字,即使是默认的参数也需要。

getallcustomersformatted()函数,这是一种暴行,但我不能让java集合正确地使用scala集合——所以,对不起,我的眼睛(划痕,代码在teemu pöntelin的帮助下得到了改进,谢谢:))。

请注意,必须在构造函数中包含@autowired(),这可能在kotlin中跳过(如果您只有一个构造函数,那么实际上根本不需要@autowired),如这里所解释的那样)。

总结

尽管这个应用程序非常简单,但是对于我来说,这足以让我对如何在每一门特色语言中做一些更深入的了解有一个基本的感觉。

如果需要在 kotlin 和 scala 之间做个选择,毫无疑问我的选择是kotlin

为什么呢?

首先,我觉得scala就好像是intellij idea中的二等公民一样,而kotlin无疑是一等公民。这是显而易见的,因为创建ide(jetbrains)的公司和创建kotlin语言的公司是同一家的——所以他们当然非常支持这门语言。另一方面,scala是通过一个插件集成的。两者的区别是显而易见的,至少对我个人来说,这种区别是非常重要的。

其次,如果我想用scala为web应用程序开发框架,我就会选择 ,原因很简单,就是因为它设计的思维是基于scala 的,而且开发语言能使得某些事情变得更容易,而不是妨碍你(就像在这个小应用程序的情况下)。

这些都是我个人的原因,但也有更多、更普遍的原因。

我觉得scala比kotlin更脱离java,因为后者基本上算是一种扩展,旨在解决java最初存在的问题,而前者的目标是将命令式编程和函数式编程混合在一起。尽管如此,我相信scala在其他领域更好地使用,比如大数据,而kotlin在它应该做的事情上做得很好——取代java解决一些比较常见的问题,并提供紧密的互操作性。

此外,spring本身似乎对kotlin 的支持远远超过了对 scala的支持。

最后,我相信,从java程序员的角度来看,kotlin比scala更容易学习。这主要是因为kotlin被设计为基于java进行的改进,并没有像scala那样重视函数式编程。在kotlin中,与java的互操作性也更加紧密,这使得调试问题更加容易。

最后,但同样重要的是——我想明确地声明我不会以任何方式抨击scala。就我个人而言,我认为 如果用一门非java的jvm 语言去开发一个spring boot的web应用程序——kotlin会是更好的选择。粗体部分是很重要的:)正如前面提到的,在其他领域,scala是很优秀的,比如前面提到的大数据,但想要取代java目前估计还有一段很长的路要走。
1
1
评论 共 6 条 请登录后发表评论
6 楼 2017-07-26 09:01
这年头都喜欢使用 var , let 关键字。。 swift, kotlin, scala
5 楼 tedeum 2017-07-18 09:08
wkcgy 写道
咋想的啊?用scala开发spring boot应用,scala生态里大把的web框架,开发效率和体检秒杀spring boot java。作者太逗了。。。

框架还停留在web阶段
4 楼 cs6641468 2017-07-18 08:59
wkcgy 写道
咋想的啊?用scala开发spring boot应用,scala生态里大把的web框架,开发效率和体检秒杀spring boot java。作者太逗了。。。

你也挺逗,还"秒杀", 自己去看看有几个像样的网站是scala的, 语言都要给自己定位好,用来写工具和脚本就行了,别太当真。
3 楼 2017-07-18 08:38
恩。。。这个不错,评论很好,去学习下
2 楼 2017-07-18 08:08
咋想的啊?用scala开发spring boot应用,scala生态里大把的web框架,开发效率和体检秒杀spring boot java。作者太逗了。。。
1 楼 2017-07-17 10:51
特别喜欢scala

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 弹簧匕首 比较使用dagger 2与spring 5在比萨在线商店中如何创建和测试简单服务 这个主意 编写一个包含以下内容的典型(虚拟)微服务: ... 可能是java,kotlin,scala,clojure,groovy甚至是frege。

  • 写在前面 ...最近准备除了java本身之外在学习一种jvm生态下的语言,scala和kotlin都是候选语言。 我在写spark相关程序时用过scala,在我自己的业余项目中在尝试kotlin和go,两者都在不同的方向创...

  • 基于spring boot和kotlin的联合开发 买球官网平台的版权声明:本文为博主chszs的原创文章,未获得博主授权均不能转载,否则视为侵权。 一、概述spring官方最近...这并不会令人意外,因为pivotal团队以广泛接纳​​jvm语言(如scala和g

  • 在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。 java是开发微服务架构时使用的一种很棒的语言。 实际上,我们行业中的一些知名人士都在使用它。 您是否听说过netflix,amazon或google? 那...

  • 从spring boot 2开始,boot也开始正式支持kotlin编程,我们可以在创建spring boot应用时程序时使用spring初始化kotlin,不过kotlin要在新的spring 5版本中才得到支持。 kotlin kotlin(https://kotlinlang.org/...

  • 今天,人们想要具有强大用户体验的高响应、交互式应用程序,这通常意味着处理异步性,尤其是当应用程序涉及高负载、实时数据和多用户时。 由于 java 是一种面向对象的语言,本质上支持命令式编程风格,因此异步性是...

  • 前言 spring官方最近宣布,将...这并不会令人意外,因为pivotal团队以广泛接纳​​jvm语言(如scala和groovy)而闻名。 kotlin 是一个基于 jvm 的编程语言,它的简洁、便利早已不言而喻。kotlin 能够胜任 java 做的...

  • 介绍 java于1996年1月23日发布,并于2020年庆祝其成立24周年。根据tiobe指数,它一直位居第一。...如果要使用java开发下一个web应用程序,则在选择适当的java web框架时需要做出正确的选择。您是否想知道如何为项目...

  • 其实客观来讲,在服务端开发中考量更多的并不是什么语法糖能带来多大的开发便捷。语言语法精简完备能为开发...以基于etcd v3的grpc为例, 该协议有这么一堆语言实现那么像 rust,lisp 一类的无实现的语言就在考虑的时...

  • pluralsight宣布,所有7000多个由专家主导的课程在2020年4月为期一个月免费,以支持因covid-19而留在家中的人们。 在过去的几周中,我们的工作和生活方式发生了很大变化,现在大多数人都待在家里。 有了如此多的不...

  • 根据tiobe指数(译者注:该排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量,使用搜索引擎统计出各类排名)的显示:java持续排名第一。java已证明了自己配得上用于定制软件开发的优秀通用编程语言。而且...

  • 在本课程中,我们提供一个框架和工具集,以便您可以开发现代的scala应用程序。 我们涵盖了广泛的主题,从sbt构建和响应式应用程序到测试和数据库访问。 通过我们简单易懂的教程,您将能够在最短的时间内启动并运行...

  • 在本课程中,我们提供一个框架和工具集,以便您可以开发现代的scala应用程序。 我们涵盖了广泛的主题,从sbt构建和响应式应用程序到测试和数据库访问。 通过我们简单易懂的教程,您将能够在最短的时间内启动并运行...

  • 文章目录java 开发看的 scala 入门前言认识 scalascala 的语言特性scala 环境搭建scala 命令行交互scala repl编译 scala 类scala 语法变量数据类型nothing 和 null值类型转换方法/函数流程控制条件判断循环处理类...

  • 第1章 kotlin是什么当下互联网大数据云计算时代,数以百万计的应用程序在服务器、移动手机端上运行,其中的开发语言有很大一部分是用流行软件界20多年的、强大稳定的主力的编程语言java编写。如果我们用一辆汽车来...

global site tag (gtag.js) - google analytics