抽象类接口、设计模式、异常等(Java学习笔记五)

单例模式

采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法,减少了系统性能的开销。

应用场景:网站计数器、应用程序的日志应用、数据库连接池、读取配置文件的类、任务管理器、回收站等。

1.饿汉式:

优:线程安全;劣:对象加载时间较长。

class Bank{
    //类内部创建私有对象
    private static Bank bankSingle = new Bank();
    //私有化构造器
    private Bank(){
    }
    //提供公共的静态方法,返回类的对象
    public static Bank getSingle(){
        return bankSingle;
    }
}

2.1.线程不安全的懒汉式:

优:延迟对象的创建;劣:线程不安全。

class Bank2 {
    //类内部创建私有对象
    private static Bank2 bank2Single = null;
    //私有化构造器
    private Bank2() {
    }
    //提供公共的静态方法,返回类的对象
    public static Bank2 getSingle() {
        if (bank2Single == null) {
            bank2Single = new Bank2();
        }
        return bank2Single;
    }
}

2.2.线程安全的懒汉式:

必须加锁 synchronized 才能保证单例,但加锁会影响效率。

public class Singleton {  
    private static Singleton instance = null;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    	if (instance == null) {  
        	instance = new Singleton();  
    	}  
    	return instance;  
    }  
}

3双检锁/双重校验锁(DCL,即 double-checked locking)

线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

被volatile关键字修饰的变量是被禁止重排序,把instance声明为volatile型,可以实现线程安全的延迟初始化。


main()

main()方法是程序的入口,是一个普通的静态方法。

main()方法与命令台做交互的方式是通过获取args[]数组中的参数。


代码块(初始化块)

类的成员:属性、方法、内部类、代码块。

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

代码块只能使用static进行修饰,或者不加任何修饰符。故代码块分为静态代码块和非静态代码块。

静态代码块:

  • 随着类的加载而执行代码块中程序。
  • 仅在类加载时执行一次。
  • 如果有多个静态代码块,则按照声明的先后顺序执行。
  • 作用:初始化类的信息。

非静态代码块:

  • 随着对象的创建而执行代码块中程序。
  • 每创建一个对象,就执行一次。
  • 作用:可以在创建对象时,对对象的属性等进行初始化。

由父及子,静态先行。

代码块赋值在构造器赋值之前。代码块赋值和显式赋值相比,根据二者在类中的出现顺序先后赋值。


final

final可以用来修饰类、方法、变量。

final修饰的类不能被继承,比如String类、System类、StringBuffer类。

final修饰的方法不能被重写,但是可以重载,比如getClass()方法。

final修饰变量,此时的“变量”称为常量

  • final修饰属性:可以考虑赋值的位置有显式赋值、代码块赋值、构造器赋值。
  • final修饰局部变量:常量,不可再改变。

static final:用来修饰属性或方法,全局常量。


抽象类和抽象方法

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计的非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

应用场景:设计模式中应用很广泛。

abstract关键字,用来修饰类和方法,对应抽象类和抽象方法。

抽象类:

  • 抽象类不能被实例化。

  • 抽象类中一定有构造器,便于子类实例化时调用。

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

抽象方法:

  • 抽象方法只有方法的声明,没有方法体。
  • 包含抽象方法的类一定的抽象类,但抽象类不一定有抽象方法。
  • 子类重写了父类中所有的抽象方法后,这个子类才可以实例化;若子类没有实现父类的抽象方法,则说明该子类也是一个抽象类。

注意点:

  • abstract不能用来修饰属性、构造器等结构。
  • abstract不能用来修饰私有方法、静态方法、final的方法、final的类。

抽象类的匿名子类,其中Person是抽象类:(仅能使用一次)

//抽象类的匿名子类对象p:
Person p = new Person(){
	@Override
	public void eat(){
        ...
	}
	@Override
	public void drink(){
        ...
	}
};
//抽象类的匿名子类的匿名对象:
System.out.println(new Person(){
	@Override
	public void eat(){
        ...
	}
	@Override
	public void drink(){
        ...
	}
};)

模板方法模式

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

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

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

应用:数据库访问的封装、Junit单元测试、Servlet中doGet/doPost方法调用、Hibernate中模板程序、Spring中JDBCTemplate等。


接口

一方面,有时必须从几个类中派生出一个子类,继承他们所有的属性和方法。但是Java不支持多重继承,故使用接口来得到多重继承的效果

类可以实现多个接口---->弥补了单继承性的局限性。

格式:class A extends B implements C,D,E…;

另一方面,有时必须从几个类中抽取出一些共同的行为特征,而他们之间没有依赖关系,仅仅是具有相同的行为特征而已。

接口的本质是契约、标准、规范。继承是一个”是不是“的关系,而接口实现则是”能不能“的关系。接口的具体使用,体现了多态性。

接口的定义:

  • 全局常量:public static final(平时省略)
  • 抽象方法:public abstract(平时省略)
  • 静态方法、默认方法

接口中不能定义构造器,意味着接口不可以实例化!

接口通过implements关键字去实现。抽象类用extends关键字去继承。

如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。

如果实现类没有覆盖接口中的所有抽象方法,则此实现类仍然是抽象类。

接口与接口之间可以继承,而且可以多继承。

JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。

public interface Vehicle {
   default void print(){//接口中的默认方法
      System.out.println("我是一辆车!");
   }
}
  • 接口中定义的静态方法,只能通过接口来调用。
  • 通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口的默认方法,则调用时会调用重写后的方法。
  • 如果子类(实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类没有重写此方法的情况下,默认调用父类中方法。(类优先原则)
  • 如果实现类实现了多个接口,且这多个接口定义了同名同参数的方法,那么实现类没有重写此方法的情况下,报接口冲突错误。

抽象类和接口的异同:

相同点:

  • 都不能实例化。
  • 都可以被继承。

不同点:

  • 抽象类有构造器,接口不能声明构造器。
  • 一个类只能继承一个抽象类,但是可以实现多个接口。
  • 接口定义的变量只能是公共的静态常量,抽象类中定义的变量可以是普通变量。

代理模式

代理模式的设计是为其他对象提供一种代理以控制对这个对象的访问。

public class Po {
    public static void main(String[] args) {
        new Client(new Server()).browse();
    }
}

interface Network{
    public void browse();
}

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

//代理
class Client implements Network{

    private Network network;

    public Client(Network network) {
        this.network = network;
    }

    public void check(){
        System.out.println("访问前的一些准备工作");
    }
    @Override
    public void browse() {
        check();
        network.browse();
    }
}

应用场景:安全代理、远程代理、延迟加载。

分类:静态代理、动态代理(JDK自带)。


工厂模式

实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。分为简单工厂、工厂方法和抽象工厂。


内部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。

允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。

内部类一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。且内部类名称不能和外部类名称相同。

分为成员内部类(static成员内部类和非static成员内部类)、局部内部类(不谈修饰符)、匿名内部类。

class Person{
    //静态成员内部类
	static class Dog{
        ...
    }
    //非静态成员内部类
    class Tiger{
        String name;
        public void sing(){
            ...
        }
    }
    public void method(){
        //局部内部类
        class Cat{   
            ...
        }
    }
    {
        //局部内部类
        class Bird{ 
            ...
        }
    }
}

对象创建:
Person.Dog dog = new Person.Dog();//静态成员内部类
Person p = new Person();
Person.Tiger tiger = p.new Tiger();//非静态成员内部类

成员内部类:

  • 作为外部类的成员
    • 可以调用外部类的结构。
    • 可以被static修饰。
    • 可以被4种权限修饰符修饰。
  • 作为一个类:
    • 类内可以定义属性、方法、构造器等。
    • 可以被final修饰,表示此内部类不能被继承。
    • 可以被abstract修饰,表示此内部类不能被实例化。

局部内部类的方法中如果调用局部内部类所在方法种的局部变量,则要求此局部变量必须声明为final。


异常

将程序执行中发生的不正常情况称为异常。(开发过程种的语法错误和逻辑错误不是异常)

Java程序在执行过程中发生的异常事件分为两类:

  • Error:JVM无法解决的严重问题。如JVM内部错误、资源耗尽、StackOverflowError和OOM等。一般不编写针对性代码处理。

  • Exception:因编程错误或偶然因素导致的一般性问题,可以使用针对性代码进行处理。例如空指针异常、数组角标越界、网络中断、试图读取不存在的文件等。

    其中又分为编译时异常和运行时异常(角标越界、除数为0等)。

图构如下:

java.lang.Throwable

  • java.lang.Error:一般不编写针对性代码处理。
  • java.lang.Exception:可以进行异常处理:
    • 编译时异常(checked):
      • IOException
        • FileNotFoundException
      • ClassNotFoundException
    • 运行时异常(unchecked,RuntimeException):
      • NullPointerException
      • ArrayIndexOutOfBoundsException
      • ClassCastException
      • NumberFormatException
      • InputMismatchException
      • ArithmeticException

异常处理:抓抛模型。

过程一:”抛“:一旦出现异常,则会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象之后,其后的代码不再执行。

过程二:“抓”:可以理解为异常处理方式,有以下两种异常处理方式。

异常处理方式一:try-catch-finally。
  try {
       //可能产生的异常的代码区
   }catch (ExceptionType1 e) {
       //捕获并处理try抛出异常类型为ExceptionType1的异常
   }catch (ExceptionType2 e){
       //捕获并处理try抛出异常类型为ExceptionType2的异常
   }
	......
   finally{
       //无论是出现异常,finally块中的代码都会被执行
   }

说明:

  • finally可选,即可写可不写。
  • finally中声明的是一定会被执行的代码,即使catch中又出现异常,或在try、catch中有return语句,finally的语句依旧会被执行。
  • 像数据库连接、输入输出流、网络编程Socket等资源,JVM不能自动回收,此时需要声明在finally中。
  • 使用try将可能出现异常的代码包装起来,在执行过程中一旦出现异常,就会生成一个对应异常类的对象,根据此对象类型,去catch中进行匹配。一旦匹配成功,则进入catch中进行异常的处理。一旦处理完成,跳出当前try-catch结构(无finally情况),继续执行其后的代码。
  • catch中的异常类型如果没有子父类关系,则前后顺序无影响。
  • catch中的异常类型如果有子父类关系,则子类一定声明在父类前,否则会报错。
  • 常用的异常处理方式: ①e.getMessage(); ②e.printStackTrace();
  • 在try结构中声明的变量,在出了try结构之后,不能再调用。
  • try-catch-finally处理编译时异常,使得程序在编译时不再报错,但是运行时仍可能报错。相当于将编译时异常延迟到了运行时。
  • 开发中,由于运行时异常很常见,所以通常不针对运行时异常编写try-catch-finally,针对于编译时异常,要考虑异常的处理。
异常处理方式二:throws+异常类型。
public static void main(String[] args) throws MyException 

当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象。此对象满足throws后面的类型时,就会被抛出。异常代码的后续代码不再执行。

二者区别:try-catch-finally真正的处理掉了异常,但是throws只是将异常抛给了方法的调用者,并没有真正将异常处理掉。

父类中被重写方法没有抛出异常,则子类重写的方法如果有异常只能用try-catch-finally进行处理。

手动生成异常对象

异常对象的产生来自两部分:系统自动生成的异常对象和手动生成的异常对象。

throw,在”抛“过程,而上述throws在”抓”过程“:

if(...){
	...
}else{
	throw new ExceptionType("错误信息");//生成一个异常对象
}
自定义异常
class MyException extends Exception {
    
    public MyException() {
        
    }
    public MyException(String msg) {
      	super(msg);
    }

}

JAVA语言基础篇结束。

JAVA语言高级篇开始:

多线程、枚举、集合、泛型、IO流、网络编程、反射等。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值