文章目录
类再生
- 组合(Composition):在新类创建现有类的的对象
- 继承(Inheritance):创建一个新类,将其做为现有类的一个“类型”。我可原样采用现有类的形式,并在其中加入代码,同时不会对现有类产生影响。
1. 组合的语法
对象句柄的初始化
确保所有字段在使用之前得到初始化
- 在对象定义时。意味着在调用构造器之前肯定能得到初始化。
- 在那个类的构造器中。
- 紧靠在要求实际使用的对象之前。这样可减少不必要的开销(如果对象并不需要创建的话)
示例:
package c6;
//:Bath.java
class Soap {
private String s;
Soap(){ //Constructed
System.out.println("Soap()");
s= new String("Constructed");
}
public String toString() { return s;}
}
public class Bath {
private String
//Initializing at point of definition(在定义变量(对象)的时候初始化)
s1=new String("Happy"),
s2="Happy",
s3,s4;
Soap castille;
int i;
float toy;
public Bath() {
System.out.println("Inside Bath()");
//在构造方法内初始化
s3=new String("JOY");
i=47;
toy=3.14f;
castille=new Soap();
}
void print() {
//Delayed initialization(对象使用之前初始化):
if(s4==null)
s4=new String("JOY");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
System.out.println("s3 = " + s3);
System.out.println("s4 = " + s4);
System.out.println("i = " + i);
System.out.println("toy = " + toy);
System.out.println("castille = " + castille);
}
public static void main(String[] args) {
Bath b=new Bath();
b.print();
}
}
2. 继承的语法
“这个新类和那个旧类差不多” “子类是父类的一种类型”
class 子类 extends 父类 {...}
举个栗子:
//:Detergent.java
//Inheritance syntax & properties
class Cleanser{
private String s=new String("Cleanser");
public void append(String a) {s +=a;}
public void dilute() {append(" dilute");}
public void apply() {append(" apply");}
public void scrub() {append(" scrub");}
public void print() {System.out.println(s);}
public static void main(String[] args) {
Cleanser x= new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
public class Detergent extends Cleanser {
//Change s method:
@Override
public void scrub() { //重写scrub方法
append(" Detergent.scrud()");
super.scrub(); //Call base-class version(调用父类原本的scrub方法)
}
//Add methods to the interface
public void foam() {append(" foam"); }
//Test the new class:
public static void main(String[] args) {
Detergent x=new Detergent();
x.dilute(); x.apply(); x.scrub(); x.foam();
x.print();
System.out.println("Test base class:");
Cleanser.main(args); //调用父类主方法
}
}
3. 初始化父类
编译器强制我们在子类的构造器中首先设置对父类构造器的调用 具体如下:
默认构造器
父类会在子类调用它之前得到正确的初始化,即使子类没有构造器编译器也会其合成一个默认构造器
//:Cartoon.java
//Constructor calls during inheritance
class Art{
Art(){
System.out.println("Art constructor");
}
}
class Drawing extends Art{
Drawing(){
super(); //默认省略
System.out.println("Drawing constructor");
}
}
public class Cartoon extends Drawing {
public Cartoon() {
System.out.println("Cartoon constructor");
}
public static void main(String[] args) {
Cartoon x=new Cartoon();
}
}
输出:
Art constructor
Drawing constructor
Cartoon constructor
有参构造器
有参数的构造器,必须明确的编写对父类构造方法的调用代码,可用super关键字实现。
//:Chess.java
//Inheritance,constructors and argument
class Game{
Game(int i){
System.out.println("Game constructor"+i);
}
}
class BoardGame extends Game {
BoardGame(int i){
super(i);
System.out.println("BoardGame constructor"+i);
}
}
public class Chess extends BoardGame {
Chess(){
super(2);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
Chess x=new Chess();
}
}
4.合成和继承的结合
编译器强制我对父类初始化,并要求在构造器的开头。但它并不会监控我们是否做了正确的初始化,所一必须特别留意。
//:PlaceSetting.java
//Combining composition & inheritance
class Plate {
Plate(int i){
System.out.println("Plate constructor");
}
}
class DinnerPlate extends Plate{
DinnerPlate(int i) {
super(i);
System.out.println("DinnerPlate constructor");
}
}
class Utensil{
Utensil(int i){
System.out.println("Utensil constructor");
}
}
class Spoon extends Utensil {
Spoon(int i){
super(i);
System.out.println("Spoon constructor");
}
}
class Fork extends Utensil{
Fork(int i){
super(i);
System.out.println("Fork constructor");
}
}
class Knife extends Utensil{
Knife(int i){
super(i);
System.out.println("Knife constructor");
}
}
//A cultural way of doing something
class Custom{
Custom(int i){
System.out.println("Custom constructor");
}
}
public class PlaceSetting extends Custom{
Spoon sp;
Fork frk;
Knife kn;
DinnerPlate pl;
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
5.选择组合还是继承
谨慎使用继承
- 只有继承在所有方法中最有效时,才能考虑使用它。
- 子类是否要向上转型回到父类,若必须向上转型就使用继承。
- 时常问自己我真的需要向上转型吗
“属于”关系永继承来表达 “包含”关系有组合来表达
例:
//:Car.java
//Composition with public objects
class Engine{
public void start() {}
public void rev() {}
public void stop() {}
}
class Wheel{
public void inflate(int psi) {}
}
class Window{
public void rollup() {}
public void rolldown() {}
}
class Door{
public Window window=new Window();
public void open() {}
public void close() {}
}
public class Car {
public Engine engine=new Engine();
public Wheel[] wheel=new Wheel[4];
public Door left=new Door(),
rigth=new Door();
Car(){
for(int i=0;i<4;i++)
wheel[i]=new Wheel();
}
public static void main(String[] args) {
Car car=new Car();
car.left.window.rolldown();
car.wheel[0].inflate(72);
}
}
6.向上转型
继承并没有给新类提供方法,而是对子类和父类之间关系的表达;“子类属于父类的一种类型”
- 向上转型: 通过子类对象(小范围)实例化父类对象(大范围),属于自动转换。
- 向下转型: 通过父类对象(大范围)实例化子类对象(小范围),这种属于强制转换
例:一个父类Instrument(乐器),其中有一个子类 Wind(管乐器),继承意味父类中的所有方法都可以在子类中使用,若Instrument类有个paly()方法,则子类Wind类中也会有这个方法,这样我们可以 肯定的认为wind对象也是Instrument的一种类型 。
//:Wind.java
//Inheritance & upcasting
import java.util.*;
class Instrument{
public void paly() {}
static void tune(Instrument i) {
//.....
i.paly();
}
}
//Wind objects are instrument
//because they have the same interface:
class Wind extends Instrument {
public static void main(String[] args) {
Wind flute=new Wind(); //flute:长笛
//由于Wind类继承自Instrument类,所以Wind对象也是一个Instrument对象
Instrument.tune(flute); //upcasting
}
} ///:~
这里我们把一个Win句柄转换为Instrument的行为叫做:向上转型
7.什么是向上转型
graph BT
Wind子类-->Instrument父类
通常类继承图的画法是根部位于最顶端,在逐渐向下扩展。
由于是造型的方向是从子类到父类,箭头由下到上所以叫向上转型,即upcasting
8.final关键字
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- **被final修饰的变量不可变
由于应用的场景不一样,final关键的含义也会有一些差异
final修饰Data
final的3种初始化方式:
- 定义变量是直接初始
- 声明变量后在构造方法中初始化(在每个构造器中都要进行初始化)
- 声明变量后在构造代码块中初始化
常量:
- 编译期常量,它永远不会改变
- 在运行期初始化的一个值,我们不希望它发生变化
final修饰对象:
- final会把对象句柄变成“常量”,声明时必须将句柄初始化一个具体的对象
- 该句柄不能在指向另一个对象,而对象本身是可以修改的
/:FinalData.java
//The effect of final on fields
class Value{
int i=1;
}
public class FinalData {
//can be compile-time constants(可做为编译时的常量):
final int i1=9;
static final int I2=99;
//Typical public constant:
public static final int I3=39;
//Cannot be compile-time constants:
final int i4=(int)(Math.random()*20);
static final int i5=(int)(Math.random()*20);
Value v1=new Value();
final Value v2=new Value();
static final Value v3=new Value(); //
//Arrays:
final int[] a= {1,2,3,4,5,6};
public void print(String id) {
System.out.println(
id + ": " + "i4 = " + i4 +
", i5 = " + i5);
}
public static void main(String[] args) {
FinalData fd1 = new FinalData();
//! fd1.i1++; //Error:无法更改值
fd1.v2.i++;
//! fd1.v2=new Value();
fd1.v1 = new Value(); // oK--not final
for (int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn't constant
// ! fd1.v2 = new Value(); // Error: Can't
// ! fd1.v3 = new Value(); // change handle
// ! fd1.a = new int[3];
fd1.print("fd1");
System.out.println("Creating new FinalData");
FinalData fd2=new FinalData();
fd1.print("fd1");
fd2.print("fd2");
}
}
空白final(未在声明时初始化)
尽管被声明成 final,但却未得到一个初始值。无论在哪种情况下,空白 final 都必须在实际使用前得到正确的初始化。
空白final具有很大的灵活性
比如:想要一个final变量对每一个对象都不同,并且依然持有保持“不变”的特性。
//:BlankFinal.java
//"Blank" final data members
class Poppet{}
public class BlankFinal {
final int i=0; //Initialized final
final int j; //Blank final
final Poppet p; //Blank final handle
//Blank finals MUST be initialized
//in the constructor
BlankFinal(){
j=1; //initialized blank final
p=new Poppet(); //initialized blank handle
System.out.println(j);
}
BlankFinal(int x){
j=x; //initialized blank final
p=new Poppet();
System.out.println(j);
}
public static void main(String args[]) {
BlankFinal df=new BlankFinal();
}
}
final修饰参数
- 这意味这在一个方法的内部,我们不能改变参数句柄所指向的东西;
- 只能读取final修饰的参数;
//: FinalArguments.java
// Using "final" with method arguments
class Gizmo{
public void spin() {}
}
public class FinalArguments {
void with(final Gizmo g) {
//! g=new Gizmo(); //Illegal--g is final
g.spin();
}
void wihtout(Gizmo g) {
g=new Gizmo(); //OK--g not final
g.spin();
}
void f(final int i) {
// i++;
}
// You can only read from a final primitive
int g(final int i) {
return i+1;
}
public static void main(String[] args) {
FinalArguments bf=new FinalArguments();
bf.wihtout(null);
bf.with(null);
}
}
final修饰方法
- final修饰的方法不能子类被改写或覆盖
- final修饰的方法可以提升性能。不过通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
- private方法都默认是final,也可为private方法添加final,但不会有额外的效果
final修饰类
final类不允许被继承。
当我们不想类做出任何改变,或出于安全方面的理由,不希望类被继承时可用final关键字
注意:
- final类下的所有方法的是final的,与单个方法声明为final是相同的。
- 可以为final下的方法添加final关键字,但那样做是没意义的
//Jurssic.java
//Making an entire class final
class SmallBrain {}
final class Dinosaur{
int i=7;
int j=1;
SmallBrain x =new SmallBrain();
void f() {}
}
//!class Further extends Dinosaur{} //Error:Cannot extend final class 'Dinosaur'
public class Jurassic {
public static void main(String[] args) {
Dinosaur n=new Dinosaur();
n.f();
n.i++;
n.j++;
}
}
9.初始化和类载入
通常,我们可认为除非那个类的一个对象构造完毕,
否则代码不会真的载入。由于 static 方法存在一些细微的歧义,所以也能认为“类代码在首次使用的时候载入”。
首次使用的地方也是static初始化发生的地方。装载的时候,所有static 对象和 static 代码块都会按照本
来的顺序初始化(亦即它们在类定义代码里写入的顺序)。当然,static 数据只会初始化一次。
继承初始化
我们有必要对整个初始化过程有所认识,其中包括继承,对这个过程中发生的事情有一个整体性的概念。请
观察下述代码:
//Beetle.java
//The full process of initialization
class Insect {
static int x1 = prt("Static Insect.x1 initialized");
int i = 9;
int j;
Insect() {
prt("i=" + i + ",j=" + j);
j = 39;
}
static int prt(String a) {
System.out.println(a);
return 47;
}
}
public class Beetle extends Insect {
int k = prt("Beetle.k initialized");
Beetle() {
prt("k=" + k);
prt("j=" + j);
}
static int x2=prt("Static Beetle.x2 initialized");
static int prt(String a) {
System.out.println(a);
return 63;
}
public static void main(String[] args) {
prt("Beetle constructor");
Beetle beetle=new Beetle();
}
}
out:
Static Insect.x1 initialized
Static Beetle.x2 initialized
Beetle constructor
i=9,j=0
Beetle.k initialized
k=63
j=39
- 首先装载外部类(父类) ps如果java程序需要时
- 初始化父类Static 再是子类 static 依次类推
- 此时必要的类已装载完成,所以可以创建对象
- 这个对象中的所有基本数据类型都会设为默认值,而将对象句柄设为null
- 随后会调用父类构造方法(一般是自动调用的,也可以用super来自行指定构造函数)
- 父类构造方法完成以后,实例变量会按原来的顺序得以初始化
- 最后执行构造方法剩余的主体部分