从这个阶段开始安装IDEA使用
在idea中,当我们run一个文件时,会先编译成.class之后再运行,src文件底下存放的是源码文件,运行过后的out文件夹下存放的是编译后的.class文件。
- IDEA常用快捷键,通过设置-按键映射-自行搜索一些常用的完成功能的快捷键
- alt + insert 自动在类中生成构造器
- CTRL+alt+L自动优化代码,即在没有打空格的地方不美观拥挤的地方给你打上空格调整
- 查看一个类的层级关系 ctrl + h
- 将光标放在一个方法上,输入ctrl + b 可以定位到方法
- 通过在new 类名()后加.var后按回车可以自动分配变量名
- 文件-设置-编辑器-实时模板可以查看系统分配的模板,也可以自己设置模板
包
包的三大作用:
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
包的本质:
创建不同的文件夹/目录来保存类文件
包的命名:
- 规则:只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
- 规范:一般是小写字母+小圆点。例:com.公司名.项目名.业务模块名 :com.sina.crm.user
常用的包:
- java.lang. * lang包是基本包,默认引用,不需要再引入比如使用Math
- java.util. * util包,系统提供的工具包,工具类,比如使用Scanner
- java.net. * 网络包,做网络开发
- java.awt. * 做java的界面开发
比如import.java.util.Scanner这句话表示引入util包下的Scanner类;而import.java.util.*这句话表示把util包下的所有类都导入(建议还是需要使用到哪个类就导入哪个类,不建议使用*的方式导)
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package。
- import指令位置放在package的下面,在类定义前面可以有多句且没有顺序要求。
package com.use; import java.util.Arrays; public class test { public static void main(String[] args) { int[] arr = {11,4,6,8}; Arrays.sort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } }
访问修饰符
Java提供四种访问控制修饰符,用于控制方法和属性的访问权限:
- 公开级别:public,对外公开
- 受保护级别:protected,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包中的类公开
- 私有级别:private,只有类本身可以访问,不对外公开
封装
把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
写setXxx和getXxx太慢,可以用快捷键alt+insert选择get与set,在set中完成对一些属性的控制校验环节设置,增加业务逻辑。
将构造器结合setXxx使用,可以在构造器中直接写set方法。下面是一段有关封装使用的代码示例:
public class test {
public static void main(String[] args) {
Person person = new Person();
person.setName("jackrose");
person.setAge(18);
person.setSalary(5000);
System.out.println(person.info());
System.out.println(person.getSalary());
Person person1 = new Person("jack", 18, 3000);
}
}
class Person{
public String name;
private int age;
private double salary;
public Person() {
}
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
this.setName(name);//这里的this都可以省去
this.setAge(age);
this.setSalary(salary);
}
public void setName(String name) {
if(name.length() >= 2 && name.length() <= 6) {
this.name = name;
}else{
System.out.println("名字的长度要在2-6个字符之间");
this.name = "无名人";
}
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
//在set中加入验证以及一些控制设置
if(age >= 1 && age <= 120){
this.age = age;
}else{
System.out.println("你设置的年龄不对,需要在1-120,给默认年龄18");
this.age = 18;
}
}
public double getSalary() {
//可以增加对当前对象的权限判断
//比如增加密码验证之类的
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String info() {
return "信息为 name=" + name + ", age=" + age + ", salary=" + salary;
}
}
继承
主要作用就是解决代码的复用性。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
//基本语法
class 子类 extends 父类{
}
- 子类就会自动拥有父类定义的属性和方法。
- 父类又叫超类,基类。
- 子类又叫派生类。
继承细节问题:
- 子类继承了所有的属性和方法,但是私有属性和方法不能再子类直接访问,要通过公共的方法(在父类中定义)去访问。
- 子类必须调用父类的构造器,完成父类的初始化。
- 当构建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。当希望指定去调用父类的某个构造器时,也要显式的调用一下super()。
- super在使用时,需要放在构造器第一行。
- this在使用时,也需要放在构造器第一行,所以this和super不能共存在一个构造器。
- java所有类都是Object的子类(输入ctrl+H可以看到类的继承关系)。
- 父类构造器的调用不限于直接父类(上一级父类),将一直往上追溯直到Object类(顶级父类)。
- 子类最多只能继承一个父类(直接继承),即java中是单继承制。那么如何让A类继承B类和C类:让A类继承B类之后,再让B类去继承C类,这样A类就可以在继承B的同时又继承C了。
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系。
继承的本质分析:
执行上述代码,创建一个son子类时内存布局运行过程如图,细节多看:
看以下代码,思考会输出什么
package com.use.extend_.exercise;
import jdk.nashorn.internal.ir.CallNode;
public class Exercise01 {
public static void main(String[] args) {
B b = new B();
}
}
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");
}
}
输出的结果是:首先进去B类中的无参构造器,有this赋值即调用本类的B(String name)构造器,但是因为有隐藏的super,所以会去先调用父类的无参构造器,即A类中的无参构造器,所以先输出a,然后回到B类中(String name)构造器,执行到输出 b name ,然后退回到B类的无参构造器执行输出 b 语句。这里注意B类中的无参构造器没有super因为super和this不能共存,所以这里的super存在于B类的第二个构造器中。
super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器。
- 访问父类的属性,但不能访问父类的private属性。语法是super.属性
- 访问父类的方法,但不能访问父类的private方法。语法是super.方法
- 访问父类的构造器,只能放在构造器的第一句,只能出现一句。语法是super(),必须在构造器中使用
- 使用super调用遵循就近原则,在多个基类中有同名的成员,如果在”近“的父类中找到了就直接调用,找不到再往上一级,并且要遵循访问权限问题,如果遇到private的方法或属性,不是选择跳过继续去上一级寻找而是会抛出异常。
例:如果在B类中调用cal方法,即在B类中写cal(),那么
super和this的比较
方法重写/覆盖(override)
简单地说就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法(这里的子类和父类不一定只是一层的关系,也可以隔着好几层级)。
- 子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样。
- 子类方法的返回类型和父类方法的返回类型一样,或者是父类返回类型的子类,比如父类的返回类型是Object,子类方法返回类型是String。如父类中的方法是public Object getInfo(){},子类中public String getInfo(){}同样也可以构成方法重写。
- 子类方法不能缩小父类方法的访问权限(访问权限:public > protected > 默认 > private)如父类中方法 void say(){},那么子类中使用public void say(){}也是可以构成方法重写的,但是如果把public换成private就不行了
方法重写和方法重载的比较
多态(多看!!)
方法或对象具有多种形态,多态是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
方法重载体现多态,对同名的方法传入不同的参数就会调用不同的方法,体现多态
方法重写也体现多态。对同名的方法,不同的对象去调用会自动定位到不同的类中,体现多态
多态的具体体现
对象的多态
- 一个对象的编译类型和运行类型可以不一致,例:Animal animal = new Dog(),animal的编译类型是Animal,运行类型是Dog
- 编译类型在定义对象时就确定了,不能改变
- 运行类型是可以变化的,例:animal = new Cat();此时animal的运行类型变成了Cat,但是它的编译类型仍是Animal
- 编译类型看定义是,看等号的左边,运行类型看等号的右边
多态的注意事项和细节问题
- 多态的前提是:两个对象(类)存在继承关系
- 多态的向上转型:
- 本质是父类的引用指向了子类的对象。
- 语法是 父类类型 引用名 = new 子类类型();
- 特点是 编译类型看左边,运行类型看右边。
- 可以调用父类中的所有成员但是需要遵守访问权限,不能调用子类中特有成员,最终的运行效果看子类的具体实现。
- 多态的向下转型:
- 语法:子类类型 引用名 = (子类类型) 父类引用;例: Cat cat = (Cat) animal;就把编译类型从Animal强转成了Cat。
- 只能强转父类的引用,不能强转父类的对象。
- 要求父类的引用必须指向的是当前目标类型的对象。例:我们上面举的例子,把编译类型从Animal强转成了Cat的前提是,我们原先的animal指向的就是堆里面的Cat对象(父类引用),即最开始那句Animal animal = new Cat();我们执行强转操作之后,得到了一个cat对象也是指向堆里的Cat对象,我们只能把当初指向猫的animal对象强转成猫,而不能强转成其他的类型,比如我们不能写Dog dog = (Dog) animal;会抛出异常
- 当向下转型后,就可以调用子类类型中所有的成员。
注意:属性没有重写一说,如果需要输出的是属性,那么依旧要看等号左边的编译类型,如果调用的是方法才需要看等号右边的运行类型,这里强调一下:
- 属性(成员变量)是类的状态信息,描述了对象的特征和属性
- 属性的类型、大小和布局在编译时就确定了,因此在编译时就可以确定属性的相关信息
- 方法(成员函数)是类的行为操作,定义了对象可以执行的操作
- 方法的实现代码是在运行时执行的,因此在编译时只能确定方法的声明,具体的方法实现由运行时决定
- instanceof 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型
Java的动态绑定机制
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明就在哪里使用
看上图代码,当我们在主方法中运行以下代码运行结果会是怎样?
A a = new B();
System.out.println(a.sum());
System.out.printlb(a.sum1());
我们先来分析这三行代码,第一句表示的是一个向上转型,对象a的编译类型是A,运行类型是B,那么输出a.sum()即方法调用时,会去运行类型中即B类中寻找有无方法sum,B类中有所以直接调用输出20+20=40,那么同理下一句的输出也是去B类中寻找的方法,调用结果是20+10=30。但是,如果我们把图片中B类中的sum与sum1方法都注释掉,即B类中不存在这两个方法了,那么输出结果又会如何呢?首先因为运行类型是B,所以出现方法调用依旧是先去B类中寻找,发现没有sum方法,去B类的父类中即A类中寻找,找到了sum方法,进入使用发现调用了方法getI,但是B类中也存在方法getI,那么调用的是哪个方法呢,这个就牵扯到了Java的动态绑定机制了,按照上面我们给出的第一条绑定机制:当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定,a的运行类型是B,调用a的方法时会和a的运行类型B绑定,所以即使是在A类中遇到了getI方法的调用,此时依旧要先去调用B类中的getI方法,B中getI调用结果是20,返回到A中的sum方法结果是20+10=30,同理下一句调用sum1方法,B中没有此方法去B的父类A类中寻找,找到了sum1,调用需要输出i+10,这里的i的取值怎么取?根据绑定机制第二条:当调用对象属性时,没有动态绑定机制,哪里声明就在哪里使用,我们需要知道i的值,直接就在A类中寻找即可,所以输出是10+10=20。
多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。看课堂案例
多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型。主要看课堂案例
Object类详解
-
equals方法:
==和equals的对比(面试题):==是一个比较运算符
- ==既可以判断基本类型,又可以判断引用类型
- 如果判断的是基本类型,判断的是值是否相等
- 如果判断的是引用类型,那么判断的是地址是否相等,即判定的是不是堆里面的同一个对象
equals是Object类中的一种方法,只能用来判断引用类型,默认判断的是地址是否相同,但是在子类中会得到重写,比如在Integer以及String中就是判断“内容”是否相等,系统自动重写了equals方法。例:
-
hashcode方法
两个引用,如果指向的是同一个对象,则哈希值肯定是一样的。哈希值主要根据地址号来的,不能完全将哈希值等价于地址
-
toString方法
默认返回:全类名(包名+类名)+@+哈希值的十六进制,子类往往会重写toString方法,用于返回对象的属性信息,默认的alt+insert快捷键重写一般是把对象的属性值输出。当直接输出一个对象时,toString方法会被默认的调用
-
finalize方法
当对象被回收时(比如把一些对象置成null时),系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作。我们在实际开发中,几乎不会运用finalize,更多的是为了应付面试
-
断点调试
可以用断点调试一步一步的看源代码执行的过程,从而发现错误的所在。在断点调试过程中,是运行状态,是以对象的运行类型来执行的。
断点调试的快捷键:F7:跳入方法内;F8:逐行执行代码;shift+F8:跳出方法回到原来的位置;F9:resume,执行到下一断点