听课笔记
初级部分学习完成
终于完成了Java网络通讯的大作业,将整个项目的大致流程掌握,可以熟练的使用socket套接字进行TCP,UDP的网络编程,但是由于在进行项目的时候没有进行前置知识的学习,导致在多线程,异常处理,IO流的部分仍然无法自己独立完成,从这一课我将开始进入Java编程高级部分。
类变量和类方法
类变量
以下是一个代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.npu.static_;
public class ChildGame { public static void main(String[] args) { int count = 0;
Child child1 = new Child("1"); child1.join(); count++; Child child2 = new Child("2"); child2.join(); count++; Child child3 = new Child("3"); child3.join(); count++;
System.out.println("一共有 " + count + " 小孩"); }
}
class Child { private String name;
public Child(String name) { this.name = name; }
public void join() { System.out.println(this.name + " join"); } }
|
我们会很容易发现这样做count(独立于对象)没有办法在类方法中使用,我们必须要将这个计数的功能单独提取出来,这会弱化代码的可读性(没有面向对象)较为麻烦,这个时候我们就要将count设置为类变量(静态变量)
使用static关键字可以修饰一个可以供所有类同时享用的变量
语法定义:
1 2
| 访问修饰符 static 数据类型 变量名 ; [推荐使用] static 访问修饰符 数据类型 变量名 ;
|
使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.npu.static_;
public class ChildGame { public static void main(String[] args) {
Child child1 = new Child("1"); Child child2 = new Child("2"); Child child3 = new Child("3");
child1.join(); child2.join(); child3.join();
System.out.println("一共有 " + Child.count + " 小孩"); }
}
class Child { private String name; public static int count = 0;
public Child(String name) { this.name = name; }
public void join() { System.out.println(this.name + " join"); count++; } }
|
各个孩子对象实例访问到的count都是同一个,加强count和Child类的联系
类变量在内存中的布局
类变量在不同版本有不同的存放方式,有存放在Class实例尾部中(堆),也有存放在方法区的静态域之中,但是在运用的时候,只需要了解静态变量可以供所有的(同一个类)对象共享,在类加载的时候生成
1 2 3 4 5 6 7 8 9 10 11 12
| package com.npu.static_;
public class VisitStatic { public static void main(String[] args) { System.out.println(A.count); } }
class A { public static int count = 0; }
|
类方法
类方法也叫静态方法
定义形式
1 2
| 访问修饰符 static 数据返回类型 方法名 () {} [推荐使用] static 访问修饰符 数据返回类型 方法名 () {}
|
类方法的调用
1 2
| 类名.类方法名()[推荐使用] 对象名.类方法名()
|
类方法的使用场景
1.同类变量一样,在没有创建对象实例前可以通过类名来调用类方法
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.npu.static_;
public class VisitStatic { public static void main(String[] args) { System.out.print("收钱前:"); Stu.showFee(); Stu tom = new Stu("Tom"); Stu.payFee(100); Stu sony = new Stu("Sony"); Stu.payFee(100); System.out.print("收钱后:"); Stu.showFee(); } }
class Stu { private String name; private static double fee = 0.0;
public Stu(String name) { this.name = name; }
public static void payFee(double fee) { Stu.fee += fee; }
public static void showFee() { System.out.println(fee); } }
|
2.当方法中不涉及任何和对象相关的成员,可以将方法设计成为静态方法,提高开发效率(例如 Math 类),自己制作工具类的时候也推荐这样做
1 2 3 4 5 6 7
| package com.npu.static_;
public class VisitStatic { public static void main(String[] args) { System.out.println(Math.abs(-3)); } }
|
Math源码中的abs静态方法
1 2 3
| public static int abs(int a) { return (a < 0) ? -a : a; }
|
类方法细节
1.类方法会随着类的加载而加载,将结构信息存放在方法区
2.类方法中没有this super参数,不允许使用和对象有关的关键字
3.类方法只能访问类方法类变量,不可以使用普通成员和普通方法
4.普通成员方法可以使用类变量,也可以使用静态方法
main方法
一个常见的main方法
1 2 3 4
| public class Main { public static void main(String[] args) { } }
|
1.main方法由java虚拟机调用,访问权限必须为public
2.java虚拟机在执行main()方法的时候不会创建对象,所以方法由static修饰
3.该方法接收String类型的数组参数,该数组中保存执行java命令时所传递给所有运行类的参数,接收参数
1 2 3 4 5 6 7 8 9
| package com.npu;
public class Main { public static void main(String[] args) { for(int i = 0; i < args.length; i++) { System.out.println("第"+ (i+1) + "个参数" + args[i]); } } }
|
在运用终端java运行class的时候,通过终端传入参数
1
| java 运行类名 第一个参数 第二个参数 第三个参数
|
静态变量和静态方法可以被对应的类直接使用,我们可以在main中使用在类中定义的静态方法和静态变量
1 2 3 4 5 6 7 8 9 10
| package com.npu; public class Main { private static String name = "Hello"; private static void Print() { System.out.println(name); } public static void main(String[] args) { Print(); } }
|
注意这里不可以在main方法中使用非静态的方法变量,要使用的前提是新创建一个main的实例,通过实例对象来进行调用
在IDEA继承的开发环境中可以通过如下设置传递命令行参数

输入对应的参数

运行结果

代码块
代码块又称为初始化块,属于类中的成员是类的一部分,类似于方法,将逻辑语句封装在方法体中,通过{ } 包围起来
和方法不同没有方法名,没有返回没有参数,只有方法体,不能够通过对象或者类显式调用,而是在加载类的时候,或者创建对象的时候隐式的调用
使用基本语法
修饰符可选static修饰,分别称作(static)静态代码块,普通代码块
代码块的使用场景
1.普通代码块相当于另外一种形式的构造器,可以进行初始化的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.npu; public class Main { public static void main(String[] args) {
} }
class Movie { private String title; private String director; private int year;
public Movie(String title, String director, int year) { System.out.println("Movie"); this.title = title; this.director = director; this.year = year; }
public Movie(String title) { System.out.println("Movie"); this.title = title; } }
|
如上的两个构造器都有输出,我们可以把相同的语句放入代码块中,在构造的时候会统一调用代码块(先调用普通代码块,后调用构造器),去除冗余
代码示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.npu; public class Main { public static void main(String[] args) { Movie movie = new Movie("2"); } }
class Movie { private String title; private String director; private int year;
{ System.out.println("Movie"); } public Movie(String title, String director, int year) { this.title = title; this.director = director; this.year = year; }
public Movie(String title) { this.title = title; } }
|
代码块的使用细节
1.静态代码块在类加载的时候执行,只会执行一次,如果是普通代码块每创建一个对象就执行一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.npu;
public class CodeBlock { public static void main(String[] args) { new AA(); } }
class AA { static { System.out.println("AA静态代码块"); } }
|
2.类什么时候被加载(只会加载一次)
创建对象实例的时候
创建子类对象实例父类会被加载
使用类的静态成员变量的时候,代码块被加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.npu;
public class CodeBlock { public static void main(String[] args) { new BB(); } }
class AA { static { System.out.println("AA静态代码块"); } }
class BB extends AA { static { System.out.println("BB静态代码块"); } }
|
先加载父类代码块,在加载子类代码块
3.普通代码块在创建对象实例的时候被隐式调用,创建多少次调用多少次,但是在使用静态成员的时候,普通代码块不会被执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.npu;
public class CodeBlock { public static void main(String[] args) { System.out.println(AA.i); } }
class AA { static { System.out.println("AA静态代码块"); }
{ System.out.println("AA的普通代码块"); }
public static int i; }
|
如上输出为(因为没有创建对象不调用普通代码块)
4.创建一个对象的时候,在一个类的调用顺序是:
1调用静态代码块,和静态属性初始化 优先级一致(和前后顺序有关)
2调用普通代码块和普通属性的初始化
3调用构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.npu;
public class CodeBlock { public static void main(String[] args) { AA a = new AA(); } }
class AA { public static int i = getN1(); private int j = getJ();
public AA(){ System.out.println("构造器"); }
public static int getN1() { System.out.println("静态方法调用"); return 100; }
public int getJ() { System.out.println("普通方法调用"); return 200; }
static { System.out.println("AA静态代码块"); }
{ System.out.println("AA的普通代码块"); } }
|
输出
1 2 3 4 5
| 静态方法调用 AA静态代码块 普通方法调用 AA的普通代码块 构造器
|
5.构造器的最前端隐含了super和普通代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.npu;
public class CodeBlock { public static void main(String[] args) { BB b = new BB(); } }
class AA { { System.out.println("AA普通代码块"); }
public AA () { System.out.println("AA构造器被调用"); } }
class BB extends AA { { System.out.println("BB普通代码块"); }
public BB() { System.out.println("BB构造器被调用"); } }
|
输出
1 2 3 4
| AA普通代码块 AA构造器被调用 BB普通代码块 BB构造器被调用
|
6.静态代码块只能调用静态成员,普通代码块可以调用任意成员
设计模式(单例)
设计模式是在大量的实践和总结下优选的代码结构,编程风格,以及解决问题的思考方式,设计模式类似于经典的棋谱,使用的时候套公式是最好的方式
单例模式:要求采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式有两种:饿汉式,懒汉式
饿汉式
1.将构造器私有化(防止new)
2.类的内部构造对象
3.向外暴露一个静态的公共方法getInstance
以下是代码具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.npu;
public class CodeBlock { public static void main(String[] args) { Game instance = Game.getInstance(); String string = instance.toString(); System.out.println(string); } }
class Game { private String name; private Game (String name) { this.name = name; } private static Game game = new Game("饿汉"); public static Game getInstance() { return game; }
public String toString() { return name; } }
|
就算调用接口再建立一个Game引用指向的也是由静态创建的实例
之所以是饿汉式,是因为如果要调用Game中的静态成员,就会提前自动创建对象实例,可能会造成资源的浪费(对象实例没有被使用)
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.npu;
public class CodeBlock { public static void main(String[] args) { int j = Game.i; } }
class Game { public static int i = 0; private String name; private Game (String name) { System.out.println("构造器被调用"); this.name = name; } private static Game game = new Game("饿汉"); public static Game getInstance() { return game; }
public String toString() { return name; } }
|
构造器被调用,结果输出
懒汉式
事实上是懒汉式的改进
1.将构造器私有化
2.定义一个static静态属性对象(没用new创建实例)
3.提供一个public的static方法,在方法中创建实例
以下是代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.npu;
public class CodeBlock { public static void main(String[] args) { Game instance = Game.getInstance(); String string = instance.toString(); System.out.println(string); } }
class Game { private String name; private Game (String name) { System.out.println("构造器调用"); this.name = name; } private static Game game; public static Game getInstance() { if (game == null) { game = new Game("懒汉"); } return game; }
public String toString() { return name; } }
|
在类加载的时候不会立即创建实例对象,而是通过静态的方法中创建
而通过判断语句可以使得只创建一个实例对象
这样就可以省去单例对象资源的浪费
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.npu;
public class CodeBlock { public static void main(String[] args) { System.out.println(Game.i); } }
class Game { private String name; public static int i = 0; private Game (String name) { System.out.println("构造器调用"); this.name = name; } private static Game game; public static Game getInstance() { if (game == null) { game = new Game("懒汉"); } return game; }
public String toString() { return name; } }
|
补充内容
饿汉式不存在线程安全问题,而懒汉式存在线程安全问题(多个线程同时判断game == null 从而创建多个对象)
final关键字
final可以修饰类,属性,方法,局部变量
使用场景
1.当不希望类被继承的时候使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.npu;
public class final_ { public static void main(String[] args) {
} }
final class A {
}
class B extends A {
}
|
2.当不希望父类的某个方法被子类覆盖(重写),可以用final修饰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.npu;
public class final_ { public static void main(String[] args) {
} }
class A { final public void Print(){ System.out.println(1); } }
class B extends A { @Override public void Print(){ System.out.println(2); } }
|
3.当不希望类的某个值被修改,可以用final修饰
1 2 3 4 5 6 7 8
| package com.npu;
public class final_ { public static final double Pi = 3.14; public static void main(String[] args) { Pi = 9; } }
|
4.当不希望某个局部变量被修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.npu;
public class final_ { public static void main(String[] args) {
} }
class Tool { public void test() { final double i = 2.0; i = 1.0; } }
|
final细节
1.final修饰的属性被称作常量,一般用XX_XX_XX_XX来命名
2.final修饰的属性需要在定义的时候初始化(定义时,构造器,代码块选择其一)
3.如果final修饰的属性使静态的,则初始化的位置只能是在定义时,静态代码块(注意不可以是在构造器中初始化,因为静态变量的赋值在构造器之前)
4.如果不是final类,但是含有final方法,不可以重修final方法,但是类可以被继承
5.一般来说类用final修饰,就没有必要将类中的方法用final修饰了
6.final不能修饰构造器
7.final和static往往搭配使用,效率更高,不会导致类的加载,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.npu;
public class final_ { public static void main(String[] args) { int num = Tool.num; } }
class Tool { public final static int num = 10000; static { System.out.println("static block"); } }
|
编译器优化,不会加载静态代码块
8.包装类例如Integer,Double,Float,Boolean都是final类
今天的内容就此结束,明天将会学习抽象类,接口,内部类