本文参考自Java编程思想第四版,并结合自己现有知识做的一些总结。

尽管Java是基于C++的,但相比之下,Java是一种更为“纯粹”的面向对象程序设计语言。

Java语言假设我们只进行面向对象的程序设计,因为可以发现Java代码都是由一个接着一个的类组成的。可以说在Java中(几乎)一切都是对象,非对象的个例(八大基本数据类型)。

1. 引用还是指针操作对象

每一种编程语言都有操作内存中元素的方式,程序员需要注意操作元素的数据类型,是直接地操作数据,还是间接地操作数据(如C和C++中的指针)。

所有的一切在Java里都得到了简化。一切都被视为对象,因此可以采用单一固定的语法。尽管一切都看作对象,但操作元素的标识符实际上是对对象的“引用”。

上述是Java编程思想中的介绍,同时书中指出“引用”只是一个相对的解释,它在某些场景可能不被理解。

上课的时候,老师是这样跟我们解释的:首先,Java中是没有C和C++中的显示指针的,即不能直接对内存空间进行操作,但为了完成某些功能,Java中是有"隐式指针"的,它指向了某个具体的对象,对该”隐式指针"进行某些操作(给他发送消息)会作用到它指向的对象身上。

我认为两种说法都是正确的,并且二者只是表达上不一样,实际上的含义是一样的。

接下来,我们通过实例来分析一下。

定义一个Student类:

public class Student {
    private int id;
    private String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
}
// s1,它既可以说是对Student对象的一个引用,也可以是一根“隐式”指针,它可以指向一个Student对象
// 无论是引用还是指针,s1都是没有初始化,由于Java编译器的严格性,下面这段代码是无法经过编译的
public static void main(String[] args) {
    Student s1;
}

将s1与(1,“小明”)这个Student对象绑定,同时声明s2(Student隐式指针)指向s1。画个图理解一下:

Java所有对象关系 java中所有数据都是对象_开发语言

这个时候通过s1和s2可以影响对象的值。

public static void main(String[] args) {
        Student s1 = new Student(1,"小明");
        Student s2 = s1;
        System.out.println(s1.id);
        System.out.println(s2.id);
        s2.id = 2;
        System.out.println(s1.id);
    }

输出结果:

1
1
2

简单的解释一下:s1.id,其中.id相对于一个消息,s1收到了.id的消息需要给出id的值,但它本身没有id这个字段因此它需要到它指向的Student对象中找到id的值并给出来。那么s2.id = 2;意思就是找到s2所指向的Student对象的id字段,并将它更改为2。这个时候,若在输出s1.id的值,它将会变成2,因为s1与s2执行的是一个Student对象。

public static void main(String[] args) {
        Student s1 = new Student(1,"小明");
        Student s2 = s1;
        s2 = new Student(2,"小明");
        System.out.println(s1.id);
        System.out.println(s2.id);
    }

输出结果:

1
2

那为什么这个时候s1与s2的id值又不同呢,因为本质上s1与s2是互不干扰的,它们可以各自指向不同的对象。

s2 = new Student(2,“小明”);在这一句中,是=向s2发出信息,由于s2本身就可以响应这个信息,所以不需要找到它指向的对象而是s2直接指向这个新的Student对象,这样最终s1.id与s2.id的值就是不相同的。

2. 对象存储在什么位置

  1. 寄存器:这是最快的存储区,位于CPU内部,同时寄存器的数量是非常稀少的。寄存器根据需求进行分配,Java不能直接或间接控制寄存区。
  2. 堆栈:位于通用RAM(随机访问存储器中),但通过堆栈指针可以获得CPU的直接支持。堆栈指针若向下移动,则分配新的内容,若向上移动,则释放那些内存。创建程序时,Java系统必须知道存储在堆栈内的所有项的确切生命周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性。所以只有部分Java数据存储在其中,包括对象引用(“隐式”指针),八大基本数据类型。
  3. 堆:一种通用的内存池,用于存放所有的Java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当需要一个对象时,只需要用new写一行代码即可,当执行这行代码时,会自动在堆里进行内存分配。当然高的灵活性也会付出相应的代价:用堆进行存储分配和清理可能比堆栈进行存储分配需要更多的时间。
  4. 常量存储:字符串常量,integer常量
  5. 非RAM存储:“流”对象,“持久化”对象

注意:

Java中char占两个字节,用于支持Unicode编码,同时Java所有的数值类型都有正负号,不支持无符号。

3. 字段和方法

作用域({}内为一个作用域):

{
    int x = 5;
    // only x
    public void test(){
        int p = 5;
        // only p and x
    }
    int z = 5;
    // only x and p and z
}
{
    int x = 5;
    public void test(){
        // 这在Java中是不允许的,编译器会直接报错x已经定义过了,但在c/C++又是可以的
        int x = 5;
    }
}
{
    // 编译器报错,x没有被初始化,这里因为x是方法内的字段,所以不会默认初始化为0,而是任意整数,
    // 所以报错处理
    public void test(){
        int x;
    }
}
{
    public void test(){
        String s = "123";
    }
    // 程序执行都这里时,虽然访问不到test中的String对象,但它仍然存在于堆中,因为JVM中的垃圾回收器
    // gc没有这么快起作用,它会综合判断,在合适的时候回收test中的String对象
}

Java类的字段(数据成员)即使没有初始化,编译器也会给它一个默认值。

数据类型

默认值

boolean

false

char

‘\u0000’(null)

byte

(byte)0

short

(short)0

int

0

long

ol

float

0.0f

double

0.0d

对象

null

**方法签名的概念:**方法名和参数列表唯一地标识某个方法。

4. static关键字

一般情况下,只有执行new来创建对象时,数据存储空间才会被分配,此时其字段和方法才能被外界访问。

但是呢,有两种情形是上述方法无法解决的:

  1. 希望类的某个字段能被它的所有对象共享,同时哪怕是不创建任何对象,也可以访问到该字段
  2. 希望某个方法不要与对象关联起来,也就是说没有创建对象也可以调用这个方法。其中最经典也是最常用的一个方法就是main()方法。
{
    // 静态字段
    static int a;
    
    // 静态方法
    static void test(){
        
    }
}

注意:因为静态字段和方法没有与任何对象绑定,即在.class被加载到虚拟机时,static字段和方法就已经载入完成。这个时候,非static字段和方法还没有被载入到内存当中。因此,static方法不能调用非静态方法和字段。但是非静态方法可以调用静态方法和访问静态字段。

如何证明static字段和方法在.class被加载到虚拟机时就载入完成呢?

public class Student {
    public Student(){
        System.out.println("这是构造方法");
    }
    static {
        System.out.println("这是静态字段");
    }
    public static void main(String[] args) {
      new Student();
      new Student();
    }
}

Java所有对象关系 java中所有数据都是对象_后端_02

new了两个Student对象,可以看到首先输出的是static语句中的"这是静态字段",并且只输出了一次,其后紧随着两次构造方法中输出的句子。

通过上述例子,很容易联想到static字段与方法会比非静态字段和方法先加载到内存中。