Java基础41 面向对象(高级)

面向对象(高级)

  • 一、类变量和类方法
    • 1.1、static (类变量)
      • 1.1.1 关于static的存放位置
      • 1.1.2 类变量使用细节及注意事项
    • 1.2、类方法
      • 1.2.1、类方法使用细节及注意事项
  • 二、main方法
    • 2.1、深入理解main方法
  • 三、代码块
    • 3.1、代码块的基本介绍
    • 3.2、代码块的使用细节及注意事项
    • 3.3、代码块练习题
  • 四、单例设计模式
    • 4.1、饿汉式
    • 4.2、懒汉式
    • 4.3、对比与小结
  • 五、final关键字
    • 5.1 final 基本介绍
    • 5.2 final的使用细节及注意事项
    • 5.3 final练习题
  • 六、抽象类(abstract)
    • 6.1 什么是抽象类?
    • 6.2 抽象类的基本内容
    • 6.3 抽象类的使用细节及注意事项
    • 6.4 抽象类练习
  • 七、接口(interface)
    • 7.1 接口的基本介绍
    • 7.2 接口的使用细节及注意事项
    • 7.3 接口与继承类的区别
    • 7.4 接口的多态特性
    • 7.4 接口练习
    • 7.5 类的进一步完善
  • 八、内部类(inner class)
    • 8.1 内部类的基本介绍
    • 8.2 局部内部类
    • 8.3 匿名内部类(☆)
      • 8.3.1 基于接口的匿名内部类
      • 8.3.2 基于类的匿名内部类
      • 8.3.3 匿名内部类的使用细节及注意事项
    • 8.4 成员内部类
    • 8.5 静态内部类
    • 8.5 内部类小练


一、类变量和类方法

1.1、static (类变量)

static:类变量,也称为静态变量

● 什么是类变量

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也可看出来。

● 如何定义类变量

定义语法

访问修饰符 static 数据类型 变量名; //[推荐]
static 访问修饰符 数据类型 变量名;

● 如何访问类变量

类名.类变量名 【推荐】
或者
对象名.类变量名

假如现在定义一个类Child,在child中定义一个静态变量count:

class Child {
	public static int count = 0;
}

静态变量的最大特点就是这个变量(count)能被Child类的所有的对象实例共享

● 类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问

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 = "ccc";
}

注意:

类变量的访问,必须遵守相关的访问权限,上面的name虽说是静态变量,确实能够直接调用而不去创建对象,但是必须建立在静态变量是public权限的情况下,若换成private则只能在本类调用,其他修饰符参考修饰符权限。


1.1.1 关于static的存放位置

关于static的存放位置根据jdk的版本不同有不同的说法,比较常见的说法就是:

  1. jdk8以后存在堆空间里
  2. jdk7、8以前,存在方法区的静态域里

static的存放位置根据jdk的版本变化会有改动,但不论存放在哪都不会影响我我们的使用,而且可以肯定的一点是:静态变量被同一个类的所有对象共享,且static类变量在方法加载的时候就生成了。


1.1.2 类变量使用细节及注意事项

类变量的细节和注意事项

  1. 什么时候需要用类变量

当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。

  1. 类变量与实例变量(普通属性)区别

类变量是该类的所有对象共享的,而实例变量是每个对象独享的。

  1. 加上static称为类变量或静态变量,否则称为实例变量 / 普通变量 / 非静态变量

  2. 类变量可以通过 (类名.类变量名) 或者 (对象名.类变量名)来访问,但比较推荐通过 类名.类变量名 的方式访问。【前提是 满足访问修饰符的访问权限和范围】

  3. 实例变量不能通过 类名.类变量名 方式访问。

  4. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量。

  5. 类变量的生命周期是随类的加载开始,随着消亡而销毁。


1.2、类方法

类方法:也称静态方法

● 类方法基本介绍

类方法访问形式如下:

访问修饰符 static 数据返回类型 方法名(){  }  //【推荐】
static 访问修饰符 数据返回类型 方法名(){  }

● 类方法的调用

使用方式:类名.类方法名 或者 对象名.类方法名 【前提是 满足访问修饰符的访问权限和范围】

● 类方法经典的使用场景

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。

比如:工具类中的方法 utils
Math类、Arrays类、Collections集合类。。

在开发自己使用的工具类时,可以将方法做成静态的,方便调用

class MyTools{
	//求出两个数的和
	public static doouble calSum(double n1, double n2){
		return n1 + n2;
	}
}

不创建对象调用工具类

public static void main(String[] args){
	//类名.类变量名
	System.out.println(MyTools.calSum(10,30));
}

1.2.1、类方法使用细节及注意事项

类方法使用注意事项

  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:

类方法中无this的参数。
普通方法中隐含着this的参数。

  1. 类方法可以通过类名调用,也可以通过对象名调用。
  2. 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
public static void main(String[] args){
	//静态方法,可以直接通过类名调用
	D.hi();
	//D.say(); 是错误的,因为say是非静态方法,需要先创建对象,在调用
	new D().say();
}

class D {
	public void say(){ //非静态方法,普通方法
	}

	public static void hi(){ //静态方法
	}
}
  1. 类方法中不允许使用和对象有关的关键字,比如this和super。
  2. 类方法(静态方法)中 只能访问 静态变量或静态方法。
  3. 普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)

小结:

静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员。(必须遵守访问权限)


二、main方法

2.1、深入理解main方法

main方法的形式: public static void main(String[] args){}

  1. main方法是虚拟机调用的
  2. java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
  4. 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。
  5. java执行的程序 参数1 参数2 参数3

Java基础41 面向对象(高级)_第1张图片

特别注意:

1)在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
2)但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例化对象后,才能通过这个对象去访问类中的非静态成员。

public class Main01{
	//静态变量/属性
	private static String name = "coco";
	//非静态的变量/属性
	private int n1 = 1008;
	//静态方法
	public static void hi(){
		System.out.println("Main01的hi方法");
	}
	//非静态方法
	public void cry(){
		System.out.println("Main01的cry方法");
	}
}

public static void main(String[] args){
	
	//1.静态方法main可以访问本类的静态成员
	System.out.println("name = " + name);
	hi();
	//2.静态方法main不可以访问本类的非静态成员
	//System.out.println("n1 = " + n1); //错误
	//cry(); //错误

	//3.静态方法main 想要访问本类的非静态成员,需要先创建对象,再调用即可
	Main01 main01 = new Main01();
	System.out.println(main01.n1); 
	main01.cry();
}

三、代码块

3.1、代码块的基本介绍

● 基本介绍

代码化块又称为初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。

● 基本语法

[修饰符]{
	代码 
};

注意:

1)修饰符可选,不过要写的话也只能写static
2)代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块 / 非静态代码块。
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4)" ; " 号可以写上,也可以省略。

● 代码块的好处

1)相当于另外一种形式的构造器(对构造器的补充机制),可以初始化的操作。
2)应用场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性。

● 代码块的使用

public static void main(String[] args){
	Movie movie = new Movie("变形金刚");
}

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) 被调用...");
		this.name = name;
	}
	public Movie(String name,double price){
		this.name = name;
		this.price = price;
	}
	public Movie(String name,double price,String director){
		this.name = name;
		this.price = price;
		this.director = director;
	}
}

调用效果

Java基础41 面向对象(高级)_第2张图片

3.2、代码块的使用细节及注意事项

● 代码块的使用细节

1)static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着 " 类的加载 " 而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。

2)类什么时候被加载
① 创建对象实例时(new)
② 创建子类对象实例,父类也会被加载
③ 使用类的静态成员时(静态属性,静态方法)
案例演示:A 类 extends B类 的静态块

3)普通的代码块,在创建对象实例时,会被隐式的调用。
被创建一次,就会调用一次

如果只是使用类的静态成员时,普通代码块并不会执行。

4)创建一个对象时,在一个类 调用顺序是:

① 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序调用)

② 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)

③ 调用构造方法。

5)构造方法(构造器)的最前面其实隐含了 super() 和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的。

6)如果创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:

① 父类的静态代码块 和 静态属性(优先级一样,按定义顺序执行)
② 子类的静态代码块 和 静态属性(优先级一样,按定义顺序执行)
③ 父类的普通代码块 和 普通属性初始化(优先级一样,按定义顺序执行)
④ 父类的构造方法
⑤ 子类的普通代码块 和 普通属性初始化(优先级一样,按定义顺序执行)
⑥ 子类的构造方法

以 A extends B 为例演示

public static void main(String[] args){
	//注意
	//(1) 进行类的加载
	//1.1 先加载  父类 A02 1.2 再加载 B02
	//(2) 创建对象
	//2.1 从子类的构造器开始
	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 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)步执行
	}
}

运行效果

Java基础41 面向对象(高级)_第3张图片

7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

3.3、代码块练习题

题1:下面的代码输出什么?

class Person{
	public static int total;
	static{
		total = 100;
		System.out.println("in static block!");
	}
}

public class Test{
	public static void main(){
		System.out.println("total = " + Person.total);
		System.out.println("total = " + Person.total);
	}
}

运行结果

Java基础41 面向对象(高级)_第4张图片


四、单例设计模式

● 什么是设计模式

  1. 静态方法和属性的经典使用
  2. 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,在不同的棋局可以沿用不同的棋谱,免去了我们自己思考的摸索的过程。

● 什么是单例模式

单例(单个的实例)

  1. 所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
  2. 单例模式有两种方式:1)饿汉式 2)懒汉式

4.1、饿汉式

饿汉式的使用步骤

  1. 构造器私有化(使外界无法创建实例)
  2. 类的内部创建对象(该对象是static对象)
  3. 向外暴露一个静态的公共方法(为外界提供获取实例的接口)。
public class EagerSingleton {
        // 静态变量,类在创建之初就会执行实例化动作。
        private static EagerSingleton instance = new EagerSingleton();
        // 私有化构造函数,使外界无法创建实例
        private EagerSingleton(){}
        // 为外界提供获取实例接口
        public static EagerSingleton getInstance(){
            return instance;
        }
    }

饿汉式使用实例

创建一个类,GirlFriend ,但是一个人只能有一个女朋友,怎么实现?

public static void main(String[] args){
	//通过方法可以获取对象
	GirlFriend instance = GirlFriend.getInstance();
	System.out.println(instance);

	GirlFriend instance2 = GirlFriend.getInstance();
	System.out.println(instance2);

	System.out.println(instance == instance2); //true
}



class GirlFriend{
	private String name;
	
	private static GirlFriend gf = new GirlFriend("小红红");
	//如何保障我们只能创建一个 GirlFriend 对象
	//步骤【单例模式-饿汉式】
	//1. 将构造器私有化
	//2. 在类的内部直接创建对象(该对象是static)
	//3. 提供一个公共的static方法,返回 gf 对象
	private GirlFriend(String name){
		this.name = name;
	}

	public static GirlFriend getInstance(){
		return gf;
	}
	
	//toString方法返回值
	public String toString(){
		return "GirlFriend{" +
			   "name =" + name + '\'' +
			   '}';
	}
}

运行结果

Java基础41 面向对象(高级)_第5张图片

注意:

饿汉式的缺点是:通常创建了对象,但是有可能没有使用它,造成资源浪费


4.2、懒汉式

懒汉式使用步骤

  1. 仍然将构造器私有化
  2. 定义一个static静态属性对象
  3. 提供一个public的static方法,可以返回一个Cat对象
  4. 懒汉式,只有当用户使用getInstance时,才返回cat对象,后面再次调用时,会返回上次创建的cat对象,从而保证了单例

使用实例

public static void main(String[] args){
	Cat instance = Cat.getInstance();
	System.out.println(instance);
	
	//再次调用getInstance
	Cat instance2 = Cat.getInstance();
	System.out.println(instance2);

	System.out.println(instance == instance2);//
		
}

class Cat{
	private String name;
	private static Cat cat;
	//步骤
	//1.仍然将构造器私有化
	//2.定义一个static静态属性对象
	//3.提供一个public的static方法,可以返回一个Cat对象
	//4.懒汉式,只有当用户使用getInstance时,才返回cat对象,
	//后面再次调用时,会返回上次创建的cat对象,从而保证了单例
	private Cat(String name){
		this.name = name;
	}
	public static Cat getInstance(){
		if(cat == null){ //如果还没有创建cat对象
			cat = new Cat("小可爱");
		}
		return cat;
	}
	
	//创建toString方法
	public String toString(){
		return "Cat{" +
			   "name=" + name + '\'' +
			   '}';
	}
}

运行效果

Java基础41 面向对象(高级)_第6张图片


4.3、对比与小结

● 饿汉式 与 懒汉式

  1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式则是在使用时才创建。
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面线程再完善)
  3. 饿汉式存在浪费资源的可能。(因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。)
  4. 在JavaSE标准类中,java.lang.Runtime就是经典的单例模式。

● 小结

  1. 单例模式的两种实现方式(1)饿汉式(2)懒汉式
  2. 饿汉式的缺点:在类加载时候就创建,可能存在资源浪费问题。
  3. 懒汉式的缺点:线程安全问题。

五、final关键字

5.1 final 基本介绍

final 中文意思:最后的,最终的。

final可以修饰类、属性、方法和局部变量。
在某些情况下,程序员可能有以下需求,就会使用到final:
1)当不希望类被继承时,可以用final修饰。
2)当不希望父类的某个方法被子类覆盖 / 重写(override)时,可以用final关键字修饰。
3)当不希望类的某个属性的值被修改,可以用final修饰。
4)当不希望某个局部变量被修改,可以使用final修饰。

//如果要求A类不能被其他类继承,可以使用final修饰
final class A{}

//如果要求B不能被子类C重写,使用final修饰
class AA{
	public final void B(){}
}

//当不希望类的某个属性的值被修改,可以用final修饰
class E{
	public final double TAX_RATE = 0.08;
}

//当不希望某个局部变量被修改,可以使用final修饰
class F{
	public void hi(){
		final double NUM = 0.01;
		//这时,NUM也被称为局部常量
	}
}

5.2 final的使用细节及注意事项

final的使用细节及注意事项

  1. final修饰的属性又叫常量,一般用 xx_xx_xx来命名
  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
    ① 定义时:如 public final double TAX_RATE = 0.08;
    ② 在构造器中
    ③ 在代码块中
  3. 如果final修饰的属性是静态的,则初始化的位置只能是:
    ① 定义时
    ② 在静态代码块中(不能在构造器中赋值)
  4. final类不能继承,但是可以实例化对象
  5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。

final常量赋值的三种情况

class A{
	//final常量的赋值方式:
	//1.在定义时赋值
	public final double TAX_RATE = 0.08; 
	//2.在构造器中赋值
	public final double TAX_RATE2;
	public AA(){
		TAX_RATE2 = 1.1;
	}
	//3.在代码块中赋值
	public final double TAX_RATE3;
	{
		TAX_RATE3 = 5.98;
	}
}

如果final修饰的属性是静态的,那么初始化的位置只能是:
1.定义时
2.在静态代码块中(不能在构造器中赋值)

class BB{
	//1.在定义时赋值
	public static final double TAX_RATE = 111;
	//2.在静态代码块中
	public static final double TAX_RATE2;
	static {
		TAX_RATE2 = 3.3;
	}
}

注意:

虽然final修饰的普通常量和静态常量都能在被定义时以及静态代码块中赋值,但是要注意:

当final修饰的是静态常量时,不能在构造器中赋值,因为类加载比构造器早执行,所以有可能类加载好了,但是构造器并没有被执行,对象并没有创建,就会造成final所修饰的量是没有初始值的,所以编译不会通过。


final类虽然不能被继承,但是可以实例化对象

public static void main(String[] args){
	CC cc = new CC(); //实例化对象
	//final类虽不能比继承,但可以实例化对象
}

final class CC{}

如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承,即,仍然遵守继承的机制。

public static void main(String[] args){
	new EE().cal;
}

class DD {
	public final void cal(){
		System.out.println("cal()方法");
	}
}

class EE extends DD{}

运行展示

Java基础41 面向对象(高级)_第7张图片


  1. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
  2. final不能修饰构造方法(即构造器)
  3. final 和 static 往往搭配使用,不会导致类加载,底层编译器会做优化,效率更高。
  4. 包装类(Integer、Doble、Float、Boolean等都是final),String也是final类。

final的重复使用

final class AAA{
	//一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
	//public final void cry(){} //错误使用
}

final 和 static 往往搭配使用,不会导致类加载,底层编译器会做优化,效率更高。

不使用 static 的情况

public static void main(String[] args){
	System.out.println(BBB.num);
}

class BBB{
	public static int num = 10000;
	static {
		System.out.println("BBB 静态代码块被执行");
	}
}

运行效果

Java基础41 面向对象(高级)_第8张图片


使用 static 与 final 搭配

public static void main(String[] args){
	System.out.println(BBB.num);
}

class BBB{
	public final static int num = 10000;
	static {
		System.out.println("BBB 静态代码块被执行");
	}
}

运行效果

Java基础41 面向对象(高级)_第9张图片
可以发现与没有static的不同是,类没有加载。


包装类(Integer、Double、Float、Boolean等都是final),String也是final类。

String类JDK源码

Java基础41 面向对象(高级)_第10张图片

Double类JDK源码

Java基础41 面向对象(高级)_第11张图片


5.3 final练习题

题1:请编写一个程序,使用final的圆周率在3个位置分别赋值,能够计算圆形的面积。要求圆周率固定为3.14,

代码实现

public static void main(String[] args){
	Circle circle = new Circle(5.0);
	System.out.println("面积=" + circle.calArea());
}

private double radius;
//1.定义时赋值
//private final double PI = 3.14;
//2.构造器中赋值
private final double PI;
public Circle(double radius){
	this.radius = radius;
	PI = 3.14;
}
//3. 代码块中
//{
// 		PI = 3.14;
//}

public double calArea(){ //计算
	return PI * radius * radius;
}

运行效果

在这里插入图片描述


题2:判断下列的代码是否有误?

public class Something{
	public int addOnew(final int x){
		++x;
		return x + 1;
	}
}

答案:++x;错误,因为++x是在x的基础上增加,而被final修饰的x值是不能被改变的。


六、抽象类(abstract)

6.1 什么是抽象类?

假如我定义了一个Animal类,里面含有eat方法,但是不同的动物需求的食物是不同的,比如兔子吃草,猫吃鱼,所以所继承的eat方法就有了不确定性,这种时候,父类方法的不确定性就造成了困扰。

所以遇到这种情况,当父类的某些方法需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就称为了抽象类。

以下面代码为例:

class Animal{
	private String name;

	public Animal(String name){
		this.name = name;
	}

	public void eat(){
		System.out.println("这是一只动物,但不确定要吃什么食物。");
	}
}

这里的eat方法即使实现了也没有意义,
所以这里产生了父类方法不确定性的问题,
我们考虑将该方法设计为抽象类(abstract)方法。

public abstract void eat();

注意:
当一个类中存在抽象方法时,需要将该类声明为abstract类。

所以最终成型的抽象类为:

abstract class Animal{
	private String name;

	public Animal(String name){
		this.name = name;
	}
public abstract void eat();	

所以当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类,这就被称为抽象类。

所以做个小结:

  1. 所谓抽象方法就是没有实现的方法
  2. 所谓没有实现就是指,没有方法体
  3. 当一个类中存在抽象方法时,需要将该类声明为abstract类
  4. 一般来说,抽象类会被继承,由其子类来实现抽象方法。

6.2 抽象类的基本内容

● 抽象类系统的介绍

  1. 用abstract关键字来修饰一个类时,这个类就是抽象类。
访问修饰符 abstract 类名{
}
  1. 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
  1. 抽象类的价值更多用于设计,在设计好框架后,让子类继承并实现抽象类。

6.3 抽象类的使用细节及注意事项

  1. 抽象类不能被实例化
  2. 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法。
public class AbstractDetail01{
	public static void main(){
		//抽象类不能被实例化
		//new A(); 会报错
	}
}

abstract class A{
	//抽象类可以没有abstract方法,且可以有实现的方法。
	public void hi(){
		System.out.println("hi");
	}
}
  1. 一旦类包含了abstract方法,则这个类必须声明为abstract。
abstract class B{ //一旦类中包含了abstract方法,则必须把这个类声明为abstract类
	public abstract void hi();
}
  1. abstract只能修饰类和方法,不能修饰属性和其它的。

abstract修饰属性会报错

Java基础41 面向对象(高级)_第12张图片

  1. 抽象类可以有任意成员【因为抽象类的本质还是类】,比如:非抽象方法、构造器、静态属性等等。
abstract class A{
	public int n1 = 10; //属性
	public static String name = "coco"; //静态属性
	public void hi(){ //方法
		System.out.println("hi");
	}
	public abstract void hello(); //抽象方法
	public static void ok(){ //静态方法
		System.out.println("ok");
	}
}
  1. 抽象方法不能有主体,即不能实现
  2. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。

Java基础41 面向对象(高级)_第13张图片

正确的使用方式:

Java基础41 面向对象(高级)_第14张图片

或者直接实现E的抽象方法

Java基础41 面向对象(高级)_第15张图片

  1. 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。

6.4 抽象类练习

  1. 思考:abstract final class A{} 能通过编译吗?

  2. 思考:abstract public static void test2(); 能通过编译吗?

  3. 思考:abstract private void test3();能通过编译吗?

  4. 编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。对于Manager类来说,他即是员工,还具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,实现work(),提示"经理/普通员工 名字 工作中…"。

答案:

第一题: 不能通过,因为final关键字修饰的类不能被继承
第二题: 不能通过,因为static关键字和方法重写无关(因为被static修饰的方法是静态的,静态方法随着类的加载而加载,不需要通过子类去实现,也就不具备继承的关系。)
第三题: 被private修饰的方法不能被重写

第四题代码实现

父类Employee

abstract public 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;
	}
}

子类 Manager 继承 父类Employee

public class Manager extends Employee{
	private double bonus;
	public Mnanager(String name,int id, double salary){
		super(name,id,salary);
	}

	public double getBonus(){
		return bonus;
	}

	public void setBouns(double bonus){
		this.bonus = bonus;
	}

	public void work(){
		System.out.println("经理 " + getName() + "工作中...");
	}
}

子类CommonEmployee 继承父类Employee

public class CommonEmployee extends Employee{
	public CommonEmployee(String name,int id,double salary){
		super(name,id,salary);
	}
	public void work(){
		System.out.println("普通员工 " + getName() + "工作中...");
	}
}

测试类

public class Test01{
	public static void main(String[] args){
		//测试经理类
		new Manager("jack",999,50000);
		jack.setBonus(8000);
		jack.work();

		//测试普通员工类
		new CommonEmployee("tom",888,20000);
		tom.work();
	}
}

运行效果
Java基础41 面向对象(高级)_第16张图片


七、接口(interface)

7.1 接口的基本介绍

● 接口是什么?

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。

从现实中看,接口就像是usb,比如电脑上的usb接口,可以接上u盘、手机、相机等等,但是你不会担心哪个插槽式专门插哪个的,因为做usb插槽的厂家和各种设备的厂家都遵守了统一的规定,包括尺寸,排线等等。

● 接口的基本语法

接口的语法:

interface 接口名{
	属性
	方法(1.抽象方法 2.默认实现方法 3.静态方法)
}

接口的使用:

class 类名 implements 接口{
	自己属性;
	自己方法;
	必须实现的接口的抽象方法
}

注意:

1.在jdk7.0之前,接口里的所有方法都没有方法体。
2.jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。

● 接口的用法介绍

例1:

AInterface接口

public interface AInterface{
	//写属性
	public int n1 = 10;
	//写方法
	//在接口中,抽象方法可以省略abstract关键字
	public void hi(); 
	
	//在jdk8及以后,可以有默认实现方法,但 需要使用default关键字修饰
	default public void ok(){
		System.out.println();
	}
	//在jdk8及以后,可以有静态方法
	public static void cry(){
		System.out.println("cry....");
	}
}

Interface01类,实现上面的Ainterface接口

public class Interface01{
	public static void main(String[] args){
	}
}

//1.如果一个类 implement实现接口,则需要将该接口的所有抽象方法都实现
class A implements AInterface{
	public void hi(){
		System.out.println("hi()....");
	}
}

例2:

现在有一个项目经理,管理两个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现。

实际要求:2个程序员,编写2个类,分别完成对Mysql、Oracle数据库的连接 connect 与 close

项目经理创建接口

public interface DBInterface{//项目经理
	public void connect();//连接方法
	public void close(); //关闭连接
}

A程序员连接mysql

public class MysqlDB implements DBInterface{ //A程序员
	public void connect(){
		System.out.println("连接mysql");
	}

	public void close(){
		System.out.println("关闭mysql");
	}
}

B程序员连接Oracle

public class OracleDB implement DBInterface{
	public void connect(){
		System.out.println("连接oracle");
	}
	public void close(){
		System.out.println("关闭oracle");
	}
}

主方法

public class Interface03{
	public static void main(String[] args){
		MsqlDB mysqlDB = new MysqlDB();
		t(mysqlDB);
		OracleDB oracleDB = new OracleDB();
		t(oracleDB);
	}

	public static void t(DBInterface db){
		db.connect();
		db.close();
	}
}

运行效果

Java基础41 面向对象(高级)_第17张图片


7.2 接口的使用细节及注意事项

接口的使用细节及注意事项

  1. 接口不能被实例化
  2. 接口中所有的方法是public方法,接口中抽象方法,可以不用abstract修饰。
void aaa();
实际上是 abstract void aaa();
  1. 一个普通类实现接口,就必须将该接口的所有方法都实现。
  2. 抽象类实现接口,可以不用实现接口的方法。
  3. 一个类同时可以实现多个接口【单继承,多实现
  4. 接口中的属性,只能是final的,而且是 public static final 修饰符。(比如:int a = 1; 实际上是 public static final int a = 1; 【必须初始化】)
  5. 接口中属性的访问形式:接口名.属性名
public class InterfaceDetail01{
	public static void main(String[] args){
		//访问接口中的属性:接口名.属性名
		System.out.println(IB.n1);
	}
}

interface IB{
	int n1 = 10;//等价 public static final int n1 = 10;
	void hi();
}
  1. 一个接口不能继承其他的类,但是可以继承多个别的接口。
interface A extends B,C{}
  1. 接口和接口是继承关系,接口和类是实现关系。
    10.接口的修饰符,只能是public和默认,这点和类的修饰符是一样的。

● 小练

下列代码语法是否正确,如果正确,该输出什么?

interface A{
	int a = 23;
}
class B implements A{
}

main函数中:
B b = new B();
System.out.println(b.a);
System.out.println(A.a); 
System.out.println(B.a); 

答案:

语法都对,结果输出3个23。

Java基础41 面向对象(高级)_第18张图片


7.3 接口与继承类的区别

假如现在有一个人,他会吃饭、睡觉,有一天他有了儿子,他的儿子先天就会吃饭、睡觉,而当他儿子长大了,会学习很多的技能,比如游泳、跑步等等,而这里的吃饭睡觉就是先天的本能,这就是继承;这里的游泳跑步是后天学习的技能,这就是接口

以代码展示:

public class TestAbstract{
	public static void main(String[] args){
		Son son = new Son("小宝");
		son.Instinct();
		son.run();
		son.swimming();
	}
}

class People{
	private String name;
	public Son(String name){
		this.name = name;
	}
	public void Instinct(){
		System.out.println(name + "人会吃饭、睡觉");
	}
}

interface Swimming(){
	public void swimming();
}

interface Run(){
	public void run();
}

class Son extends People implements Swimming,Run{
	public Son(String name){
		super(name);
	}
	
	public void Swimming(){
		System.out.println(this.getName() + "努力学习 会 游泳了。");
	}
	
	public void Run(){
		System.out.println(this.getName() + "努力学习 会 跑步了。");
	}
}

● 接口与继承区别的总结

  1. 接口和继承解决的问题不同

继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活。

  1. 接口比继承更加灵活

接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。

  1. 接口在一定程度上实现代码解耦【即:接口规范性 + 动态绑定】

7.4 接口的多态特性

1. 多态参数

在开头的usb接口介绍中,创建出一个Usb usb,既可以接收手机对象,又可以接收相机对象,就提现了接口的多态(接口引用可以指向实现了接口的类的对象

2. 多态数组

演示一个案例:给Usb数组中,存放Phone 和 相机对象,Phone类还有一个特有的方法call(),请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call。

代码实现

public class InterfaceTest01{
	public static void main(String[] args){
		//多态数组 - > 接口类型数组
		Usb[] usbs = new Usb[2];
		usbs[0] = new Phone_();
		usbs[1] = new Camera_();

		for(int i = 0; i< usbs.length;i++){
			usbs[i].work();//动态绑定
			//类型的向下转型
			if(usbs[i] instanceof Phone_){ //判断他的运行类型
				((Phone_)ubss[i]).call();
			}
		}
	}
}

interface Usb{
	void work();
}
class Phone_ implements Usb{
	public void call(){
		System.out.println("手机可以打电话...");
	}

	public void work(){
		System.out.println("手机工作中...");
	}
}
class Camera_ implements Usb{
	public void work(){
		System.out.println("相机工作中...");
	}
}

运行效果
Java基础41 面向对象(高级)_第19张图片

3. 接口存在多态传递现象

有两个接口一个类,一个接口为IH、一个接口为IG,类名为Teacher,如果Teacher继承了IG,那么我就可以创建对象,实现IG接口,那如果我想用创建另一个对象,去实现IH接口呢?

代码实现

public class InterfaceTest02{
	public static void main(String[] args){
		//接口类型的变量可以指向,实现了该接口的类的对象实例
		IG ig = new Teacher();
		//如果IG 继承了 IH接口,而Teacher 类 实现了IG接口
		//那么,实际上就相当于Teacher 类实现了IG接口。
		//这就是所谓的接口多态传递现象。
		IH ih = new Teacher();
	}
}

interface IH{}
interface IG extends IH{}
class Teacher implements IG{
	public void hi(){
	}
}

从上面的代码可以看到:

如果IG 继承了 IH接口,而Teacher 类 实现了IG接口,那么实际上就相当于Teacher 类实现了IG接口,这就是所谓的接口多态传递现象。


7.4 接口练习

下面代码是否有误?有错就修改,改好后输出什么结果?

interface A{
	int x = 0;
}

class B{
	int x = 1;
}

class C extends B implements A{
	public void pX(){
		System.out.println(x);
	}
	public static void main(String[] args){
		new C().pX();
	}
}

答案:

错误点在:

class C extends B implements A{
	public void pX(){
		System.out.println(x);
	}

因为上面代码中有两个x,想要输出的x指代不明,修改为:

  1. 访问接口的x就使用A.x
  2. 访问父类的x就使用super.x

现在修改为正确的代码为:

interface A{
	int x = 0;
}

class B{
	int x = 1;
}

class C extends B implements A{
	public void pX(){
		System.out.println(A.x + " " + super.x);
	}
	public static void main(String[] args){
		new C().pX();
	}
}

运行结果
Java基础41 面向对象(高级)_第20张图片


7.5 类的进一步完善

类从一开始的最简方式:

 class 类名{成员变量; }

再到加上成员方法的模式:

class 类名{
	成员变量;
	成员方法;
}

然后一步步的添加学过的内容:构造方法

class 类名{
	成员变量;
	构造方法;
	成员方法;
}

继续加上所学的包名:

package 包名;
class 类名{
	成员变量;
	构造方法;
	成员方法;
}

再加上继承:

package 包名;
class 类名 extends 父类{
	成员变量;
	构造方法;
	成员方法;
}

然后加上前两章的代码块以及本章所学的接口,形成了更为完善的类结构:

package 包名;
class 类名 extends 父类{
	成员变量; //属性
	构造方法; //构造器
	成员方法; //方法
	代码块
}

类的五大成员为:

  1. 属性
  2. 方法
  3. 构造器
  4. 代码块
  5. 内部类

前面的四个都有了系统的学习,所以我们即将进入类的最后一个结构:内部类,也是类的重点难点。


八、内部类(inner class)

8.1 内部类的基本介绍

● 什么是内部类?

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类就称为内部类(inner class)。嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员,内部类最大的特点就是可以直接访问私有属性,并且可以提现类与类之间的包含关系。

● 内部类基本语法

class Outer{ //外部类
	class Inner{ //内部类
	}	
}
class Other{ //外部其他类
}

● 内部类的分类

定义在外部类局部位置上(比如方法内):

  1. 局部内部类(有类名)
  2. 匿名内部类(没有类名,重点

定义在外部类的成员位置上:

  1. 成员内部类(没用static修饰)
  2. 静态内部类(使用static修饰)

8.2 局部内部类

局部内部类:是定义在外部类的局部位置,比如方法中,并且有类名。

  1. 可以直接访问外部类的所有成员,包含私有的。
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。(局部变量不能使用修饰符,但是可以使用final修饰,因为局部变量也可以使用final)。
  3. 作用域:仅仅在定义它的方法或代码块中。
  4. 局部内部类 — 访问 —>外部类的成员【访问方式:直接访问】
  5. 外部类 — 访问 —> 局部内部类的成员
    访问方式:创建对象,再访问(注意:必须在作用域内)
    6.外部其他类 — 不能访问 —>局部内部类(因为局部内部类地位相当于一个局部变量)
  6. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println("外部类的n2=" + 外部类名.this.n2);

小结:

  1. 局部内部类定义在方法中/代码块
  2. 作用域在方法体或者代码块中
  3. 本质仍然是一个类

● 局部内部类的使用演示

class Outer02{ //外部类
	private int n1 = 100;
	private void m2(){} //私有方法
	public void m1(){ //方法
		//1.局部内部类是定义在外部类的局部位置,通常在方法
		//3.不能添加访问修饰符,但是可以使用final修饰
		//4.作用域:仅仅在定义它的方法或代码中
		final class Inner02{ //局部内部类(本质上仍然是一个类)
			//2.可以直接访问外部类的所有成员,包含私有的
			private int n1 = 800;
			public void f1(){
				//5.局部内部类可以直接访问外部类的成员
				//如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
				//则可以使用(外部类名.this.成员)访问
				//Outer02.this 本质就是外部类的对象,即哪个对象调用了m1,Outer02.this就是哪个对象
				System.out.println("n1=" + n1 + "外部类的n1 =" + Outer02.this.n1);
				m2(); 
			}
		}
		//6.外部类在方法中,可以创建Inner02对象,然后调用方法即可
		Inner02 inner = new Inner02();
		inner02.fi();
	}
}

8.3 匿名内部类(☆)

● 匿名内部类(重点)

匿名内部类:是定义在外部类的局部位置,比如方法中,并且没有类名

● 匿名内部类的语法

new 类或接口(参数列表){
	类体
};

8.3.1 基于接口的匿名内部类

现在我要实现一个需求:想使用IA接口,并创建对象。

使用传统方式:写一个类,实现该接口,并创建对象

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.使用传统方式,写一个类,实现该接口,并创建对象
		//IA tiger = new Tiger(); 传统方式
		//tiger.cry();
		//3.现在的需求是 Tiger类只使用一次,且后面不再使用
		//4.现在就可以使用匿名内部类来简化开发
	}
}

interface IA{
	public void cry();
}

class Tiger implements IA{
	public void cry(){
		System.out.println("老虎叫唤...");
	}
}

class Dog implements IA{
	public void cry(){
		System.out.println("小狗汪汪叫...");
	}
}

class Father{ //类
	public Father(String name){ //构造器
	}
	public void test(){ //方法
	}
}

现在有了新的需求:要求Tiger类只使用一次,且后面不再使用。

这个时候就可以使用匿名内部类来实现

public class AnonymousInnerClass{
	public static void main(String[] args){
		Outer04 outer04 = new Outer04;
		outer04.method();
	}
}

class Outer04{ //外部类
	private int n1 = 10; 
	public void method(){ //方法
		//基于接口的匿名内部类
		//3.现在的需求是 Tiger类只使用一次,且后面不再使用
		//4.现在就可以使用匿名内部类来简化开发
		//5.tiger的编译类型 = IA
		//6.tiger的运行类型 匿名内部类
		/*
			底层的匿名内部类实际上为:
			class XXXX implements IA{ 
				XXXX的名字是底层分配: 外部类的名字$数字,
				所以这里的名字就为: Outer04$1
				
				public void cry(){
					System.out.println("老虎叫唤...");
				}
			}
		*/
		//7.jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了
		//Outer04$1实例,并且把地址返回给tiger
		//8.匿名内部类使用一次,就不能再使用
			IA tiger = new IA(){
			public void cry(){
				System.out.println("老虎叫唤...");
			}
		};
		
	}
}

interface IA{
	public void cry();
}

class Father{ //类
	public Father(String name){ //构造器
	}
	public void test(){ //方法
	}
}

8.3.2 基于类的匿名内部类

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(){
		//基于接口的匿名内部类
		public void cry(){
			System.out.println("老虎叫唤...");
		}
	};
		//基于类的匿名内部类
		//1.father编译类型 Father
		//2.father运行类型 Outer04$2
		//3.底层会创建匿名内部类
		/*
			class Outer04$2 extends Father{
				public void test(){
					System.out.println("匿名内部类重写了test方法");
				}
			}
		*/
		//4.同时也直接返回了 匿名内部类 Outer04$2
		//5.注意("jack")参数列表会传递给 构造器

		Father father = new Father("jack"){
			public void test(){
				System.out.println("匿名内部类重写了test方法");
			}
		};

		System.out.println("father对象的运行类型=" + father.getClass()); //Outer04$2
		father.test();
		//基于抽象类的匿名内部类
		new Animal = new Animal(){
			void eat(){
				System.out.println("小狗吃骨头...");
			}
		};
		animal.eat();
	}
}

interface IA{
	public void cry();
}

class Father{ //类
	public Father(String name){ //构造器
	}
	public void test(){ //方法
	}
}

abstract class Animal{ //抽象类
	abstract void eat();
}

8.3.3 匿名内部类的使用细节及注意事项

1. 匿名内部类的语法比较奇特,因为匿名内部类即是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对面前代码分析可以看出这个特点,因此可以调用匿名内部类方法。

new Person(){
	public void hi(){
		System.out.println("匿名内部类重写了hi方法。");
	}
	public void ok(String str){
		super.ok(str);
	}
}.ok("jack"); //}.hi(); hi也可以调用

class Person{
	public void hi(){
		System.out.println("Person hi()");
	}
	public void ok(String str){
		System.out.println("Person ok()" + str);
	}
}

运行效果

Java基础41 面向对象(高级)_第21张图片

2. 可以直接访问外部类的所有成员,包括私有的。

class Outer{
	private int n1 = 99;
	public void f1(){
		//创建一个基于类的匿名内部类
		Person p = new Person(){
			public void hi(){
				//可以直接访问外部类的所有成员,包括私有
				System.out.println("匿名内部类重新了hi方法" + n1);	
			}
		};
		p.hi();//动态绑定,运行类型是 Oter$1
	}
}

运行效果

Java基础41 面向对象(高级)_第22张图片

3. 不能添加访问修饰符,因为它的地位就是一个局部变量。

4. 作用域:仅仅在定义它的方法或代码块中。

5. 匿名内部类 – 访问 --> 外部类成员 【访问方式:直接访问】
6. 外部其他类 – 不能访问 --> 匿名内部类(因为匿名内部类相当于一个局部变量)
7.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问


8.4 成员内部类

成员内部类:定义在外部类的成员位置,并且没有static修饰

特点:成员内部类,是定义在外部类的成员位置上。

● 成员内部类的使用

1. 可以直接访问外部类的所有成员,包含私有的。

public class MemberInnerClass{
	public static void main(String[] args){
		Outer outer = new Outer();
		outer.t1();
	}
}

class Outer{ //外部类
	private int n1 = 10;
	public String name = "张三";

	class Inner{ //成员内部类
		public void say(){
			//可以直接访问外部类的所有成员,包含私有的。
			//n1就是外部类的私有属性,name是公有属性
			System.out.println("n1 = " + n1 + "name = " + name);	
		}
	}
	
	//写一个方法使用成员内部类
	public void t1(){
		//使用成员内部类
		Inner inner = new Inner();
		inner.say();
	}
}

运行效果

在这里插入图片描述


2. 可以添加任意访问修饰符(public、protected、默认、private),因为它相当于一个成员

受保护的内部类

class Outer{ //外部类
	private int n1 = 10;
	public String name = "张三";

	protected class Inner{ //成员内部类
		public void say(){
			//可以直接访问外部类的所有成员,包含私有的。
			//n1就是外部类的私有属性,name是公有属性
			System.out.println("n1 = " + n1 + "name = " + name);	
		}
	}
}

3. 作用域:和外部类的其他成员一样,为整个类体,比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。

4. 成员内部类 — 访问 —> 外部类成员(比如:属性)【访问方式:直接访问】

5. 外部类 — 访问 —>成员内部类 【访问方式:创建对象,再访问】
6. 外部其他类 — 访问 —>成员内部类 【有两种访问方式】

第一种:
//相当于把new Inner() 当做是outer成员
Outer.Inner inner = outer.new Inner();
inner.say();

第二种:
//在外部类中,编写一个方法,可以返回Inner对象
//该方法,返回一个Inner实例
Outer.Inner innerInstance = outer.getInnerInstance();
inner08Instance.say();

public Inner getInnerInstance(){
	return new Inner();
}

7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问

public class Inner{ //成员内部类
	private int n1 = 66;
	public void say(){
		//可以直接访问外部类的所有成员,包含私有的
		//如果成员内部类的成员和外部类的成员重名,会遵守就近原则。
		//可以通过 外部类名.this.属性 来访问外部类的成员
		System.out.println("外部类的n1=" + outer.this.n1);
	}	
}

运行效果

在这里插入图片描述


8.5 静态内部类

静态内部类:静态内部类是定义在外部类的成员位置,并且有static修饰

● 静态内部类的结构

class Outer{ //外部类
	private int n1 = 10;
	private int n2 = 10;
	//Inner10就是静态内部类
	//1.放在外部类的成员位置
	//2.使用static修饰
	static class Inner10{
	}
}

● 静态内部类的使用

1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

class Outer{ //外部类
	private int n1 = 10;
	private static String name = "张三";
	//Inner10就是静态内部类
	//3.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
	static class Inner10{
		public void sau(){
			System.out.println(name);
		}
	}
}

2. 可以添加任意访问修饰符(public、protected、默认、private),因为它相当于一个成员

	//Inner10就是静态内部类
	//4.可以添加任意访问修饰符(public、protected、默认、private),因为它相当于一个成员
	private static class Inner10{ //最前面的private可以换成任意的访问修饰符
		public void sau(){
			System.out.println(name);
		}
	}

3. 作用域:同其他的成员,为整个类体
4. 静态内部类 — 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
5. 外部类 — 访问 —> 静态内部类 访问方式:创建对象,再访问
6. 外部其他类 — 访问 —> 静态内部类
7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问

class Outer{
	private int n1 = 10;
	private static String name = "张三";
	private static void cry(){}
	static class Inner{
		private static String name = "李四";
		public void say(){
		//如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
			System.out.println(name); //输出的结果为李四	
		//如果想要访问外部类的name,就使用外部名.成员
			System.out.println("外部类name = " + Outer.name);
		}
	}
}

● 内部类小结:

1. 内部类有四种:局部内部类,匿名内部类,成员内部类,静态内部类
2. 成员内部类,静态内部类,是放在外部类的成员位置,本质就是一个成员。
3. 重点掌握的匿名内部类语法为:

new/接口(参数列表){
	//.....
};

8.5 内部类小练

分析下面代码输出的结果为多少?

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();
		Inner r = t.new Inner();
		System.out.println(r.a);
	}
}

答案为5,5

		Inner r = t.new Inner();//5
		System.out.println(r.a);//5

你可能感兴趣的:(Java基础,Java进阶,java,jvm,算法)