骏马金龙 (博客已搬家:www.junmajinlong.com)

网名骏马金龙,钟情于IT世界里的各种原理和实现机制,强迫症重症患者。爱研究、爱翻译、爱分享。特借此一亩三分田记录自己成长点滴!!!

java面向对象基础(二)

权限修饰符

权限修饰符包括public、private、protected和不加任何修饰符的default,它们都可以修饰方法和变量。其中public和默认的default(不加任何修饰符)这两个还可以修饰class。private和protected修饰类的情况只能在使用内部类时修饰,正常情况下不能使用这两个修饰符修饰类。

(1).public:用public修饰的变量及方法,包内及包外的任何类(包括子类和普通类)均可以访问;
(2).protected:用protected修饰的变量及方法,包内的任何类及包外那些继承了该类的子类才能访问,protected重点突出继承;
(3).default:没有用public、protected及private中任何一种修饰,其访问权限为default默认权限。默认访问权限的类、类属变量及方法,包内的任何类(包括继承了此类的子类)都可以访问它,而对于包外的任何类都不能访问它(包括包外继承了此类的子类)。default重点突出包;
(4)private: 用private修饰的变量及方法,只有本类可以访问,而包内包外的任何类均不能访问它。

就一句话:protected修饰符所修饰的变量和方法,只可以被子类访问,而不管子类是不是和父类位于同一个包中。default修饰符所修饰的变量和方法,只可被同一个包中的其他类访问,而不管其他类是不是该类的子类。protected属于包修饰符,还是子类修饰符,而default属于包修饰符。

从权限严格角度来说,private < default < protected < public

在考虑default修饰的权限时,它是包修饰符,其中没有加入到包中的"裸体类"属于同一个隐式的包中,因此可以互相访问。

例如,前面的person和student的继承关系中,将父类成员变量name加上private修饰符,于是下面的代码将编译出错。因为new子类对象时,构造方法中赋值给this.name,而这个name是继承自父类的,它是private的。因此对于子类来说,这个成员变量属于能看到,不能引用、不能操作的摆设属性。

class Person  {
    private String name;
    int age;

}

class Student extends Person {
    int studentID;

    Student(int id,String name,int age) {
        this.name = name;
        this.age = age;
        this.studentID = id;
    }

}

public class Inherit {
    public static void main(String[] args) {
        Student s1 = new Student(1,"Malongshuai",23);
    }
}

方法的重写(overwrite/override)

父类定义的成员相对来说都比较粗糙,当子类继承时,难免无法适当地描述子类。因此当子类对从父类继承的方法不满意时,可以重写方法。

例如Person类能eat(),但girl类吃饭是淑女的吃,boy类吃饭是粗鲁的吃。girl类很不满意,因为父类的eat()只能描述吃,不能描述怎么吃。于是girl类就重写eat()方法,让吃这个方法符合自身的淑女形象。

重写方法必须和被重写的方法具有相同的方法名称、参数列表和返回类型。重写的方法不能比被重写的方法权限更严格。从方法访问的角度来说,父类的方法都能被访问,子类重写后的方法却不能被访问,这显然是不合理的,且即使这是能访问父类方法,但重写的意义就丢失了。

重写方法时,最佳实践方式是copy整个被重写的方法的定义语句。因为即使重写方法的名称改变了,编译也不会出错。例如重写eat()结果写成了Eat(),编译是不会有任何错误出现的,此时它没有重写,而是新定义了一个Eat()方法。

class Person  {
    String name;
    int age;

    void eat() { System.out.println("eating...");}
}

class Student extends Person {
    int studentID;

    Student(int id,String name,int age) {
        this.name = name;
        this.age = age;
        this.studentID = id;
    }

    void eat() { System.out.println("graceful eating");}  //重写
    void study() {System.out.println("studing...");}
}

public class Inherit {
    public static void main(String[] args) {
        Student s1 = new Student(1,"Malongshuai",23);
        System.out.println(s1.studentID+","+s1.name+","+s1.age);
        s1.eat();  //调用重写后的eat方法
    }
}

super关键字

this关键字指向对象自身,而super关键字则指向对象中的父对象。如下图:

super既可以用来引用父对象的成员变量,也可以用来调用父对象的方法。例如下面的代码:

class FatherClass {
    public int value;
    public void f(){
        value = 100;
        System.out.println("FatherClass.value="+value);
    }
}

class ChildClass extends FatherClass {
    public int value;
    public void f() {
        super.f();       //虽然f()要重写,但父对象的f()函数还有一些用武之地来发挥余热
        value = 200;
        System.out.println("ChildClass.value="+value);
        System.out.println(value);
        System.out.println(super.value);
    }
}

public class TestInherit {
    public static void main(String[] args) {
        ChildClass cc = new ChildClass();
        cc.f();
    }
}

new出子对象时,父类和子类中都有value属性,它们都采用的初始化值0。当执行cc.f()时,调用子类的f()方法,该方法首先调用父对象的f()方法,父f()方法先将value赋值为100,这个value是父对象的属性,然后回到子f()中赋值value为200,这个value是子对象自身的value,随后输出的两个value都是子对象中的value属性,最后的super.value是父对象中的value属性。

虽然在图中看上去super和this的地位是相同的,但实际上它们之间很不公平,不公平之处在于有引用变量(上图中的cc)指向子对象,所以能够使用"return this"代码来返回一个子对象,但却不能使用"return super"来返回子对象中的父对象,因为没有引用变量指向父对象。

关于super调用的成员变量,需要区分清楚是子对象中的属性还是父对象中的属性。如果子对象和父对象中有同名属性var,在没有指定"this.var"和"super.var"时,仅模糊地指定var时将优先取子对象的属性,如果子对象中没有某属性,则var表示的是父对象中的属性。例如:

class Student extends Person {
    int studentID;
    int age = 33;

    Student(int id) {
        this.name = super.name + "x";
        this.age = age + 2;   //右边的age是子对象的属性,但如果将"int age = 33;"注释,则age是父对象的属性
        this.studentID = id;
    }
}

继承时构造方法的重写super()

子对象中总是包含父对象,这个父对象是怎么来的?对象都是通过构造方法构造出来的,因此在new子类对象的时候,会调用对应的子类构造方法构造子对象,正是这个时候使用super()方法表示调用父类构造方法将父对象构造出来的。

在写构造父对象的代码时有以下几个规则:

  1. 使用子类构造方法构造子对象时,必须要构造父对象。
  2. 子类可以在自己的构造方法中使用super(args)来调用父类的构造方法。同理,可以使用this(args)来调用本类其他的构造方法。
  3. super(args)必须写在子类构造方法中的第一行,因为要先构造出父对象,再慢慢填补子对象自身。如果没有显式书写super(args),则默认在第一行处调用父类无参数的构造方法,等价于super()。
  4. 如果子类构造方法中调用的super(args)在父类中不存在对应参数列表的构造方法,则编译错处。这包括没有显式指定super()时,且父类又重载了构造方法使得父类中没有了无参数的构造方法时。
class Person {
    String name;
    int age;

    Person() {
        System.out.println("Person()");
    }

    Person(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person(arg1,arg2)");
    }

    Person(String name) {
        this.name = name;
        age = 20;
        System.out.println("Person(arg1)");
    } 
}

class Student extends Person {
    int studentID;

    Student(int id) {
        super("Malongshuai",23);   //第一行调用父类构造方法构造父对象,且是含有两个参数的Person(arg1,arg2)
        this.name = super.name + "X"; //调用父对象中的name属性
        this.age = age + 2;           //也是调用父对象中的属性age
        this.studentID = id;
    }
}

public class TestSuper {
    public static void main(String[] args) {
        Student s1 = new Student(1);
        System.out.println(s1.studentID+", "+s1.name+", "+s1.age);
    }
}

如果将"super("Malongshuai",23);"修改为super("Malongshuai"),则表示调用父类的Person(arg1)构造方法。如果改为super(),则表示调用父类的Person()构造方法。如果省略不写super,则等价于super()。

Object类

除了明确定义了从某个父类继承的子类,java中的所有类都是从java.lang包中的Object类继承来的。也就是说,Object类是所有类继承的根,也就是它们的祖宗。一级继承一级,最终的根总是Object类。

这个类里提供了几个方法,但基本上所有方法都建议重写,因为它的级别太高,抽象化的太严重,它的提供的那些方法也就太大众化。

toString()

在和对象做数据连接时,将自动调用该类的toString()方法。例如System.out.println("Hello" + Person)时,等价于System.out.println("Hello" + Person.toString())

例如:

public class TTString {
    public static void main(String [] args) {
        Person p = new Person();
        System.out.println(p);
        System.out.println(p.toString());
    }
}

class Person {}

编译并运行,查看toString()的运行结果。

D:\myjava
λ javac TTString.java

D:\myjava
λ java TTString
Person@15db9742
Person@15db9742

toString()的结果是"类名@hex(hashcode)"。官方建议,任何子类都应该重写该方法。例如:

public class TTString {
    public static void main(String [] args) {
        Person p = new Person();
        System.out.println(p);
    }
}

class Person {
    public String toString() {  //重写toString()
        return "Hello World";
    }
}

对象的比较"=="和equals()

对象与对象之间是否有相等关系?一般可以认为,如果两个对象的对象内容完全相同,将认为是相等的对象。

在进行对象比较时,"=="比较的是两个对象的引用地址,因此两个对象使用"=="比较时是绝对不会相等的。Object类中的equals(),基本等价于"==",因此也无法正确比较对象是否相等。所以官方手册建议重写equals()方法。在String类中已经重写好了。

例如,使用String类中的equals()。

public class TTequals {
    public static void main(String [] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}

final关键字

final表示最终的意思,它可以修饰变量、方法和类。

  1. final变量的值不能被改变。
    • (1).final的成员变量不可改变。
    • (2).final的局部变量和形参不可改变。
  2. final的方法不能被重写。
  3. final的类不能被继承。

也就是说,要想让变量只读、方法不可改变或类的继承到此结束,就用final进行修饰。

注:若您觉得这篇文章还不错请点击右下角推荐,您的支持能激发作者更大的写作热情,非常感谢!

posted @ 2017-12-27 20:32  骏马金龙  阅读(475)  评论(0编辑  收藏