Java虚拟机规范读书笔记(二)

2.10.2 方法的签名
方法的签名仅包含两个部分:方法名和形式参数.返回值类型和修饰符都不算是签名.

2.10.3 方法修饰符
声明为abstract的方法不能再被声明为private,static,native或者synchronized.
static 方法(类方法 Class
Method)调用时总是不引用某个特定的对象.且只能引用该类的类方法和类变量.即其它的static内容.
当实例方法(Instance
Method):当前低相在执行该方法的主体时,由关键字this和super引用.
private方法在和所有在final类中声明的方法都隐式的是final.

synchronized语句对各对象使用同样的锁.

native方法指示它是用依赖于平台的代码实现的.native方法所在的类要使用System.loadLibrary读取连接库.native方法没有方法体,貌似一个接口,具体实现交由本地代码实现.

2.12 构造函数

静态构造函数:以文本顺序执行
    private static String str1 = getStr1();

    private static String str2 =
getStr2();

    private static String getStr1()
{
        return str2;
    }

    private static String getStr2()
{
        return “str2”;
    }
类初始化后结果:str1 = null,str2 = “str2”;

    private static String str1 =
getStr1();

    private static String str2 =
getStr2();

    private static String getStr1()
{
        return “str1”;
    }

    private static String getStr2()
{
        return str1;
    }
类初始化后结果:str1 = “str1”,str2 = “str1”;

默认的无形式参数的构造函数只能访问父类的无形参的构造函数.

2.13.4 接口(常数)域
接口的域中只能是常量值,且隐式的声明为 public static
final,域不能被transient和volatile修饰.

2.13.5 接口(抽象)方法
接口主体中的每个方法声明都隐式的是public
abstract,不能被native或者sychronized修饰,因为此二者是实现属性而不是接口属性.不能被static或者final修饰,但是此方法的实现方法可以被这二者修饰.

2.14 数组
数组是对象,对象的所有方法都可以在对象上调用.

数组必有原始数组的元素类型.只有一种情况下数组的元素可能是数组:如果元素的类型是Object.

关于字符编码问题

今天被一个哥们问住了:当文件中有ASCII编码和ANSI编码混排的时候,如何区分.

经过查找资料,结果如下:

 

中文总体上来说有ANSI编码和UNICODE编码
ANSI称之为地区编码,UNICODE是国际编码
ANSI往往是某个国家为自己的语言所创立的,比如GB2312,GBK,BIG5等
UNICODE用来涵盖世界上所有的字符,简单的说就是全球用同一套编码格式.

 

ASCII采用00~7F之间的编码,而ANSI则采用80~FF的两字节表示一个字符,UNICODE文章的开头会以FF
FE开头来标识.

 

“123好”的ANSI编码就是”123″以ASCII编码保存而’好’以ANSI保存.
ANSI编码的类型是当字在ASCII表中的时候,就以ASCII保存,如果不在,则以两位80-FF之间的编码保存.

 

范例:

123好
ASCII:31 32 33 3F (转换时溢出)
ANSI:31 32 33 BA C3
UNICODE:FF FE 31 00 32 00 33 00 7D 59

 

31 32 33 BA C3  没有以FF FE开头,故不是UNICODE
0< 31,32,33 <7F –ANSI
80< BA,C3 <FF –两字节组成一个ANSI

 

后来又简单的看了一下半角和全角的问题,其实就是对于ASCII码表中存在的字符是以ASCII码保存(半角)还是以ANSI码保存(全角)的不同而已.

 

 

测试程序如下(IDE默认编码:GBK):

import java.io.UnsupportedEncodingException;

public class CodeTest {
    private static String byte2Hex(byte b)
{
        String str = “”;
        int i = b;
       
if(i<0){
           
i=b+256;
        }
        char[] chars = new
char[] { ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’,
‘7’,
               
‘8’, ‘9’, ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’ };
        int poit = i / 16;
        str +=
chars[poit];
        poit = i % 16;
        str +=
chars[poit];
        return str;
    }

   
    public static void main(String[] args)
{

        String str =
“123好”;
        byte[] code =
str.getBytes();
        for (byte b
: code)
           
System.out.print(byte2Hex(b) + ” “);
       
System.out.println();
        try
{
           
code = str.getBytes(“unicode”);
        } catch
(UnsupportedEncodingException e) {
           
// TODO Auto-generated catch block
           
e.printStackTrace();
        }
        for (byte b
: code)
           
System.out.print(byte2Hex(b) + ” “);
       
System.out.println();
        try
{
           
code = str.getBytes(“ascii”);
        } catch
(UnsupportedEncodingException e) {
           
// TODO Auto-generated catch block
           
e.printStackTrace();
        }
        for (byte b
: code)
           
System.out.print(byte2Hex(b) + ” “);
       
System.out.println();
        System.out.println(new
String(code));
       
    }

}

 

Java虚拟机规范–读书笔记

1.2 虚拟机
Java虚拟机是与Java语言分离的,任何只要能够按照有效的class文件表述的功能语言都可以由Java虚拟机作主机.

2.1 Unicode
Java程序是用Unicode字符编码编写写.详细情况参考<Java语言规范>.Java程序的所有输入元素都是由ASCII字符形式(除注释和标识符).

2.2 标识符
标识符是不限长度的Unicode字母和数字的序列.第一个字符必须是字母.字母和数字可以从整个Unicode字符集中选择.
也就是说,可以使用中文,日文等其它语言的字符做为标识符,前提是第一个字符必须不能是数字或者其它字符.

2.4 值和类型
Java基本类型:boolean型和数值型(numeric type)
整型:byte,short,int,long,对应8,16,32,64位有符号二进制补码整数.char为16位无符号整数.

浮点型:float,double:对应32,64位IEEE浮点数.含正负有符号数量数字,正负零,正负无穷(infinity)和一个特殊的Not-a-Number(缩写NaN,代表某些操作,比如零被零除的结果.)

仅能够抛出异常的整数操作符是整数除和整数余数操作符.除零可能抛ArithmenticException.

Java对浮点数计算的近似采用最接近舍入(round-to-nearest)–当有两个数值都无限近似接近的时候,取最低位零的舍入.

对浮点数转换位整数时使用向零舍入(round-to-zero)–选择最接近的,其数量不大于无穷精确结果的格式的值做为它的结果.

浮点操作符不产生异常,溢出的操作产生一个有符号的无穷.下溢操作产生有符号零.没有数学定义的结果产生NaN.所有以NaN做为一个操作数的数值计算产生一个NaN结果.

上溢:Double.MAX_VALUE*Double.MAX_VALUE或者(-1.0/0)

下溢:-Double.MIN_VALUE/Double.MAX_VALUE
NaN:0.0/0或者-0/0.0(0/0会抛异常,而0/0.0不抛)

Object类型的变量可以持有任意对象的引用,包括类引用和数组引用.所有的类和数组类型都继承于Object类.
字符串文字是类String的实例引用.

2.9.1 域修饰符
volatile 这个关键字和synchronized的区别?
经过查阅资料,整理如下:Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值,而且,当成员变量发生变化时,强迫线程将变化值写到共享内存,这样在任何时刻,两个不同的线程总是看到某个成员变量的同一值,作用:当多个线程同事与某个对象互交时,就必须要注意到要让线程即时的得到共享成员变量的变化.

final类不允许被volatile关键字

测试如下:
public class Test extends Thread {

    private String name = “”;

    private Integer i = 0;

    public Test(String name)
{
        this.name = name;
    }

    public void run() {

        synchronized (i)
{
           
while (i < 100) {
               
System.out.println(name + i);
               
try {
                   
sleep(100);
               
} catch (InterruptedException e)
{
                   
e.printStackTrace();
               
}
           
}

       
}
    }

   
    public static void main(String[] args)
{
        Test t1 = new
Test(“T1–“);

        t1.start();
        for (; t1.i
< 1000;) {
           
try {
               
sleep(50);
           
} catch (InterruptedException e)
{

               
e.printStackTrace();
           
}
           
synchronized (t1.i) {
               
t1.i++;
           
}
        }

    }

}

运行结果:
T1–0
T1–0
T1–0
T1–0
T1–0
T1–0
T1–0

—————————–
public class Test extends Thread {

    private String name = “”;

    private volatile Integer i = 0;

    public Test(String name)
{
        this.name = name;
    }

    public void run() {
        while (i
< 100) {
           
System.out.println(name + i);
           
try {
               
sleep(100);
           
} catch (InterruptedException e)
{
               
e.printStackTrace();
           
}
        }
    }

   
    public static void main(String[] args)
{
        Test t1 = new
Test(“T1–“);
        t1.start();
        for (; t1.i
< 1000;) {
           
try {
               
sleep(50);
           
} catch (InterruptedException e)
{
               
e.printStackTrace();
           
}
           
t1.i++;
        }
    }
}

运行结果:
T1–0
T1–1
T1–3
T1–5
T1–7
T1–9

到这里仍然有点迷惑,再取查资料,得到了完美的解释,详见破除<Java神话系列文章(三)–原子操作都是线程安全的>.

2.9.2 关于域的初始化:
在类初始化的时候,具体的初始化顺序.
测试内容如下:静态块,静态变量初始化,动态变量初始化,构造函数以及在继承时的初始化过程.

测试代码如下:
public class Base {
    private String strSta = getStrSta();

    private String str = getStr();
    static {
       
System.out.println(“Base_static_Block”);
    }

    public Base() {
       
System.out.println(“Base_constructor”);
    }

    private String getStr()
{
       
System.out.println(“Base_variables”);
        return null;
    }

    private String getStrSta()
{
       
System.out.println(“Base_static_variables”);
        return null;
    }

   
    public static void main(String[] args)
{
        Sub t = new Sub();
    }
}

public class Sub extends Base {
    private String strSta = getStrSta();

    private String str = getStr();
    static {
       
System.out.println(“Sub_static_Block”);
    }

    public Sub() {
       
System.out.println(“Sub_constructor”);
    }

    private String getStr()
{
       
System.out.println(“Sub_variables”);
        return null;
    }

    private String getStrSta()
{
       
System.out.println(“Sub_static_variables”);
        return null;
    }

}

运行结果:
构造Base时
Base_static_Block
Base_static_variables
Base_variables
Base_constructor

构造Sub时
Base_static_Block
Sub_static_Block
Base_static_variables
Base_variables
Base_constructor
Sub_static_variables
Sub_variables
Sub_constructor

由此可见,在继承关系下,并非所有的与构造有关的程序块都是父类先运行而子类后运行.在父类静态程序块执行后,子类静态s程序块马上执行,而后才是静态成员初始化,动态成员变量初始化,最后运行构造函数.