Java Weed
平時在學習、應用Java的過程中,遇到的一些小知識,將它們收集到這里。雜草(weed)也不能丟棄嘛。(2009.09.16最后更新)boolean(2) | |
byte(8) | |
char(16) | short(16) |
int(32) | float(32) |
long(64) | double(64) |
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 |
一 |
+ - ++ -- ! ~ |
元 | 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認為這幾個方法名不是很直觀?。?br />
如何使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的完整內容如下:|--src
|--test
|--in
|--files
|--file.txt
|--FileInJar.java
|--bin
|--test
|--in
|--files
|--file.txt
|--FileInJar.class
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中的內容讀出,然后顯示到標準輸出流(控制臺)中。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);
}
}
}
請大家一定要注意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 int F = 10;
void bar() {
final int BAR = 1;
System.out.println(BAR);
}
對類中的final成員變量,除上面的初始化方式,還可以在構造器中對final變量進行初始化,如下所示:
class Foo {
final int F;
Foo() {
F = 10;
}
}
在使用上述方式時,如果有多個構造器,那么每個構造器都必須對final成員變量進行初始化,如下所示:final int F;
Foo() {
F = 10;
}
}
class Foo {
final int F;
Foo() {
F = 10;
}
Foo(int f) {
F = f;
}
Foo(String str) {
F = 0;
System.out.println(str);
}
}
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);
}
}
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;
}
}
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...