1、Object的常用方法
1、Object根父类
1、如何理解根父类
类 java.lang.Object
是类层次结构的根类,即所有类的父类。每个类都使用 Object
作为超类。
- Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用
- 所有对象(包括数组)都实现这个类的方法。
- 如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
// ...
}
2、Object类的其中5个方法
API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的字典
,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码。
根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5个:
1、toString()
方法签名:public String toString()
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。
例如自定义的Person类:
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
2、getClass()
public final Class<?> getClass():获取对象的运行时类型
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new Person();
System.out.println(obj.getClass());//运行时类型
}
3、equals()
public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写(重写有要求)
4、hashCode()
public int hashCode():返回每个对象的hash值。
如果重写equals,那么通常会一起重写hashCode()方法,hashCode()方法主要是为了当对象存储到哈希表(后面集合章节学习)等容器中时提高存储和查询性能用的,这是因为关于hashCode有两个常规协定:
- ①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
- ②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
重写equals和hashCode方法时,要保证满足如下要求:
- ①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
- ②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
- ③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
public static void main(String[] args) {
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112
}
5、finalize()
protected void finalize():用于最终清理内存的方法
面试题:对finalize()的理解?
- 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
- 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
- 每一个对象的finalize方法只会被调用一次。
- 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存
6、重写toString和equals
3、标准JavaBean
JavaBean
是 Java语言编写类的一种标准规范。符合JavaBean
的类,要求:
(1)类必须是具体的和公共的,
(2)并且具有无参数的构造方法,
(3)成员变量私有化,并提供用来操作成员变量的set
和get
方法。
(4)重写toString方法
public class ClassName{
//成员变量
//构造方法
//无参构造方法【必须】
//有参构造方法【建议】
//getXxx()
//setXxx()
//其他成员方法
}
小结:知道Object是所有类的祖先元素即可,对于一个底层代码逻辑的遍历。无需了解太多只需明白现阶段不需要理解的很透彻,明白怎么使用即可。
2、final关键字的作用
1、final的意义
final:最终的,不可更改的
2、final修饰类
表示这个类不能被继承,没有子类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
3、final修饰方法
表示这个方法不能被子类重写
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
4、final修饰变量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
public class TestFinal {
public static void main(String[] args){
final int MIN_SCORE = 0;
final int MAX_SCORE = 100;
MyDate m1 = new MyDate();
System.out.println(m1.getInfo());
MyDate m2 = new MyDate(2022,2,14);
System.out.println(m2.getInfo());
}
}
class MyDate{
//没有set方法,必须有显示赋值的代码
private final int year;
private final int month;
private final int day;
public MyDate(){
year = 1970;
month = 1;
day = 1;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
public String getInfo(){
return year + "年" + month + "月" + day + "日";
}
}
小结:对于 一个final关键字来说,我们只需知道如果定义了final就说明他是最终的,即无法改变属性值,无法覆盖方法,因为没有子类可以去调用。
3、多态
1、多态引用
Java规定父类类型的变量可以接收子类类型的对象,这一点从逻辑上也是说得通的。
父类类型 变量名 = 子类对象;
父类类型:指子类继承的父类类型,或者实现的父接口类型。
所以说继承是多态的前提
2、多态引用的表现
表现:编译时类型与运行时类型不一致,编译时看“父类”,运行时看“子类”。
3、多态引用的好处和弊端
弊端:编译时,只能调用父类声明的方法,不能调用子类扩展的方法;
好处:运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
4、语法格式
- 声明变量是父类类型,变量赋值子类对象
- 方法的形参是父类类型,调用方法的实参是子类对象
- 实例变量声明父类类型,实际存储的是子类对象
理解:使用多态时,声明的类型需要是父类的类名或者说接口名,即
Father father = new Son();
后面的new Son()就是子类的对象。1跟2的意思差不多。
-
数组元素是父类类型,元素对象是子类对象
private Pet[] pets;//数组元素类型是父类类型,元素存储的是子类对象
理解,即声明一个父类型的一个数组,往数组中的元素添加该类型的子类,比如宠物(Pet)它拥有与主人玩的技能,但是它所包含的子类比如dog,cat等与主人玩的方式不同,那我们存储进去后,就可以进行一个遍历,遍历的结果就是没种类型的宠物与主人的玩法。
充分的利用的多态的特性,让在原本上线项目后,需要进行添加新的品种宠物时,无需去大刀阔斧,只需添加一个子类去数组内即可。
-
方法返回值类型声明为父类类型,实际返回的是子类对象
public class PetShop { //返回值类型是父类类型,实际返回的是子类对象 public Pet sale(String type){ switch (type){ case "Dog": return new Dog(); case "Cat": return new Cat(); } return null; } }
public class TestPetShop { public static void main(String[] args) { PetShop shop = new PetShop(); Pet dog = shop.sale("Dog"); dog.setNickname("小白"); dog.eat(); Pet cat = shop.sale("Cat"); cat.setNickname("雪球"); cat.eat(); } }
首先 Pet sale()是一个方法,那么返回值必然是Pet类型,但是在调用时我们发现闯入了参数,参数是Pet的一个子类,所以说符合多态的语法格式,所以可以返回,那在编译时运行父类,实际使用时则是使用子类覆盖的方法。
小结:多态是一个抽象的,所以理解就比较麻烦,但是理解过后则会明白多态的运行机制还是比较简单的
4、向上转型和向下转型
首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。
这个和基本数据类型的转换是不同的。基本数据类型是把数据值copy了一份,相当于有两种数据类型的值。而对象的赋值不会产生两个对象。
1、为什么要类型转换呢?
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过。
- 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
- 此时,一定是安全的,而且也是自动完成的
- 向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型
- 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型
- 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
##2、如何向上转型与向下转型
向上转型:自动完成
向下转型:(子类类型)父类变量
public class ClassCastTest {
public static void main(String[] args) {
//没有类型转换
Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog
//向上转型
Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
pet.setNickname("小白");
pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouse
Dog d = (Dog) pet;
System.out.println("d.nickname = " + d.getNickname());
d.eat();//可以调用eat方法
d.watchHouse();//可以调用子类扩展的方法watchHouse
Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
//这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
}
}
##3、instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
变量/匿名对象 instanceof 数据类型
那么,哪些instanceof判断会返回true呢?
- 变量/匿名对象的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
- 变量/匿名对象的运行时类型<= instanceof后面数据类型,才为true
小结:instanceof的出现是便于我们进行多态的父子类的判断,利于我们进行一个转型操作,使得父类类型元素可以强转为子类元素类型,这样可以让我们使用子类拥有父类没有的方法,即子类自行添加的方法。
5、 抽象类
1、由来
抽象:即不具体、或无法具体
例如:当我们声明一个几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现,而是应该交给子类各自具体实现。那么父类在声明这些方法时,就只有方法签名,没有方法体,我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类必须是抽象类。
2、语法格式
- 抽象方法:被abstract修饰没有方法体的方法。
- 抽象类:被abstract修饰的类。
抽象类的语法格式
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
抽象方法的语法格式
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
注意:抽象方法没有方法体
代码举例:
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
public void run (){
System.out.println("小猫吃鱼和猫粮");
}
}
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用eat方法
c.eat();
}
}
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
3、注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
-
抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
总结:明白关键字abstract是进行一个抽象化,其次抽象类有抽象方法,抽象属性,普通方法,普通属性,抽象属性我们在声明式需要进行一个初始化,抽象方法不可以拥有代码体。最后,抽象方法只能在抽象类中进行一个创建,反之就是有抽象方法的类肯定是抽象类。还有就是抽象类的子类需要进行一个构造方法的重写。
6、接口的思想
概述
生活中大家每天都在用USB接口,那么USB接口与我们今天要学习的接口有什么相同点呢?
USB是通用串行总线的英文缩写,是Intel公司开发的总线架构,使得在计算机上添加串行设备(鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等)非常容易。只须将设备插入计算机的USB端口中,系统会自动识别和配置。 有了USB,我们电脑需要提供的各种插槽的口越来越少,而能支持的其他设备的连接却越来越多。
那么我们平时看到的电脑上的USB插口、以及其他设备上的USB插口是什么呢?
其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范的一种具体设备而已。
根据时代发展,USB接口标准经历了一代USB、第二代USB 2.0和第三代USB 3.0 。
USB规格第一次是于1995年,由Intel、IBM、Compaq、Microsoft、NEC、Digital、North Telecom等七家公司组成的USBIF(USB Implement Forum)共同提出,USBIF于1996年1月正式提出USB1.0规格,频宽为1.5Mbps。
USB2.0技术规范是有由Compaq、Hewlett Packard、Intel、Lucent、Microsoft、NEC、Philips共同制定、发布的,规范把外设数据传输速度提高到了480Mbps,被称为USB 2.0的高速(High-speed)版本.
USB 3.0是最新的USB规范,该规范由英特尔等公司发起,USB3.0的最大传输带宽高达5.0Gbps(640MB/s),USB3.0 引入全双工数据传输。5根线路中2根用来发送数据,另2根用来接收数据,还有1根是地线。也就是说,USB 3.0可以同步全速地进行读写操作。
USB版本 | 最大传输速率 | 速率称号 | 最大输出电流 | 推出时间 |
---|---|---|---|---|
USB1.0 | 1.5Mbps(192KB/s) | 低速(Low-Speed) | 5V/500mA | 1996年1月 |
USB1.1 | 12Mbps(1.5MB/s) | 全速(Full-Speed) | 5V/500mA | 1998年9月 |
USB2.0 | 480Mbps(60MB/s) | 高速(High-Speed) | 5V/500mA | 2000年4月 |
USB3.0 | 5Gbps(500MB/s) | 超高速(Super-Speed) | 5V/900mA | 2008年11月 |
USB 3.1 | 10Gbps(1280MB/s) | 超高速+(Super-speed+) | 20V/5A | 2013年12月 |
电脑边上提供了USB插槽,这个插槽遵循了USB的规范,只要其他设备也是遵循USB规范的,那么就可以互联,并正常通信。至于这个电脑、以及其他设备是哪个厂家制造的,内部是如何实现的,我们都无需关心。
这种设计是将规范和实现分离,这也正是Java接口的好处。Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面相接口的低耦合,为系统提供更好的可扩展性和可维护性。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。
- 例如:你能不能用USB进行连接,或是否具备USB通信功能,就看你是否遵循USB接口规范
- 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范
总结:接口的作用:可以让所有人调用!不用声明多个相同的属性方法。
7、接口的语法和特点
1、定义格式
接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,枚举,接口,注解。
1、接口的声明格式
【修饰符】 interface 接口名{
//接口的成员列表:
// 公共的静态常量
// 公共的抽象方法
// 公共的默认方法(JDK1.8以上)
// 公共的静态方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
示例代码:
public interface Usb3{
//静态常量
long MAX_SPEED = 500*1024*1024;//500MB/s
//抽象方法
void in();
void out();
//默认方法
default void start(){
System.out.println("开始");
}
default void stop(){
System.out.println("结束");
}
//静态方法
static void show(){
System.out.println("USB 3.0可以同步全速地进行读写操作");
}
}
2、接口的成员说明
接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。
在JDK8之前,接口中只允许出现:
(1)公共的静态的常量:其中public static final可以省略
(2)公共的抽象的方法:其中public abstract可以省略
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
在JDK1.8时,接口中允许声明默认方法和静态方法:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
在JDK1.9时,接口又增加了:
(5)私有方法
除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。
2、 接口的使用
1、使用接口的静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
public class TestUsb3 {
public static void main(String[] args) {
//通过“接口名.”调用接口的静态方法
Usb3.show();
//通过“接口名.”直接使用接口的静态常量
System.out.println(Usb3.MAX_SPEED);
}
}
2、类实现接口(implements)
接口不能创建对象,但是可以被类实现(implements
,类似于被继承)。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
注意:
-
如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。
-
默认方法可以选择保留,也可以重写。
重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
-
接口中的静态方法不能被继承也不能被重写
示例代码:
public class MobileHDD implements Usb3 {
//重写/实现接口的抽象方法,【必选】
public void out() {
System.out.println("读取数据并发送");
}
public void in(){
System.out.println("接收数据并写入");
}
//重写接口的默认方法,【可选】
//重写默认方法时,default单词去掉
public void end(){
System.out.println("清理硬盘中的隐藏回收站中的东西,再结束");
}
}
3、使用接口的非静态方法
- 对于接口的静态方法,直接使用“接口名.”进行调用即可
- 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
- 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
public class TestMobileHDD {
public static void main(String[] args) {
//创建实现类对象
MobileHDD b = new MobileHDD();
//通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
b.start();
b.in();
b.stop();
//通过接口名调用接口的静态方法
// MobileHDD.show();
// b.show();
Usb3.show();
}
}
4、接口的多实现(implements)
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
定义多个接口:
public interface A {
void showA();
void show();
}
public interface B extends A {
void showB();
void show();
}
定义实现类:
public class C implements A,B {
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}
测试类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.showA();
c.showB();
c.show();
}
}
小结:拥有了接口后,可以更加的方便,因为我们可以直接使用接口进行一个属性方法,不用再次去声明,切接口比抽象类更加方便,当然最重要的一个点就是可以进行多个接口!
总结:首先介绍了Object是所有对象的祖先对象,即生成一个类之后会默认自带方法,例如:String。接下来则是关于关键字final的作用即使用场景,接着引入了多态,多态比较抽象,不好去理解,但是理解之后我们会发现其实也不难,抽象完后我们才能进入一个转型,转型的基础就是抽象,而抽象的基础则是继承。再然后我们进入了一个抽象类,抽象方法。他们告诉我们不能创建对象即new 对象使得我们继承,或者多态时可以进行一个自定义方法,属性。最后我们来到了接口,接口则是告诉我们可以在接口里面创建,他比多态更加的方便,但同时我们也需要掌握了多态,继承后我们才好更加的使用。
每日金句
悲歌可以当泣,远望可以当归——汉乐府民歌《悲歌》