这一篇,我们就来学习面向对象的第二大特征-继承(一定要有类的基础,详情请看Java中的类和方法(方法重载)-CSDN博客)。
当我们创建两个类时,发现这两个类都有公共特征时,就可以利用继承,减少相同的代码量。
我们定义两个类,一个狗类一个猫类,他们都有相同的几个属性:姓名、年龄。一个相同的方法:吃饭。
class Dog{
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void bark(){
System.out.println(this.name + "正在汪汪叫");
}
}
class Cat{
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void bark(){
System.out.println(this.name + "正在喵喵叫");
}
}
public class New {
}
继承的关键字是extend,那么接下来我们就将这些共同的属性和方法进行抽取,并让其他的小类继承这个大类:
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
class Dog extends Animal{
public void bark(){
System.out.println(this.name + "正在汪汪叫");
}
}
class Cat extends Animal{
public void bark(){
System.out.println(this.name + "正在喵喵叫");
}
}
public class New {
}
子类会将父类的成员变量或者方法继承到子类去。被private修饰,也是可以被继承的,只是访问还是需要get和set方法,是否还记得那张图?
因为是子类,不支持直接访问,所以还是要进行进行间接访问。
我们来看以下代码:
class Base{
public int a;
public int b;
}
class Der extends Base{
public int c;
public void method(){
a = 1;
b = 2;
c = 3;
}
}
public class Test {
Der d = new Der();
}
此时父类和子类成员变量名不同。我们来看相同的情况。
class Base{
public int a = 20;
public int b = 90;
}
class Der extends Base{
public int a = 1;
public void method(){
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
public class Test {
public static void main(String[] args) {
Der d = new Der();
d.method();
}
}
也就是说,当父类和子类有相同变量成员时,优先看子类有有没有,没有再去父类中去找。所以成员变量和方法的访问遵循就近原则,自己有就限访问自己的,如果没有就去向父类中找。
此时调用方法也是一样的。
class Base{
public void method() {
System.out.println("Base::method()");
}
}
class Der extends Base{
public void method() {
System.out.println("Der::method()");
}
public void method2() {
System.out.println("Der::method()");
}
public void test() {
method();
method2();
}
}
public class Test {
public static void main(String[] args) {
Der d = new Der();
d.test();
}
}
当子类和父类中都有相同的成员和方法时,我们如果去使用方法就会使用子类中的方法,那么此时我们如何去使用父类中的成员或者方法呢? 此时就需要用到super关键字了。
当子类和父类中存在相同的成员变量和方法时,为了更好的区分,我们要使用super关键字。
class Base{
int a = 99;
public void method() {
System.out.println("Base::method()");
}
}
class Der extends Base{
int a = 1;
public void method() {
System.out.println("Der::method()");
}
public void method2() {
System.out.println("Der::method()");
}
public void test() {
System.out.println(super.a);//使用super关键字访问父类成员
super.method();//使用super关键字调用父类方法
method2();
}
}
public class Test {
public static void main(String[] args) {
Der d = new Der();
d.test();
}
}
由此可见,super关键字是对标this的,我们来对比一下:
class Base{
int a = 99;
}
class Der extends Base{
int a = 1;
public void test() {
System.out.println(super.a);//使用super关键字访问父类成员
System.out.println(this.a);
System.out.println(a);
}
}
public class Test {
public static void main(String[] args) {
Der d = new Der();
d.test();
}
}
和this的用法类似,可以使用super.方法。
class Base{
public int a = 99;
public void fun1(){
System.out.println("hehe");
}
}
class Der extends Base{
public void fun2() {
super.fun1();
}
}
public class Test {
public static void main(String[] args) {
Der d = new Der();
d.a = 99;
d.fun2();
}
}
有没有想过一个问题,就是如何将子类构造初始化?那么父类又是如何构造的?此时就需要知道super调用构造方法了。
因为写入了有参构造器,没有提供参数,所以报错。使用无参构造器则不报错。
我们来观察子类继承了父类,父类成员如何初始化。子类在构造完成之前,一定要先帮助父类进行初始化。
所以此时借助super来调用父类构造器。比如此时我们父类是有参构造器:
class Animal{
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name + "正在吃饭");
}
}
class Dog extends Animal{
public Dog() {
super("远远",10);
}
public void bark(){
System.out.println(this.name + "正在汪汪叫");
}
}
在此之前,我们没有写任何构造方法却编译通过了呢?还是一样的,它默认给了一个无参构造器(因为父类中也没有写构造器,父类中默认提供一个无参构造器,子类中默认体格一个无参构造器)。
//Dog类中的默认无参构造器
public Dog() {
super();
//只能有一个且必须在第一行
//调用父类构造方法 帮助初始化 子类从 父类继承过来的成员 并不生成 父类对象
}
所以super有以下用法:
- super.成员变量
- super.成员方法
- super()调用构造方法
this和super对标,也和以上一样。
this针对当前对象;super针对当前对象的父类。
都只能在非静态方法中使用,在构造器中调用时,必须是构造方法中的第一条语句,且不能同时存在。
还是否记得我们上一章节讲解的代码块的内容?那么接下来,我们就结合代码块来讲解在继承关系中是如何执行的。
class Animal{
static {
System.out.println("Animal::static");
}
public Animal() {
System.out.println("Animal::构造");
}
{
System.out.println("Animal::实例");
}
}
class Dog extends Animal{
static {
System.out.println("Dog::static");
}
{
System.out.println("Dog::实例");
}
public Dog() {
super();
System.out.println("Dog::构造");
}
}
public class New {
public static void main(String[] args) {
Dog d = new Dog();
}
}
注意静态代码块最先执行且只执行一次。
看过我之前文章的小伙伴都知道了private和default(包权限修饰符)的用法和性质,通过以上的知识也就知道了父类和子类的关系。那么接下来我们就来具体讲解protected的性质和用法。
我们先创建两个包,一个包是父类,另一个包继承父类(就是子类)。将父类的变量用protected修饰,此时直接访问报错。
依旧报错,此时是因为super和this不能再静态方法中使用,所以我们可以定义一个非静态方法。
所以对于protected修饰的成员或者变量,不同包的子类可以访问。所以protected一般出现在继承当中,继承的父类一定是用public修饰的。
定义类时,只能用public,其余不行,否则报错。
当继承到一定程度时,我们不想再继承时,就要使用final关键字了。
此时发现用final修饰的类不能在被继承,否则报错。所以final修饰的类也称密封类,表示当前类不能在被继承了。
因为java中没有const关键字,但是不代表没有常量,所以final也是常量修饰关键字。
一般我们不希望出现多于3层的继承关系,所以要使用final关键字。
final修饰方法:表示这个方法是密封方法,不能被重写。