Table of Contents
本篇博客是个人对一些常用到的比较零碎的知识的一个大概总结,并会持续的更新内容。
关于java语言设计理念
下面的知识是关于java语言从设计角度出发的一些知识点。
抽象类与接口的区别是什么
首先来看看接口的定义:接口,在JAVA编程语言中是一个抽象类型,主要是抽象方法的集合,接口中的变量定义必须为public static final类型。接口通常以interface来声明。
抽象类: 从面向对象的角度来讲,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样,并不是 所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就可以认为是抽象类。。抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
从定义角度来看,接口和抽象类是两个几乎没有太多联系的设计。接口只是一个抽象方法的集合。而抽象类本质上是一个类,但是它不能被实例化,但是类具备的大多特性抽象类都有。抽象类和interface在Java语言中都是用来进行抽象的,他们除了都是一个用于抽象的东西之外几乎没有任何相同之处。事实上对于一个java里的类来说,无外乎由两种成分组成,即变量和方法(静态代码块可以写在类里面但从实际效果角度并没有影响一个类)。因此接口的功能仅仅包含其中一部分,即方法的集合以及一部分静态成员变量。这样来看,接口是一种非常高的抽象,里面定义的东西被认为是不会改变的。抽象类里面就可以定义普通的成员变量,抽象类的抽象程度相对接口来说会低一点。
但是对于java来说个人认为设计接口的最大原因是为了支持多继承,从这个角度来说,抽象类和接口最重要的区别应该是在使用的时候的区别:
类可以实现多个接口,但是只能继承一个类
其他的区别就非常多了,本质上是一些java语言规则方面的区别。例如:抽象类可以写方法实现,接口也可以写方法实现,不过需要加上default修饰,等等。
问题:什么时候用接口什么时候用抽象类?
抽象类的关键好处在于 能够实现面向对象设计的一个最核心的原则OCP(Open-ClosedPrinciple)。因此当我有一部分内容是不想让子类修改的,但是子类又都通用,同时各个自乐又有自己的特点,那么就适合使用抽象类。
在面向对象领域,抽象类主要用来进行类型隐藏。 我们可以构造出一个固定的一组行为的抽象描 述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个 抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知 道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。
符合开发封闭原则,我可以对抽象出来的类进行扩展,但是只要是这个抽象类的子类,那么他必然能够。
从语法层面上讲,java单继承多实现,而接口可以多实现。
java为什么不支持多继承
典型的支持多继承的语言就是C++。在OOP的世界里,单根继承意味着所有的类都会有一个最上面的终极类,java里面这个类就是Object。单根继承既可以说是一门语言的特性,也可以说是一门语言的一个选择。从纯粹技术的角度来说,java也可以做到多继承,只是如果那样的话那么java就不会再是我们今天所认识的java。除此之外,单根继承还有下面这些优点:
单根继承的优点1:兼容性
单根继承带来的一个效果就是所有的对象归根到底都是相同的基本类型。这带来的好处就是任何java出现的新类库中,兼容性的问题会大大降低,这一点很好理解。但是在C++之中,总是会有一些不兼容的接口,这虽然带来了一定的灵活性,但是对于不兼容的接口,往往就是要通过多继承来解决。
单根继承的优点2: 便利性
因为单根继承,所有的对象都会具备某些一样的功能,比如所有的对象都会有hashcode方法,有euqals方法。因此拿到一个对象时,无论这个对象从哪里来,我们都知道可以对他执行某些基本操作。参数传递也得到了简化。
单根继承的优点3: 垃圾回收
单根继承会使得垃圾回收变得简单很多。因为所有对象都保证具有其类型信息,因此不会因为无法确定类型信息而带来不便。垃圾回收正是java相对于C++的重要改进之一。
java里的枚举实现机制是什么
枚举类型在编译器处理之后,是由一个final的继承Enum类的类实现的。该类是一个实实在在存在的类。在该类当中,编译器还帮助我们生成了每个枚举类型的实例对象,这些对象分别对应枚举中定义的每个枚举类型本身。
java中的内部类
定义:将一个类的定义放在另一个类的定义内部,即为内部类。
内部类本质上是java的一种"语法糖"。为什么这样说呢?举例说明,假设现在有如下代码:
public class A {
private int a;
static class B {
}
class C {
public void test(){
int b = a;
}
}
}
类A是一个普通的类,在他的内部定义了两个类B,以及C。从代码结构上来看,B类和C类为A类的内部,但是在使用编译器编译之后,它们并不是一个类,而是会变成符合一定名称规则的三个类,如下图所示:
它在编译之后会产生三个.class文件,分别是:A.class, A$B.class, A$C.class. 因此,本质上它们还是三个类,只是借助于java编译器的语法糖支持,我们可以写在一个类里面,从这个例子我们不难推断出,在java 里任何一个类,无论是以怎样的形式定义,在编译之后生成字节码文件之后,其必然是一个单独存在的类。理解java的类加载机制的话对这句话理解起来就更加容易,java加载任何一个类的时候都是会首先从加载其class文件开始,若一个类不存在对应的class文件,那么它必然无法被加载也无法被使用。
同时,知道了上述知识之后,我们来看这样两个问题:
- 内部类可以被继承吗?答案是肯定可以的,只是从java语法来说写起来会稍微有点区别
- 内部类的方法可以被覆盖吗?答案一样是可以的。
为何java编译器会支持定义内部类这样的使用方式,原因在于当一个类定义在另一个类内部之后,许多操作会变得简单一些,比如一个内部类可以直接访问外部类的任何成员。为什么内部类能直接访问外部类的任何成员呢?原因在于java编译器对内部类的功能t提供了支持,让我们再来看上述代码反编译回来的结果:
public class A {
private int a;
public A() {
}
class C {
C() {
}
public void test() {
int b = A.this.a;//通过类名加this关键字
}
}
static class B {
B() {
}
}
}
可以看到是通过类名跟上.this关键字实现的对外部成员的访问,这相当于是隐式的持有了一个外部类引用,即建立了一个内部类和外部类之间的联系。
同时,这里我们需要注意这里的B类声明成了static的类,C类则没有。我们常常把用static修饰的内部类成为嵌套类。
他们的区别在于,嵌套类与外围类之间是没有联系的。这意味这创建嵌套类无需外部类,当然,也不能从嵌套类的对象中访问非静态的外围类对象。同时,在嵌套类的内部可以使用static关键字,而普通的内部类不能使用static关键字。
用途:从代码的组织结构来说,使用内部类可以把逻辑相关的类组织在一起。内部类访问外部类将非常方便,内部类能访问外围对象的所有成员,且不需要任何特殊条件。但这不是最主要的原因,从设计角度出发,使用内部类最大的原因在于:每个内部类可以独立但继承一个类,这意味着虽然java的类是单继承的,但是通过使用内部类,可以达到类似多重继承的效果。因此,如果不需要解决多重继承的问题,使用内部类就并不是必须的了,因为其他的编码方式都能实现一样的效果。
关于其他语法糖的介绍可以参考博客:java中的语法糖
关于类Collections,Arrays,Objects
在jdk源代码中提供了很多有用的工具类,它们的命名也有一定的规律。
Collections类提供了很多给容器使用的实用方法。
Arrays类提供了很多给给数组容器有用的方法。例如想创建一个数组对象可以直接调用方法Arrays.asList(...)
Objects类提供了一些给Object类中的实用方法.
这些类都位于jdk的java.util包下面。除了上面提到的三个类以外,util包下面还有许多非常有用且也经常被用到的类和包,例如正则表达式相关的类,基本类型转换的类,以及流编程的类等等,感兴趣的读者可以自行查看。
java里的方法重载
所谓方法重载,指的是当两个方法具有相同的方法名称的时候,他们共存的一种方式。下面是两个重载方法的例子:
public class TestOverloading {
public void method(String s){
}
public void method(int i){
}
}
对于方法名称相同的:
- 参数数量一样,类型不一样,可以重载
- 参数数量不一样可以重载
- 参数数量一样,相同位置类型不一样,可以重载
对于第三种情况,