Java Weed
平时在学习、应用Java的过程中,遇到的一些小知识,将它们收集到这里。杂草(weed)也不能丢弃嘛。(2009.09.16最后更新)
Primitive Data Type
boolean(2)
|
|
byte(8)
|
|
char(16)
|
short(16)
|
int(32)
|
float(32)
|
long(64)
|
double(64)
|
All Possible Combinations of Features and Modifers
Modifer
|
Class
|
Variable
|
Method
|
Constructor
|
FreeFloating Block
|
public
|
yes
|
yes
|
yes
|
yes
|
no
|
protected
|
no
|
yes
|
yes
|
yes
|
no
|
(default)
|
yes
|
yes
|
yes
|
yes
|
yes
|
private
|
no
|
yes
|
yes
|
yes
|
no
|
final
|
yes
|
yes
|
yes
|
no
|
no
|
abstract
|
yes
|
no
|
yes
|
no
|
no
|
static
|
no
|
yes
|
yes
|
no
|
yes
|
native
|
no
|
no
|
yes
|
no
|
no
|
transient
|
no
|
yes
|
no
|
no
|
no
|
volatile
|
no
|
yes
|
no
|
no
|
no
|
synchronized
|
no
|
no
|
yes
|
no
|
yes
|
Priority of Operators
一
|
+ - ++ -- ! ~
|
元
|
new (type)
|
二
|
* / %
|
|
|
+ -
|
|
|
<< >> >>>
|
|
|
< > <= >=
|
|
|
== !=
|
|
|
&
|
|
|
^
|
|
|
|
|
|
|
&&
|
元
|
||
|
三元
|
? :
|
赋
|
= *= /= %= += -= <<=
|
值
|
>>= >>>= &= ^= |=
|
Object中的equals方法用于比较两个对象是否在同一个地址。但Object的子类会重载这个方法,所以其它类中的equals方法的功能可能就会不一样了。
初始化(Initialization)
阐述对象被创建时的若干步骤,假设以类Dog为例。
[1]当首次创建类型Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态字段首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
[2]然后载入Dog.class(这将创建一个Class对象),有关静态初始化的所有动作都会执行。故,静态初始化只在Class对象首次加载的时候进行一次。
[3]当用new Dog()创建对象的时候,首次将在堆上为Dog对象分配足够的存储空间。
[4]这块存储空间被清零,就自动地将Dog对象中的所有基本数据都设置成了缺省值,而引用则被设置成了null。
[5]执行所有出现于字段定义处的初始化动作。
[6]执行构造器。
多态(Polymophsim)
private方法属于final方法。只有非private方法才可以被覆盖;在子类中,对于其基类中的private方法,最好采用不同的名字。
类X可以从它的直接父类(接口)中继承它的所有non-private的,并且没有被类X覆盖(override)和隐藏的方法(无论它是不是abtract)。
构造器并不具有多态性,它实际上是static方法。除了在构造器内,禁止在其它地方调用构造器。
构造器调用顺序
[1]在任何事情发生之前,将分配给对象的存储空间初始化为二进制的零。
[2]调用该类的父类构造器。这个步骤会不断地反复递归下去,首先是调用这种层次结构的根的构造器,然后是下一层子类,...,直到最低层的子类。
[3]按声明顺序调用该类的成员的初始化方法。
[4]调用该类的构造器的主体。
编写构造器的一条有效准则
用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。在构造器中唯一能够安全调用的方法是基类中的final,private(自动属于fianl方法),因为这些方法不会被覆盖。
class Dad {
String name = "Dad";
}
class Son extends Dad {
String name = "Son";
}
Son
的每一个对象将会有两个field
"name",一个是类Dad中的"name",另一个是类Son中的"name"。具体用哪个一个"name",则将由引用变量的类型来决定。即,对于
Dad x = new Son(); x.name引用的是类Dad中的"name "("Dad");如果是Son x = new Son();
x.name,很显然引用的是类Son中的"name"。而对于方法,则仅用override原理来处理即可。
Set
的使用
实际上Set就是Collection,只是行为不同。这是继承与多态的典型应用:表现不同的行为。
使用HashSet
必须为类定义equals()方法和hashCode()方法;
使用TreeSet时必须为类定义equals()方法。但作为一种编程风格,在覆盖equals()时,也要覆盖hashCode()。
正则表达式 (Regular Expression)
lookingAt()和matches()只有在输入的最开始处就与RE匹配时才会成功(true);matches()只有在整个输入都与RE匹配时
才会成功,而lookingAt()只要求输入的第一部分与RE匹配就会成功。(Bruce认为这几个方法名不是很直观!)
如何使JTable中的列不能被移动?
使用方法JTable.getTableHeader().setReorderingAllowed(false),即可使用户不能拖动表中的各个列。
改变GUI的Look&Feel后,需要更新GUI组件
SwingUtilities.updateComponentTreeUI(java.awt.Component)
创建java.util.Date对象
由于该类中的方法不利于日期的国际化,所以它的很多构造函数与方法都被deprecated了。这些相应的功能已经由java.util.Calendar提供。一般可以使用如下方法来创建java.util.Date对象:
java.util.Calendar calendar = new java.util.Calendar();
calendar.set(int year, int month, int date);
java.util.Date date = calendar.getTime();
UnmarshalException
曾经在使用RMI时,遇到过抛该异常的情况。当时是由于我的Remote类中的一个方法的返回值是“不可序列化”的。
具体情况就是,在设计的远程接口(该接口继承自java.rmi.Remote)中有一个方法的返回值是java.sql.ResultSet,但ResultSet对象是不可序列化的。因为ResultSet没有继承Serializable,而ResultSet的实现类又没有实现
Serializable
接口,那么ResultSet对象自然就不可序列化。
解决方法就是,将返回值更换成可被序列化的对象,如String。
从jar中读文件
要读取jar中的文件,不能使用一般的创建InputStream实例之类的方法,因为InputStream没有这个能力。而需要将这个文件作为“资源”进行读取,即使用方法Class.getResourceAsStream(String name),请参见该方法的API文档
。下面会使用一个例子来描述。
假设有一个Eclipse Java工程Test,它的目录结构如下所示(Test是工程的根目录,src是源代码目录,bin是编译后的class文件的输出目录):
Test
|--src
|--test
|--in
|--files
|--file.txt
|--FileInJar.java
|--bin
|--test
|--in
|--files
|--file.txt
|--FileInJar.class
之所以使用这种工程目录布局,就为了在程序开发的阶段,就造成一种包结构的假象。即bin下的文件将可能会被打包到jar中,所以对于bin中的文件,Eclipse将会把它作为jar中的文件对待。如果src中存在非Java文件(此处是file.txt),Eclipse就会按该文件在src中的目录结构将它直接拷贝到bin目录中。FileInJar.java的完整内容如下:
package test.in;
import java.io.InputStream;
public class FileInJar {
public static void main(String[] args) throws Exception {
FileInJar path = new FileInJar();
String path = "files/file.txt";
InputStream in = path.getClass().getResourceAsStream(path);
int c;
while ((c = in3.read()) != -1) {
System.out.print((char) c);
}
}
}
该程序就是将file.txt中的内容读出,然后显示到标准输出流(控制台)中。
请大家一定要注意path变量的值,它关系到Class.getResourceAsStream(String name)是否能够找到该文件,否则它返回的InputStream将为null。
现在将重点讨论文件路径的写法,在讨论之前必须要看看API文档中关于路径算法的内容:
* If the name begins with a '/' ('\u002f'), then the absolute name of the resource is the portion of the name following the '/'.
* Otherwise, the absolute name is of the following form:
modified_package_name/name
Where the modified_package_name is the package name of this object with '/' substituted for '.' ('\u002e').
Class.getResourceAsStream(String name)是通过name(即示例程序中的path)值来找资源的。对于name的值的格式,存在两种情况:[1]以'/'(它的Unicode值为'\u002f')开头,那么这个路径就是相对于jar文件的根目录,而与class文件(如示例中的FileInJar.class)在jar文件中的位置无关;[2]对于其它情况,这个路径将相对于class文件在jar文件中的位置,实际上也就是包名后再加给出的name值(
modified_package_name/name
)。
对这两种路径格式的总结:[1]以'/'开头,路径就是指定文件在jar中的绝对路径;[2]不以'/'开头,路径就是指定文件在jar中针对class文件的相对路径。
针对上述描述,原程序中的路径还有另一种以'/'开头的写法:
/test/in/files/file.txt。这种格式与原path的格式完全一样:FileInJar.class在包test.in('.'将被转换为'/')中,而给出的name(即path)值为files/file.txt,根据文档中的算法
modified_package_name/name,
示例程序中getResourceAsStream方法实际上仍然是根据路径/test/in/files/file.txt来查找资源的(废话! ^_^)。
关于使用使用ClassLoader.getResourceAsStream(String name)
细心的朋友可以发现,在ClassLoader中也有一个getResourceAsStream方法,而且它的功能同样也是根据给定的name值来查找资源并返回一个InputStream对象。其实Class.getResourceAsStream(String)是
ClassLoader.getResourceAsStream(String)的代理方法,但它会对name值做一些处理再传递给ClassLoader。如果直接将name值传递给ClassLoader中的这个方法,可能会找不到资源(尽管你的路径没有写错)。因为Class会对name值作一些处理(其实就是按前面所讲的路径算法进行处理),但ClassLoader并不会怎么做。对于这一点,JDK文档中没有明确的描述。
注意:不建议直接使用ClassLoader中的相应方法。
另外可以尝试一下java.util.jar,该包用于读/写jar内文件。
final变量的初始化
一般情况下,final变量都是在它的声明处就进行初始化,如下所示:
class Foo {
final int F = 10;
void bar() {
final int BAR = 1;
System.out.println(BAR);
}
注:与一般的类成员变量不同,此处的变量F并不会进行默认的初始化(如果是默认初始化,F的值应该为0)。
对类中的final成员变量,除上面的初始化方式,还可以在构造器中对final变量进行初始化,如下所示:
class Foo {
final int F;
Foo() {
F = 10;
}
}
在使用上述方式时,如果有多个构造器,那么每个构造器都必须对final成员变量进行初始化,如下所示:
class Foo {
final int F;
Foo() {
F = 10;
}
Foo(int f) {
F = f;
}
Foo(String str) {
F = 0;
System.out.println(str);
}
}
Bob Lee创新的一种Singleton实现方式
public class Singleton {
static class SingletonHolder {
static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
解决使用JSplitPane.setDividerLocation(double d)无效的问题
public class BaseSplitPane extends JSplitPane {
private boolean isPainted = false;
private boolean hasProportionalLocation = false;
private double proportionalLocation = 0.0D;
public BaseSplitPane() {
super();
}
public BaseSplitPane(int newOrientation, boolean newContinuousLayout,
Component newLeftComponent, Component newRightComponent) {
super(newOrientation, newContinuousLayout, newLeftComponent,
newRightComponent);
}
public BaseSplitPane(int newOrientation, boolean newContinuousLayout) {
super(newOrientation, newContinuousLayout);
}
public BaseSplitPane(int newOrientation, Component newLeftComponent,
Component newRightComponent) {
super(newOrientation, newLeftComponent, newRightComponent);
}
public BaseSplitPane(int newOrientation) {
super(newOrientation);
}
public void setDividerLocation(double proportionalLocation) {
if (!isPainted) {
hasProportionalLocation = true;
this.proportionalLocation = proportionalLocation;
} else
super.setDividerLocation(proportionalLocation);
}
public void paint(Graphics g) {
if (!isPainted) {
if (hasProportionalLocation)
super.setDividerLocation(proportionalLocation);
isPainted = true;
}
super.paint(g);
}
}
FlowLayout与ScrollPane不能正常协作的问题
在一个只允许上下滚动(HORIZONTAL_SCROLLBAR_NEVER)的ScrollPane中有一个Container,它使用FlowLayout,那么在默认情况下,当该container中各组件宽度之和已超出了ScrollPane的宽度时,并不会自动换行。这算是JDK的一个Bug,但在Sun官方论坛中给出了一种解决方案:
public class ScrollableFlowPanel extends JPanel implements Scrollable {
private static final long serialVersionUID = -7723152015485080501L;
public ScrollableFlowPanel(int alignment) {
super(new FlowLayout(alignment));
}
public ScrollableFlowPanel() {
this(FlowLayout.CENTER);
}
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, getWidth(), height);
}
public Dimension getPreferredSize() {
return new Dimension(getWidth(), getPreferredHeight());
}
public Dimension getPreferredScrollableViewportSize() {
return super.getPreferredSize();
}
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
int hundredth = (orientation == SwingConstants.VERTICAL ? getParent()
.getHeight() : getParent().getWidth()) / 100;
return (hundredth == 0 ? 1 : hundredth);
}
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
return orientation == SwingConstants.VERTICAL ? getParent().getHeight()
: getParent().getWidth();
}
public boolean getScrollableTracksViewportWidth() {
return true;
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
private int getPreferredHeight() {
int rv = 0;
for (int k = 0, count = getComponentCount(); k < count; k++) {
Component comp = getComponent(k);
Rectangle r = comp.getBounds();
int height = r.y + r.height;
if (height > rv)
rv = height;
}
rv += ((FlowLayout) getLayout()).getVgap();
return rv;
}
}
updating...