Java第十章:面向对象高级
类变量和类方法
类变量的定义和访问
定义:
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量。
访问修饰符 static 数据类型 变量名;(推荐)
static 访问修饰符 数据类型 变量名;
访问:
类名.类变量名(推荐)
对象名.类变量名
前提:满足访问修饰符的访问权限和范围
类变量细节
1.类变量是在类加载时就初始化了,因此,即使你没有创建对象,也可访问类变量
2.类变量的生命周期是随类的加载开始,随类消亡而销毁
类变量内存布局
根据JDK版本不同:
存储在类加载时反射生成的class原型对象的尾部,而class原型对象存在堆结构中
存储在方法区的静态域中
类方法的定义和调用
定义:
访问修饰符 static 数据返回类型 方法名(){ }(推荐)
static 访问修饰符 数据返回类型 方法名(){ }
调用:
类名.类方法名
对象名.类方法名
前提:满足访问修饰符的访问权限和范围
类方法细节
1.类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区;
类方法中无this的参数
普通方法中隐含着this的参数(因为类方法无法访问非静态成员,静态成员就是this的参数)
2.类方法中不允许使用和对象有关的关键字,如this和super,普通方法可以
3.静态方法,只能访问静态成员
非静态方法,可以访问静态成员和非静态成员(必须遵守访问权限)
main方法
main方法语法:
形式: public static void main(String[] args)
1.java虚拟机需要调用类的main方法,所以该方法的访问权限是public
2.java虚拟机在执行main方法时不必创建对象,所以该方法必须是static
3.该方法接受String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
4.java 执行的程序 参数1 参数2 参数3
main方法细节
1.在main方法中,可以直接调用main方法所在类的静态成员
但是不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过该对象去访问类中的非静态成员
IDEA中main方法传递参数
代码块
代码块介绍和语法:
基本介绍:
代码块又称为初始化块,属于类中的成员(即 是类的一部分),类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用
基本语法:
[修饰符]{
代码
};
注意:
1.修饰符可选,写只能写static
2.代码块分为静态代码块和非静态代码块/普通代码块
3.;可加可不加
代码块好处:
1.相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2.如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
代码块细节:
1.static代码块是类加载时,执行,只会执行一次;
2.普通代码块是创建对象时调用的,创建一次,调用一次
3.类加载的3种情况:
1):创建对象实例时(new)
2):创建子类对象实例,父类也会被加载
3):使用类的静态成员时
4.创建一个对象时,在一个类 调用顺序是:
1):加载类信息,同时调用静态代码块和静态属性初始化(优先级一样,按定义顺序调用)
2):调用构造方法,而构造方法隐含 super() 和 普通代码块调用和普通属性初始化
3):调用super()和调用普通代码块和普通属性的初始化(优先级一样,按定义顺序调用)
4):执行构造方法剩余部分
5.创建一个子类对象时,调用顺序:
1):先加载父类,父类的静态代码块和静态属性初始化
2):后加载子类,子类的静态代码块和静态属性初始化
3):调用构造方法,而先调用隐含的super后调用普通代码块和普通属性初始化,所以!
4):父类的普通代码块和普通属性初始化
5):父类构造方法的剩余部分
6):子类的普通代码块和普通属性初始化
7):子类构造方法的剩余部分
6.静态代码块只能直接调用静态成员,普通代码块可以调用任意成员。
代码块练习
单例模式
单例模式
定义:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
饿汉式
package single_;
public class E_Han {
public static void main(String[] args) {
Girlfriend gf = Girlfriend.getInstance();
Girlfriend gf1 = Girlfriend.getInstance();
System.out.println(gf);
System.out.println(gf1 == gf);//指向同一个对象,因为对象为static,类加载时就创建,且只创建一次
//为什么叫饿汉式?因为加载即创建该实例,即使没用到也会创建
System.out.println(Girlfriend.n1);
}
}
class Girlfriend{
private String name;
public static int n1 = 100;
//如何保障我们只能创建一个Girlfriend对象
//步骤[单例模式-饿汉式]
//1.将构造器私有化
//2.在类的内部直接创建对象(该对象是static,方便后续静态方法返回该对象)
//3.提供一个公共的static方法,返回girlfriend对象
private Girlfriend(String name) {
this.name = name;
}
private static Girlfriend girlfriend = new Girlfriend("刘浩存");
public static Girlfriend getInstance(){
return girlfriend;
}
@Override
public String toString() {
return "Girlfriend{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式
package single_;
public class Lan_Han {
public static void main(String[] args) {
Love mylove = Love.getInstance();
Love mylove1 = Love.getInstance();
System.out.println(mylove);
System.out.println(mylove == mylove1);
//为什么叫懒汉式?因为getInstance才创建,加载类时只创建了对象的引用变量
System.out.println(Love.n1);
}
}
class Love{
private String name;
public static int n1 = 100;
private Love(String name) {
this.name = name;
}
private static Love mylove;
public static Love getInstance(){
if(mylove == null){
mylove = new Love("刘浩存");
}
return mylove;
}
@Override
public String toString() {
return "Love{" +
"name='" + name + '\'' +
'}';
}
//1.将构造器私有化
//2.在类内部创建对象的引用变量,而非直接创建对象
//3.提供一个公共的方法创建对象并返回
}
饿汉式和懒汉式区别
1.创建对象的时机不同:
饿汉式在类加载时创建对象实例
而懒汉式在使用时才创建
2.缺点不同:
懒汉式存在线程安全问题
而饿汉式存在浪费资源的可能
final关键字
final基本介绍
final可修饰:类、属性、方法和局部变量
使用情形:
1.不希望类被继承时
2.不希望父类方法被重写时
3.不希望类的某个属性的值被修改时
4.不希望某个局部变量被修改时
final细节
1.final修饰的属性又叫常量,一般 用XX_XX_XX来命名
2.final修饰的属性在定义时,必须初始化
修饰普通属性:
初始化位置:
(1):定义时直接初始化
(2):在构造器中
(3):在代码块中
修饰静态属性:
初始化位置:
(1):定义时直接初始化
(2):在静态代码块中,不能在构造器中,因为静态属性在类加载时就需进行初始化
3.final类不能被继承,但是可以实例化对象
4.如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
5.final类没必要再将方法修饰成final方法
6.final不能修饰构造器
7.final 和 static 往往搭配使用,效率更高,不会导致类加载。底层编译器做了优化处理。
如调用接口中的属性,IA.n1
8.包装类(Integer,Double,Float等都是final),String也是final类
抽象类
抽象类介绍
抽象类细节
1.抽象类不能被实例化
2.抽象类不一定要包含abstract方法
一旦类包含了abstract方法,则类必须声明为abstract
3.abstract只能修饰类和方法,不能修饰属性和其他的
4.抽象类可以有任意成员(抽象类本质还是类)
5.如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
6.抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的
静态方法只能被继承,不能被重写,子类中与父类相同的静态方法只会隐藏父类的静态方法不可见而已,二者没有关系,他们的行为不具有多态性
抽象类练习
抽象类最佳实践-模板设计模式
Template类
package abstract_interface.abstract_;
public abstract class Template {//抽象类-模板设计模式:子类只需重写job方法即可调用calculateTime方法呈现不同的结果
public abstract void job();
public void calculateTime(){
//统计当前距离1970-1-1 0:0:0 的时间差,单位ms
long start = System.currentTimeMillis();
job();
long end = System.currentTimeMillis();
System.out.println("Job耗时: "+ (end - start)+"ms");
}
}
Test类
package abstract_interface.abstract_;
public class Test {
public static void main(String[] args) {
Chicken goji = new Chicken("公鸡", 10.5);
System.out.println(goji.howToEat());
Job1 job1 = new Job1();
job1.calculateTime();
}
}
接口
接口基本介绍
介绍:
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,根据具体情况把这些方法写出来
语法:
interface 接口名{
//属性
//方法(1.抽象方法 2.默认实现方法(default声明) 3.静态方法)
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法;
}
小结:
1.在jdk7.0之前 接口里的所有方法都没有方法体,即都是抽象方法
2.jdk8.0后接口可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
接口应用场景
接口DBinterface
package abstract_interface.interface_.apply;
public interface DBinterface {
public void connect();
public void close();
}
MysqlDB类
package abstract_interface.interface_.apply;
public class MysqlDB implements DBinterface{
@Override
public void connect() {
System.out.println("连接Mysql...");
}
@Override
public void close() {
System.out.println("关闭Mysql...");
}
}
OracleDB类
package abstract_interface.interface_.apply;
public class OracleDB implements DBinterface{
public void connect() {
System.out.println("连接Oracle...");
}
@Override
public void close() {
System.out.println("关闭Oracle...");
}
}
interface测试类
package abstract_interface.interface_.apply;
/**
* 体会接口编程
* 1.项目经理写接口,程序员实现接口
* 2.接口回调,统一规范
*/
public class Interface {
public static void main(String[] args) {
//连接MysqlDB
MysqlDB mysqlDB = new MysqlDB();
test(mysqlDB);
//连接OracleDB
OracleDB oracleDB = new OracleDB();
test(oracleDB);
}
public static void test(DBinterface dbinterface){
//接口回调
dbinterface.connect();
dbinterface.close();
}
}
接口细节
1.接口不能被实例化
2.接口中所有的方法默认为public,抽象方法可以不用abstract修饰
如:void aaa(){} 等价于 public abstract void aaa(){}
3.一个普通类实现接口,就必须将该接口的所有抽象方法实现,
抽象类实现接口,可以不用实现接口的方法。
4.一个类同时可以实现多个接口
5.接口中的属性,只能是final的,而且是public static final 修饰符。
如:int a = 1; 等价于 public static final int a = 1;(必须初始化)
6.接口中属性的访问形式:接口名.属性名
7.接口不能继承其他的类,但是可以继承多个别的接口
8.接口的修饰符只能是public 和 默认,和类的修饰符一样
接口小练习
接口VS继承
1.解决问题不同:
继承解决代码的复用性和可维护性
接口设计好各种规范,让其它类去实现这些方法
2.接口比继承更加灵活:
继承满足 is-a 的关系
而接口只需满足 like-a 的关系
3.接口在一定程度上实现代码解耦(利用接口规范 + 动态绑定机制)
接口多态特性
1.多态参数+多态数组
package abstract_interface.interface_.poly.poly_1_2;
//体会接口回调、接口多态
public class Test {
public static void main(String[] args) {
//多态1:多态数组
//测试1: 使用Edible类型的数组测试
//接口类型 引用名 = 实现该接口的类的对象的引用
Edible[] e = new Edible[2];
e[0] = new Chicken("鸡哥", 10.5);
e[1] = new Apple("红色", 10.6);
for (int i = 0; i < 2; i++) {
System.out.println(e[i].howToEat());
}
//多态2:多态参数
//测试2: 定义showInfo函数,形参是Edible类型的引用,调用函数传入不同的实参实现多态
showMenu(new Chicken("jige",9.5));
showMenu(new Apple("绿色",9.6));
//测试3: 定义LittleBaby中的UnderstandObject函数,形参是Object的引用,
// 如果实参实现Edible,则显示吃法,否则提示用户不可吃
LittleBaby littleBaby = new LittleBaby();
littleBaby.understandObject(new Tiger("泰格",15));
littleBaby.understandObject(new Apple("黄色",11.6));
littleBaby.understandObject(new Chicken("母鸡",11.6));
}
public static void showMenu(Edible edible){
System.out.println(edible.howToEat());
}
}
2.多态传递
package abstract_interface.interface_.poly.poly_3;
/**
* 接口的多态传递现象
* 1.接口继承产生
* 2.类的继承产生
*/
public class PolyPass {
public static void main(String[] args) {
IB People1 = new People();
IA People2 = new People();//接口继承产生多态传递现象
IB teacher1 = new Teacher();//类继承产生多态传递现象
IA teacher2 = new Teacher();//接口继承和类继承同时发挥作用
}
}
interface IA{
}
interface IB extends IA{
}
class People implements IB{
}
class Teacher extends People{
}
接口练习
内部类
内部类分类
根据定义位置分:
定义在外部类局部位置上(比如方法内、代码块内):
1):局部内部类(有类名
2):匿名内部类(没有类名)
定义在外部类的成员位置上:
1):成员内部类(没有static修饰)
2):静态内部类(使用static修饰)
局部内部类
1.可以直接访问外部类的所有成员,包含私有的
2.不能添加访问修饰符,因为地位相当于局部变量。
但可以用final修饰,表示不能被其他局部内部类继承
3.作用域:仅仅在定义它的方法或代码块中
4.局部内部类--->访问--->外部类的成员:直接访问
5.外部类--->访问--->局部内部类的成员:创建对象,再访问(注意:必须在作用域内)
6.外部其他类--->不能访问--->局部内部类(因为局部内部类相当于一个局部变量)
7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)访问
注:外部类.this相当于外部类的一个实例对象,哪个对象调用就是哪个对象
匿名内部类
1.匿名内部类既是一个类的定义,也是一个对象
2.可以直接访问外部类的所有成员,包含私有的
3.不能添加访问修饰符,因为相当于一个局部变量
4.作用域:仅仅在定义它的方法或代码块中
5.匿名内部类--->访问--->外部类成员:直接访问
6.外部其他类--->不能访问--->匿名内部类(因为匿名内部类相当于一个局部变量)
7.如果外部类和匿名内部类的成员重名时,匿名内部类访问默认遵循就近原则,如果想访问外部类的成员,可以使用(外部类名.this.成员)访问
注:2、3、4、5、6、7均与局部内部类相同,因为位置相同
与局部内部类不同的是:匿名内部类只定义一次,且只创建一次对象,因此不涉及外部类访问匿名内部类的问题
基于接口的匿名内部类
AnnoymousInnerClass
package innerclass.AnonymousInnerClass.interface_innerclass;
/**
* 基于接口的匿名内部类
*/
public class AnnoymousInnerClass {
public static void main(String[] args) {
Outer01 outer01 = new Outer01();
outer01.method();
}
}
IA
package innerclass.AnonymousInnerClass.interface_innerclass;
public interface IA {
public void say();
}
Outer01
package innerclass.AnonymousInnerClass.interface_innerclass;
/**
* 1.father1的编译类型:IA
* 2.father1的运行类型:匿名内部类
* 我们看底层:
* class Outer01$1 implements IA{
* @Override
* public void say() {
* System.out.println("我是你的爸爸1");
* }
* }
* 3.jdk底层在创建匿名内部类 Outer01$1时,立即马上就创建了 Outer01$1实例,并且把地址返回给father1
* 4.匿名内部类使用一次就不能再使用
*/
public class Outer01 {
private int n1 = 10;//属性
public void method(){//方法
//基于接口的匿名内部类
IA father1 = new IA() {
@Override
public void say() {
System.out.println("我是你的爸爸1");
}
};
//基于接口的匿名对象调用say方法
father1.say();
System.out.println("father1的运行类型: "+father1.getClass());//Outer01$1
//第二种调用方式,因为其本身就是匿名对象
new IA() {
@Override
public void say() {
System.out.println("我是你的爸爸1");
}
}.say();
}
}
基于类的匿名内部类
AnnoymousInnerClass
package innerclass.AnonymousInnerClass.class_innerclass;
/**
* 基于类的匿名内部类
* 基于抽象类的匿名内部类:匿名内部类必须实现抽象类中的抽象方法
*/
public class AnnoymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
}
}
Outer02
package innerclass.AnonymousInnerClass.class_innerclass;
/**
* 1.father1的编译类型:Father
* 2.father1的运行类型:匿名内部类
* 3.同理底层会创建匿名内部类,同时创建匿名内部类实例对象,并将地址返回给father1
* 4.匿名内部类使用一次就不能再使用
* 5.参数列表 ("Jack") 会传到父类构造器
*/
public class Outer02 {
private int n1 = 10;//属性
public void method(){//方法
Father father1 = new Father("Jack"){//此处Jack会传到父类构造器并进行初始化
@Override
public void test() {
System.out.println("父类test方法被重写...");
}
};
System.out.println("father1的运行类型: "+father1.getClass());//Outer02$1
father1.test();
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("吃饭");
}
};
animal.eat();
}
}
Father
package innerclass.AnonymousInnerClass.class_innerclass;
public class Father {
private String name;
public Father(String name) {
this.name = name;
}
public void test(){
System.out.println("父类test方法...");
}
}
Animal
package innerclass.AnonymousInnerClass.class_innerclass;
public abstract class Animal {
public abstract void eat();
}
匿名内部类的应用
当作实参直接传递
匿名内部类课堂练习
package innerclass.AnonymousInnerClass.example;
/**
* 1.有一个铃声接口Bell,里面有一个ring方法
* 2.有一个手机类CellPhonex,具有闹钟功能alarmclock,参数是Bell类型
* 3.测试手机类的闹铃功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
* 4.在传入另一个匿名内部类(对象),打印:小伙伴上课了
* 5.实质:Bell是接口,等下匿名内部类基于该接口创建
* CellPhone是外部类,alarmclock是方法,方法的实参采用匿名内部类
* Example主方法中创建外部类对象,调用alarmclock方法,实参传入不同的匿名内部类,呈现不同的结果
*/
public class Example{
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
cellPhone.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了...");
}
});
cellPhone.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课了...");
}
});
}
}
class CellPhone {
public void alarmclock(Bell bell){
bell.ring();
}
}
interface Bell{
void ring();
}
成员内部类
1.可以直接访问外部类的所有成员,包含私有的
2.可以添加任意访问修饰符(public、protected、默认、private),因为它地位就是一个成员
3.作用域:整个外部类类体
4.成员内部类--->访问--->外部类:直接访问
5.外部类--->访问--->成员内部类:创建对象再访问
6.外部其他类--->访问--->成员内部类:
三种方式:
(1):Outer01 outer01 = new Outer01();
Outer01.Inner01 inner01 = outer01.new Inner01();
(2):Outer01.Inner01 inner01 = new Outer01().new Inner01();
(3)://使用一个方法来获取,更加简洁
Inner01 inner01Instance = new Outer01().getInner01Instance();
7.如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果访问外部类的成员,采用(外部类名.this.成员)
静态内部类
放在外部类的成员位置,使用static修饰
1.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2.可以添加任意访问修饰符,因为地位就是一个静态成员
3.作用域:外部类整个类体
4.静态内部类--->访问--->外部类(比如:静态属性):直接访问
5.外部类--->访问--->静态内部类:创建对象再访问
6.外部其他类--->访问--->静态内部类:
两种方式:
(1)://因为静态内部类,可通过类名直接访问
Outer01.Inner01 inner01 = new Outer01.Inner01();
(2)://编写一个方法,返回静态内部类的对象实例
//普通方法,通过外部类对象调用
Outer01.Inner01 inner01 = outer01.getInner01();
//静态方法,通过外部类名调用
Outer01.Inner01 inner01_ = Outer01.getInner01_();
再访问
6.外部其他类—>访问—>成员内部类:
三种方式:
(1):Outer01 outer01 = new Outer01();
Outer01.Inner01 inner01 = outer01.new Inner01();
(2):Outer01.Inner01 inner01 = new Outer01().new Inner01();
(3)😕/使用一个方法来获取,更加简洁
Inner01 inner01Instance = new Outer01().getInner01Instance();
7.如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果访问外部类的成员,采用(外部类名.this.成员)
### 静态内部类
放在外部类的成员位置,使用static修饰
1.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2.可以添加任意访问修饰符,因为地位就是一个静态成员
3.作用域:外部类整个类体
4.静态内部类—>访问—>外部类(比如:静态属性):直接访问
5.外部类—>访问—>静态内部类:创建对象再访问
6.外部其他类—>访问—>静态内部类:
两种方式:
(1)😕/因为静态内部类,可通过类名直接访问
Outer01.Inner01 inner01 = new Outer01.Inner01();
(2)😕/编写一个方法,返回静态内部类的对象实例
//普通方法,通过外部类对象调用
Outer01.Inner01 inner01 = outer01.getInner01();
//静态方法,通过外部类名调用
Outer01.Inner01 inner01_ = Outer01.getInner01_();