• 零基础学Java第六节(面向对象二)


    本篇文章是《零基础学Java》专栏的第六篇文章,文章采用通俗易懂的文字、图示及代码实战,从零基础开始带大家走上高薪之路!

    本文章首发于公众号【编程攻略】

    继承

    创建一个Person类

    我们创建一个用于描述的类。我们怎么抽象出一个这个类呢?我们以不同的角度做抽象,得到的属性和行为都会有些区别。这里,我们主要从的社会属性来抽象。为了表示性别,我们先顶一个枚举类型,该枚举类型中有两个值,用于表示,代码如下:

    public enum Sex {male,female}
    

    上面的代码写入单独的一个源代码文件中:Sex.java,因为上面的枚举类型是需要公开使用的,所以,也需要定义为public,因此,必须将之放在独立的源文件中 。

    再定义Person这个类,代码如下:

    public class Person{
    	private String name;//姓名
    	private Sex sex;//性别
    	private Date birthday;//出生日期
    	private String ID;//身份证号
    
    	//下面是一系列上述私有成员变量的setter和getter
    	public void setName(String name){
    		this.name = name;
    	}
    
    	public String getName(){
    		return name;
    	}
    
    	public void setSex(Sex sex){
    		this.sex = sex;
    	}
    
    	public Sex getSex(){
    		return sex;
    	}
    
    	public void setBirthday(Date date){
    		birthday = date;
    	}
    
    	public Date getBirthday(){
    		return birthday;
    	}
    
    	public void setID(String ID){
    		this.ID = ID;
    	}
    
    	public String getID(){
    		return ID;
    	}
    
    	public Person(String name, Sex sex, Date birthday, String ID){
    		this.name = name;
    		this.sex = sex;
    		this.birthday = birthday;
    		this.ID = ID;
    	}
    }
    

    我们编译上面的Person.java,它所涉及到的相关的类也会一并编译完成,如:Sex.java文件。

    定义学生子类

    如果我们考虑一个班级的组成,大致有三种类别的人员,学生教师辅导员,我们依次进行定义。这三类都具有所具有的属性,因此,我们没必要在三个类中重新定义那些公共属性,我们只需继承Person类即可。当然,这些类一定也具有不同于普通的属性和行为。先定义学生类。

    public class Student extends Person {
    	private String studentID;//学号
    	private Date rollInDate;//入学时间
    
    	public void setStudentID(String studentID){
    		this.studentID = studentID;
    	}
    
    	public String getStudentID(){
    		return studentID;
    	}
    
    	public void setRollInDate(Date rollInDate){
    		this.rollInDate = rollInDate;
    	}
    
    	public Date getRollInDate(){
    		return rollInDate;
    	}
    
    	public Student(String name, 
    					Sex sex, 
    					Date birthday, 
    					String ID, 
    					String studentID, 
    					Date rollInDate){
    		super(name,sex,birthday,ID);//对父类的构造方法的调用
    		//虽然Student能够继承父类中的私有成员,
    		//但是却不能直接像使用本类中的成员变量一样直接调用,
    		//因此,下句是不能调用的
    		//this.name = name; 
    		this.studentID = studentID; //本类的成员变量,当然可以这么写
    		this.rollInDate = rollInDate;
    	}
    }
    

    Student类继承了Person中所有的成员,包括私有的,但是对于子类虽然拥有继承自父类的成员,却不能直接使用。子类可以直接使用的继承来的成员有public和protected修饰的,对于无修饰符的成员变量,如果子类同父类不在同一个包,也是不能使用的。示例如下:

    class A{
    	private int x = 0;
    	protected int y = 1;
    	public int z = 2;
    	int m = 3;
    
      	A(){
        	System.out.println("在父类里");
      	}
    
      	A(String s){
        	System.out.println("在父类里 "+s);
      	}
    }
    
    public class B extends A{
    //在此处加上int x;会怎么样呢?
      	B(){
        	System.out.println("在子类里");
      	}
      	B(String s){
        	super(s);
        	System.out.println("在子类里 "+s);
    
      	}
     	public static void main(String s[]){
        	B b=new B("对吗?");
        	System.out.println(b.x);//不对哦
        	System.out.println(b.y);
        	System.out.println(b.z);
        	System.out.println(b.m);
      	}
    }
    
    

    继承具有以下特征:

    • 子类只能继承自一个父类,它继承父类中的所有成员,甚至包括父类中private所修饰的成员变量或方法。子类中可以定义新的成员变量和成员方法,也可以对父类中的成员变量及成员方法进行重新定义,这种对方法的重新定义称为复写(override)

    • 如果改变了父类中某些功能,而这些功能在子类中未进行复写,那么修改父类的功能改变会影响到子类。

    • 父类具有所有继承自它的子类的共同的特征和行为。

    继承最基本的作用:代码重用。 继承最重要的作用:方法可以复写。

    super的使用

    我们在Student的构造方法中使用了supper(name,sex,birthday,ID);,这是对父类的构造方法的调用。对于继承关系的类,子类的构造方法总是会调用父类的构造方法:如果没有显式调用,系统会自动调用父类的无参构造方法,但是一旦显式调用,系统就不会再调用父类中的无参构造方法。

    class A{
    	A(){
    		System.out.println("in A()");	
    	}
    }
    
    class B extends A{
    	B(){
    		System.out.println("in B()");	
    	}
    }
    
    class C extends B{
    	C(){
    		System.out.println("in C()");	
    	}
    }
    
    class TestExtends{
    	public static void main(String[] args){
    		C c=new C();
    	}
    }
    

    super的使用同this相似,但是它们两者的本质是不同的,this是引用,而super不是,super指向的不是父类对象,它代表当前子类对象中继承自父类的属性和行为。如图:

    什么时候使用super呢?当子类和父类中具有同名的成员时,例如,子类和父类中都有name这个属性,如果要在子类对象中访问继承自父类中的name属性,就需要使用 super进行区分。

    super可以用在什么地方?

    • 第一:super和this一样可以用在非静态方法中,不能用在静态方法中。
    • 第二:super可以用在构造方法中。一个构造方法第一行如果没有this(...);,也没有显式的去调用super(...);,系统会默认调用super(),因此,如果父类中没有缺省构造方法,则会出错(大家如果注释掉上面代码中的super,测试一下);
    • 注意:super(...);的调用只能放在构造方法的第一行。因此,super(....)this(....)不能共存。super(...);只是调用了父类中的构造方法,并不会创建父类对象。

    复写(override)

    复写指子类重定义了父类中的同名方法。什么时候方法要进行复写?如果父类中的方法已经无法满足当前子类的业务需求,需要将父类中的方法功能进行重新进行定义。子类如果复写父类中的方法之后,子类对象一定调用的是复写之后的方法。

    发生方法复写的条件:

    1. 发生在具有继承关系的两个类之间
    2. 复写发生在继承关系中,必须具有相同的方法名,相同的返回值类型,相同的参数列表
    3. 复写后的方法不能比被复写的方法拥有缩小的访问权限。
    4. 复写后的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
    5. 私有的方法不能被复写,但可以在子类定义和父类中同名的方法(但不称之为复写)
    6. 构造方法无法被复写。因为子类同父类的构造方法不同。
    7. 复写指的是成员方法,和成员变量无关。
    class A{
      	private int x=0;
      	protected int y=1;
      	public int z=2;
      	int m=3;
    
      	void t1(){
        	System.out.println("在父类t1里");
      	}
    }
    
    public class B extends A{
      	void t1(){
        	super.t1();
        	System.out.println("在子类t1里");
      	}
      	public static void main(String s[]){
        	B b=new B();
        	b.t1();
        	System.out.println(b.m);
      	}
    }
    
    

    final关键字

    final关键字可以出现在类名前、方法前、局部变量前、成员变量前、形参前,它具有如下作用:

    • final修饰的类无法被继承
    • final修饰的方法无法被复写
    • final修饰的局部变量,一旦赋值,不可再改变
    • final修饰形参,形参的值在方法体中也无法改变
    • final修饰的成员变量必须手动初始化,不能取它的默认值,一般和static联用,如:
    public static final int i = 100;
    
    • final修饰的引用类型,该引用不可再重新指向其他的java对象。但是final修饰的引用指向的对象的属性是可以修改的,切不可混为一谈。

    定义教师子类

    教师类除了它具有作为所具有的特征之外,更多的是教师的教务特征和功能,由此,我们先定义一个描述课程的类:Course,如下:

    public class Course{
    	private String courseName;
    	private int courseId;
    
    	public Course(int courseId, String courseName){
    		this.courseId = courseId;
    		this.courseName = courseName;
    	}
    
    	public void setCourseName(String courseName){
    		this.courseName = courseName;
    	}
    
    	public String getCourseName(){
    		return courseName;
    	}
    
    	public void setCourseId(int courseId){
    		this.courseId = courseId;
    	}
    
    	//toString方法继承自Object类
    	public String toString(){
    		System.out.println("课程编号:" + courseId + "\n课程名称:" + courseName);
    	}
    }
    

    我们定义教师类:Teacher如下:

    public class Teacher extends Person {
    	private String teacherID;//工号
    	private Date rollInDate;//入职时间
    	private Course[] teachingCourse; //教授的课程
    
    	public void setTeacherID(String studentID){
    		this.teacherID = teacherID;
    	}
    
    	public String getTeacherID(){
    		return teacherID;
    	}
    
    	public void setRollInDate(Date rollInDate){
    		this.rollInDate = rollInDate;
    	}
    
    	public Date getRollInDate(){
    		return rollInDate;
    	}
    
    	public void setTeachingCourse(Course[] teachingCourse){
    		this.teachingCourse = teachingCourse;
    	}
    
    	public Course[] getTeachingCourse(){
    		return teachingCourse;
    	}
    
    	public Teacher(String name, 
    					Sex sex, 
    					Date birthday, 
    					String ID, 
    					String teacherID, 
    					Date rollInDate,
    					Course[] teachingCourse){
    		super(name,sex,birthday,ID);//对父类的构造方法的调用
    		//虽然Student能够继承父类中的私有成员,
    		//但是却不能直接像使用本类中的成员变量一样直接调用,
    		//因此,下句是不能调用的
    		//this.name = name; 
    		this.teacherID = teacherID; //本类的成员变量,当然可以这么写
    		this.rollInDate = rollInDate;
    		this.teachingCourse = teachingCourse;
    	}
    }
    

    定义辅导员

    辅导员是教师,但辅导员在教师的功能基础上又多了一些管理功能,所以辅导员类应该继承自教师类,代码如下:

    public class Counsellor extends Teacher{
    	
    }
    

    转型(cast)

    对于上面所定义的类,我们可以说:a student is a persona teacher is a persona counsellor is a teachera counsellor is a person。从这些说法,我们可以看出来一个子类对象的类型是可以看作父类类型的。因此,下面的语句是合法的:

    Person p = new Student();
    

    这种情况,我们称之为向上转型(upcast),这有点类似于基本类型中的自动类型转换。

    有时候,我们也需要把某个父类对象当作一个子类对象使用,我们不鼓励这么用,因为这是不自然的,就像我们不能说:a person is a counsellor一样,如果非要这么做,我们必须像基本类型的强制类型转换一样使用强制转换,我们称之为向下转型(downcast),如下例:

    Teacher teacher = new Teacher();
    Counsellor counsellor = (Counsellor)teacher;
    

    上例中,我们使用了缺省构造方法,但是,我们并没有定义相关类的缺省构造方法,我们只是为了说明转型这个概念,大家为了使得上例合法,要么使用带参的构造方法,要么,修正前面定义的相关类。

    转型示例:

    class T1{
        int i = 1;
    }
    
    class T2 extends T1{
        int i = 2;
    }
    
    public class TUpcast{
        public static void main(String[] args){
            T2 t2 = new T2();
            System.out.println("t2.i=" + t2.i +"upcast t2.i=" + ((T1)t2).i);
            //这说明子类中可以重新定义父类中的成员变量,同名的可以隐藏父类中的同名变量,也就是说他们是不同的变量
        }
    }
    

    定义班级

    一个班级的基本组成,有若干学生、若干教师、一个辅导员,我们定义班级类如下:

    public class Classes{
    	public String className;
    	public int classId;
    	public Student[] students;
    	public Teacher[] teachers;
    	public Counsellor counsellor;
    
    	public Classes(int classId, 
    					String className, 
    					Student[] students, 
    					Teacher[] teachers, 
    					Counsellor counsellor){
    		this.classId = classId;
    		this.className = className;
    		this.students = students;
    		this.teachers = teachers;
    		this.counsellor = counsellor;
    	}
    }
    

    一旦某个类创建及测试后,它便具有了某种有用的功能,我们可以创建该类的对象,通过该对象,使用定义好的功能。除此之外,我们还可以通过使用类的继承机制进行复用,也可以把对象放入一个新的类中,称为成员对象,这种新类由其它类对象组成的情况,称为组合(composition)。如上面的Classes类就是由已有的类成员组合而成。

    多态

    我们由下面的例子来解释什么是多态,如下:

    class Animal{
    	void eat(){
    
    	}
    }
    
    class Cat extends Animal{
    	void eat(){
    		System.out.println("鱼好吃!");
    	}
    
    	void chasingMouse(){
    		System.out.println("追啊追,追的好开心!");
    	}
    }
    
    class Rabbit extends Animal{
    	void eat(){
    		System.out.println("萝卜比鱼好吃!");
    	}
    
    	void run(){
    		System.out.println("我比乌龟跑得快!");
    	}
    }
    
    class Test{
    
    	public static void callEat(Animal animal){
    		animal.eat();
    	}
    
    	public static void main(String[] args) {
    		Cat cat = new Cat();
    		Rabbit rabbit = new Rabbit();
    
    		//下面的两个calEat方法在执行的时候,会根据传入的实参类型,选择执行不同类中的eat
    		//这种情况就是多态
    		callEat(cat);
    		callEat(rabbit);
    
    		//animal变量存放的为其子类的对象,执行子类对象中的eat方法
    		Animal animal = cat;
    		animal.eat();
    
    		animal = rabbit;
    		animal.eat();
    		//animal.chasingMouse();
    		((Rabbit)animal).run(); //原本animal引用的就是Rabbit对象,downcast是可以的
    		((Cat)animal).chasingMouse();//原本animal引用的是Rabbit,rabbit是不能转换为cat的
    		
    	}
    }
    

    多态性是通过重载和复写及upcast来体现的。使用多态可以使代码之间的耦合度降低。项目的扩展能力增强。

  • 相关阅读:
    vue3 整合 springboot 打完整jar包
    docker 开发编译环境搭建
    pdf 转 word
    Arthas使用总结
    (Note)动态规划的基本步骤
    PHP脚本导出MySQL数据库
    【ES】springboot集成ES
    String StringBuffer 和 StringBuilder三者有什么不同?
    Spring Security认证器实现
    伦敦银如何选择最优的交易方法
  • 原文地址:https://www.cnblogs.com/code-for/p/16293874.html