一、基本语法

编写 Java 程序时,应注意以下几点:

  • 大小写敏感:Java 是大小写敏感的,这就意味着标识符 Hello 与 hello 是不同的。
  • 类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass
  • 方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
  • 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java。(如果文件名和类名不相同则会导致编译错误)。
  • 主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。

接口(interface)

在 Java 中,接口可理解为对象间相互通信的协议。接口在继承中扮演着很重要的角色。

接口只定义派生要用到的方法,但是方法的具体实现完全取决于派生类。

二、类与对象

1、构造方法

每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。


public class Puppy{
    public Puppy(){
    }
 
    public Puppy(String name){
        // 这个构造器仅有一个参数:name
    }
}

那么在使用new创建对象的时候,可以选择传入参数或不传入参数

在同一目录下的不同类(文件)可以直接引用

2、import语句

​ 在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。import 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。

import java.io.*
//将会命令编译器载入 java_installation/java/io 路径下的所有类

3、package语句

为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。

包的作用

  • 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  • 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

例如某个Something.java文件:

package net.java.util; //那么它的路径应该是 net/java/util/Something.java 这样保存的。
public class Something{
   ...
}

import 关键字

还可以使用import载入包中的类:

import payroll.Employee;
//路径为 payroll/Employee.java

如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

三、类与对象(反射)

1、类是什么?

万事万物皆是对象

class Foo{}

//foo 是 Foo的实例对象,那么Foo是谁的对象?
Foo foo = new Foo();

//任何一个类都是Class的实例对象,总共有三种表达方式

//Foo 是 Class的对象,c1就是Foo的类类型(class type)
//已知类的情况下,可以得到它的类类型(所有类都有一个隐含的静态成员变量class)
//c1也是Class类的实例对象
Class c1 = Foo.class;

//c2也是Foo的类类型
//已知类的对象,也可以得到它的类类型
Class c2 = foo.getClass();

//c3也是Foo类的类类型
//需要有异常处理
Class c3=null;
try {
	c3=Class.forName("com.my.package1.Foo");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}

//我们完全可以通过类的类类型来创建该类的实例对象
//需要有异常处理
try {
        Foo foo1=(Foo) c1.newInstance(); //使用该方法的前提是需要有无参数的构造方法
        foo1.print();
} catch (InstantiationException e) {
        e.printStackTrace();
} catch (IllegalAccessException e) {
        e.printStackTrace();
}

通过上面的一些方法构造及使用,我们能对类有一个更深的了解

2、动态加载类

有动态加载类,肯定就有静态加载类

静态加载类

静态加载(new)就是在编译时就需要加载完成的类,若是没加载成功,程序就无法运行

//Test.java
class Test{
    public static void main(String[] args){
        if("Word".equals(args[0])){
            Word word=new Word();
            word.start(); //若Word类加载失败,程序报错无法运行(即使Excel类加载成功)
        }
        if("Excel".equals(args[0])){
            Excel excel =new Excel();
            excel.start();
        }
    }
}

若是上述代码后期需要修改,我们就需要找到类一步步去修改,然后再添加相关代码在其中

动态加载类

动态加载可以解决静态加载的问题,除了无法加载的类报错外,其他的代码可以运行

比如:今后我需要对这段代码进行修改,如果使用静态加载的方式,就需要修改和添加相关代码在已完成的文件中(会很麻烦)

动态加载后,我们只需要从外部添加一个类来继承并修改其中的方法就好了

//Test.java
class TestBetter{
    public static void main(String[] args){
        try {
            //动态加载类
            Class c=Class.forName(args[0]);  //args[0]返回命令行输入的字符串,返回输入的类的类类型
            //通过类类型创建对象
            Office oa = (Office)c.newInstance();
            oa.start();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

//Office.java
interface Office{
    public void start();
}

//Hello.java
class Hello implements Office{
    public void start(){
        System.out.println("Hello...start...");
    }
}

Test.java 实现了动态加载类,只有在使用后出现问题才会报错

Office.java 使用了接口,相当于动态加载类的一个过渡,若修改,只需要添加相关方法名就行了

Hello.java 是我们需要的实现功能的类,我们借助接口,添加我们想要的功能

这样一个动态加载类就完成了,后期修改,只需要外部添加一个类(文件)就行了

这就类似于软件更新一样,动态地加载新的东西,而不是在原有的文件上进行修改

image-20220317201000421

3、获取类信息

基本的数据类型

Class c1=int.class;  //int的类类型
Class c2=String.class;  //String类的类类型
Class c3=void.class;  //void的类类型
Class c4=abc.class;  //abc类的类类型

//一个类的类类型就能获得该类的类名
System.out.println(c2.getName()); //java.lang.String
System.out.println(c2.getSimpleName());  //String(不带包名)

Class类的基本API操作

//Test.java
import java.lang.reflect.Method;

public class Test {
    /**
     * 打印所属类(Object)的信息,包括类的成员函数、成员变量
     * @param obj
     */
    public static void printClassMessage(Object obj){
        //首先获取类的类类型(共三种)
        //Foo.class; foo.getClass();  Class.forName("类名");
        Class c=obj.getClass();  //obj(Object) 传递的是哪个子类的对象,c就是哪个子类的类类型

        //获取类的名称
        System.out.println(c.getName());

        //Method是所有方法的对象,一个成员方法就是一个Method对象
        Method[] ms = c.getMethods(); //该方法获取所有public方法,包括从父类继承而来的
        Method[] ms1 = c.getDeclaredMethods();  //获取所有该类自己声明的方法,不问访问权限

        //获取方法
        //该数组存储的是方法的类类型(Method类的对象)
        for(int i=0;i<ms.length;i++){
            //方法返回值类型,得到的是数据类型的类类型
            Class returnType = ms[i].getReturnType();
            System.out.print(returnType.getName()+" ");
            //得到方法名称
            System.out.print(ms[i].getName()+"(");
            //获取参数类型,得到的是参数列表的返回值类型的类类型
            Class[] paramTypes = ms[i].getParameterTypes();
            for (Class class1:paramTypes) {
                System.out.print(class1.getName()+",");
            }
            System.out.println(")");
        }
    }
}
//ClassDemo1.java
public class ClassDemo1 {
    public static void main(String[] args){
        String s = "hello";
        Test.printClassMessage(s); //就可以获得String类的所有方法
    }
}

image-20220318200311464

4、获取成员变量信息

成员变量也是对象:java.lang.reflect.Field

Field类封装了关于成员变量的操作

public static void printClassMessage(Object obj){
    Class c=obj.getClass();
    Field[] fs=c.getFields();  //获取所有public的成员变量的信息
    Field[] fs2=c.getDeclaredFields();  //获取该类自己声明的成员变量的信息
    
    for (Field field : fs ) {
            //成员变量数据类类型
            Class fieldType = field.getType();
            //获取类类型的名字(例如int、String)
            String typeName = fieldType.getName();
            //获取成员变量的名称
            String fieldName = field.getName();
            System.out.println(typeName+" "+fieldName);
        }
获取构造函数的信息

构造函数也是对象

java.lang.Constructor

public static void printConMessage(Object obj){
    Class c = obj.getClass();
    //获取所有public对象的构造函数
    Constructor[] cs = c.getConstructors();
    //获取所有自己声明的构造函数
    Constructor[] cs = c.getDeclaredConstructors();
	for (Constructor constructor:cs
        ) {
        System.out.print(constructor.getName()+"(");
        //获取构造函数的参数列表,返回的是参数数据类型的类类型
        Class[] paramTypes = constructor.getParameterTypes();
        for (Class class1:paramTypes
             ) {
            System.out.print(class1.getName()+",");
        }
        System.out.println(")");
    }
}

image-20220319234113301

想要获取一个类的类信息,就要获取到它的类类型,从而就能获取到所有关于类的信息

5、方法反射的操作

反射可以绕过编译阶段

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        Class c1=a1.getClass();
        /**
         * 获取方法 由名称和参数来决定
         * getMethod 获取public的方法
         * getDeclaredMethod 获取自己声明的方法
         */
        try {
            Method m = c1.getMethod("print", int.class, int.class);
            //Method m = c1.getMethod("print",new Class[]{int.class,int.class});

            //方法的反射操作
            //正常:a1.print(10,30); 方法的反射操作使用m对象来进行方法调用,和a1.print效果相同
            //method.invoke 方法有返回值(调用的方法无返回值返回null,有返回值就返回返回值)
            Object o = m.invoke(a1,10,30);
            //Object o = m.invoke(a1,new Object[]{10,30});
            System.out.println("===========");

            Method m2 = c1.getMethod("print", String.class, String.class);
            //Method m2 = c1.getMethod("print",new Class[]{String.class,String.class});
            o=m2.invoke("print","qwe","qqq");
            //o=m2.invoke("print",new Object[]{"qwe","qqq"});
            System.out.println("=============");

            Method m3=c1.getMethod("print");
            //Method m3=c1.getMethod("print",new Class[]{});
            o=m3.invoke("print");
            //o=m3.invoke("print",new Object[]{});

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class A{
    public void print(int a,int b){
        System.out.println(a+b);
    }

    public void print(String a,String b){
        System.out.println(a.toUpperCase()+","+b.toLowerCase());
    }

    public void print(){
        System.out.println("helloworld");
    }
}

反射对泛型的绕过:

/**
         * 泛型是为了防止错误输入,但是只在编译阶段起作用
         */
        ArrayList list1 = new ArrayList();
        //使用泛型
        ArrayList<String> list2 = new ArrayList<String>();
        //获得集合的类类型
        Class c2 = list1.getClass();
        Class c3 = list2.getClass();

        System.out.println(c2 == c3); //说明编译之后的集合的泛型是去泛型化的
        //利用反射调用集合方法
        try {
            Method m4 = c2.getMethod("add", Object.class);
            m4.invoke(list1,"123");
            System.out.println(list1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            Method m5 = c3.getMethod("add", Object.class);
            m5.invoke(list2,123);  //这里就绕过了泛型的限制
            System.out.println(list2);
        } catch (Exception e) {
            e.printStackTrace();
        }

Java中集合的泛型是防止错误输入的,但是只在编译阶段起作用,绕过编译就无效了,而反射的操作就是在编译之后操作的,可以绕过泛型的限制

6、Class 和 Object 是什么关系?

emmm,看完视频后,在底下评论区看到了这个问题,百度之后,就记录一下。

在实现了Class类时,是继承了Object类,然后编译成.class字节码,这样就能被JVM加载了。

虚拟机加载了所有类,这些类在虚拟机中都能被Class类所描述(所以说它是编译之后的操作)。

三、修饰符

1、访问控制修饰符

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)