JAVA基础(一)基础知识总结

JAVA基础知识总结

打算在最近这段时间把java知识系统的复习下,后面分模块总结。

基础

  1. java跨平台原理:不同操作系统下有不同的JVM

  2. 注释:/* …… / 这种注释主要是为支持JDK工具javadoc采用的

  3. JRE::java运行环境,包含JVM和核心类库

    JDK:java开发工具,包含JRE,编译工具(javac.exe),打包工具(jar.exe)等

  4. 运算符

    • 逻辑运算符:

      ^按位异或(t ^f –t,t ^t –f,f^f –f)

      ~按位非 (数字42的二进制:00101010 ——> 11010101)

      &&与&区别:&&是短路,左式为false则不计算右式,&左右两边式子都计算(||同&&)

      》与》>的区别:都是右移,后者无符号右移,空位用0填充

    • 算术运算符:

      +、-、*、/、% 称为二元运算符

      ++、– 称为一元运算符

  5. 局部代码块:{ }内的变量在局部代码块内生效

    定义局部变量的生命周期 —— 性能优化

  6. if和switch区别:对于几个固定的值进行判断,建议使用switch,因为switch语句会将具体的答案都加载进内存,效率相对高一点

  7. do while 特点:不管条件是否满足,循环体至少执行一次

  8. java基本数据类型

类型 位长/b 默认值 取值范围
boolean 1 false true/false
byte 8 0 -128~127
char 16 \u0000(空) \u0000~\uffff(0~65535)
short 16 0 -32768~32767
int 32 0 -2^31~2^31-1
long 64 0 -2^63~2^63-1
float 32 0.0 1.4E-45~3.4028235E38
double 64 0.0 ……
  1. java引用类型

    引用类型是一个对象类型的,它的值是指向内存空间的一个引用,就是地址。基本类型和引用类型的不同处理:

    • 基本类型在声明的时候系统就已经自动给它分配空间

      例:int i; i = 1;

    • 引用类型在声明的时候,只给变量分配了引用空间,数据空间未分配。所以引用类型必须通过实例化开辟数据空间,才能对变量所指向的对象进行访问

      例 :Hexo hexo; hexo = new Hexo();

  2. 变量类型转换

    java数字型变量有低到高自动转换,由高到低需要强制转换

    字节型,短整型,字符型,整形,长整型,单精度实型,双精度实型

    变量与存储器有着直接关系,定义一个变量就是要编译器分配所需要是为内存空间,分配多少空间就是由所定义的变量类型决定的,变量名实际上代表所分配空间的内存首地址

  3. for 和 while 区别:

    • for为了循环而定义的变量,在for循环结束就在内存中释放
    • while循环使用的变量在循环结束后还可以继续使用
  4. 重载:在一个类中,允许存在一个以上的同名函数,参数个数或者参数类型不同即可,与返回值无关,只与参数列表有关

  5. 构造函数:

    • 特点:1. 与类名相同;

      ​ 2.不用定义返回值类型(void也不行);

      ​ 3.没有具体的返回值(return是默认的,可以有)

    • 作用:对象进行初始化才能起作用,new对象时触发的方法,不用调用就能执行,创建对象都必须通过构造函数进行初始化。类中如果没有定义构造函数,则系统会默认一个无参构造函数,如果类中定义了则没有默认构造函数(可以定义默认构造函数)

    • 构造函数与一般函数区别:

      构造函数:对象创建时,只会调用一次

      一般函数:需要调用,可以多次调用

      构造函数可以直接调用一般函数,一般函数不能直接调用构造函数,需要创建对象

    • 构造函数使用场景:在描述事物时,该事物一存在就具备的一些内容可以定义在构造函数中

    • 构造函数重载:函数名相同,参数列表不同(参数顺序不同也是重载)

    • 构造函数与构造代码块区别:

      构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块

      构造函数:是给与之对应的对象进行初始化。它具有针对性。

  6. 字符串:

    • String和StringBuffer的主要性能区别:

      String是不可变的对象,因此每次对String进行改变的时候其实都等于生成了一个新的String对象,然后将指针指向新的String对象,当内存中无引用对象多了以后,JVM的GC就会开始工作,那速度是很慢的;而StringBuffer每次都是对自身对象本身进行操作

    • 而在某些特别情况下,String对象的字符串拼接其实是被JVM解释成StringBuffer对象的拼接,这时候String的效率是比StringBuffer高的。例如:

      String s = “This is” + “ a” + “ pen”

      StringBuffer sb = new StringBuffer(“This is”).append(“ a”).append(“ pen”)

      这是因为在JVM眼里,s其实就是 This is a pen,所以当字符串来自同一对象时String效率高;当字符串来自另外的String对象时,速度就没那么快了,大部分情况:StringBuffer > String

    • StringBuffer与StringBuilder区别:

      HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也是如此,他们的原理和操作基本相同,区别在于StringBuffer支持并发操作,线性安全的,适 合多线程中使用。StringBuilder不支持并发操作,线性不安全的,不适合多线程中使用。新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。

      由此可见,如果我们的程序是在单线程下运行,或者是不必考虑到线程同步问题,我们应该优先使用StringBuilder类;如果要保证线程安全,自然是StringBuffer。

  7. 数组:有序数据的集合,先声明后创建

    • int[] a; 定义数组,并不为数据元素分配空间,因此不用在[]中指出数组的元素个数
    • a=new int[size]; 创建数组,为数组分配空间,同时进行元素的初始化
    • java中二维数组分配空间是第二维可以为空,第一维必须分配内存

内存

内存划分:

  1. 寄存器:CPU处理

  2. 本地方法区:调用不同操作系统的内容

  3. 方法区:类加载方法

  4. 栈内存:存储的都是局部变量,该变量所属的作用域一旦结束,该变量自动释放

    特点:变量生命周期短,更新快

  5. 堆内存:存储对象(凡是new出来的)

    特点:1.每一个实体都有首地址

    2.堆内存中的每一个变量都有默认初始化值,根据类型不同而不同,整数是0,小数是0.0,boolean是false,char是’\u0000’

    3.垃圾回收机制

图解:

int [] arr = new int [3]

arr[0] = 89

syso(arr[0])

arr = null

java内存图解

面向对象

万物皆对象,类 —— 属性、方法,是对事物的描述,对象 —— 类中实在的个体,也称为实例;过程和对象在程序中的体现就是:过程其实就是函数,对象是将函数等一些内容进行了封装。面向对象特点:

  1. 将复杂的事情简单化
  2. 面向对象将以前的过程中的执行者,变成了指挥者
  3. 面向对象这种思想是符合现在人们思考习惯的一种思想
  1. 成员变量和局部变量

    成员变量存在堆内存中,有默认的初始化值,随对象创建而存在,消失而消失;局部变量存在栈内存中,没有默认初始化值,随着所属区域的运行而存在,结束而释放。例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class B{
    int num;
    public static void main(String[] args){
    int num = 10;
    B b = new B();
    b.num = 4;
    system.out.println(num); //num = 10 成员变量随对象存在
    system.out.println(b.num); //num = 4
    }
    }

  2. 匿名对象

    定义对象的简写格式:

    1
    new B().show();

    用法:1. 当对象对方法仅进行一次调用的时候

    ​ 2.匿名对象可以作为实际参数传递 method(new B())

    使用场景:1. 当对方法只进行一次调用的时候,可以使用匿名对象

    ​ 2.当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字

  3. this关键字

    1
    2
    3
    4
    Person(String name, int age){
    this(age); //对this调用必须是构造函数第一行
    this.name = name;
    }
    • 使用场景:

      1. 当成员变量和局部变量重名,用this区分。哪个对象调用了this所在的函数,this就代表哪个对象

      2. 在定义功能时,如果该功能内部使用到了调用该功能的对象。这时就用this来表示这个对象

      3. this可用于在构造函数中调用其他构造函数,只能定义在构造函数第一行,因为初始化动作要先执行

      4. this 还可以用于构造函数间的调用。

        调用格式:this(实际参数);

        this对象后面跟上 . 调用的是成员属性和成员方法(一般方法);

        this对象后面跟上 () 调用的是本类中的对应参数的构造函数。

  4. static关键字

    共享模式,用于修饰成员变量,成员方法

    • 特点:1. 可以被对象调用,也可以直接用类名调用(出现在对象之前,先存在)

      ​ 2.static修饰的成员被所有对象共享(修改)

      ​ 3.static优先于对象存在,因为它随类的加载就已经存在

      ​ 4.static修饰的数据是共享数据,对象中存储的是特有数据

    • 弊端:1.有些数据是对象特有的数据,是不可以被静态修饰的。因为那样的话,特有数据会变成对象的共享数据。

      ​ 2.静态方法只能访问静态成员,不可以访问非静态成员因为静态方法加载时,优先于对象存在,所以没有办 法访问对象中的成员。

      ​ 3.静态方法中不能使用this,super关键字。因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。

  5. 成员变量和静态变量区别

    • 成员变量(实例变量)随对象的创建而存在,随对象回收而释放;静态变量(类变量)随类的加载而存在,随类的消失而消失;两者的生命周期不同。弊端是静态变量生命周期太长

    • 调用方式不同,建议静态变量使用类名调用

    • 数据存储位置不同:成员变量存储在堆中,所以也叫对象的特有数据;静态变量数据存储在方法区(的静态区),共享数据

    • 注意:1. 静态方法不能访问非静态成员(因为静态先存在,非静态还不存在),非静态方法都可以访问

      ​ 2.静态方法中不可以使用this或super关键字(没对象)

      ​ 3.主函数是静态的(用对象调用非静态方法)

    • 使用场景

      1. 静态变量:当分析对象中所具备的成员变量都是相同的(常量),对象不需要修改则不需要存储在对象中,定义成静态的
      2. 静态方法:函数是否用静态修饰,参考该函数是否要访问到对象的特有数据(该功能是否需要访问非静态成员变量),不需要就可以定义成静态的
  6. 静态代码块

    随类的加载而执行(不需要调用),而且只执行一次,作用是给类进行初始化。若该类中的方法是静态的,则该类不需要创建对象,直接用类名调用方法,这种类需要用静态代码块初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class StaticCode{
    static int num;
    static{ //作用类似于封装,可控制属性
    num = 10;
    num += 3;
    }

    { //构造代码块---与对象有关,创建几个对象执行几次,可以给所有对象初始化
    system.out.println("123");
    }
    }

    当类中的方法都是静态的时,所以不需要创建对象,为防止其他类中创建该类的对象,可以将该类的构造方法私有化(构造方法是给对应的对象进行针对性的初始化的)

    1. 创建一个对象都在内存中做了什么事情

      1
      2
      3
      4
      5
      6
      7
      8
      9
      Person p = new Person();
      //1.先将硬盘上指定位置的Person.class文件加载进内存
      //2.执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量p
      //3.在堆内存中开辟一个实体空间,分配了一个内存首地址值new
      //4.在该实体空间中进行属性的空间分配,并进行了默认初始化
      //5.对空间中的属性进行显示初始化
      //6.进行实体的构造代码块初始化
      //7.调用该实体对应的构造函数,进行构造函数初始化
      //8.将首地址赋值给p ,p变量就引用了该实体(指向了该对象)

    2. 类的访问权限

      对于Java中的“类”(不是其内部成员,两者要区分开),其访问权限修饰词仅有public和“无”(即包访问权)两种,而没有private和protected(有 一个特例,就是“内部类”,其可以是private或protected的)。所以对于类的访问权限,你仅有两个选择:包访问权或是public。如果你 不希望其他任何人对该类拥有访问权,你可以把所有的构造器都指定为private,从而阻止任何人创建该类的对象。但是有一个例外,就是在该类的 static成员内部进行创建。如:

      1
      2
      3
      4
      5
      6
      7
      8
      class Soup {
      // private Constructor!
      private Soup() {}
      // Allow creation via static method:
      public static Soup makeSoup() {
      return new Soup();
      }
      }

      如果一个类的访问权限为“包访问权”,并且其内部有一个static的成员为public的话,则其他包中的类仍旧可以访问该static成员,哪怕它们并不能生成该类的对象。

    3. 类之间的三种关系

      依赖关系(uses-a)、聚集关系(has-a)、集成关系(is-a)

封装

隐藏对象的属性及实现细节,封装成set/get方法,对属性可控,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员

封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过 外部接口,一特定的访问权限来使用类的成员

  1. 封装的原则:
  1. 把尽可能多的东西藏起来.对外提供简捷的接口
  2. 把所有的属性藏起来
  3. 封装好处:将变化隔离;便于使用;提高重用性;安全性
  1. private 只能修饰成员,不能修饰局部

继承

对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类),并且java中是单继承(两父类中有相同方法时不知道执行哪个,会产生调用的不确定性),一个子类只能有一个父类继承中的构造方法

当生成子类对象时,Java默认首先调用父类的不带参数的构造方法,然后执行该构造方法,生成父类的对象。接下来,再去调用子类的构造方法,生成子类的对象。【要想生成子类的对象,首先需要生成父类的对象,没有父类对象就没有子类对象。

  1. 方法重写

    • 重写的要求:子类覆盖方法和父类被覆盖方法的方法返回类型,方法名称,参数列表必须相同
    • 子类覆盖方法的访问权限必须大于等于父类的方法的访问权限
    • 方法覆盖只能存在于子类和父类之间
    • 子类覆盖方法不能比父类被覆盖方法抛出更多异常
  2. 方法重写与方法重载

    重载发生在同一个类内部的两个或多个方法。重写发生在父类与子类之间

  3. final关键字在继承中的使用

    • 修饰变量,包括静态的和非静态的

      表示这个变量被赋予的值是不可变的,即它是个常量

    • 修饰对象

      表示这个变量被赋予的引用是不可变的,不可改变的只是这个变量所保存的引用,并不是这个引用所指向的对象

    • 修饰方法

      表示这个方法不可以被子类重写,但是它这不影响它被子类继承

      说明:具有private访问权限的方法也可以增加final修饰,但是由于子类无法继承private方法,因此也无法重写它。编译器在处理private方法时,是按照final方法来对待的,这样可以提高该方法被调用时的效率。不过子类仍然可以定义同父类中的private方法具有同样结构的方法,但是这并不会产生重写的效果,而且它们之间也不存在必然联系

    • 修饰类

      由于final类不允许被继承,编译器在处理时把它的所有方法都当作final的,因此final类比普通类拥有更高的效率。final的类的所有方法都不能被重写,但这并不表示final的类的属性(变量)值也是不可改变的,要想做到final类的属性值不可改变,必须给它增加final修饰

    • 内部类只能访问被final修饰的局部变量

    更贴切的表述final的含义的描述,那就是,如果一个变量或方法参数被final修饰,就表示它只能被赋值一次,但是JAVA虚拟机为变量设定的默认值不记作一次赋值。被final修饰的变量必须被初始化。初始化的方式有以下几种:

    在定义的时候初始化。

    在初始化块中初始化。

    在类的构造器中初始化。

    静态变量也可以在静态初始化块中初始化。

  4. 继承中子父类成员特点

    1. 成员变量:不存在覆盖,有不同的作用域

      1
      2
      this.num = 4; //this代表一个本类对象的引用
      super.num = 5; //super代表一个父类空间
    2. 成员函数:当子父类中出现同样的方法时,会运行子类的方法,称为重写(override)

    3. 重写注意事项

      • 子类权限必须大于等于父类权限
      • 父类方法为private时不叫重写,建议子类起不一样的名字
      • 子父类方法要同时为静态或非静态方法才能重写
    4. 构造函数:无重写,也没继承过来,靠super()

      • 在子类构造对象时,发现父类的构造函数也运行了

        在子类构造函数第一行有一个默认的隐式语句super(),调用的是父类空参数的构造函数(默认)

      • 如果父类中没有定义空参数构造函数,则子类构造函数必须用super明确调用父类的哪一构造函数

      • 子类构造函数中如果使用this调用了本类的构造函数时,super()就没有了,但是子类肯定有构造函数调用父类的

      • 通过super初始化父类内容时,子类的成员变量并未显式初始化(会默认初始化),等super()父类初始化完之后,才进行子类成员变量的显式初始化

  5. 关于继承的几点注意

    • 父类有的,子类也有
    • 父类没有的,子类可以增加
    • 父类有的,子类可以改变
    • 构造方法不能被继承
    • 方法和属性可以被继承
    • 子类的构造方法隐式地调用父类的不带参数的构造方法
    • 当父类没有不带参数的构造方法时,子类需要使用super来显式地调用父类的构造方法,super指的是对父类的引用
    • super关键字必须是构造方法中的第一行语句
    • 内存中父类先进代码区
    • 子类不能继承父类中私有的内容(可以super.getmethod()访问)

多态

多态(Polymorphism):父类型的引用可以指向子类的对象,方法的重写、重载与动态连接构成多态性。

  1. java引入多态的原因

    Java只 允许单继承,派生类与基类间有IS-A的关系,这样做虽然保证了继承关系的简单明了,但是势必在功能上有很大的限制,所以,Java引入了多态性的概念以弥补这点的不足,此外,抽象类和接 口也是解决单继承规定限制的重要手段

  2. 多态前提

    • 必须有关系:继承,实现
    • 要有方法的重写
  3. 类的多态

    在一个类中,可以定义多个同名的方法,只要确定它们的参数个数和类型不同。类的多态体现在两方面:

    • 一是方法的重载上,包括成员方法和构造方法的重载
    • 二是在继承过程中,方法的重写
  4. 对象多态性

    主要是指子类和父类对象的相互转换关系

    • 向上类型转换(upcast):比如说将Cat类型转换为Animal类型,即将子类型转换为父类型。对于向上类型转换,不需要显式指定
    • 向下类型转换(downcast):比如将Animal类型转换为Cat类型。即将父类型转换为子类型。对于向下类型转换,必须要显式指定(必须要使用强制类型转换)
  5. 类型判断

    instanceof:对象类型的判断,只能用于引用数据类型判断,可以是类,可以是接口,通常在向下转型前用于健壮性判断

  6. 多态特点

    1. 成员变量

      1
      2
      FU f = new zi();
      f.num = 3;//父类成员变量

      编译和运行都参考等号的左边(引用型变量所属的类)

    2. 成员函数(非静态)

      • 编译时:参考引用型变量所属的类中是否有调用的函数,有则编译通过,没有则失败
      • 运行时:参考的是对象所的类中是否有调用的函数
      • 简单总结:编译看左,运行看右
    3. 静态函数

      • 编译和运行都看左边
  7. 多态小结

    • Java中除了static、final和private方法外,其他所有的方法都是运行时绑定的,当在派生类中重写基类中static、final、或 private方法时,实质上是创建了一个新的方法
    • 在派生类中,对于基类中的private方法,最好采用不同的名字
    • 包含抽象方法的类叫做抽象类,抽象类在派生中就是作为基类的角色,为不同的子类提供通用的接口
    • 在基类的构造方法中小心调用基类中被重写的方法,这里涉及到对象初始化顺序

抽象

抽象类(abstract class):使用了abstract关键字所修饰的类叫做抽象类。抽象类无法实例化,也就是说,不能new出来一个抽象类的对象(实例)

抽象方法(abstract method):使用abstract关键字所修饰的方法叫做抽象方法。抽象方法需要定义在抽象类中

如果一个类中包含了抽象方法,那么这个类一定要声明成abstract class,也就是说,该类一定是抽象类;反之,如果某个类是抽象类,那么该类既可以包含抽象方法,也可以包含具体方法。

  1. 抽象类特点

    • 抽象类不可以被实例化,因为调用抽象方法没意义
    • 在子类继承父类(父类是个抽象类)的情况下,那么该子类必须要实现父类中所定义的所有抽象方法;否则,该子类需要声明成一个abstract class
  2. 抽象类细节

    • 抽象类中有构造函数,用于给子类对象进行初始化

    • 抽象类可以不定义抽象方法

      目的就是不让该类创建对象,AWT的适配器对象就是这种类,通常这种类中的方法有方法体,但是没有内容

    • abstract关键字不可以和private、static、final组合

    • 抽象类一定是父类(要被使用则子类需要重写其方法)

  3. 抽象类和一般类异同点

    • 相同点:都是用来描述事物
    • 不同点:抽象类描述信息不全;一般类中不能定义抽象方法;抽象类不可以被实例化

接口

接口(interface)中的方法都是抽象方法。java不支持多继承,但可以实现(implements)多个接口,间接的实现了多继承。接口不可以实例化,只能由实现了接口的子类并覆盖了接口的所有抽象方法后,该子类才可以实例化,否则这个子类就是抽象类

  1. 接口中的成员修饰符都是固定的

    • 成员常量:public static final(不写则系统默认),必须被显示初始化
    • 成员函数:public abstract
    • 接口中的成员都是public的
    • 接口中没有构造方法,不能被实例化
  2. 多实现

    一个类可以实现多个接口,因为接口没有方法体,所以不会出现调用的不确定性,实现多实现

  3. 接口的特点

    • 接口是对外暴露的规则
    • 接口是程序的给你扩展
    • 接口降低耦合性
  4. 不允许创建接口的实例(实例化),但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的类的实例

  5. 通过接口可以方便地对已经存在的系统进行自下而上的抽象,对于任意两个类,不管它们是否属于同一个父类,只有它们存在相同的功能,就能从中抽象出一个接口类型。对于已经存在的继承树,可以方便的从类中抽象出新的接口,但从类中抽象出新的抽象类却不那么容易,因此接口更有利于软件系统的维护与重构,对于两 个系统,通过接口交互比通过抽象类交互能获得更好的松耦合

  6. 接口是构建松耦合软件系统的重要法宝,由于接口用于描述系统对外提供的所有服务,因此接口中的成员变量和方法都必须是public类型的,确保外部使用者 能访问它们,接口仅仅描述系统能做什么,但不指明如何去做,所有接口中的方法都是抽象方法,接口不涉及和任何具体实例相关的细节,因此接口没有构造方法,,不能被实例化,没有实例变量

  7. 接口和抽象类的区别

    1. 相同点
      • 都是不断向上抽取而来,提高两个系统之间的松耦合
      • 都不能被实例化
      • 都包含抽象方法,这些抽象方法用于描述系统能提供哪些服务,但不提供具体的实现
    2. 不同点
      • 抽象类需要被继承,而且是单继承;接口需要被实现,可以多实现
      • 抽象类中可以定义抽象方法和非抽象方法,子类继承后可以直接使用非抽象方法;接口只能定义抽象方法,必须由子类实现
      • 抽象类的继承是is-a关系,在定义该体系的基本共性内容;接口的实现是like-a关系,在定义体系额外功能
  8. 使用接口和抽象类的总体原则

    1. 用接口作为系统与外界交互的窗口站在外界使用者(另一个系统)的角度,接口向使用者承诺系统能提供哪些服务,站在系统本身的角度,接口制定系统必须实现哪 些服务,接口是系统中最高层次的抽象类型。通过接口交互可以提高两个系统之间的送耦合系统A通过系统B进行交互,是指系统A访问系统B时,把引用变量声明 为系统B中的接口类型,该引用变量引用系统B中接口的实现类的实例
    2. Java接口本身必须非常稳定,Java接口一旦制定,就不允许随遇更加,否则对外面使用者及系统本身造成影响
    3. 用抽象类来定制系统中的扩展,抽象类来完成部分实现,还要一些功能通过它的子类来实现
  9. 接口总结

    • 在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们,这是抽象类的优势,但这一优势限制了多继承,而接口中只能包含抽象方法。由于在 抽象类中允许加入具体方法,因此扩展抽象类的功能,即向抽象类中添加具体方法,不会对它的子类造成影响,而对于接口,一旦接口被公布,就必须非常稳定,因 为随意在接口中添加抽象方法,会影响到所有的实现类,这些实现类要么实现新增的抽象方法,要么声明为抽象类
    • 为了简化系统结构设计和动态绑定机制,Java语言禁止多重继承。而接口中只有抽象方法,没有实例变量和静态方法,只有接口的实现类才会实现 接口的抽象方法(接口中的抽象方法是通过类来实现的),因此,一个类即使有多个接口,也不会增加Java虚拟机进行动态绑定的复杂度。因为Java虚拟机 永远不会把方法与接口绑定,而只会把方法与它的实现类绑定

内部类

  1. 使用内部类的原因

    • 使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响

    • 接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public interface Father {

      }

      public interface Mother {

      }

      public class Son implements Father, Mother {

      }

      public class Daughter implements Father{

      class Mother_ implements Mother{
      //如果Father、Mother不是接口,而是抽象类或者具体类,这个时候我们就只能使用内部类才能实现多重继承了。
      }
      }

  2. 内部类特性(Think in java)

    • 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立
    • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类
    • 创建内部类对象的时刻并不依赖于外围类对象的创建
    • 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体
    • 内部类提供了更好的封装,除了该外围类,其他类都不能访问
  3. 访问特点

    内部类可以直接访问外部类中的成员,包括私有成员;而外部类要访问内部类中的成员必须建立内部类的对象

    直接访问内部类:

    1
    2
    3
    4
    Outer.Inner in = new Outer().new Inner();
    in.show();
    //引用内部类我们需要指明这个对象的类型:OuterClasName.InnerClassName
    //同时如果我们需要创建某个内部类对象,必须要利用外部类的对象通过.new来创建内部类

    再有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Outer{
    int num = 3;
    class Inner{
    int num = 4;
    void show(){
    int num = 5;
    system.out.println(num); //5
    system.out.println(this.num); //4
    system.out.println(Outer.this.num); //3
    }
    }
    }

    内部类能直接访问外部类中的成员的原因是内部类持有外部类的引用(Outer.this)

    编译成功后产生:Outer.class和Outer$Inner.class两个class文件

  4. 成员内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class OuterClass {
    private String str;

    public void outerDisplay(){
    System.out.println("outerClass...");
    }

    public class InnerClass{
    public void innerDisplay(){
    //使用外围内的属性
    str = "chenssy...";
    System.out.println(str);
    //使用外围内的方法
    outerDisplay();
    }
    }

    /*推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 */
    public InnerClass getInnerClass(){
    return new InnerClass();
    }

    public static void main(String[] args) {
    OuterClass outer = new OuterClass();
    OuterClass.InnerClass inner = outer.getInnerClass();
    inner.innerDisplay();
    }
    }
    --------------------
    chenssy...
    outerClass...
    • 成员内部类中不能存在任何static的变量和方法
    • 成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类
  5. 局部内部类

    它是嵌套在方法和作用域内的,主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

    定义在方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Parcel5 {
    public Destionation destionation(String str){
    class PDestionation implements Destionation{
    private String label;
    private PDestionation(String whereTo){
    label = whereTo;
    }
    public String readLabel(){
    return label;
    }
    }
    return new PDestionation(str);
    }

    public static void main(String[] args) {
    Parcel5 parcel5 = new Parcel5();
    Destionation d = parcel5.destionation("chenssy");
    }
    }

    定义在作用域:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class Parcel6 {
    private void internalTracking(boolean b){
    if(b){
    class TrackingSlip{
    private String id;
    TrackingSlip(String s) {
    id = s;
    }
    String getSlip(){
    return id;
    }
    }
    TrackingSlip ts = new TrackingSlip("chenssy");
    String string = ts.getSlip();
    }
    }

    public void track(){
    internalTracking(true);
    }

    public static void main(String[] args) {
    Parcel6 parcel6 = new Parcel6();
    parcel6.track();
    }
    }
  6. 匿名内部类

    其实就是一个匿名子类对象,例:

    1
    2
    3
    4
    5
    6
    button2.addActionListener(  
    new ActionListener(){
    public void actionPerformed(ActionEvent e) {
    System.out.println("你按了按钮二");
    }
    });

    再有:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class OuterClass {
    public InnerClass getInnerClass(final int num,String str2){
    return new InnerClass(){
    int number = num + 3;
    public int getNumber(){
    return number;
    }
    }; /* 注意:分号不能省 */
    }

    public static void main(String[] args) {
    OuterClass out = new OuterClass();
    InnerClass inner = out.getInnerClass(2, "chenssy");
    System.out.println(inner.getNumber());
    }
    }

    interface InnerClass {
    int getNumber();
    }

    ----------------
    Output:

    注意:

    • 匿名内部类是没有访问修饰符的
    • new 匿名内部类,这个类首先是要存在的。如果我们将那个InnerClass接口注释掉,就会出现编译出错
    • 注意getInnerClass()方法的形参,第一个形参是用final修饰的,而第二个却没有。同时我们也发现第二个形参在匿名内部类中没有使用过,所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final
    • 匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法

    使用场景:

    • 当函数参数是接口类型时而且接口中的方法不超过三个,可以用匿名内部类作为实际参数进行传递
  7. 静态内部类

    1. 静态内部类访问:

      1
      2
      Outer.Inner in = new Outer.Inner();
      //外部类一加载,内部类就已经在内存了,相当一外部类,所以不用创建外部类对象
    2. 如果内部类中有静态方法,内部类也必须是静态的

    3. 静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:

      • 它的创建是不需要依赖于外围类的。
      • 它不能使用任何外围类的非static成员变量和方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    public class OuterClass {
    private String sex;
    public static String name = "chenssy";

    /**
    *静态内部类
    */
    static class InnerClass1{
    /* 在静态内部类中可以存在静态成员 */
    public static String _name1 = "chenssy_static";

    public void display(){
    /*
    * 静态内部类只能访问外围类的静态成员变量和方法
    * 不能访问外围类的非静态成员变量和方法
    */
    System.out.println("OutClass name :" + name);
    }
    }

    /**
    * 非静态内部类
    */
    class InnerClass2{
    /* 非静态内部类中不能存在静态成员 */
    public String _name2 = "chenssy_inner";
    /* 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的 */
    public void display(){
    System.out.println("OuterClass name:" + name);
    }
    }

    /**
    * @desc 外围类方法
    */
    public void display(){
    /* 外围类访问静态内部类:内部类. */
    System.out.println(InnerClass1._name1);
    /* 静态内部类 可以直接创建实例不需要依赖于外围类 */
    new InnerClass1().display();

    /* 非静态内部的创建需要依赖于外围类 */
    OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();
    /* 方位非静态内部类的成员需要使用非静态内部类的实例 */
    System.out.println(inner2._name2);
    inner2.display();
    }

    public static void main(String[] args) {
    OuterClass outer = new OuterClass();
    outer.display();
    }
    }
    ----------------
    Output:
    chenssy_static
    OutClass name :chenssy
    chenssy_inner
    OuterClass name:chenssy

对象的初始化过程

  1. 最开始是默认初始化
  2. 构造器的执行在显示初始化前
  3. 再是构造代码块在构造函数之前