面向对象详解
一、面向对象思想
面向过程(POP) 与 象 面向对象(OOP):二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑的是要解决某个特定的问题要分为哪些步骤。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑要解决的问题中某个模块谁来做。面向对象程序由对象构成,每个对象封装了对用户公开的特定功能部分和隐藏的具体实现的部分。
还是以那个把大象装进冰箱来说明一下:
首先是面向过程的做法:主要考虑的是怎么做,强调步骤
第一步:打开冰箱
第二步:将大象塞进冰箱
第三步:关闭冰箱
面向对象:主要是考虑谁来做
既然是要把大象塞进冰箱,那么肯定会有一个主体来做这件事,这个主体就是“人”。同时还有大象、冰箱等实体类。
其中在人这个主体内部中我们可以定义一些完成将大象塞进冰箱的方法
人{
打开(冰箱){
冰箱.开开();
}
抬起(大象){
大象.进冰箱();
}
关闭(冰箱){
冰箱.关闭();
}
}
从面向过程到面向对象,程序员从面向过程的执行者转化成了面向对象的指挥者
二、类与对象
1、类与对象的基本概念
类与对象是整个面向对象过程中最基础的组成单元。其中类是抽象的概念集合,表示的是一个共性的产物,类之中定义的是属性和行为(方法); 对象是一种个性的表示,表示一个独立的个体,每个对象拥有自己独立的属性,依靠属性来区分不同对象。 反正呢,类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。由类构造对象的过程叫做对象的实例化的过程。
1.1、对象的内存解析
关于Java虚拟机中的内存完整规划如上图所示,现在我们关注的只有方法区、虚拟机栈以及堆而已。
堆(Heap),此内存区域的唯一目的就是存放对象实例(广义上的对象,即包括数组等),几乎所有的对象
实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
1. 堆用于存储创建好的对象和数组(数组也是对象) 如:new对象的过程
2. JVM只有一个堆,被所有线程共享
3. 堆是一个不连续的内存空间,分配灵活,速度慢!
通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量、操作数、方法出口等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long 、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
1. 栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(包括main方法)
2. JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
3. 栈属于线程私有,不能实现线程间的共享!
4. 栈的存储特性是“先进后出,后进先出”
5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!
方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
1. 方法区实际也是堆,只是用于存储 类、常量相关的信息!
2. 用来存放程序中永远是不变或唯一的内容。(类信息【Class对象】、静态变量、字符串常量等)
要解析对象的内存,我们先来写一点代码:
Pserson类
public class Pserson {
String name;
Integer age=1;
boolean isMale;
//get\set方法
}
测试代码:
Person p1=new Person();
p1.name="Tom";
p1.isMale=true;
Person p2=new Person();
System.out.println(p2.name);
Person p3=p1;
p3.age=10;
因为上面这段代码是在main方法中执行的且在方法中声明的变量都是局部变量,所以p1这个局部变量会在栈中,当执行到 Person p1=new Person() 语句时,会在堆空间中划分一个p1的内存空间且生成一个引用地址,并将其引用地址赋值给栈中的p1。同时,因为p1对象中在类的定义时有定义三个属性,且不是在方法体内定义的,所以在这p1 对象的三个属性地址空间会被划分在P1的地址空间中。
然后执行下面的语句为p1对象的属性赋值。接着又执行 Person p2=new Person(); 语句,在堆中为p2开辟一个空间。执行到 Person p3=p1; 因为它没有new ,所以不能叫创建对象,而应该说是创建了一个Person的引用,且指向了p1对象的堆内存空间。所以给p3的age赋值就是给p1的age赋值。
2、类的成员
一个类中可以包括有成员变量(也叫成员属性)、成员方法、构造器方法、代码块以及内部类
2.1、变量
在Java语言里,根据定义变量位置的不同,可以将变量分成两大类:成员变量(存在于堆内存中,和类一起创建)和局部变量(存在于栈内存中,当方法执行完成,让出内存,让其他方法来使用内存)。二者的运行机制存在较大差异。
2.2.1、成员变量
在方法体外,类体内声明的变量称为成员变量
语法格式:修饰符 数据型 类型 属性名 名 = 初始化值 ;
属性赋值过程: ① 默认初始化
② 显式初始化
③ 在构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值 赋值的先后顺序:
① - ② - ③ - ④
类变量从该类的准备阶段起开始存在,直到系统完全销毁这个类,类变量的作用域与这个类的生存范围相同;
而实例变量则从该类的实例被创建起开始存在,直到系统完全销毁这个实例,实例变量的作用域与对应实例的生存范围相同。
正是基于这个原因,可以把类变量和实例变量统称为成员变量。其中类变量可以理解为类成员变量,它作为类本身的一个成员,与类本身共存亡;实例变量则可以理解为实例成员变量,它作为实例的一个成员与实例共存亡。
当然实例也可以访问类变量。但是需要注意的是因为实例不拥有类变量,所以通过实例来访问类变量进行操作,实际上是对类变量进行操作 ,不管有多少个实例来访问类变量,访问的类变量都是同一个,换句话说,这个类变量被所以实例对象共享。
成员变量无需显示初始化,只要为一个类定义了类变量或实例变量,系统就会在这个类的准备阶段或创建该类的实例时进行默认初始化。
2.2.2、局部变量
声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量都是局部变量
形参:在定义方法签名时定义的变量,形参的作用域在整个方法中都有效
方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效
代码块局部变量:这个局部变量的作用域从定义该变量的地方生效,到该代码结束时失效。
一个局部变量只在一对{}中起作用,而且注意,java允许局部变量和成员变量同名,如果方法中局部变量和成员变量同名,局部变量就会覆盖成员变量,如果需要在这个方法中引用被覆盖成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定访问成员变量。
局部变量必须经过显示初始化之后才能使用,系统不会为局部变量执行初始化。定义了局部变量以后,系统并没有给局部变量进行初始化,直到程序给这个局部变量赋给初值时,系统才会为这个局部变量分配内存空间,并将初始值保存到这块内存中。
*局部变量不属于任何类或者实例,因此它总是保存在方法的栈内存中。如果局部变量是基本数据类型,则该变量d 的值就直接存储在方法的栈内存中,如果是引用变量则将引用的地址存储在方法的栈内存中。
*栈内存中的变量无需系统垃圾回收,随着方法或者代码块的运行结束而结束。局部变量通常只保存了具体的值或者引用地址,所以所占的内存比较小。
成员变量VS局部变量
成员变量:
①成员变量定义在类中,在整个类中都可以被访问。
②成员变量随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的堆内存中。
③成员变量有默认初始化值。
局部变量:
①局部变量只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。
②局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放。
③局部变量没有默认初始化值
变量的使用原则:
在使用变量时需要遵循的原则为:就近原则
首先在局部范围找,有就使用;接着在成员位置找。
还有
能不使用成员变量就别使用成员变量
能不使用方法局部变量就别使用方法局部变量
使用代码块局部变量性能最好。
2.2、成员方法
格式:修饰符 返回值类型 方法名 ( 参数类型 形参1, 参数类型 形参2, …. ){
方法体程序代码
return 返回值;
}
2.2.3、方法调用时的过程解析
1.当程序执行到对象调用方法的语句时,编译器会先查看对象的声明类型(属于哪个类)和方法名。假设调用x.f(param),且隐式参数x声明为X类的对象。 需要注意的是:有可能存在多个名字为f的,但是参数类型不一样的方法(方法重载)。例如,可能存在f(int)和f(String)。编译器将会一一列举所有X类和其超极父类中访问属性为public且名为f的方法(超类的私有方法不可访问)
2.接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程称为重载解析。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告错误。
3.如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式称为静态绑定。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。
4.当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实例类型最合适的那个类的方法。假设x的实际类型是D,它是X类的子类,我们要调用f(String)。如果D类定义了方法f(String),就直接调用它;否则将在D类的超类中寻找f(String),以此类推。(子类中覆盖了父类优先调用子类的方法)
5.每次调用方法都要进行方法搜索,时间开销相当大。因此,虚拟机预先为每个类创建一个方法表,其中列举了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。
2.2.4、方法参数的值传递机制
关于变量的赋值情况,若是相互赋值之间的变量都是基本数据类型,那么赋值的是变量所保存的数据值;若两个相互赋值之间的变量都是引用数据类型,那么赋值所保存的就是变量所保存的地址值。
在函数的形参中有两种值传递机制:按值调用以及按引用调用
按值调用表示方法接收的是调用者提供的值
而按引用调用(通过引用调用)表示方法接收的是调用者提供的变量地址。
一个方法可以修改传递引用(地址)所对应的变量值,而不能修改传递值调用所对应的变量值.
JAVA程序设计语言总是采用按值调用.也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容. 所谓的引用传递只是通过地址值的传递 来传递引用罢了
1.当形参是基本数据类型时:将实参基本数据类型变量的“数据值”传递给形参
2.当形参是引用数据类型时:将实参引用数据类型变量的“地址值”传递给形参
与参数是基本数据类型不一样的是,引用数据类型new出来的对象实际上是放在堆中的,在栈中声明的对象变量实际上是存放new出来的对象成员的地址,通过地址值找到堆中的变量,所以修改后会改变原来的值
2.2.5、方法的重载
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
重载的特点:与返回值类型无关,只看参数类型列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
判断方法是否重载只需要看方法的形参类型以及形参个数即可。
2.2.6、可变形参方法
可变形参的方法定义格式是 :
权限修饰符 返回值类型 方法名(参数类型 ... 形参名){} //...不可省略
注意:
1.可变个数形参的方法与本类中方法名相同,形参类型不同的方法之间会构成重载
2.可变个数形参的方法与本类中方法名相同,形参类型也相同的数组的方法之间不会构成重载,同时存在会报错
3.可变个数形参方法中可变形参必须声明在形参列表的末尾,且一个方法最多只能有一个可变类型形参
2.3、构造器方法
Java构造函数,也叫构造方法,是JAVA中一种特殊的函数。与函数名相同,无返回值。
作用:一般用来初始化成员属性和成员方法的,即new对象产生后,就调用了对象的属性和方法。 而一般函数是对象调用时才执行。
构造方法的格式:
public 类名称(参数类型 参数名称){
构造方法体
}
注意事项
1.构造方法名称必须跟所在的类名称完全一致,包括大小写
2.构造方法不写返回值类型,void也不能写。.构造器的默认权限修饰符跟它的类的权限修饰符相关。不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
3.若没有编写任何构造方法,那么编译器会默认一个构造方法,但这个构造方法什么语句也不执行。一旦写了构造方法系统将不再默认赠送构造方法。(这时要自己写一个无参数构造方法)
4.父类的构造器Constructor不能被继承,因此不能被重写Override,但可以被重载Overload
5.一个对象建立,构造函数只运行一次。 而一般函数可以被该对象调用多次。
6.子类所有的构造函数 默认调用父类的无参构造函数(构造函数不会被继承,只是被子类调用而已),父类参数是private的,无法直接访问。需要在父类中使用get方法来调用私有变量值。
2.4、代码块
静态代码块与非静态代码块
相同点:
都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
不同点:
静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后再new也不执行了,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
2.5、内部类
Java内部类:一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
成员内部类(静态的、非静态的)
一方面成员内部类作为外部类的成员,和外部类不同,成员内部类还可以声明为private或protected;还可以声明为static的,但此时就不能再使用外层类的非static的成员变量; 也可以调用外部类的结构
另一方面成员内部类也是一个类, 可以在其内部定义属性、方法、构造器等结构,可以声明为abstract类 ,因此可以被其它的内部类继承。也可以声明为final的,dan就不能被继承了, 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意:
1. 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
2. 外部类访问成员内部类的成员,需要使用“内部类.成员”或“内部类对象.成员”的方式。但是 成员内部类可以直接使用外部类的所有成员,包括私有的数据
3. 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的。 语法:
new 外部类().new 内部类(),但得建立在存在外部类对象的基础上
局部内部类(方法、代码块、构造器之内)
如何使用局部内部类?
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
特点:
1. 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号
2. 局部内部类可以使用外部类的成员,包括私有的。局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致
3. 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private.局部内部类不能使用static修饰,因此也不能包含静态成员
三、面向对象的三大特征
大家都知道Java面向对象编程作为一种编程思想,有三大特性,封装,继承,多态。封装是对于一个类而言的, 那么该如何封装一个类 呢?继承是父类和子类的关系, 继承关系如何实现的呢?父类和子类继承时的方法体是怎么样的关系呢?多态是多个类之间的关系,类有不同的行为和属性,其他类也有这样的方法和属性,就实现了多态的关系,不同类的多态实现是怎么样实现的呢?
1、封装
封装是指把一个对象的状态信息(也就是属性及方法)隐藏在对象内部,不允许外部对象直接访问对象的内部信息.但是可以提供一些可以被外界访问的方法来操作属性或方法(setter和getter方法).就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调.如果属性不想被外界访问,我们大可不必提供方法给外界访问.但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了.就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法,这里只是为了举例子)。
封装的意义就是增强类的信息隐藏与模块化,提高安全性。封装的主要作用也是对外部隐藏具体的实现细节,增加程序的安全性。
权限修饰符
封装性的体现需要权限修饰符来配合,Java中提供了四种权限修饰符
2、继承
继承是一种基于已有的类(父类)去创建新类(子类)的机制,利用继承可以先去创建一个具有广泛意义的类,然后通过派生创建新类并添加上一些特有的属性或方法。 一个子类继承了一个父类,那么这个子类就获取了父类中的所有成员属性与成员方法,特别的是,若父类中的属性或方法被private修饰了,子类中虽然继承了该属性或方法,但是不能直接调用父类的结构。也就是说,父类中定义了的方法或属性,子类可以不用再次定义就初始化或使用。继承的作用就是能提高代码的复用性。子类拥有父类中的一切(拥有不一定能使用),它可以访问和使用父类中的非私有成员变量,以及重写父类中的非私有成员方法。
2.1、重写
当继承了父类的子类重新写父类中已有的相同的方法的时候,就叫方法的重写。
格式要求: 重写父类的方法时,子类重写的方法名与参数列表要和父类被重写的方法的方法名与参数列表一样
重写时的注意事项
1. 子类重写的方法的权限修饰符范围不得低于父类的权限修饰符范围【但是,子类不能重写父类中被private修饰的方法】????
2. 子类重写的方法抛出的异常不能小于父类被重写的方法抛出的异常
3. 返回值类型注意点:
--被重写方法的返回值是void,则重写的方法的返回值类型只能是void
--被重写的方法的返回值是A类型,则重写的方法的返回值类型可以是A类或者A的子类
--被重写的方法的返回值类型是基本数据类型,则重写的方法的返回值类型只能是相同的基本数据类型
2.2、子类对象实例化过程:
创建生成一个子类实例时,首先要执行的是子类的构造方法,但是若是子类继承于某个父类,那么在执行子类的构造方法前系统会自动先执行父类的(无参)构造方法。对于父类中的有参构造方法,子类可以通过在自己的构造方法中使用super关键字来调用它,但这个调用语句必须是子类构造方法中的第一条可执行语句。
2.3、继承的注意事项
1. 一个类可以被多个子类继承,也能有多重继承,但前提是一个子类只能有一个父类(单继承性)。子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有.
2. 若是某个类被final声明,则表明此类是不可被继承的
3. 子类不继承父类的构造器(构造方法或者构造函数),它只是调用父类构造器(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数(无参构造器),则在子类的构造器中不需要使用 super关键字调用父类构造器,系统会自动调用父类的无参构造器。
4. 若是子类中没有重写父类的方法,那么在子类创建的对象在引用该方法时执行的是父类没有被重写的方法,若是重写了父类中已有的方法则执行的是子类中重写父类的方法.
3、多态
多态多态,顾名思义,表示一个事物具有多种状态[ 动物可以表现为猫与狗两种状态 ].具体表现为父类的引用指向子类的实例等.【可以直接应用在抽象类和接口上】
3.1、多态的特点:
1.对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
Person p = new Person(); // p是引用 , new Person();是对象
2.对象类型不可变,引用类型可变;//???
3.方法具有多态性,属性不具有多态性;
4.引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
5.多态不能调用“只在子类存在但在父类不存在”的方法;✔✔✔✔✔✔✔
6.如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法.
7.多态时子类的对象可以替代父类的对象使用,一个变量只能有一种确定的数据类型,一个引用类型变量可能指向(引用)多种不同类型的对象
Java引用变量有两个类型: 编译时类型和 运行时类型。
编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。 简称:编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致 , 就出现了对象的多态性(Polymorphism)
多态情况下 , “ 看左边 ” : 看的是父类的引用(父类中不具备子类特有的方法)
“ 看右边 ” : 看的是子类的对象(实际运行的是子类重写父类的方法)
3.2、向上转型与向下转型
向上向下转型【对象类型转换 (Casting )】:一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该引用变量就不能再访问子类中特有的属性和方法【ADHero ad = new ADHero(); 有一个对象 new ADHero(), 同时也有一个引用ad对象是有类型的, 是ADHero引用也是有类型的,是ADHero通常情况下,引用类型和对象类型是一样的,类型转换指的是引用类型和对象类型不一致的情况下的转换问题】
3.2.1、子类转父类(向上转型)
从子类到父类的类型转换可以自动进行,实现类转换成接口(向上转型)无需强制转换,并且一定能成功。接口转换成实现类(向下转型)就不一定了
3.2.2、父类转子类(向下转型).
从父类到子类的类型转换必须通过造型( 强制类型转换) 实现
无继承关系的引用类型间的转换是非法的
在造型前可以使用instanceof 判断是否可以转化,避免报错
四、抽象与接口
(一)、抽象
抽象:随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例(只有一些特征方法,也叫抽象方法),这样的类叫做抽象类。同时,抽象父类的抽象方法没有进行实现,子类必须对父类的抽象方法进行实现。
那么什么叫抽象方法呢?在所有的普通方法上面都会有一个“{}”,这个表示方法体,有方法体的方法一定可以被对象直接使用。而抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。
一个抽象类的示例:
abstract class Person{//定义一个抽象类
public void talk(){//普通方法
System.out.println("存在方法体中的普通方法");
}
public abstract void run();//抽象方法,没有方法体,有abstract关键字做修饰
}
//不能用abstract修饰变量、代码块、构造器; 也不能用abstract修饰私有方法、静态方法、final声明的方法、final声明的类
刚刚上面说到,抽象类不能被具体实例化,既然抽象方法不能实例化,那么抽象类中的普通方法还能被使用吗??
可以的,比如一个子类A继承了一个抽象类B,那么A就有了B中所有的方法与属性,自然可以调用它的普通方法了(私有方法除外)。
4.1.1、抽象类的注意事项
1.含有抽象方法的类必须被声明为抽象类。但是抽象类可以没有抽象方法。
2.抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写**全部**的抽象方法,仍为抽象类,仍不能被实例化。
3.抽象类是用来模型化那些父类无法**确定**的全部实现,而是由其子类提 供具体实现的对象的类。【比如:抽象类中的抽象方法只是定义了有某种行为或属性,真正涉及该行为到底是怎样的还得看继承该抽象类的类去重写抽象类中的所有抽象方法,才能具体规定该行为的具体内容】
4.抽象类中也能有普通的成员方法,也可以被子类继承。
5.抽象类中含有构造器(便于子类实例化时调用):子类中的构造方法中含有默认的super();语句需要访问抽象类的构造方法
6.抽象类不可以用final声明么,因为抽象类必须有子类,而final定义的类不能有子类;
7.可以直接调用抽象类中用static声明的方法,任何时候,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。
(二)、接口
什么是接口:接口(interface)是抽象方法和常量值定义的集合。接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
4.2.1、接口的特点
1.接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。
2.一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者implement move()方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。
3.如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)
4.一个JAVA库中接口的例子是:Comparator 接口,这个接口代表了“能够进行比较”这种能力,任何类只要实现了这个Comparator接口的话,这个类也具备了“比较”这种能力,那么就可以用来进行排序操作了。
为什么有接口?
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到相当于多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。(接口和类是并列的关系)还有接口也可以用来实现解耦合。
4.2.2、接口的声明与实现
为了声明一个接口,我们使用interface这个关键字,在接口中的所有方法都必须只声明方法标识,而不要去声明具体的方法体,因为具体的方法体的实现是由继承该接口的类来去实现的,因此,接口并不用管具体的实现。接口中的属性默认为Public Static Final.一个类实现这个接口必须实现这个接口中定义的所有的抽象方法。
为了实现这个接口,我们使用implements关键词去实现接口以及接口中的抽象方法
4.2.3、接口的几个注意事项
1. 我们不能直接去实例化一个接口,因为接口中的方法都是抽象的,是没有方法体的,这样怎么可能产生具体的实例呢?但是,我们可以使用接口类型的引用指向一个实现了该接口的对象,并且可以调用这个接口中的方法。(实际上就是使用了Java中多态的特性)
2. 一个类可以实现多个接口。
3. 一个接口可以继承于另一个接口,或者另一些接口,接口也可以继承,并且可以多继承。???
4. 一个类如果要实现某个接口的话,那么它必须要实现这个接口中的所有方法。
5. 接口中所有的方法都是抽象的和public的,所有的属性都是public,static,final的。
6. 虽然接口内部定义了一些抽象方法,但是并不是所有的接口内部都必须要有方法,比如Seriallizable接口,Seriallizable接口的作用是使对象能够“序列化”,但是Seriallizable接口中却没有任何内容,也就是说,如果有一个类需要实现“序列化”的功能,则这个类必须去实现Seriallizable接口,但是却并不用实现方法(因为接口中没有方法),此时,这个Serilizable接口就仅仅是一个“标识”接口,是用来标志一个类的,标志这个类具有这个“序列化”功能。
(三)、接口与抽象类的区别
相同点:
(1)都不能被实例化
(2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点:
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
(5)接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。功能需要累积时用抽象类,不需要累积时用接口。
五、关键字
1、 this
this:表示当前对象的。 (属性、方法、构造器等)
this.属性名称
用来访问本类的成员属性
this.方法名称
用来访问本类的成员方法
this();
访问本类的构造方法
()中可以有参数的 ,如果有参数 就是调用指定的有参构造
注意事项:
1.this() 不能使用在普通方法中 只能写在构造方法中
2.必须是构造方法中的第一条语句
1.1、访问成员属性
Public Class Student {
String name; //定义一个成员变量name
private void SetName(String name) { //定义一个参数(局部变量)name
this.name=name; //将局部变量的值传递给成员变量
}
如上面这段代码中,有一个成员变量name,同时在方法中有一个形式参数,名字也是name,然后在方法中将形式参数name的值传递给成员变量name,虽然我们可以看明白这个代码的含义,但是作为Java编译器它是怎么判断的呢?到底是将形式参数name的值传递给成员变量name,还是反过来将成员变量name的值传递给形式参数name呢?也就是说,两个变量名字如果相同的话,那么Java如何判断使用哪个变量?此时this这个关键字就起到作用了。this这个关键字其代表的就是对象中的成员变量或者方法。也就是说,如果在某个变量前面加上一个this关键字,其指的就是这个对象的成员变量或者方法,而不是指成员方法的形式参数或者局部变量。为此在上面这个代码中,this.name代表的就是对象中的成员变量,又叫做对象的属性,而后面的name则是方法的形式参数,代码this.name=name就是将形式参数的值传递给成员变量。这就是上面这个代码的具体含义。
一般情况下,在Java语言中引用成员变量或者成员方法都是以对象名.成员变量或者对象名.成员方法的形式。不过有些程序员即使在没有相同变量的时候,也喜欢使用this.成员变量的形式来引用变量,这主要是从便于代码的阅读考虑的。一看到这个this关键字就知道现在引用的变量是成员变量或者成员方法,而不是局部变量。这无形中就提高了代码的阅读性。不过话说回来,这是this关键字在Java语言中的最简单的应用。从这个应用中,我们可以看出this关键字其代表的就是对象的名字。
其实如果是局部变量的话,也是相同的道理。如在上面的代码中,name不是形式参数,而是一个局部变量。此时Java也会遇到相同的疑惑,即变量名name代表的到底是局部变量还是形式参数?name=name到底代表的是什么含义?根据局部变量的作用域,在方法内部,如果局部变量与成员变量同名的话,那么是以局部变量为准。可是在name=name这个赋值语句中,将局部变量的值赋值给自己,显然并不是很合适。根据代码的含义,本来的意思应该是将局部变量赋值给成员变量。为了更清晰的表达这个含义,为此最好采用如下的书写格式this.name=name。这里的this关键字含义就是对象名student,为此this.name就表示student.name。
1.2、 调用类的构造方法
public class Student { //定义一个类,类的名字为student。
public Student() { //定义一个方法,名字与类相同故为构造方法
this(“Hello!”);
}
public Student(String name) { //定义一个带形式参数的构造方法
}
}
this关键字除了可以调用成员变量之外,还可以调用构造方法。在一个Java类中,其方法可以分为成员方法和构造方法两种。构造方法是一个与类同名的方法,在Java类中必须存在一个构造方法。如果在代码中没有显示的体现构造方法的话,那么编译器在编译的时候会自动添加一个没有形式参数的构造方法。这个构造方法跟普通的成员方法还是有很多不同的地方。如构造方法一律是没有返回值的,而且也不用void关键字来说明这个构造方法没有返回值。而普通的方法可以有返回值、也可以没有返回值,程序员可以根据自己的需要来定义。不过如果普通的方法没有返回值的话,那么一定要在方法定义的时候采用void关键字来进行说明。其次构造方法的名字有严格的要求,即必须与类的名字相同。也就是说,Java编译器发现有个方法与类的名字相同才把其当作构造方法来对待。而对于普通方法的话,则要求不能够与类的名字相同,而且多个成员方法不能够采用相同的名字。在一个类中可以存在多个构造方法,这些构造方法都采用相同的名字,只是形式参数不同。Java语言就凭形式参数不同来判断调用那个构造方法。
在上面这段代码中,定义了两个构造方法,一个带参数,另一个没有带参数。构造方法都不会有返回值,不过由于构造方法的特殊性,为此不必要在构造方法定义时带上void关键字来说明这个问题。在第一个没有带参数的构造方法中,使用了this(“Hello!”)这句代码,这句代码表示什么含义呢?在构造方法中使this关键字表示调用类中的构造方法。如果一个类中有多个构造方法,因为其名字都相同,跟类名一致,那么这个this到底是调用哪个构造方法呢?其实,这跟采用其他方法引用构造方法一样,都是通过形式参数来调用构造方法的。如上例中,this关键字后面加上了一个参数,那么就表示其引用的是带参数的构造方法。如果现在有三个构造方法,分别为不带参数、带一个参数、带两个参数。那么Java编译器会根据所传递的参数数量的不同,来判断该调用哪个构造方法。从上面示例中可以看出,this关键字不仅可以用来引用成员变量,而且还可以用来引用构造方法。
不过如果要使用这种方式来调用构造方法的话,有一个语法上的限制。一般来说,利用this关键字来调用构造方法,只有在无参数构造方法中第一句使用this调用有参数的构造方法。否则的话,翻译的时候,就会有错误信息。这跟引用成员变量不同。如果引用成员变量的话,this关键字是没有位置上的限制的。如果不熟悉这个限制的话,那么还是老老实实的采用传统的构造方法调用方式为好。虽然比较麻烦,但是至少不会出错。
1.3、返回对象值
this关键字调用方法就不用说了。挺简单的。
this关键字除了可以引用变量或者成员方法之外,还有一个重大的作用就是返回类的引用。如在代码中,可以使用return this,来返回某个类的引用。此时这个this关键字就代表类的名称。如代码在上面student类中,那么代码代表的含义就是return student。可见,这个this关键字除了可以引用变量或者成员方法之外,还可以作为类的返回值,这才是this关键字最引人注意的地方。
2、super
super:super关键字用于从子类访问父类的变量和方法。表示父类的
使用super跟static关键字的注意事项:在构造器中使用 super() 调用父类中的其他构造方法(空参、全参)时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。 . this、super不能用在static方法中:被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西。
3、 static
static 关键字主要有以下四种使用场景
3.1、修饰成员变量和成员方法
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被此类中的所有对象共享, 类变量的生命周期和类相同,在整个应用程序执行期间都有效。 (方法与成员变量都是如此)可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
1.静态方法属于类本身,非静态方法属于从该类生成的每个对象
2.如果方法执行的操作不依赖于其类本身的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。
3.静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 [静态方法不能调用非静态成员,编译会报错]
4.虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。
5.static关键字不能改变变量和方法的访问权限
6.static是不允许用来修饰局部变量。这是Java语法的规定
3.2、修饰静态代码块
静态代码块定义在类的内部,方法的外部并用static修饰, 静态代码块在非静态代码块之前执行(执行的先后顺序为:静态代码块—非静态代码块—构造方法)。
静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问. ???
该类不管创建多少对象,静态代码块只执行一次.
3.3、修饰类(只能修饰内部类)
我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
它的创建是不需要依赖外围类的创建。
它不能使用任何外围类的非static成员变量和方法。
3.4、静态导包(用来导入类中的静态资源,1.5之后的新特性)
import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
3.5、几道常见的关于static 的面试题
1、这个程序的输出结果是什么?
public class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}
结果:
base static
test static
base constructor
test constructor
分析:
代码执行过程为:
找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
加载Test类的时候,发现Test类继承Base类,于是先去加载Base类
加载Base类的时候,发现Base类有static块,而是先执行static块,输出base static结果
Base类加载完成后,再去加载Test类,发现Test类也有static块,而是执行Test类中的static块,输出test static结果
Base类和Test类加载完成后,然后执行main方法中的new Test(),调用子类构造器之前会先调用父类构造器
调用父类构造器,输出base constructor结果
然后再调用子类构造器,输出test constructor结果
2、
public class Test {
Person person = new Person("Test");
static{
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
static{
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static{
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
结果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
分析:
找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
加载Test类的时候,发现Test类有static块,而是先执行static块,输出test static结果
然后执行new MyClass(),执行此代码之前,先加载MyClass类,发现MyClass类继承Test类,而是要先加载Test类,Test类之前已加载
加载MyClass类,发现MyClass类有static块,而是先执行static块,输出myclass static结果
然后调用MyClass类的构造器生成对象,在生成对象前,需要先初始化父类Test的成员变量,而是执行Person person = new Person("Test")代码,发现Person类没有加载
加载Person类,发现Person类有static块,而是先执行static块,输出person static结果
接着执行Person构造器,输出person Test结果
然后调用父类Test构造器,输出test constructor结果,这样就完成了父类Test的初始化了
再初始化MyClass类成员变量,执行Person构造器,输出person MyClass结果
最后调用MyClass类构造器,输出myclass constructor结果,这样就完成了MyClass类的初始化了
4、package
package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:package 顶层包名.包对应于文件系统的目录,
package 语句中,用 “ “.” ” 包 来指明包( 目录) 的层次;
包通常用 小写: 单词标识。通常使用所在公司域名的倒置:com.atguigu.xxx
5、import
注意:
1. 在源文件中使用import显式的导入指定包下的类或接口
2. 声明在包的声明和类的声明之间。
3. 如果需要导入多个类或接口,那么就并列显式多个import语句即可
4. 举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
5. 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
6. 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的
是哪个类。
7. 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
8. import static组合的使用:调用指定类或接口下的静态的属性或方法
6、final
final: final关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,
修饰类的特点:final修饰的类不能被继承,final修饰的类中的所有成员方法都会被隐式的指定为final方法;
修饰方法的特点:final修饰的方法不能被重写
修饰成员变量的特点:final修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。
注意:使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率