封装
封装是面向对象编程的基本原则,封装要求在编写类时,对外隐藏内部的数据状态和实现细节,仅对外暴露接口,用于外部调用访问。
类的封装,通常类的属性私有(用private修饰符),不对外暴露数据和实现细节,而是通过get和set方法获取和设置属性。
packageoop.classDemo2;publicclassTeacher{// 私有,不对外公开访问的实例属性privateStringname;privateintage;// 构造方法(方法重载)publicTeacher(Stringname,intage){this.name=name;this.age=age;}publicTeacher(Stringname){this.name=name;}publicTeacher(intage){this.age=age;}publicTeacher(){}// 实例方法publicvoidsetName(Stringname){this.name=name;}publicStringgetName(){returnthis.name;}publicvoidsetAge(intage){this.age=age;}publicintgetAge(){returnthis.age;}publicvoidsayWhoIam(){System.out.println("我叫"+this.name+",今年"+this.age+"岁,我是一名老师");}}类的使用者,不能通过new一个对象,用对象.属性设置和获取数据,只能通过对象.方法设置和获取数据。
TeacherxiaoMing=newTeacher("小明",33);System.out.println(xiaoMing.age);// 错误代码,不能通过对象.属性名称 来访问属性System.out.println(xiaoMing.getAge());// 正确代码,通过get方法,来访问属性为什么要这么做?为什么要多此一举编写get和set方法?
这样做的好处是:
- 有利于多人团队协作。类的编写者和调用者可以分开,类的调用者不需要关心和了解类的具体属性是什么,只需要调用方法(调用API)实现需要的操作即可。
- 便于维护和更新代码。编写一个类可以使用多种数据结构、不同的数据类型,如果属性的访问不是通过封装成方法,那更新编写类的数据结构,则调用类的代码也需要同步变更。而使用封装的方法,则在调用时不需要更新,因为类更换数据结构后,只需要更新适配新数据结构的get和set方法即可。
- 利于代码的安全性。通过set方法设置变量属性,可以做数据校验和特殊处理,有利于代码的安全性。
**注意:封装并不是要求把所有的私有属性通过setter和getter暴露出来,因为开放私有属性获取和设置,这本身可能就破坏了类的封装性
很多时候,类不提供setter和getter方法,而是直接提供业务相关的方法。
publicclassBankAccount{privatedoublebalance;publicBankAccount(){this.balance=0;}// 不直接提供setBalance,而是提供业务相关的方法publicvoiddeposit(doubleamount){if(amount>0){// 判断金额是否合法balance+=amount;}}publicbooleanwithdraw(doubleamount){if(amount>0&&balance>=amount){balance-=amount;returntrue;}returnfalse;}publicdoublegetBalance(){returnbalance;}// 注意:没有setBalance方法!}继承
面向对象的编程,是以类来组织和封装代码。当类足够多时,我们会对类进行抽象,划分为所谓父类和子类,比如“动物”这个父类下,有“哺乳动物”、“脊椎动物”等子类。
从认知上,子类和父类一定有一些共同的属性特征,否则不可能抽象成一个父类。而子类一定会与父类有些差异或者特殊属性,否则页不可能被划分为子类。就像父类“教师”,可以划分为“生物教师”、“数学教师”子类。“数学教师”是(is a)“教师”,而“数学教师”又具备“教数学”特殊技能的特殊的“教师”。
什么是面向对象编程的继承?面向对象编程时,子类可继承父类的公开(public)属性和方法,这一特性可以大幅减少代码量,提高编程效率。
在Java中,父类又称为基类,子类又称为扩展类、派生类,因此使用关键字extends来标记子类。即
publicclassSonClassextendsFatherClass{// 子类体}在Java中,子类可以继承父类的:
- 公开的静态属性
- 公开的静态方法
- 所有的实例属性,父类私有的(private)实例属性无法被直接访问,但是可以通过继承公开的(public protected)实例方法进行调用,间接获取父类的私有的实例属性。
- 公开的实例方法
需要注意的是,子类不继承父类的构造方法,而是子类必须通过super()调用父类的构造方法。
publicclassApp{publicstaticvoidmain(String[]args){// MathTeacher继承Teacher的公开的静态属性System.out.println(MathTeacher.CLASS_NAME);// 老师// MathTeacher继承Teacher的公开的静态方法MathTeacher.sayHello();// Hello everyone, I am a teacher// MathTeacher继承Teacher的公开的实例属性MathTeacherzhangSan=newMathTeacher("张三",32);System.out.println(zhangSan.name);// 张三(name为父类的公开的实例属性)// MathTeacher继承Teacher的公开的实例方法System.out.println(zhangSan.getAge());// 32(age为父类的私有实例属性,通过公开的实例方法间接访问)}}publicclassTeacher{// 静态属性publicstaticfinalStringCLASS_NAME="老师";// 静态方法publicstaticvoidsayHello(){System.out.println("Hello everyone, I am a teacher");}// 实例属性(一般是要封装成私有属性,此处为示例继承功能,两个属性分别用公开和私有)publicStringname;privateintage;// 构造方法publicTeacher(Stringname,intage){this.name=name;this.age=age;}// 实例方法publicintgetAge(){returnage;}}publicclassMathTeacherextendsTeacher{// extends关键字继承父类// 继承了父类的公开的静态属性// 继承了父类的公开的静态方法// 继承了父类的公开的实例属性// 子类需要使用super()调用父类的构造方法publicMathTeacher(Stringname,intage){super(name,age);}// 继承了父类的公开的实例方法}super
子类会继承父类的实例属性,子类构造方法需要使用super关键词,调用父类构造方法,初始化继承自父类的实例属性。
子类的构造方法,会隐式的调用super()。但是,如果父类的构造方法是有参构造,则子类必须显性的编写super()方法进行调用。如果显性编写super(),比如:
// 父类publicclassTeacher{privateStringname;privateintage;publicTeacher(){System.out.println("父类初始化了");}}// 子类publicclassMathTeacherextendsTeacher{publicMathTeacher(){System.out.println("子类初始化了");}}// 子类初始化publicclassApp{publicstaticvoidmain(String[]args){MathTeacherzhangSan=newMathTeacher();}}父类初始化了 子类初始化了 进程已结束,退出代码为0初始化子类时,会调用子类的构造方法。而在子类的构造方法内部,会隐式的调用super(),即调用父类的构造方法。因此,才会运行System.out.println(“父类初始化了”)。
需要注意: super()必须是子类构造方法的第一个语句。
方法重写
子类会继承父类公开的实例方法,但是很多情况下,父类的方法可能并不是子类需要的,或者子类需要的实例方法,运行逻辑与父类不同。这种情况下,就可以在子类重新定义与父类方法相同名称的实例方法,在实例调用时,会去运行子类定义的方法。这种情况,叫方法的重写。
子类重写的方法,可添加@override注解(非必须),可以帮助编译器检查方法重写是否正确。
需要注意:
- 方法重写指的是实例方法的重写,静态方法不存在重写。也就是父类和子类都存在相同的静态方法,此类情况并非方法的重写。
- 方法的重写,要求子类和父类方法的方法名、方法参数和范围值都相同,否则系统不会认定是方法的重写。
- 访问修饰符不能比父类更严格。(宽松程度:public > protected > default > private)
- 抛出异常:不能抛出比父类方法更宽泛的检查异常
Object类
Java是用类来组织代码,java中所有的类都隐式的继承自一个Object类,继承Object类的方法,比如使用hashcode、toString等方法。
多态
多态是指同一个方法,在不同的对象上调用,会产生不同的效果,。
多态是方法的多态,跟属性没有半毛钱关系。
面向对象的多态特性,表现在方法重载和方法重写。
方法重载的多态(编译时多态/静态多态)
方法重载,是指在java的同一个类中,可以定义多个具有相同方法名的方法,只需要满足:返回类型、方法参数(参数数量、参数类型、参数顺序)不同。
在方法被调用时,系统会根据形参自动选择对应的方法(在编译阶段,编译器会根据形参,绑定选用的方法,是静态绑定)。比如:
// 方法重载示例:同一个类中,方法名相同,参数列表不同classCalculator{// 加法方法的不同形式publicintadd(inta,intb){returna+b;}publicdoubleadd(doublea,doubleb){returna+b;}publicintadd(inta,intb,intc){returna+b+c;}}publicclassOverloadExample{publicstaticvoidmain(String[]args){Calculatorcalc=newCalculator();// 编译器根据参数类型和数量决定调用哪个方法System.out.println("两个整数相加: "+calc.add(5,10));// 调用 add(int, int)System.out.println("两个小数相加: "+calc.add(5.5,10.5));// 调用 add(double, double)System.out.println("三个整数相加: "+calc.add(5,10,15));// 调用 add(int, int, int)}}方法重写的多态(运行时多态/动态多态)
方法重写,是发生在子类继承父类的场景里。方法重写,就是子类继承父类方法时,对相同的方法的代码逻辑进行重新编写。
当父类引用指向子类对象时,系统会动态的根据子类对象,调用子类对象的方法(在编译阶段,编译器会检查父类是否有需要调用的方法,而在运行阶段,系统会根据子类对象调用不同的方法,因此是动态绑定)。
// 父类classAnimal{publicvoidmakeSound(){System.out.println("动物发出声音");}}// 子类1classDogextendsAnimal{// 方法重写@OverridepublicvoidmakeSound(){System.out.println("汪汪汪!");}// 子类1狗特有的方法publicvoidwagTail(){System.out.println("狗摇尾巴");}}// 子类2classCatextendsAnimal{// 方法重写@OverridepublicvoidmakeSound(){System.out.println("喵喵喵!");}}// 子类3classBirdextendsAnimal{// 方法重写@OverridepublicvoidmakeSound(){System.out.println("叽叽喳喳!");}}publicclassPolymorphismExample{publicstaticvoidmain(String[]args){// 运行时多态:父类引用指向子类对象AnimalmyAnimal;myAnimal=newDog();// Animal引用指向Dog对象myAnimal.makeSound();// 输出:汪汪汪!myAnimal=newCat();// Animal引用指向Cat对象myAnimal.makeSound();// 输出:喵喵喵!myAnimal=newBird();// Animal引用指向Bird对象myAnimal.makeSound();// 输出:叽叽喳喳!// 示例:多态在数组中的应用Animal[]animals=newAnimal[3];animals[0]=newDog();animals[1]=newCat();animals[2]=newBird();System.out.println("\n动物合唱:");for(Animalanimal:animals){animal.makeSound();// 同一方法调用,不同行为}}}方法重写是实例方法的重写,静态方法没有重写。静态方法可以被子类“隐藏”(hide),而不是重写。调用哪个版本取决于引用类型,而不是对象类型。