我以前经常用Python编写代码。现在,出于工作原因,我用Java编写代码。我从事的项目很小,也许Python可以做得更好,但是有使用Java的非工程方面的正当理由(我无法详细介绍)。这只是另一种语言。但是,除了语法之外,Java还具有被视为“正确”的文化,一套开发方法和实践。现在,我完全无法“培养”这种文化。因此,我将不胜感激朝正确方向进行的解释或指示。一个具有多个值的结果-java-way / 43620339

我有一个任务-解析(从单个字符串)并处理一组三个值。在Python中,它是一个单行(元组),在Pascal或C中,它是一个五行记录/结构。可以在广泛使用的Apache库中使用-但是“正确”的实现方法实际上是通过为值创建一个单独的类,并同时包含getter和setter来完成。有人很乐意提供一个完整的例子。它是47行代码(嗯,其中的某些行是空白)。

我知道,庞大的开发社区可能不会“错”。因此,这是我的理解所面临的问题。 C惯例针对资源使用进行了优化。 Java实践针对什么进行了优化?我最好的猜测是可伸缩性(一切都应准备好以百万计的LOC项目状态),但这是一个非常微不足道的猜测。

评论

不会回答问题的技术方面,但仍在上下文中:softwareengineering.stackexchange.com/a/63145/13899

您可能会喜欢史蒂夫·耶格(Steve Yegge)在《名词王国》中的处决

请记住,评论是要求作者做出澄清,留下建设性的批评意见,以指导文章的改进,或者添加相关但次要或暂时的信息。请使用聊天进行进一步的讨论。

我认为这是正确的答案。您并不是在厌烦Java,因为您考虑的是像sysadmin这样的程序而不是程序员。对您来说,软件是您用来以最快的方式完成特定任务的工具。我已经编写Java代码已有20年了,而我从事的一些项目需要20、2年的团队才能完成。 Java不能替代Python,反之亦然。他们俩都在做他们的工作,但是他们的工作完全不同。

Java是事实上的标准的原因是因为它完全正确。它是第一次由认真的人创建的,这些人在胡须时髦之前就已经留着胡须。如果您习惯于敲击shell脚本来操作文件,那么Java似乎会令人ler肿。但是,如果要创建一个双冗余服务器集群,该集群每天能够为5000万用户提供服务,并由集群的redis缓存,3个计费系统和一个2000万磅的oracle数据库集群提供支持。 Python中的数据库少了25行代码。

#1 楼

Java语言

我相信,所有这些答案都试图通过将意图归因于Java的工作方式而忽略了这一点。 Java的冗长性并非源于面向对象,因为Python和许多其他语言也都具有更短的语法。 Java的冗长性也不来自于对访问修饰符的支持。相反,它只是Java的设计和发展方式。

Java最初是用OO进行稍微改进的C语言创建的。因此,Java具有70年代时代的语法。此外,Java在添加功能方面非常保守,以保持向后兼容性并使其经受住时间的考验。 Java在2005年添加了诸如XML文字的流行功能时,当时XML一直风行一时,而该语言本来会因没有人关心的幽灵功能而and肿,并在10年后限制了它的发展。因此,Java只是缺少许多简洁的语法来简洁地表达概念。例如,Java 8添加了lambda和方法引用,从而在许多情况下大大降低了详细程度。 Java可以类似地添加对紧凑数据类型声明(例如Scala的case类)的支持。但是Java根本没有这样做。请注意,自定义值类型即将出现,此功能可能会引入新的语法来声明它们。我想我们会看到的。


Java文化

企业Java开发的历史很大程度上将我们带入了今天的文化。在90年代末/ 00年代初,Java成为服务器端业务应用程序的一种非常流行的语言。那时,这些应用程序主要是临时编写的,并包含了许多复杂的问题,例如HTTP API,数据库和XML提要的处理。

到了20世纪90年代,很明显,许多应用程序具有许多共同点和框架来管理这些问题,例如Hibernate ORM,Xerces XML解析器,JSP和Servlet API以及EJB变得流行。但是,尽管这些框架减少了在它们设置为自动化的特定域中工作的工作量,但它们需要配置和协调。当时,无论出于何种原因,编写框架来迎合最复杂的用例都是很流行的,因此这些库的设置和集成都很复杂。随着时间的推移,它们随着功能的积累而变得越来越复杂。 Java企业开发逐渐变得越来越多地将第三方库集成在一起,而不再需要编写算法了。来管理管理。从理论上讲,您可以将所有配置放在一个位置,然后配置工具将配置零件并将它们连接在一起。不幸的是,这些“框架框架”在整个蜡像球上增加了更多的抽象性和复杂性。但是,随着重型企业框架的发展,整代Java程序员都已经成熟。他们的角色模型(开发框架的角色模型)编写了工厂工厂和代理配置Bean加载程序。他们必须每天配置和集成这些怪兽。结果,整个社区的文化都遵循了这些框架的示例,并且往往严重过度设计。

评论


有趣的是,其他语言似乎正在采用一些“ java-ism”。 PHP OO功能在很大程度上受到Java的启发,如今,JavaScript因其庞大的框架以及收集所有依赖项并启动新应用程序的难度而受到批评。

–马库斯
17年4月28日在14:16

@marcus也许是因为某些人终于学会了“不要重新发明轮子”的东西?依赖性是不重新发明轮子的代价

–沃尔夫特
17年4月28日在15:08



“ Terse”通常会翻译成奥秘的,“聪明的”,编码高尔夫般的单线。

–图兰斯·科尔多瓦(TulainsCórdova)
17年4月29日在4:30

周到,精心编写的答案。历史背景。相对没有意见。做得很好。

– Cheezmeister
17年4月29日在5:20

@TulainsCórdova我同意我们应该“避免...像瘟疫一样的聪明把戏”(Donald Knuth),但这并不意味着在地图更合适(更易读)时编写for循环。有一个快乐的媒介。

–布赖恩·麦卡顿(Brian McCutchon)
17年4月29日在15:21

#2 楼

我相信对于您提出的其他人没有提出的观点之一,我有一个答案。

Java从不进行任何假设就优化了编译时程序员错误的检测

通常,Java倾向于仅在程序员已经明确表达其意图之后推断有关源代码的事实。 Java编译器从不对代码进行任何假设,而只会使用推理来减少冗余代码。

这种理念背后的原因是程序员只是人类。我们编写的内容并不总是我们打算实际执行的程序。 Java语言通过强制开发人员始终显式声明其类型来尝试缓解其中的某些问题。这只是仔细检查所编写的代码是否确实达到了预期目的的一种方法。

其他一些语言通过检查前置条件,后置条件和不变量来进一步推动该逻辑(尽管我不确定他们是在编译时这样做的。对于程序员来说,这些甚至是更极端的方法,使编译器再次检查自己的工作。

,这意味着为了使编译器保证您实际上返回的是您自己的类型如果您要返回,则需要将该信息提供给编译器。

在Java中,有两种方法可以做到这一点: (这确实应该在Triplet<A, B, C>中,并且我不能真正解释为什么不是这样。特别是因为JDK8引入了java.utilFunctionBiFunctionConsumer等...似乎至少BiConsumerPair才有意义。但我离题了。
为此目的创建您自己的值类型,其中每个字段的名称都正确地命名和键入。
它声明,并且调用方意识到每个返回字段的类型是什么,并相应地使用它们。

某些语言确实同时提供了静态类型检查和类型推断,但这为微妙的类型不匹配问题类打开了大门。如果开发人员打算返回某个类型的值,但实际上却返回了另一个类型,而编译器仍接受该代码,因为碰巧的是,由于函数和调用方的共同碰巧,该函数和调用者仅使用可以应用于预期值的方法和实际类型。

在Typescript(或流类型)中考虑类似这样的情况,其中使用类型推断而不是显式输入。

function parseDurationInMillis(csvLine){
    // here the developer intends to return a Number, 
    // but actually it's a String
    return csv.firstField();
}

// Compiler infers that parseDurationInMillis is String, so it does
// string concatenation and infers that plusTwoSeconds is String
// Developer actually intended Number
var plusTwoSeconds = 2000 + parseDurationInMillis(csvLine);


当然,这是一个愚蠢的琐碎情况,但由于代码看起来正确,所以它可能更加微妙,并导致难以调试问题。用Java完全避免了这种问题,这就是整个语言的设计方向。


请注意,遵循正确的面向对象原则和域驱动建模,持续时间解析链接问题中的case也可以作为Triplet对象返回,这比上述两种情况都更加明确。

评论


指出Java针对代码正确性进行了优化可能是正确的观点,但是在我看来(编程许多语言,最近使用了一些Java),该语言要么失败要么效率极低。诚然,Java很老,那时还不存在很多东西,但是有其他现代替代方法可以提供更好的正确性检查,例如Haskell(我敢说吗?Rust或Go)。 Java的所有笨拙对于此目标都是不必要的。 -而且,这根本不能解释文化,但是所罗门诺夫(Solomonoff)透露该文化仍然是BS。

–涂药
17-4-26在20:49



该示例并没有证明类型推断就是问题,只是JavaScript决定允许使用动态语言进行隐式转换是愚蠢的。任何具有类型推断的理智的动态语言或静态吹捧的语言都不允许这样做。作为这两种示例:python会抛出一个运行时例外,Haskell不会编译。

– Voo
17-4-26在23:02



@tomsmeding值得注意的是,这种说法特别愚蠢,因为Haskell的存在时间比Java长了5年。 Java可能具有类型系统,但是在发布时甚至没有最新的正确性验证。

–亚历克西斯·金(Alexis King)
17年4月27日在0:30

“ Java为编译时错误检测进行了优化” —否。它提供了它,但是,正如其他人所指出的那样,从任何意义上讲,它当然都没有为此进行优化。此外,该论点与OP的问题完全无关,因为实际上针对编译时错误检测进行了优化的其他语言在类似情况下的语法也更加轻巧。 Java的过度冗长与编译时验证完全无关。

–康拉德·鲁道夫(Konrad Rudolph)
17-4-27在14:40



@KonradRudolph是的,尽管我认为这在情感上很有吸引力,但这个答案完全是荒谬的。我不确定为什么会有这么多票。与Java相比,Haskell(或更重要的是Idris)针对正确性的优化要多得多,它具有具有轻量级语法的元组。更重要的是,定义数据类型是单线的,您将获得Java版本所能获得的一切。这个答案是糟糕的语言设计和不必要的冗长的借口,但是如果您喜欢Java并且不熟悉其他语言,这听起来不错。

–亚历克西斯·金(Alexis King)
17年4月27日在17:25

#3 楼

Java和Python是我使用最多的两种语言,但我来自另一个方向。也就是说,在开始使用Python之前,我对Java领域很了解,因此我可能会提供帮助。我认为,“为什么东西这么重”这个更大的问题的答案可以归结为两点:气球。您可以挤压气球的一部分,而另一部分则膨胀。 Python倾向于挤压早期部分。 Java压缩了后面的部分。
Java仍然缺少一些功能,这些功能可以减轻一些负担。 Java 8在这方面发挥了巨大作用,但是这种文化尚未完全消化这些变化。 Java还可以使用诸如yield之类的其他功能。我曾经用Python编写东西,一年后又看了一下,对自己的代码感到困惑。在Java中,我可以查看其他人的代码的小片段,并立即知道它的作用。在Python中,您无法真正做到这一点。正如您似乎意识到的那样,并不是一个更好的选择,而是它们具有不同的成本。

在您提到的特定情况下,没有元组。一种简单的解决方案是创建一个具有公共价值的类。 Java刚问世时,人们经常这样做。这样做的第一个问题是维护麻烦。如果您需要增加一些逻辑或线程安全性或想要使用多态性,则至少需要触摸与该“元组式”对象进行交互的每个类。在Python中,有诸如__getattr__之类的解决方案,因此它并不是那么可怕。

虽然有一些不良习惯(IMO)。在这种情况下,如果您想要一个元组,我会问为什么要使其成为可变对象。您只需要getter(附带说明,我讨厌get / set约定,但这就是它的意思。)我确实认为裸类(是否可变)在Java的私有或程序包私有上下文中很有用。 。也就是说,通过将项目中的引用限制为该类,您可以稍后根据需要进行重构,而无需更改类的公共接口。这是一个如何创建简单的不可变对象的示例:

public class Blah 
{
  public static Blah blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    return new Blah(number, isInSeconds, lessThanOneMillis);
  }

  private final long number;
  private final boolean isInSeconds;
  private final boolean lessThanOneMillis;

  public Blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    this.number = number;
    this.isInSeconds = isInSeconds;
    this.lessThanOneMillis = lessThanOneMillis;
  }

  public long getNumber()
  {
    return number;
  }

  public boolean isInSeconds()
  {
    return isInSeconds;
  }

  public boolean isLessThanOneMillis()
  {
    return lessThanOneMillis;
  }
}


我使用的是这种模式。如果您不使用IDE,则应该开始。它将为您生成吸气剂(如果需要它们,也会有吸气剂),这样就不会那么痛苦了。

如果我不指出已经存在一种可以似乎可以满足您的大多数需求。抛开这些,您正在使用的方法不太适合Java,因为它发挥了弱点,而不是长处。这是一个简单的改进:

public class Blah 
{
  public static Blah fromSeconds(long number)
  {
    return new Blah(number * 1000_000);
  }

  public static Blah fromMills(long number)
  {
    return new Blah(number * 1000);
  }

  public static Blah fromNanos(long number)
  {
    return new Blah(number);
  }

  private final long nanos;

  private Blah(long nanos)
  {
    this.nanos = nanos;
  }

  public long getNanos()
  {
    return nanos;
  }

  public long getMillis()
  {
    return getNanos() / 1000; // or round, whatever your logic is
  }

  public long getSeconds()
  {
    return getMillis() / 1000; // or round, whatever your logic is
  }

  /* I don't really know what this is about but I hope you get the idea */
  public boolean isLessThanOneMillis()
  {
    return getMillis() < 1;
  }
}


评论


评论不作进一步讨论;此对话已移至聊天。

– Maple_shaft♦
17年4月27日在20:15

我是否可以建议您也针对“更现代”的Java功能来查看Scala ...

– MikeW
17年4月28日在12:12

@MikeW实际上,我早在早期版本中就从Scala开始的,并且在scala论坛上活跃了一段时间。我认为这是语言设计方面的一项伟大成就,但我得出的结论不是我真正想要的,我是那个社区中的钉子。我应该再看一遍,因为自那时以来,它可能已经发生了重大变化。

– JimmyJames
17年4月28日在15:21

该答案未解决OP提出的近似值方面。

– ErikE
17年4月30日在14:46

@ErikE问题文本中未出现单词“近似值”。如果您可以指出我未解决的问题的特定部分,则可以尝试这样做。

– JimmyJames
17年5月1日下午14:01

#4 楼

在对Java感到生气之前,请阅读我在另一篇文章中的答案。

您的抱怨之一是需要创建一个类以仅返回一些值作为答案。我认为这是一个有效的担忧,表明您的编程直觉是正确的!但是,我认为其他答案由于坚持您所致力于的原始痴迷反模式而没有实现。而且Java使用多个原语的便利性不如Python,在Python中,您可以本地返回多个值并轻松将它们分配给多个变量。 type为您做到了,您意识到它的范围并不狭窄,只能“看似不必要的类才能返回三个值”。此类所代表的概念实际上是您的核心领域业务概念之一-需要能够以近似的方式表示时间并进行比较。它必须是应用程序核心通用语言的一部分,并具有良好的对象和域支持,以便可以对其进行测试,模块化,可重用和有用。

是您的代码整个过程都将近似的持续时间(或持续时间有误差的总和)加在一起完全是程序上的,或者是否有任何对象性?我建议,围绕近似持续时间求和的良好设计将指示在可以测试的类中的任何消耗代码之外进行此设计。我认为使用这种域对象会在您的代码中产生积极的连锁反应,从而帮助您摆脱逐行的程序步骤来完成单个高级任务(尽管有很多职责),而转向单一职责类不受不同关注点冲突的影响。

例如,假设您了解更多有关持续时间求和和比较正常工作实际需要的精度或小数位数的信息,并且发现需要一个中间标记来指示“大约32毫秒错误”(靠近平方1000的根,因此在1到1000之间对数地对数)。如果您已绑定到使用基元表示此代码的代码,则必须在代码中找到ApproximateDuration所在的每个位置,并将其更改为is_in_seconds,is_under_1ms。到处都会改变一切!安排一个班级来负责记录误差范围,以便可以在其他地方使用它,这会使您的消费者无需了解关于误差范围重要或它们如何组合的任何细节,而让他们仅指定相关的误差范围在这一刻。 (也就是说,在类中添加新的错误裕度级别时,不会强制更改其错误裕度正确的使用方代码,因为所有旧的错误裕度仍然有效。)

结束语

随着您进一步接近SOLID和GRASP原理以及更高级的软件工程,对Java繁重的抱怨似乎消失了。

附录

我将完全免费且不公平地添加一点,C#的自动属性和在构造函数中分配仅获取属性的能力有助于进一步清理“ Java方式”所需的有点混乱的代码(具有显式的私有支持字段和getter / setter函数):

 is_in_seconds,is_about_32_ms,is_under_1ms 


这是上述的Java实现:

 // Warning: C# code!
public sealed class ApproximateDuration {
   public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
      LowMilliseconds = lowMilliseconds;
      HighMilliseconds = highMilliseconds;
   }
   public int LowMilliseconds { get; }
   public int HighMilliseconds { get; }
}
 


现在已经很干净了。请注意不变性的非常重要和有意的用法-这对于这种特殊的具有价值的类来说似乎至关重要。

因此,此类也是public final class ApproximateDuration { private final int lowMilliseconds; private final int highMilliseconds; public ApproximateDuration(int lowMilliseconds, int highMilliseconds) { this.lowMilliseconds = lowMilliseconds; this.highMilliseconds = highMilliseconds; } public int getLowMilliseconds() { return lowMilliseconds; } public int getHighMilliseconds() { return highMilliseconds; } } (值类型)的一个不错的选择。一些测试将显示切换到结构是否具有运行时性能优势(可能)。

评论


我认为这是我最喜欢的答案。我发现,当我将这种a脚的一次性持有人阶级提升为成年后的人时,它将成为承担所有相关职责的场所!一切都变得更加清洁!而且我通常同时会学习一些有关问题空间的有趣信息。显然,有一种避免过度设计的技巧……但是,当Gosling忽略了元组语法时(与GOTO一样),他并不愚蠢。您的总结发言非常出色。谢谢!

– SusanW
17年4月27日在19:01

如果您喜欢此答案,请阅读域驱动设计。关于用代码表示领域概念的这个话题有很多话要说。

– Neontapir
17年4月27日在19:37

@neontapir是的,谢谢!我知道有个名字! :-)虽然我必须说,当领域概念通过一些启发性的重构有机地出现时,我更喜欢它(像这样!)……这有点像当您通过发现海王星来解决19世纪重力问题时。 。

– SusanW
17年4月27日在20:06

@SusanW我同意。重构以消除原始的困扰是发现领域概念的绝妙方法!

– Neontapir
17年4月28日在3:36

我添加了示例的Java版本。我没有掌握C#中所有不可思议的语法功能,所以如果有什么不足之处,请告诉我。

– JimmyJames
17年5月1日在16:11

#5 楼

Python和Java都根据其设计者的理念进行了可维护性的优化,但是他们对实现这一目标的想法却大不相同。代码(易于读取和编写)。

(传统上)Java是一种基于单范例类的OO语言,即使在编写更冗长代码的代价下,也可以优化其明确性和一致性。 >
Python元组是具有固定数量字段的数据结构。带有显式声明的字段的常规类可以实现相同的功能。在Python中,提供元组作为类的替代品是很自然的,因为它可以极大地简化代码,特别是由于对元组的内置语法支持。提供此类快捷方式,因为您已经可以使用显式声明的类。无需引入其他类型的数据结构,只是为了节省一些代码行并避免一些声明。

Java倾向于始终使用最少的特殊情况语法糖的单一概念(类),而Python提供了多种工具和大量语法糖,使您可以针对任何特定目的选择最方便的方法。

评论


+1,但是如何与带有类的静态类型语言(例如Scala)相结合,却发现需要引入元组?我认为在某些情况下,元组只是更整洁的解决方案,即使对于带有类的语言也是如此。

– Andres F.
17-4-26在16:17



@AndresF .:网格是什么意思? Scala是一种不同于Java的语言,具有不同的设计原理和习惯用法。

–雅克B
17年4月26日在17:33

我是在回答您的第5段时用这个词的,它的开头是“但这与Java文化并不完全匹配”。实际上,我同意冗长是Java文化的一部分,但是缺少元组不是因为它们“已经使用了显式声明的类”-毕竟,Scala也具有显式类(并引入了更简洁的case类),但是它还允许元组!最后,我认为Java可能没有引入元组不仅是因为类可以达到相同的目的(更加混乱),而且还因为C程序员必须熟悉其语法,并且C没有元组。

– Andres F.
17-4-26在19:29



@AndresF .: Scala并不反对多种处理方式,它结合了功能范例和经典OO的功能。这样更接近于Python哲学。

–雅克B
17年4月27日在6:01

@AndresF .:是的,Java的语法接近于C / C ++,但是简化了很多。 C#最初是Java的几乎完全相同的副本,但是多年来增加了很多功能,包括元组。因此,这确实是语言设计理念上的差异。 (尽管Java的较新版本似乎没有那么教条。最初,高阶函数被拒绝,因为您可以对哪些类进行相同的处理,但是现在已经引入了它们。所以也许我们看到了哲学上的变化。)

–雅克B
17-4-27在6:07



#6 楼

不要寻找做法;正如最佳做法BAD中所说的,“模式好”通常是一个坏主意。我知道您并没有在寻求最佳实践,但我仍然认为您会在其中找到一些相关的要素。

寻找解决问题的方法胜于实践,而您的问题不是元组可以在Java中快速返回三个值:


有数组
您可以将数组作为一个列表以列表形式返回:Arrays.asList(...)
如果您希望尽可能少地使用样板(并且没有龙目岛)来保持物体的一侧:


仅包含您的数据,并且公开,因此不需要获取方法。请注意,但是,如果您使用某些序列化工具或持久性层(如ORM),则它们通常使用getter / setter(并且可以接受参数以使用字段而不是getter / setter)。这就是为什么这些做法被广泛使用的原因。因此,如果您想了解实践,最好了解它们为什么在这里以更好地使用。

最后:我使用getter,因为我使用了很多序列化工具,但是我不这样做也不要写它们;我使用lombok:我使用IDE提供的快捷方式。

评论


理解各种语言中的常见习语仍然有价值,因为它们已成为事实上更易于访问的标准。无论出于何种原因,这些惯用语都倾向于落入“最佳实践”的标题之下。

–贝琳·洛里奇(Berin Loritsch)
17年4月26日在12:57

嘿,这个问题的明智解决方案。编写一个只有几个公共成员的类(/ struct)而不是使用[gs] etters的成熟的,过度设计的OOP解决方案似乎很合理。

–涂药
17年4月26日在20:54

确实会导致膨胀,因为与Python不同的是,不鼓励使用搜索或更简短,更优雅的解决方案。但是好处是,对于任何Java开发人员而言,膨胀或多或少都是相同的。因此,由于开发人员之间的互换性更高,因此在一定程度上具有更高的可维护性,并且如果两个项目被加入或两个团队无意间发生分歧,则战斗的风格差异就更少了。

– Mikhail Ramendik
17年4月26日在22:28

@MikhailRamendik这是YAGNI和OOP之间的权衡。 Orthodox OOP表示每个对象都应该可替换;唯一重要的是对象的公共接口。这使您可以编写较少关注具体对象的代码,而更多地关注接口。并且由于字段不能成为接口的一部分,因此您永远不会公开它们。从长远来看,这可以使代码更易于维护。另外,它允许您防止无效状态。在您的原始示例中,可能有一个同时为“
–罗安
17年4月27日在9:33

@PeterMortensen这是一个Java库,它执行许多完全不相关的事情。很好。这是功能

–迈克尔
17年4月27日在16:47

#7 楼

关于Java习惯用法的一般介绍:

Java具有适用于所有内容的类的原因有多种。据我所知,主要原因是:

Java对于初学者应该很容易学习。事情越明确,就越难以错过重要的细节。


对于您的特定示例:单独的类的论据线是这样的:如果这三件事都足够相关彼此之间将它们作为一个值返回,值得将其命名为“事物”。并为以通用方式构造的一组事物引入名称意味着定义一个类。

您可以使用Lombok等工具来简化样板:

@Value
class MyTuple {
    long value_in_milliseconds;
    boolean is_in_seconds;
    boolean is_under_1ms;
}


评论


这也是Google的AutoValue项目:github.com/google/auto/blob/master/value/userguide/index.md

–伊凡
17年4月26日在12:13

这也是为什么Java程序可以相对容易维护的原因!

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
17-4-30在10:04



#8 楼

关于Java文化,可以说很多事情,但是我认为在您现在正面临的情况下,有一些重要方面:


库代码只写过一次,但使用频率更高。虽然可以最大程度地减少编写库的开销是不错的选择,但从长远来看,以尽量减少使用库的开销的方式进行编写可能更值得。
这意味着自文档化类型很棒:方法名帮助弄清正在发生的事情以及从对象中得到的结果。
静态类型输入对于消除某些类别的错误非常有用。它当然并不能解决所有问题(人们喜欢开玩笑说Haskell,一旦您让类型系统接受您的代码,那可能是正确的),但是它使得很容易使某些错误的事情变得不可能。编写库代码是关于指定合同的。为参数和结果类型定义接口可以使合同的界限更加明确。如果某个东西接受或产生一个元组,那么就不用说这是您实际上应该接收还是产生的元组,而且对这样的泛型类型的约束很少(它甚至具有正确数量的元素吗?它们是您所期望的类型吗?)。如果将它们定型,那么您将获得一个不可变的类,并使用构造函数对其进行了初始化:

   class ParseResult0 {
      public final long millis;
      public final boolean isSeconds;
      public final boolean isLessThanOneMilli;

      public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
         this.millis = millis;
         this.isSeconds = isSeconds;
         this.isLessThanOneMilli = isLessThanOneMilli;
      }
   }


当然,这意味着您被束缚了特定类,任何需要产生或使用解析结果的东西都必须使用该类。对于某些应用程序,这很好。对于其他人,这可能会引起一些痛苦。许多Java代码都是关于定义合同的,通常会将您带入接口。

另一个陷阱是,使用基于类的方法时,您将暴露字段,并且所有这些字段都必须具有值。例如,即使isLessThanOneMilli为true,isSeconds和millis始终必须具有某些值。 isLessThanOneMilli为true时,对Millis字段的值的解释应该是什么?创建不可变类型,而没有很多语法开销。例如,我可能会像这样实现您正在谈论的结果结构:也有一些好处,我认为那些好处可以开始回答您的一些主要问题。在Python中,一个元组与另一个元组并没有真正的区别。在Java中,静态类型可用,因此我们已经排除了某些类型的错误。例如,如果要使用Python返回一个元组,并且想要返回该元组(millis,isSeconds,isLessThanOneMilli),则可能会意外地执行以下操作:

   interface ParseResult {
      long getMillis();

      boolean isSeconds();

      boolean isLessThanOneMilli();

      static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
         return new ParseResult() {
            @Override
            public boolean isSeconds() {
               return isSeconds;
            }

            @Override
            public boolean isLessThanOneMilli() {
               return isLessThanOneMill;
            }

            @Override
            public long getMillis() {
               return millis;
            }
         };
      }
   }
当您的意思是:

return (true, 500, false)


使用这种Java接口,您将无法编译:

return (500, true, false)


全部。您必须执行以下操作:

return ParseResult.from(true, 500, false);


一般来说,这是静态类型语言的一个优点。

这种方法还开始使您能够限制可以获取的值。例如,当调用getMillis()时,您可以检查isLessThanOneMilli()是否为true,如果是,则抛出IllegalStateException(例如),因为在这种情况下没有有意义的millis值。 >很难做错事

在上面的接口示例中,您仍然会遇到问题,因为它们具有相同的类型,所以可能会意外地交换isSeconds和isLessThanOneMilli参数。 TimeUnit和持续时间,这样您就可以得到类似以下结果: (假设您已经正确记录了事情),最终使用您的代码的人不必猜测结果的含义,并且不必意外地执行result[0]之类的事情。您仍然可以非常简洁地创建实例,并且从它们中获取数据也不是那么困难:

基于类的方法。只需为不同情况指定构造函数。但是,仍然存在如何初始化其他字段的问题,并且不能阻止对它们的访问。这段时间,您正在组成其他已经存在的库,或者编写供其他人使用的库。如果可以避免,公共API不需要花费大量时间来查阅文档来解密结果类型。

您只编写了一次这些结构,但是创建了许多次,因此您仍然想要简洁的创作(您会得到)。静态类型可确保您从其中获取的数据符合您的期望。返回数组的内容可能会减少开销,如果是这种情况(并且开销很大,您可以通过分析确定),则在内部使用简单的值数组可能很有意义。您的公共API可能仍应具有明确定义的类型。

评论


最后,我使用了自己的枚举而不是TimeUnit,因为我只是将UNDER1MS与实际时间单位一起添加了。所以现在我只有两个领域,而不是三个。 (从理论上讲,我可能会滥用TimeUnit.NANOSECONDS,但这将非常令人困惑。)

– Mikhail Ramendik
17年4月26日在23:01

我没有创建一个吸气剂,但是现在我知道了,如果使用originalTimeUnit = DurationTimeUnit.UNDER1MS,调用者尝试读取毫秒值,那么吸气剂将如何引发异常。

– Mikhail Ramendik
17-4-26在23:02



我知道您已经提到了它,但是请记住,使用Python元组的示例实际上是关于动态vs静态类型的元组的。关于元组本身并没有说太多。您可以拥有将无法编译的静态类型元组,因为我敢肯定您知道:)

– Andres F.
17年4月28日在18:34

@Veedrac我不确定你的意思。我的观点是,可以像这样更详细地编写库代码(即使花费更长的时间),因为使用代码的时间比编写代码要花费更多的时间。

–约书亚·泰勒(Joshua Taylor)
17年4月28日在19:33

@Veedrac是的,是的。但是我坚持认为,有意义地重用的代码与不会有意义地重用的代码之间是有区别的。例如,在Java程序包中,某些类是公共的,并且可以从其他位置使用。这些API应该经过深思熟虑。在内部,有些类可能只需要相互交谈。这些内部耦合是快速而肮脏的结构(例如,预期具有三个元素的Object [])可能适合的地方。对于公共API,通常应该选择更有意义和更明确的内容。

–约书亚·泰勒(Joshua Taylor)
17年4月28日在19:51

#9 楼

问题是您将苹果与桔子进行了比较。您询问了如何模拟返回多个值而不是一个简单且无类型的元组的python示例,并且实际上收到了单线回答。没有快速的临时解决方法,您第一次必须丢弃并正确实现时,才需要对返回值进行任何实际操作,而是一个POJO类,它与大量库兼容,包括持久性,序列化/反序列化,仪器和任何可能的东西。

这也不长。您唯一需要编写的就是字段定义。可以生成setter,getter,hashCode和equals。因此,您的实际问题应该是:为什么不自动生成getter和setter,而是语法问题(语法糖问题,有人会说),而不是文化问题。

最后,您重新考虑尝试加快根本不重要的速度。与维护和调试系统所花费的时间相比,编写DTO类所花费的时间微不足道。因此,没有人会优化以减少冗长。

评论


“没人”在事实上是不正确的-有大量工具可以优化以减少冗长,正则表达式是一个极端的例子。但是您可能的意思是“在主流Java世界中没有人”,因此阅读答案很有道理,谢谢。我对冗长的关注不是花时间写代码,而是花在阅读代码上。 IDE不会节省阅读时间;但似乎是这样的想法,即99%的时间仅读取接口定义,因此应该简洁些吗?

– Mikhail Ramendik
17-4-27在21:24



#10 楼

这是影响您所观察的内容的三个不同因素。讨论元组是否是一个好主意并不是真正的重点-但是在Java中,您确实使用了较重的结构,因此这是一个稍微不公平的比较:您可以使用对象数组和某种类型转换。语言语法

声明类容易吗?我不是在谈论将字段公开或使用地图,而是像Scala的案例类那样,它提供了您描述的设置的所有优点,但更加简洁:

case class Foo(duration: Int, unit: String, tooShort: Boolean)


我们可以拥有-但是要付出代价:语法变得更加复杂。当然,在某些情况下,甚至在大多数情况下,甚至在未来5年内的大多数情况下,都值得这样做-但需要进行判断。顺便说一下,这是您可以自行修改的语言(例如lisp)的优点-并请注意,由于语法简单,这如何成为可能。即使您实际上并没有修改语言,简单的语法也可以启用更强大的工具。例如,很多时候我都错过了Java可用的一些重构选项,但是Scala却没有。思维方式。有时可能会让人感到压抑(我经常希望支持某些功能),但是删除功能与拥有它们一样重要。你能支持一切吗?当然可以,但是您最好编写一个编译每种语言的编译器。换句话说,您将没有语言-您将拥有语言的超集,并且每个项目基本上都会采用一个子集。

当然,编写与语言哲学背道而驰的代码也是可能的,而且正如您所观察到的那样,结果通常很难看。在Java中仅具有几个字段的类类似于在Scala中使用var,将prolog谓词退化为函数,在haskell中执行unsafePerformIO等。Java类并不是轻巧的-它们不在那里传递数据。当遇到困难时,退后一步,看看是否还有另一种方法通常会很有收获。在您的示例中:

为什么持续时间与单位分开?有很多时间库可以让您声明一个持续时间-类似于Duration(5,seconds)(语法会有所不同),然后它可以让您以更强大的方式执行所需的任何操作。也许您想将其转换-为什么要检查result [1](或[2]?)是否为“小时”并乘以3600?对于第三个论点-它的目的是什么?我猜想在某个时候您将不得不打印“少于1毫秒”或实际时间-这是时间数据所固有的一些逻辑。即您应该有一个这样的类:

class TimeResult {
    public TimeResult(duration, unit, tooShort)
    public String getResult() {
        if tooShort:
           return "too short"
        else:
           return format(duration)
}


}

或您实际上想要对数据做的任何事情,从而封装了逻辑。

当然,在某些情况下,这种方式可能行不通-我并不是说这是将元组结果转换为惯用Java代码的神奇算法!在某些情况下,它很丑陋且很糟糕,也许您应该使用另一种语言-这就是为什么毕竟有这么多语言的原因! Java并不是要将它们用作数据容器,而是用作逻辑的独立单元。

评论


我要对持续时间进行的操作实际上是将它们进行求和,然后与另一个进行比较。不涉及任何输出。比较需要考虑四舍五入,因此我需要知道比较时的原始时间单位。

– Mikhail Ramendik
17年4月26日在22:53

可能可以创建一种添加和比较我的类的实例的方法,但这可能会变得非常繁重。特别是因为我还需要除以浮点数并乘以浮点数(在该UI中有要验证的百分比)。所以现在我用final字段制作了一个不变的类,这看起来是一个不错的折衷方案。

– Mikhail Ramendik
17年4月26日在22:55

关于这个特定问题,最重要的部分是事物的一般性,从所有答案看来,情况似乎是一致的。

– Mikhail Ramendik
17年4月26日在22:56

@MikhailRamendik也许可以选择使用某种累加器-但您更清楚问题所在。的确,这并不是要解决这个特定的问题,对不起,如果我有点偏心-我的主要观点是,该语言不鼓励使用“简单”数据。有时,这可以帮助您重新考虑自己的方法-有时,一个int只是一个int

– Thanos Tintinidis
17年4月27日在7:47

@MikhailRamendik关于样板方式的好处是,一旦有了一个类,就可以向其添加行为。和/或使其与您一样不变(多数情况下,这是一个好主意;您始终可以返回一个新对象作为总和)。最后,您的“多余”类可以封装您需要的所有行为,然后使吸气剂的成本微不足道。而且,您可能需要也可能不需要。考虑使用lombok或自动值。

– maaartinus
17年4月28日在0:50

#11 楼

据我了解,核心原因是


接口是抽象类的基本Java方法。
Java只能从方法(对象或对象)返回单个值。数组或本机值(int / long / double / float / boolean)。
接口不能包含字段,只能包含方法。如果要访问字段,则必须遍历一个方法-即getter和setter。
如果方法返回接口,则必须具有一个实现类才能实际返回。

此给您“您必须编写一个类以返回任何不平凡的结果”,这反过来又很沉重。如果您使用类而不是接口,则可以只具有字段并直接使用它们,但这会将您绑定到特定的实现。

评论


补充说明一下:如果仅在较小的上下文中(例如,类中的私有方法)需要它,则可以使用裸记录-理想情况下是不变的。但这确实真的不应该泄漏到班级的公共合同中(甚至更糟的是,图书馆!)。当然,您可以针对记录做出决定,以确保这种情况永远不会随着将来的代码更改而发生。这通常是更简单的解决方案。

–罗安
17年4月27日在9:41

我同意“ Tuples在Java中不能很好地工作”的观点。如果您使用泛型,这将很快导致讨厌。

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
17年4月27日在19:17

@ThorbjørnRavnAndersen它们在Scala中工作得很好,Scala是具有泛型和元组的静态类型语言。那么也许这是Java的错吗?

– Andres F.
17年4月28日在18:26

@AndresF。请注意“如果您使用泛型”部分。 Java执行此操作的方法不会使其自身适用于复杂的结构,而不是例如哈斯克尔。我不太了解Scala进行比较,但是Scala是否允许多个返回值是真的吗?这可能会很有帮助。

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
17年4月28日在21:13

@ThorbjørnRavnAndersenScala函数具有单个返回值,该返回值可能是元组类型(例如def f(...):(Int,Int)是一个函数f,它返回的值恰好是整数元组)。我不确定Java中的泛型是否是问题所在;请注意,例如Haskell也会键入擦除。我认为Java没有元组是没有技术原因的。

– Andres F.
17年4月28日在22:00



#12 楼

我同意JacquesB的回答,即


Java(传统上)是一种基于单范式类的OO语言。
但是明确性和一致性并不是要优化的最终目标。当您说“为优化可读性而优化python”时,您立即提到最终目标是“可维护性”和“开发速度”。 ?我的看法是,它已经发展成为一种语言,声称可以提供可预测的,一致的,统一的方式来解决任何软件问题。

换句话说,Java文化经过了优化,可以使管理人员相信他们理解软件开发。 。

或者,就像很早以前一个聪明人所说的那样,


判断语言的最佳方法是查看由...编写的代码它的支持者。 “基数最大的恶意软件”-Java显然是面向货币的编程(MOP)的示例。正如SGI Java的首席支持者告诉我的那样:“亚历克斯,你必须去赚钱的地方。”但是我并不特别想去哪里有钱-
在那儿通常闻起来并不香。


#13 楼

(此答案不是特别针对Java的解释,而是解决了“(繁重的)实践可以优化什么?”这一一般性问题)。

请考虑以下两个原则:


程序执行正确的事情会很好。我们应该使编写易于执行正确操作的程序变得容易。
当您的程序执行错误操作时,这很不好。我们应该使编写做错事情的程序变得更困难。使其难以做正确的事,反之亦然)。

在任何特定情况下进行哪种权衡取决于应用程序,相关程序员或团队的决定以及(组织或语言社区的)文化。

例如,如果程序中的错误或几个小时的中断可能导致人员伤亡(医疗系统,航空),甚至仅仅造成金钱损失(例如Google的广告系统中的数百万美元),您将做出不同的权衡(不仅是您的语言,而且还包括工程文化的其他方面),而不是一次性脚本:它可能倾向于“繁重”的一面。

其他一些例子可能会使您系统更“繁重”:



当许多团队多年使用大型代码库时,一个大问题是有人可能会错误地使用别人的API。用错误的顺序调用参数的函数,或者在未确保其期望的前提条件/约束的情况下调用的函数,可能会造成灾难性的后果。
作为一种特殊情况,例如您的团队维护一个特定的API或库,并希望对其进行更改或重构。用户对代码使用方式的“约束”越多,更改代码就越容易。 (请注意,这里最好有实际的保证,那就是没有人会以不寻常的方式使用它。)
如果开发是由多个人或团队分配的,那么拥有一个人或一个团队似乎是一个好主意个人或团队“指定”接口,并让其他人实际实现它。为了使它起作用,您需要能够在实现完成时获得一定程度的信心,即实现实际上与规范匹配。

这些只是一些示例,可以使您了解确实使某些事情变得“繁重”(使您难以快速地编写一些代码)的情况确实是故意的。 (甚至可能会争辩说,如果编写代码需要付出很多努力,它可能会使您在编写代码之前进行更仔细的思考!当然,这种说法很快就会变得荒谬。)内部Python系统往往使事情变得“繁重”,因此您不能简单地导入其他人的代码,而必须在BUILD文件中声明依赖项,要导入其代码的团队需要将其库声明为对代码可见等。


注意:以上所有内容只是在事情趋于“沉重”时才出现。我绝对不声称Java或Python(语言本身或它们的文化)在任何特定情况下都具有最佳的权衡;这是您要考虑的。关于这种折衷的两个相关链接:


Erik Osheim的一篇很棒的文章,关于动态类型语言的吸引力是什么? —试图解释为什么有人会喜欢Python之类的问题则朝相反的方向。 br />


评论


读代码呢?那里还有另一个权衡。奇怪的咒语和丰富的仪式使代码难以读懂;在您的IDE中使用样板文件和不必要的文本(以及类层次结构!),将很难看到代码的实际作用。我可以看到这样一个论点,即代码不一定必须易于编写(不确定我是否同意它,但是它有一些优点),但是绝对应该易于阅读。显然可以并且已经使用Java构建了复杂的代码-否则声明是愚蠢的-但这是由于它的冗长还是尽管如此?

– Andres F.
17年4月28日在18:40



@AndresF。我对答案进行了编辑,以使其更加明确,它不是专门针对Java的(我也不是Java的忠实拥护者)。但是,是的,在阅读代码时需要权衡:一方面,您希望能够轻松阅读您所说的“代码实际在做什么”。另一方面,您希望能够轻松查看以下问题的答案:该代码与其他代码有何关系?该代码可以做的最坏的事情:它对其他状态的影响是什么,其副作用是什么?该代码可能取决于哪些外部因素? (当然,理想情况下,我们希望对这两组问题都提供快速答案。)

–ShreevatsaR
17年4月28日在20:55

#14 楼

Java文化已经随着时间的流逝而发展,它受到来自开放源代码和企业软件背景的巨大影响-如果您真的考虑过,这是一个奇怪的组合。企业解决方案需要重型工具,而开源则要求简单。最终结果是Java处于中间位置。

影响建议的部分原因是,在Python和Java中,可读性和可维护性被认为是非常不同的。


在Python中,元组是一种语言功能。
在Java和C#中,元组都是(或者可能是)库功能。

我只提到C#,因为标准库具有一组Tuple 类,如果该语言不直接支持它们,那么它就是一个笨拙的元组的完美示例。在几乎每种情况下,如果您选择了很好的类来处理问题,则代码将变得更具可读性和可维护性。在链接的Stack Overflow问题中的特定示例中,其他值将很容易表示为返回对象上的已计算的getter。匿名对象(在C#3.0中发布)的想法很好地解决了这一难题。不幸的是,Java还没有一个等效的类。

在修改Java的语言功能之前,最可读和可维护的解决方案是拥有一个专用对象。这是由于可追溯到1995年的语言限制。原始作者计划了更多语言功能,而这些功能从未实现过,而向后兼容性则是Java随时间演变的主要限制之一。

评论


在最新版本的c#中,新的元组是一等公民,而不是图书馆类。到那里花了太多版本。

–伊戈尔(Igor Soloydenko)
17年4月26日在14:34

最后三段可以概括为“语言X具有您所寻求的Java所没有的功能”,我看不到它们为答案提供了什么。为什么在Python to Java主题中提到C#?不,我只是不明白要点。一个20k +代表家伙有点怪异。

–奥利维尔·格雷戈尔(OlivierGrégoire)
17-4-26在15:27



正如我所说,“我只提到C#是因为Tuples是一个库功能”,类似于Java在主库中包含Tuples的情况下的工作方式。使用起来非常笨拙,更好的答案是总是有一个专门的课程。不过,匿名对象确实确实给元组设计了类似的障碍。没有更好的语言支持,您就必须使用最可维护的工具。

–贝琳·洛里奇(Berin Loritsch)
17年4月26日在16:13

@IgorSoloydenko,也许对Java 9有希望?这只是lambda的逻辑下一步。

–贝琳·洛里奇(Berin Loritsch)
17年4月26日在16:15

@IgorSoloydenko我不确定这是不是真的(一等公民)-它们似乎是语法扩展,用于从库中创建和解散类的实例,而不是像类,结构,枚举一样在CLR中具有头等支持。

– Pete Kirkham
17-4-26在16:15



#15 楼

我认为在这种情况下使用类的核心问题之一是,在一起的东西应该保持在一起。

相反,我对方法参数进行了这种讨论:
考虑一个简单的计算BMI的方法:在这种情况下,我会反对这种风格,因为体重和身高是相关的。方法“传达”那些不是两个的单独值。
何时计算一个人的体重和另一个人的身高的BMI?

CalculateBMI(weight,height)
{
  System.out.println("BMI: " + (( weight / height ) x 703));
}


更有意义,因为现在您清楚地传达出身高和体重来自同一来源。

返回多个值也是如此。如果它们明确连接,则返回整洁的小包装并使用对象(如果它们不返回多个值)。

评论


好的,但是假设您接下来有一个(假设的)任务来显示图形:BMI如何在某些特定的固定高度上取决于体重。然后,如果不为每个数据点创建一个Person,就无法做到这一点。而且,将其发挥到极致,如果没有有效的出生日期和护照号码,就无法创建Person类的实例。怎么办?顺便说一句,我没有拒绝投票,在某些类中将属性捆绑在一起是有充分理由的。

– artem
17年4月26日在18:20

@artem是接口起作用的下一步。因此,personweightheight界面可能是这样的。您在我所举的例子中陷入了困境,即指出一切应该在一起。

– Pieter B
17年4月27日在6:50

#16 楼

坦率地说,这种文化是Java程序员最初倾向于来自大学,这些大学曾教授过面向对象的原理和可持续的软件设计原理。似乎正在编写可持续的代码。我从您的示例中看到的是,存在一个非常尴尬的问题缠结。袖口编程。因此,您将需要在硬核工业环境中尝试过并经过测试的设计模式和样式进行特质化。在Java已有10多年的时间里,我倾向于使用Node / Javascript或Go进行新项目,因为两者都可以加快开发速度,而且借助微服务风格的体系结构,通常就足够了。从Google最初大量使用Java的事实来看,但它一直是Go的创始者,我想他们可能也在这么做。但是,即使我现在使用Go和Javascript,我仍然会使用多年使用和理解Java所获得的许多设计技能。

评论


值得注意的是,谷歌使用的foobar招聘工具允许将两种语言用于解决方案:java和python。我发现Go不是一种选择,这很有趣。我偶然发现了Go上的这个演示文稿,其中有一些关于Java和Python(以及其他语言)的有趣观点。例如,有一个突出的要点:“也许,因此,非专家程序员已经混淆了“搭配解释和动态类型一起使用。”值得一读。

– JimmyJames
17年4月27日在20:50

@JimmyJames刚坐在幻灯片上说“在专家的手中,他们很棒”-问题中问题的好摘要

–汤姆
17年4月27日在22:28