java面向对象基础笔记
Java面向对象编程
面向对象基础
什么是oop?
面向对象编程是一种对现实世界建立计算机模型的一种编程方法。简称OOP。OOP:Object Oriented Programming
对象的概念
在现实世界中,当我们提到动物这个概念,实际上它是一个抽象的概念。而具体动物是指老虎,狮子,大象等等。在对应的计算机模型中,我们把动物这种抽象的概念称之为class,也就是类。而那些具体的对象称之为实例,并且用不同变量标识不同的实例。
类和实例的关系
class是对象的模板。
- class定义了如何创建实例。
- class名字就是数据类型。
Instance是对象实例。
- Instance是根据class创建的实例。
- 可以创建多个instance
- 各个instance类型相同,但各自属性各自不同
定义class
一个class可以包含多个field(字段)
field用来描述一个class的特征
class实现了数据封装
创建实例
new操作符可以创建一个实例
定义一个引用类型变量来指向实例
通过变量来操作实例
通过变量.字段来访问实例字段
例子
package com.day.one; public class Main { public static void main(String[] args) { Person ps = new Person(); ps.name = "小明"; ps.age = 12; System.out.println(ps.name); System.out.println(ps.age); } }
数据封装
一个class可以包含多个filed
直接把filed用public暴露个外部可能破环了封装
用private修饰field可以拒绝外部访问
定义public方法可以间接修改filed
方法
public方法封装了数据访问
通过方法访问了实例字段更安全
通过变量.方法名()来调用实例方法
方法的定义
修饰符
方法返回值
方法名字
方法参数列表
方法的返回值通过return语句实现
没有返回值可以剩return
方法可以使用隐式变量this
this指向当前实例
this.filed 可以访问当前实例的字段
在不引起歧义情况下,可以剩略this
局部变量名优先
调用方法
实例变量.方法名(参数)
可以忽略方法返回值
方法参数
用于接收传递给方法的变量值
分为 基本类型参数 引用类型参数
Private方法
外部代码不可访问private方法
内部代码可以调用自己的private方法
例子
package com.day.one; public class Main { public static void main(String[] args) { Person ps = new Person(); ps.setName("小明"); ps.setAge(12); System.out.println(ps.getName()); System.out.println(ps.getAge()); System.out.println(ps.getBrith()); } }
package com.day.one; public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name.trim(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getBrith(){ return this.calcBrith(2018); } private int calcBrith(int year) { return year - this.age; } }
构造方法
构造方法可以在创建对象实例时初始化对象实例
构造方法名就是类名
构造方法的参数没有限制
构造方法没有返回值(也没有void)
必须使用new操作符调用构造方法
默认构造方法
如果一个类没有定义构造方法,编译器会自动生成一个默认的构造方法:无参数,无执行语句
如果自定义了构造方法,编译器就不再自动创建默认的构造方法
初始化顺序
- 先初始化字段
- 没有赋值的字段初始化为默认值:基本类型=0,引用类型=null,Boolean = false
- 再执行构造方法的代码
可以定义多个构造方法,编译器通过构造方法的参数数量,位置和类型区分
一个构造方法可以调用其他的构造方法,便于代码复用,使用this()调用
例子
package com.day.one; public class Main { public static void main(String[] args) { Person ps = new Person(); Person ps1 = new Person("liming", 11); System.out.println(ps.getName()); System.out.println(ps.getAge()); System.out.println(ps1.getName()); } }
package com.day.one; public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public Person() { this("libai", 14); } public String getName() { return name; } public void setName(String name) { this.name = name.trim(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getBrith() { return this.calcBrith(2018); } private int calcBrith(int year) { return year - this.age; } }
方法重载
方法重载是指:
多个方法的方法名相同
但各自的参数不同:
- 参数个数不同
- 参数类型不同
- 参数位置不同
方法返回值类型通常是相同
方法重载的目地:
l 相同功能的方法使用同一名字
l 便于调用
例子
package com.day.one; public class Main { public static void main(String[] args) { Person ps = new Person(); ps.setName("一个参数"); System.out.println(ps.getName()); ps.setName("第二", "参数"); System.out.println(ps.getName()); } }
package com.day.one; public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public Person() { this("libai", 14); } public String getName() { return name; } public void setName(String name) { this.name = name.trim(); } public void setName(String firstName, String lastName) { this.name = firstName + " | " + lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getBrith() { return this.calcBrith(2018); } private int calcBrith(int year) { return year - this.age; } }
继承和多态
继承
继承是代码复用的一种方式。
超类(super),父类,基类
子类(subclass),扩展类
继承树
Java默认所有类继承Object
Java只允许一个类继承自一个类
一个类有且仅有一个父类(Object除外)
Protected
父类定义的私有字段不允许被子类访问
父类定义的所保护字段允许被子类访
Protected把字段和方法的访问权限控制继承树内部
super
supser关键字表示父类(超类)
构造方法的第一行语句必须调用super()
没有super()时编译器会自动生成super()
如果父类没有定义默认的构造方法,有其他带参数的构造方法,编译器替子类生成的super()就调用不了,所以子类就必须显示调用super(),传入需要的参数。
向上转型
当我们定义一个类的时候,实际上就是定义一种数据类型。当我们定义了继承的时候,实际上就是在这些数据类型之间加上继承的关系,当我们创建一个实例对象的时候,我们需要把它赋值一个引用类型变量,让这个变量指向这个实例对象。但我们遇到一个问题:
可以对实例变量进行向上转型(upcasting)
向上转型把一个子类型安全地变为更加抽象的类型
向下转型
可以对实例变量进行向下转型(downcasting)
向下转型把抽象的类型变成一个具体的子类型
向下转型很可能会报错:ClassCastException
Instanceof操作符可以判断对象的类型
区分继承和组合
Student 不宜从Book继承
Student可以持有一个Book实例
继承是is关系
组合是has关系
class Person() {}
class Book(){}
public class Student extends Person(
private Book book;
){}
例子
package com.day.one; public class Main { public static void main(String[] args) { Person p = new Person("原始人"); Student s = new Student("李白"); s.readBood(); p.readBood(); s.foot = "大脚"; System.out.println(s.foot); // upcasting Person ps = new Student("dufu"); Object o = p; // downcasting // 向下转型前可以用instanceof判断 if(p instanceof Student){ Student s1 = (Student)p; } System.out.println(p instanceof Person); System.out.println(p instanceof Student); } }
package com.day.one; public class Student extends Person { public Student(String name) { super(name); // TODO Auto-generated constructor stub } private int scroe; public int getScroe() { return scroe; } public void setScroe(int scroe) { this.scroe = scroe; } }
package com.day.one; public class Person { private String name; private int age; protected String foot; public Person(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name.trim(); } public void setName(String firstName, String lastName) { this.name = firstName + " | " + lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void readBood() { System.out.println(this.getName()+ "读书"); } }
多态
Overide(覆写)
子类覆写父类的方法是覆写(overide)
方法签名如果不同就不是overide,而是创建一个新方法
(方法签名由方法名称和一个参数列表(方法的参数的顺序和类型)组成。
注意,方法签名不包括方法的返回类型。不包括返回值和访问修饰符。)
可以使用注解@Overide让编译器检测是否正确的覆写
引用变量的声明类型可能与实际类型不符。
实例对象的方法调用总是对应实际类型。
Java的实例方法调用是基于运行时实际类型的动态调用。
多态
public void runTwice(Person p){
p.run();
p.run();
}
从上面的代码,无法知道是否调用Person类的定义的 run()方法,还是他的父类还是他的子类。
多态是指 针对 某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
对某个类型调用某个方法,执行的方法可能是某个子类的覆写方法。
利用多态,允许添加更多类型的子类实现功能扩展。
Object定义的几个重要方法:
toString: 把instance输出为String
equals:判断连个instance是否逻辑相等
hasCode:计算一个instance的哈希值
final
用final修饰的方法不能被override
用final修饰的类不能被继承
用final修饰的字段在初始化不能被修改
例子
package com.day.one; public class Main { public static void main(String[] args) { Person p = new Person("原始人"); Student s = new Student("李白"); // Java的实例方法调用是基于运行时实际类型的动态调用。 System.out.println(s.readBook()); System.out.println(p.readBook()); // 实际调用Object类的toString 打印出Person在jvm的引用地址 System.out.println(p); } }
package com.day.one; public class Student extends Person { public Student(String name) { super(name); // TODO Auto-generated constructor stub } private int scroe; public int getScroe() { return scroe; } public void setScroe(int scroe) { this.scroe = scroe; } @Override public String readBook() { // super可以调用父类的被 重写的方法 return super.readBook() + "!!!"; } }
package com.day.one; public class Person { private String name; private int age; protected String foot; public Person(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name.trim(); } public void setName(String firstName, String lastName) { this.name = firstName + " | " + lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String readBook() { return this.name + "读书"; } }
抽象类
覆写
每个子类都可以覆写父类的方法。如果父类的方法没有实际意义,能否去掉方法的执行语句?
不能,如果去掉父类的方法,就会失去多态的特性,可以把父类声明抽象类和父类方法声明抽象方法。
抽象方法
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法:
抽象方法用abstract修饰
抽象方法没有任何执行语句
因为无法执行抽象方法,因为这个类也必须申明为抽象类。
无法实例化一个抽象类
抽象类
抽象类用于被继承
抽象类可以强迫子类实现其定义的抽象方法(否则编译错误)
抽象方法实际上相当于定义了“规范”
如果不实现抽象方法,则该子类仍是抽象类。
面向抽象编程的本质:
上层代码只定义规范
不需要子类就可以实现业务逻辑
具体的业务逻辑由不同子类实现,调用者并不关心
例子
package com.day.two; public class Cirle extends Shape { private final double radius; public Cirle(double radius) { this.radius = radius; } @Override public double area() { // TODO Auto-generated method stub return Math.PI * radius * radius; } }
package com.day.two; public class Rect extends Shape { private final double width; private final double height; public Rect(double width, double height) { this.width = width; this.height = height; } @Override public double area() { // TODO Auto-generated method stub return this.height * this.width; } }
package com.day.two; public abstract class Shape { public abstract double area(); }
package com.day.two; public class Main { public static void main(String[] args) { // TODO Auto-generated method stub Rect rect = new Rect(12,12); Cirle cirle = new Cirle(5); System.out.println(rect.area()); System.out.println(cirle.area()); } }
接口
如果一个抽象类没有字段,所有的方法都是抽象方法,就可以把抽象类改写为接口。
使用interface声明一个接口。
接口默认定义方法是public abstract
Interface是java内置的纯对象接口。
实现interface使用implements
可以实现多个interface
注意区分术语
Java的接口特指interface定义的接口,只定义方法签名
编程的接口泛指接口规范,如方法签名,数据格式,网络协议等
抽象类和接口的区别
|
abstract class |
interface |
继承 |
单继承 |
多接口 |
字段 |
可以定义实例字段 |
不可以定义实例字段 |
抽象方法 |
Yes |
Yes |
非抽象方法 |
可以定义非抽象方法 |
可以定义default方法 |
接口继承
一个接口可以继承另一个接口,使用extends, 相当于扩展的接口的方法
继承
合理设计interface和abstract class的继承关系
公共逻辑放在abstract class
接口层次代表抽象程度。
例子
package com.day.two; public class Main { public static void main(String[] args) { // TODO Auto-generated method stub Rect rect = new Rect(12,12); Cirle cirle = new Cirle(5); System.out.println(rect.area()); System.out.println(cirle.area()); } }
package com.day.two; public class Cirle implements Shape { private final double radius; public Cirle(double radius) { this.radius = radius; } @Override public double area() { // TODO Auto-generated method stub return Math.PI * radius * radius; } @Override public String hehe() { // TODO Auto-generated method stub return null; } }
package com.day.two; public class Rect implements Shape { private final double width; private final double height; public Rect(double width, double height) { this.width = width; this.height = height; } @Override public double area() { // TODO Auto-generated method stub return this.height * this.width; } public String hehe() { return "rect"; } }
package com.day.two; public interface Shape extends Shape2 { double area(); default double zhouchang() { return 0; } }
package com.day.two; public interface Shape2 { String hehe(); }
包和classpath
静态字段和方法
静态字段
用static修饰的字段称为静态字段
普通字段在每个实例中都有自己一个独立空间
静态字段只有一个共享的空间,所有的实例都共享该字段
不推荐用实例变量访问静态字段
推荐用类名访问静态字段(因为在java程序中,实例对象并没有静态字段,代码中实例对象能访问静态字段,是因为编译器根据实例自动转换类名来访问静态字段)
可以把静态字段理解为描述class本身的字段(非实例字段)
静态方法
用static修饰的方法称为静态方法
调用实例方法必须通过实例变量
调用静态方法不需要实例变量
静态方法类似其他编程语言的函数
静态方法不能访问this变量
静态方法不能访问实例字段
静态方法可以访问静态字段和静态方法
静态方法通常用于工具类
Array.sort()
Math.random
静态方法经常用于辅助方法
Java入口程序main()也是静态方法
例子
package com.day.there; public class Main { public static void main(String[] args) { Person p1 = new Person("li"); System.out.println(Person.getNumber()); Person p2 = new Person("li"); System.out.println(Person.getNumber()); Person p3 = new Person("li"); System.out.println(Person.getNumber()); } }
package com.day.there; public class Person { private String name; private static int num; public Person(String name) { this.name = name; Person.num++; } public static int getNumber(){ return Person.num; } }
包
Package
Java定义名字空间:包
包名+类名 = 完整类名
Jvm只看完整类名,只要包名不同,类就不同
包可以是多层结构
包没有父子关系(java.util和java.util.zip是不同的包)
包作用域
位于同一个包的类,可以访问包作用域的字段和方法:
不用public,private,protected修饰的字段和方法就是包作用域
引用其他类
先使用完整类名
先import,再使用类名
可以使用*(不推荐)
作用域
访问权限
Java的类,字段,方法,接口都可以设置访问权限
访问权限是指在一个类的内部,能否引用另一个类以及访问它的字段和方法
访问权限有public,private,protected,package
Public
定义为public的class,interface,field,method可以被其他类访问
Private
定义为private的field,method不可以被其他类访问
Private访问权限限定在class的内部,与方法生命顺序无关
定义为private的class无法被其他类访问
访问private class 被限定在外层class的内部
定义在一个class内部的class称为内部类
Protected
Protected作用于继承关系
定义protected的字段和方法可以被子类访问
Package
包作用域是指一个类允许访问同一个package的
没有public,private修饰的class
没有public,protected,private修饰的字段和方法
包名必须完全一致
局部变量
在方法内部定义的变量称为局部变量
局部变量作用域从变量声明出开始到对应的块结束
尽可能把局部变量的作用域缩小
尽可能延后生命局部变量
Final
Final与访问权限不冲突
用final修饰class可以阻止被继承
用final修饰method可以阻止被覆写
用final修饰filed可以被重新赋值
用final修饰局部变量可以阻止被重新赋值
classpath和jar
classpath
classpath是一个环境变量
classpath指示jvm如何搜索class
classpath设置的搜索路径与操作系统相关:window下是一分号分隔,linux是以冒号分隔
classpath设置方法:
在系统环境中设置(不推荐,在系统设置的classpath会干扰到特定程序)
启动JVM时用-classpath或-cp设置(推荐)
Eclipse自动传入当前工程bin目录作为classpath
Jar包
jar包是zip格式的压缩文件,包含若干class文件
jar包相当于目录
使用jar包来避免大量目录和class文件
创建jar包:
l JDK的jar命令
l Maven等工具
l 压缩为zip然后改名为jar
Jar包其他功能
jar包可以有一个特殊的/META-INF/MANIFEST.MF文件来指定Main-Class
MANIFEST.MF是纯文本,可以指定Main-class和其他信息
Jar包还可以包含其他jar包
JDK的class
Jvm运行时会自动加载jdk自带的class
Jdk自带的class被打包在rt.jar包
不需要在classpath中引用rt.jar包,jvm会自动加载
Java核心类
字符串和编码
String的特点
可以直接使用”…”
内容不可变
比较String内容是否相等: equals(Object)
比较String 不区分大小写: equalsgnoreCase(String)
是否包含子串:boolean contains(CharSequence)
返回子串的索引位置 int indexOf(String)
向后查找: int lastIndexOf(String)
查找字符串有某个位置开始:boolean startsWith(String)
查找字符串有莫个位置结束: Boolean endsWith(String)
trim()
移除首尾空白字符
空格, \t \r \n
注意:trim()不改变字符串内容,而是返回新的字符串
提取子串,没有改变原字符串,返回新串 substring()
大小写切换:toUpperCase() toLowerCase()
替换一个字符或一串字符: replace
使用正则替换:replaceAll(String, String)
分割字符串:String[] split(String)
拼接字符串:static String.join()
任意数据转换string
static String.valueOf(int|Object|boolean)
把string转换其他类型
static int Integer.parseInt(String)
static Integer Integer.valueOf(String)
String转换为char[] char[] toCharArray()
Char[]转换为String new String(char[])
String转换为byte[] byte[] getBytes(“”) // 默认使用系统编码,window默认编码gbk,linux默认的utf8 不推荐使用
Byte[]转换String
New String(byte[], String)
New String(byte[], Charset)
Java使用Unicode编码
输入输出时把String和byte[]转换,需要考虑编码
始终优先考虑utf-8编码
StringBuilder
String可以使用+拼接
每次循环都会创建新的字符串对象
绝大部分都是临时对象,浪费内存
影响GC效率
StringBuilder可以高效拼接字符串
l StringBuilder是可变对象
l StringBuilder可以预分配缓冲区
StringBuilder可以进行链式操作
当只有一行代码:不需要特别改写字符串+操作
编译器在内部自动把多个连续的+操作优化为StringBuilde操作
StringBuffer是StringBuilder的线程安全版本,很少使用
包装类型
Jdk为每种基本类型都创建了对应的包装类型
基本类型 |
对应的应用类型 |
boolean |
Boolean |
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
character |
int,Integer和String的相互转换
int i = 100;
Integer n1 = new Integer(i);
Integer n2 = Integer.valueOf(i);
Integer n3 = Integer.valueOf(“100”);
int x1 = n1.intValue();
int x2 = Integer.parseInt(“100”);
特别注意Integer.getInteger(String)是从系统环境中读取系统变量
自动装箱和自动拆箱
发生在编译阶段
装箱和拆箱会影响执行效率
编译后的class代码是严格区分基本类型和引用类型
Number
java包装类型全部继承Number 这个类
可以利用向上转型变成Number对象,然后通过Number通过方法转换为基本类型
JavaBean
l 若个字段private实例字段
l 通过public方法读写实例字段
符合命名规范的class被称为JavaBean
通常把一组对应的getter和setter称为属性
只有getter的属性成为只读属性
只有setter的属性成为只写属性
属性只需要定义getter/setter方法,不一定需要对应的字段
使用Introspector.getBeanInfo()获取属性列表
作用
方便ide工具读取属性
传递数据
枚举属性
枚举类
用enum定义常量:
关键字enum定义常量类型
常量本身带有类型信息
使用==比较
enum定义的类型实际上是class
继承自java.lang.Enum
不能通过new创建实例
所有常量都是唯一的实例(引用类型)
可以用switch语句