【Java基础】Java面向对象高级知识:继承、覆写、final、多态、抽象、接口、Object类匿名内部类、包装类

本文主要是介绍【Java基础】Java面向对象高级知识:继承、覆写、final、多态、抽象、接口、Object类匿名内部类、包装类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《第一行代码: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 {						// 定义子类Bpublic 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 = HelloB、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); 	 // trueSystem.out.println(a instanceof B);  	 // trueSystem.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型的默认值0System.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接口实例,trueSystem.out.println(a instanceof B) ;	// 判断a是否是B接口实例,true}
}
/*
程序执行结果:A接口的抽象方法!B接口的抽象方法!YOOTKtruetrue
*/
  • 一个子类可以同时继承(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@1db9742Book@1db9742yootk
    */
    
  • 覆写自定义类的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) {					// 对象内容为nullreturn 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@1db9742if (obj instanceof int[]) { 					// 谁否是int数组int data[] = (int[]) obj; 					// 向下转型for (int x = 0; x < data.length; x++) {	System.out.print(data[x] + "、");}}}
    }
    /*
    程序输出结果:[I@1db97421、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) {		// 当前的下一个节点为nullthis.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) { 					// 假设不允许有nullif (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); 		// 比较结果:trueSystem.out.println(obja == objc); 		// 比较结果:falseSystem.out.println(objb == objc); 		// 比较结果:falseSystem.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);			// 变为StringSystem.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接收基本数据类型。
  • 在包装类中提供了将字符串转换为基本数据类型的操作方法,但是要注意字符串的组成是否正确。

这篇关于【Java基础】Java面向对象高级知识:继承、覆写、final、多态、抽象、接口、Object类匿名内部类、包装类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/223039

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始