本章概览:
3.1 类的继承(概念与语法)
3.2 Object
类(在Java继承最顶层的类)
3.3 终结类和终结方法(只能拿来用,不可以继承)
3.4 抽象类(有一些方法暂时不能实现)
3.5 泛型
3.6 类的组合(继承是隶属关系,组合不是)
目录继承是一种以已有类为基础,生成设计新类的机制,是面向对象程序设计的重要特征。
subclass
),在面向对象概念中也称派生类,子类对象与超类对象存在“是一个...”(或“是一种...”)的关系,将在后续例题中介绍。
从外部来看,他应该包括:
其内包含着超类的所有变量和方法,但是子类的对象里面存储的只有从超类继承来的属性和自己本类所扩展新增的属性(类的方法只会存放在方法,不会在每个对象中存储,对象中进存储数据)。
/*类继承关系的说明*/ [ClassModifier]class ClassName extends SuperClassName{ //类体 }
类继承举例:
设有三个类:Person
,Emploee
,Manager
,Person
是最广泛的概念,Emploee
比Person
更具体一些,Manager
又是比泛泛的Manager
更具体的类。因此,可以让Emploee
继承Person
,再让Manager
继承Emploee
,用自然语言可以描述为Emploee
是一种Person
,Manager
是一种Emploee
,这其中便蕴含了类的继承关系设计的思想。
例:类继承
public class Person{ public String name; public String getName(){ return name; } } public class Employee extends Person{ public int employeeNumber; public int getEmployeeNumber(){ return employeeNumber; } } public class Manager extends Emploee{ public String responsibilities; public String getResponsibilities(){ return responsibilities; } }
测试类
public class Exam4_2Test { public static void main(String[] args) { Employee li = new Employee(); li.name = "Li Ming";//从超类继承的属性 li.employeeNumber = 123456;//自己的属性 System.out.println(li.getName());//从超类继承的方法 System.out.println(li.getEmploeeNumber());//自己定义的方法 Manager he = new Manager(); he.name = "He Xia";//从超类的超类继承的属性 he.employeeNumber = 543469;//从超类继承的树属性 he.responsibilities = "Internet Project";//自己的属性 System.out.println(he.getName());//从间接超类继承的方法 System.out.println(he.getEmploeeNumber());//从直接超类继承的方法 System.out.println(he.getResponsibilities());//自己定义的方法 } }
运行结果
Li Ming 123456 He Xia 543469 Internet Project
例:访问从超类继承的成员
public class classB { public int a = 10; private int b = 20; protected int c = 30; public int getB() { return b; } } public class classA extends classB { public int d; public void tryVariables() { System.out.println(a);//允许 System.out.println(b);//不允许 System.out.println(getB());//允许 System.out.println(d);//允许 } }
从上例可知,classA
中直接访问a
是可以的,但试图直接访问b
是不可以的,因为private
是私有属性,在继承类中不可以直接访问(可以查看我的《Java程序设计(2021春)——第二章笔记与思考》这篇blog中有具体介绍权限控制范围),此时可以查看超类中是否为该private
设置访问接口,此处有即为getB()
。
子类对从超类继承而来的属性和行为可以重新定义:定义重名的属性,则从超类继承过来的属性会被隐藏;如果声明一个和超类继承过来的方法圆形一模一样的方法,那么从超类继承的方法会被覆盖。
例:
class Parent { Number aNumber; }
class Child extends Parent { Float aNumber; }
通过这样的办法可以隐藏掉超类中的某些属性,从而定义我们自己的属性。
super.属性
访问从超类继承的属性。public class A1 { int x = 2; public void setx(int i) { x = i; } void printa() { System.out.println(x); } }
public class B1 extends A1 { int x = 100; void printb() { super.x = super.x + 10;//操作从超类继承而来的x System.out.println("super.x=" + super.x + " x= " + x); } }
public class Exam4_4Test { public static void main(String args[]) { A1 a1 = new A1(); a1.setx(4); a1.printa(); B1 b1 = new B1(); b1.printb(); b1.printa(); b1.setx(6);//将继承x的值设置为6 b1.printb(); b1.printa(); a1.printa(); } }
输出:
4 super.x=12 x= 100 12 super.x=16 x= 100 16 4
class A { static int x = 2;//注意,虽然可以被所有子类对象成员访问,但是静态成员不被继承 public void setx(int i) { x = i; } void printa() { System.out.println(x); } }
class B extends A { int x = 100; void printb() { super.x = super.x + 10; System.out.println("super.x=" + super.x + " x= " + x); } }
public class Tester { public static void main(String args[]) { A a1 = new A(); a1.setx(4); a1.printa(); B b1 = new B(); b1.printb(); b1.printa(); b1.setx(6); b1.printb(); b1.printa(); a1.printa(); } }
输出:
4 super.x=14 x= 100 14 super.x=16 x= 100 16 16
注意,静态成员不属于任何一个对象,不会有多个副本,所以在Tester
类中调用的方法对super.x
进行操作时,父类A
中的x
就是被改变了,从别的对象访问也是被改变的。
final
的终结方法。static
的静态方法。super.overriddenMethodName();
Object
类Object
类是所有类的直接或间接的超类,处在类层次的最高点。Object
类的主要方法public final Class getClass()
:获取当前对象所属的类信息,返回Class
对象。public String toString()
:返回表示当前对象本身有关信息的字符串对象。public boolean equals(Object obj)
:比较两个对象引用是否指向同一对象,是则返回true
,否则返回false
。protected Object clone()
: 复制当前对象,并返回这个副本。public int hashCode()
: 返回该对象的哈希代码值。protected void finalize()throws Throwable
: 在对象被回收时执行,通常完成资源的释放工作。equal
):两个对象具有相同的类型及相同的属性值。identical
):两个引用变量指向的是同一个对象。==
判断的是这两个对象是否同一。==
判断两个引用是否同一public class Tester1 { public static void main(String[] args) { BankAccount a = new BankAccount("Bob", 123456, 100.00f); BankAccount b = new BankAccount("Bob", 123456, 100.00f); if (a == b) { System.out.println("YES"); } else { System.out.println("NO"); } } }
输出:
NO
解析:判断连个引用是否相等,本质上是判断他们指向的对象是否相同。
equals
方法Objct
类中的equals()
方法的定义如下:
public boolean equals(Object x){ return this == x; }
例:equals
方法
public class EqualsTest { public static void main(String[] args) { BankAccount a = new BankAccount("Bob", 123456, 100.00f); BankAccount b = new BankAccount("Bob", 123456, 100.00f); if (a.equals(b)) { System.out.println("YES"); } else { System.out.println("NO"); } } }
输出:
NO
由上可知,equals()
方法的功能天然的是判断对象是否同一,而非判断两个对象是否相等。
因此,当我们想要判断两个对象是否相等而非对象是否相同时,需要自己写equals()
方法体覆盖Object
类中的equals()
方法。
equals()
方法(1)在BankAccount
类中覆盖equals()
方法
public boolean equals(Object x) { if (this.getClass() != x.getClass()) return false;//先判断同类 BankAccount b = (BankAccount) x;//强转未知类型的x(其实程序运行到这说明x的类型是符合要求的)到BankAccount类 return ((this.getOwnerName().equals(b.getOwnerName())) && (this.getAccountNumber() == b.getAccountNumber())&& (this.getBalance() == b.getBalance())); }
equals()
方法2public class Apple { private String color; private boolean ripe; public Apple(String aColor, boolean isRipe) { color = aColor; ripe = isRipe; } public void setColor(String aColor) { color = aColor; } public void setRipe(boolean isRipe) { ripe = isRipe; } public String getColor() { return color; } public boolean getRipe() { return ripe; } public String toString() { if (ripe) return ("A ripe" + color + "apple"); else return ("A not so ripe" + "apple"); } public boolean equals(Object obj) { if(obj instanceof Apple) { Apple a = (Apple)obj; return ( (color.equals(a.getColor())) && (ripe == a.getRipe())) } return false; } }
上述采用instanceof
来判obj
是否类型相符合。
hashCode()
方法hashCode()
是一个返回对象散列码的方法,该方法实现的一般规定是:
hashCode()
方法每次都应返回到同一个整数。在不同的执行中,对象的hashCode()
方法返回值不必一致。equals()
方法两个对象是相等的,则在这两个对象上调用hashCode()
方法应该返回同样的整数结果。equals()
方法两个对象不相等,并不要求在这两个对象上调用hashCode()
方法返回值不同。只要实现得合理,Object
类定义的hashCode()
方法为不同对象返回不同的整数。一个典型的实现是,将对象的内部地址转换为整数返回,但是Java语言并不要求必须这样实现。
clone
方法用于根据已存在的对象构造一个新的对象,即复制对象。
clone
方法复制对象覆盖clone
方法:在Object
类中被定义为protected
,所以需要覆盖为public
。
实现Clonable
接口,赋予一个对象被克隆的能力(cloneability
)。不需要再额外去实现什么,只是一个标记表示可以克隆。
例:如下表示允许MyObject
类被克隆
class MyObject implements Cloneable{ //... }
finalize
方法finalize
方法,但是该方法不能够被显式地调用。一个对象不再被使用即随时有可能被回收,但是什么时刻回收、以什么次序回收并没有规定。finalize
方法,覆盖方法的最后必须调用super.finalize
。getClass
方法getClass
是一个final
方法,返回一个Class
对象,用来代表对象所属的类。Class
对象,可以查询类的各种信息:如名字、超类、实现接口的名字等。例:
void PrintClassName(Object obj){ System.out.println("The Object's Class is "+obj.getClass().getName()); }
notify
,notifyAll
,wait
方法final
方法,不能覆盖。final
修饰的类和方法。声明ChessAlgorithm
类为final
类
final class ChessAlgorithm{...}
如果再写如下程序:
class BetterChessAlgorithm extends ChessAlgorithm{...}
编译器将显示一个错误
Chess.java:6:Can't subclass final classes:class ChessAlgorithm
final
方法举例
class Parent{ public Parent(){}//构造方法 final int getPI(){ return Math.PI;//终结方法 } }
说明:
getPI
是用final
修饰符声明的终结方法,不能在子类中对该方法进行覆盖,因而如下声明是错误的。
Class Child extends Parent{ public Child(){} int getPI{ return 3.14;//错误:不允许覆盖超类中的终结方法 } }
综上终结类是只能直接使用不能被继承的类,终结方法是只可以原样使用不可以被覆盖的方法。
抽象类往往代表比较抽象的概念,在抽象类中通常要规定整个类家族各级子类都必须具有的属性和方法。
abstract
。abstract
修饰,只有方法原型,没有方法体的实现。new
方法进行实例化,只能用作超类。abstract class Number{ ... }
如果写:
new Number();
编译器将会显示错误。
抽象方法规定了一种行为的访问接口,通常是在抽象类中规定子类都必须要有这样的行为,但是行为的具体实现没有给出。
抽象方法声明的语法形式:
public abstract <returnType><methodName>(...)
仅有方法原型,而没有方法体。
抽象方法的具体实现由子类在它们各自的类声明中完成。
只有抽象类可以包含抽象方法,即,若打算在方法中写抽象方法,则须定义类为抽象类。
例:抽象的绘图类和抽象方法
各种图形都需要实现绘图方法,可在它们的抽象超类中声明一个draw
抽象方法。
abstract class GraphicObject{ int x,y; void moveTo(int newX,int newY){ ... } abstract void draw(); }
然后在每一个子类中重写draw
方法,例如:
class Circle extends GraphicObject{ void draw(){ ... } }
class Rectangle extends GraphicObject{ void draw(){ ... } }
泛型的本质是将类型参数化。有泛型的类、泛型的方法和泛型的接口。
class GeneralType <Type>{ Type object; public GeneralType(Type object){ this.object = object; } public Type getObj(){ return object; } } public class Test { public static void main(String[] args) { GeneralType<Integer> i = new GeneralType<Integer>(2); GeneralType<Double> d = new GeneralType<Double>(0.33); System.out.println("i.object = " + (Integer) i.getObj()); System.out.println("i.object = " + (Integer) d.getObj());//编译错误:Cannot cast from Double to Integer } }
class GeneralMethod { <Type> void printClassName(Type object) { System.out.println(object.getClass().getName()); } } public class Test { public static void main(String[] args) { GeneralMethod gm = new GeneralMethod(); gm.printClassName("hello"); gm.printClassName(3); gm.printClassName(3.0f); gm.printClassName(3.0); } }
会进一步加强程序的通用性
class GeneralType <Type>{ Type object; public GeneralType(Type object){ this.object = object; } public Type getObj(){ return object; } } class ShowType { public void show(GeneralType<?> o) { System.out.println(o.getObj().getClass().getName()); } } public class Test { public static void main(String[] args) { ShowType st = new ShowType(); GeneralType<Integer> i = new GeneralType<Integer>(2); GeneralType<String> s = new GeneralType<String>("Hello"); st.show(i); st.show(s); } }
输出
java.lang.Integer java.lang.String
在参数Type
后使用extends
关键字并加上类名或接口名,表明参数所代表的类型必须是该类的子类或者实现了该接口。
注意,对于实现了某接口的有限制泛型,也是使用extends
关键字,而不是implements
关键字。
class GeneralType<Type extends Number> { Type object; public GeneralType(Type object) { this.object = object; } public Type getObj() { return object; } } public class Test { public static void main(String[] args) { GeneralType<Integer> i = new GeneralType<Integer>(2); //GeneralType<String> s = new GeneralType<String>("Hello"); //非法,T只能是Number或者Number的子类 } }
面向对象的程序是用软件来模拟现实世界中的对象,而现实世界中的对象往往是由部件组装而成的。
类的组合也是一种类的重用机制,表达的是包含关系,”有一个“的关系。
将以存在的类的对象放到新类即可
例如,可以说:厨房kitchen
中有一个炉子cooker
和一个冰箱refrigerator
。所以,可以简单地把对象myCooker
和myRefrigerator
放在类kitchen
中。
class Cooker{//类语句} class Refrigerator{//类语句} class Kitchen { Cooker myCooker; Refrigerator myRefrigerator; }
例:组合举例——线段类
一条线段包含两个端点
public class Point { private int x, y;// coordinate public Point(int x, int y) { this.x = x; this.y = y; } public int GetX() { return x; } public int GetY() { return y; } } class Line { private Point p1, p2; Line(Point a, Point b) { p1 = new Point(a.GetX(), a.GetY()); p2 = new Point(b.GetX(), b.GetY()); } public double Length() { return Math.sqrt(Math.pow(p2.GetX() - p1.GetX(), 2) + Math.pow(p2.GetY() - p1.GetY(), 2)); } }
继承表达的是”是一个“”是一种“的从属关系;组合表达的是”有一个”的包含关系。在声明复杂的类的时候往往继承和组合都用得上。
class Plate {// 声明盘子 public Plate(int i) { System.out.println("Plate constructor"); } } class DinnerPlate extends Plate {// 声明餐盘为盘子的子类 public DinnerPlate(int i) { super(i); System.out.println("DinnerPlate constructor"); } } class Utensil {// 声明器具 Utensil(int i) { System.out.println("Utensil constructor"); } } class Spoon extends Utensil {// 声明勺子为器具的子类 public Spoon(int i) { super(i); System.out.println("Spoon constructor"); } } class Fork extends Utensil {// 声明餐叉为器具的子类 public Fork(int i) { super(i); System.out.println("Fork constructor"); } } class Knife extends Utensil {// 声明餐刀为器具的子类 public Knife(int i) { super(i); System.out.println("Knife constructor"); } } class Custom {// 声明做某事的习惯 public Custom(int i) { System.out.println("Custom constructor"); } } public class PlaceSetting extends Custom {// 餐桌的布置 Spoon sp; Fork frk; Knife kn; DinnerPlate pl; public PlaceSetting(int i) { super(i + 1); sp = new Spoon(i + 2); frk = new Fork(i + 3); kn = new Knife(i + 4); pl = new DinnerPlate(i + 5); System.out.println("PlaceSetting constructor"); } public static void main(String[] args) { PlaceSetting x = new PlaceSetting(9); } }
输出
Custom constructor Utensil constructor Spoon constructor Utensil constructor Fork constructor Utensil constructor Knife constructor Plate constructor DinnerPlate constructor PlaceSetting constructor
由上可知,首先调用PlaceSetting
超类构造方法,然后构建对象成员:先调用Spoon
超类构造方法,Utensil
,同样的次序构造Fork
Knife
Plate
;调用DinnerPlate
超类Plate
构造方法再调用DinnerPlate
构造方法。
介绍了Java语言类的重用机制,形式可以是继承或组合。继承表达了一种从属关系,“是一种”“是一个”;组合表达了一种包含关系,是一种部件组装的思想,两者都实现了类的重用,使我们可以在已有类的基础上设计新的类,提高了软件开发的效率,并且程序的可靠性和稳定性会更好。
Object
类的主要方法。Object
类是Java中所有类的直接或间接的超类,在整个类继承体系的最上端。因此,在Object
类中规定了所有类都必须具有的属性和行为。但从Object
类中直接继承的行为和功能不一定好用,需要我们自己去覆盖,如equals
等方法,但是终结方法、静态方法等不能覆盖。
终结类和终结方法的特点和语法。
抽象类和抽象方法的特点和语法。无法实现方法体,在超类中声明抽象方法。抽象方法的具体实现留给子类或子类的子类直至某一级子类