二十一、数组(1)

news2024/12/27 11:24:41

本章概要

  • 数组特性
    • 用于显示数组的实用程序
  • 一等对象
  • 返回数组

简单来看,数组需要你去创建和初始化,你可以通过下标对数组元素进行访问,数组的大小不会改变。大多数时候你只需要知道这些,但有时候你必须在数组上进行更复杂的操作,你也可能需要在数组和更加灵活的 集合 (Collection)之间做出评估。因此本章我们将对数组进行更加深入的分析。

注意: 随着 Java Collection 和 Stream 类中高级功能的不断增加,日常编程中使用数组的需求也在变少,所以你暂且可以放心地略读甚至跳过这一章。但是,即使你自己避免使用数组,也总会有需要阅读别人数组代码的那一天。那时候,本章依然在这里等着你来翻阅。

数组特性

明明还有很多其他的办法来保存对象,那么是什么令数组如此特别?

将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据类型的能力。在 Java 中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。

速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 ArrayList (来自 集合)开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个 ArrayList 的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。

数组和集合(Collections)都不能滥用。不管你使用数组还是集合,如果你越界,你都会得到一个 RuntimeException 的异常提醒,这表明你的程序中存在错误。

在泛型前,其他的集合类以一种宽泛的方式处理对象(就好像它们没有特定类型一样)。事实上,这些集合类把保存对象的类型默认为 Object,也就是 Java 中所有类的基类。而数组是优于 预泛型 (pre-generic)集合类的,因为你创建一个数组就可以保存特定类型的数据。这意味着你获得了一个编译时的类型检查,而这可以防止你插入错误的数据类型,或者搞错你正在提取的数据类型。

当然,不管在编译时还是运行时,Java都会阻止你犯向对象发送不正确消息的错误。然而不管怎样,使用数组都不会有更大的风险。比较好的地方在于,如果编译器报错,最终的用户更容易理解抛出异常的含义。

一个数组可以保存基本数据类型,而一个预泛型的集合不可以。然而对于泛型而言,集合可以指定和检查他们保存对象的类型,而通过 自动装箱 (autoboxing)机制,集合表现地就像它们可以保存基本数据类型一样,因为这种转换是自动的。

下面给出一例用于比较数组和泛型集合:

CollectionComparison.java

import java.util.*;

import static com.example.test.ArrayShow.show;

class BerylliumSphere {
    private static long counter;
    private final long id = counter++;

    @Override
    public String toString() {
        return "Sphere " + id;
    }
}

public class CollectionComparison {
    public static void main(String[] args) {
        BerylliumSphere[] spheres =
                new BerylliumSphere[10];
        for (int i = 0; i < 5; i++) {
            spheres[i] = new BerylliumSphere();
        }
        show(spheres);
        System.out.println(spheres[4]);

        List<BerylliumSphere> sphereList = Suppliers.create(
                ArrayList::new, BerylliumSphere::new, 5);
        System.out.println(sphereList);
        System.out.println(sphereList.get(4));

        int[] integers = {0, 1, 2, 3, 4, 5};
        show(integers);
        System.out.println(integers[4]);

        List<Integer> intList = new ArrayList<>(
                Arrays.asList(0, 1, 2, 3, 4, 5));
        intList.add(97);
        System.out.println(intList);
        System.out.println(intList.get(4));
    }
}

Suppliers.java

import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class Suppliers {
    // Create a collection and fill it:
    public static <T, C extends Collection<T>> C
    create(Supplier<C> factory, Supplier<T> gen, int n) {
        return Stream.generate(gen)
                .limit(n)
                .collect(factory, C::add, C::addAll);
    }

    // Fill an existing collection:
    public static <T, C extends Collection<T>>
    C fill(C coll, Supplier<T> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(coll::add);
        return coll;
    }

    // Use an unbound method reference to
    // produce a more general method:
    public static <H, A> H fill(H holder,
                                BiConsumer<H, A> adder, Supplier<A> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(a -> adder.accept(holder, a));
        return holder;
    }
}

在这里插入图片描述

Suppliers.create() 方法在泛型一章中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用 [ ] 来随机存取元素,而一个 List 使用诸如 add()get() 等方法。数组和 ArrayList 之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在集合中看到的,集合的功能明显多于数组。

随着 Java 自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。

用于显示数组的实用程序

在本章中,我们处处都要显示数组。Java 提供了 Arrays.toString() 来将数组转换为可读字符串,然后可以在控制台上显示。然而这种方式视觉上噪音太大,所以我们创建一个小的库来完成这项工作。

import java.util.*;

public interface ArrayShow {
    static void show(Object[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(boolean[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(byte[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(char[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(short[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(int[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(long[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(float[] a) {
        System.out.println(Arrays.toString(a));
    }

    static void show(double[] a) {
        System.out.println(Arrays.toString(a));
    }

    // Start with a description:
    static void show(String info, Object[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, boolean[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, byte[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, char[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, short[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, int[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, long[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, float[] a) {
        System.out.print(info + ": ");
        show(a);
    }

    static void show(String info, double[] a) {
        System.out.print(info + ": ");
        show(a);
    }
}

第一个方法适用于对象数组,包括那些包装基本数据类型的数组。所有的方法重载对于不同的数据类型是必要的。

第二组重载方法可以让你显示带有信息 字符串 前缀的数组。

为了简单起见,你通常可以静态地导入它们。

一等对象

不管你使用的什么类型的数组,数组中的数据集实际上都是对堆中真正对象的引用。数组是保存指向其他对象的引用的对象,数组可以隐式地创建,作为数组初始化语法的一部分,也可以显式地创建,比如使用一个 new 表达式。数组对象的一部分(事实上,你唯一可以使用的方法)就是只读的 length 成员函数,它能告诉你数组对象中可以存储多少元素。[ ] 语法是你访问数组对象的唯一方式。

下面的例子总结了初始化数组的多种方式,并且展示了如何给不同的数组对象分配数组引用。同时也可以看出对象数组和基元数组在使用上是完全相同的。唯一的不同之处就是对象数组存储的是对象的引用,而基元数组则直接存储基本数据类型的值。

import static com.example.test.ArrayShow.show;

public class ArrayOptions {
    public static void main(String[] args) {
        // Arrays of objects:
        BerylliumSphere[] a; // Uninitialized local
        BerylliumSphere[] b = new BerylliumSphere[5];

        // The references inside the array are
        // automatically initialized to null:
        show("b", b);
        BerylliumSphere[] c = new BerylliumSphere[4];
        for (int i = 0; i < c.length; i++) {
            if (c[i] == null) // Can test for null reference
            {
                c[i] = new BerylliumSphere();
            }
        }

        // Aggregate initialization:
        BerylliumSphere[] d = {
                new BerylliumSphere(),
                new BerylliumSphere(),
                new BerylliumSphere()
        };

        // Dynamic aggregate initialization:
        a = new BerylliumSphere[]{
                new BerylliumSphere(), new BerylliumSphere(),
        };
        // (Trailing comma is optional)

        System.out.println("a.length = " + a.length);
        System.out.println("b.length = " + b.length);
        System.out.println("c.length = " + c.length);
        System.out.println("d.length = " + d.length);
        a = d;
        System.out.println("a.length = " + a.length);

        // Arrays of primitives:
        int[] e; // Null reference
        int[] f = new int[5];

        // The primitives inside the array are
        // automatically initialized to zero:
        show("f", f);
        int[] g = new int[4];
        for (int i = 0; i < g.length; i++) {
            g[i] = i * i;
        }
        int[] h = {11, 47, 93};

        //  Compile error: variable e not initialized:
        //- System.out.println("e.length = " + e.length);
        System.out.println("f.length = " + f.length);
        System.out.println("g.length = " + g.length);
        System.out.println("h.length = " + h.length);
        e = h;
        System.out.println("e.length = " + e.length);
        e = new int[]{1, 2};
        System.out.println("e.length = " + e.length);
    }
}

在这里插入图片描述

数组 a 是一个未初始化的本地变量,编译器不会允许你使用这个引用直到你正确地对其进行初始化。数组 b 被初始化成一系列指向 BerylliumSphere 对象的引用,但是并没有真正的 BerylliumSphere 对象被存储在数组中。尽管你仍然可以获得这个数组的大小,因为 b 指向合法对象。

这带来了一个小问题:你无法找出到底有多少元素存储在数组中,因为 length 只能告诉你数组可以存储多少元素;这就是说,数组对象的大小并不是真正存储在数组中对象的个数。然而,当你创建一个数组对象,其引用将自动初始化为 null,因此你可以通过检查特定数组元素中的引用是否为 null 来判断其中是否有对象。基元数组也有类似的机制,比如自动将数值类型初始化为 0,char 型初始化为 (char)0,布尔类型初始化为 false

数组 c 展示了创建数组对象后给数组中各元素分配 BerylliumSphere 对象。数组 d 展示了创建数组对象的聚合初始化语法(隐式地使用 new 在堆中创建对象,就像 c 一样)并且初始化成 BeryliumSphere 对象,这一切都在一条语句中完成。

下一个数组初始化可以被看做是一个“动态聚合初始化”。 d 使用的聚合初始化必须在 d 定义处使用,但是使用第二种语法,你可以在任何地方创建和初始化数组对象。例如,假设 hide() 是一个需要使用一系列的 BeryliumSphere对象。你可以这样调用它:

hide(d);

你也可以动态地创建你用作参数传递的数组:

hide(new BerylliumSphere[]{
    new BerlliumSphere(),
    new BerlliumSphere()
});

很多情况下这种语法写代码更加方便。

表达式:

a = d;

显示了你如何获取指向一个数组对象的引用并将其分配给另一个数组对象。就像你可以处理其他类型的对象引用。现在 ad 都指向了堆中的同一个数组对象。

ArrayOptions.java 的第二部分展示了基元数组的语法就像对象数组一样,除了基元数组直接保存基本数据类型的值。

返回数组

假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对 C++/C 这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。

而在 Java 中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。

下面,我们返回一个 字符串 数组:

import java.util.*;

import static com.example.test.ArrayShow.show;

public class IceCreamFlavors {
    private static SplittableRandom rand =
            new SplittableRandom(47);
    static final String[] FLAVORS = {
            "Chocolate", "Strawberry", "Vanilla Fudge Swirl",
            "Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
            "Praline Cream", "Mud Pie"
    };

    public static String[] flavorSet(int n) {
        if (n > FLAVORS.length) {
            throw new IllegalArgumentException("Set too big");
        }
        String[] results = new String[n];
        boolean[] picked = new boolean[FLAVORS.length];
        for (int i = 0; i < n; i++) {
            int t;
            do {
                t = rand.nextInt(FLAVORS.length);
            }
            while (picked[t]);
            results[i] = FLAVORS[t];
            picked[t] = true;
        }
        return results;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 7; i++) {
            show(flavorSet(3));
        }
    }
}

在这里插入图片描述

flavorset() 创建名为 resultsString 类型的数组。 这个数组的大小 n 取决于你传进方法的参数。然后从数组 FLAVORS 中随机选择 flavors 并且把它们放进 results 里并返回。返回一个数组就像返回其他任何对象一样,实际上返回的是引用。数组是在 flavorSet() 中或者是在其他什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。

如果你必须要返回一系列不同类型的元素,你可以使用 泛型 中介绍的 元组

注意,当 flavorSet() 随机选择 flavors,它应该确保某个特定的选项没被选中。这在一个 do 循环中执行,它将一直做出随机选择直到它发现一个元素不在 picked 数组中。(一个字符串

比较将显示出随机选中的元素是不是已经存在于 results 数组中)。如果成功了,它将添加条目并且寻找下一个( i 递增)。输出结果显示 flavorSet() 每一次都是按照随机顺序选择 flavors。

一直到现在,随机数都是通过 java.util.Random 类生成的,这个类从 Java 1.0 就有,甚至更新过以提供 Java 8 流。现在我们可以介绍 Java 8 中的 SplittableRandom ,它不仅能在并行操作使用(你最终会学到),而且提供了一个高质量的随机数。这本书的剩余部分都使用 SplittableRandom

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1221425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu18.04安装Moveit框架

简介 Moveit是一个由一系列移动操作的功能包组成的集成化开发平台,提供友好的GUI,是目前ROS社区中使用度排名前三的功能包,Moveit包含以下三大核心功能,并集成了大量的优秀算法接口: 运动学:KDL,Trac-IK,IKFast...路径规划:OMPL,CHMOP,SBPL..碰撞检测:FCL,PCD... 一、更新功…

前段-用面向对象的方式开发一个水管小鸟的游戏

首先准备好各类空文件 index.js css html 和图片 图片是下面这些&#xff0c;如果没有的可在这里下载 2 开发开始 好了&#xff0c;基础准备工作完毕&#xff0c;开发开始&#xff0c; 首先&#xff0c;先把天空&#xff0c;大地&#xff0c;小鸟的盒子准备好&#xff0c;并…

基于探路者算法优化概率神经网络PNN的分类预测 - 附代码

基于探路者算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于探路者算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于探路者优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

main函数的数组参数是干嘛用的

今天在看项目代码的时候&#xff0c;突然看到项目中用到了main函数的参数args&#xff0c;在这之前我还没怎么注意过这个参数&#xff0c;一时间居然不知道这个参数是干嘛的&#xff01; 虽然也写过一些java和scala&#xff0c;但是确实没遇到过会用这个参数的情况。 网上就查…

【PIE-Engine 数据资源】中国叶面积指数(LAI)月度合成产品

文章目录 一、 简介二、描述三、波段四、示例代码运行结果参考资料 一、 简介 数据名称中国叶面积指数&#xff08;LAI&#xff09;月度合成产品时间范围2002-2021年空间范围全国数据来源航天宏图代码片段var images pie.ImageCollection(“EMDO/MODIS_MONTH_LAI_CHINA”) 二…

Arduino项目式编程教学前言

前言–先聊聊我的经历 在停更数年之后&#xff0c;还是打算重新开启Arduino编程教学这一项目&#xff1b;这几年间&#xff0c;我从Arduino编程开发教学&#xff0c;转到C及python教学&#xff0c;又到如今的高中数学教学&#xff0c;跨度竟如此之大&#xff0c;但始终未脱离教…

echarts 三角锥形柱状图 + 带阴影的折线图示例

该示例有如下几个特点&#xff1a; ①三角锥形折线图 ②折线图自带阴影 ③三角锥形鼠标放置时颜色改变 ④数据随着鼠标移动而展示 ⑤鼠标放置时tooltip样式自定义&#xff08;echarts 实现tooltip提示框样式自定义-CSDN博客&#xff09; 代码如下&#xff1a; this.options …

【GUI】-- 08 JButton、JRadioButton、JCheckBox

GUI编程 03 Swing 3.5 JButton 图片置于按钮之上的JButton&#xff1a; package com.duo.lesson05;import javax.swing.*; import java.awt.*; import java.net.URL;public class JButtonDemo01 extends JFrame {public JButtonDemo01() {Container contentPane getConten…

opencv(4):颜色空间

文章目录 颜色空间RGB 人眼的色彩空间HSV 色彩空间HSLYUVYUV420&#xff1a;YUV422&#xff1a;YUV444&#xff1a; 颜色空间转换代码示例 颜色空间 不同色彩空间显示效果是不一样的。 RGB 人眼的色彩空间 HSV 色彩空间 HSV 代表色相&#xff08;Hue&#xff09;、饱和度&a…

YOLO改进系列之注意力机制(EffectiveSE模型介绍)

模型结构 ESE(Effective Squeeze and Extraction) layer是CenterMask模型中的一个block&#xff0c;基于SE&#xff08;Squeeze and Extraction&#xff09;改进得到。与SE的区别在于&#xff0c;ESE block只有一个fc层&#xff0c;(CenterMask : Real-Time Anchor-Free Insta…

2019年12月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 以下程序执行后,角色面向的方向是? A:右上 B:右下 C:左上 D:左下 答案:B 面向-135度,是面向左下角,向右旋转-90度等于向左旋转90度。所以会旋转到右下角。 第2题 以下程…

WordPress网站迁移实战经验

前几日,网站服务器到期,换了服务商,就把我的WordPress的网站迁移到本地电脑了。方便以后文章迁移。 本次迁移网站主要经历以下几个步骤。 1.域名转出。 2.备份数据库及网站文件下载。 3.重新搭建WordPress网站。 4.网站文件及数据库导入。 下面详细介绍下每个步骤的操作…

基于STC12C5A60S2系列1T 8051单片的IIC总线器件数模芯片PCF8591实现数模转换应用

基于STC12C5A60S2系列1T 8051单片的IIC总线器件数模芯片PCF8591实现数模转换应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍IIC总线器件数模芯片PCF8591介绍通过按…

RabbitMQ 安装及配置

前言 当你准备构建一个分布式系统、微服务架构或者需要处理大量异步消息的应用程序时&#xff0c;消息队列就成为了一个不可或缺的组件。而RabbitMQ作为一个功能强大的开源消息代理软件&#xff0c;提供了可靠的消息传递机制和灵活的集成能力&#xff0c;因此备受开发人员和系…

CMakeLists.txt基础指令与cmake-gui生成VS项目的步骤

简介 本博客主要介绍cmake的基本指令&#xff0c;同时&#xff0c;很多使用Visual Studio小白从Gitbub下载项目源码后&#xff0c;看到CMakeLists.txt&#xff0c;不知道如何使用Visual Studio编译源码&#xff1b;针对以上问题&#xff0c;做一下简单操作与解释&#xff0c;方…

各类好玩免费API推荐,早知道就不用到处爬数据了...

啥是API/接口&#xff1f; 使用方法 API平台 淘宝/京东API接口数据 一些好玩的api 地图接口 天气查询接口 快递查询接口 笔记接口 其它接口 小结 啥是API/接口&#xff1f; 有些读者刚开始学习编程遇到API或者接口不太明白到底什么意思&#xff0c;没关系&#xff0…

Java项目实战《苍穹外卖》 一、项目概述

人道洛阳花似锦&#xff0c;偏我来时不逢春。 系列文章目录 苍穹外卖是黑马程序员2023年的Java实战项目&#xff0c;作为业余练手用&#xff0c;需要源码或者课程的可以找我&#xff0c;无偿分享 Java项目实战《苍穹外卖》 一、项目概述Java项目实战《苍穹外卖》 二、项目搭建 …

PyTorch - 高效快速配置 Conda + PyTorch 环境 (解决 segment fault )

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/134463035 在配置算法项目时&#xff0c;因网络下载速度的原因&#xff0c;导致默认的 conda 与 pytorch 包安装缓慢&#xff0c;需要配置新的 co…

使用VMware安装linux虚拟机

文章目录 一、介绍二、下载VMware三、下载centOS镜像文件四、新建虚拟机1. 选择配置类型2. 安装centOS操作系统3. 命名4. 指定磁盘容量5. 虚拟机配置信息确认6. 稍后选择系统镜像 五、安装虚拟机1. 选择语言2. 设置时间、系统、以及yum源3. 开始安装4. 设置root用户密码5. 完成…

Angular菜单项激活状态保持

菜单项激活状态保持 需求描述详细需求 解决方案 需求描述 如果有个需求&#xff0c;让你实现一个导航栏&#xff0c;点击不同菜单&#xff0c;图表会有不同变化&#xff0c;页面刷新后&#xff0c;该菜单状态仍旧保持&#xff0c;实现方法如下&#xff1a; 例图&#xff0c;有…