java面向对象编程(高级)
static 关键字
如果想让一个成员变量或方法被类的所有实例所共享,就用 static 修饰即可,称为类变量、类方法或类属性、静态变量
使用范围:属性
、方法
、代码块
、内部类
被修饰后具备的特点:
- 随着类的加载而加载
- 优先于对象存在
- 被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
static 成员变量和方法
静态变量
使用 static 修饰的成员变量就是静态变量(或类变量、类属性)
public class Goods{
// 实例变量
String name;
double price;
// 类变量,静态变量
static String brand = "优乐美";
public Goods() {}
public Goods(String name) {
this.name = name;
}
}
public calss GoodsTest{
public static void main(String[] args){
Goods goods1 = new Goods("xm");
Goods goods2 = new Goods("xn");
// 都输出 “优乐美”
System.out.println(goods1.brand);
System.out.println(goods2.brand);
System.out.println(Goods.brand); // 直接类调用
// 改变了,都输出 “哇哈哈”,因为变量的值是共享的
goods1.brand = "哇哈哈";
System.out.println(goods1.brand);
System.out.println(goods2.brand);
System.out.println(Goods.brand); // 直接类调用
}
}
静态变量的特点:
- 默认值规则与实例变量一样
- 静态变量值所有对象共享
- 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用
- 如果权限修饰符允许,在其他类中可以通过
类名.静态变量
直接访问,也可以通过对象.静态变量
的方式访问 - 静态变量的 get/set 方法也静态的,当局部变量与静态变量重名时,使用
类名.静态变量
进行区分
静态方法
用 static 修饰的成员方法就是静态方法
public class Father {
static String name = "Father";
public static void method(){
// 方法内部只能访问类的 static 修饰的属性或方法
System.out.println( name + "method");
}
}
public class Son extends Father {
// @Override
// 尝试重写静态方法,加上@Override 编译报错,
// 去掉 Override不报错,但是也不是重写
public static void method() {
System.out.println("son");
}
}
public class staticTest {
public static void main(String[] args) {
// 直接类调用
Father.method(); // Father method
Son.method(); // 子类的static方法 son
// 执行 Father 类中的 method
Father f = new Son();
f.method(); // Father method
}
}
单例模式
对某个类只能存在一个实例对象,而且只提供一个取得该类对象实例的方法
实现思路:
- 将类的构造器访问权限设为
private
,外部就不能 new 产生对象了 - 设一个静态方法,返回类的内部创建的实例
- 成员变量和内部创建实例的变量都设为静态
单例模式的两种实现方式
饿汉式
class Singleton {
// 私有化构造器
private Singleton(){}
// 内部创建实例,设为static
private static Singleton singLeton = new Singleton();
// 通过公共的静态方法 返回当前类的对象
public static Singleton getInstance(){
return singleton;
}
}
特点:
- 立即加载,使用类时,对象已经创建完成
- 没有多线程安全问题,实现起来简单
- 耗费内存,类加载时,静态变量被创建并且分配内存空间,此后一直占用这块内存空间,直到类销毁 / 卸载
懒汉式
class Singleton{
// 私有化构造器
private Singleton(){}
// 内部提供实例,设为static
private static Singleton singLeton;
public static Singleton getInstance(){
if(singLeton==null) {
singLeton = new Singleton;
}
return singLeton;
}
}
特点:
- 延迟加载,调用类的静态方法时对象才创建
- 节省内存,在调用方法才创建且分配空间,一定程度上节省了内存空间
- 线程不安全,不能保证单例的唯一性,在多线程环境下这种实现方法是错误的
应用场景
- Windows 的 Task Manager (任务管理器)就是很典型的单例模式
- Windows 的 Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收 站一直维护着仅有的一个实例
- •Application 也是单例的典型应用
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直 处于打开状态,因为只能有一个实例去操作,否则内容不好追加
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
理解 main 方法的语法
public static void main(String[] args){}
- JVM 要调用 main 方法 ,权限修饰符为 public
- 执行 main 方法时不需要创建对象所以用 static 修饰
- 接收一个 String 类型的数组参数,数组中保存执行 Java 命令时传递给类的参数
例如
public class mainTest {
public static void main(String[] args){
for(int i = 0; i< args.length; i++){
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
命令行执行
java mainTest "fantasy" "jay" "yxl"
输出结果
args[0] = fantasy args[1] = jay args[2] = yxl
代码块
代码块的作用:对 java 类或对象初始化
一个类中代码块若有修饰符,则只能被 static 修饰,称为静态代码块(static block),没有修饰的是非静态代码块
静态代码块
public class Cat {
private String name;
private static int leg;
static {
leg = 4;
System.out.printIn("静态代码块");
}
}
总结:
- 可以有输出语句
- 可以对类的属性、声明进行初始化操作
- 不可以调用非静态方法和属性
- 如果有多个静态代码块,按从上往下顺序执行
- 执行顺序优先于非静态代码块
- 随着类的加载而加载,只执行一次
非静态代码块
意义:如果有多个构造器重载,而且里面有公共代码,即可提取到 非静态代码块里,减少冗余代码,因为优先于构造执行的
public class User {
private String userName;
private String passWord;
private long registrationTime;
{
// 创建 User 对象都会执行代码块里的代码
System.out.printIn("新用户注册");
registrationTime = System.currentTimeMillis();
}
public User(){
userName = registrationTime+"";
passWord = "123456";
}
public User(String userName, String passWord){
this.userName = userName;
this.passWord = passWord;
}
}
特点:
- 可以有输出语句
- 可以对类的属性、类的声明进行初始化操作
- 除了调用非静态的结构外,还可以调用静态的变量或方法
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行
- 每次创建对象的时候,都会执行一次。且先于构造器执行
final 关键字
final 表示最终的不可更改的
final 修饰类
表示类不可继承,没有子类,提高安全性,程序的可读性
例如:String 类、System 类、StringBuffer 类
final 修饰变量和方法
方法:表示方法不可以被子类重写,例如,Object 类中的 getClass()
变量:表示赋值后不可更改,即常量,变量名通常大写字母表示,例如,final double MY_PI = 3.14
注意:如果某个成员变量用 final 修饰后,没有 set 方法,并且必须初始化(可以显式 赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
abstract 关键字
类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
抽象方法
在父类声明的方法时没有方法体,只有方法签名的方法就是抽象方法
包含抽象方法的类必须是抽象类
使用 abstract
修饰的类 / 方法 就是抽象类 / 方法
例如
public abstract class Person{
public abstract void eat();
}
抽象方法的实现:子类继承抽象类重写类中的抽象方法
public class Student extends Person {
public void eat(){
System.out.printIn("吃饱喝好!!!");
}
}
总结:
-
抽象类是不能创建对象的,是用来被子类继承的
-
抽象类的子类必须重写全部抽象方法,并且提供方法体
-
抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的
-
抽象类中不一定有抽象方法,但有抽象方法必须是抽象类
interface 接口
接口就是规范,定义的是一组规则,体现了现实世界中 “如果你是/要…则必须 能…” 的思想。继承是一个 “是不是” 的 is-a 关系,而接口实现则是 “能不能” 的 has-a 关系
接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大 家都要遵守
定义格式
关键字 interface
,与定义类的方式相同,它并不是类,而是另外一种引用数据类型
public interface Student {
// 静态常量 通常采用大写
// public static final 可以省略
long CURRENT_TASK = "学习";
// 抽象方法
// public abstract 可以省略
void wirte();
void read();
// 默认方法 JDK8
default void attendClass() {
System.out.printIn("学生需要上课");
}
// 静态方法 JDK8
static void eat() {
System.out.printIn("学生需要吃饱喝好");
}
}
实现接口
接口不能创建对象,但是可以被类实现 关键字 implements
类与接口的关系为实现关系,即类实现接口,该类就被称为接口的实现类
public class xm implements Student {
// ...重写接口的抽象方法
}
// 继承 + 实现
public class xh extends Person implements Student {
// ...
}
注意:
- 实现接口的类非抽象类,必须重写接口中的所有抽象方法
- 接口的默认方法,实现类可以选择重写(重写时不用保留 default)
- 接口的静态方法不能重写也不能被继承
接口的多实现
一个类只能继承一个父类,而对于接口而言,一个 类是可以实现多个接口的,这叫做接口的多实现
【修饰符】 class 实现类 implements 接口 1,接口 2,接口 3... {}
【修饰符】 class 实现类 extends 父类 implements 接口 1,接口 2...{}
注:如果抽象方法有重名的只需要重写一次
接口的多继承
一个接口可以继承另一个或者多个接口,接口继承关键字也是 extends
// 父类
public interface Person {
void sleep();
void eat();
}
public interface Motion {
void run();
void basketball();
}
// 子接口
public interface Student extends Person,Motion {
void read();
}
// 实现类
public class xm implements Student {
void sleep(){
System.out.println("睡觉");
}
void eat(){
System.out.println("吃东西");
}
void run(){
System.out.println("跑步");
}
void basketball(){
System.out.println("篮球");
}
void read(){
System.out.println("读书");
}
}
所有父接口的抽象方法都有重写, 方法签名相同的抽象方法只需要实现一次
接口与实现类对象构成多态引用
接口类型的变量与实现类的对象之间,也可以构成多态引用
通过接口类型的变量调用方法,最终执行的是 你 new 的实现类对象实现的方法体
public interface Motion {
void run();
}
public class Cat implements Motion {
public void(){
System.out.println("猫会跑");
}
}
public class Dog implements Motion {
public void(){
System.out.println("狗会跑");
}
}
// 测试
public class TestInterface{
public static void main(String[] args){
Motion c = new Cat();
c.run();
Motion d = new Dog();
d.run;
}
}
接口的非静态方法
-
对于接口的静态方法,直接使用“接口名.”进行调用即可
- 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
-
对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
总结
-
接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类 对象构成多态引用
-
声明接口用 interface,接口的成员声明有限制:
-
公共的静态常量
-
公共的抽象方法
-
公共的默认方法(JDK8.0 及以上)
-
公共的静态方法(JDK8.0 及以上)
-
私有方法(JDK9.0 及以上)
-
-
类可以实现接口,关键字是 implements,而且支持多实现
-
如果实现类不是抽象类, 就必须实现接口中所有的抽象方法
-
如果实现类既要继承父类又要实现父接口,那么 继承(extends)在前,实 现(implements)在后
-
-
接口可以继承接口,关键字是 extends,而且支持多继承
-
接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接 口的默认方法,要去掉 default,子接口重写父接口的默认方法,不要去掉 default
-
接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过 “接口名.静态 方法名” 进行调用
接口和抽象类的对比
内部类
将一个 类A 定义在 另一个类B 里面,类A就被称为内部类,类B为外部类
内部类的分类:
- 成员内部类
- 静态成员内部类
- 非静态成员内部类
- 局部内部类
- 匿名局部内部类
- 非匿名局部内部类
成员内部类
举例
public class Outer {
private static String a ="静态外部类的静态 a";
private String b = "非静态外部类 b"
// 创建静态内部类实例
static class StaticInner {
private static String a ="静态内部类的静态 a";
public static void show(){
System.out.printIn(this.a);
System.out.printIn(Outer.a);
// 不能访问外部类的非静态成员
// System.out.printIn(Outer.b);
}
}
// 创建非静态内部类实例
class NOStaticInner {
private String a ="非静态内部类的非静态 a";
System.out.printIn(this.a);
System.out.printIn(Outer.a);
System.out.printIn(Outer.b);
}
}
// 实例化
public class TestInnerClass {
public static void main(String[] args) {
//创建静态内部类实例
Outer.StaticInner innter = new Outer.StaticInner();
//调用静态内部类静态方法
Outer.StaticInner.show();
// 创建非静态内部类实例
Outer outer = new Outer();
Outer.NoStaticInner inner1 = outer.new NoStaticInner()
}
}
总结:
- 内部类作为类的成员,可以声明为 private 或 protected,调用外部类的结构,声明为 static 的,但此时就不能使用外层类的非 static 的 成员变量
- 内部类作为类的角色,可以定义属性、方法、构造器等结构,继承父类,实现接口,声明为 abstract 抽象类,声明 final
局部内部类
举例:
public class Outer {
public static void outerMethod(){
class Inner{
public void innerMethod(){
System.out.printIn("局部内部类方法")
}
}
Inner inner = new Inner();
inner.innerMethod();
}
}
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 和局部变量一样,有作用域
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、 编号
匿名内部类
举例:使用匿名内部类对象直接调用方法
interface A {
public void show();
}
public class TestInnerClass(){
public static void main(String[] args) {
new hello(){
@Override
public void show(){
System.out.printIn("hello world");
}
}.show();
}
}
举例:通过父类或接口的变量多态引用匿名内部的对象
interface A {
public void show();
}
public class TestInnerClass(){
public static void main(String[] args) {
A a = new A(){
@Override
public void show(){
System.out.printIn("hello world");
}
}
a.show();
}
}
举例:匿名内部类的对象作为实参
interface A {
public void show();
}
public class TestInnerClass(){
public static testMethod(A a){
a.show();
}
public static void main(String[] args) {
testMethod(new A{
@Override
public void show(){
System.out.printIn("hello world");
}
})
}
}
枚举类
枚举类本质上也一个类,只不过类的对象有限,固定好,不能让用户随意更改
enum
关键字声明枚举 ,JDK5.0 之后
举例:方向,上下左右
public enum Direction {
LETF,RIGHT,TOP,BOTTOM;
}
public class TestEnum {
public static void main(String[] args) {
Direction top = Direction.TOP;
}
}
举例:季节,春夏秋冬
public enum Season{
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
总结:
- 枚举类的常量对象列表必须在枚举类的首行,常量规范大写
- 实例系统会自动添加 public static final 修饰
- 常量后面没有代码可以省略
;
,否则必须添加上 - 默认提供的是 private 的无参构造
- 需要有参构造,手动添加,private 可以省略,调用时,常量对象名(实参列表)
- 默认继承的是 java.lang.Enum 类,因此不能再继承其他的类型
- JDK5.0 之后 switch,提供支持枚举类型,case 后面可以写枚举常量名,无需添加枚 举类作为限定
常用方法
String toString()
:默认返回的是常量名(对象名),可以继续手动重写该方法static 枚举类型[] values()
:返回枚举类型对象的数值,为静态方法static 枚举类型 valueOf(String name)
:把一个字符串转为枚举类对应的对象,如果字符串不是,会有运行时异常:IllegalArg umentExceptionString name()
:获取枚举常量的名字int ordinal()
:返回当前枚举常量的次序号,从 0 开始
实现接口
- 如果每一个枚举值都需要执行同一个方法行为,统一实现接口的方法即可
- 如果每一个枚举值都需要执行同一个方法不同行为,则让每个枚举值分别来实现该方法
interface Info{
void show();
}
// 统一实现,直接重写方法即可
enum Direction1 implements Info {
LETF,RIGHT,TOP,BOTTOM;
@Override
public void show() {
System.out.printIn("Direction");
}
}
// 枚举类的每一个对象重写接口中的抽象方法
// 执行的是不同的实现的方法
public enum Direction implements Info{
LEFT{
@Override
public void show() {
System.out.println("左边");
}
},RIGHT {
@Override
public void show() {
System.out.println("右边");
}
},TOP {
@Override
public void show() {
System.out.println("上边");
}
},BOTTOM {
@Override
public void show() {
System.out.println("下边");
}
};
}
注解
注解(Annotation)是从 JDK5.0 开始引入,以
@注解名
在代码中存在
注解可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。可添加一些参数值,这些信息被保存在 Annotation 的 name=value
对中
注解可以在类编译、运行时进行加载,体现不同的功能
常用注解
生成文档相关的注解:
@author
标明开发该类模块的作者,多个作者之间使用,分割@version
标明该类模块的版本@see
参考转向,也就是相关主题@since
从哪个版本开始增加的@param
对方法中某参数的说明,如果没有参数就不能写@return
对方法返回值的说明,如果方法的返回值类型是 void 就不能写@exception
对方法可能抛出的异常进行说明 ,如果方法没有用 throws 显式抛出的 异常就不能写
在编译时进行格式检查(JDK 内置的三个基本注解):
@Override
: 限定重写父类方法,该注解只能用于方法@Deprecated
: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择@SuppressWarnings
: 抑制编译器警告
@Override
- 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误
- 只能标记在方法上
- 它会被编译器程序读取
@Deprecated
- 用于表示被标记的数据已经过时,不推荐使用
- 可以用于修饰 属性、方法、构造、类、包、局部变量、参数
- 它会被编译器程序读取
@SuppressWarnings
- 抑制编译警告。当我们不希望看到警告信息的时候,可以使用 SuppressWarnings 注 解来抑制警告信息
- 可以用于修饰类、属性、方法、构造、局部变量、参数
- 它会被编译器程序读取
元注解
元注解就是注解到其他注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其他的注解上面
JDK1.5 在 java.lang.annotation 包定义了 4 个标准的 meta-annotation 类型,它 们被用来提供对其它 annotation 类型作说明
@Target
用于描述注解的使用范围,通过枚举类型 ElementType 的 10 个常量对象来指定范围
@Retention
用于描述注解的生命周期,通过枚举类型 RetentionPolicy 的 3 个常量对象来指定
SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
@Documented
表明这个注解应该被 javadoc 工具记录
@Inherited
允许子类继承父类中的注解
自定义注解
一个完整的注解应该包含三个部分: (1)声明 (2)使用 (3)读取
【元注解】
【修饰符】 @interface 注解名{
【成员列表】
}
举例
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface myAnnotation {
String value();
}
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String columnName();
String columnType();
}
- 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到 API 文档中。
- Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声
明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String 类型、Class
类型、enum 类型、Annotation 类型、以上所有类型的数组 - 可以使用 default 关键字为抽象方法指定默认返回值
- 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式
是方法名 = 返回值
,如果只有一个抽象方法需要赋值,且方法名为 value,可以省
略value=
,所以如果注解只有一个抽象方法成员,建议使用方法名 value
JUnit 单元测试
JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个测试框架(regression testing framework),供 Java 开发人员编写单元测试之用
导入 JUnit 单元测试后 ,测试代码无需创建 main 方法 ,声明静态方法…,在方法添加 @Test
即可运行此方法
import org.junit.Test;
public class TestJUnit {
@Test
public void test01(){
System.out.println("TestJUnit.test01");
}
@Test
public void test02(){
System.out.println("TestJUnit.test02");
}
}
JUnit4 版本,要求@Test 标记的方法必须满足如下要求:
- 所在的类必须是 public 的,非抽象的,包含唯一的无参构造器
- @Test 标记的方法本身必须是 public,非抽象的,非静态的,void 无返回值,()无参 数的。
包装类
Java 针对八种基本数据类型定义了相应的引用类型:包装类(封装类)
基本数据类型 | 包装类 |
---|---|
byte | Byet |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
除了 int char 其他基本数据类型的包装类都是首字母大写
基本数据类型和包装类转化
基本数据类型转包装类(装箱)
// 构造函数
Integer num1 = new Integer(10);
Double num2 = new Double(10.2);
// 包装类的 valueOf 静态方法
Integer num3 = Integer.valueOf(36);
包装类转基本数据类型(拆箱)
Integer num3 = Integer.valueOf(36);
int num4 = num3.intValue();
Double f2 = Double.valueOf(10.2);
float f3 = f2.floatValue();
自动转化(自动装箱与拆箱)
从 JDK5.0 开始,基本类型与 包装类的装箱、拆箱动作可以自动完成
// 自动装箱
// 相当于 Integer i = Integer.valueOf(4);
Integer i = 10;
// 等号右边:将i对象转成基本数值(自动拆箱)
// 相当于 i.intValue() + 5
// 运算完成后再次装箱,把基本数值转成对象
i = i + 5;
基本数据类型、包装类与字符串间的转换
基本数据类型转为字符串:
int a = 10;
// 方式 1:调用字符串重载的 valueOf()方法
String str = String.valueOf(a);
// 方式 2:运输符拼接字符串
String str = a + "";
字符串转为基本数据类型:
方式 1:除了 Character 类之外,其他所有包装类都具有 parseXxx 静态方法可 以将字符串参数转换为对应的基本类型
String str = "10";
int num = Integer.parseInt(str);
方式 2:字符串转为包装类,然后可以自动拆箱为基本数据类型
String str = "10";
int num = Integer.valueOf(str);
方式 3:通过包装类的构造器实现
int a = new Integer("123");
double d = new Double("123.55");
参考资料