曾经C语言中的二维数组和指针的问题一直困扰着我,还好在Java中二维数组和数组使用的不多,但是数组作为执行效率较高的集合,它还是很重要的。


一、认识数组的本质

不管在其他语言中数组是什么,在Java中它就是对象。一个比较特殊的对象。

下面一段代码通过getClass()看一下数组了类型类,结果有点奇怪,先不管。然后我们获得类型类之后就可以通过一些方法来查看类型方法的信息了,如下:

package TwoWeek;

class PersonTest{
	PersonTest(){	
	}
}
public class Group {

	    public static void main(String[] args) { 
	    	
	    	PersonTest per = new PersonTest();
	    	System.out.println("per的类型类的名字是:"+per.getClass().getName());
	    	
	        int[] array = new int[10];  
	        System.out.println("array的父类是:" + array.getClass().getSuperclass());  
	        System.out.println("array的类名是:" + array.getClass().getName());  
	    }  
}


输出结果:

per的类型类的名字是:TwoWeek.PersonTest
array的父类是:class java.lang.Object
array的类名是:[I


获得类型类之后可以操作的方法:

getName():String:获得该类型的全称名称。

getSuperClass():Class:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。

getInterfaces():Class[]:获得该类型实现的所有接口。

isArray():boolean:判断该类型是否是数组。

isEnum():boolean:判断该类型是否是枚举类型。

isInterface():boolean:判断该类型是否是接口。

isPrimitive():boolean:判断该类型是否是基本类型,即是否是int,boolean,double等等。



上面仅仅说明了数组是一个对象,一个比较特殊的对象而已,下面[L这个奇怪了类的数组维度规律,和其成员函数的

还有构造方法的形式,其实这也是一个正确的思路,探究一个类就要这么探究。

如下是规律探究代码:

<span style="font-family:FangSong_GB2312;font-size:14px;">public class Group {

    public static void main(String[] args) {  
        System.out.println("Object[]:" + Object[].class);  
        System.out.println("Object[][]:" + Object[][].class);  
        System.err.println("Object[][][]:" + Object[][][].class);  
        System.out.println("Object:" + Object.class);  
    }  
}
</span>
<span style="font-family:FangSong_GB2312;font-size:14px;">Object[]:class [Ljava.lang.Object;
Object[][]:class [[Ljava.lang.Object;
Object:class java.lang.Object
Object[][][]:class [[[Ljava.lang.Object;</span><span style="font-size: 18px; font-family: "Microsoft YaHei";">
</span>

最下面的一行是红色的,说明这种写法不规范,但我们也可以看出,随着维度的加深“[”符号是越多的,只是一种表

示形式,下面我们来看一下这个类内部的构造:

package TwoWeek;
public class Group {

	public static void main(String[] args) {  
        int[] array = new int[10];  
        Class clazz = array.getClass();     
        System.out.println(clazz.getDeclaredFields().length);     
        System.out.println(clazz.getDeclaredMethods().length);     
        System.out.println(clazz.getDeclaredConstructors().length);     
        System.out.println(clazz.getDeclaredAnnotations().length);     
        System.out.println(clazz.getDeclaredClasses().length);     
    }   
}

------output-----
0
0
0
0
0

结果都是0,所以说数组类型类内部毛线构造方法。函数都没有,JVM通过其他的方式来实现的。

二、数组的声明、创建、初始化

声明方式:
type var[]; 或type[] var;

声明数组时不能指定其长度(数组中元素的个数),

Java中使用关键字new创建数组对象,格式为:

数组名 = new 数组元素的类型 [数组元素的个数]

初始化:

1.动态初始化:数组定义与为数组分配空间和赋值的操作分开进行;
2.静态初始化:在定义数字的同时就为数组元素分配空间并赋值;
3.默认初始化:数组是引用类型,它的元素相当于类的成员变量,因此数组分配空间后,每个元素也被按照成员变量的规则被隐士初始化。


动态初始化:

public class TestD  
{  
     public static void main(String args[]) {  
         int a[] ;  
         a = new int[3] ;  
         a[0] = 0 ;  
         a[1] = 1 ;  
         a[2] = 2 ;  
         Date days[] ;  
         days = new Date[3] ;  
         days[0] = new Date(2008,4,5) ;  
         days[1] = new Date(2008,2,31) ;  
         days[2] = new Date(2008,4,4) ;  
     }  
}  
 
class Date  
{  
     int year,month,day ;  
     Date(int year ,int month ,int day) {  
         this.year = year ;  
         this.month = month ;  
         this.day = day ;  
     }  
}


静态初始化:


public class TestS     
{     
     public static void main(String args[]) {     
         int a[] = {0,1,2} ;     
         Time times [] = {new Time(19,42,42),new Time(1,23,54),new Time(5,3,2)} ;     
     }     
}     
 
class Time     
{     
     int hour,min,sec ;     
     Time(int hour ,int min ,int sec) {     
         this.hour = hour ;     
         this.min = min ;     
         this.sec = sec ;     
     }     
}


默认初始化:

public class TestDefault     
{     
     public static void main(String args[]) {     
         int a [] = new int [5] ;     
         System.out.println("" + a[3]) ;     
     }     
}




三、数组比较快的-速度测试

怎么测试,当然是好ArrayList比较了,不用想都知道链表一定比较慢,但是究竟慢多少和慢在哪里,我们通过代码来分析下:

Long time1 = System.currentTimeMillis();  
        for(int i = 0 ; i < 100000000 ;i++){  
            sum += arrays[i%10];  
        }  
        Long time2 = System.currentTimeMillis();  
        System.out.println("数组求和所花费时间:" + (time2 - time1) + "毫秒");  
        Long time3 = System.currentTimeMillis();  
        for (int i = 0; i < 100000000; i++) {  
            sum  += list.get(i%10);  
        }  
        Long time4 = System.currentTimeMillis();  
        System.out.println("List求和所花费时间:" + (time4 - time3) + "毫秒");  
--------------Output:  
数组求和所花费时间:696毫秒  
List求和所花费时间:3498毫秒


  从上面的时间消耗上面来说数组对于基本类型的求和计算的速度是集合的5倍左右。其实在list集合中,求和当中有一个致命的动作:list.get(i)。这个动作是进行拆箱动作,Integer对象通过intValue方法自动转换成一个int基本类型,在这里就产生了不必要的性能消耗。


上面提到了拆箱,那什么是拆箱呢?

装箱是复制值类型的值到托管堆。
拆箱是提领指针指向的值

int i=1     //声明一个值类型的变量

object o=i;  //装箱,在堆上开辟一块内存,复制i的值到此内存块,公布一个地址,o引用它,o则为已装箱值类型

(引用类型),会发生内存复制的操作。

int b=(int)o;  //拆箱,通过o查找到托管堆上的内存快,提领这个内存快中值的地址,然后然后给b赋值,,事实上

在给b赋值这一步不能算拆箱的一步,所以拆箱比装箱廉价得多


四、数组的扩充和复制

数组的大小不够用时怎么办,当然扩充了,那么使用什么样的方式来扩充呢,问题来了。。

public class ArrayUtils {  
    public static <T> T[] expandCapacity(T[] datas,int newLen){  
        newLen = newLen < 0 ? datas.length :datas.length + newLen;     
        //生成一个新的数组  
        return Arrays.copyOf(datas, newLen);  
    }  
    public static <T> T[] expandCapacity(T[] datas){  
        int newLen = (datas.length * 3) / 2;      //扩容原始数组的1.5倍  
        //生成一个新的数组  
        return Arrays.copyOf(datas, newLen);  
    }  
    public static <T> T[] expandCapacityMul(T[] datas,int mulitiple){  
        mulitiple = mulitiple < 0 ? 1 : mulitiple;  
        int newLen = datas.length * mulitiple;  
        return Arrays.copyOf(datas,newLen );  
    }  
}



接下来是复制了,正如chenssy大神将的那样,我也犯过这样的错误,

使用List.toArray()方法转换成数组然后再通过Arrays.copyOf拷贝,在转换成集合,个人觉得非常方便,殊不知我已经陷入了其中的陷阱。我们知道若数组元素为对象,则数组里面数据是对象引用。

下面是一个浅拷贝和深拷贝的问题,接着学习一下了

浅拷贝只复制一个对象,传递引用,不能复制实例。而深拷贝对对象内部的引用均复制,它是创建一个新的实例,并且复制实例。

深拷贝实例,复制的是对象实例:

public class Person {
    private String name;
    private String sex;
    private int age;
    
    public Person(String name,String sex,int age){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    
    public Person(Person p){                   //拷贝构造方法,复制对象
        this.name = p.name;
        this.sex = p.sex;
        this.age = p.age;
    }
}


浅拷贝实例,复制的是对象引用:

public class Asian {
    private String skin;
    Person person;
    
    public Asian(String skin,Person person){
        this.skin = skin;
        this.person = person;                    //引用赋值
    }

    public Asian(Asian asian){                 //拷贝构造方法,复制对象
        this(asian.skin,asian.person);           
    }
}


对于数组也是一样的,如果使用的是浅拷贝的方式,改变数组的一个对象,其它复制该元素的对象也会改变。其实就是两个指针指向了同一个元素了。



五、asList()的分析思路

学习分析思路比分析问题跟重要,下面就一asList()为例来分析一下思路吧

问题:

enum Week{Sum,Mon,Tue,Web,Thu,Fri,Sat}  
    public static void main(String[] args) {  
        Week[] weeks = {Week.Sum,Week.Mon,Week.Tue,Week.Web,Week.Thu,Week.Fri};  
        List<Week> list = Arrays.asList(weeks);  
        list.add(Week.Sat);  
    }


这个程序非常简单,就是讲一个数组转换成list,然后改变集合中值,但是运行呢?

Exception in thread "main" java.lang.UnsupportedOperationException  
    at java.util.AbstractList.add(AbstractList.java:131)  
    at java.util.AbstractList.add(AbstractList.java:91)  
    at com.array.Test.main(Test.java:18)

编译没错,但是运行竟然出现了异常错误!UnsupportedOperationException ,当不支持请求的操作时,就会抛出

该异常。从某种程度上来说就是不支持add方法,接下来分析吧。

(1)首先想到的就是看源码了

public static <T> List<T> asList(T... a) {  
        return new ArrayList<T>(a);  
    }


注意这个参数:T…a,这个参数是一个泛型的变长参数,我们知道基本数据类型是不可能泛型化的,也是就说8个基本数据类型是不可作为泛型参数的,泛型学的不好啊,一会再补一下吧。

(2)当运行打不到我们想要的效果的时候,源码之后,再看一下它返回的类

private static class ArrayList<E> extends AbstractList<E>  
implements RandomAccess, java.io.Serializable{  
    private static final long serialVersionUID = -2764017481108945198L;  
    private final E[] a;  
    ArrayList(E[] array) {  
        if (array==null)  
            throw new NullPointerException();  
    a = array;  
}  
   /** 省略方法 **/  
}


(3)还是没有我们想要找的add()方法,怎么办,查看其父类有没有

public boolean add(E e) {  
    add(size(), e);  
    return true;  
    }  
    public void add(int index, E element) {  
    throw new UnsupportedOperationException();  
    }


父类中仅仅提供了方法,没有给出具体的实现,想使用的话只用我们自己来实现了,分析到此结束。