数组学习:
1.数组的特点
- 数组是引用数据类型,所以数组对象(动态初始化使用new关键字)是存放在堆内存中的。数组的父类是Object。
- 数组实际上是一种容器,可以存储一组数据。既可以存储基本数据类型,也可以存储引用数据类型。数组中存储 Java对象时,存储的是实际对象的引用(在堆内存中的地址),而不是具体的对象。数组不能直接存储实际对象。
- 数组一旦创建(不管是静态初始化还是动态初始化),其长度就不能再改变。
- 数组分为:一维数组,二维数组,多维数组…
- 数组对象自带length属性,数组名.length返回数组的长度
- 数组中的数据类型必须保持一致,和数组的声明类型一致
- 数组存储在一块连续的内存空间上,即数组元素的内存地址是连续的,数组是一种简单的数据结构。
- 数组名存放了数组首元素的地址,数组使用首元素的地址作为整个数组的内存地址。因为数组的内存空间是连续的,长度确定,只需要知道首地址即可。
- 数组有下标。从0~length-1。下标使得访问数组元素十分方便。
2.数组的优点和缺点
因为数组内存是一块连续的内存空间,数组元素的数据类型一致,数组有下标。使得我们访问(查找/存取)数组元素十分方便。即数组是一种随机存取的数据结构。只需要知道数组中某个元素的地址,此时访问其他元素只需进行相应的偏移即可。随机存取的时间复杂度是O(1)的。
数组中存储100个元素,或者存储100万个元素,在元素查询/检索方面,效率是相同的,
因为数组中元素查找的时候不会一个一个找,是通过数学表达式计算出来的。(算出一个
内存地址,直接定位的。)
同样,因为数组要求数组中元素的内存是连续的。在进行数组中非末尾元素的增加与删除时,必须移动改动位置后的每一个元素,一般来说时间复杂度是O(n)。
此外,数组的内存空间是连续的,当数组对象较多时,容易造成堆空间内存碎片较多。或者对于大容量的数组,很难在内存中找到一块足够大的连续空间。
3.一维数组的声明(定义)
int[] array;
double array[];
String[] array;
Person[] array;
......
4.一维数组的初始化
- 静态初始化
int[] arr = {1,2,3,4,5};
Object[] arr1 = {new Object(),new Object(),new Object()};
- 动态初始化
int[] arr = new int[length];//使用new关键字创建数组对象
- 什么时候静态初始化?什么时候动态初始化
当你创建数组的时候,确定数组中存储哪些具体的元素时,采用静态初始化方式。
当你创建数组的时候,不确定将来数组中存储哪些数据,你可以采用动态初始化的方式,预先分配内存空间。
5.传递参数是数组怎么传?
- 直接传递数组
package com.study.array;
public class ArrayTest01 {
//方法的参数是数组
public static void main(String[] args) {
String[] s = {"aaa","bbb","ccc"};
printArray(s);
}
public static void printArray(String[] str){//打印
for (String s :
str) {
System.out.println(s);
}
}
}
/*
aaa
bbb
ccc
*/
- 创建对象后直接初始化并传递
package com.study.array;
public class ArrayTest01 {
//方法的参数是数组
public static void main(String[] args) {
//静态初始化并传递
printArray(new String[]{"ddd","eee","fff"});
}
public static void printArray(String[] str){//打印
for (String s :
str) {
System.out.println(s);
}
}
}
/*
ddd
eee
fff
*/
6. main方法中的String[] args有什么用?
分析:谁负责调用main方法?
JVM 负责调用main方法,并且JVM调用main方法的时候,会自动传一个String数组过来。
测试一下String[] args这个数组:
package com.study.array;
public class ArrayTest02 {
public static void main(String[] args) {
System.out.println(args.length);
}
}
//0
输出的args数组的长度是0,所以args不是null。否则会有空指针异常。
args不是null就表示args这个引用实际已经指向了某一对象,即已经创建了一个String[]的对象。
这个数组什么时候里面会有值呢?
其实这个数组是留给用户的,用户可以在控制台上输入参数,这个参数自动会被转换为“String[] args”
例如这样运行程序:java ArrayTest05 abc def xyz
那么这个时候JVM会自动将“abc def xyz”通过空格的方式进行分离,分离完成之后,自动放到“String[] args”数组当中。
所以main方法上面的String[] args数组主要是用来接收用户输入参数的。
把abc def xyz 转换成字符串数组:{"abc","def","xyz"}
package com.study.array;
//在IDEA中设置带参数的运行
//java ArrayTest02 abc def sss rrr
public class ArrayTest02 {
public static void main(String[] args) {
System.out.println(args.length);
for (String s :
args) {
System.out.println(s);
}
}
}
/*
4
abc
def
sss
rrr
*/
使用String[] args数组写一个案例
package com.study.array;
//假设我们要写一个需要用户名和密码的系统如下
//利用String[] args数组
public class ArrayTest03 {
public static void main(String[] args) {
// // 用户名和密码输入到String[] args数组当中。
if(args.length != 2){
System.out.println("请输入账号密码");
return;
}
// 程序执行到此处说明用户确实提供了用户名和密码。
// 接下来你应该判断用户名和密码是否正确。
//判断两个字符串是否相等,需要使用equals方法。
//if(username.equals("admin") && password.equals("123")){
// 这样编写是不是可以避免空指针异常。
// 采用以下编码风格,即使username和password都是null,也不会出现空指针异常。(这是老程序员给的一条编程经验。)
if("admin".equals(args[0]) && "123".equals(args[1])){
System.out.println("欢迎使用本系统");
}else{
System.out.println("账号密码错误");
}
}
}
7.数组存储引用数据类型拓展
当数组存储引用类型数据时,存储的并不是实际的Java对象,实际上存储的是Java对象的“内存地址”,是指向对象的引用!
- 数组中的元素数据类型必须一致。但是,当数组中元素为引用类型时,数组中的元素可以是父子类关系:
package com.study.array;
public class ArrayTest04 {
public static void main(String[] args) {
Animal[] animals ={new Animal(),new Animal()};
//print(animals);
for (int i = 0; i < animals.length; i++) {
animals[i].move();
}
System.out.println("===============================");
Animal[] ans = new Animal[2];
//数组中的引用数据类型可以存储数组类型的子类对象(多态)
ans[0] = new Cat();
ans[1] = new Dog();
for (int i = 0; i < ans.length; i++) {
ans[i].move();
}
System.out.println("===============================");
//当数组中元素是子类对象时,调用子类独有的方法需要进行向下转型。向下转型需要进行判断
Animal[] animals1 = {new Cat(),new Dog()};
for (int i = 0; i < animals1.length; i++) {
if (animals1[i] instanceof Cat){
((Cat) animals1[i]).catchMouse();
}else if (animals1[i] instanceof Dog){
((Dog) animals1[i]).catchMouse();
}
}
}
public static void print(Object[] obj){
for (Object o : obj) {
System.out.println(o);
}
}
}
class Animal{
public void move(){
System.out.println("动物在移动!");
}
}
class Cat extends Animal{
@Override
public void move() {
System.out.println("猫猫冲击!");
}
public void catchMouse(){
System.out.println("猫抓老鼠!");
}
}
class Dog extends Animal{
@Override
public void move() {
System.out.println("汪汪队立大功!");
}
public void catchMouse(){
System.out.println("狗拿耗子!");
}
}
8.数组扩容与数组拷贝
在java开发中,数组长度一旦确定不可变,那么数组满了怎么办?
* 数组满了,需要扩容。
* java中对数组的扩容是:
* 先新建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数组当中。然后小数组被垃圾回收器回收。
*
* 结论:数组扩容效率较低。因为涉及到拷贝的问题。所以在以后的开发中请注意:尽可能少的进行数组的拷贝。
* 可以在创建数组对象的时候预估计一下多长合适,最好预估准确,这样可以减少数组的扩容次数。提高效率。
- 数组拷贝方法(5个参数)
public static native void arraycopy(Object src, int srcPos,
Object dest,
int destPos,
int length);
arraycopy(拷贝源,拷贝起始位置,目标源,目标起始位置,拷贝长度)
package com.study.array;
public class ArrayTest05 {
public static void main(String[] args) {
int[] src = {1,2,3,4,5};
int[] dest = new int[10];
System.arraycopy(src,0,dest,0,src.length);
for (int i = 0; i < dest.length; i++) {
System.out.print(dest[i]+"\t");
}
System.out.println();
System.out.println("=========================");
String[] strs = {"sss","aaa","bbb"};
String[] ss = new String[6];
System.arraycopy(strs,0,ss,0,strs.length);
print(ss);
System.out.println("=========================");
Object[] objs = {new Object(),new Object(),new Object()};
Object[] objects = new Object[6];
System.arraycopy(objs,0,objects,1,objs.length);
print(objects);
}
//遍历打印引用数据类型的数组
public static void print(Object[] obj){
for (Object o : obj) {
System.out.println(o);
}
}
}