Java基础之面向对象的基本概念(3--续)
这是java基础篇第三篇的续篇在第三篇中没有讲到某些关键字在面向对象的应用。本篇算是做个了结吧。
一、final 关键字
final关键字可以用来修饰类、方法和变量。
1.final修饰的类不能被继承。
2.final修饰的方法不能被重写。
3.final修饰的变量是常量,不能修改其值。
4.final变量获得初始值之后不能被重新赋值。final修饰成员变量和局部变量时有一定的不同。
5.final修饰的成员变量必须由程序员显示的指定初始值。
6.如果在构造函数,初始化块对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值。
1.1Final修饰的类变量,实例变量可以指定的初始值的地方如下:
1.类变量:必须在静态初始化块中或声明该变量时指定初始值。
2.实例变量:必须在非静态初始化块,声明该变量或构造器中指定初始值。
实例代码1:
package cn.com.basicTwo;
public class FinalDemo {
final int a = 6;
final String str;
final int c;
final static double d;
//初始化,可对没有指定默认值的实例field指定初始值。
// final char c1; 既没有指定默认值,又没有在初始化块,构造器中指定初始值,下面定义错误
{
str=""; //在初始化块中为实例Field指定初始值,合法
//a = 4; //不能为a重新复制
}
//静态初始化块,可对没有指定默认值的类Field指定初始值。
static{
//在静态初始化块中为类Field指定初始值,合法
d = 5.6;
}
/**
* 构造函数,可对即没有指定默认值又没有在初始化块中
* 指定初始化值的实例Field指定初始值
*/
public FinalDemo(){
// str = "sd";如果初始化块中对str指定了初始值,
//则构造器中不能对final变量重新赋值,上面非法。
c = 5;
}
public void changeFinal(){
//普通方法不能为final修饰的成员变量赋值
//d = 1.3;
//不能字普通方法中为final成员变量指定初始值。
//c1='c';
}
public static void main(String[] args) {
FinalDemo f= new FinalDemo();
System.out.println(f.a);
System.out.println(f.c);
System.out.println(f.d);
}
}
实例代码2:第6点说明
package cn.com.basicTwo;
public class FinalErrorTest {
//定义一个final修饰的变量。
final int age;
{
//age没有初始化,所以此处代码将引起错误。
//System.out.println(age);
age = 50;
System.out.println(age);
}
public static void main(String[] args) {
new FinalErrorTest();
}
}
1.2Final局部变量:
说明:系统不会对局部变量进行初始化,局部变量必须由程序员显示的初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值,也可以不指定默认值。
实例代码3:final修饰局部变量,形参。
package cn.com.basicTwo;
public class FinalLocaltest {
public void test(final int a ){
//不能对Final修饰的形参赋值,下面的语法错误。
//a = 5;
System.out.println("a = "+a);
}
public static void main(String[] args) {
final String str = "str"; //不可再对其赋值
final double d ;
d = 5.6 ; //不可再对其赋值
FinalLocaltest fl = new FinalLocaltest();
fl.test(34); //调用时对形参进行初始化
}
1.3 Final修饰基本类型变量和引用型变量的区别
说明:使用Final修饰的基本类型变量时,不能对其再赋值。
对于引用型变量而言保存的仅仅是一个引用,Final之保证这个引用型变量所引用的地址不变,即一直引用同一个对象,但这个对象完全可以发生改变。使用 Final修饰的引用变量不能被重新赋值,但是可以改变引用类型变量所引用对象的值。
实例代码4:
package cn.com.basicTwo;
import java.util.Arrays;
class Person{
private int age;
public Person(){}
public Person(int age){
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class FinalReferenceTest {
public static void main(String[] args) {
//Final修饰数组变量iArr是一个引用变量
final int [] iArr = {2,3,4,5,6,78,2,5};
System.out.println(Arrays.toString(iArr));
Arrays.sort(iArr); //对数组元素排序,合法
iArr[2] = 200;
System.out.println(Arrays.toString(iArr));
//iArr = null; //对iArr重新赋值,非法
final Person p = new Person(45);
p.setAge(34); //改变Person对象的age变量合法
System.out.println(p.getAge());
//p = null; //对p变量重新赋值,非法
}
}
1.4 final与“宏替换”
说明:当定义Final变量时,就为该变量指定初始值,而且该初始值可以在编译时,就确定下来,那么这个Final变量本质上就是一个“宏变量”;
宏变量的应用实例:
实例代码5:
public class FinalReplaceTest {
public static void main(String[] args) {
//定义四个宏变量
final int a = 5+3;
final double d = 5.5/3.2;
final String str = "疯狂"+"java";
final String book = "疯狂java讲义"+99.0;
//下面的Book2变量的值因为调用了方法,所以无法再编译时被确定下来
final String book2 = "疯狂java讲义"+String.valueOf(99.0);
System.out.println(book == "疯狂java讲义99.0");
System.out.println(book2 == "疯狂java讲义99.0"); //会报错,可能与java虚拟机有关。
}
}
1.5Final方法
说明:Final修饰的方法不能被重写,
应用:不希望子类重写父类的某个方法时,可以使用Final关键字修饰该方法名。相当于密封这个方法。
Object类里就有一个方法不能被重写:getClass()
一般类中的toString(),equals()方法不会被重写。
实例代码6.重写报错
public class Finalfather {
public final int getSum(int x){
int sum =0 ;
for(int i =0 ;i< x;i++){
sum +=i;
}
return sum;
}
}
public class FinalMethod extends Finalfather{
//public int getSum(int x){ 该重写方法报错
//return x;
//}
public static void main(String[] args) {
}
}
Final修饰的方法不能被重写,但是可以被重载
public class Finalfather {
//Final的重载方法1
public final int getSum(int x){
int sum =0 ;
for(int i =0 ;i< x;i++){
sum +=i;
}
return sum;
}
//Final的重载方法二
public final int getSum(){
return 0;
}
}
1.6不可变类
定义:创建该类的实例后,该类的实例的field是不能改变的,
举例:java提供的基本数据类型的8个封装类都是不可变类,还有String类。
例如:实例代码七:不可变类中的应用
public class ImmuntableTest {
public static void main(String[] args) {
Double d = new Double(34.90); //程序无法修改这两个对象传入的参数,但是可以进行手动更新赋值。
String str = new String("string");
d = 3.0;
System.out.println(d);
String str1 = new String("Hello");
String str2 = new String("Hello");
/*
* 创建了两个string对象,指针值不同,这里实际上生成了三个对象,str1,str2,"Hello",
* 这个字符串在常量池中,str1,和str2的指针分别指向这个常量池中的这个字符串,str1和str2的值是不相同的
*/
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
//hashCode()是根据字符串内容进行计算的因此两个相同的字符串有相同的hash码
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
}
}
可以创建自己的不可变类,要遵守下面的原则
1.使用private和final修饰符来修饰该类的Field。
2.提供带参数构造器,用于根据传入参数来初始化类里的Field。
3.仅仅为该类的Field的提供getter方法。
4.如果有必要,重写Object类的hashCode和equals方法,
说明:不可变类是可以被继承的。与其相对的是可变类。
可变类的含义:该类的实例Field是可变的。
与可变类相比,不可变类的实例在整个生命周期中永远处于初始化状态。它的Field不可变。因此不可变类的实例的控制更加简单。
注意:如果不可变类中有Final修饰的对象的引用变量,这个引用变量的内容可以被改变,则这个不可变对象是一个失败的或者说不好的不可变类。
不可变类的应用-----缓存技术,缓存多个程序经常用到的不可变对象,用于程序共享。
二、this 关键字
1.1说明:this关键字表示当前类的内部实例对象,类似于外部实例,但是this可以访问所有级别的成员变量和方法。
this使用原则:
1.this是当前类的内部对象,所以和类的外部对象访问类成员变量
和方法的规则一致,语法:this.<变量名>和this.<方法名>。
2.this只能在当前类的内部使用,但是,不能在static修饰的方法
(即类方法)中使用。
3.this在实例方法中通常被省略,除非实例方法中包含与成员变量
同名的局部变量时,访问成员变量需要使用this。
4.当一个类中包含多个构造方法的时,如果其中一个构造方法要调
用另外的构造方法时,使用this关键字。
详细说明:this关键字总是指向调用该方法的对象。
根据this出现的位置不同,this作为对象的默认引用有两种情况;
1.构造器中引用该构造器正在初始化的对象。
2.在方法中引用调用该方法的对象。
This关键字的最大作用就是让类中的一个方法,访问该类里的另一个方法或者field.
对于第二点的说明:
只有当这个方法被调用时,它所代表的对象才被确定下来,谁在调用这个方法,this就代表谁。
当this在这个方法里时,并且没有被调用时this的类型是确定的,代表的对象只能是当前类。
Java允许对象的一个成员直接调用另一个成员,可以省略this前缀。实际上这个this依然存在。
对使用规则的第二点进行说明:
This总是指向当前对象,无论是在调用时,还是在稳定的没有被调用的情况下,在static修饰的方法里,可以使用类名直接清楚的调用某个方法,但是如果用this替代的话,调用该方法的对象不明确,因此不允许在static修饰的方法里使用this关键字。
实例代码8:this演示
package cn.com.basicTwo;
public class ThisDemo {
public int money;
public String account;
/**
* this常用于构造方法中
* @param m
*/
public ThisDemo(int m,String _account){
this.money = m; //使用this代表当前对象的ThisDemo实例,调用实例变量money进行初始化
this.account = _account;
}
public ThisDemo(){
this(0,"abdedag21324"); //此处this代表当前对象,并且有个参数0,指向当前对象的另一个构造方法。
}
public void getMoney(){
System.out.println(money);
}
public void show(){
System.out.println("我的钱有: ");
getMoney(); //省略this关键字的调用方式,代表ThisDemo对象的调用
}
public static void account(){
System.out.println("我的银行账户: ");
//this.getAccount(); 编译不通过
}
public void getAccount(){
System.out.println(account);
this.account(); // this可以代表类名调用,或者类 的实例调用类方法,但一般不会这样调用类方法。
}
public static void main(String[] args) {
ThisDemo thisdemo = new ThisDemo();
thisdemo.getAccount();
thisdemo.getMoney();
//getMoney();这种方式编译不通过,因为main方法是static修饰的,而该方法是实例方法,没有指明具体的类的实例调用
//因此报错。
}
}
二、super 关键字解析
说明: 如果子类继承父类时,出现了成员变量的隐藏和方法重写,此时子类的对象对应的成员变量和方法都是在子类中重新定义的,如果需要在子类中调用父类被隐藏的成员变量或者被重写的方法,可以使用super关键字。
super使用原则:
1.super可以访问被子类重写的父类同名方法和被子类隐藏的父类的成员变量。
2.可以使用super关键字调用父类的构造方法。
Super具有限定的作用,限定该对象调用它从父类继承得到的Field或者方法。
3.super跟this的作用相似,同样是不能用于在static修饰的方法中使用,否则super的限定作用就没有意义了。
Super总是跟父类对象方法变量有关,因此有必要区分一下访问变量对象的规则。
子类中有和父类相同的变量,则称这个父类中的变量被子类覆盖了。
1.子类没有和父类一样的变量,不存在覆盖的情况:无需显示使用super和父类名作为调用者。
2.子类出现了覆盖的情况(方法中有a变量),调用时没有显示指定调用者:系统查找变量的方式
1.查找该方法中是否有名为a的局部变量
2.查找当前类是否有名为a的变量Field
3.查找a的直接父类中是否有a变量,依次上溯到父类的父类,查找,如果没有找到的话,系统编译出错。
如果被覆盖的是static修饰的类变量,则在子类的方法中可以使用父类名称或者super调用该变量。
虚拟机为子类变量和父类变量创建内存空间:当程序创建一个子类对象时,系统不仅为该子类的实例变量分配内存,也会为从父类那里继承得到的实例变量分配内存,即使出现覆盖的情况,父类的实例变量的内存空间与子类实例变量的内存空间毫无关系。
注意:覆盖(英文词义:Override)不是翻新,而是隐藏,因为二者的内存空间不同,父类的同名变量仅仅是被隐藏起来了,实际上还存在。可以使用super关键字引用父类的同名
实例代码9:Super使用
public class SuperFather {
public static String name;
protected int age;
private double money;
public String tag ="父类tag";
public SuperFather(String _name, int age, double money) {
super(); //默认调用Object超类的构造方法
this.name = _name;
this.age = age;
this.money = money;
}
public void getMoney(){
money = 2402295;
System.out.println("money = "+money);
}
public static void getName(){
name = "张三";
System.out.println("name = "+name);
}
}
public class SuperDemo extends SuperFather {
//当父类没有无参构造方时,在子类构造方法中实现调用父类的构造方法
public SuperDemo(String name, int age, double money) {
super(name, age, money); //子类构造器中可以调用父类构造器的初始化代码
super.getClass(); //调用Object超类中的getClass方法
super.getMoney(); //调用父类中的实例方法
super.getName(); //调用父类中的类方法
System.out.println(super.age); //调用父类中的实例变量
System.out.println(super.tag);
}
//public SuperDemo(){} 子类的无参构造方法不能被编译通过,
private String tag ="隐藏父类tag";
public void getSuper(){
System.out.println("-----------------华丽的分割线---------------------");
super.getClass(); //调用Object超类中的getClass方法
super.getMoney(); //调用父类中的实例方法
super.getName(); //调用父类中的类方法
System.out.println(super.age); //调用父类中的实例变量
System.out.println(super.tag);
}
public static void getSuperInfo(){
//System.out.println(super.age); //调用父类中的实例变量编译出错,
//System.out.println(super.tag);
System.out.println(name); //使用类名或者默认方式调用父类的类变量
System.out.println(SuperFather.name);
}
}
public class HideTest {
public static void main(String[] args) {
SuperDemo superdemo = new SuperDemo("张三", 0, 0);
//System.out.println(superdemo.tag); //该tag在子类中是私有的,因此编译出错
System.out.println(((SuperFather) superdemo).tag); //这种方式是对象上转型,调用父类的tag
superdemo.getMoney(); //调用父类方法
superdemo.getSuper(); //调用自己的方法
superdemo.getName(); //调用父类的类方法
superdemo.getSuperInfo(); //调用自己的类方法
SuperFather.getName(); //父类类名调用方法
}
}
四、Super与this在构造方法中的对比应用
说明:1.子类不会得到父类的构造方法,但是可以在子类的构造方法中使用Super调用父类构造方法,。
2.在一个构造方法中调用另一个重载的构造器使用this调用来完成,在子类构造方法中调用父类的构造方法使用Super。
3.当调用子类构造方法初始化子类对象时,总是首先调用父类构造方法,父类再调用其父类,知道根类Object的构造方法,。
4.子类调用父类构造方法的情况
4.1子类构造方法体内部第一行使用Super显示调用父类构造方法,系统自动查找符合的父类构造方法(这一段代码必须放在第一行)
4.2子类构造方法体的第一行使用this显示调用本类的其他重载的构造方法,系统自动查找符合的子类构造方法
4.3在上述两种情况都没有的时候,系统在执行子类构造方法中自动隐士的调用父类的无参构造方法。
4.4在调用普通方法,变量时,super只能调用父类方法,变量,this既可以调用父类方法,变量,也可以调用自己的方法变量,当然是在访问权限允许的情况下,static限制下。
实例代码10:super与this在方法里的使用
public class Base {
public double size;
public String name;
public Base(double size, String name) {
this.size = size;
this.name = name;
}
public void getInfo(){
System.out.println("我是父类信息。。。。。");
}
public static void info(){
System.out.println("我是父类static方法。。。。。");
}
}
public class Sub extends Base{
public String color;
/**
*
* @param size
* @param name
* @param _color
* 在第一个和第二个,第三个构造方法中,我们可以看出父类没有无参构造方法的情况下,
* 子类的所有重载构造函数里的参数列表都必须包含父类里构造方法的参数列表,而且显示使用Super关键字调用父类构造函数
* 否则该重载构造方法报错
*/
public Sub(double size, String name,String _color) {
super(size, name); //父类只有有参构造方法的情况下,子类只有有参构造方法
//因此this调用子类的其他构造方法的情况不会存在
System.out.println("我的参数列表里不仅有父类的参数,而且还有自己的参数。。。。。");
this.color = _color; //使用this给子类的变量初始化
}
public Sub(double size, String name) {
super(size, name); //父类只有有参构造方法的情况下,子类只有有参构造方法
//因此this调用子类的其他构造方法的情况不会存在,即使存在也只能是普通方法的调用如下:
System.out.println("我的参数列表只有父类的参数。。。。。。");
this.Sub( 2.0, "父类名称","子类颜色"); //调用子类普通方法
}
/**
* 作者:FCs
* 描述:这个是普通方法,虽然方法名跟类名相同,但是访问权限和返回类型不符合构造方法的特征
* 如果没有这个方法,在第二个构造方法中的this代码段将会编译出错
* 时间:2014年8月17日
*/
private void Sub(double d, String string, String string2) {
System.out.println("--------------------------");
super.getInfo(); //普通方法里使用Super调用父类实例方法
super.info(); //普通方法里使用Super调用父类的类方法
this.getInfo(); //普通方法里使用this调用父类实例方法
this.getColor(); //普通方法里使用this调用子类实例方法
this.info(); //普通方法里使用this调用父类的类方法
}
public void getColor(){
System.out.println("color = "+color);
}
//public Sub(){
//这个构造方法只有在父类有无参构造方法的时候才可以使用
//}
public static void main(String[] args) {
Sub sub = new Sub(2.3,"");
Sub sub1 = new Sub(2.4,"asdf","243");
sub1.Sub(2.4,"asdf","243");
}
}