1、面向对象
面向对象(Object Oriented)是一种新兴的程序设计方法,或者是一种新的程序设计规范(paradigm),其基本思想是使用对象、类、继承、封装、多态等基本概念来进行程序设计。从现实世界中客观存在的事物(即对象)出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。简单一句话就是,面向对象是从人的角度出发解决问题。
2、对象
对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。简而言之,对象就是现实事物的抽象表达。对象的属性就是现实事物的属性(名词),对象的方法就是现实事物的行为(动词)。例如:一辆轿车可以抽象为一个对象,轿车有四个车轮的属性,可以抽象为轿车对象的属性,轿车加速减速等动作,可以抽象为轿车对象的方法。
类的实例化可生成对象(通常使用new语句实例化一个类,这个方式也可以叫获取类的实例),一个对象的生命周期包括三个阶段:生成、使用、消除。
当不存在对一个对象的引用时,该对象成为一个无用对象。Java的垃圾收集器自动扫描对象的动态内存区,把没有引用的对象作为垃圾收集起来并释放。当系统内存用尽或调用System.gc( )要求垃圾回收时,垃圾回收线程与系统同步运行。
3、类
类是具有相同属性和方法的一组对象的集合,它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和方法两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性和方法两个主要部分。简而言之,类是对象的模板。例如:小明有嘴,能吃饭(小明看成一个对象)。小红有嘴,能吃法(小红也看成一个对象)。小明小红这两个对象都属于人类这个类,人类这个模板就有嘴这个属性和吃法的方法。
Java中的类实现包括两个部分:类声明和类体。1:类声明
2:类体
成员变量就是属性,可以理解为对象的名词。
成员方法就是行为,可以理解为对象的动词。
例如:
public class People {//声明人这个类
String mouth;//成员属性:人都具有嘴这个属性(名词)
void eat() {//成员方法:人都具有吃饭这个行为(动词)
}
}
4:访问修饰词
- 访问控制权限
public:public修饰的类或成员变量、成员方法,可以被所有的类访问。
protected:protected修饰的类或成员变量、成员方法,可以被这个类本身、它的子类(包括同一个包中以及不同包中的子类)和同一个包中的所有其他的类访问。
缺省的:不加任何访问权限限定的成员及类属于缺省的(default)访问状态,可以被这个类本身和同一个包中的类所访问。
private:private修饰的类或成员变量、成员方法,只能被这个类本身访问。
- 访问控制和继承
请注意以下方法继承(继承概念的另附博文介绍)的规则:
父类中声明为public的方法在子类中也必须为public。
父类中声明为protected的方法在子类中要么声明为protected,要么声明为public。不能声明为private。
父类中缺省的修饰符声明的方法,能够在子类中声明为private。
父类中声明为private的方法,不能够被继承。
5:静态修饰符(static)
public class People {
// 调用可以为People.mouth
static String mouth;// 静态属性
// 调用可以为People.eat()
static void eat() {// 静态方法
}
}
6:最终属性final
变量和常量
- 变量
数据类型 变量名称;
例如:int x;
在该语法格式中,数据类型可以是Java语言中任意的类型,包括前面介绍到的基本数据类型以及后续将要介绍的复合数据类型。变量名称是该变量的标识符,需要符合标识符的命名规则,在实际使用中,该名称一般和变量的用途对应,这样便于程序的阅读。数据类型和变量名称之间使用空格进行间隔,空格的个数不限,但是至少需要1个。语句使用“;”作为结束。
也可以在声明变量的同时,设定该变量的值,语法格式如下:
数据类型 变量名称 = 值;
例如:int x = 10;
在该语法格式中,前面的语法和上面介绍的内容一致,后续的“=”代表赋值,其中的“值”代表具体的数据。在该语法格式中,要求值的类型需要和声明变量的数据类型一致。
在程序中,变量的值代表程序的状态,在程序中可以通过变量名称来引用变量中存储的值,也可以为变量重新赋值。例如:
int n = 5;
n = 10;
在实际开发过程中,需要声明什么类型的变量,需要声明多少个变量,需要为变量赋什么数值,都根据程序逻辑决定,这里列举的只是表达的格式而已。
- 常量
常量在程序运行过程中主要有2个作用:
1. 代表常数,便于程序的修改(例如:圆周率的值)
2. 增强程序的可读性(例如:常量UP、DOWN、LEFT和RIGHT分辨代表上下左右,其数值分别是1、2、3和4)
常量的语法格式和变量类型,只需要在变量的语法格式前面添加关键字final即可。在Java编码规范中,要求常量名必须大写。
则常量的语法格式如下:
final 数据类型 常量名称 = 值;
final 数据类型 常量名称1 = 值1, 常量名称2 = 值2,……常量名称n = 值n;
例如:
final double PI = 3.14;
final char MALE=‘M’,FEMALE=‘F’;
在Java语法中,常量也可以首先声明,然后再进行赋值,但是只能赋值一次,示例代码如下:
final int UP;
UP = 1;
final 关键字
final 用于声明属性(常量),方法和类,分别表示属性一旦被分配内存空间就必须初始化(不会有默认初始化,局部变量也是如此,默认初始化只有普通的非final成员属性,对于static(无final修饰)类变量,类连接时候有默认初始化,对于像private int a;在类实例化时,构造函数默认初始为0,总之,变量必须初始化后方可用,这是java的安全之一。final这个关键字的含义是“这是无法改变的”或者“终态的”;
那么为什么要阻止改变呢?
java语言的发明者可能由于两个目的而阻止改变:
1).效率问题:
jdk中的某些类的某些方法,是不允许被用户覆盖的,设计者可能认为,所用方法已经是最好的方法,用户私自覆盖,或是由于疏忽而覆盖,就会影响JVM或是系统的系能;
2). 设计所需:众所周知,有些情况必须使用final关键字,比如方法中的匿名内部类的参数传递(例如检查银行卡密码的方法,就不能被重写,需要final关键字修饰)
【修饰变量】:
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
【修饰方法】:
final方法不能被子类方法覆盖,但可以被继承。
【修饰类】:
final类不能被继承,没有子类,final类中所有方法都是final的。(如String类)- 被final修饰而没有被static修饰的类的属性变量只能在两种情况下初始化:(必须初始化)
b.在构造函数里初始化
解释:
当这个属性被修饰为final,而非static的时候,它属于类的实例对象的资源(实例常量),当类被加载进内存的时候这个属性并没有给其分配内存空间,而只是 定义了一个变量a,只有当类被实例化的时候这个属性才被分配内存空间,而实例化的时候同时执行了构造函数,所以属性被初始化了,也就符合了当它被分配内存 空间的时候就需要初始化,以后不再改变的条件.
- 被static修饰而没有被final修饰的类的属性变量只能在两种情况下初始化:(可以不初始化)
b.在静态或非静态快里初始化
解释:
当类的属性被同时被修饰为static时候,他属于类的资源(类变量),在类加载后,进行连接时候,分三步: 先验证;然后准备,准备时,先分配内存,接着默认初始化;可以进行解析。最后,进行类初始化,类初始化前,必须保证它的父类已经初始化了,所以最先初始化的是超类,对于接口,不必初始其父接口。类初始化时,它把类变量初始化语句及静态初始化语句放到类初始化方法中,所以,如果无此两种语句,也就没<clinit>类初始化方法,而构造函数是在当类被实例化的时候才会执行,所以用构造函数,这时候这个属性没有被初始化.程序就会报错.而static块是类被加载的时候执行,且只执行这一次,所以在 static块中可以被初始化.
- 同时被final和static修饰的类的属性变量只能在两种情况下初始化:(必须初始化)
b.在类的静态块里初始化
c.特别对于初始化时候调用抛出异常的构造函数,初始时候注意,特别是在实现单例模式时(只能这么初始化)
当类的属性被同时被修饰为static和final的时候,他属于类的资源(类常量),那么就是类在被加载进内存的时候(也就是应用程序启动的时候)就要已经为此属性分配了内存,所以此时属性已经存在,它又被final修饰,所以必须在属性定义了以后就给其初始化值.而构造函数是在当类 被实例化的时候才会执行,所以用构造函数,这时候这个属性没有被初始化.程序就会报错.而static块是类被加载的时候执行,且只执行这一次,所以在 static块中可以被初始化.
java中的 final变量==常量
【final变量的变与不变】:final表示变量的值或引用不变有人说final变量在赋值后就不可变,此变量可以是基本数据类型+String或者是对象
那么这个不变到底指的是什么呢?
这个不变指的是引用,是地址,而所引用的对象的内容仍然是可变的。注:如果为对象,注意此时类初始化条件
就是说,这个final变量永远指向某个对象,是一个常量指针,而不是指向常量的指针。
【final关键字的具体应用】:
【final+变量】:
在实际应用中,这种形式是非常少见的。
比如logger是可以的,但是貌似并不是非常实用,或许用户仍然希望通过setter来改变logger变量。
【static+final+变量】:
常量。经常使用。
【final+方法】:
JDK中常用。
【final+类】:
helper类经常使用。
【final用于匿名内部类的参数传递】:
在多线程测试时,经常使用。
【final用于方法的参数】:
并不常用。
延伸:
在interface里面的变量都是public static final 的。所以你可以这样写:
public static final int i=10;等价于 int i=10;(可以省略掉一部分)注意在声明的时候要给变量赋予初值
解释:首先要弄清接口的含义.接口就是提供一种统一的’协议’,而接口中的属性也属于’协议’中的成员.它们是公共的,静态的,最终的常量.相当于全局常量.
抽象类是不’完全’的类,相当于是接口和具体类的一个中间层.即满足接口的抽象,也满足具体的实现.
如果接口可以定义变量,但是接口中的方法又都是抽象的,在接口中无法通过行为来修改属性。有的人会说了,没有关系,可以通过实现接口的对象的行为来修改接口中的属性。这当然没有问题,但是考虑这样的情况。如果接口A中有一个public访问权限的静态变量a。按照java的语义,我们可以不通过实现接口的对象来访问变量a,通过A.a = xxx;就可以改变接口中的变量a的值了。正如抽象类中是可以这样做的,那么实现接口A的所有对象也都会自动拥有这一改变后的a的值了,也就是说一个地方改变了a,所有这些对象中a的值也都跟着变了。如果可以修改值:这和抽象类有什么区别呢,怎么体现接口更高的抽象级别呢,怎么体现接口提供的统一的协议呢,那还要接口这种抽象来做什么呢?所以接口中不能出现变量,如果有变量,就和接口提供的统一的抽象这种思想是抵触的。所以接口中的属性必然是常量,只能读不能改,这样才能为实现接口的对象提供一个统一的属性。
通俗的讲,认为是要变化的东西,就放在实现中,不能放在接口中去,接口只是对一类事物的属性和行为更高层次的抽象。对修改关闭,对扩展(不同的实现implements)开放,接口是对开闭原则的一种体现。(接口具体概念,另附博文介绍)7:方法
真如前文所述,类体是由方法的实现也包括两部分内容:方法声明和方法体。
- 方法声明
按照方法有无返回值,有无参数,可以分为四类
- 无参无返回值
基本结构:修饰符 void 方法名(){} 其中void 关键字表示无返回值,方法名后的()为空表示无参数。例如:
public void eat() {// 无参无返回值的成员方法 }
- 无参有返回值
基本结构:修饰符 返回值类型 方法名(){} 其中{}内会有return关键字,表示返回值,方法名后的()为空表示无参数。例如:
public int eat() {// 无参有返回值的成员方法 return 1; }
- 有参无返回值
基本结构:修饰符 void 方法名(参数类型 参数名){} 其中void 关键字表示无返回值。例如:
public void eat(String a) {// 有参无返回值的成员方法 }
- 有参有返回值
基本结构:修饰符 返回值类型 方法名(参数类型 参数名){} 。例如:
public int eat(String a) {// 有参有返回值的成员方法 return 1; }
延伸:
Java中的参数传递方式
无论是什么语言,要讨论参数传递方式,就得从内存模型说起,这里主要从内存模型来说参数传递更为直观一些。闲言少叙,下面通过内存模型的方式来讨论一下Java中的参数传递。
这里的内存模型涉及到两种类型的内存:栈内存(stack)和堆内存(heap)。基本类型作为参数传递时,传递的是这个值的拷贝。无论怎么改变这个拷贝,原值是不会改变的。看下边的一段代码,然后结合内存模型来说明问题:
public class Text {
public static void add(int param) {
param = 100;
}
public static void main(String[] args) {
int num = 30;
System.out.println("调用add方法前num=" + num);
add(num);
System.out.println("调用add方法后num=" + num);
}
}
运行结果:
程序运行的结果也说明这一点,无论你在add()方法中怎么改变参数param的值,原值num都不会改变。下边通过内存模型来分析一下。
当执行了int num = 30;这句代码后,程序在栈内存中开辟了一块地址为AD8500的内存,里边放的值是30,内存模型如下图:
执行到add()方法时,程序在栈内存中又开辟了一块地址为AD8600的内存,将num的值30传递进来,此时这块内存里边放的值是30,执行param = 100;后,AD8600中的值变成了100。内存模型如下图:
地址AD8600中用于存放param的值,和存放num的内存没有任何关系,无论怎么改变param的值,实际改变的是地址为AD8600的内存中的值,而AD8500中的值并未改变,所以num的值也就没有改变。
以上是基本类型参数的传递方式,下来讨论一下对象作为参数传递的方式。
先运行以下代码:
public class Text {
public static void main(String[] args) {
String[] array = new String[] { "huixin" };
System.out.println("调用reset方法前array中的第0个元素的值是:" + array[0]);
reset(array);
System.out.println("调用reset方法后array中的第0个元素的值是:" + array[0]);
}
public static void reset(String[] param) {
param[0] = "hello, world!";
}
}
运行结果:
当对象作为参数传递时,传递的是对象的引用,也就是对象的地址。下边用内存模型图来说明。
当程序执行了String[] array = new String[] {“huixin”}后,程序在栈内存中开辟了一块地址编号为AD9500内存空间,用于存放array[0]的引用地址,里边放的值是堆内存中的一个地址,示例中的值为BE2500,可以理解为有一个指针指向了堆内存中的编号为BE2500的地址。堆内存中编号为BE2500的这个地址中存放的才是array[0]的值:huixin。
当程序进入reset方法后,将array的值,也就是对象的引用BE2500传了进来。这时,程序在栈内存中又开辟了一块编号为AD9600的内存空间,里边放的值是传递过来的值,即AD9600。可以理解为栈内存中的编号为AD9600的内存中有一个指针,也指向了堆内存中编号为BE2500的内存地址,如图所示:
这样一来,栈内存AD9500和AD9600(即array[0]和param的值)都指向了编号为BE2500的堆内存。
在reset方法中将param的值修改为hello, world!后,内存模型如下图所示:
改变对象param的值实际上是改变param这个栈内存所指向的堆内存中的值。param这个对象在栈内存中的地址是AD9600,里边存放的值是BE2500,所以堆内存BE2500中的值就变成了hello,world!。程序放回main方法之后,堆内存BE2500中的值仍然为hello,world!,main方法中array[0]的值时,从栈内存中找到array[0]的值是BE2500,然后去堆内存中找编号为BE2500的内存,里边的值是hello,world!。所以main方法中打印出来的值就变成了hello,world!
无论是基本类型作为参数传递,还是对象作为参数传递,实际上传递的都是值,只是值的的形式不用而已。第一个示例中用基本类型作为参数传递时,将栈内存中的值30传递到了add方法中。第二个示例中用对象作为参数传递时,将栈内存中的值BE2500传递到了reset方法中。当用对象作为参数传递时,真正的值是放在堆内存中的,传递的是栈内存中的值,而栈内存中存放的是堆内存的地址,所以传递的就是堆内存的地址。这就是它们的区别。
注意,在Java中,String是一个引用类型,但是在作为参数传递的时候表现出来的却是基本类型的特性,即在方法中改变了String类型的变量的值后,不会影响方法外的String变量的值。
- 方法体
方法体是对方法的实现(方法声明中的{}内),如果方法声明为有返回值,方法体中的返回值须与声明返回类型一致,或者完全相同,或是其子类。当返回类型是接口时,返回值必须实现该接口。
方法体还包括局部变量(不懂可以先行跳过,博客后文详细介绍)的声明以及所有合法的Java指令。方法体中声明的局部变量的作用域在该方法内部。若局部变量与类的成员变量同名,则类的成员变量被隐藏。
为了区别参数和类的成员变量,必须使用this关键字。this用在一个方法中引用当前对象,它的值是调用该方法的对象。
this关键字
- this 关键字用来表示当前对象本身,或当前类的一个实例,通过 this 可以调用本对象的所有方法和属性。例如:
运行结果:public class Text { public int x = 10; public int y = 15; public void sum() { // 通过 this 获取成员变量 int z = this.x + this.y; System.out.println("x + y = " + z); } public static void main(String[] args) { Text obj = new Text(); obj.sum(); } }
x + y = 25
上面的程序中,obj 是 Text类的一个实例,this 与 obj 等价,执行 int z = this.x + this.y;,就相当于执行 int z = obj.x + obj.y;。
注意:this 只有在类实例化后才有意义。 - 成员变量与方法内部的变量重名时,希望在方法内部调用成员变量。这时候只能使用this,例如:
public class Text { public String name; public int age; public Text(String name, int age) { this.name = name; this.age = age; } public void say() { System.out.println("网站的名字是" + name + ",已经成立了" + age + "年"); } public static void main(String[] args) { Text obj = new Text("百度", 3); obj.say(); } }
结果:
网站的名字是百度,已经成立了3年
形参的作用域是整个方法体,是局部变量。在Demo()中,形参和成员变量重名,如果不使用this,访问到的就是局部变量name和age,而不是成员变量。在 say() 中,没有使用 this,因为成员变量的作用域是整个实例,当然也可以加上 this:
Java 默认将所有成员变量和成员方法与 this 关联在一起,因此使用 this 在某些情况下是多余的。public void say(){ System.out.println("网站的名字是" + this.name + ",已经成立了" + this.age + "年"); }
- 作为方法名来初始化对象:调用本类的其它构造方法(不懂可以先行跳过,博客后文详细介绍),它必须作为构造方法的第一句。例如:
public class Text { public String name; public int age; public Text(){ this("百度", 3); } public Text(String name, int age){ this.name = name; this.age = age; } public void say(){ System.out.println("网站的名字是" + name + ",已经成立了" + age + "年"); } public static void main(String[] args) { Text obj = new Text(); obj.say(); } }
结果:
网站的名字是百度,已经成立了3年
值得注意的是:
在构造方法中调用另一个构造方法,调用动作必须置于最起始的位置。
不能在构造方法以外的任何方法内调用构造方法。
在一个构造方法内只能调用一个构造方法。
上述代码涉及到方法重载(不懂可以先行跳过,博客后文详细介绍)。
- 作为参数传递:需要在某些完全分离的类中调用一个方法,并将当前对象的一个引用作为参数传递时。例如:
public class Text { public static void main(String[] args) { B b = new B(new A()); } } class A { public A() { new B(this).print(); // 匿名对象 } public void print() { System.out.println("Hello from A!"); } } class B { A a; public B(A a) { this.a = a; } public void print() { a.print(); System.out.println("Hello from B!"); } }
运行结果:
Hello from A!
Hello from B!
匿名对象就是没有名字的对象。如果对象只使用一次,就可以作为匿名对象,代码中 new B(this).print(); 等价于 ( new B(this) ).print();,先通过 new B(this) 创建一个没有名字的对象,再调用它的方法。
8:构造方法(构造器)
构造方法是一个特殊的方法。Java 中的每个类都有构造方法,用来初始化该类的一个对象。
默认隐藏的构造方法为:public void 类名(){}
构造方法具有和类名相同的名称,而且不返回任何数据类型。
构造方法只能由new运算符调用。
延伸:
- 没有继承的情况
单独一个类的情况下,初始化顺序为依次为静态数据,成员变量,被调用的构造函数。其中静态数据只会初始化一次。
运行结果:public class Text { public static void main(String[] args) { Son son = new Son();// 调用Son这个类 } } class Son { public Son() {// 构造方法 System.out.println("加载构造方法."); } public Height height = new Height(1.8f);// 成员变量 public static Gender gender = new Gender(true);// 静态变量 } class Height { public Height(float height) { System.out.println("加载成员变量:初始身高为: " + height + " 米."); } } class Gender { public Gender(boolean isMale) { if (isMale) { System.out.println("加载静态变量:他是男孩!"); } else { System.out.println("加载静态变量:她是女孩!"); } } }
- 存在继承的情况
祖父类代码:
public class Grandpa {
public Grandpa() {
System.out.println("加载祖父类构造方法。");
}
private Height height = new Height(1.5f,"祖父类");
public static Gender gender = new Gender(true, "祖父类");
}
父类代码:
public class Father extends Grandpa {
public Father() {
System.out.println("加载父类构造方法");
}
private Height height = new Height(1.6f,"父类");
public static Gender gender = new Gender(true, "父类");
}
子类代码:
public class Son extends Father {
public Son() {//构造方法
System.out.println("加载子类构造方法。");
}
private Height height = new Height(1.8f,"子类");//成员变量
public static Gender gender = new Gender(true, "子类");//静态变量
}
测试类代码:
public class Text {
public static void main(String[] args) {
Son son = new Son();// 加载Son这个有继承关系的类
}
}
class Height {
public Height(float height, String identify) {
System.out.println("加载" + identify + "的成员变量:" + "初始身高为:" + height + " 米。");
}
}
class Gender {
public Gender(boolean isMale) {
if (isMale) {
System.out.println("他是男孩!");
} else {
System.out.println("她是女孩!");
}
}
public Gender(boolean isMale, String identify) {
if (isMale) {
System.out.println("加载" + identify + "的静态变量:" + " 男孩.");
} else {
System.out.println("加载" + identify + "的静态变量:" + " 女孩.");
}
}
}
运行结果:
总结:
1:静态优先。类加载第一步,先父类静态变量,再子类静态变量。当父类存在父类时,递归至基类(先加载祖先的静态变量,再按继承链依次加载静态变量)。
2:静态变量加载完毕,先父类加载全部。在加载子类全部。当父类存在父类时,递归至基类(先加载祖先的全部,再按继承链依次加载其子类的全部)。
3:在同等类非静态级别中,成员变量优先于构造方法。
4:类的加载顺序与代码的前后顺序无关。
9:变量作用域
- 类级变量又称全局级变量或静态变量,需要使用static关键字修饰。类级变量在类定义后就已经存在,占用内存空间,可以通过类名来访问,不需要实例化。
- 对象实例级变量就是成员变量,实例化后才会分配内存空间,才能访问。
- 方法级变量就是在方法内部定义的变量,就是局部变量,只能在该方法内访问。
- 块级变量就是定义在一个块内部的变量,变量的生存周期就是这个块,出了这个块就消失了,比如 if、for 语句的块。块是指由大括号包围的代码。
运行结果:public class Text{ public static String name = "百度"; // 类级变量 public int i; // 对象实例级变量 // 属性块,在类初始化属性时候运行 { int j = 2;// 块级变量 } public void test1() { int j = 3; // 方法级变量 if(j == 3) { int k = 5; // 块级变量 } // 这里不能访问块级变量,块级变量只能在块内部访问 System.out.println("name=" + name + ", i=" + i + ", j=" + j); } public static void main(String[] args) { // 不创建对象,直接通过类名访问类级变量 System.out.println(Text.name); // 创建对象并访问它的方法 Text t = new Text(); t.test1(); } }
10:instanceof 运算符
public final class Text{
public static void main(String[] args) {
// 引用 People 类的实例
Person obj = new Person();
if(obj instanceof Object){
System.out.println("我是一个对象");
}
if(obj instanceof Person){
System.out.println("我是人类");
}
if(obj instanceof Teacher){
System.out.println("我是一名教师");
}
if(obj instanceof President){
System.out.println("我是校长");
}
System.out.println("-----------"); // 分界线
// 引用 Teacher 类的实例
obj = new Teacher();
if(obj instanceof Object){
System.out.println("我是一个对象");
}
if(obj instanceof Person){
System.out.println("我是人类");
}
if(obj instanceof Teacher){
System.out.println("我是一名教师");
}
if(obj instanceof President){
System.out.println("我是校长");
}
}
}
class Person{}
class Teacher extends Person{ }
class President extends Teacher{ }
运行结果:
我是人类
———–
我是一个对象
我是人类
我是一名教师