平时在学习、应用Java的过程中,遇到的一些小知识,将它们收集到这里。杂草(weed)也不能丢弃嘛。
Object中的equals方法用于比较两个对象是否在同一个地址。但Object的子类会重载这个方法,所以其它类中的equals方法的功能可能就会不一样了。
初始化(Initialization)
阐述对象被创建时的若干步骤,假设以类Dog为例。
[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()。
如何使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文件的输出目录):
请大家一定要注意 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变量都是在它的声明处就进行初始化,如下所示:
对类中的final成员变量,除上面的初始化方式,还可以在构造器中对final变量进行初始化,如下所示:
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)无效的问题
FlowLayout与ScrollPane不能正常协作的问题
在一个只允许上下滚动(HORIZONTAL_SCROLLBAR_NEVER)的ScrollPane中有一个Container,它使用FlowLayout,那么在默认情况下,当该container中各组件宽度之和已超出了ScrollPane的宽度时,并不会自动换行。这算是JDK的一个Bug,但在Sun官方论坛中给出了一种解决方案:
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认为这几个方法名不是很直观!)如何使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;
}
}