最近懒得要死,很久之前学过的,现在才整理出来。
Java是一门面向对象的编程语言。如果现在自己没有对象,就new一个。
面向对象的基本概念,包括:
- 类
- 实例
- 方法
面向对象的实现方式,包括:
- 继承
- 多态
Java语言本身提供的机制,包括:
- package
- classpath
- jar
以及Java标准库提供的核心类,包括:
- 字符串
- 包装类型
- JavaBean
- 枚举
- 常用工具类
面向对象基础
类
class是一种对象模版,它定义了如何创建实例,因此,class本身就是一种数据类型,而instance是对象实例,instance是根据class创建的实例,可以创建多个instance,每个instance类型相同,但各自属性可能不相同。
定义class
class Book {
public String name;
public String author;
public String isbn;
public double price;
}
一个class
可以包含多个字段,每个字段描述一个类的特征,就如上面的book
类,定义了四个字段,前三个是String
类型的字段,命名为name
author
isbn
,最后一个是int
类型的字段,命名为price
,所以通过类,把一组数据汇集到一个对象上,实现数据封装。
创建实例
创建对象实例必须用new操作符,然后定义一个引用类型的变量来指向这个实例:
Person peng = new Person();
创建了一个Person类型的实例,并通过变量peng
指向它。
其中的Person peng
是定义Person
类型的变量peng
,而后面的new Person
是创建的实例。
访问实例变量通过变量.字段
peng.age = 19;//对字段age赋值
System.out.println(peng.age);//访问字段age
注意:
一个Java源文件可以包含多个类的定义,但只能定义一个public类,且public类名必须与文件名一致。如果要定义多个public类,必须拆到多个Java源文件中。
方法
如果我们只是简单地将字段用public
暴露在外面,会破坏封装性,所以通过private
修饰field
,拒绝外部访问,同时,我们还要使用方法(method
)来让外部代码可以间接修改field
:
public class Main {
public static void main(String[] args) {
Person ming = new Person();
ming.setName("Xiao Ming"); // 设置name
ming.setAge(12); // 设置age
System.out.println(ming.getName() + ", " + ming.getAge());
}
}
class Person {
private String name;
private int age;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
if (age < 0 || age > 100) {
throw new IllegalArgumentException("invalid age value");
}
this.age = age;
}
}
这里外部代码通过调用方法setName()
和setAge()
来间接修改private
字段,这样我们在方法内部就可以检查传入的参数的正确与否。
调用方法的语法是实例变量.方法名(参数);
。
一个类通过定义方法,就可以给外部代码暴露一些操作的接口,同时,内部自己保证逻辑一致性。
定义方法
定义方法的语法是:
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
方法返回值通过return
语句实现,如果没有返回值,返回类型设置为void
,可以省略return
。
方法参数
调用方法时,必须严格按照参数的定义一一传递。
例如:
class Person {
...
public void setNameAndAge(String name, int age) {
...
}
}
在调用setNameAndAge()
方法时,第一个参数为String
,第二个为int
:
Person peng = new Person();
peng.setNameAndAge("Bzp",19)
可变参数
可变参数用类型...
定义,可变参数相当于数组类型:
class Group {
private String[] names;
public void setNames(String... names) {
this.names = names;
}
}
调用时:
Group g = new Group();
g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // 传入3个String
g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
g.setNames("Xiao Ming"); // 传入1个String
g.setNames(); // 传入0个String
参数绑定
调用方把参数传递给实例方法时,调用时传递的值会按参数位置一一绑定。
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()); // "Homer Simpson"还是"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;
}
}
输出
Homer Simpson
Bart Simpson
基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。
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"还是"Alice"?
}
}
class Person {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
输出
Bob
Bob
原因:String为不可变类,会用一个新的地址引用。
java
基础数据类型,传递的是调用方 值的拷贝。
引用数据类型,传递是对象的引用。当参数为不可变对象(如String对象)时,调用方修改变量值只是将自己作用域下的变量指向了新的对象,不影响接收方的参数变量;当参数为可变对象(如Array对象)时,调用方修改变量值相当于自己作用域下的变量所指向的对象的值,影响接收方的参数变量。
python
都是传递的对象的引用(python中一切皆对象),和java的引用数据类型参数传递原理相同。
private方法
和private
字段一样,private
方法不允许外部调用,定义private
方法的理由是内部方法是可以调用private
方法的。
public class Main {
public static void main(String[] args) {
Person ming = new Person();
ming.setBirth(2008);
System.out.println(ming.getAge());
}
}
class Person {
private String name;
private int birth;
public void setBirth(int birth) {
this.birth = birth;
}
public int getAge() {
return calcAge(2019); // 调用private方法
}
// private方法:
private int calcAge(int currentYear) {
return currentYear - this.birth;
}
}
calcAge()
是一个private
方法,外部代码无法调用,但是,内部方法getAge()
可以调用它。
方法可以封装一个类的对外接口,调用方不需要知道也不关心Person
实例在内部到底有没有age
字段。
this变量
在方法内部,可以使用一个隐含的变量this
,它始终指向当前实例。
如果没有命名冲突,可以省略this
。例如:
class Person {
private String name;
public String getName() {
return name; // 相当于this.name
}
}
但是,如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this
:
class Person {
private String name;
public void setName(String name) {
this.name = name; // 前面的this不可少,少了就变成局部变量name了
}
}
构造方法
在创建对象实例时就把内部字段全部初始化为合适的值。
创建实例的时候,实际上是通过构造方法来初始化实例的。
public class Main {
public static void main(String[] args) {
Person p = new Person("Xiao Ming", 15);
System.out.println(p.getName());
System.out.println(p.getAge());
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
构造方法没有返回值(也没有void
),调用构造方法,必须用new
操作符。
默认构造方法
任何class
都有构造方法,之前没有编写构造方法就可以调用new Person()
,是因为编译器会自动为我们生成一个默认构造方法,没有参数,也没有执行语句,类似这样:
class Person {
public Person() {
}
}
如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法。
如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来:
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Xiao Ming", 15); // 既可以调用带参数的构造方法
Person p2 = new Person(); // 也可以调用无参数构造方法
}
}
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
在Java中,创建对象实例的时候,按照如下顺序进行初始化:
- 先初始化字段,例如,
int age = 10;
表示字段初始化为10
,double salary;
表示字段默认初始化为0
,String name;
表示引用类型字段默认初始化为null
; - 执行构造方法的代码进行初始化。
多构造方法
Java可以定义多个构造方法,编译器通过构造方法的参数数量、位置和类型自动区分。
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;
this.age = 12;
}
public Person() {
}
}
方法重载
在一个类中,我们可以定义多个方法。但是有时一系列的方法,功能都是类似的,仅参数不同,那么,可以把这一组方法名做成同名方法。这种方法名相同,但各自的参数不同,称为方法重载(Overload
)。
例如,String
类提供了多个重载方法indexOf()
,可以查找子串:
int indexOf(int ch)
:根据字符的Unicode码查找;int indexOf(String str)
:根据字符串查找;int indexOf(int ch, int fromIndex)
:根据字符查找,但指定起始位置;int indexOf(String str, int fromIndex)
根据字符串查找,但指定起始位置。
继承
继承是面向对象编程中非常强大的一种机制,可以复用代码。
Java使用extends
关键字来实现继承:
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
}
注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
在OOP的术语中,我们把Person
称为超类(super class),父类(parent class),基类(base class),把Student
称为子类(subclass),扩展类(extended class)。
继承树
在Java中,没有明确写extends
的类,编译器会自动加上extends Object
。所以,任何类,除了Object
,都会继承自某个类。
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object
特殊,它没有父类。
protected
继承中子类无法访问父类的private
字段或者private
方法。
为了让子类可以访问父类的字段,我们需要把private
改为protected
。用protected
修饰的字段可以被子类访问:
class Person {
protected String name;
protected int age;
}
class Student extends Person {
public String hello() {
return "Hello, " + name; // OK!
}
}
protected
关键字可以把字段和方法的访问权限控制在继承树内部,一个protected
字段和方法可以被其子类,以及子类的子类所访问。
super
super
关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName
。
class Student extends Person {
public String hello() {
return "Hello, " + super.name;
}
}
在Java中,任何class
的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
。
如果父类没有默认的构造方法,子类就必须显式调用super()
并给出参数以便让编译器定位到父类的一个合适的构造方法。
类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
1 条评论
太强了吧|´・ω・)ノ