第六章 重复运用classes
在面向过程的语言中重复运用代码只是简单的复制代码,以达到重复运用的目的,而在面向对象的java程序中,代码的重用主要体现在2点
1、在新的class中使用既有的class,这中方法称之为"组合"。但是这种重用方式只是很单纯的重复运用以有的代码 功能,而非重复运用其形式。
2、让新的class成为既有class的一类,并且根据需要加入新的功能,而无须更动原有class,这种方法称之为"继承"。
组合语法
其实组合我们在以前的例子中已经大量的用到了,我们只要将对象句柄放置在class中就是组合!
class compostion
{
private String s;
compostion()
{
System.out.println("compostion()");
s=new String("hello");
}
public String toString()
{
return s;
}
}
public class test
{
compostion c; //对象句柄
int i;
public void show()
{
System.out.println("int = "+i);
System.out.println("compostion = "+c);
}
public static void main(String args[])
{
test t = new test();
t.show();
}
}
其中每个非基本数据类型的对象都有一个toString()方法,该函数用于将compostion转换为一个string,和其他string相加class中基本数据类型会被初始化为默认值,而对象句柄会被初始化为null。如果你要使用该句柄,切记要初始化,否则会抱空指针错误!
继承
继承是java语言中极其重要的一部分,使用关键字extends来实现,这样变自动的让子类获得了父类中所有的成员数据和函数。而java中所有的类甚至包括你自己已经定义的或者将要定义的类都是继承自object类的,在编译器内部进行的隐式继承
class base
{
int i=10;
public void show()
{
System.out.println("base method");
}
public static void main(String args[]) // java允许在同一个文件中的class拥有各自的main()
{
new base().show();
}
}
class derived extends base //继承
{
public void show() //覆盖了base的函数
{
System.out.println("derived method");
super.show(); //调用base的函数
}
public void newMethod() //子类中新加入的函数
{
System.out.println(i); //打印base中的数据
}
public static void main(String args[])
{
derived d = new derived();
d.show();
d.newMethod();
}
}
base的初始化
当子类被初始化的时候系统会先将被继承的父类初始化,java编译器会在调用子类构造函数之前调用父类的构造函数
class base
{
base()
{
System.out.println("base method");
}
}
class derived extends base
{
derived()
{
//super(); 系统会自动加入对父类的调用
System.out.println("derived method");
}
public static void main(String args[])
{
derived d = new derived();
}
}
假如你的父类是带有引数的class,那么编译器是不会自动调用构造函数的,你必须使用super来调用,否则系统会抱错
class base
{
base(String s)
{
System.out.println(s);
}
}
class derived extends base
{
derived()
{
super("base method"); //必须是构造函数的第一行语句
System.out.println("derived method");
}
public static void main(String args[])
{
derived d = new derived();
}
}
兼容组合和继承
有的时候,我们在编写class的时候不但用到组合,还用到继承。
组合和继承之间的选择
如果你只是希望在新class中使用到既有class的功能,并且隐藏起实现细目,那么最好使用组合。
如果你希望为既有的class开发一种特殊版本,那么继承再好不过了。
如果你的既有类和新类是一种is a的(是一个)关系,那么使用继承,如果是一种has a(有一个)关系,那么使用组合
protected
我们再来复习一下protexted的意义:继承此class的子类,可以访问该类的成员,并且对于一个包内的其他类是friendly的
渐进式开发
继承的优点之一就是支持渐进式开发,在这种开发模式下,你可以在程序中加如新的程序代码,但是却不影响父类的代码
向上转型
由于继承的关系,在子类中可以使用父类的所有函数,并且任何发送给父类的消息,也可以发送给子类
class base
{
protected void show()
{
System.out.println("base method");
}
protected void getSomeone( base b)
{
b.show();
}
}
class derived extends base
{
public static void main(String args[])
{
derived d = new derived();
d.getSomeone(d);
}
}
注意在getsomeone的函数定义中,我们定义的是他只能接受一个base类的句柄,然而他居然接受了子类的句柄。因为子类虽然和父类不太相同,但是他毕竟是父类的一种因此适用与父类的函数当然也适用与子类咯,我们把这种把子类转型为父类的做法叫做向上转型upcasting
class base
{
public void methodOne()
{
System.out.println("base.methodOne");
}
public void methodTwo()
{
System.out.println("base.methodTwo");
}
}
class derived extends base
{
public void methodOne()
{
System.out.println("derived.methodOne");
}
public void methodTwo()
{
System.out.println("derived.methodTwo");
}
public void methodNew()
{
System.out.println("derived.methodNew");
}
public static void main(String[] args)
{
base d = new derived();
d.methodOne(); //虽然已经向上转型成为了base,但是调用的函数还是基类的函数,显示derived.methodOne
d.methodTwo(); //同上
//d.methodNew(); 因为已经向上转型,所以丢失了子类特有的函数
}
}
为什么需要向上转型?
子类是父类的一个超集,因此子类中至少包含父类中的函数,并且可能会更多。然而向上转型会使子类遗失和父类不同的方法,向上转型一定是安全的,因为这是从特殊类型改变成通用类型。
组合vs继承
当你需要向上转型的时候是使用继承的最佳时间
关键字final
什么是final?就是"最终"的意思,也就是不可改变的意思,我们在这里讨论3中final:data、method、class
final data
final的数据是固定不变的,不变的数据称之为常量,他是很有用的,因为它
1、可以是永不改变的编译期常量。编译期常量可以在编译期执行某些计算,减少执行期的负担,此类常量必须是基本数据类型,使用final修饰,必须给定初值。
2、可以在执行期被初始化,而你却不想再改变他。
如果某个数据既有static还有final,那么他就会拥有一块无法改变的储存空间。当把final用于对象时,final让句柄保持不变不能再重新指向其他对象,然而对象本身却是可以改变的,这点和final的基本数据类型不能改变其值的特点有所不同。
class Value
{
int i = 1;
}
class finalData
{
//编译期常量
final int i = 10;
static final int II = 20;
//典型的常量
public static final int III= 30;
//执行期常量
final int iiii = (int)(Math.random()*20);
static final int iiiii = (int)(Math.random()*20);
Value v = new Value();
final Value vv =new Value();
static final Value vvv =new Value();
//数组
final int[] a = { 1,2,3,4,5,6};
public void show(String id)
{
System.out.println(id+" : "+"iiii ="+iiii+" , iiiii = "+ iiiii);
}
public static void main(String[] args)
{
finalData fd = new finalData();
//fd.i++; 不能改变值
fd.vv.i++; //final对象可以改变其对象值
fd.v=new Value(); //非final
for(int i = 0;i<fd.a.length;i++)
fd.a[i]++; //final对象可以改变其值
//fd.vv = new Value(); //final对象的句柄不能改变其引用的对象
//fd.vvv =new Value(); //final对象的句柄不能改变其引用的对象
//fd.a = new int[3]; //final对象的句柄不能改变其引用的对象
fd.show("fd");
System.out.println("creat new finalData");
finalData nfd = new finalData();
fd.show("fd");
nfd.show("nfd");
}
}
/*显示fd : iiii =2 , iiiii = 3
creat new finalData
fd : iiii =2 , iiiii = 3
nfd : iiii =1 , iiiii = 3*/其中因为iiii是非static的,因此每次的值不相同。static的final数据只会在装载的时候进行初始化,不会再每次产生新对象时再被初始化一次
空白的final
final同时允许将数据成员声明为final的,但是并不初始化,但是,你必须在使用该成员数据之前保证该数据成员的初始化。这样做的好处是既保证了数据的恒久不变性,
又可以根据对象的不同产生不同的final数据,具有更大的灵活性。
class finalTest
{
final String s;
finalTest(String s)
{
this.s=s;
System.out.println(this.s);
}
public static void main(String[] args)
{
new finalTest("hello");
new finalTest("bye");
}
}
final的引数
当final的引数是基本数据类型的时候,其值不允许改变。而当是对象时,则不允许将改变句柄的指向。
final的函数
使用final函数的意义有二:
1、锁住该函数,不允许子类进行改变,也就是不能覆写
2、让短小的函数提高执行效率
final和private
class中private的函数其实就是天生的final函数,因为你无法使用private函数,当然也就无法覆写该函数,而当你尝试覆写他的时候,其实是写了一个全新的函数,把final加在private函数上,不具备任何意义!
final的class
当你确切的希望该class不能被继承的时候,就需要用到final,然而无论class是否是final的,class中的数据成员既可以是final的也可以不是
初始化以及class的装载
java中只有在必要的时候才会装载相关类,一般来说是class的初次使用和继承时装载,所谓初次使用,不仅仅是产生对象的时候,也有可能是某个static函数或者数据成员被使用的时候。首次使用class的这个时间点,也是static初始化的时候,任何static的class成员被装载时,会根据他们出现的次序分别初始化,并且只会进行一次初始化。
继承和初始化
下面的这个程序展示了继承和装载的顺序
class base
{
int i = 10;
int j ;
base() //6、执行base构造函数
{
show(" i = "+i+", j = "+j);
j=20;
}
static int m = show("static base.m init"); //2、进行静态成员的初始化以便装载
public static int show(String s)
{
System.out.println(s);
return 30;
}
}
class derived extends base
{
int k =show("derived.k init"); //7、进行基本数据类型的初始化
derived() //8、已经完成数据成员的初始化动作,开始执行函数
{
show(" k = "+ k);
show(" j = "+j);
}
static int n =show("static derived.n init"); //3、进行静态成员的初始化以便装载
public static void main(String[] args) //1、启用derived.main()静态方法,class derived被使用,引起装载器启动,发现extends,于是装载base
{
show("derived constructor"); // 4、装载完毕,开始执行程序
derived d = new derived(); //5、开始产生对象,由于extends的关系,先产生base对象
}
}
输出/*static base.m init
static derived.n init
derived constructor
i = 10, j = 0
derived.k init
k = 30
j = 20*/
总结
继承和组合都允许你根据已经有的类产生新类。使用组合来重复运用以有类,使其成为新类中的一部分;继承则用于接口的复用,由于子类继承了父类的接口,因此可以向上转型,这对多态是极为重要的。推荐多使用组合,组合具有弹性,继承具有灵活性。