##################################################
目录
继承
什么是继承
继承语法
super 关键字
如何使用继承
java.lang.Object 类
默认从父类的继承
访问修饰符的访问权限
重写和继承关系中的构造方法
方法重写
子类重写父类的方法
super 关键字
重载和重写的区别
继承关系中的构造方法
多级继承的构造
抽象类
抽象类和抽象方法的特点
示例抽象类和抽象方法
final 修饰符
final 的应用
final 修饰类
final 修饰方法
final 修饰变量
final 修饰引用型变量
abstract 是否可以与 private、static 或 final 共用
##################################################
继承
——————————
什么是继承
继承是面向对象的三大特性之一
是 Java 中实现代码重用的重要手段之一
Java 中只支持单继承 每个类都只能有一个直接父类
继承表达的是 is a 的关系 或者说是一种特殊和一般的关系 例如 You is a Ren
同样 可以让学生继承人 让香蕉继承水果 让三角形继承几何图形
我们之前设计的程序有许多不足 一是代码重复 二是修改量大
我们将多类中相同的属性和方法从父类继承 避免了代码重复也方便了日后代码修改
可以将 A 类和 B 类中相同的属性和方法提取出来存放在一个单独的 C 类中
然后让 A 类和 B 类继承 C 类 同时保留自己特有的属性和方法
这就需要使用 Java 的继承功能来实现
——————————
继承语法
语法:
修饰符 SubClass extends SuperClass {/* 类定义部分 */}
继承/inheritance 通过 extends 关键字来实现
其中的 SubClass 称为子类
SuperClass 为父类、基类或超类
修饰符如果是 public 则该类在整个项目中可见
若无 public 修饰符则该类只能在当前包可见
不可以使用 private 和 protected 修饰类
——————————
super 关键字
super 关键字表示直接调用父类的方法 包括构造方法:
super(构造方法);super.方法();
——————————
如何使用继承
创建一个人人类:
package demo;public class RRen {/* 人人类 是人类的父类 */private String name = "无名氏";// 姓名private Double gao = 1.89;// 身高public RRen () {/* 无参构造方法设置身高 */this.gao = 2.50;System.out.println ( "执行人类的无参构造方法.." );}public RRen(String name) {/* 带参构造设置姓名 */this.name = name;}public String getName() {/* 获取姓名方法 */return this.name;}public double getGao() {/* 获取身高方法 */return this.gao;}public void print () {/* 输出个人信息 */System.out.println ();System.out.printf ( "姓名 >>> [%s]\n", this.getName() );System.out.printf ( "身高 >>> [%f]\n", this.getGao() );}}
人类继承人人类:
package demo;public class Ren extends RRen {/* 人类继承人人类 */private String str = "我没什么话要说..";// 说话public Ren ( String name, String str ) {/* 有参构造方法 参数为姓名和话语 */super(name);// 调用父类的有参构造方法 这里不能使用 this.name = namethis.str = str;}public String getStr () {/* 获取话语 */return this.str;}}
小孩类也继承人人类:
package demo;public class Hai extends RRen {/* 孩子类继承人人类 */private char sex = '女';// 说话public Hai ( String name, char sex ) {/* 有参构造方法 参数为姓名和话语 */super(name);// 调用父类的有参构造方法 这里不能使用 this.name = namethis.sex = sex;}public char getSex () {/* 获取性别 */return this.sex;}}
在测试类中调用:
package demo;public class Test {/* 测试类 */public static void main(String[] args) {/* 创建一个人人对象并输出信息 */RRen rr = new RRen( "沙琪玛" );rr.print ();/* 创建一个人对象并输出信息 */Ren r = null;r = new Ren ( "奥利给", "大家好,我是人!" );r.print ();/* 创建一个小孩对象并输出信息 */Hai h = new Hai( "冰淇淋", '女' );h.print ();}}
执行结果如下:
姓名 >>> [沙琪玛]身高 >>> [1.890000]姓名 >>> [奥利给]身高 >>> [1.890000]姓名 >>> [冰淇淋]身高 >>> [1.890000]
——————————
java.lang.Object 类
在 Java 中所有的类都间接或直接继承了 Object 类
Object 类是所有 Java 类的祖先
在定义一个类时如果没有使用 extends 关键字 那么这个类直接继承 Object 类
例如:
public class MyObject {}
表明 MyObject 类的直接父类为 Object 类
Object 类的方法列表:
equals(对象) 返回布尔值测试指定对象是否与该对象相等getClass() 返回类返回该对象的运行时类hashCode() 返回整型返回该对象的哈希码值notify() 返回空值唤醒该对象监视器上等待的单个进程notifyAll() 返回空值唤醒该对象监视器上等待的所有进程toString() 返回字符串返回该对象的字符串表示wait() 返回空值在其她线程调用此对象的 notify() 或 notifyAll() 前导致该线程等待wait(时间量) 返回空值在其她线程调用此对象的 notify()/notifyAll() 或超过指定的时间量前导致该线程等待wait(时间量, 中断信号) 返回空值在其她线程调用此对象的 notify()/notifyAll() 和超过指定的时间量前以及某个线程中断当前线程导致该线程等待
——————————
默认从父类的继承
在 Java 中子类默认从父类中继承 public 和 protected 修饰的属性和方法而无论子类和父类是否在同一个包中
继承默认权限修饰符 friendly 修饰的属性和方法 但子类和父类必须在同一个包中
无法继承 private 修饰的属性和方法
也无法继承父类的构造方法
——————————
访问修饰符的访问权限
私密 private
本类
默认 friendly
本类
同包
同包 protected
本类
同包
子类
公开 public
本类
同包
子类
其她
##################################################
重写和继承关系中的构造方法
——————————
方法重写
在子类中可以根据需求对从父类继承的方法进行重新编写
称为方法的重写或方法的 覆盖/overriding
方法重写必须满足如下要求:
重写方法和被重写方法必须具有相同的方法名
重写方法和被重写方法必须具有相同的参数列表
重写方法的返回值必须和被重写方法的返回值类型相同或者是其子类
重写方法不能缩小被重写方法的访问权限
——————————
子类重写父类的方法
如果从父类继承的方法不能满足子类的需求
例如 子类的方法输出内容是父类方法的内容
则在子类中可以对父类的同名方法进行 重写/覆盖 以符合需求!
例如重写刚刚的人人类 是人类和小孩类的父类
但是人类和小孩类的 print() 输出方法输出的信息是人人类这个父类的成员属性信息
我们需要在子类中重写 print() 以覆盖父类的方法!
这是父类的 print 方法:
public void print () {/* 输出个人信息 */System.out.println ();System.out.printf ( "姓名 >>> [%s]\n", this.getName() );System.out.printf ( "身高 >>> [%f]\n", this.getGao() );}
Ren.java code:
package demo;public class Ren extends RRen {private String str = "我没什么话要说..";public Ren ( String name, String str ) {super(name);this.str = str;}public String getStr () {return this.str;}public void print() {/* 重写父类的 print() 方法 */super.print ();// 调用父类的 print 输出信息方法/* 下面加入自己的成员属性信息 */System.out.printf ( "我想说:“%s”\n", this.getStr() );}}
Hai.java code:
package demo;public class Hai extends RRen {private char sex = '女';public Hai ( String name, char sex ) {super(name);this.sex = sex;}public char getSex () {return this.sex;}public void print() {/* 重写父类的 print() 方法 */super.print ();// 调用父类的 print 输出信息方法/* 下面加入自己的成员属性信息 */System.out.printf ( "性别 >>> [%c]\n", this.getSex() );}}
IDE demo:
姓名 >>> [沙琪玛]身高 >>> [1.890000]姓名 >>> [奥利给]身高 >>> [1.890000]我想说:“大家好,我是人!”姓名 >>> [冰淇淋]身高 >>> [1.890000]性别 >>> [女]
此时子类的方法覆盖了父类 调用的不再是父类的方法而是子类的方法
——————————
super 关键字
如果在子类中想调用父类的被重写的方法 可以通过 super 关键字点方法来调用
super 代表对当前对象的直接父类对象的默认引用
在子类中可以通过 super 关键字来访问父类的成员
包括父类的属性、方法和构造方法
注意访问权限的限制
例如无法通过 super 访问 private 成员
假如以下调用的方法都没有被 private 限制:
super.name; // 访问直接父类的 name 属性super.print (); // 访问直接父类的 print 方法super.(name); // 访问直接父类的对应构造方法 只能出现在构造方法中
super 必须出现在子类的方法和构造方法中而不是其她位置!!
——————————
重载和重写的区别
重载 overloading 涉及同一个类中的同名方法 要求方法名相同
并不要求参数列表的异同 与返回值类型、访问修饰符无关
而重写 overriding 涉及的是子类和父类之间的同名方法 要求如下相同:
方法名
参数列表
返回值类型或是其子类
访问修饰符不能严于父类
——————————
继承关系中的构造方法
继承关系中的构造方法调用规则如下:
如果子类的构造方法中没有通过 super 显式地调用父类的有参构造方法也没有通过 this 显式调用自身的其她构造方法则系统会默认先调用父类的无参构造方法在该情况下 有没有 super 都是一样的如果子类的构造方法中通过 super 显式地调用父类的有参构造方法则将执行父类相应的构造方法 而不执行子类无参构造方法如果子类的构造方法中通过 this 显式地调用自身的其她构造方法则在相应构造方法中应用以上两条规则特别注意 如果存在多级继承关系 则在创建一个子类对象时 以上规则会多次向更高一级父类应用一直到执行顶级父类 Object 类的无参构造方法为止在构造方法中如果有 this 语句或者 super 语句出现则只能是第一条语句在一个构造方法中不允许同时出现使用 this 和 suoer 语句调用构造方法否则就有两条第一条语句……在类方法中不允许出现 this 或 super 关键字在实例方法中 this 和 super 语句不要求是第一条语句 可以共存
如果我们将子类中的构造方法中的第一条 super 语句去掉会如何?
Ren 类:
package demo;public class Ren extends RRen {private String str = "我没什么话要说..";public Ren ( String name, String str ) {this.str = str;}public String getStr () {return this.str;}public void print() {/* 重写父类的 print() 方法 */super.print ();// 调用父类的 print 输出信息方法/* 下面加入自己的成员属性信息 */System.out.printf ( "我想说:“%s”\n", this.getStr() );}}
Hai 类:
package demo;public class Hai extends RRen {private char sex = '女';public Hai ( String name, char sex ) {this.sex = sex;}public char getSex () {return this.sex;}public void print() {/* 重写父类的 print() 方法 */super.print ();// 调用父类的 print 输出信息方法/* 下面加入自己的成员属性信息 */System.out.printf ( "性别 >>> [%c]\n", this.getSex() );}}
很多程序员可能认为无非就是不调用父类对应的构造方法而已只是仅仅給相应属性赋值
但是 demo 结果出乎意料地调用了父类的无参构造方法:
姓名 >>> [沙琪玛]身高 >>> [1.890000]执行人类的无参构造方法..姓名 >>> [无名氏]身高 >>> [2.500000]我想说:“大家好,我是人!”执行人类的无参构造方法..姓名 >>> [无名氏]身高 >>> [2.500000]性别 >>> [女]
而我们并没有在子类中使用 super 调用父类的无参构造
完全匹配刚刚提到的第一条规则
——————————
多级继承的构造
下面测试一个存在多级继承关系的代码 深入了解继承条件下构造方法的调用规则
即继承条件下创建子类对象的执行过程
Main.java demo && code:
/*IDE demo:执行 Persion 无参构造..执行 Student()..调用 Teacher()..执行 Persion 带 [崽子] 参数构造..执行 Persion(崽子, 西安大学)..执行 Persion(崽子, 西安大学, 军老师)..*/package demo;class Person {/* 人类 */String name;// 姓名public Person() {/* 人的无参构造 *//* super();// 有没有 super 语句效果都一样 */System.out.println ( "执行 Persion 无参构造.." );}public Person(String name) {/* 人的一参构造 */this.name = name;System.out.printf ( "执行 Persion 带 [%s] 参数构造..\n", name );}}class Student extends Person {/* 学生类 继承自人类 */String schoolName;// 学校名public Student() {/* 学校的无参构造 *//* super();// 有没有 super 语句效果都一样 */System.out.println ( "执行 Student().." );}public Student(String name, String schoolName) {/* 学校的带参构造 */super(name);// 显式地调用了父类有参构造方法 将不执行无参构造this.schoolName = schoolName;System.out.printf ( "执行 Persion(%s, %s)..\n", name, schoolName );}}class Teacher extends Student {/* 教师类 继承自学生类 */String teacherName;// 教师名public Teacher() {/* 教师的无参构造 *//* super();// 有没有 super 语句效果都一样 */System.out.println ( "调用 Teacher().." );}public Teacher(String name, String schoolName, String teacherName) {/* 教师的带参构造 */super(name, schoolName);// 显式地调用了父类有参构造方法 将不执行无参构造this.teacherName = teacherName;System.out.printf ( "执行 Persion(%s, %s, %s)..\n",name, schoolName, teacherName );}}public class Main {/* 主要类 */public static void main(String[] args) {Teacher tear = null;tear = new Teacher();System.out.println ();tear = new Teacher ( "崽子", "西安大学", "军老师" );}}
执行
tear = new Teacher();
时调用无参构造方法Teacher()
在Teacher() 无参构造方法中首先调用其父类的无参构造方法Student()
而在调用Student() 无参构造时又会调用学生类的父类Person 类的无参构造方法Person()
而在执行Person() 无参构造方法时会调用她的直接父类 Object 的无参构造方法 该构造方法内容为空
执行
tear = new Teacher ( "崽子", "西安大学", "军老师" );
时调用构造方法Teacher ( "崽子", "西安大学", "军老师" )
在Teacher ( "崽子", "西安大学", "军老师" ) 构造方法中首先调用其父类的构造方法Student ("崽子", "西安大学" )
而在调用Student ( "崽子", "西安大学" ) 时又会调用学生类的父类Person 类的构造方法Person ( "崽子" )
而在执行Person( "崽子" ) 构造方法时会调用她的直接父类 Object 的无参构造方法 Object() 该构造方法内容为空
##################################################
抽象类
——————————
抽象类和抽象方法的特点
抽象类和抽象方法都通过 abstract 关键字来修饰…
抽象类不能实例化
抽象类中可以没有或有一个或多个抽象方法
甚至全部方法都可以是抽象方法!
抽象方法只有方法声明而没有方法实现
有抽象方法的类必须声明为抽象类
子类必须重写所有的抽象方法才能实例化否则子类还是一个抽象类!
这是一个有实现但实现为空的普通方法:public void 方法名() { }这才是一个抽象方法 不要忘记最后的分号:public abstract void 方法名();
abstract 可以用来修饰类和方法但是不能用来修饰属性和构造方法
抽象类中可以有构造方法 但是其构造方法可以被本来的其她构造方法调用!
若此构造方法不是由 private 修饰 也可以由被本类的子类中的构造方法调用……
——————————
示例抽象类和抽象方法
%%%%%
有些时候 创建的一些对象是没有意义的 只是我们人类抽象出来的一个概念 此时不可以实例化对象
例如上面的人人类 是人类的父类 人人类只是我们抽象出来的一个概念 现实世界中并没有人人类的人类
所以我们需要把人人类限制为不能实例化!
可以使用 Java 中的抽象类来实现 用 abstract 来修饰抽象类 被修饰的类不能通过 new 实例化!
示例代码:
package demo;abstract class RRen {/* 添加 abstract 修饰符变成抽象类 */private String name = "无名氏";private Double gao = 1.89;public RRen () {this.gao = 2.50;System.out.println ( "执行人类的无参构造方法.." );}public RRen(String name) {this.name = name;}public String getName() {return this.name;}public double getGao() {return this.gao;}public void print () {System.out.println ();System.out.printf ( "姓名 >>> [%s]\n", this.getName() );System.out.printf ( "身高 >>> [%f]\n", this.getGao() );}}class Ren extends RRen {private String str = "我没什么话要说..";public Ren ( String name, String str ) {super(name);this.str = str;}public String getStr () {return this.str;}}class Hai extends RRen {private char sex = '女';public Hai ( String name, char sex ) {super(name);this.sex = sex;}public char getSex () {return this.sex;}}public class Main {/* 主要类 */public static void main(String[] args) {RRen rr = new RRen();/* 该处报错 不能实例化抽象类 */rr.print ();Ren r = null;r = new Ren ( "奥利给", "大家好,我是人!" );r.print ();Hai h = new Hai( "冰淇淋", '女' );h.print ();}}
此时如果实例化人人类会报错:
%%%%%
但是人人类提供了 print() 这个方法
如果子类重写该方法 则会正确输出子类信息
若子类中没有重写该方法 则子类将继承人人的该方法从而无法正确输出子类信息
能否强迫子类必须重写该方法 否则就提示出错呢?
此时可以使用 Java 中的抽象方法来实现 用 abstract 来修饰方法 则子类必须重写被修饰的方法!
示例代码:
package demo;abstract class RRen {/* 添加 abstract 修饰符变成抽象类 */private String name = "无名氏";private Double gao = 1.89;public RRen () {this.gao = 2.50;System.out.println ( "执行人类的无参构造方法.." );}public RRen(String name) {this.name = name;}public String getName() {return this.name;}public double getGao() {return this.gao;}public abstract void print();/* 将 print 改为抽象方法 */}class Ren extends RRen {private String str = "我没什么话要说..";public Ren ( String name, String str ) {super(name);this.str = str;}public String getStr () {return this.str;}}class Hai extends RRen {private char sex = '女';public Hai ( String name, char sex ) {super(name);this.sex = sex;}public char getSex () {return this.sex;}}public class Main {/* 主要类 */public static void main(String[] args) {RRen rr = new RRen();/* 该处报错 不能实例化抽象类 */rr.print ();Ren r = null;r = new Ren ( "奥利给", "大家好,我是人!" );r.print ();Hai h = new Hai( "冰淇淋", '女' );h.print ();}}
此时两个子类报错 里面并没有重写抽象方法!
子类必须实现继承来自父类的抽象方法!
现在我们在子类中重写 print() 抽象方法
示例代码:
package demo;abstract class RRen {/* 添加 abstract 修饰符变成抽象类 */private String name = "无名氏";private Double gao = 1.89;public RRen () {this.gao = 2.50;System.out.println ( "执行人类的无参构造方法.." );}public RRen(String name) {this.name = name;}public String getName() {return this.name;}public double getGao() {return this.gao;}public abstract void print();/* 将 print 改为抽象方法 */}class Ren extends RRen {private String str = "我没什么话要说..";public Ren ( String name, String str ) {super(name);this.str = str;}public String getStr () {return this.str;}public void print () {/* 子类重写父类抽象方法 */System.out.println ();System.out.printf ( "姓名 >>> [%s]\n", this.getName() );System.out.printf ( "身高 >>> [%.2f]\n", this.getGao() );System.out.printf ( "我想说 >>> [%s]\n", this.getStr() );}}class Hai extends RRen {private char sex = '女';public Hai ( String name, char sex ) {super(name);this.sex = sex;}public char getSex () {return this.sex;}public void print () {/* 子类重写父类抽象方法 */System.out.println ();System.out.printf ( "姓名 >>> [%s]\n", getName() );System.out.printf ( "身高 >>> [%.2f]\n", getGao() );System.out.printf ( "性别 >>> [%c]\n", getSex() );}}public class Main {/* 主要类 */public static void main(String[] args) {/* 不再实例化抽象类 */Ren r = null;r = new Ren ( "奥利给", "大家好,我是人!" );r.print ();Hai h = new Hai( "冰淇淋", '女' );h.print ();}}
运行结果如下:
姓名 >>> [奥利给]身高 >>> [1.89]我想说 >>> [大家好,我是人!]姓名 >>> [冰淇淋]身高 >>> [1.89]性别 >>> [女]
##################################################
final 修饰符
——————————
final 的应用
如果想要某个类不能够被其她类继承 不允许再有子类
可以給该类添加 final 修饰符来实现
或者某个类可以有子类 但是她的方法不能够再被子类重写
可以給该方法添加 final 修饰符来实现
再或者该类可以有子类 但是增加一个成员属性 规定只能取一个常量值
可以通过給该属性添加 final 修饰符来实现
final 和 abstract 是功能相反的两个关键字 可以对比记忆
abstract 可以用来修饰类和方法 不能用来修饰属性和构造方法
final 可以用来修饰类、方法和属性 不能够修饰构造方法
Java 提供的很多类都是 final 类例如 String 类、Math 类她们不能再有子类Object 类中一些方法 例如getClass ()notify ()wait ()都是 final 方法只能被子类继承而不能被重写但是hashCode ()toString ()equals ()不是 final 方法可以被重写!
%%%%%
final 修饰类
被 final 修饰的类不能够再被继承
例如:
final class 类名_1 {}class 类名_2 extends 类名_1 {/* 这样会报错!不能够继承被 final 修饰的类 */}
%%%%%
final 修饰方法
被 final 修饰的方法不能被子类重写
例如:
class 类名_1 {public final void 方法名_1 () {}}class 类名_2 extends 类名_1 {public void 方法名_1 () {/* 报错 被 final 修饰的方法不能被子类重新 */}}
%%%%%
final 修饰变量
用 final 修饰的变量 包括成员变量和局部变量
将变成常量!
只能被赋值一次!
示例:
public class 类名_1 {final String name = "是崽崽!"; // 被 final 修饰的变量称为常变量public void 方法名_1 ( String name ) {this.name = name; // 会报错!被 final 修饰的常变量不可以再被赋值!}}
——————————
final 修饰引用型变量
final 修饰引用型变量 那么变量所指向的对象属性值是否能够被改变呢?
使用 final 修饰引用型变量时 变量的值是固定不变的
而变量所指向的对象的属性值是可以改变的!
如下:
package demo;class Ren {String name;public Ren ( String name ) {this.name = name;}}public class Main {public static void main(String[] args) {final Ren ren = new Ren ( "崽子" );ren.name = "仔哦";/* 正确 */ren = new Ren ( "打死你" );/* 错误 */}}
上面 ren 已经被定义为 final 修饰的常量 其值不可改变
所以ren = new Ren ( "打死你" ) 是错误的!
因为使用 final 修饰的引用型变量不可以再指向另外的对象
但是所指对象的内容是可以改变的!
所以ren.name = "仔哦" 是正确的
对于引用型变量 一定要区分对象的引用值和对象的属性值是两个概念
——————————
abstract 是否可以与 private、static 或 final 共用
abstract 只可以与 public 同时修饰
abstract 不能和 private 同时修饰一个方法
abstract 不能和 static 同时修饰一个方法
abstract 不能和 final 同时修饰一个方法或类
%%%%%
abstract 不可以与 private 修饰符共用
如下:
private abstract void 方法名();
抽象方法是用来让子类重写的 而子类无法继承到 private 方法 自然无法重写…
%%%%%
abstract 不可以与 static 修饰符共用
如下:
staic abstract void 方法名();
抽象方法只有声明没有实现 而 static 方法可以通过类名直接访问 但无法访问一个没有实现的方法
%%%%%
abstract 也不可以与 final 修饰符共用
示例:
final abstract void 方法名();
抽象方法是让子类来重写的 而 final 修饰的方法不能被重写!
这是相互矛盾的。。
同理 抽象类只有让子类继承才能实例化 而 final 修饰的类不允许被子类继承
%%%%%
abstract 可以跟 public 修饰符搭配使用!
例如:
public abstract void 方法名();
这两个关键字并不冲突!