1、用构造器确保初始化
java中通过提供构造器,可确保每个对象都会得到初始化。创建对象时,如果其类具有构造器,那java会在用户操作对象之前自动调用相应的构造器,从而保证初始化的进行。
构造器的名称必须与类名完全相同。避免与成员名称相冲突,也方便编译器知道调用哪个方法。不接受任何参数的构造器叫做默认构造器,也成为无参构造器。也可以使用带有形式参数的构造器:
public class ConstructorTest {
ConstructorTest(){
System.out.println("无参构造器");
}
ConstructorTest(int a){
System.out.print("有参构造器");
}
}
如果类中没有构造器,编译器会自动创建一个默认的构造器,如果已经定义了一个构造器(无论是否有参数),编译器就不会再自动创建默认构造器。
在创建对象时,new ConstructorTest()将会为对象分配存储空间,并调用相应的构造器。
在java中,初始化和创建捆绑在一起,两者不能分离。
构造器是一种特殊类型的方法,它没有返回值。这与返回值为空(void)明显不同。对于空返回值,尽管方法本身不会自动返回什么,但仍可选择让它返回别的东西。构造器则不会返回任何东西。new表达式确实返回了新建对象的引用,但是构造器本身并没有任何返回值。
2、方法重载
方法名相同而形式参数不同。每一个重载的方法都必须有一个独一无二的参数类型列表。参数顺序的不同也可以区分两个方法。如果编译器能够明确判断出语义,那么也可以考虑用返回类型区别方法重载;但是有时候忽略返回值类型,只是单纯调用方法,那就不好区分了。
static void f(int a,String b){
System.out.print("整形a,字符串b");
}
static void f(String a,int b){
System.out.print("字符串a,整形b");
}
基本类型的重载:
基本类型能从一个较小的类型字段提升为一个较大的类型。
void f1(char a){ System.out.print("char类型方法f1 ");}
void f1(byte a){ System.out.print("byte类型方法f1 ");}
void f1(short a){ System.out.print("short类型方法f1 ");}
void f1(int a){ System.out.print("int类型方法f1 ");}
void f1(long a){ System.out.print("long类型方法f1 ");}
void f1(float a){ System.out.print("float类型方法f1 ");}
void f1(double a){ System.out.print("double类型方法f1 ");}
void f2(byte a){ System.out.print("byte类型方法f2 ");}
void f2(short a){ System.out.print("short类型方法f2 ");}
void f2(int a){ System.out.print("int类型方法f2 ");}
void f2(long a){ System.out.print("long类型方法f2 ");}
void f2(float a){ System.out.print("float类型方法f2 ");}
void f2(double a){ System.out.print("double类型方法f2 ");}
void f3(short a){ System.out.print("short类型方法f3 ");}
void f3(int a){ System.out.print("int类型方法f3 ");}
void f3(long a){ System.out.print("long类型方法f3 ");}
void f3(float a){ System.out.print("float类型方法f3 ");}
void f3(double a){ System.out.print("double类型方法f3 ");}
void f4(int a){ System.out.print("int类型方法f4 ");}
void f4(long a){ System.out.print("long类型方法f4 ");}
void f4(float a){ System.out.print("float类型方法f4 ");}
void f4(double a){ System.out.print("double类型方法f4 ");}
void f5(long a){ System.out.print("long类型方法f5 ");}
void f5(float a){ System.out.print("float类型方法f5 ");}
void f5(double a){ System.out.print("double类型方法f5 ");}
void f6(float a){ System.out.print("float类型方法f6 ");}
void f6(double a){ System.out.print("double类型方法f6 ");}
void f7(double a){ System.out.print("double类型方法f7 ");}
1、char类型:有char类型的形式参数的方法,则调用该方法;如果没有该类型的方法,直接提升至int类型;若int型也没有,直接提升至long或者double型:
public static void main(String[] args) {
PrimitiveOverLoad pol=new PrimitiveOverLoad();
char c='x';
pol.f1(c); pol.f2(c); pol.f3(c);
pol.f4(c); pol.f5(c); pol.f6(c);
pol.f7(c);
}
结果:
char类型方法f1 int类型方法f2 int类型方法f3 int类型方法f4 long类型方法f5 float类型方法f6 double类型方法f7
2、byte型参数:有该类型参数的方法,那就调用该方法,没有的话,就一步步提升较大类型:
public static void main(String[] args) {
PrimitiveOverLoad pol=new PrimitiveOverLoad();
byte c=1;
pol.f1(c); pol.f2(c); pol.f3(c);
pol.f4(c); pol.f5(c); pol.f6(c);
pol.f7(c);
}
结果:
byte类型方法f1 byte类型方法f2 short类型方法f3 int类型方法f4 long类型方法f5 float类型方法f6 double类型方法f7
3、short型参数:有该类型参数的方法,那就调用该方法,没有的话,就一步步提升较大类型:
public static void main(String[] args) {
PrimitiveOverLoad pol=new PrimitiveOverLoad();
short c=1;
pol.f1(c); pol.f2(c); pol.f3(c);
pol.f4(c); pol.f5(c); pol.f6(c);
pol.f7(c);
}
结果:
short类型方法f1 short类型方法f2 short类型方法f3 int类型方法f4 long类型方法f5 float类型方法f6 double类型方法f7
4、int型参数:有该类型参数的方法,那就调用该方法,没有的话,就一步步提升较大类型:
public static void main(String[] args) {
PrimitiveOverLoad pol=new PrimitiveOverLoad();
int c=1;
pol.f1(c); pol.f2(c); pol.f3(c);
pol.f4(c); pol.f5(c); pol.f6(c);
pol.f7(c);
}
结果:
int类型方法f1 int类型方法f2 int类型方法f3 int类型方法f4 long类型方法f5 float类型方法f6 double类型方法f7
其他剩余类型long、float、double都是类似的规则。
3、this关键字
同一个类的不同对象调用同一个方法,在编译器内部,将不同对象的引用作为第一个参数传递给该方法。但是,如果希望在方法内部获得这个当前对象的引用,可以使用 this 关键字。
this关键字只能在方法内部使用,表示对”调用方法的那个对象”的引用。与其他对象的引用的用法一样。但是,如果在方法内部调用同一个类的其他方法,可以不必写this,编译器会自动添加,直接调用就可以。
只有当需要明确指出对当前对象的引用时,才需要使用this关键字:
public class ThisTest {
static int i=0;
ThisTest increament(){
i++;
return this;
}
public static void main(String[] args) {
ThisTest tt=new ThisTest();
//调用的都是同一个对象引用
tt.increament().increament().increament();
System.out.print("i="+i);
}
}
结果:
i=3
也可以使用this关键字将当前对象作为参数,传递给其他方法:
public class TestThis {
public static void main(String[] args) {
new Person().eat(new Apple());
}
}
class Person{
public void eat(Apple apple){
//返回一个当前对象的引用
Apple peeled=apple.getPeeled();
System.out.print("eat apple Yummy");
}
}
class Apple{
Apple getPeeled(){
//传递当前Apple对象的引用,而不是直接创建该对象引用
return Peeler.peel(this);
}
}
class Peeler{
static Apple peel(Apple apple){
//返回apple对象,可以被多个方法使用
return apple;
}
}
4、在构造器中调用构造器
同一个类中,使用this关键字,可以在一个构造器中调用另一个构造器。
可以为this添加参数列表,会对符合参数列表的某个构造器明确调用。
在构造器中,只能使用一次this调用其他构造器。并且必须将this构造器调用放置在最起始处。禁止在其他任何方法中调用构造器。
上图中出现错误提醒:在父类构造函数初始化之前不能引用这个变量。
在前面加上static关键字之后,就正常使用。这个是与java初始化顺序有关的。即使没有显式地使用static关键字,构造器实际上是静态方法。
或者直接传入参数值,而不是引用,也可以正常使用:
Flower(){
//直接传入值
this("lisi",32);
}
Flower(int age){
//参数age与数据成员age的名称相同
this.age=age;
}
Flower(String s){
this.s=s;
}
Flower(String s,int age){
this(age);
this.s=s;
}
6、static关键字
static方法就是没有this的方法。在static方法的内部不能调用非静态方法,但是反过来可以。可以通过类本身调用static方法。java中禁止全局方法,但static方法很像全局方法。
静态方法可以访问其他static方法和static域,但是不能访问非静态方法和非静态域;
非静态方法可以访问静态或者非静态的方法和域。
7、成员初始化顺序
对于方法的局部变量,没有显式赋初值会报错。
对于类的每个基本类型数据成员保证都会有一个初始值:
public class InitialValue {
boolean bool;
char c;
byte b;
short s;
int i;
long lng;
float f;
double d;
InitialValue iv;
void printValue(){
System.out.println("bool="+bool);
System.out.println("c="+c);
System.out.println("b="+b);
System.out.println("s="+s);
System.out.println("i="+i);
System.out.println("lng="+lng);
System.out.println("f="+f);
System.out.println("d="+d);
System.out.println("iv="+iv);
}
public static void main(String[] args) {
InitialValue value=new InitialValue();
value.printValue();
}
结果:
bool=false
//char值为0,所以显示空白
c=
b=0
s=0
i=0
lng=0
f=0.0
d=0.0
//对象引用未初始化时,引用获得特殊值null
iv=null
7.1、构造器初始化
可以通过调用方法或者指定初始化值的方式进行初始化;但是无法阻止自动初始化的进行,它将在构造器被调用之前发生。
例如:int a=7;那么a会先被置为0,然后变成7。
对于所有基本类型和对象引用,包括定义时,已经指定初始值的变量,这种情况都是成立的。
7.1.1、初始化顺序(没有静态数据的情况下)
在类的内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
public class OrderInitial {
public static void main(String[] args) {
House house=new House();
house.f();
}
}
class Window{
Window(int mark){
System.out.println("mark = [" + mark + "]");
}
}
class House{
//Window对象的引用分散在方法之间
Window w1=new Window(1);
House(){
System.out.println("House()");
w3=new Window(33);
}
Window w2=new Window(2);
void f(){
System.out.println("f()");
}
Window w3=new Window(3);
}
结果:
//在调用House构造函数之前,先对House类中的变量初始化
mark = [1]
mark = [2]
mark = [3]
//变量初始化完毕后,再调用构造函数
House()
//在构造函数中对变量w3再次初始化,那么第一次引用的对象将被丢弃,被回收
mark = [33]
//调用House对象的方法
f()
7.1.2、静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。
static关键字不能应用于局部变量,它只能作用于域。
如果一个域是静态基本类型,且没有初始化,那么它会获得基本类型的标准值;如果它是一个对象引用,那它的默认值就是null。
public class StaticInital {
//定义两个静态变量,一个静态方法main
static Table table=new Table();
public static void main(String[] args) {
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Cupboard cupboard=new Cupboard();
}
class Table{
//定义两个静态变量分散在方法间
static Bowl bowl=new Bowl(1);
Table(){
System.out.println("Table()");
//调用 bowl2 的方法f1
bowl2.f1(1);
}
void f2(int mark){
System.out.println("mark = [" + mark + "]");
}
static Bowl bowl2=new Bowl(2);
}
class Bowl{
Bowl(int mark){
System.out.println("mark = [" + mark + "]");
}
void f1(int x){
System.out.println("x = [" + x + "]");
}
}
class Cupboard{
//定义一个非静态变量,两个静态变量
Bowl bowl2=new Bowl(3);
static Bowl bowl4=new Bowl(4);
Cupboard(){
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int y){
System.out.println("y = [" + y + "]");
}
static Bowl bowl5=new Bowl(5);
}
结果:
//先初始化静态变量,再执行静态方法
//初始化静态变量Table
//在Table中,先初始化静态变量,再执行构造方法
mark = [1]
mark = [2]
Table()
x = [1]
//初始化静态变量Cupboard
//在Cupboard中,先初始化静态变量,在初始化非静态变量,最后执行构造方法
mark = [4]
mark = [5]
mark = [3]
Cupboard()
x = [2]
//执行main方法
//第一次访问静态数据的时候,它们才会被初始化,此后,静态对象不会再次被初始化。但是非静态变量会重新被初始化。
mark = [3]
Cupboard()
x = [2]
//访问静态变量的方法
mark = [1]
y = [1]
对象创建过程总结(包括初始化顺序):
1、查找class文件:当首次创建类的对象,或者首次访问类的静态方法/静态域,Java解释器必须查找类路径,以定位.class文件。
2、静态初始化:载入.class文件,执行静态初始化动作。因此,静态初始化只在类对象首次加载的时候进行一次。
3、分配存储空间:当使用new 创建对象时,首先在堆上为对象分配足够的存储空间。
4、默认值初始化:将存储空间清零,自动将对象中的所有基本类型数据都设置为默认值,而引用类型设置为null。
5、自定义初始化:执行所有出现于字段定义处的初始化动作。
6、执行构造器。
8、数组初始化
数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。
public class ArrayTest {
public static void main(String[] args) {
int[] array_1=new int[6];
//不能加数组长度
int[] array_2=new int[]{1,2,3,4};
int[] array_3={5,6,7};
//只是复制了一个引用
int[] copy_array=array_2;
for(int i=0;i<array_2.length;i++){
array_2[i]=array_2[i]+1;
}
System.out.println(Arrays.toString(copy_array));
//基本数据类型会自动初始化为默认值(数字和字符,就是0;布尔型就是false)
System.out.println(Arrays.toString(array_1));
}
}
结果:
[2, 3, 4, 5]
[0, 0, 0, 0, 0, 0]
9、可变参数列表
可应用于参数个数或类型未知的场合。
public class ArgsTest {
static void printArray(Object... args){
for(Object o:args){
System.out.println("o="+o);
}
}
public static void main(String[] args) {
//指定参数时,编译器实际会填充为数组,获取的依旧是一个数组
printArray(1,2,3,4);
printArray("sw","22dede");
//参数已经是一个数组,直接当做可变参数列表
printArray(new int[]{43,65,55,66});
//将0个参数传递也是可行的
printArray();
}
}
结果:
o=1
o=2
o=3
o=4
o=sw
o=22dede
o=[I@6d6f6e28
10、枚举类型
关键字enum。枚举类型的实例是常量,按照命名惯例,它们都用大写字母表示,有多个单词的,用下划线将它们隔开:
public class Enumtest {
public static void main(String[] args) {
//显示枚举值
Spiciness mild=Spiciness.MILD;
System.out.println("mild="+mild);
//遍历枚举的值
for(Spiciness s:Spiciness.values()){
//返回值 和 声明的顺序
System.out.println(s+" ordinal:"+s.ordinal());
}
}
}
enum Spiciness{
NO,MILD,MED_HOT,FLAMING
}
结果:
mild=MILD
NO ordinal:0
MILD ordinal:1
MED_HOT ordinal:2
FLAMING ordinal:3
枚举类型可以与switch语句内使用:
public class EnumSwitch {
public static void main(String[] args) {
ShowCountry sc=new ShowCountry(Country.CHINA);
sc.getCountry();
}
}
enum Country{
CHINA,USA,UK,CANADA
}
class ShowCountry{
Country country;
ShowCountry(Country country){
this.country=country;
}
public void getCountry() {
//直接将枚举类型作为选择参数
switch (country){
case CHINA:
System.out.println("中国");break;
case USA:
System.out.println("美国");break;
case UK:
System.out.println("英国");break;
default:
System.out.println("加拿大");
}
}
}
结果:
中国