本文主要梳理关于 Java面向对象的基础知识,希望对你有帮助
- Java对象
目录
Java对象
Java创建对象有几种方式
创建一个对象用什么运算符? 对象实体与对象引用有何不同?
创建一个对象的步骤
Java对象都包含什么 ?
new Object()对象占多少个字节?
对象的比较
对象的相等和引用相等的区别
==和 equals 的区别?
String类的equals方法源码有了解么 ?
Comparator与Comparable有什么区别?
不可变对象
Java对象
Java创建对象有几种方式
创建对象总共有5种方式
- 通过 new 关键字创建对象
//1.通过new关键字创建对象 Student s1 = new Student();
- 通过 Class 类的 newInstance() 方法
//2.通过类.class的newInstance方法 Student s2 = Student.class.newInstance();
通过 Constructor 类的 newInstance 方法
//3.通过类构造方法的newInstance方法 Constructor<Student> constructor = Student.class.getConstructor(); Student s3 = constructor.newInstance();
利用 Clone 方法
//4.利用 Clone 方法-->实现cloneable接口并且重写clone方法 Student s4 = new Student(); try { Student s5 = (Student) s4.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
反序列化
//5.通过序列化保存,反序列化创建对象 --> 记得要实现序列化接口 Student s6 = new Student(); try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\data.txt"))) { objectOutputStream.writeObject(s6); }catch (IOException e) { e.printStackTrace(); } //反序列化创建对象 try(ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\data.txt"))) { Student s7 = (Student) objectInputStream.readObject(); System.out.println(s7); } catch (Exception e) { e.printStackTrace(); }
创建一个对象用什么运算符? 对象实体与对象引用有何不同?
创建一个对象使用new关键字,对象实体是真正创建出来的对象(对象实体存放在堆内存),对象引用存放的是对象的地址(对象引用存放在栈内存中),对象引用指向对象.
- 一个引用只可以指向1个对象或者0个对象.
- 一个对象可以被多个引用所指向.
创建一个对象的步骤
- 为对象分配内存空间
- 初始化该对象
- 给对象的引用赋值
Java对象都包含什么 ?
首先 , 对象的组成有3个部分:对象头,实例数据,对齐填充字节, 对象头又包括 markword,指向class的指针,数组长度(如果不是数组的话就没有)
- 对象头
- markword
如果jvm是32位,则Mark Word是32bit;如果64位,则Mark Word是64bit。Mark Word
结构如下:
Mark Workd存储的是对象自身运行时的数据,比如:锁标志位;是否偏向锁;GC分代年龄;对象的hashCode;获取到该锁的线程的线程ID;偏向时间戳(Epoch)等等。
- Klass Word 类型指针
如果jvm是32bit,则Klass Word是32bit;如果jvm是64bit,则Klass Word是64bit。
Java对象存放在堆中,但其类信息存放在方法区中,所以Klass word指向该对象的类信息。
JVM通过这个指针来确定该对象是哪个类的实例
- 数组长度
如果jvm是32bit,则数组长度是32bit;如果jvm是64bit,则数组长度是64bit
2. 实例数据
3. 对其填充字节
jvm要求Java对象占用内存的大小是8byte的倍数,因此需要把对象的大小补齐至8byte倍数。
另外 , 32位系统和64位系统不同区域占用空间大小的区别,因为在64位平台的HotSpot中使用32位指针(实际存储用64位),内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力
但是现在假设一个场景,公司现在项目部署的机器是32位的,你们老板要让你将项目迁移到64位的系统上,但是又因为64位系统比32位系统要多出更多占用空间,怎么办,因为正常来说我们是不需要这一部分多余空间的,所以jvm已经帮你考虑好了,那就是指针压缩。
指针压缩
-XX:+UseCompressedOops 这个参数就是JVM提供给你的解决方案,可以压缩指针,将占用的空间压缩为原来的一半,起到节约空间的作用,classpointer参数大小就受到其影响。
new Object()对象占多少个字节?
64位系统下 :
- 在开启指针压缩的情况下,markword占用8字节,classpoint占用4字节,Interface data无数据,总共是12字节,由于对象需要为8的整数倍,Padding会补充4个字节,总共占用16字节的存储空间。
- 在没有指针压缩的情况下,markword占用8字节,classpoint占用8字节,Interface data无数据,总共是16字节。
对象的比较
对象的相等和引用相等的区别
对象相等指的是内存中存放的内容是否相等.
引用相等是指它们指向的内容地址是否相等.
==和 equals 的区别?
先说说 == ,对于==来说比较基本数据类型和引用数据类型有不同
- 基本数据类型 : 比较的是值
- 引用类型 : 比较的是对象的内存地址
根本原因就是 Java都是值传递,不管是基本数据类型还是引用数据类型都比较的都是值,基本数据类型的值就是值,而引用数据类型是内存地址
再说说equals方法 ,equals方法只能用于判断两个对象是否相等,不能用来判断基本数据类型,另外equals方法来源于Object类,又因为Object类是所有类的父类,所以,所有类都有equals方法,如果没有重写,默认就是Object类的equals方法.
//默认的equals方法 public boolean equals(java.lang.Object obj) { return (this==obj);//【这里比对的是对象的内存地址】 }
对于equals有两种情况 :
- 没有重写equals方法, 与==一样,默认是Object的equals方法
- 重写了equals方法, 比较的对象的内容,相等返回true
String类的equals方法源码有了解么 ?
对于String类就重写了equals方法,所以我们一般比较字符串的时候,比较的是两个字符串的内容
public boolean equals(Object anObject) {
if (this == anObject) {//先比较两个字符串的内存地址是否相同
return true;//相同返回true
}
if (anObject instanceof String) {//看该字符串是否是String类型
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {//如果字符串长度相等,转化成字符数组,一一比较字符
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
//发现字符不相同,直接返回false
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
//长度不同直接返回false
return false;
}
Comparator与Comparable有什么区别?
Comparable 和 Comparator 都是用来实现元素排序的,它们二者的区别如下:
- Comparable 是“比较”的意思,而 Comparator 是“比较器”的意思;
- Comparable 是通过重写 compareTo 方法实现排序的,而 Comparator 是通过重写 compare 方法实现排序的;
- Comparable 必须由自定义类内部实现排序方法,而 Comparator 是外部定义并实现排序的。
总结二者的区别:Comparable 可以看作是“对内”进行排序接口,而 通过 Comparator 接口可以实现和原有类的解耦,在不修改原有类的情况下实现排序功能,所以 Comparator 可以看作是“对外”提供排序的接口。
@Data
public class Person implements Comparable<Person> {
private int id;
private int age;
private String name;
public Person(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
/**
* 重写 compareTo 方法来实现自定义排序规则
* compareTo 方法接收的参数 p 是要对比的对象,排序规则是用当前对象和要对比的对象进行比较,
* 然后返回一个 int 类型的值。正序从小到大的排序规则是:使用当前的对象值减去要对比对象的值;
* 而倒序从大到小的排序规则刚好相反:是用对比对象的值减去当前对象的值。
* @param p
* @return
*/
@Override
public int compareTo(Person p) {
return p.getAge() - this.getAge();
}
}
@Data
public class Person2 {
private int id;
private int age;
private String name;
public Person2(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
}
//实现一个比较器 --> “对外”提供排序的接口。
class PersonComparator implements java.util.Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
}
不可变对象
怎么实现一个不可变对象
如果要实现 immutable 的类,我们需要做到:
- 将class自身声明为final,这个类就不能被继承(别人就不能扩展来绕过限制)
- 将所有成员变量定义为 private 和final,并且不要实现setter方法
- 如果要修改类的状态,必须返回一个新的对象。
- 通常构造对象的时候,成员变量使用深拷贝来初始化,而不是直接赋值,(无法确定输入对象不被其他人修改)
- 如果确实需要实现getter方法,或者其他可能返回内部状态的方法,使用 copy-on-write原则,创建私有的copy.
- 对于getter方法,和setter方法,建议做还是确定有需要的时候再去实现
不可变的好处是什么 ?
对于String来说
- 安全性
String 是 Java 中最基础也是最长使用的类,经常用于存储一些敏感信息,例如用户名、密码、网络连接等。因此,String 类的安全性对于整个应用程序至关重要。字符串容易被修改
-
节省空间——字符串常量池
通过引入字符串常量池来节省空间,内容相同的字符串可以使用相同的字符串,可以节省内存空间,如果String可以改变,并且这个对象被其他多个地方所引用,这个字符串被修改,所有的地方都会被修改
-
线程安全
String 对象是不可修改的,如果线程尝试修改 String 对象,会创建新的 String,所以不存在并发修改同一个对象的问题。
- 性能
String 被广泛应用于 HashMap、HashSet 等哈希类中,当对这些哈希类进行操作时,例如 HashMap 的 get/put,hashCode 会被频繁调用。
由于不可变性,String 的 hashCode 只需要计算1次后就可以缓存起来,因此在哈希类中使用 String 对象可以提升性能。
能否创建一个包含可变对象的不可变对象
不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝
参考 :
String 为什么不可变?不可变有什么好处?_string为什么不可变_程序员囧辉的博客-CSDN博客
能否创建一个包含可变对象的不可变对象?_秋风清,秋月明。落日夕阳一片红的博客-CSDN博客
元素排序Comparable和Comparator有什么区别? - 掘金
Java基础常见面试题总结(中) | JavaGuide(Java面试 + 学习指南)
聊聊Java中的不可变对象 | Java程序员进阶之路