为什么Java不允许在类外部提供函数定义?


22

与C ++不同,在Java中,我们不能仅在类中具有函数声明,而不能在类之外具有定义。为什么会这样呢?

是否要强调Java中的单个文件应只包含一个类,而不能包含其他任何类?



1
通过定义,您是指属性还是方法签名ala头文件?
Deco

对您的问题的一个很好的讽刺答案:steve-yegge.blogspot.com/2006/03/…–
Brandon

Answers:


33

C ++和Java之间的区别在于语言认为它们的最小链接单位。

因为C被设计为与程序集共存,所以该单元是地址调用的子例程。(对于编译为本机目标文件的其他语言,例如FORTRAN,这是正确的。)换句话说,包含函数的目标文件foo()将具有一个称为的符号_foo,该符号将仅解析为地址,例如0xdeadbeef在链接期间。这就是全部。如果函数要接受参数,则在调用其地址之前,由调用者确定函数期望的所有内容。通常,这是通过将内容堆积到堆栈上来完成的,编译器会负责繁琐的工作并确保原型匹配。在目标文件之间没有检查。如果您取消了呼叫链接,则该呼叫将不会按计划进行,也不会收到警告。尽管存在危险,但仍可以将用多种语言(包括汇编语言)编译的目标文件链接在一起,成为一个运行良好的程序,而不必大惊小怪。

尽管C ++具有所有其他奇特之处,但它们的工作方式相同。编译器将刺破名称空间,类和方法/成员/等。通过将类的内容展平为单个名称(使其以独特的方式进行修饰)来使之符合这一约定。例如,将类似的方法放入目标文件中时Foo::bar(int baz)可能会陷入混乱_ZN4Foo4barEi,而0xBADCAFE在运行时则会陷入地址中。这完全取决于编译器,因此,如果您尝试链接具有不同处理方案的两个对象,那么您将很不走运。如此丑陋,这意味着您可以使用extern "C"块来禁用重整,从而使其他语言可以轻松访问C ++代码。C ++从C继承了自由浮动函数的概念,这在很大程度上是由于本机对象格式允许的。

Java是另一种野兽,它以其自己的目标文件格式(即.class文件)生活在一个孤立的世界中。类文件包含有关其内容的大量信息,这些信息使环境可以在运行时使用类本机链接机制甚至无法实现的类。信息必须从某处开始,而起点就是class。可用的信息使编译后的代码能够描述自己,而无需使用单独的文件来包含源代码中的描述,就像使用C,C ++或其他语言时那样。这样,即使在运行时,也可以使用本机链接缺失为您提供所有类型安全优势语言,这使您能够使用反射将任意类从文件中提取出来,并在某些不匹配的情况下以保证失败的方式使用它。

如果您还没有弄清楚,那么所有这些安全性都需要权衡:您链接到Java程序的任何东西都必须是Java。(通过“链接”,我的意思是任何时候一个类文件中的内容都指向另一个类中的内容。)您可以使用JNI链接(以本机方式)到本机代码,但是有一个隐式约定说,如果破坏本机端的话,您拥有这两个部分。

Java很大,并且在首次引入时在可用硬件上并不是特别快,就像Ada在过去十年中一样。只有Jim Gosling可以肯定地说他使类成为Java的最小链接单元的动机是什么,但是我不得不猜测,添加自由浮动对象会增加运行时的额外复杂性可能是一个交易杀手。


链接断开,获取Web存档链接
2013年

14

我相信答案是,根据Wikipedia,Java被设计为简单且面向对象。函数应该在定义它们的类上运行。按照这种思路,在类之外具有函数是没有意义的。我将得出一个结论,即Java不允许使用Java,因为它不适合纯OOP。

Google的快速搜索对我的Java语言设计动机没有多大帮助。


6
原始类型的存在是否将Java排除在“纯OOP”之外?
Radu Murzea

5
@SoboLAN:是的,添加功能将使其变得更少“纯正的OOP”。
Giorgio 2013年

8
@SoboLAN取决于您对“纯OOP”的定义。在某些时候,所有东西毕竟都是计算机内存中的位的组合……
jwenting

3
@jwenting:嗯,编程语言中抽象的目的是尽可能地隐藏基础位。您在程序中看到的那些位越多,您就应该开始更多地认为您的编程语言提供了泄漏的抽象(除非它是故意与金属语言紧密结合而构建的)。因此,是的,一切都归结为操纵位,但是不同的语言的确提供了不同级别的抽象,否则您将无法将汇编与高级语言区分开。
Giorgio 2013年

2
取决于您对“纯OOP”的定义:每个数据值都是一个对象,每个数据操作都是通过消息传递获得的结果方法调用。据我所知,仅此而已。
Giorgio

11

真正的问题是,继续以C ++方式执行操作的优点是什么?头文件的原始目的是什么?简短的答案是,头文件样式允许在大型项目中加快编译速度,在大型项目中,许多类都可能引用同一类型。由于编译器的性质,在JAVA和.NET中这不是必需的。

请在此处查看此答案:头文件真的好吗?


1
+1,尽管我实际上已经听到人们说他们喜欢头文件提供的公共接口和私有实现之间的分离。那些人当然是错的。;)
vaughandroid13年

6
@Baqueta在Java中,您可以使用interfaceand 实现此功能class:)不需要标题!
Andres F.

1
我永远都不会理解必须查看3个以上的文件来了解简单的类实例如何工作的可取性。
Erik Reppen

@ErikReppen通常,接口(或C中的头文件)是客户以用户可读的形式编写其解决方案的东西,其余的仅以二进制形式提供(当然,在Java中,无需提供源代码)接口,类文件和javadoc都可以)。
jwenting 2014年

@jwenting我没想到那件事。我习惯于想到下一个可怜的混蛋,他们不得不维护这种愚蠢的代码库,因为下一个工作常常是我在花了几个小时看了一些奇怪的界面和子/超类快乐之后刺破了我的眼睛。旋转架构。
埃里克·雷彭

3

Java文件表示一个类。如果您在课外有一个程序,范围是什么?是全球性的吗?还是属于Java文件所代表的类?

大概是由于某种原因,您将其放在该Java文件中,而不是另一个文件中-因为它与该类一起使用的次数比任何其他类都多。如果某个类之外的过程实际上与该类相关联,那么为什么不强迫它进入该类所属的类呢?Java将其作为类内部的静态方法进行处理。

如果允许使用外部类过程,则可能无法对其声明其文件的类进行特殊访问,从而将其限制为不更改任何数据的实用程序函数。

此Java限制的唯一可能缺点是,如果您确实具有不与任何类关联的全局过程,则最终会制作一个MyGlobals类来保存它们,并将该类导入使用这些过程的所有其他文件中。

实际上,Java导入机制需要此限制才能起作用。使用所有可用的API,java编译器需要确切地知道要编译什么以及要针对什么进行编译,因此必须在文件顶部明确显示import语句。而不必组的全局到人工类,你会怎么告诉Java编译器来编译您的全局,而不是任何及所有全局的类路径?在您有doStuff()而其他人有doStuff()的命名空间冲突呢?这行不通。强制您指定MyClass.doStuff()和YourClass.doStuff()可解决这些问题。强迫您的过程进入MyClass内部而不是外部,只会阐明此限制,并且不会对代码施加其他限制。

Java犯了很多错误-序列化有很多小缺陷,以至于很难使用(想想SerialVersionUID)。它也可以用于打破单例和其他常见的设计模式。Object上的clone()方法应分为deepClone()和shallowClone(),并且是类型安全的。默认情况下,所有API类都可以设为不可变的(它们在Scala中的方式)。但是所有程序必须属于一个类的限制是一个很好的限制。它主要用于简化和阐明语言和您的代码,而没有施加任何繁重的限制。


3

我认为大多数回答问题的人和他们的选民误解了这个问题。这反映出他们不了解C ++。

“定义”和“声明”是在C ++中具有非常特定含义的词。

OP并不意味着改变Java的工作方式。这纯粹是关于语法的问题。我认为这是一个有效的问题。

在C ++中,有两种方法来定义成员函数。

第一种方法是Java方法。只需将所有代码放在花括号中:

class Box {
public:
    // definition of member function
    void change(int newInt) { 
        this._m = newInt;
    }
private:
    int _m
}

第二种方式:

class Box {
public:  
    // declaration of member function
    void change(int newInt); 
private:
    int _m
}

// definition of member function
// this can be in the same file as the declaration
void Box::change(int newInt) {
    this._m = newInt;
}

这两个程序是相同的。该函数change仍然是成员函数:在类外部不存在。此外,类定义必须像Java中一样,必须包含所有成员函数和变量的名称和类型。

乔纳森·亨森(Jonathan Henson)正确的是,这是C ++中标头工作方式的产物:它允许您将声明放在标头文件中,并将实现放在单独的.cpp文件中,这样您的程序就不会违反ODR(一个定义规则)。但是它还有其他优点:它使您一眼就能看到大型类的界面。

在Java中,您可以使用抽象类或接口来近似这种效果,但是抽象类或接口不能与实现类具有相同的名称,这使其相当笨拙。


那说明你们俩都不懂人也不懂Java。您可以为接口和实现使用相同的名称,只要它们不在同一程序包中即可。
jwenting 2014年

我认为包(或名称空间,或外部类)是名称的一部分。
Erik van Velzen 2014年

2

我认为这是类加载机制的产物。每个类文件都是可加载对象的容器。类文件没有“外部”位置。


1
我不明白为什么将源代码组织在不同的单元中会阻止按原样保留类文件格式。
Mat

类文件和源文件之间存在1:1的对应关系,这是整个系统中较好的设计决策之一。
ddyer

@ddyer那你还没看过Foo $ 2 $ 1.class吗?(请参阅Java内部类类文件名

这会使它成为“ onto”映射吗?无论如何,每个类都是通过仅编译一个源文件生成的,这是一个很好的设计决策。
ddyer

0

C#与Java非常相似,但通过使用部分方法确实具有这种功能,只是部分方法是专用的。

部分方法:http : //msdn.microsoft.com/en-us/library/6b0scde8.aspx

局部类和方法:http : //msdn.microsoft.com/zh-cn/library/wa80x488.aspx

我看不出Java无法做到这一点的任何原因,但可能仅归结为是否有用户认为需要将此功能添加到语言中。

大多数用于C#的代码生成工具都会生成部分类,以便开发人员可以根据需要轻松地将手动编写的代码添加到单独文件中的类。


0

C ++要求将类的整个文本作为使用其任何成员或产生任何实例的每个编译单元的一部分进行编译。保持编译时间正常的唯一方法是,使类本身的文本尽可能地仅包含其使用者实际上必需的那些东西。C ++方法通常是在包含它们的类之外编写的,这是一个令人讨厌的恶作剧,其原因是,要求编译器为使用该类的每个编译单元处理每个类方法的文本一次,疯狂的构建时间。

在Java中,已编译的类文件除其他外还包含与C ++ .h文件相当的信息。该类的使用者可以从该文件中提取他们需要的所有信息,而不必让编译器处理.java文件。与C ++不同,.h文件包含可用于其中的类的实现和客户端的信息,而Java中的流程则相反:客户端使用的文件不是编译Windows Server 2003时使用的源文件。类代码,但是由编译器使用类代码文件中的信息生成。因为不需要在包含客户端所需信息的文件和包含实现的文件之间划分类代码,所以Java不允许进行此类拆分。


-1

我认为部分原因是Java是一种保护主义语言,与大型团队的使用有关。类不能被覆盖或重新定义。您有4个级别的访问修饰符,它们专门定义了如何使用和不使用方法。一切都是强类型/静态类型,以保护开发人员免受他人或自己造成的类型不匹配的问题。将类和函数作为最小的单元可以使重塑如何设计应用程序的范式变得容易得多。

与JavaScript相比,双重用途的名词/动词一流的函数如雨后春笋般从破开的彩饰陶罐中散发出来,而等效类的函数构造函数可以通过更改原型来添加实例的新属性或更改旧属性,已经在任何时候创建了,绝对没有阻止您用自己的版本替换任何构造的东西,共有5种类型,它们会在不同情况下自动转换和自动求值,并且您可以在任何东西附近附加新属性:

function nounAndVerb(){
}
nounAndVerb.newProperty = 'egads!';

最终,它关乎利基市场。JavaScript大约在100个开发人员手中(其中很多人可能是平庸的)是适当的,并且是灾难性的,就像Java曾经在少数试图用它编写Web UI的开发人员手中一样。当您与100名开发人员合作时,您想要的最后一件事就是一个人重新发明该死的范例。当您使用UI或快速开发是一个更关键的问题时,您想要做的最后一件事就是在快速完成相对简单的事情时遇到障碍,这是因为如果您不小心的话,很容易将它们愚蠢地做到。

归根结底,它们都是流行的通用语言,因此这里有一些哲学上的争论。我使用Java和C#的最大个人经验是,我从未见过或使用过旧代码库,大多数开发人员似乎都了解基本OOP的价值。当您进入游戏时必须将所有内容都包装在一个类中时,也许更容易以为您正在执行OOP而不是伪装成3-5行类的庞大链条的意大利面条。

就是说,没有什么比一个只知道危险而又不敢炫耀的人编写的JavaScript糟糕的了。我认为这是总体思路。据说那个家伙必须遵循与Java大致相同的规则。我认为,这个混蛋总会找到一条路。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.