Java高级(1)-类和接口

类变量和类方法

类变量

不管static变量在哪里,共识(1)static变量是同一个类所有对象共享(2)static类变量,在类加载的时候就生成了。

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

类变量是随着类的加载而创建,所以即使没有创建对象实例也可访问。

在这里插入图片描述

语法

访问修饰符 static 数据类型 变量名;【推荐】

static 访问修饰符 数据类型 变量名;

访问

类名.类变量名 或者 对象名.类变量名

细节
  • 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量),比如学生类,统计所有学生的学费。
  • 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
  • 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量。
  • 类变量可以通过类名.类变量名【推荐】 或者 对象名.类变量名来访问。
  • 实例变量不能通过 类名.类变量名 方式来访问。
  • 类变量是随着类的加载而创建,所以即使没有创建对象实例也可访问。
  • 类变量的声明周期是随类的加载而开始,随类的消亡而消失。

类方法

类方法也叫静态方法。

语法

访问修饰符 static 数据返回类型 方法名(){};【推荐】

static 访问修饰符 数据返回类型 方法名(){};

调用

类名.类方法名 或者 对象名.类方法名

细节
  • 静态方法可以访问静态属性
  • 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区,类方法中无this的参数,普通方法中隐含着this的参数
  • 类方法可以通过类名调用,也可以通过对象名调用
  • 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
  • 类方法中不允许使用和对象有关的关键字,比如this和super,普通方法(成员方法)可以
  • 类方法(静态方法)中只能访问【静态变量】或【静态方法】
  • 普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)
  • 静态方法只能访问静态的变量,非静态的方法,可以访问静态成员和非静态成员
class Person {
    private int id;
    private static int total = 0;
    public static int getTotalPerson(){
        //id++;//非静态变量,静态方法不能访问非静态变量
        return total;
    }

    public static void setTotalPerson(int total){
        //this.total = total;//静态方法中不能使用this super
        Person.total = total;
    }

    public Person(){
        total++;
        id = total;
    }
}

public class TestPerson {
    public static void main(String[] args) {
        System.out.println(Person.getTotalPerson());//0
        Person p1 = new Person();
        System.out.println(Person.getTotalPerson());//1
        Person.setTotalPerson(3);
        new Person();
        System.out.println(Person.getTotalPerson());//4
    }
}

理解main方法语法

public static void main(String[] args) {}

  • Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是publiic
  • java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
  • 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
  • java执行的程序 参数1 参数2 参数3
  • 在main()方法中,可以直接调用main方法所在类的静态方法或静态属性
  • 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类的非静态成员

在这里插入图片描述

public class Test {
    public int a;
    public static int b;
    public void f(){
        System.out.println("f() a = " + a);
        System.out.println("f() b = " + b);
    }
    public static void g(){
        //System.out.println("f() a = " + a);//静态方法访问非静态属性
        System.out.println("f() b = " + b);
    }

    public static void main(String[] args) {
        System.out.println(b);
        g();
        Test test = new Test();//main()方法访问本类中的非静态变量或属性利用实例对象
        System.out.println(test.a);
        test.f();

        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i] + "\t");
        }
    }
}

在右上角选择类那里选择Edit Configurations-------Program arguments传值

代码块

代码块又叫初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或者创建对象时隐式调用。

语法

[修饰符]{

​ 代码

};

  • 修饰符可选,要写的话,也只能写static
  • 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的叫普通代码块/非静态代码块
  • 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
  • ;号可以写上,也可以省略
好处
  • 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
  • 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
public class CodeBlock {
    public static void main(String[] args) {
        Movie abc = new Movie("ABC");
    }
}

class Movie{
    private String name;
    private double price;
    private String director;
    //将构造器中重复的语句放入代码块中
    //不管调用哪个构造器,都会先调用代码块中的内容
    //代码块中的顺序优先于构造器
    {
        System.out.println("电影屏幕打开");
        System.out.println("广告开始");
        System.out.println("电影正式开始");
    }

    public Movie(String name) {
//        System.out.println("电影屏幕打开");
//        System.out.println("广告开始");
//        System.out.println("电影正式开始");
        System.out.println("Movie(String name)");
        this.name = name;
    }

    public Movie(String name, double price) {
//        System.out.println("电影屏幕打开");
//        System.out.println("广告开始");
//        System.out.println("电影正式开始");
        System.out.println("Movie(String name, double price)");
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director) {
//        System.out.println("电影屏幕打开");
//        System.out.println("广告开始");
//        System.out.println("电影正式开始");
        System.out.println("Movie(String name, double price, String director)");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}
细节
  • static代码块也叫静态代码块,作用就是对类进行初始化,而且随类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象,就执行
  • 类什么时候被加载
    • 创建对象实例时
    • 创建子类对象实例,父类也会被加载
    • 使用类的静态成员时(静态属性、静态方法)
  • 普通的代码块,在创建对象实例时,会被隐式的调用,被创建一次,就会调用一次;如果只是使用类的静态成员时,普通代码块并不会执行
  • 创建一个对象时,在一个类调用顺序是:
    • 调用静态代码块和静态属性初始化(静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
    • 调用普通代码块和普通属性的初始化(普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按他们定义的顺序调用)
    • 调用构造方法
  • 构造方法(构造器)的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
  • 创建一个子类时,他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
    • 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    • 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    • 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    • 父类的构造方法
    • 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    • 子类的构造方法
  • 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意代码。
//static代码块是类加载时,执行,只会执行一次
//普通代码块是在创建对象时调用,创建一次,调用一次

public class CodeDetail {
    public static void main(String[] args) {
        System.out.println(Teach.classroom);//使用类的静态属性
        单独执行输出//
        //父类静态代码块
        //父类静态属性
        System.out.println("-----------------");
        Teach t = new Teach("李四");//创建对象实例
        单独执行输出/
        //父类静态代码块
        //父类非静态代码块
        //父类的构造器
        System.out.println("-----------------");
        Talk tl = new Talk("张三");//创建子类对象实例(1)类加载(2)对象创建
        单独执行输出
        //父类静态代码块
        //子类静态代码块
        //父类非静态代码块
        //父类的构造器
        //子类非静态代码块
        //子类的构造器
        System.out.println("-----------------");
        System.out.print(Talk.word);//使用类的静态属性
        ///单独执行输出///
        //父类静态代码块
        //子类静态代码块
        //子类静态属性

        /四句一起执行输出
        //父类静态代码块
        //父类静态属性
        //---------------------
        //父类非静态代码块
        //父类的构造器
        //---------------------
        //子类静态代码块
        //父类非静态代码块
        //父类的构造器
        //子类非静态代码块
        //子类的构造器
        //----------------------
        //子类静态属性
    }
}
class Teach{
    private String name;
    private String object;
    public static String classroom = "父类静态属性";

    static{
        System.out.println("父类静态代码块");
    }

    {
        System.out.println("父类非静态代码块");
    }

    public Teach(String name) {
        this.name = name;
        System.out.println("父类的构造器");
    }
}

class Talk extends Teach{

    public static String word = "子类静态属性";

    static{
        System.out.println("子类静态代码块");
    }

    {
        System.out.println("子类非静态代码块");
    }

    public Talk(String name) {
        super(name);
        System.out.println("子类的构造器");
    }
}
//输出结果:
//getVal01
//A02 的一个静态代码块..
//getVal03
//B02 的一个静态代码块..
//A02 的第一个普通代码块..
//getVal02
//A02 的构造器
//getVal04
//B02 的第一个普通代码块..
//B02 的构造器

public class CodeBlockDetail04 {
    public static void main(String[] args) { 
        new B02();
    } 
}

class A02 { 
    private static int n1 = getVal01();
    static {
        System.out.println("A02 的一个静态代码块..");//(2) 
    }
    { 
        System.out.println("A02 的第一个普通代码块..");//(5) 
    }
    public int n3 = getVal02();//普通属性的初始化 
    public static int getVal01() {
        System.out.println("getVal01");//(1) 
        return 10; 
    }
    public int getVal02() {
        System.out.println("getVal02");//(6) 
        return 10; 
    }public A02() {
        //构造器 //隐藏 //super() //普通代码和普通属性的初始化...... 
        System.out.println("A02 的构造器");//(7) 
    } 
}
class B02 extends A02 {
    private static int n3 = getVal03(); 
    static {
        System.out.println("B02 的一个静态代码块..");//(4) 
    }
    public int n5 = getVal04(); 
    { 
        System.out.println("B02 的第一个普通代码块..");//(9)
    }
    public static int getVal03() {
        System.out.println("getVal03");//(3) 
        return 10; 
    }
    public int getVal04() {
        System.out.println("getVal04");//(8) 
        return 10; 
    }
    public B02() {
        //构造器 //隐藏了 //super() //普通代码块和普通属性的初始化... 
        System.out.println("B02 的构造器");//(10) 
    } 
}
    

单例设计模式

设计模式

  • 静态方法和属性的经典使用
  • 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式,设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免得我们自己再思考和摸索。
  • 单例设计模式就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取的其对象实例的方法。

饿汉式

  • 构造器私有化
  • 类的内部创建对象
  • 向外暴露一个静态的公共方法
  • 饿汉体现在在不确定会不会用到这个对象实例的时候,这个对象就已经创建好了,因为对象的创建是static的,会在类加载的时候对象就会创建,但如果对象是重量级的,就有可能造成资源的浪费。
public class HungryMan {
    public static void main(String[] args) {
        GirlFriend instance = GirlFriend.getGirlFriend();
        System.out.println(instance);
        GirlFriend onceAgain = GirlFriend.getGirlFriend();
        System.out.println(onceAgain);
        System.out.println(instance == onceAgain);
    }
}
class GirlFriend{
    private String name;
    //为了能够将girlFriend在下面静态方法中使用,所以其为静态
    private static GirlFriend girlFriend = new GirlFriend("小薇");
    private GirlFriend(String name) {
        this.name = name;
    }
    public static GirlFriend getGirlFriend(){
        return girlFriend;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

懒汉式

public class LazyMan {
    public static void main(String[] args) {
        System.out.println(Boyfriend.getBoyfriend());
    }
}
class Boyfriend{
    private String name;
    private static Boyfriend boyfriend;
    private Boyfriend(String name){
        this.name = name;
    }
    public static Boyfriend getBoyfriend(){
        if(boyfriend == null){
            boyfriend = new Boyfriend("菠萝吹雪");
        }
        return boyfriend;
    }

    @Override
    public String toString() {
        return "Boyfriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

总结

  • 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建;
  • 饿汉式不存在线程安全问题,懒汉式存在线程安全问题;
  • 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题;
  • JavaSE标准类中,java.lang.Runtime就是经典的单例模式。

final 关键字

基本介绍

  • final可以修饰类、属性、方法和局部变量,以下方法可能会用到:
  • 当不希望类被继承时,就可以用final修饰类
  • 当不希望父类的某个方法被子类覆盖/重写,就可以用final修饰方法
  • 当不希望类的某个属性的值被修改,就可以用final修饰属性
  • 当不希望某个局部变量被修改,就可以用final修饰局部变量

细节

  • final修饰的属性又叫常量,一般用AA_AA命名
  • final修饰的属性在定义时必须给值,之后不能修改,赋值可以在如下位置之一:
    • 定义时
    • 在构造器中
    • 在代码块中
  • 如果final修饰的属性是静态的,则初始化的位置只能
    • 定义时
    • 在静态代码块中,不能在构造器中赋值
  • final类不能继承,但是可以实例对象
  • 如果类不是final类,但是含有final方法,则该方法不能重写,但是可以被继承
  • 一般来说,如果一个类已经是final类了,就没必要将其中方法修饰为final方法
  • final不能修饰构造方法(构造器)
  • final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
public class Test{
    public static void main(String[] args){
        System.out.println(BBB.num);//只会输出10000,类并没有被加载
    }
}
class BBB{
    public final static int num = 10000;
    static{
        System.out.println("BBB 静态代码块");
    }
}
  • 包装类(Integer,Double,Float,Boolean等都是final),String也是final类
class Something{
    public int addOne(final int x){//允许
       // ++x;    //不允许,不允许修改x的值
        return x + 1;
    }
}

抽象类

介绍

当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。

  • 当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。
public abstract class Animal{
    String name;
    int age;
    public abstract public void cry();
}
  • 一般来说,抽象类会被继承,有其子类来实现抽象方法。

细节

  • 抽象类不能实例化
  • 抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法,而且可以有实际的方法
  • 一旦类包含了abstract方法,则这个类必须声明为abstract
  • abstract只能修饰类和方法,不能修饰属性和其他的
  • 抽象类可以有任意成员【抽象类还是类】,比如非抽象方法、构造器、抽象方法、静态方法、属性、静态属性等
  • 抽象方法不能有主体,既不能实现
  • 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法【所谓实现也就是有方法体{},方法体{}中可以没有内容】,除非它自己也声明为abstract类
  • 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的

模板设计模式

  1. 有多个类,完成不同的任务job
  2. 要求能够得到各自完成任务的时间

具体实现

  • 编写方法caleTime(),可以计算某段代码的耗时时间
  • 编写抽象方法code()
  • 编写一个子类Sub,继承抽象类Template,并实现code方法
  • 编写测试类
public abstract class Template {
    public abstract void code();
    public void caleTime(){
        long start = System.currentTimeMillis();
        code();
        long end = System.currentTimeMillis();
        System.out.println("执行时间" + (end - start));
    }
}

public class AA extends Template{
    public void code(){
        long num = 0;
        for (int i = 1; i <= 1000000; i++) {
            num += i;
        }
    }
}

public class BB extends Template{
    public void code(){
        long num = 0;
        for (int i = 1; i <= 1000000000; i++) {
            num += i;
        }
    }
}

public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.caleTime();
        BB bb = new BB();
        bb.caleTime();
    }
}

接口

介绍

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。

interface 接口名{
    //属性
    //方法  在接口中抽象方法可以省略abstract关键字
}
class 类名 implements 接口{//实现接口,将实现接口中所有的抽象方法
    自己属性;
    自己方法;(1.抽象方法  2.默认实现方法 3.静态方法)
    必须实现的接口的抽象方法    
}
小结:
1.在jdk 7.0前接口里的所有方法都没有方法体
2.jdk 8.0后接口类可以有静态方法、默认方法,也就是说接口中可以有方法的具体实现,需要使用default关键字修饰,也可以有静态方法,即用static修饰。

细节

  • 接口不能被实例化
  • 接口中所有的方法是 public 方法,接口中抽象方法,可以不用 abstract修饰
  • 一个普通类实现接口,就必须将该接口的所有方法都实现
  • 抽象类实现接口,可以不用实现接口的方法
  • 一个类同时可以实现多个接口 class 类名 implements 接口1,接口2··· ···
  • 接口中的属性,只能是final的,而且是public static final修饰符
  • 接口中属性的访问形式:接口名.属性名
  • 一个接口不能继承其他的类,但是可以继承多个别的接口 interface A extends B,C{}
  • 接口的修饰符只能是public和默认,这点和类的修饰符是一样的
interface A{
    int a = 23;//public static final int a = 23;
}
class B implements A{
    
}
B b = new B();//ok
System.out.println(b.a);//23
System.out.println(A.a);//23
System.out.println(B.a);//23

继承和接口

  • 接口和继承解决的问题不同
    • 继承的价值主要在于:解决代码的复用性和可维护性
    • 接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法。
  • 接口比继承更加灵活
    • 接口比继承更加灵活,继承是满足is-a的关系,而接口只需满足like - a的关系。
  • 接口在一定程度上实现代码解耦【接口规范性+动态绑定机制】
public class ExtendsVsInterface {
    public static void main(String[] args) {
        LittleMonkey wuKong = new LittleMonkey("悟空");
        wuKong.climbing();
        wuKong.swimming();
        wuKong.flying();
    }
}

//猴子
class Monkey {
    private String name;

    public Monkey(String name) {
        this.name = name;
    }
    public void climbing() {
        System.out.println(name + " 会爬树...");
    }

    public String getName() {
        return name;
    }
}

//接口
interface Fishable {
    void swimming();
}
interface Birdable {
    void flying();
}

//继承
//小结:  当子类继承了父类,就自动的拥有父类的功能
//      如果子类需要扩展功能,可以通过实现接口的方式扩展.
//      可以理解 实现接口 是 对java 单继承机制的一种补充.
class LittleMonkey extends Monkey implements Fishable,Birdable {

    public LittleMonkey(String name) {
        super(name);
    }

    @Override
    public void swimming() {
        System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
    }

    @Override
    public void flying() {
        System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
    }
}

接口多态

  • 多态参数
    • 接口引用可以指向实现了接口的类的对象
  • 多态数组
  • 接口存在多态传递现象
public class InterfacePolyArr {
    public static void main(String[] args) {

        //多态数组 -> 接口类型数组
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();
        usbs[1] = new Camera_();
        /*
        给Usb数组中,存放 Phone  和  相机对象,Phone类还有一个特有的方法call(),
        请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,
        还需要调用Phone 特有方法 call
         */
        for(int i = 0; i < usbs.length; i++) {
            usbs[i].work();//动态绑定..
            //和前面一样,我们仍然需要进行类型的向下转型
            if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_
                ((Phone_) usbs[i]).call();
            }
        }

    }
}

interface Usb{
    void work();
}
class Phone_ implements Usb {
    public void call() {
        System.out.println("手机可以打电话...");
    }

    @Override
    public void work() {
        System.out.println("手机工作中...");
    }
}
class Camera_ implements Usb {

    @Override
    public void work() {
        System.out.println("相机工作中...");
    }
}
public class InterfacePolyPass {
    public static void main(String[] args) {
        //接口类型的变量可以指向,实现了该接口的类的对象实例
        IG ig = new Teacher();
        //如果IG 继承了 IH 接口,而Teacher 类实现了 IG接口
        //那么,实际上就相当于 Teacher 类也实现了 IH接口.
        //这就是所谓的 接口多态传递现象.
        IH ih = new Teacher();
    }
}

interface IH {
    void hi();
}
interface IG extends IH{ }
class Teacher implements IG {
    @Override
    public void hi() {
    }
}
interface A{
    int x = 0;//public final static int x = 0
}
class B{
    int x = 1;
}
class C extends B implements A{
    public void pX(){
        System.out.println(A.x + " " + super.x);//明确指定x
    }
    
    public static void main(String[] args){
        new C().pX();
    }
}

内部类

一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class)嵌套其他类的类称为外部类(outer class),是类的第五大成员【属性、方法、构造器、代码块、内部类】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

class Outer{
    class Inner{
        
    }
}
class Other{
    
}

分类

  • 定义在外部类局部位置上(比如方法内)
    • 局部内部类(有类名)
    • 匿名内部类(没有类名,重点!!!!!!!!)
  • 定义在外部类的成员位置上
    • 成员内部类(没用static修饰)
    • 静态内部类(使用static修饰)

局部内部类

局部内部类是定义在外部类的局部位置,比如方法/代码块中,并且有类名。

  • 可以直接访问外部类的所有成员,包括私有
  • 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量也可以使用final
  • 作用域:仅仅在定义它的方法或代码块
  • 局部内部类—访问—外部类的成员【访问方式:直接访问】
  • 外部类—访问—局部内部类的成员【访问方式:创建对象,再访问(必须在作用域内)】内部类本质仍然是一个类
  • 外部其他类----不能访问----局部内部类(因为局部内部类地位是一个局部变量)
  • 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
  • 外部类名.this 本质就是外部类的对象,即哪个对象调用了内部类所在的方法,就输出哪个外部类对象的属性
public class LocalInnerClass {//
    public static void main(String[] args) {
        //演示一遍
        Outer02 outer02 = new Outer02();
        outer02.m1();
        System.out.println("outer02的hashcode=" + outer02);
    }
}


class Outer02 {//外部类
    private int n1 = 100;
    private void m2() {
        System.out.println("Outer02 m2()");
    }//私有方法
    public void m1() {//方法
        //1.局部内部类是定义在外部类的局部位置,通常在方法
        //3.不能添加访问修饰符,但是可以使用final 修饰
        //4.作用域 : 仅仅在定义它的方法或代码块中
        final class Inner02 {//局部内部类(本质仍然是一个类)
            //2.可以直接访问外部类的所有成员,包含私有的
            private int n1 = 800;
            public void f1() {
                //5. 局部内部类可以直接访问外部类的成员,比如下面 外部类n1 和 m2()
                //7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
                //   使用 外部类名.this.成员)去访问
                //   老韩解读 Outer02.this 本质就是外部类的对象, 即哪个对象调用了m1, Outer02.this就是哪个对象
                System.out.println("n1=" + n1 + " 外部类的n1=" + Outer02.this.n1);
                System.out.println("Outer02.this hashcode=" + Outer02.this);
                m2();
            }
        }
        //6. 外部类在方法中,可以创建Inner02对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }

}

匿名内部类

匿名内部类是定义在外部类的局部位置,比如说方法体、代码块中,并且没有类名。

(1)本质是类(2)内部类(3)该类没有名字(系统分配)(4)同时还是一个对象

new 类或接口(参数列表){
    类体
}
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}

class Outer04 { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
        //基于接口的匿名内部类
        //老韩解读
        //1.需求: 想使用IA接口,并创建对象
        //2.传统方式,是写一个类,实现该接口,并创建对象
        //3.老韩需求是 Tiger/Dog 类只是使用一次,后面再不使用
        //4. 可以使用匿名内部类来简化开发
        //5. tiger的编译类型 ? IA
        //6. tiger的运行类型 ? 就是匿名内部类  Outer04$1
        /*
            我们看底层 会分配 类名 Outer04$1
            class Outer04$1 implements IA {
                @Override
                public void cry() {
                    System.out.println("老虎叫唤...");
                }
            }
         */
        //7. jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1实例,并且把地址
        //   返回给 tiger
        //8. 匿名内部类使用一次,就不能再使用
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        System.out.println("tiger的运行类型=" + tiger.getClass());
        tiger.cry();
        tiger.cry();
        tiger.cry();

//        IA tiger = new Tiger();
//        tiger.cry();

        //演示基于类的匿名内部类
        //分析
        //1. father编译类型 Father
        //2. father运行类型 Outer04$2
        //3. 底层会创建匿名内部类
        /*
            class Outer04$2 extends Father{
                @Override
                public void test() {
                    System.out.println("匿名内部类重写了test方法");
                }
            }
         */
        //4. 同时也直接返回了 匿名内部类 Outer04$2的对象
        //5. 注意("jack") 参数列表会传递给 构造器
        Father father = new Father("jack"){

            @Override
            public void test() {
                System.out.println("匿名内部类重写了test方法");
            }
        };
        System.out.println("father对象的运行类型=" + father.getClass());//Outer04$2
        father.test();

        //基于抽象类的匿名内部类
        Animal animal = new Animal(){
            @Override
            void eat() {
                System.out.println("小狗吃骨头...");
            }
        };
        animal.eat();
    }
}

interface IA {//接口
    public void cry();
}
//class Tiger implements IA {
//
//    @Override
//    public void cry() {
//        System.out.println("老虎叫唤...");
//    }
//}
//class Dog implements  IA{
//    @Override
//    public void cry() {
//        System.out.println("小狗汪汪...");
//    }
//}

class Father {//类
    public Father(String name) {//构造器
        System.out.println("接收到name=" + name);
    }
    public void test() {//方法
    }
}

abstract class Animal { //抽象类
    abstract void eat();
}
细节
  • 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法。
  • 可以直接访问外部类的所有成员,包括私有的
  • 不能添加访问修饰符,因为它的地位就是一个局部变量
  • 作用域:仅仅在定义它的方法或代码块中
  • 匿名内部类-----访问------外部类成员【访问方式:直接访问】
  • 外部其他类----不能访问----匿名内部类(因为匿名内部类地位是一个局部变量)
  • 如果外部类和匿名内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
public class AnonymousInnerClassDetail {
    public static void main(String[] args) {

        Outer05 outer05 = new Outer05();
        outer05.f1();
        //外部其他类---不能访问----->匿名内部类
        System.out.println("main outer05 hashcode=" + outer05);
    }
}

class Outer05 {
    private int n1 = 99;
    public void f1() {
        //创建一个基于类的匿名内部类
        //不能添加访问修饰符,因为它的地位就是一个局部变量
        //作用域 : 仅仅在定义它的方法或代码块中
        Person p = new Person(){
            private int n1 = 88;
            @Override
            public void hi() {
                //可以直接访问外部类的所有成员,包含私有的
                //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
                //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
                System.out.println("匿名内部类重写了 hi方法 n1=" + n1 +
                        " 外部内的n1=" + Outer05.this.n1 );
                //Outer05.this 就是调用 f1的 对象
                System.out.println("Outer05.this hashcode=" + Outer05.this);
            }
        };
        p.hi();//动态绑定, 运行类型是 Outer05$1

        //也可以直接调用, 匿名内部类本身也是返回对象
        // class 匿名内部类 extends Person {}
//        new Person(){
//            @Override
//            public void hi() {
//                System.out.println("匿名内部类重写了 hi方法,哈哈...");
//            }
//            @Override
//            public void ok(String str) {
//                super.ok(str);
//            }
//        }.ok("jack");


    }
}

class Person {//类
    public void hi() {
        System.out.println("Person hi()");
    }
    public void ok(String str) {
        System.out.println("Person ok() " + str);
    }
}
//抽象类/接口...
实践
  • 当作实参直接传递
public class InnerClassExercise01 {
    public static void main(String[] args) {

        //当做实参直接传递,简洁高效
        f1(new IL() {
            @Override
            public void show() {
                System.out.println("这是一副名画~~...");
            }
        });
        //传统方法
        f1(new Picture());

    }

    //静态方法,形参是接口类型
    public static void f1(IL il) {
        il.show();
    }
}
//接口
interface IL {
    void show();
}
//类->实现IL => 编程领域 (硬编码)
class Picture implements IL {

    @Override
    public void show() {
        System.out.println("这是一副名画XX...");
    }
}
///
//  练习
///
interface Bell{
    void ring();
}
class Cellphone{
    public void alarmclock(Bell b){
        b.ring();
    }
}
public class Test{
    public static void main(String[] args){
        Cellphone c = new Cellphone();
        c.alarmclock(new Bell(){
            public void ring(){
                System.out.println("懒猪起床啦");
            }
        }
        );
            
        c.alarmclock(new Bell(){
            public void ring(){
                System.out.println("小伙伴上课了");
            }
        }
        );
    }
}

成员内部类

定义在外部类的成员位置上,并且没有static修饰。

  • 可以直接访问外部类的所有成员,包含私有的
  • 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
  • 作用域:和外部类的其他成员一样,为整个类体
  • 成员内部类----访问----外部类(比如:属性)【访问方式:直接访问】
  • 外部类—访问----内部类【访问方式:创建对象,再访问】
  • 外部其他类----访问-----成员内部类
  • 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
//第一种方法:在main函数中
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
//相当于把 new Inner()当作是outer的一个成员
//------------------------------------------------------
//第二种方法:在Outer类中写一个方法返回一个Inner对象
Outer outer = new Outer();
Outer.Inner inner = outer.getInnerInstance();

public Inner getInnerInstance(){
    return new Inner();
}
//------------------------------------------------------
//第三种方法:将第一种方式合二为一
Outer.Inner inner =  new Outer().new Inner();

静态内部类

定义在外部类的成员位置上,有static修饰。

  • 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
  • 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
  • 作用域:和外部类的其他成员一样,为整个类体
  • 静态内部类----访问----外部类(比如:静态属性)【访问方式:直接访问】
  • 外部类—访问----静态内部类【访问方式:创建对象,再访问】
  • 外部其他类----访问-----静态内部类
  • 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
//第一种方法:在main函数中
Outer outer = new Outer();
Outer.Inner inner = new Outer.Inner();
//因为静态内部类是可以通过类名直接访问(满足访问权限)
//------------------------------------------------------
//第二种方法:在Outer类中写一个方法返回一个Inner对象
Outer outer = new Outer();
Outer.Inner inner = outer.getInnerInstance();

public Inner getInnerInstance(){
    return new Inner();
}
//------------------------------------------------------
//第三种方法:在Outer类中写一个静态方法返回一个Inner对象
Outer.Inner inner = Outer.getInnerInstance();

public static Inner getInnerInstance(){
    return new Inner();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值