之前的几篇文章中,总结了java中的基本语句和基本数据类型等等一系列的最基本的东西,下面就来说说java中的函数部分

函数基础

在C/C++中有普通的全局函数、类成员函数和类的静态函数,而java中所有内容都必须定义在类中。所以Java中是没有全局函数的,Java里面只有普通的类成员函数(也叫做成员方法)和静态函数(也叫做静态方法)。这两种东西在理解上与C/C++基本一样,定义的格式分别为:

public static void test(arglist){

}
public void test(arglist){ }

基本格式为:修饰符 [static] 返回值 函数名称 形参列表

修饰符主要是用来修饰方法的访问限制,比如public 、private等等;如果是静态方法需要加上static 如果是成员方法则不需要;后面是返回值,Java函数可以返回任意类型的值;函数名用来确定一个函数,最后形参列表是传递给函数的参数列表。

函数中的内存分布

Java中函数的使用方式与C/C++中基本相同,这里就不再额外花费篇幅说明它的使用,我想将重点放在函数调用时内存的分配和使用上,更深一层了解java中函数的运行机制。

我们说在X86架构的机器上,每个进程拥有4GB的虚拟地址空间。Java程序也是一个进程,所以它也拥有4GB的虚拟地址空间。每当启动一个Java程序的时候,由Java虚拟机读取.class 文件,然后解释执行其中的二进制字节码。启动java程序时,在进程列表中看到的是一个个的Java虚拟机程序。

java虚拟机在加载.class 文件时将它的4GB的虚拟地址空间划分为5个部分,分别是栈、堆、方法区、本地方法栈、寄存器区。其中重点需要关注前3个部分。

  • 栈:与C/C++中栈的作用相同,就是用来保存函数中的局部变量和实参值的。
  • 堆:与C/C++中堆的作用相同,用来存储Java中new出来的对象
  • 方法区:用来保存方法代码和方法名与地址的这么一张表,类似于C/C++中的函数表

基本数据类型作为函数的参数

class Demo{
public static void main(String[] args){
int n = 10;
test(10);
System.out.println(n);
} public static void test(int i){
System.out.println(i);
i++;
}
}

上述代码在函数中改变了形参值,那么在调用之后n的值会不会发生变化呢?答案是:不会变化,在C/C++中很好理解,形参i只是实参n的一个拷贝,i改变不会改变原来的n。这里我们从内存的角度来回答这个问题

如上图所示,方法区中存储了两个方法的相关信息,main和test,在调用main的时候,首先从方法区中查找main函数的相关信息,然后在栈中进行参数入栈等操作。然后初始化一个局部变量n,接着调用test函数,调用test函数时首先根据方法区中的函数表找到方法对应的代码位置,然后进行栈寄存器的偏移为函数test分配一个栈空间,接着进行参数入栈,这个时候会将n的值——10拷贝到i所在内存中。这个时候在test中修改了i的值,改变的是形参中拷贝的值,与n无关。所以这里n的值不变

引用类型作为函数参数

class Demo{
public static void main(String[] args){
String s = "Hello";
test(s);
System.out.println(s); //"Hello"
} public static void test(String s){
System.out.println(s); //"Hello"
s = "World";
}
}

在C/C++中,经常有这么一句话:“按值传递不能改变实参的值,按引用传递可以改变实参的值”,我们知道String 是一个引用,那么这里传递的是String的引用,我们在函数内部改变了s的值,在外部s的值是不是也改变了呢?我们首先估计会打印一个 "Hello"、一个"World"; 实际运行结果却是打印了两个 "Hello",那么是不是有问题呢?Java中到底存不存在按引用传递呢?为了回答这个问题,我们还是来一张内存图:

从上面的内存图来看,在函数中修改的仍然是形参的值,而对实参的值完全没有影响。如果想做到在函数中修改实参的值,请记住一点:拿到实参的地址,通过地址直接修改内存。

下面再来看一个例子:

class Demo{
public static void main(String[] args){
int[] array = new int[]{1, 2, 3, 4, 5};
test(array);
for(int i = 0; i < array.length; i++){
System.out.print(array[i]);
} System.out.println(); //98345
} public static void test(int[] array){
for(int i = 0; i < array.length; i++){
System.out.print(array[i]);
} System.out.println(); //12345
array[0] = 9;
array[1] = 8;
}
}

运行这个实例,可以看到这里它确实改变了,那么这里它发生了什么?跟上面一个字符串的例子相比有什么不同呢?还是来看看内存图

这段代码执行的过程中经历了3个主要步骤:

  • new一个数组对象,并且将数组对象的地址赋值给array 实参
  • 调用test函数时将array实参中保存的地址复制一份压入函数的参数列表中
  • 在test函数中,通过这个地址值来修改对应内存中的内容

这段代码与上面两段本质上的区别在于,这段代码通过引用类型中保存的地址值找到并修改了对应内存中内容,而上面的两段代码仅仅是在修改引用类型这个变量本身的值。

说到传递引用类型,那么我就想到在C/C++中一个经典的漏洞——缓冲区溢出漏洞,那么java程序中是否也存在这个问题呢?这里我准备了这样一段代码:

class Demo{
public static void main(String[] args){
byte[] buf = new byte[7];
test(buf);
} public static void test(byte[] buf){
for(int i = 0; i < 10; i++){
buf[i] = (byte)i;
}
}
}

如果是在C/C++中,这段代码可以正常执行只是最后可能会报错或者崩溃,但是赋值是成功的,这也就留给了黑客可利用的空间。

在Java中执行它会发现,它会报一个越界访问的异常,也就说这里赋值是失败的,不能直接往内存里面写,也就不存在这个漏洞了。

返回引用类型

Java方法返回基本类型的情况很简单,也就是将函数返回值放到某块内存中,然后进行一个复制操作。这里重点了解一下它在返回引用类型时与C/C++不同的点

在C/C++中返回一个类对象的时候,会调用拷贝构造将需要返回的类对象拷贝到对应保存类对象的位置,然后针对函数中的类对象调用它的析构函数进行资源回收,那么Java中返回类对象会进行哪些操作?

C/C++中返回一个类对象的指针时,外部需要自己调用delete或者其他操作进行析构。java中的类对象都是引用类型,在函数外部为何不需要额外调用析构呢?带着这些问题,来看下面这段代码:

class Demo{
public static void main(String[] args){
String s = test();
System.out.println(s);
} public static String test(){
// return new String("hello world");
return "Hello World";
}
}

这段代码 不管是用new也好还是直接返回也好,效果其实是一样的,下面是对应的内存分布图

这段代码首先在函数test中new一个对象,此时对应在堆内存中开辟一块空间来保存"hello world" 值,然后保存内存地址在寄存器或者其他某个位置,接着将这个地址值拷贝到main函数中的s中,最后回收test函数的栈空间。

这里实质上是返回了一个堆中的地址值,这里就回答了第一个问题:在返回类对象的时候其实返回的值对象所在的堆内存的地址。

接着来回答第二个问题:java中资源回收依赖与一个引用计数。每当对地址值进行一次拷贝时计数器加一,当回收拷贝值所在内存时计数器减一。这里在返回时,先将地址值保存到某个位置(比如C/C++中是将返回值保存在eax寄存器中)。此时计数器 + 1;然后将这个值拷贝到 main 函数的s变量中,此时计数器的值再 + 1,变为2,接着回收test函数栈空间,计数器 - 1,变为1,在main函数指向完成之后,main的栈空间也被回收,此时计数器 - 1,变为0,此时new出来的对象由Java的垃圾回收器进行回收。


Java 学习笔记(3)——函数的更多相关文章

  1. Java学习笔记——回调函数

    转载:http://wangyang0311.iteye.com/blog/368031 一般来说分为以下几步: 声明回调函数的统一接口interface A,包含方法callback(); 在调用类 ...

  2. 【Java学习笔记】函数的可变参数

    package p2; public class ParamterDemo { public static void main(String[] args) { int sum1 = add(4,5) ...

  3. 【Java学习笔记】函数使用

    package aaa; public class aaa { public static int add(int a,int b) { return a+b; } public static voi ...

  4. Java学习笔记(04)

    Java学习笔记(04) 如有不对或不足的地方,请给出建议,谢谢! 一.对象 面向对象的核心:找合适的对象做合适的事情 面向对象的编程思想:尽可能的用计算机语言来描述现实生活中的事物 面向对象:侧重于 ...

  5. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  6. [java学习笔记]java语言核心----面向对象之this关键字

    一.this关键字 体现:当成员变量和函数的局部变量重名时,可以使用this关键字来区别:在构造函数中调用其它构造函数 原理:         代表的是当前对象.         this就是所在函数 ...

  7. [java学习笔记]java语言核心----面向对象之构造函数

    1.构造函数概念 特点: 函数名与类名相同 不用定义返回值类型 没有具体的返回值 作用:                给对象进行初始化 注意: 默认构造函数 多个构造函数是以重载出现的 一个类中如果 ...

  8. Java学习笔记:语言基础

    Java学习笔记:语言基础 2014-1-31   最近开始学习Java,目的倒不在于想深入的掌握Java开发,而是想了解Java的基本语法,可以阅读Java源代码,从而拓展一些知识面.同时为学习An ...

  9. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

随机推荐

  1. Word画线条5大技巧,简单实用!

    [Word画线条5大技巧,简单实用!]1.输入三个“=”,回车,就是一条双直线:2.输入三个“~”,回车,就是一条波浪线:3.输入三个“”回车,就是一条虚线:4.输入三个“-”,回车,就是一条直线:5 ...

  2. 【Leetcode堆】数据流中的第K大元素(703)

    题目 设计一个找到数据流中第K大元素的类(class).注意是排序后的第K大元素,不是第K个不同的元素. 你的 KthLargest 类需要一个同时接收整数 k 和整数数组nums 的构造器,它包含数 ...

  3. Myeclipse自定义注释

    1.设置模板 Windows—Preference—Java—Code Style—Code Templates 图中, Configure generated code and comments中的 ...

  4. 在Swift中检查API的可用性

    http://www.cocoachina.com/swift/20150901/13283.html 本文由CocoaChina译者ALEX吴浩文翻译自Use Your Loaf博客 原文:Chec ...

  5. 2018-11-3-WPF-内部的5个窗口之-MediaContextNotificationWindow

    title author date CreateTime categories WPF 内部的5个窗口之 MediaContextNotificationWindow lindexi 2018-11- ...

  6. python 语法错误

  7. Spring data jpa hibernate:查询异常java.sql.SQLException: Column '列名' not found

    使用spring boot,jap,hibernate不小心的错误: java.sql.SQLException: Column '列名' not found: 这句话的意思是:找不到此列 为什么会出 ...

  8. java对象转化为json字符串并传到前台

    package cc.util; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import ...

  9. P4930「FJ2014集训」采药人的路径

    题目:P4930「FJ2014集训」采药人的路径 思路: 这篇不算题解,是让自己复习的,什么都没说清楚. 很久没有写点分治了,以前为了赶课件学的太急,板子都没打对就照着题解写题,导致学得很不扎实. 这 ...

  10. genymotion 和genymotion eclipse 插件安装 !

    昨天天有好心网友在群里共享了一个好用的 android 模拟器 genymotion 昨天就试用了下 真心流畅 各位不妨一试 http://www.genymotion.com/ doc https: ...