Java_Day15(Object,hashCode,类中的toString,包,修饰符,JVM方法区,内存设计模式)
Object
Object是所有类的父类 ,所有的类都直接或间接的继承了Object
Object 的常用方法:
boolean | equals(Object obj) 指示一些其他对象是否等于此。 |
---|---|
int | hashCode() 返回对象的哈希码值。 |
String | toString() 返回对象的字符串表示形式。 |
equals 是用来比较两个对象是否想等
实现原理:
- Object中的equals
因为所有的类都直接或间接的继承了Object 因此所有的类都有Object类中提供的方法。
equals方法的本质就是比较两个对象的地址是否相同,而在String类中之所以比较的是两个字符串的内容,原因就在于String类重写了Object的equals方法
一般都要重写 equals方法 自行定义比较规则
//重写继承自Object的 equals
@Override
public boolean equals(Object obj){
Student student = (Student)obj;
// 如果两个学生对象的姓名相同 则认为是同一个人 此时 返回true
if(this.getName().equals(student.getName()) && this.getAge() == student.getAge()){//这句使用的equals是String类的equals
return true;
}else{
return false;
}
}
}
也可以使用idea的快捷生成方式来快速重写equals
hashCode
public int hashCode()返回对象的哈希码值 只要在执行Java应用程序时多次在同一个对象上调用该方法, hashCode方法必须始终返回相同的整数
如果根据equals(Object)方法两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
toString
重写toString
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
包
概念:
就是文件夹,对类进行分类管理
定义格式:
- 格式:
package 包名;
(多级包用“.”分开) - 范例:
package con.baidu
- 命名:小写字母,一般使用公司域名的逆序
当一个类在包中的时候:
package cn.lanqiao;
public class Animal {
}
类名分为两种:
- 简单类名: Animal
- 全类名: 包名+类名 cn.lanqiao.Animal
带包编译/运行
javac -d . 类名.java
javac ./文件名/文件名/类名.java
运行:
java 包名+类名(不加后缀 .class)
导包
导包操作是针对和当前类不在同一个包下的类,当需要调用的时候,需要先进行导包,然后再使用
// 导入操作
import cn.lanqiao.Animal;
import java.util.Scanner;
import java.util.*;
jdk的常见包:
- package java.lang;位于该包下的类在使用时不需要做导入操作。在jvm启动的时候,默认会将java.lanq包下的类全部加载
- java.awt和javax.swing这两个包中的类的作用相似,都是与图形化界面相关的类
- java.math 该包下存放的都是与数学运算相关的类
- java.net 该包下存放的都是与网络编程相关的类
- java.io 该包下存放的都是与文件传输相关类
- java.time 该包下存放的都是与时间相关的类
- java.util 该包下存放的都是一些工具类
静态导入
静态导入(static import)是在 JDK1.5 新增加的功能,其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。
示例:
import static java.lang.Math.PI;
import static java.util.Arrays.sort;
import java.util.Scanner;
修饰符
权限修饰符
修饰符 | 同一个类 | 同一个包中子类无关类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
状态修饰符
final
最终的,可以修饰类 、变量 、方法
1. 修饰一个变量: 此时变量就是 一个常量
当常量为基本类型时,此时常量的值不能修改,只能赋值一次
当常量为引用类型时。指的是常量的引用地址不能变,但是引用对象的内容是可变的。
2. 修饰一个类:
当一个类被final修饰 则这个类不能被继承 ,不能有子类 ,将这样的类称为太监类
3. 修饰一个方法:
使用final修饰的方法不能被重写
static
静态,可以修饰成员方法,成员变量
在Java类中, 可用static修饰属性、 方法、 代码块、 内部类
被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调,(推荐使用类名调用)
代码块:代码块就是指使用{}括起来的一段代码
public class CodeBlock {
private int a ;
private double b ;
{// 没有任何修饰的代码块称为构造代码块
// 对成员变量进行初始化
a = 100;
b =10.2;
System.out.println("代码块执行");
}
public CodeBlock(){
// // 对成员变量进行初始化
// a = 100;
// b =10.2;
System.out.println("构造方法执行");
}
public static void main(String[] args) {
CodeBlock cb = new CodeBlock();
CodeBlock cb1 = new CodeBlock();
System.out.println(cb.a);
System.out.println(cb.b);
}
}
构造代码块的作用和构造方法类似,但是构造方法的主要作用是 构建对象以及对成员变量进行初始化。
构造代码块的主要作用就是用来初始化成员变量
无论使用哪种构造方法,构造代码块都会被执行
在构造代码块中可以调用其他的成员方法 但是不能调用构造方法
构造代码块在创建对象时,由jvm自动调用,每次创建对象都会调用一次,且优先于构造方法执行。
Static 修饰变量 修饰方法 代码块
public static void main(String[] args) {
Student stu1 = new Student("张三",22);
Student stu2 = new Student("李四",20);
stu1 = null;
stu1.setUniversisty("中北大学");//对于静态的成员方法可以通过对象调用
Student.setUniversisty("清华大学");//也可以通过类名去调用 推荐使用类名去调用
System.out.println(stu1.getUniversisty());
System.out.println(stu2.getUniversisty());
}
Static 修饰成员方法: 是与类相关,在对象创建之前就存在,随着类的加载而分配空间
Static 修饰变量:这时的变量就成为类成员,变量为类变量,方法就称为类方法
在方法中使用变量时,非静态方法既可以使用静态变量,也可以使用非静态变量,但是在静态方法中,只能使用静态变量
在静态方法中 只能使用静态变量或者调用静态方法
静态的成员在调用时,无需创建对象 ,推荐使用类名.的方式 来调用
静态方法一般用于工具类中
main方法:
public static void main(String[] args) {
主函数可以传参,在编译运行的时候在后边加上参数即可
static修饰对象运行时的内存分配图
内存分析
static{// 静态代码块
universisty="中北大学";
System.out.println("静态代码块执行...");
}
静态代码块随着类的加载而执行 而且只执行一次
静态代码块可以完成对静态变量的初始化
JVM 方法区
- 《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。所以,方法区看作是一块独立于Java堆的内存空间。
方法区主要存放的是 class,而堆中主要存放的是实例化的对象
• 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
• 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
• 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
• 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutofMemoryError:PermGen space 或者java.lang.OutOfMemoryError:Metaspace
• 加载大量的第三方的jar包
• Tomcat部署的工程过多(30~50个)
• 大量动态的生成反射类
• 关闭JVM就会释放这个区域的内存。
HotSpot中方法区的演进:
在jdk7及以前,习惯上把方法区,称为永久代。jdk8开始,使用元空间取代了永久代。
JDK8 以后,“永久代”不存在了。存储的类信息、编译后的代码数据等已经移动到了MetaSpace(元空间)中,元空间并没有处于堆内存上,而是(直接内存)直接占用的本地内存(NativeMemory)。
JDK1.6及以前 | 有永久代,静态变量存储在永久代上 |
---|---|
JDK1.7 | 有永久代,但已经逐步 “去永久代”,字符串常量池,静态变量移除,保存在堆中 |
JDK1.8 | 无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池、静态变量仍然在堆中。 |
而到了JDK8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Metaspace)来代替
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。永久代、元空间二者并不只是名字变了,内部结构也调整了
方法区的内部结构
《深入理解Java虚拟机》书中对方法区(Method Area)存储内容描述如下:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等
类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVm必须在方法区中存储以下类型信息:
• 这个类型的完整有效名称(全名=包名.类名)
• 这个类型直接父类的完整有效名(对于interface或是java.lang.object,都没有父类)
• 这个类型的修饰符(public,abstract,final的某个子集)
• 这个类型直接接口的一个有序列表
域信息
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
方法(Method)信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
• 方法名称
• 方法的返回类型(或void)
• 方法参数的数量和类型(按顺序)
• 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的一个子集)
• 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
• 异常表(abstract和native方法除外)
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
设计模式
Java中总共有23种设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。 设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。 ”套路”
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例设计模式思路:
-
将构造方法私有化
-
提供可以获取该类对象实例的访问方法
-
饿汉式
//单利设计模式 的 饿汉式
public class Singleton {
// 声明一个静态的私有成员变量
private static Singleton singleton = new Singleton();
// 将构造方法私有化
private Singleton(){
}
// 提供获取该类对象的方法
public static Singleton getSingletonInstance(){
return singleton;
}
}
- 懒汉式
//单利设计模式 的 懒汉式 (存在线程安全问题)
public class Singleton {
// 声明一个静态的私有成员变量
private static Singleton singleton ;
// 将构造方法私有化
private Singleton(){
}
// 提供获取该类对象的方法
public static Singleton getSingletonInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
单例模式的优点:
由于单例模式只生成一个实例, 减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
Runtime使用的就是单例模式