【Java编程思想】11.持有对象

如果一个程序只包含固定数量的且生命周期都是已知的对象,那么这是一个非常简单的程序。

Java 类库中提供一套容器类,来存储比较复杂的一组对象。其中有 ListSetQueueMap 等。这些类也被称为集合类,Java 的类库中使用 Collection 这个名字指代该类库的一个特殊子集(其实 Java 中大部分容器类都实现了 Collection 接口)。


11.1 泛型和类型安全的容器

在 Java SE5 之前的容器,编译器是允许向容器中插入不正确的类型的。因此在获取容器中对象时,一旦转型就会抛出一个异常。

一个类如果没有显式地声明继承自哪个类,那么他就自动地继承自 Object,因此对于容器来说,添加不同的类,无论是编译器还是运行期都不会有问题,问题在于使用容器中存储的对象时,会引发意想不到的错误。

因此,Java SE5引入了泛型的支持(15章有详解)。通过使用泛型,可以在编译期防止将错误类型的对象放置到容器中。

List<Apple> list = new ArrayList();

在定义泛型之后,从容器中取出对象时,容器会直接转成泛型对应的类型。同时向上转型也可以作用在泛型上。


11.2 基本概念

Java 容器类库划分为两种:

  1. Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List 必须按照插入的顺序保存元素,Set 不能有重复元素,Queue 按照派对规则来确定对象产生的顺序(通常与其元素被插入的顺序相同)。
  2. Map。一组成对的键值对对象,允许使用键来查找值。映射表允许我们使用另一个对象来查找某个对象,它也被称作关联数组,因为他将某些对象与另外一些对象关联在了一起;或者被称为字典,因为你可以使用键对象来查找值对象

使用容器的时候,可能有些情况不需要使用向上转型的方式,例如 LinkedList 具有 List 接口中未包含的方法;TreeMap 具有 Map 中未包含的方法,因此如果要使用这些方法,就不能将他们向上转型为接口。

所有的 Collection 都可以用 foreach 语法遍历。


11.3 添加一组元素

添加一组元素有多种方法:

  • Arrays.asList() 方法,接收一个数组或者是逗号分隔的元素列表(使用可变参数),并将其转换为一个 List 对象。
  • Collections.addAll() 方法,接收一个 Collection 对象,以及一个数组或是用逗号分隔的列表,并将元素添加到 Collection 中。

两者的使用都有限制,Arrays.asList() 方法的输出在底层的表示是数组,因此不能调整尺寸。而 Collections.addAll() 方法只能接受另一个 Collection 对象作为参数。

像如下这种初始化,可以告诉编译器,由 Arrays.asList() 方法产生的 List 类型(实际的目标类型)。这种成为显式类型参数说明

List<Snow> snow4 = Arrays.<Snow>asList(new Light(), new Heavy());

11.4 容器的打印

数组的打印必须借助 Arrays.toString() 来表示(或者遍历打印);但是容器的打印可以直接使用 print(默认就能生成可读性很好的结果)。

  • Collection 在每个“槽”中只能保存一个元素

    • List 以特定的顺序保存一组元素。
    • Set 元素不能重复。
    • Queue 只允许在容器的一“端”插入对象,并从另一“端”移出对象。
  • Map 在每个“槽”内保存两个对象,即和与之相关联的

关于各种容器的实现类型特点:

  • ArrayList:按插入顺序保存元素,性能高
  • LinkedList:按插入顺序保存元素,性能略低于 ArrayList
  • HashSet:最快的获取元素方式
  • TreeSet:按照比较结果升序保存对象,注重存储顺序
  • LinkedHashSet:按照添加顺序保存对象
  • HashMap:提供最快的查找技术,没有特定顺序
  • TreeMap:敖钊比较结果的升序保存键
  • LinkedHashMap:按照插入顺序保存键

11.5 List

  • ArrayList:擅长于随机访问元素,在 List 的中间插入和移出元素时较慢。
  • LinkedList:通过代价较低的在 List 中间进行的插入和删除操作,提供优化的顺序访问。另外其特性集更大。

与数组不同,List 允许在他被创建之后添加元素、移除元素或者自我调整尺寸,是一种可修改的序列。

关于 List 的方法:

  • contains():确定对象是否在列表中
  • remove():从列表中移出元素
  • indexOf():获取对象在列表中的索引编号
  • subList():从较大的列表中创建出一个片段
  • containsAll():判断片段是否包含于列表
  • retainAll():求列表交集
  • removeAll():移出指定的全部元素
  • replace():在指定索引处,用指定参数替换该位置的元素
  • addAll():在初始列表中插入新的列表
  • isEmpty():校验列表是否为空
  • clear():清空列表
  • toArray():列表转数组

11.6 迭代器

迭代器(也是一种设计模式)是一个对象那个,它的工作室遍历并选择序列中的对象,使用者不需要关心该序列的底层结构。

Java 中有迭代器 Iterator,只能单向移动,Iterator 只能用来:

  1. 使用方法 iterator() 要求容器返回一个 IteratorIterator 将准备好返回序列的第一个元素。
  2. 使用 next() 获得序列中的下一个元素。
  3. 使用 hasNext() 检查序列中是否还有元素。
  4. 使用 remove() 将迭代器新近返回的元素删除。

使用 Iterator 时不需要关心容器中的元素数量,只需要向前遍历列表即可。

Iterator 可以移出有 next() 产生的最后一个元素,这意味着在调用 remove() 之前需要先调用 next()

ListIterator 是加强版的 Iterator 子类型,只能用于 List 类的访问

  • 区别于 IteratorListIterator 是可以双向移动的
  • 还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引
  • 可以使用 set() 方法替换它访问过的最后一个元素
  • 可以通过调用 listIterator() 方法产生一个指向 List 开始处的 ListIterator
  • 还可以通过调用 listIterator(n) 方法创建一个一开始就指向列表索引为 n 的元素处的 ListIterator

11.7 LinkedList

LinkedList 在执行插入和删除时效率更高,随机访问操作方面要逊色一些。

除此之外,LinkedList 还添加了可以使其用作栈、队列和双端队列的方法。

  • getFirst()/element():返回列表头,列表为空是抛出 NoSuchElementException
  • peek():返回列表头,列表为空返回 null。
  • removeFirst()/remove():移出并返回列表头,列表为空是抛出 NoSuchElementException
  • poll():移出并返回列表头,列表为空返回 null。
  • addFirst()/add()/addLst():将某元素插入到列表尾部。
  • removeLast():移出并返回列表最后一个元素。

LinkedListQueue 的一个实现。对比 Queue 接口,可以发现 QueueLinkedList 的基础上添加了 element()offer()peek()poll()remove()方法。


11.8 Stack

通常是指”后进先出”(LIFO)的容器。有时栈也被称为叠加栈,因为最后压栈的元素最先出栈。

LinkedList 具有能够直接实现一个栈的所有功能和方法,因此可以直接将 LinkedList 作为栈使用。

public class Stack<T> {
    private LinkedList<T> storage = new LinkedList<T>();
    public void push(T v) { storage.addFirst(v); }
    public T peek() { return storage.getFirst(); }
    public T pop() { return storage.removeFirst(); }
    public boolean empty() { return storage.isEmpty(); }
    public String toString() { return storage.toString(); }
}

11.9 Set

Set 是基于对象的值来确定归属性的,具有与 Collection 完全一样的接口(实际上 Set 就是 Collection,只是行为不同--一种继承与多态的典型应用,表现不同的行为)。

  • HashSet 使用了散列(更多17章介绍),因此存储元素没有任何规律。
  • TreeSet 将元素存储在红黑树数据结构中,输出结果是排序的。默认按照字典序排序(A-Z,a-z),如果想按照字母序排序(Aa-Zz),可以指定 new TreeSet<String>(String.CASE_INSENSITIVE_ORDER)
  • LinkedHashSet 使用了散列,但是也用了链表结构来维护元素的插入顺序。

11.10 Map

get(key) 方法会返回与键关联的值,如果键不在容器中,则返回 null。

containKey()containValue() 可以查看键和值是否包含在 Map 中。

Map 实际上就是讲对象映射到其他对象上的工具。


11.11 Queue

队列是一个典型的先进先出(FIFO)的容器。从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。

队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径-->例如并发编程中,安全的将对象从一个任务传输给另一个任务。

LinkedListQueue 的一个实现,可以将其向上转型为 Queue

Queue 接口窄化了对 LinkedList 的方法的访问权限,以使得只有恰当的方法才可以使用,因此能够访问的 LinkedList 的方法会变少。

队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个元素应该是等待时间最长的元素。

优先级队列声明下一个弹出元素时最需要的元素(具有最高的优先级)。PriorityQueue 提供了这种实现。当在 PriorityQueue 上调用 offer() 方法插入一个对象时,这个对象会在队列中被排序(通常这类队列的实现,会在插入时排序-维护一个堆-但是他们也可能在移出时选择最重要的元素)。默认的排序会使用对象在队列中的自然顺序,但是可以通过提供自己的 Comparator 来修改这个顺序。 这样能保证在对 PriorityQueue 使用 peek()poll()remove() 方法时,会先处理队列中优先级最高的元素。


11.12 Collection 和 Iterator

Collection 是描述所有序列容器的共性的根接口。java.util.AbstractCollection 类提供了 Collection 的默认实现,可以创建 AbstarctCollection 的子类型,其中不会有不必要的代码重复。

实现了 Collection 意味着需要提供 iterator() 方法。

(这章没太看懂,回头再看一遍)


11.13 Foreach 与迭代器

foreach 是基于 Iterator 接口完成实现的,Iterator 接口被 foreach 用来在序列中移动。任何实现了 Iterator 接口的类,都可以用于 foreach 语句。

System.getenv() 方法可以返回一个 MapSystem.getenv().entrySet() 可以产生一个有 Map.Entry 的元素构成的 Set,并且这个 Set 是一个 Iterable,因此它可以用于 foreach 循环。

foreach 语句可以用于数组或其他任何实现了 Iterable 的类,但是并不意味这数组肯定也是一个 Iterable,而且任何自动包装都不会自动发生。



在类实现 Iterable 接口时,如果想要添加多种在 foreach 语句中使用这个类的方法(在 foreach 中的条件中使用类的方法),有一种方案是适配器模式。-->在实现的基础上,增加一个返回 Iterable 对象的方法,则该方法就可以用于 foreach 语句。例如

// 添加一个反向的迭代器
class ReversibleArrayList<T> extends ArrayList<T> {
    public ReversibleArrayList(Collection<T> c) { super(c); }
    public Iterable<T> reversed() {
        return new Iterable<T>() {
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    int current = size() - 1;
                    @Override
                    public boolean hasNext() { return current > -1; }
                    @Override
                    public T next() { return get(current--); }
                    @Override
                    public void remove() { // Not implemented
                        throw new UnsupportedOperationException();
                    }};
            }};
    }
}
// 调用
for(String s : ral.reversed()) {
    System.out.print(s + " ");
}


使用 Collection.shuffle() 时,可以看到包装与否对于结果的影响。

Integer[] ia = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<Integer> list1 = new ArrayList<>(Arrays.asList(ia));
System.out.println("Before shuffling: " + list1);
Collections.shuffle(list1, rand);
System.out.println("After shuffling: " + list1);
System.out.println("array: " + Arrays.toString(ia));

List<Integer> list2 = Arrays.asList(ia);
System.out.println("Before shuffling: " + list2);
Collections.shuffle(list2, rand);
System.out.println("After shuffling: " + list2);
System.out.println("array: " + Arrays.toString(ia));

输出:

Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
After shuffling: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9]
array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
After shuffling: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]
array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]

从结果中可以看到,Collection.shuffle() 方法没有影响到原来的数组-->只是打乱了列表中的引用。

如果用 ArrayListArrays.asList(ia) 方法产生的结果包装起来,那么只会打乱引用;而不包装的时候,就会直接修改低层的数组。因此 Java 中数组和 List 的本质是不同的。


11.14 总结

Java 提供的持有对象的方式:

  1. 数组:数组保存明确类型的对象,查询对象的时候不需要对结果做类型转换。数组可以是多维的,也可以保存基本类型数据。但是数组的容量,在生成后就不能改变了。
  2. Collection/Map:Collection 保存单一的元素,Map 保存关联的键值对。在使用了泛型指定了容器存放的对象类型后,查询对象时便也不需要进行类型转换。CollectionMap 的尺寸是动态的,不能持有基本类型(但是自动包装机制-装箱-会将存入容器的基本类型进行数据转换)。
  3. List:数组和 List 都是排好序的容器,但是 List 能够自动扩充容量。大量随机访问使用 ArrayList,插入删除元素使用 LinkedList
  4. Queue:各种 Queue 以及栈的行为,由 LinkedList 提供支持。
  5. Map:Map 是一种将对象与对象相关联的设计。快速访问使用 HashMap,保持的排序状态使用 TreeMap,但是速度略慢,保持元素插入顺序以及快速访问能力使用 LinkedHashMap
  6. Set:Set 不接收重复元素。需要快速查询使用 HashSet,保持元素排序状态使用 TreeSet,保持元素插入顺序使用 LinkedHashSet
  7. 有一部分的容器已经过时不应该使用:VectorHashtableStack

下面是 Java 容器的简图:

  • 黑框:常用容器
  • 点线框:接口
  • 实线框:普通的(具体的)类
  • 空心箭头点线:一个特定的类实现了接口
  • 实心箭头:某个类可以生成箭头所指向类的对象

原文地址:https://www.cnblogs.com/chentnt/p/9791903.html

时间: 2024-10-29 17:00:18

【Java编程思想】11.持有对象的相关文章

Java编程思想(八) —— 持有对象(1)

书中的原标题是--holding your object,把握你的对象,译者翻译成持有对象.这是用的最多的类之一. 作者说的,如果一个程序包含固定数量的且其生命周期都是已知的对象,那么这是一个非常简单的程序.确实,如果数组大小已知,那么就很简单了. 除了数组,Java提供了容器类来holding object. 1)泛型和类型安全的容器 ArrayList,可以自动扩充大小的数组,add插入对象,get访问对象,size查看对象数目. class Apple{} public class Box

Java编程思想(九) —— 持有对象(2)

11)Map 作者说将对象映射到其他对象的能力是解决编程问题的杀手锏. 确实,例如查看随机数的分布,如果真是随机数的话,那么10000次产生20以内的随机数,每个数字出现的次数应该是相近的. public class TestMap { public static void main(String[] args) { Map<Integer,Integer> map = new HashMap<Integer,Integer>(); Random r = new Random(47

Java编程思想笔记(对象)

今天是七夕,猿选择拜读圣经.    抽象过程:(1)万物皆为对象.(2)程序是对象的集合(它们通过发送消息来告知彼此所要做的,要想请求一个对象,就必须对该对象发送一条消息.)(3)每个对象都有自己的由其他对象所构成的存储.(包)(4)每个对象都拥有其类型(可以发送什么样的消息给它)(5)某一特定类型的所有对象都可以接收同样的消息. 每个对象都有一个接口(class):每个对象都只能满足某些请求,这些请求由对象的接口所定义,决定接口的便是类型.接口确定了对某一特定对象所能发出的请求. 每一个对象都

学习java编程思想 第一章 对象导论

一.面向对象的五个基本特性: 1.万物皆为对象.将对象视为奇特的变量,他可以存储数据,还可以要求它在自身上执行操作. 2.程序是对象的合集,他们通过发送消息告诉彼此所要做的. 3.每个对象都有自己的由其他对象所构成的存储.换句话说,可以通过创建包含现有对象的包的形式来创建新类型的对象. 4.每个对象否拥有其类型.每个类最重要的特性就是"可以发送什么样的消息给它". 5.某一特定类型的所有对象都可以接受同样的消息. 二.在试图开发或理解一个程序设计时,最好的方法之一就是将对象想象为&qu

Java 编程思想 Chapter_14 类型信息

本章内容绕不开一个名词:RTTI(Run-time Type Identification) 运行时期的类型识别 知乎上有人推断作者是从C++中引入这个概念的,反正也无所谓,理解并能串联本章知识才是最重要的 本章的内容其实都是为类型信息服务的,主要内容有 一.Class对象 问题: 1.Class对象的创建过程是怎么样的 2.Class对象有哪几种创建方式,之间有什么差异 3.使用泛型 在了解类型信息之前,需要了解class对象 创建class对象,需要先查找这个这个类的.class文件, 查找

Java编程思想总结(一)对象导论

Java编程思想总结(一)对象导论 1.1 抽象过程 万物皆对象. 程序是对象的集合(即:类),他们通过发送消息(调用方法)来告知彼此要做的. 每个对象都有自己的由其他对象所构成的存储(引用其他对象或基本类型,即组合). 每个对象都拥有其类型.每个类最重要的区别于其他类的特征就是“可以发送什么样的消息给它”(即调用方法). 某一特定类型的所有对象都可以接受同样的消息. 对象具有状态(成员属性).行为(成员方法)和标识(引用的名称).每个对象在内存中都有唯一的一个地址. 1.2 每个对象都有一个接

【Java编程思想】一、对象导论

作为一个电子专业的人,在学习了将近3年的嵌入式技术后,决定投奔移动互联网,在互联网大潮中急流勇进! 为了学习OOP(Object-oriented Programming),为了转向移动互联网,我决定开始学习android开发,那么就从Java开始吧! Java的学习资料很多,在研究几天之后,决定从<Java编程思想>这本书开始. 而在这本书之前,我已经看完了一个培训导师的Java4Android的Java教学视频,看的很快,因为我学过C和C++. 但我的Java水平依旧很差,主要在于面向对象

Java编程思想 4th 第2章 一切都是对象

Java是基于C++的,但Java是一种更纯粹的面向对象程序设计语言,和C++不同的是,Java只支持面向对象编程,因此Java的编程风格也是纯OOP风格的,即一切都是类,所有事情在类对象中完成. 在Java中,使用引用来操纵对象,在Java编程思想的第四版中,使用的术语是"引用(reference)",之前有读过Java编程思想第三版,在第三版中,使用的术语是"句柄(handle)",事实上,我觉得第三版的术语"句柄"更加形象传神,就像你用一个

java 编程思想 22.11: java bean 案例代码

java 编程思想  22.11:   java bean 案例代码 thinking in java 4免费下载:http://download.csdn.net/detail/liangrui1988/7580155 package org.rui.swing.bean; import java.awt.Color; import java.awt.event.ActionListener; import java.awt.event.KeyListener; import org.rui.

1.JAVA 编程思想——对象入门

对象入门 欢迎转载,转载请标明出处:    http://blog.csdn.net/notbaron/article/details/51040219 如果学JAVA,没有读透<JAVA 编程思想>这本书,实在不好意思和别人说自己学过JAVA.鉴于此,蛤蟆忙里偷闲,偷偷翻看这本传说中的牛书. 面向对象编程OOP具有多方面吸引力.实现了更快和更廉价的开发与维护过程.对分析与设计人员,建模处理变得更加简单,能生成清晰.已于维护的设计方案. 这些描述看上去非常吸引人的,不过蛤蟆还是没啥印象(至少到