1. 封装是什么?
封装其实就是实现了数据的隐藏,决不能让类中的方法直接访问其他类的实例字段,所以一般我们会将那些实例字段设置为private
,只能通过对象的方法与对象数据进行交互。
@Data
public class Person{
public int id;
private int age;
public void setId(int id) {
if (id <= 0) {
return;
}
this.id = id;
}
public void setAge(int age) {
if (age <= 0) {
return;
}
this.age = age;
}
}
可以看到,其他类对于age
字段,的修改,只能通过setter方法,但对于id
字段,可以任意修改,比如改成负的,别人大可以不去调用setId
方法来实现数据的更改,因为它设置成了public
。
2. 类之间的关系
这个一般在看uml
图类之间的关系时用的到,先来说说概念。
依赖,两个类之间是use-a
,一种使用关系,比如在类A的方法中将B做为形参,返回值,局部变量,静态方法调用,这些都算是使用关系,如下代码所示。
public class Person{
//作为形参使用
public void usePhone(Phone phone) {
phone.call();
}
public void usePhone2() {
//局部变量使用
Phone phone = new Phone();
phone.call();
}
public void usePhone3() {
//作为静态方法调用
Phone.play();
}
//作为返回值
public Phone getPhone() {
return new Phone();
}
}
class Phone{
public void call() {
}
public static void play() {
}
}
聚合,这种关系比较简单,即has-a
关系,对象B作为类A的成员变量存在,如下所示。
public class Person{
private Phone phone = new Phone();
}
class Phone{
}
继承,这种关系就更简单了,使用extends关键字即可,父类的属性和方法,子类都有。
public class Person{
}
class Man extends Person {
}
在uml图中,对应的箭头关系是这样的,书中没有详细介绍关联,原文提到了关联的标准记法不是很清晰,所以我认为掌握上面三种就够了。
3. 更改器方法与访问器方法
这个概念比较新奇,之前没怎么听过,但其实具体的用法我们都见过了。
更改器方法(mtator method)
,调用该方法会导致原对象状态的变化。
反之,访问器方法(accessor method)
,只访问对象而不更改对象的方法。
看下面的例子,可以看到前两个方法使得原有的对象属性发生了改变,后面的方法,一个是访问获取属性,另一个是返回了新的对象,这种都算是访问器方法。
public class Demo2 {
public static void main(String[] args) {
Student student = new Student();
//更改状态
student.setName("dd");
student.addId();
//未更改原对象状态
student.getName();
Student student1 = student.newStudent();
}
}
@Data
class Student {
private int id;
private String name;
//原对象状态变化了
public void setName(String name) {
this.name = name;
}
//原对象状态变化了
public void addId() {
this.id += 1;
}
//只是获取了该对象的字段
public String getName() {
return name;
}
//看似更改了
public Student newStudent() {
Student student = new Student();
student.setId(1);
student.setName("bb");
return student;
}
}
4. 用var声明局部变量
虽然我们现在用的是jdk8,但未来的升级是大势所趋,所以来点小插曲。
在jdk10中,可以使用var来声明局部变量,因为编译器可以通过后面的赋值来判断类型,但需要强调的是,var关键字,只能用于方法中的局部变量,其他地方无法推测类型。
public class Demo2 {
public static void main(String[] args) {
var a = 10;
var s = "hello bb";
}
}
5. 值传递与引用传递
Java是值传递还是引用传递?
先给结论,值传递!
首先字面解释下,什么是值传递?就是在方法传参的时候,传入的形参变化不会导致实参的变化,比如你传一直大象给我,我借用过程中,创建了一个大象的副本,即便这个大象副本受伤了,最终还到你受伤的,还是那只完好无损的大象。
反之,引用传递指的就是,传入的形参和实参其实是同一份,同样如果大象受伤了,少胳膊少腿了,真正交还给你的,依旧是那只少胳膊少腿的大象。
那我们用代码验证下吧,分别用基本数据类型和引用数据类型做例子。
public class Demo4 {
public static void main(String[] args) {
//使用基本数据类型
int x = 10;
calculate(x);
System.out.println(x);//输出 10
//使用引用数据类型
Object obj1 = new Object();
Object obj2 = new Object();
System.out.println(obj1.hashCode());
System.out.println(obj2.hashCode());
swap(obj1, obj2);
System.out.println(obj1.hashCode());
System.out.println(obj2.hashCode());
}
public static void calculate(int x) {
x *= 3;
}
public static void swap(Object o1,Object o2) {
Object temp = o1;
o1 = o2;
o2 = temp;
}
}
10
1746572565
989110044
1746572565
989110044
从第一个例子来看,我们传入什么值最终返回的就是什么值,其实内部就是创建了一个形参x的副本,最终方法结束就销毁掉了。
第二个例子是两个对象进行了交换,而如果真的是引用传递,那么这中间就不会创建副本,最终的结果就是两个对象的hashcode也会对调,但我们可以发现没有任何的变化,所以在方法中的形参,都是以副本的方式存在的,最终方法结束,副本都被销毁。
我们可以得出的结论有三个。
1.方法不能修改基本数据类型的参数,即值类型传什么还是什么。
2.方法可以改变对象参数的状态(这个例子没有证明,但还是说明下,虽然都是用的副本,引用指向的是同一个对象,所以会导致真实对象发生变化)
3.方法不能让对象参数引用一个新的对象,即无法改变引用的指向。
6. 构造执行顺序
我们再来回顾下一个对象的构造过程中,是怎样进行初始化的,这里为了简便,暂时不考虑父类的情况。
public class Demo5 {
public static void main(String[] args) {
Demo5 demo5 = new Demo5();
}
{
System.out.println("代码块执行");
}
private int id = setId();
public int setId() {
System.out.println("初始化id");
return 1;
}
static {
System.out.println("静态代码块执行");
}
public Demo5() {
System.out.println("构造器执行");
}
}
静态代码块执行
代码块执行
初始化id
构造器执行
可以看到,静态代码块是最先执行的,这里没有放静态初始化方法,如果有,那么就要看这两个的顺序了,在前面的先执行。
之后是代码块和初始化方法,这个也是按顺序来,最终才是构造器方法执行。
但这里我还是忽略了一点,即字段的初始值是什么赋予的呢?即0或者null值,如果是static修饰,那么赋值将被排在static代码块前面,如果不是静态的,那么也会在普通代码块之前赋值。
7. 类设计技巧
本章的最后给出了一些类设计的技巧,让我们一起看一下吧。
1.一定要保证数据私有。
这个是封装性的体现,而一旦你将字段设置成了public,就会造成在类外不经意的修改,有些莫名其妙的方法就是这么来的,你可以想象下,如果String类里的属性都是public的,会有什么影响?
2.一定要对数据进行初始化。
我认为这是为了防止npe异常,如果通过null来调用某些方法,就会出现这种问题,而一旦有初始值了,只要不为null,就不会发生这种问题。
3.不要在类中使用过多的基本类型。
之前我理解的意思是不要用太多int,long之类的,而应该使用包装类,但其实并不是这个意思。
比如我一个Person
类,有name
,age
字段,Student
同样也有名称和年龄,如果使用继承的话,也应该在类中包含Person
对象,这样未来修改起来也方便。
4.不是所有的域都需要独立的域访问器和域更改器。
比如一个员工的生日一般是不会变的,如果之前构造了员工对象,那么生日的setBirthday方法其实不应该再使用了。
5.将职责过多的类进行分解。
单一职责,这个还是得看实际的使用,我们不能让一个类有过多的职责,但有时也不一定只能有一项职责,还是按实际的来。
6.类名和方法名要能够体现它们的职责,不要乱命名。
7.优先使用不可变的类,可以防止线程安全问题。
薪火之道,薪尽火传,个人的得失虽然重要,但更重要的在于,世界因你的成就而获得多大的影响。