Java 堆和栈
在程序运行的时候,都是在内存中分配空间的,在空间中存放对象等,内存中分为2中区域,堆(heap) 和 栈(stack),堆和栈的是由JVM进行分配的。
方法的调用和局部变量都是放在栈中的,当方法执行完后栈中的资源就会被释放掉。
产生的对象及对象中的实例变量都是放在堆上。当某个对像没有线程使用或者没有引用指向它的时候,它就满足了垃圾回收的条件。
在java程序中时常会产生某个对象的实例,如:
Person p = new Person();
以上这段非常简单的代码完成了某个对象的创建,但是当调用new Person(); 实质上调用了构造函数。此处的小写p,是一个引用,它指向了堆中的Person对象。
构造函数是在被赋值给引用之前得到调用,“=”,就是赋值操作,底层是把产生的Person 对象的内存首地址赋值给了引用p,构造函数介入了new 的过程,也就是产生Person对象的过程来完成初始化的作用。
构造函数语法规则如下:
语法格式:
< modifiers> <class_name>([< argu_list>]) {
[< statements>]
}
举例:
class Person {
int age;
Person() { //不带参数的构造函数
age = 18;
}
Person(int i) {
age = i;// 带参数的构造函数
}
void setAge(int i) {
age = i;
}
}
在一个类中可以有多个参数不同的构造函数,实际就是构造函数的重载。在调用产生对象的时候,可以根据不同的实际情况调用不同的构造函数,比如某个系统,要求登陆者输入年龄,但不是必须的,如果登陆者填写了自己的年龄,则调用Person(int i) ,如果没有输入年龄则调用Person() 在这个构造函数中会自动的给age 赋值为18。
Java语言中,每个类都至少有一个构造方法。如果类的定义者没有显式的定义任何构造方法,系统将自动提供一个默认的构造方法,系统提供的默认方法,具有1. 默认构造方法没有参数。2.默认构造方法没有方法体
Java类中,一旦类的定义者显式定义了一个或多个构造方法,系统将不再提供默认的构造方法。
类的定义:
class Person {
int age;
void setAge(int w) { age = w; }
}
等价于:
class Person{
int age
Person() {}
void setAge(int w) { age = w; }
}
应用举例:
class Test{
public static void main(String args[]){
Person d = new Person() ;
d.setAge(120);
}
}
构造方法重载
构造方法重载举例:
public class Person{
public Person(String name, int age, String s) {…}
public Person(String name, int age) {…}
public Person(String name, String s) {…}
}
构造方法重载,参数列表必须不同
可以在构造方法的第一行使用this关键字调用其它(重载的)构造方法
构造方法重载举例:
public class Person {
private String name;
private int age;
private String sex;
public Person(String name, int age, String s) {
this.name = name;
this.age = age;
this.sex= s;
}
public Person(String name, int age) {
this(name, age, “female”);
}
public Person(String name, String d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
}
Java 内存的分配
内存从比较详细的角度可分四个区域:
code segment
主要存放代码。
data segment
主要存放静态变量和字符串常量
Stack
主要存放局部变量和执行方法
Heap
主要存放new出来的对象或者数组,数组其实也是对象,大家可以认为只要有new 关键字,就是对象,比如需要创建一个int 类型数组,需要 int[] I = new int[5]。
比如当我们执行了Person p = new Person(), 其作用是通过调用构造函数产生了一个Person对象,并且有引用p指向它。在内存中分配如下:
以上是不带参数的构造函数,如果是带参数的构造函数,则涉及到实例变量和局部变量的赋值过程,看以下实例:
构造函数
public class Person{
private String pname;
public int age;
Public Person(String pname,int age){
this.pname = pname;
this.age = age;
}
}
public static void main(String args[]){
person p0 = new Person(“test0”,5);
}
当运行入口函数的时候,内存中的执行过程如下:
1. 先分配空间
2. 构造函数后被实例变量被构造函数中的局部变量赋值
3. 构造函数执行完后局部变量消失
4. 引用指向对象
在构造函数Person(String pname,int age) 中的pname 和 age 是局部变量,构造方法外部的private String pname; public int age; 是实例变量, 当调用person p0 = new Person(“test0”,5),是先给这两个局部变量进行了赋值操作。
然后根据
构造函数中的this.pname = pname; this.age = age; 把局部变量的值赋给了实例变量内存结构图如下
然后构造方法执行完毕左边栈中pname 和age 就消失了,通过执行person p0 = new Person(“test0”,5) 中的“=”,对象的内存首地址就被赋值了p0 引用,内存结构图如下:
传值
看以下实例
public class Dog {
private String dogName;
private int age;
public Dog(String dogName,int age) {
this.dogName = dogName;
this.age = age;
}
public class MemoryDemo {
public static void main(String[] args) {
Dog dog1 = new Dog("xiaobai",5);
Dog dog2 = new Dog("xiaohei",6);
MemoryDemo md = new MemoryDemo();
int date = 9;
md.change3(date);
System.out.println("date:"+date);
md.change2(dog1);
System.out.println("dog1age"+dog1.getAge());
md.change1(dog1);
System.out.println("dogName:"+dog1.getAge());
}
public void change3(int date){
date = 10;
}
public void change2(Dog dog1){
dog1.setAge(8);
}
public void change1(Dog dog1){
dog1 = new Dog();
dog1.setAge(9);
}
}
运行程序打印输出:
date:9
dog1age8
dogName:8
实际上在java 中传递参数的时候都是传值操作,java 中没有传递引用,有的书上会提到传递 引用,实际上是为了好理解。
Java中传值操作实际上是对要传的值做了一份拷贝,基本数据与引用都是一样的。
在上面的例子中首先定义了 int date = 9; 然后调用了 md.change3(date),在运行的时候是把date 值在栈上做了一份拷贝操作,对新拷贝的date 赋值为10,原本存在的date仍然是9。
在执行change2 的时候,实际上是对引用做了一份拷贝,但是由于两个引用指向了堆中同样的一个对象,所以,当修改被拷贝引用指向的对象值的时候,原本的引用指向的对象也发生了改变。
在执行change1的时候,实际上是对引用做了一份拷贝,但是拷贝出来的引用又指向了新对象,与被拷贝的引用指向的对象是不一样的,所以修改后的对象的时候对前者指向的对象没有影响。
。。。。。。String