java关键字的使用
Java中的一个关键字——native。
native 关键字在 JDK 源码中很多类中都有,在 Object.java类中,其 getClass() 方法、hashCode()方法、clone() 方法等等都是用 native 关键字修饰的。
native 用来修饰方法,用 native 声明的方法表示告知 JVM 调用,该方法在外部定义,我们可以用任何语言去实现它。 简单地讲,一个native Method就是一个 Java 调用非 Java 代码的接口。
native 语法:
①、修饰方法的位置必须在返回类型之前,和其余的方法控制符前后关系不受限制。
②、不能用 abstract 修饰,也没有方法体,也没有左右大括号。
③、返回值可以是任意类型
我们在日常编程中看到native修饰的方法,只需要知道这个方法的作用是什么,至于别的就不用管了,操作系统会给我们实现。
public final native Class<?> getClass();
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;
private static native void registerNatives();
这是一个本地方法我们知道一个类定义了本地方法后,想要调用操作系统的实现,必须还要装载本地库,但是我们发现在 Object.class 类中具有很多本地方法,但是却没有看到本地库的载入代码。而且这是用 private 关键字声明的,在类外面根本调用不了,我们接着往下看关于这个方法的类似源码:
static {
registerNatives();
}
看到上面的代码,这就明白了吧。静态代码块就是一个类在初始化过程中必定会执行的内容,所以在类加载的时候是会执行该方法的,通过该方法来注册本地方法。
Java中的一个关键字——static 。
static 是Java的一个关键字,可以用来修饰成员变量、修饰成员方法、构造静态代码块、实现静态导报以及实现静态内部类
修饰成员变量
public class Person {
private String name;
private Integer age;
}
1 Person p1 = new Person("Tom",21);
2 Person p2 = new Person("Marry",20);
3 System.out.println(p1.toString());//Person{name='Tom', age=21}
4 System.out.println(p2.toString());//Person{name='Marry', age=20}
我们创建的两个对象 p1 和 p2 存储在堆中,但是其引用地址是存放在栈中的,而且这两个对象的两个变量互相独立,我们修改任何一个对象的属性值,是不改变另外一个对象的属性值的。
public class Person {
private String name;
private static Integer age;
}
1 Person p1 = new Person("Tom",21);
2 Person p2 = new Person("Marry",20);
3 System.out.println(p1.toString());//Person{name='Tom', age=20}
4 System.out.println(p2.toString());//Person{name='Marry', age=20}
p1 对象 age 属性变为 20了
这是因为用在 jvm 的内存构造中,会在堆中开辟一块内存空间,专门用来存储用 static 修饰的成员变量,称为静态存储区,无论我们创建多少个对象,用 static 修饰的成员变量有且只有一份存储在静态存储区中,所以该静态变量的值是以最后创建对象时设置该静态变量的值为准,也就是由于 p1 先设置 age = 21,后来创建了 p2 对象,p2将 age 改为了20,那么该静态存储区的 age 属性值也被修改成了20。
PS:在 JDK1.8 以前,静态存储区是存放在方法区的,而方法区不属于堆,在 JDK1.8 之后,才将方法区干掉了,方法区中的静态存储区改为到堆中存储。
总结:static 修饰的变量被所有对象所共享,在内存中只有一个副本。由于与对象无关,所以我们可以直接通过 类名.静态变量 的方式来直接调用静态变量。对应的非静态变量是对象所拥有的,多少个对象就有多少个非静态变量,各个对象所拥有的副本不受影响。
资源是共享的,所以不用加 static
// private int num = 50;
修饰修饰成员方法
用 static 关键字修饰成员方法也是一样的道理,我们可以直接通过 类名.静态方法名() 的方式来调用,而不用创建对象。
1 public class Person {
2 private String name;
3 private static Integer age;
4
5 public static void printClassName(){
6
7 }
}
Person.printClassName();
静态代码块
用 static 修饰的代码块称为静态代码块,静态代码块可以置于类的任意一个地方(和成员变量成员方法同等地位,不可放入方法中),并且一个类可以有多个静态代码块,在类初次载入内存时加载静态代码块,并且按照声明静态代码块的顺序来加载,且仅加载一次,优先于各种代码块以及构造函数。
1 public class CodeBlock {
2 static{
3 System.out.println("静态代码块");
4 }
5 }
由于静态代码块只在类载入内存时加载一次的特性,我们可以利用静态代码块来优化程序性能,比如某个比较大配置文件需要在创建对象时加载,这时候为了节省内存,我们可以将该配置文件的加载时机放入到静态代码块中,那么我们无论创建多少个对象时,该配置文件也只加载了一次。
执行时机
静态代码块在类被加载的时候就运行了,而且只运行一次,并且优先于各种代码块以及构造函数。如果一个类中有多个静态代码块,会按照书写顺序依次执行。后面在比较的时候会通过具体实例来证明。
静态代码块的作用
一般情况下,如果有些代码需要在项目启动的时候就执行,这时候就需要静态代码块。比如一个项目启动需要加载的很多配置文件等资源,我们就可以都放入静态代码块中。
import java.util.HashMap;
import java.util.Map;
public class ServletNameConfig {
//定义一个 Servlet 配置文件,Map<key,value>
//key:表示 Servlet 的类名
//value:表示 Servlet 类名的全称
public static Map<String, String> servletMap = new HashMap<>();
static {
servletMap.put("UserServlet", "com.my.servlet.UserServlet");
}
}
//根据获取的 Servlet 类名,由配置文件 ServletNameConfig 里面的map 得到 全类名
servletAllName = ServletNameConfig.servletMap.get(servletName);
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<% response.sendRedirect("student/list.do"); %>
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.java1234.model.Student;
@Controller
@RequestMapping("/student")
public class StudentController {
private static List<Student> studentList=new ArrayList<Student>();
static{
studentList.add(new Student(1,"张三",11));
studentList.add(new Student(2,"李四",12));
studentList.add(new Student(3,"王五",13));
}
@RequestMapping("/list")
public ModelAndView list(){
ModelAndView mav=new ModelAndView();
mav.addObject("studentList", studentList);
mav.setViewName("student/list");
return mav;
}
}
<table>
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>操作</th>
</tr>
<c:forEach var="student" items="${studentList }">
<tr>
<td>${student.id }</td>
<td>${student.name }</td>
<td>${student.age }</td>
<td><a href="${pageContext.request.contextPath}/student/preSave.do?id=${student.id}">修改</a></td>
</tr>
</c:forEach>
</table>
静态代码块不能存在任何方法体中
这个应该很好理解,首先我们要明确静态代码块是在类加载的时候就要运行了。我们分情况讨论:
对于普通方法,由于普通方法是通过加载类,然后new出实例化对象,通过对象才能运行这个方法,而静态代码块只需要加载类之后就能运行了。
对于静态方法,在类加载的时候,静态方法也已经加载了,但是我们必须要通过类名或者对象名才能访问,也就是说相比于静态代码块,静态代码块是主动运行的,而静态方法是被动运行的。
不管是哪种方法,我们需要明确静态代码块的存在在类加载的时候就自动运行了,而放在不管是普通方法还是静态方法中,都是不能自动运行的。
静态代码块不能访问普通变量
这个理解思维同上,普通变量只能通过对象来调用,是不能放在静态代码块中的。
构造代码块
在java类中使用{}声明的代码块(和静态代码块的区别是少了static关键字):
构造块 构造块作用就是扩展构造器功能 每次实例化对象都会执行构造块里的内容:
静态代码块,不需要new就可以执行,类加载的时候就会执行静态代码块。类加载的时候工厂只实例化一次
public class CodeBlock {
static{
System.out.println("静态代码块");
}
{
System.out.println("构造代码块");
}
}
执行时机
构造代码块在创建对象时被调用,每次创建对象都会调用一次,但是优先于构造函数执行。需要注意的是,听名字我们就知道,构造代码块不是优先于构造函数执行,而是依托于构造函数,也就是说,如果你不实例化对象,构造代码块是不会执行的。怎么理解呢?我们看看下面这段代码:
如果存在多个构造代码块,则执行顺序按照书写顺序依次执行。
构造代码块的作用
和构造函数的作用类似,都能对对象进行初始化,并且只要创建一个对象,构造代码块都会执行一次。但是反过来,构造函数则不一定每个对象建立时都执行(多个构造函数情况下,建立对象时传入的参数不同则初始化使用对应的构造函数)。
利用每次创建对象的时候都会提前调用一次构造代码块特性,我们可以做诸如统计创建对象的次数等功能。
构造函数
用于创建实例时执行初始化操作,实例化的时候回调用默认的构造方法
1.构造函数的命名必须和类名完全相同。在java中普通函数可以和构造函数同名,但是必须带有返回值;
2.构造函数的功能主要用于在类的对象创建时定义初始化的状态。它没有返回值,也不能用void来修饰。这就保证了它不仅什么也不用自动返回,而且根本不能有任何选择。而其他方法都有返回值,即使是void返回值。尽管方法体本身不会自动返回什么,但仍然可以让它返回一些东西,而这些东西可能是不安全的;
3.构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用;而一般的方法是在程序执行到它的时候被调用的;
4.当定义一个类的时候,通常情况下都会显示该类的构造函数,并在函数中指定初始化的工作也可省略,不过Java编译器会提供一个默认的构造函数.此默认构造函数是不带参数的。而一般的方法不存在这一特点;
普通代码块
普通代码块和构造代码块的区别是,构造代码块是在类中定义的,而普通代码块是在方法体中定义的。且普通代码块的执行顺序和书写顺序一致。
public void sayHello(){
{
System.out.println("普通代码块");
}
}
执行顺序
静态代码块>构造代码块>构造函数>普通代码块
父类和子类执行顺序
对象的初始化顺序:
首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容,当子类的静态内容执行完毕之后,再去看父类有没有构造代码块,如果有就执行父类的构造代码块,父类的构造代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有构造代码块,如果有就执行子类的构造代码块。子类的构造代码块执行完毕再去执行子类的构造方法。
总之一句话,静态代码块内容先执行,接着执行父类构造代码块和构造方法,然后执行子类构造代码块和构造方法。
静态导包
用 static 来修饰成员变量,成员方法,以及静态代码块是最常用的三个功能,静态导包是 JDK1.5以后的新特性,用 import static 包名 来代替传统的 import 包名 方式。那么有什么用呢?
比如我们创建一个数组,然后用 JDK 自带的 Arrays 工具类的 sort 方法来对数组进行排序:
import java.util.Arrays 变为了 import static java.util.Arrays.*,意思是导入 Arrays 类中的所有静态方法,当然你也可以将 * 变为某个方法名,也就是只导入该方法,那么我们在调用该方法时,就可以不带上类名,直接通过方法名来调用(第 11 行代码)。
静态导包只会减少程序员的代码编写量,对于性能是没有任何提升的,反而会降低代码的可读性,所以实际如何使用需要权衡。
静态内部类
首先我们要知道什么是内部类,定义在一个类的内部的类叫内部类,包含内部类的类叫外部类,内部类用 static 修饰便是我们所说的静态内部类。
定义内部类的好处是外部类可以访问内部类的所有方法和属性,包括私有方法和私有属性。
访问普通内部类,我们需要先创建外部类的对象,然后通过外部类名.new 创建内部类的实例。
6 public class OutClass {
7
8 public class InnerClass{
9
10 }
11 }
1 * OuterClass oc = new OuterClass();
2 * OuterClass.InnerClass in = oc.new InnerClass();
访问静态内部类,我们不需要创建外部类的对象,可以直接通过 外部类名.内部类名 来创建实例。
6 public class OutClass {
7
8 public static class InnerClass{
9
10 }
11 }
OuterClass.StaticInnerClass sic = new OuterClass.StaticInnerClass();
①、静态变量能存在于普通方法中吗?
能。很明显,普通方法必须通过对象来调用,静态变量都可以直接通过类名来调用了,更不用说通过对象来调用,所以是可以存在于普通方法中的。
②、静态方法能存在普通变量吗?
不能。因为静态方法可以直接通过类名来直接调用,不用创建对象,而普通变量是必须通过对象来调用的。那么将普通变量放在静态方法中,在直接通过类来调用静态方法时就会报错。所以不能。
③、静态代码块能放在方法体中吗?
不能。首先我们要明确静态代码块是在类加载的时候自动运行的。
普通方法需要我们创建对象,然后手工去调用方法,所静态代码块不能声明在普通方法中。
那么对于用 static 修饰的静态方法呢?同样也是不能的。因为静态方法同样也需要我们手工通过类名来调用,而不是直接在类加载的时候就运行了。
也就是说静态代码块能够自动执行,而不管是普通方法还是静态方法都是需要手工执行的。
④、静态导包会比普通导包消耗更多的性能?
不会。静态导包实际上在编译期间都会被编译器进行处理,将其转换成普通按需导包的形式,所以在程序运行期间是不影响性能的。
⑤、static 可以用来修饰局部变量吗?
不能。不管是在普通方法还是在静态方法中,static 关键字都不能用来修饰局部变量,这是Java的规定。稍微想想也能明白,局部变量的声明周期是随着方法的结束而结束的,因为static 修饰的变量是全局的,不与对象有关的,如果用 static 修饰局部变量容易造成理解上的冲突,所以Java规定 static 关键字不能用来修饰局部变量。
Java中的一个关键字—— final
修饰变量
被 final 修饰的变量不可更改其引用地址,但是可以更改其内部属性。
修饰方法
final 关键字修饰的方法不可被覆盖。
类中所有的 private 方法都隐式指定为 final 的,所以对于 private 方法,我们显式的声明 final 并没有什么效果。但是我们创建一个父类,并在父类中声明一个 private 方法,其子类中是能够重写其父类的private 方法的
修饰类
final 修饰类表示该类不可被继承。
也就是说不希望某个类有子类的时候,用final 关键字来修饰。并且由于是用 final 修饰的类,其类中所有的方法也被隐式的指为 final 方法。
在 JDK 中有个最明显的类 String ,就是用 final 修饰的,将 String 类用 final 修饰很重要的一个原因是常量池。
Java中的一个关键字——this
this 关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。
其实简单来说 this 关键字就是表示当前对象
调用成员变量
1 public void setName(String name){
2 this.name = name;
3 }
this 表示当前对象,也就是调用该方法的对象,对象.name 肯定就是调用的成员变量。
方法内部调用到成员变量的,方法内有个作用域,外部的成员变量作用不到,只有通过this来调用,当前对象的属性
调用构造方法
构造方法是与类同名的一个方法,构造方法没有返回值,但是也不能用 void 来修饰。在一个类中,必须存在一个构造方法,如果没有,编译器会在编译的时候自动为这个类添加一个无参构造方法。一个类能够存在多个构造方法,调用的时候根据参数来区分。
6 public class ThisTest {
7 private String name;
8
9 public ThisTest(){
10 this("Marry");
11 }
12 public ThisTest(String name) {
13 this.name = name;
14 }
15 }
this()调用无参的构造方法
通过 this("Marry") 来调用另外一个构造方法 ThisTest(String name) 来给成员变量初始化赋值。
注意:通过 this 来调用构造方法,只能将这条代码放在构造函数的第一行,这是编译器的规定,如下所示:放在第二行会报错。
调用普通方法
this 表示当前对象,那么肯定能够调用当前类的普通方法。
1 public void printName(){
2 this.say();
3 }
4
5 public void say(){
6 System.out.println("say method...");
7 }
返回当前对象
4 public class ThisTest {
5
6 public Object newObject(){
7 return this;
8 }
9 }
这表示的意思是谁调用 newObject() 方法,那么就返回谁的引用。
Java中的一个关键字——super
调用父类的构造方法
public class Animal {
private String name; // 名字
private int age; // 年龄
/**
* 无参父类构造方法
*/
public Animal(){
System.out.println("无参父类构造方法");
}
/**
* 有参父类构造方法
* @param name 名字
* @param age 年龄
*/
public Animal(String name, int age) {
System.out.println("有参父类构造方法");
this.name = name;
this.age = age;
}
public void say(){
System.out.println("我是一个动物,我叫:"+this.getName()+",我的年龄是:"+this.getAge());
}
}
/**
* 子类无参构造方法
*/
public Cat(){
super();
System.out.println("子类无参构造方法");
}
/**
* 子类有参数构造方法
* @param name
* @param age
*/
public Cat(String name,int age,String address){
super(name,age);
this.address=address;
System.out.println("子类有参数构造方法");
}
/**
* 重写父类的say方法
*/
public void say(){
super.say();
System.out.println("我是一只猫,我叫:"+this.getName()+",我的年龄是:"+this.getAge()+",我的地址是:"+this.getAddress());
}
public static void main(String[] args) {
Cat cat=new Cat("Mini",2,"火星");
/*cat.setName("Mini");
cat.setAge(2);*/
cat.say();
}
super 关键字则是表示 父类对象的引用。
我们分析这句话“父类对象的引用”,那说明我们使用的时候只能在子类中使用,既然是对象的引用,那么我们也可以用来调用成员属性以及成员方法,当然了,这里的 super 关键字还能够调用父类的构造方法。
Java中的继承大家都应该了解,子类继承父类,我们是能够用子类的对象调用父类的属性和方法的,我们知道属性和方法只能够通过对象调用,那么我们可以大胆假设一下:
在创建子类对象的同时,也创建了父类的对象,而创建对象是通过调用构造函数实现的,那么我们在创建子类对象的时候,应该会调用父类的构造方法。
如果声明的类是原始类Object
,那么默认的构造函数有一个空的主体。否则,默认构造函数只是简单地调用没有参数的超类构造函数。
也就是说除了顶级类 Object.class 构造函数没有调用父类的构造方法,其余的所有类都默认在构造函数中调用了父类的构造函数(没有显式声明父类的子类其父类是 Object)。
超类构造函数通过 super 关键字调用,并且是以 super 关键字开头
①、子类默认是通过 super() 调用父类的无参构造方法,如果父类显示声明了一个有参构造方法,而没有声明无参构造方法,实例化子类是会报错的。
解决办法就是通过 super 关键字调用父类的有参构造方法:
1 public class Parent {
2
3 public Parent(String name){
4 System.out.println("父类有参构造方法");
5 }
6 }
7
1 public class Son extends Parent {
2
3 public Son(){
4 super("Tom");
5 System.out.println("子类默认无参构造方法");
6 }
7
8 public static void main(String[] args) {
9 Son son = new Son();
10 }
11
12 }
调用父类的成员属性
1 public class Parent {
2 public String name;
3
4 public Parent(){
5 System.out.println("父类默认无参构造方法");
6 }
7 }
8
9 public class Son extends Parent {
10
11 public Son(){
12 System.out.println("子类默认无参构造方法");
13 }
14
15 public void printName(){
16 System.out.println(super.name);
17 }
18
19 }
super.父类属性 通过这种形式来调用父类的属性。
调用父类的方法
1 public class Parent {
2 public String name;
3
4 public Parent(){
5 System.out.println("父类默认无参构造方法");
6 }
7
8 public void setName(String name){
9 this.name = name;
10 }
11 }
12
13 public class Son extends Parent {
14
15 public Son(){
16 super();//1、调用父类构造函数
17 System.out.println("子类默认无参构造方法");
18 }
19
20 public void printName(){
21 super.setName("Tom");//2、调用父类方法
22 System.out.println(super.name);//3、调用父类属性
23 }
24
25 public static void main(String[] args) {
26 Son son = new Son();
27 son.printName();//Tom
28 }
29
30 }