[关闭]
@Sakura-W 2016-07-17T12:10:28.000000Z 字数 10252 阅读 2621

Java基础(二)之Java面向对象知识

Java

---参考《Java核心技术卷》

一、面向对象概述

1.在OOP中,不必关心对象的具体实现,只要能够满足用户的需求即可

2.在结构化程序设计中,首先确定如何操作数据,然后再决定如何组织数据,以便于数据操作;而在OOP中,将数据放在第一位,然后再考虑操作数据的算法。

二、对象与类

1、概述

1.1类
1)类是构造对象的模板或蓝图,由类构造对象的过程称为创建类的实例。
2)封装:将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域,操纵数据的过程称为方法。实现封装的关键在于绝对不能让类中的方法直接访问其他类的实例域。
3)继承:通过扩展一个类来建立另外一个类的过程。扩展后的新类具有所扩展类的全部属性和方法。

1.2对象
对象的主要三个特性:

对象的行为: 用可调用的方法定义的
对象的状态: 保存着描述当前特征的信息。(对象的状态的改变必须由调用方法实现以确定封装性)
对象标识: 用于辨别具有相同行为和状态的不同对象(每一个类的对象的标识永远不同)

1.3类之间的关系

依赖(uses-a): 一个类的方法操纵另一个类的对象
聚合(has-a):类A的对象包含类B的对象(类B的对象作为类A的对象的一个实例域)
继承(is-a):表示一般与特殊的关系

2、使用预定义的类

2.1对象和对象变量
在Java中使用构造器(constructor)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。

new Date()//构造新的对象
Date birthday = new Date();//将对象的引用存放到对象变量中
System.out.print(birthday);//打印时间

一个对象变量并没有包含一个对象,而仅仅是引用一个对象。在Java中,任何对象变量的值都是对存储在另一个地方的一个对象的引用。new操作符返回值也是一个引用。

Date deadline = null;//显式地将对象变量设置微null,表明这个对象变量目前没有引用任何对象。

局部变量不会自动初始化为null,而必须通过调用new或者显式地设置为null进行初始化。

2.2Java类库中的GregorianCalendar类
1)表示时间的类:

Date类  表示时间点
GregorianCalendar类  表示日历
Calendar类  继承了GregorianCalendar类,更通用

2)Date类常用方法:

before()
after()//两个方法用于比较时间点的先后顺序

3)GregorianCalendar类构造器:

new GregorianCalendar()//构造一个新对象
new GregorianCalendar(1995,11,12)//传入年、月、日构造日历对象(月份:0表示1月,11表示12月)

日历的作用是提供某个时间点的年、月、日等信息。

2.3更改器方法和访问器方法
对实例域做出修改的方法称为更改器方法,如set()、add()方法
仅仅访问实例域而不进行修改的方法称为访问器方法,如get()方法

Date对象与GregorianCalendar对象相互转换:

Date hireday = calendar.getTime();//创建一个包含calendar信息的Date对象
calendar.setTime(hireday);//获取Date对象的年、月、日信息

3、自定义类

3.1相关概念
1)源文件名必须与public类的名字相匹配。在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类。
2)在编译时,每一个类都会产生一个类文件,无论这个类是公有还是非公有

3.2类的访问权限

public修饰  任何类的任何方法都可以调用
private修饰  类自身的方法才能访问

3.3构造器

1)构造器与类同名
2)每个类可以有一个以上的构造器
3)构造器可以有0、1个或多个参数
4)构造器没有返回值
5)构造器总是伴随new操作符一起调用

3.4封装性
不要编写返回引用可变对象的访问器方法

public Date getDate(){
    return Date date;//会引起封装性的破坏
}

3.5final实例域
将实例域定义为final后,构建对象时必须初始化这样的域,并且在后面的操作中不能对其进行修改。

4、静态域和静态方法

4.1静态域
用static修饰符修饰的域是静态域。静态域属于类而不属于任何独立的对象,即所有类的实例共享同一个静态域。

4.2静态常量

public static final double PI = 3.1415926;//Math类中定义的PI常量

使用Math.PI获得这个常量。
另一个常用的静态常量是System.out

public class System{
    public static final PrintStream out = ...;
}

实例域一般声明为private,而公有常量(final)却没问题,因为final类型的域不可更改。

4.3静态方法
静态方法可以通过对象调用(但不推荐),静态方法中没有this参数,即没有隐式参数。静态方法不能访问类的实例域,因为它不能操作对象。

Java中static的含义:属于类但不属于类对象的变量和函数。

4.4方法参数

方法参数的调用方式

按值调用 表示方法接收的是调用者提供的值
按引用调用 表示方法接收的是调用者提供的变量地址

Java程序设计语言总是按值调用,也就是说方法得到的是所有参数值的一个拷贝。
当参数是对象的引用时,方法得到的是对象引用的拷贝,对象引用及其他拷贝同时引用同一个对象

Employee a = new Employee();
Employee b = new Employee();
swap(a,b);//由于Java中方法都是按值传递,所以无论swap方法是怎样的,变量a和b中存储的对象引用并不会变

5、对象构造

5.1重载
方法的签名:方法名以及参数类型。返回类型不是方法签名的一部分,也就是说,不能有两个名字相同、参数类型也相同却返回不同类型值的方法。

重载:两个方法的方法名相同但参数类型或者参数的个数不同。

5.2默认域初始化
如果构造器没有显式地给域(包含实例域和静态域)赋初值,则就会赋默认值:数值为0、布尔值为false、对象为null。而方法中的局部变量必须显式地初始化。

5.3无参数的构造器
可以在无参数的构造器中为对象设置适当的默认值

public Employee(){
    name = "";
    salary = 0;
    hireDay = new Date();
}

仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器。

5.4显式域初始化

class Employee{
    private String name = "abc";//Java允许,C++不允许
}

Java允许这种初始化方式。在执行构造器之前,先执行赋值操作。一般当一个类的所有构造器都希望把相同的值赋给某个特定的实例域时,采用这种方法。

5.5this调用另一个构造器
如果构造器的第一个语句形如this(....),这个构造器将调用同一个类的另一个构造器。
参考Java中this关键字和super关键字

5.6初始化块
Java中初始化数据域的三种方式:

在构造器中设置值
在声明中赋值:private int a = 1;
初始化块

5.7对象析构和finalize方法
由于Java有自动垃圾回收器,所以Java不支持析构器。
但是当某些对象使用了内存之外的其他资源,比如文件,就需要手动回收资源了。可以为任何一个类添加finalize()方法,finalize()方法在垃圾回收器清除对象之前调用。在实际应用中,不要依赖finalize()方法回收资源。

6、包

Java允许使用包将类组织起来。所有标准的Java包都处于java和javax包层次中。
使用包的主要原因是确保类名的唯一性。
从编译器的角度看,嵌套的包之间没有任何关系。例如,java.util和java.util.jar包毫无关系,每一个包都拥有独立的类集合。

6.1类的导入
一个类可以使用所属包中的所有类以及其他包中公有类。
可以使用import导入一个特定的类或者整个包:

import java.util.*;//导入整个包
import java.util.Scanner;//导入Scanner类

只能使用星号(*)导入一个包,而不能使用import java.*import java.*.*导入以java为前缀的所有包。

6.2静态导入
import语句不仅可以导入类,还可以导入静态方法和静态域。

import static java.lang.System.*;//导入了System类中的静态方法和静态域

6.3将类放入包中
要想将一个类放入包中,就必须将包的名字放在源文件的开头。

package corejava;//corejava是一个包

如果没有在源文件中放置package语句,这个源文件中的类就被放置在一个默认包中。默认包是没有名字的。

6.4包作用域

public  可以被任意的类使用
private  只能被类自身使用
protected 所有子类及同一个包中所有其他类可访问
没有指定public和private  可以被同一个包中的所有方法访问

在默认情况下,包不是一个封闭的实体。任何人都可以向包中添加更多的类。

7、文档注释

7.1javadoc工具
javadoc工具可以由源文件生成一个HTML文档。windows系统下,javadoc在JDK的bin目录下,叫做javadoc.exe

7.2类注释

/**
    * ...
*/

public class A{}

7.3方法注释

/**
*...
* @param...
*/

public double raiseSalary(){}

7.4域注释
只需要对公有域(通常是指静态常量)建立文档

7.5通用注释
以下标记可以用到类注释中

@author//标记
@version

8.类设计技巧

一定要保证数据私有。
一定要对数据初始化(Java不对局部变量初始化,但是会对对象的实例域进行初始化)
不要在类中使用过多的基本类型

三、继承

利用继承,可以基于已存在的类构造一个新类。继承已存在的类就是复用这些类的方法和域。

1.类、超类和子类

1.1概述
1)关键字extends表示继承

class Manager extends Employee{
    ...
}

在Java中,所有继承都是公有继承。超类和子类是Java程序员最常用的术语。
超类更通用,子类比超类拥有的功能更多(子类更加具体)。
子类自动继承了超类的方法和域,因此可以使用相关方法和访问相关域。

2)继承之后,子类的方法并不能够直接访问超类的实例域。如果子类的方法要访问私有域,就必须借助于公有接口,即在子类的方法中调用超类的方法。

public double getSalary(){
    double baseSalary = super.getSalary();
    return baseSalary + bonus;
}

3)使用super关键字调用超类的构造器实现对继承而来的私有域进行初始化。super调用构造器的语句必须是子类构造器的第一条语句。

public Manager(String n, double s, int year, int month, int day){
    super(n,s,year,month, day);
    bonus = 0;
}

4)超类变量可以引用超类的对象,也可以引用子类的对象,引用不同的对象的时候,调用的就是不同对象的方法。一个对象变量可以指示多种实际类型的现象称为多态。在运行时能够自动地选择调用哪个方法的现象称为动态绑定

在Java中,不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如果不希望让一个方法具有虚拟特征,可以将其标记为final。

1.2继承层次
继承层次:由一个公共超类派生出来的所有类的集合。
继承链:在继承层次中,某个特定的类到其祖先的路径被称为该类的继承链。
Java不支持多继承。一个子类只能继承一个超类。

1.3多态
在Java中,对象变量是多态的。一个超类的变量既可以引用超类的对象,也可以引用超类的任何一个子类的对象。
即使超类变量能够引用子类的对象也不能调用子类特有的方法。

Employee e = new Manager();//Manager继承Employee
e.setBonus();//error,因为setBonus是Manager对象特有的方法

不能将一个超类的引用赋给子类变量。

1.4动态绑定

1)调用方法的执行过程:

编译器查看对象的声明类型和方法名
编译器查看调用方法时提供的参数类型。(根据方法的签名,进行重载解析)
调用的方法依赖隐式参数的实际类型,并在运行时实现动态绑定
虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名和实际调用的方法

2)静态绑定
如果是private方法、static方法、final方法或者构造器,编译器可以准确地知道应该调用哪个方法,称为静态绑定

3)子类覆盖一个超类的方法时,子类方法不能低于超类方法的可见性。特别是,如果超类方法是public,子类方法一定要声明为public。

1.5阻止继承:final类和方法
final类:不允许扩展的类。
final方法:不允许子类覆盖的方法(final类中所有方法都是final方法,但final类中的域并不是final的)

1.6强制类型转换
将一个类的对象引用转换为另一个类的对象引用:

Employee e = new Employee();
Manager boss = (Manager)e;

进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。
在Java中,每个对象变量都属于一个类型。类型描述了这个变量所引用的以及能够引用的对象类型。

将一个父类引用赋给子类变量时必须进行类型转换,要求如下:

只能在继承层次内进行类型转换
在将超类转换成子类之前,应该使用instanceof进行检查

一般情况下,应该尽量少用类型转换和instanceof运算符

1.7抽象类
抽象方法:用abstract关键字修饰的方法,不需要完全实现。
抽象类:包含一个或多个抽象方法的类

public abstract Person{//抽象类
    public abstract String getName();//抽象方法 
} 

抽象类可以包含:抽象方法、具体数据和具体方法。抽象类不能被实例化。
子类扩展抽象类,只有定义全部的抽象方法才不是抽象类。
类即使不含抽象方法也可以声明为抽象类。
可以定义一个抽象类的变量,但它只能引用非抽象子类的对象。

1.8受保护访问
被protected关键字修饰的域或者方法是受保护的,即子类可以直接使用,其他类则无法直接使用。Java中的受保护部分对所有子类及同一个包中的所有其他类都是可见的。

2、Object类:所有类的超类

Object类是所有类的始祖,在Java中每个类都是由它扩展而来的。如果没有明确地指出超类,Object就被认为是这个类的超类。

在Java中只有基本类型(例如数字、字符和布尔值)不是对象,所有的数组类型都是对象。

1.equals()方法
Object类中的equals()方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用,即两个对象是否处于同一块内存空间。

一般检测两个对象是否相等:两个对象的状态相等,包括对象的类型以及相关属性等。

//检测相等
public boolean equals(Object other){//重写equals()方法
    if(this == other){
        return true;//如果两个对象变量引用同一个对象,则两个对象变量必然相等
    }
    if(other == null){
        return false;//如果被比较的另一个对象是null,则不相等
    }
    if(getClass() != other.getClass()){//如果两个对象类型不同,则不相等
        return false;
    }
    if(!(other instanceof ClassName)){//如果other对象不是本类的实例,则不相等
        return false;
    }
    ClassName otherObject = (ClassName) other;//将other对象转换为相应的类类型变量
    return field1 == field1 && Objects.equals(field2, otherObject.filed2);
    //使用==比较基本类型,使用equals()比较对象域
}

对于数组类型的域,可以调用Arrays.equals()方法检测相应的数组元素是否相等。

2.hashCode()方法
散列码是由对象导出的一个整型值。散列码是没有规律的。由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。

如果重新定义了equals()方法,就必须重新定义hashCode方法,并合理地组织实例域的散列码,以便能够让各个不同对象产生的散列码更加均匀。

3.toString()方法
用于返回对象的字符串。
一般对象调用toString()方法返回的字符串:

java.awt.Point[x=10,y=20]

所以重写toString()方法一般如下:

public String toString(){
    return getClass().getName() + "[name=" + name + ",salary=" + salary + "]";
}

Object类定义了toString()方法,用来打印对象所属的类名和散列码:

java.io.PrintStream@2f6684

3、泛型数组列表ArrayList

ArrayList是一个采用类型参数的泛型类。

ArrayList<Employee> staff = new ArrayList<>();
//声明一个保存Employee对象的数组列表。尖括号中只能是某种类而不能是基本类型

add()方法向数组列表中添加元素,并且能够自动扩展数组列表的大小:

staff.add(new Employee("张三"));

size()方法返回数组列表的实际元素数目:

staff.size();

get()方法访问数组列表中的元素:

staff.get(1);//返回数组列表中的第一个元素值

set()方法更改数组列表中某一个值

staff.set(1,harry);

add()方法还可以在数组列表中间插入元素:

staff.add(1,e);//在第一个位置插入元素e

toString()方法将数组元素拷贝到一个数组中:

X[] a = new X[list.size()];
list.toArray(a);

remove()方法从数组列表中删除一个元素:

Employee e = staff.remove(2);//删除第二个元素

使用"for each"循环遍历数组列表:

for(Employee e : staff){
    ..
}

4.对象包装器与自动装箱

包装器:基本类型对应的类,包含:Integer、Long、Float、Double、Short、Byte、Character、Void、Boolean

装箱:将基本类型转变成对象类型

Interger a = Integer.valueOf(3);//把int类型的数据转换成Integer对象

拆箱:把包装器类型的对象转换成基本类型的数据:

int x = a.inValue();//a对象转换为int类型的数据

装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。

String x = "1";
int a = Integer.parseInt(x);

Integer()方法将字符串转换成整型。

5.参数数量可变的方法

public class PrintStream{
    public PrintStream printf(String fmt, Object... args){//省略号表示可以接收任何数量的对象
        return format(fmt, args);
    }
}

6.枚举类

public enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE};

1)枚举类型Size也是一个类,而且是一个不可以被继承的final类
2)枚举类也有构造器、方法和数据域,不过构造器是private的,只能在构造枚举值的时候被调用
3)枚举值如SMALL都是public static final类型的
4)所有枚举类型都是Enum的子类,继承了其方法。toString()方法能够返回枚举常量名。
5)比较两个枚举类型的值时,只需要"=="就可以了。

四、接口与内部类

1、接口

接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
接口中的所有方法自动地属于public。
接口决不能含有实例域,也不能在接口中实现方法。因此可以把接口看做没有实例域的抽象类。
在实现接口时,必须把方法声明为public。

1.接口特性
1)接口不是类,不能使用new操作符实例化一个接口,但可以声明一个接口类型的变量,该变量必须引用实现了接口的类的对象

2)可以使用instanceof操作符检测某个对象是否实现了某个接口。

if(anObject instanceof Comparable){...}

3)接口可以扩展,从通用性的接口到专用性的接口。

public interface Powerd extends Moveable{...}

4)接口不能包含实例域或静态方法,但却可以包含常量。接口中的域被自动设置为public static final

5)尽管每个类只能继承一个超类,但可以实现多个接口。

class Employee implements Cloneable, Comparable{...}

2.接口与抽象类

//接口与抽象类的异同
相同点:1)都不能实例化,但可以声明变量,并且引用对象。
        2)都需要子类进行扩展,即覆盖其中的抽象方法。
不同点:1)抽象类是包含抽象方法的类(其他域或方法都可以有)。
        2)接口只能由全局常量(public static final)和抽象方法组成。

由于多继承会让语言本身变得非常复杂,效率也会降低,所以Java不支持多继承,但某个类可以实现多个接口。Java中的风格是:从一个基类派生,然后实现若干个辅助接口。

2、对象克隆

浅拷贝:当拷贝一个对象变量时,原始变量与拷贝变量同时引用同一个对象。
Object类中的clone()方法默认是浅拷贝。在Object类中,clone()方法被声明为protected,因此无法直接调用anObject.clone(),虽然所有类都是Object的子类,但是子类只能调用受保护的clone()方法克隆自己。所以一般要在子类中重写clone()方法。

深拷贝:调用Object类的clone()方法实现基本数据类型的拷贝,再克隆所有可变的实例域。

pclass Employee implements Cloneable{
    public Employee clone() throws CloneNotSupportedException{
        Employee cloned = (Employee) super.clone().;
        cloned.hireDay = (Date) hireDay.clone();
        return cloned;
    }
} 

3、内部类

内部类是定义在另一个类中的类。使用内部类的原因:

内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据
内部类可以对同一个包中的其他类隐藏起来
当想定义一个回调函数且不想编写大量代码时,匿名内部类

内部类分为:成员内部类、静态内部类、局部内部类和匿名内部类

1.内部类特性
内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。内部类的对象总有一个隐式引用,它
指向创建了它的外部类的对象。内部类可以是私有的,而常规类只可以具有包可见性或者公有可见性。

2.成员内部类
1)成员内部类:

public class Outer{
    public class Inner{
        ...
    }
}

2)创建成员内部类的对象:

Outer out = new Outer();//先创建外部类的对象
Inner in = out.new Inner();//再通过外部类对象创建内部类对象

3)在外部类的作用域之外,引用内部类:

Outer.Inner

4)在内部类中访问外部类同名属性:

out.this.name

3.局部内部类
可以在一个方法中定义局部类

public void start(){
    class TimePrinter{
        ...
    }
}

局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。局部类对外部世界可以完全隐藏。
局部类可以访问局部变量,不过那些局部变量必须声明为final。

4.匿名内部类
将局部类的使用再深入一步,假如只创建这个类的一个对象,就不必命名了,这种类称为匿名内部类。

public void start(){
    Timer time = new Timer(){//创建一个匿名类的对象
        ...
    }
}

匿名类没有构造器

5.静态内部类
有时候为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象,为此可以把内部类声明为static,以便取消产生的引用。只有内部类可以声明为static。

静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注