2016-03-06 11:02:34 +0000   |     java thinking in java oop memory   |   Viewed times   |    

摘要

这一章主要讲了四件事:

  1. 对象和内存
  2. 编程风格
  3. Java开发环境
  4. javadoc生成注释文档

其中第一项“对象和内存”,我引申了一下,对java动态分配内存做了一个了解。第四项“Java开发环境”,因为之前一篇文章已经总结过,就不重复了。

Java内存的故事

Stack堆栈,和Heap堆

讲Java之前,先科普一下计算机里内存内存的结构。 memoryLayout 每当一个程序被执行,系统就要为它开启一个进程,并且为它分配内存。从低址区到高址区,分成几个不同的区域。

  1. 低址:存放程序代码本身。
  2. 次低址:存放全局变量,无论是初始化的还是未初始化的。
  3. 中址:就是堆和堆栈的区域。用来储存进程运行过程中产生的变量。
  4. 最高址:为系统额外预留的空间。我们无法操作。
stack(堆栈)

从高址区往下延展。用来存储Scoped Variable(限域变量)。简单说就是已知存储空间以及生命周期的变量。为什么stack效率高呢?因为变量大小确定,都是紧挨着储存的,在堆栈中创建和释放储存空间只要一条汇编语言,分别是将栈顶指针向下和向上移动。而stack本身又是LIFO(Last in First out)的,所以效率极高。

heap(堆)

从较低地址区网上移动。heap是个动态内存池。从下面的图我们看的很清楚,heap不像stack那样是数据是连续的,而且使用LIFO机制。heap的数据是不连续的,动态随便乱贴的。创建和释放效率都不高。 heap

Java对象存在哪儿

“引用”存放在”stack”(堆栈)中

Java声称一切都是对象,

Java完全采用了动态内存分配方式。这是因为所有创建的对象全都继承自单根基类Object,而这个Object又只能以唯一的方式从heap堆中创建。

javaMemory 对于Java应用程序,包含两个池:Java堆和本机堆。本机堆里包含JVM堆。剩下的就是空闲Native堆。

上图中显得Java数据好像全在heap里,完全不用stack。这是不准确的,Java用stack!实际上,Java每个对象都有一个指向他的指针,叫”引用“。可以理解为C++的指针。Java不是不用指针,只是泛化他,所有对象都用一个指针,所以反而不用特殊标示了。看下面这个声明,

String s;

这里s创建的只是”引用”,并不是”对象”。这时候还没有对象,只有引用。这时候如果要求输出s,系统会返回错误。

对象的引用存放在”stack“(堆栈)中。

“对象”存放在”heap”(堆)中

new关键字,负责创建对象。对象存放在heap(堆)中。看下面的例子,

String s = new String("hello");

这时候s是reference引用,存在stack堆栈里。String(hello)是对象,存在heap堆中。

当然,我们可以用另一种”奇怪”的方式声明一个String,

String s = "hello";

这里不用new关键字,只不过是Java的一个”特性”,并不是本性。只是说Java用了特殊的方法,形式上允许不用new来创建一个String对象,可以直接赋值。但本质上,Java内部处理以后,这个”hello”还是以对象的方式存在heap区里。

例外

为什么在“Think in Java 读书笔记:第一章 - 面向对象导论”里我要特别强调八种基本类型呢?

因为他们不属于对象,不存放在heap堆区,而是直接存在stack堆栈区。

让我们再瞻仰一下他们伟岸的面容。其实下面有九种,因为加上了一个void空型。 basicType

像英雄致敬!但Java还是给他们各写了一个包装器类,如果愿意,程序员还是可以以声明这些包装器类的方式创建这些变量。例如,

//int基本型存在stack区
int it = 5;

//int的包装器类java.lang.Integer,用new创建,存在heap区
Integer itg = new Integer(5);
//char基本型存在stack区
char c = "x";

//char的包装器类java.lang.Character,用new创建,存在heap区
Character ch = new Character("x");

一个Java对象里有些啥

Java一切都是对象的理念很美,但付出的内存的代价也是巨大的。对象的元数据,大小相当惊人,一般都是他们存放的数据本身的好几倍。这里强烈推荐一篇文章:IBM developerWorks - 《从Java代码到Java堆》,讲地非常清楚。这里只做一个简单搬运,方便以后自己查阅。

  1. :一个指向类信息的指针,描述了对象类型。举例来说,对于 java.lang.Integer 对象,这是 java.lang.Integer 类的一个指针。注意这个指针不在heap区,而是在stack区。
  2. 标记:一组标记,描述了对象的状态,包括对象的散列码(如果有),以及对象的形状(也就是说,对象是否是数组)。
  3. :对象的同步信息,也就是说,对象目前是否正在同步。

对象元数据后紧跟着对象数据本身,包括对象实例中存储的字段。对于 java.lang.Integer 对象,这就是一个 int。 如果您正在运行一个 32 位 JVM,那么在创建 java.lang.Integer 对象实例时,对象的布局可能如下图所示。也就是说,为了储存一个32位的int数据,java要占用128位内存。 javaObject

对于一个int[]数组,以及String对象,情况都是这样。这里需要特别说一下,和传统C和C++不同,Java数组不是简单的一个简单的连续内存块,不属于基本型,而是被强制包装成一个对象。为的是避免对内存的误操作。当一个数组被声明之后,会在stack区产生一个引用,如果还没有被初始化,会自动赋予关键字null,java一看到这个

javaObject_2 javaObject_3

作用域

传统C语言的作用域

像传统的C语言,有一个作用域的概念。作用域决定了变量的生命周期和可见性。在C,C++中,作用域都用一对花括号{}来标明。出了花括号,括号内声明的变量都会被自动释放。意味着,stack区的指针上移,变量消失。特殊的用new关键字动态生成在heap区的变量,需要手动用delete()来清除。

Java的作用域

前面说过了,java把对象的引用存在stack区,把对象存在heap区。看下面这张图, memoryLayout 所有存在stack区的内容,还是遵守花括号{}的作用域,比如基本型i=4y=2,还有对象的引用cls1,出了域的终点–花括号,就都消失了。但在heap区的对象本身还存在,并没有被销毁。只是我们已经找不到他了,因为指向他的引用cls1已经擦除了。

这是Java很好的一个特性,因为只要我们注意传递和复制对象的引用,在后面的程序中我们一直可以调用这个对象。因为只要有一个引用能让我们找到他,他一直在那儿。

另一个好处(对程序员),但也可以说是坏处(对系统),就是一旦一个对象完全失去引用,我们不必像C++这样手动用delete()释放heap区的对象。我们可以完全不去管它。但我们不管他,意味着必须有别人去管它,这就是JVM里的垃圾回收器(Garbage Collection)。但因为处理的对象都是heap区里的家伙,所以开销要大很多,对系统负担也很大。

垃圾回收

本章初涉垃圾回收,本在第五章做了一个初步的了解,后为了方便查阅,独立出来成一篇《Java垃圾回收初探》

变量默认值

类中的基本数据类型,声明的时候就算没有赋值,java会自动赋予一个默认值: defaultValue_1 defaultValue_2

但不是“类中字段”,或者不是“基本型”的变量,比如说方法里临时用到的变量不会给默认值。所以声明变量的时候,要养成习惯随手给个赋值。哪怕只是给个0null默认值。

变量命名

不重复类名

避免类的重复,以我的网站url:ciaoshen.com作为package路径。所以SayHello.java源码搬到java根目录下的:~/java/com/ciaoshen/test包里。SayHello.class的类名变为com.ciaoshen.test.SayHello

命名风格

为了让我的代码更容易被别人理解,严格遵守google java style

  1. 包名packagenamestyle,全小写,不用连字符,尽量不用数字。例如,com.ciaoshen.test
  2. 类名UpperCamelCase,单词首字母全大写。
  3. 常量名CONSTANT_STYLE,全大写,加下划线连字符。
  4. 方法名,变量名,及其他lowerCamelCase,除首字母外单词首字母全大写。

static关键字

//
class StaticTest {
  static int i=10;
}

//st1.i, st2.i, StaticTest.i,全部指向同一个内存地址。
public static void main(String args[]){
  StaticTest st1 = new StaticTest();
  StaticTest st2 = new StaticTest();

  System.out.println(st1.i);
  System.out.println(st2.i);
  System.out.println(StaticTest.i);
}

无论我们创建多少个StaticTest对象,或者根本不创建对象,对象中的静态字段i,都有一个内存空间,之后所有的对象对i的调用都指向这同一个内存空间。

作者说,static方法经常被用来做“牧羊人”的角色,负责看护与其隶属同一类型的实例群

import语句

哪怕不使用任何import语句,java.lang包都会被自动导入到每一个java文件中。

安装Java环境

之前的一篇文章“Mac上自搭舒服的Java环境,别总是用IDE”已经总结了。这里不复述。

javadoc注释文档

清晰,完整的注释,是一个程序猿的职业操守。要养成习惯

javadoc是用于提取注释的工具,属于JDK的一部分。可以通过注释,自动产生HTML文档。注释部分以/**开始,以*/结束。主要有三种注释:

//: object/Documentation1.java
/** A class comment 类注释 */
public class Documentation1 {
  /** A field comment 域注释*/
  public int i;
  /** A method comment 方法注释*/
  public void f() {}
} ///:~

也可以用@加注一些tag标签。比如@see@author@version@since@param@return等等。

javadoc注释的例子

下面是作者给的一个例子:

//: object/HelloDate.java
import java.util.*;
/** The first Thinking in Java example program.
 * Displays a string and today’s date.
 * @author Bruce Eckel
 * @author www.MindView.net
 * @version 4.0
*/
public class HelloDate {
  /** Entry point to class & application.
   * @param args array of string arguments
   * @throws exceptions No exceptions thrown
  */
  public static void main(String[] args) {
    System.out.println("Hello, it’s: ");
    System.out.println(new Date());
  }
} /* Output: (55% match)
Hello, it’s:
Wed Oct 05 14:39:36 MDT 2005
*///:~

用javadoc命令生成注释文档

基本命令
javadoc [options] [packagenames] [sourcefiles] [@files]

如果什么options也设置,直接给[packagenames] [sourcefiles]两个参数。下面我指定了com.ciaoshen.test包里的SayHello类。

javadoc com.ciaoshen.test ~/java/com/ciaoshen/test/SayHello.java
递归生成整个项目的注释文档

为了更有序地更完整地生成整个项目的注释文档,有三个重要的Option需要设定:

javadoc -d /yourdocpath -subpackages /packagename -sourcepath /yoursourcepath
  1. -d: 指定API文档的输出目录,默认是当前目录。建议总是指定该参数。
  2. -subpackages: 以递归的方式处理各子包。关键参数!如果不使用本参数,每次只能处理一个子包(或需手工列出所有子包)。
  3. -sourcepath: 指定源代码路径,默认是当前目录。 此参数通常是必须的。

下面这条命令可以递归生成我整个~/java根目录下所有代码的注释文档,然后存放在~/java/doc目录下。-author-version可以让javadoc自动收录我@author@version标签的内容。

javadoc -d ~/java/doc -subpackages . -sourcepath ~/java/ -author -version

下图为自动生成的文档内容: javadoc2 sayHello2 sayHello3 sayHello4

练习题

第一题

Create a class containing an int and a char that are not initialized, and print their values to verify that Java performs default initialization.

package com.ciaoshen.thinkinjava.chapter2;

public class DefaultValueTest {
    public int i;
    public char c;

    public static void main(String args[]){
        DefaultValueTest myTest = new DefaultValueTest();

        //print the int and char
        System.out.println(myTest.i);
        System.out.println(myTest.c);
    }
}

第二题

Following the HelloDate.java example in this chapter, create a “hello, world” program that simply displays that statement. You need only a single method in your class (the “main” one that gets executed when the program starts). Remember to make it static and to include the argument list, even though you don’t use the argument list. Compile the program with javac and run it using java. If you are using a different development environment than the JDK, learn how to compile and run programs in that environment.

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

public class EasySayHello {
    public static void main (String args[]){
        System.out.println("Hello world!");
    }  
}

第三题

Find the code fragments involving ATypeName and turn them into a program that compiles and runs.

!!!注意:Java构造函数不能加返回值,不然就会变成类的成员方法。

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

public class Circle {

    public int[] center = new int[2];
    public int radius = 0;

    public Circle(){
        this.center[0] = 0;
        this.center[1] = 0;
        this.radius = 0;
    }

    public Circle(int x, int y, int radius){
        this.center[0] = x;
        this.center[1] = y;
        this.radius = radius;
    }

    public static void main(String args[]){
        Circle myCircle = new Circle(1,2,3);

        System.out.println(myCircle.center[0]);
        System.out.println(myCircle.center[1]);
        System.out.println(myCircle.radius);
        return;
    }
}

第四题,第五题

Turn the DataOnly code fragments into a program that compiles and runs. Modify the previous exercise so that the values of the data in DataOnly are assigned to and printed in main( ).

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

public class DataOnly {
    int i;
    double d;
    boolean b;

    public DataOnly(){
        this.i=0;
        this.d=0;
        this.b=false;
    }
    public DataOnly(int argI, double argD, boolean argB){
        this.i=argI;
        this.d=argD;
        this.b=argB;
    }

    public static void main (String args[]){
        DataOnly myData = new DataOnly(1,1.1,true);

        System.out.println(myData.i);
        System.out.println(myData.d);
        System.out.println(myData.b);
    }
}

第六题

Write a program that includes and calls the storage( ) method defined as a code fragment in this chapter.

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

public class Calculator {

    public static int storage(String s){
        return s.length() * 2;
    }

    public static void main (String args[]){
        String argS = "Hello World!";
        System.out.println(Calculator.storage(argS));
    }
}

第七题

Turn the Incrementable code fragments into a working program.

File StaticTest.java

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

class StaticTest {
    static int i = 47;
}

File Incrementable.java

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

class Incrementable {
    static void increment() { StaticTest.i++; }

    public static void main (String args[]){
        Incrementable.increment();
        System.out.println(StaticTest.i);
    }
}

第八题

Write a program that demonstrates that, no matter how many objects you create of a particular class, there is only one instance of a particular static field in that class.

File StaticTest.java

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

class StaticTest {
    static int i = 47;
}

File Incrementable.java

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

class Incrementable {
    static void increment() { StaticTest.i++; }

    static void excercise7 (){
        Incrementable.increment();
        System.out.println(StaticTest.i);
    }

    static void excercise8() {
        //create 3 StaticTest object
        StaticTest test1 = new StaticTest();
        StaticTest test2 = new StaticTest();
        StaticTest test3 = new StaticTest();
        //print them
        System.out.println(test1.i);
        System.out.println(test2.i);
        System.out.println(test3.i);
        //StaticTest add 1
        Incrementable.increment();
        //print three StaticTest objects again.
        System.out.println(test1.i);
        System.out.println(test2.i);
        System.out.println(test3.i);
    }

    public static void main (String args[]){
        Incrementable.excercise7();
        Incrementable.excercise8();
    }
}

第九题,第十题

Write a program that demonstrates that autoboxing works for all the primitive types and their wrappers.

Write a program that prints three arguments taken from the command line. To do this, you’ll need to index into the command-line array of Strings.

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

/**
 * class for Exercise 9 and Exercise 10.
 * /
class AutoBoxing {
    /**
     *  7 of 8 primitive types.
     *  initialized by their default value.
     */
    Integer i = new Integer(0);
    Long l = new Long(0L);
    Short s = new Short((short)0);
    Byte bt = new Byte((byte)0);
    Character c = new Character((char)0);
    Float f = new Float(0.0f);
    Double d = new Double(0.0d);
    Boolean b = new Boolean(false);

    /**
    *  print three of them. converted to String
    *  @param no parameter.
    */
    public void printThree(){
        System.out.println(this.i);
        System.out.println(Long.valueOf(this.l));
        System.out.println(Character.toString(this.c));
    }

    /**
     *  main class: Exercise 9 and Exercise 10.
     *  @param args : give me 3 paras here.
     *  args[0]: int   ex: 200
     *  args[1]: long   ex: 2000L
     *  args[2]: char or String   ex:"zzz"
     */
    public static void main (String args[]){
        //create the new AutoBoxing object
        AutoBoxing letsGo = new AutoBoxing();
        //print 3 of them
        letsGo.printThree();


        //exercise 9: autoboxing
        //assign a value for each
        letsGo.i=100;
        letsGo.l=1000L;
        letsGo.s=10;
        letsGo.bt=100;
        letsGo.c='x';
        letsGo.f=1.1f;
        letsGo.d=2.2d;
        letsGo.b=true;
        //print 3 of them
        letsGo.printThree();

        //exercise 10: get the arguments from command line
        //parse the argument into Integer, Long, and Char
        letsGo.i=Integer.parseInt(args[0]);
        letsGo.l=Long.parseLong(args[1]);
        letsGo.c=args[2].charAt(0);
        //print 3 of them
        letsGo.printThree();
    }
}

第十一题

Turn the AllTheColorsOfTheRainbow example into a program that compiles and runs.

通过这道题,了解了很重要的一点,Java不允许在声明字段的时候,随意在后面初始化字段。 具体对字段初始化的分析,详见《Think in Java 读书笔记:第三章 - 操作符》

下面是第十一题我的答案:

package com.ciaoshen.thinkinjava.chapter2;
import java.util.*;

/**
 *  Class for Exercise 11.
 */
public class AllTheColorsOfTheRainbow {
    // 7 colors for the rainbow
    // int will be converted to Integer by Java Autoboxing
    private HashMap<Integer,String> rainbowColorMap = new HashMap<Integer,String>();

    // the number represent the main color of the rainbow
    private int anIntegerRepresentingColors=0;

    //constructor
    //!!!重要!!!为什么给HashMap赋值不能放在HashMap的声明后面。之前没有放在构造函数里,一直报错:illegal start of type和identifier expected。
    public AllTheColorsOfTheRainbow(){
        this.rainbowColorMap.put(1, "red");
        this.rainbowColorMap.put(2, "orange");
        this.rainbowColorMap.put(3, "yellow");
        this.rainbowColorMap.put(4, "green");
        this.rainbowColorMap.put(5, "cyan-blue");
        this.rainbowColorMap.put(6, "blue");
        this.rainbowColorMap.put(7, "purple");
    }

    /**
     *  visible to the user, to manipulate the main color
     *  return the current main color.
     *  @param newHue give me the number of the color according to the RAINBOWCOLORMAP.
     */
    public String changeTheHueOfTheColor(int newHue) {

        //set the anIntegerRepresentingColors
        this.anIntegerRepresentingColors=newHue;

        //check the color from the RAINBOWCOLORMAP. "Gray" means no color.
        String nowColor=null;
        nowColor=this.rainbowColorMap.get(this.anIntegerRepresentingColors);
        if (nowColor==null){
            System.out.println("Give me another number!!");
            return null;
        } else {
            return nowColor;
        }
    }

    /**
     *  main method, set the rainbow color by calling changeTheHueOfTheColor method
     *  @param args args[0] give us the color number.
     */
    public static void main (String args[]){
        //create rainbow object
        AllTheColorsOfTheRainbow myRainbow = new AllTheColorsOfTheRainbow();
        //get the args
        int numColor=-1;
        numColor=Integer.parseInt(args[0]);
        //call changeTheHueOfTheColor method if args exist
        if (numColor!=-1){
            String myRainbowColor=myRainbow.changeTheHueOfTheColor(Integer.parseInt(args[0]));
            if(myRainbowColor!=null){
                System.out.println("The current main color of the rainbow is: "+myRainbowColor);
            }
        } else {
            //if the args is exactly -1
            System.out.println("Give me another number!!");
        }
    }
}