3183 2018-04-03 2020-06-25
前言:就不写了,直接看吧。
一、基本概念
1、形参和实参
在Java中,定义一个方法的语法格式如下
- 【修饰符】 返回值类型 方法名(【形参1, 形参2, … 形参n】){ 方法体 }
而调用一个方法的语法格式如下
- 方法名(【实参1, 实参2, … 实参n】)
实参和形参的传递广义上上包含传值和传址两种方式,有几点需要注意:
- 实参要和形参参数类型、数量相对应(符合自动转换机制也可)。
- 传值方式是将实参的值复制一份传递给形参(当实参是基本数据类型时),方法调用时,形参值的改变不会影响实参值。
- 传址方式是指将实参的地址传递个形参(当实参是非基本数据类型时),即形参和实参指向同一内存空间,形参值的改变会引起实参的同样改变(后面有更深入的讨论)。
2、延伸至数组
看代码
static class Test {
int value;
Test(int value) {
this.value = value;
}
}
static final class Test1 {
Integer value;
public Test1(int value) {
this.value = value;
}
}
public static void main(String[] args) {
int a = 1, b = 2, c = 3, d = 4;
int[] test = {a, b, c, d};
test[0] += 1;
System.out.print(test[0] + " ");
System.out.println(a);// 2 1
String aa = "1", bb = "2", cc = "3";
String[] test1 = {aa, bb, cc};
test1[0] = "11";// 想当于new了一个对象
System.out.print(test1[0] + " ");
System.out.println(aa);// 11 1
Test aaa = new Test(1), bbb = new Test(2), ccc = new Test(3);
Test[] test2 = {aaa, bbb, ccc};
test2[0].value += 1;
System.out.print(test2[0].value + " ");
System.out.println(aaa.value);// 2 2
Test1 aaaa = new Test1(1), bbbb = new Test1(2), cccc = new Test1(3);
Test1[] test3 = {aaaa, bbbb, cccc};
test3[0] = new Test1(11);
System.out.print(test3[0].value + " ");
System.out.println(aaaa.value);// 11 1同String
Integer temp1 = new Integer(10);
Integer temp2 = new Integer(20);
test3[1].value = temp1;
temp1 = temp2;
System.out.print(test3[1].value + " ");
System.out.println(temp1);// 10 20这一步需要好好理解
}
3、值传递(常考)
在程序设计语言中,将参数传递分为按值调用和按引用调用。按值调用:表示方法接收的是调用者提供的值。而按引用调用表示方法接收的是调用者提供的变量地址。 一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
Java总是采用按值调用。方法得到的是所有参数值的一个拷贝,特别的,方法不能修改传递给它的任何参数变量的内容。
public class Example {
String str = new String("good");
char[] ch = { 'a', 'b', 'c' };
public static void main(String[] args) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.print(ex.ch);
}
public void change(String[] str, char[] ch) {
str = "test ok";
ch[0] = 'g';
}
// 同理,如果传入int i, i = i + 1或i = 0,也不会生效,原因见下
}
按照前面的逻辑,String是传址,char数组也是传址,结果应该是“test ok and gbc”,但结果却是“good and gbc”。这里引用一下别人的,我觉得他讲得很清楚了。如下:
“大家可能以为Java中String和数组都是对象所以肯定是对象引用,然后就会选D,其实这是个很大的误区:因为在java里没有引用传递,只有值传递。这个值指的是实参的地址的拷贝,得到这个拷贝地址后,你可以通过它修改这个地址的内容(引用不变),因为此时这个内容的地址和原地址是同一地址,但是你不能改变这个地址本身使其重新引用其它的对象,也就是值传递”。
这篇写得很不错。总结一下Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型和布尔型)
- 一个方法可以改变一个对象参数的状态
- 一个方法不能让对象参数引用一个新的对象
这里再补充一个例子
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
List<String> list1 = exchange(list);
System.out.println(list.size());
System.out.println(list1.size());
}
public static List<String> exchange(List<String> list) {
list.add("C");
list.add("D");
list = new ArrayList<>();
list.add("E");
list.add("F");
return list;
}
// 结果为4 2
4、访问权限
具体如下
~ | 同一个类 | 同一个包 | 不同包子类 | 不同包的非子类 |
---|---|---|---|---|
private | √ | |||
无修饰符 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
5、重载
Java中方法的重载机制是匹配方法名和匹配参数特征(参数个数或参数类型不同),与方法的返回值无关(尽管不能定义两个仅返回值不同的函数)。
6、继承
子类拥有父类非private的属性,方法。
7、构造方法
使用构造方法时,需要注意以下几点:
- 一个构造方法调用另一构造方法,只能使用this,只能位于第一句,且只能出现一次。
- 不能在一个类的两个构造方法中使用this相互调用。
- 当父类没有显式声明无参构造方法,且显式声明有参构造方法,那么子类构造方法中第一行需显式调用父类的构造方法(子类实例化时,会先实例化父类,默认是父类的无参构造 )。
8、内部类
内部类分为:成员内部类、局部内部类、匿名内部类、静态内部类。其中
- 成员内部类可以访问外部类的私有变量和方法,修饰符可以为public,实例化时是外部类.内部类 x= new 外部类实例化对象.new 内部类。
- 局部内部类等同于局部变量,但不能拥有修饰符(如public、protected、private、static)。
- 匿名内部类也可以访问外部类的私有变量和方法。
- 静态内部类的实例化不依赖外部类的实例化,它的实例化方式是外部类.内部类 x = new 外部类.内部类。
下面是一段测试代码
public class InnerClassTest {
public void test () {
class TempClass {
}
}
public void testAnno() {
new Thread(new Runnable() {
@Override
public void run() {
}
});
}
static class StaticClass {
}
class MemberlClass {
}
public static void main(String[] args) {
new InnerClassTest().test();
}
}
运行一次后,编译目录下有如下相关.class文件:
- InnerClassTest$1TempClass.class(局部内部类)
- InnerClassTest$1.class(匿名内部类)
- InnerClassTest$MemberlClass(成员内部类)
- InnerClassTest$StaticClass(静态内部类)
- InnerClassTest(原始类)
值得一提的是,使用javap -verbose是找不到第一项和第二项的,第三项和第四项虽然找到了,但也是转到了第五项。
二、关键字
1、break和continue
break语句和continue语句都只作用于当前循环语句。对于嵌套循环,如果break和continue包含在内层循环中,仅对内层循环有效,而对外层循环无效。外层循环同理。
2、static
static关键字用来修饰类的成员,如成员变量、成员方法及代码块,分别称为静态成员变量(或类成员变量)、静态成员方法(或类成员方法)、静态代码块。
- 被static修饰的类成员,都会在类加载时,自动开辟属于自己的内存空间,而无需等到类实例化时才开辟空间。
- static方法无法访问一般的成员变量、方法,原因是一般的成员变量、方法需等到类实例化时才开辟内存空间。
- 由于类只加载一次,因此静态代码块里面的代码只会被执行一次。
- 普通代码块与构造器是绑定的,且普通代码块会在构造器之前执行。
3、abstract
- 抽象类不一定有抽象方法,但是有抽象方法就一定是抽象类或接口。
- 抽象方法默认的修饰符是public,不能为private,方法没有方法体(没有大括号)。
- 子类必须实现父类的抽象方法。
4、interface
- 接口的方法默认是public abstract,接口修饰符必须为public(默认),方法没有方法体。
- 接口中不可以定义变量,即只能定义常量(加上final修饰就会变成常量)。所以接口的属性默认是public static final常量(可以不显式声明修饰符,且只能是public),必须赋初始值。
- 接口只是对一类事物的属性和行为更高层次的抽象。对修改关闭,对扩展(不同的实现 implements)开放,接口是对开闭原则的一种体现。
5、transient
- transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
- 一个静态变量不管是否被transient修饰,均不能被序列化。
三、进阶概念
1、类加载(常考)
通过两个例子来说明,如下
class HelloA {
public HelloA() {
System.out.println("HelloA");
}
{ System.out.println("I'm A class"); }
static { System.out.println("static A"); }
}
public class HelloB extends HelloA {
public HelloB() {
System.out.println("HelloB");
}
{ System.out.println("I'm B class"); }
static { System.out.println("static B"); }
public static void main(String[] args) {
new HelloB();
}
}
输出结果如下
static A
static B
I'm A class
HelloA
I'm B class
HelloB
第二个例子更加容易理解一些。
class HelloA {
public HelloA() {
System.out.println("HelloA");
}
{ System.out.println("I'm A class"); }
static { System.out.println("static A"); }
}
public class HelloB extends HelloA {
public HelloB() {
System.out.println("HelloB");
}
{ System.out.println("I'm B class"); }
static { System.out.println("static B"); }
public static void main(String[] args) {
System.out.println("-------main start-------");
new HelloB();
new HelloB();
System.out.println("-------main end-------");
}
}
输出结果如下
static A
static B
-------main start-------
I'm A class
HelloA
I'm B class
HelloB
I'm A class
HelloA
I'm B class
HelloB
-------main end-------
//(本例中,main方法是子类的一个方法)
这题考查了静态语句块、构造语句块/普通代码块(就是只有大括号的那块)以及构造函数的执行顺序,即一个对象的初始化顺序。下面是总结:
- 首先加载包含main方法的那个类,这里就是HelloB。
- 由于HelloB的父类HelloA并没有加载,所以先要加载HelloA。
- 首先从上往下执行static语句代码,当static语句执行完之后,即类已被加载(注意一个类只会被加载一次)。
- 当调用构造方法时,将依次执行构造代码块、构造器(两者可以说绑定在一起)。
这里再补充一个例子
public class Main {
int a = 1;
static int b = 2;
B ab = new B();
static {
System.out.println("执行静态代码,b=" + ++b);
}
{
System.out.println("执行构造代码块,a=" + ++a + ",b=" + ++b);
}
public Main() {
System.out.println("执行构造方法,a=" + ++a + ",b=" + ++b);
}
static class A {
static {
System.out.println("初始化A");
}
}
static class B extends A {
static {
System.out.println("初始化B");
}
}
public static void main(String[] args) {
new Main();
new Main();
}
}
// 下面为结果
执行静态代码,b=3
初始化A
初始化B
执行构造代码块,a=2,b=4
执行构造方法,a=3,b=5
执行构造代码块,a=2,b=6
执行构造方法,a=3,b=7
2、反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
- 获取Class对象
获取Class对象有下面几种:class.getClass()、Class.forName(str)、class.getClassLoader.loadClass(str)。
- 后两者的区别
这一点比较容易混淆,Class.forName()会触发类加载的初始化阶段,而class.getClassLoader.loadClass(str)不会触发类的初始化,只是把连接阶段完成。 可以参考下面的示意图
下面是一些测试代码
static class A {
static {
System.out.println("A static");
}
}
static class B {
static {
System.out.println("B static");
}
}
private static void testClassLoad() throws Exception {
Class a = Class.forName("site.xiaokui.common.hk.basic.ClassLoader$A");
System.out.println("class a init");
Class b = a.getClassLoader().loadClass("site.xiaokui.common.hk.basic.ClassLoader$B");
System.out.println("class b not init");
b.newInstance();
System.out.println("class b init");
}
// 下面是输出
A static
class a init
class b not init
B static
class b init
下面是一些常用的反射代码
static class Father1 {
private int father = 0;
protected int father1 = 1;
int father2 = 2;
public int father3 = 3;
private void father() {
}
void father1 () {
}
protected void father2() {
}
public void father3() {
}
}
static class Son1 extends Father1 {
private int son = 0;
protected int son1 = 1;
int son2 = 2;
public int son3 = 3;
private void son() {
}
void son1() {
}
protected void son2() {
}
public void son3() {
}
}
public static void testReflect() {
Class<?> cls = Son1.class;
Field[] fields = cls.getDeclaredFields();
System.out.println("本类声明的所有字段(包含private)");
for (Field f : fields) {
System.out.println(f);
}
System.out.println("本类及从父类继承的字段(只显示public)");
fields = cls.getFields();
for (Field f : fields) {
System.out.println(f);
}
System.out.println("本类声明的所有方法(包含private)");
Method[] methods = cls.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
}
System.out.println("本类及从父类继承的方法(只显示public)");
methods = cls.getMethods();
for (Method m : methods) {
System.out.println(m);
}
}
对于平时反射用得多的情况下,熟悉一下反射的使用是很有必要的。
总访问次数: 255次, 一般般帅 创建于 2018-04-03, 最后更新于 2020-06-25
欢迎关注微信公众号,第一时间掌握最新动态!