文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 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; // n的值为15
p.setAge(n); // 传入n的值
System.out.println(p.getAge()); // 15
n = 20; // n的值改为20
System.out.println(p.getAge()); // 15
}
}

class Person {
private int age;

public int getAge() {
return this.age;
}

public void setAge(int age) {
this.age = age;
}
}

从结果可知,修改外部的局部变量n,不影响实例page字段,原因是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); // 传入fullname数组
System.out.println(p.getName()); // "Homer Simpson"
fullname[0] = "Bart"; // fullname数组的第一个元素修改为"Bart"
System.out.println(p.getName()); // "Bart Simpson"?
}
}

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); // 传入bob变量
System.out.println(p.getName()); // "Bob"
bob = "Alice"; // bob改名为Alice
System.out.println(p.getName()); // 注意:还是"Bob"
}
}

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中,创建对象实例的时候,按照如下顺序进行初始化:

  1. 先初始化字段,例如,int age = 10;表示字段初始化为10double salary;表示字段默认初始化为0String name;表示引用类型字段默认初始化为null
  2. 执行构造方法的代码进行初始化。

多构造方法

可以定义多个构造方法,在通过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){ //调用Person(String, int)构造方法
this(name, 18);
}

public Person(){
this('Unnamed') //调用Person(String)构造方法
}
}

方法重载

在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。这种方法名相同,但各自的参数不同,称为方法重载(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;
}
}

结论:

  1. 如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
  2. 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

Java允许向上转型,不允许向下转型:

1
2
3
4
5
Person p1 = new Student(); //upcasting ok
Person p2 = new Preson();
Student s1 = p1; // ok
Student s2 = p2 //error

多态

定义:多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。

举个例子:

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类型,所以我们可以把这些修饰符都去掉,编译器会自动加上。

Copyright © ca01h 2019-2020 | 本站总访问量