08.创建和使用类

 创建对象

Car myCar = new Car();

这一行代码包含了三个部分:

  1. Car myCar表示声明了一个Car类型的变量myCar,即myCar是一个引用类型变量
  2. new关键字表示创建一个对象
  3. Car()则是构造器的调用,对创建的对象进行初始化

每次用new创建一个对象,就会在堆中分配新的内存来保存新的对象信息,而myCar这个引用变量本身则存储在栈中。


 

 堆和栈的区别

  • 方法中定义的基本类型变量和引用类型变量,其内存分配在栈上,变量出了作用域(即定义变量的代码块)就会自动释放

  • 堆内存主要作用是存放运行时通过new操作创建的对象

下面这张图展示了Car myCar = new Car();这行代码运行时的内存状态:

 

图中0x6E34是我们假设的内存地址。myCar作为一个引用类型变量保存在栈中,你可以直观地认为myCar变量保存的就是所创建对象在堆中的地址0x6E34,即myCar引用了一个对象,这正是引用类型变量这个叫法的原因;而堆中则保存着的对象本身,包含了其成员变量,如speedcolorengine

如果成员变量没有在构造器中初始化,则会是默认值。speedcolorint基本类型,默认值为0engine引用类型,默认值为null,即不引用任何对象

 

一个对象的成员变量,如果是引用类型的变量的话,比如engine,则该成员变量可以引用到堆中的其它对象。 如下代码:

Engine engine = new Engine(180);
Car myCar = new Car(0xffffff, 100, engine);

此时内存状态如下:

 

堆中的对象如果没有任何变量引用它们时,Java就会适时地通过垃圾回收机制释放这些对象占据的内存。你可以认为没有任何引用的对象(即没有任何引用类型的变量指向它),这个对象就成为"垃圾",Java虚拟机就会清理它们,为将来要创建的对象腾出空间。


 引用类型和基本类型的区别

int color = 0;
int speed = 100;
Car myCar = new Car(color, speed);

则内存状态如下:

与引用类型myCar不同,基本类型变量的值就是存储在栈中,作用域结束(比如main方法执行结束)则这些变量占据的栈内存会自动释放。


 

访问对象属性 

在类的内部可以访问自身的属性,在类的内部可以通过this来访问自身的属性。

 在外部(即其它类中)也可以访问一个类的非private属性,通过对象名.属性名的方式进行访问。


 

访问对象方法

方法可以在一个类内部进行调用

void run() {
    this.startup();
    System.out.println("前进,速度为:" + speed);
}

 

 即在类的内部可以通过this来访问自身的方法。

 在外部(即其它类中)也可以访问一个类的非private方法,通过对象名.方法名的方式进行访问。


方法的返回和参数

可见调用方法时,传入的实参可以为字面量、变量或者表达式。

 int e = calculator.add(c, c * d);

int c = calculator.add(1, 3);

public int add(int a, int b); 


 方法的调用过程

int e = calculator.add(c, c * d);为例,当程序执行到这一行代码时,会跳转到add()方法的内部执行:

  • ab就类似于在add()方法内定义的两个局部变量,它们是main()方法中cc * d的值的拷贝;
  • add()执行完之后,ab两个形参专用的内存就会被释放掉,同时会返回main()方法继续执行其接下来的代码。

 


 

基本类型参数

传参即是实参的值赋给形参。对于基本类型的形参,在方法内部对形参的修改只会局限在方法内部,不会影响实参。

比如,给Calculator增加一个increase(int)方法:

 1 基本类型参数
 2 传参即是实参的值赋给形参。对于基本类型的形参,在方法内部对形参的修改只会局限在方法内部,不会影响实参。
 3 
 4 比如,给Calculator增加一个increase(int)方法:
 5 
 6 class Calculator {
 7     public int add(int a, int b) {    
 8         return a + b;
 9     }
10     
11     public int increase(int a) {
12         return ++a;
13     }
14     public static void main(String args[]) {
15         Calculator calculator = new Calculator();
16         int x = 10;
17         int y = calculator.increase(x);
18         System.out.println(x);
19     } 
20 }

increase(int a)方法定义了一个int形参a,将x作为实参传入,虽然方法内部做了自增操作,但是并不会改变x的值。因此,打印出来的x的值是10而不是11


 

引用类型参数

引用类型的实参传入方法中时,是将对象的引用传入,而非对象本身。因此,在方法执行时,实参和形参会引用到同一个对象。

在方法结束时,形参占据的内存虽然会被释放,但是通过形参对对象进行的修改则不会丢失,因为对象依然保存在堆中。

例如,Car的构造器中如果对engine进行修改:

1 public Car(int color, int speed, Engine engine) {
2     this.color = color;
3     this.speed = speed;
4     engine.power = 200; // 这里讲engine的power赋值为200
5     this.engine = engine;
6 }

 

则在main方法中执行如下代码

1 Engine myEngine = new Engine(180);
2 Car myCar = new Car(0xffffff, 100, myEngine);
3 System.out.println(myEngine.power);

 在Car myCar = new Car(0xffffff, 100, myEngine);这行代码中,我们将myEngine作为实参传递给了Car的构造器,由于构造器中的engine形参此时和myEngine指向同一个对象,因此执行完构造器后,myEnginepower值会从180变成200

再来考虑另外一种情况:

1 public Car(int color, int speed, Engine engine) {
2     this.color = color;
3     this.speed = speed;
4     engine = null; // 这里将engine设置为null
5 }

 

myEngine传入到这个构造器中执行后,myEngine是否会变为null呢? 答案是否定的。虽然实参指向的对象可以在方法调用时被修改,但是实参本身的值(你可以认为是实参引用的对象地址)不会发生改变。

 


 初始化成员变量

成员变量直接赋值:

private String title = "默认标题";

 通过final方法赋值(无法被重写的特点,就完成了初始化的任务)

1 private String content = initContent();
2     
3     private final String initContent() {
4         return "默认内容";
5     }

 通过构造块初始化

还有一种方式是通过初始化构造块来,编译器会将初始化构造块的代码会自动插入到在每个构造器中,优先于构造器执行。例如:

 1 class Post {
 2     private long id;
 3     private String title;
 4     private String content;
 5     
 6     {
 7         title = "默认标题";
 8         content = "默认内容";
 9     }
10     
11     public Post() {
12     }
13     
14     public Post(long id) {
15         this.id = id;
16     }
17 }

 

这种情况下Post的两个构造器内部虽然没有初始化titlecontent,但是由于构造块的存在,这两个成员变量会进行赋值。构造块可用于多个构造器复用代码(其优点)。


 final关键字

 但总体上来说,它指的是“这是不可变的”。

 final关键字是我们经常使用的关键字之一,它的用法有很多,但是并不是每一种用法都值得我们去广泛使用。它的主要用法有以下四种:

  1. 用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。但其引用的对象的值是可以改变的,见下图。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值;
  2. 用来修饰方法参数,表示在变量的生存期中它的值不能被改变;
  3. 修饰方法,表示该方法无法被重写;
  4. 修饰类,表示该类无法被继承。

上面的四种方法中,第三种和第四种方法需要谨慎使用,因为在大多数情况下,如果是仅仅为了一点设计上的考虑,我们并不需要使用final来修饰方法和类。

 

 

posted on 2018-01-29 23:15  婴儿学步  阅读(123)  评论(0)    收藏  举报