day03【多态、内部类、包装类】
今日内容
- 多态—>必须掌握
- 实现多态
- 多态时成员访问特点
- 多态的好处和弊端
- 引用类型转换
- 多态的应用场景
- 内部类
- 匿名内部类的使用—>必须掌握
- 引用类型使用小结
- 包装类
第一章 多态
3.1 概述
- 多态是面向对象三大特征之一。
- 多态指对于同一行为,对于不同的对象,具有不同的表现形式。在程序中,表示对同一方法,不同的对象有不同实现。
- 实现多态的前提条件
- 继承或者实现
- 父类引用指向子类对象 eg: Fu f = new Zi();
- 方法的重写(没有重写方法多态是没有意义的)
3.2 实现多态
-
父类的引用指向子类的对象:
- 格式: 父类类型 变量名 = new 子类类型(实参);
- eg: Fu f = new Zi();
-
接口的引用指向实现类的对象:
- 格式: 接口名 变量名 = new 实现类名(实参);
- eg: IA a = new Imp();
-
继承时的多态:
abstract class Animal{
public abstract void eat();
}class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃骨头..."); } } class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼..."); } } public class Test { public static void main(String[] args) { /* 实现多态的三个条件: 1.继承 2.父类的引用指向子类的对象 3.方法的重写 */ // 多态 Animal anl = new Dog(); anl.eat(); anl = new Cat(); anl.eat(); } }
-
实现时的多态:
interface IA{
void show();
}class Imp implements IA{ @Override public void show() { System.out.println("实现类重写的show..."); } } public class Test { public static void main(String[] args) { /* 实现多态的三个条件: 1.实现 2.接口的引用指向实现类的对象 3.方法的重写 */ // 多态 IA a = new Imp(); a.show(); } }
3.3 多态时访问成员的特点
- 1.成员变量: 编译看左边(父类),运行看左边(父类)
- 2.成员方法:
-
非静态成员方法:编译看左边(父类),运行看右边(子类)
-
静态成员方法: 编译看左边(父类),运行看左边(父类)
-
记忆: 除了非静态成员方法编译看左边,运行看右边,其余都看左边
class Fu{
int num = 10;public void method1(){ System.out.println("Fu 非静态方法method1..."); } public static void method2(){ System.out.println("Fu 静态方法method2..."); }
}
class Zi extends Fu{
int num = 20;public void method1(){ System.out.println("Zi 非静态方法method1..."); } public static void method2(){ System.out.println("Zi 静态方法method2..."); }
}
public class Test {
public static void main(String[] args) {
/*
多态时访问成员的特点:
1.成员变量: 编译看左边(父类),运行看左边(父类)
2.成员方法:
非静态成员方法:编译看左边(父类),运行看右边(子类)
静态成员方法: 编译看左边(父类),运行看左边(父类)除了非静态成员方法编译看左边,运行看右边,其余都看左边 */ // 父类的引用指向子类的对象 Fu f = new Zi(); System.out.println(f.num);// 10 f.method1();// Zi 非静态方法method1... f.method2();// Fu 静态方法method2... }
}
-
3.4 多态的几种表现形式
-
普通父类多态
public class Fu{}
public class Zi extends Fu{}
public class Test{
public static void main(String[] args){
Fu f = new Zi();// 多态
}
} -
抽象父类多态
public abstract class Fu{}
public class Zi extends Fu{}
public class Test{
public static void main(String[] args){
Fu f = new Zi();// 多态
}
} -
父接口多态
public interface IA{}
public class Imp implements IA{}
public class Test{
public static void main(String[] args){
IA a = new Imp();// 多态
}
}
3.5 多态的应用场景
-
变量多态 -----> 意义不大
class Animal{
public void eat(){
System.out.println(“吃东西…”);
}
}class Dog extends Animal{ public void eat(){ System.out.println("狗吃骨头..."); } } class Cat extends Animal{ } public class Test { public static void main(String[] args) { // 父类的引用指向子类的对象 Animal anl1 = new Dog();// 变量多态 Animal anl2 = new Cat();// 变量多态 // 使用anl1变量一定要根据多态时成员访问特点去使用 anl1.eat();// 狗吃骨头... } }
-
形参多态----> 常用
-
结论: 参数的类型是父类类型,那么就可以接收该父类类型的对象以及该父类类型的所有子类对象
class Animal{
public void eat(){
System.out.println(“吃东西…”);
}
}class Dog extends Animal{ public void eat(){ System.out.println("狗吃骨头..."); } } class Cat extends Animal{ public void eat(){ System.out.println("猫吃鱼..."); } } public class Test { public static void main(String[] args) { Dog d = new Dog(); method(d);// 狗吃骨头... System.out.println("======"); Cat c = new Cat(); method(c);// 猫吃鱼... } // 定义一个方法,既可以接收Dog对象也可以接收Cat对象,甚至Animal类对象以及Animal类其他子类对象,并且在方法中调用eat方法 // 形参多态: 参数的类型是父类类型,那么就可以接收该父类类型的对象以及该父类类型的所有子类对象 // 实参赋值给形参的时候构成了多态 public static void method(Animal anl){ anl.eat(); } // 需求:定义一个方法,可以接收Dog对象,并且在方法中调用eat方法 /*public static void method1(Dog dog){ dog.eat(); }*/ // 需求:定义一个方法,可以接收Cat对象,并且在方法中调用eat方法 /*public static void method2(Cat cat){ cat.eat(); }*/ // ... }
-
-
返回值多态—> 常用
-
返回值类型为父类类型,那么就可以返回该父类类型的对象,或者该父类类型的子类对象
class Animal {
public void eat() {
System.out.println(“吃东西…”);
}
}class Dog extends Animal {
public void eat() {
System.out.println(“狗吃骨头…”);
}
}class Cat extends Animal {
public void eat() {
System.out.println(“猫吃鱼…”);
}
}public class Test {
public static void main(String[] args) {
// 接收返回值的时候其实就是多态: Animal anl1 = new Dog();
Animal anl1 = method1(“Dog”);
anl1.eat();// 狗吃骨头…System.out.println("====="); // 接收返回值的时候其实就是多态: Animal anl2 = new Cat(); Animal anl2 = method1("Cat"); anl2.eat();// 猫吃鱼... } // 定义一个方法,可以返回Dog对象,也可以返回Cat对象,也可以返回Animal对象,或者Animal类的其他子类对象 // 返回值多态: 返回值类型为父类类型,那么就可以返回该父类类型的对象,或者该父类类型的子类对象 public static Animal method1(String classType) { //return new Dog(); //return new Cat(); //return new Animal(); if ("Dog".equals(classType)) { return new Dog(); } else if ("Cat".equals(classType)) { return new Cat(); } else if ("Animal".equals(classType)) { return new Animal(); } else { return null; } }
}
-
3.6 多态的好处和弊端
- 好处:
- 可以将方法的参数定义为父类引用,使程序编写的更简单,提高程序的灵活性,扩展性
- 案例参考多态的应用场景
- 弊端:
-
无法直接访问子类独有的成员变量和成员方法(因为多态时成员访问特点编译都是看左边(父类))
-
案例:
class Animal {
public void eat() {
System.out.println(“吃东西…”);
}
}class Dog extends Animal { // 独有的成员变量 int num = 10; @Override public void eat() { System.out.println("狗吃骨头..."); } // 独有的成员方法 public void lookHome() { System.out.println("狗正在看家..."); } } public class Test { public static void main(String[] args) { // 多态的弊端: 无法访问子类独有的成员变量和成员方法 // 变量多态 Animal anl1 = new Dog(); // System.out.println(anl1.num);// 编译报错,因为编译看左边,父类没有num成员变量 // anl1.lookHome();// 编译报错,因为编译看左边,父类没有lookHome成员方法 // 返回值多态 Animal anl2 = method1("Dog"); // System.out.println(anl2.num);// 编译报错,因为编译看左边,父类没有num成员变量 // anl2.lookHome();// 编译报错,因为编译看左边,父类没有lookHome成员方法 } // 形参多态 public static void method(Animal anl) { anl.eat(); // anl.lookHome();// 编译报错,因为编译看左边,父类没有lookHome成员方法 } // 返回值多态 public static Animal method1(String classType) { //return new Dog(); //return new Cat(); //return new Animal(); if ("Dog".equals(classType)) { return new Dog(); } else if ("Animal".equals(classType)) { return new Animal(); } else { return null; } } }
-
3.7 引用类型转换
-
为什么要转型
- 为了实现多态
- 为了解决多态的弊端
-
如何转型:
- 向上转型: 父类类型 变量名 = new 子类名(实参);
- 向下转型: 子类类型 变量名 = (子类类型) 父类类型的变量;
- 注意: 父类类型的变量指向的对象一定是左边子类类型的对象,否则会报类型转换异常ClassCastException
-
避免转型异常—>转型判断
- instanceof关键字
- 格式: 变量名 instanceof 数据类型
- 执行:
- 如果变量指向的对象的数据类型是属于后面的数据类型,就返回true,否则返回false
-
案例:
class Animal {
public void eat() {
System.out.println(“吃东西…”);
}
}class Dog extends Animal { // 独有的成员变量 int num = 10; @Override public void eat() { System.out.println("狗吃骨头..."); } // 独有的成员方法 public void lookHome() { System.out.println("狗正在看家..."); } } class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼..."); } } public class Test1 { public static void main(String[] args) { /* 为什么转型 1.为了实现多态 2.为了解决多态的弊端(无法访问子类独有的成员变量和成员方法) 引用类型转换分类 向上转型:子类类型向父类类型转换的过程,称为向上转型。这个过程是默认的。 格式:父类类型 变量名 = new 子类类型() 或 子类对象引用; Dog d = new Dog(); Animal anl = new Dog(); Animal anl = d; 向下转型:父类类型向子类类型转换的过程,称为向下转型,这个过程需要强制执行。 格式:子类类型 变量名 = (子类类型) 父类类型变量; 要求: 父类类型的变量必须指向的是前面子类类型的对象 类型转换判断: 使用instanceof关键字 格式: 变量名 instanceof 数据类型 执行: 如果变量指向的对象是属于后面的数据类型,那就返回true 如果变量指向的对象不属于后面的数据类型,那就返回false */ // 向上转型: 父类类型的引用指向子类类型的对象 Animal anl1 = new Dog(); anl1.eat(); // System.out.println(anl1.num);// 编译报错 // anl1.lookHome();// 编译报错 //Dog d = new Dog(); //Animal anl2 = d; System.out.println("======="); // 改变anl1指向的对象 anl1 = new Cat(); // 向下转型: 子类类型向父类类型转换的过程 if (anl1 instanceof Dog){ Dog dog = (Dog) anl1; System.out.println(dog.num); dog.lookHome(); } } }
第二章 内部类
4.1 内部类的概述
- 概述: 将一个类定义在另一个类的里面,里面的那个类就叫做内部类,外面的那个类就叫做外部类
- 特点: 内部类是一个独立的类,在编译后,有自己独立的class文件,前面冠以:外部类名+$+内部类类名
- 分类:
- 成员内部类
- 匿名内部类
- 局部内部类(自己了解)
4.2 成员内部类
-
概述: 定义在类的成员位置(类中方法外)的类就叫做成员内部类
-
格式:
class 外部类名{
class 内部类名{} }
-
成员访问特点:
-
成员内部类中的成员变量和成员方法在其他类中访问,需要创建成员内部类对象
-
格式: 外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
-
案例:
public class Body {public class Heart{ // 成员变量 int numN = 20; // 成员方法 public void methodN1(){ System.out.println("成员内部类的methodN1方法..."); } } } public class Test { public static void main(String[] args) { // 创建Heart对象 Body.Heart bh = new Body().new Heart(); System.out.println(bh.numN); bh.methodN1(); } }
-
-
注意事项:
-
在成员内部类中,可以直接访问外部类的一切成员,包括外部类的私有成员
-
在外部类中,需要直接访问内部类的成员,需要创建内部类对象来访问
public class Body {
int numW = 10;public void methodW1(){ System.out.println("外部类的methodW1方法..."); } // - 在外部类中,需要直接访问内部类的成员,需要创建内部类对象来访问 public void methodW2(){ System.out.println("外部类的methodW2方法..."); // 创建内部类对象 Heart heart = new Heart(); System.out.println(heart.numN); heart.methodN1(); } public class Heart{ // 成员变量 int numN = 20; // 成员方法 public void methodN1(){ System.out.println("成员内部类的methodN1方法..."); } // - 在成员内部类中,可以直接访问外部类的一切成员,包括外部类的私有成员 public void methodN2(){ System.out.println(numW); methodW1(); } } } public class Test { public static void main(String[] args) { // 创建Heart对象 Body.Heart bh = new Body().new Heart(); System.out.println(bh.numN); bh.methodN1(); bh.methodN2(); System.out.println("======"); new Body().methodW2(); } }
-
-
扩展
class Body {
int num = 10;public class Heart{ // 成员变量 int num = 20; // 成员方法 public void show(){ int num = 30; System.out.println("局部变量num:"+num);// 30 System.out.println("内部类的成员变量num:"+this.num);// 20 System.out.println("外部类的成员变量num:"+Body.this.num);// 10 } } } public class Test { public static void main(String[] args) { // 创建Heart对象 Body.Heart bh = new Body().new Heart(); bh.show(); } }
4.3 匿名内部类(重点)
-
概述
- 本质其实就是一个类的子类对象
- 本质其实就是一个接口的实现类对象
-
格式
new 类名(){
必须重写所有抽象方法
};new 接口名(){ 必须重写所有抽象方法 };
-
使用场景
- 如果想得到一个抽象类的子类对象,那么就可以直接给该类的匿名内部类
- 如果想得到一个接口的实现类对象,那么就可以直接给该接口的匿名内部类
-
案例1:
abstract class Animal{
public abstract void eat();
}public class Test { public static void main(String[] args) { // 需求:调用Animal类的eat方法 /* 以前: 1.创建一个子类继承Animal类,在子类中重写eat方法 2.创建子类对象 3.使用子类对象调用eat方法 现在: 直接创建Animal类的匿名内部类,然后使用该匿名内部类调用eat方法即可 */ // 父类的引用指向子类的对象 Animal anl1 = new Animal() { @Override public void eat() { System.out.println("匿名内部类的eat..."); } }; anl1.eat(); } }
-
案例2:
interface IA{
void show();
}public class Test { public static void main(String[] args) { // 需求:得到IA接口的实现类对象调用show方法 /* 以前: 1.创建一个实现类实现IA接口,重写show方法 2.创建实现类对象 3.使用实现类对象调用show方法 现在: 直接创建IA接口的匿名内部类调用show方法 */ IA a = new IA() { @Override public void show() { System.out.println("匿名内部类的show"); } }; a.show(); System.out.println("====="); new IA() { @Override public void show() { System.out.println("匿名内部类的show"); } }.show(); } }
第三章 引用类型使用小结
5.1 引用类型作为方法参数和返回值
-
引用类型作为方法的参数传递的是地址值
-
引用类型作为方法的返回值返回的是地址值
-
案例
class Person{
String name;public Person(String name) { this.name = name; } public void eat(){ System.out.println("吃饭..."); } } public class Test { public static void main(String[] args) { /* 需求:定义如下类,演示引用类型作为方法的参数和返回值 人类: 属性:姓名 行为:吃饭 在测试类中,定义一个方法,能接收人类对象,并且可以返回一个人类对象 */ Person person = new Person("itcast"); System.out.println(person.name);// itcast // 调用method1方法,传入person对象 Person p1 = method1(person); System.out.println(person.name);// itheima System.out.println(p1 == person);// true } // 引用类型作为方法的参数和返回值 public static Person method1(Person p){ p.name = "itheima"; return p; } }
5.2 引用类型作为成员变量
abstract class Pet{}
interface FaShu{}
class Person{
int age;// 基本类型作为成员变量
String name; // 普通类作为成员变量
Pet pet;// 抽象类作为成员变量
FaShu fs;// 接口作为成员变量
}
public class Test {
public static void main(String[] args) {
// 创建Person对象
Person p = new Person();
// 给p对象的属性赋值
p.age = 18;
p.name = "张三";
p.pet = new Pet() {};// 成员变量类型为抽象类,那么就赋该抽象类的子类对象
p.fs = new FaShu() {};// 成员变量类型为接口,那么就赋该接口的实现类对象
}
}
第四章 包装类
3.13 包装类
-
包装类的概述
- 概述: 为了更好的维护基本类型数据,java为基本类型创建了对应的引用类型,这些类称为包装类
- 分类:
| 基本类型 | 对应的包装类(位于java.lang包中) |
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
-
Integer类的使用---->了解
-
概述: int类型对应包装类,包装一个对象中的原始类型 int 的值
-
构造方法
- public Integer(int value) 根据 int 值创建 Integer 对象(过时)
- public Integer(String s) 根据 String 值创建 Integer 对象(过时)
-
静态方法
- public static Integer valueOf(int i) 返回表示指定的 int 值的 Integer 实例
- public static Integer valueOf(String s) 返回保存指定String值的 Integer 对象
-
代码:
public class Test {
public static void main(String[] args) {
// Integer的构造方法
Integer i1 = new Integer(10);// 表示整数10
Integer i2 = new Integer(“10”);// 表示整数10// Integer的静态方法 Integer i3 = Integer.valueOf(10);// 表示整数10 Integer i4 = Integer.valueOf("10");// 表示整数10 } }
-
-
拆箱和装箱
-
装箱: 把基本类型转换为对应的包装类类型---->包装类的构造方法\valueOf静态方法可以实现
-
拆箱: 把包装类类型转换为对应的基本类型---->包装类的 xx类型Value() 非静态方法实现
-
案例:
public class Test {
public static void main(String[] args) {
// 装箱: 基本类型---->对应的包装类类型
// Integer的构造方法
Integer i1 = new Integer(10);// 表示整数10// Integer的静态方法 Integer i2 = Integer.valueOf(10);// 表示整数10 // 拆箱: 包装类型类型---->基本类型 int num1 = i1.intValue();// 10 int num2 = i2.intValue();// 10 System.out.println(num1 + num2);// 20 } }
-
-
自动装箱和自动拆箱
-
自动装箱: 基本类型自动转换为对应的包装类类型---->直接把基本类型的值赋值给对应的包装类类型变量
-
自动拆箱: 包装类类型自动转换为对应的基本类型---->直接把包装类的对象赋值给对应的基本类型的变量
public class Test {
public static void main(String[] args) {
// - 自动装箱: 基本类型自动转换为对应的包装类类型---->直接把基本类型的值赋值给对应的包装类类型变量
Integer i1 = 10;
Double d = 3.14;// - 自动拆箱: 包装类类型自动转换为对应的基本类型---->直接把包装类的对象赋值给对应的基本类型的变量 int num = i1; double numD = d; System.out.println(num + 10);//20 } }
-
-
基本类型与字符串之间的转换
- 基本类型–>字符串:
-
方式一: 基本类型的数据 + 空的字符串(“”)
-
方式二: 字符串的静态方法public static String valueOf(基本类型的值);
-
案例:
public class Test1 {
public static void main(String[] args) {
// 基本类型–>字符串:
//- 方式一: 基本类型的数据 + 空的字符串(“”)
int num1 = 100;
String str1 = num1 + “”;//- 方式二: 字符串的静态方法public static String valueOf(基本类型的值); int num2 = 200; String str2 = String.valueOf(num2); } }
-
- 字符串–>基本类型:
-
方式一: 通过包装类的静态方法valueOf(String s)得到包装类对象,然后包装类对象自动拆箱为基本类型—>除了Character包装类之外,所有包装类都有这个方法
-
方式二: 通过包装类的静态方法parseXXX类型(String s)得到对应的基本类型—>除了Character包装类之外,所有包装类都有这个方法
public class Test2 {
public static void main(String[] args) {
// 字符串–>基本类型:
// - 方式一: 通过包装类的静态方法valueOf(String s)得到包装类对象,然后包装类对象自动拆箱为基本类型—>除了Character包装类之外,所有包装类都有这个方法
Integer i1 = Integer.valueOf(“10”);
int num1 = i1;// 方式二: 通过包装类的静态方法parseXXX类型(String s)得到对应的基本类型--->除了Character包装类之外,所有包装类都有这个方法 int num2 = Integer.parseInt("20"); System.out.println(num1+","+num2);// 10,20 } }
-
- 基本类型–>字符串:
总结
必须练习:
1.实现多态
2.多态场景下成员访问特点
3.多态的应用场景---->形参多态,返回值多态 重点重点
4.解决多态的弊端--->向下转型,转型判断
5.匿名内部类----> 本质,格式
6.引用类型的使用---->重点
7.自动装箱和自动拆箱
8.基本类型和字符串之间的转换
- 能够说出多态的前提
继承\实现
父类的引用指向子类的对象\接口的引用指向实现类的对象
方法的重写
- 能够写出多态的格式
父类类型 变量名 = 子类对象;
接口类型 变量名 = 实现类对象;
- 能够理解多态向上转型和向下转型
向上转型: 父类类型 变量名 = new 子类类型(实参); 存在的意义就是为了实现多态
向下转型: 子类类型 变量名 = (子类类型)父类类型的变量; 存在的意义就是为了解决多态的弊端
注意: 父类类型的变量一定要指向左边子类类型的对象,否则会报类型转换异常ClassCastException
避免转型异常: 变量名 instanceof 数据类型
- 能够说出内部类概念
一个类中包含另一个类,被包含的类就是内部类
- 能够理解匿名内部类的编写格式
本质: 表示一个类的子类对象,或者一个接口的实现类对象
格式:
new 类名(){重写抽象方法};
new 接口名(){重写抽象方法};
- 能够说出自动装箱、自动拆箱的概念
- 自动装箱: 基本类型自动转换为对应的包装类类型---->直接把基本类型的值赋值给对应的包装类类型变量
- 自动拆箱: 包装类类型自动转换为对应的基本类型---->直接把包装类的对象赋值给对应的基本类型的变量
- 能够将基本类型转换为对应的字符串
基本类型的数据 + 空的字符串("")
- 能够将字符串转换为对应的基本类型
包装类的静态方法: parseXXX类型(String s)