Java学习笔记之面向对象编程
主要学习地址:
1、 廖雪峰的官方网站
2、 Java程序员进阶之路
补充:Java中的命名规则
包名用全小写,类名用首字母大写驼峰,变量名和方法名用首字母小写驼峰
一、面向对象的三大特性
封装:
封装可以使我们更容易修改类的内部实现,而不必修改实现该类的代码。
类比于平台升级和维护,开发人员只需要修复好平台的bug即可,而不必去修复每个用户机器的bug,有点“一键修复”的意味。提高了代码开发效率。
封装还可以利用getter和setter对成员变量进行更加精确的控制。
继承
合理使用继承可以大大减少重复代码,提高代码复用性。
实现继承的两个关键词:extends和implements
关于继承,需要掌握的几个知识点:
1、this和super关键字
便于我们在构造方法中调用子父类的属性和方法
this关键字的作用:
- 调用当前类的方法;
- this() 可以调用当前类的构造方法;
- this 可以作为参数在方法中传递;
- this 可以作为参数在构造方法中传递;
- this 可以作为方法的返回值,返回当前类的对象
示例代码:
package demo;
public class ThisTest {
String name;
ThisTest(){
System.out.println("我是无参构造函数");
}
ThisTest(String name){
//1.this()调用当前类的构造方法,注意:this()必须放在构造方法的第一行,否则编译报错
this();
//2.this指向当前对象,调用当前类的属性
this.name = name;
System.out.println("我是有参构造函数");
}
public void method1(){
System.out.println("我是方法1");
}
public void method2(){
//3.this调用当前类的方法
this.method1();
System.out.println("我是方法2");
}
public void method3(ThisTest thisTest){
System.out.println(thisTest);
}
public void method4(){
//4.this作为参数在方法中传递
method3(this);
}
public static void main(String[] args) {
//演示this的前两种作用
ThisTest t = new ThisTest("snoopy");
//演示this的第三种作用
t.method2();
//演示this的第四种作用
t.method4();
System.out.println(t);
}
}
输出结果:
super关键字的用法:
- 指向父类对象;
- 调用父类的方法;
- super() 可以调用父类的构造方法。
2、构造方法
当我们创建完对象时,少不了子父类构造方法的执行,所以需要了解子父类有参/无参构造方法的执行规则
父类的构造方法不能被继承,子类的构造过程必须调用父类的构造方法。一个基本的继承案例如下:
class A{
public String name;
public A() {//无参构造
}
public A (String name){//有参构造
}
}
class B extends A{
public B() {//无参构造
super();
}
public B(String name) {//有参构造
//super();
super(name);
}
}
如果子类的构造方法中没有显示地调用父类构造方法,则系统默认调用父类无参数的构造方法。
示例代码:
package demo;
public class Test {
public static void main(String[] args) {
String name="snoopy";
B b1 = new B();
B b2 = new B(name);
}
}
class A{
public String name;
A(){//无参构造
System.out.println("我是父类无参构造");
}
A(String name){//有参构造
System.out.println("我是父类有参构造");
}
}
class B extends A{
B(){//无参构造
System.out.println("我是子类无参构造");
}
B(String name){//有参构造
//super(name);如果不加这一行的话,系统会默认调用父类无参构造函数,即第三行的输出结果为“我是父类无参构造”
System.out.println("我是子类有参构造");
}
}
输出结果:
3、方法重写Override
方法重写只能存在子类和父类中,且重写的方法和父类的要一致(包括返回值类型、方法名、参数列表)
4、方法重载Overload
方法重载只能存在同一类中,被重载的方法必须改变参数列表(参数个数或类型或顺序不一样)
一个简单的重载例子:
class A{
public int add(int a,int b){
return a+b;
}
public double add(double a,double b) {
return a+b;
}
public int add(int a,int b,int c) {
return a+b+c;
}
}
注意:当两个方法的名字相同,参数类型和个数相同,仅方法返回值不同时,不算方法重载,此时编译器会报错。
多态
参考文章: 廖雪峰的官方网站之Java多态
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定。
比如说下面这种例子:
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 应该打印Person.run还是Student.run?
}
}
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
运行结果:
通过上面的运行结果就可以知道,实际上调用的方法是Student的run()方法。因此可得出结论:
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。这个非常重要的特性在面向对象编程中称之为多态。
多态的三个前提条件:
- 子类继承父类
- 子类覆盖父类的方法
- 父类引用指向子类对象
如下面这段代码:
package demo;
public class Main {
public static void main(String[] args) {
// 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}
public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
}
class Income {
protected double income;
public Income(double income) {
this.income = income;
}
public double getTax() {
return income * 0.1; // 税率10%
}
}
class Salary extends Income {
public Salary(double income) {
super(income);
}
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}
class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}
@Override
public double getTax() {
return 0;
}
}
当我们使用totalTax()方法计算总税收时,只需要调用Income类的对象的getTex方法即可,甚至不需要知道其他子类的存在。就是多态的这种特性,允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
二、Java中的类和对象
Java类可以包含字段、方法、构造方法。而创建Java对象时,需要用到new关键字。所有对象在创建的时候都会在堆内存中分配空间。
在同一个Java源文件中,只能有一个public class,只能在这个public class中写一个public static void mian作为主函数,当然也可以不写,如果没必要的话。
如果需要创建对象,则需要一个main方法作为入口,main方法可以在当前类,也可以在别的类中。
创建完对象后,少不了的一步是初始化,初始化的方法有以下三种
- 通过对象的引用变量
- 通过构造函数
- 通过新增方法
三、Java变量
Java变量分为四种:局部变量、成员变量(实例变量)、静态变量、常量
局部变量:
在方法体内声明的变量被称为局部变量,该变量只能在该方法内使用,类中的其他方法并不知道该变量。
注意事项:
- 局部变量声明在方法、构造方法或者语句块中。
- 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,将会被销毁。
- 访问修饰符不能用于局部变量。
- 局部变量只在声明它的方法、构造方法或者语句块中可见。
- 局部变量是在栈上分配的。
- 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。
成员变量:
在类内部但在方法体外声明的变量称为成员变量,或者实例变量。之所以称为实例变量,是因为该变量只能通过类的实例(对象)来访问。
注意事项:
- 成员变量声明在一个类中,但在方法、构造方法和语句块之外。
- 当一个对象被实例化之后,每个成员变量的值就跟着确定。
- 成员变量在对象创建的时候创建,在对象被销毁的时候销毁。
- 成员变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息。
- 成员变量可以声明在使用前或者使用后。
- 访问修饰符可以修饰成员变量。
- 成员变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把成员变量设为私有。通过使用访问修饰符可以使成员变量对子类可见;成员变量具有默认值。数值型变量的默认值是 0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定。
静态变量:
通过 static 关键字声明的变量被称为静态变量(类变量),它可以直接被类访问
注意事项:
- 静态变量在类中以 static 关键字声明,但必须在方法构造方法和语句块之外。
- 无论一个类创建了多少个对象,类只拥有静态变量的一份拷贝。
- 静态变量除了被声明为常量外很少使用。
- 静态变量储存在静态存储区。
- 静态变量在程序开始时创建,在程序结束时销毁。
- 与成员变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
- 静态变量的默认值和实例变量相似。
- 静态变量还可以在静态语句块中初始化。
四、 Java方法
方法声明:
实例方法:
没有使用 static 关键字修饰,但在类中声明的方法
实例方法有两种特殊类型:getter 方法setter 方法
getter 方法用来获取私有变量(private 修饰的字段)的值,setter 方法用来设置私有变量的值。
静态方法:
有 static 关键字修饰的方法
静态方法直接利用类名.方法名即可调用
抽象方法
没有方法体的方法被称为抽象方法,它总是在抽象类中声明。
这意味着如果类有抽象方法的话,这个类就必须是抽象的。可以使用 atstract 关键字创建抽象方法和抽象类。
五、Java构造方法
只用public修饰,或者不用任何单词修饰
默认构造方法的目的:为对象的字段提供默认值
有参构造方法的目的:同上
重载构造方法,可以写很多个构造方法,但每个构造方法的参数列表不同,编译器会利用参数的个数来决定调用哪个构造方法
六、Java访问权限修饰符
在Java中,共有4种访问权限控制,为默认访问权限(包访问权限),public,private,protected
其修饰对象分为两种:类,类的方法和变量。
如果修饰的是类,则访问权限只有默认访问权限defult和public访问权限。
如果修饰的是类中的方法和变量,则访问权限有默认访问权限(包访问权限),public,private,protected
假设有两个类,名为:Main类和Person类
在同一包下,不管类名前是否加public修饰,这两个类都可以相互引用,比如说,可以在Main类中new一个Person类的对象,也可以在Main类中调用Person类的方法(private修饰的不行)
在不同包下,如果想在Main类中new一个Preson类对象,Person类必须被public修饰
如果一个类的方法或者变量被 public 修饰
这些类和变量无论在什么地方都是可见的。(类比全人类都可见)
如果一个类的方法或者变量被 protected 修饰
对于同一个包的类,这个类的方法或变量是可以被访问的。对于不同包的类,只有继承于该类的类才可以访问到该类的方法或者变量。(类比只有自己的家人和亲戚,邻居可见)
如果一个类的方法或者变量被 private 修饰
那么这个类的方法或者变量只能在该类本身中被访问,在类外以及其他类中都不能显式的进行访问。(类比只有自己的家人可见)
七、Java中的代码初始化块
目的:对成员变量进行更加复杂的赋值
执行顺序:
代码初始化块的三个规则:
- 类实例化的时候执行代码初始化块;
- 实际上,代码初始化块是放在构造方法中执行的,只不过比较靠前;
- 代码初始化块里的执行顺序是从前到后的。
八、Java抽象类
参考文章: 廖雪峰的官网网站–抽象类
由于多态的存在,子类可以重写父类的方法。假设有一个普通类Person类作为父类,在其中定义一个普通方法run()。即使这个方法无任何意义,也需要加上方法的执行语句。否则会出现编译报错。
当我们希望父类的方法仅仅用于定义方法签名,让子类去覆写它,我们就可以使用抽象类。
继承抽象类的其他类,需要重写抽象类的抽象方法,否则编译报错。
我们不仅可以利用子类扩展实现抽象类的抽象方法,还可以对抽象类的普通方法实现子类复用。即抽象类的子类可以继承父类的普通方法。看下面这段代码:
package demo;
public class AbstractTest {
public static void main(String[] args) {
Pig p = new Pig();
p.run();
p.name();
}
}
abstract class Animal{
public abstract void run();
public void name(){
System.out.println("我是动物");
}
}
class Pig extends Animal{
@Override
public void run() {
System.out.println("我是一只猪,我会飞");
}
}
执行结果:
当我们定义了抽象类及其具体子类时,我们可以不用关心抽象类引用变量的子类型,直接利用引用变量调用抽象类的抽象方法即可,
如下面这段代码所示:
package demo;
public class AbstractTest {
public static void main(String[] args) {
Animal pig = new Pig();
Animal dog = new Dog();
pig.run();
dog.run();
}
}
abstract class Animal{
public abstract void run();
}
class Pig extends Animal{
@Override
public void run() {
System.out.println("我是一只猪,我会飞");
}
}
class Dog extends Animal{
@Override
public void run() {
System.out.println("我是一只狗,我会跑");
}
}
这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
九、Java接口
参考文章: 廖雪峰的官方网站–接口
接口比抽象类还抽象。接口中定义的方法全为抽象方法或者defult方法。对于实现类来说,实现类必须重写接口中的全部抽象方法,而不必重写接口中的defult的方法。
在Java8之后,接口中可以有字段,只不过这些成员变量的类型只能是public static final
default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
接口与抽象类的对比:
package demo;
public class InterfaceTest {
public static void main(String[] args) {
Person s = new Student("小红");
s.study();
}
}
interface Person{
//接口中的方法如果不写defult,则默认为abstract方法
String getName();
default void study(){
System.out.println(getName()+" is studying");
};
}
class Student implements Person{
private String name;
public Student(String name){
this.name = name;
}
@Override
public String getName() {
return this.name;
}
}
另外,接口可以继承接口,这相当于接口方法的扩展
十、一些重要的Java知识点
this和super的用法总结
this的六个作用:
1、指向当前对象
2、调用当前类的方法
3、调用当前类的构造方法
4、作为参数在方法中传递
5、作为参数在构造方法中传递
6、作为方法的返回值
代码如下:
public class ThisStudentTest {
String name;
int age;
ThisStudentTest(String name, int age) {
//1.this指向当前对象,调用类的字段
this.name = name;
this.age = age;
}
void out() {
//2.this调用当前类的方法
this.print();
System.out.println(name+" " + age);
}
void print(){
System.out.println("hello");
}
public static void main(String[] args) {
WithThisStudent s1 = new WithThisStudent("沉默王二", 18);
WithThisStudent s2 = new WithThisStudent("沉默王三", 16);
s1.out();
s2.out();
}
}
super关键字的三个主要作用:
1、指向父类对象
2、调用父类方法
3、super()可调用父类的构造方法
static关键字的总结
静态变量、静态方法、静态代码块、静态内部类
静态变量:
将类的字段声明为static,可以减少创建对象时造成的内存开销。引用变量放在栈内存中,对象放在堆内存中,静态变量放在静态区。
代码如下:
public class Student {
String name;
int age;
static String school = "郑州大学";
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Student s1 = new Student("沉默王二", 18);
Student s2 = new Student("沉默王三", 16);
}
静态方法:
不得在静态方法中访问非静态变量(因为生命周期不同)
直接通过类名调用静态方法,静态方法是属于类的,而不是属于对象的
静态代码块:
用static修饰的代码块为静态代码块。静态代码块先于mian()方法执行,Java7之后,如果类中没有main方法,则也无法执行static代码块。
在实际的项目开发中,通常使用静态代码块来加载配置文件到内存当中。
public class StaticBlockDemo {
public static List<String> writes = new ArrayList<>();
static {
writes.add("1");
writes.add("2");
System.out.println("第一块");
}
static {
writes.add("3");
writes.add("4");
System.out.println("第二块");
}
}
静态内部类:
静态内部类不能访问外部类的成员变量,但可以访问外部类的静态变量,外部类不得声明为static。
注意:构造方法不得被static修饰,因为构造器总是关联一个对象而被调用,所以把它声明为static是没有意义的。
final关键字总结
final变量:
final变量一旦初始化,就无法更改。
final修饰的成员变量必须有一个默认值,否则编译器认为它没有初始化。
final和static同时修饰的变量为常量,
如果用final修饰方法参数,则该参数在方法体内不得再修改。
final方法
final方法可以被继承,但不得被重写,当我们在设计一个类的时候,如果觉得这个类的方法不能被重写,可将其声明为final类型的方法。
final类
如果一个类被final修饰,则它无法被继承。如果一个类中的所有方法都被final修饰,则该类可以被继承,并且可以追加非final方法。String类就是一个final类。
String类被设计成final类的原因有以下三种:
1、为了实现字符串常量池
2、为了线程安全
3、为了HashCode不可变性