
-
接口仅描述行为。它没有状态。但是抽象类包括状态:它描述了两者。
Bird
以抽象类和接口为例CanFly
:public abstract class Bird { private String species; private int age; public abstract void fly(); public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
让我们创建一个
MockingJay
鸟类并让它继承Bird
:public class MockingJay extends Bird { @Override public void fly() { System.out.println("Fly, bird!"); } public static void main(String[] args) { MockingJay someBird = new MockingJay(); someBird.setAge(19); System.out.println(someBird.getAge()); } }
如您所见,我们可以轻松访问抽象类的状态——its
species
和age
variables。但如果我们尝试对界面做同样的事情,情况就不一样了。我们可以尝试给它添加变量:
public interface CanFly { String species = new String(); int age = 10; public void fly(); } public interface CanFly { private String species = new String(); // Error private int age = 10; // Another error public void fly(); }
我们甚至不能在接口内声明私有变量。为什么?因为创建private修饰符是为了对用户隐藏实现。接口内部没有实现:没有什么可以隐藏的。
接口仅描述行为。因此,我们不能在接口内实现 getter 和 setter。这是接口的本质:它们需要处理行为,而不是状态。
Java 8 为具有实现的接口引入了默认方法。你已经知道他们了,所以我们不会重复自己。
-
抽象类连接和联合非常密切相关的类。同时,单个接口可以由完全没有共同点的类实现。
让我们回到鸟类的例子。
需要我们的
Bird
抽象类来创建基于该类的鸟类。只有鸟,没有别的!当然,会有不同种类的鸟。有了
CanFly
界面,每个人都以自己的方式前进。它仅描述与其名称相关的行为(飞行)。许多不相关的东西“可以飞”。这 4 个实体彼此无关。他们甚至都不是活着的。然而,他们都
CanFly
。我们无法使用抽象类来描述它们。它们不共享相同的状态或相同的字段。要定义一架飞机,我们可能需要型号、生产年份和最大乘客人数等字段。对于 Carlson,我们需要一些田地来存放他今天吃的所有糖果,以及他将与他的弟弟一起玩的游戏列表。对于一只蚊子,……呃……我什至不知道……也许,一个“烦人程度”?:)
关键是我们不能用一个抽象类来描述它们。他们太不一样了。但它们确实有共同的行为:它们会飞。界面非常适合描述世界上所有可以飞行、游泳、跳跃或表现出某些其他行为的事物。
-
类可以实现任意多个接口,但只能继承一个类。
我们已经不止一次提到过这一点。Java没有类的多继承,但是支持接口的多继承。这一点部分源自前一点:一个接口连接许多不同的类,这些类通常没有其他共同点,而抽象类是为一组非常密切相关的类创建的。因此,您只能继承一个这样的类是有道理的。抽象类描述了一种“is-a”关系。
标准接口:InputStream 和 OutputStream
我们已经了解了负责输入和输出流的各种类。让我们考虑InputStream
和OutputStream
。通常,这些根本不是接口,而是完全真正的抽象类。现在你知道那是什么意思了,所以使用它们会容易得多:) InputStream
是一个负责字节输入的抽象类。Java 有几个继承InputStream
. 它们中的每一个都旨在从不同来源接收数据。因为InputStream
是父类,所以它提供了多种方法,可以轻松处理数据流。每个后代InputStream
都有这些方法:
int available()
返回可供读取的字节数;close()
关闭输入流;int read()
返回流中下一个可用字节的整数表示。如果已到达流的末尾,则返回 -1;int read(byte[] buffer)
尝试将字节读入缓冲区,并返回读取的字节数。当到达文件末尾时,返回-1;int read(byte[] buffer, int byteOffset, int byteCount)
写入字节块的一部分。当字节数组可能没有完全填充时使用它。当到达文件末尾时,返回-1;long skip(long byteCount)
跳过输入流中的 byteCount 个字节,并返回忽略的字节数。
FileInputStream
: 最常见的类型InputStream
。它用于从文件中读取信息;StringBufferInputStream
: 另一种有用的类型InputStream
。它将字符串转换为InputStream
;BufferedInputStream
:缓冲输入流。它最常用于提高性能。
BufferedReader
说你不必使用它吗? 当我们写:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
......你不必使用BufferedReader
: AnInputStreamReader
就可以完成这项工作。但是BufferedReader
提高了性能并且还可以读取整行数据而不是单个字符。同样的事情适用于BufferedInputStream
!该类在特殊缓冲区中累积输入数据,而无需不断访问输入设备。让我们考虑一个例子:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
public class BufferedInputExample {
public static void main(String[] args) throws Exception {
InputStream inputStream = null;
BufferedInputStream buffer = null;
try {
inputStream = new FileInputStream("D:/Users/UserName/someFile.txt");
buffer = new BufferedInputStream(inputStream);
while(buffer.available()>0) {
char c = (char)buffer.read();
System.out.println("Character read: " + c);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
inputStream.close();
buffer.close();
}
}
}
在此示例中,我们从位于计算机“ D:/Users/UserName/someFile.txt ”的文件中读取数据。我们创建了 2 个对象——aFileInputStream
和一个BufferedInputStream
“包装”它的对象。然后我们从文件中读取字节并将它们转换为字符。我们这样做直到文件结束。如您所见,这里没有什么复杂的。您可以复制此代码并在计算机上的真实文件上运行它:) 该类OutputStream
是表示字节输出流的抽象类。如您所知,这与InputStream
. 它不负责从某处读取数据,而是负责将数据发送到某处。就像 一样InputStream
,这个抽象类为它的所有后代提供了一组方便的方法:
void close()
关闭输出流;void flush()
清除所有输出缓冲区;abstract void write(int oneByte)
将 1 个字节写入输出流;void write(byte[] buffer)
将字节数组写入输出流;void write(byte[] buffer, int offset, int count)
从数组的偏移位置开始写入一系列 count 字节。
OutputStream
:
-
DataOutputStream
. 包含用于编写标准 Java 数据类型的方法的输出流。一个非常简单的类,用于编写原始 Java 数据类型和字符串。即使没有解释,您也可能会理解以下代码:
import java.io.*; public class DataOutputStreamExample { public static void main(String[] args) throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("testFile.txt")); dos.writeUTF("SomeString"); dos.writeInt(22); dos.writeDouble(1.21323); dos.writeBoolean(true); } }
它对每种类型都有单独的方法——
writeDouble()
、writeLong()
、writeShort()
等等。 FileOutputStream
. 此类实现了一种将数据发送到磁盘文件的机制。顺便说一句,我们已经在上一个例子中使用了它。你注意到了吗?我们将它传递给充当“包装器”的 DataOutputStream。BufferedOutputStream
. 缓冲输出流。这里也没有什么复杂的。它的目的类似于BufferedInputStream
(orBufferedReader
)。它不是通常的顺序读取数据,而是使用特殊的“累积”缓冲区写入数据。缓冲区可以减少访问数据接收器的次数,从而提高性能。import java.io.*; public class DataOutputStreamExample { public static void main(String[] args) throws IOException { FileOutputStream outputStream = new FileOutputStream("D:/Users/Username/someFile.txt"); BufferedOutputStream bufferedStream = new BufferedOutputStream(outputStream); String text = "I love Java!"; // We'll convert this string to a byte array and write it to a file byte[] buffer = text.getBytes(); bufferedStream.write(buffer, 0, buffer.length); } }
同样,您可以自己试用此代码并验证它是否适用于您计算机上的真实文件。
FileInputStream
,FileOutputStream
和的单独课程BuffreredInputStream
,所以对于初次相识的人来说,这些信息已经足够了。 就是这样!我们希望您了解接口和抽象类之间的区别,并准备好回答任何问题,甚至是技巧性问题 :)
更多阅读: |
---|
GO TO FULL VERSION