文章目录
1. 面向对象
面向对象编程的特征:抽象、封装、继承、多态。
1.1 封装
我们知道,Java是面向对象的语言,而面向对象设计语言的三大特性之一就是封装。封装的作用就是将对象的属性和方法封印在对象内部,通过对象向外提供的方法操作对象,实现了数据和操作的结合,并将对象的某些数据彻底保护起来,避免了模块间的干扰,达到降低程序复杂度,提高可控性的目的。
1.1.1 访问控制符
Java提供了4种访问控制符,主要用于控制其他类是否可以访问某一类中的属性或方法,从而实现数据封装。四种访问控制符的权限大小(由大到小)为public(公共)、protected(保护)、default(默认)、 private(私有)。
位置 | private | 默认default | protect | public |
---|---|---|---|---|
同一个类(非子类) | 是 | 是 | 是 | 是 |
同一个包内的类 | 是 | 是 | 是 | |
不同包的子类 | 是 | 是 | ||
不同包的非子类 | 是 |
1.2 继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
通过继承能够非常方便地复用以前的代码。
1.2.1 抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
在Java中,抽象类只是一种类的形态,因此在抽象类中也可以实现方法,不过有抽象方法的类一定是抽象类。
public abstract class Test_1 {
private int a;
private int b = 5;
public abstract void setA(int a); // 抽象方法
public void setB(int b){ // 实现方法
this.b = b;
}
}
抽象类的具体使用如下:
- 被抽象类继承时,可以不用实现父类的抽象方法
- 被实现类继承时,必须实现抽象父类的抽象方法
- 实例化抽象类时,必须实现抽象类的方法
1.2.2 接口
接口是Java的一大特性,通过在接口中定义一系列方法,从而交给具体的实现类的实现,这里思考一个问题,为什么在有继承的前提下,Java还实现了接口呢?
原因是有的情况下,可能某个类具有多种特性,举个例子:一个手机,既能够当电视机来使用,也能当收音机,也能当遥控器。所以在有的时候需要继承多个特性来实现一个类。
那么有人会问,为什么不实现多继承呢?反而要提出另一个概念接口呢?
为什么不实现多继承
原因是多继承会导致情况很复杂,下面是著名的钻石问题。
请看上面问题,如果父亲和母亲都对祖父的抽象方法进行了实现或重写,那么儿子到底会使用哪个方法呢?所以为了避免逻辑变得更复杂,Java不支持多级继承,转而实现了接口。
那么要实现的功能类似,但是叫法完全不同,接口和多继承有什么特殊呢?
接口与多继承
在Java中,接口的定义如下:为实现类提供功能上的约束(规定),而不能实现。
因此,我们可以发现接口和抽象类最大的区别是抽象类中可以实现方法,而接口不可以(1.8可以实现默认(default)方法)。
1.2.3 接口与抽象类的区别
语法上
- 抽象类
- 由abstract关键词修饰的类称之为抽象类。
- 抽象类中没有实现的方法称之为抽象方法,也需要加关键字abstract。
- 抽象类中也可以没有抽象方法,比如HttpServlet方法。
- 抽象类中可以有已经实现的方法,可以定义成员变量。
- 接口
- 由interface关键词修饰的称之为接口;
- 接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量。
- 接口中没有已经实现的方法,全部是抽象方法。(1.8后可以实现默认/default的方法)
- 一个类实现某一接口,必须实现接口中定义的所有方法。
- 一个类可以实现多个接口。
设计上
- 抽象层次
- **抽象类是对类的抽象,包括数据、行为,**体现的是一种"is a"的关系,是一种模板设计
- 接口的实现很大程度上定义了某类型的行为,是对行为的抽象,只要实现了接口就可以实现接口的全部行为,体现的是一种"like a" 的关系,是一种行为规范。
- 跨域不同
- 抽象类所跨域的是具有相似特点的类,父类实现的功能子类一般也能实现,两者同属一个物种
- 接口定义的是一种行为,实现接口的子类不存在任何关系,无法发生替换
- 设计层次不同
- 抽象类是自下而上设计的,通过对下层实体进行抽象,得到父类
- 接口是扁平化设计,其不依赖任何现有类,也能作用于任何现有类
1.3 多态
所谓多态通俗来讲就是多种状态,比如跑步这个动作在某些时候是锻炼,某些时候是逃命。在Java中,由于Java是静态类型语言,因此Java中的多态分为两种:编译时多态、运行时多态。
1.3.1 编译时多态
编译时多态主要是指在编译期间所有变量都要指定参数(泛型先不讲),因此可以根据参数、返回值的不同来划分两个同名方法。
重载(overloading)
重载是指在一个类里面,多个方法名字相同,而参数一定不同,返回类型可以相同也可以不同。
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型、可以改变访问修饰符、可以声明不同的异常检查
1.3.2 运行时多态
运行时多态主要是指某个方法或对象在编译时并不确定,只能在程序运行期间才能确定,或者经常改变。比如下面的例子,通过继承父类并重写父类方法,很好的实现了多态。
重写
重写是指父子继承或接口实现时,对继承类或实现接口方法的重新书写(也称覆盖)。
- 名称一定相同,参数列表一定要相同(两同)
- 子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等(两小)
- 子类方法的访问权限应比父类方法的访问权限更大或相等。(一大)
class A{
public void say(){
System.out.println("A");
}
}
class B extends A{
@Override
public void say() {
System.out.println("B");
}
}
class C extends A{
@Override
public void say() {
System.out.println("C");
}
}
class D{
private A a;
public D(A a) {
this.a = a;
}
public void say(){
a.say();
}
}
public class Test_10 {
public static void main(String[] args) {
D d = new D(new B()); // 在程序运行时,传入B则说B,传入C则说C
d.say();
}
}
上面那个例子很好的体现了多态的强大,通过绑定任意的对象,程序可以在运行时动态的改变执行结果,大大加强了程序的可拓展性和可维护性。实际上,多态也可以通过接口实现。
1.3.3 重写和重载的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
2. 类
前面提到,Java是面向对象设计语言,在语法上的体现就是类。类是一个模板,它描述一类对象的行为和状态,对应类中的方法/函数和属性。为了更好的体现对象的特性,Java使用了内部类。
2.1 内部类
将一个类定义在另一个给类里面或者方法里面,这样的类就被称为内部类。而内部类可以分为四种:成员内部类、局部内部类、匿名内部类、静态内部类
2.1.1 成员内部类
下面的成员内部类的实例,称D为内部类,C为外部类。
class C{
private String name = "外部类";
public void run(){
System.out.println("外部类奔跑");
}
class D{
public void say(){
System.out.println(name);
run();
}
}
}
public class Test_10 {
public static void main(String[] args) {
B b = new B();
B.D d = b.new D(); // 外部类对象.new 成员内部类()
}
}
- 成员内部类无条件访问外部类的属性和方法
- 外部类必须通过内部类对象过来访问内部类中的属性和方法
- 如果外部类属性或方法被内部类隐藏(同名)时,可以按照(
B.this.name
)来调用
成员内部类的访问权限
成员内部类前可加上四种访问修饰符。
- private:仅外部类可访问。
- protected:同包下或继承类可访问。
- default:同包下可访问。
- public:所有类可访问。
2.1.2 局部内部类
局部内部类存在于方法中。他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
public class B {
private int b = 2;
public void say(){
class C{
private int a = 0;
public C(int a) {
this.a = a;
}
class D{}
}
}
}
- 局部内部类就像局部变量一样,前面不能访问修饰符以及static修饰符。
- 局部内部类只在当前方法中有效
- Java的局部内部类中不能定义 static 成员。
- 局部内部类中也能包含内部类,但这些内部类不能使用访问控制符
- 在Java的局部内部类中可以访问外部类的所有成员。
- 在局部内部类中只可以访问当前方法中 final 类型的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用 .this. 的形式访问外部类中的成员。
局部内部类一般并不常见,了解一下即可。
2.1.3 匿名内部类
匿名内部类就是内部类的简化写法,前提是存在一个类或接口。
public class B {
public static void main(String[] args) {
new Thread(new Runnable() { // 通过接口
@Override
public void run() {
System.out.println(111);
}
}).start();
}
}
public class B {
public static void main(String[] args) {
say(new Car() { // 通过抽象类
@Override
void drive() {
}
});
}
}
abstract class Car{
abstract void drive();
}
我们最常见的匿名内部类可能就是创建线程实现Runnable
了。
2.1.4 静态内部类
静态内部类和成员内部类相比多了一个static修饰符。它与类的静态成员变量一般,是不依赖于外部类的。
同时静态内部类也有它的特殊性,因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。
public class B {
private int b = 2;
static class C{
}
}
public class Test_10 {
public static void main(String[] args) {
B b = new B();
B.C c = new B.C(); // new 外部类.静态内部类()
}
}
2.2 枚举类
关键词enum可以将一组具名值的有限集合创建成一种新的类型,而这些具名的值可以作为常规程序组件使用。
枚举最常见的用途便是替换常量定义,为其增添类型约束,完成编译时类型验证。
以这种方式定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为。
2.2.1 枚举的特性
枚举值具有单例性,及枚举中的每个值都是一个单例对象,可以直接使用==进行等值判断。枚举是定义单例对象最简单的方法。
枚举定义单例模式
public enum Singleton{
//定义1个枚举的元素,即为单例类的1个实例
INSTANCE;
// 隐藏了1个空的、私有的 构造方法
// private Singleton () {}
}
// 获取单例的方式:
Singleton singleton = Singleton.INSTANCE;
2.2.2 枚举的本质
创建enum时,编译器会为你生成一个相关的类,这个类继承自java.lang.Enum。