类的继承

本文详细介绍了Java中类的继承概念、使用场景及其关键要素,包括如何使用super关键字访问父类成员,方法重写的原则等内容。

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

类的继承



1、什么是类的继承



类的继承是指子类通过继承父类,自动获得父类的可访问成员。用extends关键字实现。一般而言,子类可以自动获得父类的public和protected成员,当子类和父类在同一个包的情况下,还可以访问“包访问权限”成员,而父类的private成员是无法访问的



2、为什么要用类的继承



继承是Java的三大特性之一,是实现软件复用的重要手段,通过继承父类的成员,大大提高了开发效率。不过与此同时缺点也很明显,父类和子类的耦合度过高,有些违背Java的另一大特性:封装。但是它也有着特定的作用,为“多态”服务,具体的讨论会在后续章节展开。



3、类的继承要点分析



类的继承意味着子类需要用到一些父类的既定成员,而作为父类的特殊情况,子类又需要在某些地方做出调整,完成自身需求的实现。由此,它面临着两大问题,对父类成员的访问对父类成员的重写,以下将会从这两个方面进行探究。


示例代码:



父类:

package test.superclasspackage;

public class SuperClass {
	
	/** 父类实例变量 */
	public String FieldA  = "父类实例变量A";
	/** 父类静态变量  */
	public static String FieldB  = "父类静态变量B";
	
	/**
	 * 父类普通方法
	 */
	public Object commonMethod(){
		System.out.println("父类普通方法");
		return "父类普通方法";
	}
	
	/**
	 * 父类静态方法
	 */
	public static void staticMethod(){
		System.out.println("父类静态方法");
	}
}



子类:

package test.subclasspackage;

import test.superclasspackage.SuperClass;

public class SubClass extends SuperClass{
	
	/** 子类重写父类实例变量 */
	public String FieldA  = "子类实例变量A";
	
	/**
	 * 访问父类的成员变量
	 */
	public void readSuperClassFields(){
		//访问子类的实例变量
		System.out.println("访问子类实例变量:"+FieldA);
		//访问父类的实例变量
		System.out.println("访问父类实例变量:"+super.FieldA);
		//访问父类静态变量方法1
		System.out.println("访问父类静态变量方法1:"+super.FieldB);
		//访问父类静态变量方法2
		System.out.println("访问父类静态变量方法2:"+FieldB);
		//访问父类静态变量方法3
		System.out.println("访问父类静态变量方法3:"+SuperClass.FieldB);
		
	}
	
	/**
	 * 访问父类方法
	 */
	public void readSuperClassMethods(){
		super.commonMethod();
		super.staticMethod();
	}
	
	/**
	 *  重写父类的实例方法
	 */
	@Override
	public String commonMethod(){
		System.out.println("子类普通方法");
		return "子类普通方法";
	}
	
	/**
	 *  "重写"父类的静态方法
	 */
//	@Override
	public static void staticMethod(){
		System.out.println("子类静态方法");
	}
	
	
	public static void main(String[] args) {
		//访问父类的实例变量A,此处会报错,静态方法里不能用super
//		System.out.println(super.SuperClassFieldA);
		SubClass subClass = new SubClass();
		System.out.println("访问父类成员变量--------------------");
		subClass.readSuperClassFields();
		System.out.println();
		System.out.println("访问父类方法-----------------------");
		subClass.readSuperClassMethods();
		System.out.println();
		System.out.println("运用多态访问子类静态方法-----------------------");
		SuperClass superClass1 = new SubClass();
		superClass1.staticMethod();
		staticMethod();
		System.out.println();
		System.out.println("运用多态访问子类实例方法-----------------------");
		SuperClass superClass2 = new SubClass();
		superClass2.commonMethod();
	}
}



运行结果:



问题1:super关键字的使用


super是Java提供的关键字,它是对父类对象的引用。通过super.变量名super.方法名可以实现对父类成员变量和成员方法的访问。当父类的成员变量和成员方法没有被重写时,super关键字可以不加,不影响使用,可为了程序的可读性考虑,建议从父类继承过来的属性和方法都加上super限定。

注意:

1. super是针对对象的,所以super不能出现在静态方法中,this关键字也是

2. 既然super是针对对象的,所以一般用super来获得父类的实例成员,然而用super去获得父类的静态成员语法上也不会报错,不过还是推荐用静态的方式去访问静态成员


问题2:方法的重写


(1)方法重写基于一个基本原则:两同两小一大

两同:子类方法和父类方法的方法名字,形参列表相同

两小:子类方法的返回值类型及抛出异常比父类小(这个小的意思是返回值类型与父类返回值类型相等或是其子类)

一大:子类方法的访问权限比父类的大


解析:为什么会有这样一个原则?两同很好理解,那两小一大呢?我的理解,子类重写父类的方法是为了全部替换父类方法出现的地方,如果子类的访问权限小于父类,则必定有一个范围是子类涵盖不了的。两小的话从多态方面考虑,SuperClass c = new SubClass(),编译类型是SuperClass,我们程序可能会用到SuperClass中一个方法的返回值,而此方法被SubClass重写了。而且运行时类型实际上是SubClass,如果此时子类实际的返回值类型比父类大的话,可能在运行时出现编译期无法预知的问题,从而使程序崩溃,异常的话同理。


(2)静态方法的"重写"


仔细观察代码中分别对父类的实例方法和静态方法进行重写后的效果,很明显,父类的实例方法被重写成功了,它执行的是被重写后的方法。而静态方法分别用了多态和直接调用两种方式去尝试对重写后静态方法的调用,结果是多态方式调用失败,直接调用成功。为什么呢?


实际上父类的静态方法是伪“”重写“”,实际是覆盖,不存在多态。仔细观察代码,我在子类重写父类静态方法上面有个@Override被注释掉了,如果放开的话会报错,所以是不允许被重写的。为什么说是覆盖,因为在代码中直接调用,调用的是重写后的方法,说明父类的静态方法被覆盖了。至于为什么多态方式调用失败也很好理解,因为多态是动态绑定,根据对象实际运行时候的类型去决定调用哪个方法,而静态方法则是在编译期就已经确定下来的了,在编译期对象的类型是SuperClass,所以运行时候去调用的方法也应该是SuperClass中的静态方法。



对了最后记录一下,实例方法中是直接可以调用静态方法的,而静态方法不能调实例方法。以前一直以为实例调实例,静态调静态,汗!!!

为什么会这样,我想也是和类的初始化有关,静态方法在实例方法之前,静态方法初始化的时候,实例方法并没有初始化,如果调用被崩掉。所以实例方法和静态方法的调用是单向的。



### 接口的功能和用途 #### 1. **抽象功能定义** 接口提供了一种标准化的方式来定义一组方法或行为,而不涉及具体的实现细节。这种特性允许开发者专注于“做什么”,而不是“怎么做”。通过这种方式,接口成为不同模块之间通信的桥梁[^4]。 例如,在面向对象编程中,接口可以用来指定某些必须具备哪些方法以及这些方法应该如何签名(参数列表、返回值型等),从而确保遵循统一的标准。 --- #### 2. **多态性支持** 利用接口可以实现运行时多态——即相同的接口引用可以根据实际绑定的对象表现出不同的行为。这是软件开发灵活性的重要体现之一[^4]。 以下是基于接口实现多态的一个简单例子: ```java interface IAbility { void eat(); } class Cat implements IAbility { @Override public void eat() { System.out.println("Cat eats fish."); } } class Child implements IAbility { @Override public void eat() { System.out.println("Child eats chocolate."); } } public class PersonDemo { public static void main(String[] args) { feed(new Cat()); // 输出: Cat eats fish. feed(new Child()); // 输出: Child eats chocolate. } public static void feed(IAbility ability) { ability.eat(); } } ``` 在这个案例中,`feed` 方法接受任意实现了 `IAbility` 的对象作为参数,并调用其 `eat` 方法,展现了动态分派的能力。 --- #### 3. **解耦合与扩展性增强** 通过将接口置于组件间交互的核心位置,可以在很大程度上降低系统的耦合程度。这样做的好处在于未来即使需要替换或者新增某种具体实现,只要保持对接口契约的遵从就不会影响到其他部分的工作流程[^1]。 设想这样一个场景:当前有一个业务逻辑依赖于三个外部服务 A/B/C 的响应结果来进行决策处理。如果直接硬编码每一步骤间的关联关系,则每次调整策略都需要重新部署整套应用。但如果把这部分复杂的判定规则抽取出来形成单独的服务层并通过配置文件等方式加载进来的话,就能极大简化维护成本并提升适应变化的速度。 --- #### 4. **序列化标记** 虽然像 Serializable 这样特殊的 marker interface 并未包含任何方法声明,但它仍然承担着重要的职责——向虚拟机传达额外的信息。以 Serializable 为例,当某个显式继承自它之后便表明自己愿意参与序列化进程,进而允许自己的实例被转换成字节流形式存储下来或是发送至远程节点上去[^2]。 值得注意的是,默认情况下此操作完全由 JDK 自带框架完成,除非另有特殊需求才需自行定制 Externalizable 版本。 --- #### 5. **硬件设备互联媒介** 除了纯软件领域之外,“接口”这个词也广泛应用于描述物理世界里的各种连接标准及其对应协议栈。比如 USB 是通用串行总线规范的一种表现形态;HDMI 则代表高清晰多媒体传输线路等等[^3]。它们共同构成了现代电子装置相互协作的基础架构体系。 --- ### 总结 综上所述,无论是促进代码重用还是加强系统健壮性方面来看待,合理运用好各接口都是至关重要的环节所在。无论是在高层级的设计蓝图规划阶段思考如何划分界限分明的责任区域,亦或者是深入底层探索数据交换背后的原理机制,都能看到接口无处不在的身影发挥着不可替代的关键角色。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值