1.对象的构造以及初始化:
1.1初始化对象:
前面知识里,在java内部方法中定义一个局部变量(定义在方法、构造方法、代码块或者方法参数中的变量),必须要初始化,要不然会编译失败。
public static void main(String[]args){
int a;
System.out.println(a);
}
上面这个代码,要将a初始化之后,才能编译通过。
class Date{
int year;
int month;
int day;
//构造方法进行初始化
public Date(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
}
public void printDate(){
System.out.println(this.year+"年"+this.month+"月"+this.day+"日");
}
}
public static void main(String[]args){
Date d=new Date();
d.printDate();
d.setDate(2021,6,9);
d.printDate();
}
上面这个代码里:
1.2构造方法:
构造方法(也称构造器)是一种特殊的成员方法,构造方法的名字必须和类名相同,在创建对象的时候,由编译器自动调用,并且在整个对象的生命周期中只调用一次。
构造方法:
名字与类名相同。没有返回值类型,设置为void也不行。
一般情况下使用public修饰。
在创建对象的时候编译器自动调用,并且在对象的生命周期中只调用一次。
class Date{
int year;
int month;
int day;
public Date(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
}
public void printDate(){
System.out.println(this.year+"年"+this.month+"月"+this.day+"日");
}
}
public static void main(String[]args){
Date d=new Date(2021,6,9);//这里创建了一个Date的对象,并没有显式调用构造方法,但是调用了Date(int.int,int)的构造方法
d.printDate();
}
注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
1.3构造方法的特性:
1.名字必须与类名相同。
2.没有返回值类型,设置为void也不行
3.由编译器自动调用,并且在对象的生命周期中只调用一次。
4.构造方法也可以重载(用户根据自己的需求,提供不同参数的构造方法)
class Date{
int year;
int month;
int day;
//构造方法1
public Date(){
this.year=2001;
this.month=12;
this.day=29;
}
//构造方法2
public Date(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
}
public void printDate(){
System.out.println(this.year+"年"+this.month+"月"+this.day+"日");
}
上面代码中,Date类中,有两个名字相同,参数列表不同的构造方法,因此构成了构造方法的重载。可以在实例化对象的时候调用合适的构造方法。
如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
注意:一旦用户定义,编译器便不再生成默认的构造方法。
class Date{ int year; int month; int day; //默认的无参的构造方法 public Date(){ } public void printDate(){ System.out.println(this.year+"年"+this.month+"月"+this.day+"日"); } } public static void main(String[]args){ Date d=new Date();//这里创建了一个Date的对象,并没有显式调用构造方法,但是调用了Date(int.int,int)的构造方法 d.printDate(); }
构造方法中可以通过this来调用其他构造方法来简化代码。
class Date{
int year;
int month;
int day;
//构造方法1,这里可以通过this来调用构造方法2
public Date(){
this(2019,2,3);//记住这里this调用必须在第一句
}
//构造方法2
public Date(int year,int month,int day){
this.year=year;
this.month=month;
this.day=day;
}
public void printDate(){
System.out.println(this.year+"年"+this.month+"月"+this.day+"日");
}
注意:
1.this(......)调用必须在第一句。
2.而且不能成环(比如上面这个代码,在构造方法1中用this调用了构造方法2,所以就不能在构造方法2中用this调用构造方法1)
构造方法大部分用public来修饰,特殊场景用private修饰(后面讲)
1.4默认初始化:
上面提出了一个问题,为什么局部变量必须要赋初值,不然编译出错,而成员变量不用赋初值,就能通过编译。
要搞清楚这个过程,就要知道new背后发生的一切。
Date d=new Date;
在程序层面,就一句简单的代码,但在JVM层面要做许多事情
1.检测对应对象的类是否加载了,如果没有加载则加载(一个类只加载一次)
2.为对象分配空间。
3.处理并发安全问题。比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突。
初始化所分配的空间:
数据类型 默认值byte 0char '\u0000'short 0int 0long 0boolean falsefloat 0.0fdouble 0.0reference null
1.5就地初始化:
public class Date {
public int year = 1900;
public int month = 1;
public int day = 1;
public Date(){
}
public Date(int year, int month, int day) {
}
}
注意:代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造函数中 。
2.封装:
面向对象程序的三大特征:封装,继承,多态。在类与对象阶段,主要研究就是封装特性,何为封装,就是套壳屏蔽细节。
举一个例子:对于计算机这个复杂设备,计算机厂商在出厂时,在外部套上壳 子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。
封装:就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装扩展之包:
java中允许使用包将类组织在一个集合中,借助包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理,在面向对象体系中,提出了软件包的概念,为了更好管理类,就把多个类收集在一起成为一组,称为软件包。比如为了更好管理歌曲,就把属性相似的歌曲放在同一个文件下面,也可以在某个文件下进行详细的分类
自定义包:
使用包的主要原因是确保类名唯一性,假如有两个程序员不约而同的建立了Employee类,只要将这些类放置在不同的包中,就不会产生冲突,为了保证包名的唯一性,要用一个因特网域名(这显然是唯一的)以逆序的方式作为包名,然后对于不同的工程使用不同的子包。比如:域名horstmann.com。如果逆序来写,就得到包名com.horstmann。然后追加一个工程名,如com.horstmann.corejava。如果再把Employee类放在这个包里面,那么这个类的”完全限定“名就是com.horstmann.corejava.Employee。
注释:从编译器来看,嵌套包之间没有任何关系,例如,java.until包和java.until.jar包毫无关系,每一个包都是独立的类集合。
类的导入:
一个类可以使用所属包的所有类,以及其他包中的公共类
我们可以采用两种方式访问另一个包中的公共类。
第一种就是使用完全限定名字,就是包名后面跟着类名,例如:
java.time.LocalDate.today=new java.time.LocalDate();
这是很繁琐的方法,第二种方法就是使用import语句。import语句是一种引用包中的各个类的简捷方式,一旦使用了import语句,在使用类的时候,就不必写出类的全名了。可以使用import语句导入一个特定的类或者整个包。import语句应该位于源文件顶部(但位于package语句后面)。例如:用下面这条语句导入java.util包中的所有类。(注意在import导包的时候,要具体到某个类上)
import.java.time.*;
然后,就可以使用此包中所有的类成员。比如:LocatDate today =new LocalDate();就不用在前面加上包的前缀。还可以导入一个包的特定类:
import java.time.LocalDate;
java.time.*的语法比较简单,对代码规模也没有任何负面影响,不过,如果能够明确的指出所导入的类,代码会让读者更加准确的知道你使用了那些类。大多情况,可以只导入你需要的包,但在发生文件名冲突的时候,就要注意包了,java.util和java.sql包都有Date类,如果在同一个文件中导入这两个包:
import.java.util.*;
import.java.sql.*;
此时在使用Date类的时候,就会出现一个编译错误:
Date.today;//ERROR--java.util.Date or java.sql.Date?
此时编译器不知道你到底要用哪一个包下面的Date类。如果这两个Date类都要使用,又该怎么办呢?答案:在每个类名前面加上完整的包名。
静态导入包:
有一种import语句允许导入静态方法和静态字段,而不是只有类。例如:
import static java.lang.System.*;
然后就可以使用System类的静态方法和静态字段,且不用加类名的前缀:比如println来打印一个Hello world,就可以写成,out.println("Hello world");省略掉了System前缀。
3.static成员:
1.static修饰成员变量:
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。
静态成员变量的属性:
1.不属于具体某个对象,是类的属性,所有对象所共享的,不储存在某个对象的空间中。
2.既可以通过对象访问,也可以通过类名访问,一般更推荐用类名访问。
3.类变量(static修饰的成员变量)储存在方法区当中。
4.生命周期伴随类的一生(随类的加载而创建,随类的卸载而销毁)
class Main {
public String name;
public int age;
public static int a;
public Main(String name,int age){
}
public static void main(String[] args) {
Main main1=new Main("aaa",20);
Main main2=new Main("bbb",21);
main1.a=2;
main2.a=3;
}
}
调试上面这个代码,然后在监视窗口可以看到,静态成员变量a并没有储存到某个具体的对象中。
注意:静态成员变量不依赖于对象
2.static修饰的成员方法:
被static修饰的成员方法,也被称为类方法,和成员变量一样不依赖于对象,不是某个对象所特有的,静态成员变量一般是通过静态成员方法来访问的。
看上面这个代码,在func(静态方法)中调用了name(非静态成员变量),在静态方法中是不能出现this关键字的,因为静态成员方法是不依赖于对象的,而this是指向当前对象的引用,依赖于对象的,在一个静态方法中调用非静态的变量,是行不通的。所以只能通过在静态方法中实例化对象,通过对象来调用非静态成员变量。
看上面这个代码,可以在静态方法中实例化一个对象,然后通过对象调用非静态的成员变量(这种也是可行的)
在静态方法中调用非静态方法也是一样。因为非静态方法有this参数,在静态方法调用时无法传递this引用,但是可以通过实例化对象来进行调用非静态方法(如右图)
总结:
调用静态成员变量或者静态方法:1.通过对象的引用调用。2.通过类名来调用
在静态方法中,不能直接使用非静态的成员变量和成员方法,要想使用,要通过new对象,然后通过对象引用访问。
但是在非静态方法中,可以直接使用静态成员变量和静态成员方法
注意:静态的不依赖于对象,属于类,不属于对象。
3.static成员变量的初始化:
注意:静态方法一般不在构造方法中进行初始化,构造方法初始化的是与对象相关的实例属性。
静态成员变量初始化分以下几种:
(1)就地初始化
(2)通过get set方法初始化
(3)构造方法【较少使用】
(4)代码块初始化:静态代码块、非静态代码块/实例代码块/构造代码块、
同步代码块(暂时不提)
1.就地初始化:
如下图:
在定义变量的时候就进行赋值,称就地初始化。
2. 通过get set方法初始化:
如下图:
3.代码块初始化:
那啥叫代码块呢?代码块是一段被{ }括起来的一段代码,代码块可以在类,方法中使用。
一般用来成员的初始化。
实例方法块是在对象的每个方法执行之前执行的代码块,它使用花括号括起来,并且没有任何修饰符,不带参数。实例方法块可以用于在每个实例方法执行之前执行一些通用的操作,例如检查对象状态或设置对象属性。
如果有多个实例代码块:则会按照定义顺序执行。
静态代码块(也称为静态初始化块):使用关键字
static
定义的代码块。静态代码块在类加载时执行,并且只执行一次。用来初始化静态成员变量
在静态代码块中也不能用来初始化非静态成员变量。因为静态代码块不依赖于对象,但是成员变量是会有this调用,其是依赖对象的,所以不能初始化。
静态代码块不管创建多少次对象,其只会执行一次:
运行结果:
可以得出:不管生成多少个对象,静态代码块只会执行一次。
如果出静态代码块,都是静态的情况下,输出的结果和定义静态代码块的顺序有关。
运行结果:
那如果在一个类中,同时存在静态代码块,实例代码块和构造方法,那执行顺序又是怎么样呢?
请看下面这个代码:
运行结果:
打乱顺序的运行结果也是如此,可以自行操作。
可知,静态代码块先执行,其次是实例代码块执行,最后是构造方法被执行。