Java构造函数
构造函数
构造函数的最大作用就是创建对象时完成初始化,当我们在new一个对象并传入参数的时候,会自动调用构造函数并完成参数的初始化
构造函数的规则:
- 构造函数与类同名
- 每个类可以有一个以上的构造函数
- 构造函数可以有0 个 、 1 个或多个参数
- 构造函数没有返回值
- 构造函数总是伴随着new操作一起调用
看一个Employee类例子:
public class Employee {
// 关键字 private 确保只有 Employee 类自身的方法能够访问这些实例域 , 而其他类的方法不能够读写这些域 。
// 可以用 public 标记实例域 , 但这是一种极为不提倡的做法
//public 數据域允许程序中的任何方法对其进行读取和修改 ,。 这就完全破坏了封装 。
//任何类的任何方法都可以修改 public 域 , 从我们的经验来看 , 某些代码将使用这种存取权限 , 而这并不我们所希
//望的 , 因此这里强烈建议将实例域标记为 private , ,
private String name ;
private double salary ;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
public static void main(String[] args) {
// 说明在我们创建Employee对象的时候,会自动调用构造函数完成初始化,
Employee employee = new Employee("John",44545);
System.out.println(employee);
}
}
细节:
-
如果你不写构造函数,程序会自动给你加上一个无参数无操作的构造函数(当然你看不见)
-
如果你写了构造函数,则自定义构造函数会覆盖无参数构造函数
public class Employee_1 {
@Override
public String toString() {
return "Employee_1{}";
}
public static void main(String[] args) {
// 没有显式提供构造函数
// 程序会自动给你加上一个无参数无操作的构造函数
Employee_1 employee_1 = new Employee_1();
System.out.println(employee_1);
}
}
构造函数与普通函数的区别:
-
一般函数是用于定义对象应该具备的功能。而构造函数定义的是,对象在调用功能之前,在建立时,应该具备的一些内容。也就是对象的初始化内容。
-
构造函数是在对象建立时由JVM调用, 给对象初始化。一般函数是对象建立后,当对象调用该功能时才会执行。
-
普通函数可以使用对象多次调用,构造函数就在创建对象时调用。
-
构造函数的函数名要与类名一样,而普通的函数只要符合标识符的命名规则即可。
-
构造函数没有返回值类型。
初始化
为什么需要初始化
java规定,变量没有初始化不能使用,全局变量也就是类的属性,java会在编译的时候,自动将他们初始化,所以可以不进行变量初始化的操作,但是(局部)变量必须初始化
初始化就是在最开始定义成员变量时给它一个初始的值,为了防止程序运行时候出现未知的错误,或者bug
总之一句话,为了安全
默认初始化
在Java中,一个类的数据成员(成员变量)如果没有指定初始化,那么Java会对其执行默认初始化
public class InitialValues {
boolean t;
char c;
short s;
int i;
long l;
float f;
double d;
String str;
InitialValues reference;
void printInitialValues() {
System.out.printf("%-10s %-5s\n", "boolean:", t);
System.out.printf("%-10s %-5s\n", "char:", c);
System.out.printf("%-10s %-5s\n", "short:", s);
System.out.printf("%-10s %-5s\n", "int:", i);
System.out.printf("%-10s %-5s\n", "long:", l);
System.out.printf("%-10s %-5s\n", "float:", f);
System.out.printf("%-10s %-5s\n", "double:", d);
System.out.printf("%-10s %-5s\n", "String:", str);
System.out.printf("%-10s %-5s\n", "reference:", reference);
}
public static void main(String[] args) {
InitialValues iv = new InitialValues();
iv.printInitialValues();
}
}
输出:
boolean: false
char:
short: 0
int: 0
long: 0
float: 0.0
double: 0.0
String: null
reference: null
静态数据的初始化
初始化的顺序是:先静态对象,而后是“非静态”对象。
class Bowl{
Bowl(int marker){
System.out.println("Bowl("+marker+")");
}
void f1(int marker){
System.out.println("f1("+marker+")");
}
}
class Table{
static Bowl bowl1=new Bowl(1);
Table(){
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker){
System.out.println("f2("+marker+")");
}
static Bowl bowl2= new Bowl(2);
}
class Cupboard{
Bowl bowl3=new Bowl(3);
static Bowl bowl4=new Bowl(4);
Cupboard(){
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker){
System.out.println("f3("+marker+")");
}
static Bowl bowl5= new Bowl(5);
}
public class StaticInitialization {
// 要执行main(),必须加载StaticInitialization类,然后其静态域table和cupboard被初始化,这导致他们对应的类也被加载,并且由于它们都包含静态的Bowl对象,
// 因此Bowl随后也被加载。这样,在这个特殊的程序中的所有的类在main()开始之前就都被加载了。
//
// 首先从main()函数开始,加载StaticInitializaition类,然后对StaticInitializaition类中的静态域Table进行初始化,加载Table类,Table类中包含静态的Bowl对象,
// 接着加载Bowl类,加载Bowl类构造器创建bowl1对象,输出Bowl(1),加载Bowl类构造器创建bowl2对象,输出Bowl(2);同里创建cupboard对象。
//
// 需要注意的是,bowl3是非静态域,每次创建Cupboard对象都会创建一个Bowl对象。
public static void main(String[] args) {
System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard() in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
非静态成员初始化
在一个类中,非静态成员的初始化,发生在任何方法(包括构造器)被调用之前。并且它们定义的顺序,决定了初始化的顺序
class A{
A(String s) {
System.out.println(s);
}
}
class B{
A a1 = new A("a1");
B() {
System.out.println("B");
A a2 = new A("a2");
}
A a3 = new A("a3");
void f(){
System.out.println("f---");
}
A a4 = new A("a4");
}
public class Initialization_1 {
public static void main(String[] args) {
B b = new B();
b.f();
}
}
静态块代码初始化
整个static { 静态代码块 } 可以看作是一个静态成员。当一个类需要执行静态初始化时,该类中的静态成员初始化和静态代码块,会按照先后定义的顺序执行。当然,这个流程也是就执行这一次
class AA{
public AA(String s) {
System.out.println(s);
}
}
class BB{
AA a1 = new AA("a1");
static AA a2 = new AA("a2");
static AA a3,a4;
static {
System.out.println("______________");
a3 = new AA("a3");
a4 = new AA("a4");
AA a5 = new AA("a5");
System.out.println("++++++++++++");
}
static AA a6 = new AA("a6");
AA a7 = new AA("a7");
public BB() {
System.out.println("BB");
}
}
public class Initialization_3 {
static BB b1 = new BB();
public static void main(String[] args) {
new BB();
}
}
非静态代码初始化
非静态代码块,可以看作一个非静态成员。涉及非静态初始化,也会执行它。和普通的非静态成员初始化一样,它的执行也发生在构造器调用之前,并且每当创建对象之前都会调用。
class AA{
public AA(String s) {
System.out.println(s);
}
}
class BB{
AA a1 = new AA("a1");
static AA a2 = new AA("a2");
static AA a3,a4;
{
System.out.println("______________");
a3 = new AA("a3");
a4 = new AA("a4");
AA a5 = new AA("a5");
System.out.println("++++++++++++");
}
static AA a6 = new AA("a6");
AA a7 = new AA("a7");
public BB() {
System.out.println("BB");
}
}
public class Initialization_4 {
static BB b1 = new BB();
public static void main(String[] args) {
new BB();
}
}
方法重载
如果多个方法 ( 比如 , StringBuilder构造器方法 ) 有相同的名字 、 不同的参数, 便产生了重载 。 编译器必须挑选出具体执行哪个方法 , 它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法 。 如果编译器找不到匹配的参数 , 就会产生编译时错误, 因为根本不存在匹配 ,或者没有一个比其他的更好。( 这个过程被称为重载解析 ( overloading resolution )。
Java 允许重载任何方法 ,而不只是构造器方法。 因此 , 要完整地描述一个方法 ,需要指出方法名以及参数类型 。 这叫做方法的签名 ( signature ) 。 例如 , String 类有 4 个称为 indexOf 的公有方法。它们的签名是
-
indexOf ( int )
-
indexOf ( int , int )
-
indexOf ( String )
-
indexOf ( String , int )
返回类型不是方法签名的一部分 。 也就是说, 不能有两个名字相同 、参数类型也相同却返回不同类型值的方法 。
涉及基本类型的重载
Java中的基本类型有一个特性:它能自动从一个"较小"的类型转换成"较大"的类型!上面这句话什么意思呢?指的就是byte、char、short等"较小"类型能自动转换成int类型,而不需要额外的其他操作。因此,如果这个自动转换的过程涉及到重载,可能会造成混淆。
“由小到大”依次为:byte->short->int->long->float->double;
this关键字
this:引用当前类的实例变量——解决形参和实参的重名尴尬
this关键字可以用来引用当前类的实例变量。如果实例变量和参数之间存在歧义,则 this 关键字可用于明确地指定类变量以解决歧义问题
先看没有使用this例子:
public class Person {
private int age;
private String name;
// public Person(int age, String name) {
// this.age = age;
// this.name = name;
// }
public Person(int age, String name) {
age = age;
name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) {
Person person = new Person(15,"John");
System.out.println(person);
}
}
输出:
Person{age=0, name='null'}
在上面的例子中,参数(形式参数)和实例变量(age和name)是相同的。 所以要使用this关键字来区分局部变量和实例变量。
使用this:
public class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
// public Person(int age, String name) {
// age = age;
// name = name;
// }
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) {
Person person = new Person(15,"John");
System.out.println(person);
}
}
如果局部变量(形式参数)和实例变量不同,则不需要像下面的程序一样使用this关键字:
public class Person {
private int age;
private String name;
// public Person(int age, String name) {
// this.age = age;
// this.name = name;
// }
// public Person(int age, String name) {
// age = age;
// name = name;
// }
public Person(int the_age, String the_name) {
age = the_age;
name = the_name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public static void main(String[] args) {
Person person = new Person(15,"John");
System.out.println(person);
}
}
this:调用当前类方法——对象调用方法时其实调用的this.[方法],但在实际中时隐去的
使用this关键字调用当前类的方法。如果不使用this关键字,编译器会在调用方法时自动添加此 this 关键字
public class Demo_1 {
void f(){
System.out.println("f_______");
}
void m(){
f();
}
void m_1(){
this.f();
}
public static void main(String[] args) {
Demo_1 d1 = new Demo_1();
d1.m();
d1.m_1();
}
}
this():调用当前类的构造函数
this()构造函数调用可以用来调用当前类的构造函数。 它用于重用构造函数。 换句话说,它用于构造函数链接。也就是说调用一次 this(); 也就调用了一次类(其构造方法也就调用了一次类)
class A{
public A() {
System.out.println("the a is ___");
}
public A(String name){
this();
System.out.println(name);
}
}
public class D2 {
public static void main(String[] args) {
A a = new A("jsaksjk");
}
}
调用this()必须是构造函数中的第一个语句
this:作为参数传递给方法
this关键字也可以作为方法中的参数传递
public class D3 {
void m(D3 obj) {
System.out.println("method is invoked");
System.out.println(obj.hashCode());
}
void p() {
m(this);
}
public static void main(String[] args) {
D3 d3 = new D3();
d3.p();
}
}
this:在构造函数调用中作为参数传递
在构造函数中传递this关键字。 如果必须在多个类中使用一个对象,可以使用这种方式
class DD{
D4 d4;
public DD(D4 d4) {
this.d4 = d4;
}
@Override
public String toString() {
return "DD{" +
"d4=" + d4 +
'}';
}
}
public class D4 {
int d = 100;
public D4() {
DD dd = new DD(this);
System.out.println(dd);
}
public static void main(String[] args) {
D4 d4 = new D4();
}
}
this关键字用来返回当前类的实例
可以从方法中 this 关键字作为语句返回。 在这种情况下,方法的返回类型必须是类类型(非原始)
class DDD{
DDD get(){
return this;
}
void f(){
System.out.println("Hello World");
}
}
public class D5 {
public static void main(String[] args) {
new DDD().get().f();
}
}