第七章 静态、抽象、内部类
static
-
static 修饰属性,方法,代码块、内部类。
静态【贵族】 非静态【平民】 属性属于谁 属于类 属于对象 属性调用 类名访问(推荐)、对象访问 对象访问 属性特有?共享? 所有对象共享 对象特有 属性初始化时间 类加载到内存时 创建对象后 方法调用 只能调用静态方法 静态方法 + 非静态方法 -
静态属性是属于类的,可以直接使用类名来访问,也可以使用对象访问,但推荐使用类名访问
-
非静态的成员变量是属于对象特有的,
静态的成员变量是所有对象所共享。
-
静态属性是属于类的,只要类加载到内存了,就可以使用类名来访问。
非静态属性是属于对象的,只有创建出对象了,使用对象才可以访问。
-
非静态方法中可以调用静态方法,【贵族平民】
静态方法中只能调用静态的。
-
属性的初始化时间:【饮水机和同学们的水杯】
- 非静态属性:创建对象后,系统会自动给对象中的非静态属性做初始化赋默认值,也正是因为这个原因,非静态属性只有在创建对象后,使用对象才能访问。
- 静态属性:类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值,所以,即使还没有创建对象,只要这个类加载到了内存,就可以直接使用类名来访问静态属性,因为这个时候静态属性已经完成了初始化赋默认值的操作。
-
在非静态方法中,是否可以直接访问类中的静态属性和静态方法?
- 是可以的,因为非静态方法都可以调用了,说明肯定已经创建对象了,同时也说明了这个类早就完成了类加载,那么类中的静态属性和静态方法,现在肯定是可以访问和调用的。
-
-
静态属性的存储位置:
类中的静态属性,跟随着类,一起保存在内存中的方法区。 -
类中的构造器,可以给非静态属性做初始化,但是不能给静态属性做初始化。
-
因为我们可以绕过创建对象的步骤,直接使用类名访问这个静态属性。
-
注意:一般不使用构造器初始化静态属性。
-
-
静态代码块,也叫做静态初始化代码块,它的作用就是给类中的静态属性做初始化的。
-
注意:不能使用this在静态方法中,this表示调用方法的对象。
-
静态代码块的执行时刻:
- 由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。
所以静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作。 - 并且,静态代码块只会自动被执行一次,因为JVM在一次运行中,对一个类只会加载一次!
- 由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。
-
匿名代码块:
一种非静态代码块,也叫做匿名代码块,它的作用是给非静态属性做初始化操作- 每次创建一个对象,会默认调用一次非静态代码块。
-
匿名代码块执行的时刻:
- 由于匿名代码块没有名字,我们并不能主动调用,它会在创建对象时,构造器执行之前,自动执行。
- 并且每次创建对象之前,匿名代码块都会被自动执行。
-
执行顺序:
-
在一个类中:
-
先静态,再非静态
-
静态:静态属性默认赋值 > 静态属性显式赋值 >静态代码块
-
非静态:匿名代码块 > 构造器
父类构造器 > 子类属性的显式赋值 > 子类匿名代码块 > 子类构造器
【一般属性定义在匿名代码块前面,所以在前】
【如果匿名代码块定义在属性显式赋值前,执行顺序:匿名代码块 > 属性显式赋值。】
-
混合:静态代码块 > 匿名代码块 > 构造器
-
-
在子父类中:
- 父类 > 子类
-
-
类的加载 :子类和父类 中 静态的
创建对象: 子类、父类 中 非静态的
-
只要加载子类,它就会执行父类中静态属性赋值和静态代码块
-
静态导入:在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名
-
子父类中,静态方法不属于重写,是新的方法。
-
静态与非静态,与类、与对象的关系。
final
-
final修饰符,可以用来修饰类、变量、方法。
-
final修饰的类,无法被继承。
-
用final修饰的方法可以被继承,不能被重写。
-
用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错
(常量不能重新赋值。)
-
-
final常量赋值:
- 声明的同时赋值(显式赋值)
- 匿名代码块中赋值
- 构造器中赋值
- 使用构造器给常量赋值时,所有构造器必须都对此常量进行赋值。
abstract
-
abstract修饰符,可以修饰类、方法
-
抽象类不能创建对象,抽象方法没有方法体。
-
抽象方法的特点:
- 只有方法的声明 //方法的声明:public void test()
- 没有方法的实现 //方法的实现:{}
-
普通子类继承了抽象父类,需要实现父类中所有的抽象方法。
- 子类是抽象类,需要实现父类及其间接抽象父类中的抽象方法。
-
抽象方法一定是在抽象类当中
抽象类中可以不包含抽象方法。
-
抽象类和非抽象类的区别:
抽象类 非抽象类 修饰符 abstract修饰符 没有使用abstract 抽象方法 可以有 不能有抽象方法 创建对象 不能创建对象 可以创建对象 -
抽象类和抽象方法的关系:
- 抽象类中可以没有抽象方法
- 有抽象方法的类,一定要声明为抽象类
-
既然抽象类不能被实例化创建对象,那么这个类有什么用?
-
抽象类是用来被子类继承的,子类继承抽象类,并且实现抽象类中的抽象方法。
(实现抽象方法当做重写方法)
-
-
抽象类不实例化创建对象,那么抽象类中是否有构造器?
- 有构造器,这个构造器是让子类调用的,子类继承父类,子类创建对象的时候,会先调用父类的构造器!(对父类的属性进行初始化)
interface接口
- 接口:使用interface关键字
- 接口的内部主要就是封装了方法和静态常量。
- 接口最终也会被编译成.class文件。
- 接口中没有静态代码块,通过显式赋值操作静态常量。
- 接口使用 extends 继承其他接口。
- 注意:
- 接口中的属性都是公共的静态常量 (默认就是静态的 int b = 0;)
- 接口中的方法一般都是抽象方法 (默认修饰符就是public abstract)
- 注意:JDK8中,还允许在接口中编写静态方法和默认方法
- 注意:JDK9中,还允许在接口中编写私有方法
- 接口的中抽象方法,不需要使用 abstract 修饰符,因为接口中的方法默认就是抽象方法
- 接口里面的属性,默认就是public static final修饰的
接口里面的方法,默认就是public abstract修饰的
所以这些都可以省去不写的
- 类和类之间的关系是继承,类和接口之间的关系是实现:一个类实现了一个或多个接口
- 单继承,多实现
- 一个类实现了接口,那么就要实现接口中所有的抽象方法。
- 接口类型的引用可以指向任意的实现类对象。(多态)
- java中,类和类之间是单继承,类和接口之间是多继承
- 接口的引用调用方法,调用到的是实现类中重写的方法
- 接口类型的引用,不能直接调用实现类中特有的方法。
访问控制
-
public > protected > default > private
修饰符 类中 同包非子类 同包子类 不同包子类 不同包非子类 public Y Y Y Y Y protected Y Y Y Y N default Y Y Y N N private Y N N N N - public_公共的:在所有地方都可以访问
- protected_受保护的:当前类中、子类中,同一个包中其他类中可以访问
- default_默认的:当前类中、同一个包中的子类中可以访问
- 注意,default默认的,指的是空修饰符,并不是default这个关键字
例如,String name; 在类中,这种情况就是默认的修饰符
- 注意,default默认的,指的是空修饰符,并不是default这个关键字
- private_私有的:当前类中可以访问
-
new对象,方法参数(Test2 t),public Test2 t (不去new只声明) 。
-
权限
- public:所有权限
- protected:包权限(同包) 或 子类
- 同包下,使用引用调用
- 不同包下,不能使用引用调用,只能通过继承调用。
- defalut : 包权限 (同包)
- private:当前类中
-
父类的引用不能调用父类受保护的方法和受保护的属性。?写代码检测-子类吧?
内部类
-
内部类,不是在一个java源文件中编写俩个平行的类,而是在一个类的内部再定义另外的一个类。
-
匿名内部类【很重要】
- 创建抽象类Person子类 --> 匿名内部类
- new Person() {} ;
- 匿名内部类可以定义特有的方法,但无法去调用。
- 不能在匿名内部类中定义构造器
- 写在方法中时,只能在创建匿名内部类的方法中使用。
- 实现的代码只调用一次时,通过匿名内部类实现。
- 创建匿名内部类
- 简化了创建子类对象,简化重写方法。
- 缺点:范围只在当前方法中 (解决:通过定义成员变量)测试
- 注意,如果用父类型声明这个匿名内部类,那么这个匿名内部类默认就是这个父类型的子类
- 匿名内部类的俩种形式:
- 利用一个父类,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个父类的子类型
- 利用一个接口,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个接口的实现类
- 匿名内部类因为没有类名:
- 匿名内部类必须依托于一个父类型或者一个接口
- 匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了
- 匿名内部类中无法定义构造器
- 创建抽象类Person子类 --> 匿名内部类
-
成员内部类
-
在类中,可以定义成员方法、成员变量,除此之外,还可以定义成员内部类
-
注意,成员内部类中,不能编写静态的属性和方法。
注意,当前这个代码,编译成功后,会生成俩个class文件,一个外部类,一个内部类。-
外部类的方法,访问成员内部类的属性和方法,需要创建内部类对象,然后才可以访问。
-
如果这个成员内部类不是private修饰的,那么在其他类中就可以访问到这个内部类
-
-
在其他类中,使用这个非private修饰的成员内部类的时候,需要注意以下几点:
\1. 这个内部类需要import导入,并且在其他包中是外部类.内部类
的形式导入。
\2. 在创建对象的时候,需要先创建出外部类对象,然后使用外部类对象再创建内部类对象。形式为:外部类对象.new 内部类对象();
-
类的内部除了嵌套另一个类之外,是否还可以嵌套接口?
- 以,不仅类中可以嵌套接口,接口的内部也可以嵌套其他接口。
-
-
静态内部类
- 静态内部类和成员内部类是类似的,只是这个内部类,多了static关键字进行修饰。
- 注意,静态内部类中,【可以】编写静态的属性和方法,另外在四种内部类中,只有静态内部类可以编写静态属性和方法
- 注意,在静态内部类中访问不了外部类中的非静态属性和方法
- 在其他类中,使用这个非private修饰的静态内部类的时候,需要注意以下几点:
\1. 这个内部类需要import导入,并且是外部类.内部类
的形式导入。
\2. 在创建对象的时候,直接使用这个静态内部类的名字即可:new 静态内部类对象();
,不再需要依赖外部类对象了。
-
局部内部类
- 局部内部类,是另一种形式的内部,在声明在外部类的方法中,相当于方法中的局部变量的位置,它的作用范围只是在当前方法中。
- 局部内部类中,访问当前方法中的变量,这个变量必须是final修饰的
- 注意:在JDK1.8中,一个局部变量在局部内部类中进行访问了,那么这个局部变量自动变为final修饰
-
内部类的选择:
假设现在已经确定了要使用内部类,那么一般情况下,该如何选择?
\1. 考虑这个内部类,如果需要反复的进行多次使用(必须有名字)-
- 在这个内部类中,如果需要定义静态的属性和方法,选择使用静态内部类
-
- 在这个内部类中,如果需要访问外部类的非静态属性和方法,选择使用成员内部类
\2. 考虑这个内部类,如果只需要使用一次(可以没有名字)
-
- 选择使用匿名内部类
\3. 局部内部类,几乎不会使用
-
包装类
-
包装类,是用来解决无法使用面向对象处理基本数据类型问题。
-
创建Integer对象:new 或者 Integer.valueOf()获取对象
-
parseInt():将字符串参数解析为带符号的十进制整数。
valueOf():返回一个Integer对象。
intValue():将Integer的值作为int
- 装箱的时候自动调用的是Integer的valueOf(int)方法。
- 而在拆箱的时候自动调用的是Integer的intValue方法。
-
JDK1.5或以上,可以支持基本类型和包装类型之间的自动装箱、自动拆箱。
-
自动装箱使用的是同一个对象,范围在-128~127。
-
当自动装箱 或者 调用Integer.valueOf(),当数值在-128~127之间,Integer通过获取内部类的缓存数组,返回已存在的对象。(是一个对象数组)
- 超过范围,会新建对象。
-
同一对应类型才能自动装箱。
Object常用方法
-
toString方法
-
该方法可以返回一个对象默认的字符串形式
-
System.out.println(stu);
System.out.println(stu.toString())
注意,默认情况下,println方法会调用这个对象的toString方法
注意,推荐使用第一种,因为当stu的值为null时,第二种输出方式会报错,空指针异常
-
-
getClass方法
- 它可以返回一个引用在运行时所指向的对象,具体类型是什么
- 子类中不能重写getClass,调用的一定是Object中的getClass方法:
-
equals方法
- 该方法可以比较俩个对象是否相等
- Object中的equals方法,是直接使用的==号进行的比较,比较俩个对象的地址值是否相等
- 对equals方法重写,一般需要注意以下几点:
\1. 自反性:对任意引用obj,obj.equals(obj)的返回值一定为true.
\2. 对称性:对于任何引用o1、o2,当且仅当o1.equals(o2)返回值为true时,o2.equals(o1)的返回值一定为true;
\3. 传递性:如果o1.equals(o2)为true, o2.equals(o3)为true,则o1.equals(o3)也一定为true
\4. 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
\5. 非空性:任何非空的引用obj,obj.equals(null)的返回值一定为false
-
hashCode方法
- 该方法返回一个int值,该int值是JVM根据对象在内存的中的特征(地址值),通过hash算法计算出的一个结果。
- Hash,一般翻译做“散列”,也可以音译为“哈希”,就是把任意长度的数据输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。
- Object中的hashCode方法默认返回的是对象的内存地址
- 对于俩个对象的hashCode值:
- 相等的俩个对象,hashCode值一定相等
- hashCode值相同,俩个对象有可能相等,也可能不相等(压缩映射)
- hashCode值不同,俩个对象一定不同 (-128~127 和 new 的)
- 该方法返回一个int值,该int值是JVM根据对象在内存的中的特征(地址值),通过hash算法计算出的一个结果。
关于String对象
- 字符串String,是程序中使用最多的一种数据,JVM在内存中专门设置了一块区域(字符串常量
池),来提高字字符串对象的使用效率。 - JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
- 创建字符串常量时,首先会检查字符串常量池中是否存在该字符串,如果存在该字符串,则返回该实例的引用,如果不存在,则实例化创建该字符串,并放入池中
- String str1 = “hello”;
String str2 = new String(“hello”);- str1指向的是常量池中的hello对象
- str2指向的是堆区中新创建的hello对象
- 只有用双引号,里面加入文字的方式,才会利用到内存中的字符串常量池
- 注意,使用+拼接的字符串也会利用字符串常量池,但是参与+号拼接的必须是双引号的形式才可以
- 当调用 intern() 方法时,JVM会将字符串添加到常量池中,并返回指向该常量的引用
查漏补缺
-
== 和 equals() 各自的概念 ,区别,场景。
-
== 是直接比较的两个对象的堆内存地址,如果相等,则说明这两个引用实际是指向同一个对象地址的。
- 对于基本数据类型,在常量池中,一个常量只会对应一个地址(范围:-128~127)
-
equals(),比较的是对象的内容。
-
Object类型的equals方法是直接==通过来比较的
但是,所有的类都直接或间接地继承自java.lang.Object类。都可以重写equals方法。
-
而基本类型的包装类,都重写了equals()方法,所以,比较的是对象的内容!
-
-
-
注意:有变量参与拼接字符串,那么就不会使用常量池了
-
String s = new String(); //表示空字符串 “”。
- value = char[] 字符串值存储的是一个字符数组
-
字符串的特点:不可变的字符串对象
- ( replcace()方法 return new String() 返回新的对象 由方法的逻辑决定)
-
字符串常用方法:【字符串的底层是操作数组 ,char[] 】练习
- charAt(int index) :返回下标处的字符值
- concat(“abc”):拼接字符串
- contains(“a”):判断是否包含
- endWith(“d”):判断字符串后缀
- getBytes():返回byte类型数组
- isEmpty():判断字符串是否为空
- length():字符串的长度
- split(","):分割字符串,分割的依据。
- 如果分隔符是特殊字符,通过转义字符或正则表达式。
- trim():去除空格 (登录用户名)
- toCharArray():字符串 --> 数组
- String.valueOf():基本数据类型、类、数组 --> 字符串
- substring(beginIndex, endIndex):截取
-
null 是不指向任何字符串对象。
"" 是空字符串,不包含任何字符,是一个对象。
“null” ,由4个字符组成,系统作打印。
作业
- abstract的作用
- 可以修饰类和方法
- 不能修饰属性和构造方法
- abstract 修饰的类是抽象类,需要被继承
- abstract 修饰的方法是抽象方法,需要子类被重写
- 父类引用不能调用子类特有的方法。
快捷键
- 快速生成构造器等:右键 --> source