Java基础笔记(一) 面向对象
[TOC]
1、定义类
[修饰符] class 类名
{
零到多个构造器定义
零到多个Field
零到多个方法
}
其中的修饰符可以是public
、final
、abstract
或省略。
- 类也是引用数据类型,用类定义的变量也只是一个引用(或者说指针),里面只是存放了一个地址值。
-
static
修饰的方法不能直接访问没有static
修饰的成员。(理由很简单:静态成员是独立于具体对象而存在,属于类本身,而非静态成员是依赖于具体的对象的) - Java里方法的参数传递方式只有一种:值传递。基本数据类型和引用数据类型都是将实参的一个副本传给形参,只不过引用数据类型拷贝的是地址值!
2、可变参数函数
JDK 1.5之后,Java允许为方法指定数量不确定的形参,通过在最后一个形参的类型后增加三个点(…),代码实例如下:
public class MyClass {
/**
* 可变参数的方法
*/
public static void func(int a, String... str) {
for(String s : str) {
System.out.println(s);
}
System.out.println(a);
}
/**
* 程序入口-main
*/
public static void main(String[] args) {
func(15, "第一个字符串","第二个字符串","第三个字符串");
}
}
当然,如果你觉得这样麻烦,可以直接用一个数组代替
3、方法重载
Java允许同一个类里定义多个同名方法,只要形参列表不同就行。
在Java中,根据变量定义位置的不同,可以将变量分为两大类:成员变量 和 局部变量
1、成员变量的初始化和内存中的运行机制
- 当系统加载类、或者创建该类的实例时,系统自动为成员变量分配内存空间,并自动指定初始值。
- 类 Field(静态成员变量),系统会在类加载时为其分配内存空间,并指定默认初始值。
- 实例 Field 是在创建实例时分配内存空间并指定初始值的,注意:实例变量指向的是这部分内存。
2、局部变量的初始化和内存中的运行机制
- 局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。
- 也就是说,定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初值时才分配,并将初值保存在这块内存中。
- 局部变量总是保存在其所在方法的栈内存中,所以它不属于任何类或实例。
- 如果是基本类型的局部变量,则栈内存中是变量的值;如果是引用类型的局部变量,则栈内存中存放的是地址,引用堆内存中的实际对象。
4、类的封装
封装是面向对象的三大特征之一。为了实现良好的封装,需要:
- 将 Field 和实现细节隐藏起来,不允许外部直接访问。
- 把方法作为外部接口暴露出来,让方法来控制对 Field 进行安全的访问和操作。
Java中提供了4个访问控制级别:private
、protected
、public
和不加任何访问控制符(default
),它们的访问控制级别由小到大:
package、import 和 import static
包(package):为了解决类的命名冲突,Java引入了包机制,提供了类的多层命名空间。
如果一个类被放于某个包中,则我们应该在该Java源文件的第一个非注释行添加如下代码:
package packageName;
import
语句可以导入指定包下某个类或全部类,但import
语句并不是必需的,只要坚持在类里面使用其他类的全名,则可以无须使用import
语句。
注意:在JDK 1.5以后增加了一种静态导入(import static
)的语法,用于导入指定类的某个静态 Field、方法或该类全部的静态 Field、方法。
import static package.subpackage...className.fieldName; // 导入某一静态变量
import static package.subpackage...className.methodName; // 导入某一静态方法
import static package.subpackage...className.*; // 导入该类的所有静态Field、方法
用一句话归纳import
和import static
的作用:使用import可以省略写包名,而使用import static则可以连类名都省略。
4、Java的常用包
Java的核心类都放在java
这个包及其子包下,Java扩展的许多类都放在javax
包及其子包下。下面几个包是Java语言中的常用包:
-
java.lang
:这个包下包含了Java语言的核心类,如 String、Math、System 和 Thread 类等,使用这个包下的类无须使用 import 语句导入,系统会自动导入这个包下的所有类。 -
java.util
:这个包下包含了Java的大量工具类/接口和集合框架类/接口,例如 Arrays、List 和 Set 等。 -
java.net
:这个包下包含了一些Java网络编程相关的类/接口。 -
java.io
:这个包下包含了一些Java输入/输出编程相关的类/接口。 -
java.text
:这个包下包含了一些Java格式化相关的类。 -
java.sql
:这个包下包含了Java进行 JDBC 数据库编程的相关类/接口。 -
java.awt
:这个包下包含了抽象窗口工具集(Abstract Window Toolkits)的相关类/接口,这些类主要用于构建 GUI 程序。 -
java.swing
:这个包下包含了 Swing 图形用户界面编程的相关类/接口,这些类可用于构建平台无关的 GUI 程序。
5、构造器
构造器也就是构造函数!!!
Java类可以包含一个或一个以上的构造器。一旦程序员提供了自定义的构造器,系统就不再提供默认的无参构造器了。(所以如果为一个类编写了有参数的构造器,通常建议为该类也额外提供一个无参数的构造器)
6、类的继承
继承是面向对象的三大特征之一。Java的继承具有单继承的特点,每个子类只有一个直接父类。
继承的语法格式:
修饰符 class Derive extends Base
{
// 类定义部分
}
Java使用extends
作为继承的关键字,extends
在英文中是扩展的意思。
重写父类的方法要遵循“两同两小一大”的规则:
- “两同”:方法名相同,形参列表相同。
- “两小”:子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。
- “一大”:子类方法的访问权限应比父类方法的访问权限更大或相等。
如果需要在子类方法中调用父类中被覆盖的方法,若被覆盖的是实例方法,使用super
作为调用者;若被覆盖的是类方法,使用父类类名作为调用者。
7、构造器的执行顺序
子类不会获得父类的构造器,但子类构造器里可以调用父类构造器。有如下几种情况:
- 子类构造器函数体的第一行使用
super
显式调用父类构造器。 - 子类构造器函数体的第一行使用
this
显示调用本类中重载的构造器,执行本类中另一个构造器时即会调用父类构造器。 - 子类构造器函数体中既没有
super
调用,也没有this
调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
不管上面哪种情况,当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行;不仅如此,执行父类构造器时,系统会再次上溯执行其父类构造器……依此类推,创建任何Java对象,最先执行的总是java.lang.Object
类的构造器。
8、多态
多态是面向对象的三大特征之一。
Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时的类型决定,运行时类型由实际赋给该变量的对象决定。两个类型不一致时,就可能出现多态。
当把一个子类对象直接赋给父类引用变量时,这个引用变量的编译时类型是 BaseClass,而运行时类型是 SubClass,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征。也就是说:相同类型的变量 调用同一个方法时,呈现出多种不同的行为特征,这就是多态。
多态的两个前提: 要有继承(inheritance),要有方法重写(override)。
9、instanceof 运算符
instanceof
是Java中的一个二元运算符,它的作用是在运行时判断左边对象是否是右边类(或其子类)的实例。如果是,返回true
,否则返回false
。
public class MyClass {
public static void main(String[] args) {
String str = ""; // str 是String类型引用变量
Object obj = ""; // obj 的编译时类型是 Object,但实际类型是 String
System.out.println("str 是 String 的实例:" + (str instanceof String)); // true
System.out.println("str 是 Object 的实例:" + (str instanceof Object)); // true
System.out.println("obj 是 String 的实例:" + (obj instanceof String)); // true
System.out.println("obj 是 Object 的实例:" + (obj instanceof Object)); // true
System.out.println("obj 是 Math 的实例:" + (obj instanceof Math)); // false
}
}
需要注意:instanceof
运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。
instanceof
运算符的常用之处:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码更加健壮。
10、初始化块
初始化块是Java类里可出现的第4种成员(前面依次有 Field、方法和构造器)。与构造器的作用类似,初始化块也可以对Java对象进行初始化操作。
初始化块的语法格式如下:
[修饰符] {
// 可执行代码
...
}
初始化块的修饰符只能是static
,使用 static 修饰的初始化块被称为静态初始化块。
一个类里可以有多个初始化块,先定义的初始化块先执行,后定义的初始化块后执行,下面是一个例子:
public class MyClass {
{
int a = 5;
System.out.println("第一个初始化块!");
}
{
System.out.println("第二个初始化块!");
}
public MyClass() {
System.out.println("类的无参数构造器!");
}
public static void main(String[] args) {
new MyClass(); // 创建一个对象
}
}
控制台输出结果:
第一个初始化块!
第二个初始化块!
类的无参数构造器!
可以看出,初始化块是在构造器之前执行的。创建一个 Java 对象时,不仅会执行该类的普通初始化块和构造器,而且系统会一直上溯到java.lang.Object
类,先执行 java.lang.Object 类的初始化块,开始执行 java.lang.Object 的构造器,依次向下执行其父类的初始化块,开始执行其父类的构造器……最后才执行该类的初始化块和构造器,返回该类的对象。
虽然 Java 允许一个类中定义多个的普通初始化块,但这没有任何意义,所以如果要使用初始化块的话定义一个就行了。
11、初始化块和构造器的区别
初始化块总是在构造器之前执行。虽然它们的作用非常相似,但依然存在一些差异的。
与构造器不同的是,初始化块是一段固定执行的代码,它不能接受任何参数。因此,如果有一段初始化的代码对所有对象完全相同,且无须接收任何参数,就可以把这段初始化代码提取到初始化块中。
通过把多个构造器中的相同代码提取到初始化块中,能更好地提高初始化代码的复用,提高整个应用的可维护性。
12、静态初始化块
初始化块的修饰符只能是static
,使用 static 修饰的初始化块被称为静态初始化块。
静态初始化块,也属于类的静态成员,因此静态初始化块不能访问非静态成员(包括实例Field和实例方法)。静态初始化块用于对整个类进行初始化处理,通常用于对类Field执行初始化处理。
系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此,静态初始化块总是比普通初始化块先执行。与普通初始化块类似的是,系统在类初始化阶段不仅会执行本类的静态初始化块,还会一直上溯到 java.lang.Object 类(如果它包含静态初始化块),从上往下依次执行其父类的静态初始化块……最后才执行该类的静态初始化块。经过这个过程,才完成了该类的初始化。而只有类完成初始化以后,才可以在系统中使用这个类,包括访问这个类的类Field、类方法,或者用这个类来创建实例。
13、包装类
Java 是面向对象的编程语言,但它也包含了 8 种基本数据类型。基本数据类型的数据不具备“对象”的特性:没有Field、方法可以被调用。
所有引用类型的变量都继承了Object
类,都可当成 Object 类型变量使用,但基本数据类型的变量却不可以。为了解决这个问题,Java 提供了包装类(Wrapper Class),可以把 8 个基本类型的值包装成对象使用。
把基本数据类型变量 包装成 对应的包装类对象 是通过对应包装类的构造器来实现的,不仅如此,8个包装类中除了 Character 之外,还可以通过传入一个字符串来构建包装类对象。
public class MyClass {
public static void main(String[] args) {
boolean b1 = true;
int i1 = 5;
Boolean _b = new Boolean(b1);
Integer _i = new Integer(i1);
Float _f = new Float("3.14");
// 取出包装类对象里的值
boolean b2 = _b.booleanValue();
int i2 = _i.intValue();
float f2 = _f.floatValue();
}
}
可能你会觉得,这样的转换有些繁琐。但从 JDK 1.5 开始提供了自动装箱(Autoboxing) 和 自动拆箱(AutoUnboxing)功能,即可以把一个基本类型变量直接赋给对应的包装类变量或 Object 变量(自动装箱),也可以把包装类对象直接赋给一个对应的基本类型变量。
14、基本类型变量与字符串的转换
15、toString( )方法
toString()
方法是 Object 类里的一个实例方法,而所有的 Java 类都是 Object 类的子类,因此所有的 Java 对象都具有 toString() 方法。
不仅如此,所有的 Java 对象都可以和字符串进行连接运算,也可以使用System.out.println()
进行输出。当进行上面的操作时,系统会自动调用 Java 对象的 toString()
方法,使用其返回的字符串。
Object 类的toString
方法是一个“自我描述”的方法,它总是返回该对象实现类的“类名@hashCode”值。但是这个返回值并不能真正实现“自我描述”的功能,这时可以对这个方法进行重写。
16、==和equals的区别
Java 程序中判断两个变量是否相等有两种方式:一种是使用==
运算符,另一种是使用equals
方法。
- 对于基本类型变量来说,它们并没有
equals
方法,只能使用==
判断两个变量的值是否相等。 - 对于引用类型变量来说,
==
运算符是判断两个引用变量是否指向内存中的同一个对象,也就是比较对象的内存地址;而equals
是比较两个对象的值是否相等(String类,Integer类等等)。
需要知道的是,equals 方法是 Object 类的一个实例方法。在 Object 类中equals
方法和==
没有任何区别,都是判断两个变量是否指向同一个对象。<u>而String类,Integer类等等一些类,是重写了equals方法,才使得equals和“==”不同。</u>
所以,当自己创建类时,想要自定义相等的标准,必须重写equals方法。
17、final修饰符
Java 提供了final
关键字来修饰变量、方法和类。系统不允许为 final
变量重新赋值,子类不允许覆盖父类的final
方法,不允许继承final
类。
- final 成员变量必须由程序员显式地指定初始值,系统不会对 final 成员变量进行隐式初始化。对于 final 修饰的类 Field,必须在声明该Field时或在静态初始化块中指定初始值;对于 final 修饰的实例 Field,必须在声明该Field时、普通初始化块或构造器中指定初始值。
- 前面说过,系统不会为局部变量执行隐式初始化,必须由程序员显式指定。对于 final 修饰的局部变量,可以在声明时指定初始值,也可以在后面的代码中对其赋值,但只能一次。
- final 修饰基本类型变量时,表示变量的值不能被改变;final 修饰引用类型变量时,表示该变量所引用的地址不能被改变,即一直引用同一个对象,但这个对象是可以改变的。
- 当定义 final 变量时就为该变量指定了初始值,而且该初始值可以在编译时就被确定下来,那么这个变量就变成了“宏变量”。编译器会把程序中所有用到该变量的地方直接替换成该变量的值。
18、抽象类与抽象方法
Java 中使用abstract
修饰符来定义抽象类和抽象方法。有抽象方法的类必须定义成抽象类,但抽象类里可以没有抽象方法。
抽象类不能被实例化,只能当作父类被其他子类继承。抽象方法没有函数体,必须由子类提供实现(即重写)。
与abstract
不能同时使用的关键字:
- final和abstract不能同时使用,因为它们是对立的。
- static和abstract不能同时修饰某个方法,因为如果一个抽象方法被定义成静态方法,通过类名调用该方法将出现错误。
- private和abstract不能同时修饰某个方法,因为抽象方法必须被子类重写才有意义,而子类不能访问和重写父类的 private 方法。
19、接口(interface)
上面说到,抽象类既可以包含抽象方法,也可以普通方法。而接口(interface)是一种更彻底的抽象,接口里的所有方法都是抽象方法。
接口定义的是多个类共同的公共行为规范,故它里面通常是定义一组公用方法。基本语法如下:
[修饰符] interface 接口名 extends 父接口1,父接口2...
{
零个到多个常量定义...
零个到多个抽象方法定义...
}
修饰符可以是 public 或者省略,如果省略了 public 访问控制符,则默认采用包权限访问控制符。另外,与类继承不同的是,接口继承中一个接口可以有多个直接父接口(接口只能继承接口而不能继承类)。
由于接口是一种规范,因此接口里不能包含构造器和初始化块。接口里可以包含3种成员: Field(只能是常量)、方法(只能是抽象方法)、内部类(包括内部接口、枚举)。
- 接口里所有成员都是
public
访问权限。 - 接口里的常量 Field 是使用
public static final
修饰符来修饰,不管定义时是否指定。 - 接口里的方法是自动使用
public abstract
修饰符修饰,不管定义方法时是否指定。 - 接口里的内部类(接口、枚举类)是自动使用
public static
修饰符修饰,不管定义时是否指定。
接口不能用于创建实例,其主要用途是被实现类实现。实现使用implements
关键字:
[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{
// 类体部分
}
一个类只能有一个直接父类,但一个类可以实现多个接口。实现接口与继承父类相似,也可以获得所实现的接口里定义的成员,因此可以把实现接口理解为一种特殊的继承。
20、接口与抽象类的比较
相同点:
- 接口和抽象类都不能被实例化,它们都用于被其他类实现或继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
不同点:
- 接口里只能包含抽象方法,而抽象类既可以抽象方法也可以包含普通方法,还可以没有抽象方法。
- 接口里不能定义静态方法(因为全部是抽象方法),抽象类里可以定义静态方法。
- 接口里只能定义静态常量 Field,而抽象类既可以定义普通 Field,也可以定义静态常量 Field。
- 接口里不包含构造器和初始化块,而抽象类里完全可以包含。
- 一个类最多只能有一个直接父类,但却可以直接实现多个接口(弥补Java单继承的不足)。
21、内部类
在Java类里只能包含5种成员:Field、方法、构造器、初始化块、内部类(包括接口和枚举类)。前四种类成员已经介绍过了,下面介绍一下内部类。
内部类也叫嵌套类,语法格式:
public class OuterClass
{
// 此处可以定义内部类
}
通常,我们把内部类作为成员内部类来定义,而不是作为局部内部类(在方法里定义的内部类)。
非静态内部类里不允许定义静态成员,而静态内部类里可以定义静态成员,也可以定义非静态成员。
根据静态成员不能访问非静态成员的规则,外部类的静态方法不能使用非静态内部类,静态内部类也不能访问外部类的非static成员。
22、枚举类
枚举类是一种不能自由创建对象的类,它的对象在定义类时已经固定下来。枚举类特别适合定义像行星、季节这样的类,它们能创建的实例是有限且确定的。
在 Java 1.5 以前,要定义一个枚举类,必须手动去实现。下面就是一个 Season 枚举类的例子:
public class Season {
private final String name;
private final String description;
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season FALL = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","围炉赏雪");
// 私有化构造器
private Season(String name, String description) {
this.name = name;
this.description = description;
}
// 只为两个 Field 提供 getter 方法
public String getName() {
return name;
}
public String getDescription() {
return description;
}
}
上面的 Season 类是一个不可变类,它只能创建4种对象,可以通过Season.SPRING的方式来取得 Season 对象。
Java 1.5 新增了一个enum
关键字,用以定义枚举类:
public enum Season {
SPRING,SUMMER,FALL,WINTER;
}
枚举类(enum)是一种特殊的类,它一样可以有自己的 Field、方法和构造器,可以实现一个或者多个接口。因为它特殊,所以有几点需要注意:
- 用
enum
定义的枚举类默认继承了java.lang.Enum
类,而不是继承 Object 类。 - 用
enum
定义的非抽象的枚举类默认使用 final 修饰,因此枚举类不能派生子类。 - 枚举类的构造器默认使用且只能使用
private
访问控制符修饰。 - 枚举类的所有实例必须在枚举类的第一行显式列出,并且系统会自动添加
public static final
修饰。