oop - 为什么java.lang.Object不是抽象的?
可能重复:
Java:Object类的基本原理未被声明为abstract
为什么Object类是基于Java的基类,而不是抽象的?
我真的很长时间都有这个问题,这里纯粹是出于好奇而被问到这一点。 我的代码或任何人的代码都没有破坏因为它不是抽象的,但我想知道为什么它们具体化了?
为什么有人想要这个Object类的“实例”(而不是它的存在a.k.a. Reference)? 一种情况是一个糟糕的同步代码,它使用一个Object的实例进行锁定(至少我用这种方式一次......我的坏)。
是否有任何实际使用Object类的“实例”? 它的实例化如何适合OOP? 如果他们将其标记为抽象(当然在为其方法提供实现之后)会发生什么?
如果没有java.lang.Object
的设计师告诉我们,我们必须根据意见作出答案。 有几个问题可以提出来帮助清理它。
Object的任何方法都会受益于抽象吗?
可以说有些方法会从中受益。 以synchronized()
和java.lang.Lock
为例,如果它们都被抽象化,那么对于这两者的复杂性可能会有很少的挫败感。 这将要求开发人员弄清楚他们应该如何实现它们,使它们更加明显,它们应该是一致的(参见Effective Java)。 但是,我更倾向于hashCode()
,equals()
和clone()
属于单独的选择加入抽象(即接口)。 其他方法,wait()
,notify()
,finalize()
等是足够复杂和/或原生的,因此最好它们已经实现,并且不会从抽象中受益。
所以我猜答案是否定的,Object的任何方法都不会从抽象中受益。
将Object类标记为抽象是否有益处?
假设所有方法都已实现,标记Object abstract的唯一效果是它无法构造(即synchronized()
是编译错误)。 这会有好处吗? 我认为术语“对象”本身就是抽象的(你能找到任何可以完全描述为“对象”的东西吗?),所以它适合面向对象的范式。 然而,它是纯粹的一面。 可以说,强制开发人员为任何具体的子类选择一个名称,即使是空子类,也会产生更好地表达其意图的代码。 我认为,就范式而言,完全正确的是,对象应该标记为java.lang.Lock
,但是当它归结为它时,没有真正的好处,这是设计偏好(实用主义与纯度)的问题。
使用普通对象进行同步的做法是否具有足够的理由使其具体化?
许多其他答案谈论构建一个在synchronized()
操作中使用的普通对象。 虽然这可能是一种常见且被接受的做法,但我不认为如果设计师希望将对象抽象化,那将是一个足够好的理由。 其他答案已经提到我们如何在我们想要在某个对象上同步时声明一个空的Object子类,但是这并没有站起来 - SDK中可以提供一个空子类(java.lang.Lock
或者其他什么) ),可以在我们想要同步的任何时候构建。 这样做可以产生更强大的意图声明。
是否有任何其他因素可以通过使对象抽象而受到不利影响?
有几个区域,与纯粹的设计观点分开,可能影响了选择。 不幸的是,我对他们的了解并不充分。 但是,如果其中任何一项对决定产生影响,我不会感到惊讶:
- 性能
- 安全
- 简化JVM的实现
还有其他原因吗?
有人提到它可能与反思有关。 但是,在设计Object之后引入了反射。 因此,它是否影响反射是没有实际意义的 - 这不是原因。 对于泛型一样。
java.lang.Object也是人类设计的难忘点:他们可能犯了一个错误,他们可能没有考虑过这个问题。 没有没有缺陷的语言,这可能是其中之一,但如果是,它就不是一个大问题了。 而且我认为我可以肯定地说,在不缺乏野心的情况下,我不太可能参与设计这种广泛使用的技术的关键部分,特别是那个持续15年(?)并仍然强劲的技术,所以这个 不应该被视为批评。
话虽如此,我会把它变成抽象的;-p
摘要
基本上,据我所知,两个问题的答案“为什么java.lang.Object具体?” 或者(如果是这样)“为什么java.lang.Object是抽象的?” 是......“为什么不呢?”
Animal
的普通实例通常用于锁定/同步方案,这是公认的惯例。
另外 - 它是抽象的原因是什么? 因为它作为一个实例本身并不完全正常? 它真的可以用一些抽象的成员吗? 不要这么认为。 因此,首先使其成为抽象的论点是不存在的。 所以事实并非如此。
采取经典的动物等级,你有一个抽象类Animal
,使Animal
类抽象的原因是因为动物的一个实例实际上是一个'无效' - 缺乏一个更好的单词动物(即使它的所有方法都是如此) 提供基础实现)。 随着Object
,情况根本不是这样。 首先要让它抽象化是没有压倒性的。
我可以想到Object o = new Object() {...code here...}
实例有用的几种情况:
- 锁定和同步,就像你和其他评论者提到的那样。 它可能是代码味道,但我看到Object实例一直以这种方式使用。
- 作为Null对象,因为
Object o = new Object() {...code here...}
将始终返回false,除了实例本身。 - 在测试代码中,特别是在测试集合类时。 有时最简单的方法是使用虚拟对象而不是
Object o = new Object() {...code here...}
来填充集合或数组。 - 作为匿名类的基本实例。 例如:
Object o = new Object() {...code here...}
从我读过的所有内容来看,似乎Object
不需要具体,实际上应该是抽象的。
它不仅没有必要具体,而且经过一些阅读后我确信Object
不是抽象的与基本继承模型冲突 - 我们不应该允许具体类的抽象子类,因为子类应该只 添加功能。
很明显,Java不是这种情况,我们有抽象的子类Object
。
我认为它可能应该被声明为抽象的,但是一旦完成并发布它就很难撤消而不会造成很大的痛苦 - 参见Java语言规范13.4.1:
“如果将不是抽象的类更改为声明为抽象,那么尝试创建该类的新实例的预先存在的二进制文件将在链接时抛出InstantiationError,或者(如果使用反射方法)在运行时抛出InstantiationException 因此,不建议对广泛分布的课程进行这样的改变。“
有时你需要一个没有自己状态的普通对象。 尽管这些物体一见钟情似乎毫无用处,但它们仍具有实用性,因为每个物体都具有不同的身份。 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是抽象的并且我们需要一个锁,我们必须将它子类化而不添加任何方法或字段,以便我们可以实例化锁。
想想它,这是你的双重问题:假设对象是抽象的,它会定义任何抽象方法吗? 我想答案是否定的。在这种情况下,将类定义为抽象没有太大价值。
我不明白为什么大多数人似乎相信制作一个功能齐全的类,它以完全抽象的方式实现其所有方法都是一个好主意。
我宁愿问为什么要把它抽象化? 它有什么不应该做的吗? 它缺少一些应该具备的功能吗? 这两个问题都可以用no来回答,它本身就是一个完全可以工作的类,使它抽象化只会导致人们实现空类。
public class UseableObject extends AbstractObject{}
UseableObject继承自抽象Object,令人惊讶它可以实现,它不添加任何功能,它唯一的存在理由是允许访问Object公开的方法。
另外,我不同意在“差”同步中的使用。 使用私有对象来同步访问比使用synchronize(this)更安全,并且比java util concurrent中的Lock类更安全且更易于使用。
在我看来,这里有一个简单的实用性问题。 制作类摘要会剥夺程序员执行某些操作的能力,即实例化它。 对于一个你不能用具体类做的抽象类,你无能为力。 (好吧,你可以在其中声明抽象函数,但在这种情况下我们不需要抽象函数。)因此,通过使其具体化,你可以使它更灵活。
当然,如果通过使其具体化而产生一些主动伤害,那么“灵活性”将是一个缺点。 但我无法想象通过使Object可实现的任何活动伤害。 (“可实例化”是一个词吗?无论如何。)我们可以讨论是否有人使用原始对象实例的任何特定用途是一个好主意。 但是,即使你能说服我,我曾经见过的一个原始Object实例的每一次使用都是一个坏主意,但仍然不能证明那里可能没有很好的用途。 所以,如果它没有伤害任何东西,它可能有所帮助,即使我们想不出它现在实际上有帮助的方式,为什么要禁止呢?
我怀疑设计人员不知道人们可能会以何种方式使用Object将来可能会使用,因此不希望通过强制执行它们来创建一个不需要的附加类来限制程序员,例如对于互斥锁,密钥之类的东西 等等
我认为到目前为止所有答案都忘记了Java 1.0的用途。 在Java 1.0中,你不能创建一个匿名类,所以如果你只是想要一个对象用于某种目的(同步或空占位符),你必须为此目的声明一个类,然后一大堆代码就会有 这些额外的课程用于此目的。 更直接的是允许直接实例化Object。
当然,如果你今天在设计Java,你可能会说每个人都应该这样做:
Object NULL_OBJECT = new Object(){};
但这不是1.0的选择。
它还意味着它可以在数组中实例化。 在1.5天之前,这将允许您拥有通用数据结构。 在某些平台上可能仍然如此(我在想J2ME,但我不确定)
对象需要具体的原因。
反射
请参阅Object.getClass()通用(Java 5之前)
比较/输出
请参阅Object.toString(),Object.equals(),Object.hashCode()等。同步
请参阅Object.wait(),Object.notify()等。
尽管已经替换/弃用了几个区域,但仍然需要一个具体的父类来为每个Java类提供这些功能。
Object类用于反射,因此代码可以在不确定类型的实例上调用方法,即'Object.class.getDeclaredMethods()'。 如果Object是抽象的,那么想要参与的代码必须在客户端代码可以对它们使用反射之前实现所有抽象方法。
根据Sun的说法,抽象类是一个被声明为抽象的类 - 它可能包含也可能不包含抽象方法。 抽象类无法实例化,但可以进行子类化。 这也意味着您无法调用方法或访问抽象类的公共字段。
抽象根类的示例:
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有一个父对象(“Object”),它是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);
}
Pre-Java5泛型(与数组一样)是不可能的
Object[] array = new Object[100];
array[0] = me;
array[1] = meTwo;
需要构造实例以用作占位符,直到接收到实际对象。
我怀疑简短的回答是收集类在Java泛型之前的几天丢失了类型信息。 如果集合不是通用的,那么它必须返回一个具体的Object(并在运行时向下转换为以前的任何类型)。
由于将具体类转换为抽象类会破坏二进制兼容性(如上所述),因此保留了具体的Object类。 我想指出,在任何情况下都不是为了同步的唯一目的而创建的; 虚拟课程同样适用。
设计缺陷从一开始就不包括泛型。 许多设计批评都是针对该决定及其后果。 [哦,数组子类型规则。]
它不是抽象的,因为每当我们创建一个新类时它会扩展Object类,那么如果它是抽象的,你需要实现Object类的所有方法,这是开销...已经在该类中实现了方法...