【左京淳的JAVA学习笔记】第五章 class定义与物件生成

如果把程式当成是魔法,前面几章都是基本的咒文。
到这章开始需要用到想像力了。

class(类)

class像是没有生命的模型,里面记载了一些关于物件的叙述与特徵。
透过new()方法,可以从模型中複製一或多个一模一样的物件出来,并且具有生命力,可以执行各种动作。想像一下你的玩具车或钢弹模型被附加了想像力,真正动起来的样子吧!

来看看以下範例:

class Employee {  int id;  void setId(int i){    id = i;  }  int getId(){    return id;  }}
class Sample5_1 {  public static void main(String[] args) {    //实例化社员A    Employee a = new Employee();    //设定A的社员编号    a.setId(100);    //实例化社员B    Employee b = new Employee();    //设定B的社员编号    b.setId(200);        //请电脑告诉我们社员A和社员B的编号    System.out.println("社员A的编号:" + a.getId());    System.out.println("社员B的编号:" + b.getId());  }}

执行结果

社员A的编号:100社员B的编号:200

JAVA的物件像是黑盒子,如果要把资料存进去或拿出来,得要透过特定的方法。
通常会命名为set和get方法,利用这两个方法来从物件里存取资料。如範例中的setId()和getId()。
set资料的时候要準备引数,用来放进物件里。

void setId(int i) //指定引数为单个int型数据,void表示不返回数值。

get资料的时候不用引数,但要指定拿出资料时,资料的型态。

int getId() //没有引数,返回int数值。

在範例5_1里使用new()实例化了社员A和社员B。这两个物件複製了class Employee的所有资料及方法。在实例化之后,A和B各自保有自己的资料。

变数的使用範围(scope)

在方法内定义的变数又称为区域变数,只适用于该方法内(也就是{}的里面)。看看以下範例:

class Test {  int x;  public static void main(String[] args) {    boolean y = true;    if (y){      String z = "JAVA";    }    System.out.println(z);  }}

变数x的使用範围在class Test内
变数y的使用範围在main method内
变数z的使用範围在if内,所以System.out.println(z);这行因无法找到变数z,而导致编译错误。

建构式(constructor)

class除了变数及方法外,还可以定义建构式。
建构式是物件实例化的时候,会执行一次的式子(相当于将物件初期化)。
建构式长得跟一般方法有点像,透过以下特徵来区隔两者:

建构式的名字与class一样没有返回值可以建立複数个名称一样,但接收不同引数的建构式。依照引数的格式,会执行相应的建构式。

如果没有写建构式的话,JAVA预设会给一个空的建构式,长得像这样:Class名(){}
这就是为什么前面的案例没有给任何引数也能执行的原因。
不过一旦自己写了建构式,JAVA就不会再帮忙写空的建构式了。
看看以下範例:

class Employee {  int id;  String name;  //建构式定义  Employee(int i){    id = i;  }  Employee(int i,String s){    id = i;    name = s;  }}class Sample5_2 {  public static void main(String[] args) {    //实例化员工    Employee a = new Employee();  //会因为找不到相应的建构式而报错    Employee b = new Employee(100);    Employee c = new Employee(100,"Tom");  }}

从此範例可以发现,当要实例化一个员工时
若没有提供任何引数,则会因为找不到相应的建构式而报错
至少需提供int数值(id编号),此时Employee(int i)建构式会被呼叫。
若提供了int数值和String资料,则Employee(int i,String s)建构式会被呼叫。
这个特性称为overload,确保了资料不足的时候,程式仍具有通用性。

overload的特性不只适用于建构式,也适用于一般方法。如下例:

class Test {  void myPirnt(){    //处理1  }  void myPirnt(int i){    //处理2  }}

static修饰子

static是静态的意思,static修饰子可以放在变数或方法的前面,表示其属于class的变数或方法,而非属于实例化物件(instance)。
JVM在执行时,会先载入静态资料和class,然后执行main方法,然后进入动态的世界(实例化物件、建立非静态的变数等..)

注意:
static变数或方法于class建构时被建立。
非static变数或方法于实例化物件时被建立。
这表示非static变数或方法(后被建立者)可以访问static变数或方法(先被建立者)
相对的,在static方法中要访问非static变数或方法时,需要先实例化一个物件之后才能使用。
请看下例:

class Sample5_3 {  int instanceVal;  static int staticVal;    int methodA(){return instanceVal;}             //OK  int methodB(){return staticVal;}               //OK  static int methodC(){return instanceVal;}      //NG static方法不能直接访问instance变数  static int methodD(){return staticVal;}        //OK  static int methodE(){                          //OK 实例化一个物件后,static方法可以访问此物件的instance变数    Sample5_3 obj = new Sample5_3();    return obj.instanceVal;  }}

就如同各个物件拥有自己的数据,class也可以保存自己的数据(即static变数)。
由同一个class实例化出来的各个物件,可以共用和修改这些static变数。
※要使用class变数的时候,别忘记加上static喔!只有静态变数和方法,会在class建立时一同被建立。非静态的则是物件实例化时会被建立。

Static Initializer (类的建构式)

刚刚提到物件被实例化时,会执行建构式。而Static Initializer相当于类的建构式。
当类别初次被使用时,Static Initializer会被执行。
看以下範例:

class Foo {  static {    System.out.println("Foo class: Static Initializer");  }  Foo() {    System.out.println("Foo class: Constructor");  }}class Sample5_4 {  static {    System.out.println("Sample5_4 class: Static Initializer");  }  public static void main(String[] args) {    System.out.println("Sample5_4 class: Main Method");    Foo f = new Foo();  }}

执行结果

Sample5_4 class: Static InitializerSample5_4 class: Main MethodFoo class: Static InitializerFoo class: Constructor

以上的结果翻译成中文就是:

JVM(JAVA虚拟机)先载入了Sample5_4类,执行了他的Static Initializer。然后执行main方法。由于在main方法里new了一个 Foo物件,所以Foo类先被载入。然后Foo物件被初始化,Constructor被执行。

Access Modifier(存取修饰子)

还记得main方法前面的public吗?这就是存取修饰子,可以放在类、变数及方法前面,决定这些东西的公开範围(存取权限)。
例如JAVA的实例化物件里面,通常会把变数设为private,而get,set等方法设定为public。以确保物件里面的值不会直接被外部看见或修改,而是只能透过固定的方法来访问。

class Employee {  private int id;  public Employee(int i) {id = i;}  public int getId() {return id;}}class Sample5_5 {  public static void main(String[] args) {    Employee emp = new Employee(100);    System.out.println(emp.id);    System.out.println(emp.getId());  }}

尝试编译以上的档案会发现,System.out.println(emp.id);这行会因权限不足而报错。必须使用getId()方法才能访问id的值。

除了public和private之外,还有protected和预设值(未指定)两种。
要解释修饰子,首先得先认识JAVA的package(包)的概念。包就像是资料夹,里面存放不同的class文件。
文法如下:

package 包名;class X {…}

如果没指定包名的类,则会被JAVA归类到没有名字的包内。

关于包的详细介绍待会儿会再说明,先回到修饰子的说明。
public表示完全开放,任何别的包里的类都可使用此类、变数或方法。
protected只开放给同一个包使用,或是不同包但继承了此类的子类也可使用。
不指定,即採用预设值,只开放给同一个包使用。
private,仅该类自己可以使用。

值及物件的複製

在JAVA里面,如果把一个基本数值的资料,当作引数丢给一个方法进行处理。则丢进去的会是一个複製的新值,而非原本的数值。例如:

int num = 10;obj.method1(num);System.out.println(num);  //方法外的num保持为10void method1(int num) {  num += 10;  System.out.println(num);  //方法内的num为20}

写成以下的样子可能比较好理解

void method1(int n) {  n += 10;  System.out.println(n); //n为20}

也就是方法内的num其实跟外部的num没有任何关係。只是複製了其值而已。而且num作为引数被传入方法之后,其变数名也可以任意取名。

不过如果引数是物件的话,则物件本身会被传入方法内。

int[] array = {10};obj.method2(array);System.out.println(array[0]);  //方法外的值也变为20void method2(int[] array) {  array[0] += 10;  System.out.println(array[0]);  //方法内的值为20}

package(包)的建构

请看以下範例:

package chap05.com.se;class Foo {  void print() {    System.out.println("package sample");  }}class Sample5_6 {  public static void main(String[] args) {    Foo f = new Foo();    f.print();  }}

由于在java文件中指定了包的路径,因此编译和执行时也都要符合设定的路径。
首先在当前路径下,建立以下资料夹chap05/com/se,然后把Sample5_6.java档放进去。
进行编译

javac chap05/com/se/Sample5_6.java

执行

java chap05.com.se.Sample5_6

由于手动新增资料夹很麻烦,所以也可使用-d指令自动建立路径(d后面的"."表示指定当前路径为起点)

javac -d . Sample5_6.java

package的引用(import)

如果需要用到的类,在不同包里面时,就使用import指令来引用。
建立以下的範例档案:
Foo.java

package chap05.com.se.ren;public class Foo {  //要给不同包的程式使用,必须加上public修饰子  public void print() {  //要给不同包的程式使用,必须加上public修饰子    System.out.println("package sample");  }}

Sample5_7.java

package chap05.com.se;import chap05.com.se.ren.Foo;//import chap05.com.se.ren.*;  //也可以使用"*"来引用ren包下面所有类。class Sample5_7 {  public static void main(String[] args) {    Foo f = new Foo();    f.print();  }}

先编译Foo.java,再编译Sample5_7.java(由于Sample5_7里面import了Foo,所以如果没先建立Foo的话会报错)

javac -d . Foo.javajavac -d . Sample5_7.java

※因中文导致编译失败请指定以utf-8编码进行编译,如下例:

javac -d . -encoding utf-8 Foo.java

编译完成后执行

java chap05.com.se.Sample5_7

以上是第五章 class定义及物件的学习心得,接下来第六章会介绍继承与多型。

参考教材: JAVAプログラマSilver SE8 - 山本 道子


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章