首页 > 代码库 > Java基础之OOP
Java基础之OOP
1. 类(类型)于对象
(1)面向过程的开发于面向对象开发的区别:
面向过程更重视流程化以及功能的开发,简单点来讲,就是按照固定的模式一步步按部就班的进行,最终达成一个功能的实现。这种模式叫做面向过程开发。
也可以称之为对于一个功能的“增删改查“性质的开发。例如图3中的案例,最终是站在一个功能实现的角度,来最终实现学员信息的增删改查。(纯为了完成功能来进行开发)
参照下图:
面向对象开发不是以功能为主导,而是以对象为主导。所谓对象主导是指一个软件的开发,不光包含功能的实现还要包含所有参与软件的参与者。
所以面向对象的开发就是面对所有软件参与者的开发。因为开发一个软件,最终的目的是给软件的相关人员使用或者被使用的。所以在软件开发的时候要考虑到使用软件所牵扯到的所有者。这种思维的开发方式就是面向对象的开发。
而在进行面向对象开发的时候,首先要搞清楚各个参与者之间的属性以及对应关系。 (属性于属性之间/参与者于参与者之间的关系)
参考下图:
上图中很形象的把面向对象开发的含义给描述清楚了。
我们再开简单概括一下。所谓的面向对象的开发,无软是开发什么样的软件,都是面向软件的参与者。把各个参与者以及参与者之间的属性(也叫特征),行为和关系很好的描述出来的开发称之为面向对象的开发。(而我们的现实世界其实就是面向对象的!所以在程序员眼里万物皆对象。)
(2)万物皆对象
上述已说明,我们的现实生活中其实就是一个面向对象的世界。不管任何物种都包含了这个物种的特征跟行为。那我们来看一个例子如下图。
比如说下图中的电话,电话的外观,品牌,大小这些是电话的属性也叫特征。而接电话,打电话,发短信,打游戏这类的动作就是电话的行为。
而我们假如说要开发一个描述电话的代码时,就要先把这些元素通过代码的形式描述出来。(书写方法在后续更新)
那么我们简单概括一下,只要在软件系统中把某个对象的属性跟行为用代码的方式描述清楚了,也就是这个对象成了系统的参与者,那么这种开发方式就是面向对象的开发。
(3)类于对象的关系。
类于对象的关系,其实就是类型于对象之间的关系。比如说人于小张之间的关系。人是一个物种而小张是这个物种中的一个具体的个体。而这种关系其实就是类别于个体之间的关系,也就是类于对象之间的关系。
来我们用图像的方式更形象的来解释一下类以及对象之间的关系。如下图:
下图中小黑跟小白都有自己的姓名,性别,年龄以及各自的行为方式,而且都为同一物种。所以我们把小白跟小黑归类为狗狗。
那么写代码也是一样,我们可以把相同属性的数据或者说是物种归为一类。然后再定义他们的行为,并且执行。
下图为相反案例,我们从外观上就可以看出狗跟猫的不同。而且他们之间的属性跟行为也各有不同。
所以当我们写代码时,也要把不同特征的事物分别作为不同的类来定义,并且要分别描述出各个类中单个个体的参与者的属性跟行为。
那么问题来了,我们如何把对象归为一类呢?
参照下图来描述,我们要将对象抽取成类,必须要遵循具有共同的属性和相同的行为这样一个规则来进行抽取的,这样我们才能确定这个类别具有哪些属性和行为!
也就是说类其实就是具有一些相同属性和共同行为的一些对象的集合,类可以是一个概念,也可以看成是一个模板,面向的对象是一个真实存在的实体!
类是从对象当中抽取出来的,对象是类当中的一个实例,这就是类于对象的关系于区别。
2.面向对象的优点以及三大特点:
(1)面对对象的优点其实下图内容很显而易见。就是用代码的形式于现实接轨,从而更高效的把开发成果展现出来。
这种开发方式也使得软件的参与者之间的交流更为顺畅。
(2)封装,继承,多态
封装:所谓的封装就是把所有部件都组合在一起,包起来。可以设想一下其实我们现实世界上的任何物体都是封装而成的。如果没有封装那么每个个体就成了这个物体的部件,乱七八糟的了。比如说人:是由眼睛,鼻子,耳朵等组成的,如果不封装也就不能称作人,人本身其实这也是一种封装的产物。
继承: 继承的话大家都很好理解,最简单的例子就是我们人。我们每个人都是从生下来就继承了上一代的基因。包括父母将来的财产等这些都是一种继承的方式。
多态:顾名思义就是指多种形态。还那我们人类做例子,比如说男人。要想做一个好男人,至少要在不同形态的3个方面来完成。比如说做个好儿子,做个好丈夫以及做个好爸爸。在不同的状态中扮演不同的角色,这种呈现方式在java中被称为多态。(可参考多态的图片)
总结:
上述讲了很多关于面向过程以及面向对象的内容。让我们再来对比一下面对过程以及面对对象的不同之处。
如下图中图1是面对过程的案例,从内容中我们就能一眼就能看出这是一个面向功能开发方面的拓扑,而图2中的案例是把要开发软件或者说代码的参与对象给排列出来了,而我们接下来的开发也是围绕着这些对象来进行代码的编写的。
3. 用代码的方式来实现面向对象的开发
写代码跟大千世界一样先要归类然后再针对类中的个体进行编写。也就是说面向对象的开发,就是分析对象的过程,我们分析完之后再根据这些对象的属性和行为抽取成类。我们的程序也是以类为单元的!那么我们是如何来描述类的呢?
参照下例我们来写一个实际的面向对象代码。图中的class 代表类型的意思。class后面跟的是创建类的名称。这里创建的是一个Person类,所以下面的代码也是围绕人类的特征来描述的。而代码中的name,gender,age是这个类型的特征也叫做属性。而void后面跟的eat,work是这个类的行为。至于eat(),work()中以及{}中的内容我们先不做讨论在后续的更新中会体现出来。需要关注的是这段代码本身描述的就是一个人的特性跟行为,合起来其实就是一个描述人类的一个类,知道这个就可以了。这样我们就通过代码的方式把现实生活中的人做了一个彻底的描述。
/*
* 人类
*/
public class Person { //特征(通过变量来保存特征) public String name; //描述(保存)人的姓名 public String gender; //描述(保存)人的性别 public int age; //描述(保存)人的年龄
//行为(通过方法的语法来描述对象的行为。所谓的方法就是一种语法) public void eat(String name){ System.out.println(this.name+"邀请"+name+"共进晚餐!"); } public void work(){ System.out.println(this.name+"工作理念:干活挣钱有饭吃!"); } public void work(String contect){ System.out.println(this.name+"工作理念:"+contect); } }
我们再来做一个例子,分别把类以及对象用代码的方式体现出来。同上例一样先定义一个类。下例因为是做一个电影类,所以我们用Film来作为这个类的名称。
然后分别定义电影中所有的属性,比如影片的类型,名字,主演,导演等。然后通过赋值变量的方式保存下来。声明变量的时候要加public类宝标识符,这个不要忘记。
定义好了类的属性后我们还要定义类的行为。我们简单的打印一下刚才定义类中所有的属性,然后打印出来。在此要说明的是,因为我们是通过变量的方式来保存类的属性的,所以在写代码的时候,这里也要通过变量的方式先把要打印的内容通过“+“给串联起来。那么变量中的值是如何传进来的呢?让我们接着看下一段代码。
public class Film { /* * 定义电影类 */ public String type; public String name; public String director; public String act;
//定义电影类的方法(也就是行为) public void display(){ System.out.println("电影类型:"+type+"\n电影名称:"+name+"\n导演:"+director+"\n主演:"+act); }}
OK,既然我们用变量的方式来存储了类的属性跟行为。那么这些变量目前为止只是存在了内存中还为被使用。既然我们定义了变量目的就是为了使用它们的。所以我们需要再重新创建一个class 然后在这个class中使用之前已经被我们创建好的类,然后给类中各个变量再赋值后就可以直接使用了。如下代码。
public class Film_test { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub
//直接使用创建好的Film类,并给类中的各个属性赋值 Film a = new Film(); //new Filme()的意思是创建概类型下面的个体。而a只是一个变量。
a.type ="爱情片"; a.name = "<微微一笑很倾城>"; a.director ="林玉芬"; a.act = "扬洋,郑爽"; a.display(); //因为之前创建类中的方法我们定义的变量是display。所以这里 .display();的意思就是直接调用之前类中定义好的display中的内容的意思。 }}
最终打印结果请参考下图内容
注释:上述例子中我们创了2个class。第一个class是Film,这个是面对对象的过程。而第二个创建的class,其实这个是面向过程而不是面向对象的。原因在于第二个类中
我们并没有对对象事物进行描述,而是为了完成一个功能才创建的。所以说第二个类其实是面向过程的。这两者之间的关系一定要分清楚。
OK,到目前为止我们对类的属性以及行为的定义,还有如何编写一个面对对象的代码应该有一个清晰的认识了。我们再来补充一个小知识点。下图中的代码是上述我们讲创建一个Person中的代码。对于定义类的属性跟行为的代码就不重复解释了。仔细看行为中()小括号中我们加入了参数。而这个参数我们在java中称之为局部变量。跟其他变量一样最终的目的也是为了传值而使用的。局部变量跟成员变量的变量名在java中允许相同。但是在使用的过程中最好对于成员变量,通过this.变量名的方式进行书写。以便于让别人知道你每个变量分别指的是谁。
另外下例中我们定义的2个work方法。一个是public void work(),另一个是public String work().这2者之间的区别在于void是不带有返回值的,直接输出。而定义的String类型的方法是带有返回值的,书写方式也不同。带有返回值的代码是通过定义变量,然后return 变量的方式来进行返回的。我们这2个work方法虽然书写格式不同但是实现的功能相同,我们把这种方法称之为“重载”!
public class Person { public String name; public String gender; public int age; public void eat(String name){ System.out.println(this.name+"邀请"+name+"共进晚餐!");//this.name是指上述的Public String name;中的name.也叫做成员变量. //而+name的name是指eat(String name)中的name.也叫做局部变量 } public void work(){ System.out.println(this.name+"工作理念:干活挣钱有饭吃!"); } public String work(String contect){ String msg = this.name+"工作理念:"+contect; //System.out.println(this.name+"工作理念:"+contect); return msg; } }
4.方法的重载
上述简单的提了下方法中重载的问题。那么我们说一下方法重载中的注意事项都有哪些。
(1)方法要同名 (2)参数项不同 (是指参数的类型或者个数不同)(3)方法是否能重载跟访问类型以及访问修饰符无关(public(访问修饰符),void(访问类型))
注释(2):下图中有三个work方法。但是()中的类型各自不同,所以当类型相同的情况下是可以被使用的。(如图1)如果同为String类型,而且两者同为只有一个参数的话那么就不允许创建方法了。但是如果同为String类型但是参数个数不同的情况下,Java中是允许创建方法的。(如图2)
图1:
图2:
5. 构造方法
什么叫构造方法?
简单一句话来讲,new后面跟的就是构造方法。我们是用new来调用构造函数来创建类的对象的。
还拿下面这个图片的例子举例。图1中new 后面跟的Film()其实就是我们所讲的构造方法。那么我们再来看图2,图2中是我们定义这个Film类的对象。但是我们并没有看到有定义一个Film()的构造方法,那么它是从哪里来的呢?那是因为Java虚拟机,会提供一个默认的无参的构造函数。
图1:
图2:
构造方法都有哪些特征?
(1)于类同名 (new后面的构造方法是调用创建的类,所以要跟类同名)
(2)不指定返回值类型 (比如说new Film()是java虚拟机给我们自动创建的构造方法。实际上创建的是一个"public Film(){ }"这样一个无参数的构造方法,这种构造方法是不带返回值的,而我们之前的方法中是带有返回值的。如:return以及System.out.println();)
(3)有参构造方法于无参构造方法(其实上述例子中已经涉及到有参及无参的案例了。说白了就是小括号中是否有定义变量或者说定义参数,如果有定义就是有参构造方法,没定义就是无参构造方法。在有参构造方法中还分1个参数跟多个参数,如果函数名相同,那么参数的类型跟数量不能相同。相反如果函数名相同但是类型不同或者类型相同,参数不同的情况也可以允许使用)
public class Person { public String name; public String gender; public int age; public void display(){ System.out.println("姓名:"+this.name+"\n性别:"+this.gender+"\n年龄:"+this.age); System.out.println(this.name+"邀请一个"+"年龄"+age+"岁,名字叫"+name+"的"+gender+"孩一起看电影!"); } //无参构造方法 public Person(){} //1个参数的构造方法 public Person(String name){ this.name = name; System.out.println(this.name+"邀请一个"+"年龄"+age+"岁,名字叫"+name+"的"+gender+"孩一起看电影!"); } //多个参数的构造方法 public Person(String name,int age,String gender){ this.name = name; this.age = age; this.gender = gender; }}
public class Person_test { public static void main(String[] args) { Person per = new Person(); per.name = "小王"; per.gender = "男"; per.age = 22; per.display(); Person per1 = new Person("小刘"); Person per2 = new Person("小张",22,"男"); } }
6, 面对对象三大特性之一(封装!)
封装上述我们已有讲过。简单概括一下就是把各个部件包起来组合在一起。例如汽车引擎,发动机,手机电路板等这些都是我们平时在面上不经常看到的。而我们经常看到的是手机本身或者汽车本身。那么封装表现的其实也是被藏起来的这部分东西。下面的图也很形象的表达的什么是封装。
(1)封装代码案例
下面这段代码中的内容在上述举例中其实多次出现过,从案例中我们可以看出是一个描述人类的代码。也就是内容封装的是人类的属性跟行为。
那么对于属性的定义中我们还学习了什么是构造方法,在下例中,存在了2个构造方法。那让我们一一再来回顾一下其中的具体内容。
(无参构造方法:在图1中,class中定义了Person类并定义了三个变量分别来接收人类属性的值。而public Person()中并没有指定任何的参数,所以我们称之为无参构造方法。再下面是分别对定义好的变量直接赋值,并在void方法中直接打印。而图2中我们直接拿定义好的Person,new一个新的构造方法,并把这个构造方法赋值给一个变量xm。然后因为图1中已经把打印所需的内容在构造方法中都直接赋值了。所以我们在图2中直接通过xm.display();的方式调用图1中的构造方法就可以了。)
图1:
图2:
(有参构造方法:)
看完无参我们再来看一下有参。还是上面这个例子。只是我们在面向功能的这个PerTest类中把new Person后面的括号添加了参数而已。添加参数的前提是在Person类的构造方法中事先已经创建了参数的基础上才能赋值,不然是不可以赋值的。这个可以跟变量一起去理解。这样做的好处是,不用对于局部的各个属性一个个的赋值,我们可以通过传参的方式一并赋值,效率更高效。那么其打印的结果其实跟无参打印的结果是一样的。具体可以参考下图以及相关的代码。
public class Person { public String name; public String gender; public int age; //无参构造方法 public Person(){ this.name = "小明"; this.gender ="男"; this.age = 23; } //有参构造方法 public Person(String name,String gender,int age){ this.name = name; this.gender = gender; this.age = age; } public void display(){ System.out.println("******************************************"); System.out.println("自我介绍"+"\n姓名:"+this.name+"\n性别:"+this.gender+"\n年龄:"+age); System.out.println("******************************************"); }
public class PersonTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Person per = new Person("大嘴","女",18); //per.gender="不男不女"; //per.age = 222; per.display(); }}
上述有参跟无参的案例中最终所打印的结果可以看出,输出的性别跟年龄等的属性值都是日常我们认为是正常的结果。那么来看一下下图中的输出值,明显看出输出的结果跟我们日常的性别跟年龄是有很大差异的,可以讲世界上应该不存在不男不女以及能活到222岁的人。(人妖是特例。。。。)但是如果我们输出值指定的是非法值时,计算机是没有那么智能可以自己来做判断的。它只默认是一个可以输出的字符串跟数字就打印在屏幕上了而不会像我们人类一样去思考有没有能活这么久的人等等这样的思维。那么问题来了。如何能做到不让输出这种非法的数字,字符串输出呢?通过什么方法可以防止这个问题的出现呢?让我们接着往下看。
MyEclipse开发工具,给我们提供了很多内置方法,我们可以直接拿来使用。这样省去了我们自己来敲代码的时间了,非常的方便。那么我们一起来看一下get以及set相关的方法。在类调用方法或者其他空白处右键然后如图所示,选中Source然后再选中“Generate Getters and Stters...”。
图1:
在弹出来的窗口中选择想要封装的属性然后直接点击OK。
图2:
点击完OK后,java会自动帮我们创建一组get,set成对的代码组。而get跟set的主要作用就是针对我们想封装的个别属性来做封装的。听起来比较绕口。其实想强调的是在封装个别属性的过程中需要满足2个条件。首先条件1是必须要把我们之前定义类中人类的属性从public变成private。很显然public从字面意义上看是公共的意思。也就是说所有人都可以对其进行修改,调用等。但是如果我们不想让部分人来修改我们的某些属性,就可以通过定义private的方式来限制权限的访问。private是私有的意思,跟个人所有权跟公有所有权的道理是一样的。另外条件2是既然我们已经限制了对部分属性的调用跟修改,那么在推广以及使用中对我们自身其实也是有限制的。这样就大大降低了使用率。为了解决这个问题,所以就如图3中所示,需要定义get,set的构造方法来获取以及设定我们想更改的部分属性。通过这种方式来限制直接被人修改调用的风险。具体代码请参考图3,图4中的内容。
图3:
图4:
另外还需要说明一下因为图4中我们把public变成了private,那么也就限制了所有人的访问跟调用。所以在PersonTest类中per.gender以及per.age就会报错。因为它们访问不到Person类的人类属性了。
首先要解决这个问题就需要让计算机通过我们刚才创建的get,set的构造方法去访问如图5,6。
图5:
图6:
上述图5跟图6的对比可以看出在图5中的报错,在图6中消失了。但是奇怪了为什么输出内容还是显示非法字符跟数字而且还不报错。那么究竟能不能正常输出呢?不是用了get,set后就能避免的吗?让我们继续再来看一下图7中的结果,首先看是否能够正常的打印出来。
图7:
结果是可以输出跟之前没用get,set的结果其实是一样的。NND在骗我,在骗我,在骗我吗?可能到此为止很多人跟初学java的我一样可能都会产生疑惑。别急,让我们慢慢道来。
计算机没那么聪明,没那么聪明,没那么聪明。重要的事情说三遍。它是不会帮我们自己去判断的。即使我们用了get跟set的方法。但是我们并没有在set方法中做任何的设置,保持的是默认值。所以当我们在调用的时候。虽然类中的定义已经是private的了。但是我们在创建get跟set的时候类运算符用的是public而不是private。所以我们在PersonTest类中才可以通过setAge,setGender去访问到类中属性。这是可以访问到类属性元素的主要原因。但是因为我们没有指定任何条件来限制我们的输出结果,所以无论你怎么写计算机还是会认为你的输出是OK的,所以才能在屏幕上打印。要想彻底的限制我们的输出,必须通过条件语句来限制输出内容。让我们继续往下看。
回到类的行为中来,我们用刚才创建的set方法分别加入条件语句的判断,然后后在条件成立的时候才输出我们想要看到的正常结果。如果条件不成立我们会设定规则,下列图8中的规则是我们设置如果条件不成立就恢复到默认值。这个默认值其实是PersonTest类中 Person per = new Person()中我们给的赋值,就是我们的默认值。然后通过return语句返回到默认值打印结果。
图8:
public class Person { private String name; private String gender; private int age; //无参构造方法 /*public Person(){ this.name = "小明"; this.gender ="男"; this.age = 23; } //有参构造方法*/ public Person(String name,String gender,int age){ this.name = name; this.gender = gender; this.age = age; } public void display(){ System.out.println("******************************************"); System.out.println("自我介绍"+"\n姓名:"+this.name+"\n性别:"+this.gender+"\n年龄:"+age); System.out.println("******************************************"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { if(gender.equals("男")||gender.equals("女")){ this.gender = gender; }else{ System.out.println("非法性别,重置为默认值!"); return; } } public int getAge() { return age; } public void setAge(int age) { if(age<0 || age>200){ System.out.println("非法年龄,重置为默认值!"); age = 18; return; } this.age = age; } }
public class PersonTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Person per = new Person("大嘴","女",18); per.setGender("不男不女"); per.setAge(222); per.display(); }}
总结:
7.包的创建跟使用和类的使用
(1)包的作用:
让我们先了解一下java中包都有那些作用。如下图。
其实对于java中的包而言就跟我们windows以及linux上的文件夹和目录是一个概念。它的主要作用就是来存放管理java文件的。并且对于相同名称的文件冲突,可以通过创建不同包的方式存放。文件名虽然相同但是存放点在不同的包内的话,也不会发生文件名冲突。这样就大大方便了我们对于java文件的管理。
比如说我们在下图中的(default package)这个包中再建立一个名为Person类的时候,就会发现Java Class下方会显示“Type already exists.”包已经存在。一个包中不允许有重名的类出现所以才报错。如果在不同的包中创建Person类的话是允许的。如图2.
图1:
图2:
(2)包的创建
创建包的方式很多,最标准的方式就是从File中点击New然后再选择Package来创建。也可以在当前的javaProject上右键New然后选中Package的方式来创建。也有快捷方式直接点击Package案件来创建。图1为当前javaProject右键创建的。
图1:
点击后会弹出New java Package的对话框。Source folder是当前projeck的路径,而包是创建在这下面的。我们在Name中指定包的名称。如图2
图2:
※要注意了。在上述包的创建中我们给包的命名时用了“. “的方式来分隔。而这个“ . ”在这里是指上下级的意思。相当于windows系统中父文件夹于子文件夹之间的关系。我们命名为“java.liupeng.sep”那么实际在我们的windows机器上显示是分别创建了3个文件夹。它们之间的关系也是父于子文件夹之间的关系。
图3:
(3)包的使用
一个新包的创建完之后,因为包中没有任何文件,所以刚创建好的包是白色的。上图3中其实已有显示。当我们把包内添加文件后,这个包的颜色也就相继改变代表里面有文件的意思。如图4。
那么我们再来看一下刚才我们所说的。在不同的包中可以存储相同名称的文件的案例吧。
图4:
另外除了我们创建新包起相同名称的类文件以外。我们还可以直接把一个包中已经存在的包直接拖拽到另外一个新创建的包中如图5.随着包的移动我们仔细观察会发现在代码中的最上端会出现Package开头的代码,而后面跟的是这个包的存放路径。这个路径是不允许被删除掉的。如果被删掉代码会因找不到包,无法执行其中的代码而报错。
图5:
还可以这么玩。如果图5中的Person.java跟PersonTest.java这2个文件分别放在不同的包中的话,在执行代码的时候需要注意的是Test类必须要把路径指向实际类。那么指向在代码中是用import来定义的。
如图6。要明确知名Person类所存放的地址后方能被执行。那么通过这个案例可以看出如果以后我们在写代码的时候不用什么类都靠自己来写。如果有已经存在的并且是自己想要使用的类的话,可以通过直接编写面向过程的test类并import源类型就可以了。这样大大提高了编程的效率也减轻了程序员的负担。
图6:
(4)包的功能说明
在java中存在很多不同种类的包,这些包里内置了很多用法,当我们想要使用这些方法的时候必须先用import的方式来导入相应的包之后才能被使用。下面图中所显示的只有4种包的类型。其实还有很多第三方等工具包以方面我们在各个领域要用java做开发的使用来使用。先简单来看下下面的这4种类型的包。
<1> java.lang : 比如说我们创建字符串的String,创建数字的int等这些基础的语言类都包含在java.long这个包中。但是我们在声明变量的时候并没有事先先import java.long。那是因为java.long这个包不需要导入就默认可以直接来使用。而其他的包不同是需要先导入后使用的。
<2>java.util :工具类的包一般都放在这里。比如说之前我们用的Scanner这个方法,就必须实现先导入java.util*.
<3>java.io :包含了输入,输出相关功能的类。比如说要想对电脑中的文件以及文件夹进行操作的时候。就必须事先导入java.io这个包。
<4>java.sql : 顾名思义当我们想对包含数据库操作相关的数据的时候就要导入java.sql这个包。
8,访问控制
所谓的访问控制就是对类的访问以及类成员的访问来进行控制。
(1)类的访问控制
针对类的访问控制就如同有一个Public Class Person{}的类。这个类中Public是访问修饰符,其本身的含义是公有的意思。言外之意就是允许所有人对其进行访问。如果我们把public去掉的话,那么其他成员在调用Person这个类的时候就会被拒绝访问。那么就从公有变成了私有。java中称之为默认访问。如图2,图3
图1:
图2:
图3:
上述出现报错是因为Person per = new Person("大嘴","女",18)中的代码在我们的Person类中不是公有的,而是私有的。因为我们把类的属性从public变成了private了。所以当我们把类前的public删除后,java对于私有的类内容进行访问时就出现拒绝,因而出现报错。那么如果对于没有public直接Class创建的默认类,我们只能在本包内的基础上才允许进行访问而不能在其他包中来调用它。(这里就不再列举案例了)
(2)类成员的访问控制
那么对于成员类型的访问控制我们可以参照下表中的数据。清晰的列举了不同访问修饰符下所具有的访问控制权限。具体用法已经在各个案例中所列举了所以在此不再继续说明。
对于protected的使用我们目前还没有列举案例。这个会在日后的继承讲解中做具体说明。
总结:
10. Static 关键字其实static关键字跟封装的关系不是那么大。在这里只是微微提一下这个Static关键字。因为当我们用在使用Static关键字的时候往往会给我们带来很多意想不到的效果。
下图中的案例是一个描述投票的类。类中的属性包含了统计投票总数,投票人的人名以及投票人的个人票数。然后定义一个有参构造方法,设置一个name变量分别来统计每个投票人的人名。
在行为中设置一个无参方法,内中分别记录每个人投票的总票数,以及每个人当前的票数。最后打印结果。
package newtest;public class VoteAction_test { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub VoteAction per = new VoteAction("老刘"); VoteAction per1 = new VoteAction("老张"); VoteAction per2 = new VoteAction("老李"); VoteAction per3 = new VoteAction("老王"); per.getAllvote(); per1.getAllvote(); per2.getAllvote(); per3.getAllvote(); }}
package newtest;public class VoteAction { static int count; //计票总数 private String name; //投票人姓名 private int votedself;//当前个人投票数 public VoteAction(String name){ this.name = name; } public void getAllvote(){ count++; votedself++; System.out.println(this.name+"投票次数为:"+votedself); System.out.println("候选人总票数为:"+count+"\n"); }}
从上述代码以及图中所显示的输出结果中可以看出每个投票人的名字跟票数都为1,而候选人总票数在累加并没有为1.这其中的原因就是static起到的关键作用。因为静态变量的生命周期比非静态的生命周期要长。
当老刘在投票后代表着private标识符的类型的生命周期就结束了,而static的生命周期在此并没有结束还在继续累加。一直到所有对象的票投完了。那么static的生命周期才真正结束。
static其实体现的是一种共享的思想并非封装。通过这个反比的例子来加深封装的概念。
总结:
11, 面对对象三大特性之一(继承!)
(1)为什么需要继承:
我们人类社会中其实就已经充分体现了继承的概念。如父亲于儿子等之间的关系。java是面向对象的语言,而我们代码中的继承的含义跟现实生活中的继承是一个意思。那为什么要用继承呢,主要原因在于继承会省去我们很多不必要重复编写的代码,通过继承子类可以直接调用父类中的代码来使用。这样大大缩短了开发周期,也降低了开发成本,同时也增加了程序的易维护性。
另外向java,Pyhton,swift等这些面向对象的语言中给我们提供了很多类文件,我们是基于类文件的基础上去开发的。所以不需要我们一一去写这些类文件,可以直接拿来使用。这本身就提高了开发的效率。这种方式也是一种继承。
下图是继承中继承的主要作用:
(2)通过代码体现继承的好处
首先看下面2个代码,分别是描述2个不同部门的类。对比一下人事部跟研发部的代码,我们会发现2个类中的代码很多都是相同的,也就是重复代码。这样每当我们在创建新的对象类时,都要重复写一遍相同代码的话,那么就会大大降低我们写代码的质量跟效率。所以为了避免这种情况的发生,我们就要用到继承的概念。
package dept;/* *人事部 */public class PersonelDept { private int ID; //部门编号 private String name; //部门名称 private int amount=0; //部门人数 private String responsibility; //部门职责 private String manager = "无名氏"; //部门经理 private int count;//本月计划招聘人数 public int getID() { return ID; } public void setID(int iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public String getResponsibility() { return responsibility; } public void setResponsibility(String responsibility) { this.responsibility = responsibility; } public String getManager() { return manager; } public void setManager(String manager) { this.manager = manager; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public void display(){ System.out.println("部门:"+this.name+"\n部门经理:"+this.manager+"\n本月招聘人数"+count+"\n部门职责:"+this.responsibility+"\n****************************************"); }}
package dept;/* * 研发部 */public class ResearchDept { private int ID; //部门编号 private String name; //部门名称 private int amount=0; //部门人数 private String responsibility; //部门职责 private String manager = "无名氏"; //部门经理 private int speciality;//研发方向 public int getID() { return ID; } public void setID(int iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public String getResponsibility() { return responsibility; } public void setResponsibility(String responsibility) { this.responsibility = responsibility; } public String getManager() { return manager; } public void setManager(String manager) { this.manager = manager; } public int getSpeciality() { return speciality; } public void setSpeciality(int speciality) { this.speciality = speciality; } public void printDetail(){ System.out.println("部门:"+this.name+"\n部门经理:"+this.manager+"\n研发方向"+speciality+"\n部门职责:"+this.responsibility+"\n****************************************"); }}
而在java中当一个类要继承另一个类的时候。要用到extends关键字。关键字左边为子类,右边为继承的父类。这样做的话就可以删除子类中父类已经包含的内容了,这就是代码中类的继承。详细请参考下列内容。
package dept;/* * 部门类 */public class Department { /** * 属性(成员变量) */ private int ID; //部门编号 private String name; //部门名称 private int amount; //部门人数 private String responsibility; //部门职责 private String manager = "无名氏"; //部门经理 public int getID() { return ID; } public void setID(int iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public String getResponsibility() { return responsibility; } public void setResponsibility(String responsibility) { this.responsibility = responsibility; } public String getManager() { return manager; } public void setManager(String manager) { this.manager = manager; } /** * 行为(打印部门信息) */ public void printDetail(){ System.out.println("部门:"+this.name+"\n部门经理:"+this.manager+"\n部门职责:"+this.responsibility+"\n****************************************"); }}
package dept;/* *人事部 */public class PersonelDept extends Department{ private int count;//本月计划招聘人数 public int getCount() { return count; } public void setCount(int count) { this.count = count; }}
package dept;/* * 研发部 */public class ResearchDept extends Department { private int speciality;//研发方向 public int getSpeciality() { return speciality; } public void setSpeciality(int speciality) { this.speciality = speciality; }}
上述代码中我们创建了部门类。而人事部跟研发部中我们通过extends关键字来让人事部跟研发部分别继承了部门类。这样做部门类就成为了人事部跟研发部的父类。也就是说部门类中的所有内容,在人事部跟研发部中都可以直接调用来使用。
注意:在人事部跟研发部中分别去掉了“方法“。关于方法的继承会在接下来的内容中记录。此节先说明类的继承。
这样做的好处已经显而易见了。对于子类中的代码就显得清晰明了,以及方便于程序员的编写,更提高了代码的实用性。
说了那么多,那么真实的代码是否能被真正使用呢?让我们看一下下面这个测试类。
下面图1中的案例为人事部的一个测试类。我们在测试类中创建人事部的对象看是否能正常创建。
图1:
如图1所示,并没看到代码出错,说明是可以创建的。那么我们图2为人事部的属性及方法。但是并没有看到有ID,Manger以及Amount的相关属性,那么既然没有相关属性,测试类中为了不报错呢。让我们继续看图3中的内容。
图2:
图3中是我们的部门类,也就是我们的父类。在父类中我们已经定义好了各个部门的相关属性了。而图2中人事部类extends Department,已经继承了父类,因此就产生了继承的关系。因而在测试类中当我们创建人事部成员的属性时没有产生报错,允许创建。
图3:
总结:
1. 被继承的类称为父类或基类。
2.继承父类的类称为子类或派生类
(3)理解继承概念及其特征
想必大家已经对继承都已经有一定的理解,所以就不反复重复继承的概念了。一副图说明继承的概念参照下图内容。
(4)继承的特性
继承的特性跟我们现实生活中的人是一样的。每个人都只能有一个亲爸妈。这种直属的关系在继承中这也叫做单根性。说白了继承只能继承一个直属父类,不能继承多父类。直属继承的语法也就是extends的使用方法。
但是除了直属父类外,现实生活中还有干爸,干妈的说法。在我们面对对象的语言中也很形象的体现了这种表达方法,我们对于干爸干妈的概念在java编程中称之为“接口”,一个类可以同时实现多个接口。这个会在后续继续更新。
说完子类继承父类。那么父类还可以继承父类吗?答案是当然可以。比如还是上面这个例子。我们再建立一个新类。叫做SuperDept的类。然后让Department类 extends 到SuperDept类这样就相当于爸爸继承了爷爷的属性。那么作为孙子的人事部跟研发部也同时可以继承爷爷辈的类,代码中在Department以及PersonelDept,ResearchDept中没有的部分但是在SuperDept中含有的部分就可以继承过来。(这里就不上图片解释了)
另外对于子类能继承父类的什么呢?如下图。
如上图内容所示,我们逐一通过代码的方式展示一下子类于父类之间都有哪些是可以以及不能继承的部分。
<1>无法继承父类的构造方法:
如下图1-2我们首先在再父类中创建2个无参跟有参的函数。而图2中我们在调用父类中的有参函数时报错。当我们自身主动赋值时发现找不到父类中定义的参数变量出错。从这个例子中就验证了我们上述所讲额子类无法继承父类的构造方法。
图1:
图2:
<2>无法继承private修饰的属性和方法
图1为父类中的属性,我们定义的是private。而当我们在图2的测试类中想要直接访问父类中的private属性时会发现报错。因为父类中的private属性是私有的只有在父类同类中的测试类才允许访问此属性。而我们能够访问get,set是因为get,set的类标识符为public所以才允许我们访问。
图1:
图2:
<3>子类可以继承父类默认修饰符的属性和方法,但必须在同一个包里
图1中我们把父类中的get,set前public修饰符去掉后,那么它就变成了一个默认修饰符。当变成默认修饰符的时候我们再继续看图2中的内容会发现,图2中的代码出现了报错。(图2为图1同样的代码copy过来的)。但是图3中我们在PersonelDept这个子类中创建一个void test构造方法。在这个构造方法中再调用this.getID(),this.setID();时并不报错。那是因为父类跟子类是在同一个包中。对于子类继承父类默认修饰符的属性跟方法,跨包是不允许访问的。
图1:
图2:
图3:
<4>子类继承public和protected修饰的属性和方法,不管子类和父类是否在同一个包中
关于public和private的使用相信我们都很清楚了。对于protected 也是用与继承的。而对于protected的使用描述如下。
父类用protected修改的成员,同一包中的子类是可以调用访问的。不同包中调用代码如果直接写在子类里是可以的,否则会报错。(如果写在跨包的测试类中是不允许访问的。因为不属于父类中的子类)
(5) 继承中的方法重写
关于继承的重写就是当我们的子类在调用父类中的内容时,不光包含了父类而子类自身的属性也一并想要输出打印时。会发现输出的只有父类中的内容。而子类中的属性并没有被打印。这种时候就代表了父类不能满足于我们子类的需求。那么想要一并输出子类的属性就要使用@Override ,super的使用方法。而supper的使用方法必须在父类的子类中使用。非父类的子类是无法直接使用的。
package test;/* * 部门类 */public class Department { /** * 属性(成员变量) */ private int ID; //部门编号 private String name; //部门名称 private int amount; //部门人数 private String responsibility; //部门职责 private String manager = "无名氏"; //部门经理 public int getID() { return ID; } public void setID(int iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public String getResponsibility() { return responsibility; } public void setResponsibility(String responsibility) { this.responsibility = responsibility; } public String getManager() { return manager; } public void setManager(String manager) { this.manager = manager; } /** * 构造方法 */ //无参构造方法 public Department(){} //有参构造方法 public Department(int id,String name,int amount,String responsibility,String manger){ this.ID =id; this.amount=amount; this.responsibility = responsibility; this.manager =manger; } /** * 行为(打印部门信息) */ public void printDetail(){ System.out.println("部门:"+this.name+"\n部门经理:"+this.manager+"\n部门职责:"+this.responsibility+"\n****************************************"); }}
package test1;import test.*;public class DeptTest{ public static void main(String[] args) { PersonelDept dept = new PersonelDept(); dept.setID(1000); dept.setManager("老王"); dept.setAmount(30); dept.setName("人事部"); dept.setResponsibility("人员招聘等工作!"); dept.setCount(20); dept.printDetail(); }}
package test;/* * 人事部 */public class PersonelDept extends Department{ private int count; public int getCount() { return count; } public void setCount(int count) { this.count = count; } /*public void test(){ this.getID(); this.setID(100);*/ @Override public void printDetail() { // TODO Auto-generated method stub super.printDetail(); System.out.println("招聘人数:"+this.count); }}
如上述代码以及图中所示。通过super方法我们可以在子类中重写父类的代码,并且可以添加子类中父类不包含的属性。这样就满足了子类的需求。继承父类以及自身的属性都可以一并的打印出来。这就是继承的魅力。
OK,我们上述对重写的概念以及案例做了演示,可以看出所谓的重写其实就是父类与子类中方法之间的关系。子类重写父类方法时,在子类对象调用方法时,优先执行子类的重写方法。
总结:
重载的要求:
1.同一个类中
2.方法名称相同
3.方法参数类型不同或方法参数个数不同
4.与返回值和访问修饰符无关
重写的要求:
1.有继承关系的类,方法名称相同
2.相同的参数列表
3.返回值类型必须相同或是其子类型
4.不能缩小被重写方法的访问权限(比如说父类的访问类型为public,而子类定义的类型是private等小于public的权限时是不允许的)
(6)Super关键字的使用
Super代表当前对象的直接父类对象的默认引用。仔细品味一下这句话的意思。我们还拿上面这个例子来解释一下super究竟是代表什么。
比如说super.printDetail();我们通过@override生成的这句话其实就相当于我们写了以下两句代码:Department是我们的父类,我用创建父类的对象把值赋给suDep。然后suDep.printDetail();其实就是调用了父类中void中的方法了。跟我们用super.printDetail();语句的调用其实是一样的。
Department suDep = new Department();
suDep.printDetail();
另外关于super的使用方法中还有3种注意事项,请参照上图内容所示。
package Department;/* * 部门类 */public class Department { private int ID; //部门编号 private String name; //部门名称 private int amount=0; //部门人数 private String responsibility; //部门职责 private String manager = "无名氏"; //部门经理 private int speciality;//研发方向 public int getID() { return ID; } public void setID(int iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public String getResponsibility() { return responsibility; } public void setResponsibility(String responsibility) { this.responsibility = responsibility; } public String getManager() { return manager; } public void setManager(String manager) { this.manager = manager; } public int getSpeciality() { return speciality; } public void setSpeciality(int speciality) { this.speciality = speciality; } /** * 构造方法 */ //无参构造方法 public Department(){ //System.out.println("我是父类!"); } //有参构造方法 public Department(int id,String name,int amount,String responsibility,String manger){ this.ID =id; this.amount=amount; this.responsibility = responsibility; this.manager =manger; } /** * 行为(打印部门信息) */ public void printDetail(){ System.out.println("部门:"+this.name+"\n部门经理:"+this.manager+"\n部门职责:"+this.responsibility+"\n****************************************"); }}
package Department;public class DepTest { public static void main(String[] args) { PersonelDept dept = new PersonelDept(); dept.setAmount(30); dept.setID(1999); dept.setManager("鬼脚七"); dept.setResponsibility("新人招聘,其他"); dept.setName("人事部"); dept.setCounter(50); dept.printDetail(); }}
package Department;/* * 人事部 */public class PersonelDept extends Department{ private int counter;//本月计划招聘人数 public int getCounter() { return counter; } public void setCounter(int counter) { this.counter = counter; } public PersonelDept(){ //System.out.println("我是子类!"); } @Override public void printDetail() { // TODO Auto-generated method stub super.printDetail(); System.out.println("本月招聘人数:"+counter+"\n****************************************"); }}
(7)构造方法与继承
下图中所讲的意思其实就是当子类在执行自身的构造方法时,因为继承的关系其实同时也执行了父类中的构造方法。这种执行其实是调用了父类的构造方法。那么我们之前说过子类是无法继承父类的构造函数的,但是不代表不能够调用父类的构造函数。在这里继承跟调用是两种不同的关系不要混淆。那么我们接着看一下继承条件下构造方法都有哪些调用规则吧。
构造方法调用的规则
<1>规则一
意思也就是说如果在子类的构造方法中没有使用super关键字或者this指明调用父类的哪个构造方法时,默认调用父类的默认构造方法。
package Department;/* * 部门类 */public class Department { private int ID; //部门编号 private String name; //部门名称 private int amount=0; //部门人数 private String responsibility; //部门职责 private String manager = "无名氏"; //部门经理 private int speciality;//研发方向 public int getID() { return ID; } public void setID(int iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public String getResponsibility() { return responsibility; } public void setResponsibility(String responsibility) { this.responsibility = responsibility; } public String getManager() { return manager; } public void setManager(String manager) { this.manager = manager; } public int getSpeciality() { return speciality; } public void setSpeciality(int speciality) { this.speciality = speciality; } /** * 构造方法 */ //无参构造方法 public Department(){ System.out.println("我是父类!"); } //有参构造方法 public Department(int id,String name,int amount,String responsibility,String manger){ this.ID =id; this.amount=amount; this.responsibility = responsibility; this.manager =manger; } /** * 行为(打印部门信息) */ public void printDetail(){ System.out.println("部门:"+this.name+"\n部门经理:"+this.manager+"\n部门职责:"+this.responsibility+"\n****************************************"); }}
package Department;/* * 人事部 */public class PersonelDept extends Department{ private int counter;//本月计划招聘人数 public int getCounter() { return counter; } public void setCounter(int counter) { this.counter = counter; } public PersonelDept(){ System.out.println("我是子类!"); } @Override public void printDetail() { // TODO Auto-generated method stub super.printDetail(); System.out.println("本月招聘人数:"+counter+"\n****************************************"); }}
结果:
结合上述代码我们可以看出父类中我们有2个构造函数,1个是无参的1个是有参的构造函数。而在子类中public PersonelDept(){}这个方法中我们并没有看到super关键字以及this.的使用方法。那么当这种情况下在调用父类的构造方法时,默认会先调用父类的无参构造方法,而不会调用有参构造方法,并且先调用父类构造方法再执行子类的构造方法。如下图所示。
<2>规则二
如规则二中所示我们一起来看一个例子。这样就更深刻的理解super调用父类构造方法的顺序了。
下图中当我们在子类中使用super关键字要调用父类的有参构造方法时,不能用super.的方式去调用。要通过super()小括号的方式去访问。
当我们用小括号时会发现下图所示,会出现2个选项1个是父类中无参构造方法,另一个是有参构造方法。我们选择第二个有参的构造函数。
接着上例在子类中选择调用父类的有参构造函数了。那么下面是实际的调用后的代码以及事后结果。让我们一起来看一下其相关的具体内容。
package dept;/* * 部门类 */public class Department { /** * 属性(成员变量) */ private int ID; //部门编号 private String name; //部门名称 private int amount; //部门人数 private String responsibility; //部门职责 private String manager = "无名氏"; //部门经理 public int getID() { return ID; } public void setID(int iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } public String getResponsibility() { return responsibility; } public void setResponsibility(String responsibility) { this.responsibility = responsibility; } public String getManager() { return manager; } public void setManager(String manager) { this.manager = manager; } /* * 无参构造方法 */ public Department(){ //System.out.println("我是父类"); } /* * 有参构造方法 */ public Department(String name,int ID,int amount,String manager,String responsibility ){ this.name = name; this.ID = ID; this.manager =manager; this.responsibility = responsibility; this.amount = amount; } /** * 行为(打印部门信息) */ public void printDetail(){ System.out.println("部门:"+this.name+"\n部门经理:"+this.manager+"\n部门职责:"+this.responsibility+"\n****************************************"); }}
package test1;import dept.*;public class DeptTest { /** * @param args */ public static void main(String[] args) { // 创建人事部对象 PersonelDept pdept = new PersonelDept(); pdept.setID(1000); pdept.setManager("大白"); pdept.setAmount(20); pdept.setResponsibility("人事招聘"); pdept.setName("人事部"); pdept.setCount(50); //pdept.printDetail(); ResearchDept rdept = new ResearchDept(); rdept.printDetail(); }}
package dept;/* * 研发部 */public class ResearchDept extends Department { private String speciality;//研发方向 public String getSpeciality() { return speciality; } public void setSpeciality(String speciality) { this.speciality = speciality; } //子类构造方法 public ResearchDept(){ super("人事部门", 1999, 100, "三胖", "人事招聘及其他"); this.speciality = "大数据"; } @Override public void printDetail() { // TODO Auto-generated method stub super.printDetail(); System.out.println("研发方向:"+this.speciality); }}
结果:
在子类中通过super()关键字调用了父类的有参数构造方法后,把父类相应的参数在super()中先声明。this.sepciality=seciality;是研发部自己特有的属性所以这里通过this.的方式赋值。跟上述例子一样要想在调用父类的构造方法并且要包含子类中独有的构造方法时必须先通过override,super关键字来重写父类的构造方法,然后再打印子类中的构造方法。其结果如下图所示不光调用了父类中的有参构造函数的方法同时也调用了子类独有的构造方法。
注意事项:
当我们在子类中用super关键字或this关键字调用父类的构造方法时,必须写在第一行,否则会报错。
<3>规则三
package dept;/* * 研发部 */public class ResearchDept extends Department { private String speciality;//研发方向 public String getSpeciality() { return speciality; } public void setSpeciality(String speciality) { this.speciality = speciality; } //子类构造方法 //无参 public ResearchDept(){ this("大数据"); //this.speciality = "大数据"; } //有参 public ResearchDept(String speciality){ super("人事部门", 1999, 100, "三胖", "人事招聘及其他"); this.speciality = speciality; } @Override public void printDetail() { // TODO Auto-generated method stub super.printDetail(); System.out.println("研发方向:"+this.speciality); }}
规则三中所示,在子类的构造方法中通过this关键字调用自身的其他构造方法。我们在上述代码中定义了子类中的2个构造方法,1个有参1个无参。当在无参构造方法中通过this关键字调用自身有参构造方法时。要求this关键字必须放在首行。而且写法是this("")小括号的方式而不是this.的方式;那么问题来了。super也是要放在首行,this调用自身其他构造方法也要放在首行那么就产生了冲突。那么为了解决这个问题我们把super关键字的代码方法有参的第一行。那么无参中的 this跟有参中的super就都满足了在首行的要求。再看看结果跟规则二中结果的图片一致。说明是可以正常被调用的。
12, Objeck类
<1>Objeck简介Objeck类是所有类的父类。当一个类在没有父类的时候,那么Objeck就等同于是它的父类。如果拿人类来做例子的话,那么这里的Objeck就相当于人类的鼻祖。比喻成神话中的女娲娘娘是一样的道理。
就例如我们下图中的代码显示一样Department是我们PersonelDept,ResearchDept的父类,它本身没有父类。但是我们在代码中extends Objeck后并没发现代码报错,dept包中也没发现有一个Objeck的类。由此可以看出当我们extends Object后那么Department的父类就变成了Object。而Objeck类是不需要手动创建的。它可以称为任何类的父类。
<2>Objeck类中都包含那些东东
上面我们提到了我们并没有创建Objeck类但是能够让其他类继承这个类。那么它不是凭空出世的,它是我们java虚拟机自动为我们创建好的。下图1中就是Objeck类中所包含的所有方法。当我们想要查看这些方法时只需要在我们的Myeclipse按住ctril键然后点击Objeck就会自动跳转到下图1中的地址。而红框中java.lang.Class就是Objeck类的地址。
图1:
因为Objeck类中的方法很多,我们可以在日后的编程中遇到了在详细了解。在此我们主要还是针对以下这几个平时主要用到的方法来进行详细说明一下。
图2:
<3> equals的使用方法:
首先我们重点先来看一下equals()这个使用方法。在之前的讲解以及代码体现中我们曾用过equals来对2个值进行对比。而equals本身的含义也是做对比而使用的。那么为什么还要在此重提呢?原因是因为在Objeck类中除了可以用equals来对比2个值以外还可以用它来对比两个对象。也就是下图中所指的同一个引用对象,对比2个对象在内存中是不是同时指向同一块内存地址。
equals实例1:
我们再来回顾一下之前我们用equals来对比2个值时候的案例。当我们用String类型定义值的时候在使用equals对比时输出的结果是正常的。而当我们用int定义数值再来用equals来对值进行对比的时候会发现图2中的报错。明明是用对值进行对比但是int类型反而不能被equals所使用的原因是因为在我们的Objeck类中String类型默认是允许调用equals的使用方法的。而int也包含了equals的使用方法,但是默认是被屏蔽的。所以当我们用int类型想使用equals对比值时就会出现以下的报错。
从此可以看出虽然Objeck是我们所有类中的父类。但是并不代表所有类型都可以继承Objeck类中的方法。有些类型在Objeck类中,方法是被屏蔽掉的。
图1:
图2:
equals实例2:
对比完两个值之后我们再来看一下equals对比2个对象的案例。下面代码中我们创建了2个对象,分别为研发1跟研发2.当我们用equals对比这2个对象后所得出的结果却是内存地址不相同。
package Sep14;import dept.ResearchDept;public class EqTest { /** * @param args */ public static void main(String[] args) { // equals使用案例 String a = "10"; String b = "10"; if(a.equals(b)){ System.out.println("相同"); }else{ System.out.println("不同"); } ResearchDept yf1 = new ResearchDept("研发1"); ResearchDept yf2 = new ResearchDept("研发2"); if(yf1.equals(yf2)){ System.out.println("内存地址相同"); }else{ System.out.println("内存地址不同"); } } }
为什么我们都是指向了ResearchDept但是结果确实不一样呢?原因是我们分别new了2次。只要new了就代表在内存中新开辟了新空间。那么就代表了虽然内容相同但是在内存中的存储是代表了不同的地址。而equlas对比对象时就是对比的2者的地址的。所以这里显示的结果是内存地址不同。(如同A跟B都有宝马A6,宝马A6都是同一款的,但是使用的人缺不同。所以自然B的宝马就不是A的。跟我们上面这个例子是一个道理)如果我们把代码改写成下面的这种方式,那么再来运行看一下会发现得出的结果是内存结果相同。我们只new了一次,而yf4是用=运算符来指向了yf3的地址,它本身并没有在内存中重新开辟新的空间因此打印出来的结果是内存地址相同。
再看一下下面这个案例。首先String类型也是引用类。所以我们直接引用String来创建对象是OK的。然后在下例中我们都是对yf5于yf6来来做对比。一个是用equals而另一个用==的方式对比的结果却是截然不同的。
而yf5跟yf6通过String来创建对象时都用了new,说明都在内存中开辟了新的空间。那么既然都开辟了新空间。而且在equals对比对象时不是应该不相同才对吗?为什么在这里确实相同的结果呢?那么让我们一一来解释一下。
解释:
1.在Objeck里定义的equals方法,在进行比较时,如果比较的是值类型(普通类型:int,double,char)那么其实比较的是值是否相等。
2.如果比较的是引用类型(对象。String也是引用类型),那么比较的是内存地址是否相同。
3.每new一次就是在内存中创建了新地址。所以说如第三种情况及第二种情况所示,new了2次后再比较地址不同,new了1次yf4=yf3的话其实指向的是内存中同一块地址。
4.但是不排除某些类会屏蔽掉Objeck类的方法。如方法1中的int。也不排除某些类会重写Objeck类的方法。而我们下图中的例子就是这种情况。new了2次也确实是在内存中开辟了2块地址这个没有改变。但是结果却告诉我们指向的是同一块地址,其中的原因是在String创建字符串对象后用equals来对比字符串对象时,String类中的equals方法已经被重写了。其实对比的不是内存地址而是值。
5. ==号是运算符,如果是基本数据类型,那么比较的是值。如果是引用数据类型,那么比较的是内存地址。它不存在被屏蔽或重写。
13,多态
(1)多态的概述:
多态就是根据不同的情况,变换不同的角色。例如一个男人在一生中至少会扮演父亲,儿子,老公的角色。在不同的情况下的职责不同。那么代码也是一样,具有以下很多种不同的形态,体现了多态的灵活性。
另外官方对多态的解释如下。同一个引用的类型,使用不同的实例而执行不同的操作。比如说同一样物品对于不同的人看待这种物品的态度不同。(例如金钱,有的人对它可能欲望不是那么强,够花就够。而可能另外一部分人对钱的占有欲就比较强。可能会在某些方面为了钱不择手段。所然例子不是很贴切但是充分体现了同一样物品对于不同的人看待的态度不同,这也是多态的一种体现。)
(2)多态的实例详解:
对于上图的“同一个引用类型,使用不同的实例而执行不同的操作”这句话我们在实际的代码中看一下是如何体现的。
<1> 没有体现多态的案例:(以下代码虽然没有体现多态的概念,但是体现出了重载的用法)
package Animal;/* * 小鸟类 */public class Bird { private String name;//小鸟名字 private String sex;//小鸟性别 private int health;//小鸟健康值 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getHealth() { return health; } public void setHealth(int health) { this.health = health; } }
package Animal;/* * 小狗类 */public class Dog { private String name;//小狗名字 private String sex;//小狗性别 private int health;//小狗健康值 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getHealth() { return health; } public void setHealth(int health) { this.health = health; } }
package Animal;/* * 医生类 */public class Doctor { /* * 给小狗看病 */ public void cure(Dog dog){ if(dog.getHealth()<50){ System.out.println("打针,吃药!"); dog.setHealth(40); } } /* * 给小鸟看病 */ public void cure(Bird bird){ if(bird.getHealth()<50){ System.out.println("吃药,疗养!"); bird.setHealth(30); } }}
package Animal;public class Test { /** * 没有体现多态的代码 * 用重载的方式实现了不同种动物看病的过程 */ public static void main(String[] args) { //医生对象 Doctor dapeng = new Doctor(); //小狗对象 Dog ws = new Dog(); ws.setHealth(30); dapeng.cure(ws); //小鸟对象 Bird gz = new Bird(); gz.setHealth(40); dapeng.cure(gz); }}
思路:
我们分别创建了4个类,小鸟,小狗,医生以及测试类。
医生类是为了给动物看病的,对于医生而言不管是小狗还是小鸟都是动物,所以在医生类中我们创建构造方法时,分别创建了public void cure(Dog dog){}和public void cure(Bird bird){}两个不同的构造方法来面对不同的对象。虽然用的都是cure但是()中的类型不同所以是允许同名创建的,在这里也就是我们之前所学到的重载的概念。
小鸟跟小狗类中的属性很简单分别定义对象的属性(private权限),然后通过set,get方法创建好各自的对象即可。
测试类中为了能够很好的体现医生给狗狗,小鸟看病的过程。首先创建Doctor dapeng = new Doctor();方法。然后同样再分别对Bird(小鸟),Dog(小狗)同样创建它们各自的方法。因为要体现出看病必须要在各个动物的健康值中才能体现目前以及医生治疗过后的状态。所以我们在小鸟跟小狗的方法中分别 .setHealth();指定目前的健康值。然后医生再分别针对不同的对象来分别看病,并给出方子。
对于案例1的总结:
上面的这种案例,虽然能够体现医生分别给小狗跟小鸟治病了。但是如果宠物的对象多了比如说还有小猫,小乌龟,小猪等等的话。就相当于我们要在代码中重载好多次。显然这种方法只能满足于对象少的情况当治疗对象多的时候就不适用了。为了更好的解决这个问题,所以我们就必须必须要用到java中多态的概念了。下面<2>中的例子就体现了多态的用法一起来仔细看一下。
<2> 相同例子中的多态的案例:
package Animal;/* * 宠物类 */public class Pet { private String name; private String sex; private int healthy; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getHealthy() { return healthy; } public void setHealthy(int healthy) { this.healthy = healthy; } public void toHospital(){ System.out.println("宠物病了就要去医院看病!"); }}
package Animal;/* * 小鸟类 */public class Brid extends Pet { @Override public void toHospital() { System.out.println("吃药,疗养"); } }
package Animal;/* * 小狗类 */public class Dog extends Pet{ @Override public void toHospital() { System.out.println("打针,吃药"); } }
package Animal;/* * 医生类 */public class Doctor { /* * 给宠物看病 */ public void cure(Pet pet){ if(pet.getHealthy()<50){ pet.toHospital(); //关键代码:让宠物去医院看病 pet.setHealthy(60); } } }
package Animal;/* * 猪猪 */public class Pig extends Pet{ @Override public void toHospital() { System.out.println("打针,化疗!"); } }
package Animal;public class Test { /** * @param args */ public static void main(String[] args) { Doctor dp = new Doctor(); Dog wc = new Dog(); wc.setHealthy(30); Brid gz = new Brid(); gz.setHealthy(40); Pig zz = new Pig(); zz.setHealthy(45); //看病对象为小狗 System.out.println("*************小狗看病结果:*****************"); dp.cure(wc); //看病对象为小鸟 System.out.println("*************小鸟看病结果:*****************"); dp.cure(gz); //看病对象为猪猪 System.out.println("*************猪猪看病结果:*****************"); dp.cure(zz); }}
输出结果:
思路:
上述的代码以及图片显示结果可以看出,无论对象是小鸟,小狗,小猪也好,还是未来要追加新的对象也好。对于对象本身而言不需要再定义自己各自的属性以及get,set方法。创建一个pet宠物类让宠物类作为这些小动物的父类,属性在父类中定义。然后让各个对象中的宠物extends继承父类。这样就省去了子类中的属性代码。这些内容属于之前所讲的继承的概念。然后再在各个子类中通过override方法重写父类中的内容,分别打印属于自己类型的病状。这样不光继承了父类中的属性也同时输出各个子类中自己部分的属性,这也就前面提到的继承中重写的概念。
做完这2点再回到Doctor类中,之前我们医生要针对每个对象一个个的指定对象然后再通过if,else判断语句来判断每个不同对象的病情后再分别进行治疗。通过多态的方法后我们不用逐一指定每个宠物,只需要创建一个宠物方法这样面向的是所有的宠物而不是单独的对象,再做if,else判断。在判断中只需要让生病的宠物去医院诊治,具体的诊治在Test测试类中进行就可以了。然后再设定治疗后的健康值就可以了。省去了不使用多态的很多代码。
最后在Test测试类中分别创建每个宠物当前的健康值是多少。然后再创建医生类的方法例如Doctor dp = new Doctor();然后医生给动物看病时。只需要dp.cure()括号中选中要治疗的动物变量就可以了。
优化:
在我们代码的执行过程中会发现在我们pet类中下图的代码永远不会被执行。因为每个宠物在重写父类的方法时只是调用了父类去医院看病的方法,而每个宠物治疗的结果都是符合不同宠物中属于自己的行为。因此这里的代码看似没有任何的作用。既然没用就要舍弃掉。直接删除也可以,代码也不会报任何的错误。但是为了更好的体现这句代码的重用性,我们在这里用abstract关键字来实现。详情见代码。 abstract关键字的使用在java中代表抽象类的使用。对于抽象类的解释是:有抽象方法的类必然是抽象类。抽象类里不仅只有抽象方法。(之前的静态方法,虚方法等都可以使用)
package Animal;/* * 宠物类 */public abstract class Pet { private String name; private String sex; private int healthy; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getHealthy() { return healthy; } public void setHealthy(int healthy) { this.healthy = healthy; } abstract void toHospital();}
(3)向上转型于向下转型
<向上转型>
父类方法在执行的时候其实执行的是子类中的方法,这种方法叫做向上转型。看下图中的解释。
我们按照上面解释说明内容一起来看一下图1跟图2,图1本来执行的是狗狗自己重写后的方法。而图2呢把狗狗的方法体中的Dog变成了Pet父类的方法体了。
图1:
图2:
当我们的代码中使用父类对象,调用和执行子类对象的方法时,那么这行代码体现了多态,做了向上转型。
向上转型不需要人为干预,编译器会自动为我们执行向上转型的操作。
<向下转型>关于向下转型我们一起看一下下面的代码。分别在各个宠物中创建属于自己的方法。例如public void fly(){System.out.println("飞的更高"); },其他宠物依次类推。然后当我们Doctor类中医生给各个宠物看完病时,每个宠物回复后的状态必须要通过医生治疗后体现出来才可以。所以在医生类中需要调用到各个宠物恢复后的状态,例如dog.run()。那么如何能让掉用到子类中特有的方法呢?这里需要用到类型转换跟instanceof类型转换关键字。具体用法请参照代码。
package Animal;/* * 小鸟类 */public class Brid extends Pet { @Override public void toHospital() { System.out.println("吃药,疗养"); } public void fly(){ System.out.println("飞的更高"); } }
package Animal;/* * 小狗类 */public class Dog extends Pet{ @Override public void toHospital() { System.out.println("打针,吃药"); } public void run(){ System.out.println("跑的更快"); }}
package Animal;/* * 猪猪 */public class Pig extends Pet{ @Override public void toHospital() { System.out.println("打针,化疗!"); } public void go(){ System.out.println("走的更好!"); }}
package Animal;/* * 医生类 */public class Doctor { /* * 给宠物看病 */ public void cure(Pet pet){ if(pet.getHealthy()<50){ pet.toHospital(); //关键代码:让宠物去医院看病 pet.setHealthy(60); if(pet instanceof Dog){ Dog dog =(Dog)pet; dog.run(); }else if (pet instanceof Brid){ Brid bird = (Brid)pet; bird.fly(); }else if (pet instanceof Pig){ Pig bird = (Pig)pet; bird.go(); } } } }
package Animal;public class Test { /** * @param args */ public static void main(String[] args) { Doctor dp = new Doctor(); Dog wc = new Dog(); wc.setHealthy(30); Brid gz = new Brid(); gz.setHealthy(40); Pig zz = new Pig(); zz.setHealthy(45); //看病对象为小狗 System.out.println("*************小狗看病结果:*****************"); dp.cure(wc); //看病对象为小鸟 System.out.println("*************小鸟看病结果:*****************"); dp.cure(gz); //看病对象为猪猪 System.out.println("*************猪猪看病结果:*****************"); dp.cure(zz); }}
(4)包含多态所有语法的案例:
以下代码是通过上述学到的多态中的方法描述了一个爸爸带着孩子去动物园看动物表演,吃肯德基的场景。同时在代码的描述中充分利用了我们学到的知识体现了封装,继承,多态的特性。具体内容请参照以下代码及代码中的描述。
package demo1;/* * 人类 */public abstract class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } /** * 人自身的构造方法 */ //无参构造方法 public Person(){} //有参构造方法 public Person(String name,int age){ this.name = name; this.age = age; } /** * 自我介绍 **/ public void intraductor(){ System.out.println("我叫"+this.name+"\n今年"+this.age+"岁"); } /** *去动物园看动物 表演 **/ //Person同Animal之间的交互(人要看动物表演,所以必须要跟动物之间建立关系 .把动物类作为参数传进来.然后再调用动物的方法) public void seeAnimalplay(Animal animal){ System.out.println("我在看"); animal.play(); //这里调用的是Animal中的方法 } /** * 吃东西 **/ //@@eat()中定义了2个参数,KFC kfc为去哪儿吃,吃东西的场所.String choice是选择吃什么 public void eat(KFC kfc,String choice){ //选择要吃的食物通过getFood()去选择要吃的东西(这里调用的是KFC中工厂的方法) Foods food = kfc.getFood(choice); System.out.println("我花了:"); food.price(); System.out.println("钱"+"吃:"+food.getName()); }}
package demo1;/** * 动物类 */public abstract class Animal { public abstract class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } //每个动物玩耍的行为各自不同 public abstract void play(); }
package demo1;/** * 父亲类 */public class Father extends Person { //调用父类的无参构造方法 public Father(){ super(); //通过super关键字调用 } //调用父类的有参构造方法 public Father(String name,int age){ super(name,age); } public Father(String name){ this("林志颖",35); //通过this();关键字调用自己本身的构造方法.调用的是上面这个构造方法.除此之外这里还体现了重载的概念.名称相同参数个数不同 }}
package demo1;/** * 儿子类 *///这里孩子继承的不是Person类而是自己的父亲类(体现了单根性跟传递性.因为孩子继承了父亲,父亲继承了人类)public class Children extends Father { //调用父类的无参构造方法 public Children(){ super(); //通过super关键字调用 } //调用父类的有参构造方法 public Children(String name,int age){ super(name,age); } public Children(String name){ this("KiMi",8); //通过this();关键字调用自己本身的构造方法.调用的是上面这个构造方法.除此之外这里还体现了重载的概念.名称相同参数个数不同 }}
package demo1;/** * 海豚类 */public class Dolphin extends Animal{ //重写父类 @Override public void play() { System.out.println("海豚喜欢游泳!"); } }
package demo1;/** * 熊猫类 */public class panda extends Animal { //重写父类 @Override public void play() { System.out.println("熊猫喜欢爬树!"); } }
package demo1;/** * 食物类 */public abstract class Foods { //食物的名字 private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void price(){ System.out.println("食物的价格"); }}
package demo1;/** *可乐类 */public class Cola extends Foods{ @Override public void price() { System.out.println("可乐的价格"); } }
package demo1;/** * 汉堡类 **/public class Hamburger extends Foods { @Override public void price() { System.out.println("汉堡的价格"); } }
package demo1;/** * KFC **///@@对于汉堡跟可乐而言必须要有个生产的工厂.KFC就是生产的工厂public class KFC { //KFC生产食物方法:因为要提供食物给顾客.所以要把食物作为生产的返回值. //而对于食物而言有汉堡跟可乐可以选择所以定义一个choice参数,通过判断语句来选择食品. public Foods getFood(String choice){ if(choice.equals("可乐")){ return new Cola(); //把选择的对象return给顾客 }else{ return new Hamburger(); } }}
鸡汤:
“今天的努力都是明天别人对你的膜拜,今天的停滞就是明天别人对你的唾弃!“
“做人要靠自己!!”
Java基础之OOP