【Java】Java进阶学习笔记(二)—— 面向对象(继承)
一、继承的概念
继承是 java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
生活中的继承:
兔子和羊属于食草动物类,狮子和豹属于食肉动物类。
食草动物和食肉动物又是属于动物类。
所以继承需要符合的关系是:is-a
,父类更通用,子类更具体。
虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
格式:
class 父类 {
}
子类 extends 父类 {
}
继承的特点
-
继承是子类到父类的一种关系;
-
子类继承了一个父类,**子类就可以直接得到父类的属性(成员变量)和行为(方法)**了;
-
在java中继承是 “is a” 的关系。Lion extends Animal : 狮子是一个动物;
-
在java中,子类是更强大的,子类不仅继承了父类的功能,自己还可以定义自己的功能。
-
子类继承父类,子类就得到了父类的属性和行为,但是并非所有父类的属性和行为等子类都可以继承
-
子类不能继承父类的构造器:子类有自己的构造器(没有争议)
二、继承中构造器的影响
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
// SubClass 类继承
class SubClass extends SuperClass{
private int n;
SubClass(){
// 自动调用父类的无参数构造器
System.out.println("SubClass");
}
public SubClass(int n){
super(300); // 调用父类中带有参数的构造器
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
// SubClass2 类继承
class SubClass2 extends SuperClass{
private int n;
SubClass2(){
super(300); // 调用父类中带有参数的构造器
System.out.println("SubClass2");
}
public SubClass2(int n){
// 自动调用父类的无参数构造器
System.out.println("SubClass2(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
System.out.println("------SubClass 类继承------");
SubClass sc1 = new SubClass();
SubClass sc2 = new SubClass(100);
System.out.println("------SubClass2 类继承------");
SubClass2 sc3 = new SubClass2();
SubClass2 sc4 = new SubClass2(200);
}
}
输出结果为:
------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200
继承后构造器特点
子类继承父类,子类的全部构造器默认会先访问父类的无参构造器,再执行子类自己的构造器。
为什么子类的构造器会先调用父类的构造器?
答:1. 子类的构造器的第一行默认有一个super()调用父类的无参构造器,写不写都存在
2.当我们调用子类构造器初始化子类对象数据的时候,必须先调用父类构造器初始化继承自父类的属性和行为(先有爸爸,后有儿子)
三、继承后的成员变量与方法
1、继承后成员变量的访问特点
就近原则:子类有就找子类,子类没有找父类,父类没有就报错!
this
代表了当前对象的引用,可以用于访问当前子类对象的成员变量
super
代表了父类对象的引用,可以用于访问父类中的成员变量
public class TestDemo {
public static void main(String[] args) {
Cat cat = new Cat();
cat.show();
}
}
class Animal{
public String name = "动物名称";
}
class Cat extends Animal {
public String name = "子类名称";
public void show() {
String name = "局部名称";
System.out.println(name); //局部名称
System.out.println(this.name); //子类名称
System.out.println(super.name); //父类名称
}
}
继承后成员方法的访问特点: 子类对象优先使用子类已有的方法。
2、继承后方法重写(override )
java 建议在重写的方法上面加一个@override 注解
方法一旦加了这个@override 注解,那就必须是成功重写父类的方法,否则报错!
方法重写的规范:
- 子类重写方法的名称和形参列表必须与父类被重写方法一样。
- 子类重写方法的返回值类型声明要么与父亲一样,要么比父类方法返回值类型范围更小。
- 子类重写方法的修饰符权限应该与父类被重写方法的修饰符权限相同或者范围更大。
- 子类重写方法申明抛出的异常应该与父类被重写方法申明抛出的异常一样或者范围更小。
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}
以上实例编译运行结果如下:
动物可以移动
狗可以跑和走
总结与拓展
小结:
- 方法重写是子类重写一个与父类申明一样的方法覆盖父类的方法;
- 方法重写要加@override 注解;
- 方法重写的核心要求:方法名称,参数列表必须与被重写方法一致!
扩展:
静态方法和私有方法是否可以被重写?
答: 静态方法和私有方法都不可以被重写,加上@override会报错。
3、 this 和 super
this代表了当前对象的引用(继承中指代子类对象)
this.子类成员变量
this.子类成员方法
this(....):可以根据参数匹配访问本类中其他构造器
super代表了父类对象的引用(继承中指代了父类对象空间)
super.父类成员变量
super.父类的成员方法
super(....):可以根据参数匹配访问父类的构造器
注意:
this(…)借用本类其他构造器;super(…)调用父类的构造器。 this(…)和super(…)必须放在构造器的第一行,否则报错,this(…)和super(…)不能同时出现在构造器中!!
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal b = new Dog(); // Dog 对象
b.move(); //执行 Dog类的方法
}
}
以上实例编译运行结果如下:
动物可以移动
狗可以跑和走
4、重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
public class Overloading {
public int test(){
System.out.println("test1");
return 1;
}
public void test(int a){
System.out.println("test2");
}
//以下两个参数类型顺序不同
public String test(int a,String s){
System.out.println("test3");
return "returntest3";
}
public String test(String s,int a){
System.out.println("test4");
return "returntest4";
}
public static void main(String[] args){
Overloading o = new Overloading();
System.out.println(o.test());
o.test(1);
System.out.println(o.test(1,"test3"));
System.out.println(o.test("test4",1));
}
}
- 重写与重载之间的区别
四、单继承与多层继承
单继承: 一个类只能继承一个直接父类
多层继承:一个类可以间接继承多个父类(C继承B,B继承A)
一个类可以有多个子类(C继承B,D也继承B,B就有了C,D两个子类)
一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类
五、面试常考
2.1八种访问形式问答
实例方法是否可以直接访问实例成员变量?
答:可以的,因为它们都属于对象
实例方法是否可以直接访问静态成员变量?
答:可以的,静态成员变量可以被共享访问
实例方法是否可以直接访问实例方法?
答:可以的,实例方法和实例方法都属于对象
实例方法是否可以直接访问静态方法?
答:可以的,静态方法可以被共享访问
静态方法是否可以直接访问实例变量?
答:不可以,静态方法属于类,实例变量属于对象,实例变量必须用对象访问
静态方法是否可以直接访问静态变量?
答:可以的,静态成员变量可以被共享访问
静态方法是否可以直接访问实例方法?
答:不可以,实例方法必须被对象访问
静态方法是否可以直接访问静态方法?
答:可以的,静态方法可以被共享访问
2.2继承
子类中不能继承父类东西是什么?
答:子类不能继承父类的构造器,子类有自己的构造器
子类是否可以继承父类的私有成员(私有成员变量,私有成员方法)?
答:有争议,我认为子类是可以继承父类的私有成员的,只是不能直接访问而已
子类上是否可以继承父类的静态成员?
答:子类可以继承父类的静态成员。
2.3重写
静态方法和私有方法是否可以被重写?
答:不可以,加上@Override会报错
2.4继承后构造器特点
为什么子类的构造器会调用父类的构造器
答:子类的构造器第一行默认有super()调用父类的无参构造器,写不写都存在
2.5单继承
为什么Java是单继承的?
答: 反证法
// 假如Java可以多继承,请看如下代码
class A{
// 成员方法
public void test(){
System.out.println("A");
}
}
class B{
// 成员方法
public void test(){
System.out.println("B");
}
}
class C extends A,B{
public static void main(String[] args){
C c = new C();
c.test(); //出现了类的二义性!所以Java不能多继承!!
}
}