文章目录
一、继承方式
通过在类的声明中加入extends
子句来创建一个类的子类。
class Father{
}
class Son extends Father{
}
(其实默认所有的类继承java.lang.Object
类)
二、继承规则
1.默认继承
继承是子类利用父类中定义的成员方法和成员变量(包括实例变量和类变量)就像它们属于子类本身一样。
默认继承:
- 成员变量和成员方法
2.成员变量的继承
- 继承成员变量(实例变量和类变量),只要声明了就可以。
class Animal {
public static String birthday = "unknown";
public int age = 10;
public String name = "Animal";
}
public class Dog extends Animal {
Dog(String name)
{
// 调用父类的成员变量
System.out.println(super.name);
this.name = name;
}
public static void main(String[] args) {
Dog dog = new Dog("Odie");
// 继承父类的成员变量name,修改了新的值
System.out.println(dog.name);
// 继承父类的类变量birthday,值还一样
System.out.println(dog.birthday);
// 继承父类的实例变量age,值还一样
System.out.println(dog.age);
//System.out.println(super.name);
}
}
/*
Animal
Odie
null
10
*/
3.构造函数的继承super(...)
:
- Java在派生类的构造函数(不管有参还是无参)中自动调用基类的默认构造函数,即隐式调用
super()
。当然,你也可以显式调用super()
。 - 当然指定执行
super(arg)
后就不会调用super()
。 - 同构造函数重写
this(...)
一样- 只能在构造器中调用构造器,不能在非构造方法中调用
- 调用时,
super(...)
必须放在第一位 - 同一个构造器中不能调用两次构造器
(1)子类和父类构造函数隐式调用的例子
// 子类和父类都使用默认的构造函数,是可以的。
class Art {
}
class Drawing extends Art {
public static void main(String[] args) {
Drawing drawing = new Drawing();
}
}
// 父类自定义无参构造函数,子类使用默认的构造函数
class Art {
Art() {
System.out.println("Art constructor()");
}
}
class Drawing extends Art {
public static void main(String[] args) {
Drawing drawing = new Drawing();
}
}
/*
Art constructor()
*/
// 父类使用默认的构造函数,子类自定义无参构造函数
class Art {
}
class Drawing extends Art {
Drawing() {
System.out.println("Drawing constructor()");
}
public static void main(String[] args) {
Drawing drawing = new Drawing();
}
}
/*
Drawing constructor()
*/
// 父类子类都自定义无参构造函数
class Art {
Art() {
System.out.println("Art constructor()");
}
}
class Drawing extends Art {
Drawing() {
System.out.println("Drawing constructor()");
}
Drawing(String arg){
System.out.println("Drawing constructor(" + "arg" + ")");
}
public static void main(String[] args) {
Drawing drawing1 = new Drawing();
Drawing drawing2 = new Drawing("arg");
}
}
/*
Art constructor()
Drawing constructor()
Art constructor()
Drawing constructor(arg)
*/
(2)显示调用的例子
// 指定执行super(arg)后就不会调用super()
class Art {
Art() {
System.out.println("Art constructor()");
}
Art(String arg){
System.out.println("Art constructor(" + "arg" + ")");
}
}
class Drawing extends Art {
Drawing() {
super("arg");
System.out.println("Drawing constructor()");
}
Drawing(String arg){
super(arg);
System.out.println("Drawing constructor(" + "arg" + ")");
}
public static void main(String[] args) {
Drawing drawing1 = new Drawing();
Drawing drawing2 = new Drawing("arg");
}
}
/*
Art constructor(arg)
Drawing constructor()
Art constructor(arg)
Drawing constructor(arg)
*/
(3)构造出错
原因:父类自定义有参构造函数,就不会生成默认的无参构造函数,且未显示定义无参构造函数,而导致子类的构造器隐式调用出错。
报错信息:Implicit super constructor Animal() is undefined for default constructor. Must define an explicit constructor
// 错误示例
class Animal {
public String name;
// 不会自动生成Animal()了
public Animal(String name)
{
this.name = name;
}
}
public class Dog extends Animal {
// 这里的Dog()哪怕不显示写,也会报错
// 因为不写会生成默认的无参构造函数,而这个无参构造函数也会隐式调用super()
Dog(){}
public static void main(String[] args) {
Dog dog = new Dog();
}
}
解决方案:
- 在父类显式写出了一个无参构造函数:这样
super()
就没问题了。 - 在子类中显示指定调用父类的有参构造函数:这样就不会调用没有构造的
Animal()
- 删除父类的有参构造函数也可以是一个办法。
// 解决方案1:在父类显式写出了一个无参构造函数
class Animal {
public String name;
// 显式构造无参构造函数
Animal(){}
public Animal(String name)
{
this.name = name;
}
}
public class Dog extends Animal {
Dog(){}
public static void main(String[] args) {
Dog dog = new Dog();
}
}
// 解决方案2:在子类中显示指定调用父类的有参构造函数
class Animal {
public String name;
public Animal(String name)
{
this.name = name;
}
}
public class Dog extends Animal {
// 显式调用super(arg)
// 这里只是例子,一般是子类有参构造函数调用父类对应的有参构造函数。
Dog(){
super("something");
}
public static void main(String[] args) {
Dog dog = new Dog();
}
}
// 解决方案3:删除父类的有参构造函数
class Animal {
public String name;
}
public class Dog extends Animal {
// 删除后调用默认的无参构造函数
Dog(){}
public static void main(String[] args) {
Dog dog = new Dog();
}
}
4.实例方法的覆盖/重写(override)
实例方法和静态方法都可以被继承。
class Animal {
public void bark()
{
System.out.println("~~~");
}
public static void drink()
{
System.out.println("animal drink");
}
}
public class Dog extends Animal {
public static void main(String[] args) {
Dog dog = new Dog();
dog.bark();
dog.drink();
}
}
/*
~~~
animal drink
*/
(1)重写override
意思:在子类中重新定义父类中已有的方法。
规则:
- 子类覆盖的方法同父类的方法要保持名称、返回值类型、参数列表的统一。
- 改写后的方法不能比被覆盖的方法有更严格的访问权限
- 改写后的方法不能比被覆盖的方法产生更多的异常
写法:
super.func(...)
:调用父类的成员方法@Override
:重写方法的注记符,方便标出重写方法,没有也不报错。
class Animal {
public void sleep()
{
System.out.println("zzz");
}
public void eat()
{
System.out.println("food");
}
public void bark()
{
System.out.println("~~~");
}
}
public class Dog extends Animal {
// 重写eat()
@Override
public void eat() {
System.out.println("eat bone");
}
// 重写bark(),并调用父类的bark()
@Override
public void bark() {
super.bark();
System.out.println("woo!");
}
// 子类新增的方法
public void play(){
System.out.println("play!");
}
public static void main(String[] args) {
// 注意构造,这里是用子类的构造函数构造
Dog dog = new Dog();
// 直接调用父类继承下来的sleep()
dog.sleep();
dog.eat();
dog.bark();
dog.play();
}
}
/*
zzz
eat bone
~~~
woo!
play!
*/
(2)继承与重载(overload)、重写(override)
-
重载
在父类中重载的方法会很好地被子类继承下来,能和在子类中新重载的的同名方法一样被使用。 -
重写
可以重写父类中的方法(当然不可以重写子类中的方法,重写只能写继承来的方法)
class Animal {
// 父类重载1
void bark(int i)
{
System.out.println("Animal-bark(int)");
}
// 父类重载2
void bark(char c)
{
System.out.println("Animal-bark(char)");
}
}
public class Dog extends Animal {
// 子类重载3:与父类不同参数列表的bark
void bark(String s)
{
System.out.println("Animal-bark(String)");
}
// 子类重写:重写bark(char c)
@Override
void bark(char c)
{
System.out.println("Dog-bark(char)");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.bark(0);
dog.bark('a');
dog.bark("S");
}
}
/*
Animal-bark(int)
Dog-bark(char)
Animal-bark(String)
*/
三、继承与访问控制
- 子类可以继承父类中访问权限设定为
public
、protected
、"default"
的成员变量和方法。 - 但是不能继承访问权限为
private
的成员变量和方法。
四、向上造型和向下造型
1.理解
意思:
- 类比基本数据类型
int i = 100; long l = i;
100用int
类型表示就够了,用long
表示更够了,表示的范围扩宽了(向上造型)。
long l = 100; int i = (int)l;
缩短了表示范围(向下造型)。 - 继承和范围
重写方法时就可以看出父类表示的“范围”比子类更大。
改写后的方法不能比被覆盖的方法有更严格的访问权限,改写后的方法不能比被覆盖的方法产生更多的异常。 - 所以类:
- 向上造型:扩大范围
子类实例→父类类型(父类实例 = 子类实例
),是自动转换。
如Father father = new Son();
- 向下转型:缩小范围
父类实例→子类类型(子类实例 = (子类)父类实例
),是强制转换。
如Son son = (Son)father;
- 向上造型:扩大范围
2.功能
(1)向上造型
- 功能:主要是用来调用子类重写的方法和父类特有的方法。
- 注意:
- 调用同名变量,还是父类的成员变量值。(只跟实例的类型有关)。
- 向上转型时,父类只能调用父类方法或者子类覆写后的方法,而子类中的单独方法则是无法调用的。当然在初始化时的构造函数可以调用。
class Animal {
public String name = "Animal";
public void bark() {
System.out.println("Animal:???");
}
}
public class Dog extends Animal {
public String name = "Dog";
public void bark() {
System.out.println("Dog:woo!");
}
public static void main(String[] args) {
// 向上造型
Animal animal= new Dog();
// 成员变量为父类自身
System.out.println(animal.name);
// 调用子类重写的方法
animal.bark();
}
}
/*
Animal
Dog:woo!
*/
易错点:容易误认为重写
class Animal {
public void printSelf(Animal animal) {
System.out.println("Animal itself");
}
}
public class Dog extends Animal {
public void printSelf(Dog dog) {
System.out.println("Dog itself");
}
public static void main(String[] args) {
// 向上造型
Animal animal= new Dog();
/**
* 无论你写什么,就是不调用子类的方法
* 因为这根本就不算是重写Overriding,注意参数不一致
*/
Dog dog = new Dog();
Dog downDog = (Dog)animal;
animal.printSelf(animal);
animal.printSelf(dog);
animal.printSelf((Dog)animal);
animal.printSelf((Dog)dog);
animal.printSelf((Animal)dog);
animal.printSelf(downDog);
}
}
/*
Animal itself
Animal itself
Animal itself
Animal itself
Animal itself
Animal itself
*/
(2)向下造型
- 功能:主要是用来调用子类特有的方法和子类重写的方法。
- 注意:
- 向下造型只能转化由向上造型转化的实例
- 调用的是子类的成员变量(只跟实例的类型有关)
- 可以调用子类的特有的方法和重写的方法。
class Animal {
public String name = "Animal";
public void bark() {
System.out.println("Animal:???");
}
}
public class Dog extends Animal {
public String name = "Dog";
public void bark() {
System.out.println("Dog:woo!");
}
public void eatBone() {
System.out.println("eating bones!");
}
public static void main(String[] args) {
// 向上造型
Animal animal= new Dog();
// 向下造型
Dog dog = (Dog)(animal);
// 调用的是子类的成员变量
System.out.println(dog.name);
// 调用子类的方法
dog.eatBone();
}
}
/*
Dog
eating bones!
*/
由非向上造型而转化的向下造型:虽然编译器不会提示,但是编译不会通过。(ClassCastException: Animal cannot be cast to Dog
)
(3)如何访问成员变量
父类的成员变量
用向上造型,直接访问即可
子类的成员变量
- 方式1:重写方法
getXXX()
可以通过重写方法getXXX()
得到子类的成员变量值(所以不是白写get的啊) - 方式2:向下造型
(更简单)如果不想重写get方法,也可以通过向下造型访问子类的成员变量。
class Animal {
public String name = "Animal";
public String getName() {
return name;
}
}
public class Dog extends Animal {
public String name = "Dog";
@Override
public String getName() {
return name;
}
public static void main(String[] args) {
// 方式1:重写getName()
Animal animal= new Dog();
System.out.println(animal.getName());
// 方式2:向下造型
Dog dog = (Dog)animal;
System.out.println(dog.name);
}
}
/*
Dog
Dog
*/
3.用途举例
常用在泛型集合(声明为父类对象),集合内元素是子类的实例。这样就可以集合内存储的就是专用的重写方法。
import java.util.ArrayList;
class Animal {
public String name = "Animal";
public void bark() {
System.out.println("Animal:???");
}
}
class Dog extends Animal {
public String name = "Dog";
public void bark() {
System.out.println("Dog:woo!");
}
}
class Cat extends Animal {
public String name = "Cat";
public void bark() {
System.out.println("Cat:miao~!");
}
}
public class Zoo {
public static void main(String[] args) {
// 泛型集合,元素类型声明为Animal类
ArrayList<Animal> zoo = new ArrayList<Animal>();
// 添加一个Dog类实例
zoo.add(new Dog());
// 添加一个Cat类实例
zoo.add(new Cat());
// 让狗叫
zoo.get(0).bark();
// 让猫叫
zoo.get(1).bark();
}
}
/*
Dog:woo!
Cat:miao~!
*/
五、final关键字
六、继承的构造顺序
- 先执行父类的初始化的静态变量,再子类的初始化的静态变量
PS:
类内的main:先执行初始化的静态变量,再执行main内的语句
类外的main:先执行main内的语句,再执行初始化的静态变量 - 执行真正赋值成员变量(不管在构造函数前还是后)
- 执行构造函数(先父类再子类)
/*类内的main*/
class Insect {
int i = 9;
int j;
Insect() {
print("i = " + i + ", j = " + j);
j = 39;
}
static int x1 = print("static Insect.x1 initialized");
static int print(String s) {
System.out.println(s);
return 47;
}
}
class Beetle extends Insect {
private int k = print("Beetle.k initialized");
public Beetle() {
System.out.println("Beetle constructor");
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 = print("static Beetle.x2 initialized");
static int print(String s) {
System.out.println(s);
return 47;
}
public static void main(String[] args) {
System.out.println("main begin...");
Beetle b = new Beetle();
System.out.println("main end...");
}
}
/*
static Insect.x1 initialized
static Beetle.x2 initialized
main begin...
i = 9, j = 0
Beetle.k initialized
Beetle constructor
k = 47
j = 39
main end...
*/
/*类外的main*/
class Insect {
int i = 9;
int j;
Insect() {
print("i = " + i + ", j = " + j);
j = 39;
}
static int x1 = print("static Insect.x1 initialized");
static int print(String s) {
System.out.println(s);
return 47;
}
}
class Beetle extends Insect {
private int k = print("Beetle.k initialized");
public Beetle() {
System.out.println("Beetle constructor");
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 = print("static Beetle.x2 initialized");
static int print(String s) {
System.out.println(s);
return 47;
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("main begin...");
Beetle b = new Beetle();
System.out.println("main end...");
}
}
/*
main begin...
static Insect.x1 initialized
static Beetle.x2 initialized
i = 9, j = 0
Beetle.k initialized
Beetle constructor
k = 47
j = 39
main end...
*/
Reference:
Java 向上转型与向下转型