多态
重载与重写
输出是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为例,这三个静态工厂方法都保证只有两个(有限个数)对象实例被使用。
有限个数不同意义对象使用的好处在于可以只通过 == 地址比较对象意义是否相同,并且减少了对象的分配。 同样的控制对象个数的如:单例和枚举。在后面会提到。
合租
某公寓合租4个月,500元/月(水电煤网均摊,网费30元/月),空调、卫生间、厨房齐全,室内均为IT行业人士,喜欢安静。所以要求来租者最好为同行或者刚毕业的年轻人,爱干净、安静。
有意者请电话联系。
联系人:赵先生
联系方式:请阅读代码
建造者模式
试想这么一个场景,用户注册。为了用户良好的体验,在注册时应该避免过多的信息录入。除了用户名、密码等必要信息外,不同的用户可能有意愿输入的信息并不一样。当然可以采用不同的构造器重叠来解决这个问题,或者统一的构造器(必要参数),其它信息使用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;
}
构造器重叠
静态工厂方法
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类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。
比如人的例子:不同穿着风格的人:运动服、正装、休闲装。这些装饰只是不同类型人的属性,并不是人的子类。
矩阵转置
问题描述
给出一个矩阵,求出矩阵的转置,如:
给出左侧矩阵,转置为右侧矩阵
、
将转置前的a矩阵行列互换赋给转置后的b矩阵。打印出矩阵
实现代码
加载顺序
在写java代码的时候,我们按照自然语言逻辑所写的代码就是java程序的执行顺序。这让我们不需要关注java是如何解析层级的加载顺序。也可以忽略类属模块和对象模块的区别。这是java设计的简单之处。大巧若拙。
代码块的执行顺序:
父类
静态内部子类:(这里只是为了代码方便)
执行Parent b = new Sub();
成员的加载顺序
我们定义一个接口
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方法。发现子类重写。调用子类方法(这时子类还没初始化)。子类初始化。
静态与非静态
接着上面的例子写个测试:
访问权限运行(如:public)静态内部类可以直接访问,这在某种场合下是需要的。
访问权限运行(如:public)内部类需要外部类的实例来调用。这是受限于static关键字。如果我们只需要用内部类,不需要外部类的实例,用静态内部类。一般非静态内部类用法较多。
静态方法(Static Method)与静态成员变量一样,属于类本身,在类装载的时候被装载到内存(Memory),不自动进行销毁,会一直存在于内存中,直到JVM关闭。
非静态方法(Non-Static Method)又叫实例化方法,属于实例对象,实例化后才会分配内存,必须通过类的实例来引用。不会常驻内存,当实例对象被JVM 回收之后,也跟着消失。
这个描述存在问题,后续修正?!静态方法和静态变量创建后始终使用同一块内存,是连续的。非静态方法会存在于内存的多个地方,是离散的。如果静态方法在系统中定义太多,会占用大量的资源,最后造成内存溢出,所以静态方法不能滥用。
异常
简介
异常:是程序在运行时出现的不正常情况。异常的产生有三种情况:
- 主动抛出异常
- 同步异常,当前线程不正常执行
- 异步异常,虚拟机内部错误或异常线程导致其它线程的异常
在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。
- Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
- Error(错误):是程序无法处理的错误不需要捕获,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
- Exception(异常):是程序本身可以处理的异常。
- Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示"JVM 常用操作"引发的错误。
注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
- 可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
- 不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。
Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。
- 运行时异常:RuntimeException(运行时异常)是指程序员因设计或实现方式不当而导致的问题 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
- 非运行时异常 (编译异常):是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()
- 打印异常在堆栈中的跟踪信息;
- 获取异常类名和异常信息,以及异常出现在程序中的位置。
。
异常使用的建议
- 不要忽略异常。很多时候对于异常的处理采用妥协的方式,空的try-catch。对于当时不能处理的异常应该记录异常信息,保证能获取异常发生时程序的运行状态。
- 异常信息保真。记录的异常信息应该是程序运行的堆栈,这样能让我们从技术角度来分析问题,而不是业务角度。比如:此异常方式是因为用户输入数字不合法,这种业务记录信息应该与异常信息分别记录,而不是替换。
- 不要避免抛出异常。抛出异常让当前线程停止执行当前栈帧,并抛往上层的栈帧直至处理或者结束当前线程。往下的逻辑没有必要执行。为了前端展示可能在底层要求不要抛出异常,但是这个异常应该有业务前端做处理,而不是不抛出异常。并且要详细记录,如非必要请勿将异常向上转型。
- 记录文档。异常的出现由于输入不合法,或者硬件执行异常。明确指明输入参数的范围,是否做了参数校验。哪种情况抛出什么样的异常。
异常处理
在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。异常处理器是当前方法注册的异常处理集合,描述异常处理器的作用范围、能处理的异常类型、处理异常代码的所在位置。
没有找到任何异常处理器,那么恢复到该方法调用这的栈帧中重新抛出。在方法调用链里重复进行前面的操作,直到方法调用链的顶端,还没找到对应的异常处理器,执行线程退出。
异常程序执行是按照程序的执行分支执行。每种执行的情况是不同的分支。在分支处理中,finally代码块会跟在每种分支后面执行。也就是说,有多少种执行分支就会复制多少份finally代码块,保证立即执行finally代码。各个执行分支是对立的,互不影响。