Java Basics

Java 基础

Java的四大特性

抽象

抽象类和接口是java语言中对抽象类概念进行定义的两种机制,正式由于他们的存在才赋予了java强大的面向对象的能力。他们两者之间对抽象概念的支持有很大的相似,甚至可以互换,但是也有区别。

abstract 抽象类

在面向对象的领域一切都是对象,同时所有的对象都是通过类来描述的,但并不是所有的类都是用来描述对象的

由于抽象的概念在问题领域没有对应的具体概念,所以用以表示抽象概念的抽象类是不能实例化的

同时,抽象类体现了数据抽象的思想,是实现多态的一种机制

同时,抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以定义的抽象类一定是用来继承的在一个用以抽象类为节点的继承关系等级链中,叶子节点一定是具体的实现类

使用抽象类时需注意的几个点:

  1. 抽象类不能实例化,实例化的工作应该交由塔的子类来完成,它只需有一个引用即可。
  2. 抽象方法必须由子类进行重写。
  3. 只要包含一个抽象方法的抽象类,该方法必须要定义成抽象类,不管是否还包含其他方法。
  4. 抽象类中可以包含具体的方法,当然也可以不包含抽象方法。
  5. 子类中抽象方法不能与父类抽象方法同名。
  6. abstract不能与final并列修饰同一个类。
  7. abstract不能与private static final native 并列修饰同一个方法。

实例:

定义一个抽象动物类Animal,提供抽象方法cry()Cat Dog都是动物类的子类,由于cry()为抽象方法,所以Cat Dog 必须实现cry()方法。如下:

public abstract class Aniaml {
	public abstract void cry();
}

public class Cat extends Animal {
	@Override
	public void cry() {
	}
}

public class Dog extends Animal {
	@Override
	public void cry() {
	}
}

抽象类和抽象方法可以使类的抽象性明确起来,并告诉用户和编译器怎样使用它们,抽象类还是有有用的重构器,因为它们使我们可以很容易地将公共方法沿着继承层次结构向上移动。

interface 接口

接口本身不是类,从我们不能实例化一个接口就可以看出。例如:错误的用法new Runnable();

接口是用来建立类与类之间的协议,它提供的只是一种形式,而没有具体的实现。同时实现该接口的实现类必须要实现该接口的所有方法,通过使用implements关键字使用。

通过实现一个接口的实现类,表示该类在遵循某个或某组特定的接口,同时也声明它的工作规范

接口是抽象类的延伸,java为了保证数据安全是不能多重继承的。也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系。所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。

使用接口时需注意的几个点:

  1. interface的所有方法访问权限自动被声明为public
  2. 接口中可以定义成员变量,或者说是不可变的常量,因为接口中的成员变量会自动变成public static final。可以通过类命名直接访问:implements Class.name
  3. 接口中不存在实现的方法。
  4. 实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不实现。
  5. 不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用一个实现该接口的类的对象。可以使用instanceof检查一个对象是否实现了某个特定的接口。
  6. 在实现多接口的时候一定要避免方法名的重复。

抽象类与接口的区别

尽管抽象类和接口之间存在较大的相同点,甚至有时候还可以互换,但这样并不能弥补它们之间的差异之处。

下面将从语法层次和设计层次两个方面对抽象类和接口进行阐述:

语法层次

java对于抽象类和接口分别给出了不同的定义。

public abstract class Demo {
	abstract void method1();
	
	void method2() {
		// 实现
	}	
}

interface Demo {
	void method1();
	void method2();
}

抽象类方式中,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法;但是接口方式中,它仅能够有静态、不能修改的成员数据(但是我们一般是不会在接口中使用成员数据),同时它所有的方法都必须是抽象的

设计层次

抽象类与接口存在的三个不同点:

抽象层次不同 抽象类是对类抽象,而接口是对行为的抽象。 抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

跨域不同 抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。

抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在is-a关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已

设计层次的不同 抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。 对于抽象类而言,它是自下而上来设计的,要先知道子类才能抽象出父类,但接口不同,它不需要知道子类的存在,只需定义一个规则即可。(比如说飞,我们不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口)

例子

// 抽象类
abstract class Door {
	abstract void open();
	abstract void close();
	
	// 增加报警功能
	abstract void alarm();
}

// 接口
interface Door {
	void open();
	void close();

	// 增加报警功能
	void alarm();
}

以上这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segrega Principle)[^Interface Segregation Principle]。

在Door的定义中把Door概念本身固有的行为方法和另外一个概念报警器的行为方法混乱在一起。

这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为报警器这个概念的改变而改变。

依据ISP原则将不同改的的模块分开定义在两个代表不同概念的抽象类里面,定义的方式有三种:

  1. 两个都是用抽象类来定义 X
  2. 两个都使用接口来定义
  3. 一个使用抽象类定义,一个使用接口定义

  4. 由于java不支持多继承所以第一种是不可行的。
  5. 如果使用第二种定义,那么就反映了两个问题:
    1. 对问题域的错误理解:AlarmDoor在概念本质上到底是门还是报警器。
    2. 对问题域的理解:或者AlarmDoor在本质上概念是一致的,那么我们再设计时就没有正确的反映设计的意图。因为使用了两个接口来进行定义,他们概念的定义并不能够反映上述含义。
  6. 对问题域的理解:AlarmDoor本质上是Door,对于这个概念使用抽象类来行医,但同时它拥有报警的行为功能,可以使用接口来进行定义。

方案三

abstract class Door {
	abstract void open();
	abstract void close();
}

interface Alarm {
	void alarm();
}

class AlarmDoor extends Door implements Alarm {
	void open(){}
	void close(){}
	void alarm(){}
}

抽象类在java语言中所表示的是一种继承关系,一个子类只能存在一个父类,但是可以存在多个接口

在抽象类中可以拥有自己的成员变量和非抽象类方法,但是接口中只能存在静态的不可变的成员数据(不过一般都不在接口中定义成员数据),而且它的所有方法都是抽象的

抽象类和接口所反映的设计理念是不同的,抽象类所代表的是is-a的关系,而接口所代表的是like-a的关系。

一个类对另外一个类的依赖性应当是建立在最小的接口上的。 一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。

封装

隐藏具体实现的细节和属性,仅仅对外公开接口。对于程序合理的封装让外部调用更加方便,更加利于写作。同时,对于实现者来说也更加容易修正和改版代码。

封装具有以下优点:

  1. 便于使用者正确、便捷的使用系统,防止使用者错误修改系统属性
  2. 提高软件的复用性
  3. “高内聚,低耦合”

封装的两大原则:

  1. 将不需要对外提供的内容隐藏在内部,仅对外提供便捷使用的接口
  2. 把所有的属性都隐藏在内部,对外提供公共方法对其访问

继承

继承是从已有的类中派生出新的类,新的类能吸收已有类的属性和行为,并能扩展新的能力。比如,我们定义了人类,再定义Boy类时,只需要继承扩展人类即可,实现了代码的重用,不需要重复造轮子(don’t reinvent wheels)。

Java中继承的特点:

  • 被继承的叫父类(parent class)或超类(superclass),继承父类的类叫子类(subclass)或派生类(derivedclass)。
  • 子类继承父类的属性和方法,但父类私有属性和构造方法除外
  • 子类出了拥有从父类继承过来的属性和方法外,还可以拥有属于自己的属性和方法
  • 在Java中只支持单一继承,也就是说一个子类只能有一个父类,但一个父类可以有多个子类

多态

Java作为面向对象的语言,同样可以描述一个事物的多种形态。

Java多态的特点:

  • 多态是同一行为具有多个不同的表现形式或形态的能力;
  • 多态是同一接口,使用不同的实例而执行不同的操作。

Java 关键字

static

static 静态变量

声明static变量实际上声明了全局变量。 当声明一个static变量时,并不产生static变量的拷贝,而是所有的实例共用一个static变量。

static静态方法的用途

static方法就是没有this和super的方法。在static方法内部不能调用非静态方法,反过来可以。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。

方便在没有创建对象的情况下来进行调用(方法/变量)。

由于静态方法不依赖于任何对象就可以进行访问,因此对静态方法来说,它是没有this的,也由于这个特性,在静态方法中不能访问类的非静态成员变量/方法,因为非静态成员方法/变量都是必须依赖具体对象才能够被调用。

final

final关键字的基本用法

final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。

当final修饰一个类时,表明这个类不能被继承。因此一个类不能即被声明为abstract又被声明为final。(final类中所有的成员方法都会被隐式地被指定为final方法,变量同理)

当final修饰一个方法时,表明该方法不被任何继承类修改。 (只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final)

当final修饰一个变量时,如果是基本数据类型的变量,表明该变量一旦在初始化之后便不能更改;如果是引用类型的变量,则再对其初始化之后便不能再让其指向另一个对象。

深入理解final关键字

  1. 类的final变了和普通变量有什么区别? 当用final作用于类的成员变量时,成员变量必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值(局部变量只需要保证在使用之前被初始化赋值即可)。

  2. 被final修饰的引用变量指向的对象内容可变吗? 引用的变量被final修饰之后,不能只想其他对象,但它所指向的对象的内容是可变的。

  3. final和static static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。

  4. 匿名内部类中使用的外部局部变量为什么只能是final变量?
  5. 关于final参数的问题
  6. final finally finalize 之间的区别
  • 定义的区别 final: 修饰属性/方法/类,分别表示属性不可变,方法不可覆盖,类不可继承。 finally: 是异常处理语句结构的一部分,表示总是执行(最后)。 finalize:是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
  • 本质的区别 final:java中的关键字/修饰符。 finally:java中的一种一场处理机制。 finalize:java中的一个方法名。

finally是对java异常处理模型的最佳补充。 finally结构使代码总会执行,而不管有无异常发生。 使用finally可以维护对象的内部状态,并可以清理非内存资源。 特别是在关闭数据库连接这方面,如果把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。

finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

volatile

Java语言是支持多线程的,为了解决线程并发问题,在语言内部引入了synchronizedvolatile关键字机制。

通常,volatile关键字用来阻止(伪)编译器认为的无法“被代码本身”改变的代码(变量/对象)进行优化。

synchronized

通过synchronized修饰的方法或者代码块,在多线程访问的时候,同一时刻只能有一个线程执行该段代码。

super

在Java类中使用super来引用父类的成分,用this来引用当前对象。

this是指当前对象的引用,super是指当前对象的父对象的引用。