本篇博客着重说明方法的参数传递机制和 对象上转型,作为前几篇博客的续貂之作,当然面向对象我还没有回顾完呢。言归正传。
一、方法的参数传递机制
1.1说明:java里的方法不能单独存在,调用方法必须使用类或者对象作为主调者。
如果声明的方法有形式参数声明,则在调用时必须指定这些形式参数的实际值。
那么java的参数实际值是怎么传递到方法体内部的呢?
首先说一下:java的方法的参数传递方式只有“值传递”一种,
值传递:将实际参数值的副本(复制品)传入方法内部。而参数本身不会受到影响。
参看下面的代码:
实例代码1;
public class PrimitiveTransferTest {
public static void main(String[] args) {
int a = 9;
int b = 10;
System.out.println("原值: a = "+a+" b = "+b);
swap( a , b);
System.out.println("交换后的原值: a = "+a+" b = "+b);
}
public static void swap(int a ,int b){
int temp = a;
a = b;
b = temp;
System.out.println("参数的副本交换 : a = "+a+" , b = "+b);
}
}
运行结果是a,b在main函数里的值并没有交换,但是swap里的参数值却交换了,但是swap却没有达到它应该有的功能,是因为程序执行swap方法的时候,为该方法传递的是a,b变量的副本,而不是a,b,变量的本身,进入swap方法后系统产生了四个变量,a,b本身在main方法所在的栈区里,而a,b的副本在swap方法的栈区中,swap的功能仅仅是交换自己栈区的a,b的值。
这个例子很经典,也很容易令初学者犯错,其实是很简单的,但是如何能让swap达到可以交换main方法里的a,b的值呢,大家应该能想到用指针可以实现这个交换操作,。
怎么使用指针呢,a,b是基本数据类型,只有直接调用的方式,而main方法中的a,b值是局部变量,因此可以在main方法中直接进行a,b的交换(有时候在算法设计时该交换是单独作为一个函数出现的),或者将a,b放入一个引用类型的数据类型里。
解决方法一:
a = a^b;b = a^b;a= b^a;
这个是交换两个数最快也最省事的了。
解决方法二:该方法需要两个空间的数组,因为数组是引用类型的,因此可以使用该方法,传递的是一个int类型的数组。
实例代码2;
int arr[] = new int[2];
arr[0] = a;
arr[1] = b;
swap(arr);
public static void swap(int array[]){
array[0] = array[0]^array[1];
array[1] = array[0]^array[1];
array[0] = array[1]^array[0];
//System.out.println("交换后的参数2:arr[0] = "+arr[0]+" arr[1] = "+arr[1]);
}
解决方法三:
Info info = new Info();
System.out.println("交换前: "+info.a+" ---- "+info.b);
swap(info);
System.out.println("交换后: "+info.a+" ---- "+info.b);
/*
* 第三种方式使用class 将 a,b的值存入,然后方法内使用Info 的实例,进行交换
*/
public static void swap(Info info){
int temp = info.a;
info.a = info.b;
info.b = temp;
}
对于第二种和第三种方式的解释:由于采用了引用类型的数据进行传值,所传的便是真正的值本身,而不是值的一个副本,
系统复制的只是引用数据指针,而不是引用数据指向的数据,因此在程序执行的时候main方法创建了一个Info的对象,并定义了一个info引用变量来指向Info对象,这是一个与基本类型不同的地方,创建一个对象时,系统内存中有两个东西:堆内存中保存了对象本身,栈内存保存了引用该对象的引用变量。在swap方法交换时,只是利用引用变量的副本实现指向两个变量的功能,并在swap函数里进行交换。
可以将第三种方式里的info参数赋值为null;
即 info = null;
再在主函数里进行info调用a,b的值,可以发现不会报错,
下面是这个例子的完整实例:提供了三个重载swap的方法。
实例代码3;
public class PrimitiveTransferTest {
public static void main(String[] args) {
Info info = new Info();
System.out.println("交换前: "+info.a+" ---- "+info.b);
swap(info);
System.out.println("交换后: "+info.a+" ---- "+info.b);
}
public static void swap(int array[]){
array[0] = array[0]^array[1];
array[1] = array[0]^array[1];
array[0] = array[1]^array[0];
//System.out.println("交换后的参数2:arr[0] = "+arr[0]+" arr[1] = "+arr[1]);
}
public static void swap(int a ,int b){
int temp = a;
a = b;
b = temp;
System.out.println("参数的副本交换 : a = "+a+" , b = "+b);
}
/*
* 第三种方式使用class 将 a,b的值存入,然后方法内使用Info 的实例,进行交换
*/
public static void swap(Info info){
int temp = info.a;
info.a = info.b;
info.b = temp;
}
}
class Info{
int a = 10;
int b = 11312;
}
1.2形参个数可变的方法。
说明:从JDK 1.5之后,java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。
实例代码4;
package cn.com.basicTwo;
public class Varargs {
//形参数目可变的方法
public static void test(int a,int b,String ... books){
//这种形式的是增强for循环形式
for(String book: books){
System.out.println(book);
}
}
public static void test(int param[]){
for(int i =0;i<param.length;i++){
System.out.println(param[i]);
}
}
public static void main(String[] args) {
Varargs.test(1, 2, "abcd","efds");
System.out.println("\n\n");
Varargs.test(3, 4, "asdfawe","sdfwewr","sdf","asdwe");
System.out.println("\n\n");
int array []= new int [4];
array[0]= 130;
array[1] = 102;
array[3]= 2425;
Varargs.test(array);
}
}
上面使用两种方式指定形参数目不同,可以满足一定特殊的需求。
注意:长度可变的形参只能出现在形参列表里的最后,一个方法里最多只能包含一个长度可变的形参,调用包含一个长度可变的形参方法时,这个长度可变的形参既可以传入多个参数,也可以传入一个数组。
不过数组形式的可变参数跟普通参数一样,没有什么特殊要求。
1.3递归方法
说明:一个方法体内调用它自身,被称为方法递归。方法递归包含了一种隐士的循环,会重复执行某段代码,但这种重复执行无需循环控制。
实例代码5:
public class TestRec {
//求和(有限递归),根据返回值递归退出循环
public static int getSum(int x){
if(x == 1){
return 0;
}else{
return getSum(x-1)+x;
}
}
/**
*
* 作者:FCs
* 描述:这种方式没有返回值重复调用自己,
* 循环不能终止,这种方式类似于死循环,将不断的申请栈空间最终导致内存溢出。
* 尽量不要这样用。而且这种方法非常危险。
* 时间:2014年8月18日
* 返回:void
*/
public static void getInfo(){
System.out.println("我是不好的代码,循环调用自己。。。。");
getInfo();
}
public static void main(String[] args) {
int a = getSum(5);
System.out.println(a);
// getInfo();
}
}
说明:一般使用递归都是有某种返回值的,当满足返回条件的时候,递归会逐层返回,就像栈一样,每一次调用自己都会在自己的栈区里申请一块内存,用指针指向它,这种方法非常消耗内存,一般可以使用递归的情况都可以使用非递归形式进行递归的消除,但是使用非递归的形式进行消除是一种非常困难的时候,代码难懂,尤其是在算法方面,因为递归的形式比非递归的形式更加简洁明了。
二、对象上转型,下转型(为下一章做个引子先)
对象上转型属于java多态的内容,但是先在这里总结一下。
定义: 父类声明,子类实例化的对象称为上转型对象。该对象是子类的简化,不关系子类的新增功能,只关心子类继承的和重写的功能。
这种类型通常见于父类与子类之间
形式:
上转型对象: 父类 name = new 子类();
下转型对象: 子类 name = (父类)子类name;
说明:下转型对象需要强制转型,
上转型对象的特点
1.上转对象不能操作子类新增的成员变量,失掉了这部分属性,不能使用子类新增的方法,失掉了一些功能。
2.上转型对象可以操作子类继承的成员变量,也可以使用子类继承的或重写的方法。
3.如果子类重写了父类的某个方法后,当对象的上转型对象调用这个方法时一定是调用了子类重写的方法。因为程序在运行时知道,这个上转对象的实例是子类创建的,只不过损失了一些功能而已。
注意:
1.可以将对象的上转型对象再强制转换为一个子类对象,这时,
该对象又具备了子类所有属性和功能。
2.不可以将父类创建的对象赋值给子类声明的对象。
实例代码6:
package cn.com.basicThree;
/**
*
* @author fcs
* 2014年8月18日
* Computer
* 说明:对象上转型,与下转型
*/
public class Computer{
public String name = "重量";
public String band = "品牌";
public String getInfo(){
return name+" -- "+band;
}
//父类特有方法
public void getBand(){
System.out.println(band);
}
public static void main(String[] args) {
Computer computer = new Lenovo(); //上转型对象
System.out.println(computer.band); //上转型对象调用从父类继承的变量,该对象不能调用自己的变量
System.out.println(computer.name);
computer.getInfo(); //上转型对象调用从父类重写的方法的方法,该对象不能调用自己的增加的方法
//computer.getCom();报错
computer.getBand(); //调用父类未被重写的方法
Lenovo lenovo = (Lenovo) computer; //将上转型对象再转回子类对象(父类下转型),跟普通子类对象具有相同功能特点。
System.out.println(lenovo.band); //调用父类变量
System.out.println(lenovo.price); //调用自己的变量
lenovo.getBand();
//lenovo.getInfo(); 这种方式不建议使用,即当上转型对象再转回子类对象的时候,不要再调用被子类重写的方法。
lenovo.getCom(); //调用子类新增方法
// Computer comp = new Computer();
// Lenovo lenv = (Lenovo)comp; 编译报错? 请读者思考
//System.out.println(lenv.band);
//System.out.println(lenv.price);
//lenv.getBand();
//System.out.println(lenv.getCom());
//lenv.getInfo();
}
}
class Lenovo extends Computer{
public String xinghao = "G480";
public double price = 3453.89;
//重写父类方法
public String getInfo(){
return xinghao+" -- "+price;
}
//子类新增方法
public String getCom(){
return "Lenovo";
}
}
上转型对象扩展了父类的使用范围和灵活度,因为子类对父类的方法在某种程度上进行了优化,因此在这种形式下上转型对象可以最大化的使用从父类继承的优点。可以使用父类对各个继承自己的子类进行这种方式的变化,进而体现了多态的特性。
三、instanceof 运算符说明
Instanceof的运算符的前一个操作数通常是一个引用类型的变量,后一个操作数通常是一个类或者接口,
作用:首先用于判断前面的对象是否是后面的类的实例,然后是否可以成功进行类型转换,从而使程序更加健壮。
说明:整个表达式的值要么是true,要么是false
使用:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系。否则编译时出错。
实例代码7:
package cn.com.basicThree;
/**
*
* @author fcs
* 2014年8月19日
* Instanceof
* 说明:instanceof功能说明
*
*
*/
public class Instanceof {
public static void main(String[] args) {
//声明Hello的时候使用Object类。则Hello的编译类型是Object
//Object是所有类的父类,但Hello变量的实际类型是String
Object hello = "hello";
//String是Object的子类返回true
System.out.println("字符串是否是Object类的实例:"+(hello instanceof Object));
System.out.println("字符串是否是 String类的实例: "+(hello instanceof String));
System.out.println("字符串是否是Math类的实例: "+(hello instanceof Math));
//String 实现了Comparable接口,返回true
System.out.println("字符串是否是Comparable接口的实例: "+(hello instanceof Comparable));
String a = "hello";
System.out.println("字符串是否是Object类的实例:"+(a instanceof Object));
TestInstanceof ti1 = new TestInstanceof();
System.out.println("ti1 对象是否是Object类的实例 ;"+(ti1 instanceof Object));
TestInstanceof2 ti2 = new TestInstanceof2();
//System.out.println("ti2 对象是否是Object类的实例 ;"+(ti2 instanceof ti1)); //第二个操作数是对象,编译出错
System.out.println("ti2 对象是否是Object类的实例 ;"+(ti2 instanceof TestInstanceof2));
//前后类型不同编译出错。
//System.out.println("a 是否是TestInstanceof2类的实例 ;"+(a instanceof TestInstanceof2));
}
}
class TestInstanceof { }
class TestInstanceof2{ }