为啥 java.lang.Object 不是抽象的? [复制]

Posted

技术标签:

【中文标题】为啥 java.lang.Object 不是抽象的? [复制]【英文标题】:Why java.lang.Object is not abstract? [duplicate]为什么 java.lang.Object 不是抽象的? [复制] 【发布时间】:2011-01-08 05:30:22 【问题描述】:

可能重复:Java: Rationale of the Object class not being declared abstract

为什么 Object 类是 Java 中它们的基类,而不是抽象的?

这个问题我已经问了很久了,在这里问这个问题纯粹是出于好奇,仅此而已。我的代码或任何人的代码都没有破坏,因为它不是抽象的,但我想知道他们为什么把它具体化?

为什么有人想要这个 Object 类的“实例”(而不是它的存在,也就是引用)?一种情况是糟糕的同步代码,它使用对象的实例进行锁定(至少我曾经这样使用过一次......我的错)。

Object 类的“实例”有什么实际用途吗?它的实例化如何适应 OOP?如果他们将其标记为抽象(当然是在为其方法提供实现之后)会发生什么?

【问题讨论】:

+1,它立刻让我想到了同样的问题,但是关于基于 .NET 的语言 这个问题似乎有很多困惑;抽象类不需要有抽象方法。相反,问题是问“我们有什么真正的理由需要才能做到new Object();?”如果不是,Object 应该是抽象的。 另一方面,IMO 最好问的问题是:“有什么真正需要 Object 应该是抽象的吗?”没有。所以应该是具体的。 问这个问题的目的正是为了找到使它具体化的原因,因为你需要一个理由来使一个类抽象,所以你要使它具体化,对吗?至于Object是否需要抽象的问题;一个答案是:我们对 Object 类感兴趣,因为它添加到子类的行为以及它在继承中的特殊 Super-Most 基类角色。我找不到此类实例的任何用途。如果 String 是最终的,那么必须有充分的理由说明 Object 是非抽象的,而不仅仅是它们可以使其非抽象,对吗?对吗? +1 这是一个很好的问题。唯一正确的答案可以来自设计师,我们只能提供我们的意见。如果我们都只是就为什么它是抽象的给出我们的意见,我认为我们永远不会同意,但它很有趣! 【参考方案1】:

java.lang.Object 的普通实例通常用于锁定/同步场景,这是公认的做法。

另外 - 它是抽象的原因是什么?因为它作为一个实例本身并没有完全发挥作用?真的可以用一些抽象的成员吗?不要这么想。因此,首先将其抽象化的论点是不存在的。所以不是。

以动物的经典层次结构为例,你有一个抽象类Animal,使Animal 类抽象的原因是因为Animal 的一个实例实际上是一个“无效”——因为缺少更好的词——动物(即使它的所有方法都提供了基础实现)。对于Object,情况并非如此。首先没有压倒性的案例可以将其抽象化。

【讨论】:

对我来说听起来像个黑客 @Adamski:这种信念是基于什么? @Wim:我的意思是对于设计师来说这将是一种黑客行为——如果他们使对象非抽象的意图真的只是为了让它们可以用作同步对象,设计师应该为此目的制作一个专用的同步对象。我仍然看不出为什么 Object 应该是非抽象的。 Hack 与否,除非语言设计者说“我们将Object 具体化以便它可以用于同步”,否则这并不是一个正确的答案。仅仅因为人们最终将它用于同步并不意味着这就是 为什么 它被具体化了。反正我就是这么看的。 我们现在有java.util.concurrent.locks.Lock【参考方案2】:

有时您需要一个没有自身状态的普通对象。尽管这些对象乍一看似乎毫无用处,但它们仍然具有实用性,因为每个对象都有不同的身份。 Tnis 在几种情况下很有用,其中最重要的是锁定:您想要协调两个线程。在 Java 中,您通过使用将用作锁的对象来做到这一点。对象不需要任何状态,它的存在就足以成为锁:

class MyThread extends Thread 
   private Object lock;
   public MyThread(Object l)  lock = l; 

   public void run()  

      doSomething();
      synchronized(lock) 
          doSomethingElse();
       
   


Object lock = new Object();
new MyThread(lock).start();
new MyThread(lock).start();

在这个例子中我们使用了一个锁来防止两个线程同时执行doSomethingElse()

如果 Object 是抽象的并且我们需要一个锁,我们必须在不添加任何方法或字段的情况下对其进行子类化,以便我们可以实例化锁。

想一想,这里有一个双重问题:假设对象是抽象的,它会定义任何抽象方法吗?我猜答案是。在这种情况下,将类定义为抽象类没有多大价值。

【讨论】:

嗯...这些天使用新的 java.util.concurrent 东西会不会更好 你知道任何证据表明原因 对象不是抽象的吗?当然,它很有用,但它是否导致最初的决定变得具体? “如果 Object 是抽象的并且我们需要一个锁,我们必须在不添加任何方法或字段的情况下对其进行子类化,以便我们可以实例化锁。” - 是的,但这可以在 SDK 中完成一次,因此您使用 java.lang.Lock(或其他)对象,并且您可以更好地表达您的意图。并不是每个人每次想要同步锁时都必须创建一个新的空类。 @Grundlefleck:使Object具体化的一个缺点是它迫使每个对象将其身份暴露给外界;从 OOP 的角度来看,如果代码显示 ImmutablePoint foo1 = ImmutablePoint.Create(3,4), foo2 = ImmutablePoint.Create(3,4);,则外部代码没有特别的理由应该能够判断 foo1foo2 是否是 ImmutablePoint 的不同实例。如果像 synchronized 这样的东西只适用于实现 IdentifiableByReference 的类型,那么封装相同数据的不可变对象无法区分的抽象可以得到支持。【参考方案3】:

我能想到Object 的实例有用的几种情况:

锁定和同步,就像您和其他评论者提到的那样。这可能是代码异味,但我一直看到 Object 实例一直以这种方式使用。 与Null Objects 一样,因为equals 将始终返回false,但实例本身除外。 在测试代码中,尤其是在测试集合类时。有时用虚拟对象而不是nulls 填充集合或数组是最简单的。 作为匿名类的基础实例。例如:Object o = new Object() ...code here...

【讨论】:

"作为匿名类的基础实例。" -> Object 是抽象的时候也可以这样做 所有非常有效的点,但当 Object 是抽象的时,所有这些都可以通过其他方式完成。【参考方案4】:

它不是抽象的,因为每当我们创建一个新类时,它都会扩展 Object 类,那么如果它是抽象的,则需要实现 Object 类的所有方法,这是开销...已经在该类中实现了方法...

【讨论】:

你只需要重新实现抽象方法,而不是每个方法。 嗯....这个问题引发了对抽象类是什么的一些非常基本的误解.... 只有抽象方法需要被覆盖/实现,而不是抽象类的所有方法。 我的意思是说抽象的方法..我需要实现 是的,但是您可以使 Object 抽象而不使其任何方法抽象。【参考方案5】:

这也意味着它可以在数组中实例化。在 1.5 之前的日子里,这将允许您拥有通用数据结构。在某些平台上这仍然是正确的(我在想 J2ME,但我不确定)

【讨论】:

你可以有一个Object[] 类型的通用数组,即使Object 是抽象的——你只需要用Object-subtypes 填充它。【参考方案6】:

对象需要具体的原因。

    反射 参见 Object.getClass()

    通用使用(Java 5 之前)

    比较/输出 参见 Object.toString()、Object.equals()、Object.hashCode() 等。

    同步 参见 Object.wait()、Object.notify() 等。

即使有几个领域已被替换/弃用,仍然需要一个具体的父类来为每个 Java 类提供这些功能。

Object 类用于反射,因此代码可以调用不确定类型实例的方法,即“Object.class.getDeclaredMethods()”。如果 Object 是 Abstract,那么想要参与的代码必须先实现所有抽象方法,然后客户端代码才能对它们使用反射。

根据 Sun 的说法,An abstract class is a class that is declared abstract—it may or may not include abstract methods. Abstract classes cannot be instantiated, but they can be subclassed. 这也意味着您不能调用方法或访问抽象类的公共字段。

抽象根类示例:

abstract public class AbstractBaseClass

    public Class clazz;

    public AbstractBaseClass(Class clazz)
   
       super();
       this.clazz = clazz;
   

我们 AbstractBaseClass 的一个子类:

public class ReflectedClass extends AbstractBaseClass  

    public ReflectedClass() 
    
       super(this);
    

    public static void main(String[] args)  
    
        ReflectedClass me = new ReflectedClass();
    

这不会编译,因为在构造函数中引用“this”是无效的,除非它调用同一个类中的另一个构造函数。如果我将其更改为:

   public ReflectedClass()
   
       super(ReflectedClass.class);
     

但这只是因为 ReflectedClass 有一个父级(“对象”),它 1)具体,2)有一个字段来存储其子级的类型。

一个更典型的反射示例是在非静态成员函数中:

public void foo()

    Class localClass = AbstractBaseClass.clazz;  

除非您将字段“clazz”更改为静态,否则此操作将失败。对于 Object 的类字段,这不起作用,因为它应该是特定于实例的。 Object 没有静态类字段是没有意义的。

现在,我确实尝试了以下更改,它确实有效,但有点误导。它仍然需要扩展基类才能工作。

public void genericPrint(AbstractBaseClass c)

    Class localClass = c.clazz;
    System.out.println("Class is: " + localClass);


public static void main(String[] args)

    ReflectedClass me = new ReflectedClass();
    ReflectedClass meTwo = new ReflectedClass();

    me.genericPrint(meTwo);

Java5 之前的泛型(如数组)是不可能的

Object[] array = new Object[100];
array[0] = me;
array[1] = meTwo;

在收到实际对象之前,需要构造实例以充当占位符。

【讨论】:

他们不是必须在编译之前实现所有抽象方法吗? 再一次,仅仅因为类本身是抽象的并不意味着方法必须是抽象的。请参阅 girinie 的答案。 @BlueRaja,重点不在于必须实现什么,只要有未实现的抽象方法,您就无法调用 Object.class。选择是具体的对象,或者每个想要参与反射的类都必须为任何抽象方法提供实现。换句话说,反射是 Object 不抽象的原因之一。 @Kelly,我还是不明白你的意思。 “参与反思”与“可以编译”有何不同?我对反射不太熟悉,所以这也许就是我无法连接这些点的原因。子类如何影响对Object.class.getDeclaredMethods() 的调用?他们肯定只是返回设置了抽象标志的方法吗? @Kelly:您的代码中的某些部分无法正常工作,原因与您想象的不同。 ReflectedClass 无法调用 super(this),因为 this 不是 Class 类型。试试super(this.getClass())。此外,您不能引用AbstractBaseClass.clazz,因为该字段未声明static,并且您试图通过类而不是实例来引用它。如果您有一个实例,您将能够引用该字段,即instanceOfSubclass.clazz。还是我错过了什么?【参考方案7】:

从我读到的所有内容看来,Object不需要具体,实际上应该是抽象的。

不仅不需要具体化,而且经过一些more reading 之后,我确信Object 不是 是抽象的与基本继承模型相冲突 - 我们不应该允许具体类的抽象子类,因为子类应该只添加功能。 显然,在 Java 中情况并非如此,我们有 Object 的抽象子类。

【讨论】:

我看不出用抽象类扩展具体类如何违反添加功能的想法。我可以有一个提供 4 个函数的具体类 A,然后用提供 1 个函数的类 B 扩展它,但该函数是抽象的,因为不同的子类可以有不同的实现。就像,Employee 是具体的并且具有诸如 recordHoursWorked 和enrollInRetirementPlan 之类的功能。然后我用具有 calculateCommission 的 Salesman 对其进行扩展,但这是抽象的,因为我们有具有不同佣金规则的 Salesman 子类。 我个人不认为“不需要具体”==“实际上应该是抽象的。”此外,您提供的链接只是说“它应该是”,然后继续讨论当它被确定为抽象时可以做哪些其他类型的事情。您提供的参考资料似乎并没有为您的论点提供任何形式的支持。所以我同意“它不需要是具体的”,但我还是想看看为什么它应该是抽象的。我还没有看到有人不小心创建了一个对象而引起的任何缺陷。它将如何受益? @BlueRaja:好的,让我们使用“是”的术语。推销员是雇员。 FullTimeSalesman 是一名推销员。 PartTimeSalesman 是一名推销员。推销员是抽象的,因为它具有只能在全职/兼职级别实现的方法。这如何违反“是一个”概念?我在每个级别添加功能。事实上,在一个层面上,我们创造了一个直到下一个层面才解决的二元性,这对我来说似乎是完全合理的。 (续...) 无论如何,如果问题是,“我能想出一个不违反你的‘抽象不能扩展具体’规则的解决方案,即使它比其他可能的解决方案更复杂和不自然?”我确定答案是“是”。但你为什么想要?这又回到了我最初的问题:这样的规则有什么作用?为什么我们甚至会考虑遵循一条好规则? 它不会让设计变得更简洁,当然也不是 abstract 关键字存在的原因。【参考方案8】:

没有java.lang.Object 的设计者告诉我们,我们必须根据意见来回答。有几个问题可以问,这可能有助于解决问题。

Object 的任何方法都可以从抽象中受益吗?

可以说某些方法会从中受益。以hashCode()equals() 为例,如果它们都被抽象化,对于这两者的复杂性可能会少很多挫折。这将要求开发人员弄清楚他们应该如何实现它们,从而更明显地表明它们应该是一致的(参见 Effective Java)。但是,我更倾向于认为hashCode()equals()clone() 属于单独的可选抽象(即接口)。其他方法,wait()notify()finalize() 等已经足够复杂和/或者是原生的,因此最好它们已经实现,并且不会从抽象中受益。

所以我猜答案是否定的,Object 的任何方法都不会从抽象中受益。

将 Object 类标记为抽象会有好处吗?

假设所有方法都实现了,那么标记Object abstract的唯一作用就是不能构造(即new Object()是编译错误)。这会有好处吗?我认为“对象”这个词本身就是抽象的(你能在你周围找到任何可以完全描述为“对象”的东西吗?),所以它符合面向对象的范式。然而,在纯粹主义者方面。有人可能会争辩说,强迫开发人员为任何具体的子类(甚至是空的子类)选择一个名称,将导致代码更好地表达他们的意图。我认为,就范式而言,要完全正确,对象应该标记为abstract,但归根结底,没有真正的好处,这是设计偏好的问题(实用主义与.纯度)。

使用普通对象进行同步的做法是否足以使其具体化?

许多其他答案都谈到了构造一个普通对象以在synchronized() 操作中使用。虽然这可能是一种常见且被接受的做法,但我认为如果设计人员希望它是抽象的,我认为这不是一个足够好的理由来阻止 Object 抽象。其他答案提到了我们如何在任何时候想要在某个对象上同步时声明一个 Object 的单个空子类,但这并不成立 - SDK 中可能提供了一个空子类 (java.lang.Lock或其他),可以在我们想要同步的任何时候构建。这样做还有一个额外的好处,那就是创建更强有力的意图声明。

是否有任何其他因素可能会因使 Object 抽象化而受到不利影响?

从纯粹的设计角度来看,有几个领域可能会影响选择。不幸的是,我对它们的了解还不够,无法扩展它们。但是,如果其中任何一个对决定有影响,我不会感到惊讶:

性能 安全性 JVM 实现的简单性

还有其他原因吗?

有人提到它可能与反射有关。但是,反射是在设计 Object 之后引入的。所以它是否影响反射是没有意义的——这不是原因。泛型也是如此。

还有一个令人难忘的一点,java.lang.Object 是人类设计的:他们可能犯了错误,他们可能没有考虑过这个问题。没有一种语言是没有缺陷的,这可能就是其中之一,但如果是的话,它也算不上什么大问题。而且我想我可以肯定地说,我不太可能参与设计如此广泛使用的技术的关键部分,尤其是持续了 15 年(?)并且仍然很强大的技术,所以这不应被视为批评。

话虽如此,我会把它抽象化;-p

总结 基本上,据我所知,这两个问题的答案“为什么 java.lang.Object 是具体的?”或(如果是的话)“为什么 java.lang.Object 是抽象的?”是……“为什么不呢?”。

【讨论】:

方法本身不必是抽象的——使类抽象的关键是你不能在代码中调用new Object()。问题是,真的需要每个人都必须实例化一个Object 对象吗?虽然我同意你的观点,hashCode/clone 的事情不是最好的,但它与问题并没有真正的关系。 提到 hashCode/clone 是为了帮助了解哪些方法可以从抽象中受益,而 IMO 的答案是没有,所以我认为它是相关的。关于无法新建对象的好处,添加了一些相关信息。 +1 表示有趣的事情,如果没有别的。 默认的 hashCode 太有用了。 只是想我会回到这里说我在 cmets 中谈到的假设 memoryLocationOf() 方法存在,如 System.identityHashCode(Object x)【参考方案9】:

我认为它可能应该被声明为抽象的,但是一旦完成并发布它就很难撤消而不会造成很多痛苦 - 请参阅 Java 语言规范 13.4.1:

“如果将非抽象类更改为声明为抽象类,则尝试创建该类的新实例的预先存在的二进制文件将在链接时抛出 InstantiationError,或者(如果使用反射方法)抛出 InstantiationException在运行时;因此不建议对广泛分布的类进行此类更改。”

【讨论】:

有趣。这个添加是指关于使 Object 抽象化的讨论,还是一般性观点? IIRC 这只是一般二进制兼容性建议章节的一部分。【参考方案10】:

我怀疑设计者不知道人们将来会以何种方式使用 Object,因此不想通过强制程序员在不必要的情况下创建额外的类来限制程序员,例如:互斥体、键等。

【讨论】:

【参考方案11】:

我怀疑简短的回答是集合类在 Java 泛型之前的日子里丢失了类型信息。如果一个集合不是泛型的,那么它必须返回一个具体的对象(并在运行时向下转换为之前的任何类型)。

由于将具体类变成抽象类会破坏二进制兼容性(如上线程所述),因此保留了具体的 Object 类。我想指出,在任何情况下,它都不是为了同步而创建的。虚拟类也能正常工作。

设计缺陷从一开始就不包括泛型。许多设计批评都针对该决定及其后果。 [哦,还有数组子类型化规则。]

【讨论】:

“如果一个集合不是通用的,那么它必须返回一个具体的对象” - 我不明白这个评论。集合可以保存/返回抽象类型,它只是不能——也不需要——实例化它们(因此抽象类型的所有“实例”实际上都是子类型的实例)。这是它一直以来的工作方式。 -1,集合类不需要返回具体的对象,它们返回其他对象(例如字符串)转换为对象。 即使使用泛型,并且由于类型擦除,集合不会丢失类型信息??? +1 Andrew 的评论 - 泛型类型用于编译器,没有关于类型的运行时信息。据我所知,向下转换仍然会发生(编译器插入转换并确保它们是安全的)。但是,集合可以返回抽象类型,我认为抽象与具体对集合没有影响。事实上,在编写 Object 时是否存在 Collections 类?二进制兼容性是它现在不能改变的原因,而不是它一开始就这样设计的原因。【参考方案12】:

我认为到目前为止所有的答案都忘记了 Java 1.0 的情况。在 Java 1.0 中,您无法创建匿名类,因此如果您只是出于某种目的(同步或空占位符)想要一个对象,则必须为此目的声明一个类,然后一大堆代码就会这些额外的类用于此目的。更直接的是只允许直接实例化 Object。

当然,如果您今天设计 Java,您可能会说每个人都应该这样做:

 Object NULL_OBJECT = new Object();

但这不是 1.0 中的选项。

【讨论】:

【参考方案13】:

我不明白为什么大多数人似乎认为创建一个功能齐全的类,以完全抽象的方式实现其所有方法是个好主意。 我宁愿问为什么要抽象?它会做不该做的事吗?它是否缺少它应该具有的某些功能?这两个问题都可以回答否,它本身就是一个完全工作的类,使其抽象只会导致人们实现空类。

public class UseableObject extends AbstractObject

UseableObject 继承自抽象 Object,令人惊讶的是它可以被实现,它没有添加任何功能,它存在的唯一原因是允许访问 Object 公开的方法。 我也不得不不同意在“差”同步中使用。使用私有对象来同步访问比使用 synchronize(this) 更安全,并且比 java util concurrent 中的 Lock 类更安全且更易于使用。

【讨论】:

回应“为什么要抽象?”我会说,因为它意味着什么成为“一个对象”。事实上,“对象”这个词是一个抽象的术语。我的意思是,如果你环顾四周,你找不到任何可以完全描述为“对象”的东西,总会有具体事物的具体术语。我想这是在允许有用但没有真正意义的东西的实用主义与范式的精确概念之间进行权衡。这并没有太大的区别,但想想很有趣:) @Grundlefleck 他们可以称它为 MinimalJavaObject 然后它将描述 Java 中任何 Object 的最小实现,这是具体的,但我必须同意肯定存在实用主义的权衡,我有jet 看看完美的编程语言:)。 嗯,我想这确实有道理。【参考方案14】:

在我看来,这里有一个简单的实用性问题。将类抽象化会剥夺程序员做某事的能力,即实例化它。抽象类不能做任何具体类不能做的事情。 (好吧,您可以在其中声明抽象函数,但在这种情况下,我们不需要抽象函数。)因此,通过使其具体化,您可以使其更加灵活。

当然,如果通过具体化来造成一些积极的伤害,那么“灵活性”将是一个缺点。但是我想不出通过使 Object 可实例化会造成任何积极的伤害。 (“可实例化”是一个词吗?随便。)我们可以争论某人对原始 Object 实例的任何给定使用是否是一个好主意。但是,即使您可以说服我,我所见过的原始 Object 实例的每一次使用都是一个坏主意,但这仍然不能证明那里可能没有好的用途。因此,如果它不会伤害任何东西,而且可能会有所帮助,即使我们目前想不出真正有用的方法,为什么要禁止它?

【讨论】:

以上是关于为啥 java.lang.Object 不是抽象的? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Java.lang.Object 不实现 Serializable 接口? [复制]

为啥 Java 中的每个对象都隐式扩展 java.lang.Object 类?

为啥 java.lang.Object 中的 finalize() 方法是“受保护的”?

《疯狂Java讲义》(十六)---- 枚举类

Java超类-java.lang.object

java.lang.Object 之 clone() 使用