天天看点

Java对于面向对象编程的设计

多态

重载与重写

Java对于面向对象编程的设计
Java对于面向对象编程的设计
Java对于面向对象编程的设计

输出是Object。

在这个例子中,重载的3个get方法,Object包含另外两种参数类型。这种设计会带来问题:String... 本可以是null,单String,String[];int数组也有可能在运行时当做Object类型。

接口与抽象类

接口定义了必须实现的规范,接口也可以保持对非可见类的引用。面向接口让扩展变得很容易。

抽象类定义一些公有骨架,这些骨架应该自成内部逻辑。如果抽象类调用子类的实现,那么很可能这个结果是不可控的。如果必须调用子类的实现,要么子类的实现对父类的逻辑没影响,要么规定子类结果的规范。

接口标记和注解标记

java.io.Serializable与@resource

例子

分支与循环

do whlie whlile do

for三个条件 i++与++i

foreach数组或迭代器

异常分支

continue break

if bealpoint else

switch

享元模式

控制类实例的数量。

基本类型的设计模式,String的字符串常量池

静态工厂方法

以Boolean为例,这三个静态工厂方法都保证只有两个(有限个数)对象实例被使用。

Java对于面向对象编程的设计
Java对于面向对象编程的设计
Java对于面向对象编程的设计

有限个数不同意义对象使用的好处在于可以只通过 == 地址比较对象意义是否相同,并且减少了对象的分配。 同样的控制对象个数的如:单例和枚举。在后面会提到。

合租

某公寓合租4个月,500元/月(水电煤网均摊,网费30元/月),空调、卫生间、厨房齐全,室内均为IT行业人士,喜欢安静。所以要求来租者最好为同行或者刚毕业的年轻人,爱干净、安静。

有意者请电话联系。

联系人:赵先生

联系方式:请阅读代码

Java对于面向对象编程的设计

建造者模式

试想这么一个场景,用户注册。为了用户良好的体验,在注册时应该避免过多的信息录入。除了用户名、密码等必要信息外,不同的用户可能有意愿输入的信息并不一样。当然可以采用不同的构造器重叠来解决这个问题,或者统一的构造器(必要参数),其它信息使用Java Bean的set/get方法。 这里User省略了set/get方法,但已经生成了,这里没有必要显示。

import lombok.Data;


@Data
public class User {
    protected String userName;
    protected String password;
    protected Boolean gender;
    protected String  email;
    protected Long tel;
}
           

构造器重叠

Java对于面向对象编程的设计
Java对于面向对象编程的设计

静态工厂方法

public final class Users {

    private Users() {
    }

    public static User register(String userName, String password) {
        User user = new User();
        user.setUserName(userName);
        user.setPassword(password);
        return user;
    }

    public static User registerByMail(String userName, String password, String email) {
        User user = new User();
        user.setUserName(userName);
        user.setPassword(password);
        user.setEmail(email);
        
        return user;
    }
}
           

建造者

public class UserBuilder extends User {

    public UserBuilder(Build build) {
        this.userName = build.userName;
        this.password = build.password;
        this.gender = build.gender;
        this.email = build.email;
        this.tel = build.tel;
    }

    public static class Build {
        private String userName;
        private String password;
        private Boolean gender;
        private String email;
        private Long tel;

        public Build(String userName, String password) {
            this.userName = userName;
            this.password = password;
        }

        public Build gender(Boolean gender) {
            this.gender = gender;
            return this;
        }

        public Build email(String email) {
            this.email = email;
            return this;
        }

        public Build tel(Long tel) {
            this.tel = tel;
            return this;
        }

        public User build() {
            return new UserBuilder(this);
        }
    }

    public static void main(String[] args) {
        User user = new Build("name","pwd").email("[email protected]").tel(18076767865L).build();
    }
}
           

组合与继承

继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在 b is a 关系的时候,类B才应该继承类A。

组合(has-a)关系可以显式地获得被包含类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包含类和父类对应,而组合外部类和子类对应。如果你确定复用另外一个类的方法永远不需要改变时,应该使用组合,因为组合只是简单地复用被包含类的接口,而继承除了复用父类的接口外,它甚至还可以覆盖这些接口,修改父类接口的默认实现,这个特性是组合所不具有的。

从逻辑上看,组合最主要地体现的是一种整体和部分的思想,例如在电脑类是由内存类,CPU类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。

比如人的例子:不同穿着风格的人:运动服、正装、休闲装。这些装饰只是不同类型人的属性,并不是人的子类。

Java对于面向对象编程的设计
Java对于面向对象编程的设计

矩阵转置

问题描述

给出一个矩阵,求出矩阵的转置,如:

给出左侧矩阵,转置为右侧矩阵

Java对于面向对象编程的设计

Java对于面向对象编程的设计

将转置前的a矩阵行列互换赋给转置后的b矩阵。打印出矩阵

实现代码

Java对于面向对象编程的设计

加载顺序

在写java代码的时候,我们按照自然语言逻辑所写的代码就是java程序的执行顺序。这让我们不需要关注java是如何解析层级的加载顺序。也可以忽略类属模块和对象模块的区别。这是java设计的简单之处。大巧若拙。

代码块的执行顺序:

父类

Java对于面向对象编程的设计

静态内部子类:(这里只是为了代码方便)

Java对于面向对象编程的设计

执行Parent b = new Sub();

Java对于面向对象编程的设计

成员的加载顺序

我们定义一个接口

ICallable.java

public interface ICallable {
    String IBASE = "IBASE";
    void call();
}
           

父类实现这个接口,子类重写接口

public class Parent implements ICallable {
    private String base = "base";

    public Parent() {
        call();
    }

    @Override
    public void call() {
        System.out.println(IBASE + "-" + base);
    }

    static class Sub extends Parent {
        private String sub = "sub";

        @Override
        public void call() {
            System.out.println(IBASE + "-" + sub);
        }
    }

    public static void main(String[] args) {
        Parent o = new Sub();
    }
}
           

执行Parent b = new Sub();构造器this逃逸。结果是什么?答案是:IBASE-null

代码从右往左运行。调用子类构造方法,发现有继承的父类。初始化父类:成员变量。调用父类构造方法。父类调用call方法。发现子类重写。调用子类方法(这时子类还没初始化)。子类初始化。

静态与非静态

接着上面的例子写个测试:

Java对于面向对象编程的设计

访问权限运行(如:public)静态内部类可以直接访问,这在某种场合下是需要的。

Java对于面向对象编程的设计

访问权限运行(如:public)内部类需要外部类的实例来调用。这是受限于static关键字。如果我们只需要用内部类,不需要外部类的实例,用静态内部类。一般非静态内部类用法较多。

静态方法(Static Method)与静态成员变量一样,属于类本身,在类装载的时候被装载到内存(Memory),不自动进行销毁,会一直存在于内存中,直到JVM关闭。

非静态方法(Non-Static Method)又叫实例化方法,属于实例对象,实例化后才会分配内存,必须通过类的实例来引用。不会常驻内存,当实例对象被JVM 回收之后,也跟着消失。

这个描述存在问题,后续修正?!静态方法和静态变量创建后始终使用同一块内存,是连续的。非静态方法会存在于内存的多个地方,是离散的。如果静态方法在系统中定义太多,会占用大量的资源,最后造成内存溢出,所以静态方法不能滥用。

异常

简介

异常:是程序在运行时出现的不正常情况。异常的产生有三种情况:

  1. 主动抛出异常
  2. 同步异常,当前线程不正常执行
  3. 异步异常,虚拟机内部错误或异常线程导致其它线程的异常

在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。

  1. Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
  2. Error(错误):是程序无法处理的错误不需要捕获,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
  3. Exception(异常):是程序本身可以处理的异常。
  4. Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示"JVM 常用操作"引发的错误。

注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。

  1. 可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
  2. 不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。

Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

  1. 运行时异常:RuntimeException(运行时异常)是指程序员因设计或实现方式不当而导致的问题 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
  2. 非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

允许忽略不可查的RuntimeException和Error。

异常处理的5个关键字

  • try
    • 代码块,可能发生异常的逻辑代码。必须与catch或finally一个或多个搭配使用
  • catch
    • 代码块,对应try代码块发生的异常处理,一个或者多个
    • 异常捕获规则先捕获子类异常再捕获父类异常,否则会被负载,如:声明Exception,可以捕获任何异常。为了更清楚处理异常,应该捕获或者抛出具体的异常类型。
  • finally
    • 避免在finally块中使用return,否则会报"finally block does not complete normally"提醒
    • 代码块,发生或者不发生异常都会执行
    • 代码块可以嵌套使用try、catch/finally
  • throw
    • 语句,对于处理不了的异常,抛出让调用这处理
  • throws
    • 用于方法声明中,表示抛出的一个或者多个异常

Throwable中的方法

  • String getMessage()
    • 获取异常信息,返回字符串。
  • String toString()
    • 获取异常类名和异常信息,返回字符串。
  • void printStackTrace()
    • 打印异常在堆栈中的跟踪信息;
    • 获取异常类名和异常信息,以及异常出现在程序中的位置。

异常使用的建议

  1. 不要忽略异常。很多时候对于异常的处理采用妥协的方式,空的try-catch。对于当时不能处理的异常应该记录异常信息,保证能获取异常发生时程序的运行状态。
  2. 异常信息保真。记录的异常信息应该是程序运行的堆栈,这样能让我们从技术角度来分析问题,而不是业务角度。比如:此异常方式是因为用户输入数字不合法,这种业务记录信息应该与异常信息分别记录,而不是替换。
  3. 不要避免抛出异常。抛出异常让当前线程停止执行当前栈帧,并抛往上层的栈帧直至处理或者结束当前线程。往下的逻辑没有必要执行。为了前端展示可能在底层要求不要抛出异常,但是这个异常应该有业务前端做处理,而不是不抛出异常。并且要详细记录,如非必要请勿将异常向上转型。
  4. 记录文档。异常的出现由于输入不合法,或者硬件执行异常。明确指明输入参数的范围,是否做了参数校验。哪种情况抛出什么样的异常。

    异常处理

    Java对于面向对象编程的设计

    在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。异常处理器是当前方法注册的异常处理集合,描述异常处理器的作用范围、能处理的异常类型、处理异常代码的所在位置。

    没有找到任何异常处理器,那么恢复到该方法调用这的栈帧中重新抛出。在方法调用链里重复进行前面的操作,直到方法调用链的顶端,还没找到对应的异常处理器,执行线程退出。

    异常程序执行是按照程序的执行分支执行。每种执行的情况是不同的分支。在分支处理中,finally代码块会跟在每种分支后面执行。也就是说,有多少种执行分支就会复制多少份finally代码块,保证立即执行finally代码。各个执行分支是对立的,互不影响。

    Java对于面向对象编程的设计
    Java对于面向对象编程的设计
    Java对于面向对象编程的设计
    Java对于面向对象编程的设计

继续阅读