理解 Java 对象:抽象对象和具体对象
我最近一直在用Java编程,但直到我的项目需要用到一些著名的面向对象设计模式时,我才意识到自己对底层原理有多么不了解,所以要跟上所有术语的步伐对我来说真是一个巨大的挑战。我感觉自己就像撞上了一堵墙。
我决定花些时间来剖析这些基础模块——所以让我们揭开Java术语的神秘面纱吧。我希望,如果你也曾有过类似的困惑,这些笔记或许能帮助你更深入地理解Java的庞大体系。
Java术语#1:创建对象
在 Java 中,几乎所有东西都是对象——例如 `Object` String、` Object`ArrayList和HashMap`Object`。对象由类创建。你可以把你的类代码想象成 Java 创建对象的指令手册。
创建对象需要创建类的实例——因此,“实例化类”这个短语经常被那些想显得自己特别博学的人使用。但这两个术语可以互换使用,具体取决于你和谁交谈,或者你是否在玩拼字游戏。
要正式创建一个对象/实例化一个类,也就是说,为了防止编译器像 Mr Resetti 一样对我们大发雷霆,我们必须遵循三个步骤:声明、实例化和初始化。
声明是通过指定变量的类型type和值来实现的,所以我们可以告诉 Java ,它会准备好一切,使其能够接受一个类型。但此时,我们的对象更像是我的银行账户,而不是一条可怕的巨龙:它是空的。里面什么都没有。空空如也,什么都没有,或者更确切地说,是空的。nameDragon viserion;Dragonnull
时刻提防null。如果我有孩子,我不希望他们和这些人混在一起null。
顺便提一下:Java 同时拥有基本类型变量和引用变量,我们现在可能觉得它们无关紧要,但它们偶尔也会带来一些麻烦,尤其是在赋值和比较的时候。不过,嘿,我们用的是 Java 编程,所以偶尔遇到点小麻烦也无可厚非。
总之,我们已经准备好进行步骤 2 和步骤 3——实例化和初始化,这两者通常成对出现:
viserion = new Dragon();
该new运算符会实例化类,在后台分配所需的内存,然后返回该内存的引用(地址),以便我们的viserion变量知道将任何访问者指向哪里。调用该运算符new还需要调用构造函数,这就是括号的作用所在。这()部分处理初始化,它会查找与签名匹配的类构造函数,最终使我们的对象完全设置好,可以随时调用。
Java 类中的构造函数与类名相同,且没有返回值。如果我们查看一下这个类,会发现它有两个构造函数签名:一个可以响应零参数构造函数,而零参数构造函数又会调用我们带有默认值Dragon的双参数构造函数。StomachMouth
public class Dragon {
private Stomach stomach;
private Mouth mouth;
public Dragon() {
this(new BigStomach(), new BigMouth());
}
public Dragon(Stomach stomach, Mouth mouth) {
this.stomach = stomach;
this.mouth = mouth;
}
public eat(Food food) {
mouth.consume(food);
stomach.digest(food);
}
public dracarys(Target target) {
return mouth.breatheFire(target);
}
}
构造函数对于类来说是必需的,但如果没有显式定义构造函数,编译器将免费为该类提供一个无参默认构造函数。
我们还把声明和实例化/初始化拆分到viserion了两行,这其实没必要——如果需要的话,我们可以把所有赋值操作都放在一行里。我们甚至不需要把对象赋值给变量,可以直接在表达式中使用它们。让我们把这些整合起来,再创建一个Dragon:
Dragon drogon = new Dragon(new GiantStomach(), new GiantMouth());
我们声明了一个引用变量drogon,用它实例化了 Dragon 类new,然后使用满足 ` Stomachand`Mouth签名要求的构造函数对其进行初始化。我们还在构造函数调用中直接实例化了两个新对象 `A`GiantStomach和`B`。GiantMouth
现在,我们来看一个实际的例子。请看ArrayListJava 源代码中三种构造函数签名的示例:
// Constructs an empty list with the specified initial capacity.
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// Constructs an empty list with an initial capacity of ten.
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
哎呀,信息量真不小。就当是打好基础吧。如果你跟我一样,经常会不知不觉地知道这些东西,那温故知新总是好的。
Java术语#2:具体类
这个词到处都能看到,而且我发现大家似乎都应该默认知道它的意思。说实话,我好几个月都不知道它是什么意思。
简而言之,具体类是指任何可以使用new关键字创建(实例化)的类。具体类的所有方法都已实现,无论它们包含多少个接口implement或类extend。
在制作具体作品时,不需要特定的语言关键字,但你可能会在 UML 图和人们闲聊面向对象编程时经常看到这个词,所以了解它的含义以便参与讨论是很有益的。
我们的Dragon类是名副其实的、100% 认证的具体类,这在不使用任何抽象概念的情况下很容易做到。我发现,默认情况下将类视为具体类更容易理解,只有在使用继承(通过 `__init__` extends)和接口(通过 `__init__` implements)时,才真正需要考虑具体类和抽象类的区别。
Java术语#3:接口
如果说具体的类是使用说明书,那么接口就是蓝图——它是一份未实现方法签名的列表。通过声明使用implement接口,我们的类就承诺支持这些方法。当一个类选择使用implement某个接口时,这通常被称为一种契约——因为实现该接口的类实际上承诺会包含这些方法。
如果你在夜间编译代码,不履行接口契约也会导致编译器在深夜找上门来。它肯定不会高兴的。
一个 Java 类可以实现多个接口,如下所示ArrayList:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
在 Java 编程中,你会经常看到“-able”接口——这里我们可以看到 `Object`Cloneable和Serializable`Object`,你会发现它们反复出现,但现在我们只关注 `Object` List。注意 `Object`List使用了`-able`interface关键字而不是`-able` class:
public interface List<E> {
int size();
boolean isEmpty();
//... there's plenty more empty methods
此接口中既没有实现 `and`,size()也没有实现 `or` isEmpty()。我们无法使用`is` 关键字List从此接口创建对象new,因此它不是一个具体的类。但是,通过实现 `or` List,ArrayList承诺遵守约定,并确保它知道如何响应对接口中任何方法签名的请求。
接口就像是给某些类系上一个小领结——它们增添了一份简洁的规范性,类型检查器会遵守这种规范。你还可以使用接口名称作为引用变量的类型,这意味着任何实现了该接口的类都可以被赋值给它。
Java术语#4:抽象类
抽象类可以同时拥有已实现的方法和未实现的方法,但它们不是具体类,因为它们不能使用 ` new__init__` 关键字创建对象。然而,子类可以使用 ` extends__init__` 关键字继承它们的功能。
Java 中的许多 List 集合都继承自抽象类AbstractList:
// AbstractList.java
public abstract class AbstractList<E> {
//...
public boolean add(E e) {
add(size(), e);
return true;
}
public abstract E get(int index);
注意abstract用于表示抽象类和抽象方法的关键字:使用抽象方法需要先声明抽象类。还要注意,抽象方法get没有使用大括号,而是用分号括起来,这与我们之前声明接口方法的方式类似。
ArrayList是 的子类AbstractList,因此必须实现一个get方法,但会继承该add方法:
// ArrayList.java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// ...
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
如果在扩展时ArrayList没有实现自己的方法,则必须声明自己的类才能满足编译器的要求,这意味着它不能使用关键字创建对象,也无法达到梦寐以求的具体状态。getAbstractListabstractnew
总结
我们已经了解了 Java 中具体类和抽象类的区别,这意味着我们需要熟悉类的实例化方式。我们从一个简单的示例Dragon类开始,逐步研究 JDK 12 中的一些源代码片段,了解了ArrayListextendsAbstractList和 implements是如何工作的ArrayList。
理解这些基本组成部分,就能为掌握 Java 一些最突出的设计模式的严格面向对象特性奠定极其有用的基础。
这篇文章对您有帮助吗?如果您有任何意见或建议,例如哪些地方可以更清晰或解释得更清楚,我将不胜感激。如果您能指出任何错误或错误,也请不吝赐教!
文章来源:https://dev.to/martingaston/understanding-java-objects-abstract-and-concrete-488a