今日内容
-
多态
-
内部类
-
权限修饰符
-
代码块
教学目标
-
能够说出多态的前提
-
能够写出多态的格式
-
能够理解多态向上转型和向下转型
-
能够说出内部类概念
-
能够理解匿名内部类的编写格式
-
能够说出每种权限修饰符的作用
第一章 多态(必须掌握)
1.1 概述
引入
面向对象语言三大特征:封装(private)、继承(extends)、多态。
多态:表示的是一个事物的多种表现形态。同一个事物,以不同的形态表现出来.
多态来源于生活,在生活中我们经常会对某一类事物使用它的共性统称来表示某个具体的事物,这时这个具体的事物就以其他的形式展示出来。
苹果:说苹果,说水果。
狗:说狗,说动物。
猫:说猫,说动物。
前提【重点】
- 继承(extends)或者实现(implements)【二选一】
- 方法的重写【意义体现:不重写,无意义】
- 父类引用指向子类对象【格式体现】
1.2 多态的体现
多态体现的格式:
父类类型 变量名 = new 子类对象;
变量名.方法名();
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
代码如下:
Fu f = new Zi();
f.method();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。
代码如下:
定义父类:
public abstract class Animal {
public abstract void eat();
}
定义子类:
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的 eat
a1.eat();
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的 eat
a2.eat();
}
}
多态在代码中的体现为父类引用指向子类对象。
1.3 多态的好处
实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。代码如下:
定义父类:
public abstract class Animal {
public abstract void eat();
}
定义子类:
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Cat c = new Cat();
Dog d = new Dog();
// 调用showCatEat
showCatEat(c);
// 调用showDogEat
showDogEat(d);
/*
以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代
而执行效果一致
*/
showAnimalEat(c);
showAnimalEat(d);
}
public static void showCatEat (Cat c){
c.eat();
}
public static void showDogEat (Dog d){
d.eat();
}
public static void showAnimalEat (Animal a){
a.eat();
}
}
由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当然可以把Cat对象和Dog对象,传递给方法。
当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上两方法。
不仅仅是替代,在扩展性方面,无论之后再多的子类出现,我们都不需要编写showXxxEat方法了,直接使用showAnimalEat都可以完成。
所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。
小结:多态的好处是提高程序的灵活性,扩展性
1.4 引用类型转换
多态的转型分为向上转型与向下转型两种:
向上转型
- 向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
向下转型
- 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c =(Cat) a;
为什么要转型
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类有而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
转型演示,代码如下:
定义类:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}
转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。
所以,转换前,我们最好先做一个判断,代码如下:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
小结:多态向上转型是将子类类型转成父类类型,多态向下转型是将父类类型转成子类类型。
1.5笔记本案例
-
案例目的:接口也可以体现多态
-
案例代码:
USB接口:
/* * 定义一个USB接口 */ public interface USB { public void open(); public void close(); }
鼠标类:
/* * 定义一个鼠标类 */ public class Mouse implements USB{ //行为:开启 public void open() { System.out.println("mouse open...."); } //行为:关闭 public void close() { System.out.println("mouse close...."); } }
键盘类:
/* * 定义一个键盘类 */ public class Keyboard implements USB{ // 行为:开启 public void open() { System.out.println("keyboard open...."); } // 行为:关闭 public void close() { System.out.println("keyboard close...."); } }
电脑类:
/* * 描述电脑类 */ public class Computer { //属性 //行为 public void run() { System.out.println("computer run...."); } //给电脑外围设备提供预留的功能,该功能要遵守USB接口的规则 public void useUSB(USB u)//USB u=m { if(u!=null) { u.open(); u.close(); } } }
测试类:
public class ComputerTest { public static void main(String[] args) { // 创建笔记本电脑类的对象 Computer c = new Computer(); c.run(); Mouse m = new Mouse(); //电脑使用鼠标 c.useUSB(m); Keyboard k = new Keyboard(); // //电脑使用键盘 c.useUSB(k); } }
总结:发现接口的出现:
1、 扩展了笔记本电脑的功能.
2、 定义了规则.
3、降低了笔记本电脑和外围设备之间的耦合性.
解耦
第二章 内部类
2.1 概述
什么是内部类
类是描述事物的,内部类,它也是用来描述事物,只不过在描述的这个事物需要依赖在其他事物中。
内部类:在其他类中类。
使用地方:
当我们在描述事物的时候,事物中还有其他的事物,这个位于事物中的那个事物,就可以使用Java中提供的内部类来描述。
比如:人,可以使用Person类描述。人这个事物还有心脏这个事物,可是心脏这个事物不仅仅人所具备,而其他的动物也会具备心脏这个事物。
因此我们在描述比如人这类事物的时候,如果还需要描述心脏,那么就必须把心脏这个类描述在人这个类的内部。
这个心脏就需要使用内部类描述。
简单来讲:在一个类中包含了另一个类。
2.2 成员内部类
- 成员内部类 :定义在类中方法外的类。
定义格式:
class 外部类 {
class 内部类{
}
}
在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。比如,汽车类Car
中包含发动机类Engine
,这时,Engine
就可以使用内部类来描述,定义在成员位置。
代码举例:
class Car { //外部类
class Engine { //内部类
}
}
访问特点
- 内部类可以直接访问外部类的成员,包括私有成员。
- 外部类要访问内部类的成员,必须要建立内部类的对象。
创建内部类对象格式:
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
访问演示,代码如下:
定义类:
public class Person {
private boolean live = true;
class Heart {
public void jump() {
// 直接访问外部类成员
if (live) {
System.out.println("心脏在跳动");
} else {
System.out.println("心脏不跳了");
}
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
}
定义测试类:
public class InnerDemo {
public static void main(String[] args) {
// 创建外部类对象
Person p = new Person();
// 创建内部类对象
Person.Heart heart = p.new Heart();
// 调用内部类方法
heart.jump();
// 调用外部类方法
p.setLive(false);
// 调用内部类方法
heart.jump();
}
}
输出结果:
心脏在跳动
心脏不跳了
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。
比如,Person$Heart.class
小结:内部类是定义在一个类中的类。
2.3 匿名内部类(重点)
- 匿名内部类 :是内部类的简化写法。它的本质是一个
带具体实现的
父类或者父接口的
匿名的
子类对象。
开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作,
- 定义子类
- 重写接口中的方法
- 创建子类对象
- 调用重写后的方法
我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。
前提
存在一个类或者接口,这里的类可以是具体类也可以是抽象类。
格式
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
使用方式
以接口为例,匿名内部类的使用,代码如下:
定义接口:
public abstract class FlyAble{
public abstract void fly();
}
匿名内部类可以通过多态的形式接受
public class InnerDemo01 {
public static void main(String[] args) {
/*
1.等号右边:定义并创建该接口的子类对象
2.等号左边:是多态,接口类型引用指向子类对象
*/
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
};
}
}
匿名内部类直接调用方法
public class InnerDemo02 {
public static void main(String[] args) {
/*
1.等号右边:定义并创建该接口的子类对象
2.等号左边:是多态,接口类型引用指向子类对象
*/
new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
}.fly();
}
}
方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递
public class InnerDemo3 {
public static void main(String[] args) {
/*
1.等号右边:定义并创建该接口的子类对象
2.等号左边:是多态,接口类型引用指向子类对象
*/
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
};
// 将f传递给showFly方法中
showFly(f);
}
public static void showFly(FlyAble f) {
f.fly();
}
}
以上可以简化,代码如下:
public class InnerDemo2 {
public static void main(String[] args) {
/*
创建匿名内部类,直接传递给showFly(FlyAble f)
*/
showFly( new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
});
}
public static void showFly(FlyAble f) {
f.fly();
}
}
小结:匿名内部类做的事情是创建一个类的子类对象
第三章 权限修饰符
3.1 概述
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,
-
public:公共的。没有限制。在同一个包中同一个类中 或 在同一个包中不同类之间 或 不同包之间的子类中 或 不同包不同类之间都可以访问(简而言之-------》任何包中的类都可以访问);修饰成员变量 方法 构造方法 类
-
protected:受保护的。在本类中访问 或 同一个包中的其它类中访问 或 不同包中的子类访问;
-
默认的权限(空的):就是什么权限修饰符也不写。在本类访问或同一个包中的其它类中访问;
-
private:私有的。只能在本类(同一类)中访问。其它类(包含子类)都不能访问;修饰成员变量 方法 构造方法
public > protected>默认的权限(空的)>private
3.2 不同权限的访问能力
public | protected | (空的) | private | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中(子类与无关类) | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包中的无关类 | √ |
可见,public具有最大权限。private则是最小权限。
说明:关于protected(受保护权限)的演示,可以在不同包的子类中访问。
1、public访问权限:没有任何限制(权限最大)
代码举例:
需求:
1)定义一个com.fu包,在这个包中创建一个使用public关键字来修饰的Fu类,在这个父类中定义一个使用public修饰的method函数,在这个函数中随便打印一句话;
2)再重新定义一个com.zi包,在这个包中引入com.fu包下面的Fu类,定义一个使用public关键字来修饰的Zi类继承Fu类,在Zi类中定义一个public修饰的函数run,在这个函数中调用父类中method方法;
3)在重新定义一个com.test包,在这个包中引入com.fu包下面的Fu类和com.zi包下面的Zi类,定义一个测试类Test,在这个测试类中分别创建子父类对象,并使用对象分别调用各自中的函数;
父类:
package com.fu;
public class Fu
{
public void method()
{
System.out.println("父类中的方法....");
}
}
子类:
package com.zi;
import com.fu.Fu;//导入Fu类
public class Zi extends Fu
{
public void run()
{
System.out.println("子类中的方法....");
method(); //子类继承了父类 是可以直接去访问父类中的方法
}
}
测试类:
package com.test;
import com.fu.Fu;//导入Fu类
import com.zi.Zi;//导入子类
class Test
{
public static void main(String[] args)
{
Zi z = new Zi();
z.run();//子类对象调用子类中的方法
/*
测试类没有继承Fu这个类,但是可以创建对象,那么就可以调用Fu类中的method方法,
这样导致Fu类中的方法不仅仅子类可以直接去 访问,不是Fu的子类也可以访问了。
*/
Fu f = new Fu();
f.method();
}
}
问题:
com.test包中的Test类没有继承com.fu包中的Fu这个类,但是可以在com.test包中的Test类中创建Fu类对象,那么就可以在com.test包中的Test类中调用com.fu包中的Fu类中的method方法,这样导致Fu类中的方法不仅仅在com.zi包中的子类Zi可以直接去访问,而不是Fu的子类也可以访问了。
上述现象导致Zi类继承Fu类没有任何意义了。
在java中当定义一个类的时候,如果当前这个类中的方法只让自己的子类使用,而其他的类不让使用,这时可以在这个方法前面加上protected关键字。
protected访问权限:专门给子类使用的访问权限。
将上述Fu类中的method函数的修饰符public换成protected。
父类:
package com.fu;
public class Fu
{
protected void method()
{
System.out.println("父类中的方法....");
}
}
子类:
package com.zi;
import com.fu.Fu;//导入Fu类
public class Zi extends Fu
{
public void run()
{
System.out.println("子类中的方法....");
method(); //子类继承了父类 是可以直接去访问父类中的方法
}
}
测试类:
package com.test;
import com.fu.Fu;//导入Fu类
import com.zi.Zi;//导入子类
class Test
{
public static void main(String[] args)
{
Zi z = new Zi();
z.run();//子类对象调用子类中的方法
Fu f = new Fu();
f.method();//报错
}
}
注意:
1)如果父类中的函数使用protected关键字修饰,那么这个函数可以被不同包下的子类访问,而不同包中的其他类不能访问。
2)protected只能修饰成员函数或成员变量,不能修饰类。
3) 四种访问权限,能够跨包访问的只有 public protected(必须是子类)
4)public修饰符和默认修饰符注意事项
package com.itheima.sh.demo_03;
/*
1.public 修饰的类,要求当前.java源文件名必须和public修饰的类名一致
2.如果一个类是默认修饰符,那么当前源文件名可以随便定义
3.如果一个源文件中具有多个class类,那么只能有一个使用public修饰,并且源文件名必须是public修饰的类的名字
*/
class Demo02{
}
public class Demo01 {
}
编写代码时,如果没有特殊的考虑,建议这样使用权限:
-
成员变量使用
private
,隐藏细节。 -
构造方法使用
public
,方便创建对象。 -
成员方法使用
public
,方便调用方法。
第四章 代码块
4.1 构造代码块
-
构造代码块:
-
位置:定义在成员位置的代码块{}
-
执行:每次创建对象都会执行构造代码块
-
作用:一般是一个类的多个构造方法重复的代码放到构造代码块中
-
使用场景:
举例:统计当前类一共创建了几个对象
-
public class Teacher {
String name;
int age;
//定义计数器
static int count = 0;
//构造代码块
//构造代码块会在构造方法之前执行,并且每次执行构造方法都会先执行构造代码块
{
System.out.println("构造代码块");
//统计
count++;
}
//无参构造
public Teacher(){
System.out.println("构造方法");
}
//有参构造
public Teacher(String name, int age){
this.name = name;
this.age = age;
}
}
public class Demo02 {
public static void main(String[] args) {
//创建对象
Teacher t1 = new Teacher();
Teacher t2 = new Teacher();
Teacher t3 = new Teacher("柳岩",35);
System.out.println("一共创建过" + Teacher.count +"次");
}
}
4.2 静态代码块(掌握)
-
静态代码块:
-
格式:
//静态代码块 static{ }
-
位置:定义在成员位置,类中方法外。
-
执行:在当前类第一次被使用时,静态代码块会执行。并且静态代码块只会执行一次。
-
使用场景:比如我们加载驱动(驱动需要在第一次使用前加载,只需要加载一次),这种功能就可以放在静态代码块中。
-
格式:
public class Person {
private String name;
private int age;
//静态代码块
static{
System.out.println("静态代码块执行了");
}
}
4.3 局部代码块
- 格式:
定义在方法中:
//局部代码块
{
}
public class Demo03 {
public static void main(String[] args) {
int a = 10;
//局部代码块
//局部代码块的作用就是限制变量的作用域
{
int b = 20;
}
//int a = 30; //在同一个区域不能定义重名变量
//不报错,因为作用域不同
int b = 40;
}
}
4.4 执行顺序演示
public class AAA {
//静态代码块
//静态代码块只会执行一次 并在最开始第一个执行
static{
System.out.println(2);
}
//构造代码块
//在每次执行构造方法之前先执行构造代码块
{
System.out.println(3);
}
//构造方法
public AAA() {
System.out.println(1);
}
}
public class Demo04{
public static void main(String[] args) {
//创建对象
AAA a1 = new AAA();
AAA a2 = new AAA();
}
}
执行结果: 2 3 1 3 1