可以认为,人们所能解决的问题的复杂性直接取决于抽象的类型与质量.”命令式语言”所作的主要抽象仍要求在解决问题时要基于计算机结构,而不是基于所要解决的问题的结构来考虑.程序员必须建立起在机器模型(解空间内,你对问题建模的地方,如计算机) AND 实际待解决问题的模型(位于解空间内,这是问题存在的地方,例如计算机) 之间的关联.
AlanKay总结的, Smalltalk的五个特性(表现一种纯粹面向对象设计方式):
1) 万物皆对象.
2) 程序是对象的集合,它们通过发送消息来告诉彼此所要做的.
3) 每个对象有自己的有其他对象所构建的存储.
4) 每个对象都拥有其类型.
5) 某一特定类型的所有对象都可以接收同样的信息.
Booch对对象提出:对象具有状态,行为与标识.这意味着每个对象都:
w 可以拥有内部数据(它们给出了该对象的状态)
w 方法(它们产生行为)
w 每一个对象中都存有一个唯一的地址.
程序本身将向用户提供服务,它将通过调用其他对象提供的服务来实现这一目的.你的目标就是去创建(或者最好在现有代码库中寻找)能提供理想的服务来解决这一问题的一系列对象.
高内聚是软件设计的基本质量要求之一:意味着一个软件构件(对象,方法,对象库)
的各个方面”组合”得很好.在良好面向对象设计中,每个对象都可以很好的完成一项任务,但是他并不想做更多的事.
新的类可以有任意数量,类型的其他对象以任意可以实现新的类中想要功能的方式所组成.组合:在现有得类中合成新的类.聚合:组合是动态发生的,常常用has-a关系
异常是一种对象,它从错误点被”抛出”,并被专门设计用来处理特定类型错误的相应的异常处理器”捕获”.异常处理是唯一可接受的错误报告方式,若没有编写正确的处理异常的代码,那么就会得到一条编译时的出错信息.
在同一时刻处理多个任务的思想.在语言级别上,多线程的便利之一便是:程序员不用再担心机器上是有多个处理器还是只有一个处理器.但也有隐患:共享资源.
尽管一切看作对象,但操纵的标识符实际上是对对象的一个引用(reference)
New关键字二等意思是”给我一个新对象 String s = new String(“asdf”);
他不仅表示”给我一个新字符”,而且通过提供一个初始字符,给出了怎样产生的信息.
”=”意思是:”取右边的值,赋给左边”.
右值可以是任何常数,变量,表达式(只要能生成一个值就行).
左边是一个明确的,已命名的变量.
对基本类型赋值:基本类型存储了实际值,而并非指向一个对象的引用,故其赋值时,是直接将一个地方的内容复制到了另一个地方.
给对象赋值: 对一个对象操作时,实际上操作的是它的引用,倘若”将一个对象赋值给另一
个对象”,实际上是将”引用”从一个地方复制到另一个地方.
++a :先执行运算,再生成值
a--:先生成值:再执行运算
窄化转换:将能容纳更多信息的数据类型转换成无法容纳那么多信息的类型.(longàint),可能面临信息丢失的危险,此时编译器会强制我们进行类型转换,相当于说:”这是一件危险的事情,如果无论如何要做,请显式进行类型转换.”
扩展转化:将容纳少的信息数据类型转换为可以容纳更多信息的数据类型.不必显式进行类型转换.因为新的类型肯定可以容纳原来类型的信息.
提升:基本数据类型执行算术运算或按位运算,只要类型比int小(char,byte,short),运算前会自动转换为int,这样,最终的结果就是int类型.通常,表达式中出现的最大数据类型决定了最终结果类型.
指定一个方法返回什么值(假设它没有void返回值)
Return关键词作用:它会导致当前方法退出
Break作用:强行退出循环,不执行循环中剩余语句.
Continue作用:停止执行当前迭代,然后退回循环起始处,开始下一次迭代.
标签 label: 唯一使用的地方是在迭代语句之前.如continue label1同时中断内部迭代以及外部迭代,直接跳转到label处
如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升.
如果传入的实际参数较大,就通过类型转换来执行窄化处理.如果不这样做,编译器会报错.
This关键字只能在方法内部使用,表示对”调用方法的那个对象”的引用.注意:在方法内部调用同一个类的另一个方法,可以不必使用this.只有当需要明确指出当前对象的引用时,才需要this.
无法阻止自动初始化的进行,它将在构造器被调用前发生.下面的i首先会被置于0,然后成7.
Public class Counter{
Int I;
Counter(){i=7;}
}
类的内部,变量定义的先后顺序决定了初始化顺序.静态初始化只有在必要时刻才进行.顺序是先静态,后非静态.总结一下对象的创建过程,假设有一个Dog类:
1) 即使没有显示使用static关键字,构造器实际上也是静态方法,因此,当首次创建为Dog的对象时(构造器可看作静态方法),或者Dog的静态方法/静态域首次被访问时,java解释器必须查找类路径,以定位Dog.class文件
2) 然后载入Dog.class文件,有关静态初始化的所有动作都会执行.因此,静态初始化只在Class对象首次加载时进行一次.
3) 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的空间
4) 这块存储空间会被清零,这就自动地Dog对象中的所有基本数据都设置成立默认值(对数字来说就是0,对布尔和字符类型也相同),而引用则被设置成了null.
5) 执行所有出现于字段定义处的初始化动作.
6) 执行构造器,第七章还会讲,因为涉及到继承.
面向对象设计中需要考虑的一个问题:”如何把变动的事物与保持不变的事物区分开来”.
当编写一个java源文件代码文件时,此文件通常被称为编译单元(有时也叫转译单元).每个编译单元都必须有一个后缀名.java.每个编译单元只能有一个public类,否则编译器不接受.如果该编译单元中还有额外的类的话,那么在包之外的世界是无法看见的,这是由于他们不是public类.
当编译一个.java文件时,在.java文件中的每个类都会有一个输出文件,而该文件的名称与.java文件中每个类的名称相同,只是多了一个后缀.class.因此,在编译少量.java文件后,会得到大量的.class文件.
类库其实就是一组类文件.其中每个文件都有一个public类,以及任意数量的非public类.因此每个文件都有一个构件(每个都有他自己的独立.java和.class文件)从属于同一个群组,就可以使用关键字package.
Java解释器的运行过程如下:
1. 找出环境变量CLASSPATH.CLASSPATH包含一个或多个目录,用作查找.class文件的根目录.
2. 从根目录开始,解释器获取包名称并将每个句点替换成反斜杠,以从CLASSPATH根中产生一个路径名称.
3. 得到的路径会与CLASSPATH中的各个不同的项相连接,解释器就在这些目录中查找与你所要创建的类名称相关的.class文件.
包访问权限:默认访问权限没有任何关键词,通常指包访问权限.其意味着当期的包中的所有其他类对哪个成员都有访问权限,但对于这个包之外的所有类,这个成员却是private.
Public:接口访问权限:其意味着public之后紧跟着的成员声明自己对每个都是可用的.
Private:其意思是,除了包含该成员的类以外,其他任何类都无法访问这个成员.
Protected:有时,基类的创建者会希望有某个特定成员,把它的访问权限赋予派生类而不是所有类.
仅有两个选择:包访问权限和public.如果不希望其他任何人对该类有访问权限,可以把所有的构造器都指定为private,从而阻止任何人创建该类对象.但是,在你该类的static成员内部可以创建.
class Soup1 {
private Soup1() {}
// 1 Allow creation via static method;
public static Soup1 makeSoup() {
return new Soup1();
}
}
class Soup2 {
private Soup2() {}
// 2 Create a static object and returna refrence
// upon request.(the"singleton" pattern);
private static Soup2 ps1= new Soup2();
public static Soup2 access() {
return ps1;
}
public void f() {}
}
public class Lunch {
// can't do this!Private constructor:Soup1 soup=new Soup1();
void testStatic() {
Soup1 soup =Soup1.makeSoup();
}
void testSingleton() {
Soup2.access().f();
}
}
如果把构造器指定为private,那么谁也无法创建该类对象了,那么如何使用这个类?两种选择?
1. 在Soup1中,创建一个static方法,它创建一个新的Soup1对象并返回对它的引用.
2. Soup2是单例模式(singleton),这是由于你始终只能创建它的一个对象,Soup2类的对象是作为Soup2的一个static private成员而创建的.
控制对成员的访问权限有两个原因:
1. 为了使用户不要碰触到那些他们不该碰触的部分.
2. 为了让类库设计者可以更改类的内部工作方式,而不必担心这样对客户端程序员产生重大影响.
注意:访问权限控制专注于类库创建者和该类库的外部使用者之间的关系,这中关系关系也是一种通信方式.
Java中所有问题的解决都是围绕类展开的,可以通过创建新类来复用代码,不用重头编写.可以使用别人业已开发并调试好的类.
此方法窍门在于使用类而不破坏现有程序代码.本章有两种方式达成这一目的.
1. 在新的类中产生现有类的对象.由于新的类是由现有类的对象所组成,所以这种方式成为组合.该方式复用了现有程序代码的功能,而非它的形式.
2. 按照现有类的类型创建新类,.无需改变现有类的形式,采用现有类的形式并在其中添加新代码.这叫做继承.
继承并不只是复制类的接口,当创建一个导出类的对象时,该对象包含了一个基类对象的子对象.在实际中,构建过程是从基类”向外”扩散的,所以在导出构造器可以访问它之前,就已经完成了初始化.
如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显示地编写调用基类构造器的语句.
class Creature {
Creature(int i) {System.out.println("We hava create " + i + "creature(s)");}
}
class Animal extends Creature {
Animal(int i) {
super(i);
System.out.println("We hava create " + i + "animal(s)");
}
}
public class Cat extends Animal {
public Cat() {
super(11);
System.out.println("cat constructor");
}
public static voidmain(String[] args) {
Cat cat = new Cat();
}
}/*Output:
We hava create 11creature(s)
We hava create 11animal(s)
cat constructor*/
看另一个例子(关于覆盖重写):
class transportation {
void start() {System.out.println("transportationis started");}
void stop() {System.out.println("transportationis stopped");}
void turnLeft() {System.out.println("transportationis turning left");}
}
public class Car extends transportation {
//use @Override to show 方法覆盖
@Override
void start() {System.out.println("Caris started");}
//当然也可以不用@Override
void stop() {}
//想要重载方法,要先覆盖,否则不能在子类对父类方法进行重载
String stop(int i) {return"stop";}
}
指明:”就类用户用而言,这是private的,但对于任何继承自此类的导出类和任何位于同一个包内的类来说,它却是可以访问的”(private提供了包访问权限)
继承技术中最重要的方面是用来表现新类和基类之间的关系.”新类是现有类的一种类型”.
向上转型:将子类引用转换为父类引用的动作.
到底该用组合还是继承:问问自己是否真的需要从新类向基类进行向上转型.
1. 一个永不改变的编译时常量.
2. 一个在运行时被初始化的值,而你不希望它改变.
对于编译期常量,编译器可以将该常量值代入任何可能用到它的计算式中,即是说可以在编译期执行计算式,减轻
了一些运行时负担.在java中,这类常量必须是基本数据类型,以final表示.在对其进行定义时,比对其进行赋值.
当对对象引用而不是基本类型运用final时,final使引用恒定不变.
把方法锁定,以防止任何继承类修改它的含义.想要确保在继承中使方法行为保持不变,并且不会被覆盖.
Final和private关键字
“覆盖”只有在某方法是基类的接口的一部分时才会出现,.即,必须将一个对象向上转型为它的基本类型并调用相同的方法(下一章阐明).若某方法为private,它就不是基类的接口的一部分,它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已.但如果在导出类中以相同的名称生成一个public,protect或包访问权限方法的话,该方法就不会产生在基类中出现的”仅具有相同名称”的情况. 此时你并没有覆盖该方法,仅是生成了一个新的方法,由于private方法无法触及而且能有效隐藏,所以除了把它看成是它所归属的类的组织结构的原因而存在外,其他任何事物都不用考虑它.
将类定义为final是,表明不打算继承该类,而且也不允许别人这么做.即,出于某种因素,你对该类的设计永不需要做任何改动,或出于安全考虑,你不希望他有子类.
Final类中所有方法都隐式为final的
每个类的编译代码都存在于它自己的独立文件中,该文件只在需要程序代码时才会被加载.一般可以说:”类的代码初次使用时才加载.”者通常是指加载发生于创建类的第一个对象之时,但是访问static域或static方法时,也会发生加载.
构造器也是static方法,尽管static关键字并没有显示写出来.因此准确来说:;类是在任何static成员被访问时加载的.
class Insect {
private int i = 9;
protected int j;
Insect() {
System.out.println("i=" + i+ ",j=" + j);
j = 39;}
private static intx1 = printInit("static Insect.x1 initialized");
static int printInit(String s){
System.out.println(s);
return 47;}
}
public class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle() {
System.out.println("k=" + k);
System.out.println("j=" + j);}
private static intx2 = printInit("static Beetle.x2 initialized");
public static voidmain(String[] args) {
System.out.println("Beetle constructor");
Beetleb = newBeetle();
}
}/*Output:
static Insect.x1initialized
static Beetle.x2initialized
Beetle constructor
i=9,j=0
Beetle.k initialized
k=47
j=39
*/
1. 在Beetle上运行java时,发生的第一件事就是试图访问Beetle.main()(一个static方法),于是加载器开始启动并找出Beelte类的编译代码(在名为Beetle.class的文件中),在对它进行加载时,编译器注意到他有个基类,于是转而先加载基类,即根基类中的static初始化.然后才是导出类的static初始化.
2. 类都已经加载完毕了,对象就可以被创建了首先,对象中所有基本类型都会被设成默认值,对象引用被设成null.然后,基类构造器会被调用.在基类构造器完成之后,实例化变量按其次序被初始化.最后,构造器的其余部分被执行.
在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征.
“封装”通过合并特征和行为来创建新的数据类型.”实现隐藏”则通过将细节”私有化”把接口和实现分离出来.
多态的作用是消除类型之间的耦合关系.多态方法调用允许一种类型表现出与其他相类似之间的区别,只要他们是从同一基类导出来的,这种区别是根据房行为的不同而表现出来的,虽然这些方法可以通过同一个基类来调用.
class Mouse extends Rodent {
void peep() {
System.out.println("Mouse is peeping");
}
}
class Gerbil extends Rodent {
void peep() {
System.out.println("Gerbil is peeping");
}
}
public class Rodent {
public static void test(Rodent r){
r.peep();}
void peep() {
System.out.println("Rodent is peeping");
}
public static voidpeepAll(Rodent[] rodents) {
for (Rodent rodent : rodents){
test(rodent);
}
}
public static voidmain(String[] args) {
Rodent[] rodents = { new Gerbil(), newRodent(), new Mouse() };
peepAll(rodents);
}
}
只有非private方法才可以被覆盖,但还是需要密切注意覆盖private方法的现象:这时虽然编译器不会报错,但也不会按照我们所期望的来执行.在导出类中,对于基类中的private方法,最好采用不同的名字.
只有普通的方法调用是可以多态的.在例子中,为super.field和Sub.field分配了不同的存储空间.这样,Sub实际上包含两个称为field的域:它自己和它从Super处得到的.然而,在引用Sub中的field时所产生的默认域并非Super版本的field域.因此,为了得到Super.field,必须显示地指明super.field.
复杂对象调用构造器要遵照下面顺序:
1) 调用基类构造器.此步骤会不断反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,直到父类.
2) 按声明顺序调用成员的初始化方法.\
3) 调用导出类构造器主体.
初始化的实际过程是:
1) 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零.
2) 如前所述地那样调用基类构造器.此时,调用备覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于1步骤的原因,我们此时会发现radius的值为0.
3) 按照声明的顺序调用成员的初始化方法.
4) 调用导出类的构造器主体.
class Glyph {
void draw() {
System.out.println("Glyph.draw");
}
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw");
}
}
class RoundGlyph extends Glyph{
private int radius= 1;
RoundGlyph(int r) {
radius = r;
System.out.println("RoundGlyph.RoundGlyphy,radius=" + radius);
}
}
public class PolyConstructors {
public static voidmain(String[]args) {
newRoundGlyph(5);
}
}/*Output:
Glyph() before draw()
Glyph.draw
Glyph() after draw
RoundGlyph.RoundGlyphy,radius=5
*/
class Useful {
public void f() {
}
public void g() {
}
}
class MoreUseful extends Useful{
public void f() {
}
public void g() {
}
public void u() {
}
public void v() {
}
}
public class RTTI {
public static voidmain(String[] args) {
Useful[] x = { new Useful(), newUseful() };
x[0].f();
((MoreUseful) x[1]).u();//报错java.lang.ClassCastException
}
}
抽象方法机制:仅有声明而没有方法体.
abstractvoid f();
包含抽象方法的类叫做抽象类.如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的(否则编译器报错)
抽象类和抽象方法非常有用,因为他们可以使类的的抽象性明确起来,并告诉用户和编译器打算怎样来使用他们.
abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应具体实现,这些实现是类的继承者创建的.
interface这个关键字产生一个完全抽象的类,他根本就没有提供任何具体实现,它允许创建者确定方法名,参数列表和返回类型,但是没有任何的方法体.接口只提供了形式,而未提供任何具体实现.
当要实现一个接口是,在接口中被定义的方法必须被定义为public的,否则,它们将只能得到默认的包权限,这样在方法被继承的过程中,其可访问权限就被降低.
策略设计模式:创建一个能够根据所传递的参数对象的不同而具有不同行为的方法.这类方法包含要执行算法中固定不变的部分,而”策略”包含变化的部分
使用接口的核心原因:
1) 为了能向上转型为多个基类型(以及由此带来的灵活性)
2) 防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口.
如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口接口而不是抽象类.
事实上,如果知道某事物应该成为一个基类,那么第一选择应该使他成为一个接口.
interface CanFight {void fight();}
interface CanSwim {void swim();}
interface CanFly {void fly();}
class ActionCharacter {public void fight() {} }
class Hero extendsActionCharacter implements CanFight,CanSwim, CanFly {
public void swim() {}
public void fly() {}
}
public class Advanture {
public static voidt(CanFight x) {x.fight();}
public static voidu(CanSwim x) {x.swim();}
public static voidv(CanFly x) {x.fly();}
public static voidw(ActionCharacter x) {x.fight();}
public static voidmain(String[] args) {
Hero h = newHero();
t(h);// treat it as a CanFight;
u(h);// treat it as a CanSwim;
v(h);// treat it as a CanFly;
w(h);// treat it as an ActionCharacter;
}
}
在Advanture类中,可以看到有四个方法把上述接口和具体类作为参数,当Hero对象被创建时,它可以被传递给这些方法中的任意一个,这意味着它依次被向上转型为每一个接口.
放入接口中的任何域都是public,static和final的,所以接口成为了一种很便捷的用来创建常量组的工具.
工厂方法.我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象.
1) 先创建一个基类产品的接口,然后创建基类产品的工厂类(工厂类要有生成的基类产品的方法).
2) 创建子类产品类(当然要实现基类产品的接口)
3) 创建各子类产品的工厂(当然要实现基类产品工厂),里面写子类方法
4) Public class中调用子类产品工厂,调用方法.
下面这个是工厂方法的一个例子:
interface Cycle {
void draw();
void ease();
}
interface CycleFactory {
Cycle getCycle();
}
class Unicycle implements Cycle{
@Override
public void draw() {
System.out.println("draw Unicycle");
}
@Override
public void ease() {
System.out.println("ease Unicycle");
}
}
class Unicyclefactory implementsCycleFactory {
@Override
public Cycle getCycle() {
return new Unicycle();
}
}
public class UseCycleFactory {
static void use(Unicyclefactory u) {
Cycle c = u.getCycle();
c.draw();
c.ease();
}
public static void main(String[] args) {
use(new Unicyclefactory());
}
}/*Output:
draw Unicycle
ease Unicycle
*/
可以将一个类的定义放到另一个类的定义内部,这就是内部类.
如果想从外部类的非静态方法之外(比如main方法中)的任意位置创建某个内部类的对象,呢么必须具体指明这个对象的类型:OuterClassName.InnerClassName.
内部类自动拥有对其外围类所有成员的访问权.
如果需要生成对外部类的引用,可以使用外部类的名字后面紧跟着.ths.这样产生的应用自动具有正确的类型.(编译期就被知晓并被检查
如果告知某些其他对象,去创建其某个内部类的对象.要实现此目的:在new表达式中提供其他外部类对象的引用.
DotNew dn= newDotNew();
DotNew.Innerdni=dn.new Inner();
在拥有外部类对象之前是不可能创建内部类对象的.这是由于内部类对象会暗暗连接到创建它的外部类对象上.但是如果创建的是嵌套类(内部静态类),它就不需要对外部类对象引用.
class OtherOutClass {
public void showSelf() {System.out.println("thisis an OtherOutClass");}
class InnerClass {
public void showSelf() {System.out.println("thisis an InnerClass");}
}
public InnerClassgetInnerClass() {return new InnerClass();}
}
public class OuterClass {
public static voidmain(String[] args) {
OtherOutClass ooc = new OtherOutClass();
OtherOutClass.InnerClass oocic= ooc.newInnerClass();// 通过外部类的一个对象创建一个内部类对象
oocic.showSelf();
// 通过外部类的一个可返回内部类对象的方法得到内部类
OtherOutClass.InnerClass oocic2= ooc.getInnerClass();
oocic2.showSelf();
}
}
Private在内部类中的访问情况
class OtherOutClass {
class InnerClass {
private int i = 11;
public int i1 = 12;
}
private class PrivateInnerClass {
private int pi = 11;
public int pi1 = 12;
}
}
public class OuterClass {
public static void main(String[] args) {
OtherOutClass ooc= new OtherOutClass();
OtherOutClass.InnerClass oocic = ooc.new InnerClass();
// System.out.println(oocic.i);另外一个类的内部类中的private元素不能获得
System.out.println(oocic.i1);//默认访问是可以获得
// System.out.println(oocic.pi);private内部类中的元素不能在另一个类中获得
//关于自己内部类:
OuterClass oc = new OuterClass();
OuterClass.ThisPrivateClass oct= oc.new ThisPrivateClass();
System.out.println(oct.tpi);//外部类可以访问自己private的内部类中的任意元素
}
private class ThisPrivateClass {
private int tpi = 11;
public int tpi1 = 12;
}
}
为啥要在一个方法里面或者任意作用域内定义内部类,
1) 实现了某类型的接口,于是可以创建并返回对其的引用.
2) 你要解决一个复杂问题,创一个类来辅助你的解决方案,担忧不希望这个类是公共可用的.
public class NoNameInnerClass {
classInnerNoNameInnerClass implements ball{
private int i = 11;
public int getInt() { returni; }
@Override
public void roll() {
System.out.println("InnerNoNameInnerClass is rolles");
}
}
public ball getBall() {
return new InnerNoNameInnerClass();
}
public static voidmain(String[] args) {
InnerNoNameInnerClass innic= (new NoNameInnerClass()).new InnerNoNameInnerClass();
innic.roll();
}
}
上面的代码可以用下面的匿名内部类实现:
public class NoNameInnerClass {
public Ball getBall() {
return new Ball() {
privateint i= 11;
publicint getInt() {
returni;
}
@Override
publicvoid roll() {
System.out.println("roll!");
}
};
}
public static voidmain(String[] args) {
Ball ball = new NoNameInnerClass().getBall();
ball.roll();
}
}
如果基类需要一个有参数的构造器:
public class Parcl8 {
public Wrapping wrapping(int x) {
return new Wrapping(x) {
public int value() {
return super.value() * 47;
}
};
}
public static void main(String[] args) {
Parcl8 p = new Parcl8();
System.out.println(p.wrapping(10).value());//Output:470
}
}
现在我们再来看看工厂模式:
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implementsService {
@Override
public void method1() {
System.out.println("Implementation1 method1");
}
@Override
public void method2() {
System.out.println("Implementation1 method2");
}
public static ServiceFactory factory= new ServiceFactory() {
@Override
public ServicegetService() {
returnnew Implementation1();
}
};
}
public class Factories {
public static voidserviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static voidmain(String[] args) {
serviceConsumer(Implementation1.factory);
}
}
现在用于Implementation1的构造器都可以是private的,并且没有任何必要去创建作为工厂的具体名类.另外,你经常只需要单一的工厂对象,因此在本例中它被创建为Service实现中的一个static域,
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为static.通常被称为嵌套类.普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象.当内部类是static的时,就不是这样了,嵌套类意味着:
1) 要创建嵌套类的对象,并不需要其外围类对象
2) 不能从嵌套类的对象中访问非静态外围类对象.
普通的内部类不能有static数据和字段,也不能包含嵌套类.,但是嵌套类可以包含.
在一个普通的(非static)内部类中,通过一个特殊的this引用可以链接到期外围类对象.嵌套类没有这个特殊this引
用,这使得它类似于一个static方法.
public class OuterStaticClass {
static class StaticInnerStaticClass {
private int i = 1;
public int getAndPrintInt() {
System.out.println(i);
returni;
}
}
public static StaticInnerStaticClassgetStaticInnerStaticClass() {
return new StaticInnerStaticClass();
}
public static voidmain(String[] args) {
getStaticInnerStaticClass().getAndPrintInt();
}
}//Output:1
内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象,所以可以认为内部类提供了某种进入外围类的窗口.内部类最吸引人的原因:
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响.
内部类允许继承多个非接口类型(类或抽象类)
class D{}
abstract class E {}
class Z extends D {
E makeE() {return new E() {};}
}
public class MultiImplementation {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
}
其还可以获得一些其他的特性:
1) 内部类可以有很多个示例,每个示例都有自己的状态信息,并且与其外围类对象的信息相互独立.
2) 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类.
3) 创建内部类的时刻并不依赖于外围类对象的创建.
4) 内部类并没有”is-a”关系;他就是一个独立的实体.
应用程序框架就是被设计用以解决某类特定问题的一个类或一组类.要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法.在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,已解决特定问题.(模板方法)设计模式总是将变化的事物与保持不变的事物分离开,在此模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物.
当继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化.这两内部类是完全独立的两个实体,各自在自己的名称空间里.当明确的继承某个内部类也是可以的:
class Egg2 {
protected class Yolk {
public Yolk(){System.out.println("Egg2.Yolk()");}
public void f() {System.out.println("Egg2.Yolk.f()");}
}
private Yolk y = newYolk();
public Egg2() {System.out.println("New Egg2()");}
public void insertYolk(Yolk yy){y = yy;}
public void g() {y.f();}
}
public class BigEgg2 extends Egg2 {
public class Yolk extendsEgg2.Yolk {
public Yolk(){System.out.println("BigEgg2.Yolk");}
public void f() {System.out.println("BigEgg2.Yolk.f()");}
}
public BigEgg2(){insertYolk(new Yolk());}
public static voidmain(String[] args) {
Egg2 e2=new BigEgg2();
e2.g();
}
}/* Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk
BigEgg2.Yolk.f()*/
可在代码块里面创建内部类,典型方式是在一个方法体里面创建.局部内部类不能有访问说明符(因为它不是外围类的一部分);但可以访问当前代码块内的常量,以及此外围类的所有成员.
既然局部内部类的名字在方法外是不见的,那我们为啥还要用局部内部类而不是匿名内部类呢?
1) 我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化.
2) 需要不止一个该内部类的对象.
Java容器类类库的用途是”保存对象”,并将其划分为两个不同的概念:
1) Collection.独立元素的序列.List可重复有顺序.Set不能重复无顺序.Queue按照排队规则来确定对象产生的顺序(与他插入的顺序相同)
2) Map.一组成对的”键值对”对象,允许用键来查找值.
Arrays.asList()方法接受一个数组或是一个用逗号分隔的元素列表(即可变参数),并将其转换为一个List对象.
Collections.addAll()方法接受一个Collection对象,以及一个数组或一个用逗号分割的列表,将元素添加到Collection.
Collection.addAll()成员方法只能接受另一个Collection对象作为参数,因此他不如Arrays.asList()或Collections.addAll()灵活,这两个有的都是可变参数列表.
迭代器(也是一种设计模式)是一个对象,他的工作是遍历并选择序列中的对象.此外迭代器通常被称为轻量级对象:创建它的代价小.Java的Iterator只能单向移动,这个Iterator这能用来:
1) 使用方法iterator()要求容器返回一个Iterator.Iterator将准备好返回序列的第一个元素.
2) 使用next()获得序列中的下一个元素.
3) 使用hasNext()检查序列中是否还有元素.
4) 使用remove()将迭代器新返回的元素删除.
Iterator的真正威力:能够将遍历序列的操作与序列的操作与底层的结构分离.正因有此,我们常说:迭代器统一了对
容器的访问方式.
public class IteratoerTest {
public static voidmain(String[] args) {
List
Iterator
int index = 1;
while (it.hasNext()) {
String st = it.next();
System.out.print(index + ":"+ st + " ");
index++;
}
}
}//Output: 1:a 2:b 3:c 4:d 5:e
也实现了List基本接口,但执行某些操作(在List的中间插入和移除)比ArrayList更高效,但在随机访问方面要逊色一些.LinkList还添加了可以用作队,栈,双端队列的方法.
通常指”后进先出的”(LIFO)的容器.有时也被称作叠加栈.
如果想在自己的代码中使用自己创建的Stack类,当创建其示例时,就需要完整指出包名,或者更改包名.
Set不保存重复的元素.Set中最常被使用的就是测试归属性,你可以很容易的询问某个对象是否在某个Set中.因此查找就是Set中最重要的操作.你通常会选择一个HashSet(使用了散列,TreeSet将元素存储在红-黑树数据结构中,LinkHashSet也是用了散列)的实现.Set是基于对象的值来确定归属性的.
public class ColorFruitHashMap {
public static voidmain(String[] args) {
Map
color_fruit.put("yellow", Arrays.asList("banana", "pear"));
color_fruit.put("red", Arrays.asList("redapple", "watermalon","straybeery"));
color_fruit.put("green", Arrays.asList("greenapple", "grapes"));
System.out.println(color_fruit);
Set
for (String color : colors){
System.out.println(color + color_fruit.get(color));
}
}
}/*Outout: {red=[redapple,watermalon, straybeery], green=[greenapple, grapes], yellow=[banana, pear]}
red[redapple, watermalon,straybeery]
green[greenapple, grapes]
yellow[banana, pear]/
队列是一个典型的先进先出(FIFO)的容器,即放入容器的顺序与取出容器的顺序是相同的.队列常被当做一种可靠的将对象从程序的一个区域传输到另一个区域的途径.在并发编程中很重要,因为其可以安全地将对象从一个任务传输给另一个任务.
LinkList提供了方法以支持队列的行为,用作Queue的一种实现.
offer()方法是与Queue的相关方法之一,它在允许的情况下,将一个元素插入到队尾,或者返回false.
peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常.
poll()和remove()方法将移除并返回队头,但poll()在队列为空时返回null.而remove()抛出NoSuchElementException异常.
先进先出描述了最典型的的队列规则.队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素规则.先进先出声明的是下一个元素应该是等待时间最长的元素.
优先级队列声明下一个弹出元素是最需要的元素(具有最高优先级).
当在PriorityQueue调用offer()方法来插入一个对象时,这个对象会在队列中被排序.默认的排序将使用的对象在队列中自然排序,但是你可以通过提供自己的Comoarator来修改这个顺序,PriorityQueue可以确保当你调用peek(),poll()和remove()方法时,获取的元素将是队列中优先级最高的元素.
public class PriorityQueueDemo {
public static voidmain(String[] args) {
PriorityQueue
Random rand = new Random(47);
for (int i = 0; i < 10; i++)
priorityQueue.offer(rand.nextInt(i+ 10));
System.out.println(priorityQueue);
String fact = "education should eschew obfuscation";
List
PriorityQueue
System.out.println(stringpq);
stringpq = new PriorityQueue
stringpq.addAll(strings);
System.out.println(stringpq);
Set
for (char character: fact.toCharArray())
charSet.add(character);// autoboxing;
PriorityQueue
System.out.println(characterpq);
}
}
Collection是描述所有序列容器的共根性接口,它可能会被认为是一个”附属接口”,即因为要表示其他若干个接口的共性而出现的接口.另外,java.util.AbstractCollection类提供了Collection的默认实现,使得你可以创建AbstractCollection的子类型,而其中没有不必要的代码重复.
使用接口描述的一个重要理由是他可以使我们能创建更通用的代码.针对接口而非具体实现来编写代码,我们的代码可以应用于更多的对象类型.因此,如果我编写的方法将接受一个Collection,那么该方法就可以应用于任何实现了Collection的类——这也就使得一个新类可以选择器实现Collection接口,以便我们的方法可以使用它.
在Java中,用迭代器而不是Collection来表示容器之间的共性.但是,这两种方法绑定到了一起,因为实现Collection就意味着需要提供iterator()方法.
到目前为止,foreach语法主要用于数组,但是它也可以应用于任何Collection对象.
public class ForEachCollections{
public static voidmain(String[] args) {
Collection
Collections.addAll(cs,"Take the long way home,I am reallytired".split(" "));
for (String s : cs) {
System.out.println("'" + s+ "'");
}
}
}
适配器方法.”适配器”部分来自于设计模式,因为你必须提供特定接口以满足foreach语句.当你有一个接口并需要另一个接口时,适配器就可以解决问题.这里,我希望在默认的前向迭代器的基础上,添加反向迭代器的能力,因此我不能使用覆盖,而是添加了一个能够产生Iterable对象的方法,该对象可以用于foreach语句.
class ReversibleArrayList
publicReversibleArrayList(Collection
super(c);
}
public Iterable
return new Iterable
publicIterator
returnnew Iterator
intcurrent = size() - 1;
publicboolean hasNext() {return current> -1;}
@Override
publicT next() {return get(current--);}
publicvoid remove() {throw newUnsupportedOperationException();}
};
}
};
}
}
public class AdapterMethodIdiom{
public static voidmain(String[] args) {
ReversibleArrayList
Arrays.asList("To be or not to be".split(" ")));
for (String s : ral)
System.out.print(s + " ");
System.out.println();
for (String s : ral.reversed())
System.out.print(s + " ");
}
}
有关shuffle()方法的需要注意的一点:shuffle()方法没有影响到原来的数组而只是打乱了shuffled中的引用.
Integer[] ia={1,2,3,4,5,6,7,8,9};
List
Collections.shuffle(list1,rand);//print out:[5,1,2,7,8,4,9,3,6]
System.ou.println(Arrays.toString)//printout:[1,2,3,4,5,6,7,8,9]
List
Collections.shuffle(list2,rand);//printout:[3,6,8,2,5,9,1,4,7]
System.ou.println(Arrays.toString)//printout:[1,2,3,4,5,6,7,8,9]
在第一种情况中,Arrays.asList()的输出方被传递给了ArrayList()构造器,这将创建一个引用ia元素的ArrayList,因此打乱这些引用不会修改数组.但是直接使用Arrays.asList(ia)的结果,这种打乱就会修改ia的顺序.
Java提供了大量持有对象的方式:
1) 数组将数字与对象联系起来.他保存类型明确的对象,查询对象时,不需要做结果转换.他可以是多维的,可以保存基本数据.
2) Collection保存单一的元素,Map保存键值对.有了Java泛型,你就可以指定容器中存放的对象类型,因此你就不会将错误类型的对象放置到容器中,并且再从容器中获取元素时,不必进行类型转换.容器不能持有基本类型,但是自动包装机制会仔细的执行基本类型到容器中所持有的包装类型之间的双向转换.
3) 像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排好序的容器.
4) 如果进行大量的随机访问,就使用ArrayList;如果要经常从中插入或删除元素,这应改使用LinkList.
5) 各种Queue以及栈的行为,由LinkList提供支持.
6) Map是一种将对象(而非数字)与对象相关联的设计.HashMap设计用来快速访问,而TreeMap保持”键”始终位于排序状态,所以没HashMap快.LinkedHapMap保持元素插入的顺序,但是也通过散列提供了快速访问能力.
7) Set不接受重复元素.HasSet提供最快的查询速度,而TreeSet保持元素处于排序状态.LinkedHashSet以插入顺序保存元素.
String对象是不可变的.String类中的每一个看起来会修改String值的方法,实际上是创建了一个全新的String对象,以包含修改后的字符串内容,而最初的String对象则丝毫未动.
String对象是不可变的,你可以给一个String对象加任意多的别名.因为String具有制度性,所以指向它的任何引用都不可能改变它的值,因此也就不会对其他的引用有什么影响.
操作符的重载:一个操作符在应用于特定的类时,被赋予了特殊的意义(用于String的”+”与”+=”是Java中仅有的两个重载过的操作符,Java不允许程序员重载任何操作符)
public class SimpleFormat {
public static voidmain(String[] args) {
int x = 5;
double y = 5.3325655;
System.out.println("Row1:[" + x+ " " + y+ "]");
System.out.format("Row1:[%d %f]\n", x, y);
System.out.printf("Row1:[%d %f]\n", x, y);
}
}/*Output: Row1:[55.3325655]
Row1:[5 5.332566]
Row1:[5 5.332566]*/
一些Format输出方式:
private Formatter f=new Formatter(System.out);
f.format(“%-15s %5s %10s\n”,”Item”,”Qty”,”Price”);
f.format(“c: %c\n”,u);
正则表达式是一种强大而灵活的文本处理工具.使用它我们能够以编程的方式,构造复杂的文本模式,并对输入的字符串进行搜索.一旦找到了匹配这些模式的部分,就可以随心所欲地进行处理.
使用+来表示”一个或多个之前的表达式”.如果表示”可能有一个负号,后面跟着一位或多位数字”,可这样:
-?\\d+
看几个关于正则的例子:
str.replaceFirst(“f\\w+”,”located”));//要匹配的是以f开头,后面接一个或多个字母,并且只替换掉第一个
str.replaceAll(“shrubbery|tree|herring”,”banana”);//要匹配的是三个单词中的任意一个,|表示或
abc+与 (abc)+
第一个表示匹配ab,后面跟随一个或多个c,第二个表示匹配一个或多个的abc
一般我们更愿意构造功能强大的正则表达式对象.只需导入java.util.regex包,然后用哪个static Parrtern.compile()方法来编译即可.它会根据String类型的正则表达式生成一个Pattern对象.接下来,把你想要检索的字符串传入Pattern对象的matcher()方法.matcher()方法会生成一个Matcher对象,他有很多功能可以用.
public class TestScanner {
public static voidmain(String[] args) {
int i;long l;String s;
Scanner sc = new Scanner(System.in);
List
i = Integer.parseInt(list.get(0));
l = Integer.parseInt(list.get(1));
s = list.get(2);
//System.out.format("i=%d ,l=%f ,s=%s", l, i, s);
System.out.println("i=" + i+ " l=" + l+ " s=" + s);
}
}
在默认情况下,Scanner根据空白字符串对输入进行分词,但是也可以用正则表达式指定自己所需的定界符.
可以用useDelimiter()来设置定界符,同时,还有一个delimiter()方法,用来返回当前正在作为定界符使用的Pattern对象.
public class TestScanner {
static String threatData="";
public static voidmain(String[] args) {
Scanner scanner=new Scanner(threatData);
String pattern="(\\d+[.]\\d+[.]\\d+[.]d+)@"+"(\\d{2}/\\d{2}/\\d{4})";
while(scanner.hasNext(pattern)){
scanner.next(pattern);
MatchResult match=scanner.match();
String ip=match.group(1);
String date=match.group(2);
}
}
}
当next()方法配合指定的正则表达式用时,将找到下一个匹配该模式的输入部分,调以match()方法就可以获得匹配结果.注意它仅仅向下一个输入分词进行匹配,如果你的正则表达式中含有界定符,那永远不可以匹配成功.
运行时类型信息使得你可以在程序运行时发现和使用类型信息.
本章将讨论Java是如何让我们在运行时识别对象和类的信息的.主要有两种方式:
1) 传统的RTTI,它假定我们在编译时已经知道了所有的类型.
2) “反射”机制,它允许我们在运行时发现和使用类的信息.
回顾一下P313中的例子,同时回顾一下多态:
List
For(Shape shape:shapeList)
Shape.draw();
在此例子中,当把Shape对象放入List
的具体类型.对数组而言,他们只是Shape对象.
当从数组中取出元素时,这种容器——实际上它将所有的事物都当做Object持有——当然会自动将结果转型会Shape.这是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的.这也是RTTI名字的含义:在运行时,识别一个对象的类型.
在此例子中,RTTI类型转换得并不彻底:Object被转型为Shape,而不是转型为Cricle,Squre或Triangle.这是因为我们只知道这个List
接下来就是多态机制的事情了,Shape对象实际执行的是什么样的代码,是由引用所指向的具体对象Cricle,Squre或者Triangle而决定的.你希望大部分代码尽可能少地了解对象的具体类型,而只是与对象家族中的一个通用表示打交道(这里面的Shape).这也使得代码更容易写,更容易读,更容易维护;设计也更容易实现,理解,改变.所以”多态”是面向对象编程的基本目标.
类是程序的一部分,每个类都有一个Class对象.换言之,每当编写了一个新类,就会生成一个Class对象(更恰当的说,是被保存在了一个同名的.class文件中).为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为”类加载器”的子系统.
Class.forName(“Gum”);
forName()是取得Class对象的引用的一种方法.它是包含目标文本名(全限定名,即包含包名)的String作输入参数,返回的是一个Class对象的引用.对其调用时,其static自居也会被调用.
无论何时,只要你现在运行时使用类型信息,就必须首先获得恰当的Class对象的引用.Class.forName()就是实现此功能的便捷途径,因为你不需要为了获得Class引用而持有该类型的对象.但是,如果你已经有了一个该类型的对象,那就可以通过调用getClass()方法来获取Class引用,它将返回表该对象的实际类型的Class引用.
取得Class对象有三种方法: Class.forName(“Gum”); 对象名.getClass() 类字面常量: 类名称.class
关于Class包含的方法: getName()产生全限名,getSimpleName()和getCanonicalName()来产生不包含包名的类名和全限定名.
getInterface()返回的是Class对象,表示的是该Class对象中所要包含的接口.
if(Class对象 instanceof 类型名称) :用于判断这个字节码对象是否属于该类型
Class的newInstance()方法是实现”虚拟构造器”的一种途径,其允许你声明:”我不知道你的确切类型,但无论如何要正确地闯进你自己”,当创建新实例时,会得到一个Object引用,使用newInstance()来创建的类,必须带有默认的构造器.
注意:当使用”.class”来创建对Class对象的引用时,不会自动地初始化该Class对象.为了使用类有三个步骤:
1. 加载,这是由类加载器执行的.该步骤将查找字节码,并从这些字节码中创建一个Class对象.
2. 链接.在链接阶段将验证类中的字节码,为了静态分配存储空间,并且如果必须的话,将解析这个创建的对其他类的所有引用.
3. 初始化,如果该类具有超类,则对其初始化,执行静态初始化和静态初始化块.
仅使用.class语法来获得对类的引用不会引发初始化.但为了产生Class引用,Class.forName()会立即进行了初始化.
如果一个static final值是”编译器常量”,那么这个值不需要对该类进行初始化就能对象.但是,如果只是将一个域设置为static和final的,还不足以确保这种行为.
如果一个static域不是final的,那么对它访问时总要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间).
ClassintClass = int.class 与 Class
假设你想稍微放松一点限制:Class
实际上,用Class> intClass=int.class; 在java中,Class>优于平凡的Class,其好处是:他表示你并非碰巧或者由于疏忽,而使用了某个非具体的类的引用,你就是选择了非具体版本.
为了创建一个Class引用,他被限制为某种类型,或者该类型的任意子类,你需要通配符与extends关键字相结合,创建一个范围: Class extends Number> bounded = int.class;
bounded=double.class;
bounded=Number.class;
我们已知的RTTI形式包括:
1) 传统的类型转换,如”(Shape)”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常.
2) 代表对象类型的Class对象.通过查询Class对象可以获取运行时所需要的信息.
“(Shape)”.Java要执行类型检查,者通常被称为”类型安全的向下转型”.比如将Cricle类型转换为Shape类型,由于
Cricle肯定是一个Shape,所有编译器允许自由地向上转型的赋值操作,而不需要任何显式的转型操作.编译器无法知道对于给定的Shape到底是什么Shape.在编译期,编译器只知道它是Shape.因此,如果不使用显示的类型转换,编译器就不允许你执行向下转型赋值,以告诉编译器你拥有额外的信息,这些信息使你知道该类型是某种特定类型(编译器将检查向下转型是否合理,因此它不允许向下转型到实际上不是待转型类的子类类型上)
生成器在其列表中不包含这个类,因此它永远不能创建这个类对象,而这个类也就不能被加载并置于这个列表之中.
这里我们需要做的其他修改就是使用工厂方法设计模式,将对象的创建交给类自己去完成.它可以多态地被调用从而为你创建恰当类型的对象.
publicinterface Fartory
Instanceof和isInstance()生成的结果完全一样,equals()和==也一样.
Instanceof保持了类型的概念,他指出的是”你是这个类吗?或者你是这个类的派生类吗?”
==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是.
反射提供了一种机制——用来检查可用的方法,并返回方法名.
人们想要在运行时获取类的信息的另一动机,便是希望提供在跨网络的远程平台上创建和运行类的能力.
Class类与java.lang.reflect类库一起对反射概念进行了支持,该类库包含了Field,Method和Constructor类(每个类都实现了Member接口).这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员.这样你就可以使用Constructer创建新的对象,用get()和set()方法读取和修改与Field对象向关联的字段,用invoke()方法调用与Method对象关联的方法.另外,getFields()返回字段数组,getMethods()返回方法数组,getConstructors()返回构造器对象数组.
RTTI和反射之间的真正区别在于,对RTTI来说,编译器在编译时打开和检查.class文件.(换句话说,我们可以用””普通”方式调用对象的所有方法.)而对于反射机制来说,.class文件在编译时是不可以获取的,所以是在运行时打开和检查.class文件.
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替”实际”对象的对象.这些操作通常涉及到与”实际”对象通信,因此代理通常充当中间人的角色.
interface Interface {
void doSomething();
voidsomethingElse(String arg);
}
class RealObject implementsInterface {
@Override
public void doSomething() {System.out.println("doSomething");}
@Override
public void somethingElse(String arg) {System.out.println("somethingElse" + arg);}
}
class SimpleProxy implementsInterface {
private Interface proxied;
publicSimpleProxy(Interface proxied) {
this.proxied = proxied;
}
@Override
public void doSomething() {
System.out.println("simpleProxy dosomething");
proxied.doSomething();
}
@Override
public void somethingElse(String arg) {
System.out.println("simpleProxy somethingElse" + arg);
proxied.somethingElse(arg);
}
}
public class SimpleProxyDemo {
public static voidconsumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static voidmain(String[] args) {
consumer(newRealObject());
consumer(newSimpleProxy(new RealObject()));
}
}/* doSomething
somethingElsebonobo
simpleProxy dosomething
doSomething
simpleProxy somethingElsebonobo
somethingElsebonobo*/
因为consumer()接受的Interface,所以它无法知道正在获得的到底是RealObject还是SimpleProxy,因为这二者都实现了Interface.但是SimpleProxy已经被插入到了客户端和RealObject之间,因此它会执行操作,然后调用RealObject相同的方法.
Java的动态代理思想比代理思想更向前进了一步,因为他可以动态的创建代理并动态地处理对所代理方法的调用.
interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性.但是通过类型信息,这种耦合性还是会传播出去——接口并非是对耦合性的一种无懈可击的保障.
interface A {void f();}
class B implements A {
@Override
public void f() {}
public void g() {}
}
public class InterfaceViolation{
public static void main(String[] args) {
A a = new B();
a.f();
// a.g();//Compile error
System.out.println(a.getClass().getName());
if (a instanceof B) {
B b = (B) a;
b.g();
System.out.println("b'sg() has used");
}
}
}/*Output: cn.luofuwei.demo1.B
b's g() has used/
这是完全合法并可以接受的,但你并不想让客户端程序员这么做,因为这给了他们一个机会,使得他们的代码与你的代码耦合度超过了你的期望.
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类.如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大.
我们希望达到的目的是编写更通用的代码,要使代码能够英语”某种不具体的类型”,而不是一个具体的接口或类.
泛型实现了参数化类型的概念,是代码可以应用于多种类型.”泛型”术语的意思是:”适用于许多许多的类型”
通常,我们只会使用容器来存储一种类型的对象.泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性.
因此,我们喜欢暂时不指定参数,而是稍后再决定具体使用什么类型.要达这个目的,需要使用类型参数,
Java泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节.使用泛型时,我们只需要指定它们的名称以及参数列表即可.
元组(数据传送对象,信使)将一组对象直接打包存储于其中的一个单一对象.这个容器对象允许读取其中的元素,但是不允许向其中放新的对象.
传统的下推堆栈.实现自己的内部链式存储机制.
public class LinkedStack
private static classNode {
U item;
Node next;
Node() {item = null;next =null;}
Node(U item,Node next) {
this.item = item;
this.next = next;
}
boolean end() {return item== null && next == null;}
}
private Node
public void push(T item){
top = new Node
}
public T pop() {
T result = top.item;
if (!top.end())
top = top.next;
return result;
}
public static voidmain(String[] args) {
LinkedStack
for (String s : "Phasers onstun!".split(" "))
lss.push(s);
String s;
while ((s = lss.pop())!= null)
System.out.println(s);
}
}
这个例子使用了一个末端哨兵(end sentinel)来判断堆栈何时为空.它是在构造LinkedStack时创建的.每调用一次push方法,就会创建一个Node
泛型也可以应用于接口,例如生成器(generator),这是一种专门负责创建对象的类.实际上,这是工厂方法设计模式的一种应用.不过,生成器创建新对象时,他不需任何参数,而工厂方法一般需要参数.
基本的指导原则:无论何时,只要能做到,就应该尽量使用泛型方法.另外,static方法无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方式.
public class GenericMethods {
public
System.out.println(x.getClass().getName() + "\n"+ y.getClass().getName() + "\n" + z.getClass().getName());
}
public static voidmain(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("123", 2f, 2.25);
}
}/* java.lang.String
java.lang.Float
java.lang.Double*/
类型推断只对赋值操作有效,其他时候并不起作用.如果将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器不会执行类型推断.此时编译器认为:调用泛型后,其返回值被赋值给了一个Object类型的变量.
泛型方法与可变参数列表能够很好的共存:
public class GenericVargs {
public static
List
for (T item : args)
result.add(item);
return result;
}
public static voidmain(String[] args) {
List
System.out.println(ls);
List
System.out.println(ls1);
}
}/* [1, 2, 3]
[1, a, 4.1]*/
一个通用的Generator:
public class BasicGenerator
private Class
publicBasicGenerator(Class
@Override
public T next() {
try {
returntype.newInstance();//assums type is a public class
} catch(Exception e) {
throw new RuntimeException();
}
}
// produce a default generator given atype token;
public static
return newBasicGenerator<>(type);
}
}
这个类提供了一个基本实现,用以生成某个类的对象.这个类必须具备两个特点:
1) 他必须声明为public.(因为BasicGenerator与要处理的类在在不同的包中,所以该类必须声明为public,并且不能具有包内访问权限)
2) 它必须具备默认的构造器(无参数的构造器)
要创建这样的BasicGenerator对象,只需要调用create()方法,并传入想要生成的类型.泛型化的creat()方法允许执行BasicGenerator.create(MyType.class),而不必执行麻烦的new BasicGenerator
(剩余部分待续…)