写在前面:Java基础系列文章
Java基础要点笔记1(尚硅谷)
Java基础要点笔记2面向对象(尚硅谷)
Java编程之异常处理
Java编程之多线程
Java编程之常用类
Java编程之枚举类&注解
Java编程之Java集合
Java编程之泛型
Java编程之IO流
Java编程之网络编程
Java编程之反射机制
Java编程之Java8&9&10&11新特性
十大排序算法
关于设计模式
Java基础
Java面向对象编程(尚硅谷)
1. Java类及类成员(属性、方法、构造器、代码块、内部类)
1.1 概述
类就是一个抽象的概念,对象就是一个具体的实例,类成员有类属性和类方法,通过“对象.属性”或“对象.方法”调用对象的结构
注:Person p3 = p1 ; // 将p1变量保存的对象地址赋给p3,导致p1和p3指向堆空间中的同一个对象实体,一个属性的修改,另一个也会进行相应的修改
1.2 对象的内存解析(重点)
主要介绍运行时数据区的三个部分:
堆Heap: 此内存区域的唯一目的就是存放对象实例,所有对象实例以及数组都要在堆上分配;
虚拟机栈Stack: 即通常所说的栈,用于存放局部变量等,局部变量表存放了编译器可知长度的各种基本数据类型、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址),方法执行完,自动释放;
方法区Method Area: 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
为了加深理解,再附一张对象数组的内存解析图:
引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
比如Phone的对象p,打印出来的值为
需要注意的是,编译完源程序以后,生成一个或多个字节码文件,我们使用JVM中的类加载器和解释器对生成的字节码文件进行解释运行。意味着,需要将字节码文件对应的类加载解析(是执行java xxx.exe的时候做的事情)
1.3 属性与局部变量
相同点在于定义的格式相同;先声明、后使用;变量都有其对应的作用域。
下面重点介绍一下不同点:
1.3.1 在类中声明的位置不同
属性:直接定义在类中;
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量;
1.3.2 关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符:peivate、public、protected
局部变量:不可以使用权限修饰符
1.3.3 默认初始化值的情况
属性:根据其类型,都有默认初始化值
整型(byte、short、int、long) 0
浮点型(float、double) 0.0
字符型(char) 0或’\u0000’
布尔型(boolean) false
引用数据类型(类、数组、接口):null
局部变量:没有默认初始值,调用局部变量之前一定要显示赋值,特别的,形参在调用时赋值即可。
1.3.4 在内存中加载的位置(重点)
属性:加载到堆内存中(非static)
局部变量:加载到栈空间
注:解释一下堆共享和栈共享,参见https://blog.csdn.net/fox_bert/article/details/88367002,讲的挺清楚
栈是数据共享,堆是线程共享(我理解的数据共享就是在栈中有1个3,然后多个int类型的变量指向它。而对于new出来的对象,比如String,每次new都会在堆中有新的地址,哪怕字符串的内容是一样的。)
数据共享的有栈、常量池、寄存器、PC
线程共享的有堆、全局变量、静态变量、方法区
1.4 类中方法的声明和使用
ctrl+shift+t 可以搜索类文件,进到类文件后 ctrl+o 可以搜索方法
权限修饰符 返回值类型 方法名(形参列表){
// 方法体
}
注:static、final、abstract也可以修饰方法,后面再讲
如何理解“万事万物皆对象”?
在Java语言中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构;当涉及到与前端、数据库在java层面交互时,都体现为类、对象。
1.5 匿名对象的使用
- 理解:在创建对象的时候,没有显式的赋给一个变量名,即为匿名对象
- 特征:匿名对象只能调用一次
1.6 再谈方法
1.6.1 方法的重载overload
定义: 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。(与返回类型无关)
总结: “两同一不同”:同一个类、相同方法名;参数列表不同
1.6.2 可变形参的方法
jdk1.5提供了Varargs机制,允许直接定义和多个实参相匹配的形参。
- 格式:数据类型 … 变量名
- 特点:当调用可变个数形参的方法时,传入的参数个数可以是:0个、1个、2个…
- 可变个数形参的方法与本类中方法同名,形参不同的方法之间构成重载(如果有参数完全匹配的方法,那么会调用完全匹配的方法)
- 可变个数形参的方法与本类中方法同名,形参类型也相同数组之间不构成重载。换句话说,二者不能共存。
publiv void show(String … strs){
}
public void show(String[] strs){
}
上面两个方法是一样的,遍历的时候,也是一样的,都是当做数组 - 可变个数形参在方法的形参中,必须声明在末尾
- 可变个数形参在方法的形参中,最多只能声明一个可变形参
这种可变形参的方法可以用在sql语句的where字句那,因为有时候不知道会匹配几个参数。
补充:对于可以自动类型转换的,可以在传参时传入,比如现在实参为int型,但是方法中没有形参为int型,有形参是double,那么也可以传进去。(多个参数也是一样,能自动类型转换,表示能够匹配,我在eclipse中试着是可以的)
1.6.3 方法参数的值传递机制★
关于变量赋值:
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值;
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值;
值传递机制:
- 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
- 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
下面从内存角度理解下:
拓展:
第一道题目:
public class Test {
public static void main(String[] args) {
int a = 10 ;
int b = 10 ;
method(a,b) ;
System.out.println("a="+a);
System.out.println("b="+b);
}
}
在不改变原本题目的前提下,如何写这个函数才能在main函数中输出a=100,b=200?
方法一:在method方法中输出,之后System.exit(0) ;
方法二:重置打印流,重写println
第二道题目:微软
定义一个int型的数组:int[] arr = new int[]{12,3,4,5,654,765,54,34,645,32} ;让数组的每个位置上的值除以首位置的元素,得到结果,作为该位置上的数值,遍历新的数组。
解法:倒着遍历
第三道题目:
int[] arr = new int[]{1,2,3} ;
System.out.println(arr) ; //地址值
char[] arr1 = new char[]{‘a’,‘b’,‘c’} ;
System.out.println(arr1) ; //abc
分析:这是跟println方法有关,传入的数组参数都是用Object来接收,但是对于char数组,是直接用char数组来接收的,会打印数组中的所有内容。
1.6.4 递归方法recursion
一个方法体内调用它自身。
例题:计算1-100之间所有自然数的和
public int getSum(int n){
if(n==1){
return 1 ;
}else{
return n + getSum(n-1) ;
}
}
1.7 类的成员之四:代码块(初始化块)
这里需要注意的是: 代码块先于构造器执行
比如现在Leaf的父类是Mid,Mid的父类是Root,每个类中有静态代码块、非静态代码块,若干构造函数,现在new一个静态Leaf,它们的执行顺序为:
main方法除了是程序的入口之外,也是一个普通的静态方法,也是在执行main方法之前,如果main所在的类中有静态代码块,也会先于main方法
1.8 类的成员之四:内部类
成员内部类和局部内部类,在编译以后,都会生成字节码文件。
格式:
成员内部类:外部类$内部类.class
局部内部类:外部类$数字 内部类名.class
规定:局部内部类的方法中,如果调用局部内部类所声明的方法中的局部变量,则要求该变量声明为final。jdk7及以前的版本,要求显示声明为final,但是Jdk1.8之后可以省略final的声明(我理解如果被局部内部类调用了,那么在其他地方也不能修改了)
局部内部类不能有访问控制修饰符,且不能用static修饰,但是可以是abstract和final的
需要知道三个问题
- 如何实例化成员内部类的对象?
// 创建静态成员内部类实例
Person.Dog dog = new Person.Dog() ;
// 创建非静态成员内部类实例
Person p = new Person() ;
Person.Bird bird = p.new Bird() ;
- 如何在成员内部类中区分调用外部类的结构?
// 内部类中的方法
public void display(String name) {
System.out.println(name) ; // 方法形参
System.out.println(this.name) ; // 内部类的属性
System.out.println(Person.this.name) ; // 外部类的属性
}
- 开发中局部内部类的使用?
内部类总结:
2. 面向对象的三大特征
2.1封装性
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅对外暴露少量的方法用于使用
面试回答:隐藏对象内部的复杂性,只对外公开简单的接口,便于外界调用,从而提高系统的可扩展性、可维护性。
问题的引入:
当我们创建一个类的对象以后,我们可以通过“对象.属性”的方式,对对象的属性进行赋值,这里,赋值操作要受属性的数据和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件,这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件大的添加。同时为了避免用户再使用“对象.属性”的方式对属性进行赋值,则需要将属性声明为私有的private—>此时针对属性就体现了封装性。
封装性的体现
我们将类的属性xxx私有化private,同时,提供公共的public方法来获取getXxx和设置setXxxx
拓展:封装性的其他体现:比如 不对外暴露的私有方法、单例模式、如果不希望类在包外被调用,可以将类设置为缺省(类只能是public或缺省)等等。
封装性的体现,需要权限修饰符配合
- Java规定的四种权限修饰符:private、缺省、protected、public,置于类成员定义前,用来限定对象对该类成员的访问权限。
- 四种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类 (无代码块)
修饰类的话,只能使用:缺省、public
总结:Java提供了四种权限修饰符来修饰类及类的内部结构,体现了其在被调用时的可见性大小。
2.1.1 类的成员之三(构造器也称构造方法)
构造器的作用:
- 创建对象
- 初始化对象的信息
说明:
- 如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器(默认构造器的权限和类权限相同)
- 定义构造器的格式:权限修饰符 类名(形参列表){ }
- 一个类中定义的多个构造器,彼此之间构成重载
- 一旦我们显示的定义了类中的构造器之后,系统就不再提供默认的空参构造器
- 一个类中,至少会有一个构造器
总结:属性赋值的先后顺序
① 默认初始化
② 显示初始化/③ 在代码块中赋值 (这两个看写的先后顺序)
④ 构造器中初始化
⑤ 通过“对象.方法” 或 “对象.属性”的方式,赋值
拓展知识:JavaBean
① JavaBean是一种Java语言写成的可重用组件
②所谓JavaBean,符合如下标准的java类:
类是公共的
有一个无参的公共的构造器
有属性,且有对应的get、set方法
③
拓展知识:UML类图
2.2 继承性
1. 继承性的好处
① 减少了代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 为之后的多态性的使用,提供了前提
2. 继承性的格式:class A extends B{ }
A:子类、派生类、subclass
B:父类、超类、基类、superclass
① 体现:一旦子类A继承父类B之后,子类A中就获取了父类B中声明的所有的属性和方法;
特别的,父类中声明为private的属性或方法,子类中继承之后,仍然认为获取了父类中的私有结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
② 子类继承父类之后,还可以声明自己特有的属性或方法:实现功能的拓展
3. Java中关于继承性的规定
① 一个类可以被多个子类继承
② Java中类的单继承:一个类只能有一个父类
③ 子父类是相对的概念
④ 子类直接继承的父类称为直接父类,间接继承的父类称为间接父类
⑤ 子类继承父类之后,就获取了父类以及所有间接父类中声明的属性和方法
4. 类图
子类箭头指向父类
2.2.1 Object类
eclipse中快捷键ctrl+t可以看到类的继承情况
如果我们没有显示的声明一个类的父类的话,则此类继承于java.lang.Object类。也就是说所有java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。
所以,所有的java类具有java.lang.Object类声明的功能,后面会对Object进行讲解。
2.2.2 方法的重写(override / overwrite)
1. 重写: 子类继承父类之后,可以对父类中同名同参数的方法,进行覆盖操作