在创造各式各样的物件时,有很多时候会发现怎麽重复的代码很多。
为了解决这个问题,可以采用继承与介面的方式。
继承的文法
class Employee {}
class sales extends Employee {}
想像一间公司里有许多员工,有业务有後勤有管理人员。这些职位的人员虽然都有各自的特性,但也都是员工的一种,他们应该会有员工编号之类的共用属性。
例如sales类继承了Employee类,就可以获得里面所有的变数与方法,不用重新写一次。而且还可以追加新的方法,或是覆写掉不喜欢的方法!
介面的文法
class Employee implements MyInterface {}
介面有点类似继承,一样可以获得来自其他类里面的变数和方法。不过两者概念上有点不一样。
继承只能继承一个类,概念像是一个Employee类可以升级为业务、後勤或是管理人员,拥有进阶的能力。
而介面可以引用很多个,类似装备或工具,可以随时装上或拆卸。
接下来介绍一下关於继承的细节
被继承的类又被称为Super类或是父类,继承类则被称为Sub类或子类。
请看以下关於覆写(override)的范例:
class Super {
public void print(String s){
System.out.println("Super class : " + s );
}
public void method(){}
}
class Sub extends Super {
public void print(String s){
System.out.println("Sub class : " + s );
}
//void method(){}
}
class Sample6_1 {
public static void main(String[] args) {
Super s1 = new Super();
s1.print("text"); //会调用Super类的方法
Sub s2 = new Sub();
s2.print("text"); //会调用Sub类的方法
}
}
执行结果
Super class : text
Sub class : text
可以发现Sub class里面的print方法被复写掉了。与Super class的执行结果不同。
要注意的是,覆写Super类的方法时,使用一样的名字和返回值类型,修饰词则需一样或者权限更开放。
(权限开放程度: public > protected > 不指定 > private)
例如本案例的method()在Super class里面的修饰词是public,则Sub class里面的method()也必须是public,如果没写或写其他修饰词就会报错。
final修饰词可以用在变数前面,让其成为无法被修改的定数。也可以放在方法前面,让其无法被覆写。如果是放在类的前面,则此类无法被继承。如下例:
class Super {
final void method(){}
}
final class Super {}
this用来代称本物件,用来解决变数代称的问题。
例如有个有名的对话故事如下
「谁打我?」
谁:「我没打人」
人:「我知道」
我:「知道什麽?」
程序范例如下
int id;
void setId(int id){
//id = id; //左右两个id都是同一个变数(第二行的id)。
this.id = id; //this.id是指这个物件的id(第一行的id)。
}
this也可以用来减少重复写代码的问题,例如应用在建构式中,范例如下:
class Foo {
String s; int i;
public Foo(){
this("no_data");
}
public Foo(String s){
this(s,1);
}
public Foo(String s, int i){
this.s = s;this.i = i;
System.out.println("String : " + this.s);
System.out.println("int : " + this.i);
}
}
class Sample6_2 {
public static void main(String[] args) {
System.out.println("调用Foo()------");
Foo f1 = new Foo();
System.out.println("调用Foo(String s)------");
Foo f2 = new Foo("Tom");
System.out.println("调用Foo(String s, int i)------");
Foo f3 = new Foo("Mary",30);
}
}
执行结果
调用Foo()------
String : no_data
int : 1
调用Foo(String s)------
String : Tom
int : 1
调用Foo(String s, int i)------
String : Mary
int : 30
可以看到Foo里面有三个建构式,分别对应无资料、只有文字、有文字及数值资料等三种状况。
利用this指令,当无String资料时,将预设值("no_data")填入後,传给第二个建构式。
在第二个建构式中,当无int值时,将预设值(1)填入後,然後传给第三个建构式。
在第三个建构式中进行完整的处理。
利用这样的结构,就不需要重复写很多个类似的建构式了。
super表示父类,利用这个指令可以调用父类的方法或变数。
虽然子类一般都继承了父类的方法,但是如果有覆写的状况,则可以利用super调用原本的父类方法。
class Super {
int num;
public void methodA(){num += 100;}
public void print(){System.out.println("num = " + num);}
}
class Sub extends Super {
public void methodA(){num += 500;}
public void methodB(int num){
methodA();
print();
super.methodA();
print();
}
}
class Sample6_3 {
public static void main(String[] args) {
Sub s = new Sub();
s.methodB(0);
}
}
执行结果
num = 500
num = 600
调用覆写後的方法让num增加了500
再调用父类原本的方法,让num增加了100
当子类被实例化时,会先执行父类的建构式,再执行子类的建构式。
但是有个跟直觉不太一样的地方,需要透过super指令来修正。看看以下范例:
class Super {
public Super(){System.out.println("Super()");}
public Super(int a){System.out.println("Super(int a)");}
}
class Sub extends Super {
public Sub(){System.out.println("Sub()");}
public Sub(int a){System.out.println("Sub(int a)");}
}
class Sample6_4 {
public static void main(String[] args) {
Sub s1 = new Sub();
Sub s2 = new Sub(10);
}
}
执行结果
Super()
Sub()
Super()
Sub(int a)
由结果的第一行及第二行可以发现,实例化物件时,确实先执行了父类的建构式,然後执行子类的建构式。
但是第三行为什麽不是super(int a)呢?
这是因为当子类被实例化时,预设会执行父类的无参数建构式(因为参数并未传递给父类)。
如果要避免这种现象,需要使用super指令,把参数丢给父类。如下例:
class Super {
public Super(){System.out.println("Super()");}
public Super(int a){System.out.println("Super(int a)");}
}
class Sub extends Super {
public Sub(){System.out.println("Sub()");}
public Sub(int a){
super(a);
System.out.println("Sub(int a)");
}
}
class Sample6_5 {
public static void main(String[] args) {
Sub s1 = new Sub();
Sub s2 = new Sub(10);
}
}
执行结果
Super()
Sub()
Super(int a)
Sub(int a)
在JAVA里面,处理内容被详细记载,可以实例化并使用的类称为实例类。相对的,只记载了方法名称,却未填写内容的类,被称为抽象类。
想像一下,假如我们想设计一台吸尘器,但形式和内容都还不确定,只知道它需要110V的电,能够吸灰尘。
那麽首先我们就先设计一个具有110V插头、具有吸灰尘方法的class,至於详细的内容,就等物件实例化时再填写就好。
抽象类具有以下特点:
无法被实例化。要实例化需新增一个实例类,继承了此抽象类之後,把所有抽象方法都覆写成实例方法(记入内容)。
抽象类里面可以写抽象方法以及实例方法
抽象类也可以继承抽象类。
抽象类和抽象方法的写法,就是在最前面加上一个abstract修饰词即可。
abstract class 类名{}
abstract 返回值 方法名(引数);
范例
abstract class Employee{}
class abstract Employee{} //报错,abstract须放在最前面
abstract void funcA();
abstract void funcA(){}; //报错,abstract方法不需要{}及其内容。
void abstract funcA(); //报错,abstract须放在最前面
介面说起来有点像抽象类,里面也放了一些抽象方法。不过不一样的是,介面使用implements来引用,而非extends继承。
请看以下范例:
interface MyInter1 {
double methodA(int num);
default void methodB(){System.out.println("methodB()");}
}
interface MyInter2 {
int methodC(int val1, int val2);
static void methodD(){System.out.println("methodD()");}
}
class MyClass implements MyInter1,MyInter2 {
public double methodA(int num){return num * 0.3;}
public int methodC(int val1, int val2){return val1 * val2;}
}
class Sample6_6 {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println("methodA() " + obj.methodA(10));
System.out.println("methodC() " + obj.methodC(10,20));
obj.methodB();
//obj.methodD(); //error
MyInter2.methodD();
}
}
执行结果
methodA() 3.0
methodC() 200
methodB()
methodD()
说明
methodA和methodC是抽象方法,在MyClass里面被覆写为实例方法而能执行。(注意覆写时使用了public修饰词,这是因为覆写时的权限必须宽於抽象方法。)
methodB前面加了default修饰词,这是因为interface类原本是只能写抽象方法的,但JAVA SE8之後利用default修饰词也可以写实例方法。
methodD是static方法,看起来没有传递给实例化物件使用,只能由interface呼叫。
在interface里面,储存值会自动加上public static final修饰词。
在interface里面,方法会自动加上public和abstract修饰词
interface的继承
interface简称为IF,也可以使用继承,范例如下:
interface XIF {
void methodA();
}
interface YIF {
void methodB();
}
interface SubIF extends XIF, YIF{
void methodC();
}
class MyClass implements XIF,YIF {
public void methodA(){System.out.println("methodA()");}
public void methodB(){System.out.println("methodB()");}
public void methodC(){System.out.println("methodC()");}
}
class Sample6_7 {
public static void main(String[] args) {
MyClass c = new MyClass();
c.methodA();c.methodB();c.methodC();
}
}
以下两点请注意:
interface可以继承复数的interface(还记得class只能继承一个class吗?)
将抽象方法覆写时,需使用pbulic修饰词。
为了方便,有时JAVA会自动进行资料型态的变换。
由左至右,比较小的资料储存型态可以转换成比较大的资料储存型态。
byte -> short -> int -> long -> float -> double
char -> int
如果要反向转换的话,需使用()符号进行强制转换。
范例
变数的自动转换
short s = 10;
int i = s;
变数s会从short型自动转换为int型态
引数的自动转换
int i = 100;
method(i);
void method(double b){};
i虽然是int型态的资料,但是作为引数被丢入method时,可以自动转换为double型。
返回值的自动转换
double d = method();
int method(){int i = 100; return i;}
返回的int型资料,可以自动被转换为double型资料填入变数d中。
变数的强制转换
int i = 100;
short s = (short)i;
由大至小的转换需要使用()符号进行强制转换。
引数的强制转换
double d = 10.5
method((int)d);
void method(int i){}
返回值的强制转换
int i = method();
int method(){double d = 10.5; return (int)d;}
计算时的注意点:
两个数值进行计算时,JAVA会自动把双方变为一样的资料型态。
如果原本的资料型态是byte,short等较小的资料型态,会被转换成int型态之後计算。
范例
short s1 = 10;
s1 = ++s1; //可顺利执行。因为没有使用计算符,因此资料型态未被转换。
s1 = s1 + 1; //NG,因为s1会被转换为int型後+1,无法再放回short型的变数里面。
解决例
s1 = short(s1 + 1);
除了数值外,物件也可以自动变换。
子类可以自动变换为父类。
实例类可以自动变换为抽象类。
这是什麽意思呢?
想像员工是一个父类,业务是一个子类。
由於业务保有员工的各种属性,所以可以被当成员工来处理。
但如果是相反方向的强制转换,则须使用()。
例:
class Super {}
class Sub extends Super {}
class Test {
Super super = new Sub(); //子类转父类可自动转换
Sub sub = (Sub)super; //父类转子类须强制转换
}
请看以下范例
class Super {
void methodA(){}
}
class Sub extends Super {
void methodA(){}
void methodB(){}
}
class Test {
Super super = new Sub();
super.methodA();
//super.methodB();
Sub sub = (Sub)super;
sub.methodB();
}
此范例中实例化了一个Sub物件,并转存成Super形式。
super.methodA(); 这行执行的methodA,会是Sub class里覆写後的。
super.methodB(); 这行会报错,因为super class里面没有methodB方法。
须将其转回Sub形式,才能呼叫methodB方法。
举个容易了解的例子,假设员工拥有「沟通(基础)」这个技能,业务也有,但是是升级版的「沟通(进阶)」。
这时你请这位员工执行此技能,就会执行出「沟通(进阶)」(回不去了)。
但基於JAVA的保护机制,你无法让一个人以员工身分执行业务专有的技能,例如「开发市场」。(即便他会)
必须要明白的告诉JAVA说,这个员工是个业务,才能够使用业务的技能。
关於物件的转换,有一些需要注意的点,请看下例:
class Super {}
class Sub extends Super {}
class Foo {}
Super obj1 = new Sub();
Sub sub1 = (Sub)obj1; //可正常执行。
Foo obj2 = new Foo();
Sub sub2 = (Sub)obj2; //NG,无继承关系的物件无法转换,编译时会报错。
Super obj3 = new Super();
Sub sub3 = (Sub)obj3; //NG,本质是Super的物件无法转换为Sub物件,执行时会报错。
以上案例说明了强制转换只是一种声明,并不能改变物件的本质。
所以本来是Sub类的物件,即使转为Super,也可再转回Sub。
但原本是Super的物件,无法转换为Sub。
概念说明如下:
Sub(拥有100%能力) ->Super(被当成Super看待,部分能力受限)->Sub(拥有100%能力)
Super(拥有100%能力) ->Sub(被当成Sub看待,超出自己的能力范围,因此NG)
原来只有超人才能变身成超人呀..
instance是实例的意思,可以判断目标物件是否为特定的class(也包含父类或interface)
来看看范例:
interface A {}
interface B {}
class C {}
class D extends C implements B {}
class E {}
class Sample6_8 {
public static void main(String[] args) {
D d = new D();
System.out.println(d instanceof A);
System.out.println(d instanceof B);
System.out.println(d instanceof C);
System.out.println(d instanceof D);
//System.out.println(d instanceof E);
}
}
执行结果
false
true
true
true
以上是第六章 继承与多型的学习心得,下一章会介绍API的使用。
参考教材: JAVAプログラマSilver SE8 - 山本 道子
<<: SEO:关於 Custom Campaign Tracking
>>: [Android Studio] -- Day 5 主题变换Theme02
我的背景 目前自己前端的程序能力只学过基础 html+css+js,没有独立开发过大型专案的经验,算...
我很重视客户的意见与收货速度。 当时我是网路拍卖的创办人,与客户约定好了要五天内到货。为了达成目标,...
IPv6节点使用本地链接地址(前缀为FE80 :: / 10)引导,并使用多播与DHCP服务器联系。...
ListView相信各位应该或多或少有使用过,是单层式,项目会一个个排列,而ExpandableLi...
VScode: Step 1 开启index.html存挡 Step 2 开启all.js存挡 St...