Java-类的封装、继承和多态
类和对象
概念
面向对象编程的本质:以类的方式组织代码,以对象的方式封装数据!
类是一种数据类型,声明了一个类就是定义了一个数据类型。
类的实例(Instance)是类的取值
对象是类的变量
类(class)
类(class)是既包括数据又包括作用于数据的一组操作的封装体。类可以看成是一类相似对象的模板!类具有封装性、继承性、多态性和抽象性。
对象(object)
对象(object)是类的具体实例(instance)。Java中,万事万物皆对象!!!Java中通过引用操作对象!引用就是指向对象的内存中地址!!
变量
局部变量、实例变量、类变量
实例变量
也叫对象变量、类成员变量;从属于类由类生成对象时,才分配存储空间,各对象间的实例变量互不干扰,能通过对象的引用来访问实例变量。但在Java多线程中,实例变量是多个线程共享资源,要注意同步访问时可能出现的问题。
1 | public class Demo { |
类变量
也叫静态变量,是一种比较特殊的实例变量,用static关键字修饰;一个类的静态变量,所有由这类生成的对象都共用这个类变量,类装载时就分配存储空间。一个对象修改了变量,则所以对象中这个变量的值都会发生改变。
1 | public class Demo { |
局部变量
方法中或者某局部块中声明定义的变量或方法的参数被称为局部变量,他们只存在于创建他们的block里({}之间)无法在block外进行任何操作,如读取、赋值。在Java多线程中,每个线程都复制一份局部变量,可防止某些同步问题发生。
声明对象相当于依照某一类构造出来的一个具体的东西,但是必须通过赋值才能使对象获得实例
在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量、属于类,被该类的所有实例共享,在类被载入时被显示初始化;对于该类的所有对象来说,static成员变量只有一份;可以使用“对象.类属性”来调用,不过,一般都是用”类名.类属性”;static变量置于方法区中
局部变量使用前必须要先赋值、而实例变量则有缺省初值(数值:0,0.0;字符char:\u0000;布尔类型boolean:false;所有引用类型:null)
方法
面向对象中,整个程序的基本单位是类,方法是从属于类或对象的。
方法定义格式:
1 | [修饰符] 方法返回值类型方法名(形参列表) { |
修饰符可省略,可是public,protected,private,static,final,abstract等;类型可以是基本类型和引用类型或void。
class关键字
声明类
声明类
成员变量和成员方法统称为类的成员(Menmber)
1 | [修饰符] class 类<泛型> [extends 父类][implements 接口列表] |
声明成员变量和成员方法
1 | [修饰符] 返回值类型方法([参数列表])[throws 异常类] |
声明对象
方法
构造方法
用于构造该类的实例
格式:[修饰符] 类名(形参列表) {…}
通过new关键字调用;
构造器虽然有返回值,但是不能定义返回类型(返回值的类型肯定是本类),不
能在构造器里调用return;
如果我们没有定义构造器,系统自动会定义一个无参的构造器,如果已定义则编译器不会添加;
构造器的方法名必须和类名一致。
作用:构造该类的对象,经常也用来初始化对象的属性。
声明及调用构造方法
1 | //声明构造方法 |
默认构造方法
1 | public MyDate() |
方法的重载(overload)
方法的重载是指一个类中可以定义有相同的名字,但参数不同的多个方法。调用时,会根据不同的参数列表选择对应的方法。
两同三不同:同一个类,同一个方法名,不同的参数列表(类型、个数、顺序不同)。
只有返回值不同不构成方法的重载。
只有形参的名称不同,不构成方法的重载。
构造方法与普通方法一样也可以重载。
对象的引用和运算
this
只能出现在方法内,从属于对象,是普通成员方法的一个隐式参数。
调用非静态方法时,自动传递了这两个隐式参数(this,super)!!必须传这两个隐式参数,因为类的所有的实例化对象都共享一份类代码(包括方法代码),如果不传,则在方法中不知道是哪个对象调用的!
this不能用于static方法!
可以在一个构造方法中通过this调用其它构造方法,且必须是构造方法中的第一条语句。
普通方法中,this总是指向调用该方法的对象
构造方法中,this总是指向正要初始化的对象
instanceof
访问控制
“高内聚、低耦合”
类中成员的访问权限
权限修饰符 | 同一类 | 同一包 | 不同包的子类 | 所有类 |
---|---|---|---|---|
public(公有) | ✅ | ✅ | ✅ | ✅ |
protected(受保护的) | ✅ | ✅ | ✅ | |
缺省 | ✅ | ✅ | ||
private(私有) | ✅ |
静态成员
类中的实例变量是在创建对象时被初始化的,被static修饰的属性(类变量),是在类加载时被创建并进行初始化,类加载的过程只进行一次。也就是类变量只会被创建一次。
静态成员变量
上面有
静态方法
用static声明的方法为静态方法。
不需要对象,就可以调用(“类名.方法名”);在调用该方法时,不会将对象的引用(this)传递给它,所以在static 方法中不可访问非static的成员。
静态初始化块static {}
如果希望加载后,对整个类进行某些初始化操作,可以使用static初始化块
继承(extends)
相关概念知识
方法的重写(override)
重写发生在子类和父类之间
在子类中可以根据需要对父类中继承来的方法进行重写。
重写方法必须和被重写方法具有相同方法名称、参数列表(形成重写的要点1)。
通过子类去调用该方法,会调用重写方法而不是被重写方法(叫做重写方法覆盖被重写方法)。
重写方法的访问权限,子类大于等于父类(由于多态)(形成重写的要点2)。
重写方法的返回值类型和声明异常类型,子类小于等于父类(形成重写的要点3)。
可以在子类重写方法中调用被重写方法:super关键字。
与重载(overload)没有任何关系。方法重载指的是:同一个类中,一个方法名
对应了多个方法(形参列表不同);方法的重写指的是:子类重写了父类的方法!
利用重写,既可以重用父类的方法,而且还可以灵活的扩充。
对象.方法():先在本类内部找是否有该方法,如果没有,到直接父类去找,如果还没有,则一直往上层找,一直找到Object,如果还没有,则报错。
子类对父类成员的访问权限
1.子类不能访问父类的私有成员(private)。
2.子类能够访问父类的公有成员(public)和保护成员(protected)。
3.子类对父类的缺省权限成员的访问控制,以包为界分两种情况,可以访问当前包中
子类的构造方法
使用super()调用父类构造方法
super([参数列表])
1 | public Student(String name, MyDate birthday, String spec) |
默认执行super()
1 | public Student() //Java提供的默认构造方法 |
子类的构造方法没有调用super()或this(),将默认执行super()
super引用
总结
super是直接父类对象的引用。可以通过super来访问父类中被子类覆盖的方法或属性。
super和this一样,只能用于方法内部,是方法的两个隐式参数。
普通方法:没有顺序限制,可以随便调用。
构造方法:任何类的构造方法中,若是构造函数的第一行代码没有显式调用super(…);那么Java默认都会调用super();作为父类的初始化函数。所以这里的super()加不加都会无所谓。(内存分析,wrap:new对象的时候采用子类包裹父类的结构)
同一个构造方法里面不能同时调用super()和this()。
在本类构造方法中通过super()调用,会一直上溯到Object()这个构造函数,然后按类层级,依次向下执行各层级构造函数中剩下的代码,直至最低层级的构造函数。同this()一样,super()方法也应该放到构造方法的第一行。
new一个类的对象的时候,通过构造方法的从上至下的依次调用,就依次建立了新的根对象、父类对象和自身对象,其中,this指向新建的对象本身,super指向新建的直接父类对象本身。
一个例子(注意一下其中的this)
1 | package edu.whut.myServlet; |
1 | package edu.whut.myServlet; |
多态
编译时多态
略
运行时多态
p不能调用子类增加的方法
方法的多态性总结
三个必要条件
- 要有继承
- 要有方法重写
- 父类引用指向子类对象
Person p = new Child();
举个栗子:
1 | class Shape { |
多态一般指的是重写方法的多态。属性没有多态。
继承时,对于方法覆盖时,new的谁,this就指向谁,多态性决定。
如果是成员变量,this在哪个类就指向哪个类的成员变量,成员变量没有多态性。
类的多态性总结
方法的多态是子类重写了父类的方法,父类引用指向子类对象,通过父类.被重写方法,实际调用的是子类的重写方法(这个相当于把子类对象地址赋给父类对象引用,编译时,编译器通过声明的父类类型去查找父类代码中是否有该方法,有,则通过了编译,实际运行时,则依据实际内存地址找到了子类对象内存空间,因此,实际执行的是子类方法)。
A instanceof B:A对象的类型是否是B类型,只有在A对象的类型和B类型相同,或为父子类型时,编译不报错。而在运行时,只有A对象类型为B类型的子类型或者就是B类型时,结果才返回true。
例子”myServlet”
调用父类的service(),然后调用子类的doGet()(注意:this关键字指向整个最终包裹对象,即最外层的子对象;而在包裹对象中,每一层对象通过super关键字指向内一层的父对象)。
抽象类
为什么需要抽象类?如何定义抽象类?
是一种模版模式。抽象类为所有子类提供了一个通用模版,子类可以在这个模版基础上进行扩展。
通过抽象类,可以避免子类设计的随意性。通过抽象类,可以严格限制子类的设计,使子类之间更加通用。
要点
有抽象方法的类只能定义成抽象类。
抽象类不能实例化,即不能用new来实例化抽象类。
abstract修饰方式的初衷就是要求其子类覆盖(实现)这个方法,并且调用时可以以多态方式调用子类覆盖后的方法(抽象类主要和多态技术相结合),即抽象方法必须在其子类中实现,除非子类本身也是抽象类。abstract不允许修饰成员变量,因为成员变量也没有重写这个概念!
不能放在一起的修饰符:final和abstract,private和abstract,static和abstract,因为abstract修饰的方法是必须在其子类中实现(覆盖),才能以多态方式调用,以上修饰符在修饰方法时期子类都覆盖不了这个方法,final是不可以覆盖,private是不能够继承到子类,所以也就不能覆盖,static是可以覆盖的,但是在调用时会调用编译时类型的方法,因为调用的是父类的方法,而父类的方法又是抽象的方法,又不能够调用,所以上面的修饰符不能放在一起。
声明抽象类与抽象方法
1 | public abstract class ClosedFigure//闭合图形抽象类 |
抽象类的特点
1.构造方法、静态成员方法不能被声明为抽象方法。
2.一个非抽象类必须实现从父类继承来的所有抽象方法。
3.不能创建抽象类的实例。
最终类 final
最终类不能被继承
最终方法不能被子类覆盖
final 修饰变量时表示常量。必须为常量赋值。