面向对象基础
1、类
- 定义
类是一个模板,是对一系列具有相同属性和方法的对象的描述。 - 语法
修饰符 class 类名 {
类变量
成员变量
构造方法
类方法
成员方法
...
}
//eg.
public class Person {
static public String name; // 类变量
private int age; // 成员变量
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 成员方法
public int getAge() {
return age;
}
// 成员方法
public void setAge(int age) {
this.age = age;
}
}
- 修饰符:是对类访问权限的修饰,后面将会深入讲解。
- class:这个关键字表示定义的是一个类。
- 类名:类的名字,类的命名必须要表意,命名规则首先要遵循Java标识符的规范,用驼峰法命名,首字母大写,相邻单词之间用大写字母分割。
- 类变量:声明为static类型的变量,类变量也声明在类中,方法体之外。外部访问时可以通过类直接访问。
- 成员变量:成员变量是定义在类中、方法体之外的变量。这种变量在创建对象的时候实例化(分配内存)。成员变量可以被类中的方法和特定类的语句访问。
- 局部变量:定义在方法中的变量,只可以在该方法中被调用。
-
构造方法:在类实例化的过程中自动执行的方法叫做构造方法,它不需要你手动调用。
- 构造方法的名称必须与类的名称相同,并且没有返回值。
- 每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认的无参数的构造方法。
- 构造方法不能被显示调用。
- 构造方法的作用是初始化对象。
- 构造方法的使用场景:既然构造方法是对新对象进行初始化,那么当开发中分析事物的时候,发现事物一出现就具备了某些特征时,就可以将其定义在构造方法中,这样方便快捷,也符合面向对象的编程思想。
- 类方法:定义在类里面的方法,属于类本身的方法,用static关键字修饰。外部访问时可以通过类直接访问。
- 成员方法:定义在类里面的方法,在创建对象的时候实例化。外部访问时必须通过具体的实例。
2、对象
对象就是类的实例化结果,实例化指的是创建对象的过程。
- 创建对象的过程:
- 声明:声明一个对象,包括对象名称和对象类型。
- 实例化:使用关键字new来创建一个对象。
- 初始化:使用new创建对象时,会调用构造方法初始化对象。
- 语法
类名 对象名 = new 类的构造方法
//eg.
Person zhangsan = new Person("zhangsan", 18);
- 访问成员变量和方法
对象名.变量名
对象名.方法
//eg.
zhangsan.name;
zhangsan.getAge();
3、包(package)
包是Java语言用来区分类的最主要方式,在Java系统开发中,会产生成千上万的各式各样的类,那么区分不同类就是很关键的问题,包概念的引入解决了这个问题。包与我们在操作系统下面的文件夹非常类似,我们会将同种类别的文件用文件夹来区分,方便管理。另一个方面来看,包也能解决同名文件的问题,相同名称的类在一个包下面是无法区分的,但可以放在不同的包里面,从这个角度来看,包创建了一个独立的命名空间。
- 创建包
- 包的命名规则是:所有单词小写,相邻单词之间用下划线隔开。
- 在scr目录下创建包
创建包 - 在包下面创建类
4、import
在Java中,如果给出一个完整的限定名,包括包名、类名,那么Java编译器就可以很容易地定位到源代码或者类。Import语句就是用来提供一个合理的路径,使得编译器可以找到某个类。
//引用上面model包里面定义的Student类
import model.Student;
public class Main {
public static void main(String[] args) {
Student student = new Student();
student.setName("zhangsan");
System.out.println(student.getName());
}
}
5、继承
子类继承父类的特征和行为,使得子类对象具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
- 继承的实现:使用extends关键字
//语法
class ParentClass {
}
class SubClass extends ParentClass {
}
//eg.
public class Person {
protected String name;
protected int age;
private int money;
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;
}
}
public class Student extends Person {
private int studentNumber;
public Student(String name, int age) {
super(name, age);
}
public int getStudentNumber() {
return studentNumber;
}
public void setStudentNumber(int studentNumber) {
this.studentNumber = studentNumber;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("zhangsan", 18);
student.setStudentNumber(101);
System.out.println(student.name);
System.out.println(student.getStudentNumber());
}
}
- 继承的优点
- 继承可以扩充类的功能:对于子类来说,只要继承了父类,子类自动就具有父类的方法和属性,扩充的类的职能。
- 继承可以消除重复逻辑。
- 继承的局限
- 一个子类只能够继承一个父类,存在单继承局限
- 子类只能继承父类中非私有的属性和行为:
比如上面Person类里面的money是私有字段(用private关键字修饰的字段),这个私有字段是父类自己私有的,不能给子类使用。所以Student类无法访问父类的money属性。
- 子类不能继承父类的构造器,只能调用(隐式或显式):
- 如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super关键字调用父类的构造器并配以适当的参数列表。
- 如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器
- Java中所有类都继承自Object类:
Object类是Java所有类的基类,位于Java的lang包里,里面提供了一些方法供所有类去使用。
6、多态
对同一个行为具有多个不同表现形式或形态的能力。
-
重写
重写是指,子类对能够继承的父类的方法的实现过程进行重新实现,通常用关键字Override实现。比如父类Animal就只知道吃,但是对狮子来说,它重新实现了吃的过程,改成吃肉了。重写的核心是:方法的外表(名称,参数,返回值等)不变,内心重新实现! -
重写的条件
- 声明为final的方法不能被重写。
- 声明为static的方法不能被重写,但是能够被再次声明。
- 参数列表必须完全与被重写方法的相同;
- 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 父类的成员方法只能被它的子类重写。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
- 语法
class ParentClass {
public void method() {
// code
}
}
class SubClass extends ParentClass {
@Override
public void method() {
// code
}
}
例子:
public class Animal {
public void eat() {
System.out.println("老子就知道吃!!!");
} //重写方法
}
public class Cow extends Animal {
@Override
public void eat() {
System.out.println("人家可是素食主义者呢!!");
} //重写方法
}
public class Lion extends Animal {
@Override
public void eat() {
System.out.println("老子是吃肉长大的!!!");
}
}
public class Test {
public static void main(String[] args) {
Animal cow = new Cow();
cow.eat(); //人家可是素食主义者呢!!
Animal lion = new Lion();
lion.eat(); //老子是吃肉长大的!!!
}
}
-
关于多态的几点说明:
- 子类就是父类(is-a):
从上面的例子可以看出来,牛和狮子都是动物,所以在生成牛和狮子的实例的时候用动物类来指向,所以,子类就是父类;但是,反过来就不行,比如“动物是牛”这句话就说不通。所以在继承体系中,子类会比父类更加具体,代指的实例也更加明确。这种现象在Java中叫做“Is-A”的关系。 - 多态形成的条件:
继承的存在:继承是多态形成的前提条件,没有继承就没有多态。
重写:子类重写父类的方法才能形成多态。
父类引用变量指向子类实例:就是上面说的子类就是父类,“IS-A“的关系。
- 子类就是父类(is-a):
7、抽象类(Abstract Class)
-
抽象方法:
定义:抽象方法是一种特殊的方法,它只有声明,而没有具体的实现,用abstract关键字修饰。 - 语法:
// 语法:
abstract returnType methodName();
//eg.
abstract void eat();
-
抽象类
定义:包含抽象方法或被abstract修饰的类称为抽象类,抽象方法必须在抽象类中,抽象类中不一定有抽象方法。但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法 - 语法:
abstract class ClassName {
abstract returnType methodName(); //抽象方法
returnType methodName(){
// other method
}
}
// eg.
public abstract class Animal {
public abstract void eat(); //抽象方法
}
public class Cow extends Animal {
@Override
public void eat() {
System.out.println("人家可是素食主义者呢!!");
}
}
public class Lion extends Animal {
@Override
public void eat() {
System.out.println("老子是吃肉长大的!!!");
}
}
-
抽象类和普通类的区别:
- 抽象方法必须为public或者protected,缺省情况下默认为public。
- 抽象类不能用来创建对象:抽象类创建对象其实没有任何意义。
- 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
8、接口(Interface)
-
定义:
接口是一种声明的集合,是一种声明的规范,里面包含了很多抽象方法。 - 语法:
public interface interfaceName {
returnType method();
...
}
public class className implement iterfaceName {
public returnType method() {
// code
}
...
}
// eg.
public interface AnimalInterface {
void eat();
}
public class LionImplement implements AnimalInterface {
@Override
public void eat() {
System.out.println("老子是吃肉长大的!!!");
}
}
public class Test {
public static void main(String[] args) {
AnimalInterface animalInterface = new LionImplement();
animalInterface.eat();
}
}
-
说明:
- 接口可以被其他类实现,用interface来声明。
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
- 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现,用implement来实现。
-
抽象类与接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型即常量。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
- 接口只能继承接口,不可以实现接口
- 一个类可以使用implements关键字实现多个接口,一个接口可以使用extends关键字继承多个接口,一个类可以在继承一个父类的同时,实现一个或多个接口,但是extends关键字要在前
[修饰符] class 【类名】extends 【父类名】implements【接口列表】{
// code
}
9、引用类型
-
概念:
引用类型是通过class来定义的类型,除了八种数据类型之外的所有类型都是引用类型。引用类型继承于Object类(也是引用类型)都是按照Java里面存储对象的内存模型来进行数据存储的,使用Java内存堆和内存栈来进行这种类型的数据存储,简单地讲,“引用”是存储在有序的内存栈上的,而对象本身的值是存储在内存堆上的; -
引用类型和值类型的主要区别:
主要在于值类型是分配在栈上的,而引用类型是分配在堆上的(需要java中的栈、堆概念) -
传递
- 值传递:
基本数据类型的赋值都属于值传递,值传递传递的是实实在在的变量值,是传递原参数的备份,值传递后,实参传递给形参的值,形参发生改变不影响实参。 - 引用传递
引用传递传递的是地址,形参改变会改变实参变量的值
- 值传递:
-
类型转换
- 基本类型转换:
类型转换主要在赋值、方法调用、算术运算三种情况下发生。 - 赋值和方法调用 转换规则:
- 基本类型转换:
- 从低位类型到高位类型自动转换;从高位类型到低位类型需要强制类
型转换: - 布尔型和其它基本数据类型之间不能相互转换;
- byte型可以转换为short、int、long、float和double;
- short可转换为int、long、float和double;
- char可转换为int、long、float和double;
- int可转换为long、float和double;
- long可转换为float和double;
- float可转换为double
- 算术运算中的类型转换:
- 基本就是先转换为高位数据类型,再参加运算,结果也是最高位的数据类型;
- byte short char运算会转换为Int;
- 如操作数之一为double,则另一个操作数先被转化为double,再参与算术运算。
- 如两操作数均不为double,当操作数之一为float,则另一操作数先被转换为float,再参与运算。
- 如两操作数均不为double或float,当操作数之一为long,、则另一操作数先被转换为long,再参与算术运算。
- 如两操作数均不为double、float或long,则两操作数先被转换为int,再参与运算。
- 特殊:
a. 如采用+=、*=等缩略形式的运算符,系统会自动强制将运算结果转换为目标变量的类型。
b. 当运算符为自动递增运算符(++)或自动递减运算符(--)时,如果操作数为byte,short或char类型不发生改变;
-
引用类型转换:
基本类型与对应包装类可自动转换,这是自动装箱和拆箱的原理(后面将介绍)。
子类能直接转换为父类或接口类型: 子类就是父类,前几节有例子。
父类转换为子类要强制类型转换;且在运行时若实际不是对应的对象,会抛出ClassCastException运行时异常。 -
自动装箱与拆箱机制
-
定义:
自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。 -
自动装箱拆箱要点
-
- 自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
-
自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。
image.png
10、Java常用类
Object类
Object类是Java中所有类的父类,也就是所有类的祖宗,所有类都是继承自Object类,所以,在这个类里面,会包含一些所有对象共有的方法。
常用方法:
1) clone
- 定义
clone方法可以得到一个和原始对象A完全相同新对象B,并且此后对B 任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。 - 说明:
- 拷贝对象返回的是一个新对象,而不是一个引用。
- 拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。
- 用法
class CloneClass implements Cloneable {
public int aInt;
public Object clone() {
CloneClass o = null;
try {
o = (CloneClass) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
- 影子clone
package clone;
class UnCloneA {
private int i;
public UnCloneA(int ii) {
i = ii;
}
public void doublevalue() {
i *= 2;
}
public String toString() {
return Integer.toString(i);
}
}
class CloneB implements Cloneable {
public int aInt;
public UnCloneA unCA = new UnCloneA(111);
public Object clone() {
CloneB o = null;
try {
o = (CloneB) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
public class CloneMain {
public static void main(String[] a) {
CloneB b1 = new CloneB();
b1.aInt = 11;
System.out.println("before clone,b1.aInt = " + b1.aInt); //11
System.out.println("before clone,b1.unCA = " + b1.unCA); //111
CloneB b2 = (CloneB) b1.clone();
b2.aInt = 22;
b2.unCA.doublevalue();
System.out.println("=================================");
System.out.println("after clone,b1.aInt = " + b1.aInt); //11
System.out.println("after clone,b1.unCA = " + b1.unCA); //222
System.out.println("=================================");
System.out.println("after clone,b2.aInt = " + b2.aInt); //22
System.out.println("after clone,b2.unCA = " + b2.unCA); //222
}
}
在对b2中b2.aInt进行赋值后,原b1中的b1.aInt的值不会发生改变,而b2.unCA的值会改变,这是因为b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用,所以调用Object类中clone()方法对于基本数据类型来说可以得到新的内存空间而对于引用数据类型来说只是生成了一个新的引用,这种被称为"影子clone"
- 深度clone
package ss;
class UnCloneA implements Cloneable {
private int i;
public UnCloneA(int ii) {
i = ii;
}
public void doublevalue() {
i *= 2;
}
public String toString() {
return Integer.toString(i);
}
public Object clone() {
UnCloneA o = null;
try {
o = (UnCloneA) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
class CloneB implements Cloneable {
public int aInt;
public UnCloneA unCA = new UnCloneA(111);
public Object clone() {
CloneB o = null;
try {
o = (CloneB) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
o.unCA = (UnCloneA) unCA.clone();
return o;
}
}
public class CloneMain {
public static void main(String[] a) {
CloneB b1 = new CloneB();
b1.aInt = 11;
System.out.println("before clone,b1.aInt = " + b1.aInt); //11
System.out.println("before clone,b1.unCA = " + b1.unCA); //111
CloneB b2 = (CloneB) b1.clone();
b2.aInt = 22;
b2.unCA.doublevalue();
System.out.println("=================================");
System.out.println("after clone,b1.aInt = " + b1.aInt); //11
System.out.println("after clone,b1.unCA = " + b1.unCA); 111
System.out.println("=================================");
System.out.println("after clone,b2.aInt = " + b2.aInt); //22
System.out.println("after clone,b2.unCA = " + b2.unCA); //222
}
}
b1.unCA与b2.unCA指向了两个不同的UnCloneA实例
2) equals
用于判断两个数据是否相等。
-
equals 与 == 操作符的区别总结如下:
- 若 == 两侧都是基本数据类型,则判断的是左右两边操作数据的值是否相等
- 若 == 两侧都是引用数据类型,则判断的是左右两边操作数的内存地址是否相同。若此时返回 true , 则该操作符作用的一定是同一个对象。
- Object 基类的 equals 默认比较两个对象的内存地址,在构建的对象没有重写 equals 方法的时候,与 == 操作符比较的结果相同。
- equals 用于比较引用数据类型是否相等。在满足equals 判断规则的前体系,两个对象只要规定的属性相同我们就认为两个对象是相同的。
public class Test {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
String d = new String("abc");
System.out.println(a==b);//true
System.out.println(c==d);//false
System.out.println(a.equals(b));//true
System.out.println(c.equals(d));//true
System.out.println(b.equals(d));//true
System.out.println(a.hashCode()); //96354
System.out.println(b.hashCode()); //96354
System.out.println(c.hashCode()); //96354
System.out.println(d.hashCode()); //96354
}
}
3) hashCode
- 作用
为了配合基于散列的集合会根据对象的hashCode返回值来初步确定对象在容器中的位置,然后内部再根据一定的 hash 算法来实现元素的存取。这样的散列集合包括HashSet、HashMap以及HashTable。
不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。
4) getClass
在Java中获得类名的方法,getClass() 是一个类的实例所具备的方法,而class() 方法是一个类的方法。getClass() 是在运行时才确定的,而class() 方法是在编译时就确定了。
class A{
public void func(){
}
}
class B extends A{
}
public class Test {
public static void main(String[] args) {
A a = new A();
B b = new B();
A ab = new B();
System.out.println(a.getClass()+" "+A.class); //class A class A
System.out.println(b.getClass()+" "+B.class); //class B class B
System.out.println(ab.getClass()); //class B
ab = a;
System.out.println(ab.getClass()); //class B
}
}
5) toString
该方法返回的是该Java对象的内存地址经过哈希算法得出的int类型的值在转换成十六进制。这个输出的结果可以等同的看作Java对象在堆中的内存地址。
public class Test {
public static void main(String[] args) {
Object o1 = new Object();
System.out.println(o1.toString()); //java.lang.Object@16d3586
}
}