面向对象编程OOP

  • 抽象是有效控制程序复杂性的重要手段。类是目前支持抽象的最好工具
  • 对象的特性是属性,对象的行为是方法
  • 面向对象基本特点是抽象,封装,继承,多态

  • 慎用继承,优先使用组合
  • 所谓组合(Composition),就是在新类中创建现有类的对象。不管是继承和组合,都允许在新类中直接复用旧类的公有方法或字段。 ```java class Animal { public void beat(){

    1. System.out.println("My heart is beating");

    } public void breath(){

      System.out.println("I'm breathing");
    

    } } public class Cat { // 组合 private Animal animal; // 使用构造函数初始化成员变量 public Cat(Animal animal){

      this.animal = animal;
    

    } // 通过调用成员变量的固有方法使新类具有相同的功能 public void breath(){

      animal.breath();
    

    } // 通过调用成员变量的固有方法使新类具有相同的功能 public void beat(){

      animal.beat();
    

    } // 为新类增加新的方法 public void run(){

      System.out.println("I'm running");
    

    }

    public static void main(String[] args) {

      // 显式创建被组合的对象实例 animal
      Animal animal = new Animal();
    

    // 以 animal 为基础组合出新对象实例 cat

      Cat cat = new Cat(animal);
    

    // 新对象实例 cat 可以 breath()

      cat.breath();
    

    // 新对象实例 cat 可以 beat()

      cat.beat();
    

    // 新对象实例 cat 可以 run()

      cat.run();
    

    } }

//继承 public class Cat extends Animal{ // 为新类增加新的方法 public void run(){ System.out.println(“I’m running”);
} } Cat cat = new Cat(); // 子类实例 cat 可以 breath() cat.breath(); // 子类实例 cat 可以 beat() cat.beat(); // 子类实例 cat 可以 run() cat.run();

以上便是组合实现复用的方式,Cat 对象由 Animal 对象组合而成,如上面的示例代码,在创建 Cat 对象之前先创建 Animal 对象,并利用这个 Animal 对象来创建 Cat 对象。<br />实际上,组合表示出来的是一种明确的**整体-部分**的关系。而对于继承来说,是将某一个抽象的类,改造成能够适用于不同特定需求的类。

---

- 以上看起来组合比继承麻烦,但是**继承会打破封装性**,违反了 OOP 原则。迫使开发者去了解父类的实现细节,子类和父类耦合       而且**父类更新后可能会导致一些不可知的错误**

<br />
<a name="uTKMQ"></a>
## 面向过程编程POP
面向对象相比于面向过程对于方法和属性运用的效率更高。

---

<a name="FokvY"></a>
# 类和对象

- **java 程序由类和对象构成,而类又由方法和变量构成。java 的方法由语句构成,而语句又由标识符和运算符构成**

**类概括了同类对象的共有性质:数据和方法。**

- **类体中不能有独立的执行代码,所有的执行代码只能出现在方法中。**
- **类的对象又称为类的实例**
<a name="if7JF"></a>
# 引用

- **只有引用数据类型才有引用的概念,所以如string变量可以称为对象变量**
- **在 Java 中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用**
-  **在 Java 中,一切都被视为对象,但操纵的标识符实际上是对象的一个引用(reference)**。

举个形象点的例子:我们可以用遥控器(引用)去操纵电视(对象)。只要拥有对象的“引用”,就可以操纵该“对象”。换句话说,我们无需直接接触电视,就可通过遥控器(引用)自由地控制电视(对象)的频道和音量。此外,没有电视,遥控器也可以单独存在。就是说,你仅仅有一个“引用”并不意味着你必然有一个与之关联的“对象”。

- 如创建一个String引用`String s;`此时对 s 应用 String 方法,会报错。因为此时 s 没有与任何对象进行关联。**只有对象及其引用才能调用成员方法**
   - **创建一个引用的同时进行初始化即可关联对象  如**`String s = "小牛肉";`
- **将对象变量设置为null即表示没有****引用任何对象,****所以null引用也无法调用方法**
- **如果2个变量指向的是同一个对象。那么修改任何一个引用变量的值都会影响另外一个变量**
```java
Person{
int age;
main{
    person p1.age=10;
    p2=p1;
    p2.age=8;
    sout(p1.age);   ->p1.age=8;
}
}

New

  • new关键字是最常用的创建对象的方式,它返回的是一个引用
    • String s=new String("小牛肉")是创建了一个String类型的对象,并将该对象的引用存储到对象变量s中

  • 还可以让对象变量引用一个已经存在的对象

String str = new String("小牛肉"); String s; s = str
这是让对象变量s引用str引用的对象

clone()

  • 借助Object.clone()方法可以复制一个对象的复制。它们的各种属性都一样,但是之后可以改变各自属性而互不影响,详见链接

    变量

    变量按照定义域分为局部变量和全局变量。按照类型分为基本类型变量和引用类型变量。

  • 引用类型变量可以定义为null,基本类型不可以

  • 局部变量:方法体内和参数列表中的变量,位于栈内存
  • 成员变量:方法体外的变量,又分为静态变量(类变量)和成员变量(成员属性)。位于堆内存
  • 如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。这样可以减少bug的来源。(特别是创建构造函数时)
    • 不管是局部还是成员,最好都显式的主动初始化 | 基本类型 | 初始值 | | —- | —- | | boolean | false | | char | \u0000 (null) | | byte | (byte) 0 | | short | (short) 0 | | int | 0 | | long | 0L | | float | 0.0f | | double | 0.0d |

作用域

  • 一对大括号{}及其中的若干条语句称之为块

    变量的作用域

  • 变量的作用域都是由{}的位置决定!即只限于{}内

    • 类中的其他方法都能访问成员变量,但是主函数不能,这不是作用域的原因,这是由于“静态的只能访问静态的”

      见第一段代码

  • 成员变量在方法内会被同名局部变量覆盖(隐藏)

  • 见下,不能在2个嵌套的块中声明同名的变量

    见第二段代码

main{
    int x = 12; 
    {
        int q = 96;
    }
   sout(x+q);   //此时会报错,因为识别不到q这个变量。
    //q作用域只限于第二个{}中     
    //x限于第一个花括号,而第一个{}包含了第二个{}     所以x第二个花括号内也能访问到   
}
{
    int x = 12;
    {
        int x = 123; // c语言、c++中合法,java中非法。可以看成是在12x看来只有一个叫x的变量,但是在123x看来,有2个叫x的变量,重复了
    }
}

对象的作用域

  • Java 对象不具备和基本类型一样的生命周期当用 new 创建一个 Java 对象时,它可以存活于作用域之外。但是它的引用作用域还是看{}位置
  • Java 有一个垃圾回收器,用来监视 new 创建的所有对象,并辨别那些不会被再引用的对象,然后释放这些对象的内存空间

    public class Test {
      public static void main(String[] args) {
          String s = new String("DDD");   
      }
      Test() {
          System.out.println(s);   //此时报错,访问不到s
      }
    }
    

    调用顺序

  • 静态代码块>非静态代码块>构造方法

    方法

  • 分为实例方法和类方法。实例方法又叫成员方法。类方法又叫静态方法

  • 方法名和参数列表统称为方法签名(signature of the method)。签名是作为方法的唯一标识

    • 参数列表不同可以是参数的顺序不同,个数不同,类型不同 (顺序不同必须是类型也不同)

      参数的传递

      首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
  • 按值调用 (call by value ) 表示方法接收的是调用者提供的值。

  • 按引用调用 ( call by reference ) 表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值

  • Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容。(只限于基本类型) ```java public static void tripleValue(double x){ x *= 3; }

double percent = 10; tripleValue(percent);//调用这个方法之后,percent 的值还是 10。下面看一下具体的执行过程: / x 被初始化为 percent 值的一个拷贝(也就 是 10 ) x 被乘以 3 后等于 30。 但是 percent 仍然是 10 这个方法结束之后,参数变量 x 不再使用。 /


- **对象引用作为参数就不同了**,可以很容易地修改对象的字段值
```java
public class Employee {   // 员工类
    private int salary;  //薪水
    Employee(int salary){
        this.salary=salary;
    }
    public static void tripleSalary (Employee x) {  //3倍的工资
       x.salary*=3;
    }
    public static void main(String[] args) {
        Employee harry = new Employee(200);   //哈利的工资是200
        tripleSalary(harry);   //这里传入对象作为参数,方法便轻易的修改了方法的字段值
        System.out.println(harry.salary);
    }
}

可变参数

  • 在 「JDK 1.5」 之后,如果我们定义一个方法需要接受多个参数,并且「多个参数类型一致」,我们可以对其简化成如下格式:修饰符 返回值类型 方法名 (参数类型... 形参名){ }

… 用在参数上,称之为「可变参数」,它「表明这个方法可以接收任意数量的参数」

  • 等价于修饰符 返回值类型 方法名 (参数类型[] 形参名){ }

    • 一个方法里只能有一个可变参数,且可变参数得是最后一个参数位置 ```java public class ChangeArgs { //可变参数写法 public static int getSum(int… arr) { int sum = 0; for (int a : arr) {
       sum += a;
      
      } return sum; }

    public static void main(String[] args) { int[] arr = { 1, 4, 62, 431, 2 }; int sum = getSum(arr); System.out.println(sum);

    int sum2 = getSum(6, 7, 2, 12, 2121); System.out.println(sum2); } } ``` image.png

    static

  • 静态的只能访问静态的,而实例的可以访问实例也可访问静态的。
    • 静态要访问实例必须借助对象
  • static只能修饰成员变量,方法,内部类,代码块(不能修饰局部变量)
  • 类变量的内存只有一处,让类的所有对象共享它属于整个类,而不属于类的某个对象。

Test类有静态字段int a=5; 创建t1,t2两个对象。此时它们的a都为5。如果修改t1.a=8; 则t2.a也变为8

  • 引用类变量途径有两条,或通过类,或通过对象;当在其所在类调用时,可以省略类直接调用
    • 因为静态区属于类,所以不应该通过对象进行调用,虽然可以执行,但是会警告
    • 类方法调用和类变量调用方法差不多
  • 一个方法如果与它所在类的实例对象无关,那么它就应该设置为静态方法
  • static代码块:静态代码块会在jvm加载类的时候首先被执行,且从始至终只会被执行一次。即执行顺序优先于main方法
    • 非静态代码块在new对象时都会被执行一次
  • static变量/方法:方便在没有创建对象的情况下进行调用

    访问权限

  • java所有变量和方法和类默认都是default。default不能自己定义,只能默认为省略状态。

    • 没有修饰的成员变量和方法称为友好变量和友好方法。即default。
  • 访问权限不能修饰局部变量,局部变量访问范围只在方法体中
  • 访问权限有四个 private<default<protected<public 控制级别由小到大
    • private:只能被当前类的其他成员访问
    • default:只能被同一包的其他类访问
    • protected:作用于继承关系。可以被子类访问,以及子类的子类
    • public:随便访问,其他包都可以来访问

image.png

final

final意思为最终的,可以修饰类,方法,变量

  1. 修饰类:final不能被继承
  2. 修饰方法:final方法不能被重写(最终的方法)
  3. 修饰变量:final变量不能被改变值,即最终的变量 这种用法一般用于创建常量

private只能修饰内部类。private属性和方法不能被继承(无法访问到也就不能继承)

this

常常用于封装
用法:

  1. 调用被局部变量覆盖的成员变量
  2. 调用所在类的成员方法,这种用法可以省略this
  3. 调用构造方法(构造函数里调用本类的其他重载构造函数) 此种用法时this语句得是构造方法里第一条语句。但是只允许调用一次构造函数
  4. 作为一个对象使用
  • 注意作用域问题和静态实例调用问题,这些使得this只能用于成员方法中
  • this本质是当前对象的引用(当前对象即调用this方法的那个对象),所以1,2,3相当于当前对象调用成员变量,成员方法……

public boolean equals(Object obj) { return (this == obj); }
对象a.equals(对象b) 即返回a==obj

super

  • super用于调用父类的成员(成员变量和方法)。常常用于调用被覆盖的父类成员变量或成员方法
  • 子类构造方法在实例化对象时会隐式调用父类无参构造方法(**即super();**) 如果父类定义了有参构造,则子类隐式调用无参会失败。这时要么子类手动写有参super(),要么父类增加无参构造

    super()   //访问父类无参构造方法
    super(?,?,...)     //访问父类有参构造方法
    super.?/?()   //访问父类属性/方法
    

    return

  • return 语句返回值后会退出整个方法。所以其他语句要写return之前;

  • return语句返回一个表达式或者变量

    构造方法

    构造方法的名与它的类名相同,并且不返回结果,也不写上 void 关键字构造方法的作用是创建类的对象并给对象初始化构造方法是公共方法但程序不能显式调用构造方法。(即只能通过new告诉编译器调用构造方法)程序运行时,当有对象要创建时,由系统自动调用构造方法。

  • 如果类的声明没有定义构造方法,系统就增补一个没有参数的默认构造方法。

  • 如果写了一个有参构造,那么系统就不会再增补无参构造。自己记得根据情况写一个无参构造

构造方法可以重载不能重写

对象

类被声明后,就可用类创建对象,被创建的对象称为类的实例。程序使用对象需依次经历4个步骤:声明对象、创
建对象、使用对象和撤销对象
创建对象即对对象进行初始化,即分配内存,又叫实例化内存

抽象类&接口

抽象类

  • 抽象类只声明一种模板,不应该有具体实现代码的类。只有它的子类才能是有实际意义的类,接口也一样
    • 但是可以创建抽象类的引用指向子类对象
  • **abstract class T{}**

抽象类中一定得有抽象方法,有抽象方法的类一定是抽象类(抽象方法只能出现在抽象类),也可以有普通方法
抽象类不能有实例

一个类继承抽象类必须实现抽象类的所有抽象方法

抽象方法

不能有具体实现语句,即只能声明方法不能实现方法
**abstract void dd();**这便是抽象方法

接口

  • 接口是纯粹的抽象类,只能有抽象方法不能有普通方法
  • interface T{}
  • 接口可以继承其他接口,但是不能被类继承
  • 接口名通常以 able ible 结尾,意指能做什么
  • 接口 的所有变量都默认为 **public final static **属性;所有的方法都默认为是 **public abstract **属性
    • 因为是final,因此不能被修改且必须有初始值
  • 一个类实现接口必须实现所有抽象方法(相当于实现所有接口方法)
  • 在 Java 8 中,允许在接口中增加静态方法和默认方法
    • 接口中默认方法使用default,可以有方法体。实现类不强制重写父接口的default
    • 实现类无法通过super访问父接口的defult方法

在实现接口的方法时,方法的名字、返回值类型、参数个数及类型必须与接口中的定义的方法完全一致

重载Overload

  • 即方法重载。方法重载只需要方法名相同,参数列表和返回值,修饰符可以不同(因此static,final,普通都可以进行重载)

参数名不能作为编译器区分重载方法的依据。因为jvm是根据方法签名来区分方法的

  • 如果调用重载方法,不存在相同参数类型的重载方法,则实参自动类型转换升一级再寻找方法进行调用
    • 如果传入参数大于所有重载方法的参数类型,就要强制转换类型,否则报错
  • 主函数main也是可以重载的,但是参数非String数组的main只能人为调用

    class PrimitiveOverloading {
    
      void f2(float x) {
          System.out.println("f2(float)");
      }
      void f2(double x) {
          System.out.println("f3(double)");
      }
    }
    public class demo {
      public static void main(String[] args) {
          PrimitiveOverloading p = new PrimitiveOverloading();
          p.f2(5);
      }
    }
    //输出:f2(float)
    

继承

子类又叫派生类。父类又叫超类。
java可以多层继承,但是不可以多重继承。但是可以实现多个接口,也就相当于多重继承
**java.lang.Object**是所有类的超类
子类只会继承父类非私有的成员作为自己的成员

多层继承

多层继承中构造方法的调用顺序与类的调用顺序一致:从最高层次类调用至最低层次类
class T3 extends T2;T3(){sout T3}
class T2 extends T1;T2(){sout T2}
class T1;//最高级别类 T1(){sout T1}
主函数创建T3对象,会先执行T3()
子类构造函数在实例化对象时默认调用父类无参构造方法
然后T3()调用T2() T2()调用T1()
最后输出T1 T2 T3

重写Override

  • 子类如果创建一个与继承方法同名的方法。该方法会覆盖继承的父类方法。
  • 重写父类与子类的方法必须方法签名一致,返回值也相同
    • jdk1.5后允许重写的子类方法返回值与父类不同,但是返回值类型必须是父类返回类型的子类,如父类返回值类型为Object,子类返回值类型为String
  • 子类重写父类方法时,不能使用比父类中被重写方法更严格的访问权限
  • final方法不能被重写,static也不能。因为final是最终的不可修改,而sttatic是独属于一个类的,像构造函数一样,因此也不能被重写

    封装

  • 成员变量设置为私有,通过公有方法获取获取成员变量。这样加强了数据的安全性

    多态Override

    多态即使得同一个行为具有多个不同表现形式或形态的能力,具体的体现即:Override重写
    Father f=new Son(); 父类对象的引用指向子类对象,即用起来本质还是子类对象,但是理解起来理解为父类就行 这里的父类包括父接口,父抽象类等等
    可以详见LinkedList之上下转型应用

  • 多态子类方法会覆盖父类同名方法。但是子类的属性不会覆盖父类的同名属性。不过子类方法获取同名属性还是获取的子类的属性

    public class Main2 extends Main {
      int a=2;
      public int getA(){
          return a;
      }
      public static void main(String[] args) {
          Main main=new Main2();
          System.out.println(main.a);  //1
          System.out.println(main.getA());  //2
      }
    }
    class Main {
      int a=1;
      int getA(){
          return a;
      }
    }
    

多态对象只能调用多态方法(即重写方法)

上/下转型

即父子类对象的类型转换
例如Animals类和Dog类,animal和dog2个对象
上转型(即向上转):即引用是父类类型 上转型可能会丢失子类的一些方法
Animals animals2 =(Animals)dog; //上转型可以省略强转
Animals animals3=new Dog(); 上转型即多态//省略了(Animal) 这种方法不能用于下转行,下转型只能转变经历了上转型的对象,且不能省略强转
下转型(即向下转)
Dog dog2=(Dog)animals2;

  • 上下转型可以这样理解:子类继承父类,又可以增加自己的东西,所以父类相当于子类的一部分,所以子类能直接转为父类,因为父类需要的东西子类都有。但是父类转子类,不一定有子类的东西,所以转不了。(不考虑父类有私有的情况)
  • 如果一个类实现与继承的类不止一个(实现+继承的数量不止一个),那么如果它的对象是A接口/类的多态对象,那么它要调用B类的接口或者对象,就需要下转型,这样才能既调用A方法也调用B方法。因为多态对象只能调用父接口或者父类所拥有的方法(父接口与父类也实现。继承了类/接口,也是一个道理)

    public class Xiaoming implements  Ba,Person{
      public String ret(){
          return "我是小明";
      }
      @Override
      public String ret2() {
          return "Ws";
      }
      public static void main(String[] args) {
          Person person=new Xiaoming();
          System.out.println( ((Xiaoming) person).ret2());
      }
    }
    

    联编(又称为绑定)

    编译时暂不绑定调用哪个方法,必须在运行时才绑定调用方法的技术称为动态联编(重写)。
    而重载是静态联编,编译时根据参数列表进行绑定
    联编是将发送给对象的消息与含执行该消息方法的对象连接起来。

    代码块

  • 除了普通代码块是伴随着方法写。其他代码块都是直接写在类里

  • 静态代码块 > 构造代码块 > 构造函数

  • 普通代码块:即方法定义时,方法体的{}
  • 静态代码块:静态代码块会在jvm加载类的时候首先被执行,且从始至终只会被执行一次。即执行顺序优先于main方法
  • 同步代码块: 使用 synchronized 关键字修饰,并使用“{}”括起来的代码片段,它表示同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。
  • 构造代码块:即没有任何修饰的{}块。构造代码块和构造函数一样同样是在生成一个对象时被调用。执行时会把构造代码块里的语句插入到所有构造函数的最前端

    public class Test {
      /**
       * 构造代码
       */
      {
          System.out.println("执行构造代码块...");
      }
    
      /**
       * 无参构造函数
       */
      public Test(){
          System.out.println("执行无参构造函数...");
      }
    
      /**
       * 有参构造函数
       * @param id  id
       */
      public Test(String id){
          System.out.println("执行有参构造函数...");
      }
    }
    插入后相当于
    public Test(){
          System.out.println("执行构造代码块...");
          System.out.println("执行无参构造函数...");
      }
    
      /**
       * 有参构造函数
       * @param id  id
       */
      public Test(String id){
          System.out.println("执行构造代码块...");
          System.out.println("执行有参构造函数...");
      }
    

    对象拷贝

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象,不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝的属性和原对象的属性共用同一个内部对象。

  • 深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
    • 深拷贝就是拷贝完对象后,再让子属性再调用clone获得新对象,然后set进父对象里
  • 拷贝方法是clone(),位于Object类,实现了对象中各个属性的复制,但它的可见范围是protected的(最大到保护级别,不能访问public
    • 实体类要使用拷贝,需要实现Cloneable接口,这是一个标记接口,自身没有方法。并覆盖clone()方法,将可见性提升为public。
  • 引用拷贝就是直接完全相等,无论是该对象还是对象的属性。如直接赋值对象Person person1Copy=person1;
    • image.png ```java public class Address implements Cloneable{ private String name; // 省略构造函数、Getter&Setter方法 @Override public Address clone() { try {
         return (Address) super.clone();
      
      } catch (CloneNotSupportedException e) {
         throw new AssertionError();
      
      } } }

public class Person implements Cloneable { private Address address; // 省略构造函数、Getter&Setter方法 @Override public Person clone() { try { Person person = (Person) super.clone(); return person; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } }

//——————测试—————————————— Person person1 = new Person(new Address(“武汉”)); Person person1Copy = person1.clone(); //拷贝person1 // true,内部的引用类型的属性与原对象的内部的该属性是相同 System.out.println(person1.getAddress() == person1Copy.getAddress());

```java
@Override
public Person clone() {
    try {
        Person person = (Person) super.clone();
        person.setAddress(person.getAddress().clone());
        return person;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
//------------测试-------------------------
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
// false
System.out.println(person1.getAddress() == person1Copy.getAddress());

hashCode()

  • hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。位于Object,java里的hashCode用来将对象的内存地址转换为整数之后返回,可用于判断2个对象是否相等
  • hashCode在一些容器(比如 HashMap、HashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高。
    • 如HashSet添加元素,首先通过hashCode判断。如果存在同样的 hashCode的对象,再继续使用 equals() 来判断是否真的相同。比直接用equals()更快
    • 即同样的hashCode的对象不一定相等,但是不相同的hashCode一定不同
  • 重写 equals() 时必须也重写 hashCode() 方法
    • hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。