封装、继承、多态、(抽象)公认的有3个,抽象不常说,但是也是特征之一
一、封装
体现形式:
1、函数:(将一段功能或者逻辑封装成一个函数)
提高了代码的复用性
2、属性私有化:通过提供对外的访问方法间接操作对应的属性,可以在方法内对属性做一系列的限定使之符合要求,将属性保护起来,使之更符合事实逻辑
提高了代码的安全性
3、内部类
在实际开发中,大部分属性都是私有的。 |
权限修饰符
修饰符 | 本类 | 子类 | 同包类 | 其他类 |
public | ok | ok | ok | ok |
protected | ok | ok | ok | X |
private | ok | X | X | X |
默认 | ok | 同包子类可以 | ok | X |
方法的使用应该在对应的对象所在的类中使用。
二、继承
对一些类中的共有的属性和方法进行抽取,抽取到一个新的类中,利用extends关键字使原来的类和新的类产生联系,那么这种联系就称之为继承。新的类称之为父类(基类、超类),原来的类称之为子类(派生类)
注意:Java中支持的是单继承 |
一个子类只能有一个父类,而一个父类可以有多个子类 |
多继承可能会出现的问题:
class A{
public String m(){
return "aaaaa";
}
}
class B{
public int m(){
return 0;
}
}
class C extends A, B{
C c = new C();
String s = c.m();
//目的是执行A中的方法,但是JVM并不知道到底执行哪一个,所以不一定执行的哪个方法。
//返回值类型可能是int,可能是String,不确定
}
Java之所以摒弃多继承就是为了避免出现调用的混乱。
优点
(1)提高了代码的复用性
(2)提高了代码的安全性
(3)统一了结构
super
super代表了父类对象的引用,可以认为是父类产生的一个虚拟对象,在子类中之所以能够调用父类的非私有的方法和属性,是因为有super这个关键字的存在。
super语句:在子类的构造函数中创建一个父类对象,这个父类对象用super表示
每个子类在创建对象的时候,必须先创建父类对象,没有父类对象就不会产生子类对象,而这个父类对象在子类中以super关键字形式存在。
如果子类的构造函数中没有手动添加super语句,JVM在编译的时候会默认添加一个无参的super语句。
如果父类中没有无参构造,这时候JVM能够成功添加supper语句吗?
不能,这时候要求子类中的构造函数中必须手动添加含参的super语句。
super语句必须放在子类构造函数的首行。(this语句和super语句不可以同时出现)
如果一个构造函数已经存在了this语句,那么此时不能写super语句,此时如何创建父类对象?
this语句是调用其他的构造方法,如果该构造函数已经有了this语句,可以在它调用的构造方法里写super语句。间接调用。
所有子类的构造函数中都直接或者间接地有一个super语句。
可以通过this来调用父类中的属性和方法吗?
可以。This优先调用的是子类的,但是如果子类中没有对应的属性和方法,则会转到父类中寻找。
在栈中先开辟主方法的空间,然后在主方法里开辟变量的空间,遇到new,先检查是否有父类,有父类的话,先在堆中创建父类对象,接着创建子类对象,在子类对象中存放着父类对象的地址。
调用方法的时候,用super的时候直接找是父类的;用的是this时会先检查当前对象是否有对应的方法,如果有,直接使用,如果没有,再去检查父类的。
重写(要有继承)
父子类中存在了方法签名完全一致的方法,就称之为方法的重写/覆盖
注意:两等两小一大原则
1.= 方法签名一致
2. = 如果父类中方法的返回值是基本数据类型/void/最终类,那么子类在重写方法的时候,返回值类型必须一致
3. <= 当父类中的方法的返回值类型是引用类型的时候,子类的方法的返回值类型要么和父类方法的返回值一致,要么是父类返回值类型的子类。
4. <= 子类声明的异常的类型不能超过父类。
5. >= 子类中重写的方法的权限修饰符必须大于等于父类
三、多态
两种体现形式:
编译时多态:函数的重载—在编译时期就动态绑定了执行的过程
运行时多态:向上造型和函数的重写—继承是运行时多态的前提
(向上造型:用父类来声明,用子类来创建也就是父类引用指向子类对象)
向上造型是运行时多态是因为:在编译的时候只会检查两个类之间是否有继承关系,而不会具体关心是哪个子类也就是并不会在编译的时候去绑定具体的子类。
方法的重写是运行时多态是因为:在编译的时候,只需要检查这个声明的的类中是否包含指定的方法,到运行的时候才绑定子类中的具体方法。
注意:用向上造型创建对象的时候,用父类声明,用子类创建;父类在声明的时候就相当于给这个对象一份提纲;子类创建对象的时候是告诉这个对象该怎么干----能不能用看父类,具体怎么做看子类。
用向上造型创建的对象只能使用父类中已有的方法,而不能使用具体子类中特有的方法。
在类型强制转换时,编译的时候只需要检查这两个类之间是否有继承关系,并不会具体到是哪个对象。
由于Java支持的是多实现,并且接口之间的多继承,所以放弃了在编译的时候检查类与接口的实现关系。这就导致任何一个对象都可以被一个接口进行强转。
上述两种情况在运行的时候如果不符合规定均会报错。
重写的理解
(1)、方法的签名完全一致(重写发生的前提)。
(2)、 子类中重写的方法的权限修饰符必须大于等于父类
class A {
public void m(){}
}
class B extends A {
private void m(){}
}
A a = new B(); //用A类来声明的a对象,告诉了a对象可以使用m方法
a.m(); //到了具体使用的时候,看实现子类
//实现子类是B类,就去B类中找m方法,在B类中的m方法是私有的,不能在类外使用
//这时候产生了冲突。
//所以在方法重写的时候权限修饰符必须大于等于父类
(3)、 如果父类中方法的返回值是基本数据类型/void/最终类,那么子类在重写方法的时候,返回值类型必须一致。
注意:八种基本数据类型之间是平等关系,不存在继承关系,也就是说它们没有子类,所以就只能返回它本身。
(4)、子类中重写的方法的返回值类型必须小于等于父类。也就是说当父类中的方法的返回值类型是引用类型的时候,子类的方法的返回值类型要么和父类方法的返回值一致,要么是父类返回值类型的子类。
class A{
public void m(){}
}
class B extends A {
public void m(){}
public void mb(){}
}
class C {
public B mc(){}
}
class D extends C {
public A mc(){}
}
C c = new D();
B b = c.mc();
b.mb();
// B b = new A();
b.mb();
//声明c对象用的是C类,告诉c对象身上有一个mc方法,并且返回值类型是B类,所以调用mc方法的时候可以用一个B类的对象来接住结果,可以再次调用B类对象身上的mb方法。
//实现用的是D类,具体执行mc方法的时候调用的是D的mc方法,D类中的方法返回值类型是A类,所以可以定义一个A类对象来接住方法执行后的结果,A身上有mb方法吗?
//没有。所以就产生了冲突。
优点
(1)提高了代码的灵活性
(2)结合反射、接口以及配置文件可以实现程序的解耦
注意:变量的声明和初始化是两个过程。变量在声明的时候规定了这个变量可以有什么,可以做什么,但是不知道怎么做;变量的初始化是告诉这个变量怎么做。变量的声明过程相当于在写一个规划书。变量的初始化的过程相当于在实现这份规划书。