重温Java基础(四)继承-覆写-多态性-抽象类-接口-Object类-包装类

继承

实现

继承性严格来讲就是指扩充一个类已有的功能,在Java中如果要实现继承的关系,可用以下语法完成。

class 子类 extends 父类{}

对于继承的格式有以下3点说明。

  • 对于 extends而言,应该翻译为扩充,但是为了理解方便,统一将其称为继承.
  • 子类又被称为派生类
  • 父类又被称为超类(Super Class )

class Person {
	private String name;
	private int age;
	public void setAge(int age) {
		this.age = age;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public String getName() {
		return name;
	}
}
class Student extends Person{
		private String school;
		public void setSchool(String school) {
			this.school = school;
		}
		public String getSchool() {
			return school;
		}
}
public class data{
	public static void main(String args[]) {
		Student stu = new Student();
		stu.setAge(200);
		stu.setName("aaa");
		stu.setSchool("ustc");
		System.out.println(stu.getAge()+stu.getName()+stu.getSchool());
	}
}

子类从外表上看是扩充了父类的功能,但是对于本程序的代码,子类还有一个特点,即子类实际上是将父类定义得更加具体化的一种手段。父类表示的范围大,而子类表示的范围小。

继承的限制

虽然继承可以进行类功能的扩充,但是其在定义的时候也会存在若干种操作的限制。

  • 限制一:Java不允许多重继承,但是允许多层继承。
    在C++语言中具备一种概念—多继承,即一个子类可以同时继承多个父类。

错误示范

class A{
    
    }
class B{
    
    }
class C extends A,B{
    
    } //一个子类继承了两个父类

正确-多层继承:

class A{
    
    }
class B extends A{
    
    } //B类继承A类
class C extends B{
    
    } //C类继承B类

限制二:子类在继承父类时,严格来讲会继承父类中的全部操作,但是对于所有的私有操作属于隐式继承,而所有的非私有操作属于显式继承。

利用子类对象设置的属性可以正常取得,此属性是通过父类继承而来的但是在子类里面不能针对父类属性进行直接访问,因为它在父类中属于私有声明,只能利用setter或 getter方法间接地进行私有属性的访问。

限制三:在子类对象构造前一定会默认调用父类的构造(默认使用无参构造),以保证父类的对象先实例化,子类对象后实例化。


class A{
    
    
	public A(){
    
    
		System.out.println("A类构造方法");
	}
}
class B extends A {
    
    
	public B(){
    
    
		System.out.println("B类构造");
	}
}
public class Demo {
    
    

	public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		new B();
	}

}

A类构造方法
B类构造

本程序虽然实例化的是子类对象,但是发现它会默认先执行父类构造,调用父类的构造方法体执行,再实例化子类对象并且调用子类的构造方法,而这时对于子类的构造而言,就相当于隐含了super()的语句调用,由于“super()”主要是调用父类的构造方法,所以必须放在子类构造方法的首行。

class B extends A{
    
    
    public B(){
    
    
        super();
        System.out.println("B类构造");
    }
}

从本程序中可以发现,当父类中提供有无参构造方法时,是否编写“ super()”没有区别。但是如果类中没有无参构造方法,则必须明确地使用 super()调用父类指定参数

父类不提供无参构造方法


class A{
    
    
	public A(String title){
    
    
		System.out.println("A类构造"+title);
	}
}
class B extends A {
    
    
	public B(String title){
    
    
		super(title);
		System.out.println("B类构造");
	}
}
public class Demo {
    
    

	public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		new B("Hello");
	}

}

本程序在父类中由于没有提供无参构造方法,所以在子类中就必须明确地使用 super()调用指定参数的构造方法,否则将出现语法错误。

覆写

在子类中定义了一个与父类完全一样的方法,所以当实例化子类对象,调用覆写方法时,将不再执行父类的方法,而是直接调用已经被子类覆写过的方法。

一个类可能会产生多个子类,每个子类都可能会覆写父类中的方法,这样一个方法就会根据不同的子类有不同的实现效果。
注意:关于覆写方法的执行问题。
对于方法的覆写操作,一定要记住一个原则:如果子类覆写了方法(如B类中的fun方法,或者可能有更多的子类也覆写了fun方法),并且实例化了子类对象(B b=new B())时,调用的一定是被覆写过的方法。

简单地讲,就是要注意:

  • 观察实例化的是哪个类;

  • 观察这个实例化的类里面调用的方法是否已经被覆写过,如果没被覆写过则调用这一原则直接与后续的对象多态性息息相关.

同时在覆写的过程中,还必须考虑到权限问题,即:被子类所覆写的方法不能拥有比父类更严格的访问控制权限。
对于访问控制权限由宽到严的顺序是:public>default(默认,什么都不写)> private,也就是说 private的访问权限是最严格的(只能被一个类访问)

即如果父类的方法使用的是 public声明,那么子类覆写此方法时只能是 public;如果父类的方法是default(默认),那么子类覆写方法时候只能使用 default或 public。

(可以简单理解为子类不能占为己有)

父类方法使用 private声明可以覆写吗?
如果父类中方法使用了 private声明,子类覆写时使用了 public声明,这时子类可以覆写父类方法吗?
不能覆写。
从概念上来讲,父类的方法是 private,属于小范围权限,而 public属于扩大范围的权限。权限上符合覆写要求。但从实际上讲, private是私有的,既然是私有的就无法被外部看见,故子类是不能覆写的。

常见面试题分析:请解释一下方法重载与覆写的区别。当方法重载时能否改变其返回值类型?
方法重载与覆写的区别如图所示。
在发生重载关系时,返回值可以不同,但是考虑到程序设计的统一性,重载时应尽量保证方法的返回值类型相同。

图1

this 与super 区别

图2

final关键字(终结器)

  1. 方法
  2. 属性

(1)使用 final定义的类不能再有子类,即:任何类都不能继承以 final声明的父类。

(2)使用 final定义的方法不能被子类所覆写。
在一些时候由于父类中的某些方法具备一些重要的特征,并且这些特征不希望被子类破坏(不能够覆写)。就可以在方法的声明处加上final,意思是子类不要去破坏这个方法的原本作用。
(3)使用 final定义的变量就成为了常量,常量必须在定义的时候设置好内容,并且不能修改。

注意:常量命名规范。
常量名称使用了全部字母大写的形式( final double GOOD=100.0;),这是Java中的命名规范要求,这样做的好处是可以明确地与变量名称进行区分,开发中必须遵守。

在定义常量中还有一个更为重要的概念一全局常量,所谓全局常量指的就是利用了“ public static”"final”3个关键字联合定义的常量,例如:
public static final String MSG=“WWW”:
static的数据保存在公共数据区,所以此处的常量就是一个公共常量。同时在定义常量时必须对其进行初始化赋值,否则将出现语法错误。

多态性

多态在开发中主要体现在方法与对象上。

  • 方法的多态性:重载与覆写

    重载:同一个方法名称,根据不同的参数类型及个数可以完成不同的功能。覆写:同一个方法,根据实例化的子类对象不同,所完成的功能也不同。

  • 对象的多态性:父子类对象的转换。

    向上转型:子类对象变为父类对象,格式:父类 父类对象=子类实例,自动转换;

    向下转型:父类对象变为子类对象,格式:子类 子类对象=(子类)父类实例,强制转换;

向上转型

Tips:对象多态性有什么用,难道只是为了无聊的转型吗?
对于对象多态性其本质是根据实例化对象所在的类是否覆写了父类中的指定方法来决定最终执行的方法体,而这种向上或向下的对象转型有什么意义?
答:在实际开发中,对象向上转型的主要意义在于参数的统一,也是最为主要的用法,而对象的向下转型指的是调用子类的个性化操作方法。

class A{
    
    
	public void print(){
    
    
		System.out.println("A");
	}
}
class B extends A {
    
    
	public void print() {
    
    
		System.out.println("B");
	}
}
public class Demo {
    
    

	public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		fun(new B());
	}
	public static void fun(A a) {
    
    
		a.print();
	}
}

B

本程序的fun()方法上只是接收A类的实例化对象,即按照大类接受,按照对象的向上转型原则,此时的fun方法可以接收A类对象或所有A类的子类对象,这样只需要一个A这个大类的参数类型,此方法就可以处理一切A的子类对象(即便A类有几百万个子类,fun方法依然可以接收)而在fun方法中将统一调用print方法,如果此时传递的是子类对象,并且覆写过子类对象就表示执行被子类所覆写过的方法。

向上转型

如果说向上转型是统一调用的参数类型,那么向下转型就表示要执行子类的个性化操方法。实际上当发生继承关系后,父类对象可以使用的方法必须在父类中明确定义,比如此时在父类中存在一个 print方法,哪怕这时此方法被子类覆写过,父类对象依然可以调用。但是如果子类要扩充一个funB方法,这个方法父类对象并不知道,一旦发生向上转型,那么funB方法父类对象无法便用。

如果此时要想调用子类的方法,就必须将父类对象向下转型。

public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		fun(new B());
	}
	public static void fun(A a) {
    
    
		B b = (B)a;	//要调用子类的特殊操作,需要向下转型
        b.funB();	//调用子类的扩充方法
	}

本程序如果要调用fun方法,则子类B的实例化对象一定要发生向上转型操作,但是这个时候父类对象A无法调用子类B的funB方法,所以需要进行向下转型才能正常调用funB方法。但是如果每一个子类都去大量扩充自己的新功能,这样是不利于开发的。

通过以上分析

  • 向上转型:其目的是参数的统一,但是向上转型中,通过子类实例化后的父类对象所能调用的方法只能是父类中定义过的方法;

  • 向下转型:其目的是父类对象要调用实例化它的子类中的特殊方法,但是向下转型是需要强制转换的,这样的操作容易带来安全隐患对于对象的转型。

    80%的情况下都只会使用向上转型,因为可以得到参数类型的统一,方便于程序设计。

    子类定义的方法大部分情况下请以父类的方法名称为标准进行覆写,不要过多地扩充方法;

    5%的情况下会使用向下转型,目的是调用子类的特殊方法

    15%的情况下是不转型,例如: String

抽象类

抽象类是代码开发中的重要组成部分,利用抽象类可以明确地定义子类需要覆写的方法,这样相当于在语法程度上对子类进行了严格的定义限制,代码的开发也就更加标准。下面具体介绍抽象类的概念。

普通类可以直接产生实例化对象,并且在普通类中可以包含构造方法、普通方法、 static方法、常量、变量的内容。

而所谓抽象类就是指在普通类的结构里面增加抽象方法的组成部分,抽象方法指的是没有方法体的方法,同时抽象方法还必须使用 abstract关键字进行定义。拥有抽象方法的类一定属于抽象类,抽象类要使用abstract声明。

Tips:关于抽象方法与普通方法。
所有的普通方法上面都会有一个“{}”,来表示方法体,有方法体的方法一定可以被对象直接调用。抽象类中的抽象方法没有方法体,声明时不需要加“{}”,但是必须有abstract声明,否则在编译时将出现语法错误。

abstract  class A{
    
    
	public void print(){
    
    
		System.out.println("A");
	}
	public abstract void pp();
}

抽象类不能进行直接的对象实例化操作,不能够实例化的原因很简单一个类的对象实例化后,就意味着这个对象可以调用类中的属性或方法,但是在抽象类里面存在抽象方法,抽象方法没有方法体,没有方法体的方法怎么可能去调用呢?既然不能调用方法,那么又怎么去产生实例化对象呢?

  • 抽象类必须有子类,即每一个抽象类一定要被子类所继承(使用 extends关键字),但是在Java中每一个子类只能够继承一个抽象类,所以具备单继承局限。
  • 抽象类的子类(子类不是抽象类)必须覆写抽象类中的全部抽象方法(强制子类覆写);
  • 依靠对象的向上转型概念,可以通过抽象类的子类完成抽象类的实例化对象操作。
abstract  class A{
    
    
	public void print(){
    
    
		System.out.println("A");
	}
	public abstract void pp();
}
public B extends A{
    
    
    public void pp()
    {
    
    
        System.out.println("B");
    }
}
public class Demo {
    
    

	public static void main(String[] args) {
    
    
		A a = new B();  //向上转型
        a.pp();			//被子类覆写过的方法
	}
}

本程序为抽象类定义了一个子类B,而子类B(是一个普通类)必须要覆写抽象类中的全部抽象方法,而在主方法中依靠子类对象的向上转型实现了抽象类A对象的实例化操作,而调用的 pp方法由于被子类所覆写,所以最终调用的是在子类B中覆写过的 pp方法。

Tips:开发中继承一个普通类还是抽象类?

虽然一个子类可以去继承任意一个普通类,但是从开发的实际要求来讲,普通类不要去继承另外一个普通类,而要继承抽象类。
相比较开发的约定,开发者更愿意相信语法程度上给子的限定。

很明显,强制子类去覆写父类的方法可以更好地进行操作的统一,所以对于抽象类与普通类的对比,如下3点总结

  • 抽象类继承子类里面会有明确的方法覆写要求,而普通类没有;
  • 抽象类只比普通类多了一些抽象方法的定义,其他的组成部分与普通类完全一样;
  • 普通类对象可以直接实例化,但是抽象类的对象必须经过向上转型后才可以得到实例化对象。

抽象类相关局限

抽象类的组成和普通类组成的最大区别只是在抽象方法的定义上,但是由于抽象类和普通类使用以及定义的区别,如下概念可能会被忽略。

(1)抽象类里面由于会存在一些属性,那么在抽象类中一定会存在构造方法,目的是为属性初始化,并且子类对象实例化时依然满足先执行父类构造再调用子类构造的情况;
(2)抽象类不能使用 final定义,因为抽象类必须有子类,而final定义的类不能有子类;
(3)抽象类中可以没有任何抽象方法,但是只要是抽象类,就不能直接使用关键字new实例化对象;
(4)在抽象类中,如果定义了 static属性或方法时,就可以在没有对象的时候直接调用。

abstract  class A{
    
    
	public static void print(){
    
    
		System.out.println("A");
	}
	public abstract void pp();
}

public class Demo {
    
    
	public static void main(String[] args) {
    
    
		A.print();
	}
	
}

本程序在抽象类A中定义了一个 static方法,由于 static方法不受实例化对象的限制,所以可以直接由类名称调用。

抽象类应用-模板设计模式

抽象类的最主要特点相当于制约了子类必须覆写的方法,同时抽象类中也可以定义普通方法,而且最为关键的是,这些普通方法定义在抽象类时,可以直接调用类中定义的抽象方法,但是具体的抽象方法内容就必须由子类来提供。

如在抽象类普通方法中调用抽象方法

abstract  class A{
    
    
	public void fun(){
    
    
		this.print();
	}
	public abstract void print();
}
class X extends A {
    
    
	public void print(){
    
    
		System.out.println("xxx");
	}
}
public class Demo {
    
    

	public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		A a = new X();
		a.fun();
	}
	
}

本程序在抽象类中的抽象方法 print()被fun()方法直接调用,在定义抽象类A的时候并不知道具体的子类是什么,但是只要是有子类,就必须明确地强制子类来覆写 print0方法,当调用fun()方法时,一定是被子类所覆写的抽象方法。

接口

利用抽象类可以实现对子类覆写方法的控制,但是抽象类的子类存在一个很大的问题—单继承局限,所以为了打破这个局限,就需要用Java接口来解决。

接口包含抽象方法和全局常量(JDK1.8之前)

Java中使用interface

定义接口

interface A{
	public static final String MSG = "SSS";
	public static void print();
}

本程序定义了一个A接口,在此接口中定义了一个抽象方法( print())和一个全局常量(MSG)但是由于接口中存在抽象方法,所以接口对象不可能直接使用关键字new进行实例化的操作。因此,接口具有以下使用原则。

  • 接口必须要有子类,但是此时一个子类可以使用 implements关键字实现多个接口,避免单继承局限;
  • 接口的子类(如果不是抽象类),必须要覆写接口中的全部抽象方法;
  • 接口的对象可以利用子类对象的向上转型进行实例化操作。

接口的简化定义,对接口而言,其组成部分就是抽象方法和全局变量,所以很多时候进行简写,

不写 abstract或 public static final,并且在方法上是否编写 public结果都是一样的,因为在接口里面只能够使用一种访问权限— public。以下两个接口的定义最终效果就是完全相同的。

interface A{
    
    
    public static final String MSG = "hhh";
    public abstract void fun();
}
interface A{
    
    
    String MSG = "hhh";
    void fun();
}

即便在接口的方法中没有写 public,其最终的访问权限也是 public,绝对不会是default(默认)权限。所以为了准确定义,强烈建议在接口定义方法时一定要写上public,如下代码所示。

interface A{
    
    
    String MSG = "hhh";
    public void fun();
}

在实际的开发中,只要是定义接口,大部分情况下都是以定义抽象方法为主,很少有接口只是单纯地去定义全局常量。

如果一个子类既要继承抽象类又要实现接口,那么应该采用先继承( extends)后实现接口
( implements)的顺序完成

class X extents C implements A,B{
    
     //X类继承了抽象类C,实现了A和B两个接口
    
}

接口不能继承抽象类,但可以继承多个父接口

interface C extends A,B{
    
    		//利用extends实现接口多继承
    
}

提示:抽象类的限制要比接口多。
从继承关系上讲抽象类的限制要比接口多。

  • 抽象类只能继承一个抽象的父类,而接口没有这个限制,一个接口可以继承多个父接口;

  • 一个子类只能继承一个抽象类,却可以实现多个接口。

    所以,在整个Java中,接口主要用于解决单继承局限的问题。


interface USB{
    
    
	public void start();
	public void stop();
}
class Computer{
    
    
	public void plugin(USB usb) {
    
    
		usb.start();
		usb.stop();
	}
}
//定义U盘子类
class Flash implements USB{
    
    
	public void start() {
    
    
		System.out.println("U盘开始使用");
	}
	public void stop() {
    
    
		System.out.println("U盘停止使用");
	}
}
class Print implements USB{
    
    
	public void start() {
    
    
		System.out.println("打印机开始");
	}
	public void stop() {
    
    
		System.out.println("打印机停止");
	}
}
public class Demo{
    
    
	public static void main(String arg[]) {
    
    
		Computer computer = new Computer();
		computer.plugin(new Flash());
		computer.plugin(new Print());
	}
}

U盘开始使用
U盘停止使用
打印机开始
打印机停止

本程序首先实例化了一个 Computer计算机类的对象,然后就可以在计算机上插入(引用传递USB设备(USB接口子类)

工厂类

工厂类不提供属性,专门做new的工作,通过类实例化对象的标记来取得指定类型的接口对象。

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("吃橘子");
	}
}
class Factory{
    
     //定义工厂类,此类不提供属性
	/**
	 * 通过类实例化对象标记来取得指定类型的接口对象
	 */
	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 arg[]) {
    
    
		Fruit fruit = Factory.getInstance("apple");
		fruit.eat();
	}
	
}

图4

抽象类与接口的区别

抽象类和接口都会强制性地规定子类必须要覆写的方法,这样在使用形式上是很相似的,那么在实际开发中是使用抽象类还是使用接口呢?为了更加清楚两个概念的对比,下面给出表所示的抽象类与接口的比较。

No. 区别 抽象类 接口
1 关键字 abstract class interface
2 组成 构造方法、普通方法、抽象方法、 static方法、常量、变量 抽象方法、全局常量
3 子类使用 class子类 extends抽象类 class子类 implements接口,接口,…
4 关系 抽象类可以实现多个接口 接口不能继承抽象类,却可以继承多个父接口
5 权限 可以使用各种权限 只能使用 public权限
6 限制 单继承局限 没有单继承局限
7 子类 抽象类和接口都必须有子类,子类必须要覆写全部的抽象方法
8 实例化对象 依靠子类对象的向上转型进行对象的实例化

经过比较可以发现,抽象类中支持的功能绝对要比接口多,但是其有一点不好,那就是单继承局限,所以这重要的一点就掩盖了所有抽象类的优点,即当抽象类和接口都可以使用时,优先考虑接口。

提示:关于实际开发中接口使用的几个建议。
对于实际的项目开发

在进行某些公共操作时一定要定义出接口;

有了接口就需要利用子类完善方法;

如果是自己写的接口,那么绝对不要使用关键字new直接实例化接口子类,应该使用工厂类完成。

接口实在类之上的标准:
如果现在要定义一个动物,那么动物肯定是一个公共标准,而这个公共标准就可以通过接口来完成。
在动物中又分为两类:哺乳动物、卵生动物,而这个标准属于对动物标准进一步细化,应该称为子标准,所以此种关系可以使用接口的继承来表示而哺乳动物又可以继续划分为:人、狗、猫等不同的类型,这些由于不表示具体的事务标准,所以可以使用抽象类进行表示。
现在如果要表示出个工人或者是学生这样的概念,肯定是一个具体的定义,则需要使用类的方式。
由于每一个学生或每一个工人都是具体的,因此就通过对象来表示。所以以上几种关系可以通过图来表示。

图5

常见面试题分析:请解释抽象类与接口的区别

对于本问题,可以直接回答表所列出的概念对比。但是在进行这两个概念对此时一定要明确地给出“抽象类存在单继承局限”的总结。随着技术开发实力的增加,实际上也可以回答出更多关于这两种结构的使用特点。

Object类

Object类的基本定义:Object类是所有类的父类,也就是说任何一个类在定义时如果没有明确地继承个父类,那它就是 Object类的子类,也就是说以下两种类定义的最终效果是完全相同的。
class Book {}

class Book extends Object {}

既然 Object类是所有类的父类,那么最大的一个好处就在于:利用 Object类可以接收全部类的对象,因为可以向上自动转型。

class Book extends Object{
    
    
	
}
public class Test {
    
    
		public static void main(String args[]) {
    
    
			Object object1 = new Book();
			Object object2 = "hello";
			Book book = (Book)object1;//向下转型
			String string = (String)object2;
		}
}

本程序为了测试 Object可以接收任意类对象,使用Book类与 String类实现了向上与向下转型操作。所以在设计代码时,如果在不确定参数类型时,使用 Object类应该是最好的选择。
提示:Object类提供无参构造方法。
通过 Java doc文档可以发现,在 Object类中提供了一个无参构造方法"public Object()”,之所以提供这样的无参构造,是因为在子类对象实例化时都会默认调用父类中的无参构造方法,这样在定义类时即使没有明确定义父类为Object,读者也不会感觉到代码的强制性要求。

Object类的3个覆写方法

No. 方法 类型 描述
1 public String toString() 普通 取得对象信息
2 public boolean equal(Object obj) 普通 对象比较
3 public int hashCode() 普通 取得对象哈希码

取得对象信息:toString()

输出一个对象时不管是否调用 toString(),其最终都是调用tostring()将对象信息转换为 String进行输出,而在Object类中的 toString()方法设计时,由于要考虑其可以满足所有对象的输出信息,所以默认返回的是对象的编码。而之所以 String类对象输出的时候没有输出编码,是因为在 String类中已经明确地覆写了 tostring0方法,依照这个思路,也可以根据自己的情况来选择覆写 tostring()方法。

对象比较:equals()

实际上对于 equals()方法应该并不陌生,这个方法在 String类中使用过,而 String也是 Object类的子类,所以 String类的 equals()方法就是覆写Object类中的 equals()方法。

在Object类中,默认的equals()方法实现比较的是两个对象的内存地址数值,但是并不符合真正的对象比较需要(按照之前讲解对象比较操作而言,还需要比较对象内容)。而在之前使用了一个自定义名的compare()方法作为对象比较方法的名称,但是这个做法不标准,标准的做法是覆写equals()方法完成。

Tips:Object->数据类->链表的使用

包装类

基本数据类型包装类

Java在设计中有一个基本原则,即一切皆对象,也就是说一切操作都要求用对象的形式进行描述。但是就会出现一个矛盾:“基本数据类型不是对象”,为了解决这样的矛盾,可以采用基本数据类型包装的形式描述。

所以从JDK1.0开始,为了方便用户的开发,Java专门给出了一组包装类,来包装8种基本数据类型:byte(Byte)、 short( Short)、int( Integer)、long(Long)、foat( Float)、 double( Double)、 char( Character)和 boolean( Boolean)。
以上给出的包装类又可以分为以下两种子类型。

  • 对象型包装类:(Object直接子类):Character、 Boolean
  • 数值型包装类:(Number直接子类):Byte、Short、 Integer、Long、 Float、 Double

提示:关于 Number类的定义。
Number是一个抽象类,里面一共定义了6个操作方法:intValue()、 doubleValue()、
floatValue()、 byteValue()、 shortValue()、longValue()。

装箱与拆箱

现在已经存在基本数据类型与包装类两种类型,这两种类型间的转换可以通过以下方式定义。

  • 装箱操作:

    将基本数据类型变为包装类的形式;

    每个包装类的构造方法都可以接收各自数据类型的变量;

  • 拆箱操作:从包装类中取出被包装的数据。
    利用从 Number类中继承而来的一系列 XxxValue0方法完成

			Integer obj = new Integer(10);//将基本数据类型装箱
			int temp = obj.intValue();	//将基本数据类型拆箱
			System.out.println(temp*2);

20

自动装箱与自动拆箱

		Integer obj = 10;		//自动装箱
		int temp = obj;			//自动拆箱
		obj++;					//包装类直接进行数学计算
		System.out.println(temp*obj);//包装类直接进行数学计算

110

通过本程序读者可以发现,利用自动装箱操作可以将一个int型常量直接赋予 Integer类对象,需要时也可以利用自动拆箱操作将包装的数据取出,而且最关键的是可以直接利用包装类进行数学计算。

关于数值型包装类的相等判断问题

有了自动装箱这一概念,实际上又会引发一个与 String类似的古老问题, Integer类直接装箱实例化对象,与调用构造方法实例化对象的区别。很明显, Integer毕竟是一个类,所以如果使用自动装箱实例化对象,对象就会保存在对象池中,可以重复使用。

		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

true
false
false
true

通过本程序可以观察到,如果使用直接装箱实例化的方式,会使用同一块堆内存空间,而使用了构造方法实例化的包装类对象,会开辟新的堆内存空间。而在进行包装类数据相等比较时,最可靠的方法依然是 equals(),这一点在日后进行项目开发时一定要特别注意。

Tips:利用 Object类可以接收全部数据类型。
清楚了包装类的基本作用以及自动装箱的处理操作后,实际上也就意味着Object可以进行参数操作的统一了。所有的引用数据类型都可以利用 Object类来接收,而现在由于存在自动装箱机制,那么基本数据类型也同样可以使用 Object接收。利用 Object接收基本数据类型的流程是:基本数据类型→自动装箱(成为对象)→向上转型为Object。

			Object object = 10;	//	自动装箱后再向上转型,此时不能进行数学计算
			int temp = (Integer)object;//向下变为Integer后自动拆箱
			System.out.println(temp*2);

本程序利用 Object接收了int基本数据类型,但是 Object类对象并不具备直接的数学计算功能。如果要想将 Object类中的包装数据取出,必须将其强制转换为包装类后才可以利用自动拆箱的完成。

	Boolean flag = true;
	if(flag){
    
    
   System.out.println("Hello");
	}

本程序使用 Boolean型数据实现了自动装箱的操作,并且由于自动拆箱机制的存在,可以直接在if句中使用Boolean型的包装类进行条件判断。

数据类型转换

使用包装类最多的情况实际上是它的数据类型转换功能,在包装类里面提供了将 String型数据变为基本数据类型的方法。下面对使用 Integer、 Double、 Boolean三个常用类做以下说明。

Integer 类:public static int parseInt(String s);
Double 类:public static double parseDouble(String s);

Boolean 3e:public static boolean parseBoolean(String s)

实际上在给出的8种基本数据类型的包装类中,一共有7个类都定义了 parseXxx方法,可以实现将字符串变为指定的基本数据类型。但是在 Character类(单个字符)中并没有提供这样的方法,这是因为在 String类中提供了一个 charAt()方法,利用这个方法就可以将字符串变为char字符数据了。

			String string = "123";
			int temp = Integer.parseInt(string);
			double d = Double.parseDouble(string);
			float f = Float.parseFloat(string);
			System.out.println(temp*2);

246

本程序首先将字符串数据直接利用 Integer.parseInt()方法变为了int型数据,然后进行了乘法计算。

注意:数据转换时要注意数据组成格式如果要将一个字符串数据变为数字,就必须保证字符串中定义的字符都是数字(如果是小数会包含小数点“.",如果出现了非数字的字符,那么转换就会出现异常。

技术穿越:基本数据类型变为String型数据既然以上的操作实现了字符串变为基本数据类型的功能,那么也一定存在将基本数据类型变为字符串的操作,而这样的转换可以通过以下两种方式完成。
方式一:任何基本数据类型与字符串使用“+”操作后都表示变为字符串;

		int num = 100;
		String string = num +"";
		System.out.println(string.replaceAll("0", "9"));

199

本程序使用了“+”将一个int型数据与一个空字符串(不是null)进行连接,这样就会将num变量所包含的内容自动变为字符串后进行连接,结果就变为了字符串。实际上任何数据类型遇见“+”都会变为字符串进行连接处理(如果是引用数据类型会调用 toString()转换后进行连接)。但是这样的连接会造成垃圾空间的产生,所以不建议使用。

方式二:利用 String类中提供的方法:public static String valueOf(数据类型变量)

		int num = 100;
		String string = String.valueOf(num);
		System.out.println(string.replaceAll("0", "9"));

199

总结

  1. 继承可以扩充已有类的功能。通过 extends关键字实现,可将父类(超类)的成员(包含数据成员和方法)继承到子类(派生类),在Java中一个类只允许继承一个父类,存在有单继承局限。

  2. Java在执行实例化子类对象前(子类构造方法执行前),会先默认调用父类中无参构造方法,其目的是对继承自父类的成员做初始化的操作。

  3. 父类有多个构造方法时,如果要调用特定的构造方法,则可在子类的构造方法中,通过 super()这个关键字来完成,但是此语句必须放在子类构造方法的首行

  4. this调用属性或方法时,会先从本类查找是否存在指定的属性或方法,如果没有,则会去查找父类中是否存在指定的属性或方法。而 super是子类直接调用父类中的属性或方法,不会查找本类定义。

  5. this()与 super()的相似之处:当构造方法有重载时,两者均会根据所给予的参数的类型与个数,正确地执行相应的构造方法;二者均必须编写在构造方法内的第一行,也正是这个原因,this0与 super0无法同时存在于同一个构造方法内

  6. “覆写”(overriding),它是在子类当中,定义名称、参数个数与类型均与父类相同的方法,但是覆写的方法不能拥有比父类更为严格的访问控制权限。覆写的意义在于:保存父类中的方法名称,但是不同子类可以有不同的实现。

  7. 如果父类的方法不希望被子类覆写,可在父类的方法之前加上“final”关键字,这样该方法便不会被覆写。

  8. final的另一个功能是把它加在数据成员变量前面,这样该变量就变成了一个常量,便无法在程序代码中再做修改了。使用 public static final可以声明一个全局常量。

  9. 所有的类均继承自 Object类。一个完整的简单Java类理论上应该覆写 Object类中的toString()、 equals()、 hashCode()3个方法。所有的数据类型都可以使用 Object类型接收。

  10. Java可以创建抽象类,专门用来当做父类。抽象类的作用类似于“模板”,其目的是依据其格式来修改并创建新的类,在定义抽象类时类中可以不定义抽象方法。

  11. 抽象类的方法可分2种:一种是普通方法,另一种是以 abstract关键字开头的“抽象方法”。其中,“抽象方法”并没有定义方法体,在子类(不是抽象类)继承抽象类时,必须要覆写全部抽象方法。

  12. 抽象类不能直接用来产生对象,必须通过对象的多态性进行实例化操作

  13. 接口是方法和全局常量的集合的特殊结构类,使用 interface关键字进行定义,接口必须被子类实现( implements),一个接口可以同时继承多个接口,一个子类也可同时实现多个接口。

  14. Java并不充许类的多重继承,但是允许实现多个接口,即使用接口来实现多继承的概念。

  15. 接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口;派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可以加入新的成员以满足实际的需要。

  16. Java对象的多态性分为:向上转型(自动)向下转型(强制)

  17. 通过 instanceof关键字,可以判断对象属于哪个类。

  18. 匿名内部类的好处是可利用内部类创建不具有名称的对象,并利用它访问类里的成员。

  19. 基本数据类型的包装类可以让基本数据类型以对象的形式进行操作,从JDK1.5开始支持自动装箱与拆箱操作,这样就可以使用 Object接收基本数据类型。

  20. 在包装类中提供了将字存串转换为基本数据类型的操作方法,但是要注字符串的组成是否正确。(小数)

面试题

		Integer integer = new Integer(1);
		Integer integer2 = new Integer(1);
		System.out.println(integer == integer2);
// Integer内部定义了 Integercache结构, Integercache中定义了 Integer[],
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给 Integer赋值的范围在
//-128~127范围内时,可以直接使用数组中的元素,不用再去new		
		Integer m = 1;
		Integer n = 1;
		System.out.println(m == n);
		Integer x = 128;
		Integer y = 128;
		System.out.println(x ==y);;

false
true
false

猜你喜欢

转载自blog.csdn.net/kilotwo/article/details/103746721