Java学习笔记之面向对象

时间:2022-03-02 12:22:59

3.面向对象

基本类型
运算符
流程控制
数组
====================================


面向对象
====================================
  * 人为抽象的一种编程模型

  * 将问题分解成一个一个独立的小问题,
    通过独立解决每个小问题,
    来解决复杂问题

  * 什么是面向对象

        封装、继承、多态



  类
  对象(实例)
  引用
  构造方法
  this
  方法重载
  继承
  重写
  super
  多态
  instanceof
  抽象类
  final
  static
  接口
  内部类



=======================================
  * 类比成“图纸”

  * 对事物、算法、逻辑、概念等的抽象
  * 封装相关的数据和逻辑运算方法









对象(实例)
=======================================
  * 类比成“从图纸创建的产品”

  * 从类创建出的具体实例
  * 对象占用独立的内存控件,
    来保存这个对象的属性数据
  * 独立控制一个对象,
    来执行一个方法








引用
=======================================
  * 类比成“遥控器”

  * 引用变量,保存一个对象的内存地址
  * 用引用,可以找到内存中对象的存储空间,
    控制或调用这个对象的属性数据和方法

  * 引用的特殊值: null

      null: 空,不保存任何对象的内存地址



1. 属性与方法
属性和方法都是类的成员,用于描述类的特征,每个类都可以有若干个属性、若干个方法。
public class Sample {
}
属性用于描述可以使用值进行量化的特征,通常属性的名称会使用名词,例如:
public class Person {
String from; // 默认为null
String name; // 默认为null
int height; // 默认为0
float weight; // 默认为0.0f
}
方法用于描述动作或者行为,通常方法的名称是动词,例如:
public class Person {
void eat() {
// 东西怎么吃的?
}
void run() {
// 怎么实现跑步的?
}
}
方法的参数:参数是当执行方法时所需要的条件。
public class Sample {
void sum(int x, int y) {

}
}
当方法声明了参数后,调用该方法时,必须提供匹配数据类型、匹配数量、匹配位置的参数值,例如:
public class Test {
public static void main(String[] a) {
Sample sample = new Sample();
// sample.sum(); // 错误
// sample.sum(1234); // 错误
// sample.sum("hello"); // 错误
sample.sum(10, 20); // 正确
}
}
每一个方法都可以有返回值,如果不需要返回值,则应该声明返回类型为void,如果需要,则声明为对应的类型,且在方法的最后,应该使用return语句返回对应类型的数据,例如:
public class Sample {
int sum(int x, int y) {
return x + y;
}
}
当调用有返回值的方法时,可以获取其返回值,也可以不获取,例如:
public class Test {
public static void main(String[] args) {
Sample sample = new Sample();
sample.sum(10, 15); // 仅调用,不获取值
int a = sample.sum(3, 7); // 调用,且获取值
}
}
对于声明为void返回值的方法,也可以使用return语句,但是,return关键字后不可以有任何值,例如:
public class Sample {
void test() {
return;
// System.out.println(); // 无法被执行的语句,错误
}
}
不管返回值是什么类型,return都应该是方法的最后一条有效语句。
为了便于开发,方法允许重载(Overload),即:在同一个类中允许存在若干个名称相同的方法,但是,这些方法的参数列表必须不同,例如:
public class Sample {
void run() {}
void run(int x) {}
void run(String str) {}
void run(String str1, String str2, int x) {}
void run(int x, String str2, String str1) {}
void run(String str1, int x, String str2) {}
}
参数列表的区别表现为:数量、数据类型、顺序

【测试题】
public class Test1 {
public static void add1(int i) {
i++;
}
public static void add2(int[] arr) {
arr[0]++;
}
public static void main(String[] args) {
int x = 10;
add1(x);
System.out.println("x=" + x); // 10
int[] array = { 9, 5, 2, 7 };
add2(array);
System.out.println("array[0]=" + array[0]); // 10
}
}
public class Test2 {
public static void main(String[] args) {
int x = 10;
int y;
y = x;
y = 15;
System.out.println("x=" + x); // 10
System.out.println("y=" + y); // 15

int[] arr1 = { 9, 5, 2, 7 };
int[] arr2;
arr2 = arr1;
arr2[0] = 15;
System.out.println("arr1[0]=" + arr1[0]); // 15
System.out.println("arr2[0]=" + arr2[0]); // 15
}
}




构造方法

构造方法是一类特殊的方法,其特征是:
1) 构造方法不允许声明返回值类型,即连void都不可以
2) 构造方法的名称必须与类的名称完全相同
如果类中没有显式的声明构造方法,则编译器会自动的添加公有的、无参数的构造方法,例如以下2段代码是等效的:
public class Person {
}
public class Person {
  public Person() {
    super();
  }
}

  * 新建对象时,执行的一个特殊方法

      new Soldier();
      new Car();

  * 一个类中,必须存在构造方法

  * 如果不定义构造方法,
    编译器会添加默认构造方法

      class A {
          public A() {
          }
      }

如果开发者显式的声明了构造方法,则编译器不会再自动添加构造方法。
构造方法可以用于创建对象时直接指定对象的某些属性值,例如没有构造方法时:
public class Person {
  public String name;
  public int age;
}
public class Test {
  public static void main(String[] args) {
    Person p = new Person();
    p.name = "Jack";
    p.age = 18;
  }
}
如果指定了构造方法:
public class Person {
  public String name;
  public int age;
 
  public Person(String personName, int personAge) {
    name = personName;
    age = personAge;
  }
}
public class Test {
  public static void main(String[] args) {
    Person p = new Person("Jack", 18);
  }
}



  * 构造方法重载(不同参数的多个方法)


      class A {
          public A() {}
          public A(int i) {}
          public A(int i,double d) {}
          public A(int i,double d,String s) {}
      }


构造方法也可以重载,例如:
public class Person {
  public String name;
  public int age;
 
  public Person() {
  }
 
  public Person(String personName, int personAge) {
    name = personName;
    age = personAge;
  }
}


  * 构造方法的作用:

      *)构造方法用于创建类的对象,例如:
public class Test {
  public static void main(String[] args) {
    Person p = new Person();
  }
}
      *)常见作用,为成员变量赋值








this
======================================
  * 两种用法:
      *) 引用当前对象
      *) 构造方法之间调用


  引用当前对象
  -----------------------------
    *) this 保存当前对象的内存地址

        当前对象: 正在调用的对象

    *) 用 this 调用当前对象的成员

          this.name
          this.toString()
          this.f()

  构造方法之间调用
  -----------------------------
    *) 目的:减少代码重复
    *) 一般从参数少的方法,调用参数多的方法,
       在参数最多的方法中编写代码,集中处理参数数据

    *) this(...) 必须是构造方法首行代码



提问:请详细描述“封装”

封装是面向对象的三大特征(封装、继承、多态)之一。
封装的具体表现是“装”与“封”。
装:将一组相关的属性和方法编写在一个类中。
封:使用相对比较严格的访问权限修饰各个属性,并提供相对宽松的set和get方法。
封装是“装了再封”的过程。
public class Student {
  public String name;
  private int age;
  public String from;

  public void setAge(int age) {
    if(age >= 12 && age<= 50) {
      this.age = age;
    }
  }

  public int getAge() {
    return this.age;
  }
}
public class Test {
  public static void main(String[] args) {
    Student stu1 = new Student();
    stu1.name = "Jack";
    stu1.setAge(18);
    // stu.age = 1800;
    stu1.from = "Beijing";
    System.out.println(stu1.getAge());

    Student stu2 = new Student();
    stu2.setAge(70);
  }
}




方法重载 Overload
=====================================
  * 同名不同参







继承
========================================
  * 作用: 代码重用、代码复用

  * 继承了什么:

        *) 可见的成员

  * 不继承什么:

        *) 构造方法
        *) 不可见的成员
        *) 静态成员

  * 单继承:

      *) 一个子类只能有一个父类
      *) 一个父类,可以有多个子类

  * 创建子类对象:

      1) 先创建父类对象,执行父类构造方法
      2) 再创建子类对象,执行子类构造方法

      *) 两个对象绑定在一起,
         整体作为一个子类对象

      *) 调用成员,先找子类,再找父类


  * 创建子类对象,执行父类构造方法 

      *) 默认执行父类无参构造方法

            super();

      *) 手动调用父类有参构造方法

            super(参数);


  * 方法重写 Override

      *) 从父类继承的方法,不满足子类需要,
         可以在子类中重新编写这个方法

      *) 在重写的方法中,
         可以调用父类同一个方法的代码

            super.toString()


  * super 两种用法

      *) 重写方法时,调用父类中同一个方法

            super.toString()

      *) 子类构造方法中,手动调用父类构造方法

            super(参数)

            *) 必须是首行代码








多态
============================================
  * 作用: 一致的类型

  * 类型转换

      *) 向上转型

           子类对象转型成父类型
           *) 向上转型后,
              只能调用父类定义的通用成员

      *) 向下转型

           已经转为父类型的子类对象,
           再转回成子类型


  * instanceof

      运行期类型识别

      *)对真实类型,及其父类型判断,
        都得到 true

      Shape s = new Line();

      s instanceof Line     true
      s instanceof Shape    true


提问:请详细描述多态的概念与特性
----------------------------------------
多态指的是编译期(声明变量时)和运行期(创建对象后)表现为不同的形态(数据类型)。
多态是综合了继承、重写、向上转型、动态绑定等机制的综合应用方式。
多态在代码中的表现可以简单的理解为:当需要父类的对象时,可以使用子类的对象来表示,且,调用方法时,会优先执行子类重写的方法。
Animal a = new Dog();
【相关专业名词】
1. 向上转型:声明时使用父级的类型,但是创建出的对象是子级的类型,例如:
Animal a = new Dog();
对象被向上转型以后,只能调用转型后(父级类别/声明时的类别)的类别的属性、方法,而不能再调用自身特有的属性和方法。
2. 向下转型:将父类对象强制转换为子类的对象,当然,前提是该父类的对象原本就是由子类创建出来的,例如:
Animal a = new Dog();
Dog d = (Dog) a;
或者:
TextView tvTitle = (TextView) findViewById(R.id.tv_title);
3. instanceof关键字:用于判断对象是否归属于某个类,例如:
dog instanceof Dog -> true
dog instanceof Animal -> true
dog instanceof Object -> true
4. 动态绑定:当实现继承关系,且向上转型后,对象被视为父级的类别,理应执行父类的方法,但是,如果子类重写了,则执行子类重写后的方法。
名词解释为:声明期决定了调用父类的方法,但是,在执行过程中,判定结果为执行子类重写的方法,所以称之为动态绑定。



抽象类
=====================================
  * 作用:
      *) 为子类提供通用代码
      *) 为子类提供通用方法的定义

  * 半成品类 (没画完的图纸)

  * 抽象类不能创建对象

  * 由子类,来实现抽象类中未完成的方法

  * 包含抽象方法的类,必须是抽象类

  * 抽象类中,不一定包含抽象方法



提问:
1. 请详细描述“抽象”的概念
  抽象使用abstract关键字,该关键字可以修饰方法,这样的方法叫作抽象方法,或者修饰类,这样的类叫作抽象类。
  抽象的方法不允许有方法体,抽象的方法不允许存在于普通的类当中。如果抽象方法在类中,则该类必须也是抽象的。
  抽象类中的方法并不要求是抽象的,即抽象类中可以没有抽象方法,也可以所有方法都是抽象的。
  抽象类被认为是“不完整”的类,所以,抽象类不可以直接创建对象,即不可以使用new语法创建对象,但是,抽象类也是存在构造方法的,且该构造方法会在创建其子类对象时被调用。
  由于抽象类本身不可以直接创建对象,所以,在绝大部分应用场景中,可以认为“抽象类就是用于被继承的”。
  抽象方法没有方法体,具体的实现应该是由子类来重写的。当某个类继承了抽象类之后,必须重写抽象类中的抽象方法,除非该子类也是抽象的。
 
  public abstract class Animal {
    public void eat(){};
    public abstract void run();
  }

  public abstract class Cat extends Animal{
    // public abstract void run();
  }

  public abstract class Dog{}






final
===================================
  * 修饰变量、方法、类

  * final 变量

      *) 变量的值不可变,称为“常量”
      *) 基本类型,值本身不可变
      *) 引用类型,引用内存地址不可变

          final int a = 10;
          //a = 100;//不能修改 a 的值

          ----

          final Point a = new Point(2, 3);
          a.x = 20;
          //a = new Point(5,7);
          //a = null


  * final 方法

      *) 方法不能在子类中重写

  * final 类

      *) 不能被继承

            System
            String


提问:3. 请详细描述final关键字的意义
final修饰类不允许被继承
final修饰的方法不可以被重写
final修饰的变量不允许被二次赋值




static
===================================
  * 静态
  * 静态成员属于类,而不属于实例
  * 静态成员可以称为:

        类变量
        类方法

  * 非静态成员可以称为:

        实例变量
        实例方法

  * 一般用类名调用静态成员

        A.age
        Integer.MAX_VALUE
        Math.random()

  * 静态方法中,不能调用非静态成员

  * 什么时候使用静态

      *) 使用原则: 能不用就不用

          *) 静态是“非面向对象”的语法

      *) 使用场景:

          *) 共享的数据
          *) 工具方法

               Math.sqrt()
               Math.random()

  * 静态初始化块

      class A {
          static {
              在类加载时,只执行一次
          }
      }


提问:请详细描述static关键字的作用与特点。
--------------------------------------------
static -> 静态的
被static修饰的成员将最优先加载到内存。
(被static修饰的成员,不能直接访问非static成员),且最后释放内存,即常驻内存,
被static修饰的成员将加载到内存的特定区域,在该区域内,这些成员都是唯一的。
public class Student {
  public static int i = 100;
  public String name;

  public static void main(String[] args) {
    i = 999; // 正确的
    // name = "Jack"; // 错误的
    Student stu = new Student();
    stu.name = "Jack"; // 正确的
  }
}


常量
=================================
  * static final

      static final int MAX_VALUE = 100;






对象创建过程
==========================================
  class A {
      int v1 = 1;
      static int v2 = 2;
      static {
          ...
      }
      public A() {
          ...
      }
  }

  class B extends A {
      int v3 = 3;
      static int v4 = 4;
      static {
          ...
      }
      public B() {
          ...
      }
  }


  new B()
  -----------------------------------------
    *) 第一次用到 A, B 两个类

       1)加载父类,为它的静态变量分配内存
       2)加载子类,为它的静态变量分配内存
       3)执行父类静态变量的赋值运算,并执行静态初始化块
       4)执行子类静态变量的赋值运算,并执行静态初始化块

    *) 每次创建对象时

       5)为父类对象分配内存,为它的非静态变量分配内存
       6)为子类对象分配内存,为它的非静态变量分配内存
       7)执行父类非静态变量的赋值运算
       8)执行父类的构造方法
       9)执行子类非静态变量的赋值运算
       10)执行子类的构造方法









访问控制符
===================================
  * 控制一个类或类中的成员的访问范围

                类    包    子类    任意
    public      O     O     O       O
    protected   O     O     O
    [default]   O     O
    private     O

  * 选择的原则:

      *) 尽量选择小范围
      *) public 是与其他开发者的一个契约,
         约定公开的东西会保持稳定不变








接口
=======================================
  * 作用:
        结构设计工具,
        用来解耦合

  * 接口是极端的抽象类

  * 用 interface 代替了 class

  * 用 implements 代替了 extends

  * 接口中只能定义:

        *) 公开的抽象方法
        *) 公开的常量
        *) 公开的内部类、内部接口


  * 一个类,可以同时实现多个父接口

      class A implements B,C,D {
      }

      class A extends B implements C,D,E {
      }

  * 接口之间继承

      interface A extends B,C,D {
      }




2. 请详细描述“接口”的概念
  接口的组成结构与类基本一致。
  接口中的所有成员都是公有(public)的。
  接口中的所有属性都是静态常量(static final)。
  接口中的所有方法都是抽象的(abstract)
  以下2种语法是完全等效的:
  public interface USB {
    String NAME = "usb-device";
    void read();
    void write();
  }
  public interface USB {
    public static final String NAME = "usb-device";
    public abstract void read();
    public abstract void write();
  }
  类与接口的关系是实现与被实现的关系,使用implements关键字,当类实现了接口,则该类称为该接口的实现类。
  同一个类可以同时实现若干个接口,各接口名称之间使用逗号进行分隔,且该类需要实现所有接口中的所有抽象方法,例如:
  public class MainActivity extends Activity implements OnClickListener, OnItemClickListener, OnCheckedChangeListener, OnSeekBarChangeListener { }
  接口与接口之间也可以继承,使用extends关键字,例如:
  public interface InterfaceA extends InterfaceB {}
  同一个接口可以同时继承若干个接口,各父级接口名称之间使用逗号进行分隔,例如:
  public interface InterfaceA extends InterfaceB, InterfaceC, InterfaceD {}
  假设某个类实现了InterfaceA,则该类必须重写InterfaceA、InterfaceB, InterfaceC, InterfaceD中所有的抽象方法。
  接口的作用通常是制定一套标准或规范。




内部类
=============================================
  * 定义在类内部、方法内部,或局部代码块内的类

  * 成员内部类
  * 静态内部类
  * 局部内部类
  * 匿名内部类


  成员内部类
 
成员内部类可以使用任何访问权限修饰符。
内部类可以访问外部类的任意成员,包括私有的。
在没有创建外部类的对象之前,不可以直接创建内部类的对象。

    *) 成员内部类实例,
       依赖于外部类实例存在

    *) 成员内部类中,不能定义静态成员

    class A {
        class Inner {

        }
    }

    A a = new A();
    Inner inner = a.new Inner();


  静态内部类
 
静态内部类不可以访问外部类中非静态的成员。
静态内部类可以直接创建对象,而不需要依赖于外部类的对象。
    class A {
        static class Inner {
        }
    }

    A.Inner inner = new A.Inner();



  局部内部类
 
局部内部类不可以使用任何访问权限修饰符。
使用局部内部类时,需要注意代码的先后顺序。
在局部内部类中,不可以直接访问局部变量,如果一定需要访问,可以使用final修饰该局部变量,或者将局部变量声明为全局变量(类的属性)。

    class A {
        Weapon f() {
            class Inner implements Weapon {
                ...
            }

            Inner inner = new Inner();
            return inner;
        }
    }


    A a = new A();
    Weapon w = a.f();
    w.kill();



  匿名内部类
 
直接创建已知的类的子类对象,或者已知的接口的实现类对象,而创建时,并不体现子类/实现类的名称,即创建出来的对象的类名是未知的
    Weapon w = new Weapon() {...};



提问:请详细描述内部类的种类和各种内部类的语法特征。
-----------------------------------
把某个类定义在其它类的内部,则称之为内部类。
内部类可以访问外部类的所有成员(静态内部类除外),无论这些成员是使用哪种访问权限修饰符进行修饰的。
1. 成员内部类
  在类(外部类)的内部,且与外部类的成员是“同一级别”的。
  public class OutterClass {
    private int i;
    private void run() {
      int x;
    }
    private void test() {
      int y;
    }
    public class InnerClass {
    }
  }
  成员内部类可以使用任何访问权限修饰符。
  成员内部类不可以在外部类的外部直接创建对象。
2. 局部内部类
  内部类的“定位”类似于局部变量,即声明在方法内部的类,则称之为局部内部类。
  public class MainActivity extends Activity {
    public Button btn;
    public int a;
    public void onCreate(final int i) {
      final int x = 100;
      class InnerClass {
        public void test() {
          System.out.println(x);
          System.out.println(i);
          System.out.println(a);
        }
      }
      InnerClass ic = new InnerClass();
      btn.setOnClickListener(ic);
    }
  }
  局部内部类不可以使用任何访问权限修饰符。
  局部内部类的定义、对象的创建,必须符合方法中代码的执行逻辑。
  局部内部类不可以直接访问局部变量(方法的参数等效于局部变量),如果一定需要访问,则需要使用final对局部变量进行修饰,使之成员常量,或者,将局部变量提升为成员变量。
3. 静态(成员)内部类
  使用static修饰的成员内部类,称之为静态内部类。
  public class OutterClass {
    public int x = 5;
    public static int y = 100;
    public static class InnerClass {
      public void run() {
        // System.out.println(x); // 错误
        System.out.println(y); // 正确
      }
    }
  }
  静态内部类只能访问外部类的静态成员,而不能直接访问外部类的非静态成员。
  静态内部类可以在任何位置(包括外部类的外部)直接创建对象。
4. 匿名内部类
  直接创建已知的类的子类的对象(或者已知的接口的实现类的对象),则该对象的类型就是匿名内部类,顾名思义,该类型并没有名称。
  public class OutterClass {
    public void run() {
      new OnClickListener() {
        public voic onClick() {
        }
      };
    }
  }




提问:
1. 请详细描述“单例模式”
单例模式表现为:实例唯一,即某个类的对象最多只允许存在1个。
>> 步骤1:普通的类是可以随意创建若干个对象的:
public class King {

}
public class Test {
  public static void main(String[] args) {
    King k1 = new King();
    King k2 = new King();
  }
}
>> 步骤2:将构造方法私有化,则在其它类中不可以创建King的对象
public class King {
  private King() {
  }
}
public class Test {
  public static void main(String[] args) {
    // King k1 = new King();
    // King k2 = new King();
  }
}
>> 步骤3:在King的内部仍然可以调用被私有化的构造方法,则可以创建公有的getInstance()方法,用于获取King的对象
public class King {
  private King() {
  }
  public King getInstance() {
    return new King();
  }
}
>> 步骤4:由于在类的外部不可以创建King的对象,则不可以调用getInstance()方法,所以,必须将该方法修饰为static
public class King {
  private King() {
  }
  public static King getInstance() {
    return new King();
  }
}
public class Test {
  public static void main(String[] args) {
    King k1 = King.getInstance();
    King k2 = King.getInstance();
  }
}
>> 步骤5:以上完成了King的对象的创建过程的限制,但是并没有实现单例!则应该在King的内部,声明King类型的对象,并使用static修饰,保证该对象是唯一的
public class King {
  private static King king;
  private King() {
  }
  public static King getInstance() {
    return new King();
  }
}
>> 步骤6:调整getInstance方法:
public class King {
  private static King king;
  private King() {
  }
  public static King getInstance() {
    if(king == null) {
      king = new King();
    }
    return king;
  }
}



2. 请详细描述“工厂模式”
工厂模式是用于生产对象的,即使用工厂类控制产品类的创建过程。
public class Fruit {
}

// Apple类去掉public修饰符,且与Fruit、FruitFactory在同一个包中,保证FruitFactory对Apple有访问权限
class Apple extends Fruit {
}

class Orange extends Fruit {
}

public class FruitFactory {
  public static Fruit newInstance(int type){
      Fruit f = null;
      switch(type){
        case 1:
          f = new Apple();
          break;
     
        case 2:
          f = new Banana();
          break;
      }
      return f;
  }
}