韩顺平 零基础30天学会Java 学习笔记
文章目录
第10章 面向对象编程(高级部分)(P374 - P424)
10.1 类变量和类方法
类变量
-
类变量(又称静态变量/静态属性),是该类的所有对象共享的变量。任何一个该类的对象去访问它时,取到的都是相同的值。同样任何一个该类的对象去修改它时,修改的也是同一个变量。
类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问
普通成员变量 = 普通属性 = 非静态属性 = 非静态成员变量 = 实例变量
实例变量 = 普通变量 = 非静态变量
-
语法
访问修饰符 static 数据类型 变量名;
[推荐]static 访问修饰符 数据类型 变量名;
-
访问类变量
类名.类变量名
[推荐] 或者对象名.类变量名
静态变量的访问修饰符的访问权限和范围 和 普通属性是一致的
-
类变量使用注意事项和细节讨论
- 什么时候需要类变量:当需要让每个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)
类方法
-
类方法又称静态方法
-
语法
访问修饰符 static 数据返回类型 方法名(){}
[推荐]static 访问修饰符 数据返回类型 方法名(){}
-
调用类方法
类名.类方法名
[推荐] 或者对象名.类方法名
静态方法的访问修饰符的访问权限和范围 和 普通方法是一致的
-
类方法经典的使用场景
当方法中不涉及到任何和对象相关的成员,则可以将对象设计成静态方法(即当做工具来使用),提高开发效率;例如Math类,Array类
开发自己的工具类时,可以设计静态方法
-
类方法使用注意事项和细节讨论
(1) 静态方法,只能访问静态成员
(2) 非静态方法,可以访问所有的成员
(3) 在编写代码时,仍然要遵守访问权限规则
10.2 main 方法语法
public static void main(String[] args) {}
-
深入理解main方法
- main方法是 虚拟机 调用
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- java 执行的程序 参数1 参数2 参数3 …
特别提示:
- 在main()方法中,我们可以直接调用main 方法所在类的静态方法或静态属性。
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
Eclipse传递main函数参数:
在项目上右击 Run As->Run Configurations…->Arguments->在Program arguments:的文本框中输入你要传入的参数,若有几个参数则在参数间空格就行。然后点击Run按钮。
10.3 代码块
-
基本介绍
-
又称初始化块,属于类中的成员,类似于方法。将逻辑语句封装在方法体中,通过{}包围起来。
-
但和方法不同,代码块没有方法名、返回、参数,只有方法体,而且不同通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
-
-
基本语法
[修饰符] { 代码 };
- 修饰符可选。要写只能写 static;
- 代码块分为两类,使用static修饰的叫 静态代码块,反之叫 普通代码块/非静态代码块
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断)
;
可以写,也可以不写
-
优势
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
public class CodeBlock01 { 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; //3 个构造器-》重载 //老韩解读 //(1) 下面的三个构造器都有相同的语句 //(2) 这样代码看起来比较冗余 //(3) 这时我们可以把相同的语句,放入到一个代码块中,即可 //(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容 //(5) 代码块调用的顺序优先于构造器.. { System.out.println("电影屏幕打开..."); System.out.println("广告开始..."); System.out.println("电影正是开始..."); }; public Movie(String name) { super(); System.out.println("Movie(String name) 被调用..."); this.name = name; } public Movie(String name, double price) { super(); this.name = name; this.price = price; } public Movie(String name, double price, String director) { super(); this.name = name; this.price = price; this.director = director; } }
-
代码块使用注意事项和细节讨论
-
static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
-
类什么时候被加载(重要)
-
创建对象实例时(new)
-
创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
-
使用类的静态成员时(静态属性,静态方法)
-
-
普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行
- static代码块是类加载时,执行,只会执行一次
- 普通代码块是在创建对象时调用的,创建一次,调用一次
- 类加载的3中情况,要记住
public class CodeBlockDetail01 { public static void main(String[] args) { //类被加载的情况举例 //1. 创建对象实例时(new) AA aa = new AA(); //2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载 AA aa2 = new AA(); //3. 使用类的静态成员时(静态属性,静态方法) System.out.println(Cat.n1); //static 代码块,是在类加载时,执行的,而且只会执行一次. DD dd = new DD(); DD dd1 = new DD(); //普通的代码块,在创建对象实例时,会被隐式的调用。 // 被创建一次,就会调用一次。 // 如果只是使用类的静态成员时,普通代码块并不会执行 System.out.println(DD.n1);//8888, 静态模块块一定会执行 } } class DD { public static int n1 = 8888;//静态属性 //静态代码块 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 = 999;//静态属性 //静态代码块 static { System.out.println("Cat 的静态代码1 被执行...");// } } class BB { // 静态代码块 static { System.out.println("BB 的静态代码1被执行..."); } } class AA extends BB { // 静态代码块 static { System.out.println("AA 的静态代码1被执行..."); } }
-
创建一个对象时,在一个类中的调用顺序(重点,难点):
-
调用静态代码块和静态属性初始化
两者调用的优先级一样,如果有多个静态代码块和静态属性变量初始化,则按定义的顺序调用
-
调用普通代码块和普通属性初始化
两者调用的优先级一样,如果有多个普通代码块和普通属性变量初始化,则按定义的顺序调用
-
调用构造方法
静态 -> 普通 -> 构造器
public class CodeBlockDetail02 { public static void main(String[] args) { A a = new A(); // (1) A 静态代码块01 (2) getN1 被调用...(3)A 普通代码块01(4)getN2 被调用...(5)A() 构造器被调用 } } class A { { //普通代码块 System.out.println("A 普通代码块01"); } private int n2 = getN2();//普通属性的初始化 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() 构造器被调用"); } }
-
-
构造器的最前面其实隐含了
super()
和 调用普通代码块。静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于 构造器和普通代码块的
class AAA { //父类Object { System.out.println("AAA 的普通代码块"); } public AAA() { //(1)super() //(2)调用本类的普通代码块 System.out.println("AAA() 构造器被调用...."); } } // 输出: // AAA 的普通代码块 // AAA() 构造器被调用....
-
创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行);
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行);
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
- 父类的构造器;
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
- 子类的构造器;
举例:
new BB();
时,(1) 进行类的加载;
先父类后子类,同时加载类时,还要调用静态相关的属性和方法。
(2) 创建对象;
从子类的构造器开始。所以先调用super(),即调用父类的构造器。然后调用普通属性初始化和普通代码块,再调用构造器的其他语句。
-
静态代码块只能直接调用静态成员,普通代码块可以调用任意成员。
package com.codeblock; public class CodeBlockDetail04 { public static void main(String[] args) { //老师说明 //(1) 进行类的加载 //1.1 先加载父类A02 1.2 再加载B02 //(2) 创建对象 //2.1 从子类的构造器开始 new B02();//对象 //new C02(); } } 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 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 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) // TODO Auto-generated constructor stub } }
-
-
练习
答案:in static block!" total = 100 total = 100
答案:静态成员sam初始化 static块被执行 sam1成员初始化 Test默认构造函数被调用
10.4 单例设计模式
-
什么是设计模式
理解:套路,模板
-
什么是单例模式
单例(单个的实例)
-
类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
在整个软件运行过程中,保证某个类只能有一个实例。
有时候软件开发有一个核心类很耗费资源,且只需要一个,但就采用单例模式
-
-
单例模式分类
-
饿汉式
-
步骤
1)构造器私有化(防止直接new)
2)类的内部创建对象 (修饰为static是为了能在static方法中调用)
3)向外暴露一个静态的公共方法(这样就不用在外部创建对象)。 getInstance
-
-
可能造成创建了对象,但是没有使用
-
懒汉式
-
步骤
1)构造器私有化(防止直接new)
2)定义一个static静态属性对象
3)提供一个public的static方法,可以返回一个对象。 getInstance
-
只有当用户使用getInstance时,才返回对象。后面再次调用时,返回之前的对象
-
-
两者区别
总结:
- 单例模式的两种实现方式 (1)饿汉式 (2) 懒汉式
- 饿汉式的问题:在类加载的时候就创建,可能存在资源浪费问题
- 懒汉式的问题:线程安全问题
- 要独立写出单例模式
-
10.5 final 关键字
-
基本介绍
final可以修饰类、属性、方法、局部变量
-
当不希望类被继承时,可以用final修饰
-
当不希望父类的某个方法被子类覆盖或者重写,可以用final修饰
访问修饰符 final 返回数据类型 方法名(){}
-
当不希望类的某个属性的值被修改,可以用final修饰
访问修饰符 final 数据类型 属性名 = 值;
-
当不希望某个局部变量被修改,可以用final修饰
final 数据类型 属性名 = 值;
只能赋值一次
-
-
final 使用注意事项和细节讨论
-
final修饰的属性又叫常量,一般用 XX_XX_XX来命名
-
final修饰的属性在定义时,必须赋初值,并且以后不能修改。赋值可以在如下位置之一:
- 定义时
- 在构造器中
- 在代码块中
-
如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 在静态代码块中
-
final类不能继承,但是可以实例化对象
-
如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
-
一般来说,如果一个类已经是final 类了,就没有必要再将方法修饰成final 方法
-
final不能修饰构造器
-
final和static往往搭配使用,效率更高,不会导致类加载。底层编译器做了处理
class BBB { public static int num = 10000; static { System.out.println("BBB 静态代码块被执行"); } } // System.out.println(BBB.num); // 输出 // 10000
class BBB { public final static int num = 10000; static { System.out.println("BBB 静态代码块被执行"); } } // System.out.println(BBB.num); // 输出 // 10000 // BBB 静态代码块被执行
static final b=1; final int c=1;
这里c和b的区别在于,b存放在静态空间,不会在程序运行时被释放,它永远占着内存直到程序终止,而c在程序用完它而不会再用到它的时候就会被自动释放,不再占用内存。
当一个常数或字符串我们需要在程序里反复反复使用的时候,我们就可以把它定义为static final,这样内存就不用重复的申请和释放空间。
-
包装类 (Integer,Double,Float,Boolean等都是final),String也是final类,所以不能被继承。
-
10.6 抽象类
抽象类介绍
-
基本介绍
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
一般来说,抽象类会被继承,有其子类来实现抽象方法.
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() ; }
-
用abstract关键字来修饰一个类时,这个类就叫抽象类;
访问修饰符 abstract 类名 { }
-
用abstract关键字来修饰一个方法时,这个方法就是抽象方法;
访问修饰符 abstract 返回类型方法名(参数列表); //没有方法体
-
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类
抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多.
-
-
抽象类的使用细节
-
抽象类不能被实例化;
-
抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法;
-
一旦类包含了abstract方法,则这个类必须声明为abstract;
-
abstract 只能修饰类和方法,不能修饰属性和其它的。
-
抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等;
-
抽象方法不能有主体,即不能实现.如图所示
-
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
所谓实现方法,就是有方法体
-
抽象方法不能使用private、final(不希望子类重写)和 static修饰,因为这些关键字都是和重写相违背的。
-
抽象类最佳实践 - 模板设计模式
-
基本介绍
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
- 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
多个类有一个相同的方法job,但每个类的具体内容有差异,那么就将其写成抽象方法。同时多个类有一个相同的方法cal,内容一模一样,将其写到父类中
编写父类,写成抽象类,并编写一个抽象方法;
abstract public 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 //存在动态绑定机制,运行类型会先找该方法AA类 @Override public void job() { //实现Template的抽象方法job long num = 0; for (long i = 1; i <= 800000; i++) { num += i; } } // public void job2() { // //得到开始的时间 // long start = System.currentTimeMillis(); // long num = 0; // for (long i = 1; i <= 200000; i++) { // num += i; // } // //得的结束的时间 // long end = System.currentTimeMillis(); // System.out.println("AA 执行时间 " + (end - start)); // } }
public class BB extends Template{ public void job() {//这里也去,重写了Template的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 ;运行内存是AA;先找运行类型中的方法 aa.calculateTime(); //这里还是需要有良好的OOP基础,对多态 BB bb = new BB(); bb.calculateTime(); } }
10.7 接口
接口介绍
-
基本介绍
如果一个类 implements实现 接口,需要将该接口的所有抽象方法都实现
public interface AInterface { //写属性 public int n1 = 10; //写方法 // 1.在接口中,抽象方法,可以省略abstract关键字 public void hi(); // 2.在jdk8后,可以有默认实现方法,需要使用default关键字修饰 default public void ok() { System.out.println("ok ..."); } // 3.在jdk8后, 可以有静态方法 public static void cry() { System.out.println("cry ...."); } }
-
应用场景
制定规范
视频有详细讲解
-
注意事项和细节
-
接口不能被实例化
本质是抽象类
-
接口中所有的方法是 public方法,接口中抽象方法,可以不用abstract修饰图示
-
一个普通类实现接口,就必须将该接口中的所有方法都实现。可以使用alt+enter来解决
-
抽象类实现接口,可以不用实现接口的方法。
-
一个类同时可以实现多个接口。
-
**接口中的属性,只能是final的,而且是 public static final修饰符。**比如:int a=1;实际上是public static final int a=1;(必须初始化)
-
接口中属性的访问形式:接口名.属性名。
-
接口不能继承其它的类,但是可以继承多个别的接口。
接口和接口之间是继承关系,用extends关键字;接口和类之间是实现关系,用implements关键字。
-
接口的修饰符只能是public 和默认,这点和类的修饰符是一样的。
-
-
练习
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 { //正确 }
实现接口 vs 继承类
-
当子类继承了父类,就自动的拥有父类的功能
-
如果子类需要扩展功能,可以通过实现接口的方式扩展。实现接口这种机制是对java中单继承的一种补充(扩展功能)。
继承:小猴子悟空继承了猴子,自动拥有猴子的功能
接口:小猴子悟空还希望拥有鸟的飞翔,鱼的游泳(但猴子没有这些功能)
-
小结
-
接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活。 -
接口比继承更加灵活,继承是满足is - a的关系,而接口只需满足like - a的关系。
-
接口在一定程度上实现代码解耦[即:接口规范性+动态绑定机制]
-
接口的多态特性
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 {}
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("相机工作中...");
}
}
/**
* 演示多态传递现象
*/
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() {
}
}
-
练习
public class InterfaceExercise02 { public static void main(String[] args) { } } interface A { // 1min 看看 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(); } }
10.8 内部类
类的五大成员:属性、方法、构造器、代码块、内部类(难点)
- 基本介绍
注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.
public class InnerClass01 { //外部其他类
public static void main(String[] args) {
}
}
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类的内部
}
}
-
分类
- 定义在外部类局部位置上(比如方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名,重点!!!!!)
- 定义在外部类的成员位置上
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
- 定义在外部类局部位置上(比如方法内):
01 局部内部类
说明:局部内部类是定义在外部类的局部位置,比如通常在方法中,并且有类名。
- 可以直接访问外部类的所有成员,包含私有的;
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final。
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类—访问---->外部类的成员[访问方式:直接访问]。
- 外部类—访问---->局部内部类的成员。访问方式:创建对象,再访问(注意:必须在作用域内)。
- 外部其他类—不能访问----->局部内部类(因为局部内部类定位是一个局部变量)。
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。解释:外部类名.this表示外部类的一个对象(谁调用就是谁的对象)
/**
* 演示局部内部类的使用
*/
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 = 100;
private void m2() {
System.out.println("Outer02 m2()");
}//私有方法
public void m1() {//方法
//1.局部内部类是定义在外部类的局部位置,通常在方法
//3.不能添加访问修饰符,但是可以使用final 修饰
//4.作用域 : 仅仅在定义它的方法或代码块中
final class Inner02 {//局部内部类(本质仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的
private int n1 = 800;
public void f1() {
//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类n1 和 m2()
//7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
// 使用 外部类名.this.成员)去访问
// Outer02.this 本质就是外部类的对象, 即哪个对象调用了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();
}
}
- 局部内部类定义在方法中/代码块
- 作用域在方法体或者代码块中
- 本质仍然是一个类
02 匿名内部类(重要!!!)
-
基本介绍
- 本质是类
- 内部类
- 该类没有名字(系统分配的),型如外部类
$
数字,$
这个符号表示匿名内部类。 - 同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名。
有时候对于某个类只需要使用一次,后面就不再适应了。然后可以使用匿名内部类来简化开发
-
基本语法
- 基于接口的内部类
- 基于类的内部类
new 类或接口(参数列表) { 类体 };
/** * 演示匿名内部类的使用 */ public class AnnoymousInnerClass { 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/Dog 类只是使用一次,后面再不使用 //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(); // ================================================ //演示基于类的匿名内部类 //分析 //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"); 这就是一个对象了 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("小狗吃骨头..."); } }; animal.eat(); } } 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("小狗汪汪..."); // } //} class Father {//类 public Father(String name) {//构造器 System.out.println("接收到name=" + name); } public void test() {//方法 } } abstract class Animal { //抽象类 abstract void eat(); }
getClass()就是获得运行类型;
-
注意事项
-
匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法;
-
可以直接访问外部类的所有成员,包含私有的;
-
不能添加访问修饰符,因为它的地位就是一个局部变量。
-
作用域:仅仅在定义它的方法或代码块中。
-
匿名内部类—访问---->外部类成员[访问方式:直接访问]。
-
外部其他类—不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)。
-
如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
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() { 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 匿名内部类 extends Person {} new Person() { @Override public void hi() { System.out.println("匿名内部类重写了 hi()"); } @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); } }
-
-
应用场景
当做实参直接传递,简洁高效。
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("这是一副名画XX..."); } }
-
练习
- 有一个铃声接口 Bell,里面有个 ring 方法。
- 有一个手机类 Cellphone,具有闹钟功能 alarmClock,参数是 Bell 类型
- 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
public class InnerClassExercise02 { public static void main(String[] args) { CellPhone cellphone = new CellPhone(); // 1.传递的是实现了Bell接口的匿名内部类 // 2.重写了ring() 方法 // 3. Bell bell = new Bell() { // @Override // public void ring() { // System.out.println("懒猪起床了"); // } // } cellphone.alarmclock(new Bell() { @Override public void ring() { // 要加public System.out.println("懒猪起床了"); } }); } } interface Bell { // 接口 void ring(); // 方法 } class CellPhone { // 类 public void alarmclock(Bell bell) { // 形参是Bell接口 System.out.println(bell.getClass());// 打印运行类型 InnerClassExercise02$1 bell.ring(); // 会实现动态绑定,重写回到匿名内部类的ring()方法 } }
匿名内部类涉及很多知识点
(1)继承 (2)多态 (3)动态绑定 (4)内部类
03 成员内部类
成员内部类是定义在外部类的成员位置,并且没有static修饰。
- 可以直接访问外部类的所有成员,包含私有的。
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
- 作用域和外部类的其他成员一样,为整个类体,比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。
- 成员内部类—访问---->外部类成员(比如:属性) [访问方式:直接访问]。
- 外部类—访问------>成员内部类(说明) 访问方式:创建成员内部类对象,再访问。
- 外部其他类—访问---->成员内部类
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的两种方式
// 第一种方式
// outer08.new Inner08(); 相当于把 new Inner08()当做是outer08成员
// 这就是一个语法,不要特别的纠结.
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部类中,编写一个方法,可以返回 Inner08对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 { //外部类
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
//1.注意: 成员内部类,是定义在外部类的成员位置上
//2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
public class Inner08 {//成员内部类
private double sal = 99.8;
private int n1 = 66;
public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
//,可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("n1 = " + n1 + " name = " + name + " 外部类的n1=" + Outer08.this.n1);
hi();
}
}
//方法,返回一个Inner08实例
public Inner08 getInner08Instance(){
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
04 静态内部类
静态内部类是定义在外部类的成员位置,并且有static修饰。
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
- 作用域:同其他的成员,为整个类体。
- 静态内部类—访问---->外部类(比如:静态属性) [访问方式:直接访问所有静态成员]。
- 外部类—访问------>静态内部类 访问方式: 创建对象,再访问。
- 外部其他类—访问----->静态内部类。
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
// 外部其他类 使用静态内部类
// 方式1
// 因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
// 区别成员内部类
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式2
//编写一个方法,可以返回静态内部类的对象实例.
Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("============");
inner101.say();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
System.out.println("************");
inner10_.say();
}
}
class Outer10 { //外部类
private int n1 = 10;
private static String name = "张三";
private static void cry() {}
//Inner10就是静态内部类
//1. 放在外部类的成员位置
//2. 使用static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
//5. 作用域 :同其他的成员,为整个类体
static class Inner10 {
private static String name = "哈哈哈";
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();
}
}
小结
- 内部类有四种:
- 放在方法或代码块中:局部内部类、匿名内部类
- 放在外部类的成员位置:成员内部类、静态内部类
- 重点掌握 匿名内部类
- 成员内部类,静态内部类 放在外部类的成员位置,本质上就是一个成员