java第七章总结(面对对象)

7.1类的封装

使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。(载体是类)

封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。

例7.1 创建Restaurant这个类,实现餐馆点菜的场景。
 

public class Restaurant {//创建主类
 
    public static void main(String[] args) {//主方法
        // TODO Auto-generated method stub
        String cookName="Tom Cruise";//厨师的名字叫Tom Cruise
        System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出结果
        System.out.println(cookName + "切葱花");//输出结果
        System.out.println(cookName + "洗蔬菜");//输出结果
        System.out.println(cookName + "开始烹饪" + "香辣肉丝");//输出结果
        System.out.println("**请问厨师叫什么名字?***");//输出结果
        System.out.println(cookName);//输出结果
        System.out.println("**请让厨师给我切一点葱花。***");//输出结果
        System.out.println(cookName + "切葱花");//输出结果
    }

}

 

封装的好处(优点):

1)通过隐藏对象的属性来保护对象内部的状态(隐藏信息、实现细节)。

2)提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展(将变化隔离,类内部的结构可以自由修改,增加内部实现部分的可替换性)。

3)禁止对象之间的不良交互提高模块化(良好的封装能够减少耦合)。

4)可以对成员变量进行更精确的控制。

5)容易保证类内部数据间的一致性,从而提高软件的可靠性。
例7.2 将厨师封装成Cook类,实现餐馆点菜的场景
 

public class Restaurant{//创建主类
 
	public static void main(String[] args) {//主方法
		// TODO Auto-generated method stub
		Cook1 cook = new Cook1();// 创建厨师类的对象
		System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出结果
		cook.cooking("香辣肉丝");// 厨师烹饪香辣肉丝
		System.out.println("**你们的厨师叫什么名字?***");//输出结果
		System.out.println(cook.name);// 厨师回答自己的名字
		System.out.println("**请让厨师给我切一点葱花。***");//输出结果
		cook.cutOnion();// 厨师去切葱花
	}
}
class Cook1 {//创建Cool类
	String name;// 厨师的名字
	public Cook1() {//公共类Cook1
		this.name = "Tom Cruise";// 厨师的名字叫Tom Cruise
	}
	void cutOnion() {// 厨师切葱花
		System.out.println(name + "切葱花");//输出结果
	}
	void washVegetavles() {// 厨师洗蔬菜
		System.out.println(name + "洗蔬菜");//输出结果
	}
	void cooking(String dish) {// 厨师烹饪顾客点的菜
		washVegetavles();//洗蔬菜
		cutOnion();//切葱花
		System.out.println(name + "开始烹饪" + dish);//输出结果
	}


例7.3 将厨师的属性和部分方法用private修饰。

public class Restaurant {//创建主类
 
	public static void main(String[] args) {//主方法
		// TODO Auto-generated method stub
		Cook2 cook = new Cook2();// 创建厨师类的对象
		System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出结果
		cook.cooking("香辣肉丝");// 厨师烹饪香辣肉丝
		System.out.println("**你们的厨师叫什么名字?***");//输出结果
		System.out.println(cook.name);// 厨师回答自己的名字
		System.out.println("**请让厨师给我切一点葱花。***");//输出结果
		cook.cutOnion();// 厨师去切葱花
	}
}
class Cook2 {//创建Cook2主类
	String name;//厨师的名字
	public Cook2() {//公共类Cook2
		this.name = "Tom Cruise";//厨师的名字叫Tom Cruise
	}
	void cutOnion() {//厨师切葱花
		System.out.println(name + "切葱花");//输出结果
	}
	private void washVegetavles() {//厨师洗蔬菜
		System.out.println(name + "洗蔬菜");//输出结果
	}
	void cooking(String dish) {//厨师烹饪顾客点的菜
		washVegetavles();//洗蔬菜
		cutOnion();//切葱花
		System.out.println(name + "开始烹饪" + dish);//输出结果
	}
}


例7.4 将厨师对象封装在餐馆类中,顾客无法接触到厨师的任何信息。


public class Restaurant{//创建主类
	private Cook2 cook = new Cook2();// 餐厅封装的厨师类
 
	public void takeOrder(String dish) {// 下单
		cook.cooking(dish);// 通知厨师做菜
		System.out.println("您的菜好了,请慢用。");//输出结果
	}
 
	public String saySorry() {// 拒绝顾客请求
		return "抱歉,餐厅不提供此项服务。";//输出结果
	}
 
	public static void main(String[] args) {//主方法
		Restaurant4_4 water = new Restaurant4_4();// 创建餐厅对象,为顾客提供服务
		System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出结果
		water.takeOrder("香辣肉丝");// 服务员给顾客下单
		System.out.println("**你们的厨师叫什么名字?***");//输出结果
		System.out.println(water.saySorry());// 服务员给顾客善意的答复
		System.out.println("**请让厨师给我切一点葱花。***");//输出结果
		System.out.println(water.saySorry());// /服务员给善意的答复顾客
}

}

 

 

抽象和封装的不同点:

抽象和封装是互补的概念。一方面,抽象关注对象的行为。另一方面,封装关注对象行为的细节。一般是通过隐藏对象内部状态信息做到封装,因此,封装可以看成是用来提供抽象的一种策略。

 

实现Java封装

Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:

1)隐藏一个类中不需要对外提供的实现细节;

2)使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;

3)便于修改,增强代码的可维护性;
private  属性类型 属性名称; // private修饰的成员在自己所在的类中可以使用,在类外边不可以使用
private 方法返回值 方法名称(参数列表){}

 

为属性封装:

 

 

 



 

7.2类的继承

7.2.1extends关键字

 sticker_start_tag_for_text 19:33:12
private  属性类型 属性名称; // private修饰的成员在自己所在的类中可以使用,在类外边不可以使用
private 方法返回值 方法名称(参数列表){}

 sticker_start_tag_for_text 19:38:35
继承是面向对象的一个显著的特征。继承是从已有的类中派生出新类,新的类能吸收已有类的属性和方法,并能扩展新的属性和方法。
继承的语法格式:
class 子类 extends 父类{}
子类也被称为派生类,父类有被称为基类、超类

 sticker_start_tag_for_text 19:41:57
继承是面向对象的一个显著的特征。继承是从已有的类中派生出新类,新的类能吸收已有类的属性和方法,并能扩展新的属性和方法。

继承的语法格式:

class 子类 extends 父类{}

子类也被称为派生类,父类有被称为基类、超类

子类并没有定义任何的操作,而在主类中所使用的全部是由父类定义的,这也就说明了,子类即使不扩充父类,也能维持父类的操作。
从表面上看,子类扩充了父类的功能,但是子类还有一个特点:子类实际上是将父类的定义更加具体化了,父类表示的范围大,而子类表示的范围小。
继承的限制

虽然继承可以进行功能的扩充,但是其在定义的时候也是有若干限制的:

限制一:一个子类只能继承一个父类(单继承局限)

从表面上看,子类扩充了父类的功能,但是子类还有一个特点:子类实际上是将父类的定义更加具体化了,父类表示的范围大,而子类表示的范围小。

二、继承的限制

虽然继承可以进行功能的扩充,但是其在定义的时候也是有若干限制的:

2.1限制一:一个子类只能继承一个父类(单继承局限)



 sticker_start_tag_for_text 19:42:58
范例(继承多个的错误写法):

class A{}

class B{}

class C extends A,B{}

这种操作称为多重继承,实际上以上的做法无非就是希望一个子类可以继承多个父类的功能,但以上的语法不支持,可以换为以下的写法:

class A{}

class B extends A{}

class C extends B{}

在这里,C实际上属于(孙)子类,这样一来,相当于B继承了A的全部方法,而C又继承了A和B的方法,这种操作称为多层继承。

2.2限制:二:在一个子类继承的时候,实际上会继承父类中所有的属性和方法,但是要注意的是,对于所有的非私有操作(没有private)属于显示继承(可以直接利用对象操作),而所有的私有操作属于隐式继承(间接完成)。


 sticker_start_tag_for_text 19:43:47
范例:
 
package com.wfg.demo;
 
import javax.naming.Name;
 
/**
 * @Author WFG
 * @Date 2019/6/1 20:28
 */
 
class A {
    private String name;
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
}
 
class B extends A{
    public void print() {
        //错误,name属性是私有的(被private修饰),对外不可见
        System.out.println(name);
    }
}
public class TestDemo3 {
    public static void main(String[]args){
        B b = new B();
        b.setName("王富贵");
        System.out.println(b.getName());
    }
}

虽然对A类中的name属性无法直接进行访问,但是却可以通过getter、setter方法间接的进行操作。
2.3限制三:在继承关系中,如果要实例化子类的对象,会默认的先调用父类的构造,为父类之中的属性初始化,之后再调用子类构造,为子类之中的属性初始化,即:默认情况下,子类会找到父类之中的无参构造方法。
package com.wfg.demo;

 
import javax.naming.Name;
 
/**
 * @Author WFG
 * @Date 2019/6/1 20:28
 */
 
class A {
    public A() {
        System.out.println("这是父类的无参构造");
    }
}
class B extends A {
    public B() {
        System.out.println("这是子类的构造");
    }
}
public class TestDemo3 {
    public static void main(String[]args){
        B b = new B();//实例化子类的对象
    }
}

运行结果:
这是父类的无参构造
这是子类的构造
虽然实例化的是子类的对象,但是从上我们可以看出,它会默认先执行父类的构造,调用父类构造方法执行,而后在实例化子类的对象,调用子类的构造方法,这个时候,对于子类而言,相当于隐含了一个super()的形式:
class B extends A {
    public B() {
        super();//调用父类的构造
        System.out.println("这是子类的构造");
    }
}
以上例子都是调用的父类的无参构造,而如果这时候父类没有无参构造,则子类必须通过super()调用指定参数的构造方法:
 
package com.wfg.demo;
 
import javax.naming.Name;
 
/**
 * @Author WFG
 * @Date 2019/6/1 20:28
 */
 
class A {
    public A(String name) {
        System.out.println("这是父类的有参构造");
    }
}
class B extends A {
    public B() {
        super("王富贵");//调用父类的构造
        System.out.println("这是子类的构造");
    }
}
public class TestDemo3 {
    public static void main(String[]args){
        B b = new B();//实例化子类的对象
    }
}
运行结果:
这是父类的有参构造
这是子类的构造
在任何情况下,子类都逃不出父类的构造方法的调用,很明显,super调用父类构造,这个语法和this很相似:super调用父类构造时,一定要放在构造方法的首行。(this关键字:this关键字作用)
例7.5 创建Pad类,继承Computer类。


 class Computer {// 父类:电脑
	String screen = "液晶显示屏";//创建字符串
 
	void startup() {//开机
		System.out.println("电脑正在开机,请等待...");//输出结果
	}
}
public class Pad_5 extends Computer{//创建主类
	String battery = "5000毫安电池";// 子类独有的属性
	public static void main(String[] args) {//主方法
		// TODO Auto-generated method stub
		Computer pc = new Computer();// 电脑类
		System.out.println("computer的屏幕是:" + pc.screen);//输出结果
		pc.startup();//父类方法
		Pad_5 ipad = new Pad_5();// 平板电脑类
		System.out.println("pad的屏幕是:" + ipad.screen);// 子类可以直接使用父类属性
		System.out.println("pad的电池是:" + ipad.battery);// 子类独有的属性
		ipad.startup();// 子类可以直接使用父类方法
	}
}

 

 

7.2.2方法的重写

前提是继承)

java中方法的重写

1.定义
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。

2.方法重写的要求

子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
子类不能重写父类中声明为private权限的方法
子类方法抛出的异常不能大于父类被重写方法的异常
注意:
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
super关键字

(1)super能出现在实例方法和构造方法中。

(2)super的语法是“super.”和“super()”。

(3) super不能出现在静态方法中。

(4) super大部分情况下是可以省略的。

(5)super.什么时候不能省略呢?
别急,我们想一下this指向的是什么,是当前对象自己。super和this类似,它指向了当前对象自己的父类型特征(也就是继承过来的那些东西)。

super和this区别是:this可以看做一个引用变量,保存了该对象的地址,是当前对象整体,而super代表的是父类型特征,是子类局部的一些东西,这些继承过来的东西已经在子类里面了,你可以输出整体this,但不能输出父类型特征super。因为super指向的东西不是一个整体,没法打印输出。

System.out.println(this);  //输出this.toString()的值
System.out.println(super);  //编译报错,需要'.'
1
2
当在子类对象中,子类想访问父类的东西,可以使用“super.”的方式访问。例如:方法覆盖后,子类内部虽然重写了父类的方法,但子类也想使用一下父类的被覆盖的方法,此时可以使用“super.”的方式。当子类中出现和父类一样的属性或者方法,此时,你要想去调用父类的那个属性或者方法,此时“super.”不能省略。

this和super都只能在对象内部使用。
this代表当前对象本身,super代表当前对象的父类型特征。
例7.6 创建 Pad2 类,继承 Computer2 类,并重写父类的 showPicture()方法。

class Computer2 {// 父类:电脑
	 
	void showPicture() {//父类方法
		System.out.println("鼠标点击");//输出结果
	}
}
public class Pad2_6 extends Computer2{//创建主类
	void showPicture() {//子类重写父类方法
		System.out.println("手指点击触摸屏");//输出结果
	}
	public static void main(String[] args) {//
		// TODO Auto-generated method stub
		Computer2 pc = new Computer2();// 电脑类
		System.out.print("pc打开图片:");//输出结果
		pc.showPicture();// 调用方法
		Pad2_6 ipad = new Pad2_6();// 平板电脑类
		System.out.print("ipad打开图片:");//输出结果
		ipad.showPicture();// 重写父类方法
		Computer2 computerpad = new Pad2_6();// 父类声明,子类实现
		System.out.print("computerpad打开图片:");//输出结果
		computerpad.showPicture();// 调用父类方法,实现子类重写的逻辑
	}

}

 

 

例7.7创建Pad3类,继承Computer3类,重写父类方法,并用使用super关键字调用父类

方法。


 class Computer3 {// 父类:电脑
	String  sayHello(){//创建字符串
		return "欢迎使用";//返回"欢迎使用"值
	}
}
public class Pad3_7 extends Computer3{//创建主类,继承
	String sayHello() {// 子类重写父类方法
		return super.sayHello() + "平板电脑";// 调用父类方法,在其结果后添加字符串
	}
	public static void main(String[] args) {//主方法
		Computer3 pc = new Computer3();// 电脑类
		System.out.println(pc.sayHello());//输出结果
		Pad3_7 ipad = new Pad3_7();// 平板电脑类
		System.out.println(ipad.sayHello());//输出结果
	}
}

 

 

7.2.3所有的;类的父亲--Object类

Object类是Javajava.lang包下的核心类,Object类是所有类的父类,何一个类时候如果没有明确的继承一个父类的话,那么它就是Object的子类;

以下两种类的定义的最终效果是完全相同的:

class Person { }

class Person extends Object { }

使用Object类型接收所有类的对象
在这里插入图片描述

Object 类属于java.lang包,此包下的所有类在使用时无需手动导入,系统会在程序编译期间自动导入
Object 类的结构图(Object提供了11 个方法)
在这里插入图片描述
下面我们一个个方法进行分析,看这些方法到底有什么作用:

1. clone()

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

2. getClass()

final方法,返回Class类型的对象,反射来获取对象。

3. toString()

该方法用得比较多,一般子类都有覆盖,来获取对象的信息。

例7.8在项目中创建ObjectInstance类,在类中重写Object类的toString()方法,并在主方法中输出该类的实例对象。
 


 
public class ObjectInstance_8 {//创建主类
	public String toString() {				//重写toString()方法
		return "在" + getClass().getName() + "类中重写toString()方法";//返回
	}
	public static void main(String[] args) {//主方法
		// TODO Auto-generated method stub
		System.out.println(new ObjectInstance_8()); //打印本类对象
	}
 
}


4. finalize()

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

5. equals()

比较对象的内容是否相等

例7.9 在项目中创建OverWriteEquals类,在类的主方法中定义两个字符串对象,调用 equals()fang 方法判断两个字符串对象是否相等。


 
class V { // 自定义类V
}
public class OverWriteEquals_9 {//创建主类
 
	public static void main(String[] args) {//主方法
		String s1 = "123"; // 实例化两个对象,内容相同
		String s2 = "123";//实例化两个对象,内容相同
		System.out.println(s1.equals(s2)); // 使用equals()方法调用
		V v1 = new V(); // 实例化两个V类对象
		V v2 = new V();//实例化两个V类对象
		System.out.println(v1.equals(v2)); // 使用equals()方法比较v1与v2对象
	}
}

 

6. hashCode()

该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

7. wait()

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

其他线程调用了该对象的notify方法。
其他线程调用了该对象的notifyAll方法。
其他线程调用了interrupt中断该线程。
时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

8. notify()

该方法唤醒在该对象上等待的某个线程。

9. notifyAll()

该方法唤醒在该对象上等待的所有线程。
二. Object类的常用方法

方法名称 类型 描述
toString( ) 普通 取得对象信息
equals() 普通 对象内容比较
toString方法

toString():取得对象信息,返回该对象的字符串表示

我们先看一个简单的例子:
在这里插入图片描述

输出:iqqcode.algorithm.bintree.Person@1ee12a7
在使用对象直接输出的时候,默认输出的是一个对象在堆内存上的地址值;如若要输出该对象的内容,则要覆写toString()方法

覆写Person中的toString()方法
在这里插入图片描述

名字为:Mr.Q 年龄为:20
toString( )的核心目的在于取得对象信息

String作为信息输出的重要数据类型,在Java中所有的数据类型只要遇见String就执行了+,都要求其变为字符串后连接,而所有对象想要变为字符串就默认用toString( )方法

例如:

System.out.println("hello" + 123);

>>> 输出:hello 123

为什么hello 和 123 (一个是字符串,一个是int类型的数据) 就可以直接拼接在一起呢?

因为字符串是爸爸,在这个拼爹的时代,他有一个万能的爸爸Object

换而言之,Object是所有类的父类,任意类都是继承Object类的。而Object中定义了 toString()方法,所以任意类中都包含了toString()方法,对象在实例化之后都可以调用。

所以任意对象转字符串的输出,是通过覆写 toString()方法实现的…

每一个类中都包含有toString(),但是并不是每一个类都覆写了toString()
在这里插入图片描述
在这里插入图片描述
在源码中,可以发现通过反射,获取到了当前对象的全限定类名和@十六进制哈希值字符串。这就是不覆写toString()时直接打印输出的内容。
equals方法

equals():对象比较

String类对象比较 使用的是 equals()方法,实际上String类的equals()方法就是覆写 Object类中的equals()方法
基本数据类型的比较用 == (如: a == 3,b == 4, a == b,比较的是值是否相等)
引用类型数据比较:调用 equals()方法进行比较
​​用equals( )来比较对象内容是否相同:
​​在这里插入图片描述
两个对象per1和per2的内容明明相等,应该是true呀?怎么会是false?

因为此时直接调用equals()方法默认进行比较的是两个对象的地址。

在源码中,传递来的Object对象和当前对象比较地址值,返回布尔值。
在这里插入图片描述
但是,new一下就会在堆上创建新空间,两个对象地址自然不会相同,所以为false。

但是在判断两个对象是否相等时,比如要判断一个Person类的两个对象的姓名是否相同时,此时要重新覆写equals()

还是上面的例子,覆写equals()方法
所以,引用类型的数据在进行比较时,应该先覆写equals()方法,不然比较的还是两个对象的堆内存地址值,必然不会相等.

 

7.3类的多态 
        多态意为一个名字可具有多种语义,在程序设计语言中,多态性是指“一种定义,多种实现”例如,运算符“+”作用于两个整型量时是求和,而作用于两个字符型量时则是将其连接在一起。利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。类的多态性可以从两方面体现:一是方法的重载,二是类的上下转型。

7.3.1 方法的重载
方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。

例7.10 在项目中创建 OverLoadTest类,在类中编写add()方法的多个重载形式,然后在主方法中分别输出这些方法的返回值。

public class OverLoadTest {//创建主类
 
    public static int add(int a) {//定义一个对象
        return a;//返回到a的值
    }
    
    public static int add(int a, int b) {// 定义与第一个方法参数个数不同的方法
        return a + b;//返回到“a+b”的值
    }
    
    public static double add(double a, double b) {// 定义与第一个方法相同名称、参数类型不同的方法
        return a + b;//返回到“a+b”的值
    }
    
    public static int add(int a, double b) {// 定义一个成员方法
        return (int) (a + b);//返回
    }
    
    public static int add(double a, int b) {// 这个方法与前一个方法参数次序不同
        return (int) (a + b);//返回
    }
    
    public static int add(int... a) {// 定义不定长参数
        int s = 0;//定义整型变量s的值为0
        
        for (int i = 0; i < a.length; i++) {// 根据参数个数循环操作
            s += a[i];// 将每个参数的值相加
        }
        return s;// 将计算结果返回
    }
    public static void main(String args[]) {//主方法
        System.out.println("调用add(int)方法:" + add(1));//输出结果
        System.out.println("调用add(int,int)方法:" + add(1, 2));//输出结果
        System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));//输出结果
        System.out.println("调用add(int a, double b)方法:" + add(1, 3.3));//输出结果
        System.out.println("调用add(double a, int b) 方法:" + add(2.1, 3));//输出结果
        System.out.println("调用add(int... a)不定长参数方法:"+ add(1, 2, 3, 4, 5, 6, 7, 8, 9));//输出
 System.out.println("调用add(int... a)不定长参数方法:" + add(2, 3, 4));//输出结果
    }
}


 
结果


      

 

 7.3.2向上转型
对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型操作。

例7.11在项目中创建Quadrangle父类,再创建Parallelogram子类,并使Parallelogram子类 Quadrangle父类,然后在主方法中调用父类的draw()方法。


 

class Quadrangle { // 四边形类
    public static void draw(Quadrangle q) { // 四边形类中的方法
        // SomeSentence
    }
}
public class Parallelogram_11 extends Quadrangle{//创建主类
    public static void main(String args[]) {//主方法
        Parallelogram_11 p = new Parallelogram_11(); // 实例化平行四边形类对象引用
        draw(p); // 调用父类方法
    }
}


结果:无 

7.3.3向下转型
通过向上转型可以推理出向下转型是将较抽象类转换为较具体的类。

例7.12修改例7.11,在Parallelogram子类的主方法中将父类Quadrangle的对象赋值给子类Parllelogram的对象的引用变量将使程序产生错误。

class Quadrangle {// 四边形类
    public static void draw(Quadrangle q) {// 四边形类中的方法
        // SomeSentence
    }
}
public class H7_12 extends Quadrangle {//创建主类
    public static void main(String args[]) {//主方法
        draw(new H7_12());
        // 将平行四边形类对象看作是四边形对象,称为向上转型操作
        Quadrangle q = new H7_12();// 将父类对象赋予子类对象
        H7_12 p = (H7_12) q; 
        // //将父类对象赋予子类对象,并强制转换为子类型
        //Parallelogram p = (Parallelogram) q;
    }
}
结果 

在Parallelogram 子类的主方法中将父类 Quadrangle 的对象赋值给子类Parallelogram 的对象的引用变量将使程序产生错误。

7.3.4 instanceof关键字
        当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。

instanceof的语法格式如下:

myobject instanceof ExampleClass

myobject:某类的对象引用。

ExampleClass:某个类。   

注意:

instanceof是Java语言的关键字,在Java语言中的关键字都为小写。

例7.13在项目中创建Parallelogram类和另外3个类Quadrangle、Square、Anything。其中 Parallelogram 类和 Square 类继承 Quadrangle类,在Parallelogram类的主方法中分别创建这些类的对象,然后使用instanceof操作符判断它们的类型并输出结果。


 
 
class Quadrangle {// 四边形类
    public static void draw(Quadrangle q) {// 四边形类中的方法
        // SomeSentence
    }
}
 
class Square extends Quadrangle {//square类
    // SomeSentence
}
 
class Anything {//anything类
    // SomeSentence
}
public class Parallelogram_13 extends Quadrangle {//创建主类,继承
public static void main(String args[]) {//主方法
        Quadrangle q = new Quadrangle(); // 实例化父类对象
        if (q instanceof Parallelogram_13) {// 判断父类对象是否为Parallelogram子类的一个实例
            Parallelogram_13 p = (Parallelogram_13) q; // 进行向下转型操作
        }
        if (q instanceof Square) {// 判断父类对象是否为Parallelogram子类的一个实例
            Square s = (Square) q; // 进行向下转型操作
        }
        // 由于q对象不为Anything类的对象,所以这条语句是错误的
        // System.out.println(q instanceof Anything);
    }
}
结果:无

7.4 抽象与接口 
7.4.1 抽象类与抽象方法
        在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体图形,但它的子类却可以。

Java中定义抽象类时,需要使用abstract关键字,其语法如下

[权限修饰符] abstract  class 类名{

类体

}

        使用abstract关键字定义的类称为抽象类,而使用abstract关键字定义的方法称为抽象方法,抽象方法的定义语法如下:

[权限修饰符]  abstract 方法返回值类型方法名(参数列表);

        从上面的语法可以看出,抽象方法是直接以分号结尾的,它没有方法体,抽象方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上,抽象类除了被继承之外没有任何意义。

例7.14使用抽象类模拟“去商场买衣服”场景。去商场买衣服,这句话描述的是一个抽象的行为:到底去哪个商场买衣服,是实体店还是网店,买什么样的衣服,是短衫、裙子,还是其他的什么衣服?在“去商场买衣服”这句话中,并没有对“买衣服”这个抽象行为指明一个确定的信息。因此,我们可以封装一个商场的抽象类,并在其中定义个买东西的抽象方法,具体是什么商场、买什么东西,交给子类去实现即可。代码如下:

public abstract class Market {//创建主类
    public String name;//商场名称
    public String goods;//商品名称
    public abstract void shop();//抽象方法,用来输出信息
 
}
定义一个TaobaoMarket类,继承自Market抽象类,实现其中的shop抽象方法,代码如下:

public class TaobaoMarket extends Market {//创建TaobaoMarket主类,继承了Market类
 
    @Override
    public void shop() {//shop抽象方法
        // TODO Auto-generated method stub
        System.out.println(name + "实体店购买"+goods);//输出结果
    }
 
}
定义一个WallMarket类,继承子Market抽象类,实现其中的shop抽象方法

public class WallMarket extends Market {//创建WallMarket主类,继承Market类
 
    @Override
    public void shop() {//shop抽象方法
        // TODO Auto-generated method stub
        System.out.println(name + "网购"+goods);//输出结果
    }
 
}
定义一个GoShopping类,该类中分别使用实现的 WallMarket子类和TaobaoMarket子类创建抽象类的对象,并分别给抽象类中的成员变量赋不同的值,使用shop方法分别输出结果,代码如下:

public class GoShopping //创建主类
{
    public static void main(String[] args)//主方法
    { 
        Market market=new WallMarket();//使用派生类对象创建抽象类对象 
    market.name="沃尔玛";//商场名称为沃尔玛
    market.goods ="七匹狼西服";// 商品名称为七匹狼西服
    market.shop();//使用shop抽象方法
    market = new TaobaoMarket();//使用派生类对象创建抽象类对象 
    market.name="淘宝";//商场名称为淘宝
    market.goods="韩都衣舍花裙"; //商品名称为韩都衣舍花裙
    market.shop();//使用shop抽象方法
    }
    
}
 结果 

 

综上所述,使用抽象类和抽象方法时,需要遵循以下原则:

(1)在抽象类中,可以包含抽象方法,也可以不包含抽象方法,但是包含了抽象方法的类必须被定义为抽象类。

(2)抽象类不能直接实例化,即使抽象类中没有声明抽象方法,也不能实例化。

(3)抽象类被继承后,子类需要实现其中所有的抽象方法。

(4)如果继承抽象类的子类也被声明为抽象类,则可以不用实现父类中所有的抽象方法。

7.4.2接口的声明及实现
        接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于7.4.1小节中遗留的问题,可以将draw()方法封装到一个接口中,这样可以让一个类既能继承图形类,又能实现draw()方法接口,这就是接口存在的必要性。在图7.21中描述了各个子类继承图形类后使用接口的关系。

接口使用interface关键字进行定义,其语法如下:

[修饰符]interface 接口名 [extends父接口名列表] {
 
[public] [static] [final] 常量;
 
[public] [abstract] 方法;
 
}
修饰符:可选,用于指定接口的访问权限,可选值为public。如果省略则使用默的访问权限。

接口名:必选参数,用于指定接口的名称,接口名必须是合法的 Java 标识符。一般情况下,要求首字母大写。

extends 父接口名列表:可选参数,用于指定要定义的接口继承于哪个父接口。当使用 extends 关键字时,父接口名为必选参数。

方法:接口中的方法只有定义而没有被实现。

一个类实现一个接口可以使用implements关键字,代码如下:

public class Parallelogram extends Quadrangle implements drawTest!
 
…//
 
}
例7.15在项目中创建QuadrangleUseInterface类,该类中,首先创建一个drawTest接口,该接口中定义一个公有的draw()方法;然后创建两个类ParallelogramgleUseInterface 和 SquareUseInterface,使它们分别实现 drawTest接口,并分别实现接口中的draw()方法;然后在主方法中分别调用这两个子类的 draw0方法。

interface drawTest{        //定义接口
    public void draw();//定义方法
}
     
class ParallelogramgleUseInterface implements drawTest {// 定义平行四边形类,该类实现了drawTest接口
    public void draw() { // 由于该类实现了接口,所以需要覆盖draw()方法
        System.out.println("平行四边形.draw()");//输出结果
        }
    }
     
class SquareUseInterface implements drawTest {    // 定义正方形类,该类实现了drawTest接口
    public void draw() {// 由于该类实现了接口,所以需要覆盖draw()方法
        System.out.println("正方形.draw()");//输出结果
        }
    }
public class QuadrangleUseInterface_15 {//创建主类
    public static void main(String[] args) {//主方法
        drawTest[] d = { // 接口也可以进行向上转型操作
                new SquareUseInterface(), new ParallelogramgleUseInterface() };//创建新类
        for (int i = 0; i < d.length; i++) {//循环语句
            d[i].draw(); // 调用draw()方法
            }
    }
}
结果 

 

7.4.3多重继承
        在Java 中类不允许多重继承,但使用接口就可以实现多重继承,因为一个类可以同时实现多个接口,这样可以将所有需要实现的接口放置在 implements 关键字后并使用逗号“,”隔开,但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。

        通过接口实现多重继承的语法如下: 如果

class 类名 implements 接口1,接口2,…,接口n
例7.16通过类实现多个接口模拟家庭成员的继承关系,比如,爸爸喜欢抽烟和钓鱼,妈妈喜欢看电视和做饭,儿子完全继承了爸爸妈妈的爱好。定义一个IFather接口,并在其中定义两个 smoking和goFishing,代码如下:

public interface IFather {//定义一个接口
 
    void smoking();//抽烟的方法
 
    void gofishing();//钓鱼的方法
 
}
定义一个 IMother 接口,并在其中定义两个方法 watchTV 和 cooking,代码如下:

public interface IMother {//定义一个接口
 
    void cooking();//做饭的方法
 
    void watchTV();//看电视的方法
 
}
创建一个名称为 Me 的类,继承IFather 和 IMother 两个接口,并实现接口中定义的方法;然后在main方法中使用Me子类对象分别创建IFather和Mother两个接口的对象,并通过这两个接口对象调用相应的方法执行,代码如下:

public class Me implements IFather, IMother {// 继承IFather 接口和IMother物
    public void watchTV() { //重写watchTV()方法 
        System.out.println("我喜欢看电视");//输出我喜欢看电视
        }
    public void cooking() { //重写cooking()方法 
        System.out.println("我喜欢做饭");//输出我喜欢做饭
        }
    public void smoking() { //重写smoking()方法 
        System.out.println("我喜欢抽烟");//输出我喜欢抽烟
        }
    public void goFishing() { //重写goFishing()方法 
        System.out.println("我喜欢钓鱼");//输出我喜欢钓鱼

    public static void main(String[] args){//主方法
    IFather father = new Me();//通过子类创建IFather接口对象
    System.out.println("爸爸的爱好:");//输出爸爸的爱好:
    father.smoking();//使用接口对象调用子类中实现的方法
    father.gofishing();//使用接口对象调用子类中实现的方法
    IMother mather = new Me(); //通过子类创建IMother接口对象 
    System.out.println("\n 妈妈的爱好:");//换行输出妈妈的爱好:
    mather.cooking();// 使用接口对象调用子类中实现的方法 
    mather.watchTV();//使用接口对象调用子类中实现的方法
    }
    @Override
    public void gofishing() {//gofishing方法
        // TODO Auto-generated method stub
        
    }
}
结果  

 

 7.4.4 区分抽象类与接口
抽象类和接口的区别主要有以下几点。

(1)子类只能继承一个抽象类,但可以实现任意多个接口。

(2)一个类要实现一个接口必须实现接口中的所有方法,而抽象类不必。

(3)抽象类中的成员变量可以是各种类型,而接口中的成员变量只能是public static final的。

(4)接口中只能定义抽象方法,而抽象类中可以定义非抽象方法。

(5)抽象类中可以有静态方法和静态代码块等,接口中不可以。

(6)接口不能被实例化,没有构造方法,但抽象类可以有构造方法。

综上所述,抽象类和接口在主要成员及继承关系上的不同如表 7.1所示。  

比较项

抽象类

接口

方法

可以有非抽象方法

所有方法都是抽象方法

属性

属性中可以有非静态常量

所有的属性都是静态常量

构造方法

有构造方法

没有构造方法

继承

一个类只能继承一个父类

一个类可以同时实现多个接口

被继承

一个类只能继承一个父类

一个接口可以同时继承多个接口

7.5 访问控制
        前面多次提到了public、private、包等关键字或者概念,这些都是用来控制类、方法或者变量的访问范围的,Java中主要通过访问控制符、类包和final关键字对类、方法或者变量的访问范围进行控制。

7.5.1 访问控制符
        Java中的访问控制符主要包括public、protected、private和default(缺省)等4种,这些控制符控制着类和类的成员变量以及成员方法的访问权限。
 

使用访问控制符时,需要遵循以下原则。

(1)大部分顶级类都使用public 修饰;

(2)如果某个类主要用作其他类的父类,该类中包含的大部分方法只是希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰;

(3)类中的绝大部分属性都应该使用private修饰,除非一些static或者类似全局变量的属性,才考虑使用public修饰;

(4)当定义的方法只是用于辅助实现该类的其他方法(即工具方法),应该使用private修饰;

(5)希望允许其他类自由调用的方法应该使用public修饰。

7.5.2 Java类包
        在Java 中每定义好一个类,通过 Java编译器进行编译之后,都会生成一个扩展名为.class的文件,当这个程序的规模逐渐庞大时,就很容易发生类名称冲突的现象。那么JDK API中提供了成千上万具有各种功能的类,又是如何管理的呢?Java 中提供了一种管理类文件的机制,就是类包。

7.5.3 final 关键字 
1.final 类

定义为final 的类不能被继承。

如果希望一个类不允许任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为 final 形式。

final 类的语法如下:

final class类名{ }

如果将某个类设置为final形式,则类中的所有方法都被隐式地设置为final形式,但是final类中的成员变量可以被定义为 final 或非 final 形式。

例7.17在项目中创建FinalClass 类,在类中定义doit()方法和变量a,实现主方法中操作变量a自增。

final class FinalClass_17 {//创建FinalClass_17主类
    int a = 3;//定义整型变量a的值为3
    void doit() {//调用 doit()方法
    }
    public static void main(String[] args) {//主方法
        // TODO Auto-generated method stub
        FinalClass_17 f = new FinalClass_17();//新建数组
        f.a++;//累加语句
        System.out.println(f.a);//输出“f.a”的结果
    }
 
}
 结果

 

1.final方法

将方法定义为final类型可以防止子类修改该类的定义与实现方式,同时定义final的方法的执行效率要高于非final方法,在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为 private的方法隐式被指定为final类型,这样将无需将一个定义为 private 的方法再定义为final类型。例如的语句:

private final void test(){

…//省略一些程序代码

}

但是在父类中被定义为private final法似乎可以被子类覆盖,来看下面的实例。

例7.18项目中创建FinalMethod类,在该类中创建Parents 类和继承该类的Sub类,在主

法中分别调用这两个类中的方法,并查看 final 类型方法能否被覆盖。

class Parents{             //创建父类
     private final void doit() {   //调用final类
      System.out.println("父类.doit()");  //输出调用父类
     }
     final void doit2() {      //调用doit2()方法
       System.out.println("父类.doit2()");//输出调用父类
     }
     public void doit3() {        //调用doit3()方法
      System.out.println("父类.doit3()");//输出调用父类
     }
    }
    class Sub extends Parents {    //子类继承父类
     public final void doit() { //在子类中定义一个doit()方法
      System.out.println("子类.doit()");//输出调用子类
     }
    // final void doit2(){  //调用doit2()方法
    //  System.out.println("子类.doit2()"); //输出调用子类
    // }
     public void doit3() {   //调用doit3()方法
      System.out.println("子类.doit3()");//输出调用子类
     }
    }
    public class FinalMethod_18 {   //创建类
     public static void main(String[] args) {   //主函数
      Sub s=new Sub();                    //实例化 
      s.doit();                     //调用 doit()方法 
      Parents p=s;            //执行向上转型操作 
      //p.doit();               //不能调用private方法 
      p.doit2();        //调用 doit2()方法 
      p.doit3();            //调用 doit3()方法 
     }
    }
结果

 

3.final变量

        final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final的常量赋值。例如,在类中定义PI值,可以使用如下语句:

final double PI-3.14;

        当在程序中使用PI这个常量时,它的值是3.14,如果在程序中再次对定义为final的常量赋值,编译器将不会接受。

        final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看作一个对象来引用,所以final可以修饰数组。一个对象引用被修饰为final后,它能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。为了深入了解 final 关键字,来看下面的实例。

例7.19在项目的com.lzw包中创建FinalData类,在该类中创建Test内部类,并定义各种类型的final变量。

import static java.lang.System.out;    // 导入System.out
import java.util.Random;     // 导入需要java.util.Random的包
class Test {   //类名
 int i = 0;
    }
    public class FinalData_19{   //创建类
     static Random rand =new Random();        //创建新数组
     private final int VALUE_1 = 9;             //声明一个final常量
     private static final int VALUE_2 = 10;     //声明一个 final、static常量
     private final Test test = new Test();       //声明一个 final引用
     private Test test2 = new Test();             //声明一个不是 final 的引用
     private final int[] a = {1,2,3,4,5,6 };         //声明一个定义为final 的数组
     private final int i4 = rand.nextInt(20);           //声明一个final常量
     private static final int i5= rand.nextInt(20);     //声明一个final常量
     public String toString() {        //调用toString()
      return i4 +" "+i5+" ";          //输出结果
     }
     public static void main(String[] args){       //主函数
         FinalData_19 data = new FinalData_19();             //创建新数组
      //data.test=new Test();              
      //可以对指定为final的引用中的成员变量赋值
      //但不能将定义为final的引用指向其他引用 
      //data.VALUE_2++;
      //不能改变定义为final的常量值
      data.test2=new Test(); //可以将没有定义为 final的引用指向其他 
      for (int i = 0; i < data.a.length; i++) {   //控制长度
      //a[i]=9; 
      //不能对定义为final的数组赋值
      }
      out.println(data);             //输出结果
      out.println("data2");               //输出data2结果
      out.println(new FinalData_19());             //输出数组结果
      out.println(data);              //输出结果
        
    }
 
}
结果 

 

例7.20 在项目的com.lzw包中创建FinalStaticData类,在该类中创建Random类的对象,在主方法中分别输出类中定义的final变量a1与a2。

import java.util.Random;// 导入需要java.util.Random的包
import static java.lang.System.out;// 导入System.out 
public class FinalStaticData_20 {//创建主类
    private static Random rand = new Random();//实例化一个Random类对象 
    private final int al =rand.nextInt(10);//随机产生0~10之间的随机数赋予定义为final的a1     
    private static final int a2=rand.nextInt(10); //随机产生0~10之间的随机数赋予定义为static final的a2
    public static void main(String[] args) {//主方法
        FinalStaticData_20 fdata= new FinalStaticData_20();//实例化一个对象//调用定义为final的al
        out.println("重新实例化对象调用a1的值:"+fdata.al);//调用定义为static final的a2
        out.println("重新实例化对象调用al的值:"+fdata.a2);//实例化另外一个对象
        FinalStaticData_20 fdata2 =new FinalStaticData_20();//实例化另外一个对象
        out.println("重新实例化对象调用al的值:"+ fdata2.al); //输出结果
        out.println("重新实例化对象调用a2的值:"+ fdata2.a2);//输出结果
    }
}
 
结果 

 

7.6内部类 
        一个文件中定义两个类,但其中任何一个类都不在另一个类的内部,而在类中再定义一个类,则将在类中再定义的那个类称为内部类。内部类可以分为成员内部类、局部内部类以及匿名类。

7.6.1成员内部类 
1.成员内部类简介
在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。
成员内部类的语法如下: 

public class OuterClass {  //外部类 
private class InnerClass{//内部类
//…
}
}

 例7.21中创uterOuterClass类,在类中定义innerClass内部类和doit()方法,在主方法中创建OuterClass类的实例对象和doit()方法。 

public class OuterClass_21 {//创建主类
    innerClass in = new innerClass();//在外部类实例化内部类对象引用
    public void ouf() { //普通类
        in.inf();//在外部类方法中调用内部类方法
    }
    class innerClass{//innerClass类
        innerClass(){ //内部类构造方法 
        }
        public void inf() {  //内部类成员方法
        }
        int y=0; //定义内部类成员变量
    }
    public innerClass doit() {//外部类方法,返回值为内部类引用
                        //外部类不可以直接访问内部类成员变量
        in.y=4;//y=4;
        return new innerClass(); //返回内部类引用 
    }
    public static void main(String args[]){//主方法
        OuterClass_21 out = new OuterClass_21();//创建新数组
//内部类的对象实例化操作必须在外部类或外部类的非静态方法中实现 
        OuterClass_21.innerClass in = out.doit();//创建新数组doit()
        OuterClass_21.innerClass in2 =out.new innerClass();//创建新数组innerClass()
    }
}
注:该例题无运行结果 

2.内部类向上转型为接口
如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口,在接口中声命一个方法。
例7.22下面修改例7.21,在项目中创建InterfaceInner类,并定义接口 OutInterface,使内部类InnerClass实现这个接口,最后使doit()方法返回值类型为该接口,代码如下:

interface OutInterface {     //接口
    public void f();      //  普通类
}
public class InterfaceInner_22 {       //创建类
    public static void main(String[] args) {       // 主函数
        OutClass2 out = new OutClass2();        //实例化一个OutClass2对象
        
        OutInterface  outinter = out.doit();//调用doit()方法,返回一个OutInterface 接口
        outinter.f();   //存放值
    }
}
    class OutClass2 {   //普通类 OutClass2 
        
        private class InnerClass implements OutInterface {//定义一个内部类实现OutInterface接口
            InnerClass(String s) {   //返回参数
                 System.out.println(s);  // 输出结果
            }
            public  void f() {   //创建类
                 System.out.println("访问内部类中的f()方法");//输出访问内部类中的f()方法
            }
        }
        public  OutInterface doit() {         //创建 OutInterface doit()方法
            return new InnerClass ("访问内部类构造方法");  //输出访问内部类构造方法
        }
    }
结果 

 

3.使用this关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用 this关键字。
例7.23项目中创建TheSameName类,类中定义成员变量x,再定义一个内部类Inner,在内部类中也创建x变量,并在内部类的doit()方法中分别操作两个x变量。关键代码如下:

public class TheSameName_23 {//创建主类
    private int x;//private定义方法
    private class Inner{//普通方法
        private int x = 9;//定义x的值为9
        public void doit(int x) {//调用的形参是x
            x++;//累加
            this.x++;//调用内部类变量x
            TheSameName_23.this.x++;//调用外部类变量x
        }
    }
}
注:该例题无结果 

综上所述,使用员内部类时,应该遵循以下原则:  

(1)可以有各种修饰符,可以用private、public、protected、static、final、abstract 等修饰;(2)如果内部类有static限定,就是类级别的,否则为对象级别。类级别可以通过外部类直接访问,对象级别需要先生成外部的对象后才能访问; }

(3)内外部类不能同名;

(4)非静态内部类中不能声明任何 static 成员;

(5)内部类可以互相调用。

 7.6.2 局部内部类
        内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类。

例7.24 修改例 7.22,将InnerClass 类放在doit()方法的内部。关键代码如下:

interface OutInterface2 {//创建主类
}
class OuterClass3_24 {//主方法
    public OutInterface2 doit(final String x) { //doit()方法参数为final 
         class InnerClass2 implements OutInterface2 {在doit()方法中定义一个内部类
             InnerClass2(String s) {//内部类
                 s = x;//定义s的值为x
                 System.out.println(s);//输出s
             }
         }
         return new InnerClass2("doit");  输出结果doit()方法中定义一个内部类
    }
}
结果 

 

7.6.3匿名内部类
例7.25在return语句编写回值为一个匿名内部类。

interface OutInterface2{ //定义一个接口
}
class OuterClass4_25 {//创建类
    public OutInterface2 doit() { //定义doit()方法
        return new OutInterface2() { //声明匿名内部类
            private int i = 0;//定义整型变量i的值为0
            public int getvalue() {//
                return i;//返回到i
            }
        };
    }
}
注:该例题无结果 

匿名类的所有实现代码都需要在大括号之间进行编写。语法如下:

return new A(){

…//内部类体

};

其中,A指类名。

由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成 OutInterface2对象。在匿名内部类定义结束后,需要加分号标识,这个分号并不是代表定义内部类结束的标识,而是代表创建OutInterface2引用表达式的标识。

说明:

匿名内部类编译以后,会产生以“外部类名$序号”为名称的.class 文件,序号以 1~n 排列,分别代表 1~n个匿名内部类。

使用匿名内部类时应该遵循以下原则:

(1)匿名类没有构造方法;

(2)匿名类不能定义静态的成员;

 (3)匿名类不能用private、public、protected、static、final、abstract 等修饰;

(4)只可以创建一个匿名类实例。

7.6.4 静态内部类 
        在内部类中添加修饰符static,这个内部类就变成为静态内部类了。一个静态内部类中可以声明静态成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一个最大的特点,就是不能使外部类的非静态成员,所以静态内部类在程序开发中比较少见。

        可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为 static,就会有更多的限制。静态内部类具有以下两个特点:  

(1)如果创建静态内部类的对象,不需要创建其外部类的对象;

(2)不能从静态内部类的对象中访问非静态外部类的对象。

例如,定义一个StaticInnerClass,可以使用如下代码: 

public class StaticInnerClass{//创建主类
    int x = 100;//定义整型变量x的值为100
    static class Inner{//Inner类
        void doitInner() {//doitInner方法
            //System.out.println("外部类" + x);//不能调用外部类的成员变量x
        }
    }
}
例7.26 在静态内部类中定义主方法。

Public class StaticInnetrClass_26 {//创建主类
    int x = 100;//定义整型变量x的值为100
    static class Inner{//创建Inner类
        void doitInner() {//doitInner方法
            //System.out.println("外部类" + x);//不能调用外部类的成员变量x
        }
    public static void main(String[] args) {//主方法
        // TODO Auto-generated method stub
        System.out.println();//换行
        }
    }
}
注:该例题无结果 

7.6.5 内部类的继承
        内部类和其他普通类一样可以被继承,但继承内部类比继承普通类复杂,需要设置专门的语法来完成。

例7.27 在项目中创建OutputInnerClass类,使OutputInnerClass类继承ClassA类中的内部类ClassB。

package 烦死了;
 
public class OutputInnerClass_27 extends ClassA.ClassB{//创建主类,继承内部类
    public OutputInnerClass_27(ClassA a) {//继承类中内部类
        a.super();构造方法体中使用 a.super()
    }
}
class ClassA {//创建ClassA类
    class ClassB{//创建ClassB类
    }
 
}
注:该例题无结果  

        在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数必须是该内部类的外部类引用,就像例子中的ClassA a,同时在构造方法体中使用a.super()语句。

总结

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遇見即是上上籤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值