u02-类的进阶

135 阅读14分钟

1. 初始化块

概念: JVM会在创建一个类和使用这个类中间的这一段时间里,执行初始化块(包括构造方法、动态块和静态块)里面的内容。

1.1 构造方法

概念: 构造方法,也叫构造器,是一种特殊的方法,当我们实例化(new)一个类的时候,就是在调用这个类的构造方法的过程,每new一次,就会调用一次。

  • 特点:
    • 构造方法的名字必须和类名一致。
    • 构造方法没有返回值也不写 voidreturn
    • 构造方法不能被 static 等修饰。
  • 分类:
    • 隐式无参构造方法:这种构造方法是一个类的默认构造方法,new一个类的时候默认调用这个方法。
    • 显式无参构造方法:自己在类中编写一个没有参数的构造方法,会覆盖掉隐式无参构造方法。
    • 显式有参构造方法:自己在类中编写一个有参数的构造方法这种构造器可以写多个,它们的共存要求是参数列表不相同,new一个类的时候可以自动根据传进来的参数列表对应找到那个有参构造方法。
  • 构造方法只能通过new来调用,不能使用方法的调用模式来进行调用。

源码: /javase-oop/

  • src: c.y.klass.ConstructorTest

/**
 * @author yap
 */
public class ConstructorTest {
    @Test
    public void constructor() {
        new ConstructorDemo();
        new ConstructorDemo("赵四");
        new ConstructorDemo(15, 16);
    }
}

class ConstructorDemo {
    public ConstructorDemo() {
        System.out.println("ConstructorDemo 的无参构造...");
    }

    public ConstructorDemo(String str) {
        System.out.println("ConstructorDemo 的有参构造..." + str);
    }

    public ConstructorDemo(int numA, int numB) {
        System.out.println("ConstructorDemo 的有参构造..." + (numA + numB));
    }
}

1.2 动态块

概念: 动态块的格式就是 {...},和构造方法一样,每new一次就会执行一次,不一样的是动态块必须全部执行,无法指定执行哪一个,而且执行顺序在构造器之前。

源码: /javase-oop/

  • src: c.y.klass.DynamicBlockTest
/**
 * @author yap
 */
public class DynamicBlockTest {
    @Test
    public void dynamicBlock() {
        new DynamicBlockDemo();
        new DynamicBlockDemo();
        new DynamicBlockDemo();
    }
}

class DynamicBlockDemo {
    public DynamicBlockDemo() {
        System.out.println("DynamicBlockDemo的构造...");
    }

    {
        System.out.println("DynamicBlockDemo的动态块01...");
    }

    {
        System.out.println("DynamicBlockDemo的动态块02...");
    }
}

1.3 静态块

概念:

  • 静态块的格式就是 static{...},常用于一些初始化数据的工作。
  • 静态块和动态块不一样,它就只执行一次,而且执行顺序在动态块之前。
  • 静态块和静态方法一样,里面只能访问到静态的属性和方法。
  • 静态块必须全部执行,无法指定执行哪一个。

源码: /javase-oop/

  • src: c.y.klass.StaticBlockTest
/**
 * @author yap
 */
public class StaticBlockTest {
    @Test
    public void dynamicBlock() {
        new StaticBlockDemo();
        new StaticBlockDemo();
        new StaticBlockDemo();
    }
}

class StaticBlockDemo {
    public StaticBlockDemo() {
        System.out.println("StaticBlockDemo的构造...");
    }

    {
        System.out.println("StaticBlockDemo的动态块01...");
    }

    {
        System.out.println("StaticBlockDemo的动态块02...");
    }

    static {
        System.out.println("StaticBlockDemo的静态块01...");
    }

    static {
        System.out.println("StaticBlockDemo的静态块02...");
    }
}

2. this关键字

概念: java中提供this关键字,用法有两种:

  • 当类中某个非静态方法的参数名和类的某个成员变量名相同时,为了避免参数的作用范围覆盖了成员变量的作用范围,必须明确的使用this关键字来指定哪个变量是当前类的成员属性。
    • this. 可以翻译成"当前实例的",如 this.name 就是"当前类里的name属性"。
  • 如果某个构造方法的第一条语句具有形式 this(...),那么这个构造方法将调用本类中的其他构造方法。

源码: /javase-oop/

  • src: c.y.klass.ThisTest
/**
 * @author yap
 */
public class ThisTest {
    @Test
    public void thisConstructor() {
        new ThisDemo();
        new ThisDemo("刘能");
    }
}

class ThisDemo {
    private String name;

    public ThisDemo() {
        this("赵四");
    }

    public void setName(String name) {
        this.name = name;
    }

    public ThisDemo(String str) {
        System.out.println("姓名为:" + str);
    }
}

3. 单例模式

概念: 单例模式(单件模式)要求有且只有一个实例,其对外提供一个可以获取该实例的方法。

3.1 饿汉单例模式

概念: 饿汉单例模式下,无论是否调用这个 getInstance(),都将会new一个Singleton的实例出来,就像一个饿汉一样,无论是否吃馒头,都先做一个馒头出来备用,这无疑有些浪费。

  • 将构造器私有化。
  • 在类内部自己new一个实例备用。
  • 对外提供一个静态方法,可以将我们自己new出来的实例返回。

源码: /javase-oop/

  • src: c.y.singleton.HungrySingletonTest

/**
 * @author yap
 */
public class HungrySingletonTest {
    @Test
    public void hungrySingleton() {
        HungrySingleton instanceA = HungrySingleton.getInstance();
        HungrySingleton instanceB = HungrySingleton.getInstance();
        System.out.println(instanceA == instanceB);
    }
}

class HungrySingleton {

    private final static HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

3.2 饱汉单例模式

概念: 饱汉单例模式下,可以解决资源浪费的问题。

  • 将构造器私有化。
  • 对外提供一个静态方法,只有当这个方法被调用的时候才new自己的实例并返回。

源码: /javase-oop/

  • src: c.y.singleton.FullSingletonTest
/**
 * @author yap
 */
public class FullSingletonTest {
    @Test
    public void fullSingleton() {
        FullSingleton instanceA = FullSingleton.getInstance();
        FullSingleton instanceB = FullSingleton.getInstance();
        System.out.println(instanceA == instanceB);
    }
}

class FullSingleton {

    private static FullSingleton instance;

    private FullSingleton() {
    }

    public static FullSingleton getInstance() {
        if (instance == null) {
            instance = new FullSingleton();
        }
        return instance;
    }
}

4. POJO

概念:

  • POJO(Plain Ordinary Java Object)简单的Java对象,POJO中有一些属性及其getter/setter方法的类,没有业务逻辑,当然如果你有一个简单的运算属性也是可以的,但不允许有复杂的业务方法。
  • 当一个POJO可序列化,有一个无参的构造函数,使用getter和setter方法来访问属性时,它就是一个JavaBean。
  • POJO分类:
    • PO:Persistant Object, 用在持久层,可以理解为POJO经过持久化后的对象。
    • DTO:Data Transfer Object,据传输对象,一般用于向数据层外围提供仅需的数据,如查询一个表有50个字段,界面或服务只需要用到其中的10个字段,DTO就包装出去的对象,DTO可用于隐藏数据层字段定义,也可以提高系统性能,减少不必要字段的传输损耗。
    • VO:View Object 用在视图层,一般用于web层向view层封装并提供需要展现的数据
    • BO:Business Object 用在service层,当业务比较复杂,用到比较多的业务对象时,可用BO类组合封装所有的对象一并传递,现在基本不用。
  • 这些定义在实际使用设计中并不会全部用到,根据不同设计架构定义不同的类对象,形态大致如此,可根据自己项目进行调整。

POJO和DOMAIN也是同一个概念。

5. 继承

概念:

  • 所谓继承就是将父亲的非private成员拿过来直接用,但是不包括构造方法,同时也可以拥有自己的成员。
  • 继承是一种 is a 的关系,如"鸟is a动物","赵四is a人"等。
  • 继承在java中是单方向的(单继承),且final修饰的类不能被继承。
  • 继承具有层次性和传递性。
  • 继承是为了提高代码可重用性,使程序变得简单。
  • 类的祖先是 java.lang.Object 类,如果一个类没有声明指定父类,就默认继承Object类,Object类中有一些常用的方法。
    • toString(): 返回代表该实例的字符串形式,格式为类名@内存地址十六进制串。
    • equals():判断两个实例是否指向同一内存区域。
    • getClass():得到实例对应的模板的类全名。
    • hashCode():得到对象的hash码,hashCode被设计用来提高性能,如果两个实例变量内存地址相等,那么它们一定有相同的hash值,但是两个实例变量有相同的hash值,但它们未必相等。

5.1 继承的实现

概念: java中的继承通过 extends 关键字来完成继承关系。

源码: /javase-oop/

  • src: c.y.klass.ExtendTest
/**
 * @author yap
 */
public class ExtendTest {
    @Test
    public void employeeAndManager() {
        Manager manager = new Manager();
        double salary = manager.getSalary();
        double bonus = manager.getBonus();
        System.out.println("月总薪水:" + (salary + bonus));
    }
}

class Employee implements Serializable {
    private double salary = 2000.0;

    public double getSalary() {
        return this.salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

class Manager extends Employee implements Serializable {
    private double bonus = 200.0;

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }
}

5.2 super关键字

概念: java中提供super关键字,用法和this类似,也有两种:

  • 在子类调用父类的普通方法时,可以使用 super.方法()
    • 使用super调用后,仅会在父类中寻找方法,若没找到直接报错。
    • 使用super调用后,会将子类的this传递到父类方法中,即父类方法中的this不指向父类,而是指向子类。
  • 想在子类调用父类的构造方法,必须在子类构造方法中的第一行使用 super()
  • 调用子类构造的时候,一定会先去调用父类的构造,然后再调用子类的构造。

源码: /javase-oop/

  • src: c.y.klass.SuperTest
/**
 * @author yap
 */
public class SuperTest {
    @Test
    public void animalAndBirdAndDog() {
        System.out.println(new Bird().getName());
        new Dog().methodA();
    }

    @Test
    public void thisTransfer() {
        new Dog().printThisInDog();
    }
}

class Animal {
    private String name;

    Animal() {
    }

    Animal(String name) {
        this.name = name;
    }

    void move() {
        System.out.println("All animals can move!");
    }

    void printThisInAnimal() {
        System.out.println("this of Animal: " + this);
    }

    String getName() {
        return name;
    }
}

class Bird extends Animal {
    Bird() {
        super("jing-wei");
    }
}

class Dog extends Animal {
    Dog() {
        super.move();
    }

    void methodA() {
        super.move();
    }

    void printThisInDog() {
        System.out.println("this in Dog: " + this);
        printThisInAnimal();
    }
}

6. 多态

概念:

  • 一种类型多种形态,多态是一个 like a 的关系:
    • 如同一个电话,拨不同的号码,实现不同的功能。
    • 如同一个插排,插不同的电器,实现不同的功能。
  • 多态在JAVA中的实现就是方法的重载 overload 和方法的重写 override

6.1 方法的重载

概念: 方法的重载可以提高调用方法时的体验,节省代码,要求:

  • 在同一个类中。
  • 方法名相同。
  • 参数列表不同。
  • 其他方法要素,如返回值等均无要求。

源码: /javase-oop/

  • src: c.y.klass.PolymorphismTest.overload()
/**
 * @author yap
 */
public class PolymorphismTest {
    @Test
    public void overload() {
        new Son().method();
        new Son().method(100);
    }
}  
class Parent {
    public void method() {
        System.out.println("method in Parent...");
    }
}

class Son extends Parent {
    @Override
    public void method() {
        System.out.println("method in Son...");
    }

    public void method(int num) {
        System.out.println("method in Son..." + num);
    }
}

6.2 方法的重写

概念: 方法的重写,要求:

  • 在继承关系的两个类中,即父子类中。
  • 方法名相同。
  • 参数列表也相同。
  • 修饰符越写越大。
  • 异常和返回值越写越小。
  • final 或者 private 修饰的方法,不能被重写。
  • 重写的方法建议在方法上添加 @Override 进行标记。

重写原则:一大两小两不变。

源码: /javase-oop/

  • src: c.y.klass.PolymorphismTest.override()
/**
 * @author yap
 */
public class PolymorphismTest {
    @Test
    public void override() {
        new Parent().method();
        new Son().method();
    }
}
class Parent {
    public void method() {
        System.out.println("method in Parent...");
    }
}

class Son extends Parent {
    @Override
    public void method() {
        System.out.println("method in Son...");
    }

    public void method(int num) {
        System.out.println("method in Son..." + num);
    }
}

6.3 引用数据类型转换

概念: 我们可以new一个子类,然后使用父类实例接收,此时,如果 FatherSon 中有相同名字的方法或属性,则会发生动态绑定的现象:

  • 方法看右边,属性看左边。

源码: /javase-oop/

  • src: c.y.klass.TypeConversionTest
/**
 * @author yap
 */
public class TypeConversionTest {
    @Test
    public void typeConversion() {
        // 动态绑定:不运行不知道到底new个什么
        Fu fu = new Zi();

        // 具体调用的是哪个方法,看等号右边(运行时类型)
        fu.method();

        // 具体调用的是哪个属性,看等号左边(编译时类型)
        System.out.println(fu.name);
    }
}

class Fu {
    public String name = "fu";

    public void method() {
        System.out.println("method in Fu...");
    }
}

class Zi extends Fu {
    public String name = "zi";

    @Override
    public void method() {
        System.out.println("method in Zi...");
    }
}

6.4 instanceof实例判断

概念:

  • 对象变量名 instanceof 类名(接口名) 来判断该变量所指向的对象是否属于该类。
  • 只有父类原本就是由子类new出来的时候,才能向下转型,否则会报类转换异常:
    • Dog -> Animal -> (Dog)Dog,转换成功。
    • Dog -> Animal -> (Cat)Cat,转换异常。

源码: /javase-oop/

  • src: c.y.klass.InstanceOfTest
/**
 * @author yap
 */
public class InstanceOfTest {

    @Test
    public void instanceOf() {
        Superman superman = new Superman();
        Hero hero = new Hero();

        System.out.println(superman instanceof Hero);
        System.out.println(superman instanceof Superman);
        System.out.println(hero instanceof Hero);
        System.out.println(hero instanceof Superman);

        hero = new Superman();
        superman = (Superman) new Hero();

        superman = (Superman) hero;
        System.out.println(hero instanceof Hero);
        System.out.println(hero instanceof Superman);
        System.out.println(superman instanceof Hero);
        System.out.println(superman instanceof Superman);
    }
}

class Hero {
}

class Superman extends Hero {
}

7. 抽象类

概念:

  • 抽象类:被 abstract 修饰的类叫做抽象类,它有完整的构造方法,但不能直接调用,而是依靠子类构造方法来间接调用,从而完成实例化过程。
  • 抽象方法:被 abstract 修饰且没有方法体的方法叫抽象方法,不能被 privatefinal 修饰。
  • 抽象类中不一定有抽象方法,但拥有抽象方法的类一定得声明为抽象类。
  • 抽象类命名应建议以Abstract或Base开头。

实际上抽象类就是允许比普通类多一个抽象方法,且不能直接创建本类型而已。

源码: /javase-oop/

  • src: c.y.klass.AbstractClassTest
/**
 * @author yap
 */
public class AbstractClassTest {
    @Test
    public void buildBySubClass() {
        BaseStartDemo baseStartDemo = new MyStartDemo();
        baseStartDemo.methodA();
        baseStartDemo.methodB();
        baseStartDemo.methodC();
    }
}

abstract class BaseStartDemo {
    public void methodA() {
        System.out.println("methodA");
    }

    public abstract void methodB();

    public abstract void methodC();
}

class MyStartDemo extends BaseStartDemo {
    @Override
    public void methodB() {
        System.out.println("子类重写了methodB...");
    }

    @Override
    public void methodC() {
        System.out.println("子类重写了methodC...");
    }
}

8. 接口类

概念:

  • 接口:由 interface 代替 class 定义的类叫做接口,它没有构造方法,需要依靠子孙类通过 implements 来实现(不再叫做继承)。
  • 接口属性:接口中的属性都是默认被 public static final 修饰的。
  • 接口方法:接口中的方法都是默认被 public abstract 修饰的。
  • 接口类的实现类命名应建议在接口同包下新建子包 impl,然后实现类的名称就是接口名称并添加 Impl 后缀,如:
    • 接口名:com.yap.service.UserService
    • 实现类:com.yap.service.impl.UserServiceImpl

父子类关系中,如果创建一个子类,需要先去调用父类的构造器,然而接口没有构造器,所以子普通类或子抽象类不能继承父接口,但是子接口可以继承父接口,因为子类接口也无法被调用构造器。

源码: /javase-oop/

  • src: c.y.klass.InterfaceClassTest
/**
 * @author yap
 */
public class InterfaceClassTest {
    @Test
    public void userServiceBySubClass() {
        UserService userService = new UserServiceImpl();
        System.out.println(UserService.NAME);
        System.out.println(UserService.GENDER);
        userService.methodA();
    }
}

interface UserService {
    String NAME = "赵四";
    String GENDER = "female";

    void methodA();
}

class UserServiceImpl implements UserService {
    @Override
    public void methodA() {
        System.out.println("实现了methodA...");
    }
}

9. 工厂模式

流程:

  1. 设计接口 Car 和接口方法 drive()
  2. 设计奔驰实现类 Benz
  3. 测试:创建一辆奔驰实例并调用 drive()

源码: /javase-oop/

  • src: c.y.factory.start.CarTest
/**
 * @author yap
 */
public class CarTest {
    @Test
    public void factory() {
        new Benz().drive();
    }
}

interface Car {
    /**驾驶方法*/
    void drive();
}

class Benz implements Car {
    @Override
    public void drive() {
        System.out.println("奔驰在跑...");
    }
}

总结: factory() 即是生产者,负责造车又是使用者,负责开车,这不符合工厂模式的理念。

9.1 静态工厂

概念: 静态工厂模式(也称简单工厂)的目的就是想要将生产者和使用者分离,生产者只负责生产,使用者只负责使用。

流程:

  1. 保留接口 Car 和奔驰实现类 Benz
  2. 设计工厂类 CarFactory
  3. 设计工厂方法 getBenz():返回一个奔驰实例。
  4. 测试:从工厂类中获取一辆奔驰实例并调用 drive()

源码: /javase-oop/

  • src: c.y.factory.staticFactory.CarFactoryTest
/**
 * @author yap
 */
public class CarFactoryTest {
    @Test
    public void staticFactory() {
        CarFactory.getBenz().drive();
    }
}

interface Car {
    void drive();
}

class Benz implements Car {
    @Override
    public void drive() {
        System.out.println("奔驰在跑...");
    }
}

class CarFactory {
    public static Car getBenz() {
        return new Benz();
    }
}

总结: 静态工厂做到了生产者和使用者的分离,但它不满足OCP开闭原则,因为一旦你想再添加一辆新品牌的车,则需要修改实现类 CarFactory 的内容。

OCP开闭原则:Open-Closed Principle: 一个软件的实体应当对添加拓展开放,对修改重构关闭。

9.2 工厂方法

概念: 与静态工厂不同,工厂方法模式不将 CarFactory 定义为类,而是定义为接口。

流程:

  1. 保留接口 Car 和奔驰实现类 Benz
  2. 设计工厂接口 CarFactory
  3. 设计工厂接口方法 build():返回一辆车的实例。
  4. 设计奔驰工厂接口实现类 BenzFactory 实现 CarFactory,专门负责造奔驰。

源码: /javase-oop/

  • src: c.y.factory.factorymethod.CarFactoryTest
/**
 * @author yap
 */
public class CarFactoryTest {
    @Test
    public void factoryMethod() {
        new BenzFactory().build().drive();
    }
}

interface Car {
    /**
     * 驾驶方法
     */
    void drive();
}

class Benz implements Car {
    @Override
    public void drive() {
        System.out.println("奔驰在跑...");
    }
}

interface CarFactory {
    /**
     * 构建一辆车的实例
     *
     * @return 车的接口实例
     */
    Car build();
}

class BenzFactory implements CarFactory {
    @Override
    public Car build() {
        return new Benz();
    }
}

总结:

  • 工厂方法满足OCP原则,因为你添加一辆奥迪,不需要去修改任何一个类或接口的内容,而是再添加一个奥迪类 AuDi 和奥迪工厂接口实现类 AuDiFactory
  • 但是它的缺点也显而易见,你需要让使用者去new对应的车厂,所以这种模式,理论上优于静态工厂,但实际开发中还是静态工厂用的比较多。

9.3 抽象工厂

概念: 抽象工厂模式在大型的项目中才会遇得到,这种工厂模式并不是对单独的产品(发动机/轮胎/内饰)进行操作,而是对一条产品族(发动机+轮胎)进行整体操作。

流程:

  1. 设计一个轮胎的接口 Tires 和两个轮胎的实现类:GoodTiresBadTires
  2. 设计一个发动机的接口 Engine 和两个发动机的实现类:GoodEngineBadEngine
  3. 设计一个车厂接口 CarFactory,负责制造轮胎和发动机等,有两个实现类:GoodCarFactoryBadCarFactory
  4. 在使用的时候,创造一个好车厂或者坏车厂,就可以决定创造出来的零件都是好的或都是坏的。

源码: /javase-oop/

  • src: c.y.factory.abstractfactory.CarFactoryTest
/**
 * @author yap
 */
public class CarFactoryTest {
    @Test
    public void abstractFactory() {
        // by good
        CarFactory goodFactory = new GoodCarFactory();
        goodFactory.getEngine().info();
        goodFactory.getTires().info();

        // by bad
        CarFactory badFactory = new BadCarFactory();
        badFactory.getEngine().info();
        badFactory.getTires().info();
    }
}

总结: 这种工厂模式满足OCP原则,因为想加一条产品族,不需要改变原来的"车厂+零件"的产品族,而是再去添加一个"水果厂+配料",或者"粮食厂+粮食"等即可。