java零基础Ⅱ-- 1.面向对象编程(高级)
一、类变量和类方法
类变量 - 提出问题
提出问题的主要是目的就是让大家思考解决之道,从而引出要讲的知识点。
说:有一群小孩子在玩堆雪人,不时有新的小孩子加入,请问如何知道现在共有多少人在玩?编写程序解决。
传统的方法解决
使用我们现有的技术来解决这个问题,大家看看如何?
思路:
1、在main方法中定义一个变量 count
2、当一个小孩子加入游戏后 count++ ,最后 count 就记录有多少个小孩子在玩游戏
package com.zzpedu.static_;
public class ChildGame {
public static void main(String[] args) {
//定义一个变量 count,统计有多少个小孩子加入了游戏
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 小孩子加入了游戏...
问题分析:
1、count 是一个独立对象/类变量,很尴尬
2、以后我们访问 count 很麻烦,没有使用到OOP
3、因此,我们因此 类变量/静态变量
类变量快速入门
思考:如果,设计一个int count 表示总人数,我们在创建一个小孩时,就把 count 加1,并且 count 是所有对象共享的就OK了!我们使用类变量来解决【改进】
package com.zzpedu.static_;
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;
//定义一个变量 count,是一个类变量(静态变量) static 静态
//该变量最大的特点就是 Child 类所有的对象实例共享
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
类变量内存布局
画一个小图给大家理解。 ==> 画出一个示意图帮助理解,静态变量放在哪里?
https://blog.csdn.net/x_iya/article/details/81260154/
https://www.zhihu.com/question/59174759/answer/163207831
有效书说在方法区…, jdk 版本有关系,记住一点: static 变量是对象共享
不管static 变量,在哪里,
共识(1)static 变量是同一个类所有对象共享 (2)static类变量,在类加载的时候就生成。
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也可以看出来。
如何定义类变量
定义语法:
访问修饰符 static 数据类型 变量名; 【推荐】
static 访问修饰符 数据类型 变量名;
如何访问类的变量
类名.类的变量名【说明:类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问】
或者
对象名.类变量名【静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的】
推荐使用:类名.类变量名
public class VisitStatic {
public static void main(String[] args) {
//类名.类的变量名
//说明:类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问
System.out.println(A.name);
A a = new A();
//通过 对象名.类变量名
System.out.println("a.name=" + a.name);
}
}
class A{
//类变量
//类变量的访问,必须遵守 相关的访问权限
public static String name = "zzp先生";
}
==========控制台输出=============
zzp先生
a.name=zzp先生
类变量使用注意事项和细节讨论
1、什么时候需要使用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计使用学生共交多少钱。
Student (name, static fee)
2、类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象的独享的。
3、加上 static 称为类变量或者静态变量,否则称为实例变量/普通变量/非静态变量
class A{
//类变量
//类变量的访问,必须遵守 相关的访问权限
public static String name = "zzp先生";
//实例变量/普通成员变量/普通属性/非静态属性/非静态成员变量
private int num = 10;
}
4、类变量可以通过 类名.类变量名 或者 对象名.类变量名 类访问,但java设计者推荐我们使用 类名.类变量名 方式访问 。【前提是 满足访问修饰符的访问权限和范围】
5、实例变量不能通过 类名.类变量名 方式访问
6、类变量是在类加载就初始化 ,也就是说,即使你没有创建对象,只有类加载了,就可以使用类变量了。
7、类变量的生命周期是随着类的加载开始,随着类消亡而销毁。
public class StaticDetail {
public static void main(String[] args) {
B b = new B();
//System.out.println(B.n1);//实例变量不能通过 类名.类变量名 方式访问
System.out.println(B.n2);//200
//静态变量 是类加载的时候,就创建了,所以我们没有创建对象实例
//也可以通过 类名.类变量名 来访问
System.out.println(C.address);//中国
}
}
class B{
//实例变量
public int n1 = 100;
//静态变量
public static int n2 = 200;
}
class C{
//静态变量
public static String address = "中国";
}
类的方法基本介绍
类方法也叫静态方法。
形式如下:
访问修饰符 static 数据返回类型 方法名(){ }【推荐】
static 访问修饰符 数据返回类型 方法名(){ }
类方法的调用
使用方式:
类名.类名方法 或者
对象名.类方法名 【前提是 满足访问修饰符的访问权限范围】
类方法应用案例
请大家看一个静态方式小案例。(统计学费总和)
package com.zzpedu.static_;
public class StaticMethod {
public static void main(String[] args) {
Stu tom = new Stu("tom");
//tom.payFee(100);
Stu.payFee(100);
tom.showFee();
Stu mary = new Stu("mary");
//mary.payFee(200);
Stu.payFee(200);
mary.showFee();
//输出当前收到的总学费
System.out.println("------");
Stu.showFee();
}
}
class Stu{
private String name;//普通成员
//定义一个静态变量,来累计学生的学费
private static double fee = 0;
public Stu(String name) {
this.name = name;
}
//说明:
//1.当方法使用了 static 修饰后,改该方法就是静态方法
//2.静态就可以访问静态变量/属性
public static void payFee(double fee){
Stu.fee += fee; //累计到静态变量
}
public static void showFee(){
System.out.println("总学费有:" + Stu.fee);
}
}
==========控制台输出=============
总学费有:100.0
总学费有:300.0
------
总学费有:300.0
类方法经典的使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计静态方法,提高开发效率。
如果我们希望不创建实例(对象),也可以调用某个(即当做工具来使用),这时,把方法做成静态方法非常合适
比如:工具类中的方法 utils
Math类、Arrays类、Collections 集合类看下源码
小结:
在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这有我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务 等
//开发自己的工具类,可以将方法静态的,方便调用
class MyTools{
//求出两个数之和
public double calSum(double n1, double n2){
return n1 + n2;
}
//可以写出很多这样的工具方法...
}
类方法使用注意事项和细节讨论
1)类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区上:
类方法中无this的参数
普通方法中隐含着this的参数
2)类方法可以通过类名调用,也可以通过对象调用
3)普通方法和对象有关,需要通过对象名调用,比如:对象名.方法名(参数),不能通过类名调用
4)类方法中不允许使用和对象相关的关键字,比如:this和super。普通方法(成员方法)可以。
5)类方法(静态方法)中 只能访问 静态变量 或 静态方法
口诀:静态方法只能方法静态成员/方法
6)普通成员方法,即可以访问 非静态的成员,也可以访问静态成员。
小结:
静态方法 只能访问静态的成员,
非静态的方法,可以访问静态成员和非静态成员
(必须遵守访问权限)
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi();//ok
//非静态方法,不能通过类名调用
//D.say();//错误,需要选创建对象,在调用
D d = new D();
d.say();
}
}
class D{
private int n1 = 100;
private static int n2 = 200;
public void say(){//非静态方法,普通方法
}
public static void hi(){//静态方法,类方法
//类方法中不允许使用和对象相关的关键字,
// 比如:this和super。普通方法(成员方法)可以
//super();//错误
//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();
}
}
类方法练习
1、下面输出什么?
public class Test{
static int count = 9;
public void count(){
System.out.println("count=" + (count++));//后++ 先输出 后自增
}
public static void main(String[ ] args) {
new Test().count();//9
new Test().count();//10
System.out.println(Test.count);//11
}
}
2、看看下面代码有没有错误,如果有错误,就修改,看看输出什么?
public class TestPerson {
public static void main(String[] args) {
System.out.println("Number of total is " + Person.getTotalPerson());// 0
Person p1 = new Person();//创建对象 执行构造器 --》total++ --》 id = total
System.out.println("Number of total is " + Person.getTotalPerson());// 1
}
}
class Person{
private int id;
private static int total = 0; //初始化 0
public static int getTotalPerson(){
//id++;//错误 静态方法不可以访问非静态成员
return total;
}
public Person(){//构造器
total++;//普通方法 可以访问静态属性 total=1
id = total;// id = 1
}
}
===========控制台输出===========
Number of total is 0
Number of total is 1
3、看看下面代码有没有错误,如果有错误,就修改,看看total等于多少? = 4
public class TestPerson {
public static void main(String[] args) {
Person.setTotalPerson(3);
new Person();//最后 total的值就是 4
}
}
class Person{
private int id;
private static int total = 0; //初始化 0
public static void setTotalPerson(int total){
//this.total = total;//错误 静态方法不能使用this关键字
Person.total = total;
}
public Person(){//构造器
total++;
id = total;
}
}
小结:
(1)静态方法只能访问静态成员【属性、方法】
(2)非静态方法,可以访问使用成员
(3)在编写代码时,仍然要遵守访问权限规则
二、理解main方法语法 static
深入理解main方法
理解main方法的形式:
public static void main(String[] args){ }
-
main方法是虚拟机调用
-
java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
-
java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
-
该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数,案例演示,接收参数
-
java 执行的程序 参数1 参数2 参数3
public class Hello {
public static void main(String[] args) {
//args 是如何传入的
//遍历显示
for(int i = 0; i < args.length; i++){
System.out.println("第" + (i + 1) + "个参数=" + args[i]);
}
}
}
特别提示:
1)在main()方法找中,我们可以直接调用main方法所在类的静态方法或静态属性
2)但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
package com.zzpedu.main_;
public class Main01 {
//静态的变量/属性
private static String name = "zzp先生";
//非静态的变量/属性
private int n1 = 100;
//静态方法
public static void hi(){
System.out.println("Main01的 hi方法");
}
//非静态方法
public static void cry(){
System.out.println("Main01的 cry方法");
}
public static void main(String[] args) {
//可以直接使用 name
//1.静态方法main 可以访问本类的静态成员
System.out.println("name=" + name);//zzp先生
hi();//Main01的 hi方法
//2.静态方法main 不可以访问本类的非静态成员
//System.out.println("n1=" + n1);//错误
//cry();//错误
//3.静态方法main 要访问本类的非静态成员,需要先创建对象,再调用即可
Main01 main01 = new Main01();
System.out.println(main01.n1);//100
main01.cry();//Main01的 cry方法
}
}
============控制台输出==========
name=zzp先生
Main01的 hi方法
100
Main01的 cry方法
案例演示
public class CommandPara{
public static void main(String[] args){
for(int i = 0; i < args.length; i++){
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
//运行程序
java CommandPara "lisa" "bily" "Mr Brown"
说明:在idea如何传递参数
运行
三、代码块
基本介绍
代码块又称为初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过 { } 包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建类对象时隐式调用。
基本语法
[修饰符] {
代码
};
说明注意:
1)修饰符 可选,要写得话,也只能写 static
2)代码块可分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的,叫普通代码块/非静态代码块
3)逻辑语句可以以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4); 号可以写上,也可以省略。
代码块的好处和案例演示
理解:
1)相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2)场景:如果多个构造器中都有重复的语句,可以抽取到初始化代码块中,提高代码的重用性
package com.zzpedu.codeblock_;
public class CodeBlock01 {
public static void main(String[] args) {
Movie movie1 = new Movie("明日之战");
System.out.println("---------------------");
Movie movie2 = new Movie("失控玩家",50,"肖恩·利维");
}
}
class Movie{
private String name;
private double price;
private String director;
//3个构造器 =》 重载
//说明:
//(1) 下面的三个构造器都有相同的语句
//(2) 这样代码看起来比较冗余
//(3) 这时我们可以把相同的语句,放入一个代码块中,即可
//(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
//(5) 代码块调用顺序优先于构造器...
{
System.out.println("电影屏幕打开了...");
System.out.println("广告开始了...");
System.out.println("电影正式开始了...");
}
public Movie(String name) {
System.out.println("Movie(String name) 构造器被调用了...");
// System.out.println("电影屏幕打开了...");
// System.out.println("广告开始了...");
// System.out.println("电影正式开始了...");
this.name = name;
}
public Movie(String name, double price) {
// System.out.println("电影屏幕打开了...");
// System.out.println("广告开始了...");
// System.out.println("电影正式开始了...");
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
System.out.println("Movie(String name, double price, String director) 构造器被调用了...");
// System.out.println("电影屏幕打开了...");
// System.out.println("广告开始了...");
// System.out.println("电影正式开始了...");
this.name = name;
this.price = price;
this.director = director;
}
}
==============控制台输出================
电影屏幕打开了...
广告开始了...
电影正式开始了...
Movie(String name) 构造器被调用了...
---------------------
电影屏幕打开了...
广告开始了...
电影正式开始了...
Movie(String name, double price, String director) 构造器被调用了...
代码块使用注意事项和细节讨论
1)static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行
2)类什么时候被加载
2.1 创建对象实例时(new)
2.2 创建子类对象实例,父类也会被加载,而且,父类先被加载,子类后被加载
2.3 使用类的静态成员时(静态属性,静态方法)
案例演示:A 类 extends B 类 的静态块
3)普通的代码块,才创建对象实例时,会被隐式的调用。
被创建一次,就调用一次。
如果只是使用类的静态成员时,普通代码块并不会执行。
package com.zzpedu.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
//类被加载的情况举例
//1. 创建对象实例时(new)
//AA aa = new AA();//AA 的静态代码块1被执行...
//2. 创建子类对象实例,父类也会被加载,而且,父类先被加载,子类后被加载
AA aa = new AA();
System.out.println("-------------");
//3. 使用类的静态成员时(静态属性,静态方法)
System.out.println(Cat.n1);
System.out.println("-------------");
//static 静态代码块 是在类加载时,执行时,而且只会执行一次
//普通的代码块,才创建对象实例时,会被隐式的调用。
//被创建一次,就调用一次。
// DD dd = new DD();
// DD dd1 = new DD();
System.out.println("-------------");
//如果只是使用类的静态成员时,普通代码块并不会执行
System.out.println(DD.n1);//888 静态代码块先被调用
}
}
class DD{
public static int n1 = 888;//静态属性
//静态代码块
static {
System.out.println("DD 的静态代码块1被执行...");
}
//普通代码块,在new 对象时,被调用,而且是每创建一个对象,就调用一次
//可以这样简单的理解,普通代码块是构造器的补充
{
System.out.println("DD 的普通代码块...");
}
}
class Animal{
//静态代码块
static {
System.out.println("Animal 的静态代码块1被执行...");
}
}
class Cat extends Animal{
//静态属性
public static int n1 = 100;
//静态代码块
static {
System.out.println("Cat 的静态代码块1被执行...");
}
}
class BB {
//静态代码块
static {
System.out.println("BB 的静态代码块1被执行...");//1
}
}
class AA extends BB{
//静态代码块
static {
System.out.println("AA 的静态代码块1被执行...");//2
}
}
=========控制台输出============
BB 的静态代码块1被执行...
AA 的静态代码块1被执行...
-------------
Animal 的静态代码块1被执行...
Cat 的静态代码块1被执行...
100
-------------
-------------
DD 的静态代码块1被执行...
888
小结:
- static 代码块是类加载时,执行,只会执行一次
- 普通代码块是在创建对象时调用的,创建一次,调用一次
4)创建一个对象时,在一个类 调用顺序是:
4.1 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
4.2 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
4.3 调用构造方法。
public class CodeBlockDetail02 {
public static void main(String[] args) {
//(1) A 静态代码块01 (2) getN1被调用
//(3) getN2被调用 (4) A 普通代码块01
//(5) A() 无参构造器被调用
A a = new A();
}
}
class A{
private int n2 = getN2();//普通属性的初始化
{//普通代码块
System.out.println("A 普通代码块01....");
}
static {//静态代码块
System.out.println("A 静态代码块01....");
}
//静态属性的初始化
private static int n1 = getN1();
public static int getN1(){
System.out.println("getN1被调用...");
return 100;
}
public int getN2(){//非静态方法
System.out.println("getN2被调用...");
return 200;
}
public A(){//无参构造器
System.out.println("A() 无参构造器被调用....");
}
}
========控制台输出======
A 静态代码块01....
getN1被调用...
getN2被调用...
A 普通代码块01....
A() 无参构造器被调用....
5)构造器 的最前面其实隐含了 super() 和 调用普通代码块。
新写一个类演示,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于 构造器和普通代码块执行的
class A {
public A{//构造器
//这里有隐藏的执行要求
//(1) super();
//(2) 调用普通的代码块的
System.out.println("ok");
}
}
public class CodeBlockDetail03 {
public static void main(String[] args) {
new BBB();//(1)AAA的普通代码块 (2)AAA() 构造器被调用 (3)BBB的普通代码块 (4)BBB() 构造器被调用
}
}
class AAA{//父类Object
{
System.out.println("AAA的普通代码块...");
}
public AAA() {
//(1) super();
//(2) 调用本类的普通代码块
System.out.println("AAA() 构造器被调用...");
}
}
class BBB extends AAA{
{
System.out.println("BBB的普通代码块...");
}
public BBB() {
//(1) super();
//(2) 调用本类的普通代码块
System.out.println("BBB() 构造器被调用...");
}
}
=============控制台输出===============
AAA的普通代码块...
AAA() 构造器被调用...
BBB的普通代码块...
BBB() 构造器被调用...
6)我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
6.1 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
6.2 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
6.3 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
6.4 父类的构造方法
6.5 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
6.6 子类的构造方法
public class CodeBlockDetail04 {
public static void main(String[] args) {
//说明:
//(1) 进行类的加载
// 1.1 先加载 父类 A02
// 1.2 再加载 B02
//(2) 创建对象
// 2.1 从子类的构造器开始
// 2.2
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 static 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 static 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的构造器...
7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
public class CodeBlockDetail05 {
public static void main(String[] args) {
new C02();
}
}
class C02{
private int n1 = 100;
private static int n2 = 200;
private void m1(){
System.out.println("m1()普通方法被调用");
}
private static void m2(){
System.out.println("m2()静态方法被调用");
}
static {
//静态代码块,只能调用静态成员
//System.out.println(n1);//错误
System.out.println(n2);
//m1();//错误
m2();
}
{
//普通代码块,可以使用任意成员
System.out.println(n1);
System.out.println(n2);
m1();
m2();
}
}
============控制台输出===========
200
m2()静态方法被调用
100
200
m1()普通方法被调用
m2()静态方法被调用
练习
1、下面的代码输出什么?
class Person{
public static int total;//静态变量
static{//静态代码块
total = 100;
System.out.println("in static block!");//1
}
}
public class Test{
public static void main(String[] args){
System.out.println("total = " + Person.total);//in static block! total = 100
System.out.println("total = " + Person.total);//total = 100
}
}
public class CodeBlockExercise01 {
public static void main(String[] args){
System.out.println("total = " + Person.total);//in static block! total = 100
System.out.println("total = " + Person.total);//total = 100
}
}
class Person{
public static int total;//静态变量
static{//静态代码块
total = 100;
System.out.println("in static block!");//1
}
}
============控制台输出===========
in static block!
total = 100
total = 100
2、下面的代码输出什么?
class Sample{
Sample(String s){
System.out.println(s);//1 3
}
Sample(){
System.out.println("Sample默认构造函数被调用");
}
}
class Test{
Sample sam1 = new Sample("sam1 成员初始化");//3
static Sample sam = new Sample("静态成员sam初始化");//1
static{
System.out.println("static 块执行");//2
if(sam == null){
System.out.println("sam is null");
}
}
Test(){//构造器
System.out.println("Test默认构造函数被调用");//4
}
}
//主方法
public static void main(String[] args){
Test a = new Test();//无参构造
}
//运行结果
public class CodeBlockExercise02 {
public static void main(String[] args){
Test a = new Test();//无参构造
}
}
class Sample{
Sample(String s){
System.out.println(s);//1 3
}
Sample(){
System.out.println("Sample默认构造函数被调用");
}
}
class Test{
Sample sam1 = new Sample("sam1 成员初始化");//3
static Sample sam = new Sample("静态成员sam初始化");//1
static{
System.out.println("static 块执行");//2
if(sam == null){
System.out.println("sam is null");
}
}
Test(){//构造器
System.out.println("Test默认构造函数被调用");//4
}
}
============控制台输出===========
静态成员sam初始化
static 块执行
sam1 成员初始化
Test默认构造函数被调用
四、单例设计模式
什么是设计模式
1、静态方法和属性的经典使用
2、设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式、设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己思考和摸索
什么是单例模式
单例(单个的实例)
1、所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法
2、单例模式有两种方法:
1)饿汉式
2)懒汉式
单例模式引用实例
演示 饿汉式 和 懒汉式 单例模式的实现。
步骤如下:
1)构造器私有化 ⇒ 防止直接 new 对象
2)类的内部创建对象
3)向外暴露一个静态的公共方法。getInstance
4)代码实现
饿汉式演示
package com.zzpedu.single_;
/**
* 饿汉式演示
*/
public class SingleTon01 {
public static void main(String[] args) {
System.out.println(GirlFriend.n1);
//通过方法可以获取对象
GirlFriend instance1 = GirlFriend.getInstance();
System.out.println(instance1);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance1 == instance2);//true
}
}
//有一个类,GirlFriend
//只能有一个 GirlFriend
class GirlFriend{
private String name;
public static int n1 = 100;
//为了能够静态方法中,返回 gf对象,需要将其修饰为static
//静态 只能初始化一次
//对象,通常是重量级的对象 类加载时就创建了
//饿汉式模式可能造成创建了对象,但是没有使用
private static GirlFriend gf = new GirlFriend("小红");
//如何保障我们只能创建一个 GirlFriend
//步骤:[单例模式--饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(改对象是静态的static)
//3. 提供一个公共的static方法
private GirlFriend(String name) {
System.out.println("构造器被调用...");
this.name = name;
}
public static GirlFriend getInstance(){
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
=========控制台输出========
构造器被调用...
100
GirlFriend{name='小红'}
GirlFriend{name='小红'}
true
懒汉式演示
package com.zzpedu.single_;
/**
* 懒汉式演示
*/
public class SingleTon02 {
public static void main(String[] args) {
System.out.println(Cat.n1);
Cat instance = Cat.getInstance();
System.out.println(instance);
//再次调用getInstance
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//true
}
}
//希望在程序运行过程中,只能创建一个Cat
//使用单例模式
class Cat{
private String name;
public static int n1 = 999;
private static Cat cat;//默认为null
//步骤:
//1. 仍然将构造器私有化
//2. 定义一个static静态属性对象
//3. 提供一个public的static方法,可以返回一个Cat对象
//4. 懒汉式,只有当用户使用getInstance时,才返回cat对象,后面再次调用时,会上次创建的对象
// 从而保证了单例
private Cat(String name){
System.out.println("构造器被调用...");
this.name = name;
}
public static Cat getInstance(){
if(null == cat){//如果没有创建cat对象
cat = new Cat("小花");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
=========控制台输出========
999
构造器被调用...
Cat{name='小花'}
Cat{name='小花'}
true
饿汉式 VS 懒汉式
-
二者最主要的区别在于创建对象的 时机 不同;饿汉式是在类加载就创建了对象实例,而 懒汉式 是在使用时才创建
-
饿汉式 不存在线程安全问题,懒汉式存在线程安全问题。
-
饿汉式 存在浪费资源的可能。因为如果是程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
-
在我们javaSE标准类中,java.lang.Runtime 就是经典的单例模式。
小结:
1、单例模式的两种实现方式:(1)饿汉式(2)懒汉式
2、饿汉式的问题:在类加载时候就创建,可能存在资源浪费问题
3、懒汉式的问题:线程安全问题
五、final 关键字
基本介绍
final
中文意思:最后的,最终的
final
可以修饰类、属性、方法和局部变量。
在某些情况下,程序员可能有以下需求,就会使用到 final
:
1)当不希望类被继承时,可以使用final修饰。
2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。
演示:访问修饰符 final 返回类型 方法名
3)当不希望类的某个属性的值被修改时,可以使用final修饰
演示:public final double TAX_RATE = 0.08;
4)当不希望某个局部变量被修改时,可以使用final修饰
final double TAX_RATE = 0.08;
package com.zzpedu.final_;
public class Final01 {
public static void main(String[] args) {
E e = new E();
//e.TAX_RATE = 0.09;//报错
}
}
//如果我们要求A类不能被其它类继承
//可以使用final修饰 A类
final class A{
}
//class B extends A{ }//报错
class C{
//如果我们要求hi方法不能被子类重写
//可以使用final修饰 hi方法
public final void hi(){}
}
class D extends C{
// @Override
// public void hi() {//报错
// System.out.println("重写了C类的hi方法...");
// }
}
//当不希望类的某个属性的值被修改时,可以使用final修饰
class E{
public final double TAX_RATE = 0.08;
}
//当不希望某个局部变量被修改时,可以使用final修饰
class F{
public void cry(){
//这时,NUM 也称为 局部常量
final double NUM = 0.01;
//NUM = 0.9;//报错
System.out.println("NUM=" + NUM);
}
}
final使用注意事项和细节讨论
1)final
修饰的属性又叫常量
,一般 用 XX_XX_XX(大写)来命名
2)final 修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置初值即可】:
- 定义时:如 public final double TAX_RATE = 0.08;
- 在构造器中
- 在代码块中
3)如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 在静态代码块 不能在构造器中赋值
4)final类不能继承,但是可以实例化对象
5)如果类不是final类,但是含有final方法,则该方法虽然不能重写,但可以被继承。
public class FinalDetail01 {
public static void main(String[] args) {
CC cc = new CC();
new EE().cal();//输出:cal()方法
}
}
class AA{
//1. 定义时:如 public final double TAX_RATE = 0.08;
//2. 在构造器中
//3. 在代码块中
public final double TAX_RATE = 0.08;//1. 定义时 赋值
public final double TAX_RATE2;
public final double TAX_RATE3;
public AA() {//2. 在构造器中 赋值
TAX_RATE2 = 1.1;
}
{//3. 在代码块中 赋值
TAX_RATE3 = 8.8;
}
}
class BB{
/*
如果final修饰的属性是静态的,则初始化的位置只能是
1.定义时
2.在静态代码块 不能在构造器中赋值
*/
public static final double TAX_RATE = 99.99;//1. 定义时
public static final double TAX_RATE2;
//public static final double TAX_RATE3;
// public BB(){//2. 不能在构造器中赋值 因为构造器不在类加载的时候赋值
// TAX_RATE3 = 8.8;
// }
static {//2. 在静态代码块中赋值
TAX_RATE2 = 3.3;
}
}
//final类不能继承,但是可以实例化对象
final class CC{ }
//如果类不是final类,但是含有final方法,则该方法虽然不能重写,但可以被继承
//即,仍然遵守继承的机制
class DD{
public final void cal(){
System.out.println("cal()方法");
}
}
class EE extends DD{ }
6)一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
7)final不能修饰构造器方法(即构造器)
8)final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理。
9)包装类(Integer、Double、Float、Boolean等都是final),String也是final类。
public class FinalDetail02 {
public static void main(String[] args) {
System.out.println(BBB.NUM);//10000 静态代码块不会执行
}
}
//final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理。
class BBB{
//final 和 static 可以颠倒
public final static int NUM = 10000;
static{
System.out.println("BBB 静态代码块执行了...");
}
}
final class AAA{
//一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
//public final void cry(){ }
public void cry(){ }
}
final应用实例
题1
请编写一个程序,能够计算圆形的面积。要求圆周率为 3.14。赋值的位置3个方式都写一下
public class FinalExercise01 {
public static void main(String[] args) {
Circle circle = new Circle(5.0);
System.out.println("面积=" + circle.calArea());//面积=78.5
}
}
class Circle{
private double radius;//半径
//private final double PI = 3.14;//第1种 推荐
private final double PI;
//构造器
public Circle(double radius) {
this.radius = radius;
// PI = 3.14;//第2种
}
{//普通代码块
PI = 3.14;//第3种
}
public double calArea(){
return PI * radius * radius;
}
}
题2
public class Something{//下面代码有误,为什么?
public int addOne(final int x){
++x;//错误,原因是不能修改 final x的值
return x + 1;//这里是可以的
}
}
六、抽象类
先看一个问题
一个小问题,还是看个程序
class Animal{
private String name;
private int age;
public Animal(String name, int age){
super();
this.name = name;
this.age = age;
}
//动物都有eat的行为
public void eat(){
System.out.println("这是一个动物,但是目前不知道吃什么");
}
}
父类方法不确定性
小结:
当父类的某些方法,需要声明,但是有又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。
abstract class Animal{
private String name;
public Animal(String name) {
this.name = name;
}
//思考:这里eat 这里你实现了,其实没有什么意义
//即:父类方法不确定性的问题
//===》 考虑将改方法设计为抽象(abstract)方法
//===》 所谓抽象方法就是没有实现的方法
//===》 所谓没有实现是就是指,没有方法体
//===》 当一个类中存在抽象方法时,需要将该类声明abstract类
//===》 一般来说,抽象类会被继承,由其子类来实现抽象方法
// public void eat(){
// System.out.println("这是一个动物,但是不知道吃什么...");
// }
public abstract void eat();//抽象方法
}
抽象类的介绍
1)用 abstract
关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{ }
2)用 abstract
关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体
3)抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类
4)抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
抽象类使用的注意事项和细节讨论
1)抽象类不能被实例化
2)抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有abstract方法,还可以有实现的方法
3)一旦类包含了abstract方法,则这个类必须声明为abstract
4)abstract 只能修饰类和方法,不能修饰属性和其他的。
package com.zzpedu.abstract_;
public class AbstractDetail01 {
public static void main(String[] args) {
//1. 抽象类不能被实例化
// new A();//报错
}
}
//2. 抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有abstract方法
//,还可以有实现的方法
abstract class A{
public void hi(){
System.out.println("hi");
}
}
//3. 一旦类包含了abstract方法,则这个类必须声明为abstract
abstract class B{
public abstract void hi();
}
//4. abstract 只能修饰类和方法,不能修饰属性和其他的。
class C{
//public abstract int n1 = 1;//报错
}
5)抽象类可以有任意成员【因为抽象类还是类】,比如:非抽象方法、构造器、静态属性等等
6)抽象方法不能有主体,即不能实现,
abstract void aaa(){ };//错误,不能有{ }
7)如果一个类继承了抽象类,则它必须实现抽象类的使用抽象方法,除非自己也声明为abstract类
8)抽象方法不能使用private、final 和 static
来修饰,因为这些关键字都是和重写相违背的
class Father{
public static void method(){
System.out.println("父类的方法");
}
}
class Son exends Father{
public static void method(){
System.out.println("子类的方法");
}
}
//抽象方法不能使用private、final 和 static 来修饰,因为这些关键字都是和重写相违背的
abstract class H{
public abstract void hi();//抽象方法
//private abstract void OK();//报错
//public final abstract void OK();//报错
//static abstract void OK();//报错
}
//如果一个类继承了抽象类,则它必须实现抽象类的使用抽象方法,除非自己也声明为abstract类
abstract class E{
public abstract void hi();
}
abstract class F extends E{ }
class G extends F{
@Override
public void hi() {//这里相等于G子类实现了父类E的抽象方法,所谓实现方法,就是有方法体
}
}
// 抽象类本质还是类,所以可以有类的各种成员
abstract class D{
public int n1 = 10;
public static String name = "zzp先生";
public void hi(){
System.out.println("hi");
}
public abstract void hello();
public static void ok(){
System.out.println("ok");
}
}
练习题
1)题1:思考:abstract final class A{} 能编译通过吗?为什么
错误。final 修饰的类不能被继承,因为abstract 类需要被子类继承
2)题2:思考:abstract public static void test02(); 能编译通过吗?为什么
错误。static 关键字和重写无关 ,因为abstract 方法需要被重写
3)题3:思考:abstract private void test03(); 能编译通过吗?为什么
错误。private 的方法不能重写,因为abstract 方法需要被重写
4)编写一个Employee类,声明为抽象,包含如下三个属性:
name,id,salary。
提供必要的构造器和抽象方法:work()。对于Manager类来说,他即是员工,还具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,实现work(),提示:“经理/普通员工 名字 工作中…”
package com.zzpedu.abstract_;
public 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; }
public void setName(String name) { this.name = name; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
}
public class Manager extends Employee{
//特有属性
private double bonus;
public Manager(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("经理 " + getName() + " 工作中...");
}
public double getBonus() { return bonus; }
public void setBonus(double bonus) { this.bonus = bonus; }
}
public class CommonEmployee extends Employee{
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("普通员工 " + getName() + " 工作中...");
}
}
public class AbstractExercise01 {
public static void main(String[] args) {
//测试
Manager jack = new Manager("jack", 998, 50000);
jack.setBonus(8000);
jack.work();
CommonEmployee commonEmployee = new CommonEmployee("tom",888,20000);
commonEmployee.work();
}
}
==========控制台输出=========
经理 jack 工作中...
普通员工 tom 工作中...
抽象类最佳实践 - 模板设计模式
最佳实践
需求:
1)有对个类,完成不同的任务job
2)要求统计得到各自完成任务的时间
3)请编程实现
感情的自然流露:
1、先用最容易想到的方法 -》 代码实现
2、分析问题,提出使用模板设计模式
设计一个抽象类(Template),能完成如下功能:
1)编写一个方法calculateTime(),可以计算某段代码的耗时时间
2)编写抽象方法job();
3)编写一个子类Sub,继承抽象类Template,并实现job方法
4)编写一个测试类TestTemplate,看看是否好用。
package com.zzpedu.abstract_;
public abstract class Template {//抽象类 - 模板设计模式
public abstract void job();//抽象方法
public void calculateTime(){//实现方法,调用job方法
//得到开始时间
long start = System.currentTimeMillis();
job();//动态绑定机制
//得到结束的时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间(单位毫秒)=" + (end - start) );
}
}
public class AA extends Template{
//计算任务
//1+..+800000
@Override
public void job(){//这里重写的父类方法
long num = 0;
for (long i = 1; i <= 800000; i++) {
num += i;
}
}
}
public class BB extends Template{
@Override
public void job(){//这里重写的父类方法
long num = 0;
for (long i = 1; i <= 80000; i++) {
num *= i;
}
}
}
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();//多态,动态绑定机制
BB bb = new BB();
bb.calculateTime();
}
}
七、接口
为什么有接口
请求大家看一张图:
usb插槽就是实现中的接口
你可以把手机,相机,u盘都插在sub插槽上,而不用担心那个插槽是专门插哪个的,原因是做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定包括尺寸,排线等等。
接口快速入门
这样的设计需求在java编程 /php/.net/go 中也是会大量存在的。我曾经说过,一个程序就是一个世界,在现实世界存在的情况,在程序也会出现。我们用程序来模拟一下。
package com.zzpedu.interface_;
public interface UsbInterface {//接口
//规定接口的相关方法,即 规范
public void start();
public void stop();
}
//Phone 类 实现 UsbInterface接口
//1. 即Phone类需要实现 UsbInterface接口 规定/声明的方法
public class Phone implements UsbInterface{
@Override
public void start() {
System.out.println("手机开始工作...");
}
@Override
public void stop() {
System.out.println("手机停止工作...");
}
}
public class Camera implements UsbInterface{//实现接口,就是把接口的方法实现
@Override
public void start() {
System.out.println("相机开始工作...");
}
@Override
public void stop() {
System.out.println("相机停止工作...");
}
}
public class Interface01 {
public static void main(String[] args) {
//创建手机,相机对象
Camera camera = new Camera();
Phone phone = new Phone();
Computer computer = new Computer();
computer.work(phone);//那手机接入到计算机
System.out.println("--------------");
computer.work(camera);//那相机接入到计算机
}
}
======控制台输出=========
手机开始工作...
手机停止工作...
--------------
相机开始工作...
相机停止工作...
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
语法:
interface 接口名{
//属性
//方法(1.抽象方法 2.默认实现方法 3.静态方法)
}
class 类名 implements 接口{
//自己属性
//自己方法
必须实现的接口的抽象方法
}
小结:
1、在 jdk7.0 前 接口里面的所有方法都没有方法体,即都是抽象方法
2、jdk8.0 后 接口可以有静态方法(但是需要使用default关键字修饰),默认方法,也就是说接口中可以有方法的具体实现
package com.zzpedu.interface_;
public interface AInterface {
//写属性
public int n1 = 10;
//写方法
//在接口中,抽象方法,可以省略abstract关键字
public void hi();
//在jkd8后,可以默认实现方法,但是需要使用default关键字修饰
default void ok(){
System.out.println("ok()...");
}
//在jkd8后, 可以静态方法
public static void cry(){
System.out.println("cry()...");
}
}
//解读
//1.如果一个类 implements实现 接口
//2.需要将该接口的所有抽象方法都实现
class A implements AInterface{
@Override
public void hi() {
System.out.println("hi()...");
}
}
深入讨论
对初学者讲,理解接口的概念不算太难,难的是不知道什么时候使用接口,下面列举几个应用场景:
1、说现在要制造战斗机,武装直升机,专家只需要的功能/规格定下来即可,然后让别的人具体实现即可。
2、说现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后有程序员具体实现。
实际要求:3个程序员,编写三个类,分别完成对Mysql,Oracle,DB2数据库的连接 connect close
package com.zzpedu.interface_;
public interface DBInterface {//项目经理定义
public void connect();//连接方法
public void close();//关闭连接
}
//A程序员连接mysql
public class MysqlDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接mysql");
}
@Override
public void close() {
System.out.println("关闭mysql");
}
}
//B程序员连接Oracle
public class OracleDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接oracle");
}
@Override
public void close() {
System.out.println("连接oracle");
}
}
public class Interface03 {
public static void main(String[] args) {
MysqlDB mysqlDB = new MysqlDB();
t(mysqlDB);
OracleDB oracleDB = new OracleDB();
t(oracleDB);
}
public static void t(DBInterface db){
db.connect();
db.close();
}
}
============控制台输出===================
连接mysql
关闭mysql
连接oracle
连接oracle
注意事项和细节
1)接口不能被实例化
2)接口中所以方法是 public 方法,接口中的抽象方法,可以不用abstract 修饰
void add();
实际上是 abstract void add();
void add(){ };//错误
3)一个普通类实现接口,就必须将该接口的所有方法都实现
4)抽象类实现接口,可以不用实现接口的方法。
package com.zzpedu.interface_;
public class InterfaceDetail01 {
public static void main(String[] args) {
//接口不能被实例化
// new IA();//报错
}
}
//1.接口不能被实例化
//2.接口中所以方法是 public 方法,接口中的抽象方法,可以不用abstract 修饰
//3.一个普通类实现接口,就必须将该接口的所有方法都实现,(将光标放在类上,使用 alt + enter 快捷键来解决)
//4.抽象类实现接口,可以不用实现接口的方法
interface IA{
void say();//等价于==》public abstract void say();
void hi();//修饰符 public protected 默认 private
}
class Cat implements IA{
@Override
public void say() {
}
@Override
public void hi() {
}
}
//4.抽象类实现接口,可以不用实现接口的方法
abstract class Tiger implements IA{
}
5)一个类同时可以实现多个接口
6)接口中的属性,只能是final的,而且是 public static final 修饰符:
比如:
int a = 1; 实际上是 public static final int a =1;(必须初始化)
7)接口中属性的访问形式:接口名.属性名
8)接口不能继承其它类,但是可以继承多个别的接口
interface A extends B,C{ }
9)接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的。
public class InterfaceDetail02 {
public static void main(String[] args) {
//接口中的属性,是 public static final
System.out.println(IB.a);//1 说明a就是static静态属性
//IB.a = 10;//报错 不能修改因为a是 final属性
}
}
interface IB{
//6.接口中的属性,只能是final的,而且是 public static final 修饰符
int a = 1;//等价于==》public static final int a = 1;//(必须初始化)
void hi();
}
interface IC{
void say();
}
//8.接口不能继承其它类,但是可以继承多个别的接口
interface ID extends IB,IC{
}
//9.接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的。
interface IE{ }
//5.一个类同时可以实现多个接口
class Pig implements IB,IC{
@Override
public void hi() {
}
@Override
public void say() {
}
}
练习
1、
interface A{
int a = 23;//等价于==》public static final int a = 23;
}
class B implements A{
}
在main中
B b = new B();//ok
System.out.println(b.a);//23
System.out.println(A.a);//23
System.out.println(B.a);//23
语法是否正确,如果正确,输出什么?
public class InterfaceExercise01 {
public static void main(String[] args) {
B b = new B();//ok
System.out.println(b.a);//23
System.out.println(A.a);//23
System.out.println(B.a);//23
}
}
interface A{
int a = 23;//等价于==》public static final int a = 23;
}
class B implements A{ }
========控制台输出======
23
23
23
实现接口 VS 继承类
代码演示:
public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey wukong = new LittleMonkey("悟空");
wukong.climbing();
wukong.swimming();
wukong.flying();
}
}
//猴子
class Monkey{
private String name;
public Monkey(String name){
super();
this.name = name;
}
public void climbing(){
System.out.println(name + " 会爬树...");
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
//接口
interface Fishable{
void swimming();
}
interface Birdable{
void flying();
}
//继承
//小结:当子类继承父类,就自动拥有父类的功能,
// 如果子类需要扩展功能,可以通过实现接口的方式扩展
// 可以理解,实现接口 是 对java 单继承机制的一种补充。
class LittleMonkey extends Monkey implements Fishable,Birdable{
public LittleMonkey(String name){
super(name);
}
@Override
public void flying() {
System.out.println(this.getName() + " 通过学习,可以像鸟儿一样飞翔...");
}
@Override
public void swimming() {
System.out.println(this.getName() + " 通过学习,可以像鱼儿一样游泳...");
}
}
========控制台输出==========
悟空 会爬树...
悟空 通过学习,可以像鱼儿一样游泳...
悟空 通过学习,可以像鸟儿一样飞翔...
接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活…
接口比继承更加灵活
接口比继承更加灵活,继承是满足 is - a 的关系,而接口是需要满足 like - a 的关系。
接口在一定程度上实现代码解耦【即:接口规范性 + 动态绑定】
接口的多态特性
1)多态参数
在前面的Usb接口案例,UsbInterface usb,即可以接收手机对象,有可以接收相机对象,就体现了 接口多态(接口引用可以指向实现了接口的类对象)
public class Computer {
//编写一个方法,计算机工作
//解读:
//1. UsbInterface usbInterface 形参是接口类型
//2. 看到 接收 实现了 UsbInterface接口的类的对象实例
public void work(UsbInterface usbInterface){
//通过接口,来调用方法
usbInterface.start();
usbInterface.stop();
}
}
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口的多态体现
//接口类型的变量 if01 可以指向 实例了IF接口类的对象实例
IF if01 = new Monster();
if01 = new Car();
//继承体现的多态
//父类类型的变量 a 可以指向 继承了AAA的子类的对象实例
AAA a = new BBB();
a = new CCC();
}
}
interface IF{ }
class Monster implements IF{}
class Car implements IF{}
class AAA{
}
class BBB extends AAA{}
class CCC extends AAA{}
2)多态数组
演示一个案例:给Usb数组中,存放 Phone 和 相机对象,Phone类还一个特有的方法call(),请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,还需要调用Phone 特有的方法 call
public class InterfacePolyArr {
public static void main(String[] args) {
//多态数组 --> 接口数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
/*
给Usb数组中,存放 Phone 和 相机对象,Phone类还一个特有的方法call(),
请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,
还需要调用Phone 特有的方法 call
*/
for (int i = 0; i < usbs.length; i++) {
usbs[i].work();//动态绑定
//类型判断,向下转型
if(usbs[i] instanceof Phone_){//判断它的运行类型是 Phone_
((Phone_) usbs[i]).call();
}
}
}
}
interface Usb{
void work();
}
class Phone_ implements Usb{
public void call(){
System.out.println("手机可以打电话...");
}
@Override
public void work() {
System.out.println("手机工作中...");
}
}
class Camera_ implements Usb{
@Override
public void work() {
System.out.println("相机工作中...");
}
}
=========控制台输出======
手机工作中...
手机可以打电话...
相机工作中...
3)接口存在多态传递现像。
/**
* 演示接口存在多态传递现像
*/
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实例该接口的类的对象实例
IG ig = new Teacher();
//如果IG 继承了 IH接口,而Teacher 类实现了 IG接口
//那么,实际上就相当于 Teacher 类也实现了 IH接口
//这就是所谓的 接口多态传递现象
IH ih = new Teacher();
}
}
interface IH{
void hi();
}
interface IG extends IH{ }
class Teacher implements IG {
@Override
public void hi() {
}
}
接口练习
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();//0 1
}
}
代码有没错误,有错误就改,改好后,看输出什么?
八、内部类
基本介绍
一个类的内部类又完整的嵌套了另一个类结构。被嵌套的类称为 内部类(inner class),嵌套其他类的类称为外部类(out class)。是我们类的第五大成员【属性、方法、构造器、代码块、内部类】,内部类最大的特点就是直接访问私有属性,并且可以体现类于类之间包含关系。【注意:内部类是学习的难点,同时也是重点】
基本语法
class Outer{//外部类
class Inner{//内部类
}
}
class Other{//外部其他类
}
快速入门案例
class Outer{
private int n1 = 100;//属性
public Outer(int n1) {//构造器
this.n1 = n1;
}
public void m1(){//方法
System.out.println("m1()方法");
}
{//代码块
System.out.println("代码块...");
}
class Inner{//内部类,在Outer类的内部
}
}
内部类的分类
定义在外部类局部位置上(比如方法内):
1)局部内部类(有类名)
2)匿名内部类(没有类名,重点)
定义在外部类的成员位置上:
1)成员内部类(没有static修饰)
2)静态内部类(使用static修饰)
局部内部类的使用
说明:局部内部类是定义在外部的局部位置,比如方法中,并且有类名。
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类 – 访问 —> 外部类的成员【访问方式:直接访问】
- 外部类 – 访问 —> 局部内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
记住:(1)局部内部类定义在方法中或者代码块中
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类
package com.zzpedu.innerclass;
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass {
public static void main(String[] args) {
//演示一下
Outer02 outer02 = new Outer02();
outer02.m1();
}
}
class Outer02{//外部类
private int n1 = 200;//属性
private void m2(){//私有方法
System.out.println("Outer02外部类的m2()方法...");
}
public void m1(){//方法
//1.局部内部类是定义在外部的局部位置,通常在方法中
//3.不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。
// 但是可以使用final 修饰,因为局部变量也可以使用final
//4.作用域:仅仅在定义它的方法或代码块中。
final class Inner02{//局部内部类(本质仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的
public void f1(){
//5.局部内部类可以直接访问外部类的成员,比如下面 外部类的n1 和m2()
System.out.println("n1=" + n1);
m2();
}
}
//6.外部类在方法中,可以创建Inner02对象实例,然后调用方法
Inner02 inner02 = new Inner02();
inner02.f1();
}
{//代码块
class Inner03{
}
}
}
=============控制台输出=========================
n1=200
Outer02外部类的m2()方法...
- 外部类其他类 – 不能访问 —> 局部内部类(因为 局部内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println(“外部类的n2=” + 外部类.this.n2);
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass {
public static void main(String[] args) {
//演示一下
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02的hashcode=" + outer02);
}
}
class Outer02{//外部类
private int n1 = 200;//属性
private void m2(){//私有方法
System.out.println("Outer02外部类的m2()方法...");
}
public void m1(){//方法
final class Inner02{//局部内部类(本质仍然是一个类)
private int n1 = 800;
public void f1(){
//7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
// 则可以使用(外部类名.this.成员)去访问
// Outer02.this.n1 本质就是外部类的对象,即哪个对象调用了m1, Outer02.this就是哪个对象
System.out.println("局部内部类n1=" + n1 + " 外部类n1=" + Outer02.this.n1);
System.out.println("Outer02.this hashcode=" + Outer02.this);
m2();
}
}
//6.外部类在方法中,可以创建Inner02对象实例,然后调用方法
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
=============控制台输出=========================
局部内部类n1=800 外部类n1=200
Outer02.this hashcode=com.zzpedu.innerclass.Outer02@1b6d3586
Outer02外部类的m2()方法...
outer02的hashcode=com.zzpedu.innerclass.Outer02@1b6d3586
匿名内部类的使用(重要)
(1)本质是类
(2)内部类
(3)该类没有名字
(4)同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
1、匿名内部类的基本语法
new 类或接口(参数列表){
类体
};
/**
* 演示匿名内部类的使用
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04{//外部类
private int n1 = 10;//属性
public void method(){//方法
//基于接口的匿名内部类
//解读:
//1.需求:想使用IA接口,并创建对象
//2.传统方法,是写一个类,实现该接口,并实创对象
//3.需求是 Tiger 类只是使用一次,后面再不使用
//4. 可以使用匿名内部类来简化开发
//5. tiger的编译类型 是IA
//6. tiger的运行类型 就是匿名内部类 Outer04$1
/*
我们看底层:会分配 类名 Outer04$1
class Outer04$1 implements IA{
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
*/
//7. jdk底层在创建匿名内部类 Outer04$1,立即马上创建了 Outer04$1实例,并且把地址
// 返回给 tiger
//8. 匿名内部类使用一次,就不能再使用
IA tiger = new IA(){
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
};
System.out.println("tiger的运行类型=" + tiger.getClass());
tiger.cry();
tiger.cry();
tiger.cry();
// IA tiger = new Tiger();
// tiger.cry();
}
}
interface IA{//接口
public void cry();
}
//class Tiger implements IA{
// //传统方法,是写一个类,实现该接口,并实创对象
// @Override
// public void cry() {
// System.out.println("老虎叫唤...");
// }
//}
//class Dog implements IA{
// @Override
// public void cry() {
// System.out.println("小狗汪汪...");
// }
//}
=================控制台输出==========================
tiger的运行类型=class com.zzpedu.innerclass.Outer04$1
老虎叫唤...
老虎叫唤...
老虎叫唤...
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04{//外部类
private int n1 = 10;//属性
public void method(){//方法
IA tiger = new IA(){
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
};
System.out.println("tiger的运行类型=" + tiger.getClass());
tiger.cry();
//演示基于类的匿名内部类
//分析:
//1. father的编译类型 是 Father
//2. father的运行类型 是 Outer04$2
//3. 底层会创建匿名内部类
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写test()方法");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
//5. 注意("jack") 参数列表会传递给 构造器
Father father = new Father("jack"){
@Override
public void test() {
System.out.println("匿名内部类重写test()方法");
}
};
System.out.println("father对象的运行类型=" + father.getClass());//Outer04$2
father.test();
//基于抽象类的匿名内部类
// 抽象类必须抽象方法
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("小狗啃骨头...");
}
};
System.out.println("animal对象的运行类型=" + animal.getClass());//Outer04$3
animal.eat();
}
}
interface IA{//接口
public void cry();
}
class Father{//类
public Father(String name){//构造器
System.out.println("Father的构造器name=" + name);
}
public void test(){//方法
}
}
abstract class Animal{
abstract void eat();
}
=================控制台输出==========================
tiger的运行类型=class com.zzpedu.innerclass.Outer04$1
老虎叫唤...
Father的构造器name=jack
father对象的运行类型=class com.zzpedu.innerclass.Outer04$2
匿名内部类重写test()方法
animal对象的运行类型=class com.zzpedu.innerclass.Outer04$3
小狗啃骨头...
2、匿名内部类的语法比较奇特
请大家注意,因为匿名内部类即是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法
new A(){
@Verride
public void cry(){
System.out.println("hello~");
}
}.cry();
A a = new A(){
@Verride
public void cry(){
System.out.println("hello~");
}
};
a.cry();
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
//创建
Outer05 outer05 = new Outer05();
outer05.f1();
}
}
class Outer05{
private int n1 = 99;
public void f1(){
//创建一个基于类的匿名内部类
Person p = new Person(){
@Override
public void hi() {
System.out.println("匿名内部类重写了 hi方法");
}
};
p.hi();//动态绑定,运行类型是 outer05$1
//也可以直接调用,匿名内部类本身也是返回对象
// class 匿名内部类 extends Person{ }
new Person(){
@Override
public void ok(String str) {
super.ok(str);
}
}.ok("jack");
}
}
class Person{//类
public void hi(){
System.out.println("Person hi()方法");
}
public void ok(String str){
System.out.println("Person ok()方法 参数=" + str);
}
}
//抽象类/接口...
==========控制台输出==========
匿名内部类重写了 hi方法
Person ok()方法 参数=jack
3、可以直接访问外部类的使用成员,包含私有的
4、不能添加访问修饰符,因为它的地位就是一个局部变量
5、作用域:仅仅在定义它的方法或代码块中
6、匿名内部类 – 访问 —> 外部类成员 【访问方式:直接访问】
7、外部类其他类 – 不能访问 —> 匿名内部类(因为 匿名内部类地位是一个局部变量)
8、如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
//创建
Outer05 outer05 = new Outer05();
outer05.f1();
//外部类其他类 – 不能访问 —> 匿名内部类(因为 匿名内部类地位是一个局部变量)
System.out.println("main方法 outer05 hashcode=" + outer05);
}
}
class Outer05{
private int n1 = 99;
public void f1(){
//创建一个基于类的匿名内部类
//不能添加访问修饰符,因为它的地位就是一个局部变量
//作用域:仅仅在定义它的方法或代码块中 outer05$1
Person p = new Person(){
private int n1 = 88;
@Override
public void hi() {
//可以直接访问外部类的使用成员,包含私有的
//如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
// 默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println("匿名内部类重写了 hi方法 n1=" + n1 +
" 外部类的n1=" + Outer05.this.n1);
//Outer05.this 就是调用 f1的 对象
System.out.println("Outer05.this hashcode=" + Outer05.this);
}
};
p.hi();//动态绑定,运行类型是 outer05$1
}
}
class Person{//类
public void hi(){
System.out.println("Person hi()方法");
}
public void ok(String str){
System.out.println("Person ok()方法 参数=" + str);
}
}
==========控制台输出==========
匿名内部类重写了 hi方法 n1=88 外部类的n1=99
Outer05.this hashcode=com.zzpedu.innerclass.Outer05@1b6d3586
main方法 outer05 hashcode=com.zzpedu.innerclass.Outer05@1b6d3586
匿名内部类的最佳实际
1、当做实参直接传递,简洁高效
public class InnerClassExercise01 {
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new IL() {
@Override
public void show() {
System.out.println("这时一副名画~~...");
}
});
//传统方式
f1(new Picture());
}
//静态方法,形参是接口类型
public static void f1(IL il){
il.show();
}
}
//接口
interface IL{
void show();
}
//类 -> 实现IL => 编程领域 (硬编码)
class Picture implements IL{
@Override
public void show() {
System.out.println("这时一副名画...");
}
}
==========控制台输出==========
这时一副名画~~...
这时一副名画...
2、(1)有一个铃声接Bell,里面有个ring方法。
(2)有一个手机号类CellPhone,具有闹钟功能alarmClock,参数是Bell类型
(3)测试手机类的闹钟功能;通过匿名内部类(对象)作为参数,打印:懒猪起床了
(4)再打印另外一个匿名内部类(对象),打印:小伙伴上课了
public class InnerClassExercise02 {
public static void main(String[] args) {
/*
(1)有一个铃声接Bell,里面有个ring方法。
(2)有一个手机号类CellPhone,具有闹钟功能alarmClock,参数是Bell类型
(3)测试手机类的闹钟功能;通过匿名内部类(对象)作为参数,打印:懒猪起床了
(4)再打印另外一个匿名内部类(对象),打印:小伙伴上课了
*/
CellPhone cellPhone = new CellPhone();
//解读:
//1. 传递的是实现了 Bell接口的匿名内部类 InnerClassExercise02$1
//2. 重写了 ring
//3. Bell bell = new Bell() {
// @Override
// public void ring() {
// System.out.println("懒猪起床了");
// }
// });
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接口类型
System.out.println("bell.getClass()=" + bell.getClass());
bell.ring();//动态绑定
}
}
==========控制台输出==========
bell.getClass()=class com.zzpedu.innerclass.InnerClassExercise02$1
懒猪起床了
bell.getClass()=class com.zzpedu.innerclass.InnerClassExercise02$2
小伙伴上课了
匿名内部类:(1)继承 (2)多态 (3)动态绑定 (4)内部类
成员内部类的使用
说明:成员内部类是定义在外部类的成员位置,并且没有static修饰。
1、可以直接访问外部类的所有成员,包含私有的
2、可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
}
}
class Outer08{//外部类
private int n1 = 10;
public String name = "张三";
//1.注意:成员内部类,是定义在外部类的成员位置,并且没有static修饰。
//2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
public class Inner08{//成员内部类
public void say(){
//可以直接访问外部类的所有成员,包含私有的
System.out.println("n1=" + n1 + " name=" + name);
}
}
//方法
public void t1(){
//使用成员内部类
Inner08 inner08 = new Inner08();
inner08.say();
}
}
==========控制台输出==========
n1=10 name=张三
3、作用域:和外部类其他成员一样,为整个类体,比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。
4、成员内部类 --> 访问 —> 外部类成员(比如:属性)【访问方式:直接访问】
5、外部类 --> 访问—> 成员内部类【访问方式:创建对象,在访问】
6、外部其他类 --> 访问 --> 成员内部类
7、如果外部类和内部类的成员重名,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用【外部类.this.成员】去访问
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的三种方式
//解读:
// 第1种方式
// outer08.new Inner08();相当于把 new Inner08()当做是outer08成员
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第2种方式
// 在外部类中,编写一个方法,可以返回 Inner08对象
Outer08.Inner08 inner08Instance = outer08.getInner08();
inner08Instance.say();
// 第3种方式
Outer08.Inner08 inner081 = new Outer08().new Inner08();
}
}
class Outer08{//外部类
private int n1 = 10;
public String name = "张三";
private void hi(){
System.out.println("hi()方法");
}
public class Inner08{//成员内部类
private double sal = 99.9;
private int n1 = 66;
public void say(){
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,遵守就近原则
//,可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("n1=" + n1 + " name=" + name +
" 外部类的n1=" + Outer08.this.n1);
hi();
}
}
//该方法,返回一个Inner08实例
public Inner08 getInner08(){
return new Inner08();
}
//方法
public void t1(){
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
==========控制台输出==========
n1=66 name=张三 外部类的n1=10
hi()方法
99.9
n1=66 name=张三 外部类的n1=10
hi()方法
n1=66 name=张三 外部类的n1=10
hi()方法
静态内部类的使用
说明:静态内部类是定义在外部类的成员位置,并且有static修饰
1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2、可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
3、作用域:同其他的成员,为整个类体
4、静态内部类 – 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
5、外部类 – 访问 —> 静态内部类【访问方式:创建对象,再访问】
6、外部其他类 – 访问 —> 静态内部类
new 外部类.静态内部类
编写一个方法,可以返回静态内部类
7、如果外部类和静态内部类的成员重名时,静态内部类访问时,默认遵循就近原则,如果想访问外部类的成员,则可以使用【外部类名.成员】去访问
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
//外部其他类 使用 静态内部类
//方式一
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式二
//编写一个方法,可以返回静态内部类
Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("---------");
inner101.say();
System.out.println("---------");
Outer10.Inner10 inner10_ = Outer10.getInner10_();
inner10_.say();
}
}
class Outer10{//外部类
private int n1 = 10;
public static String name = "张三";
private static void cry(){ }
//Inner10 就是静态内部类
//1. 放在外部类成员位置
//2. 使用static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
//5. 作用域:同其他的成员,为整个类体
public static class Inner10{
private static String name = "zzp";
public void say(){
//如果外部类和静态内部类的成员重名时,静态内部类访问时,
// 默认遵循就近原则,如果想访问外部类的成员,则可以使用【外部类名.成员】去访问
System.out.println(name + " 外部类name=" + Outer10.name);
cry();
}
}
//外部类 – 访问 —> 静态内部类【访问方式:创建对象,再访问】
public void m1(){
Inner10 inner10 = new Inner10();
inner10.say();
}
//方法
public Inner10 getInner10(){
return new Inner10();
}
//静态方法
public static Inner10 getInner10_(){
return new Inner10();
}
}
==========控制台输出==========
zzp 外部类name=张三
zzp 外部类name=张三
---------
zzp 外部类name=张三
---------
zzp 外部类name=张三
小结:
(1)内部类有四种,局部内部类,匿名内部类,成员内部类,静态内部类
(2)重点还是掌握 匿名内部类的使用
new 类/接口(参数列表){ //…};
(3)成员内部类,静态内部类 是放在外部类的成员位置,本质就是一个成员
测试题
1、写出输出结果
public class Test{//外部类
public Test(){//构造器
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
System.out.println(s2.a);
}
class Inner{//成员内部类
public int a = 5;
}
public static void main(String[] args) {
Test t = new Test();//构造器输出 5
Inner r = t.new Inner();//创建成员内部类对象
System.out.println(r.a);//5
}
}