文章目录
1.多态
1.在多态的代码当中,成员变量的访问规则是:
多态 =上转型 //多态和上转型是等价的
成员变量:编译看左边,运行还看左边。(左边和右边指的是等号的左边和等号的右边)
成员方法:编译看左边,运行看右边。(左边和右边指的是等号的左边和等号的右边)
成员变量:编译看左边,运行还看左边。
Fu obj = new Zi()为例:编译的时候看等号左边“Fu obj”所以obj只能调用父类有的变量,obj.num是父类有的变量,但是obj.age就不行因为父类没有这个变量
运行的时候obj.num结果是多少呢?你要看的还是等号左边父类的值
public class Fu {
int num = 10;
public void showNum() {
System.out.println(num);
}
}
public class Zi extends Fu {
int num = 20;
int age = 16;
@Override
public void showNum() {
System.out.println(num);
}
}
public class Demo01MultiField {
public static void main(String[] args) {
Fu obj = new Zi();
System.out.println(obj.num); //10
System.out.println(obj.age); //错误!
//编译的时候看左“Fu obj”所以obj只能调用父类有的变量,obj.num是父类有的变量,但是obj.age就不行因为父类没有这个变量。运行的时候obj.num结果是多少呢?你要看的还是左边父类的值
obj.showNum();//20
//涉及到成员方法的访问规则,见下面。
}
}
2.在多态的代码当中,成员方法的访问规则是:
成员变量:编译看左边,运行还看左边。(左边和右边指的是等号的左边和等号的右边)
成员方法:编译看左边,运行看右边。(左边和右边指的是等号的左边和等号的右边)
成员方法:编译看左边,运行看右边。
Fu obj = new Zi()为例:编译的时候看左“Fu obj”所以obj只能调用父类有的方法和变量,obj.method()和obj.methodFu()都是父类有的方法,但是obj.methodZi()就不行因为父类没有这个方法
运行的时候obj.method()和obj.methodFu()结果是多少呢?你要看右边也就是子类
public class Fu {
int num = 10;
public void showNum() {
System.out.println(num);
}
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
public class Zi extends Fu {
int num = 20;
int age = 16;
@Override
public void showNum() {
System.out.println(num);
}
@Override
public void method() {
System.out.println("子类方法");
}
public void methodZi() {
System.out.println("子类特有方法");
}
}
public class Demo02MultiMethod {
public static void main(String[] args) {
Fu obj = new Zi(); // 多态
obj.method();
obj.methodFu();
obj.methodZi(); // 错误写法!
//Fu obj = new Zi()为例:编译的时候看左“Fu obj”所以obj只能调用父类有的方法和变量,obj.method()和obj.methodFu()都是父类有的方法,但是obj.methodZi()就不行因为父类没有这个方法
//运行的时候obj.method()和obj.methodFu()结果是多少呢?你要看右边也就是子类
}
}
3.多态和继承
4.上转型和下转型
Animal animal = new Cat(); // 上转型,new Cat()就是cat的一个对象,现在把cat的一个对象new Cat()当作了Animal
Cat cat = (Cat) animal;//下转型,(Cat) animal就是把本来的猫的animal还原为Cat
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
// 子类特有方法
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃SHIT");
}
public void watchHouse() {
System.out.println("狗看家");
}
}
public class Demo01Main {
public static void main(String[] args) {
。
Animal animal = new Cat(); // 上转型
animal.eat(); // 猫吃鱼
animal.catchMouse(); // 错误写法!
//对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。编译看左运行看右嘛,左侧animal没有catchMouse()方法
// 向下转型,进行“还原”动作,本来就是猫才能还原为猫,现在不把猫当作animal看待了
Cat cat = (Cat) animal;
cat.catchMouse(); // 猫抓老鼠
Dog dog = (Dog) animal; 错误的向下转型,下转型不是随便转的,它必须是一个“还原”的动作
// animal=new Cat() 本来new的时候这个animal是一只猫,现在非要当做狗
}
}
5.instanceof的使用
如何知道这个animal是狗转过来的呢还是猫转过来的呢?用instanceof
对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
// 子类特有方法
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃SHIT");
}
public void watchHouse() {
System.out.println("狗看家");
}
}
public class Demo02Instanceof {
public static void main(String[] args) {
Animal animal = new Dog(); // 本来是一只狗
animal.eat(); // 狗吃SHIT
// 如果希望掉用子类特有方法,需要向下转型
// 判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
// 判断一下animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
giveMeAPet(new Dog());
}
public static void giveMeAPet(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
}
6.笔记本案例
public interface USB {
public abstract void open(); // 打开设备
public abstract void close(); // 关闭设备
}
// 鼠标就是一个USB设备
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("鼠标点击");
}
}
// 键盘就是一个USB设备
public class Keyboard implements USB {
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
public void type() {
System.out.println("键盘输入");
}
}
public class Computer {
public void powerOn() {
System.out.println("笔记本电脑开机");
}
public void powerOff() {
System.out.println("笔记本电脑关机");
}
// 使用USB设备的方法,使用接口作为方法的参数
public void useDevice(USB usb) {
usb.open(); // 打开设备
if (usb instanceof Mouse) {
// 一定要先判断
Mouse mouse = (Mouse) usb; // 向下转型
mouse.click();
} else if (usb instanceof Keyboard) {
// 先判断
Keyboard keyboard = (Keyboard) usb; // 向下转型
keyboard.type();
}
usb.close(); // 关闭设备
}
}
public class DemoMain {
public static void main(String[] args) {
// 首先创建一个笔记本电脑
Computer computer = new Computer();
computer.powerOn();
// 首先进行向上转型
USB usbMouse = new Mouse(); // 多态写法
// 参数是USB类型,我正好传递进去的就是USB鼠标
computer.useDevice(usbMouse);
// 创建一个USB键盘
Keyboard keyboard = new Keyboard(); // 没有使用多态写法
// 方法参数是USB类型,传递进去的是实现类对象
computer.useDevice(keyboard); // 正确写法!也发生了向上转型
// 使用子类对象,匿名对象,也可以
// computer.useDevice(new Keyboard()); // 也是正确写法
computer.powerOff();
}
}
我要说的一点事:
public class DemoMain {
public static void main(String[] args) {
method(10.0); // 正确写法,double --> double
method(20); // 正确写法,int --> double
}
public static void method(double num) {
System.out.println(num);
}
}
/*
method(方法要求传参的是一个double但你传进来了int型,不报错,因为int自动转为double是安全的)
同样的,在上面的USB实例中,useDevice(USB usb)要求传参的是一个USB类型的,而你computer.useDevice(new Keyboard())却传参的是keyboard的对象,
这也没关系,因为此时keyboard自动转为USB属于上传型也是安全的
*/
2.final关键字
final关键字代表最终、不可改变的,而且没有子孙、不能被继承。
常见四种用法:
- 可以用来修饰一个类
- 可以用来修饰一个方法
- 还可以用来修饰一个局部变量
- 还可以用来修饰一个成员变量
1.final用来修饰一个类
当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
// ...
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子。)
public final class MyClass /*extends Object*/{
//我们说过任何类都是Object的子类,当让包括final类
public void method() {
System.out.println("方法执行!");
}
}
// 不能使用一个final类来作为父类
public class MySubClass /*extends MyClass*/ {
}
2.final用来修饰一个方法
当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符 final 返回值类型 方法名称(参数列表) {
// 方法体
}
public abstract class Fu {
public final void method() {
System.out.println("父类方法执行!");
}
public abstract /*final*/ void methodAbs() ;
//对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。
}
public class Zi extends Fu {
@Override
public void methodAbs() {
}
// 错误写法!不能覆盖重写父类当中final的方法
// @Override
// public void method() {
// System.out.println("子类覆盖重写父类的方法!");
// }
}
3.final用来修饰一个局部变量
// 一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。
// “一次赋值,终生不变”
final int num2 = 200;
num2 = 250; // 错误写法!不能改变!
// 对于基本类型来说,不可变说的是变量当中的数据不可改变
// 对于引用类型来说,不可变说的是变量当中的地址值不可改变
public class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo01Final {
public static void main(String[] args) {
final Student stu2 = new Student("高圆圆");
stu2 = new Student("赵又廷");// 错误写法!final的引用类型变量,其中的地址不可改变
stu2.setName("高圆圆圆圆圆圆"); //这是可以的,并没有改变地址值
}
}
4.final用来修饰一个成员变量
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。
- 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
- 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
①直接赋值
public class Person {
private final String name = "鹿晗";
//你一旦直接赋值后下面试图改变name的setName(String name)\Person(String name)都会报错
public Person() {
}
public Person(String name) {
this.name = name; //报错,因为你在修改name值
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;//报错,因为你在修改name值
}
}
②构造方法赋值
使用构造方法赋值时必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
//下面有两个构造方法,那么这两个构造方法必须都有对final name赋值的语句,这样才会不论你调用哪个构造方法final name都有初值
public class Person {
private final String name;
public Person() {
name = "关晓彤";
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;//报错,因为你在修改name值
}
}
3.四种权限修饰符
Java中有四种权限修饰符:
public > protected > (default) > private
同一个类(我自己) YES YES YES YES
同一个包(我邻居) YES YES YES NO
同一包子类(亲儿子) YES YES YES NO
不同包子类(私生子) YES YES NO NO
不同包非子类(陌生人) YES NO NO NO
注意事项:(default)并不是关键字“default”,而是根本不写,你写上default反而会报错。
在同一个包里,不能访问private
什么叫做不同包子类?
注意事项:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1. 外部类:public / (default)
2. 成员内部类:public / protected / (default) / private
3. 局部内部类:什么都不能写
class Outer {
//外部类要么public要么(default)
public void methodOuter() {
class Inner {
// 局部内部类,什么都不写
}
}
public class Inner2 {
// 成员内部类public / protected / (default) / private
System.out.println("成员内部类");
}
}
4.内部类
内部类好比:身体和心脏的关系。汽车和发动机的关系。
分类:
- 成员内部类
- 局部内部类(包含匿名内部类)
1.成员内部类
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// ...
}
// ...
}
注意:内用外,随意访问;外用内,需要内部类对象。
//也就是说内部随意访问外面的东西,但外面不能随意访问内部的
==========================
如何使用成员内部类?有两种方式:
1. 间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
2. 直接方式,公式:
类名称 对象名 = new 类名称();
【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】
public class Body {
// 外部类
public class Heart {
// 成员内部类
// 内部类的方法
public void beat() {
System.out.println("心脏跳动:蹦蹦蹦!");
System.out.println("我叫:" + name); // 正确写法!
}
}
// 外部类的成员变量
private String name;//尽管是private但可以被内部类访问
// 外部类的方法
public void methodBody() {
System.out.println("外部类的方法");
new Heart().beat(); //外用内,需要创建内部类对象。
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo01InnerClass {
public static void main(String[] args) {
Body body = new Body(); // 外部类的对象
// 通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
body.methodBody();
// 【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
2.成员内部类重名问题
// 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
public class Outer {
int num = 10; // 外部类的成员变量
public class Inner /*extends Object*/ {
int num = 20; // 内部类的成员变量
public void methodInner() {
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
System.out.println(super.num);//错误,内部类和外部类可不是继承关系
System.out.println(Outer.this.num); // 【外部类名称.this.外部类成员变量名】10
}
}
}
public class Demo02InnerClass {
public static void main(String[] args) {
// 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
Outer.Inner obj = new Outer().new Inner();
obj.methodInner();
}
}
3.局部内部类。
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表) {
class 局部内部类名称 {
// ...
}
}
}
“局部”性:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
class Outer {
public void methodOuter() {
class Inner {
// 局部内部类
int num = 10;
public void methodInner() {
System.out.println(num); // 10
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
public class DemoMain {
public static void main(String[] args) {
Outer obj = new Outer();
obj.methodOuter();
}
}
4.局部内部类的注意事项。
/*
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【final类型的】,final可以省略。
*/
public class MyOuter {
public void methodOuter() {
int num = 10; // 因为局部内部类访问了它,它事实上是final类型,不能再次赋值的。
class MyInner {
public void methodInner() {
System.out.println(num);
}
}
}
}
5.匿名内部类(本节内容最最重要的知识点!!)
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。
匿名内部类的定义格式:
接口名称 对象名 = new 接口名称() {
// 覆盖重写所有抽象方法
};
另外还要注意几点问题:
- 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。 - 匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。 - 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!
public interface MyInterface {
void method1(); // 抽象方法
void method2();
}
public class MyInterfaceImpl implements MyInterface {
@Override
public void method1() {
System.out.println("实现类覆盖重写了方法!111");
}
@Override
public void method2() {
System.out.println("实现类覆盖重写了方法!222");
}
}
public class DemoMain {
public static void main(String[] args) {
//方法一,传统的上转型方法,适合多次调用的,匿名只适合使用一次的
MyInterface obj = new MyInterfaceImpl();
obj.method1();
//这是错误写法
MyInterface obj1 = new MyInterface(); // 错误写法!接口可不能直接new对象
// 使用匿名内部类,但不是匿名对象,对象名称就叫objA
MyInterface objA = new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-A");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-A");
}
};
objA.method1();
objA.method2();
System.out.println("=================");
// 使用了匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method1();
// 因为匿名对象无法调用第二次方法,所以要使用method2()需要再创建一个匿名内部类的匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method2();
}
}
6.实例
public interface Skill {
void use(); // 释放技能的抽象方法
}
public class SkillImpl implements Skill {
@Override
public void use() {
System.out.println("Biu~biu~biu~");
}
}
public class Hero {
private String name; // 英雄的名称
private Skill skill; // 英雄的技能
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
public void attack() {
System.out.println("我叫" + name + ",开始施放技能:");
skill.use(); // 调用接口中的抽象方法
System.out.println("施放技能完成。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Skill getSkill() {
return skill;
}
public void setSkill(Skill skill) {
this.skill = skill;
}
}
public class DemoGame {
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("艾希"); // 设置英雄的名称
// 方法一:设置英雄技能
hero.setSkill(new SkillImpl()); // 使用单独定义的实现类
//方法二:假装SkillImpl不存在的情况下,还可以改成使用匿名内部类
Skill skill = new Skill() {
@Override
public void use() {
System.out.println("Pia~pia~pia~");
}
};
hero.setSkill(skill);
// 方法三:进一步简化,同时使用匿名内部类和匿名对象
hero.setSkill(new Skill() {
@Override
public void use() {
System.out.println("Biu~Pia~Biu~Pia~");
}
});
hero.attack();
}
}
5.发红包案例
p212—p214(day11_18-----day11_20)