7 面向对象(下)
7.1 static
7.1.1 static的使用
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。
我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份。
例如,所有的中国人都有一个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用户代表国家名称的变量。
1、使用
- static:静态的。
- static可以用来修饰:属性、方法、代码块、内部类。
2、使用static修饰属性:静态变量
- 属性:按是否使用static修饰,又分为
- 静态属性
- 非静态属性(实例变量)
- 实例变量:
- 我们创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性。
- 当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
- 静态变量:
- 我们创建了类的多个对象,多个对象共享同一个静态变量。
- 当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
- 静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用。
- 静态变量的加载要早于对象的创建。
- 由于类只会加载一次,则静态变量在内存中也只会存在一份。
静态属性举例:System.in、Math.PI。
示例:
public class StaticTest { public static void main(String[] args) { Chinese c1 = new Chinese(); c1.name = "张三"; c1.age = 18; Chinese c2 = new Chinese(); c2.name = "李四"; c2.age = 19; c1.nation = "CHN"; System.out.println(c2.nation); } } class Chinese { String name; int age; static String nation; }
3、使用static修饰方法
随着类的加载而加载,可以通过“类.静态方法”的方式进行调用。
- 静态方法中,只能调用静态的方法或属性。
- 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
4、static注意点
- 在静态的方法内,不能使用this关键字、super关键字。
- 关于静态属性和静态方法的使用,都可以从生命周期的角度去理解。
5、开发中,如何确定一个属性是否要声明为static的?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 类中的常量也常常声明为static。
6、开发中,如何确定一个方法是否要声明为static的?
- 操作静态属性的方法,通常设置为static的。
- 工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections。
7.2.2 static应用举例
1、圆
package com.xianhuii.statictest; public class CircleTest { public static void main(String[] args) { Circle c1 = new Circle(); Circle c2 = new Circle(); Circle c3 = new Circle(2.0); System.out.println(c1.getId()); System.out.println(c2.getId()); System.out.println(c3.getId()); System.out.println(c1.getTotal()); } } class Circle { private double radius; private int id; private static int total; // 记录创建的圆的个数 private static int init = 1001; // static声明的属性被所有对象所共享 public Circle() { id = init++; total++; } public Circle(double radius) { this(); this.radius = radius; } public double getRadius() { return radius; } public static int getTotal() { return total; } public void setRadius(double radius) { this.radius = radius; } public int getId() { return id; } public double findArea() { return 3.14 * radius * radius; } }
2、银行账户
package com.xianhuii.statictest; public class Account { private int id; private String pwd = "000000"; private double balance; private static double interestRate; private static double minMoney = 1.0; private static int init = 1001; public Account() { id = init++; } public Account(String pwd, double balance) { this(); this.pwd = pwd; this.balance = balance; } public int getId() { return id; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public double getBalance() { return balance; } public static double getInterestRate() { return interestRate; } public static void setInterestRate(double interestRate) { Account.interestRate = interestRate; } public static double getMinMoney() { return minMoney; } public static void setMinMoney(double minMoney) { Account.minMoney = minMoney; } @Override public String toString() { return "Account{" + "id=" + id + ", pwd='" + pwd + '\'' + ", balance=" + balance + '}'; } } //======================= package com.xianhuii.statictest; import org.junit.Test; import static org.junit.Assert.*; public class AccountTest { @Test public void test1() { Account account1 = new Account(); Account account2 = new Account("qwerty", 2000); System.out.println(account1); System.out.println(account2); } }
7.1.3 单例(Singleton)设计模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
1、实现步骤
- 私有化类的构造器。
- 内部创建类的对象,此属性是静态的。
- 提供公共的静态方法,返回类的对象。
2、饿汉式
class Bank { private static Bank instance = new Bank(); private Bank() { } public static Bank getInstance() { return instance; } }
3、懒汉式
class Bank { private static Bank instance; private Bank() { } public static Bank getInstance() { if (instance == null) { synchronized (Bank.class) { if (instance == null) { instance = new Bank(); return instance; } } } return instance; } }
4、区分饿汉式和懒汉式
- 饿汉式
- 坏处:对象加载时间过长。
- 好处:天然就是线程安全的。
- 懒汉式
- 好处:延迟对象的创建。
5、单例模式的应用场景
优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、生产其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
举例:java.lang.Runtime(饿汉式)
应用场景
- 网站的计数器
- 应用程序的日志应用
- 数据库连接池
- 读取配置文件的类
- Windows的任务管理器
- Windows的回收站
7.2 main方法
1、main()方法的使用说明
main()方法作为程序的入口。
main()方法也是一个普通的静态方法。
package com.xianhuii.main; public class MainTest { public static void main(String[] args) { Main.main(new String[100]); } } class Main { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { args[i] = "args_" + i; System.out.println(args[i]); } } }
main()方法可以作为我们与控制台交互的方式。(之前,使用Scanner)
class Main { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { // args[i] = "args_" + i; System.out.println("*********" + args[i]); } } }
7.3 类的成员之四:代码块
1、代码块的作用
- 用来初始化类、对象。
2、代码块的分类
- 代码块如果有修饰的话,只能使用static。
- 静态代码块。
- 非静态代码块。
3、静态代码块
- 内部可以有输出语句。
- 随着类的加载而执行,而且只执行一次。
- 作用:初始化类的信息。
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。
- 静态代码块的执行要优先于非静态代码块的执行。
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构。
4、非静态代码块
- 内部可以有输出语句。
- 随着对象的创建而执行。
- 每创建一个对象,就执行一次非静态代码块。
- 作用:可以在创建对象时,对对象的属性等进行初始化。
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
5、对属性可以赋值的位置
默认初始化
显示初始化/非静态代码块
构造器中初始化
有了对象以后,可以通过“对象.属性”或“对象.方法”的方式,进行赋值
package com.xianhuii.block; public class OrderTest { public static void main(String[] args) { Order order = new Order(); System.out.println(order.orderId); // 3 } } class Order { { orderId = 4; } int orderId = 3; }
6、开发中代码块的使用举例
单例模式中,假如需要对单例对象进行初始化操作时,可以使用静态代码块。
7、练习
Leaf
package com.xianhuii.block; public class LeafTest { public static void main(String[] args) { new Leaf(); System.out.println(); new Leaf(); } } class Root { static { System.out.println("Root的静态初始化块"); } { System.out.println("Root的普通初始化块"); } public Root() { super(); System.out.println("Root的无参数的构造器"); } } class Mid extends Root { static { System.out.println("Mid的静态初始化块"); } { System.out.println("Mid的普通初始化块"); } public Mid() { super(); System.out.println("Mid的无参数的构造器"); } } class Leaf extends Mid { static { System.out.println("Leaf的静态初始化块"); } { System.out.println("Leaf的普通初始化块"); } public Leaf() { System.out.println("Leaf的无参数的构造器"); } } /* Root的静态初始化块 Mid的静态初始化块 Leaf的静态初始化块 Root的普通初始化块 Root的无参数的构造器 Mid的普通初始化块 Mid的无参数的构造器 Leaf的普通初始化块 Leaf的无参数的构造器 Root的普通初始化块 Root的无参数的构造器 Mid的普通初始化块 Mid的无参数的构造器 Leaf的普通初始化块 Leaf的无参数的构造器 */
Son
package com.xianhuii.block; class Father { static { System.out.println("1111111"); } { System.out.println("2222222"); } public Father() { System.out.println("3333333"); } } public class Son extends Father { static { System.out.println("4444444"); } { System.out.println("5555555"); } public Son() { System.out.println("6666666"); } public static void main(String[] args) { System.out.println("7777777"); new Son(); System.out.println("8888888"); new Son(); System.out.println("99999999"); } }
7.4 final
final:最终的。
1、final可以用来修饰的结构
- 类、方法、变量
2、final用来修饰一个类
此类不能被其他类所继承。
final class FinalA { } //class B extends FinalA {} 报错
比如:String类、System类、StringBuffer类、Integer等包装类。这些类的对象不可以被方法修改。
package com.xianhuii.finaltest; public class FinalTest { public static void main(String[] args) { String str = "before"; changeStr(str); System.out.println(str); // before Integer in = 1; changeInt(in); System.out.println(in); // 1 } public static void changeStr(String str) { str = "changed"; } public static void changeInt(Integer in) { in = 2; } }
3、final用来修饰方法
表明此方法不可以被重写。
class A { public final void show(){} } class B extends A { // public void show(){} 报错 }
比如:Object类中的getClass()
4、final用来修饰变量
此时的“变量”就称为是一个常量。
“变量”是基本数据类型时,赋值后其值不能改变。
“变量”是引用数据类型时,赋值后其地址不能改变。
class A { final static int WIDTH; final int LEFT; final int RIGHT; static { WIDTH = 0; } { LEFT = 1; } public A() { RIGHT = 1; } public static void main(String[] args) { final int num; num = 1; // num = 2; 编译不通过 } public void show(final int num) { // num = 2; 编译不通过 } }
final修饰实例属性:
- 必须在对象创建时完成赋值。
- 可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化。
final修饰静态变量(static final)——全局常量:
- 必须在类加载时完成赋值。
- 可以考虑赋值的位置有:显式初始化、静态代码块中初始化。
final修饰局部变量:
- 尤其是使用final修饰形参时,表明此时形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
- 修饰普通的局部变量是,赋值之后就不能再重新赋值。
5、练习
public int addOne(final int x) { // return ++x; 编译失败 return x + 1; } //======== public class Something { public static void main(String[] args) { Other o = new Other(); new Something().addOne(o); } public void addOne(final Other o) { o.i++; // o = new Other(); 编译失败 } } class Other { public int i; }
7.5 抽象类与抽象方法
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时候将一个父类设计得非常抽象,以至于它没有的具体的实例,这样的类叫做抽象类。
- abstract:抽象的。
- abstract可以用来修饰的结构:类、方法。
1、abstract修饰类:抽象类
此类不能实例化。
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)。
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作。
abstract class Person { String name; int age; public Person(){} public Person(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println("人吃饭"); } }
2、abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法体。
public abstract void walk();
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化。
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰。
3、abstract使用上的注意点
- abstract不能用来修饰属性、构造器等结构。
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类。
4、练习
package com.xianhuii.abstractdemo; public abstract class Employee { private String name; private int id; private double salary; public Employee(){} public Employee(String name, int id, double salary) { this.name = name; this.id = id; this.salary = salary; } public abstract void work(); } class Manager extends Employee { private double bonus; public Manager() {} public Manager(double bonus) { this.bonus = bonus; } public Manager(String name, int id, double salary, double bonus) { super(name, id, salary); this.bonus = bonus; } @Override public void work() { System.out.println("管理员工,提高公司运行的效率"); } } class CommonEmployee extends Employee { @Override public void work() { System.out.println("员工在一线车间生产"); } }
5、抽象类的匿名子类
package com.xianhuii.abstractdemo; public class PersonTest { public static void main(String[] args) { Employee e = new Employee() { @Override public void work() { System.out.println("重写抽象类的匿名类的abstract方法1"); } }; method(e); method(new Employee() { @Override public void work() { System.out.println("重写抽象类的匿名类的abstract方法2"); } }); } public static void method(Employee employee) { employee.work(); } }
6、抽象类的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
7、练习
package com.xianhuii.abstractdemo; public abstract class Employee { private String name; private int number; private MyDate birthday; public Employee() { } public Employee(String name, int number, MyDate birthday) { this.name = name; this.number = number; this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public MyDate getBirthday() { return birthday; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } @Override public String toString() { return "name='" + name + '\'' + ", number=" + number + ", birthday=" + birthday.toDateString() + '}'; } public abstract double earnings(); } //============================ package com.xianhuii.abstractdemo; public class MyDate { private int year; private int month; private int day; public MyDate(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } public String toDateString() { return year + "年" + month + "月" + day + "日"; } } //============ package com.xianhuii.abstractdemo; public class SalariedEmployee extends Employee { private double monthlySalary; public SalariedEmployee() { } public SalariedEmployee(String name, int number, MyDate birthday) { super(name, number, birthday); } public SalariedEmployee(double monthlySalary) { this.monthlySalary = monthlySalary; } public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) { super(name, number, birthday); this.monthlySalary = monthlySalary; } public double getMonthlySalary() { return monthlySalary; } public void setMonthlySalary(double monthlySalary) { this.monthlySalary = monthlySalary; } @Override public double earnings() { return monthlySalary; } @Override public String toString() { return "SalariedEmployee{" + super.toString() + '}'; } } //==================== package com.xianhuii.abstractdemo; public class HourlyEmployee extends Employee { private int wage; private int hour; public HourlyEmployee() { } public HourlyEmployee(String name, int number, MyDate birthday) { super(name, number, birthday); } public HourlyEmployee(int wage, int hour) { this.wage = wage; this.hour = hour; } public HourlyEmployee(String name, int number, MyDate birthday, int wage, int hour) { super(name, number, birthday); this.wage = wage; this.hour = hour; } public int getWage() { return wage; } public void setWage(int wage) { this.wage = wage; } public int getHour() { return hour; } public void setHour(int hour) { this.hour = hour; } @Override public double earnings() { return wage * hour; } @Override public String toString() { return "HourlyEmployee{" + super.toString() + '}'; } } //============= package com.xianhuii.abstractdemo; import java.util.Scanner; public class PayrollSystem { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入当月的月份:"); int month = scanner.nextInt(); scanner.close(); Employee[] employees = new Employee[2]; employees[0] = new SalariedEmployee("马森", 1002, new MyDate(1992, 2, 28), 10000); employees[1] = new HourlyEmployee("潘玉生", 2001, new MyDate(1991, 5, 6), 60, 240); for (int i = 0; i < employees.length; i++) { System.out.println(employees[i]); double salary = employees[i].earnings(); System.out.println("月工资为:" + salary); if (month == employees[i].getBirthday().getMonth()) { System.out.println("生日快乐,奖励100元。"); } } } }
7.6 接口(interface)
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要……则必须能……”的思想。继承是一个“是不是”的关系,而接口实现则是“能不能”的关系。
接口的本质是契约、标准、规范,指定好后大家都要遵守。
1、接口的使用
接口使用interface来定义。
Java中,接口和类是并列的结构。
如何定义接口:定义接口中的成员。
接口中不能定义构造器!意味着接口不可以实例化。
在Java开发中,接口通过让类去实现(implements)的方式使用。如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化;如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类。
class Plane implements Flyable { @Override public void fly() { System.out.println("通过引擎起飞"); } @Override public void stop() { System.out.println("驾驶员减速停止"); } }
Java类可以实现多个接口 —> 弥补了Java单继承性的局限性。
interface Flyable { // 全局常量 public static final int MAX_SPEED = 7900; // 第一宇宙速度 int MIN_SPEED = 1; // 省略了:public static final // 抽象方法 public abstract void fly(); void stop(); // 省略了:public abstract } interface Attackable { void attack(); } class Bullet implements Flyable, Attackable { @Override public void fly() { } @Override public void stop() { } @Override public void attack() { } }
- 格式:
class A extends B implements C, D {}
- 格式:
接口和接口之间可以继承,而且可以多继承。
interface A { void method1(); } interface B { void method2(); } interface C extends A, B {}
接口的具体使用,体现多态性。
接口实际上可以看做是一种规范。
开发中,体会面向接口编程。
package com.xianhuii.intefacedemo; public class USBTest { public static void main(String[] args) { Computer computer = new Computer(); // 1.创建了接口的非匿名实现类的非匿名对象 Flash flash = new Flash(); computer.transferData(flash); // 2.创建了接口的非匿名实现类的匿名对象 computer.transferData(new Printer()); // 3.创建了接口的匿名实现类的非匿名对象 USB phone = new USB() { @Override public void start() { System.out.println("手机启动"); } @Override public void stop() { System.out.println("手机停止"); } }; computer.transferData(phone); // 4.创建了接口的匿名实现类的匿名对象 computer.transferData(new USB() { @Override public void start() { System.out.println("启动"); } @Override public void stop() { System.out.println("停止"); } }); } } class Computer { public void transferData(USB usb) { usb.start(); System.out.println("具体传输数据的细节"); usb.stop(); } } interface USB { // 常量:定义了长、宽、最大最小的传输速度等 void start(); void stop(); } class Flash implements USB { @Override public void start() { System.out.println("U盘开启工作"); } @Override public void stop() { System.out.println("U盘停止工作"); } } class Printer implements USB { @Override public void start() { System.out.println("打印机开启工作"); } @Override public void stop() { System.out.println("打印机停止工作"); } }
2、接口中的成员
JDK7及以前:只能定义全局常量和抽象方法
全局常量:public static final(书写时,可以省略)
抽象方法:public abstract(书写时,可以省略)
interface Flyable { // 全局常量 public static final int MAX_SPEED = 7900; // 第一宇宙速度 int MIN_SPEED = 1; // 省略了:public static final // 抽象方法 public abstract void fly(); void stop(); // 省略了:public abstract }
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
接口中定义的静态方法,只能通过接口来调用。
通过实现类的对象,可以调用接口中的默认方法。
如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法。
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。—> 类优先原则
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写的此方法的情况下,报错。—> 接口冲突。这就需要我们必须在实现类中重写此方法。
在实现类中调用接口中的默认方法:CompareA.super.method3();
package com.xianhuii; public interface CompareA { public static void method1() { System.out.println("CompareA:北京"); } static void method2() { System.out.println("CompareA:北京"); } public default void method3() { System.out.println("CompareA:上海"); } default void method4() { System.out.println("CompareA:上海"); } } //======== package com.xianhuii; public interface CompareB { public default void method3() { System.out.println("CompareB:北京"); } } //========== package com.xianhuii; public class SuperClass { public void method3() { System.out.println("SuperClass:北京"); } } //============ package com.xianhuii; public class SubClassTest { public static void main(String[] args) { SubClass s = new SubClass(); // s.method1(); Static method may be invoked on containing interface class only CompareA.method1(); CompareA.method2(); s.method3(); s.method4(); } } class SubClass extends SuperClass implements CompareA, CompareB { public void method4() { System.out.println("SubClass:上海"); } public void method3() { System.out.println("SubClass:北京"); } public void myMethod() { method3(); // 调用自己定义的重写的方法 super.method3(); // 调用的是父类中声明的 // 调用接口中的默认方法 CompareA.super.method3(); CompareB.super.method3(); } }
3、接口的应用:Proxy模式(Proxy)
Proxy模式是Java开发中使用较多的一种设计模式。Proxy设计就是为其他对象提供一种Proxy以控制对这个对象的访问。
应用场景:
- 安全Proxy
- 远程Proxy
- 延迟加载
分类:
静态Proxy
动态Proxy
package com.xianhuii.intefacedemo; public class NetWorkTest { public static void main(String[] args) { Server server = new Server(); ProxyServer proxyServer = new ProxyServer(server); proxyServer.browse(); } } interface NetWork { void browse(); } class Server implements NetWork { // 被Proxy类 @Override public void browse() { System.out.println("真实的服务器访问网络"); } } class ProxyServer implements NetWork { // Proxy类 private NetWork work; public ProxyServer(NetWork work) { this.work = work; } public void check() { System.out.println("联网之前的检查工作"); } @Override public void browse() { check(); work.browse(); } }
4、接口的应用:工厂设计模式
实现了创建者和调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
5、练习
interface AA { int x = 0; } class BB { int x = 1; } class CC extends BB implements AA { public void pX() { // 编译不通过:Reference to 'x' is ambiguous, both 'BB.x' and 'AA.x' match // System.out.println(x); } public static void main(String[] args) { new CC().pX(); } }
7.7 类的成员之五:内部类
当一个事务的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事务提供服务,那么整个内部的完整结构最好使用内部类。
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
内部类一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
内部类的名字不能与包含它的外部类类名相同。
分类:
成员内部类(static成员内部类和非static成员内部类)
局部内部类(方法内、代码块内、构造器内)、匿名内部类
class Person { // 静态成员内部类 static class Dog { } // 非静态成员内部类 class Bird { } public void method() { // 局部内部类 class AA { } } { // 局部内部类 class BB { } } public Person() { class CC { } } }
7.7.1 成员内部类
class Person { String name; int age; public void eat() { System.out.println("人,吃饭"); } // 静态成员内部类 abstract static class Dog { String name; int age; public void show() { System.out.println("卡拉是条狗"); } } // 非静态成员内部类 final class Bird { String name; public Bird(){} public void sing() { System.out.println("我是一只鸟"); Person.this.eat(); // 调用外部类的非静态属性 } } }
1、作为一个类
可以定义属性、方法、构造器等。
可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承。
可以被abstract修饰
2、作为一个成员
调用外部类的结构
可以被static修饰
可以被4种不同的权限修饰
3、关注如下三个问题
- 如何实例化成员内部类的对象
// 创建Dog实例(静态成员内部类) Person.Dog dog = new Person.Dog(); dog.show(); // 创建Bird实例(非静态的成员内部类) Person.Bird bird = new Person().new Bird(); bird.sing();
- 如何在成员内部类中区分调用外部类的结构
class Person { String name; // 非静态成员内部类 class Bird { String name; public void display(String name) { System.out.println(name); // 形参 System.out.println(this.name); // 内部类的属性 System.out.println(Person.this.name); // 外部类的属性 } } }
7.7.2 局部内部类
1、开发中局部内部类的使用
package com.xianhuii; 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 public int compareTo(Object o) { return 0; } }; } }