面向对象(上)

文章目录

一、类和对象

概要:类是面向对象的重要内容,可以把类当成一种自定义的类型,可以使用类来定义变量,这种类型的变量统称为引用变量,所有类都是引用类型。

01、定义类
  • 类和对象的区别

    类是对某一类事物的共性抽象的概念,而对象描述的是一个具体的事物,是一个具体存在的实体。

  • 定义类格式
    修饰符 class 类名{
           0~n个构造器;
           0~n个成员变量;
           0~n个方法;
    }
    
  • 修饰符

    修饰符一般有public、final、abstract、private、protected、static等,其中一般使用public、final、和private、protected来修饰类,都可以用来修饰方法,在修饰方法的时候,abstract和final最多只能出现其中之一,它们可以与static组合起来修饰方法。在修饰构造器的时候,修饰符可以是public、protected、private其中之一。这些修饰符在省略的时候,系统默认使用public作为修饰符。

  • 定义成员变量语法
    [修饰符] 类型 成员变量名 = [初始值]
    类型:java中的基本数据类型和引用类型
    
  • 定义方法格式
    [修饰符] 方法返回值类型 方法名(形参列表){
        //方法体
    }
    
  • static关键字

    “static”是一个特殊的关键字,它可以用来修饰方法、成员变量和初始化块等。static修饰的成员和方法属于这个类本身,而不属于这个类实例,所以也可以称它们为类方法(静态方法)和类变量(静态变量),而不被static修饰的成员变量和方法分别被称为成员变量(非静态变量)和普通方法(非静态方法)。静态成员不能访问非静态成员。

  • 定义构造器语法
    [修饰符] 构造器名(形参列表){
        //构造器执行体
    }
    

    构造器是一个特殊的方法,也是一个类创建对象的根本途径。当构造器使用void声明之后,编译不会出错,但是java会把该构造器当成普通方法处理,它不再是构造器,实际上构造器是有返回值的,返回值是当前类,构造器的返回值是隐式的。

02、对象的产生与使用
  • java对象的 产生

    创造对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例。

  • java对象的作用

    2.1、访问对象的实例变量。

    2.2、调用对象的方法。

    2.3、static修饰的成员变量和方法即可以通过类来调用,也可以通过类实例来调用,没有使用static修饰的普通方法和成员变量只能通过类实例调用。

03、对象、引用与指针
  • java对象存储示意图

在这里插入图片描述

在这里插入图片描述

  • 引用变量与c中指针的区别

    实际上,java的引用就是c里的指针,只是java语言将这个指针封装起来,避免开发者进行繁琐的指针操作。

  • 对象的垃圾回收机制

    ​ 如果内存区的对象里没有任何变量再指向该对象,那么程序将无法再访问该对象,这个对象也就成为了垃圾,java 的垃圾回收机制将回收该对象,释放该对象所占的内存区。

    ​ 因此,如果希望通知垃圾回收机制回收某个对象,只需要切断所有引用变量与这个对象信息之间的联系,也就是将这些引用变量赋值为null。

04、对象的this引用
  • this默认引用情形

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

    4.1、构造器中引用该构造器正在初始化的对象。

    4.2、在方法中引用调用该方法的对象。

  • this引用的作用

    ​ this关键字最大的作用就是让类中的一个方法,访问该类里的另一个方法或实例变量,this可以代表任何对象,当this出现在某个方法体中的时候,它所代表的对象是不确定的,但是类型是确定的,它代表的只能是当前类的实例,只有当这个方法被某个确切的对象所调用,这个this代表的对象才确定下来,谁在调用这个方法,它就代表谁。

    ​ java允许对象的一个成员直接调用另外一个成员,可以省略this前缀。

  • 为什么静态成员不能直接访问非静态成员?

    ​ 对于static关键字来讲,它所修饰的方法属于类方法,可以直接被类所调用,也可以被该类实例所引用,调用static方法的主调总是类本身,如果在这样的方法中使用this关键字,那么这个关键字就无法指向有效的对象,则会使得编译错误。由于不能在static方法中使用this关键字,那么在static方法中也就无法调用非静态成员变量,所以java语法规定:静态成员不能直接访问非静态成员。

  • 注意
    1. java编程的时候不要使用对象去调用static修饰的成员变量、方法,而是应该使用类来调用static修饰的成员变量、方法。
    2. this引用也可以用于构造器中默认引用,由于构造器是直接使用new关键字来调用,而不是对象直接调用的,所以this关键字在构造器中代表的是该构造器正在初始化的对象。
    3. 如果在构造器中有一个与成员变量相同的局部变量,又必须在这个构造器中访问这个被覆盖的成员变量,这个时候就必须加上this关键字。
    4. 类中的成员变量和普通方法只能通过类实例调用。

二、方法详解

01、方法的所属性
  • 在java中方法不能独立存在,方法必须属于类和对象。
  • 永远不能独立执行方法,执行方法必须使用类或对象作为调用者。
  • 使用static修饰的方法可以使用类来调用也可以使用类实例来调用。
02、方法的参数传递机制

java方法的参数传递方式只有一种,那就是值传递。所谓的值传递指的就是将实际参数值的副本(复制品)传入方法内,而方法本身不受影响。

  • 在执行类中的方法的时候,系统会为方法分配内存区存放局部变量。

    public class Test{
        public static void swap(DW dw){
            int temp = dw.a;
            dw.a = dw.b;
            dw.b = temp;
        }
        public static void main(String[] args){
            DW dw = new DW();
            dw.a = 6;
            dw.b = 9;
            swap(dw);
            //此时dw对象中a与b成员变量的值交换,因为对象信息是存放在堆内存区的,在main方法中执行swap方法的时候,复制了dw引用变量的值(对象信息地址)到swap栈区,这样的话在swap方法中对dw对象成员变量的更改是在堆内存执行的,而main方法中的对象(引用变量)指向的也是这块区域,则最终呈现的结果是dw对象成员变量值交换。
        }
    }
    
03、形参个数可变的方法
  • 概要

    从jdk1.5之后,java允许定义形参个数可变的参数,从而允许为参数指定数量不确定的形参。如果在定义方法的时候,在最后一个形参的类型后增加三点(…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。

  • 样例
    public class Test{
        public static void test(int a,String... books){
            for(String book:books){
                ...
            }
        }
    }
    
  • 以数组形参声明和以可变个数形参形式定义方法的相同点和区别?
    1. 两者在方法体内操作的都是数组。

    2. 在调用的时候以可变形参的形式定义的方法更加简洁。

      test(5,"中国","每个");
      
    3. 数组形式的形参可以位于形参列表的任意位置,但是可变个数形参只能位于形参列表的最后,也就是说一个方法中最多只能有一个个数可变的形参。

04、递归方法
  • 概要

    一个方法体内调用它自身,被称为方法递归。方法递归包含了一种隐式的循环,它会重复执行某段代码,但是这种重复无须循环控制。

  • 案例

    遍历某个路径下的所有文件。

05、方法重载
  • 概念

    java允许同一个类中出现多个同名方法,只要它们的形参列表不同就行。如果一个类中包含了两个或两个以上方法的名称相同,但形参列表不同,我们称之为方法重载。

  • java确定一个方法的三要素
    1. 调用者,也就是方法的所属者,即可以是类,也可以是对象。
    2. 方法名,方法的标识。
    3. 形参列表,调用方法的时候,系统会根据传入的实参列表进行匹配。
  • 为什么方法的返回值不能用于区分重载的方法?

    对于int f(){}和void f(){}两个方法来讲,如果我们这样调用int result = f(),系统可以识别为我们使用的是第一个方法,但是java在调用方法的时候允许忽略掉返回值,即是我们可以在main方法中直接书写f();这个时候java就不知道我们到底想要调用哪一个f()方法了,所以方法的返回值不能用于区分重载的方法。

  • 重载的方法里包含个数可变的形参的情况。
    public class overLoad{
        public void test(String msg){
            ....
        }
        public void test(String... msgs){
            ....
        }
        public static void main(String[] args){
            overLoad o = new overLoad();
            //下面两次调用执行的是第二个test方法
            o.test();
            o.test("aa","bb");
            //下面调用的是第一个test方法
            o.test("aa");
            //下面调用的是第二个test方法
            o.test(new String[]{"aa"});
        }
    }
    
  • 注意
    1. 方法重载要求的是两同一不同,同一个类中方法名称相同,参数列表不同,至于方法的其他部分,至于方法的返回值类型、修饰符等,与方法重载没有任何关系。
    2. 不推荐重载形参个数可变的方法,这样做没有多大意义。

三、成员变量和局部变量

01、成员变量和局部变量

在这里插入图片描述

  • 类变量生命周期

    从该类的准备阶段开始存在,直到系统完全销毁这个类。

  • 实例变量生命周期

    从类的实例被创建起开始存在,直到系统完全销毁这个实例。

  • 局部变量生命周期

    在局部范围内从定义开始生效,局部操作完成之后失效。

02、成员变量初始化和内存中运行机制
Person p1 = new Person();
Person p2 = new Person();
p1.name = "张三";
p1.age = 18;
p1.eyeNum = 2;
p2.eyeNum = 3;

class Person{
    public String name;
    public static int eyeNum;
}

在这里插入图片描述

03、局部变量初始化和内存中运行机制

局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。

04、变量使用规则
  1. 描述类或对象的固有信息的变量,比如人的身高,性别等,这些变量定义为成员变量。
  2. 信息对类的所有实例应该是相同的情况下,比如人的眼睛,定义为类变量。
  3. 某个信息需要在类的多个方法中共享,这种变量定义为成员变量。

四、隐藏和封装

01、理解封装
  • 概念

    封装是面向对象的三大特征(另外两个是继承和多态)之一,它指的是将类的状态信息隐藏在类的内部,不允许外部程序直接访问,只能通过类提供的方法来实现对类的内部信息的操作和访问。

    重点:将该隐藏的隐藏起来,该暴露的暴露出来,通过访问控制符实现。

02、使用访问控制符
  • 访问控制符

    1. private:(当前类访问权限)

    2. default:(包访问权限)

    3. protected:(子类访问权限)

    4. public:(公共访问权限)

      privatedefaultprotectedpublic
      同一个类中
      同一个包中
      子类中
      全局范围中

    **注意:**在一个java源文件中定义了一个被public修饰的类,那么这个文件名必须和类名保持一致。

  • JavaBean

    概念:如果一个java类中的每一个实例变量都被private修饰,并为每一个实例变量都提供了public修饰的getter和setter方法,那么这个类就是一个符合JavaBean规范的类。所以JavaBean总是一个封装良好的类。

03、package、import和import static
  • 包的概念:Java引入了包(package)的机制,提供了类的多层命名空间,用以解决类的命名冲突和类的管理问题。

  • 规范:

    1. 包名应该全是小写字母组成。
    2. package语句必须作为源文件的第一条非注释性语句存在,一个源文件只能指定一个包。
    3. 父包和子包在用法上不存在任何关系。
  • 静态导入包

    1. 语法:

      静态导入使用import static语句,静态导入语句也有两种语法,一种是导入指定类的单个静态成员变量和单个静态方法,另外一种是导入指定类的全部静态成员变量和静态方法。

      import static package.subpackage...ClassName.fieldName|methodName
      
      import static package.subpackage...ClassName.*;
      
    2. 总结:

      使用import可以省略写包名;而使用import static则可以连类名都可以省略。

04、java常用包
包名描述
java.lang包含了java语言的核心类,如String,Math,System和Thread类等,使用这个包下的类,无须使用import语句导入,系统会自动导入这个包下的所有类。
java.utiljava工具类和接口,框架类和接口,例如Arrays和List、Set等。
java.netjava网络编程相关类和接口。
java.io输入输出编程相关的类和接口。
java.textjava格式化的类。
java.sqljava进行jdbc数据库编程的相关的类和接口。
java.awt抽象窗口工具集相关类和接口。
java.swingSwing图形用户界面编程的相关类和接口。

五、深入构造器

概念:构造器是一个特殊的方法,这个方法用于创建实例时候执行初始化。构造器是创建对象的重要途径,但不是根本途径,因此java类必须包含一个及以上数量的构造器。

01、使用构造器执行初始化
  • 默认构造器(无参构造器)执行对象初始化的过程?

    当创建一个java对象的时候,系统为对象中的成员变量执行初始化,这种默认的初始化会使得所有基本数据类型的实例变量(数值变量)值为0,或false(布尔型变量),引用类型值为null,这是在对象中的实例变量没有指定初始值的情况下

  • 构造器完全负责创建java对象?

    这个说法是不对的,构造器是创建对象的重要途径。通过new关键字来调用构造器的时候,系统已经为对象分配内存空间,并且为这个对象执行默认初始化,这个对象已经产生了,也就是说,还没有执行构造器中的方法体的时候,java对象已经产生了,只是这个对象暂时还不能被外部程序所访问,只能在该构造器中通过this来引用。当构造器中的之行体执行完成之后,这个对象作为构造器的返回值返回,通常这个值被赋值给一个引用型变量从而让外部程序可以访问这个对象。

02、构造器重载
  • 概念

    同一个类中有多个构造器,这些构造器的形参列表不同,即被称为构造器的重载。

  • 注意:

    1. 系统通过new调用构造器的时候,系统会根据传入的实参列表来决定调用哪个构造器。

    2. 为了在一个构造器中引用另一个构造器的初始化代码,又不会创建一个对象的情况下,可以使用this关键字来调用相应的构造器。

      public class Apple{
          public String name;
          public String color;
          public String weight;
          public Apple(String name,String color){
              this.name = name;
              this.color = color;
          }
          public Apple(String name,String color,String weight){
              this(name.color);
              this.weight = weight;
          }
          public Apple(){
              
          }
      }
      
    3. 使用this调用另一个重载的构造器只能在构造器中使用,而且必须作为构造器执行体中的第一句。使用this调用重载的构造器的时候,系统会根据this后括号里的实参来调用形参列表与之对应的构造器。

六、类的继承

概念:继承是面向对象的三大特征之一,也是实现软件复用的重要手段。Java继承具有单继承的特点,也就是说一个子类只能拥有一个直接父类。

01、继承的特点
  • 语法

    修饰符 class SubClass extends A{
        
    }
    
  • 特点

    1. 实现继承的类被称之为子类,被继承的类被称为父类。

    2. 子类扩展了父类,子类可以获得父类的全部成员变量和方法。

    3. 子类不能获得父类的构造器,但是子类构造器中可以调用父类的构造器。

在这里插入图片描述

02、重写父类的方法
  • 概念:

    子类和父类出现同名方法,这种现象称为方法重写,也称为方法覆盖。

  • 方法重写规则:”两同两小一大“

    1. ”两同“指的是,两个方法的名称和形参列表相同。
    2. ”两小“指的是,子类方法的返回值比父类方法中的返回值类型更小或相等,子类方法抛出的异常类型比父类抛出的异常类型更小或相等。
    3. ”一大“指的是,子类方法的访问权限比父类同名方法的访问权限更大或相等。
    4. 覆盖方法和被覆盖方法要么都是实例方法,要么都是类方法。
  • 注意:如果父类中的方法被private修饰,这个时候子类中出现同名、同形参列表的方法,子类中的方法并不叫做重写父类方法。

  • 方法重载和方法重写的区别?

    1. 方法重载主要发生在同一个类中多个同名但是形参列表不同的方法中。
    2. 方法重写主要发生在父类与子类中方法名相同、形参列表也相同的方法中。
    3. 父类与子类中也可以发生方法的重载。
03、super限定
  • 概念:super是java的一个关键字,super用于限定该对象从父类中获得的实例和方法。正如this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法中,因为static修饰的是类,该方法的调用者是一个类而不是一个对象。
  • 在某个方法中访问名为a的成员变量的执行顺序?
    1. 查找方法中是否存在该变量。
    2. 在该方法所在的类中查找看看是否存在这个变量。
    3. 在该类的父类中查找是否存在这个变量。
  • 注意:通过super作为限定调用实例变量和实例方法。
04、调用父类构造器
  • 注意:
    1. 在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用完成。
    2. super调用的是父类的构造器,而this调用的是同一个类中重载的构造器,所以this和super调用不会同时出现。
    3. 当调用子类构造器初始化对象的时候,父类构造器总会在子类构造器之前执行,不仅如此,执行父类构造器的时候,系统会继续往上追溯执行其父类的构造器,以此类推,创建任何java对象,最先执行的总是java.lang.Object类的构造器。

七、多态

概念:相同类型的变量调用同一个方法的时候呈现出多种不同的行为特征,这叫做多态。

01、多态性
  • 多态产生的原因?

    java引用变量有两种类型,一种是编译时类型,一种是运行时类型。编译时类型是由声明该变量的时候使用的类型决定的,运行时类型由实际赋给该变量的类型来决定的。如果编译时类型和运行时类型不一致,则可能导致多态。

  • 向上转型

    子类是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为**”向上转型“**,向上转型由系统自动完成。

  • 注意

    1. 实例变量不具有多态性,当父类引用变量指向子类对象的时候,调用父类的实例成员变量,访问的依旧是父类的实例成员变量。
    2. 引用变量在编译阶段的时候只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法
02、引用变量的强制类型转换
  • 基本类型之间的转换

    数值类型:整数型、浮点型、字符型。

  • 引用类型之间的转换

    引用类型之间的转换只能在具有继承关系的两个类型之间运行,如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,运行时类型为子类类型),否则会引发ClassCastException异常。

    public class Test{
        public static void main(String[] args){
            double d = 13.4;
            float f = 13.4f;
            long l = (long)d;
            //obj的编译类型为Object,实际运行类型为String,下面的强制转换可以执行
            Object obj = "hello";
            String str = (String)obj;
        }
    }
    
03、instanceof运算符
  • 概念

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:

boolean result = obj instanceof Class

其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。

注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

  • obj必须为引用类型,不能为基本类型

    int i = 0;
    System.out.println(i instanceof Integer);//编译不通过
    System.out.println(i instanceof Object);//编译不通过
    

    instanceof 运算符只能用作对象的判断。

  • obj为null

    System.out.println(null instanceof Object);//false
    

    关于 null 类型的描述在官方文档:https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.1 有一些介绍。一般我们知道Java分为两种数据类型,一种是基本数据类型,有八个分别是 byte short int long float double char boolean,一种是引用类型,包括类,接口,数组等等。而Java中还有一种特殊的 null 类型,该类型没有名字,所以不可能声明为 null 类型的变量或者转换为 null 类型,null 引用是 null 类型表达式唯一可能的值,null 引用也可以转换为任意引用类型。我们不需要对 null 类型有多深刻的了解,我们只需要知道 null 是可以成为任意引用类型的特殊符号

    JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。

  • obj为class类的实例对象

    Integer integer = new Integer(1);
    System.out.println(integer instanceof  Integer);//true
    
  • obj为class接口的实现类

    了解Java 集合的,我们知道集合中有个上层接口 List,其有个典型实现类 ArrayList

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    

    所以我们可以用 instanceof 运算符判断 某个对象是否是 List 接口的实现类,如果是返回 true,否则返回 false

    ArrayList arrayList = new ArrayList();
    System.out.println(arrayList instanceof List);//true
    

    或者反过来也是返回 true

    List list = new ArrayList();
    System.out.println(list instanceof ArrayList);//tru
    
  • obj为class类的直接或间接子类

    我们新建一个父类 Person.class,然后在创建它的一个子类 Man.class

    public class Person {
     
    }
    public class Man extends Person{
         
    }
    Person p1 = new Person();
    Person p2 = new Man();
    Man m1 = new Man();
    System.out.println(p1 instanceof Man);//false
    System.out.println(p2 instanceof Man);//true
    System.out.println(m1 instanceof Man);//true
    

    注意第一种情况, p1 instanceof Man ,Man 是 Person 的子类,Person 不是 Man 的子类,所以返回结果为 false。

  • 问题

在这里插入图片描述

八、继承与组合

01、使用继承的注意点
  • 优点

    1. 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
    2. 提高代码的重用性;
    3. 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有 父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
    4. 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
  • 缺点

    1. 继承是侵入性的,会破坏封装。只要继承,就必须拥有父类的所有属性和方法;
    2. 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
    3. 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在 缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。
  • 注意

    1. 如果希望父类中的某个方法被重写但是不希望其他类来访问,可以将这个方法使用protected修饰。

    2. 尽量不要在父类构造器中调用即将被子类重写的方法。

      原因:当系统创建子类对象的时候,系统总会先调用其父类的构造器,如果父类构造器中调用了已经被改写的方法,那么父类构造器中引用方法引用的是子类中重写的方法,如果这个重写的方法中引用了子类的实例变量,那么在创建这个子类对象的时候就会报空指针异常。

      class Base{
          public Base(){
              test();
          }
          public void test(){
              System.out.println("ok");
          }
      }
      class Sub extends Base{
          private String name;
          
          public void test(){
              System.out.println("ss" + name);
          }
          
          public static void main(String[] args){
              //下面代码会报空指针异常
              Sub sub = new Sub();
              
          }
      }
      
  • 何时需要从父类中派生子类?

    1. 子类需要增加额外的属性,而不仅仅是属性值的改变。
    2. 子类增加自己独有的行为方式(包括增加新的方法和重写父类的方法)。
02、利用组合实现复用

对于继承而言,子类可以直接获得父类的public方法,程序使用子类时,都可以直接访问该子类从父类那里继承到的方法;而组合则是把旧类对象作为新类的成员变量组合起来,用以实现新类的功能。

package chap5_7;

class Animal {
    private void beat() {
        System.out.println("心脏跳动......");
    }

    public void breath() {
        beat();
        System.out.println("吸一口气,吐一口气,呼吸中......");
    }
}

class Bird extends Animal {
    public void fly() {
        System.out.println("我在天空自在的飞翔.....");
    }
}

class Wolf extends Animal {
    public void run() {
        System.out.println("我在陆地上快速的奔跑......");
    }
}

public class InheritTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Bird b = new Bird();
        b.breath();
        b.fly();
        Wolf w = new Wolf();
        w.breath();
        w.run();

    }

}

package chap5_7;

class Animal {
    private void beat() {
        System.out.println("心脏跳动......");
    }

    public void breath() {
        beat();
        System.out.println("吸一口气,吐一口气,呼吸中......");
    }
}

class Bird {
    private Animal a;

    public Bird(Animal a) {
        // TODO Auto-generated constructor stub
        this.a = a;
    }

    public void breath() {
        a.breath();
    }

    public void fly() {
        System.out.println("我在天空自在的飞翔.....");
    }
}

class Wolf {
    private Animal a;

    public Wolf(Animal a) {
        // TODO Auto-generated constructor stub
        this.a = a;
    }

    public void breath() {
        a.breath();
    }

    public void run() {
        System.out.println("我在陆地上快速的奔跑......");
    }
}

public class CompositeTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Animal a1 = new Animal();
        Bird b = new Bird(a1);
        b.breath();
        b.fly();
        Animal a2 = new Animal();
        Wolf w = new Wolf(a2);
        w.breath();
        w.run();

    }

}

心脏跳动......
吸一口气,吐一口气,呼吸中......
我在天空自在的飞翔.....
心脏跳动......
吸一口气,吐一口气,呼吸中......
我在陆地上快速的奔跑......


  • 总结
    1. 如果采用组合的设计方式,先创建被嵌入类实例,此时需要分配2块内存空间,再创建整体类实例,也需要分配3块内存空间,只是需要多一个引用变量来引用被嵌入的对象。通过这个分析来看,继承设计与组合设计的系统开销不会有本质的差别。
    2. 如果Person类需要复用Arm类的方法,此时就应该采用组合关系来实现复用。
    3. 继承要表达的是一种 是 is-a的关系,而组合表达的是有 has a的关系。

九、初始化块

01、使用初始化块

在Java中,有两种初始化块:静态初始化块和非静态初始化块。它们都是定义在类中,用大括号{}括起来,静态代码块在大括号外还要加上static关键字。

  • 非静态初始化块(构造代码块):
    作用:给对象进行初始化。对象一建立就隐式运行,且优先于构造函数的运行。
    与构造函数的区别:非静态初始化块给所有对象进行统一初始化,构造函数只给对应对象初始化。
    应用:将所有构造函数共性的东西定义在构造代码块中。

  • 注意:普通初始化块、声明实例变量指定的默认值都可以认为是对象的初始化代码,它们的执行顺序与程序中的排列顺序是相同的。

    public class Ins{
        {
            a = 6;
        }
        int a = 9;
        public static void main(String[] args){
            //这里输出的a变量的值为9,当代码块和默认变量初始化语句调换位置之后,最终输出的是6.
            System.out.println(new Ins().a);
        }
    }
    
02、初始化快构造器
  • 注意:
    1. 初始化块总在构造器之前执行。
    2. 与构造器不同的是,它不接收任何参数。
    3. 实际上初始化块只是一个假象,使用javac命令编译java类后,该java类中的初始化块会消失–初始化块中代码会被还原到每个构造器中,且位于构造器所有代码的最前面。
03、静态初始化块
  • **概念:**使用static修饰符修饰的初始化块被称为静态初始化块。

  • 静态初始化块:
    作用:给类进行初始化。随着类的加载而执行,且只执行一次。
    与构造代码块的区别:
    1)构造代码块用于初始化对象,每创建一个对象就会被执行一次;静态代码块用于初始化类,随着类的加载而执行,不管创建几个对象,都只执行一次,它属于类成员。
    2)静态代码块优先于构造代码块的执行。
    3)都定义在类中,一个带static关键字,一个不带static 。

  • 案例

    class Root{
        static{
            System.out.println("Root的静态初始化块");
        }
        {
            System.out.println("Root的普通初始化块");
        }
        public Root(){
            System.out.println("Root的无参构造器");
        }
    }
    
    class Mid extends Root{
        static{
            System.out.println("Mid的静态初始化块");
        }
        {
            System.out.println("Mid的普通初始化块");
        }
        public Mid(){
            System.out.println("Mid的无参构造器");
        }
        public Mid(String msg){
            this();
            System.out.println("Mid的带参数构造器,其参数值" + msg);
        }
    }
    
    class Leaf extends Mid{
      class Root{
        static{
            System.out.println("Root的静态初始化块");
        }
        {
            System.out.println("Root的普通初始化块");
        }
        public Root(){
            System.out.println("Root的无参构造器");
        }
    }
    
    class Mid extends Root{
        static{
            System.out.println("Mid的静态初始化块");
        }
        {
            System.out.println("Mid的普通初始化块");
        }
        public Mid(){
            System.out.println("Mid的无参构造器");
        }
        public Mid(String msg){
            this();
            System.out.println("Mid的带参数构造器,其参数值" + msg);
        }
    }
    
    class Leaf extends Mid{
       static{
            System.out.println("Leaf的静态初始化块");
        }
        {
            System.out.println("Leaf的普通初始化块");
        }
        public Leaf(){
            super("疯狂java讲义");
            System.out.println("执行Leaf构造器");
        }
    }
    
    class Test{
        public static void main(String[] args){
            new Leaf();
            new Leaf();
        }
    }
    }
    
    class Test{
        public static void main(String[] args){
            new Leaf();
            new Leaf();
        }
    }
    
    //结果
    Root的静态初始化块
    Mid的静态初始化块
    Leaf的静态初始化块
    Root的普通初始化块
    Root的无参构造器
    Mid的普通初始化块
    Mid的无参构造器
    Mid的带参数构造器,其参数值疯狂java讲义
    Leaf的普通初始化块
    执行Leaf构造器
    Root的普通初始化块
    Root的无参构造器
    Mid的普通初始化块
    Mid的无参构造器
    Mid的带参数构造器,其参数值疯狂java讲义
    Leaf的普通初始化块
    执行Leaf构造器
    
    

十、总结

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值