关于JAVA数组的几点注意事项与一些低级错误

1、数组不是集合,它只能保存同种类型的多个原始类型或者对象的引用。数组保存的仅仅是对象的引用,而不是对象本身。

2、数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。

3、数组声明的两种形式:一、int[] arr; 二、int arr[]; 推荐使用前者,这符合Sun的命名规范,而且容易了解到关键点,这是一个int数组对象,而不是一个int原始类型。

  数组初始化可以在声明是进行,int[] arr = {1,2,3}或者int[] arr = new int[3]{1,2,3}。

4、在数组声明中包含数组长度永远是不合法的!如:int[5] arr; 。因为,声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。

5、在数组构造的时候必须指定长度,因为JVM要知道需要在堆上分配多少空间。反例:int[] arr = new int[];

6、多维数组的声明。int[][][] arr; 是三维int型数组。

7、一维数组的构造。形如:String[] sa = new String[5];

或者分成两句:String[] sa; sa = new String[5];

8、原始类型数组元素的默认值。对于原始类型数组,在用new构造完成而没有初始化时,JVM自动对其进行初始化。默认值:byte、short、 int、long--0 float--0.0f double--0.0 boolean--false char--'"u0000'。(无论该数组是成员变量还是局部变量)

9、对象类型数组中的引用被默认初始化为null。如:Car[] myCar = new Car[10]; 相当于从myCar[0]到myCar[9]都这样被自动初始化为myCar[i] = null;

10、对象类型的数组虽然被默认初始化了,但是并没有调用其构造函数。也就是说:Car[] myCar = new Car[10];只创建了一个myCar数组对象!并没有创建Car对象的任何实例!

11、多维数组的构造。float[][] ratings = new float[9][]; 第一维的长度必须给出,其余的可以不写,因为JVM只需要知道赋给变量ratings的对象的长度。

12、数组索引的范围。数组中各个元素的索引是从0开始的,到length-1。每个数组对象都有一个length属性,它保存了该数组对象的长度。(注意和String对象的length()方法区分开来,这两者没有统一起来是很遗憾的。)

13、Java有数组下标检查,当访问超出索引范围时,将产生ArrayIndexOutOfBoundsException运行时异常。注意,这种下标检查不是在编译时刻进行的,而是在运行时!也就是说int[] arr = new int[10]; arr[100] = 100; 这么明显的错误可以通过编译,但在运行时抛出!

Java的数组下标检查是需要额外开销的,但是出于安全的权衡还是值得的,因为很多语言在使用数组时是不安全的,可以任意访问自身内存块外的数组,编译运行都不会报错,产生难以预料的后果!

使用reflict.Array创建Array实例

import java.util.*;
import java.lang.reflect.*;

public class ArrayTest {
    public static void main(String...args) {
        int[] arr = (int[])Array.newInstance(Integer.TYPE, 10);
        Random r = new Random(100);
        for(int i =0; i<arr.length; i++) {
            arr[i] = r.nextInt(100);
            }
        System.out.println(Arrays.toString(arr));
        }
    }

 

1、 不能用“==”比较两个字符串内容相等。

2、 对list做foreach循环时,循环代码中不能修改list的结构。

java foreach只能用于只读的情况.如果需要删除操作,请用迭代器或者直接遍历List.

3、 空指针异常。

4、 数组下标越界。

// 获取一个数组对象

String[] cIds = ContentService.queryByName(name);

if(null != cIds)

{

// 只是考虑到cids有可能为null的情况,但是cids完全有可能是个0长度的数组,因此cIds[0]有可能数组下标越界  

5、 将字符串转换为数字时没有捕获NumberFormatException异常。

6、 对文件、IO、数据库等资源进行操作后没有及时、正确进行释放。

7、 循环体编码时不考虑性能,循环体中包含不需要的重复逻辑。

8、 数据类没有重载toString()方法。

9、 嵌套使用try-catch,或者try-catch后面没有必要的finally操作。

10、 equals操作时没有将常量放在equals操作符的左边。

java低级错误

  1. Calendar的错误使用

Calendar从星期日开始到星期六为一个周期,数字表示依次为:1,2,3……7;MONTH的表示是从数字0开始,所以月份应该是该数字+1。所以我们在使用的时候一定要仔细的阅读API文档,避免类似的陷阱。

  1. 序列化类的多版本问题

Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。当实现java.io.Serializable接口的实体(类)没有显式地定义serialVersionUID时,Java序列化机制会根据编译的class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,只有同一次编译生成的 class才会生成相同的serialVersionUID

---解决方法:可以在程序一生成一个

serialVersionUID属性,这样运行

就不会出错。

  1. 正确定义hashcode
  2. 解读

   类A覆写了hashCode方法,并采用A的属性value来生成A的hashCode。使用HashSet作为集合对象,把类A的一个对象a加入到HashSet中后,如果对象a的属性value发生了变化,则a的hashCode()方法返回的值也就发生变化,则无法将对象a从HashSet中删除。

 

预防措施如下:

1、如果覆写了equals方法,必须同时覆写hashCode方法。

2、equals方法和hashCode方法应避免使用易变的属性,避免该对象的hashCode频繁变化。

3、当对象被加入到HashSet(或作为Key加入到HashMap)后,应该避免该对象的hashCode发生变化。

 

4.正确理解Java的浅clone和深clone

浅clone和深clone都是clone,它们本质区别是对象内部的成员属性(非原生类型属性,如int等)在clone时是否处理为引用。如果仍然保留为引用,则称为浅clone,反之称为深clone。浅clone方式得到clone对象即可,深clone方式在得到clone对象后,还需要对引用的成员属性进行“clone”处理。使用Vector等容器的clone方法一定要注意:对于不变属性可以直接拷贝,对于可变属性需要手动写方法实现深层拷贝防止引用调用。深拷贝要深到什么程度,答案就是一直深到你想要的程度,包括深到不能再深的程度。

 

  1. 为什么定义equals方法的同时也要定义hashCode方法

equals方法用于实现对象之间逻辑上是否相等的判断,而不是判断两个引用是否指向同一个对象,hashCode用于返回对象的哈希码(也有翻译成散列码的),逻辑上相等(equals比较相等)的两个不同对象它们返回的hashCode值肯定不相等。 Java规范中规定:如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果,所以定义equals的同时一定要定义hashCode,并且要保证equals比较相等的对象,hashCode返回值也必须相同。

如果定义了equals方法,没有定义hashCode,两个对象的hashCode却不同,Map查找对象的时候会首先比较hashCode值,然后再使用equals比较,造成Map无法找到期望的对象。

 

  1. 大小写转换的正确处理

String提供有大小写转换方法:String.toUpperCase()和String.toLowerCase(),另外还有带Locale参数的大小写转换方法:String.toUpperCase(Locale locale)和String.toLowerCase(Locale locale)。 String.toUpperCase(Locale locale)和String.toLowerCase(Locale locale)之所以要带Locale参数,就是希望你指定使用的是哪种语言,不带参数的String.toUpperCase()和String.toLowerCase()使用的是系统缺省的语言,例如操作系统的当前语言。有些语言的大小写转换使用了较特殊的规则,甚至不是1:1的字符对应关系,也就是说转换前和转换后的字符串长度不一定相等。

---如果在Eclipse的run对话框中显示告诉VM我用的是Turkish,那最后输出的结果已经不是我预期的大写的I了,而是另外一个字符(İ)。

这个例子充分说明,大小写转换因语言环境的不同,转换结果可能并非如你所预期的。

如果大小写转换之后你是要用于字符串比较(这样的转换纯粹是为了比较的方便),也就是在我们的26个字母通常大小写转换预期内,则强烈建议你显示指定“英文”的Locale(Local.US)。

如果你的大小写转换就是希望遵循当前使用软件的国家语言大小写转换习惯,则使用不带参数的String.toUpperCase()和String.toLowerCase()更好一些,这样的代码反而能够适应不同的语言环境。

 

  1. 使用包装器对象带来的低效问题

解读

每个基本类型(primitive)都有相应的包装器(wrapper)对象:Integer、Long、Float、Double、Short、Byte、Character和Boolean,我们在使用时不要直接new Integer对象(这样的做法是低效的),而应该调用包装器对象的valueOf方法

为什么以上代码是低效的,而要使用Integer result = Integer.valueOf(-1)或者Integer result = -1来代替?我们来看看JDK的Integer.valueOf方法实现

public static Integer valueOf(int i) {

final int offset = 128;

if (i >= -128 && i <= 127) { // must cache

    return IntegerCache.cache[i + offset];

}

        return new Integer(i);

    }

可以看到内存中已经缓存有-128到127这个区间的256个Integer对象,如果方法传入的i值介于这个范围就不用再new Integer对象了,省去了运行时间,也节省了内存资源。另外的几个包装器对象( Long、Short、Byte、Character和Boolean )类似。

 

  1. 对于Map元素的遍历使用entrySet还是KeySet?

JDK实现的数据结构中常用的Map有两类:HashMap和TreeMap。keySet和entrySet在Map元素数较少时(小于10000)在查询速度上的区别不大,它们对于程序性能的影响可以忽略不计。但在元素较多时(大于100000)时entrySet的速度要明显快于keySet,尤其是TreeMap更明显。

 

  1. 对InputStream.skip()返回值的处理(隐藏的异常)

java.io.InputStream.skip(long n):跳过和放弃此输入流中的 n 个数据字节,返回的是跳过的实际字节数。如果skip方法的返回值小于要跳过得字节数,则说明有异常发生,此时需要对异常情况进行处理。

需要比较skip的返回值和输入参数,如果两者不相等时,需做特殊处理。

两者不相等的情况可能的原因有:

1)在跳过 n 个字节之前已到达文件的末尾;

2)输入参数为负;

  1. 正确理解String/StringBuffer/StringBuilder的区别

StringBuffer的内部实现方式和String不同,所以StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。

   StringBuilder提供一个与StringBuffer兼容的API,该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

 11.

  1. Java中流的操作,不要使用匿名流对象,以免出现异常,没有句柄,无法关闭

例子:

 

try

        {

               File file = new File("test.exe");

            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));

            //之后只关闭了一个流bis,匿名流new FileInputStream(file)没有关闭

            ........

        }

           改写之后:

           try

        {

               File file = new File("test.exe");

            FileInputStream fis = new FileInputStream(file);

            BufferedInputStream bis = new BufferedInputStream(fis);

            //之后关闭两个流fis和bis

            ........

        }

 

 这个效率相对低点
m.keySet().iterator()
以后最好用下面这个
jmap.entrySet().iterator()

posted @ 2015-02-08 20:45  光闪  阅读(7439)  评论(1编辑  收藏  举报