目录
1.类的成员
类的成员可以包括:字段、方法、代码块、内部类和接口等;
1.1 字段/属性/成员变量
1.1.1 普通成员变量:
代码:
class Person{
public String name;
public int age;
public void printName(){
System.out.println("The person's name is "+name);
}
public void printAge(){
System.out.println("The person's age is "+age);
}
}
public class Main {
public static void main(String[] args) {
Person person1 = new Person();
person1.name="Mike";
person1.age=18;
System.out.print(person1.name+" ");
System.out.println(person1.age);
Person person2 = new Person();
person2.name="Jake";
person2.age=19;
System.out.print(person2.name+" ");
System.out.println(person2.age);
}
}
输出结果:
内存分布:
1.1.2 静态成员变量:
令写Student类代码如下:
class Student{
private String name;
private int age;
public static String classRoom = "Java001";
public void doClass(){
System.out.println("上课");
}
public Student(){
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student("Mike",18);
Student student2 = new Student("Jacky",19);
Student student3 = new Student("Mary",19);
System.out.println(student1.classRoom);
System.out.println(Student.classRoom);
System.out.println();
}
}
假设Student类表示某个班的学生,为了避免在每个学生对象内都存有班级信息而占空间,在Student类内定义一个静态的classRoom属性并就地初始化为"Java001"班,调试查看三个对象:
发现三个对象中并没有classRoom属性,说明该属性不属于对象;
输出结果:
由于静态成员变量不属于对象,故而可以使用类名.静态成员变量的方式进行访问;
内存分布:
被static修饰的静态成员变量也被称为类变量,存储在方法区中,是不依赖对象的:
静态成员变量时类的属性,是在JVM加载类时开辟空间并初始化的;
注:一般情况下,静态成员变量的初始化方式有四种:
方法一:直接赋值,如上文代码:
public static String classRoom = "Java001";
方法二:默认初始化;
方法三:通过get和set方法进行初始化:
//假设某类中含有静态的size属性
public static void setSize(int size){
Student.size=size;
}
public static int getSize(){
return size;
}
方法四:在构造对象时在构造方法中赋值:
public Student(String name,int age,int si){
this.name=name;
this.age=age;
size=si; //不建议这样书写
}
但并不建议采取该种方法进行初始化,这样的初始化方法每实例化一个对象就会将共用的静态成员变量size重新赋值一次,在实际应用中并不可靠;
1.2 方法
1.2.1 普通成员方法:
代码:
class Person{
public String name;
public int age;
public void printName(){
System.out.println("The person's name is "+name);
}
public void printAge(){
System.out.println("The person's age is "+age);
}
}
public class Main {
public static void main(String[] args) {
Person person1 = new Person();
person1.name="Luke";
person1.age=20;
person1.printName();
person1.printAge();
}
}
输出结果:
(普通成员方法与普通成员变量读写方法相同)
注:一般情况下成员方法都会设置为public;
1.2.2 静态成员方法:
class Person{
//静态成员方法
public static void staFun(){
System.out.println("static function");
}
}
public static void main(String[] args) {
Person.staFun(); //无需再创建对象
}
输出结果为:
注:(1)普通方法内部不可以定义静态变量,但是可以调用静态方法 ;
(2)在静态方法内部不可以调用普通方法;
(3)main函数使用static定义是取决于JVM的底层实现逻辑的;
(4)引用不一定存在于栈上;
(5)静态方法内不能使用this;
(6)建议将获取静态成员变量与设置静态成员变量的方法设置为静态方法,这样可以避免实例化对象才可使用;
1.3 static 关键字
(1)修饰属性:static修饰的属性不依赖于对象存在,是属于类的,上文已作详细解释此处不再赘述;
(2)修饰方法:静态方法也是属于类而非对象的,可以直接调用静态方法、无需创建对象。
由于static关键字的特殊性,导致:
① 静态方法不能直接使用非静态数据成员或调用非静态方法,因为他们都是与实例相关的;
② this 和super两个关键字不能在静态上下文中使用;
③ 曾经的方法学习为了简单,统一加上了static修饰,实际上是否需要static修饰还需按情景而定;
④ 一个对象存储在什么位置与是否被final修饰无关;
1.4 默认初始化与就地初始化
1.4.1 默认初始化
new在程序层面只是一行语句,但是在JVM层面做了诸多工作:
① 检测对象对应的类是否已经加载,如果没有加载则加载;
② 为对象分配内存空间;
③ 处理并发安全问题:如多个线程同时申请对象,JVM要保证给对象分配的空间不冲突;
④ 初始化所分配的空间即对象被申请好之后,对象中包含的成员已经设置好了初始值,这也是区别于普通局部变量初始化的地方:
对于局部变量,在使用局部变量之前必须进行初始化:比如运行以下代码:
int a;
System.out.println(a);
编译器会报错:
但是对于类的属性,在未初始化的情况下,编译器会赋予它们一个默认值:
比如运行以下代码:
class Person{
public String name;
public int age;
}
public class Main{
public static void main(String[] args) {
Person person1 = new Person();
System.out.println(person1.name);
System.out.println(person1.age);
}
}
输出结果为:
1.4.2 就地初始化
在声明成员变量时就直接给初始值称为就地初始化;
class Date{
public int year=1997;
public int month=8;
public int day=5;
public Date(){
}
public Date(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
}
@Override
public String toString() {
return "Date{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
public class Main {
public static void main(String[] args) {
Date date1 = new Date(2014,9,15);
Date date2 = new Date();
System.out.println(date1);
System.out.println(date2);
}
}
输出结果为:
这种使用方法并不常见,只有在特殊需求下才会偶尔使用;
2. 封装
面向对象三大特性:封装、继承、多态。在类与对象阶段主要研究封装;
2.1 访问限定符
No | 范围 | private | default | protected | public |
1 | 同一包中的同一类 | √ | √ | √ | √ |
2 | 同一包中的不同类 | √ | √ | √ | |
3 | 不同包中的子类 | √ | √ | ||
4 | 不同包中的非子类 | √ |
注意:(1)default不是访问权限,表示默认权限;
(2)protected 主要使用在继承中;
2.2 包
2.2.1 包的概念
在面向对象体系中提出了一个软件包的概念,即:为了更好地管理类,把多个类收集在一起成为一组,成为软件包。在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或接口等很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可;
2.2.2 导入包中的类
(1)在学习数组时,会经常使用到Arrays的相关方法,此时就需要导入Arrays所在的util包,即编写在代码主体前的:import java.util.Arrays; 语句;
一般导入包可以根据编译器提示,或是OverView进行查询。
附:OverView网址:https://docs.oracle.com/javase/8/docs/api/
(2)如果使用import导入包,则需要在使用该类前进行注明,以Date类为例:
导入包时:
import java.util.Date;
public class Main{
public static void main(String[] args) {
Date date = new Date();
}
}
不导入包时:
java.util.Date date = new java.util.Date();
(3)也可以使用以下方法进行包的导入:
import java.util.*;
注意:java的import java.util.*; 方式与C语言的不同,C语言中#include<stdlib.h>会将该文件中的所有类在预编译时就导入当前文件中,但是java是一个随用随取机制,更类似于C++的namespace和using;
建议在写程序时注明需要使用的包的类名,这样会避免不同包中含有同名类而导入的冲突问题:
如:util与aql包中都包含Date类,当没有注明包的类时,就会出现引用不明确:
import java.util.*;
import java.sql.*;
public class Main{
public static void main(String[] args) {
Date date = new Date();
}
}
报错如下:
(4)也可以使用import static 导入包中静态的方法的字段:
import static java.lang.Math.*;
public class Main {
public static void main(String[] args) {
double x=30;
double y=40;
double result=sqrt(pow(x,2)+pow(y,2));
System.out.println(result);
}
}
相比于每一次标明Math.的表达会更方便;
2.2.3 自定义包
(1)基本规则:
① 在文件的最上方加一个package语句指定该代码在哪个包中;
② 包名需要尽量指定成唯一的名字,通常会使用公司的域名的颠倒形式;
③ 包名要和代码路径相匹配;
(2)应用示例:
其实创建包就是在不同文件夹中再创建文件夹:
据上图,TestDemo.java文件首行的package com.boda.xy; 表明TestDemo类在com.boda.xy包中:
2.2.4 包的访问权限控制
基于以上包与类关系,以不同包的不同类为例:编写如下代码:
在TestDemo中访问TestDemo2的类中的默认权限属性,结果与2.1表格情况对应,编译器报错;
2.2.5 常见的包
(1)java.lang:系统常用基础类(String,Object),此包从JDK1.1后自动导入;
(2)java.lang.reflect:反射编程包;
(3)java.net:进行网络编程开发包;
(4)java.sql:进行数据库开发的支持包;
(5)java.util:是java提供的工具程序包;(集合类等)
(6)java.io:I/O编程开发包;
2.3 private 实现封装
class Person{
//普通成员变量
private String name; //private修饰即代表封装,只能在当前类中使用
private int age;
//提供接口
public void setName(String myName){
name=myName;
}
public String getName(){
return name;
}
public void setAge(int myAge){
age=myAge;
}
public int getAge(){
return age;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Mike");
person.setAge(19);
System.out.println(person.getName());
System.out.println(person.getAge());
}
输出结果为:
注:(1)在编写setName于setAge方法时,注意二者的形参避免与成员变量命名相同的情况,会导致局部变量自身赋值而对类的成员变量赋值失败的问题出现,为了解决该问题,可以使用this指针:
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
this指针代表当前对象的引用;
2.4 getter 与 setter 方法
java编译器也提供了相应的getter与setter方法来实现对已封装的类的成员变量的读写操作:
step 1:alt+insert打开generate窗口
step 2:选择Getter and Setter 选项进入设置:
step 3:选择name:String 与age:int 选项点击OK即可:
此时译器就会自动生成get与set函数;
3. 构造方法
3.1 基本语法
(1)构造方法的方法名与类名相同,是一个没有返回值的初始化方法;
(2)对象的实例化过程:① 为对象分配内存;② 调用合适的构造方法;
(3)当没有自定义编写任何构造方法时,编译器会默认生成一个无参的构造方法,但如果已经自
定义编写了其他的带参构造方法,那么编译器就不会再自动生成了,如:
class Person{
//普通成员变量
private String name; //private修饰即代表封装,只能在当前类中使用
private int age;
//构造方法
// public Person(){
// System.out.println("Person();");
// }
public Person(String name){
this.name=name;
System.out.println("Person(String name);");
}
public Person(String name,int age){
this.name=name;
this.age=age;
System.out.println("Person(String name,int age);");
}
public static void main(String[] args) {
Person person1 = new Person();
System.out.println("----------------------------");
Person person2 = new Person("Mike");
System.out.println("----------------------------");
Person person3 = new Person("Jake",19);
}
将显式书写的无参构造方法注释后,编译器报错如下:
(4)构造方法之间可以构成重载,传递的参数不同,调用的构造方法不同:
class Person{
//普通成员变量
private String name; //private修饰即代表封装,只能在当前类中使用
private int age;
//构造方法
public Person(){
System.out.println("Person();");
}
public Person(String name){
this.name=name;
System.out.println("Person(String name);");
}
public Person(String name,int age){
this.name=name;
this.age=age;
System.out.println("Person(String name,int age);");
}
public static void main(String[] args) {
Person person1 = new Person();
System.out.println("----------------------------");
Person person2 = new Person("Mike");
System.out.println("----------------------------");
Person person3 = new Person("Jake",19);
}
输出结果如下:
3.2 this 关键字
this 表示当前对象的引用,可以借助this访问类的属性与方法;
(1)this.data:调用当前对象的属性:
当类的方法的参数与类的成员变量名称相同时,需要使用this.成员变量名表示被赋值变量名;
(2)this.func(): 调用当前对象的方法:
注意不能调用静态方法;
class Date{
public int year;
public int month;
public int day;
public void setDate(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
}
public void showDate(){
System.out.println("Year: "+year+", Month: "+month+", Day: "+day);
}
}
public class Main{
public static void main(String[] args) {
Date date1 = new Date();
date1.setDate(1997,8,5);
date1.showDate();
System.out.println("-----------------------------");
Date date2 = new Date();
date2.setDate(2014,9,15);
date2.showDate();
System.out.println("-----------------------------");
Date date3 = new Date();
date3.setDate(2019,6,27);
date3.showDate();
}
}
输出结果为:
(3)this():调用当前对象的其他构造方法:
使用示例1:一般情况下会使用在无参构造方法中可调用带参构造方法:
class Person{
//普通成员变量
private String name;
private int age;
//构造方法
public Person(){
this("Mike"); //调用一个参数的构造方法
System.out.println("Person();");
}
public Person(String name){
this.name=name;
System.out.println("Person(String name);");
}
输出结果为:
使用示例2:
class Date{
public int year;
public int month;
public int day;
//无参构造方法
public Date(){
this(1997,8,5); //调用三参构造方法
System.out.println("Date()");
}
//二参构造方法
public Date(int month,int day){
this.month=month;
this.day=day;
System.out.println("Date(int month,int day)");
}
//三参构造方法
public Date(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
System.out.println("Date(int year,int month,int day)");
}
@Override
public String toString() {
return "Date{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
public class Main {
public static void main(String[] args) {
Date date1 = new Date();
System.out.println(date1);
}
}
输出结果为:
调用逻辑为:Date实例化date1时,没有传递实参,故而首先调用无参构造方法,执行进入无参构造方法后,this(1997,8,5)又调用了三参构造方法;
注:① this();表示调用当前对象的构造方法,注意不能如下般使用,会导致递归调用:
public Person(){
this();
}
② 在无参构造函数中只能在首行调用一次this();调用其他构造函数;
③ 不可以在类的成员方法中调用this();只能在构造方法中使用;
4.代码块
代码块包括很多种:本地代码块、实例代码块、静态代码块和同步代码块。
4.1 普通代码块:
定义在方法内部的代码块称为普通代码块,简单使用示例如下:
class Student{
private String name;
private String classNum;
public Student() {
}
public Student(String name, String classNum) {
this.name = name;
this.classNum = classNum;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassNum() {
return classNum;
}
public void setClassNum(String classNum) {
this.classNum = classNum;
}
public static void fun(){
Student student = new Student("Mike","Java001");
System.out.println(student);
{
// 该代码块不需要条件直接执行
System.out.println("普通代码块");
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", classNum='" + classNum + '\'' +
'}';
}
}
public class Demo1 {
public static void main(String[] args) {
Student.fun();
}
}
但该种写法非常少见也并不推荐,极少数应用场景应用于:限制代码作用域:
public static void main(String[] args) {
{
int a=10;
}
System.out.println(a);
}
运行上述代码,报错如下:
4.2 构造代码块(非静态代码块、实例代码块)
在类内部、方法外的代码块称为构造代码块:
package TestDemo1;
class Student{
private String name;
private String classNum;
public Student() {
System.out.println("不带参数的构造方法");
}
{
System.out.println("非静态代码块、实例代码块、构造代码块");
}
public Student(String name, String classNum) {
this.name = name;
this.classNum = classNum;
System.out.println("带三个参数的构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassNum() {
return classNum;
}
public void setClassNum(String classNum) {
this.classNum = classNum;
}
}
public class Demo1 {
public static void main(String[] args) {
Student student = new Student();
}
}
输出结果为:
会发现:
(1)在调用构造方法前会先调用构造代码块,且二者编写位置的前后不影响执行顺序;
(2)构造代码块一般用于初始化非静态数据成员,如:
代码示例1:调用带两个参数的构造方法进行实例化:
package TestDemo1;
class Student{
private String name;
private String classNum;
public Student() {
System.out.println("不带参数的构造方法");
}
{
name = "Jack";
System.out.println("非静态代码块、实例代码块、构造代码块");
}
public Student(String name, String classNum) {
this.name = name;
this.classNum = classNum;
System.out.println("带三个参数的构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassNum() {
return classNum;
}
public void setClassNum(String classNum) {
this.classNum = classNum;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", classNum='" + classNum + '\'' +
'}';
}
}
public class Demo1 {
public static void main(String[] args) {
Student student = new Student("Mike","Java001");
System.out.println(student);
}
}
在实例代码块中,"Jack"赋值给name,实例化时在main方法内传递的“MIke”是传递了给了构造方法,而构造方法后于实例代码块执行,故而实例代码块的赋值被覆盖,故而输出结果为:
代码示例2:调用不带参数的构造方法进行示例化:
package TestDemo1;
class Student{
private String name;
private String classNum;
public Student() {
System.out.println("不带参数的构造方法");
}
{
name = "Jack";
System.out.println("非静态代码块、实例代码块、构造代码块");
}
public Student(String name, String classNum) {
this.name = name;
this.classNum = classNum;
System.out.println("带三个参数的构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassNum() {
return classNum;
}
public void setClassNum(String classNum) {
this.classNum = classNum;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", classNum='" + classNum + '\'' +
'}';
}
}
public class Demo1 {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student);
}
}
由于此时调用的是不带参数的构造方法,不会导致实例代码块中的初始赋值被覆盖,故而输出结果为:
注:假设当前类属性存在默认值,此时如果非静态代码块与非静态类成员的赋值不同,则取决于定义位置的先后顺序;
4.3 静态代码块:
被static修饰的代码块称为静态代码块,一般用于初始化静态的数据成员或提前准备一些数据;
代码1:静态代码块先于实例代码块执行并且只执行一次:
package TestDemo1;
class Student{
private String name;
private String classNum;
public Student() {
System.out.println("不带参数的构造方法");
}
{
name = "Jack";
System.out.println("非静态代码块、实例代码块、构造代码块");
}
static{
System.out.println("静态代码块");
}
public Student(String name, String classNum) {
this.name = name;
this.classNum = classNum;
System.out.println("带三个参数的构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassNum() {
return classNum;
}
public void setClassNum(String classNum) {
this.classNum = classNum;
}
public static void fun(){
Student student = new Student("Mike","Java001");
System.out.println(student);
{
// 该代码块不需要条件直接执行
System.out.println("普通代码块");
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", classNum='" + classNum + '\'' +
'}';
}
}
public class Demo1 {
public static void main(String[] args) {
Student student1 = new Student();
System.out.println("------------------");
Student student2 = new Student();
}
}
输出结果为:
注:① 执行顺序:静态代码块、实例(非静态、构造)代码块、构造方法;
② 在程序实际上编译时,会将实例代码块合并到构造方法中的最前列,故而先执行实例代码块再执行构造方法原本的语句;
代码2:静态代码块不依赖对象存在,不实例化对象也可执行静态代码块:
package TestDemo1;
class Student{
private String name;
private String classNum;
public Student() {
System.out.println("不带参数的构造方法");
}
{
name = "Jack";
System.out.println("非静态代码块、实例代码块、构造代码块");
}
static{
System.out.println("静态代码块");
}
public Student(String name, String classNum) {
this.name = name;
this.classNum = classNum;
System.out.println("带三个参数的构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassNum() {
return classNum;
}
public void setClassNum(String classNum) {
this.classNum = classNum;
}
public static void fun(){
Student student = new Student("Mike","Java001");
System.out.println(student);
{
// 该代码块不需要条件直接执行
System.out.println("普通代码块");
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", classNum='" + classNum + '\'' +
'}';
}
}
public class Demo1 {
public static void main(String[] args) {
Student.fun();
}
}
输出结果为:
注:① 静态代码块内不可以使用this,但可以读写静态变量;
② 静态代码块不依赖于对象存在,只要这个类被加载,则类内的静态代码块都会被执行;
5. toString 方法
仍以person类为例,基于前面setter与getter,已经可以读写已封装的类属性;
在打印输出时,可以使用getter方法打印类属性,但还是需要手动调用;
试直接将person对象传递给println方法作为参数:
class Person {
//普通成员变量
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Mike");
person.setAge(19);
System.out.println(person);
}
}
输出结果为:
java底层实现println的逻辑与机制精简后如下:
即println函数底层使用了toString方法进行,引出两种打印对象的方式:
法1:自定义toString方法:
class Person{
//普通成员变量
private String name;
private int age;
//自定义
public String toString(){
return "name: "+name+", age: "+age;
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("Mike");
person.setAge(19);
System.out.println(person);
}
输出结果如下:
法2:编译器自动生成:
类似于上文Getter and Setter方法的自动生成模式,alt+insert选择toString()方法后选定属性即可自动生成:
class Person{
//普通成员变量
private String name;
private int age;
//编译器自动生成
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("Mike");
person.setAge(19);
System.out.println(person);
}
输出结果为:
也可以在该代码基础上进行调整与修改输出的格式以达到不同的输出要求;
注:将一个对象转化为字符串称为序列化,将一个字符串转变为对象称为反序列化;
6. 匿名对象
class Person{
public String name;
public int age;
public void printName(){
System.out.println("The person's name is "+name);
}
public void printAge(){
System.out.println("The person's age is "+age);
}
}
public static void main(String[] args) {
//创建匿名对象
new Person().printName();
new Person().printAge();
}
(1)没有引用的对象称为匿名对象;
(2)匿名对象只能在创建对象时使用;
(3)如果一个对象只使用一次后续不再需要使用就可以考虑使用匿名对象,否则重复创建匿名对象反倒占用空间;
附:类与对象的经典应用之使用方法交换两个变量的值:
class MyValue{
private int val;
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
}
public class Main {
public static void swap(MyValue x,MyValue y){
int tmp = x.getVal();
x.setVal(y.getVal());
y.setVal(tmp);
}
public static void main(String[] args) {
MyValue myValue1 = new MyValue();
myValue1.setVal(10);
MyValue myValue2 = new MyValue();
myValue2.setVal(20);
System.out.println("a = "+myValue1.getVal()+", b = "+myValue2.getVal());
swap(myValue1,myValue2);
System.out.println("a = "+myValue1.getVal()+", b = "+myValue2.getVal());
}
}