面向对象
1.面向对象的主要特征
- 抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象包括两个方面:一个是过程抽象;二是数据抽象。
- 继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,他提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类(派生类,也称子类)继承了原始类(基类,也称父类)的特性。派生类继承了基类的方法和实例变量,并且派生类可以修改或增加新的方法,使之更适合特殊的需求。
- 封装:封装就是指客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
- 多态:多态是指允许不同类的对象对同一消息做出响应。多态包括参数化多态和包含多态。多态性语言具有灵活、抽象行为共享、代码共享等优势,很好地解决了应用程序函数同名问题。
2.继承和组合
组合式指在新类里面创建原有类的对象,重复利用已有类的功能。继承允许设计人员根据其他类的实现来定义一个类的实现。组合都允许在新的类中设置子对象。组合和继承存在着对应关系:组合中的整体类和继承中的子类对应,组合中的局部类和继承的父类对应。
二者区别:如,Car表示汽车对象,Vehicle表示交通工具对象,Tire表示轮胎对象。Car是Vehicle的一种,因此是一种继承关系(又被称为"is - a"关系);而Car包含了多个Tire,因此是一种组合关系(又被称为"has - a"关系):
继承 | 组合 |
---|---|
class Vehicle{} class Car extends Vehicle{} |
class Tire{} class Car extends Vehicle{ private Tire t = new Tire(); } |
选择:
- 除非两个类之间是"is - a"的关系,否则不要轻易地使用继承。当父类被修改时,会影响到所有继承自它的子类,从而增加程序的维护难度与成本。
- 如果类之间没有"is - a"的关系,可以通过接口与组合方式来达到相同的目的。策略设计模式采用接口与组合的方式比采用继承的方式具有更好的可扩展性。
能使用组合就尽量不要使用继承。
3.多态的实现机制
表示当同一个操作作用在不同对象时,会有不同的语义,从而会产生不同的结果。多态主要有两种表现方式:
- 方法的重载(overload):重载是指同一个类中有多个同名的方法,但这些方法有着不同的参数(参数个数和类型、顺序都不相同),因此在编译时就可以确定到底调用哪个方法,它是一种编译时多态。重载可看作一个类中的方法多态性。
- 方法的覆盖(override):子类可以覆盖(重写)父类的方法,因此同样的方法(参数个数和类型都相同)会在父类与子类又不同的表现形式。在Java中,基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象。同样,接口的引用变量也可以指向其实现类的实例对象。而程序调用的方法在运行期才动态绑定(绑定指的是将一个方法调用和一个方法主体连接到一起),就是引用变量所指向的具体实例对象的方法。只有在运行时才决定调用哪个方法,因此被称为运行时多态。如下例:
class Base{
public Base() {
g();
}
public void f(){
System.out.println("Base f()");
}
public void g(){
System.out.println("Base g()");
}
}
class Derived extends Base{
public void f(){
System.out.println("Derived f()");
}
public void g(){
System.out.println("Derived g()");
}
}
public class Test{
public static void main(String []args){
Base b = new Derived();
b.f();
b.g();
}
}
程序运行结果是:
Derived g()
Derived f()
Derived g()
注:只有类中的方法才有多态的概念,类中成员变量没有多态的概念。成员变量的值取决于所定义变量的类型,这是在编译期间确定的
注意:
- 对于继承来说,如果基类方法的访问权限为private,那么就不能在派生类对其重载;如果派生类也定义了一个同名函数,这只是一个新的方法,不会达到重载的效果。
- 派生类中的覆盖方法的返回值必须和基类中被覆盖的方法的返回值相同。
- 派生类中的覆盖方法所抛出的异常必须和基类(或是其子类)中被覆盖的方法所抛出的异常一致。
- 基类中被覆盖的方法不能为private,否则其子类只是定义了一个方法,并没有对其覆盖。
重载和覆盖的区别:
- 覆盖是子类和父类之间的关系,是垂直关系;重载是同一个类中方法之间的关系,是水平关系。
- 覆盖只能由一个方法或只能有一对方法产生关系;重载是多个方法之间的关系。
- 覆盖要求参数列表相同;重载要求参数列表、类型、顺序不同。
- 覆盖关系中,调用方法体是根据对象的类型(对象对应存储空间类型)来决定;而重载关系是根据调用时的实参表和形参表来选择方法体的。
注:函数是不能以返回值来区分的,虽然父类和子类中的函数有着不同的返回值,但是它们有着相同的函数名,因此,编译器无法区分。
4.抽象类和接口的异同
在Java语言中,可以把类或类中的方法声明为abstract(只能修饰类或方法,不能修饰属性)来表示一个类是抽象类。接口是指一个方法的集合,接口中的所有方法都没有方法体,通过关键字interface来实现。
- 只要包含一个抽象方法的类就必须被生命为抽象类,抽象类可以声明方法的存在而不去实现它,被声明为抽象的方法不能包含方法体。
- 在实现时,必须包含相同的或者耕地的访问级别(public>protected>private)。
- 抽象类在使用过程中,不能被实例化,但是可以创建一个对象使其指向具体子类的一个实例。
- 抽象类的子类为父类中的所有抽象方法提供具体的实现,否则它们也是抽象类。
- 接口中的所有方法都是抽象的,可以通过接口来间接实现多重继承。
- 接口中的成员变量都是static final类型。
- 由于抽象类可以包含部分方法的实现,因此,抽象类比接口有更多的优势。
接口与抽象类的不同点:
- 接口只有定义,其方法不能在接口中实现,只有实现接口的类才能实现接口中定义的方法,而抽象类可以定义域实现某个方法。
- 接口需要实现(implements),但抽象类只能被继承(extends)。
- 接口强调特定功能的实现,其设计理念是"has - a"关系;而抽象类强调所属关系,其设计理念为"is - a"关系。
- 接口中定义成员变量默认为public static final,只能够有静态的不能被修改的数据成员,而且必须赋值,其所有成员方法都是public、abstract的,而且只能被这两个关键字修饰。而抽象类可以有自己的数据成员变量,也可以有非抽象的成员方法,而且,抽象类中的成员变量默认是default,也可以定义private、protected和public,这些成员变量可以在子类中被重新定义,也可以被重新赋值,抽象类中的抽象方法(abstract修饰)不能用private、static、synchronized、native等访问修饰符修饰,同时方法必须以分号结尾,并且不带花括号。当功能需要累积时,用抽象类;不需要累积时,用接口。
- 接口被运用于实现毕竟常用的功能,便于日后维护或添加删除方法;而抽象类更倾向于充当公共类的角色,不适合用于日后重新对里面的代码进行修改。
注:
- 抽象类多用在同类事物中又无法具体描述的方法的场景,所以当子类和父类之间存在有逻辑上的层次结构是,使用抽象类;
- 接口多用在不同类之间,定义不同类之间的通信规则,所以当希望支持差别较大的两个或者更多对象之间的特定交互行为时,应该使用接口。
- 接口可以继承接口,抽象类可以实现接口,抽象类也可以继承具体类。抽象类也可以有静态的main方法。
5.内部类
主要有4种:静态内部类、成员内部类、局部内部类、匿名内部类。
- 静态内部类是指被声明为static的内部类,不依赖于外部类实例而被实例化。不能跟外部类有相同的名字,不能访问外部类的普通成员变量,只能访问外部类的静态成员和方法(包括私有类型)。
- 成员内部类可以自由地引用外部类的属性和方法,无论这些属性和方法是静态的还是非静态的。它与一个实例绑定在了一起,不可以定义静态的属性和方法。只有在外部的类被实例化后,这个内部类才能被实例化。非静态内部类中不能有静态成员。
- 局部内部类像局部变量一样,不能被public、protected、private以及static修饰,只能访问方法中定义为final类型的局部变量。
- 匿名内部类是一种没类名的内部类,必须继承其他类或者实现其他接口。
5.this与super区别
在Java语言中,this用来指向当前实例对象,它的一个非常重要的作用就是用来区分对象的成员变量与方法的形参。(如:this.name = name)。
super可以用来访问父类的方法或成员变量。当子类的方法或成员变量与父类有相同的名字时也会覆盖父类的方法或成员变量,要想访问父类的方法或成员变量只能通过super关键字来访问。
注:当子类构造函数需要显示调用父类构造函数时,super()必须为构造函数中的第一条语句。
6.continue、break、return
- continue用于停止当次循环,回到循环起始处,进入下一次循环操作。
- break用于直接强行跳出当前循环,不在执行剩余代码。如下代码可跳出多层循环:
public class Break {
public static void main(String args[]){
out:
for(int i = 0;i < 5; i++) {
for(int j=0;j<5;j++) {
if(j>=2) {
break out;
System.out.println(j);
}
}
}
}
}
7.final、finally、finalize
- final用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖和类不可被继承(不能再派生出新的子类)。
被final修饰的变量不可变:一是引用不可变;二是对象不可变。final指的是引用的不可变性,即它只能指向初始时指向的那个对象,而不关心指向对象内容的变化。所以,被final修饰的变量必须被初始化。初始化方式:- 在定义时初始化;
- final成员变量可以在初始化块中初始化,但不可在静态初始化块中初始化;
- 静态final成员变量可以在静态初始化块中初始化,但不可在初始化块中初始化;
- 在类的构造器重初始化,但静态final成员变量不可在构造函数中初始化。
final方法:当一个方法声明为final时,该方法不允许任何子类重写这个方法,但子类仍然可以使用这个方法。另外,有一种称为inline(内联)机制,当调用也给被声明为final的方法时,直接将方法主体插入到调用处,而不是进行方法调用,这样提高程序效率。
final参数:用来表示这个参数在这个函数内部不允许修改。
final类:当一个类被声明为final时,此类不能被继承,所有方法不能被重写(覆盖)。但是要想final类的成员变量不可改变,必须给成员变量增加final修饰,不然可以改变。一个类不能被声明为final,同时又被声明为final。
- finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定被执行,经常被用在需要释放资源的情况下。
- finalize是Object类的一个方法,在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法实现对其他资源的回收,例如关闭文件等。
8.断言assert
它的作用是对也给boolean表达式进行检查,一个正在运行的程序必须保证这个boolean表达式的值为true,若boolean表达式的值为false,则说明程序已经处于一种不正确的状态下,系统需要提供告警信息并且退出程序。一般在调试程序时使用。
有两种表达式:1.assert expression1 与 assert expression1 : expression2,其中,expression1表示一个boolean表达式,expression2表示一个基本类型或者是一对象,基本类型包括:boolean、char、double、float、int和long。
public class Test {
public static void main(String []args){
assert 1 + 1 == 2;
System.out.println("assert1 ok");
assert 1 + 1 == 3 : "assert faild,exit."
System.out.println("assert2 ok");
}
}
当执行指令java -ea Test时,程序的输出结果为:
assert1 ok
Exception in thread "main" Java.lang.AssertionError:assert faild,exit at Test.main(Test.java:5)
应用范围包括:
- 检查控制流;
- 检查输入参数是否有效;
- 检查函数结果是否有效;
- 检查程序不变量。
9.static关键字
主要有两种作用:第一,为某种特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。第二,实现某个方法或属性与类而不是对象关联在一起,即在不创建对象的情况下就可以通过类来直接调用方法或使用类的属性。
- static成员变量
Java语言中,可通过static关键字来达到全局的效果。静态变量属于类,在内存中只有也给复制(所有实例都指向同一个内存地址),只要静态变量所在的类被加载,这个静态变量就会被分配空间,这样就可以被使用。静态变量只有一个,被类拥有,而实例对象是与具体对象有关的(对象的引用)。Java中,不能再方法体中定义static变量。 - static成员方法
与静态成员变量类似,是类的方法,不需要创建对象就可以被调用。不能使用this和super关键字,不能调用非static方法和非static成员变量,只能访问所属类的静态成员变量和成员方法。
static一个很重要的用途是实现单例模式。单例模式的特点是该类只能有一个实例,为了实现这一功能,必须隐藏类的构造函数,即把构造函数声明为private,并提供一个创建对象的方法,由于构造对象被声明为private,外界无法直接创建这个类型的对象,只能通过该类提供的方法来获取类的对象,要达到这样的目的只能把创建对象的方法声明static,代码如下:
class Singleton{
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance(){
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
用public修饰的static变量和方法本质上都是全局的,若在static变量前用private修饰,则表示这个变量可以在类的静态代码块或者类的其他静态成员方法中使用,但是不能在其他类中通过类名来直接引用。
-
static代码块
static代码块在类中是独立于成员变量和成员函数的代码块的,它不再任何一个方法体内,JVM在加载类时会执行static代码块,如果有多个static代码块,JVM按顺序执行。static代码块经常被用来初始化静态变量,只会被执行一次。 -
static内部类
指被声明为static的内部类,它可以不依赖于外部类实例对象而被实例化,而通常的内部类需要在外部类实例化才能实例化。不能与外部类有相同名字,不能访问外部类的普通成员变量,只能访问外部类的静态成员和静态方法(包括私有类型)。
值传递与引用传递
- 值传递
在方法中调用中,实参会把它的值传递给形参,形参只是实参的值初始化一个临时的存储单元,因此形参与实参虽然有着相同的值,但是却有着不同的存储单元,因此对形参的改变不会影响实参的值 - 引用传递
在方法调用中,传递的是对象(也可以看做是对象的地址),这是形参与实参的对象指向同一块存储单元,因此对形参的修改就会影响实参的值。
在Java中,原始数据类型(int、short、long、byte、char、float、double、boolean)在传递参数时都是按值传递,而包装类型(Integer、String、Short、Byte、Long、Float、Double,Character、Boolean为不可变量)传递参数时是按引用传递。按引用传递其实与传递指针类似,是把对象的地址作为参数的。
public class Test{
public static void changeStringBuffered(StringBuffer ss1, StringBuffer ss2) {
ss1.append(" world");
ss2 = ss1; //ss1的地址赋值给ss2了
}
public static void main(String []args){
Integer a = 1;
Integer b = a;
b++;
System.out.println(a);
System.out.println(b);
StringBuffer s1 = new StringBuffer("Hello");
StringBuffer s2 = new StringBuffer("Hello");
changeStringBuffered(s1, s2);//把s1、s2的地址传递给形参ss1、ss2
System.out.println(s1);
System.out.println(s2;
}
}
程序运行结果为:
1
2
Hello World
Hello //因为s2的值没变,只是ss2的地址指向s1的了