在多数情况下,初始化一个对象的最终步骤是去调用这个对象的构造方法。构造方法负责对象的初始化工作,为实例变量赋予合适的初始值。构造方法必须满足一下语法规则:
1.方法名必须和类名相同
2.不要声明返回的类型
3.不能被static final.abstract和native修饰,构造方法不能被子类继承,所以用final和abstract修饰没有意义,构造方法用于初始化一个新建的对象,所以用static修饰没有意义,多个线程不会同时创建内存地址相同的同一个对象,因此用synchronized修饰没有必要.此外,java语言不支持native类型的构造方法
这里加点题外话一:static :1.静态方法
通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法。声明为static的方法有以下限制
一.它们仅能调用其他的 static方法
二.它们只能访问static数据
三.它们不能以任何方式引用this或super
class Simple{
static void go(){
System.out.println("welcome");
}
}
public class Cal{
public static void main(String[] args){
Simple.go();
}
}
调用一个静态方法就是“类名.方法名”,静态方法的使用很简单如上所示。一般来说,静态方法常常为应用程序中的其它类提供一些实用工具所用,在java的类库中大量的静态方法正是出于此目的而定义的.
2.静态变量
声明为static的变量实质上就是全局变量,当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量,静态变量与静态方法类似,所有此类实例共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操作此块存储空间,当然对于final则另当别论了
class Value{
static int c=0;
static void inc(){
c++;}
}
public class Count2{
public static vois main(String[] args){
Value v1,v2;
v1=new Value();
v2=new Value();
System.out.print(v1.c+v2.c);
v1.inc();
System.out.print(v1.c+v2.c);
}
}
结果是v1.c=0 v2.c=0 v1.c=1 v2.c=1
由此可以证明的就是它们共享一块存储区
值得探讨的是静态变量的初始化问题
如果你需要通过计算初始化你的static变量,你可以声明一个static块,static块仅在该类加载时执行一次,下面的例子显示有一个static方法,一些static变量,以及一个static初始块:
class Value3{
static int c=0;
Value3(){
c=15;
}
Value3(int i){
c=i;
}
static void inc(){
c++;
}
}
public class Count{
public static void prt(String s){
System.out.println(s);
}
}
Value3 v=new Value3(10);
static Value3 v1,v2;
static{即为static 块
prt(v1.c+v2.c);
v1=new Value3(27);
prt(v1.c+v2.c);
v2=new Value3(15);'
prt(v1.c+v2.c);
}
public static void main(String[] args){
Count ct=new Count();
prt(ct.v.c);
prt(v1.c+v2.c);
v1.inc();
prt(v1.c+v2.c);
prt(ct.v.c);
}
结果是:
v1.c=0 v2.c=0
v1.c=27 v2.c=27
v1.c=15 v2.c=15
ct.c=10
v1.c=10 v2.c=10
v1.c=11 v2.c=11
ct.c=11
这个程序展示了静态初始化的各种特性。如果你初次接触Java,结果可能令你吃惊,可能会对static后加大括号感到困惑。首先我要告诉你的是,static定义的变量会优先任何的其他非static变量,不论顺序如何,正如在程序中看到的一样,虽然v在v1与v2的前面,但是结果是v1和v2的初始化在v的前面,在static后面的代码,这是用来进行显示静态变量的初始化,这段代码只会初始化一次,并且在类被第一次装载时,如果你读懂了并理解这段代码,会帮你对static关键字的认识,在涉及到继承的时候,会先初始化父类的static变量,然后在子类的,依次类推
二,final关键字
在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。
1.修饰类
当用final修饰类时,表明这个类不能被继承,也就是说如果一个类你永远你不会让它继承,就可以用 final修饰,final类中的成员变量可以根据需要设为final,但是要注意的是final类中的所有成员方法都会被隐式地指定为final方法
在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
2.修饰方法
“使用final方法的原因有2个,第一个原因是把这个方法锁定,防止任何继承类修改它的含义,第二个是效率,”
因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。
3.修饰变量
修饰变量是final用的最多的地方
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象
class man{
private final int i=0;
public Man(){
i=1;
final Object obj=new Object();
obj=new Object();
}
}
上面这段代码,对于i与obj的重新赋值都会报错
深入理解final关键字
1.当用 final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前进行赋值即可),而且final变量一旦被初始化赋值之后就不能在被赋值了
public class Test {
public static void main(String[] args) throws Exception {
String a="hello2";
final String b="hello";
String d="hello";
String c=b+2;
String e=d+2;
System.out.println(c);
System.out.println(e);
}
}
大家可以先想一下这道题的输出结果。为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的 值。而对于变量d的访问却需要在运行时通过链接来进行,不过要注意的是,只有当编译期间确切知道final变量值的情况时,编译器才会进行优化
public
class
Test {
public
static
void
main(String[] args) {
String a =
"hello2"
;
final
String b = getHello();
String c = b +
2
;
System.out.println((a == c));
}
public
static
String getHello() {
return
"hello"
;
}
}
2.被修饰的final修饰的引用变量指向的对象内容可变
3.final与static
很多时候我们容易 把static与final混淆,static只保存一份副本,而final的作用是保存变量不可变,看下面的例子
public
class
Test {
public
static
void
main(String[] args) {
MyClass myClass1 =
new
MyClass();
MyClass myClass2 =
new
MyClass();
System.out.println(myClass1.i);
System.out.println(myClass1.j);
System.out.println(myClass2.i);
System.out.println(myClass2.j);
}
}
class
MyClass {
public
final
double
i = Math.random();
public
static
double
j = Math.random();
运行这段代码,每次打印的j的值都是一样的,而i的值却不相同,
废话不多了,还是言归正传把
在以下的Sample类中,具有int返回类型的Sample(int x)方法只是个普通的方法,不能作为构造方法
public class Sample{
private int x;
public Sample(){不带参数的构造方法
this(1);
}
public Sample(int x){带参数的构造方法
this.x=x;
}
public int Sample(int x){不是构造方法
return x++;
}
}
以上例子尽管能编译通过,但是使用实例方法与构造方法同名,不是好的编程习惯,容易造成混淆,例如以下Mystery()类的Mystery()方法有void返回类型,由此不是构造方法
public class Mystery {
private String s;
public void Mystery() { //不是构造方法
s = "constructor";
}
void go() {
System.out.println(s);
}
public static void main(String[] args) {
Mystery m = new Mystery();
m.go();
}
}
以上程序输出的是null,因为new语句创建Mystery实例时,调用的是Mystery类的默认的方法,而不是以上有void返回类型的Mystery()方法11.2.1 重载构造方法
当通过new语句创建一个对象时,在不同的条件下,对象可能会有不同的初始化行为,例如对于公司新来的一个雇员,在一开始的时候,有可能不知道他的姓名和年龄是未知的,也有可能仅仅是他的姓名是已知的,也有可能姓名和年龄是已知的,如果姓名是未知的,那就叫无名氏,如果年龄是未知的,那就暂且把年龄设为-1。
可通过重载方法来表达对象的多种初始化行为,java中允许一个构造方法中,用this用英语来调用另外一个构造方法
例程11-2 Employee.java
public class Employee {
private String name;
private int age;
/** 当雇员的姓名和年龄都已知,就调用此构造方法 */
public Employee(String name, int age) {
this.name = name;
this.age=age;
}
/** 当雇员的姓名已知而年龄未知,就调用此构造方法 */
public Employee(String name) {
this(name, -1);
}
/** 当雇员的姓名和年龄都未知,就调用此构造方法 */
public Employee() {
this( "无名氏" );
}
public void setName(String name){this.name=name; }
public String getName(){return name; }
public void setAge(int age){this.age=age;}
public int getAge(){return age;}
}
例子中,this(name,-1)语句用来调用Employee(String name,int age)构造方法,在Employee()构造方法中,this(无名氏)语句用来调用Employee(String name)构造方法
用this语句来调用其他构造方法时,必须遵守以下语法规则。
l 假如在一个构造方法中使用了this语句,那么它必须作为构造方法的第一条语句(不考虑注释语句)。以下构造方法是非法的:
public Employee(){
String name="无名氏";
this(name); //编译错误,this语句必须作为第一条语句
}
l 只能在一个构造方法中用this语句来调用类的其他构造方法,而不能在实例方法中用this语句来调用类的其他构造方法。
l 只能用this语句来调用其他构造方法,而不能通过方法名来直接调用构造方法。以下对构造方法的调用方式是非法的:
public Employee() {
String name= "无名氏";
Employee(name); //编译错误,不能通过方法名来直接调用构造方法
}
11.2.2默认的构造方法默认构造方法是没有参数的构造方法1.隐含默认的构造方法 2.程序显示定义的默认构造方法
在java语言中,每个类至少有一个构造方法,为了保证这一点,如果用户定义的类中没有提供任何构造方法,那么java语言将自动提供一个隐含的默认构造方法,该构造方法没有参数,用public修饰,而且方法体为空,格式如下
public ClassName(){}//隐含的默认构造方法
在程序中也可以显式地定义默认构造方法,它可以是任意的访问级别。例如:
protected Employee() { //程序显式定义的默认构造方法
this("无名氏");
}
如果类中显式定义了一个或多个构造方法,并且所有的构造方法都有参数,那么这个类就失去了默认构造方法,在一下程序中,Sample1类中隐含一个默认的构造方法,SamPle2类没有默认的构造方法,Sample3有一个显式默认构造方法
public class Sample1{}
public Class Sample2{
public class Sample2(int a){
system,.out.println("aa");
}
public class Sample3{
public class Sample3(){
System.out.print("aa");
}
}
}
可以调用Sample1类的默认构造方法来创建Sample1对象
Sample1 s=new Sample1();//合法
由于Sample2 没有默认的构造方法
Sample2 s=new Sample2();//编译出错
Sample3有显式定义了默认的构造方法,因此语句合法
Sample3 s=new Sample3();