关键字:static
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上 的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象, 其方法才可以供外部调用。
我们有时候希望无论是否产生了对象或无论产生了多少 对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个 国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中 都单独分配一个用于代表国家名称的变量。
使用static修饰属性:静态变量
使用范围:
在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点:
随着类的加载而加载
优先于对象存在
修饰的成员,被所有对象所共享
访问权限允许时,可不创建对象,直接被类调用
类中的常量也常常声明为static
由于类只会加载一次,则静态变量在内存也只会存在一份,存放在方法区的静态域中。
属性的分类:按是否使用static修饰,又分为:静态属性和非静态属性(实例变量)。
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性,当修改其中一个对象的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过的
如果想让一个类的所有实例共享数据,就用类变量!
静态结构使用例:
System.out 、Math.PI
使用static修饰方法:静态方法
随着类的加载二加载,可以通过类.静态方法的方式进行调用
静态方法中只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
静态方法中不能使用this和super关键字
类属性、类方法的设计思想
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪 些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
操作静态属性的方法通常设置为静态
工具类的方法习惯上声明为static
测试代码
package com.xxx.java;
public class StaticTest {
public static void main(String[] args) {
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c1.nation = "CHN";
System.out.println(c2.nation);
Chinese.show();
//Chinese.eat();
}
}
class Chinese{
String name;
int age;
static String nation;
public void eat() {
System.out.println("吃中餐");
}
public static void show() {
System.out.println("我是一个中国人");
//eat();
//name = "Tom";
//可以调用静态的结构
nation = "中国";
walk();
}
public static void walk() {
System.out.println("走路");
}
}
ArrayUtil工具类优化
package com.xxx.java;
//自定义数组工具类
public class ArrayUtil {
// 求数组的最大值
public static int getMax(int[] arr) {
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
return max;
}
// 求数组的最小值
public static int getMin(int[] arr) {
int min = arr[0];
for (int i = 0; i < arr.length; i++) {
if (min > arr[i]) {
min = arr[i];
}
}
return min;
}
// 求数组的总和
public static int getSum(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// 求数组的平均值
public static int getAvg(int[] arr) {
return getSum(arr) / arr.length;
}
// 反转数组
public static void reverse(int[] arr) {
for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 复制数组
public static int[] copy(int[] arr) {
int[] arr1 = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
arr1[i] = arr[i];
}
return arr1;
}
// 数组排序
public static void sort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr,j,j+1);
}
}
}
}
// 遍历数组
public static void print(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
// 查找指定元素
public static int getIndex(int[] arr, int dest) {
for (int i = 0; i < arr.length; i++) {
if (dest == arr[i]) {
return i;
}
}
return -1;
}
//交换数组中指定两个位置元素的值
private static void swap(int[]arr,int i,int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
测试
package com.xxx.java;
public class ArrayUtilTest {
public static void main(String[] args) {
//ArrayUtil util = new ArrayUtil();
int[] arr = {32,34,32,5,3,54,654,-98,0,-53,5};
int max = ArrayUtil.getMax(arr);
System.out.println("最大值为:" + max);
// System.out.println("排序前");
// ArrayUtil.print(arr);
//
// ArrayUtil.sort(arr);
// System.out.println();
// System.out.println("排序后");
// ArrayUtil.print(arr);
System.out.print("查找:");
int index = ArrayUtil.getIndex(arr, -5);
if (index >= 0) {
System.out.println(index);
} else {
System.out.println("没找到");
}
}
}
相关应用
package com.xxx.java;
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
Circle c3 = new Circle(3.4);
System.out.println("c1的id为:" + c1.getId());
System.out.println("c2的id为:" + c2.getId());
System.out.println("c3的id为:" + c3.getId());
System.out.println("创建的圆的个数为:" + Circle.getTotal());
}
}
class Circle {
private double radius;
private int id;
private static int total;
private static int init = 1001;
public Circle() {
super();
id = init++;
total++;
}
public Circle(double radius) {
this();
this.radius = radius;
//id = init++;
//total++;
}
public static int getTotal() {
return total;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public double findArea() {
return 3.14 * radius * radius;
}
}
相关练习
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密 码”、“存款余额”、“利率”、“最小余额”,定义封装这些 属性的方法。账号要自动生成。
编写主类,使用银行账户类,输入、输出3个储户的上述信息。 考虑:哪些属性可以设计成static属性。
package com.xxx.exer;
public class Account {
private int id;
private String pwd = "000000";
private double balance;
private static double interestRate;
private static double minMoney;
private static int init;
public Account() {
super();
id = init++;
}
public Account(String pwd, double balance) {
this();
this.pwd = pwd;
this.balance = balance;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
}
public static double getMinMoney() {
return minMoney;
}
public static void setMinMoney(double minMoney) {
Account.minMoney = minMoney;
}
public int getId() {
return id;
}
public double getBalance() {
return balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", pwd=" + pwd + ", balance=" + balance + "]";
}
}
测试
package com.xxx.exer;
public class AccountTest {
public static void main(String[] args) {
Account.setInterestRate(0.012);
Account.setMinMoney(100);
Account acc1 = new Account();
Account acc2 = new Account("114514",2000);
System.out.println(acc1);
System.out.println(acc2);
System.out.println(Account.getInterestRate());
System.out.println(Account.getMinMoney());
}
}
单例 (Singleton)设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对 某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构 造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生 类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无 法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象, 静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象 的变量也必须定义成静态的。
饿汉式:
package com.xxx.java1;
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 = bank2);
}
}
//饿汉式
class Bank{
//私有化构造器
private Bank(){
}
//内部创建类的对象
//要求声明为静态,否则静态方法中无法调用
private static Bank instance = new Bank();
//提供public的方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}
懒汉式:
package com.xxx.java1;
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
//懒汉式
class Order {
//私有化类的构造器
private Order() {
}
//声明当前类对象,不进行初始化
private static Order instance = null;
//声明public static的返回当前类对象的方法
public static Order getInstance() {
//不判断会每次执行时都新生成一个对象实例
if(instance == null) {
instance = new Order();
}
return instance;
}
}
区分饿汉式和懒汉式:
懒汉式:
优点:延迟对象的创建
缺点:线程不安全——到多线程时修改
饿汉式:
优点:饿汉式是线程安全的
缺点:对象加载时间过长
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的 产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
应用场景
网站的计数器,一般也是单例模式实现,否则难以同步。
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
Application 也是单例的典型应用
Windows的Task Manager (任务管理器)就是很典型的单例模式
Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
理解main方法的语法
说明
作为程序的入口
是一个普通的静态方法
public:由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
static:又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的
void:main方法执行完后程序就结束了,不需要返回值
形参:该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行 的类的参数。
形参的使用方式(Eclipse)
1.先run as一遍代码
2.选择Run Configurations
3.左侧选择当前编译好的字节码文件,右侧选择Arguments
在Program arguments框内输入字符串,空格隔开,可以不加双引号。Run运行程序
执行结果如下
相关代码
package com.xxx.java1;
public class MainDemo {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("***********" + args[i]);
}
}
}
若是其他基本数据类型,可以使用包装类的parseXxx方法
package com.xxx.java1;
public class MainTest {
public static void main(String[] args) {
Main.main(new String[100]);
}
}
class Main{
public static void main(String[] args) {
args = new String[100];
for (int i = 0; i < args.length; i++) {
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
形参的使用方式(控制台)
1、先删掉包名,在编译javac xxx
2、java xxx 形参
代码块(或初始化块)
代码块(或初始化块)的作用:对Java类或对象进行初始化。
代码块(或初始化块)的分类:
一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块 (static block),没有使用static修饰的,为非静态代码块。
static代码块通常用于初始化static的属性
静态代码块:
可以有输出语句。
初始化类的信息。
不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
若有多个静态的代码块,那么按照从上到下的顺序依次执行。
静态代码块的执行要先于非静态代码块。
静态代码块随着类的加载而加载,且只执行一次。
非静态代码块:
可以有输出语句。
可以在创建对象时,对对象的属性进行初始化。
除了调用非静态的结构外,还可以调用静态的变量或方法。
若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
每次创建对象的时候,都会执行一次。且先于构造器执行。
代码
package com.xxx.java2;
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
Person.info();
}
}
class Person {
//属性
String name;
int age;
static String desc = "我是一个人";
//构造器
public Person() {
}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
//代码块
static {
System.out.println("hello,static block-1");
desc = "我是一个爱学习的人";
info();
}
static {
System.out.println("hello,static block-2");
}
{
System.out.println("hello,block-1");
age = 1;
}
{
System.out.println("hello,block-2");
}
//方法
public void eat() {
System.out.println("吃饭");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void info() {
System.out.println("我是一个快乐的人");
}
}
使用示例
例1
package com.xxx.java2;
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
//new Leaf();
}
}
解析:
先加载父类和间接父类,然后加载每个类静态代码块即:
加载Object的静态代码块(无)
加载Root的静态代码块
加载Mid的静态代码块
加载Leaf的静态代码块
之后对象创建完毕,先加载父类和间接父类的构造器,而此时非静态代码块先于父类构造器执行:
加载Object的代码块(无),加载Object的构造器(无内容)
加载Root的代码块,加载Root的构造器
加载Mid的代码块,加载Mid的无参构造器,加载Mid的带参构造器
加载Leaf的代码块,加载Leaf的构造器
若再次new Leaf(),静态代码块内容不在执行
例2
package com.xxx.java2;
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
//System.out.println("************************");
//new Son();
//System.out.println("************************");
//new Father();
}
}
解析:
main方法是由当前main所在的类通过类.main调用的,所有先初始化当前类以及他的父类和间接父类
当前main方法在Son中,由Son.main()调用,要初始化Son就要先初始化Son的父类和间接父类
初始化Object
初始化Father(静态代码块)
初始化Son(静态代码块)
后续执行同例1
结果为:1 4 7 * 2 3 5 6 * 2 3 5 6 * 2 3
对属性的赋值顺序
1.默认初始化
2.显式初始化 / 3在代码块中赋值
4构造器中初始化
5对象.属性或对象.方法的方式赋值
执行顺序1——2 / 3——4——5
package com.xxx.java2;
public class OrderTest {
public static void main(String[] args) {
Order o = new Order();
System.out.println(o.orderId);
}
}
class Order{
// int orderId = 3;
// {
// orderId = 4;
// }
{
orderId = 4;
}
int orderId = 3;
}
关键字:native
native关键字声明调用底层c或c++的方法
关键字:final
在Java中声明类、变量和方法时,可使用关键字final来修饰。
final标记的类不能被继承。
内置的final类:String、System、StringBuffer
final标记的方法不能被子类重写。
内置的funal方法:Object的getClass()
final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
final修饰属性,可以赋值的位置有
显式初始化、在代码块中赋值、在构造器中赋值
final修饰局部变量:为常量,无法修改
修饰形参:表示此形参是一个常量,只能在方法内使用该形参,不能对其进行修改
static final:修饰属性 全局常量
package com.xxx.java2;
public class FinalTest {
// final int UP; 无法默认初始化赋值
final int WIDTH = 10; // 可以显式初始化赋值
// 可以在代码块内赋值
final int LEFT;
{
LEFT = 1;
}
// 可以在构造器中赋值,但每个构造器中都必须赋值
final int RIGHT;
public FinalTest() {
RIGHT = 2;
}
public FinalTest(int n) {
RIGHT = n;
}
//不可以在方法中赋值
// final int DOWN;
//
// public int setDOWN(int down) {
// DOWN = down;
// }
// public void doWidth() {
// WIDTH = 20;
// }
public void show() {
final int NUM = 10; //常量
//NUM += 1;
}
public void show(final int num) {
//num = 5;
System.out.println(num);
}
public static void main(String[] args) {
FinalTest test = new FinalTest();
//test.setDOWN(5);
test.show(10);
}
}
final class FinalA {
}
//class B extends FinalA{
//
//}
//class C extends String{
//
//}
class AA {
public final void name() {
}
}
class BB extends AA {
// public void name() {
//
// }
}
练习:
1
public class Something {
public int addOne(final int x) {
return ++x; //不行
// return x + 1; //可以,x本身没有变
}
}
2
public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
// o = new Other(); //不行,地址变了
o.i++; //可以,地址没有变
}
}
class Other {
public int i;
}