万物皆可归类,类是对于世界事物的高度抽象,不同的事物有不同的关系:一个类自身与外界的封装关系;一个父类与子类的继承关系;一个类和多个类的多态关系。
万物皆对象,对象是具体的世界事物,面向对象的三大特征:封装、继承、多态,封装说明一个行为和属性与其他的类的关系,继承是父类和子类的关系,多态是类与类之前的关系
概念
封装就是把抽象的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作,才能对数据进行操作
理解和好处
1、封装隐藏了实现细节,类的内部机制实现不会呈现,细节是写方法的人考虑的,而外部只需要传入必要的参数实现操作就可以了
2、封装可以对数据进行验证,保证合理性
Person p = new Person();
p.name = "jack";
p.age = 1000;
显而易见,不合理的地方是年龄,不封装时可以对数据进行修改,并且系统并不会发现其不合理,而封装就是在方法内部对数据的验证和处理
3、可以在不影响使用的情况下改变类的内部结构,同时保护了内部数据。一个类内部中,通常会对其属性进行私有化private,所以不能直接修改属性,而提供了公有的set方法,可以对数据进行判断和处理,而属性私有化后,外部无法访问,又提供了get方法,用于对数据进行获取
概念
继承是从已有类中派生出新的类,新的类能吸收已有类的数据属性和方法,并且扩展新的能力,不用再重新写已有类的属性和方法,直接继承即可。通过extends关键字来实现继承
好处
1、继承避免了对于一般类和特殊类之间的共同特征进行的重复的特征描写,通过继承可以清晰得表达每一项共同特征所适应的概念范围,在一般类中定义的属性和方法适用于这个类本身以及这个类以下的每一层特殊类的全部对象。通过继承原则使得系统模型比较简练和清晰
对于小学生、中学生和大学生共有的属性,姓名、年龄、性别等属性可以写在一个父类中,而小学生类使用时直接通过extends来获取,不需要再重写
//student类
public class Student {
public String name;
public int age;
public void setAge(int age) {
this.age = age;
}
public void show(){
System.out.println("学生名:"+name+"年龄:"+age);
}
}
//子类pupil
public class Pupil extends Student{
public void testing(){
System.out.println("年龄为"+age+"的小学生"+name+"正在考试...");
}
}
//测试
public class Test {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "rose";
pupil.age = 19;
pupil.testing();//年龄为19的小学生rose正在考试...
}
}
2、扩展性和维护性提高了
当在父类中添加一个方法或者属性,而其下的所有类都可以调用这个方法或者属性
细节
1、父类中使用private 定义的变量和方法不会被继承,不能在子类中直接操作父类定义私有的变量和方法,但是可以在父类中公有的方法来获取属性和方法。
//父类中属性私有,子类不能调用
private String name;//属性私有
private void call(){
}//方法私有
public String showName(){
return name;
}
public void showCall(){
call();
}
2、子类在创建对象必须调用父类的构造器,完成父类的初始化
//父类
public Student(){
System.out.println("父类构造器被调用....");
}
//子类
public Pupil(){
System.out.println("子类构造器被调用....");
}
//测试
Pupil pupil = new Pupil();
输出:父类构造器被调用....
子类构造器被调用....
默认在子类的构造器中有super()
,无论写与不写都默认存在
3、当创建子类对象时,不管使用子类的哪个构造器,默认情况下都会调用父类的无参构造器;如果父类没有提供无参构造器,则必须在子类的构造器中用super去指向使用父类的哪一个构造器完成对父类的初始化,否则编译不会通过
4、如果希望指定去调用父类的某一个构造器,就需要显式的调用一下
super(参数列表)
注意:
5、java所有类都是Object的子类,Object是所有类的父类
6、父类构造器的调用不限于直接父类,将一直往上追溯直到Object类,也就是他们的顶级父类
7、子类最多只能直接继承一个父类,java中是单继承机制
8、不能滥用继承,子类和父类之间必须满足is-a的逻辑关系
即Cat is Animal、Pupil is Student
继承本质
分析子类继承父类,创建子类对象时,内存发生了什么?
案例:
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();
}
}
class Grandpa{
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends Grandpa{
String name = "大头爸爸";
int age = 39;
}
class Son extends Father{
String name = "大头儿子";
}
内存布局:
1、先加载顶级父类object,然后再加载grandpa,再加载father,最后加载son类
2、在堆存放son对象的数据,然后指向方法区的字符串常量池
3、当把堆的数据分配好后,并且指向也建立好后,0x11就会配给主方法对象引用
而创建完Son对象后,如何调用Grandpa和Father类中相同的属性
当类很多,而且对对象操作操作的方法很多,不利于管理和维护,从而多态就被建立了
方法或对象具有多种形态是面向对象的第三大特征,多态是建立在封装和继承基础上的
多态的具体体现
方法的多态,重写和重载就体现了多态
重载
public int sum(int n1,int n2){
return n1+n2;
}
public int sum(int n1,int n2,int n3){
reruen n1+n2+n3;
}
两个方法构成重载,通过不同的参数个数去调用sum方法,就会去调用不同的方法,对于sum方法来说就是多种状态的体现
重写
A类中方法
public void show{
System.out.println("A类方法show被调用了");
}
在B类中重写show方法
public void show{
System.out.println("B类方法show被调用了");
}
在一个不同对象中写相同方法就是重写,对于show方法来说就是多种状态的体现
对象的多态
=
号的左边,运行类型看=
号的右边Animal animal = new Dog();
//animal的编译类型是Animal,而运行类型是Dog
animal = new Cat();
//animal的运行类型变成了Cat, 而编译类型仍然是animal
animal.call();//输出的是Cat的call方法
//运行时,执行到这行时,animal的运行类型是Cat,所以是Cat的call()
多态可以统一管理对象操作方法
public void feed(Animal animal,Food food){
System.out.println("主人"+name+"给"+animal.getName()+"吃"+food.getName ());
}
animal编译类型是Animal,可以指向Animal子类的对象,而food编译类型是Food,可以指向Food子类的对象,当animal子类对象执行方法,不用在对方法进行重写或重载,只需要运行对象初始化成使用对象即可调用方法,不用再具体编译
细节分析
1、首先确定的是,多态是封装和继承的基础之上建立的,多态的前提是两个对象存在继承关系
2、还有就是多态的向上转型,他的本质就是父类的引用指向了子类的对象(animal指向cat)
3、多态的向下转型
Dog dog = (Dog) animal();
子类类型 引用名 = (子类类型)父类引用
这时dog的编译类型和运行类型都是Dog
注意:
Dog dog = (Dog) animal();
Cat cat = (Cat) animal();
//会报错
这时Animal向下转型成dog,指向了Dog对象,而下一句就是cat指向现在父类animal的引用指向Dog对象,即cat指向Dog对象,明显是不对的