Java-Day-9(IDE工具 + 包 + 访问修饰符 + 面向对象编程的三大特征)—— Java 中级
Java-Day-9
IDE ( 集成开发环境 ) 工具
Intellij IDEA
Eclipse
( 以上两种工具的安装会另行编写随笔 )
- IDEA代码常用快捷键
- 配置:File — settings — Keymap — 搜索、自查 — 右键 Reset Shortcuts 删除已有 — 右键 Add Keyboard Shortcut 添加新的快捷键
- 复制当前行默认:ctrl + D
- 补全代码:alt + /
- 注释增删:ctrl + /
- 删除当前行 ( Delete Line )
- 快速运行程序 ( Run )
- 快速生成构造器:alt + insert
- 右键 Generate — Constructor
- 查看一个类的层级关系:ctrl + H ( 继承常用 )
- 直接定位到方法:将光标放在一个方法上 + ctrl + B
- 自动分配变量名:编写 new 对象后面加 .var + 回车
- 快速格式化代码 ( Reformat Code ):ctrl + alt + L
- 快速导入所需的类:
- File — se ttings — Editor — General — Auto Import — 勾选 Add unambiguous imports on the fly 和 Optimize imports on the fly — 点击 OK,勾选如图:
- 然后就可以使用 alt + enter 快捷,上下键选中后回车确定选择
- File — se ttings — Editor — General — Auto Import — 勾选 Add unambiguous imports on the fly 和 Optimize imports on the fly — 点击 OK,勾选如图:
- 模板快捷键
- 查看:File — settings — editor — Live templates
- 增加自己的模板:+ 号键右键 — Live Template — 依次编写模板名、模板 — Define 选择应用范围 ( java ... )
- sout、fori
包
- 作用
- 区分相同名字的类 ( 同一文件夹不能有两个重名包 )
- 当类很多的时候能够更好的管理
- 控制访问范围
- 基本语法
- package com.qut
- package:关键字,表示打包
- com.qut:表示包名
- package com.qut
- 本质分析
- 实际就是创建不同的文件夹/目录来保存类文件
- 包的命名
- 只能是数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
- 一般都是小写字母 + 小圆点
- com.公司名.项目名.业务模块名
- 常用的包
- java.lang.* :lang 包是基本包,默认引入
- java.util.* :util 包,系统提供的工具包,
- java.net.* :网络包,网络开发
- java.awt.* :是做 java 的界面开发,GUI
- 包的导入
- 语法
- import 包;
- 加 * :将此包下的所有类都导入
- 建议使用哪个就导入哪个包,少用 *
- 语法
- 注意细节
- package 的作用是声明当前类所在的包,要放在 class 的最上面,一个类中最多只有一句 package
- import 指令,放在 package 的下面,在类定义前面,可以有多句且没有顺序要求
访问修饰符
-
作用
- java 提供四种访问修饰符号,用于控制方法和属性的访问权限 ( 范围 )
-
四种访问修饰符
-
公开级别:用 public 修饰,对外公开
-
受保护级别:用 protected 修饰,对子类和同一个包中的类公开
-
默认级别:没有修饰符号,向同一个类公开
-
私有级别:用 private 修饰,只有类本身可以访问,对外不公开
-
( 同包无 private,不同包只有 public )
访问级别 访问控制修饰符 同类 同包 子类 不同包 公开 public √ √ √ √ 受保护 protected √ √ √ 默认 没有修饰符号 √ √ 私有 private √
-
-
注意细节
- 修饰符可以用来修饰类中的属性、成员方法以及类
- 注意:只有默认和 public 才能修饰类
- 成员方法的访问规则和属性完全一样
面向对象编程的三大特征
封装
( encapsulation )
-
把抽象出的数据 ( 属性 ) 和对数据的操作 ( 方法 ) 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作 ( 方法 ) 才能对数据进行操作
-
封装的好处
- 可以实现隐藏细节
- 把方法写好,使用时传入参数调用方法即可,调用者无需知道内部如何
- 可以对数据进行验证,保证安全合理
- 可以实现隐藏细节
-
封装的实现步骤
-
对属性进行私有化 private,不能直接修改属性
-
( 以下 set 和 get 方法可以在开发软件里右键Generate 自动加入已有属性的方法 )
-
提供一个公共的 set 方法,用于对属性判断并赋值
public void setXxx(类型 参数名){ // 可以编写数据验证的业务逻辑语句,例if(参数名.length() < 2)... 属性 = 参数名; }
-
提供一个公共的 get 方法,用于获取属性的值
public 数据类型 getXxx(){ // 权限判断 return xx; }
-
还有是将构造器和 set 方法结合
// 提供两个构造器,一个无参构造器 // 无参构造器就正常使用提供的 get 和 set 方法来赋值 public Test() {} // 一个有参构造器,创建即赋值(属性私有且有set方法时常用) public Test(String name, double pwd, int age) { this.setName(name); this.setPwd(pwd); this.setAge(age); }
-
继承
( extends )
-
作用
- 解决代码复用的问题,当多个类存在相同的属性和方法时,可以从这些类中抽出父类,在父类中定义这些相同的属性和方法,所有的子类就不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可
- 此时父类 ( 基类 ) 内的是共有属性和共有方法,各子类 ( 派生类 ) 内是各自的特有属性和特有方法,子类下还可以再有一个子类,继承的就是父类的和第一层子类的所有属性和方法
-
基本语法
- class 子类 extends 父类
-
注意细节
-
子类继承了父类的所有属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共的方法去访问 ( 私有的属性和方法放进提供了的公共的方法里 — 同类直接用,相当于转了个弯实现了对私有的调用 )
-
子类必须调用父类的构造器,完成父类的初始化
所以父类只有有参构造器时,刚创建好子类会报错 ( 默认的 super 找不到父无参构造器了 )
public class B extends A{ public B() { // 默认调用父类的无参构造器,不写出来就是自带的一行代码 super(); // 运行后子类的构造器和父类的无参构造器都被执行了 }
}
- 当创建子类对象时,不管使用子类的哪个有参或无参的构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。 ```java // 若是父类仅一个有参构造器如下 public A(String name, int age){ System.out.println("不声明无参构造器的话,无参就被覆盖,只有父类有参构造器被调用") } // 子类不写super就会报错,需要加上以下代码 public class B extends A{ private int id; // 静态定死了父类构造器的赋值 public B() { super("zhu", 20); System.out.println("子类的构造器和父类的有参构造器都被调用"); } // 活用动态型,在main里调用时再赋值 public B(String name, int age, int id){ super(name,age); this.id = id; } }
-
如果希望指定去调用父类的某个构造器,则显式的调用一下,即:super ( 所需的参数列表 )
-
super 只能在构造器中使用,在使用时,必须放在构造器第一行,代码先走父,再到子
-
super() 和 this() 都只能放在构造器的第一行,所以这两个语句不能同时出现在同一个构造器里面
-
java 所有类都是 Object 类的子类,Object 是所有类的基类,可 ctrl + H 查看 类的层级关系
-
父类构造器的调用不限于直接父类,将一直向上追溯到 Object 类 ( 顶级父类 — 只不过隐藏起来了 )
-
java 是单继承机制,即子类最多只能继承一个父类 ( 指直接继承 ),间接的父类继承不限制 ( 即让父类再继承所需的类 )
-
不能滥用继承,如员工类继承动物类等等,所以子类和父类之间必须满足符合常理的逻辑关系
-
-
继承的本质分析
- jvm 内继承的内存布局,例 son 继承 father,father 继承 grandpa
- Son son = new Son() 时,最先在方法区加载 Object 类,再加载Grandpa 类,再加载 Father 类,最后再加载 Son 类,而且相互之间两两有着子指向父的关联关系
- 然后因 new 在堆里开辟了一块拥有地址的空间,其中又划分了三个空间,分别存放 Grandpa 的所有属性、 father 的所有属性和 Son 的所有属性,三个空间相互之间各自独立,所以各类可有重复的属性名,重复属性名若是 String 类型,则各自指向常量池不同的地址来存值
- son 对象名在栈中存有地址,一个指向堆内真正对象的地址
- 若是 son、father、grandpa 类含同类型 name 属性,那么在 main 里是先访问子类的这个属性,子类若是没有就访问父的这个属性,借此逻辑没有就找上一级,直到 Object,都没有则报错
- 若是某属性在 son 里没有,father 里面是私有 private 修饰符,则就直接访问不到,直接报错,不会跳到 grandpa 里面再找有没有。
- jvm 内继承的内存布局,例 son 继承 father,father 继承 grandpa
-
小练习
class A{ A(){ System.out.println("a"); } A(String name){ System.out.println("a name"); } } class B extends A{ B(){ this("abc"); System.out.println("b"); } B(String name){ System.out.println("b name"); } } // main 中 B b = new B(); // 输出:a // b name // b
super关键字
-
super 代表父类的引用,用于访问父类的属性、方法、构造器
-
基本语法
-
访问父类的属性,但不能访问私有属性
// 在子类的普通方法里 System.out.println(super.n1 + " " + super.n2);
-
访问父类的方法,但不能访问私有方法
// 在子类的普通方法里 public void test(){ super.test1(); super.test2(); }
-
访问父类的构造器,只能放在构造器的第一句
// 详见继承所讲 public B(){ super(); }
-
-
便利细节
- 分工明确,父属性由父类初始化,子类的属性由子类初始化
- 当子类中有和父类中成员 ( 属性和方法 ) 的重名时,为了访问父类的成员,必须通过 super,但若是无重名,super、this、直接访问效果相同
- 例:在子类中 this.sum() 等价于 sum(),查找 sum() 方法逻辑如下
- 调用方法时,如果本类有就直接调用
- 没有的话就去父类、父类的父类找... 直到 Object
- 继承中但凡能调用的话 ( 非私有 ) 都是直接调用就可
- this:当前对象
- super:子类中访问的父类对象
- 例:在子类中 this.sum() 等价于 sum(),查找 sum() 方法逻辑如下
- 在子类中 super.sum() 就跨过了子类直接从父类开始找 sum() 方法,注意 super 的访问不限于直接父类,遵循就近原则,逻辑同上的 this
方法重写 / 覆盖
( override )
-
简单来说就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类 ( 甚至父类的父类,并不单一指一级父类 ) 的方法
-
注意细节
- 参数列表、方法名要完全一样
- 除了参数列表和方法名外,返回类型也要一样,或者是父类返回类型的子类
- 父类返回是 Object 类型,子类返回类型是 String 也可以重写 ( string 是 object 的子类 )
- 已知 class BBB extends AAA 情况下 ( 不管是在父类还是子类声明的 ),父类方法返回类型可以是 AAA,其子类就可以返回 AAA / BBB 的类型来完成重写
- 子类方法不能缩小父类方法的访问权限
- 父类默认访问权限时,子类的访问权限可以是 public、protected,不能是 private
-
小练习
-
定义了父一方法,返回父类的私有属性值,在子类中重写父类的这一方法,返回的值是在父类基础上再加上子类的一些私有属性值
// 父类 private int id; public String say(){ return "id=" + id; } // 子类,活用super private String name; public String say(){ return super.say() + "name=" + name; } // main里new子类和父类,调用同一个方法但是返回值:子比父多一个name
-
-
重载和重写
范围 方法名 形参列表 返回类型 修饰符 overload 本类 必须一样 类型、个数、顺序至少一个不同 无要求 无要求 override 父子类 必须一样 相同 父子同,或子类返回类型是父返回类型的子类 子类不能缩小访问权限
多态
( polymorphic )
-
传统方法:( 代码复用性不高,而且不利于代码维护 )
-
Food 作为 Fish 和 Bone 的父类,Food 构造器仅含食物种类名这一私有属性的 this.name = name 语句
-
Animal 作为 Dog 和 Cat 的父类,也同样父类只含动物名这一私有属性的 this.name = name 语句
-
Master 作为主人,构造器也只含人名这一私有属性的 this.name = name 语句,新编写喂食方法给狗喂骨头,给猫喂鱼 ( 方法的重载 )
-
main 里 new 人、狗、骨、猫和鱼,调用喂食方法,将对象写进参数列表
public void feed(Dog dog, Bone bone){ System.... } public void feed(Cat cat, Fish fish){ System... } ......
-
如此下来,若是再添加动物和食物,会有更多喂食方法要再编写,所以要使用多态
-
-
多态,就是多种状态,方法或对象具有多种状态,是建立在封装和继承之上的
-
方法重载体现多态
-
传入不同的参数,就会调用不同的 sum,这就是种多态的体现
// class里 public int sum(int n1, int n2){ return n1 + n2; } public int sum(int n1, int n2, int n3){ return n1 + n2 + n3; }
-
-
方法重写体现多态
-
School s = new School(); Student st = new Student(); s.say(); st.say();
-
-
对象的多态
- 一个对象的编译类型和运行类型可以不一致
- Animal animal = new Dog();
- Dog 是 Animal 的子类,能用父类的引用指向子类的一个对象
- 编译类型是 Animal,运行类型是 Dog
- 编译类型在定义对象时就确定了,不能改变
- animal 编译类型是 Animal 不可改变
- 运行类型是可以变化的
- animal = new Cat();
- 编译类型仍然是 Animal,运行类型却变成了 Cat,指向的堆里的真正对象做了更改
- 编译类型看定义时等号的左边,运行类型在等号的右边
- 一个对象的编译类型和运行类型可以不一致
-
对象的多态的体现:披着羊皮的狼,狼是运行对象,但从栈里表面上看是羊,狼脱下来给狈穿,就又变成披着羊皮的狈了,羊皮 ( animal ) 重复利用
-
传统方法进行多态改善:
// 所有feed都简化为了这么一个方法 public void feed(Animal animal, Food food){ System... } // main里 Dog dog = new Dog("博美犬"); Bone bone = new Bone("小骨头"); master.feed(dog, bone);
- animal 编译类型是 Animal,可以指向所以的 ( 接收 ) Animal 子类的对象
- food 编译类型是 Food,可以指向所有的 ( 接收 ) Food 子类的对象
-
注意细节
-
多态前提为两个对象 ( 类 ) 存在继承关系
-
多态是向上转型
- 本质:父类的引用指向了子类的对象 ( 栈里是 Animal ( Object ),堆里是 Dog )
- 语法:父类类型 引用名 = new 子类类型();
-
可以调用父类的所有成员 ( 遵守访问权限 )
Animal animal = new Dog(...); //1 animal.所有的权限允许的父中的方法 // 编译类型是Animal,运行类型是Dog
-
不能调用子类中特有成员( 除非向下转型 )
// animal.Dog中特有的方法 ——> 报错
- 注意:因为在编译阶段,能调用哪些成员是由编译类型 ( 左边的 Animal ) 来决定的
-
最终运行结果看子类的具体实现
- 但运行结果是看运行类型 ( 右边的 ),方法是从运行类型开始按照就近子到父逻辑来找的
-
编译看左,能用的成员看左,实际运行看右,从右的子的方法开始找具体实现代码,动态的
-
多态的向下转型
-
语法: 子类类型 引用名 = ( 子类类型 ) 父类引用
Dog dog = (Dog) animal; //2 dog.Dog中特有的方法 //就不会报错了 // 此时编译类型是Dog,运行类型也是Dog了
-
只能强转父类的引用,不能强转父类的对象,即堆部分的真正的对象不能强转
-
要求父类的引用必须指向的是当前目标类型的对象
// 原来animal在创建是就是指向Dog的,即父类的引用指向着堆里的Dog // 现在强转了,变成Dog引用指向堆里的Dog,变成栈里两个都指向着这个堆 // 此时再 Cat cat = (Cat) animal; //3 // 报错,因为new时堆里的是Dog,指向Dog的父类引用animal,怎么能转成Cat宁
-
当向下转型后,就可以调用子类类型中所有成员了
-
-
属性没有重写之说
// Animal里name = 动物 // Dog里name = 狗 Animal animal = new Dog(); System.out.println(animal.name); // 输出为 动物 // 属性方面不能与方法同逻辑,属性的话直接看左边编译类型,属性就是编译类型的属性
-
instanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或者XX类型的子类型
Dog dog = new Dog(); System.out.println(dog instanceof Dog); // true System.out.println(dog instanceof Animal); //true Animal animal = new Dog(); System.out.println(animal instanceof Dog); // true System.out.println(animal instanceof Animal); //true // 判断的是对象的运行类型,右边的,instanceof语句:左边(对象名)是否小于等于右边(对象) Object obj = new Object(); System.out.println(obj instanceof Animal); // false String str = "狗"; System.out.println(str instanceof object); // true
-
-
小练习
Object obj = "Hello"; // 可以,向上转型 String str = (String)obj; // 可以,向下转型 System.out.println(str); // Hello Dog dog = new Dog(); Animal animal = dog; // 向上转型 System.out.println(animal == dog); // true System.out.println(animal.name); // 动物 animal.say(); // 我是狗 // 属性看左边编译类型,方法看右运行类型
java 的动态绑定机制
( dynamic binding ) !!
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 例:即便因继承用了父类的方法,再遇到某方法时还是第一时间到运行类型 ( 子类 ) 里找
- 当调用对象属性时,注意属性没有动态绑定机制,哪里声明,就在哪里使用
- 例:除非在声明的类 ( 要调用属性的方法所在的类 ) 中没有此属性,才再依继承去父类找
class A{
public int i = 10;
public int sum(){
return geti() + 10;
}
public int geti(){
return i;
}
public int sum1(){
return i + 10;
}
}
class B extends A{
public int i = 20;
public int geti(){
return i;
}
}
// main里
A a = new B();
System.out.println(a.sum()); // 因动态绑定机制先去B找,B没有sum()就依继承机制到A找,用A的sum方法,但其方法内的geti是指B类的geti(方法的动态绑定机制又体现),返回的i属性是geti所在类里的i(属性无动态绑定),即20,所以输出结果是30
System.out.println(a.sum1()); // B没有sum1()就到A找,用A的sum1方法,因为属性没有动态绑定机制,所以就在sum1所在的类中找属性i,即10,所以输出结果是20
//若B里没有声明属性i,则在a.sum()时就是A的sum方法、B的geti方法、A的i属性 —— (动态绑定+继承机制),输出结果为20
-
小练习
// 在employee里构造器this.salary = salary,写方法A算年基本工资12*salary,在员工Worker和管理员Manager的类中重写A方法,加上各自的奖金bonus,员工Worker类中是work方法,管理员Manager类中是manage方法 // 主类public class Person里 public static void main(String[] args){ Worker zhang = new Worker(...); Manager zhu = new Manager(...); Person p = new Person(); p.showAnnual(zhang); p.showAnnual(zhu); } // 获取对应员工的年工资 public void showAnnual(Employee e){ System.out.println(e.A); } // 获取各员工在干什么 public void testWork(Employee e){ if(e instanceof Worker){ ((Worker) e).work(); } else if(... Manager){ ((Manager) e).manage(); } else...... }
多态数组
-
数组定义类型为父类类型,里面保存的实际元素为子类型
- idea 打开 structure 查看,点击设置可 move 到右边直观看各类方法
// 父类Person有一个say方法:输出Person中所有的属性:name // 子类Student extends Person,特有属性score,类中重写say方法 public String say(){ return super.say() + "成绩" + score; } // 子类Teacher extends Person,特有属性salary,重写say似上 // main中 Person[] persons = new Person[]; person[0] = new Person("zhu"); person[1] = new Student("zhu",20); person[2] = new Teacher("zhu",7000); for(int i = 0; i < persons.length; i++){ // 编译类型Person,运行类型根据实际情况来判断 System.out.println(persons[i].say()); // 动态绑定机制 } // 若子类Student、Teacher都再加一个特有方法,想要加上特有的方法就: for(int i = 0; i < persons.length; i++){ System.out.println(persons[i].say()); if(persons[i] instanceof Student){ Student student = (Student)person[i]; //向下转型 student.特有方法; } else if(...... Teacher){ ...... } else if(...... Person){ // 不操作 } else { System.out.println("类型有误"); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App