目录
1.继承
1.1概念
Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序就需要考虑。
比如两个类:猫 和 狗
public class Dog {
public int age;
public String name;
public void eat(){
System.out.println("正在吃饭");
}
public void sleep(){
System.out.println("正在休息");
}
public void bark(){
System.out.println("汪汪叫");
}
}
public class Cat {
public int age;
public String name;
public void eat(){
System.out.println("正在吃饭");
}
public void sleep(){
System.out.println("正在休息");
}
public void bark(){
System.out.println("喵喵叫");
}
}
通过观察可以发现两个类中存在大量重复,那么写出的代码也会有大量雷同。所以面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。
1.2继承的语法
一个类继承其父类需要用到extends关键字,形如:
修饰符 class 子类 extends 父类 {
// ...
}
我们拿1.1的场景来举例子:
//Animal.java
public class Animal {
public int age;
public String name;
public void eat(){
System.out.println(this.name+"正在吃饭");
}
public void sleep(){
System.out.println(this.name+"正在休息");
}
}
//Dog.java
public class Dog extends Animal{
public void bark(){
System.out.println(this.name+"汪汪叫");
}
}
//Cat.java
public class Cat extends Animal{
public void bark(){
System.out.println(this.name+"喵喵叫");
}
}
注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
1.3访问父类成员
继承中子类会将父类中的成员变量或者成员方法继承到子类当中,那我们在子类中访问父类的成员变量或成员方法会发生什么呢?
使用base类和Derived类演示。
1.3.1访问父类成员变量
1.子类和父类不存在同名成员变量
public class Base {
int a = 10;
public int b = 23;
public static int c = 20;
}
public class Derived extends Base{
int d ;
public void test(){
d = 100;//子类直接访问自己成员变量
System.out.println(a);
System.out.println(b);
System.out.println(c);//访问从父类继承过来的a、b、c
System.out.println(d);
a = 0;
System.out.println(this.a);
}
}
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.test();
}
}
//运行结果
//10
//23
//20
//100
//0
2.子类和父类存在同名成员变量
public class Base {
int a = 10;
public int b = 1;
}
public class Derived extends Base{
public int b;
public void test(){
b = 100;
System.out.println(b);
//如果子类与父类的成员变量同名,优先访问子类自己的
}
}
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.test();
}
}
//运行结果
//100
总结:
在子类方法中 或者 通过子类对象访问成员时: 如果访问的成员变量子类中有,优先访问自己的成员变量。 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。 成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
1.3.1访问父类成员方法
1.子类和父类不存在同名成员方法
public class Base {
int a = 10;
public int b = 1;
public void methodA(){
System.out.println("Base()...");
}
}
public class Derived extends Base{
public int b;
public void methodB(){
System.out.println("Derived()...");
}
public void methodC(){
methodB();//访问自己的
methodA();//访问父类的
}
}
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.methodB();
derived.methodA();
derived.methodC();
}
}
//运行结果
//Derived()...
//Base()...
//Derived()...
//Base()...
2.子类和父类存在同名成员方法
public class Base {
int a = 10;
public int b = 1;
public void methodA(){
System.out.println("Base的methodA()");
}
public void methodB(){
System.out.println("Base的methodB()");
}
}
public class Derived extends Base{
public int b;
public void methodA(int a){
System.out.println("Derived的methodA()");
}
public void methodB(){
System.out.println("Derived的methodB()");
}
}
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.methodB();//只能调用到子类自己的methodB
//构成重载,参数不同调用的methodA不同
derived.methodA(100);
derived.methodA();
}
}
//运行结果
//Derived的methodB()
//Derived的methodA()
//Base的methodA()
总结:
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错;
1.4 super 关键字
通过1.3的学习,我们发现当通过子类访问父类与子类同名的成员变量和成员方法时,都是优先在调用子类的。那当我们想要调用父类的方法或变量时就要用到super关键字。
看看如何使用:
public class Base {
int a = 10;
public int b = 1;
public void methodA(){
System.out.println("Base的methodA()");
}
public void methodB(){
System.out.println("Base的methodB()");
}
}
public class Derived extends Base{
public int b ;
public void methodA(int a){
System.out.println("Derived的methodA()");
}
public void methodB(){
System.out.println("Derived的methodB()");
}
public void methodC(){
b = 100;
System.out.println(b);//直接访问是子类自己的
System.out.println(a);
//super的作用:在子类方法中访问父类的成员。
System.out.println(super.b);
methodA(100);
methodA();
methodB();//调用自己的
super.methodB();//调用父类的
}
}
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.methodC();
}
}
//运行结果
//100
//10
//1
//Derived的methodA()
//Base的methodA()
//Derived的methodB()
//Base的methodB()
需要注意的是:
super有点像this,它不能再静态方法中使用的。 并且super也是可以再构造函数中使用的。后文会提到!!!
1.5 构造方法
由于存在父与子的关系,对于子类的构造函数在被调用时,会先调用父类的构造方法,再执行子类的构造方法。
public class A{
public int a;
int b;
public A(int a, int b) {
this.a = a;
this.b = b;
System.out.println("执行了A的构造....");
}
}
public class B extends A{
int c ;
public B(int a, int b, int c) {
super(a, b);
this.c = c;
System.out.println("执行了B的构造...");
}
}
public class Test {
public static void main(String[] args) {
B b = new B(1,2,3);
}
}
运行结果:
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
注意:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现
1.6 带继承关系的代码块运行顺序问题
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
}
{
System.out.println("Person:实例代码块执行");
}
static {
System.out.println("Person:静态代码块执行");
}
}
class Student extends Person{
public Student(String name,int age) {
super(name,age);
System.out.println("Student:构造方法执行");
}
{
System.out.println("Student:实例代码块执行");
}
static {
System.out.println("Student:静态代码块执行");
}
}
public class TestDemo4 {
public static void main(String[] args) {
Student student1 = new Student("张三",19);
System.out.println("===========================");
Student student2 = new Student("gaobo",20);
}
public static void main1(String[] args) {
Person person1 = new Person("wht",10);
System.out.println("============================");
Person person2 = new Person("cxk",20);
}
}
运行结果:
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
1.7 protected关键字
protected作为访问修饰符的一种,它用于定义类、变量、方法和构造函数的访问权限。以下三个场景protected修饰的成员可以被访问:
同一个类中:无论是否使用
protected
修饰符,类中的所有成员都可以互相访问。同一个包中:如果其他类与定义了
protected
成员的类位于同一个包内,则这些类可以访问该protected
成员。不同包中的子类:即使两个类不在同一个包中,但如果一个类继承了定义了
protected
成员的类,则这个子类可以访问父类中的protected
成员。4.不同包中的非子类不可访问:如果试图从不同包中的非子类去访问 protected 成员,则会失败。这与 public 和默认(不使用任何修饰符)访问级别有所不同。
我们看代码:
// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {
private int a;
protected int b;
public int c;
int d;
}
// extend01包中
// 同一个包中的子类
public class D extends B{
public void method(){
// super.a = 10; // 编译报错,父类private成员在相同包子类中不可见
super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
super.c = 30; // 父类中public成员在相同包子类中可以直接访问
super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
// extend02包中
// 不同包中的子类
public class C extends B {
public void method(){
// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
// extend02包中
// 不同包中的类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
// System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
}
1.8继承的方式
继承的方式分为三种:1.单继承 2.不同类继承同一个类 3.多层继承。需要注意的是Java是不支持多继承的。
1.单继承
2.不同类继承同一个类
3.多层继承
1.9 final 关键字
final可以用来修饰类、方法和变量。
1.修饰类
当final修饰类,那么此类不能被继承
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
2.修饰变量
此变量具有常性,不能修改
final int a = 10;
a = 20; // 编译出错
3.修饰方法
此方法不能被重写(在多态会补充!!!)
1.10 继承和组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法 (诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车
// 轮胎类
class Tire{
// ...
}
// 发动机类
class Engine{
// ...
}
// 车载系统类
class VehicleSystem{
// ...
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
}
// 奔驰是汽车
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}