Java学习07

第七章 抽象、接口、内部类、枚举

一、abstract

抽象方法:
将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体实现,就可以将该方法定义为抽象方法。

抽象类:
如果一个类中存在抽象方法,那么该类必须声明为抽象类

特点:抽象方法和抽象类必须使用abstract关键字修饰实现

抽象类和抽象方法的关系

  • 使用abstract修饰的类就是抽象类
  • 抽象类可以包含,也可以不包含抽象方法
  • 包含抽象方法的类,一定要声明为抽象类

抽象类和普通类的区别

  • 抽象类必须使用abstract修饰符
  • 抽象类相对普通类,多了包含抽象方法的能力
  • 抽象相对普通类,失去了实例创建对象的能力(编程层面的语法限制)

抽象类和普通类的相同点

  • 符合继承关系特点,能够使用多态机制
  • 子类可以重写从抽象类继承的方法
  • 实例化子类对象需要借助父类构造器实现父类部分的初始化

注意点:
如果某个类继承了抽象类,必须把父类中的所有抽象方法给出实现。
用一个非抽象的方法重写这个抽象方法。
如果哪怕一个抽象方法没有被重写,那么必须将子类也定义为抽象类。
抽象类存在的意义:专门给别人继承用的

  1. 如果父类中的某些方法,不便于给出具体的实现,或者无法提供对所有子类有价值的默认是实现,就可以把它定义为一个抽象类。抽象方法:只有结构,没有实现(无方法体)
  2. 如果一个方法被定义为了抽象方法,那么它所在的类必须定义为抽象类
  3. 有抽象方法存在的类必然是抽象类,但是抽象类不一定有抽象方法
  4. 抽象类不能被实例化,抽象类存在的意义就是给别人继承用的(定义标准)
  5. 如果某个类继承了一个带有抽象方法的抽象类,那么就将所有的方法给出实现(重写),要么就必须将子类继续定义为抽象。

如果某个类,没有抽象方法,定义抽象的目的是什么?
因为抽象类不能被实例化,所以有时候定义抽象类就是借助这一特征。例如某些类,因为由于特殊原因,不允许被实例化,可以借助抽象类实现这一点。
某些Java类在设计的时候,目前虽然没有抽象方法可以定义,但是根据分析和设计,认为将来在某些版本迭代中可能会出现该类中定义抽象方法的需求,为了避免将来修改大面积代码所带来的麻烦,我们就可以提前将该类设置为抽象。

构造器是new对象用的,既然抽象类不能new对象,构造器存在的意义是什么?
从实用角度出发,抽象类本身不能被实例化,但是可以通过提供构造器给子类使用调用,通常用于快速地声明在父类中的属性进行初始化赋值
从内存结构/底层原理角度出发,Java中任何一个类都不可能没有构造方法,,,子类创建对象的时候必然先调用父类的构造器,,,每一个子类对象在内存中都是由一个直接父类对象+自己子类的特征组合而成的。
以上机制,在抽象类中依然成立。如果对某个抽象类的子类进行实例化,在内存中实际上也是先创建了一个抽象类的实例,然后再和子类的特征结合到一起,变成了一个完整的子类对象。

注意:抽象类不能实例化只是编程层面的语法约束,在运行过程中,内存实际上是会存在抽象类的实例。(作为子类对象的一部分构成)

二、interface

引用数据类型:类、数组、接口

接口需要使用interface关键字来定义,接口最终会被编译成.class文件,但是接口并不是类,而是另外一种引用数据类型。

接口中的数据成员默认 public static final 修饰,是常量,名称一般全大写
接口中的方法,默认public abstract 修饰,是抽象方法

1.接口实现

Java中类和类之间的关系是继承,且只能是单继承。
类和接口是实现关系,通过implements关键字表示,可以是单实现,也可以是多实现。
子类还可以在继承一个父类的同时实现多个接口。

注意:

  • 接口属于引用数据类型的一种,他不是类,没有构造方法
  • 接口的实现类(子类),可以是正常类(重写所有抽象方法),也可以是抽象类(包含抽象方法)
  • 接口不能创建对象,一般用接口引用指向实现类对象

在类和接口的实现关系中,可以是用多态,因为类和接口的实现关系,可以理解为继承的一种形式。
一个类可以实现多个接口,但是需要把多个接口的抽象方法全部重写。

2.接口继承

Java中,类和类之间是单继承关系,接口和接口之间是多继承
[修饰符] interface 子接口 extends 父接口1,父接口2… {
//新增成员或抽象方法
}

接口多态应用时,编译看左边,运行看右边
即接口引用只能调用接口中包含的方法,成功调用的是重写以后得方法

3.类与接口的关系

  • 类与类的关系
    继承关系,只能单继承,但是可以多层继承
  • 类与接口的关系
    实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
  • 接口与接口的关系
    继承关系,可以单继承,也可以多继承

接口和抽象类有什么区别?如何选择?
语法结果区别

  1. 定义方式:抽象类通过使用 abstract 关键字来定义,而接口使用 interface 关键字来定义
  2. 实现方式:一个类可以继承(extends)一个抽象类,而一个类可以实现(implements)多个接口
  3. 构造函数:抽象类可以有构造函数,而接口不能有构造函数
  4. 方法实现:抽象类可以包含具体的方法实现,而接口只能包含抽象方法,即没有方法体的方法声明
  5. 多继承:Java不支持多继承,一个类只能继承一个抽象类,但可以实现多个接口
  6. 数据成员:抽象类可以包含普通数据成员和static数据成员,而接口只能包含 static final 修饰的数据成员

设计理念的区别

  • 不同的实现类之间体现like a的关系,接口更加强调行为规范的定义,适用于多个类具有相同行为规范的情况。
  • 子类和抽象父类体现的是is a的关系,抽象类归根到底还是类,它比较特殊,不能被实例化,只能被继承。抽象类用于定义一种通用的模板或者规范,其中可包含了一些具体数据成员、方法实现和抽象方法声明。

结论:

  • 如果仅仅是要额外扩展已存在类的功能,则选择定义接口,让类去实现接口
  • 如果需要创建一组相关的类,且这些类之间有一些共同的行为和属性,那么可以定义一个类作为这些类的父类。如果不想实例化父类对象,则可以把这个父类设置为抽象类。

三、内部类

1.静态内部类

静态内部类定义在外部类的成员位置(全局性质)
使用static修饰符修饰,访问修饰符可以任意,其他使用过的修饰符,在内部类上都可以使用。

完全限定性类名:包名.外部类名.内部类名
静态内部类对应的class文件:外部类$内部类.class

内部类和外部类之间可以比较方便的访问私有成员
静态内部类成员方法内,只能访问外部类static成员或方法

注意:静态内部类虽然作为外部类的一个静态成员,访问规则、作用范围与其他静态成员非常类似,但是静态内部类采用了懒加载设计。
即:什么时候用,什么时候加载

静态内部类中可以定义static成员和方法
静态内部类成员方法内,只能访问外部类static成员或方法
外部类方法可以访问静态内部类所有成员以及方法

对象定义格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner inner = new Outer.Inner();
静态方法的访问格式:外部类名.内部类名.方法名
Outer.Inner.outerMethod();
如果访问静态内部类中的非静态方法,那么需要通过静态内部类的对象去访问

2.成员内部类

在类中,除了可以定一成员方法、成员变量、还可以定义成员内部类。
成员内部类相当于类中定义的实例变量成员

  1. 定义在类中方法外部,不能使用是static修饰符
  2. 成员内部类中不能定义静态成员

在每一个内部类的内部,都包含一个指向所属外部类对象的指针
访问方式:外部类名.this.成员名
Outer.this.name

内部类的字节码文件类名 外部类名$内部类名.class

内部类对象实例化格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象;
例:Outer.Inner oi = new Outer().new Inner();
Outer o = new Outer();
Outer.Inner i = o.new Inner();

成员内部类中可以直接访问外部类中的所有方法和成员(含有private)
在外部类中可以直接访问内部类所有成员和方法(含有private),但必须借助内部类对象
成员内部类内部不能定义static成员或方法

成员内部类中为什么不允许出现静态成员?
1.内存结构和原理
如果成员内部类中允许定义静态成员,那么这些静态成员就应该在内部类被加载的时候加载
内部类应该在第一次被访问的时候进行加载,也就说明已经出现了外部类的对象
先出现对象,在去加载静态成员,不符合Java的规定
2.从实际应用的角度
如果成员内部类中允许定义静态成员,按照静态的使用规则,
应该就可以通过类名进行访问,例如Inner.number
但是,Inner类本身都是属于每个Outer对象的
这时候就无法确定Outer对象一定存在,也许还没有被创建(甚至Outer类都没有被加载)
就算Outer对象存在,也无法确定是哪个Outer对象

3.局部内部类

在外部类的方法中定义内部类,称为局部内部类,它的作用范围只是在当前方法中。

1.局部内部类不能使用访问修饰符、静态修饰符
2.与局部变量一致,必须先声明,后使用
3.与局部变量一致,作用范围就是声明这个类的代码所在的这一级
4.生命周期与局部变量一致。所在的大括号的开始到运行结束
5.局部内部类中不允许出现任何静态成员
6.局部内部类中如果访问外部类中的变量。。
6(1).如果访问的是所在方法所在类中的成员,可以直接访问(区分静态与非静态)
局部内部类所在的方法是一个静态方法,只能访问外部的静态成员
局部内部类所在的方法是一个实例方法,可以访问全部成员
6(2).如果访问的是局部内部类所在方法当中声明的局部变量
要求这个变量必须是final修饰(jdk1.8之前) jdk1.8之后做了修改,编译器会自动添加final修饰符

4.匿名内部类

一种没有名字的内部类。(本质上是一个特殊的局部内部类)

匿名内部类可以用于快速定义所有类的子类对象,或者所有接口的类实现对象
匿名内部类中可以定义的成员:实例变量、实例方法、构造代码块
不允许定义静态成员,构造函数

匿名内部类注意事项:

  • 匿名内部类必须依托于一个接口或一个父类(可以是抽象类,也可以是普通类)
  • 匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了
  • 匿名内部类中无法定义构造器

注意:虽然可以定义实例变量和实例方法,但是匿名内部类通常需要使用多态语法,用父类/接口类型表示对象引用,所以,这些额外扩展的实例变量和实例方法在外部无法通过父类/接口类型的引用访问,只能在类的内部自己访问。
例如:

interface ISleep {
    void sleep();
}
public class Test034_Interface {
    public static void main(String[] args) {
        //1.正常方式(传统方式)
        ISleep s1 = new SleepImpl();
        s1.sleep();
        SleepImpl s2 = new SleepImpl();
        s2.sleep();
        //2.匿名内部类
        // new 接口名() {};
        //这里的{}对应的是实现类中类{}的范围
        ISleep s3 = new ISleep() {
            @Override
            public void sleep() {
                System.out.println("坐着睡");
            }
        };
        s3.sleep();
        //3.简化写法
        new ISleep() {
            @Override
            public void sleep() {
                System.out.println("坐着睡");
            }
        }.sleep();
        //4.使用lambda表达式
        ISleep s4 = () -> System.out.println("ddd");
abstract class TestClass {

    private int number;
    public TestClass() {}
    public TestClass(int number) {
        this.number = number;
    }

    // 一个非抽象方法
    void sayHello() {
        System.out.println("Hello!");
    }
    // 一个抽象方法
    abstract void sayHi();
}
public class Test034_Interface {
    public static void main(String[] args) {
        int a = 10;
        TestClass tc = new TestClass(10) {
            String name;
            int age;
            @Override
            void sayHi() {
                test();
                System.out.println(a);
                System.out.println(name);
                System.out.println(age);
                test();
            }
            void test() {

            }
        };
        tc.sayHi(); //通过封装在方法中调用子类的变量即方法
    }
}

四、包装类

针对这八种基本类型,JavaAPI又专门提供了对应的类类型,目的就是为了分别把这八种基本类型的数据,包装成对应的类类型,这时候就变成对象了,就可以调用方法了或者访问属性了。
在这里插入图片描述
JDK1.5或以上,可以支持基本类型和包装类型之间的自动装箱、自动拆箱,这简化了基本类型和包装类型之间的转换。

  • 自动装箱:基本数据类型值 自动转化为 包装类对象
  • 自动拆箱:包装类对象 自动转化为 基本数据类型值
int i = 10;
 //将i转换为对应的包装类对象
  //1.调用包装类的静态方法valueOf()
 Integer iobj1 = Integer.valueOf(i);
//2.通过创建一个包装类并传参
  Integer iobj2 = new Integer(i);
   print(iobj1);
   print(iobj2);

//拆箱:调用xxxValue()方法
        print(iobj1.intValue());
        print(iobj2.intValue());

public static void print(Object obj) {
        System.out.println(obj);
    }

注意事项:
使用基本类型和包装类时,要考虑隐式类型转换。
不同类型的基本数据类型和包装类,是不可以自动装箱拆箱的,例如int和
Long。

Integer 缓冲区

在Java中方法区有一个叫做运行时常量池的区域,用于存储编译期间生成的各种字面量和符号引用,Integer常量池就存在运行时常量池里面。

Integer常量池是一个特殊的缓存机制,用于存储在范围[-128,127]之间的Integer常量对象。这个范围是Java中的一个固定范围,超出这个范围的整形常量不会被缓存。

当使用自动装箱讲一个整数赋值给一个Integer对象时,如果整数在[-128,127]范围内,那么会从Integer常量池获取一个已经存在的对象,而不是创建一个新的对象。这是因为在这个范围内的整数常见且频繁使用,通过复用对象可以节省内存空间和提高性能。

identityHashCode(Object x);该方法会返回对象的哈希码,即Java根据对象在内存中的地址计算出来一个整数值,不同的地址算出来的结果是不一样的。

五、Object类

1.toString()

可以返回一个对象默认的字符串形式
子类可以对该方法进行重写
输出对象时,自动调用对象.toString()方法
Student s1 = new Student();
//下面两行效果相同
System.out.println(s1);
System.out.println(s1.toString());

2.equals()

该方法用于比较两个对象是否相等
在Object源码中,Object中的equals方法,是直接使用的==号进行的比较,比较俩个对象的地址值是否相等
在创建新的类时,如果有需要就要对该方法进行重写

3.hashCode()

该方法返回一个int值,该int值是JVM根据对象在内存的中的特征(地址值),通过哈希算法计算出的一个结果。

引用变量的hashCode值:

  • 俩个引用变量指向同一个对象,则它们的hashCode值一定相等
  • 俩个引用变量的hashCode值相同,则它们有可能指向同一个对象,也可能指向不同对象
  • 俩个引用变量的hashCode值不同,则它们肯定不可能指向同一个对象

如果需要重写equals方法的话,我们一般会同时重写hashCode(),建议使用STS自动生成重写代码

4.getClass()

返回引用变量在运行时所指向的字节码对象。
注意:子类中不能重写getClass,调用的一定是Object中的getClass方法。

六、String类

在Java中, String 是一个类,用于表示字符串,它是Java中最常用的类之一,用于处理文本数据。

String基础内容:

  • String 类在 java.lang 包下,所以使用的时候不需要导包
  • Java 程序中所有字符串字面值(如"abc")都是 String类对象
  • 字符串值不可变, String 对象是不可变的,一旦创建,它们的值就不能被修改
    在这里插入图片描述
常量池

JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化

  • 为字符串开辟一个字符串常量池,类似于缓存区
  • 创建字符串常量时,首先会检查字符串常量池中是否存在该字符串,如果存在该字符串,则返回该实例的引用,如果不存在,则实例化创建该字符串,并放入池中

当我们创建字符串常量时,如果字符串常量池中已经存在相同内容的字符串,那么新创建的字符串常量会直接引用已存在的字符串对象,而不会创建新的对象。这样可以避免重复创建相同内容的字符串,节省内存空间。
在JDK8及之后的版本中,字符串常量池的位置与其他对象的存储位置,都位于堆内存中。这样做的好处是,字符串常量池的大小可以根据需要进行调整,并且可以享受到垃圾回收器对堆内存的优化。

Java将字符串放入String常量池的方式:

  1. 直接赋值:通过直接赋值方式创建的字符串常量会被放入常量池中。
    例如: String str = “Hello”;
  2. 调用String类提供intern()方法:可以将字符串对象放入常量池中,并返回常量池中的引用。
    例如: String str = new String(“World”).intern();
    注意:通过new关键字创建的字符串对象不会放入常量池中,而是在堆内存中创建一个新的对象。只有通过直接赋值或调用intern()方法才能将字符串放入常量池中。

String s1 = “a”;
String s2 = “b”;
常量优化机制:“a” 和 "b"都是字面值常量,借助 + 连接,其结果 “ab” 也被当作常量

注意事项:
使用 + 拼接多个字符串常量,拼接的结果仍旧是字符串常量
如果结果字符串常量在常量池中不存在,则Java会将其放入到字符串常量池中

常见方法

length() 获取字符串的长度
equals(Object obj) 比较字符串的内容,严格区分大小写
charAt(inr index)返回指定索引处的char值
toCharArry()将字符串拆分为字符数组后返回
split(String regex)根据传入的规则切割字符串,得到字符串数组
substring(int begin, int end)根据开始和结束索引进行截取,得到新的字符串[begin,end)
substring(int begin) 从传入的索引处截取,截取到末尾,得到新的字符串[begin, str.length())
replace(CharSequence target, CharSequence replacement)将字符串中的旧值替换,得到新的字符串

七、枚举

枚举,是jdk1.5引入的新特性,可以通过enum关键字来定义枚举

我们可以将 Gender 定义为一个 枚举类型(enum) ,在枚举类型中,我们需要提前定义 枚举元素 (枚举类型对象固定的几个取值),以后开发过程中只能使用枚举元素给 Gerder类对象 赋值。
enum Gender {
MALE,FEMALE
}

  • 枚举类Gender本质上是一个final修饰的类,不可以被继承
  • 枚举类会默认继承 java.lang.Enum 这个抽象泛型类
  • 枚举元素,本质上是枚举类对象,且由 static和final 修饰
  • 枚举类提供私有构造器,我们在类外不能主动创建枚举类对象
  • 枚举类中可以包含 public static 静态方法

枚举类注意事项:

  • 定义枚举类要使用关键字 enum
  • 所有枚举类都是 java.lang.Enum 的子类
  • 枚举类的第一行上必须是枚举元素(枚举项)
  • 最后一个枚举项后的分号是可以省略的,但是 ; 后面还有其他有效代码,这个分号就不能省略,建议不要省略
  • 用户如果不提供构造方法,系统会提供默认的构造方法: private 枚举类() {}
  • 用户可以提供构造方法,但必须用 private修饰 ,同时系统不再提供默认构造方法
  • 枚举类也可以有抽象方法,但是枚举元素必须重写所有抽象方法

可变字符串:对象是一个用来表示字符串内容的对象,但是保存的字符内容是可以发生改变的。

可变字符串类型:
StringBuilder 线程不安全
StringBuffer 线程安全
使用可变字符串的大致流程:
1.先创建一个可变字符串对象
new StringBuilder()
2.调用相关方法完成字符串的处理
追加 append()
3.toString方法生成String对象

StringBuilder stringBuilder = new StringBuilder();
        //调用append()方法进行字符串内容拼接
//        stringBuilder.append("a");
//        stringBuilder.append("b");
//        stringBuilder.append("c");
//        stringBuilder.append("d");

        stringBuilder.append("a").append("b").append("c").append("d");
        System.out.println(stringBuilder);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值