Java编程中,面向对象是最基础的编程思想,面向对象的三大特性!——封装性、继承性、多态性
面向对象是指以对象的方式对现实世界的理解和抽象的方法。
官方解释确实实在难以理解,其实简单来,面对象就是对物体的抽象。举个栗子:我现在要去水果店买一袋苹果。那么面向对象与之不同的就是将对象抽取出来,在例子中我们的对象就是 我、水果店、苹果。我们的业务是 去水果店 和 买 两个动作,最终我们得出的例子是这样的:
package com.itaem;
/**
* 我
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class My {
}
package com.itaem;
/**
* 水果店
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class Shop {
}
package com.itaem;
/**
* 苹果
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class Apple {
}
逛街服务:
package com.itaem;
/**
* 逛街服务
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class ShoppingSevrice {
/**
* 去商店
*/
public void goToShop() {
//具体逻辑
}
/**
* 买水果
*/
public void buyApple() {
//具体逻辑
}
public static void main(String[] args) {
//初始化逛街服务
ShoppingSevrice shoppingService = new ShoppingSevrice();
//去水果店
shoppingService.goToShop();
//买苹果
shoppingService.buyApple();
}
}
从对象的角度分析。对于我去买苹果这样一个场景,将每个对象抽象成一个类,如 我(My),苹果(Apple),水果店(Shop)。其实面向对象就是这么简单,大家都懂了吧?基于这个基础,下面就开始三大特性了。
封装性即意味这对象封装其内部的数据成员,使其对外不可见,以保证数据的安全性。
提高数据安全性,有利于代码管理。
其实如果理解了封装性的话,那么就会觉得上面这句话很精辟很容易理解,说出来很专业。还是不多说废话,直接上代码(对着代码看上面封装性的定义):
package com.itaem;
/**
* 我
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class My {
private String name ; //姓名(数据成员)
private int age ; //年龄(数据成员)
/**
* 想获取name的值只能通过getName方法获取
* @return
*/
public String getName() {
return name;
}
/**
* 想改变name的值只能通过setName方法改变
* @param name
*/
public void setName(String name) {
this.name = name;
}
//与getName相同
public int getAge() {
return age;
}
//与setName相同
public void setAge(int age) {
this.age = age;
}
}
package com.itaem;
/**
* 逛街服务
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class ShoppingSevrice {
public static void main(String[] args) {
//封装性
My my = new My() ;
System.out.println(my.name) ; //编译不通过,不能直接调用数据成员name
my.setName("AAA"); //改变name
System.out.println(my.getName()) ; //获取name
my.setName("BBB"); //改变name
System.out.println(my.getName()); //再次获取name
}
}
以上代码的流程:
①当改变name的值,都只是调用了my里面的setName()方法,没有直接改变name的值;
②而是把"AAA"值传入setName()而已,
③而真正直接改变name的值是在My类的setName()方法的this.name=name。
getName()原理相同。也就是说,ShoppingService不知道my里面有name这个数据成员,只是调用方法间接改变My的数据成员值而已。回归定义——封装性即意味这对象封装其内部的数据成员,使其对外不可见,以保证数据的安全性。是不是觉得这句话很精辟!好了,想必大家理解了封装性,那么通过问题来加深理解和深入提高了。
解答:封装的概念就是 自己的数据成员仅对自己可见,只有自己才能改变其值。
所以setter方法就是为了改变数据成员而诞生的。
解答:因为数据成员用了private进行对外不可见,所以用getter。
解答:getter方法返回对象引用,返回可变对象的引用则破环了封装性。
方案:返回引用可变对象可采用克隆。 如:日期(Date),数组(String[]),集合(List),键值对(Map)等
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
提高代码复用。
向上转型,向下转型(向下转型一般不会使用也不要去使用)
继承衍生了两个名词,一个叫父类(也叫超类),一个叫子类,父类就是要被继承的类,子类就是使用继承的类。 继承就是获取上一代的东西,那么在代码里面也一样。下面举个栗子,我有名字和年龄,儿子继承了我,而且多了一招哭的技能:
package com.itaem;
/**
* 我
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class My {
private String name ; //姓名(数据成员)
private int age ; //年龄(数据成员)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
void nothingName() {
System.out.println("包域"); //只能在该包com.itaem中调用
}
protected void protectedName() {
System.out.println("protected域"); //只能在父类和子类中调用
}
private void privateName() {
System.out.println("private域"); //只能在本类中调用
}
}
package com.itaem;
/**
* 儿子
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class Children extends My {
private String cry = "我要哭";
public String getCry() {
return cry;
}
public void setCry(String cry) {
this.cry = cry;
}
}
package com.itaem;
/**
* 逛街服务
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class ShoppingSevrice {
public static void main(String[] args) {
//封装性
My my = new My() ;
System.out.println(my.getName());
System.out.println(my.getAge());
System.out.println(my.getCry()); //编译时错误,因为我没有哭这招技能
Children child = new Children() ;
System.out.println(child.getName());
System.out.println(child.getAge());
System.out.println(child.getCry());
child.nothingName(); //只能在该包com.itaem中被继承
child.protectedName(); //只能在父类和子类中被继承
child.privateName(); //编译时错误,private标示符标示的字段或方法是继承不了的
}
}
简单来说,继承是子类拥有父类的属性、函数等。但是要注意几点,private和final标示的字段或方法是不能被继承调用的,这个要谨记!上面只是增加大家对继承的理解,后面就又到深入提高了。
package com.itaem;
/**
* 服务
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class ShoppingSevrice {
public static void main(String[] args) {
//继承性
My my = new Children() ; //向下转型 子类对象传给my变量
System.out.println(my.getName());
System.out.println(my.getAge());
System.out.println(my.getCry()); //编译时错误,因为我没有哭这招技能
}
}
子类创建的一个对象引用赋值给其父类变量,叫做向上转型。这个大家看了代码都很好理解,但是为什么原理(1)中说通过父类对象不能操作子类新增的成员变量,不能调用子类新增的方法呢?我们将引用堆进行讲解:
解释:虽然堆中Children对象存在getCry()的信息,但是我们程序调用的是虚拟机栈的my指针,因为my指针中没有存储getCry()方法的引用信息,所以在程序在编译时期调用my.getCry()就会因为找不到getCry()信息报错。所以向下转型的最重要之一就是不能调用子类的新增方法。
另外还有一点是父类对象调用子类重写父类的方法时,调用的是子类重写后的方法。这点可以参照上图,只是虚拟机栈的my变量多了一个getCry()信息,但是实际引用的还是我们堆中的方法Children.getCry()。
实例:
package com.itaem;
/**
* 服务
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class ShoppingSevrice {
public static void main(String[] args) {
/*
* 错误示范
*/
My my = new My() ;
Children children = (Children)my ; //不安全的向下转型,编译无错但会运行会出错,抛出 java.lang.ClassCastException
/*
* 一般示范
*/
My my2 = new Children() ; //向上转型
Children children2 = (Children)my2 ; //向下转型,编译和运行皆不会出错
/*
* 正确师范
*/
My my3 = new Children() ; //向上转型
if(my3 instanceof Children) {
Children children3 = (Children) my3; //安全的向下转型
}
}
}
看得是不是有点似懂非懂的感觉?没关系,我们继续借助虚拟机堆栈分析,上图:
解释:原理其实跟向下转型差不多,也就是说在虚拟机栈Children栈帧中调用getCry()引用时,发现堆中对象my没有getCry()信息,所以会抛出运行时错误。
多态性即多种形态。多态性的实现与静态联编、动态联编有关。
静态联编和动态联编
①静态联编/静态绑定 支持的多态性称为编译时的多态性,也称静态多态性,它是通过重载实现的。——重载
②动态联编/动态绑定 支持的多态性称为运行时的多态性,也称动态多态性,它是通过继承和虚函数实现的。——覆盖+对象变量多态
①重载 ②覆盖 ③对象变量是多态的(因为父子类)。
上面都是非常专业的解释,太深奥了!来个简单的例子解释一下为什么是多态性(高手直接跳到下面问题)
package com.itaem;
/**
* 苹果
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class Apple {
public void eat() {
System.out.println("吃苹果");
}
public void eat2() {
System.out.println("吃1个苹果");
}
}
package com.itaem;
/**
* 梨子
*
* @author wu-shangsen
* @version 1.0, 2016/9/23
*/
public class Pear extends Apple{
/**
* 覆写 覆写了苹果的eat()方法
*/
@Override
public void eat() {
System.out.println("吃雪梨");
}
/**
* 重载
* @param number
*/
public void eat2(int number) {
System.out.println("吃"+number+"个雪梨");
}
}
package com.itaem;
/**
* 服务
*
* @author wu-shangsen
* @version 1.0, 2016/9/22
*/
public class ShoppingSevrice {
public static void main(String[] args) {
Pear pear = new Pear() ;
pear.eat(); //吃雪梨
pear.eat2(); //吃一个苹果
pear.eat2(5); //吃5个雪梨
}
}
吃雪梨
吃1个苹果
吃5个雪梨
Process finished with exit code 0
简单来说,子类写了一个和父类一样名称的方法,就是应用了多态性! 够简单了吧?那么回顾上面定义的什么是静态多态性和动态多态性吧!特征:相同方法名称,根据参数类型或个数的不同调用对应的方法。
范围:private方法、static方法、final方法、构造器(不参与动态联编)
条件:重载,非继承
优点:方便调用,速度快。
特征:首先会查找这个类实际引用,然后在查找对应的方法,如果方法在该类已经定义了则直接调用,否则则在该类的超类中查找对应方法,以此类推。
范围:Java中除了 private方法、static方法、final方法、构造器 外,其他的所有方法都是动态绑定的。
特例:如果调用super编译器会自动对超类方法进行搜索。(动态联编虚拟机优化方案:每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表,其中列出了所有动态联编法的签名和实际调用的方法)
条件:覆写,继承,向上转型。
优点:无需对现存的代码进行修改,就可以对程序进行扩展。(直接将引用改为新增类即可)