类的复用有两种:
第一种很直接,叫组合,composition,也就是创建一个类的对象并使用。has-a
第二种叫继承:他是面向对象的基石之一。is-a
The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition.
继承是is-a的关系,比如基类是车,那么子类是卡车,或者轿车。
组合是has-a的关系,比如基类是车,那么里面需要有轮子,还有窗户等对象。
The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition.
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = "Constructed";
}
public String toString() {
return s;
}
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
private WaterSource source = new WaterSource();
private int i;
private float f;
public String toString() {
return "valve1 = " + valve1 + " " + "valve2 = " + valve2 + " "
+ "valve3 = " + valve3 + " " + "valve4 = " + valve4 + "\n"
+ "i = " + i + " " + "f = " + f + " " + "source = " + source;//会调用这个对象中的toString 方法
}
public static void main(String[] args) {
SprinklerSystem sprinklerSystem = new SprinklerSystem();
System.out.println(sprinklerSystem);//调用这个对象中的toString方法
}
} /*
* Output:
* WaterSource()
* valve1 = null valve2 = null valve3 = null valve4 = null
* i = 0 f = 0.0 source = Constructed
*/// :
java中所有的类都继承Object
类,所有的类都继承toString()
方法
When you do this, you automatically get all the fields and methods in the base class.当你继承的时候你会自动获得所有的父类fields和method,而成为子类的一部分
如果从其他的包继承类则这个类中的方法都应该是public的,所以为了允许继承,一个通常的规则是:让所有的成员变量都用private所有方法都用public修饰,当然一些特殊情况下可以考虑使用protected修饰符。但是通常遵循上面的规则。
se methods if there were some other package were to inherit from Cleanser, it could access only public members. So to allow for inheritance, as a general rule make all fields private and all methods public.
class Cleanser {
private String s = "Cleanser";
public void append(String a) {
s += a;
}
public void dilute() {
append(" dilute()");
}
public void apply() {
append(" apply()");
}
public void scrub() {
append(" scrub()");
}
//通过该方法返回类中的private变量
public String toString() {
return s;
}
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute();
x.apply();
x.scrub();
print(x);
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() {
append(" foam()");
}
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();//继承的父类方法
x.apply();
x.scrub();//重写父类方法
x.foam();//添加的子类方法
print(x);//继承父类的toString方法,不可以直接使用private变量,但是可以通过public方法来获取private变量
print("Testing base class:");
Cleanser.main(args);//调用父类main方法传入参数args
}
} /* Output:
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
*/// :~
当创建子类对象的时候的时候就好像创建了他的一个父类对象。
子类创建对象时会自动调用父类的构造方法,即使你没调用,java会自动在子类构造器中插入super()调用语句来调用。
//基类
class Art {
Art() {
print("Art constructor");
}
}
//子类继承Art
class Drawing extends Art {
Drawing() {
print("Drawing Constructor");
}
}
//子类继承Drawing
public class Cartoon extends Drawing {
public Cartoon() {
print("Cartoon Constructor");
}
public static void main(String[] args) {
Cartoon cartoon = new Cartoon();//创建对象
}
}/*Output:
Art constructor
Drawing Constructor
Cartoon Constructor
*/// :~
当父类的构造方法没有参数会默认直接调用,但是当父类构造方法有参数的时候,需要在子类构造方法中显示的用super来调用父类构造方法才可以正确初始化,而且是子类构造器的第一行,不然编译器会报错。
class Game {
Game(int i) {
print("Game Constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
print("BoardGame Constructor");
}
}
public class Chess extends BoardGame {
public Chess() {
super(11);
print("Chess Constructor");
}
public static void main(String[] args) {
Chess chess = new Chess();
}
}/* Output
Game Constructor
BoardGame Constructor
Chess Constructor
*///:~
子类可以重写父类的方法,相当于新的方法。还是会继承父类所有的方法。
为了容易辨别是重写的方法,子类重写的方法建议都加上@Override
说明这个方法是继承来重写的,重写的方法参数类别需要完全相同,返回值类型需要相同或者小于父类的类型。 使用重写操作符更明确的确认子类是重写了父类的某个方法。
重载的方法参数列表不同,返回值类型随意。因为他相当于新的方法,只是名字一样。
class Homer {
char doh(char c) {
print("doh(char)");
return 'd';
}
float doh(float f) {
return 1.0f;
}
}
class Bart extends Homer {
//@Override 如果这里写override则会报错,因为这个是重载方法而不是重写方法,不是继承来的
void doh(Milhouse m) {
print("doh(Milhouse)");
}
@Override
float doh(float a) {
print("doh(float)");
return 1.0f;
}
}
class Milhouse {
}
public class Hide {
public static void main(String[] args) {
Bart b = new Bart();
b.doh(1);//自己的方法
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
}/* Output
doh(float)
doh(char)
doh(float)
doh(Milhouse)
*/
继承最重要的部分不仅仅是提供给子类方法,而是说明子类和父类的关系,子类是父类的一种类型
The most important aspect of inheritance is not that it provides methods for the new class. It’s the relationship expressed between the new class and the base class. This relationship can be summarized by saying, “The new class is a type of the existing class.”。
//基类
class Instrument {
public void play() {
}
static void tune(Instrument i) {//需要Instrument类型
// ...
i.play();
}
}
//子类
// Wind objects are instruments because they have the same interface:
public class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();//创建子类对象
Instrument.tune(flute);//这里需要的是Instrument类型,而flute就是他的子类所以也属于该类型,这个就是向上转型
}
}
子类类型转换成父类类型就是向上转型。因为子类属于父类的一种类型。
向上转型都是安全的,因为子类向上转型成为父类会缩小他的范围所以安全,如果扩大范围就是向下转型,就不是安全的了,具体会在类型信息的章节提到。
当你决定使用继承还是组合的时候,你需要考虑的就是你是否需要向上转型,这点很重要。如果需要向上转型就使用继承,其他情况尽量使用组合。
用final是说明这个变量不能被改变
final用在三个地方:修饰数据,修饰方法,修饰类
我们经常需要用到常量,这里我们就用final来表示。如果还加上了static修饰,则此常量只被创建一次。
A field that is both static and final has only one piece of storage that cannot be changed.一个filed如果被static 和final修饰则他会被分配一个内存而不会被改变。并且变量名称字母全部大写且用下划线分割.
final修饰的引用不能被分配新的对象,但是可以修改该对象中的成员,比如数组中的元素。
final修饰基本类型不加static是在该对象中不可改变,而加上static修饰则是在创建该类的时候就创建了且不可改变的常量。
final修饰方法 该方法不能被子类重写,所有private修饰的方法都默认是final的。(慎用)
final 修饰形参,则在方法中传入形参为只读不能赋值了。
final 修饰类 表示此类不能被其他类继承。你想保证这个类的安全,不想让他有子类。
class Gizmo {
public void spin() {
}
}
public class FinalArguments {
void with(final Gizmo g) {
// ! g =new Gizmo(); // llegal -- g is final
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}
// void f(final int i){ i++; } // Can't change
// You can only read from a final primitive:
int g(final int i) {
return i + 1;
}
}
初始化顺序:静态(会在类加载的时候初始化)-> 然后创建对象时(父类成员和构造)->子类成员和构造
加上继承:
父类静态变量->子类静态变量->父类变量->父类构造器->子类变量->子类构造器)
class Parent {
/* 静态变量 */
public static String p_StaticField = "父类--静态变量";
/* 变量 */
public String p_Field = "父类--变量";
protected int i = 9;
protected int j = 0;
/* 静态初始化块 */
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
/* 初始化块 */ {
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
/* 构造器 */
public Parent() {
System.out.println("父类--构造器");
System.out.println("i=" + i + ", j=" + j);
j = 20;
}
}
public class SubClass extends Parent {
/* 静态变量 */
public static String s_StaticField = "子类--静态变量";
/* 变量 */
public String s_Field = "子类--变量";
/* 静态初始化块 */
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
/* 初始化块 */ {
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
/* 构造器 */
public SubClass() {
System.out.println("子类--构造器");
System.out.println("i=" + i + ",j=" + j);
}
/* 程序入口 */
public static void main(String[] args) {
System.out.println("子类main方法入口");//因为这个main是在子类里所以当加载这个类的时候就会加载静态变量
System.out.println("类加载结束,开始创建对象");
SubClass subClass = new SubClass();
}
}
/*
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
子类main方法入口
类加载结束,开始创建对象
父类--变量
父类--初始化块
父类--构造器
i=9, j=0
子类--变量
子类--初始化块
子类--构造器
i=9,j=20
*/