一、封装
封装就是隐藏对象内部的复杂性,只对外公开简单的接口。
封装的思想是你只需要知道如何调用接口就行了,不需要去了解内部实现
1. 封装的体现:
- 属性的封装
- 方法的封装
- 构造函数的封装(单例模式)
将类的某些信息隐藏在类的内部,不让外部程序直接访问,而是提供操作数据的方法,这就是属性的封装性
2. 封装的实现:
属性的封装:使用private修饰符修饰成员变量,然后根据需求定义getter或setter方法。
方法的封装:有些方法仅仅只是在类的内部使用,不希望外部程序调用它,通过private修饰即可。
构造函数的封装:使用private修饰构造器,不让外部程序创建类对象,在类内部实例化对象,并提供公共接口。形如单例模式。
开发建议:在定义类的时候,所有的属性都要用private封装,封装的属性如果需要被外部操作,提供对应的getter和setter方法。
3. 举例代码
1.
class Person{//属性封装,封装了age和name两个属性,提供了对应的getter和setter方法
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.
private void swap(int[]arr,int m, int n){ //方法的封装,只有内部方法才能调用
int temp=arr[m];
arr[m]=arr[n];
arr[n]=temp;
}
3.
public class JavaTest {
public static void main(String[] args) {
Person p1=Person.getPersonInstance();
}
}
class Person{
private Person(){} //构造器的封装
public static Person getPersonInstance(){
return new Person();
}
}
4. 封装的好处
- 只能通过规定方法访问数据
- 隐藏类数据的实现细节
- 方便修改实现
- 方便加入控制语句
- 可以修改属性的读写权限
二、继承
思考一下:为什么会有继承?
当多个类中存在相同属性和行为时,为了提高代码复用性,将这些相同的内容抽取到一个单独的类中,这些多个类无需再定义这些属性和行为,只要去继承那一个单独的类即可。
此处的多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。可以理解为:“子类 is a 父类”。
继承就是子类继承父类‘所有’的特征和行为,使得子类对象(实例)具有父类的成员变量和方法,还可以在此基础上添加新方法和成员变量来满足需求。
当创建一个类时,总是在继承。Java中的类没有显示的继承任何类,则默认继承Object类,Object类是Java语言提供的根类,也就是说,一个对象与生俱来就有Object类当中的所有特征。
注意
- 子类拥有父类的所有属性和方法是对的,只不过父类的私有属性和方法,子类是无法直接访到的。即只是拥有,但是无法使用。(这里不考虑Java反射机制)但是如果子类中公有的方法影响到了父类私有属性,那么私有属性是能够被子类使用到的。
- 不能被继承的成员父类:构造方法,构造函数。但是子类可以通过super()显示父类的构造函数。
如上图所示,动物继承生物类,老虎又继承动物类。从这个例子可以看出:越往上的类越抽象,越往下的类越具体。
继承规则
- Java只支持单继承和多层继承,不允许多重继承
- 一个类可以被多个子类继承,一个类只能继承一个父类(直接父类),使用extends关键字
- 子父类是相对的概念
- 子类直接继承的父类,称为直接父类;间接继承的父类称为间接父类。
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
代码实例
class Biology{ //生物类
String name;
int age;
}
class Animal extends Biology{//动物类继承了生物类,生物类是动物类的直接父类
}
class Plant extends Biology{//植物类继承了生物类,生物类是植物类的直接父类
}
class Tiger extends Animal{//老虎类继承了动物类,动物类是老虎类的直接父类,生物类是老虎类的间接父类
}
class Tree extends Plant{//树类继承了植物类,植物类是树类的直接父类,生物类是植物类的间接父类
}
使用继承还会关系到方法重写和super关键字的使用,下面来介绍
方法重写
在子类中,可以根据需要对从父类中继承来的方法进行方法重写。重写以后,当子类对象调用与父类的同名同参数的方法时,优先调用子类重写的方法。
当子类调用属性和方法的时候,如果自己类里面有该属性和方法,肯定先调用自己的,如果自己类里面没有,就去父类(先直接父类/再间接父类)找,一直找到Object类为止。
方法重写有如下要求:
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表;
- 子类重写的方法的返回值类型不能大于父类被重写方法的返回值类型
1.父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void。
2.父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以使A类或A类的子类。
3.父类被重写的方法的返回值类型是基本数据类型(比如double),则子类重写的方法的返回值类型必须是相同的基本数据类型。
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限 (可以参考这篇博客:四种权限访问修饰符)
- 子类方法抛出的异常不能大于父类被重写方法的异常
- 子类能重写的都是非static的方法
思考:如果父类有个方法是private访问权限,在子类中将此方法声明为default访问权限,这个叫方法重写吗?
达咩,当然不是。
super关键字
在Java类中,使用super调用父类中的指定操作。可以访问父类中的构造器、属性和成员方法。
我们需要掌握的内容:
- super的三种语法格式:访问父类构造器(必须放构造函数中的第一行):super()或super(参数);访问父类属性:super.属性;访问父类方法:super.方法名()。
- 我们可以在子类的方法或构造器中,通过使用super.或super(),显式地调用父类中声明的属性/方法或构造器。通常情况下,我们习惯省略‘super.’。
- 特殊情况,若想在子类中调用父类的同名属性或被重写方法时,必须显式地使用'super.'。
- 子类中所有的构造器默认都会访问父类中空参数的构造器。
- 当父类中没有空参数的构造器时,子类的构造器必须通过super(参数列表)语句指定父类中相应的构造器,必须放在构造器的首行。目的在于在创建子类对象的时候,先初始化父类。
- 如果类里面有n个构造器,那么可以调用n-1次,至少1次是调父类的。
- this()与super()不能同时出现,在构造器中,必须二选一,且只能出现在第一行。
结论:在java语言中,不管new什么对象,最后Object类中的无参构造方法是一定会执行的。
代码举例:
public class JavaTest {
public static void main(String[] args) {
Chinese cn=new Chinese();
cn=new Chinese("I 'm Chinese!");
cn=new Chinese("I 'm Chinese!",18);
cn.sout();
}
}
class Person{
int id=431234566;
String name;
public Person(){}
Person (String name){
this.name=name;
}
public void sout(){
System.out.println("这是父类中的输出方法1");
}
}
class Chinese extends Person{
public Chinese(){
super(); //调用父类无参构造方法,这个子类每个构造器都会默认调用的,可不写
}
public Chinese(String name){
super(name);//调用父类有参构造方法
}
public Chinese(String name,int age){
this(name);//通过this去调用构造器中的super方法
System.out.println("父类中的id"+super.id);//调用父类的属性
}
@Override
public void sout() {
System.out.println("这是子类中的输出方法!");
super.sout();//调用父类的sout方法
}
}
输出结果是
父类中的id431234566
这是子类中的输出方法!
这是父类中的输出方法1
super与this的区别
- super()函数在子类构造函数中调用父类的构造函数时使用,而且必须要在构造函数的第一行。
- this()函数调用本类中另一种形成的构造函数。this()只能用在构造函数中,并且也只能在第一行。
- super()和this()都指的是对象,不能同时出现在构造函数里,只能二选一,均不可在static环境中使用。
- super:引用当前对象的父类中的成员。语法格式:super.属性名/方法名()
- this:代表当前对象名。this是对象的隐藏属性 (本质就是一个普通的成员变量),和其他non-static 属性一样,在创建对象的时候会为每个新对象分配该对象的专属成员变量(this就是其中一个),this这个成员变量存储在堆内存中,该变量的值是所属对象在堆内存的地址。
- this代表本类对象的引用,super代表父类的内存空间的标识。
从本质上来讲,this是一个面向对象的指针,然后super是一个Java关键字。
继承的好处:
- 提高代码复用性,减少代码冗余
- 便于功能扩展
- 为多态性的使用,提供了前提
继承也存在一些缺点,继承的类与类之间的耦合度非常高,不利于代码的扩展。
三、多态
多态是方法或对象具有多种形态。多态不适用于属性。
简单解释一下:
即同一方法可以根据发送对象的不同而采用多种不同的行为方式。一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类,有关系的类)。
所谓多态就是指一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,在编译时并不确定,必须在程序运行期才确定。
多态的前提:
- 需要存在继承或实现关系
- 有方法的重写
- 父类引用指向子类引用(也叫向上转型)
注:可以直接应用在抽象类和接口上。
多态的总结
- 一个对象的编译类型和运行类型可以不一致。编译类型是在定义对象时,就确定了,不能改变;运行类型是可以变化的。
- 编译类型看定义时=左边,运行类型看=右边。
- 多态特点:可以调用父类中所有成员(需遵守访问权限),不能调用子类中特有成员;最终运行效果要看子类的具体实现。
- 当使用多态方式调用方法时,首先检查父类中是否有所调用的方法,如果没有,则编译错误;如果有,再去调用子类(运行类型)中的重写方法。
- 想调用子类中所有的成员,可以进行
向下转型
,语法:子类类型 引用名=(子类类型) 父类引用
。一般推荐使用instanceof 去判断一下。 - 对象 instanceof 类名,用于判断对象的运行类型是否为××类型或××类型的子类型。
- java动态绑定机制:当调用对象方法时,该方法会和对象的内存地址/运行内存绑定;调用属性时,没有绑定机制,哪里声明哪里使用。
- 对象类型转换:
向上转型:父类引用指向子类对象
,对象多态性;向下转型:子类引用指向父类对象
,必须强转。
代码举例:
public class JavaTest {
public static void main(String[] args) {
A a=new B();
System.out.println(a.sum());
System.out.println(a.sum1());
}
}
class A{//父类
int i=10;
public int sum(){
return getI()+10;
}
public int sum1(){
return i+10;
}
public int getI(){
return i;
}
}
class B extends A{//子类
int i=20;
public int getI(){
return i;
}
}
猜猜答案是多少,猜对了就说明你真正意义上知道动态绑定机制了。答案是30 20
多态作用
提高了代码的通用性,常称作接口重用。
最后再啰嗦一句:面向对象编程的本质是:以类的方式组织代码,以对象的组织(封装)数据。