目录
- ==本文声明==
- 一、初识java
- 二、类
- 2.1类含义的解释
- 2.2对象
- 2.3 Java 面向对象编程三大特性:封装、继承、多态
- 2.4 Java基本类型
- 2.5访问修饰符
- 2.6成员变量与局部变量
- 2.7字符串与字符的区别
- 2.8类的构造方法
- 2.9静态变量和静态方法
- 2.10成员方法
- 2.11可变参数
- 2.12重写(Override)
- 2.13重载(Overload)
- 2.14重写和重载区别
- 2.15包
- 2.16 this指针
- 2.17 super关键字
- 2.18 super和this的区别
- 2.19 Object类
- 2.20对象类型的转型
- 2.21 final关键字
- 2.22面向对象编程特性(详细)
- 2.23代码块
- 2.24类什么时候被加载?
- 2.25对象创建时代码块构造方法调用顺序
- 2.26抽象类与接口
- 2.27内部类
本文声明
刚刚学习完Java面向对象部分同时由于写者水平有限,如有缺陷或错误欢迎指正;
一、初识java
1.1程序
计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合;
1.2 Java平台的三个版本
①JavaSE:即java标准版,主要用于开发和部署桌面。
②JavaEE:即Java企业版,主要针对企业应用的开发。
③JavaME:即Java微型版,主要针对移动设备和嵌入式设备。
1.3 Java特点
①Java语言是面向对象的语言(Object Oriented Programming)
②Java语言是跨平台性
③Java语言是健壮性的。
④Java语言是解释型的(c/c++是编译型的)
1.4 JRE基本介绍
① JRE(Java Runtime Environment)是Java的运行环境;
JRE = JVM + Java的核心类库
② 包括了Java虚拟机和Java程序所需要的核心类库等
因此运行Java程序只需要安装JRE即可
1.5 JDK基本介绍
①JDK(Java Development Kit):Java开发工具包 JDK = JRE + java开发工具
②JDK是提供给Java开发人员使用的,包含了Java开发工具也包含了JRE
1.6 JVM基本介绍
①JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器;
②JVM负责将字节码转换为特定机器代码(不同平台的运行代码)。
③Java虚拟机机制屏蔽了底层运行平台的差别从而保证Java程序跨平台性;
1.7什么是字节码?
①了解字节码前需要知道java文件类型:
扩展名为java,编译之前是纯文本文件,用来存放Java源代码。
扩展名为class,编译之后生成用来存储字节码的二进制文件。
②为什么用字节码?
可以实现一次编译到处运行,也就是与运行的平台无关,它依靠不同平台的JVM虚拟机将字节码解释为特定平台的机器指令并且执行。
1.8 Java内存结构(简单版)
①栈区:一般存放基本数据类型和局部变量等。
②堆区:存放对象、数组等
③方法区:常量池(常量,比如:字符串等),类加载信息等。
1.9递归
①执行一个方法时,就创建新的受保护的独立空间
②方法的局部变量是独立的,不会相互影响
③如果方法中使用的是引用类型变量(数组,对象等)就会共享该引用类型 的数据
④递归必须向退出递归的条件逼近,否则无限递归(栈溢出)
⑤当一个方法执行完毕遇到return,就会返回同时遵守谁调用返回给谁
二、类
2.1类含义的解释
①类就是一类事物的统称,如果将现实世界某一事物抽象成对象,类就是这类对象的统称。例如:鸟类、鱼类、人类等等。基本上所有的鸟具有“翅膀”可以飞行,这样具有相同特性和行为的一类事物称其为类。
②类是封装对象的属性和行为的载体。
2.2对象
上面讲了类,而对象则是类抽象出来的一个实例。
2.3 Java 面向对象编程三大特性:封装、继承、多态
①封装
- 封装是面向对象编程的核心思想。将对象的属性和行为封装起来,其实现的载体是类,类通常会对外部隐藏其实现的细节,这就是封装的思想。
- 只需向外提供接口供外部使用内部实现使用者无需了解。
②继承
继承是使用已有类作为新类的基础,新类可以添加新的成员(包含数据成员、方法或者重写父类方法)同时也可以使用父类的成员。通过使用继承我们能够方便地复用以前的代码从而提高开发效率。子类全部继承了父类的属性和方法但受到访问限制(访问修饰符)。
注:父类与子类只是相对的。
③多态
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量调用的方法在编程时并不确定的,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量调用的方法调用到底是哪个类中的方法,必须在由程序运行期间才能决定。
2.4 Java基本类型
①Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
注:Java中char类型占两个字节
②引用类型
类、接口类型、数组类型、枚举类型、注解类型。
③主要区别
基本数据类型在被创建时,在栈上给其分一块内存,将数值直接存储在栈上。而引用类型在栈上创建空间存放堆上返回的地址。
2.5访问修饰符
注:默认等于不写访问修饰符
2.6成员变量与局部变量
class Test01 {
private String name;
public int i;
protected char j;
boolean k;
}
成员变量
①定义: [访问修饰符] 类型 标识符(名字);
②属性定义类型可以为任意类型,包含基本类型和引用类型
class Test01 {
private int a;//成员变量
public void cry() {
int b;//方法内的局部变量
}
{//代码块内的局部变量
int c;
}
}
局部变量
顾名思义:变量定义在程序的局部位置。例如:代码块内,方法内(c++称为函数)
2.6.1局部变量与成员变量区别
①语法层面:成员变量属于类的,而局部变量是在代码块和方法内。成员变量可以被访问修饰符所修饰及static修饰,而局部变量不能被访问修饰符所修饰同时也不能被static所修饰。但局部变量和成员变量可以被final修饰。
②生命周期:成员变量是对象的属性所以它和创建它的对象伴生,而局部变量随着方法调用的结束而被销毁,代码块执行结束也被销毁。
③存储层面:因为成员变量是对象的属性,而对象存在于堆中。局部变量存在于栈内存中
④初始化问题:成员变量没有赋初值则是默认值,如果被final修饰必须显式赋值。而局部变量不会隐式赋值。
2.7字符串与字符的区别
char ch = 'A';
String str01 = "";//可以是空串
String str02 = "哈喽";
①从定义上看:字符常量是单引号里面只有一个字符,而字符串常量是双引号里面可以有若干个字符。
②从内容看:字符常量其实相当于一个整形数字(ASCII码值)所以它可以参加表达式的运算,而字符串常量则代表的是一个地址,该字符串在内存中的存放位置。
2.8类的构造方法
①构造方法的定义语法格式如下:
class F1 {
public F1() {
//构造方法体
}
}
- public:构造器访问修饰符。也可以是private、protected
- F1:构造方法名。
②构造方法特点:
- 构造方法没有返回值。
- 构造方法的名称要与本类的名称相同。
注:如果在类中定义的构造方法都不是无参的构造方法,那么编译器也不会为类提供一个无参的构造方法。所以只有类中无构造方法时编译器才会给类提供一个无参的构造方法。
2.9静态变量和静态方法
①因为由static关键字修饰的变量和方法被称为静态变量或静态方法。
②静态变量
- 语法定义:
[访问修饰符] [static] [数据类型] [变量名] ;(推荐定义方式)
public static int a;
[static] [访问修饰符] [数据类型] [变量名] ;
static public int a;
- 访问方式:
类名.变量名;(推荐)
对象名.变量名;
- 静态变量特性:
1.可以做到共享数据(访问权限和普通变量一样)。
2.在类加载的时候就已经生成(随着类的销毁而消亡)。
③静态方法
- 语法:
[访问修饰符] [static] [数据返回类型] [方法名]() {
}
public static void cry() {
//statement;方法体
}
- 调用方式:
类名.方法名
对象名.方法名
- 使用场景:
当方法不涉及到任何和对象相关的成员,则可以将方法设计成静态方法。
- 注意:
<1>静态方法和普通方法都伴随类的加载而加载,将信息存储于方法。
<2>静态方法中不允许使用和对象有关的关键字,例如:this和super;静态方法中无这些参数(普通方法中隐含this等一些参数)。
<3>静态方法中只能访问静态变量和静态方法。非静态方法既可以访问静态的也可以访问非静态的。
2.9.1 main方法
public static void main(String[] args) {
}
①main方法是虚拟机调用,因为调用着和main方法不在一个包中所以使用public修饰。
②Java虚拟机在执行main()方法时不必创建对象,所以该方法必须被修饰为static
③该方法接收String类型的数组参数,该数组中保存执行Java命令时传递给运行的类参数。
④静态方法main要访问本类的非静态成员,先创建实例后才能通过对象访问。
2.10成员方法
①定义成员方法的语法格式如下:
访问修饰符 返回值类型 方法名(参数类型 参数名){
statement;//方法体
return 返回值;(有时没有返回值)
}
成员方法中参数可以是基本类型也可以是引用类型。要使成员方法无返回值可以使用void关键字。
②成员方法调用细节
举例:Person是一个类。
- 先加载Person类信息(属性和方法信息,只会加载一次)
- 在堆中分配空间,进行默认初始化(new Person()语句)
- 把堆中地址赋值给P,P指向对象,P是局部变量所以被创建于栈上
- 进行指定初始化,例如构造器给数据成员赋值
注:默认初始化是对数据成员进行赋值,具体默认值是什么请参照Java基本类型中的默认值然后才会对数据成员特定赋值(用户给定的值)。
2.11可变参数
①概念:Java允许将同一个类中的多个同名同功能但参数个数不同的方法,封装成一个方法,可以通过可变参数实现。
②基本语法:
/*
语法:[访问修饰符] [返回类型] 方法名(数据类型...形参名){
//方法体
}
*/
public int Variable(int... nums) {//求和
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
- int…表示接收的是可变参数,类型为int,即可以接收多个int(0到N)
- 使用可变参数时可以当做数组使用,即nums数组
- 它的本质是数组
- 可变参数可以和普通类型的参数一起放在形参列表但必须保证可变参数是最后一个参数同时列表中只能出现一个参编参数
注:编译器是对应赋值所以普通参数必须放在可变参数前边
2.12重写(Override)
①重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
②重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
③重写规则(重写是建立在继承的基础之上)
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类。
- 访问权限不能比父类中被重写的方法的访问权限更低。(只能升不能降)
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 构造方法不能被重写。
class F1 {
public void show(){
System.out.println("F1的show方法");
}
}
class F2 extends F1{
@Override
public void show() {//重写了F1类的show方法
System.out.println("F2的show方法");
}
}
2.13重载(Overload)
①重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
②重载规则
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 方法能够在同一个类中或者在一个子类中被重载;
- 无法以返回值类型作为重载函数的区分标准;
2.14重写和重载区别
①方法的重写和重载是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
2.15包
①包的三大作用
- 区分相同类名字的类
- 当很多类时,可以很好的管理类
- 控制访问范围
②包的基本语法:package com.Test;
- package关键字表示打包
- com.Test表示包名
③包的本质:实际上就是创建不同的文件夹来保存类文件
//实例
{
//本类的Dog类
Dog dog01 = new Dog();
//别的包的Dog类
com.Test.Dog dog02 = new com.Test.Dog();
}
④包的命名
- 只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
- 命名规范:一般是小写字母和小圆点(com.公司名.项目名.业务模块名)
⑤常用包
- Java.lang是基本包,默认引入不需要再引入
- Java.util系统提供的工具包(工具类)
- Java.net网络包(网络开发)
- Java.awt是做java的界面开发GUI
注意:
- package的作用是声明当前类所在包,需要放在类(或文件)最上面,一个类中最多只有一句package
- import关键字,放在package
2.16 this指针
①Java语言中规定使用this关键字来代表本类对象的引用,this关键字被隐式地用于引用对象的成员变量与方法。Java虚拟机会给每个对象分配this代表当前对象。
类可以实例化多个对象,编译器是如何正确调用合适对象的方法和属性?
在通过对象名调用方法时会隐式传入对象地址所以this可以指向正确对象
public void setName(String name){
this.name = name;
}
②this关键字可以用来访问本类的属性、方法、构造器
访问属性语法:this.属性名;
访问方法语法:this.方法名(参数列表);
访问构造器语法:this(参数列表); 注意只能在构造器中使用(一个构造器访问另外一个构造器且语句必须放在第一句)
③this作为返回值
public Person getPerson(){
return this;//this指向对象即this存着对象的地址
}
④this区分当前类的属性和局部变量
在方法中如果传入的参数和数据成员重名即可显式使用this指针指向数据成员。如果不使用this指针则name = name把参数name赋给参数name。
⑤this关键字只能在方法内使用
2.17 super关键字
①基本介绍:super代表父类的引用,用于访问父类的属性、方法、构造器。
3. 访问父类属性和方法:不能访问父类的private属性和方法。
4. 访问父类构造器:super(参数);只能放在构造器第一句话且只能出现一次。
②细节
- 调用父类的构造器的好处:分工明确、父类属性由父类构造器初始化、子类属性由子类构造器初始化。
- 当子类中有和父类中成员(属性和方法)重名时,为了能访问父类的成员必须通过super。如果没有重名使用super和this直接访问是一样的效果。
- super的访问不限于直接父类,如果上级类和本类中有同名的成员,也可以使用super去访问父类的成员,super访问遵循就近原则。
2.18 super和this的区别
区别 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类查找 | 从父类开始查找 |
调用方法 | 先本类后父类 | 从父类开始 |
调用构造器 | 调用本类构造器必须放在构造器内首行 | 调用父类构造器必须放在构造器内首行 |
特殊性 | 表示当前对象 | 子类访问父类对象 |
2.19 Object类
详细讲述Object类中几个重要的方法。
①getClass()方法
概念:getClass()方法时Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称。语法如下:
getClass().getname();
②toString()方法
基本介绍:toString()方法功能是将一个对象返回为字符串形式。
默认返回:全类名+@+哈希值的十六进制。子类往往重写toString方法
注:全限定名 = 包名+类型。
③equals()方法
在Java语言中,有两种比较对象的方式,分别为“==”运算符和equals()方法。
- ==和equals的对比
“ ==”是一个比较运算符。
“ ==”即可以判断基本类型,又可以判断引用类型,如果判断基本类型,判断的是值是否相等。如果判断引用类型,判断的是地址是否相等。 - equals是Object类中的方法,只能判断引用类型。
- equals默认判断的是地址相等,子类中往往重写该方法,用于判断内容是否相等。
④finalize()方法
- 概念:当对象被收回时,系统自动调用该对象的finalize方法,子类可以重写该方法做一些释放资源的操作。
- 什么时候被回收:当某个对象没有任何引用时,JVM就认为该对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前会先调用finalize方法
- 垃圾回收机制的调用是由系统决定的。(GC算法)
2.20对象类型的转型
2.20.1向上转型
①概念:向上转型可以被理解为将子类类型对象转换为父类类型的对象。即把子类类型的对象直接赋值给父类类型的对象。
②本质:父类的引用指向子类对象
③语法:父类类型 引用名 = new 子类类型();
④特点:
- 可以调用父类中的所有成员(需要受访问权限限制)。
- 不能调用子类特有成员(编译器阶段调用成员,是由编译类型决定的)。
- 最终运行效果看子类实现(子类重写了父类方法,没有重写向上找父类的方法)。
2.20.2向下转型
①概念:向下转型可以被理解为将父类类型对象转换为子类类型的对象。即把父类类型的对象直接赋值给子类类型的对象。
②语法:子类类型 引用名 = (子类类型)父类引用;
③注意:
- 只能强转父类的引用,不能强转父类对象。
- 要求父类的引用必须指向的是当前目标类型的对象(父类引用必须指向需要转的类型)。
- 可以调用子类类型中所有成员(需要受访问权限限制)。
2.21 final关键字
2.21.1 final变量
final关键字可用于变量声明,一旦该变量被设定,就不可以改变该变量的值。通常,由final定义的变量为常量。
final double PI = 3.14;
当程序中使用到PI这个常量,它的值就是3.14。如果在程序中再次对定义为final的常量赋值,编译器将不会接受。一旦一个对象的引用被修饰为final后,它就只能恒定指向一个对象,无法将其改变指向其他对象。一个既是static又是final的字段只能占据不能改变的存储空间。
2.21.2 final方法
将方法定义为final类型,可以防止子类修改父类的定义与实现方式,同时定义为final的方法的执行效率高于非final方法。一个定义为private访问权限的方法隐式被指定为final类型。
private final void cry() {
//程序代码;
}
2.21.3 final类
定义为final的类不能被继承。语法如下:
final 类名{}
如果将某个类设置为final类,则该类中的所有方法都将被隐式设置为final方法,但是final中成员变量可以被定义为final或非final形式。
2.21.4 final总结
①在这些情况下可能有需求:
- 当不希望类被继承时,可以用final修饰。
- 当不希望父类的某个方法被子类覆盖/重写,可用final修饰。
- 当不希望类的某个属性的值被修改,可以用final修饰。
- 当不希望某个局部变量被修改,可以用final修饰。
②注意事项和细节
- final修饰的属性又叫常量,一般用xx_xx来命名。(单词大写,单词之间用下划线)
- final修饰的属性在定义时,必须赋初值并且以后不能修改,赋值可以在如下位置之一:
<1>定义时:public final double TAX_PATE=0.08;
<2>在构造器中和代码块中赋值,定义可以在类内只在构造和代码块内赋值。
class F1 {
final double A = 0.07;//定义时赋初值
final double D;
final double G;
public F1() {
D = 0.08; //在构造器内赋值
}
{
G = 0.09;//在代码块内赋值
}
}
③如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时。
- 静态代码块内。
④final类不能继承,但可以实例化对象。
⑤final不修饰构造方法
⑥final和static往往搭配使用,效率更高,不会导致类加载(底层编译器已经优化处理了)
⑦有些包装类也是被final修饰的。
2.22面向对象编程特性(详细)
2.22.1继承
①继承细节
- 子类继承了父类的所有属性和方法,但是私有属性和方法不能在子类直接访问,可以通过公共的方法访问。不能直接访问是因为访问机制的限制(访问修饰符)。
- 子类必须调用父类的构造器,完成对父类的初始化。
- 当创作子类对象时,不管使用子类的那个构造器,默认情况下总会去调用父类的无参构造器。如果父类没有提供的无参构造器,则必须在子类的构造器中显式用super去调用父类中指定的构造器完成对父类的初始化,否则编译器不会通过。super(参数)通过参数类型和参数个数具体调用那个构造器。
- super在使用时,必须放在构造器第一行且只能在构造器中使用。
- super()和this()都要放在构造器的第一行,因此这两种方法不可同时使用于同一构造器中。this()是调用本类的其他构造器而super()调用父类的构造器。
- Java所有类都是Object类的子类,所以Object类是所有的基类。
- 父类构造器的调用不限于直接父类,一直往上找父类直到Object类(最上层的类)
- 由于Java是单继承的,所以子类只能直接继承一个父类。
- 不能滥用继承,子类和父类间必须符合一定的逻辑关系。
②子类访问某属性
- 首先看本类是否有该属性。
- 如果本类有这个属性,并且可以访问则使用(调用)属性(方法)。
- 如果子类没有这个属性,则往上(指父类)依次找父类有没有该属性;如果有该属性且可以访问则返回信息。
注:在查找过程中,如果遇到第一个该属性但是不能访问(私有属性)则停止访问,编译器不会跳过这个属性去找第二个相同的属性(同名和同属性)只会找到第一个该属性
//子类访问某属性
class F1 {
private int f1_a = 10;
public int b = 100;
public int f_c = 1000;
}
class F2 extends F1 {
private int f2_a = 20;
public int b = 200;
private int f_c = 2000;
}
class F3 extends F2 {
public void show() {
//f1_a是F1的私有属性所以无法直接访问
//System.out.println(f1_a);
//只会访问F2类的b而不会访问F1类里的b。
System.out.println(b);
//编译器只会找到第一个f_c属性
//如果无法访问则报错而不会越过F2的f_c去访问F1的f_c属性
//System.out.println(f_c);
}
}
public class Test {
public static void main(String[] args) {
F3 f3 = new F3();
f3.show();//入口
}
}
创建F3之前会往上查找父类直到Object类(或其他类)停止,然后从Object类开始初始化直到F3类。
2.22.2多态
①基本介绍:方法或对象具有多种形态,多态是建立在封装的基础之上。
②具体体现:
- 方法的多态:重写和重载就体现多态。
- 对象的多态:例 Animal animal = new dog();
<1>一个对象的编译类型和运行类型可以不一致。
<2>编译类型在定义对象时,就确定了不能改变。
<3>运行类型是可以变化的。
<4>编译类型看定义时“=”的左边,运行类型看“=”的右边。 - 多态的前提:两个对象(类)存在继承关系。
③注意:
- 属性没有重写之说,属性的值看编译类型。
- instanceof比较操作符,用于判断对象的运行类型是否为xx类型或xx类型的子类型。
public class Test {
public static void main(String[] args) {
People people = new Teacher();
System.out.println(people.a);//属性的值看编译类型(people)
}
}
class People {
public int a = 10;
}
class Teacher extends People {
public int a = 20;
}
④多态应用
- 多态数组:数组的定义类型为父类类型,里面保存的实际元素为父类类型和子类类型。
- 多态参数:方法定义的形参类型为父类类型,实参允许为子类类型。
2.22.3 Java的动态绑定机制
①内容:
- 当调用对象方法时候,该方法会和该对象的内存地址(运行类型)绑定。
- 当调用对象属性的时,没有动态绑定机制,哪里声明那里使用。
2.23代码块
①代码块分为普通代码块和静态代码块。
- 普通代码块的定义:封装在一对花括号之间
{
statement;//内容
}
在创建对象实例时,会被隐式的调用(被创建一次,就会调用一次)。如果只是通过类名访问静态成员时,普通代码块不会执行。类先加载所以静态代码块先执行。
- 静态代码块的定义:封装在一对花括号之间且括号前与static修饰
static {
statement;//内容
}
静态代码块只能调用静态成员(静态属性,静态方法),普通代码块可以调用任意成员。
2.24类什么时候被加载?
①创建对象实例时。
②创建子类对象实例,父类也会被加载。
③使用类的静态成员时(静态属性,静态方法)。
2.25对象创建时代码块构造方法调用顺序
①创建一个对象(无继承关系)时,在一个类调用的顺序是:
- 调用静态代码块和静态属性初始化(注意:静态代码和静态属性初始化调用的优先级一样,如有多个则按定义顺序调用)。
- 调用普通代码块和普通属性初始化(注意:普通代码和普通属性初始化调用的优先级一样,如有多个则按定义顺序调用)。
- 调用构造方法。
class F1 {
public F1() {
//1.执行普通代码块
//2.执行super()方法
//3.执行构造方法体
}
}
注:构造器的最前面其实隐含了super()和调用普通代码块,静态代码块再类加载时就已经执行完毕了。
②创建一个子类对象时(具有继承关系),它们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块和静态属性初始化(优先级一样,按照定义顺序)。
- 子类的静态代码块和静态属性初始化(优先级一样,按照定义顺序)。
- 父类的普通代码块和普通属性初始化(优先级一样,按照定义顺序)。
- 父类的构造方法。
- 子类的普通代码块和普通属性初始化(优先级一样,按照定义顺序)。
- 子类的构造方法。
1-2是类加载时就已经完成(只加载一次),3-6是创建对象时才执行(创建一次执行一次)。
//例:
class F1 {
public F1() {
//1.执行普通代码块
//2.执行super()方法
//3.执行构造方法体
System.out.println("F1的构造方法");
}
{
System.out.println("F1普通代码块!!!");
}
static {
System.out.println("F1静态代码块");
}
}
class F2 extends F1 {
public F2() {
System.out.println("F2的构造方法");
}
{
System.out.println("F2普通代码块!!!");
}
static {
System.out.println("F2静态代码块");
}
}
public class Test {
public static void main(String[] args) {
F2 f2 = new F2();
/*运行结果->
F1静态代码块
F2静态代码块
F1普通代码块!!!
F1的构造方法
F2普通代码块!!!
F2的构造方法
*/
}
}
2.26抽象类与接口
2.26.1抽象类
①为什么需要抽象类?
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。想回继承和多态原理,继承树中越是向上方的类越抽象,如果鸽子类继承鸟类、鸟类继承动物等。在多态机制中并不需要将父类的初始化为对象,我们只需的只是子类对象,所以在Java语言中设置抽象类不可实例化为对象。
②使用abstract关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法。抽象方法没有方法体,这个方法本身没有任何意义,除非被重写,而继承一个抽象方法的类必须被继承,实际上抽象类除了被继承没有任何意义。语法如下:
public abstract class Parent {
abstract void testAbstract();
}
注:只要类中有一个抽象方法,此类必须被定义为抽象类。
③注意事项和细节
- 抽象类不能被实例化。
- 抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法。
- 一旦包含了abstract方法,类必须声明为abstract类。
- abstract只能修饰类和方法,不能修饰其他的。
- 抽象类可以有任意成员(本质还是类)。
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
- 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
/*
* 子类继承F1类,子类实现job()方法,子类创建对象调用calTime();
*/
abstract class F1 {
public abstract void job();
public void calTime() {
//记录开始时间
long state = System.currentTimeMillis();
//子类实现
job();//子类实现
//记录结束时间
long end = System.currentTimeMillis();
//计算出子类job大概运行时间
System.out.println("服务时间:" + (end - state));
}
}
2.26.2接口
①基本介绍:接口就是给出一些没有实现方法,封装到一起。在某个类要使用的时候,在根据具体情况把这些方法写出来
②语法:
interface 接口名 {
//属性
//方法
}
注:
<1>在JDK7.0前,接口里的所有方法都没有方法体,即都是抽象方法。
<2>在JDK8.0后,接口可以有静态方法,默认方法也就是说接口中可以有方法体的具体实现。(默认方法需要使用default关键字修饰)
③注意事项和细节
- 接口不能被实例化(接口本质抽象类)。
- 接口中所有的方法是public方法,接口中抽象方法可以不用abstract修饰。
- 一个普通类实现接口,就必须将该接口的所有方法都实现。
- 抽象类实现接口,可以不用实现接口的方法。
- 一个类同时可以实现多个接口。class 类名 implements A,B {}
- 接口中的属性只能是final的,而且还是public、static、final修饰。
- 接口不能继承其他的类,但是可以继承多个别的接口。
interface ID extends IC,IB {} - 接口的修饰符只能是public或默认。
2.26.3实现接口VS继承
①当子类继承了父类,就自动的拥有父类的功能。
②如果子类需要扩展功能,可以通过实现接口的方式扩展。可以理解为实现接口是对Java单继承机制的一种补充。
③继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计好各种规范(方法),让其它类去实现这些方法。
接口比继承更加灵活。
2.27内部类
①基本介绍:一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类,嵌套其他类的类称为外部类。
②语法:
class Outer {//外部类
class Inner {}//内部类
}
class other {}//外部其他类
③内部类分类:
- 局部内部类(有类名)
- 匿名内部类(无类名)
- 成员内部类
- 静态内部类
2.27.1局部内部类
class Outer {
private int n = 100;
public void m1(){
class Inner02 {//m1方法内的内部类
public void f1(){
System.out.println("n = " + n);//访问外部类的私有属性
}
}
class Inner03 extends Inner02 {}
Inner02 inner02 = new Inner02();//实例化内部类Inner02
inner02.f1();
}
{
class Inner04{}//代码块中的内部类
}
}
①局部内部类是定义在外部类的局部位置,比如:方法和代码块中并且有类名。
②可以直接访问外部类的所有成员,包含私有的。
③不能添加访问修饰符,因为它的地位是一个局部变量,局部变量不能使用访问修饰符,但可以用final。
④作用域:仅仅在定义它的方法或代码块中。
⑤局部内部类直接访问外部类的成员,外部类必须在作用域内创建对象再访问成员。
⑥外部其他类不能访问局部内部类(因为它的地位是一个局部变量)。
⑦如果外部类和局部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员则可以使用(外部类名.this.成员名)去访问。
class Outer {
private int a = 100;
public void m1(){
class Inner02{
private int a = 200;
public void F1(){
//访问局部内部类的a(200);
System.out.println(a);
//访问外部类的a(100);
System.out.println(Outer.this.a);
}
}
Inner02 inner02 = new Inner02();
inner02.F1();
}
}
public static void main(String[] args) {
Outer outer = new Outer();//创建外部类
outer.m1();//调用m1方法
}
注:Outer.this本质就是外部类的一个对象,即谁调用了m1()方法,Outer就是哪个对象。
2.27.2匿名内部类
①本质还是一个类,它是一个内部类,该类没有名字(表面看没有,但系统分配了名字),同时还是一个对象。匿名类是只在创建对象时才会编译类体的一种写法。
②语法:
new 类/接口(参数列表) {
//类体
};
例:使用匿名内部类创建一个抽象狗类的对象。
/**
1. 创建一个抽象的狗类,类中有一个颜色属性和两个抽象方法,
2. 在测试类的主方法中创建抽象类对像,并且用匿名内部类实现
*/
abstract class Dog {
String Color;
public abstract void move();
public abstract void call();
}
public class Test {
public static void main(String[] args) {
Dog mao = new Dog() {
@Override
public void move() {
System.out.println("狂奔");
}
@Override
public void call() {
System.out.println("嗷呜!");
}
};
mao.Color="灰色";
mao.call();
mao.call();
}
}
/**
*运行结果:
*狂奔
*嗷呜!
*/
③注意:
- 匿名类不能写构造方法。
- 匿名类不能定义静态成员。
- 如果匿名类创建的对象没有赋值给任何引用变量,会导致该对象用完一次会被Java虚拟机销毁。
④注意事项
- 匿名内部类的调用(即使类又是对象)
//第一种调用方式
class Outer {
public void f1(){
Person person = new Person() {
@Override
public void hi() {}
};
person.hi();
}
}
//第二种调用方式
class Outer01 {
public void f2(){
new Person() {
@Override
public void hi() {}
}.hi();//new对象直接调用方法
}
}
- 可以直接访问外部类的所有成员,包括私有的。
- 不能添加访问修饰符,因为地位是一个局部变量。
- 作用域:仅仅在定义时方法内和代码块内。
- 外部其他类不能访问匿名内部类。(因为地位是一个局部变量)
- 如果外部类和匿名局部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员则可以使用(外部类名.this.成员名)去访问。
2.27.3成员内部类
成员内部类是定义在外部类的成员位置并且没有static修饰。
class Outer {
public int i;
class Inner{}//成员内部类
}
①可以直接访问外部类的所有成员,包含私有的。
②可以添加任意访问修饰符(public、protected、默认、private)因为它的地位就是一个成员。
③作用域:整个类体。
④其他类访问成员内部类
- 成员内部类直接访问访问外部类成员包含私有的。
- 外部类需要创建对象再访问成员内部类。
- 外部其他类访问成员内部类
<1>Outer.Inner inner = Outer.new Inner();
外部类名[点]成员内部类名 名字 = 外部类名[点] new 成员内部类名();
<2>在外部类中编写一个方法,返回一个成员内部类对象。
class Outer {
class Inner{}
public Inner getInner(){
return new Inner();
}
}
//先创建一个外部类对象
Outer outer = new Outer();
//通过外部类对象调用getInner方法返回一个Inner对象
Outer.Inner inner = outer.getInner();
<3>将方法二整合在一起
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();//直接new
⑤如果外部类和成员局部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员则可以使用(外部类名.this.成员名)去访问。
2.27.4静态内部类
静态内部类是定义在外部类的成员位置,并且用static修饰。
①可以直接访问外部类的所有成员,包含私有的,但不能直接访问非静态成员。
②可以添加任意访问修饰符(public、protected、默认、private)因为它的地位就是一个成员。
③作用域:整个类体。
④其他类访问静态内部类
- 静态内部类直接访问外部内部类的成员。
- 外部类需要创建对象再访问静态内部类。
- 外部其他类访问静态内部类
<1>Outer.Inner inner = new Outer.Inner();
Inner地位是类的成员,由于是静态的,所以直接访问可以不创建外部类对象。
<2>编写一个方法,返回一个静态内部类对象。
⑤如果外部类和静态局部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员则可以使用(外部类名.this.成员名)去访问。
如发现错误,恳请留言或私信我。谢谢