定义
数据介绍
如果有一个程序需要有若干个相同类型的变量进行运算,那么我们以一个一个定义的方式显然是低效的,这就需要引入一个新的类型变量了——数组。
数组是一组相同的数据类型元素的集合。(数据元素包括:八大基本数据类型; 引用类型; 数组; 字符串【引用】)
在Java中数组属于引用类型,即一个数组就是一个对象。
数组的声明
数组声明方式非常简单,格式为:
数据类型 [ ] 数组名 ;
或者 数据类型 数组名 [ ] ;
数组的定义方式:
数据类型 [ ] 数组名 = new 数据类型 [ 数组长度];
或者 用 数据类型 数组名 [ ] = new 数组类型 [ 数组长度 ]; 但我们一般不这么用,因为第一种定义我们可以在一行中声明多个数组,而第二种方式则不行,所以后文中,我们使用第一种方式定义。
例:
int [] IntArrays0; //数组的声明
int [] IntArrays = new int[N]; //数组的定义
double [] DoubleArrays = new double[N];
float [] FloatArrays = new float[N];
String [] Strings = new String[N];
char [] CharArrays = new char[N];
// 其中 N 为数组长度,注意这表示真实长度,从 1 开始
//自定义类型class的数组
class Test
{
//属性
//方法
}
Test [] TestArrays = new Test[N]; //对象数组,其中每一个元素都为一个对象
数组的初始化
初始化
我们上文的数组定义代码貌似和之前介绍的数组声明有所不同。这是为什么嘞?
原来,我们在定义的同时完成了两个操作,分别是数组的声明和数组的初始化。简单点说,就是我们不光说明了我有一个数组,还顺便说明了数组它应该能装多少东西。
所以,我们给出初始化的介绍:所谓初始化就是给数组元素分配内存,并且与C语言不同,在进行内存分配的同时Java还会给元素及逆行赋初始值的操作(一般对于数字来说是赋零值)
需要注意的是:
1、数组的初始化是必须的,即数组必须在初始化后才能使用。 数组是引用类型,我们必须为它分配实际内存,这样引用的指向才能有意义(虽然不会像C一样出现指针错误使用的问题就是了,毕竟是实时解析式的语言…)
2、数组的大小是确定的,即数组的大小在它定义时就确定了,不可以在后期改变,除非重新初始化。 所以在日常使用时,一定要在定义时明确知道所需的数组的空间大小,在调用数组时不要超出索引的最大值。值得一提的是,Java可以使用较为灵活的方式完成数组大小定义,我们可以输入一个数赋值给一个变量,再使用这个变量作为数组长度(N)完成数组的初始化。
由上文的代码我们不难总结出初始化数组的范式:
new 数据类型 [数组长度];
但是,这并不是数组唯一的初始化方式,数组初始化还可以这么来:
int [] IntArrays = {1, 2, 3, 4, 5, 6}
int [] IntArrays1 = new int[]{1, 2, 3,4 5, 6}
//验证有效性代码
System.out.println("The Arrays length is " + IntArrays.length); //The Arrays length is 6
System.out.println("The Arrays length is " + IntArrays1.length); //The Arrays length is 6
需要说明的是两种方法从结果上是一致的,与new关键字的初始化不同在于可以直接同时进行了对应位的数值赋值。
引用类型
最通俗易懂的一句介绍为:一种更为安全的指针!
但显然的,很多同学读到这句话仍然是一头雾水,并恼火道:什么玩意啊,就这还最通俗易懂,我完全不道你在说啥啊。
哈哈,被骗了吧,之前我才接触Java的时候有在论坛上看到有很多同学有这方面的疑问的时候,我就明白了,并不是所有的Java初学者都是在有C语言的基础上进行学习的,所以很多教程会不自觉的默认大家都学好了C…导致在引用类型的介绍上有所欠缺。接下来我详细介绍一下Java的引用类型。
基本介绍
在Java中,引用类型是指除了基本数据类型(如int, double等)之外的所有类型,通常是通过class定义的类型。引用类型的变量存储的是对象在堆内存中的地址,而不是实际的数据值。这意味着当你创建一个对象时,Java会在堆内存中为其分配空间,并给你一个指向那块内存的引用,你可以通过这个引用来操作对象。
Java中的引用类型主要分为四种:
- 强引用(Strong Reference): 这是最常见的引用类型,只要强引用存在,垃圾回收器就不会回收该对象。例如:
StringBuffer buffer = new StringBuffer();
上面代码中,buffer是对StringBuffer对象的强引用。
- 软引用(Soft Reference): 比强引用弱,只有在内存不足时,垃圾回收器才可能回收软引用指向的对象。软引用是用于描述一些还有用但非必需的对象。例如:
SoftReference<String> softRef = new SoftReference<String>(new String("hello"));
在上面的代码中,softRef 是对字符串 "hello" 的软引用。
- 弱引用(Weak Reference): 比软引用更弱,垃圾回收器可以在任何时候回收弱引用指向的对象。只要垃圾回收器工作,就会回收只被弱引用关联的对象… 例如:
WeakReference<String> weakRef = new WeakReference<String>(new String("hello"));
在上面的代码中,weakRef 是对字符串 "hello" 的弱引用。
- 虚引用(Phantom Reference): 最弱的一种引用类型,它的存在主要是为了在对象被垃圾回收时进行一些特殊处理。一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用来获取对象实例。虚引用主要用于跟踪对象被垃圾回收的活动。例如:
PhantomReference<Object> phantomRef = new PhantomReference<Object>
在上面的代码中,phantomRef 是对一个新对象的虚引用。
使用
在Java中,引用类型的使用方法涉及到对象的创建、操作和内存管理。一些基本的使用方法介绍:
创建对象 在Java中,使用new关键字来创建对象,并将其引用赋值给一个变量。例如:
String str = new String("Hello");
这里,str是对新创建的String对象的引用。
访问对象 通过引用,我们可以访问对象的属性和方法。例如:
str.length(); // 调用String对象的length方法
引用传递 在Java中,方法参数传递的是引用的副本。这意味着方法内部对引用的修改不会影响原始引用。例如:
public void modifyReference(StringBuffer buffer) {
buffer.append(" World");
}
StringBuffer sb = new StringBuffer("Hello");
modifyReference(sb);
System.out.println(sb); // 输出 "Hello World"
在这个例子中,modifyReference方法接收StringBuffer对象的引用的副本,并修改了对象的内容。由于传递的是引用的副本,所以原始的sb引用指向的对象内容也被修改了。
内存管理 Java提供了自动垃圾回收机制来管理内存。当没有任何引用指向一个对象时,该对象成为垃圾回收的候选对象。例如:
String s1 = new String("Hello");
s1 = null; // 现在原始的String对象可以被垃圾回收
在这里,将s1设置为null后,原始的String对象没有任何强引用指向它,因此可以被垃圾回收。
使用不同类型的引用 Java还提供了软引用、弱引用和虚引用等特殊类型的引用,它们在内存管理中有特殊的用途。例如,软引用可以用于实现内存敏感的缓存机制。
SoftReference<String> softRef = new SoftReference<String>(new String("Hello"));
在这个例子中,softRef是对字符串"Hello"的软引用,它可能在内存不足时被垃圾回收。
与C语言的对比
在Java和C语言中,引用和指针都是用来访问内存中的对象。但是,它们在使用和概念上有一些重要的区别:
-
Java引用:
- Java的引用可以看作是指向对象的指针,但它更加安全,因为Java的引用不允许直接操作内存地址,也不能进行算术运算。
- Java引用的声明和使用类似于C语言的指针,但Java引用总是指向一个具体的对象,而且一旦初始化后就不能再指向另一个对象。
- Java方法参数传递时,无论是基本类型还是引用类型,都是值传递。对于引用类型,传递的是引用的副本。
-
C语言指针:
- C语言的指针可以指向任何类型的数据,包括其他指针,甚至是函数。
- 指针可以进行算术运算,比如增加或减少指针的值来访问数组中的元素。
- C语言的函数参数可以通过指针实现引用传递,从而允许函数修改调用者的变量。
比较示例:
在Java中,你可能会这样使用引用:
String str = "Hello";
在这里,str是一个引用,指向一个String对象。
而在C语言中,你可以有一个指针指向一个字符数组(字符串):
char *str = "Hello";
在这里,str是一个指针,指向字符数组的第一个元素。
总的来说,Java的引用提供了一种更加安全和受控的方式来访问对象,而C语言的指针提供了更多的灵活性,但也带来了更高的风险,比如野指针和内存泄漏的问题。在使用时,应根据具体的需求和场景选择合适的工具。
读完以上的介绍,你是否明白了那浓缩的一句:“一种更为安全的指针”的真实意义了呢?😏
数组的使用
向函数方法传递数组
在Java中,引用类型进行传参时,其实是进行了引用指向的传递。在将一个数组传递给方法时,我们传递过去了原数组的引用 ,所以当我们在方法中对数组进行操作时,所进行的操作会反映在原数组的身上。
这是引用的特点,下面我举个简单的例子:
int []a = {1, 2, 3};
int []b = {4, 5};
a = b;
for(int each:a)
{
System.out.println(each); //4, 5
}
请思考,执行上述代码的结果是什么?你会说这不是很明显的,肯定是b的内容啊。那我换个问题,如果我此时对a及逆行操作,b会不会改变?
a[1] = 147456 //修改a的第二个元素值
for(int each:a)
{
System.out.println(each); //4, 147456
}
运行一下程序,结果是不是很意外呢?这是因为,所谓的引用类型传值实际上是引用类型指向的改变,即原先a的引用指向的b的引用,引用中存在着实际内存的地址,如果a和b都指向了一个内存地址,那我们对a进行的改动,在b上也会出现。
这样是不是能好理解了一点为什么向方法传递数组后,更改会作用于原数组了?
遍历数组
上边的代码中是不是出现了一个陌生的语法
for(int each:a)
这一句是Java中特有的一种循环方式,我们称之为for-each语句。当然int 定义的变量名是可以随意设置的,我只是为了方便设置为了each。这一句的作用是用:前定义的变量遍历数组中每一个元素,可以视为以下代码的简写版:
int each;
for(int i = 0; i < a.length; i++)
{
each = a[i];
}
数组常用方法总结
ArraysInstance.length该方法返回一个数组的长度(真实长度,从1开始)Arrays.sort(ArraysInstance)该方法将实例变量ArraysInstance里的元素进行排序,从小到大,返回一个新数组。Arrays.binarySearch()该方法可以在排序后的数组中搜索特定元素,并返回找到该元素的索引值,如若未找到返回-1
int index = Arrays.binarySearch(anotherArray, 3); //在anotherArray数组中搜索元素3
Arrays.toString()该方法将数组转换为字符串,并返回一个字符串Arrays.copyOf(arr,length)该方法可将arr数组复制一份,返回一个长度为length的复制数组(若length长度比原数组长,则在复制数组中用0补齐)
import java.util.Arrays;
int [] arr = {1, 4, 7, 4, 5, 6};
int [] copyarr = Arrays.copyOf(arr, 8);
System.out.println(Arrays.toString(copyarr)); //[1,4,7,4,5,6,0,0]
Arrays.equals()和.equals()这两个方法的作用是相同的,比较两个引用类型引用内容的相等性。区别在于第一个方法需要先引入java.util.Arrays的包。特别提醒,比较引用类型时,切不可使用==在使用连等号进行比较时,由于引用类型指向的是内存地址,所以真实执行的是两个引用类型内存地址的比较,很容易发生隐含错误!
新手出行,欢迎大家补充和指正错误!