2019-09-23
2.6k
Java基础学习之面向对象基础
方法
可变参数
可变参数用类型...
定义,可变参数相当于数组类型:
1 2 3 4 5 6 7 class Group { private String[] name; public void setNames (String... names) { this .names = names } }
参数绑定
Java中的参数绑定其实也就是传值传递和传址传递。
我们先观察一个基本类型参数的传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Main { public static void main (String[] args) { Person p = new Person(); int n = 15 ; p.setAge(n); System.out.println(p.getAge()); n = 20 ; System.out.println(p.getAge()); } } class Person { private int age; public int getAge () { return this .age; } public void setAge (int age) { this .age = age; } }
从结果可知,修改外部的局部变量n
,不影响实例p
的age
字段,原因是setAge()
方法获得的参数,复制了n
的值,因此,p.age
和局部变量n
互不影响。
结论:基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
我们再看一个传递引用参数的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Main { public static void main (String[] args) { Person p = new Person(); String[] fullname = new String[] { "Homer" , "Simpson" }; p.setName(fullname); System.out.println(p.getName()); fullname[0 ] = "Bart" ; System.out.println(p.getName()); } } class Person { private String[] name; public String getName () { return this .name[0 ] + " " + this .name[1 ]; } public void setName (String[] name) { this .name = name; } }
注意到setName()
的参数现在是一个数组。一开始,把fullname
数组传进去,然后,修改fullname
数组的内容,结果发现,实例p
的字段p.name
也被修改了!
结论:引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方
再来看一个引用类型的参数绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Main { public static void main (String[] args) { Person p = new Person(); String bob = "Bob" ; p.setName(bob); System.out.println(p.getName()); bob = "Alice" ; System.out.println(p.getName()); } } class Person { private String name; public String getName () { return this .name; } public void setName (String name) { this .name = name; } }
String[] fullname
传递给p.name
的是堆中的地址,共同操作堆中的内容。bob
传递给p.name
以后两者均指向常量池的Bob
,后bob
指向了常量池中的Alice
,但是p.name
仍然指向Bob
,再一次 p.setName(bob);
的话p.name
将指向Alice
。
构造方法
默认构造方法
在Java中,创建对象实例的时候,按照如下顺序进行初始化:
先初始化字段,例如,int age = 10;
表示字段初始化为10
,double salary;
表示字段默认初始化为0
,String name;
表示引用类型字段默认初始化为null
;
执行构造方法的代码进行初始化。
多构造方法
可以定义多个构造方法,在通过new
操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person { private String name; private int age; public Person (String name, int age) { this .name = name; this .age = age; } public Person (String name) { this .name = name; } public Person () { } }
如果调用new Person("Xiao Ming", 20);
,会自动匹配到构造方法public Person(String, int)
。
如果调用new Person("Xiao Ming");
,会自动匹配到构造方法public Person(String)
。
如果调用new Person();
,会自动匹配到构造方法public Person()
。
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Person () { private String name; private int age; public Person (String name, int age) { this .name = name; this .age = age; } public Person (String name) { this (name, 18 ); } public Person () { this ('Unnamed' ) } }
方法重载
在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名 方法。这种方法名相同,但各自的参数不同,称为方法重载(Overload
)。
注意:方法重载的返回值类型通常都是相同的 。
继承
protected
允许子类访问父类的字段和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Main { public void main (String[] args) { Student s = new Student("Xiao Ming" , 18 ); } } class Person { protected String name; protected int age; public Person (String name, int age) { this .name = name; this .age = age; } } class Student extends Person { protected int score; public Student (String name, int age, int score) { this .score = score; } }
运行上面的代码,会得到一个编译错误,大意是在Student
的构造方法中,无法调用Person
的构造方法。这是因为在Java中,任何class
的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
但是,Person
类并没有无参数的构造方法,因此,编译失败。解决方法是调用Person
类存在的某个构造方法。例如:
1 2 3 4 5 6 7 8 class Student extends Person { protected int score; public Student (String name, int age, int score) { super (name, age); this .score = score; } }
结论:
如果父类没有默认的构造方法,子类就必须显式调用super()
并给出参数以便让编译器定位到父类的一个合适的构造方法。
子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
Java允许向上转型,不允许向下转型:
1 2 3 4 5 Person p1 = new Student(); Person p2 = new Preson(); Student s1 = p1; Student s2 = p2
多态
定义:多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class Main () { public void main (String[] args) { Income[] incomes = new Income[] { new Income(3000 ), new Salary(5000 ), new StateCouncilSpecialAllowance(15000 ) }, System.out.println(totalTax(incomes)); } public static double totalTax (Income[] incomes) { double total = 0 ; for (Income income: incomes){ total += income.getTax(); } return total; } } class Income { protected double income; public Income (double income) { this .income = income; } public double getTax (double income) { return income * 0.1 ; } } class Salary extends Income { public Salary (double income) { this .income = income; } @Override public getTax (double income) { if (income <= 5000 ){ return 0 ; } return (income - 5000 ) * 0.2 ; } } class StateCouncilSpecialAllowance extends Income { public StateCouncilSpecialAllowance (double income) { this .income = income; } @Override public getTax (double income) { return 0 ; } }
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
;如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
;对于一个类的实例字段,同样可以用final
修饰。用final
修饰的字段在初始化后不能被修改。
抽象类
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:
1 2 3 4 class Person { public abstract void run () ; }
必须把Person
类本身也声明为abstract
,才能正确编译它:
1 2 3 4 abstract class Person { public abstract void run () ; }
无法实例化的抽象类有什么用?
因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。
接口
抽象类和接口的对比如下:
abstract class
interface
继承
只能extends一个class
可以implements多个interface
字段
可以定义实例字段
不能定义实例字段
抽象方法
可以定义抽象方法
可以定义抽象方法
非抽象方法
可以定义非抽象方法
可以定义default方法
default方法
在接口中,可以定义default
方法。例如,把Person
接口的run()
方法改为default
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Main () { public static void main (String args[]) { Person p = new Student('Xiao Ming' ); p.run(); } } interface Person () { String getName () ; default void run () { System.out.println(getName() + " run" ); } } class Student implements Person () { private String name; public Student (String name) { this .name = name; } public getName () { return this .name; } }
实现类可以不必覆写default
方法。default
方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default
方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
静态字段和静态方法
静态字段
静态字段定义:static field
静态字段并不属于实例,所有实例共享一个静态字段,所以无论修改哪个实例的静态字段,效果都是一样的:所有实例的静态字段都被修改了。
在代码中,实例对象能访问静态字段只是因为编译器可以根据实例类型自动转换为类名.静态字段
来访问静态对象。所以,推荐用类名来访问静态字段。
静态方法
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Main { public static void Main (String args[]) { Person.setNumber(99 ); System.out.println(Person.number); } } class Person { public static int number; public static void setNumber (int value) { number = value; } }
静态方法属于类而不属于实例,因此,静态方法内部无法访问this
变量,也无法访问实例字段,只能访问静态字段。
接口的静态字段
interface
是一个纯抽象类,所以不能定义实例字段。但是interface
可以有静态字段,并且静态字段必须为final
类型。例如:
1 2 3 4 5 public interface Person { public static final int MALE = 1 ; public static final int FEMALE = 2 ; }
但是,interface
的字段只能是public static final
类型,所以我们可以把这些修饰符都去掉,编译器会自动加上。