学习笔记

1.Java和c的区别

1)、与C语言不同,JAVA里没有无符号(unsigned)整型。JAVA的8种基本类型(boolean,byte,short,char,int,long,float,double)都是有符号的。

2)、与C语言不同,JAVA里的char类型,占2个字节,专为存放Unicode字符所设计(比如说:String字符串的每个字符都是16位或者2个字节,以此来提供对unicode字符集的支持);

3)、与C语言不同,JAVA里的boolean只能赋值为true或false,不能直接赋值0或1;

4)、与C语言不同,JAVA里的long类型,不是占4字节,而是占8字节。

JAVA与C的不同之处,还有很多:

JAVA中没有指针,其实引用就是指针,但不能进行加减,也没有取值(*)和取地址(&),这也说明了Java的健壮性。

JAVA中有垃圾回收机制,不需要自己释放空间;只管new,不需要delete。

JAVA中的基本类型所占内存宽度是固定的,而C里可能跟平台相关。

JAVA中没有预处理指令。(如C中的#define , #include , #ifdef等)。

JAVA中没有GOTO,但可以break或continue到指定的标签。

JAVA中没有const关键字,而是用static final来定义常量。

JAVA中没有全局变量,却有静态(static)变量。

JAVA中不支持struct和union,enum的类型。

JAVA中不支持可变参数列表。不过它只有对可变参数独特的支持方式,即:func(String ...strings); 用...来代表传入的是一个可变参数,并且可变参数的类型必须是同一种类型。

JAVA中没有BitField的功能。

JAVA中没有typedef的功能。

JAVA通用性好,可以跨平台直接移植,只要安装JAVA虚拟机就可以。C平台相关,因此若要跨平台,绝大多数需要重新编译。

Java采用Unicode字符集,C通常采用的是ASCII字符集。

C变量可以不进行初始化,但Java的变量要进行初始化。

JAVA类 继承是单一的,非多重的,一个子类只有一个父类,子类的父类又只有一个父类。而C++可以支持多重继承

C语言支持运算符重载,它是数据抽象和泛型编程的利器,它允许直接对对象进行四则运算,就像基本数据类型那样.Java不支持这种多态机制,也是为了降低复杂性。但Java支持方法重载和重写,重载就是一个类具备多个相同属性行为;重写就是在继承关系中父类的行为在不同的子类上有不同的实现。

Java多了一个类修饰符,除了public, private, protected, 还有 默认。

Java有super关键字,指代父类对象,通常被用于调用父类的构造方法或一般方法。C语言没有super关键字,两者都有this,指代当前对象。

在Java中,除了static, final, private 是静态绑定以外,所有办法一律按动态绑定代理。

Java中可以将类组织起来用package打包,而c语言没有。

在C/c++编译器中,会对未初始化的变量给予警告,而Java则视为错误。

2.c/java中数组的区别

c中的数组就是内存块,如果程序访问越界或者访问前未初始化都会产生难以预料的后果,并且这种后果只能在运行时保留出来,风险性极高。

Java的数组就不会有这种问题,它确保数组会被初始化(未手动初始化的部分,自动初始化为null),而且不能在它的范围之外被访问。这种范围检查,是以每个数组上少量的内存开销及运行时的下标检查为代价的。但是却换来的是安全性和效率的提示。

Java在创建一个数组对象时,实际上是创建了一个引用数组,如果试图使用一个还是null的引用,那么在运行时就会保错。在使用任何引用前,必须为其指定一个对象。这样,常犯的数组错误在java中就可以避免。

Java还可以创建用来存放基本数据类型的数组,它在自动初始化时会将这种数组所占内存全部置零。

3.c/c++中允许一个在较大作用域中已经被声明的变量,但是Java不允许,这样避免了程序混乱。

比如:

{

int x = 12l;

{

int x= 12; //illegal in java but legal in c/c++

}

}

4.当用new创建一个java对象时,它可以存活与作用域之外,但是其引用在离开其作用域就消失了。

eg:

{

String s = new String("hello world!");

}

在离开作用域后,引用s就消失了,但是s所指向的String对象仍继续占据内存空间。但是这并不会造成"内存泄露"这类问题,因为Java有一个垃圾回收器,用来监视用new创建的所有对象,并辨别那些不会再被引用的对象,随后,释放这些对象的内存空间,以便其他新的对象使用。也就是说,你只需要创建对象,一旦不再需要,它们就会自动消失。

5.当变量作为类的成员并且为基本类型时,java才确保给它默认值。以确保它们得到初始化,防止产生程序错误。但是,这些初始值对你的程序来说,可能是不正确的,甚至是不合法的。所以最好明确的对变量进行初始化。

基本类型 默认值

boolean false

char '\u0000'(null)

byte (byte)0

short (short)0

int 0

long 0L

float 0.0f

double 0.0d

reference null

但是,上述确保初始化的方法并不适用与“局部” 变量(即并非某个类的字段)

6.Java中任何传递对象的场合,实际上传递的都是对象的引用(但是基本类型boolean, char, byte, short, int,long, float和double是个例外),并且传递的类型必须与接收的类型一致!否则,编译器将抛出错误。

7.ecplise 技巧

1.ctrl + / :添加/取消 //注释

2.ctrl + shift + / : 增加 /* */注释

3.ctrl + shift + \ : 取消 /* */注释

4.ctrl + m : 使/取消 编辑窗口最大化

5.ctrl + F6 :切换到不同类的窗口中去

6.ctrl + D :快速的删除某一行(删除的是光标所指向的那行)

7.ctrl + shift + down : 快速的复制一行或者几行

8.alt + 上箭头 :快速的向上移动一行或者几行

9.alt + 下箭头 :快速的向下移动一行或者几行

10.alt + shift + s,然后在按下r键 : 对属性快速的生成getter 和setter方法

11.alt + shift + s, 选择 覆盖/实现方法 : 重写父类快捷键

12.ctrl + shift +o: 快速导入所有的包

13.alt +shift + j : 增加javadoc注释,java注释是能够生成html的,甚至android 官方文档也是所谓java doc生成的。

14.ctrl+shift + F4: 关闭编辑的窗口

15.ctrl + q :回到上一次的编辑点,可用来恢复ctrl+shift +F4之后的窗口

16.alt + shift + r : 变量重命名(所有使用该变量的地方均会被重命名)

17.ctrl + o : 展示类的层次结构,

18.ctrl + t : 展示类的继承关系

19.ctrl + 点击方法名/类名: 快速跳转到方法/类的定义

20.ctrl + shift + h :快速查找项目对类的的调用

21.ctrl + shift + p :快速定位与之匹配的花括号

22.ctrl + shift + t : 快速查找/定位类

23.ctrl + shift + b : 快速打断点

24.F6 :单步调试

25.F8 :断点调试

8.C/C++和java中new的区别

java中使用new关键字,只是用来表示所声明的变量为引用,当把它以参数的形式传递给方法时,实际上其传递的是拷贝(即c/c++中的值传递),不过数组除外,如果入参为数组,则表现形式上是c/c++中的引用传递。

eg:

实例1:

{

String xString = new String("hello world!"); //xString为引用,其指向的值为hello world,

...

public void change(String xString){

xString = "changed"; //试图修改入参值

}

...

change(xString);

System.out.println(xString); //输出依然为"hello world!"

}

实例2:

{

int [] attr = new int [10] ;

...

public void change(int [] a){

a[0] = 12; //试图修改a[0]

}

...

change(attr);

System.out.println(attr[0]); //输出:12

}

9.System.getProperties().list(System.out):void //获取系统属性,并以list的形式输出到输出流System.out

10.System.getProperty(String key):String //获取指定key的属性值,并以String的形式返回

11.编程风格:"驼峰法":类名的首字母大写,如果有多个单词构成,则把它们并在一起(也就是说,不要用下划线来分割名字),其中每个内部单词的首字母都采用大写形式。

对于方法,字段(成员变量)以及对象引用名称等,风格基本和类的风格一致,只是标识符的一个字母采用小写。

12.String字符串:它是不可变的

原因有二:

1)java.lang.String类型在实现时,其内部成员变量全部使用final来修饰,保证成员变量的引用值只能通过构造函数来修改。

2)java.lang.String类型在实现时,在外部可能修改其内部存储值的函数实现中(比如:replace, concat等)返回时一律构造新的String对象或者新的byte数组或者char数组。

因此,下列中:

{

String x1 = new String("x1");

String x2 = new String("x2");

x1= x2; //使x1,x2指向同一个对象

x2 = x2.replace("x", "w"); //x2修改为"w2",修改后,x2引用的指向发生变化,不在指向x1所指向的值,

System.out.println(x1); //此时x1仍为"x2"

}

引申;这并不是引起著名的"别名问题",即:两个变量同为一块内存的引用,修改一个变量引用值,另一个也发生同样变化,因为它们指向的内存是一样的。

13.Random随机数生成器:随机数生成器对于特定的种子值总是产生相同的随机数序列。

14.String.equals(String):bool equals的默认行为是比较引用。

例子1:

String x1 = new String("10");

String x2 = new String("10");

System.out.println(x1.equals(x2)); //false, 虽然值相等,但是引用不同

x1 = x2;

System.out.println(x1.equals(x2)); //true, x1,x2指向同一个引用

15.在java中,e表示"10的幂次",比如:1e-2f =0.01

16.printBinaryInt和printBinaryLong().它们分别接受int或long型的参数,并用二进制格式输出。

17.如果一个表达式以一个字符串开始,那么后续所有操作数都必须是字符串类型(请注意,编译器会把双引号内的字符序列自动转成字符串)

例子:

int i=1, j = 2;

String s = "x,y,";

System.out.println(s + i + j ); //输出:x,y,12, 以字符串开头,后面的i,j自动被转换为字符串类型

System.out.println(i+j+s); //输出:3x,y, i和j首先进行加法运算,在将其转换为字符串

System.out.println(s + (i+j)); //输出:x,y,3,这是因为()的作用使得()内的内容优先处理

Sysyem.out.println(s+Integer.toString(i+j)); //输出:x,y,3

总结:从上面可以看出,不同的摆放顺序会造成输出值的不同,为避免产生歧义,应该使用Integer.toString()来显示转换

18.java编译器不会把int型自动转换为bool型。

19.窄化转换:高精度向低精度转换,比如(long转换为int),必须显示转化,否则编译器报错。

扩展转换:低精度向高精度转换,则不需显示转换,因此这不会造成任何信息的丢失。

Java允许我们把任何基本数据类型转换成别的基本数据类型,但bool类型除外,后者不允许进行任何类型的转换处理,"类"数据类型不允许进行类型转换。为了将一种类转换为另一种类型,必须采用特殊的办法。

20.Math.round(long):long 将long类型进行四舍五入,该函数位于包:java.lang.Math中

21.算术运算中,通常最大的数据类型决定了最终结果的数据类型。比如将一个float与double相乘,结果就是double, 但是有一点,在对基本数据进行算术运算或者按位运算中,只要类型比int小(char, byte, short),那么在运算之前,这些值都会自动转换为int,这样生成的结果也就是int类型

例子:

char chr = 'a' //即:chr = 97,char占2个字节

byte by = 2; //byte占一个字节

short shrt = chr + by; //编译错误,short占两个字节,要想使表达式成立,必须进行显示转换

int i = chr + by; //正确,i= 99

22.java没有sizeof, c/c++中引进sizeof()用来告诉我们为数据项分配的字节数。使用它最大的原因是为了"移植", 不同的数据类型在不同的机器上可能有不同的大小,所以在进行一些存储空间有关的运算时,程序员必须获悉那些类型具体有多大,而java不需要考虑这方面的问题,因为所以的数据类型在所有机器中的大小都是相同的,它已经被设计在了语言中

23.foreach语句可以非常方便的用于数组和拥有Iterable对象的容器。

比如:

int [] attr = new int [10];

for(int i; attr){

.... //i表示attr[0]到attr[9]的值

}

24.static方法的内部不能调用非静态方法,反过来倒是可以的。

25.finalize()用途:用在手动释放那些不是通过new方法来分配内存,它在垃圾回收器回收由new分配的内存之前被触发。它类似C++的析构函数,但是和它有本质区别:在C++中,对象一定会被销毁(如果程序没有缺陷的话);而java里的对象却并非总是被垃圾回收。只要程序没有濒临存储控件用完的那一刻,对象占用的控件就总也得不到释放,如果程序执行结束, 并且垃圾回收器一直没有释放对象的内空间,则随着程序的退出,那些资源也会全部交还给操作系统。

26.System.gc():显示请求系统进行垃圾回收,然而系统会不会理会是不一定的。

27.基本类型的自动初始化一般只会发生在类的数据成员中,对于普通变量是不会自动初始化的。而且需要牢记一点:无法阻止自动初始化的进行,因为它将在构造函数被调用之前发生。也就是说,如果在构造函数中也初始化了数据成员,则该数据成员实际上会被初始化两次,第一次是默认值,第二次是构造函数中的赋值。

28.static关键字不能应用于局部变量,因此它只能作用于域。常见的就是作为类的静态数据成员使用。

29.总结一下对象的初始化顺序: 首先是显示静态对象(显示使用static表明的对象) ->隐式静态对象(比如:构造器)->非静态对象。额外需要说明的是,静态对象的初始化只有在必要时刻才会进行,如果不创建静态对象所在类的类对象,则该静态对象不会进行初始化。

class T1{

T1(){

System.out.println("T1::T1()");

}

}

class T2{

static T1 t1 = new T1();

T2(){

System.out.println("T2::T2()");

}

}

public class one {

public static void main(String[] args) {

//T2 t2 = new T2(); //未创建类对象,则该类中的静态对象不会被初始化。

System.out.println("end");

}

}

输出:

end

30.显示的静态初始化

Java允许将多个静态初始化动作组织成一个特殊的"静态子句"(也叫静态块),它和直接初始化没有什么区别

比如:

public class Spoon{

static int i;

static char chr;

....

static {

i = 3;

chr = "w";

}

}

扩展:对于非静态对象,也可以采用上述静态块的模式来初始化,只是没有static关键字。

public class Spoon{

int i;

int j;

{

i = 2;

j = 3;

}

}

31.关于《java编程思想第四版》中的"编译器不允许指定数组的大小"的理解

答:联系上下文,正确的解读应该是编译器不允许在数组声明时向c语言一样指定其大小,但是可以通过初始化的形式来确定大小.声明数组时实际拥有的是对数组的一个引用,既然是引用自然没有大小可言,而且此时也没有给数组对象本身分配任何空间.

eg:

int a[20]; //right in c,but wrong in java

int a[] = new int [20]; //both right in c and java

int a[] = {1,2,3,4} //声明时就初始化

在java中,int a[] 和int []a等价,均为表示数组a

另外为什么说给数组赋值实际是对内存做一个引用呢? 请看下面这个例子

int []a = {1,2,3}

int []b;

b = a;

for(int i = 0; i < a.length; i++){

a[i] = a[i] + 3;

}

for(int j:b){

System.out.print(j); //输出:4,5,6

}

扩展:如何将一个数组复制到另一个数组?

答:可以采用System类的arraycopy()方法

32.java中的可变参数列表

当调用方式的参数个数和类型未知时,称其为可变参数列表。在以前的JAVA SE5之前的可以使用object数组来实现。因为所有的类都是直接或者间接集成于object类。

例1:

package sample;

class A1{}

public class VarArgs {

static void printArray(Object[] args){

for(Object obj:args)

System.out.print(obj+" ");

System.out.println();

}

/* 定义类的toString方法

public String toString(){

return "22";

} */

public static void main(String[] args){

printArray(new Object[]{

new Integer(47),new Float(3.14),new Double(11.11)

});

printArray(new Object[]{"one","two","three"});

printArray(new Object[]{new A1(),new A1(),new A1()});

}

}

结果:

47 3.14 11.11

one two three

sample.A1@a90653 sample.A1@de6ced sample.A1@c17164

扩展:

toString()方法:每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你只有一个对象时,该方法就会被调用

如果一个类没有toString()方法,那么在通过可变参数打印时默认行为是打印类的名字和对象的地址(比如:sample.A1@a90653);反之如果定义了类的toString方法,则print的就toString方法的返回值。

Java SE5实现可变参数列表,同样的方法我们可以定义成这样(Object ...args)

class A{}

public class NewVarArgs {

static void printArray(Object...args){

for(Object obj:args)

System.out.print(obj+" ");

System.out.println();

}

public static void main(String[] args){

printArray(new Integer(47),new Float(3.14),new Double(11.11));

printArray(47);

printArray("one","two","three");

printArray(new A(),new A(),new A());

printArray((Object[])new Integer[]{1,2,3,4});

printArray(); //这一行表明可变参数的长度为0也是可以的,但是如果出现printArray重载拥有多个类型不同的可变参数方法时,0长度的入参往往会产生歧义(既然虽然编译器调用最合适的方法匹配它),解决办法就是:定义一个非可变参数

}

}

结果:

47 3.14 11.11

47 3.14 11.11

one two three

sample.A@a90653 sample.A@de6ced sample.A@c17164

1 2 3 4

这里没有显式的使用数组作为参数,但编译器实际上会为你填充数组。所以同样可以使用foreach语法来遍历之。

注意倒数第二行,编译器已经发现它是一个数组,所以不会在其上执行任何转换。最后一行表明,可以传递零个参数。

也可以指定所有可变参数的类型.

static void printArray(String ...args){

....

}

说到这里,不得不提出一个特性:自动包装机制:在可变参数列表中,自动包装机制可以将基本类型与该类型的包类型自由切换。

class A{}

public class NewVarArgs {

static void printArray(int ...args){

for(Object obj:args)

System.out.print(obj+" ");

System.out.println();

}

public static void main(String[] args){

printArray(new Integer(47), 1, 2);

}

}

结果:

47, 1, 2

扩展:自动包装机制有两个概念:装箱和拆箱。

这里的装箱应该理解为 封装对象 ,即把基础数据类型(如 int)转换成基础类型封装类的对象(如 new Integer())

拆箱就是装箱的反过程,即把基础类型封装类的对象(如 new Integer())转换为基础数据类型(如 int)。

装箱:

Integer a = new Integer() ;

a = 100 ; //1.5以前不支持为对象如此赋值

拆箱:

int b = new Integer(100) ;

33.基本数据类型与包装类对应关系如下:

short Short

int Integer

long Long

char Char

float Float

double Double

boolean Boolean

34.getClass()方法:它属于Object对象,它的输出是该类类型的编码字符串。

比如:

print(new int[20].getClass());

结果:

class [I //其中的[表示该对象类型为数组,I表示基本类型int.

35.enum 枚举,它在Java SE5中添加的新特性。它和C语言中的enum用法基本一致。但在Java中,它实际上是被定义成一个类。正是如此,当我们在创建一个enum时,编译器会自动添加一些有用的特性,比如:它会创建toString()方法来显示enum实例名字,还自动创建ordinal()方法来表示某个特定enum常量的声明顺序,以及static values()方法用来按照enum常量的声明顺序,产生由这些常量值构成的数组:

public enum Num{

ONE,

TWO

}

public static void main(String[] args) {

Num num = Num.ONE;

println(num);

for (Num i:Num.values()){

System.out.print(i.ordinal() + " ");

}

}

结果:

ONE

0 1

36.类的访问权限有四种:public, 包访问权限(无修饰符)和final终态修饰符 和abstract修饰符。

final修饰的类或者对象表示该对象不可变更和被继承。

abstract抽象类中可以定义方法,但是不可被实例化即new; interface接口中不可定义方法

37.如果创建一个拥有私有构造函数的类对象?

答:直接通过该类的静态函数来创建!一个类的静态函数可以访问该类的所有基本的方法,包括私有(这点和c相同)

比如;

class Sunda{

private Sunda() {}

static Sunda makeSunda() { return new Sunda(); }

}

扩展:在设计模式中,有种特定的模式称为单例:只能创建该类的一个对象,并且通过public的方法来访问它。

eg;

class Sunda{

private Sunda() {}

private static Sunda ps1 = new Sunda();

public Sunda access() { return ps1; }

}

38.每个编译单元(文件)都最多只能有一个pubilc类。public类的名称必须完全和含有该编译单元的文件名相匹配,包含大小写。当编译单元不带public类时,则可以随意对文件命名。

39.基类的初始化:如果想调用一个带参数的基本构造函数,则必须使用super显示调用说明

eg:

class Game{

Game(int i){ ....}

}

class BoardGame extends Game{

BoardGame(int i ) {

super(i); //用super来指代基类

....

}

}

40.java代理机制

代理模式的作用是:为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。使用代理可以避免真实角色的接口暴露在场景中。

代理模式一般涉及到三个角色:

1.抽象角色:声明真实对象和代理对象的共同接口

2.代理角色:代理对象内部包含有真实角色的引用,从而可以操作真实角色,同时代理对象与真实对象有相同的接口,能在任何时候代替真实对象,同时代理对象可以在执行真实对 象前后加入特定的逻辑以实现功能的扩展。

3.真实角色:代理角色所代表的真实对象,是我们最终要引用的对象

常见的代理有:

1.远程代理(Remote Proxy):对一个位于不同的地址空间对象提供一个局域代表对象,如RMI中的stub

2.虚拟代理(Virtual Proxy):根据需要将一个资源消耗很大或者比较复杂的对象,延迟加 载,在真正需要的时候才创建

3.保护代理(Protect or Access Proxy):控制对一个对象的访问权限。

4.智能引用(Smart Reference Proxy):提供比目标对象额外的服务和功能。

通过代理类这一中间层,能够有效控制对实际委托类对象的直接访问,也可以很好地隐藏和 保护实际对象,实施不同的控制策略,从而在设计上获得了更大的灵活性。

举个例子:

//太空船操作

class SpaceShipControls{

void left(int velocity) {...}

void right(int velocity) {...}

}

//太空船

class SpaceShip {

private SpaceShipControls controls = new SpaceShipControls();

void left(int velocity) { controls.left(); } //代理对象和真实对象拥有相同的接口

void right(int velocity) { controls.right(); }

}

41.java interface概念及使用

在抽象类中,可以包含一个或多个抽象方法;但在接口(interface)中,所有的方法都必须是抽象的,不能有方法体,它比抽象类更加“抽象”。

接口必须通过类来实现(implements)它的全部抽象方法,然后再实例化类。类实现接口的关键字为implements。

实现接口的格式如下:

修饰符 class 类名 extends 父类 implements 多个接口 {

实现方法

}

extends只能单一继承,但是implements可以同时包含多个接口。

interface关键字的重要目标是允许程序员进行方法隔离,进而减低耦合性。但是通过类型转换,这种耦合性还是会传播出去-接口并非对解耦是一种无懈可击的保障。

比如:

public interface A{

void f();

}

class B implements A{

void f(){...};

void g(){...};

}

public static void main(String[] args){

A a = new B();

a.f();

//a.g(); //compile.error

if (a instanceof B){

B b = (B)a;

b.g();

}

}

在该例子中,你想隔开的g()方法依然被暴露在了外面,没有达到使用接口的目的。那么怎么办呢?最简单的方式就是实现使用包访问权限。

比如:

class C implements A{

public void f(){...};

public void m(){println("C.m()")};

}

public class HiddenC{

public static A makeA(){return new C();}

}

这样,在包外没有任何C类型可用,就达到了我们隔离C类型中m方法的目的。

到此,你认为圆满了? NO! java 提供了反射,通过反射,依然可以达到并调用所有方法,甚至是private方法。

下面是一个列子:

static void callHiddenMethod(Object obj, String methondName){

Method g = obj.getClass().getDeclaredMethod(methondName);

//屏蔽方法访问权限,这样即便是private,我们依然可以正常调用

g.setAccessible(true);

g.invoke(obj);

}

static void main(String[] args){

A a = HiddenC.makeA();

CallHiddenMethod(a. "m");

}

输出:

C.m()

43.Java 动态代理

是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy, 它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

动态代理被代理的对象方法必须来自于接口,非接口方法不能被代理。

它一般包含以下几个步骤:

1)声明接口(接口必须是抽象的)

interface

{

public void request();

public void hello(String str);

}

2)定义真实角色类

//真实角色RealSubject:

class RealSubject implements Subject {

...

//在任何implements 接口的类中,必须定义接口(因为接口都是抽象的)

public void request() {

System.out.println( " From real subject. " );

}

public void hello(String str) {

System.out.println( "hello: " + str );

}

}

3)定义代理类

//InvocationHandler接口只有一个invoke方法,它的声明与DynamicSubject类中的invoke方法一致

import java.lang.reflect.Method;

import java.lang.reflect.InvocationHandler;

class DynamicSubject implements InvocationHandler {

private Object sub; //sub用来存储我们要代理的真实对象

public DynamicSubject (Object obj) {

sub = obj;

}

/* invoke方法被覆写

proxy:  指代我们所代理的那个真实对象

method:  指代的是我们所要调用真实对象的某个方法的Method对象

args:  指代的是调用真实对象某个方法时接受的参数

*/

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println( " before calling " + method );

//调用真实对象的方法,下面这行的写法和我们常用的方法“实例.方法(参数)”不一致,下面是“方法.invoke(实例,参数)”

method.invoke(sub,args);

System.out.println( " after calling " + method);

return null ;

}

}

------------------------------------------------------------------

4)调用

static public void main(String[] args) throws Throwable {

//声明真实对象

RealSubject rs = new RealSubject();

//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的

InvocationHandler ds = new DynamicSubject(rs);

Class cls = rs.getClass();

// 以下是一次性生成代理

/*

为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号

*/

/*

* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数

* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象

* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了,getInterfaces()方法返回的是接口的Class对象的数组。

* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上

* 返回值:接口的实例

*/

Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), ds );

//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用, 如下面这句,将会自动跳转到代理类对象的invoke方法, 传入给method的就是request()方法的Method对象

subject.request();

subject.hello("world");

}

输出:

before calling public abstract void demo1.Subject.request()

From real subject.

after calling public abstract void demo1.Subject.request()

before calling public abstract void demo1.Subject.request()

hello: world

after calling public abstract void demo1.Subject.request()

44.final, finally, finalize区别

final用于声明属性(必须是基本数据类型),方法和类不可变(属性不可改变,方法不可被重载,类不可被继承),如果用final来修饰对象表示该对象引用恒定不变,但是对象其自身却是可以被修改的。

eg:int [] a = {1,2} ;

final int [] b = a;

int [] c = {3};

b = c; //错误,试图修改b的引用

另外,java支持“空白final”,即:在声明时指定为final但是不进行初始化。但是必须确保在使用前对其初始化,否则编译器编译不通过。final还可以用以修饰参数,意味着无法更改参数引用所指向的对象,private方法默认都是final的,因为它们不可被继承,因而谈不上被重载。如果在子类中声明了一个和基类名称一样的private方法,这并不代表该private方法被继承并重载了,它是一个全新的方法,只是和基类private方法“具有相同的名称而已”

finally,它是try的子句,用来捕获异常模块。它表示无论是否发生异常,其finally子句总是被执行

finalize,当需要手动释放内存时,需要重载该方法,它在垃圾回收器之前被调用。

45.如果java的基类拥有某个已被多次重载的方法,那么在派生类中重新定义(或者重载)该方法并不会屏蔽其在基类的任何版本(这一点与C++不同,C++中如果派生类中重新定义或者重载了基类函数,则派生类自动屏蔽掉基类中该函数的所有重载版本)。因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作。但它也令人迷惑不解(这也就是为什么c++自动屏蔽的原因 - 防止你可能会犯错误)

46.Java SE5中增加了@Override注释,虽然它不是关键字,但是它可以当做关键字来使用。当要覆写某个方法时,可以选择添加这个注释,在你不留心重载而并非覆写了该方法时,编译器就会报错“method does not override a method from its superclass”

47.protected:该类所属包内的类或该类的派生类都可以访问protected成员。

那么考虑如下几种场景

1).在子类中,能否通过子类(或子类的子类)的引用来访问父类的protected方法和成员变量? 答案:yes

2).在子类中,能否直接通过父类的实例来访问父类中的protected方法和成员变量? 答案:如果父类和子类不在一个包中,则不可以,如果要显示访问父类的方法,可以使用super.方法()的方式; 如果父类和该子类在同一个包中,则可以。因为它满足“包类的类可以访问protected方法”

eg:

class CA {

protectd void print() {...};

}

class CB extends CA{

void print(){

CA ca = new CA();

ca.print(); //错误:

super.print(); //正确

}

}

3).在子类中,能否通过父类的另一个子类来访问父类的projected方法和成员变量? 答案:如果父类和子类不在一个包中,则不可以;如果父类和该子类在同一个包中,则可以。因为它满足“包类的类可以访问protected方法”

eg:

class CC extends CA{

....

}

class CB extends CA{

void print(){

CC cc = new CC();

cc.print(); //错误

}

}

48.java中常量 = static + final + "变量"

49.java中所有事物都是对象,每个类的编译代码都存在于它自己的独立的文件中,该文件在初次使用时才会被加载。它一般是指加载发生在创建类的第一个对象之时,但是当访问static域或者static方法时,也会发生加载.

50.Java中除了static和final方法(private方法属于final方法)之外,其他都是运行时绑定(动态绑定)。但是在c++中,只有虚函数才使用的是动态绑定,其他的全部是静态绑定的(目前还没有发现不适用这句话的),不过有一点需要注意,当缺省参数和虚函数一起出现时情况有点复杂,极易出错。我们知道虚函数的动态绑定的,但是为了执行效率,缺省参数是静态绑定的。

class B

{

public:

virtual void vfun(int i = 10){ std::cout << "B:" << i << std::endl; }

};

class D : public B

{

public:

virtual void vfun(int i = 20){ std::cout <<"D:"<< i << std::endl; }

};

D* pD = new D(); //pD的静态类型和动态类型都是D类型

B* pB = pD; //pB的静态类型是B类型,但是动态类型是D类型

pD->vfun(); //

pB->vfun(); //调用调用的D类的vfun函数,但是入参是静态绑定的,所以输出10

输出:

D:20

D:10

51.为什么要将某个方法声明为final?

1)可以防止其他人覆盖该方法

2)更重要的是,能有效的“关闭”动态绑定。或者说,告诉编译器不需要对其进行动态绑定。这样编译器就可以为final方法调用生成更有效的代码,但是大多数情况下,这并不会对程序性能有所改观。因此最好是根据设计来决定是否使用final,而不是出于试图提供性能的目的来使用final.

52.如果构造器调用某个动态绑定方法,那会发生什么情况?

动态绑定的调用是在运行时才决定的,它可以向外深入到继承层次结构内部,调用派生类的方法。如果我们在基类构造器中调用动态绑定方法,当我们创建一个派生类对象时,在基类构造器中调用的动态绑定方法实际上调用的是派生类中的被“覆写”的方法,而这个方法所操纵的对象可能会未初始化 --这肯定将招致灾难。

class CA{

CA(){

System.out.println("CA::begin");

print();

System.out.println("CA::end");

}

void print(){

java.lang.System.out.println("CA::print()");

}

}

class CB extends CA{

int a = 1;

public CB(){

}

void print(){

java.lang.System.out.println("CB::print(),a=" + a);

}

}

.....

CB cb = new CB();

输出:

CA::begin

CB::print(),a=0

CA::end

从上面的列子可以看出两点:

1)创建一个派生类对象时,其基类构造器中调用的print()方法实际上调用的是CB派生类中而非基类中的(这点也证实了上面我们所说的)

2)CB属性a默认初始值本应为1,但是却输出为0。为什么呢?

答:实际上这与初始化的实际过程相关,它的步骤如下:

<1>在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零(如果是对象引用,则为null)。

<2>按照声明顺序初始化基类中拥有默认值的成员变量

<3>然后调用基类的构造函数。如果该构造函数中调用了动态绑定方法,则向外深入到继承结构层次内部,实际调用派生类中被"覆写"的方法(这点C++不同,C++会产生更合理的行为:实际调用基类方法,而不是派生类)

<4>按照声明顺序初始化派生类中拥有默认值的成员变量

<5>调用派生类的构造函数

总结:在构造器中唯一能安全调用的那些方法是基类中的final方法(也使用于private方法,它们自动属于final方法),因为它们不能被“覆写”

53.关于向上转型和向下转型的问题

1)、父类引用可以指向子类对象,子类引用不能指向父类对象。

2)、把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转型。

   如Father father = new Son();

3)、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转型。

   如father就是一个指向子类对象的父类引用(即:Son son; Father father = son),把father赋给子类引用son 即Son son =(Son)father;

   其中father前面的(Son)必须添加,进行强制转换。

向下转型分为两种情况:

<1>如果父类引用的对象如果引用的是指向的子类对象,那么在向下转型的过程中是安全的。也就是编译是不会出错误的。(比如: Son son; Father father = son; Son son =(Son)father;)

<2>如果父类引用的对象是父类本身或者其他子类,那么在向下转型的过程中是不安全的,编译不会出错(实测在jre1.6中编译会报错),但是运行时会出现java.lang.ClassCastException错误。(比如: Father father = new Father(); Son son =(Son)father;)

eg:

Father father = new Father();

Son son = (Son)father; //在jre 1.6中编译报错

Father father = new Son();

Son son = (Son)father; //在jre 1.6中编译不报错,是安全的向下转型

4)、upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法,子类方法有效

5)、向上转型的作用,减少重复代码,父类为参数,调有时用子类作为参数,就是利用了向上转型。这样使代码变得简洁。体现了JAVA的抽象编程思想。

54.instanceof关键字:(比如:son instanceof Father)判断左边的对象是否为右边类型的实例。如果是返回true,否则返回false

55.abstract关键字:用来表示抽象类和抽象方法。

抽象方法:它等价于C++中的纯虚函数,只有声明没有定义。

抽象类:如果一个类包含一个或多个抽象方法,该类必须被限定为抽象类。如果我们定义一个抽象类的实例,将会自动生成该抽象类中的匿名内部方法。也就是说在,在定义一个抽象类实例前必须定义抽象类的所有方法。

abstract class Father {

abstract public void smile();

}

Father father = new Father() {

@Override

public void smile() {

// TODO 自动生成的方法存根

}

};

56.interface接口:接口中所有方法都是抽象的且是public的(不显示声明也是public),如果设定为非public类型,则编译器直接报错。接口中也可以定义成员变量,它默认是static & final (静态常量)的,它不允许是私有的。对于接口中的成员变量是在何时进行初始化的呢? 答案是:它们在类第一次被加载时初始化,为何不在编译时初始化呢? 因此它可以用非常量表达式初始化。eg; Random RAND = new Random(47); int RANDOM_INT = RAND.nextInt(10);

57.策略设计模式:指的是能够根据所传递的参数对象(个人把其也理解为类型)的不同而具有不同行为的方法。它一般和多态相结合使用。

eg;

class Processor{

public String name() {

return getClass().getSimpleName(); //获取类名称

}

Object process(Object input){return input;}

}

class Upcase extends Processor {

String process(Object input){

return ((String)input).toUpperCase();

}

}

class Downcase extends Processor {

String process(Object input){

return ((String)input).toLowerCase();

}

}

public class Apply{

public static void process(Processor p, Object s) {

print("Using Processor " + p.name());

print(p.process(s));

}

public static void main(String[] args){

String s = "Hello World";

process(new Upcase(), s );

process(new Downcase(), s);

}

}

输出:

Using Processor Upcase

HELLO WORLD

Using Processor Downcase

hello world

58. extends只能单一继承,但是implements可以同时包含多个接口(接口类和抽象类一样不允许创建对象),以逗号隔开。当组合继承时,继承类必须写在前面,后面接接口,否则编译器会报错

59.使用接口的核心原因是为了能够向上转型为不同的基类类型(以及由此而带来的灵活性),然后,使用接口的 第二原因却是和使用抽象基类相同:防止客户端程序创建该类的对象,并确保这仅仅是建立了一个接口。

这就带来了一个问题:我们应该使用接口还是抽象类?

如果要创建不带任何方法定义和成员变量的基类,那么就该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。

59.接口可以嵌套在类或其他接口中,当在类中嵌套时,它可以拥有public 和"包访问"两种可视性。

60.工厂方式设计模式:

· 1)简单工厂模式:它又叫静态工厂,它主要有一个静态方法,用来接受参数,并根据参数来决定返回实现同一接口的不同类的实例。

//第一步:定义公共接口 <-抽象产品角色

public interface Product{

void print()[

}

//第二步:定义拥有公共接口的不同类 <-具体产品角色

class Washer implements Product {

@Override void print(){

System.out.println("洗衣机被制造了");

}

}

class Icebox implements Product {

@Override void print(){

System.out.println("冰箱被制造了");

}

}

//第三步:定义工厂类,它根据参数不同而返回不同类的实例 <-工厂类角色,用以分发处理生成不同类实例。

static Product factory (String productName) throws Exception{

if (productName.equals("Washer")){

return new Washer();

} else if (productName.equals("Icebox")) {

return new Icebox();

} else{

throw new Exception("没有该产品");

}

}

//第四步:调用

try{

factory("Washer");

factory("Icebox");

} catch(Exception e) {

e.printStackTrace();

}

2)工厂方法模式:它是简单工厂模式的进一步抽象化和推广:工厂方法模式不再只由一个产品类应当被实例化,这个决定被交给抽象工厂的子类去做。

来看下它的组成:

(1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。

(2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。

(3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。

(4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

//抽象工厂角色

interface Factory{

public Product create();

}

//抽象产品角色

interface Product{}

//具体产品角色

class Washer implements Product{

public Washer(){

System.out.println("洗衣机被制造了");

}

}

class Icebox implements Product{

public Icebox(){

System.out.println("冰箱被制造了");

}

}

//具体工厂角色

//创建洗衣机的工厂

class CreateWasher implements Factory{

public Product create(){

return new Washer();

}

}

//创建冰箱的工厂

class CreateIcebox implements Factory{

public Product create(){

return new Icebox();

}

}

//调用接口,根据入参不同,而调用不能的类方法

public static void serviceConsumer(Factory fac){

fac.create(); //利用了多态性质,一个指向派生类的基类指针,

}

....

serviceConsumer(new CreateWasher());

serviceConsumer(new CreateIcebox());

输出:

洗衣机被制造了

冰箱被制造了

抽象产品角色(抽取公共方法作为接口)

| |

具体产品角色1 具体产品角色2

| |

具体工厂角色1 —— 抽象工厂 —— 具体工厂角色2

| |

封装一个经常方法(通过参数,以多态的形式满足不同产品的调用)

融合了匿名内部类的工厂方法:

----------------------------------------------------------------------------------------------------------

//抽象产品类

interface Service{

void method1();

void method2();

}

//抽象工厂类

interface ServiceFatory{

//生成产品

Service getService();

}

//具体产品类1

class Ser1 implements Service{

private Ser1(){}

@Override

public void method1() {

// TODO 自动生成的方法存根

System.out.println("Ser1.metho1()");

}

@Override

public void method2() {

System.out.println("Ser1.metho2()");

};

static ServiceFatory Fac = new ServiceFatory() {

@Override

public Service getService() {

// TODO 自动生成的方法存根

return new Ser1();

}

};

}

//具体产品类2

class Ser2 implements Service{

private Ser2(){}

@Override

public void method1() {

// TODO 自动生成的方法存根

System.out.println("Ser2.metho1()");

}

@Override

public void method2() {

// TODO 自动生成的方法存根

System.out.println("Ser2.metho2()");

}

static ServiceFatory Fac2 = new ServiceFatory(){

@Override

public Service getService() {

// TODO 自动生成的方法存根

return new Ser2();

}

};

}

public static void Create(ServiceFatory fac) {

Service service = fac.getService();

service.method1();

service.method2();

}

Create(Ser1.Fac);

Create(Ser2.Fac2);

输出:

Ser1.metho1()

Ser1.metho2()

Ser2.metho1()

Ser2.metho2()

说明:这种方法看似简单,但却是建立在“产品 - 工厂”这种一对一的生成模式,如果要达到一个工厂生成多个产品,这种方法不是很适合

------------------------------------------------------------------------------------------

3)抽象工厂模式:相比工厂方法,它引进了分类管理的概念,并把产品分类(同种产品和不同种产品)成了抽象工厂的重点。

//抽象工厂类

public abstract class AbstractFactory {

public abstract Vehicle createVehicle();

public abstract Weapon createWeapon();

public abstract Food createFood();

}

//具体工厂类,其中Food,Vehicle,Weapon是抽象类,

public class DefaultFactory extends AbstractFactory{

@Override

public Food createFood() {

return new Food();

}

@Override

public Vehicle createVehicle() {

return new Vehicle();

}

@Override

public Weapon createWeapon() {

return new Weapon();

}

}

//测试类

public class Test {

public static void main(String[] args) {

AbstractFactory f = new DefaultFactory();

Vehicle v = f.createVehicle();

v.run();

Weapon w = f.createWeapon();

w.shoot();

Food a = f.createFood();

a.printName();

}

}

61. java中内部类作用:

1)名字隐藏和组织代码

2)与C++不同的是,它还拥有外部类的所有元素的访权限,而不需要任何特殊条件。(c++嵌套类的设计不同,在C++中只是单独的名字隐藏机制,与外界对象没有联系,也没有隐含的访问权限)

3)接口虽然解决了多重继承问题,而内部类有效的实现了“多重继承”,也就说内部类允许“继承”多个类或者抽象类。

eg:

abstract class A{};

abstract class B{

void method() {}

};

class C extends A{

B makeB(){

return new B(){

void method(){

System.out.println("C.B.methd()");

};

};

};

}

4)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立

5)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口或继承同一个类

6)创建内部类对象的时刻不取决于外围类对象的创建时刻。

7)内部类并没有令人迷惑的"is -a"关系,它是一个独立的实体。

内部类最吸引人的地方:每个内部类都能独立继承自一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类都是没有影响。

62.java中嵌套类

1)静态嵌套类:因为静态嵌套类不具备指向外部类的引用,因此它不能直接访问外部类的非静态成员,只能访问其静态成员。

2)非静态嵌套类:和1)相反,它隐式保存了一个指向外部类的引用,因此它可以直接访问静态和非静态成员变量。

3)对于外部类,它也不能直接访问其内部嵌套类,而只能通过内部嵌套类对象的方式访问。

63.正常情况下,接口内不能放置任何方法实现,如果我们想要创建某些公共代码,使得它们被该接口的不同实现所共用,那么我们可以考虑使用接口内部类,因此接口默认是public和static的,因此其内部类也是static & public的

64.外部类B继承了外部类A,并"覆写"了A类的内部类,是否会造成像继承方法哪样覆写内部类? 答案是:不会,除非我们在B类中直接继承A类的内部类并重写。

eg:

class A{

A(){

print("A.A()");

new Inter();

}

class Inter{

Inter(){

print("A.Inter.Inter()");

}

}

}

class B extends A{

/*重写Inter,而非重载*/

class Inter{

Inter(){

print("B.Inter.Inter()");

}

}

/*直接继承基类的内部类,

class Inter extends A.Inter{

public Inter() {

// TODO 自动生成的构造函数存根

System.out.println("B.method()");

}

}

*/

}

new B();

输出:

A.A();

A.Inter.Inter();

65.局部内部类不能有访问说明符(但可以用final),因为它不上外部类的一部分,但是它可以访问当前代码块内的常量以及外围类的所有成员。使用局部内部类而不是匿名内部类的理由:

A:我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用实例初始化。

B:我们需要不止一个该内部类的对象。

66.每个类都会生成一个.class文件,对于内部类,它的命名规则是:外部类的名字,加上"$",再加上内部类的名字。对于匿名内部类,编译器会简单产生一个数字作为其标识符。eg:LocalInnerClasss$1.class

67.ArrayList:数组队列:它保存的元素类型为Object, 因为所有类的都继承自Object, 因此所有类的对象实例都可以被add到ArrayList中。

eg:ArrayList list = new ArrayList();

list.add(new Apple());

list.add(new Blanan);

同样,我们可以指定ArrayList保存的数据类型,它的用法和模块一致,eg:ArrayList list = new ArrayList();

68.Collection, List, Set和Map都是泛型接口,不是具体的类实现。当我们创建集合类时必须指定该集合类的数据保存类型

常用集合类的继承结构:

Collection<--List<--Vector

Collection<--List<--ArrayList

Collection<--List<--LinkedList

Collection<--Set<--HashSet

Collection<--Set<--HashSet<--LinkedHashSet

Collection<--Set<--SortedSet<--TreeSet

Map<--SortedMap<--TreeMap

Map<--HashMap

差异:

1.ArrayList: 元素单个,效率高,多用于查询

2.Vector: 元素单个,线程安全,多用于查询

3.LinkedList:元素单个,多用于插入和删除

4.HashMap: 元素成对,元素可为空, 允许使用null值和null键

5.HashTable: 元素成对,线程安全,元素不可为空

6. HashSet:存储结构和LinkedHashSet一样,为了查找效率都是使用的散列结构,它们的存储按字典序不自动排序

7. TreeSet:可以看成是只拥有Key,无value的TreeMap,存储方式为红黑树,存储按字典序自动排序

List:基于Array数组来实现, Set基于HashMap来实现。

eg:Vector vec = Vector(); //与C++语法一样

大多数情况下,从性能上来说ArrayList最好,但是当集合内的元素需要频繁插入、删除时LinkedList会有比较好的表现,但是它们三个性能都比不上数组,另外Vector是线程同步的。所以:

如果能用数组的时候(元素类型固定,数组长度固定),请尽量使用数组来代替List;

如果没有频繁的删除插入操作,又不用考虑多线程问题,优先选择ArrayList;

如果在多线程条件下使用,可以考虑Vector;

如果需要频繁地删除插入,LinkedList就有了用武之地;

如果你什么都不知道,用ArrayList没错。

技巧:当我们在创建一个集合对象时,应首先考虑将其向上转化成其接口类型:

eg: List list = new ArrayList();

而非直接:

ArrayList list = new ArrayList();

这样做有一个好处:

如果我们将要修改集合的类型,那么我们修改起来相当方便。只需这样即可:

List list = new Vector();

//list.getClass().getSimpleName(); //可以打印出list的集合类型

同样,这种方式并非总能奏效,因为某些类具有额外的功能,比如:LinkedList具有在List接口中未包含的额外方法,而TreeMap也具有在Map接口中未包含的方法。如果需要使用这些方法,就不能将它们向上转型为更通用的接口.

额外说明:

1.LinkedList提供了Queue的方法并实现了Queue的接口,因此LinkedList可以向上转型为Queue. Queue <-LinkedList

69.在Java数组的长度一经初始化就不可被改变.但是可以通过下标的方式去引用和修改其值。

70.Arrays.aslist():接受一个数组或是一个用逗号分割的元素列表(使用可变参数)并将其转换为一个List对象,但是在这种情况下,其底层表示依然是数组,因此不能调整其大小.

eg:List list = Arrays.asList(1,2,3);

//list.add(4); //错误:试图change list的 大小

71.HashSet,TreeSet,和LinkedHashSet都是Set类型。TreeSet存储在红黑树数据结构中,它对数据自动排序

72.Collections.shuffle(vec, rand); //将vec容器中东西随机打乱

73. vec.set(index, content); //替换容器下标为index的内容为content

74.Iterator设计模式:它的出现使得我们可以只使用容器,而不去关心容器的具体类型是List还是Set,

eg:List pets = new ArrayList();

Iterator it = pets.iterator();

1)它的remove()方法:是将迭代器上一个指向的元素删除。因此我们在删除前,需得先next(),在remove().

2)它只能单向向前遍历

3)it.next():返回当前指向的元素对象,然后指向下一个元素

4)it.hasNext();判断是否到迭代器的结尾, 如果是,返回false,否则返回true

注意:

Map没有iterator迭代器,但是我们可以先利用entrySet()将其转换为Entry类型(为Map的内部静态类,它是key,value的键值对类型,有点像c里面的 typedef pair Pair(k,v),一旦初始化就不可被改变)的Set类型,然后在调用其iterator迭代器进行迭代循环。

eg:

Map mmap = new TreeMap();

mmap.put(2, 0);

mmap.put(1, 0);

mmap.put(3, 0);

for(Iterator it = mmap.entrySet().iterator(); it.hasNext();)

{

Map.Entry s = it.next();

System.out.println(s.getKey() + ":" + s.getValue());

}

75.ListIterator:它是更强大的Iterator的子类型

1)它只能用于各种List类的访问(Vector, ArrayList,LinkedList)

2)它可以双向移动,next(), previous()

3)它拥有nextIndex():返回当前指向当前元素的下标,迭代器指针不移动

4)它拥有previousIndex():返回上一个指向元素的下标,如果迭代器指针当前指向第一个元素,则previousIndex()返回-1

5)它拥有previous():返回上一个指向元素,然后迭代器指针指向上一个元素

76.LinkedList:除了List的基本接口,还添加了可以使其用作栈,队列和双端队列的方法。

虽然这些方法有些在彼此之间只是名称差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加使用、

比如:

1)getFirst()和element()等价,它们都返回列表的第一个元素,而并不移除它,如果List为空,则抛出NoSuchElementException的异常,peek()方法与这两个方式只是稍有差异,它在列表为空时返回null.

2)removeFirst()和remove()也完全等价,它们移除并返回链表的头,而在链表为空时抛出NoSuchElementException.poll()稍有差异,它在链表为空时返回null

3)addFirst():在链表头部插入元素

4)add和addLast相同,在链表尾部插入元素

5)removeLast():移除并返回链表的最后一个元素,当链表为空时抛出NoSuchElementException、

6)它支持以List作为构造函数的入参

77.Java中,有两个地方定义了Stack,分别为java.util.Stack 和net.mindview.util.Stack。但是需要注意的是,前者由于Java1.0设计欠佳导致它内部的Stack并没有任何的栈公共接口,比如:pop(), push(), peek()等,它更像一个纯粹的List(因为它基于List实现,所以该Stack具有List的所有接口);要想使用“栈”,请显示import net.mindview.util.Stack

78.Set:Set与Collection具有完全一样的接口。因此它没有任何额外的功能,实际上Set就是Collection,只是行为不同而已。

79.TreeSet和TreeMap默认都是按照字典序排序的,如果我们需要忽略其大小写,则应该向TreeSet/TreeMap的构造器中传入String.CASE_INSENTIVE_ORDER.

eg:

Set words = new TreeSet(String.CASE_INSENSITIVE_ORDER);

words.add("2");

...

80.Queue:先进先出容器,因为LinkedList提供了方法以支持队列的行为,并且实现了Queue接口,因此LinkedList可以作为Queue的一种实现,可以通过将LinkedList向上转型为Queue

Queue q = new LinkedList();

方法介绍:

1)Offer(T t):将元素插入到队尾,如果插入失败则返回false

2)peek和element():都在不移除的情况下返回队头,但是peek()在队列为空时返回null, element会抛出NoSuchElementException异常

3)poll()和remove()都移除队头,但是poll()在队列为空时返回null, 而remove()会remove()会抛出NoSuchElementException异常。

番外:自动包装机制会自动将nextInt()的int结果转换为Queue所需的Integer对象,将char转换为queue所需的Charactor对象。Queue接口窄化了LinkedList的方法的访问权限。

81.foreach语法多用于数组和Collection对象。之所以支持,是Java SE5中引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator()方法。并且Iterable接口被foreach用来在序列中移动。因此任何实现了Iterable的类,都可以将它用于foreach语句。注意:这并不意味着数组肯定也是一个Iterable!!!!

class Student implements Iterable{

private String[] cStrings = {"22", "23"};

@Override

public Iterator iterator() {

// TODO 自动生成的方法存根

return new Iterator() {

private int index = 0;

@Override

public boolean hasNext() {

return index < cStrings.length;

}

@Override

public String next() {

// TODO 自动生成的方法存根

return cStrings[index++];

}

@Override

public void remove() {

// TODO 自动生成的方法存根

}

};

}

82.instanceof关键字,判断变量类型

String obj;

if (obj instanceof String){

...

} elseif(obj instanceof Integer){

...

}

83.getFields()和getDeclaredFields()

两者皆用来获取某个类声明的成员变量,返回一个数组对象

getFields()获取某个类的所有的公共(public)成员变量,包含父类。

getDeclaredFields()获取某个类的所有声明的成员变量,包含public, private和proteced, 但是不包括父类的声明的。

Class Bean{

pubilic int a;

private String[] str;

}

Field[] f = Bean.class.getDeclaredFields();

for (int i = 0; i < f.length; i++) {

print(f[i].getType()); //获取类字段名类型

print(f[i].getName()); //获取类字段名

print(f[i].getType().getName()); //获取类字段名类型,并将其结果转换为String

if (f[i].getType().isArray()) {

...

}

}

同样的类似还有getConstructors() 和getDeclaredConstructors(), getMethods()和getDeclaredMethods().

getConstructors():返回一个数组对象,用来获取某个类声明的所有公共的构造函数,包含父类

getDeclaredConstructors():返回一个数组对象,用来获取某个类声明的构造函数,包含public, private,proteced,但是不包含父类的声明字段。

在class类中加个Declared(声明的)代表本类,继承,父类都不包含。

84.Field.setInt(obj, value); 给obj类对象实例的某非private字段赋值,

Field [] f = obj.getClassName().getDeclaredFields(); //或者使用getFields();

它等价于c中的, obj[f[i].getName()] = value;

实例:

class Bean{

int id;

String name;

}

...

{

Bean bean;

Field [] fields = bean.getDeclaredFields();

for(Field field:fields){

if (field.getType().getName() == "int" && field.getName() == "id"){

field.setInt(bean, 2);

}else if (field.getType().getName() == "java.lang.String" && field.getName() == "name"){

field.set(bean, "zql"); //注意:没有setString()方法,详情请查阅java api

}

}

结果:

bean.id=2; bean.name="zql";

}

85.java.util.UUID.randomUUID(): (UUID: Universally Unique identifier:) 生成一个16进制36字节长度的全球唯一标识符,它保证对同一时空的所有机器都是唯一的。

86.java.io.DataInputStream.skipBytes(int n) 方法从输入流中的数据跳过n个字节。该方法不会抛出EOFException。

87.String类的“+”与“+=”是Java中仅有的两个重载过的操作符,而java并不允许程序员重载任何操作符。

88.String,StringBuffer和StringBuilder区别

String是字符串常量,其对象不可变。后两者都是变量,允许对象改变。StringBuffer是线程安全的,StringBuilder(Java SE5新增)是非线程安全的。

String在进行字符串拼接时大多数时候都会每进行一个“+”操作申请引用一次StringBuilder临时变量来存储。但是后两者都只申请一次。

处理速度:StringBuilder >StringBuffer > String

89.如果你希望在toString()方法中打印出对象的内存地址,正常的方式应该怎么做呢?

在c/c++中,通过使用this关键字,但是在java中,编译器会将this转换成调用toString()方法,如果此时在toString()调用this,就会造成无休止的递归。

如果想要打印出对象的内存地址,不应该使用this,而是应该调用super.toString()方法。这才是负责此任务的方法。类缺省的toString()方法就是如此。

String toString(){

return super.toString();

}

90.在java中,String的getBytes()方法是得到一个操作系统默认的编码格式的字节数组。这个表示在不同os下,返回的东西不一样。

String.getBytes(String decode)方法会根据指定的decode编码返回某字符串在该编码下的byte数组表示,如

byte[] b_gbk = "中".getBytes("GBK");

byte[] b_utf8 = "中".getBytes("UTF-8");

byte[] b_iso88591 = "中".getBytes("ISO8859-1");

将分别返回“中”这个汉字在GBK、UTF-8和ISO8859-1编码下的byte数组表示,此时b_gbk的长度为2,b_utf8的长度为3,b_iso88591的长度为1。

而与getBytes相对的,可以通过new String(byte[], decode)的方式来还原这个“中”字时,这个new String(byte[], decode)实际是使用decode指定的编码来将byte[]解析成字符串。

String s_gbk = new String(b_gbk,"GBK");

String s_utf8 = new String(b_utf8,"UTF-8");

String s_iso88591 = new String(b_iso88591,"ISO8859-1");

通过打印s_gbk、s_utf8和s_iso88591,会发现,s_gbk和s_utf8都是“中”,而只有s_iso88591是一个不认识的字符,为什么使用ISO8859-1编码再组合之后,无法还原“中”字呢,其实原因很简单,因为ISO8859-1编码的编码表中,根本就没有包含汉字字符,当然也就无法通过"中".getBytes("ISO8859-1");来得到正确的“中”字在ISO8859-1中的编码值了,所以再通过new String()来还原就无从谈起了。

有时候,为了让中文字符适应某些特殊要求(如http header头要求其内容必须为iso8859-1编码),可能会通过将中文字符按照字节方式来编码的情况,如

//编码:将"中"先转换为UTF-8格式字节数组,然后再将其转换为ISO8859-1格式并存储在string中

String s_iso88591 = new String("中".getBytes("UTF-8"),"ISO8859-1"),

//解码:

String string = new String(s_iso88591.getBytes("ISO8859-1"), "UTF-8");

print(string) //输出:中

同样,如果汉字是通过GBK转换成的ISO88591-1格式,那么在还原中,也应由IOS88591-1还原为GBK。

我们可以通过这些先编码,再解码的方式来获取当前环境对字符串的编码格式, 解码后能正常还原(equal 编码前字符)则采用该编码格式。

byte[] buf = "中".getBytes();

String string2 = new String(buf, "GBK");

if(string2.equal("中"))

return "ok";

等价于:

System.out.println(System.getProperty("file.encoding")); //输出:GBK

91."=="和equal方法的区别

1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比较的是他们的值。

2.复合数据类型(类) 当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,equal是比较的值

92.String.intern()方法:虽然返回的依然是String内容,但是它做了一个小动作:检查字符串池中是是否存在String这个字符串,如果存在,则返回池里的引用;如果不存在,则将该String添加到字符串池中,然后在返回它的引用

eg:

String s1 = "month";

String s2 = new String("month"); //此时,明显s1,s2指向的是不同的对象

print(s1 == s2) //false

s2 = s2.intern()

print(s1 == s2) //true

93.java.util.Formatter类是JDK1.5新增的类库,功能很强大,主要用来格式化文本。比如数字,日志,金额,宽度,对齐方式等

eg:

Formatter formatter = new Formatter(System.out); //初始化时需要表明输出的位置

formatter.format("%-10s %-5d\n", "monty", 1);

formatter.format("%-10s %-5d\n", "htl", 123);

monty 1

htl 123

-:左对齐, 默认是右对齐方式

%s:字符串

%d:数字[0-9]

94.Process进程问题

Java中Runtime.getRuntime().exec (String cmd)或者new ProcessBuilder(String cmd).start()都可以产生子进程对象Process。通过调用Process对象的waitFor()方法可以使主进程进入等待状态,直至子进程执行完毕,再进行下一步工作。如果对子进程处理不当,有可能造成主进程阻塞,整个程序死掉。

java Api中关于Process说的是:

ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获取相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。

创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。

在对getOutputStream(),getInputStream(),getErrorStream()的描述中,有个注意事项:对其输出流和错误流进行缓冲是一个好主意!嗯,好抽象啊!

问题正在于此,Process.getInputStream()和Process.getErrorStream()分别返回Process的标准输出流和错误流,两个流如果处理不当,其缓冲区不能被及时清除而被塞满,则进程被阻塞,即使调用Process.destory()也未必能销毁被阻塞的子进程。

方法1:通过启动两个线程来并发地读取和处理输出流和错误流

方法2:用ProcessBuilder的redirectErrorStream()方法合并输出流和错误流。

int rs = 0;

String command="test.bat arg1 arg2";

//对于windows

String[] cmds = {"cmd", "/c", command};

//对于linux

String[] cmds={"/bin/sh", "-c", command};

ProcessBuilder builder = new ProcessBuilder(cmds);

//合并错误输出流到标准输出流

builder.redirectErrorStream(true);

Process process = builder.start();

BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));

String output = null;

while (null != (readLine = br.readLine()))

{

//todo:可以不需要做任何事情,目的仅仅是清空缓存内容,避免阻塞

//print(output);

}

rs = process.waitFor();

return rs;

95.java.lang.object类

object是所有类的超类这些基本知识就不再赘述

现在说下它的本质,它指向对象的内存地址,这些有点像c中的指针,可以改变object指向的内存地址,也可以改变其内存地址中的值(通过反射机制)。

基于此,我们不难发现,它的equal方法和"=="比较应该是完全相同的,查看API源码也基本证实了这点

public boolean equals(Object obj) {

return (this == obj);

}

eg:

//通过反射机制修改字符串的值,该方法操作修改的是内存地址中的内容

String str = "123";

//获取所有string类型的"value"属性,该属性中保存了string字符串的值

Field field = str.getClass().getDeclaredField("value");

//忽略访问权限,因此string的value属性是private && final的,不能直接访问和修改

field.setAccessible(true);

//获取str的值

Object obj = field.get(str);

char [] chr = (char [])obj;

for(int i = 0; i < chr.length; i++){

chr[i] = '2';

}

//将str的值修改为chr中的内容,此方式不能调整字符串的长度,比如原始字符串长度为4,chr长度为5,则只能截取前4个字节,如果要修改字符串的长度,则需同步修改string的"count"和"value"属性

field.set(str, chr);

print(str); //str="222";

Field field2 = str.getClass().getDeclaredField("count");

field2.set(str, 10); //将str长度修改为10

96.正则表达式的() [] {}有不同的意思。

() 是为了提取匹配的字符串。表达式中有几个()就有几个相应的匹配字符串。

(\s*)表示连续空格的字符串。

[]是定义匹配的字符范围。比如 [a-zA-Z0-9] 表示相应位置的字符要匹配英文字符和数字。[\s*]表示空格或者*号。

{}一般用来表示匹配的长度,比如 \s{3} 表示匹配三个空格,\s[1,3]表示匹配一到三个空格。

(0-9) 匹配 '0-9′ 本身。 [0-9]* 匹配数字(注意后面有 *,可以为空)[0-9]+ 匹配数字(注意后面有 +,不可以为空){1-9} 写法错误。

[0-9]{0,9} 表示长度为 0 到 9 的数字字符串。

97.BufferedReader的用法(分享)

在网在看到挺好的,就转来和大家分享了!

public class BufferdReader extends Reader

从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。

通常,Reader 所作的每个读取请求都会导致对基础字符或字节流进行相应的读取请求。因此,建议用 BufferedReader 包装所有其 read() 操作可能开销很高的 Reader(如 FileReader 和 InputStreamReader)。例如,

BufferedReader in

= new BufferedReader(new FileReader("foo.in"));

将缓冲指定文件的输入。如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节,并将其转换为字符后返回,而这是极其低效的。

可以对使用 DataInputStream 进行按原文输入的程序进行本地化,方法是用合适的 BufferedReader 替换每个 DataInputStream。

import java.io.InputStreamReader;

import java.io.BufferedReader;

public class BufferedReaderDemo

{

public static void main(String[] args) throws java.io.IOException{

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

System.out.println("请输入一段文字,可包含空白:");

String text = br.readLine();

System.out.println("您输入的文字:"+text);

}

}

98.java.util.Scanner是java 5的新特征,主要功能是简化文本扫描。这个类最使用的地方是获取控制台输入,其他的功能都比较鸡肋。

用法:

java.util.Scanner scanner = java.util.Scanner(System.in);

while(scanner.hasNext()){

print(scanner.Next());

}

print("end");

上例用以监控控制台并等待其输入,但是它是回车打印之后,并不会结束而是继续等待,直到使用“ctrl+z”返回才会输出打印"end"。如果需要结束则应该在循环中使用计数然后break;

这点没有BufferedReader人性化。

另外还有一点,Scanner可以用usrDelimiter()来设置分隔符,同时,还有一个delimiter()方法来返回当前分隔符使用的Pattern对象。

Scanner的构造器可以接受任何类型的输入对象,包括File对象。InputStream,String或者Readable对象。

99.Modifier类:它在反射中用来判断类或者变量,方法的修饰符。

它有如下几种方法:

boolean Modifier.isPrivate(int mod): //判断是否为私有,其中mod是用来代表修饰符属性的Int值,它可以通过getModifiers方法得到

boolean Modifier.isPublic(int mod):

boolean Modifier.isProtected(int mod):

...

它还有很多类似的判断方法,

除此之外,它还可以打印出修饰符的字符串类型

String Modifier.toString(int mod);

class P{

public int a;

private int b;

public void method();

...

}

.....................

P pk = new P();

//获取类的成员变量,并以"成员变量名->修饰符->修饰符mod值"

Field[] fields = pk.getClass().getDeclaredFields();

for (Field field : fields) {

println(field.toString() + "->" + Modifier.toString(field.getModifiers()) + "->" +field.getModifiers());

}

java.lang.reflect.Method[] methods = pk.getClass().getDeclaredMethods();

for (java.lang.reflect.Method method : methods) {

println(method.toString() + "->" + Modifier.toString(method.getModifiers()) + "->" + method.getModifiers());

}

.....................

输出:

public int demo3.ClassDemo3$1P.a->public->1

private int demo3.ClassDemo3$1P.b->private->2

public void demo3.ClassDemo3$1P.method()->public->1

100.java static{}域

用来初始化所有的静态成员变量,它在默认初始化之前被执行,另外拥有static{}域的类不能为内部类,否则编译报错

java中static域均是在类加载后完成的,而java本身是一种运行时加载的动态语言(同样的动态语言有java, python,perl等),因此static{}域的执行是在调用构造器之前,但需要注意一点,如果该类没有new 出实例或是调用其静态方法/属性,则static{}域也不会被执行,这不是bug,而是java自身的性能优化机制-尽可能节约开销,减少不必要的浪费

class A{

A(){

System.out.print("A:A()");

}

static{

System.out.println("ok");

a = 2;

}

static int a=1;

}

println(A.a);

new A();

输出:

ok

1

101.Java反射机制和Class.forName(), 类名.class(), 实例对象.getClass()的区别

相同点:它们三个都是用来获取一个类的Class对象的引用,如果该类有多少实例,它的Class对象是唯一的.forName是一种静态方法,它用来加载类。

每个java程序在执行前都必须经过编译,加载,链接和初始化几个阶段。

加载:查找并加载类的二进制数据到JVM中,并将其放在运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象,病来封装类在方法区的数据结构。

链接:

-验证:确保被加载类的正确性

-准备:为类的静态变量分配内存,并将其初始化为默认值。

-解析:将类中的符号引用转换为直接引用

初始化:

-赋值:java中代码的初始值

-构造:调用构造函数

那么问题来了?,static静态域是在何时进行加载或者执行的???

..................................................................................

class A{

A(){

println("A::A()");

}

static{

System.out.println("ok");

a = 2;

b =3;

}

static int a;

static int b=1;

}

println(A.a);

println(A.b);

输出:

ok

2

1

从上面结果可以看出,static静态域的加载执行应该是在"链接-准备"之前,否则输出应该是2,3而不是2,1

即,类的实际加载顺序是:

加载 -> static域的加载/执行 ->链接->初始化

或者是

加载

-常规加载

-static域的加载/执行

链接

-验证:确保被加载类的正确性

-准备:为类的静态变量分配内存,并将其初始化为默认值。

-解析:将类中的符号引用转换为直接引用

初始化:

-赋值:java中代码的初始值

-构造:调用构造函数

.....................................................................................

接下来我们分别调用类名.class, Class.forName()

println(A.class);

println(Class.forName("demo3.A"));

输出:

class demo3.A

ok

class demo3.A

从上面得出:

1.类名.class方法只是加载该类,但不会执行该类的static静态块,而Class.forName()加载该类,并执行/加载该类的static静态块(实际上它还包括链接),当然Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。

2.一个类的Class对象只有一个,无论用什么方法获得,它都是唯一的。

3.类名.class和Class.forName()获取一个类的Class对象时,不需要创建该类的实例,因为该类的构造函数并未被执行!而实例对象.getClass()则必须是建立在实例对象之上。

.....................................................................................

上面这个列子:

Class.forName(XX).newInstance();

类名.class.newInstance();

这两者的输出一样(都执行了static静态块的加载/执行),且调用同一个newInstance()方法来初始化实例。类名.class实际上等价于Class.forName(),即也是进行了"加载"和"链接"两个步骤。唯一的区别是前者不加载static静态块,也不提供其接口。后者缺省加载static静态块,且提供了其重载接口用以控制static静态块是否加载。从这里来看,Class.forName()更具有灵活性!

......................................................................................

引申:new 和newInstance()方法区别???

相同点:它们都是用来初始化一个类实例的。

从jvm的角度看,使用new创建一个类对象时,这个类可以没有被加载,但是使用Class对象的newInstance()方法的时候,我们必须保证:

1)这个类已经被加载

2)这个类已经被链接

实际上,这两步正是Class静态方法的forName()方法所完成的,这个静态方法调用了启动类加载器,即加载java API的那个加载器。

现在我们可以发现,Class对象的newInstance()实际上把new分解上两步,将首先调用Class加载方法加载并链接某个类,然后实例化。

这样做的好处是显而易见的,我们可以在调用Class.forName()时获取更好的灵活性,这也提供给了一种降耦手段。

最后用最简单的描述来区分new关键字和newInstance()方法的区别:

newInstance: 弱类型。低效率。只能调用无参构造。 该方法告诉JVM,我虽然不知道类的类型,但是你必须安全的创建它的实例。

new: 强类型。相对高效。能调用任何public构造。

102.如何在不明确子类的父类类型的前提下创建子类父类的实例?

Java 泛型编程提供了很好的方法

eg:

class people{

...

}

class Student extens people{

...

}

//Class 表示创建某个类对象的引用。它是一个泛型

//定义一个泛型类,它被限定为Student的父类

Class cls = Student.class.getSuperclass();

//下面这种写法看上去是合理的,但是jvm并不允许这样做,虽然明确知道它父类类型,但实际上它返回的是Objcet类型,

//Class cls = Student.class.getSuperclas(); //错误

object = cls.newInstance() //注意它的返回值并不是精确类型,而是统一的object类型

同理,我们可以通过泛型来运用于派生类

Class cls = Student.class(); //java指提供了getSuperClass方法,却没有提供getDeriveClass方法,因为派生类类型是不可预估的

Object obj = cls.newInstance();

((Student)obj).write(); //调用派生类方法

103.Java SE 5中新转型语法:cast,它允许我们通过Class对象引用来进行类型转化,这被证明是一种罕见情况--整个Java SE5中,只有一处使用了cast() (在com.sun.mirror.util.DeclarationFiler中)

class people{}

class student extends people{}

people p = new student();

Class sType = student.class;

student s = sType.cast(p);

或者

student s = (student)p;

如果类型转化失败,就会抛出一个ClassCastException异常。

104. x instanceof y:用于检查x对象是否从属于y类型

这里的从属有几层含义:

1.派生类对象从属于基类类型

2.派生类对象的基类指针从属于基类类型

3.派生类对象的基类指针从属于派生类类型

4.基类对象不从属于派生类类型

在进行类型转换之前,进行instanceof判断是非常有必要的,这样可以有效避免ClassCastException异常的发生,如果x对象为null,其返回都是false

105.RTTI和反射之间的真正区别:对RTTI来说,编译器在编译时打开和检查.class文件(换句话说,我们可以用"普通"方式调用对象的所有方法),而对于反射机制来说,.class文件在编译时是不可获取的,所以在运行时打开和检查.class文件。

106.String1.indexOf(String2):查找字符串2在 String1中首次出现的位置。如果没有出现,则返回-1.

107.在JVM中用来判断类的唯一标识是:类名,类所在的包名和类加载器。需要注意的是,用同一个类的加载器的不同实例去加载相同的类,JVM也会认为它们是不同的。

.................................................................................

package com.example;

public class Sample {

private Sample instance;

public void setSample(Object instance) {

this.instance = (Sample) instance;

}

}

..................................................................................

public void testClassIdentity() {

String classDataRootPath = "C:\\workspace\\Classloader\\classData";

FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);

FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);

String className = "com.example.Sample";

try {

Class class1 = fscl1.loadClass(className);

Object obj1 = class1.newInstance();

Class class2 = fscl2.loadClass(className);

Object obj2 = class2.newInstance();

Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);

setSampleMethod.invoke(obj1, obj2);

} catch (Exception e) {

e.printStackTrace();

}

}

输出:

java.lang.reflect.InvocationTargetException

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

at java.lang.reflect.Method.invoke(Method.java:597)

at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26)

at classloader.ClassIdentity.main(ClassIdentity.java:9)

Caused by: java.lang.ClassCastException: com.example.Sample

cannot be cast to com.example.Sample

at com.example.Sample.setSample(Sample.java:7)

... 6 more

了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。

不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到,后面会详细介绍。

108.只有在静态类型或者顶级类型中才能声明静态方法

109.模板方法设计模式:

模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。,没有关联关系。

因此,在模板方法模式的类结构图中,只有继承关系。

模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。

代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。

你可能感兴趣的:(学习札记)