java基础
<p> 抽空把java基础部分内容通过自己的理解整理如下,心想着等我技艺升级后在回过头来可能会有不一样的收获呢 ,这也是极好的。 同时也想请大家慷慨指出下面错误、遗漏、或不准确的地方,谢谢大家!</br>
如果大家对某一块有不一样的理解,也请一并留言!</br>
<b>再次感谢!</b>
</p>
java基础笔记
1. java 关键字(也称为保留字)
这里只是罗列出我们工作可能用到的.
public | class | new | import | package | static | final |
---|---|---|---|---|---|---|
synchronized | private | default | protected | return | break | continue |
for | while | do | goto[1] | enum | interface | extends |
abstract | implements | int | long | double | float | short |
byte | char | boolean | super | if | else | case |
switch | finally | instanceof | this | throws | transient | try |
catch | void | const | throw | - | - | - |
2. java 的CLASSPATH
java 的classpath 用来设置JVM加载类(class) 的路径,一般设置值为.
表示从当前路径开始加载。JVM默认为从当前路径开始加载.但是我们依然需要设置这个值,因为,如果此路径被指定为其他路径则当前路径下class 就不会被加载了。 比如我们设置CLASSPATH='/home/test/javatest/'
, 则我们将javatest下生成的class文件移动到home
目录下,执行java
命令会提示找不到xxx class
。
3.java 的path
path
在系统里用来帮助系统找到应用程序可执行文件.这里我们是为了方便找到javac
和java
命令. 包含jdk/bin/和jre/bin/ 目录下的命令
4. 关于类文件名和public 修饰的类名一致
我们要求类文件名要和public
修饰的类名一致,这是为什么呢?我们可以尝试如下操作:
我们尝试将文件名和public
修饰的类名不一致,编译后我们观察class
文件.我们会发现class
文件名称是我们public
修饰的名子,不会编译成为这个类所在的文件的名.所以会有找不到编写类的可能. 同时也是为了方便找到对应文件所在的类。
5.标识符
标识符是我们平常编写代码经常使用到的.我们给类、变量等去名子的时候需要遵守java标识符规范。
标识符规范我们:只能用数字、字母、下划线(_) 、$组成,且不能以数字开头或使用java关键字.
6.数据类型
java 数据类型大致分为以下两大类型:
-
基本数据类型:
- 整型数据类型:byte、int、short、long -> 默认值 : 0
- 浮点数据类型(实型): float、double -> 默认值:0.0
- 字符型: char -> 默认值:‘\u0000’
- 布尔型: boolean -> 默认值: false
-
引用类型:
数组、类、接口 -> 默认值: null
这里有一个需要记住的是
char
的取值范围是-128 ~ 127
。基本数据类型中,数据最大值和最小值是相邻
的,比如说: int 数据最大值加一则变为最小值. 同理,最小值减一则变为最大值。这个特性很重要 。
说明
Byte、Short、Integer、Long、Character、Boolean 这五种包装类默认创建了数值[-128 ~ 127] 对应类型的缓存数据,但是超出范围后仍然会创建新的对象。Float 、Double 并没有实现常量池技术。
比如:
Integer i1 = 40; //在编译的时候会直接放入常量池中
Integer i2 = new Intege r(40);//会创建新的对象
Integer i5 = new Integer(0);
System.out.println("i1 == i2 "+ (i1 == i2)); //false
System.out.println("i1 == i2+i5 "+ (i1 == i2+i5)); //true
System.out.println("i1 == i2 "+ (40 == i2)); //true
Integer i3 = 129;
Integer i4 = 129;//new Integer(129);
System.out.println("i3== i4 "+ (i3 == i4));// false
但是有一个特性,普通操作符不使用于包装类,所以包装类做普通运算的时候会自动拆箱进行运算。例如 Integer 和int 比较 或者Integer 和int 做 + - * /运算都会自动拆箱后运算。所以结果会跟我想的不一样,我们需要注意.
7. 运算符
- 逻辑运算符
& 、&& 、 | 、|| 解释:
&
: 逻辑运算的时候会判断所有表达式的值,其结果是,如果有一个为false 则结果为false。
|
: 逻辑运算的时候会判断所有表达式的值,其结果是,如果有一个为true 则结果为true。
&&
: 称为短路与,其运算规则为:如果遇到一个false 值,则不再计算之后的表达式的值,返回其结果为false。
短路与的意义可以用一下程序片段解释:
//
int n =12;
if (n < 0 && n/0> 2){
System.out.println("满足条件.");
}
显然这段程序不会出错,因为地一个表达式值为false,则不会再判断之后的表达式。也就不会出错。
||
同理.
||
: 也称为短路或,其运算规则为: 如果遇到一个true值,则不再计算之后的表达式的值,返回其结果为true。
- 位运算
&、| 解释:
& 在位运算中,规则为:其中一个为0
则结果就为0
, 两个都为1
结果才为1
。
| 在位运算中, 规则为:其中一个为1
则结果就为1
,两个都为0
结果才为0
。
ex:
**& **
10010
&
10011
-------------
10010
|
10010
|
10011
-----------
10011
- 三目运算符
三目运算格式为: 类型 变量 = 逻辑表达式 ? 值1 : 值2;
解释:
当逻辑表达式的值为true
时 则将值1赋值给变量。否则将值2赋值给变量。
此多运用于简单的if ...else .....
判断。简化代码。 但是不可以滥用。
8. 程序结构
- 顺序结构
按照代码编写顺序执行。
- 分支结构
可以直接理解为
if
判断 或者多个if
或者if ... else ....
或者if ...else if ... else ... .....
等等.
这里需要重视的是分支结构里当有重叠条件匹配时,只会进入首次匹配条件,不会再进入第二次匹配上的条件。
- 循环结构
使用
while
、do ...while
、for
来控制代码执行顺序的代码结构。
这里我们可以使用
break
、continue
加标签的形式控制多层循环。代码实例如下:
outer:
for(int i=0;i<10;i++){
inner:
for(int y=0;y<8;y++){
for(int n=0;n<5;n++){
if(n ==3 ){
continue outer;
}else if(n==4){
break inner;
}
}
}
}
以上代码顺序执行解释如: 当n 为3时 则从最外层的下一次循环开始执行。则i值为1的这次循环。
当n 为4时,则跳出y变量所在for循环进入i 变量所在for循环的下一次循环。
- 多分支结构
switch 语句用于多分支结构,使用基本类型值匹配条件,执行语句,避免编写复杂的if...else ....
使代码更易维护和查看。
格式为:
switch(值){
case v1: [代码块;]
[break];
case v2: [代码块;]
[break];
......
default:代码块;
}
以上 []
里面的内容代表可选。我们可以不用做任何操作,语法上是允许的。
- 假如我们没有在没一个匹配上的
case
之后加上break
组织程序继续执行,则程序会在首个匹配上的case
之后继续执行之后的所有case
. 导致得到你预想之外的结果。 - 假如程序没有匹配上任何一个
case
如果有编写default
语句,则自动执行default
代码块,如果没有则走完所有case
后退出switch
。
关于for'
和while
使用上的选择: 一般,我们在知道循环次数的时候首先选择 ‘for' 控制循环,在不知道循环次数,但是知道循环结束条件的时候考虑使用while
类循环,
9. 方法
- 方法的重载
平时我们可能需要某一类的功能,区别只是参数类型或者个数有所区别,那么我们可以考虑方法的重载,不用编写多个方法。例如: 我们需求如下:
- 需要两个int 型数据相加的方法;
- 需要三个int 型数据相加的方法;
- 需要两个double型数据相加的方法;
以上需求我们实现最原始的方式是给三个方法取三个不同的方法名以适应以上三个需求。假如我们考虑重载(Overload) 则只需要同一个方法名,不同参数就可以了,如下:
public int add(int x,int y){
return x + y;
}
public int add(int x,int y,int z){
return x + y + z;
}
public double add(double x,double y){
return x + y;
}
以上为方法重载,在我们调用的时候直接调用
add(..)
即可,不用如此调用add1(...)
/add2(..)
以上使用了不同的返回值类型,我们一般在编写重载方法的时候不建议编写返回值类型不同的重载方法。虽然jdk没有如此强调。重载方法统一返回类型,有利用使用。
10. String 一些事儿
String 不是基本数据类型。String 是一个特殊的类。首先我们实例化对象有以下两种方式:
- 直接赋值实例化
String str = "字符串"; - 使用
new
实例化Stirng对象
String str = new ("字符串"); 这种实例化方式在内存分配上有如下问题:
首先,在堆内存中会分配一块内存存放匿名字符串对象 --- "字符串"。然后,关键字new
再次堆内存再一次分配内存给"字符串"; 然后使用str
引用指向刚才分配的堆内存。
至此开始分配的内存成为垃圾资源,等待GC回收。
需要注意String 字符比较中的==
和equlas
。前者是比较字符串内存地址,是数值比较,后者比较字符内容。
关于字符比较注意点: 我们使用equlas
比较字符串的时候应该避免未实例化对象调用方法。应该将未知字符对象作为参数使用。如:不应该 这样:
String var = "hello";
if(var.equlas("hello")){
//
}
**10.1 String 方法 **
善于查询JavaSE API。我可以打开在线java文档查询。
如图:
我们找到java.lang包下面的String 类.找到 Method Summary
这是普通方法表.
需要记住平常工作中比较常用的几类方法.
- 字符串查找:
方法名 | 返回值 | 描述 | 备注 |
---|---|---|---|
public int indexOf(String str) | 返回所查找的字符串的索引值,如果没有匹配上则返回-1 | 这个索引值是从0 开始的 |
只会返回第一次匹配上的字符串所在索引,从前往后查找 |
public int indexOf(String str,int fromIndex) | 返回所查找的字符串的索引值,如果没有匹配上则返回-1 | 这个索引值是从0 开始的 |
只会返回第一次匹配上的字符串所在索引,从指定索引处由前向后开始查找 |
public int lastIndexOf(String str) | 返回所查找的字符串的索引值,如果没有匹配上则返回-1 | 这个索引值是从0 开始的 |
需要注意的是如果匹配字符串为空串则依然会返回值 ,对于这个方法会返回this.length() 注意这里的 this 代表的含义 |
public int lastIndexOf(String str,int fromIndex) | 返回所查找的字符串的索引值,如果没有匹配上则返回-1 | 这个索引值是从0 开始的 |
从指定索引处开始 |
- 字符串截取
方法名 | 返回值 & 描述 |
---|---|
public String substring(int beginIndex) | 返回字符串,从指定索引处开始截取到末尾 |
public String substring(int beginIndex,int endIndex) | 从索引beginIndex开始截取到索引endIndex处结束,返回字符串 |
- 字符串拆分
方法名 | 返回值 & 描述 |
---|---|
public String[] split(String regex) | 返回字符串数组,这里支持正则表达式,所以需要注意转义,比如:. 等 需要"\\\\."
|
public String[] split(String regex,int limit) |
limit 指定拆分长度,默认从前向后拆分。即达到指定长度后,即使后面匹配拆分条件也不再拆分 |
-
字符串转换
- 字符串转字符
public char charAt(int index) 一般使用charAt(0) 将字符串转换为字符.返回char
。 - 字符串转字符数组
public char[] toCharArray() 将一个字符串转换为字符数组。 - 字符串转字节数组
public byte[] getBytes() 将一个字符串转换为字节数组。
public byte[] getBytes(Charset charset) 将一个字符串按照指定编码格式转换为字节数组。我们常用这个方法来转换编码。这个方法为JDK1.6版本新增。 - 其他类型到字符串的转换
public static String valueOf(boolean b) 将布尔值转换为字符串,如果为true
则返回"true"
。
public static String valueOf(char c) 将字符转换为字符串.
public static String valueOf(char[] data) 将字符数组转换为字符串.
public static String valueOf(char[] data,int offset,int count) 将指定位置的字符数组转换为字符串。
public static String valueOf(double d) 将double 转换为字符串。
public static String valueOf(float f) 将float 转换为字符串。
public static String valueOf(int i) 将int 转换为字符串。
public static String valueOf(long l) 将Long 转换为字符串。
public static String valueOf(Object obj) 将Object子类转换为字符串。参数可以是null。
public String toString() 从Object 继承而来。
- 字符串转字符
-
其他字符串方法
public String trim() 去掉字符串左右空字符串。
public String toUpperCase() 转大写字母
public String toLowerCase()转小写字母
public boolean startsWith(String prefix) 是否以指定字符串开始。
public boolean startsWith(String prefix,int toffset) 从指定索引开始,是否以指定字符串开始。
public String intern() 将来字符串入池,返回字符串,返回的字符串为入池后的字符串,为共享字符串。
public boolean endsWith(String suffix) 是否以指定字符串结尾。
public boolean isEmpty() 判断是否空字符串. JDK1.6新增 。
public int length() 返回字符串长度.不包含左右空字符串。
public boolean contains(CharSequence s) 是否包含指定字符串。 -
字符串比较
public boolean equals(Object anObject) 比较两个字符串内容是否相等。字符串必须完全一致。
public boolean equalsIgnoreCase(String anotherString) 忽略大小写的字符串内容比较。
public int compareTo(String anotherString) 比较两个字符串的大小,按照unicode 编码值比较。
public int compareToIgnoreCase(String str) 忽略大小写比较两个字符串的大小,按照unicode 编码值比较。 -
字符串替换
public String replace(char oldChar,char newChar) 将所有的oldChar
替换为newChar
。
public String replace(CharSequence target,CharSequence replacement) 将所有的target
替换为replacement
。 这里需要注意CharSequence
是String
的接口,不同的是不能使用new
实例化.可以直接赋值。如:CharSequence ch = "abc";
public String replaceAll(String regex,String replacement) 全部替换,同地一个方法,只是参数不同。
public String replaceFirst(String regex,String replacement)只替换第一次出现的匹配。同样支持正则。
11. 类与对象
面向对象有显著的三大特征:
-
封装
-
继承
-
多态
-
封装: 简单的来说就是内部实现对外不可见。常用的就是我们在简单java 类中经常使用private 修饰属性权限。这就是封装的典型使用。这样我们就不能在非本类中直接操作类信息,需要通过类对象操作类属性。
类实例化两种方式:
-
类型名称 对象名称 = new 类型名称();
//声明并实例化对象 -
类型名称 对象名称;
//声明对象
对象名称 = new 类型名称();
//实例化对象
在java中对内存分配有栈
和堆
之分。 栈 存储基本类型数据和引用类型数据变量。堆存储引用类型数据内容。引用类型数据操作都存在内存关系处理。需要谨慎。
比如,现在有一个类A 则以下表达式内存关系解释:
A a ;
在栈内存块中,声明一个变量a
没有任何指向,也可以认为a
现在指向null
。
a = new A();
在堆内存块中开辟一块内存空间,内容为A
中属性的初始值。并将a
指向它。
简单java类的编写:
- 属性全部使用 private 封装。
- 属性必须有setter & getter方法。
- 构造方法安装参数升序编写,必须有至少一个无参构造。
- 保留一个能输出本类全部信息的方法。
12. this 、super关键字
this
和super
的区别:
- 使用this 操作属性或方法,优先从本类查找,否则再从父类查找。this 可以构造方法互调用,但是必须留一个出口否则形成循环调用,必须放在构造方法的首行。this 指当前对象。
- super 存在继承关系中,指子类直接操作父类属性或方法。在实例化子类对象的时候必须先实例化其父类,默认会调用父类无参构造.
super();
必须放在构造方法的首行。
13. 方法的多态
- 重载(Overloading) : 发生在一个类中,方法名称相同,方法参数类型及个数不同可以称为方法重载,方法重载能根据不同的参数完成不同的功能。 重载可以改变方法的返回值,但是根据经验,要求重载方法不要改变其返回值。
- 覆写(Override) : 发生在继承关系中,方法名称、方法参数类型及个数相同、返回值相同可以称为覆写。覆写的方法在继承关系中,需要使用super 才可以调用父类方法,否则调用的是其子类覆写过的方法。
14.继承
继承是为了扩展新特性。在已存在类的基础上扩展。一般的,普通类之间不应该出现继承关系,因为已经实现的类不适合描述更抽象的事物,因为父类的描述范围要大于子类的描述范围。而且,普通类之间的继承在方法覆写上没有强制要求,可以选择性覆写。即可以选择不覆写普通父类中的方法。
- 抽象方法: 没有方法体的方法叫抽象方法。
- 包含至少一个抽象方法的类必须声明的为抽象类,且其子类必须覆其抽象方法。
- 一个类只能继承一个抽象或普通类,不能像C++一样可以实现多继承。如果需要实现多继承可以使用接口.
interface
。 - 抽象类不能直接实例化对象,它需要其子类来帮助实例化。利用类多态性向上转型实现抽象类实例化.
- 抽象类可以包含以下成员:普通方法、抽象方法、普通属性、全局成员变量、构造方法。
- 抽象类不能使用final 定义,因为抽象类必须被继承。final标识类,则该类不能被继承。
- 继承后,子类覆写的方法的访问权限不能比父类更严格。
- 子类可以继承父类的全部操作,父类的私有操作属于隐式继承,非私有操作属于显示继承。
- 父类的构造方法不能被子类继承。
- 抽象父类引用只能调用子类从父类继承来并覆写过的方法。普通父类引用调用(指向子类实例)调用子类没有覆写的父类方法时,直接调用父类方法。否则调用子类覆写过的方法。
- 不管是抽象类还是普通类,如果自定义了有参构造,且忽略了无参构造,那么子类继承后在构造方法中没有指定调用父类有参构造则不能编译通过,提示找不到父类无参构造。所以我们可以知道子类实例化之前会先调用父类无参构造(默认)实例化父类。
- 抽象类可以提供一个默认的实现,子类可以直接使用,也可以选择自己实现,覆盖父类对应实现。
-
抽象类定义 - 抽象类: 包含至少一个抽象方法的类为抽象类,包含至少一个抽象方法的类必须定义为抽象类。
关键字 :abstract
-
定义一个抽象类
abstract class A {
}
使用abstract
定义的类可以没有抽象方法。但是没有多少实际意义,如果不需要这个规范则打都不必要定义为抽象类。
- 抽象类组成结构:
- 成员变量(属性)
- 全局常量 - 构造方法
- 普通方法
抽象类不是用来直接实现业务功能的,是为了提供一组信息的抽象,规范行为的归属,即这一组抽象信息下具体信息的行为和此具体信息是完全符合的。因为抽象类中的抽象方法子类必须全部覆写,从而实现规范。利用向上转型达到信息统一。从表面上看,我们看到的都是抽象类而非具体类。因为抽象类中包含有抽象方法所以,不能直接实例化。
其实能用抽象类的地方基本都可以使用接口。且接口没有单继承局限。可能我们有些时候还需要成员变量,此时接口就组能为力了,因为接口不能出想全局常量以外的变量.
15. 线程
在Java中,实现多线程的方式有两种:
- 继承Thread 类,该类是Runnable接口的子类。并覆写
run()
方法. - 直接实现Runnable 接口,覆写接口的方法
run()
.
以上两种方式表面上看基本没有区别,不过,需要注意的是启动线程的功能并不在Runnable 接口,而在Thread类中,我们覆写的方法并不能启动多线程.只是一个普通的方法,我们称为线程体,run()
只是功能实现方法,且run()
是存在于线程内的,简单的说,run()
是在资源得到分配后才会执行的,而start()
不是,我们调用了start()
并不意味着这个线程就会立马执行,只是就绪状态,待抢占到资源后才会有机会调用run()
执行。所以,真正实现多线程的是Thread 的 start()方法,我们可以查看Thread 源码的start()方法,我们可以看到在start()方法实现中,调用各个平台的cpu资源调度本地方法实现多线程. 如下代码:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0 || this != me)
throw new IllegalThreadStateException();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
再看 内部调用的start0()
方法详情:
private native void start0();
从关键字native
可以知道这是一个本地方法,也就是非java语言实现方法,这是一个抽象方法,具体实现交给了JVM 调用各个平台实现。
从关键字synchronized
我们也可以看出,同一个线程在存活的时候不能再启动第二次.
多线程使用实例:
- 继承Thread:
class MyThread extends Thread {
public void run(){
System.out.println(" this is thread.");
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
mythread.run();
mythread.start();//启动线程
}
}
- 实现Runnable接口
class MyThread implements Runnable{
public void run(){
System.out.println("this is thread implements Runnable.");
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
new Thread(mythread).start();
}
}
从上面代码可以看出,我们不能像继承Thread类一样直接调用start()
方法,因为Runnable接口并没有start()
方法。所以我们需要借助Thread
类,因为我们前面说过,Runnable接口没有启动线程的功能,Thread 有。
new Thread(mythread).start();
这段代码我们可以看出,Thread 类的构造方法中接收了Runnbale接口实现类,那么我们看下API:
public Thread(Runnable target)
接收Runnable接口对象构造,我们大概能猜测到,我们覆写的run()
方法一直都是接口的,启动线程后调用的run()
方法也是接口的。
MyThread 、Thread、Runnable三者的关系如下:
其中MyThread 实现Runnable接口
Thread 也实现了Runnable 接口
覆写run()方法。
我们使用的时候代码如下:
new Thread(mythread).start();
也就是说真正实现类是Thread类,有点像代理设计模式,但是又似乎不是。正常的代理设计,我们调用真实业务操作应该是run()
方法。可是这里不是。所以,它并不是真正的代理设计模式,据说这是历史遗留的问题。从API可以看出Runnable接口和Thread都是是JDK1.0版本就有的,所以这个说法待考证。
- 总结
- Thread 是Runnable 的实现类
- 真正启动线程是Thread类
- run()方法只是线程体,并不是启动线程的方法。
- start()是启动线程的方法,同一个线程存活的状态下,不可以重复启动。
- 线程同步
- 需要线程同步,可以使用同步代码块或者使用同步方法,将共享资源操作方法进行同步。
class MyThread implements Runnable{
private int count = 7;
public void run(){
for (int i =0 ; i<20;i++){
if (count >0) {
System.out.println("this is thread implements Runnable."+ count--);
}
}
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
MyThread my1 = new MyThread();
new Thread(mythread).start();
new Thread(my1).start();
//
}
}
以上操作就出现了共享资源不同步的问题。我们可以看打印日志:
this is thread implements Runnable.7
this is thread implements Runnable.7
this is thread implements Runnable.6
this is thread implements Runnable.6
this is thread implements Runnable.5
this is thread implements Runnable.5
this is thread implements Runnable.4
this is thread implements Runnable.4
this is thread implements Runnable.3
this is thread implements Runnable.3
this is thread implements Runnable.2
this is thread implements Runnable.2
this is thread implements Runnable.1
this is thread implements Runnable.1
我们可以看到出现了两个线程抢到了同一个资源的问题。所以我们可以使用如下方法解决这个问题。看代码:
class MyThread implements Runnable{
private int count = 8;
public void run(){
for (int i =0 ; i<300;i++){
/**try{
Thread.sleep(1000);
}catch(InterruptedException e){
//
}**/
synchronized(this){
if (count >0) {
System.out.println("this is thread implements Runnable. threadName "+Thread.currentThread().getName()+" --- "+ count--);
}
}
}
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
new Thread(mythread,"A").start();
new Thread(mythread,"B").start();
new Thread(mythread,"C").start();
new Thread(mythread,"D").start();
new Thread(mythread,"E").start();
//
}
}
我们工作中,不到万不得已建议不要使用同步,通过其他方式或者方法绕过同步或者间接实现同步。
在实现线程的整体结构上很像代理设计模式,但是不完全是。
16. 反射
反射核心类Class
,通过Class
可以获得反射需要的全部操作。此亦是反射的源头。有下列三种方式获得Class
实例:
- 通过
Object
提供方法:
Date date = new Date() ;
Class<?> cls = date.getClass();
Object obj = cls.newInstance();//获得反射的实例对象, equals new Date();
//次代码会抛出异常.需要捕捉或者方法抛出.
- 通过
Class
提供方法:
Date date = new Date() ;
Class<?> cls = Date.class ;
Object obj = cls.newInstance() ;
- 通过
Class
提供方法:
Date date = new Date() ;
Class<?> cls = Class.forName("java.util.Date") ;//
Object obj = cls.newInstance() ;
这里我们可以根据第三种方法改造以往工厂模式:
老的工厂模式:
interface Person {
void say() ;
}
class Student implements Person {
public void say() {
System.out.println("我是一个学生.") ;
}
}
class Work implements Person {
public void say() {
System.out.println("我是一个工人.") ;
}
}
class Factory {
public static Person getInstance(String className) {
if ("student".equals(className)){
return new Student() ;
}
if ("work".equals(className)){
return new Work() ;
}
return null ;
}
}
public class RefDemo {
public static void main(String[] args) throws Exception{
Person per = Factory.getInstance("student") ;
//Person per = Factory.getInstance("work") ;
per.say() ;
}
}
以上代码明显耦合严重。如果我们新添加一个Person
子类,则不仅仅需要修改客户端代码,同时也需要在Factory
内部添加Person
新子类获得实例实现。而且,Factory
各种if
判断条件和客户端绝对一致,否则不能实现通过工厂获取子类实例.
下面我们通过反射实现工厂进一步解耦合:
package cn.palm.test;
interface Person {
void say() ;
}
class Student implements Person {
public void say() {
System.out.println("我是一个学生.") ;
}
}
class Work implements Person {
public void say() {
System.out.println("我是一个工人.") ;
}
}
class Factory {
public static Person getInstance(String className) {
Person per = null ;
try{
Class<?> cls = Class.forName(className) ;
per = (Person)cls.newInstance() ;
}catch(Exception e){
e.printStackTrace() ;
}
return per ;
}
}
public class RefDemo {
public static void main(String[] args){
Person per = Factory.getInstance("cn.palm.test.Student") ;
//Person per = Factory.getInstance("work") ;
per.say() ;
}
}
以上可以通过反射达到不修改工厂类添加新子类的目的。从而使代码更加灵活、健壮.
反射还有很多获取类结构中全部的信息。如 属性 、 构造方法、普通方法。
也可以获取继承来的属性或者方法。这些全部需要通过Class
获取.
-
break lable\continue lable 可以用到 ↩