Java编程思想学习笔记七:复用类

本文详细介绍了Java中组合与继承的概念及其使用方法,探讨了如何通过组合和继承复用代码,以及它们之间的选择。此外,文章还深入讲解了final关键字的用法、类的加载与初始化过程等关键知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、组合语法

    代码复用是所有语言中都具有的一种特性,我们不必编写功能相同或相似的代码,只需要将原有的代码直接拿来或者是在原有代码的基础上进行改动即可。改动的方式有两种,一种是组合的方式,另一种是继承的方式。组合方式的使用很常见,前边的例子也用到过,就是比如说我们新建了一个类,在这个类中引用了其它类的实例对象,这种就是组合。类中的变量域是成员变量,正如第二章所说,当成员变量为基本类型时,比如int,如果我们没有给它显式的赋值时,编译器会自动的将其初始化为0,同时对于对象的引用,编译器会将其初始化为null,当然这时如果你试图调用对象的任意方法,编译器会抛出错误。编译器大多数都不会为对象引用创建默认的对象,这一点是很有必要的,因为可以减少不必要的负担。所以我们可以通过以下方式初始化这些引用,为他们找到指定的对象。
    1.在定义对象的地方,也就是对象引用建立的时候直接指定对象,这意味着对象的初始化发生在构造器调用之前。
    2.在类的构造器中,如第5章所述,我们可以通过构造器的形式,也就是new,初始化一个对象。
    3.就在这样使用这些对象之前,也就是所谓的延时加载,当我们必须要使用这个对象的时候,发现没有初始化该对象呢,那么进行初始化,这种方式可以提供系统性能,降低资源消耗,在必须用到的时候才创建。
    4.使用实例初始化,这种与构造器的相同之处都是在代码中显式的去创建一个对象,区别在于一个调用了构造器方法,一个赋值了一个实例。
    下面分别举例说明四种初始化的方式:
1.在定义对象的地方,我们在类中定义了String的引用,直接初始化赋值:
class Construct{
	public String cont = "Hello World";
}

2.在类的构造器中,如第五章的构造方法初始化:

package com.chenxyt.java.practice;
class Construct{
	public String cont;
	Construct(String cont){
		this.cont = cont;
	}
}
public class ConstructTest {
	Construct construct = new Construct("Hello");
}

3.延迟加载,我们需要用到这个对象引用了,判断一下是否为null,然后再决定是否初始化

public class ConstructTest {
	public static String s;
	public static void main(String[] args) {
		if(s==null){
			s=new String("Hello");
		}
	}
}

4.使用实例进行初始化,如下边这种,我们没有使用构造方法而是直接赋值了个实例进行初始化

public class ConstructTest {
	public static String s;
	public static void main(String[] args) {
		s="Hello";
	}
}

二、继承语法

    继承是所有面向对象语言的不可缺少的一部分,就像前面所说,我们创建了一个类,就是在继承,如果没有显式的说明继承自哪个类,那么就是继承自Object类。或者我们可以显示的使用extends关键字告诉编译器我们要继承哪个类,这样我们会自动的继承父类中的所有域和方法,当然不包括private修饰的域和方法。
package com.chenxyt.java.practice;
class Father{
	public String Name;
	public int age;
	private double money;
	public void doPrint(){
		System.out.println("Hello World");
	}
}
public class Son extends Father {
	public static void main(String[] args) {
		Son son = new Son();
		son.Name = "Zhang San";
		son.doPrint();
		//money是父类私有的域,所以子类不能进行访问
		//son.money = 2014;
	}
}

子类继承了父类所有的域跟方法,我理解的是对于private修饰的域跟方法也同样继承了下来,只不过private修饰的是类自己私有,对象本身无法访问,比如Father类的对象也无法访问Father类的私有域。同时我做了一个测试来证明这一点。示例中,由于父类显示的写有带参数的构造函数,所以我们必须显示的引用父类的构造函数。

package com.chenxyt.java.practice;
class Father{
	public String Name;
	public int age;
	private double money;
	Father(double money){
		this.money = money;
	}
	public double getMoney() {
		return money;
	}
}
public class Son extends Father {
	Son(double money) {
		super(money);
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		Son son = new Son(2014);
		son.Name = "Zhang San";
		System.out.println(son.getMoney());
	}
}

运行结果:

    

三、代理

    Java中还有一种基于组合与继承之间的关系,但是实际上Java并没有对它提供直接支持。我从文中理解到,这种方式主要是避免使用继承的时候将父类的所有方法都暴露了出来,具体的做法是,使用一个组合,组合内部定义要使用类的对象,然后组合内部再提供一个方法(我们称作方法A),这个方法实现直接使用前边的对象调用要使用的类方法(方法B)即可。方法A我们称作是个代理,方法B则是我们实际要调用的方法。概括说来就是内部提供一个可以调用另一个类的方法的方法(这段理解来源于对书中示例代码的解读,如有不正,敬请指点)
package com.chenxyt.java.practice;
class Construct{
	public void print(){
		System.out.println("print");
	}
	public void say(){
		System.out.println("say");
	}
	public void clean(){
		System.out.println("clean");
	}
}
public class Son{
	public Construct construct = new Construct();
	public void delegation(){
		construct.clean();
	}
	public static void main(String[] args) {
		Son son = new Son();
		son.delegation();
	}
}

四、结合使用组合和继承

    同时使用组合和继承是很常用的事情,一般就是在一个子类中引用一个其它的类实例对象即可。诸如下边这种形式:
package com.chenxyt.java.practice;
class Father{
	//---
}
class Mother{
	//---
}
public class Son extends Father{
	public Mother mother = new Mother();
	public static void main(String[] args) {
		//---
	}
}

    关于继承还有一点说明,前文说到子类可以继承父类的全部,所以对于父类重载了的方法,子类也是可以继承并可以重载使用的。而且可以灵活的使用,避免了像C++中需要屏蔽父类该方法的方式。

package com.chenxyt.java.practice;
class Father{
	public void doFunc(){
		//---
	}
	public void doFunc(String s){
		//--
	}
}
public class Son extends Father{
	public void doFunc(int i){
		//--
	}
	public static void main(String[] args) {
		Son son = new Son();
		son.doFunc();
		son.doFunc("H");
		son.doFunc(1);
	}
}

五、在组合和继承之间选择

    组合和继承都允许在新类中放置子对象,组合是显式的,而继承是隐式的。一般情况下,使用组合的地方多出是想使用其对象,就是在新类内部引用一个对象,然后利用这个对象完成一系列功能。而继承的使用场景主要是新类想使用原来类的一部分接口这种。在使用组合和继承的时候,要注意把我访问权限控制,因为一个完善的访问权限控制可以使程序更加健壮稳定。

六、protected关键字

    前文说到访问权限控制,在一般情况下,为了保证不被他人修改自己的私有域,父类的只有自己可以有权使用的域跟方法会被设置为private,而在一些特殊情况下或许我们会放宽这种约束,即我的东西其他人都不可以用,但是我的子类可以用,这时候就是前文所说的protected关键字的作用了。被protected关键字修饰的域,可以被子类访问。
package com.chenxyt.java.practice;
class Father{
	protected String s = "Hello";
}
public class Son extends Father{
	public static void main(String[] args) {
		Son son = new Son();
		System.out.println(son.s);
	}
}
运行结果,因为字符串s被声明为protected,所以在子类的对象引用son可以访问到:
    

七、向上转型

    在继承的关系中,由于子类继承了父类所有的方法,所以发给父类方法的所有消息,子类都可以接收,注意这里是发给父类方法的所有消息,而不是子类调用了继承的方法。编译器会知道传递的参数引用属于哪一种类型,并将其子类对象引用的类型转换成父类,这个过程称作向上引用。

package com.chenxyt.java.practice;
class Father{
	private static String s;
	Father(String s){
		this.s = s;
	}
	public static void doFunc(Father father){
		System.out.println(s);
	}
}
public class Son extends Father{
	Son(String s) {
		super(s);
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		Son son = new Son("Hello");
		Father.doFunc(son);
	}
}

如上所示,父类中有一个static的方法,并且方法的参数是父类的对象,在子类中直接通过类名.方法的形式调用了该方法,并传递了子类的对象引用作为参数。编译器没有报错,运行结果如下:

    
    向上转型的概念来源于子类继承父类,子类在下,父类在上,由子类转换成父类,所以称作是向上转型。因为子类是父类的超集,所以向上转型总是安全的,相对也有向下转型,将在后文介绍。对于组合和继承,组合关系跟加方便且便于理解也常用,继承的关系虽然重要但不是说一定要多用。

八、Final关键字

    final关键字与static同等重要,final顾名思义就是最终的,不可改变的。所以Java中final关键字也是指“这是无法改变的”。一般无法改变主要是出于两种理由,设计或者效率,这两种相差比较远,所以容易误用。这里主要从三个方面说明final关键字的用法。
    1.final数据:当我们需要告诉编译器这一块数据区域不得改变的时候,比如一个固定的常量,或者一个在运行时候被初始化的值而我们却不希望它被改变,那么就可以使用final来修饰。比如类库中的某个成员变量,提供给开发者使用,但是不允许开发者修改这个变量在程序运行过程中的值,那么就可以使用final修饰。
package com.chenxyt.java.practice;
public class FinalTest{
	private static final String ARG_NAME = "Hello World";
	public static void main(String[] args) {
		System.out.println(ARG_NAME);
	}
}

如果一个常量被修饰为static final 那么表明这个常量是不可被修改且只有一份的,并且它的命名规则要使用大写且用下划线分割所有单词。对于基本数据类型,final表示这个值不可以修改,对于对象的引用,表示这个引用不可以修改,也就是不可以指向别的对象。

    2.final参数:Java中允许方法使用final修饰的参数,这意味着这个参数指向的对象不可以被修改。
package com.chenxyt.java.practice;
class Gzino{
	public void doFunc(){
		//--
	}
}
public class FinalTest{
	public void with(final Gzino g){
		//error! 此处不可以修改该对象的引用
		//g = new Gzino();
		//可以使用该参数
		g.doFunc();
	}
	public void without(Gzino g){
		//没有被final修饰,可以使用
		g = new Gzino();
		g.doFunc();
	}
	public static void main(String[] args) {
		FinalTest ft = new FinalTest();
		ft.with(new Gzino());
		ft.without(new Gzino());
	}
}

    3.final方法:使用final方法的原因有两个,第一个是把它锁定,以防任何继承它的类修改这个方法。第二个原因是提高效率,如果一个方法被声明为final方法,那么编译器会跳过这个方法的常规调用方式(参数入栈,跳到方法的执行部分,然后返回清理栈中参数,最后返回结果),并且以方法体中的实际代码副本代替方法调用,这样减小了方法调用的开销,但在方法过大的时候却不实用,因此在JDK1.5之后,取消了该种形式的作用,final方法只用来锁定防止其它类修改。

    类中所有的private域都被隐式的注为private,因为不允许其它人修改。还有一种是final类,被final类修饰的方法我们认为这个类不能被其它人修改,也不能被继承,当你想达到这样的目的时,可以使用final类。

九、初始化及类加载

    在许多语言中,程序在启动的过程中发生了加载,然后是初始化,最后是程序运行。Java不同,Java采用不同的加载机制, 每个类的编译文件都在它自己的代码文件中,该文件只有在用到该程序的时候才被加载。所以一般可以说“类的代码在初次使用时才发生加载“,这通常是指加载发生在创建第一个类对象时,但是当static域被访问时,也会发生加载。

    下面通过一个示例看一下带继承的初始化:
package com.chenxyt.java.practice;
class Insect{
	private int i = 9;
	protected int j;
	public Insect() {
		System.out.println("i=" + i + "--j=" + j);
		j=39;
	}
	private static int x1 = printInit("static Insect.x1 initialized");
	static int printInit(String s){
		System.out.println(s);
		return 53;
	}
}
public class LoadTest extends Insect{
	private int k = printInit("LoadTest.k initialized");
	public LoadTest(){
		System.out.println("k= " + k);
		System.out.println("j= " + j);
	}
	private static int x2 = printInit("static LoadTest.x2 initialized");
	public static void main(String[] args) {
		System.out.println("LoadTest constructor");
		LoadTest lt = new LoadTest();
	}
}

运行结果:

    
    分析上面的程序,程序在启动的时候先查找main函数,在多数语言中,main函数都是程序的入口,发现main方法在LoadTest类中,这时开始加载LoadTest类,在加载的过程中,发现这个类有基类,于是他继续加载基类,如果还有基类那么继续加载另一个基类,因为子类中可能会用到基类的内容。然后基类中的static域初始化,这时第一行打印。然后子类中的static域初始化,这时第二行执行。这时必要的类资源已经加载完毕,可以进行对象创建,第三行打印。初始化对象,首先对象中的所有基本类型被赋初始值0,对象被赋值为null,这是通过将对象内存设置为二进制零实现的。然后基类的构造函数被调用,这里是自动调用的,也可以通过super方法直接调用,这时第四行被打印。基类构造器和子类构造器执行相同的方式经过相同的过程。基类构造器执行完毕之后,实例变量按顺序被创建,最后执行子类的构造方法,所以最后两行被打印。

十、总结

    继承和组合都能从现有类中得到新的类型,区别在于组合一般是将现有类型作为底层实现的一部分使用,而继承复用的是接口。本章的重点内容是熟悉final关键字的用法以及类加载和初始化的过程,最后一点尤为重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值