文章目录
下一篇:06【接口、多态】
05【继承、抽象、权限修饰符、final】
一、继承
1.1 继承概述
继承是面向对象三大特征之一,继承就是子类继承父类的特征(属性)和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。
- 总结:
1)子类继承父类可以获得父类的功能,提高代码的复用性
2)子类可以重写(覆盖)某些父类的功能,我们一般称为增强
3)子类除了可以继承父类的功能之外,还可以额外添加子类独有的功能,一般来说,子类要比父类强大(你的是我的,我的还是我的);
在上图中,有4类动物,分别为狮子、老虎、牛、羊,他们都是动物,都具备一些动物的基本属性和行为,例如眼睛、耳朵、鼻子等,所以他们的顶层父类是动物(Aniaml);接着狮子与老虎属于食肉动物,他们具备食肉动物的一些基本属性和行为,他们都继承与食肉动物(Carnivorous);而牛与羊属于食草动物,他们具备食草动物的一些基本属性和行为所以他们都继承与食草动物(Herbivorous)
1.2 继承的格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
继承演示,代码如下:
public class Demo1 {
public static void main(String[] args) {
Student s=new Student();
s.name="小明";
s.age=20;
s.eat();
s.sleep();
Teacher t=new Teacher();
t.name="小明";
t.age=20;
t.eat();
t.sleep();
Worker w=new Worker();
w.name="小明";
w.age=20;
w.eat();
w.sleep();
}
}
class Person{
String name;
int age;
public void eat(){
System.out.println(name +"吃饭");
}
public void sleep(){
System.out.println(name+"睡觉");
}
}
//继承Person类
class Student extends Person{
}
//继承Person类
class Teacher extends Person{
}
//继承Person类
class Worker extends Person{
}
1.3 继承案例
1.3.1 需求
请使用继承定义以下类:
- 程序员(Coder)
- 成员变量:姓名、年龄
- 成员方法:**吃饭、睡觉、**敲代码
- 老师(Teacher)
- 成员变量: 姓名、年龄
- 成员方法: 吃饭、睡觉、上课
1.3.2 分析
1.3.3 代码实现
1)定义父类,抽取共性属性、行为。
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro:
*/
class Person {
// 共性的东西
String name;
int age;
/**
* 吃饭行为
*/
public void eat() {
System.out.println(name + "吃饭");
}
/**
* 睡觉行为
*/
public void sleep() {
System.out.println(name + "睡觉");
}
}
2)定义程序员类,继承父类的name,age等属性,继承eat,sleep等行为,再添加特有的敲代码方法
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro:
*/
class Coder extends Person {
public void coding() {
// 继承父类的name属性
System.out.println(name + "敲代码");
}
}
3)定义程序员类,继承父类的name,age等属性,继承eat,sleep等行为,再添加特有的上课方法
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro:
*/
class Teacher extends Person {
public void teach() {
// 继承父类的name属性
System.out.println(name + "上课");
}
}
4)测试类:
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Teacher t = new Teacher();
t.name = "小灰";
t.age = 20;
t.eat();
t.sleep();
t.teach(); //上课方法
Coder coder = new Coder();
coder.name = "小蓝";
coder.age = 18;
coder.eat();
coder.sleep();
coder.coding(); //敲代码方法
}
}
1.4 父类不可被继承的内容
并不是父类的所有内容都可以给子类继承的,以下2个内容不能被子类继承:
- 被private修饰的
- 构造方法不能继承
tips:虽然被private修饰的成员不能被继承下来,但是通过getter/setter方法访问父类的private成员变量
测试案例:
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.num1 = 10;
// zi.num2=20; // 子类无法继承父类私有的成员
zi.setNum2(20); // 可以通过set方法来设置值(前提是set方法没有被私有)
zi.method1();
// zi.method2(); // 子类无法继承父类私有的成员
}
}
class Fu {
int num1;
private int num2;
public Fu() {
}
public void method1() {
System.out.println("method1");
}
private void method2() {
System.out.println("method2");
}
public int getNum1() {
return num1;
}
public void setNum1(int num1) {
this.num1 = num1;
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
class Zi extends Fu {
//构造方法不能继承,因为构造方法和类名相同,父类和子类的名称肯定不相同,无法继承
/*
public Fu(){
}
*/
}
1.5 成员变量的继承
当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?
1.5.1 成员变量不重名
- 当子类与父类成员变量不重名时:
当子类父类中出现不重名的成员变量,这时的访问是没有影响的;
测试代码:
package com.dfbz.demo04;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Zi zi=new Zi();
zi.method1();
}
}
class Fu{
int num1=10;
}
class Zi extends Fu{
int num2=20;
public void method1(){
System.out.println("父类的: "+num1);
System.out.println("子类的: "+num2);
}
}
运行结果:
父类的: 10子类的: 20
1.5.2 成员变量重名
- 当子类与父类成员变量重名时:
当子类父类中出现重名的成员变量,这时的访问是有影响的,默认取子类的(就近原则);
测试代码:
package com.dfbz.demo05;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method1();
}
}
class Fu {
int num = 10;
}
class Zi extends Fu {
int num = 20;
public void method1() {
System.out.println("父类的: " + super.num);
System.out.println("子类的: " + num);
}
}
运行结果:
父类的: 10子类的: 20
1.6 成员方法的继承
当类之间产生了关系,其中各类中的成员方法,又产生了哪些影响呢?
1.6.1 成员方法不重名
如果子类和父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法;
示例代码:
package com.dfbz.demo06;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Zi zi=new Zi();
zi.method1(); //子类没有,但是父类有
zi.method2();
}
}
class Fu{
public void method1(){
System.out.println("Fu---Method1");
}
}
class Zi extends Fu{
public void method2(){
System.out.println("Zi---Method2");
}
}
1.6.2 成员方法重名(方法的重写)
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。
- 方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
示例代码:
package com.dfbz.demo07;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Zi zi=new Zi();
//执行重写后的method方法
zi.method(); //Zi---Method
}
}
class Fu{
public void method(){
System.out.println("Fu---Method");
}
}
class Zi extends Fu{
//子类重写了父类的method方法(相当于重新实现此方法的功能)
public void method(){
System.out.println("Zi---Method");
}
}
1.6.3 重写的案例
需求:
- 猫类:
- 行为:喵喵的叫、吃鱼
- 狗类:
- 行为:汪汪的叫、吃骨头
图解:
代码实现:
1)定义一个父类(Animal),提供共性功能;
/**
* 动物类
*/
class Animal {
public void eat() {
System.out.println("吃饭");
}
public void speak() {
System.out.println("说话");
}
}
2)定义猫类,重写父类提供的说话、吃饭方法:
/**
* 猫类
*/
class Cat extends Animal{
// 重写吃饭方法
public void eat(){
System.out.println("吃鱼~");
}
// 重写说话方法
public void speak(){
System.out.println("喵喵的叫~");
}
}
3)定义狗类,重写父类提供的说话、吃饭方法:
/**
* 狗类
*/
class Dog extends Animal{
// 重写吃饭方法
public void eat(){
System.out.println("吃骨头~");
}
// 重写说话方法
public void speak(){
System.out.println("汪汪的叫~");
}
}
4)测试类:
package com.dfbz.demo08;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Cat cat=new Cat();
cat.eat();
cat.speak();
Dog dog=new Dog();
dog.eat();
dog.speak();
}
}
1.7 构造方法的继承特点
当类之间产生了关系,其中各类中的构造方法,又产生了哪影响呢?
首先我们要回顾两个事情,构造方法的定义格式和作用。
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
继承后子类构造方法特点:子类所有构造方法都会调用父类的无参构造
示例代码:
package com.dfbz.demo09;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println("----------");
Zi zi2 = new Zi(20);
}
}
class Fu {
private int num = 10;
public Fu() {
System.out.println("Fu无参构造");
}
public Fu(int num) {
System.out.println("Fu有参构造");
}
}
class Zi extends Fu {
private int num = 20;
public Zi() {
// 子类在创建会先把父类加载进内存,子类的所有构造方法中都有一句默认的super();
System.out.println("Zi无参构造");
}
public Zi(int num) {
System.out.println("Zi有参构造");
}
}
运行结果:
tips:子类在继承父类时,必须保证父类有无参构造方法,否则编译报错;
1.8 super关键字
1.8.1 super的作用
- 问题1):在子类继承父类时,如果子类中的成员变量名称和父类的冲突时,那么默认情况下是取子类的成员变量;但是继承自父类的成员变量该怎么获取呢?
- 问题2):在子类继承父类时,子类方法名和父类方法名冲突了,我们知道会出现重写(Override),但此时如果需要调用父类的成员方法该怎么办呢?
super
关键字:用于修饰父类成员变量,类似于之前学过的 this
;this
代表的是本类对象,而super
代表的是父类对象;使用super
我们可以调用父类的成员(属性和行为),注意super关键字不能访问父类私有(private修饰)的成员
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量(不是private修饰的)时,需要使用 super
关键字,修饰父类成员变量,类似于之前学过的 this
。
关于权限修饰符知识我们后面再讲解;
- super的关键字的作用如下:
- 1)用于访问父类中定义的属性
- 2)用于调用父类中定义的成员方法
- 3)用于在子类构造方法中调用父类的构造器
1.8.2 this和super图解
- 1)main方法进栈执行;
- 2)执行
new Zi()
,首先现将Fu、Zi两个类加载到内存(方法区) - 3)初始化Fu类,再初始化子类;子类中保留父类的引用
super
;可以通过super来获取父类的非私有内容; - 4)子类调用method方法,调用的是this区中的方法,因此输出的是
Zi method...
示例代码:
package com.dfbz.demo10;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println(zi.num);
zi.method();
}
}
class Fu {
private int num = 10;
public void method() {
System.out.println("Fu method...");
}
}
class Zi extends Fu {
int num = 20;
public void method() {
System.out.println("Zi method...");
}
}
1.8.3 使用super获取父类成员
使用格式:
super.父类成员变量名
子类方法需要修改,代码如下:
class Zi extends Fu {
int num = 20;
public void method() {
System.out.println("Zi method..."); // 获取父类的属性
System.out.println(super.num); // 获取父类的方法
super.method();
}
}
tips:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用
private
修饰成员变量,但是可以在父类中提供公共的getXxx
方法和setXxx
方法。
在父类提供get/set方法:
class Fu {
private int num = 10; // 提给get/set方法
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public void method() {
System.out.println("Fu method...");
}
}
子类通过get/set方法来访问父类私有成员:
class Zi extends Fu {
int num = 20;
public void method() {
System.out.println("Zi method..."); // 获取父类的属性
// System.out.println(super.num); // 父类私有成员不可以通过super获取
// 通过提供的get/set方法来访问
System.out.println(super.getNum()); // 获取父类的方法
super.method();
}
}
1.8.4 this和super访问构造方法
我们知道子类的每个构造方法中均有默认的super()
,调用父类的空参构造。手动调用父类构造会覆盖默认的super();super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
示例代码:
package com.dfbz.demo11;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
Coder c = new Coder();
c.method();
}
}
class Person {
private String name;
private int age;
/**
* 无参构造
*/
public Person() {
}
/**
* 有参构造
*
* @param name
* @param age
*/
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("人吃饭");
}
}
class Coder extends Person {
public Coder() {
//this(); //不能自己调用自己
this("程序员-小张", 20);
// super(); //写了super就不能写this了
}
public Coder(String name, int age) {
super(name, age);
}
public void eat() {
System.out.println("程序员吃饭");
}
public void method() {
super.eat();
this.eat();
}
}
运行结果:
人吃饭
程序员吃饭
1.9 继承的特点
1.9.1 继承问题
在Java中,不支持多继承,只支持单继承,但支持多重继承;即A继承B,B继承C,这样下来C间接继承与A;
- Java只支持单继承,不支持多继承。
class A{}
class B{}
class C extends A{}
//class C extends A extends B{} //错误
- 一个类可以有多个子类。
// A可以有多个子类
class A {}
class B extends A {}
class C extends A {}
- Java支持多层继承。
class A {}
class B extends A {}
class C extends B {}
1.9.2 钻石问题
钻石问题(diamond problem)指的是在多继承中,多个父类拥有同名方法时,子类继承时的问题;
示例代码:
// 祖父类
class A {
public void method() {
System.out.println("Grandparent");
}
}
// 父类1
class B extends A {
public void method() {
System.out.println("Parent1");
}
}
// 父类2
class C extends A {
public void method() {
System.out.println("Parent2");
}
}
// 编译报错
class D extends B, C {}
1.9.3 static 继承的特点
我们知道被static
修饰的成员是属于类的,而不是对象的;那么被static修饰的成员能否被继承到子类,供子类使用呢?
答:被static修饰的成员是可以被继承到子类的;
测试代码:
package com.dfbz.demo13;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
System.out.println(A.abc);
System.out.println(B.abc);
System.out.println(new A().abc); // 使用匿名对象方法
System.out.println(new B().abc); // 使用匿名对象方法
A.method(); // aa
B.method(); // bb
new A().method(); // aa
new B().method(); // bb
}
}
class A{
static int abc=10;
static void method(){
System.out.println("aa");
}
}
class B extends A{
// 重写static方法必须使用static修饰该方法
static void method(){
System.out.println("bb");
}
}
二、抽象类(Abstract Class)
2.1 抽象类概述
在继承体系中,由于父类的设计应该保证继承体系中所有子类的共性,子类往往比父类要描述的更加清晰、具体;因此我们有时需要将父类设计的抽象化;即方法只声明方法体,而没有方法具体功能,我们把没有方法主体的方法称为抽象方法。包含抽象方法的类就是抽象类。
2.1.1 举例
1. 厨师 成员变量:工号,姓名,工资 成员方法:工作(炒菜)
2. 保安 成员变量:工号,姓名,工资 成员方法:工作(检查外来人员)
3. 保洁 成员变量:工号,姓名,工资 成员方法:工作(负责公司的清洁)
图解:
2.1.2 定义
- 抽象方法 :没有方法体的方法。
- 抽象类:包含抽象方法的类。
2.2 abstract使用格式
使用 abstract
关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
代码举例:
public abstract void work();
2.2.1 抽象类
如果一个类包含抽象方法,那么该类必须是抽象类。
定义格式:
abstract class 类名字 {
}
代码举例:
public abstract class Animal {
public abstract void run();
}
2.2.2 抽象的使用
1)继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。
2)必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
抽象类是不可以进行实例化的,抽象类本就是包含有无法实例化的抽象方法,或者说这个方法是没有任何意义的,他存在的意义就是让子类去实现它;因此抽象类是不可以实例化的,也就是不能创建对象;
代码举例:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
}
}
/**
* 员工类(抽象)
*/
abstract class Employee {
private String id;
private String name;
private Double salary;
public Employee() {
}
public Employee(String id, String name, Double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// 抽象方法不能有方法体
public abstract void work(); // 表示员工本就有工作,但是每个员工的工作内容是不一样的(由具体的子类来实现)
}
/**
* 厨师类
*/
class Cook extends Employee {
// 必须重写父类的抽象方法
@Override
public void work() {
System.out.println("炒菜");
}
}
/**
* 保洁类
*/
class Cleaner extends Employee {
// 必须重写父类的抽象方法
@Override
public void work() {
System.out.println("检查外来人员");
}
}
/**
* 保安类
*/
class Security extends Employee {
// 必须重写父类的抽象方法
@Override
public void work() {
System.out.println("负责公司清洁");
}
}
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
2.3 抽象类注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
三、权限修饰符
3.1 概述
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,
- public:公共的。
- protected:受保护的
- default:默认的
- private:私有的
public > protected > 默认 > private
3.2 不同权限的访问能力
public(公共) | protected(保护) | default(默认) | private(私有) | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中(子类或任意类) | √ | √ | √ | |
不同包的子类(通过super访问) | √ | √ | ||
不同包的任意类 | √ |
public
具有最大权限。private
则是最小权限。
tips:不加权限修饰符,就是default权限
3.2.1 private
private是最小的权限,只能在本类中访问,其他类均不可访问private修饰的成员;
class Fu{
private String name;
public void show(){
System.out.println(name);
}
}
class Zi extends Fu{
public void show(){
System.out.println(super.name);
}
}
将权限修饰符改为defalut之后:
3.2.2 default
default
是默认的权限修饰符,使用时不用显式的加上default
关键字,什么修饰符都不加默认是default
权限;default权限允许同一个包下是可以访问的,如果是不同包,那就访问不了了;
将权限修饰符改为protected:
3.2.3 protected
protected
修饰符的范围是不同包下的子类可以访问,只要是子类,都可以访问的到,但是如果不是子类就不能访问了;
3.2.4 public
public
是最高的权限修饰符了,他的权限是任意包的任意类中都可以访问;
3.3 权限修饰符和方法的重写
在继承那一章节我们学习过方法的重写,当子类继承父类时,我们可以重新父类的方法,使其更加强大;
但是方法的重写必须保证子类方法的权限修饰符>=父类方法的权限修饰符;
换句话说,如果子类方法的权限修饰符小于父类方法的权限修饰符那么方法将不能被重写(语法报错);
- 父类:
class Fu {
// 私有
private void method1() {
}
// 默认
void method2() {
}
// 保护
protected void method3() {
}
// 公开
public void method4() {
}
}
测试:
大家私下可以多测试几种;
四、final关键字
fianl是Java中的一个关键字,中文含义是”最终的”,fianl可以修饰类、方法、变量等;
4.1 fianl关键字的特点
-
修饰类:
- final修饰的类不能被继承。提高安全性,提高程序的可读性;
final class A{ } class B extends A{ // 错误,被final修饰的类不能被继承。 }
-
修饰方法:
- final修饰的方法不能被子类重写;
class A{ public final void method(){ System.out.println("hello"); } } class B extends A{ public void method(){ // 错误,被final修饰的方法不能被重写 System.out.println("Hi"); } }
-
修饰变量:
- final修饰的变量(成员变量或局部变量)只能被赋值一次;因此被final修饰的变量我们称为常量,通常全大写命名;
class A{ private final double PI = 3.14; // 声明常量 public void print(){ PI = 3.15; // 错误,被final修饰的变量是常量(不可更改值) } }
下一篇:06【接口、多态】
记得点赞~!!!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131780.html