文章目录
Demo09-面向对象进阶(包、权限修饰符、final、常量、枚举、抽象类、接口)
1.包
- 同一个包下的类,互相可以直接访问
- 不同包下的类,必须先导包才可以访问
- 如果这个类中使用不同包下的相同的包名.此时默认只能导入一个类的包,另一个类要使用全名访问:com.itheima.d1_package.it2.Student s2 = new com.itheima.d1_package.it2.Student();
2.权限修饰符
权限修饰符是用来控制一个成员能够被访问的范围,可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制.
权限修饰符四种作用范围由小到大:private->缺省->protected->public
修饰符 | 同一个类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | √ |
public | √ | √ | √ | √ |
package com.itheima.d2_modifier;
public class Fu {
protected void protectedMethod(){
System.out.println("---protected--");
}
}
package com.itheima.d2_modifier.itcast;
import com.itheima.d2_modifier.Fu;
public class Zi extends Fu {
public static void main(String[] args) {
/**
* 访问protected修饰的成员时,并不是在子
* 类中创建父类对象访问,而是创建子类对象访问
*/
Fu fu = new Fu();
// fu.protectedMethod();//报错
Zi zi = new Zi();
zi.protectedMethod();
}
}
3.final关键字
- final关键字是最终的意思,可以修饰类,方法,变量
- 修饰类时表明该类是最终类,不能被继承
- 修饰方法时表明该方法时最终方法,不能被重写
- 修饰变量时表明该变量第一次赋值后,不能再次被赋值(能且仅能被赋值一次)
变量有几种:
- 局部变量
- 成员变量
- 静态成员变量
- 实例成员变量
public class Test2 {
//2.修饰静态成员变量(public static final修饰的也称为常量了)
public static final String schoolName = "情话";
//3.修饰实例成员变量(创建的所有对象的某个属性一模一样且不能更改,几乎不用)
private final String name = "猪八戒";
public static void main(String[] args) {
// 1.1 修饰局部变量
final double rate = 3.14;
// rate = 3.19; // 第二次赋值了
buy(0.8);
}
// 1.2修饰局部变量
public static void buy(final double z){
// z = 0.1; // 第二次赋值了
}
}
final修饰变量的注意:
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的
4.常量
- 什么是常量:常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变
- 常量命名规范:英文单词全部大写,多个单词下划线连接起来
- 常量的执行原理:在编译阶段会进行"宏替换",把使用常量的地方全部替换成真实的字面量,这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的
5.枚举
-
枚举是java中一种特殊类型,是为了做信息的标志和信息的分类
-
定义枚举类的格式:
修饰符 enum 枚举名称{ 第一行都是罗列枚举类实例的名称,建议全部大小写 }
下面用反编译的方法了解枚举特点:
-
桌面建Season.java文件,并编写代码
public enum Season { SPRING, SUMMER, AUTUMN, WHITE; }
-
打开命令行窗口,先cd C:\Users\mxy\Desktop切换到桌面,再javac Season.java进行编译,最后javap Season.class即可得到反编译后的代码,如下所示:
Compiled from "Season.java" public final class Season extends java.lang.Enum<Season> { public static final Season SPRING = new Season(); public static final Season SUMMER = new Season(); public static final Season AUTUMN = new Season(); public static final Season WHITE = new Season(); public static Season[] values(); public static Season valueOf(java.lang.String); static {}; }
观察上述代码可以知道枚举的以下特征:
- 枚举类都是继承了枚举类型:java.lang.Enum
- 枚举类都是最终类,不可以被继承
- 反编译后看不到构造器,所以枚举类的构造器都是私有的,枚举对外不能创建对象
- 枚举类的第一行默认都是罗列枚举对象的名称的
- 枚举类相当于是多例模式(枚举了几个对象名称就会创建几个实例对象,对外不能再创建对象)
选择常量和枚举做信息标志和分类的区别:
- 常量:虽然可以实现可读性,但是入参值不受约束,代码相对不够严谨
- 枚举:代码可读性好,入参约束严谨,代码优雅,是最好的信息分类技术!建议使用(但因为使用常量更加简单,所以很少有人用枚举)
- 如果需要给该变量设具体的值,只能用常量,如public static final String name = “张三”。因为枚举只是表示一种信息对象,不能够赋值.
为什么说使用常量入参值不受约束而使用枚举入参约束严谨呢:
public class MyTest {
public static void main(String[] args) {
write_1(Contant_1.UP);
write_1(5);//只要传入的参数是int就不会报错.那我传个5,就是偏
//偏不传Contant_1.UP,Contant_1.DOWN,程序仍能运行,这就是入参值不受约束
write_2(Contant_2.DOWN);//传入的必须是Contant_2类型的对象,否则
// 程序直接报错,这就是入参约束严谨
}
public static void write_1(int num) {
switch (num) {
case Contant_1.UP:
System.out.println("玛丽往↑飞了一下~~");
break;
case Contant_1.DOWN:
System.out.println("玛丽往↓蹲了一下~~");
break;
}
}
public static void write_2(Contant_2 contant_2) {
switch (contant_2) {
case UP://这里直接UP而不再需要Contant_2.UP是因为Switch已经兼
//容枚举,这是java做的优化,如果用Contant_2.UP反而报错
System.out.println("玛丽往↑飞了一下~~");
break;
case DOWN:
System.out.println("玛丽往↓蹲了一下~~");
break;
}
}
}
class Contant_1 {
public static final int UP = 1;
public static final int DOWN = 2;
}
enum Contant_2 {
UP,DOWN;
}
6.抽象类
6.1抽象类基础知识
1.使用场景:当父类知道子类一定要完成某些行为,但是每个子类行为的实现又不同,于是该父类就把该行为定义成抽象方法的形式,具体实现交给子类去完成,此时这个类就可以声明成抽象类
2.抽象类和抽象方法的特点:
- 都是用abstract修饰的,且abstract只能修饰类,方法,不能修饰变量,代码块,构造器
- 抽象方法只有方法签名,不能写方法体
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 抽象类中可以有普通方法
- 子类继承抽象类后只有两种选择:
- 子类重写抽象类的全部抽象方法
- 子类定义成抽象类(相当于子类继承了抽象方法,有了抽象方法就必须声明为抽象类)
- 抽象类不能创建对象(反证法:如果创建了抽象类对象,调用该对象的某个抽象方法时,这个方法连方法体都没有怎么运行呢,只能报错.可能有人会说了,我定义一个抽象类,类中声明一些成员变量和成员方法,但是没有抽象方法,那就不存在调用类的抽象方法这一说辞了,那是不是就可以推翻"抽象类不能创建对象"?答案是不能!如果哪天给该抽象类加一个抽象方法,那么以前写的很多代码可能都会爆红需要重新处理,java是很严谨的,绝不会允许这种情况出现,所以抽象类一定不能创建对象)
3.final和abstract的关系:
- 互斥关系
- abstract定义的抽象类需要被子类继承(虽然不是一定,但是抽象类不能创建对象,如果不被子类继承那么这个抽象类还有什么意义呢),而final定义的类不能被继承
- 抽象方法定义通用功能让子类重写(虽然不是一定,但是抽象方法没有方法体,如果不重写那么这个抽象方法还有什么意义呢),final定义的方法子类不能重写
6.2抽象类的应用知识:模板方法模式
1.使用场景:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候
2.模板方法模式实现步骤
- 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码
- 模板方法中不能决定的功能定义成抽象方法让具体的子类去实现
public class Test {
public static void main(String[] args) {
StudentMiddle s = new StudentMiddle();
s.write();
}
}
public abstract class Student {
public final void write(){
System.out.println("\t\t\t\t《我的爸爸》");
System.out.println("你的爸爸是啥样,来说说:");
System.out.println(writeMain());//因为子类已经重写了writeMain方法,就近原则调用子类的该方法而不是调用父类的(感觉这样理解不太对)
System.out.println("我的爸爸简直太好了~~");
}
public abstract String writeMain();
}
public class StudentMiddle extends Student{
@Override
public String writeMain() {
return "我爸爸很牛,开车都不看红绿灯的~~";
}
}
7.接口
1.接口中有什么:JDK8之前接口中只能有常量(public static final修饰)和抽象方法.由于接口的作用的规范思想(就是规定你干嘛),规范默认都应该是公开的,所以修饰常量的public static final和修饰方法的public abstract都可以省略
2.接口的特点:
- 一个类可以继承一个接口也可以继承多个接口
- 类继承接口后只有两种选择:
- 实现接口的所有方法
- 该类定义为抽象类
- 接口可以多继承(创作理念:一个类可能需要实现多个接口,那么就需要在implements后面写好几个接口,这不是太麻烦了吗,所以让接口可以多继承,这样类实现多个接口时implements后面只需要写一个接口就可以了)
3.JDK8以后允许接口中有非抽象方法(为什么要这样做呢:我写好了一个项目,后来有一天业务需求我必须向接口中加一个新的规范,那这样的话就会出现实现该接口的类都会爆红需要重新处理,这样的话太麻烦了,所以JDK8后允许接口中有非抽象方法,这样的话有新的业务需求我们就可以直接在接口中写非抽象方法,与此同时实现该接口的类也会自动拥有该方法)
新增的三种非抽象方法:
- 默认方法(其实就是类中的实例方法,只是在研发java时就已对外说明接口是一个抽象概念,所以其永远不可能会有普通实例方法,后来为了不打脸,就改了个说法叫默认方法)
- 默认用public修饰,必须用default修饰
- 接口不能创建对象所以这个方法只能过继给实现类,由实现类的对象调用
- 静态方法
- 默认使用public修饰,必须使用static修饰
- 只能由接口名自己调用,其他的都会报错
- 私有方法(JDK9以后才有)
- 默认使用private修饰,这里要不能使用默认的public,而应该手动用private修饰
- 必须在接口内部才能被访问
上述的三种方法我们自己在开发中很少使用,通常是java源码涉及到
public class Test {
public static void main(String[] args) {
PingPongMan p = new PingPongMan();
p.run();
SportMan.inAddr();//用接口名调用自己的静态方法
}
}
public interface SportMan {
default void run(){
go();
System.out.println("==跑的贼溜==");
}
static void inAddr(){
System.out.println("我们在美国");
}
private void go(){
System.out.println("开始跑~~");
}
}
public class PingPongMan implements SportMan{
}
4.接口的注意事项:
- 接口不能创建对象(因为接口更加抽象,没有抽象方法也没有构造器)
- 一个类实现多个接口,多个接口中有同样的静态方法不冲突(因为该类中只能A接口调用A的静态方法,B接口调用B的静态方法,没有别的方式可以调用静态方法,不会出现语法冲突)
- 一个类实现了多个接口,多个接口中存在相同的默认方法(何为相同,方法头一模一样,其中权限修饰符一定是public,因为接口中方法要么是public要么是private,如果是后者那就是私有方法了而不再是默认方法),不冲突,这个类重写该方法即可
- 一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的
- 一个接口继承多个接口,是没有问题的,但是如果多个接口中存在规范冲突则不能多继承
我认为的规范冲突(以下几点缺一不可):
- 接口继承的多个接口中有重名方法
- 该重名方法中至少有一个方法是抽象方法或默认方法
- 这一个抽象方法或默认方法(如果重名方法中有多个抽象或默认方法,随机挑选一个即可)与其他任一方法(除了私有方法以外,也就是抽象方法,默认方法,静态方法)存在参数列表相同,那么就是规范冲突
//报错
interface a {
default void m1() {
System.out.println("m1");
}
}
interface b {
default void m1() {
System.out.println("m1");
}
}
interface c extends a,b{
}
//不报错
interface a {
static void m1() {
System.out.println("m1");
}
}
interface b {
static void m1() {
System.out.println("m1");
}
}
interface c extends a,b{
}
//报错
interface a {
void m1();
}
interface b {
default void m1() {
System.out.println("m1");
}
}
interface c extends a,b{
}
//报错
interface a {
int m1();
}
interface b {
default void m1() {
System.out.println("m1");
}
}
interface c extends a,b{
}
//不报错
interface a {
int m1(int a);
}
interface b {
default void m1() {
System.out.println("m1");
}
}
interface c extends a,b{
}
//报错
interface a {
static void m1() {
System.out.println("m1");
}
}
interface b {
void m1();
}
interface c extends a,b{
}
//不报错
interface a {
private void m1() {
System.out.println("m1");
}
}
interface b {
void m1();
}
interface c extends a,b{
}