我的上一篇博客讲述了一些面向对象的基本内容,讲解了一些对象,类,方法重载,访问修饰符,构造函数,this关键字以及一些其他的东西,今天来讲一讲面向对象的特点,如果有什么问题,希望各位前辈不吝赐教。
一.封装
封装就像把一些东西用一个黑盒子装起来,不用去考虑它里面是什么结构,只需要知道它是什么,怎么使用。就比如,你手上拿着一个手机,你不用知道手机里面的硬件是什么形状的,不用知道里面用了多少芯片,每一个部件长什么样,用了多少条线路连接,但是你知道这是一个手机,你可以用它来听歌,打电话,发信息。
类的封装就是如此,把类的属性和方法统统封装在类的内部,外部类如果想要使用它,则必须实例化该对象,然后调用其提供的方法去使用它的属性。
public class Student{
private String name;
private int age;
public Student(){
}
public Student(String name){
this.name = name;
}
public void study(){
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int n){
this.age = age;
}
public int getAge(){
return this.age;
}
}
如上代码,Student类将name属性和age属性以及study方法一起封装了起来。封装最主要的功能就是自己可以修改类中代码,而不用修改调用这些类的代码片段,并且也提高了程序的安全性。
二.继承
还是以上面的代码为例,学生的种类很多,有大学生,有小学生,有中学生,每种学生都有其自生的特有学习方式,但是这些人又具有相同的特点,他们都具有名字和年龄这两个相同的特征。这时候就需要用到继承了。继承关系就是 子类 is a 父类 。在继承中,子类继承了父类所具有的所有属性和方法,同时可以具有自己独特的方法和属性,但是父类所具有的私有属性和方法是无法直接访问的。私有属性可以通过public修饰的getter和setter方法访问到,但是私有方法不行,即“只能看,不能用”。
public class UNstudent extends Student{
public UNstudent (String name){
super(name);
}
public void study(){
System.out.println("我是一个大学生,我在学习");
}
}
Java中的继承使用extends关键字,注意java中可以有多个子类继承一个父类,也支持多级继承(一个类既是一个类的父类又是另一个类的子类),但是不能支持多继承(一个子类继承多个父类)。同时,如果父类具有默认的构造方法,则子类无需手动添加调用父类的构造方法,若父类没有默认的构造方法,则子类中必须添加调用父类构造方法的代码,代码格式为super(参数值),因为父类的构造方法无法被子类继承。
在继承当中又涉及了方法的重写问题,即子类重写父类的方法。方法重写需要满足以下几个条件。1.两个类必须存在继承关系;2.子类方法的访问修饰符可以大于或者等于父类的访问修饰符;3.子类方法的返回值类型,方法名,参数必须和父类方法一致;4.方法的具体实现要不同。方法进行重写之后,在实例化对象时,根据new关键字之后的类名,如果是子类,则优先调用子类重写之后的方法,如果子类没有才会去调用父类方法。如果想要在调用子类方法时,父类的方法也能执行,则必须在子类重写的方法中,使用super关键字来调用父类方法,这样当你调用子类方法时父类方法也能被执行。
三.多态
多态是指同一个行为具有不同的表现形式或形态的能力,也可以说是同一个接口使用不同的实例而执行不同的操作。
类之间的继承关系使子类具有父类的所有变量和方法,这也就意味着父类所具有的方法也可以在它所派生的各级子类中使用,发给父类的任何消息,也可以发给子类。所以子类的对象也是父类的对象,即子类对象既可以作为该子类的类型也可以作为其父类的类型对待。因此从一个基础父类派生的各种子类都可以作为同一种类型———基础父类的类型对待。将一种类型(子类)对象的引用转换成另一种类型(父类)对象使用,就称为上溯造型。之所以称为上溯造型,是因为在类继承体系图之中,父类位于上部,而子类位于下部,造型的方向是从子类到父类,箭头朝上,所以叫上溯造型。上溯造型是从一个具体的特殊的类型到一个通用的抽象的类型的转换,所以是安全的,在java中直接可以运用。下溯造型俗称强制转换,将父类对象的引用转换为子类对象,不一定安全。
上溯造型的运用,使子类对象既可以作为子类使用,也可以作为父类对象使用;父类对象变量可以指向子类对象。这样通过一个父类变量发出的方法调用,可能执行的是该方法在父类中的实现,也可能是某个子类中的实现,这只能通过在运行时刻根据该变量指向的具体对象类型确定,这就是运行时多态。运行时多态的实现机理是动态联编技术,也叫做晚联编或运行期。将一个方法调用和一个方法体连接在一起,称为联编。若在程序运行之前执行联编操作,称为“早联编”(C语言的编译器只支持早联编)。在运行时刻执行联编称为“晚联编”。
提到上溯造型就不得不提自动转型,这两个概念其实是一样的。上溯造型是将父类指向子类对象,而自动转型也是指定义一个父类,用以接收子类或者父类,如果接收的是子类,则会自动转化为父类,如果是父类,则不需要改变。很多情况下两个概念是通用的,不用区分。要使用自动转型,必须存在继承关系。
自动转型的使用格式如下:
访问修饰符 返回值数据类型 方法名(父类名 参数名,...){
}
父类名 对象名A = new 子类构造方法(参数值,...);
方法名(对象名A);
子类名 对象名B = new 子类构造方法(参数值,...);
方法名(对象名B);
但是自动转型存在一个问题,就是使用自动转型之后,无法调用子类特有的属性和方法,这是因为Java编译的是文件,在编译的时候只会根据对象名的类型进行属性和方法查找,如果在类名对应的文件中存在,则通过编译,如果不存在的编译报错, 编译的错误是JVM的问题。既然自动转型存在问题,还要使用的原因是:如果类型是父类的,它可以接收任何子类的对象,这样就降低了重复的代码。
下面是一些测试代码:定义了一个Student父类,两个子类:UNstudent和PUstudent,一个show方法,和一个包含主函数的类。
public class Student{
private String name;
public Student(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void study(){
System.out.println("I am studying!");
}
}
public class UNstudent extends Student{
private String name;
public UNstudent(String name){
super(name);
}
public void study(){
System.out.println("I am a university student and I am "+getName());
}
}
public class PUstudent extends Student{
private String name;
public PUstudent(String name){
super(name);
}
public void study(){
System.out.println("I am a pupil and I am "+getName());
}
}
public class Show {
public void show(Student stu){ //用父类接收子类
stu.study();
if(stu instanceof PUstudent){
PUstudent pupil1 = (PUstudent)stu;
pupil1.study();
}
if(stu instanceof UNstudent){
UNstudent unstu1 = (UNstudent)stu;
unstu1.study();
}
}
}
public class Main {
public static void main(String[] args){
PUstudent pupil = new PUstudent("小明"); //实例化一个小学生类
UNstudent unstudent = new UNstudent("Lily"); //实例化一个大学生类
Show show1 = new Show();
show1.show(pupil); //自动转型,子类转父类
show1.show(unstudent);
Student stu1 = new UNstudent("Lucy");
stu1.study();
UNstudent stu2 = (UNstudent)stu1;
stu2.study();
}
}
运行结果: