Java对象和类
Java作为一种面向对象语言,支持以下基本概念:
- 多态
- 继承
- 封装
- 抽象
- 类
- 对象
- 实例
- 方法
- 重载
对象和类
- 对象:对象是类的一个实例(对象不是找个女朋友),有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
- 类:类是一个模板,它描述一类对象的行为和状态。
public class Dog{ String breed; int age; String color; void barking(){ } void hungry(){ } void sleeping(){ } }
一个类可以包含一下类型变量:
- 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
- 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
- 类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。
一个类可以拥有多个方法
构造方法
每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。
在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。
创建对象
对象根据类创建。
在Java中,使用关键字new来创建一个对象。需要三步:
- 声明:声明一个对象,包括对象名称和对象类型。
- 实例化:使用关键字new来创建一个对象。
- 初始化:使用new创建对象时,会调用构造方法初始化对象。
访问实例变量和方法
/* 实例化对象 */ ObjectReference = new Constructor(); /* 访问类中的变量 */ ObjectReference.variableName; /* 访问类中的方法 */ ObjectReference.MethodName();
源文件声明规则
当一个源文件中定义多个类,并且还有import语句和package语句时,要特别注意:
- 一个源文件中只能有一个public类
- 一个源文件可以有多个非public类
- 源文件名应该和public类的类名保持一致
- 如果一个类定义在某个包中,package语句应该在源文件的首行
- 如果源文件包含import语句,应该放在package语句和类定义之间。如果没有package语句,那么import语句应该在源文件中最前面
- import语句和package语句对源文件中定义的所有类都有效。
- 在同一源文件中,不能给不同的类不同的包声明
继承
继承允许创建分等级层次的类
继承就是
- 子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法
- 子类从父类继承方法,使得子类具有父类相同的行为
为什么需要继承
提高代码的复用性
继承的特性
- 子类拥有父类非private的属性和方法
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方式实现父类的方法
- Java的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类(但可以同时继承多个接口),多重继承就是,例如A类继承B类,B类继承C类,所以按照关系就是C类是B类的父类,B类是A类的父类
- 提高了类之间的耦合性(继承的缺点,继承的缺点,耦合度高就会造成代码之间的联系)
final关键字
- 声明类可以把类定义为不能继承的,即最终类
- 修饰方法,该方法不能被子类重写
注:被声明为final类的方法自动地声明为final,但实例变量并不是final
构造器
子类不能继承父类的构造器
- 父类的构造器带有参数的,必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表
- 父类有无参构造器,则在子类的构造器中用super调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器
重写(Override)
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。外壳不变,核心重写
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类可以根据需要实现父类的方法。
- 重写方法不能抛出新的异常检查或者比被重写方法申明更加宽泛的异常。
class Animal{ public void move(){ System.out.println("动物可以移动"); } } class Dog extends Animal{ public void move(){ System.out.println("狗可以跑和走"); } } public class TestDog{ public static void main(String args[]){ Animal a = new Animal(); // Animal 对象 Animal b = new Dog(); // Dog 对象 a.move();// 执行 Animal 类的方法 b.move();//执行 Dog 类的方法 } }
在上面的例子中,尽管b属于Animal类型,但它运行的是Dog类的move方法
这是由于在编译阶段,只是检查参数的引用类型,然而在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法
因此在上面的例子中,之所以能编译成功,是因为Animal类中存在move方法,然而运行时,运行的是特定对象的方法
class Animal{ public void move(){ System.out.println("动物可以移动"); } } class Dog extends Animal{ public void move(){ System.out.println("狗可以跑和走"); } public void bark(){ System.out.println("狗可以吠叫"); } } public class TestDog{ public static void main(String args[]){ Animal a = new Animal(); // Animal 对象 Animal b = new Dog(); // Dog 对象 a.move();// 执行 Animal 类的方法 b.move();//执行 Dog 类的方法 b.bark(); } }
该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法
方法的重写规则
- 参数列表必须完全与被重写方法的相同
- 返回类型必须完全与被重写方法的返回类型相同
- 访问权限不能比父类中被重写的方法的访问权限更低。(这样可以确保任何可以使用超类实例的地方也都可以使用子类的实例)
- 父类的成员方法只能被它的子类重写
- 声明为fianl的方法不能被重写
- 声明为static的方法不能被重写,但是能够被再次声明
- 子类和父类在同一个包中,那么子类可以重写父类所有除了声明为private和final的方法
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以
- 构造方法啊不能被重写
- 如果不能继承一个方法,则不能重写这个方法
super关键字
当需要在子类中调用父类的被重写方法时,要使用super关键字
重载(Overload)
重载是在一个类里面,方法名字相同而参数不同,返回类型可以相同也可以不同
每个重载的方法都必须有一个独一无二的参数类型列表
最常用构造方法重载
重载规则
- 被重载的规则必须改变参数列表(参数个数或类型或顺序不一样)
- 被重载的方法可以改变返回类型
- 被重载的方法可以改变访问修饰符
- 被重载的方法可以声明新的或更广的检查异常
- 方法可以在同一个类中或者在一个子类中被重载
- 无法以返回值类型作为重载函数的区分标准
重写与重载之间的区别
Overriding和Overloading是java多态性的不同表现,Overriding是父类与子类之间多态性的一种表现,Overloading可以理解成多态的具体表现形式
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
多态
多态是同一个行为具有多个不同表现形式或形态的能力
多态就是同一个接口,使用不同的实例而执行不用操作
多态的优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩展性
- 接口性
- 灵活性
- 简化性
多态存在的三个必要条件
- 继承
- 重写
- 父类引用子类对象
当使用多态方式调用方法时,首先检查父类中是否有该方法 ,如果没有,则编译错误,如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理
虚方法
当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法
要想调用父类中被重写的方法必须使用关键字super
public class Employee { private String name; private String address; private int number; public Employee(String name, String address, int number) { System.out.println("Employee 构造函数"); this.name = name; this.address = address; this.number = number; } public void mailCheck() { System.out.println("邮寄支票给: " + this.name + " " + this.address); } public String toString() { return name + " " + address + " " + number; } public String getName() { return name; } public String getAddress() { return address; } public void setAddress(String newAddress) { address = newAddress; } public int getNumber() { return number; } } public class Salary extends Employee { private double salary; // 全年工资 public Salary(String name, String address, int number, double salary) { super(name, address, number); setSalary(salary); } public void mailCheck() { System.out.println("Salary 类的 mailCheck 方法 "); System.out.println("邮寄支票给:" + getName() + " ,工资为:" + salary); } public double getSalary() { return salary; } public void setSalary(double newSalary) { if(newSalary >= 0.0) { salary = newSalary; } } public double computePay() { System.out.println("计算工资,付给:" + getName()); return salary/52; } } public class VirtualDemo { public static void main(String [] args) { Salary s = new Salary("员工 A", "北京", 3, 3600.00); Employee e = new Salary("员工 B", "上海", 2, 2400.00); System.out.println("使用 Salary 的引用调用 mailCheck -- "); s.mailCheck(); System.out.println("\n使用 Employee 的引用调用 mailCheck--"); e.mailCheck(); } }
实例中,实例化了两个 Salary 对象:一个使用 Salary 引用 s,另一个使用 Employee 引用 e。
当调用 s.mailCheck() 时,编译器在编译时会在 Salary 类中找到 mailCheck(),执行过程 JVM 就调用 Salary 类的 mailCheck()。
因为 e 是 Employee 的引用,所以调用 e 的 mailCheck() 方法时,编译器会去 Employee 类查找 mailCheck() 方法 。
在编译的时候,编译器使用 Employee 类中的 mailCheck() 方法验证该语句, 但是在运行的时候,Java虚拟机(JVM)调用的是 Salary 类中的 mailCheck() 方法。
以上整个过程被称为虚拟方法调用,该方法被称为虚拟方法。
多态的实现方式
- 重写
- 接口
- 抽象类和抽象方法
抽象类
在面向对象的概念中,所有的对象都是通过类来描述的,但是反过来,并不是所有的类都是用来描述对象的
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
抽象类除了不能实例化对象之外,类的其他功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样
抽象类不能实例化对象,所以抽象类必须被继承才能使用
父类包含了子类的常见方法,但是由于父类本身是抽象的,所以不能使用这些方法
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,但可以实现多个接口
抽象方法
如果你想设计一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法
抽象方法只包含一个方法名,而没有方法体
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号
声明抽象方法会造成以下两个结果 :
- 如果一个类包含抽象方法,那么该方法必须是抽象类
- 任何子类必须重写父类的抽象方法 ,或者声明自身为抽象类。最终必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象
抽象类总结
- 抽象类不能被实例化,只有抽象类的非抽象子类可以创建对象
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
- 抽象类中的抽象方法只是声明,不包含方法体
- 构造方法、类方法(用static修饰的方法)不能声明为抽象方法
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类
封装
在面向对象式设计方法中,封装(Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问
要访问该类的代码和数据,必须通过严格的接口控制
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性
封装的优点
- 良好的封装能够减少耦合
- 类内部的结构可以自由修改
- 可以对成员变量进行更精确的控制
- 隐藏信息,实现细节
实现封装的步骤
- 修改属性的可见性来限制对属性的访问(一般限制为private)
- 对每个属性一共对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问(采用this关键字可以解决实例变量和局部变量之间发生的同名冲突)
接口
Interface在java中是一个抽象类型,是抽象方法的集合,一个类通过继承接口的方式,从而来继承接口的抽象方法
接口不是类,类描述对象的属性和方法,接口则包含类要实现的方法
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法
接口无法被实例化,但可以被实现
java中接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象
接口与类相似点
- 一个接口可以有多个方法
- 接口文件保存在.java结尾的文件中,文件名使用接口名
- 接口的字节文件保存在.class结尾的文件中
- 接口相应的字节码文件必须在与包名称相匹的目录结构
接口与类的区别
- 接口不能用于实例化对象
- 接口没有构造方法
- 接口中所有的方法必须是抽象方法
- 接口不能包含成员变量,除了static和final变量
- 接口不是被类继承了,而是要被类实现
- 接口支持多继承
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为public abstract(只能是public abstract,其他修饰符都会报错)
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为public static final变量(并且只能是public,用private修饰会报编译错误)
- 接口中的方法是不能在接口中实现的,只能有实现接口的类来实现接口中的方法
抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的
- 接口中不能含有静态代码块以及静态方法(用static修饰的方法),而抽象类是可以有静态代码块和静态方法的(注:java中:静态代码块>main方法>构造代码块>构造方法,其中静态代码块指执行一次,先于主方法执行,用于初始化类,而构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序先于类构造函数)
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口
重写接口中声明的方法时,需要注意以下规则:
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法
在实现接口的时候,需要注意一些规则:
- 一个类可以同时实现多个接口
- 一个类只能继承一个类,但是能实现多个接口
- 一个接口只能继承另一个接口,这和类之间的继承比较相似
接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子类接口继承父接口的方法。
接口的多继承
在java中,类的多继承是不合法的,但接口允许多继承
标记接口
没有任何方法的接口被称为标记接口,标记接口主要用于以下两种目的:
- 建立一个公共的父接口:正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
- 向一个类添加数据类型:这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法,但是该类通过多态性变成一个接口类型。
接口类型变量
- 定义一个接口TestFace
public interface TestFace{//定一个接口 void make();//定义一个接口方法 }
- 定义一个类MycClass,里面包含一个接口型变量
public class MyClass{//定义一个类 TestFace tf;//定义一个接口型变量tf public MyClass(TestFace tf){//构造函数初始化接口型变量tf this.tf = tf; } public work(){ //函数调用 this.tf.make();//实际的接口型类实例。 } }
- 定义两个类,实现接口TestFace
public class C1 implements TestFace{//定一个类C1实现接口TestFace public void make(){//实现接口的方法make System.out.println("c1");//打印c1 } } public class C2 implements TestFace{//定一个类C2实现接口TestFace public void make(){//实现接口的方法make System.out.println("c2");//打印c2 } }
- 使用接口变量
TestFace tf1 = new C1();//实现一个类实例C1 TestFace tf2 = new C2();//实现一个类实例C2 MyClass mc = MyClass(tf1);//定义一个MyClass的类实例,使用tf1 MyClass mc2 = MyClass(tf2);//定义一个MyClass的类实例,使用tf1 同一个类的work,能够实现不同的打印内容 mc.work(); mc2.work();
Java包(package)
为了更好的组织类,Java提供了包机制,用于区别类名的命名空间
包的作用
- 把功能类似或相关的类或接口组织在同一个包中,方便类的查找和使用
- 如图文件夹一样,包也采用了树形目录的存储方式, 同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别,因此,包可以避免名字冲突。
- 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类
开发者可以自己把一组类和接口等打包,并定义自己的包,而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。