平时在学习、应用Java的过程中,遇到的一些小知识,将它们收集到这里。杂草(weed)也不能丢弃嘛。
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;
}
}