1、数组是保存一组对象的最有效的方式。但数组有固定的尺寸而受限(p216)
2、数组与其他种类的容器之间的区别有三方面:效率、类型和保存基本类型的能力。在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速。但是为这种速度所付出的代价就是数组对象的大小被固定,并且在其生命周期中不可改变。
3、无论使用哪种类型的数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,这个(数组)对象用以保存指向其他对象的引用。
4、对象数组和基本类型数组在使用上几乎是相同的,唯一的区别就是对象数组保存的是引用,基本类型数组直接保存基本类型的值。新建一个对象数组未赋值时自动初始化为null,同样基本类型的数组如果是数值型的,就被自动初始化为0,如果是字符型(char)的,就被自动初始化为(char)O,如果是布尔型(boolean),就被自动初始化为false。
5、多维数组
//创建多维数组 public static void main(String[] args) { int[][] a = {{ 1, 2, 3}, { 4, 5, 6}}; System.out.println(Arrays.deepToString(a)); //deepToString返回指定数组“深层内容”的字符串表示形式。如果数组包含作为元素的其他数组, //则字符串表示形式包含其内容等。此方法是为了将多维数组转换为字符串而设计的。 int[][][] b = new int[2][2][4]; System.out.println(Arrays.deepToString(b)); } public static void main(String[] args) { Integer[][] a; a = new Integer[6][]; for (int i = 0; i < a.length; i++) { a[i] = new Integer[3]; for (int j = 0; j < a[i].length; j++) a[i][j] = i * j; // Autoboxing } System.out.println(Arrays.deepToString(a)); <span style="font-family:Microsoft YaHei;">//</span>Output: [[0, 0, 0], [0, 1, 2], [0, 2, 4], [0, 3, 6], [0, 4, 8], [0, 5, 10]] }
6、数组与泛型
通常,数组与泛型不能很好地结合。你不能实例化具有参数化类型的数组,擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。但是,你可以参数化数组本身的类型:
//类泛型 class ClassParameter<T> { public T[] f(T[] arg) { return arg; } } class MethodParameter { // 方法泛型 public static <T> T[] f(T[] arg) { return arg; } } public class ParameterizedArrayType { public static void main(String[] args) { Integer[] ints = { 1, 2, 3, 4, 5 }; Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 }; Integer[] ints2 = new ClassParameter<Integer>().f(ints); Double[] doubles2 = new ClassParameter<Double>().f(doubles); ints2 = MethodParameter.f(ints); doubles2 = MethodParameter.f(doubles); System.out.println(Arrays.deepToString(ints2)); } }
Arrays有六个基本方法:
(1)equals()用于比较两个数组是否相等(deepEquals()用于多维数组),
(2)fill()
(3)sort()用于对数组排序
(4)binarySearch()使用二分搜索法在已经排序的数组中查找元素,必须在进行此调用前对数组进行排序,如果没有对数组进行排序,则结果是不确定的。如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。如果元素包含在数组中,则返回搜索元素的索引,否则返回搜索键的索引(好像就是得到搜索数组的长度的负值);否则返回(-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为a.length。注意,这保证了当且仅当此键被找到时,返回的值将 >= 0 。
(5)toString()产生数组的String表示
(6)hashCode()产生数组的散列码
(7)asList()转变成List容器
(8)copyOf
(9)System.arraycopy(),该方法不会执行自动包装和自动拆包,两个数组必须具有相同的确切类型。
8、利用Comparable进行比较
利用该接口来进行比较很简单,只需实现该接口然后实现compartTo方法即可。如果当前对象小于参数则返回负值,如果相等则返回零,如果当前对象大于参数则返回正值。
集合类,也称Java容器类
1、Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念。(p219)
(1)Collection。该容器类类库包括List、Set、Queue。List必须按照插入的顺序保存元素,Set不能有重复的元素,Queue按照排队规则来确定对象产生的顺序(通常与他们被插入的顺序相同)。
(2)Map。它被称为“关联数组”,因为它将某些对象与另外一些对象关联在一了一起。Map是强大的编程工具。
2、Collection接口概括了序列的概念(一种存放一组对象的方式。)(p219)
3、每个java.util容器都有其自己的Abstract类,它们提供了该容器的部分实现。
4、为了创建只读的Map,可以继承AbstractMap并实现entrySet(),
为了创建只读的Set,可以继承AbstractSet并实现iterator()和size(),
为了创建只读的List,可以继承AbstractList并实现get()和size()。
3、在java.util包中的Arrays和Collections类中都有很多实用方法。
public static void main(String[] args) { Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5,6,7,8,9,0)); Integer[] moreInts = {43,82,98,90,21}; //Arrays.asList()方法接收一个数组或是一个用逗号分隔的元素列表(使用可变参数) --> 转成List对象 collection.addAll(Arrays.asList(moreInts)); //Collections.addAll()方法接受一个Collection对象,以及一个数组或者是可变参数,将元素添加到Collection中 Collections.addAll(collection, 11,12,34,843,90,80,3); Collections.addAll(collection, moreInts); List<Integer> list = Arrays.asList(73,92,84,93,26,78,79); list.set(1, 0);//将索引为1的元素的值修改为0 }4、完整的容器分类
由上图可以看出,Hashtable、Vector、Stack的特征是,它们是过去遗留下来的类,目的是为了支持老的程序(最好不要在新的程序中使用它们)
不同类型的Queue只在它们接受和产生数值的方式上有所差异
1、ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时比较慢(p223)
2、LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。但是在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
LinkedList还添加了可以使其用作栈、队列或双端队列的方法。(p228)
LinkedList具有能够直接实现栈的所有功能的方法
3、如果要进行大量的随机访问,就使用ArrayList;如果要经常从表中间插入或删除元素,则应该使用LinkedList。(p245)
4、Arrays.asList会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作,因此add等操作方法会抛出一个不可选操作的异常。我们应该吧Arrays.asList的结果作为参数传递给任何Collection(或者使用addAll()方法,或Collections.addAll()静态方法),这样可以生成允许使用所有的方法的普通容器。
1、“栈”通常是指“后进先出”(LIFO)的容器。有时栈也被称为叠加栈,因为最后“压入”栈的元素,第一个弹出栈。
1、
Set不能保存重复的元素,Set中最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。正因为如此,查找就成为了Set中最重要的操作。Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像List会有两个不同的List。实际上Set就是Collection,只是行为不同。Set是基于对象的值来确定归属的 (p231)
(1)HashSet无序的,查找元素最快;HashSet使用的是相当复杂的方式来存储元素的(p222),出于速度的原因,HashSet使用了散列。HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为他们的实现具有不同的元素的存储方式。TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数,LinkedHashSet因查询速度的原因,也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
(2)TreeSet它按照比较结果的升序保存对象,保持元素处于排序状态;
(3)LinkedHashSet,它按照被添加的顺序保存对象,以插入顺序保存元素。
1、Map也被称为关联数组,就像一个简单的数据库。
Map是一种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
如何解释散列?(p245)Map保存的顺序并不是他们插入的顺序;
HashMap也提供了最快的查找技术,无序保存,键和值在Map中的保存顺序并不是他们的插入顺序,因为HashMap实现使用的是一种非常快的算法来控制顺序;
TreeMap按照比较结果的升序保存键;
LinkedHashMap按插入顺序保存键,同时还保留了HashMap的查询速度。
1、Queue:队列是一个典型的先进先出(FIFO)的容器。即从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。
队列在并发编程中特别重要,因为他们可以安全地将对象从一个任务传输到另一个任务。
LinkedList提供了方法以支持队列的行为,并且它实现了Queue的接口,(p236)
各种Queue以及栈的行为,由LinkedList提供支持(p245)
public static void main(String[] args) { Queue<Integer> queue = new LinkedList<Integer>(); Random rand = new Random(47); for(int i=0; i<10; i++){ queue.offer(rand.nextInt(i + 10));//offer方法是一个元素插入到队尾或者返回false } while(queue.peek() != null){//peek获取栈顶第一元素,pop弹栈,push压栈 System.out.println(queue.remove() + " "); } }
2、先进先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。
先进先出声明的是下一个元素应该是等待时间最长的元素。
优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。(p237)
1、迭代器也是一种设计模式,迭代器通常被称为轻量级对象:创建它的代价小。(p226)
2、Java的Iterator只能单向移动
public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(100, "jack"); map.put(200, "marry"); map.put(300, "sisi"); /* 1、Set迭代 */ System.out.println("******Set迭代******"); Set<Integer> set = new HashSet<Integer>(); Collections.addAll(set, 100,200,300); Iterator<Integer> setIt = set.iterator(); while (setIt.hasNext()) { Integer key = setIt.next(); System.out.print(key + "\t"); } /* 2、Map迭代一:keySet */ System.out.println("\r\r******Map迭代:keySet******"); Set<Integer> keySet = map.keySet();// 将Map集合的Key转成Set集合,再通过key获取对应的值 Iterator<Integer> map1It = keySet.iterator(); while (map1It.hasNext()) { Integer key = map1It.next(); String value = map.get(key); System.out.print(key + "-" + value + "\t"); } /* 3、Map迭代二:entrySet */ System.out.println("\r\r******Map迭代:entrySet******"); Set<Entry<Integer, String>> entrySet = map.entrySet(); Iterator<Entry<Integer, String>> it = entrySet.iterator(); while (it.hasNext()) { Entry<Integer, String> entry = it.next(); Integer key = entry.getKey(); String value = entry.getValue(); System.out.print(key + "<->" + value + "\t"); } /* 4、Map迭代三:增强for循环 */ System.out.println("\r\r******Map迭代:增强for循环******"); for(Integer key : map.keySet()){//注意这里Key的类型是由map的泛型决定,否则是object System.out.print(key + ":" + map.get(key) + "\t"); } /* 5、增强for循环迭代List */ List<Department> newDeptList = new ArrayList<Department>(); List<String> deptName = new ArrayList<String>(); for(Department dept:deptList){ if(!deptName.contains(dept.getDepartmentName())){ deptName.add(dept.getDepartmentName()); newDeptList.add(dept); } } }输出结果:
3 、ListIterator
ListIterator是一个更加强大的Iterator的子类型,是一个接口,它只能用于各种List类的访问。Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。你可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。
public static void main(String[] args) { List<String> list = new ArrayList<String>(); Collections.addAll(list, "java","c","c++","oracle","mysql"); ListIterator<String> it = list.listIterator(); while(it.hasNext()){ System.out.println(it.next() + " " + it.nextIndex() + ", " + it.previousIndex() + ";"); } System.out.println(); //Backwards while(it.hasPrevious()){ System.out.print(it.previous() + " ");//previous前一个元素 } }
public static void main(String[] args) { List<String> list = new ArrayList<String>(); Collections.addAll(list, "jack","marry","sisi"); /* 1、使用iterator动态添加数据 */ Iterator<String> it = list.iterator(); while(it.hasNext()){ String key = it.next();//这行在第二次循环时会出错,因为在创建迭代器的时候(it = list.iterator()),list通知迭代器的是长度为3,而在执行完list.add(“qq”)时,list长度已经是4,但是没有及时通知迭代器,所以会出错。 System.out.print(key+"\t"); // list.add("qq");//迭代时向List集合中添加数据,在第一次循环最后会想list中添加数据,在验证iterator动态添加数据时需吧这行注释去掉 } System.out.println("\r**************************"); /* 2、使用ListIterator动态添加数据 */ ListIterator<String> it2 = list.listIterator();//ListIterator while (it2.hasNext()) { String key = it2.next(); System.out.print(key + "\t"); // 动态通知迭代器,加入了新元素,从而迭代器自动通知List集合。 it2.add("qq");//现在添加进去的数据,在下次迭代时才会被迭代出来 } System.out.println("\r**************************"); /* 3、再次对集合迭代,验证数据是否被添加进去 */ ListIterator<String> it3 = list.listIterator(); while (it3.hasNext()) { String key = it3.next(); System.out.print(key + "\t"); } }
输出结果:
1、新程序中不应该使用过时的Vector、Hashtable 和Stack。(p245)
3、程序的输出是从Object默认的toString()方法产生的,该方法将打印类名,后面跟随该对象的散列码的无符号十六进制表示(这个散列码是通过hashCode()方法产生的)。你将在第17章中了解到有关散列码的内容(p218)
4、优化是一个很棘手的问题,最好的策略就是置之不顾,直到你发现需要担心它了(尽管理解这些问题总是一种好的思路)
5、通过针对接口而非具体实现来编码,我们的代码可以应用与更多的对象类型。(p239)
6、将保持不变的事物与会发生改变的事物相分离
7、Collections常用方法(不包括Object继承而来的方法),下列方法也是可通过Set或List执行的所有操作(List还有额外的功能),Map不是继承自Collection会另行介绍。