为什么java.lang.Object不是抽象的?
可能重复:
Java:Object类的基本原理不被声明为抽象的
为什么Object类是Java的基础类,而不是抽象的?
这个问题我真的有很长的一段时间,纯粹是出于好奇而被问到,就是这样。 我的代码或者任何人的代码都没有破坏,因为它不是抽象的,但是我想知道为什么他们把它变成具体的?
为什么会有人想要这个对象类的“实例”(而不是它的存在和参考)? 一种情况是一个糟糕的同步代码,它使用一个对象的实例进行locking(至less我这样用了一次..我的坏)。
对象类的“实例”有没有实际的用法? 它的实例如何适合OOP? 如果将它标记为抽象(当然在为其方法提供实现之后)会发生什么?
如果没有java.lang.Object
的devise者告诉我们,我们必须根据观点来回答。 有几个问题可以帮助解决问题。
对象的任何方法都会从抽象中获益吗?
可以说有些方法会从中受益。 以hashCode()
和equals()
为例,如果两者都被抽象化了,那么对于这两者的复杂性可能就会less得多了。 这要求开发人员弄清楚他们应该如何实现它们,使其更加明显,他们应该是一致的(见Effective Java)。 然而,我更多的认为, hashCode()
, equals()
和clone()
属于不同的抽象抽象(即接口)。 其他的方法, wait()
, notify()
, finalize()
等是非常复杂和/或是本地的,所以最好是已经实现了,不会被抽象化。
所以我猜测答案是否定的,Object的任何方法都不会从抽象中受益。
将Object类标记为抽象types会有好处吗?
假设所有的方法都实现了,标记Object抽象的唯一效果是它不能被构造(即new Object()
是一个编译错误)。 这会有好处吗? 我认为“对象”这个术语本身就是抽象的(你能find你身边的任何可以被完全描述为“一个对象”的东西吗?),所以它符合面向对象的范式。 然而,在纯粹的一面。 有人可能会争辩说,迫使开发人员为任何具体的子类select一个名字,甚至是空的,都会导致代码更好地expression他们的意图。 我认为,在范式方面是完全正确的,对象应该是abstract
标记,但是当它归结为它时,没有真正的利益,这是一个devise偏好(实用主义与纯洁)的问题。
是否使用普通对象进行同步的做法是否具有足够的理由?
其他许多答案都讨论了在synchronized()
操作中构造一个纯对象。 虽然这可能是一个普遍的和被广泛接受的做法,但是我不相信如果devise者想要的那样防止Object被抽象的话是不够的。 其他答案已经提到过,如果我们想要在某个对象上进行同步时,我们将不得不声明一个Object的空子类,但是这并不能保证 – 在SDK中可以提供一个空的子类( java.lang.Lock
或其他),它可以在我们想要同步的任何时候构build。 这样做会创造更强有力的意向声明。
有没有其他因素可以通过使对象抽象而受到不利影响?
有几个区域与纯粹的devise观点分开,这可能影响了select。 不幸的是,我对他们的了解还不够多。 但是,如果其中的任何一个对决定产生影响,我不会感到惊讶:
- 性能
- 安全
- 简单的JVM实现
还有其他原因吗?
有人提到它可能与反思有关。 但是,在deviseObject之后引入了reflection。 所以它是否影响反思是没有意义的 – 这不是原因。 generics相同。
还有一个令人难忘的地方就是java.lang.Object是由人类devise的:他们可能犯了一个错误,他们可能没有考虑过这个问题。 没有任何语言没有缺陷,这可能是其中之一,但如果是这样的话,那不是一个大问题。 而且我想我可以放心地说,我不太可能会参与devise这样一个被广泛使用的技术的关键部分,尤其是持续15(?)年并且仍然强大的技术,所以这个不应该被认为是一种批评。
说了这么多,我会把它抽象的; – p
概要
基本上,就我所见,这两个问题的答案是“为什么java.lang.Object是具体的?” 或者(如果是这样的话)“为什么java.lang.Object是抽象的? 是…“为什么不呢?”
java.lang.Object
普通实例通常用于locking/同步场景,这是公认的做法。
另外 – 这是什么原因是抽象的? 因为它作为一个实例本身并没有完全实用? 它真的可以做一些抽象的成员吗? 不要这样想。 所以把它抽象化的论点是不存在的。 所以不是。
拿经典的动物层次来说,你有一个抽象类Animal
, Animal
类抽象的理由是因为动物的一个实例实际上是一个“无效的” – 由于缺less一个更好的单词动物(即使它的所有方法提供基础实施)。 与Object
,情况根本不是这样。 没有压倒性的情况下,首先要把它抽象出来。
我可以想到Object
实例有用的几种情况:
- locking和同步,就像你和其他评论者所说的。 这可能是一种代码味道,但我已经看到对象实例一直用这种方式。
- 作为空对象 ,因为除了实例本身,
equals
总是返回false。 - 在testing代码中,特别是在testing集合类时。 有时用dummy对象而不是
null
来填充集合或数组是最容易的。 - 作为匿名类的基础实例。 例如:
Object o = new Object() {...code here...}
从我读过的所有东西看, Object
似乎不需要具体 ,实际上应该是抽象的。
不仅没有必要具体化,而且经过一些更多的阅读后,我确信Object
不是抽象的,与基本的inheritance模型相冲突 – 我们不应该允许具体类的抽象子类,因为子类只应该添加function。
很明显,在Java中我们有Object
抽象子类。
我认为它可能应该被声明为抽象的,但一旦完成并释放,就很难撤销而不会造成很大的痛苦 – 请参阅Java语言规范13.4.1:
“如果一个非抽象的类更改为声明为抽象的,那么试图创build该类的新实例的预先存在的二进制文件将在链接时引发InstantiationError,或者(如果使用reflection方法)在运行时引发InstantiationException ;因此,对于广泛分布的类别,不推荐这样的改变。“
有时你需要一个没有自己的状态的普通对象。 虽然这样的东西一眼看起来毫无用处,但由于每个人都有不同的身份 ,所以它们还是有用的。 Tnis在几个场景中很有用,其中最重要的是locking:你想协调两个线程。 在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是抽象的,而且我们需要一个锁,那么我们就不得不为它添加子类而不添加任何方法或字段,以便我们可以实例化锁。
想想看,这是一个双重的问题:假设对象是抽象的,它会定义任何抽象方法吗? 我想答案是否定的 。 在这种情况下,把这个类定义为抽象的东西是没有多大价值的。
我不明白为什么大多数人似乎认为,做一个function完备的类,实现所有的方法在一个使用完全抽象的将是一个好主意。
我宁愿问为什么要抽象? 它做它不应该做的事情吗? 它是否缺less一些应有的function? 这两个问题都可以用“否”来回答,它是一个完全自主的工作课堂,抽象只会导致人们实施空课。
public class UseableObject extends AbstractObject{}
UseableObject从抽象Objectinheritance而来,它可以被实现,但是它并没有增加任何function,它唯一存在的原因是允许访问Object公开的方法。
我也不同意在“差”同步中的使用。 使用私有对象来同步访问比使用同步(this)更安全,比java util并发的Lock类更安全也更容易使用。
在我看来,这里有一个简单的实用性问题。 编写一个类的抽象消除了程序员做某事的能力,即实例化它。 对于一个抽象类,你无法做什么,你不能用一个具体的类。 (嗯,你可以在其中声明抽象函数,但在这种情况下,我们不需要抽象函数)。因此,通过使它具体化,使其更加灵活。
当然,如果通过具体的做法造成了一些积极的伤害,那么“灵活性”就是一个缺点。 但是我不能想象通过使对象可实现的任何主动的伤害。 (是“可实例化”的一个词?什么都可以)。我们可以辩论是否某人使用了一个原始对象实例,这是一个好主意。 但是,即使你能说服我,我见过的一个原始对象实例的每一个用法都是一个坏主意,但是这仍然不能certificate在那里没有很好的用处。 所以,如果不伤害任何东西,这可能会有所帮助,即使我们现在还不能想到一个真正有用的方法,为什么要禁止呢?
我怀疑devise者不知道将来人们可能会用什么方法来使用一个对象,因此不希望限制程序员通过强制他们创build一个不必要的附加类,比如像互斥体,键等等
我认为迄今为止所有的答案都忘记了Java 1.0的情况。 在Java 1.0中,你不能创build一个匿名类,所以如果你只是为了某种目的而想要一个对象(同步或者一个空的占位符),你就必须为此目的声明一个类,然后一大堆代码会这些额外的类为此目的。 更直接地允许直接实例化对象。
当然,如果你今天在deviseJava,你可能会说每个人都应该这样做:
Object NULL_OBJECT = new Object(){};
但是这不是1.0的select。
这也意味着它可以在一个数组中实例化。 在1.5天之前,这将允许你有通用的数据结构。 这在某些平台上仍然可能是正确的(我在考虑J2ME,但我不确定)
对象需要具体的原因。
-
reflection
看Object.getClass() -
通用(Java 5之前)
-
比较/输出
请参阅Object.toString(),Object.equals(),Object.hashCode()等 -
同步化
请参阅Object.wait(),Object.notify()等
尽pipe一些领域已被replace/弃用,但仍然需要一个具体的父类来为每个Java类提供这些特性。
Object类用于reflection,因此代码可以调用不确定types实例的方法,即“Object.class.getDeclaredMethods()”。 如果Object是抽象的,那么想要参与的代码必须在客户端代码使用reflection之前实现所有的抽象方法。
据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有一个父类(“对象”),它是1)具体的,2)有一个字段来存储它的子types。
reflection的一个更典型的例子是在一个非静态成员函数中:
public void foo() { Class localClass = AbstractBaseClass.clazz; }
除非您将字段“clazz”更改为静态,否则将失败。 对于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之前的generics(就像数组)是不可能的
Object[] array = new Object[100]; array[0] = me; array[1] = meTwo;
需要构造实例来充当占位符,直到接收到实际对象。
我怀疑,简单的答案是,集合类在Javagenerics之前的那些日子里丢失了types信息。 如果一个集合不是通用的,那么它必须返回一个具体的对象(并且在运行时被downcast为任何以前的types)。
由于将一个具体类变成一个抽象类会破坏二进制兼容性(如注意到的upthread),因此保留了具体的Object类。 我想指出的是,这绝不是为了同步的目的而创造的。 虚拟课堂也是如此。
devise缺陷从一开始就不包括generics。 很多devise批评都是针对这个决定及其后果。 [哦,和数组分类规则。]
它不是抽象的,因为每当我们创build一个新的类,它扩展了对象类,那么如果它是抽象的,你需要实现的所有方法的对象类的开销…已经有在该类中实现的方法…