学习IO流视频时,发现老师讲解的在mian函数里调用递归函数,但是递归函数没有返回值,但是他的传值居然有改变。
具体代码如下:(main方法 getFiles(dir,filter,list) 递归调用处)
package cn....test;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/*
*
* 获取指定目录下,指定扩展名的文件(包含子目录中的)
* 这些文件的绝对路径写入到一个文本文件中。
*
* 简单说,就是建立一个指定扩展名的文件的列表。
*
* 思路:
* 1,必须进行深度遍历。
* 2,要在遍历的过程中进行过滤。将符合条件的内容都存储到容器中。
* 3,对容器中的内容进行遍历并将绝对路径写入到文件中。
*
*
*/
public class Test {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
File dir = new File("e:\\java0331");
FilenameFilter filter = new FilenameFilter(){
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
};
List<File> list = new ArrayList<File>();
getFiles(dir,filter,list);
File destFile = new File(dir,"javalist.txt");
write2File(list,destFile);
}
/**
* 对指定目录中的内容进行深度遍历,并按照指定过滤器,进行过滤,
* 将过滤后的内容存储到指定容器List中。
* @param dir
* @param filter
* @param list
*/
public static void getFiles(File dir,FilenameFilter filter,List<File> list){
File[] files = dir.listFiles();
for(File file : files){
if(file.isDirectory()){
//递归啦!
getFiles(file,filter,list);
}else{
//对遍历到的文件进行过滤器的过滤。将符合条件File对象,存储到List集合中。
if(filter.accept(dir, file.getName())){
list.add(file);
}
}
}
}
public static void write2File(List<File> list,File destFile)throws IOException{
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(destFile));
for(File file : list){
bufw.write(file.getAbsolutePath());
bufw.newLine();
bufw.flush();
}
} /*catch(IOException e){
throw new RuntimeException("写入失败");
}*/finally{
if(bufw!=null)
try {
bufw.close();
} catch (IOException e) {
throw new RuntimeException("关闭失败");
}
}
}
}
让我想起了VB函数里重要的 ByRef 和 ByVal。对于用过VB,或者C#,或者delphi的人来说,对于这个参数传值并不陌生,但是在JAVA里,好像没有显式的传参类型让人设置。
ByRef 是对地址或者说是指针的直接引用,ByVal 是针对值的复制引用。用法其实就是
' 久未写VB,大致语法这样吧
Sub from_load(.....)
String x = "x"
String y = "y"
change(x,y)
Msgbox(x + "," + y)
// 弹窗结果是 x,b 因为ByVal是复制值,ByRef是指向内存地址。
End Sub
' 注意参数的显示传值类型
Sub change(ByVal String X , ByRef string y)
x = "a"
y = "b"
End Sub
现在因为JAVA无这种方式,起码我未找到关于ByRef 和 ByVal的方法。
我找了几篇博客,大致解释如下,传参的结果是否发生变化根据数据类型来区分。
值传递,分为引用数据类型值传递和基本数据类型值传递,
前者会对传值进行改变,后者不会,
前者只有接口,自定义类,数组,后者为8种基本类型和其延伸。
如果是基本数据类型,运行时参数只会复制传递进来的值,类似VB里的ByVal(By value)。
如果是引用数据类型,运行时参数会直接指向传参的内存地址,类似VB里的ByRef。
这样一来复制的值怎么操作都不会对原参数产生影响,而直接指向内存地址的修改行为,自然会对同样指向内存地址的原参数造成影响。
复制博客的图片如下:
图片引用自:深入理解--Java按值传递和按引用传递
四类 八种 基本数据类型
1. 整型 byte(1字节) short (2个字节) int(4个字节) long (8个字节)
2.浮点型 float(4个字节) double(8个字节)
3.逻辑性 boolean(八分之一个字节)
4.字符型 char(2个字节,一个字符能存储下一个中文汉字)
整理起来就是 byte short int long float double char boolean,包括他的包装类型
String类型:String类型不是基本数据类型,而是引用数据类型,但是因为有特殊的用法,常量池的存在,所以对于String类型来说,和基本类型类似。它每一次赋新值,都会在常量池中寻找是否已存在这个新值,如果存在则直接将String变量指向常量池已存在的地址,如果不存在则会在常量池创建这个地址,再指向新创建的地址。当然,无用的地址会被自动清理掉。但是对于StringBuffer和StringBuilding来说,内置了Append等等直接对自身进行修改的方法,不会创建新的常量池,直接对这个值进行改变。所以demo2,会修改成功,是使用了内置的方法,直接对内存地址对应的值进行了改变,而demo1中包括上面的对于String的各种修改,都只是用=来创建一个新的常量池地址值并指过去,不会对旧地址造成任何影响。
关于String类型与常量池可参考大神博客链接:从底层彻底搞懂String,StringBuilder,StringBuffer的实现
两种类型的区别原因
值类型/引用类型,是用于区分两种内存分配方式,值类型在调用栈上分配,引用类型在堆上分配。
理论来自于:Java中的值传递,具体的可参考此大神博客,分析的更详细。
如下是我测试结果:
public class Main {
public static void main(String[] args) {
String s = "asd";
changeString(s);
System.out.println("s:" + s);
int i = 1;
changeInt(i);
System.out.println("i:" + i);
Integer integer = 55;
changeInteger(integer);
System.out.println("integer:" + integer);
//demo1
String str= new String("hello");
char []chs={'w','o','r','l','d'};
change(str, chs);
System.out.println(str+" "+new String(chs));
//-------------------------------------------------
//demo2
StringBuffer sb=new StringBuffer("hello");
change(sb);
System.out.println(sb);
//对abc通过调用方法进行修改
StringBuilder a=new StringBuilder("A");
StringBuilder b=new StringBuilder("B");
StringBuilder c=new StringBuilder("C");
method3(a,b,c);
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
private static void changeString(String s) {
s = "789";
}
private static void changeInt(int i) {
i = 5;
}
private static void changeInteger(Integer integer) {
integer = 99;
}
public static void change(StringBuffer sb)
{
sb.append(" world");
sb.deleteCharAt(0);
}
public static void change(String str,char[]chs)
{
str.replace('h', 'H');
chs[0]='W';
}
public static void method3(StringBuilder a,StringBuilder b,StringBuilder c){
// 将a原值添加了A(a=AA;b=B;c=C)
a.append("A");
// 将b换了个新对象,原b对象不动(a=AA;b=B;新的b=BB;c=C)
b=new StringBuilder("BB");
// 将新对象添加了B(a=AA;b=B;新的b=BBB;c=C)
b.append("B");
// 将C重新换了指定的新对象,原c对象不动(a=AA;b=B;新的b=BBB;c=AA)
c=a;
// c现在指定的是a所以是将a添加了C(a=AAC;b=B;新的b=BBB;c=AAC)
c.append("C");
}
}
运行结果如下所示:
s:asd
i:1
integer:55
hello World
ello world
AAC
B
C
进程已结束,退出代码0
到此地步算是初步解开了我的疑问,算是基本了解了一点浅薄的参数传递的知识,虽然对于以前用到的各种语言来说,不是很方便。
如果有错误,请回复指正,望大神指点。
补充:到后面测试过程里,才发现,好像只有 静态的方法才能起到效果。