[Java]重学Java-成员与方法
Java类
Java中的类都可以抽象成Class,Class的组成大致有:
-
Class: 类型,许多Java框架的时候,Class通常代表类型.在
java.lang.Class
中,Class正是实现了Type
接口.Type
是Java编程语言中所有类型的通用超级接口。这些包括原始类型,参数化类型,数组类型,类型变量和基本类型,我们自定义的Class也是一种类型. -
Constructor: 构造器,使用
new
关键字的时候,Java就会调用构造器来生产对象.构造器是类的成员. - Method: 方法提供有关类的单个方法的信息,以及对单个方法的访问.方法也是类的成员.
- Field: 定义在类中的成员属性.
- ClassName: 类名.
命名空间与访问权限
包
什么是Java中的包,笔者理解,包其实就像我们操作系统的文件集合(不同的是,系统文件通常以/
进行路径划分,而Java中使用的是.
),每个包之间的文件互相独立.Java中规定同一个包下不允许出现两个同名的类.(当然,如果是在Maven管理中,出现同名的类貌似并不冲突,以工程的包优先).
常见的包有:java.lang、java.util...这些包都代表了在java/lang
、java/util
目录下的基础类文件集合.
- IDEA下的JDK类库视图

在企业开发中,包的命名规范通常为该公司的域名反转(域名是唯一性的),例如
com.baidu
、com.imooc
...
导入-import
在面向对象的世界里面,并不提倡某个功能强大的类可以涵盖系统的所有功能.而是应该将每个功能领域的类组合在一起协助来完成一个复杂的功能.这个时候,我们就需要用到import
关键字来导入我们需要的类进行api调用
.
package com.tea.modules.java8.oop;
import com.tea.modules.model.User;
/**
*
* @author jaymin
* @since 2021/4/10 16:15
*/
public class OOPDemo {
public static void main(String[] args) {
User user = new User();
System.out.println(user.toString());
}
}
我在
com.tea.modules.java8.oop
这个包下创建了一个名为OOPDemo
的类,同时使用import
导入了com.tea.modules.model
的User类.但是在实际开发中,借助IDE我们可以轻易的实现导包.不过你会经常遇到与包相关,特别是你的伙伴将一个类移到另一个地方的时候,编译器无法在指定的路径找到该类,就会发生编译错误.
值得一提的是,import支持通配符,例如需要用到很多
java.util
下的工具类,那么可以直接import java.util.*
访问权限控制符
Java提供了一些访问修饰符来控制对类中的资源的访问权限:
- public: 公共的、一旦使用这个修饰符,意味着该成员可以直接被外部访问.
- protected: 仅对"家族"成员(同一个包下,不需要通过继承)开放访问权限,通常用于具有继承关系的类之间访问.
- private: 私人的,使用这个修饰符,意味着该成员是该类的私人属性,不提供给其他任何类进行访问.
- 包访问权限(friendly): 注意,Java中并没有提供这个修饰符,如果成员不声明任何的修饰符,那么就默认为包级别的访问权限.举个例子:
org.spring.framework是一个包,如果其中某个类没有显式声明访问权限,那么该属性在org.spring.framework这个包内都可以被访问.但是如果是org.apache.framework去访问这个属性,那就是属于越权了.
下面是关于修饰符的一些建议:
- 定义POJO类,所有的属性最好使用
private
关键字,除非你确认当前的属性是可以公开的. - 如果只希望继承基类(基类即上层的父类,在后面的章节会学习到)的子类访问相关属性,可以使用
protected
关键字. - 如果需要访问类的私有属性,通过访问器和修改器方法(getter和setter)进行访问.
- 工具类中的方法应提供
public
级别的访问. - 构造器方法通常是
public
的,但是如果你希望以静态方法的方式来访问类的功能,可以设置成private
(在一些单例模式中,也会将构造器设置成private
). - 类只能是
public
和包访问级别的,不能设置成private
和protected
.
类
static
被static
关键字标识的变量或者方法,在类加载的时候就会存储到JVM的方法区.对于生产出来的对象来说,它们是"共享"同一份静态域的.
- User.class
package com.tea.modules.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* 静态变量:int,在类加载的时候就会初始化为0
*/
public static int value = 0;
private String userName;
private String password;
protected int age;
private String phone;
}
- Test
package com.tea.modules.java8.oop;
import com.tea.modules.model.User;
/**
* 理解static
* @author jaymin
* @since 2021/4/10 16:15
*/
public class OOPDemo {
public static void main(String[] args) {
User userA = new User();
User userB = new User();
// userA对value值做一次自增,此时userB也会感知到这个存在
userA.incrValue();
System.out.println(userA.getValue());
System.out.println(userB.getValue());
}
}
- Result
1
1
- 为什么会产生static共享的现象

getter和setter
如果你有相关的编程经验,你会发现许多的Java对象都具备每个成员变量的getter
和settter
方法.编写这类方法的目的是因为一个对象中的属性通常为private
,外部类需要访问到这个数据,那么需要通过方法
,这类方法就是我们常用的getter
和setter
.如此一来,便可以屏蔽内部的一些信息,通过封装的方式来改变对象的状态(前面我们说过,对象中的数据即为状态).
这样做是否有必要呢?我们通过一个简单的例子来说明:
- User.class
package com.tea.modules.model;
import lombok.Data;
@Data
public class User {
/**
* 手机号码校验正则表达式
*/
private final String phoneRex = "^1[3|4|5|7|8][0-9]{9}$";
private String userName;
private String password;
private int age;
private String phone;
/**
* 校验手机号码是否正确
*/
public void validate(){
boolean isMatch = phoneRex.matches(this.phone);
if(!isMatch){
throw new RuntimeException("不正确的手机号码");
}
}
}
- Test
package com.tea.modules.java8.oop;
import com.tea.modules.model.User;
/**
*
* @author jaymin
* @since 2021/4/10 16:15
*/
public class OOPDemo {
public static void main(String[] args) {
User user = new User();
user.setPhone("111123000");
user.validate();
}
}
我们通过validate方法来封装了校验手机号码的这个过程(当然真实项目中并不是如此),通过validate方法屏蔽了代码细节,只需要调用方法即可达到校验的目的。
- 如果没有封装,那么在每一个需要校验手机号码的地方,可能都充斥着大量一致的代码,当你的代码出现这种症状,你需要考虑用封装的思维来重构你的代码.
- 万一在后期的版本中,产品经理需要更改校验手机号的逻辑,那么使用封装,你只需要修改一处代码;如果你采用的是粘贴+复制大法,那么请自求多福吧。总而言之,封装是为了让软件的健壮性和扩展性变得更加强大.
成员
final
final
在之前的变量和常量中讲解过了,final
关键字其实比较复杂,但是作为入门的开发者,我们先暂且记住final
是保证引用不可变的就行了.但是这个"不可变"很多人容易理解错误,下面我们通过例子来讲解:
package com.tea.modules.java8.string;
import java.util.HashMap;
import java.util.Map;
/**
* @author jaymin
* 2021/2/1 22:05
*/
public class FinalOfString {
/**
* 集合对象其实是在栈中持有引用.真正的数据存储在堆中.所以put操作是可行的.因为引用没有发生改变
*/
private static final Map<String, Object> finalMap = new HashMap();
private static final String FINAL_STRING = "finalString";
public static void main(String[] args) {
finalMap.put("a", 1);
// 此处会报错,为什么?因为String已经指向了常量池中的"finalString",String本身也是不可变的,所以重新赋值相当于更换引用.
// FINAL_STRING = "other";
}
}
成员属性应该如何设计
- 应尽量让类中的成员保持私有,对外提供
方法
来改变对象的状态. - 数据类型尽量别使用基础类型.
- 使用基础类型的弊端
package com.tea.modules.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String userName;
private String password;
private int age;
private String phone;
}
假设在网络传输中,对方传输了一个空对象,由于一些框架会进行json->对象的映射(往往会触发构造器),那么此时的User对象中的age就会被无端端的初始化为0了,这是比较危险的行为.如果使用包装器类型
Integer
,那么此时的age指向null
.
- getter和setter并不是成对出现的,比如定义枚举类型,通常只需要用到getter而不需要提供setter.
- 如果编写的是工具类,那么常量一定要使用
final
,防止别人修改.
方法
参数
输入参数与返回结果是方法关心的问题.
// valueOf输入一个字符串,返回一个数字
Integer one = Integer.valueOf("1");
我们来看看这个方法的声明:
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
valueOf是方法的名称,它要求输入一个String入参进行解析,返回一个
Integer
类型的结构,并且它是静态方法,在转换的过程中可能会发生异常.方法使用了public
,代表它是可以被公共访问的。
传递值还是传递引用
package com.tea.modules.java8.oop;
import com.tea.modules.model.User;
/**
* 理解值传递
* @author jaymin
* @since 2021/4/10 16:15
*/
public class OOPDemo {
public static void main(String[] args) {
User userA = new User();
User userB = new User();
userA.setUserName("张三");
userB.setUserName("李四");
swapUserName(userA,userB);
System.out.println(userA.toString());
System.out.println(userB.toString());
}
/**
* 尝试交换userA和userB的状态
* @param userA
* @param userB
*/
private static void swapUserName(User userA, User userB) {
User temp = userA;
userA = userB;
userB = temp;
}
}
这里我们创建了用户A和用户B,然后分别设置用户A和用户B的名字,通过方法
swapUserName
来尝试交换两者的名字,如果此时方法传参中将引用直接传递,那么此时我们可以看到用户A和用户B的名字进行了交换.
- Result

然而,事与愿违,这是因为在方法传递对象的时候,只传递了对象的引用拷贝,并不是真正的引用,因此交换方法在执行完后,变量userA和userB就被丢弃了,并未真正影响到main方法中的userA和userB.因此,我们说Java中的对象引用是"值传递"的。
脑洞大开一下:在swapUserName中执行userA.setName("值传递");会产生什么结果?
静态方法与非静态方法
被static
修饰的方法为静态方法,静态方法不需要实例化对象进行调用,可以直接用Class.method
的方式进行调用,例如JDK8提供的LocalDate.now()
便是一个经典的静态方法,该方法返回当前的时间.
- now
public static LocalDate now() {
return now(Clock.systemDefaultZone());
}
非静态方法便是不带static
的常规方法,这通常要求对对象初始化后才能调用,像上文中的validate
、getValue
都是需要实例化User
对象后才能调用的非静态方法.
什么情况下我们需要编写静态方法呢:
- 工具类.工具类通常不需要存储对象的状态,只需要关注参数调用类库方法即可.例如
Math.round
- 提供工厂方法生产对象.例如
LocalDate.of
. - main方法.main方法不需要创建对象,它只需要创建其他类所需要创建的对象.