面向对象思想就是不断的创建对象(属性与行为的封装,让二者作为整体参与程序执行),使用对象,指挥对象做事情。(在已有对象的情况下,直接使用对象,而不再去考虑对象的内部构造)
面向对象的开发来讲也分为三个过程:OOA(面向对象分析)、OOD(面向对象设计)、OOP(面向对象编程)
成员变量:访问修饰符修饰符(作用范围) 类型(存储结构) 属性名称(引用)=初始值(实际物理值);
访问修饰符:可以使用四种不同的访问修饰符中的一种,包括public(公共的),protected(受保护的),无修饰符和 private(私有的)。public 访问修饰符表示属性可以从任何其它代码调用。private 表示属性只可以由该类中的其它方法来调用。
修饰符:是对属性特性的描述,例如:static、final 等等。
类型:属性的数据类型,可以是任意的类型。
属性名称:任何合法标识符
初始值:赋值给属性的初始值。如果不设置,那么会自动进行初始化,基本类型使用缺省值,对象类型自动初始化为 null。
成员变量有两种:一种是被static关键字修饰的变量,叫类变量或者静态变量(在类加载时初始化);另一种没有static修饰,为实例变量(在对象创建时初始化)。
二者在程序运行时的区别,实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了(直接在方法区读取数据)。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用
成员方法:访问修饰符 修饰符 返回值类型 方法名称 (参数列表) throws 异常列表 {方法体}
修饰符:是对方法特性的描述,例如:static、final、abstract、synchronized 等等。
返回值类型:表示方法返回值的类型。如果方法不返回任何值,它必须声明为 void(空)。 Java 技术对返回值是很严格的,例如,如果声明某方法返回一个int值,那么方法必 须从所有可能的返回路径中返回一个int值(只能在等待返回该 int 值的上下文中被调用。)
方法名称:可以是任何合法标识符,并带有用已经使用的名称为基础的某些限制 条件。
参数列表:允许将参数值传递到方法中。列举的元素由逗号分开,而每一个元素包含一个类型和一个标识符。
throws 异常列表:子句导致一个运行时错误(异常)被报告到调用的方法中,以便以合适的方式处理它。异常在后面的课程中介绍。
花括号内是方法体,即方法的具体语句序列。
和变量一样分为实例方法和静态方法
目的:主要用来给对象的数据进行初始化
格式:访问修饰符类名() { }
1、局部代码块
在局部代码块(方法体)中定义的变量属于局部变量,随方法的调用创建,随方法的出栈而消失
2、构造代码块
优先于构造方法执行(等同于将其放置在构造方法体中的首行),随构造方法执行而执行
3、静态代码块
只随类的加载而加载,无论创建多少次对象,只执行一次。
创建对象时代码执行顺序:
静态代码块(父类到子类)--构造代码块(父类)--构造方法(父类)--构造代码块(子类)--构造方法(子类)--方法(自然顺序)
public package(default) protected private 本类 √ √ √ √ 子类 √ √ √ 同包 √ √ 全局 √ |
this:本类对象的引用,本类对象的构造器
supper:父类对象的引用,父类对象的构造器
重写:等同于对重写对象方法(非静态方法)的引用,修饰符,返回类型,方法名和参数一样
重载:等同定义新的方法,只不过方法名和原来一样,但参数必须不一样(参数位置无关)
隐藏不需要对外公开的属性和方法,以减少对外耦合(通常将成员变量private,提供对应的getXxx()/setXxx()方法)
而外部只能通过方法来控制成员变量的操作,可提高了代码的安全性;同时把代码用方法进行封装,提高了代码的复用性
多个类有共同的成员变量和成员方法,抽取到另外一个类中(父类),在让多个类去继承这个父类
特点:
1.子类继承父类的成员变量
当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:
1)能够访问父类的public和protected成员变量;不能够访问父类的private成员变量(可以通过反射强制获取);
2)对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;
3)对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。
2.子类继承父类的方法
1)能够访问父类的public和protected成员方法;不能够访问父类的private成员方法(但是能继承);
2)对于父类的包访问权限成员方法,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;
3)对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法(但是父类的成员方法如果是静态,子类也必须是静态才能屏蔽,不然编译错误,反之亦然)。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。
注意:隐藏和覆盖是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的。
3.构造器
子类是不能够继承父类的构造器,但是要注意的是,如果父类是有参构造,那么子类必须有同类型参数构造,如果父类是无参构造,那么子类任意构造都可。
父类的引用指向子类对象:父类类型 引用名称= new 子类类型
父类类型 引用名称= (父类类型 引用名称)new 子类类型
A:多态成员变量
当子父类中出现同名的成员变量时,多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记:编译和运行都参考等号的左边。
B:多态成员方法
编译时期:参考引用变量所属的类,如果没有类中没有调用的方法,编译失败。
运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
简而言之:编译看左边(父类是否拥有该方法),运行看右边(静态都看左边)
多态的转型分为向上转型与向下转型两种:
A:向上转型
当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向 上转型的过程。
使用格式:父类类型 变量名 = new 子类类型();
如:Person p = new Student();
B:向下转型
一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的
使用格式:子类类型 变量名 = (子类类型) 父类类型的变量;
如:Student stu = (Student) p; //变量p 实际上指向Student对象
*执行Object[] o ={“java”,”java”};String[] s = (String[])o;报错
转化异常:Java虚拟机的转化机制无法判断数组内部元素的类型,故不可强制为Object
如果一个类含有抽象方法,则称这个类为抽象类(不包含抽象方法,但被abstract修饰的也是抽象类,但是没有意义),抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。
抽象类和普通类的主要有三点区别:
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承, 子类便无法实现该方法),缺省情况下默认为public。
2)抽象类有构造方法,但不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没 有实现父类的抽象方法,则必须将子类也定义为为abstract类。
为什么需要内部类:
无论该内部类的外围类实现或者继承了什么都不会对它造成影响
成员内部类,定义在外部类中的成员位置。与类中的成员变量相似,可通过外部类对象进行访问
A:定义格式
class 外部类 {
修饰符 class 内部类 {
//其他代码
}
}
B:访问方式
1. 非静态情况:Outer.Inner I = new Outer().new Inner();
2. 静态:Outer.Inner I = new Outer .Inner();
局部内部类,定义在外部类方法中的局部位置。与访问方法中的局部变量相似,可通过调用方法进行访问
A:定义格式
class 外部类 {
修饰符返回值类型 方法名(参数) {
class 内部类 {
//其他代码
}
}
}
B:访问方式
在外部类方法中,创建内部类对象,进行访问
匿名内部类
A:作用:匿名内部类是创建某个类型子类对象的快捷方式。
B:格式:
new 父类或接口(){
//进行方法重写(抽象方法必须重写)
};
接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,接口是对行为的抽象,属于整个程序的规范。
接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
接口与抽象类的区别:
1.语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及普通方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将飞行这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将飞行设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
枚举是一种规范它规范了参数的形式,这样就可以不用考虑类型的不匹配并且显式的替代了int型参数可能带来的模糊概念 枚举像一个类,又像一个数组。
Enum作为Sun全新引进的一个关键字,看起来很象是特殊的class, 它也可以有自己的变量,可以定义自己的方法,可以实现一个或者多个接口。当我们在声明一个enum类型时,我们应该注意到enum类型有如下的一些特征。
1. 它不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的实例。
2. 所有枚举值都是public , static , final的。注意这一点只是针对于枚举值,我们可以和在普通类里面定义 变量一样定义其它任何类型的非枚举变量,这些变量可以用任何你想用的修饰符。
3 . Enum默认实现了java.lang.Comparable接口。
4.Enum覆载了了toString方法,因此我们如果调用Color.Blue.toString()默认返回字符串”Blue”.
5.Enum提供了一个valueOf方法,这个方法和toString方法是相对应的。调用valueOf(“Blue”)将返回Color.Blue.因此我们在自己重写toString方法的时候就要注意到这一点,一把来说应该相对应地重写valueOf方法。
6.Enum还提供了values方法,这个方法使你能够方便的遍历所有的枚举值。
7.Enum还有一个oridinal的方法,这个方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定,这里Color.Red.ordinal()返回0。
完整实例如下:
enum Color {
red("红色"), bule("蓝色"); //定义枚举实例(利用构造方法传递参数)
private String color; //定义成员变量
private Color(Stringcolor) { //定义构造方法,只能用private
this.color = color;
}
public String toString() { //定义或重写所需的方法
return "你选择的颜色是" + color;
}
}
使用到类中的内容时加载:有三种情况
1.创建对象:newStaticCode();
2.使用类中的静态成员:StaticCode.num=9; StaticCode.show();
3.在命令行中运行:javaStaticCodeDemo
利用语句进行分析:
创建对象 Person p=new Person("zhangsan",20);
该句话所做的事情:
1.在栈内存中,开辟main函数的空间,建立main函数的变量 p。
2.加载类文件:因为new要用到Person.class,所以要先从硬盘中找到Person.class类文件,并加载到内存中。
加载类文件时,除了非静态成员变量(对象的特有属性)不会被加载,其它的都会被加载。
记住:加载,是将类文件中的一行行内容存放到了内存当中,并不会执行任何语句。---->加载时期,即使有输出语句也不会执行。
静态成员变量(类变量)----->方法区的静态部分
静态方法 ----->方法区的静态部分
静态代码块 ----->方法区的静态部分
非静态方法(包括构造函数)----->方法区的非静态部分
构造代码块 ----->方法区的静态部分
注意:
在Person.class文件加载时,静态方法和非静态方法都会加载到方法区中,只不过要调用到非静态方法时需要先实例化一个对象,对象才能调用非静态方法。如果让类中所有的非静态方法都随着对象的实例化而建立一次,那么会大量消耗内存资源,所以才会让所有对象共享这些非静态方法,然后用this关键字指向调用非静态方法的对象。
3.执行类中的静态代码块:如果有的话,对Person.class类进行初始化。
4.开辟空间:在堆内存中开辟空间,分配内存地址。
5.默认初始化:在堆内存中建立对象的特有属性,并进行默认初始化。
6.显示初始化:对属性进行显示初始化。
7.构造代码块:执行类中的构造代码块,对对象进行构造代码块初始化。
8.构造函数初始化:对对象进行对应的构造函数初始化。
9.将内存地址赋值给栈内存中的变量p。
执行普通方法 p.setName("lisi");
1.在栈内存中开辟setName方法的空间,里面有:对象的引用this,临时变量name
2.将p的值赋值给this,this就指向了堆中调用该方法的对象。
3.将"lisi"赋值给临时变量name。
4.将临时变量的值赋值给this的name。
执行静态方法.Person.showCountry();
1.在栈内存中,开辟showCountry()方法的空间,里面有:类名的引用Person。
2.Person指向方法区中Person类的静态方法区的地址。
3.调用静态方法区中的country,并输出。
注意:要想使用类中的成员,必须调用。通过什么调用?有:类名、this、super
静态代码块:用于给类初始化,类加载时就会被加载执行,只加载一次。
构造代码块:用于给对象初始化的。只要建立对象该部分就会被执行,且优先于构造函数。
构造函数: 给对应对象初始化的,建立对象时,选择相应的构造函数初始化对象。
创建对象时,三者被加载执行顺序:静态代码块--->构造代码块--->构造函数
实例
父类属性引用对象:
package com.homework.labmanager.labmanager.utils;
import java.io.*;
/**
* @author sunyiran
* @date 2018-07-09
* @purpose
*/
public class F implements Cloneable, Serializable {
public F() {
System.out.println("F实例化");
}
}
子类属性引用对象
package com.homework.labmanager.labmanager.utils;
/**
* @author sunyiran
* @date 2018-07-10
* @purpose
*/
public class K {
public K() {
System.out.println("K实例化");
}
}
父类对象
package com.homework.labmanager.labmanager.utils;
/**
* @author sunyiran
* @date 2018-07-10
* @purpose
*/
public class Father {
F f = new F();
static {
System.out.println("父类静态代码块");
}
{
System.out.println("父类普通代码块");
}
Father() {
System.out.println("父类实例化");
}
static F f1 = new F();
}
子类对象
package com.homework.labmanager.labmanager.utils;
/**
* @author sunyiran
* @date 2018-07-09
* @purpose
*/
public class Kid extends Father{
K k = new K();
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类普通代码块");
}
Kid() {
System.out.println("子类实例化");
}
static K k1 = new K();
public static void main(String[] args) {
Kid k = new Kid();
}
}
最终结果
父类静态代码块
F实例化//父类静态属性
子类静态代码块
K实例化//子类静态属性
F实例化//父类普通属性
父类普通代码块
父类实例化//父类构造函数
K实例化//子类普通属性
子类普通代码块
子类实例化//子类构造函数
总结:
第一阶段:父类静态属性,与静态代码块,按自然顺序执行
第二阶段:子类静态属性,与静态代码块,按自然顺序执行
第三阶段:父类普通属性,父类普通代码块,按自然顺序执行
第四阶段:父类构造函数执行
第五阶段:子类普通属性,子类普通代码块,按自然顺序执行
第六阶段:子类构造函数执行