どのように覚えているが、時には説明なしの原理を利用し、議論の印刷ができない値で渡され、参照によって渡されるには、Javaとの最初の接触の困難な時期であるが、時には実用化のための構文を覚えている:Javaベースの人々が知って学びました話題は議論がある:Javaは唯一の値によって渡されたいくつかのフォーラムの投稿、両方言った、といくつかのブログ;これはのは、フォーラムのブログに、書籍に、このトピックに関するいくつかの研究をやらせる、少し混乱しました言って、信頼性の高い答えを得るための研究を行います。
出てくると例の相当数を説明することができるようになります、実際には、値が渡され、百度の構文と使用のために参照によって渡され、多分あなたは例を見てすることは理解しているようだが、インタビューのためのあなたのペンの質問には、ポイントを行う際に、この知識私は胸成熟した答弁書を感じるが、間違っていることが判明し、またはあなたは、単に行うことはありませんされます。
その理由は何ですか?
あなたは、知識の完全な理解を持ってその毛皮を知らないからです。おなじみの構文に、非常にシンプルであることは、コードの行を理解することは難しくありませんが、知識が直列に理解、習得を学んだことができ、それはこの時に、非常に困難であり、かつ参照によって渡された値を渡す、小扁は、前からとなります最初の基礎を学んだ、メモリモデルから開始し、値によって渡され、読者が私と一緒にクマの希望よりも多くのステップ、そう長く、より多くの知識、によってステップの原則の本質への参照を渡してしまいます。
実際のパラメータの1種類
構文の一連の自分自身を思い出させるしてみましょう:
- FUNCでそのように::パラメータ、着信メソッドが呼び出される渡す必要がある(INT A)A、それだけFUNC中に呼び出されることは理にかなって、つまりパラメータは、メソッドFUNCの実装が完了した後、メモリ空間を割り当てられます、空き領域が破壊される、それは存在しません。
- 引数:メソッドが呼び出されると、メソッドが呼び出されたときに渡される前に、実際の値が渡されたときにメソッドが呼び出されたが、それが初期化されています。
栗の場合:
1public static void func(int a){
2 a=20;
3 System.out.println(a);
4}
5public static void main(String[] args) {
6 int a=10;//实参
7 func(a);
8}
复制代码
例
int型のA = 10;すでにFUNCメソッドを呼び出したときに呼び出される前に作成され、初期化中に、彼がパラメータとして渡されたので、これは引数です。
そして、funcが(INT A)のみ、そのライフサイクルで呼び出されるFUNCに始まった、ともJVMを解放されるFUNC呼び出し、終了後,,これはパラメータである。とき
2. Javaデータ型
いわゆるデータ型は、我々はプログラムが実行される前に、これらのコードは、ハードディスク内に存在する、プログラムコードファイルとは、静的なリソースで構成されていることを知っているプログラミング言語のメモリの抽象的表現で、プログラムが実行を開始し、コードは次のようになりますコンピュータに変換を実行するためにメモリに内容を把握することができます。
故に
データ型定義は、コンピュータのメモリにビットメモリの値を決定する方法であり、データの同じ種類のに使用される基本的なプログラミング言語の形式で格納されています。
したがって、メモリに格納されたデータは、格納形式は、区画及びデータタイプの格納位置です。
だから、
Javaデータ型があるのですか?
- 基本タイプ:最小粒子径構築されたプログラミング言語のデータ型。それは、次の4つのカテゴリの8種類が含まれています。
整数型の4種類:バイト、ショート、整数、ロング
2忠浮動小数点型:フロート、ダブル
1つのワード文字の種類:チャー
1忠のブール型:Boolean
- 参照型:参照、また、ハンドル、参照型として知られているが、実際の内容は、アドレスに格納されている場合に定義されたプログラミング言語のデータ値へのアドレスの形でハンドル。これは含まれています:
クラス
インタフェース
アレイ
データ型、標準化された手続き上のデータのJVM管理、異なる種類のデータ、その保存形式と場所は、同じではありません知っているとJVMは、各種のデータを格納する方法では、まずJVMを理解する必要がありますメモリ分割し、各部分の機能。
3.JVMメモリ分割と機能
Java言語自体がメモリを動作していないJavaがメモリ領域に分割されるので、それは、管理および制御するためにJVMにすべてのJVM、JVMのメモリの前に言った部門のゾーニングされ、我々は最初のJavaを見てプログラムの実行は、以下に示すように:
見ることができます:Javaコードは、後にバイトコードコンパイラにコンパイルされ、JVMはオープンメモリ空間(また、実行時のデータ領域と呼ばれる)、クラスローダによって実行時に必要なプログラムを格納するランタイムデータ領域に追加されましたデータ情報は、この領域のデータは、以下の成分からなります:
1.仮想マシンのスタック
2.ヒープ
3.プログラムカウンタ
4.メソッド領域
ネイティブメソッドスタック
その後、我々はプログラムの実行プロセスを格納するために使用される原則と具体的なデータの各部分を見てください。
1.仮想マシンのスタック
VMスタックメモリモデルJavaメソッドが実行され、スタックフレームがスタックに格納され、各スタックは、対応する方法をフレームと呼ばれる、メソッドの呼び出し手順は、仮想マシン内のスタックのスタックフレームをプッシュする手順に対応します。
、プログラムは、(スタックの最上部)をスタックフレームスタックを作成し、対応する方法でスレッドを起動したとき、方法の終了時に、スタックは、スレッドのスタックが分離されている間、つまり、スレッドプライベートポップスタックフレーム。
以下の図は、モデルとJavaスタックフレームスタックの組成を示します。
フレームをスタック :仮想マシンのデータ構造、メソッド呼び出しと実行の方法をサポートするために使用され、それは仮想マシンのスタックのスタック要素の仮想マシンの実行時データ領域です。
各スタックフレームであって、
- ローカル変数テーブル:使用されるローカル変数(非静的変数、関数パラメータ)ストレージ方法。変数は、基本データ型である場合、特定のストレージ・オブジェクトに向けられたときに参照型変数、値は直接、格納されています。
- オペランドスタック:オペランドスタックの意味の範囲内でスタックを意味し、「スタックベースの実行エンジン」と呼ばれるJava仮想マシン解釈実行エンジン、。
- 参照のポインティング実行時定数プール:定数への参照は、プログラムの実行を格納するために使用することができます。
- メソッドは、アドレスを返す:ストレージ・メソッドの実行後の戻りアドレスが完了しています。
2.ヒープ:
ヒープは、オブジェクト自体と配列を格納するために使用される、JVMでのみヒープ、したがって、ヒープは、すべてのスレッドによって共有されます。
メソッド領域3:
この方法は、それが、複数のスレッドがメソッド領域にアクセスするスレッドセーフであり、メモリの論理領域のすべてのスレッドで共有領域は、JVMは、コンテンツを共有することができるいくつかのスレッドを格納するために使用される唯一のメソッド領域を、ある場合に同じコンテンツのみスレッドがデータをロードすることができ、別のスレッドが待つことができます。
領域のコンテンツを格納することができる方法は、次のとおり、クラス、クラスの型(クラスまたはインタフェース)、整然とした完全修飾クラス名に直接インタフェースのクラス、重量クラスの直接スーパーアクセス修飾子完全修飾名のフルパス名リスト、定数プール(フィールド、メソッド情報、静的変数、基準の種類(クラス))ようにと。
4. Aネイティブメソッドスタック:
本地方法栈的功能和虚拟机栈是基本一致的,并且也是线程私有的,它们的区别在于虚拟机栈是为执行Java方法服务的,而本地方法栈是为执行本地方法服务的。
有人会疑惑:什么是本地方法?为什么Java还要调用本地方法?
5. 程序计数器:
线程私有的。
记录着当前线程所执行的字节码的行号指示器,在程序运行过程中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。
4. 数据如何在内存中存储?
从上面程序运行图我们可以看到,JVM在程序运行时的内存分配有三个地方:
- 堆
- 栈
- 静态方法区
- 常量区
相应地,每个存储区域都有自己的内存分配策略:
- 堆式:
- 栈式
- 静态
我们已经知道:Java中的数据类型有基本数据类型和引用数据类型,那么这些数据的存储都使用哪一种策略呢?
这里要分以下的情况进行探究:
1. 基本数据类型的存储:
- A. 基本数据类型的局部变量
- B. 基本数据类型的成员变量
- C. 基本数据类型的静态变量
2. 引用数据类型的存储
1. 基本数据类型的存储
我们分别来研究一下:
A.基本数据类型的局部变量
- 定义基本数据类型的局部变量以及数据都是直接存储在内存中的栈上,也就是前面说到的“虚拟机栈”,数据本身的值就是存储在栈空间里面。
如上图,在方法内定义的变量直接存储在栈中,如
1int age=50;
2int weight=50;
3int grade=6;
复制代码
当我们写“int age=50;”,其实是分为两步的:
1int age;//定义变量
2age=50;//赋值
复制代码
首先JVM创建一个名为age的变量,存于局部变量表中,然后去栈中查找是否存在有字面量值为50的内容,如果有就直接把age指向这个地址,如果没有,JVM会在栈中开辟一块空间来存储“50”这个内容,并且把age指向这个地址。因此我们可以知道:
我们声明并初始化基本数据类型的局部变量时,变量名以及字面量值都是存储在栈中,而且是真实的内容。
我们再来看“int weight=50;”,按照刚才的思路:字面量为50的内容在栈中已经存在,因此weight是直接指向这个地址的。由此可见:栈中的数据在当前线程下是共享的。
那么如果再执行下面的代码呢?
1weight=40;
复制代码
当代码中重新给weight变量进行赋值时,JVM会去栈中寻找字面量为40的内容,发现没有,就会开辟一块内存空间存储40这个内容,并且把weight指向这个地址。由此可知:
基本数据类型的数据本身是不会改变的,当局部变量重新赋值时,并不是在内存中改变字面量内容,而是重新在栈中寻找已存在的相同的数据,若栈中不存在,则重新开辟内存存新数据,并且把要重新赋值的局部变量的引用指向新数据所在地址。
B. 基本数据类型的成员变量
成员变量:顾名思义,就是在类体中定义的变量。
看下图:
我们看per的地址指向的是堆内存中的一块区域,我们来还原一下代码:
1public class Person{
2 private int age;
3 private String name;
4 private int grade;
5//篇幅较长,省略setter getter方法
6 static void run(){
7 System.out.println("run....");
8 };
9}
10
11//调用
12Person per=new Person();
复制代码
同样是局部变量的age、name、grade却被存储到了堆中为per对象开辟的一块空间中。因此可知:基本数据类型的成员变量名和值都存储于堆中,其生命周期和对象的是一致的。
C. 基本数据类型的静态变量
前面提到方法区用来存储一些共享数据,因此基本数据类型的静态变量名以及值存储于方法区的运行时常量池中,静态变量随类加载而加载,随类消失而消失
2. 引用数据类型的存储:
上面提到:堆是用来存储对象本身和数组,而引用(句柄)存放的是实际内容的地址值,因此通过上面的程序运行图,也可以看出,当我们定义一个对象时
1Person per=new Person();
复制代码
实际上,它也是有两个过程:
1Person per;//定义变量
2per=new Person();//赋值
复制代码
在执行Person per;时,JVM先在虚拟机栈中的变量表中开辟一块内存存放per变量,在执行per=new Person()时,JVM会创建一个Person类的实例对象并在堆中开辟一块内存存储这个实例,同时把实例的地址值赋值给per变量。因此可见:
对于引用数据类型的对象/数组,变量名存在栈中,变量值存储的是对象的地址,并不是对象的实际内容。
6. 值传递和引用传递
前面已经介绍过形参和实参,也介绍了数据类型以及数据在内存中的存储形式,接下来,就是文章的主题:值传递和引用的传递。
值传递:
在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。
来看个例子:
1public static void valueCrossTest(int age,float weight){
2 System.out.println("传入的age:"+age);
3 System.out.println("传入的weight:"+weight);
4 age=33;
5 weight=89.5f;
6 System.out.println("方法内重新赋值后的age:"+age);
7 System.out.println("方法内重新赋值后的weight:"+weight);
8 }
9
10//测试
11public static void main(String[] args) {
12 int a=25;
13 float w=77.5f;
14 valueCrossTest(a,w);
15 System.out.println("方法执行后的age:"+a);
16 System.out.println("方法执行后的weight:"+w);
17}
复制代码
输出结果:
1传入的age:25
2传入的weight:77.5
3
4方法内重新赋值后的age:33
5方法内重新赋值后的weight:89.5
6
7方法执行后的age:25
8方法执行后的weight:77.5
复制代码
从上面的打印结果可以看到:
a和w作为实参传入valueCrossTest之后,无论在方法内做了什么操作,最终a和w都没变化。
这是什么造型呢?!!
下面我们根据上面学到的知识点,进行详细的分析:
首先程序运行时,调用mian()方法,此时JVM为main()方法往虚拟机栈中压入一个栈帧,即为当前栈帧,用来存放main()中的局部变量表(包括参数)、操作栈、方法出口等信息,如a和w都是mian()方法中的局部变量,因此可以断定,a和w是躺着mian方法所在的栈帧中
如图:
押圧スタックにvalueCrossTest()メソッドを実行するだけでなく、仮想マシンのJVMスタックの場合、ローカル変数にvalueCrossTest()情報を格納するための、現在のスタックフレームであり、年齢と体重は、このように横たわっています方法valueCrossTestスタックフレームが配置され、且つ、およびWの値から示されるように、それらの値は、コピーのコピーを得られます:
。
そしてコンテンツよい従って年齢、W、及び対応する重みが方法における再割り当ては、実際のプロセスに示すように、矛盾している:
すなわち、現在のスタックフレームは、年齢及び体重の変化であるが、変更(valueCrossTest方法ここ場合スタック・フレーム)の含有量は、方法の実行の終了後、これらのローカル変数は、コンテンツがまだ初期化されている場合、スタックの背面上部には、現在のスタックフレームとなり、再びWを出力するスタックフレームミアン方法を破壊します。
したがって:
渡された値が転送のコピーが真である、パラメータの変更は、コンテンツに対応する実際のパラメータに影響を与えないだろうか、つまり、元のコンテンツのコピーの動作には影響を与えません。
参照渡し:
「基準」方法は、プロセスによってアドレスの引数は、in vivoで呼び出される対応するパラメータに渡され呼ばれるアドレス値の実際の内容、を指している、形状パラメータ及び引数は、快適なメモリ・アドレスを介して方向付け実際のコンテンツは、パラメータ操作に影響を与えます。
栗の場合:
オブジェクトを定義します。
1public class Person {
2 private String name;
3 private int age;
4
5 public String getName() {
6 return name;
7 }
8 public void setName(String name) {
9 this.name = name;
10 }
11 public int getAge() {
12 return age;
13 }
14 public void setAge(int age) {
15 this.age = age;
16 }
17}
复制代码
私たちは、機能テストを記述します。
1public static void PersonCrossTest(Person person){
2 System.out.println("传入的person的name:"+person.getName());
3 person.setName("我是张小龙");
4 System.out.println("方法内重新赋值后的name:"+person.getName());
5 }
6//测试
7public static void main(String[] args) {
8 Person p=new Person();
9 p.setName("我是马化腾");
10 p.setAge(45);
11 PersonCrossTest(p);
12 System.out.println("方法执行后的name:"+p.getName());
13}
复制代码
出力:
1传入的person的name:我是马化腾
2方法内重新赋值后的name:我是张小龙
3方法执行后的name:我是张小龙
复制代码
図から分かるように、personCrossTest()メソッドを介して人を実行した後、コンテンツは、上記確認した、変更された「基準転送」を実際のオブジェクトの内容を変更する、動作パラメータのために、。
さて、ここで結び目にまだ?
いや、それほど単純ではない、
所望の効果が見ることができる
だけで唯一の例の選挙からです!!!
、のは上記の例に加え、コードの行にいくつかの変更を作ろう
1public static void PersonCrossTest(Person person){
2 System.out.println("传入的person的name:"+person.getName());
3 person=new Person();//加多此行代码
4 person.setName("我是张小龙");
5 System.out.println("方法内重新赋值后的name:"+person.getName());
6 }
复制代码
出力:
1传入的person的name:我是马化腾
2方法内重新赋值后的name:我是张小龙
3方法执行后的name:我是马化腾
复制代码
`
なぜ出力し、それの最後のと同じではありませんか?
表示するには問題は何ですか?
上記JVMメモリモデルによれば、次のコードときにmain()メソッドへのプログラムの実行を知っている、配列は、Javaヒープメモリ内のオブジェクトであり、ヒープ領域を共有していることができます
1Person p=new Person();
2 p.setName("我是马化腾");
3 p.setAge(45);
4 PersonCrossTest(p);
复制代码
JVMは、図示のように、スレッドのスタック領域は、メインメモリヒープ領域P Pオブジェクトを参照実アドレスを作成するために、一方()メソッドすべてのコンテンツのpオブジェクトを格納するために使用されるメモリのヒープに開かれます。
PersonCrossTest()メソッドを実行するときに、コードのこのようなラインの方法理由:
1person=new Person();
复制代码
JVMは、アドレスが「xo3333」であるならば、それは人のパラメータアドレスで指摘されたこの時点では、スタック内の()開かれた新しい人を格納するための追加のメモリを必要とし、それらが実際に参照渡しされている場合は、上記:参照が転送します同一のオブジェクトを指し示すパラメータ引数は、パラメータ変更の操作は、引数オブジェクトを変更します。
これを導入することができる:引数には、人新しく作成されたオブジェクトのアドレスを指す必要があり、そうPersonCrossTest()終了を実行した後、最終的な出力は、背後にあるコンテンツ作成の対象とすべきです。
しかし実際には、最終的な出力が、憶測は、最終的な出力は、まだ最初に作成されたコンテンツオブジェクトである、私たちのようではありません。
このように:参照渡し、それは、Javaには存在しません。
しかし、一部の人々が疑問に思うことがあります。なぜ、最初の例では、元のオブジェクトがそれを変えるの内容を原因となります方法におけるパラメータの内容を変更しますか?
これはれる:着信パラメータ引数は値によって渡されたときの両方が基本タイプと参照型であり、むしろコンテンツ自体よりも、配信されたコピーです。
それは単に、この時点でのpのポインティングオブジェクトのアドレスをコピーし、参考人と本当の相関p引数の方法で形成され、そこに見ることができます。
pと人は同じオブジェクトを指しています。
因此在第一个例子中,对形参p的操作,会影响到实参对应的对象内容。而在第二个例子中,当执行到new Person()之后,JVM在堆内开辟一块空间存储新对象,并且把person改成指向新对象的地址,此时:
p依旧是指向旧的对象,person指向新对象的地址。
所以此时对person的操作,实际上是对新对象的操作,于实参p中对应的对象毫无关系。
结语
因此可见:在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。
只是在传递过程中:
如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。
如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。
转自:https://juejin.im/post/5bce68226fb9a05ce46a0476