详解JAVA高质量代码之数组与集合_java

  1.性能考虑,优先选择数组

  数组在项目开发当中使用的频率是越来越少,特别是在业务为主的开发当中,首先数组没有List,Set等集合提供的诸多方法,查找增加算法都要自己编写,极其繁琐麻烦,但由于List,Set等集合使用泛型支持后,存放的都为包装类,而数组是可以使用基本数据类型,而使用基本数据类型的执行运算速度要比包装类型快得多,而且集合类的底层也是通过数组进行实现.

  2.若有必要,使用变长数组

  在学习集合类当中,很多人喜欢将数组的定长拿来和集合类型的自变长来做比较,但其实这种比较并不合适,通过观察集合类例如ArrayList的实现其实可以看出,所谓的集合变长,其实只是用婉转的方式对原数组进行了扩容

  

复制代码 代码如下:

  public static T[] expandCapacity(T[] data, int newLength) {

  // 判断是否为负值

  newLength = newLength < 0 ? 0 : newLength;

  // 生成新数组,拷贝原值并制定长度

  return Arrays.copyOf(data, newLength);

  }
 

  当性能要求高的时候,可以考虑使用对数组进行封装使用,数组长度不变不是我们不使用它们的借口

  3.警惕数组的浅拷贝

  数组的浅拷贝在Java编程中亦是基础中的基础,浅拷贝是在为数组拷贝时,基本类型拷贝的是值,而引用类型拷贝的是引用地址,在上面的例子当中,拷贝数组使用的Arrays.copyOf为浅拷贝,在使用时需要注意

  4.在明确的场景下,为集合指定初始容量

  在我们平常的使用当中,因为集合类型是自动变长的,所以基本创建对象时不会为集合类附上初始值,就拿我们最常用的ArrayList来说明,我们首先要知道,当集合容量到达临界点时,会将底层的数组进行copyOf的操作,生成新的数组,而新的数组容量为旧数组的1.5倍,而默认数组长度为10,当我们明确知道要放置入容器中的数据数量较多时,应该指明初始值,避免多次使用copyOf造成的性能开销

  5.选择合适的最值算法

  对数据进行最大值或最小值的查找,这是数据结构最基本的知识,在Java当中我们亦有很多种的方式进行实现,以下列举2种算法

  

复制代码 代码如下:

  public static int getMaxByArray(int[] data) {

  // 最简单自行实现的查找方式

  int max = data[0];

  for (int i = 1, size = data.length; i < size; i++) {

  max = max < i ? i : max;

  }

  return max;

  }

复制代码 代码如下:

  public static int getMaxByArray(int[] data) {

  // 先排序后获取最后位

  Arrays.sort(data);

  return data[data.length - 1];

  }

  6.基本类型数组转换陷阱!

  请观察以下代码

复制代码 代码如下:

  public static void main(String[] args) {

  int[] nums = new int[] { 1, 2, 3, 4, 5 };

  List list = Arrays.asList(nums);

  System.out.println(list.size());

  // 此时输出的size为1

  }

  我们期望的结果是将数组中的元素通过Arrays.asList转换到集合类当中,但事与愿违,我们只将数组本身增加了进入,并没有将数组内的值分拆分开来,此时若然对集合List增加了泛型就会在编译期间给出错误的提示,或将数组本身改变成Integer就可以解决问题

  7.asList方法产生的List对象不可更改

  通过上面的例子,我们可以看到使用Arrays.asList方法可以将一个数组转换成一个List,那通过asList方法返回的List有什么特别呢?注意,这个返回的List是不支持更改的,原因是因为asList方法返回的,并不是java.util.ArrayList,而是Arrays工具类中的一个静态私有内部类,虽然都有实现和ArrayList一样的父类AbstractList,但在复写add等方法时,却是抛出了UnsupportedOperationException,这个静态私有内部类只实现了size,toArray,get,contains这几个方法

  8.对不同的数据结构使用不同的遍历方式

  请观看以下代码

复制代码 代码如下:

  public static void main(String[] args) {

  // 以下为ArrayList集合的遍历方式

  int num = 80 * 10000;

  List arrayList = new ArrayList(num);

  for (int i = 0, size = arrayList.size(); i < size; i++) {

  arrayList.get(i);

  }

  // 以下为LinkedList集合的遍历方式

  List linkedList = new LinkedList();

  for (Integer integer : linkedList) {

  }

  }

  为什么对LinkedList和ArrayList要选择不同的遍历方式?

  1.因为ArrayList实现了RamdomAccess接口(随机存取接口),RamdomAccess接口和Serializable,Cloneable接口一样是Java中的标示接口,代表这个这个类可以随机存取,对ArrayList来说就标志着,数据之间没有关联,即相邻的两个位置没有互相依赖的关系,可以随机访问,

  2.Java中的foreach语法是iterator(迭代器)的变形用法,我们知道迭代器是23种设计模式的一种,但迭代器是需要知道两个元素时间的关系的,不然怎么提供hasNext的支持呢?就是因为上一个元素要判断下一个元素是否存在,强行建立了这种关系,违背了ArrayList随机存取的特别

  3.在LinkedList中,因为是通过双向链表的形式来存储,所以对迭代器的支持非常好,因为LinkedList相邻的两个元素本来就存在关系所以在对LinkedList和ArrayList要采取不同的遍历方式,读者若然有兴趣可以尝试一下对LinkedList采用下标的形式访问,会发现两者的效率有较大的差距

  8.适时选择ArrayList或LinkedList

  ArrayList和LinkedList的主要区别:

  1.ArrayList底层的数据结构为数组,而LinkedList底层结构为双向链表

  2.在插入数据时,由于ArrayList每次插入后都需要将数组元素向后顺延位置,而LinkedList只需要更改头节点和尾节点即可完成插入操作,所以在插入操作较为频繁时,优先使用LinkedList

  3.在删除数据时,由于ArrayList要保持数组的有序性,当删除后元素要亦需要向后或向前移位,而LinkedList照旧还是更改头尾节点.

  4.在更新时,由于LinkedList会使用折半遍历的方式进行查找定位元素再进行更新,对比起ArrayList的直接定位下标元素替换,ArrayList对更新的效率更佳

  5.LinkedList可以模拟队列,通过LinkedList的addFirst,addLast等操作

  9.列表相等只需关心元素数据

  Java为了我们可以安心的面向List,Set,Map等接口进行编程,因此对集合类中的equlas进行了复写,让我们在比较两个集合是否相等时,只需要比较元素数据是否相等即可,避免了因为替换集合实现类造成的错误Java代码

复制代码 代码如下:

  public static void main(String[] args) {

  List arrayList = new ArrayList();

  arrayList.add(1);

  arrayList.add(2);

  List linkedList = new LinkedList();

  linkedList.add(1);

  linkedList.add(2);

  System.out.println(arrayList.equals(linkedList));

  // 不用关心具体实现,输出为true

  }

时间: 2025-01-30 18:09:07

详解JAVA高质量代码之数组与集合_java的相关文章

详解java动态代理的2种实现方式_java

java的动态代理在接java的api上有说明,这里就不写了.我理解的代理: 对特定接口中特定方法的功能进行扩展,这就是代理.代理是通过代理实例关联的调用处理程序对象调用方法. 下面通过一个例子看一下: 接口: public interface Num { void show(); int getNum(); int getProduct(int x); } 实现类: public class MyNum implements Num { @Override public int getNum(

详解Java正则表达式中Pattern类和Matcher类_java

前言 本文将介绍Java正则表达式中的Pattern类与Matcher类.首先我们要清楚指定为字符串的正则表达式必须首先被编译为pattern类的实例.因此如何更好的了解这两个类,是编程人员必须知道的. 以下我们就分别来看看这两个类: 一.捕获组的概念 捕获组可以通过从左到右计算其开括号来编号,编号是从1 开始的.例如,在表达式 ((A)(B(C)))中,存在四个这样的组: 1 ((A)(B(C))) 2 (A) 3 (B(C)) 4 (C) 组零始终代表整个表达式. 以 (?) 开头的组是纯的

详解Java的设计模式编程中的原型模式_java

定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象.类型:创建类模式类图: 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype.Prototype类需要具备以下两个条件: 实现Cloneable接口.在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法.在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException

详解Java中的时区类TimeZone的用法_java

一.TimeZone 简介TimeZone 表示时区偏移量,也可以计算夏令时. 在操作 Date, Calendar等表示日期/时间的对象时,经常会用到TimeZone:因为不同的时区,时间不同. 下面说说TimeZone对象的 2种常用创建方式.1.获取默认的TimeZone对象使用方法: TimeZone tz = TimeZone.getDefault() 2.使用 getTimeZone(String id) 方法获取TimeZone对象使用方法: // 获取 "GMT+08:00&qu

详解Java设计模式编程中的中介者模式_java

定义:用一个中介者对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互. 类型:行为类模式 类图: 中介者模式的结构       中介者模式又称为调停者模式,从类图中看,共分为3部分:  抽象中介者:定义好同事类对象到中介者对象的接口,用于各个同事类之间的通信.一般包括一个或几个抽象的事件方法,并由子类去实现. 中介者实现类:从抽象中介者继承而来,实现抽象中介者中定义的事件方法.从一个同事类接收消息,然后通过消息影响其他同时类. 同事类:

详解JAVA常用的时间操作【实用】_java

项目中经常有对时间进行处理的需求,下面是一些常用的操作整理,方便以后再次使用以及做相关复习. 1.字符串转换为日期 /** * 字符串转换为日期 * @param dateStr 需要转换的日期 * @param dateFormat 日期格式yyyy-MM-dd/yyyy-MM-dd HH:mm:ss */ public static Date toDate(String dateStr, SimpleDateFormat dateFormat) throws ParseException{

详解Java向服务端发送文件的方法_java

本文实例为大家分享了Java向服务端发送文件的方法,供大家参考,具体内容如下 /* *给服务端发送文件,主要是IO流. */ import java.io.*; import java.net.*; class send2 { public static void main(String[] args) throws Exception { Socket s = new Socket("192.168.33.1",10005);//建立服务 BufferedReader bufr =

详解Java编程中final,finalize,finally的区别_java

final:final可以让你控制你的成员.方法或者是一个类是否可被覆写或继承等功能,这些特点使final在Java中拥有了一个不可或缺的地位,也是学习Java时必须要知道和掌握的关键字之一. final成员 当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变.其初始化可以在两个地方,一是其定义处,二是在构造函数中,两者只能选其一. 下面程序很简单的演示了final的常规用法

详解Java设计模式编程中的依赖倒置原则_java

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象:抽象不应该依赖细节:细节应该依赖抽象. 问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成.这种场景下,类A一般是高层模块,负责复杂的业务逻辑:类B和类C是低层模块,负责基本的原子操作:假如修改类A,会给程序带来不必要的风险. 解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率.          依赖倒置原则基于这样一个事实:相