Day11 Java面向对象(下)

九. 继承

  • 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。例如一个学生,他也是人。我们就可以联系到学生类是人类的子类,学生类继承自人类。

  • 继承使用关键字extends,他的意思是扩展。可以非常形象的理解为子类是父类的扩展。继承的语法如下:

    class ClassName1 extends ClassName2
    
  • Java中类只有单继承,没有多继承!(可以类比你和你亲生父母的的关系)

  • 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。

  • 继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示

  • 子类和父类之间,从意义上讲应该具有"is a"的关系.即"学生 is a 人"。

  • 当你新建一个类的时候,无论是否显示的声明,他都直接或间接的继承自Java.lang中的Object类。Object类是Java中所有类的基类。

  • final关键字所修饰的类不能被继承!

  • super:显示的说明该操作是使用父类的,必须是一个类的子类才能使用

    1. super()调用父类的构造方法,必须在子类的构造方法的第一行。
    2. super只能出现在子类的方法中。
    3. superthis不能同时调用构造方法!
    • 其他的一些说明:
      1. 子类在构造时会首先调用父类的构造器。在子类构造器中既可以显示的使用super()调用,也可以不写,会默认调用父类的无参构造器。
      2. 当父类使用了有参构造器,在子类中想要不写super()就必须显示的声明无参构造器。或者也可以在子类中使用super调用父类的有参构造器。
  • 重写:子类重写父类的实例方法

    • 重写的要求:方法与父类一致,只有方法体不同。

      • 不能重写父类的static方法!

      • 返回值,方法名和参数列表必须相同。

      • 修饰符:范围可以缩小,但是不能扩大。

      • 抛出的异常范围可以缩小不能扩大。

    • 为什么需要重写:父类的功能子类可能不需要,或者功能不满足子类的需求。

    • 补充:修饰符的范围大小

      public > protected > default > private
      //default等级就是默认什么修饰符都不加的状态
      
  • 还有一些细节:

    • 父类中的private成员对于子类不可见,子类不能直接操作他们。而其他成员对于子类都可见。
    • 子类会继承父类除了private关键字修饰的所有的属性和方法,但是会保持他们的权限不变。
    • 父类的引用变量可以指向子类的对象,但是子类的引用变量不能指向父类的对象。他们使用起来的区别我们等到多态再去细说。
  • 虽然可以使用父类的引用变量指向子类的对象,但是不能通过父类的引用变量调用子类的独有方法。

  • 通过下面一个实例程序,来更好地理解上面的知识。

package konoha.oop.objectLearn;
//这是一个父类
public class Pet {
    //两个私有属性
    private String name;
    private int age;
    protected String male; //一个受保护的属性

    public Pet(){} //显示声明的空构造方法

    //含参构造方法
    public Pet(String name, int age) {
        this.name = name;
        this.age = age;
    }
	//get/set()
    public String getName() {return name;}

    public void setName(String name) {this.name = name;}

    public int getAge() {return age;}

    public void setAge(int age) {
        //对age进行合法性检查
        if (age > 120 || age < 0) {
            System.out.println("Out of age bound, please enter" +
                    " a number in 0~120!");
        } else this.age = age;
    }
}
package konoha.oop.objectLearn;

public class Dog extends Pet {

    public Dog(){}//默认的空无参构造器

    public Dog(String name, int age) {
        super(name,age);//显示的调用了含参构造器
    }

    //对父类的实例方法进行了重写
    @Override
    public void setAge(int age) {
        //对age进行合法性检查
        if (age > 20 || age < 0) {
            System.out.println("Out of age bound, please enter" + " a number in 0~20!");
        } else super.setAge(age);
    }
    
    public String getMale() {return male;}
	
     public void change(String male) {
        male = "female"; //子类直接操作继承的成员属性
    }
}

package konoha.oop.extendsLearn;

import konoha.oop.objectLearn.Dog;

public class Control {
    public static void main(String[] args) {
        Dog dog = new Dog("Ali", 13);//使用了重写的子类方法
                                     //可以更改参数进行实验

        //子类使用继承自父类的公共方法操作
        System.out.println("Pet Name: " + dog.getName() + " , Pet age: " + dog.getAge());
        
        //调用子类方法修改继承的属性
        System.out.println("Pet Male: " + dog.getMale());
        dog.change("female");
        System.out.println("Pet Male: " + dog.getMale());        
    }
}
//大家还可以实验更多的操作
  • 其他的一些问题:子类在继承父类时,我们说过子类不会继承父类的私有成员,然而在上面的例子上我们发现了子类实例仍然可以通过继承的公共方法访问,操作父类的这些私有成员。联系到子类实例在构造时会首先调用父类的构造器,我产生了一个疑问,那这些父类的变量又存在在哪里呢?这些变量应该还是存储在子类的实例对象中。但是其中的原理还需要更加深入的学习。也希望看到这个问题的人可以不吝赐教。

十. 多态

  • 同一方法可以根据发送对象的不同而采用多种不同的行为方式。

  • 一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多。

  • 多态存在的条件:

    • 有继承关系
    • 子类重写了父类方法
    • 父类引用指向子类对象
  • 注意:

    1. 多态是方法的多态,属性没有多态
    2. 使用父类引用指向子类实例,二者必须有联系,不能是任意的类的引用变量。否则会报错,类型转换异常ClassCastException。只能使用父类的引用变量指向子类实例,子类的引用变量不能指向父类的实例。
    3. 使用static,final,private修饰词的方法不能被重写,自然也就没有多态。static方法,静态方法,它属于类,不属于实例。final修饰的方法不允许再次修改。private方法不被子类所继承,自然也就不能重写。即使满足多态的条件,他们也是不同的方法。
  • 下面是实例代码:

package konoha.oop.polymorphism;
//父类
public class Pet {

    private String name;
    private int age;

    public Pet(){} //显示声明的空构造方法

    //含参构造方法
    public Pet(String name, int age) {
        this.name = name;//this 关键字指代自身
        this.age = age;
    }

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}

    public int getAge() {return age;}

    public void setAge(int age) {
        this.age = age;
    }
}
package konoha.oop.polymorphism;
//继承自Pet的一个子类Cat
public class Cat extends Pet {

    public Cat() {}//隐式的调用父类的无参构造

    public Cat(String name, int age) {
        super(name, age);//显示的调用了父类含参构造器
    }

    @Override//Cat类对父类的实例方法进行了重写
    public void setAge(int age) {
        //对age进行合法性检查
        if (age < 0 || age > 15) {
            System.out.println("Cat age wrong!");
            return;
        } else super.setAge(age);
    }
}
package konoha.oop.polymorphism;
//继承自Pet的一个子类Dog
public class Dog extends Pet {

    public Dog(){}//隐式的调用父类的无参构造

    public Dog(String name, int age) {
        super(name,age);//显示的调用了父类含参构造器
    }


    @Override//Dog类对父类的实例方法进行了重写
    public void setAge(int age) {
        //对age进行合法性检查
        if (age > 20 || age < 0) {
            System.out.println("Dog age wrong!");
        } else super.setAge(age);
    }

}


package konoha.oop.polymorphism;
//控制类,观察输出,体会方法的多态
public class Control {
    public static void main(String[] args) {
        Dog dog = new Dog("Bob", 16); //正常构建
        Cat cat = new Cat("Ali", 3);

        Pet pet1 = dog; //父类引用指向Dog类的实例对象
        pet1.setAge(50);

        Pet pet2 = cat; //父类引用指向Cat类的实例对象
        pet2.setAge(50);

        Pet pet = new Pet();  //Pet类引用指向自己
        pet.setAge(50);
        System.out.println(pet.getAge());
    }
}

十一. instanceof与类的转换

  • instanceof 操作符: Java 中可以使用 instanceof 关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例,语法格式如下所示。

    boolean flag = obj instanceof classsName;
    //          实例对象的引用           类名
    

    当obj为类的一个实例或者是其子类的一个实例时,instanceof操作符返回true,其他都返回false。

    需要注意的是,instanceof不允许毫无关联的实例和类名进行比较,可以看以下例子:

package konoha.oop.instanceofKeyWorld;

import konoha.oop.polymorphism.Cat;
import konoha.oop.polymorphism.Dog;
import konoha.oop.polymorphism.Pet;
//Pet为Dog和Cat的父类
public class Control {
    public static void main(String[] args) {
        Dog pet1 = new Dog();
        System.out.println(pet1 instanceof Dog);   //true
        //System.out.println(pet1 instanceof Cat); 不允许的操作
        System.out.println(pet1 instanceof Pet);   //true
        System.out.println(pet1 instanceof Object);//true

        System.out.println("==================================");

        Cat pet2 = new Cat();
        //System.out.println(pet2 instanceof Dog); 不允许的操作
        System.out.println(pet2 instanceof Cat);   //true
        System.out.println(pet2 instanceof Pet);   //true
        System.out.println(pet2 instanceof Object);//true

        System.out.println("==================================");

        Pet pet3 = new Pet();
        System.out.println(pet3 instanceof Dog);   //false
        System.out.println(pet3 instanceof Cat);   //false
        System.out.println(pet3 instanceof Pet);   //true
        System.out.println(pet3 instanceof Object);//true
        //System.out.println(pet3 instanceof String); 不允许的操作

    }
}
  • 对象类型强制转换

    • 可以类比基本类型的转换规则,将父子继承关系类比基本类型高低。父类高,子类低。低到高自动转换,高到低需要强制转换。
    • 程序中,对象的类型需要看的是定义类型,而不是使用new关键字新建的类的对象类型。比如下面这个例子:
            //Dog中有一个父类没有的方法shout();
            Pet dog = new Dog();
            //dog.shout();  无法调用
            ((Dog)dog).shout(); //可以调用
    
    • 类型转换方便了方法的调用,减少了重复的代码。

十二. static关键字

  • 使用static修饰的类的属性:静态的变量,被这个类的所有实例共享,建议使用className.varName的方法方位。在以后的多线程编程中会经常用到。

  • 使用static修饰的方法:静态方法,在类加载时一起被加载。可以不创建类的实例对象,直接使用className.methodName()调用。

    通过下面的一个例子了解这受static修饰的属性和方法的细节:

    public class StaticKeyWord {
        String name = "konoha";
        static int age = 18;
    
        //静态的main()方法
        public static void main(String[] args) {
            hello();
            //showName(); 无法调用
    
            //新建Method对象
            Method method = new Method();
            method.showName();
        }
        //静态的hello()方法
        public static void hello() {
            System.out.println("Hello world!");
            //showName(); 无法调用非静态方法
            //String name = this.name; 实例变量不能在静态方法中被调用
        }
    
        //非静态的show方法
        public void show() {
            System.out.println("Name: " + name + " Age: " + StaticKeyWord.age);
            hello(); //可以直接调用自己的静态方法
        }
    }
    
  • 使用一对 {} 括起来的代码叫做代码块(匿名代码块),使用static修饰的就是静态代码块。代码块在类的对象创建时先于构造方法被执行。静态代码块在类被加载时执行,并且只执行一次。

    看下面这个例子,思考他的输出结果是怎么样的,在实际试试看:

    public class StaticCodeBlock {
        {
            //匿名代码块,可以用来赋初始值
            System.out.println("匿名代码块执行!");
        }
        static {
            //静态代码块
            System.out.println("静态代码块执行!");
        }
    
        public StaticCodeBlock() {
            System.out.println("构造方法执行!");
        }
    
        public static void main(String[] args) {
            StaticCodeBlock st1 = new StaticCodeBlock();
            System.out.println("=============================");
            StaticCodeBlock st2 = new StaticCodeBlock();
        }
    }
    
  • 静态导入包:看下面的两个例子理解

    import java.lang.Math;
    //正常的方式使用导入包
    public class StaticImport {
        public static void main(String[] args) {
            System.out.println(Math.random());
            //必须使用className.methodName调用,否则报错
            //System.out.println(random()); 报错
        }
    }
    
    import static java.lang.Math.random;
    import static java.lang.Math.PI;
    //静态导入包,可以直接使用类中静态的方法和变量
    //非静态的成员无法被静态导入
    public class StaticImport {
        public static void main(String[] args) {
            System.out.println(random());
            System.out.println(PI);
        }
    }
    

十三. 抽象类

  • 抽象类就是由关键词abstract修饰的类。abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那公该类就是抽象类。

  • 抽象方法:只有方法的声明,没有方法的实现,它是用来让子类实现的。

  • abstract关键字只能用于普通方法,不能使用于static方法或者构造方法。

  • 注意抽象方法不能使用private关键词修饰。因为抽象类的所有抽象方法都必须被子类重写,而如果使用了private关键字,子类就无法重写。

  • 抽象类:不能使用new关键字来直接创建建对象,它是用来让子类继承的。

  • 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。

  • 子类继承抽象类,那么就必须要实现抽象类所有没有实现的抽象方法,否则该子类也要声明为抽象类。

    通过下面的实例更好地理解抽象类:

    //抽象类
    package konoha.oop.abstractClass;
    
    public abstract class Pet {
        //public Pet() {}
        protected String name;
        //public void showName() {} 可以有非抽象方法
        public abstract void shout();
    }
    
    package konoha.oop.abstractClass;
    //继承了抽象类的子类
    public class Dog extends Pet {
    
        public Dog() {}
    
        public Dog(String name) {
            this.name = name;
        }
    
        @Override//重写了抽象类的方法
        public void shout() {
            System.out.println(name + " shout!");
        }  //不重写方法则需要将子类也声明为抽象类
    }
    
    package konoha.oop.abstractClass;
    //测试类
    public class Control {
        public static void main(String[] args) {
            Dog dog = new Dog("Bob");
            dog.shout();
    
            //Pet pet = new Pet();  不能创建
        }
    }
    
  • 抽象类仍然存在构造器,他的构造器不是用于创建自己的实例对象,而是让子类调用这些构造方法来完成属于抽象类的初始化操作。抽象类和普通类相比,除了不能实例化以外没有任何区别

十四. 接口(Java最重要的概念之一)

  • 接口就是规范!他为实现他的类定义了一组必须遵守的规范。

  • 接口可以被理解为一种特殊的类,没有构造方法,由全局常量和公共的抽象方法所组成。他的定义方法如下:

    interface interfaceName [extends interfaceName,[...]]定义接口的关键词  接口名        接口可以继承多个接口
    
  • 接口的特征:

    1. 接口不能被private,protected所修饰。
    2. 接口定义的所有实例方法默认都是public abstract的,你可以不必显式的声明。实现他的子类重写时也不能改变他的权限。
    3. 接口定义的变量都是由public static final修饰的,你可以不必显式的声明。相当于常量,在定义的时候必须初始化。
    4. 接子接口继承父接口时,可以重写父接口的常量和方法。
    5. 接口没有构造方法,不能被实例化。
  • 实现接口:接口最主要的用途就是被实现类实现。一个类可以实现一个或多个接口,使用implements关键字,每个接口用逗号分隔。语法如下:

    class className implements interfaceName1[, ...]
    

    一个类可以实现多个接口,也是对Java只能单继承灵活性不足的问题做的补充。

  • 实现接口时的注意点:

    1. 实现接口与继承父类相类似,一样可以获得所实现接口里的定义的常量和方法,。如果一个类需要实现多个接口,则多个接口以逗号隔开。
    2. 一个类可以在继承一个父类的同时实现一个或多个接口,implement关键词部分必须放在extends部分之后。
    3. 一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。

    下面是一个使用接口的实例:

    package konoha.oop.interfaceLearn;
    //公共的接口
    public interface Person {
        public static final int MAX_AGE = 150;//可以不写修饰符
    
        public abstract void eat(); //可以不写修饰符
    	void drink();
        void sleep();
    }
    
    package konoha.oop.interfaceLearn;
    //默认的接口
    interface Other {
        void speak();
    }
    
    package konoha.oop.interfaceLearn;
    
    /*
     * 使用接口可以实现多继承
     * 实现接口必须实现所有接口的方法
     * 继承了上述两个接口的类
     */
    
    public class Student implements Person, Other {
        @Override //重写了两个接口中的所有方法
        public void eat() {
            System.out.println("Student eat");
        }
    
        @Override
        public void drink() {
            System.out.println("Student drink");
        }
    
        @Override
        public void sleep() {
            System.out.println("Student sleep");
        }
    	//使用了接口中定义的公共属性
        public void showMaxAge() {
            System.out.println("Max Age: " + MAX_AGE);
        }
    
        @Override
        public void speak() {
            System.out.println("Student speak");
        }
    }
    
    
    package konoha.oop.interfaceLearn;
    //测试类
    public class Control {
        public static void main(String[] args) {
            Student student = new Student();
    
            student.drink();
            student.eat();
            student.speak();
            student.sleep();
            student.showMaxAge();
        }
    }
    

十五. 抽象类和接口的区别

​ 抽象类和接口看起来很相像,他们都不能被实例化,都包含有抽象方法,都是主要用于被其他的类实现。但实际上他们的差距却非常大,主要体现在他们的设计目的上。


  • 接口:

    ​ 接口作为系统和外界交互的窗口,他体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法)。当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。

    ​ 从某种程度上来看,接口类似于整个系统的“总纲”,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,会导致系统中大部分类都需要改写。

  • 抽象类:

    ​ 抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式,就是他的子类们。

    参考C语言中文网的Java课程

十六. 内部类

posted @ 2021-02-25 20:44  心叶酱  阅读(18)  评论(0编辑  收藏  举报