面向对象
面向对象三大特征:封装、继承、多态
1、类(分为属性和行为)
通常类名大写开头,例如:Student
类的关系:关联关系(has a)、继承关系(is a)、实现关系(like a)、聚合关系、依赖关系、组合关系
关联关系:一对一、一对多、多对多
List:有序可重复
set:无序不可重复
map:KEY-VALUE结构
定义一个类,主要有三个步骤:
1.1、定义类名,
用于区分不同的类。如下代码中 public class 后面跟的就是类名。class是声明类的关键字,类名后面跟上大括号,大括号里面就是类的一些信息。public 为权限修饰符。
public class 类名 {
//定义属性部分(成员变量)
属性1的类型 属性1;
属性2的类型 属性2;
...
//定义方法部分
方法1
方法2
...
}
1.2、编写类的属性。
对象有什么,需要通过属性来表示。属性的定义是写在类名后面的大括号里,在定义属性时,要明确属性的类型。在一个类当中可以写一个或多个属性。当然也可以不定义属性。
1.3、编写类的方法。
方法也是写在大括号里面。可以定义一个方法或多个方法,当然也可以不定义方法。
1.4、类的变量
- 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
- 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
- 类变量:也叫静态变量,类变量也声明在类中,方法体之外,但必须声明为 static 类型。
1.5、权限修饰符
如图所示,代表了不同的访问修饰符的访问范围,比如 private 修饰的属性或者方法,只能在当前类中访问或者使用。默认 是什么修饰符都不加,默认在当前类中和同一包下都可以访问和使用。protected 修饰的属性或者方法,对同一包内的类和所有子类可见。public 修饰的属性或者方法,对所有类可见。
2、对象
2.1、创建对象
定义类的时候不会为类开辟内存空间,但是一旦创建了对象,系统就会在内存中为对象开辟一块空间,用来存放对象的属性值和方法。
声明对象:
类名 对象名;
创建对象:
对象名 = new 类名();
2.2、默认字段初始化
如果在构造器中没有显式地为字段设置初值,那么就会被自动地赋为默认值:数值为0、布尔值为false、对象引用为null。有些人认为依赖默认值的做法是一种不好的编程实践。确实,如果不明确地对字段进行初始化,就会影响程序代码的可读性。
注释:这是字段与局部变量的一个重要区别。方法中的局部变量必须明确地初始化。
但是在类中,如果没有初始化类中的字段,将会自动初始化为默认值(0、false或null)。
例如,考虑Employee类。假定没有在构造器中指定如何初始化某些字段,默认情况下,就会将salary字段初始化为0,将name 和 hireDay字段初始化为null。
但是,这并不是一个好主意。如果此时调用getName方法或getHireDay方法,就会得到一个null引用,这应该不是我们所希望的结果:
LocalDate h = harry.getHireDay();
int year = h.getYear(); // throws exception if h is null
3、构造方法
构造方法分为有参构造方法和无参构造方法,若类没有显式创建构造方法,则 Java 会默认创建一个无参构造方法;而如果你显式创建了一个构造方法,则 Java 不会再为该类创建构造方法。
此时如果有子类继承该类,则子类需要显式调用该类的构造方法,否则会出现编译错误。
构造方法不能被继承。
构造函数的特点
-
函数名与类名相同
-
不用定义返回值类型。(不同于void类型返回值,void是没有具体返回值类型;构造函数是连类型都没有)
-
不可以写return语句。(返回值类型都没有,故不需要return语句)
注:一般函数不能调用构造函数,只有构造函数才能调用构造函数。
创建子类对象调用子类的构造方法的时候会先调用父类的构造方法,在子类的构造方法中调用父类的构造方法是用super(),如果没有写super(),则默认调用父类的无参构造方法。
在面向对象中有一个非常重要的知识点,就是构造方法。每个类都有构造方法,在创建该类的对象的时候他们将被调用,如果没有定义构造方法,Java 编译器会提供一个默认构造方法。 创建一个对象的时候,至少调用一个构造方法。比如在新建一个对象 new Object(),括号中没有任何参数,代表调用一个无参构造方法(默认构造方法就是一个无参构造方法)。构造方法的名称必须与类名相同,一个类可以定义多个构造方法。
-
构造方法的名称与类名相同,且没有返回值。它的语法格式如下:
//与类同名,可以指定参数,没有返回值 public 构造方法名(){ //初始化代码 }
下面是一个构造方法的例子:
public class People{ //无参构造方法 public People(){} //有一个参数的构造方法 public People(int age){ } }
又例如具体的构造方法:
public class People { //属性(成员变量)有什么 double height; //身高 int age; //年龄 int sex; //性别,0为男性,非0为女性 //构造函数,初始化了所有属性 public People(double h, int a, int s){ height = h; age = a; sex = s; } }
//创建对象,调用我们自己定义的有参构造方法 People XiaoMing = new People(168, 21, 1);
上面的例子中通过 new 关键字将类实例化成对象,而 new 后面跟的就是构造方法。于是可以知道 new + 构造方法 可以创建一个新的对象。
-
如果在定义类的时候没有写构造方法,系统会默认生成一个无参构造方法,这个构造方法什么也不会做。
-
当有指定的构造方法时,系统都不会再添加无参构造方法了。
-
构造方法的重载:方法名相同,但参数不同的多个方法,调用时会自动根据不同的参数选择相应的方法。
-
引用与对象实例
在新建对象实例时,需要为对象实例设置一个对象名,就像这样
Object object=new Object();
那么变量 object 就真的是 Object 对象么,这里其实只是创建了一个 object 对象的引用。如果同学们学过 C 语言,这里就和指针一样,变量 object 保存的其实 Object 对象的引用,指向了 Object 对象。
Object object1 = new Object(); Object object2 = object1; System.out.println(object1 == object2);
运行得到的结果为 true,说明 object1 和 object2 的内存地址相同 (== 会比较两个对象的内存地址是否相同),它们实际上是引用同一对象,如果改变 object1 对象内部的属性,那么 object2 的属性同样会改变,同学们可以下来验证这一点。
4、继承
继承需要符合的关系是:is-a,父类更通用,子类更具体。
首先,子类一定要调用超类的构造器。如果没有显示调用超类的构造器,那么子类构造器就会默认调用超类的无参构造器,如果超类没有无参构造器,那么编辑器就会报错,说: 超类中没有可用的默认构造器
语法:
class 子类 extends 父类
继承的作用:实现代码的复用
继承的特点:
-
子类拥有父类除 private 以外的所有属性和方法。
-
子类可以拥有自己的属性和方法。
-
子类可以重写实现父类的方法。
-
Java 中的继承是单继承,一个类只有一个父类。
Java 实现多继承的一个办法是 implements(实现)接口,但接口不能有非静态的属性,这一点请注意。
5、封装
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别
这样做有什么好处?
- 只能通过规定的方法访问数据。
- 隐藏类的实例细节,方便修改和实现。
如何去实现类的封装呢?
- 修改属性的可见性,在属性的前面添加修饰符 (private)
- 对每个值属性提供对外的公共方法访问,如创建 getter/setter(取值和赋值)方法,用于对私有属性的访问
- 在 getter/setter 方法里加入属性的控制语句,例如我们可以加一个判断语句,对于非法输入给予否定。
主类
public class NewObject {
public static void main(String[] args) {
People LiLei = new People(); //创建了一个People对象LiLei
//利用setter方法为属性赋值
LiLei.setHeight(170.0);
//利用getter方法取属性值
System.out.println("LiLei的身高是"+LiLei.getHeight());
}
}
public class People {
//属性(成员变量)有什么,前面添加了访问修饰符private
//变成了私有属性,必须通过方法调用
private double height; //身高
//属性已经封装好了,如果用户需要调用属性
//必须用getter和setter方法进行调用
//getter和setter方法需要程序员自己定义
public double getHeight(){
//getter 方法命名是get关键字加属性名(属性名首字母大写)
//getter 方法一般是为了得到属性值
return height;
}
//同理设置我们的setter方法
//setter 方法命名是set关键字加属性名(首字母大写)
//setter 方法一般是给属性值赋值,所以有一个参数
public void setHeight(double newHeight){
height = newHeight;
}
}
6、方法重载与重写
6.1、方法重载
重载(overloading),如果多个方法(比如,StringBuilder构造器方法)有相同的名字、不同的参数,便出现了重载。编译器必须挑选出具体调用哪个方法。它用各个方法首部中的参数类型与特定方法调用中所使用的值类型进行匹配,来选出正确的方法。如果编译器找不到匹配的参数,就会产生编译时错误,因为根本不存在匹配,或者没有一个比其他的更好(这个查找匹配的过程被称为重载解析(overloading resolution))。
注释:Java允许重载任何方法,而不只是构造器方法。因此,要完整地描述一个方法,需要指定方法名以及参数类型。这叫作方法的签名(signature)。例如,String类有4个名为 index0f的公共方法。它们的签名是
- index0f(int)
- index0f(int,int)
- indexOf(String)
- indexOf(String,int)
返回类型不是方法签名的一部分。也就是说,不能有两个名字相同、参数类型也相同却有不同返回类型的方法。
6.2、方法重写
子类可以继承父类的方法,但如果子类对父类的方法不满意,想在里面加入适合自己的一些操作时,就需要将方法进行重写。并且子类在调用方法中,优先调用子类的方法。
比如 Animal 类中有 bark() 这个方法代表了动物叫,但是不同的动物有不同的叫法,比如狗是汪汪汪,猫是喵喵喵。
当然在方法重写时要注意,重写的方法一定要与原父类的方法语法保持一致,比如返回值类型,参数类型及个数,和方法名都必须一致。
例如:
public class Animal {
//类方法
public void bark() {
System.out.println("动物叫!");
}
}
copy
public class Dog extends Animal {
//重写父类的bark方法
public void bark() {
System.out.println("汪!汪!汪!");
}
}
写个测试类来看看输出结果:
public class Test{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Dog d = new Dog(); // Dog 对象
Animal b = new Dog(); // Dog 对象,向上转型为Animal类型,具体会在后面的内容进行详解
a.bark();// 执行 Animal 类的方法
d.bark();//执行 Dog 类的方法
b.bark();//执行 Dog 类的方法
}
}
7、多态
多态就是同一种行为作用在不同得对象上,有不同的结果
Java 实现多态有三个必要条件:继承、重写和向上转型(即父类引用指向子类对象)。
Java 中多态的实现方式:继承父类进行方法重写,抽象类和抽象方法,接口实现。
package cn.Test;
public class App
{
public static void main( String[] args )
{
/**
* 利用面向抽象实现OCP原则:Open Close Principle(开闭原则 或闭合原则 :对修改闭合,对扩展开放)
* 对修改闭合:将来系统新增需求,不修改原代码软件,通过配置文件来修改
* 对扩展开放:只需要新增相应的类和配置文件来满足需求
*/
//向上转型,特点:父类的引用(animal)指向子类的对象( new Dog() 或 new Cat() )
Animal animal =new Dog(); // 结果打印 “ 汪汪汪 ”
animal.bark(); //该行代码不变,就会出现多态,即同一种行为作用在不同得对象上,有不同的结果
}
}
/**
* 抽象类
*/
public abstract class Animal {
/**
* bark叫不能具体化
* 不同动物有不同叫声,所以这种行为必须是抽象的,因为不确定是哪种动物
*/
abstract void bark();
}
public class Cat extends Animal {
@Override
void bark() {
System.out.println("喵喵");
}
}
public class Dog extends Animal {
@Override
void bark() {
System.out.println("汪汪汪");
}
}
8、向上转型
Animal a = new Animal(); //a是父类的引用指向的是本类的对象
Animal b = new Dog(); //b是父类的引用指向的是子类的对象
在这里,可以认为由于 Dog 继承于 Animal,所以 Dog 可以自动向上转型为 Animal,所以 b 是可以指向 Dog 实例对象的。
如果定义了一个指向子类对象的父类引用类型,那么它除了能够引用父类中定义的所有属性和方法外,还可以使用子类强大的功能。但是对于只存在于子类的方法和属性就不能获取。
例如:
class Animal {
//父类方法
public void bark() {
System.out.println("动物叫!");
}
}
class Dog extends Animal {
//子类重写父类的bark方法
public void bark() {
System.out.println("汪、汪、汪!");
}
//子类自己的方法
public void dogType() {
System.out.println("这是什么品种的狗?");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
Animal b = new Dog();
Dog d = new Dog();
a.bark();
b.bark();
//b.dogType();
//b.dogType()编译不通过
d.bark();
d.dogType();
}
}
/* 结果打印
动物叫!
汪、汪、汪!
汪、汪、汪!
这是什么品种的狗?
*/
9、抽象类
在定义类时,前面加上 abstract 关键字修饰的类叫抽象类。
抽象类中有抽象方法,这种方法仅有声明而没有方法体。抽象方法声明语法如下:
abstract void f(); //f()方法是抽象方法
那我们什么时候会用到抽象类呢?
- 在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法。也就是说抽象类是约束子类必须要实现哪些方法,而并不关注方法如何去实现。
- 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性。
所以由上可知,抽象类是限制规定子类必须实现某些方法,但不关注实现细节。
那抽象类如何用代码实现呢,它的规则如下:
- 用 abstract 修饰符定义抽象类。
- 用 abstract 修饰符定义抽象方法,只用声明,不需要实现。
- 包含抽象方法的类就是抽象类。
- 抽象类中可以包含普通的方法,也可以没有抽象方法。
- 抽象类的对象不能直接创建,通常是定义引用变量指向子类对象。
10、接口
如果子类是非抽象类,则必须实现接口中的所有方法;
如果子类是抽象类,则可以不实现接口中的所有方法,因为抽象类中允许有抽象方法的存在!
在 Java8 中:
-
接口不能用于实例化对象。
-
接口中方法只能是抽象方法、default 方法、静态方法。
-
接口成员是 static final 类型。
-
接口支持多继承。
在 Java9 中,接口可以拥有私有方法和私有静态方法,但是只能被该接口中的 default 方法和静态方法使用。
接口只能由public修饰,其他修饰符修饰都会引发问题或者不能通过编译。
public>protected>default>private
原因:
protected,这个问题用反证法比较好解释清楚,假设类B和A不在同一个包,A又是protected类, 那么B能访问A的前提是B是A的子类,而B能成为A的子类(或者说B能继承A)的前提又是B能访问A。这两个条件互为前提,无法实现。
11、内部类
将一个类的定义放在另一个类的定义内部,这就是内部类。而包含内部类的类被称为外部类。
内部类的主要作用如下:
- 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
- 内部类的方法可以直接访问外部类的所有数据,包括私有的数据
- 内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便
- 内部类允许继承多个非接口类型
// People.java
//外部类People
public class People {
private String name = "LiLei"; //外部类的私有属性
//内部类Student
public class Student {
String ID = "20151234"; //内部类的成员属性
//内部类的方法
public void stuInfo(){
System.out.println("访问外部类中的name:" + name);
System.out.println("访问内部类中的ID:" + ID);
}
}
//测试成员内部类
public static void main(String[] args) {
People a = new People(); //创建外部类对象,对象名为a
Student b = a.new Student(); //使用外部类对象创建内部类对象,对象名为b
// 或者为 People.Student b = a.new Student();
b.stuInfo(); //调用内部对象的suInfo方法
}
}
/*结果打印
访问外部类中的name:LiLei
访问内部类中的ID:20151234
*/
成员内部类的使用方法:
- Student 类相当于 People 类的一个成员变量,所以 Student 类可以使用任意访问修饰符。
- Student 类在 People 类里,所以访问范围在类里的所有方法均可以访问 People 的属性(即内部类里可以直接访问外部类的方法和属性,反之不行)。
- 定义成员内部类后,必须使用外部类对象来创建内部类对象,即 内部类 对象名 = 外部类对象.new 内部类();。
- 如果外部类和内部类具有相同的成员变量或方法,内部类默认访问自己的成员变量或方法,如果要访问外部类的成员变量,可以使用 this 关键字。如上述代码中:a.this。
注:成员内部类不能含有 static 的变量和方法,因为成员内部类需要先创建了外部类,才能创建它自己的。
12、静态内部类
静态内部类是 static 修饰的内部类,通常被称为嵌套类。
// People.java
//外部类People
public class People {
private String name = "LiLei"; //外部类的私有属性
/*外部类的静态变量。
Java 中被 static 修饰的成员称为静态成员或类成员。它属于整个类所有,而不是某个对象所有,即被类的所有对象所共享。静态成员可以使用类名直接访问,也可以使用对象名进行访问。
*/
static String ID = "510xxx199X0724XXXX";
//静态内部类Student
public static class Student {
String ID = "20151234"; //内部类的成员属性
//内部类的方法
public void stuInfo(){
System.out.println("访问外部类中的name:" + (new People().name));
System.out.println("访问外部类中的ID:" + People.ID);
System.out.println("访问内部类中的ID:" + ID);
}
}
//测试成员内部类
public static void main(String[] args) {
Student b = new Student(); //直接创建内部类对象,对象名为b
b.stuInfo(); //调用内部对象的suInfo方法
}
}
/*结果打印
访问外部类中的name:LiLei
访问外部类中的ID:510xxx199X0724XXXX
访问内部类中的ID:20151234
*/
静态内部类的特点是:
- 静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问。
- 如果外部类的静态成员与内部类的成员名称相同,可通过 类名.静态成员 访问外部类的静态成员;如果外部类的静态成员与内部类的成员名称不相同,则可通过 成员名 直接调用外部类的静态成员。
- 创建静态内部类的对象时,不需要外部类的对象,可以直接创建 内部类 对象名 = new 内部类(); 。
13、局部内部类
局部内部类,是指内部类定义在方法和作用域内。
例如:
// People.java
//外部类People
public class People {
//定义在外部类中的方法内:
public void peopleInfo() {
final String sex = "man"; //外部类方法中的常量
class Student {
String ID = "20151234"; //内部类中的常量
public void print() {
System.out.println("访问外部类的方法中的常量sex:" + sex);
System.out.println("访问内部类中的变量ID:" + ID);
}
}
Student a = new Student(); //创建方法内部类的对象
a.print();//调用内部类的方法
}
//定义在外部类中的作用域内
public void peopleInfo2(boolean b) {
if(b){
final String sex = "man"; //外部类方法中的常量
class Student {
String ID = "20151234"; //内部类中的常量
public void print() {
System.out.println("访问外部类的方法中的常量sex:" + sex);
System.out.println("访问内部类中的变量ID:" + ID);
}
}
Student a = new Student(); //创建方法内部类的对象
a.print();//调用内部类的方法
}
}
//测试方法内部类
public static void main(String[] args) {
People b = new People(); //创建外部类的对象
System.out.println("定义在方法内:===========");
b.peopleInfo(); //调用外部类的方法
System.out.println("定义在作用域内:===========");
b.peopleInfo2(true);
}
}
/*结果打印
定义在方法内:===========
访问外部类的方法中的常量sex:man
访问内部类中的变量ID:20151234
定义在作用域内:===========
访问外部类的方法中的常量sex:man
访问内部类中的变量ID:20151234
*/
局部内部类也像别的类一样进行编译,但只是作用域不同而已,只在该方法或条件的作用域内才能使用,退出这些作用域后无法引用的。
14、匿名内部类
匿名内部类,顾名思义,就是没有名字的内部类。正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口。
例如:
// Outer.java
public class Outer {
public Inner getInner(final String name, String city) {
return new Inner() {
private String nameStr = name;
public String getName() {
return nameStr;
}
};
}
public static void main(String[] args) {
Outer outer = new Outer();
Inner inner = outer.getInner("Inner", "NewYork");
System.out.println(inner.getName());
}
}
interface Inner {
String getName();
}
//运行结果:Inner。
匿名内部类是不能加访问修饰符的。要注意的是,new 匿名类,这个类是要先定义的, 如果不先定义,编译时会报错该类找不到。
同时,在上面的例子中,当所在的方法的形参需要在内部类里面使用时,该形参必须为 final。这里可以看到形参 name 已经定义为 final 了,而形参 city 没有被使用则不用定义为 final。
然而,因为匿名内部类没名字,是用默认的构造函数的,无参数的,如果需要该类有带参数的构造函数,示例如下:
public Inner getInner(final String name, String city) {
return new Inner(name, city) {
private String nameStr = name;
public String getName() {
return nameStr;
}
};
}
注意这里的形参 city,由于它没有被匿名内部类直接使用,而是被抽象类 Inner 的构造函数所使用,所以不必定义为 final。
15、static
修饰内部类说明该内部类属于外部类而不属于外部类的某个实例。修饰字段说明该字段属于类而不属于类实例。修饰方法说明该方法属于类而不属于类实例
-
静态成员(修饰变量)
Java 中被 static 修饰的成员称为静态成员或类成员。它属于整个类所有,而不是某个对象所有,即被类的所有对象所共享。静态成员可以使用类名直接访问,也可以使用对象名进行访问。 -
静态方法(修饰方法)
被 static 修饰的方法是静态方法,静态方法不依赖于对象,不需要将类实例化便可以调用,由于不实例化也可以调用,所以不能有 this,也不能访问非静态成员变量和非静态方法。但是非静态成员变量和非静态方法可以访问静态方法。- 在Java中静态方法可以被继承,但是不能被覆盖,即不能重写。
- 如果子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。
- 父类引用指向子类对象时,只会调用父类的静态方法。所以,它们的行为也并不具有多态性。
-
静态块(修饰块)
被static修饰的代码块只执行一次
public static void main(String args[]){ static { System.out.println("Hello world!"); } }
-
静态内部类(修饰类时只能修饰内部类)
16、final
final 关键字可以修饰类、方法、属性和变量
-
final 修饰类,则该类不允许被继承,为最终类
-
final 修饰方法,则该方法不允许被覆盖(重写)
-
final 修饰属性:则该类的属性不会进行隐式的初始化(类的初始化属性必须有值)或在构造方法中赋值(但只能选其一)
-
final 修饰变量,则该变量的值只能赋一次值,即常量
如://静态常量 public final static String SHI_YAN_LOU="shiyanlou";
17、this (必须在构造方法的第一行)
this 关键字代表当前对象
this.属性 操作当前对象的属性
this.方法 调用当前对象的方法
- this可以调用本类的构造函数
- this可以作为参数传递(暂时没有案例演示,第一阶段项目就会用到),非常实用的技术点
- this可以作为返回值返回
18、super (必须在构造方法的第一行)
super关键字在子类内部使用,代表父类对象。
- 访问父类的属性 super.属性名。
- 访问父类的方法 super.方法名。
- 子类构造方法需要调用父类的构造方法时,在子类的构造方法体里最前面的位置:super()。这种方法调用的是父类的无参构造函数,如果父类中创建了有参构造函数,则需要再括号中写入父类有参构造函数的名字,否则会报错。
public class A {
public A(String s) {
}
}
public class B extends A{
public B(String s) {
super(s);
}
19、package
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
包的作用:
- 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
- 包采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。
- 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
//在定义文件夹的时候利用"/"来区分层次
//包中用"."来分层
package com.shiyanlou.java
包的命名规范是全小写字母拼写。