• 第五章.面向对象(上)


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

    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-

     

  • 相关阅读:
    单链表
    队列

    面向对象的数组与查找算法
    线性结构
    数据结构与算法概述
    webstorm 格式化代码快捷键
    Web规范小记录1:Web文件规范
    豆瓣 API报错 ( "code":104 问题 )
    舔狗日记二之心灵日记(H5版本)
  • 原文地址:https://www.cnblogs.com/lanshanxiao/p/7255411.html
Copyright © 2020-2023  润新知