Java是一种OP语言,即面向对象的语言,主要的特点有:封装、继承、多态、组合。
一.继承
1.super()
class Base{
private int ma;//数据成员
//静态代码块
static{
System.out.println("Base static init!");
}
//实例代码块
{
System.out.println("Base instanc init");
}
//构造方法
public Base(int a){
System.out.println("Base init");
this.ma = a;
}
}
上述代码定义了一个类,我们将它称之为基类(父类、超类),有自己的数据成员,构造函数
class Derive extends Base{
private int mb;//子类自己的数据成员
static{
System.out.println("Derive static init!");
}
{
System.out.println("Derive instanc init");
}
public Derive(int a,int b){
super(a);//调用基类的构造函数,把基类的东西继承过来,必须第一行!!!
this.mb = b;
System.out.println("Derive init");
}
}
上述代码通过extends关键字将Derive类继承于Base基类,我们把Derive类叫作派生类(子类)。它也有自己的构造函数,并在构造函数的第一行有一个super(a)。这是因为派生类会继承除了构造函数之外的数据成员和成员方法,因此需要在派生类中显示的调用基类的构造函数。
super用法: super( ) 调用基类的构造函数
super.data 调用基类的数据成员
super.func() 调用基类的成员方法
如果不调用super,发现会报错。
2.派生类继承基类之后的打印顺序。
类—>对象的初始化顺序:①静态数据成员
②静态代码块
③实例数据成员
④实例代码块
⑤调用合适的构造函数
Derive d = new Derive(10, 20);
System.out.println("==========");
Derive d2 = new Derive(10, 20);
我们在主函数里写这样一段代码,打印顺序如图。
静态代码块只打印了一次,这是因为静态代码块只初始化一次。
3.关于访问权限。
同包子类 | 同包非子类 | 不同包子类 | 不同包非子类 | |
public | 可以 | 可以 | 可以 | 可以 |
protected(包访问权限) | 可以 | 可以 | 可以 | 不可以 |
private | 不可以 | 不可以 | 不可以 | 不可以 |
默认(包访问权限) | 可以 | 不可以 | 不可以 | 不可以 |
4.派生类和基类的相互复制
Base b = new Base(50);
Derive d = new Derive(10, 20);
b = d;//将子类给父类
Base b2 = new Derive(100, 1000);//基类引用了派生类对象,这个是多态的基础
我们可以定义一个基类对象和一个派生类对象,然后将派生类给基类,但不能将基类给派生类,因为这样子类自己的东西就没有了,会报错。
二. 多态
1.重载和覆盖(重写)
函数名 | 参数列表 | 返回值 | |
重载 | 相同 | 不同 | 不要求 |
覆盖(重写) | 相同 | 相同 | 相同 |
接下来看一段代码
class Base2{//基类
int ma;
public Base2(int a){
this.ma = a;
}
//如何区分实例方法和类方法(有无static)
public void fun1(){//实例方法
System.out.println("Base fun1!");
}
public static void fun2(){//类方法,有static
System.out.println("Base fun2!");
}
}
class Derive2 extends Base2{
int mb;
public Derive2(int a,int b){
super(a);
this.mb = b;
}
//重载
public void fun1(int i){
System.out.println("Derive fun1(参数)");
}
}
class Derive3 extends Base2{
int mc;
public Derive3(int a,int c){
super(a);
this.mc = c;
}
public void fun1(){
System.out.println("Derive3 fun1()");
}
public static void fun2(){
System.out.println("Derive3 fun2()");
}
}
打印结果如图。
Derive2类实现了类的重载,Derive3类实现了方法的覆盖。
2.invokespecial invokevirtual invokestatic
public static void main(String[] args) {
Base2 b = new Base2(10);//invokespecial
System.out.println(b);
b.fun1();//invokevirtual
Base2.fun2();//invokestatic
}
Base2@1d7fbfb:当前引用对象的类信息 @1d7fbfb对象的地址
反汇编中出现了invokespecial invokevirtual invokestatic
Java中构造函数被处理为invokespecial,静态函数被处理为invokestatic,其他的都被处理为invokevirtual。
3.动态绑定和静态绑定
public static void main(String[] args) {
Base2 b1 = new Derive3(10,20);
System.out.println(b1);
b1.fun1();
}
打印结果如图,看一下反汇编
打印结果时派生类的fun1( )函数,为什么反汇编显示调用基类的fun1( )呢?
这是因为Derive3对基类的fun1进行了覆盖,此时派生类的地址就会将基类的地址覆盖,所以打印的时派生类的fun1函数
public static void main(String[] args) {
Base2 b1 = new Derive3(10,20);
System.out.println(b1);
b1.fun2();//打印基类的fun2
}
打印结果如图
为什么又会打印基类的fun2呢?因为fun2是静态方法,在编译时产生,而动态绑定是发生在运行时的,所以fun2方法不会有动态绑定。fun2属于静态绑定,发生在编译时。
3.构造函数也会发生动态绑定