采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法,减少了系统性能的开销。
应用场景:网站计数器、应用程序的日志应用、数据库连接池、读取配置文件的类、任务管理器、回收站等。
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()方法与命令台做交互的方式是通过获取args[]数组中的参数。
类的成员:属性、方法、内部类、代码块。
代码块的作用:用于初始化类、对象。
代码块只能使用static进行修饰,或者不加任何修饰符。故代码块分为静态代码块和非静态代码块。
静态代码块:
非静态代码块:
由父及子,静态先行。
代码块赋值在构造器赋值之前。代码块赋值和显式赋值相比,根据二者在类中的出现顺序先后赋值。
final可以用来修饰类、方法、变量。
final修饰的类不能被继承,比如String类、System类、StringBuffer类。
final修饰的方法不能被重写,但是可以重载,比如getClass()方法。
final修饰变量,此时的“变量”称为常量。
static final:用来修饰属性或方法,全局常量。
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计的非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
应用场景:设计模式中应用很广泛。
abstract关键字,用来修饰类和方法,对应抽象类和抽象方法。
抽象类:
抽象类不能被实例化。
抽象类中一定有构造器,便于子类实例化时调用。
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作。
抽象方法:
注意点:
抽象类的匿名子类,其中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…;
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而他们之间没有依赖关系,仅仅是具有相同的行为特征而已。
接口的本质是契约、标准、规范。继承是一个”是不是“的关系,而接口实现则是”能不能“的关系。接口的具体使用,体现了多态性。
接口的定义:
接口中不能定义构造器,意味着接口不可以实例化!
接口通过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();//非静态成员内部类
成员内部类:
局部内部类的方法中如果调用局部内部类所在方法种的局部变量,则要求此局部变量必须声明为final。
将程序执行中发生的不正常情况称为异常。(开发过程种的语法错误和逻辑错误不是异常)
Java程序在执行过程中发生的异常事件分为两类:
Error:JVM无法解决的严重问题。如JVM内部错误、资源耗尽、StackOverflowError和OOM等。一般不编写针对性代码处理。
Exception:因编程错误或偶然因素导致的一般性问题,可以使用针对性代码进行处理。例如空指针异常、数组角标越界、网络中断、试图读取不存在的文件等。
其中又分为编译时异常和运行时异常(角标越界、除数为0等)。
图构如下:
java.lang.Throwable
异常处理:抓抛模型。
过程一:”抛“:一旦出现异常,则会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象之后,其后的代码不再执行。
过程二:“抓”:可以理解为异常处理方式,有以下两种异常处理方式。
try {
//可能产生的异常的代码区
}catch (ExceptionType1 e) {
//捕获并处理try抛出异常类型为ExceptionType1的异常
}catch (ExceptionType2 e){
//捕获并处理try抛出异常类型为ExceptionType2的异常
}
......
finally{
//无论是出现异常,finally块中的代码都会被执行
}
说明:
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流、网络编程、反射等。