文章目录
实验8:包与接口
8.1 实验目的
- 了解多个目录下,多个类并存且由类同名的情况下对程序运行的影响
- 掌握Java程序中包的定义以及使用方法
- 掌握接口定义的语法格式、成员变量的定义、成员方法的定义
- 掌握接口实现的语法格式
- 掌握接口的使用和Java语言中利用接口实现多重继承
8.2 实验内容
实验指导中的代码直接插到这里来了
8.2.1 编写两个Java程序,在Tree.java中,显示“我是一棵树”,在Bamboo.java中,显示“我是一棵竹子”。(实验需在命令行中进行编译)
8.2.1.1 将Tree.java和Bamboo.java放与同一文件夹下
【前提引入】
1️⃣ 如果代码中含有中文并且是在windows的cmd命令行进行javac编译指令的执行,如果直接使用javac Tree.java
是会报错:不可映射的字符集编码。
- 这是因为我们的代码文件
Tree.java
是使用的unicode字符集的UTF-8编码,则存储方式(编码)就为UTF-8。 - 但是如果在cmd命令行执行javac编码指令,那么首先是需要读取 Tree.java 文件信息的,但是Windows操作系统默认使用 GBK 字符集,这对程序员就很不友好,导致在对 Tree.java 读取时是使用GBK的解码方式。
- 即Tree.java存储时是 UTF-8 编码,而读取时是 GBK 解码,因此造成了乱码问题。
- 但是如果 Tree.java 中不含有中文,我们使用
javac Tree.java
是没有问题的,因为** Unicode字符集和GBK字符集是完全兼容 ASCII字符集的。**
???? 解决方案:
- 在写Tree.java文件时指定字符集为GBK。但是windows11是没有该功能了似乎。
- 使用 sublime,notepad 等编译软件,可以指定编码方式为GBK。
- 修改windows默认的字符集编码为UTF-8。
- 在cmd控制台写命令时指定解码方式为UTF-8,如:
javac -encoding utf8 Tree.java
。(推荐)
2️⃣ 对下面的代码显示的结果分析:
Tree.java 和 Bamboo.java 文件中都含有 Living 类,因此我们如果这样执行:
- 编译Tree.java文件:
javac -encoding utf8 Tree.java
。这样就会在 package-interface文件夹中 生成 Tree.class 和 Living.class 两个字节码二进制文件。 - 编译Bamboo.java文件:
javac -encoding utf8 Bamboo.java
。这样就会生成在 package-interface文件夹中 Bamboo.class 和 Living.class 两个字节码二进制文件。 - Living.class命名冲突问题:由于编译 Tree.java 和 Bamboo.java 时都会生成 Living.java 文件,并且会生成在同一级目录package-interface文件夹下,这必然会冲突。那会发生声明呢?即在编译 Bamboo.java 时生成的 Living.class 会发现 package-interface 文件夹下已经有了 Tree.java编译生成的 Living.class,那么就会覆盖掉有 Tree.java编译生成的 Living.class,在 package-interface 文件下就只有由 Bamboo.java 编译生成的 Living.class 字节码二进制文件了。
【核心代码】
???? Tree.java
public class Tree
{
public static void main(String[] args){
Living tree=new Living ();
tree.say();
}
}
class Living
{
public void say(){
System.out.println("我是一棵树");
}
}
???? Bamboo.java
public class Bamboo
{
public static void main(String[] args)
{
Living bamboo =new Living ();
bamboo.say();
}
}
class Living
{
public void say()
{
System.out.println("我是一棵竹子");
}
}
【运行流程】
-
将Tree.java和Bamboo.java放与同一文件夹下。
-
编译Tree.java,运行Tree,观察提示结果。
-
编译Bammboo.java,运行Bammboo,观察提示结果。
-
运行Tree,观察提示结果
8.2.1.2 将Tree.java和Bamboo.java放与不同文件夹下
【前提引入】
1️⃣ 如果将两个Living类分别放在两个文件夹Tree和Bamboo中,这样在编译这两个 Living.java 的时候由于在不同文件下生成 Living.class 字节码二进制文件,肯定就不会造成命名冲突,也就不会造成覆盖问题。
???? 谈一谈包
-
包的三大作用
- 目的是区分相同名字的类
- 当类很多的时候,能够很好的管理类
- 控制访问范围
-
基础语法
/* 声明包:package 关键字 打包名称 声明当前类所在的包 */ package com.bamboo //声明当前类是在com包下的子包bamboo下 /* 引用包:import 关键字 打包名称 引用某个类 */ import java.util.Scanner; //引用到 java包下 的 util包 中的 Scanner类文件 import java.net.* //引用java包下 的 net包 中的 所有类文件
-
本质
实际上就是创建不同的 文件夹/目录 来保存类文件
-
注意事项
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只能有一句package。
- import指令位置放在package下面,在类定义上面,可以有多句且没有顺序要求。
【核心代码】
-
Tree文件夹下的Living.java
package Tree; //当前在Tree包中 public class Living { public void say() { System.out.println("我是一棵树"); } }
-
Bamboo文件夹下的Living.java
package Bamboo; //声明当前在 Bamboo包下 public class Living { public void say() { System.out.println("我是一个小竹子"); } }
-
package-interface文件夹下的Tree.java
import Tree.Living; //引用在Tree包下的Living类 public class Tree { public static void main(String[] args) { Living tree=new Living (); tree.say(); } }
-
package-interface文件夹下的Bambo.java
import Bamboo.Living; //找Bamboo包下的Living类 public class Bamboo { public static void main(String[] args) { Living bamboo=new Living (); bamboo.say(); } }
【运行流程】
-
将两个Living类分别放在两个文件夹Tree和Bamboo中,Tree.java和Bamboo.java放在根目录(文件夹Tree和Bamboo的上一级目录下)
-
编译Tree.java和Living.java,运行Tree,观察提示结果。
-
编译Bamboo.java和Living.java,运行Bamboo,观察提示结果。
-
再次运行Tree,查看结果。
8.2.2 编写一个Java程序,在程序中定义一个接口Bulid,定义一个类Tree实现接口,在Tree类中实现Bulid的接口。
【前提引入-接口简介】
-
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些这些方法写出来。
-
基本语法
interface 接口名{ //属性 //方法(抽象方法,默认实现方法,静态方法) }
class 类名 implements 接口名{ //自己属性 //自己方法 //必须实现的抽象接口方法 }
-
注意事项
-
接口不能被实例化,必须由类去实现它
-
接口所有的方法是 public 方法,接口中抽象方法可以不用 abstract 修饰,因为在javac编译生成 字节码二进制文件 时会认为是抽象方法加上 abstract 关键字。我们在这里可以用 javac反编译指令进行查看:
-
接口中的属性,只能是 final 的,而且必须是 public static final 修饰符,则在定义的时候必须初始化或者使用静态代码块进行初始化。
-
-
实现接口 vs 继承类
-
接口和继承解决的问题不同:
- 继承的主要价值:解决diamante复用性和可维护性的问题
- 接口的主要价值:设计,设计好各种规范(方法),让其它类去实现这些方法。
实现接口是 对 java单继承机制 的一种很好的补充。
-
接口比继承更灵活
继承是满足
is-a
关系,而接口只需要满足like-a
关系。 -
接口在一定程度上实现 代码解耦(接口规范化+动态绑定机制)
-
【核心代码】
-
Build类
public interface Build { public final static double PI = 3.14; /** * 切面积 */ public void area(); /** * 体积 */ public void volume(); /** * 用途 */ public void use(); }
-
Tree类
public class Tree implements Build { /** * 树的半径(单位:m) */ private double r; /** * 树的高度(单位:m) */ private double h; public Tree(double r, double h) { this.r = r; this.h = h; } @Override public void area() { System.out.println("切面积是:" + PI * r * r); } @Override public void volume() { System.out.println("体积是:" + PI * r * r * h); } @Override public void use() { System.out.println("我的小树用来造我们的小家"); } }
-
Test类
public class Test { public static void main(String[] args) { Tree tree = new Tree(0.5, 5); tree.area(); tree.volume(); tree.use(); } }
【运行流程】
8.2.3 定义个类Plant,修改(2)声明Tree类继承Plant类,同时实现的Bulid接口内容不变。
【前提引入】
继承不多解释,主要谈谈 super
关键字
-
基本介绍
super代表对父类(可以不是直接父类,也可以是超类)的引用,用于访问父类的属性、方法、构造器。
-
基本语法
- 可以访问父类的属性:
super.属性名
,但不能访问父类的 private属性。 - 可以访问父类的方法:
super.方法名(实参列表)
,但不能访问父类的 private方法。 - 能访问父类的构造器:super(实参列表),完成父类的初始化工作,只能放在构造器的第一句,且只能出现一句。
- 可以访问父类的属性:
-
注意事项:默认情况下构造器中都会隐式存在super(),调用父类的无参构造器。我们举个例子,看下如下代码:
public class Animal { String name; /** * 这是有参构造器, * 因此如果没有声明无参构造器,那么该类中不会存在无参构造器 */ public Animal(String name) { this.name = name; } } class Dog extends Animal{ public Dog(){ } }
这段代码会是错的,因为我们在调用Dog类的无参构造器中会默认存在一句super(),但是父类 Animal类 中并不存在无参构造器,因此发生错误,修改:
public class Animal { String name; /** * 这是有参构造器, * 因此如果没有声明无参构造器,那么该类中不会存在无参构造器 */ public Animal(String name) { this.name = name; } } class Dog extends Animal{ public Dog(String name){ //如果显示的声明了 super调用,那么默认的 super() 就不会存在在代码中了 super(name); } }
【核心代码】
-
创建Plant类
public class Plant { private String name; private int age; public Plant(String name, int age) { this.name = name; this.age = age; } public void introduce() { System.out.println("我是一颗生长了 " + age + " 年的 " + name + " 树"); } }
-
修改Tree类
public class Tree extends Plant implements Build { /** * 树的半径(单位:m) */ private double r; /** * 树的高度(单位:m) */ private double h; public Tree(double r, double h, String name, int age) { //父类构造器初始化 super(name, age); this.r = r; this.h = h; } @Override public void introduce() { //调用父类的 introduce 方法 super.introduce(); } @Override public void area() { System.out.println("切面积是:" + PI * r * r); } @Override public void volume() { System.out.println("体积是:" + PI * r * r * h); } @Override public void use() { System.out.println("我的小树用来造我们的小家"); } }
-
修改Test类
public class Test { public static void main(String[] args) { Tree tree = new Tree(0.5, 5,"逐浪",18); tree.introduce(); } }
【运行流程】