目录
一、封装
1.面向对象开发原则
高内聚,低耦合。
内聚,指一个模块内各个元素彼此结合的紧密程度;
耦合指一个软件结构内不同模块之间互连程度 的度量。
内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。
“高内聚,低耦合”的解释:
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉。
- 低耦合 :仅暴露少量的方法给外部使用,尽量方便外部调用。
2.什么是封装性?
把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或者对象开放,向没必要开放的类或者对象隐藏信息。
3.封装的实现
(1)定义
Java的封装依赖访问控制修饰符,也称为权限修饰符来控制。实现封装就是控制类或成员的可见性范围。
(2)权限修饰符
public 、 protected 、 缺省 、 private 。
(3)具体修饰的结构
外部类:public、缺省
成员变量、成员方法、构造器、成员内部类:public、protected、缺省、private
4.封装性的体现与应用
4.1 成员变量/属性私有化
(1)定义
私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能。
(2)具体操作
操作①:使用 private 修饰成员变量。
private 数据类型 变量名 ;
操作②:提供 getXxx 方法 / setXxx 方法,可以访问成员变量。
public class Animal {
private String name;
private int age;
private int id;
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;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
animal.setAge(6);
System.out.println(animal.getAge());
animal.setName("Dog A");
System.out.println(animal.getName());
animal.setId(001);
System.out.println(animal.getId());
}
}
(3)成员变量封装的好处
- 让使用者只能通过事先预定的方法来访问数据 ,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改 ,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问 方式不变的话,外部根本感觉不到它的修改。
4.2 私有化方法
(1)定义
私有化方法(Private Method)是指访问级别为private
的方法,这意味着该方法只能在其所在的类的内部访问,外部类和其他类的对象无法直接调用这个方法。私有化方法的主要目的是隐藏类的实现细节,仅暴露类的公共接口,从而提高代码的封装性和安全性。
(2)具体操作
// Animal.java
public class Animal {
// 私有化属性
private String name;
private int age;
// 构造方法
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// 公共的获取 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 > 0) { // 简单的验证,确保年龄是正数
this.age = age;
}
}
// 私有化方法,用于内部逻辑
private void makeSound() {
System.out.println("Animal is making a sound");
}
// 公共方法,调用私有方法
public void performSound() {
makeSound();
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal("Dog", 5);
// 使用公共的 getter 和 setter 方法访问私有属性
System.out.println("Name: " + animal.getName());
System.out.println("Age: " + animal.getAge());
animal.setName("Cat");
animal.setAge(3);
System.out.println("Updated Name: " + animal.getName());
System.out.println("Updated Age: " + animal.getAge());
// 调用公共方法,它内部会调用私有方法 makeSound
animal.performSound();
}
}
(3)私有化方法的好处
- 信息隐藏:隐藏类的内部实现细节,防止外部代码依赖内部逻辑,从而提高代码的灵活性和可维护性。
- 数据保护:保护类内部的数据和状态,防止外部代码直接修改或访问这些数据,增强类的安全性。
- 简化接口:通过私有化方法,可以将类的接口简化为仅暴露必要的公共方法,减少外部代码的复杂性。
二、继承
1.继承的定义
继承是指一个类(子类)从另一个类(父类)获取属性和方法的机制。子类不仅可以继承父类的属性和方法,还可以增加新的属性和方法,或者修改父类的方法。
①自上而下的思想
自上而下的继承设计方法适用于从抽象概念出发,逐步具体化实现的场景,适合系统架构设计和模块化编程。
②自下而上的思想
自下而上的继承设计方法则适用于从具体实现出发,逐步抽象出通用类的场景,适合代码重构和优化。
2.继承的好处
- 代码重用:通过继承,子类可以复用父类的代码,减少重复代码。
- 层次结构:继承帮助我们构建清晰的类层次结构,表达类与类之间的关系。
- 多态性:继承是实现多态性的基础,使得程序更加灵活和可扩展。
3.继承的注意事项
- 避免过深的继承层次:过深的继承层次会导致类之间的耦合度增加,维护困难。
- 优先考虑组合而非继承:在某些情况下,组合(使用现有类的实例作为新类的成员)比继承更灵活。
- 子类与父类的强关联:子类对父类有强依赖,父类的修改可能会影响子类的行为。
4.语法格式
通过 extends
关键字,可以声明一个类B继承另外一个类A,定义格式如下:
[修饰符] class 类A {
...
}[修饰符] class 类B extends 类A {
...
}
类B,称为子类、派生类(derived class)、SubClass
类A,称为父类、超类、基类(base class)、SuperClass
5.具体操作
public class Person {
String name;
int id;
public void write(){
System.out.println("id为:"+id+"的"+name+"在写东西");
}
}
public class Students extends Person{
int page;
public void doHomework(){
System.out.println("学生已经写了"+page+"篇作业");
}
}
public class TestStudents {
public static void main(String[] args) {
Students s1 = new Students();
s1.name = "Theodore_1022";
s1.id = 001;
s1.page = 17;
s1.doHomework();
s1.write();
}
}
/*
运行结果:
学生已经写了17篇作业
id为:1的Theodore_1022在写东西
*/
6.说明
- 子类会继承父类所有的实例变量和实例方法
- 子类不能直接访问父类中私有的(private)的成员变量和方法
- 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”
- Java支持多层继承(继承体系)
- 一个父类可以同时拥有多个子类
- Java只支持单继承,不支持多重继承
三、多态
1.定义
多态性指的是在不同情况下,调用同一个方法可以表现出不同的行为。这通常通过继承和接口实现,即父类引用指向子类对象,并在运行时根据实际对象的类型调用对应的方法。
2.对象的多态性
在Java中,子类的对象可以替代父类的对象使用。所以,一个引用类型变量可能指向(引用)多种不同类型的对象。
格式:(父类类型:指子类继承的父类类型,或者实现的接口类型)
父类类型 变量名 = 子类对象;
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
3.多态的理解
Java引用变量有两个类型:编译时类型和运行时类型。
编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋值给该变量的类型决定。
简称:编译时,看左边;运行时,看右边。
-
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
-
多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
解释“看左边”和“看右边”
看左边:
- 指的是编译时类型,也就是父类的引用。
- 父类引用变量只能调用父类中定义的方法,不能调用子类特有的方法。
- 编译器在编译时检查的方法和属性,必须在父类中有定义。
看右边:
- 指的是运行时类型,也就是实际赋值给变量的子类对象。
- 实际运行的是子类重写父类的方法,具体的行为由子类对象决定。
多态的使用前提:① 类的继承关系 ② 方法的重写
4.具体操作
// 父类
public class Animal {
public void makeSound() {
System.out.println("Animal is making a sound");
}
}
// 子类
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog is barking");
}
public void fetch() {
System.out.println("Dog is fetching a ball");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog(); // 编译时类型是 Animal,运行时类型是 Dog
myDog.makeSound(); // 调用的是 Dog 的 makeSound 方法,输出: Dog is barking
// myDog.fetch(); // 编译错误,父类引用不能调用子类特有的方法
}
}
5.三种常见情况举例
5.1 方法内局部变量的赋值
在方法内部使用父类类型的局部变量,可以赋值为不同的子类对象。
在demonstratePolymorphism
方法中,animal
是父类Animal
类型的局部变量。我们可以在同一个方法中将其赋值为不同的子类对象(Dog
和Cat
),并调用对应的方法,体现了多态性。
public class Animal {
public void makeSound() {
System.out.println("Animal is making a sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog is barking");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat is meowing");
}
}
public class Test {
public void demonstratePolymorphism() {
Animal animal; // 声明父类类型的局部变量
animal = new Dog(); // 赋值为 Dog 对象
animal.makeSound(); // 输出: Dog is barking
animal = new Cat(); // 赋值为 Cat 对象
animal.makeSound(); // 输出: Cat is meowing
}
public static void main(String[] args) {
Test test = new Test();
test.demonstratePolymorphism();
}
}
5.2 方法的形参声明
方法参数使用父类类型,可以接收不同的子类对象。
在makeAnimalSound
方法中,参数类型是Animal
,可以传入Dog
或Cat
对象,调用时表现出不同的行为。这就是方法参数声明体现多态的例子。
public class Animal {
public void makeSound() {
System.out.println("Animal is making a sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog is barking");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat is meowing");
}
}
public class Test {
public void makeAnimalSound(Animal animal) {
animal.makeSound(); // 调用实际对象的 makeSound 方法
}
public static void main(String[] args) {
Test test = new Test();
Animal myDog = new Dog();
Animal myCat = new Cat();
test.makeAnimalSound(myDog); // 输出: Dog is barking
test.makeAnimalSound(myCat); // 输出: Cat is meowing
}
}
5.3 方法返回值类型
方法返回父类类型,可以返回不同的子类对象。
在AnimalFactory
类中,createAnimal
方法的返回类型是Animal
,但是根据传入的参数,它可以返回不同的子类对象。这体现了方法返回值类型的多态性。
public class Animal {
public void makeSound() {
System.out.println("Animal is making a sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog is barking");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat is meowing");
}
}
public class AnimalFactory {
// 返回类型是父类 Animal,可以返回任何子类对象
public Animal createAnimal(String type) {
if (type.equals("Dog")) {
return new Dog();
} else if (type.equals("Cat")) {
return new Cat();
} else {
return new Animal();
}
}
public static void main(String[] args) {
AnimalFactory factory = new AnimalFactory();
Animal animal1 = factory.createAnimal("Dog");
animal1.makeSound(); // 输出: Dog is barking
Animal animal2 = factory.createAnimal("Cat");
animal2.makeSound(); // 输出: Cat is meowing
}
}
6.多态的好处和弊端
好处:变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
弊端:一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
7.虚方法调用
- 在Java中虚方法是指在编译阶段不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。
- 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
- Java中所有的非静态方法都是虚方法,除非它们被声明为
final
、private
或static
,这些方法不能被子类重写,因此编译时就能确定调用哪个方法。
public class Animal {
public void makeSound() {
System.out.println("Animal is making a sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog is barking");
}
public void fetch() {
System.out.println("Dog is fetching a ball");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat is meowing");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
// 虚方法调用
myDog.makeSound(); // 输出: Dog is barking
myCat.makeSound(); // 输出: Cat is meowing
}
}
在这个例子中:
Animal myDog = new Dog();
:编译时类型是Animal
,运行时类型是Dog
。Animal myCat = new Cat();
:编译时类型是Animal
,运行时类型是Cat
。
尽管编译时类型都是Animal
,但调用makeSound
方法时,JVM根据实际对象类型Dog
和Cat
调用相应的方法。这就是虚方法调用的体现。
虚方法调用的优点
- 支持多态:虚方法调用是实现多态的基础,使得一个父类引用可以指向不同子类对象,并调用子类重写的方法。
- 代码复用和灵活性:通过虚方法调用,可以编写更加通用和灵活的代码,提高代码复用性。
- 可扩展性:通过虚方法调用,可以在不修改现有代码的情况下添加新的子类,并正确调用新子类的方法。
7.成员变量没有多态性
在Java中,多态性主要体现在方法上,而成员变量不具备多态性。这意味着当我们在父类和子类中定义同名的成员变量时,引用类型变量指向的是父类类型或子类类型,并不影响成员变量的访问。成员变量的绑定在编译时就确定了,不能在运行时根据对象的实际类型来决定。
public class Animal {
public String name = "Animal";
public void makeSound() {
System.out.println("Animal is making a sound");
}
}
public class Dog extends Animal {
public String name = "Dog"; // 子类定义了一个同名的成员变量
@Override
public void makeSound() {
System.out.println("Dog is barking");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
// 调用方法时,表现出多态性
myDog.makeSound(); // 输出: Dog is barking
// 访问成员变量时,不表现出多态性
System.out.println(myDog.name); // 输出: Animal
}
}
在这个例子中:
Animal myDog = new Dog();
:编译时类型是Animal
,运行时类型是Dog
。myDog.makeSound();
:调用的是Dog
类中的makeSound
方法,表现出多态性。System.out.println(myDog.name);
:访问的是Animal
类中的name
成员变量,输出Animal
,没有表现出多态性。
即使在运行时,myDog
的实际类型是Dog
,但由于成员变量的绑定在编译时就已经确定了,因此myDog.name
访问的是父类Animal
中的name
变量,而不是子类Dog
中的name
变量。
解释
-
方法调用的多态性:
- 方法调用是动态绑定的,实际调用的方法取决于对象的运行时类型。
- 在上述例子中,
myDog.makeSound()
调用的是Dog
类的makeSound
方法,因为在运行时myDog
是Dog
类型。
-
成员变量访问的静态绑定:
- 成员变量访问是静态绑定的,访问哪个成员变量在编译时就已经确定了。
- 在上述例子中,
myDog.name
在编译时已经被绑定到Animal
类的name
变量,因此即使在运行时myDog
是Dog
类型,访问的仍然是Animal
类的name
变量。
8.向上转型与向下转型
8.1 定义
首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间
,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用
子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过
。
-
向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
-
此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
-
但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
-
此时,一定是安全的,而且也是自动完成的
-
-
向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型
-
此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
-
但是,运行时,仍然是对象本身的类型
-
不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
-
8.2 格式
向上转型:自动完成
向下转型:(子类类型)父类变量
public class ClassCastTest {
public static void main(String[] args) {
//没有类型转换
Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog
//向上转型
Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
pet.setNickname("小白");
pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouse
Dog d = (Dog) pet;
System.out.println("d.nickname = " + d.getNickname());
d.eat();//可以调用eat方法
d.watchHouse();//可以调用子类扩展的方法watchHouse
Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
//这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
}
}
8.3 instanceof 关键字
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验。如下代码格式:
//检验对象a是否是数据类型A的对象,返回值为boolean型
对象a instanceof 数据类型A
说明:
-
只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
-
如果对象a属于类A的子类B,a instanceof A值也为true。
-
要求对象a所属的类与类A必须是子类和父类的关系,否则编译错误。
// 定义父类 Person
class Person {
private String name; // 成员变量 name,表示人的名字
// 设置名字的方法
public void setName(String name) {
this.name = name;
}
// 获取名字的方法
public String getName() {
return name;
}
// 定义一个工作方法,可以被子类重写
public void work() {
System.out.println(name + " is working.");
}
}
// 定义子类 Doctor,继承自 Person
class Doctor extends Person {
// 重写父类的 work 方法
@Override
public void work() {
System.out.println(getName() + " is treating patients.");
}
// 子类特有的方法
public void performSurgery() {
System.out.println(getName() + " is performing surgery.");
}
}
// 定义子类 Teacher,继承自 Person
class Teacher extends Person {
// 重写父类的 work 方法
@Override
public void work() {
System.out.println(getName() + " is teaching students.");
}
// 子类特有的方法
public void gradePapers() {
System.out.println(getName() + " is grading papers.");
}
}
public class TestInstanceof {
public static void main(String[] args) {
// 创建一个 Person 类型的数组,长度为 2
Person[] people = new Person[2];
// 向上转型:将 Doctor 对象赋值给 Person 类型的数组元素
people[0] = new Doctor();
people[0].setName("Dr. Smith"); // 设置名字为 Dr. Smith
// 向上转型:将 Teacher 对象赋值给 Person 类型的数组元素
people[1] = new Teacher();
people[1].setName("Ms. Johnson"); // 设置名字为 Ms. Johnson
// 遍历数组
for (int i = 0; i < people.length; i++) {
// 调用 work 方法,这是一个虚方法调用,实际调用的是子类的方法
people[i].work();
// 检查当前对象是否是 Doctor 类型
if (people[i] instanceof Doctor) {
Doctor doctor = (Doctor) people[i]; // 向下转型,将 Person 类型转换为 Doctor 类型
doctor.performSurgery(); // 调用 Doctor 类特有的方法 performSurgery
}
// 检查当前对象是否是 Teacher 类型
else if (people[i] instanceof Teacher) {
Teacher teacher = (Teacher) people[i]; // 向下转型,将 Person 类型转换为 Teacher 类型
teacher.gradePapers(); // 调用 Teacher 类特有的方法 gradePapers
}
}
}
}
总结
本篇详细的对Java的三个特性:封装、继承与多态进行了讲解,便于回顾和理解。文章内容部分源自网络,如有侵权,请联系作者删除,谢谢!
标签:Java,子类,多态,Dog,Animal,父类,方法,public,三大 From: https://blog.csdn.net/Theodore_1022/article/details/139507406