Java基本数据类型一共有八种,引用一下菜鸟教程上的介绍:
byte:
byte 数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是 -128(-2^7);
最大值是 127(2^7-1);
默认值是0;
byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int类型的四分之一;
例子:byte a = 100,byte b = -50。
short:
short 数据类型是 16 位、有符号的以二进制补码表示的整数 最小值是 -32768(-2^15);
最大值是 32767(2^15 - 1);
Short 数据类型也可以像 byte 那样节省空间,一个short变量是int型变量所占空间的二分之一;
默认值是 0;
例子:short s = 1000,short r = -20000。
int:
int 数据类型是32位、有符号的以二进制补码表示的整数;
最小值是 -2,147,483,648(-2^31);
最大值是 2,147,483,647(2^31 - 1);
一般地整型变量默认为 int 类型;
默认值是 0 ;
例子:int a = 100000, int b = -200000。
long:
long 数据类型是 64 位、有符号的以二进制补码表示的整数;
最小值是 -9,223,372,036,854,775,808(-2^63);
最大值是 9,223,372,036,854,775,807(2^63 -1);
这种类型主要使用在需要比较大整数的系统上;
默认值是 0L;
例子: long a = 100000L,Long b = -200000L。
“L”理论上不分大小写,但是若写成”l”容易与数字”1”混淆,不容易分辩。所以最好大写。
float:
float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
float 在储存大型浮点数组的时候可节省内存空间;
默认值是0.0f;
浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f。
double:
double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
浮点数的默认类型为double类型;
double类型同样不能表示精确的值,如货币;
默认值是 0.0d;
例子:double d1 = 123.4d。boolean:
boolean数据类型表示一位的信息;
只有两个取值: true 和 false;
这种类型只作为一种标志来记录 true/false 情况;
默认值是 false;
例子:boolean one = true。
char:
char类型是一个单一的 16 位 Unicode 字符;
最小值是 \u0000(即为0);
最大值是\uffff(即为65,535);
char 数据类型可以储存任何字符;
例子:char letter = ‘A’;。
抱着作死的心理,想了解一下它们的工作原理。
然而找了许久定义八种数据类型的类,除了他们的包装类,什么都没找到。
最后在百度知道里找到了一条回答:【原问题地址】
基本类型是内置到编译器实现中的,属于“关键字”的范畴,不用头文件支持。而像编译器拓展类型,如__int32,__int64和具体的编译器相关,有些是放在头文件,有些也是内置到编译器中(比如版本较新的VS),也不用手动#include。而BOOL这种类型,就要手动引入头文件。总之不同编译器情况不同。
回答中提到了基本数据类型是关键字(好像的确曾经在关键字表看到过,却没想这么深),由编译器来搞定的,那我先姑且一试。
Java的编译器是javac,那我现在就去找一下javac的源码看一下。
在JDK的安装目录下有一个src.zip,这个就是JDK的源码包,javac的源码在com.sun.tools.javac中。
解压src.zip,进入com.sun,并没有tools这个文件夹。
原来,jdk有两个版本,一种是Oracle jdk,就是我们一般从Oracle官网下载的,它里面并没有完整版的源码包,而完整版的源码包由另一个版本,也就是Open jdk携带,除了一小部分机密的源代码之外,其余都有提供。
Open jdk的官网:http://openjdk.java.net/
安装方式好像只有Linux的,这类我比较懒得折腾,就想到了用OpenSusu去获取,令人兴奋的试OpenSuse自带的jdk就是Open jdk,而且通过OpenSuse的安装与管理程序YaST2也可以很轻松地获取最新版的Open jdk。
jdk1.8的源码压缩包一共49.01MB。
源码阅读
文件夹下有一个Main.java,这应该就是javac的入口函数,这个类下文称为Main类。
Main.java里的函数只有三个,一个main,一个compile,以及一个compile函数的重载形式。
package com.sun.tools.javac;
import java.io.PrintWriter;
/**
* The programmatic interface for the Java Programming Language
* compiler, javac.
*/
@jdk.Exported
public class Main {
/** Main entry point for the launcher.
* Note: This method calls System.exit.
* @param args command line arguments
*/
public static void main(String[] args) throws Exception {
System.exit(compile(args));
}
/** Programmatic interface to the Java Programming Language
* compiler, javac.
*
* @param args The command line arguments that would normally be
* passed to the javac program as described in the man page.
* @return an integer equivalent to the exit value from invoking
* javac, see the man page for details.
*/
public static int compile(String[] args) {
com.sun.tools.javac.main.Main compiler =
new com.sun.tools.javac.main.Main("javac");
return compiler.compile(args).exitCode;
}
/** Programmatic interface to the Java Programming Language
* compiler, javac.
*
* @param args The command line arguments that would normally be
* passed to the javac program as described in the man page.
* @param out PrintWriter to which the compiler's diagnostic
* output is directed.
* @return an integer equivalent to the exit value from invoking
* javac, see the man page for details.
*/
public static int compile(String[] args, PrintWriter out) {
com.sun.tools.javac.main.Main compiler =
new com.sun.tools.javac.main.Main("javac", out);
return compiler.compile(args).exitCode;
}
}
流程很简单,main函数调用compiler函数,运行结束之后退出。
参数String[] args就是用户输入的命令行参数
还是看下compiler函数,首先new了一个com.sun.tools.javac.main.Main(“javac”),然后我有一个大胆的想法,这个参数”javac”应该就是正式调用javac编译器。
看一下com.sun.tools.javac.main.Main类(这个类下文简单称为main.Main类)的构造函数:
/**
* Construct a compiler instance.
*/
public Main(String name) {
this(name, new PrintWriter(System.err, true));
}
/**
* Construct a compiler instance.
*/
public Main(String name, PrintWriter out) {
this.ownName = name;
this.out = out;
}
这里也看到了之前compiler重载函数的差别只是引入一个外部的PrintWriter,其余并无两样。
回过头继续看Main类,new好main.Main类之后,便调用了它的compile(String[] args)函数
/** Programmatic interface for main function.
* @param args The command line parameters.
*/
public Result compile(String[] args) {
Context context = new Context();
JavacFileManager.preRegister(context); // can't create it until Log has been set up
Result result = compile(args, context);
if (fileManager instanceof JavacFileManager) {
// A fresh context was created above, so jfm must be a JavacFileManager
((JavacFileManager)fileManager).close();
}
return result;
}
Context类,这名字直白的翻译过来是环境、上下文的意思,位于com.sun.tools.javac.util包下,下文简称util.Context类。
看了util.Context的介绍:
/**
* Support for an abstract context, modelled loosely after ThreadLocal
* but using a user-provided context instead of the current thread.
*
* <p>Within the compiler, a single Context is used for each
* invocation of the compiler. The context is then used to ensure a
* single copy of each compiler phase exists per compiler invocation.
*
* <p>The context can be used to assist in extending the compiler by
* extending its components. To do that, the extended component must
* be registered before the base component. We break initialization
* cycles by (1) registering a factory for the component rather than
* the component itself, and (2) a convention for a pattern of usage
* in which each base component registers itself by calling an
* instance method that is overridden in extended components. A base
* phase supporting extension would look something like this:
*/
感觉这个类的主要功能就是加载编译器所需要的上下文环境,从哪儿得知呢?由用户提供。
关于Context的理解,这篇提问比较说的明白:https://www.zhihu.com/question/26387327
然后就是用JavacFileManager把Context里涉及到的文件注册一下,应该就是把这些文件的信息全丢进JavacFileManager,用JavacFileManager来管理。
接着Result是一个枚举类型,包含在main.Main类中,看了下应该是用来表明编译是否通过,或者发生了哪类错误,错误不会很详细,只是说一下是大致。
/** Result codes.
*/
public enum Result {
OK(0), // Compilation completed with no errors.
ERROR(1), // Completed but reported errors.
CMDERR(2), // Bad command-line arguments
SYSERR(3), // System error or resource exhaustion.
ABNORMAL(4); // Compiler terminated abnormally
Result(int exitCode) {
this.exitCode = exitCode;
}
public boolean isOK() {
return (exitCode == 0);
}
public final int exitCode;
}
compile函数有额外三个重载形式,一个接着一个调用:
public Result compile(String[] args, Context context) {
return compile(args, context, List.<JavaFileObject>nil(), null);
}
/** Programmatic interface for main function.
* @param args The command line parameters.
*/
public Result compile(String[] args,
Context context,
List<JavaFileObject> fileObjects,
Iterable<? extends Processor> processors)
{
return compile(args, null, context, fileObjects, processors);
}
public Result compile(String[] args,
String[] classNames,
Context context,
List<JavaFileObject> fileObjects,
Iterable<? extends Processor> processors)
{
context.put(Log.outKey, out);
log = Log.instance(context);
if (options == null)
options = Options.instance(context); // creates a new one
filenames = new LinkedHashSet<File>();
classnames = new ListBuffer<String>();
JavaCompiler comp = null;
/*
* TODO: Logic below about what is an acceptable command line
* should be updated to take annotation processing semantics
* into account.
*/
try {
if (args.length == 0
&& (classNames == null || classNames.length == 0)
&& fileObjects.isEmpty()) {
Option.HELP.process(optionHelper, "-help");
return Result.CMDERR;
}
Collection<File> files;
try {
files = processArgs(CommandLine.parse(args), classNames);
if (files == null) {
// null signals an error in options, abort
return Result.CMDERR;
} else if (files.isEmpty() && fileObjects.isEmpty() && classnames.isEmpty()) {
// it is allowed to compile nothing if just asking for help or version info
if (options.isSet(HELP)
|| options.isSet(X)
|| options.isSet(VERSION)
|| options.isSet(FULLVERSION))
return Result.OK;
if (JavaCompiler.explicitAnnotationProcessingRequested(options)) {
error("err.no.source.files.classes");
} else {
error("err.no.source.files");
}
return Result.CMDERR;
}
} catch (java.io.FileNotFoundException e) {
warning("err.file.not.found", e.getMessage());
return Result.SYSERR;
}
boolean forceStdOut = options.isSet("stdout");
if (forceStdOut) {
log.flush();
log.setWriters(new PrintWriter(System.out, true));
}
// allow System property in following line as a Mustang legacy
boolean batchMode = (options.isUnset("nonBatchMode")
&& System.getProperty("nonBatchMode") == null);
if (batchMode)
CacheFSInfo.preRegister(context);
// FIXME: this code will not be invoked if using JavacTask.parse/analyze/generate
// invoke any available plugins
String plugins = options.get(PLUGIN);
if (plugins != null) {
JavacProcessingEnvironment pEnv = JavacProcessingEnvironment.instance(context);
ClassLoader cl = pEnv.getProcessorClassLoader();
ServiceLoader<Plugin> sl = ServiceLoader.load(Plugin.class, cl);
Set<List<String>> pluginsToCall = new LinkedHashSet<List<String>>();
for (String plugin: plugins.split("\\x00")) {
pluginsToCall.add(List.from(plugin.split("\\s+")));
}
JavacTask task = null;
for (Plugin plugin : sl) {
for (List<String> p : pluginsToCall) {
if (plugin.getName().equals(p.head)) {
pluginsToCall.remove(p);
try {
if (task == null)
task = JavacTask.instance(pEnv);
plugin.init(task, p.tail.toArray(new String[p.tail.size()]));
} catch (Throwable ex) {
if (apiMode)
throw new RuntimeException(ex);
pluginMessage(ex);
return Result.SYSERR;
}
}
}
}
for (List<String> p: pluginsToCall) {
log.printLines(PrefixKind.JAVAC, "msg.plugin.not.found", p.head);
}
}
comp = JavaCompiler.instance(context);
// FIXME: this code will not be invoked if using JavacTask.parse/analyze/generate
String xdoclint = options.get(XDOCLINT);
String xdoclintCustom = options.get(XDOCLINT_CUSTOM);
if (xdoclint != null || xdoclintCustom != null) {
Set<String> doclintOpts = new LinkedHashSet<String>();
if (xdoclint != null)
doclintOpts.add(DocLint.XMSGS_OPTION);
if (xdoclintCustom != null) {
for (String s: xdoclintCustom.split("\\s+")) {
if (s.isEmpty())
continue;
doclintOpts.add(s.replace(XDOCLINT_CUSTOM.text, DocLint.XMSGS_CUSTOM_PREFIX));
}
}
if (!(doclintOpts.size() == 1
&& doclintOpts.iterator().next().equals(DocLint.XMSGS_CUSTOM_PREFIX + "none"))) {
JavacTask t = BasicJavacTask.instance(context);
// standard doclet normally generates H1, H2
doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2");
new DocLint().init(t, doclintOpts.toArray(new String[doclintOpts.size()]));
comp.keepComments = true;
}
}
fileManager = context.get(JavaFileManager.class);
if (!files.isEmpty()) {
// add filenames to fileObjects
comp = JavaCompiler.instance(context);
List<JavaFileObject> otherFiles = List.nil();
JavacFileManager dfm = (JavacFileManager)fileManager;
for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files))
otherFiles = otherFiles.prepend(fo);
for (JavaFileObject fo : otherFiles)
fileObjects = fileObjects.prepend(fo);
}
comp.compile(fileObjects,
classnames.toList(),
processors);
if (log.expectDiagKeys != null) {
if (log.expectDiagKeys.isEmpty()) {
log.printRawLines("all expected diagnostics found");
return Result.OK;
} else {
log.printRawLines("expected diagnostic keys not found: " + log.expectDiagKeys);
return Result.ERROR;
}
}
if (comp.errorCount() != 0)
return Result.ERROR;
} catch (IOException ex) {
ioMessage(ex);
return Result.SYSERR;
} catch (OutOfMemoryError ex) {
resourceMessage(ex);
return Result.SYSERR;
} catch (StackOverflowError ex) {
resourceMessage(ex);
return Result.SYSERR;
} catch (FatalError ex) {
feMessage(ex);
return Result.SYSERR;
} catch (AnnotationProcessingError ex) {
if (apiMode)
throw new RuntimeException(ex.getCause());
apMessage(ex);
return Result.SYSERR;
} catch (ClientCodeException ex) {
// as specified by javax.tools.JavaCompiler#getTask
// and javax.tools.JavaCompiler.CompilationTask#call
throw new RuntimeException(ex.getCause());
} catch (PropagatedException ex) {
throw ex.getCause();
} catch (Throwable ex) {
// Nasty. If we've already reported an error, compensate
// for buggy compiler error recovery by swallowing thrown
// exceptions.
if (comp == null || comp.errorCount() == 0 ||
options == null || options.isSet("dev"))
bugMessage(ex);
return Result.ABNORMAL;
} finally {
if (comp != null) {
try {
comp.close();
} catch (ClientCodeException ex) {
throw new RuntimeException(ex.getCause());
}
}
filenames = null;
options = null;
}
return Result.OK;
}
最主要的其实就是那个最长的重载形式,但是发现里面好多从没见过的类,瞬间感觉自己就是个麻瓜。
不知道的太多,效率太低,我决定还是换个思路,待日后再重读javac源码,目前直奔主题为好,我就想知道个基本类型而已。。。
换个思路
既然int这些并没有通过类来保存,那么我猜测是编译的时候通过匹配的方式来了解这是啥基本类型,然后进行分配,那么肯定在某个类里会出现字符串类型的”int”,”short”,”byte”等,而这些东西是Java的开发公司sun公司完成的,又方便javac调用的,那么还是优先在com.sun包内查找。
在com.sun.codemodel.internal包中,有一个JJavaName.java类,这里面包含了java的全部53个关键字:
/** All reserved keywords of Java. */
private static HashSet<String> reservedKeywords = new HashSet<String>();
static {
// see http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html
String[] words = new String[]{
"abstract",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while",
// technically these are not reserved words but they cannot be used as identifiers.
"true",
"false",
"null",
// and I believe assert is also a new keyword
"assert",
// and 5.0 keywords
"enum"
};
for (String w : words)
reservedKeywords.add(w);
}
除此之外com.sun.codemodel.internal包里的其他类都是规定了许多语法的格式,包括类的种类:
package com.sun.codemodel.internal;
/**
* This helps enable whether the JDefinedClass is a Class or Interface or
* AnnotationTypeDeclaration or Enum
*
* @author
* Bhakti Mehta (bhakti.mehta@sun.com)
*/
public final class ClassType {
/**
* The keyword used to declare this type.
*/
final String declarationToken;
private ClassType(String token) {
this.declarationToken = token;
}
public static final ClassType CLASS = new ClassType("class");
public static final ClassType INTERFACE = new ClassType("interface");
public static final ClassType ANNOTATION_TYPE_DECL = new ClassType("@interface");
public static final ClassType ENUM = new ClassType("enum");
}
还有比如很熟悉的for循环、while循环,也有以前从没用过的语法的格式。
找到了保存关键字及对其操作的类,我决定看看javac的源码中有哪个类导入了JJavaName类。
结果是在javac的源码中并没有找到有任何一个类调用了JJavaName类。
但是在com.sun.tools.javac中,确实找到了很多蛛丝马迹,表明在编译期的时候,基础数据类型关键字被转换成了相应的指令码。
在com.sun.tools.javac.jvm包下的ByteCoded类中包含了字节码的指令码,以及用作指令修饰符的类型码。
抱着好奇心,写了一段测试程序,想直观地看一下转换结果:
public class BaseTest {
public static void main(String argc[]) {
int c = 3;
short b = (short) 4;
byte a = (byte) 5;
long d = (long) 2;
float e = 10.0f;
double f = 20.0d;
boolean g = false;
boolean h = true;
boolean i = false;
char j = 'A';
Integer k = new Integer(88);
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println(e);
System.out.println(f);
System.out.println(g);
System.out.println(h);
}
}
D:\>javap -c BaseTest.class
Compiled from "BaseTest.java"
public class BaseTest {
public BaseTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_3
1: istore_1
2: iconst_4
3: istore_2
4: iconst_5
5: istore_3
6: ldc2_w #2 // long 2l
9: lstore 4
11: ldc #4 // float 10.0f
13: fstore 6
15: ldc2_w #5 // double 20.0d
18: dstore 7
20: iconst_0
21: istore 9
23: iconst_1
24: istore 10
26: iconst_0
27: istore 11
29: bipush 65
31: istore 12
33: new #7 // class java/lang/Integer
36: dup
37: bipush 88
39: invokespecial #8 // Method java/lang/Integer."<init>":(I)V
42: astore 13
44: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
47: iload_3
48: invokevirtual #10 // Method java/io/PrintStream.println:(I)V
51: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
54: iload_2
55: invokevirtual #10 // Method java/io/PrintStream.println:(I)V
58: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
61: iload_1
62: invokevirtual #10 // Method java/io/PrintStream.println:(I)V
65: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
68: lload 4
70: invokevirtual #11 // Method java/io/PrintStream.println:(J)V
73: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
76: fload 6
78: invokevirtual #12 // Method java/io/PrintStream.println:(F)V
81: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
84: dload 7
86: invokevirtual #13 // Method java/io/PrintStream.println:(D)V
89: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
92: iload 9
94: invokevirtual #14 // Method java/io/PrintStream.println:(Z)V
97: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
100: iload 10
102: invokevirtual #14 // Method java/io/PrintStream.println:(Z)V
105: return
}
印证了在编译期的时候,基础数据类型关键字被转换成了相应的指令码。
在这里的另一个发现就是,编译成字节码文件之后,byte、short类型都归为int类型了。