《第一行代码:Java》第4章、面向对象高级知识读书笔记
文章目录
- 第4章、面向对象高级知识
- 4.1 继承性
- 继承的实现
- 继承的限制
- 4.2 覆写
- 方法的覆写
- 属性的覆盖
- this与super
- 4.4 final关键字
- 4.5 多态性
- 多态性的分类
- 对象向上转型
- 对象向下转型
- 对象多态性的作用
- instanceof
- 4.6 抽象类
- 抽象类定义
- 抽象类的相关限制
- 4.7 接口
- 接口的基本定义
- 接口的实际应用——标准
- 接口的应用——工厂设计模式(Factory)
- 接口的应用——代理设计模式(Proxy)
- 抽象类与接口的区别
- 4.8 Object类
- Object类的基本定义
- toString():取得对象信息
- equals():对象比较
- Object类与引用数据类型
- 完善链表
- 3.9 综合练习:宠物商店
- 4.10 匿名内部类
- 4.11 基本数据类型的包装类
- 装箱与拆箱
- 数据类型转换
- 本章小结
- 本章小结
第4章、面向对象高级知识
4.1 继承性
继承性要解决的就是代码重用的问题,利用继承性可以从已有的类继续派生出新的子类,也可以利用子类扩展出更多操作功能
继承的实现
继承性严格来讲就是指扩充一个类已有的功能。在Java中,如果要实现继承的关系,可以使用如下语法完成:
class 子类 extends 父类 {}
对于继承的格式有3点说明:
- 对于extends而言,应该翻译为扩充,但为了理解方便,统一将其称为继承
- 子类又被称为派生类
- 父类又被称为超类(Super Class)
class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
class Student extends Person { // Student类继承了Person类
private String school ; // 子类扩充的属性
public void setSchool(String school) { // 扩充的方法
this.school = school ;
}
public String getSchool() { // 扩充的方法
return this.school ;
}
}
继承的限制
- 限制一:Java不允许多继承,但允许多层继承。
- 限制二:子类继承父类时,严格来说会继承父类中的全部操作,但是对于所有的私有操作属于隐式继承,而非私有操作为显示继承。
- 隐式继承:即不允许通过子类直接访问,但可以通过显示继承的方法进行对隐式继承的属性进行操作
class A {
private String msg;
public void setMsg(String msg) {
this.msg = msg;
}
public String getMsg() {
return this.msg;
}
}
class B extends A { // 继承自A类,但在B类中不能针对msg属性进行直接访问,因为私有属性是隐式继承的
}
public class Demo {
public static void main(String args[]) {
B b = new B();
b.setMsg("Hello"); // 设置msg属性,属性通过A类继承
System.out.println(b.getMsg()); // 通过子类对象取得msg属性
}
}
- 限制三:在子类对象构造前一定会默认调用父类的构造(默认使用无参构造),以保证父类的对象先实例化,子类对象后实例化。如果父类中只有有参构造方法,则在子类的构造函数中必须使用super()去调用父类中的的构造函数。
class A {
public A() {
System.out.println("A、A类的构造方法!");
}
}
class B extends A {
public B() {
System.out.println("B、B类的构造方法!");
}
}
public class Demo {
public static void main(String args[]) {
new B(); // 实例化子类对象
}
}
/*
程序执行结果:
A、A类的构造方法!
B、B类的构造方法!
*/
- 这相当于子类构造方法中隐含了super()的语句调用,由于super()主要是调用父类的构造方法,所以必须方法子类构造方法的首行。
- 如果父类中拥有无参构造方法,则可不加super()方法;但如果父类中没有无参构造方法,则子类中必须使用super(参数1, 参数2, …)来调用父类指定的构造方法。
class A {
public A(String title) { // 父类提供的有参构造方法
System.out.println("A、A类的构造方法,title = " + title);
}
}
class B extends A { // 定义子类B
public B(String title) { // 子类提供有参构造
super(title); // 明确调用父类构造,否则将出现编译错误
System.out.println("B、B类的构造方法!");
}
}
public class Demo {
public static void main(String args[]) {
new B("Hello"); // 实例化子类对象
}
}
/*
程序执行结果:
A、A类的构造方法,title = Hello
B、B类的构造方法!
*/
4.2 覆写
子类在定义属性或方法时,有可能出现定义的属性或方法与父类中的同名的情况,这样的操作就被称为覆写。
方法的覆写
- 只有子类定义了和父类的方法名称、返回值类型、参数类型及个数都完全相同的情况下才叫方法的覆写
- 如果子类覆写了父类的一个方法,那么子类对象在调用该方法时一定是调用的是覆写后的方法。(准确的说是:如果该对象使用的是子类构造方法,则它调用覆写过的方法时所调用的是被覆写过的方法,)
- 可以使用 super.方法() 来调用父类中被覆写的方法
class A {
public void print() {
System.out.println("更多课程请访问:www.mldn.cn") ;
}
}
class B extends A {
public void print() {
super.print(); // 访问父类中的print()方法
System.out.println("更多课程请访问:www.yootk.com") ;
}
}
public class TestDemo {
public static void main(String args[]) {
B b = new B();
b.print();
}
}
/*
程序执行结果:
更多课程请访问:www.mldn.cn
更多课程请访问:www.yootk.com
*/
- 被子类所覆写的方法不能拥有比父类更严格的访问控制权限(public > default(默认,什么都不写) > private)。
即如果父类该方法是public,那子类覆写该方法只能是public,父类为default,子类可以为default和public。
- 注意:如果父类中的方法是private的,则不可以覆写;因为子类是看不到父类的private操作得,所以这样写相当于重新定义了一个方法。
以下为覆写失败:
class A {
public void fun() {
this.print() ; // 调用print()方法
}
private void print() { // 此为private权限,无法覆写
System.out.println("更多课程请访问:www.mldn.cn") ;
}
}
class B extends A {
public void print() { // 不能覆写print()方法
System.out.println("更多课程请访问:www.yootk.com") ;
}
}
public class TestDemo {
public static void main(String args[]) {
B b = new B() ; // 实例化子类对象
b.fun() ; // 调用父类继承来的fun()方法
b.print(); // 调用子类定义的print()方法
}
}
// 程序执行结果: 更多课程请访问:www.mldn.cn
// 更多课程请访问:www.yootk.com
以下为覆写成功:
class A {
public void fun() {
this.print() ; // 调用print()方法
}
public void print() { // 此为private权限,无法覆写
System.out.println("更多课程请访问:www.mldn.cn") ;
}
}
class B extends A {
public void print() { // 不能覆写print()方法
System.out.println("更多课程请访问:www.yootk.com") ;
}
}
public class TestDemo {
public static void main(String args[]) {
B b = new B() ; // 实例化子类对象
b.fun() ; // 调用父类继承来的fun()方法
b.print(); // 调用子类覆写的print()方法
}
}
// 程序执行结果: 更多课程请访问:www.yootk.com
// 更多课程请访问:www.yootk.com
属性的覆盖
- 如果子类定义了和父类完全相同的属性名称时,就称为属性覆盖
- 只需要属性名称相同,就算覆盖
- 覆盖后可以使用 super.属性 对父类中被覆盖的属性进行访问。
- 但属性覆盖在实际开发中没有意义,因为属性都需要private封装,一旦被封装了,子类根本看不到。
this与super
- this:
- 功能:调用本类构造、本类方法、本类属性
- 形式:先查找本类中是否存在有指定的调用结构,如果有则直接调用,如果没有则调用父类定义。
- super:
- 功能:子类调用父类构造、父类方法、父类属性
- 形式:不查找子类,直接调用父类操作。
4.4 final关键字
在Java中final被称为终结器,在Java里面可以使用final来定义类、方法、属性。
- 用final定义类:使用final定义的类不能有子类,即任何类都不能继承以final声明的父类(比如说String类就是使用final定义的类,所以String类不能被继承)
- 用final定义方法:使用final定义的方法不能被子类覆写。(相当于保护这个方法不被破坏)
- 用final定义属性:使用final定义的属性就变为常量,意思就是必须在定义的时候就进行初始化,且不能进行修改。
4.5 多态性
多态性的分类
面向对象的多态性体现在两个方面:
- 方法的多态性:重写和覆写
- 重载:统一方法名称,根据不同的参数类型及个数完成不同的功能
- 覆写:同一个方法,根据实例化的子类对象不同,完成的功能也不同。
- 对象的多态性:父子类对象的转换
- 向上转型:子类对象变父类对象,自动转换,格式:
父类 父类对象 = 子类实例;
- 向下转型:父类对象变子类对象,强制转换,(注意只有这个父类实例是由子类构造实例化出来的时候才能这样转换,也就是说得先有子类对象的向上转型为父类对象,才能将这个父类对象向下转型为子类对象)格式:
子类 子类对象 = (子类)父类实例
- 对象多态性和方法的覆写是紧密联系在一起的
对象向上转型
向上转型:子类对象变父类对象,自动转换
Class A {
public void print() {
System.out.println("A、public void print(){}");
}
}
class B extends A {
public void print() {
System.out.println("B、public void print(){}");
}
}
public class TestDemo {
public static void main(String args[]) {
A a = new B(); // 实例化的是子类对象,对象向上转型
a.print(); // 调用被子类覆写过的方法
}
}
// 程序执行结果: B、public void print(){}
对象向下转型
向下转型:父类对象变子类对象,强制转换
public class TestDemo {
public static void main(String args[]) {
A a = new B(); // 实例化的是子类对象,对象向上转型
B b = (B) a ; // 对象需要强制性地向下转型
b.print(); // 调用被子类覆写过的方法
}
}
// 程序执行结果: B、public void print(){}
错误的向下转型操作:
public class TestDemo {
public static void main(String args[]) {
A a = new A(); // 直接实例化子类对象
// 此时并没有发生子类对象向上转型的操作,所以强制转型会带来安全隐患
B b = (B) a; // 强制向下转型,此处产生“ClassCastException”异常
b.print(); // 此语句无法执行
}
}
对象多态性的作用
在实际开发中,对象向上转型的主要意义在于参数的同一,也是最为重要用法;而对象的向下转移指的是调用子类的个性化操作方法。
- 对象向上转型作用: (使用同一个fun()方法,调用的却是不同子类的print()方法)
class A {
public void print() {
System.out.println("A、public void print(){}");
}
}
class B extends A { // 定义A的子类
public void print() { // 此时子类覆写了父类中的print()方法
System.out.println("B、public void print(){}");
}
}
class C extends A { // 定义A的子类
public void print() { // 此时子类覆写了父类中的print()方法
System.out.println("C、public void print(){}");
}
}
public class TestDemo {
public static void main(String args[]) {
fun(new B()) ; // 对象向上转型,等价于:A a = new B() ;
fun(new C()) ; // 对象向上转型,等价于:A a = new C() ;
}
public static void fun(A a) {
a.print();
}
}
instanceof
对象 instanceof 类:判断某一对象是否属于指定类的实例,返回值为boolean型
public class TestDemo {
public static void main(String args[]) {
A a = new B(); // 对象向上转型
System.out.println(a instanceof A); // true
System.out.println(a instanceof B); // true
System.out.println(null instanceof A); // false
}
}
为了保证安全,在进行对象向下转换的操作之前,最好使用instanceof判断一下对象是否属于子类
if (a instanceof B) { // 如果a对象是B类的实例
B b = (B) a; // 向下转型
}
4.6 抽象类
抽象类是代码开发中的重要组成部分,利用抽象雷可以明确地定义子类需要覆写的方。
抽象类定义
- 普通类:可以直接产生实例化对象,并且普通类中可以包含构造方法、普通方法、static方法、常量、变量。
- 抽象类:使用关键字 abstract 进行定义的类。
- 抽象方法:使用关键字 abstract 进行定义的方法。
- 普通方法与抽象方法的区别:
- 抽象方法必须用 abstract 关键字定义
- 普通方法必须要有方法体(方法体即 “{ }” )
- 抽象方法不需要方法体
- 抽象类不能够直接进行实例化操作
- 要想使用抽象类,必须遵守以下原则:
- 抽象类必须有子类,即一个抽象类必须被子类继承
- 抽象类的子类必须覆写抽象类中的全部抽象方法
- 依靠对象的向上转型概念,即可以通过子类完成实例化
abstract class A { // 定义一个抽象类,使用abstract声明
public void fun() {
System.out.println("存在有方法体的方法!");
}
public abstract void print(); // 抽象方法,使用abstract声明,无需方法体
}
//一个子类只能够继承一个抽象类,属于单继承局限
class B extends A { // B类是抽象类的子类,并且是一个普通类
public void print() { // 强制要求覆写的方法
System.out.println("Hello World !") ;
}
}
public class TestDemo {
public static void main(String args[]) {
A a = new B() ; // 向上转型
a.print() ; // 被子类覆写过的方法
}
}
- 注意:在开发过程中,普通类尽量不要继承另一个普通类,而要继承抽象类。
抽象类的相关限制
- 抽象类一定存在构造方法,目的是为了属性的初始化,并且子类对象实例化时依旧是先执行父类构造在执行子类构造。
- 抽象类不能使用final定义,因为抽象类必须有子类,而final定义的类不能有子类
- 抽象类可以没有抽象方法
- 抽象类也可以定义内部的抽象类,而且子类也可以根据需求选择是否定义内部类来继承抽象内部类:
abstract class A { // 定义一个抽象类
abstract class B { // 定义内部抽象类
public abstract void print() ;
}
}
class X extends A {
public void print() {
System.out.println("更多课程请访问:www.yootk.com") ;
}
class Y extends B { // 定义抽象内部类的子类,此类不是必须编写
public void print() {
// 方法覆写
}
}
}
- 外部抽象类不允许使用static声明,而内部抽象类可以使用static声明。使用static声明的内部抽象类就相当于一个外部抽象类,继承时可以使用
“ 外部类.内部类 ”的形式表示类名。
abstract class A { // 定义一个抽象类
static abstract class B { // static定义的内部类属于外部类
public abstract void print() ;
}
}
class X extends A.B { // 继承static内部抽象类
public void print() {
System.out.println("更多课程请访问:www.yootk.com") ;
}
}
public class TestDemo {
public static void main(String args[]) {
A.B ab = new X() ; // 向上转型
ab.print() ;
}
}
- 在抽象类在,如果定义了static属性或方法时,就可以在没有实例化对象时使用,通过类名来使用
- 隐藏抽象类子类:抽象类中的内部类继承外部类
abstract class A { // 定义一个抽象类
public abstract void print(); // 定义抽象方法
private static class B extends A { // 内部抽象类子类
public void print() { // 覆写抽象类的方法
System.out.println("更多课程请访问:www.yootk.com");
}
}
public static A getInstance() {
return new B(); // 完成对象向上的转换
}
}
public class TestDemo {
public static void main(String args[]) {
// 此时取得抽象类对象时完全不需要知道B类这个子类存在
A a = A.getInstance(); // 通过类名A直接调用getInstance()
a.print(); // 调用被覆写过的抽象方法
}
}
本程序在抽象类A中利用内部类B进行子类继承,而后在调用,用户不需要知道抽象类的具体子类,只需要调用类中的getInstance()方法就可以取得抽象类的实例化对象。
- 在任何一个类的构造执行完之前,所有属性的内容都是其对应数据结构的默认值。
abstract class A { // 定义抽象类
public A() { // 2. 父类构造方法
// 此方法为抽象方法,所以要调用子类中已经被覆写过的方法
this.print(); // 3. 调用print()方法
}
public abstract void print(); // 抽象方法
}
class B extends A {
private int num = 100; // 子类属性的默认值,开始子类对象构造后才生效
public B(int num) { // 5. 子类构造方法
this.num = num;
}
public void print() { // 4. 调用覆写后的方法,此时子类对象还未构造,num还没被初始化,所以为int型的默认值0
System.out.println("num = " + num);
}
}
public class TestDemo {
public static void main(String args[]) {
new B(30); // 1. 执行构造
}
}
// 程序执行结果: num = 0
4.7 接口
利用抽象类可以实现对子类覆写方法的控制,但是抽象类的子类存在一个很大的问题,即单继承局限。为了打破这个局限,就需要用Java接口来解决。
接口的基本定义
- 如果一个抽象类中只有抽象方法和全局常量,那么这种情况下不会将其定义为一个抽象类,而是定义为接口。严格来说接口是一种特殊的类,而这个类里只有抽象方法和全局常量。
- 注意:这是JDK1.8之前对接口的定义,JDK1.8之后接口中可以定义更多的操作,具体在第8章中讲解,这里先了解传统意义上的接口。
- 接口使用关键字 interface 来定义
interface A { // 定义接口
public static final String MSG = "YOOTK"; // 全局常量
public abstract void print(); // 抽象方法
}
- 对于接口而言,组成部分只有抽象方法和全局常量,所以可以将接口定义进行简化:
interface A {
String MSG = "HELLO";
void fun();
}
- 接口使用原则:
- 接口必须有子类,子类使用关键字 implements 实现多个接口,避免单继承局限
- 接口的子类必须覆写接口中的全部抽象方法
interface A {} // 定义接口A
interface B {} // 定义接口B
class X implements A,B {} // X类实现了A和B两个接口
- 接口对象可以利用子类对象的向上转型进行实例化操作
interface A { // 定义接口
public static final String MSG = "YOOTK"; // 全局常量
public abstract void print(); // 抽象方法
}
interface B { // 定义接口
public abstract void get(); // 抽象方法
}
class X implements A, B { // X类实现了A和B两个接口
public void print() { // 覆写A接口的抽象方法
System.out.println("A接口的抽象方法!");
}
public void get() { // 覆写B接口的抽象方法
System.out.println("B接口的抽象方法!");
}
}
public class TestDemo {
public static void main(String args[]) {
// 此时X类是A和B两个接口的子类,所以此类对象可以同时实现两个接口的向上转型
X x = new X(); // 实例化子类对象
A a = x; // 向上转型
B b = (B)a; // a实际上代表X类对象
a.print(); // 调用被覆写过的方法
b.get(); // 调用被覆写过的方法
System.out.println(A.MSG); // 直接访问全局常量
System.out.println(a instanceof A) ; // 判断a是否是A接口实例,true
System.out.println(a instanceof B) ; // 判断a是否是B接口实例,true
}
}
/*
程序执行结果:
A接口的抽象方法!
B接口的抽象方法!
YOOTK
true
true
*/
- 一个子类可以同时继承(extends)抽象类和实现接口(implements)
interface A {} // 定义接口
interface B {} // 定义接口
abstract class C {} // 定义抽象类
class X extends C implements A, B {} // X类继承了抽象类C,实现了A和B两个接口
子类X是接口A、B以及抽象类C三个的子类,所以X类的对象可以同时被三个父类实例化
- 一个抽象类可以继承(extends)一个抽象类或者实现(implements)多个接口
一个接口不能继承(extends)抽象类,但可以继承(extends)多个父接口
interface A {} // 定义父接口
interface B {} // 定义父接口
interface C extends A, B {} // 利用extends,实现接口多继承
class X implements C {} // 实现C接口子类要覆写全部抽象方法
- 接口内部可以定义普通内部类、抽象内部类、内部接口。注意:使用static定义内部接口,这个内部接口就表示一个外部接口
接口的实际应用——标准
在日常的生活中,人们会经常听到接口这一词,而最为常见的就是USB接口。利用USB接口可以连接U盘、打印机、MP3等标准设备,如下图所示。
通过上图所示的类图关系可以发现,计算机应该作为一个类,而计算机上要提供对USB接口标准的支持,这样不管什么设备,在计算机上都会按照USB接口中定义的标准执行,符合USB 接口标准的可以有很多类设备。
// 定义USB标准
interface USB {
public void start(); // USB设备开始工作
public void stop(); // USB设备停止工作
}
// 定义计算机类
class Computer {
public void plugin(USB usb) { // 插入USB接口设备(子类对象)
usb.start(); // 开始工作
usb.stop(); // 停止工作
}
}
// 定义U盘子类
class Flash implements USB { // 实现USB接口
public void start() {
System.out.println("U盘开始使用");
}
public void stop() {
System.out.println("U盘停止使用");
}
}
// 定义打印机子类
class Print implements USB { // 实现USB接口
public void start() {
System.out.println("打印机开始使用");
}
public void stop() {
System.out.println("打印机停止使用");
}
}
public class TestDemo {
public static void main(String args[]) {
Computer com = new Computer(); // 实例化计算机类
com.plugin(new Flash()); // 插入USB接口设备
com.plugin(new Print()); // 插入USB接口设备
}
}
/*
程序执行结果:
U盘开始使用
U盘停止使用
打印机开始使用
打印机停止使用
*/
接口的应用——工厂设计模式(Factory)
解决代码耦合问题。
观察代码问题:
interface Fruit { // 定义接口
public void eat(); // 定义抽象方法
}
class Apple implements Fruit { // 定义接口子类
public void eat() { // 覆写抽象方法
System.out.println("*** 吃苹果。");
}
}
class Orange implements Fruit { // 定义接口子类
public void eat() { // 覆写抽象方法
System.out.println("*** 吃橘子。");
}
}
public class TestDemo {
public static void main(String args[]) {
Fruit f1 = new Apple(); // 子类实例化父类对象
Fruit f2 = new Orang();
f1.eat(); // 调用被覆写过的方法
f2.eat();
}
}
好的代码风格应该遵循以下两个标准:
- 客户端(现在为主方法)调用简单,不需要关注具体的细节;
- 程序代码的修改,不影响客户端的调用,即使用者可以不去关心代码是否变更。
而上面的代码每增加一个类,都要去修改客服端(主方法)的代码。这个时候如果有更多的子类呢?难道每次都要去修改实例化接口的子类吗?在整个过程中,客户端关心的事情只有一件:如何可以取得 Fruit 接口对象。至于说这个对象是被哪个子类所实例化的客户端根本就不需要知道,所以在整个代码中最大的问题就在于关键字“new”的使用上,也就是 Fruit f1 = new Apple(); 这条代码上。
如果我们想要的是让客服端只看见接口而不让其看见子类,所以只需一个中间工具类来取得接口对象就可以了。解决方案是加一个工厂类(Factory),如下图所示。这样客户端就不再需要关系接口子类了,只需要通过工厂类(Factory)就可以取得接口对象。
// 增加工厂类进行过渡
class Factory { // 定义工厂类,此类不提供属性
/**
* 取得指定类型的接口对象
* @param className 要取得的类实例化对象标记
* @return 如果指定标记存在,则Fruit接口的实例化对象,否则返回null
*/
public static Fruit getInstance(String className) {
if ("apple".equals(className)) { // 是否是苹果类
return new Apple();
} else if ("orange".equals(className)) { // 是否是橘子类
return new Orange();
} else {
return null;
}
}
}
public class TestDemo {
public static void main(String args[]) {
Fruit f1 = Factory.getInstance("apple"); // 通过工厂类取得指定标记的对象
Fruit f2 = Factory.getInstance("orange");
f1.eat(); // 调用接口方法
f2.eat();
}
}
本程序在客户端的操作上取消关键字new的使用,而使用Factory.getInstance()方法根据指定子类的标记取得接口实例化对象,这时客户端不再需要关注具体子类,也不需要关注Factory类是怎样处理的只需要关注如何取得接口对象并且操作。这样的设计在开发中就称为工厂设计模式。
接口的应用——代理设计模式(Proxy)
代理设计也是在Java开发中使用较多的一种设计模式,所谓代理设计就是指一个代理主题来操作真实主题,真实主题执行具体的业务操作,而代理主题负责其他相关业务的处理。就好比在生活中经常使用到的代理上网,客户通过网络代理连接网络,由代理服务器完成用户权限、访问限制等与上网操作相关的操作,如图下图所示。
不管是代理操作也好,真实的操作也好,其共同的目的就是上网,所以用户关心的只是如何上网,至于里面是如何操作的用户并不关心,因此可以得出下图所示的分析结果。
interface Network{ // 定义Network接口
public void browse() ; // 定义浏览的抽象方法
}
class Real implements Network{ // 真实的上网操作
public void browse(){ // 覆写抽象方法
System.out.println("上网浏览信息") ;
}
}
class Proxy implements Network{ // 代理上网
private Network network ;
public Proxy(Network network){ // 设置代理的真实操作
this.network = network ; // 设置代理的子类
}
public void check(){ // 与具体上网相关的操作
System.out.println("检查用户是否合法");
}
public void browse(){
this.check() ; // 可以同时调用多个与具体业务相关的操作
this.network.browse() ; // 调用真实上网操作
}
}
public class TestDemo {
public static void main(String args[]){
Network net = null ; // 定义接口对象
net = new Proxy(new Real()) ; // 实例化代理,同时传入代理的真实操作
net.browse() ; // 客户只关心上网浏览一个功能
}
}
抽象类与接口的区别
- 抽象类与接口的比较
经过比较可以发现,抽象类中支持的功能绝对要比接口多,但是其有一点不好,那就是单继承局限,所以这重要的一点就掩盖了所有抽象类的优点,即当抽象类和接口都可以使用时,优先考虑接口。
- 关于实际开发中接口使用的几个建议:
- 在进行某些公共操作时一定要定义出接口
- 有了接口就需要利用子类完善方法
- 如果是自己写的接口,那么绝对不要在客户端使用关键字 new 直接实例化接口子类,应该使用工厂类完成。
- 接口是在类之上的标准
如果现在要定义一个动物,那么动物肯定是一个公共标准,而这个公共标准就可以通过 ”接口“ 来完成。
在动物中又分为两类:哺乳动物、卵生动物,而这个标准属干对动物标准进一步细化,应该称为 ”子标准 “,所以此种关系可以使用接口的继承(extends)来表示。
而哺乳动物又可以继续划分为:人、狗、猫等不同的类型,这些由于不表示具体的事务标准,所以可以使用抽象类来实现(implements)哺乳动物接口进行表示。
现在如果要表示出个工人或者是学生这样的概念,肯定是一个具体的定义,则需要使用类的继承来表示。
由于每一个学生或每一个工人都是具体的,因此就通过对象来表示。所以以上几种关系可以通过下图来表示。
在所有设计中,接口应该是最先被设计出来的,所以在项目开发中,接口设计最重要。
4.8 Object类
利用继承与对象多态性的概念可以解决子类对象与匪类对象的自动转型操作,但如果想要统一开发中的参数类型,就必须有一种类可以称为所有类的父类,而这个类就是 Object 类。
Object类的基本定义
- Object类是所有类的父类,也就是说任何一个类在定义时没有明确的继承一个父类,那么它就是Object类的子类。即下面两个类的定义效果完全相同:
class Book {}
class Book extends Object {}
- 既然Object类是所有类的父类,那么就可以利用Object类接受所有类的对象
class Book {
}
public class TestDemo {
public static void main(String args[]) {
Object obja = new Book(); // 向上转型,接收Book子类对象
Object objb = "hello"; // 向上转型,接收String子类对象
Book b = (Book) obja; // 测试向下转型
String s = (String) objb; // 测试向下转型
}
}
- 对于任意一个简单Java类而言,理论上应覆写Object类中的3个方法:
public String toString(); // 取得对象
public boolean equals(Object obj); // 对象比较
public int hashCode(); // 取得对象哈希码,在第14章中讲解
toString():取得对象信息
之前我们进行过这样的实验,直接用System.out.print()打印对象,默认情况下会输出这个对象的编码。但是String类的对象直接输出却能输出对象中的内容。其实这是因为在String类中覆写了 toString() 方法。
- 自定义类对象与String类对象的直接输出
class Book extends Object { // 子类可以继承Object类中的方法
}
public class TestDemo {
public static void main(String args[]) {
Object obja = new Book(); // 向上转型,接收Book子类对象
Object objb = "yootk"; // 向上转型,接收String子类对象
System.out.println(obja);
System.out.println(obja.toString()); // 直接调用toString()方法输出
System.out.println(objb); // 输出String对象
}
}
/*
程序执行结果:
Book@1db9742
Book@1db9742
yootk
*/
- 覆写自定义类的toString()方法
class Book { // 此类为Object子类
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public String toString() { // 替代了getInfo(),并且toString()可以在输出的时候自动调用
return "书名:" + this.title + ",价格:" + this.price;
}
// setter、getter、无参构造略
}
public class TestDemo {
public static void main(String args[]) {
Book b = new Book("Java开发", 79.9); // 实例化对象
System.out.println(b); // 直接输出对象,默认调用toString()
}
}
// 程序执行结果: 书名:Java开发,价格:79.9
equals():对象比较
在Object类中,默认的 equals() 方法比较的是两个对象的内存地址是否相同(类似于 “ == ” ),但这不符合对象比较的需求,应该比较对象中的内容才对。
class Book {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public boolean equals(Object obj) { // 覆写equals()方法
if (this == obj) { // 地址相同
return true;
}
if (obj == null) { // 对象内容为null
return false;
}
if (!(obj instanceof Book)) { // 不是本类实例
return false;
}
Book book = (Book) obj;
if (this.title.equals(book.title) && this.price == book.price) {
return true;
}
return false;
}
public String toString() { // 替代了getInfo(),并且toString()可以自动调用
return "书名:" + this.title + ",价格:" + this.price;
}
// setter、getter、无参构造略
}
public class TestDemo {
public static void main(String args[]) {
Book b1 = new Book("Java开发", 79.9); // 实例化对象
Book b2 = new Book("Java开发", 79.9); // 实例化对象
System.out.println(b1.equals(b2)); // 对象比较
}
}
// 程序执行结果: true
Object类与引用数据类型
Object类不但可以接收所有类的对象,但其实还可以接收所有引用数据类型的数据,包括数组、接口、类。
- 接收数组对象:
public class TestDemo {
public static void main(String args[]) {
Object obj = new int[] { 1, 2, 3 }; // 向上转型
System.out.println(obj); // 数组编码:[I@1db9742
if (obj instanceof int[]) { // 谁否是int数组
int data[] = (int[]) obj; // 向下转型
for (int x = 0; x < data.length; x++) {
System.out.print(data[x] + "、");
}
}
}
}
/*
程序输出结果:
[I@1db9742
1、2、3、
*/
数组对象的编码都是以 “ [ ” 打头的,第二位:int型为 “ I ” ,double型为 “ D ” .
- 接收接口对象
因为接口不会继承任何类,自然也不会继承Object类,之所以可以用Object接收接口对象,是因为接口也属于引用数据类型。
interface A {
public void fun();
}
class B extends Object implements A { // 所有类一定继承Object类,所以此处只是强调说明
public void fun() {
System.out.println("更多课程请访问:www.yootk.com");
}
public String toString() {
return "魔乐科技:www.mldn.cn" ;
}
}
public class TestDemo {
public static void main(String args[]) {
A a = new B(); // 实例化接口对象
Object obj = a; // 接收接口对象
A t = (A) obj; // 向下转型
t.fun(); // 调用接口方法
System.out.println(t); // 直接调用toString()输出
}
}
完善链表
在之前讲解链表程序的开发过程中,一直存在这样的一个设计问题:链表不能实现操作数据的统一,所以就造成了每一次使用链表时都需要进行重复开发。但是由于 Object类型可以接收所有引用数据类型,利用这样的特性就可以弥补之前链表设计中的参数不统一问题,也就可以开发出真正的可重用链表操作。但是在链表中需要依靠对象比较的操作支持(在链表中的 contains)与remove()两个方法),所以就要求在操作类中覆写equals()方法。
class Link { // 链表类,外部能够看见的只有这一个类
private class Node { // 定义的内部节点类
private Object data; // 要保存的数据
private Node next; // 下一个节点引用
public Node(Object data) { // 每一个Node类对象都必须保存相应的数据
this.data = data;
}
/**
* 设置新节点的保存,所有的新节点保存在最后一个节点之后
* @param newNode 新节点对象
*/
public void addNode(Node newNode) {
if (this.next == null) { // 当前的下一个节点为null
this.next = newNode ; // 保存节点
} else { // 向后继续保存
this.next.addNode(newNode) ;
}
}
/**
* 数据检索操作,判断指定数据是否存在
* 第一次调用(Link):this = Link.root
* 第二次调用(Node):this = Link.root.next
* @param data 要查询的数据
* @return 如果数据存在返回true,否则返回false
*/
public boolean containsNode(Object data) {
if (data.equals(this.data)) { // 当前节点数据为要查询的数据
return true; // 后面不再查询了
} else { // 当前节点数据不满足查询要求
if (this.next != null) { // 有后续节点
return this.next.containsNode(data); // 递归调用继续查询
} else { // 没有后续节点
return false; // 没有查询到,返回false
}
}
}
/**
* 根据索引取出数据,此时该索引一定是存在的
* @param index 要取得数据的索引编号
* @return 返回指定索引节点包含的数据
*/
public Object getNode(int index) {
// 使用当前的foot内容与要查询的索引进行比较,随后将foot的内容自增,目的是下次查询方便
if (Link.this.foot++ == index) { // 当前为要查询的索引
return this.data; // 返回当前节点数据
} else { // 继续向后查询
return this.next.getNode(index); // 进行下一个节点的判断
}
}
/**
* 修改指定索引节点包含的数据
* @param index 要修改的索引编号
* @param data 新数据
*/
public void setNode(int index, Object data) {
// 使用当前的foot内容与要查询的索引进行比较,随后将foot的内容自增,目的是下次查询方便
if (Link.this.foot++ == index) { // 当前为要修改的索引
this.data = data; // 进行内容的修改
} else {
this.next.setNode(index, data); // 继续下一个节点的索引判断
}
}
/**
* 节点的删除操作,匹配每一个节点的数据,如果当前节点数据符合删除数据
* 则使用“当前节点上一节点.next = 当前节点.next”方式空出当前节点
* 第一次调用(Link),previous = Link.root、this = Link.root.next
* 第二次调用(Node),previous = Link.root.next、this = Link.root.next.next
* @param previous 当前节点的上一个节点
* @param data 要删除的数据
*/
public void removeNode(Node previous, Object data) {
if (data.equals(this.data)) { // 当前节点为要删除节点
previous.next = this.next; // 空出当前节点
} else { // 应该向后继续查询
this.next.removeNode(this, data); // 继续下一个判断
}
}
/**
* 将节点中保存的内容转化为对象数组
* 第一次调用(Link):this = Link.root;
* 第二次调用(Node):this = Link.root.next;
*/
public void toArrayNode() {
Link.this.retArray[Link.this.foot++] = this.data; // 取出数据并保存在数组中
if (this.next != null) { // 有后续元素
this.next.toArrayNode(); // 继续下一个数据的取得
}
}
}
// ===================== 以上为内部类 ===================
private Node root; // 根节点定义
private int count = 0 ; // 保存元素的个数
private int foot = 0 ; // 节点索引
private Object [] retArray ; // 返回的数组
/**
* 用户向链表增加新的数据,在增加时要将数据封装为Node类,这样才可以匹配节点顺序
* @param data 要保存的数据
*/
public void add(Object data) { // 假设不允许有null
if (data == null) { // 判断数据是否为空
return; // 结束方法调用
}
Node newNode = new Node(data); // 要保存的数据
if (this.root == null) { // 当前没有根节点
this.root = newNode; // 保存根节点
} else { // 根节点存在
this.root.addNode(newNode); // 交给Node类处理节点的保存
}
this.count ++ ; // 数据保存成功后保存个数加一
}
/**
* 取得链表中保存的数据个数
* @return 保存的个数,通过count属性取得
*/
public int size() { // 取得保存的数据量
return this.count;
}
/**
* 判断是否是空链表,表示长度为0,不是null
* @return 如果链表中没有保存任何数据则返回true,否则返回false
*/
public boolean isEmpty() {
return this.count == 0;
}
/**
* 数据查询操作,判断指定数据是否存在,如果链表没有数据直接返回false
* @param data 要判断的数据
* @return 数据存在返回true,否则返回false
*/
public boolean contains(Object data) {
if (data == null || this.root == null) { // 现在没有要查询的数据,根节点也不保存数据
return false ; // 没有查询结果
}
return this.root.containsNode(data) ; // 交由Node类查询
}
/**
* 根据索引取得保存的节点数据
* @param index 索引数据
* @return 如果要取得的索引内容不存在或者大于保存个数,返回null,反之返回数据
*/
public Object get(int index) {
if (index > this.count) { // 超过了查询范围
return null ; // 没有数据
}
this.foot = 0 ; // 表示从前向后查询
return this.root.getNode(index) ; // 查询过程交给Node类
}
/**
* 根据索引修改数据
* @param index 要修改数据的索引编号
* @param data 新的数据内容
*/
public void set(int index, Object data) {
if (index > this.count) { // 判断是否超过了保存范围
return; // 结束方法调用
}
this.foot = 0; // 重新设置foot属性的内容,作为索引出现
this.root.setNode(index, data); // 交给Node类设置数据内容
}
/**
* 链表数据的删除操作,在删除前要先使用contains()判断链表中是否存在指定数据
* 如果要删除的数据存在,则首先判断根节点的数据是否为要删除数据
* 如果是,则将根节点的下一个节点作为新的根节点
* 如果要删除的数据不是根节点数据,则将删除操作交由Node类的removeNode()方法完成
* @param data 要删除的数据
*/
public void remove(Object data) {
if (this.contains(data)) { // 主要功能是判断数据是否存在
// 要删除数据是否是根节点数据,root是Node类的对象,此处直接访问内部类的私有操作
if (data.equals(this.root.data)) { // 根节点数据为要删除数据
this.root = this.root.next; // 空出当前根节点
} else { // 根节点数据不是要删除数据
// 此时根元素已经判断过了,从第二个元素开始判断,即第二个元素的上一个元素为根节点
this.root.next.removeNode(this.root, data);
}
this.count--; // 删除成功后个数要减少
}
}
/**
* 将链表中的数据转换为对象数组输出
* @return 如果链表没有数据,返回null,如果有数据,则将数据变为对象数组后返回
*/
public Object[] toArray() {
if (this.root == null) { // 判断链表是否有数据
return null; // 没有数据,返回null
}
this.foot = 0; // 脚标清零操作
this.retArray = new Object[this.count]; // 根据保存内容开辟数组
this.root.toArrayNode(); // 交给Node类处理
return this.retArray; // 返回数组对象
}
/**
* 清空链表数据
*/
public void clear() {
this.root = null; // 清空链表
this.count = 0; // 元素个数为0
}
}
// 创建Book类
class Book{
private final String name;
private final int price;
public Book(String name, int price){
this.name = name;
this.price = price;
}
public boolean equals(Object obj){
if (this == obj){
return true;
}
if (obj == null){
return false;
}
if (!(obj instanceof Book)){
return false;
}
Book book = (Book)obj;
if (this.name.equals(book.name) && this.price == book.price){
return true;
}
return false;
}
public String toString(){
return "书名:" + this.name + ",价格:" + this.price;
}
}
// 测试
public class LinkDemo {
public static void main(String[] args) {
Link all = new Link();
all.add(new Book("C++入门", 30));
all.add(new Book("java入门", 40));
all.add(new Book("python入门", 50));
all.add(new Book("matlab入门", 35));
all.set(2, new Book("go入门", 45));
Book book = new Book("C++入门", 30);
System.out.println(all.contains(book));
all.remove(book);
Object[] arr = all.toArray();
for(int i =0; i < arr.length; i++){
System.out.println(arr[i]);
}
System.out.println(all.get(1));
}
}
3.9 综合练习:宠物商店
为了更好地理解接口的技术概念,下面一起来看一道思考题:“实现一个宠物商店的模型,一个宠物商店可以保存多个宠物的信息(主要属性为:名字、年龄),可以实现宠物的上架、下架、模糊查询的功能”。
设计分析:本程序中最为重要的就是宠物标准的定义,因为宠物商店需要这个标准,同样,所有可以进入宠物商店销售的宠物也都需要实现这个标准,那么根据这个原则就可以给出图4-13所示的类设计结构图。
- 首先给出宠物的标准:
~~~java
interface Pet { // 定义一个宠物的标准
/**
* 取得宠物的名字
* @return 宠物名字信息
/
public String getName();
/*
* 取得宠物的年龄
* @return 宠物年龄信息
*/
public int getAge();
}
~~~
- 定义宠物商店:
class PetShop { // 一个宠物商店要保存有多个宠物信息
private Link pets = new Link(); // 保存的宠物信息
/**
* 新的宠物类型上架操作,向链表中保存宠物信息
* @param pet 要上架的宠物信息
*/
public void add(Pet pet) {
this.pets.add(pet); // 向链表中保存数据
}
/**
* 宠物下架操作,通过链表删除保存的信息,需要对象比较equals()方法的支持
* @param pet 要删除的宠物信息
*/
public void delete(Pet pet) {
this.pets.remove(pet); // 从链表中删除宠物信息
}
// 模糊查询一定是返回多个内容,不知道多少个,返回Link即可
/**
* 宠物数据的模糊查询,首先要取得全部保存的宠物信息
* 然后采用循环的方式依次取出每一种宠物信息,并且对名称进行判断
* @param keyWord 模糊查询关键字
* @return 宠物信息通过Link类型返回,如果有指定查询关键字的宠物信息则通过Link集合返回,否则返回null
*/
public Link search(String keyWord) {
Link result = new Link(); // 保存结果
// 将集合变为对象数组的形式返回,因为集合保存的是Object
// 但是真正要查询的数据在Pet接口对象的getName()方法的返回值
Object obj[] = this.pets.toArray();
for (int x = 0; x < obj.length; x++) {
Pet p = (Pet) obj[x]; // 向下转型找到具体的宠物对象
if (p.getName().contains(keyWord)) { // 查询到了
result.add(p); // 保存满足条件的结果
}
}
return result;
}
}
本程序主要功能就是利用链表操作宠物信息,在增加、删除宠物信息时接收的参数都是Pet接口类型,这样只要是此接口子类对象都可以进行链表操作。
- 定义宠物子类:由于链表中需要用到比较方法equals(),所以每个宠物子类都应该覆写equals()方法
- 定义宠物猫:
class Cat implements Pet { // 如果不实现接口无法保存宠物信息
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) { // 覆写Object类中的方法
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Cat)) {
return false;
}
Cat c = (Cat) obj;
if (this.name.equals(c.name) && this.age == c.age) {
return true;
}
return false;
}
public String getName() { // 覆写接口中的方法
return this.name;
}
public int getAge() { // 覆写接口中的方法
return this.age;
}
public String toString() { // 覆写Object类中的方法
return "猫的名字:" + this.name + ",年龄:" + this.age;
}
// 其余的setter、getter、无参构造略
}
- 定义宠物狗:
class Dog implements Pet { // 如果不实现接口无法保存宠物信息
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) { // 覆写Object类中的方法
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Cat)) {
return false;
}
Dog c = (Dog) obj;
if (this.name.equals(c.name) && this.age == c.age) {
return true;
}
return false;
}
public String getName() { // 覆写接口中的方法
return this.name;
}
public int getAge() { // 覆写接口中的方法
return this.age;
}
public String toString() { // 覆写Object类中的方法
return "狗的名字:" + this.name + ",年龄:" + this.age;
}
// 其余的setter、getter、无参构造略
}
- 进行代码测试
public class TestDemo {
public static void main(String args[]) {
PetShop shop = new PetShop() ; // 实例化宠物商店
shop.add(new Cat("波斯猫",1)) ; // 增加宠物
shop.add(new Cat("暹罗猫",2)) ; // 增加宠物
shop.add(new Cat("波米拉猫",1)) ; // 增加宠物
shop.add(new Dog("松狮",1)) ; // 增加宠物
shop.add(new Dog("波尔多",2)) ; // 增加宠物
shop.delete(new Cat("波米拉猫",9)) ; // 删除宠物信息
Link all = shop.search("波") ; // 关键字检索
Object obj [] = all.toArray() ; // 将结果转换为对象数组输出
for (int x = 0 ; x < obj.length ; x ++) {
System.out.println(obj[x]) ;
}
}
}
4.10 匿名内部类
匿名内部类是没有名字的内部类,其必须在抽象类或接口基础上才可以定义。
- 分析匿名内部类产生的动机
interface Message { // 定义接口
public void print();
}
class MessageImpl implements Message { // 定义实现子类
public void print() {
System.out.println("Hello World !");
}
}
public class TestDemo {
public static void main(String args[]) {
fun(new MessageImpl()); // 传递子类实例化对象
}
public static void fun(Message msg) { // 接收接口对象
msg.print();
}
}
如果 MessageImpl 类只需要用一次,那么还有没有必要为他定义一个具体的类?这时就可采用匿名内部类的方式来进行简化。
- 采用匿名内部类进行简化
interface Message { // 定义接口
public void print();
}
public class TestDemo {
public static void main(String args[]) {
fun(new Message() { // 直接实例化接口对象
public void print() { // 匿名内部类中覆写print()方法
System.out.println("Hello World !");
}
}); // 传递匿名内部类实例化
}
public static void fun(Message msg) { // 接收接口对象
msg.print();
}
}
这种方法虽然节约了子类,但是也带来了代码结构的混乱
4.11 基本数据类型的包装类
Java在设计中有一个基本原则,即一切皆对象,也就是说一切的操作都要求用对象的形式进行描述。但这就会出现一个矛盾:“基本数据类型不是对象”。为了解决这个问题,可以采用基本数据类型包装的形式描述。
- 包装类雏形:
class MyInt { // 基本数据类型包装类
private int num; // 这个类包装的是基本数据类型
public MyInt(int num) { // 将基本类型包装类
this.num = num;
}
public int intValue() { // 将包装的数据内容返回
return this.num;
}
}
public class TestDemo {
public static void main(String args[]) {
MyInt mi = new MyInt(10); // 将int包装为类
int temp = mi.intValue(); // 将对象中包装的数据取出
System.out.println(temp * 2); // 只有取出包装数据后才可以进行计算
}
}
本程序实现了int基本数据类型的包装,这样就可以把int基本数据类型先转换为MyInt类对象,在进行需要对象程序的操作。
- 上面程序虽然实现了一个包装类,但每次都需要用户自己来包装8种基本数据类型,过于麻烦。
- 所有从 JDK 1.0 开始,为方便使用,Java专门给出了一组包装类,包含了这8种基本数据类型:
byte ( Byte )、short ( Short )、int ( Integer )、long ( Long )、float ( Float )、double ( Double )、char( Character)和 boolean ( Boolean )。 - 以上给出的包装类又分为两个子类型:
- 对象型包装类(Object直接子类):Character、Boolean
- 数值型包装类(Number直接子类):Byte、Short、Integer、 Long、Float、 Double
- Number类是一个抽象类,里面定义了6个操作方法:intValue()、doubleValue()、floatValue()、 byteValue()、shortValue()、longValue()
即上面6种包装类都继承了Number类,并覆写了上面6种方法。
注意:Character、Boolean包装类也有对应的charValue()、booleanValue()
装箱与拆箱
- 基本数据类型与其对应的包装类之间的转换有以下两种:
- 装箱操作:将基本数据类型转换为包装类形式,使用包装类的构造函数
- 拆箱操作:从包装类中取出包装的数据,使用从Number类中继承来的XXXValue()方法完成
- 装箱与拆箱举例,int和Integer:
public class TestDemo {
public static void main(String args[]) {
Integer obj = new Integer(10); // 将基本数据类型装箱
int temp = obj.intValue(); // 将基本数据类型拆箱
System.out.println(temp * 2); // 数学计算
}
}
- 自动装箱与拆箱,JDK1.5开始,就提供了自动装箱和拆箱机制
public class TestDemo {
public static void main(String args[]) {
Integer obj = 10; // 自动装箱
int temp = obj; // 自动拆箱
obj++; // 包装类直接进行数学计算
System.out.println(temp * obj); // 包装类直接进行数学计算
}
}
- 包装类的相等判断问题:
与String类似,包装类虽然可以直接装箱实例化对象,但与构造函数实例化的对象有区别。比如在讲String的时候,讲到直接利用String str = “xxx” 实例化String对象时,该String对象会保存在对象池中。所有同理,当数值包装类直接装箱实例化对象时,其对象也会保存在对象池中。
public class TestDemo {
public static void main(String args[]) {
Integer obja = 10; // 直接装箱实例化
Integer objb = 10; // 直接装箱实例化
Integer objc = new Integer(10); // 构造方法实例化
System.out.println(obja == objb); // 比较结果:true
System.out.println(obja == objc); // 比较结果:false
System.out.println(objb == objc); // 比较结果:false
System.out.println(obja.equals(objc)); // 比较结果:true
}
}
由上面的代码可以得到:在进行包装类数据相等判断时,最可靠的方法依然是equals()
- 可以利用Object类来接收全部的数据类型
之前讲Object类时讲到Object类可以接收所有引用数据类型数据,现在因为有了自动装箱操作后,使得Object类也可以接收基本数据类型数据了。
其 具体流程是:基本数据类型—>自动装箱(成为对象)—>向上转型为Object对象
public class TestDemo {
public static void main(String args[]) {
Object obj = 10; // 先自动装箱后再向上转型,此时不能进行数学计算
// Object不可能直接向下转型为int,所以要先向下转型为指定的包装类,在自动拆箱为int型
int temp = (Integer) obj; // 向下变为Integer后自动拆箱
System.out.println(temp * 2);
}
}
- 什么时候使用包装类?什么时候使用基本数据类型?
包装类默认值为null,这在与数据库的操作上会显得特别方便,而基本数据类型的默认值是有具体值的。
数据类型转换
- String型转基本数据类型
使用包装类最多的情况实际上是它的数据转换功能,在8个基本数据类型的包装类中,除了Character类(因为在String类中提供了一个charAt()方法将String型转换为char型),其他7个包装类都定义了parseXxx()方法,该方法可以将String数据类型转换为指定的基本数据类型。
public class TestDemo {
public static void main(String args[]) {
String str = "123"; // 字符串,由数字组成
int temp = Integer.parseInt(str); // 将字符串转化为int型数据
System.out.println(temp * 2); // 数学计算
}
}
注意:如上代码,如果String字符串中出现非数字字符,则转换会发生异常。但也有例外,比如在将String型转换为boolean型时,只有"true"会转换为true,其他所有字符都会转换为false,也就是说将String型转化为boolean型永远不会出现异常。
- 基本数据类型转String型:
- 方法一:使用 “+” 操作
public class TestDemo {
public static void main(String args[]) {
int num = 100;
String str = num + "";
System.out.println(str.replace("0", "9"));
}
}
可以转换任何数据,但这样做会产生垃圾空间,不建议使用。
- 方法二:利用String类在提供的方法:public static String valueOf(数据类型变量)
public class TestDemo {
public static void main(String args[]) {
int num = 100;
String str = String.valueOf(num); // 变为String
System.out.println(str.replace("0", "9"));
}
}
本章小结
- 继承可以扩充已有类的功能。通过 extends关键字实现,可将父类(超类)的成员(包含数据成员与方法)继承到子类(派生类),在Java中一个类只允许继承一个父类,存在有单继承局限。
- Java在执行实例化子类对象前(子类构造方法执行前),会先默认调用父类中无参的构造方法,其目的是对继承自父类的成员做初始化的操作。
- 父类有多个构造方法时,如果要调用特定的构造方法,则可在子类的构造方法中,通过super()这个关键字来完成,但是此语句必须放在子类构造方法的首行。
- this调用属性或方法时,会先从本类查找是否存在指定的属性或方法,如果没有,则会去查找父类中是否存在指定的属性或方法。而super是子类直接调用父类中的属性或方法,不会查找本类定义。
- this()与 super()的相似之处:当构造方法有重载时,两者均会根据所给予的参数的类型与个数,正确地执行相应的构造方法;二者均必须编写在构造方法内的第一行,也正是这个原因,this()与super()无法同时存在于同一个构造方法内。
- “覆写”( overriding ),它是在子类当中,定义名称、参数个数与类型均与父类相同的方法,但是覆写的方法不能拥有比父类更为严格的访问控制权限。覆写的意义在于:保存父类中的方法名称,但是不同的子类可以有不同的实现。
- 如果父类的方法不希望被子类覆写,可在父类的方法之前加上“final”关键字,这样该方法便不会被覆写。
- final 的另一个功能是把它加在数据成员变量前面,这样该变量就变成了一个常量,便无法在程序代码中再做修改了。使用public static final可以声明一个全局常量。
- 所有的类均继承自Object类。一个完整的简单Java类理论上应该覆写Object类中的toString()、equals()、hashCode()3个方法。所有的数据类型都可以使用Object类型接收。
- Java可以创建抽象类,专门用来当做父类。抽象类的作用类似于“模板”,其目的是依据其格式来修改并创建新的类,在定义抽象类时类中可以不定义抽象方法。
- 抽象类的方法可分为两种:一种是普通方法,另一种是以abstract关键字开头的“抽象方法”。其中,“抽象方法”并没有定义方法体,在子类(不是抽象类)继承抽象类时,必须要覆写全部抽象方法。
- 抽象类不能直接用来产生对象,必须通过对象的多态性进行实例化操作。
- 接口是方法和全局常量的集合的特殊结构类,使用interface 关键字进行定义,接口必须被子类实现(implements),一个接口可以同时继承多个接口,一个子类也可以同时实现多个接口。
- Java并不允许类的多重继承,但是允许实现多个接口,即使用接口来实现多继承的概念。
- 接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口;派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可以加入新的成员以满足实际的需要。
- Java对象的多态性分为:向上转型(自动)、向下转型(强制)。
- 通过instanceof关键字,可以判断对象属于哪个类。
- 匿名内部类的好处是可利用内部类创建不具有名称的对象,并利用它访问类里的成员。
- 基本数据类型的包装类可以让基本数据类型以对象的形式进行操作,从JDK 1.5开始支持自动装箱与拆箱操作,这样就可以使用Object接收基本数据类型。
(String args[]) {
int num = 100;
String str = String.valueOf(num); // 变为String
System.out.println(str.replace(“0”, “9”));
}
}
~~~
本章小结
- 继承可以扩充已有类的功能。通过 extends关键字实现,可将父类(超类)的成员(包含数据成员与方法)继承到子类(派生类),在Java中一个类只允许继承一个父类,存在有单继承局限。
- Java在执行实例化子类对象前(子类构造方法执行前),会先默认调用父类中无参的构造方法,其目的是对继承自父类的成员做初始化的操作。
- 父类有多个构造方法时,如果要调用特定的构造方法,则可在子类的构造方法中,通过super()这个关键字来完成,但是此语句必须放在子类构造方法的首行。
- this调用属性或方法时,会先从本类查找是否存在指定的属性或方法,如果没有,则会去查找父类中是否存在指定的属性或方法。而super是子类直接调用父类中的属性或方法,不会查找本类定义。
- this()与 super()的相似之处:当构造方法有重载时,两者均会根据所给予的参数的类型与个数,正确地执行相应的构造方法;二者均必须编写在构造方法内的第一行,也正是这个原因,this()与super()无法同时存在于同一个构造方法内。
- “覆写”( overriding ),它是在子类当中,定义名称、参数个数与类型均与父类相同的方法,但是覆写的方法不能拥有比父类更为严格的访问控制权限。覆写的意义在于:保存父类中的方法名称,但是不同的子类可以有不同的实现。
- 如果父类的方法不希望被子类覆写,可在父类的方法之前加上“final”关键字,这样该方法便不会被覆写。
- final 的另一个功能是把它加在数据成员变量前面,这样该变量就变成了一个常量,便无法在程序代码中再做修改了。使用public static final可以声明一个全局常量。
- 所有的类均继承自Object类。一个完整的简单Java类理论上应该覆写Object类中的toString()、equals()、hashCode()3个方法。所有的数据类型都可以使用Object类型接收。
- Java可以创建抽象类,专门用来当做父类。抽象类的作用类似于“模板”,其目的是依据其格式来修改并创建新的类,在定义抽象类时类中可以不定义抽象方法。
- 抽象类的方法可分为两种:一种是普通方法,另一种是以abstract关键字开头的“抽象方法”。其中,“抽象方法”并没有定义方法体,在子类(不是抽象类)继承抽象类时,必须要覆写全部抽象方法。
- 抽象类不能直接用来产生对象,必须通过对象的多态性进行实例化操作。
- 接口是方法和全局常量的集合的特殊结构类,使用interface 关键字进行定义,接口必须被子类实现(implements),一个接口可以同时继承多个接口,一个子类也可以同时实现多个接口。
- Java并不允许类的多重继承,但是允许实现多个接口,即使用接口来实现多继承的概念。
- 接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口;派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可以加入新的成员以满足实际的需要。
- Java对象的多态性分为:向上转型(自动)、向下转型(强制)。
- 通过instanceof关键字,可以判断对象属于哪个类。
- 匿名内部类的好处是可利用内部类创建不具有名称的对象,并利用它访问类里的成员。
- 基本数据类型的包装类可以让基本数据类型以对象的形式进行操作,从JDK 1.5开始支持自动装箱与拆箱操作,这样就可以使用Object接收基本数据类型。
- 在包装类中提供了将字符串转换为基本数据类型的操作方法,但是要注意字符串的组成是否正确。