Java虚拟机将内存分为堆内存(heap)和栈内存(stack)。
在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块(也就是一对花括号门之间)定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放并为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆内存用来存放由New创建的对象和数组,在堆栈中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象,引用变量就相当于是为数组或对象起的一个名称(叫代号也行)。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占据的内存不合被释放,数组和对象在没有引用变量指向它时,才会变为垃圾,不能再被使用,但仍然占据内存空间不放,在随后一个不确定的时间被垃圾回收器收走(释放掉)。这也是Java比较占内存的原因。
至于类变量(静态)和静态方法,是在一个叫方法区的内存中创建的,是在类加载的时候即被创建而且只创建一次,所以如果一个类有类变量,不管你new多少个这个类的对象,他的类变量只创建一次,只有一个!
class Test{
public static void main(String [] args){
Point pt = new Point();
int [] it = new int[100];
}
}
class Point{
int x;
int y;
Point(){
x =0;
y=0;
}
}
上述例子中,创建一个Point对象,并创建一个Point对象的引用pt,同时创建一个int类型的数组,和该数组对象的一个引用it。其中it,pt存放在栈内存中,而利用new创建的Point对象和int数组对象存放在堆内存中。
问:Point 中的变量存放在什么内存中?
<[if !supportLists]>1、 <[endif]>static块中的内容享有最高优先级,初始化后存储在静态内存区域(方法区),而且只初始化一次。
<[if !supportLists]>2、 <[endif]>如果有父类,首先初始化父类。
<[if !supportLists]>3、 <[endif]>如果父类有static块,首先初始化父类的static块,然后初始化子类的static块,然后初始化父类的实例变量和构造函数。然后初始化子类的实例变量和构造函数!
初始化顺序:
先父后子
先父类的类变量(静态变量),然后是父类的static块,然后是子类的static块,然后是父类的实例变量,然后是父类的构造函数,然后是子类的类变量和实力变量,然后是子类的构造函数。
构造函数上一个特殊的方法,用来创建对象,构造函数的名称必须与类的名称相同,而且不能有任何返回值(当然也不包扩void),如果你没有定义构造函数,java会给你默认一个不带参数的构造函数。你可以定义带任意多参数的构造函数,这叫构造函数的重载如果你定义了构造函数,java将不会在默认构造函数。
一般构造函数都被声明为public的,但是在单例模式中,构造函数会被声明为private的,也就是不允许你用new关键字创建这个类。
构造函数在初始化中的优先级,仅次于static块、类变量和实例变量。符合先父后子的顺序(想想也是,没有父亲,哪来的孩子?)。
实例变量:是类的变量,非线程安全 存放在堆内存中 在static之后初始化
类变量:静态的实例变量,非线程安全 存放在方法区内
局部变量:方法内部的变量,线程安全 存放在栈内存内 啥时候调用此方法,啥时候初始化
两个原则:
<[if !supportLists]>1、 <[endif]>保证软件没有重复的代码
<[if !supportLists]>2、 <[endif]>保证代码是干净的,没有异味的,并且富有表现力,可以清楚的显示程序员的意图。
基于以上两条简单的原则,我们要经常对我们的代码进行重构!
变量名称规则:
<[if !supportLists]>1、 <[endif]>非数字开头
<[if !supportLists]>2、 <[endif]>第一个字符小写,然后采取驼峰式的命名,比如companyName
<[if !supportLists]>3、 <[endif]>名称最好有意义,比如companyName是个好名字,而qiyeMingCheng,就是个坏的命名。
类命名:
<[if !supportLists]>1、 <[endif]>类命名采用大写驼峰式:比如CreateUser
<[if !supportLists]>2、 <[endif]>尽量不要用复数作为类的名字。比如Student 和Students,如果要new一个对象,Students让人觉着别扭。
注意:java是大小写敏感的!
this指当前正在执行的对象(的引用)
用敏捷JAVA书上的原文:关键字this指向当前对象的引用,当前对象是指在运行的代码所属的对象。
另外;this关键字还可以用来构建构造函数链:
public Inter(String football,double total){
this.football = football;
this.total = total;
}
public Inter(String football){
this(football,100000.0);//必须在第一行
}
super,指向当前对象的父类。用super可以调用当前对象父类的public protected等关键字修饰的方法、变量,但是不能调用private关键字修饰的东西,因为private是私有的。
Super可以调用父类的构造函数:
public class InterII extends Inter {
public InterII(){
super();//必须在第一行
}
public static void main(String [] args){
}
}
关于this,看一个有趣的小题目:
package com;
class Leaf{
private int i = 0;
Leaf getAnother(){
i++;
return this;
}
void print(){
System.out.println(i);
}
public static void main(String [] args){
Leaf x = new Leaf();
x.getAnother().getAnother().getAnother().print();
}
}
1、要随时重构,消除不必要的代码。
2、暴露该暴露的,隐藏该隐藏的。比如不要随便将成员变量直接暴露给其他的对象,所以您会用到private关键字。
<[if !supportLists]>3、 <[endif]>消除不必要的局部变量。
String name = getName();
return name;
更改为:
return getName() 会更好
<[if !supportLists]>4、 <[endif]>不要将字符串写的到处都是,要用类常量(final static)替换字符串或者数字。
<[if !supportLists]>5、 <[endif]>要进行必要的封装。
public class Student{
private List students = new ArrayList();
public List getAllStudents(){
return students;
}
}
然后在别的对象中可以这样调用来增加、删除学生
Student student = new Student();
Student. getAllStudents().size() //所有学生的数目
Student. getAllStudents().add(new Student())//新增一个学生
Student. getAllStudents().get(0)//获得第一个学生
Student. getAllStudents().remove(1);//删除第二个学生
如果这样写,别的对象就默认了用ArrayList存储学生这个事实,以后如果想换别的数据结构存储学生,将变的困难,正确的做法是:隐藏实现,只暴露接口:
public class Student{
private List students = new ArrayList();
public Student get(int i){
return students.get(i);
}
public int size(){
return students.size();
}
……
}
这样用户看到的永远都是size()获得学生数量。Get(i),获得某个学生,而不知道内部是如何实现的。就像司机知道方向盘可以控制汽车的方向,而永远不知道内部是否更换了实现的方式。
<[if !supportLists]>6、 <[endif]>尽量消除不赞成警告。