面向对象编程(高级)
一 static关键字
如果想让一个成员变量被类的所有实例所共享,就用static修饰即可,称为类变量(或类属性)!
1 static关键字
-
static:静态的
-
使用范围:
- 在Java类中,可用static修饰属性、方法、代码块、内部类
-
被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
2 静态变量
2.1 语法格式
使用static修饰的成员变量就是静态变量(或类变量、类属性)
[修饰符] class 类{
[其他修饰符] static 数据类型 变量名;
}
2.2 静态变量的特点
-
静态变量的默认值规则和实例变量一样。
-
静态变量值是所有对象共享。
-
静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
-
如果权限修饰符允许,在其他类中可以通过“
类名.静态变量
”直接访问,也可以通过“对象.静态变量
”的方式访问(但是更推荐使用类名.静态变量的方式)。 -
静态变量的get/set方法也静态的,当局部变量与静态变量
重名时
,使用“类名.静态变量
”进行区分。
2.3 对比静态变量与实例变量
(1)个数
静态变量:在内存空间中只有一份,被类的多个对象所共享
实例变量:类的每一个实例(或对象)都保存着一份实例变量
(2)内存位置
静态变量:jdk6之前,存放在方法区。jdk7及之后,存放在堆空间
实例变量:存放在堆空间的对象实体中。
(3)加载时机
静态变量:随着类的加载而加载,由于类只会加载一次,所以静态对象也只有一份。
实例变量:随着对象的创建而加载,每个对象拥有一份实例对象。
(4)调用者
静态变量:可以被类直接调用,也可以使用对象调用
实例变量:只能使用对象调用
(5)消亡时机
静态变量:随着类的卸载而消亡
实例变量:随着对象的消亡而消亡
- 4 举例
package com.lq.Javase.entity.test2;
public class Chinese {
String name;//实例变量
int age;//实例变量
static String nation;//静态变量
public Chinese(){
}
public Chinese(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.lq.Javase.entity.test2;
import java.util.concurrent.Callable;
public class ChineseTest {
public static void main(String[] args) {
Chinese c1 = new Chinese("Tom",19);
c1.nation = "中国";
System.out.println(c1.toString());
System.out.println(c1.nation);//中国
Chinese c2 = new Chinese("Lily",23);
System.out.println(c2.toString());
System.out.println(c2.nation);//中国
c1.nation = "美国";
System.out.println(c1.nation);//美国
System.out.println(c2.nation);//美国
}
}
3 静态方法
3.1 语法格式
用static修饰的成员方法就是静态方法
[修饰符] class 类{
[其他修饰符] static 返回值类型 方法名(形参列表){
方法体
}
}
3.2 静态方法的特点
- 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
- 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
- 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
- 补充:在类的非静态方法中,可以调用当前类中的静态结构(属性、方法)或非静态结构(属性、方法)。
- 静态方法可以被子类继承,但不能被子类重写。
- 静态方法的调用都只看编译时类型。
- 因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。
3.3 开发中,什么时候需要将属性声明为静态的?
- 判断当前类的多个实例是否能共享此成员变量,且此成员变量的值是相同的。
- 开发中,常将一些常量声明为静态的,比如:Math类中的PI
3.4 开发中,什么时候需要将方法声明为静态的?
- 方法内操作的变量如果都是静态的的话,则此方法声明为静态的。
- 开发中,常将工具类声明为静态的,比如Array类、Math类。
3.5 举例
package com.lq.Javase.entity.test2;
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
System.out.println(c1);
System.out.println(c2);
Circle c3 = new Circle(5.6);
System.out.println(c3);
System.out.println(Circle.total);//类直接调用静态方法
}
}
package com.lq.Javase.entity.test2;
public class Circle {
double radius;
int id;
static int total;//创建Circle的个数
public Circle(){
this.id = init;
init++;
total++;
}
public Circle(double radius){
this();
this.radius = radius;
}
private static int init = 1001;//自动给id赋值的基数
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
", id=" + id +
'}';
}
}
二 单例(Singleton)设计模式
1 何为单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
2 实现思路
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
。
3 单例模式的两种实现方式
饿汉式
package com.lq.Javase.entity.test2;
public class BankTest {
public static void main(String[] args) {
Bank b1 = Bank.getInstance();
Bank b2 = Bank.getInstance();
System.out.println(b1 == b2);
}
}
package com.lq.Javase.entity.test2;
//饿汉式
public class Bank {
//1 类的构造器私有化
private Bank(){
}
//2 在类的内部创建当前类的实例
//4 此属性也必须声明为static
private static Bank instance = new Bank();
//3 使用getXxx()方法获取当前类的实例,必须声明为static
public static Bank getInstance() {
return instance;
}
}
懒汉式
package com.lq.Javase.entity.test2;
public class BoyFriendTest {
public static void main(String[] args) {
BoyFriend b1 = BoyFriend.getInstance();
BoyFriend b2 = BoyFriend.getInstance();
System.out.println(b1 == b2);//true
}
}
package com.lq.Javase.entity.test2;
//懒汉式
public class BoyFriend {
//1 类的构造器私有化
private BoyFriend(){
}
//2声明当前类的实例
//4 此属性也必须声明为static
private static BoyFriend instance = null;
//3使用getXxx()方法获取当前类的实例,必须声明为static
public static BoyFriend getInstance() {
if(instance == null){
instance = new BoyFriend();
}
return instance;
}
}
3.3 饿汉式 vs 懒汉式
饿汉式:
- 特点:
立即加载
,即在使用类的时候已经将对象创建完毕。 - 优点:实现起来
简单
;由于内存中较早加载,使用更方便、更快;是线程安全的。 - 缺点:当类被加载的时候,会初始化static的实例,静态变量被创建并分配内存空间,从这以后,这个static的实例便一直占着这块内存,直到类被卸载时,静态变量被摧毁,并释放所占有的内存。因此在某些特定条件下会
耗费内存
。
懒汉式:
- 特点:
延迟加载
,即在调用静态方法时实例才被创建。 - 优点:实现起来比较简单;当类被加载的时候,static的实例未被创建并分配内存空间,当静态方法第一次被调用时,初始化实例变量,并分配内存,因此在某些特定条件下会
节约内存
。 - 缺点:在多线程环境中,这种实现方法是完全错误的,
线程不安全
,根本不能保证单例的唯一性。- 说明:在多线程章节,会将懒汉式改造成线程安全的模式。
三 理解main方法的语法
1 main()方法的剖析
public static void main(String[] args){}
2 理解1:看做是一个普通的静态方法。
理解2:看作是程序的入口,格式是固定的。
3 与控制台交互
如何从键盘获取数据?
3.1 使用Scanner
3.2 使用main()的形参进行传值
4 举例
package com.lq.Javase.entity.test2;
public class MainTest {
public static void main(String[] args) {//程序的入口
String[] arr = new String[]{"AA","BB","CC"};
Main.main(arr);
}
}
package com.lq.Javase.entity.test2;
public class Main {
public static void main(String[] args) {//普通静态方法
System.out.print("Main里的main()方法调用");
for(int i = 0;i < args.length;i++){
System.out.print(args[i]);
}
}
}
四 类的成员之四:代码块
1 代码块(或初始化块)的作用
:
- 对Java类或对象进行初始化
2 代码块(或初始化块)的分类
:
-
一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block)
-
没有使用static修饰的,为非静态代码块。
3 静态代码块
如果想要为静态变量初始化,可以直接在静态变量的声明后面直接赋值,也可以使用静态代码块。
3.1 语法格式
在代码块的前面加static,就是静态代码块。
【修饰符】 class 类{
static{
静态代码块
}
}
4 非静态代码块的语法格式
【修饰符】 class 类{
{
非静态代码块
}
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}
5 具体使用
5.1 静态代码块:
> 随着类的加载而执行
> 由于类的加载只会执行一次,进而静态代码块,也只会执行一次
> 作用:用来初始化类的信息
> 内部可以声明变量,调用属性或方法,编写输出语句等操作
> 静态代码块的执行要先于非静态代码块的执行
> 如果声明有多个静态代码块,则按照声明的先后顺序执行
> 静态代码块内部只能调用静态的结构(即静态的属性或方法),不能调用非静态的结构。
5.2 非静态代码块
> 随着对象的创建而执行
> 每创建当前类的一个实例,就会执行一次非静态代码块
> 作用:用来初始化对象的信息
> 内部可以声明变量,调用属性或方法,编写输出语句等操作
> 如果声明有多个非静态代码块,则按照声明的先后顺序执行
> 非静态代码块内部可以调用静态的结构(即静态的属性或方法),也可以调用非静态的结构(即静态的属性或方法)。
6 示例代码
package com.lq.Javase.entity.test2;
public class PersonTest1 {
public static void main(String[] args) {
System.out.println(Person1.info);
Person1 p1 = new Person1();
System.out.println(p1.age);
}
}
package com.lq.Javase.entity.test2;
public class Person1 {
String name;
int age;
static String info = "人也要睡觉";
public void eat(){
System.out.println("人吃饭");
}
public Person1(){
}
//非静态代码块
{
System.out.println("非静态代码块1");
age = 19;
}
//静态代码块
static {
System.out.println("静态代码块1");
System.out.println(info);
}
}
运行效果:
静态代码块1
人也要睡觉
人也要睡觉
非静态代码块1
19
五 实例变量赋值顺序
六 final关键字
1 final的意义
final:最终的,不可更改的
2 final的使用
2.1 final修饰类
表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性。
例如:String类、System类、StringBuffer类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
2.2 final修饰方法
表示这个方法不能被子类重写。
例如:Object类中的getClass()
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
2.3 final修饰变量
final修饰某个变量(成员变量或局部变量),一旦赋值,它的值就不能被修改,即常量,常量名建议使用大写字母。
例如:final double MY_PI = 3.14;
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
- 修饰成员变量
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
}
}
- 修饰局部变量
public class TestFinal {
public static void main(String[] args){
final int MIN_SCORE ;
MIN_SCORE = 0;
final int MAX_SCORE = 100;
MAX_SCORE = 200; //非法
}
}
七 抽象类与抽象方法(或abstract关键字)
1 抽象类:被abstract修饰的类。
2 抽象方法:被abstract修饰没有方法体的方法。
3 抽象类的语法格式
[权限修饰符] abstract class 类名{
}
[权限修饰符] abstract class 类名 extends 父类{
}
4 抽象方法的语法格式
[其他修饰符] abstract 返回值类型 方法名([形参列表]);
注意:抽象方法只有方法的声明,没有方法体
5 使用说明
5.1 抽象类不能创建对象(实例化),如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
5.2 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
5.3 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
5.4 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
6 示例代码
package com.lq.Javase.entity.test2;
public class PersonTest2 {
public static void main(String[] args) {
Student s1 = new Student();
s1.eat();
s1.sleep();
// 必须重写抽象父类中Person2所有的抽象方法
// Worker w1 = new Worker() //非法
}
}
package com.lq.Javase.entity.test2;
public abstract class Person2 {//抽象类
String name;
int age;
public Person2(){
}
public abstract void eat();//抽象方法
public abstract void sleep();//抽象方法
}
package com.lq.Javase.entity.test2;
public class Student extends Person2{
public Student(){
}
public void eat(){
System.out.println("学生要多吃有营养的食物");
}
public void sleep(){
System.out.println("学生要有充足的睡眠");
}
}
package com.lq.Javase.entity.test2;
public abstract class Worker extends Person2 {//抽象类
public Worker(){
}
public void eat(){
System.out.println("工人要多吃肉");
}
}
7 注意事项
-
不能用abstract修饰变量、代码块、构造器;
-
不能用abstract修饰私有方法、静态方法、final的方法、final的类。
八 接口(interface)
1 接口的理解:接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大家都要遵守。
2 定义接口的关键字:interface
3 接口内部结构的声明:
3.1 可以声明:
-
属性:必须使用public static final修饰
-
方法:jdk8之前:声明抽象方法,修饰为public abstract
jdk8:声明静态方法、默认方法
jdk9:声明私有方法
3.2 不可以声明:
构造器、代码块等
4 接口与类的关系:实现关系
5 格式:
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
6 说明:
- 类可以实现多个接口
- 类针对于接口的多实现,一定程度上就弥补了类的单继承的局限性
- 类必须将实现的接口中的所有的抽象方法都重写(或实现),方可实例化。否则,此实现类必须声明为抽象类
7 接口与接口的关系:继承关系,且可以多继承
8 接口的多态性:接口名 变量名 = new 实现类对象;
9 区分抽象类和接口
- 共同点:都可以声明抽象方法
- 都不能实例化
- 不同点:抽象类一定有构造器,接口没有构造器
- 类与类之间是继承关系,类与接口之间是实现关系,接口与接口之间是多继承关系。
10 举例
package Test4;
public class EatableTest {
public static void main(String[] args) {
Eatable[] eatables = new Eatable[3];
eatables[0] = new Chinese();//多态性
eatables[1] = new American();
eatables[2] = new Indian();
for(int i = 0;i < eatables.length;i++){
eatables[i].eat();
}
}
}
package Test4;
public interface Eatable {
public abstract void eat();
}
package Test4;
public class Chinese implements Eatable{
public void eat(){
System.out.println("中国人用筷子吃饭");
}
}
package Test4;
public class American implements Eatable{
public void eat(){
System.out.println("美国人用刀叉吃饭");
}
}
package Test4;
public class Indian implements Eatable{
public void eat(){
System.out.println("印度人用手抓着吃饭");
}
}
jdk8和jdk9中接口的新特性
九 类的成员之五 内部类
1 什么是内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass)
,类B则称为外部类(OuterClass)
。
2 内部类的分类
2.1 成员内部类:直接声明在外部类的里面
- 使用static修饰的,静态成员内部类
- 不使用static修饰的,非静态成员内部类
2.2 局部内部类:声明在方法内、构造器内、代码块内的内部类
- 匿名的局部内部类
- 非匿名的局部内部类
3 成员内部类的使用特征,概括来讲有如下两种角色:
- 成员内部类作为
类的成员的角色
:- 和外部类不同,Inner class还可以声明为private或protected;
- 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
- 成员内部类作为
类的角色
:- 可以在内部定义属性、方法、构造器等结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的,表示不能被继承
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意点:
-
外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
-
成员内部类可以直接使用外部类的所有成员,包括私有的数据
-
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
4 示例:
public class PeopleTest {
public static void main(String[] args) {
People.Dog dog = new People.Dog();//静态Dog
dog.eat();
People people = new People();
People.Bord bord = people.new Bord();//非静态Bord
bord.eat();
bord.show("黄鹂鸟");
}
}
public class People {//外部类
String name = "Tom";
int age = 19;
static class Dog{//静态内部类
public void eat(){
System.out.println("狗吃骨头");
}
}
class Bord{//非静态内部类
String name = "啄木鸟";
public void eat(){
System.out.println("鸟吃虫子");
}
public void show(String name){
System.out.println("name=" + name);
System.out.println("age=" + age);
System.out.println("name=" + People.this.name);
System.out.println("name=" + this.name);
}
}
}
十 个最基本的注解
1 @Override
-
用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误!
-
只能标记在方法上。
-
它会被编译器程序读取。
2 @Deprecated
-
用于表示被标记的数据已经过时,不推荐使用。
-
可以用于修饰 属性、方法、构造、类、包、局部变量、参数。
-
它会被编译器程序读取。
3 @SuppressWarnings
-
抑制编译警告。当我们不希望看到警告信息的时候,可以使用 SuppressWarnings 注解来抑制警告信息
-
可以用于修饰类、属性、方法、构造、局部变量、参数
-
它会被编译器程序读取。
-
可以指定的警告类型有(了解)
- all,抑制所有警告
- unchecked,抑制与未检查的作业相关的警告
- unused,抑制与未用的程式码及停用的程式码相关的警告
- deprecation,抑制与淘汰的相关警告
- nls,抑制与非 nls 字串文字相关的警告
- null,抑制与空值分析相关的警告
- rawtypes,抑制与使用 raw 类型相关的警告
- static-access,抑制与静态存取不正确相关的警告
- static-method,抑制与可能宣告为 static 的方法相关的警告
- super,抑制与置换方法相关但不含 super 呼叫的警告
十一 包装类
1 为什么要使用包装类?
为了使得基本数据类型的变量具备引用数据类型变量的相关特性(比如:封装性、继承性、多态性),我们给各个基本数据类型的变量都提供了对应的包装类。
2 基本数据类型对应的包装类类型:
byte->Byte
short->Short
int->Integer
long->Long
float->Float
double->Double
char->Character
boolean->Boolean
3 基本数据类型、包装类与字符串间的转换
(1)基本数据类型转为字符串
**方式1:**调用字符串重载的valueOf()方法
int a = 10;
//String str = a;//错误的
String str = String.valueOf(a);
(2)字符串转为基本数据类型
**方式1:**除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型,例如:
public static int parseInt(String s)
:将字符串参数转换为对应的int基本类型。public static long parseLong(String s)
:将字符串参数转换为对应的long基本类型。public static double parseDouble(String s)
:将字符串参数转换为对应的double基本类型。
**方式2:**字符串转为包装类,然后可以自动拆箱为基本数据类型
public static Integer valueOf(String s)
:将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型public static Long valueOf(String s)
:将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型public static Double valueOf(String s)
:将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型
注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException
异常。
**方式3:**通过包装类的构造器实现
int a = Integer.parseInt("整数的字符串");
double d = Double.parseDouble("小数的字符串");
boolean b = Boolean.parseBoolean("true或false");
int a = Integer.valueOf("整数的字符串");
double d = Double.valueOf("小数的字符串");
boolean b = Boolean.valueOf("true或false");
int i = new Integer(“12”);
(2)字符串转为基本数据类型
**方式1:**除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型,例如:
public static int parseInt(String s)
:将字符串参数转换为对应的int基本类型。public static long parseLong(String s)
:将字符串参数转换为对应的long基本类型。public static double parseDouble(String s)
:将字符串参数转换为对应的double基本类型。
**方式2:**字符串转为包装类,然后可以自动拆箱为基本数据类型
public static Integer valueOf(String s)
:将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型public static Long valueOf(String s)
:将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型public static Double valueOf(String s)
:将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型
注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException
异常。
**方式3:**通过包装类的构造器实现
int a = Integer.parseInt("整数的字符串");
double d = Double.parseDouble("小数的字符串");
boolean b = Boolean.parseBoolean("true或false");
int a = Integer.valueOf("整数的字符串");
double d = Double.valueOf("小数的字符串");
boolean b = Boolean.valueOf("true或false");
int i = new Integer(“12”);