1、父类型引用指向子类型对象,只能调用父类有的方法,不能调用子类独有的方法。输出结果取决于子类实例到底什么样。
<span style="font-family:Microsoft YaHei;">package com.cry.practice;
import static com.cry.utils.Print.*;
class A {
void f1() {
println("a");
}
}
public class B extends A {
void f1() {
println("b");
}
void f2() {
println("bbbb");
}
public static void main(String[] args) {
A a = new B();
a.f1();
//! a.f2(); 这是错误的!A类型引用根本无法调用f2()
}
/*
Output:
b
*///:~
}
</span>
2、关于跨包继承,是否能重写,是否能调用的实验:
项目目录:
A.java:
<span style="font-family:Microsoft YaHei;">package com.cry.another;
import static com.cry.utils.Print.println;
public class A {
protected void f1(){
println("a");
}
}
</span>
B.java:
<span style="font-family:Microsoft YaHei;">package com.cry.practice;
import com.cry.another.A;
import static com.cry.utils.Print.*;
public class B extends A {
// 重写父类的方法总是可行的,不管父类中该方法是什么访问权限。
// 但要保证子类中该方法的访问权限大于等于父类
protected void f1() {
println("b");
}
public void f2() {
f1(); //这样调用正确
//! A a = new A();
//! a.f1(); 错误,你不能调用另一个A类对象的f1(),因为f1()是protected的。
}
public static void main(String[] args) {
B b = new B();
b.f2();
}
/*
Output:
b
*///:~
}
</span>
3、只要是用final修饰的数据,初始化之后就再也不能更改,不管是在什么地方,什么访问权限。只要是final修饰的变量在声明时就必须明确的初始化。
<span style="font-family:Microsoft YaHei;">class A {
// final int v; //ERROR!
private final int v1 = 1;
protected final int v2 = 1;
public final int v3 = 1;
final int v4 = 1;
public void f() {
// v1 = 2; //ERROR!
// v2 = 2; //ERROR!
// v3 = 2; //ERROR!
// v4 = 2; //ERROR!
}</span>
4、父类的private成员,子类不能直接访问,但是如果父类有public修饰的getter/setter,子类则可以直接使用。比如下面的例子,可以说明,一个子类实例中确实包含一个名为val的int型数据实体,一个子类实例实际上包含一个父类实例在其中,但是父类的private修饰部分,子类却无法直接访问。面向对象的程序设计中,我们应当持这样的态度:“我们除了把private部分看成该类内部的实现结构之外,其他任何时候都不必考虑另一个类的private”。(注:JAVA的@Override注解似乎对这一问题有奇妙作用,日后研究)。或者可以说,private将类与类之间完全隔离开,哪怕它们是父子类。
<span style="font-family:Microsoft YaHei;">package com.cry.practice;
import static com.cry.utils.Print.println;
class A {
private int val;
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
}
public class B extends A {
// val = 5; //ERROR!
void f() {
// val = 5; //ERROR!
setVal(1);
}
public static void main(String[] args) {
B b = new B();
b.f();
println(b.getVal());
b.setVal(5);
println(b.getVal());
}
}
/*
Output:
1
5
*///:~</span>
5、final方法,子类根本不能重写,但子类可以调用。也就是说子类仍具有该final方法,只不过该方法的含义截止在父类那“一代”,子嗣无法修改。这满足了设计中这样的需求:锁定一个方法,以防止任何继承类修改它的含义,确保了该方法在继承中行为不变且不被覆盖。
<span style="font-family:Microsoft YaHei;">package com.cry.practice;
import static com.cry.utils.Print.println;
class A {
public final void f(){
println("AAA");
}
}
public class B extends A{
// public void f(){} //ERROR!编译器直接报错,final方法不能被子类重写
public static void main(String[] args) {
B b = new B();
b.f(); //但是子类可以使用这个final方法,这是没问题的。
}
}
/*
Output:
AAA
*///:~</span>
6、private修饰的东西别的类就是不能访问到,子类也不能访问到,“同名重写覆盖”它,其实编译器只是认为子类有了一个新方法。
package com.cry.practice;
import static com.cry.utils.Print.*;
class Son extends Father{
public void f(){
println("son");
}
}
public class Father {
private void f(){
println("father");
}
public static void main(String[] args) {
Father o1 = new Father();
Father o2 = new Son();
Son o3 = new Son();
o1.f();
o2.f();
o3.f();
}
/*
Output:
father
father
son
*///:~
}
7、关于抽象类的一个基本小例子, 注意其数据访问权限和初始化顺序。
记住三不相容:
1、abstract不能与private同用,因为private不可被子类访问,也不可被重写(子类可以同名覆盖,但那相当于子类自己的新方法),而abstract是需要被重写的抽象方法。
2、abstract不能与final同用,因为final修饰的不能被重写,而abstract是需要被重写的抽象方法。
3、abstract不能与static同用,static修饰的方法归该类所有而非某个具体对象,并且也只归该类所有。如果该方法是protected或public,子类可以继承或调用,但不能重写(子类可以同名覆盖,但那相当于子类自己的新方法),而abstract表示期望被子类实现,所以不可同时使用。
2、abstract不能与final同用,因为final修饰的不能被重写,而abstract是需要被重写的抽象方法。
3、abstract不能与static同用,static修饰的方法归该类所有而非某个具体对象,并且也只归该类所有。如果该方法是protected或public,子类可以继承或调用,但不能重写(子类可以同名覆盖,但那相当于子类自己的新方法),而abstract表示期望被子类实现,所以不可同时使用。
package com.cry.practice;
import static com.cry.utils.Print.*;
//如果包含抽象方法,那这个类在声明时就必须用abstract修饰,否则IDE报错。
abstract class Student {
public String type = "student";
public abstract void introduce();
}
public class Tom extends Student{
public static void main(String[] args) {
Tom tom = new Tom();
tom.introduce();
}
//如果一个非抽象类继承了抽象类,那就必须给出抽象方法的定义。
@Override
public void introduce() {
//抽象类也是类,其中已经具体定义的数据成员可以访问,其子类的整个初始化过程也按标准进行。
println("Tom is a " + type);
}
/*
Output:
Tom is a student
*///:~
}