如果把程序当成是魔法,前面几章都是基本的咒文。
到这章开始需要用到想像力了。
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各自保有自己的资料。
在方法内定义的变数又称为区域变数,只适用於该方法内(也就是{}的里面)。看看以下范例:
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,而导致编译错误。
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修饰子可以放在变数或方法的前面,表示其属於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会被执行。
看以下范例:
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 Initializer
Sample5_4 class: Main Method
Foo class: Static Initializer
Foo class: Constructor
以上的结果翻译成中文就是:
还记得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保持为10
void 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]); //方法外的值也变为20
void method2(int[] array) {
array[0] += 10;
System.out.println(array[0]); //方法内的值为20
}
请看以下范例:
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
如果需要用到的类,在不同包里面时,就使用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.java
javac -d . Sample5_7.java
※因中文导致编译失败请指定以utf-8编码进行编译,如下例:
javac -d . -encoding utf-8 Foo.java
编译完成後执行
java chap05.com.se.Sample5_7
以上是第五章 class定义及物件的学习心得,接下来第六章会介绍继承与多型。
参考教材: JAVAプログラマSilver SE8 - 山本 道子
<<: Golang-gRPC & Protocol Buffers
今天先跟大家说明在我的学习过程中做出的一个小小专题,这个专题在当初还有跟三个同学共同完成,那就先说明...
昨天教的字串格式化你有没有学会了呢╰(*°▽°*)╯,还没学会的在等什麽,快回去复习呀!!!在此附上...
文章内使用Unity 2019 LTS 目标 Bloom效果 Bloom 以下这张图片也是一个常见...
一起来延伸视野,迎接更大的画面吧! 今天要介绍的 FullScreen API 会被忽略的原因可能...
小结 前两天讲了几个比较重要的 Dockerfile best practice 之後,今天要来说一...