Java之数组,ArrayList,自动装箱与拆箱和LinkedList

一.数组

数组是一种数据结构,它允许你存储多个相同类型的元素在单个变量中。对于数值类型的数组元素,其默认元素为0。布尔类型的数组,其默认元素为false。如果是Sting或者是其他对象类型的数组,则默认值为null。

Java中数组元素的声明:dataType arrRef;

Java中数组元素的初始化,有两种方式:
(1)dataType[] arrRef = new dataType(arrSize);
这种方式声明的数组只是初始化了数组中元素的类型以及数组的大小,具体的值需要通过手动赋值的方式完成:arrRef[0] = 1,……
(2)dataType[] arrRef = { 1, 2, 3, 4, 5, 5, 6, 7, 8, 8 };
这种方式声明的数组直接初始化了数组中所包含的元素。(此种方式只能在数组声明时初始化,例如:dataType[] arrRef = new dataType(10); arrRef = { 1, 2, 3, 4, 5, 5, 6, 7, 8, 8 },这样是不允许的)

Java这种强类型语言中数组的定义与JavaScript这种弱类型语言的定义十分不同,Java数组定义时就必须指定数组元素的类型以及数组元素的个数,在使用的过程中不能动态增加,否则会出现数组越界错误。而JavaScript数组的定义则随意许多,什么类型的元素都可以丢在一起,而且数组的长度会自动增加(如果不够的话)。从这里可以看出,Java的数组定义更为严格,在编译阶段会执行更为严格的检查。

当我们试图访问越界索引位置的数组元素时,Java编译器会报错:ArrayIndexOutOfBoundsException。

以下是一个数组使用的简单例子:

package com.zhoujh;

import java.util.Scanner;

public class Main {
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        int[] intArr = getInteger(5);
        for (int i=0; i < intArr.length; i++) {
            System.out.println("索引" + i + "的值为:" + intArr[i]);
        }
    }

    public static int[] getInteger(int number) {
        System.out.println("请输入" + number + "个数:" + "\r");
        int[] values = new int[number];

        for (int i=0; i < values.length; i++) {
            values[i] = scanner.nextInt();
        }

        return values;
    }
}

需要注意,之前提到过,static方法中只能访问静态成员,所以getInteger方法要想在main方法中执行,必须声明为static类型的。

二. 引用类型(reference types)与值类型(value types)

Java中的原始类型(primitive types,或称为基本类型)都是值类型,总共有八种:byte,short,int,long,float,double,char,boolean。

而引用类型则指的是数组(Array),对象(Object)等。

值类型的赋值是值的复制,所以相互间不会影响。
而引用类型的赋值,是引用的赋值,复制的是指向同一个对象的内存地址,所以“一改则全改”。

三. List与ArrayList

List是Java内部定义的一个接口(interface),而ArrayList正是实现了该接口。

ArrayList可以理解为一个变长的数组,功能更加的强大,以下代码将具体介绍如何使用和操作ArrayList:

pakage com.zhoujh;

import java.util.ArrayList;

public class LearnArrayList {
	private ArrayList<String> arrList = new ArrayList<String>();
	
	// 向arrList中添加元素
	public void addEle(String item) {
		arrList.add(item);
	}
	
	// 获取arrList中指定位置的元素
	public String getEle(int position) {
		return arrList.get(position);
	}
	
	// 读取并打印arrList中所有的元素
	public void printAllEle() {
		for (String ele: arrList) {
			System.out.println(ele);
		}
		// 或者是:
		for (int i = 0; i < arrList.size(); i++) {
			System.out.println(arrList.get(i))
		}
	}
	
	// 更新arrList中的某个位置的元素
	public void updateEle(int position, String newItem) {
		arrList.set(position, newItem);
	}
	
	// 移除arrList某个位置的元素
	public void removeEle(int position) {
		arrList.remove(position);
	}

	// 判断某个元素是否在arrList中,如果在则返回对应的索引,否则返回null
	public String findEle(String searchItem) {
		// boolean isExist = arrList.contains(searchItem);
		int position = arrList.indexOf(searchItem);
		if (position > -1) {
			return position;
		}
		return null;
	}
}

以上代码中,ArrayList中的type表示的是创建ArrayList集合的元素类型。

接下来的代码将展示具体如何使用我们创建的LearnArrayList类:

package com.zhoujh;

import java.util.Scanner;

public class Main {
	private static Scanner scanner = new Scanner(System.in);
	private static LearnArrayList learnArrList = new LearnArrayList();

	public static void main(String[] args) {
		boolean quit = false;
		int choice = 0;
		printInstructions();
		while(!quit) {
			switch() {
				case 0:
					printInstructions();
					break;
				case 1:
					learnArrList.printAllEle();
					break;
				case 2:
					addItem();
					break;
				case 3:
					modifyItem();
					break;
				case 4:
					removeItem();
					break;
				case 5:
					searchForItem();
					break;
				case 6:
					quit = true;
					break;
			}
		}
	}
	
	public static void printInstructions() {
        System.out.println("\nPress ");
        System.out.println("\t 0 - To print choice options.");
        System.out.println("\t 1 - To print the list of arrList items.");
        System.out.println("\t 2 - To add an item to the arrList.");
        System.out.println("\t 3 - To modify an item in the arrList.");
        System.out.println("\t 4 - To remove an item from the arrList.");
        System.out.println("\t 5 - To search for an item in the arrList.");
        System.out.println("\t 6 - To quit the application.");
    }
	
	public static void addItem() {
		System.out.println("Please enter the item that you want to add!");
		learnArrList.addEle(scanner.nextLine());
	}
	
	public static void modifyItem() {
        System.out.print("Enter item number: ");
        int itemNo = scanner.nextInt();
        scanner.nextLine();
        System.out.print("Enter replacement item: ");
        String newItem = scanner.nextLine();
        learnArrList.updateEle(itemNo - 1, newItem);
    }
	
	public static void removeItem() {
        System.out.print("Enter item number: ");
        int itemNo = scanner.nextInt();
        scanner.nextLine();
        learnArrList.removeEle(itemNo - 1);
    }
	
	public static void searchForItem() {
        System.out.print("Item to search for: ");
        String searchItem = scanner.nextLine();
        if(learnArrList.findEle(searchItem) != null) {
            System.out.println("Found " + searchItem + " in  our arrList");
        } else {
            System.out.println(searchItem + " is not in the arrList");
        }
    }
}

最后,如果想要复制创建的arrList实例到新的ArrayList实例中,可以使用addAll方法并这样做:

在LearnArrayList中给arrList设置一个getter:


public ArrayList<String> getArrayList() {
	return arrList;
}
	

然后在Main类的main方法中:


ArrayList<String> newArrList = new ArrayList<String>();
newArrList.addAll(learnArrList.getArrayList());
// 或者是
ArrayList<String> newArrList = new ArrayList<String>(learnArrList.getArrayList());
	

如果还想要将ArrayList集合中的元素复制到数组中,还可以这样:


String[] newArr = new String[learnArrList.getArrayList().size()];
newArr = learnArrList.getArrayList().toArray(newArr);

四. 自动封箱(auto boxing)和拆箱(unboxing)

上面已经大致介绍了ArrayList的用法,但是ArrayList使用的时候有一个限制,就是其中的元素不能够是原始类型,例如:


ArrayList<int> arrListInt = new ArrayList<int>();

以上代码在IDEA中会提示:Type argument cannot be of primitive type.这是因为原始类型并不是类,而这里的type类型要求必须是类才可以。

既然没有类,我们可以创建一个整型类呀?例如:

public class IntClass {
	private int myValue;
	
	public int getMyValue() {
		return myValue;
	}
	
	public void setMyValue(int value) {
		this.myValue = value;
	}
}

然后,就可以把上面这个类用于ArrayList,当我们需要ArrayList形式的整型数集合时:


ArrayList<IntClass> intArrList = new ArrayList<IntClass>();
intArrList.add(new IntClass(42));

但是,这样每当我们需要创建原始类型的ArrayList集合时,都需要手动创建一个对应的类,这无疑是十分繁琐的。

所幸,JavaSE 5.0之后提供了基本类型的自动装箱(auto boxing)以及拆箱(unboxing)的功能,接下来就将介绍这两个特性:

实际上,Java内置的基本类型的装箱非常类似于我们前面编写的IntClass类。所有的原生类型本质上就是一个值而已(在内存中以特定的二进制序列表示),这些值因为不是对象,所以没有方法。而装箱的过程则可以理解为是通过创建一个对应的类“包裹住”(Wrapper)我们的原始类型,并在该类中定义一系列的方法,让它们去具有一些方法,能够执行一些特定的操作。

Java中内置了Integer,Double等原始类型对应的“包装类”,去完成装箱的操作。基本用法如下:


Integer myInt = new Integer(42);
Double myDouble = new Double(4.2);

有了以上内建的(built-in)包装类,我们在创建原始类型的ArrayList集合时就不用自行定义对应的类了,比如:


ArrayList<Integer> arrListInt = new ArrayList<Integer>();

调用add方法添加集合元素:

arrListInt.add(Integer.valueOf(42));

valueOf()会返回具体整型值(specified integer value)的整型实例(Integer Instance)。

调用get方法获取指定索引位置的集合元素:

arrListInt.get(0).intValue();

intValue()会返回该整型对象(Integer Instance)所代表(转换后)的整型数值(int)(原始类型)。

其实,以上的valueOf()操作执行的就是装箱(boxing)的过程,而intValue()执行的就是拆箱(unboxing)的过程。

那么什么是所谓的自动装箱(auto boxing)呢?自动装箱指代的是以下代码:

Integer myIntValue = 42;			// Integer = int

实际上会被编译为以下代码(自动装箱):


Integer myIntValue = Integer.valueOf(42);

而以下代码:


int valueInt = myIntValue;				// int = Integer

则会被编译为以下代码(自动拆箱):


int valueInt = myIntValue.intValue();

五. LinkedList

在介绍LinkedList之前,先看看数组的内部实现原理:

如上图所示,如果数组中的元素是基本类型(基本类型能够完全确定每个元素需要占据的内存空间),那么数组内部元素都是以连续的地址进行保存的。在索引数组中的某个具体值时,就可以结合数组对象的基址以及数组元素占据的大小(上图中的int类型是4个字节),再加上索引值,就能够立刻读取/写入对应的数组元素。

如果数组元素中保存的是不定长的对象,又是如何具体实现的呢?看下图:

可以发现,数组元素连续的内存空间中(以8个字节为单位)存储的不是具体的值,而是对应值的引用(即地址)。(右边表格中,如果某个元素的被引用数为0,则会被垃圾回收)

而ArrayList的内部原理也是一样的。这种通过开辟连续的内存空间保存值或引用的方式,尽管都有读写快的优点,但是在插入和删除元素时,就会变得十分低效(大数据量的情况下),例如:


ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
intList.add(2);
intList.add(3);
	
// 如果我们尝试在索引1的位置上插入一个元素,那么其后索引的所有元素都要下移
intList.add(1, 1);

// 如果我们想删除索引1的元素,那么其后索引的所有元素都要上移
intList.remove(1);

当我们的数组或者是ArrayList非常大的时候,需要移动元素的数量也是非常大的,这就是效率低的根源,也是通过连续内存空间存储元素的缺点。

为了能够解决数据集合插入,删除低效的问题,数据结构中提供了一种链表的结构,而LinkedList则是链表的Java语言实现:

通过采用链表的结构,我们的数据不一定需要存放在连续的内存空间中,如果是单链表,只要保证前一个元素节点中不仅保存元素的值(或引用),还保存索引其下一个元素的地址即可。这样即使元素集合非常大,插入和删除元素也非常简单,只需要修改对应索引位置保存的指向下一个元素的引用即可。不过,这种存储结构,虽然插入和删除元素的效率极高,但是读写速度肯定比不上连续的内存地址存储快(需要根据索引值逐个向下查找元素的位置)。

接下来,就介绍一下如何具体使用LinkedList,实际上LinkedList也是List接口的实现,而且使用方法也与ArrayList差不多:

package com.zhoujh;

import java.util.Iterator;
import java.util.LinkedList;

public class Demo {
	public static void main(String[] args) {
		LinkedList<String> placeToVisit = new LinkedList<String>();
		placeToVisit.add("Sydney");
		placeToVisit.add("China");
		placeToVisit.add("America");
		placeToVisit.add("Perth");
		placeToVisit.add("ShenZhen");
	
		printPlaceToVisit(placeToVisit);
		placeToVisit.add(1, "ChangChun");
		printPlaceToVisit(placeToVisit);
		placeToVisit.remove(4);
		printPlaceToVisit(placeToVisit);
	}
	
	private static void printPlaceToVisit(LinkedList<String> linkedList) {
		Iterator i = linkedList.iterator();
		while(i.hasNext()) {
			System.out.println(i.next());
		}
	}
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注