如何让一切都成为对象?

7
在Java中,例如有一个原始数据类型“int”,它表示32位值,还有一个“Integer”类,只是一个带有单个“int”属性的类(当然还有一些方法)。这意味着Java的“Integer”类在幕后仍然使用基本数据类型。这就是Java不是纯面向对象编程语言的原因。
如果没有基本数据类型,值将存储在哪里?例如,我想象这个伪类:
class Integer
{
    private Integer i = 12;

    public Integer getInteger
    {
        return this.Integer;
    }
}

这将是一个递归的过程。

如果没有基本元素,如何实现编程语言?

我感激任何帮助解决我的困惑。


13
没有基本元素就无法实现它,但您不需要使语言用户能够看到或使用它们... - assylias
2
一种编程语言在某个时刻需要将 int 存储到内存中,例如 RAM。每种语言的底层都是位和字节以及原始指针。像 Scala 这样的语言可能决定不向程序员公开基本类型。 - Manos Nikolaidis
文档可以从无到有。 - Martin James
5
这个问题与代码太不相关了,不适合在 Stack Overflow 上发布。你可以考虑将它发布在 programmers.stackexchange.com 上更为恰当。 - zwol
7个回答

11

在幕后,计算机始终会使用原始数据类型,因为它们只是内存中的一些比特。但有些语言会把原始数据类型隐藏起来,让你只能使用对象进行操作。Java 允许同时使用对象和原始数据类型。


我相信原帖作者现在明白了。 - Alec Teal
那怎么办?处理器无法处理对象。 - VDanyliuk

4
如果您所说的“primitives”是值类型,那么作为用户,您可以不使用它们,而是使用“Integer”代替“int”,并为堆分配和“GC”付出开销。但这不是免费的,您必须承担成本。像32位/64位整数和“IEEE-754浮点数”这样的原语始终会更快,因为硬件支持它们。
从编译器编写者的角度来看,您必须使用机器支持的内容来使事情正常运行。

3

LISP是一种非常简单的函数式语言。最初的LISP没有原始的int类型,因此解决整数的方法之一是将successor of successor of successor of zero表示为3。

实际上,这种方法有一些优点,包括整数是开放式的,没有溢出,因此操作确实满足交换律、结合律等性质。还有一些不错的优化方法。当然,succ(succ(succ(zero)))也可以以更类似于元组的方式进行编码(最好不要在LISP中这样做)。

后来,正常的LISP中,'3'将成为一个原子,123也将是这样的原子,并带有数学运算符。

然后,还有一些符号操作语言(如SNOBOL),可以对数字字符串执行运算,例如['4', '0'] * ['3']

因此,名称就像字符'a'或整数42一样,都是对象(原子)。


3

如果让你看一下采用“一切皆为对象”设计原则的语言中类似的代码,可能会有所帮助,这种语言比Java更加严格。那就是Smalltalk。想象一下,如果Java只有int而没有Integer,但你以前需要使用Integer的所有内容都可以用int实现,那么这就是Smalltalk。

以下是在Squeak 5.0中定义SmallInteger类的代码摘录:

Integer immediateSubclass: #SmallInteger
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Kernel-Numbers'!
!SmallInteger commentStamp: 'eem 11/20/2014 08:41' prior: 0!
My instances are at least 31-bit numbers, stored in twos complement
form. The allowable range in 32-bits is approximately +- 10^9
(+- 1billion).  In 64-bits my instances are 61-bit numbers,
stored in twos complement form. The allowable range is
approximately +- 10^18 (+- 1 quintillion).   The actual
values are computed at start-up.  See SmallInteger class startUp:,
minVal, maxVal.!

!SmallInteger methodsFor: 'arithmetic' stamp: 'di 2/1/1999 21:31'!
+ aNumber 
        "Primitive. Add the receiver to the argument and answer with the result
        if it is a SmallInteger. Fail if the argument or the result is not a
        SmallInteger.
        Essential, No Lookup. See Object documentation whatIsAPrimitive."

        <primitive: 1>
        ^ super + aNumber! !

!SmallInteger class methodsFor: 'instance creation' stamp: 'tk 4/20/1999 14:17'!
basicNew

        self error: 'SmallIntegers can only be created by performing arithmetic'! !

不要为语法或语义的细节烦恼。你应该知道:SmallInteger 被定义为像语言中的其他所有东西一样的对象类,算术运算是像语言中的所有其他代码一样的方法。但它有点奇怪。它没有实例变量,你只能通过执行算术运算来创建实例,并且大多数方法看起来像是循环定义。
在底层,实现将算术映射到适当的机器指令(<primitive: 1> 是关于此的实现提示),并将 SmallInteger 存储为仅仅是整数本身。与硬件相比,受限的范围是因为几个位被保留用于标记内存字为整数,而不是指向对象的指针(“tagged pointers”)。

2

如果不能直接或间接地访问机器上的实际数据(例如基元或实际位),那么它不再是一种编程语言,而是一种接口描述语言


1

最终,所有的东西都会回到存储器中的位和指令。汇编语言、编译、过程式、面向对象以及其他所有的东西之间的区别在于你与位之间有多少抽象层,并且你从这种抽象中获得了多少好处(或成本)。


1

我会重新表述问题,以我的理解来回答。如果您认为我理解有误,请随时评论。

如果没有固有类型作为起点,基于组合和继承的类型系统如何定义任何有用的类型?除非语言实现至少知道一个固有类型作为起点,否则定义的类型注定要么是递归的,要么是空的。这是不可避免的吗?

是的,在我所知道的所有C家族语言中,这几乎是不可避免的。

如果每种类型都由其他类型组成,那么至少需要有一个固有类型来构建 - 例如,表示一个 比特 的固有类型,以便通过组合构造出 字节 类型,然后是 类型,然后是各种 整数 类型等等。然后,您需要定义可以对这些类型执行的操作,通过操纵构成其内部表示的位。

即使你只需要一个内在类型来构建,但这很可能效率非常低 - 你不想浪费空间或CPU周期,而且你想利用目标架构提供的各种存储位置和指令,包括FP寄存器和其他东西。

因此,在性能和“纯度”之间达成良好的折衷是,在语言中提供一些内置类型,这些类型可能会被现代CPU识别(如int32int64floatdouble等),并在它们之上构建其余的类型系统。在Java中,他们决定将这些内在类型称为primitives,并将它们与类分开。


这正是我想表达的。谢谢!同时也非常感谢其他人。你们帮我解决了困惑。 - user3585773

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接