1 九种基本数据类型的大小,以及他们的封装类。
答:参考Java官方文档数据类型。java有8种基本类型。其分别为:
基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
char | 16-bit | Unicode 0 | Unicode 216−1 | Character |
byte | 8 bits | -128 | +127 | Byte |
short | 16 bits | - 215 | + 215−1 | Short |
int | 32 bits | - 231 | + 231−1 | Integer |
long | 64 bits | - 263 | + 263−1 | Long |
float | 32 bits | IEEE754 | IEEE754 | Float |
double | 64 bits | IEEE754 | IEEE754 | Double |
有几点值得注意:
(1) boolean是用来表示状态的,仅有true和false两个值。其”size”并未被明确指定。
但是boolean在虚拟机里面,实际上都是使用int类型进行操作的。
引用一段R大的回答:
原因:
C++里有Integral Promotion(整数提升)的规定,算术运算符会把操作数至少提升到int来运算。Java与JVM的规定其实就是继承了C++的设计。这么看下来是非常直观的。
然后,另一种导致这个设计的因素是JVM的指令集设计偏向于能高效实现解释器。当时32位环境开始成为主流,JVM也是设计为在32位环境上能很容易实现。因而栈帧里的每个“slot”设计为至少32位。
byte short等数据的存在意义
明确取值范围的意图。这没啥好说的,例如我就要在某个地方取0-127的范围的话,那用byte就更容易表达这个意图(虽然不像Ada能指定Integer的子类型那么精确…)
作为对象字段或者数组元素存储的时候,特别是数组的情况。在byte[]、short[]里,JVM通常会让1个byte元素真的只占用1字节、1个short元素真的只占用2字节。Sun最初的JVM实现得很糟糕,在对象字段里有byte或者short的话也还是占用1个32为的slot…现在的HotSpot VM会做字段重排序来尽可能节省空间,用byte/short作为字段还是可以有节省空间的意义的。(这一点与《深入理解java虚拟机》的描述不太相符)
(2)不要用float,double进行精确计算。
如有以下代码:
package test;
public class FloatTest {
public static void main(String args[]){
float moneyLeft = 1;
int totalNumber = 0;
for(double price = 0.1; moneyLeft > price; price += 0.1){
moneyLeft -= price;
totalNumber++;
}
System.out.println(moneyLeft);
System.out.println("totalNumber:" + totalNumber);
}
}
运行结果为:
0.39999998
totalNumber:3
此时可以使用BigDecimal类来进行操作:
package test;
import java.math.BigDecimal;
public class BigDecimalTest {
public static void main(String args[]){
BigDecimal moneyLeft = new BigDecimal(1.0);
int totalNumber = 0;;
for(BigDecimal price = new BigDecimal("0.1");
moneyLeft.compareTo(price) >= 0; price = price.add(new BigDecimal("0.1"))){
moneyLeft = moneyLeft.subtract(price);
totalNumber++;
}
System.out.println(moneyLeft);
System.out.println(totalNumber);
}
}
结果为:
0.0
4
因此可见BigDecimal可以用于精确的浮点型计算。缺点是:耗时,不好操作。
(3) void算不算基本类型?
《Thinking in java》将void也视为基本类型了。看源码:
package java.lang;
/**
* The {@code Void} class is an uninstantiable placeholder class to hold a
* reference to the {@code Class} object representing the Java keyword
* void.
*
* @author unascribed
* @since JDK1.1
*/
public final
class Void {
/**
* The {@code Class} object representing the pseudo-type corresponding to
* the keyword {@code void}.
*/
public static final Class<Void> TYPE = Class.getPrimitiveClass("void");
/*
* The Void class cannot be instantiated.
*/
private Void() {}
}
2.Switch能否用String做参数?
可以,这个功能是在Java 7.0之后加入的。
其底层实现为先比较hashcode(),再调用.equals()方法。
例如有源码如下:
package test;
public class SwitchTest {
public static void main(String args[]){
if(args.length == 0){
System.out.println("Nothing Input");
}else{
switch(args[0]){
case "a":{
System.out.println("Input is a");
break;
}
case "b":{
System.out.println("Input is b");
break;
}
default:
System.out.println("Not compared");
break;
}
}
}
}
反编译出来的汇编代码为:
// Compiled from SwitchTest.java (version 1.8 : 52.0, super bit)
public class test.SwitchTest {
// Method descriptor #6 ()V
// Stack: 1, Locals: 1
public SwitchTest();
0 aload_0 [this]
1 invokespecial java.lang.Object() [8]
4 return
Line numbers:
[pc: 0, line: 3]
Local variable table:
[pc: 0, pc: 5] local: this index: 0 type: test.SwitchTest
// Method descriptor #15 ([Ljava/lang/String;)V
// Stack: 2, Locals: 2
public static void main(java.lang.String[] args);
0 aload_0 [args]
1 arraylength
2 ifne 16
5 getstatic java.lang.System.out : java.io.PrintStream [16]
8 ldc <String "Nothing Input"> [22]
10 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
13 goto 106
16 aload_0 [args]
17 iconst_0
18 aaload
19 dup
20 astore_1
21 invokevirtual java.lang.String.hashCode() : int [30]
24 lookupswitch default: 98
case 97: 52
case 98: 64
52 aload_1
53 ldc <String "a"> [36]
55 invokevirtual java.lang.String.equals(java.lang.Object) : boolean [38]
58 ifne 76
61 goto 98
64 aload_1
65 ldc <String "b"> [42]
67 invokevirtual java.lang.String.equals(java.lang.Object) : boolean [38]
70 ifne 87
73 goto 98
76 getstatic java.lang.System.out : java.io.PrintStream [16]
79 ldc <String "Input is a"> [44]
81 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
84 goto 106
87 getstatic java.lang.System.out : java.io.PrintStream [16]
90 ldc <String "Input is b"> [46]
92 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
95 goto 106
98 getstatic java.lang.System.out : java.io.PrintStream [16]
101 ldc <String "Not compared"> [48]
103 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
106 return
Line numbers:
[pc: 0, line: 5]
[pc: 5, line: 6]
[pc: 13, line: 7]
[pc: 16, line: 8]
[pc: 76, line: 10]
[pc: 84, line: 11]
[pc: 87, line: 14]
[pc: 95, line: 15]
[pc: 98, line: 18]
[pc: 106, line: 24]
Local variable table:
[pc: 0, pc: 107] local: args index: 0 type: java.lang.String[]
Stack map table: number of frames 7
[pc: 16, same]
[pc: 52, append: {java.lang.String}]
[pc: 64, same]
[pc: 76, same]
[pc: 87, same]
[pc: 98, same]
[pc: 106, chop 1 local(s)]
}
可以看到,在底层的实现中。是先使用hashcode()来判断对象是否具有相同的hash值,当判断hash值相等时,再调用String.equals()方法判断是不是应该选用这个。
3.equals与==的区别。
equals是Object中的一个public 方法,可以在子类中覆盖。可是要注意的是,如果在子类中重写了equal()方法,那么需要重写hashcode()方法。
因为Object规范规定equals()与hashcode()需要满足以下规定。
(1)如果对象的equals()方法判断所需要的信息不修改的话,那么此对象对hashcode()的调用返回值要相同。
(2)若两对象依据equals()方法判断为相等,那么这两个对象.hashcode()返回值要相等。
(3)若两对象.equals()判断为不相等,.hashcode()不一定一定不相等。但是对于设计良好的方法,应该尽量让不同的hashcode()分布均匀。
虽然说equals()是可以由程序员自由定义的。但是一般来说,这个方法都是用来定义为两个Object的内容相等而设计的。如“qifengl”.equals(new String(“qifengl”))返回为true.
== 是指对象的引用变量相同(其实对于JVM而言,本质上是判断堆栈顶端的两个变量是否相同,因此==可以用来表示基本类型相等),也就是说“qifengl”== new String(“qifengl”)返回的是false。
4.Object有哪些公用方法?
Object的public方法有:
(1) public boolean equals(Object obj)
//见#3
(2) public final Class
public final native Class<?> getClass();
示例:
public class ClassTest {
public static void main(String args[]){
System.out.println(new Object().getClass());
}
}
结果:
class java.lang.Object
由此可见返回的是一个Class对象。
(3) public int hashCode()
HashCode()生成的示例为:
public class HashcodeTest {
public static void main(String args[]){
System.out.println(new Object().hashCode());
}
}
运行结果:
2018699554
这就是Object()生成的hashcode值。为了看这个值是咋来的,让我们来看下jdk中的源码。
public native int hashCode();
由此可见,生成hashCode码算法的hashCode()是在底层定义的,应该是由C++写的。
public String toString()
jdk中toString()源码为:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
子类可以覆盖掉。
(4) public final void wait() throws InterruptedException
public final void notify()
public final void notifyAll()
这三个方法是与线程相关的方法,所以放到一起讲。
公司里面前辈的代码就经常在需要synchronized的地方使用这几个方法。
下面的讲述引用自Effective java - page 245
java 5.0之后,应该首先使用并发工具而非wait和notify。但是很多时候你可能不得不维护包含了wait()和notify()的代码。这个时候就该知道wait和notify怎么用了。
wait方法被用来使线程来等待某个条件。它必须在同步方法区域里面被调用,这个同步区域将对象锁定在了调用wait()方法的对象上面了。始终应该使用wait()循环模式下来调用wait()方法,永远不要在循环外调用wait()方法。循环会在等待前后检测条件。
是使用wait的标准模式:
下面是测试源码。代码来自于
wait, notify示例
package test;
public class NotifyTest {
private String flag[] = { "true" };
class NotifyThread extends Thread {
public NotifyThread(String name) {
super(name);
}
public void run() {
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (flag) {
flag[0] = "false";
flag.notifyAll();
}
}
};
class WaitThread extends Thread {
public WaitThread(String name) {
super(name);
}
public void run() {
synchronized (flag) {
while (flag[0] != "false") {
System.out.println(getName() + " begin waiting!");
long waitTime = System.currentTimeMillis();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
waitTime = System.currentTimeMillis() - waitTime;
System.out.println("wait time :" + waitTime);
}
System.out.println(getName() + " end waiting!");
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Main Thread Run!");
NotifyTest test = new NotifyTest();
NotifyThread notifyThread = test.new NotifyThread("notify01");
WaitThread waitThread01 = test.new WaitThread("waiter01");
WaitThread waitThread02 = test.new WaitThread("waiter02");
WaitThread waitThread03 = test.new WaitThread("waiter03");
notifyThread.start();
waitThread01.start();
waitThread02.start();
waitThread03.start();
}
}
运行结果为:
Main Thread Run!
waiter01 begin waiting!
waiter03 begin waiting!
waiter02 begin waiting!
wait time :3003
waiter02 end waiting!
wait time :3003
waiter03 end waiting!
wait time :3003
waiter01 end waiting!
有一个问题是是用notify()还是notifyAll()来通知线程。在此给出的意见是:你总是应该使用notifyAll(),这样可以保证所有符合唤醒条件的线程被唤醒。而且也能防止恶意通知唤醒不相关线程导致真正的线程无休止等待下去。
下面我自己理解的wait(),notify()的流程:
Thread-1 启动,获取obj锁,然后执行obj.wait()并释放锁
Thread-2启动,获取obj锁,然后obj.notifyAll()。这时候所有符合条件的正在wait的线程都变成Alive()状态了。可以继续执行线程了。