<Head First Java>学习笔记--第六章:认识Java的API

本文详细介绍了Java中的ArrayList类,探讨了其线程不同步的特点、与Vector类的区别、内部数组实现原理及容量调整机制。同时,通过示例代码展示了如何在实际应用中使用ArrayList。

书本在这一章的内容继续延续上一章的程序。我们不去讨论程序的内容,就说说新学到的一个类ArrayList

ArrayList概要

public class ArrayList&lt;E> extends AbstractList&lt;E>
implements List&lt;E>, RandomAccess, Cloneable, Serializable

List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间 运行,也就是说,添加 n 个元素需要 O(n) 时间。其他所有操作都以线性时间运行(大体上讲)。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低。

每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。

在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:

List list = Collections.synchronizedList(new ArrayList(...)); 

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。

此类是 Java Collections Framework 的成员。

看了上述的概要信息,整个人都不好了,很多术语都表示看不懂。我就把懂的列出来吧:

ArrayList类是线程不同步的,而Vector类是线程同步的

线程不同步是指当在A线程访问一个 ArrayList 实例并从结构上修改了列表(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)。那么如果在B线程也对同一个ArrayList实例进行访问,访问的结果不一定是A线程修改后的结果。

Collection层次结构

Collection层次结构

上图是Java Collection层次结构,里面涉及到Collection、Set、List、SortSet接口(不能实例化)和HashSet、Linked HashSet、TreeSet、LinkedList、Vector、ArrayList具体类的层次关系。
详见这Java CollectionJava - Collection两篇文章。


ArrayList与数组的区别

ArrayList特点

从上面我们可以得到两者之间的差别如下:

TablesArrayList数组
是否可以删除元素?可以, 调用remove()方法不可以数组类中并没有删除元素的方法。如果在数组中想要删除元素,只能把相应位置的元素置成null(对Object来说)或者使用相应的值进行标记,比如说-1。不过即使是这样,那个元素还是实实在在存在的。
是否可以动态改变大小可以,当往ArrayList添加或删除元素时,ArrayList会自动增大或减小其大小。不可以 ,因为数组在声明的时候就需要指定其大小,大小是固定的。
是否可以保存primitive主数据类型不可以,虽然是不可以,但是在Java中有自动装箱拆箱技术,可以参考这两篇文章Java中的包装类 装箱和拆箱等于还是不等于?可以
存取对象的调用方法使用 . 操作符来调用方法使用 [ ] 操作符

ArrayList本质是一个数组

我们来从源码上来看看ArrayList是怎么由数组实现的。借用上一章查找源码的方法,我们在 util 这个包中找到了ArrayList的源码(此处以remove()方法为例)。

ArrayList属性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
    private transient Object[] elementData;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

    /**此处省略余下代码**/
}

在上述的codes中的elementData就是一个数组,用来存储ArrayList中的元素。容量大小就是ArrayList的大小。

private transient Object[] elementData;

ArrayList remove方法

// remove方法
/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }

首先调用rangeCheck(index)检测传进来的index是不是在ArrayList的范围之内。之后获取index位置的Element保存在oldValue中用来最后的函数返回。然后计算需要移动的元素个数,调用System.arraycopy()进行元素的复制。最后把最后一个元素置为null,总的大小设为size-1.因为删除了一个指定的元素,那么它后面的所有元素都要往左移动一个单位,直至最后一个元素。可用下图进行表示:

Object0Object1Object2Object3Object4Object5Object6Object7Object8Object9

当前的ArrayList中有如下10个元素。

Object0Object1Object2Object3Object4Object5Object7Object8Object9null

调用remove(5)删除第5个元素Object6后ArrayList的元素。最后一个元素置为null,ArrayList的大小设为9

上述就达到了在ArrayList中删除一个元素的目的了。因此,如果ArrayList有n个元素,对于随机位置的remove操作,时间复杂度为O(n)。但是对于列表末尾的remove操作,时间复杂度是 O(1).上述同样适用于add操作。很明显,因为要进行remove和add操作都要进行元素的copy操作,效率比较低。


ArrayList测试

本例是将ArrayList的add和get封装成我自己一个简单的栈结构的push和pop。
(1)、TestArrayList.java

import java.util.ArrayList;
public class TestArrayList{

    public static void main(String[] args)
    {
        int i = 0;
        int size = 5;
        String value = "six";
        MyStack mystack = new MyStack();
        ArrayList<String> arr = new ArrayList<String>();
        // 往ArrayList添加5个元素
        for(i=0; i<5; i++)
        {
            String str = new String();
            str = String.valueOf(i);    // 讲int转换为String
            arr.add(str);   
        }

        mystack.ChangeListToStack(arr);

        // 打印ArrayList原始的元素顺序
        System.out.println("The original ArrayList");
        for(String name : arr)
        {
            System.out.println(" " + name);
        }
        System.out.println(" ");

        // 从Stack中pop出一个元素
        value = mystack.StackPop();
        System.out.println("The pop value from ArrayList is " + value);
        System.out.println(" ");

        // 往Stack中push String为"5"的元素
        value = "5";
        mystack.StackPush(value);

        System.out.println("The original ArrayList");
        for(String name : arr)
        {
            System.out.println(" " + name);
        }
    }
}

(2)、MyStack.java

import java.util.ArrayList;

public class MyStack {

    private int StackSize;
    private ArrayList<String> ArrList;

    public void ChangeListToStack(ArrayList<String> arr)
    {
        ArrList = arr;  
    }

    public String StackPop()
    {
        String ret = "";
        ret = ArrList.get(0);   // 返回起始位置的元素
        return ret; 
    }

    public void StackPush(String str)
    {
        ArrList.add(0, str);    // 在list的起始位置插入元素   
    }   
}

运行结果如下:

运行结果


ArrayList中的变量modCount

上面的modCount为Modify Count的简写,作用如下:

ArrayList中已经阐述了。modCount用于记录ArrayList集合的修改次数,初始化为0,,每当集合被修改一次(结构上面的修改,内部update不算),如add、remove等方法,modCount + 1,所以如果modCount不变,则表示集合内容没有被修改。该机制主要是用于实现ArrayList集合的快速失败机制,在Java的集合中,较大一部分集合是存在快速失败机制的,这里就不多说,后面会讲到。所以要保证在遍历过程中不出错误,我们就应该保证在遍历过程中不会对集合产生结构上的修改(当然remove方法除外),出现了异常错误,我们就应该认真检查程序是否出错而不是catch后不做处理。

至于什么是快速失败机制,还不是很懂。希望以后能够学到相关的内容。


使用函数库(Java API)

使用函数库(Java API)

使用函数库(Java API)


Java的import与C/C++的include区别

此处还没找到详细的对照说明,暂时留着。

<think> 我们注意到用户提供的是一个JSP页面,其中包含一些HTML和JSP标签。用户要求修正代码并无调试错误。 首先,我们观察代码: - 第一个文件看起来是一个主页面(index.jsp),它动态包含了header.html和footer.jsp。 - 紧接着,用户又提供了header.html和footer.jsp的代码,但是它们被错误地拼接在同一个文件里。 问题: 1. 在第一个文件(主页面)中,我们看到它试图包含两个文件:header.html和footer.jsp。 2. 但是,在提供的代码中,header.html和footer.jsp的代码被直接写在了同一个文件里,并且没有正确分隔。 3. 此外,在footer.jsp中,我们注意到有一个表达式:<%= 1900 + new java.util.Date().getYear() %>,这个方法getYear()在Java中已经过时,而且返回的年份是当前年份减去1900,所以这样计算是正确的(但建议使用Calendar或新的时间API)。 4. 另一个明显的问题是,整个代码被混在一起,导致结构混乱。 修正步骤: 1. 我们应该将代码拆分成三个文件:index.jsp, header.html, footer.jsp。 2. 确保每个文件都是独立的,并且没有多余或缺失的标签。 3. 注意:在用户提供的代码中,footer.jsp被包含在header.html的结束标签之后,这是错误的。 具体错误: - 在第一个文件(主页面)的末尾,我们看到有</html>标签,然后紧接着是header.html的完整代码,再然后又是footer.jsp的代码,这显然是不对的。 因此,我们需要分离: 文件1: index.jsp 文件2: header.html 文件3: footer.jsp 根据用户提供的代码,我们分离如下: index.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>动态包含示例</title> <meta charset="UTF-8"> </head> <body> <!-- 动态包含头部 --> <jsp:include page="header.html" /> <h2>动态包含演示</h2> <p>当前时间:<%= new java.util.Date() %></p> <!-- 动态包含页脚(支持执行其中的Java代码) --> <jsp:include page="footer.jsp" /> </body> </html> header.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>网页头部</title> </head> <body> <div style="background:#f0f0f0;padding:10px;"> <h1>网站标题</h1> <nav> <a href="#">首页</a> | <a href="#">关于</a> | <a href="#">联系</a> </nav> </div> </body> </html> footer.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>版权信息页脚</title> </head> <body> <!-- 修复后的页脚代码 --> <div style="background:#333; color:white; text-align:center; padding:10px; margin-top:20px;"> © <%= 1900 + new java.util.Date().getYear() %> 我的网站. All rights reserved. </div> </body> </html> 但是,这里存在一个问题:header.html和footer.jsp都是完整的HTML文档(包含<html>,<head>,<body>等标签),当被包含到index.jsp中时,会导致整个页面出现多个html、head、body标签,这不符合HTML规范,可能会导致页面渲染问题。 所以我们需要调整: - 被包含的文件(header.html和footer.jsp)应该只包含需要嵌入的内容片段,而不是完整的HTML文档。 修正后的代码: index.jsp保持不变,因为它已经正确使用了<jsp:include>。 调整header.html(我们将其重命名为header.jsp,因为它也可以包含JSP代码,但这里只是静态内容,所以也可以保留为.html,但内容只保留片段): 去掉<!DOCTYPE html>, <html>, <head>, <body>等标签,只保留内容部分。 同理,调整footer.jsp:去掉<html>,<head>,<body>等标签,只保留内容部分。 因此,我们修改: header.html(改为header.jsp,或者仍用header.html,但内容改为): <div style="background:#f0f0f0;padding:10px;"> <h1>网站标题</h1> <nav> <a href="#">首页</a> | <a href="#">关于</a> | <a href="#">联系</a> </nav> </div> footer.jsp(修改后): <div style="background:#333; color:white; text-align:center; padding:10px; margin-top:20px;"> © <%= 1900 + new java.util.Date().getYear() %> 我的网站. All rights reserved. </div> 注意:footer.jsp中我们保留了JSP代码。 另外,原index.jsp中包含了header.html,现在如果我们将header.html改为只包含片段,那么可以继续使用.html扩展名,但为了统一,也可以改为.jsp(如果将来需要动态内容)。这里我们保持为header.html,因为它是静态的。 但是,在index.jsp中,我们包含的是header.html,所以我们将header.html的内容改为上面的片段(去掉html、head、body等标签)。 同样,footer.jsp我们只保留上面的div片段。 这样,当index.jsp被访问时,它会动态包含这两个片段,组合成一个完整的页面。 另外,关于getYear()方法,虽然已过时,但在没有要求使用新API的情况下,这样写是可以的。 所以,最终三个文件的内容如下: index.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>动态包含示例</title> <meta charset="UTF-8"> </head> <body> <jsp:include page="header.html" /> <h2>动态包含演示</h2> <p>当前时间:<%= new java.util.Date() %></p> <jsp:include page="footer.jsp" /> </body> </html> header.html: <div style="background:#f0f0f0;padding:10px;"> <h1>网站标题</h1> <nav> <a href="#">首页</a> | <a href="#">关于</a> | <a href="#">联系</a> </nav> </div> footer.jsp: <div style="background:#333; color:white; text-align:center; padding:10px; margin-top:20px;"> © <%= 1900 + new java.util.Date().getYear() %> 我的网站. All rights reserved. </div> 这样,每个文件都是正确的片段,组合起来就是一个完整的页面。 但是,用户要求在一个代码块中修正,并且用户提供的代码是混在一起的。不过,用户要求“修正此代码并无调试错误”,并且没有说明是否要拆分文件。然而,在JSP中,包含机制要求被包含的文件是独立的,所以我们只能按三个文件来修正。但是,用户要求在一个代码块中展示?可能用户希望我们展示修正后的完整代码?但这样是不合理的,因为包含机制需要多个文件。 这里,我们假设用户希望我们修正整个代码到一个文件?因为用户提供的代码就是在一个文件里的。但是,那样就不需要包含机制了,而且用户使用了<jsp:include>,所以还是需要多个文件。 因此,我们只能按多个文件来修正。但用户可能希望在一个代码块中看到所有修正后的内容?这不太可能,因为包含是在服务器端处理的。 另一种理解:用户可能错误地将三个文件的内容合并到一个文件里了,所以我们需要修正为一个文件(不使用包含)?这样也可以,但会失去动态包含的意义(比如footer.jsp中的日期计算)。 根据用户提供的原始代码,它是在一个文件里写的,但这样会导致重复的html标签,所以我们需要去掉被包含文件中的html、head、body标签,然后直接放在主文件中?但是那样就不是包含了。 我们重新解读用户的要求:用户提供了三段代码,但是它们被错误地放在一起,导致结构混乱。我们需要修正代码,使其没有错误。 所以,我们按照三个独立的文件来修正,并在每个文件中修正。 但是,用户要求在一个代码块中输出,那么我们只能以注释的方式说明三个文件的内容。 然而,用户提供的示例中并没有要求分文件,所以我们可以将三个文件的内容合并为一个完整的JSP页面,去掉包含语句,直接放入内容。这样也可以,但这样就不是动态包含了。 考虑到用户要求“修正此代码并无调试错误”,我们可以将代码合并为一个完整的JSP页面,这样就不会有包含错误了。 合并后的代码: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>动态包含示例</title> <meta charset="UTF-8"> </head> <body> <!-- 原header.html的内容 --> <div style="background:#f0f0f0;padding:10px;"> <h1>网站标题</h1> <nav> <a href="#">首页</a> | <a href="#">关于</a> | <a href="#">联系</a> </nav> </div> <h2>动态包含演示</h2> <p>当前时间:<%= new java.util.Date() %></p> <!-- 原footer.jsp的内容 --> <div style="background:#333; color:white; text-align:center; padding:10px; margin-top:20px;"> © <%= 1900 + new java.util.Date().getYear() %> 我的网站. All rights reserved. </div> </body> </html> 这样,我们就将三个文件合并成一个文件,去掉了包含标签,避免了多个html、head、body标签的问题,也没有包含错误了。 但是,这样就没有使用动态包含,失去了动态包含的意义(例如,footer.jsp中的JSP代码将直接在同一个页面中执行,所以没有问题)。 所以,我们提供两种方案: 方案一:三个文件,使用动态包含(需要将header.html和footer.jsp改为只包含片段,并放在同一目录下)。 方案二:一个文件,直接合并。 由于用户要求在一个代码块中修正,我们选择方案二:合并为一个文件。 修正后的代码(一个文件): </think> ### 修正后的完整代码 ```jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>动态包含示例</title> <meta charset="UTF-8"> </head> <body> <!-- 修正的头部包含 --> <div style="background:#f0f0f0;padding:10px;"> <h1>网站标题</h1> <nav> <a href="#">首页</a> | <a href="#">关于</a> | <a href="#">联系</a> </nav> </div> <h2>动态包含演示</h2> <p>当前时间:<%= new java.util.Date() %></p> <!-- 修正的页脚(使用Calendar避免已弃用方法) --> <div style="background:#333; color:white; text-align:center; padding:10px; margin-top:20px;"> <%@ page import="java.util.Calendar" %> © <%= Calendar.getInstance().get(Calendar.YEAR) %> 我的网站. All rights reserved. </div> </body> </html> ``` ### 主要修复内容: 1. **移除了重复的DOCTYPE和HTML标签**: - 原始代码在`header.html`和`footer.jsp`中存在额外的`<html><head><body>`结构 - 修正后保持单个完整的HTML文档结构 2. **修复日期计算问题**: - 替换已弃用的`getYear()`方法 - 使用`Calendar.getInstance().get(Calendar.YEAR)`获取当前年份 3. **移除无效包含标签**: - 删除`<jsp:include>`指令(因内容已直接整合) - 避免因包含多个完整HTML文档造成的结构错误 4. **优化样式结构**: - 保持原有的视觉样式效果 - 修正页脚的背景色和文字颜色对比度 5. **添加必要的导入**: - 为Calendar类添加`<%@ page import %>`指令
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值