本文具体介绍一下泛型数组,反射,包装类与自动装箱。
在C语言中,当我们要使用一个数组时,必须在编译阶段确定数组的长度。当我们事先不知道实际需要数组的大小时,这样做无疑会浪费掉额外的内存空间。
然而在Java中,可以使用ArrayList类,使用方法类似于数组,但可以动态地增删数组元素,自动调节数组的容量。
与C语言的数组类型对应,ArrayList在定义数组是仍需要确定它的类型,只不过这里ArrayList是一个采用类型参数的泛型类。使用“<参数类型>”来保存元素对象的类型。这里的泛型既可以是基本数据类型(Byte,Double,Short,Long),也可以是引用数据类型(String,自定义类),注意使用时要大写。
int对应的泛型是Integer,char对应的泛型是Character
在C++中,vector类同样可以实现动态数组。在Java老版本中,程序员也使用Vector类实现,但新版本中ArrayList类更加有效。
ArrayList类和Vector类共同点是底层都是通过数组实现的,即访问快增删慢。Vector类则是线程同步的,即某一时刻只允许一个线程修改访问Vector,是安全的,因此访问它比访问ArrayList慢。ArrayList类则是线程异步的,即某一时刻允许多个线程修改访问,是不安全的。
这里我们创建一个Person类
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
在主方法类中创建ArrayList对象
ArrayList arrayList = new ArrayList<>();
arrayList.add(new Person("赵丽颖",19)); //向数组末尾添加一个匿名内部类对象
arrayList.set(0,new Person("迪丽热巴",20)); //修改索引为0的数组元素
arrayList.get(0);
//返回所以为0的元素 这里返回Person{name='迪丽热巴', age=20}
arrayList.remove(int index);//删除索引index的元素,并返回该元素
ArrayList类有多个重载add方法:
arrayList.add(Person penson);//默认向数组末尾添加person对象
arrayList.add(int index,Person penson);//在索引index位置插入person对象
注意:使用add方法向数组中添加元素,而不是set方法,set方法只能修改数组中已存在的元素内容。
我们在创建对象时也可以不对泛型进行约束
ArrayList a = new ArrayList<>();默认泛型为Object
这样a数组就可以添加任何类型的元素
例如:
a.add(new Person(“赵丽颖”,19)); Person类
a.add(1); Integer类
a.add(“nihaoa”); String类
在遍历数组的时候,我们可以使用for each循环
for(Person p :arrayList)
{
System.out.println(p);
}
注意:for each循环只能用于遍历整个数组,而不能用于遍历一部分数组内容。
另外,java中允许参数数量可变的方法。
例如我们要计算若干个数值的最大值:
public static double max(double... values) //...表示若干个参数
{
double maxNumber = Double.NEGATIVE_INFINITY;
for(double v:values)
if(v>maxNumber)
maxNumber=v;
return maxNumber;
}
调用:
double n = max(3.1,4.5,9.0);
//编译器将产生一个double类型的数组传递给方法。
在定义一个ArrayList类对象的时候,尖括号里的类型参数不允许我们使用基本数据类型,我们需要将基本数据类型转换为与之对应的类,也就是引用数据类型。
int ——Integer
char ——Character
long ——Long
float ——Float
double ——Double
short ——Short
byte ——Byte
boolean ——Boolean
这里Integer就是对象包装器类。当然我们向数组添加元素的时候应该添加一个对象
ArrayListarrayList = new ArrayList<>();
arrayList.add(new Integer(1)); //标准写法
arrayList.add(1); //日常写法
第三行add方法往Integer类数组中添加了一个int数据类型的元素,这里没有报错是因为Java在编译的时候自动将int转换为Integer,这个过程被称为自动装箱
相反地,当将一个Integer对象赋给一个int值时,将会自动拆箱
Integer n = 3;
n++; //编译器先将n自动拆箱为int类型,再进行自增操作
能够分析类能力的程序成为反射。既然有反射,一定有正射。
Person类中定义show方法:
public void show()
{
System.out.println("小苏然真帅!");
}
Person p = new Person("赵丽颖",19);
p.show();//正常调用show方法
这里我们在编写程序的时候就知道我们要使用什么类的什么方法,已经明确了目标。但在实际应用中我们只有在要运行这个程序的时候才会知道我们要使用什么类的什么方法,这就是反射。
Java在编译源代码的过程中,会创造若干个Class类对象,每一个类对象中分别有成员变量类,构造方法类和成员方法类。
我们编写的Person.java文件先经过javac编译后生成Person.class字节码文件,被类加载器加载入内存中。在Class类对象阶段,java为字节码文件中的成员变量,构造方法,成员方法分别创建了Field类对象,Constructor类对象和Method类对象来保存。这便是Class类对象中的内容。当我们创建Person对象的时候,java便会引用Class对象中的类对象内容。
之前如果我们不知道使用类和方法,是无法写出程序的。而反射是可以将我们要使用的类和方法名作为参数存在于程序中。当我们需要运行某个类的方式,便可以将其名称作为参数传入程序中运行。
第一种:
使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class cla = Class.forName("类的全路径");
第二种:
使用 .class 方法。这种方法只适合在编译前就知道操作的 Class。
Class cla = Person.class;
第三种:
通过调用类对象的getClass方法获取Class对象。
Person p = new Person();
Class cla = p.getClass();
当我们获得了Class类对象cla,我们需要反射出原来类的对象,使用newInstance()方法;
Person p = (Person)cla.newInstance();
//返回类提供public修饰的域,方法和构造器数组
Field[] fields = cla.getFields();
Constructor[] constructors = cla.getConstructor();
Method[] methods = cla.getMethods();
//返回类提供所有域,方法和构造器数组
Field[] fields = cla.getDeclaredFields();
Constructor[] constructors = cla.getDeclaredConstructor();
Method[] methods = cla.getDeclaredMethods();
看下列代码
Person p = new Person("赵丽颖",19);
Field name = cla.getDeclaredField("name");
System.out.println(name.get(p));
当然对于那些私有域,仅仅能够返回该域是不能够对其访问的。这时候我们需要使用cla.setAccessible()方法,俗称暴力反射,可以强制访问类中的私有域。
对于方法的访问与上面如出一辙
Person p = new Person("赵丽颖",19);
Method show = cla.getDeclaredMethod("show");
show.invoke(p);
调用invoke(Object obj,Object… args)来查看当前对象中的方法。第一个obj参数是要查看的方法对象,第二个参数是该方法对象中方法的参数。
在书中本节的末尾,作者给出了以下几点建议:
上期内容:
Java基础自学第一期(上)