面向對象(17):内部類及其案例
一、内部類的介紹
1、内部類的概述
把類定義在其他類的内部,我們稱之為内部類
舉例:在類A中定義了一個類B,類B就是内部類
2、内部類的特點
(1)内部類可以通路外部類的成員,包括私有和靜态的外部類成員
(内部類如果是靜态的,就不能通路外部類私有的,但是可以通路靜态的成員)
案例:
class Outer{
private int num = 10;
class Inner{
public void show(){
System.out.println(num);
}
}
}
(2)外部類要想通路内部類的成員,就必須要建立内部類的對象
案例:
class Outer{
private int num = 10;
class Inner{
public void show(){
System.out.println(num);
}
}
//在外部類中建立一個方法,在方法中建立内部類對象,通過内部類對象來通路内部類成員
public void show2(){
//建立内部類對象
Inner inner = new Inner();
inner.show();
}
}
3、内部類位置
根據内部類定義的位置不同,可以分為兩種類型:
(1)成員的位置上(成員内部類)
(2)局部的位置上(局部内部類),定義在方法内的
案例:
class Outer2{
//成員内部類
class Inner2{
}
public void fun(){
//局部内部類
class Inner3{
}
}
}
4、成員内部類的修飾符
成員内部類常見的修飾符為:private、static
private:為了保證資料安全性
static:為了友善通路資料
靜态修飾的特點:
内部類如果是靜态修飾的,隻能通路外部類靜态的成員
案例:
class Outer4{
private int num = 10;
private static int num2 = 30;
static class Inner4{
public static void show2(){
//System.out.println(num);靜态的内部類不能通路外部私有的,會報錯
System.out.println(num2)//靜态的内部類可以通路外部類靜态的成員
}
}
二、成員内部類
1、如果内部類是非靜态的,在測試類中該如何通路内部類中的成員?
格式(1)外部類名.内部類名 對象名 = new 外部類名().new 内部類名();
= 外部類對象.内部類對象;
(2)通過對象名調取
2、如果内部類是靜态的,在測試類中如何通路内部類中的成員呢?
如果内部類是靜态的,内部類成員是非靜态的 ,測試類中需要建立對象來調取内部類成員
外部類名.内部類名 對象名 = new 外部類類名.内部類類名();
對象名.成員名;//調取靜态内部類的非靜态成員變量
對象名.成員名();//調取靜态内部類的非靜态成員方法
如果内部類是靜态的,内部類成員也是靜态的 ,測試類中可以通過類名直接調用内部類成員
外部類名.内部類名.成員名;//調取靜态内部類的靜态成員變量
外部類名.内部類名.成員名();//調取靜态内部類的靜态成員方法
3、案例:
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(?);
System.out.println(??);
System.out.println(???);
}
}
}
需求:在控制分别輸出:30,20,10
答:
//外部類
class Outer5 {
public int num = 10;
//内部類
class Inner5 {
//成員内部類
public int num = 20;
public void show() {
int num = 30;
System.out.println(num);//這裡的num代表方法裡的局部變量30
System.out.println(this.num);//this表示這裡的num代表目前類的成員變量
//Inner5與Outer5不是繼承關系,不能使用super關鍵字
//System.out.println(super.num);
System.out.println(Outer5.this.num);//表示目前外部類下的成員變量10
//或者 System.out.println(new Outer5().num);通過外部類對象調取外部類成員變量
}
}
}
public class InnerClassDemo5 {
public static void main(String[] args) {
//内部類是非靜态的,成員也非靜态的
Outer5.Inner5 oi5 = new Outer5().new Inner5();
oi5.show();
}
}
執行結果如下:
30
20
10
Process finished with exit code 0
三、局部内部類
1、位置:定義在方法内的類稱之為局部内部類
2、局部内部類的特點
特點(1)可以直接通路外部類的所有成員
案例:
class Outer6{
private int num = 10;
public void fun(){
int num2 = 100;
//局部内部類
class Inner6{
int num3 = 200;
//局部内部類中的方法
public void show(){
System.out.println(num);//外部類的成員變量
System.out.println(num2);//外部類方法中的成員變量
System.out.println(num3);//局部内部類中的成員變量
}
}
如果想要實作上面局部内部類的show方法,該怎麼辦?
①需要在外部類成員方法内、局部内部類外,建立局部類對象
②在測試類中中建立外部類對象,調取外部類成員方法
class Outer6{
private int num = 10;
//外部類的成員方法
public void fun(){
int num2 = 100;
//局部内部類
class Inner6{
int num3 = 200;
//局部内部類中的方法
public void show(){
System.out.println(num);//外部類的成員變量
System.out.println(num2);//外部類方法中的成員變量
System.out.println(num3);//局部内部類中的成員變量
}
}
//需要在外部類的成員方法中建立内部類對象
Inner6 inner6 = new Inner6();
//調取内部類的show方法
inner6.show();
}
}
//測試類
public class InnerClassDemo6 {
public static void main(String[] args) {
//建立外部類對象
Outer6 outer6 = new Outer6();
//調取外部類成員方法
outer6.fun();
}
}
執行結果如下:
10
100
200
Process finished with exit code 0
由上得出局部内部類的第二個特點:
特點(2)可以在外部類中的局部範圍中建立對象,通過對象調用内部類中的方法,來使用内部類的局部功能
3、局部内部類通路局部變量的注意事項
通過反編譯工具檢視後發現, 局部内部類存在的方法中定義的局部變量自動加上了final,不能進行二次指派
特點:jdk1.8之後會自動添加final關鍵字
四、匿名内部類
1、匿名内部類的含義
匿名内部類是内部類的一個簡化寫法
2、存在匿名内部類的前提
要存在一個類或者是一個接口,類可以是具體的類也可以是抽象類
3、定義格式
new 類名或者接口名(){重寫方法;}
本質上:
是一個繼承了這個類或者實作了這個接口的子類匿名對象
/建立一個接口
interface Inner{
//定義兩個抽象方法
public abstract void show1();
public abstract void show2();
}
class Outer{
//建立一個方法
public void fun(){
//匿名内部類
new Inner(){
//重寫方法
@Override
public void show1() {
System.out.println("這是show1方法");
}
@Override
public void show2() {
System.out.println("這是show2方法");
}
}.show1();//new Inner(){}整體相對于new了一個接口的對象
//可以直接調用接口中的方法
}
}
public class InnerClassDEmo11 {
public static void main(String[] args) {
//想要調用fun方法,必須先建立外部類對象
Outer outer = new Outer();
outer.fun();
}
}
執行結果如下:
這是show1方法
Process finished with exit code 0
//如果想要再調取show2方法,必須再建立一個匿名内部類,因為匿名對象隻能使用一次
想一想,如果接口中的方法很多的時候,每次調用一個方法,都需要new一下,要寫的内容都重複
了,怎麼改進?
使用多态的形式,這裡叫做接口多态,将匿名内部類用接口來接收一下
/建立一個接口
interface Inner{
//定義兩個抽象方法
public abstract void show1();
public abstract void show2();
}
class Outer{
//建立一個方法
public void fun(){
//接口多态:用接口來接收一下匿名内部類
Inter i = new Inner(){
//重寫方法
@Override
public void show1() {
System.out.println("這是show1方法");
}
@Override
public void show2() {
System.out.println("這是show2方法");
}
};
i.show1();//在fun方法中直接調取show1()
i.show2();//在fun方法中直接調取show2()
}
}
public class InnerClassDEmo11 {
public static void main(String[] args) {
//想要調用fun方法,必須先建立外部類對象
Outer outer = new Outer();
outer.fun();
}
}
執行結果如下:
這是show1方法
這是show2方法
Process finished with exit code 0
學習了匿名内部類,接口的寫法又多了一個
之前:
//接口
interface A{
b();
c();
}
//建立一個類來實作接口
class B implements A{
b(){..}//重寫方法
c(){..}//重寫方法
}
//在測試類中使用接口多态建立對象
A a = new B();
現在:
//接口
interface A{
b();
c();
}
//在外部類中的一個方法内
A a = new A(){
b(){..}//重寫方法
c(){..}//重寫方法
};
//測試類中
建立外部類對象,然後調取外部類的方法,就相當于實作了接口
五、匿名内部類在開發中的使用案例
實作接口的方法一:接口傳參
//在開發中,會有人提供一個接口給我們
interface Person{
public abstract void study();
}
//定義一個類
class PersonDemo{
//建立一個方法,接口作為形參傳入進去
//當接口作為方法的參數的時候,實際上需要的是實作該接口類的對象
public void fun(Person p){
p.study();
}
}
//定義一個類,來實作該接口
//當接口作為方法的參數的時候,需要的就是本類的對象
class Student66 implements Person{
@Override
public void study() {
System.out.println("好好學習");
}
}
//測試類
public class InnerClassDemo1 {
public static void main(String[] args) {
//想要調方法,先建立對象
PersonDemo pd = new PersonDemo();
Student66 s = new Student66();
pd.fun(s);
}
}
實作接口的方法二(進階):接口傳參、使用匿名對象
//在開發中,會有人提供一個接口給我們
interface Person{
public abstract void study();
}
//定義一個類
class PersonDemo{
//建立一個方法,接口作為形參傳入進去
//當接口作為方法的參數的時候,實際上需要的是實作該接口類的對象
public void fun(Person p){
p.study();
}
}
//定義一個類,來實作該接口
//當接口作為方法的參數的時候,需要的就是本類的對象
class Student66 implements Person{
@Override
public void study() {
System.out.println("好好學習");
}
}
//測試類
public class InnerClassDemo1 {
public static void main(String[] args) {
//想要調方法,先建立對象
PersonDemo pd = new PersonDemo();
// Student66 s = new Student66();
pd.fun(new Student66());
}
}
實作接口的方法三(更進階1):使用匿名内部類
不需要專門寫一個類來實作接口
//在開發中,會有人提供一個接口給我們
interface Person{
public abstract void study();
}
//定義一個類
class PersonDemo{
//建立一個方法,接口作為形參傳入進去
//當接口作為方法的參數的時候,實際上需要的是實作該接口類的對象
public void fun(Person p){
p.study();
}
}
//測試類
public class InnerClassDemo1 {
public static void main(String[] args) {
//想要調方法,先建立對象
PersonDemo pd = new PersonDemo();
pd.fun(new Person() { //直接調取fun方法,将匿名内部類當參數傳入進去
@Override
public void study() {
System.out.println("好好學習");
}
});
}
}
實作接口的方法三(更進階2):使用構造方法
//在開發中,會有人提供一個接口給我們
interface Person{
public abstract void study();
}
//定義一個類
class PersonDemo{
Person p;
//構造方法
PersonDemo(Person p){
this.p = p;
}
}
//測試類
public class InnerClassDemo1 {
public static void main(String[] args) {
//使用匿名内部類的形式建立對象,并将匿名内部類當參數傳入進去
PersonDemo pd = new PersonDemo(new Person() {
@Override
public void study() {
System.out.println("好好學習");
}
});
Person p = pd.p; //pd.p.var回車
p.study();
/*
或者
PersonDemo pd = new PersonDemo(new Person() {
@Override
public void study() {
System.out.println("好好學習");
}
}).p.study();
*/
}
}
六、匿名内部類面試題
/*
interface Inter { void show(); }
class Outer { //補齊代碼 }
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}
要求在控制台輸出”HelloWorld”
分析:
1、method()方法可以通過類名直接調用➡說明method()方法是靜态的
2、調用完method()方法之後又可以去 .show() 方法,而show()方法恰好是接口Inter中的方法
➡說明method()方法是有傳回值的,而且傳回值類型是Inter類型
*/
interface Inter2 {
void show();
}
class Outer8 {
//1、推出第一個結論:method方法是靜态的
//2、推出第二個結論:由于main方法中調用完method方法之後還能繼續調用方法
//是以得出method方法是有傳回值的,由于show方法恰好是接口Inter2中的方法,是以傳回值類型
//是接口Inter2類型
public static Inter2 method(){
//使用匿名内部類的形式
return new Inter2() {
@Override
public void show() {
System.out.println("HelloWorld");
}
};
}
}
public class InnerClassDemo9 {
public static void main(String[] args) {
Outer8.method().show();
}
}