问题:final修饰的变量能否在定义时不初始化?
首先,先看一下final可以修饰哪些东西:
final可以修饰类、变量、方法。
- final修饰的类不能被继承
- final修饰的方法不能被重写
- final修饰的变量是一个常量,只能被赋值一次
修饰的类不可继承,所以不能修饰抽象类。
那么final是否可以修饰内部类呢?
package com.newre.demo2;
public class OuterClass {
/**
* 用final修饰成员内部类
* @author newre
*
*/
final class InnerClass1 {
{
System.out.println(
"InnerClass1:I can"
);
}
}
/**
* 用final修饰静态内部类
* @author newre
*
*/
static final class InnerClass2 {
{
System.out.println(
"InnerClass2:I can"
);
}
}
public static void main(String[] args) {
/**
* 用final修饰方法内部类
* @author newre
*
*/
final class InnerClass3 {
{
System.out.println(
"InnerClass3:I can"
);
}
}
new OuterClass().new InnerClass1();
new InnerClass2();
new InnerClass3();
}
}
########################################
执行结果:
InnerClass1:I can
InnerClass2:I can
InnerClass3:I can
无论是成员内部类、静态内部类、还是方法内部类,我们可以看到三者均可以用final修饰。
让我们再看看final修饰的方法。
众所周知的是:普通方法是能被final修饰的,抽象方法不能被final修饰。
那么,静态方法是否能被final修饰?
静态方法可以存在于类中,也可以存在于方法中,所以分别试一下。
package com.newre.demo2;
public class TestClass {
public final static void test() {
System.out.println("Class FinalMethod:I can");
}
public static void main(String[] args) {
TestClass.test();
}
}
#######################################
执行结果:
Class FinalMethod:I can
package com.newre.demo2;
public interface TestInferface {
/**
* 此处出现了编译时异常:
* Illegal modifier for the interface method test;
* only public, abstract, default, static and strictfp are permitted
*/
final static void test() {
System.out.println("Interface FinalMethod:I can't");
}
}
结论就是,类中的静态方法可以被final修饰,而接口中的静态方法不能被final修饰。
看到这,也许有人会问:
类中的静态方法可以被final修饰,可是……这有啥用?静态方法本身就是可继承,不可重写的呀?
的确是的,所以,这样写的作用似乎只剩下了一个:
一起使用时的主要目的是使代码规范化,便于以后阅读更改……
啰里啰唆说了一大堆,现在进入正题,final修饰的变量能否在定义时不被初始化?
答案是,可以。
通常情况,我们定义常量共有三种常见形式:
package com.newre.demo2;
public class FinalField {
// final修饰实例变量并初始化
private final String A = "1";
// final修饰类变量并初始化
private static final String B = "2";
/**
* final修饰形参,此时不初始化,
* 但内部不能改变形参的值
*
* @param A final修饰的形参
*/
public void test(final String A) {
// 此句不成立,编译时异常:
// The final local variable A cannot be assigned.
// It must be blank and not using a compound assignment
// A = "aaa";
}
public static void test2(final String A) {
}
}
通常情况这三种足以应付大多数的问题,但是事实上,我们定义的实例变量与类变量,不一定非要在被定义时就初始化,这一点其实是很好用的,我们稍后再议,先进行代码测试。

此时,我们没有赋值,然后报错……
此时,不用心慌,因为代码还没写完。
package com.newre.demo2;
public class FinalField {
// final修饰实例变量并初始化
private final String A1;
private final String A2;
// final修饰类变量并初始化
private static final String B1;
private static final String B2;
{
A1 = "a";
// 此处编译报错:
// The final field FinalField.B2 cannot be assigned
// 因为B2是final修饰的类变量
// 又因为在实例化时才会触发代码块内容
// 所以在代码块内部不能初始化静态常量
// B2 = "b";
}
static {
// 此处编译报错:
// Cannot make a static reference to the non-static field A2
// 有编程基础的朋友们应该明白,
// 此处出错是因为在静态代码块中,
// 不能有实例变量(包括实例常量)
// A2 = "a";
B1 = "b";
B2 = "b";
}
public FinalField() {
A2 = "a";
// 此处仍旧编译报错,
// 原因不多说,
// 与代码块中报错原因相同
// B2 = "b";
}
public FinalField(String A) {
A2 = "";
}
}
此时代码编译通过。
所以结论就是:
- 实例常量可以在代码块或构造器中被初始化(如果是构造器中初始化,那么必须保证每个构造器都能够有实例常量的初始化步骤。
- 类常量可以在静态代码块中被初始化,不能在代码块或构造器中被初始化。
这样设计的好处在哪儿呢?
好处之一就是,能体现配置的灵活性。
package com.newre.demo2;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class TestJDBC {
private static final String DRIVER;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static {
Properties properties = new Properties();
try {
properties.load(TestJDBC.class.getClassLoader().getResourceAsStream("jdbc-config.properties"));
} catch (IOException e) {
e.printStackTrace();
}
DRIVER = properties.getProperty("DRIVER");
URL = properties.getProperty("URL");
USERNAME = properties.getProperty("USERNAME");
PASSWORD = properties.getProperty("PASSWORD");
}
public static Connection getConnection() throws SQLException, ClassNotFoundException {
Class.forName(DRIVER);
return DriverManager.getConnection(URL,USERNAME,PASSWORD);
}
public static void main(String[] args) {
try {
Connection connection = getConnection();
System.out.println(connection);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
jdbc-config.properties配置文件内容如下:
DRIVER=com.mysql.jdbc.Driver
URL=http://localhost:3306/test
USERNAME=root
PASSWORD=123456
由于新装的电脑上暂时没有安装MySQL,故而无法测试是否运行正确。
总之,编译能通过。
而这,就是final延迟一点初始化的一个小小的应用。
如果没有学过JDBC的朋友们也可以看下面的一个例子:
package com.newre.demo2;
public class SimpleFinalField {
private final String A;
private SimpleFinalField(String A) {
this.A = A;
}
public String getA() {
return A;
}
public static SimpleFinalField A() {
return new SimpleFinalField("A");
}
public static SimpleFinalField B() {
return new SimpleFinalField("B");
}
public static SimpleFinalField C() {
return new SimpleFinalField("C");
}
public static void main(String[] args) {
String a = SimpleFinalField.A().getA();
String b = SimpleFinalField.B().getA();
String c = SimpleFinalField.C().getA();
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}
##########################################
执行结果:
a = A
b = B
c = C
当然,这串代码的实用性不是很高,更多的应用场景需要由各位摸索下去。
最后,请记住:
final修饰的变量可以在定义时不被初始化!!