1 类和对象
1.1 对象、引用和指针
public class Person{
//定义两个成员变量
public String name;
public int age;
public void say(String content){
System.out.println(content);
}
}
Person p = new Person();
//Person p;使用Person类定义一个Person类型的变量
//new Person();使用new关键字调用Person类的构造器,返回一个Person实例
//p = new Person();将该Person实例赋给p
当创建Person对象时,包含两个实例变量,被存放在堆内存里;
定义的Person类型的变量实际上是一种引用,被存放在栈内存里;
引用变量只是指向该堆内存里的对象,从这个角度来看,引用变量与C语言里的指针很像,它们都是存储一个地址值,通过这个地址值来引用到实际变量。
1.2 对象的this引用
this关键字总是指向调用该方法的对象,根据this出现位置的不同,this作为对象的默认引用有两种情形:
①构造器中引用该构造器正在初始化的对象;
②在方法中引用调用该方法的对象。
this关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。
2 方法详解
2.1 方法重载
如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。
在Java中,确定一个方法需要三个要素:
- 调用者:也就是方法的所属者,既可以是类,也可以是对象;
- 方法名:方法的标识;
- 形参列表:当调用方法时,系统将会根据传入的实参列表匹配。
方法的重载要求两同一不同:同一个类中方法名相同,参数列表不同。
常见面试题:方法的返回值类型可以区分重载的方法吗?
答:不可以,因为Java调用方法时可以忽略方法返回值。
3 成员变量和局部变量
成员变量指的是类中定义的变量;局部变量指的是方法中定义的变量。(使用类定义的变量是引用变量,如 Person p)
成员变量被分为类变量和实例变量两种,定义成员变量时没有static修饰的就是实例变量,有static修饰的就是类变量
局部变量根据定义形式的不同,被分为三种:
- 形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效
- 方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效
- 代码块局部变量:在代码块中定义的局部变量,它的作用域是从定义该变量的地方生效,到该代码块结束时失效
与成员变量不同的是,局部变量除形参之外,都必须显式初始化,也就是说,必须先给方法局部变量和代码块局部变量指定初始值,否则不可以直接访问它们。
public class VariableOverrideTest {
//定义一个实例变量
private String name="张三";
//定义一个类变量
private static int age=30;
public static void main(String[] args) {
//方法里的局部变量,局部变量覆盖成员变量
int age=80;
//直接访问age变量,将输出age局部变量的值
System.out.println(age);
//使用类名作为age变量的限定,将输出类变量的值
System.out.println(VariableOverrideTest.age);
//运行info()方法
new VariableOverrideTest().info();
}
public void info() {
//方法里的局部变量,局部变量覆盖成员变量
String name="李四";
System.out.println(name);
//使用this作为name的限定,将输出实例变量的值
System.out.println(this.name);
}
}
运行结果:
80
30
李四
张三
4 封装
4.1 理解封装
封装指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
程序设计追求高内聚(尽可能把模块的内部数据、功能实现细节隐藏在模块内部独立完成,不允许外部直接干预)、低耦合(仅暴露少量的方法给外部使用)。
访问控制符:private当前类访问权限---->default包访问权限------>protected子类访问权限------>public公共访问权限
控制符的使用存在几条基本原则:
- 类里的绝大部分成员变量都应该使用private修饰,只有一些static修饰的、类似全局变量的成员变量,才可能考虑使用public修饰。此外,有些方法只用于辅助实现该类的其他方法,被称为工具方法,也应该使用private修饰。
- 如果某个类主要用作其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不是被外界直接调用,则应该使用protected修饰这些方法
- 希望暴露出来给其他类自由调用的方法应该使用public修饰,因此,构造器通过使用public修饰,允许在其他地方创建该类的实例。因为外部类通常希望被其他类自由使用,所以大部分外部类使用public修饰。
4.2 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)的相关类/接口,这些类主要用于构建图形用户界面程序。
- java.swing:这个包下包含了Swing图形用户界面编程的相关类/接口,这些类可用于构建平台无关的GUI程序。
5 类的继承
5.1 继承的特点
子类扩展了父类,将获得父类所有的成员变量和方法。
public class Fruit {
public double weight;
public void info() {
System.out.println("我是水果,重量为"+weight+"g");
}
}
public class Apple extends Fruit{
public static void main(String[] args) {
//创建Apple对象
Apple a = new Apple();
//apple对象本身没有weight成员变量,但是其的父类有weight成员变量,因此也可以访问apple的成员变量
a.weight=88;
//调用apple对象的info()方法
a.info();
}
}
运行结果
我是水果,重量为88.0g
5.2 重写父类的方法
- 子类包含与父类同名方法的现象被称为方法重写(override),也称方法覆盖。
- 方法的重写遵循“两同两小一大”规则:
“两同”即方法名相同、形参列表相同;
“两小”即子类方法返回值类型应比父类方法返回值类型更小或相等、子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
需要指出的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法。(方法前加static的是类方法,不加的是实例方法) - 子类如需访问父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)或父类类名(被覆盖的是类方法)。如果父类方法具有private访问权限,则方法对其子类是隐藏的,子类无法访问、重写该方法。
5.3 super限定
super用于限定该对象调用它从父类继承得到的实例变量或方法。如果在构造器中使用super,则super用于限定该构造器初始化的是该对象从父类继承得到的实例变量,而不是该类自己定义的实例变量。
5.4 调用父类构造器
- 子类不会获得父类中的构造器,但子类构造器里可以调用父类构造器的初始化代码。
- 在一个构造器中调用另一个重载的构造器使用this关键字完成,在子类构造器中调用父类构造器使用super调用完成。
(this和super的区别:super调用的是其父类构造器,而this调用的是同一个类中重载的构造器;相同点:两者都出现在构造器执行体的第一行)
- 当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行,不仅如此,执行父类构造器时,系统会再次上溯执行其父类构造器......以此类推,创建任何Java对象,最先执行的总是java.lang.Object类的构造器。
class Test1{
public Test1() {
System.out.println("test1无参数的构造器");
}
}
class Test2 extends Test1{
public Test2(String context) {
System.out.println("test2带一个参数的构造器,"+"参数为:"+context);
}
public Test2(String context,String context2) {
this(context);//使用this调用同一个重载的构造器
System.out.println("test2带两个参数的构造器,"+"第一个参数:"+context+",第二个参数:"+context2);
}
}
public class Test3 extends Test2 {
public Test3() {
super("hello","world");//调用父类中有两个参数的构造器
System.out.println("test3无参数的构造器");
}
public static void main(String[] args) {
new Test3();
}
}
运行结果为:
test1无参数的构造器
test2带一个参数的构造器,参数为:hello
test2带两个参数的构造器,第一个参数:hello,第二个参数:world
test3无参数的构造器
6 多态
Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
6.1 多态性
通过Object p = new Person()代码定义一个变量p,则这个引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。
与方法不同的是,对象的实例变量则不具备多态性。因此使用引用变量来访问其包含的实例变量时,总是访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。
6.2 引用变量的强制类型转换
当把子类对象赋给父类引用变量时,被称为向上转型,这种转型总是可以成功的;但把一个父类对象赋给子类引用变量时,就需要进行强制类型转换,而且还可能在运行时产生异常,使用instanceof运算符可以让强制类型转换更安全。
instanceof运算符的作用是:在进行强制类型转换之前,首先判断一个对象是否是后一个类的实例,是否可以转换成功,从而使代码更加健壮。语法是 A instanceof B 其中,A是一个引用类型变量,B通常是一个类,也可以是接口。
7 继承和组合
7.1 利用组合实现复用
对于继承而言,子类可以直接获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类那里继承到的方法;而组合则是把旧类对象作为新类的成员变量组合进来,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此,通常需要在新类里使用private修饰被组合的旧类对象。