Java基础知识-第12章-Java内部类以及设计模式介绍

289 阅读4分钟

1、内部类

1.1、定义

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使 用内部类。

Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类

Inner class 一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。Inner class 的名字不能与包含它 的外部类类名相同;

1.2、内部类的分类

成员内部类(静态、非静态 ) vs 局部内部类(方法内、代码块内、构造器内)

局部内部类:

class Person{

    public void method(){
        //局部内部类
        class AA{

        }
    }

    {
        //代码块里面的局部内部类
        class BB{

        }
    }

    public Person(){
        //构造器里面的局部内部类
        class CC{

        }
    }
}

成员内部类

class Person{
	
    //静态成员内部类
    static class Dog{

    }

    //非静态成员内部类
    class Bird{

    }		
}

1.3、成员内部类的理解

一方面,作为外部类的成员:

  • 调用外部类的结构
  • 可以被static修饰,但此时就不能再使用外层类的非static的成员变量;
  • 可以被4种不同的权限修饰

另一方面,作为一个类:

  • 类内可以定义属性、方法、构造器等
  • 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
  • 可以被abstract修饰,因此可以被其它的内部类继承
  • 编译以后生成 OuterClass InnerClass class 字节码文件 也适用于局部内部类

注意

  • 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
  • 外部类访问成员内部类的成员,需要“内部类.成员(静态的)”或“内部类对象.成员(非静态的)”的方式
  • 成员内部类可以直接使用外部类的所有成员(要考虑静态还是费静态),包括私有的数据,考虑名称相同否
  • 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的

如何创建成员内部类的对象?(静态的,非静态的)

public class InnerClassTest {
    public static void main(String[] args) {

        //创建Dog实例(静态的成员内部类):
        Person.Dog dog = new Person.Dog();
        dog.show();
        
        //创建Bird实例(非静态的成员内部类):
        //		Person.Bird bird = new Person.Bird();//错误的,非静态必须用对象去调用
        Person p = new Person();
        Person.Bird bird = p.new Bird();
        bird.sing();
    }
}

class Person{

    String name = "小明";
    int age;

    public void eat(){
        System.out.println("人:吃饭");
    }

    //静态成员内部类
    static class Dog{
        String name;
        int age;

        public void show(){
            System.out.println("卡拉是条狗");
            //eat();不能调用,因为这是静态结构
        }

    }
    //非静态成员内部类,静态结构不能调用外部非静态的
    class Bird{
        String name = "杜鹃";

        public Bird(){

        }

        public void sing(){
            System.out.println("我是一只小小鸟");
            Person.this.eat();//调用外部类的非静态属性
            eat();
            System.out.println(age);
        }

        public void display(String name){
            System.out.println(name);//方法的形参
            System.out.println(this.name);//内部类的属性
            System.out.println(Person.this.name);//外部类的属性
        }
    }
}

如何在成员内部类中调用外部类的结构?

//非静态成员内部类,静态结构不能调用外部非静态的
class Bird{
    String name = "杜鹃";

    public Bird(){

    }

    public void sing(){
        System.out.println("我是一只小小鸟");
        Person.this.eat();//调用外部类的非静态属性
        eat();
        System.out.println(age);
    }

    public void display(String name){
        System.out.println(name);//方法的形参
        System.out.println(this.name);//内部类的属性
        System.out.println(Person.this.name);//外部类的属性,因为name这个属性外类和内类名称一样,为了区分要加外部类名,如果不一样就和age一样直接调用
    }
}

1.4、局部内部类的使用

如何使用局部内部类

  • 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
  • 但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型,所以要继承或实现

局部内部类的特点

  • 局部内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
  • 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
  • 局部内部类可以使用外部类的成员,包括私有的。
  • 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
    • jdk7及之前版本:要求此局部变量显式的声明为final
    • jdk8及之后的版本:可以省略final的声明
public class InnerClassTest {

  public void method(){  
      //局部变量,常量  
      int num = 10;  

      class AA{     
          public void show(){  
              //num = 20;  //编译错误,不能修改,因为外部方法局部变量不是final的
              System.out.println(num);                  
          }             
      }         
  }     
}  
  • 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
  • 局部内部类不能使用static

实例演示

package com.lanmeix.java2;
public class InnerClassTest1 {

    //开发中很少见
    public void method(){
        //局部内部类
        class AA{
        }
    }

    //返回一个实现了Comparable接口的类的对象
    public Comparable getComparable(){
        //创建一个实现了Comparable接口的类:局部内部类
        //方式一:
        class MyComparable implements Comparable{
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        }
        return new MyComparable();

        //方式二:匿名对象匿名类的方式 ,这就是后面说的匿名内部类
        return new Comparable(){
            @Override  //重写方法,因为 MyComparable类要实现Comparable接口,所以必须重写内部compareTo方法
            public int compareTo(Object o) {
                return 0;
            }
        };
    }
}

1.5、匿名内部类

匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一 个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或 实现一个类。

格式:

new 父类构造器(实参列表) | 实现接口{
	//匿名内部类的类体部分
}

实例

//方式二:匿名对象匿名类的方式 ,这就是后面说的匿名内部类
return new Comparable(){
    @Override  //重写方法,因为 MyComparable类要实现Comparable接口,所以必须重写内部compareTo方法
    public int compareTo(Object o) {
        return 0;
    }
};

由于我们要实例化一个实现了Comparable接口的类MyComparable,并且接口不能new ,但是这是子类实现的接口,只不过没有为对象起名,所以就可以使用这种匿名内部类的方式(相当于Comparable com = new MyComparable()),因此就必须重写Comparable接口内部compareTo方法

匿名内部类的特点

  • 匿名内部类必须继承父类或实现接口
  • 匿名内部类只能有一个对象
  • 匿名内部类对象只能使用多态形式引用,传参

image.png

  • 调用的是子类重写的fun1方法,用外部类去调用方法,传入多态(匿名内部类对象)
  • outer是外部类,内部类实现了A接口,子类对象匿名了,只不过用的是A的名字表示,A换成父类也是这样。不知道具体是哪个子类,类和对象都匿名了,只不过用A代替区分。
  • New outer()对象为了使用一次,也取匿名。
  • New A(){} 匿名内部类,由于实现了接口A,所以匿名内部类里面必须重写A的fun1方法
  • 这样写就是体现一种多态(传参):A a = New A(){},A 是自动起的一个对象名字
  • 其实就是在outer外部类里面调用一个方法,而这个方法里面的参数就是我们内部类的对象(匿名类匿名对象)

总结

成员内部类和局部内部类,在编译以后,都会生成字节码文件。

  • 成员内部类:外部类$内部类名.class
  • 局部内部类:外部类$数字 内部类名.class

2、单例设计模式(singleton)

详见设计模式专讲模块,这里简单介绍

2.1、单例设计模式的说明

设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类 的构造器的访问权限设置为 private ,这样,就不能用 new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的 。

2.2、23种经典的设计模式

创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

2.3、单例设计模式

2.3.1、饿汉式

方式一:

public class SingletonTest1 {
    public static void main(String[] args) {
        
        //Bank bank1 = new Bank();
        //Bank bank2 = new Bank();

        Bank bank1 = Bank.getInstance();
        Bank bank2 = Bank.getInstance();

        System.out.println(bank1 == bank2);
    }
}

//饿汉式
class Bank{

    //1.私有化类的构造器
    private Bank(){
    }

    //2.内部创建类的对象
    //4.要求此对象也必须声明为静态的
    private static Bank instance = new Bank();

    //3.提供公共的静态的方法,返回类的对象
    public static Bank getInstance(){
        return instance; //静态方法里面要静态的属性
    }
}

方式二:使用了静态代码块

class Order{
    //1.私化类的构造器
    private Order(){
    }
    
    //2.声明当前类对象,没初始化
    //4.此对象也必须声明为static的
    private static Order instance = null;
    
    static{
        instance = new Order();
    }
    //3.声明public、static的返回当前类对象的方法
    public static Order getInstance(){
        return instance;
    }
}

2.3.2、懒汉式

package com.lanmeix.java2;
/*
 * 单例模式的懒汉式实现
 * 
 */
public class SingletonTest2 {
    public static void main(String[] args) {

        Order order1 = Order.getInstance();
        Order order2 = Order.getInstance();

        System.out.println(order1 == order2);

    }
}


class Order{

    //1.私有化类的构造器
    private Order(){

    }

    //2.声明当前类对象,没有立即初始化
    //4.此对象也必须声明为static的
    private static Order instance = null;

    //3.声明public、static的返回当前类对象的方法
    public static Order getInstance(){		
        if(instance == null){			
            instance = new Order();			
        }
        return instance;
    }
}

2.4、两种方式的对比

饿汉式:

  • 坏处:对象加载时间过长。
  • 好处:饿汉式是线程安全的

懒汉式:

  • 好处:延迟对象的创建
  • 目前的写法坏处:线程不安全。到多线程内容时,再修改

2.5、单例模式的优点:

由于单例模式只生成一个实例,减少了系统性能开销 ,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用 启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

比如 java.lang.Runtime

image.png

2.6、单例设计模式应用场景

  • 网站的计数器 ,一般也是单例模式实现,否则难以同步。
  • 应用程序的日志应用 ,一般都使用 单例模式实现,这一般是由于共享的日志
  • 文件一直处于打开状态,因为只能有一个实例去操作, 否则内容不好追加。
  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源 。
  • 项目中,读取配置文件的类 ,一般也只有一个对象。没有必要每次使用配置文件数据 ,都生成一个对象去读取 。
  • Application也是单例的典型 应用
  • Windows 的 Task Manager ( 任务管理器 就是 很典型的单例模式
  • Windows 的 Recycle Bin 回收站 也 是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例 。

3、main方法命令行参数的使用

main()方法作为程序的入口。main()方法也是一个普通的静态方法。main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)

  • 有时候你希望运行一个程序时候再传递给它消息。这要靠传递命令行参数给main()函数实现。
  • 命令行参数是在执行程序时候紧跟在程序名字后面的信息。

实例

下面的程序打印所有的命令行参数:

public class CommandLine {
   public static void main(String[] args){ 
      for(int i=0; i<args.length; i++){
         System.out.println("args[" + i + "]: " + args[i]);
      }
   }
}

如下所示,运行这个程序:

$ javac CommandLine.java 
$ java CommandLine this is a command line 200 -100
args[0]: this
args[1]: is
args[2]: a
args[3]: command
args[4]: line
args[5]: 200
args[6]: -100

【idea配置】在 idea 中添加参数

  • 点击Run下的Edit Configurations
  • 配置Configuration页中的Program arguments选项,就可以在idea中传入参数,参数之间用空格隔开。 如下图所示:

image.png

4、MVC设计模式

MVC是常用的设计模式之一,将整个程序分为三个层次: 视图模型层,控制器层,与数据模型层。 这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式,使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。

image.png

image.png