【java筑基】浅谈代码复用技术——继承、组合

Java
324
0
0
2022-12-08

一、继承1.1 继承的特点 子类可以获得父类的全部成员变量和方法称为继承,注意子类不能够继承父类的构造器。Java具有单继承的特点,一个类只能够有一个直接父类。所有类都继承了java.lang.Object类。 1.2 重写 子类可以对父类方法进行重写,重写必须遵守两小两同一大,即子类方法返回值的类型要比父类方法返回值类型要更小或者相同(儿子排场要小),子类方法的异常类型要比父类方法的异常类型要更小或者相同(儿子犯的错误要更少),方法名要相同,形参列表要相同,访问权限要比父类访问权限更大或者相同(儿子要搞出点名堂,让别人看见)。特别注意的是子类与父类重写的方法不能一个是属于类,一个是属于对象的。

如果父类定义了一个private修饰的方法,子类无法访问该方法,不可以对该方法进行重写。此时,即使子类中有一个与父类private具体相同名字,相同形参列表,相同返回值类型的方法,依然不是重写。 1.3 super限定 可以在子类对象中使用super限定该对象调用父类中被子类覆盖的方法、成员变量。和this关键字一样。super关键字也不能出现在静态方法中。

其实,子类定义的同名实例变量并不会真的覆盖掉父类实例变量,只是简单的被隐藏了。当创建一个类的实例对象时,依然会为该对象父类中的实例变量分配内存空间,即使他被隐藏了。

class Parent {
	public String tag ="疯狂java讲义";
}

class Child extends Parent {
	private String tag = "轻量级java EE企业应用实战";
}

public class HideTest {
	public static void main(String[] args) {
		Child a = new Child();
		// 该语句不能通过编译,因为Child中的tag标签是私有的
		// System.out.println(a.tag);
		//输出疯狂java讲义
		System.out.println(((Parent) a).tag);
	}

}

上面的代码第13行之所以不能通过编译,是因为访问哪个实例变量是由声明该变量的类型决定,a声明的是Child类型,故访问Child类中的tag标签。进行强转后,则可以访问父类中被隐藏的tag。创建Child对象后内存如下图。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nxk564gt-1657196387175)(https://uploadfiles.nowcoder.com/images/20210315/408218111_1615820134290/DECF015354E84983C28DB8828842FF44 “图片标题”)] 虽然子类不会获得父类的构造器,但是仍然可以通过super关键字调用父类构造器的代码。由于this调用本类被重载的构造器和super调用父类构造器的代码都必须放在发出调用的构造器的第一行,它们不能同时出现。不管是否显示调用父类的构造器,实际上执行子类构造器前都会先调用父类构造器。

class Creature {
	public Creature() {
		System.out.println("Creature无参构造器。");
	}
}

class Animal extends Creature {
	public Animal(String name) {
		System.out.println("Animal的一个参数构造器" + "Animal名字为" + name);
	}

	public Animal(String name, int age) {
		this(name);
		System.out.println("Animal的两个参数的构造器" + "其年龄为" + age);
	}
}

public class Wolf extends Animal {
	public Wolf(String name, int age) {
		super(name, age);
		System.out.println("Wolf的一个参数构造器");
	}

	public static void main(String[] args) {
		//输出:Creature无参构造器。  
        //Animal的一个参数构造器Animal名字为灰太狼  
        //Animal的两个参数的构造器其年龄为18  
        //Wolf的一个参数构造器
		new Wolf("灰太狼", 18);
	}
}

二、继承的缺陷 继承和组合都可以实现复用,但是继承带来一个坏处,破坏封装。如果权限允许,子类不仅可以访问父类的成员变量、方法,还可以改变它的实现细节(重写),父类可能被子类恶意篡改。因此为了使父类保持良好的封装,我们应遵循以下规则。

1.父类的成员变量尽量设置为private类型。

2.父类的辅助其它方法的工具方法尽量设置为private,需要被别的类访问但是不希望被重写的方法添加final关键字。只希望被子类访问的方法设置为protected。

3.尽量不要在父类的构造方法中调用可能被子类改写的类。

class Base {
	public Base() {
		test();
	}

	// 一号test方法
	public void test() {
		System.out.println("父类的Test");
	}
}

public class Sub extends Base {
	private String name;

	// 二号Test方法
	public void test() {
		System.out.println("子类重写的test" + name.length());
	}

	public static void main(String[] args) {
		// 下列语句将引发空指针异常
		Sub s = new Sub();
	}
}

上面代码第22行创建Sub类型的对象,需要先执行父类Base的构造方法,而父类中的构造方法调用了已经被重写的test方法,语句"test();“其实已经省略了this关键字,完整的写法是"this.test();”,而this指代调用方法的对象,故我们调用的是二号test方法,此时sub的成员变量是null,执行name.length()出现空指针异常。

如果希望某一个类不被继承,可以用final关键字进行修饰,比如java.lang.System类和java.lang.String类,我们也可以把构造器设置为private类型,另外提供一个静态方法来创建实例。

什么时候需要继承?

1.子类需要额外增加特有属性。比如Person类可以派生出Student类,因为Student有需要额外增加的属性,比如grade.但是Animal没有必要因为体型不一样而派生出BigAnimal和SmallAnimal,只需要在Aniaml类中增加一个成员变量size。

2.子类需要额外增加特有方法。

三、组合 如果仅仅是需要实现复用,我们也可以使用组合。继承子类可以直接获得父类的方法,而组合是把旧类对象作为新类的一个变量组合进来,通常在新类中用private修饰这个旧类对象。

选择继承还是组合呢?

继承表达的是一个is a的关系,是从一个已有类别中做一番改造,生成一个独特的版本,扩展出一个新的类,比如Animal和Wolf就适合选用继承。组合表达的是一种has a的关系,比如Person类需要复用Arm类就适合用组合。