Java面向对象(三)
作业
- 继承
- 成员的覆盖
- 多态
- 抽象类和接口
- 其他
继承
定义:
继承就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
注:父类又称为超类或者基类。子类又称为派生类!
继承的格式:
通过 extends 关键字,可以声明一个子类继承另外一个父类
class A {undefined
}
class b extends A { // b继承A
}
继承的实现:
class Person{
String name;
int age ;
}
class Student extends Person{
void study(){
System.out.println("student study..." + age);
}
}
class Worker extends Person{
void work(){
System.out.println("worker work..." + age);
}
}
class ExtendDemo{
public static void main(String[] args){
Student s = new Student();
s. name = "zhangsan" ;
s. age = 20;
s.study();
Worker w = new Worker();
w. name = "lisi" ;
w. age = 30;
w.work();
}
}
注意:
1)Java不支持多重继承,一个子类只能有一个父类,不允许出现以下情况
class 子类 extends 父类1,父类2{}
2)在Java中可以有多层继承,如A继承了B,B又继承了C。此时相当于A间接继承了C。
3)如果一个成员要被子类继承之后使用,这个成员不能是private类型,因为私有的成员不能在类的外部使用,当然也不能被子类使用。一般情况下,成员变量定义为protect类型,成员函数定义为public类型
继承的底层本质:
从本质上来讲,子类继承父类之后实例化子类对象的时候,系统会首先实例化父类对象
只要实例化子类对象,系统就会先自动实例化一个父类对象与之对应,当然此时掉用的是父类没有参数的构造函数。
这就出现了一个问题--------父类构造函数万一有参数呢?
因此,此时,系统必须要求在实例化父类对象之时传入参数,否则会报错
解决问题有以下两种方法:
1)给父类增加一个不带参数的空构造函数
2)在子类的构造函数中,第一句用super给父类构造函数传参数
注意:
"super(title);"必须写在子类构造函数的第一句,传入的参数必须和父类的构造函数中的参数列表类型相匹配
成员的覆盖:
问题:
在父类和子类中都有函数show(),子类对象调用“fd.shou();”,调用的是子类的show,还是父类的show?
如果子类中的函数定义和父类相同,最后调用时是调用子类中的方法,如此称为覆盖或重写(Override)。
注意:
1)将override和overload相区别
2)如果在子类中定义了一个名称和参数列表与父类相同的函数,但是返回类型不同,此时系统会报错
3)在重写时,子类函数的访问权限不能比父类更加严格。例如,父类的成员函数的访问权限是public,子类在重写时就不能定义为protect。
4)在覆盖的情况下,如果一定要在子类中调用父类的成员函数,可以使用关键字super,调用方法是"super.函数名"
下面来通过一个例子了解一下:
public class Father
{
public static String name1 = "父类的类变量";
public String name2 = "父类的实例变量名";
//定义一个类方法
public static void classMethod()
{
System.out.println("Father父类通过类方法调用它的类变量:" + name1);
}
//定义一个实例方法
public void instanceMethod()
{
System.out.println("Father父类通过实例方法调用它的实例变量:" + name2);
}
}
public class Son extends Father
{
public static String name1 = "子类的类变量";
public String name2 = "子类的实例变量";
//定义一个类方法
public static void classMethod()
{
System.out.println("Son子类通过类方法调用它的类变量:" + name1);
}
//定义一个实例方法
public void instanceMethod()
{
System.out.println("Son子类通过实例方法调用它的实例变量:" + name2);
}
}
public class Test
{
public static void main(String[] args)
{
System.out.println("\n-----------------------------------------");
Son mySon1 = new Son();
Father myFather1 = mySon1; //对象类型转换的上转型
myFather1.instanceMethod();
System.out.println("\n------------------------------------");
Father myFather2 = new Father();
if (myFather2 instanceof Son)
{
//隐式对象类型转换
Son mySon2 = (Son) myFather2;
//调用myFather对象的实例方法
mySon2.instanceMethod();
}
}
}
多态:
多态是面向对象的基本特征之一
注意:
函数重载也是一种多态,称为静态多态
动态多态的理论基础是父类引用可以指向子类对象,示例代码如下:
在main函数中"Dialog dialog=new FontDialog();",首先定义了一个Dialog类型的父类引用,却指向了一个子类对象。
在这种情况下,"dialog.show();"到底是调用父类的show函数还是子类的show函数呢?
运行代码,控制台打印结果如下所示:
FontDialog.show()
可以看出,调用的是子类的show函数。
多态的使用:
- 函数传入的形参可以是父亲类型,而实际传入的可以是子类对象
...
public class main(){
public static void fun(Dialog dialog){ //父类类型形参
dialog show();
}
public static void main(String[]args){
fun(new FontDialog()); //实际传入的是子类对象
}
}
实际调用时传入的是子类对象,因此调用的是子类对象的show方法。
2. 函数的返回类型是父类类型,而实际返回的可以是子类对象
...
public class main{
public static Dialog fun(){ //函数返回类型是父类类型
return new FontDialog(); //实际返回类型是子类对象
}
public static void main(String [] args){
Dialog dialog=fun();
dialog.show();
}
}
父类和子类对象的类型转换:
1:子类类型对象转换成父类类型
Dialog dialog=new FontDialog();
2:父类类型对象转换成子类类型
Dialog dialog=new FontDialog();
FontDialog fd=(FontDialog)dialog;
注意:
这是一种特殊情况,父类类型对象原来就是子类类型的对象,则可以像上面一样强制转换成相应子类类型
问答:
问)如何知道一个对象是什么类型的?
答)可以使用 instanceof操作符进行判断,格式为:
对象名 instanceof 类名
抽象类和接口:
抽象类
首先看抽象方法:
抽象方法: 只有方法签名,没有方法的实现。并且被abstract修饰。
例如:
abstract void test();
有抽象方法的类只能被定义为抽象类,抽象类里可以没有抽象方法。
抽象类的规则
抽象类必须使用abstract修饰符来修饰。
抽象类不能被实例化,无法使用new关键字来创建实例,即使抽象类中没有抽象方法,也不可以创建实例。只能当做父类被其他子类继承。
抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器(不能用于创建实例,主要是被子类调用)、初始化块、内部类(接口、枚举)5种成分。
含有抽象方法的类。(直接定义了一个抽象方法;或者继承了一个抽象父类,但没有完全实现父类包含的抽象方法;或者实现了一个接口,但没有完全实现接口包含的抽象方法)这三种情况,只能被定义成抽象类。
当使用abstract修饰类时,表明这个类只能被继承;当使用abstract修饰方法时,表明这个方法必须由子类提供实现(即重写),因此abstract方法不能定义为private访问权限(必须用public和protected修饰,缺省情况下为public),即private和abstract不能同时修饰方法。而final修饰的类不能被继承,final修饰的方法不能被重写。因此,final和abstract永远不能同时使用。
static和abstract不能同时修改某个方法,即没有所谓的类抽象方法。
抽象类的作用
作为子类的模板,从而避免了子类设计的随意性。抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为。
接口
介绍
接口定义了一种规范。
基本语法如下:
[修饰符] interface 接口名 extends 父接口1, 父接口2...
{
零到多个常量定义... (因为接口是一种规范,不能包含构造器和初始化块定义。只能是静态常量)
零到多个抽象方法定义...
零到多个内部类、内部接口、内部枚举定义...
零到多个默认方法或类方法定义... (只有在java8以上版本才允许)
}
说明:
修饰符可以是public或者省略,如果省略了public访问控制符,则默认采用包权限访问控制符,即只有在相同包结构下可以访问该接口。
接口名应该与类名采用相同的命名规则。合法的标识符,并具有可读性,可以使用形容词。
一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
注意:
接口中定义的是多个类共同的公共行为规范,因此接口里所有成员,包括常量、方法、内部类和内部枚举都是public访问权限。定义接口成员时,可以忽略访问控制修饰符,如果指定访问控制修饰符,则只能使用public访问控制修饰符。
对于接口里定义的静态常量而言,它们是接口相关的,因此系统会自动为这些成员变量增加static和final两个修饰符。也就是说,在接口中定义成员变量时,不管是否使用public static final修饰符,接口中的成员变量总是使用这三个修饰符修饰。 而且接口中没有构造器和初始化块,因此接口里定义的成员变量只能在定义时指定默认值。
下面两行代码的结果是一样的,系统会自动增加public static final修饰符
int MAX_SIZE = 10;
public static final int MAX_SIZE = 10;
接口里定义的方法只能是抽象方法、类方法和默认方法,因此如果不是定义默认方法,系统将自动为普通方法增加abstract修饰符;定义接口里的普通方法时,不管是否使用public abstract修饰符,接口里的普通方法总是使用public abstract来修饰。 接口里的普通方法不能有方法实现(方法体);但类方法、默认方法都是必须有方法实现(方法体)。
接口里定义内部类、内部接口、内部枚举默认都采用public static两个修饰符,不管定义时是否指定这两个修饰符,系统都会自动使用public static对它们进行修饰。
使用接口
接口不能创建实例,但是可以用于声明引用类型变量。当使用接口来声明引用类型变量时,这个引用类型变量必须引用到到其实现类的对象。用途如下:
- 定义变量,也可以用于强制类型装换
- 调用接口中定义的常量
- 被其他类实现
一个类可以继承一个父类,但是可以实现一个或多个接口, 类实现接口的语法如下:
[修饰符] class 类名 extends 父类 implements 接口1, 接口2, ....
{
类体部分
}
一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里的所定义的全部抽象方法(也就是重写这些抽象方法);
否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义为抽象类。
抽象类和接口的比较
相同之处
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
差别
- 接口里只能包含抽象方法、静态方法和默认方法,不能为普通方法提供方法实现;抽象类则完全可以包含普通方法。
- 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。
- 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让子类调用这些构造器来完成属于抽象类的初始化操做。
- 接口里不能包含初始化块;但抽象类里完全可以包含初始化块。
- 一个类最多只有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多接口,可以弥补Java单继承的不足。
其他
关键字 final:
- 用final修饰一个类,则该类不能被继承
- 用final修饰一个函数,该函数不能被其子类重写
- 用final修饰一个成员变量,则该成员变量的值不允许被改变
注意:
final成员变量必须在声明时或在构造函数中显式赋值才能使用。一般情况下,在定义时就进行赋值。
Object 类
Object类是所有类的父类,如果没有用extends明确标明,那就默认继承Object类
Object类中的两个方法:
1): toString()
Object类具有toString()方法,你创建的每个类都会继承该方法。它返回对象的一个String表示,并且对于调试非常有帮助。然而对于默认的toString()方法往往不能满足需求,需要覆盖这个方法。
toString()方法将对象转换为字符串。看以下代码
package sample;
class Villain {
private String name;
protected void set(String nm) {
name = nm;
}
public Villain(String name) {
this.name = name;
}
public String toString() {
return "I'm a Villain and my name is " + name;
}
}
public class Orc extends Villain {
private int orcNumber;
public Orc(String name, int orcNumber) {
super(name);
this.orcNumber = orcNumber;
}
public void change(String name, int orcNumber) {
set(name);
this.orcNumber = orcNumber;
}
public String toString() {
return "Orc" + orcNumber + ":" + super.toString();
}
public static void main(String[] args) {
Orc orc = new Orc("Limburger", 12);
System.out.println(orc);
orc.change("Bob", 19);
System.out.println(orc);
}
}
toString的好处是在碰到“println”之类的输出方法时会自动调用,不用显式打出来
1): equals()
java equals 方法是在 java 中用于将字符串与指定的对象比较,在 String 类中重写了 equals () 方法用于比较两个字符串的内容是否相等,如果给定对象与字符串相等,则返回 true;否则返回 false。
public class Test {
public static void main(String args[]) {
String Str1 = new String("runoob");
String Str2 = Str1;
String Str3 = new String("runoob");
boolean retVal;
retVal = Str1.equals( Str2 );
System.out.println("返回值 = " + retVal );
retVal = Str1.equals( Str3 );
System.out.println("返回值 = " + retVal );
}
}
输出结果:
返回值 = true
返回值 = true
更多推荐
所有评论(0)