Java是否像C ++那样促进了类定义和实现之间的分离?


10

我有一个作业,我需要根据GRASP“ Protected Variation”评估哪种方法更好。我在Stack Overflow上发现了一个有关C ++中的头文件和代码文件分离的问题

但是,我想知道为什么Java在促进类定义和类实现之间的分离时不遵循C ++。与C ++方法相比,Java方法有什么优势吗?


如果您想问“为什么Java不使用头文件”,那么就问这个问题,并删除“哪个更好”的东西-如您所见,我们对此很过敏;)另外搜索,我可以肯定的是,这个问题(或者至少是密切相关的问题)之前已经提出过。

糟糕,该链接无效。我想重新了解一下,我基本上想知道的是两者之间的差异,以及哪一个往往更容易重用代码或具有扩展性。
EtienneNoël11年

1
由于创建C时有限的一遍编译器技术,C(以及扩展名C ++)实际上别无选择,只能将头文件与实现文件分开。
Channel72

2
如果有问题的类实现了接口,则Java可以具有可以将类定义和类实现分开的接口。虽然与C ++不太一样。
FrustratedWithFormsDesigner

1
另外,除非使用PIMPL惯用语,否则C ++头文件公开的实现要比我喜欢的要多得多。即使列出,也必须列出所有数据成员,private以便实现知道大小,并且private成员函数也应知道。
David Thornley

Answers:


13

以下程序中有几行代码?

#include <iostream>

int main()
{
   std::cout << "Hello, world!\n";
   return 0;
}

您可能回答了7(如果不计算空白行,则为6;如果不计算括号,则为4)。

但是,您的编译器会看到非常不同的东西:

~$ cpp hello.cpp | wc
  18736   40822  437015

是的,这是18.7 KLOC,仅用于“您好,世界!” 程序。C ++编译器必须解析所有这些内容。这是为什么C ++编译与其他语言相比要花这么长时间的主要原因,也是现代语言避开头文件的主要原因。

一个更好的问题是

为什么没有 C ++在把头文件?

C ++被设计为C的超集,因此必须保留头文件以实现向后兼容性。

好的,为什么C有头文件?

由于其原始的独立编译模型。C编译器生成的目标文件不包含任何类型信息,因此,为了防止类型错误,您需要在源代码中包含此信息。

~$ cat sqrtdemo.c 
int main(void)
{
    /* implicit declaration int sqrt(int) */
    double sqrt2 = sqrt(2);
    printf("%f\n", sqrt2);
    return 0;
}

~$ gcc -Wall -ansi -lm -Dsqrt= sqrtdemo.c
sqrtdemo.c: In function main’:
sqrtdemo.c:5:5: warning: implicit declaration of function printf [-Wimplicit-function-declaration]
sqrtdemo.c:5:5: warning: incompatible implicit declaration of built-in function printf [enabled by default]
~$ ./a.out 
2.000000

添加正确的类型声明可修复该错误:

~$ cat sqrtdemo.c 
#undef printf
#undef sqrt

int printf(const char*, ...);
double sqrt(double);

int main(void)
{
    double sqrt2 = sqrt(2);
    printf("%f\n", sqrt2);
    return 0;
}

~$ gcc -Wall -ansi -lm sqrtdemo.c
~$ ./a.out 
1.414214

请注意,没有#include。但是,当您使用大量外部函数(大多数程序会使用)时,手动声明它们会变得乏味且容易出错。使用头文件要容易得多。

现代语言如何能够避免头文件?

通过使用包含类型信息的其他目标文件格式。例如,Java * .class文件格式包括指定字段类型和方法参数的“描述符”。

这不是一个新发明。早在(1987年),当Borland在Turbo Pascal 4.0中添加了单独编译的“单元”时,它选择使用一种新*.TPU格式而不是Turbo C *.OBJ来消除对头文件的需要。


尽管有趣,但我相当确定您可以将Turbo Pascal设置为输出OBJ文件而不是TPUs ...
CVn

7

Java具有定义合同的接口。这从调用者的需求和实际实现中提供了更高层次的抽象。也就是说,调用者不需要知道实现类,只需要知道它支持的协定即可。

假设您想编写一种减慢Map中所有键/值的方法。

public static <K,V> void printMap(Map<K,V> map) {
    for(Entry<K,V> entry: map.entrySet())
        System.out.println(entry);
}

此方法可以在抽象接口上调用entrySet(),该接口已从实现该接口的类中移除。您可以使用此方法。

printMap(new TreeMap());
printMap(new LinkedHashMap());
printMap(new ConcurrentHashMap());
printMap(new ConcurrentSkipListMap());

1
欢迎来到那里的程序员Peter-以为我认出这个名字了:-)。我要补充一点,有些人会争辩说Java中的Abstract基类也定义了协定-但这可能是一个单独线程的参数。
Martijn Verburg

您好@MartijnVerburg,很好。我认为抽象类模糊了没有实现和具体类的接口之间的区别。扩展方法将进一步模糊区分。如果可能的话,我倾向于使用接口,因为它们更简单。
彼得·劳瑞

是的,Java将开始沿着具有多种方法来定义公共合同的Scala道路-我不确定这是否是一件好事:-)
Martijn Verburg

-1在C ++中,也可以使用一个简单的#define interface class
Sjoerd

@Sjoerd我不知道C ++方法不再需要使用virtual关键字来获取多态性,并且如果您仅使用一种或两种具体类型(例如Java中的具体类型),则不会对性能造成任何影响。您能指出我有关C ++如何工作的任何文档吗?
彼得·劳瑞

5

坦率地说,标题是历史性的意外。这是一个非常糟糕的系统,没有其他语言能提供如此可怕的功能,任何不需要与他们打交道的人都应该为之高兴。


3

标头可用于进行单独的编译。通过#include头文件,编译器无需了解有关已编译C ++代码的二进制结构的任何信息,并且可以将该工作留给单独的链接器。Java不会在其编译器中使用单独的链接器,并且由于.class文件是严格定义的,因此编译器可以读取它们以确定其所有类型的所有成员,而无需在每个编译单元中重新声明它们。

您可以将所有实现都包含在C ++头文件中,但是每次编译器包含#include时,它都会导致编译器对其进行重新编译,从而迫使链接程序进行整理并丢弃重复的副本。


0

Java确实促进了类定义和实现的分离,这仅取决于您要查找的位置。

当您是Java类的作者时,您会在一个文件中看到该类的定义及其实现。这简化了开发过程,因为您只需要去一个地方维护类,就不必在两个文件之间切换(就像在C ++中那样,在.h和.cpp之间)。但是,当您是该类的使用者时,您只能通过打包在.jar或独立.class中的.class文件来处理定义。

C ++允许您将定义和实现分开,但这是临时的。例如,没有什么可以阻止您在头文件中内联编写方法,对于模板类,这是强制性的。头文件还列出了所有成员变量,即使这些成员变量是该类的实现细节,并且与使用者无关,所有查看该头文件的人都可以看到它们。

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.