看一个问题:有一群小孩在玩堆雪人,不时有新的小孩加入,问:如何知道现在共有多少人在玩?编写程序解决。
public class ChildGame {
public static void main(String[] args) {
int count = 0;//统计有多少小孩加入了游戏
Child child1 = new Child("白骨精");
child1.join();
count++;
Child child2 = new Child("狐狸精");
child2.join();
count++;
Child child3 = new Child("老鼠精");
child3.join();
count++;
System.out.println("共有" + count + "个小孩加入了游戏...");
}
}
class Child {
private String name;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + " 加入了游戏..");
}
}
输出结果:
白骨精 加入了游戏..
狐狸精 加入了游戏..
老鼠精 加入了游戏..
共有3个小孩加入了游戏...
由输出结果看,上面代码虽然按照预期解决了问题,但是存在以下缺陷:
如果设计一个 int count 表示总人数,我们在创建一个小孩时,就把 count 加 1,并且count 是所有对象共享的就 ok 了,这就要用类变量 / 静态变量来解决。
类变量 / 静态变量 最大的特点就是会被类的所有的对象实例共享。
引入类变量 / 静态变量后,上面的问题可以这样解决:
public class ChildGame {
public static void main(String[] args) {
Child child1 = new Child("白骨精");
child1.join();
child1.count++;
Child child2 = new Child("狐狸精");
child2.join();
child2.count++;
Child child3 = new Child("老鼠精");
child3.join();
child3.count++;
//类变量,可以通过类名来访问
System.out.println("共有" + Child.count + "个小孩加入了游戏...");
//用任意一个对象访问也可以
System.out.println("child1.count=" + child1.count);//3
System.out.println("child2.count=" + child2.count);//3
System.out.println("child3.count=" + child3.count);//3
}
}
class Child {
private String name;
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + " 加入了游戏..");
}
}
输出结果:
白骨精 加入了游戏..
狐狸精 加入了游戏..
老鼠精 加入了游戏..
共有3个小孩加入了游戏...
child1.count=3
child2.count=3
child3.count=3

在JDK8之前,类变量的内存布局如红色虚线所示,JDK8之后如绿色实线所示(大概是这样,可能不是很准确)。
不管类变量在哪里,可以确定的是:
(1)类变量可以被同一个类的所有对象共享。
(2)类变量在类加载的时候就生成了。
类变量 / 静态(static)变量 / 静态属性: 是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,任何一个该类的对象去修改它时,修改的也是同一个变量。
定义语法:
(1)访问修饰符 static 数据类型 变量名;(推荐)
(2)static 访问修饰符 数据类型 变量名;
访问:
(1)类名 . 类变量名(推荐)
类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问。
(2)对象名 . 类变量名
静态变量访问修饰符的访问权限、范围与普通属性相同。
public class Test {
public static void main(String[] args) {
//通过类名.类变量名访问
System.out.println(A.name);
A a = new A();
//通过对象名.类变量名访问
System.out.println("a.name=" + a.name);
}
}
class A {
//类变量的访问,必须遵守相关的访问权限
//若用private修饰,其他类中就不能直接访问了
public static String name = "marry";
}
类方法也叫静态方法。
定义语法:
(1)访问修饰符 static 返回类型 方法名(){ }(推荐)
(2)static 访问修饰符 返回类型 方法名(){ }
调用:
(1)类名 . 类方法名(推荐)
(2)对象名 . 类方法名
前提:满足访问修饰符的访问权限和范围。
【例1】学生学费总和。
public class Test {
public static void main(String[] args) {
//创建2个学生对象,交学费
Stu tom = new Stu("tom");
Stu.payFee(100);//tom.payFee(100)也可以
Stu mary = new Stu("mary");
Stu.payFee(200);//mary.payFee(200)也可以
Stu.showFee();//当前总学费300
}
}
class Stu {
private String name;
private static double fee = 0;//静态变量,累积学费
public Stu(String name) {
this.name = name;
}
//当方法使用了 static 修饰后,该方法就是静态方法
//静态方法就可以访问静态属性/变量
public static void payFee(double fee) {
Stu.fee += fee;
}
public static void showFee() {
System.out.println("总学费有:" + Stu.fee);
}
}
public class Test {
public static void main(String[] args) {
System.out.println(MyTools.calSum(10, 30));
System.out.println(Math.sqrt(9));
}
}
class MyTools {
public static double calSum(double n1, double n2) {
return n1 + n2;
}
//可以写出很多这样的工具方法...
}
class D {
private int n1 = 100;
private static int n2 = 200;
public void say() {
}
public static void hi() {
//静态方法中不能使用this或super
//System.out.println(this.n1);
}
//类方法(静态方法)只能访问静态成员
public static void hello() {
System.out.println(n2);
System.out.println(D.n2);
//System.out.println(this.n2);//错误
hi();//正确
//say();//错误
}
//普通成员方法,可以访问访问静态成员和非静态成员
public void ok() {
//非静态成员
System.out.println(n1);
say();
//静态成员
System.out.println(n2);
hello();
}
}
//如果类方法(静态方法)非要访问非静态成员,要先创建对象再访问
class Person {
private static int total = 3;
private int id = 5;
public static void printVar(){
System.out.println(total);
Person person = new Person();
System.out.println(person.id);
}
}
public class Test {
public static void main(String[] args) {
Person.printVar();//3 5
}
}
练习:
【例1】看看下面代码有没有错误,如果有错误,就修改,看看输出什么?

class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++;//错误, 静态方法不能访问非静态变量
return total;
}
//构造器也是非静态方法,可以访问非静态和静态成员
public Person() {
total++;
id = total;
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Person.getTotalPerson()); //0
Person p1 = new Person();
System.out.println(Person.getTotalPerson()); //1
}
}
【例2】下面代码有没有错误,如果有错误,就修改。修改后,total 等于多少?

class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
// this.total = total;//错误,static方法不能使用this
//不能使用this,又要区分局部变量total和属性total
//就可以直接用类名的形式访问
Person.total = total;
}
public Person() {
total++;
id = total;
}
public static void printTotal(){
System.out.println(total);
}
}
public class Test {
public static void main(String[] args) {
Person.setTotalPerson(3);
new Person();
Person.printTotal();//total的值是 4
}
}
解释 main 方法的形式:public static void main(String[] args)




main()方法与普通static方法访问本类成员的规则一致:
public class Test {
private static String name = "Smith";
private int n1 = 10000;
public static void hi() {
System.out.println("Test 的 hi 方法");
}
public void cry() {
System.out.println("Test 的 cry 方法");
}
public static void main(String[] args) {
System.out.println(name);
hi();
Test test = new Test();
System.out.println(test.n1);
test.cry();
}
}
输出结果:
Smith
Test 的 hi 方法
10000
Test 的 cry 方法
代码块是什么:
语法:
[修饰符]{
代码
};
【例】下面的三个构造器都有相同的语句,这样代码很啰嗦。可以把相同语句,放入一个代码块中。在创建对象时,不管调用哪个构造器,都会先调用代码块的内容。
public class Test {
public static void main(String[] args){
Movie movie = new Movie("你好,李焕英");
System.out.println("===============");
Movie movie2 = new Movie("唐探 3", 100, "陈思诚");
}
}
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("Movie(String name) 被调用...");
this.name = name;
}
public Movie(String name, double price, String director) {
System.out.println("Movie(String name, double price, String director) 被调用...");
this.name = name;
this.price = price;
this.director = director;
}
}
输出结果:
电影屏幕打开...
广告开始...
电影正式开始...
Movie(String name) 被调用...
===============
电影屏幕打开...
广告开始...
电影正式开始...
Movie(String name, double price, String director) 被调用...
static 代码块也叫静态代码块,作用就是对类进行初始化。
静态代码块在类加载的时候执行;
无论为某个类创建多少个对象,该类都只会加载一次;
所以,为某个类创建多个对象时,静态代码块只执行一次。
public class Test {
public static void main(String[] args){
DD dd = new DD();
DD dd2 = new DD();
}
}
class DD {
static {//静态代码块
System.out.println("DD 的静态代码块被执行...");
}
}
输出结果:
DD 的静态代码块被执行...
类什么时候被加载 ?
① 创建对象实例时(new)
public class Test {
public static void main(String[] args){
//1. 创建对象实例时
AA aa = new AA();
}
}
class AA {
static {//静态代码块
System.out.println("AA 的静态代码块被执行...");
}
}
输出结果:
AA 的静态代码块被执行...
② 创建子类对象实例,父类也会被加载(先加载父类,再加载子类),实际上只要用到子类,就会加载父类。
public class Test {
public static void main(String[] args){
//创建子类对象时,父类也会被加载。先加载父类,再加载子类
AA aa2 = new AA();
}
}
class BB {
static {//静态代码块
System.out.println("BB 的静态代码块被执行...");
}
}
class AA extends BB {
static {//静态代码块
System.out.println("AA 的静态代码块被执行...");
}
}
输出结果:
BB 的静态代码块被执行...
AA 的静态代码块被执行...
③ 使用类的静态成员时(静态属性 / 方法)
【例1】
public class Test {
public static void main(String[] args){
//使用类的静态成员时(静态属性,静态方法)
System.out.println(Cat.n1);
}
}
class Cat{
public static int n1 = 999;//静态属性
static {//静态代码块
System.out.println("Cat 的静态代码块被执行...");
}
}
输出结果:
Cat 的静态代码块被执行...
999
【例2】
public class Test {
public static void main(String[] args){
//使用类的静态成员时(静态属性,静态方法)
System.out.println(Cat.n1);
}
}
class Animal {
static {//静态代码块
System.out.println("Animal 的静态代码块被执行...");
}
}
class Cat extends Animal {
public static int n1 = 999;//静态属性
static {//静态代码块
System.out.println("Cat 的静态代码块被执行...");
}
}
输出结果:
Animal 的静态代码块被执行...
Cat 的静态代码块被执行...
999
普通的代码块,在创建对象实例时,会被隐式地调用。每创建一次对象,就会调用一次代码块。
对普通代码块的理解:
(1) 相当于构造器的补充机制,可以做初始化操作。
(2) 如果多个构造器中都有重复语句,可以抽取到代码块中,提高重用性。
public class Test {
public static void main(String[] args){
DD dd = new DD();
DD dd2 = new DD();
}
}
class DD {
static {//静态代码块
System.out.println("DD 的静态代码块被执行...");
}
{//普通代码块
System.out.println("DD 的普通代码块被执行...");
}
}
输出结果:
DD 的静态代码块被执行...
DD 的普通代码块被执行...
DD 的普通代码块被执行...
如果只是使用类的静态成员,普通代码块不会执行。
(普通代码块被调用只与是否创建对象有关,与类加载无关。可以这样简单理解:普通代码块是构造器的补充,构造器被调用时,普通代码块才被调用)
public class Test {
public static void main(String[] args){
System.out.println(DD.n1);
}
}
class DD {
public static int n1 = 8888;
static {//静态代码块
System.out.println("DD 的静态代码块被执行...");
}
{//普通代码块
System.out.println("DD 的普通代码块被执行...");
}
}
输出结果:
DD 的静态代码块被执行...
8888
小结:
static代码块是在类加载时调用的,只会调用一次。(类加载的3种情况要记住)
普通代码块是在创建对象时调用的,每创建一次对象,就调用一次普通代码块。
创建一个对象时,在一个类中的调用顺序是:
① 调用静态代码块和静态属性初始化(两者优先级相同,若有多个静态代码块 / 属性初始化,则按定义顺序调用)。
② 调用普通代码块和普通属性的初始化(两者优先级相同,若有多个普通代码块 / 属性初始化,则按定义顺序调用)。
③ 调用构造方法。
public class Test {
public static void main(String[] args){
A a = new A();
}
}
class A {
public A(){
System.out.println("构造器 被调用...");
}
//普通属性的显式初始化在创建对象时、构造器初始化之前,普通代码块的调用也是在创建对象时、构造器执行之前
//普通属性n2在前,所以先被初始化,于是getN2()被调用
//普通代码块在后,所以后被调用
private int n2 = getN2();
{ //普通代码块
System.out.println("普通代码块 被调用...");
}
public int getN2() {
System.out.println("getN2 被调用...");
return 100;
}
//静态变量在类加载时初始化,静态代码块也是在类加载时被调用
//但静态变量n1在前,所以先被初始化,于是getN1()被调用
//静态代码块在后,所以后被调用
private static int n1 = getN1();
static { //静态代码块
System.out.println("静态代码块 被调用...");
}
public static int getN1() {
System.out.println("getN1 被调用...");
return 100;
}
}
输出结果:
getN1 被调用...
静态代码块 被调用...
getN2 被调用...
普通代码块 被调用...
构造器 被调用...
构造器的最前面其实隐含了 super 和调用普通代码块。静态相关的代码块、属性初始化,在类加载时,就执行完毕,因此优先于构造器和普通代码块执行。

输出结果:
AAA 的普通代码块...
AAA() 构造器被调用...
BBB 的普通代码块...
BBB() 构造器被调用...
创建子类对象时(继承关系),静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
① 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
② 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
③ 父类的普通代码块和普通属性初始化(优先级一样, 按定义顺序执行)
④ 父类的构造方法
⑤ 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
⑥ 子类的构造方法
【例】
(1)加载类,由于B02有父类A02:
所以先加载父类A02:初始化静态属性(getVal01被调用)、调用静态代码块
再加载子类B02:初始化静态属性(getVal03被调用)、调用静态代码块
(2)创建B02对象:
进入B02的构造器 ➡ super()进入父类A02的构造器,在父类A02的构造器中:
super()调用Object类的构造器;
初始化普通属性(getVal02被调用)、调用普通代码块;
执行A02构造器中的剩余语句。
父类构造器执行完毕后,回到子类B02构造器,执行super()后面的语句:
初始化普通属性(getVal04被调用)、调用普通代码块;
执行B02构造器中的剩余语句。
public class Test {
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)
}
}
输出结果:
getVal01
A02 的一个静态代码块..
getVal03
B02 的一个静态代码块..
A02 的第一个普通代码块..
getVal02
A02 的构造器
getVal04
B02 的第一个普通代码块..
B02 的构造器
在本类中,静态代码块只能直接访问静态成员(静态属性 / 方法),若要访问非静态成员可以先创建对象*;普通代码块可以直接访问任意成员。(静态代码块很多用法与静态方法相同)
【注】静态代码块在类加载的时候执行,那时候还没有创建对象、没有初始化非静态成员,所以要先创建对象才能访问非静态成员。
class C02 {
private int n1 = 100;
private static int n2 = 200;
private void m1() {
}
private static void m2() {
}
static {
//静态代码块,只能调用静态成员
//System.out.println(n1);错误
System.out.println(n2);//ok
//m1();//错误
m2();
}
{//普通代码块,可以使用任意成员
System.out.println(n1);
System.out.println(n2);//ok
m1();
m2();
}
}
【例】下面的代码输出什么?
class Person {
public static int total;
static {
total = 100;
System.out.println("in static block!");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("total = "+ Person.total);
System.out.println("total = "+ Person.total);
}
}
分析:
访问 Person.total 前先加载Person类:初始化静态变量、调用静态代码块(输出"in static block!")
Person类加载完成后,输出Person.total(100)
再次输出Person.total前,不再加载Person类,直接输出Person.total(100)
输出结果:
in static block!
total = 100
total = 100
(1)是静态方法和属性的经典使用。
(2)设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。
单例设计模式: 单例就是单个的实例。类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式有两种方式: (1)饿汉式 (2)懒汉式
饿汉式和懒汉式单例模式的实现步骤:
(1)构造器私有化,防止直接 new
(2)类的内部创建对象
(3)向外暴露一个静态的公共方法,getInstance
(4)代码实现
public class Test {
public static void main(String[] args) {
System.out.println("n1="+GirlFriend.n1);
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
//因为类只加载一次,所以getInstance得到的仍是第一次创建对象前类加载时创建的对象
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//true
}
}
class GirlFriend {
public static int n1 = 999;
private String name;
//为了能够在静态方法getInstance中,直接访问属性(返回gf对象)
//需要将其修饰为static
//类一加载,就初始化static属性,就创建对象(饿:还没等到要用就自己创建了)
private static GirlFriend gf = new GirlFriend("小红红");
//将构造器私有化,其他类中不能直接调用构造器,从而不能直接创建对象
private GirlFriend(String name) {
System.out.println("构造器被调用.");
this.name = name;
}
//在其他类中不创建对象就直接调用该方法,返回该类中创建的gf对象
//所以方法应该是static的
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
输出结果:
构造器被调用.
n1=999
GirlFriend{name='小红红'}
GirlFriend{name='小红红'}
true
单例模式中的对象通常都是重量级的对象,可能存在创建了但是没有使用的情况。
若上面代码的GirlFriend类中有public static int n1 = 100,main方法中只有这样一条语句:System.out.println(GirlFriend.n1),则GirlFriend.n1会导致GirlFriend类加载,从而创建了该类的对象,但是之后却没有用到,造成资源浪费。
于是有了懒汉式单例模式,只有使用时才会创建对象。
public class Test {
public static void main(String[] args) {
System.out.println("n1="+Cat.n1);
Cat instance = Cat.getInstance();
System.out.println(instance);
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//true
}
}
class Cat {
public static int n1 = 999;
private String name;
//保证静态方法getInstance能够直接访问该静态属性
private static Cat cat ; //默认是 null
//构造器私有化,保证其他类不能直接利用该构造器创建对象
//4.懶汉式,只有当用户使用 getInstance 方法时,才返回cat对象(没有就创建),
//当再次调用时,会返回上次创建的cat对象。从而保证了单例
private Cat(String name) {
System.out.println("构造器调用...");
this.name = name;
}
//在其他类中不创建对象就直接调用该方法,返回该类中创建的cat对象
//所以方法应该是static的
public static Cat getInstance() {
if(cat == null) {//如果还没有创建cat对象
cat = new Cat("小可爱");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
输出结果:
n1=999
构造器调用...
Cat{name='小可爱'}
Cat{name='小可爱'}
true
二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建。
饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程后,会完善)
多个线程同时执行到懒汉式的getInstance方法时,可能出现 ”检查发现对象还未创建,从而都去创建对象“ 的情况(最终保留最后创建的对象)

饿汉式存在浪费资源的可能,原因是:如果用不到对象实例,饿汉式创建的对象就浪费了;懒汉式是使用时才创建,就不存在浪费的问题。
在我们JavaSE标准类中,java.lang.Runtime就是经典的单例模式:

有以下需求时,就会使用到final。
(1)不希望类被继承

(2)不希望父类的某个方法被子类覆盖/重写

(3)不希望类的某个属性的值被修改(常量)

(4)不希望某个局部变量被修改(常量)

final修饰的属性又叫常量,一般用 XX_XX_XX 来命名。
final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一(多于一种会报错):
(1)定义时;
(2)在构造器中;
(3)在代码块中。
class AA {
public final double TAX_RATE = 0.08;//定义时赋值
public final double TAX_RATE2 ;
public final double TAX_RATE3 ;
public AA() {//构造器中赋值
TAX_RATE2 = 1.1;
}
{//在代码块赋值
TAX_RATE3 = 8.8;
}
}
如果final修饰的属性是静态的,则初始化的位置只能是:
(1)定义时;(2)在静态代码块。
不能在构造器中赋值,因为静态属性初始化是在类加载阶段,调用构造器是创建对象时,太晚了。
class AA {
public static final double TAX_RATE = 99.9;
public static final double TAX_RATE2 ;
static {
TAX_RATE2 = 3.3;
}
}
final类不能继承,但是可以实例化对象。
public class Test {
public static void main(String[] args) {
CC cc = new CC();
}
}
final class CC {}
如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承。
public class Test {
public static void main(String[] args) {
new EE().cal();//输出"cal()方法"
}
}
class DD{
public final void cal(){
System.out.println("cal()方法");
}
}
class EE extends DD{}
一般来说,如果某个类已经是 final 类了,就没有必要再将方法修饰成 final 方法。
(因为 final 方法是为了不让子类重写,而 final 类连子类都没有,所以没必要)
final 不能修饰构造方法(即构造器)
final 也可以修饰局部变量。
final 和 static 往往搭配使用,效率更高。因为不会导致类加载(底层编译器做了优化处理)。final 与 static 顺序可颠倒。
public class Test {
public static void main(String[] args) {
System.out.println(BBB.n);
}
}
class BBB{
public final static int n = 10000;
static {
System.out.println("BBB静态代码块");
}
}
输出结果:
10000
若上面BBB类中的静态变量 n 不用 final 修饰,则会输出:
BBB静态代码块
10000
包装类(Integer,Double,Float,Boolean 等都是 final 类),String 也是 final 类。这些类都不能被继承。
下面的代码是否有误,为什么?
public int addOne(final int x) {//形参可以用final修饰
++x; //错误,不能更改x的值
return x + 1; //可以,没有更改x的值
}
看一个问题:写一个父类Animal,其中有一个eat方法,子类要重写该方法。但这时,父类如果写了eat方法,可能没有用处,或者不知道方法体里面应该写什么。就像下面代码:
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println("这是一个动物,但是不知道吃什么..");
}
}
为了解决上述问题,将 eat 方法设计为抽象(abstract)方法。
抽象方法就是没有实现的方法,即:没有方法体。
当一个类中存在抽象方法时,需要将该类声明为 abstract 类。
一般来说,抽象类会被继承,由子类来实现抽象方法。
所以,上述代码可以修改为:
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public abstract void eat();
}
抽象类不能被实例化。

抽象类可以没有 abstract 方法。
一旦类包含了 abstract 方法,则这个类必须声明为 abstract。
abstract 只能修饰类和方法,不能修饰属性和其它。
抽象类可以有任意成员(因为抽象类还是类),比如:非抽象方法(有方法体的方法)、构造器、静态属性等等。

抽象方法不能有方法体。
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract 类。

抽象方法不能用 private、final 和 static 修饰,因为这些关键字都与重写相违背。
private:私有方法不能重写(子类不能直接访问到)
final:修饰方法的目的就是不允许子类重写
static:与重写无关,不能与 abstract 一起
【例】编写一个Employee类,声明为抽象类,包含如下三个属性 name, id, salary.
提供必要的构造器和抽象方法:work()。 对于Manager类来说,他既是员工,还
具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和
Manager类,要求类中提供必要的方法进行属性访问,实现work(), 提示 “经理/普
通员工+名字+工作中…
public class Test {
public static void main(String[] args) {
Manager jack = new Manager("jack", 999, 50000);
jack.setBonus(8000);
jack.work();
CommonEmployee tom = new CommonEmployee("tom", 888, 20000);
tom.work();
}
}
abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
//将 work 做成一个抽象方法
public abstract void work();
public String getName() {
return name;
}
}
class Manager extends Employee{
private double bonus;
public Manager(String name, int id, double salary) {
super(name, id, salary);
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("经理 " + getName() + " 工作中...");
}
}
class CommonEmployee extends Employee{
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("普通员工 " + getName() + " 工作中...");
}
}
输出结果:
经理 jack 工作中...
普通员工 tom 工作中...
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
(1)当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
(2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
【例】有多个类,完成不同的任务job,要求统计得到各自完成任务的时间。
一般解法:
public class Test1{
public static void main(String[] args)
{
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
class AA {
public void calculateTime() {
//开始的时间
long start = System.currentTimeMillis();
job();
//结束的时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间 " + (end - start));
}
public void job() {
long num = 0;
for (long i = 1; i <= 10000000; i++) {
num += i;
}
}
}
class BB {
public void calculateTime() {
//开始的时间
long start = System.currentTimeMillis();
job();
//结束的时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间 " + (end - start));
}
public void job() {
long num = 0;
for (long i = 1; i <= 8000000; i++) {
num *= i;
}
}
}
上述代码中,AA类和BB类有重复的方法,代码冗余度较大,可以考虑将两个类中重复的方法抽取到一个父类(抽象类)中,不同的方法声明为抽象的,然后再去子类AA和BB中实现(重写)抽象方法。
public class Test {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
abstract class Template { //抽象类-模板设计模式
public abstract void job();//抽象方法
public void calculateTime() {
//开始的时间
long start = System.currentTimeMillis();
//发起者是aa/bb(运行类型),所以会去执行AA/BB类中的job()方法
job(); //动态绑定机制
//结束的时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间 " + (end - start));
}
}
class AA extends Template {
@Override
public void job() {//实现(重写)Template的抽象方法job
long num = 0;
for (long i = 1; i <= 10000000; i++) {
num += i;
}
}
}
class BB extends Template{
@Override
public void job() {//实现(重写)Template的抽象方法job
long num = 0;
for (long i = 1; i <= 8000000; i++) {
num *= i;
}
}
}
看下面例子,直观地体会接口。
package com.hspedu.interface_;
public class Interface01 {
public static void main(String[] args) {
//Camera 实现了 UsbInterface
Camera camera = new Camera();
//Phone 实现了 UsbInterface
Phone phone = new Phone();
Computer computer = new Computer();
computer.work(phone);//把手机接入到计算机
System.out.println("===============");
computer.work(camera);//把相机接入到计算机
}
}
class Computer {
public void work(UsbInterface usbInterface){
usbInterface.start();
usbInterface.stop();
}
}
interface UsbInterface{
//规定接口的相关方法,即规范
void start();
void stop();
}
package com.hspedu.interface_;
public class Phone implements UsbInterface{
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机停止工作");
}
}
package com.hspedu.interface_;
public class Camera implements UsbInterface{
@Override
public void start() {
System.out.println("相机开始工作");
}
@Override
public void stop() {
System.out.println("相机停止工作");
}
}
输出结果:
手机开始工作
手机停止工作
===============
相机开始工作
相机停止工作
(可以将接口理解为一个特殊的类。类实现接口也可达到代码重用的目的)
接口就是给出一些没有实现的方法,封装到一起。到某个类要使用的时候,再根据具体情况把这些方法写出来。语法:
interface 接口名{
//属性
//抽象方法(Jdk8及以后还有默认实现方法、静态方法)
}
class 类名 implements 接口名{
自己的属性;
自己的方法;
必须实现的接口的抽象方法;
}
Jdk7.0及以前,接口里的所有方法都没有方法体,即:都是抽象方法。
Jdk8.0及以后,接口里可以有静态方法,默认方法,即:接口中可以有方法的具体实现。

在接口中,抽象方法可以省略 abstract 关键字。
如果一个类 implements 接口,就要将该接口中的所有抽象方法都实现。


对初学者讲,理解接口的概念不算太难,难的是不知道什么时候使用接口,下面列举几个应用场景:
(1)现在要制造战斗机、直升机。专家只需把飞机需要的功能 / 规格定下来,然后让别的人具体实现即可。
(2)一个项目经理管理3个程序员,分别实现 MySql、Oracle、DB2 数据库的 connect、close。项目经理写好接口,程序员根据需求去具体实现接口中的方法。这样就能保证3个程序员写的方法有相同的方法名,便于管理。(下面以MySql、Oracle为例)
package com.hspedu.interface_;
public class Test {
public static void main(String[] args) {
MySqlDB mySqlDB = new MySqlDB();
m(mySqlDB);
OracleDB oracleDB = new OracleDB();
m(oracleDB);
}
public static void m(DBInterface db){
db.connect();
db.colse();
}
}
package com.hspedu.interface_;
public interface DBInterface {
//强制MySql、Oracle的连接和关闭方法都叫connect、close
public void connect();
public void colse();
}
package com.hspedu.interface_;
public class MySqlDB implements DBInterface{
@Override
public void connect() {
System.out.println("MySql已连接...");
}
@Override
public void colse() {
System.out.println("MySql已关闭...");
}
}
package com.hspedu.interface_;
public class OracleDB implements DBInterface{
@Override
public void connect() {
System.out.println("Oracle已连接...");
}
@Override
public void colse() {
System.out.println("Oracle已关闭...");
}
}
输出结果:
MySql已连接...
MySql已关闭...
Oracle已连接...
Oracle已关闭...
接口不能被实例化。
(接口的本意是让类去实现它,然后我们再去创建该类的对象进行其他操作)

接口中所有的方法都是 public 方法,接口中的抽象方法可以不用 abstract 修饰。
接口中的 void aaa() 实际上是 public abstract void aaa(),所以在接口中写 void aaa(){} 是错误的。
一个普通类实现接口,就必须将该接口的所有方法都实现。
抽象类实现接口,可以不用实现接口的方法。
就像身为抽象类的子类可以不去实现父类(也是抽象类)中的抽象方法一样。
一个类可以同时实现多个接口。

接口中的属性,只能是 final 的,而且是 public static final 修饰符。比如 int a=1 实际上是 public static final int a = 1(必须初始化)。
接口中属性的访向形式:接口名.属性名。
接口不能继承其它的类,但是可以继承多个别的接口。(类可以实现多个接口)
interface A extends B,C{}
接口的修饰符只能是 public 和默认,这点和类的修饰符是一样的。
【例】

接口可以看作对单继承机制的补充。
下面代码中 LittleMonkey 只能拥有父类的功能:
class Monkey{
private String name;
public Monkey(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void climb(){
System.out.println(name+"会爬树");
}
}
class LittleMonkey extends Monkey {
public LittleMonkey(String name) {
super(name);
}
}
下面代码 LittleMonkey 除了可以拥有父类的功能,还可以拥有接口中的功能:
public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey wuKong = new LittleMonkey("孙悟空");
wuKong.climb();
wuKong.swimming();
wuKong.flying();
}
}
class Monkey{
private String name;
public Monkey(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void climb(){
System.out.println(name+"会爬树");
}
}
interface Fish{
void swimming();
}
interface Bird{
void flying();
}
class LittleMonkey extends Monkey implements Fish,Bird{
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName()+"通过学习,可以像鱼儿一样游泳");
}
@Override
public void flying() {
System.out.println(getName()+"通过学习,可以像鸟儿一样飞翔");
}
}
输出结果:
孙悟空会爬树
孙悟空通过学习,可以像鱼儿一样游泳
孙悟空通过学习,可以像鸟儿一样飞翔
小结:当子类继承了父类,就自动的拥有父类的功能。如果子类需要扩展功能,就可以通过实现接口的方式扩展。可以理解为:“实现接口” 是对 Java 单继承机制的一种补充。
接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计好各种规范(方法),让其它类去实现这些方法,不同的类实现方式不同。
接口比继承更加灵活
接口比继承更加灵话,继承要满足 is-a 的关系(人是一种动物),而接口只需满足like-a的关系(人像鸟一样飞翔)。
接口在一定程度上实现代码解耦[即:接口规范性+动态绑定机制](集合那里再讲)
接口引用可以指向实现了接口的类的对象。
多态参数
在前面 8.1 的案例中,Computer 类中的work方法的形参 UsbInterface usbInterface 既可以接收手机对象,又可以接收相机对象。
接收新创建的实现了接口的类的对象
public class InterfacePolyParameter {
public static void main(String[] args) {
IF if1 = new Monster();
IF if2 = new Car();
}
}
interface IF{}
class Monster implements IF{}
class Car implements IF{}
多态数组
演示一个案例:给Usb数组中,存放 Phone 和 相机对象,Phone类还有一个特有的方法cal(),请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,还需要调用 Phone 特有方法 call。
public class InterfacePolyArr {
public static void main(String[] args) {
Usb[] usbs = new Usb[3];
usbs[0] = new Phone();
usbs[1] = new Pad();
usbs[2] = new Phone();
for (int i = 0; i < usbs.length; i++) {
System.out.println("--------------");
if (usbs[i] instanceof Phone){
((Phone) usbs[i]).call();//向下转型
}
usbs[i].work();//动态绑定机制
}
}
}
interface Usb{
void work();
}
class Phone implements Usb{
@Override
public void work() {
System.out.println("手机工作中");
}
public void call(){
System.out.println("手机打电话");
}
}
class Pad implements Usb{
@Override
public void work() {
System.out.println("Pad工作中");
}
}
接口存在多态传递现象。
public class InterfacePolyPass {
public static void main(String[] args) {
IG ig = new Teacher();
IH ih = new Teacher();
}
}
interface IH{}
interface IG extends IH{}
class Teacher implements IG{}
上例中,IG继承了IH接口,Teacher类实现了IG接口,就相当于Teacher类也实现了IH接口。所以,若IH接口中有抽象方法,Teacher类也应该去实现。
练习:看下面代码有没有错误,有就改正。

package com.hspedu;
interface A {
int x = 0;//等价 public static final int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
//System.out.println(x); //错误,原因不明确 x
//可以明确的指定 x
//访问接口的x使用A.x
//访问父类的x使用super.x
System.out.println(A.x + " " + super.x);
}
public static void main(String[] args) {
new C().pX();
}
}
内部类: 一个类的内部又完整地嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。内部类是类的第五大成员。【类的五大成员:属性、方法、构造器、代码块、内部类】
内部类的特点: 可以直接访问私有属性,并体现类与类之间的包含关系。
注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类。
基本语法:
class Outer{//外部类
class Inner{//内部类
}
}
class Other{//外部的其他类
}
内部类的分类:
局部内部类是定义在外部类的局部位置(方法或代码块中),并且有类名。
不能添加访向修饰符,因为它的地位就是一个局部变量。局部变量不能使用修饰符。但是可以使用final修饰,因为局部变量也可以使用final。不用final修饰时,本外部类中的其他与该内部类同级的类可以继承该内部类;用final修饰时就不能再被继承。
作用域在定义它的方法或代码块中。
本质仍是一个类。
局部内部类可以直接访问外部类的成员,包含私有的。
外部类访问局部内部类的成员,要先创建对象(注意作用域),再访问。
外部其他类不能访问局部内部类(因为局部内部类地位是一个局部变量)。
如果外部类和局部内部类的成员重名,默认遵循就近原则,如果想访向外部类的成员,则可以使用(外部类名.this.成员)去访问。
public class InnerClassTest {
public static void main(String[] args) {
Outer outer = new Outer();
outer.m2();
}
}
class Outer{
private int n1 = 100;
private void m1(){
System.out.println("外部类的m1()被调用");
}
public void m2(){
final class Inner{//地位等同于局部变量
public void f1(){
//Outer.this本质就是外部类的对象, 即调用m2的对象
//如果外部类中的n1是静态的,可以直接用Outer.n1访问
System.out.println("内部类的n1=" + n1 + " 外部类的n1=" + Outer.this.n1);
m1();//内部类直接访问外部类中的成员
}
}
//class inner02 extends inner{}
//外部类先在内部类作用域内创建内部类的对象,再访问内部类的成员
Inner inner = new Inner();
inner.f1();
}
}
代码执行流程:
匿名内部类: 匿名内部类定义在外部类的局部位置(比如方法中),并且没有类名。它的本质是一个类,同时还是一个对象。
**匿名内部类的基本语法: **
new 类或接口(参数列表){
类体
};
package com.hspedu.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
}
}
interface IA{
void cry();
}
class Outer02 {
public void method(){
//想使用IA接口,并创建对象
//传统方式是写一个类实现该接口,并创建对象
//但是,如果该类只使用一次,就可以用匿名内部类简化
//tiger的编译类型是IA,运行类型是匿名内部类
//IA tiger = new IA(){...}使jdk底层 先创建匿名内部类Outer04$1(外部类名+$+编号,编号是挨着排的,)
//然后马上创建了Outer04$1对象,并且把地址返回给tiger
//jdk底层创建的类:
//class Outer02$1 implements IA{
// @Override
// public void cry() {
// System.out.println("老虎在叫");
// }
//}
//匿名内部类使用一次,就不能再使用
IA tiger = new IA(){
@Override
public void cry() {
System.out.println("老虎在叫");
}
};
System.out.println(tiger.getClass());//获取运行类型(获取系统分配的匿名内部类名)
tiger.cry();//动态绑定机制
}
}
输出结果:
老虎在叫
class com.hspedu.innerclass.Outer02$1
package com.hspedu.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
}
}
class Father{
public Father(String name) {//构造器
System.out.println("接收到 name=" + name);
}
public void test() {//方法
}
}
abstract class Animal{
abstract void eat();
}
class Outer02 {
public void method(){
//father的编译类型是Father,运行类型是匿名内部类Outer02$1
//jdk底层 先创建匿名内部类Outer04$1,然后马上创建了Outer04$1对象,并且把地址返回给father
//class Outer04$1 extends Father{
// @Override
// public void test() {
// System.out.println("匿名内部类重写了 test 方法");
// }
//}
注意("jack")参数列表会传递给Father类的构造器
Father father = new Father("jack"){
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
};
System.out.println("father 对象的运行类型=" + father.getClass());//Outer04$1
father.test();//动态绑定机制
System.out.println("===============================================");
//基于抽象类的匿名内部类 必须实现父类的抽象方法
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("小狗吃骨头");
}
};
System.out.println("animal 对象的运行类型=" + animal.getClass());
animal.eat();//动态绑定机制
}
}
输出结果:
接收到 name=jack
father 对象的运行类型=class com.hspedu.innerclass.Outer02$1
匿名内部类重写了test方法
===============================================
animal 对象的运行类型=class com.hspedu.innerclass.Outer02$2
小狗吃骨头
匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象。从语法上看,它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法。
package com.hspedu.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
}
}
class Outer02{
public void method(){
Person person = new Person(){
@Override
public void ok(String name) {
System.out.println("子类(匿名内部类1)重写了父类的ok方法 "+name);
}
};
person.ok("jack");
//也可以这样调用
new Person(){
@Override
public void ok(String name) {
System.out.println("子类(匿名内部类2)重写了父类的ok方法 "+name);
}
}.ok("tom");//直接调用
}
}
class Person{
public void ok(String name) {
System.out.println("父类的ok方法 "+name);
}
}
输出结果:
子类(匿名内部类1)重写了父类的ok方法 jack
子类(匿名内部类2)重写了父类的ok方法 tom
匿名内部类可以直接访问外部类的所有成员,包含私有的。
package com.hspedu.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
}
}
class Outer02{
private int n1 = 100;
public void method(){
//类继承Person,并创建了该类对象,用父类Person的引用指向子类的对象
Person person = new Person(){
@Override
public void hi() {
//匿名内部类直接访问外部类的所有成员,包含私有的
System.out.println("n1 "+n1);
}
};
person.hi();
}
}
class Person{
public void hi() {
System.out.println("父类的hi方法");
}
}
不能添加访问修饰符,因为它的地位就是一个局部变量。
作用域仅在定义它的方法或代码块中。
匿名内部类只能使用一次
匿名内部类可以直接访问外部类成员。
外部其他类不能访问匿名内部类,因为匿名内部类的地位是一个局部变量。
如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,可以使用 “外部类名.this.成员” 去访问。
package com.hspedu.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
System.out.println("outer02的运行类型:"+outer02.getClass());
}
}
class Outer02{
private int n1 = 100;
public void method(){
//类继承Person,并创建了该类对象,用父类Person的引用指向子类的对象
Person person = new Person(){
private int n1 = 200;
@Override
public void hi() {
//Outer.this本质就是外部类的对象, 即调用method的对象
//如果外部类中的n1是静态的,可以直接用Outer.n1访问
System.out.println("内部类中的n1="+n1+" 外部类中的n1="+Outer02.this.n1);
System.out.println("Outer02.this的运行类型:"+Outer02.this.getClass());
}
};
person.hi();
}
}
class Person{
public void hi() {
System.out.println("父类的hi方法");
}
}
输出结果:
内部类中的n1=200 外部类中的n1=100
Outer02.this的运行类型:class com.hspedu.innerclass.Outer02
outer02的运行类型:class com.hspedu.innerclass.Outer02
传统方法:类实现接口 ➡ 创建对象 ➡ 传参
public class Test {
public static void main(String[] args) {
//传统方法
f1(new Picture());//创建对象和传参
}
//因为不知道匿名内部类的类名
//所以只能用IL类型的形参接收
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");
}
}
匿名内部类做实参直接传递:
public class Test {
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new IL() {
@Override
public void show() {
System.out.println("一副名画~~");
}
});
}
//因为不知道匿名内部类的类名
//所以只能用IL类型的形参接收
public static void f1(IL il) {
il.show();
}
}
interface IL {
void show();
}
练习:
有一个铃声接口Bell,里面有ring方法,
有一个手机类 Cellphone,具有闹钟功能 alarmclock,参数是 Bell 类型
测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
再传入另一个匿名内部类(对象),打印:小伙伴上课了
public class Test {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
cellPhone.alarmclock(new Bell(){
@Override
public void ring(){
System.out.println("懒猪起床了");
}
});
cellPhone.alarmclock(new Bell(){
@Override
public void ring(){
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell {
void ring();
}
class CellPhone{
public void alarmclock(Bell bell){
bell.ring();
}
}
输出结果:
懒猪起床了
小伙伴上课了
说明:成员内部类定义在外部类的成员位置,并且没有static修饰
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.t1();
}
}
class Outer{
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
public class Inner{
private double sal = 99.8;
public void say(){
//可以直接访问外部类的所有成员,包括私有的
System.out.println("n1="+n1+" name="+name);
hi();
}
}
public void t1(){
//外部类访问成员内部类中的成员:先创建对象,再访问
Inner inner = new Inner();
inner.say();
//可以访问内部类的私有成员,因为在同一个类中
System.out.println("sal="+inner.sal);
}
}
public class Test {
public static void main(String[] args) {
//外部其他类,使用成员内部类的2种方式(记住就行)
Outer outer = new Outer();
//第1种方式:相当于把new Inner()当成outer的成员
Outer.Inner inner = outer.new Inner();
inner.say();
System.out.println("========================");
// 第2种方式:在外部类编写一个方法,返回Inner对象
Outer.Inner innerInstance = outer.getInnerInstance();
innerInstance.say();
}
}
class Outer{
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
public class Inner{
private double sal = 99.8;
public void say(){
//可以直接访问外部类的所有成员,包括私有的
System.out.println("n1="+n1+" name="+name);
hi();
}
}
//返回一个 Inner对象
public Inner getInnerInstance(){
return new Inner();
}
}
说明:静态内部类定义在外部类的成员位置,并且有static修饰。
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.m();
}
}
class Outer{
private int n1 = 10;
public static String name = "张三";
private static void cry() {
System.out.println("cry()方法...");
}
static class Inner{
public void say(){
//可以直接访问外部类的所有静态成员,包括私有的
System.out.println("name="+name);
cry();
}
}
public void m(){
//外部类访问静态内部类中的成员:先创建对象,再访问
Inner inner = new Inner();
inner.say();
}
}
public class Test {
public static void main(String[] args) {
//外部其他类 使静态内部类
//方式1
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer.Inner inner = new Outer.Inner();
inner.say();
System.out.println("============");
//方式2
//编写一个方法,可以返回静态内部类的对象实例
Outer.Inner inner2 = new Outer().getInner();
inner2.say();
System.out.println("============");
//方式2改进 返回对象的方法改成静态的
Outer.Inner inner3 = Outer.getInner_();
inner3.say();
}
}
class Outer{
private int n1 = 10;
public static String name = "张三";
private static void cry() {
System.out.println("cry()方法...");
}
static class Inner{
public void say(){
//可以直接访问外部类的所有静态成员,包括私有的
System.out.println("name="+name);
cry();
}
}
public Inner getInner() {//(方法2用)返回静态内部类的对象
return new Inner();
}
public static Inner getInner_() {//(方法2改进用)
return new Inner();
}
}
输出结果:
name=张三
cry()方法...
============
name=张三
cry()方法...
============
name=张三
cry()方法...