# 面向对象<下>
本文是面向对象-下,没看过上篇可跳转:面向对象-上
继承
现实生活中,说到继承,通常会想到子女继承父辈的财产、事业等。在程序中,继承描述的是事物之间的从属关系,通过继承可以使多种事物之间形成一种关系体系。例如,猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物。
在java中,类的继承是指在一个现有类的基础上构建一个新的类,构建的新类被称作子类,现有类被称作父类。
子类会自动继承父类的属性和方法,使得子类具有父类的特征和行为。
在java程序中,如果想声明一个类继承另一个类,需要使用extends关键字,其语法格式:
class 父类{
...
}
class 子类 extends 父类{
...
}
class Animal{
private String name;
private int age;
public final String Color = "黑色";
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
}
定义Dog类继承Animal类
class Dog extends Animal{
}
main主函数类
class class Example{
public static void main(String[] args){
Dog dog = new Dog();
dog.setName("牧羊人");
dog.setAge(3);
System.out.println("名称:"+dog.getName()+",年龄:"+dog.getAge()+",颜色"+dog.Color);
}
}
Dog类通过extends关键字继承了Animal类,这样Dog类便成了Animal类的子类。
Dog类中并没有定义任何属性和方法。
因为父类Animal中name属性和age属性使用private关键字修饰,即name属性和age属性为Animal类的私有属性,所以需要使用getter方法和setter方法访问。
在main()方法中创建了一个Dog类的对想dog,并使用实例对象dog访问父类Animal中name和age属性的setter方法设置名称、年龄的值。
通过dog对象访问父类Animal中name属性和age属性的getter方法获取名称、年龄的值,通过dog对象直接访问了Animal类中的非私有属性color获取颜色的值。
运行结果:
名称:牧羊人,年龄:3,颜色:黑色
由此结果说明子类Dog虽然没有定义任何属性和方法,但是能调用父类Animal的方法。这证明了子类在继承父类的时候会自动继承父类的属性和方法。
子类除了可以继承父类的属性和方法,也可以定义自己的属性和方法。
class Animal{
private String name;
private int age;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public int setAge(int age){
this.age = age;
}
}
class Dog extends Animal{
private String color;
public String getColor(){
return color;
}
public void setColor(String color){
this.color = color;
}
}
public class Example{
public static void main(String[] args){
Dog dog = new Dog();
dog.setName("牧羊人");
dog.setAge(3);
dog.setColor("黑色");
System.out.println("名称:"+dog.getName()+",年龄:"+dog.getAge()+",颜色"+dog.Color);
}
}
Dog类不仅继承了Animal类的属性和方法,还增加了color属性及对应的getter和setter方法。在main()方法中,通过dog对象调用Animal类的setter方法设置名称和年龄;通过dog对象调用Dog类的setter方法设置颜色。
子类虽然可以通过继承访问父类的成员和方法,但不是所有的父类属性和方法都可以被子类访问。子类只能访问父类用public和protected修饰的属性和方法,父类中被private修饰的属性和方法不能被子类访问。如果父类和子类不在同一个包中,那么被默认修饰符default修饰的属性和方法也不能被子类访问。
- 在java中,类只能支持单继承,不允许多继承。
class A{}
class B{}
class C extends A,B{}❌❌❌这样是错误的,C类不可以同时继承A类和B类
- 多个类可以继承一个父类
class A{}
class B extends A{}
class C extends A{}
- 在java中,多层继承也是可以的,即一个类可以再继承另外的父类。
class A{}
class B extends A{}
class C extends A{}
- 在java中,子类和父类是相对的,一个类可以是某个类的父类,也可以是另一个类的子类。
方法的重写
在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类方法进行重写。
在子类中重写的方法需要和父类中被重写的方法具有相同的方法名、参数列表以及返回值类型。
class Animal{
void shout(){
System.out.println("动物发声");
}
}
class Dog extends Animal{
void shout(){
System.out.println("汪汪汪")
}
}
public class Example{
public static void main(String[] args){
Dog dog = new Dog();
dog.shout();
}
}
上述代码构建了Animal类,并崽Animal类中定义了shout()方法。Dog类继承Animal类,在Dog类中重写了父类Animal类的shout方法,最后,Example类中创建并实例化Dog类,并通过dog对象调用了shout方法。
运行结果:
汪汪汪
子类重写父类方法时的访问权限
子类重写父类的方法时,不能使用比父类的方法更严格的访问权限。例如,父类的方法是public权限,子类的方法就不能是private权限。如果子类在重写父类的方法时定义的权限更严格,则在编译时会出现错误。
super关键字
当子类重写父类的方法后,子类对象将无法访问父类中被子类重写过的方法。为了解决这个问题,java提供了super关键字,使用super关键字,使用super关键字可以在子类中访问父类的非私有方法、非私有属性以及构造方法。
- 使用super关键字访问父类的非私有属性或调用父类的非私有方法,具体格式:
super.属性
super.方法(参数1,参数2)
示例:
class Animal{
String name="小狗";
void shout(){
System.out.println("动物发声");
}
}
class Dog extends Animal{
public void shout(){
super.shout();
System.out.println("狗子类重写");
}
public void printName(){
System.out.println("名字:"+super.name);
}
}
public class Example{
public static void main(String[] args){
Dog dog = new Dog();
dog.shout();
dog.printName();
}
}
运行结果:
动物发声
汪汪汪
名字:小狗
- 使用super关键字调用父类中指定的构造方法
格式:
super(参数1,参数2)
public class Animal03 {
private String name;
private double age;
public Animal03(String name,double age){
this.age =age;
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public double getAge(){
return age;
}
public void setAge(double age){
this.age = age;
}
public String info(){
return "名称:"+this.getName()+",年龄:"+this.getAge();
}
}
class Cat extends Animal03{
private String color;
public Cat(String name,double age,String color){
super(name,age);
this.setColor(color);
}
public String getColor(){
return color;
}
public void setColor(String color){
this.color = color;
}
public String info(){
return super.info()+",颜色:"+this.getColor();
}
}
public class Example15 {
public static void main(String[] args){
Cat cat = new Cat("崽崽",1.5,"梨花色");
System.out.println(cat.info());
}
}
super和this的作用非常相似,都可以访问属性以及调用方法和构造方法,但是两者之间还是有区别的。
区别 | super | this |
---|---|---|
访问属性 | 直接访问父类中的非私有属性 | 访问本类中的属性。如果本类中没有该属性,则从父类中继续查找 |
调用方法 | 直接调用父类中的非私有方法 | 调用本类中的方法。如果本类中没有该方法,则从父类中继续查找 |
调用构造方法 | 调用父类构造方法,必须放在子类构造方法的首行 | 调用本类构造方法,必须放在构造方法的首行 |
需要注意的是:this和super不可以同时出现,因为使用this和super调用方法方法的代码都要求必须放在构造方法的首行。
final 关键字
在默认情况下,所有的成员变量和成员方法都可以被子类重写。
如果父类的成员不希望被子类重写,可以在声明父类的成员变量时使用final关键字修饰。
final有“最终”不可以更改的意思,在Java中,可以使用final关键字修饰类、属性、方法。
在使用final关键字时需注意:
- 使用final关键字修饰的类不能有子类。
- 使用final关键字修饰方法不能被子类重写。
- 使用final关键字修饰的变量是常量,常量不可以修改。
final关键字修饰类
Java中使用final关键字修饰的类不可以被继承,也就是这样的类不能派生子类。
public class Animal_final {
}
public class Dog_final extends Animal_final {
//用本类继承Animal_final类
}
public class Example16 {
public static void main(String[] args){
Dog_final dog = new Dog_final();
}
}
以上是未使用final修饰父类的代码,运行正常
final class Animal_final {
}
public class Dog_final extends Animal_final {
//用本类继承Animal_final类❌❌❌
}
public class Example16 {
public static void main(String[] args){
Dog_final dog = new Dog_final();
}
}
由图可知运行报错。
final关键字修饰方法
当一个类的方法被final关键字修饰后,该类的子类将不能重写该方法,否则会报错。
class Aniaml{
public final void shot(){
//使用final关键字修饰shout方法
}
}
class Dog extends Animal{
public void shout(){❌❌❌
//重写Animal类的shout方法
}
}
public class example{
public static void main(String[] args){
Dog dog = new Dog();
}
}
报错如图:
final关键字修饰变量
java中被final修饰的变量为常量,常量只能在声明时被赋值一次,后面的程序中,常量的值是不能被改变的。
public class Example{
public static void main(String[] args){
final age =12;
age =20;❌❌❌
}
}
运行结果如图:
需要注意的是:在适应final关键字来修饰变量时,变量的名称要求全部大写,如果一个程序中的变量使用public static final声明,则此变量为全局常量。
抽象类和接口
抽象类
定义一个类,常常需要定义一些成员方法用来描述类的行为特征,但有时这些方法的实现方式时无法确定的。例如:前面定义的Animal类中的shout()方法用于描述动物的叫声,但时不同的动物叫声也不相同,因此在shout()方法中无法准确描述动物的叫声。
针对上面的情况,java提供了抽象方法来满足这种需求。
抽象方法是使用abstract 关键字 修饰的成员方法,抽象方法在定义时不需要实现方法体。
抽象方法语法如下:
abstract 返回值类型 方法名称(参数列表);
当一个类包含了抽象方法,该类就是抽象类。
抽象类和抽象方法一样,,必须使用abstract关键字来修饰。
抽象类的语法格式如下:
abstract class 抽象类名称{
属性;
访问权限 返回值类型 方法名称(参数){
return 返回值;
}
访问权限 abstract 返回值类型 抽象方法名称(参数);
}
抽象类等等定义规则:
- 包含抽象方法的类必须是抽象类
- 声明抽象类和抽象方法时都必须使用abstract关键字
- 抽象方法只需要声明而不需要实现
- 如果一个非抽象类继承了抽象类之后,那么该类必须重写继承的抽象类中的全部抽象方法。
接口
接口是一种用来定义程序的协议,它用于描述类或结构的一组相关行为。
接口时由抽象类衍生的一个概念,并由此产生了一种编程方式,可以称这种编程方式为:面向接口编程。
面向接口编程:
- 就是将程序的不同的业务逻辑分离,以接口的形式对接不同的业务模块。
- 接口中不实现任何业务逻辑,业务逻辑由接口的实现类完成。
- 当业务需求变更时,只需要修改实现类中的业务逻辑,而不需要修改接口中的内容,以减少需求变更对系统产生的影响。
下面通过现实生活中的例子来类比面向接口编程。例如,鼠标、U盘等外部设备通过USB接口来连接计算机,即插即用,非常灵活。
如果需要更换与计算机连接的外部设备,只需要拔掉当前USB接口上的设备,把新的设备插入即可,这就是面向接口编程的思想。
在java中,使用接口的目的是客服单继承的限制,因为一个类只能有一个父类,而一个类可以同时实现多个父接口。
在jDK8之前,接口时由全局常量和抽象方法组成的。JDK8对接口进行了重新定义,接口中除了抽象方法外,还可以定义默认方法和静态方法,默认方法使用default关键字修饰,静态方法使用static关键字修饰,而且这两种方法都允许有方法使用default关键字修饰,静态方法使用static关键字修饰,而且这两种方法都允许有方法体。
接口使用interface关键字声明,语法格式如下:
[public] interface 接口名 [extends 接口1,接口2,...]
[public] [static] [final] 数据类型 常数名 = 常量;
[public] [abstract] 返回值的数据类型 方法名(存储参数列表);
[public] static 返回值的数据类型 方法名(参数列表){}
[public] default 返回值的数据类型 方法名(参数列表){}
上述语法格式中,“extends 接口1,接口2”表示一个接口可以有多个父接口,父接口之间用逗号间隔。接口中的变量默认使用 public static final 进行修饰,即全局变量。接口中定义的抽象方法默认使用 public abstract 进行修饰。
在很多java程序中,经常看到编写接口中的方法时忽略了public,有很多读者认为它的访问权限时default,这是错误的,不管写不写访问权限,接口中方法的访问权限永远是public
接口本身不能进行直接实例化,接口中的抽象方法和默认方法只能通过接口实现类的实例对象进行调用。实现类通过implements关键字实现接口,并且实现类必须重写接口中所有的抽象方法。
需要注意的是,一个类可以同时实现多个接口,实现多个接口时,多个接口名需要使用英文逗号分隔。
定义接口实现类的语法格式:
修饰符 class 类名 implements 接口1,接口2,...{
...
}
示例:
//定义接口Animal
interface Animal_interface {
int ID =1;
String NAME ="二哈";
void shout();
public void info();
static int getID(){
return Animal_interface.ID;
}
}
//定义接口Action
public interface Action {
public void eat();
}
//定义Dog类实现Animal接口和Action接口
public class Dog_interface implements Animal_interface,Action{
public void eat(){
System.out.println("喜欢吃史");
}
public void shout(){
System.out.println("汪汪");
}
public void info(){
System.out.println("名称:"+NAME);
}
}
//测试类
public class Example17 {
public static void main(String[] args){
System.out.println("编号:"+Animal_interface.getID());
Dog_interface dog = new Dog_interface();
dog.info();
dog.shout();
dog.eat();
}
}
Animal接口中定义了全局常量ID和NAME、抽象方法shout()、info()和静态方法getID()。
Action接口中定义了抽象方法eat()
Dog类通过implements关键字实现了Animal接口和Action接口,并重写了这两个接口中的抽象方法。
需要注意的是,接口的实现类必须实现接口中的所有抽象方法,否者程序编译会报错。
一个类既可以实现接口又可以继承抽象类的情况
示例:
//定义接口Animal
interface Animal_interface {
int ID =1;
String NAME ="二哈";
void shout();
public void info();
static int getID(){
return Animal_interface.ID;
}
}
//定义Action类
abstract class Action {
public abstract void eat();
}
//定义Dog类实现Animal接口和Action接口
public class Dog_interface extends Action implements Animal_interface{
public void eat(){
System.out.println("喜欢吃史");
}
public void shout(){
System.out.println("汪汪");
}
public void info(){
System.out.println("名称:"+NAME);
}
}
//测试类
public class Example17 {
public static void main(String[] args){
System.out.println("编号:"+Animal_interface.getID());
Dog_interface dog = new Dog_interface();
dog.info();
dog.shout();
dog.eat();
}
}
在java中,接口不允许继承抽象类,但允许接口继承接口,并且一个接口可以同时继承多个接口。
//定义接口Animal
interface Animal_interface {
int ID =1;
String NAME ="二哈";
void shout();
public void info();
static int getID(){
return Animal_interface.ID;
}
}
//定义Action接口继承Animal_interface接口和Color接口
abstract interface Action extends Animal_interface,Color{
public abstract void eat();
}
//定义Dog类继承Action接口
public class Dog_interface implements Action{
public void eat(){
System.out.println("喜欢吃史");
}
public void shout(){
System.out.println("汪汪");
}
public void info(){
System.out.println("名称:"+NAME);
}
public void black(){
System.out.println("黑色");
}
}
public interface Color {
public void black();
}
//测试类
public class Example17 {
public static void main(String[] args){
System.out.println("编号:"+Animal_interface.getID());
Dog_interface dog = new Dog_interface();
dog.info();
dog.shout();
dog.eat();
}
}
多态
多态概述
多态是面向对象思想中的一个非常重要的概念
在java中,多态是指不同类的对象在调用同一个方法时表现出的多种不同的行为。
例如:要实现一个输出动物叫声的方法,由于每种动物的叫声是不同的,因此可以在方法中接受一个动物类型的参数,当传入猫类对象时就会发出猫类的叫声,当传入犬类对象时就发出犬类的叫声。
在同一个方法中,这种由于参数类型不同而导致执行效果不同的现象就是多态。
Java中的多态主要又以下两种形式:
- 方法的重载
- 对象的多态(方法的重写)
示例:
//定义抽象类Animal
abstract class Animal{
abstract void shout();
}
//定义Cat类继承Animal抽象类
class Cat extends Animal{
//重写shout()方法
public void shout(){
System.out.println("喵喵");
}
}
//定义Dog类继承Animal抽象类
class Dog extends Animal{
//重写shout()方法
public void shout(){
System.out.println("汪汪");
}
}
//定义测试类
public class Example{
public static void main(String[] args){
Animal an1 =new Cat();
Animal an2 =new Dog();
an1.shout();
an2.shout();
}
}
运行结果:
喵喵 汪汪
对象类型的转换
对象类型转换主要分为以下两种情况:
- 向上转型:子类对象 -> 父类对象
- 向下转型:父类对象 -> 子类对象
对象向上转型
对象向上转型,父类对象可以调用子类重写父类的方法,这样当需要新添功能时,只需要新增一个子类,在子类中对父类的功能进行扩展,而不用更改该父类的代码,保证了程序的安全性。
父类类型 父类对象 = 子类实例;
实例:
class Animal{
public void shout(){
System.out.println("喵喵");
}
}
//定义Dog类
class Dog extends Animal{
public void shout(){
System.out.println("汪汪");
}
public void eat(){
System.out.print("吃骨头")
}
}
//定义测试类
public class Example{
public static void main(String[] args){
Dog dog =new Dog();
Animal an = dog;
an.shout();
}
}
运行结果:
汪汪
虽然程序中使用父类对象 an 调用了shout()方法,但实际上调用的时被子类重写过的shout()方法。
也就是说,如果对象发生了向上转型后,调用的方法一定时被子类重写过的方法。
父类Animal的对象 an 时无法调用Dog类中的eat()方法的,因为eat()方法只能在子类中定义,而没有在父类中定义。
对象向下转型
父类类型 父类对象 = 子类实例;
子类类型 子类对象 = (子类)父类对象;
实例:
class Animal{
public void shout(){
System.out.println("喵喵");
}
}
//定义Dog类
class Dog extends Animal{
public void shout(){
System.out.println("汪汪");
}
public void eat(){
System.out.print("吃骨头")
}
}
//定义测试类
public class Example{
public static void main(String[] args){
Animal an = new Dog();
Dog dog = (Dog)an;
dog.eat();
an.shout();
}
}
运行结果:
吃骨头 汪汪
需要注意的是,在向下转型时,不能直接将父类实例强制转换为子类实例,否则程序会报错:
Dog dog = (Dog)new Animal(); //编译报错
instanceof 关键字
java中可以使用instanceof关键字判断一个对象是否是某个类(或接口)的实例
格式如下:
对象 instanceof 类(或接口)
上述语法格式中,如果“对象”是指定的类或接口的实例对象,则返回true,否则返回false。
实例:
public class Animal_instanceof {
public void shout(){
System.out.println("动物叫");
}
}
public class Dog_instanceof extends Animal_instanceof{
//重写shout()方法
public void shout(){
System.out.println("汪汪");
}
public void eat(){
System.out.println("吃骨头");
}
}
public class Example18 {
public static void main(String[] args){
Animal_instanceof a1 = new Dog_instanceof();
System.out.println("Animal a1 = new Dog():"+(a1 instanceof Animal_instanceof));
System.out.println("Animal a1 = new Dog():"+(a1 instanceof Dog_instanceof));
Animal_instanceof a2 = new Animal_instanceof();
System.out.println("Animal a2 = new Animal():"+(a2 instanceof Animal_instanceof));
System.out.println("Animal a2 = new Animal():"+(a2 instanceof Dog_instanceof));
}
}
运行结果:
Animal a1 = new Dog():true
Animal a1 = new Dog():true
Animal a2 = new Animal():true
Animal a2 = new Animal():false
Animal a1 = new Dog();
这里我们创建了一个Dog实例,但将其引用赋给了Animal类型的变量a1,这是Java中允许的,因为Dog是Animal的子类。
System.out.println(“Animal a1 = new Dog():”+(a1 instanceof Animal_instanceof));这里检查a1是否为Animal类的一个实例。其他代码同理。
instanceof 关键字 用于检查一个对象是否是某个类的实例,或者是其子类的实例。
Object 类
Object类是所有类的父类,每个类都直接或间接继承了Object类,因此Object类通常被称为超类。
当定义一个类时,如果没有使用extends关键字为这个类显式指定父类,那么该类会默认继承Object类。
方法名称 | 方法说明 |
---|---|
boolean equals() | 判断两个对象是否“相等” |
int hashCode() | 返回对象的哈希值 |
String toString() | 返回对象的字符串表达形式 |
public class Animal_Object {
void shout(){
System.out.println("动物叫");
}
}
public class Example19 {
public static void main(String[] args){
Animal_Object animal = new Animal_Object();
System.out.println(animal.toString());//调用toString()方法并打印
}
}
运行结果:Animal_Object@b4c966a
在实际开发过程中,通常情况下不会直接调用Object类中的方法,因为Object类中的方法并不适用于所有的子类,这时就需要对Object类中的方法进行重写,以满足实际开发需求。
public class Animal_Object {
public String toString(){
return "这是一个动物";
}
}
public class Example19 {
public static void main(String[] args){
Animal_Object animal = new Animal_Object();
System.out.println(animal.toString());//调用toString()方法并打印
}
}
运行结果:这是一个动物
内部类
在Java中,允许在一个类的内部定义类,这样的类称为内部类。
内部类所在的类称作为外部类。
内部类分为:
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类
成员内部类
一个类中除了可以定义成员变量、成员方法,还可以定义类,这样的类被称作成员内部类。
成员内部类可以访问外部类的所有成员,无论外部类的成员是何种访问权限。如果想通过外部类访问内部类,则需要通过外部类创建内部类对象。
创建内部类对象的具体语法:
外部类名 外部类对象 = new 外部类名();
外部类名.内部类名 内部类对象 = 外部类对象.new 内部类名();
“`java
public class Outer {
int m = 0;
//外部类方法test_out
void test_out(){
System.out.println(“外部类成员方法test out”);
}
//下面定义成员内部类Inner
class Inner{
int n = 1;
void show_Inner(){
//在成员内部类的方法中访问外部类的成员变量m
System.out.println(“外部类成员变量m=”+m);
//在成员内部类的方法中访问外部类的成员方法test_out()
test_out();
}
void show2(){
System.out.println(“内部类成员方法show2”);
}
}
//外部类方法test2()
void test2(){
Inner inner = new Inner();
System.out.println(“内部类成员变量n=”+inner.n);
inner.show2();
}
}
public class Example20 {
public static void main(String[] args){
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show_Inner();
inner.show2();
}
}
运行结果:
外部类成员变量m=0
外部类成员方法test out
内部类成员方法show2