JavaSE基础——面向对象2:封装、继承与多态006
三、封装
1. 封装是什么?
在面向对象编程中,程序要追求高内聚,低耦合。高内聚就是类的内部数组操作细节自己完成,不允许外部干涉;低耦合就是仅暴露少量的方法接口供外部使用。
封装是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。通过封装可以禁止外部直接访问一个对象中数据,而只能通过操作接口来访问。
2. 封装的优点
-
良好的封装能够减少耦合。要访问该类的代码和数据,必须通过严格的接口控制。
-
类内部的结构可以自由修改。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
-
适当的封装可以是程序便于理解和维护,安全性增强。隐藏信息,实现细节。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
-
可以对成员变量进行更精确的控制
3. 封装的实现
java实现封装的步骤:
-
修改属性的可见性来限制堆属性的访问,一般限制为
private; -
package com.oop.www;
//这是一个子类,类名为Person,文件中不需要main()方法
public class Person {
//属性私有,使用private关键字,此时通过 对象名.属性 将无法访问
private String name;
private int age;
private char sex;
//提供一些可以访问和设定私有属性的公共方法
//getter()、setter()方法,可通过快捷键 Alt+insert 创建
//对私有属性name的公共访问方法
public String getName() {
return name;
}
//对私有属性name的公共赋值方法
public void setName(String name) {
this.name = name;
}
//对私有属性age的公共访问方法
public int getAge() {
return age;
}
//对私有属性age的公共赋值方法
public void setAge(int age) {
//安全性验证,只有数据合法才对私有属性赋值
if (age < 120 && age > 0) {
this.age = age;
} else {
this.age = 3;
}
}
//对私有属性sex的公共访问方法
public char getSex() {
return sex;
}
//对私有属性sex的公共赋值方法
public void setSex(char sex) {
this.sex = sex;
}
}
package com.oop.www;
public class Application {
public static void main(String[] args) {
//实例化类对象
Person automan = new Person();
//调用setter方法,通过给定的接口给私有属性赋值
// automan.name = "James" 因为属性设为私有,通过 对象名.属性 将不可访问,该语句报错
automan.setName("James");
automan.setAge(20);
automan.setSex('m');
//调用getter方法,通过给定的接口访问私有属性
System.out.println("Name: " + automan.getName() + " Age: " + automan.getAge());
}
}
---------------------------------------------------
运行结果:
Name: James Age: 20
封装实现总结:属性私有,getter/setter
四、继承
1. 继承是什么?

的本质式对一批类的抽象,比如兔子类和羊类都属于食草动物类,那么它们就有一些通用的属性和方法,这些通用的属性和方法就可以抽象为食草动物类,而兔子类和山羊类作为食草动物类的子类,可以继承父类食草动物类的属性和方法,同时扩展各自特有的属性和方法,从而使得代码更加简洁,提高代码的复用性和可维护性。
因此,继承是类与类之间的一种关系。除了继承以外,类与类之间的关系还有依赖、组合、聚合等等。
继承关系的两个类,一个是父类,一个是子类,从意义上来讲两个类具有 "is a" 的关系。
package com.oop_extends.www;
//创建一个类,Person,作为父类,其下有两个子类,Teacher和Student
//不是主实现类,因此Person类不含main方法
public class Person {
//父类中定义一个属性,并封装为私有
//父类中的私有属性子类无法继承(朕给你的才是你的,朕不给你你不能抢!!!)
private int money = 1000;
//父类中定义的一些public方法,子类可以继承使用
public void say() {
System.out.println("说了一句话");
}
//父类中定义一些公共的操作私有属性的方法,比如getter和setter方法。子类可以继承使用,从而操作父类中的私有属性
public int getMoney() {
return money;
}
//父类中定义一些公共的操作私有属性的方法,比如getter和setter方法。子类可以继承使用,从而操作父类中的私有属性
public void setMoney(int money) {
this.money = money;
}
}
package com.oop_extends.www;
//创建Student类,继承于Person类,是Person类的子类
//通过extends关键字,实现继承关系。继承后可操作父类中的public属性和方法
public class Student extends Person {
}
package com.oop_extends.www;
//这是一个主类,含有main方法,用于调用各个类实现
public class Application {
public static void main(String[] args) {
//通过new实例化一个学生对象
//Student类本身没有任何属性和方法,但它继承于Person类,
Student student = new Student();
//student对象调用Student类的父类Person类中的方法
System.out.println("我有:" + student.getMoney() + "元");
student.say();
}
}
---------------------------------------------------
运行结果:
我有:1000元
说了一句话
//可以发现,虽然Student类中未定义任何属性和方法,但由于它是Person类的子类,因此student对象也可以调用Person类中的方法
通过快捷键Ctrl + H可以打开当前类的继承树,比如在Student类下面打开继承树可以看到:

2. 继承的特性
-
子类拥有父类非 private的属性、方法。
-
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
-
子类可以用自己的方式实现父类的方法。
-
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
-
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
3. 继承的类型
Java 的继承是单继承,但是可以多重继承

4. 继承中的构造器(构造方法)
在创建对象的时候必须要调用构造方法,使用new关键字来创建对象的本质就是在调用构造方法。
那么当继承的时候,子类的构造方法与父类的构造方法之间又是什么关系呢?
package com.oop_extends.www;
//创建一个类,Animal,其下属子类为Dog
class Animal {
//Animal的无参构造方法
public Animal() {
System.out.println("父类Animal的无参构造方法");
}
}
//创建Animal的子类Dog
class Dog extends Animal {
//Dog类的无参构造方法
public Dog() {
System.out.println("子类Dog的无参构造方法");
}
}
//主类,含main方法,用于各类的调用与实现
public class Demo01 {
public static void main(String[] args) {
//实例化一个student对象,我们知道在用new实例化对象的时候,一定会调用类中的构造方法
Dog dog = new Dog();
}
}
------------------------------------------
运行结果:
父类Animal的无参构造方法
子类Dog的无参构造方法
可见在用new实例化对象的时候,会先调用父类的无参构造方法,再调用子类的无参构造方法。相当于:
public Dog() {
//super(); 在此处自动调用了父类的构造方法,虽然未写,但自动默认的有;
//当显式地书写出来时,子类的构造方法方法体不能在其之前。
System.out.println("子类Dog的无参构造方法");
}
当显式地书写出来时,子类的构造方法方法体不能在其之前。否则将报错。

在用new实例化对象的时候,会先调用父类的无参构造方法,再调用子类的无参构造方法
那么对于有参构造方法呢?

总结:
-
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。
-
如果父类构造器没有参数,则在子类的构造器中不需要使用
super关键字调用父类构造器,系统会自动调用父类的无参构造器。 -
如果父类的构造器带有参数,则必须在子类的构造器中显式地通过
super 关键字调用父类的构造器并配以适当的参数列表。且此时,必须父类中显式定义了无参构造方法,子类才能调用父类的无参构造方法。 -
super必须只能出现在子类的方法或构造方法中; -
super和this不能同时调用构造方法,因为用它们来调用构造方法时,都必须在构造方法方法体的第一位,同时使用就互相干涉。
5. 继承中的关键字
5.1 extends关键字
继承可以使用 extends和implements 这两个关键字来实现继承。
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
通过extends关键字标识一个类(子类)继承于类(父类).
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。(后面学完接口再扩展)
5.2 super与this关键字
super关键字:可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
package com.oop_extends.www;
//创建一个类,Person,作为父类,其下有两个子类,Teacher和Student
//不是主实现类,因此Person类不含main方法
public class Person {
//定义Persoon类中的属性name
protected String name = "人类";
//定义Person类中的一般方法print
public void print() {
System.out.println("人类中的方法");
}
}
package com.oop_extends.www;
//创建Student类,继承于Person类,是Person类的子类
//通过extends关键字,实现继承关系。继承后可操作父类中的public属性和方法
public class Student extends Person {
//定义Student类的私有属性name
private String name = "学生类";
public void testSay(String name) {
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
//定义Student类的一般方法print
public void print() {
System.out.println("学生类中的print方法");
}
public void testPrint() {
print();
this.print();
super.print();
}
}
package com.oop_extends.www;
//这是一个主类,含有main方法,用于调用各个类实现
public class Application {
public static void main(String[] args) {
//实例化一个student对象
Student student = new Student();
//调用student中的方法,区别this与super
student.testSay("奥特曼");
System.out.println("------------------");
student.testPrint();
}
}
-----------------------------------
运行结果:
奥特曼
学生类
人类
------------------
学生类中的print方法
学生类中的print方法
人类中的方法
//根据结果可以看出,this是指当前类中的属性和方法,super是指父类中的属性和方法
this与super的区别:
-
代表的对象不同:
this指调用者本身,super指父类的对象 -
使用的条件不同:
this没有继承的时候也能用,super只有在继承条件下才能使用 -
构造方法:
this()指本类的构造方法,super()指父类的构造方法
5.3 final关键字
final 关键字声明类可以把类定义为不能继承的,即最终类。
final 关键字用于修饰方法,该方法不能被子类重写。
6. 继承中的方法重写
6.1 什么是方法的重写?
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写都是方法的重写,与属性无关!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
6.2 重写的规则
-
重写是子类对父类方法的重写。如果不能继承一个类,则不能重写该类的方法。
-
方法重写,方法的方法名和参数列表必须完全相同。
-
重写后的方法的访问权限不能比父类中被重写的方法的访问权限更低,即访问权限只能扩大不能缩小。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
-
重写的方法抛出异常的范围要比被重写的方法大。即无论被重写的方法是否抛出异常,重写的方法要能够抛出任何非强制异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
-
父类的构造方法不能被重写,只能重写父类的成员方法。
-
声明为 final 的方法不能被重写。
-
声明为 static 的方法不能被重写,但是能够被再次声明。
-
子类和父类在同一个包中,那么子类可以重写父类所有的除了声明为 private 和 final 的方法。
-
子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
-
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
//创建一个类,Animal,其下属子类为Dog
class Animal {
//Animal1的成员方法
public void move() {
System.out.println("动物可以移动");
}
}
//创建Animal的子类Dog
class Dog extends Animal {
//在子类Dog中对父类Animal的成员方法move实现重写
//可以通过快捷键Alt + insert实现方法的重写
@Override //注解,相当于有功能的注释
public void move() {
System.out.println("狗可以跑和走");
}
}
//主类,含main方法,用于各类的调用与实现
public class Demo01 {
public static void main(String[] args) {
//实例化两个对象
Animal a = new Animal();
Dog b = new Dog();
//分别调用move方法,但由于子类中的方法进行了重写,所以同样地方法调用结果不同
a.move();
b.move();
Animal c = new Dog(); //相当于c是Animal类型数据,将其指向Dog型数据,因为Dog是Animal的子类,所以可以自动转换
//c本身是一个Animal类型的数据,但指向Dog型的对象,因此调用的方法也是Dog中的方法
c.move();
c.bark();//c无法调用bark方法,因为c是Animal类型的数据,没有bark方法。该语句将报错
// Dog d = new Animal(); 将会报错。相当于d是Dog类型数据,将其指向Animal型数据,因为Dog是Animal的子类,小指向大不能实现
}
}
---------------------------------------------------
运行结果:
动物可以移动
狗可以跑和走
狗可以跑和走
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("狗可以跑和走");
}
}
public class Demo01{
public static void main(String args[]){
Animal b = new Dog(); // Dog 对象
b.move(); //执行 Dog类的方法
}
}
---------------------------------------------------
运行结果:
动物可以移动
狗可以跑和走
6.3 重载与重写
方法重载
-
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
-
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
-
最常用的地方就是构造器的重载。
-
方法重载的规则:
-
被重载的方法必须改变参数列表(参数个数或类型不一样);
-
被重载的方法可以改变返回类型;
-
被重载的方法可以改变访问修饰符;
-
被重载的方法可以声明新的或更广的检查异常;
-
方法能够在同一个类中或者在一个子类中被重载。
-
无法以返回值类型作为重载函数的区分标准。
-
方法重写
-
重写是子类对父类方法的重新实现,方法重写前后名称相同,参数列表也完全相同,但方法体的内容实现不同。
-
重写后的方法的访问权限不能比父类中被重写的方法的访问权限更低,即访问权限只能扩大不能缩小。
-
重写的方法抛出异常的范围要比被重写的方法大。
-
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
| 区别点 | 重载方法 | 重写方法 |
|---|---|---|
| 参数列表 | 必须修改 | 一定不能修改 |
| 返回类型 | 可以修改 | 一定不能修改 |
| 异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
| 访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
总结
-
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成一个类的多态性的具体表现形式。
-
方法重载是一个类中定义多个方法名相同,但参数列表不同(类型不同、数目不同、次序不同等)。
-
方法重写是子类当中存在与父类中名称相同、参数列表也相同的方法。
五、多态
1.多态是什么?
多态是同一个行为具有多个不同表现形式或形态的能力。即同一个方法根据调用该方法对象的不同而采用不同的表现方式。
多态性是对象具有多种表现形式的体现。

2. 多态的优点
-
消除类型之间的耦合关系
-
可替换性
-
可扩充性
-
接口性
-
灵活性
-
简化性
-
可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
3. instanceof关键字
instanceof关键字可以判断一个数据是否是某种类型,可以用于父类子类关系的判断。
x instanceof Y // 用于判断x是否是Y的一个实例,是则为true,反之则为false // 同时还会检查x是否能转换为Y类型,不能转换则直接报错
//创建父类Aniamal
class Animal {
}
//创建子类Dog和Cat
class Dog extends Animal {
}
class Cat extends Animal {
}
// instanceof功能示例
public class Demo01{
public static void main(String[] args) {
//创建一个object类型的引用,指向Dog类性的对象
Object object = new Dog();
System.out.println(object instanceof Object); //true object引用类型为Object,指向对象类型为Dog,是Animal类的子类
System.out.println(object instanceof Animal); //true object引用类型为Object,指向对象类型为Dog,是Animal类的子类
System.out.println(object instanceof Dog); //true object引用类型为Object,指向对象类型为Dog,是Dog类的实例化对象
System.out.println(object instanceof Cat); //false object引用类型为Object,指向对象类型为Dog,不是Cat类的子类,但Object类型可以转换为Cat类型
System.out.println(object instanceof String); //false object引用类型为Object,指向对象类型为Dog,是Animal类的子类,但Object类型可以转换为String类型
System.out.println("-------------------------------------------");
//创建一个object类型的引用,指向Dog类性的对象
Animal animal = new Dog();
System.out.println(animal instanceof Object);
System.out.println(animal instanceof Animal);
System.out.println(animal instanceof Dog);
System.out.println(animal instanceof Cat);
//System.out.println(animal instanceof String);
System.out.println("-------------------------------------------");
//创建一个object类型的引用,指向Dog类性的对象
Dog dog = new Dog();
System.out.println(dog instanceof Object);
System.out.println(dog instanceof Animal);
System.out.println(dog instanceof Dog);
//System.out.println(dog instanceof Cat);
//System.out.println(dog instanceof String);
}
}
---------------------------------------------------
运行结果:
true
true
true
false
false
-------------------------------------------
true
true
true
false
-------------------------------------------
true
true
true
4. 类型转换
数值类型可以进行类型转换,引用类型也可以。对于继承来说,父类就是高类型,子类就是低类型。由低向高转可以自动转换,由高向低转换需要强制转换,转换后还可能损失部分方法。
class Animal{
//父类中定义成员方法move
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
//子类实现对父类方法的重写
public void move(){
System.out.println("狗可以跑和走");
}
//子类中特有的方法
public void bark() {
System.out.println("狗可以叫");
}
}
public class Demo01{
public static void main(String args[]){
//animal是Animal类型,却可以指向Dog对象,该对象本身是Dog类型,引用时Animal类型,由低转高,可以自动转换
//子类转为父类,向上转换,可以自动转换,但转换后会丢失自身扩展的一些方法,比如这里用Animal类型的引用animal指向Dog类对象,但animal无法直接使用Dog类中的bark方法
Animal animal = new Dog();
// 这里调用move方法,执行的是Dog类中重写之后的move方法
animal.move();
//animal.bark(); animal时Animal类型,用animal调用子类特有的方法,由高到低转换,向下转换,需要强制转换,因此该语句报错
//父类向子类转换,向下转换,需要显式强制转换:减少重复代码,方便方法的调用
((Dog)animal).bark();
Dog dog = new Dog();
dog.bark();
dog.move();
//子类转为父类,会丢失掉自身所扩展的一些方法
((Animal)dog).move(); // dog转换为Animal类型,但执行的方法仍然是Dog类中重写后的方法
//((Animal)dog).bark();
}
}
---------------------------------------------------
运行结果:
狗可以跑和走
狗可以叫
狗可以叫
狗可以跑和走
狗可以跑和走
5. 抽象类与抽象方法
接下来介绍一个新的修饰符,即abstract,用abstract修饰的类称为抽象类。用abstract修饰的方法称为抽象方法。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
-
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的声明与访问方式和普通类一样。
-
抽象类中可以有抽象方法,也可以有普通方法。但抽象方法必须在抽象类中。抽象方法的特点是用
abstract修饰,并且只有方法名,没有方法体。 -
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
-
继承抽象类的子类必须重写抽象方法,给出抽象方法的具体实现,否则子类也将声明为抽象类。
-
构造方法,类方法(静态方法)不能声明为抽象方法。
抽象类中含有构造方法吗?
每个类,包括抽象类,都含有构造方法。虽然抽象类本身是不能实例化对象的,但是抽象类是要被继承的,其子类实例化对象的时候也要先调用父类(抽象类)的构造方法。
抽象类的存在意义:
抽象类将设计和实现分离,抽象类和实现方法互不干扰。
6. 多态的实现
多态实现的三个必要条件:
-
继承
-
方法重写
-
父类引用指向子类对象
一个对象的实际类型是确定的,但是执行该对象的引用的类型可以有很多。因此可以定义一个类型为父类的引用,使其指向类型为子类的一个对象。这是实现多态的条件之一。
多态是方法的多态,属性没有多态!!!
//多态的示例
//创建一个抽象类Animal,作为父类
abstract class Animal {
//抽象方法eat,只有方法名,没有方法体,在子类中给出抽象方法的具体实现
abstract void eat();
}
//创建子类,扩展方法
class Cat extends Animal {
//重写父类的eat抽象方法
public void eat() {
System.out.println("猫吃鱼");
}
//子类特有的方法
public void work() {
System.out.println("猫的工作是捉老鼠");
}
}
class Dog extends Animal {
//重写父类的eat抽象方法
public void eat() {
System.out.println("狗吃骨头");
}
//子类特有的方法
public void work() {
System.out.println("狗的工作是看家");
}
}
public class Demo01 {
public static void main(String[] args) {
//向上转型,自动进行,用Animal类型的引用指向Cat类型的对象
Animal a = new Cat();
//调用Cat中重写后的eat方法
a.eat();
//向下转型,显式强制转换,将a引用的类型由Animal转为Cat
Cat c = (Cat) a;
//调用Cat中的work方法
c.work();
System.out.println("******多态展示******");
//多态展示,同样是调用show方法,由于传入的参数不一样,结果也不一样
//这里进行了一次自动转换,由于show方法接收的参数类型是Animal,这里传入的是Cat
show(new Cat());
System.out.println("***************");
show(new Dog());
}
public static void show(Animal a) {
//根据传入的类型,执行对应子类中重写后的eat方法
a.eat();
//使用instanceof进行类型判断
if (a instanceof Cat) {
//是Cat类型,就向下转换,强制将引用类型由Animal转为Cat
Cat c = (Cat)a;
//然后执行对应的Cat的work方法
c.work();
}else if (a instanceof Dog) {
//是Dog类型,就向下转换,强制将引用类型由Animal转为Dog
Dog c = (Dog)a;
//然后执行对应的Dog的work方法
c.work();
}
}
}
---------------------------------------------------
运行结果:
猫吃鱼
猫的工作是捉老鼠
******多态展示******
猫吃鱼
猫的工作是捉老鼠
***************
狗吃骨头
狗的工作是看家
多态的实现机制
原理也很简单,父类或者接口定义的引用变量可以指向子类或者具体实现类的实例对象,由于程序调用方法是在运行期才动态绑定的,那么引用变量所指向的具体实例对象在运行期才确定。所以这个对象的方法是运行期正在内存运行的这个对象的方法而不是引用变量的类型中定义的方法。
这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。

浙公网安备 33010602011771号