面向对象进阶
1、包
包是用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护。
建包的语法格式: package公司域名倒写.技术名称。包名建议全部英文小写,且具备意义
建包语句必须在第一行,一般IDEA工具会帮助创建。
导包
相同包下的类可以直接访问,不同包下的类必须导包,才可以使用!
导包格式: import包名.类名;
假如一个类中需要用到不同类,而这个两个类的名称是一样的,那么默认只能导入一个类,另一个类要带包名访问。
2、权限修饰符
权限修饰符:是用来控制一个成员能够被访问的范围的。
可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。
权限修饰符的分类和具体作用范围:
权限修饰符:有四种作用范围由小到大(private ->缺省-> protected - > public )
修饰符 | 同一个类中 | 同一个包中其它类 | 不同包下的子类 | 不同包下的无关类 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
public class Fu {
// 1.private 只能本类中访问
private void show1() {
System.out.println("private");
}
// 2.缺省:本类,同一个包下的类中。
void show2() {
System.out.println("缺省");
}
// 3.protected:本类,同一个包下的类中,其他包下的子类
protected void show3() {
System.out.println("protected");
}
// 4.任何地方都可以
public void show4() {
System.out.println("public");
}
public static void main(String[] args) {
//创建Fu的对象,测试看有哪些方法可以使用
Fu f = new Fu();
f.show1();
f.show2();
f.show3();
f.show4();
}
}
public class Demo {
public static void main(String[] args) {
//创建Fu的对象,测试看有哪些方法可以使用
Fu f = new Fu();
// f.show1(); // 私有的
f.show2();
f.show3();
f.show4();
}
}
3、final
final关键字是最终的意思,可以修饰(方法,变量,类)
- 修饰方法:表明该方法是最终方法,不能被重写。
- 修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)。
- 修饰类:表明该类是最终类,不能被继承。
public class Test {
// 属于类,只加载一次,可以共享 (常量)
public static final String schoolName = "河工大";
public static final String schoolName2;
static{
schoolName2 = "中粮大";
// schoolName2 = "中粮大"; // 第二次赋值,报错了!
}
// 属于对象的! (final基本上不会用来修饰实例成员变量,没有意义!)
private final String name = "王麻子";
public static void main(String[] args) {
// final修饰变量,变量有且仅能被赋值一次。
/* 变量有几种:
局部变量。
成员变量。
-- 1、静态成员变量。
-- 2、实例成员变量。
*/
final int age;
age = 12;
// age = 20; // 第二次赋值,报错了!
System.out.println(age);
final double rate = 3.14;
buy(0.8);
// schoolName = "传智"; // 第二次赋值,报错了!
Test t = new Test();
// t.name = "麻子"; // 第二次赋值,报错了!
System.out.println(t.name);
}
public static void buy(final double z){
// z = 0.1; // 第二次赋值,报错了!
}
}
/**
final修饰方法,方法不能被重写
*/
class Animal{
public final void run(){
System.out.println("动物可以跑~~");
}
}
class Tiger extends Animal{
// @Override
// public void run() {
// System.out.println("老虎跑的贼快~~~");
// }
}
final修饰变量的注意
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
public class Test2 {
public static void main(String[] args) {
// final修饰变量的注意事项:
// 1、final修饰基本类型变量,其数据不能再改变
final double rate = 3.14;
// rate = 3.15; // 第二次赋值,报错
// 2、final修饰引用数据类型的变量,变量中存储的地址不能被改变,但是地址指向的对象内容可以改变。
final int[] arr = {10, 20, 30};
System.out.println(arr);
// arr = null; // 属于第二次赋值,arr中的地址不能被改变
arr[1] = 200;
System.out.println(arr);
System.out.println(arr[1]);
}
}
4、常量
常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。
常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。
常量命名规范:英文单词全部大写,多个单词下划线连接起来。
public class ConstantDemo {
public static final String SCHOOL_NAME = "河工大";
public static final String USER_NAME = "hauter";
public static final String PASS_WORD = "123456";
public static void main(String[] args) {
System.out.println(SCHOOL_NAME);
System.out.println(SCHOOL_NAME);
System.out.println(SCHOOL_NAME);
System.out.println(SCHOOL_NAME);
System.out.println(SCHOOL_NAME);
System.out.println(SCHOOL_NAME);
if(USER_NAME.equals("")){
}
}
}
常量的执行原理
在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量。
这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。
5、枚举
枚举是Java中的一种特殊类型
枚举的作用:“是为了做信息的标志和信息的分类”。
修饰符 enum 枚举名称{
第一行都是罗列枚举类实例的名称
}
public enum Season{
SPRING,SUMMER.AUTUMN,WINTER;
}
枚举的特征:
- 枚举类都是继承了枚举类型: java.lang.Enum
- 枚举都是最终类,不可以被继承。
- 构造器都是私有的,枚举对外不能创建对象。
- 枚举类的第一行默认都是罗列枚举对象的名称的。
- 枚举类相当于是多例模式。
6、抽象类
6.1 抽象类、抽象方法
在Java中abstract是抽象的意思,如果一个类中的某个方法的具体实现不能确定,就可以申明成abstract修饰的抽象方法(不能写方法体了),这个类必须用abstract修饰,被称为抽象类。
修饰符 abstract 返回值类型 方法名称(形参列表);
修饰符 abstract class 类名{}
public abstract class Animal {
private String name;
public abstract void run();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Dog extends Animal{
@Override
public void run() {
System.out.println("狗跑的也很快~~~");
}
}
public class Tiger extends Animal{
@Override
public void run() {
System.out.println("老虎跑的贼溜~~~~");
}
}
public class Test {
public static void main(String[] args) {
Tiger t = new Tiger();
t.run();
Dog t1 = new Dog();
t1.run();
}
}
抽象的使用总结与注意事项
- 抽象类可以理解成类的不完整设计图,是用来被子类继承的。
- 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
6.2 抽象的特征和注意事项
- 有得有失:得到了抽象方法,失去了创建对象的能力。
- 类有的成员(成员变量、方法、构造器)抽象类都具备
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
- 不能用abstract修饰变量、代码块、构造器。
final和abstract是互斥关系
- abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
6.3 模板方法模式
模板方法模式实现步骤
- 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
- 模板方法中不能决定的功能定义成抽象方法让具体子类去实现。
public abstract class Account {
private String cardId;
private double money;
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
/**
模板方法
*/
public final void handle(String loginName , String passWord ){
// a.判断登录是否成功
if("huater".equals(loginName) && "123456".equals(passWord)){
System.out.println("登录成功。。");
// b.正式结算利息
// 当前模板方法知道所有子类账户都要结算利息,但是具体怎么结算,模板不清楚,交给具体的子类来计算
double result = calc();
// c.输出利息详情
System.out.println("本账户利息是:"+ result);
}else{
System.out.println("用户名或者密码错误了");
}
}
public abstract double calc();
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
/**
活期账户
*/
public class CurrentAccount extends Account {
public CurrentAccount(String cardId, double money) {
super(cardId, money);
}
@Override
public double calc() {
// b.正式结算利息
double result = getMoney() * 0.0175; // 结算利息了
return result;
}
}
public class Test {
public static void main(String[] args) {
CurrentAccount acc = new CurrentAccount("ICBC-111", 100000);
acc.handle("hauter", "123456");
}
}
7、接口
7.1 接口概述
接口是一种规范,是接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
接口的定义如下
public interface 接口名{
//常量
//抽象方法
}
接口不能实例化。
接口中的成员都是public修饰的,写不写都是,因为规范的目的是为了公开化。
public class Test {
public static void main(String[] args) {
// 接口不能创建对象!
// SportManInterface s = new SportManInterface();
}
}
public interface SportManInterface {
// 接口中的成员:JDK 1.8之前只有常量 和 抽象方法
// public static final 可以省略不写,接口默认会为你加上!
// public static final String SCHOOL_NAME = "河工大";
String SCHOOL_NAME = "河工大";
// 2、抽象方法
// public abstract 可以省略不写,接口默认会为你加上!
// public abstract void run();
void run();
// public abstract void eat();
void eat();
}
7.2 接口的使用–被实现
接口的用法:
接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类。
修饰符 class 实现类 implements 接口1,接口2,接口3,...{
}
接口可以被类单实现,也可以被类多实现
接口实现的注意事项:
一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。
public interface Law {
void rule(); // 遵章守法
}
public interface SportMan {
void run();
void competition();
}
/**
实现类(子类)
*/
public class PingPongMan implements SportMan , Law{
private String name;
public PingPongMan(String name) {
this.name = name;
}
@Override
public void rule() {
System.out.println(name + "要遵章守法,不能随意外出,酗酒,约会~~~");
}
@Override
public void run() {
System.out.println(name + "必须要跑步训练~~");
}
@Override
public void competition() {
System.out.println(name + "需要参加国际比赛~~");
}
}
public class Test {
public static void main(String[] args) {
PingPongMan p = new PingPongMan("张松松");
p.rule();
p.run();
p.competition();
}
}
7.3 接口与接口的关系–多继承
类和类的关系:单继承。
类和接口的关系:多实现。
接口和接口的关系:多继承,一个接口可以同时继承多个接口。
public interface Law {
void rule(); // 遵章守法
void eat();
}
public interface People {
void eat();
}
public interface SportMan extends Law, People {
void run();
void competition();
}
/**
实现类
*/
// public class BasketballMan implements Law, SportMan, People {
public class BasketballMan implements SportMan{
@Override
public void rule() {
}
@Override
public void eat() {
}
@Override
public void run() {
}
@Override
public void competition() {
}
}
接口多继承的作用
规范合并,整合多个接口为同一个接口,便于子类实现。
7.4 接口的注意事项
- 接口不能创建对象
- 一个类实现多个接口,多个接口中有同样的静态方法不冲突。
- 一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
- 一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可
- 一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
8、面向对象特征–多态
同类型的对象,执行同一个行为,会表现出不同的行为特征。
父类类型对象名称=new子类构造器;
接口对象名称=new 实现类构造器;
/**
父类
*/
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~");
}
}
public class Dog extends Animal{
public String name = "狗名称";
@Override
public void run() {
System.out.println("🐕跑的贼溜~~~~~");
}
}
public class Tortoise extends Animal{
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("🐢跑的非常慢~~~");
}
}
public class Test {
public static void main(String[] args) {
// 目标:先认识多态的形式
// 父类 对象名称 = new 子类构造器();
Animal a = new Dog();
a.run(); // 方法调用:编译看左,运行看右
System.out.println(a.name); // 方法调用:编译看左,运行也看左,动物名称
Animal a1 = new Dog();
a1.run();
System.out.println(a1.name); // 动物名称
}
}
多态中成员访问特点
- 方法调用:编译看左边,运行看右边。
- 变量调用:编译看左边,运行也看左边。
(多态侧重行为多态)多态的前提
- 有继承/实现关系;
- 有父类引用指向子类对象;
- 有方法重写。
在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
Animal a =new Dog0;
a.run();//后续业务行为随对象而变,后续代码无需修改
定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
多态下会产生的一个问题:多态下不能使用子类的独有功能
- 自动类型转换(从子到父):子类对象赋值给父类类型的变量指向。
- 强制类型转换(从父到子) 此时必须进行强制类型转换:子类对象变量=(子类)父类类型的变量
作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
Animal t = new Tortoise();
Dog d = (Dog)t; //出现异常ClassCastException
/**
父类
*/
public class Animal {
public String name = "动物名称";
public void run(){
System.out.println("动物可以跑~~");
}
}
public class Dog extends Animal {
public String name = "狗名称";
@Override
public void run() {
System.out.println("🐕跑的贼溜~~~~~");
}
/**
独有功能
*/
public void lookDoor(){
System.out.println("🐕在看🚪!!!");
}
}
public class Tortoise extends Animal {
public String name = "乌龟名称";
@Override
public void run() {
System.out.println("🐢跑的非常慢~~~");
}
/**
独有功能
*/
public void layEggs(){
System.out.println("🐢在下蛋~~~");
}
}
/**
多态形式下的类中转换机制。
*/
public class Test {
public static void main(String[] args) {
// 自动类型转换
Animal a = new Dog();
a.run();
// a.lookDoor(); // 多态下无法调用子类独有功能
// 强制类型转换:可以实现调用子类独有功能的
Dog d = (Dog) a;
d.lookDoor();
// 注意:多态下直接强制类型转换,可能出现类型转换异常
// 规定:有继承或者实现关系的2个类型就可以强制类型转换,运行时可能出现问题。
// Tortoise t1 = (Tortoise) a;
// 建议强制转换前,先判断变量指向对象的真实类型,再强制类型转换。
if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEggs();
}else if(a instanceof Dog){
Dog d1 = (Dog) a;
d1.lookDoor();
}
System.out.println("---------------------");
Animal a1 = new Dog();
go(a1);
}
public static void go(Animal a){
System.out.println("预备~~~");
a.run();
// 独有功能
if(a instanceof Tortoise){
Tortoise t = (Tortoise) a;
t.layEggs();
}else if(a instanceof Dog){
Dog d1 = (Dog) a;
d1.lookDoor();
}
System.out.println("结束~~~~");
}
}
Java建议强转转换前使用instanceof判断当前对象的真实类型,再进行强制转换
变量名instanceof真实类型
判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则源回true,反之。
9、内部类
9.1 内部类概述
内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。
public class People{
public class Heart{
}
}
内部类的使用场景、作用
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
- 内部类通常可以方便访问外部类的成员,包括私有的成员。
- 内部类提供了更好的封装性,内部类本身就可以用private protectecd等修饰,封装性可以做更多控制
9.2 静态内部类(了解)
有static修饰,属于外部类本身。
它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
public class Outer{
//静态成员内部类
public static class Inner{
}
}
静态内部类创建对象的格式
外部类名.内部类名 对象名 = new 外部类名.内部类构造器;
Outer.Inner in = new Outer.Inner();
静态内部类中可以直接访问外部类的静态成员,外部类的静态成员只有一份可以被共享访问。
静态内部类中不可以直接访问外部类的实例成员,外部类的实例成员必须用外部类对象访问。
9.3 成员内部类(了解)
无static修饰,属于外部类的对象。
JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员了。
public class Outer{
//成员内部类
public class Inner{
}
}
成员内部类创建对象的格式:
外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器();
Outer.Inner in = new Outer().new Inner();
成员内部类中可以直接访问外部类的静态成员,外部类的静态成员只有一份可以被共享访问。
成员内部类的实例方法中可以直接访问外部类的实例成员,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员
9.4 局部内部类(了解)
局部内部类放在方法、代码块、构造器等执行体中。
局部内部类的类文件名为:外部类$N内部类.class。
9.5 匿名内部类(重点)
本质上是一个没有名字的局部内部类,定义在方法中、代码块中、等。
作用:方便创建子类对象,最终目的为了简化代码编写。
new 类|抽象名|接口名(){
重写方法;
};
/**
匿名内部类的形式和特点。
*/
public class Test1 {
public static void main(String[] args) {
Animal a = new Animal(){
@Override
public void run() {
System.out.println("老虎跑的块~~~");
}
};
a.run();
}
}
//class Tiger extends Animal{
// @Override
// public void run() {
// System.out.println("老虎跑的块~~~");
// }
//}
abstract class Animal{
public abstract void run();
}
/**
匿名内部类的使用形式(语法)
*/
public class Test2 {
public static void main(String[] args) {
Swimming s = new Swimming() {
@Override
public void swim() {
System.out.println("学生快乐的自由泳🏊");
}
};
go(s);
System.out.println("--------------");
Swimming s1 = new Swimming() {
@Override
public void swim() {
System.out.println("老师泳🏊的贼快~~~~~");
}
};
go(s1);
System.out.println("--------------");
go(new Swimming() {
@Override
public void swim() {
System.out.println("运动员🏊的贼快啊~~~~~");
}
});
}
/**
学生 老师 运动员可以一起参加游泳比赛
*/
public static void go(Swimming s){
System.out.println("开始。。。");
s.swim();
System.out.println("结束。。。");
}
}
interface Swimming{
void swim();
}
特点总结:
- 匿名内部类是一个没有名字的内部类。
- 匿名内部类写出来就会产生一个匿名内部类的对象。
- 匿名内部类的对象类型相当于是当前new的那个的类型的子类类型。
- 匿名内部类可以作为方法的实际参数进行传输。