理解:private是访问修饰符的一种,访问修饰符规定了访问权限.
作用:
1.private修饰属性:该属性只能在类的内部使用
2.private修饰方法:该方法只能在类的内部使用
应用场景:不让让外界访问的属性和方法就用private修饰
public class A {
private String str = "好好学习";
private void method01(){
System.out.println("aaabbbccc -- " + str);
}
public void method02(){
method01();
}
}
public class Test01 {
public static void main(String[] args) {
A a = new A();
a.method02();
}
}
面向对象三大特征之一: 封装,继承,多态
理解:不能直接操作属性,可以添加get/set方法
*对象代表什么,就得封装对应的数据,并提供数据对应的行为*
步骤:
1.私有化属性: 成员变量使用private
2.添加get-获取/set-设置方法
好处:外界不能直接操作属性(有风险),通过get/set方法操作属性,可以在方法内添加额外的功能
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
思考题:操作属性有什么功能?
1.设置属性 – set
2.获取属性 – get
set 存 get 取 先存后取,在set中做判断等操作
需求:模拟银行用户操作余额的功能
分析:
操作余额(存钱、取钱、查看)
import java.time.LocalDateTime;
public class User {
String username;
String password;
String name;
private double surplus;//余额
public User() {
}
public User(String username, String password, String name, double surplus) {
this.username = username;
this.password = password;
this.name = name;
this.surplus = surplus;
}
//设置余额
public void setSurplus(double surplus){
//额外的功能
double num = (surplus - this.surplus);
System.out.println(LocalDateTime.now() + " -- " + this.name + "用户操作了金额:" + ((num>0)?"+":"") + num); //显示当前时间
//设置属性
this.surplus = surplus;
}
//获取余额
public double getSurplus(){
//额外的功能
System.out.println(LocalDateTime.now() + " -- " + this.name + "用户获取了金额");
//返回余额
return surplus;
}
}
public class Test01 {
public static void main(String[] args) {
User user = new User("1445584980", "123123", "小彭", 2000);
//存钱
//user.surplus = user.surplus+200;
user.setSurplus(user.getSurplus() + 200);
//取钱
//user.surplus = user.surplus-1800;
user.setSurplus(user.getSurplus() -1800);
//查看
//System.out.println(user.surplus);//400.0
System.out.println(user.getSurplus());//400.0
}
}
运行结果:
概念:表示本对象
理解:哪个对象调用该方法,该方法里的this就表示该对象
作用:
1.this.属性:调用本对象的成员属性
2.this.方法:调用本对象的成员方法
3.this():在构造方法的第一句调用本类另外的构造方法
this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
方法外面,类里面是成员变量,而方法里面是局部变量
问题: 成员变量何时在内存中存在? 静态变量何时在内存中存在?
成员变量在创建对象时。静态变量在类创建时就有
成员变量会在对象被创建时分配内存并存在于对象的内存空间中;静态变量在类加载时被初始化,此时就会在内存中分配空间
import java.time.LocalDateTime;
public class User {
private String username;
private String password;
private String name;
private double surplus;//余额
public User() {
//在当前构造方法中调用另一个构造方法
this("默认账号", "默认密码", "亚当", 0.0);
}
public User(String username, String name, String phone, char sex, double surplus) {
//在当前构造方法中调用另一个构造方法
this(username,"000000", name, phone, sex, surplus);
}
public User(String username, String password, String name, double surplus) {
this.username = username;
this.password = password;
this.name = name;
this.surplus = surplus;
}
//设置余额
public void setSurplus(double surplus){
//额外的功能
double num = (surplus - this.surplus);
System.out.println(LocalDateTime.now() + " -- " + this.name + "用户操作了金额:" + ((num>0)?"+":"") + num);
//设置属性
this.surplus = surplus;
}
//获取余额
public double getSurplus(){
//额外的功能
System.out.println(LocalDateTime.now() + " -- " + this.name + "用户获取了金额");
//返回余额
return surplus;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 转账
* @param username 对方的账号
* @param money 转账的金额
*/
public void transferAccounts(String username,double money){
//获取本对象的余额,判断是否比转账的金额更多
if(this.getSurplus() > money){
//将当前用户的余额的money部分转给对方账号
//this.setSurplus(this.getSurplus()-money);
setSurplus(getSurplus()-money);
System.out.println("已经给" + username + "转账成功");
}else{
System.out.println("余额不足");
}
}
}
public class Test01 {
public static void main(String[] args) {
// User user = new User("1445584980", "123123", "小彭", 2000);
// user.transferAccounts("12345678999", 800);
}
}
概念:
1.成员属性:每个对象独享一份
2.静态属性:每个对象共享一份
3.静态属性何时创建?
使用到类,JVM会判断方法区中是否有该类的class文件,如果没有,就把该类的class文件加载到方法区
JVM会扫描该类的所有属性,并把属性添加到静态区中(1.开辟空间 2.赋系统的默认值)
4.静态属性何时销毁?
项目结束时,静态属性才会被销毁,所以静态属性的生命周期很长,项目中使用时需警慎
5.静态属性的应用场景?
该类所有的对象需要共享的属性,就可以设置为静态
注意:静态属性属于类的属性,直接使用类名调用。使用对象调用会出现警告
静态属性比成员属性更早的在内存中存在
public class A {
//成员属性
String str1;
//静态属性
static String str2;
}
public class Test01 {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
a1.str1 = "aaa";
a2.str1 = "bbb";
System.out.println(a1.str1);//aaa
System.out.println(a2.str1);//bbb
//注意:静态属性属于类的属性,直接使用类名调用。使用对象调用会出现警告
// a1.str2 = "xxx";
// a2.str2 = "yyy";
// System.out.println(a1.str2);//yyy
// System.out.println(a2.str2);//yyy
A.str2 = "xxx";
A.str2 = "yyy";
System.out.println(A.str2);//yyy
System.out.println(A.str2);//yyy
}
}
注意:A.str2不是xxx,而是yyy
static修饰属性内存图:
成员方法 vs 静态方法 应用场景
成员方法:必须使用对象调用,也就是说调用成员方法之前必须创建对象(开辟空间)
静态方法:属于类的方法,直接使用类名调用,纯功能的方法就可以设计成静态方法(工具类),比如MyArrays
需求:模仿Java的MyArrays,编写自己的MyMyArrays
目的:
1.掌握工具类的概念
2.回顾之前的知识点
3.理解文档注释的作用
/**
* 自己编写的数组工具类
* @author 小彭
* @version 1.0
*/
public class MyArrays {
/**
* 数组的排序
* @param a 目标数组
*/
public static void sort(int[] a){
for (int i = 0; i < a.length-1; i++) {
for (int j = 0; j < a.length-1-i; j++) {
if(a[j] > a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
/**
* 数组的查找,必须先排序,在查找
* @param a 目标数组
* @param key 需要查找的元素
* @return 如果搜索的元素包含在数组中就返回元素的下标; 否则,返回(-插入点-1)
*/
public static int binarySearch(int[] a,int key){
int start = 0;
int end = a.length-1;
while(start <= end){
int mid = (start+end)/2;
if(key < a[mid]){
end = mid-1;
}else if(key > a[mid]){
start = mid+1;
}else{
return mid;
}
}
return -start-1;
}
/**
* 拷贝数组
* @param original 目标数组
* @param newLength 新数组的长度
* @return 新数组
*/
public static int[] copyOf(int[] original, int newLength){
int copyLength = original.length;
if(copyLength > newLength){
copyLength = newLength;
}
int[] newArr = new int[newLength];
for (int i = 0; i < copyLength; i++) {
newArr[i] = original[i];
}
return newArr;
}
/**
* 拷贝区间数组
* @param original 目标数组
* @param from 开始下标-包含
* @param to 结束下标 - 排他
* @return 新数组
*/
public static int[] copyOfRange(int[] original, int from, int to){
int newLength = to-from;
int[] newArr = new int[newLength];
int index = 0;
for (int i = from; i < to; i++) {
newArr[index++] = original[i];
}
return newArr;
}
/**
* 替换全部元素
* @param a 目标数组
* @param val 替换的值
*/
public static void fill(int[] a, int val){
fill(a, 0, a.length, val);
}
/**
* 替换区间元素
* @param a 目标数组
* @param fromIndex 开始下标 - 包含
* @param toIndex 结束下标 - 排他
* @param val 替换的值
*/
public static void fill(int[] a, int fromIndex, int toIndex, int val){
for (int i = fromIndex; i < toIndex; i++) {
a[i] = val;
}
}
/**
* 将数组转换为字符串
* @param a 目标数组
* @return 转换后的字符串
*/
public static String toString(int[] a){
String str = "[";
for (int element : a) {
if(str.length() != 1){
str += ",";
}
str += element;
}
str += "]";
return str;
}
}
import com.qf.array.MyArrays;
public class Test01 {
public static void main(String[] args) {
int[] arr = {18,32,81,63,20,9};
//排序 - [9, 18, 20, 32, 63, 81]
MyArrays.sort(arr);
//查找(底层还用二分法去查找元素,所以必须先排序,再查找!)
//返回值规则:如果搜索的元素包含在数组中就返回元素的下标; 否则,返回(-插入点-1)
int index = MyArrays.binarySearch(arr, 28);
System.out.println("查找元素的下标为:" + index);
//拷贝数组(目标数组,新的长度)- [9, 18, 20, 32, 63, 81, 0, 0, 0, 0]
int[] copyOf = MyArrays.copyOf(arr, 10);
//拷贝区间数组(目标数组,开始下标-包含,结束下标-排他)- [18, 20, 32, 63, 81, 0, 0]
int[] copyOfRange = MyArrays.copyOfRange(copyOf, 1, 8);
//替换元素 - [666, 666, 666, 666, 666, 666, 666]
MyArrays.fill(copyOfRange, 666);
//替换区间元素(目标数组,开始下标-包含,结束下标-排他,替换的值) - [666, 666, 888, 888, 888, 666, 666]
MyArrays.fill(copyOfRange, 2, 5, 888);
//将数组转换为字符串
String str = MyArrays.toString(copyOfRange);
System.out.println(str);
}
静态代码块是类加载到方法区时才会被调用,该代码块只能初始化静态变量
代码块是创建对象时优先于构造方法调用,该代码块可以初始化成员变量和静态变量()
构造方法是创建对象时调用,该方法可以初始化成员变量和静态变量
经验:
1.创建对象时,在构造方法中初始化数据
2.项目中可以在静态代码块中初始化静态属性
public class A {
String str1;//成员属性
static String str2;//静态属性
//静态代码块:class文件加载到方法区时调用
//作用:操作静态属性,不能操作成员属性
static{
A.str2 = "eee";//底层实现:A.str2 = "eee";
System.out.println("静态代码块 --" + A.str2);
}
//代码块:创建对象时优先于构造方法调用
//作用:操作成员属性和静态属性
{
str1 = "ccc";//底层实现:this.str1 = "ccc";
str2 = "ddd";//底层实现:A.str2 = "ddd";
System.out.println("代码块-- " + str1 + " -- " + str2);
}
//构造方法:创建对象时调用构造方法
//作用:操作成员属性和静态属性
public A() {
str1 = "aaa";//底层实现:this.str1 = "aaa";
str2 = "bbb";//底层实现:A.str2 = "bbb";
System.out.println("构造方法 -- " + str1 + " -- " + str2);
}
}
public class Test01 {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
}
}
思考题:什么算使用到该类了?
1.新建一个类的对象
2.调用该类的静态属性
3.调用该类的静态方法
4.Class.forName(“java.lang.String”)
基本作用:防止了类的重名问题
项目作用:项目中有众多的类,把相同功能的类放在同一个包中,方便管理
工具类:com.dream.util/tool
实体类:com.dream.vo/bean/entity/bto/pojo
数据库类:com.dream.dao/mapper
包名的命名规范
路径名.路径名.xxx.xxx
例如:com.pcb.Work
1.项目右键,选择 Export,然后选择java下的 JAR file (java项目选择JAR,web项目上线选择WAR)
2.选择路径(桌面)
3.桌面上出现JAR图标
1.项目右键选择Folder,新建一个文件夹,名为lib
2.将需要的JAR包放在lib文件夹中
3.右键选择Build path ,建立链接
1.项目右键选择Export ,选择Javadoc
2.选择jdk中的javadoc.exe,然后选择存放路径桌面(注意:生成的是一个一个页面,所有在桌面后面加一个UtilsAPI文件夹)
3.等待生成
4.怎么找–打开文件夹,选择index.html,就可以了
类加载机制内存图:
使用某个类时,如果该类的class文件没有加载到内存时,则系统会通过以下三个步骤来对该类进行初始化
1.类的加载(Load) → 2.类的连接(Link) → 3.类的初始化(Initialize)
类的加载(Load):将类的class文件读入内存,并为之创建一个java.lang.Class的对象,此过程由类加载器(ClassLoader )完成
类的连接(Link):将类中的数据加载到各个内存区域中
类的初始化(Initialize):JVM负责对类进行初始化
类的完整生命周期 :加载、连接(验证、准备、解析)、初始化、使用、卸载
加载
通过一个类的全限定名来获取其定义的二进制字节流
将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口
注意: 相对于类加载过程的其他阶段而言,加载阶段是可控性最强的阶段,因为程序员可以使用系统的类加载器加载,还可以使用自己的类加载器加载,在这里我们只需要知道类加载器的作用就是上面虚拟机需要完成的三件事
连接
验证
文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理
元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。
字节码验证:这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出威胁虚拟机安全的事。
符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外 (常量池中的各种符号引用) 的信息进行校验。目的是确保解析动作能够完成。
注意: 对整个类加载机制而言,验证阶段是一个很重要但是非必需的阶段,如果我们的代码能够确保没有问题,那么我们就没有必要去验证,毕竟验证需要花费一定的的时间。当然我们可以使用
-Xverfty:none来关闭大部分的验证。
准备 - 重要
准备阶段主要为类变量(static)分配内存并设置初始值。这些内存都在方法区分配。在这个阶段我们只需要注意两点就好了,类变量和初始值两个关键词:
类变量(static):会分配内存,但不会对应的分配值,其次实例变量不会分配空间,因为实例变量主要随着对象的实例化一块分配到java堆内存中
初始值:这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值
比如1:public static int value = 1;
在这里准备阶段过后的value值为0,而不是1赋值为1的动作在初始化阶段
比如2:public static final int value = 1;
同时被final和static修饰准备阶段之后就是1了,因为static final在编译器就将结果放入调用它的类的常量池中
解析
解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程
初始化
这是类加载机制的最后一步,在这个阶段,java程序代码才开始真正执行。在准备阶段已经为类变量赋过一次值。在初始化阶端,程序员可以根据自己的需求来赋值了。一句话描述这个阶段就是执行类构造器clinit()方法的过程。
在初始化阶段,主要为类的静态(stitic)变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量(stitic)进行初始化。在Java中对类变量进行初始值设定有两种方式:
补充:clinit() 方法具有以下特点:
- 由编译器自动收集类中所有类变量(static)的赋值动作和静态语句块(static{} 块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码
class Test { static { i = 0; // 给变量赋值可以正常编译通过 System.out.print(i); // 这句编译器会报错,提示“非法向前引用” } static int i = 1; }
- 与类的构造函数(或者说实例构造器 init())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 clinit() 方法运行之前,父类的 clinit() 方法已经执行结束。因此虚拟机中第一个执行 clinit() 方法的类肯定为 java.lang.Object。由于父类的 clinit() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。例如以下代码:
public class Test { public static void main(String[] args) { System.out.println(Son.B);//输出结果是父类中的静态变量A的值,也就是2 } } class Father{ public static int A = 1; static { System.out.println("a"); A = 2; } } class son extends Father { public static int B = A; }
clinit() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 clinit() 方法。
接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 clinit() 方法。但接口与类不同的是,执行接口的 () 方法不需要先执行父接口的 clinit() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 clinit() 方法。
虚拟机会保证一个类的 clinit() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 clinit() 方法,其它线程都会阻塞等待,直到活动线程执行 clinit() 方法完毕。如果在一个类的 clinit() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
JVM初始化步骤:
- 假如这个类还没有被加载和连接,则程序先加载并连接该类
2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类
3. 假如类中有初始化语句,则系统依次执行这些初始化语句类初始化时机:
只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
1. 创建类的实例,也就是new的方式 2. 访问某个类或接口的静态变量,或者对该静态变量赋值 3. 调用类的静态方法 4. 反射 5. 初始化某个类的子类,则其父类也会被初始化 6. Java虚拟机启动时被标明为启动类的类,直接使用 java.exe命令来运行某个主类
比如:测试类Test
使用: 当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码
卸载: 当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存
public class Test {
public static void main(String[] args) {
A a = A.getInstance();
System.out.println("A value1:" + a.value1);//1
System.out.println("A value2:" + a.value2);//0
B b = B.getInstance();
System.out.println("B value1:" + b.value1);//1
System.out.println("B value2:" + b.value2);//1
}
}
class A{
private static A a = new A();
public static int value1;
public static int value2 = 0;
private A(){
value1++;
value2++;
}
public static A getInstance(){
return a;
}
}
class B{
public static int value1;
public static int value2 = 0;
private static B b = new B();
private B(){
value1++;
value2++;
}
public static B getInstance(){
return b;
}
}
类加载器实现的功能是即为加载阶段获取二进制字节流的时候,在 Java 虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。
分析一:
public class A {
//准备阶段1:JVM给静态属性在静态区中开辟空间
//A a;
//int value1;
//int value2;
//准备阶段2:JVM给静态属性赋系统默认值
//A a = null;
//int value1 = 0;
//int value2 = 0;
//初始化阶段:java程序代码才开始真正执行
//A a = new A();
//int value1 = 1;
//int value2 = 0;
static A a = new A();
static int value1;
static int value2 = 0;
public A() {
value1++;
value2++;
}
}
public class Test01 {
public static void main(String[] args) {
System.out.println(A.value1);//1
System.out.println(A.value2);//0
}
}
分析二:
public class A {
//准备阶段1:JVM给静态属性在静态区中开辟空间
//int value1;
//int value2;
//A a;
//准备阶段2:JVM给静态属性赋系统默认值
//int value1 = 0;
//int value2 = 0;//0是系统赋的默认值
//A a = null;
//初始化阶段:java程序代码才开始真正执行
//int value1 = 1;
//int value2 = 1;
//A a = new A();
static int value1;
static int value2 = 0;
static A a = new A();
public A() {
value1++;
value2++;
}
}
public class Test01 {
public static void main(String[] args) {
System.out.println(A.value1);//1
System.out.println(A.value2);//1
}
}
理解:
Java万物皆对象!!!!!
一个class文件,对于Java来说,都是一个对象,而这个对象就是 class对象
class文件是什么?
class文件包含了该类的所有的信息
eg: 一个类有几个成员属性 一个类有几个静态属性? 一个类有几个构造方法属性 一个类有几个成员方法 等这些信息
class对象是什么????
class对象是class文件的访问入口
1.创建一个对象用什么运算符?对象实体与对象引用有何不同?
new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在
栈内存中)。一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);
一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
1.private关键字 – 私有化
修饰属性:属性不能被外界访问
修饰方法:方法不能被外界访问
2.封装
步骤:
1.私有化属性
2.get/set方法注意:理解封装的概念
3.面相对象版本的五子棋
a.私有化属性的业务场景
b.私有化方法的业务场景
c.状态码 – play方法的返回值
4.static
a.修饰属性
b.修饰方法(注重MyArrays,导出Jar包、导入Jar包、导出API)
c.静态代码块
5.类加载机制(注重加载过程和面试题)