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的声明
- jdk7及之前版本:要求此局部变量显式的声明为
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方法
匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用,传参
- 调用的是子类重写的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
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中传入参数,参数之间用空格隔开。 如下图所示:
4、MVC设计模式
MVC是常用的设计模式之一,将整个程序分为三个层次: 视图模型层,控制器层,与数据模型层。 这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式,使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。