目录
1.继承与修饰符
Java修饰符的作用就是对类或类成员进行修饰或限制,每个修饰符都有自己的作用,而在继承中可能有些特殊修饰符使得被修饰的属性或方法不能被继承,或者继承需要一些其他的条件,下面就详细介绍在继承中一些修饰符的作用和特性。
Java语言提供了很多修饰符,修饰符用来定义类、方法或者变量,通常放在语句的最前端。主要分为以下两类:
访问修饰符
非访问修饰符
这里访问修饰符主要讲解public,protected,default,private四种访问控制修饰符。非访问修饰符这里就介绍static修饰符,final修饰符和abstract修饰符。
2. 访问修饰符
public,protected,default(无修饰词),private修饰符是面向对象中非常重要的知识点,而在继承中也需要懂得各种修饰符使用规则。
首先我们都知道不同的关键字作用域不同,四种关键字的作用域如下:
同一个类 | 同一个包 | 不同包子类 | 不同包非子类 | |
---|---|---|---|---|
private | ✅ | |||
default | ✅ | ✅ | ||
protect | ✅ | ✅ | ✅ | |
public | ✅ | ✅ | ✅ | ✅ |
private:Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
default:(也有称friendly)即不加任何访问修饰符,通常称为“默认访问权限“或者“包访问权限”。该模式下,只允许在同一个包中进行访问。
protected:介于public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
public:Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问。
Java 子类重写继承的方法时,不可以降低方法的访问权限,子类继承父类的访问修饰符作用域不能比父类小,也就是更加开放,假如父类是protected修饰的,其子类只能是protected或者public,绝对不能是default(默认的访问范围)或者private。所以在继承中需要重写的方法不能使用private修饰词修饰。
如果还是不太清楚可以看几个小案例就很容易搞懂,写一个A1类中用四种修饰词实现四个方法,用子类A2继承A1,重写A1方法时候你就会发现父类私有方法不能重写,非私有方法重写使用的修饰符作用域不能变小(大于等于)。
正确的案例应该为:
class A1 {
private void doA(){ }
void doB(){}//default
protected void doC(){}
public void doD(){}
}
class A2 extends A1{
@Override
public void doB() { }//继承子类重写的方法访问修饰符权限可扩大
@Override
protected void doC() { }//继承子类重写的方法访问修饰符权限可和父类一致
@Override
public void doD() { }//不可用protected或者default修饰
}
还要注意的是,继承当中子类抛出的异常必须是父类抛出的异常或父类抛出异常的子异常。下面的一个案例四种方法测试可以发现子类方法的异常不可大于父类对应方法抛出异常的范围。
正确的案例应该为:
class B1{
public void doA() throws Exception{}
public void doB() throws Exception{}
public void doC() throws IOException{}
public void doD() throws IOException{}
}
class B2 extends B1{
//异常范围和父类可以一致
@Override
public void doA() throws Exception { }
//异常范围可以比父类更小
@Override
public void doB() throws IOException { }
//异常范围 不可以比父类范围更大
@Override
public void doC() throws IOException { }//不可抛出Exception等比IOException更大的异常
@Override
public void doD() throws IOException { }
}
3. 向上转型
向上转型 : 通过子类对象(小范围)实例化父类对象(大范围),这种属于自动转换。用一张图就能很好地表示向上转型的逻辑:
父类引用变量指向子类对象后,只能使用父类已声明的方法,但方法如果被重写会执行子类的方法,如果方法未被重写那么将执行父类的方法。
4. 向下转型
向下转型 : 通过父类对象(大范围)实例化子类对象(小范围),在书写上父类对象需要加括号
()
强制转换为子类类型。但父类引用变量实际引用必须是子类对象才能成功转型,这里也用一张图就能很好表示向上转型的逻辑:
子类引用变量指向父类引用变量指向的对象后(一个Son()对象),就完成向下转型,就可以调用一些子类特有而父类没有的方法 。
在这里写一个向上转型和向下转型的案例:
Object object=new Integer(666);//向上转型
Integer i=(Integer)object;//向下转型Object->Integer,object的实质还是指向Integer
String str=(String)object;//错误的向下转型,虽然编译器不会报错但是运行会报错
5. 子父类初始化顺序
在Java继承中,父子类初始化先后顺序为:
父类中静态成员变量和静态代码块
子类中静态成员变量和静态代码块
父类中普通成员变量和代码块,父类的构造函数
子类中普通成员变量和代码块,子类的构造函数
总的来说,就是静态>非静态,父类>子类,非构造函数>构造函数。同一类别(例如普通变量和普通代码块)成员变量和代码块执行从前到后,需要注意逻辑。
创建子类对象的时候需要先创建父类对象,所以父类优先于子类。
而在调用构造函数的时候,是对成员变量进行一些初始化操作,所以普通成员变量和代码块优于构造函数执行。
至于更深层次为什么这个顺序,就要更深入了解JVM执行流程啦。下面一个测试代码为:
class Father{
public Father() {
System.out.println(++b1+"父类构造方法");
}//父类构造方法 第四
static int a1=0;//父类static 第一 注意顺序
static {
System.out.println(++a1+"父类static");
}
int b1=a1;//父类成员变量和代码块 第三
{
System.out.println(++b1+"父类代码块");
}
}
class Son extends Father{
public Son() {
System.out.println(++b2+"子类构造方法");
}//子类构造方法 第六
static {//子类static第二步
System.out.println(++a1+"子类static");
}
int b2=b1;//子类成员变量和代码块 第五
{
System.out.println(++b2 + "子类代码块");
}
}
public class test9 {
public static void main(String[] args) {
Son son=new Son();
}
}
执行结果: