JavaSE_面向对象3
来源:互联网 发布:js滚轮放大缩小 拖动 编辑:程序博客网 时间:2024/05/18 02:15
1、课程名称:面向对象(高级)
2、知识点
2.1、上次课程的主要知识点
1、 static关键字
· 使用static关键字声明静态属性和静态方法,静态方法可以调用静态属性和静态方法,静态方法不能调用非静态的属性或方法,反之可以。static属性是所有对象共同拥有的。
· 静态属性或方法可以由类名称直接调用
· main方法组成:public static void main(String args[])
2、 对象数组:
· 对象数组与一般的数组一样,可以分为静态初始化和动态初始化。
· 使用动态初始化时,对象数组中的所有元素的默认值是null。
· 可变参数及foreach输出。
3、 代码块:普通代码块、构造块、静态块
4、 内部类:
· 在一个类的内部再定义另外一个类
· 使用内部类可以方便的访问外部类的私有成员,但是辉破坏程序的结构
· 使用static声明的内部类就成为外部类
· 定义在方法中的内部类如果要想访问方法中的参数,则参数前要加上“final”关键字
2.2、本次预计讲解的知识点
1、 继承的基本概念及实现
2、 方法的覆写、super关键字
3、 抽象类和接口的基本概念
4、 Object类的作用
3、具体内容
面向对象有三大特征:封装、继承、多态。
3.1、继承的基本概念
在讲解继承之前先来观察以下的程序。
范例:要求完成一个学生类和人类
class Person{
private String name ;
private int age ;
public void setName(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
public void setAge(int age){
this.age = age ;
}
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 String getName(){
return this.name ;
}
public void setAge(int age){
this.age = age ;
}
public int getAge(){
return this.age ;
}
public void setSchool(String school){
this.school = school ;
}
public String getSchool(){
return this.school ;
}
};
之前所学习到的全部知识,只能这样设计这两个类。
但是从以上的两个类中,可以发现代码中出现了重复,从实际的生活来看,学生是一个人,学生本身就应该具备人的各个信息,但是以上的操作是两个独立的类,所以代码重复,那么此时就需要使用继承来解决问题。
实现继承的基本语法:
class 子类 extends 父类{}
在以上的操作中,关于子类有时候也会称为派生类,关于父类有时候也会称为超类。
范例:使用继承改善程序
class Person{
private String name ;
private int age ;
public void setName(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
public void setAge(int age){
this.age = age ;
}
public int getAge(){
return this.age ;
}
};
class Student extends Person{
// 此处没有任何的定义
};
public class ExtDemo02{
public static void main(String args[]){
Student stu = new Student() ;
stu.setName("张三") ;
stu.setAge(30) ;
System.out.println(stu.getName() + " --> " + stu.getAge()) ;
}
};
以上的全部方法都是从Person类中继承而来的,本身并没有任何的定义。除了以上的维持操作之外,实际上也可以对类进行扩充。
class Person{
private String name ;
private int age ;
public void setName(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
public void setAge(int age){
this.age = age ;
}
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 ExtDemo03{
public static void main(String args[]){
Student stu = new Student() ;
stu.setName("张三") ;
stu.setAge(30) ;
stu.setSchool("清华大学") ;
System.out.println(stu.getName() + " --> " + stu.getAge() + " --> " + stu.getSchool()) ;
}
};
以上完成了一个基本的继承关系操作。
下一步需要对继承进行进一步的扩充。
3.2、继承的基本限制
继承在使用中存在很多的限制要求,第一个要求:
·一个子类只能继承一个父类,所以在Java中不支持多重继承
class A{
};
class B{
};
class C extends A,B{
};
以上一个C类同时继承了A和B类,所以语法是出错的。
·在Java中允许多层继承,即:一个子类可以有一个父类,一个父类还可以有其他的父类。
在java中所有的继承只是子类继承了父类中的公共操作。、
更具体的来讲,是直接继承了所有的操作,只是非私有的操作是直接继承的,而私有的操作是隐含继承的(隐含继承是指通过间接的方式继承私有属性和操作,例如通过setter和getter方法间接访问私有属性)。
3.3、子类的实例化过程
在子类进行实例化操作的时候,首先会先让其父类进行实例化操作。之后子类再自己进行实例化操作。
class Person{
private String name ;
private int age ;
public Person(){
System.out.println("父类的构造方法!!!") ;
}
public void setName(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
public void setAge(int age){
this.age = age ;
}
public int getAge(){
return this.age ;
}
}
class Student extends Person{ // 此处的Student类扩充了类的已有功能
private String school ;
public Student(){
System.out.println("子类的构造方法!!!") ;
}
public void setSchool(String school){
this.school = school ;
}
public String getSchool(){
return this.school ;
}
}
public class ExtDemo05{
public static void main(String args[]){
Student stu = new Student() ;
}
}
在以上的操作中,发现父类中的构造方法会首先被执行,一旦构造方法执行就意味着要为父类中的属性初始化,父类的对象进行实例化。
实际上,对于以上的操作代码,在子类的构造方法中隐含了以下的语句:
public Student(){
super() ; // 隐含了此语句
System.out.println("子类的构造方法!!!") ;
}
那么以上的“super()”实际上调用的是父类中的无参构造方法,那么既然可以调用无参构造,也应该可以调用有参构造。
class Person{
private String name ;
private int age ;
public Person(String name,int age){
this.setName(name) ;
this.setAge(age) ;
}
public void setName(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
public void setAge(int age){
this.age = age ;
}
public int getAge(){
return this.age ;
}
}
class Student extends Person{ // 此处的Student类扩充了类的已有功能
private String school ;
public Student(String name,int age,String school){
super(name,age) ; //调用父类中有两个参数的构造方法
this.setSchool(school) ;
}
public void setSchool(String school){
this.school = school ;
}
public String getSchool(){
return this.school ;
}
}
public class ExtDemo06{
public static void main(String args[]){
Student stu = new Student("张三",30,"清华大学") ;
System.out.println(stu.getName() + " --> " + stu.getAge() + " --> " + stu.getSchool()) ;
}
}
通过以上的操作可以得出以下的子类对象的实例化操作结论:
· 在进行子类对象的实例化之前会首先默认调用父类中的无参构造方法,为父类中的属性进行初始化操作,之后再调用子类自己的构造方法完成子类的初始化操作。
· 在子类中可以通过super关键字明确的指出要调用父类中的那个构造方法。
· 如果没有写任何的super语句,则在子类的构造方法中默认隐含了一个“super()”。
3.4、方法的覆写(重点)
方法的覆写:在子类中声明了与父类中同名的方法称为覆写,在进行方法的覆写时必须注意以下一个问题,被覆写的方法不能拥有比子类更严格的访问权限。
· 关于访问权限现在已经学习过了三种,三种的大小关系如下:public > default(默认) > private
范例:以下演示了覆写的操作
class A{
public void fun(){
System.out.println("A类中的fun()方法。") ;
}
}
class B extends A{
public void fun(){ // 此时方法名称一致,参数的类型或个数一致,实现了方法的覆写
System.out.println("B类中的fun()方法。") ;
}
}
public class OverrideDemo01{
public static void main(String args[]){
B b = new B() ;
b.fun() ; // 调用的是被覆写过的方法
}
}
以上的操作就完成了一个覆写的功能,在此处必须记住以下一个原则“在使用子类对象操作的时候,调用的方法永远是被子类覆写过的方法。”
如果,现在在方法覆写的时候将权限缩小了,则会出现以下的问题:
OverrideDemo01.java:7: fun() in B cannot override fun() in A; attempting to assign weaker access privileges; was public
void fun(){ // 此时方法名称一致,参数的类型或个数一致,实现了方法的覆写
^
1 error
以上的提示表示,正在使用一个更弱的权限表示,应该使用public。
如果现在希望在被子类被覆写过的方法中调用父类的方法该如何做呢?
此时可以加上super关键字,使用“super.方法()”的形式完成。
class A{
public void fun(){
System.out.println("A类中的fun()方法。") ;
}
}
class B extends A{
public void fun(){ // 此时方法名称一致,参数的类型或个数一致,实现了方法的覆写
super.fun() ; // 调用父类的方法
System.out.println("B类中的fun()方法。") ;
}
}
public class OverrideDemo01{
public static void main(String args[]){
B b = new B() ;
b.fun() ; // 调用的是被覆写过的方法
}
}
思考?
如果在父类中使用private关键字修饰了一个方法,子类中使用default修饰方法,那么此方法是否称为覆写呢?
class A{
private void print(){
System.out.println("A类中的print()方法。") ;
}
public void fun(){
this.print() ;
}
}
class B extends A{
void print(){ // 此时方法名称一致,参数的类型或个数一致,实现了方法的覆写
System.out.println("B类中的print()方法。") ;
}
}
public class OverrideDemo02{
public static void main(String args[]){
B b = new B() ;
b.fun() ; //调用的是父类中的方法
}
}
实际上,以上方法操作不是覆写,而是相当于子类自己重新定义了一个新的方法。
3.5、属性的覆盖(了解)
在继承的关系中,除了方法可以进行覆写之外,属性也是可以进行覆盖操作的。但是一般情况下此种操作都不使用,只是作为概念了解一下即可。
class A{
public String info = "hello" ;
}
class B extends A{
public int info = 30 ; // 属性名称完全一致
public void print(){
System.out.println("info = " + super.info) ;
System.out.println("info = " + this.info) ;
}
}
public class OverrideDemo03{
public static void main(String args[]){
B b = new B() ;
b.print() ;
}
}
3.6、修改之前的继承操作(重点)
之前已经完成了学生类和人类的操作,但是人类中的信息还不是很完善。
class Person{
private String name ;
private int age ;
public Person(String name,int age){
this.setName(name) ;
this.setAge(age) ;
}
public void setName(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
public void setAge(int age){
this.age = age ;
}
public int getAge(){
return this.age ;
}
public String getInfo(){
return "姓名:" + this.getName() + ",年龄:" + this.getAge() ;
}
}
class Student extends Person{ // 此处的Student类扩充了类的已有功能
private String school ;
public Student(String name,int age,String school){
super(name,age) ; // 调用父类中有两个参数的构造方法
this.setSchool(school) ;
}
public void setSchool(String school){
this.school = school ;
}
public String getSchool(){
return this.school ;
}
public String getInfo(){
return super.getInfo() + ",学校:" + this.getSchool();
}
}
public class ExtDemo07{
public static void main(String args[]){
Student stu = new Student("张三",30,"清华大学") ;
System.out.println(stu.getInfo()) ;
}
}
3.7、super关键字(重点)
之前已经了解过了super的一些基本的操作过程,super可以完成以下的操作:
·使用super调用父类中的属性
·使用super调用父类中的方法
·使用super调用父类中的构造方法
但是在调用构造方法的时候曾经说过,子类实例化时永远会调用父类中的构造方法,这个时候有人说了,老师,可以不调用构造方法。
class Person{
private String name ;
private int age ;
public Person(){}
public Person(String name){
this.name = name ;
}
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
}
class Student extends Person{ // 此处的Student类扩充了类的已有功能
private String school ;
public Student(){
super() ;
}
public Student(String name,int age){
this() ;
}
public Student(String name,int age,String school){
this(name,age) ; // 调用父类中有两个参数的构造方法
}
}
public class SuperDemo01{
public static void main(String args[]){}
}
在子类的构造方法中至少有一个构造方法是没有使用this()调用的,那么一般这个构造方法将作为程序的出口,调用父类的构造方法,即:无论任何情况下,子类永远都要去调用父类中的构造方法。
3.8、继承习题(理解)
现在要求定义一个数组类(Array),里面定义了一个整型数组,但是此整型数组属于动态分配大小,即:所有的大小由程序指定,并在此基础上实现以下的两个子类:
•反转类:可以将数组的内容反转排列
•排序类:可以对数组进行排序的操作
问:这样的类该如何实现?
本程序肯定是通过继承实现,每一个都应该是一个具体的操作类。
首先先定义一个Array类,里面包含一个整型数组。
1、 完成父类
class Array{
private int[] temp = null ; // 声明一个整型数组
private int foot ; // 定义增加记录的脚标
public Array(int len){
if(len>0){
this.temp = new int[len] ; // 如果传过来的数据大于0,可以开辟空间
}else{
this.temp = new int[1] ; // 最少维持1个数组大小
}
}
public boolean add(int t){ // 增加元素
if(this.foot<this.temp.length){ // 还有空间,可以增加
this.temp[this.foot] = t ;
this.foot++ ;
return true ;
}else{
return false ;// 不能增加
}
}
public int[] getArray(){
return this.temp ;
}
}
父类中存放的数据是采用顺序存放的方式,即:增加的顺序就是最终保存的顺序。但是要求在以上的类中派生两个子类:排序类、反转类。
2、 完成排序的子类
class SortArray extends Array{ // 排序类
public SortArray(int len){ // 在子类的构造方法中必须明确的调用父类的构造方法
super(len) ;
}
public int[] getArray(){
java.util.Arrays.sort(super.getArray()) ; //排序
return super.getArray() ; //返回排序后的结果
}
}
public class ExecDemo01{
public static void main(String args[]){
SortArray arr = new SortArray(5) ; // 开辟5个大小的整型数组
System.out.println(arr.add(11)) ;
System.out.println(arr.add(2)) ;
System.out.println(arr.add(33)) ;
System.out.println(arr.add(4)) ;
System.out.println(arr.add(0)) ;
System.out.println(arr.add(6)) ;
print(arr.getArray()) ;
}
public static void print(int temp[]){ // 打印数组
for(int x:temp){
System.out.print(x + "、") ;
}
}
}
通过合理的操作,将getArray()方法覆写,但是对于客户端(主方法)来讲,本身依然还是调用固定的方法完成。
3、 完成反转类
class ReverseArray extends Array{ // 排序类
public ReverseArray(int len){ // 在子类的构造方法中必须明确的调用父类的构造方法
super(len) ;
}
public int[] getArray(){
int x[] = new int[super.getArray().length] ; // 以父类中的数组大小进行开辟
int count = x.length - 1 ;
for(int i=0;i<super.getArray().length;i++){
x[count] = super.getArray()[i] ;
count-- ;
}
return x ;
}
}
public class ExecDemo01{
public static void main(String args[]){
ReverseArray arr = new ReverseArray(5) ; // 开辟5个大小的整型数组
System.out.println(arr.add(11)) ;
System.out.println(arr.add(2)) ;
System.out.println(arr.add(33)) ;
System.out.println(arr.add(4)) ;
System.out.println(arr.add(0)) ;
System.out.println(arr.add(6)) ;
print(arr.getArray()) ;
}
public static void print(int temp[]){ // 打印数组
for(int x:temp){
System.out.print(x + "、") ;
}
}
}
在本道程序中融合了以下的知识点:
1、 继承的基本概念:扩充了类的功能
2、 方法的覆写的作用
3、 引用传递
4、 数组的排序、foreach输出
3.9、final关键字(重点)
在Java中可以使用final关键字完成以下的操作:
·使用final关键字声明一个类
·使用final关键字声明一个方法
·使用final关键字声明一个常量
1、 使用final关键字声明的类不能有子类
final class A{
}
class B extends A{
}
报错信息:
ReWriteMethod.java:24: cannot inherit from final A
class B extends A{
^
1 error
2、 使用final声明的方法为最终方法,不能被子类所覆写
class A{
public final void fun(){}
};
class B extends A{
public void fun(){} // 不能被覆写
};
报错信息:
ReWriteMethod.java:26: fun() in B cannot override fun() in A; overridden method
is final
public void fun(){} //不能被覆写
^
1 error
3、 使用final关键字声明的变量即成为常量,常量在声明的时候必须为其赋值。
class A{
public final String INFO = "VINCE" ;
public void fun(){
INFO = "world" ;
System.out.println(INFO) ;
}
}
报错:
ReWriteMethod.java:25: cannot assign a value to final variable INFO
INFO = "world" ;
^
1 error
但是,关于常量有以下两点说明:
1、 常量的命名规则是全部字母大写:INFO
2、 使用public static final声明的常量称为全局常量。public static final String INFO = "VINCE" ;
3.10、抽象类(重点)
抽象类本身的应用极其复杂,现在的主要目的是熟悉抽象类的基本语法,具体该如何应用,要等待学习完对象多态性之后才能够深入研究。
抽象类的概念:
在Java中包含一个抽象方法的类称为抽象类。抽象类也必须使用abstract class声明。(疑问???)
一个抽象类中可以没有抽象方法。(编译无错误)所以抽象类的定义是否可以修改成,用abstract声明的类是抽象类。
抽象方法必须写在抽象类或者接口中。
抽象方法的概念:
只声明而未实现的方法称为抽象方法,抽象方法必须使用abstract关键字声明。
abstract class A{ // 抽象类
public static final String INFO = "VINCE" ;
public void print(){
System.out.println("INFO = " + INFO) ;
}
public abstract void fun() ; //抽象方法,只声明而未实现
}
以上定义了一个抽象类,并在抽象类中定义了一个抽象方法fun(),但是fun()方法上并没有“{}”,所以其是一个抽象方法。
范例:使用抽象类
public class AbstractDemo01{
public static void main(String args[]){
A a = null ;
a = new A() ;
a.print() ;
}
}
程序编译时出现了以下的错误提示:
AbstractDemo01.java:11: A is abstract; cannot be instantiated
a = new A() ;
^
1 error
以上的错误提示表示:A是一个抽象类,不能被实例化。
在抽象类的使用中有几个原则:
·抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键字new完成。
·一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须覆写抽象类中的全部抽象方法。
范例:为抽象类定义子类
abstract class A{ // 抽象类
public static final String INFO = "VINCE" ;
public void print(){
System.out.println("INFO = " + INFO) ;
}
public abstract void fun() ; // 抽象方法,只声明而未实现
public abstract void met() ; // 抽象方法,只声明而未实现
};
class B extends A{ // 继承抽象类
private String name = "HELLO" ;
public void fun(){ // 覆写了抽象类中的抽象方法
System.out.println(name) ;
}
public void met(){ // 有几个抽象方法就要覆写几个抽象方法
System.out.println("Hello World!!!") ;
}
};
public class AbstractDemo02{
public static void main(String args[]){
B b = null ;
b = new B() ;
b.print() ;
b.fun() ;
b.met() ;
}
};
思考:
1、 抽象类能否使用final声明?
· 不能,因为final属于终结器,被其修饰的类是不能有子类的
· 而抽象类本身必须有子类,所以不能。
2、 抽象类能否有构造方法?
· 能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法。
abstract class Person{
public Person(){
System.out.println("Person类的构造方法。") ;
}
public abstract String getInfo() ; // 抽象方法
};
class Student extends Person{
public Student(){
System.out.println("Student类的构造方法。") ;
}
public String getInfo(){
return null ;
}
};
public class AbstractDemo03{
public static void main(String args[]){
new Student() ;
}
};
只是抽象类中的构造方法虽然有,但是依然无法直接通过抽象类产生实例化对象。
因为一旦有实例化对象产生,则意味着就可以调用类中的方法了,但是抽象方法本身只是一个声明,并没有具体的实现,所以即便存在了构造,外部也是不能直接调用的,而只能由子类通过构造方法调用。
实际上最简单的一个理解:抽象类就是普通类的一个扩展,只是在普通类的基础之上加上了一个抽象方法而已,除此之外与普通类是完全一致的。
3.11、接口(重点)
接口也是先了解其基本的概念,等学习完对象多态性之后,再来观察接口的具体应用。
接口的地位:之前所学习到的一切内容,都是为最后的接口最准备的。
接口的概念:
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
接口的定义格式:
interface 接口名称{
全局常量 ;
抽象方法 ;
}
范例:按照以上的格式定义一个接口
interface A{ // 定义接口A
public static final String INFO = "VINCE" ; // 全局常量
public static final String AUTHOR = "MJW" ; // 全局常量
public abstract void print() ; // 抽象方法
public abstract String getInfo() ; // 抽象方法
}
但是需要注意的有两点:
1、 接口中的所有方法的访问权限都是public
2、 接口本身都是由全局常量和抽象方法组成,所以以上的定义可以简化为以下的形式:
interface A{ // 定义接口A
// public static final String INFO = "VINCE" ; // 全局常量
String INFO = "VINCE" ; //全局常量
// public static final String AUTHOR = "MJW" ; // 全局常量
String AUTHOR = "MJW" ; //全局常量
// public abstract void print() ; // 抽象方法
public void print() ; //抽象方法
// public abstract String getInfo() ; // 抽象方法
public String getInfo() ; //抽象方法
}
一个接口定义完成之后,接口本身也是需要子类支持的,即,如果一个接口要想使用,则也必须依靠子类。那么子类(如果不是抽象类的话)要覆写接口中的所有抽象方法。
子类继承接口采用如下的语法格式:
class 子类 implements 父接口1,父接口2,….
一个子类可以同时实现多个接口。
interface A{ // 定义接口A
String INFO = "VINCE" ; // 全局常量
public String getInfo() ; // 抽象方法
}
interface B{
String AUTHOR = "MJW" ; // 全局常量
public void print() ; // 抽象方法
}
class X implements A,B { // 一个子类同时实现两个接口
public String getInfo(){
return "HELLO WORLD" ;
}
public void print(){
System.out.println(INFO) ;
System.out.println(AUTHOR) ;
}
};
public class InterfaceDemo03{
public static void main(String args[]){
X x = new X() ;
System.out.println(x.getInfo()) ;
x.print() ;
}
};
以上的代码称为接口的实现。那么如果一个类即要实现接口,又要继承抽象类的话,则按照以下的格式编写即可:
class 子类 extends 父类 implements 父接口1,父接口2,…{}
那么,此时的子类不光要覆写抽象类的方法,还要覆写接口中的全部操作。
interface A{ // 定义接口A
String INFO = "VINCE" ; // 全局常量
public String getInfo() ; // 抽象方法
}
interface B{
String AUTHOR = "MJW" ; // 全局常量
public void print() ; // 抽象方法
}
abstract class C{
public abstract void fun() ;
};
class X extends C implements A,B { // 一个子类同时实现两个接口
public String getInfo(){
return "HELLO WORLD" ;
}
public void print(){
System.out.println(INFO) ;
System.out.println(AUTHOR) ;
}
public void fun(){}
};
在开发中一个抽象类可以实现多个接口,但是一个接口不能继承一个抽象类。
interface A{ // 定义接口A
String INFO = "VINCE" ; // 全局常量
public String getInfo() ; // 抽象方法
}
interface B{
String AUTHOR = "MJW" ; // 全局常量
public void print() ; // 抽象方法
}
abstract class C implements B { // 抽象类实现接口
public abstract void fun() ;
};
一个接口虽然不能继承抽象类,但是一个接口允许同时继承多个接口。
interface A{
public void printA() ;
}
interface B{
public void printB() ;
}
interface C extends A,B{ // 同时继承多个接口
public void printC() ;
}
class X implements C{
public void printA(){}
public void printB(){}
public void printC(){}
};
那么此时,就要求此接口的子类要覆写全部接口的抽象方法。
3.12、多态性(绝对重点)
多态性是面向对象的最后一个重要的特性,实际上对象多态性分为两种:一种属于方法的重载与覆写,另外一种就是马上要讲解到的对象多态性。
对象多态性,从概念上非常好理解,在类中有子类和父类之分,所以对象多态性就从此而来:
·向上转型:将子类实例变为父类实例
|-格式:父类 父类对象 = 子类实例 ; à 自动完成
|-以基本数据类型操作为例:int i = 'c' ; à 因为c的容量比int小,所以可以自动完成
·向下转型:将父类实例变为子类实例
|-格式:子类 子类对象 = (子类)父类实例 ; à 强制完成
|-以基本数据类型操作为例:char c = (char)98 ; à 因为整型的容量比char大,所以强制完成
范例:观察以下的操作代码
class A{
public void fun1(){
System.out.println("1、A --> public void fun1()") ;
}
public void funx(){
this.fun1() ; // 调用fun1()方法
}
};
class B extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("2、B --> public void fun1()") ;
}
public void fun2(){
System.out.println("3、B --> public void fun2()") ;
}
};
public class PolDemo01{
public static void main(String args[]){
B b = new B() ;
A a = b; // 子类向父类转型
a.funx() ;
}
};
向上转型可以自动完成,完成之后调用的方法是被子类覆写过的方法。
既然可以发生向上转型关系,那么就可以使用向下转型的操作。
class A{
public void fun1(){
System.out.println("1、A --> public void fun1()") ;
}
public void funx(){
this.fun1() ; // 调用fun1()方法
}
};
class B extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("2、B --> public void fun1()") ;
}
public void fun2(){
System.out.println("3、B --> public void fun2()") ;
}
};
public class PolDemo02{
public static void main(String args[]){
B b = new B() ;
A a = b; // 子类向父类转型
B x = (B)a ;
x.funx() ;
}
};
范例:错误的转型
class A{
public void fun1(){
System.out.println("1、A --> public void fun1()") ;
}
public void funx(){
this.fun1() ; // 调用fun1()方法
}
};
class B extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("2、B --> public void fun1()") ;
}
public void fun2(){
System.out.println("3、B --> public void fun2()") ;
}
};
public class PolDemo03{
public static void main(String args[]){
A a = new A() ;
B x = (B)a ;
x.funx() ;
}
};
以上的代码执行时出现了以下的错误:
Exception in thread "main" java.lang.ClassCastException: A cannot be cast to B
at PolDemo03.main(PolDemo03.java:20)
以上的错误是除了空指向异常之外,第二个最常见的一种异常了,表示的是类型的转换异常,即:两个没有任何关系的对象进行相互的转型操作,才会造成此问题。
所以,如果一个对象要想进行向下转型之前,必须首先发生向上转型操作。建立关系。
了解了对象多态性的基本概念之后,那么对象多态性到底有那些用处呢?
下面分析一道题目,题目分为两种方式,要求如下:
·要求设计一个方法,此方法可以接收任意的A类的子类实例。
范例:如果现在不使用对象多态性完成
· 只能通过方法重载的方式进行操作
class A{
public void fun1(){
System.out.println("1、A --> public void fun1()") ;
}
public void funx(){
this.fun1() ; // 调用fun1()方法
}
};
class B extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("2、B --> public void fun1()") ;
}
public void fun2(){
System.out.println("3、B --> public void fun2()") ;
}
};
class C extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("4、C --> public void fun1()") ;
}
public void fun3(){
System.out.println("5、C --> public void fun5()") ;
}
};
public class PolDemo04{
public static void main(String args[]){
fun(new B()) ;
fun(new C()) ;
}
public static void fun(B b){
b.fun1() ;
b.fun2() ;
}
public static void fun(C c){
c.fun1() ;
c.fun3() ;
}
};
以上的操作确实已经满足了要求,但是这样的代码使用时会有那些限制呢?
现在的A类有两个子类,但是从实际上看A类可能会有更多的子类,那么一旦有了更多的子类,则fun()方法就要重复的重载,则操作类要反复的进行修改。
范例:使用对象多态性完成
public class PolDemo05{
public static void main(String args[]){
fun(new B()) ;
fun(new C()) ;
}
public static void fun(A a){
a.fun1() ;
}
};
此时,以上的fun()方法将能够接收任意的A类的子类实例,就算操作再有所增加,方法不用做任何的改动。
3.13、instanceof关键字(重点)
之前的操作代码中在设计方法的时候使用了对象的多态性进行参数的接收,但是本身此操作现在并没有满足之前的要求,至少在传递B类的时候要求调用fun2()方法,如果传入的是C类的时候要求调用fun3()方法。
那么如果现在按照之前的方法完成,使用强制的转型操作则会产生问题:
public static void fun(A a){
a.fun1() ;
B b = (B) a ;
b.fun2() ;
}
以上的操作代码中无法判断传入的对象是哪个类的实例,所以无法正确的进行对象的转型操作,所以,此时如果要想判断某个对象是否是指定类的实例,则可以使用instanceof关键字完成。
语法格式如下:
实例化对象 instanceof 类 à 此操作返回boolean类型的数据
范例:验证instanceof关键字
class A{
public void fun1(){
System.out.println("1、A --> public void fun1()") ;
}
public void funx(){
this.fun1() ; // 调用fun1()方法
}
};
class B extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("2、B --> public void fun1()") ;
}
public void fun2(){
System.out.println("3、B --> public void fun2()") ;
}
};
public class PolDemo07{
public static void main(String args[]){
A a = new A() ; // 向上转型
System.out.println(a instanceof A) ;
System.out.println(a instanceof B) ;
}
};
那么,下面使用以上的形式修改之前的操作代码:
class A{
public void fun1(){
System.out.println("1、A --> public void fun1()") ;
}
public void funx(){
this.fun1() ; // 调用fun1()方法
}
};
class B extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("2、B --> public void fun1()") ;
}
public void fun2(){
System.out.println("3、B --> public void fun2()") ;
}
};
class C extends A{
public void fun1(){ // 覆写了父类中的fun1()方法
System.out.println("4、C --> public void fun1()") ;
}
public void fun3(){
System.out.println("5、C --> public void fun5()") ;
}
};
public class PolDemo08{
public static void main(String args[]){
fun(new B()) ;
fun(new C()) ;
}
public static void fun(A a){
a.fun1() ;
if(a instanceof B){
B b = (B) a ;
b.fun2() ;
}
if(a instanceof C){
C c = (C) a ;
c.fun3() ;
}
}
};
但是,以上的程序如果要在真实的开发中去使用的话也很麻烦。如果假设子类的实例过多,则要依次判断,所以如果此时父类设计的足够合理,那么则程序的操作代码将变得非常容易。所以在实际类的开发中,父类的设计是最重要的。
但是,以上的所有操作代码只是演示了对象多态性的特点,本身不具备任何的意义。
在实际的开发中,一个子类永远不要去继承一个已经完全实现好的类,要么继承抽象类,要么实现接口,此一原则将直接决定类设计的和好坏。
3.14、抽象类的实际应用(绝对重点)
抽象类在使用的时候需要依靠子类,子类本身也需要覆写全部的抽象方法,在使用对象多态性的时候,有一个非常明显的特点:在进行对象转型之后,调用的方法永远是被子类覆写过的方法。
范例:依靠对象多态性,进行抽象类的实例化
abstract class A {
public abstract String getInfo() ;
};
class B extends A{ // 子类要覆写全部的抽象方法
public String getInfo(){
return "Hello World" ;
}
};
public class AppAbastractDemo01{
public static void main(String args[]){
A a = new B() ; // 子类向父类转型
System.out.println(a.getInfo()) ; // 调用的方法是被子类覆写过的操作
}
};
使用抽象类完成以下的一种操作:
abstract class Person{
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public void say(){ // 说话是一个具体的功能
System.out.println(this.getContent()) ;
}
public abstract String getContent(); //说话的内容由具体的子类决定
public String getName(){
return this.name ;
}
public int getAge(){
return this.age ;
}
};
class Student extends Person{
private String school ;
public Student(String name,int age,String school){
super(name,age) ;
this.school = school ;
}
public String getContent(){
return "学生说话 -->姓名:" + super.getName() + ",年龄:" + super.getAge() + ",学校:" + this.school;
}
};
class Worker extends Person{
private String job ;
public Worker(String name,int age,String job){
super(name,age) ;
this.job = job ;
}
public String getContent(){
return "工人说话 -->姓名:" + super.getName() + ",年龄:" + super.getAge() + ",职位:" + this.job ;
}
};
public class AppAbastractDemo02{
public static void main(String args[]){
Person per1 = new Student("张三",20,"清华大学") ;
Person per2 = new Worker("李四",33,"清洁工") ;
fun(per1) ;
fun(per2) ;
}
public static void fun(Person p){
p.say() ;
}
};
以上代码的操作形式是抽象类应用的一个最好的体现,完成的是一个模板功能。
例如:大家上小学的时候有可能写过如下的一种单子:
违纪卡
姓名:
日期:
班级:
事由:
一旦出现了错误,就要填写此卡片,但是填写之前此卡片无意义。
违纪卡
姓名:
WEUGUANGXIN
日期:
今天
班级:
JAVA
事由:
上课睡觉、吸毒、赌博、…
3.15、接口的实际应用(绝对重点)
接口与抽象类一样,也是需要通过对象多态性进行对象的实例化操作。
范例:实例化接口对象
interface A{
public String getInfo() ;
}
class X implements A{
public String getInfo(){
return "Hello World!!!" ;
}
};
public class AppInterfaceDemo01{
public static void main(String args[]){
A a = new X() ; // 子类为父类进行实例化操作
System.out.println(a.getInfo()) ;
}
};
通过对象多态性可以完成接口对象的实例化操作,调用的方法永远是被子类覆写过的操作。
接口在实际的开发中有以下几个用处:
1、 定义操作标准。
2、 表示一种能力。
3、 将远程方法暴露给远程客户端。
范例:以USB操作标准为主,观察接口的作用
interface USB{ // 定义USB接口
public void start() ; // 开始工作
public void stop() ; // 结束工作
}
class Computer {
public static void plugin(USB usb){ // 电脑上可以插入USB设备
usb.start() ;
usb.stop() ;
}
};
class Mp3 implements USB{
public void start(){
System.out.println("Mp3开始工作。") ;
}
public void stop(){
System.out.println("Mp3停止工作。") ;
}
};
class Print implements USB{
public void start(){
System.out.println("打印机开始工作。") ;
}
public void stop(){
System.out.println("打印机停止工作。") ;
}
};
public class AppInterfaceDemo02{
public static void main(String args[]){
Computer.plugin(new Mp3()) ;
Computer.plugin(new Print()) ;
}
};
只要是符合接口标准的设备都可以插入,所以接口的主要作用在于定义标准。
3.16、Object类(重点)
Object类是所有类的父类,如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。
class Person extends Object{}
对于一个设计良好的类来讲,需要了解Object类中的以下三个重要方法:
·得到对象信息:public String toString()
·进行对象比较:public boolean equals(Object obj)
·得到对象的hash码:public int hashCode()
3.16.1、toString()方法
一个对象在实例化之后可以直接打印。
class Person{};
public class ObjectDemo01{
public static void main(String args[]){
System.out.println(new Person()) ;
}
};
如果现在将以上的代码修改如下:
class Person{
};
public class ObjectDemo01{
public static void main(String args[]){
System.out.println(new Person().toString()) ;
}
};
输出对象时调用toString()与不写toString()的操作的结果是一样的,那么,可以得出一个结论:在对象打印的时候将默认调用toString()方法,但是以上的toString()方法的返回信息并不明确,如果希望返回一个具体类的信息,则必须在子类中覆写toString()方法。
class Person{
public String toString(){ // 覆写toString()方法
return "Hello World!!!" ;
}
};
public class ObjectDemo02{
public static void main(String args[]){
System.out.println(new Person()) ;
}
};
既然可以证明覆写toString()方法有效,那么此时就可以通过toString()输出一些复杂的数据。
class Person{
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
};
public class ObjectDemo03{
public static void main(String args[]){
System.out.println(new Person("张三",30)) ;
}
};
3.16.2、equals()方法
在之前讲解String类的时候曾经介绍过equals()方法,此方法的主要作用进行对象的比较,那么之前也编写过对象的比较操作,实际上那样的操作应该写在equals()方法中最合适。
范例:完成对象比较
class Person{
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public boolean equals(Object obj){ // 覆写equals()方法
if(this==obj){
return true ;
}
if(!(obj instanceof Person)){
return false ;
}
Person p = (Person)obj ;
if(this.name.equals(p.name)&&this.age==p.age){
return true ;
}else{
return false ;
}
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
};
public class ObjectDemo04{
public static void main(String args[]){
Person per1 = new Person("张三",30) ;
Person per2 = new Person("张三",30) ;
System.out.println(per1.equals(per2)) ;
}
};
3.16.3、使用Object可以接收任意的引用数据类型
在Java中引用数据类型存在三种:类、数组、接口
范例:使用Object接收接口实例
interface A{
}
class B implements A{
};
public class ObjectDemo05{
public static void main(String args[]){
A a = new B() ;
Object o = a ; // 使用Object接收接口实例
A x = (A)o ; // 向下转型
}
};
范例:使用Object接收数组
public class ObjectDemo06{
public static void main(String args[]){
int i[] = {1,2,3,4,5,6} ;
Object o = i ; // 使用Object接收数组实例
fun(o) ;
}
public static void fun(Object t){
if(t instanceof int[]){ // 判断是否是整型数组
int temp [] = (int[]) t ;
for(int x:temp){
System.out.print(x + "、") ;
}
}
}
};
可以进一步扩充,可变参数中如果使用了Object作为类型,则任何类型都可以接收。
public class ObjectDemo07{
public static void main(String args[]){
fun("A","B","C") ;
}
public static void fun(Object ... t){
for(Object x:t){
System.out.print(x + "、") ;
}
}
};
---------------------------------------------------------------------------------------------------------
public class Test{
public static void main(String args[]){
int a[]=new int[3];
fun("A",a,"C") ;
}
public static void fun(Object ... t){
for(Object x:t){
System.out.print(x + "、") ;
}
}
};
4、总结
1、 继承可以完成类的功能扩充,使用extends关键字完成继承。
2、 继承本身有严格要求:一个子类只能继承一个父类,父类中的所有操作都将被继承,只是公共操作属于显式继承,而私有操作属于隐式继承。
3、 子类对象实例化之前,一定会先调用父类中的构造方法,默认情况下调用的是父类中的无参构造,也可以通过super关键字调用父类中指定参数的构造方法。
4、 方法的覆写,当子类定义了与父类中方法名称完全相同的方法时,称为方法的覆写,被覆写的方法要求与父类中的方法名称、参数的类型或个数完全一致,方法覆写时一定要注意访问权限的问题。
方法的重载及覆写的比较
No.
比较点
重载
覆写
1
定义
方法名称相同,参数的类型或个数不同
和返回值无关
方法名称、参数的类型和个数完全相同
返回值类型必须一致
2
注意点
-
访问权限不能更加严格
3
范围
发生在一个类中
发生在继承关系中
5、 super可以在子类调用父类中的属性、方法、构造方法
this与super的比较
No.
比较点
this
super
1
属性
调用本类中的属性,如果本类中没有此属性,将从父类中继续查找
直接表示调用的属性是父类中的属性
2
方法
调用本类中的方法
调用父类中的方法,一般在方法被覆写之后
3
构造
调用本类中的其他构造方法
调用父类中的其他构造方法
4
使用
必须放在构造方法的首行
必须放在子类构造方法的首行
this和super无法同时出现,但是在类中至少有一个构造方法是没有使用this()调用的,那么这个构造方法一般都会去使用super调用父类中的构造方法
5
特殊点
表示当前对象
无此概念
6、 包含一个抽象方法的类称为抽象类,抽象类必须被子类继承,子类继承之后要覆写抽象类中的全部抽象方法。
7、 由抽象方法和全局常量组成的特殊类称为接口,接口必须被子类实现,一个子类可以同时实现多个接口,但是只能继承一个抽象类。一个接口可以同时继承多个接口。
8、 对象多态性:向上转型、向下转型
9、 final关键字可以定义类、方法、常量、public static final定义全局常量。
10、 抽象类一般是作为一个模板出现的,而接口是作为一个标准出现的。
11、 Object类是所有类的父类,任何类的实例化对象都可以向Object进行转换,Object类中有toString()、equals()方法需要被子类覆写。而且Object类可以接收任意的引用数据类型。
5、预习任务
1、 巩固抽象类和接口
2、 包装类
3、 匿名内部类
4、 包及访问控制权限
6、作业
类设计:
一个宠物商店中允许有多种宠物,可以实现宠物的模糊查找功能。
问:此类该如何设计
- JavaSE_面向对象3
- JAVASE_面向对象上
- JAVASE_面向对象中
- JAVASE_面向对象下
- JavaSE_面向对象1
- JavaSE_面向对象2
- JavaSE_面向对象4
- JavaSE_面向对象(封装、继承、多态)
- 黑马程序员——javaSE_面向对象
- 【JAVASE_学习笔记】类与对象
- 面向对象3原则
- C#面向对象3
- 面向对象3
- 面向对象基本3
- java面向对象3
- 面向对象总结3
- 面向对象基础3
- 面向对象3
- Cube Stacking -- 并查集
- 数据库 事务、事务的特性、事务的隔离
- Go的异常处理 defer, panic, recover
- 百度语音合成(TTS)离在线融合,包括-102问题的完美解决!
- Ubuntu创建Git仓库并上传到GitHub
- JavaSE_面向对象3
- 【总结】RPC框架Dubbo深入分析
- find the most comfortable road HDU
- Javascript DOM操作
- http post请求接口 传输json
- 第X大的数
- JAVA如何编译本地的jar包 启动服务!
- 在salesforce中用Apex实现在父记录上对所有符合要求的子记录批量提交审批
- 机器学习四 数据的差异性