Java - Notes

1.

数组

Java数组是嵌套的, 没有多维的概念, 这样每个数组就不需要有同样的长度, Java数组实际上是对象, 它知道自己的长度(length属性), 这为遍历提供了方便.

比如:

        int[][] a = new int[5][];
        for (int j=0; j<a.length; j++)
        {
            a[j] = new int[j];
        }

或:

int b[][] = {{1, 2, 3}, {2, 3, 4}, {4, 5, 6}};

遍历的时候, 好的编程习惯应该用length属性进行边界判断, 越界会抛出java.lang.ArrayIndexOutOfBoundsException.

这与C++很不一样, 它与C++一样的是下标从0开始, Basic默认从1开始.

这与JavaScript类似, 但由于Java是强类型, 所以不像JavaScript一样, 数组里面可以放任何类型的东西.

另外, Java数组的大小一旦声明了, 就不能再改变了, 不像VB那样用redim preserve实现数组大小改变.

Java数组可以声明和初始化放在一起, 不过一旦放在一起, 声明就不能包含数组的长度, 比如:

Object[] a = new Object[2]{new Object(), new Object()};              //这是错误的
Object[] a = new Object[]{new Object(), new Object()};                 // 这是正确的

另外Java数组哪怕是局部变量, 一旦声明了, 其所有元素都具有了默认值. 所有Primitive type数值的(从byteà double)默认值都为0(0.0f或0.0d), char默认值是’/0’, boolean默认值是false, 所有对象默认值为null. Java里局部变量光声明(除了数组元素, 我说过数组本身是对象)是不具默认值的, 使用会在编译时出错, 如果作为类的属性, 不管什么类型都具默认值.

数组是对象的证据:

int a[] = new int[5];

a instanceof Object 将返回true.

BTW, instanceof 还有很多内容实际上, 只说两点:

Instanceof两边都不能是Primitive type, 否则编译会出错.

如果Object obj = null; obj instanceof Object将返回false.

2.

修饰语

作用范围的修饰语将从小到大排列:

private < 默认 < protected < public

默认是包内可见.

重写只能范围大的重写范围小的, 即子类更远离private/protected.

native, transient, synchronized, volatile, abstract, final, static, strictfp(精确浮点, 符合浮点规范IEEE-754) 再说.

提一句, 这些修饰语并不能用到每一处, 比如顶层class就不能用private修饰.

3.

Java的一些小细节

Java可以定义与构造函数同名的方法(只要带返回值), 也可以定义函数名为main的方法, 但不是程序入口; 这些都不是编译和运行错误, 但一般没人这么做.

函数参数不能是void, 与C/C++不同.

若两个函数:

public void f(String a);

public void f(Object a);

调用f(null)时将调用更具体的类(更下层), 这里调用public void f(String a);

而要是还有public void f(StringBuffer a);

则会出现编译错误, 因为String和StringBuffer在同一层

在构造函数中使用super(args); this(args); 只能放在首句, 否则编译错误.

文件结构必须是package语句, import语句, 类定义, 省略一部分可以, 但弄错顺序会编译出错(注释可放在任何位置, 不计).

赋值对象和赋值原始类型的一个重要区别是, 原始类型在编译时被检查而对象在运行时被检查. 如:

byte b = 128; 就会出现编译错误, byte范围是-128~127.

Java primitive type自动类型转化, 从byte à double方向进行, 反之会编译出错, char能自动转化为int, 反之不行, boolean不能和其他任何类型自动类型转化.

逻辑运算符(&&和||)有短路现象, 只操作boolean类型, 按位运算符(&和|)可操作boolean和所有整数类型.

java原始类型自动转化顺序是(byte->short)/char->int->long->float->double. java中的char, 及字符串是以unicode编码的. 对原始类型赋值在编译其间就会检查有没超过范围, 超过范围了需加上强制类型转换(相当于从上面序列的后面向前面转换), 强制转换的结果是自动截去多余位数, 或截去小数部分.

4.

流程控制

if语句只能带boolean, 实际所有像for, while中的判断都只能是boolean, switch 语句的参数必须是一个byte,char,short 或int 类型的变量, 这些与C++不同.

case后只能是常量, default 语句不是必须在case 语句的结尾处出现, 但判断时default只有在case所有不匹配时才执行, 只是如果default中没加break, 结果可能会不一样. 因为switch语句一旦找到匹配, 它会一直执行下面语句直到遇到break或switch结束. 这些与C++是一样的.

Java中goto是保留字, 虽然实际中没什么用(const也是), 与C++不同. 但Java的确通过break和continue后带标签实现了goto的部分功能. 当然这主要从编译的简易性方便考虑(否则多层for循环的跳出只能用flag加if实现), 个人觉得也没有破坏程序的结构, 因为这种跳转是严加限制的.

使用标签跳转只要记住两点就OK了:

1). 有效标签只能加在原先break和continue能跳到的地方(下面这种除外, 即if前也可以放标签); 或者换一种说法, 有效标签只能放在选择(if, switch不能是else或case)或循环语句(for, while, do)前面. 有效标签是指可以被跳转的地方. 事实上, 跳转只能在这些标签包含的程序块中.

        int a = 10;
        label: if (a == 1)
        {
            break label;
        }

2). 跳转时只能是有意义的跳转才可用, 比如continue用来跳来if就没什么用, 也会编译错误. 这种看自己理解了. 其实最主要的用途是用来跳出多重循环, 如下:

       outer: for (int i = 0; i < 3; i++)
        {
            inner: for (int j = 0; j < 3; j++)
            {
                if (j > 1)
                {
                    break outer;
                }
            }
        }

5.

重载(overloaded)与重写(overridden)

java的重写是彻底的, 与C++的运行时的那个什么表很不相同. 如下例子:

class Base
{
    int i = 99;
 
    public void amethod()
    {
        System.out.println("Base.amethod ()");
    }
 
    Base()
    {
        amethod();
    }
}
 
public class RType extends Base
{
    int i = -1;
 
    public static void main(String argv[])
    {
        Base b = new RType();
        System.out.println(b.i);
        b.amethod();
    }
 
    public void amethod()
    {
        System.out.println("RType.amethod ()");
    }
}

输出结果为:

RType.amethod
99
RType.amethod

若同样的代码, 当然重写加了virtual之类的符合C++语法, 结果为:

Base.amethod
99
RType.amethod

这些当然只是表象, 深层原因有点忘了, 再说.

构造函数是不能被继承的.

不能重写方法使其访问权限更靠近私有,

6.

Exception

Java所有可抛出的异常都继承Exception, 与C++不同; C++可抛出任何类型的异常, 包括primitive type. 正因如些, C++中用…来表示捕获所有异常, 而Java用Exception就可以捕获所有异常了, 另外try/catch块后常常加finally, 这不是捕捉异常, 换句话说, 如果异常在catch中没被捕捉, 加了finally, 异常还会抛出来. 只是在try/catch执行完毕后, 就会执行finally中内容, 哪怕try/catch中包含return语句, 在return之后也会执行finally, 这由JVM保证, 除非程序在finally之前调用了System.exit. 所以finally一般放一些除释放内存(由垃圾收集来做)之外的释放资源, 比如文件句柄.

Java要在方法中抛出Exception, 必须在方法后加throws Exception进行声明, 但声明了不抛也是没问题的.

Java异常分检查异常和非检查异常, 检查异常强制需要向上层抛出或加try/catch处理, 非检查异常不强制需要这么做, 系统会检测异常, 并在抛出异常后中止程序. 当然如果非检查异常也想自己处理, 只要像检查异常那样向上抛或try/catch处理, 这样做的好处是防止程序中止:

       int[] aa = new int[5];
        try
        {
            aa[10] = 1;
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            //e.printStackTrace();
        }

通常来说,所有的I/O 操作都需要外在的使用try/catch 块的异常处理

对于finally还是加一个例子吧

public class TestFinally
{
    public static void main(String[] args)
    {
        TestFinally tf = new TestFinally();
        System.out.println(tf.f());
    }
   
    private int f()
    {
        int i = 10;
        int a[] = new int[4];
        try
        {
            a[10] = 100;
            //a[2] = 100;
            i += 2;
            return i;
        }
        catch(Exception e)
        { 
        }
        finally
        {
            System.out.println(i);
            i += 1;
        }
        return i;
    }
}

输出结果为10, 11.

若把a[10] = 100; 改为a[1] = 100;

输出结果为12, 12.

7.

断言

JDK1.4开始支持

编译用:

javac –source1.4 Myprog.java

如果你以如下形式正常运行程序

java Myprog

此时断言失效了,不会抛出断言异常。如果你后来需要查明一个问题,并确定所有的断言条

目都是true,你可以像下面那样激活断言来运行程序。

java –enableassertions Myprog

assert somebooleantest

assert somebooleantest : someinformativemethod

assert (iAge>0);

assert (iAge>0) : “age must be greater than zero”;

只有对程序正常运行不该出现的情况才使用断言. 断言实际应作为一种调试工具.

8.

垃圾回收

Java 语言保证一个对象的finalize 方法在对象被回收之前会调用.

可以使用System.gc()来建议垃圾回收器收集垃圾, 但是这并不能保证执行.

可以使用System.runFinalization()来保证程序结束前

9.

位移运算符

<<左移, 后补0, >>有符号右移, >>>有符号右移(这个C++中没有).

这些运算符只对byte, short, int, long有效(左右两边).

对于左边是byte, short, int时, 在移位之前, JVM会对右边进行对32取模, 即:

a << b;

实际的计算为:

a << (b % 32);

所以a << 32结果为a, 就不足为奇了.

注意左边为long, 右边取的模是64.

10.

内部类

分高层嵌套类(static), 成员类, 局部类(方法中的类)和匿名类.

在局部类定义中的代码只能使用包容块中的final 局部变量或方法的final参数, 或外层类的成员变量.

方法中不能有静态成员, 自然也没有静态类.

匿名类不能被引用, 也没有构造函数, 它一般会继承某个类或某个接口. 这在事件响应中很有用, 简化代码.

Thinking in Java中内部类讨论得蛮多, 看那个去好了.

11.

Object

看看Object本身也是件有趣的事.

要想使用Clone, 必须实现接口Cloneable, Cloneable中没有任何东西. 否则会抛异常java.lang.CloneNotSupportedException.

finalize用以垃圾回收.

equals和hashCode对Java中的Collection系统提供了很好的支持. 一般对这两者要符合JDK DOC中的规范才能正确使用Collection, 当然这不是编译器的要求.  equals同时弥补了==只比较内存地址的不足. Object中的hashCode默认只返回内存地址, 所以别指望同一个程序的不同运行会返回相同的值, 当然大部分时候相同, 与具体的JVM有关.

toString使一切对象能转化为String, 这对打Log很有好处, 同时个人感觉与Reflection呼应.

notify, nofityAll, wait实现线程同步很好的东东, 可见Java对多线程有多方便的支持.

getClass是对Reflection的支持. 任何一个对象obj, obj.class == obj.getClass()结果是true. 得到Class类对象那便是Reflection的天堂.

12.

Collection

所有实现Collection 接口的类存储对象为元素而不是原始数据类型.

往Collection中增加的元素都是引用, 不会创造新的元素, 这与C++中STL不同. 因为在Java中是自动垃圾回收机制, 只要引用在, 对象就不会被析构, 所以Collection保存引用就足够了.

如下:

package tony.javaTest;

import java.util.ArrayList;
import java.util.List;

class Hello
{
String ss = "Hello";
}

public class CollectionTest
{

public static void main(String[] args)
{
List list = new ArrayList();
Hello hello = new Hello();
list.add(hello);
hello.ss = "hi";

System.out.println(((Hello)list.get(0)).ss);
System.out.println(hello.ss);
}
}

输出为:

hi
hi

java.util.Iterator中hasNext()只是判断当前位置还有没有元素, 而next()返回当前位置元素, 并移到下一个元素位置处.

13.

Serializable

“对象的寿命通常随着生成该对象的程序的终止而终止. 有时候, 可能需要将对象的状态保存下来, 在需要时再将对象恢复. 我们把对象的这种能记录自己的状态以便将来再生的能力. 叫作对象的持续性(persistence). 对象通过写出描述自己状态的数值来记录自己 , 这个过程叫对象的串行化(Serialization) .”

可以说, 没有序列化, RMI, RPC之类的都很难实现.

Java对象只要实现接口java.io.Serializable就能被序列化. Serializable接口中没有任何的方法. 当一个类声明要实现Serializable接口时, 只是表明该类参加串行化协议, 而不需要实现任何特殊的方法.

需要注意的是:

串行化只能保存对象的非静态成员交量, 不能保存任何的成员方法和静态的成员变量, 而且串行化保存的只是变量的值, 对于变量的任何修饰符都不能保存.

对于某些类型的对象, 其状态是瞬时的, 这样的对象是无法保存其状态的. 例如一个Thread对象或一个FileInputStream对象 , 对于这些字段, 我们必须用transient关键字标明, 否则编译器将报错. transient的作用就是让对象的一些属性不被序列化, 所以对于一些需保密的字段, 比如不想存在磁盘或网络传输, 应声明该字段为transient.

14.

Class loader

可以定制类加载程序 (继承java.lang.ClassLoader) 改变类的字节码.

默认加载顺序: 引导程序类加载程序 (null bootstrap class loader)  扩展类加载程序 (sun.misc.Launcher$ExtClassLoader)  应用程序类加载程序 (sun.misc.Launcher$AppClassLoader).

要使用自己的定制类加载程序, 必须把类放在上面那些类加载程序找不到的路径下, 或者重写loadClass. 默认下, loadClass总是先调用父类加载程序的loadClass.

ClassLoader中:


protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null)
{
try
{
if (parent != null)
{
c = parent.loadClass(name, false);
}
else
{
c = findBootstrapClass0(name);
}
}
catch (ClassNotFoundException e)
{
}
if (c == null)
// If still not found, then call findClass in order
// to find the class.
c = findClass(name);
}
if (resolve)
{
resolveClass(c);
}
return c;
}

被加载的类不仅仅通过它的名称来确定, 还要通过一个类名称和定义类加载程序对才能确定. 两个名字相同加载自同一个类文件但加载程序不同的类也一定是不同的.

15.

对于Reflection, Thread及同步等, 还有好多…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值