1.对象的创建和销毁
1.1 对象的创建
这里只介绍创建对象与构造方法的关系
(1).每实例化一个对象就会自动调用一次构造方法,实质上这个过程就是创建对象的过程,准确的说,在Java语言中使用new操作符调用构造方法创建对象。
(2).当创建对象时,自动调用构造方法,也就是说在Java语言中,初始化与创建对象是捆绑在一起的。
(3).每个对象都是相互独立的,在内存中占独立的内存地址,并且每个对象都有自己的生命周期,当一个对象的生命周期结束时,对象变成垃圾,由Java虚拟机回收处理。
1.2 对象的引用
引用只是存放一个对象的内存地址,并非存放一个对象,严格的说引用和对象是不同的,但可以忽略。
1.3 对象的比较
在Java语言中对象的比较有两种方式,分别为 “==” 和 “equals()”,但这两种方式有本质的区别:
例:
public class Compare{ public static void main(){ String c1 = new String("acb"); String c2 = new String("acb"); String c3 = c1; System.ou.println(c2==c3); System.ou.println(c2.equals(c3)); } }
运行结果:false
true
分析:equals()方法是String类中的方法,它可以用于比较对象引用所指的内容是否相等,而“==”运算符比较的是两个对象引用的地址是否相等,由c1与c2是两个不同地对象的引用,两者内存位置不同,而String中c3=c1,语句将c1的引用赋给c3,所以c1与c3这两个对象引用是相等的
1.4 对象的销毁
有以下两种情况会被Java虚拟机销毁:
(1).对象引用超过其作用范围
(2).将对象赋值为空
注:垃圾回收器只能回收那些由new操作符创建的对象,否则,不能回收,所以在Java中提供了一个finalize()方法,这个方法是Object类的方法,它被声明为protected,用户可以在自己的类中定义这个方法,在垃圾回收时首先会调用这个方法,在下一次垃圾回收动作发生时,才能真正回收对象占用的内存。但垃圾回收方法finaliza()不保证一定会发生。
2.访问对象属性和方法
举个例子:
1 public class Test { 2 int i = 47; 3 public void call(){ 4 for (i = 0; i < 3; i++){ 5 System.out.print(i+" "); 6 if(i == 2){ 7 System.out.println(" "); 8 } 9 } 10 } 11 public Test(){ 12 } 13 public static void main(String[] args) { 14 Test t1 = new Test(); 15 Test t2 = new Test(); 16 t2.i = 60; 17 System.out.println(+t1.i); 18 t1.call(); 19 System.out.println(+t2.i); 20 t2.call(); 21 } 22 23 }
运行结果:
47 0 1 2 60 0 1 2
分析:
在上述代码的主方法中首先实例化一个对象,然后使用“.”操作符调用成员变量和成员方法,但是从运行结果可以看到,虽然使用两个对象调用同一个成员变量,结果却不同,因为在打印这个成员变量值之前将该值重新赋值为60,单在赋值使用时的是第二个对象t2对象调用成员变量,所以在第一个对象t1调用成员变量打印该值时仍然是成员变量的初始值。由此可见,两个对象的产生是相互独立的,改变了t2的值,不会影响到t1的 i 的值。在内存中如下图所示:
如果希望成员变量不被其中任何一个对象改变,可以使用static关键字,代码如下:
1 public class Test { 2 static int i = 47; 3 public void call(){ 4 for (i = 0; i < 3; i++){ 5 System.out.print(i+" "); 6 if(i == 2){ 7 System.out.println(" "); 8 } 9 } 10 } 11 public Test(){ 12 } 13 public static void main(String[] args) { 14 Test t1 = new Test(); 15 Test t2 = new Test(); 16 t2.i = 60; 17 System.out.println(+t1.i); 18 t1.call(); 19 System.out.println(+t2.i); 20 t2.call(); 21 } 22 23 }
运行结果:
60 0 1 2 3 0 1 2
分析:
从上述运行结果中可以看到,由于使用t2.i=60语句改变了静态成员变量的值,使用对象t1调用成员变量的值也是60,这正是i值被定义为静态成员变量的效果,即使使用两个对象对同一个静态成员变量进行操作,依然可以改变静态成员变量的值,因为在内存条中两个对象指向同一块内存区域,t1.i++语句执行后,i的值变为3,当再次调用call()方法时又被重新赋值为0,做循环打印操作。
3.final变量,方法和类
(1)filnal修饰的方法不能被重写;
(2)final修饰在属性上,属性的值不能被改变。
(3)final修饰的类不能被继承。
3.1 finla变量关键字
finla变量关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值,通常,有final定义的变量为常量。
final关键字定义的变量必须在声明时对其进行赋值定义,final除了可以修饰基本数据类型的常量,还可以修饰对象引用,由于数组也可以被看成一个对象的引用,所以final可以修饰数组,一旦一个对象引用被修饰成final后,它只能恒定指向一个对象,无法将其改变指定另一个对象,一个既是static又是final的字段只占据一段不能改变的存储空间,以下面的例子深入了解final:
例:
1 import static java.lang.System.*; 2 import java.util.*; 3 class Test { 4 int i = 0; 5 } 6 7 public class FinalData { 8 static Random rand = new Random(); 9 private final int VALUE_1 = 9; // 声明一个final常量 10 private static final int VALUE_2 = 10; // 声明一个final、static常量 11 private final Test test = new Test(); // 声明一个final引用 12 private Test test2 = new Test(); // 声明一个不是final的引用 13 private final int[] a = { 1, 2, 3, 4, 5, 6 }; // 声明一个定义为final的数组 14 private final int i4 = rand.nextInt(20); //随机数 15 private static final int i5 = rand.nextInt(20); 16 17 public String toString() { 18 return i4 + " " + i5 + " "; 19 } 20 21 public static void main(String[] args) { 22 FinalData data = new FinalData(); 23 // data.test=new Test(); 24 //可以对指定为final的引用中的成员变量赋值 25 //但不能将定义为final的引用指向其他引用 26 // data.value2++; 27 //不能改变定义为final的常量值 28 data.test2 = new Test(); // 可以将没有定义为final的引用指向其他引用 29 for (int i = 0; i < data.a.length; i++) { 30 // a[i]=9; 31 // //不能对定义为final的数组赋值 32 } 33 out.println(data); 34 out.println("data2"); 35 out.println(new FinalData()); 36 // out.println(data); 37 } 38 }
运行结果
8 3
data2
6 3
分析:
在本实例子中,被定义成final的常量定义时需要使用大写字母命名,并且中间使用下划线进行连接,这是Java中的编码规则,同时,定义为final的数据无论是常量,对象,还是数组,在主函数中都不可以被修改。一个被定义为final的对象引用只能指向唯一一个对象,不可以将它再指向其他对象,但是一个对象本身的值却是可以改变的,那么为了使一个常量真正做到不可以更改,可以将常量声明为staticfinal。为了验证这个理论,看以下实例子:
1 import static java.lang.System.*; 2 3 import java.util.*; 4 5 public class FinalStaticData { 6 private static Random rand = new Random(); // 实例化一个Random类对象 7 // 随机产生0~10之间的随机数赋予定义为final的a1 8 private final int a1 = rand.nextInt(10); 9 // 随机产生0~10之间的随机数赋予定义为static final的a2 10 private static final int a2 = rand.nextInt(10); 11 12 public static void main(String[] args) { 13 FinalStaticData fdata = new FinalStaticData(); // 实例化一个对象 14 // 调用定义为final的a1 15 out.println("重新实例化对象调用a1的值:" + fdata.a1); 16 // 调用定义为static final的a2 17 out.println("重新实例化对象调用a1的值:" + fdata.a2); 18 // 实例化另外一个对象 19 FinalStaticData fdata2 = new FinalStaticData(); 20 out.println("重新实例化对象调用a1的值:" + fdata2.a1); 21 out.println("重新实例化对象调用a2的值:" + fdata2.a2); 22 } 23 }
运行结果:
重新实例化对象调用a1的值:4 重新实例化对象调用a1的值:1 重新实例化对象调用a1的值:5 重新实例化对象调用a2的值:1
从实例的结果可以看出,定义为final的常量是恒定不变的,将随机数赋值定义为final的常量,可以做到每次运行程序时改变a1的值,但a1与a2不同,由于他被声明为static final的形式,所以在内存中为a2开辟了一个恒定不变的区域,当再次实例化一个finalstaticdata对象时,仍然指向a2这块内存区域,所以a2的值保持不变,a2是在装载时被初始化,而不是每次创建新对象时度被初始化,而a1会在重新实例化对象时被更改。
3.2 final方法
首先说明一点,f定义为inal的的方法不能被重写。
例:
1 class Parents { 2 private final void doit() { 3 System.out.println("父类.doit()"); 4 } 5 6 final void doit2() { 7 System.out.println("父类.doit2()"); 8 } 9 10 public void doit3() { 11 System.out.println("父类.doit3()"); 12 } 13 } 14 15 class Sub extends Parents { 16 public final void doit() { // 在子类中定义一个doit()方法 17 System.out.println("子类.doit()"); 18 } 19 // final void doit2(){ //final方法不能覆盖 20 // System.out.println("子类.doit2()"); 21 // } 22 public void doit3() { 23 System.out.println("子类.doit3()"); 24 } 25 } 26 27 public class FinalMethod { 28 public static void main(String[] args) { 29 Sub s = new Sub(); // 实例化 30 s.doit(); // 调用doit()方法 31 Parents p = s; // 执行向上转型操作 32 // p.doit(); //不能调用private方法 33 p.doit2(); 34 p.doit3(); 35 } 36 }
运行结果
子类.doit()
父类.doit2()
子类.doit3()
分析:
从上例子中可以看出,final方法不能被覆盖。例如doit2()方法不能再子类中被重写,但是在父类中定义了一个private final的doit()方法,同时在子类中也定义了一个doit()方法,从表面上看,子类中的doit()方法覆盖了父类的doit()方法,但必须满足一个对象向上转型为它的基本类型并调用相同方法这样一个条件,在例子中,对象p不能调用doit()方法,可见,子类中的doit()方法并不是正常覆盖,而是生成一个新的方法。
3.3 final类
在这只说明一点:如果将某个类设置为final类,则类中的所有方法都被隐式设置为final形式,但是final类中的成员变量可以被定义为final或非final形式,并且,其值可以被改变。
4.静态变量,常量和方法
(1)为什么要使用静态变量,常量和方法
通常,在处理问题,会遇到多个不同的类要使用同一变量,常量或方法,然而,同一个常量在不同i的类中创建时系统都会为之分配内存,造成内存浪费,如果能将这些不同类中的变量共享到一个内存中,那就大大减少了内存的使用,而静态变量(关键字 static)就是解决这个问题的。如下图所示:
(2).被声明的static的变量,常量和方法被称为静态成员,静态成员属于类所有,区别于个别对象,可以在本类或其他类使用类名“.”运算符调用静态成员。如下代码:
1 public class AnyThing { 2 static double PI = 3.1415; //在类中定义静态常量 3 static int id; //在类中定义静态变量 4 public static void method1(){ //在类中定义静态方法 5 6 } 7 public void method2(){ 8 System.out.println(AnyThing.PI); //调用静态常量 9 System.out.println(AnyThing.id); //调用静态变量 10 AnyThing.method1(); //调用静态方法 11 } 12 }
注意:
(1) 虽然静态成员可以使用“对象.静态成员”的形式进行调用,但通常不这么使用,这样容易混淆静态成员和非静态成员。
(2) 在静态方法中不可以使用this关键字。
(3) 在静态方法中不可以直接调用非静态方法。
(4) 在Java中规定不能将方法中的局部变量声明为static的。
(5) 如果在执行类时,希望先执行类的初始化动作,可以使用static定义一个静态区域,例如
1 public class example{ 2 static{ 3 ... 4 } 5 }
当这段代码被执行时,首先执行static块中的程序,并且只会执行一次,即在类加载的时候就被执行,之后执行main()方法。并且只是自上而下的顺序执行。
(3)实例语句块在构造方法调用之前调用,调用一次构造函数,就调用一次实例语句块,执行顺序自上而下。
(4)在java中,静态方法和普通方法的区别:
a、在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
b、静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
c、程序的static块只能调用静态方法,不能调用实例的方法。
如果要追溯其区别的根源,可以这样理解:在编译期间静态方法的行为就已经确定,而实例方法只有在运行期间当实例确定之后才能确定。
5.this关键字
在下面代码中。成员变量与setName()方法中的形式参数的名称相同,都为name,那么该如何在类中区分使用的是哪一个比阿娘呢?在Java语言中规定使用this关键字来代表本类对象的引用,this关键字被隐士的用于引用对象的成员变量和方法,如在上述代码中,this.name指的name是成员变量,第二个name是形参。
private void setName(String name){ this.name = name; }
那么,在java中this和对象都可以调用成员变量或成员方法,二者之间有什么区别呢?
区别:this引用的就是本类的一个对象,在局部变量或方法参数覆盖了成员变量时,就要添加this关键字说明引用的是类成员还是局部变量或方法参数;如果this关键字直接写成name = name;成员变量的值并没有改变,因为参数name在方法的作用域中覆盖了成员了变量name。
this关键字还可以调用类中的构造方法,如下代码
1 public class AnyThing { 2 public AnyThing(){ 3 this(""); //使用this调用有参构造方法 4 System.out.println("无参构造方法"); 5 } 6 public AnyThing(String name){ 7 System.out.println("有参构造方法"); 8 } 9 10 }
在上例定义了两个构造方法,在无参构造方法中使用this关键字调用有参的构造方法,但使用这种方法需要注意的是只可以在无参构造方法中的第一句使用。