适配器模式(Adapter)
还是先从引入说起,先来看一个问题吧,总所周知,在中国通用的电压时 220V,
而美国电压则是 110V,如果有经常在美国和中国之间跑的 IT 人,而其笔记本都是随身携带的,
那么它的笔记本的电压问题如何解决呢?
(因为在美国和中国电压不同,所以一般的电器会不通用的)
而适配器在这个问题上体现得妙极妙极。
现在的笔记本都有一个电源适配器,而正是这个电源适配器来解决上面提到的适配器问题,
比如,一款索尼笔记本,其输入电流为交流100V~240V,而输出则是统一的直流 19.5V,
在电源适配器的一端输入交流电流,然后通过电源适配器把电源变成需要的电压,
也就是适配器的作用是使得一个东西适合另外一个东西。
下面来给出适配器模式的定义
适配器模式将一个接口转换成另外一个接口,以符合客户的期望。
主要用于以下情况:
比如现在我有一个旧的软件系统,而其中有一个组件呢,它已经过时了,需要更新,
所以我又有了第三方的组件(新组件),但是旧组件的接口和新组件的接口不同,
同时,您又不想去改变现有的代码(如果系统大的话,或许您改都改不了),
此时呢,就是让适配器模式登场的时刻了,
您可以通过适配器模式将新组件的一些接口转换成为你所期望的接口(也就是和新组件符合),
这样的话,您就无需要改变原来的代码而轻松实现从旧组件更新到新组件了。
然后呢,还有一种比较好的应用就是,比如现在很多在 Windows 上的东西都不能在 Linux 上运行,
比如一个 WINE 的工具,它呢,就允许用户在 Linux 环境下运行 Windows 程序,这也是一种适配器。
适配器模式的应用
适配器模式是Java I/O库中第二个最为重要的设计模式。
InputStream原始流处理器中的适配器模式
InputStream类型的原始流处理器是适配器模式的应用。
ByteArrayInputStream是一个适配器类
ByteArrayInputStream继承了InputStream的接口,而封装了一个byte数组。换言之,它将一个byte数组的接口适配成InputStream流处理器的接口。
我们知道Java语言支持四种类型:Java接口,Java类,Java数组,原始类型(即int,float等)。前三种是引用类型,类和数组的实例是对象,原始类型的值不是对象。
也即,Java语言的数组是像所有的其他对象一样的对象,而不管数组中所存储的元素类型是什么。
这样一来的话,ByteArrayInputStream就符合适配器模式的描述,是一个对象形式的适配器类。
FileInputStream是一个适配器类
在FileInputStream继承了InputStrem类型,同时持有一个对FileDiscriptor的引用。这是将一个FileDiscriptor对象适配成InputStrem类型的对象形式的适配器模式
其实呢,可以这样来理解适配器模式的,适配器模式就是将一些对象包装起来,
然后让它们的接口看起来是别的接口。
/*
* %W% %E%
*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
/**
* A <code>FileInputStream</code> obtains input bytes
* from a file in a file system. What files
* are available depends on the host environment.
*
* <p><code>FileInputStream</code> is meant for reading streams of raw bytes
* such as image data. For reading streams of characters, consider using
* <code>FileReader</code>.
*
* @author Arthur van Hoff
* @version %I%, %G%
* @see java.io.File
* @see java.io.FileDescriptor
* @see java.io.FileOutputStream
* @since JDK1.0
*/
public
class FileInputStream extends InputStream
{
/* File Descriptor - handle to the open file */
private FileDescriptor fd;
private FileChannel channel = null;
private Object closeLock = new Object();
private volatile boolean closed = false;
private static final ThreadLocal<Boolean> runningFinalize =
new ThreadLocal<Boolean>();
private static boolean isRunningFinalize() {
Boolean val;
if ((val = runningFinalize.get()) != null)
return val.booleanValue();
return false;
}
/**
* Creates a <code>FileInputStream</code> by
* opening a connection to an actual file,
* the file named by the path name <code>name</code>
* in the file system. A new <code>FileDescriptor</code>
* object is created to represent this file
* connection.
* <p>
* First, if there is a security
* manager, its <code>checkRead</code> method
* is called with the <code>name</code> argument
* as its argument.
* <p>
* If the named file does not exist, is a directory rather than a regular
* file, or for some other reason cannot be opened for reading then a
* <code>FileNotFoundException</code> is thrown.
*
* @param name the system-dependent file name.
* @exception FileNotFoundException if the file does not exist,
* is a directory rather than a regular file,
* or for some other reason cannot be opened for
* reading.
* @exception SecurityException if a security manager exists and its
* <code>checkRead</code> method denies read access
* to the file.
* @see java.lang.SecurityManager#checkRead(java.lang.String)
*/
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
/**
* Creates a <code>FileInputStream</code> by
* opening a connection to an actual file,
* the file named by the <code>File</code>
* object <code>file</code> in the file system.
* A new <code>FileDescriptor</code> object
* is created to represent this file connection.
* <p>
* First, if there is a security manager,
* its <code>checkRead</code> method is called
* with the path represented by the <code>file</code>
* argument as its argument.
* <p>
* If the named file does not exist, is a directory rather than a regular
* file, or for some other reason cannot be opened for reading then a
* <code>FileNotFoundException</code> is thrown.
*
* @param file the file to be opened for reading.
* @exception FileNotFoundException if the file does not exist,
* is a directory rather than a regular file,
* or for some other reason cannot be opened for
* reading.
* @exception SecurityException if a security manager exists and its
* <code>checkRead</code> method denies read access to the file.
* @see java.io.File#getPath()
* @see java.lang.SecurityManager#checkRead(java.lang.String)
*/
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
fd = new FileDescriptor();
fd.incrementAndGetUseCount();
open(name);
}
/**
* Creates a <code>FileInputStream</code> by using the file descriptor
* <code>fdObj</code>, which represents an existing connection to an
* actual file in the file system.
* <p>
* If there is a security manager, its <code>checkRead</code> method is
* called with the file descriptor <code>fdObj</code> as its argument to
* see if it's ok to read the file descriptor. If read access is denied
* to the file descriptor a <code>SecurityException</code> is thrown.
* <p>
* If <code>fdObj</code> is null then a <code>NullPointerException</code>
* is thrown.
*
* @param fdObj the file descriptor to be opened for reading.
* @throws SecurityException if a security manager exists and its
* <code>checkRead</code> method denies read access to the
* file descriptor.
* @see SecurityManager#checkRead(java.io.FileDescriptor)
*/
public FileInputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkRead(fdObj);
}
fd = fdObj;
/*
* FileDescriptor is being shared by streams.
* Ensure that it's GC'ed only when all the streams/channels are done
* using it.
*/
fd.incrementAndGetUseCount();
}
/**
* Opens the specified file for reading.
* @param name the name of the file
*/
private native void open(String name) throws FileNotFoundException;
/**
* Reads a byte of data from this input stream. This method blocks
* if no input is yet available.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* file is reached.
* @exception IOException if an I/O error occurs.
*/
public native int read() throws IOException;
/**
* Reads a subarray as a sequence of bytes.
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes that are written
* @exception IOException If an I/O error has occurred.
*/
private native int readBytes(byte b[], int off, int len) throws IOException;
/**
* Reads up to <code>b.length</code> bytes of data from this input
* stream into an array of bytes. This method blocks until some input
* is available.
*
* @param b the buffer into which the data is read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the file has been reached.
* @exception IOException if an I/O error occurs.
*/
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
/**
* Reads up to <code>len</code> bytes of data from this input stream
* into an array of bytes. If <code>len</code> is not zero, the method
* blocks until some input is available; otherwise, no
* bytes are read and <code>0</code> is returned.
*
* @param b the buffer into which the data is read.
* @param off the start offset in the destination array <code>b</code>
* @param len the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the file has been reached.
* @exception NullPointerException If <code>b</code> is <code>null</code>.
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
* <code>len</code> is negative, or <code>len</code> is greater than
* <code>b.length - off</code>
* @exception IOException if an I/O error occurs.
*/
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
/**
* Skips over and discards <code>n</code> bytes of data from the
* input stream.
*
* <p>The <code>skip</code> method may, for a variety of
* reasons, end up skipping over some smaller number of bytes,
* possibly <code>0</code>. If <code>n</code> is negative, an
* <code>IOException</code> is thrown, even though the <code>skip</code>
* method of the {@link InputStream} superclass does nothing in this case.
* The actual number of bytes skipped is returned.
*
* <p>This method may skip more bytes than are remaining in the backing
* file. This produces no exception and the number of bytes skipped
* may include some number of bytes that were beyond the EOF of the
* backing file. Attempting to read from the stream after skipping past
* the end will result in -1 indicating the end of the file.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @exception IOException if n is negative, if the stream does not
* support seek, or if an I/O error occurs.
*/
public native long skip(long n) throws IOException;
/**
* Returns an estimate of the number of remaining bytes that can be read (or
* skipped over) from this input stream without blocking by the next
* invocation of a method for this input stream. The next invocation might be
* the same thread or another thread. A single read or skip of this
* many bytes will not block, but may read or skip fewer bytes.
*
* <p> In some cases, a non-blocking read (or skip) may appear to be
* blocked when it is merely slow, for example when reading large
* files over slow networks.
*
* @return an estimate of the number of remaining bytes that can be read
* (or skipped over) from this input stream without blocking.
* @exception IOException if this file input stream has been closed by calling
* {@code close} or an I/O error occurs.
*/
public native int available() throws IOException;
/**
* Closes this file input stream and releases any system resources
* associated with the stream.
*
* <p> If this stream has an associated channel then the channel is closed
* as well.
*
* @exception IOException if an I/O error occurs.
*
* @revised 1.4
* @spec JSR-51
*/
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
/*
* Decrement the FD use count associated with the channel
* The use count is incremented whenever a new channel
* is obtained from this stream.
*/
fd.decrementAndGetUseCount();
channel.close();
}
/*
* Decrement the FD use count associated with this stream
*/
int useCount = fd.decrementAndGetUseCount();
/*
* If FileDescriptor is still in use by another stream, the finalizer
* will not close it.
*/
if ((useCount <= 0) || !isRunningFinalize()) {
close0();
}
}
/**
* Returns the <code>FileDescriptor</code>
* object that represents the connection to
* the actual file in the file system being
* used by this <code>FileInputStream</code>.
*
* @return the file descriptor object associated with this stream.
* @exception IOException if an I/O error occurs.
* @see java.io.FileDescriptor
*/
public final FileDescriptor getFD() throws IOException {
if (fd != null) return fd;
throw new IOException();
}
/**
* Returns the unique {@link java.nio.channels.FileChannel FileChannel}
* object associated with this file input stream.
*
* <p> The initial {@link java.nio.channels.FileChannel#position()
* </code>position<code>} of the returned channel will be equal to the
* number of bytes read from the file so far. Reading bytes from this
* stream will increment the channel's position. Changing the channel's
* position, either explicitly or by reading, will change this stream's
* file position.
*
* @return the file channel associated with this file input stream
*
* @since 1.4
* @spec JSR-51
*/
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, true, false, this);
/*
* Increment fd's use count. Invoking the channel's close()
* method will result in decrementing the use count set for
* the channel.
*/
fd.incrementAndGetUseCount();
}
return channel;
}
}
private static native void initIDs();
private native void close0() throws IOException;
static {
initIDs();
}
/**
* Ensures that the <code>close</code> method of this file input stream is
* called when there are no more references to it.
*
* @exception IOException if an I/O error occurs.
* @see java.io.FileInputStream#close()
*/
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
/*
* Finalizer should not release the FileDescriptor if another
* stream is still using it. If the user directly invokes
* close() then the FileDescriptor is also released.
*/
runningFinalize.set(Boolean.TRUE);
try {
close();
} finally {
runningFinalize.set(Boolean.FALSE);
}
}
}
}
StringBufferInputString继承了InputString类型,同时持有一个对String对象的引用,这是一个将String对象适配成InputString类型的对象形式的适配器模式,如下图所示:
OutputStream原始流处理器中的适配器模式
同样地,在OutputStream类型中,所有的原始流处理器都是适配器类。
ByteArrayOutputStream继承了OutputStream类型,同时持有一个对byte数组的引用。它一个byte数组的接口适配成OutputString类型的接口,因此也是一个对象形式的适配器模式的应用。
FileOutputStream是一个适配器类
FileOutputStream继承了OutputStream类型,同时持有一个对FileDiscriptor对象的引用。这是一个将FileDiscriptor接口适配成OutputStream接口形式的对象形适配器模式。
Reader原始流处理器中的适配器模式
Reader类型的原始流处理器都是适配器模式的应用。
StringReader是一个适配器类
StringReader类继承了Reader类型,持有一个对String对象的引用。它将String的接口适 配成Reader类型的接口,如下图所示:
从byte流到char流的适配
在Java I/O库中,使用比较频繁的要数InputStreamReader,OutputStreamWriter这两种类了,
InputStreamReader是从byte输入流到char输入流的一个适配器。下图所示就是 InputStreamReader与Reader和InputStream等类的结构图:
当把InputStreamReader与任何InputStream的具体子类链接的时候,可以从InputStream的输出读入byte类型的数据,将之转换成为char类型的数据