面向对象进阶(包、权限修饰符、抽象类、接口)
1.包
什么是包?
包是用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护。
建包的语法格式:package 公司域名倒写.技术名称。包名全部英文小写且有意义。
建包语句必须在第一行,一般IDEA工具会帮助创建
导包
相同包下的类可以直接访问,不同包下的类必须导包才可以使用。
导包格式:import 包名.类名;
若一个类中需要用到不同包下的两个同名类,那么默认只能导入一个类,另一个类要带包名访问。
示例代码如下:
现有一个包com.itheima.d1_package,包下有User类和Test类,两个包com.itheima.d1_package.testanotherpackage和com.itheima.d1_package.testanotherpackage2,每个包下均有学生类Student。
packagecom.itheima.d1_package; // 导包importcom.itheima.d1_package.testanotherpackage.Student; //import com.itheima.d1_package.testanotherpackage2.Student; // 报错,在实例化Student对象时会引起歧义,所以同名包只能导入一个,其他的用全包名.类名的形式访问,无需导入publicclassTest { publicstaticvoidmain(String[] args) { // 相同包下的类可以直接互相访问System.out.println(User.onlineNumber); // 不同包下的类必须先进行导包,才可以访问Studentstu=newStudent(); // 若一个类中需要用到不同包下的两个同名类,那么默认只能导入一个类,另一个类要带包名访问com.itheima.d1_package.testanotherpackage2.Studentstu2=newcom.itheima.d1_package.testanotherpackage2.Student(); } }
2.权限修饰符
什么是权限修饰符?
权限修饰符:是用来控制一个成员能够被访问的范围。
可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。
权限修饰符的分类和具体作用范围:
权限修饰符:有四种作用范围由小到大(private-->缺省-->protected-->public)
权限修饰符
修饰符 |
同一类 |
同一包下不同类 |
不同包下的子类 |
不同包下无关系类 |
private |
√ |
|
|
|
缺省 |
√ |
√ |
|
|
protected |
√ |
√ |
√ |
|
public |
√ |
√ |
√ |
√ |
现有如下图所示结构的包与类,分析权限修饰符的作用范围。
示例代码如下:
同一类
/*** 同一类*/publicclassFather { /*1.定义私有的成员:private 允许在本类中访问*/privatevoidprivateMethod() { System.out.println("-----private-----"); } /*2.定义缺省的成员 允许在同一类、同一包下不同类中访问(包访问权限)*/voidmethod() { System.out.println("-----缺省-----"); } /*3.定义受保护的成员:protected 允许在同一类、同一包下不同类、不同包下的子类中访问*/protectedvoidprotectedMethod() { System.out.println("-----protected-----"); } /*4.定义公开的成员:public 允许在同一类、同一包下不同类、不同包下的子类、不同包下的无关类(任何地方)中访问*/publicvoidpublicMethod() { System.out.println("-----public-----"); } publicstaticvoidmain(String[] args) { Fatherfather=newFather(); father.privateMethod(); father.method(); father.protectedMethod(); father.publicMethod(); } }
同一包下的不同类
/*** 同一包下的不同类*/publicclassTest { publicstaticvoidmain(String[] args) { Fatherfather=newFather(); // father.privateMethod(); // 报错,private定义的成员只允许在本类中访问father.method(); father.protectedMethod(); father.publicMethod(); } }
不同包下的无关类
// 相同包下的类可以直接访问,不同包下的类必须导包才可以使用importcom.itheima.d2_modifier.Father; /*** 不同包下的无关类*/publicclassTest2 { publicstaticvoidmain(String[] args) { Fatherfather=newFather(); // father.privateMethod(); // 报错,private定义的成员只允许在本类中访问// father.method(); // 报错,权限修饰符缺省时定义的成员只允许在本类、同一包下的其他类中访问// father.protectedMethod(); // 报错,protected定义的成员只允许在本类、同一包下的其他类、不同包下的子类中访问father.publicMethod(); } }
不同包下的子类
importcom.itheima.d2_modifier.Father; /*** 不同包下的子类*/publicclassSonextendsFather { publicstaticvoidmain(String[] args) { Fatherfather=newFather(); // father.privateMethod(); // 报错,private定义的成员只允许在本类中访问// father.method(); // 报错,权限修饰符缺省时定义的成员只允许在本类、同一包下的其他类中访问// father.protectedMethod(); // 报错,protected定义的成员允许在同一类、同一包下不同类、不同包下的子类中访问// 不同包下的子类访问指的是不同包下的子类中的子类对象可以访问父类对象成员,而不是子类中的父类对象访问父类成员Sonson=newSon(); son.protectedMethod(); father.publicMethod(); } }
注:protected定义的成员允许不同包下的子类访问指的是不同包下的子类中的子类对象可以访问父类对象成员,而不是子类中的父类对象访问父类成员或非子类中的子类对象访问父类成员。
定义成员(方法,成员变量,构造器等)一般满足如下要求:
①成员变量一般私有。
②方法一般公开。
③如果该成员只希望本类访问,使用private修饰。
④如果该成员只希望本类,同一个包下的其他类和子类访问,使用protected修饰。
3.final
final的作用
final关键字是最终的意思,可以修饰方法、变量、类。
修饰方法:表明该方法是最终方法,不能被重写。
修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)。
修饰类:表明该类是最终类,不能被继承。
示例代码如下:
修饰类与方法
publicclassTest { publicstaticvoidmain(String[] args) { // 1.修饰类:表明该类是最终类,不能被继承// 2.修饰方法:表明该方法是最终方法,不能被重写 } } //class Wolf extends Animal { // 报错,修饰类:表明该类是最终类,不能被继承。//}////final class Animal {//}//class Student extends People {// public void eat() { // 报错,修饰方法:表明该方法是最终方法,不能被重写// System.out.println("学生吃东西");// }//}////class People {// public final void eat() {// System.out.println("人吃东西");// }//}
修饰变量
publicclassTest2 { publicstaticfinalStringname="张三"; // public static final修饰分变量称为常量publicfinalintage=10; // 使用final必须要赋初值,否则报错publicstaticvoidmain(String[] args) { // 1.局部变量finaldoublerate=1.5; // rate = 1.2; // 报错,修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)// 2.成员变量// 2.1静态成员变量// name = "李四"; // 报错,修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)// 2.2实例成员变量Test2test2=newTest2(); // test2.age = 20; // 报错,修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次) } // public static void buy(final double z) {// z = 0.1; // 报错,参数传入时是第一次为z赋值,此时属于第二次赋值了// }}
final修饰变量的注意事项:
final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
常量
常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。
常量命名规范:英文单词全部大写,多个单词下划线连接起来。
常量的执行原理
在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量,这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。
常量常做信息标志和分类,提高代码可读性。
4.枚举
枚举的概述
枚举是Java中的一种特殊类型。
枚举的作用:做信息的标志和信息的分类。
枚举的格式:
将上图中的示例代码反编译后观察枚举的特征:
枚举特征:
枚举类都是继承了枚举类型:java.lang.Enum。
枚举都是最终类,不可以被继承。
构造器都是私有的,枚举对外不能创建对象。
枚举类的第一行默认都是罗列枚举对象的名称的。
枚举类相当于是多例模式。
注:反编译的命令是javapxxx.class
5.抽象类
5.1抽象类概念
在Java中abstract是抽象的意思,如果一个类中的某个方法的具体实现不能确定,就可以声明成abstract修饰的抽象方法(不能写方法体),这个类必须用abstract修饰,被称为抽象类。
抽象类、抽象方法的格式如下图所示。
注:抽象方法只有方法签名,不能声明方法体。
一个类中如果定义了抽象方法,那么这个类也必须声明为抽象类,否则报错。
一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
抽象类的使用场景
抽象类可以理解成不完整的设计图,一般作为父类,让子类继承。
当父类知道子类一定要完成某些行为,但是每个子类该行为的实现又不同,于是该父类就把该行为定义成抽象方法的形式,具体实现交给子类去完成,这个类声明为抽象类。
5.2抽象类案例
需求:某加油站推出了2种支付卡,一种是预存10000的金卡,后续加油享受8折优惠,另一种 是预存5000的银卡 ,后续加油享受8.5折优惠。
请分别实现2种卡片进入收银系统后的逻辑,卡片需要包含主人名称,余额,支付功能。
分析实现:
创建一张卡片父类:定义属性包括主人名称、余额、支付功能(具体实现交给子类)
创建一张白金卡类:重写支付功能,按照原价的8折计算输出。
创建一张银卡类:重写支付功能,按照原价的8.5折计算输出。
以白金卡为例,代码如下:
卡片类(抽象类)
publicabstractclassCard { privateStringname; privatedoublemoney; publicabstractvoidpay(doublemoney); // setter、getter方法}
白金卡类(继承卡片类并具体实现抽象方法)
publicclassGoldCardextendsCard{ publicvoidpay(doublemoney) { System.out.println(getName() +"卡片余额:"+getMoney()); System.out.println(getName() +"当前消费:"+money); // 优惠价格计算doublers=money*0.8; System.out.println(getName() +"实际需要支付:"+rs); // 更新账户余额doubleaccountMoney=getMoney() -rs; setMoney(accountMoney); } }
测试类
publicclassTest { publicstaticvoidmain(String[] args) { GoldCardgoldCard=newGoldCard(); goldCard.setName("张三"); goldCard.setMoney(10000); goldCard.pay(600); System.out.println(goldCard.getName() +"卡片余额:"+goldCard.getMoney()); } }
程序运行结果如下:
张三卡片余额:10000.0
张三当前消费:600.0
张三实际需要支付:480.0
张三卡片余额:9520.0
5.3抽象类的特征、注意事项:
①类有的成员(成员变量、方法、构造器)抽象类都具备。
②抽象类中可以没有抽象方法,但是有抽象方法的类必须是抽象类。
③一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
④不能用abstract修饰变量、代码块、构造器。
⑤※抽象类不能创建对象—有得有失。
final和abstract是什么关系?
互斥关系
abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
5.4抽象类的应用知识:模板方法模式
使用场景说明:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同。
模板方法模式实现步骤:
①把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
②模板方法中不能决定的功能定义成抽象方法让具体子类去实现。
模板方法模式的原理:
模板方法已经定义了通用结构,模板方法不能确定的部分定义成抽象方法,交给子类实现,因此,使用者只需要关心自己需要实现的功能即可,提高了代码的复用性。
需求:现在有两类学生,一类是中学生,一类是小学生,他们都要写一篇作文。要求每种类型的学生,标题第一段和最后一段,内容必须一样。正文部分自己发挥。
请选择最优的面向对象方案进行设计。
使用模板方法模式解决:将大量重复代码添加到父类的模板方法中去,不同子类实现的不同功能定义成抽象方法,同样加到模板方法中,并在子类中重写这些不同功能的具体方法。
在不同对象实现时,调用父类中的模板方法,实现重复代码,其中此对象特有的具体功能,通过在模板方法中调用子类中重写的具体功能的形式实现。
示例代码如下:
学生类
publicabstractclassStudent { /*模板方法模式*/publicfinalvoidwrite() { // 模板方法使用final修饰,防止子类重写// 开头,所有对象都一样System.out.println("开头"); // 正文部分每个子类都要写,但具体内容不同// 因此将正文部分定义成模板方法,交由不同的对象实现不同的功能writeMain(); // 调用抽象方法,在子类具体实现时重写该重抽象方法// 结尾,所有对象都一样System.out.println("结尾"); } // 将模板方法中的具体实现不同的方法定义成抽象方法publicabstractvoidwriteMain(); }
中学生类
publicclassMiddleSchoolStudentextendsStudent { // 大量重复代码// public void write() {// System.out.println("开头");// System.out.println("中学生正文");// System.out.println("结尾");// }// 使用模板方法模式,只需要写不同的部分,提高代码复用性// 重写实现不同功能的方法publicvoidwriteMain() { System.out.println("中学生正文"); } }
小学生类
publicclassPupilextendsStudent { // 大量重复代码// public void write() {// System.out.println("开头");// System.out.println("小学生正文");// System.out.println("结尾");// }// 使用模板方法模式,只需要写不同的部分,提高代码复用性// 重写实现不同功能的方法publicvoidwriteMain() { System.out.println("小学生正文"); } }
测试类
publicclassTest { publicstaticvoidmain(String[] args) { Pupilpupil=newPupil(); // 调用父类中的模板方法,实现重复代码,其中此对象特有的具体功能,通过在模板方法中调用子类中重写的具体功能的形式实现pupil.write(); System.out.println("-----------"); MiddleSchoolStudentmiddleStu=newMiddleSchoolStudent(); middleStu.write(); } }
程序运行结果如下:
开头
小学生正文
结尾
-----------
开头
中学生正文
结尾
注:模板方法是给子类直接使用的,不是让子类重写的,一旦子类重写了,模板方法就失效了。所以模板方法使用final修饰,防止子类重写。
6.接口
6.1接口的概述、特点
接口的格式如下图所示。
接口是一种规范,是公开的(接口中的成员变量默认pubic static final修饰,成员方法默认public abstract修饰)。
接口不能实例化。
JDK8之前接口中只能是抽象方法和常量,没有其他成分。
示例代码如下:
publicinterfaceInterfaceDemo { // 常量publicstaticfinalStringSCHOOL_NAME="光明小学"; // 2.抽象方法// 接口是一种规范,是公开的(接口中的内容默认是pubic)publicabstractvoidrun(); publicabstractvoidgo(); }
6.2接口的基本使用:被实现
接口是用来被类实现(implements)的,实现接口的类称为实现类,实现类可以理解成子类。
实现类的格式如下图所示。
接口实现的注意事项:
①类可以实现单接口,也可以实现多接口。
②一个类实现接口,必须重写全部接口的全部抽象方法,否则这个类需要定义成抽象类。
示例代码如下:
Law接口
publicinterfaceLaw { voidrule(); // 遵纪守法}
Athletes接口
publicinterfaceAthletes { voidrun(); voidcompetition(); }
实现类
/*实现类*/publicclassTableTennisAthletesimplementsAthletes, Law { // 接口可以被类单实现,也可以被类多实现。privateStringname; publicTableTennisAthletes() { } publicTableTennisAthletes(Stringname) { this.name=name; } publicStringgetName() { returnname; } publicvoidsetName(Stringname) { this.name=name; } publicvoidrun() { System.out.println(name+"跑步"); } publicvoidcompetition() { System.out.println(name+"比赛"); } publicvoidrule() { System.out.println(name+"遵纪守法"); } }
测试类
publicclassTest { publicstaticvoidmain(String[] args) { TableTennisAthletestableTennisAthletes=newTableTennisAthletes("张三"); tableTennisAthletes.run(); // 张三跑步tableTennisAthletes.competition(); // 张三比赛tableTennisAthletes.rule(); // 张三遵纪守法 } }
基本小结:
类和类的关系:单继承。
类和接口的关系:多实现。
接口和接口的关系:多继承,一个接口可以同时继承多个接口。
接口多继承的作用:
规范合并,整合多个接口为同一个接口,接口继承(合并)后,只需要implements一个总的接口即可,便于子类实现。
6.3JDK8开始接口新增方法
第一种:默认方法(实例方法)
类似之前写的普通实例方法:必须用default修饰。
默认public修饰,需要用接口的实现类的对象来调用。
第二种:静态方法
默认public修饰,必须static修饰。
注意:接口的静态方法必须用本身的接口名来调用。
第三种:私有方法
就是私有的实例方法:,必须使用private修饰,从JDK1.9才开始有的。
只能在本类中被其他的默认方法或者私有方法访问。
6.4接口注意事项
1、※接口不能创建对象。
2、一个类实现多个接口,多个接口中有同样的静态方法不冲突。
3、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
4、一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
5、一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
示例代码如下:
publicclassTest { publicstaticvoidmain(String[] args) { // 1、※接口不能创建对象 } } // 2、一个类实现多个接口,多个接口中有同样的静态方法不冲突。interfaceA { staticvoidtest() { System.out.println("A"); } } interfaceB { staticvoidtest() { System.out.println("B"); } } classCimplementsA, B { publicstaticvoidmain(String[] args) { // C.test(); // 报错,接口的静态方法只允许通过接口名.静态方法的形式访问A.test(); // AB.test(); // B } } // 3、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的interfaceFood { defaultvoideat() { System.out.println("接口中的吃方法"); } } classAnimal { publicvoideat() { System.out.println("父类中的吃方法"); } } classCatextendsAnimalimplementsFood { } classTest2 { publicstaticvoidmain(String[] args) { Catcat=newCat(); cat.eat(); // 父类中的吃方法 } } // 4、一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。interfaceA2 { defaultvoidrun() { System.out.println("A2"); } } interfaceB2 { defaultvoidrun() { System.out.println("B2"); } } classTest3implementsA2, B2 { publicvoidrun() { System.out.println("Test3"); } } // 5、一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。interfaceA3 { intrun(); } interfaceB3 { voidrun(); } //interface Test4 extends A3, B3 {} // 报错,多个接口中存在规范冲突(同名方法两种不同的返回值),无法进行接口继承(合并)