继承
继承主要解决的问题是:共性抽取
继承关系的特点:
- 子类可以拥有父类的“内容”
- 子类还可以拥有自己专有的内容
在继承的关系中,“子类就是一个父类”。关系:is-a
继承格式
//定义父类的格式:(一个普通的类定义)
public class 父类名称 {
//......
}
// 定义子类的格式:
public class 子类名称 extends 父类名称 {
//.......
}
继承中成员变量的访问特点
在父子类的继承关系中,如果成员变量重名,则创建子类对象时,有两种访问方式:
- 直接通过子类对象访问成员变量
等号左边是谁,就优先用谁,没有再向上找 - 间接通过成员方法访问成员变量
该方法属于谁,就优先用谁,没有则向上找
//定义父类
public class Employee {
int num = 10;
public void methodEmployee() {
System.out.println(num);
}
}
//定义子类
public class Teacher extends Employee {
int num = 20;
public void methodTeacher() {
System.out.println(num);
}
}
public class Demo01Extends {
public static void main(String[] args) {
Employee employee = new Employee();
System.out.println(employee.num); //10
Teacher teacher = new Teacher();
System.out.println(teacher.num); //20
//这个方法是子类的,优先使用子类的,没有再向上找
teacher.methodTeacher(); //20
//这个方法是父类中定义的
teacher.methodEmployee(); //10
}
}
区分子类方法中的三种变量重名
局部变量:直接写
子类的成员变量:this.成员变量
父类的成员变量:super.成员变量
public class Father {
int num = 10;
}
public class Son extends Father {
int num = 20;
public void method(){
int num = 30;
System.out.println(num); //30,局部变量
System.out.println(this.num); //20,本类的成员变量
System.out.println(super.num); //10,父类的成员变量
}
}
public class Demo01ExtendsField {
public static void main(String[] args) {
Son son = new Son();
son.method(); //30 20 10
}
}
继承中成员方法的访问特点
在父子类的继承关系中,创建子类对象,访问成员方法的规则:创建的对象是谁,就优先访问谁的方法,如果没有则向上找
注意:无论是成员方法或者成员变量,若没有都是向上找父类
public class Father {
public void methodFather() {
System.out.println("父类方法执行");
}
public void method(){
System.out.println("父类重名方法执行");
}
}
public class Son extends Father{
public void methodSon(){
System.out.println("子类方法执行");
}
public void method(){
System.out.println("子类重名方法执行");
}
}
public class Demo02ExtendsField {
public static void main(String[] args) {
Son son = new Son();
son.methodSon(); //子类方法执行
son.methodFather(); //父类方法执行
//创建的是子类对象,所以优先使用子类方法
son.method(); //子类重名方法执行
}
}
继承中方法的覆盖重写
重写(override):发生在继承关系中,方法的名称一样,参数列表也一样。发生在继承关系中(也称为覆盖、覆写)
重载(overload):方法的名称一样,参数列表不一样
方法的重写特点:创建的是子类对象,则优先使用子类方法
注意:
- 必须保证父子类之间的方法名称相同,参数列表也相同
@Override 可以选择写在方法前面,用来检测是不是有效的正确重写(以防不小心重载而非覆写了该方法) - 子类方法的返回值必须 <= 父类方法的返回值范围
提示:java.lang.Object类是所有类的公共最高父类,所以比如 java.lang.String 就是Object 的子类 - 子类方法的权限必须 >= 父类方法的权限修饰符
提示:public > protected > (default)> private(其中default 不是关键字,而是什么都不写,留空)
public class Phone {
public void call() {
System.out.println("打电话");
}
public void send() {
System.out.println("发短信");
}
public void show() {
System.out.println("显示号码");
}
}
public class newPhone extends Phone {
@Override
public void show(){
super.show(); //把父类的show()方法拿来复用
System.out.println("显示姓名");
System.out.println("显示头像");
}
}
继承中构造方法的访问特点
- 子类构造方法当中,有一个默认隐含的 “super();” 调用,所以先调用父类构造方法,后执行的子类构造方法
- 子类构造可以通过super关键字来调用父类重载构造
- super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造
- 子类必须调用父类构造方法,不写则自动调用super(),写了则用指定的super调用
super关键字的三种用法
- 在子类的成员方法中,访问父类的成员变量
- 在子类的成员方法中,访问父类的成员方法
- 在子类的构造方法中,访问父类的构造方法
public class Father {
int num =10;
public void method() {
System.out.println("父类方法");
}
}
public class Son extends Father {
int num = 20;
public Son(){
super(); //访问父类构造器,其实如果不写程序也会默认调用
}
public void methodSon(){
System.out.println(super.num); //父类中的num
}
public void method(){
super.method(); //父类的method()方法
System.out.println("子类方法");
}
}
this关键字的三种用法
super关键字用来访问父类内容,而this关键字用来访问本类内容
- 在本类的成员方法中,访问本类的成员变量
- 在本类的成员方法中,访问本类的另一个成员方法
- 在本类的构造方法中,访问本类的另一个构造方法(注意:this(…)调用也必须是构造方法的第一个语句,且是唯一一个)
注意:super和this两种构造调用,不能同时使用
public class Father {
int num = 30;
}
public class Son extends Father {
int num = 10;
public Son(){
this(123); //构造方法的重载调用。本类的无参构造,调用本类的有参构造
//this(1,2); //错误写法,必须是第一个语句,只能调用一个构造方法
}
public Son(int n){
}
public Son(int n, int m){
}
public void showNum(){
int num = 20;
System.out.println(num); //局部变量,20
System.out.println(this.num); //成员变量,10
System.out.println(super.num); //父类成员变量,30
}
public void methodA(){
System.out.println("AAA");
}
public void methodB(){
this.methodA(); //调用本类methodA()方法
System.out.println("BBB");
}
}
下面是一个继承关系中super与this的内存图:
Java中继承的特点
- Java是单继承的。即一个类的直接父类只能有唯一一个
- Java可以多级继承(Java中所有类都继承自java.lang.Object类)
- 一个子类的直接父类是唯一的,但一个父类可以拥有多个子类。
抽象类
-
抽象类概念:如果一个类中没有包含足够的信息来描绘一个具体的对象,即如果父类当中的方法不确实如何进行方法体{}的具体实现,这样的类就是抽象类。
-
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
-
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
-
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
抽象方法和抽象类的格式
抽象方法:加上abstract关键字,然后去掉{},直接分号结束
抽象类:抽象方法所在的类,必须是抽象类,(在class之前直接加上abstract即可)
抽象方法和抽象类的使用
- 抽象类不能实例化对象
- 所以用一个子类继承抽象父类类,才能被使用。
- 子类必须覆盖重写抽象父类当中所有的抽象方法
覆盖重写(实现):子类去掉父类的abstract关键字,然后补上方法体{} - 创建子类对象进行使用
//抽象类
public abstract class Animal {
//抽象方法
public abstract void eat();
public void normalMethod() { }
}
public class Cat extends Animal {
@Override
public void eat(){
System.out.println("猫吃鱼");
}
}
public class Demo01Abstract {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat(); //猫吃鱼
}
}
注意:
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
(子类的构造方法中,有默认的super(),需要访问父类构造方法) - 抽象类中不一定包含抽象方法,但有抽象方法的类必须是抽象类
(没有抽象方法的抽象类也不能直接创建对象,在一些特殊场景下有用途,比如设计模式中的适配器模式) - 抽象类的子类,必须重写抽象父类中所有的抽象方法,除非该子类也是抽象类
//2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
public abstract class Animal {
public abstract void eat();
public Animal(){
System.out.println("抽象父类构造方法执行");
}
}
public class Cat extends Animal {
public Cat(){
//super(); //程序自动执行
System.out.println("子类构造方法执行");
}
@Override
public void eat(){
System.out.println("吃鱼");
}
}
public class Demo01Abstract {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
}
}
//Output:
//抽象父类构造方法执行
//子类构造方法执行
//吃鱼
//4. 抽象类的子类,必须重写抽象父类中**所有的**抽象方法,除非该子类也是抽象类
public abstract class Animal {
public abstract void eat();
public abstract void sleep();
}
public abstract class Dog extends Animal {
@Override
public void eat(){
System.out.println("狗吃骨头");
}
}
public class DogGolden extends Dog {
@Override
public void sleep(){
System.out.println("呼呼呼...");
}
}
public class Demo02Main {
public static void main(String[] args) {
DogGolden dog = new DogGolden();
dog.eat(); //狗吃骨头
dog.sleep(); //呼呼呼...
}
}
接口
接口就是多个类的公共规范,是一种引用数据类型
接口最重要的内容就是其中的抽象方法
接口的定义:
public interface 接口名称 { //接口内容 }
(备注:使用关键字 interface 后,编译生成的字节码文件依然为 .java → .class)
接口中可以包含的内容有
- 常量
- 抽象方法
- 默认方法(Java 8)
- 静态方法(Java 8)
- 私有方法(Java 9)
接口的抽象方法
在任何版本的Java中,接口都可以定义抽象方法
注意: 接口中的抽象方法,修饰符必须是两个固定关键字:public,abstract(这两个修饰符可以选择性省略),方法的三要素可以随意定义
接口使用步骤:
- 接口不能直接使用,必须有一个“实现类”来“实现”该接口
public class 实现类名称 implements 接口名称 { //... }
- 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法(去掉 abstract 关键字,加上方法体大括号{})
- 创建实现类的对象,进行使用
注意:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自身必须是抽象类
public interface MyInterfaceAbstract {
public abstract void methodAbs1(); //这是一个抽象方法
//abstract void methodAbs2(); //这也是抽象方法
//public void methodAbs3(); //这也是抽象方法
//void methodAbs4(); //这也是抽象方法
}
//实现类
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
@Override
public void methodAbs1() {
System.out.println("这是一个方法");
}
}
public class Demo01Interface {
public static void main(String[] args) {
//MyInterfaceAbstract inter = new MyInterfaceAbstract(); //错误!不能直接new接口对象使用
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
impl.methodAbs1();
}
}
接口的默认方法
从 Java 8 开始,接口中允许定义默认方法
格式:
public default 返回值类型 方法名称 (参数列表) { //方法体 }
默认方法必须为public方法 (public可省略,default不可省略)
备注:接口当中的默认方法,可以解决接口升级的问题
注意:
- 接口的默认方法,可以通过接口实现类对象,直接调用
- 接口的默认方法,也可以被接口实现类进行覆盖重写
- 接口中的默认方法会被实现类继承
public interface MyInterfaceDefault {
//抽象方法
public abstract void methodAbs();
//新添加了一个抽象方法
//public abstract void methodAbs2(); //此时所有接口的实现类都必须覆盖重写此抽象方法,不合理
//新添加的方法,改为默认方法
public default void methodDefault() {
System.out.println("这是新添加的默认方法");
}
}
public class MyInterfaceDefaultA implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,AAA");
}
}
public class MyInterfaceDefaultB implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,BBB");
}
@Override
public void methodDefault() {
System.out.println("实现类B覆盖重写了接口的默认方法");
}
}
public class Demo02Interface {
public static void main(String[] args) {
//创建实现类对象
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
//调用抽象方法,实际运行的是右侧实现类
a.methodAbs(); //OutPut:实现了抽象方法,AAA
//调用默认方法,如果实现类d当中没有,会向上找接口的
a.methodDefault(); //OutPut:这是新添加的默认方法
MyInterfaceDefaultB b = new MyInterfaceDefaultB();
b.methodAbs(); //OutPut:实现了抽象方法,BBB
b.methodDefault(); //OutPut:实现类B覆盖重写了接口的默认方法
}
}
接口的静态方法
从 Java 8 开始,接口中允许定义静态方法
格式:
public static 返回值类型 方法名称 (参数列表) { //方法体 }
( public 可省略)
注意:
- 不能通过接口实现类的对象来调用接口中的静态方法(一个类可以实现多个接口,多个接口中静态方法可能产生冲突)
- 通过接口名称,直接调用其中的静态方法:
接口名称.静态方法名()
public interface MyInterfaceStatic {
public static void methodStatic(){
System.out.println("这是接口的静态方法");
}
}
/*public class MyInterfaceStaticImpl implements MyInterfaceStatic {
}*/
public class Demo03Interface {
public static void main(String[] args) {
//MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
//错误写法
//impl.methodStatic();
MyInterfaceStatic.methodStatic();
}
}
接口的私有方法
(如接口中多个方法具有大量重复内容,我们需要抽取一个共有方法,来解决两个方法之间重复代码的问题。但这个共有方法,不能被实现类使用,应该为私有化的)
从 Java 9 开始,接口中允许定义私有方法:
- 普通私有方法(解决多个默认方法之间重复代码问题)
private 返回值类型 方法名称 (参数列表) { //方法体 }
- 静态私有方法(解决多个静态方法之间重复代码问题)
private static 返回值类型 方法名称 (参数列表) { //方法体 }
private 方法只有接口自己才能调用,不能被实现类或他人使用
public class MyInterfacePrivateB {
public static void methodStatic1(){
System.out.println("静态方法1");
methodStaticCommon();
}
public static void methodStatic2(){
System.out.println("静态方法2");
methodStaticCommon();
}
//只能被methodStatic1()和methodStatic2()访问
private static void methodStaticCommon(){
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}
}
public class Demo04Interface {
public static void main(String[] args) {
MyInterfacePrivateB.methodStatic1();
MyInterfacePrivateB.methodStatic2();
//MyInterfacePrivateB.methodStaticCommon(); // 错误写法
}
}
接口的常量
接口中可以定义“成员变量”,但是必须使用 public static final 三个关键字进行修饰。从效果上看,其实就是接口的“常量”
格式:
public static final 数据类型 常量名称 = 数据值;
注意:
- 接口当中的常量,可以省略 public static final,但性质不变
- 接口当中的常量,必须进行赋值
- 接口中常量的名称,使用完全大写的字母,并用下划线进行分隔(推荐命名规则)
public interface MyInterfaceConst {
//其实就是常量,赋值后不可修改
public static final int NUM_OF_MY_CLASS = 10;
}
public class Demo05Interface {
public static void main(String[] args) {
System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS);
}
}
多接口实现
- 接口没有静态代码块或构造方法
- 一个类的直接父类是唯一的,但一个类可以同时实现多个接口
public class 实现类名称 implements 接口1, 接口2 { //覆盖重写抽象方法 }
- 若实现类没有覆盖重写所有接口中的所有抽象方法,则实现类必须是抽象类
- 若实现类所实现的多个接口中,存在重复的抽象方法,则只需要覆盖重写一次即可
- 若实现类实现的多个接口中,存在重复的默认方法,则实现类一定要对冲突的默认方法进行覆盖重写
- 一个类如果直接父类当中的方法,和接口当中的默认方法产生冲突,会优先使用父类中的方法
/* 上述 1-5 */
public interface MyInterfaceA {
/* 错误写法,接口没有静态代码块
static {
}*/
/* 错误写法,接口没有构造方法
public MyInterface(){
}*/
public abstract void methodA();
public abstract void methodAbs();
public default void methodDefault(){
System.out.println("AAA");
}
}
public interface MyInterfaceB {
public abstract void methodB();
public abstract void methodAbs();
public default void methodDefault(){
System.out.println("BBB ");
}
}
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
@Override
public void methodA() {
System.out.println("覆盖重写A方法");
}
@Override
public void methodB() {
System.out.println("覆盖重写B方法");
}
@Override
public void methodAbs() {
System.out.println("覆盖重写了AB接口都有的抽象方法");
}
@Override
public void methodDefault() {
System.out.println("对多个接口中冲突的默认方法进行覆盖重写");
}
}
//上述 6. 继承的优先级高于接口的优先级
public class Father {
public void method(){
System.out.println("父类方法");
}
}
public interface MyInterface {
public default void method(){
System.out.println("接口的默认方法");
}
}
public class Son extends Father implements MyInterface {
public static void main(String[] args) {
Son son = new Son();
son.method(); //父类方法
}
}
接口之间的多继承
- 类与类之间是单继承的
- 类与接口之间是多实现的
- 接口与接口时间是多继承的
注意:
- 多个父接口当中的抽象方法重复,无影响
- 多个父接口当中的默认方法重复,则子接口必须进行默认方法的覆盖重写,必须加上default关键字
public interface MyInterfaceA {
public abstract void methodA();
public abstract void methodCommon();
public default void methodDefault() {
System.out.println("AAA");
}
}
public interface MyInterfaceB {
public abstract void methodB();
public abstract void methodCommon();
public default void methodDefault() {
System.out.println("BBB");
}
}
//这个子接口中有五个方法:
//methodA(),来源于接口A
// methodB(),来源于接口B
// methodCommon(),同时来源于接口A和接口B
// method(),来源于接口本身
//methodDefault(),同时来源于接口A和接口B,且为静态方法有冲突,必须覆盖重写
public interface MyInterface extends MyInterfaceA, MyInterfaceB {
public abstract void method();
@Override
public default void methodDefault() {
System.out.println("默认方法覆盖重写");
}
}
多态
一个对象拥有多种形态,这就是对象的多态性
extends 继承或者 implements 实现,是多态性的前提
代码当中体现多态性:父类引用指向子类对象
格式:父类名称 对象名 = new 子类名称();
或接口名称 对象名 = new 实现类名称();
即:右侧子类对象被当做父类进行使用
public class Father {
public void method(){
System.out.println("父类方法");
}
public void methodFaher(){
System.out.println("父类特有方法");
}
}
public class Son extends Father {
@Override
public void method(){
System.out.println("子类名称");
}
}
public class Demo01Multi {
public static void main(String[] args) {
//左侧父类的引用,指向右侧子类的对象
Father obj = new Son();
obj.method(); //Output:子类名称
obj.methodFaher(); //Output:父类特有方法
}
}
多态中成员变量的访问特点
访问成员变量的两种方法:
- 直接通过对象名称访问成员变量:等号左边是谁,优先用谁,没有则向上找
- 间接通过成员方法访问:看该方法属于谁,优先用谁,没有则向上找
(子类覆盖重写,则访问子类方法;子类没有覆盖重写,则访问父类方法)
编译看左边,运行也看左边
public class Father {
int num = 10;
public void showNum1(){
System.out.println(num);
}
public void showNum2(){
System.out.println(num);
}
}
public class Son extends Father {
int num = 20;
int age = 18;
@Override
public void showNum2(){
System.out.println(num);
}
}
public class Demo02MultiField {
public static void main(String[] args) {
Father obj = new Son();
System.out.println(obj.num); //Output:10
//System.out.println(obj.age); //错误写法,不能访问到obj.num
obj.showNum1(); //Output:10,子类没有覆盖重写,访问父类的方法
obj.showNum2(); //Output:20,子类覆盖重写,则访问子类的方法
}
}
多态中成员方法的访问特点
多态中成员方法的访问规则:new的是谁,就优先访问谁的方法,没有则向上找
编译看左边,运行看右边
public class Father {
public void method(){
System.out.println("父类方法");
}
public void methodFather(){
System.out.println("父类特有方法");
}
}
public class Son extends Father {
@Override
public void method(){
System.out.println("子类方法");
}
public void methodSon(){
System.out.println("子类特有方法");
}
}
public class Demo03Multi {
public static void main(String[] args) {
Father obj = new Son();
//编译看左边,父类中有method()方法,所以编译通过。运行看右边,子类中有method()方法,所以优先访问子类method()方法
obj.method(); //Output:子类方法
//编译看左边,父类中有methodFather()方法,所以编译通过。运行看右边,子类中没有methodFather()方法,则向上找,父类有则访问父类methodFather()方法
obj.methodFather(); //Output:父类特有方法
//编译看左边,父类中没有methodSon()方法,所以编译报错
//obj.methodSon(); //错误写法
}
}
使用多态的好处
若现在只需要调用 work() 方法,而对其他功能(比如具体哪个对的work方法)不关心
//若不使用多态
Teacher one = new Teacher();
one.work(); //讲课
Assistant two = new Assistant();
two.work(); //辅导
//若使用多态
Employee one = new Employee();
one.work(); //讲课
Employee two = new Employee();
two.work(); //辅导
所以使用多态的好处是:无论new的是哪个子类对象,等号左边的引用方法都不会改变
对象的向上转型
向下转型即为多态
父类名称 对象名 = new 子类名称();
创建子类对象,把它当做父类看待使用
向上转型一定是安全的。 (从小范围转换为大范围)
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 Demo04Main {
public static void main(String[] args) {
Animal animal = new Cat(); //创建一只猫,把它当做动物看待
animal.eat();
//animal.catchMouse(); //错误写法
}
}
弊端:对象一旦向上转型为父类,就无法调用子类原本特有的内容
解决方案:对象的向下转型进行“还原”
对象的向下转型
子类名称 对象名 = (子类名称) 父类对象
将父类对象还原为本来的子类对象(“还原”是指:本来创建的就为该子类对象)
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("狗吃骨头");
}
//子类特有方法
public void watchHouse(){
System.out.println("狗看家");
}
}
public class Demo04Main {
public static void main(String[] args) {
Animal animal = new Cat();
//向下转型
((Cat) animal).catchMouse(); //Output:猫捉老鼠哦
//向下转型
Cat cat = (Cat)animal;
cat.catchMouse(); //Output:猫捉老鼠哦
//错误的向下转型,animal本身为Cat对象
//Dog dog = (Dog)animal; //编译不会报错,但运行会出现类转换异常java.lang.ClassCastException
}
}
为防止向下类型转换错误,确保父类引用的对象本身为什么对象,采用 instanceof 关键字
对象名 instanceof 类型
:得到一个boolean值,判断前面的对象能否当做后面类型的实例
public class Demo04Instanceof {
public static void main(String[] args) {
giveMeAPet(new Dog());
}
//当一个方法不知道其参数是谁的对象时,通过instanceof关键字进行判断
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();
}
}
}
附:附一个利用多态和接口实现的实例,即实现笔记本通过USB接口,使用鼠标和键盘设备,代码如下:
//笔记本电脑类,使用USB接口
public class Laptop {
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 Laptop) {
KeyBoard keyBoard = new KeyBoard(); //向下转型
keyBoard.input();
}
usb.close(); //关闭设备
}
}
//USB接口
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 input(){
System.out.println("键盘输入");
}
}
public class DemoMain {
public static void main(String[] args) {
//创建笔记本
Laptop laptop = new Laptop();
laptop.powerOn();
//创建鼠标,采用多态进行向上转型,把鼠标当做一种usb
USB usbMouse = new Mouse();
//使用鼠标设备
laptop.useDevice(usbMouse); //方法参数是USB类型,传递参数也是USB
//创建键盘
KeyBoard keyBoard = new KeyBoard();
//使用键盘设备
laptop.useDevice(keyBoard); //正确写法,方法参数是USB类型,传递参数是实现类对象,也是发生了向上转型
//laptop.useDevice(new KeyBoard()); //正确写法,使用子类对象,匿名对象
laptop.powerOff();
}
}
//Output:
//笔记本电脑开机
//打开鼠标
//鼠标点击
//关闭鼠标
//打开键盘
//关闭键盘
//笔记本电脑关机
final 关键字
final 关键字表示最终的,不可改变的
final 关键字常见的四种用法:
- 修饰一个类
- 修饰一个方法
- 修饰一个局部变量
- 修饰一个成员变量
当 final 关键字修饰类时:
public final class 类名称() { }
:指这个类不能拥有任何子类
注意:一个final 类,其中所有的成员方法都不能进行覆盖重写
当 finla 关键字修饰一个方法时:
修饰符 final 返回值类型 方法名称(参数列表) { }
:指这个方法是最终方法,不能被覆盖重写(可以有子类,但子类不能对该 final 方法进行覆盖重写)
注意:对于类和方法来说,abstract 和 final 不能同时使用,相互矛盾
当 finla 关键字修饰一个局部变量时:
指该变量一旦被赋值,不可改变
对于基本类型来说,指变量当中的数据不可改变
对于引用类型来说,指变量当中的地址值不可改变
final int num1 = 10;
//num = 10; //错误写法,不可被再次赋值
final int num2;
num2 = 200; //正确写法,只能有唯一一次赋值
public class Student {
public String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo03Main {
public static void main(String[] args) {
final Student stu1 = new Student("张三");
System.out.println(stu1.getName());
//stu1 = new Student("李四"); //错误写法,final的引用类型变量,地址值不可改变
stu1.setName("李四"); //正确写法,引用类型地址值指向的内容可以随意改变
System.out.println(stu1.getName());
}
}
当 finla 关键字修饰一个成员变量时:
表示这个成员变量不可变,注意:
- 由于成员变量具有默认值,一旦用了 final 后不提供默认值,必须手动赋值
- final 成员变量,要么采用直接赋值,要么通过构造方法赋值
- 必须保证类当中所有重载的构造方法,都最终会对 final 的成员变量进行赋值
public class Person {
//赋值方法一:
//private final String name = "张三";
private final String name;
public Person(){
name = "张三";
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
//不可再使用setter()方法
/* public void setName(String name) {
this.name = name;
}*/
}
四种权限修饰符
四种权限修饰符:public > protected > (default) > private
default 不是关键字,指不写权限修饰符时的默认权限 (包访问权限)
权限 | public | protected | (default) | private |
---|---|---|---|---|
同一个类 | √ | √ | √ | √ |
同一个包 | √ | √ | √ | × |
不同包子类 | √ | √ | × | × |
不同包非子类 | √ | × | × | × |
内部类
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类
(如:身体与心脏的关系,汽车与发动机的关系)
分类:
- 成员内部类
- 局部内部类(包含匿名内部类)
1. 成员内部类
格式:
修饰符 class 类名称 {
修饰符 class 内部类名称 {
//...
}
}
注意:内部类使用外部类可以随意访问,外部类使用内部类必须通过内部类对象
使用成员内部类的两种方式:
- 间接使用
在外部类的方法当中使用内部类,main 只调用外部类的方法 - 直接使用
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
//外部类
public class Body {
//成员内部类
public class Heart {
//内部类方法
public void beat(){
System.out.println("心脏跳动");
System.out.println("我叫" + name); //正确写法
}
}
private String name; //外部类成员变量
//外部类方法
public void methodBody(){
System.out.println("外部类的方法");
//Heart heart = new Heart();
//heart.beat();
new Heart().beat(); //可直接使用匿名内部类的方法
}
}
public class Demo01Main {
public static void main(String[] args) {
Body body = new Body(); //外部类对象
//1. 通过外部类对象调用外部类方法,里面间接使用内部类Heart
body.methodBody();
//2. 直接使用
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
内部类的同名变量访问
若要在内部类方法中访问外部类成员变量,则格式为:外部类名称.this.变量名
public class Outer {
int num = 10; //外部类成员变量
public class Inner {
int num = 20; //内部类成员变量
public void methodInner(){
int num = 30; //内部类方法的局部变量
System.out.println(num); //30,局部变量,就近原则
System.out.println(this.num); //20,内部类的成员变量
System.out.println(Outer.this.num); //10,外部类的成员变量
}
}
}
public class Demo02Main {
public static void main(String[] args) {
Outer.Inner obj = new Outer().new Inner();
obj.methodInner();
}
}
2. 局部内部类
一个类定义在方法内部,则就是局部内部类。(只有当前所属方法才能使用该类)
格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称 (参数列表) {
class 局部内部类名称 {
//...
}
}
}
注意:权限修饰符的使用规则:
- 外部类:public / (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不能写
public class Outer {
public void methodOuter(){
class Inner{ //局部内部类
int num = 10;
public void methodInner(){
System.out.println(num); //10
}
}
Inner inner = new Inner();
}
}
public class Demo03Main {
public static void main(String[] args) {
Outer obj = new Outer();
obj.methodOuter();
}
}
注意:局部内部类如果要访问所在方法的局部变量,则这个局部变量必须是 有效final 的
(在Java 8 以前的版本,这种情况下所在方法的局部变量必须要加 final 修饰符,Java 8+ 开始,只要局部变量事实不变,可以省略 final 关键字)
原因:new 对象发生在堆内存中。局部变量是跟着方法走的,发生在栈内存中。方法运行结束后,立刻出栈,局部变量也会立刻消失,但 new 出来的对象会在堆当中持续存在,直到垃圾回收消失
public class Outer {
public void methodOuter(){
int num = 10; //所在方法的局部变量
//num = 20; //错误写法,不能重新赋值,改变局部变量的方法
class Inner{
public void methodInner(){
System.out.println(num);
}
}
}
}
3. 匿名内部类
如果接口的实现类,或者父类的子类,只需要使用唯一的一次,则这种情况下可以省略该类的定义,而使用匿名内部类
格式:
接口名称 对象名 = new 接口名称() {
//覆盖重写所有抽象方法
};
注意:
- 匿名内部类,在创建对象时只能使用唯一一次。若希望多次创建对象且类的内容相同,则必须使用单独定义的实现类
- 匿名对象,在调用方法时只能调用唯一一次。若希望同一个对象,调用多次方法,则必须给对象取对象名
- 匿名内部类省略了实现类/子类名称,但匿名对象时省略了对象名称(注意两者不同)
public interface MyInterface {
void method(); //抽象方法
}
/*public class MyInterfaceImpl implements MyInterface {
@Override
public void method() {
System.out.println("实现类覆盖重写了方法");
}
}*/
public class Demo05Main {
public static void main(String[] args) {
/* //写法一:
MyInterfaceImpl impl = new MyInterfaceImpl();
impl.method();
//写法二:多态
MyInterface obj = new MyInterfaceImpl();
obj.method(); */
//匿名内部类,但不是匿名对象,对象名叫objA
MyInterface objA = new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类实现了方法");
}
};
objA.method();
//匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
@Override
public void method() {
System.out.println("匿名内部类实现了方法");
}
}.method();
}
}