一、封装
1.1 封装的概述
1.1.1 什么是封装
封装:指的是将数据和对数据的操作方法封装在一个单独的实体中,使其对外部不可见,并通过公共的接口来访问和操作数据。
封装的目的是隐藏数据的具体实现细节,使得对象的使用者只能通过事先定义好的接口来访问和操作数据,无需了解内部具体实现细节。这样可以提高代码的安全性、可维护性和可扩展性。
1.1.2 访问修饰符
访问修饰符:是一种用于控制类的成员(属性和方法)可被访问的范围和权限的关键字。
Java中有四种访问修饰符:
① public(公共访问修饰符):被public修饰的成员可以被任何类访问,无论是当前类、同一包中的类还是不同包中的类。
② 默认(默认访问修饰符):如果没有明确指定访问修饰符,默认为默认访问修饰符。被默认修饰的成员可以在当前类和同一包中的类中被访问,但对于不同包中的类是不可访问的。
③ protected(受保护访问修饰符):被protected修饰的成员可以在当前类、同一包中的类以及不同包中的子类中被访问,对于不同包中的非子类是不可访问的。
④ private(私有访问修饰符):被private修饰的成员只能在当前类中被访问,其他类无法直接访问,但可以通过公共的setter和getter方法进行访问。
下面是各种访问修饰符的可访问性总结:
1.1.3 setter和getter方法
所有的类的属性一般情况下最好使用private进行修饰,如果需要对属性进行获取或者是修改,那么则需要通过getter/setter方法对属性进行封装。
在创建类的时候,所有的属性都需要设置setter/getter方法进行封装:
package test;
// 创建一个书本类
public class Book {
String title; // 书名
int pageNum; // 页数
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
// 判断页数是否正确
if(pageNum<200){
System.out.println("书籍页数有误,系统将自动将页数改为200页");
this.pageNum = 200;
}else{
this.pageNum = pageNum;
}
}
// 输出书本的细节
public void detail(){
System.out.println(this.title+"的页数是:"+this.pageNum+"页");
}
}
在代码调用的时候,禁止直接通过对象.属性进行修改或者调用,需要通过setter/getter方法修改或者调用。
package test;
// 测试类
public class BookTest {
public static void main(String[] args) {
// 创建《老人与海》
Book book1 = new Book();
// 设置书名
book1.setTitle("《老人与海》");
// 设置页数,页数<200,setPageNum方法会对其进行判断修改
book1.setPageNum(189);
// 输出书本的细节
book1.detail();
}
}
运行结果如下:
1.1.3 封装的优点
① 数据隐藏:封装将数据隐藏在类的内部,防止外部直接访问和修改数据,确保数据的安全性和一致性。
② 简化使用:封装提供了公共的接口,使得使用者可以简单、直观地访问和操作对象,无需了解内部的实现细节。
③ 隔离变化:封装将类的实现细节与外部的代码隔离开来,当类的内部实现发生变化时,外部的代码不受影响。
④ 提高代码的可维护性和可扩展性:封装使得修改类的内部实现只需修改内部的代码,而不需要修改外部的代码,提高了代码的可维护性和可扩展性。
二、继承
2.1 继承的概述
2.1.1 生活中的继承
生活中知道的最清楚继承莫过于是王位继承了,下一代皇帝可以继承上一代皇帝的东西。生活中的继承允许信息、财富、技能和价值观在家族、职业、社会和文化中得以传递和延续。在现实生活中也扮演着重要的角色。
2.1.2 程序中的继承
在编程中,继承是一种面向对象的重要概念,允许一个类(称为子类或派生类)继承另一个类(称为父类、基类或超类)的属性和方法。通过继承,子类可以获得父类的特性,并可以添加自己的额外功能或修改继承的行为。
2.2 继承的使用
2.2.1 基本使用
继承主要是通过extends
关键字来实现:
package test;
// 游戏的公共(父类)类
public class GameCommon {
String kinds; // 种类
String name; // 名字
int pow; // 攻击力
int blood; // 血量
int defence; // 防御力`
String specialEffect; // 特效
// 无参构造
public GameCommon(){
}
// 有参构造
public GameCommon(String kinds, String name, int pow, int blood, int defence, String specialEffect) {
this.kinds = kinds;
this.name = name;
this.pow = pow;
this.blood = blood;
this.defence = defence;
this.specialEffect = specialEffect;
}
public String getKinds() {
return kinds;
}
public void setKinds(String kinds) {
this.kinds = kinds;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPow() {
return pow;
}
public void setPow(int pow) {
this.pow = pow;
}
public int getBlood() {
return blood;
}
public void setBlood(int blood) {
this.blood = blood;
}
public int getDefence() {
return defence;
}
public void setDefence(int defence) {
this.defence = defence;
}
public String getSpecialEffect() {
return specialEffect;
}
public void setSpecialEffect(String specialEffect) {
this.specialEffect = specialEffect;
}
}
package test;
// 英雄类
public class Heroes extends GameCommon{
String dressUp; // 皮肤
String weapon; // 武器
String[] skill; // 技能
// 无参构造
public Heroes(){
}
// 有参构造
public Heroes(String dressUp, String weapon) {
this.dressUp = dressUp;
this.weapon = weapon;
}
// 有参构造
public Heroes(String kinds, String name, int pow, int blood, int defence, String specialEffect, String dressUp, String weapon) {
super(kinds, name, pow, blood, defence, specialEffect);
this.dressUp = dressUp;
this.weapon = weapon;
}
public String getDressUp() {
return dressUp;
}
public void setDressUp(String dressUp) {
this.dressUp = dressUp;
}
public String getWeapon() {
return weapon;
}
public void setWeapon(String weapon) {
this.weapon = weapon;
}
public void setSkill(){
if(this.kinds=="英雄"){
this.skill = new String[]{"保护","攻击","推塔","逃跑","打小兵","打野怪","推水晶"};
}else {
System.out.println("对不起,您的身份不是英雄!");
}
}
public void getSkill(){
System.out.println("恭喜"+this.name+"你已获得以下技能:");
for(int i=0;i<this.skill.length;i++){
System.out.print(this.skill[i] + ", ");
}
}
public void move(){
System.out.println(this.name+"在移动!");
}
}
package test;
// 测试类
public class GameTest {
public static void main(String[] args) {
// 继承父类的属性
Heroes hero1 = new Heroes("英雄","虞姬",5,100,6,"落花","霸王别姬","弓弩");
// 继承父类的方法
hero1.setDefence(5);
// 调用自己的方法
hero1.setSkill();
hero1.getSkill();
}
}
运行结果:
2.2.2 继承中构造方法的使用
子类继承父类之后,子类在被new出来的时候,是需要调用父类的构造方法的。默认调用的是无参的构造方法。
一旦父类提供一个带有参数的构造方法后,默认的无参构造方法就会失效。解决方案如下:
方案1:父类必须要写一个无参的构造方法。
方案2:父类没有无参的构造方法,那么在子类中的构造方法中,就必须要通过super调用父类的构造方法。而且必须super构造方法必须要写在第一行,其他的代码必须要在super之后。
2.3 方法的重写和重载
2.3.1 方法的重写(覆盖)
它允许子类重新定义继承自父类的方法。当子类继承自父类时,子类可以选择重写父类的方法以改变其行为,以满足子类的特定需求。方法的重写也被称为方法的覆盖或方法的重新定义。
方法的重写具有以下特点:
① 继承关系:方法的重写是基于类之间的继承关系。子类可以继承父类的方法,并在需要时对其进行重写。
② 方法签名:重写的方法在子类中必须具有与父类方法相同的名称、返回类型和参数列表。方法签名指定了方法的名称和参数,它是识别方法的重要特征。
③ 访问修饰符:重写的方法可以具有与父类方法相同或更高的访问修饰符。子类可以将父类方法改为更具体的访问级别,但不能更改为更宽松的访问级别。
④ @Override注解:Java中,可以使用@Override
注解来标记重写的方法。这样做有助于提高代码的可读性,并且如果不小心错误地重写了一个方法,编译器会给出警告。
以下是一个方法重写的示例:
package homework.monster;
// 怪物父类
public class Monster {
String name; // 怪物名字
int blood; // 生命值
int power; // 攻击力
// 无参构造
public Monster(){
}
// 有参构造
public Monster(String name, int blood, int power) {
this.name = name;
this.blood = blood;
this.power = power;
}
// 攻击方法
public void attack(){
System.out.println("怪物"+this.name+"发起进攻!");
System.out.println("当前生命值:"+this.blood);
System.out.println("攻击力是:"+this.power);
}
// 移动方法
public void move(){
System.out.println("我是"+this.name+"开始移动");
}
}
package homework.monster;
// 蛇类
public class Snake extends Monster{
public Snake(String name, int blood, int power) {
super(name, blood, power);
}
@Override
// 重写父类攻击方法
public void attack(){
System.out.println("怪物"+super.name+"发起进攻!");
System.out.println("当前生命值:"+super.blood);
if(super.blood<10){
System.out.println("生命值小于10,发动技能进行补血……");
super.blood += 20;
System.out.println("当前生命值:"+super.blood);
}
System.out.println("攻击力是:"+this.power);
}
@Override
// 重写父类移动方法
public void move(){
System.out.println("我是"+this.name+", 我走S型路线。");
}
}
package homework.monster;
// 测试类
public class MonsterTest {
public static void main(String[] args) {
// 创建蛇妖
Snake snake = new Snake("蛇妖", 5,20);
snake.attack();
snake.move();
System.out.println("=======================================");
}
}
运行结果:
2.3.2 方法的重载
它允许在同一个类中定义多个具有相同名称但参数列表不同的方法。方法重载通过参数的类型、数量或顺序的不同来区分方法,使得可以根据不同的需求调用适合的方法。
方法重载具有以下特点:
① 方法名称相同:重载的方法必须具有相同的名称。
② 参数列表不同:重载的方法必须具有不同的参数列表。参数列表可以通过参数的类型、数量或顺序的不同来区分方法。
③ 返回类型可以相同也可以不同:重载的方法可以具有相同的返回类型,也可以具有不同的返回类型。返回类型通常不是重载方法的区分标准。
④ 与访问修饰符和异常无关:重载的方法可以具有不同的访问修饰符(如public、private、protected)和不同的抛出异常,这些因素不影响方法的重载。
以下是一个方法重写的示例:
package test;
// 加法类
public class MathUtil {
// 加法
public int add(int a, int b) {
return a + b;
}
// 重载加法方法
public double add(double a, double b) {
return a + b;
}
// 重载加法方法
public int add(int a, int b, int c) {
return a + b + c;
}
}
package test;
// 测试类
public class TestMathUtil {
public static void main(String[] args) {
MathUtil math = new MathUtil();
int sum1 = math.add(2, 3);
double sum2 = math.add(2.5, 3.7);
int sum3 = math.add(2, 3, 4);
// 输出
System.out.println("两个整数相加:"+sum1); // Output: 5
System.out.println("两个小数相加:"+sum2); // Output: 6.2
System.out.println("三个整数相加:"+sum3); // Output: 9
}
}
2.4 super关键字
是Java中的一个关键字,用于引用父类(超类)的成员(方法、属性、构造函数)。
2.4.1 super关键字的使用
① 调用父类的构造函数:在子类的构造函数中,可以使用super
关键字来调用父类的构造函数。这样可以在创建子类对象时,先执行父类的初始化操作,确保子类对象具有正确的初始状态。
public class Child extends Parent {
public Child() {
super(); // 调用父类的无参构造函数
}
}
Child
类的构造函数通过super()
调用了父类Parent
的无参构造函数。
注意:如果子类的构造函数没有显式调用super()
或指定其他父类构造函数,编译器会自动插入一个默认的super()
调用,以调用父类的无参构造函数。
② 调用父类的成员:在子类中,可以使用super
关键字来引用父类的成员(方法或属性),即使子类中存在同名的成员。通过super
关键字,可以在子类中访问父类的实现或继承的成员。
public class Parent {
public void print() {
System.out.println("Parent class");
}
}
public class Child extends Parent {
public void print() {
super.print(); // 调用父类的print()方法
System.out.println("Child class");
}
}
super
关键字的使用可以帮助在继承关系中正确地处理父类和子类之间的关系。它提供了一种方式来访问父类的成员或调用父类的构造函数,从而实现代码的灵活性和可复用性。
2.4.2 super关键字和this关键字
super关键字(引用父类的成员或调用父类的构造函数):
① super
关键字用于在子类中引用父类的成员(方法、属性、构造函数)。
② 在子类中,可以使用super
关键字来调用父类的构造函数,以执行父类的初始化操作。
③ 在子类中,可以使用super
关键字来调用父类的方法或访问父类的属性,即使子类中存在同名的成员。
this关键字(引用当前对象的成员或调用同一类中的其他构造函数或方法):
① this
关键字用于在类内部引用当前对象的成员(方法、属性、构造函数)。
② 在类中,可以使用this
关键字来引用当前对象的成员,以区分成员变量和局部变量之间的命名冲突。
③ 在构造函数中,可以使用this
关键字来调用同一类中的其他构造函数,以实现构造函数的重载和代码的复用。
④ 在方法中,可以使用this
关键字来调用当前对象的其他方法。
三、多态
3.1 多态的概述
指的是通过统一的接口或抽象类,使用不同的实现类对象来实现同一个操作或方法调用,从而实现代码的灵活性、可扩展性和可重用性。
3.2 多态的要素和格式
多态的要素包括继承关系、方法重写和父类引用指向子类对象。实现多态的基本格式如下:
① 继承关系:存在父类和子类之间的继承关系。
class Parent {
// 父类的成员和方法
}
class Child extends Parent {
// 子类继承父类并可以扩展或重写父类的成员和方法
}
② 方法重写:子类重写(覆盖)继承自父类的方法,以实现特定的行为。
class Parent {
public void method() {
// 父类的方法实现
}
}
class Child extends Parent {
@Override
public void method() {
// 子类重写父类的方法实现
}
}
③ 父类引用指向子类对象:使用父类的引用变量来引用子类的对象,实现多态。
Parent obj = new Child(); // 父类引用指向子类对象
在上述代码中,Parent
是父类,Child
是子类,子类继承了父类并重写了父类的方法。通过创建一个父类引用变量obj
,并将其指向子类对象,实现了多态。通过该父类引用变量,可以调用父类中定义的方法,同时根据实际的对象类型,执行子类中重写的方法。
3.3 多态的使用
多态的使用主要涉及父类引用指向子类对象,并通过该引用调用方法或访问成员。下面是多态的具体使用方法:
① 创建父类引用变量,并指向子类对象:
Parent obj = new Child(); // 父类引用指向子类对象
在上述代码中,Parent
是父类,Child
是子类。通过创建一个父类引用变量obj
,并将其指向子类对象new Child()
,实现了多态。这样可以通过父类引用obj
来操作子类对象。
② 调用父类中定义的方法:
obj.method(); // 调用父类的方法
通过父类引用obj
调用父类中定义的方法method()
,实际执行的是子类中重写的方法。这是因为引用变量obj
指向的是子类对象,所以会根据对象的实际类型调用相应的方法。
③ 访问父类中的成员:
int value = obj.property; // 访问父类的成员属性
通过父类引用obj
访问父类中的成员属性property
,可以直接访问父类中定义的成员。由于引用变量obj
指向的是子类对象,所以可以访问子类中继承的父类成员。
需要注意的是,通过父类引用调用的方法和访问的成员都受限于父类中声明的方法和成员。如果子类中有新增的方法或成员,父类引用无法直接访问。如果需要访问子类新增的方法或成员,可以使用类型转换将父类引用转换为子类引用。
多态的使用可以使代码更加灵活,可以根据需要在不同的子类对象之间切换,并通过统一的接口调用相同的方法。这样可以实现代码的重用性和可维护性,并且在需要添加新的子类时,不需要修改已有的代码,只需要通过添加新的子类来扩展功能。
以上的内容,如有不正确的地方,请各位大佬指出,我会加以改正!