依赖关系
package java.io;
import java.net.URI;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.ArrayList;
import java.security.AccessController;
import java.security.SecureRandom;
import java.nio.file.Path;
import java.nio.file.FileSystems;
import sun.security.action.GetPropertyAction;
通过这里我们先搞清楚谁是爸爸谁是儿子,看不懂的地方可以从这再去找。
类的定义
public class File implements Serializable, Comparable<File> {
...
}
重要的成员变量
private static final FileSystem fs = DefaultFileSystem.getFileSystem();
private final String path;
fs为一个FileSystem类型的静态最终变量,不可改变。初始化为DefaultFileSystem.getFileSystem();。如果你去源码中去寻找,你会发现这个:
class DefaultFileSystem {
/**
* Return the FileSystem object for Windows platform.
*/
public static FileSystem getFileSystem() {
return new WinNTFileSystem();
}
}
一看就是安装了Windows版本的JDK,感兴趣的话可以去看看Linux版本的JDK,一定有所不同。
构造函数
private File(String pathname, int prefixLength) {
...
}
private File(String child, File parent) {
...
}
public File(String parent, String child) {
...
}
public File(File parent, String child) {
...
}
public File(URI uri) {
...
}
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
共有2个私有,4个公有的构造函数。也许有一天,面试官提出问题:
- 问:构造函数可以是私有的吗?
- 答:可以,我读过Java.io.File.java,源码里面就有两个私有的构造方法。
深藏功与名。
当然知其然知其所以然,私有的构造方法多用于单例模式。或者说这个类干脆就不想让别人实例化,此时就可以使用唯一私有的构造函数完成目标。
回到构造函数,我们看最简单的参数为String pathname的构造函数。首先检测是否为空,若为空抛出异常,若不为空则对path与prefixLength(前缀长度)赋值。两个函数均来自java.nio.file.FileSystems,或许我们应该先从它开始学习的。
函数分析
private static enum PathStatus { INVALID, CHECKED };
private transient PathStatus status = null;
final boolean isInvalid() {
if (status == null) {
status = (this.path.indexOf('\u0000') < 0) ? PathStatus.CHECKED
: PathStatus.INVALID;
}
return status == PathStatus.INVALID;
}
很简单的判断路径是否有效,判断条件为this.path.indexOf('\u0000') < 0
,我是肯定不会这么写,我会写的特别的丑。而使用enum枚举去判断路径状态,要是我写的话可能就是个Boolean吧。
/**
* The length of this abstract pathname's prefix, or zero if it has no
* prefix.
*/
private final transient int prefixLength;
/**
* Returns the length of this abstract pathname's prefix.
* For use by FileSystem classes.
*/
int getPrefixLength() {
return prefixLength;
}
从这里我们看得出来File类引入了一个概念即——abstract path。注意这并不是相对路径,而是抽象路径。Java提出这个概念的理由是Unix与Windows系统下,路径的分割符号不同。前者为’/’,后者为’\’,为了统一路径表达方式才引入了抽象路径。
访问控制
public boolean canRead() {
...
}
public boolean canWrite() {
...
}
public boolean exists() {
...
}
public boolean isDirectory() {
...
}
public boolean isFile() {
...
}
public boolean isHidden() {
...
}
public long lastModified() {
...
}
public long length() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return 0L;
}
return fs.getLength(this);
}
这几个函数写法都很类似,都先是调用SecurityManager.checkRead(path)函数进行一次检查。之后调用上面读过的isValid()函数判断文件是否合法。最后按照不同函数功能调用FileSystem的函数去实现。再往后看,你会发现整个这个类里面90%的函数都是这个格式,最终还是调用对应操作系统FileSystem中的相关函数完成相应功能的。
一些重写的方法
public int compareTo(File pathname) {
return fs.compare(this, pathname);
}
public boolean equals(Object obj) {
if ((obj != null) && (obj instanceof File)) {
return compareTo((File)obj) == 0;
}
return false;
}
public int hashCode() {
return fs.hashCode(this);
}
public String toString() {
return getPath();
}
简单到一眼看过去就明白的地步,toString()函数还非要调用一下getPath(),这么写不也挺好吗:
public String toString() {
return path;
}
源码当然也是可以随意修改的,只要你觉得理解没问题。