Java面向对象三个特征:封装、继承、多态。
封装性
封装性的思想
为什么需要封装:现实中如果我们要使用洗衣机,我们只需按下进行模式切换和按下开关就行了,我们并不需要了解他是如何运行的,没必要知道内部构造是什么样的。而在编程中也同样如此,
封装性的思想:隐藏对象内部的复杂性,只对外公开简单的调用接口。把该隐藏的隐藏起来,该公开供他人使用的就暴露出来,这就是封装性的设计思想。
代码举例(为了尽量简洁,舍弃了构造方法和this关键字的使用):
//定义一个Animal类
class Animal{
String name;//动物名称
int age;//动物年龄
int legs;//腿的个数
//定义一个方法来输出信息
public void show{
System.out.println("name:" + name+"age:" + age+"legs:" + legs);
}
}
创建一个对象
public class Test{
public static void main(String[] args) {
Animal a = new Animal(); // 创建一个对象
a.nane = "dog";
a.age = 3;
a.legs = 4;
a.show(); //调用show方法输出a的信息
}
}
此时就是一个很常见的对象的创建和属性赋值,但在实际中会有很多的限制如:
a.legs =-1
很显然并没有-1条腿的狗,现实中也有比如输入名字时有长度限制等需求,封装就是用来解决这样的问题的。
这时我们将属性赋值写在一个方法里
public void setLegs(int l){
//一般腿的个数为大于0的偶数,所以我们进行一个if判断
if(l >=0 && l % 2 ==0){
legs = l;
}
else{
//不符合则报错或者抛出异常
}
}
此时通过调用方法并传入参数给属性赋值
a.setLegs(6)
但这样原本a.legs = -4的方式仍然可以使用,所以我们需要通过在之前构造类的时候属性前加上权限修饰符private(或protected)将属性私有化来把这种方式给禁止掉
class Animal{
private String name;//动物名称
private int age;//动物年龄
private int legs;//腿的个数
}
这时a.legs= -4时,就会报错了,因为没有权限直接为这个属性赋值了,只能通过调用setLegs()方法来给属性赋值
但这时你想输出这个属性时也会有没有权限直接调用,所以我们也得写一个输出属性的方法
public int getLegs(){
return legs;
}
继承性
为什么需要继承
我们直接从代码来看,假设我们需要两个类
Person类
//人的类
public class Person {
String name;
int age;
public void eat(){ //人会吃
}
public void play() {//人会玩
}
}
Student学生类
//学生的类
public class Student {
String name;
int age;
String major;
public void eat(){//学生也会吃
}
public void play() {//学生也会睡觉
}
public void study(){//学生多了一个学习的任务
}
}
可以看出,这两个类十分相似,因为学生是人的一种,它具有人的特征,同时也有属于自己的专有特征,很明显,这样如此相似的两个类的定义有点多余了,此时就需要我们的继承了。
当我们拥有人的类后,我们想创建"特殊"的人类——Student可以通过如下方式
public class Student extends Person{
//extends Person表示继承Person类
String major;
public void study(){
}
}
extends关键字代表的就是继承啦,这时的Stundent类就很简单了,只有一个major属性和study方法,但是我们可以通过Stundy的对象来调用Person类里才有的属性或者方法。
public static void main(String[] args) {
Student student = new Student();
student.name = "xiaoming";
System.out.println(student.name);
}
Student类中本身没有这些,但通过extends关键字从Person()中继承过来了,这时我们一般称Person类是Student类的父类或者超类,反过来称Student类是Person类的子类
(继承的子类中还可以通过声明与父类同名的方法重写的方式来起到新的作用,详见我的另一篇关于重写和重载的文章Java中f方法的重载和重写)**
继承性的总结:
- 减少了代码的冗余,提高了代码的复用性
- 便于功能的扩展
- 为之后多态性的使用,提供了前提
继承性的作用:使得子类获得父类的结构、属性和方法等(包括被priivate关键字所封装起来的属性,这意味着内存堆空间中会创建,只是不能直接调用)。
Java中继承性的规定:
1. 一个类可以被多个子类继承。
2. 一个子类只能单继承于一个父类
3. 子父类是相对概念。。
4. 子类直接继承的父类,称为直接父类,间接继承的类,a的父类b继承自c,则a间接继承c,称为间接父类
5. 子类继承父类之后,就获取了直接父类以及所有间接父类中声明的属性和方法
6. Object类是所有类的直接父类或间接父类,如果没有显示声明一个类的父类,则此类继承Object类
多态性(基于继承)
多态性的体现:
老样子,直接上代码
//又是熟悉的Person类
public class Person {
private String name;
private int age;
public void eat(){
System.out.println("人都要吃饭");
}
public void sleep(){
System.out.println("人都要睡觉");
}
}
man类 继承 Person类并重写了Person类的两个方法
public class Man extends Person{
public void eat(){
System.out.println("男人要吃的多一点");
}
public void sleep(){
System.out.println("男人也要睡觉");
}
public void work(){
System.out.println("男人还要努力地工作");
}
}
关键的代码来了
Person p1 = new Man()
这个时候就有很多不了解的小伙伴有疑问了,不是你这个p1对象怎么又是Person,又是Man的,哪能这么写啊?
是的你没看错,真的可以这样写,那么此时我们调用p1的方法时会发生什么情况?
p1.eat(); //输出 男人要吃的多一点
p1.sleep();// 输出 男人也要睡觉
p1.work();//报错
从运行结果来看 p1对象调用的都是Man类重写Person类之后的方法,而Man类自己独有的work()方法却不能调用而报错了
从内存上来看,目前的状态是这样的
我们栈空间存着一个名叫p1的对象引用,他的地址值指向堆空间中的对应位置,而我们的地址值是由数据类型和地址组成的16进制数。
此时他的数据类型16进制数表示为Person,但实际堆空间中地址对应的是Man对象。
所以我们在编译的时候,只可以编译通过调用父类中有的方法,因为你引用的是父类,而运行时,运行的是子类对象拥有的方法,因为内存中存在的的是子类对象。
那么如何才能调用子类特有的属性和方法?:
// “=” 意味着赋值,你不能将一个Person类型直接赋值给Man类型
Man m1 = p1 //?这显然是不行的
//强制将我们p2转换成Man类型,声明一个Man类型并赋值
Man m1 = (Man)p2;
m1.work() //编译通过
和基本数据类型的转换和提升类似,我们子父类之间也可以转换,这也是多态的体现
多态性总结
实际上对象的多态性可以理解为一个事物的多种形态,就是将父类的引用指向子类的对象(或者子类的对象赋给父类的引用)
总之可以理解一句话,编译看左边,运行看右边