java基础面试题之对象与内存控制

1. 怎么区分局部变量、成员变量、实例变量,类变量,他们有什么区别?

    java变量大体可以分为局部变量和成员变量:

        局部变量可分为:形参、方法内的局部变量,代码块内的局部变量,它们作用时间短暂,被存在方法栈内存中

        成员变量是在类体内定义的变量:根据static修饰符来区分非静态变量或实例变量,和静态变量或类变量

    PS:static的作用就是将实例成员变成类成员,static只能修饰在类里定义的成员部分,包括成员变量、方法、内部类、初始化块、内部枚举类。如果没有使用static修饰这些类里的成员,则这些成员属于类的实例,否则属于类本身。所以static不能修饰外部类、局部变量、局部内部类。

    对于实例变量,程序每初始化一个class对象,JVM就会分配一块内存给实例变量,而对于类变量,一旦初始化之后,则不再分配内存,所有实例对象共用一块内存。所以通过实例对象访问类变量时,最终会转成通过class来访问类变量,因为它属于类本身

2. 实例变量和类变量的初始化顺序是怎么样的?

    从语法的角度来看实例变量和类变量的初始化:

        实例变量初始化:可以在定义实例变量时指定初始值、在非静态初始化块中指定初始值和在构造器中指定初始值

        类变量初始化:可以在定义类变量是指定初始值和静态初始化块中指定初始值

     那么初始化的时候很明显了,

        对于实例变量,首先是定义实例变量初始化或非静态初始化块初始化(看代码中的顺序),然后再导构造器初始化

        从下面编译后的代码可知,所有的实例变量初始化操作,最终都是在构造器中完成的

public class HelloWorld {
    String name = "12";

    {
        name = "123";
    }

    public HelloWorld(){
        System.out.println(name);
    }
}



//编译完成后,使用javap命令查询编译后的代码
//可以看到下面第5和第11行中,对类变量执行初始化,顺序保持和代码中的顺序

~java>javap -c HelloWorld.class

Compiled from "HelloWorld.java"
public class HelloWorld {
  java.lang.String name;

  public HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: aload_0
       5: ldc           #2                  // String 12
       7: putfield      #3                  // Field name:Ljava/lang/String;
      10: aload_0
      11: ldc           #4                  // String 123
      13: putfield      #3                  // Field name:Ljava/lang/String;
      16: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: aload_0
      20: getfield      #3                  // Field name:Ljava/lang/String;
      23: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: return
}

        对于类变量,就是定义类变量或者静态初始化块,具体看代码中的顺序

        从下面编译后的代码可知,所有的类变量初始化操作,最终都是在静态初始化块中完成的

public class HelloWorld {

    static {
        name = "123";
    }

    static String name = "12";

    public HelloWorld(){
        System.out.println(name);
    }
}


//下面是对类变量进行初始化, 可以看到类变量的初始化没有放在构造器中了
//而是放在static初始化块中了,初始化顺序也是和代码中的顺序保持一致

~java>javap -c HelloWorld.class

Compiled from "HelloWorld.java"
public class HelloWorld {
  static java.lang.String name;

  public HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: getstatic     #3                  // Field name:Ljava/lang/String;
      10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: return

  static {};
    Code:
       0: ldc           #5                  // String 123
       2: putstatic     #3                  // Field name:Ljava/lang/String;
       5: ldc           #6                  // String 12
       7: putstatic     #3                  // Field name:Ljava/lang/String;
      10: return
}

3. 构造器中的this代表的是类自己吗?

    构造器中的this代表的是正在初始化的java对象,假如正在初始化的是类本身,那么this在编译时类型和运行时类型都代表类本身,但假若正在初始化的是其他类,那么this在编译时类型代表类本身,在运行时类型代表的是其他类

下面程序可以看出this的真面目

public class HelloWorld {

    public static void main(String[] args) {
        new Dog();
    }
}

class Animal {

    private int i=2;

    public Animal(){
        System.out.println(this.i);
        this.show();
        System.out.println(this.getClass());
    }
    public void show(){
        System.out.println(i);
    }

}

class Dog extends Animal {

    private int i = 22;

    public Dog(){
        i=222;
    }
    @Override
    public void show(){
        System.out.println(i);
    }
}



//最后输出
2
0
class Dog

4. 继承成员变量和继承方法时有什么区别?

    大家都知道,如果c继承了p,假若在修饰符允许的情况下,那么c可以访问p的成员变量和方法,但是,继承的时候,成员变量和方法的处理是不一样的,下面看示例代码:

//编译Dog.java文件,  查看编译后的文件信息,可以看到Animal父类的方法show被移到Dog类中
// 而Animal中的成员变量i并没有迁移到Dog中

public class Dog extends Animal {

    private int i = 22;
}

class Animal {

    private int i=2;
    public void show(){
        System.out.println(i);
    }

}


//编译Dog.java文件,  查看编译后的文件信息,可以看到Animal父类的方法show被移到Dog类中
// 而Animal中的成员变量i并没有迁移到Dog中

java>javap -c -private Dog

Compiled from "Dog.java"
public class Dog extends Animal {
  private int i;

  public Dog();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method Animal."":()V
       4: aload_0
       5: bipush        22
       7: putfield      #2                  // Field i:I
      10: return

  public void show();
    Code:
       0: aload_0
       1: invokespecial #3                  // Method Animal.show:()V
       4: return
}

    有了上面的说明,那么我们在实例化Animal和Dog之后,分别调用成员变量i,可以得到值2和22,而调用方法show时则会得到2和2,所以从内存控制上来看,Dog在初始化的时候会给成员变量i分配两块内存,记住是两块,假如LittleDog再继承了Dog,那么就是3块内存,而show方法只会分配一块内存;

    那么考虑下面问题,在Dog中重写show方法,代码保持和Animal一样打印成员变量i,那么实现如下代码:

   Dog dog = new Dog();
   Animal animal = dog;
   dog.show();
   animal.show();
   System.out.println("访问Dog:" + dog.i);
   System.out.println("访问Animal:" + animal.i);

    程序将输出什么呢?

    答案是:

        22
        22 
        访问Dog:22
        访问Animal:2

    由此可以得出下面结论:对于一个引用类型变量而言,当它访问引用的对象的实例变量时,该实例变量的值取决于声明该实例变量是的类型,当它访问对象的方法时,该方法的行为取决于它实际引用的对象的类型;

    所以继承成员变量和继承方法的区别在于,当他们被访问时,成员变量的值属于声明该变量的对象,方法的调用属于实际引用它的对象,在内存分配上,成员变量会另外分配内存,而继承方法不会,除非方法被重写,或者不允许继承

 

 

你可能感兴趣的:(java)