1.static关键字
通过new产生对象,并分配内存空间,然后对象.方法或对象.属性进行调用。但有时无论产生多少对象, 某些特定的数据在内存空间里只有一份,使用static。
1.1.类属性、类方法的设计思
类属性 | 类方法 | |
含义 | static修饰的变量叫静态变量或类变量 | static修饰的方法叫静态方法或类方法 |
调用 | 类名.变量 | 类名.方法() |
注意 | static修饰的变量和方法从属于类(根据调用来说的);普通变量和方法从属于对象;静态方法不能调用普通方法 | |
使用范围 | static可修饰属性、方法、代码块、内部类 | |
特点 | 随着类的加载而加载;优先于对象加载;修饰的成员,被所有对象所共享;访问权限允许时,可不创建对象,直接被类调用 |
类属性是该类各个对象共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
1.2.类变量(class Variable)
类变量(类属性)由该类的所有实例共享。
实例变量(非静态变量) | 静态变量 |
用static修饰的变量,我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象的非静态属性时,不会导致其他对象中同样的属性值的修改。每创造一个对象,JVM都会为他分配一个新的内存;只能通过对象.变量去调用 | 我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。静态变量:JVM只为静态变量分配一次内存;可以通过类名.变量去调用或者对象.变量去调用 |
//static的关键的应用
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
System.out.println(c1.getId());// 1001
System.out.println(c2.getId());// 1002
System.out.println(Circle.getTotal());// 2
}
}
class Circle {
private double radius;
private int id;
public Circle() {
id = init++;// 多个对象共享
total++;
}
private static int total;// 圆的个数
private static int init = 1001;
public double findArea() {
return 3.14 * radius * radius;
}
//省略getter和setter方法
static修饰属性:静态变量随着类的加载而加载,可以通过“类名.静态变量”的方式进行调用;静态变量的加载要早于对象的创建;由于类置灰加载一次,则静态变量在内存中只会存在一份,存在方法区的静态域中;静态属性举例:System.out;Math.PI。
public class StaticText {
public static void main(String[] args) {
//访问方式:类名.类属性,类名.类方法
Chinese.nation = "中国"; // 不用创建对象就可以访问静态成员
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHN"; //编译不通过
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c2.nation = "CHINA"; //编译不通过
//编译不通过
//Chinese.age = 10;
System.out.println(c1.nation); //编译不通过
c1.eat();
Chinese.show();
//编译不通过
//Chinese.eat();
}
}
//中国人
class Chinese{
String name;
int age;
static String nation;
public void eat(){
System.out.println("中国人吃中餐");
}
public static void show(){
System.out.println("我是一个中国人!");
//编译错误,不能调用非静态的结果
//eat();
//name = "Tom";
//可以调用静态的结构
System.out.println(Chinese.nation);
wolk();
}
public static void wolk(){
}
}
静态变量的内存解析
1.3.类方法(class method)
没创建实例时,可用类名.方法名()的形式访问由static修饰的类方法。使用static修饰方法:静态方法,随着类的加载而加载,可以通过“类.静态方法”的方式进行调用。静态方法中,只能调用静态的方法或属性,不能访问类的非static的结构;非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性。
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++; //非法
return total;
}
public Person() {
total++;
id = total;
}
}
public class PersonTest {
public static void main(String[] args) {
// 没有创建对象也可以访问静态方法
System.out.println("Number of total is " + Person.getTotalPerson());
}
}
static修饰的方法不能被重写因为不需要实例就可以访问static方法,因此static方法内部不能有this,不能使用super;静态属性、方法的使用,可从生命周期的角度理解。
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
this.total=total; //非法,在static方法中不能有this,也不能有super
}
public Person() {
total++;
id = total;
}
}
public class PersonTest {
public static void main(String[] args) {
Person.setTotalPerson(3);
}
}
开发中,属性可被多个对象所共享,不会随着对象改变而变化,设置为静态变量;常量也常常声明为static。操作静态属性的方法,通常设置为static的;工具类中的方法,习惯上声明为static的。比如:Math/Arrays/Collections。
在同一个类中:静态方法可直接调用静态方法;静态方法只能通过对象.方法去调用普通方法;普通方法可以调用一切。在不同类中:静态方法可以通过类名.方法去调用;普通方法只能通过对象.方法调用。黄金法则:普通方法:对象.方法;静态方法:类名.方法。
import com.school.java3.Person;
//封装和static
public class Person {
private String name;
private int age;
private String sex;// 普通变量
public static int LIFE_COUNT = 1;// 生命次数----静态变量
// static 通常和final一起配合使用称为静态常量 特点是:
// 声明的时候如果没有赋值,后面可以有一次赋值的机会
// 如果声明的时候已经赋值,那么以后不可更改
// 静态常量一般用于值不改变的情况下
public static final int LIFE_COUNT1 = 2;// 生命次数----静态常量
public void show() {
System.out.println("name:" + name + "\tage:" + age + "\tsex:" + sex);
}
public static void show1() {// 静态方法
System.out.println("静态方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 130 || age <= 0) {
System.out.println("年龄不合法");
return;
}
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public static void main(String[] args) {
Person person = new Person();
//属性没有封装之前可以任意赋值--不合理
// person.age = 18;
// person.setAge(18);
person.setAge(-18);
person.setName("小张");
person.setSex("男");
System.out.println(person.getName());
person.show();
System.out.println("----------static关键字------------");
// 访问静态变量==》类名加.去访问,不用创建对象就可以直接调用
Person.LIFE_COUNT = 3;
//错误赋值因为静态常量在声明的时候已经赋值,后期不可更改
// Person.LIFE_COUNT1 = 2;
// 访问静态方法===》类名加.去访问,不用创建对象就可以直接调用
Person.show1();
}
}
2.单例 (Singleton)设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去自己再思考和摸索。像经典的棋谱,不同的棋局用不同的棋谱。”套路”。
类的单例设计模式:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,首先将类的构造器的访问权限设置为private,就不能用new在类的外部产生类的对象,但在类内部仍可以产生该类的对象。因为在类的外部无法生成类对象,只能调用该类的某个静态方法获得类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
单例(Singleton)设计模式-饿汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
单例(Singleton)设计模式-懒汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
饿汉式:坏处:对象加载时间过长;好处:线程安全;懒汉式:好处:延迟对象的创建;目前写法的坏处:线程不安全。-----》多线程内容时,在修改
单例模式的优点:由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
2.1.单例(Singleton)设计模式-应用场景
网站的计数器,一般也是单例模式实现,否则难以同步;应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加;数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取;Application 也是单例的典型应用;Windows的Task Manager (任务管理器)就是很典型的单例模式;Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程
中,回收站一直维护着仅有的一个实例。
3.main方法的语法
Java虚拟机需要调用类的main()方法,访问权限必须是public,虚拟机在执行main()方法时不必创建对象,那么该方法由static修饰,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
因为main() 方法是静态的,就不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
main()方法的使用说明:main()方法作为程序的入口;main()方法也是一个普通的静态方法;main()方法可以作为我们与控制台交互的方式(之前,使用Scanner)。
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
public class MainTest {
public static void main(String[] args) {
Main.main(new String[100]);
}
}
class Main{
public static void main(String[] args) {
args = new String[100];
for(int i = 0;i < args.length;i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
4.类的成员之四:代码块
代码块(或初始化块)的作用:对Java类或对象进行初始化
代码块(或初始化块)的分类:一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块,没有使用static修饰的,为非静态代码块;static代码块通常用于初始化static的属性。
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
...... //其它属性或方法声明
}
静态代码块:可以有输出语句;可以对类的属性、类的声明进行初始化操作;不可以对非静态的属性初始化,即:不可以调用非静态的属性和方法;若有多个静态的代码块,那么按照从上到下的顺序依次执行;静态代码块的执行要先于非静态代码块;静态代码块随着类的加载而加载,且只执行一次。
非静态代码块:可以有输出语句;可以对类的属性、类的声明进行初始化操作;除了调用非静态的结构外,还可以调用静态的变量或方法;若有多个非静态的代码块,那么按照从上到下的顺序依次执行;每次创建对象的时候,都会执行一次。且先于构造器执行。
class Person {
// 属性
String name;
int age;
static String desc = "我是一个人";
// 构造器
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 非static的代码块
{
System.out.println("hello, block - 2");
}
{
System.out.println("hello, block - 1");
// 调用非静态结构
age = 1;
eat();
// 调用静态结构
desc = "我是一个爱学习的人1";
info();
}
// static的代码块
static {
System.out.println("hello,static block-2");
}
static {
System.out.println("hello,static block-1");
// 调用静态结构
desc = "我是一个爱学习的人";
info();
// 不可以调用非静态结构
// eat();
// name = "Tom";
}
// 方法
public void eat() {
System.out.println("吃饭");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void info() {
System.out.println("我是一个快乐的人!");
}
}
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
Person.info();
}
}
总结:程序中成员变量赋值的执行顺序:声明成员变量的默认初始化 》显式初始化,多个初始化块依次被执行(同级别下按先后顺序执行)》构造器在对成员进行初始化操作 》通过”对象.属性"或"对象.方法"的方式,可以多次给属性赋值。
静态代码块执行时机:类被加载的时候执行;构造函数只针对当前对象初始化。
优先级别:静态代码块 》构造代码块 》构造函数。
public class Person {
String name;
int age;
String sex;
public Person() {
System.out.println("无参构造被调用");
}
public Person(String name, int age, String sex) {// 有参构造
super();
System.out.println("有参构造3个参数被调用");
this.name = name;
this.age = age;
this.sex = sex;
}
// 吃的功能
public void eat() {
System.out.println("吃饭");
}
{// 构造代码块
System.out.println("构造代码块");
}
static {// 静态代码块--》的执行时机在类被加载的时候就执行
System.out.println("静态代码块1");
}
static {// 静态代码块
System.out.println("静态代码块2");
}
public static void main(String[] args) {
Person person = new Person("李四", 28, "男");
person.eat();
}
5.final关键字
关键字final能修饰类、变量和方法,表示“最终的”。
final标记的类不能被继承,提高安全性和程序的可读性:String类、System类、StringBuffer类;final标记的方法不能被子类重写:比如:Object类中的getClass();final标记的变量(成员变量或局部变量)即称为常量,名称大写,且只能被赋值一次,标记的成员变量必须在声明时、每个构造器中或代码块中显式赋值,然后才能使用。
final double MY_PI = 3.14;final修饰局部变量:使用final修饰形参式,表明此形参是一个常量,当调用此方法时,给常量形参赋值一个实参, 一旦赋值以后,就只能在方法内使用此形参,但不能进行重新赋值。
// final修饰类
final class A{
}
class B extends A{ //错误,不能被继承。
}
// final修饰方法
class A {
public final void print() {
System.out.println("A");
}
}
class B extends A {
public void print() { // 错误,不能被重写。
System.out.println("你好");
}
}
// final修饰变量——常量
class A {
private final String INFO = "atguigu"; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
//INFO = "你好";
}
}
常量名要大写,内容不可修改。——如古代皇帝的圣旨。static final:全局常量。
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
final int J;
J = 20;
J = 30; // 非法
}
}
总结:由父及子,静态先行
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的无参数的构造器");
}
public Mid(String msg) {
// 通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:" + msg);
}
}
class Leaf extends Mid {
static {
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf() {
// 通过super调用父类中有一个字符串参数的构造器
super("你好");
System.out.println("Leaf的构造器");
}
}
public class LeafTest {
public static void main(String[] args) {
new Leaf();
System.out.println();
new Leaf();
}
}
// 输出
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:你好
Leaf的普通初始化块
Leaf的构造器
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:你好
Leaf的普通初始化块
Leaf的构造器
由父及子,静态先行
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
}
// 输出
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
对属性可以赋值的位置: ①默认初始化;②显式初始化/⑤在代码块中赋值;③构造器中初始化;④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
执行的先后顺序:① - ② / ⑤ - ③ - ④
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.orderId);// 4
}
}
class Order {
int orderId = 3;
{
orderId = 4;
}
}
6.练习
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。编写主类,使用银行账户类,输入、输出3个储户的上述信息。 考虑:哪些属性可以设计成static属性。
public class Account {
private int id;
private String pwd;
private double balance;
public int getId() {
return id;
}
private static double interestRate;
private static int init = 1001;
public Account(){
id = init++;
}
public Account(String pwd,double balance){
id = init++;
this.balance = balance;
this.pwd = pwd;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
}
public double getBalance() {
return balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", pwd=" + pwd + ", balance=" + balance + "]";
}
}
// 测试
public class AccountTest {
public static void main(String[] args) {
Account a1 = new Account();
Account a2 = new Account("123456",200);
System.out.println(a1);
System.out.println(a2);
}
}
// 输出
Account [id=1001, pwd=null, balance=0.0]
Account [id=1002, pwd=123456, balance=200.0]
7.抽象类与抽象方法
随着继承层次中新子类的定义,类变得越来越具体,而父类则更通用和一般。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
用abstract关键字来修饰一个类,这个类叫做抽象类。
用abstract来修饰一个方法,该方法叫做抽象方法。抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如:public abstract void talk();
含有抽象方法的类必须被声明为抽象类。
抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
抽象类:抽象很多类相同的功能,很多类具有相同的功能,功能主体不同,这时可以进行向上抽取。抽象类特点:1.抽象类必须用abstract进行修饰;2.抽象类中可以定义普通方法也可以定义抽象方法;3.抽象类不可以new对象。
抽象方法:1.抽象方法必须使用abstract进行修饰;2.抽象方法没有方法体。3.抽象方法必须在抽象类中。特殊:抽象类中可以没有抽象方法也可以没有普通方法(可以什么方法都没有),这样操作的目的,仅仅不让其创造对象。
抽象方法和普通方法的区别:1.普通方法必须要有方法体,抽象方法不能有方法体(大括号也没有);2.抽象方法要用abstract修饰;2.抽象方法必须存在于抽象类中;4.抽象类要用abstract修饰;5.普通类可以实例化,抽象类不能实例化。简单的说,抽象类是一个不能实例化的类,它可以具有抽象方法或者普通方法。
// 抽象类
public abstract class Student {
public abstract void study();// 抽象方法
public void sleep() {// 普通方法
System.out.println("睡觉");
}
}
// 子类
public class SoftStudent extends Student {
public void study() {
System.out.println("study-软件");
}
}
// 子类
public class MedicineStuent extends Student {
public void study() {
System.out.println("study-学医");
}
}
// 测试
public class Test {
public static void main(String[] args) {
SoftStudent softStudent = new SoftStudent();
softStudent.study();
System.out.println("--------------");
// 多态--父类的引用指向其子类
Student student = new MedicineStuent();
student.study();
// 报错,抽象类不可以被实例化,不能new对象
// Student student2 = new Student();
}
}
abstract:抽象的,可修饰类和方法。修饰类时叫抽象类:一定要有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程),开发中,都会提抽象类的子类,让子类对象实例化,完成相关的操作。
修饰方法时叫抽象方法:只有方法声明,没有方法体;包含抽象方法的类,一定是抽象类。反之,抽象类可以没有抽象方法;若子类重写了父类中的所有的抽象方法,子类方可实例化;若子类没有重写父类中的所有抽象方法,则子类也是一个抽象类,使用abstract修饰。
public class AbstractTest {
public static void main(String[] args) {
//一旦Person类抽象了,就不可实例化
// Person p1 = new Person();
// p1.eat();
}
}
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("人吃饭");
}
//不是抽象方法
public void show() {
}
//抽象方法
public abstract void sleep();
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
}
public void sleep() {
System.out.println("学生需要睡觉");
}
}
不能用abstract修饰变量、代码块、构造器、私有方法、静态方法、final的方法、final的类。
abstract class A {
abstract void m1();
public void m2() {
System.out.println("A类中定义的m2方法");
}
}
class B extends A {
void m1() {
System.out.println("B类中定义的m1方法");
}
}
public class Test {
public static void main(String args[]) {
A a = new B();
a.m1();
a.m2();
}
}
抽象类应用:模型化那些父类无法确定全部实现,而由其子类提供具体实现的对象的类。
解决方案:Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。
抽象类的优势:1.抽象类可以将已经实现的方法提供给其子类使用,使代码可以被复用,例如:鸟的飞行和叫的方法,不需在子类重复实现。2.抽象类中的抽象方法在子类中重写,保证了子类还具有自身的独特性,例如:每个鸟都有自己独特的攻击行为。3.通过抽象类指向其子类的对象,可以实现多态。
// 员工类
// 父类/基类/超类
public abstract class Employee {
private String name;
private int baseMoney;
public abstract void calaMony();// 计算薪水
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getBaseMoney() {
return baseMoney;
}
public void setBaseMoney(int baseMoney) {
this.baseMoney = baseMoney;
}
public Employee() {
super();
}
public Employee(String name, int baseMoney) {
super();
this.name = name;
this.baseMoney = baseMoney;
}
}
// 子类
public class Emp extends Employee {
@Override
public void calaMony() {
System.out.println("姓名:" + getName() + ",薪水:" + getBaseMoney());
}
public Emp() {
super();
}
public Emp(String name, int baseMoney) {
super(name, baseMoney);
}
}
// 子类
// 经理类
public class Manager extends Employee {
private int yearMoney;// 年终奖
@Override
public void calaMony() {
System.out.println("姓名:" + getName() + ",薪水:" + (getBaseMoney() + getYearMoney()));
}
public int getYearMoney() {
return yearMoney;
}
public void setYearMoney(int yearMoney) {
this.yearMoney = yearMoney;
}
public Manager(String name, int baseMoney, int yearMoney) {
super(name, baseMoney);
this.yearMoney = yearMoney;
}
}
// 子类
// 销售员类
public class Seller extends Employee {
// 软件数量
private int softCount;
@Override
public void calaMony() {
System.out.println("姓名:" + getName() + ",薪水:" + (getBaseMoney() + softCount * 1000));
}
public Seller(String name, int baseMoney, int softCount) {
super(name, baseMoney);
this.softCount = softCount;
}
public Seller() {
super();
}
}
// 测试类
public static void main(String[] args) {
// 多态
Employee employee = new Manager("王经理", 2000, 10000);
employee.calaMony();
}
}
7.1.多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现了一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
public class TemplateTest {
public static void main(String[] args) {
subTemplate template = new subTemplate();
template.spendTime();
}
}
abstract class Template {
public void spendTime() {
long start = System.currentTimeMillis();
code();// 不确定的部分,易变的部分
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));// 2
}
public abstract void code();
}
class subTemplate extends Template {
@Override
public void code() {
for (int i = 2; i <= 1000; i++) {
boolean isFlag = true;
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
isFlag = false;
break;
}
}
if (isFlag) {
System.out.println(i);// 1000以内的素数
}
}
}
}
7.2.抽象类的应用:模板方法的涉及模式
解决的问题:当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
abstract class Template {
public final void getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("执行时间是:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template {
public void code() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:数据库访问的封装;Junit单元测试; JavaWeb的Servlet中关于doGet/doPost方法调用;Hibernate中模板程序;Spring中JDBCTemlate、HibernateTemplate等。
抽象类的匿名子类
public class PersonTest {
public static void main(String[] args) {
method(new Student("Tom",12));// 匿名对象
Worker worker = new Worker();
merhod1(worker);//非匿名的类非匿名的对象
merhod1(new Worker());//非匿名的类匿名的对象
//创建了一匿名子类的对象:person
Person person = new Person() {
@Override
public void sleep() {
System.out.println("睡觉");
}
};
merhod1(person);
System.out.println("------------------------");
//创建匿名子类的匿名对象
merhod1(new Person(){
@Override
public void sleep() {
System.out.println("好好睡觉");
}
});
}
public static void merhod1(Person s){
s.eat();
s.sleep();
}
public static void method(Student s) {
}
}
class Worker extends Person{
@Override
public void sleep() {
}
}
// 输出
人吃饭
人吃饭
人吃饭
睡觉
------------------------
人吃饭
好好睡觉
8.接口
概述:有时必须从几个类中派生出一个子类,继承它们所有的属性和方法,但Java不支持多重继承。而接口可得到多重继承的效果;另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、移动硬盘等都支持USB连接。
接口就是规范,定义一组规则,体现现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系;接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口:是一种标准或约定一个特殊的抽象类(当抽象类只有抽象方法,就可将此类定义为接口)。class用来定义类;interface用来定义接口。类和类通常会有继承关系extends,类和接口是实现关系implements,接口和接口是继承关系。类可实现多个接口,是Java支持单继承的补充。接口中的成员:静态常量(public static final int NUM = 3;)和抽象方法。
接口和类是并列的两个结构。JDK7及之前,只能定义全局常量和抽象方法:全局常量:public static final修饰,但定义时可省略;抽象方法:public abstract修饰,JDK8除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。接口中的方法都是public。
public interface CompareA {
//静态方法
public static void merhod1(){
System.out.println("CompareA:北京");
}
//默认方法
public default void merhod2(){
System.out.println("CompareA:北京");
}
default void merhod3(){
System.out.println("CompareA:北京");
}
}
public interface CompareB {
default void method3() {
System.out.println("SuperClass:上海");
}
}
接口(interface)是抽象方法和常量值定义的集合。
特点:用interface来定义;接口中的所有成员变量都默认是由public static final修饰的;接口中的所有抽象方法都默认是由public abstract修饰的;接口中没有构造器:意味着接口不可以实例化;接口与接口之间可以继承,而且可以多继承,也就是接口采用多继承机制。
Java开发中,接口让类实现(implements)的方式来使用;如果实现类覆盖了接口中的所有抽象方法,则实现类就可以实例化;如果实现类没重写所有抽象方法,则实现类仍是抽象类;Java类可以实现多个接口--->弥补了Java单继承的局限性。
public class SubClassTest {
public static void main(String[] args) {
SubClass subClass = new SubClass();
// subClass.merhod1();
// SubClass.merhod1();
// 知识点1:接口定义的静态方法,只能通过接口来调用
CompareA.merhod1();
// 知识点2,通过实现类的对象,可以调用接口中的默认方法
// 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
subClass.merhod2();
//知识点3:如果子类(或实现)继承的父类和实现的接口中声明了同名同参数的方法
//那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法--》类优先原则
subClass.merhod3();
//知识点4:如果子类实现了多个接口,而这多个接口中定义了同名同参数的方法
//那么实现类在没有重写此方法的情况下,报错--》接口冲突,需要我们必须在实现类中重写此方法
}
}
class SubClass extends SuperClass implements CompareA,CompareB {
public void method3() {
System.out.println("SuperClass:上海");
}
//知识点5:如何在子类或实现类的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的方法
//调用接口中的默认方法
CompareA.super.merhod3();
CompareB.super.method3();
}
}
定义Java类的语法格式:先extends,后implements:class SubClass extends SuperClass implements InterfaceA{ };一个类可以实现多个接口,接口也可以继承其它接口;实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类;接口的主要用途就是被实现类实现。(面向接口编程)。
public class USBTest {
public static void main(String[] args) {
Computer computer = new Computer();
//创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
flash.start();
//创建接口的非匿名实现类的匿名对象
//创建类接口名副其实类的
//4.创建了接口的匿名实现类的匿名对象
}
}
interface USB{
//常量:定义了长、宽、最小最大的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
}
@Override
public void stop() {
}
}
class Computer{
}
与继承关系类似,接口与实现类之间存在多态性; 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyavle.MAX_SPEED);
System.out.println(Flyavle.MIN_SPEED);
Plane p1 = new Plane();
p1.fly();
}
}
interface Flyavle {
// 全局常量
public static final int MAX_SPEED = 7900;// 第一宇宙速度
int MIN_SPEED = 1;// 省略了public static final
// 抽象方法
public abstract void fly();
// 省略了public abstract
void stop();
}
interface Attackable{
void attack();
}
class Plane implements Flyavle {
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
abstract class Kit extends Object implements Flyavle {
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
}
class Bullet implements Flyavle,Attackable{
@Override
public void attack() {
}
@Override
public void fly() {
}
@Override
public void stop() {
}
}
interface AA{
void aa();
}
interface BB{
void bb();
}
interface CC extends AA,BB{
void cc();
}
抽象类和接口的区别:1.抽象类中可以定义抽象方法,也可以定义普通方法,接口中只有抽象方法2.抽象类用abstract定义修饰,接口用interface定义3.抽象类方法可以有定义,也可以有实现,接口中方法只有定义;4.当功能需求积累时用抽象类,不需要积累时用接口(功能需要扩充时用抽象类,不需要扩充时用接口)。
// 父类接口
public interface InterfaceA {// 接口中的成员,省略了默认修饰符
int NUM = 8;// 常量
void method1();// 抽象方法
}
// 接口
public interface InterfaceB extends InterfaceA {
}
// 子类接口
public interface InterfaceC extends InterfaceB {
}
// 子类接口
public interface InterfaceD {
}
// 普通实现接口
public class ExD implements InterfaceC,InterfaceD{
@Override
public void method1() {
System.out.println("方法1");
}
}
// 测试
public class Test {
public static void main(String[] args) {
InterfaceA iA = new ExD();// 多态,接口的引用指向了子类对象
iA.method1();
}
}
实现多个接口:一个类只能继承一个父类,但能同时实现多个接口,也可以同时继承抽象类和实现接口。语法:class 类名 extends 父类名 implements 接口1,接口2,……{//类的成员},注意:extends必须位于implements之前;一个类实现多个接口,要求实现多个接口中的方法。
面向对象设计原则:1.抽取出代码中变化的行为,形成接口 ;2.多用组合,少用继承 ;3.针对接口编程,不依赖于具体实现;4.针对扩展开放,针对改变关闭。
// usb接口
public interface UsbInterface {
void service();// usb接口可以提供服务
}
// 实现类
public class UsbFengShan implements UsbInterface {
@Override
public void service() {
System.out.println("风扇吹起来");
}
}
// 实现类
public class UsbTV implements UsbInterface {
@Override
public void service() {
System.out.println("电视开始播放");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
// 多态 接口的引用指向其子类对象
UsbInterface uf = new UsbTV();
uf.service();
System.out.println("--------------------");
// 多态 接口的引用指向其子类对象
UsbInterface uf1 = new UsbFengShan();
uf1.service();
}
}
8.1.抽象类练习
定义Employee类,该类包含:private成员变量name,number,birthday,其中birthday 为MyDate类的对象;abstract方法earnings(); toString()方法输出对象的name,number和birthday。
public abstract class Employee {
private String name;
private int number;
private Mydate birthday;
public abstract double earnings();
// 省略getter和setter方法
public String getName() {
return name;
}
public Employee(String name, int number, Mydate birthday) {
super();
this.name = name;
this.number = number;
this.birthday = birthday;
}
@Override
public String toString() {
return "name=" + name + ", number=" + number + ", birthday=" + birthday.toDateString();
}
}
MyDate类包含:private成员变量year,month,day ;toString()方法返回日期对应的字符串:xxxx年xx月xx日
public class Mydate {
private int year;
private int month;
private int day;
public Mydate() {
super();
}
public Mydate(int year, int month, int day) {
super();
this.year = year;
this.month = month;
this.day = day;
}
public String toDateString() {
return year + "年" + month + "月" + day + "日";
}
// 省略getter和setter方法
}
定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:private成员变量monthlySalary;实现父类的抽象方法earnings(),该方法返回monthlySalary值; toString()方法输出员工类型信息及员工的name,number,birthday。
public class SalatiedEnployee extends Employee {
private double monthlySalary;
public SalatiedEnployee(String name, int number, Mydate birthday) {
super(name, number, birthday);
}
// 省略getter和setter方法
public SalatiedEnployee(double monthlySalary,String name, int number, Mydate birthday) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
@Override
public double earnings() {
return monthlySalary;
}
public String toString() {
return "SalariedEmployee[" + super.toString() + "]";
}
}
参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:private成员变量wage和hour;实现父类的抽象方法earnings(),该方法返回wage*hour值; toString()方法输出员工类型信息及员工的name,number,birthday。
public class HourlyEmployee extends Employee {
private int wage;// 每小时工资
private int hour;// 月工作的小时数
public HourlyEmployee(String name, int number, Mydate birthday) {
super(name, number, birthday);
}
public HourlyEmployee(int wage,int hour,String name, int number, Mydate birthday) {
super(name, number, birthday);
this.hour = hour;
this.wage = wage;
}
@Override
public double earnings() {
return wage * hour;
}
public String toString() {
return "HourlyEmployee[" + super.toString() + "]";
}
}
定义PayrollSystem类,创建Employee变量数组并初始化, 该数组存放各类雇员对象的引用。利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。 当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息。
public class PayrollSystem {
public static void main(String[] args) {
Employee[] emps = new Employee[2];
emps[0] = new SalatiedEnployee(10000,"马僧", 1002, new Mydate(1992,2,28));
emps[1] = new HourlyEmployee(50,240,"哦按预算", 1003, new Mydate(1995,7,28));
for(int i = 0;i < emps.length;i++){
System.out.println(emps[i]);
double salary = emps[i].earnings();
System.out.println(salary);
}
}
}
// 输出
SalariedEmployee[name=马僧, number=1002, birthday=1992年2月28日]
10000.0
HourlyEmployee[name=哦按预算, number=1003, birthday=1995年7月28日]
12000.0
8.2.接口的应用:代理模式(Proxy)
概述:代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
代理模式举例
interface Network {
public void browse();
}
// 被代理类
class RealServer implements Network {
@Override
public void browse() {
System.out.println("真实服务器上网浏览信息");
}
}
// 代理类
class ProxyServer implements Network {
private Network network;
public ProxyServer(Network network) {
this.network = network;
}
public void check() {
System.out.println("检查网络连接等操作");
}
public void browse() {
check();
network.browse();
}
}
public class ProxyDemo {
public static void main(String[] args) {
Network net = new ProxyServer(new
RealServer());
net.browse();
}
}
应用场景: 安全代理:屏蔽对真实角色的直接访问;远程代理:通过代理类处理远程方法调用(RMI); 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
分类:静态代理(静态定义代理类);动态代理(动态生成代理类);JDK自带的动态代理,需要反射等知识。
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work) {
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
8.3.接口的应用:工厂设计模式
工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。其实设计模式和面向对象设计原则都是为了使得开发项目更加容易扩展和维护,解决方式就是一个“分工”。
面向对象与工厂模式相关的三大设计原则(总共六个):
OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。写完的代码,不能因为需求变化就修改,可以通过新增代码的方式来解决变化的需求。如果每次需求变动都去修改原有代码,存在有意或无意的修改,可能导致功能失效,甚至引发严重的蝴蝶效应。开闭原则除了表面上的可扩展性强以外,在企业中更看重的是维护成本。开闭原则是设计模式的第一大原则,它的潜台词是:控制需求变动风险,缩小维护成本。
DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程。如果 A 中关联 B,那么尽量使得 B 实现某个接口,然后 A 与接口发生关系,不与 B 实现类发生关联关系。依赖倒置的潜台词是:面向抽象编程,解耦调用和被调用者。
LOD(迪米特法则,Law Of Demeter):只与你直接的朋友通信,而避免和陌生人通信。要求尽量的封装,尽量的独立,尽量的使用低级别的访问修饰符。这是封装特性的典型体现。迪米特原则要求类之间的直接联系尽量的少,两个类的访问,通过第三个中介类来实现。迪米特原则的潜台词是:不和陌生人说话,有事去中介。
工厂模式的分类:
简单工厂模式:用来生产同一等级结构中的任意产品。对于增加新的产品,需要修改已有代码;
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品);
抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)。
核心本质:实例化对象,用工厂方法代替 new 操作;将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
无工厂模式
package com.atguigu.pattern.factory.nofactory;
interface Car{
void run();
}
class Audi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
public class Client01 {
public static void main(String[] args) {
Car a = new Audi();
Car b = new BYD();
a.run();
b.run();
}
}
简单工厂模式:简单工厂模式,从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的工厂类。
package com.atguigu.pattern.factory.simple;
interface Car {
void run();
}
class Audi implements Car {
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car {
public void run() {
System.out.println("比亚迪在跑");
}
}
// 工厂类
class CarFactory {
// 方式一
public static Car getCar(String type) {
if ("奥迪".equals(type)) {
return new Audi();
} else if ("比亚迪".equals(type)) {
return new BYD();
} else {
return null;
}
}
//方式二
// public static Car getAudi() {
// return new Audi();
// }
// public static Car getByd() {
// return new BYD();
// }
}
public class Client02 {
public static void main(String[] args) {
Car a = CarFactory.getCar("奥迪");
a.run();
Car b = CarFactory.getCar("比亚迪");
b.run();
}
}
调用者只要知道他要什么,从哪里拿,如何创建,不需要知道。分工,多出了一个专门生产 Car 的实现类对象的工厂类。把调用者与创建者分离。
小结:简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的
参数的不同来返回不同的实例对象。
缺点:对于增加新产品,不修改代码的话,是无法扩展的。违反了开闭原则(对扩展开放;对修改封闭)。
工厂方法模式
为了避免简单工厂模式的缺点,不完全满足 OCP(对扩展开放,对修改关闭)。工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立的模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
package com.atguigu.pattern.factory.method;
interface Car{
void run();
}
//两个实现类
class Audi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
//工厂接口
interface Factory{
Car getCar();
}
//两个工厂类
class AudiFactory implements Factory{
public Audi getCar(){
return new Audi();
}
}
class BydFactory implements Factory{
public BYD getCar(){
return new BYD();
}
}
public class Client {
public static void main(String[] args) {
Car a = new AudiFactory().getCar();
Car b = new BydFactory().getCar();
a.run();
b.run();
}
}
总结:简单工厂模式与工厂方法模式真正的避免了代码的改动了?没有。在简单工厂模式中,新产品的加入要修改工厂角色中的判断语句;而在工厂方法模式中,要么将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就像上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。面对这种情况,Java 的反射机制与配置文件的巧妙结合突破了限制——这在Spring 中完美的体现了出来。
抽象工厂模式
抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。
抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。
而且使用抽象工厂模式还要满足一下条件:系统中有多个产品族,而系统一次只可能消费其中一族产品;同属于同一个产品族的产品以其使用。
8.4.接口和抽象类的对比
区别点 | 抽象类 | 接口 |
定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
组成 | 构造方法、抽象方法、普通方法、 | 常量、抽象方法、(jdk8.0:默认方法、静态方法) |
使用 | 子类继承抽象类(extends) | 子类实现接口(implements |
关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
常见的设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
对象 | 通过对象的多态性产生实例化对象 | |
局限 | 抽象类有单继承的局限 | 接口没有此局限 |
实际 | 作为一个模板 | 是作为一个标准或是表示一种能力 |
选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |
8.5.Java 8中关于接口的改进
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
接口中的默认方法: 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突:解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
// 接口冲突解决方法
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
}
}
class Man implements Filial, Spoony {
@Override
public void help() {
System.out.println("我该怎么办呢?");
Filial.super.help();
Spoony.super.help();
}
}
9.类的成员之五:内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称:Inner class的名字不能与包含它的外部类类名相同。
分类: 成员内部类(static成员内部类和非static成员内部类);局部内部类(不谈修饰符,方法内、代码块内、构造器内)、匿名内部类。
成员内部类作为类的成员的角色:和外部类不同,Inner class还可以声明为private或protected; 可以调用外部类的结构;Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
成员内部类作为类的角色:可以在内部定义属性、方法、构造器等结构;可以声明为abstract类 ,因此可以被其它的内部类继承;可以声明为final的,表示此类不能被继承。言外之意,使用final,就不能被继承;可以被abstract修饰;编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
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 person = new Person();
Person.Bird bird = person.new Bird();
bird.sing();
System.out.println("----------------------");
bird.display("黄鹂");
}
}
class Person {
String name = "小明";
public void eat() {
System.out.println("吃东西");
}
// 静态成员内部类
static class Dog {
public void show() {
System.out.println("一条狗");
}
}
// 非静态成员内部类
class Bird {
String name = "杜鹃";
public void sing() {
Person.this.eat();// 调用外部类的属性、方法
eat();
}
public void display(String name) {
System.out.println(name);
System.out.println(this.name);
System.out.println(Person.this.name);
}
}
//非静态成员内部类
class Birds{
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);//外部类的属性
}
}
public void method() {
// 局部内部类
class AA {
}
}
{
// 局部内部类
class BB {
}
}
public Person() {
// 局部内部类
class CC {
}
}
}
注意:非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员; 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式;成员内部类可以直接使用外部类的所有成员,包括私有的数据;当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的。
// 举例1
class Outer {
private int s;
public class Inner {
public void mb() {
s = 100;
System.out.println("在内部类Inner中s=" + s);
}
}
public void ma() {
Inner i = new Inner();
i.mb();
}
}
public class InnerTest {
public static void main(String args[]) {
Outer o = new Outer();
o.ma();
}
}
// 举例2
public class Outer {
private int s = 111;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(Outer.this.s); // 外部类对象属性s
}
}
public static void main(String args[]) {
Outer a = new Outer();
Outer.Inner b = a.new Inner();
b.mb(333);
}
}
如何声明局部内部类
class 外部类{
方法(){
class 局部内部类{
}
}
{
class 局部内部类{
}
}
}
如何使用局部内部类
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类;但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型。
public class InnerClassTest1 {
// 开发中很少见
public void merhod() {
// 局部内部类
class AA {
}
}
// 返回一个实现Comparable接口的类的对象
public Comparable GetComparable() {
// 创建一个实现类Comparable接口的类:局部内部类
// class MyComparable implements Comparable{
//
// @Override
// public int compareTo(Object o) {
// // TODO Auto-generated method stub
// return 0;
// }
// }
// return new MyComparable();
// 方式2
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
局部内部类的特点
内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号;只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类;局部内部类可以使用外部类的成员,包括私有的;局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致;局部内部类和局部变量地位类似,不能使用public,protected,缺省,private;局部内部类不能使用static修饰,因此也不能包含静态成员。
public class InnerClassTest2 {
/*
* 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如method)中的局部变量(比如:num)的话,
* 要求此局部变量声明为final的.
* JDK7及之前的版本,要求此局部变量显式的声明为final
* JDK8及之后的版本,可以省略final的声明
*/
public void method() {
// 局部变量
int num = 10;
class AA {
public void show() {
//num = 20;//错误
System.out.println(num);
}
}
}
}
匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
格式:
new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
}
匿名内部类的特点
匿名内部类必须继承父类或实现接口
匿名内部类只能有一个对象
匿名内部类对象只能使用多态形式引用
interface A{
public abstract void fun1();
}
public class Outer{
public static void main(String[] args) {
new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
public void fun1() {
System.out.println(“implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}