目录
学习笔记,案例分析3、4重要
继承
面向对象的第二大特征是继承性,主要特点在于:可以扩充已有类的功能。
继承的问题的引出
所谓良好的代码是指结构性合理、适合用于维护、可重用性高。如果用之前学习的概念定义,那么不可避免的面对重复问题。下面定义两个类:人类、学生类。那么按照传统定义,则结构如下:
class person{
private String name ;
private int age ;
// 构造方法 略
public void setName(String name){
this.name = name ;
}
public void setAge(int age){
this.age = age ;
}
public String getName(){
return this.name ;
}
public int getAge(){
return this.age ;
}
}
class Student{
private String name ;
private int age ;
private String school ;
// 构造方法 略
public void setName(String name){
this.name = name ;
}
public void setAge(int age){
this.age = age ;
}
public void setSchool(String school){
this.school = school ;
}
public String getName(){
return this.name ;
}
public int getAge(){
return this.age ;
}
public String getSchool(){
return this.school ;
}
}
虽然类的概念可以解决结构性的问题,但是对于之前开发的程序代码,总能够发现有一些重复的代码在里面。进一步思考,学生是人,人是一个更加广泛的定义范畴,而学生是一个相对狭小的定义范畴。从另一角度来讲,学生之中包含人的特点。
如果要进行代码的重用,需要使用继承的概念来解决,所谓的继承本质,在已有类的功能上进行功能的扩充。
继承的实现
如果在Java程序之中要实现继承关系,就需要使用extends关键字来完成,具体语法如下:
- class 子类 extends 父类{}
需要注意的是,很多情况下将子类称为派生类,将父类称为超类(SuperClass)。
范例:观察继承的实现
class Person{
private String name ;
private int age ;
// 构造方法 略
public void setName(String name){
this.name = name ;
}
public void setAge(int age){
this.age = age ;
}
public String getName(){
return this.name ;
}
public int getAge(){
return this.age ;
}
}
class Student extends Person{ // Student是子类
//在子类中不定义任何的功能
}
public class StringDemo{
public static void main(String[] args){
Student stu = new Student() ;
stu.setName("林大强") ; //父类定义
stu.setAge(38) ;
System.out.println("姓名:" + stu.getName() + " 年龄:" + stu.getAge()) ;
}
由于此时存在继承关系,所以此时即便没有任何的操作方法,也可以通过父类继承的方法实现,这个时候的内存关系如下:
继承的本质在于子类可以重用父类的结构,实现功能的扩充。同时子类可以定义更多的内容,描述的范围更小。
范例:子类扩充定义
class Person{
private String name ;
private int age ;
// 构造方法 略
public void setName(String name){
this.name = name ;
}
public void setAge(int age){
this.age = age ;
}
public String getName(){
return this.name ;
}
public int getAge(){
return this.age ;
}
}
class Student extends Person{ // Student是子类
private String school ;
public void setSchool(String school){
this.school = school ;
}
public String getSchool(){
return this.school ;
}
}
public class StringDemo{
public static void main(String[] args){
Student stu = new Student() ;
stu.setName("林大强") ; //父类定义
stu.setAge(38) ;
stu.setSchool("家里蹲大学") ;
System.out.println("学校:" + stu.getSchool() + "、姓名:" + stu.getName() + " 年龄:" + stu.getAge()) ;
}
}
此时如果讨论继承关系,则会出现两个范围的属性(Person父类范畴、Student子类范畴)
子类对象实例化流程
现在已经成功的实现了继承的关系,并且已经发现继承的主要特点,但是程序之中如果出现继承的定义,程序对于子类对象实例化是有要求的。对于继承程序逻辑来讲,首先实例化父类对象,再进行子类对象实例化,列子如下:
范例:观察一个程序
class Person{
public Person(){
System.out.println("【person父类】一个新的person父类实例化对象产生") ;
}
}
class Student extends Person{ // Student是子类
public Student(){ // 构造方法执行
System.out.println("【student子类】一个新的student实例化对象产生") ;
}
}
public class StringDemo{
public static void main(String[] args){
Student stu = new Student() ;
}
}
即使没有进行父类对象的实例化,也会由系统自动调用父类的构造方法(实例化父类对象),默认子类对象的实例化流程会自动实现其父类对象的实例化。实际上,就相当于子类对象的构造方法里面隐含了一个”super()“的形式
范例:修改子类定义
class Student extends Person{ // Student是子类
public Student(){ // 构造方法执行
super() ; // 写与不写效果一样
System.out.println("【student子类】一个新的student实例化对象产生") ;
}
}
super()表示子类构造调用父类构造的语句,该语句只允许放在子类构造方法的首行。在默认情况下的子类处理,子类只会调用父类中的无参构造,所以写与不写的区别不大,但是如果你的父类中没有提供无参构造,这时就必须使用super()明确调用有参构造。
class Person{
private String name ;
private int age ;
public Person(String name, int age){
this.name = name ;
this.age = age ;
}
}
class Student extends Person{ // Student是子类
private String school ;
public Student(String name, int age, String school){ // 构造方法执行
super(name, age) ; // 写与不写效果一样
this.school = school ;
}
}
public class StringDemo{
public static void main(String[] args){
Student stu = new Student("张三", 18, "家里蹲大学") ;
}
}
结论:在实例化子类对象的同时,一定会先实例化父类对象,目的是为了所有的属性可以进行空间的分配。
super与this都可以调用构造方法,super是由子类调用父类的构造,this是调用本类构造,并且一定要放在构造方法的第一行。两个语句不允许同时出现。
继承相关限制
现在已经清楚继承的逻辑,那么下面对于继承还有一些要求,
1. Java中不要求多重继承,只要求多层继承。
- 在实际生活中一个人只有一个亲老子,在程序之中也是这样。
范例:错误的继承
class A{
}
class B{
}
class C extends A, B{ //多重继承
}
继承的主要目的是扩展已有类的主要功能,但是多重继承的目的希望可以继承多个类中的方法,而面对于多继承的要求应该将范围限定在同一类之中。如果使用了多层继承,子类也同样具有多个父类的操作,方法如下:
范例:正确的方法
class A{
}
class B extends A{
}
class C extends B{ //多层继承
}
但是多层继承也应该有一个限制,对于写的代码,最好不要超过三层。
2. 在进行继承关系定义的时候,实际上子类可以继承父类的所有操作结构。但是对于私有操作属于隐式继承,而所有非私有操作属于显示继承。
class Person{
private String name ;
public void setName(String name ){
this.name = name ;
}
public String getName(){
return this.name ;
}
}
class Student extends Person{
public Student(String name){
setName(name) ; //设置name的属性内容
}
public void fun(){
// System.out.println(name) ; // 直接访问不可能,name私有的
System.out.println(getName()) ; // 间接访问
}
}
public class StringDemo{
public static void main(String[] args){
Student stu = new Student("张三") ;
stu.fun() ;
}
}
继承一旦发生了,所有的操作都可以被子类使用。在程序设计里面,并没有考虑到所谓的“败家子”的概念,子类至少会维持父类的现有功能。
覆写
子类与父类一旦产生继承关系之后,实际上子类会继承父类的所有操作,但是这里面会出现不合理的场景。子类如果发现父类中设计不足并且需要保留方法或属性名称的情况下就会发生覆写。
方法的覆写
当子类定义了与父类方法名称相同,参数类型与个数相同的时候,称为方法的覆写(跟父类方法一样)。
范例:观察覆写
class Channel{
public void connect(){
System.out.println("【Channel父类】进行资源连接") ;
}
}
class DatabaseChannel extends Channel{ // 要进行数据库的连接
public void connect(){ // 保留已有方法名称,而后进行覆写
System.out.println("【子类】进行数据库资源的连接") ;
}
}
public class StringDemo{
public static void main(String[] args){
DatabaseChannel channel = new DatabaseChannel() ;
channel.connect() ;
}
}
由于现在实例化的子类对象,所以调用的方法一定是子类覆写过的方法,如果该方法没有被覆写过,那么将调用父类中的方法,覆写的意义在于优化我们的功能。
覆写的意义:
在子类进行方法覆写之后,要想调用父类中的方法,就必须使用“super.方法”。
class Channel{
public void connect(){
System.out.println("【Channel父类】进行资源连接") ;
}
}
class DatabaseChannel extends Channel{ // 要进行数据库的连接
public void connect(){ // 保留已有方法名称,而后进行覆写
super.connect() ; // 从父类中方法
System.out.println("【子类】进行数据库资源的连接") ;
}
}
public class StringDemo{
public static void main(String[] args){
DatabaseChannel channel = new DatabaseChannel() ;
channel.connect() ;
}
}
只要进行子类调用父类方法的时候,一定要在方法前追加由super。
方法覆写的限制
虽然利用方法的父类,可以更好的扩充父类的功能,但是覆写也是有自身的要求的:被覆写的方法不能有比父类方法更为严格的访问权限(权限缩小)。
对于访问控制权限现在已经接触了三种(属性或方法):public > default(什么都不写) > private ,private是权限最小的,如果在父类中使用了default定义,那么在子类中定义该方法时,只能使用public 或 default定义,如果父类中的方法使用public定义,那么子类中的方法只能使用public。
范例:观察错误的覆写
class Channel{
public void connect(){
System.out.println("【Channel父类】进行资源连接") ;
}
}
class DatabaseChannel extends Channel{ // 要进行数据库的连接
void connect(){ // 保留已有方法名称,而后进行覆写
System.out.println("【子类】进行数据库资源的连接") ;
}
}
public class StringDemo{
public static void main(String[] args){
DatabaseChannel channel = new DatabaseChannel() ;
channel.connect() ;
}
}
此时父类中的方法使用public定义,而子类中的方法使用default,权限缩小了是错误的。
说到权限问题就必须考虑到private权限,private除了定义到方法上也可以定义到属性上。
范例: 方法名称与参数相同,不是覆写
class Channel{
private void connect(){
System.out.println("【Channel父类】进行资源连接") ;
}
public void fun(){
this.connect() ; //调用本类方法
}
}
class DatabaseChannel extends Channel{ // 要进行数据库的连接
// 此时并不是一个覆写,因为父类的connect方法不可见。这个时候connect相当于一个新定义的方法于覆写没有任何的关系
public void connect(){
System.out.println("【子类】进行数据库资源的连接") ;
}
}
在以后开发之中只要是定义方法 95%的情况下,都是使用public所以在覆写的时候,最好使用public。
面试题:请解释Override(覆写)与Overloading(重载)的区别?
重载:方法名称相同,参数的类型与个数不同。没有权限限制。范围:发生在一个类中。
覆写:方法名称相同,参数的类型与个数及返回值相同。覆写的方法的权限不能比原方法权限更小。范围:发生在继承关系之中。
在进行方法重载时,并没有对返回类型做出限制,但是好的习惯应该与原方法返回类型保持一致。
属性覆盖
当子类定义了与父类相同名称的时候,称之为属性覆盖。
范例:观察属性覆盖
class Channel{
String info = "【父类】info" ;
}
class DatabaseChannel extends Channel{
String info = "【子类】info" ;
public void fun(){
System.out.println(super.info) ;
System.out.println(this.info) ;
}
}
public class StringDemo{
public static void main(String[] args){
DatabaseChannel channel = new DatabaseChannel() ;
channel.fun() ;
}
}
如果按照标准的开发,属性进行封装了,这个时候子类就和父类中的私有属性就没有关系了,即便名称一样,也算子类定义了一个新的属性而已。但是可以使用getter方法访问,如下:
class Channel{
String info = "【父类】info" ;
public String getInfo(){
return this.info ;
}
}
class DatabaseChannel extends Channel{
String info = "【子类】info" ;
public void fun(){
System.out.println(super.getInfo()) ;
System.out.println(this.info) ;
}
}
public class StringDemo{
public static void main(String[] args){
DatabaseChannel channel = new DatabaseChannel() ;
channel.fun() ;
}
}
属性覆盖没太大意义
面试题:请解释super 与 this 的区别 ?
-
this:在程序类中使用this,先从本类中查找所需要的属性或方法,如果本类不存在则查找父类定义。super : 则不查找子类,直接查找父类。
-
this和super都可以进行构造方法的调用,但是this只能调用本类构造,但是super是由子类调用父类构造,这两个语句都必须放在方法的首行,所以不能同时出现。
-
this 可以表示当前对象, super 没有这种概念
final关键字
final在程序之中秒速和的是一种终接器的概念,在Java中使用关键字final可以实现如下功能:定义一个不能被继承的类、不能都覆写的方法、常量。
范例:使用final定义一个不能被继承的类
class final Channel{ // 这个类不能被继承
}
class DatabaseChannel extends Channel{ // 执行出错
}
当子类继承了父类之后,实际上可以进行父类中的方法覆写,但是如果比不希望你的某个方法被子类所覆写,就可以使用final定义。
范例:定义子类不能够覆写的方法
class Channel{
public final void coonect(){}
}
class DatabaseChannel extends Channel{ // 执行出错
public void coonect(){}
}
一般涉及到底层类库的时候可能用上。
在有一些系统开发之中,可能使用1表示开关打开、使用0表示开关关闭,如果使用0、1就会造成混乱,所以就希望通过将一些名称表示0或者1。在final关键字中有一个重要的应用技术,可以用其定义常量,常量的内容一旦定义则不可修改。
范例:使用final定义常量
class Channel{
private final int ON = 1 ; // ON就是常量
private final int OFF = 0 ; // PFF就是常量
public final void coonect(){}
}
实际上常量往往是公共的,所以为了体现共享的概念,往往使用全局常量的形式定义,即使用public static final 公共 共享 常量来定义全局常量。
class Channel{
public static final int ON = 1 ; // ON就是常量
public static final int OFF = 0 ; // PFF就是常量
public final void coonect(){}
}
在定义全局常量的时候每一个字母必须大写。
范例:观察一个程序代码验证常量
public class StringDemo{
public static void main(String[] args){
final String info = "oW" ;
String strA = "HelloWorld" ;
String strB = "Hell" + info + "orld" ;
System.out.println(strA == strB ) ; //没加final为false,加上true
}
}
在方法的时候也可以使用final定义参数,此时也表示一个常量的概念。
案例继承与覆写分析
下面通过几个简短的程序,来巩固继承的概念,对于程序开发而言,简单Java类是其最基础的组成,也是所有概念最好的融合。
案例分析一
正常来讲在进行一个程序开发并不需要考虑子类的问题,也就是说现在做的只是进行程序功能完善定义而已。
class Person{
private String name ;
private String addr ;
private char sex ;
private int age ;
public Person(){
this("无名氏", "未知", '空', '0') ;
}
public Person(String name, String addr){
this(name, addr, '空', '0') ;
}
public Person(String name, String addr, char sex, int age){
this.name = name ;
this.addr = addr ;
this.sex = sex ;
this.age = age ;
}
// setter getter 略
public String getInfo(){
return "【人的信息】姓名:" + this.name +
"、年龄:" + this.age +
"、性别:" + this.sex +
"、住址:" + this.addr ;
}
}
class Student extends Person{
private double math ;
private double english ;
public Student(){
this("无名氏", "未知", '空', '0', 0.0, 0.0) ;
}
public Student(String name, String addr){ //两参主要传基础信息
super(name, addr) ;
}
public Student(String name, String addr, char sex, int age, double math, double english){
super(name, addr, sex, age) ;
this.math = math ;
this.english = english ;
}
// setter getter 略
public String getInfo(){
return super.getInfo() +
"、数学成绩:" + this.math +
"、英语成绩:" + this.english ;
}
}
public class StringDemo{
public static void main(String[] args){
Student stu = new Student("张三", "天安门", '男', 15, 89 , 98) ;
System.out.println(stu.getInfo()) ;
}
}
在这样的操作案例里面,发现子类对象实例化、构造方法调用、方法覆写
案例分析二
定义员工类,具有姓名、年龄、性别属性,并具有构造方法和显示数据方法。定义管理层类,继承员工类,并且拥有自己的属性:职务、年薪。定义职员类继承员工类,并拥有自己的属性:所属部门、月薪。
class Employee{ // 员工类
private String name ;
private int age ;
private char sex ;
public Employee(){}
public Employee(String name, int age, char sex){
this.name =name ;
this.age = age ;
this.sex = sex ;
}
// setter getter 略
public String getInfo(){
return "【员工信息】姓名:" + this.name +
"、年龄:" + this.age +
"、性别:" + this.sex ;
}
}
class Manager extends Employee{
private String job ;
private double income ;
public Manager(){}
public Manager(String name, int age, char sex, String job, double income){
super(name, age, sex) ;
this.job = job ;
this.income = income ;
}
//setter getter略
public String getInfo(){
return "【管理层】" + super.getInfo() + "、职位:" + this.job +
"、年薪:" + this.income ;
}
}
class Staff extends Employee{
private String dept ;
private double salary ;
public Staff(){}
public Staff(String name, int age, char sex, String dept, double salary){
super(name, age, sex ) ;
this.dept = dept ;
this.salary = salary ;
}
// setter getter 略
public String getInfo(){
return "【职员】" + super.getInfo() +
"、部门:" + this.dept +
"、月薪:" + this.salary ;
}
}
public class StringDemo{
public static void main(String[] args){
System.out.println(new Employee("张三", 18, '男').getInfo()) ;
System.out.println(new Manager("李四", 38, '女', "主管", 1200000.00).getInfo()) ;
System.out.println(new Staff("王五", 28, '男', "财务部", 2000.00).getInfo()) ;
}
}
案例分析三
编写程序,统计字符串“want you to know one thing ”中"n"和“o”出现的次数。
分析:最简单的操作是在主方法中直接定义操作,或定义一个新的类。
class StringUtil{
// 返回的第一个内容“n”的个数,第二内容“o”的个数
public static String count(String str){
int countData [] = new int [2] ;
char [] data = str.toCharArray() ;
for ( char node : data){
if (node == 'n' || node == 'N'){
countData[0] ++ ;
}
if (node == 'o' || node == 'O'){
countData[1] ++ ;
}
}
return "n的个数:" + countData[0] + "、o的个数:" + countData[1] ;
}
}
public class StringDemo{
public static void main(String[] args){
System.out.println(new StringUtil().count("want you to know ong thing ONO")) ;
}
}
以上的解决方案严格来讲只是顺序式的思维模式,假设说以后还有可能其它统计的设计。如下:
class StringUtil{
private String content ;
public StringUtil(String content){
this.content =content ;
}
public String getCount(){
return this.content ;
}
public String getInfo(){
return this.content ;
}
}
class StringCount extends StringUtil{
private int nCount ;
private int oCount ;
public StringCount(String content){
super(content) ;
this.countChar() ; // 构造方法统计
}
public void countChar(){
int countData [] = new int [2] ;
char [] data = super.getCount().toCharArray() ;
for ( char node : data){
if (node == 'n' || node == 'N'){
this.nCount ++ ;
}
if (node == 'o' || node == 'O'){
this.oCount ++ ;
}
}
}
public int getNcount(){
return this.nCount ;
}
public int getOcount(){
return this.oCount ;
}
public String getInfo(){
return "字母n的个数:" + this.nCount +
"、字母o的个数:" + this.oCount ;
}
}
public class StringDemo{
public static void main(String[] args){
StringCount sc = new StringCount("want you to know ong thing ONO") ;
System.out.println(sc.getInfo()) ;
}
}
记住任何方案都可以,第一种方案比较直观,第二种方更加适合结构化设计 。
案例分析四
对于本程序而言,首先考虑父类如何定义完善
第一步:进行数组操作类定义
class Array{ //数组操作类
private int [] data ;
private int foot ; //进行数组索引控制
public Array(int len){ // 构造方法
if (len > 0){
this.data = new int [len] ; // 开辟数组空间
}else{
this.data = new int [1] ; //开辟一个空间
}
}
// 实现数组容量扩充,给出的似乎扩充大小,实际大小:已有大小 + 扩充大小
public void increment(int num){
int newData [] = new int [this.data.length + num] ;
System.arraycopy(this.data, 0, newData, 0, this.data.length) ;
this.data = newData ;
}
public boolean add(int num){ //数据增加
if (this.foot < this.data.length ){ // 有位置
this.data[this.foot ++] = num ;
return true ;
}
return false ;
}
public int [] getData(){
return this.data ;
}
}
public class StringDemo{
public static void main(String[] args){
Array arr = new Array(5) ;
System.out.println(arr.add(15)) ;
System.out.println(arr.add(20)) ;
System.out.println(arr.add(3)) ;
System.out.println(arr.add(6)) ;
System.out.println(arr.add(1)) ;
arr.increment(3) ;
System.out.println(arr.add(5)) ;
System.out.println(arr.add(5)) ;
System.out.println(arr.add(5)) ;
}
}
第二步:定义排序子类
class SortArray extends Array{ // 定义排序子类
public SortArray(int len){
super(len) ;
}
public int[] getData(){ // 获得排序结果
java.util.Arrays.sort(super.getData()) ; // 排序
return super.getData() ;
}
}
第三步:定义反转子类(全部代码)
class Array{ //数组操作类
private int [] data ;
private int foot ; //进行数组索引控制
public Array(int len){ // 构造方法
if (len > 0){
this.data = new int [len] ; // 开辟数组空间
}else{
this.data = new int [1] ; //开辟一个空间
}
}
// 实现数组容量扩充,给出的似乎扩充大小,实际大小:已有大小 + 扩充大小
public void increment(int num){
int newData [] = new int [this.data.length + num] ;
System.arraycopy(this.data, 0, newData, 0, this.data.length) ;
this.data = newData ;
}
public boolean add(int num){ //数据增加
if (this.foot < this.data.length ){ // 有位置
this.data[this.foot ++] = num ;
return true ;
}
return false ;
}
public int [] getData(){
return this.data ;
}
}
class SortArray extends Array{ // 定义排序子类
public SortArray(int len){
super(len) ;
}
public int[] getData(){ // 获得排序结果
java.util.Arrays.sort(super.getData()) ; // 排序
return super.getData() ;
}
}
class ReverseArray extends Array{ // 定义排序子类
public ReverseArray(int len){
super(len) ;
}
public int[] getData(){ // 获得反转结果
int center = super.getData().length/2 ;
int head = 0 ;
int tail = super.getData().length - 1 ;
for (int i = 0; i < center ; i++){
int temp = super.getData()[head] ;
super.getData()[head] = super.getData()[tail] ;
super.getData()[tail] = temp ;
head ++ ; tail -- ;
}
return super.getData() ;
}
}
public class StringDemo{
public static void main(String[] args){
Array arr = new ReverseArray(5) ;
System.out.println(arr.add(15)) ;
System.out.println(arr.add(20)) ;
System.out.println(arr.add(3)) ;
System.out.println(arr.add(6)) ;
System.out.println(arr.add(1)) ;
arr.increment(3) ;
System.out.println(arr.add(5)) ;
System.out.println(arr.add(5)) ;
System.out.println(arr.add(5)) ;
int result [] = arr.getData() ;
for (int temp : result){
System.out.println(temp) ;
}
}
}
父类中定义的方法名称往往很重要,如果功能相同时子类应该以覆写父类的方法为优先考虑。