第九章 面向对象编程

本文详细介绍了面向对象编程的三大特性:封装、继承和多态,以及抽象类和接口的概念。通过实例展示了如何在Java中实现这些特性,并解释了它们在代码复用、灵活性和扩展性方面的重要性。同时,讨论了抽象类与接口的区别,强调了它们在行为抽象和多继承中的角色。
摘要由CSDN通过智能技术生成

面向对象程序设计(OOP)是一种计算机编程思想,主要目标是为了实现代码的重用性、灵活性和扩展性。面向对象程序设计以对象为核心,程序由一系列对象组成。对象间通过调用相互通信,来模拟现实世界中不同事物间的关系。

面向对象程序设计有三大特性:封装,继承,多态。

封装的基本体现就是对象,封装使得程序的实现“高内聚、低耦合”的目标。封装就是把数据和数据处理包围起来,对数据的处理只能通过已定义的方法来实现。封装可以隐藏实现细节,使得代码模块化。

继承允许我们依据一个类来定义另一个类。当创建一个类时,不需要重新编写新的数据成员和方法成员,只需继承一个已有类即可。这个已有类称为基类(父类),新建的类称为派生类(子类)。派生类就自然拥有了基类的数据成员和方法成员。这样做可以重用代码功能和提高执行效率。

接下来,我们演示类的继承:

// 定义父类
class Person {
	
	// 定义数据成员(属性)
	public String name;
	public boolean sex;
	public int age;
	
	// 定义方法成员
	public void say(){
		
		System.out.println("I am " + name);
	}
}

// 定义子类
class Teacher extends Person { }

我们在main方法中使用Teacher类,如下:

		// 声明并实例化子类
		Teacher t = new Teacher();
		t.name = "liu";
		t.say();

我们可以看到,当Teacher类继承了Person类之后,它就拥有了Person的数据成员和方法成员。我们就可以使用Person类一样,使用Teacher类了。

当然,Teacher类自身也可以拥有自己的成员变量和成员方法,我们继续完善一下Teache类,如下:

// 定义子类
class Teacher extends Person {
	
	// 学科
	private String subject;
	
	// 定义新方法:设置学科
	public void setSubject(String sub){
		
		subject = sub;
	}
	
	// 重写父类方法
	public void say(){
		
		System.out.println("I am " + name);
		System.out.println("I'm a " + subject + " teacher");
	}
}

我们定义了一个新的成员变量subject,来代表该名老师是教那个科目的。注意,该成员变量是private私有的,也就是说,这个变量只能在Teacher类中使用。因此,我们又新定义了一个方法setSubject用来指定变量subject的值,该方法是public公开的,可以被外类访问。

当然,这还没有结束。子类还可以重写父类的方法,让其更加适用于自身的特性。

然后,我们在main方法中这样使用:

		// 声明并实例化子类
		Teacher t = new Teacher();
		t.name = "liu";
		t.setSubject("java");
		t.say();

重新编译运行结果如下:

重写父类方法,是一个非常巧妙的设计。接下来,我们在重新修改一下子类的say方法:

	// 重写父类方法
	public void say(){
		
		//System.out.println("I am " + name);
		super.say();
		System.out.println("I'm a " + subject + " teacher");
	}

重新编译运行后,输出的结果是一样的。我们可以猜到,super.say(); 执行的应该是父类的say方法,是的,没错。关键字super代表的就是父类。通过以上的代码示例,我们发现,这个继承对我们编程帮助很大,不仅能够进行代码复用,而且非常的灵活。子类不仅可以拥有父类的所有,还可以对父类进行改造。需要特别注意的是,父类中的private修饰的成员是不能被子类继承的,我们之前也讲过private的访问控制说明。

接下来,我们讨论一下子类和父类的数据成员初始化操作。我们知道,类的成员变量初始化是在构造方法中完成的。那么子类继承父类之后,这个构造方法也继承下来了嘛?答案不继承,但是可以通过super调用。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。代码示例如下:

// 定义父类
class Person {
	
	// 定义数据成员(属性)
	public String name;
	public boolean sex;
	public int age;
	
	// 定义默认构造方法
	Person(){
		
		name = "liu";
		sex = false;
		age = 0;
	}
		
	// 定义方法成员
	public void say(){
		
		System.out.println("I am " + name);
	}
}

我们在父类中定义了默认构造方法,并初始化父类的成员变量值,接下来,我们在子类中也定义一个默认构造方法,同样初始化子类的成员变量值,代码示例如下:

// 定义子类
class Teacher extends Person {
	
	// 学科
	private String subject;
	
	// 定义默认构造方法
	Teacher(){
		subject = "java";
	}
		
	// 定义新方法:设置学科
	public void setSubject(String sub){
		
		subject = sub;
	}
	
	// 重写父类方法
	public void say(){
		
		//System.out.println("I am " + name);
		super.say();
		System.out.println("I'm a " + subject + " teacher");
	}
}

最后,我们就在main方法中实例化子类并使用他,代码如下:

		// 声明并实例化子类
		Teacher t = new Teacher();
		t.say();

重新编译运行后结果如下:

我们发现,当我们实例化子类的时候,系统会自动调用父类的构造方法和子类的构造方法

接下来,我们分别给父类和子类构造有参的构造方法,代码示例如下:

	// 定义有参构造方法
	Person(String name, boolean sex, int age){
		
		this.name = name;
		this.sex = sex;
		this.age = age;
	}

在定义子类的有参构造方法的时候,系统不会自动调用父类的有参构造方法,需要我们使用super手动调用,同时子类的构造方法的参数应该是包含所有的成员变量。如下所示:

	// 定义有参构造方法
	Teacher(String name, boolean sex, int age, String subject){
		
		super(name,sex,age);
		this.subject = subject;
	}

在上述代码中,我们使用 super(name,sex,age); 语句来调用父类的有参构造方法。然后,我们就可以在main方法中使用有参构造方法来定义子类并使用它了,如下所示:

		// 使用有参构造方法
		Teacher t2 = new Teacher("lisi", true, 30, "C++");
		t2.say();

最后,我们需要再次注意的就是,无参的默认构造方法是自动调用的,有参的构造方法需要我们自己手动调用。

多态性是指不同的对象接收到同一个方法调用之后,所表现出来的行为是各不相同的。多态是构建在封装和继承的基础之上的。多态就是允许不同类的对象对同一方法名的调用后的结果是不一样的。多态虽然概念有些复杂,但是只要理解了继承,多态就非常简单了。这里需要大家注意的是,多态的三个必要条件:继承,重写,父类引用变量指向子类的实例对象。我们代码实例说明:

// 定义父类
class Person {
	
	// 定义方法成员
	public void say(){
		
		System.out.println("I am a person");
	}
}

// 定义教师子类
class Teacher extends Person {
	
	// 重写父类方法
	public void say(){
		
		System.out.println("I'm a teacher");
	}
}

// 定义学生子类
class Student extends Person {
	
	// 重写父类方法
	public void say(){
		
		System.out.println("I'm a student");
	}
}

上述代码中,我们定义了父类,并定了两个子类,子类中都重写了父类的方法。接下来,我们在main中这样使用这两个子类,代码如下:

		// 定义教师类
		Person teacher = new Teacher();
		teacher.say();
		
		// 定义学生类
		Person student = new Student();
		student.say();

通过编译运行,我们发现,两个子类虽然声明为父类Person类型,但是在调用say方法的时候,执行的确实子类的say方法。因为,我们在new实例化的时候,是他们的子类。

面向对象的三大特征,封装,继承,多态。封装性是基础 ,继承性是关键 ,多态性是补充 ,多态性又存在于继承的环境之中 ,所以这三大特征是相互关联的 ,相互补充的。除了面向对象的三大特征之外,我们还需要了解“抽象”的使用。我们在类和对象的章节中,说到过,类就是对现实事物的抽象。这种抽象不仅仅是将现实事物转变成程序实现,更重要的也是一种编程思想,也就是说,我们如何将现实事物转化为类的过程。

面向对象程序设计中一切都是对象,对象都是通过类来描述的,但并不是所有的类都可以来描述对象的。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来实现它,那么这样的类我们称它为抽象类。例如,我们说一个建筑物,它是一个现实的事物,但是每个人听到这个词的时候,脑海里想象的肯定不一样。有的人想的是一座普通的住宅楼,有的人想的可能是一座豪华的办公楼。这个时候,我们将这个建筑物作为一个类的话,这个类所表达的内容(数据成员和方法成员)有些模糊了。在Java中,我们就可以将其定义为一个“抽象类”,“抽象类”的目的就是为了继承,也就是说,它需要子类来将这个模糊的概念清晰化,当然,我们使用的也肯定就是子类了。

// 定义抽象父类(建筑物)
abstract class Building {
	
	// 大小尺寸:长,宽,高
	public int length, width, height;
	
	// 定义方法成员: 建筑物用途
	public void use(){

        System.out.println("I don't know!");
    }
}

// 定义住宅楼子类
class HouseBuilding extends Building {
	
	// 重写父类方法
	public void use(){
		
		System.out.println("life in building");
	}
}

// 定义办公楼子类
class OfficeBuilding extends Building {
	
	// 重写父类方法
	public void use(){
		
		System.out.println("work in building");
	}
}

然后,我们在main方法中使用这两个子类,代码如下:

		// 定义住宅楼
		HouseBuilding house = new HouseBuilding();
		house.use();
		
		// 定义办公楼
		OfficeBuilding office = new OfficeBuilding();
		office.use();

关键字abstract就是抽象的意思,用它修饰的类就是一个抽象类。这里,大家需要注意的是,抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。那么,既然抽象类不能够实例化,那么它里面的 use 方法其实意义性不大了,因为它肯定是要被子类继承,并重写的。因此,我们可以在抽象类中把use方法定义成一个抽象方法,抽象方法和抽象类是一个意思,都是用来被继承的,因此抽象方法只是一个名称,并没有方法体。如下所示:

// 定义抽象父类(建筑物)
abstract class Building {
	
	// 大小尺寸:长,宽,高
	public int length, width, height;
	
	// 定义方法成员: 建筑物用途
	public abstract void use();
}

我们同样使用abstract来修饰一个方法,并且这个方法不需要写方法体。这样看的化,抽象类就真的非常的抽象了,它只声明一些基础的成员,具体的实现都交给子类来完成。

回过头来,我们在看抽象类的用途,其实非常的简单,就是把子类共性的内容放到父抽象类中。也就是说,我们从代码书写角度出发,是先定义父抽象类,然后在定义子类。但是,从程序设计角度出发,我们是先分析子类,然后再抽取子类们中共性部分放到父类中。这里需要注意的是,抽象类中不一定包含抽象方法,但一个类包含抽象方法,那么该类必须是抽象类,而且子类必须重写父类的抽象方法,或者声明自身(子类)也是一个抽象类。

接下来,我们继续讲解Java中另一个抽象概念:接口(Interface)。接口比抽象类更加抽象。我们知道一个普通的抽象类,可以定义普通的数据成员和方法成员,它的子类能够继承这些成员。也就是说,抽象类是可以提供一些完整的功能的。而我们现在要说的接口,它里面只能包含数据成员和抽象方法。接口和抽象类很相似,它同样需要类来继承,并且必须实现该接口里面的抽象方法,如果不实现抽象方法的话,那么这个类就必须声明为抽象类。这就是为什么接口要比抽象类更加抽象的原因啦。接口同样不可以被实例化,但是可以被声明为一个变量,实例化的目标必须是继承该接口的类。我们举例如下:

// 定义接口
interface Language {
	
	// 定义方法
	public void say();
}

// 定义继承接口的类
class Person implements Language {
	
	// 语言
	public String lang;
	
	// 构造方法
	Person(String lang){
		
		this.lang = lang;
	}
	
	// 实现接口方法
	public void say(){
		
		System.out.println("speak in "+lang);
	}
}

上述代码中,我们定义了一个“语言”接口,然后定义了一个类,并继承该接口。这样,这个类就必须实现接口里面的say的抽象方法。关键字interface用来定义一个接口,但是其内部的抽象方法就不需要关键字abstract来修饰了,这一点大家有注意。同样的,实现接口的类必须使用关键字 implements 来继承,而不是关键字 extends 来继承。那么,我们就可以在main方法中使用实现接口的类啦,如下:

		// 定义汉语
		Language chinese = new Person("chinese");
		chinese.say();
		
		// 定义英语
		Language english = new Person("english");
		english.say();

上述代码,大家注意,我们可以使用接口来声明变量,但实例化的时候,可以使用接口的实现类来进行实例化。这个操作类似于,我们使用父类来声明变量,使用子类实例化。接下来,我们就可以调用接口方法了,那么他们实际就会调用Person类里面的say方法了。当然,我们也可以直接将对象声明为Person类型,也是可以的。这个取决于我们的使用目的,如果我们使用的就是接口,那么我们可以声明为接口类型,如果我们使用的是类,那么我就可以声明为类类型了。

抽象类和接口有很多相似的地方,他们都是用来被继承的,而且都可以被用于声明变量,但是实例化必须是实现接口的类,或者实现抽象类的子类。他们不同点直接表现上,就是Java语法是不同的。抽象类是abstract声明,extends继承,而接口是interface声明,implements继承。另外,抽象类中可以定义普通的数据变量,但是接口中的成员变量只能是 public static final 类型的,也就是说接口中的变量必须是静态的,且是常量不可修改。最后,抽象类和接口的另一个重要区别,一个类只能继承一个父抽象类,但是可以继承多个接口。为什么会有这样的区别呢?其实还是源于面向对象的设计。抽象类的本质是类,类是现实事物的程序表现,主要包括数据和行为的表现,行为就是方法,方法就是功能的封装。一个复杂的业务模型可能需要多个类和多个行为才能实现,业务模型的变化也决定了行为的变化。因此,接口就是从行为的角度出发,对某些事物,某类事物等共性行为的封装。因为事物行为的复杂性,造成我们需要单独对行为进行抽象,这就是接口。

本课程涉及的代码可以免费下载:
https://download.csdn.net/download/richieandndsc/85645935

今天的内容就讲的这里,我们来总结一下。今天我们主要讲了Java的面向对象的三大特性:封装,继承,多态。类本身就是一种封装的体现。继承可以让我们类产生父子关系,代码重用,并且子类可以对父类的方法进行重写。多态就是不同对象同一个行为的不同表现。最后我们介绍的是抽象类和接口。本章节涉及的内容很多,但更多的是让大家去理解。因为在我们日常的Web开发中,尤其是基于框架开发的话,很少会使用这些内容完成功能开发。但是,如果遇到这类的代码,大家必须能够看懂!!!好的,谢谢大家的收看,欢迎大家在下方留言,我也会及时回复大家的留言的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值