Java面向对象编程
类和对象
类:是一组相关属性和行为的集合,可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。
对象:是一类事物的具体体现,对象是类的一个实例,必须具备该类事物的属性和行为。
类和对象的关系:类是一类事物的描述,是抽象的,对象是一类事物的实例,是具体的。
面向对象的三大特征:封装、继承、多态。
类的定义和使用
定义一个学生类,命名为Student
public class Student {
//成员变量
String name;
int age;
//成员方法
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
public void study() {
System.out.println("学习");
}
}
对象的创建:
public static void main(String[] args) {
//类名称 对象名 = new 类名称()
Student stu = new Student();
//使用成员变量
System.out.println(stu.name);
System.out.println(stu.age);
//属性赋值
stu.name = "tom";
stu.age = 18;
System.out.println(stu.name);
System.out.println(stu.age);
}
注意:如果创建多个对象,每个对象都独立的拥有一套类的属性(非static的),如果修改一个对象的属性,则不会影响另一个对象属性的值。
成员变量和局部变量
成员变量:
- 定义在类的大括号中,方法的外部。
- 如果没有赋值,会有默认初始化值。
- 位于堆内存。
- 随着对象创建而诞生,随着对象被垃圾回收而消失。
- 声明时可以使用权限修饰符,如public、private、protected。
局部变量:
- 声明在方法内、方法形参、代码块内。
- 没有默认值,如果要使用必须手动赋值。
- 位于栈内存。
- 随着方法进栈而诞生,随着方法出栈而消失。
示例:
class User{
//成员变量
String name;
//可以使用权限修饰符
public boolean isMale;
public void methodA(){
int num = 20;
System.out.println(num);
System.out.println(name);
}
public void methodB(int param){ //方法的参数是局部变量
int age;
//没有赋值,不能使用
//System.out.println(age);
}
}
匿名对象
当创建对象的时候如果省略对象的名称,就称为匿名对象。
Person类如下:
public class Person {
String name;
public void showName() {
System.out.println("我叫:" + name);
}
}
测试如下:
/**
* 匿名对象就是创建对象的时候没有显式的赋给一个变量名,
* 注意:匿名对象只能调用一次,下次在使用需要在创建一个新对象。
*/
public class DemoAnonymous {
public static void main(String[] args) {
new Person().name = "tom";
new Person().showName(); //我叫:null
}
}
匿名对象作为方法的参数和返回值
public class Demo02Anonymous {
public static void main(String[] args) {
//使用匿名对象传参
// methodParam(new Scanner(System.in));
Scanner sc = methodReturn();
int num = sc.nextInt();
System.out.println("输入的是:" + num);
}
public static void methodParam(Scanner sc) {
int num = sc.nextInt();
System.out.println("输入的是:" + num);
}
//匿名对象作为方法的返回值
public static Scanner methodReturn() {
return new Scanner(System.in);
}
}
方法的重载
重载的定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
注意:和方法的权限修饰符,返回值类型,形参变量名,方法体都没关系。
public class OverLoadTest {
//如下4个方法构成重载
public void getSum(int i, int j) {
System.out.println(i + j);
}
public void getSum(double d1, double d2) {
}
public void getSum(String s, int i) {
}
public void getSum(int i, String s) {
}
}
可变个数的形参
JavaSE5.0提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。
public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.show(100);
test.show("hello");
test.show("hello", "world");
}
public void show(int i) {
System.out.println(i);
}
public void show(String s) {
System.out.println("show(String s):" + s);
}
public void show(String... strs) {
System.out.println("show(String... args)");
for (String str : strs) {
System.out.println(str);
}
}
}
注:
- 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。
- 可变个数的形参在方法的形参中,必须声明在末尾。
- 可变个数的形参在方法的形参中,最多只能声明一个可变形参。
封装性
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性,通俗的说,就是把该隐藏的隐藏起来,该暴露的暴露出来。需要将类的属性私有化,同时提供公共的方法来获取和设置。
封装性的体现需要权限修饰符来配合,Java中提供了四种权限(从小到大排列):private、default(缺省)、protected、public。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | yes | |||
default(缺省,即什么也不写) | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
对于class的权限修饰符只可以使用public和default。
public class Person {
private int age;
public void setAge(int a) {
if (a < 0 || a > 130) {
System.out.println("传入的数据非法!");
return;
}
age = a;
}
public int getAge() {
return age;
}
}
构造方法
构造方法(构造器)的作用:用来创建对象。
构造器的特定:
(1)如果没有显式的定义类的构造器,则编译器默认提供一个空参的构造器。
(2)如果显式的定义了构造器,则编译器不会自动生成空参的构造器了。
(3)构造器的名称必须和类名相同。
(4)构造器没有返回值类型,也不写void。
(5)一个类中可以重载多个构造器。
构造器的语法结构:
修饰符 类名(){
}
//或者
修饰符 类名(形参列表){
}
示例:
public class TestConstructor {
public static void main(String[] args) {
Circle circle = new Circle(1.5);
circle.printInfo();
}
}
class Circle {
private double radius;
//有参构造
public Circle(double r) {
radius = r;
}
//无参构造
public Circle() {
}
public void printInfo() {
System.out.println(radius);
}
}
this关键字的使用
this可以用来修饰属性、方法、构造器。
this修饰属性和方法:this可以理解为当前对象。
- 在类的方法中可以使用
this.属性
或者this.方法
的方式调用当前对象属性和方法,通常情况下,可以省略this
。但是如果方法的形参和类的属性同名时,必须显式的使用this.变量
的方式,表明此变量是属性,而非形参。 - 在类的构造器中,我们可以使用
this.属性
或this.方法
的方式,调用当前正在创建的对象的属性和方法,通常情况下,可以省略this
。但是如果构造器的形参和类的属性同名时,必须显式的使用this.变量
的方式,表明此变量是属性,而非形参。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在类的构造器中可以使用this
来调用本类中的其他构造器。规定使用this
调用其他构造器时必须声明在当前构造器的首行。
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(int age) {
this();
this.age = age;
}
public Person(String name, int age) {
this(age);
this.name = name;
this.age = age;
}
...
}
package和import的作用
package
package关键字的作用:为了更好的实现项目中类的管理,提供了包的概念,使用package声明类或接口所属的包,声明在源文件的首行。
语法格式:
package pkg1[.pkg2[.pkg3…]];
每 .
就代表一层文件目录。
注意:同一个包下不能命名同名的类、接口。
JDK中主要的包介绍:
(1)java.lang:包含java语言的核心类,如String、Math、Integer、System等。
(2)java.net:包含与网络相关操作的类和接口。
(3)java.io:包含能提供多种输入输出功能的类。
(4)java.util:包含一些工具类,如自定义系统特性、接口的集合框架类、日期相关的函数。
import
在源文件中使用 import 导入指定包下的类、接口。import 语句应位于 package 语句之后,所有类的定义之前。
语法格式:
import package1[.package2…].(classname|*);
可以使用通配符 *
导入包下的所有结构。
如果使用的类或接口是java.lang包下定义的,则省略import。
如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
调用指定类或接口下的静态属性或方法,使用import static的方式。
继承性
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法。Java使用extends关键字来实现继承。
class A extends B{
}
子类A继承父类B之后,子类A就获取了父类B中声明的所有属性和方法。
子类继承父类之后,还可以声明自己特有的属性和方法。
一个类可以被多个子类继承。
继承中成员变量的访问特点
定义父类:
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu() {
System.out.println(num);
}
}
定义子类:
public class Zi extends Fu {
int numZi = 20;
int num = 200;
public void methodZi() {
System.out.println(num);
}
}
测试如下:
/**
* 在父子类的继承关系中,如果成员变量名重名,则创建子类对象时,访问有两种方式:
* (1)直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有向上找
* (2)间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找
*/
public class Demo01ExtendsField {
public static void main(String[] args) {
Fu fu = new Fu(); //创建父类对象
System.out.println(fu.numFu); //只能调用父类的,不能调用子类的
Zi zi = new Zi();
System.out.println(zi.numFu);
System.out.println(zi.numZi);
//当父类和子类都有num,等号左边是谁,优先用谁
System.out.println(zi.num); //优先子类
//这个是子类的方法,优先用子类的,没有在向上找
zi.methodZi();
// 这个方法是在父类中定义的
zi.methodFu();
}
}
子类方法中重名的三种变量
父类:
public class Fu {
int num = 10;
}
子类:
public class Zi extends Fu {
int num = 20;
public void method() {
int num = 30;
System.out.println(num); //30 局部变量
System.out.println(this.num); // 20 本类的成员变量
System.out.println(super.num); //10 父类的成员变量
}
}
测试如下:
/**
* 局部变量:直接写成员变量名
* 本类中的局部变量:this.成员变量名
* 父类中的成员变量: super.成员变量名
*/
public class Demo01ExtendsField {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
}
}
-
在子类的方法或者构造器中使用
super.属性
或者super.方法()
的方式显示的调用父类中声明的属性或方法,通常情况下,可以省略super。 -
但是当子类和父类中定义了同名的属性时,在子类中调用父类中声明的属性,必须显式的使用
super.属性
的方式,来表明调用的是父类的属性。 -
在子类的构造器中可以使用
super(形参列表)
的方式,调用父类中指定的构造器,必须声明在首行调用。 -
在类的构造器中
this.(形参列表)
和super(形参列表)
只能存在一个。
继承中的重写
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重写,在程序执行时,子类的方法将覆盖父类的方法。
要求:
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型。
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限。
- 子类不能重写父类中声明为private权限的方法。
- 子类方法抛出的异常不能大于父类被重写方法的异常。
- 父类被重写的方法返回值类型是void,则子类重写的方法的返回值类型只能是void。
定义父类:
public class Person {
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("吃饭");
}
public void walk(int distance) {
System.out.println("距离:" + distance);
}
}
定义子类:
public class Student extends Person {
String major;
public Student() {
}
public Student(String major) {
this.major = major;
}
public void study() {
System.out.println("学习的专业是:" + major);
}
//重写父类的方法
public void eat() {
System.out.println("学生应该吃点好的");
}
}
多态性
多态是同一个行为具有多个不同表现形式或形态的能力。
多态的使用条件:类的继承或者接口的实现。
多态的格式和使用
格式:
父类名称 对象名 = new 子类名称();
//或者
接口名称 对象名 = new 实现类名称();
- 父类引用指向子类对象
- 在编译器,只能调用父类中声明的方法,在运行期,实际执行的是子类重写父类的方法。总结:编译看左边,运行看右边
父类如下:
public class Fu {
public void method(){
System.out.println("父类方法");
}
}
子类如下:
public class Zi extends Fu{
@Override
public void method() {
System.out.println("子类方法");
}
}
测试如下:
public class Demo01Multi {
public static void main(String[] args) {
//左侧父类的引用指向右侧子类的对象
Fu obj = new Zi();
obj.method();
}
}
多态成员变量的使用特点
父类如下:
public class Fu {
int num = 10;
public void showNum() {
System.out.println(num);
}
}
子类如下:
public class Zi extends Fu{
int num = 20;
@Override
public void showNum() {
System.out.println(num);
}
}
测试如下:
public class Demo01Multi {
public static void main(String[] args) {
//左侧父类的引用指向右侧子类的对象
Fu obj = new Zi();
System.out.println(obj.num); //10
obj.showNum(); //子类没有覆盖重写就是父中的10,子类如果覆盖重写就是子中的20
}
}
多态中成员方法的使用特点
父类如下:
public class Fu {
int num = 10;
public void showNum() {
System.out.println(num);
}
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
子类如下:
public class Zi extends Fu {
int num = 20;
@Override
public void showNum() {
System.out.println(num);
}
@Override
public void method() {
System.out.println("子类方法");
}
public void methodZi() {
System.out.println("子类特有方法");
}
}
测试如下:
public class Demo01Multi {
public static void main(String[] args) {
Fu obj = new Zi();
//父子都有,优先用子
obj.method(); //子类方法
//子类没有,父类有,向上找父类
obj.methodFu(); //父类特有方法
}
}
对象的向上向下转型
对象的向上转型
对象的向上转型其实就是多态的写法,对象的向上转型一定是安全的,从小范围转向了大范围。
父类名称 对象名 = new 子类名称();
示例:
Animal animal = new Cat();
对象的向下转型一定是安全的,从小范围转向了大范围。
对象的向下转型
父类引用的对象转换为子类类型。
子类名称 对象名 = (子类名称)父类对象;
示例:
Animal animal = new Cat();
Cat cat = (Cat)animal;
instanceof关键字的使用
要想知道一个父类引用的对象,本来是什么子类,可以使用instanceof关键字。返回的是boolean类型。
父类Animal如下:
public abstract class Animal {
public abstract void eat();
}
子类Cat如下:
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("猫捉老鼠");
}
}
子类Dog如下:
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃肉");
}
public void watchHouse(){
System.out.println("狗看家");
}
}
测试如下:
public class Demo04Instanceof {
public static void main(String[] args) {
Animal animal = new Dog();
//判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
//判断一下父类引用animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
method(new Dog());
}
public static void method(Animal animal) {
//判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
//判断一下父类引用animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
}