第五章.面向对象(上)

类:可被认为是一种自定义的数据类型,可使用类来定义变量,所有使用类定义的变量都是引用变量,所有的类是引用类型。

Java程序使用类的构造器来创建该类的对象。

Java支持面向对象的三大特征:封装、继承、多态:

  java提供了private、protected、public访问控制修饰符来实现封装,提供extends关键字让子类继承父类,有了继承就有了多态。

构造器用于对类的实例进行初始化操作,构造器支持重载,若多个重载构造器里包含了相同的初始化代码,可以把这些初始化代码放置在普通初始化块里完成,初始化块总是在构造器

执行前被调用。

静态初始化块:用于初始化类,在类的初始化阶段被执行。若继承书里的某个类需要被初始化时,系统将会同时初始化该类的所有父类。

类包含三种常见的成员:

  1.构造器

    通过new关键字来调用构造器,返回该类的实例。

    一个类没有构造器,该类无法创建实例。所以Java语言默认功能:若程序员没有为一个类编写构造器,则系统会为该类提供一个默认的构造器;一旦提供了构造器,系统将

    不再为该类提供构造器。

    修饰符可以是public、protected、private(三选一),也可以省略

    构造器名必须和类名相同,构造器既不能定义返回值类型,也不能使用void声明构造器没有返回值。若构造器有了返回值类型或void,编译不会出错,但Java会把这个

    所谓的构造器当成方法来处理——它就不是构造器了

    不要在构造器中显示使用return返回当前类的对象,因为构造器中的返回值是隐式的。

    系统默认构造器是没有参数的。

  2.成员变量

    定义该类或该类的实例所包含的状态数据

    修饰符可以是public、protected、private(三选一),static、final,也可以省略

  3.方法

    定义该类或该类的实例的行为特征或功能实现

    修饰符可以是public、protected、private(三选一),final、abstract(二选一),static,也可以省略

  static修饰的成员不能访问没有static修饰的成员。static是一个特殊关键字,它修饰的成员表明成员属于这个类本身,不属于该类的单个实例。通常把static修饰的成员变量

  和方法称为类变量、类方法。

  static真正作用是用于区分成员变量、方法、内部类、初始化块这四种成员是属于类本身还是属于实例。

Java类的作用:以Person类为例

  1.定义变量

    Person p;

  2.创建对象

    p = new Person();

  3.调用类的类方法或访问类的类变量

    p.name = "lanshanxiao";

    p.say("Java 学习很简单,很容易!");

对象的this引用:

  this关键字总是指向调用该方法的对象。根据this出现的位置不同,this作为对象的默认引用有两种情形:

    1.构造器中引用 该构造器正在初始化的对象

    2.在方法中引用 调用该方法的对象

  this关键字最大的作用就是让类中一个方法,可以访问该类中的另一个方法或实例变量

  this可以代表任何对象,当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的:它所代表的只能是当前类的实例;只有当这个方法被调用时,它所

  代表的对象才能被确定下来:谁在调用这个方法,this就代表谁。

 1 class Dog{
 2     //定义一个jump()方法
 3     public void jump(){
 4         System.out.println("正在执行jump方法");
 5     }
 6     
 7     //定义一个run()方法,run()方法需要借助jump()方法
 8     public void run(){
 9         //使用this引用run()方法的对象
10         this.jump();
11         System.out.println("正在执行run方法");
12     }
13 }
14 
15 public class DogTest{
16     public static void main(String[] args){
17         //创建一个Dog对象
18         Dog dog = new Dog();
19         //调用dog对象的run方法
20         dog.run();
21     }
22 }
View Code
 1 class Dog{
 2     //定义一个jump()方法
 3     public void jump(){
 4         System.out.println("正在执行jump方法");
 5     }
 6     
 7     //定义一个run()方法,run()方法需要借助jump()方法
 8     public void run(){
 9         //调用自身jump()方法
10         jump();
11         System.out.println("正在执行run方法");
12     }
13 }
14 
15 public class DogTest{
16     public static void main(String[] args){
17         //创建一个Dog对象
18         Dog dog = new Dog();
19         //调用dog对象的run方法
20         dog.run();
21     }
22 }
View Code

  这说明从自身方法调用本身的另一个方法时,this是可以省略的。但这只是假象,这个程序省略的this依然存在。

  对于static修饰的方法而言,可以使用类名直接调用该方法,如果在static修饰的方法中使用this关键字,则this就无法指向合适的对象。所以static修饰的方法中不能使用this引用

  由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员,因此,Java语法规定:静态成员不能直接访问非静态成员。

下面是一个静态方法调用了非静态方法的例子:

 1 public class StaticAccessNonStatic{
 2     public void info(){
 3         System.out.println("简单的info方法");
 4     }
 5     
 6     public static void main(String[] args){
 7         //因为main()方法时静态方法,而info()非静态方法
 8         //调用main()方法的是该类本身,而不是该类的实例
 9         //因此省略的this无法指向有效的对象
10         info();
11     }
12 }
View Code

 

这里要提醒一句:

  Java编程中不要使用对象去调用static修饰的成员变量、方法。虽然可以使用对象调用static修饰的成员变量、方法,但是最好不要使用,当我们遇到了对象调用static修饰的

  成员变量、方法时,我们去修改成用类名调用static修饰的成员变量、方法。因为static修饰的成员变量、方法属于类本身,不属于任何实例。

若确实需要在静态方法中访问另一个普通方法,则只能重新创建一个对象:

 1 public class StaticAccessNonStatic{
 2     public void info(){
 3         System.out.println("简单的info方法");
 4     }
 5     
 6     public static void main(String[] args){
 7         //创建一个对象作为调用者来调用info()方法
 8         new StaticAccessNonStatic().info();
 9     }
10 }
View Code

 this也可以作为普通方法的返回值:

 1 public class ReturnThis{
 2     public int age;
 3     public ReturnThis grow(){
 4         age++;
 5         //return this返回调用该方法的对象
 6         return this;
 7     }
 8     
 9     public static void main(String[] args){
10         ReturnThis rt = new ReturnThis();
11         //可以连续调用同一个方法
12         rt.grow()
13           .grow()
14           .grow();
15         System.out.println("rt的age成员变量值是:" + rt.age);
16     }
17 }
View Code

使用this作为方法的返回值可以让代码更加简洁,但可能造成实际意义的模糊。

方法:

  Java语言中,方法不能独立存在,方法必须属于类或对象。

  一旦将一个方法定义在某个类的体内,若方法使用了static修饰,则方法属于类,否则方法属于类的实例。

  执行方法时必须使用类名或对象作为调用者。

  同一个类的一个方法调用另一个方法时,若被调方法是普通方法,则默认使用this作为调用者;若被调方法是静态方法,则默认使用类名作为调用者。

  使用static修饰的方法属于类本身,因此使用该类任何对象来调用这个方法时,将会得到相同的执行结果;没有static修饰的方法则属于该类的对象,不属于类本身,因此

  使用不同对象作为调用者来调用同一个普通方法,可能得到不同的结果。

形参个数可变的方法:

  若在定义方法时,在最后一个形参的类型后增加三点(...),则表明该形参可以接受多个参数值,多个参数值被当成数组传入:

 1 public class Varargs{
 2     //定义了一个参数个数可变的方法
 3     //和public static void test(int a, String[] books)等价
 4     public static void test(int a, String ... books){
 5         //books被当成数组处理
 6         for(String tmp : books){
 7             System.out.println(tmp);
 8         }
 9         //输出整数变量a的值
10         System.out.println(a);
11     }
12     
13     public static void main(String[] args){
14         //调用test方法
15         test(5, "疯狂Java讲义", "轻量级Java EE企业应用实战");
16         test(5, new String[] {"疯狂Android讲义", "经典Java EE企业应用实战"});
17     }
18 }
View Code

  个数可变的形参只能处于形参列表的最后,也就是说,一个方法中最多只能有一个个数可变的形参。

  个数可变的形参本质就是一个数组类型的形参,因此调用包含个数可变形参的方法时,该个数可变的形参既可以传入多个参数,也可以传入一个数组。

方法重载:

  方法重载要求两同一不同:同一个类中方法名相同,参数列表不同。至于方法的其他部分,如方法的返回值类型、修饰符等,与方法重载没有任何关系。

成员变量和局部变量: 

  成员变量:

              A:在类方法外

              B:在堆内存中

              C:有默认的初始化值

  局部变量:

             A:在方法定义中

             B:在栈内存中

             C:随着方法的调用而存在,随着方法的调用完毕而销毁

             D:除了形参之外的局部变量没有初始化的值,使用前必须定义和赋值

      E:定义局部变量后,系统并没有为这个变量分配内存空间,知道程序为这个变量赋初始值,才会为局部变量分配内存空间。

  Java允许局部变量和成员变量同名,若方法中的局部变量和成员变量同名,局部变量会覆盖成员变量,若需要在这个方法中引用被覆盖的成员变量,可以使用this(对于

       实例变量)或类名(对于类变量)作为调用者来限定访问成员变量:

    public void variable(String name){

      this.name = name;//成员变量this.name,局部变量name

    }

 隐藏和封装:

  封装是面向对象的三大特征之一,它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作

  和访问。

  访问控制级别:

    private:(当前类可以访问)

    default:(包访问权限)default访问控制的成员或外部类可以被相同包下的其他类访问

    protected:(子类访问权限)可被不同包下的子类访问,通常使用protected修饰一个方法,通常是希望其子类来重写这个方法

    public:(公共访问权限)

  若一个Java源文件中定义的所有类都没有使用public修饰,则这个Java源文件的文件名可以是一切合法的文件名;但若一个Java源文件中定义了一个public修饰的类,

  则这个源文件的文件名必须与public修饰的类的类名相同。

package、import和 import static:

  package:提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。

  java允许将一组功能相关的类放在同一个package下,从而组成逻辑上的类库单元。

1 package lanshanxiao;
2 
3 public class Hello{
4     public static void main(String[] args){
5         System.out.println("Hello World!");
6     }
7 }
View Code

自动产生一个lanshanxiao文件夹:

  一个Java源文件只能有一个package语句,但是可以有多个import语句。

    import com.lanshanxiao.*;//星号代表只导入com.lanshanxiao包下的所有类,但是不能导入com.lanshanxiao包下的子包

    import com.lanshanxiao.first.*;//同样只能导入com.lanshanxiao.first包下的所有类。

  Java默认为所有源文件导入java.lang包下所有类。

  静态导入(import static):

 1 import static java.lang.System.*;
 2 import static java.lang.Math.*;
 3 //import和import static的区别
 4 //import可以省略写包名
 5 //import static 连类名都省略
 6 //import static java.lang.System.*;//导入指定类的全部静态变量成员和静态方法,这里导入了System类下所有静态成员变量和静态方法
 7 //import static java.lang.System.out;//导入指定类的单个静态成员变量和静态方法,这里导入了out静态成员变量
 8 
 9 public class StaticImportTest{
10     public static void main(String[] args){
11         //out是java.lang.System类的静态成员变量,代表标准输出
12         //PI是java.lang.Math类的静态成员变量,表示圆周率常量
13         out.println(PI);
14         //直接调用Math类的sqrt静态方法
15         out.println(sqrt(256));
16     }
17 }
View Code

构造器:this();

 1 public class Apple{
 2     public String name;
 3     public String color;
 4     public double weight;
 5     
 6     public Apple(){}
 7     //两个参数的构造器
 8     public Apple(String name, String color){
 9         this.name = name;
10         this.color = color;
11     }
12     
13     //三个参数的构造器
14     public Apple(String name, String color, double weight){
15         //通过this调用另一个重载的构造器的初始化代码
16         this(name, color);
17         //下面this引用该构造器正在初始化的Apple对象
18         this.weight = weight;
19     }
20 }
21 
22 //程序中this(name, color);该语句只能在构造其中使用,而且必须作为构造器执行体的第一条语句。
View Code

  为了防止代码重复一般构造其中调用另一构造器的方法就是 this();

类的继承:

  关键在extends;

  方法重写@Override

    遵循“两同两小一大”规则,“两同”:方法名相同、形参列表相同;“两小”:子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比

    父类方法声明跑出的异常类更小或相等;“一大”:子类方法的访问权限应比父类方法访问权限更大或相等。

    覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。

1 //错误的方法覆盖
2 class BaseClass{
3     public static void test(){}
4 }
5 
6 class SubClass{
7     public void test(){}
8 }
View Code

  子类覆盖了父类同名方法,若想访问父类同名方法可以使用super.方法名()(被覆盖的是实例方法)访问父类同名方法 或 父类名.方法名()(被覆盖的是类方法)访问

  父类同名方法。

  若父类方法具有private访问权限,则子类不能继承该方法。也无法重写该方法。若子类中定义了一个与父类private方法同名的方法名、相同的形参列表、相同的返回值类型

  的方法,依然不是重写,只是在子类中重新定义了一个新方法。

1 class BaseClass{
2     //test()方法是private,子类不能继承
3     private void test(){}
4 }
5 
6 class SubClass{
7     //不是方法重写,所以可以增加static关键字
8     public static void test(){}
9 }
View Code

  super关键字:

    用于限定该对象调用它从父类继承得到的实例变量或方法。

    super也不能和static修饰符一起使用(和this一样)

    在构造器中使用super(),用于限定该构造器初始化的是该对象从父类继承得到的实例变量,而不是该类自己定义的实例变量。

 1 class Parent {
 2     public String tag = "疯狂Java讲义";
 3 }
 4 
 5 class Derived extends Parent{
 6     //定义一个私有的tag实例变量来隐藏弗雷德tag实例变量
 7     private String tag = "轻量级Java EE企业应用实战";
 8 }
 9 
10 public class HideTest{
11     public static void main(String[] args){
12         Derived d = new Derived();
13         //程序不可访问d的私有变量tag,所以下面语句将引起编译错误
14         System.out.println(d.tag);
15         //将d变量显式的向上转型为Parent后,即可访问tag实例变量
16         System.out.println(((Parent) d).tag);
17     }
18 }
View Code

 1 class Parent {
 2     public String tag = "疯狂Java讲义";
 3 }
 4 
 5 class Derived extends Parent{
 6     //定义一个私有的tag实例变量来隐藏弗雷德tag实例变量
 7     private String tag = "轻量级Java EE企业应用实战";
 8 }
 9 
10 public class HideTest{
11     public static void main(String[] args){
12         Derived d = new Derived();
13         //程序不可访问d的私有变量tag,所以下面语句将引起编译错误
14         //System.out.println(d.tag);
15         //将d变量显式的向上转型为Parent后,即可访问tag实例变量
16         System.out.println(((Parent) d).tag);
17     }
18 }
View Code

    使用在子类构造器中super()调用父类构造器,必须在子类构造器中的第一句(和this()一样)。

    不管是否显式使用super(),子类构造器总会调用父类构造器一次。

通过一个this()和super()的例子,看一下运行结果,想一想为什么会这样?

 1 class Creature{
 2     public Creature(){
 3         System.out.println("Creature无参数的构造器");
 4     }
 5 }
 6 
 7 class Animal extends Creature{
 8     public Animal(String name){
 9         System.out.println("Animal 带一个参数的构造器,该动物的name为:" + name);
10     }
11     
12     public Animal(String name, int age){
13         //使用this调用同一个重载的构造器
14         this(name);
15         System.out.println("Animal 带两个参数的构造器,该动物的age为:" + age);
16     }
17 }
18 
19 public class Wolf extends Animal{
20     public Wolf(){
21         //显示调用父类有两个参数的构造器
22         super("灰太狼", 3);
23         System.out.println("Wolf 无参数的构造器");
24     }
25     
26     public static void main(String[] args){
27         new Wolf();
28     }
29 }
View Code

多态:

  子类继承父类,并重写父类的方法,这样方法就会有了多态;但是实例成员变量不会有多态,引用类型是谁,则实例变量就是谁的;一旦使用多态(向上转型),那么

  父类中有的方法,子类才可以调用(编译看左边,运行看右边):

 1 class BaseClass{
 2     public int book = 5;
 3     public void test(){
 4         System.out.println("父类被覆盖的方法");
 5     }
 6 }
 7 
 8 class SubClass extends BaseClass{
 9     public String book = "疯狂Java讲义";
10     
11     public void test(){
12         System.out.println("子类覆盖父类方法");
13     }
14     
15     public void sub(){
16         System.out.println("子类普通方法");
17     }
18     public static void main(String[] args){
19         BaseClass bc = new SubClass();
20         System.out.println(bc.book);
21         bc.test();
22         
23         //因为bc编译时的类型是BaseClass
24         //BaseClass类中没有提供sub()方法,所以下面代码编译时会出错
25         bc.sub();
26     }
27 }
View Code

 1 class BaseClass{
 2     public int book = 5;
 3     public void test(){
 4         System.out.println("父类被覆盖的方法");
 5     }
 6 }
 7 
 8 class SubClass extends BaseClass{
 9     public String book = "疯狂Java讲义";
10     
11     public void test(){
12         System.out.println("子类覆盖父类方法");
13     }
14     
15     public void sub(){
16         System.out.println("子类普通方法");
17     }
18     public static void main(String[] args){
19         BaseClass bc = new SubClass();
20         System.out.println(bc.book);
21         bc.test();
22         
23         //因为bc编译时的类型是BaseClass
24         //BaseClass类中没有提供sub()方法,所以下面代码编译时会出错
25         //bc.sub();
26     }
27 }
View Code

   引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。

  通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。

强制类型转换:

  1.基本类型之间的转换只能在数值类型之间进行,数值类型包括整型、字符型、浮点型。但数值类型和布尔类型之间是不能进行类型转换的

  2.引用类型之间的转换只能在具有继承关系的两个类型之间进行,否则编译会报错。若试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才可以(即

   编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException异常。

  3.考虑到进行强制类型转换可能出现异常,因此进行类型转换之前先通过instanceof运算符来判断是否可以成功转换。

instanceof运算符:

  前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是一个接口)

  注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。

继承是实现类复用的重要手段,但继承带来了一个最大的坏处就是:破坏封装。

组合:采用组合方式来实现类复用则能提供更好的封装性。

  父类构造器中调用了被其子类重写的方法,则调用的是被子类重写后的方法:http://www.cnblogs.com/chuliang/p/5813641.html

 1 class Base{
 2     public Base(){
 3         test();
 4     }
 5     
 6     public void test(){
 7         System.out.println("将被子类重写的方法");
 8     }
 9 }
10 
11 public class Sub extends Base{
12     private String name;
13     public void test(){
14         System.out.println("子类重写父类的方法,其name字符串长度:" + name.length());
15     }
16     
17     public static void main(String[] args){
18         //下面的代码会引发空指针异常
19         Sub s = new Sub();
20     }
21 }
View Code

 

静态初始化块,普通初始化块,构造器,它们执行的顺序用一个Java程序来看:

 1 class Root{
 2     static{
 3         System.out.println("Root的静态初始化块");
 4     }
 5     
 6     {
 7         System.out.println("Root的普通初始化块");
 8     }
 9     
10     public Root(){
11         System.out.println("Root的无参数构造器");
12     }
13 }
14 
15 class Mid extends Root{
16     static{
17         System.out.println("Mid的静态初始化块");
18     }
19     
20     {
21         System.out.println("Mid的普通初始化块");
22     }
23     
24     public Mid(){
25         System.out.println("Mid的无参数构造器");
26     }
27     
28     public Mid(String msg){
29         //通过this调用同一类中重载的构造器
30         this();
31         System.out.println("Mid的带参数构造器,其参数值:" + msg);
32     }
33 }
34 
35 class Leaf extends Mid{
36     static{
37         System.out.println("Root的静态初始化块");
38     }
39     
40     {
41         System.out.println("Root的普通初始化块");
42     }
43     
44     public Leaf(){
45         //通过super()调用弗雷中有一个字符串参数的构造器
46         super("疯狂Java讲义");
47         System.out.println("执行Leaf的构造器");
48     }
49 }
50 
51 public class Test {
52     public static void main(String[] args){
53         new Leaf();
54         new Leaf();
55     }
56 }
View Code

  第一次new Leaf()和第二次new Leaf()结果是不同的。看上面的结果想一想为什么?

GitHub链接:https://github.com/lanshanxiao/-Java-

 

posted @ 2017-07-30 00:02  lanshanxiao  阅读(291)  评论(1编辑  收藏  举报