>  기사  >  Java  >  잘 알려지지 않은 Java 배열의 특수 기능

잘 알려지지 않은 Java 배열의 특수 기능

Y2J
Y2J원래의
2017-05-17 09:08:581513검색

배열은 기본적으로 모든 언어에 존재하는 데이터 유형으로, 동일한 유형의 데이터 집합을 나타내며, 고정된 길이를 가지며, 메모리에서 연속적인 공간을 차지합니다. C, C++ 등의 언어에서는 배열의 정의가 간결하고 명확하지만, Java에서는 실제로 몇 가지 혼란스러운 기능이 있습니다. 이 기사에서는 이러한 기능을 분석하려고 합니다.

배열이 Java 객체에 있습니까?

Java와 C++는 모두 객체 지향 언어입니다. 이러한 언어를 사용할 때는 표준 클래스 라이브러리를 직접 사용할 수도 있고, 구성 및 상속과 같은 객체지향 기능을 사용하여 자체 클래스를 만들 수도 있고, 이를 바탕으로 객체를 생성합니다. 그렇다면 다음 질문을 생각해 보아야 합니다. 객체 지향 언어에서 배열은 객체입니까?

배열이 객체인지 확인하려면 먼저 객체가 무엇인지, 즉 객체의 정의를 명확히 해야 합니다. 높은 수준에서 개체는 클래스에서 생성된 인스턴스이며 사물 클래스의 특정 개체를 나타냅니다. 개체에는 다양한 속성이 있고 특정 동작이 있습니다. 낮은 수준에서 컴퓨터의 관점에서 개체는 메모리의 메모리 블록입니다. 이 메모리 블록은 클래스에 정의된 다양한 속성인 일부 데이터를 캡슐화하는 데 사용됩니다. 다음은 메모리에 있는 Person 객체의 표현입니다:

참고:

1) 작은 빨간색 직사각형은 참조(주소) 또는 기본 유형의 데이터를 나타냅니다. 큰 빨간색 직사각형은 여러 개의 작은 빨간색 직사각형이 결합되어 객체를 형성할 수 있습니다.

2) 이름은 객체의 참조, 즉 실제 기존 string 객체를 가리키는 주소 값만을 나타냅니다. 여기서는 참조와 객체를 엄격하게 구분합니다.

그럼 Java에서는 배열이 위의 조건을 만족하나요? 높은 수준에서 배열은 특정 유형의 특정 개체가 아니라 여러 개체의 모음입니다. 그렇다면 그것은 객체가 되어서는 안 됩니다. 컴퓨터 관점에서 배열은 일부 데이터를 캡슐화하는 메모리 블록이기도 합니다. 이 경우 객체라고도 합니다. 다음은 메모리에서의 배열 표현입니다.

이 경우 배열은 객체일 수도 있고 객체가 아닐 수도 있습니다. 배열을 객체로 취급할지 여부는 모두 Java 디자이너에 따라 다릅니다. 배열이 객체인지 여부는 코드를 통해 확인할 수 있습니다:


int[] a = new int[4]; 
//a.length; //对属性的引用不能当成语句 
int len = a.length; //数组中保存一个字段, 表示数组的长度 
//以下方法说明数组可以调用方法,java中的数组是对象.这些方法是Object中的方法,所以可以肯定,数组的最顶层父类也是Object 
a.clone(); 
a.toString();

배열 a에서 해당 속성에 액세스하고 일부 메서드를 호출할 수 있습니다. 기본적으로 Java의 배열도 객체라고 결론 내릴 수 있습니다. 이는 Java의 다른 객체의 몇 가지 기본 특성을 가지고 있습니다. 즉, 일부 데이터를 캡슐화하고 속성에 액세스할 수 있으며 메서드를 호출할 수도 있습니다. 따라서 배열은 객체입니다.

C++에서 배열은 데이터를 캡슐화하지만 배열 이름은 호출할 속성이나 메서드가 없는 배열의 첫 번째 요소를 가리키는 포인터일 뿐입니다. 다음 코드에서 볼 수 있듯이:


int main(){ 
 int a[] = {1, 2, 3, 4}; 
 int* pa = a; 
 //无法访问属性,也不能调用方法。 
 return 0; 
}

따라서 C++의 배열은 객체가 아니며 단지 데이터의 모음일 뿐 객체로 사용할 수 없습니다. .

Java의 배열 유형

Java는 강력한 형식의 언어입니다. 객체이므로 유형에 속해야 합니다. 예를 들어 Person 클래스 를 기반으로 객체 를 생성하는 경우 이 객체의 유형은 Person입니다. 그렇다면 배열의 유형은 무엇입니까? 다음 코드를 보십시오.


int[] a1 = {1, 2, 3, 4}; 
System.out.println(a1.getClass().getName()); 
//打印出的数组类的名字为[I 
String[] s = new String[2]; 
System.out.println(s.getClass().getName()); 
//打印出的数组类的名字为 [Ljava.lang.String; 
String[][] ss = new String[2][3]; 
System.out.println(ss.getClass().getName()); 
//打印出的数组类的名字为 [[Ljava.lang.String;

는 a1의 유형이 [ I 이고 s의 유형이 [Ljava.lang.String; ss의 유형은 [ [Ljava.lang.String;

이므로 배열에도 유형이 있습니다. 그냥 이런 유형이 이상한 것 같아요. a1의 유형이 int[]라고 말할 수 있는데 이는 이해할 수 있습니다. 하지만 우리는 이 클래스를 직접 만든 것이 아니며 Java의 표준 라이브러리에서도 이 클래스를 찾지 못했습니다. 즉, 자체 코드이든 JDK이든 다음과 같은 정의가 없습니다.


public class int[] { 
 // ... 
 // ... 
 // ... 
}

이것은 하나만 설명할 수 있으며, 가상 머신은 8가지 기본 데이터 유형과 마찬가지로 Java 내장 유형으로 사용할 수 있는 배열 유형을 자동으로 생성합니다. 이 유형의 명명 규칙은 다음과 같습니다.

* 每一维度用一个[表示;开头两个[,就代表是二维数组

* [后面是数组中元素的类型(包括基本数据类型和引用数据类型)

在java语言层面上,s是数组,也是一个对象,那么他的类型应该是String[],这样说是合理的。但是在JVM中,他的类型为[java.lang.String。顺便说一句普通的类在JVM里的类型为 包名+类名,也就是全限定名。同一个类型在java语言中和在虚拟机中的表示可能是不一样的。

Java中数组的继承关系

上面已经验证了,数组是对象,也就是说可以以操作对象的方式来操作数组。并且数组在虚拟机中有它特别的类型。既然是对象,遵循Java语言中的规则 -- Object是上帝, 也就是说所有类的顶层父类都是Object。数组的顶层父类也必须是Object,这就说明数组对象可以向上直接转型到Object,也可以向下强制类型转换,也可以使用instanceof关键字做类型判定。 这一切都和普通对象一样。如下代码所示:


//1 在test1()中已经测试得到以下结论: 数组也是对象, 数组的顶层父类是Object, 所以可以向上转型 
int[] a = new int[8]; 
Object obj = a ; //数组的父类也是Object,可以将a向上转型到Object 
//2 那么能向下转型吗? 
int[] b = (int[])obj; //可以进行向下转型 
//3 能使用instanceof关键字判定吗? 
if(obj instanceof int[]){ //可以用instanceof关键字进行类型判定 
 System.out.println("obj的真实类型是int[]"); 
}

Java中数组的另一种“继承”关系

如下代码是正确的,却很容易让我们疑惑:


String[] s = new String[5]; 
Object[] obja = s; //成立,说明可以用Object[]的引用来接收String[]的对象

Object[]类型的引用可以指向String[]类型的数组对象? 由上文的验证可以得知数组类型的顶层父类一定是Object,那么上面代码中s的直接父类是谁呢?难道说String[]继承自Object[],而Object[]又继承自Object? 让我们通过反射的方式来验证这个问题:


//5 那么String[] 的直接父类是Object[] 还是 Object? 
System.out.println(s.getClass().getSuperclass().getName()); 
//打印结果为java.lang.Object,说明String[] 的直接父类是 Object而不是Object[]

由代码可知,String[]的直接父类就是Object而不是Object[]。可是Object[]的引用明明可以指向String[]类型的对象。那么他们的继承关系有点像这样:

这样的话就违背了Java单继承的原则。String[]不可能即继承Object,又继承Object[]。上面的类图肯定是错误的。那么只能这样解释:数组类直接继承了Object,关于Object[]类型的引用能够指向String[]类型的对象,这种情况只能是Java语法之中的一个特例,并不是严格意义上的继承。也就是说,String[]不继承自Object[],但是我可以允许你向上转型到Object[],这种特性是赋予你的一项特权。

其实这种关系可以这样表述:如果有两个类A和B,如果B继承(extends)了A,那么A[]类型的引用就可以指向B[]类型的对象。如下代码所示:


public static class Father { 
} 
public static class Son extends Father { 
} 
//6 下面成立吗? Father是Son的直接父类 
Son[] sons = new Son[3]; 
Father[] fa = sons; //成立 
//7 那么Son[] 的直接父类是Father[] 还是 Object[] 或者是Object? 
System.out.println(sons.getClass().getSuperclass().getName()); 
//打印结果为java.lang.Object,说明Son[]的直接父类是Object

上面的结论可以扩展到二维数组和多维数组


Son[][] sonss = new Son[2][4]; 
Father[][] fathers = sonss;

上面的代码可以这样理解:

将Father[][]数组看成是一维数组, 这是个数组中的元素为Father[],将Son[][]数组看成是一维数组, 这是个数组中的元素为Son[],因为Father[]类型的引用可以指向Son[]类型的对象,所以,根据上面的结论,Father[][]的引用可以指向Son[][]类型的对象。

数组的这种用法不能作用于基本类型数据:


int[] aa = new int[4]; 
//Object[] objaa = aa; //错误的,不能通过编译

这是错误的, 因为int不是引用类型,Object不是int的父类,在这里自动装箱不起作用。但是这种方式是可以的:


 Object[] objss = {"aaa", 1, 2.5};

这种情况下自动装箱可以工作,也就是说,Object数组中可以存放任何值,包括基本数据类型。

Java为什么会为数组提供这样一种语法特性呢?也就是说这种语法有什么作用?编写过AndroidSqlite数据库操作程序的同学可能发现过这种现象,用一个Object[]引用接收所有的数组对象,在编译SQL语句时,为SQL语句中的占位符提供对应的值。


 db.execSQL("INSERT INTO person VALUES (NULL, ?, ?)", new Object[]{person.name, person.age});

所以这种特性主要是用于方法中参数的传递。如果不传递数组,而是依次传递各个值,会使方法参数列表变得冗长。如果使用具体的数组类型,如String[],那么就限定了类型,失去了灵活性。所以传递数组类型是一种比较好的方式。但是如果没有上面的数组特性(如果有两个类A和B,如果B继承(extends)了A,那么A[]类型的引用就可以指向B[]类型的对象),那么数组类型就只能通过Object类型接收,这样就无法在方法内部访问或遍历数组中的各个元素。如下代码:


private static void test3() { 
 String[] a = new String[3]; 
 doArray(a); 
} 
private static void doArray(Object[] objs){ 
} 
private static void doArray1(Object obj){ 
 //不能用Object接收数组,因为这样无法对数组的元素进行访问 
 // obj[1] //错误 
 //如果在方法内部对obj转型到数组,存在类型转换异常的风险 
 // Object[] objs = (Object[]) obj; 
} 
private static void doArray2(String[] strs){ 
 //如果适用特定类型的数组,就限制了类型,失去灵活性和通用性 
} 
private static void doArray3(String name, int age, String id, float account){ 
 //如果不适用数组而是依次传递参数,会使参数列表变得冗长,难以阅读 
}

到此为止,数组的特性就总结完了。上文中加粗的部分为重要结论。下面贴出整个源码:

源码


package com.pansoft.zhangjg.testarray; 
public class ArrayTest { 
 /** 
  * @param args 
  */ 
 public static void main(String[] args) { 
  test1(); 
  test2(); 
  test3(); 
 } 
 /** 
  * 数组具有这种特性: 
  * 如果有两个类A和B,如果B继承(extends)了A,那么A[]类型的引用就可以指向B[]类型的对象 
  * 测试数组的特殊特性对参数传递的便利性 
  */ 
 private static void test3() { 
  String[] a = new String[3]; 
  doArray(a); 
 } 
 private static void doArray(Object[] objs){ 
 } 
 private static void doArray1(Object obj){ 
  //不能用Object接收数组,因为这样无法对数组的元素进行访问 
  // obj[1] //错误 
  //如果在方法内部对obj转型到数组,存在类型转换异常的风险 
  // Object[] objs = (Object[]) obj; 
 } 
 private static void doArray2(String[] strs){ 
  //如果适用特定类型的数组,就限制了类型,失去灵活性和通用性 
 } 
 private static void doArray3(String name, int age, String id, float account){ 
  //如果不适用数组而是依次传递参数,会使参数列表变得冗长,难以阅读 
 } 
 /** 
  * 测试数组的集成关系, 并且他的继承关系是否和数组中元素的类型有关 
  */ 
 private static void test2() { 
  //1  在test1()中已经测试得到以下结论: 数组也是对象, 数组的顶层父类是Object, 所以可以向上转型 
  int[] a = new int[8]; 
  Object obj = a ; //数组的父类也是Object,可以将a向上转型到Object 
  //2  那么能向下转型吗? 
  int[] b = (int[])obj; //可以进行向下转型 
  //3  能使用instanceof关键字判定吗? 
  if(obj instanceof int[]){ //可以用instanceof关键字进行类型判定 
   System.out.println("obj的真实类型是int[]"); 
  } 
  //4  下面代码成立吗? 
  String[] s = new String[5]; 
  Object[] obja = s; //成立,说明可以用Object[]的引用来接收String[]的对象 
  //5  那么String[] 的直接父类是Object[] 还是 Object? 
  System.out.println(s.getClass().getSuperclass().getName()); 
  //打印结果为java.lang.Object,说明String[] 的直接父类是 Object而不是Object[] 
  //6 下面成立吗? Father是Son的直接父类 
  Son[] sons = new Son[3]; 
  Father[] fa = sons; //成立 
  //7  那么Son[] 的直接父类是Father[] 还是 Object[] 或者是Object? 
  System.out.println(sons.getClass().getSuperclass().getName()); 
  //打印结果为java.lang.Object,说明Son[]的直接父类是Object 
  /** 
   * 做一下总结, 如果A是B的父类, 那么A[] 类型的引用可以指向 B[]类型的变量 
   * 但是B[]的直接父类是Object, 所有数组的父类都是Object 
   */ 
  //8  上面的结论可以扩展到二维数组 
  Son[][] sonss = new Son[2][4]; 
  Father[][] fathers = sonss; 
  //将Father[][]数组看成是一维数组, 这是个数组中的元素为Father[] 
  //将Son[][]数组看成是一维数组, 这是个数组中的元素为Son[] 
  //因为Father[]类型的引用可以指向Son[]类型的对象 
  //所以,根据上面的结论,Father[][]的引用可以指向Son[][]类型的对象 
  /** 
   * 扩展结论: 
   * 因为Object是所有引用类型的父类 
   * 所以Object[]的引用可以指向任何引用数据类型的数组的对象. 如: 
   * Object[] objs = new String[1]; 
   * Object[] objs = new Son[1]; 
   * 
   */ 
  //9  下面的代码成立吗? 
  int[] aa = new int[4]; 
  //Object[] objaa = aa; //错误的,不能通过编译 
  //这是错误的, 因为Object不是int的父类,在这里自动装箱不起作用 
  //10 这样可以吗? 
  Object[] objss = {"aaa", 1, 2.5};//成立 
 } 
 /** 
  * 测试在java语言中,数组是不是对象 
  * 如果是对象, 那么他的类型是什么? 
  */ 
 private static void test1() { 
  int[] a = new int[4]; 
  //a.length; //对属性的引用不能当成语句 
  int len = a.length; //数组中保存一个字段, 表示数组的长度 
  //以下方法说明数组可以调用方法,java中的数组是对象.这些方法是Object中的方法,所以可以肯定,数组的最顶层父类也是Object 
  a.clone(); 
  a.toString(); 
  /** 
   * java是强类型的语言,一个对象总会有一个特定的类型,例如 Person p = new Person(); 
   * 对象p(确切的说是引用)的类型是Person类, 这个Person类是我们自己编写的 
   * 那么数组的类型是什么呢? 下面使用反射的方式进行验证 
   */ 
  int[] a1 = {1, 2, 3, 4}; 
  System.out.println(a1.getClass().getName()); 
  //打印出的数组类的名字为[I 
  String[] s = new String[2]; 
  System.out.println(s.getClass().getName()); 
  //打印出的数组类的名字为 [Ljava.lang.String; 
  String[][] ss = new String[2][3]; 
  System.out.println(ss.getClass().getName()); 
  //打印出的数组类的名字为 [[Ljava.lang.String; 
  /** 
   * 所以,数组也是有类型的,只不过这个类型不是有程序员自己定义的类, 也不是jdk里面 
   * 的类, 而是虚拟机在运行时专门创建的类 
   * 类型的命名规则是: 
   *  每一维度用一个[表示; 
   *  [后面是数组中元素的类型(包括基本数据类型和引用数据类型) 
   * 
   * 在java语言层面上,s是数组,也是一个对象,那么他的类型应该是String[], 
   * 但是在JVM中,他的类型为[java.lang.String 
   * 
   * 顺便说一句普通的类在JVM里的类型为 包名+类名, 也就是全限定名 
   */ 
 } 
 public static class Father { 
 } 
 public static class Son extends Father { 
 } 
}

【相关推荐】

1. 特别推荐“php程序员工具箱”V0.1版本下载

2. Java免费视频教程

3. 全面解析Java注解

위 내용은 잘 알려지지 않은 Java 배열의 특수 기능의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.