这里会讲到java的面向对象部分:1.类和对象 ;2.类的无参方法和带参方法;3.对象与封装;4.继承;5.多态。有代码,有注释,有颜色标注重点,有详细的步骤。所以对java面向对象部分迷惑的童鞋可以多看看呀,一定会有所收获的!!!
一、类和对象
1.1什么是对象:
用来描述客观事物的一个实体,有一组属性和方法构成
1.2对象的特征:
属性:对象具有的各种特征
每个对象的每个属性都拥有特定值
例如:张浩和李明的年龄、姓名不一样
方法(能做的事):对象执行的操作
1.3封装:
对象同时具有属性和方法两种特性
对象的属性和方法通常是被封装在一起,共同体现事物的特性,二者相辅相成,不能分割
1.4什么是类:
类是模子,定义对象将会拥有的特征(属性)和行为(方法)
类和对象的关系:
类是多个对像进行综合抽象的结果,是实体对像的概念模型;而一个对象是一个类的实例类是抽象的概念,仅仅是模板。
对象是一个你能看的着,摸得着的具体实体,类是对象的类型。
1.5定义类:
①定义类名
语法:
访问修饰符 class 类名称{
//省略类的内部具体代码
}
注意:
访问修饰符如public、private等是可选的
class是声明类的关键字
按照命名规范,类名首字母大写
示例:
//定义一个“人”类publicclassPerson{//省略类的内部具体代码}
②编写类的属性
对象所拥有的特征在类中表示时称为类的属性
语法:访问修饰符 数据类型 属性名;
注意:访问修饰符是可选的,除了访问修饰符外,其他的语法和声明变量类似
示例:
//创建“人”类,并为“人”类添加相应属性publicclassPerson{publicStringname;publicStringgender;publicintage;}
③编写类的方法
对象执行操作的行为称为类的方法
语法:
访问修饰符 返回类型 方法名称(参数类型 参数名1,参数类型 参数名2,……){
//……省略方法体代码
}
注意:访问修饰符是可选的
返回类型可以是void,在定义方法时,返回类型为void时表明没有返回值,方法体中不必使用“return”关键字返回具体数据,但是可以使用“return”关键字退出方法
返回类型如果不是“void”,那么在方法体中一定要使用”return“关键字返回对应类型的结果,否则程序会出现编译错误
小括号中的“参数数据类型 参数名1,参数数据类型 参数名2,……“称为参数列表
当需要在方法执行的时候为方法传递参数时才需要参数列表,如果不需要传递参数就可以省略,不过小括号不可以省略,传递多个参数时,以英文的逗号进行分隔
示例:
//在“人”类中定义方法,描述人工作的行为publicclassPerson{//定义人类publicStringname;//姓名publicStringgender;//性别publicintage;//年龄//工作的行为publicvoidwork(){System.out.println(this.name+"工作理念:干活挣钱有饭吃");}}
1.6创建和使用对象:
创建对象:
类中的对象可以调用类中的成员,如属性和方法等
语法:类名 对象名 = new 类名();
注意:new是关键字;左边的类名为对象的数据类型;右边的类名() 称为类的构造方法
示例:
//创建 Person 类的对象Personperson=newPerson();
使用对象:
在java中,要引用对象的属性和方法,需要使用“.“
语法:对象名.属性 //引用对象的属性
对象名.方法名() //引用对象的方法
示例:
publicstaticvoidmain(String[]args){Personperson=newPerson();//创建对象person.name="韩冰";person.gender="女";person.age=22;person.work();}
1.7面对对象的优点:
提高代码的可重用性
提高代码的安全性和可维护性
与人类的思维习惯一致
二、类的无参方法和带参方法
2.1无参方法:
2.1.1定义类的方法:
语法:
访问修饰符 返回值类型 方法名(){
//方法的主体
}
两种情况:1有返回值 2 无返回值
1.有返回值:
如果方法具有返回值,方法中必须使用关键字return返回该值,要返回的数据类型要和返回值的类型一样
语法:return 表达式;
作用:跳出方法,返回结果
2.无返回值:
如果方法没有返回值,返回值类型为void
无参的方法表示的就是方法名后面的括号中没有任何内容
示例:
publicclassAutoLion{Stringcolor="黄色";publicvoidrun() {System.out.println("正在以0.1米/秒的速度向前奔跑");}publicvoidcry() {Stringsound="大声吼叫";}publicStringrobBall() {Stringball="球";returnball;}}
2.1.2方法的调用:
语法:对象名.方法名();
注意:方法是个“黑匣子”,完成某个特定的应用程序功能,并返回结果
方法调用:执行方法中包含的语句
本类中方法的调用:
本类中的方法调用,我们直接编写方法名();即可
非本类中方法的调用:
如果调用的是非本类中的方法,需要先创建对象,然后用对象名.方法名(); 调用
方法调用小结:
方法之间允许相互调用,不需要知道方法的具体实现,实现重用,提高效率
2.1.3全局变量和局部变量:
全局变量:声明在类以内方法以外
局部变量:声明在方法以内
含义:变量声明的位置决定变量作用域;变量作用域确定可在程序中按变量名访问该变量的区域
publicclassAutoLion{//变量1,2,3为全局变量 AutoLion类的方法,其他类的方法都可用变量1类型变量1;变量2类型变量2;变量3类型变量3;public返回类型方法1(){//变量4为局部变量 仅方法1可用变量4类型变量4;}public返回类型方法2(){//变量5为局部变量 仅方法2可用变量5类型变量5;}
全局变量和局部变量的区别:
1.作用域不同:
局部变量的作用域仅限于定义它的方法
成员变量的作用域在整个类内部都是可见的
2.初始值不同:
java会给全局变量一个初始值
java不会给局部变量赋予初始值
注意:
在同一个方法中,不允许有同名的局部变量
在不同方法中,可以有同名的局部变量
在同一个类中,全局变量和局部变量同名时,局部变量具有更高的优先级
全局变量和局部变量是可以重名,但是在使用的时候按照就近原则
publicclassBian{inta=10;publicvoida(){inta=20;}publicvoidb(){System.out.println(a);}publicstaticvoidmain(String[]args) {Bianbian=newBian();bian.b();//结果是10}}publicclassBian{inta;publicvoida(){inta=20;}publicvoidb(){System.out.println(a);}publicstaticvoidmain(String[]args) {Bianbian=newBian();bian.b();//结果是0}}publicclassBian{inta;publicvoida(){intb;}publicvoidb(){System.out.println(b);//会报错 因为b是局部变量,不会自动给初始值}publicstaticvoidmain(String[]args) {Bianbian=newBian();bian.b();}}
2.2带参方法:
2.2.1定义带参数的方法:
语法:
1 返回类型2 (3) {
//方法的主体
}
示例:
publicclassStudentsBiz{String[ ]names=newString[30];publicvoidaddName(Stringname){//一个形式参数//增加学生姓名}publicvoidshowNames() {//显示全部学生姓名}
调用带参数的方法:
语法:对象名.方法名(参数1, 参数2,……,参数n4)
示例:
publicstaticvoidmain(String[]args) {StudentsBizst=newStudentsBiz();Scannerinput=newScanner(System.in);for(inti=0;i<5;i++){System.out.print("请输入学生姓名:");StringnewName=input.next();st.addName(newName);//实参的类型、数量、顺序都要与形参一一对应}st.showNames();}
带多个参数的方法:
publicclassStudent{publicbooleansearchName(intstart,intend,Stringname){String[]names={"哈哈","呵呵","嘻嘻","嘿嘿","哼哼"};booleanfind=false;for(inti=start-1;i
2.2.2数组的使用:
publicclassArrayTest{publicStringmax(int[]a){intmax=a[0];for(inti=0;i
2.2.3新创一个类型:
//创建一个学生类publicclassStudent{intstuId;Stringname;intage;publicvoidshow(){System.out.println("学号是:"+stuId+"姓名是:"+name+"年龄是:"+age);}}publicclassTestStudent{Student[]students=newStudent[30];//创建对象数组,和创建对象一样;可以使用多次publicvoidaddStu(Studentstudent){for(inti=0;i
三、对象与封装
面向对象设计和开发程序的好处:
交流更加流畅;提高设计和开发效率
一个现实世界的问题如何在计算机中描述它们:
1.找出它的种类
2.找出它的属性
3.找出它的行为
类图:
1.作用:用于分析和设计“类”
2.优点:直观、容易理解
3.图像说明:
构造方法:
1.为什么使用构造方法:
由于创建对象使用点的方式给属性赋值相对繁杂,我们可以使用构造方法来给属性进行初始化值
2.作用:初始化值;更简便的赋值
3.语法:访问修饰符 类名(){}
4.分类:
无参构造方法:
publicclassCat{Stringname;inthealth;publicvoidshow(){System.out.println(name+health);}}
publicclassTestCat{publicstaticvoidmain(String[]args) {Catcat=newCat();cat.show();//此时的结果是 null 0 因为 name和health都没有赋值都是初始值}}
有参构造方法:
publicclassCat{Stringname;inthealth;publicvoidshow(){System.out.println(name+health);}publicCat(Stringa,intb){this.name=a;//this. 后面跟的是类中上面声明的属性 "="后面的是有参构造方法括号里的this.health=b;}}publicclassTestCat{publicstaticvoidmain(String[]args) {Catcat=newCat("哈哈",18);cat.show();//此时的结果是 哈哈 18,因为在上面类中的有参构造方法中让name和health等于了a和b又在创建对象时给a和b赋了值}}
注意:
在我们创建对象的时候就会调用这个构造方法
在我们在类中没有写构造方法的时候,系统会默认给我们一个无参构造方法
但是如果类中有了有参的构造方法,则不会自动生成无参构造方法;推荐在写类的时候将有参和无参构造方法都写
publicclassCat{Stringname;inthealth;publicvoidshow(){System.out.println(name+health);}}//在类中没有有参构造方法时,我们在main方法中创建对象时可以直接创建,此时其实就是无参的构造方法publicclassTestCat{publicstaticvoidmain(String[]args) {Catcat=newCat();cat.show();}}//当类中有了有参的构造方法时,在创建对象时就需要在括号中添加内容publicclassCat{Stringname;inthealth;publicvoidshow(){System.out.println(name+health);}publicCat(inta){}//有参的构造方法}publicclassTestCat{publicstaticvoidmain(String[]args) {Catcat=newCat();//报错,因为在类中有了有参的创建对象,如果要修改只能在类中创建一个无参的构造方法,或者在括号中加入有参的构造方法括号中同类型的数据cat.show();}}
在我们执行的时候,new 对象时,对象名后面括号(如:Dog dog = new Dog())里面可以有的参数必须符合类中已声明的有参构造方法
当类中有无参和有参构造方法时,我们在创建对象的时候如果想调用两个,在创建对象时,对象名要区分开,不能取同样的名
publicclassCat{Stringname;inthealth;publicvoidshow(){System.out.println(name+health);}publicCat(Stringa,intb){this.name=a;this.health=b;}publicCat(){}}publicclassTestCat{publicstaticvoidmain(String[]args) {Catcat1=newCat("哈哈",18);cat1.show();//结果是 哈哈 18Catcat2=newCat();cat2.show();//结果是 null 0 虽然都是调用的show方法但是由于构造方法不同结果也是不同的}}
this:
1.只能用在方法中;this表示当前类的;不写也是默认存在的
2.可以修饰属性(this.属性名)、方法(this.方法名();)、构造方法(无参:this(); 有参:this(参数);)
在使用this调用构造方法的时候必须在构造方法的第一行(构造方法只能在构造方法中调用)
3.子类在继承父类后也可以直接用this来使用父类中的属性和方法(不是调用父类中的方法,因为子类继承了父类,相当于调用本类中的属性和方法,因此当父类中的方法被重写后,调用父类中的方法需要用super)
重载:
1.方法名相同; 2.参数列表不同(个数,顺序,数据类型); 3.在同一个类中;和访问修饰符以及返回值类型无关;4.并不只是构造方法有重载,普通方法也有重载;5.两个构造方法不能同时互相调用(行成死循环)
publicvoids1(inta,intb){}publicvoids1(intc,intd){}//不能形成重载,因为数据类型形同
publicvoids1(inta,intb){}publicvoids2(Stringa,intb}//不可以重载,因为方法名不同publicvoids1(inta,intb){}publicvoids1(Stringa,intb}//可以重载,因为数据类型不同 和实参名无关publicvoids1(inta,intb){}publicints1(intc,intd){return1}//可以重载,和访问修饰符以及返回值类型无关
为什么需要使用重载:
在使用的时候调用方法或者赋值更加的便捷
static:
使用场景:在共用的情况下使用(试想饮水机和水杯的案列)
static:静态的,是随着类的加载而加载(静态的加载完才加载普通的);可以修饰属性和方法还有代码块,表示公用的
使用 static修饰的属性或者方法,可以使用对象名. 的方式调用;也可以使用类名. 的方式调用(推荐使用)
static修饰的变量也称为静态变量,静态变量和非静态变量的区别是:静态变量被所有对象共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。
被static修饰的成员变量属于类,不属于某个对象(也就是说:多个对象访问或修改static修饰的成员变量时,其中一个对象将static成员变量进行了修改,其它的对象的static成员变量值跟着改变,即多个对象共享同一个static成员变量,不是不能改变)
而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
虽然对于静态方法来说没有this,但是我们在非静态方法中能够通过this访问静态方法成员变量。如下:
publicclassTest{staticintvalue=11;publicstaticvoidmain(String[]args) {newTest().printValue();}privatevoidprintValue() {intvalue=22;System.out.println(this.value);//输出的结果是:11}}
这里的this表示的是当前对象,那么通过new Test()来调用printValue的话,当前对象就是通过new Test()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是11。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出11。需要记住的是:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要权限足够)。
方法调用:
普通方法:普通方法是可以调用普通方法的,也可以调用静态方法
静态方法:静态方法可以调用静态方法,不能调用普通方法(因为静态的加载完才加载普通的)
注意:在静态方法中不能使用this关键字
示例:
publicclassStartPerson{Stringname;intage;staticStringcountry;publicStartPerson(){}publicStartPerson(Stringname,intage,Stringcountry){this.name=name;this.age=age;this.country=country;}publicvoidshow(){System.out.println("姓名是:"+name+",年龄是:"+age+",国籍是:"+country);}}@Testpublicvoidtest01(){StartPersons1=newStartPerson("刘德华",40,"中国");StartPersons2=newStartPerson("凤姐",30,"中国");StartPersons3=newStartPerson("古天乐",39,"美国");s1.show();s2.show();s3.show();/*结果是姓名是:刘德华,年龄是:40,国籍是:美国姓名是:凤姐,年龄是:30,国籍是:美国姓名是:古天乐,年龄是:39,国籍是:美国因为国家是static修饰的*/}
封装:
为什么使用封装:
java 封装,说白了就是将一大坨公共通用的实现逻辑玩意,装到一个盒子里(class),出入口都在这个盒子上。你要用就将这个盒子拿来用,连接出入口,就能用了,不用就可以直接扔,对你代码没什么影响。
对程序员来说,使用封装的目的:
1.偷懒,辛苦一次,后面都能少敲很多代码,增强了代码得复用性
2.简化代码,看起来更容易懂
3.隐藏核心实现逻辑代码,简化外部逻辑,并且不让其他人修改,jar 都这么干
4.一对一,一个功能就只为这个功能服务;避免头发绳子一块用,导致最后一团糟
在编码过程中,给属性赋值的时候,往往会出现一些不符合实际的值,严重一些的情况会影响项目的安全。此时就可以使用封装来解决这样的问题。没有使用封装的时候,用户可以随意给属性赋值
含义:封装其实就是将属性给隐藏起来,不让用户轻易的去访问
publicclassStudent{intage;publicvoidshow(){System.out.println("年龄是:"+age);}}publicclassTestStudent{publicstaticvoidmain(String[]args) {Studentstudent=newStudent();student.age=-10;}}
封装的步骤:
①将属性私有化 private
/* 用户可能不会输入符合实际的数值* 为了安全性,选择将属性使用private保护起来* private:私有的,仅本类可见*/privateintage;
②编写setter方法,可以针对用户输入的值进行判断
publicvoidsetAge(intage){if(age>0&&age<100){this.age=age;}else{System.out.println("请输入符合实际情况的年龄(0—100),默认18岁");this.age=18;}}
③编写getter方法,来获取值
publicintgetAge(){returnage;}
四、继承
4.1为什么使用继承:
再多个同种类型中有多个重复的代码,就会冗余
可以使用继承避免此种情况出现
4.2概念:
将共同的代码提取出来放到一个单独的类中,让原来的类继承这个新的类
4.3满足条件:
子类和父类是is-a关系
注意:
继承只能是单根继承(同时只能继承一个类)
可以间接的去继承(如a继承b,b继承c,a也就间接继承了c)
4.4关键字:
extends
4.5哪些是不能被继承的:
1.父类的构造方法
2.private 修饰的内容
3.不同包下的 默认修饰符(default)修饰的内容
4.默认修饰符在同包下修饰的内容可以被继承
4.6super(写法与this相同):
可以调用 父类中的 属性,方法,构造方法
如果调用构造方法,必须在第一行(构造方法只能在构造方法中调用)
4.7**构造方法执行顺序:
在有子父类的情况下,创建对象(子类)中,如果在子类的构造方法中没有写调用父类的构造方法的话,会默认调用父类的无参构造方法
在子类构造方法中如果没有明示调用父类构造方法的话,则默认调用父类的无参构造方法
在子类构造方法中如果写了调用父类的具体某个构造方法,则调用写的那个构造方法
子类对象在进行实例化前首先调用父类构造方法,再调用子类构造方法实例化子类对象。
实际在子类构造方法中,相当于隐含了一个语句super(),调用父类的无参构造。同时如果父类里没有提供无参构造,那么这个时候就必须使用super(参数)明确指明要调用的父类构造方法。
4.8如何使用继承:
编写父类:
语法:
class Pet{
//公共的属性和方法
}
编写子类,继承父类:
语法:
class Dog extends1 Pet2{
//子类特有的属性和方法
}
class Penguin extends Pet{
}
注意:
继承只能是单继承,只能同时继承一个
示例:
//子类publicclassDogextendsPet{Stringstrain;publicDog(){System.out.println("子类构造方法");super.setHealth(health);}publicStringgetStrain() {returnstrain;}publicvoidsetStrain(Stringstrain) {this.strain=strain;}/*public void show(){System.out.println("我的名字叫"+name+",健康值是"+health+",和主人的亲密度是"+love+"我是一只"+strain);}*/publicvoidshow(){System.out.println("品种是:"+strain);}}//子类publicclassPenguinextendsPet{Stringgender;publicStringgetGender() {returngender;}publicvoidsetGender(Stringgender) {this.gender=gender;}publicvoidshow(){System.out.println("性别是"+gender);}}//父类publicclassPet{Stringname;inthealth;intlove;publicPet(){System.out.println("父类构造方法");}publicStringgetName() {returnname;}publicvoidsetName(Stringname) {this.name=name;}publicintgetHealth() {returnhealth;}publicvoidsetHealth(inthealth) {this.health=health;}publicintgetLove() {returnlove;}publicvoidsetLove(intlove) {this.love=love;}publicvoidshow(){System.out.print("姓名是:"+name+",健康值是:"+health+",好感度是:"+love);}}
4.9访问修饰符:
在同一个项目中
4.10方法的重写:
示例1:
比如,定义Father类
1:姓名,吃饭方法,吃窝窝头。
2:定义Son类,继承Father
1:Son类中不定义任何成员,子类创建对象,仍然可以调用吃饭的方法。
2:父类的吃饭的方法,Son不愿吃。Son自己定义了吃饭的方法。
1:此时父类中有一个吃饭的方法,子类中有2个吃饭的方法,只是方法体不一样。
2:一个类中两个方法一模一样,是不允许的。
1:编译运行,执行了子类的方法。
2:使用父类的方法,在子类方法中,使用super.父类方法名。
classFather{Stringname;voideat() {System.out.println("吃窝窝");}}classSonextendsFather{publicvoideat() {// 继承可以使得子类增强父类的方法System.out.println("来俩小菜");System.out.println("来两杯");System.out.println("吃香喝辣");System.out.println("来一根");}}classDemo8{publicstaticvoidmain(String[]args) {Sons=newSon();//执行子类的方法s.eat();}}
定义:
该现象就叫做重写(覆盖 override)
在继承中,子类可以定义和父类相同的名称并且参数列表一致的方法,将这种函数称之为方法的重写
前提:
必须存在有继承关系
特点:
当子类重写了父类的方法,那么子类的对象如果调用该方法,一定调用的是重写过后的方法,可以通过super关键字进行父类的重写方法的调用
继承可以使得子类增强父类的方法
**细节:
方法名必须相同
参数列表必须相同
子类重写父类的方法的时候,方法的访问权限必须大于等于父类的方法的访问权限否则会编译报错
子类重写父类的方法的时候,返回值类型必须是父类方法的返回值类型或该返回值类型的子类,不能返回比父类更大的数据类型,如子类方法的返回值类型是object
抛出的异常不能大于父类
***示例2:
publicvoidtest01(){Studentstu1=newStudent("张三",4,55);Studentstu2=newStudent("张三",4,55);booleani=(stu1.equals(stu2));System.out.println(i);//stu1和stu2是同一人但是结果是false 这时候就需要重写equals方法//等于是在Student 类里写了一个equals方法}publicbooleanequals(Objectobj) {//比较地址值 地址值如果一样别的就不用看了if(this==obj){returntrue;}//先判断传进来的obj是否和Student为同一个对象,用instanceof 来判断if(objinstanceofStudent){Studenti=(Student)obj;//如果不一样,强转,类似把int类型的转为double类型//看obj里面的内容是否和比较的对象内容是否一样,如果一样为trueif(this.id==i.id&&this.name==i.name&&this.score==i.score){returntrue;}}returnfalse;}
abstract:
抽象的,可以修饰方法和类
抽象方法没有方法体(大括号:{}),抽象方法一定要在抽象类中,但是抽象类中不一定有抽象方法
抽象方法需要被子类都继承实现,如果子类没有重写父类中的抽象方法的话则这个类也需要是抽象类,其方法再由其子类重写
抽象类不能被实例化
重点:
final和abstract,private和abstract,static和abstract,这些是不能放在一起的修饰符,因为abstract修饰的方法是必须在其子类中实现(覆盖),才能以多态方式调用,以上修饰符在修饰方法时其子类都覆盖不了这个方法,final是不可以覆盖,private是不能够继承到子类,所以也就不能覆盖,static是可以覆盖的,但是在调用时会调用编译时类型的方法,因为调用的是父类的方法,而父类的方法又是抽象的方法,又不能够调用,所以上的修饰符不能放在一起。
final:
修饰的类不能被其它类继承
修饰的方法不能被重写
修饰的属性不能被修改
***经典例题:
classA{publicA(){System.out.println("1.A类的构造方法");}{System.out.println("2.A类的构造块");}static{System.out.println("3.A类的静态方法");}}publicclassBextendsA{publicB(){System.out.println("4.B类的构造方法");}{System.out.println("5.B类的构造块");}static{System.out.println("6.B类的静态方法");}publicstaticvoidmain(String[]args){System.out.println("7.start......");newB();newB();System.out.println("8.end.....");}}
主类中的静态块优先于主方法执行,所以6应该在7前面执行,但是B类继承于A类,所以先执行A类的静态块3,所以进主方法前的执行顺序为:3 6
进主方法后执行7,new B()之后应先执行A的构造方法然后执行B的构造方法,但由于A类和B类均有构造块,构造块又优先于构造方法执行即 2 1(A的构造家族) 5 4(B的构造家族),有多少个对象,构造家族就执行几次,题目中有两个对象 所以执行顺序为:3 6 7 2 1 5 4 2 1 5 4 8(如果main方法重新建一个类执行顺序是7 3 6 2 1 5 4 2 1 5 4 8)
五、多态
5.1什么是多态:
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:
酒 a = 剑南春
酒 b = 五粮液
酒 c = 酒鬼酒
…
这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。
诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:
JNC a = new JNC();
对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?
Wine a = new JNC();
在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。
但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了。
5.2使用:
父类的引用指向子类的实例(对象)
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
5.3语法:
父类名 父类引用 = new 子类类名(); (类似自动类型转换)
//类名 对象名 = new 类名();Dogdog=newDog();Catcat=newCat();Penguinpenguin=newPenguin();//多态// 父类名 引用 = new 子类类名();Petpet=newDog();Petpet=newCat();Petpet=newPenguin();
如果父类和子类中都有同样的方法,那么我们在多态方式调用的时候调用执行的是子类中的方法
那么父类中的方法就不方便同一操作,因此我们可以将方法设置成抽象方法(abstract修饰)
示例:
publicclassDogextendsPet{privateStringstrain;publicStringgetStrain() {returnstrain;}publicvoidsetStrain(Stringstrain) {this.strain=strain;}publicvoidshow(){System.out.println("品种为:"+strain);}publicvoidhospital(){System.out.println("吃药打针....");inth=getHealth()+5;setHealth(h);}}publicclassPenguinextendsPet{privateStringsex;publicStringgetSex() {returnsex;}publicvoidsetSex(Stringsex) {this.sex=sex;}/*public void show(){System.out.println(name+","+health+","+love+","+sex);}*/publicvoidshow(){System.out.println(sex);}publicvoidhospital(){System.out.println("吃药睡觉....");setHealth(getHealth()+3);}}publicabstractclassPet{privateStringname;privateinthealth;privateintlove;publicStringgetName() {returnname;}publicvoidsetName(Stringname) {this.name=name;}publicintgetHealth() {returnhealth;}publicvoidsetHealth(inthealth) {this.health=health;}publicintgetLove() {returnlove;}publicvoidsetLove(intlove) {this.love=love;}publicvoidshow(){System.out.println(name+","+health+","+love);}publicabstractvoidhospital();}publicclassMaster{/*** 编写一个带dog去医院的方法*//*public void toHospital(Dog dog){if(dog.getHealth()<70){dog.hospital();}else{System.out.println("狗狗很健康.....");}}public void toHospital(Penguin pen){if(pen.getHealth()<80){pen.hospital();}else{System.out.println("企鹅很健康....");}}*/
publicvoidtoHospital(Petpet){if(pet.getHealth()<80){pet.hospital();}else{System.out.println("宠物很健康....");}}}publicclassTestPet{/*** 多态:* 同一种事物在不同条件下的不同状态*/@Testpublicvoidtest03(){Mastermaster=newMaster();//Pet pet = new Pet(); 错误//类名 引用 = new 子类();//编译看左边运行看右边Petpet=newPenguin();pet.setHealth(40);master.toHospital(pet);inthealth=pet.getHealth();System.out.println(health);}
多态情况下,关于属性和方法的访问总结:
父子和子类具有相同的成员变量时(静态或非静态),访问的都是父类的成员变量。
子类重写非静态方法,访问子类的方法,子类重写静态方法,还是访问父类的方法
多态:同一个对象(事物),在不同时刻体现出来的不同状态。
***示例:
猫是猫,猫是动物。水(液体,固体,气态)。多态的前提:A:要有继承关系。B:要有方法重写。其实没有也是可以的,但是如果没有这个就没有意义。动物 d = new 猫();d.show();动物 d = new 狗();d.show();C:要有父类引用指向子类对象。父 f = new 子();用代码体现一下多态。多态中的成员访问特点:A:成员变量编译看左边,运行看左边。B:构造方法创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。C:成员方法编译看左边,运行看右边。D:静态方法编译看左边,运行看左边。(静态和类相关,算不上重写,所以,访问还是左边的)由于成员方法存在方法重写,所以它运行看右边。
***经典例题:
publicclassA{publicStringshow(Dobj) {return("A and D");}publicStringshow(Aobj) {return("A and A");}}publicclassBextendsA{publicStringshow(Bobj){return("B and B");}publicStringshow(Aobj){return("B and A");}}publicclassCextendsB{}publicclassDextendsB{}publicclassTest{publicstaticvoidmain(String[]args) {Aa1=newA();Aa2=newB();Bb=newB();Cc=newC();Dd=newD();System.out.println("1--"+a1.show(b));System.out.println("2--"+a1.show(c));System.out.println("3--"+a1.show(d));System.out.println("4--"+a2.show(b));System.out.println("5--"+a2.show(c));System.out.println("6--"+a2.show(d));System.out.println("7--"+b.show(b));System.out.println("8--"+b.show(c));System.out.println("9--"+b.show(d));}}//运行结果1--AandA2--AandA3--AandD4--BandA5--BandA6--AandD7--BandB8--BandB9--AandD
在这里看结果1、2、3还好理解,从4开始就开始糊涂了,对于4来说为什么输出不是“B and B”呢?
首先我们先看一句话:当超类(父类)对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
分析:从上面的程序中我们可以看出A、B、C、D存在如下关系。
首先我们分析5,a2.show(c),a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。
方法已经找到了但是我们这里还是存在一点疑问,我们还是来看这句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这我们用一个例子来说明这句话所代表的含义:a2.show(b);
这里a2是引用变量,为A类型,它引用的是B对象,因此按照上面那句话的意思是说有B来决定调用谁的方法,所以a2.show(b)应该要调用B中的show(B obj),产生的结果应该是“B and B”,但是为什么会与前面的运行结果产生差异呢?这里我们忽略了后面那句话“但是这儿被调用的方法必须是在超类中定义过的”,那么show(B obj)在A类中存在吗?根本就不存在!所以这句话在这里不适用?那么难道是这句话错误了?非也!其实这句话还隐含这这句话:它仍然要按照继承链中调用方法的优先级来确认。所以它才会在A类中找到show(A obj),同时由于B重写了该方法所以才会调用B类中的方法,否则就会调用A类中的方法。
所以多态机制遵循的原则概括为:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
什么叫引用:
1、首先,你要明白什么是变量。变量的实质是一小块内存单元。这一小块内存里存储着变量的值
比如int a = 1。 a就是变量的名名,1就是变量的值。
2、而当变量指向一个对象时,这个变量就被称为引用变量。 比如A a =new A();
a就是引用变量,它指向了一个A对象,也可以说它引用了一个A对象。我们通过操纵这个a来操作A对象。 此时,变量a的值为它所引用对象的地址。
好啦。到目前为止,关于java的面向对象部分,基本写的差不多啦(字数超出了,发不出来,留到下篇发),还剩下接口、异常、对象引用与对象的区别、多态性理解、向上转型和向下转型、栈和堆等知识,会放在下篇讲,我们 下篇 见,帮助到你们的话,可以点赞关注收藏一波O(∩_∩)O~!!!
喜欢前端、后端java开发的可以加+qun:609565759,有详细视频、资料、教程,文档,值得拥有!!!希望可以一起努力,加油ヾ(◍°∇°◍)ノ゙!!!