复用类两种方式:
第一种非常直观:只需要在新的类中产生现有类的对象,由于新的类是由现有类的对象所组成,所以这种方法称为组合
第二种方法则更细致一些:它按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新的代码,这种神奇的方式被称为继承
组合语法
- 只需将对象引用置于新类中即可
- 类中域为基本类型时能够自动被初始化为0,但是对象引用会被初始化为null,编译器并不是简单的为每一个引用都创建默认对象,这一点是很有意义的,因为若真要那样做的话,就会在许多情况下增加不必要的负担,如果想要初始化这些引用,可以在代码中的下列位置进行
①:在定义对象的地方,这意味着它们总是能够在构造器被调用之前被初始化
②:在类的构造器中
③:就在正在使用这些对象之前,这种方式被称为惰性初始化.在生成对象不值得及不必要每次都生成对象的情况下,这种方式可以减少额外的负担.
④:使用实例初始化
class Soap {
private String s;
public Soap() {
System.out.println("Soap()");
s="Constructed";
}
@Override
public String toString() {
return s;
}
}
public class Bath {
private String
s1 = "Happy",
s2 = "Happy",
s3, s4;
private Soap castille;
private int i;
private float toy;
public Bath() {
System.out.println("Inside Bath()");
s3 = "Joy";
toy = 3.14F;
castille = new Soap();
}
{
i = 47;
}
public String toString() {
if (s4 == null)
s4 = "Joy";
return
"s1 =" + s1 + "\n" +
"s2=" + s2 + "\n" +
"s3=" + s3 + "\n" +
"s4=" + s4 + "\n" +
"i=" + i + "\n" +
"toy=" + toy + "\n" +
"castille=" + castille;
}
public static void main(String []args){
Bath b = new Bath();
System.out.println(b);
}
}
继承语法
- 当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式的从java标准根类object进行继承
- 可以把继承看做是对类的复用
- java使用super()关键字表示超类(父类)的意思,当前类就是从超类继承来的.
- 对基类子对象的正确初始化也是至关重要的,而且也仅有一种方式来保证这一点:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力.java会自动在导出类的构造器中插入对基类构造器的调用.
①:初始化无参构造器:无参编译器可以轻松的调用
②:带参数的构造器:必须用super()显示地编写调用基类构造器的语句,并且配以适当的参数列表
③:调用基类构造器必须是你在导出类构造器中要做的第一件事.
代理
- 代理:java并没有提供对它的直接支持.这是继承与组合之间的中庸之道.因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承).
public class SpaceShipControls {
void up(int velocity){}
void down(int velocity){}
void left(int velocity){}
void right(int velocity){}
void back(int velocity){}
void forward(int velocity){}
void turboBoost(){}
}
public class SpaceShipDelegation {
private String name;
private SpaceShipControls controls = new SpaceShipControls();
public SpaceShipDelegation(String name){
this.name=name;
}
public void forward(int velocity){
//代理
controls.forward(velocity);
}
//最好实现SpaceShipControls的所有方法
public static void main(String[] args) {
SpaceShipDelegation protector = new SpaceShipDelegation("NEW");
protector.forward(100);
}
}
代理与继承比较的好处:继承会暴露父类的所有的方法给子类,不可控.但是代理我们可以拥有更多的控制力,我们可以选择只提供在成员对象中的方法的某个子集.
结合使用组合与继承
- 同时使用组合和继承是很常见的事
确保正确清理
- 最好的清理办法是除了内存以外,不能依赖垃圾回收器去做任何事情.如果需要进行清理,最好是编写你自己的清理方法,但是不要使用finalize().
名称屏蔽
- 如果java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本,因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作
public class Homer {
void doh(String args){
System.out.println("String");
}
void doh(int args){
System.out.println("int");
}
}
public class Milhouse {}
public class Bart extends Homer{
void doh(Milhouse m){
System.out.println("Milhouse");
}
}
public class Hide {
public static void main(String[] args) {
Bart b = new Bart();
b.doh("s");
b.doh(new Milhouse());
b.doh(1);
//String-->Milhouse-->int
}
}
注意假如参数接收有double 和float的时候传1.0 默认是进入double方法,所以这时候需要使用类型标识例如:1.0F与1.0D
java SE5新增了@Override注解,它并不是关键字,但是可以把它当做关键字.当你想要重写(覆盖)某个方法时,可以选择添加这个注解,在你不留心重载而并非重写(覆盖)了该方法时,编译器会生成一条错误信息.这样@Override注解可以防止你在不想重载时而意外地写成了重载.
在组合与继承之间选择
- 组合和继承都允许在新的类中放置子对象,组合是显式的做,而继承是隐式的做.
- 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形.即,在新类中嵌入某个对象,让其实现所需要的功能,但是新类的用户看到的只是新类所定义的接口,而非所嵌入对象的接口,为取得此效果,需要在新类中嵌入一个现有类的private对象.有时,允许类的用户直接访问新类中的组合成分是极具意义的;也就是说将成员对象声明为public.如果成员对象自身隐藏了具体实现,那么这种做法是安全的.
- 在继承的时候,使用某个现有的类,并开发一个它的特殊版本.通常,这意味着你在使用一个通用类,并为了某些特殊需要而将其特殊化.
- is-a是一个的关系是用继承来表达,而has-a有一个的关系则是用组合来表达的.
- 到底该使用组合还是继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型
protected关键字
- 它指明了就类而言,这是private的,紧靠关键字private就已经足够了.但是在实际项目中,经常会想将某些事物尽可能对这个世界隐藏起来,但仍然允许导出类的成员访问它们.关键字protected就是起这个作用的.它指明就类用户而言,这是private的,但是对于任何继承与此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的.
向上转型
- “为新的类提供方法”并不是继承技术中最重要的方面,其最重要的的方面是用来表现 新类和基类之间的关系.这种关系可以用”新类是现有类的一种类型”这句话加以概括.
- 由于继承可以确保基类中所有的方法在导出类中同样有效,所以能够向基类发送的所有信息同样也可以向导出类发送
- 将子类引用转换为父类引用的动作,我们称之为向上转型.由于向上转型是从一个较专业的类型向通用类型转换,所以总是安全的,也就是说,导出类是基类的一个超集.它可能比基类含有更多的方法,但它必须至少具备基类中所有的方法.在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法,而不是获取它们.这就是为什么编译器在”未曾明确表示转型”或”未曾指定特殊标记”的情况下,仍然允许向上转型的原因.
final关键字
- final数据:有时数据的恒定不变是很有用的,比如:
①:一个永不改变的编译时常量.
②:一个在运行时被初始化的值,而你不希望它被改变
一个既是static又是final的域只占据一段不能改变的存储空间. - 编译时常量和运行时常量
我们用final来修饰以上两种数据来保持它们恒定不变,其中,对于编译时常量,编译器可以在编译时将该常量值代入任何需要它的地方执行计算公式,减轻了一些运行时的负担,在java中,编译时常量必须是基本数据类型,尽管经常看到诸如pubic static final String VAR = “var”这样的代码,但是,这个“VAR”并不是一个编译时常量,也许很多人碰到这样的面试题都会做错。
当final用于对象引用而不是基本类型时,表示的是对象的引用恒定不变,对象一旦被初始化后,虽然对象自身可以随意修改,但是这个对象的引用无法再指向另一个对象。
注意:以上关于final用于对象引用的情况同时也使用于数组,因为数组也是对象。 - 编译时常量与运行时常量的区别?
①:编译时常量并不依赖于类,而运行时常量依赖类
②:编译时常量在编译时确定值,而运行时常量在编译时是不可能确定值的
③:由于编译时常量不依赖类,所以对编译时常量的访问不会引发类的初始化.
④:静态块的执行在运行时常量之前,在编译常量之后
⑤:我们不能因为某数据是final的就认为在编译时可以知道它的值.因为运行时常量是在运行时候生成的值
class Constant {
static{
System.out.println("class has been loaded...");
}
public static final int A = 47;
public static final int B = "HelloWorld".length();
}
public class ConstantDiff {
public static void main(String[] args) {
System.out.println(Constant.A);
System.out.println(Constant.B);
}
}
//47
//class has been loaded...
//10
- final与 static final区别?
VALUE_TWO不变,这是因为它是static的,在装载时已被初始化,而不是每次创建新对象时都被初始化.
public class FinalData {
private static Random rand = new Random(47);
private String id;
public FinalData(String id) {
this.id = id;
}
private final int valueOne = rand.nextInt(20);
private static final int VALUE_TWO = rand.nextInt(20);
public String toString() {
return "valueOne=" + valueOne + ",VALUE_TWO=" + VALUE_TWO;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
FinalData fd2 = new FinalData("fd2");
System.out.println(fd1);//valueOne=15,VALUE_TWO=18
System.out.println(fd2);// valueOne=13,VALUE_TWO=18
}
}
- 空白final
java允许使用空白final,所谓空白final是指被声明为final但又未给定初始值得域,无论什么情况编译器都确保空白final在使用前必须初始化
public class Value {
private int i;
public Value(int i) {
this.i = i;
}
}
public class FinalData {
private final int i = 0;
private final int j;
private static final int er;
private final Value p;
static {
er=45;
}
public FinalData() {
j = 1;
p = new Value(1);
}
public FinalData(int x) {
this.j = x;
p = new Value(x);
}
public static void main(String[] args) {
new FinalData();
new FinalData(47);
}
}
必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总被初始化的原因所在
- final参数
java允许在参数列表中以声明的方式将参数指明为final.这意味着你无法再方法中更改参数引用所指向的对象.
//这是运算并没有改变i的值
static void g (final int i){System.out.println(i+1);}
//final不能重新复制
static void f(final int a){ a++;}
public static void main(String[] args) {
f(10);
g(10);
}
- final方法:把方法锁定,以防止任何继承类修改它的含义.这是出于设计的考虑:想要确保在继承中使方法行为保持不变,并且不会覆盖.
final与private关键字
①:类中所有的private方法都隐式地指定为final的,由于无法取出private方法,所以也就无法覆盖它.可以对private方法添加final修饰词,但这并不能给方法增加任何额外意义.
②:当试图覆盖一个private方法(隐含是final的),似乎是奏效的,因为编译器不会给出错误信息.”覆盖”只有在方法是基类接口的一部分时才会出现.即,必须将一个对象向上转型为它的基本类型并调用相同的方法.如果方法为private,它就不是基类接口的一部分.它仅是一些隐藏于类中的程序代码,只不过是具有相同的名字而已(只适用于private).此时你并没有覆盖方法,仅是生成了一个新的方法final类:当将某个类定义成final的时,就表明了不打算继承该类.final类不允许被继承.,final类中所有的方法都隐式指定为final的,因为无法覆盖它们
- 初始化及类加载:类的代码在初次使用时才加载,这通常是指加载发生于创建类的第一个对象的时候,但是当访问static域或static方法时,也会发生加载(构造器也是static方法,尽管static关键字并没有显示地写出来,因此更加准确的讲,类是在其任何static成员被访问时加载的)