java--3面向对象1【OOP基本概念】【匿名对象】【封装】【构造函数】【this关键字】 static关键字】【main方法】【静态成员】 【工具类】【静态

1.    面向对象的基本概念
1). 面相对象 (OOP) 和面向过程(OPP) 的关系
(1). 面相对象是把以前需要逐个执行的动作都全部封装起来
(2). 面向对象是基于面向过程的!!!!!
2). 面向对象的三大特征
封装、继承和多态
3). 类和对象
(1).基本概念
[1]. 类是对现实生活中事物的描述。
[2]. 对象是这类事物实实在在的一个个体
[3]. 类和对象在Java中的体现
       {1}.在Java中描述事物就是用class来定义类
       {2}.在Java中产生一个类的实体就是用new来创建类的对象
[4]. 描述事物和Java中类的关系
       {1}.描述事物就是在描述事物的属性和行为
       {2}.定义类就是在描述事物,就是在定义事物的属性和行为
(2). 类的成员
类中的属性和行为共同组成了类中的成员
(3). 局部变量和成员变量的区别
从内存中的位置和作用范围两个方面来考虑
[1]. 内存中的位置:成员变量位于堆内存中 (因为对象是存储在堆内存中的)
                              局部变量位于栈内存中
[2]. 作用范围:成员变量作用于整个类中             局部变量作用于函数体或者局部代码块中
 
4). 匿名对象
(1). 匿名对象与普通对象的关系
[1]. 对象可以有名字,也可以没有名字。没有对象名的对象就是匿名对象。
[2]. 匿名对象是普通对象的简化形式。
(2). 匿名对象对属性和方法的调用
[1]. 匿名对象直接调用属性是没有意义的!!
原因就是匿名对象没有名字,也就是没有引用变量在栈内存中直接指向堆内存中的匿名对象。因此 new Car(). num =5;执行完之后,匿名对象就变成了垃圾,会被回收掉。那么对其属性的读写操作也就没有什么意义了。
[2]. 匿名对象直接调用方法是有意义的!!
(3). 匿名对象的适用范围
[1]. 当对象的方法仅被调用一次的时候,可以使用匿名对象来简化书写。
匿名对象的弊端:但是如果对一个对象的多个成员进行调用的时候,匿名对象失去作用。
[2]. 可以将匿名对象作为实参进行参数传递。
注意:当这个被调用的方法接受一个匿名对象并且执行完毕之后,这个堆内存中的匿名对象就变成了垃圾对象。
5). 封装
(1). 含义
[1]. 是指隐藏对象的属性和实现细节,仅仅对外提供公共访问接口。
[2].对外提供公共访问接口的原因就是:在访问方式中加入逻辑判断语句
(2). 封装和私有的关系
[1]. 私有仅仅为封装的一种表现形式
[2]. 不私有同样也能实现封装。只要权限在你访问不到的权限的范围内,对你来说就是封装。
(3). 封装的原则
[1]. 将不需要对外提供访问的内容隐藏起来
[2]. 把属性都隐藏,提供公共方法对属性进行带有逻辑判断的访问。
 
6). 构造函数
(1). 构造函数的特点:
[1]. 函数名和类名相同
[2]. 没有返回值类型   (根本没有return语句)
[3]. 没有返回值类型和void的区别
       {1}.void 是一种返回值类型。代表没有具体结果
       {2}.无返回类型:根本不需要返回类型
[4]. 构造函数只能用来跟在new之后初始化对象,而不能像普通方法那样被调用!!!!
(2). 构造函数被调用的时机
对象一建立,就会调用与之对应的构造函数。
(3). 构造函数的作用
给对象进行初始化 (也就是为对象的某些属性进行赋值使得对象一产生就具备某些特征)
(4). 构造函数的小细节
[1]. 一个类中没有显式定义构造函数的时候,OS就会给该类加入一个空参的构造函数。
[2]. 如果一个类显式定义了一个构造函数,那么OS便不会给这个类在自动加入一个空参数的构造函数了。
(5). 构造函数和普通函数的区别
[1]. 写法上不同
[2]. 运行上不同
       {1}构造函数在对象一建立的时候,就来给对象进行初始化的。
       {2}只有对象调用才执行
【【再次强调!!!构造函数只能用来跟在new之后初始化对象,而不能像普通方法那样被调用!!!!】】
[3]. 执行的次数
       {1}构造函数用来给对象进行初始化,所以仅仅运行一次;
       {2}普通的方法可以被对象多次调用,可以执行多次
(6). 何时定义构造函数
当事物一存在的时候,就具备某些特性或行为的时候,就为这个类定义构造函数,将这些对象与生俱来的特性和行为封装到构造函数中去进行初始化
 
7). 构造代码块
很多时候,函数和代码块是相对应的。方法有自己的名字,而代码块却没有。
(1). 构造代码块的写法与目的:
[1]. 构造代码块的写法:用一对{}封装起来一段代码,并把这个代码块放在类的成员的位置上。这样的代码块就是构造代码块。
[2]. 构造代码块,顾名思义,也是起到构造方法的作用的代码块,也是给对象进行初始化。
(2). 构造代码块和构造函数的区别
[1]. 初始化对象的方面不同:
       {1}.构造代码块是对这个类所有的对象进行进行共性的初始化。
       {2}.构造函数可以指定。所以构造函数是对指定对象进行个性的初始化
[2]. 执行的顺序不同:构造代码块先于构造函数执行。
举例说明:(面试题) 
class Person{
    private String name;
    private int age;
  
    //构造代码块
    {
        name ="Benjamin";
        age = 18;
        System.out.println("Person code run....");
    }
  
    Person(){
        System.out.println("name ="+ name+", age="+age);
    }
  
    Person(Stringname, int age){
        this.name =name;
        this.age =age;
        System.out.println("name ="+ name+", age ="+age);
    }
}
 
public class testtt {
    public static void main(String[] args) {
        Personp1 =new Person();//调用空参数构造函数进行初始化
        Personp2 =new Person("zxm", 28);//调用非参数构造函数进行初始化
        /*
         * 两个Person对象,分别指定用不同的构造函数进行初始化。个性的初始化
         * 但是,在每一个构造函数运行之前,构造代码块都对这两个对象进行统一的共性初始化
         */
    }
}
运行结果:
 
8). this关键字
(1). this关键字指代谁?
this代表他所在函数所属的对象(指的是调用这个函数的对象)的引用变量。
****也就是哪个对象调用this所在的函数,this就代表哪个对象。
(2). 为什么类之间的非静态成员可以相互调用??
非静态成员要使用必须带上所属的对象才能使用。所以相互调用的时候,一定是通过this对象来完成的。这个this很多时候可以省略掉。
e.g.
class Person{
    private String name;
    private int age;
  
    //非静态的speak方法调用了非静态的属性 name和 age
    public void speak(){
        System.out.println("name ="+ name+", age="+age);
    }
  
    //非静态的show()方法调用了非静态的方法speak()
    public void show(){
        speak();
    }
}

在这里面,成员方法speak()调用了成员变量name和age,但是这里面name跟age都是非静态的成员,所以这两个成员的调用必须依赖某个对象。这个对象就是调用speak方法的对象。在speak方法中表示为this。这样,调用的name和age也就是调用了这个对象的name和age。所以:上面的speak方法相当于
public void speak(){
      System.out.println("name ="+ this.name+", age ="+ this.age);
}

【个人理解】由于在某个对象的speak中调用了name和age,name和age前面都省略了调用这些属性的对象,所以这个对象就调用speak的对象,在speak方法内部表示成为this。
    同样道理,上面的show相当于:
public void show(){
    this.speak();
}

 
【个人总结】
{1}. 一旦在某个非静态方法中调用了另一个非静态的成员属性/方法(这就是非静态成员之间的相互调用),这个非静态的成员属性/方法前面必须跟上一个对象。
{2}. 如果没有跟对象来调用这个属性/方法,那么调用这个属性/方法的对象一定是和调用这个非静态方法的对象同为一个对象--àthis
 
一句话:非静态方法/变量要使用一定离不开相对应的对象来调用他们,否则无法使用!!!!
【类中的非静态成员被使用,全部由对象调用来完成。本类对象用this来表示】
 
(3). this的基本应用
[1]. 当定义类的功能的时候,如果一个方法的内部要用到调用该方法对象的时候,这是可以使用this表示这个对象。
e.g. 需求给Person类定义一个用于比较年龄是否相同的功能。
错误做法:X
public boolean compare(Person p1, Person p2){
        if(p1.age == p2.age)
            return true;
        return false;
    }
错误原因:这是Person类的compare功能可以比较另外两个人的年龄是否相同。但是需求是比较外来一个人和自己的年龄是否相同。所以  要在调用compare方法的内部引用到调用这个方法的对象自身,要使用this来表示。
public boolean compare(Person p){
        if(this.age == p.age)
            return true;
        return false;
    }


[2]. this在构造方法中的应用**
**(背景)需求:优化下面一个类的两个构造方法。
class Person{
    private String name;
    public int age;

    Person(){
this.name =name;
        System.out.println("name ="+ name+", age="+age);
    } 
    Person(Stringname, int age){
        this.name =name;
        System.out.println("name ="+ name+", age ="+age);
this.age =age;
    }
}


{1}. 错误做法:将构造方法作为普通方法调用。
 
class Person{
    private Stringname;
    public int age;
 
    Person(Stringname){
this.name =name;
        System.out.println("name ="+name+", age="+age);
    }
    Person(Stringname, int age){
        Person(name); X!!
this.age =age;
    }
}
【个人总结】
构造函数只能用来跟在new之后初始化对象,而不能像普通方法那样被调用!!!!所以,这里面按照非静态成员之间相互调用的观点来看:Person()的使用一定是存在对象调用它才可以的。由于没有写,那么就是this来调用,this.Person()。这样违背了构造函数不能被调用的原则!!所以错误。为了解决构造方法之间重用的问题,使用this语句来解决这样的问题。
 
解决办法:this(name)来替代Person(name)
{1}.this语句:
this以方法名的形式出现的时候,this(实参列表),称为this语句。是用来进行构造方法重用而设计的!!
注意:在一个构造方法中使用this语句的时候,不是再次创建一个对象,而是调用相应构造方法中的内容(实现代码重用)。是否进行实例化要看构造方法被调用的时候前面有没有new出现。只有new关键字+构造方法出现了,才能实例化对象。
this语句一定要放在构造方法的第一行!!!
***this语句必须放在另一个构造方法的第一行的原因:
【个人理解】this语句本身代表参数相对应的构造函数。构造函数的目的就是初始化对象。一个构造方法中又一次调用了this语句,那么说明这个构造的时候,要执行更细微的对象初始化工作。所以一定要先执行!!!
{2}.this关键字:
 This以对象的形式出现的时候,就是关键字,this.成员,称为this关键字

.    static知识点
1). 方法区/共享区/数据区
(1). 类中的函数代码本身==>这片区域叫方法区的意义
       **方法内部使用的局部变量数据是存储在栈中该方法对应的区域
(2). 类中的静态数据 (也就是共享数据) ==è这片区域叫共享区的意义
2). static的基本用法
(1). static 是一个修饰符,只能修饰类的成员(也就是只能修饰成员变量和成员方法)
       static绝对不能修饰局部变量!!!
(2). 静态成员的两种调用方式
[1]. 对象引用.静态成员变量
[2]. 类名.静态成员变量
(3). 静态成员变量存储的位置
类中静态成员变量是存储在方法区/共享区/数据区***
类中非静态成员是存储在堆内存中
注意区分上面两种情况。
(4). static静态成员的特点
[1].随着类的加载而加载,随着类的消失而消失。与类共存亡。
class Person{
    Stringname;
    static String country;
}

注意这个Person类一加载到内存的时候,静态成员变量country就在方法区开辟内存空间。随着类加载而加载。
[2]. 先于类的对象而存在
[3]. 被所有的该类的对象共享
[4]. 可以通过类名来调用静态成员
(5). 别名
[1]. 静态成员变量也叫类变量。
[2]. 非静态成员变量也叫实例变量
3). 类变量和实例变量的区别
 
内存中的存储位置
生命周期
类变量/静态变量
随着类加载而加载,存储到方法区
与类共存亡。
实例变量/非静态变量
随着对象建立而存在堆内存中
与对象共存亡。
 
4). 静态使用的注意事项
(1). 静态成员方法只能访问静态的成员(方法/变量) ------à静态只能访问静态
       非静态可以访问静态(原因就是对象也可以调用静态成员)。
       结论:静态 (方法)只能访问静态但是非静态 (方法)可以访问任意
       【个人理解】
       [1].澄清一下:这个静态成员方法调用的是本类的非静态成员才会出问题。
所以,如果通过静态方法调用
{1}.没有“对象.”非静态成员
{2}. “this.本类非静态成员”
都是错误的,编译无法通过!!
       [2].如果调用有“非this的具体对象引用.非静态成员”是可以的。
       所以,大原则:如果静态方法调用的非静态的成员,这个成员之前的对象是存在,那就没有问题。
(2). 静态方法中不可以使用super、this等关键字
(3). main方法为静态的
5). 静态的利与弊
(1). 静态的好处
       [1].节省空间。对对象的共享数据在方法区单独开辟了内存空间。没有为每一个对象在堆内存中单独复制一份备份。
       [2].调用方便。类名就可以直接调用静态方法,不用创建实例再调用。
(2). 静态的坏处
       [1],生命周期过长
       [2].出现访问局限性(只能访问本类的静态成员。 无法直接访问非静态成员)
6). 如何使用static
(1). 何时使用static修饰成员变量
[1]. 当类中出现了所有对象都共享的数据,那么就把这个成员定义为static的成员变量
[2]. 每个对象都有不同取值的成员变量是对象特有的属性,应该定义成非静态的成员变量
(2). 何时使用static修饰成员方法
[1]. 当要定义的函数的功能没有直接访问到该类的非静态成员或者通过this来访问该类的非静态成员的时候,将这个要定义的功能设置为static的成员方法
2.    main函数
1). main方法的作用
main方法是程序的入口,可以被JVM调用。
2). main方法的标准格式及含义
(1). main函数各个部分的含义
publicstatic void main(String[] args) {}
[1]. public: 访问修饰符
{1}. 代表main函数的访问权限是最大的,由JVM调用。
{2}. main不是Java中的关键字,但是确是可以被JVM识别。
[2]. static: 成员修饰符
{1}. main方法在Java中一定是在某个类的成员位置上出现的。所以,可以被专门修饰成员的修饰符static来修饰。
{2}. 代表main函数是随着所属的类加载而加载到内存中的方法区。
{3}. JVM直接采用 “类名.main()”来调用某个类的主方法。
[3]. void: 返回值类型
主函数没有具体返回值。这是因为JVM自身调用main方法,main中如何执行的,JVM本身非常清楚,所以不存在什么数据的值,JVM不知道,所以执行完main方法之后,没有必要给JVM一个返回值。
[4]. String[] args:参数列表
    {1}.main方法的参数是一个字符类型的数组。
    {2}.用来接受程序在启动执行的时候,用户传递给程序的参数。这些参数以String的形式存储到String[] args中。
(2). 主函数main的格式是固定的
[1]. 如果想让JVM识别main方法,那么这个main方法必须遵从
       public static void main(String[] args) {}
[2]. 以上的格式中,只有形参列表String[] args的变量名args可以改变。其余的地方如果被改变,虽然能通过编译(这是因为只要是函数,就能够被重载),但是无法被JVM识别!!!!
e.g.
public static void main(int x){}
    public static void main(String[] args) {}
其中,JVM只能识别第二个main方法。第一个作为第二个的重载形式可以存在,所以编译通过。
(3). 为主方法main传值的两种方式
[1]. 通过JDK中的java.exe这个工具为main的String[]传值。
假设MainDemo为一个类名
{1}. > javaMainDemo
       要执行哪个类,就把类名作为参数传入JVM。传入这个类名之后,JVM会调用这个类的main方法。
{2}. >javaMainDemo tomcat jetty maven
JVM会将类名之后的字符串分别封装到MainDemo类的main方法中的String[]args参数。
[2]. 通过其他方法调用main并为其进行传值
class MainDemo{
    public static void main(String[] args) {
        for(String arg: args){
            System.out.println(arg);
        }
    }
}
class MainTest{
    public static void main(String[] args) {
        show();
    }
    static void show(){
        String[]arr =new String[]{"Tomcat", "Maven","Jetty"};
        MainDemo.main(arr);//其他方法调用了MainDemo的main方法,并且为其进行了传值
    }
}

.    工具类
静态方法的重要应用场景 工具类
(1). 工具类出现的背景
[1]. 许多不同的类都具有功能相同的方法,如下图。这样会造成代码冗余。

需求:想统一不同类中功能相同的方法,做法就是将功能相同的方法封装到一个新的类中。
[2]. 如果使用这样的不同类中相同的功能的函数,十分麻烦。
class Demo1{
    //查找给定数组的最大元素
    int getMax(int[] arr){
        return 0;//内部没有实现,仅仅示例一下
    }
}

class Demo2{
    //查找给定数组的最大元素
    int getMax(int[] arr){
        return 0;//内部没有实现,仅仅示例一下
    }
}

class Demo3{
    //查找给定数组的最大元素
    int getMax(int[] arr){
        return 0;//内部没有实现,仅仅示例一下
    }
  
}

public class Test {
    public static void main(String[] args) {
        int[] arr ={12, 3, 4, 456, 7, 567};
        new Demo1().getMax(arr);
        new Demo2().getMax(arr);
        new Demo3().getMax(arr);
    }
}

(2). 工具类-----静态重要的应用
[1]. 将多个类中相同功能的函数抽取到一个新定义的类中,独立封装,以便复用。这个新定义的类就称为工具类。
示例:将上面的例子
class ArrayToolDemo{
    //查找给定数组的最大元素
    int getMax(int[] arr){
        return 0;//内部没有实现,仅仅示例一下
    }
}
class Demo1{}
class Demo2{}
class Demo3{}

public class Test {
    public static void main(String[] args) {
        int[] arr ={12, 3, 4, 456, 7, 567};
        new ArrayToolDemo().getMax(arr);
    }
}

[2]. 从静态化的角度来看,由于工具类中的方法都没有使用到当前方法所属对象的特有数据,所以,应该把工具类中的方法静态化。
举例:
class ArrayToolDemo{
    //查找给定数组的最大元素
    static int getMax(int[] arr){
        return 0;//内部没有实现,仅仅示例一下
    }
}

[3]. 方法静态化以后的工具类,构造方法还是能够被调用以建立工具类对象。为了严谨,工具类不应该有对象产生。工具类中的构造方法要私有化。
举例1.
class ArrayToolDemo{
    private ArrayToolDemo(){}
    //查找给定数组的最大元素
    static int getMax(int[] arr){
        return 0;//内部没有实现,仅仅示例一下
    }
}
public class Test {
    public static void main(String[] args) {
        int[] arr ={12, 3, 4, 456, 7, 567};
        ArrayToolDemo.getMax(arr);
    }
}

举例:java.util包中的Collections和Arrays是两个工具类,查看源码发现,两个类的构造方法都已经私有化。
【工具类总结】:方法全部静态化,构造方法全部私有化。
2.    静态代码块
1). 静态代码块概述
(1). 静态代码块的格式
在构造代码块的原有格式之前加上static关键字就把构造代码块变成了静态代码块。
static{
        //静态代码块中的执行语句
}
(2). 静态代码块的特点
[1]. 随着类加载而执行。
并且静态代码块仅仅执行一次。用来为类进行初始化。
优先于本类的main执行。
{1}. 为类初始化的含义是什么?
*类被加载进内存的时候,在没有对象的情况下,很有可能需要执行一些动作。这些随着类加载就执行的动作就可以写在静态代码块中。
*如果这些动作写在一个静态方法中,那么只有这个静态方法被调用,相对应的动作才会被执行!!!无法实现随着类加载就执行的功能。
{2}. 为什么优先于本类的main执行?
静态代码块优先于main执行的原因就是:静态代码块没有名字,如果不马上执行就无法再找到。而main方法具有名字,可以被JVM调用。
e.g. 分析输出结果

分析下执行顺序:
>javaStaticDemo之后,这个命令是要运行StaticDemo这个类的main方法。一旦在命令行中调用了这个命令之后,StaticDemo就立刻被加载到内存中。
由于静态代码块优先于本类中的main方法执行,所以再执行main方法之前,先执行StaticDemo中的第一个静态代码块,输出b。
紧接着执行第二个静态代码块,输出c。
这时候执行到main方法,执行main方法中的第一条语句new StaticCode();
此时StaticCode这个类首次使用,立刻被加载到内存中。这个时候,先执行StaticCode类中的静态代码块。输出a
    之后,回到StaticDemo中的main方法中输出语句,输出over
**e.g.2 如果将上面的new StaticCode();改为StaticCode s =null; 会执行StaticCode中的静态代码块么?
    不会,没有使用到这个类中的成员,是不会被加载到内存的!!!
输出结果 b c over
【类加载到内存的方式】
大原则:只要是提及到这个类名,这个类就立马被加载到内存中。
{1}. Java源文件中,第一次用到了这个类中某个成员的时候,才会将这个类加载到内存!!
***这条规则一定要注意如果没有使用到这个类中的内容,就一定不会加载这个类
StaticCode s =null; 由于这条语句中没有使用到StaticCode这个类中的内容,所以这个类不会被加载到内存中。
new StaticCode(); 这句话使用到了StaticCode中的构造方法,所以会立刻加载到内存中。!!!!
{2}. ****在cmd命令行中,使用“>java类名”或者点击myeclipse中的运行按钮运行源文件的时候,java命令后面跟的类名一定是要立刻进入内存的 -------这种方式最容易被忽略。
[2]. 静态代码块没有名字,不能被调用。
(3). 静态代码块和构造代码块的关系
[1]. 静态代码块:给类初始化,仅执行一次,优先于main执行。
[2]. 构造代码块:给对象进行公有初始化,仅执行一次,优先于指定的构造方法执行。

给出一段代码:
class Person{
    private String name;
    private int age =20;
    private static String country ="cn";
  
    static{
        System.out.println("Staic Block....");
    }
  
    //构造代码块
    {
        System.out.println("Construction Block....name ="+name +"...age ="+age);
    }
  
    //构造方法
    Person(Stringname, int age){
        this.name =name;
        this.age =age;
        System.out.println("Construction Function....name ="+name +"...age ="+age);
    }
}

1.    对象初始化的过程
在测试类的main中,创建两个Person类的对象。
class Test{
    public static void main(String[] args) {
        Personp =new Person("Benjamin", 28);
        Personp1 =new Person("ZXM", 28);
    }
}
【结论】
在调用一个类的构造方法进行对象的创建的时候,进行的动作如下:
【step 1】. 该类中静态成员默认初始化(执行一次,当类加载到内存时)[option]
【step 2】. 该类中静态成员显式初始化(执行一次,当类加载到内存时) [option]
【step 3】. 执行该类的静态代码块为类初始化(执行一次,当类加载到内存时) [option]
【step 4】. 实例变量默认初始化 (堆内存中的成员具有默认值)
【step 5】. 实例变量显式初始化 [实际上就是直接为这个成员变量赋值]
【step 6】. 构造代码块为该对象进行公共初始化
【step 7】. 构造函数为该对象进行特定初始化
注意:其中,前三步是可选的,如果这个类中存在静态变量和静态代码块,才执行前面三步。但是,仅仅执行一次!!
【总结】Person p =new Person("Benjamin", 28);这句话一执行,都做了什么事情?
<1>. 因为new的时候用到了Person类的构造方法,所以类文件Person.class会被加载到内存中。此时静态成员变量在方法区中开辟了内存空间。
<2>. 为静态成员country进行默认初始化,初始化值为null
<3>. 为静态成员country进行显式初始化,初始化值为cn
<4>. 执行Person类的静态代码块,并输出Staic Block.... country: cn
<5>. OS在堆内存中开辟空间,为Person类的对象分配内存地址
<6>. 在堆内存中建立成员变量,并进行默认初始化
     这里面,name默认初始化为null,age默认初始化为0
<7>. 为堆内存中的成员变量进行显式初始化。age显式初始化为20
<8>. 为该对象进行构造代码块初始化。此处打印出:
        Construction Block....name=null...age =20
<9>. 为该对象进行指定构造函数初始化。name和age分别显式初始化为Benjamin和28
     并且打印出:Construction Function....name=Benjamin...age =28
<10>. 将对象在堆内存中的地址赋值给栈内存中的Person类的引用变量p。
2.    对象调用成员的过程
class Person{
    private String name;
    private int age =20;
    private static String country="cn";
  
    Person(Stringname, int age){
        this.name =name;
        this.age =age;
    }
  
    public void speak(){
        System.out.println(this.name +"..."+ this.age);
    }
  
    public void setName(String name){
        this.name =name;
    }
  
    public static void showCountry(){
        System.out.println("country ="+ country);
        method();
    }
  
    public static void method(){
        System.out.println("static method is running");
    }
}

class Test{
    public static void main(String[] args) {
        Personp =new Person("Benjamin", 28);
        p.setName("ZXM");
        Person.showCountry();
    }
}

这里面,main方法执行每一条语句,内存变化过程如下:

<1>. 对象初始化过程在前面,并且在栈内存中,为main方法开辟了方法中局部变量的内存空间。此时Person类引用变量p指向堆内存中的Person类对象。
name值为Benjamin  age值为20
<2>. 执行p.setName("ZXM");
首先OS在栈内存中为setName方法开辟了局部变量的内存空间,并且形参name接受main方法中传入的实参,初始化为ZXM。
由于setName为非静态方法,并且调用了该对象的另一个非静态成员。所以,这个name成员所属的对象就是this。这个this就是在主方法中调用setName方法的对象p。因此setName中的this指向堆内存中的那个对象。p.setName(“ZXM”)相当于将p的值赋值给了this。每一个非静态方法中都有一个this变量。
执行完之后,栈内存的setName中对应的临时变量name和引用变量this全部释放。
<3>. 执行到Person.showCountry();
    这个是静态方法,在栈内存中开辟了临时变量的内存空间。注意:静态方法中是没有this的!! 此时调用的静态变量country并没有经过堆内存。而是经过方法区中的country。值为cn。
<4>. 静态方法showCountry()中又调用了另一个静态方法method()。此时在栈内存中也为method开辟了一个局部变量对应的内存空间。注意:静态方法中是没有this的!!
请问:showCountry中调用了method()
public static void showCountry(){
    System.out.println("country ="+ country);
    method();
}
这个method之前有省略的东西么?
一定有!!因为只要是方法执行了,一定是被调用了,必须先调用,后执行。
静态方法之间相互访问,如果写成直接访问,那省略的一定是“类名.”
public static void showCountry(){
    System.out.println("country ="+ country);
    Person.method();
}
【结论】
非静态成员方法直接访问其他的非静态成员,省略的是this.
静态成员方法直接访问其他的静态成员,省略的是类名.
【个人总结】
从上面方法区和栈、堆可以看出,代码和代码中使用到的数据是相对独立的内存区域。





猜你喜欢

转载自zhyp29.iteye.com/blog/2305349