1.继承
继承 (inheritance) :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能 ,这样产生新的类,称 派生类 。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用 。
1.1 继承语法
修饰符 class 子类 extends 父类 {...}1.子类会将父类中的成员变量或者成员方法继承到子类中了2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
public class Base {
int a;
int b;
}
public class Derived extends Base{
int c;
public void method(){
a = 10; //父类中继承下来的a
b = 20; //父类中继承下来的b
c = 30; //子类自己的c
}
}
子类和父类成员变量或方法同名时,优先访问子类,如果子类未定义,则访问父类。
1.2 成员访问
当子类与父类中的方法名相同时,如果重载,按重载后的调用进行,如果未重载,则优先访问子类中的
public class Base {
public void methodA(){
System.out.println("Base中的methodA()方法");
}
}
public class Derived extends Base{
public void methodB(){
System.out.println("Derived中的methodB()方法");
}
public void methodC(){
methodB();
methodA();
}
}
1.3 super关键字
该关键字主要作用:在子类方法中访问父类的成员,尤其是在子类父类成员同名时
如下代码
同名,不使用super则访问子类,使用super则可以访问父类中的同名成员。
在子类方法中,如果想要明确访问父类中成员时,借助 super 关键字即可。
此外,super只能在非静态方法中使用。
1.4 子类构造
构造子类时,会先构造父类
class Basda {
public Basda(){
System.out.println("1");
}
}
class Daer extends Basda{
public Daer()
{
System.out.println("2");
System.out.println("3");
}
}
//构造子类时,输出为
//1
//2
//3
1.若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的 super() 调用,即调用基类构造方法2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。3. 在子类构造方法中, super() 调用父类构造时,必须是子类构造函数中第一条语句。如上图
1.5 再谈this与super
在构造时,两者不可同时出现,但两者都可以在成员方法中用来访问成员变量和调用其他的成员函数,也都可以作为构造方法的第一条语句。
总结如下:
相同点:
1. 都是 Java 中的关键字2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点:
- this是当前对象的引用,super相当于是子类对象中从父类继承下来部分成员的引用
- 在非静态成员方法中,this用来访问本类成员,super用来访问父类继承下来的成员
- 在构造方法中:this()用于调用本类构造方法,super()用于调用父类构造方法,两种调用不能同时在构造方法中出现,如上图代码所示
- 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this()用户不写则没有
1.6 初始化的深入
class ASDWE {
public String name;
public int age;
public ASDWE(String name, int age) {
this.name = name;
this.age = age;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class Test {
public static void main(String[] args) {
Person person1 = new ASDWE("bit",10);
System.out.println("============================");
Person person2 = new ASDWE("gaobo",20);
}
静态代码块只执行一次,在类的加载阶段,当有对象创建时,会执行实例代码块,最后执行构造方法
同理,在创建子类实例时,静态代码块的执行优先度高于实例与构造,
1,父类静态代码块优先于子类静态代码块执行,且是最早执行2,父类实例代码块和父类构造方法紧接着执行3,子类的实例代码块和子类构造方法紧接着再执行4,第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
class Basda {
private int a;
private int b;
public Basda(int a, int b) {
this.a = a;
this.b = b;
System.out.println("1");
}
static{
System.out.println("2");
}
{
System.out.println("3");
}
}
class Daer extends Basda{
public Daer()
{
super(1,3);
System.out.println("4");
}
static {
System.out.println("5");
}
{
System.out.println("6");
}
}
运行结果为:2 5 3 1 6 4
1.7 protected 关键字
封装特性下的访问限定符,主要限定类成员能否被类外或者其他包所访问。
在父类中的默认访问限定符,只能在相同包的子类中可直接访问
同一个包中的不同类可以访问pubilc,protected,默认访问权限以及default接口,不能访问private;
如下图所示,在不同包的非子类中 ,只有public能够被访问
不同包中的子类,只能访问public和protected两种,如下图所示:
1.7 继承方式
1.8 final 关键字
1.修饰变量或者字段,表示常量,不能修改
final int a = 10 ;a = 20 ; // 编译出错2.修饰类:表示此类不能被继承
3.修饰方法:表示该方法不能被重写
1.9 组合
是将一个类的实例作为另外一个类的字段。
// 轮胎
class Tire{
// ...
}
// 发动机
class Engine{
// ...
}
// 车载系统
class VehicleSystem{
// ...
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
}
2. 多态
当不同的对象去完成完成某个行为时会产生出不同的状态
- 1. 必须在继承体系下
- 2. 子类必须要对父类中方法进行重写
- 3. 通过父类的引用调用重写的方法
class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃x");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
//重写Animal下的eat方法
@Override
public void eat(){
System.out.println(name+"吃鱼");
}
}
class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃骨头");
}
}
public class Test {
public static void eat(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Cat cat =new Cat("cat",2);
Dog dog =new Dog("dog",3);
eat(cat);
eat(dog);
}
}
在上述代码中,父类并不关注当前的引用指向哪种子类的实例,此时的这个eat()有多种不同的表现,这种行为就叫多态
2.1 重写
重写也被称为覆盖,重写是子类对父类非静态、非 private 修饰,非 final 修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变 。
2.2 向上转移和向下转型
2.2.1 向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
如上述代码中的Animal animal = new Cat("cat",2);从小范围向大范围的转换
3种用法:
- .直接赋值:Animal animal = new Cat("cat",2);
- 方法传参,形参作为父类型引用,接受任意子类的对象。
public static void eatFood ( Animal a ){a . eat ();}3.作为返回值,可以返回任意子类对象public static Animal buyAnimal ( String var ){if ( " 狗 " . equals ( var ) ){return new Dog ( "dog " , 1 );} else if ( " 猫 " . equals ( var )){return new Cat ( "cat" , 1 );} else {return null ;}
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
2.2.2 向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
public static void main(String[] args) {
Cat cat = new Cat("maomao",2);
Dog dog = new Dog("gougou", 1);
// 向上转型
Animal animal = cat;
//向下转型
if(animal instanceof Cat){
cat=(Cat)animal;
cat.eat();
}
//不会执行
if(animal instanceof Dog){
dog = (Dog)animal;
dog.eat();
}
}
2.3 多态的优缺点
优点:
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
2. 可扩展能力更强
在一定条件下,使用多态可以降低代码的复杂程度。避免使用大量的if-else
如果要扩展代码,直接派生类即可,更新成本低。
缺点:代码的运行效率降低。
属性无多态性,如果父类与子类有同名成员属性,通过父类引用,只能引用父类自己的成员
class B { public B() { // do nothing func(); } public void func() { System.out.println("B.func()"); } } class D extends B { private int num = 1; @Override public void func() { System.out.println("D.func() " + num); } } public class Test { public static void main(String[] args) { D d = new D(); } } // 执行结果 D.func() 0
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
- 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
2.4 动态绑定
动态绑定在上面的重写部分,此处给出详细的定义
动态绑定是指在执行期间(非编译器期间)判断所引用的对象的实际类型,根据其类型调用相应的方法,在程序运行的过程中,把函数调用与响应所需要的代码相结合的过程称为动态绑定。