面向对象(下)

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-06-25

static

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。有时候,我们希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有同一个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。

static(静态的)可以用来修饰属性、方法、代码块、内部类。

static修饰属性

static修饰属性被称为静态变量,没有static修饰的属性就是实例变量,当我们创建了类的多个对象,则每个对象中都独立都拥有一套类中的非静态变量。当修改实例变量不会导致其他对象中同样的属性值的修改。

1、类的静态变量被所有创建出来的对象共享,当通过某一个对象修改静态变量时,当其他对象调用时就是被修改过的。

import org.junit.Test;

public class StaticTest {

  @Test
  public void testA() {
    Chinese c1 = new Chinese();
    c1.name = "yaoming";

    Chinese c2 = new Chinese();
    c1.name = "liuxiang";

    c1.nation = "china";
    System.out.println(c2.nation); // "china" 会发现修改的c1,却影响到了c2
  }
}

class Chinese {
  String name;
  static String nation;
}

2、静态变量随着类的加载而加载,归属于类,静态变量的加载要早于对象的创建。由于类只加载一次,则静态变量在内存中也只会存在一份,存在方法区的静态域中。

public class StaticTest {

  @Test
  public void testB() {
    // 无需创建对象就可以赋值和访问
    Chinese.nation = "中国";
    System.out.println(Chinese.nation); // 中国

    Chinese c1 = new Chinese();
    System.out.println(c1.nation); // 中国
  }
}

static修饰方法

1、也是随着类的加载而加载,可以通过 “类.静态方法” 的方式进行调用。

2、类和对象都可以调用类的静态方法,需要注意的是类不能调用非静态方法和非静态变量。

3、静态方法中,只能调用静态的方法和属性,因为它们的生命周期是一致的,不能调用非静态的方法和属性,也不能使用thissuper,静态方法加载时它们还没加载 ;而非静态方法,即可以调用非静态方法和属性,也可以调用静态方法和属性。

4、开发中,如何确定一个属性是否要声明为static的? 属性可以被多个对象所共享,不会随着对象的不同而不同的;类中的常量也常常声明为static,因为也可以被多个对象共享,常量只是代表着一旦声明不能修改;

5、开发中,如何确定一个方法是否要声明为static的? 操作静态属性的方法,通常设置为static的;工具类中的方法,习惯声明为static的,因为没有必要造对象。

单例设计模式

所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法,如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

饿汉式单例模式:

class Bank {
  // 声明为私有的,避免使用new Bank()来造对象
  private Bank() {};

  // 内部创建类的对象
  // 2、因为获取单例对象方法是静态的,所以保存的单例对象也是静态属性,
  // 静态方法只能访问静态属性和方法
  private static Bank instance = new Bank();

  // 提供公共静态方法,返回类的对象
  // 1、前面避免了使用new Bank()来造对象,所以要求提供获取实例的方法要是静态的
  // 才能在不创造对象的前提下通过类来调用该方法获取单例对象
  public static Bank getInstance() {
    return instance;
  }
}

懒汉式单例模式:

class Order {
  // 声明为私有的,避免使用new关键字来造对象
  private Order() {};

  // 内部创建类的对象
  private static Order instance = null;

  // 提供公共静态方法,返回类的对象
  public static Order getInstance() {
    if (instance == null) {
      // 不安全表现在,判断的时候,并发执行,第一个new Order还没有创建成功
      // 第二个getInstance也进来,此时instance还是null,那么就会又创建一个
      // 最终晚创建的覆盖早创建的
      instance = new Order();
    }
    return instance;
  }
}

饿汉式:坏处是对象一开始就被加载创建了,可能不会被使用,在对象存在比较长的时间,也会造成对象加载时间变长;好处是饿汉式是线程安全的;

懒汉式:好处是延迟对象的创建,需要的时候才会被创建一次。需要注意的是上述的懒汉式写法是不安全的(安全的涉及到多线程,在后面提及);

单例模式的优点是只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生依赖对象,则可以通过在应用启动时直接产生一个单例对象, 然后永久驻留内幕才能的方式来解决。

main方法

1、main方法作为程序的入口

2、main也是一个普通的静态方法。

3、main方法的形参可以作为我们与控制台交互的方式,之前使用Scanner。 java demo.java arg1 arg2

代码块

1、代码块的作用:用来初始化类、对象。

2、代码块如果有修饰的话,只能使用static。

3、分类:静态代码块和非静态代码块。

public class BlockTest {
  public static void main(String[] args) {
    String desc = Person.desc; // 输出"静态的代码块",因为静态的代码块随着类的加载而执行,只会执行一次。

    Person p1 = new Person(); // 输出"非静态代码块",随着对象的创建而执行,每造一个对象执行一次。
    System.out.println(p1.age); // 1
  }
}

class Person {
  String name;
  int age;
  static String desc;

  public Person() {};

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  // 非静态代码块 可以有输出语句 可以定义多个 按照声明的先后顺序执行
  {
    System.out.println("非静态的代码块");
    age = 1; // 非静态代码块可以在创建对象时,对对象的属性等进行初始化
    say();
    show(); // 在非静态的代码块中,静态方法和非静态方法都可以调用。
  }

  // 静态代码块 可以有输出语句,可以定义多个静态代码块,按照声明的先后顺序执行
  static {
    System.out.println("静态的代码块");
    desc = "i am person"; // 静态代码块可以对静态的属性进行初始化
    show(); // 只能调用静态方法
  }

  public static void show() {
    System.out.println("show");
  }

  public void say() {
    System.out.println("say");
  }
}

执行顺序:

  • 首先静态代码块随着类的加载而执行,非静态代码块随着对象的创建而执行,构造函数中的语句最后执行。

  • 在new一个子对象时,先执行父类的静态代码块,然后执行子类的静态代码块,再执行父类的非静态代码块、父类的构造函数,最后执行子类的非静态代码块、子类的构造函数。

  • 所有的静态代码块都只会执行一次

总结:由父及子,静态先行。

对属性可以赋值的位置

  • 1、默认初始化
  • 2、显示初始化
  • 3、构造器中初始化
  • 4、有了对象之后,可以通过对象.属性的方式进行赋值
  • 5、在代码块中赋值

顺序是:1、2、5、3、4,需要注意的是,2、5哪个声明在前面哪个就先执行,后执行的赋值会覆盖先执行进行的赋值。

final

1、final可以用来修饰的结构:类、方法、变量

2、final用来修饰一个类,此类不能被其他类所继承。比如String类、System类、StringBuffer类。

3、final用来修饰一个方法,表明此方法不可以被重写。比如Object类中的getClass()

4、final用来修饰一个变量,表明此变量就称为一个常量,不能被重新赋值改变0。

  • final修饰属性:可以考虑赋值的位置有,显示初始化、代码块中初始化、构造器中初始化(如果有多个构造器,需要在每个构造器中都去赋值,因为会报错final修饰的变量没有初始值,因为有可能不会被执行到,要求在类加载完毕、对象加载完毕之后final修饰的变量必须得被初始化,之后也不能再被修改)。

  • final修饰局部变量:当使用final修饰形参时,表明此形参是一个常量,当我们调用此方法给常量形参赋值一个实参,一旦赋值以后,就只能在方法内只能使用,不能更改。

5、staic final 用来修饰属性即为全局常量;用来修饰一个方法即说明该方法不能被重写,并只能通过类调用;

抽象类和抽象方法

1、抽象类

  • 抽象类不能实例化

  • 抽象类中也一定要有构造器,便于子类实例化时调用。涉及子类对象实例化的全过程。

  • 开发中,都会提供抽象类的子类,让子类的对象实例化,完成相关的操作。

2、抽象方法

  • 抽象方法只有方法的声明,没有方法体。

  • 包含抽象方法的类,一定要是一个抽象类。反之,抽象类中可以没有抽象方法。

  • 子类继承抽象类的时候,要重写所有父类中所有的抽象方法之后方可实例化,若没有重写,则需要声明子类也为抽象类。

3、注意点

  • 不能修饰属性、构造器等结构

  • abstract不能修饰私有方法,静态方法、finnal的方法、final的类

4、抽象类的匿名子类

public class AbstractTest {

  public static void main(String[] args) {
    // 创建了一个匿名子类的对象p
    Person P = new Person() { // 注意 这不是一个Person类,而是Person类的匿名子类
      @Override
      public void work() {}

      @Override
      public void eat() {}
    };
  }
}

abstract class Person {
  String name;

  public abstract void work();

  public abstract void eat();
}

多态的应用:模板方法设计模式(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题:

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

示例代码如下:

public class TemplateTest {
  public static void main(String[] args) {
    Template t = new SubTemplate();
    t.spendTime();
  }
}

abstract class Template {
  public void spendTime() { // 计算某段代码执行花费的时间的流程是固定的
    long start = System.currentTimeMillis();

    this.code(); // 不确定的部分,易变的部分

    long end = System.currentTimeMillis();

    System.out.println("花费的时间为:" + (end - start));
  }

  public abstract void code();
}

class SubTemplate extends Template { // 求质数

  @Override
  public void code() {
    for (int i = 2; i < 1000; i++) {
      boolean isFlag = true;
      for (int j = 2; j <= Math.sqrt(i); j++) {
        if ( i % j == 0) {
          isFlag = false;
          break;
        }
      }
      if (isFlag) {
        System.out.println(i);
      }
    }
  }
}

接口

Java是不支持多重继承的,可以使用接口得到多重继承的效果。

接口就是规范,定义的是一组规则,

/**
 * interface
 * 如何定义接口?
 * JDK7.1以前:只能定义全局常量(public static final xx)和抽象方法(public abstract xx)
 * JDK8:除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法
 */

interface Flyable {
  // 全局常量
  public static final int MAX_SPEED = 7900; // 第一宇宙速度
  int MIN_SPEED = 1; // public static final 可以省略不写,但是实际是存在的

  // 抽象方法
  public abstract void fly();
  void stop(); // public abstract 也可以省略,但也实际存在

  // 接口中不能定义构造器,不能实例化
}

// 开发中,接口通过让类去实现(implements)的方式来使用
// 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化,反之此实现类依然为一个抽象类
class Plane implements Flyable {

  @Override
  public void fly() {
    System.out.println("飞机起飞");
  }

  @Override
  public void stop() {
    System.out.println("飞机降落");
  }
}

// Java类可以实现多个接口,弥补了Java单继承性的局限性
interface Attackable { // 攻击性
  void attack();
}

// 战斗机
class Fighter implements Flyable,Attackable {

  @Override
  public void fly() {
    System.out.println("战斗机起飞");
  }

  @Override
  public void stop() {
    System.out.println("战斗机降落");
  }

  @Override
  public void attack() {
    System.out.println("发射子弹");
  }
}

// 接口的继承,接口可以继承另一个接口,而且可以继承多个
interface A {
  void methodA();
};

interface B {
  void methodB();
};

interface C extends A,B {
  void methodC();
};

接口的使用

public class USBTest {
  public static void main(String[] args) {
    Computer c = new Computer();
    Disk d = new Disk();
    c.transformData(d);
  }
}

interface USB {
  void start();
  void stop();
}

class Disk implements USB {

  @Override
  public void start() {
    System.out.println("插入硬盘");
  }

  @Override
  public void stop() {
    System.out.println("拔出硬盘");
  }
}

class Computer {
  public void transformData(USB usb) { // 接口的使用上也满足多态性,USB usb = new Disk();
    usb.start();
    System.out.println("开始传输");
    usb.stop();
  }
}

代理模式

public class NetWorkTest {
  public static void main(String[] args) {
    Server s = new Server();
    ProxyServer p = new ProxyServer(s);
    p.browse(); // 本来应该s.browse() 但是却交给了proxy
    // 应用场景:
    // 安全代理:屏蔽对真实角色的直接访问
    // 远程代理:通过代理类处理远程方法调用
    // 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
    // 分类:
    // 静态代理 (静态定义代理类)
    // 动态代理 (动态生产代理类)

    // 意图:为其他对象提供一种代理以控制对这个对象的访问。
    // 主要解决:在直接访问对象时带来的问题,
    //    比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,
    //    或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,
    //    我们可以在访问此对象时加上一个对此对象的访问层。
    // 何时使用:想在访问一个类时做一些控制。
  }
}

interface NetWork {
  void browse();
}

// 被代理类
class Server implements NetWork {
  @Override
  public void browse() {
    System.out.println("真实的服务器访问网络");
  }
}

// 代理类
class ProxyServer implements NetWork {

  private NetWork work;

  public ProxyServer(NetWork work) {
    this.work = work;
  }

  public void check() {
    System.out.println("联网之前检查工作");
  }

  @Override
  public void browse() {
    check();
    work.browse();
  }
}

接口面试题

interface A {
  int x = 0;
}

class B {
  int x = 1;
}

class C extends B implements A {
  public void pX() {
    // System.out.println(x);  // error x是不明确的

    // 如果想调父类的x
    System.out.println(super.x);

    // 如果想调接口的x 接口中的x是一个全局常量
    System.out.println(A.x);
  }

  public static void main(String[] args) {
    new C().pX();
  }
}

内部类

Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类。

内部类的分类:成员内部类 和 局部内部类(方法内、代码块内、构造器内)

class Person {

  // 静态成员内部类
  static class Leg {

  }

  // 非静态成员内部类
  class Foot {

  }

  public void method() {
    // 局部内部类
    class AA {
      public void eat() {
        System.out.println('eat~')
      }
    }

    class A {
      public void walk() {
        eat(); // 这里调用的是AA的eat方法 前面被省略了 完整的应该是AA.this.eat()
      }
    }
  }

  {
    // 局部内部类
    class BB {}
  }

  public Person() {
    // 局部内部类
    class CC {};
  }
}

成员内部类

1、作为外部类的成员

  • 调用外部类的结构(非静态的)
  • 可以被static修饰
  • 可以被4中不同的权限修饰

2、作为一个类

  • 类内可以定义属性、方法、构造器等。
  • 可以被final修饰,表示此类不能被继承,不使用final修饰时可以被继承。
  • 可以被abstract修饰

3、如何实例化成员内部类的对象

public class InnerClassTest {
  public static void main(String[] args) {
    // 创建静态的成员内部类的对象
    Person.Dog d = new Person.Dog();
    d.show();

    // 创建非静态的成员内部类的对象
    Person p = new Person();
    Person.Bird bird = p.new Bird(); // 使用逻辑是,有了对象,再通过 对象. 的方式去调用内部结构
    bird.sing();
  }
}

class Person {
  String name;

  static class Dog {
    public void show() {
      System.out.println("is a dog");
    }
  }

  class Bird {
    public void sing() {
      System.out.println("sing ~ ");
    }
  }
}

4、如何在成员内部类中区分调用外部类的结构

public class InnerClassTest {
  public static void main(String[] args) {
    // 创建非静态的成员内部类的对象
    Person p = new Person();
    Person.Bird bird = p.new Bird(); // 使用逻辑是,有了对象,再通过 对象. 的方式去调用内部结构
    bird.display("b");
  }
}

class Person {
  String name = "person";

  class Bird {
    String name = "bird";
    int age = 1;

    public void display(String name) {
      System.out.println(name); // b
      System.out.println(this.name); // bird
      System.out.println(Person.this.name); // person
      System.out.println(age); // 没有重名的话可以直接调用
    }
  }
}

局部内部类

public class InnerClassTest2 {

  public void method() {

    final int num = 10;

    class AA {
      public void show() {
        // 在局部内部类的方法中,如果要调用局部所声明的变量的话,则要求此局部变量声明为final。
        System.out.println(num);
      }
    }

  }

  // 返回一个实现了Comparable接口的类的对象
  public Comparable getComparable() { // 局部内部类的使用

    // 创建一个实现了Comparable接口的类
    class MyComparable implements Comparable {
      public void compareTo(Object o) {
        return 0;
      }
    }

    return new MyComparable();
  }
}