Java中的类可以通过继承来获得另一个类的属性和方法。继承可以使代码重用性更好,也可以使代码更加灵活和可维护。
在Java中,一个类可以通过extends关键字来继承另一个类。被继承的类称为父类,继承它的类称为子类。
在Java语言里,一个类继承另一个类需要用到关键字extends,关键字extends的使用方法如下:
class Child extends Parent{ }
因为Java只支持单继承,即一个类只能有一个父类,所以以下代码是错误的:
class Child extends Parent1,Parents2{ }
Java虽然不允许同时继承两个父类,但不代表没有多继承的关系,可以通过类似“祖父>父>儿子>孙子”的方式实现多代继承。
例如:绝大多数动物有眼睛,鼻子和嘴,犬类继承动物类,所以犬类也有眼睛,鼻子和嘴,哈士奇是犬类的一个品种,犬类有的特征哈士奇都有,但哈士奇的眼睛鼻子和嘴并不是从犬类继承的,而是从动物类继承的。
class Animal { //父类:动物类
Eyes eyes;
Nose nose;
Mouth mouth;
}
class Dog extends Animal{} //子类:犬类
class Husky extends Dog{} //子类:哈士奇类
在Java中,所有的类都默认继承自Object类,因此Object类是Java中最基本的类之一。Object类中定义了一些基本的方法,其他所有的类都可以使用这些方法,也可以重写这些方法来满足自己的需求。
getClass()方法是Object类中的一个方法,它返回一个对象的运行时类型的Class对象。Class对象包含了该对象的类的相关信息,比如类的名称、类的修饰符、类的父类、类的实现的接口等等。
getClass()方法的使用场景比较广泛,例如在反射中,我们可以使用getClass()方法获取一个对象的Class对象,进而通过反射来获取该类的各种信息,比如构造方法、成员变量、成员方法等等。
getClass方法语法如下:
getClass().getName();
可以将getClass()方法与toString()方法联合使用
toString()方法是object类中的一个方法,用于返回对象的字符串表示形式。它的作用是将对象转换为字符串,方便打印和调试。
在Java中,所有的类都继承自Object类,如果一个类没有自己实现toString()方法,则默认继承Object类的toString()方法。这个默认的toString()方法返回一个字符串,包含该对象所属类的名称和对象的哈希码。
如果要自定义toString()方法,可以在类中重写该方法,并返回一个适当的字符串,用于表示该对象的状态。这样可以使得打印对象时输出的内容更加有意义。
在Java中,equals()是Object类中的方法,它的作用是用于比较两个对象是否相等。在默认情况下,equals()方法与“==”运算符的作用相同,即比较两个对象的引用是否相等,两者的区别在于“==”比较的是两个对象引用内存地址是否相等,而equals()方法比较的是两个对象的实际内容。如果我们想要比较两个对象的内容是否相等,就需要重写equals()方法。
对象类型的转换是指将一个对象从一种类型转换为另一种类型。在Java中,对象类型的转换分为两种,即向上转型(upcasting)和向下转型(downcasting)。
向上转型是指将一个子类对象转换为父类对象的过程,可以自动进行,无需显式调用转型方法。例如,如果有一个Animal类和一个Dog类,Dog是Animal的子类,那么可以将Dog对象转换为Animal对象,如下所示:
Dog dog = new Dog();
Animal animal = dog; // 向上转型
在上述代码中“People tom = new Teacher();”运用了向上转型的语法,理解方式如下:
综上所述,因为人类(people)是教师类的(teacher)的父类,所以通过向上转型,能够把教师类(teacher)类型的对象(new Teacher();)直接赋值给人类(People)类型的变量(tom)。也就是说,进行向上转型,父类类型的对象可以引用子类类型的对象。而且,向上转型是安全的,因为向上转型是将一个较具体的类的对象转换成一个较抽象的类的对象。例如,可以说tom是人类,不能说人类是tom。
向下转型是指将一个父类对象强制转换成子类类型,这种转换需要手动进行,并且可能会引发ClassCastException异常。
向下转型在使用时需要注意以下几点:
1.必须先进行向上转型,才能进行向下转型。即需要先将父类对象转换为子类对象才能调用子类对象特有的方法。
2.需要使用instanceof运算符进行类型检查。即在进行向下转型之前,需要判断对象是否是子类的实例,否则可能会引发ClassCastException异常。
3.向下转型可能会引发ClassCastException异常。如果转换失败,即父类对象并不是子类对象的实例,则会抛出该异常。
该例在运行前会报错,这是因为父类对象不能直接赋值给子对象。
如果想要告诉编译器“某只鸟是一只鸽子”,应该如何修正?,答案就是强制类型转换。也就是说要想实现向下转型,需要借助强制类型转换,语法如下:
子类类型 子类对象 = (子类类型)父类对象;
因此要实现把鸟类对象转换成鸽子类对象需要把第九行代码修改为:
Pigeon pigeon = (Pigeon)Bird; //通过强制类型转换,告诉编译器“某只鸟就是一只鸽子”
在Java中,可以使用instanceof关键字来判断一个对象是否为特定类或其子类的实例。语法如下:
myobject instanceof ExampleClass
myobject:某类的对象引用
ExampleClass:某个类
instanceof 关键字的作用是检查一个对象是否是某个类或其子类的实例,它的返回值是一个布尔值,如果对象是该类或其子类的实例,返回 true,否则返回 false。
在实际应用中,instanceof 常用于以下场景:
对于向上转型后的对象,需要判断它的真实类型是否为某个类或其子类,以执行相应的操作;
在遍历集合或数组时,需要判断每个元素的类型是否为某个类或其子类,以执行相应的操作。
需要注意的是,在使用 instanceof 进行类型判断时应当尽量避免滥用,以免影响程序性能。同时,在进行向下转型时,应当先使用 instanceof 进行类型判断,以确保不会抛出ClassCastException 异常。
运行这段代码时会报错,原因是条件操作数类型Quadrangle和Circular不兼容,没有继承关系不能作比较,要删除或注释这段代码才能运行。
方法的重载(Overloading)是指在同一个类中可以定义多个方法名相同但参数类型、个数或顺序不同的方法。重载方法可以让程序代码更加简洁、清晰,并且可以根据不同的参数类型、个数和顺序进行不同的处理。
方法的重载需要满足以下两个条件:
1.方法名相同
2.参数列表不同,包括参数类型、个数或顺序不同
注意:返回值类型不是方法的重载条件。
例如,下面就是一个计算两个整数之和的重载方法:
public int sum(int a, int b) {
return a + b;
}
public int sum(int a, int b, int c) {
return a + b + c;
}
public int sum(double a, double b) {
return (int)(a + b);
}
上面的三个方法都是sum,但是它们的参数列表不同,所以它们是不同的方法。我们可以根据参数的不同类型、个数或顺序调用不同的方法,实现相应的功能。例如:
int result1 = sum(1, 2);
int result2 = sum(1, 2, 3);
int result3 = sum(1.0, 2.0);
通过方法的重载,我们可以使用相同的方法名来完成不同的功能,这样可以提高代码的重用性和可读性。
在谈到参数个数可以确定两个方法是否具有重载关系时,会想到定义不定长参数方法。不定长方法语法如下:
返回值 方法名(参数数据类型...参数名称)
在上述实例中,在参数列表中使用“...”形式定义不定长参数,其实这个不定长参数a就是一个数组,编译器会将“int...a”这种形式看成“int[] a”,所以在add()方法体做累加操作时使用到了for循环语句,在循环中是根据数组a的长度作为循环条件的,最后将累加结果返回。
final 是 Java 中的一个关键字,可以用来修饰类、方法和变量。使用 final 修饰的类不能被继承,使用 final 修饰的方法不能被子类重写,使用 final 修饰的变量不能被修改。
在Java中,使用final关键字来修饰变量,表示该变量的值不可被修改。final变量可以在声明时初始化,也可以在构造函数中初始化,但是在程序运行期间,它的值不能再被修改。
一般来说,final变量的命名全部大写,用下划线分隔,例如MAX_VALUE。
final变量的特点:
1.一旦被赋值,就不能再次修改
2.必须在声明时或在构造函数中赋初值
3.在方法中声明的final变量在方法结束时就被销毁
4.final变量的值不能被修改,但它所引用的对象内部的状态可以改变
final变量的作用:
1.保证常量的值不会被修改
2.避免因为修改常量的值导致程序出现错误
3.提高代码的可读性和可维护性
4.在编译器优化时会被作为常量直接替换掉,提高程序的执行效率
7.6.2 final方法
在Java中,可以使用final关键字来修饰方法,表示该方法不能被子类重写(覆盖)。当一个方法被定义为final时,子类无法改变该方法的实现,从而确保了方法在继承体系中的行为一致性和安全性。
在实际应用中,常常将一些重要的、敏感的、安全性高的方法定义为final,防止在子类中被修改,以确保程序的正确性和安全性。
本例题在运行前,会报错,因为打开电视这个方法是由final方法修饰的,子类无法重写。
final类是指被声明为final的类,即该类不能被继承,不能有子类。
final类语法如下:
final 类名{}
使用final关键字修饰一个类,可以保证该类在被创建后不能被继承。这样的类在一定程度上增强了安全性和稳定性,避免了在子类中意外修改父类的行为。final类通常被用于定义一些核心类或者工具类,如java.lang.Math和java.util.Collections等。
final类中的所有方法默认都是final方法,因此它们不能被子类重写,进一步保护了final类的行为不受到修改。在一些需要高度稳定性的场合,使用final类和final方法可以确保代码的安全性和稳定性。
多态是面向对象程序设计中的一个重要概念,指的是同一类型的对象在不同情况下会表现出不同的行为。这是通过继承和接口实现的。
在Java中,多态实现的方式主要有两种:重载和重写。重载指的是在一个类中可以定义多个方法,这些方法名称相同但参数类型和个数不同,通过传递不同的参数,调用不同的方法。重写指的是子类重写父类的方法,当父类的方法在子类中被重写时,通过子类对象调用这个方法时,实际上调用的是子类的方法,而不是父类的方法。
多态具有以下特点:
1.多态是针对方法的调用,而不是针对对象的调用。
2.多态可以使程序更加灵活和可扩展,可以实现代码的重用。
3.多态可以提高程序的可读性和可维护性。
4.多态是动态绑定的,即在程序运行时才确定调用哪个方法,因此可以实现运行时动态绑定。
5.多态可以通过接口实现,实现了接口的类可以实现多态
假如现在要编写一个绘制图形的方法draw(),如果传入正方形对象就绘制正方形,如果传入圆形对象就绘制圆形,这种场景可以使用方法重载来实现,定义如下:
public void draw(Square s){ //绘制正方形的方法
}
public void draw(Circular c){ //绘制圆形的方法
}
但这种方法有个问题:正方形和圆形都是图形,这场景细分的重载方式不仅增加了代码量,还降低了“易用度”。如果定义一个图形类,让他处理所有继承该类的对象,根据“向上转型”原则可以使每个继承图形类的对象作为draw()方法的参数,然后再draw()方法中做一些限定就可以根据不同的图形类对象绘制相应的图形。这样处理能够很好地解决代码冗余问题,同时程序也易于维护。
抽象类和接口是Java中两种不同的语言机制,它们都可以用来实现多态和封装的特性,但具体的使用场景和特点略有不同。
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是往上的类越抽象,如鸽子类继承鸟类,鸟类继承动物类等。在多态机制中,并不需要将父类初始化为对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象。
使用abstact关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法。抽象方法没有方法体,这个方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上抽象类除了被继承没有任何意义。定义抽象类的语法如下:
public abstract class Parent{
abstract void testAbstrac(); //定义抽象方法
}
只要类中有一个抽象方法,这个类就是抽象类。
抽象类被继承后需要实现其中所有的抽象方法,也就是保证以相同的方法名称,参数列表和返回值类型创建出的非抽象方法,当然也可以是抽象方法。
从图中可以看出,继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。这样在多态机制中,就可以将父类修改为抽象类,将draw()方法设置为抽象方法,然后每个子类都重写这个方法来处理。蛋这样又会出现我们刚探讨多态时讨论的问题,程序太有很多冗余的代码,同时这样的父类局限性很大,也许某个不需要draw()方法的子类也不得不重写draw()方法。如果将draw()方法设置在另一个类中,让那些需要draw()方法的类继承该类,不需要draw()方法的类继承图形类,又会产生新的问题:所有的子类都需要继承图形类,因为这些类是从图形类导出的,同时某些类还需要draw()方法,而Java中规定类不能同时继承多个父类。为了应对这种问题,接口的概念便出现了。
接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于上面的问题,可以将draw()方法封装到一个口袋中,使需要draw()方法的类实现这个接口,同时继承图形类,这就是接口存在的必要性。
Java中,接口通过关键字“interface”来定义,接口中的方法默认是public abstract类型的,常量是public static final类型的。接口可以被类实现(implements),表示类遵循了该接口的规范,同时必须实现该接口中定义的所有抽象方法。语法如下:
public interface Paintable{
void draw(); //定义接口方法可省略public abstract关键字
}
一个类可以实现多个接口,但只能继承一个父类。这使得接口成为了Java中实现多继承的一种方式。通过实现多个接口,一个类可以拥有多种类型的行为特征。
接口也可以继承(extends)其他接口,这使得接口可以通过继承机制扩展接口的行为特征。当一个接口继承了另一个接口时,它将自动包含另一个接口中的所有常量和方法定义。
总之,接口提供了一种机制,用于定义类的一组操作行为,并将这些行为规范化,以便在实现类中实现具体的操作行为。
结束力