1.面向对象基本概念

1.1.简述

Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。
定义:以基于对象的思维去分析和解决问题,万物皆对象。

通过造自行车例子来解释面向对象的概念:
1、以面向过程的思维造自行车
A先搞轮子,B再搞车架,C再搞链条,D再搞脚踏板…最终拼凑出一辆自行车,但是假如某个部件要调整下,其他相应的部件也要跟着调整,甚至还得重头开始造。所以说面向过程的缺点就是扩展性、维护性差,但是也是有优点的就是速度快
2、以面向对象的思维造自行车
这里的造倒不如说是设计,首先分析一辆自行车所需要的部件,比如轮子,车架,链条,脚踏板等等部件,然后分析和设计出每个部件的尺寸和用料,然后把每个部件分配给指定的人(A,B,C,D…)去做,最后把所有已经完成的部件组装成一辆自行车。这样造出来的自行车,维护性和扩展性都要比面向过程思维造出来的强,但是唯一的缺点就是前期投入的时间比较长。

1.2.面向对象三大特性

封装:把类进行封装,类里有方法,属性等等功能,仅仅对外暴露一些接口来实现模块化,组件化和安全性。
继承:Java中的类可以继承,类似子女可以继承父母的东西,来实现可扩展。
多态:Java中的父类接口可以指向子类实现的引用。

1.3.面向对象开发过程

OOA(面向对象分析):全称Object-Oriented Analysis,是在一个系统的开发过程中进行了系统业务调查以后,按照面向对象的思想来分析问题。
OOD(面向对象设计):全称Object-Oriented Design,是OO方法中一个中间过渡环节,是一种解决软件问题的设计范式。其主要作用是对OOA分析的结果作进一步的规范化整理,以便能够被OOP直接接受。
OOP(面向对象编程):全称Object Oriented Programming,是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。


2.类与对象

2.1.基本概念

类和对象是面向对象组成的基本单位。
对象:对象是类的一个实例,是一种个性的表示,拥有自己独立的属性。
:是抽象概念的集合,是一类对象的共性产物,类之中定义的是属性和方法。

下图中汽车为类(class),而具体的每辆车为该汽车类的对象(object),对象包含了汽车的颜色、品牌、名称等。
面向对象 - 图1

通过概念来理解类与对象太抽象化了,不好区分,总而言之:类是对象的模板,对象是类的实例。类只有通过对象才可以使用,所以应该是先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。

2.2.定义和使用

使用关键字class定义类。
范例:定义一个Car类

  1. public class Car{
  2. private String color;//颜色
  3. private String brand;//品牌
  4. private String name;//名称
  5. .....//getter和setter方法略写
  6. public void tell(){
  7. System.out.println("颜色:"+name+",品牌:"+brand+",名称:"+name);
  8. }
  9. }

类定义完后是无法直接使用的,必须依靠对象来进行操作。类是一个引用类型,引用数据类型与基本数据类型最大的不同在于:引用数据类型需要内存的分配和使用。所以,关键字new的主要功能就是分配内存空间,也就是说,只要使用引用数据类型,就要使用关键字new来分配内存空间。

范例:声明并实例化car对象。

  1. public static void main(String[] args){
  2. Car car = new Car();// 声明并实例化对象
  3. car.setColor("黄色");//操作属性内容
  4. car.setBrand("红旗");//操作属性内容
  5. car.setName("黑武士");//操作属性内容
  6. car.tell();//调用类中的tell()方法
  7. }


3.类和对象的定义格式

3.1.类定义格式

  1. class [类名称]{
  2. [属性]
  3. [方法]
  4. }

3.2.对象定义格式

由于类属于引用数据类型,所以对象的定义格式有两类:1、声明并实例化对象,2先声明再实例。

  1. 1)声明并实例化对象
  2. 类名称 对象名称 = new 类名称();
  3. 2)先声明,再实例
  4. 类名称 对象名称 = null;
  5. 对象名称 = 类名称();


4.对象与内存分析

4.1.简述

上面讲到只要使用new关键字,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。那么对于两种不同的实例化对象,分配的内存有什么区别呢?
首先,要明白内存空间的概念:
(1)堆内存:保存对象的属性内容。
(2)栈内存:保存堆内存的地址。

两种对象实例化内存表示:
面向对象 - 图2
两种方式的区别在于步骤1和步骤2,第一种声明并实例化的方式实际就是步骤1和步骤2组合在一起,而第二种先声明然后实例化是把步骤1和步骤2分步骤来。

4.2.场景模拟

(1)只声明没有实例化进行操作
如果只声明了对象,但并没有实例化对象(只有了栈内存,并没有对应的堆内存空间),则程序在编译的时候不会出现任何的错误,但是在执行操作步骤的时候就会出现“NullPointerException(空指向异常)”这个错误信息。
(2)同一块堆内存空间,同时被多个栈内存所指向,不同的栈修改同一块堆内存的内容

  1. public static void main(String[] args){
  2. Car car1 = new Car();
  3. car1.setColor("黄色");
  4. Car car2 = car1;
  5. car2.setColor("红色");
  6. }

面向对象 - 图3

  1. public static void main(String[] args){
  2. Car car1 = new Car();
  3. Car car2 = new Car();
  4. car1.setColor("黄色");
  5. car2.setColor("红色");
  6. car2 = car1;
  7. car2.setColor("白色");
  8. }

面向对象 - 图4

垃圾:指的是在程序开发之中没有任何对象所指向的一块堆内存空间,这块空间就成为垃圾,所有的垃圾将等待GC(垃圾收集器)不定期的进行回收与空间的释放。


5.封装性

5.1.简述

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问,要访问该类的代码和数据,必须通过严格的接口控制。在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

5.2.问题

当创建一个类的对象以后,可以通过“对象.属性”的方式对对象的属性进行赋值。赋值操作除了受属性的数据类型存储范围的制约,没有其他制约条件。但是,在实际问题中,往往需要给属性赋值加入额外的限制条件,这个条件就不能再属性声明时体现,所以只能通过方法进行限制条件的添加(比如:setxxx()方法)。同时,要避免使用“对象.属性”的方式对属性进行赋值,需要将属性声明为私有的。

5.3.权限修饰符

1635748376(1).jpg
这四种权限可以用来修饰类和类的内部结构:属性、方法、构造器和内部类。对于class的权限修饰只可以用public和default(缺省)进行修饰,其中pubilic类可以在任意地方被访问,default类只可以被同一个包内部的类访问。

5.4.实现封装

将类的属性私有化(private),只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏,同时提供公共的方法来访问(getter和setter方法)。

  1. public class Car{
  2. private String color;//颜色
  3. public String getColor(){
  4. return color;
  5. }
  6. public void setColor(String color){
  7. this.color = color;
  8. }
  9. }

注意:采用 this 关键字是为了解决实例变量(private String color)和局部变量(setColor(String color)中的color变量)之间发生的同名的冲突。

5.5.优点

  1. 良好的封装能够减少耦合,禁止对象之间的不良交互提高模块化;
    2. 类内部的结构可以自由修改,提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展;
    3. 可以对成员变量进行更精确的控制,容易保证类内部数据间的一致性,从而提高软件的可靠性;
    4. 隐藏信息,实现细节,通过隐藏对象的属性来保护对象内部的状态;


    6.构造方法

    6.1.简述

    构造方法(构造函数)是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们,即构造函数的重载。

    6.2.语法规则

    1、方法名必须与类名相同;
    2、无返回值类型,也不能用void修饰(有任何返回值类型的方法都不是构造方法);
    3、可以指定参数,也可以不指定参数,分为有参构造方法和无参构造方法;

    6.3.特点

    1、当没有指定构造方法时,系统会自动添加无参的构造方法
    2、构造方法可以重载:方法名相同,但参数不同的多个方法,调用时会自动根据不同的参数选择相应的方法。
    3、构造方法是不被继承的,在函数的继承里,子类必须调用父类的构造函数。但是,子类只能继承父类的默认构造函数,如果父类没有默认的构造函数,那子类不能从父类继承默认构造函数。这时子类必须使用super来实现对父类的非默认构造函数的调用。
    4、当我们手动的指定了构造方法时,无论是有参的还是无参的,系统都将不会再添加无参的构造方法。默认情况下,一个类会有一个默认的构造函数,这个构造函数没有内容也没有返回值,一般都会略去不写。这种情况下,编译器在编译的时候会默认加上一个无参且方法体为空的构造函数。但是,如果类的构造函数被重写了(类里已经有了一个构造函数),这时编译器就不会再给它默认加上一个无参且方法体为空的构造函数。可以理解为无参的构造函数被覆盖了,这种情况称为没有默认构造函数。
    5、构造方法不但可以给对象的属性赋值,还可以保证给对象的属性赋一个合理的值。构造函数的主要作用是完成对象的初始化工作,它能够把定义对象时的参数传给对象的域。也就是说当创建一个对象时,这个对象就被初始化。如果这时构造函数不为空,则会在创建对象时就执行构造函数里面的代码。
    6、构造方法不能被static、final、synchronized、abstract和native修饰。
  1. public class Car{
  2. private String color;
  3. public Car(){
  4. }
  5. public Car(String color){
  6. this.color = color;
  7. this();
  8. }
  9. //getter、setter方法略
  10. ...
  11. }


7.this关键字

7.1.简述

this关键字指向的是当前对象的引用。所谓当前对象,指的是调用类中方法或属性的那个对象。

7.2.用法

this.[属性名称]

指的是访问类中的成员变量,用来区分成员变量和局部变量(参考5.4章节)。

this.[方法名称]

用来访问本类的成员方法。有些开发者为了“更清楚更明确”,把this放在每一个方法调用和字段引用前,虽然没错,但是会使读你程序的人不知所措,因为编译器能帮你自动添加,所以你没有必要自行添加。我们应该遵循一种一致且直观的编程风格,只在必要处使用this。这就是我们使用高级语言的原因之一,因为它们能帮我们做一些事情。

this()

访问本类的构造方法,()中可以有参数的,如果有参数,就是调用指定的有参构造。
注意事项:
(1)this() 不能使用在普通方法中,只能写在构造方法中。
(2)必须是构造方法中的第一条语句。


8.值传递与引用传递

8.1.值传递

方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。

  1. public class test{
  2. pricate static int x=10;
  3. public static void main(String[] args){
  4. System.out.println("x="+x);
  5. updateValue(x);
  6. System.out.println("x="+x);
  7. }
  8. public static void updateValue(int value){
  9. value = value * 100;
  10. }
  11. }
  12. 打印结果:
  13. x=10
  14. x=10

8.2.引用传递

也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址。在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。
注意的是:要特殊考虑String,以及Integer、Double等几个基本类型包装类,它们都是immutable类型,因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待,可以认为是和基本数据类型相似,传值操作。

  1. public class Car{
  2. private int money;
  3. public Car(){}
  4. public Car(int money){
  5. this.money = money;
  6. }
  7. //省略setter和getter方法
  8. ...
  9. }
  10. public class test{
  11. public static void main(String[] args){
  12. Car car = new Car(10);
  13. System.out.println("x="+car.getMoney());
  14. updateValue(car);
  15. System.out.println("x="+car.getMoney());
  16. }
  17. public static void updateValue(Car car){
  18. car.setMoney(car.getMoney() * 100);
  19. }
  20. }
  21. 打印结果:
  22. x=10
  23. x=1000


9.对象的一对一关系

9.1.简述

对象与对象的关系可以分为三种: 一对一,一对多和多对多。
一对一关系: 分别在两个对象将对方对象设置为属性进行关联。
一对多关系: 在 “一” 关系设置对多关系的集合表示,在 “ 多” 关系将以关系设置为属性。
多对多关系: 分别在两个对象将对方对象作为集合的方式进行关联。
实际开发中一对一和一对多的关系用得最多,多对多关系慎用,逻辑过于复杂,不好处理。通常把多对多的关系进行拆分,设置一个中间对象,拆分为两个一对多的关系进行处理。

9.2.一对一关系

一对一关系就是只实例对象A,就能获取实例B的属性,比如说实例了书本的对象就知道这本书作者的名字等信息。

  1. //书本
  2. public class book{
  3. private String name;//书本名称
  4. private Date date;//创作时间
  5. private Author author;//作者
  6. public book(){}
  7. public book(String name,Date date){
  8. this.name = name;
  9. this.date = date;
  10. }
  11. //setter和getter方法略写
  12. ...
  13. }
  14. //作者
  15. public class Author{
  16. private String name;//名字
  17. public Author(){}
  18. public Author(String name){
  19. this.name = name;
  20. }
  21. //setter和getter方法略写
  22. ...
  23. }
  24. public Class main{
  25. public static void main(String[] args){
  26. Book book = new Book("编程笔记",new Date());
  27. Author author = new Author("Maxteng");
  28. book.setAuthor(author);
  29. System.out.println(book.getName()+"的作者是:"+book.getAuthor().getName());
  30. }
  31. }
  32. //打印结果
  33. 编辑笔记的作者是:Maxteng

而一对多关系的原理和一对一关系一致,一个作者可以有多本书。

  1. public Class Author{
  2. public List<Book> list = Lists.newArrayList();
  3. }


10.static关键字

10.1.简述

static可以用来修饰方法、属性和代码块,在类中,用static声明的成员变量为静态成员变量,也成为类变量,类变量的生命周期和类相同,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。static内部只能出现static变量和其他static方法,static方法不能调用非静态成员,否则会编译报错,而且static方法中还不能使用this等关键字。

10.2.static方法

static方法也称为静态方法,由于静态方法不依赖对象就可以直接访问,所以在静态方法中不能访问类的非静态成员变量和非静态方法,因为非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用,反过来在非静态成员方法中是可以访问静态成员方法和静态成员变量。
在JVM加载类时,static方法就已经存在内存中,不会被虚拟机GC回收掉,内存负荷很大,但是非static方法会在运行完毕后被虚拟机GC掉,减轻内存压力。
最常见的静态方法就是main方法,程序在执行main方法的时候没有创建任何对象,只能通过类名来访问。

  1. public class book{
  2. public static String bookName(){
  3. return "编辑笔记";
  4. }
  5. }

10.3.static变量

static变量也称为静态变量,静态变量被所有对象共享,在内存中只有一个,在类初次加载的时候才会初始化,非静态变量是对象所拥有的,在创建对象的时候被初始化,可以存在多个,并且各个对象互不影响。java语法的规定,static是不允许用来修饰局部变量。

  1. public class book {
  2. private static String name = "编辑笔记";
  3. }

静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问

  1. public class book {
  2. private static String name = "编辑笔记";
  3. public static void main(String[] args){
  4. new book().getName();
  5. }
  6. public void getName(){
  7. String name = "测试打印";
  8. System.out.println(this.name);
  9. }
  10. }
  11. //运行结果
  12. 编辑笔记

10.4.static块

静态初始化块的作用就是提升程序性能。构造方法用于对象的初始化。静态初始化块,用于类的初始化操作。在静态初始化块中不能直接访问非staic成员。

  1. public class book{
  2. private static Date startDate,endDate;
  3. static {
  4. startDate = new Date();
  5. endDate = new Date();
  6. }
  7. }


11.main方法分析

11.1.简述

java虚拟机通过main()方法找到需要启动的运行程序,并且检查main()函数所在类是否被java虚拟机装载。如果没有装载,那么就装载该类,并且装载所有相关的其他类。因此程序在运行的时候,第一个执行的方法就是main()方法。通常情况下, 如果要运行一个类的方法,必须首先实例化出来这个类的一个对象,然后通过”对象名.方法名() “的方式来运行方法,但是因为main()是程序的入口,这时候还没有实例化对象,因此将main方法声明为static的,这样这个方法就可以直接通过“类名.方法名() ”的方式来调用。

11.2.解析

  1. public class test{
  2. public static void main(String[] args) {}
  3. }

(1)调用者是 JVM
(2)关键字 public 表示访问说明符,表明该类是一个公共类,可以控制其他对象对类成员的访问。
(3)关键字 class 用于声明一个类,其后所跟的字符串是类的名称。
(4)关键字 static 表示该方法是一个静态方法,允许调用 main() 方法,无须创建类的实例。保证了方法的加载时机 可以不需要对象就能访问。
(5)关键字 void 表示 main() 方法没有返回值。
(6)main() 方法是所有程序的入口,最先开始执行,方便JVM去使用,因此是固定。
(7)String[] args 表示main方法是可以接收参数的。


12.继承

12.1.简述

继承就是在原有的代码结构上扩充新的功能,继承解决的是代码的重复的现象。把重复的操作(方法和属性)定义到一个公共的类中,之后如果其他类要使用到这些公共的操作只需要继承这个公共的类即可,这个公共的类被叫做父类,有人叫做超类,又叫做基类,继承的叫做子类或者派生类。特别说明,子类不能继承父类的构造方法,一个父类可以有多个子类,但是一个子类只能有一个父类

12.2.语法

  1. public class [子类] extends [父类]{
  2. }

举例说明:

  1. public class book{
  2. private String name;
  3. private Date date;
  4. public book(){}
  5. public book(String name,Date date){
  6. this.name = name;
  7. this.date = date;
  8. }
  9. //setter和getter略写
  10. ...
  11. public void put(){
  12. System.out.println(this.name+"上架了");
  13. }
  14. public void off(){
  15. System.out.println(this.name+"下架了");
  16. }
  17. }
  18. public class biji extends book{
  19. public biji(){super()}
  20. public biji(String name,Date date){
  21. super(name,date)
  22. }
  23. public void test(){
  24. super.put();
  25. super.off();
  26. }
  27. }

12.3.继承类型

面向对象 - 图6
(1)子类拥有父类非 private 的属性、方法。
(2)子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
(3)子类可以用自己的方式实现父类的方法。
(4)Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类。
(5)提高了类之间的耦合性,但是耦合度越高就会造成代码之间的联系越紧密,代码独立性越差。

12.4.关键字

继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。

12.4.1.extends

一个子类只能拥有一个父类,所以 extends 只能继承一个类,相关例子参考12.2。

12.4.2.implements

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

  1. public interface A {
  2. public void hide();
  3. }
  4. public interface B {
  5. public void show();
  6. }
  7. public class C implements A,B {
  8. }


13.对象的初始化

13.1.初始化类

之前提到new 一个类的时候就开始初始化了,我们称之为动态初始化。我们都知道在JVM加载类时,static方法就已经存在内存中,所以首次访问某个类的静态方法或者静态字段时,Java 解释器就会去找类的路径,定位已经编译好的 .class 文件,这个称为静态初始化

13.2.获取类资源

初始化类好后,jvm会载入.class文件,生成一个class对象。这些资源都会放在jvm的方法区,又叫做静态区,跟堆内存一个,可以被所有线程共享。方法区中包含的都是在整个程序中永远唯一的元素,即所有的 class 和 static 变量

13.3.初始化对象

没有父类的情况下初始化对象步骤:
分配存储空间 -> 到其构造函数 -> 创建默认的属性 -> 给它的默认的属性赋值和方法的初始化。

有父类的情况下初始化对象步骤:
分配存储空间 -> 到其构造函数 -> 创建默认的属性 -> 检查是否有父类(直到它“根部”,并且对默认的属性赋值和方法的初始化)-> 子类的构造函数 -> 子类对默认属性和方法分别进行赋值和初始化。

在堆上为对象分配足够的存储空间,所有属性和方法都被设置成默认值(数字为 0,字符为 null,布尔为 false,而所有引用被设置成 null),然后执行构造函数检查是否有父类,如果有父类会先调用父类的构造函数,执行默认值字段的赋值即方法的初始化动作,然后执行子类构造函数并且对默认属性和方法分别进行赋值和初始化。

扩展延伸:
(1)先执行父类的构造方法才继续子类的属性及方法赋值
因为子类的非静态变量和方法的初始化有可能使用到其父类的属性或方法,所以子类构造默认的属性和方法之后不应该进行赋值,而要跳转到父类的构造方法完成父类对象的构造之后,才来对自己的属性和方法进行初始化。这也是为什么子类的构造函数显示调用父类构造函数 super() 时要强制写在第一行的原因,程序需要跳转到父类构造函数完成父类对象的构造后才能执行子类构造函数的余下部分。

(2)对属性和方法初始化之后再执行构造函数其他的部分
因为构造函数中的显式部分有可能使用到对象的属性和方法。其实这种初始化过程都是为了保证后面资源初始化用到的东西前面的已经初始化完毕了


14.方法的重写与重载

14.1.简述

重写(Override)是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变,简单的说就是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法。什么意思呢?举例说明:B从A那里继承了一笔家产,其中有一辆凤凰牌自行车,但是这辆自行车链条坏了,B就买了新链条换上去。更换链条的过程中,就是方法重写。重载(Overload) 是在一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同。还是上面的例子,B除了继承A的凤凰牌自行车外,还购买了一辆携带照明灯凤凰牌自行车。购买自行车的过程中,就是方法重载。方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
面向对象 - 图7

特别注意:重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。

14.2.重写与重载的区别

image.png

14.2.1.重写

(1)参数列表必须完全相同。
(2)返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
(3)被重写的方法访问权限不能比父类的更低。
(4)父类的成员方法只能被它的子类重写。
(5)声明为 final 的方法不能被重写。
(6)声明为 static 的方法不能被重写,但是能够被再次声明。
(7)子类和父类在同一个包中,除了声明为 private 的方法,子类可以重写父类所有方法。
(8)子类和父类不在同一个包中,子类只能重写父类声明为 public 和 protected 的方法。
(9)重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常(例子参考14.1的特别注意)。
(10)构造方法不能被重写。
(11)如果不能继承类,则不能重写该类的方法。

  1. public class A{
  2. public void test(){
  3. System.out.println("方法A");
  4. }
  5. }
  6. public class B extends A{
  7. @Override
  8. public void test(){
  9. System.out.println("方法B");
  10. }
  11. }
  12. public class C{
  13. public static void main(String[] args){
  14. B b = new B();
  15. b.test();
  16. }
  17. }
  18. //运行结果
  19. 方法B

14.2.2.重载

(1)参数列表不一致。
(2)返回类型可以不一致。
(3)访问修饰符可以不一致。
(4)可以声明新的或更广的检查异常。
(5)能够在同一个类中或者在一个子类中被重载。
(6)无法以返回值类型作为重载函数的区分标准。

  1. public class A{
  2. public void test(){
  3. System.out.println("方法");
  4. }
  5. public void test(String value){
  6. System.out.println("方法"+value);
  7. }
  8. }
  9. public class C{
  10. public static void main(String[] args){
  11. A a = new A();
  12. a.test();
  13. a.test("测试");
  14. }
  15. }
  16. //运行结果
  17. 方法
  18. 方法测试


15.super关键字

15.1.简述

super关键字在前面也出现过了,这里做详细说明。
super不是引用,super也不保存内存地址,super也不指向任何对象,super只是代表当前对象内部的那一块父类型的特征(或者说super指代的是一个父类的对象),也简单理解为super相当于在父类中的this关键字,但是有个限制就是只能调用父类中的方法和属性。并且只能用于子类的构造函数和实例方法中,不能用于子类的类(静态)方法中。原因是super(父类对象)在运行时被创建,而静态方法是类方法,它是类的一部分,当类被加载时,静态方法已经存在,但是这时候父类对象还没有被初始化(可以参考13.对象的初始化)。

15.2.语法格式

  1. super.属性名;//访问父类的属性
  2. super.方法名(参数);//调用父类的方法。
  3. super(参数);//调用父类的构造方法,初始化当前对象的父类型特征

15.3.用法

  1. //父类
  2. public class Fu {
  3. int num = 10;
  4. public void method(){
  5. System.out.println("父类方法");
  6. }
  7. }
  8. //子类
  9. public class Zi extends Fu{
  10. int num = 20;
  11. @Override
  12. public void method(){
  13. super.method();//调用了父类方法
  14. System.out.println("子类方法");
  15. }
  16. public void show(){
  17. int num = 30;
  18. System.out.println(num);
  19. System.out.println(this.num);
  20. System.out.println(super.num);
  21. }
  22. }
  23. public class test {
  24. public static void main(String[] args) {
  25. Zi zi = new Zi();
  26. zi.show();
  27. zi.method();
  28. }
  29. }
  30. //运行结果
  31. 30
  32. 20
  33. 10
  34. 父类方法
  35. 子类方法

image.png
子类中有与父类相同的属性或者方法,那么这时如果你想调用父类的属性或者方法,那么你必须使用super关键字。因为子类中和父类中的属性或方法同名,则父类中的方法或属性将被覆盖或隐藏。在Java中,子类是父类的派生类,它的实例化依赖于父类的实例化,所以它的任何一个构造函数都必须要初始化父类,Java就是super关键字调用父类构造方法,来完成这个操作。在上面的例子中没有定义构造函数,那么Java会隐式的为该类定义一个无参构造函数。但是如果开发者自己定一个构造函数(无论有参还是无参),那么Java就不会再为该类隐式的定义一个无参构造函数了。特别说明,在任何情况下构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法,当构造一个子类的对象时,子类构造方法会在完成自己的任务之前,首先调用它的父类的构造方法,如果父类继承自其他类,那么父类构造方法又会在完成自己的任务之前,调用它自己的父类的构造方法,这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止(参考13.对象的初始化)。
总而言之,当一个构造方法第一行既没有this()又没有super()的话,默认会有一个super();表示通过当前子类的构造方法调用父类的无参数构造方法,所以必须保证父类的无参数构造方法是存在的,当然如果父类没有无参构造函数,那么子类中就必须显示的调用super关键字来调用已有的有参构造函数来初始化父类。注意的是构造方法中this ()和super()是不能共存,并且它们都是只能出现在构造方法第一行,因为子类的实例化依赖于父类的实例化,在构建子类时,必须要有父类实例,只能有了父类的实例,子类才能够初始化自己。就好像人类世界里,都是要先有父亲,再有孩子一样。


16.final关键字

16.1.简述

一般来说,只要使用了final关键字声明,那么“这个东西就不能改变”(被final修饰目标不可变),但是由于应用场景不一致,所以会在细微处有所不同。final 关键字的三种应用场景分别为:变量、方法以及类。将变量(成员变量和局部变量)、方法和类声明为final能够提高性能,JVM会对方法、变量及类进行优化。JVM和java应用都会缓存final变量。final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
(1)可以修饰变量(成员变量和局部变量)、方法和类。
(2)final变量不能再次赋值,按照java代码编程规范,final变量就是常量,常量名要大写。
(3)final成员变量必须在声明时赋值或者在构造器中初始化,final局部变量必须在声明时赋值。
(5)匿名类中使用的局部变量必须是final声明。
(6)接口(interface关键字)中声明的变量都是final。
(6)final方法不能被重写。
(7)final类不能被继承。
(8)对于集合类对象声明为final,指的是集合变量的引用不能被修改,但是增删改查不受final的影响。
(9)final与abstract是反相关的,final是不能和abstract共存的。

16.2.final变量

被final修饰的变量(成员变量+局部变量)叫做final变量,final变量不能再次改变其引用。final经常与static一起声明常量。不变指的是引用(地址),而所引用的对象的内容仍然是可变的。也就是说,这个final变量永远指向某个对象,是一个常量指针,而不是指向常量的指针
被final关键字修饰的基本数据类型,则其数值一旦在初始化之后便不能更改。如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,但该引用所指向的对象的内容是可以发生变化的。因为引用数据类型存储的引用对象在堆内存中的地址,final修饰引用类型之后,要求该引用指向的堆内存空间(或者说该引用存储的堆内存地址)不能改变。

  1. public class A {
  2. public String name = "book";
  3. }
  4. public class test{
  5. public static void main(String[] args){
  6. final A a = new A();
  7. a.setName("test")
  8. System.out.println(a.getName())
  9. }
  10. }
  11. //运行结果
  12. test

用final修饰类的非静态成员变量时,成员变量的初始化有两种方式:
1、在声明时进行初始化。
2、在声明变量时不进行初始化,此时该变量被称为blank final,即final空白,但是要在这个变量所在的类的所有的构造方法中对这个变量赋初值。

  1. public class A{
  2. public final String NAME = "book";
  3. public final String COLOR;
  4. public A(){
  5. COLOR = "red";
  6. }
  7. }


用final修饰类的静态成员变量时,静态成员变量的初始化方式也有两种:
1、在声明时进行初始化。
2、在静态初始化块中进行初始化。

  1. public class A{
  2. public static final String NAME = "book";
  3. public static final String COLOR;
  4. static{
  5. COLOR = "red";
  6. }
  7. }


用final修饰接口的静态变量时,其初始化方式只有一种:在声明时进行初始化。
用final修饰局部成员变量时,只需要保证在使用之前被初始化赋值即可。
总之,被final关键字修饰的变量在声明时可以不进行初始化,但必须保证该变量在使用之前被初始化,一旦被初始化赋值之后,就不能再被重新赋值了。

16.3.final方法

被final修饰的方法称为final方法,不能被子类重写。如果你认为一个方法的功能已经足够完善了,子类中无需改变的话你可以使用final修饰此方法。 final关键字修饰方法,它表示该方法不能被覆盖(重写)。另外,类中所有的private方法都隐式地指定为是final的,由于无法在类外使用private方法,所以也就无法覆盖它。此时可以在子类中定义相同的方法名和参数,这种情况不再产生重写与final的矛盾,而是在子类中重新定义了新的方法,可以对private方法添加final修饰符,但并没有添加任何额外意义。
final方法要比普通的方法要快,因为在编译时已经静态绑定了,不需要再运行时动态绑定。将一个方法设成 final 后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final 方法调用,就会忽略执行方法调用机制(将自变量压入堆栈=》跳至方法代码并执行它=》跳回来=》清除堆栈自变量=》最后对返回值进行处理),相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能感受不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java 编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个 final 方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。

16.4.final类

被final修饰的类称为final类,final类通常是完整的,不能被继承。java中有很多类是final类,比如String,Integer及其它包装类。final类的对象一旦被创建就不能被更改了。String类是典型的不可变类的代表。其有很多好处,譬如对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。如果一个类你永远不想让它被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。


17.抽象类

17.1.简述

对于面向对象编程来说,抽象是它的一大特征之一。在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类。这两者有太多相似的地方,又有太多不同的地方。在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。简单的说,普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含有构造方法、普通方法、static方法、常量和变量等内容。而抽象类是指在普通类的结构里面增加抽象方法的组成部分,抽象类不能被实例化,如果被实例化就会报错,编译无法通过,只有抽象类的非抽象子类可以创建对象。那么什么叫抽象方法呢?在所有的普通方法上面都会有一个“{}”,这个表示方法体,有方法体的方法一定可以被对象直接使用。而抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。而拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。抽象类中不一定包含抽象方法(只声明abstract class但是没有抽象方法),但是有抽象方法的类必定是抽象类

17.2.抽象方法

抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。只包含一个方法名,而没有方法体。抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。除此之外,还需注意的是构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
抽象方法的声明格式为:

  1. abstract void [方法名]();

注意的是:继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

17.3.总结

抽象类就是为了继承而存在的,如果定义了一个抽象类,却不去继承它,那么定义这个抽象类毫无意义,因为不能用它来做任何事情。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。除此之外,抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。


18.接口

18.1.简述

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法,除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,它们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。可以说接口是抽象方法和常量值的定义的集合。
从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现

18.2.接口的使用

接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字,每一个方法也是隐式抽象的,声明时同样不需要abstract关键字,并且方法都是公有的。
接口声明的语法格式:

  1. [可见度] interface 接口名称 [extends 其他的接口名] {
  2. // 声明变量
  3. // 抽象方法
  4. }

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。实现接口的语法格式:

  1. ...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

image.png

  1. public interface RunnerStart{
  2. public void start();
  3. }
  4. public interface RunnerStop{
  5. public void stop();
  6. }
  7. public interface Runner extends RunnerStart,RunnerStop{
  8. public void run();
  9. }
  10. public class Person implements Runner{
  11. public void start(){
  12. System.out.println("Person start");
  13. };
  14. public void run(){
  15. System.out.println("Person run");
  16. };
  17. public void stop(){
  18. System.out.println("Person stop");
  19. };
  20. public void dance(){
  21. System.out.println("Person dance");
  22. };
  23. }
  24. public class Car implements Runner{
  25. public void start(){
  26. System.out.println("Car start");
  27. };
  28. public void run(){
  29. System.out.println("Car run");
  30. };
  31. public void stop(){
  32. System.out.println("Car stop");
  33. };
  34. public void fillFuel(){
  35. System.out.println("Car fillFuel");
  36. };
  37. public void crack(){
  38. System.out.println("Car crack");
  39. };
  40. }
  41. public class Bird implements Runner{
  42. public void start(){
  43. System.out.println("Bird start");
  44. };
  45. public void run(){
  46. System.out.println("Bird run");
  47. };
  48. public void stop(){
  49. System.out.println("Bird stop");
  50. };
  51. public void fly(){
  52. System.out.println("Bird fly");
  53. };
  54. }

总之,接口用 interface 来定义。接口中的所有成员变量都默认是由public static final修饰的,接口中的所有方法都默认是由public abstract修饰的,接口没有构造方法,构造方法用于创建对象,实现接口的类中必须提供接口中所有方法的具体实现内容。多个无关的类可以实现同一个接口,一个类可以实现多个无关的接口(参考12.4.2),与继承关系类似,接口与实现类之间存在多态性,接口也可以继承另一个接口,使用extends关键字。


19.多态性

19.1.简述

多态就是指程序中定义的引用变量所指向的具体类型,通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量会指向哪个类的实例对象,由引用变量发出的方法调用在由程序运行期间才能决定具体是哪个类中实现的方法。因为是在程序运行时才确定具体的类,这样不用修改源程序代码就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,总之,同一个事件发生在不同的对象上会产生不同的结果,这就是多态性。简单的说,多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
面向对象 - 图11

19.2.必要条件

(1)继承:在多态中必须存在有继承关系的子类和父类。
(2)重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
(3)父类引用指向子类对象:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。例如:Parent p = new Child();

public abstract class Printer{
    void print();
}
public class BlackAndWhite extends Printer{
    public void print(){
      System.out.println("黑白打印");
  }
  public void copy(){
      System.out.println("黑白复印");
  }
}
public class Color extends Printer{
    public void print(){
      System.out.println("彩色打印");
  }
  public void copy(){
      System.out.println("彩色复印");
  }
}
public class test{
    public static void main(String[] args){
      work(new BlackAndWhite());
    work(new Color());

    Printer c = new Color()
    c.print();
    BlackAndWhite b = (BlackAndWhite)c;
    b.copy();
  }
  public void work(Printer p){
      p.print();
    // 类型判断
    if (p instanceof BlackAndWhite)  {  // 黑白打印 
        BlackAndWhite a = (BlackAndWhite)p;  
        a.copy();  
    } else if (p instanceof Color) { // 彩色打印
        Color a = (Color)p;  
        a.copy();  
    }  
  }
}
//运行结果
黑白打印
黑白复印
彩色打印
彩色复印
彩色打印
黑白复印


20.instanceof关键字

instanceof是Java的一个二元操作符,和==,>,<是同一类。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。java 中的instanceof运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
用法:
result = object instanceof class
参数:
Result:布尔类型。
Object:必选项。任意对象表达式。
Class:必选项。任意已定义的对象类。
说明:
如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。


21.内部类

21.1.简述

将一个类的定义放在另一个类的定义内部,这就是内部类。而根据使用场景的不同,内部类还可以分为四种:成员内部类,局部内部类,匿名内部类和静态内部类。在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整(PS:使用内部类最大的优点就在于它能够非常好的解决多重继承的问题)。(摘自《Think in java》)

public interface Father {

}

public interface Mother {

}

public class Son implements Father, Mother {

}

public class Daughter implements Father{

    class Mother_ implements Mother{

    }
}

21.2.特性

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

21.3.成员内部类

成员内部类是定义在类内部,作为类的成员的类。成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有成员属性和方法,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。如下:

public class OuterClass {
    private String str;
    private String name;

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

    public class InnerClass{
            private String name;
        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...

特点如下:
(1)成员内部类可以被权限修饰符(eg. public、private等)所修饰。
(2)成员内部类可以访问外部类的所有成员,(包括private)成员。
(3)成员内部类是默认包含了一个指向外部类对象的引用。
(4)如同使用this一样,当成员名或方法名发生覆盖时,可以使用外部类的名字加.this指定访问外部类成员。
如:OuterClass.this.name
(5)成员内部类不可以定义static成员
(6)成员内部类创建语法:

OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();

推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 。

21.4.局部内部类

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

//定义在方法里
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");
    }
}
//在作用域内
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();
    }
}

特点如下:
(1)不能有访问权限修饰符。
(1)不能被定义为static类
(1)不能定义static成员
(1)默认包含了外部类对象的引用。
(1)也可以使用OuterClass.this语法制定访问外部类成员。
(1)想要使用方法或域中的变量,该变量必须是final的。
21.5.匿名内部类
匿名内部类是与继承合并在一起的没有名字的内部类。

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:
5

//常用匿名类语法
public class OuterClass{
    public List<String> list = new ArrayList<String>(){
        {
            add("匿名类");
        }
    };
}

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

21.5.静态内部类

前面我们提到,关键字static可以修饰成员变量、方法、代码块,其实它还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类,或者称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:它的创建是不需要依赖于外围类的,并且不能使用任何外围类的非static成员变量和方法。(PS:静态内部类是四种类中唯一一个不包含对外部类对象的引用的内部类。)

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 外围类方法
     * @author chenssy
     * @data 2013-10-25
     * @return void
     */
    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