定义#
单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例(哪怕是多线程),并且该类只提供一个取得其对象实例的方法。
如果要让一个类在JVM中只产生一个实例,可以采用以下方法:
- 将类的构造器的访问权限设置为private => 这样在类的外部无法通过new操作符获取类的实例
- 此时,在类的内部可以产生该类的对象,由于类外部无法获取实例,因此需要提供一个静态方法来返回类内部创建的实例
- 由于静态方法只能访问类中的静态成员变量,因此,类内部创建的实例必须定义为static的
分类#
饿汉式(线程安全)
public class Singletion{
private Singletion() {
}
private static Singleton single = new Singletion();
public static Singleton getInstance() {
return single;
}
}
懒汉式
以下代码存在线程安全问题:如果有多个访问者同时去获取对象实例,就像⼀堆人在抢厕所,就会造成多个同样的实例并存,从而没有达到单例的要求。
public class Singletion{
private Singletion() {
}
private static Singleton single = null;
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
修复线程安全问题
-
使用
synchronized
关键字(不推荐)
由于把锁加到⽅法上后,所有的访问都因需要锁占⽤导致资源的浪费。 如果不是特殊情况下,不建议此种⽅式实现单例模式。
public class Singletion{
private Singletion() {
}
private static Singleton single = null;
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
- 使用内部类(推荐)
既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。因为JVM虚拟机可以保证多线程并发访问的正确性,也就是⼀个类的构造方法在多线程环境下可以被正确的加载。
public class Singleton {
private static class SingletonHolder {
private static Singleton single = new Singleton();
}
private Singletion() {
}
public static Singleton getInstance() {
return SingletonHolder.single;
}
}
- 双重锁校验
双重锁校验是方法级锁的优化,减少了部分获取实例的耗时。
public class Singletion{
private Singletion() {
}
private static Singleton single = null;
public static Singleton getInstance() {
if (single != null) {
return single;
}
synchronized (Singleton.class) {
if (single == null) {
single = new Singleton();
}
}
return single;
}
}
优点#
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,e.g.读取配置、产生其他依赖对象,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。(e.g.java.lang.Runtime)
应用场景#
- 网站的计数器:如不采用单例模式则难以实现同步
- 应用程序的日志应用:一般都使用单例模式实现,因为共享的日志文件一般一直处于打开状态,只能有一个实例去操作,否则内容不便于追加
- 数据库连接池:数据库连接是一种数据库资源
- 读取配置文件的类:配置文件也是一种资源,没有必要每次使用配置文件数据,都生成一个对象去读取
- Windows的任务管理器和回收站
注意#
在平时的开发中如果可以确保此类是全局可用不需要做懒加载,那么直接创建并给外部调用即可。但如果是很多的类,有些需要在用户触发⼀定的条件后(游戏关卡)才显示,那么⼀定要用懒加载。线程的安全上可以按需选择。
抽象类与抽象方法#
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
相关概念#
-
抽象类:用
abstract
修饰的类 -
抽象方法:用
abstract
修饰的方法,只有方法的声明,没有方法的实现,即没有方法体,该方法的实现由抽象类的子类提供 - 含有抽象方法的类必须声明为抽象类
- 抽象类中一定有构造器。
- 抽象类是用来被继承的,它不能被实例化,其子类必须重写抽象方法,提供方法体;若未重写全部抽象方法,则子类仍然是抽象类
-
不能用
abstract
修饰变量、代码块、构造器、私有方法、静态方法、final方法、final类
abstract class A {
abstract void method1();
public void method2() {
System.out.println("methon2");
}
}
class B extends A {
void method1() {
System.out.println("methon1 in class B");
}
}
public class Test {
public static void main(String[] args) {
A instance = new B();
instance.methon1();
instance.method2();
}
}
应用:模板方法设计模式#
模板方法:当功能内部一部分实现是确定的,一部分实现是不确定的,这时可以将不确定的部分暴露出去,让子类去实现。
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展改造,但子类总体上会保留抽象类的行为方式。
模板方法的核心设计思路是:在抽象类中定义抽象方法的执行顺序(执行策略与标准),并将抽象方法设定为只有子类实现,但不设计独立访问的方法。
接口#
概念#
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。
继承:“是不是”的关系
接口:“能不能”的关系
接口的本质是契约、标准、规范,制定好后大家都要遵守。
接口是抽象方法和常量值定义的集合。
特点#
-
使用关键字
interface
-
接口中所有成员变量默认是用
public static final
修饰 -
接口中所有抽象方法默认是用
public abstract
修饰 - 接口中没有构造器
- 接口采用多继承机制,一个类可以实现多个接口,接口也可以继承其他接口
e.g.接口定义
public interface Runner {
int ID = 1; // public static final int ID = 1
void start(); // public abstract void start()
void run(); // public abstract void run()
void stop(); // public abstract void stop()
}
-
一个类既有继承又有实现时,先写
extends
后写implements
e.g. class SubClass extends SuperClass implements InterfaceA{}
- 接口的实现类中必须提供接口中所有方法具体实现内容才能实例化,否则仍然是抽象类
应用:代理模式#
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
public interface Network {
public void browse();
}
// 被代理类
class RealServer implements Network {
@Override
public void browse() {
System.out.println("真实服务器上网流量信息");
}
}
// 代理类
class ProxyServer implements Network {
private Network network;
public ProxyServer(Network n) {
this.network = n;
}
public void check() {
System.out.println("检查网络连接");
}
public void browse() {
// 代理做的一些其他行为
check();
network.browse();
}
}
public class ProxyDemo {
public static void main(String[] args) {
Network net = new ProxyServer(new RealServer());
net.browse();
}
}
场景
- 安全代理:屏蔽对真实角色的直接访问
- 远程代理:通过代理类处理远程方法调用
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
e.g. 开发一个大文档查看软件,其中有较大的图片,在打开文件时不可能将所有的图片都显示出来,就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开
分类
- 静态代理:静态定义代理类
- 动态代理:动态生成代理类,JDK自带动态代理,涉及到反射
应用:工厂模式#
工厂模式实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂模式分为3种:
- 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码,违反了开闭原则)
// 工厂类
class CarFactory {
public static Car getCar(String type) {
if ("奥迪".equals(type)) {
return new Audi();
} else if ("比亚迪".equals(type)) {
return new BYD();
} else {
return null;
}
}
}
public class Test {
public static void main(String[] args) {
Car a = CarFactory.getCar("奥迪");
a.run();
Car b = CarFactory.getCar("比亚迪");
b.run();
}
}
调用者只需要知道他要什么,从哪里拿和如何创建是不需要知道的。多出了一个专门生产Car的工厂类,把调用者与创建者分离。
由于工厂类一般使用静态方法,通过接收参数来决定返回什么实例对象,因此简单工厂模式也叫静态工厂模式。
- 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
interface Car {
void run();
}
class Audi implements Car {
public void run(){}
}
class BYD implements Car {
public void run(){}
}
// 工厂接口
interface Factory {
Cat getCar();
}
// 两个工厂类
class AudiFactory implements Factory {
public Audi getCar() {
return new Audi();
}
}
class BYDFactory implements Factory {
public BYD getCar() {
return new BYD();
}
}
public class Test {
public static void main(String[] args) {
Car a = new AudiFactory().getCar();
a.run();
Car b = new BYDFactory().getCar();
b.run();
}
}
简单工厂模式只有一个工厂类(对于一个项目/一个独立的模块),而工厂方法模式有一组实现了相同接口的工厂类,这样在简单工厂模式里集中在工厂方法上的压力可以有工厂方法模式里不同的工厂实现类来分担。
工厂方法模式也没有真正的避免代码改动,要么将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色固定(如上述例子),且产品对象创建条件的改变必然会引起工厂角色修改,面对这种情况,Java的反射机制与配置文件的巧妙结合突破了限制(体现在Spring中)。
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品无能为力,但支持增加产品族)
抽象工厂模式和工厂方法模式的区别在于需要创建对象的复杂程度。抽象工厂模式的用意是给客户端提供一个接口,可以创建多个产品族中的产品对象。
使用抽象工厂模式需要满足:
1)系统中有多个产品族,且系统一次只可能消费其中一族产品;
2)同属于同一产品族的产品一起使用
核心本质:实例化对象时用工厂方法代替new操作。
接口与抽象类对比#
JDK8中的新特性#
在JDK7及之前,接口是一种特殊的抽象类,只包含常量和抽象方法的定义,而没有变量和方法的实现。
-
除了定义全局常量和抽象方法之外,还可以定义静态方法和默认方法。
-
静态方法:使用
static
修饰,实现类的实例无法获取静态方法,只能通过接口调用。 -
默认方法:使用
default
修饰,只能通过实现类的实例来调用。如果实现了重写了默认方法,则调用的是重写后的方法。
-
静态方法:使用
public interface CompareA {
// 静态方法
public static void method1() {
System.out.println("method1");
}
// 默认方法
public default void method2() {
System.out.println("method2");
}
}
class SubClass implements CompareA {
}
public class SubClassDemo {
public static void main(String[] args) {
SubClass s = new SubClass();
s.method1(); // 报错
CompareA.method1();
s.method2();
CompareA.method2(); // 报错
}
}
-
若一个接口中定义了一个默认方法,另一个接口中也定义了一个同名同参的方法(不管是否是默认方法),在实现类同时实现了这两个接口时,会出现接口冲突。
- 解决方案:实现类必须覆盖接口中同名同参的方法
-
若一个接口中定义了一个默认方法,而继承的父类中也定义了一个同名同参的非抽象方法,则不会冲突,此时遵循类优先原则,接口中的这一方法会被忽略。
interface Filial {
default void help() {
}
}
interface Spoony {
default void help() {
}
}
class BetterMan implements Filial, Spoony {
@Override
public void help() {
Filial.super.help();
Spoony.super.help();
}
}
内部类#
概念#
当一个事物的内部还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
内部类:定义在一个类内部的另一个类。
分类#
-
成员内部类:
static
成员内部类和非static
成员内部类-
可以声明为
private
或protected
- 可以调用外部类的结构
-
可以声明为
static
(此时不能再使用外部类的非静态成员) - 可以在内部定义属性、方法、构造器等
-
可以声明为
abstract
类,因此可以被其他内部类继承 -
可以声明为
final
(则不能被继承) -
编译后生成
OuterClass$InnerClass.class
字节码文件(局部内部类也有)
注意:
- 非静态的成员内部类中不能声明静态成员
- 成员内部类可以直接使用外部类的所有成员,包括私有成员(局部内部类也可以)
class Outer { private int s = 111; // 非静态成员内部类 public class Inner { private int s = 222; public void method(int s) { System.out.println(s); // 实参s System.out.println(this.s); // 222 System.out.println(Outer.this.s); // 111 } } // 静态成员内部类 public static class StaticInner { } } public class Test { public static void main(String[] args) { Outer out = new Outer(); // 非静态 Outer.Inner in = out.new Inner(); in.method(333); // 静态 Outer.StaticInner staticIn = new Outer.StaticInner(); } }
-
可以声明为
-
局部内部类:不谈修饰符
-
只能在声明它的方法或代码块中使用,且必须先声明后使用,其他地方都无法使用该类
-
局部内部类的实例可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
-
局部内部类可以使用外部方法的局部变量,但必须是
final
的 -
局部内部类和局部变量地位类似,不能使用权限修饰符,也不能使用
static
修饰,因此不能包含静态成员。
class Outer { public void method() { class PartialInnerA { ... } PartialInnerB b = new Outer.PartialInnerB(); } { class PartialInnerB { ... } } // 另一种场景(更常见) public Comparable getComparable() { class MyComparable implements Comparable { @Override public int compareTo(Object o) { return 0; } } return new MyComparable(); } }
-
-
匿名内部类
-
不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例(有且仅有一个)。一个匿名内部类一定是在
new
的后面,用它隐含实现一个接口或一个类。 - 匿名内部类必须继承父类或实现接口
- 匿名内部类对象只能使用多态形式引用
interface A { void fun(); } public class Outer { public void callInner(A a) { a.fun(); } public static void main(String[] args) { new Outer().callInner(new A(){ public void fun() { System.out.prinln("fun"); } }); } }
-
不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例(有且仅有一个)。一个匿名内部类一定是在